Merge "Cleanup testOverrideVendorId static member after running unittest."
diff --git a/apishim/29/com/android/networkstack/apishim/api29/CaptivePortalDataShimImpl.java b/apishim/29/com/android/networkstack/apishim/api29/CaptivePortalDataShimImpl.java
index 681ba1a..42216a9 100644
--- a/apishim/29/com/android/networkstack/apishim/api29/CaptivePortalDataShimImpl.java
+++ b/apishim/29/com/android/networkstack/apishim/api29/CaptivePortalDataShimImpl.java
@@ -16,6 +16,8 @@
package com.android.networkstack.apishim.api29;
+import android.net.CaptivePortalData;
+
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
@@ -48,8 +50,25 @@
throw new UnsupportedApiLevelException("CaptivePortalData not supported on API 29");
}
+ @Override
+ public String getVenueFriendlyName() {
+ // Not supported in API level 29
+ return null;
+ }
+
@VisibleForTesting
public static boolean isSupported() {
return false;
}
+
+ /**
+ * Generate a {@link CaptivePortalData} object with a friendly name set
+ *
+ * @param friendlyName The friendly name to set
+ * @return a {@link CaptivePortalData} object with a friendly name set
+ */
+ public CaptivePortalData withVenueFriendlyName(String friendlyName) {
+ // Not supported in API level 29
+ return null;
+ }
}
diff --git a/apishim/29/com/android/networkstack/apishim/api29/NetworkInformationShimImpl.java b/apishim/29/com/android/networkstack/apishim/api29/NetworkInformationShimImpl.java
index 1fa4907..8dc7b5c 100644
--- a/apishim/29/com/android/networkstack/apishim/api29/NetworkInformationShimImpl.java
+++ b/apishim/29/com/android/networkstack/apishim/api29/NetworkInformationShimImpl.java
@@ -16,6 +16,7 @@
package com.android.networkstack.apishim.api29;
+import android.net.CaptivePortalData;
import android.net.IpPrefix;
import android.net.LinkProperties;
import android.net.NetworkCapabilities;
@@ -57,6 +58,14 @@
return false;
}
+ /**
+ * Indicates whether the shim can use APIs above the R SDK.
+ */
+ @VisibleForTesting
+ public static boolean useApiAboveR() {
+ return false;
+ }
+
@Nullable
@Override
public Uri getCaptivePortalApiUrl(@Nullable LinkProperties lp) {
@@ -105,4 +114,17 @@
@NonNull Inet4Address serverAddress) {
// Not supported on this API level: no-op
}
+
+ /**
+ * Set captive portal data in {@link LinkProperties}
+ * @param lp Link properties object to be updated
+ * @param captivePortalData Captive portal data to be used
+ */
+ public void setCaptivePortalData(@NonNull LinkProperties lp,
+ @Nullable CaptivePortalData captivePortalData) {
+ if (lp == null) {
+ return;
+ }
+ lp.setCaptivePortalData(captivePortalData);
+ }
}
diff --git a/apishim/30/com/android/networkstack/apishim/api30/CaptivePortalDataShimImpl.java b/apishim/30/com/android/networkstack/apishim/api30/CaptivePortalDataShimImpl.java
index 5639386..19a41db 100644
--- a/apishim/30/com/android/networkstack/apishim/api30/CaptivePortalDataShimImpl.java
+++ b/apishim/30/com/android/networkstack/apishim/api30/CaptivePortalDataShimImpl.java
@@ -37,7 +37,7 @@
public class CaptivePortalDataShimImpl
extends com.android.networkstack.apishim.api29.CaptivePortalDataShimImpl {
@NonNull
- private final CaptivePortalData mData;
+ protected final CaptivePortalData mData;
protected CaptivePortalDataShimImpl(@NonNull CaptivePortalData data) {
mData = data;
diff --git a/apishim/31/com/android/networkstack/apishim/CaptivePortalDataShimImpl.java b/apishim/31/com/android/networkstack/apishim/CaptivePortalDataShimImpl.java
index 6ff6adb..955167d 100644
--- a/apishim/31/com/android/networkstack/apishim/CaptivePortalDataShimImpl.java
+++ b/apishim/31/com/android/networkstack/apishim/CaptivePortalDataShimImpl.java
@@ -20,13 +20,31 @@
import androidx.annotation.NonNull;
+import com.android.networkstack.apishim.common.CaptivePortalDataShim;
+
/**
* Compatibility implementation of {@link CaptivePortalDataShim}.
*/
public class CaptivePortalDataShimImpl
extends com.android.networkstack.apishim.api30.CaptivePortalDataShimImpl {
- // Currently, this is the same as the API 30 shim, so inherit everything from that.
protected CaptivePortalDataShimImpl(@NonNull CaptivePortalData data) {
super(data);
}
+
+ @Override
+ public String getVenueFriendlyName() {
+ return mData.getVenueFriendlyName();
+ }
+
+ /**
+ * Generate a {@link CaptivePortalData} object with a friendly name set
+ *
+ * @param friendlyName The friendly name to set
+ * @return a {@link CaptivePortalData} object with a friendly name set
+ */
+ public CaptivePortalData withVenueFriendlyName(String friendlyName) {
+ return new CaptivePortalData.Builder(mData)
+ .setVenueFriendlyName(friendlyName)
+ .build();
+ }
}
diff --git a/apishim/31/com/android/networkstack/apishim/NetworkInformationShimImpl.java b/apishim/31/com/android/networkstack/apishim/NetworkInformationShimImpl.java
index 828063c..d668d7e 100644
--- a/apishim/31/com/android/networkstack/apishim/NetworkInformationShimImpl.java
+++ b/apishim/31/com/android/networkstack/apishim/NetworkInformationShimImpl.java
@@ -16,11 +16,45 @@
package com.android.networkstack.apishim;
+import android.net.LinkProperties;
+import android.os.Build;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.networkstack.apishim.common.CaptivePortalDataShim;
+import com.android.networkstack.apishim.common.NetworkInformationShim;
+import com.android.networkstack.apishim.common.ShimUtils;
+
/**
* Compatibility implementation of {@link NetworkInformationShim}.
*/
public class NetworkInformationShimImpl
extends com.android.networkstack.apishim.api30.NetworkInformationShimImpl {
- // Currently, this is the same as the API 30 shim, so inherit everything from that.
protected NetworkInformationShimImpl() {}
+
+ /**
+ * Indicates whether the shim can use APIs above the R SDK.
+ */
+ @VisibleForTesting
+ public static boolean useApiAboveR() {
+ return ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.R);
+ }
+
+ /**
+ * Get a new instance of {@link NetworkInformationShim}.
+ */
+ public static NetworkInformationShim newInstance() {
+ if (!useApiAboveR()) {
+ return com.android.networkstack.apishim.api30.NetworkInformationShimImpl.newInstance();
+ }
+ return new com.android.networkstack.apishim.NetworkInformationShimImpl();
+ }
+
+ @Nullable
+ @Override
+ public CaptivePortalDataShim getCaptivePortalData(@Nullable LinkProperties lp) {
+ if (lp == null || lp.getCaptivePortalData() == null) return null;
+ return new CaptivePortalDataShimImpl(lp.getCaptivePortalData());
+ }
}
diff --git a/apishim/common/com/android/networkstack/apishim/common/CaptivePortalDataShim.java b/apishim/common/com/android/networkstack/apishim/common/CaptivePortalDataShim.java
index a18ba49..4bd5532 100644
--- a/apishim/common/com/android/networkstack/apishim/common/CaptivePortalDataShim.java
+++ b/apishim/common/com/android/networkstack/apishim/common/CaptivePortalDataShim.java
@@ -16,6 +16,8 @@
package com.android.networkstack.apishim.common;
+import android.annotation.NonNull;
+import android.net.CaptivePortalData;
import android.net.INetworkMonitorCallbacks;
import android.net.Uri;
import android.os.RemoteException;
@@ -50,7 +52,20 @@
Uri getVenueInfoUrl();
/**
+ * @see CaptivePortalData#getVenueFriendlyName()
+ */
+ String getVenueFriendlyName();
+
+ /**
* @see INetworkMonitorCallbacks#notifyCaptivePortalDataChanged(android.net.CaptivePortalData)
*/
void notifyChanged(INetworkMonitorCallbacks cb) throws RemoteException;
+
+ /**
+ * Generate a {@link CaptivePortalData} object with a friendly name set
+ *
+ * @param friendlyName The friendly name to set
+ * @return a {@link CaptivePortalData} object with a friendly name set
+ */
+ CaptivePortalData withVenueFriendlyName(@NonNull String friendlyName);
}
diff --git a/apishim/common/com/android/networkstack/apishim/common/NetworkInformationShim.java b/apishim/common/com/android/networkstack/apishim/common/NetworkInformationShim.java
index ee19759..6cdcf8c 100644
--- a/apishim/common/com/android/networkstack/apishim/common/NetworkInformationShim.java
+++ b/apishim/common/com/android/networkstack/apishim/common/NetworkInformationShim.java
@@ -16,6 +16,7 @@
package com.android.networkstack.apishim.common;
+import android.net.CaptivePortalData;
import android.net.IpPrefix;
import android.net.LinkProperties;
import android.net.NetworkCapabilities;
@@ -76,4 +77,11 @@
*/
void setDhcpServerAddress(@NonNull LinkProperties lp, @NonNull Inet4Address serverAddress);
+ /**
+ * Set captive portal data in {@link LinkProperties}
+ * @param lp Link properties object to be updated
+ * @param captivePortalData Captive portal data to be used
+ */
+ void setCaptivePortalData(@NonNull LinkProperties lp,
+ @Nullable CaptivePortalData captivePortalData);
}
diff --git a/common/moduleutils/src/android/net/ip/ConntrackMonitor.java b/common/moduleutils/src/android/net/ip/ConntrackMonitor.java
index b0fc48a..95eda96 100644
--- a/common/moduleutils/src/android/net/ip/ConntrackMonitor.java
+++ b/common/moduleutils/src/android/net/ip/ConntrackMonitor.java
@@ -16,7 +16,11 @@
package android.net.ip;
+import static android.net.netlink.ConntrackMessage.DYING_MASK;
+import static android.net.netlink.ConntrackMessage.ESTABLISHED_MASK;
+
import android.net.netlink.ConntrackMessage;
+import android.net.netlink.NetlinkConstants;
import android.net.netlink.NetlinkMessage;
import android.net.util.SharedLog;
import android.os.Handler;
@@ -24,6 +28,11 @@
import androidx.annotation.NonNull;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Objects;
+
+
/**
* ConntrackMonitor.
*
@@ -45,7 +54,96 @@
/**
* A class for describing parsed netfilter conntrack events.
*/
- public static class ConntrackEvent { /*TODO*/ }
+ public static class ConntrackEvent {
+ /**
+ * Conntrack event type.
+ */
+ public final short msgType;
+ /**
+ * Original direction conntrack tuple.
+ */
+ public final ConntrackMessage.Tuple tupleOrig;
+ /**
+ * Reply direction conntrack tuple.
+ */
+ public final ConntrackMessage.Tuple tupleReply;
+ /**
+ * Connection status. A bitmask of ip_conntrack_status enum flags.
+ */
+ public final int status;
+ /**
+ * Conntrack timeout.
+ */
+ public final int timeoutSec;
+
+ public ConntrackEvent(ConntrackMessage msg) {
+ this.msgType = msg.getHeader().nlmsg_type;
+ this.tupleOrig = msg.tupleOrig;
+ this.tupleReply = msg.tupleReply;
+ this.status = msg.status;
+ this.timeoutSec = msg.timeoutSec;
+ }
+
+ @VisibleForTesting
+ public ConntrackEvent(short msgType, ConntrackMessage.Tuple tupleOrig,
+ ConntrackMessage.Tuple tupleReply, int status, int timeoutSec) {
+ this.msgType = msgType;
+ this.tupleOrig = tupleOrig;
+ this.tupleReply = tupleReply;
+ this.status = status;
+ this.timeoutSec = timeoutSec;
+ }
+
+ @Override
+ @VisibleForTesting
+ public boolean equals(Object o) {
+ if (!(o instanceof ConntrackEvent)) return false;
+ ConntrackEvent that = (ConntrackEvent) o;
+ return this.msgType == that.msgType
+ && Objects.equals(this.tupleOrig, that.tupleOrig)
+ && Objects.equals(this.tupleReply, that.tupleReply)
+ && this.status == that.status
+ && this.timeoutSec == that.timeoutSec;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(msgType, tupleOrig, tupleReply, status, timeoutSec);
+ }
+
+ /**
+ * Check the established NAT session conntrack message.
+ *
+ * @param msg the conntrack message to check.
+ * @return true if an established NAT message, false if not.
+ */
+ public static boolean isEstablishedNatSession(@NonNull ConntrackMessage msg) {
+ if (msg.getMessageType() != NetlinkConstants.IPCTNL_MSG_CT_NEW) return false;
+ if (msg.tupleOrig == null) return false;
+ if (msg.tupleReply == null) return false;
+ if (msg.timeoutSec == 0) return false;
+ if ((msg.status & ESTABLISHED_MASK) != ESTABLISHED_MASK) return false;
+
+ return true;
+ }
+
+ /**
+ * Check the dying NAT session conntrack message.
+ * Note that IPCTNL_MSG_CT_DELETE event has no CTA_TIMEOUT attribute.
+ *
+ * @param msg the conntrack message to check.
+ * @return true if a dying NAT message, false if not.
+ */
+ public static boolean isDyingNatSession(@NonNull ConntrackMessage msg) {
+ if (msg.getMessageType() != NetlinkConstants.IPCTNL_MSG_CT_DELETE) return false;
+ if (msg.tupleOrig == null) return false;
+ if (msg.tupleReply == null) return false;
+ if (msg.timeoutSec != 0) return false;
+ if ((msg.status & DYING_MASK) != DYING_MASK) return false;
+
+ return true;
+ }
+ }
/**
* A callback to caller for conntrack event.
@@ -74,6 +172,12 @@
return;
}
- mConsumer.accept(new ConntrackEvent() /* TODO */);
+ final ConntrackMessage conntrackMsg = (ConntrackMessage) nlMsg;
+ if (!(ConntrackEvent.isEstablishedNatSession(conntrackMsg)
+ || ConntrackEvent.isDyingNatSession(conntrackMsg))) {
+ return;
+ }
+
+ mConsumer.accept(new ConntrackEvent(conntrackMsg));
}
}
diff --git a/common/netlinkclient/src/android/net/netlink/ConntrackMessage.java b/common/netlinkclient/src/android/net/netlink/ConntrackMessage.java
index 4326f7a..7f34547 100644
--- a/common/netlinkclient/src/android/net/netlink/ConntrackMessage.java
+++ b/common/netlinkclient/src/android/net/netlink/ConntrackMessage.java
@@ -16,17 +16,27 @@
package android.net.netlink;
+import static android.net.netlink.StructNlAttr.findNextAttrOfType;
+import static android.net.netlink.StructNlAttr.makeNestedType;
import static android.net.netlink.StructNlMsgHdr.NLM_F_ACK;
import static android.net.netlink.StructNlMsgHdr.NLM_F_REPLACE;
import static android.net.netlink.StructNlMsgHdr.NLM_F_REQUEST;
+import static android.system.OsConstants.IPPROTO_TCP;
+import static android.system.OsConstants.IPPROTO_UDP;
import static java.nio.ByteOrder.BIG_ENDIAN;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.system.OsConstants;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.net.Inet4Address;
+import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
+import java.util.Objects;
/**
@@ -42,6 +52,7 @@
// enum ctattr_type
public static final short CTA_TUPLE_ORIG = 1;
public static final short CTA_TUPLE_REPLY = 2;
+ public static final short CTA_STATUS = 3;
public static final short CTA_TIMEOUT = 7;
// enum ctattr_tuple
@@ -57,6 +68,106 @@
public static final short CTA_PROTO_SRC_PORT = 2;
public static final short CTA_PROTO_DST_PORT = 3;
+ // enum ip_conntrack_status
+ public static final int IPS_EXPECTED = 0x00000001;
+ public static final int IPS_SEEN_REPLY = 0x00000002;
+ public static final int IPS_ASSURED = 0x00000004;
+ public static final int IPS_CONFIRMED = 0x00000008;
+ public static final int IPS_SRC_NAT = 0x00000010;
+ public static final int IPS_DST_NAT = 0x00000020;
+ public static final int IPS_SEQ_ADJUST = 0x00000040;
+ public static final int IPS_SRC_NAT_DONE = 0x00000080;
+ public static final int IPS_DST_NAT_DONE = 0x00000100;
+ public static final int IPS_DYING = 0x00000200;
+ public static final int IPS_FIXED_TIMEOUT = 0x00000400;
+ public static final int IPS_TEMPLATE = 0x00000800;
+ public static final int IPS_UNTRACKED = 0x00001000;
+ public static final int IPS_HELPER = 0x00002000;
+ public static final int IPS_OFFLOAD = 0x00004000;
+ public static final int IPS_HW_OFFLOAD = 0x00008000;
+
+ // ip_conntrack_status mask
+ // Interesting on the NAT conntrack session which has already seen two direction traffic.
+ // TODO: Probably IPS_{SRC, DST}_NAT_DONE are also interesting.
+ public static final int ESTABLISHED_MASK = IPS_CONFIRMED | IPS_ASSURED | IPS_SEEN_REPLY
+ | IPS_SRC_NAT;
+ // Interesting on the established NAT conntrack session which is dying.
+ public static final int DYING_MASK = ESTABLISHED_MASK | IPS_DYING;
+
+ /**
+ * A tuple for the conntrack connection information.
+ *
+ * see also CTA_TUPLE_ORIG and CTA_TUPLE_REPLY.
+ */
+ public static class Tuple {
+ public final Inet4Address srcIp;
+ public final Inet4Address dstIp;
+
+ // Both port and protocol number are unsigned numbers stored in signed integers, and that
+ // callers that want to compare them to integers should either cast those integers, or
+ // convert them to unsigned using Byte.toUnsignedInt() and Short.toUnsignedInt().
+ public final short srcPort;
+ public final short dstPort;
+ public final byte protoNum;
+
+ public Tuple(TupleIpv4 ip, TupleProto proto) {
+ this.srcIp = ip.src;
+ this.dstIp = ip.dst;
+ this.srcPort = proto.srcPort;
+ this.dstPort = proto.dstPort;
+ this.protoNum = proto.protoNum;
+ }
+
+ @Override
+ @VisibleForTesting
+ public boolean equals(Object o) {
+ if (!(o instanceof Tuple)) return false;
+ Tuple that = (Tuple) o;
+ return Objects.equals(this.srcIp, that.srcIp)
+ && Objects.equals(this.dstIp, that.dstIp)
+ && this.srcPort == that.srcPort
+ && this.dstPort == that.dstPort
+ && this.protoNum == that.protoNum;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(srcIp, dstIp, srcPort, dstPort, protoNum);
+ }
+ }
+
+ /**
+ * A tuple for the conntrack connection address.
+ *
+ * see also CTA_TUPLE_IP.
+ */
+ public static class TupleIpv4 {
+ public final Inet4Address src;
+ public final Inet4Address dst;
+
+ public TupleIpv4(Inet4Address src, Inet4Address dst) {
+ this.src = src;
+ this.dst = dst;
+ }
+ }
+
+ /**
+ * A tuple for the conntrack connection protocol.
+ *
+ * see also CTA_TUPLE_PROTO.
+ */
+ public static class TupleProto {
+ public final byte protoNum;
+ public final short srcPort;
+ public final short dstPort;
+
+ public TupleProto(byte protoNum, short srcPort, short dstPort) {
+ this.protoNum = protoNum;
+ this.srcPort = srcPort;
+ this.dstPort = dstPort;
+ }
+ }
+
public static byte[] newIPv4TimeoutUpdateRequest(
int proto, Inet4Address src, int sport, Inet4Address dst, int dport, int timeoutSec) {
// *** STYLE WARNING ***
@@ -105,35 +216,238 @@
// Just build the netlink header and netfilter header for now and pretend the whole message
// was consumed.
// TODO: Parse the conntrack attributes.
- final ConntrackMessage conntrackMsg = new ConntrackMessage(header);
-
- conntrackMsg.mNfGenMsg = StructNfGenMsg.parse(byteBuffer);
- if (conntrackMsg.mNfGenMsg == null) {
+ final StructNfGenMsg nfGenMsg = StructNfGenMsg.parse(byteBuffer);
+ if (nfGenMsg == null) {
return null;
}
- byteBuffer.position(byteBuffer.limit());
- return conntrackMsg;
+ final int baseOffset = byteBuffer.position();
+ StructNlAttr nlAttr = findNextAttrOfType(CTA_STATUS, byteBuffer);
+ int status = 0;
+ if (nlAttr != null) {
+ status = nlAttr.getValueAsBe32(0);
+ }
+
+ byteBuffer.position(baseOffset);
+ nlAttr = findNextAttrOfType(CTA_TIMEOUT, byteBuffer);
+ int timeoutSec = 0;
+ if (nlAttr != null) {
+ timeoutSec = nlAttr.getValueAsBe32(0);
+ }
+
+ byteBuffer.position(baseOffset);
+ nlAttr = findNextAttrOfType(makeNestedType(CTA_TUPLE_ORIG), byteBuffer);
+ Tuple tupleOrig = null;
+ if (nlAttr != null) {
+ tupleOrig = parseTuple(nlAttr.getValueAsByteBuffer());
+ }
+
+ byteBuffer.position(baseOffset);
+ nlAttr = findNextAttrOfType(makeNestedType(CTA_TUPLE_REPLY), byteBuffer);
+ Tuple tupleReply = null;
+ if (nlAttr != null) {
+ tupleReply = parseTuple(nlAttr.getValueAsByteBuffer());
+ }
+
+ // Advance to the end of the message.
+ byteBuffer.position(baseOffset);
+ final int kMinConsumed = StructNlMsgHdr.STRUCT_SIZE + StructNfGenMsg.STRUCT_SIZE;
+ final int kAdditionalSpace = NetlinkConstants.alignedLengthOf(
+ header.nlmsg_len - kMinConsumed);
+ if (byteBuffer.remaining() < kAdditionalSpace) {
+ return null;
+ }
+ byteBuffer.position(baseOffset + kAdditionalSpace);
+
+ return new ConntrackMessage(header, nfGenMsg, tupleOrig, tupleReply, status, timeoutSec);
}
- protected StructNfGenMsg mNfGenMsg;
+ /**
+ * Parses a conntrack tuple from a {@link ByteBuffer}.
+ *
+ * The attribute parsing is interesting on:
+ * - CTA_TUPLE_IP
+ * CTA_IP_V4_SRC
+ * CTA_IP_V4_DST
+ * - CTA_TUPLE_PROTO
+ * CTA_PROTO_NUM
+ * CTA_PROTO_SRC_PORT
+ * CTA_PROTO_DST_PORT
+ *
+ * Assume that the minimum size is the sum of CTA_TUPLE_IP (size: 20) and CTA_TUPLE_PROTO
+ * (size: 28). Here is an example for an expected CTA_TUPLE_ORIG message in raw data:
+ * +--------------------------------------------------------------------------------------+
+ * | CTA_TUPLE_ORIG |
+ * +--------------------------+-----------------------------------------------------------+
+ * | 1400 | nla_len = 20 |
+ * | 0180 | nla_type = nested CTA_TUPLE_IP |
+ * | 0800 0100 C0A8500C | nla_type=CTA_IP_V4_SRC, ip=192.168.80.12 |
+ * | 0800 0200 8C700874 | nla_type=CTA_IP_V4_DST, ip=140.112.8.116 |
+ * | 1C00 | nla_len = 28 |
+ * | 0280 | nla_type = nested CTA_TUPLE_PROTO |
+ * | 0500 0100 06 000000 | nla_type=CTA_PROTO_NUM, proto=IPPROTO_TCP (6) |
+ * | 0600 0200 F3F1 0000 | nla_type=CTA_PROTO_SRC_PORT, port=62449 (big endian) |
+ * | 0600 0300 01BB 0000 | nla_type=CTA_PROTO_DST_PORT, port=433 (big endian) |
+ * +--------------------------+-----------------------------------------------------------+
+ *
+ * The position of the byte buffer doesn't set to the end when the function returns. It is okay
+ * because the caller ConntrackMessage#parse has passed a copy which is used for this parser
+ * only. Moreover, the parser behavior is the same as other existing netlink struct class
+ * parser. Ex: StructInetDiagMsg#parse.
+ */
+ @Nullable
+ private static Tuple parseTuple(@Nullable ByteBuffer byteBuffer) {
+ if (byteBuffer == null) return null;
+
+ TupleIpv4 tupleIpv4 = null;
+ TupleProto tupleProto = null;
+
+ final int baseOffset = byteBuffer.position();
+ StructNlAttr nlAttr = findNextAttrOfType(makeNestedType(CTA_TUPLE_IP), byteBuffer);
+ if (nlAttr != null) {
+ tupleIpv4 = parseTupleIpv4(nlAttr.getValueAsByteBuffer());
+ }
+ if (tupleIpv4 == null) return null;
+
+ byteBuffer.position(baseOffset);
+ nlAttr = findNextAttrOfType(makeNestedType(CTA_TUPLE_PROTO), byteBuffer);
+ if (nlAttr != null) {
+ tupleProto = parseTupleProto(nlAttr.getValueAsByteBuffer());
+ }
+ if (tupleProto == null) return null;
+
+ return new Tuple(tupleIpv4, tupleProto);
+ }
+
+ @Nullable
+ private static Inet4Address castToInet4Address(@Nullable InetAddress address) {
+ if (address == null || !(address instanceof Inet4Address)) return null;
+ return (Inet4Address) address;
+ }
+
+ @Nullable
+ private static TupleIpv4 parseTupleIpv4(@Nullable ByteBuffer byteBuffer) {
+ if (byteBuffer == null) return null;
+
+ Inet4Address src = null;
+ Inet4Address dst = null;
+
+ final int baseOffset = byteBuffer.position();
+ StructNlAttr nlAttr = findNextAttrOfType(CTA_IP_V4_SRC, byteBuffer);
+ if (nlAttr != null) {
+ src = castToInet4Address(nlAttr.getValueAsInetAddress());
+ }
+ if (src == null) return null;
+
+ byteBuffer.position(baseOffset);
+ nlAttr = findNextAttrOfType(CTA_IP_V4_DST, byteBuffer);
+ if (nlAttr != null) {
+ dst = castToInet4Address(nlAttr.getValueAsInetAddress());
+ }
+ if (dst == null) return null;
+
+ return new TupleIpv4(src, dst);
+ }
+
+ @Nullable
+ private static TupleProto parseTupleProto(@Nullable ByteBuffer byteBuffer) {
+ if (byteBuffer == null) return null;
+
+ byte protoNum = 0;
+ short srcPort = 0;
+ short dstPort = 0;
+
+ final int baseOffset = byteBuffer.position();
+ StructNlAttr nlAttr = findNextAttrOfType(CTA_PROTO_NUM, byteBuffer);
+ if (nlAttr != null) {
+ protoNum = nlAttr.getValueAsByte((byte) 0);
+ }
+ if (!(protoNum == IPPROTO_TCP || protoNum == IPPROTO_UDP)) return null;
+
+ byteBuffer.position(baseOffset);
+ nlAttr = StructNlAttr.findNextAttrOfType(CTA_PROTO_SRC_PORT, byteBuffer);
+ if (nlAttr != null) {
+ srcPort = nlAttr.getValueAsBe16((short) 0);
+ }
+ if (srcPort == 0) return null;
+
+ byteBuffer.position(baseOffset);
+ nlAttr = StructNlAttr.findNextAttrOfType(CTA_PROTO_DST_PORT, byteBuffer);
+ if (nlAttr != null) {
+ dstPort = nlAttr.getValueAsBe16((short) 0);
+ }
+ if (dstPort == 0) return null;
+
+ return new TupleProto(protoNum, srcPort, dstPort);
+ }
+
+ /**
+ * Netfilter header.
+ */
+ public final StructNfGenMsg nfGenMsg;
+ /**
+ * Original direction conntrack tuple.
+ *
+ * The tuple is determined by the parsed attribute value CTA_TUPLE_ORIG, or null if the
+ * tuple could not be parsed successfully (for example, if it was truncated or absent).
+ */
+ @Nullable
+ public final Tuple tupleOrig;
+ /**
+ * Reply direction conntrack tuple.
+ *
+ * The tuple is determined by the parsed attribute value CTA_TUPLE_REPLY, or null if the
+ * tuple could not be parsed successfully (for example, if it was truncated or absent).
+ */
+ @Nullable
+ public final Tuple tupleReply;
+ /**
+ * Connection status. A bitmask of ip_conntrack_status enum flags.
+ *
+ * The status is determined by the parsed attribute value CTA_STATUS, or 0 if the status could
+ * not be parsed successfully (for example, if it was truncated or absent). For the message
+ * from kernel, the valid status is non-zero. For the message from user space, the status may
+ * be 0 (absent).
+ */
+ public final int status;
+ /**
+ * Conntrack timeout.
+ *
+ * The timeout is determined by the parsed attribute value CTA_TIMEOUT, or 0 if the timeout
+ * could not be parsed successfully (for example, if it was truncated or absent). For
+ * IPCTNL_MSG_CT_NEW event, the valid timeout is non-zero. For IPCTNL_MSG_CT_DELETE event, the
+ * timeout is 0 (absent).
+ */
+ public final int timeoutSec;
private ConntrackMessage() {
super(new StructNlMsgHdr());
- mNfGenMsg = new StructNfGenMsg((byte) OsConstants.AF_INET);
+ nfGenMsg = new StructNfGenMsg((byte) OsConstants.AF_INET);
+
+ // This constructor is only used by #newIPv4TimeoutUpdateRequest which doesn't use these
+ // data member for packing message. Simply fill them to null or 0.
+ tupleOrig = null;
+ tupleReply = null;
+ status = 0;
+ timeoutSec = 0;
}
- private ConntrackMessage(StructNlMsgHdr header) {
+ private ConntrackMessage(@NonNull StructNlMsgHdr header, @NonNull StructNfGenMsg nfGenMsg,
+ @Nullable Tuple tupleOrig, @Nullable Tuple tupleReply, int status, int timeoutSec) {
super(header);
- mNfGenMsg = null;
- }
-
- public StructNfGenMsg getNfHeader() {
- return mNfGenMsg;
+ this.nfGenMsg = nfGenMsg;
+ this.tupleOrig = tupleOrig;
+ this.tupleReply = tupleReply;
+ this.status = status;
+ this.timeoutSec = timeoutSec;
}
public void pack(ByteBuffer byteBuffer) {
mHeader.pack(byteBuffer);
- mNfGenMsg.pack(byteBuffer);
+ nfGenMsg.pack(byteBuffer);
+ }
+
+ public short getMessageType() {
+ return (short) (getHeader().nlmsg_type & ~(NetlinkConstants.NFNL_SUBSYS_CTNETLINK << 8));
}
}
diff --git a/common/netlinkclient/src/android/net/netlink/RtNetlinkNeighborMessage.java b/common/netlinkclient/src/android/net/netlink/RtNetlinkNeighborMessage.java
index 6ae22c0..099ff07 100644
--- a/common/netlinkclient/src/android/net/netlink/RtNetlinkNeighborMessage.java
+++ b/common/netlinkclient/src/android/net/netlink/RtNetlinkNeighborMessage.java
@@ -48,23 +48,6 @@
public static final short NDA_IFINDEX = 8;
public static final short NDA_MASTER = 9;
- private static StructNlAttr findNextAttrOfType(short attrType, ByteBuffer byteBuffer) {
- while (byteBuffer != null && byteBuffer.remaining() > 0) {
- final StructNlAttr nlAttr = StructNlAttr.peek(byteBuffer);
- if (nlAttr == null) {
- break;
- }
- if (nlAttr.nla_type == attrType) {
- return StructNlAttr.parse(byteBuffer);
- }
- if (byteBuffer.remaining() < nlAttr.getAlignedLength()) {
- break;
- }
- byteBuffer.position(byteBuffer.position() + nlAttr.getAlignedLength());
- }
- return null;
- }
-
public static RtNetlinkNeighborMessage parse(StructNlMsgHdr header, ByteBuffer byteBuffer) {
final RtNetlinkNeighborMessage neighMsg = new RtNetlinkNeighborMessage(header);
@@ -75,25 +58,25 @@
// Some of these are message-type dependent, and not always present.
final int baseOffset = byteBuffer.position();
- StructNlAttr nlAttr = findNextAttrOfType(NDA_DST, byteBuffer);
+ StructNlAttr nlAttr = StructNlAttr.findNextAttrOfType(NDA_DST, byteBuffer);
if (nlAttr != null) {
neighMsg.mDestination = nlAttr.getValueAsInetAddress();
}
byteBuffer.position(baseOffset);
- nlAttr = findNextAttrOfType(NDA_LLADDR, byteBuffer);
+ nlAttr = StructNlAttr.findNextAttrOfType(NDA_LLADDR, byteBuffer);
if (nlAttr != null) {
neighMsg.mLinkLayerAddr = nlAttr.nla_value;
}
byteBuffer.position(baseOffset);
- nlAttr = findNextAttrOfType(NDA_PROBES, byteBuffer);
+ nlAttr = StructNlAttr.findNextAttrOfType(NDA_PROBES, byteBuffer);
if (nlAttr != null) {
neighMsg.mNumProbes = nlAttr.getValueAsInt(0);
}
byteBuffer.position(baseOffset);
- nlAttr = findNextAttrOfType(NDA_CACHEINFO, byteBuffer);
+ nlAttr = StructNlAttr.findNextAttrOfType(NDA_CACHEINFO, byteBuffer);
if (nlAttr != null) {
neighMsg.mCacheInfo = StructNdaCacheInfo.parse(nlAttr.getValueAsByteBuffer());
}
diff --git a/common/netlinkclient/src/android/net/netlink/StructNfGenMsg.java b/common/netlinkclient/src/android/net/netlink/StructNfGenMsg.java
index e0f4333..fca78b8 100644
--- a/common/netlinkclient/src/android/net/netlink/StructNfGenMsg.java
+++ b/common/netlinkclient/src/android/net/netlink/StructNfGenMsg.java
@@ -79,8 +79,11 @@
public void pack(ByteBuffer byteBuffer) {
byteBuffer.put(nfgen_family);
byteBuffer.put(version);
- // TODO: probably need to handle the little endian case.
+
+ final ByteOrder originalOrder = byteBuffer.order();
+ byteBuffer.order(ByteOrder.BIG_ENDIAN);
byteBuffer.putShort(res_id);
+ byteBuffer.order(originalOrder);
}
private static boolean hasAvailableSpace(@NonNull ByteBuffer byteBuffer) {
diff --git a/common/netlinkclient/src/android/net/netlink/StructNlAttr.java b/common/netlinkclient/src/android/net/netlink/StructNlAttr.java
index 747998d..b6e1d3f 100644
--- a/common/netlinkclient/src/android/net/netlink/StructNlAttr.java
+++ b/common/netlinkclient/src/android/net/netlink/StructNlAttr.java
@@ -16,10 +16,12 @@
package android.net.netlink;
+import androidx.annotation.Nullable;
+
import java.net.InetAddress;
import java.net.UnknownHostException;
-import java.nio.ByteOrder;
import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
/**
@@ -48,9 +50,7 @@
}
final int baseOffset = byteBuffer.position();
- // Assume the byte order of the buffer is the expected byte order of the value.
- final StructNlAttr struct = new StructNlAttr(byteBuffer.order());
- // The byte order of nla_len and nla_type is always native.
+ final StructNlAttr struct = new StructNlAttr();
final ByteOrder originalOrder = byteBuffer.order();
byteBuffer.order(ByteOrder.nativeOrder());
try {
@@ -87,20 +87,39 @@
return struct;
}
+ /**
+ * Find next netlink attribute with a given type from {@link ByteBuffer}.
+ *
+ * @param attrType The given netlink attribute type is requested for.
+ * @param byteBuffer The buffer from which to find the netlink attribute.
+ * @return the found netlink attribute, or {@code null} if the netlink attribute could not be
+ * found or parsed successfully (for example, if it was truncated).
+ */
+ @Nullable
+ public static StructNlAttr findNextAttrOfType(short attrType,
+ @Nullable ByteBuffer byteBuffer) {
+ while (byteBuffer != null && byteBuffer.remaining() > 0) {
+ final StructNlAttr nlAttr = StructNlAttr.peek(byteBuffer);
+ if (nlAttr == null) {
+ break;
+ }
+ if (nlAttr.nla_type == attrType) {
+ return StructNlAttr.parse(byteBuffer);
+ }
+ if (byteBuffer.remaining() < nlAttr.getAlignedLength()) {
+ break;
+ }
+ byteBuffer.position(byteBuffer.position() + nlAttr.getAlignedLength());
+ }
+ return null;
+ }
+
public short nla_len = (short) NLA_HEADERLEN;
public short nla_type;
public byte[] nla_value;
- // The byte order used to read/write the value member. Netlink length and
- // type members are always read/written in native order.
- private ByteOrder mByteOrder = ByteOrder.nativeOrder();
-
public StructNlAttr() {}
- public StructNlAttr(ByteOrder byteOrder) {
- mByteOrder = byteOrder;
- }
-
public StructNlAttr(short type, byte value) {
nla_type = type;
setValue(new byte[1]);
@@ -112,10 +131,16 @@
}
public StructNlAttr(short type, short value, ByteOrder order) {
- this(order);
nla_type = type;
setValue(new byte[Short.BYTES]);
- getValueAsByteBuffer().putShort(value);
+ final ByteBuffer buf = getValueAsByteBuffer();
+ final ByteOrder originalOrder = buf.order();
+ try {
+ buf.order(order);
+ buf.putShort(value);
+ } finally {
+ buf.order(originalOrder);
+ }
}
public StructNlAttr(short type, int value) {
@@ -123,10 +148,16 @@
}
public StructNlAttr(short type, int value, ByteOrder order) {
- this(order);
nla_type = type;
setValue(new byte[Integer.BYTES]);
- getValueAsByteBuffer().putInt(value);
+ final ByteBuffer buf = getValueAsByteBuffer();
+ final ByteOrder originalOrder = buf.order();
+ try {
+ buf.order(order);
+ buf.putInt(value);
+ } finally {
+ buf.order(originalOrder);
+ }
}
public StructNlAttr(short type, InetAddress ip) {
@@ -152,13 +183,58 @@
return NetlinkConstants.alignedLengthOf(nla_len);
}
+ /**
+ * Get attribute value as BE16.
+ */
+ public short getValueAsBe16(short defaultValue) {
+ final ByteBuffer byteBuffer = getValueAsByteBuffer();
+ if (byteBuffer == null || byteBuffer.remaining() != Short.BYTES) {
+ return defaultValue;
+ }
+ final ByteOrder originalOrder = byteBuffer.order();
+ try {
+ byteBuffer.order(ByteOrder.BIG_ENDIAN);
+ return byteBuffer.getShort();
+ } finally {
+ byteBuffer.order(originalOrder);
+ }
+ }
+
+ public int getValueAsBe32(int defaultValue) {
+ final ByteBuffer byteBuffer = getValueAsByteBuffer();
+ if (byteBuffer == null || byteBuffer.remaining() != Integer.BYTES) {
+ return defaultValue;
+ }
+ final ByteOrder originalOrder = byteBuffer.order();
+ try {
+ byteBuffer.order(ByteOrder.BIG_ENDIAN);
+ return byteBuffer.getInt();
+ } finally {
+ byteBuffer.order(originalOrder);
+ }
+ }
+
public ByteBuffer getValueAsByteBuffer() {
if (nla_value == null) { return null; }
final ByteBuffer byteBuffer = ByteBuffer.wrap(nla_value);
- byteBuffer.order(mByteOrder);
+ // By convention, all buffers in this library are in native byte order because netlink is in
+ // native byte order. It's the order that is used by NetlinkSocket.recvMessage and the only
+ // order accepted by NetlinkMessage.parse.
+ byteBuffer.order(ByteOrder.nativeOrder());
return byteBuffer;
}
+ /**
+ * Get attribute value as byte.
+ */
+ public byte getValueAsByte(byte defaultValue) {
+ final ByteBuffer byteBuffer = getValueAsByteBuffer();
+ if (byteBuffer == null || byteBuffer.remaining() != Byte.BYTES) {
+ return defaultValue;
+ }
+ return getValueAsByteBuffer().get();
+ }
+
public int getValueAsInt(int defaultValue) {
final ByteBuffer byteBuffer = getValueAsByteBuffer();
if (byteBuffer == null || byteBuffer.remaining() != Integer.BYTES) {
diff --git a/src/com/android/networkstack/NetworkStackNotifier.java b/src/com/android/networkstack/NetworkStackNotifier.java
index dbb62b1..0558d3a 100644
--- a/src/com/android/networkstack/NetworkStackNotifier.java
+++ b/src/com/android/networkstack/NetworkStackNotifier.java
@@ -198,7 +198,6 @@
// Don't show the notification when SSID is unknown to prevent sending something vague to
// the user.
final boolean hasSsid = !TextUtils.isEmpty(getSsid(networkStatus));
-
final CaptivePortalDataShim capportData = getCaptivePortalData(networkStatus);
final boolean showVenueInfo = capportData != null && capportData.getVenueInfoUrl() != null
// Only show venue info on validated networks, to prevent misuse of the notification
@@ -235,7 +234,14 @@
// channel even if the notification contains venue info: the "venue info" notification
// then doubles as a "connected" notification.
final String channel = showValidated ? CHANNEL_CONNECTED : CHANNEL_VENUE_INFO;
- builder = getNotificationBuilder(channel, networkStatus, res, getSsid(networkStatus))
+
+ // If the venue friendly name is available (in Passpoint use-case), display it.
+ // Otherwise, display the SSID.
+ final String friendlyName = capportData.getVenueFriendlyName();
+ final String venueDisplayName = TextUtils.isEmpty(friendlyName)
+ ? getSsid(networkStatus) : friendlyName;
+
+ builder = getNotificationBuilder(channel, networkStatus, res, venueDisplayName)
.setContentText(res.getString(R.string.tap_for_info))
.setContentIntent(mDependencies.getActivityPendingIntent(
getContextAsUser(mContext, UserHandle.CURRENT),
diff --git a/tests/unit/src/android/net/ip/ConntrackMonitorTest.java b/tests/unit/src/android/net/ip/ConntrackMonitorTest.java
index 578ee08..406ecff 100644
--- a/tests/unit/src/android/net/ip/ConntrackMonitorTest.java
+++ b/tests/unit/src/android/net/ip/ConntrackMonitorTest.java
@@ -15,14 +15,25 @@
*/
package android.net.ip;
+import static android.net.ip.ConntrackMonitor.ConntrackEvent;
+import static android.net.netlink.ConntrackMessage.Tuple;
+import static android.net.netlink.ConntrackMessage.TupleIpv4;
+import static android.net.netlink.ConntrackMessage.TupleProto;
+import static android.net.netlink.NetlinkConstants.IPCTNL_MSG_CT_DELETE;
+import static android.net.netlink.NetlinkConstants.IPCTNL_MSG_CT_NEW;
import static android.system.OsConstants.AF_UNIX;
+import static android.system.OsConstants.IPPROTO_TCP;
import static android.system.OsConstants.SOCK_DGRAM;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.fail;
-import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
+import android.net.InetAddresses;
+import android.net.netlink.NetlinkConstants;
import android.net.netlink.NetlinkSocket;
import android.net.util.SharedLog;
import android.os.ConditionVariable;
@@ -46,6 +57,7 @@
import java.io.FileDescriptor;
import java.io.InterruptedIOException;
+import java.net.Inet4Address;
/**
@@ -128,26 +140,182 @@
mHandlerThread.quitSafely();
}
- // TODO: Add conntrack message attributes to have further verification.
- public static final String CT_V4NEW_HEX =
+ public static final String CT_V4NEW_TCP_HEX =
// CHECKSTYLE:OFF IndentationCheck
// struct nlmsghdr
- "14000000" + // length = 20
+ "8C000000" + // length = 140
"0001" + // type = NFNL_SUBSYS_CTNETLINK (1) << 8 | IPCTNL_MSG_CT_NEW (0)
- "0006" + // flags = NLM_F_CREATE | NLM_F_EXCL
+ "0006" + // flags = NLM_F_CREATE (1 << 10) | NLM_F_EXCL (1 << 9)
"00000000" + // seqno = 0
"00000000" + // pid = 0
// struct nfgenmsg
"02" + // nfgen_family = AF_INET
"00" + // version = NFNETLINK_V0
- "0000"; // res_id
+ "1234" + // res_id = 0x1234 (big endian)
+ // struct nlattr
+ "3400" + // nla_len = 52
+ "0180" + // nla_type = nested CTA_TUPLE_ORIG
+ // struct nlattr
+ "1400" + // nla_len = 20
+ "0180" + // nla_type = nested CTA_TUPLE_IP
+ "0800 0100 C0A8500C" + // nla_type=CTA_IP_V4_SRC, ip=192.168.80.12
+ "0800 0200 8C700874" + // nla_type=CTA_IP_V4_DST, ip=140.112.8.116
+ // struct nlattr
+ "1C00" + // nla_len = 28
+ "0280" + // nla_type = nested CTA_TUPLE_PROTO
+ "0500 0100 06 000000" + // nla_type=CTA_PROTO_NUM, proto=IPPROTO_TCP (6)
+ "0600 0200 F3F1 0000" + // nla_type=CTA_PROTO_SRC_PORT, port=62449 (big endian)
+ "0600 0300 01BB 0000" + // nla_type=CTA_PROTO_DST_PORT, port=443 (big endian)
+ // struct nlattr
+ "3400" + // nla_len = 52
+ "0280" + // nla_type = nested CTA_TUPLE_REPLY
+ // struct nlattr
+ "1400" + // nla_len = 20
+ "0180" + // nla_type = nested CTA_TUPLE_IP
+ "0800 0100 8C700874" + // nla_type=CTA_IP_V4_SRC, ip=140.112.8.116
+ "0800 0200 6451B301" + // nla_type=CTA_IP_V4_DST, ip=100.81.179.1
+ // struct nlattr
+ "1C00" + // nla_len = 28
+ "0280" + // nla_type = nested CTA_TUPLE_PROTO
+ "0500 0100 06 000000" + // nla_type=CTA_PROTO_NUM, proto=IPPROTO_TCP (6)
+ "0600 0200 01BB 0000" + // nla_type=CTA_PROTO_SRC_PORT, port=443 (big endian)
+ "0600 0300 F3F1 0000" + // nla_type=CTA_PROTO_DST_PORT, port=62449 (big endian)
+ // struct nlattr
+ "0800" + // nla_len = 8
+ "0300" + // nla_type = CTA_STATUS
+ "0000019e" + // nla_value = 0b110011110 (big endian)
+ // IPS_SEEN_REPLY (1 << 1) | IPS_ASSURED (1 << 2) |
+ // IPS_CONFIRMED (1 << 3) | IPS_SRC_NAT (1 << 4) |
+ // IPS_SRC_NAT_DONE (1 << 7) | IPS_DST_NAT_DONE (1 << 8)
+ // struct nlattr
+ "0800" + // nla_len = 8
+ "0700" + // nla_type = CTA_TIMEOUT
+ "00000078"; // nla_value = 120 (big endian)
// CHECKSTYLE:ON IndentationCheck
- public static final byte[] CT_V4NEW_BYTES =
- HexEncoding.decode(CT_V4NEW_HEX.replaceAll(" ", "").toCharArray(), false);
+ public static final byte[] CT_V4NEW_TCP_BYTES =
+ HexEncoding.decode(CT_V4NEW_TCP_HEX.replaceAll(" ", "").toCharArray(), false);
+
+ @NonNull
+ private ConntrackEvent makeTestConntrackEvent(short msgType, int status, int timeoutSec) {
+ final Inet4Address privateIp =
+ (Inet4Address) InetAddresses.parseNumericAddress("192.168.80.12");
+ final Inet4Address remoteIp =
+ (Inet4Address) InetAddresses.parseNumericAddress("140.112.8.116");
+ final Inet4Address publicIp =
+ (Inet4Address) InetAddresses.parseNumericAddress("100.81.179.1");
+
+ return new ConntrackEvent(
+ (short) (NetlinkConstants.NFNL_SUBSYS_CTNETLINK << 8 | msgType),
+ new Tuple(new TupleIpv4(privateIp, remoteIp),
+ new TupleProto((byte) IPPROTO_TCP, (short) 62449, (short) 443)),
+ new Tuple(new TupleIpv4(remoteIp, publicIp),
+ new TupleProto((byte) IPPROTO_TCP, (short) 443, (short) 62449)),
+ status,
+ timeoutSec);
+ }
@Test
- public void testConntrackEvent_New() throws Exception {
- mConntrackMonitor.sendMessage(CT_V4NEW_BYTES);
- verify(mConsumer, timeout(TIMEOUT_MS)).accept(any() /* TODO: check the content */);
+ public void testConntrackEventNew() throws Exception {
+ final ConntrackEvent expectedEvent = makeTestConntrackEvent(IPCTNL_MSG_CT_NEW,
+ 0x19e /* status */, 120 /* timeoutSec */);
+ mConntrackMonitor.sendMessage(CT_V4NEW_TCP_BYTES);
+ verify(mConsumer, timeout(TIMEOUT_MS)).accept(eq(expectedEvent));
}
+
+ @Test
+ public void testConntrackEventEquals() {
+ final ConntrackEvent event1 = makeTestConntrackEvent(IPCTNL_MSG_CT_NEW, 1234 /* status */,
+ 5678 /* timeoutSec*/);
+ final ConntrackEvent event2 = makeTestConntrackEvent(IPCTNL_MSG_CT_NEW, 1234 /* status */,
+ 5678 /* timeoutSec*/);
+ assertEquals(event1, event2);
+ }
+
+ @Test
+ public void testConntrackEventNotEquals() {
+ final ConntrackEvent e = makeTestConntrackEvent(IPCTNL_MSG_CT_NEW, 1234 /* status */,
+ 5678 /* timeoutSec*/);
+
+ final ConntrackEvent typeNotEqual = new ConntrackEvent((short) (e.msgType + 1) /* diff */,
+ e.tupleOrig, e.tupleReply, e.status, e.timeoutSec);
+ assertNotEquals(e, typeNotEqual);
+
+ final ConntrackEvent tupleOrigNotEqual = new ConntrackEvent(e.msgType,
+ null /* diff */, e.tupleReply, e.status, e.timeoutSec);
+ assertNotEquals(e, tupleOrigNotEqual);
+
+ final ConntrackEvent tupleReplyNotEqual = new ConntrackEvent(e.msgType,
+ e.tupleOrig, null /* diff */, e.status, e.timeoutSec);
+ assertNotEquals(e, tupleReplyNotEqual);
+
+ final ConntrackEvent statusNotEqual = new ConntrackEvent(e.msgType,
+ e.tupleOrig, e.tupleReply, e.status + 1 /* diff */, e.timeoutSec);
+ assertNotEquals(e, statusNotEqual);
+
+ final ConntrackEvent timeoutSecNotEqual = new ConntrackEvent(e.msgType,
+ e.tupleOrig, e.tupleReply, e.status, e.timeoutSec + 1 /* diff */);
+ assertNotEquals(e, timeoutSecNotEqual);
+ }
+
+ public static final String CT_V4DELETE_TCP_HEX =
+ // CHECKSTYLE:OFF IndentationCheck
+ // struct nlmsghdr
+ "84000000" + // length = 132
+ "0201" + // type = NFNL_SUBSYS_CTNETLINK (1) << 8 | IPCTNL_MSG_CT_DELETE (2)
+ "0000" + // flags = 0
+ "00000000" + // seqno = 0
+ "00000000" + // pid = 0
+ // struct nfgenmsg
+ "02" + // nfgen_family = AF_INET
+ "00" + // version = NFNETLINK_V0
+ "1234" + // res_id = 0x1234 (big endian)
+ // struct nlattr
+ "3400" + // nla_len = 52
+ "0180" + // nla_type = nested CTA_TUPLE_ORIG
+ // struct nlattr
+ "1400" + // nla_len = 20
+ "0180" + // nla_type = nested CTA_TUPLE_IP
+ "0800 0100 C0A8500C" + // nla_type=CTA_IP_V4_SRC, ip=192.168.80.12
+ "0800 0200 8C700874" + // nla_type=CTA_IP_V4_DST, ip=140.112.8.116
+ // struct nlattr
+ "1C00" + // nla_len = 28
+ "0280" + // nla_type = nested CTA_TUPLE_PROTO
+ "0500 0100 06 000000" + // nla_type=CTA_PROTO_NUM, proto=IPPROTO_TCP (6)
+ "0600 0200 F3F1 0000" + // nla_type=CTA_PROTO_SRC_PORT, port=62449 (big endian)
+ "0600 0300 01BB 0000" + // nla_type=CTA_PROTO_DST_PORT, port=433 (big endian)
+ // struct nlattr
+ "3400" + // nla_len = 52
+ "0280" + // nla_type = nested CTA_TUPLE_REPLY
+ // struct nlattr
+ "1400" + // nla_len = 20
+ "0180" + // nla_type = nested CTA_TUPLE_IP
+ "0800 0100 8C700874" + // nla_type=CTA_IP_V4_SRC, ip=140.112.8.116
+ "0800 0200 6451B301" + // nla_type=CTA_IP_V4_DST, ip=100.81.179.1
+ // struct nlattr
+ "1C00" + // nla_len = 28
+ "0280" + // nla_type = nested CTA_TUPLE_PROTO
+ "0500 0100 06 000000" + // nla_type=CTA_PROTO_NUM, proto=IPPROTO_TCP (6)
+ "0600 0200 01BB 0000" + // nla_type=CTA_PROTO_SRC_PORT, port=433 (big endian)
+ "0600 0300 F3F1 0000" + // nla_type=CTA_PROTO_DST_PORT, port=62449 (big endian)
+ // struct nlattr
+ "0800" + // nla_len = 8
+ "0300" + // nla_type = CTA_STATUS
+ "0000039E"; // nla_value = 0b1110011110 (big endian)
+ // IPS_SEEN_REPLY (1 << 1) | IPS_ASSURED (1 << 2) |
+ // IPS_CONFIRMED (1 << 3) | IPS_SRC_NAT (1 << 4) |
+ // IPS_SRC_NAT_DONE (1 << 7) | IPS_DST_NAT_DONE (1 << 8) |
+ // IPS_DYING (1 << 9)
+ // CHECKSTYLE:ON IndentationCheck
+ public static final byte[] CT_V4DELETE_TCP_BYTES =
+ HexEncoding.decode(CT_V4DELETE_TCP_HEX.replaceAll(" ", "").toCharArray(), false);
+
+ @Test
+ public void testConntrackEventDelete() throws Exception {
+ final ConntrackEvent expectedEvent =
+ makeTestConntrackEvent(IPCTNL_MSG_CT_DELETE, 0x39e /* status */,
+ 0 /* timeoutSec (absent) */);
+ mConntrackMonitor.sendMessage(CT_V4DELETE_TCP_BYTES);
+ verify(mConsumer, timeout(TIMEOUT_MS)).accept(eq(expectedEvent));
+ }
+
}
diff --git a/tests/unit/src/android/net/netlink/ConntrackMessageTest.java b/tests/unit/src/android/net/netlink/ConntrackMessageTest.java
index 18a0c52..575d53e 100644
--- a/tests/unit/src/android/net/netlink/ConntrackMessageTest.java
+++ b/tests/unit/src/android/net/netlink/ConntrackMessageTest.java
@@ -22,6 +22,7 @@
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
@@ -39,6 +40,7 @@
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
+import java.util.Arrays;
@RunWith(AndroidJUnit4.class)
@SmallTest
@@ -82,6 +84,14 @@
public static final byte[] CT_V4UPDATE_TCP_BYTES =
HexEncoding.decode(CT_V4UPDATE_TCP_HEX.replaceAll(" ", "").toCharArray(), false);
+ private byte[] makeIPv4TimeoutUpdateRequestTcp() throws Exception {
+ return ConntrackMessage.newIPv4TimeoutUpdateRequest(
+ OsConstants.IPPROTO_TCP,
+ (Inet4Address) InetAddress.getByName("192.168.43.209"), 44333,
+ (Inet4Address) InetAddress.getByName("23.211.13.26"), 443,
+ 432000);
+ }
+
// Example 2: UDP (100.96.167.146, 37069) -> (216.58.197.10, 443)
public static final String CT_V4UPDATE_UDP_HEX =
// struct nlmsghdr
@@ -115,17 +125,27 @@
public static final byte[] CT_V4UPDATE_UDP_BYTES =
HexEncoding.decode(CT_V4UPDATE_UDP_HEX.replaceAll(" ", "").toCharArray(), false);
+ private byte[] makeIPv4TimeoutUpdateRequestUdp() throws Exception {
+ return ConntrackMessage.newIPv4TimeoutUpdateRequest(
+ OsConstants.IPPROTO_UDP,
+ (Inet4Address) InetAddress.getByName("100.96.167.146"), 37069,
+ (Inet4Address) InetAddress.getByName("216.58.197.10"), 443,
+ 180);
+ }
+
@Test
- public void testConntrackIPv4TcpTimeoutUpdate() throws Exception {
+ public void testConntrackMakeIPv4TcpTimeoutUpdate() throws Exception {
assumeTrue(USING_LE);
- final byte[] tcp = ConntrackMessage.newIPv4TimeoutUpdateRequest(
- OsConstants.IPPROTO_TCP,
- (Inet4Address) InetAddress.getByName("192.168.43.209"), 44333,
- (Inet4Address) InetAddress.getByName("23.211.13.26"), 443,
- 432000);
+ final byte[] tcp = makeIPv4TimeoutUpdateRequestTcp();
assertArrayEquals(CT_V4UPDATE_TCP_BYTES, tcp);
+ }
+ @Test
+ public void testConntrackParseIPv4TcpTimeoutUpdate() throws Exception {
+ assumeTrue(USING_LE);
+
+ final byte[] tcp = makeIPv4TimeoutUpdateRequestTcp();
final ByteBuffer byteBuffer = ByteBuffer.wrap(tcp);
byteBuffer.order(ByteOrder.nativeOrder());
final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, OsConstants.NETLINK_NETFILTER);
@@ -142,26 +162,39 @@
assertEquals(1, hdr.nlmsg_seq);
assertEquals(0, hdr.nlmsg_pid);
- final StructNfGenMsg nfmsgHdr = conntrackMessage.getNfHeader();
+ final StructNfGenMsg nfmsgHdr = conntrackMessage.nfGenMsg;
assertNotNull(nfmsgHdr);
assertEquals((byte) OsConstants.AF_INET, nfmsgHdr.nfgen_family);
assertEquals((byte) StructNfGenMsg.NFNETLINK_V0, nfmsgHdr.version);
assertEquals((short) 0, nfmsgHdr.res_id);
- // TODO: Parse the CTA_TUPLE_ORIG and CTA_TIMEOUT.
+ assertEquals(InetAddress.parseNumericAddress("192.168.43.209"),
+ conntrackMessage.tupleOrig.srcIp);
+ assertEquals(InetAddress.parseNumericAddress("23.211.13.26"),
+ conntrackMessage.tupleOrig.dstIp);
+ assertEquals((byte) OsConstants.IPPROTO_TCP, conntrackMessage.tupleOrig.protoNum);
+ assertEquals((short) 44333, conntrackMessage.tupleOrig.srcPort);
+ assertEquals((short) 443, conntrackMessage.tupleOrig.dstPort);
+
+ assertNull(conntrackMessage.tupleReply);
+
+ assertEquals(0 /* absent */, conntrackMessage.status);
+ assertEquals(432000, conntrackMessage.timeoutSec);
}
@Test
- public void testConntrackIPv4UdpTimeoutUpdate() throws Exception {
+ public void testConntrackMakeIPv4UdpTimeoutUpdate() throws Exception {
assumeTrue(USING_LE);
- final byte[] udp = ConntrackMessage.newIPv4TimeoutUpdateRequest(
- OsConstants.IPPROTO_UDP,
- (Inet4Address) InetAddress.getByName("100.96.167.146"), 37069,
- (Inet4Address) InetAddress.getByName("216.58.197.10"), 443,
- 180);
+ final byte[] udp = makeIPv4TimeoutUpdateRequestUdp();
assertArrayEquals(CT_V4UPDATE_UDP_BYTES, udp);
+ }
+ @Test
+ public void testConntrackParseIPv4UdpTimeoutUpdate() throws Exception {
+ assumeTrue(USING_LE);
+
+ final byte[] udp = makeIPv4TimeoutUpdateRequestUdp();
final ByteBuffer byteBuffer = ByteBuffer.wrap(udp);
byteBuffer.order(ByteOrder.nativeOrder());
final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, OsConstants.NETLINK_NETFILTER);
@@ -178,37 +211,85 @@
assertEquals(1, hdr.nlmsg_seq);
assertEquals(0, hdr.nlmsg_pid);
- final StructNfGenMsg nfmsgHdr = conntrackMessage.getNfHeader();
+ final StructNfGenMsg nfmsgHdr = conntrackMessage.nfGenMsg;
assertNotNull(nfmsgHdr);
assertEquals((byte) OsConstants.AF_INET, nfmsgHdr.nfgen_family);
assertEquals((byte) StructNfGenMsg.NFNETLINK_V0, nfmsgHdr.version);
assertEquals((short) 0, nfmsgHdr.res_id);
- // TODO: Parse the CTA_TUPLE_ORIG and CTA_TIMEOUT.
+ assertEquals(InetAddress.parseNumericAddress("100.96.167.146"),
+ conntrackMessage.tupleOrig.srcIp);
+ assertEquals(InetAddress.parseNumericAddress("216.58.197.10"),
+ conntrackMessage.tupleOrig.dstIp);
+ assertEquals((byte) OsConstants.IPPROTO_UDP, conntrackMessage.tupleOrig.protoNum);
+ assertEquals((short) 37069, conntrackMessage.tupleOrig.srcPort);
+ assertEquals((short) 443, conntrackMessage.tupleOrig.dstPort);
+
+ assertNull(conntrackMessage.tupleReply);
+
+ assertEquals(0 /* absent */, conntrackMessage.status);
+ assertEquals(180, conntrackMessage.timeoutSec);
}
- // TODO: Add conntrack message attributes to have further verification.
- public static final String CT_V4NEW_HEX =
+ public static final String CT_V4NEW_TCP_HEX =
// CHECKSTYLE:OFF IndentationCheck
// struct nlmsghdr
- "14000000" + // length = 20
+ "8C000000" + // length = 140
"0001" + // type = NFNL_SUBSYS_CTNETLINK (1) << 8 | IPCTNL_MSG_CT_NEW (0)
- "0006" + // flags = NLM_F_CREATE | NLM_F_EXCL
+ "0006" + // flags = NLM_F_CREATE (1 << 10) | NLM_F_EXCL (1 << 9)
"00000000" + // seqno = 0
"00000000" + // pid = 0
// struct nfgenmsg
"02" + // nfgen_family = AF_INET
"00" + // version = NFNETLINK_V0
- "1234"; // res_id = 0x1234 (big endian)
+ "1234" + // res_id = 0x1234 (big endian)
+ // struct nlattr
+ "3400" + // nla_len = 52
+ "0180" + // nla_type = nested CTA_TUPLE_ORIG
+ // struct nlattr
+ "1400" + // nla_len = 20
+ "0180" + // nla_type = nested CTA_TUPLE_IP
+ "0800 0100 C0A8500C" + // nla_type=CTA_IP_V4_SRC, ip=192.168.80.12
+ "0800 0200 8C700874" + // nla_type=CTA_IP_V4_DST, ip=140.112.8.116
+ // struct nlattr
+ "1C00" + // nla_len = 28
+ "0280" + // nla_type = nested CTA_TUPLE_PROTO
+ "0500 0100 06 000000" + // nla_type=CTA_PROTO_NUM, proto=IPPROTO_TCP (6)
+ "0600 0200 F3F1 0000" + // nla_type=CTA_PROTO_SRC_PORT, port=62449 (big endian)
+ "0600 0300 01BB 0000" + // nla_type=CTA_PROTO_DST_PORT, port=443 (big endian)
+ // struct nlattr
+ "3400" + // nla_len = 52
+ "0280" + // nla_type = nested CTA_TUPLE_REPLY
+ // struct nlattr
+ "1400" + // nla_len = 20
+ "0180" + // nla_type = nested CTA_TUPLE_IP
+ "0800 0100 8C700874" + // nla_type=CTA_IP_V4_SRC, ip=140.112.8.116
+ "0800 0200 6451B301" + // nla_type=CTA_IP_V4_DST, ip=100.81.179.1
+ // struct nlattr
+ "1C00" + // nla_len = 28
+ "0280" + // nla_type = nested CTA_TUPLE_PROTO
+ "0500 0100 06 000000" + // nla_type=CTA_PROTO_NUM, proto=IPPROTO_TCP (6)
+ "0600 0200 01BB 0000" + // nla_type=CTA_PROTO_SRC_PORT, port=443 (big endian)
+ "0600 0300 F3F1 0000" + // nla_type=CTA_PROTO_DST_PORT, port=62449 (big endian)
+ // struct nlattr
+ "0800" + // nla_len = 8
+ "0300" + // nla_type = CTA_STATUS
+ "00000198" + // nla_value = 0b110011000 (big endian)
+ // IPS_CONFIRMED (1 << 3) | IPS_SRC_NAT (1 << 4) |
+ // IPS_SRC_NAT_DONE (1 << 7) | IPS_DST_NAT_DONE (1 << 8)
+ // struct nlattr
+ "0800" + // nla_len = 8
+ "0700" + // nla_type = CTA_TIMEOUT
+ "00000078"; // nla_value = 120 (big endian)
// CHECKSTYLE:ON IndentationCheck
- public static final byte[] CT_V4NEW_BYTES =
- HexEncoding.decode(CT_V4NEW_HEX.replaceAll(" ", "").toCharArray(), false);
+ public static final byte[] CT_V4NEW_TCP_BYTES =
+ HexEncoding.decode(CT_V4NEW_TCP_HEX.replaceAll(" ", "").toCharArray(), false);
@Test
public void testParseCtNew() {
assumeTrue(USING_LE);
- final ByteBuffer byteBuffer = ByteBuffer.wrap(CT_V4NEW_BYTES);
+ final ByteBuffer byteBuffer = ByteBuffer.wrap(CT_V4NEW_TCP_BYTES);
byteBuffer.order(ByteOrder.nativeOrder());
final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, OsConstants.NETLINK_NETFILTER);
assertNotNull(msg);
@@ -217,17 +298,108 @@
final StructNlMsgHdr hdr = conntrackMessage.getHeader();
assertNotNull(hdr);
- assertEquals(20, hdr.nlmsg_len);
+ assertEquals(140, hdr.nlmsg_len);
assertEquals(makeCtType(IPCTNL_MSG_CT_NEW), hdr.nlmsg_type);
assertEquals((short) (StructNlMsgHdr.NLM_F_CREATE | StructNlMsgHdr.NLM_F_EXCL),
hdr.nlmsg_flags);
assertEquals(0, hdr.nlmsg_seq);
assertEquals(0, hdr.nlmsg_pid);
- final StructNfGenMsg nfmsgHdr = conntrackMessage.getNfHeader();
+ final StructNfGenMsg nfmsgHdr = conntrackMessage.nfGenMsg;
assertNotNull(nfmsgHdr);
assertEquals((byte) OsConstants.AF_INET, nfmsgHdr.nfgen_family);
assertEquals((byte) StructNfGenMsg.NFNETLINK_V0, nfmsgHdr.version);
assertEquals((short) 0x1234, nfmsgHdr.res_id);
+
+ assertEquals(InetAddress.parseNumericAddress("192.168.80.12"),
+ conntrackMessage.tupleOrig.srcIp);
+ assertEquals(InetAddress.parseNumericAddress("140.112.8.116"),
+ conntrackMessage.tupleOrig.dstIp);
+ assertEquals((byte) OsConstants.IPPROTO_TCP, conntrackMessage.tupleOrig.protoNum);
+ assertEquals((short) 62449, conntrackMessage.tupleOrig.srcPort);
+ assertEquals((short) 443, conntrackMessage.tupleOrig.dstPort);
+
+ assertEquals(InetAddress.parseNumericAddress("140.112.8.116"),
+ conntrackMessage.tupleReply.srcIp);
+ assertEquals(InetAddress.parseNumericAddress("100.81.179.1"),
+ conntrackMessage.tupleReply.dstIp);
+ assertEquals((byte) OsConstants.IPPROTO_TCP, conntrackMessage.tupleReply.protoNum);
+ assertEquals((short) 443, conntrackMessage.tupleReply.srcPort);
+ assertEquals((short) 62449, conntrackMessage.tupleReply.dstPort);
+
+ assertEquals(0x198, conntrackMessage.status);
+ assertEquals(120, conntrackMessage.timeoutSec);
+ }
+
+ @Test
+ public void testParseTruncation() {
+ assumeTrue(USING_LE);
+
+ // Expect no crash while parsing the truncated message which has been truncated to every
+ // length between 0 and its full length - 1.
+ for (int len = 0; len < CT_V4NEW_TCP_BYTES.length; len++) {
+ final byte[] truncated = Arrays.copyOfRange(CT_V4NEW_TCP_BYTES, 0, len);
+
+ final ByteBuffer byteBuffer = ByteBuffer.wrap(truncated);
+ byteBuffer.order(ByteOrder.nativeOrder());
+ final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer,
+ OsConstants.NETLINK_NETFILTER);
+ }
+ }
+
+ @Test
+ public void testParseTruncationWithInvalidByte() {
+ assumeTrue(USING_LE);
+
+ // Expect no crash while parsing the message which is truncated by invalid bytes. The
+ // message has been truncated to every length between 0 and its full length - 1.
+ for (byte invalid : new byte[]{(byte) 0x00, (byte) 0xff}) {
+ for (int len = 0; len < CT_V4NEW_TCP_BYTES.length; len++) {
+ final byte[] truncated = new byte[CT_V4NEW_TCP_BYTES.length];
+ Arrays.fill(truncated, (byte) invalid);
+ System.arraycopy(CT_V4NEW_TCP_BYTES, 0, truncated, 0, len);
+
+ final ByteBuffer byteBuffer = ByteBuffer.wrap(truncated);
+ byteBuffer.order(ByteOrder.nativeOrder());
+ final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer,
+ OsConstants.NETLINK_NETFILTER);
+ }
+ }
+ }
+
+ // Malformed conntrack messages.
+ public static final String CT_MALFORMED_HEX =
+ // CHECKSTYLE:OFF IndentationCheck
+ // <-- nlmsghr -->|<-nfgenmsg->|<-- CTA_TUPLE_ORIG -->|
+ // CTA_TUPLE_ORIG has no nla_value.
+ "18000000 0001 0006 00000000 00000000 02 00 0000 0400 0180"
+ // nested CTA_TUPLE_IP has no nla_value.
+ + "1C000000 0001 0006 00000000 00000000 02 00 0000 0800 0180 0400 0180"
+ // nested CTA_IP_V4_SRC has no nla_value.
+ + "20000000 0001 0006 00000000 00000000 02 00 0000 0C00 0180 0800 0180 0400 0100"
+ // nested CTA_TUPLE_PROTO has no nla_value.
+ // <-- nlmsghr -->|<-nfgenmsg->|<-- CTA_TUPLE_ORIG
+ + "30000000 0001 0006 00000000 00000000 02 00 0000 1C00 0180 1400 0180 0800 0100"
+ // -->|
+ + "C0A8500C 0800 0200 8C700874 0400 0280";
+ // CHECKSTYLE:ON IndentationCheck
+ public static final byte[] CT_MALFORMED_BYTES =
+ HexEncoding.decode(CT_MALFORMED_HEX.replaceAll(" ", "").toCharArray(), false);
+
+ @Test
+ public void testParseMalformation() {
+ assumeTrue(USING_LE);
+
+ final ByteBuffer byteBuffer = ByteBuffer.wrap(CT_MALFORMED_BYTES);
+ byteBuffer.order(ByteOrder.nativeOrder());
+
+ // Expect no crash while parsing the malformed message.
+ int messageCount = 0;
+ while (byteBuffer.remaining() > 0) {
+ final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer,
+ OsConstants.NETLINK_NETFILTER);
+ messageCount++;
+ }
+ assertEquals(4, messageCount);
}
}
diff --git a/tests/unit/src/android/net/netlink/RtNetlinkNeighborMessageTest.java b/tests/unit/src/android/net/netlink/RtNetlinkNeighborMessageTest.java
index c3f9068..a1f1d44 100644
--- a/tests/unit/src/android/net/netlink/RtNetlinkNeighborMessageTest.java
+++ b/tests/unit/src/android/net/netlink/RtNetlinkNeighborMessageTest.java
@@ -151,6 +151,13 @@
assertEquals(0, hdr.nlmsg_seq);
assertEquals(11070, hdr.nlmsg_pid);
+ final int probes = neighMsg.getProbes();
+ assertTrue("Unexpected number of probes. Got " + probes + ", max=5",
+ probes < 5);
+ final int ndm_refcnt = neighMsg.getCacheInfo().ndm_refcnt;
+ assertTrue("nda_cacheinfo has unexpectedly high ndm_refcnt: " + ndm_refcnt,
+ ndm_refcnt < 0x100);
+
messageCount++;
}
// TODO: add more detailed spot checks.
diff --git a/tests/unit/src/com/android/net/module/util/TrackRecordTest.kt b/tests/unit/src/com/android/net/module/util/TrackRecordTest.kt
index c2bdcd4..9fb4d8c 100644
--- a/tests/unit/src/com/android/net/module/util/TrackRecordTest.kt
+++ b/tests/unit/src/com/android/net/module/util/TrackRecordTest.kt
@@ -30,6 +30,7 @@
import org.junit.runners.JUnit4
import java.util.concurrent.CyclicBarrier
import java.util.concurrent.TimeUnit
+import java.util.concurrent.atomic.AtomicInteger
import kotlin.system.measureTimeMillis
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
@@ -183,6 +184,33 @@
}
@Test
+ fun testConcurrentPollDisallowed() {
+ val failures = AtomicInteger(0)
+ val readHead = ArrayTrackRecord<Int>().newReadHead()
+ val barrier = CyclicBarrier(2)
+ Thread {
+ barrier.await(LONG_TIMEOUT, TimeUnit.MILLISECONDS) // barrier 1
+ try {
+ readHead.poll(LONG_TIMEOUT)
+ } catch (e: ConcurrentModificationException) {
+ failures.incrementAndGet()
+ // Unblock the other thread
+ readHead.add(0)
+ }
+ }.start()
+ barrier.await() // barrier 1
+ try {
+ readHead.poll(LONG_TIMEOUT)
+ } catch (e: ConcurrentModificationException) {
+ failures.incrementAndGet()
+ // Unblock the other thread
+ readHead.add(0)
+ }
+ // One of the threads must have gotten an exception.
+ assertEquals(failures.get(), 1)
+ }
+
+ @Test
fun testPollWakesUp() {
val record = ArrayTrackRecord<Int>()
val barrier = CyclicBarrier(2)
diff --git a/tests/unit/src/com/android/networkstack/NetworkStackNotifierTest.kt b/tests/unit/src/com/android/networkstack/NetworkStackNotifierTest.kt
index 348392d..3b44597 100644
--- a/tests/unit/src/com/android/networkstack/NetworkStackNotifierTest.kt
+++ b/tests/unit/src/com/android/networkstack/NetworkStackNotifierTest.kt
@@ -112,6 +112,22 @@
}
}
+ private val mTestCapportVenueUrlWithFriendlyNameLp by lazy {
+ LinkProperties().apply {
+ captivePortalData = CaptivePortalData.Builder()
+ .setCaptive(false)
+ .setVenueInfoUrl(Uri.parse(TEST_VENUE_INFO_URL))
+ .build()
+ val networkShim = NetworkInformationShimImpl.newInstance()
+ val captivePortalDataShim = networkShim.getCaptivePortalData(this)
+
+ if (captivePortalDataShim != null) {
+ networkShim.setCaptivePortalData(this, captivePortalDataShim
+ .withVenueFriendlyName(TEST_NETWORK_FRIENDLY_NAME))
+ }
+ }
+ }
+
private val TEST_NETWORK = Network(42)
private val TEST_NETWORK_TAG = TEST_NETWORK.networkHandle.toString()
private val TEST_SSID = "TestSsid"
@@ -125,6 +141,7 @@
private val TEST_VENUE_INFO_URL = "https://testvenue.example.com/info"
private val EMPTY_CAPPORT_LP = LinkProperties()
+ private val TEST_NETWORK_FRIENDLY_NAME = "Network Friendly Name"
@Before
fun setUp() {
@@ -333,6 +350,28 @@
verify(mNm, never()).notify(eq(TEST_NETWORK_TAG), anyInt(), any())
}
+ @Test
+ fun testConnectedVenueInfoWithFriendlyNameNotification() {
+ // Venue info (CaptivePortalData) with friendly name is not available for API <= R
+ assumeTrue(NetworkInformationShimImpl.useApiAboveR())
+ mNotifier.notifyCaptivePortalValidationPending(TEST_NETWORK)
+ onLinkPropertiesChanged(mTestCapportVenueUrlWithFriendlyNameLp)
+ onDefaultNetworkAvailable(TEST_NETWORK)
+ val capabilities = NetworkCapabilities(VALIDATED_CAPABILITIES).setSSID(TEST_SSID)
+ onCapabilitiesChanged(capabilities)
+
+ mLooper.processAllMessages()
+
+ verifyConnectedNotification(timeout = 0)
+ verifyVenueInfoIntent(mIntentCaptor.value)
+ verify(mResources).getString(R.string.tap_for_info)
+ verify(mNm).notify(eq(TEST_NETWORK_TAG), mNoteIdCaptor.capture(), mNoteCaptor.capture())
+ val note = mNoteCaptor.value
+ assertEquals(TEST_NETWORK_FRIENDLY_NAME, note.extras
+ .getCharSequence(Notification.EXTRA_TITLE))
+ verifyCanceledNotificationAfterDefaultNetworkLost()
+ }
+
private fun verifyVenueInfoIntent(intent: Intent) {
assertEquals(Intent.ACTION_VIEW, intent.action)
assertEquals(Uri.parse(TEST_VENUE_INFO_URL), intent.data)
diff --git a/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java b/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
index 4d032c2..8f42a61 100644
--- a/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
+++ b/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
@@ -2285,9 +2285,10 @@
setStatus(mFallbackConnection, 204);
nm.forceReevaluation(Process.myUid());
// Expect to send HTTP, HTTPs, FALLBACK and evaluation results.
- runNetworkTest(VALIDATION_RESULT_INVALID,
+ verifyNetworkTested(VALIDATION_RESULT_INVALID,
NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_FALLBACK,
null /* redirectUrl */);
+ HandlerUtils.waitForIdle(nm.getHandler(), HANDLER_TIMEOUT_MS);
}
@Test