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.DnsResolver.CLASS_IN; 20 import static android.net.DnsResolver.TYPE_AAAA; 21 import static android.net.InetAddresses.parseNumericAddress; 22 import static android.system.OsConstants.ICMP_ECHO; 23 import static android.system.OsConstants.ICMP_ECHOREPLY; 24 import static android.system.OsConstants.IPPROTO_ICMP; 25 import static android.system.OsConstants.IPPROTO_ICMPV6; 26 import static android.system.OsConstants.IPPROTO_IP; 27 import static android.system.OsConstants.IPPROTO_IPV6; 28 import static android.system.OsConstants.IPPROTO_TCP; 29 import static android.system.OsConstants.IPPROTO_UDP; 30 import static com.android.net.module.util.DnsPacket.ANSECTION; 31 import static com.android.net.module.util.DnsPacket.DnsHeader; 32 import static com.android.net.module.util.DnsPacket.DnsRecord; 33 import static com.android.net.module.util.DnsPacket.QDSECTION; 34 import static com.android.net.module.util.HexDump.dumpHexString; 35 import static com.android.net.module.util.IpUtils.icmpChecksum; 36 import static com.android.net.module.util.IpUtils.ipChecksum; 37 import static com.android.net.module.util.NetworkStackConstants.ARP_REPLY; 38 import static com.android.net.module.util.NetworkStackConstants.ARP_REQUEST; 39 import static com.android.net.module.util.NetworkStackConstants.ETHER_ADDR_LEN; 40 import static com.android.net.module.util.NetworkStackConstants.ETHER_BROADCAST; 41 import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV4; 42 import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV6; 43 import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_PIO; 44 import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_SLLA; 45 import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_TLLA; 46 import static com.android.net.module.util.NetworkStackConstants.ICMPV6_NEIGHBOR_SOLICITATION; 47 import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT; 48 import static com.android.net.module.util.NetworkStackConstants.ICMP_CHECKSUM_OFFSET; 49 import static com.android.net.module.util.NetworkStackConstants.IPV4_CHECKSUM_OFFSET; 50 import static com.android.net.module.util.NetworkStackConstants.IPV4_HEADER_MIN_LEN; 51 import static com.android.net.module.util.NetworkStackConstants.IPV4_LENGTH_OFFSET; 52 import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ALL_NODES_MULTICAST; 53 import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_OVERRIDE; 54 import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED; 55 import static com.android.net.module.util.NetworkStackConstants.TCPHDR_SYN; 56 import static org.junit.Assert.assertNotNull; 57 import static org.junit.Assert.fail; 58 59 import android.net.dhcp.DhcpAckPacket; 60 import android.net.dhcp.DhcpOfferPacket; 61 import android.net.dhcp.DhcpPacket; 62 import android.text.TextUtils; 63 import android.util.ArrayMap; 64 import android.util.Log; 65 66 import androidx.annotation.NonNull; 67 import androidx.annotation.Nullable; 68 69 import com.android.net.module.util.DnsPacket; 70 import com.android.net.module.util.Ipv6Utils; 71 import com.android.net.module.util.PacketBuilder; 72 import com.android.net.module.util.Struct; 73 import com.android.net.module.util.arp.ArpPacket; 74 import com.android.net.module.util.structs.EthernetHeader; 75 import com.android.net.module.util.structs.Icmpv4Header; 76 import com.android.net.module.util.structs.Icmpv6Header; 77 import com.android.net.module.util.structs.Ipv4Header; 78 import com.android.net.module.util.structs.Ipv6Header; 79 import com.android.net.module.util.structs.LlaOption; 80 import com.android.net.module.util.structs.NsHeader; 81 import com.android.net.module.util.structs.PrefixInformationOption; 82 import com.android.net.module.util.structs.RaHeader; 83 import com.android.net.module.util.structs.TcpHeader; 84 import com.android.net.module.util.structs.UdpHeader; 85 import com.android.testutils.TapPacketReader; 86 87 import java.net.Inet4Address; 88 import java.net.Inet6Address; 89 import java.net.InetAddress; 90 import java.nio.ByteBuffer; 91 import java.util.ArrayList; 92 import java.util.Arrays; 93 import java.util.List; 94 import java.util.Random; 95 import java.util.concurrent.TimeoutException; 96 import java.util.function.Predicate; 97 98 /** 99 * A class simulate tethered client. When caller create TetheringTester, it would connect to 100 * tethering module that do the dhcp and slaac to obtain ipv4 and ipv6 address. Then caller can 101 * send/receive packets by this class. 102 */ 103 public final class TetheringTester { 104 private static final String TAG = TetheringTester.class.getSimpleName(); 105 private static final int PACKET_READ_TIMEOUT_MS = 500; 106 private static final int DHCP_DISCOVER_ATTEMPTS = 10; 107 private static final int READ_RA_ATTEMPTS = 10; 108 private static final byte[] DHCP_REQUESTED_PARAMS = new byte[] { 109 DhcpPacket.DHCP_SUBNET_MASK, 110 DhcpPacket.DHCP_ROUTER, 111 DhcpPacket.DHCP_DNS_SERVER, 112 DhcpPacket.DHCP_LEASE_TIME, 113 }; 114 private static final InetAddress LINK_LOCAL = parseNumericAddress("fe80::1"); 115 // IPv4 header definition. 116 protected static final short ID = 27149; 117 protected static final short FLAGS_AND_FRAGMENT_OFFSET = (short) 0x4000; // flags=DF, offset=0 118 protected static final byte TIME_TO_LIVE = (byte) 0x40; 119 protected static final byte TYPE_OF_SERVICE = 0; 120 121 // IPv6 header definition. 122 private static final short HOP_LIMIT = 0x40; 123 // version=6, traffic class=0x0, flowlabel=0x0; 124 private static final int VERSION_TRAFFICCLASS_FLOWLABEL = 0x60000000; 125 126 // UDP and TCP header definition. 127 private static final short WINDOW = (short) 0x2000; 128 private static final short URGENT_POINTER = 0; 129 130 // ICMP definition. 131 private static final short ICMPECHO_CODE = 0x0; 132 133 // Prefix64 discovery definition. See RFC 7050 section 8. 134 // Note that the AAAA response Pref64::WKAs consisting of Pref64::/n and WKA. 135 // Use 64:ff9b::/96 as Pref64::/n and WKA 192.0.0.17{0|1} here. 136 // 137 // Host DNS64 server 138 // | | 139 // | "AAAA" query for "ipv4only.arpa." | 140 // |----------------------------------------------->| 141 // | | 142 // | "AAAA" response with: | 143 // | "64:ff9b::192.0.0.170" | 144 // |<-----------------------------------------------| 145 // 146 private static final String PREF64_IPV4ONLY_HOSTNAME = "ipv4only.arpa"; 147 private static final InetAddress PREF64_IPV4ONLY_ADDR = parseNumericAddress( 148 "64:ff9b::192.0.0.170"); 149 150 // DNS header definition. 151 private static final short FLAG = (short) 0x8100; // qr, ra 152 private static final short TTL = (short) 0; 153 154 public static final String DHCP_HOSTNAME = "testhostname"; 155 156 private final ArrayMap<MacAddress, TetheredDevice> mTetheredDevices; 157 private final TapPacketReader mDownstreamReader; 158 private final TapPacketReader mUpstreamReader; 159 TetheringTester(TapPacketReader downstream)160 public TetheringTester(TapPacketReader downstream) { 161 this(downstream, null); 162 } 163 TetheringTester(TapPacketReader downstream, TapPacketReader upstream)164 public TetheringTester(TapPacketReader downstream, TapPacketReader upstream) { 165 if (downstream == null) fail("Downstream reader could not be NULL"); 166 167 mDownstreamReader = downstream; 168 mUpstreamReader = upstream; 169 mTetheredDevices = new ArrayMap<>(); 170 } 171 createTetheredDevice(MacAddress macAddr, boolean hasIpv6)172 public TetheredDevice createTetheredDevice(MacAddress macAddr, boolean hasIpv6) 173 throws Exception { 174 if (mTetheredDevices.get(macAddr) != null) { 175 fail("Tethered device already created"); 176 } 177 178 TetheredDevice tethered = new TetheredDevice(macAddr, hasIpv6); 179 mTetheredDevices.put(macAddr, tethered); 180 181 return tethered; 182 } 183 184 public class TetheredDevice { 185 public final MacAddress macAddr; 186 public final MacAddress routerMacAddr; 187 public final Inet4Address ipv4Addr; 188 public final Inet4Address ipv4Gatway; 189 public final Inet6Address ipv6Addr; 190 TetheredDevice(MacAddress mac, boolean hasIpv6)191 private TetheredDevice(MacAddress mac, boolean hasIpv6) throws Exception { 192 macAddr = mac; 193 DhcpResults dhcpResults = runDhcp(macAddr.toByteArray()); 194 ipv4Addr = (Inet4Address) dhcpResults.ipAddress.getAddress(); 195 ipv4Gatway = (Inet4Address) dhcpResults.gateway; 196 routerMacAddr = getRouterMacAddressFromArp(ipv4Addr, macAddr, 197 dhcpResults.serverAddress); 198 ipv6Addr = hasIpv6 ? runSlaac(macAddr, routerMacAddr) : null; 199 } 200 } 201 202 /** Simulate dhcp client to obtain ipv4 address. */ runDhcp(byte[] clientMacAddr)203 public DhcpResults runDhcp(byte[] clientMacAddr) 204 throws Exception { 205 // We have to retransmit DHCP requests because IpServer declares itself to be ready before 206 // its DhcpServer is actually started. TODO: fix this race and remove this loop. 207 DhcpPacket offerPacket = null; 208 for (int i = 0; i < DHCP_DISCOVER_ATTEMPTS; i++) { 209 Log.d(TAG, "Sending DHCP discover"); 210 sendDhcpDiscover(clientMacAddr); 211 offerPacket = getNextDhcpPacket(); 212 if (offerPacket instanceof DhcpOfferPacket) break; 213 } 214 if (!(offerPacket instanceof DhcpOfferPacket)) { 215 throw new TimeoutException("No DHCPOFFER received on interface within timeout"); 216 } 217 218 sendDhcpRequest(offerPacket, clientMacAddr); 219 DhcpPacket ackPacket = getNextDhcpPacket(); 220 if (!(ackPacket instanceof DhcpAckPacket)) { 221 throw new TimeoutException("No DHCPACK received on interface within timeout"); 222 } 223 224 return ackPacket.toDhcpResults(); 225 } 226 sendDhcpDiscover(byte[] macAddress)227 private void sendDhcpDiscover(byte[] macAddress) throws Exception { 228 ByteBuffer packet = DhcpPacket.buildDiscoverPacket(DhcpPacket.ENCAP_L2, 229 new Random().nextInt() /* transactionId */, (short) 0 /* secs */, 230 macAddress, false /* unicast */, DHCP_REQUESTED_PARAMS, 231 false /* rapid commit */, DHCP_HOSTNAME); 232 mDownstreamReader.sendResponse(packet); 233 } 234 sendDhcpRequest(DhcpPacket offerPacket, byte[] macAddress)235 private void sendDhcpRequest(DhcpPacket offerPacket, byte[] macAddress) 236 throws Exception { 237 DhcpResults results = offerPacket.toDhcpResults(); 238 Inet4Address clientIp = (Inet4Address) results.ipAddress.getAddress(); 239 Inet4Address serverIdentifier = results.serverAddress; 240 ByteBuffer packet = DhcpPacket.buildRequestPacket(DhcpPacket.ENCAP_L2, 241 0 /* transactionId */, (short) 0 /* secs */, DhcpPacket.INADDR_ANY /* clientIp */, 242 false /* broadcast */, macAddress, clientIp /* requestedIpAddress */, 243 serverIdentifier, DHCP_REQUESTED_PARAMS, DHCP_HOSTNAME); 244 mDownstreamReader.sendResponse(packet); 245 } 246 getNextDhcpPacket()247 private DhcpPacket getNextDhcpPacket() throws Exception { 248 final byte[] packet = getDownloadPacket((p) -> { 249 // Test whether this is DHCP packet. 250 try { 251 DhcpPacket.decodeFullPacket(p, p.length, DhcpPacket.ENCAP_L2); 252 } catch (DhcpPacket.ParseException e) { 253 // Not a DHCP packet. 254 return false; 255 } 256 257 return true; 258 }); 259 260 return packet == null ? null : 261 DhcpPacket.decodeFullPacket(packet, packet.length, DhcpPacket.ENCAP_L2); 262 } 263 264 @Nullable parseArpPacket(final byte[] packet)265 private ArpPacket parseArpPacket(final byte[] packet) { 266 try { 267 return ArpPacket.parseArpPacket(packet, packet.length); 268 } catch (ArpPacket.ParseException e) { 269 return null; 270 } 271 } 272 maybeReplyArp(byte[] packet)273 private void maybeReplyArp(byte[] packet) { 274 ByteBuffer buf = ByteBuffer.wrap(packet); 275 276 final ArpPacket arpPacket = parseArpPacket(packet); 277 if (arpPacket == null || arpPacket.opCode != ARP_REQUEST) return; 278 279 for (int i = 0; i < mTetheredDevices.size(); i++) { 280 TetheredDevice tethered = mTetheredDevices.valueAt(i); 281 if (!arpPacket.targetIp.equals(tethered.ipv4Addr)) continue; 282 283 final ByteBuffer arpReply = ArpPacket.buildArpPacket( 284 arpPacket.senderHwAddress.toByteArray() /* dst */, 285 tethered.macAddr.toByteArray() /* srcMac */, 286 arpPacket.senderIp.getAddress() /* target IP */, 287 arpPacket.senderHwAddress.toByteArray() /* target HW address */, 288 tethered.ipv4Addr.getAddress() /* sender IP */, 289 (short) ARP_REPLY); 290 try { 291 sendUploadPacket(arpReply); 292 } catch (Exception e) { 293 fail("Failed to reply ARP for " + tethered.ipv4Addr); 294 } 295 return; 296 } 297 } 298 getRouterMacAddressFromArp(final Inet4Address tetherIp, final MacAddress tetherMac, final Inet4Address routerIp)299 private MacAddress getRouterMacAddressFromArp(final Inet4Address tetherIp, 300 final MacAddress tetherMac, final Inet4Address routerIp) throws Exception { 301 final ByteBuffer arpProbe = ArpPacket.buildArpPacket(ETHER_BROADCAST /* dst */, 302 tetherMac.toByteArray() /* srcMac */, routerIp.getAddress() /* target IP */, 303 new byte[ETHER_ADDR_LEN] /* target HW address */, 304 tetherIp.getAddress() /* sender IP */, (short) ARP_REQUEST); 305 sendUploadPacket(arpProbe); 306 307 final byte[] packet = getDownloadPacket((p) -> { 308 final ArpPacket arpPacket = parseArpPacket(p); 309 if (arpPacket == null || arpPacket.opCode != ARP_REPLY) return false; 310 return arpPacket.targetIp.equals(tetherIp); 311 }); 312 313 if (packet != null) { 314 Log.d(TAG, "Get Mac address from ARP"); 315 final ArpPacket arpReply = ArpPacket.parseArpPacket(packet, packet.length); 316 return arpReply.senderHwAddress; 317 } 318 319 fail("Could not get ARP packet"); 320 return null; 321 } 322 getRaPrefixOptions(byte[] packet)323 private List<PrefixInformationOption> getRaPrefixOptions(byte[] packet) { 324 ByteBuffer buf = ByteBuffer.wrap(packet); 325 if (!isExpectedIcmpPacket(buf, true /* hasEth */, false /* isIpv4 */, 326 ICMPV6_ROUTER_ADVERTISEMENT)) { 327 fail("Parsing RA packet fail"); 328 } 329 330 Struct.parse(RaHeader.class, buf); 331 final ArrayList<PrefixInformationOption> pioList = new ArrayList<>(); 332 while (buf.position() < packet.length) { 333 final int currentPos = buf.position(); 334 final int type = Byte.toUnsignedInt(buf.get()); 335 final int length = Byte.toUnsignedInt(buf.get()); 336 if (type == ICMPV6_ND_OPTION_PIO) { 337 final ByteBuffer pioBuf = ByteBuffer.wrap(buf.array(), currentPos, 338 Struct.getSize(PrefixInformationOption.class)); 339 final PrefixInformationOption pio = 340 Struct.parse(PrefixInformationOption.class, pioBuf); 341 pioList.add(pio); 342 343 // Move ByteBuffer position to the next option. 344 buf.position(currentPos + Struct.getSize(PrefixInformationOption.class)); 345 } else { 346 buf.position(currentPos + (length * 8)); 347 } 348 } 349 return pioList; 350 } 351 runSlaac(MacAddress srcMac, MacAddress dstMac)352 private Inet6Address runSlaac(MacAddress srcMac, MacAddress dstMac) throws Exception { 353 sendRsPacket(srcMac, dstMac); 354 355 final byte[] raPacket = verifyPacketNotNull("Receive RA fail", getDownloadPacket(p -> { 356 return isExpectedIcmpPacket(p, true /* hasEth */, false /* isIpv4 */, 357 ICMPV6_ROUTER_ADVERTISEMENT); 358 })); 359 360 final List<PrefixInformationOption> options = getRaPrefixOptions(raPacket); 361 362 for (PrefixInformationOption pio : options) { 363 if (pio.validLifetime > 0) { 364 final byte[] addressBytes = pio.prefix; 365 // Random the last two bytes as suffix. 366 // TODO: Currently do not implmement DAD in the test. Rely the gateway ipv6 address 367 // genetrated by tethering module always has random the last byte. 368 addressBytes[addressBytes.length - 1] = (byte) (new Random()).nextInt(); 369 addressBytes[addressBytes.length - 2] = (byte) (new Random()).nextInt(); 370 371 return (Inet6Address) InetAddress.getByAddress(addressBytes); 372 } 373 } 374 375 fail("No available ipv6 prefix"); 376 return null; 377 } 378 sendRsPacket(MacAddress srcMac, MacAddress dstMac)379 private void sendRsPacket(MacAddress srcMac, MacAddress dstMac) throws Exception { 380 Log.d(TAG, "Sending RS"); 381 ByteBuffer slla = LlaOption.build((byte) ICMPV6_ND_OPTION_SLLA, srcMac); 382 ByteBuffer rs = Ipv6Utils.buildRsPacket(srcMac, dstMac, (Inet6Address) LINK_LOCAL, 383 IPV6_ADDR_ALL_NODES_MULTICAST, slla); 384 385 sendUploadPacket(rs); 386 } 387 maybeReplyNa(byte[] packet)388 private void maybeReplyNa(byte[] packet) { 389 ByteBuffer buf = ByteBuffer.wrap(packet); 390 final EthernetHeader ethHdr = Struct.parse(EthernetHeader.class, buf); 391 if (ethHdr.etherType != ETHER_TYPE_IPV6) return; 392 393 final Ipv6Header ipv6Hdr = Struct.parse(Ipv6Header.class, buf); 394 if (ipv6Hdr.nextHeader != (byte) IPPROTO_ICMPV6) return; 395 396 final Icmpv6Header icmpv6Hdr = Struct.parse(Icmpv6Header.class, buf); 397 if (icmpv6Hdr.type != (short) ICMPV6_NEIGHBOR_SOLICITATION) return; 398 399 final NsHeader nsHdr = Struct.parse(NsHeader.class, buf); 400 for (int i = 0; i < mTetheredDevices.size(); i++) { 401 TetheredDevice tethered = mTetheredDevices.valueAt(i); 402 if (!nsHdr.target.equals(tethered.ipv6Addr)) continue; 403 404 final ByteBuffer tlla = LlaOption.build((byte) ICMPV6_ND_OPTION_TLLA, tethered.macAddr); 405 int flags = NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED 406 | NEIGHBOR_ADVERTISEMENT_FLAG_OVERRIDE; 407 ByteBuffer ns = Ipv6Utils.buildNaPacket(tethered.macAddr, tethered.routerMacAddr, 408 nsHdr.target, ipv6Hdr.srcIp, flags, nsHdr.target, tlla); 409 try { 410 sendUploadPacket(ns); 411 } catch (Exception e) { 412 fail("Failed to reply NA for " + tethered.ipv6Addr); 413 } 414 415 return; 416 } 417 } 418 isExpectedIcmpPacket(byte[] packet, boolean hasEth, boolean isIpv4, int type)419 public static boolean isExpectedIcmpPacket(byte[] packet, boolean hasEth, boolean isIpv4, 420 int type) { 421 final ByteBuffer buf = ByteBuffer.wrap(packet); 422 return isExpectedIcmpPacket(buf, hasEth, isIpv4, type); 423 } 424 isExpectedIcmpPacket(ByteBuffer buf, boolean hasEth, boolean isIpv4, int type)425 private static boolean isExpectedIcmpPacket(ByteBuffer buf, boolean hasEth, boolean isIpv4, 426 int type) { 427 try { 428 if (hasEth && !hasExpectedEtherHeader(buf, isIpv4)) return false; 429 430 final int ipProto = isIpv4 ? IPPROTO_ICMP : IPPROTO_ICMPV6; 431 if (!hasExpectedIpHeader(buf, isIpv4, ipProto)) return false; 432 433 if (isIpv4) { 434 return Struct.parse(Icmpv4Header.class, buf).type == (short) type; 435 } else { 436 return Struct.parse(Icmpv6Header.class, buf).type == (short) type; 437 } 438 } catch (Exception e) { 439 // Parsing packet fail means it is not icmp packet. 440 } 441 442 return false; 443 } 444 hasExpectedEtherHeader(@onNull final ByteBuffer buf, boolean isIpv4)445 private static boolean hasExpectedEtherHeader(@NonNull final ByteBuffer buf, boolean isIpv4) 446 throws Exception { 447 final int expected = isIpv4 ? ETHER_TYPE_IPV4 : ETHER_TYPE_IPV6; 448 449 return Struct.parse(EthernetHeader.class, buf).etherType == expected; 450 } 451 hasExpectedIpHeader(@onNull final ByteBuffer buf, boolean isIpv4, int ipProto)452 private static boolean hasExpectedIpHeader(@NonNull final ByteBuffer buf, boolean isIpv4, 453 int ipProto) throws Exception { 454 if (isIpv4) { 455 return Struct.parse(Ipv4Header.class, buf).protocol == (byte) ipProto; 456 } else { 457 return Struct.parse(Ipv6Header.class, buf).nextHeader == (byte) ipProto; 458 } 459 } 460 isExpectedUdpPacket(@onNull final byte[] rawPacket, boolean hasEth, boolean isIpv4, Predicate<ByteBuffer> payloadVerifier)461 private static boolean isExpectedUdpPacket(@NonNull final byte[] rawPacket, boolean hasEth, 462 boolean isIpv4, Predicate<ByteBuffer> payloadVerifier) { 463 final ByteBuffer buf = ByteBuffer.wrap(rawPacket); 464 try { 465 if (hasEth && !hasExpectedEtherHeader(buf, isIpv4)) return false; 466 467 if (!hasExpectedIpHeader(buf, isIpv4, IPPROTO_UDP)) return false; 468 469 if (Struct.parse(UdpHeader.class, buf) == null) return false; 470 471 if (!payloadVerifier.test(buf)) return false; 472 } catch (Exception e) { 473 // Parsing packet fail means it is not udp packet. 474 return false; 475 } 476 return true; 477 } 478 479 // Returns remaining bytes in the ByteBuffer in a new byte array of the right size. The 480 // ByteBuffer will be empty upon return. Used to avoid lint warning. 481 // See https://errorprone.info/bugpattern/ByteBufferBackingArray getRemaining(final ByteBuffer buf)482 private static byte[] getRemaining(final ByteBuffer buf) { 483 final byte[] bytes = new byte[buf.remaining()]; 484 buf.get(bytes); 485 Log.d(TAG, "Get remaining bytes: " + dumpHexString(bytes)); 486 return bytes; 487 } 488 489 // |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)490 public static boolean isExpectedUdpPacket(@NonNull final byte[] rawPacket, boolean hasEth, 491 boolean isIpv4, @NonNull final ByteBuffer expectedPayload) { 492 return isExpectedUdpPacket(rawPacket, hasEth, isIpv4, p -> { 493 if (p.remaining() != expectedPayload.limit()) return false; 494 495 return Arrays.equals(getRemaining(p), getRemaining( 496 expectedPayload.asReadOnlyBuffer())); 497 }); 498 } 499 500 // |expectedPayload| is copied as read-only because the caller may reuse it. 501 // See hasExpectedDnsMessage. isExpectedUdpDnsPacket(@onNull final byte[] rawPacket, boolean hasEth, boolean isIpv4, @NonNull final ByteBuffer expectedPayload)502 public static boolean isExpectedUdpDnsPacket(@NonNull final byte[] rawPacket, boolean hasEth, 503 boolean isIpv4, @NonNull final ByteBuffer expectedPayload) { 504 return isExpectedUdpPacket(rawPacket, hasEth, isIpv4, p -> { 505 return hasExpectedDnsMessage(p, expectedPayload); 506 }); 507 } 508 509 public static class TestDnsPacket extends DnsPacket { 510 TestDnsPacket(byte[] data) throws DnsPacket.ParseException { 511 super(data); 512 } 513 514 TestDnsPacket(@NonNull DnsHeader header, @Nullable ArrayList<DnsRecord> qd, 515 @Nullable ArrayList<DnsRecord> an) { 516 super(header, qd, an); 517 } 518 519 @Nullable 520 public static TestDnsPacket getTestDnsPacket(final ByteBuffer buf) { 521 try { 522 // The ByteBuffer will be empty upon return. 523 return new TestDnsPacket(getRemaining(buf)); 524 } catch (DnsPacket.ParseException e) { 525 return null; 526 } 527 } 528 529 public DnsHeader getHeader() { 530 return mHeader; 531 } 532 533 public List<DnsRecord> getRecordList(int secType) { 534 return mRecords[secType]; 535 } 536 537 public int getANCount() { 538 return mHeader.getRecordCount(ANSECTION); 539 } 540 541 public int getQDCount() { 542 return mHeader.getRecordCount(QDSECTION); 543 } 544 545 public int getNSCount() { 546 return mHeader.getRecordCount(NSSECTION); 547 } 548 549 public int getARCount() { 550 return mHeader.getRecordCount(ARSECTION); 551 } 552 553 private boolean isRecordsEquals(int type, @NonNull final TestDnsPacket other) { 554 List<DnsRecord> records = getRecordList(type); 555 List<DnsRecord> otherRecords = other.getRecordList(type); 556 557 if (records.size() != otherRecords.size()) return false; 558 559 // Expect that two compared resource records are in the same order. For current tests 560 // in EthernetTetheringTest, it is okay because dnsmasq doesn't reorder the forwarded 561 // resource records. 562 // TODO: consider allowing that compare records out of order. 563 for (int i = 0; i < records.size(); i++) { 564 // TODO: use DnsRecord.equals once aosp/1387135 is merged. 565 if (!TextUtils.equals(records.get(i).dName, otherRecords.get(i).dName) 566 || records.get(i).nsType != otherRecords.get(i).nsType 567 || records.get(i).nsClass != otherRecords.get(i).nsClass 568 || records.get(i).ttl != otherRecords.get(i).ttl 569 || !Arrays.equals(records.get(i).getRR(), otherRecords.get(i).getRR())) { 570 return false; 571 } 572 } 573 return true; 574 } 575 576 public boolean isQDRecordsEquals(@NonNull final TestDnsPacket other) { 577 return isRecordsEquals(QDSECTION, other); 578 } 579 580 public boolean isANRecordsEquals(@NonNull final TestDnsPacket other) { 581 return isRecordsEquals(ANSECTION, other); 582 } 583 } 584 585 // The ByteBuffer |actual| will be empty upon return. The ByteBuffer |excepted| will be copied 586 // as read-only because the caller may reuse it. 587 private static boolean hasExpectedDnsMessage(@NonNull final ByteBuffer actual, 588 @NonNull final ByteBuffer excepted) { 589 // Forwarded DNS message is extracted from remaining received packet buffer which has 590 // already parsed ethernet header, if any, IP header and UDP header. 591 final TestDnsPacket forwardedDns = TestDnsPacket.getTestDnsPacket(actual); 592 if (forwardedDns == null) return false; 593 594 // Original DNS message is the payload of the sending test UDP packet. It is used to check 595 // that the forwarded DNS query and reply have corresponding contents. 596 final TestDnsPacket originalDns = TestDnsPacket.getTestDnsPacket( 597 excepted.asReadOnlyBuffer()); 598 assertNotNull(originalDns); 599 600 // Compare original DNS message which is sent to dnsmasq and forwarded DNS message which 601 // is forwarded by dnsmasq. The original message and forwarded message may be not identical 602 // because dnsmasq may change the header flags or even recreate the DNS query message and 603 // so on. We only simple check on forwarded packet and monitor if test will be broken by 604 // vendor dnsmasq customization. See forward_query() in external/dnsmasq/src/forward.c. 605 // 606 // DNS message format. See rfc1035 section 4.1. 607 // +---------------------+ 608 // | Header | 609 // +---------------------+ 610 // | Question | the question for the name server 611 // +---------------------+ 612 // | Answer | RRs answering the question 613 // +---------------------+ 614 // | Authority | RRs pointing toward an authority 615 // +---------------------+ 616 // | Additional | RRs holding additional information 617 // +---------------------+ 618 619 // [1] Header section. See rfc1035 section 4.1.1. 620 // Verify QR flag bit, QDCOUNT, ANCOUNT, NSCOUNT, ARCOUNT. 621 if (originalDns.getHeader().isResponse() != forwardedDns.getHeader().isResponse()) { 622 return false; 623 } 624 if (originalDns.getQDCount() != forwardedDns.getQDCount()) return false; 625 if (originalDns.getANCount() != forwardedDns.getANCount()) return false; 626 if (originalDns.getNSCount() != forwardedDns.getNSCount()) return false; 627 if (originalDns.getARCount() != forwardedDns.getARCount()) return false; 628 629 // [2] Question section. See rfc1035 section 4.1.2. 630 // Question section has at least one entry either DNS query or DNS reply. 631 if (forwardedDns.getRecordList(QDSECTION).isEmpty()) return false; 632 // Expect that original and forwarded message have the same question records (usually 1). 633 if (!originalDns.isQDRecordsEquals(forwardedDns)) return false; 634 635 // [3] Answer section. See rfc1035 section 4.1.3. 636 if (forwardedDns.getHeader().isResponse()) { 637 // DNS reply has at least have one answer in our tests. 638 // See EthernetTetheringTest#testTetherUdpV4Dns. 639 if (forwardedDns.getRecordList(ANSECTION).isEmpty()) return false; 640 // Expect that original and forwarded message have the same answer records. 641 if (!originalDns.isANRecordsEquals(forwardedDns)) return false; 642 } 643 644 // Ignore checking {Authority, Additional} sections because they are not tested 645 // in EthernetTetheringTest. 646 return true; 647 } 648 649 650 private static boolean isTcpSynPacket(@NonNull final TcpHeader tcpHeader) { 651 return (tcpHeader.dataOffsetAndControlBits & TCPHDR_SYN) != 0; 652 } 653 654 public static boolean isExpectedTcpPacket(@NonNull final byte[] rawPacket, boolean hasEth, 655 boolean isIpv4, int seq, @NonNull final ByteBuffer payload) { 656 final ByteBuffer buf = ByteBuffer.wrap(rawPacket); 657 try { 658 if (hasEth && !hasExpectedEtherHeader(buf, isIpv4)) return false; 659 660 if (!hasExpectedIpHeader(buf, isIpv4, IPPROTO_TCP)) return false; 661 662 final TcpHeader tcpHeader = Struct.parse(TcpHeader.class, buf); 663 if (tcpHeader.seq != seq) return false; 664 665 // Don't try to parse the payload if it is a TCP SYN segment because additional TCP 666 // option MSS may be added in the SYN segment. Currently, TetherController uses 667 // iptables to limit downstream MSS for IPv4. The additional TCP options will be 668 // misunderstood as payload because parsing TCP options are not supported by class 669 // TcpHeader for now. See TetherController::setupIptablesHooks. 670 // TODO: remove once TcpHeader supports parsing TCP options. 671 if (isTcpSynPacket(tcpHeader)) { 672 Log.d(TAG, "Found SYN segment. Ignore parsing the remaining part of packet."); 673 return true; 674 } 675 676 if (payload.limit() != buf.remaining()) return false; 677 return Arrays.equals(getRemaining(buf), getRemaining(payload.asReadOnlyBuffer())); 678 } catch (Exception e) { 679 // Parsing packet fail means it is not tcp packet. 680 } 681 682 return false; 683 } 684 685 @NonNull 686 public static ByteBuffer buildUdpPacket( 687 @Nullable final MacAddress srcMac, @Nullable final MacAddress dstMac, 688 @NonNull final InetAddress srcIp, @NonNull final InetAddress dstIp, 689 short srcPort, short dstPort, @Nullable final ByteBuffer payload) 690 throws Exception { 691 final int ipProto = getIpProto(srcIp, dstIp); 692 final boolean hasEther = (srcMac != null && dstMac != null); 693 final int payloadLen = (payload == null) ? 0 : payload.limit(); 694 final ByteBuffer buffer = PacketBuilder.allocate(hasEther, ipProto, IPPROTO_UDP, 695 payloadLen); 696 final PacketBuilder packetBuilder = new PacketBuilder(buffer); 697 698 // [1] Ethernet header 699 if (hasEther) { 700 packetBuilder.writeL2Header(srcMac, dstMac, getEthType(srcIp, dstIp)); 701 } 702 703 // [2] IP header 704 if (ipProto == IPPROTO_IP) { 705 packetBuilder.writeIpv4Header(TYPE_OF_SERVICE, ID, FLAGS_AND_FRAGMENT_OFFSET, 706 TIME_TO_LIVE, (byte) IPPROTO_UDP, (Inet4Address) srcIp, (Inet4Address) dstIp); 707 } else { 708 packetBuilder.writeIpv6Header(VERSION_TRAFFICCLASS_FLOWLABEL, (byte) IPPROTO_UDP, 709 HOP_LIMIT, (Inet6Address) srcIp, (Inet6Address) dstIp); 710 } 711 712 // [3] UDP header 713 packetBuilder.writeUdpHeader(srcPort, dstPort); 714 715 // [4] Payload 716 if (payload != null) { 717 buffer.put(payload); 718 // in case data might be reused by caller, restore the position and 719 // limit of bytebuffer. 720 payload.clear(); 721 } 722 723 return packetBuilder.finalizePacket(); 724 } 725 726 @NonNull 727 public static ByteBuffer buildUdpPacket(@NonNull final InetAddress srcIp, 728 @NonNull final InetAddress dstIp, short srcPort, short dstPort, 729 @Nullable final ByteBuffer payload) throws Exception { 730 return buildUdpPacket(null /* srcMac */, null /* dstMac */, srcIp, dstIp, srcPort, 731 dstPort, payload); 732 } 733 734 @NonNull 735 public static ByteBuffer buildTcpPacket( 736 @Nullable final MacAddress srcMac, @Nullable final MacAddress dstMac, 737 @NonNull final InetAddress srcIp, @NonNull final InetAddress dstIp, 738 short srcPort, short dstPort, final short seq, final short ack, 739 final byte tcpFlags, @NonNull final ByteBuffer payload) throws Exception { 740 final int ipProto = getIpProto(srcIp, dstIp); 741 final boolean hasEther = (srcMac != null && dstMac != null); 742 final ByteBuffer buffer = PacketBuilder.allocate(hasEther, ipProto, IPPROTO_TCP, 743 payload.limit()); 744 final PacketBuilder packetBuilder = new PacketBuilder(buffer); 745 746 // [1] Ethernet header 747 if (hasEther) { 748 packetBuilder.writeL2Header(srcMac, dstMac, getEthType(srcIp, dstIp)); 749 } 750 751 // [2] IP header 752 if (ipProto == IPPROTO_IP) { 753 packetBuilder.writeIpv4Header(TYPE_OF_SERVICE, ID, FLAGS_AND_FRAGMENT_OFFSET, 754 TIME_TO_LIVE, (byte) IPPROTO_TCP, (Inet4Address) srcIp, (Inet4Address) dstIp); 755 } else { 756 packetBuilder.writeIpv6Header(VERSION_TRAFFICCLASS_FLOWLABEL, (byte) IPPROTO_TCP, 757 HOP_LIMIT, (Inet6Address) srcIp, (Inet6Address) dstIp); 758 } 759 760 // [3] TCP header 761 packetBuilder.writeTcpHeader(srcPort, dstPort, seq, ack, tcpFlags, WINDOW, URGENT_POINTER); 762 763 // [4] Payload 764 buffer.put(payload); 765 // in case data might be reused by caller, restore the position and 766 // limit of bytebuffer. 767 payload.clear(); 768 769 return packetBuilder.finalizePacket(); 770 } 771 772 // PacketBuilder doesn't support IPv4 ICMP packet. It may need to refactor PacketBuilder first 773 // because ICMP is a specific layer 3 protocol for PacketBuilder which expects packets always 774 // have layer 3 (IP) and layer 4 (TCP, UDP) for now. Since we don't use IPv4 ICMP packet too 775 // much in this test, we just write a ICMP packet builder here. 776 @NonNull 777 public static ByteBuffer buildIcmpEchoPacketV4( 778 @Nullable final MacAddress srcMac, @Nullable final MacAddress dstMac, 779 @NonNull final Inet4Address srcIp, @NonNull final Inet4Address dstIp, 780 int type, short id, short seq) throws Exception { 781 if (type != ICMP_ECHO && type != ICMP_ECHOREPLY) { 782 fail("Unsupported ICMP type: " + type); 783 } 784 785 // Build ICMP echo id and seq fields as payload. Ignore the data field. 786 final ByteBuffer payload = ByteBuffer.allocate(4); 787 payload.putShort(id); 788 payload.putShort(seq); 789 payload.rewind(); 790 791 final boolean hasEther = (srcMac != null && dstMac != null); 792 final int etherHeaderLen = hasEther ? Struct.getSize(EthernetHeader.class) : 0; 793 final int ipv4HeaderLen = Struct.getSize(Ipv4Header.class); 794 final int Icmpv4HeaderLen = Struct.getSize(Icmpv4Header.class); 795 final int payloadLen = payload.limit(); 796 final ByteBuffer packet = ByteBuffer.allocate(etherHeaderLen + ipv4HeaderLen 797 + Icmpv4HeaderLen + payloadLen); 798 799 // [1] Ethernet header 800 if (hasEther) { 801 final EthernetHeader ethHeader = new EthernetHeader(dstMac, srcMac, ETHER_TYPE_IPV4); 802 ethHeader.writeToByteBuffer(packet); 803 } 804 805 // [2] IP header 806 final Ipv4Header ipv4Header = new Ipv4Header(TYPE_OF_SERVICE, 807 (short) 0 /* totalLength, calculate later */, ID, 808 FLAGS_AND_FRAGMENT_OFFSET, TIME_TO_LIVE, (byte) IPPROTO_ICMP, 809 (short) 0 /* checksum, calculate later */, srcIp, dstIp); 810 ipv4Header.writeToByteBuffer(packet); 811 812 // [3] ICMP header 813 final Icmpv4Header icmpv4Header = new Icmpv4Header((byte) type, ICMPECHO_CODE, 814 (short) 0 /* checksum, calculate later */); 815 icmpv4Header.writeToByteBuffer(packet); 816 817 // [4] Payload 818 packet.put(payload); 819 packet.flip(); 820 821 // [5] Finalize packet 822 // Used for updating IP header fields. If there is Ehternet header, IPv4 header offset 823 // in buffer equals ethernet header length because IPv4 header is located next to ethernet 824 // header. Otherwise, IPv4 header offset is 0. 825 final int ipv4HeaderOffset = hasEther ? etherHeaderLen : 0; 826 827 // Populate the IPv4 totalLength field. 828 packet.putShort(ipv4HeaderOffset + IPV4_LENGTH_OFFSET, 829 (short) (ipv4HeaderLen + Icmpv4HeaderLen + payloadLen)); 830 831 // Populate the IPv4 header checksum field. 832 packet.putShort(ipv4HeaderOffset + IPV4_CHECKSUM_OFFSET, 833 ipChecksum(packet, ipv4HeaderOffset /* headerOffset */)); 834 835 // Populate the ICMP checksum field. 836 packet.putShort(ipv4HeaderOffset + IPV4_HEADER_MIN_LEN + ICMP_CHECKSUM_OFFSET, 837 icmpChecksum(packet, ipv4HeaderOffset + IPV4_HEADER_MIN_LEN, 838 Icmpv4HeaderLen + payloadLen)); 839 return packet; 840 } 841 842 @NonNull 843 public static ByteBuffer buildIcmpEchoPacketV4(@NonNull final Inet4Address srcIp, 844 @NonNull final Inet4Address dstIp, int type, short id, short seq) 845 throws Exception { 846 return buildIcmpEchoPacketV4(null /* srcMac */, null /* dstMac */, srcIp, dstIp, 847 type, id, seq); 848 } 849 850 private static short getEthType(@NonNull final InetAddress srcIp, 851 @NonNull final InetAddress dstIp) { 852 return isAddressIpv4(srcIp, dstIp) ? (short) ETHER_TYPE_IPV4 : (short) ETHER_TYPE_IPV6; 853 } 854 855 private static int getIpProto(@NonNull final InetAddress srcIp, 856 @NonNull final InetAddress dstIp) { 857 return isAddressIpv4(srcIp, dstIp) ? IPPROTO_IP : IPPROTO_IPV6; 858 } 859 860 public static boolean isAddressIpv4(@NonNull final InetAddress srcIp, 861 @NonNull final InetAddress dstIp) { 862 if (srcIp instanceof Inet4Address && dstIp instanceof Inet4Address) return true; 863 if (srcIp instanceof Inet6Address && dstIp instanceof Inet6Address) return false; 864 865 fail("Unsupported conditions: srcIp " + srcIp + ", dstIp " + dstIp); 866 return false; // unreachable 867 } 868 869 public void sendUploadPacket(ByteBuffer packet) throws Exception { 870 mDownstreamReader.sendResponse(packet); 871 } 872 873 private void sendDownloadPacket(ByteBuffer packet) throws Exception { 874 assertNotNull("Can't deal with upstream interface in local only mode", mUpstreamReader); 875 876 mUpstreamReader.sendResponse(packet); 877 } 878 879 private byte[] getDownloadPacket(Predicate<byte[]> filter) { 880 byte[] packet; 881 while ((packet = mDownstreamReader.poll(PACKET_READ_TIMEOUT_MS)) != null) { 882 if (filter.test(packet)) return packet; 883 884 maybeReplyArp(packet); 885 maybeReplyNa(packet); 886 } 887 888 return null; 889 } 890 891 @NonNull 892 private ByteBuffer buildUdpDnsPrefix64ReplyPacket(int dnsId, @NonNull final Inet6Address srcIp, 893 @NonNull final Inet6Address dstIp, short srcPort, short dstPort) throws Exception { 894 // [1] Build prefix64 DNS message. 895 final ArrayList<DnsRecord> qlist = new ArrayList<>(); 896 // Fill QD section. 897 qlist.add(DnsRecord.makeQuestion(PREF64_IPV4ONLY_HOSTNAME, TYPE_AAAA, CLASS_IN)); 898 final ArrayList<DnsRecord> alist = new ArrayList<>(); 899 // Fill AN sections. 900 alist.add(DnsRecord.makeAOrAAAARecord(ANSECTION, PREF64_IPV4ONLY_HOSTNAME, CLASS_IN, TTL, 901 PREF64_IPV4ONLY_ADDR)); 902 final TestDnsPacket dns = new TestDnsPacket( 903 new DnsHeader(dnsId, FLAG, qlist.size(), alist.size()), qlist, alist); 904 905 // [2] Build IPv6 UDP DNS packet. 906 return buildUdpPacket(srcIp, dstIp, srcPort, dstPort, ByteBuffer.wrap(dns.getBytes())); 907 } 908 909 private void maybeReplyUdpDnsPrefix64Discovery(@NonNull byte[] packet) { 910 final ByteBuffer buf = ByteBuffer.wrap(packet); 911 912 // [1] Parse the prefix64 discovery DNS query for hostname ipv4only.arpa. 913 // Parse IPv6 and UDP header. 914 Ipv6Header ipv6Header = null; 915 try { 916 ipv6Header = Struct.parse(Ipv6Header.class, buf); 917 if (ipv6Header == null || ipv6Header.nextHeader != IPPROTO_UDP) return; 918 } catch (Exception e) { 919 // Parsing packet fail means it is not IPv6 UDP packet. 920 return; 921 } 922 final UdpHeader udpHeader = Struct.parse(UdpHeader.class, buf); 923 924 // Parse DNS message. 925 final TestDnsPacket pref64Query = TestDnsPacket.getTestDnsPacket(buf); 926 if (pref64Query == null) return; 927 if (pref64Query.getHeader().isResponse()) return; 928 if (pref64Query.getQDCount() != 1) return; 929 if (pref64Query.getANCount() != 0) return; 930 if (pref64Query.getNSCount() != 0) return; 931 if (pref64Query.getARCount() != 0) return; 932 933 final List<DnsRecord> qdRecordList = pref64Query.getRecordList(QDSECTION); 934 if (qdRecordList.size() != 1) return; 935 if (!qdRecordList.get(0).dName.equals(PREF64_IPV4ONLY_HOSTNAME)) return; 936 937 // [2] Build prefix64 DNS discovery reply from received query. 938 // DNS response transaction id must be copied from DNS query. Used by the requester 939 // to match up replies to outstanding queries. See RFC 1035 section 4.1.1. Also reverse 940 // the source/destination address/port of query packet for building reply packet. 941 final ByteBuffer replyPacket; 942 try { 943 replyPacket = buildUdpDnsPrefix64ReplyPacket(pref64Query.getHeader().getId(), 944 ipv6Header.dstIp /* srcIp */, ipv6Header.srcIp /* dstIp */, 945 (short) udpHeader.dstPort /* srcPort */, 946 (short) udpHeader.srcPort /* dstPort */); 947 } catch (Exception e) { 948 fail("Failed to build prefix64 discovery reply for " + ipv6Header.srcIp + ": " + e); 949 return; 950 } 951 952 Log.d(TAG, "Sending prefix64 discovery reply"); 953 try { 954 sendDownloadPacket(replyPacket); 955 } catch (Exception e) { 956 fail("Failed to reply prefix64 discovery for " + ipv6Header.srcIp + ": " + e); 957 } 958 } 959 960 private byte[] getUploadPacket(Predicate<byte[]> filter) { 961 assertNotNull("Can't deal with upstream interface in local only mode", mUpstreamReader); 962 963 byte[] packet; 964 while ((packet = mUpstreamReader.poll(PACKET_READ_TIMEOUT_MS)) != null) { 965 if (filter.test(packet)) return packet; 966 967 maybeReplyUdpDnsPrefix64Discovery(packet); 968 } 969 return null; 970 } 971 972 private @NonNull byte[] verifyPacketNotNull(String message, @Nullable byte[] packet) { 973 assertNotNull(message, packet); 974 975 return packet; 976 } 977 978 public byte[] testUpload(final ByteBuffer packet, final Predicate<byte[]> filter) 979 throws Exception { 980 sendUploadPacket(packet); 981 982 return getUploadPacket(filter); 983 } 984 985 public byte[] verifyUpload(final ByteBuffer packet, final Predicate<byte[]> filter) 986 throws Exception { 987 return verifyPacketNotNull("Upload fail", testUpload(packet, filter)); 988 } 989 990 public byte[] verifyDownload(final ByteBuffer packet, final Predicate<byte[]> filter) 991 throws Exception { 992 sendDownloadPacket(packet); 993 994 return verifyPacketNotNull("Download fail", getDownloadPacket(filter)); 995 } 996 997 // Send DHCPDISCOVER to DHCP server to see if DHCP server is still alive to handle 998 // the upcoming DHCP packets. This method should be only used when we know the DHCP 999 // server has been created successfully before. 1000 public boolean testDhcpServerAlive(final MacAddress mac) throws Exception { 1001 sendDhcpDiscover(mac.toByteArray()); 1002 return getNextDhcpPacket() != null; 1003 } 1004 } 1005