Merge changes from topic "roll_forward_testtetherclatudp"

* changes:
  Skip testTetherClatUdp before S
  Revert "Revert "EthernetTetheringTest: add testTetherClatUdp""
This commit is contained in:
Treehugger Robot
2022-06-03 07:21:01 +00:00
committed by Gerrit Code Review
2 changed files with 175 additions and 41 deletions

View File

@@ -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<LinkAddress> upstreamAddresses, List<InetAddress> 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,96 @@ 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
@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));
runClatUdpTest(new TetheringTester(mDownstreamReader),
new RemoteResponder(mUpstreamReader));
}
private <T> List<T> toList(T... array) {
return Arrays.asList(array);
}

View File

@@ -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);
}