Merge "Support for Venue friendly name"
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 30a1165..7f34547 100644
--- a/common/netlinkclient/src/android/net/netlink/ConntrackMessage.java
+++ b/common/netlinkclient/src/android/net/netlink/ConntrackMessage.java
@@ -16,18 +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;
/**
@@ -59,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 ***
@@ -113,19 +222,33 @@
}
final int baseOffset = byteBuffer.position();
- StructNlAttr nlAttr = StructNlAttr.findNextAttrOfType(CTA_STATUS, byteBuffer);
+ StructNlAttr nlAttr = findNextAttrOfType(CTA_STATUS, byteBuffer);
int status = 0;
if (nlAttr != null) {
status = nlAttr.getValueAsBe32(0);
}
byteBuffer.position(baseOffset);
- nlAttr = StructNlAttr.findNextAttrOfType(CTA_TIMEOUT, byteBuffer);
+ 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;
@@ -136,7 +259,126 @@
}
byteBuffer.position(baseOffset + kAdditionalSpace);
- return new ConntrackMessage(header, nfGenMsg, status, timeoutSec);
+ return new ConntrackMessage(header, nfGenMsg, tupleOrig, tupleReply, status, timeoutSec);
+ }
+
+ /**
+ * 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);
}
/**
@@ -144,6 +386,22 @@
*/
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
@@ -165,14 +423,21 @@
private ConntrackMessage() {
super(new StructNlMsgHdr());
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(@NonNull StructNlMsgHdr header, @NonNull StructNfGenMsg nfGenMsg,
- int status, int timeoutSec) {
+ @Nullable Tuple tupleOrig, @Nullable Tuple tupleReply, int status, int timeoutSec) {
super(header);
this.nfGenMsg = nfGenMsg;
+ this.tupleOrig = tupleOrig;
+ this.tupleReply = tupleReply;
this.status = status;
this.timeoutSec = timeoutSec;
}
@@ -181,4 +446,8 @@
mHeader.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/StructNlAttr.java b/common/netlinkclient/src/android/net/netlink/StructNlAttr.java
index a3a26b9..b6e1d3f 100644
--- a/common/netlinkclient/src/android/net/netlink/StructNlAttr.java
+++ b/common/netlinkclient/src/android/net/netlink/StructNlAttr.java
@@ -183,6 +183,23 @@
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) {
@@ -207,6 +224,17 @@
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/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 6af1046..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
@@ -166,7 +168,16 @@
assertEquals((byte) StructNfGenMsg.NFNETLINK_V0, nfmsgHdr.version);
assertEquals((short) 0, nfmsgHdr.res_id);
- // TODO: Parse the CTA_TUPLE_ORIG.
+ 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);
}
@@ -206,24 +217,60 @@
assertEquals((byte) StructNfGenMsg.NFNETLINK_V0, nfmsgHdr.version);
assertEquals((short) 0, nfmsgHdr.res_id);
- // TODO: Parse the CTA_TUPLE_ORIG.
+ 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
- "24000000" + // length = 36
+ "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)
+ // 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
@@ -235,14 +282,14 @@
"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);
@@ -251,7 +298,7 @@
final StructNlMsgHdr hdr = conntrackMessage.getHeader();
assertNotNull(hdr);
- assertEquals(36, 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);
@@ -264,7 +311,95 @@
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/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)