diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java index 39812cd5c0..50786e68c8 100644 --- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java +++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java @@ -27,20 +27,33 @@ import static android.net.TetheringManager.CONNECTIVITY_SCOPE_GLOBAL; import static android.net.TetheringManager.CONNECTIVITY_SCOPE_LOCAL; import static android.net.TetheringManager.TETHERING_ETHERNET; import static android.net.TetheringTester.TestDnsPacket; -import static android.net.TetheringTester.isExpectedIcmpv6Packet; +import static android.net.TetheringTester.isExpectedIcmpPacket; +import static android.net.TetheringTester.isExpectedTcpPacket; import static android.net.TetheringTester.isExpectedUdpDnsPacket; import static android.net.TetheringTester.isExpectedUdpPacket; +import static android.system.OsConstants.ICMP_ECHO; +import static android.system.OsConstants.ICMP_ECHOREPLY; +import static android.system.OsConstants.IPPROTO_ICMP; import static android.system.OsConstants.IPPROTO_IP; import static android.system.OsConstants.IPPROTO_IPV6; +import static android.system.OsConstants.IPPROTO_TCP; import static android.system.OsConstants.IPPROTO_UDP; import static com.android.net.module.util.ConnectivityUtils.isIPv6ULA; import static com.android.net.module.util.HexDump.dumpHexString; +import static com.android.net.module.util.IpUtils.icmpChecksum; +import static com.android.net.module.util.IpUtils.ipChecksum; import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV4; import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV6; import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REPLY_TYPE; import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE; import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT; +import static com.android.net.module.util.NetworkStackConstants.ICMP_CHECKSUM_OFFSET; +import static com.android.net.module.util.NetworkStackConstants.IPV4_CHECKSUM_OFFSET; +import static com.android.net.module.util.NetworkStackConstants.IPV4_HEADER_MIN_LEN; +import static com.android.net.module.util.NetworkStackConstants.IPV4_LENGTH_OFFSET; +import static com.android.net.module.util.NetworkStackConstants.TCPHDR_ACK; +import static com.android.net.module.util.NetworkStackConstants.TCPHDR_SYN; import static com.android.testutils.DeviceInfoUtils.KVersion; import static com.android.testutils.TestNetworkTrackerKt.initTestNetwork; import static com.android.testutils.TestPermissionUtil.runAsShell; @@ -88,6 +101,8 @@ import com.android.net.module.util.bpf.Tether4Key; import com.android.net.module.util.bpf.Tether4Value; import com.android.net.module.util.bpf.TetherStatsKey; import com.android.net.module.util.bpf.TetherStatsValue; +import com.android.net.module.util.structs.EthernetHeader; +import com.android.net.module.util.structs.Icmpv4Header; import com.android.net.module.util.structs.Ipv4Header; import com.android.net.module.util.structs.Ipv6Header; import com.android.net.module.util.structs.UdpHeader; @@ -167,8 +182,11 @@ public class EthernetTetheringTest { (Inet6Address) parseNumericAddress("2002:db8:1::515:ca"); private static final ByteBuffer TEST_REACHABILITY_PAYLOAD = ByteBuffer.wrap(new byte[] { (byte) 0x55, (byte) 0xaa }); + private static final ByteBuffer EMPTY_PAYLOAD = ByteBuffer.wrap(new byte[0]); private static final short DNS_PORT = 53; + private static final short WINDOW = (short) 0x2000; + private static final short URGENT_POINTER = 0; private static final String DUMPSYS_TETHERING_RAWMAP_ARG = "bpfRawMap"; private static final String DUMPSYS_RAWMAP_ARG_STATS = "--stats"; @@ -179,6 +197,10 @@ public class EthernetTetheringTest { private static final int VERSION_TRAFFICCLASS_FLOWLABEL = 0x60000000; private static final short HOP_LIMIT = 0x40; + private static final short ICMPECHO_CODE = 0x0; + private static final short ICMPECHO_ID = 0x0; + private static final short ICMPECHO_SEQ = 0x0; + // TODO: use class DnsPacket to build DNS query and reply message once DnsPacket supports // building packet for given arguments. private static final ByteBuffer DNS_QUERY = ByteBuffer.wrap(new byte[] { @@ -450,7 +472,10 @@ public class EthernetTetheringTest { final long deadline = SystemClock.uptimeMillis() + timeoutMs; do { byte[] pkt = reader.popPacket(timeoutMs); - if (isExpectedIcmpv6Packet(pkt, true /* hasEth */, ICMPV6_ROUTER_ADVERTISEMENT)) return; + if (isExpectedIcmpPacket(pkt, true /* hasEth */, false /* isIpv4 */, + ICMPV6_ROUTER_ADVERTISEMENT)) { + return; + } timeoutMs = deadline - SystemClock.uptimeMillis(); } while (timeoutMs > 0); @@ -971,14 +996,16 @@ public class EthernetTetheringTest { tester.verifyUpload(request, p -> { Log.d(TAG, "Packet in upstream: " + dumpHexString(p)); - return isExpectedIcmpv6Packet(p, false /* hasEth */, ICMPV6_ECHO_REQUEST_TYPE); + return isExpectedIcmpPacket(p, false /* hasEth */, false /* isIpv4 */, + ICMPV6_ECHO_REQUEST_TYPE); }); ByteBuffer reply = Ipv6Utils.buildEchoReplyPacket(remoteIp6Addr, tethered.ipv6Addr); tester.verifyDownload(reply, p -> { Log.d(TAG, "Packet in downstream: " + dumpHexString(p)); - return isExpectedIcmpv6Packet(p, true /* hasEth */, ICMPV6_ECHO_REPLY_TYPE); + return isExpectedIcmpPacket(p, true /* hasEth */, false /* isIpv4 */, + ICMPV6_ECHO_REPLY_TYPE); }); } @@ -1009,26 +1036,21 @@ public class EthernetTetheringTest { private static final ByteBuffer TX_PAYLOAD = ByteBuffer.wrap(new byte[] { (byte) 0x56, (byte) 0x78 }); + private short getEthType(@NonNull final InetAddress srcIp, @NonNull final InetAddress dstIp) { + return isAddressIpv4(srcIp, dstIp) ? (short) ETHER_TYPE_IPV4 : (short) ETHER_TYPE_IPV6; + } + + private int getIpProto(@NonNull final InetAddress srcIp, @NonNull final InetAddress dstIp) { + return isAddressIpv4(srcIp, dstIp) ? IPPROTO_IP : IPPROTO_IPV6; + } + @NonNull private ByteBuffer buildUdpPacket( @Nullable final MacAddress srcMac, @Nullable final MacAddress dstMac, @NonNull final InetAddress srcIp, @NonNull final InetAddress dstIp, short srcPort, short dstPort, @Nullable final ByteBuffer payload) throws Exception { - int ipProto; - short ethType; - if (srcIp instanceof Inet4Address && dstIp instanceof Inet4Address) { - ipProto = IPPROTO_IP; - ethType = (short) ETHER_TYPE_IPV4; - } else if (srcIp instanceof Inet6Address && dstIp instanceof Inet6Address) { - ipProto = IPPROTO_IPV6; - ethType = (short) ETHER_TYPE_IPV6; - } else { - fail("Unsupported conditions: srcIp " + srcIp + ", dstIp " + dstIp); - // Make compiler happy to the uninitialized ipProto and ethType. - return null; // unreachable, the annotation @NonNull of function return value is true. - } - + final int ipProto = getIpProto(srcIp, dstIp); final boolean hasEther = (srcMac != null && dstMac != null); final int payloadLen = (payload == null) ? 0 : payload.limit(); final ByteBuffer buffer = PacketBuilder.allocate(hasEther, ipProto, IPPROTO_UDP, @@ -1036,7 +1058,9 @@ public class EthernetTetheringTest { final PacketBuilder packetBuilder = new PacketBuilder(buffer); // [1] Ethernet header - if (hasEther) packetBuilder.writeL2Header(srcMac, dstMac, (short) ethType); + if (hasEther) { + packetBuilder.writeL2Header(srcMac, dstMac, getEthType(srcIp, dstIp)); + } // [2] IP header if (ipProto == IPPROTO_IP) { @@ -1513,12 +1537,156 @@ public class EthernetTetheringTest { // TODO: test CLAT bpf maps. } + // TODO: support R device. See b/234727688. @Test @IgnoreUpTo(Build.VERSION_CODES.R) public void testTetherClatUdp() throws Exception { runClatUdpTest(); } + // PacketBuilder doesn't support IPv4 ICMP packet. It may need to refactor PacketBuilder first + // because ICMP is a specific layer 3 protocol for PacketBuilder which expects packets always + // have layer 3 (IP) and layer 4 (TCP, UDP) for now. Since we don't use IPv4 ICMP packet too + // much in this test, we just write a ICMP packet builder here. + // TODO: move ICMPv4 packet build function to common utilis. + @NonNull + private ByteBuffer buildIcmpEchoPacketV4( + @Nullable final MacAddress srcMac, @Nullable final MacAddress dstMac, + @NonNull final Inet4Address srcIp, @NonNull final Inet4Address dstIp, + int type, short id, short seq) throws Exception { + if (type != ICMP_ECHO && type != ICMP_ECHOREPLY) { + fail("Unsupported ICMP type: " + type); + } + + // Build ICMP echo id and seq fields as payload. Ignore the data field. + final ByteBuffer payload = ByteBuffer.allocate(4); + payload.putShort(id); + payload.putShort(seq); + payload.rewind(); + + final boolean hasEther = (srcMac != null && dstMac != null); + final int etherHeaderLen = hasEther ? Struct.getSize(EthernetHeader.class) : 0; + final int ipv4HeaderLen = Struct.getSize(Ipv4Header.class); + final int Icmpv4HeaderLen = Struct.getSize(Icmpv4Header.class); + final int payloadLen = payload.limit(); + final ByteBuffer packet = ByteBuffer.allocate(etherHeaderLen + ipv4HeaderLen + + Icmpv4HeaderLen + payloadLen); + + // [1] Ethernet header + if (hasEther) { + final EthernetHeader ethHeader = new EthernetHeader(dstMac, srcMac, ETHER_TYPE_IPV4); + ethHeader.writeToByteBuffer(packet); + } + + // [2] IP header + final Ipv4Header ipv4Header = new Ipv4Header(TYPE_OF_SERVICE, + (short) 0 /* totalLength, calculate later */, ID, + FLAGS_AND_FRAGMENT_OFFSET, TIME_TO_LIVE, (byte) IPPROTO_ICMP, + (short) 0 /* checksum, calculate later */, srcIp, dstIp); + ipv4Header.writeToByteBuffer(packet); + + // [3] ICMP header + final Icmpv4Header icmpv4Header = new Icmpv4Header((byte) type, ICMPECHO_CODE, + (short) 0 /* checksum, calculate later */); + icmpv4Header.writeToByteBuffer(packet); + + // [4] Payload + packet.put(payload); + packet.flip(); + + // [5] Finalize packet + // Used for updating IP header fields. If there is Ehternet header, IPv4 header offset + // in buffer equals ethernet header length because IPv4 header is located next to ethernet + // header. Otherwise, IPv4 header offset is 0. + final int ipv4HeaderOffset = hasEther ? etherHeaderLen : 0; + + // Populate the IPv4 totalLength field. + packet.putShort(ipv4HeaderOffset + IPV4_LENGTH_OFFSET, + (short) (ipv4HeaderLen + Icmpv4HeaderLen + payloadLen)); + + // Populate the IPv4 header checksum field. + packet.putShort(ipv4HeaderOffset + IPV4_CHECKSUM_OFFSET, + ipChecksum(packet, ipv4HeaderOffset /* headerOffset */)); + + // Populate the ICMP checksum field. + packet.putShort(ipv4HeaderOffset + IPV4_HEADER_MIN_LEN + ICMP_CHECKSUM_OFFSET, + icmpChecksum(packet, ipv4HeaderOffset + IPV4_HEADER_MIN_LEN, + Icmpv4HeaderLen + payloadLen)); + return packet; + } + + @NonNull + private ByteBuffer buildIcmpEchoPacketV4(@NonNull final Inet4Address srcIp, + @NonNull final Inet4Address dstIp, int type, short id, short seq) + throws Exception { + return buildIcmpEchoPacketV4(null /* srcMac */, null /* dstMac */, srcIp, dstIp, + type, id, seq); + } + + @Test + public void testIcmpv4Echo() throws Exception { + final TetheringTester tester = initTetheringTester(toList(TEST_IP4_ADDR), + toList(TEST_IP4_DNS)); + final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, false /* hasIpv6 */); + + // TODO: remove the connectivity verification for upstream connected notification race. + // See the same reason in runUdp4Test(). + probeV4TetheringConnectivity(tester, tethered, false /* is4To6 */); + + final ByteBuffer request = buildIcmpEchoPacketV4(tethered.macAddr /* srcMac */, + tethered.routerMacAddr /* dstMac */, tethered.ipv4Addr /* srcIp */, + REMOTE_IP4_ADDR /* dstIp */, ICMP_ECHO, ICMPECHO_ID, ICMPECHO_SEQ); + tester.verifyUpload(request, p -> { + Log.d(TAG, "Packet in upstream: " + dumpHexString(p)); + + return isExpectedIcmpPacket(p, false /* hasEth */, true /* isIpv4 */, ICMP_ECHO); + }); + + final ByteBuffer reply = buildIcmpEchoPacketV4(REMOTE_IP4_ADDR /* srcIp*/, + (Inet4Address) TEST_IP4_ADDR.getAddress() /* dstIp */, ICMP_ECHOREPLY, ICMPECHO_ID, + ICMPECHO_SEQ); + tester.verifyDownload(reply, p -> { + Log.d(TAG, "Packet in downstream: " + dumpHexString(p)); + + return isExpectedIcmpPacket(p, true /* hasEth */, true /* isIpv4 */, ICMP_ECHOREPLY); + }); + } + + // TODO: support R device. See b/234727688. + @Test + @IgnoreUpTo(Build.VERSION_CODES.R) + public void testTetherClatIcmp() throws Exception { + // CLAT only starts on IPv6 only network. + final TetheringTester tester = initTetheringTester(toList(TEST_IP6_ADDR), + toList(TEST_IP6_DNS)); + final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, true /* hasIpv6 */); + + // Get CLAT IPv6 address. + final Inet6Address clatIp6 = getClatIpv6Address(tester, tethered); + + // Send an IPv4 ICMP packet in original direction. + // IPv4 packet -- CLAT translation --> IPv6 packet + final ByteBuffer request = buildIcmpEchoPacketV4(tethered.macAddr /* srcMac */, + tethered.routerMacAddr /* dstMac */, tethered.ipv4Addr /* srcIp */, + (Inet4Address) REMOTE_IP4_ADDR /* dstIp */, ICMP_ECHO, ICMPECHO_ID, ICMPECHO_SEQ); + tester.verifyUpload(request, p -> { + Log.d(TAG, "Packet in upstream: " + dumpHexString(p)); + + return isExpectedIcmpPacket(p, false /* hasEth */, false /* isIpv4 */, + ICMPV6_ECHO_REQUEST_TYPE); + }); + + // Send an IPv6 ICMP packet in reply direction. + // IPv6 packet -- CLAT translation --> IPv4 packet + final ByteBuffer reply = Ipv6Utils.buildEchoReplyPacket( + (Inet6Address) REMOTE_NAT64_ADDR /* srcIp */, clatIp6 /* dstIp */); + tester.verifyDownload(reply, p -> { + Log.d(TAG, "Packet in downstream: " + dumpHexString(p)); + + return isExpectedIcmpPacket(p, true /* hasEth */, true /* isIpv4 */, ICMP_ECHOREPLY); + }); + } + @NonNull private ByteBuffer buildDnsReplyMessageById(short id) { byte[] replyMessage = Arrays.copyOf(DNS_REPLY, DNS_REPLY.length); @@ -1607,6 +1775,155 @@ public class EthernetTetheringTest { (short) udpHeader.srcPort, (short) dnsQuery.getHeader().id, tester); } + @NonNull + private ByteBuffer buildTcpPacket( + @Nullable final MacAddress srcMac, @Nullable final MacAddress dstMac, + @NonNull final InetAddress srcIp, @NonNull final InetAddress dstIp, + short srcPort, short dstPort, final short seq, final short ack, + final byte tcpFlags, @NonNull final ByteBuffer payload) throws Exception { + final int ipProto = getIpProto(srcIp, dstIp); + final boolean hasEther = (srcMac != null && dstMac != null); + final ByteBuffer buffer = PacketBuilder.allocate(hasEther, ipProto, IPPROTO_TCP, + payload.limit()); + final PacketBuilder packetBuilder = new PacketBuilder(buffer); + + // [1] Ethernet header + if (hasEther) { + packetBuilder.writeL2Header(srcMac, dstMac, getEthType(srcIp, dstIp)); + } + + // [2] IP header + if (ipProto == IPPROTO_IP) { + packetBuilder.writeIpv4Header(TYPE_OF_SERVICE, ID, FLAGS_AND_FRAGMENT_OFFSET, + TIME_TO_LIVE, (byte) IPPROTO_TCP, (Inet4Address) srcIp, (Inet4Address) dstIp); + } else { + packetBuilder.writeIpv6Header(VERSION_TRAFFICCLASS_FLOWLABEL, (byte) IPPROTO_TCP, + HOP_LIMIT, (Inet6Address) srcIp, (Inet6Address) dstIp); + } + + // [3] TCP header + packetBuilder.writeTcpHeader(srcPort, dstPort, seq, ack, tcpFlags, WINDOW, URGENT_POINTER); + + // [4] Payload + buffer.put(payload); + // in case data might be reused by caller, restore the position and + // limit of bytebuffer. + payload.clear(); + + return packetBuilder.finalizePacket(); + } + + private void sendDownloadPacketTcp(@NonNull final InetAddress srcIp, + @NonNull final InetAddress dstIp, short seq, short ack, byte tcpFlags, + @NonNull final ByteBuffer payload, @NonNull final TetheringTester tester) + throws Exception { + final boolean isIpv4 = isAddressIpv4(srcIp, dstIp); + final ByteBuffer testPacket = buildTcpPacket(null /* srcMac */, null /* dstMac */, + srcIp, dstIp, REMOTE_PORT /* srcPort */, LOCAL_PORT /* dstPort */, seq, ack, + tcpFlags, payload); + tester.verifyDownload(testPacket, p -> { + Log.d(TAG, "Packet in downstream: " + dumpHexString(p)); + + return isExpectedTcpPacket(p, true /* hasEther */, isIpv4, seq, payload); + }); + } + + private void sendUploadPacketTcp(@NonNull final MacAddress srcMac, + @NonNull final MacAddress dstMac, @NonNull final InetAddress srcIp, + @NonNull final InetAddress dstIp, short seq, short ack, byte tcpFlags, + @NonNull final ByteBuffer payload, @NonNull final TetheringTester tester) + throws Exception { + final boolean isIpv4 = isAddressIpv4(srcIp, dstIp); + final ByteBuffer testPacket = buildTcpPacket(srcMac, dstMac, srcIp, dstIp, + LOCAL_PORT /* srcPort */, REMOTE_PORT /* dstPort */, seq, ack, tcpFlags, + payload); + tester.verifyUpload(testPacket, p -> { + Log.d(TAG, "Packet in upstream: " + dumpHexString(p)); + + return isExpectedTcpPacket(p, false /* hasEther */, isIpv4, seq, payload); + }); + } + + void runTcpTest( + @NonNull final MacAddress uploadSrcMac, @NonNull final MacAddress uploadDstMac, + @NonNull final InetAddress uploadSrcIp, @NonNull final InetAddress uploadDstIp, + @NonNull final InetAddress downloadSrcIp, @NonNull final InetAddress downloadDstIp, + @NonNull final TetheringTester tester) throws Exception { + // Three way handshake and data transfer. + // + // Server (base seq = 2000) Client (base seq = 1000) + // | | + // | [1] [SYN] SEQ = 1000 | + // |<---------------------------------------------------------| - + // | | ^ + // | [2] [SYN + ACK] SEQ = 2000, ACK = 1000+1 | | + // |--------------------------------------------------------->| three way handshake + // | | | + // | [3] [ACK] SEQ = 1001, ACK = 2000+1 | v + // |<---------------------------------------------------------| - + // | | ^ + // | [4] [ACK] SEQ = 1001, ACK = 2001, 2 byte payload | | + // |<---------------------------------------------------------| data transfer + // | | | + // | [5] [ACK] SEQ = 2001, ACK = 1001+2, 2 byte payload | v + // |--------------------------------------------------------->| - + // | | + // + + // This test can only verify the packets are transferred end to end but TCP state. + // TODO: verify TCP state change via /proc/net/nf_conntrack or netlink conntrack event. + // [1] [UPLOAD] [SYN]: SEQ = 1000 + sendUploadPacketTcp(uploadSrcMac, uploadDstMac, uploadSrcIp, uploadDstIp, + (short) 1000 /* seq */, (short) 0 /* ack */, TCPHDR_SYN, EMPTY_PAYLOAD, + tester); + + // [2] [DONWLOAD] [SYN + ACK]: SEQ = 2000, ACK = 1001 + sendDownloadPacketTcp(downloadSrcIp, downloadDstIp, (short) 2000 /* seq */, + (short) 1001 /* ack */, (byte) ((TCPHDR_SYN | TCPHDR_ACK) & 0xff), EMPTY_PAYLOAD, + tester); + + // [3] [UPLOAD] [ACK]: SEQ = 1001, ACK = 2001 + sendUploadPacketTcp(uploadSrcMac, uploadDstMac, uploadSrcIp, uploadDstIp, + (short) 1001 /* seq */, (short) 2001 /* ack */, TCPHDR_ACK, EMPTY_PAYLOAD, tester); + + // [4] [UPLOAD] [ACK]: SEQ = 1001, ACK = 2001, 2 byte payload + sendUploadPacketTcp(uploadSrcMac, uploadDstMac, uploadSrcIp, uploadDstIp, + (short) 1001 /* seq */, (short) 2001 /* ack */, TCPHDR_ACK, TX_PAYLOAD, + tester); + + // [5] [DONWLOAD] [ACK]: SEQ = 2001, ACK = 1003, 2 byte payload + sendDownloadPacketTcp(downloadSrcIp, downloadDstIp, (short) 2001 /* seq */, + (short) 1003 /* ack */, TCPHDR_ACK, RX_PAYLOAD, tester); + } + + @Test + public void testTetherTcpV4() throws Exception { + final TetheringTester tester = initTetheringTester(toList(TEST_IP4_ADDR), + toList(TEST_IP4_DNS)); + final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, false /* hasIpv6 */); + + // TODO: remove the connectivity verification for upstream connected notification race. + // See the same reason in runUdp4Test(). + probeV4TetheringConnectivity(tester, tethered, false /* is4To6 */); + + runTcpTest(tethered.macAddr /* uploadSrcMac */, tethered.routerMacAddr /* uploadDstMac */, + tethered.ipv4Addr /* uploadSrcIp */, REMOTE_IP4_ADDR /* uploadDstIp */, + REMOTE_IP4_ADDR /* downloadSrcIp */, TEST_IP4_ADDR.getAddress() /* downloadDstIp */, + tester); + } + + @Test + public void testTetherTcpV6() throws Exception { + final TetheringTester tester = initTetheringTester(toList(TEST_IP6_ADDR), + toList(TEST_IP6_DNS)); + final TetheredDevice tethered = tester.createTetheredDevice(TEST_MAC, true /* hasIpv6 */); + + runTcpTest(tethered.macAddr /* uploadSrcMac */, tethered.routerMacAddr /* uploadDstMac */, + tethered.ipv6Addr /* uploadSrcIp */, REMOTE_IP6_ADDR /* uploadDstIp */, + REMOTE_IP6_ADDR /* downloadSrcIp */, tethered.ipv6Addr /* downloadDstIp */, + tester); + } + private List toList(T... array) { return Arrays.asList(array); } diff --git a/Tethering/tests/integration/src/android/net/TetheringTester.java b/Tethering/tests/integration/src/android/net/TetheringTester.java index 9cc2e494d3..ae39b246b6 100644 --- a/Tethering/tests/integration/src/android/net/TetheringTester.java +++ b/Tethering/tests/integration/src/android/net/TetheringTester.java @@ -17,7 +17,9 @@ package android.net; import static android.net.InetAddresses.parseNumericAddress; +import static android.system.OsConstants.IPPROTO_ICMP; import static android.system.OsConstants.IPPROTO_ICMPV6; +import static android.system.OsConstants.IPPROTO_TCP; import static android.system.OsConstants.IPPROTO_UDP; import static com.android.net.module.util.DnsPacket.ANSECTION; @@ -39,6 +41,7 @@ import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_AD import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ALL_NODES_MULTICAST; import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_OVERRIDE; import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED; +import static com.android.net.module.util.NetworkStackConstants.TCPHDR_SYN; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.fail; @@ -57,6 +60,7 @@ import com.android.net.module.util.DnsPacket; import com.android.net.module.util.Ipv6Utils; import com.android.net.module.util.Struct; import com.android.net.module.util.structs.EthernetHeader; +import com.android.net.module.util.structs.Icmpv4Header; import com.android.net.module.util.structs.Icmpv6Header; import com.android.net.module.util.structs.Ipv4Header; import com.android.net.module.util.structs.Ipv6Header; @@ -64,6 +68,7 @@ import com.android.net.module.util.structs.LlaOption; import com.android.net.module.util.structs.NsHeader; import com.android.net.module.util.structs.PrefixInformationOption; import com.android.net.module.util.structs.RaHeader; +import com.android.net.module.util.structs.TcpHeader; import com.android.net.module.util.structs.UdpHeader; import com.android.networkstack.arp.ArpPacket; import com.android.testutils.TapPacketReader; @@ -268,7 +273,8 @@ public final class TetheringTester { private List getRaPrefixOptions(byte[] packet) { ByteBuffer buf = ByteBuffer.wrap(packet); - if (!isExpectedIcmpv6Packet(buf, true /* hasEth */, ICMPV6_ROUTER_ADVERTISEMENT)) { + if (!isExpectedIcmpPacket(buf, true /* hasEth */, false /* isIpv4 */, + ICMPV6_ROUTER_ADVERTISEMENT)) { fail("Parsing RA packet fail"); } @@ -298,7 +304,8 @@ public final class TetheringTester { sendRsPacket(srcMac, dstMac); final byte[] raPacket = verifyPacketNotNull("Receive RA fail", getDownloadPacket(p -> { - return isExpectedIcmpv6Packet(p, true /* hasEth */, ICMPV6_ROUTER_ADVERTISEMENT); + return isExpectedIcmpPacket(p, true /* hasEth */, false /* isIpv4 */, + ICMPV6_ROUTER_ADVERTISEMENT); })); final List options = getRaPrefixOptions(raPacket); @@ -360,20 +367,27 @@ public final class TetheringTester { } } - public static boolean isExpectedIcmpv6Packet(byte[] packet, boolean hasEth, int type) { + public static boolean isExpectedIcmpPacket(byte[] packet, boolean hasEth, boolean isIpv4, + int type) { final ByteBuffer buf = ByteBuffer.wrap(packet); - return isExpectedIcmpv6Packet(buf, hasEth, type); + return isExpectedIcmpPacket(buf, hasEth, isIpv4, type); } - private static boolean isExpectedIcmpv6Packet(ByteBuffer buf, boolean hasEth, int type) { + private static boolean isExpectedIcmpPacket(ByteBuffer buf, boolean hasEth, boolean isIpv4, + int type) { try { - if (hasEth && !hasExpectedEtherHeader(buf, false /* isIpv4 */)) return false; + if (hasEth && !hasExpectedEtherHeader(buf, isIpv4)) return false; - if (!hasExpectedIpHeader(buf, false /* isIpv4 */, IPPROTO_ICMPV6)) return false; + final int ipProto = isIpv4 ? IPPROTO_ICMP : IPPROTO_ICMPV6; + if (!hasExpectedIpHeader(buf, isIpv4, ipProto)) return false; - return Struct.parse(Icmpv6Header.class, buf).type == (short) type; + if (isIpv4) { + return Struct.parse(Icmpv4Header.class, buf).type == (short) type; + } else { + return Struct.parse(Icmpv6Header.class, buf).type == (short) type; + } } catch (Exception e) { - // Parsing packet fail means it is not icmpv6 packet. + // Parsing packet fail means it is not icmp packet. } return false; @@ -578,6 +592,42 @@ public final class TetheringTester { return true; } + + private static boolean isTcpSynPacket(@NonNull final TcpHeader tcpHeader) { + return (tcpHeader.dataOffsetAndControlBits & TCPHDR_SYN) != 0; + } + + public static boolean isExpectedTcpPacket(@NonNull final byte[] rawPacket, boolean hasEth, + boolean isIpv4, int seq, @NonNull final ByteBuffer payload) { + final ByteBuffer buf = ByteBuffer.wrap(rawPacket); + try { + if (hasEth && !hasExpectedEtherHeader(buf, isIpv4)) return false; + + if (!hasExpectedIpHeader(buf, isIpv4, IPPROTO_TCP)) return false; + + final TcpHeader tcpHeader = Struct.parse(TcpHeader.class, buf); + if (tcpHeader.seq != seq) return false; + + // Don't try to parse the payload if it is a TCP SYN segment because additional TCP + // option MSS may be added in the SYN segment. Currently, TetherController uses + // iptables to limit downstream MSS for IPv4. The additional TCP options will be + // misunderstood as payload because parsing TCP options are not supported by class + // TcpHeader for now. See TetherController::setupIptablesHooks. + // TODO: remove once TcpHeader supports parsing TCP options. + if (isTcpSynPacket(tcpHeader)) { + Log.d(TAG, "Found SYN segment. Ignore parsing the remaining part of packet."); + return true; + } + + if (payload.limit() != buf.remaining()) return false; + return Arrays.equals(getRemaining(buf), getRemaining(payload.asReadOnlyBuffer())); + } catch (Exception e) { + // Parsing packet fail means it is not tcp packet. + } + + return false; + } + private void sendUploadPacket(ByteBuffer packet) throws Exception { mDownstreamReader.sendResponse(packet); }