EthernetTetheringTest: add testTetherClatIcmp

Bug: 237369591
Test: atest EthernetTetheringTest
Change-Id: I9dad9b559e0963a2d3a3f6a4c8d3b159a185801d
This commit is contained in:
Hungming Chen
2022-07-25 16:56:24 +08:00
parent ba8a3a15b6
commit 90551145ae
2 changed files with 140 additions and 13 deletions

View File

@@ -27,20 +27,29 @@ 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.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_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.testutils.DeviceInfoUtils.KVersion;
import static com.android.testutils.TestNetworkTrackerKt.initTestNetwork;
import static com.android.testutils.TestPermissionUtil.runAsShell;
@@ -88,6 +97,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;
@@ -179,6 +190,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 +465,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 +989,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);
});
}
@@ -1513,12 +1533,108 @@ 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(
@NonNull final MacAddress srcMac, @NonNull 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 int etherHeaderLen = Struct.getSize(EthernetHeader.class);
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);
final EthernetHeader ethHeader = new EthernetHeader(dstMac, srcMac, ETHER_TYPE_IPV4);
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);
final Icmpv4Header icmpv4Header = new Icmpv4Header((byte) type, ICMPECHO_CODE,
(short) 0 /* checksum, calculate later */);
ethHeader.writeToByteBuffer(packet);
ipv4Header.writeToByteBuffer(packet);
icmpv4Header.writeToByteBuffer(packet);
packet.put(payload);
packet.flip();
// Used for updating IP header fields. IPv4 header offset in buffer equals ethernet header
// length because IPv4 header is located next to ethernet header.
final int ipv4HeaderOffset = etherHeaderLen;
// 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;
}
// 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);

View File

@@ -17,6 +17,7 @@
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_UDP;
@@ -57,6 +58,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;
@@ -268,7 +270,8 @@ public final class TetheringTester {
private List<PrefixInformationOption> 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 +301,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<PrefixInformationOption> options = getRaPrefixOptions(raPacket);
@@ -360,20 +364,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;