From e78cfa130b72826d585f098f7e9f1dcaf1119da4 Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Fri, 3 Jun 2022 02:55:26 +0000 Subject: [PATCH 1/2] Revert "Revert "EthernetTetheringTest: add testTetherClatUdp"" This reverts commit 5bbd292bedc0fa922d99421668c803d2f5b07598. Bug: 215655463 Change-Id: I06490001b06446b6a4ff3dc084ce11e8ebbe17f8 Test: atest ConnectivityCoverageTests --- .../android/net/EthernetTetheringTest.java | 205 ++++++++++++++---- .../server/connectivity/Nat464Xlat.java | 10 +- 2 files changed, 174 insertions(+), 41 deletions(-) diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java index b3d2ba6cfb..ad2cbbca2f 100644 --- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java +++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java @@ -30,6 +30,7 @@ import static android.net.TetheringTester.RemoteResponder; import static android.net.TetheringTester.isIcmpv6Type; import static android.system.OsConstants.IPPROTO_ICMPV6; import static android.system.OsConstants.IPPROTO_IP; +import static android.system.OsConstants.IPPROTO_IPV6; import static android.system.OsConstants.IPPROTO_UDP; import static com.android.net.module.util.ConnectivityUtils.isIPv6ULA; @@ -150,6 +151,9 @@ public class EthernetTetheringTest { private static final LinkAddress TEST_IP6_ADDR = new LinkAddress("2001:db8:1::101/64"); private static final InetAddress TEST_IP4_DNS = parseNumericAddress("8.8.8.8"); private static final InetAddress TEST_IP6_DNS = parseNumericAddress("2001:db8:1::888"); + private static final IpPrefix TEST_NAT64PREFIX = new IpPrefix("64:ff9b::/96"); + private static final Inet6Address REMOTE_NAT64_ADDR = + (Inet6Address) parseNumericAddress("64:ff9b::808:808"); private static final ByteBuffer TEST_REACHABILITY_PAYLOAD = ByteBuffer.wrap(new byte[] { (byte) 0x55, (byte) 0xaa }); @@ -159,6 +163,10 @@ public class EthernetTetheringTest { private static final String BASE64_DELIMITER = ","; private static final String LINE_DELIMITER = "\\n"; + // version=6, traffic class=0x0, flowlabel=0x0; + private static final int VERSION_TRAFFICCLASS_FLOWLABEL = 0x60000000; + private static final short HOP_LIMIT = 0x40; + private final Context mContext = InstrumentationRegistry.getContext(); private final EthernetManager mEm = mContext.getSystemService(EthernetManager.class); private final TetheringManager mTm = mContext.getSystemService(TetheringManager.class); @@ -793,6 +801,7 @@ public class EthernetTetheringTest { final LinkProperties lp = new LinkProperties(); lp.setLinkAddresses(addresses); lp.setDnsServers(dnses); + lp.setNat64Prefix(TEST_NAT64PREFIX); return initTestNetwork(mContext, lp, TIMEOUT_MS); } @@ -828,6 +837,7 @@ public class EthernetTetheringTest { private void runPing6Test(TetheringTester tester, RemoteResponder remote) throws Exception { // Currently tethering don't have API to tell when ipv6 tethering is available. Thus, let // TetheringTester test ipv6 tethering connectivity before testing ipv6. + // TODO: move to a common place to avoid that every IPv6 test needs to call this function. tester.waitForIpv6TetherConnectivityVerified(); TetheredDevice tethered = tester.createTetheredDevice(MacAddress.fromString("1:2:3:4:5:6"), @@ -870,12 +880,11 @@ public class EthernetTetheringTest { // Used by public port and private port. Assume port 9876 has not been used yet before the // testing that public port and private port are the same in the testing. Note that NAT port // forwarding could be different between private port and public port. + // TODO: move to the start of test class. private static final short LOCAL_PORT = 9876; private static final short REMOTE_PORT = 433; private static final byte TYPE_OF_SERVICE = 0; private static final short ID = 27149; - private static final short ID2 = 27150; - private static final short ID3 = 27151; private static final short FLAGS_AND_FRAGMENT_OFFSET = (short) 0x4000; // flags=DF, offset=0 private static final byte TIME_TO_LIVE = (byte) 0x40; private static final ByteBuffer PAYLOAD = @@ -886,19 +895,20 @@ public class EthernetTetheringTest { ByteBuffer.wrap(new byte[] { (byte) 0x9a, (byte) 0xbc }); private boolean isExpectedUdpPacket(@NonNull final byte[] rawPacket, boolean hasEther, - @NonNull final ByteBuffer payload) { + boolean isIpv4, @NonNull final ByteBuffer payload) { final ByteBuffer buf = ByteBuffer.wrap(rawPacket); if (hasEther) { - final EthernetHeader etherHeader = Struct.parse(EthernetHeader.class, buf); - if (etherHeader == null) return false; + if (Struct.parse(EthernetHeader.class, buf) == null) return false; } - final Ipv4Header ipv4Header = Struct.parse(Ipv4Header.class, buf); - if (ipv4Header == null) return false; + if (isIpv4) { + if (Struct.parse(Ipv4Header.class, buf) == null) return false; + } else { + if (Struct.parse(Ipv6Header.class, buf) == null) return false; + } - final UdpHeader udpHeader = Struct.parse(UdpHeader.class, buf); - if (udpHeader == null) return false; + if (Struct.parse(UdpHeader.class, buf) == null) return false; if (buf.remaining() != payload.limit()) return false; @@ -907,21 +917,47 @@ public class EthernetTetheringTest { } @NonNull - private ByteBuffer buildUdpv4Packet(@Nullable final MacAddress srcMac, - @Nullable final MacAddress dstMac, short id, - @NonNull final Inet4Address srcIp, @NonNull final Inet4Address dstIp, + 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 boolean hasEther = (srcMac != null && dstMac != null); final int payloadLen = (payload == null) ? 0 : payload.limit(); - final ByteBuffer buffer = PacketBuilder.allocate(hasEther, IPPROTO_IP, IPPROTO_UDP, + final ByteBuffer buffer = PacketBuilder.allocate(hasEther, ipProto, IPPROTO_UDP, payloadLen); final PacketBuilder packetBuilder = new PacketBuilder(buffer); + // [1] Ethernet header if (hasEther) packetBuilder.writeL2Header(srcMac, dstMac, (short) ETHER_TYPE_IPV4); - packetBuilder.writeIpv4Header(TYPE_OF_SERVICE, ID, FLAGS_AND_FRAGMENT_OFFSET, - TIME_TO_LIVE, (byte) IPPROTO_UDP, srcIp, dstIp); + + // [2] IP header + if (ipProto == IPPROTO_IP) { + packetBuilder.writeIpv4Header(TYPE_OF_SERVICE, ID, FLAGS_AND_FRAGMENT_OFFSET, + TIME_TO_LIVE, (byte) IPPROTO_UDP, (Inet4Address) srcIp, (Inet4Address) dstIp); + } else { + packetBuilder.writeIpv6Header(VERSION_TRAFFICCLASS_FLOWLABEL, (byte) IPPROTO_UDP, + HOP_LIMIT, (Inet6Address) srcIp, (Inet6Address) dstIp); + } + + // [3] UDP header packetBuilder.writeUdpHeader(srcPort, dstPort); + + // [4] Payload if (payload != null) { buffer.put(payload); // in case data might be reused by caller, restore the position and @@ -933,10 +969,10 @@ public class EthernetTetheringTest { } @NonNull - private ByteBuffer buildUdpv4Packet(short id, @NonNull final Inet4Address srcIp, - @NonNull final Inet4Address dstIp, short srcPort, short dstPort, + private ByteBuffer buildUdpPacket(@NonNull final InetAddress srcIp, + @NonNull final InetAddress dstIp, short srcPort, short dstPort, @Nullable final ByteBuffer payload) throws Exception { - return buildUdpv4Packet(null /* srcMac */, null /* dstMac */, id, srcIp, dstIp, srcPort, + return buildUdpPacket(null /* srcMac */, null /* dstMac */, srcIp, dstIp, srcPort, dstPort, payload); } @@ -944,9 +980,9 @@ public class EthernetTetheringTest { // See #runUdp4Test. private boolean isIpv4TetherConnectivityVerified(TetheringTester tester, RemoteResponder remote, TetheredDevice tethered) throws Exception { - final ByteBuffer probePacket = buildUdpv4Packet(tethered.macAddr, - tethered.routerMacAddr, ID, tethered.ipv4Addr /* srcIp */, - REMOTE_IP4_ADDR /* dstIp */, LOCAL_PORT /* srcPort */, REMOTE_PORT /*dstPort */, + final ByteBuffer probePacket = buildUdpPacket(tethered.macAddr, + tethered.routerMacAddr, tethered.ipv4Addr /* srcIp */, + REMOTE_IP4_ADDR /* dstIp */, LOCAL_PORT /* srcPort */, REMOTE_PORT /* dstPort */, TEST_REACHABILITY_PAYLOAD); // Send a UDP packet from client and check the packet can be found on upstream interface. @@ -954,7 +990,8 @@ public class EthernetTetheringTest { tester.sendPacket(probePacket); byte[] expectedPacket = remote.getNextMatchedPacket(p -> { Log.d(TAG, "Packet in upstream: " + dumpHexString(p)); - return isExpectedUdpPacket(p, false /* hasEther */, TEST_REACHABILITY_PAYLOAD); + return isExpectedUdpPacket(p, false /* hasEther */, true /* isIpv4 */, + TEST_REACHABILITY_PAYLOAD); }); if (expectedPacket != null) return true; } @@ -975,23 +1012,23 @@ public class EthernetTetheringTest { assertTrue(isIpv4TetherConnectivityVerified(tester, remote, tethered)); // Send a UDP packet in original direction. - final ByteBuffer originalPacket = buildUdpv4Packet(tethered.macAddr, - tethered.routerMacAddr, ID, tethered.ipv4Addr /* srcIp */, - REMOTE_IP4_ADDR /* dstIp */, LOCAL_PORT /* srcPort */, REMOTE_PORT /*dstPort */, + final ByteBuffer originalPacket = buildUdpPacket(tethered.macAddr, + tethered.routerMacAddr, tethered.ipv4Addr /* srcIp */, + REMOTE_IP4_ADDR /* dstIp */, LOCAL_PORT /* srcPort */, REMOTE_PORT /* dstPort */, PAYLOAD /* payload */); tester.verifyUpload(remote, originalPacket, p -> { Log.d(TAG, "Packet in upstream: " + dumpHexString(p)); - return isExpectedUdpPacket(p, false /* hasEther */, PAYLOAD); + return isExpectedUdpPacket(p, false /* hasEther */, true /* isIpv4 */, PAYLOAD); }); // Send a UDP packet in reply direction. final Inet4Address publicIp4Addr = (Inet4Address) TEST_IP4_ADDR.getAddress(); - final ByteBuffer replyPacket = buildUdpv4Packet(ID2, REMOTE_IP4_ADDR /* srcIp */, - publicIp4Addr /* dstIp */, REMOTE_PORT /* srcPort */, LOCAL_PORT /*dstPort */, + final ByteBuffer replyPacket = buildUdpPacket(REMOTE_IP4_ADDR /* srcIp */, + publicIp4Addr /* dstIp */, REMOTE_PORT /* srcPort */, LOCAL_PORT /* dstPort */, PAYLOAD2 /* payload */); remote.verifyDownload(tester, replyPacket, p -> { Log.d(TAG, "Packet in downstream: " + dumpHexString(p)); - return isExpectedUdpPacket(p, true/* hasEther */, PAYLOAD2); + return isExpectedUdpPacket(p, true /* hasEther */, true /* isIpv4 */, PAYLOAD2); }); if (usingBpf) { @@ -1004,13 +1041,13 @@ public class EthernetTetheringTest { // See kernel upstream commit b7b1d02fc43925a4d569ec221715db2dfa1ce4f5 and // nf_conntrack_udp_packet in net/netfilter/nf_conntrack_proto_udp.c Thread.sleep(UDP_STREAM_TS_MS); - final ByteBuffer originalPacket2 = buildUdpv4Packet(tethered.macAddr, - tethered.routerMacAddr, ID, tethered.ipv4Addr /* srcIp */, + final ByteBuffer originalPacket2 = buildUdpPacket(tethered.macAddr, + tethered.routerMacAddr, tethered.ipv4Addr /* srcIp */, REMOTE_IP4_ADDR /* dstIp */, LOCAL_PORT /* srcPort */, - REMOTE_PORT /*dstPort */, PAYLOAD3 /* payload */); + REMOTE_PORT /* dstPort */, PAYLOAD3 /* payload */); tester.verifyUpload(remote, originalPacket2, p -> { Log.d(TAG, "Packet in upstream: " + dumpHexString(p)); - return isExpectedUdpPacket(p, false /* hasEther */, PAYLOAD3); + return isExpectedUdpPacket(p, false /* hasEther */, true /* isIpv4 */, PAYLOAD3); }); // [1] Verify IPv4 upstream rule map. @@ -1046,7 +1083,7 @@ public class EthernetTetheringTest { for (int i = 0; i < TX_UDP_PACKET_COUNT; i++) { tester.verifyUpload(remote, originalPacket, p -> { Log.d(TAG, "Packet in upstream: " + dumpHexString(p)); - return isExpectedUdpPacket(p, false /* hasEther */, PAYLOAD); + return isExpectedUdpPacket(p, false /* hasEther */, true /* isIpv4 */, PAYLOAD); }); } @@ -1054,7 +1091,7 @@ public class EthernetTetheringTest { for (int i = 0; i < RX_UDP_PACKET_COUNT; i++) { remote.verifyDownload(tester, replyPacket, p -> { Log.d(TAG, "Packet in downstream: " + dumpHexString(p)); - return isExpectedUdpPacket(p, true/* hasEther */, PAYLOAD2); + return isExpectedUdpPacket(p, true /* hasEther */, true /* isIpv4 */, PAYLOAD2); }); } @@ -1079,13 +1116,14 @@ public class EthernetTetheringTest { } } - void initializeTethering() throws Exception { + void initializeTethering(List upstreamAddresses, List upstreamDnses) + throws Exception { assumeFalse(mEm.isAvailable()); // MyTetheringEventCallback currently only support await first available upstream. Tethering // may select internet network as upstream if test network is not available and not be // preferred yet. Create test upstream network before enable tethering. - mUpstreamTracker = createTestUpstream(toList(TEST_IP4_ADDR), toList(TEST_IP4_DNS)); + mUpstreamTracker = createTestUpstream(upstreamAddresses, upstreamDnses); mDownstreamIface = createTestInterface(); mEm.setIncludeTestInterfaces(true); @@ -1106,7 +1144,7 @@ public class EthernetTetheringTest { @Test @IgnoreAfter(Build.VERSION_CODES.R) public void testTetherUdpV4UpToR() throws Exception { - initializeTethering(); + initializeTethering(toList(TEST_IP4_ADDR), toList(TEST_IP4_DNS)); runUdp4Test(new TetheringTester(mDownstreamReader), new RemoteResponder(mUpstreamReader), false /* usingBpf */); } @@ -1142,7 +1180,7 @@ public class EthernetTetheringTest { @Test @IgnoreUpTo(Build.VERSION_CODES.R) public void testTetherUdpV4AfterR() throws Exception { - initializeTethering(); + initializeTethering(toList(TEST_IP4_ADDR), toList(TEST_IP4_DNS)); final String kernelVersion = VintfRuntimeInfo.getKernelVersion(); boolean usingBpf = isUdpOffloadSupportedByKernel(kernelVersion); if (!usingBpf) { @@ -1209,6 +1247,95 @@ public class EthernetTetheringTest { return null; } + @Nullable + private Inet6Address getClatIpv6Address(TetheringTester tester, + RemoteResponder remote, TetheredDevice tethered) throws Exception { + final ByteBuffer probePacket = buildUdpPacket(tethered.macAddr, + tethered.routerMacAddr, tethered.ipv4Addr /* srcIp */, + REMOTE_IP4_ADDR /* dstIp */, LOCAL_PORT /* srcPort */, REMOTE_PORT /* dstPort */, + TEST_REACHABILITY_PAYLOAD); + + // Send an IPv4 UDP packet from client and check that a CLAT translated IPv6 UDP packet can + // be found on upstream interface. Get CLAT IPv6 address from the CLAT translated IPv6 UDP + // packet. + byte[] expectedPacket = null; + for (int i = 0; i < TETHER_REACHABILITY_ATTEMPTS; i++) { + tester.sendPacket(probePacket); + expectedPacket = remote.getNextMatchedPacket(p -> { + Log.d(TAG, "Packet in upstream: " + dumpHexString(p)); + return isExpectedUdpPacket(p, false /* hasEther */, false /* isIpv4 */, + TEST_REACHABILITY_PAYLOAD); + }); + if (expectedPacket != null) break; + } + if (expectedPacket == null) return null; + + // Above has guaranteed that the found packet is an IPv6 packet without ether header. + final Ipv6Header ipv6Header = Struct.parse(Ipv6Header.class, + ByteBuffer.wrap(expectedPacket)); + return ipv6Header.srcIp; + } + + // Test network topology: + // + // public network (rawip) private network + // | UE (CLAT support) | + // +---------------+ V +------------+------------+ V +------------+ + // | NAT64 Gateway +---------+ Upstream | Downstream +---------+ Client | + // +---------------+ +------------+------------+ +------------+ + // remote ip public ip private ip + // [64:ff9b::808:808]:443 [clat ipv6]:9876 [TetheredDevice ipv4]:9876 + // + // Note that CLAT IPv6 address is generated by ClatCoordinator. Get the CLAT IPv6 address by + // sending out an IPv4 packet and extracting the source address from CLAT translated IPv6 + // packet. + // + private void runClatUdpTest(TetheringTester tester, RemoteResponder remote) + throws Exception { + // Currently tethering don't have API to tell when ipv6 tethering is available. Thus, let + // TetheringTester test ipv6 tethering connectivity before testing ipv6. + // TODO: move to a common place to avoid that every IPv6 test needs to call this function. + tester.waitForIpv6TetherConnectivityVerified(); + + final TetheredDevice tethered = tester.createTetheredDevice(MacAddress.fromString( + "1:2:3:4:5:6"), true /* hasIpv6 */); + + // Get CLAT IPv6 address. + final Inet6Address clatAddr6 = getClatIpv6Address(tester, remote, tethered); + assertNotNull(clatAddr6); + + // Send an IPv4 UDP packet in original direction. + // IPv4 packet -- CLAT translation --> IPv6 packet + final ByteBuffer originalPacket = buildUdpPacket(tethered.macAddr, + tethered.routerMacAddr, tethered.ipv4Addr /* srcIp */, + REMOTE_IP4_ADDR /* dstIp */, LOCAL_PORT /* srcPort */, REMOTE_PORT /* dstPort */, + PAYLOAD /* payload */); + tester.verifyUpload(remote, originalPacket, p -> { + Log.d(TAG, "Packet in upstream: " + dumpHexString(p)); + return isExpectedUdpPacket(p, false /* hasEther */, false /* isIpv4 */, PAYLOAD); + }); + + // Send an IPv6 UDP packet in reply direction. + // IPv6 packet -- CLAT translation --> IPv4 packet + final ByteBuffer replyPacket = buildUdpPacket(REMOTE_NAT64_ADDR /* srcIp */, + clatAddr6 /* dstIp */, REMOTE_PORT /* srcPort */, LOCAL_PORT /* dstPort */, + PAYLOAD2 /* payload */); + remote.verifyDownload(tester, replyPacket, p -> { + Log.d(TAG, "Packet in downstream: " + dumpHexString(p)); + return isExpectedUdpPacket(p, true /* hasEther */, true /* isIpv4 */, PAYLOAD2); + }); + + // TODO: test CLAT bpf maps. + } + + @Test + public void testTetherClatUdp() throws Exception { + // CLAT only starts on IPv6 only network. + initializeTethering(toList(TEST_IP6_ADDR), toList(TEST_IP6_DNS)); + runClatUdpTest(new TetheringTester(mDownstreamReader), + new RemoteResponder(mUpstreamReader)); + } + private List toList(T... array) { return Arrays.asList(array); } diff --git a/service/src/com/android/server/connectivity/Nat464Xlat.java b/service/src/com/android/server/connectivity/Nat464Xlat.java index 738caab925..e4ad391724 100644 --- a/service/src/com/android/server/connectivity/Nat464Xlat.java +++ b/service/src/com/android/server/connectivity/Nat464Xlat.java @@ -17,6 +17,7 @@ package com.android.server.connectivity; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; +import static android.net.NetworkCapabilities.TRANSPORT_TEST; import static com.android.net.module.util.CollectionUtils.contains; @@ -127,6 +128,11 @@ public class Nat464Xlat { final boolean supported = contains(NETWORK_TYPES, nai.networkInfo.getType()); final boolean connected = contains(NETWORK_STATES, nai.networkInfo.getState()); + // Allow to run clat on test network. + // TODO: merge to boolean "supported" once boolean "supported" is migrated to + // NetworkCapabilities.TRANSPORT_*. + final boolean isTestNetwork = nai.networkCapabilities.hasTransport(TRANSPORT_TEST); + // Only run clat on networks that have a global IPv6 address and don't have a native IPv4 // address. LinkProperties lp = nai.linkProperties; @@ -137,8 +143,8 @@ public class Nat464Xlat { final boolean skip464xlat = (nai.netAgentConfig() != null) && nai.netAgentConfig().skip464xlat; - return supported && connected && isIpv6OnlyNetwork && !skip464xlat && !nai.destroyed - && (nai.networkCapabilities.hasTransport(TRANSPORT_CELLULAR) + return (supported || isTestNetwork) && connected && isIpv6OnlyNetwork && !skip464xlat + && !nai.destroyed && (nai.networkCapabilities.hasTransport(TRANSPORT_CELLULAR) ? isCellular464XlatEnabled() : true); } From 8b56874caaefde7b013fabf9724b94de60e51e49 Mon Sep 17 00:00:00 2001 From: Remi NGUYEN VAN Date: Fri, 3 Jun 2022 12:04:23 +0900 Subject: [PATCH 2/2] Skip testTetherClatUdp before S The test does not pass on R. Bug: 234727688 Test: atest EthernetTetheringTest Change-Id: I831859e521b026ed4acb84de5857a7d3ec318ba5 --- .../tests/integration/src/android/net/EthernetTetheringTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java index ad2cbbca2f..92be84de9a 100644 --- a/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java +++ b/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java @@ -1329,6 +1329,7 @@ public class EthernetTetheringTest { } @Test + @IgnoreUpTo(Build.VERSION_CODES.R) public void testTetherClatUdp() throws Exception { // CLAT only starts on IPv6 only network. initializeTethering(toList(TEST_IP6_ADDR), toList(TEST_IP6_DNS));