Merge changes from topic "test-tethering-icmp"

* changes:
  EthernetTetheringTest: add testTetherTcpV6
  EthernetTetheringTest: add testTetherTcpV4
  EthernetTetheringTest: add testIcmpv4Echo
  EthernetTetheringTest: add testTetherClatIcmp
This commit is contained in:
Nucca Chen
2022-09-19 15:53:43 +00:00
committed by Gerrit Code Review
2 changed files with 395 additions and 28 deletions

View File

@@ -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 <T> List<T> toList(T... array) {
return Arrays.asList(array);
}

View File

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