diff options
author | Aaron Huang <huangaaron@google.com> | 2019-04-20 00:08:24 +0800 |
---|---|---|
committer | Aaron Huang <huangaaron@google.com> | 2019-05-10 22:41:48 +0800 |
commit | fbe2cdd4235b970583019e1cc2836b3a7a429f44 (patch) | |
tree | c0e4ab29ffced3075a6efd5505c5a68fa2b9ceb6 | |
parent | ffa16b2cd5a9fe56d127269269ec632e3185a3c5 (diff) |
Dropping NAT-T keepalive packet from APF
Add NAT Traversal keepalive filter to APF program to
drop NAT-T keepalive packets when NAT-T keepalive
offload is starting.
Bug: 33530442
Test: - atest NetworkStackTests
- atest FrameworksNetTests
Change-Id: I1c537485e11b31e5a6e0d8b7b6a1f396f9441746
-rw-r--r-- | src/android/net/apf/ApfFilter.java | 287 | ||||
-rw-r--r-- | src/android/net/util/NetworkStackUtils.java | 11 | ||||
-rw-r--r-- | tests/src/android/net/apf/ApfTest.java | 150 |
3 files changed, 341 insertions, 107 deletions
diff --git a/src/android/net/apf/ApfFilter.java b/src/android/net/apf/ApfFilter.java index 359c859..2e1e96e 100644 --- a/src/android/net/apf/ApfFilter.java +++ b/src/android/net/apf/ApfFilter.java @@ -155,7 +155,9 @@ public class ApfFilter { DROPPED_ETHERTYPE_BLACKLISTED, DROPPED_ARP_REPLY_SPA_NO_HOST, DROPPED_IPV4_KEEPALIVE_ACK, - DROPPED_IPV6_KEEPALIVE_ACK; + DROPPED_IPV6_KEEPALIVE_ACK, + DROPPED_IPV4_NATT_KEEPALIVE, + DROPPED_IPV6_NATT_KEEPALIVE; // Returns the negative byte offset from the end of the APF data segment for // a given counter. @@ -857,12 +859,129 @@ public class ApfFilter { } } - // A class to hold keepalive ack information. - private abstract static class TcpKeepaliveAck { + // TODO: Refactor these subclasses to avoid so much repetition. + private abstract static class KeepalivePacket { // Note that the offset starts from IP header. // These must be added ether header length when generating program. static final int IP_HEADER_OFFSET = 0; + static final int IPV4_SRC_ADDR_OFFSET = IP_HEADER_OFFSET + 12; + // Append a filter for this keepalive ack to {@code gen}. + // Jump to drop if it matches the keepalive ack. + // Jump to the next filter if packet doesn't match the keepalive ack. + abstract void generateFilterLocked(ApfGenerator gen) throws IllegalInstructionException; + } + + // A class to hold NAT-T keepalive ack information. + private abstract static class NattKeepaliveAck extends KeepalivePacket { + static final int UDP_LENGTH_OFFSET = 4; + static final int UDP_HEADER_LEN = 8; + + protected static class NattKeepaliveAckData { + public final byte[] srcAddress; + public final int srcPort; + public final byte[] dstAddress; + public final int dstPort; + + NattKeepaliveAckData(final NattKeepalivePacketDataParcelable sentKeepalivePacket) { + srcAddress = sentKeepalivePacket.dstAddress; + srcPort = sentKeepalivePacket.dstPort; + dstAddress = sentKeepalivePacket.srcAddress; + dstPort = sentKeepalivePacket.srcPort; + } + } + + protected final NattKeepaliveAckData mPacket; + protected final byte[] mSrcDstAddr; + protected final byte[] mPortFingerprint; + // NAT-T keepalive packet + protected final byte[] mPayload = {(byte) 0xff}; + + NattKeepaliveAck(final NattKeepaliveAckData packet, final byte[] srcDstAddr) { + mPacket = packet; + mSrcDstAddr = srcDstAddr; + mPortFingerprint = generatePortFingerprint(mPacket.srcPort, mPacket.dstPort); + } + + static byte[] generatePortFingerprint(int srcPort, int dstPort) { + final ByteBuffer fp = ByteBuffer.allocate(4); + fp.order(ByteOrder.BIG_ENDIAN); + fp.putShort((short) srcPort); + fp.putShort((short) dstPort); + return fp.array(); + } + + public String toString() { + try { + return String.format("%s -> %s", + NetworkStackUtils.addressAndPortToString( + InetAddress.getByAddress(mPacket.srcAddress), mPacket.srcPort), + NetworkStackUtils.addressAndPortToString( + InetAddress.getByAddress(mPacket.dstAddress), mPacket.dstPort)); + } catch (UnknownHostException e) { + return "Unknown host"; + } + } + } + + private class NattKeepaliveAckV4 extends NattKeepaliveAck { + NattKeepaliveAckV4(final NattKeepalivePacketDataParcelable sentKeepalivePacket) { + this(new NattKeepaliveAckData(sentKeepalivePacket)); + } + NattKeepaliveAckV4(final NattKeepaliveAckData packet) { + super(packet, concatArrays(packet.srcAddress, packet.dstAddress) /* srcDstAddr */); + } + + @Override + void generateFilterLocked(ApfGenerator gen) throws IllegalInstructionException { + final String nextFilterLabel = "natt_keepalive_filter" + getUniqueNumberLocked(); + + gen.addLoadImmediate(Register.R0, ETH_HEADER_LEN + IPV4_SRC_ADDR_OFFSET); + gen.addJumpIfBytesNotEqual(Register.R0, mSrcDstAddr, nextFilterLabel); + + // A NAT-T keepalive packet contains 1 byte payload with the value 0xff + // Check payload length is 1 + gen.addLoadFromMemory(Register.R0, gen.IPV4_HEADER_SIZE_MEMORY_SLOT); + gen.addAdd(UDP_HEADER_LEN); + gen.addSwap(); + gen.addLoad16(Register.R0, IPV4_TOTAL_LENGTH_OFFSET); + gen.addNeg(Register.R1); + gen.addAddR1(); + gen.addJumpIfR0NotEquals(1, nextFilterLabel); + + // R0 = R0 + R1 -> R0 contains IP header + gen.addLoadFromMemory(Register.R1, gen.IPV4_HEADER_SIZE_MEMORY_SLOT); + gen.addLoadImmediate(Register.R0, ETH_HEADER_LEN); + gen.addAddR1(); + gen.addJumpIfBytesNotEqual(Register.R0, mPortFingerprint, nextFilterLabel); + + // Payload offset = R0 + UDP header length + gen.addAdd(UDP_HEADER_LEN); + gen.addJumpIfBytesNotEqual(Register.R0, mPayload, nextFilterLabel); + + maybeSetupCounter(gen, Counter.DROPPED_IPV4_NATT_KEEPALIVE); + gen.addJump(mCountAndDropLabel); + gen.defineLabel(nextFilterLabel); + } + } + + private class NattKeepaliveAckV6 extends NattKeepaliveAck { + NattKeepaliveAckV6(final NattKeepalivePacketDataParcelable sentKeepalivePacket) { + this(new NattKeepaliveAckData(sentKeepalivePacket)); + } + + NattKeepaliveAckV6(final NattKeepaliveAckData packet) { + super(packet, concatArrays(packet.srcAddress, packet.dstAddress) /* srcDstAddr */); + } + + @Override + void generateFilterLocked(ApfGenerator gen) throws IllegalInstructionException { + throw new UnsupportedOperationException("IPv6 NAT-T Keepalive is not supported yet"); + } + } + + // A class to hold TCP keepalive ack information. + private abstract static class TcpKeepaliveAck extends KeepalivePacket { protected static class TcpKeepaliveAckData { public final byte[] srcAddress; public final int srcPort; @@ -870,6 +989,7 @@ public class ApfFilter { public final int dstPort; public final int seq; public final int ack; + // Create the characteristics of the ack packet from the sent keepalive packet. TcpKeepaliveAckData(final TcpKeepalivePacketDataParcelable sentKeepalivePacket) { srcAddress = sentKeepalivePacket.dstAddress; @@ -902,28 +1022,18 @@ public class ApfFilter { return fp.array(); } - static byte[] concatArrays(final byte[]... arr) { - int size = 0; - for (byte[] a : arr) { - size += a.length; - } - final byte[] result = new byte[size]; - int offset = 0; - for (byte[] a : arr) { - System.arraycopy(a, 0, result, offset, a.length); - offset += a.length; - } - return result; - } - public String toString() { - return String.format("%s(%d) -> %s(%d), seq=%d, ack=%d", - mPacket.srcAddress, - mPacket.srcPort, - mPacket.dstAddress, - mPacket.dstPort, - mPacket.seq, - mPacket.ack); + try { + return String.format("%s -> %s , seq=%d, ack=%d", + NetworkStackUtils.addressAndPortToString( + InetAddress.getByAddress(mPacket.srcAddress), mPacket.srcPort), + NetworkStackUtils.addressAndPortToString( + InetAddress.getByAddress(mPacket.dstAddress), mPacket.dstPort), + Integer.toUnsignedLong(mPacket.seq), + Integer.toUnsignedLong(mPacket.ack)); + } catch (UnknownHostException e) { + return "Unknown host"; + } } // Append a filter for this keepalive ack to {@code gen}. @@ -933,7 +1043,6 @@ public class ApfFilter { } private class TcpKeepaliveAckV4 extends TcpKeepaliveAck { - private static final int IPV4_SRC_ADDR_OFFSET = IP_HEADER_OFFSET + 12; TcpKeepaliveAckV4(final TcpKeepalivePacketDataParcelable sentKeepalivePacket) { this(new TcpKeepaliveAckData(sentKeepalivePacket)); @@ -987,7 +1096,7 @@ public class ApfFilter { @Override void generateFilterLocked(ApfGenerator gen) throws IllegalInstructionException { - throw new UnsupportedOperationException("IPv6 Keepalive is not supported yet"); + throw new UnsupportedOperationException("IPv6 TCP Keepalive is not supported yet"); } } @@ -997,7 +1106,7 @@ public class ApfFilter { @GuardedBy("this") private ArrayList<Ra> mRas = new ArrayList<>(); @GuardedBy("this") - private SparseArray<TcpKeepaliveAck> mKeepaliveAcks = new SparseArray<>(); + private SparseArray<KeepalivePacket> mKeepalivePackets = new SparseArray<>(); // There is always some marginal benefit to updating the installed APF program when an RA is // seen because we can extend the program's lifetime slightly, but there is some cost to @@ -1171,9 +1280,12 @@ public class ApfFilter { gen.addJumpIfR0Equals(broadcastAddr, mCountAndDropLabel); } - // If any keepalive filter matches, drop + // If any TCP keepalive filter matches, drop generateV4KeepaliveFilters(gen); + // If any NAT-T keepalive filter matches, drop + generateV4NattKeepaliveFilters(gen); + // Otherwise, this is an IPv4 unicast, pass // If L2 broadcast packet, drop. // TODO: can we invert this condition to fall through to the common pass case below? @@ -1191,25 +1303,36 @@ public class ApfFilter { gen.addJump(mCountAndPassLabel); } - private void generateV4KeepaliveFilters(ApfGenerator gen) throws IllegalInstructionException { - final String skipV4KeepaliveFilter = "skip_v4_keepalive_filter"; - final boolean haveV4KeepaliveAcks = NetworkStackUtils.any(mKeepaliveAcks, - ack -> ack instanceof TcpKeepaliveAckV4); + private void generateFilters(ApfGenerator gen, Class<?> filterType, int proto, int offset, + String label) throws IllegalInstructionException { + final boolean haveKeepaliveAcks = NetworkStackUtils.any(mKeepalivePackets, + ack -> filterType.isInstance(ack)); - // If no keepalive acks - if (!haveV4KeepaliveAcks) return; + // If no keepalive packets of this type + if (!haveKeepaliveAcks) return; - // If not tcp, skip keepalive filters - gen.addLoad8(Register.R0, IPV4_PROTOCOL_OFFSET); - gen.addJumpIfR0NotEquals(IPPROTO_TCP, skipV4KeepaliveFilter); + // If not the right proto, skip keepalive filters + gen.addLoad8(Register.R0, offset); + gen.addJumpIfR0NotEquals(proto, label); - // Drop IPv4 Keepalive acks - for (int i = 0; i < mKeepaliveAcks.size(); ++i) { - final TcpKeepaliveAck ack = mKeepaliveAcks.valueAt(i); - if (ack instanceof TcpKeepaliveAckV4) ack.generateFilterLocked(gen); + // Drop Keepalive packets + for (int i = 0; i < mKeepalivePackets.size(); ++i) { + final KeepalivePacket ack = mKeepalivePackets.valueAt(i); + if (filterType.isInstance(ack)) ack.generateFilterLocked(gen); } - gen.defineLabel(skipV4KeepaliveFilter); + gen.defineLabel(label); + } + + private void generateV4KeepaliveFilters(ApfGenerator gen) throws IllegalInstructionException { + generateFilters(gen, TcpKeepaliveAckV4.class, IPPROTO_TCP, IPV4_PROTOCOL_OFFSET, + "skip_v4_keepalive_filter"); + } + + private void generateV4NattKeepaliveFilters(ApfGenerator gen) + throws IllegalInstructionException { + generateFilters(gen, NattKeepaliveAckV4.class, IPPROTO_UDP, IPV4_PROTOCOL_OFFSET, + "skip_v4_nattkeepalive_filter"); } /** @@ -1294,24 +1417,8 @@ public class ApfFilter { } private void generateV6KeepaliveFilters(ApfGenerator gen) throws IllegalInstructionException { - final String skipV6KeepaliveFilter = "skip_v6_keepalive_filter"; - final boolean haveV6KeepaliveAcks = NetworkStackUtils.any(mKeepaliveAcks, - ack -> ack instanceof TcpKeepaliveAckV6); - - // If no keepalive acks - if (!haveV6KeepaliveAcks) return; - - // If not tcp, skip keepalive filters - gen.addLoad8(Register.R0, IPV6_NEXT_HEADER_OFFSET); - gen.addJumpIfR0NotEquals(IPPROTO_TCP, skipV6KeepaliveFilter); - - // Drop IPv6 Keepalive acks - for (int i = 0; i < mKeepaliveAcks.size(); ++i) { - final TcpKeepaliveAck ack = mKeepaliveAcks.valueAt(i); - if (ack instanceof TcpKeepaliveAckV6) ack.generateFilterLocked(gen); - } - - gen.defineLabel(skipV6KeepaliveFilter); + generateFilters(gen, TcpKeepaliveAckV6.class, IPPROTO_TCP, IPV6_NEXT_HEADER_OFFSET, + "skip_v6_keepalive_filter"); } /** @@ -1701,11 +1808,11 @@ public class ApfFilter { public synchronized void addTcpKeepalivePacketFilter(final int slot, final TcpKeepalivePacketDataParcelable sentKeepalivePacket) { log("Adding keepalive ack(" + slot + ")"); - if (null != mKeepaliveAcks.get(slot)) { + if (null != mKeepalivePackets.get(slot)) { throw new IllegalArgumentException("Keepalive slot " + slot + " is occupied"); } final int ipVersion = sentKeepalivePacket.srcAddress.length == 4 ? 4 : 6; - mKeepaliveAcks.put(slot, (ipVersion == 4) + mKeepalivePackets.put(slot, (ipVersion == 4) ? new TcpKeepaliveAckV4(sentKeepalivePacket) : new TcpKeepaliveAckV6(sentKeepalivePacket)); installNewProgramLocked(); @@ -1720,7 +1827,15 @@ public class ApfFilter { */ public synchronized void addNattKeepalivePacketFilter(final int slot, final NattKeepalivePacketDataParcelable sentKeepalivePacket) { - Log.e(TAG, "APF add NATT keepalive filter is not implemented"); + log("Adding NAT-T keepalive packet(" + slot + ")"); + if (null != mKeepalivePackets.get(slot)) { + throw new IllegalArgumentException("Natt Keepalive slot " + slot + " is occupied"); + } + final int ipVersion = sentKeepalivePacket.srcAddress.length == 4 ? 4 : 6; + mKeepalivePackets.put(slot, (ipVersion == 4) + ? new NattKeepaliveAckV4(sentKeepalivePacket) + : new NattKeepaliveAckV6(sentKeepalivePacket)); + installNewProgramLocked(); } /** @@ -1729,7 +1844,8 @@ public class ApfFilter { * @param slot The index used to access the filter. */ public synchronized void removeKeepalivePacketFilter(int slot) { - mKeepaliveAcks.remove(slot); + log("Removing keepalive packet(" + slot + ")"); + mKeepalivePackets.remove(slot); installNewProgramLocked(); } @@ -1785,14 +1901,29 @@ public class ApfFilter { } pw.decreaseIndent(); - pw.println("Keepalive filters:"); + pw.println("TCP Keepalive filters:"); + pw.increaseIndent(); + for (int i = 0; i < mKeepalivePackets.size(); ++i) { + final KeepalivePacket keepalivePacket = mKeepalivePackets.valueAt(i); + if (keepalivePacket instanceof TcpKeepaliveAck) { + pw.print("Slot "); + pw.print(mKeepalivePackets.keyAt(i)); + pw.print(" : "); + pw.println(keepalivePacket); + } + } + pw.decreaseIndent(); + + pw.println("NAT-T Keepalive filters:"); pw.increaseIndent(); - for (int i = 0; i < mKeepaliveAcks.size(); ++i) { - final TcpKeepaliveAck keepaliveAck = mKeepaliveAcks.valueAt(i); - pw.print("Slot "); - pw.print(mKeepaliveAcks.keyAt(i)); - pw.print(" : "); - pw.println(keepaliveAck); + for (int i = 0; i < mKeepalivePackets.size(); ++i) { + final KeepalivePacket keepalivePacket = mKeepalivePackets.valueAt(i); + if (keepalivePacket instanceof NattKeepaliveAck) { + pw.print("Slot "); + pw.print(mKeepalivePackets.keyAt(i)); + pw.print(" : "); + pw.println(keepalivePacket); + } } pw.decreaseIndent(); @@ -1858,4 +1989,18 @@ public class ApfFilter { + (uint8(bytes[2]) << 8) + (uint8(bytes[3])); } + + private static byte[] concatArrays(final byte[]... arr) { + int size = 0; + for (byte[] a : arr) { + size += a.length; + } + final byte[] result = new byte[size]; + int offset = 0; + for (byte[] a : arr) { + System.arraycopy(a, 0, result, offset, a.length); + offset += a.length; + } + return result; + } } diff --git a/src/android/net/util/NetworkStackUtils.java b/src/android/net/util/NetworkStackUtils.java index abfed3e..541f9d8 100644 --- a/src/android/net/util/NetworkStackUtils.java +++ b/src/android/net/util/NetworkStackUtils.java @@ -23,6 +23,8 @@ import android.util.SparseArray; import java.io.FileDescriptor; import java.io.IOException; import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; import java.net.SocketException; import java.util.List; import java.util.function.Predicate; @@ -227,4 +229,13 @@ public class NetworkStackUtils { private static native void addArpEntry(byte[] ethAddr, byte[] netAddr, String ifname, FileDescriptor fd) throws IOException; + + /** + * Return IP address and port in a string format. + */ + public static String addressAndPortToString(InetAddress address, int port) { + return String.format( + (address instanceof Inet6Address) ? "[%s]:%d" : "%s:%d", + address.getHostAddress(), port); + } } diff --git a/tests/src/android/net/apf/ApfTest.java b/tests/src/android/net/apf/ApfTest.java index 93ab3be..41c3fab 100644 --- a/tests/src/android/net/apf/ApfTest.java +++ b/tests/src/android/net/apf/ApfTest.java @@ -40,6 +40,7 @@ import static org.mockito.Mockito.verify; import android.content.Context; import android.net.LinkAddress; import android.net.LinkProperties; +import android.net.NattKeepalivePacketDataParcelable; import android.net.TcpKeepalivePacketDataParcelable; import android.net.apf.ApfFilter.ApfConfiguration; import android.net.apf.ApfGenerator.IllegalInstructionException; @@ -998,47 +999,54 @@ public class ApfTest { } } - private static final int ETH_HEADER_LEN = 14; - private static final int ETH_DEST_ADDR_OFFSET = 0; - private static final int ETH_ETHERTYPE_OFFSET = 12; + private static final int ETH_HEADER_LEN = 14; + private static final int ETH_DEST_ADDR_OFFSET = 0; + private static final int ETH_ETHERTYPE_OFFSET = 12; private static final byte[] ETH_BROADCAST_MAC_ADDRESS = {(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff }; - private static final int IPV4_HEADER_LEN = 20; - private static final int IPV4_VERSION_IHL_OFFSET = ETH_HEADER_LEN + 0; + private static final int IPV4_HEADER_LEN = 20; + private static final int IPV4_VERSION_IHL_OFFSET = ETH_HEADER_LEN + 0; private static final int IPV4_TOTAL_LENGTH_OFFSET = ETH_HEADER_LEN + 2; - private static final int IPV4_PROTOCOL_OFFSET = ETH_HEADER_LEN + 9; - private static final int IPV4_SRC_ADDR_OFFSET = ETH_HEADER_LEN + 12; - private static final int IPV4_DEST_ADDR_OFFSET = ETH_HEADER_LEN + 16; - private static final int IPV4_TCP_HEADER_LEN = 20; - private static final int IPV4_TCP_HEADER_OFFSET = ETH_HEADER_LEN + IPV4_HEADER_LEN; - private static final int IPV4_TCP_SRC_PORT_OFFSET = IPV4_TCP_HEADER_OFFSET + 0; - private static final int IPV4_TCP_DEST_PORT_OFFSET = IPV4_TCP_HEADER_OFFSET + 2; - private static final int IPV4_TCP_SEQ_NUM_OFFSET = IPV4_TCP_HEADER_OFFSET + 4; - private static final int IPV4_TCP_ACK_NUM_OFFSET = IPV4_TCP_HEADER_OFFSET + 8; + private static final int IPV4_PROTOCOL_OFFSET = ETH_HEADER_LEN + 9; + private static final int IPV4_SRC_ADDR_OFFSET = ETH_HEADER_LEN + 12; + private static final int IPV4_DEST_ADDR_OFFSET = ETH_HEADER_LEN + 16; + + private static final int IPV4_TCP_HEADER_LEN = 20; + private static final int IPV4_TCP_HEADER_OFFSET = ETH_HEADER_LEN + IPV4_HEADER_LEN; + private static final int IPV4_TCP_SRC_PORT_OFFSET = IPV4_TCP_HEADER_OFFSET + 0; + private static final int IPV4_TCP_DEST_PORT_OFFSET = IPV4_TCP_HEADER_OFFSET + 2; + private static final int IPV4_TCP_SEQ_NUM_OFFSET = IPV4_TCP_HEADER_OFFSET + 4; + private static final int IPV4_TCP_ACK_NUM_OFFSET = IPV4_TCP_HEADER_OFFSET + 8; private static final int IPV4_TCP_HEADER_LENGTH_OFFSET = IPV4_TCP_HEADER_OFFSET + 12; - private static final int IPV4_TCP_HEADER_FLAG_OFFSET = IPV4_TCP_HEADER_OFFSET + 13; + private static final int IPV4_TCP_HEADER_FLAG_OFFSET = IPV4_TCP_HEADER_OFFSET + 13; + + private static final int IPV4_UDP_HEADER_OFFSET = ETH_HEADER_LEN + IPV4_HEADER_LEN;; + private static final int IPV4_UDP_SRC_PORT_OFFSET = IPV4_UDP_HEADER_OFFSET + 0; + private static final int IPV4_UDP_DEST_PORT_OFFSET = IPV4_UDP_HEADER_OFFSET + 2; + private static final int IPV4_UDP_LENGTH_OFFSET = IPV4_UDP_HEADER_OFFSET + 4; + private static final int IPV4_UDP_PAYLOAD_OFFSET = IPV4_UDP_HEADER_OFFSET + 8; private static final byte[] IPV4_BROADCAST_ADDRESS = {(byte) 255, (byte) 255, (byte) 255, (byte) 255}; - private static final int IPV6_NEXT_HEADER_OFFSET = ETH_HEADER_LEN + 6; - private static final int IPV6_HEADER_LEN = 40; - private static final int IPV6_SRC_ADDR_OFFSET = ETH_HEADER_LEN + 8; - private static final int IPV6_DEST_ADDR_OFFSET = ETH_HEADER_LEN + 24; - private static final int IPV6_TCP_HEADER_OFFSET = ETH_HEADER_LEN + IPV6_HEADER_LEN; - private static final int IPV6_TCP_SRC_PORT_OFFSET = IPV6_TCP_HEADER_OFFSET + 0; - private static final int IPV6_TCP_DEST_PORT_OFFSET = IPV6_TCP_HEADER_OFFSET + 2; - private static final int IPV6_TCP_SEQ_NUM_OFFSET = IPV6_TCP_HEADER_OFFSET + 4; - private static final int IPV6_TCP_ACK_NUM_OFFSET = IPV6_TCP_HEADER_OFFSET + 8; + private static final int IPV6_HEADER_LEN = 40; + private static final int IPV6_NEXT_HEADER_OFFSET = ETH_HEADER_LEN + 6; + private static final int IPV6_SRC_ADDR_OFFSET = ETH_HEADER_LEN + 8; + private static final int IPV6_DEST_ADDR_OFFSET = ETH_HEADER_LEN + 24; + private static final int IPV6_TCP_HEADER_OFFSET = ETH_HEADER_LEN + IPV6_HEADER_LEN; + private static final int IPV6_TCP_SRC_PORT_OFFSET = IPV6_TCP_HEADER_OFFSET + 0; + private static final int IPV6_TCP_DEST_PORT_OFFSET = IPV6_TCP_HEADER_OFFSET + 2; + private static final int IPV6_TCP_SEQ_NUM_OFFSET = IPV6_TCP_HEADER_OFFSET + 4; + private static final int IPV6_TCP_ACK_NUM_OFFSET = IPV6_TCP_HEADER_OFFSET + 8; // The IPv6 all nodes address ff02::1 - private static final byte[] IPV6_ALL_NODES_ADDRESS = + private static final byte[] IPV6_ALL_NODES_ADDRESS = { (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }; private static final byte[] IPV6_ALL_ROUTERS_ADDRESS = { (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 }; - private static final int ICMP6_TYPE_OFFSET = ETH_HEADER_LEN + IPV6_HEADER_LEN; - private static final int ICMP6_ROUTER_SOLICITATION = 133; - private static final int ICMP6_ROUTER_ADVERTISEMENT = 134; + private static final int ICMP6_TYPE_OFFSET = ETH_HEADER_LEN + IPV6_HEADER_LEN; + private static final int ICMP6_ROUTER_SOLICITATION = 133; + private static final int ICMP6_ROUTER_ADVERTISEMENT = 134; private static final int ICMP6_NEIGHBOR_SOLICITATION = 135; private static final int ICMP6_NEIGHBOR_ANNOUNCEMENT = 136; @@ -1050,9 +1058,9 @@ public class ApfTest { private static final int ICMP6_RA_OPTION_OFFSET = ETH_HEADER_LEN + IPV6_HEADER_LEN + ICMP6_RA_HEADER_LEN; - private static final int ICMP6_PREFIX_OPTION_TYPE = 3; - private static final int ICMP6_PREFIX_OPTION_LEN = 32; - private static final int ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET = 4; + private static final int ICMP6_PREFIX_OPTION_TYPE = 3; + private static final int ICMP6_PREFIX_OPTION_LEN = 32; + private static final int ICMP6_PREFIX_OPTION_VALID_LIFETIME_OFFSET = 4; private static final int ICMP6_PREFIX_OPTION_PREFERRED_LIFETIME_OFFSET = 8; // From RFC6106: Recursive DNS Server option @@ -1063,17 +1071,17 @@ public class ApfTest { // From RFC4191: Route Information option private static final int ICMP6_ROUTE_INFO_OPTION_TYPE = 24; // Above three options all have the same format: - private static final int ICMP6_4_BYTE_OPTION_LEN = 8; + private static final int ICMP6_4_BYTE_OPTION_LEN = 8; private static final int ICMP6_4_BYTE_LIFETIME_OFFSET = 4; - private static final int ICMP6_4_BYTE_LIFETIME_LEN = 4; + private static final int ICMP6_4_BYTE_LIFETIME_LEN = 4; - private static final int UDP_HEADER_LEN = 8; + private static final int UDP_HEADER_LEN = 8; private static final int UDP_DESTINATION_PORT_OFFSET = ETH_HEADER_LEN + 22; - private static final int DHCP_CLIENT_PORT = 68; + private static final int DHCP_CLIENT_PORT = 68; private static final int DHCP_CLIENT_MAC_OFFSET = ETH_HEADER_LEN + UDP_HEADER_LEN + 48; - private static final int ARP_HEADER_OFFSET = ETH_HEADER_LEN; + private static final int ARP_HEADER_OFFSET = ETH_HEADER_LEN; private static final byte[] ARP_IPV4_REQUEST_HEADER = { 0, 1, // Hardware type: Ethernet (1) 8, 0, // Protocol type: IP (0x0800) @@ -1714,6 +1722,76 @@ public class ApfTest { return packet.array(); } + @Test + public void testApfFilterNattKeepalivePacket() throws Exception { + final MockIpClientCallback cb = new MockIpClientCallback(); + final ApfConfiguration config = getDefaultConfig(); + config.multicastFilter = DROP_MULTICAST; + config.ieee802_3Filter = DROP_802_3_FRAMES; + final TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb, mLog); + byte[] program; + final int srcPort = 1024; + final int dstPort = 4500; + final int slot1 = 1; + // NAT-T keepalive + final byte[] payload = {(byte) 0xff}; + + // src: 10.0.0.5, port: 1024 + // dst: 10.0.0.6, port: 4500 + InetAddress srcAddr = InetAddress.getByAddress(IPV4_KEEPALIVE_SRC_ADDR); + InetAddress dstAddr = InetAddress.getByAddress(IPV4_KEEPALIVE_DST_ADDR); + + final NattKeepalivePacketDataParcelable parcel = new NattKeepalivePacketDataParcelable(); + parcel.srcAddress = srcAddr.getAddress(); + parcel.srcPort = srcPort; + parcel.dstAddress = dstAddr.getAddress(); + parcel.dstPort = dstPort; + + apfFilter.addNattKeepalivePacketFilter(slot1, parcel); + program = cb.getApfProgram(); + + // Verify IPv4 keepalive packet is dropped + // src: 10.0.0.6, port: 4500 + // dst: 10.0.0.5, port: 1024 + final byte[] nattKaPkt = ipv4UdpPacket(IPV4_KEEPALIVE_DST_ADDR, + IPV4_KEEPALIVE_SRC_ADDR, dstPort, srcPort, 1 /* dataLength */); + System.arraycopy(payload, 0, nattKaPkt, IPV4_UDP_PAYLOAD_OFFSET, payload.length); + assertDrop(program, nattKaPkt); + // Verify IPv4 non-keepalive packet from the same source address is passed + assertPass(program, + ipv4UdpPacket(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR, + dstPort, srcPort, 10 /* dataLength */)); + // Verify IPv4 non-keepalive packet from other source address is passed + assertPass(program, + ipv4UdpPacket(IPV4_ANOTHER_ADDR, IPV4_KEEPALIVE_SRC_ADDR, + dstPort, srcPort, 10 /* dataLength */)); + + apfFilter.removeKeepalivePacketFilter(slot1); + apfFilter.shutdown(); + } + + private static byte[] ipv4UdpPacket(byte[] sip, byte[] dip, int sport, + int dport, int dataLength) { + final int totalLength = dataLength + IPV4_HEADER_LEN + UDP_HEADER_LEN; + final int udpLength = UDP_HEADER_LEN + dataLength; + ByteBuffer packet = ByteBuffer.wrap(new byte[totalLength + ETH_HEADER_LEN]); + + // ether type + packet.putShort(ETH_ETHERTYPE_OFFSET, (short) ETH_P_IP); + + // IPv4 header + packet.put(IPV4_VERSION_IHL_OFFSET, (byte) 0x45); + packet.putShort(IPV4_TOTAL_LENGTH_OFFSET, (short) totalLength); + packet.put(IPV4_PROTOCOL_OFFSET, (byte) IPPROTO_UDP); + put(packet, IPV4_SRC_ADDR_OFFSET, sip); + put(packet, IPV4_DEST_ADDR_OFFSET, dip); + packet.putShort(IPV4_UDP_SRC_PORT_OFFSET, (short) sport); + packet.putShort(IPV4_UDP_DEST_PORT_OFFSET, (short) dport); + packet.putShort(IPV4_UDP_LENGTH_OFFSET, (short) udpLength); + + return packet.array(); + } + // Verify that the last program pushed to the IpClient.Callback properly filters the // given packet for the given lifetime. private void verifyRaLifetime(byte[] program, ByteBuffer packet, int lifetime) { |