EthernetTetheringTest: add testTetherClatIcmp
Bug: 237369591 Test: atest EthernetTetheringTest Change-Id: I9dad9b559e0963a2d3a3f6a4c8d3b159a185801d
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user