Merge "EthernetTetheringTest: testTetherUdpV4Dns"
This commit is contained in:
@@ -24,7 +24,9 @@ import static android.net.InetAddresses.parseNumericAddress;
|
|||||||
import static android.net.TetheringManager.CONNECTIVITY_SCOPE_GLOBAL;
|
import static android.net.TetheringManager.CONNECTIVITY_SCOPE_GLOBAL;
|
||||||
import static android.net.TetheringManager.CONNECTIVITY_SCOPE_LOCAL;
|
import static android.net.TetheringManager.CONNECTIVITY_SCOPE_LOCAL;
|
||||||
import static android.net.TetheringManager.TETHERING_ETHERNET;
|
import static android.net.TetheringManager.TETHERING_ETHERNET;
|
||||||
|
import static android.net.TetheringTester.TestDnsPacket;
|
||||||
import static android.net.TetheringTester.isExpectedIcmpv6Packet;
|
import static android.net.TetheringTester.isExpectedIcmpv6Packet;
|
||||||
|
import static android.net.TetheringTester.isExpectedUdpDnsPacket;
|
||||||
import static android.net.TetheringTester.isExpectedUdpPacket;
|
import static android.net.TetheringTester.isExpectedUdpPacket;
|
||||||
import static android.system.OsConstants.IPPROTO_IP;
|
import static android.system.OsConstants.IPPROTO_IP;
|
||||||
import static android.system.OsConstants.IPPROTO_IPV6;
|
import static android.system.OsConstants.IPPROTO_IPV6;
|
||||||
@@ -81,7 +83,9 @@ import com.android.net.module.util.bpf.Tether4Key;
|
|||||||
import com.android.net.module.util.bpf.Tether4Value;
|
import com.android.net.module.util.bpf.Tether4Value;
|
||||||
import com.android.net.module.util.bpf.TetherStatsKey;
|
import com.android.net.module.util.bpf.TetherStatsKey;
|
||||||
import com.android.net.module.util.bpf.TetherStatsValue;
|
import com.android.net.module.util.bpf.TetherStatsValue;
|
||||||
|
import com.android.net.module.util.structs.Ipv4Header;
|
||||||
import com.android.net.module.util.structs.Ipv6Header;
|
import com.android.net.module.util.structs.Ipv6Header;
|
||||||
|
import com.android.net.module.util.structs.UdpHeader;
|
||||||
import com.android.testutils.DevSdkIgnoreRule;
|
import com.android.testutils.DevSdkIgnoreRule;
|
||||||
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
|
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
|
||||||
import com.android.testutils.DeviceInfoUtils;
|
import com.android.testutils.DeviceInfoUtils;
|
||||||
@@ -156,6 +160,8 @@ public class EthernetTetheringTest {
|
|||||||
private static final ByteBuffer TEST_REACHABILITY_PAYLOAD =
|
private static final ByteBuffer TEST_REACHABILITY_PAYLOAD =
|
||||||
ByteBuffer.wrap(new byte[] { (byte) 0x55, (byte) 0xaa });
|
ByteBuffer.wrap(new byte[] { (byte) 0x55, (byte) 0xaa });
|
||||||
|
|
||||||
|
private static final short DNS_PORT = 53;
|
||||||
|
|
||||||
private static final String DUMPSYS_TETHERING_RAWMAP_ARG = "bpfRawMap";
|
private static final String DUMPSYS_TETHERING_RAWMAP_ARG = "bpfRawMap";
|
||||||
private static final String DUMPSYS_RAWMAP_ARG_STATS = "--stats";
|
private static final String DUMPSYS_RAWMAP_ARG_STATS = "--stats";
|
||||||
private static final String DUMPSYS_RAWMAP_ARG_UPSTREAM4 = "--upstream4";
|
private static final String DUMPSYS_RAWMAP_ARG_UPSTREAM4 = "--upstream4";
|
||||||
@@ -165,6 +171,66 @@ public class EthernetTetheringTest {
|
|||||||
private static final int VERSION_TRAFFICCLASS_FLOWLABEL = 0x60000000;
|
private static final int VERSION_TRAFFICCLASS_FLOWLABEL = 0x60000000;
|
||||||
private static final short HOP_LIMIT = 0x40;
|
private static final short HOP_LIMIT = 0x40;
|
||||||
|
|
||||||
|
// 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[] {
|
||||||
|
// scapy.DNS(
|
||||||
|
// id=0xbeef,
|
||||||
|
// qr=0,
|
||||||
|
// qd=scapy.DNSQR(qname="hello.example.com"))
|
||||||
|
//
|
||||||
|
/* Header */
|
||||||
|
(byte) 0xbe, (byte) 0xef, /* Transaction ID: 0xbeef */
|
||||||
|
(byte) 0x01, (byte) 0x00, /* Flags: rd */
|
||||||
|
(byte) 0x00, (byte) 0x01, /* Questions: 1 */
|
||||||
|
(byte) 0x00, (byte) 0x00, /* Answer RRs: 0 */
|
||||||
|
(byte) 0x00, (byte) 0x00, /* Authority RRs: 0 */
|
||||||
|
(byte) 0x00, (byte) 0x00, /* Additional RRs: 0 */
|
||||||
|
/* Queries */
|
||||||
|
(byte) 0x05, (byte) 0x68, (byte) 0x65, (byte) 0x6c,
|
||||||
|
(byte) 0x6c, (byte) 0x6f, (byte) 0x07, (byte) 0x65,
|
||||||
|
(byte) 0x78, (byte) 0x61, (byte) 0x6d, (byte) 0x70,
|
||||||
|
(byte) 0x6c, (byte) 0x65, (byte) 0x03, (byte) 0x63,
|
||||||
|
(byte) 0x6f, (byte) 0x6d, (byte) 0x00, /* Name: hello.example.com */
|
||||||
|
(byte) 0x00, (byte) 0x01, /* Type: A */
|
||||||
|
(byte) 0x00, (byte) 0x01 /* Class: IN */
|
||||||
|
});
|
||||||
|
|
||||||
|
private static final byte[] DNS_REPLY = new byte[] {
|
||||||
|
// scapy.DNS(
|
||||||
|
// id=0,
|
||||||
|
// qr=1,
|
||||||
|
// qd=scapy.DNSQR(qname="hello.example.com"),
|
||||||
|
// an=scapy.DNSRR(rrname="hello.example.com", rdata='1.2.3.4'))
|
||||||
|
//
|
||||||
|
/* Header */
|
||||||
|
(byte) 0x00, (byte) 0x00, /* Transaction ID: 0x0, must be updated by dns query id */
|
||||||
|
(byte) 0x81, (byte) 0x00, /* Flags: qr rd */
|
||||||
|
(byte) 0x00, (byte) 0x01, /* Questions: 1 */
|
||||||
|
(byte) 0x00, (byte) 0x01, /* Answer RRs: 1 */
|
||||||
|
(byte) 0x00, (byte) 0x00, /* Authority RRs: 0 */
|
||||||
|
(byte) 0x00, (byte) 0x00, /* Additional RRs: 0 */
|
||||||
|
/* Queries */
|
||||||
|
(byte) 0x05, (byte) 0x68, (byte) 0x65, (byte) 0x6c,
|
||||||
|
(byte) 0x6c, (byte) 0x6f, (byte) 0x07, (byte) 0x65,
|
||||||
|
(byte) 0x78, (byte) 0x61, (byte) 0x6d, (byte) 0x70,
|
||||||
|
(byte) 0x6c, (byte) 0x65, (byte) 0x03, (byte) 0x63,
|
||||||
|
(byte) 0x6f, (byte) 0x6d, (byte) 0x00, /* Name: hello.example.com */
|
||||||
|
(byte) 0x00, (byte) 0x01, /* Type: A */
|
||||||
|
(byte) 0x00, (byte) 0x01, /* Class: IN */
|
||||||
|
/* Answers */
|
||||||
|
(byte) 0x05, (byte) 0x68, (byte) 0x65, (byte) 0x6c,
|
||||||
|
(byte) 0x6c, (byte) 0x6f, (byte) 0x07, (byte) 0x65,
|
||||||
|
(byte) 0x78, (byte) 0x61, (byte) 0x6d, (byte) 0x70,
|
||||||
|
(byte) 0x6c, (byte) 0x65, (byte) 0x03, (byte) 0x63,
|
||||||
|
(byte) 0x6f, (byte) 0x6d, (byte) 0x00, /* Name: hello.example.com */
|
||||||
|
(byte) 0x00, (byte) 0x01, /* Type: A */
|
||||||
|
(byte) 0x00, (byte) 0x01, /* Class: IN */
|
||||||
|
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, /* Time to live: 0 */
|
||||||
|
(byte) 0x00, (byte) 0x04, /* Data length: 4 */
|
||||||
|
(byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04 /* Address: 1.2.3.4 */
|
||||||
|
};
|
||||||
|
|
||||||
private final Context mContext = InstrumentationRegistry.getContext();
|
private final Context mContext = InstrumentationRegistry.getContext();
|
||||||
private final EthernetManager mEm = mContext.getSystemService(EthernetManager.class);
|
private final EthernetManager mEm = mContext.getSystemService(EthernetManager.class);
|
||||||
private final TetheringManager mTm = mContext.getSystemService(TetheringManager.class);
|
private final TetheringManager mTm = mContext.getSystemService(TetheringManager.class);
|
||||||
@@ -1371,6 +1437,94 @@ public class EthernetTetheringTest {
|
|||||||
runClatUdpTest();
|
runClatUdpTest();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private ByteBuffer buildDnsReplyMessageById(short id) {
|
||||||
|
byte[] replyMessage = Arrays.copyOf(DNS_REPLY, DNS_REPLY.length);
|
||||||
|
// Assign transaction id of reply message pattern with a given DNS transaction id.
|
||||||
|
replyMessage[0] = (byte) ((id >> 8) & 0xff);
|
||||||
|
replyMessage[1] = (byte) (id & 0xff);
|
||||||
|
Log.d(TAG, "Built DNS reply: " + dumpHexString(replyMessage));
|
||||||
|
|
||||||
|
return ByteBuffer.wrap(replyMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private void sendDownloadPacketDnsV4(@NonNull final Inet4Address srcIp,
|
||||||
|
@NonNull final Inet4Address dstIp, short srcPort, short dstPort, short dnsId,
|
||||||
|
@NonNull final TetheringTester tester) throws Exception {
|
||||||
|
// 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.
|
||||||
|
final ByteBuffer dnsReplyMessage = buildDnsReplyMessageById(dnsId);
|
||||||
|
final ByteBuffer testPacket = buildUdpPacket((InetAddress) srcIp,
|
||||||
|
(InetAddress) dstIp, srcPort, dstPort, dnsReplyMessage);
|
||||||
|
|
||||||
|
tester.verifyDownload(testPacket, p -> {
|
||||||
|
Log.d(TAG, "Packet in downstream: " + dumpHexString(p));
|
||||||
|
return isExpectedUdpDnsPacket(p, true /* hasEther */, true /* isIpv4 */,
|
||||||
|
dnsReplyMessage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send IPv4 UDP DNS packet and return the forwarded DNS packet on upstream.
|
||||||
|
@NonNull
|
||||||
|
private byte[] sendUploadPacketDnsV4(@NonNull final MacAddress srcMac,
|
||||||
|
@NonNull final MacAddress dstMac, @NonNull final Inet4Address srcIp,
|
||||||
|
@NonNull final Inet4Address dstIp, short srcPort, short dstPort,
|
||||||
|
@NonNull final TetheringTester tester) throws Exception {
|
||||||
|
final ByteBuffer testPacket = buildUdpPacket(srcMac, dstMac, srcIp, dstIp,
|
||||||
|
srcPort, dstPort, DNS_QUERY);
|
||||||
|
|
||||||
|
return tester.verifyUpload(testPacket, p -> {
|
||||||
|
Log.d(TAG, "Packet in upstream: " + dumpHexString(p));
|
||||||
|
return isExpectedUdpDnsPacket(p, false /* hasEther */, true /* isIpv4 */,
|
||||||
|
DNS_QUERY);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTetherUdpV4Dns() 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 */);
|
||||||
|
|
||||||
|
// [1] Send DNS query.
|
||||||
|
// tethered device --> downstream --> dnsmasq forwarding --> upstream --> DNS server
|
||||||
|
//
|
||||||
|
// Need to extract DNS transaction id and source port from dnsmasq forwarded DNS query
|
||||||
|
// packet. dnsmasq forwarding creats new query which means UDP source port and DNS
|
||||||
|
// transaction id are changed from original sent DNS query. See forward_query() in
|
||||||
|
// external/dnsmasq/src/forward.c. Note that #TetheringTester.isExpectedUdpDnsPacket
|
||||||
|
// guarantees that |forwardedQueryPacket| is a valid DNS packet. So we can parse it as DNS
|
||||||
|
// packet.
|
||||||
|
final MacAddress srcMac = tethered.macAddr;
|
||||||
|
final MacAddress dstMac = tethered.routerMacAddr;
|
||||||
|
final Inet4Address clientIp = tethered.ipv4Addr;
|
||||||
|
final Inet4Address gatewayIp = tethered.ipv4Gatway;
|
||||||
|
final byte[] forwardedQueryPacket = sendUploadPacketDnsV4(srcMac, dstMac, clientIp,
|
||||||
|
gatewayIp, LOCAL_PORT, DNS_PORT, tester);
|
||||||
|
final ByteBuffer buf = ByteBuffer.wrap(forwardedQueryPacket);
|
||||||
|
Struct.parse(Ipv4Header.class, buf);
|
||||||
|
final UdpHeader udpHeader = Struct.parse(UdpHeader.class, buf);
|
||||||
|
final TestDnsPacket dnsQuery = TestDnsPacket.getTestDnsPacket(buf);
|
||||||
|
assertNotNull(dnsQuery);
|
||||||
|
Log.d(TAG, "Forwarded UDP source port: " + udpHeader.srcPort + ", DNS query id: "
|
||||||
|
+ dnsQuery.getHeader().id);
|
||||||
|
|
||||||
|
// [2] Send DNS reply.
|
||||||
|
// DNS server --> upstream --> dnsmasq forwarding --> downstream --> tethered device
|
||||||
|
//
|
||||||
|
// DNS reply 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.
|
||||||
|
final Inet4Address remoteIp = (Inet4Address) TEST_IP4_DNS;
|
||||||
|
final Inet4Address tetheringUpstreamIp = (Inet4Address) TEST_IP4_ADDR.getAddress();
|
||||||
|
sendDownloadPacketDnsV4(remoteIp, tetheringUpstreamIp, DNS_PORT,
|
||||||
|
(short) udpHeader.srcPort, (short) dnsQuery.getHeader().id, tester);
|
||||||
|
}
|
||||||
|
|
||||||
private <T> List<T> toList(T... array) {
|
private <T> List<T> toList(T... array) {
|
||||||
return Arrays.asList(array);
|
return Arrays.asList(array);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,11 @@ import static android.net.InetAddresses.parseNumericAddress;
|
|||||||
import static android.system.OsConstants.IPPROTO_ICMPV6;
|
import static android.system.OsConstants.IPPROTO_ICMPV6;
|
||||||
import static android.system.OsConstants.IPPROTO_UDP;
|
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.NSSECTION;
|
||||||
|
import static com.android.net.module.util.DnsPacket.QDSECTION;
|
||||||
|
import static com.android.net.module.util.HexDump.dumpHexString;
|
||||||
import static com.android.net.module.util.NetworkStackConstants.ARP_REPLY;
|
import static com.android.net.module.util.NetworkStackConstants.ARP_REPLY;
|
||||||
import static com.android.net.module.util.NetworkStackConstants.ARP_REQUEST;
|
import static com.android.net.module.util.NetworkStackConstants.ARP_REQUEST;
|
||||||
import static com.android.net.module.util.NetworkStackConstants.ETHER_ADDR_LEN;
|
import static com.android.net.module.util.NetworkStackConstants.ETHER_ADDR_LEN;
|
||||||
@@ -41,12 +46,14 @@ import static org.junit.Assert.fail;
|
|||||||
import android.net.dhcp.DhcpAckPacket;
|
import android.net.dhcp.DhcpAckPacket;
|
||||||
import android.net.dhcp.DhcpOfferPacket;
|
import android.net.dhcp.DhcpOfferPacket;
|
||||||
import android.net.dhcp.DhcpPacket;
|
import android.net.dhcp.DhcpPacket;
|
||||||
|
import android.text.TextUtils;
|
||||||
import android.util.ArrayMap;
|
import android.util.ArrayMap;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.android.net.module.util.DnsPacket;
|
||||||
import com.android.net.module.util.Ipv6Utils;
|
import com.android.net.module.util.Ipv6Utils;
|
||||||
import com.android.net.module.util.Struct;
|
import com.android.net.module.util.Struct;
|
||||||
import com.android.net.module.util.structs.EthernetHeader;
|
import com.android.net.module.util.structs.EthernetHeader;
|
||||||
@@ -124,12 +131,14 @@ public final class TetheringTester {
|
|||||||
public final MacAddress macAddr;
|
public final MacAddress macAddr;
|
||||||
public final MacAddress routerMacAddr;
|
public final MacAddress routerMacAddr;
|
||||||
public final Inet4Address ipv4Addr;
|
public final Inet4Address ipv4Addr;
|
||||||
|
public final Inet4Address ipv4Gatway;
|
||||||
public final Inet6Address ipv6Addr;
|
public final Inet6Address ipv6Addr;
|
||||||
|
|
||||||
private TetheredDevice(MacAddress mac, boolean hasIpv6) throws Exception {
|
private TetheredDevice(MacAddress mac, boolean hasIpv6) throws Exception {
|
||||||
macAddr = mac;
|
macAddr = mac;
|
||||||
DhcpResults dhcpResults = runDhcp(macAddr.toByteArray());
|
DhcpResults dhcpResults = runDhcp(macAddr.toByteArray());
|
||||||
ipv4Addr = (Inet4Address) dhcpResults.ipAddress.getAddress();
|
ipv4Addr = (Inet4Address) dhcpResults.ipAddress.getAddress();
|
||||||
|
ipv4Gatway = (Inet4Address) dhcpResults.gateway;
|
||||||
routerMacAddr = getRouterMacAddressFromArp(ipv4Addr, macAddr,
|
routerMacAddr = getRouterMacAddressFromArp(ipv4Addr, macAddr,
|
||||||
dhcpResults.serverAddress);
|
dhcpResults.serverAddress);
|
||||||
ipv6Addr = hasIpv6 ? runSlaac(macAddr, routerMacAddr) : null;
|
ipv6Addr = hasIpv6 ? runSlaac(macAddr, routerMacAddr) : null;
|
||||||
@@ -386,8 +395,8 @@ public final class TetheringTester {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isExpectedUdpPacket(@NonNull final byte[] rawPacket, boolean hasEth,
|
private static boolean isExpectedUdpPacket(@NonNull final byte[] rawPacket, boolean hasEth,
|
||||||
boolean isIpv4, @NonNull final ByteBuffer payload) {
|
boolean isIpv4, Predicate<ByteBuffer> payloadVerifier) {
|
||||||
final ByteBuffer buf = ByteBuffer.wrap(rawPacket);
|
final ByteBuffer buf = ByteBuffer.wrap(rawPacket);
|
||||||
try {
|
try {
|
||||||
if (hasEth && !hasExpectedEtherHeader(buf, isIpv4)) return false;
|
if (hasEth && !hasExpectedEtherHeader(buf, isIpv4)) return false;
|
||||||
@@ -395,15 +404,178 @@ public final class TetheringTester {
|
|||||||
if (!hasExpectedIpHeader(buf, isIpv4, IPPROTO_UDP)) return false;
|
if (!hasExpectedIpHeader(buf, isIpv4, IPPROTO_UDP)) return false;
|
||||||
|
|
||||||
if (Struct.parse(UdpHeader.class, buf) == null) return false;
|
if (Struct.parse(UdpHeader.class, buf) == null) return false;
|
||||||
|
|
||||||
|
if (!payloadVerifier.test(buf)) return false;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// Parsing packet fail means it is not udp packet.
|
// Parsing packet fail means it is not udp packet.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (buf.remaining() != payload.limit()) return false;
|
// Returns remaining bytes in the ByteBuffer in a new byte array of the right size. The
|
||||||
|
// ByteBuffer will be empty upon return. Used to avoid lint warning.
|
||||||
|
// See https://errorprone.info/bugpattern/ByteBufferBackingArray
|
||||||
|
private static byte[] getRemaining(final ByteBuffer buf) {
|
||||||
|
final byte[] bytes = new byte[buf.remaining()];
|
||||||
|
buf.get(bytes);
|
||||||
|
Log.d(TAG, "Get remaining bytes: " + dumpHexString(bytes));
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
return Arrays.equals(Arrays.copyOfRange(buf.array(), buf.position(), buf.limit()),
|
// |expectedPayload| is copied as read-only because the caller may reuse it.
|
||||||
payload.array());
|
public static boolean isExpectedUdpPacket(@NonNull final byte[] rawPacket, boolean hasEth,
|
||||||
|
boolean isIpv4, @NonNull final ByteBuffer expectedPayload) {
|
||||||
|
return isExpectedUdpPacket(rawPacket, hasEth, isIpv4, p -> {
|
||||||
|
if (p.remaining() != expectedPayload.limit()) return false;
|
||||||
|
|
||||||
|
return Arrays.equals(getRemaining(p), getRemaining(
|
||||||
|
expectedPayload.asReadOnlyBuffer()));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// |expectedPayload| is copied as read-only because the caller may reuse it.
|
||||||
|
// See hasExpectedDnsMessage.
|
||||||
|
public static boolean isExpectedUdpDnsPacket(@NonNull final byte[] rawPacket, boolean hasEth,
|
||||||
|
boolean isIpv4, @NonNull final ByteBuffer expectedPayload) {
|
||||||
|
return isExpectedUdpPacket(rawPacket, hasEth, isIpv4, p -> {
|
||||||
|
return hasExpectedDnsMessage(p, expectedPayload);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TestDnsPacket extends DnsPacket {
|
||||||
|
TestDnsPacket(byte[] data) throws DnsPacket.ParseException {
|
||||||
|
super(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static TestDnsPacket getTestDnsPacket(final ByteBuffer buf) {
|
||||||
|
try {
|
||||||
|
// The ByteBuffer will be empty upon return.
|
||||||
|
return new TestDnsPacket(getRemaining(buf));
|
||||||
|
} catch (DnsPacket.ParseException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public DnsHeader getHeader() {
|
||||||
|
return mHeader;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<DnsRecord> getRecordList(int secType) {
|
||||||
|
return mRecords[secType];
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getANCount() {
|
||||||
|
return mHeader.getRecordCount(ANSECTION);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getQDCount() {
|
||||||
|
return mHeader.getRecordCount(QDSECTION);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getNSCount() {
|
||||||
|
return mHeader.getRecordCount(NSSECTION);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getARCount() {
|
||||||
|
return mHeader.getRecordCount(ARSECTION);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isRecordsEquals(int type, @NonNull final TestDnsPacket other) {
|
||||||
|
List<DnsRecord> records = getRecordList(type);
|
||||||
|
List<DnsRecord> otherRecords = other.getRecordList(type);
|
||||||
|
|
||||||
|
if (records.size() != otherRecords.size()) return false;
|
||||||
|
|
||||||
|
// Expect that two compared resource records are in the same order. For current tests
|
||||||
|
// in EthernetTetheringTest, it is okay because dnsmasq doesn't reorder the forwarded
|
||||||
|
// resource records.
|
||||||
|
// TODO: consider allowing that compare records out of order.
|
||||||
|
for (int i = 0; i < records.size(); i++) {
|
||||||
|
// TODO: use DnsRecord.equals once aosp/1387135 is merged.
|
||||||
|
if (!TextUtils.equals(records.get(i).dName, otherRecords.get(i).dName)
|
||||||
|
|| records.get(i).nsType != otherRecords.get(i).nsType
|
||||||
|
|| records.get(i).nsClass != otherRecords.get(i).nsClass
|
||||||
|
|| records.get(i).ttl != otherRecords.get(i).ttl
|
||||||
|
|| !Arrays.equals(records.get(i).getRR(), otherRecords.get(i).getRR())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isQDRecordsEquals(@NonNull final TestDnsPacket other) {
|
||||||
|
return isRecordsEquals(QDSECTION, other);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isANRecordsEquals(@NonNull final TestDnsPacket other) {
|
||||||
|
return isRecordsEquals(ANSECTION, other);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The ByteBuffer |actual| will be empty upon return. The ByteBuffer |excepted| will be copied
|
||||||
|
// as read-only because the caller may reuse it.
|
||||||
|
private static boolean hasExpectedDnsMessage(@NonNull final ByteBuffer actual,
|
||||||
|
@NonNull final ByteBuffer excepted) {
|
||||||
|
// Forwarded DNS message is extracted from remaining received packet buffer which has
|
||||||
|
// already parsed ethernet header, if any, IP header and UDP header.
|
||||||
|
final TestDnsPacket forwardedDns = TestDnsPacket.getTestDnsPacket(actual);
|
||||||
|
if (forwardedDns == null) return false;
|
||||||
|
|
||||||
|
// Original DNS message is the payload of the sending test UDP packet. It is used to check
|
||||||
|
// that the forwarded DNS query and reply have corresponding contents.
|
||||||
|
final TestDnsPacket originalDns = TestDnsPacket.getTestDnsPacket(
|
||||||
|
excepted.asReadOnlyBuffer());
|
||||||
|
assertNotNull(originalDns);
|
||||||
|
|
||||||
|
// Compare original DNS message which is sent to dnsmasq and forwarded DNS message which
|
||||||
|
// is forwarded by dnsmasq. The original message and forwarded message may be not identical
|
||||||
|
// because dnsmasq may change the header flags or even recreate the DNS query message and
|
||||||
|
// so on. We only simple check on forwarded packet and monitor if test will be broken by
|
||||||
|
// vendor dnsmasq customization. See forward_query() in external/dnsmasq/src/forward.c.
|
||||||
|
//
|
||||||
|
// DNS message format. See rfc1035 section 4.1.
|
||||||
|
// +---------------------+
|
||||||
|
// | Header |
|
||||||
|
// +---------------------+
|
||||||
|
// | Question | the question for the name server
|
||||||
|
// +---------------------+
|
||||||
|
// | Answer | RRs answering the question
|
||||||
|
// +---------------------+
|
||||||
|
// | Authority | RRs pointing toward an authority
|
||||||
|
// +---------------------+
|
||||||
|
// | Additional | RRs holding additional information
|
||||||
|
// +---------------------+
|
||||||
|
|
||||||
|
// [1] Header section. See rfc1035 section 4.1.1.
|
||||||
|
// Verify QR flag bit, QDCOUNT, ANCOUNT, NSCOUNT, ARCOUNT.
|
||||||
|
if (originalDns.getHeader().isResponse() != forwardedDns.getHeader().isResponse()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (originalDns.getQDCount() != forwardedDns.getQDCount()) return false;
|
||||||
|
if (originalDns.getANCount() != forwardedDns.getANCount()) return false;
|
||||||
|
if (originalDns.getNSCount() != forwardedDns.getNSCount()) return false;
|
||||||
|
if (originalDns.getARCount() != forwardedDns.getARCount()) return false;
|
||||||
|
|
||||||
|
// [2] Question section. See rfc1035 section 4.1.2.
|
||||||
|
// Question section has at least one entry either DNS query or DNS reply.
|
||||||
|
if (forwardedDns.getRecordList(QDSECTION).isEmpty()) return false;
|
||||||
|
// Expect that original and forwarded message have the same question records (usually 1).
|
||||||
|
if (!originalDns.isQDRecordsEquals(forwardedDns)) return false;
|
||||||
|
|
||||||
|
// [3] Answer section. See rfc1035 section 4.1.3.
|
||||||
|
if (forwardedDns.getHeader().isResponse()) {
|
||||||
|
// DNS reply has at least have one answer in our tests.
|
||||||
|
// See EthernetTetheringTest#testTetherUdpV4Dns.
|
||||||
|
if (forwardedDns.getRecordList(ANSECTION).isEmpty()) return false;
|
||||||
|
// Expect that original and forwarded message have the same answer records.
|
||||||
|
if (!originalDns.isANRecordsEquals(forwardedDns)) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore checking {Authority, Additional} sections because they are not tested
|
||||||
|
// in EthernetTetheringTest.
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendUploadPacket(ByteBuffer packet) throws Exception {
|
private void sendUploadPacket(ByteBuffer packet) throws Exception {
|
||||||
|
|||||||
Reference in New Issue
Block a user