diff --git a/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java b/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java index a9add20239..91625460d3 100644 --- a/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java +++ b/Tethering/tests/integration/base/android/net/EthernetTetheringTestBase.java @@ -119,9 +119,11 @@ public abstract class EthernetTetheringTestBase { (Inet4Address) parseNumericAddress("8.8.8.8"); protected static final Inet6Address REMOTE_IP6_ADDR = (Inet6Address) parseNumericAddress("2002:db8:1::515:ca"); + // The IPv6 network address translation of REMOTE_IP4_ADDR if pref64::/n is 64:ff9b::/96. + // For more information, see TetheringTester#PREF64_IPV4ONLY_ADDR, which assumes a prefix + // of 64:ff9b::/96. protected static final Inet6Address REMOTE_NAT64_ADDR = (Inet6Address) parseNumericAddress("64:ff9b::808:808"); - protected static final IpPrefix TEST_NAT64PREFIX = new IpPrefix("64:ff9b::/96"); // LOCAL_PORT is 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 @@ -630,7 +632,6 @@ public abstract class EthernetTetheringTestBase { final LinkProperties lp = new LinkProperties(); lp.setLinkAddresses(addresses); lp.setDnsServers(dnses); - lp.setNat64Prefix(TEST_NAT64PREFIX); return runAsShell(MANAGE_TEST_NETWORKS, () -> initTestNetwork(mContext, lp, TIMEOUT_MS)); } diff --git a/Tethering/tests/integration/base/android/net/TetheringTester.java b/Tethering/tests/integration/base/android/net/TetheringTester.java index 33baf933dc..3f3768e85f 100644 --- a/Tethering/tests/integration/base/android/net/TetheringTester.java +++ b/Tethering/tests/integration/base/android/net/TetheringTester.java @@ -16,6 +16,8 @@ package android.net; +import static android.net.DnsResolver.CLASS_IN; +import static android.net.DnsResolver.TYPE_AAAA; import static android.net.InetAddresses.parseNumericAddress; import static android.system.OsConstants.ICMP_ECHO; import static android.system.OsConstants.ICMP_ECHOREPLY; @@ -28,6 +30,8 @@ import static android.system.OsConstants.IPPROTO_UDP; import static com.android.net.module.util.DnsPacket.ANSECTION; import static com.android.net.module.util.DnsPacket.ARSECTION; +import static com.android.net.module.util.DnsPacket.DnsHeader; +import static com.android.net.module.util.DnsPacket.DnsRecord; import static com.android.net.module.util.DnsPacket.NSSECTION; import static com.android.net.module.util.DnsPacket.QDSECTION; import static com.android.net.module.util.HexDump.dumpHexString; @@ -130,6 +134,27 @@ public final class TetheringTester { // ICMP definition. private static final short ICMPECHO_CODE = 0x0; + // Prefix64 discovery definition. See RFC 7050 section 8. + // Note that the AAAA response Pref64::WKAs consisting of Pref64::/n and WKA. + // Use 64:ff9b::/96 as Pref64::/n and WKA 192.0.0.17{0|1} here. + // + // Host DNS64 server + // | | + // | "AAAA" query for "ipv4only.arpa." | + // |----------------------------------------------->| + // | | + // | "AAAA" response with: | + // | "64:ff9b::192.0.0.170" | + // |<-----------------------------------------------| + // + private static final String PREF64_IPV4ONLY_HOSTNAME = "ipv4only.arpa"; + private static final InetAddress PREF64_IPV4ONLY_ADDR = parseNumericAddress( + "64:ff9b::192.0.0.170"); + + // DNS header definition. + private static final short FLAG = (short) 0x8100; // qr, ra + private static final short TTL = (short) 0; + public static final String DHCP_HOSTNAME = "testhostname"; private final ArrayMap mTetheredDevices; @@ -490,6 +515,11 @@ public final class TetheringTester { super(data); } + TestDnsPacket(@NonNull DnsHeader header, @Nullable ArrayList qd, + @Nullable ArrayList an) { + super(header, qd, an); + } + @Nullable public static TestDnsPacket getTestDnsPacket(final ByteBuffer buf) { try { @@ -862,10 +892,85 @@ public final class TetheringTester { return null; } + @NonNull + private ByteBuffer buildUdpDnsPrefix64ReplyPacket(int dnsId, @NonNull final Inet6Address srcIp, + @NonNull final Inet6Address dstIp, short srcPort, short dstPort) throws Exception { + // [1] Build prefix64 DNS message. + final ArrayList qlist = new ArrayList<>(); + // Fill QD section. + qlist.add(DnsRecord.makeQuestion(PREF64_IPV4ONLY_HOSTNAME, TYPE_AAAA, CLASS_IN)); + final ArrayList alist = new ArrayList<>(); + // Fill AN sections. + alist.add(DnsRecord.makeAOrAAAARecord(ANSECTION, PREF64_IPV4ONLY_HOSTNAME, CLASS_IN, TTL, + PREF64_IPV4ONLY_ADDR)); + final TestDnsPacket dns = new TestDnsPacket( + new DnsHeader(dnsId, FLAG, qlist.size(), alist.size()), qlist, alist); + + // [2] Build IPv6 UDP DNS packet. + return buildUdpPacket(srcIp, dstIp, srcPort, dstPort, ByteBuffer.wrap(dns.getBytes())); + } + + private void maybeReplyUdpDnsPrefix64Discovery(@NonNull byte[] packet) { + final ByteBuffer buf = ByteBuffer.wrap(packet); + + // [1] Parse the prefix64 discovery DNS query for hostname ipv4only.arpa. + // Parse IPv6 and UDP header. + Ipv6Header ipv6Header = null; + try { + ipv6Header = Struct.parse(Ipv6Header.class, buf); + if (ipv6Header == null || ipv6Header.nextHeader != IPPROTO_UDP) return; + } catch (Exception e) { + // Parsing packet fail means it is not IPv6 UDP packet. + return; + } + final UdpHeader udpHeader = Struct.parse(UdpHeader.class, buf); + + // Parse DNS message. + final TestDnsPacket pref64Query = TestDnsPacket.getTestDnsPacket(buf); + if (pref64Query == null) return; + if (pref64Query.getHeader().isResponse()) return; + if (pref64Query.getQDCount() != 1) return; + if (pref64Query.getANCount() != 0) return; + if (pref64Query.getNSCount() != 0) return; + if (pref64Query.getARCount() != 0) return; + + final List qdRecordList = pref64Query.getRecordList(QDSECTION); + if (qdRecordList.size() != 1) return; + if (!qdRecordList.get(0).dName.equals(PREF64_IPV4ONLY_HOSTNAME)) return; + + // [2] Build prefix64 DNS discovery reply from received query. + // DNS response transaction id must be copied from DNS query. Used by the requester + // to match up replies to outstanding queries. See RFC 1035 section 4.1.1. Also reverse + // the source/destination address/port of query packet for building reply packet. + final ByteBuffer replyPacket; + try { + replyPacket = buildUdpDnsPrefix64ReplyPacket(pref64Query.getHeader().getId(), + ipv6Header.dstIp /* srcIp */, ipv6Header.srcIp /* dstIp */, + (short) udpHeader.dstPort /* srcPort */, + (short) udpHeader.srcPort /* dstPort */); + } catch (Exception e) { + fail("Failed to build prefix64 discovery reply for " + ipv6Header.srcIp + ": " + e); + return; + } + + Log.d(TAG, "Sending prefix64 discovery reply"); + try { + sendDownloadPacket(replyPacket); + } catch (Exception e) { + fail("Failed to reply prefix64 discovery for " + ipv6Header.srcIp + ": " + e); + } + } + private byte[] getUploadPacket(Predicate filter) { assertNotNull("Can't deal with upstream interface in local only mode", mUpstreamReader); - return mUpstreamReader.poll(PACKET_READ_TIMEOUT_MS, filter); + byte[] packet; + while ((packet = mUpstreamReader.poll(PACKET_READ_TIMEOUT_MS)) != null) { + if (filter.test(packet)) return packet; + + maybeReplyUdpDnsPrefix64Discovery(packet); + } + return null; } private @NonNull byte[] verifyPacketNotNull(String message, @Nullable byte[] packet) {