• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.net;
18 
19 import static android.net.InetAddresses.parseNumericAddress;
20 import static android.system.OsConstants.IPPROTO_ICMP;
21 import static android.system.OsConstants.IPPROTO_ICMPV6;
22 import static android.system.OsConstants.IPPROTO_TCP;
23 import static android.system.OsConstants.IPPROTO_UDP;
24 
25 import static com.android.net.module.util.DnsPacket.ANSECTION;
26 import static com.android.net.module.util.DnsPacket.ARSECTION;
27 import static com.android.net.module.util.DnsPacket.NSSECTION;
28 import static com.android.net.module.util.DnsPacket.QDSECTION;
29 import static com.android.net.module.util.HexDump.dumpHexString;
30 import static com.android.net.module.util.NetworkStackConstants.ARP_REPLY;
31 import static com.android.net.module.util.NetworkStackConstants.ARP_REQUEST;
32 import static com.android.net.module.util.NetworkStackConstants.ETHER_ADDR_LEN;
33 import static com.android.net.module.util.NetworkStackConstants.ETHER_BROADCAST;
34 import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV4;
35 import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV6;
36 import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_PIO;
37 import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_SLLA;
38 import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_TLLA;
39 import static com.android.net.module.util.NetworkStackConstants.ICMPV6_NEIGHBOR_SOLICITATION;
40 import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
41 import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ALL_NODES_MULTICAST;
42 import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_OVERRIDE;
43 import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED;
44 import static com.android.net.module.util.NetworkStackConstants.TCPHDR_SYN;
45 
46 import static org.junit.Assert.assertNotNull;
47 import static org.junit.Assert.fail;
48 
49 import android.net.dhcp.DhcpAckPacket;
50 import android.net.dhcp.DhcpOfferPacket;
51 import android.net.dhcp.DhcpPacket;
52 import android.text.TextUtils;
53 import android.util.ArrayMap;
54 import android.util.Log;
55 
56 import androidx.annotation.NonNull;
57 import androidx.annotation.Nullable;
58 
59 import com.android.net.module.util.DnsPacket;
60 import com.android.net.module.util.Ipv6Utils;
61 import com.android.net.module.util.Struct;
62 import com.android.net.module.util.structs.EthernetHeader;
63 import com.android.net.module.util.structs.Icmpv4Header;
64 import com.android.net.module.util.structs.Icmpv6Header;
65 import com.android.net.module.util.structs.Ipv4Header;
66 import com.android.net.module.util.structs.Ipv6Header;
67 import com.android.net.module.util.structs.LlaOption;
68 import com.android.net.module.util.structs.NsHeader;
69 import com.android.net.module.util.structs.PrefixInformationOption;
70 import com.android.net.module.util.structs.RaHeader;
71 import com.android.net.module.util.structs.TcpHeader;
72 import com.android.net.module.util.structs.UdpHeader;
73 import com.android.networkstack.arp.ArpPacket;
74 import com.android.testutils.TapPacketReader;
75 
76 import java.net.Inet4Address;
77 import java.net.Inet6Address;
78 import java.net.InetAddress;
79 import java.nio.ByteBuffer;
80 import java.util.ArrayList;
81 import java.util.Arrays;
82 import java.util.List;
83 import java.util.Random;
84 import java.util.concurrent.TimeoutException;
85 import java.util.function.Predicate;
86 
87 /**
88  * A class simulate tethered client. When caller create TetheringTester, it would connect to
89  * tethering module that do the dhcp and slaac to obtain ipv4 and ipv6 address. Then caller can
90  * send/receive packets by this class.
91  */
92 public final class TetheringTester {
93     private static final String TAG = TetheringTester.class.getSimpleName();
94     private static final int PACKET_READ_TIMEOUT_MS = 500;
95     private static final int DHCP_DISCOVER_ATTEMPTS = 10;
96     private static final int READ_RA_ATTEMPTS = 10;
97     private static final byte[] DHCP_REQUESTED_PARAMS = new byte[] {
98             DhcpPacket.DHCP_SUBNET_MASK,
99             DhcpPacket.DHCP_ROUTER,
100             DhcpPacket.DHCP_DNS_SERVER,
101             DhcpPacket.DHCP_LEASE_TIME,
102     };
103     private static final InetAddress LINK_LOCAL = parseNumericAddress("fe80::1");
104 
105     public static final String DHCP_HOSTNAME = "testhostname";
106 
107     private final ArrayMap<MacAddress, TetheredDevice> mTetheredDevices;
108     private final TapPacketReader mDownstreamReader;
109     private final TapPacketReader mUpstreamReader;
110 
TetheringTester(TapPacketReader downstream)111     public TetheringTester(TapPacketReader downstream) {
112         this(downstream, null);
113     }
114 
TetheringTester(TapPacketReader downstream, TapPacketReader upstream)115     public TetheringTester(TapPacketReader downstream, TapPacketReader upstream) {
116         if (downstream == null) fail("Downstream reader could not be NULL");
117 
118         mDownstreamReader = downstream;
119         mUpstreamReader = upstream;
120         mTetheredDevices = new ArrayMap<>();
121     }
122 
createTetheredDevice(MacAddress macAddr, boolean hasIpv6)123     public TetheredDevice createTetheredDevice(MacAddress macAddr, boolean hasIpv6)
124             throws Exception {
125         if (mTetheredDevices.get(macAddr) != null) {
126             fail("Tethered device already created");
127         }
128 
129         TetheredDevice tethered = new TetheredDevice(macAddr, hasIpv6);
130         mTetheredDevices.put(macAddr, tethered);
131 
132         return tethered;
133     }
134 
135     public class TetheredDevice {
136         public final MacAddress macAddr;
137         public final MacAddress routerMacAddr;
138         public final Inet4Address ipv4Addr;
139         public final Inet4Address ipv4Gatway;
140         public final Inet6Address ipv6Addr;
141 
TetheredDevice(MacAddress mac, boolean hasIpv6)142         private TetheredDevice(MacAddress mac, boolean hasIpv6) throws Exception {
143             macAddr = mac;
144             DhcpResults dhcpResults = runDhcp(macAddr.toByteArray());
145             ipv4Addr = (Inet4Address) dhcpResults.ipAddress.getAddress();
146             ipv4Gatway = (Inet4Address) dhcpResults.gateway;
147             routerMacAddr = getRouterMacAddressFromArp(ipv4Addr, macAddr,
148                     dhcpResults.serverAddress);
149             ipv6Addr = hasIpv6 ? runSlaac(macAddr, routerMacAddr) : null;
150         }
151     }
152 
153     /** Simulate dhcp client to obtain ipv4 address. */
runDhcp(byte[] clientMacAddr)154     public DhcpResults runDhcp(byte[] clientMacAddr)
155             throws Exception {
156         // We have to retransmit DHCP requests because IpServer declares itself to be ready before
157         // its DhcpServer is actually started. TODO: fix this race and remove this loop.
158         DhcpPacket offerPacket = null;
159         for (int i = 0; i < DHCP_DISCOVER_ATTEMPTS; i++) {
160             Log.d(TAG, "Sending DHCP discover");
161             sendDhcpDiscover(clientMacAddr);
162             offerPacket = getNextDhcpPacket();
163             if (offerPacket instanceof DhcpOfferPacket) break;
164         }
165         if (!(offerPacket instanceof DhcpOfferPacket)) {
166             throw new TimeoutException("No DHCPOFFER received on interface within timeout");
167         }
168 
169         sendDhcpRequest(offerPacket, clientMacAddr);
170         DhcpPacket ackPacket = getNextDhcpPacket();
171         if (!(ackPacket instanceof DhcpAckPacket)) {
172             throw new TimeoutException("No DHCPACK received on interface within timeout");
173         }
174 
175         return ackPacket.toDhcpResults();
176     }
177 
sendDhcpDiscover(byte[] macAddress)178     private void sendDhcpDiscover(byte[] macAddress) throws Exception {
179         ByteBuffer packet = DhcpPacket.buildDiscoverPacket(DhcpPacket.ENCAP_L2,
180                 new Random().nextInt() /* transactionId */, (short) 0 /* secs */,
181                 macAddress,  false /* unicast */, DHCP_REQUESTED_PARAMS,
182                 false /* rapid commit */,  DHCP_HOSTNAME);
183         mDownstreamReader.sendResponse(packet);
184     }
185 
sendDhcpRequest(DhcpPacket offerPacket, byte[] macAddress)186     private void sendDhcpRequest(DhcpPacket offerPacket, byte[] macAddress)
187             throws Exception {
188         DhcpResults results = offerPacket.toDhcpResults();
189         Inet4Address clientIp = (Inet4Address) results.ipAddress.getAddress();
190         Inet4Address serverIdentifier = results.serverAddress;
191         ByteBuffer packet = DhcpPacket.buildRequestPacket(DhcpPacket.ENCAP_L2,
192                 0 /* transactionId */, (short) 0 /* secs */, DhcpPacket.INADDR_ANY /* clientIp */,
193                 false /* broadcast */, macAddress, clientIp /* requestedIpAddress */,
194                 serverIdentifier, DHCP_REQUESTED_PARAMS, DHCP_HOSTNAME);
195         mDownstreamReader.sendResponse(packet);
196     }
197 
getNextDhcpPacket()198     private DhcpPacket getNextDhcpPacket() throws Exception {
199         final byte[] packet = getDownloadPacket((p) -> {
200             // Test whether this is DHCP packet.
201             try {
202                 DhcpPacket.decodeFullPacket(p, p.length, DhcpPacket.ENCAP_L2);
203             } catch (DhcpPacket.ParseException e) {
204                 // Not a DHCP packet.
205                 return false;
206             }
207 
208             return true;
209         });
210 
211         return packet == null ? null :
212                 DhcpPacket.decodeFullPacket(packet, packet.length, DhcpPacket.ENCAP_L2);
213     }
214 
215     @Nullable
parseArpPacket(final byte[] packet)216     private ArpPacket parseArpPacket(final byte[] packet) {
217         try {
218             return ArpPacket.parseArpPacket(packet, packet.length);
219         } catch (ArpPacket.ParseException e) {
220             return null;
221         }
222     }
223 
maybeReplyArp(byte[] packet)224     private void maybeReplyArp(byte[] packet) {
225         ByteBuffer buf = ByteBuffer.wrap(packet);
226 
227         final ArpPacket arpPacket = parseArpPacket(packet);
228         if (arpPacket == null || arpPacket.opCode != ARP_REQUEST) return;
229 
230         for (int i = 0; i < mTetheredDevices.size(); i++) {
231             TetheredDevice tethered = mTetheredDevices.valueAt(i);
232             if (!arpPacket.targetIp.equals(tethered.ipv4Addr)) continue;
233 
234             final ByteBuffer arpReply = ArpPacket.buildArpPacket(
235                     arpPacket.senderHwAddress.toByteArray() /* dst */,
236                     tethered.macAddr.toByteArray() /* srcMac */,
237                     arpPacket.senderIp.getAddress() /* target IP */,
238                     arpPacket.senderHwAddress.toByteArray() /* target HW address */,
239                     tethered.ipv4Addr.getAddress() /* sender IP */,
240                     (short) ARP_REPLY);
241             try {
242                 sendUploadPacket(arpReply);
243             } catch (Exception e) {
244                 fail("Failed to reply ARP for " + tethered.ipv4Addr);
245             }
246             return;
247         }
248     }
249 
getRouterMacAddressFromArp(final Inet4Address tetherIp, final MacAddress tetherMac, final Inet4Address routerIp)250     private MacAddress getRouterMacAddressFromArp(final Inet4Address tetherIp,
251             final MacAddress tetherMac, final Inet4Address routerIp) throws Exception {
252         final ByteBuffer arpProbe = ArpPacket.buildArpPacket(ETHER_BROADCAST /* dst */,
253                 tetherMac.toByteArray() /* srcMac */, routerIp.getAddress() /* target IP */,
254                 new byte[ETHER_ADDR_LEN] /* target HW address */,
255                 tetherIp.getAddress() /* sender IP */, (short) ARP_REQUEST);
256         sendUploadPacket(arpProbe);
257 
258         final byte[] packet = getDownloadPacket((p) -> {
259             final ArpPacket arpPacket = parseArpPacket(p);
260             if (arpPacket == null || arpPacket.opCode != ARP_REPLY) return false;
261             return arpPacket.targetIp.equals(tetherIp);
262         });
263 
264         if (packet != null) {
265             Log.d(TAG, "Get Mac address from ARP");
266             final ArpPacket arpReply = ArpPacket.parseArpPacket(packet, packet.length);
267             return arpReply.senderHwAddress;
268         }
269 
270         fail("Could not get ARP packet");
271         return null;
272     }
273 
getRaPrefixOptions(byte[] packet)274     private List<PrefixInformationOption> getRaPrefixOptions(byte[] packet) {
275         ByteBuffer buf = ByteBuffer.wrap(packet);
276         if (!isExpectedIcmpPacket(buf, true /* hasEth */, false /* isIpv4 */,
277                 ICMPV6_ROUTER_ADVERTISEMENT)) {
278             fail("Parsing RA packet fail");
279         }
280 
281         Struct.parse(RaHeader.class, buf);
282         final ArrayList<PrefixInformationOption> pioList = new ArrayList<>();
283         while (buf.position() < packet.length) {
284             final int currentPos = buf.position();
285             final int type = Byte.toUnsignedInt(buf.get());
286             final int length = Byte.toUnsignedInt(buf.get());
287             if (type == ICMPV6_ND_OPTION_PIO) {
288                 final ByteBuffer pioBuf = ByteBuffer.wrap(buf.array(), currentPos,
289                         Struct.getSize(PrefixInformationOption.class));
290                 final PrefixInformationOption pio =
291                         Struct.parse(PrefixInformationOption.class, pioBuf);
292                 pioList.add(pio);
293 
294                 // Move ByteBuffer position to the next option.
295                 buf.position(currentPos + Struct.getSize(PrefixInformationOption.class));
296             } else {
297                 buf.position(currentPos + (length * 8));
298             }
299         }
300         return pioList;
301     }
302 
runSlaac(MacAddress srcMac, MacAddress dstMac)303     private Inet6Address runSlaac(MacAddress srcMac, MacAddress dstMac) throws Exception {
304         sendRsPacket(srcMac, dstMac);
305 
306         final byte[] raPacket = verifyPacketNotNull("Receive RA fail", getDownloadPacket(p -> {
307             return isExpectedIcmpPacket(p, true /* hasEth */, false /* isIpv4 */,
308                     ICMPV6_ROUTER_ADVERTISEMENT);
309         }));
310 
311         final List<PrefixInformationOption> options = getRaPrefixOptions(raPacket);
312 
313         for (PrefixInformationOption pio : options) {
314             if (pio.validLifetime > 0) {
315                 final byte[] addressBytes = pio.prefix;
316                 // Random the last two bytes as suffix.
317                 // TODO: Currently do not implmement DAD in the test. Rely the gateway ipv6 address
318                 // genetrated by tethering module always has random the last byte.
319                 addressBytes[addressBytes.length - 1] = (byte) (new Random()).nextInt();
320                 addressBytes[addressBytes.length - 2] = (byte) (new Random()).nextInt();
321 
322                 return (Inet6Address) InetAddress.getByAddress(addressBytes);
323             }
324         }
325 
326         fail("No available ipv6 prefix");
327         return null;
328     }
329 
sendRsPacket(MacAddress srcMac, MacAddress dstMac)330     private void sendRsPacket(MacAddress srcMac, MacAddress dstMac) throws Exception {
331         Log.d(TAG, "Sending RS");
332         ByteBuffer slla = LlaOption.build((byte) ICMPV6_ND_OPTION_SLLA, srcMac);
333         ByteBuffer rs = Ipv6Utils.buildRsPacket(srcMac, dstMac, (Inet6Address) LINK_LOCAL,
334                 IPV6_ADDR_ALL_NODES_MULTICAST, slla);
335 
336         sendUploadPacket(rs);
337     }
338 
maybeReplyNa(byte[] packet)339     private void maybeReplyNa(byte[] packet) {
340         ByteBuffer buf = ByteBuffer.wrap(packet);
341         final EthernetHeader ethHdr = Struct.parse(EthernetHeader.class, buf);
342         if (ethHdr.etherType != ETHER_TYPE_IPV6) return;
343 
344         final Ipv6Header ipv6Hdr = Struct.parse(Ipv6Header.class, buf);
345         if (ipv6Hdr.nextHeader != (byte) IPPROTO_ICMPV6) return;
346 
347         final Icmpv6Header icmpv6Hdr = Struct.parse(Icmpv6Header.class, buf);
348         if (icmpv6Hdr.type != (short) ICMPV6_NEIGHBOR_SOLICITATION) return;
349 
350         final NsHeader nsHdr = Struct.parse(NsHeader.class, buf);
351         for (int i = 0; i < mTetheredDevices.size(); i++) {
352             TetheredDevice tethered = mTetheredDevices.valueAt(i);
353             if (!nsHdr.target.equals(tethered.ipv6Addr)) continue;
354 
355             final ByteBuffer tlla = LlaOption.build((byte) ICMPV6_ND_OPTION_TLLA, tethered.macAddr);
356             int flags = NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED
357                     | NEIGHBOR_ADVERTISEMENT_FLAG_OVERRIDE;
358             ByteBuffer ns = Ipv6Utils.buildNaPacket(tethered.macAddr, tethered.routerMacAddr,
359                     nsHdr.target, ipv6Hdr.srcIp, flags, nsHdr.target, tlla);
360             try {
361                 sendUploadPacket(ns);
362             } catch (Exception e) {
363                 fail("Failed to reply NA for " + tethered.ipv6Addr);
364             }
365 
366             return;
367         }
368     }
369 
isExpectedIcmpPacket(byte[] packet, boolean hasEth, boolean isIpv4, int type)370     public static boolean isExpectedIcmpPacket(byte[] packet, boolean hasEth, boolean isIpv4,
371             int type) {
372         final ByteBuffer buf = ByteBuffer.wrap(packet);
373         return isExpectedIcmpPacket(buf, hasEth, isIpv4, type);
374     }
375 
isExpectedIcmpPacket(ByteBuffer buf, boolean hasEth, boolean isIpv4, int type)376     private static boolean isExpectedIcmpPacket(ByteBuffer buf, boolean hasEth, boolean isIpv4,
377             int type) {
378         try {
379             if (hasEth && !hasExpectedEtherHeader(buf, isIpv4)) return false;
380 
381             final int ipProto = isIpv4 ? IPPROTO_ICMP : IPPROTO_ICMPV6;
382             if (!hasExpectedIpHeader(buf, isIpv4, ipProto)) return false;
383 
384             if (isIpv4) {
385                 return Struct.parse(Icmpv4Header.class, buf).type == (short) type;
386             } else {
387                 return Struct.parse(Icmpv6Header.class, buf).type == (short) type;
388             }
389         } catch (Exception e) {
390             // Parsing packet fail means it is not icmp packet.
391         }
392 
393         return false;
394     }
395 
hasExpectedEtherHeader(@onNull final ByteBuffer buf, boolean isIpv4)396     private static boolean hasExpectedEtherHeader(@NonNull final ByteBuffer buf, boolean isIpv4)
397             throws Exception {
398         final int expected = isIpv4 ? ETHER_TYPE_IPV4 : ETHER_TYPE_IPV6;
399 
400         return Struct.parse(EthernetHeader.class, buf).etherType == expected;
401     }
402 
hasExpectedIpHeader(@onNull final ByteBuffer buf, boolean isIpv4, int ipProto)403     private static boolean hasExpectedIpHeader(@NonNull final ByteBuffer buf, boolean isIpv4,
404             int ipProto) throws Exception {
405         if (isIpv4) {
406             return Struct.parse(Ipv4Header.class, buf).protocol == (byte) ipProto;
407         } else {
408             return Struct.parse(Ipv6Header.class, buf).nextHeader == (byte) ipProto;
409         }
410     }
411 
isExpectedUdpPacket(@onNull final byte[] rawPacket, boolean hasEth, boolean isIpv4, Predicate<ByteBuffer> payloadVerifier)412     private static boolean isExpectedUdpPacket(@NonNull final byte[] rawPacket, boolean hasEth,
413             boolean isIpv4, Predicate<ByteBuffer> payloadVerifier) {
414         final ByteBuffer buf = ByteBuffer.wrap(rawPacket);
415         try {
416             if (hasEth && !hasExpectedEtherHeader(buf, isIpv4)) return false;
417 
418             if (!hasExpectedIpHeader(buf, isIpv4, IPPROTO_UDP)) return false;
419 
420             if (Struct.parse(UdpHeader.class, buf) == null) return false;
421 
422             if (!payloadVerifier.test(buf)) return false;
423         } catch (Exception e) {
424             // Parsing packet fail means it is not udp packet.
425             return false;
426         }
427         return true;
428     }
429 
430     // Returns remaining bytes in the ByteBuffer in a new byte array of the right size. The
431     // ByteBuffer will be empty upon return. Used to avoid lint warning.
432     // See https://errorprone.info/bugpattern/ByteBufferBackingArray
getRemaining(final ByteBuffer buf)433     private static byte[] getRemaining(final ByteBuffer buf) {
434         final byte[] bytes = new byte[buf.remaining()];
435         buf.get(bytes);
436         Log.d(TAG, "Get remaining bytes: " + dumpHexString(bytes));
437         return bytes;
438     }
439 
440     // |expectedPayload| is copied as read-only because the caller may reuse it.
isExpectedUdpPacket(@onNull final byte[] rawPacket, boolean hasEth, boolean isIpv4, @NonNull final ByteBuffer expectedPayload)441     public static boolean isExpectedUdpPacket(@NonNull final byte[] rawPacket, boolean hasEth,
442             boolean isIpv4, @NonNull final ByteBuffer expectedPayload) {
443         return isExpectedUdpPacket(rawPacket, hasEth, isIpv4, p -> {
444             if (p.remaining() != expectedPayload.limit()) return false;
445 
446             return Arrays.equals(getRemaining(p), getRemaining(
447                     expectedPayload.asReadOnlyBuffer()));
448         });
449     }
450 
451     // |expectedPayload| is copied as read-only because the caller may reuse it.
452     // See hasExpectedDnsMessage.
isExpectedUdpDnsPacket(@onNull final byte[] rawPacket, boolean hasEth, boolean isIpv4, @NonNull final ByteBuffer expectedPayload)453     public static boolean isExpectedUdpDnsPacket(@NonNull final byte[] rawPacket, boolean hasEth,
454             boolean isIpv4, @NonNull final ByteBuffer expectedPayload) {
455         return isExpectedUdpPacket(rawPacket, hasEth, isIpv4, p -> {
456             return hasExpectedDnsMessage(p, expectedPayload);
457         });
458     }
459 
460     public static class TestDnsPacket extends DnsPacket {
461         TestDnsPacket(byte[] data) throws DnsPacket.ParseException {
462             super(data);
463         }
464 
465         @Nullable
466         public static TestDnsPacket getTestDnsPacket(final ByteBuffer buf) {
467             try {
468                 // The ByteBuffer will be empty upon return.
469                 return new TestDnsPacket(getRemaining(buf));
470             } catch (DnsPacket.ParseException e) {
471                 return null;
472             }
473         }
474 
475         public DnsHeader getHeader() {
476             return mHeader;
477         }
478 
479         public List<DnsRecord> getRecordList(int secType) {
480             return mRecords[secType];
481         }
482 
483         public int getANCount() {
484             return mHeader.getRecordCount(ANSECTION);
485         }
486 
487         public int getQDCount() {
488             return mHeader.getRecordCount(QDSECTION);
489         }
490 
491         public int getNSCount() {
492             return mHeader.getRecordCount(NSSECTION);
493         }
494 
495         public int getARCount() {
496             return mHeader.getRecordCount(ARSECTION);
497         }
498 
499         private boolean isRecordsEquals(int type, @NonNull final TestDnsPacket other) {
500             List<DnsRecord> records = getRecordList(type);
501             List<DnsRecord> otherRecords = other.getRecordList(type);
502 
503             if (records.size() != otherRecords.size()) return false;
504 
505             // Expect that two compared resource records are in the same order. For current tests
506             // in EthernetTetheringTest, it is okay because dnsmasq doesn't reorder the forwarded
507             // resource records.
508             // TODO: consider allowing that compare records out of order.
509             for (int i = 0; i < records.size(); i++) {
510                 // TODO: use DnsRecord.equals once aosp/1387135 is merged.
511                 if (!TextUtils.equals(records.get(i).dName, otherRecords.get(i).dName)
512                         || records.get(i).nsType != otherRecords.get(i).nsType
513                         || records.get(i).nsClass != otherRecords.get(i).nsClass
514                         || records.get(i).ttl != otherRecords.get(i).ttl
515                         || !Arrays.equals(records.get(i).getRR(), otherRecords.get(i).getRR())) {
516                     return false;
517                 }
518             }
519             return true;
520         }
521 
522         public boolean isQDRecordsEquals(@NonNull final TestDnsPacket other) {
523             return isRecordsEquals(QDSECTION, other);
524         }
525 
526         public boolean isANRecordsEquals(@NonNull final TestDnsPacket other) {
527             return isRecordsEquals(ANSECTION, other);
528         }
529     }
530 
531     // The ByteBuffer |actual| will be empty upon return. The ByteBuffer |excepted| will be copied
532     // as read-only because the caller may reuse it.
533     private static boolean hasExpectedDnsMessage(@NonNull final ByteBuffer actual,
534             @NonNull final ByteBuffer excepted) {
535         // Forwarded DNS message is extracted from remaining received packet buffer which has
536         // already parsed ethernet header, if any, IP header and UDP header.
537         final TestDnsPacket forwardedDns = TestDnsPacket.getTestDnsPacket(actual);
538         if (forwardedDns == null) return false;
539 
540         // Original DNS message is the payload of the sending test UDP packet. It is used to check
541         // that the forwarded DNS query and reply have corresponding contents.
542         final TestDnsPacket originalDns = TestDnsPacket.getTestDnsPacket(
543                 excepted.asReadOnlyBuffer());
544         assertNotNull(originalDns);
545 
546         // Compare original DNS message which is sent to dnsmasq and forwarded DNS message which
547         // is forwarded by dnsmasq. The original message and forwarded message may be not identical
548         // because dnsmasq may change the header flags or even recreate the DNS query message and
549         // so on. We only simple check on forwarded packet and monitor if test will be broken by
550         // vendor dnsmasq customization. See forward_query() in external/dnsmasq/src/forward.c.
551         //
552         // DNS message format. See rfc1035 section 4.1.
553         // +---------------------+
554         // |        Header       |
555         // +---------------------+
556         // |       Question      | the question for the name server
557         // +---------------------+
558         // |        Answer       | RRs answering the question
559         // +---------------------+
560         // |      Authority      | RRs pointing toward an authority
561         // +---------------------+
562         // |      Additional     | RRs holding additional information
563         // +---------------------+
564 
565         // [1] Header section. See rfc1035 section 4.1.1.
566         // Verify QR flag bit, QDCOUNT, ANCOUNT, NSCOUNT, ARCOUNT.
567         if (originalDns.getHeader().isResponse() != forwardedDns.getHeader().isResponse()) {
568             return false;
569         }
570         if (originalDns.getQDCount() != forwardedDns.getQDCount()) return false;
571         if (originalDns.getANCount() != forwardedDns.getANCount()) return false;
572         if (originalDns.getNSCount() != forwardedDns.getNSCount()) return false;
573         if (originalDns.getARCount() != forwardedDns.getARCount()) return false;
574 
575         // [2] Question section. See rfc1035 section 4.1.2.
576         // Question section has at least one entry either DNS query or DNS reply.
577         if (forwardedDns.getRecordList(QDSECTION).isEmpty()) return false;
578         // Expect that original and forwarded message have the same question records (usually 1).
579         if (!originalDns.isQDRecordsEquals(forwardedDns)) return false;
580 
581         // [3] Answer section. See rfc1035 section 4.1.3.
582         if (forwardedDns.getHeader().isResponse()) {
583             // DNS reply has at least have one answer in our tests.
584             // See EthernetTetheringTest#testTetherUdpV4Dns.
585             if (forwardedDns.getRecordList(ANSECTION).isEmpty()) return false;
586             // Expect that original and forwarded message have the same answer records.
587             if (!originalDns.isANRecordsEquals(forwardedDns)) return false;
588         }
589 
590         // Ignore checking {Authority, Additional} sections because they are not tested
591         // in EthernetTetheringTest.
592         return true;
593     }
594 
595 
596     private static boolean isTcpSynPacket(@NonNull final TcpHeader tcpHeader) {
597         return (tcpHeader.dataOffsetAndControlBits & TCPHDR_SYN) != 0;
598     }
599 
600     public static boolean isExpectedTcpPacket(@NonNull final byte[] rawPacket, boolean hasEth,
601             boolean isIpv4, int seq, @NonNull final ByteBuffer payload) {
602         final ByteBuffer buf = ByteBuffer.wrap(rawPacket);
603         try {
604             if (hasEth && !hasExpectedEtherHeader(buf, isIpv4)) return false;
605 
606             if (!hasExpectedIpHeader(buf, isIpv4, IPPROTO_TCP)) return false;
607 
608             final TcpHeader tcpHeader = Struct.parse(TcpHeader.class, buf);
609             if (tcpHeader.seq != seq) return false;
610 
611             // Don't try to parse the payload if it is a TCP SYN segment because additional TCP
612             // option MSS may be added in the SYN segment. Currently, TetherController uses
613             // iptables to limit downstream MSS for IPv4. The additional TCP options will be
614             // misunderstood as payload because parsing TCP options are not supported by class
615             // TcpHeader for now. See TetherController::setupIptablesHooks.
616             // TODO: remove once TcpHeader supports parsing TCP options.
617             if (isTcpSynPacket(tcpHeader)) {
618                 Log.d(TAG, "Found SYN segment. Ignore parsing the remaining part of packet.");
619                 return true;
620             }
621 
622             if (payload.limit() != buf.remaining()) return false;
623             return Arrays.equals(getRemaining(buf), getRemaining(payload.asReadOnlyBuffer()));
624         } catch (Exception e) {
625             // Parsing packet fail means it is not tcp packet.
626         }
627 
628         return false;
629     }
630 
631     private void sendUploadPacket(ByteBuffer packet) throws Exception {
632         mDownstreamReader.sendResponse(packet);
633     }
634 
635     private void sendDownloadPacket(ByteBuffer packet) throws Exception {
636         assertNotNull("Can't deal with upstream interface in local only mode", mUpstreamReader);
637 
638         mUpstreamReader.sendResponse(packet);
639     }
640 
641     private byte[] getDownloadPacket(Predicate<byte[]> filter) {
642         byte[] packet;
643         while ((packet = mDownstreamReader.poll(PACKET_READ_TIMEOUT_MS)) != null) {
644             if (filter.test(packet)) return packet;
645 
646             maybeReplyArp(packet);
647             maybeReplyNa(packet);
648         }
649 
650         return null;
651     }
652 
653     private byte[] getUploadPacket(Predicate<byte[]> filter) {
654         assertNotNull("Can't deal with upstream interface in local only mode", mUpstreamReader);
655 
656         return mUpstreamReader.poll(PACKET_READ_TIMEOUT_MS, filter);
657     }
658 
659     private @NonNull byte[] verifyPacketNotNull(String message, @Nullable byte[] packet) {
660         assertNotNull(message, packet);
661 
662         return packet;
663     }
664 
665     public byte[] testUpload(final ByteBuffer packet, final Predicate<byte[]> filter)
666             throws Exception {
667         sendUploadPacket(packet);
668 
669         return getUploadPacket(filter);
670     }
671 
672     public byte[] verifyUpload(final ByteBuffer packet, final Predicate<byte[]> filter)
673             throws Exception {
674         return verifyPacketNotNull("Upload fail", testUpload(packet, filter));
675     }
676 
677     public byte[] verifyDownload(final ByteBuffer packet, final Predicate<byte[]> filter)
678             throws Exception {
679         sendDownloadPacket(packet);
680 
681         return verifyPacketNotNull("Download fail", getDownloadPacket(filter));
682     }
683 }
684