1 /* 2 * Copyright (C) 2018 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.dhcp; 18 19 import static android.net.dhcp.DhcpPacket.DHCP_CLIENT; 20 import static android.net.dhcp.DhcpPacket.DHCP_HOST_NAME; 21 import static android.net.dhcp.DhcpPacket.DHCP_SERVER; 22 import static android.net.dhcp.DhcpPacket.ENCAP_BOOTP; 23 import static android.net.dhcp.IDhcpServer.STATUS_INVALID_ARGUMENT; 24 import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS; 25 import static android.net.dhcp.IDhcpServer.STATUS_UNKNOWN_ERROR; 26 import static android.net.util.NetworkStackUtils.DHCP_RAPID_COMMIT_VERSION; 27 import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY; 28 import static android.system.OsConstants.AF_INET; 29 import static android.system.OsConstants.IPPROTO_UDP; 30 import static android.system.OsConstants.SOCK_DGRAM; 31 import static android.system.OsConstants.SOCK_NONBLOCK; 32 import static android.system.OsConstants.SOL_SOCKET; 33 import static android.system.OsConstants.SO_BROADCAST; 34 import static android.system.OsConstants.SO_REUSEADDR; 35 36 import static com.android.net.module.util.Inet4AddressUtils.getBroadcastAddress; 37 import static com.android.net.module.util.Inet4AddressUtils.getPrefixMaskAsInet4Address; 38 import static com.android.net.module.util.NetworkStackConstants.INFINITE_LEASE; 39 import static com.android.net.module.util.NetworkStackConstants.IPV4_ADDR_ALL; 40 import static com.android.net.module.util.NetworkStackConstants.IPV4_ADDR_ANY; 41 import static com.android.net.module.util.NetworkStackConstants.TAG_SYSTEM_DHCP_SERVER; 42 import static com.android.server.util.PermissionUtil.enforceNetworkStackCallingPermission; 43 44 import static java.lang.Integer.toUnsignedLong; 45 46 import android.content.Context; 47 import android.net.INetworkStackStatusCallback; 48 import android.net.IpPrefix; 49 import android.net.MacAddress; 50 import android.net.TrafficStats; 51 import android.net.util.NetworkStackUtils; 52 import android.net.util.SharedLog; 53 import android.net.util.SocketUtils; 54 import android.os.Handler; 55 import android.os.Message; 56 import android.os.RemoteException; 57 import android.os.SystemClock; 58 import android.system.ErrnoException; 59 import android.system.Os; 60 import android.text.TextUtils; 61 import android.util.Pair; 62 63 import androidx.annotation.NonNull; 64 import androidx.annotation.Nullable; 65 import androidx.annotation.VisibleForTesting; 66 67 import com.android.internal.util.HexDump; 68 import com.android.internal.util.State; 69 import com.android.internal.util.StateMachine; 70 import com.android.net.module.util.DeviceConfigUtils; 71 72 import java.io.FileDescriptor; 73 import java.io.IOException; 74 import java.net.Inet4Address; 75 import java.net.InetAddress; 76 import java.nio.ByteBuffer; 77 import java.util.ArrayList; 78 79 /** 80 * A DHCPv4 server. 81 * 82 * <p>This server listens for and responds to packets on a single interface. It considers itself 83 * authoritative for all leases on the subnet, which means that DHCP requests for unknown leases of 84 * unknown hosts receive a reply instead of being ignored. 85 * 86 * <p>The server relies on StateMachine's handler (including send/receive operations): all internal 87 * operations are done in StateMachine's looper. Public methods are thread-safe and will schedule 88 * operations on that looper asynchronously. 89 * @hide 90 */ 91 public class DhcpServer extends StateMachine { 92 private static final String REPO_TAG = "Repository"; 93 94 // Lease time to transmit to client instead of a negative time in case a lease expired before 95 // the server could send it (if the server process is suspended for example). 96 private static final int EXPIRED_FALLBACK_LEASE_TIME_SECS = 120; 97 98 private static final int CMD_START_DHCP_SERVER = 1; 99 private static final int CMD_STOP_DHCP_SERVER = 2; 100 private static final int CMD_UPDATE_PARAMS = 3; 101 @VisibleForTesting 102 protected static final int CMD_RECEIVE_PACKET = 4; 103 private static final int CMD_TERMINATE_AFTER_STOP = 5; 104 105 @NonNull 106 private final Context mContext; 107 @NonNull 108 private final String mIfName; 109 @NonNull 110 private final DhcpLeaseRepository mLeaseRepo; 111 @NonNull 112 private final SharedLog mLog; 113 @NonNull 114 private final Dependencies mDeps; 115 @NonNull 116 private final Clock mClock; 117 @NonNull 118 private DhcpServingParams mServingParams; 119 120 @Nullable 121 private DhcpPacketListener mPacketListener; 122 @Nullable 123 private FileDescriptor mSocket; 124 @Nullable 125 private IDhcpEventCallbacks mEventCallbacks; 126 127 private final boolean mDhcpRapidCommitEnabled; 128 129 // States. 130 private final StoppedState mStoppedState = new StoppedState(); 131 private final StartedState mStartedState = new StartedState(); 132 private final RunningState mRunningState = new RunningState(); 133 private final WaitBeforeRetrievalState mWaitBeforeRetrievalState = 134 new WaitBeforeRetrievalState(); 135 136 /** 137 * Clock to be used by DhcpServer to track time for lease expiration. 138 * 139 * <p>The clock should track time as may be measured by clients obtaining a lease. It does not 140 * need to be monotonous across restarts of the server as long as leases are cleared when the 141 * server is stopped. 142 */ 143 public static class Clock { 144 /** 145 * @see SystemClock#elapsedRealtime() 146 */ 147 public long elapsedRealtime() { 148 return SystemClock.elapsedRealtime(); 149 } 150 } 151 152 /** 153 * Dependencies for the DhcpServer. Useful to be mocked in tests. 154 */ 155 public interface Dependencies { 156 /** 157 * Send a packet to the specified datagram socket. 158 * 159 * @param fd File descriptor of the socket. 160 * @param buffer Data to be sent. 161 * @param dst Destination address of the packet. 162 */ 163 void sendPacket(@NonNull FileDescriptor fd, @NonNull ByteBuffer buffer, 164 @NonNull InetAddress dst) throws ErrnoException, IOException; 165 166 /** 167 * Create a DhcpLeaseRepository for the server. 168 * @param servingParams Parameters used to serve DHCP requests. 169 * @param log Log to be used by the repository. 170 * @param clock Clock that the repository must use to track time. 171 */ 172 DhcpLeaseRepository makeLeaseRepository(@NonNull DhcpServingParams servingParams, 173 @NonNull SharedLog log, @NonNull Clock clock); 174 175 /** 176 * Create a packet listener that will send packets to be processed. 177 */ 178 DhcpPacketListener makePacketListener(@NonNull Handler handler); 179 180 /** 181 * Create a clock that the server will use to track time. 182 */ 183 Clock makeClock(); 184 185 /** 186 * Add an entry to the ARP cache table. 187 * @param fd Datagram socket file descriptor that must use the new entry. 188 */ 189 void addArpEntry(@NonNull Inet4Address ipv4Addr, @NonNull MacAddress ethAddr, 190 @NonNull String ifname, @NonNull FileDescriptor fd) throws IOException; 191 192 /** 193 * Check whether or not one specific experimental feature for connectivity namespace is 194 * enabled. 195 * @param context The global context information about an app environment. 196 * @param name Specific experimental flag name. 197 */ 198 boolean isFeatureEnabled(@NonNull Context context, @NonNull String name); 199 } 200 201 private class DependenciesImpl implements Dependencies { 202 @Override 203 public void sendPacket(@NonNull FileDescriptor fd, @NonNull ByteBuffer buffer, 204 @NonNull InetAddress dst) throws ErrnoException, IOException { 205 Os.sendto(fd, buffer, 0, dst, DhcpPacket.DHCP_CLIENT); 206 } 207 208 @Override 209 public DhcpLeaseRepository makeLeaseRepository(@NonNull DhcpServingParams servingParams, 210 @NonNull SharedLog log, @NonNull Clock clock) { 211 return new DhcpLeaseRepository( 212 DhcpServingParams.makeIpPrefix(servingParams.serverAddr), 213 servingParams.excludedAddrs, servingParams.dhcpLeaseTimeSecs * 1000, 214 servingParams.singleClientAddr, log.forSubComponent(REPO_TAG), clock); 215 } 216 217 @Override 218 public DhcpPacketListener makePacketListener(@NonNull Handler handler) { 219 return new PacketListener(handler); 220 } 221 222 @Override 223 public Clock makeClock() { 224 return new Clock(); 225 } 226 227 @Override 228 public void addArpEntry(@NonNull Inet4Address ipv4Addr, @NonNull MacAddress ethAddr, 229 @NonNull String ifname, @NonNull FileDescriptor fd) throws IOException { 230 NetworkStackUtils.addArpEntry(ipv4Addr, ethAddr, ifname, fd); 231 } 232 233 @Override 234 public boolean isFeatureEnabled(@NonNull Context context, @NonNull String name) { 235 return DeviceConfigUtils.isFeatureEnabled(context, NAMESPACE_CONNECTIVITY, name); 236 } 237 } 238 239 private static class MalformedPacketException extends Exception { 240 MalformedPacketException(String message, Throwable t) { 241 super(message, t); 242 } 243 } 244 245 public DhcpServer(@NonNull Context context, @NonNull String ifName, 246 @NonNull DhcpServingParams params, @NonNull SharedLog log) { 247 this(context, ifName, params, log, null); 248 } 249 250 @VisibleForTesting 251 DhcpServer(@NonNull Context context, @NonNull String ifName, @NonNull DhcpServingParams params, 252 @NonNull SharedLog log, @Nullable Dependencies deps) { 253 super(DhcpServer.class.getSimpleName() + "." + ifName); 254 255 if (deps == null) { 256 deps = new DependenciesImpl(); 257 } 258 mContext = context; 259 mIfName = ifName; 260 mServingParams = params; 261 mLog = log; 262 mDeps = deps; 263 mClock = deps.makeClock(); 264 mLeaseRepo = deps.makeLeaseRepository(mServingParams, mLog, mClock); 265 mDhcpRapidCommitEnabled = deps.isFeatureEnabled(context, DHCP_RAPID_COMMIT_VERSION); 266 267 // CHECKSTYLE:OFF IndentationCheck 268 addState(mStoppedState); 269 addState(mStartedState); 270 addState(mRunningState, mStartedState); 271 addState(mWaitBeforeRetrievalState, mStartedState); 272 // CHECKSTYLE:ON IndentationCheck 273 274 setInitialState(mStoppedState); 275 276 super.start(); 277 } 278 279 /** 280 * Make a IDhcpServer connector to communicate with this DhcpServer. 281 */ 282 public IDhcpServer makeConnector() { 283 return new DhcpServerConnector(); 284 } 285 286 private class DhcpServerConnector extends IDhcpServer.Stub { 287 @Override 288 public void start(@Nullable INetworkStackStatusCallback cb) { 289 enforceNetworkStackCallingPermission(); 290 DhcpServer.this.start(cb); 291 } 292 293 @Override 294 public void startWithCallbacks(@Nullable INetworkStackStatusCallback statusCb, 295 @Nullable IDhcpEventCallbacks eventCb) { 296 enforceNetworkStackCallingPermission(); 297 DhcpServer.this.start(statusCb, eventCb); 298 } 299 300 @Override 301 public void updateParams(@Nullable DhcpServingParamsParcel params, 302 @Nullable INetworkStackStatusCallback cb) { 303 enforceNetworkStackCallingPermission(); 304 DhcpServer.this.updateParams(params, cb); 305 } 306 307 @Override 308 public void stop(@Nullable INetworkStackStatusCallback cb) { 309 enforceNetworkStackCallingPermission(); 310 DhcpServer.this.stop(cb); 311 } 312 313 @Override 314 public int getInterfaceVersion() { 315 return this.VERSION; 316 } 317 318 @Override 319 public String getInterfaceHash() { 320 return this.HASH; 321 } 322 } 323 324 /** 325 * Start listening for and responding to packets. 326 * 327 * <p>It is not legal to call this method more than once; in particular the server cannot be 328 * restarted after being stopped. 329 */ 330 void start(@Nullable INetworkStackStatusCallback cb) { 331 start(cb, null); 332 } 333 334 /** 335 * Start listening for and responding to packets, with optional callbacks for lease events. 336 * 337 * <p>It is not legal to call this method more than once; in particular the server cannot be 338 * restarted after being stopped. 339 */ 340 void start(@Nullable INetworkStackStatusCallback statusCb, 341 @Nullable IDhcpEventCallbacks eventCb) { 342 sendMessage(CMD_START_DHCP_SERVER, new Pair<>(statusCb, eventCb)); 343 } 344 345 /** 346 * Update serving parameters. All subsequently received requests will be handled with the new 347 * parameters, and current leases that are incompatible with the new parameters are dropped. 348 */ 349 void updateParams(@Nullable DhcpServingParamsParcel params, 350 @Nullable INetworkStackStatusCallback cb) { 351 final DhcpServingParams parsedParams; 352 try { 353 // throws InvalidParameterException with null params 354 parsedParams = DhcpServingParams.fromParcelableObject(params); 355 } catch (DhcpServingParams.InvalidParameterException e) { 356 mLog.e("Invalid parameters sent to DhcpServer", e); 357 maybeNotifyStatus(cb, STATUS_INVALID_ARGUMENT); 358 return; 359 } 360 sendMessage(CMD_UPDATE_PARAMS, new Pair<>(parsedParams, cb)); 361 } 362 363 /** 364 * Stop listening for packets. 365 * 366 * <p>As the server is stopped asynchronously, some packets may still be processed shortly after 367 * calling this method. The server will also be cleaned up and can't be started again, even if 368 * it was already stopped. 369 */ 370 void stop(@Nullable INetworkStackStatusCallback cb) { 371 sendMessage(CMD_STOP_DHCP_SERVER, cb); 372 sendMessage(CMD_TERMINATE_AFTER_STOP); 373 } 374 375 private void maybeNotifyStatus(@Nullable INetworkStackStatusCallback cb, int statusCode) { 376 if (cb == null) return; 377 try { 378 cb.onStatusAvailable(statusCode); 379 } catch (RemoteException e) { 380 mLog.e("Could not send status back to caller", e); 381 } 382 } 383 384 private void handleUpdateServingParams(@NonNull DhcpServingParams params, 385 @Nullable INetworkStackStatusCallback cb) { 386 mServingParams = params; 387 mLeaseRepo.updateParams( 388 DhcpServingParams.makeIpPrefix(params.serverAddr), 389 params.excludedAddrs, 390 params.dhcpLeaseTimeSecs * 1000, 391 params.singleClientAddr); 392 maybeNotifyStatus(cb, STATUS_SUCCESS); 393 } 394 395 class StoppedState extends State { 396 private INetworkStackStatusCallback mOnStopCallback; 397 398 @Override 399 public void enter() { 400 maybeNotifyStatus(mOnStopCallback, STATUS_SUCCESS); 401 mOnStopCallback = null; 402 } 403 404 @Override 405 public boolean processMessage(Message msg) { 406 switch (msg.what) { 407 case CMD_START_DHCP_SERVER: 408 final Pair<INetworkStackStatusCallback, IDhcpEventCallbacks> obj = 409 (Pair<INetworkStackStatusCallback, IDhcpEventCallbacks>) msg.obj; 410 mStartedState.mOnStartCallback = obj.first; 411 mEventCallbacks = obj.second; 412 transitionTo(mRunningState); 413 return HANDLED; 414 case CMD_TERMINATE_AFTER_STOP: 415 quit(); 416 return HANDLED; 417 default: 418 return NOT_HANDLED; 419 } 420 } 421 } 422 423 class StartedState extends State { 424 private INetworkStackStatusCallback mOnStartCallback; 425 426 @Override 427 public void enter() { 428 if (mPacketListener != null) { 429 mLog.e("Starting DHCP server more than once is not supported."); 430 maybeNotifyStatus(mOnStartCallback, STATUS_UNKNOWN_ERROR); 431 mOnStartCallback = null; 432 return; 433 } 434 mPacketListener = mDeps.makePacketListener(getHandler()); 435 436 if (!mPacketListener.start()) { 437 mLog.e("Fail to start DHCP Packet Listener, rollback to StoppedState"); 438 deferMessage(obtainMessage(CMD_STOP_DHCP_SERVER, null)); 439 maybeNotifyStatus(mOnStartCallback, STATUS_UNKNOWN_ERROR); 440 mOnStartCallback = null; 441 return; 442 } 443 444 if (mEventCallbacks != null) { 445 mLeaseRepo.addLeaseCallbacks(mEventCallbacks); 446 } 447 maybeNotifyStatus(mOnStartCallback, STATUS_SUCCESS); 448 // Clear INetworkStackStatusCallback binder token, so that it's freed 449 // on the other side. 450 mOnStartCallback = null; 451 } 452 453 @Override 454 public boolean processMessage(Message msg) { 455 switch (msg.what) { 456 case CMD_UPDATE_PARAMS: 457 final Pair<DhcpServingParams, INetworkStackStatusCallback> pair = 458 (Pair<DhcpServingParams, INetworkStackStatusCallback>) msg.obj; 459 handleUpdateServingParams(pair.first, pair.second); 460 return HANDLED; 461 462 case CMD_START_DHCP_SERVER: 463 mLog.e("ALERT: START received in StartedState. Please fix caller."); 464 return HANDLED; 465 466 case CMD_STOP_DHCP_SERVER: 467 mStoppedState.mOnStopCallback = (INetworkStackStatusCallback) msg.obj; 468 transitionTo(mStoppedState); 469 return HANDLED; 470 471 default: 472 return NOT_HANDLED; 473 } 474 } 475 476 @Override 477 public void exit() { 478 mPacketListener.stop(); 479 mLog.logf("DHCP Packet Listener stopped"); 480 } 481 } 482 483 class RunningState extends State { 484 @Override 485 public boolean processMessage(Message msg) { 486 switch (msg.what) { 487 case CMD_RECEIVE_PACKET: 488 processPacket((DhcpPacket) msg.obj); 489 return HANDLED; 490 491 default: 492 // Fall through to StartedState. 493 return NOT_HANDLED; 494 } 495 } 496 497 private void processPacket(@NonNull DhcpPacket packet) { 498 mLog.log("Received packet of type " + packet.getClass().getSimpleName()); 499 500 final Inet4Address sid = packet.mServerIdentifier; 501 if (sid != null && !sid.equals(mServingParams.serverAddr.getAddress())) { 502 mLog.log("Packet ignored due to wrong server identifier: " + sid); 503 return; 504 } 505 506 try { 507 if (packet instanceof DhcpDiscoverPacket) { 508 processDiscover((DhcpDiscoverPacket) packet); 509 } else if (packet instanceof DhcpRequestPacket) { 510 processRequest((DhcpRequestPacket) packet); 511 } else if (packet instanceof DhcpReleasePacket) { 512 processRelease((DhcpReleasePacket) packet); 513 } else if (packet instanceof DhcpDeclinePacket) { 514 processDecline((DhcpDeclinePacket) packet); 515 } else { 516 mLog.e("Unknown packet type: " + packet.getClass().getSimpleName()); 517 } 518 } catch (MalformedPacketException e) { 519 // Not an internal error: only logging exception message, not stacktrace 520 mLog.e("Ignored malformed packet: " + e.getMessage()); 521 } 522 } 523 524 private void logIgnoredPacketInvalidSubnet(DhcpLeaseRepository.InvalidSubnetException e) { 525 // Not an internal error: only logging exception message, not stacktrace 526 mLog.e("Ignored packet from invalid subnet: " + e.getMessage()); 527 } 528 529 private void processDiscover(@NonNull DhcpDiscoverPacket packet) 530 throws MalformedPacketException { 531 final DhcpLease lease; 532 final MacAddress clientMac = getMacAddr(packet); 533 try { 534 if (mDhcpRapidCommitEnabled && packet.mRapidCommit) { 535 lease = mLeaseRepo.getCommittedLease(packet.getExplicitClientIdOrNull(), 536 clientMac, packet.mRelayIp, packet.mHostName); 537 transmitAck(packet, lease, clientMac); 538 } else { 539 lease = mLeaseRepo.getOffer(packet.getExplicitClientIdOrNull(), clientMac, 540 packet.mRelayIp, packet.mRequestedIp, packet.mHostName); 541 transmitOffer(packet, lease, clientMac); 542 } 543 } catch (DhcpLeaseRepository.OutOfAddressesException e) { 544 transmitNak(packet, "Out of addresses to offer"); 545 } catch (DhcpLeaseRepository.InvalidSubnetException e) { 546 logIgnoredPacketInvalidSubnet(e); 547 } 548 } 549 550 private void processRequest(@NonNull DhcpRequestPacket packet) 551 throws MalformedPacketException { 552 // If set, packet SID matches with this server's ID as checked in processPacket(). 553 final boolean sidSet = packet.mServerIdentifier != null; 554 final DhcpLease lease; 555 final MacAddress clientMac = getMacAddr(packet); 556 try { 557 lease = mLeaseRepo.requestLease(packet.getExplicitClientIdOrNull(), clientMac, 558 packet.mClientIp, packet.mRelayIp, packet.mRequestedIp, sidSet, 559 packet.mHostName); 560 } catch (DhcpLeaseRepository.InvalidAddressException e) { 561 transmitNak(packet, "Invalid requested address"); 562 return; 563 } catch (DhcpLeaseRepository.InvalidSubnetException e) { 564 logIgnoredPacketInvalidSubnet(e); 565 return; 566 } 567 568 transmitAck(packet, lease, clientMac); 569 } 570 571 private void processRelease(@NonNull DhcpReleasePacket packet) 572 throws MalformedPacketException { 573 final byte[] clientId = packet.getExplicitClientIdOrNull(); 574 final MacAddress macAddr = getMacAddr(packet); 575 // Don't care about success (there is no ACK/NAK); logging is already done 576 // in the repository. 577 mLeaseRepo.releaseLease(clientId, macAddr, packet.mClientIp); 578 } 579 580 private void processDecline(@NonNull DhcpDeclinePacket packet) 581 throws MalformedPacketException { 582 final byte[] clientId = packet.getExplicitClientIdOrNull(); 583 final MacAddress macAddr = getMacAddr(packet); 584 int committedLeasesCount = mLeaseRepo.getCommittedLeases().size(); 585 586 // If peer's clientID and macAddr doesn't match with any issued lease, nothing to do. 587 if (!mLeaseRepo.markAndReleaseDeclinedLease(clientId, macAddr, packet.mRequestedIp)) { 588 return; 589 } 590 591 // Check whether the boolean flag which requests a new prefix is enabled, and if 592 // it's enabled, make sure the issued lease count should be only one, otherwise, 593 // changing a different prefix will cause other exist host(s) configured with the 594 // current prefix lose appropriate route. 595 if (!mServingParams.changePrefixOnDecline || committedLeasesCount > 1) return; 596 597 if (mEventCallbacks == null) { 598 mLog.e("changePrefixOnDecline enabled but caller didn't pass a valid" 599 + "IDhcpEventCallbacks callback."); 600 return; 601 } 602 603 try { 604 mEventCallbacks.onNewPrefixRequest( 605 DhcpServingParams.makeIpPrefix(mServingParams.serverAddr)); 606 transitionTo(mWaitBeforeRetrievalState); 607 } catch (RemoteException e) { 608 mLog.e("could not request a new prefix to caller", e); 609 } 610 } 611 } 612 613 class WaitBeforeRetrievalState extends State { 614 @Override 615 public boolean processMessage(Message msg) { 616 switch (msg.what) { 617 case CMD_UPDATE_PARAMS: 618 final Pair<DhcpServingParams, INetworkStackStatusCallback> pair = 619 (Pair<DhcpServingParams, INetworkStackStatusCallback>) msg.obj; 620 final IpPrefix currentPrefix = 621 DhcpServingParams.makeIpPrefix(mServingParams.serverAddr); 622 final IpPrefix newPrefix = 623 DhcpServingParams.makeIpPrefix(pair.first.serverAddr); 624 handleUpdateServingParams(pair.first, pair.second); 625 if (currentPrefix != null && !currentPrefix.equals(newPrefix)) { 626 transitionTo(mRunningState); 627 } 628 return HANDLED; 629 630 case CMD_RECEIVE_PACKET: 631 deferMessage(msg); 632 return HANDLED; 633 634 default: 635 // Fall through to StartedState. 636 return NOT_HANDLED; 637 } 638 } 639 } 640 641 private Inet4Address getAckOrOfferDst(@NonNull DhcpPacket request, @NonNull DhcpLease lease, 642 boolean broadcastFlag) { 643 // Unless relayed or broadcast, send to client IP if already configured on the client, or to 644 // the lease address if the client has no configured address 645 if (!isEmpty(request.mRelayIp)) { 646 return request.mRelayIp; 647 } else if (broadcastFlag) { 648 return IPV4_ADDR_ALL; 649 } else if (!isEmpty(request.mClientIp)) { 650 return request.mClientIp; 651 } else { 652 return lease.getNetAddr(); 653 } 654 } 655 656 /** 657 * Determine whether the broadcast flag should be set in the BOOTP packet flags. This does not 658 * apply to NAK responses, which should always have it set. 659 */ 660 private static boolean getBroadcastFlag(@NonNull DhcpPacket request, @NonNull DhcpLease lease) { 661 // No broadcast flag if the client already has a configured IP to unicast to. RFC2131 #4.1 662 // has some contradictions regarding broadcast behavior if a client already has an IP 663 // configured and sends a request with both ciaddr (renew/rebind) and the broadcast flag 664 // set. Sending a unicast response to ciaddr matches previous behavior and is more 665 // efficient. 666 // If the client has no configured IP, broadcast if requested by the client or if the lease 667 // address cannot be used to send a unicast reply either. 668 return isEmpty(request.mClientIp) && (request.mBroadcast || isEmpty(lease.getNetAddr())); 669 } 670 671 /** 672 * Get the hostname from a lease if non-empty and requested in the incoming request. 673 * @param request The incoming request. 674 * @return The hostname, or null if not requested or empty. 675 */ 676 @Nullable 677 private static String getHostnameIfRequested(@NonNull DhcpPacket request, 678 @NonNull DhcpLease lease) { 679 return request.hasRequestedParam(DHCP_HOST_NAME) && !TextUtils.isEmpty(lease.getHostname()) 680 ? lease.getHostname() 681 : null; 682 } 683 684 private boolean transmitOffer(@NonNull DhcpPacket request, @NonNull DhcpLease lease, 685 @NonNull MacAddress clientMac) { 686 final boolean broadcastFlag = getBroadcastFlag(request, lease); 687 final int timeout = getLeaseTimeout(lease); 688 final Inet4Address prefixMask = 689 getPrefixMaskAsInet4Address(mServingParams.serverAddr.getPrefixLength()); 690 final Inet4Address broadcastAddr = getBroadcastAddress( 691 mServingParams.getServerInet4Addr(), mServingParams.serverAddr.getPrefixLength()); 692 final String hostname = getHostnameIfRequested(request, lease); 693 final ByteBuffer offerPacket = DhcpPacket.buildOfferPacket( 694 ENCAP_BOOTP, request.mTransId, broadcastFlag, mServingParams.getServerInet4Addr(), 695 request.mRelayIp, lease.getNetAddr(), request.mClientMac, timeout, prefixMask, 696 broadcastAddr, new ArrayList<>(mServingParams.defaultRouters), 697 new ArrayList<>(mServingParams.dnsServers), 698 mServingParams.getServerInet4Addr(), null /* domainName */, hostname, 699 mServingParams.metered, (short) mServingParams.linkMtu, 700 // TODO (b/144402437): advertise the URL if known 701 null /* captivePortalApiUrl */); 702 703 return transmitOfferOrAckPacket(offerPacket, request, lease, clientMac, broadcastFlag); 704 } 705 706 private boolean transmitAck(@NonNull DhcpPacket packet, @NonNull DhcpLease lease, 707 @NonNull MacAddress clientMac) { 708 // TODO: replace DhcpPacket's build methods with real builders and use common code with 709 // transmitOffer above 710 final boolean broadcastFlag = getBroadcastFlag(packet, lease); 711 final int timeout = getLeaseTimeout(lease); 712 final String hostname = getHostnameIfRequested(packet, lease); 713 final ByteBuffer ackPacket = DhcpPacket.buildAckPacket(ENCAP_BOOTP, packet.mTransId, 714 broadcastFlag, mServingParams.getServerInet4Addr(), packet.mRelayIp, 715 lease.getNetAddr(), packet.mClientIp, packet.mClientMac, timeout, 716 mServingParams.getPrefixMaskAsAddress(), mServingParams.getBroadcastAddress(), 717 new ArrayList<>(mServingParams.defaultRouters), 718 new ArrayList<>(mServingParams.dnsServers), 719 mServingParams.getServerInet4Addr(), null /* domainName */, hostname, 720 mServingParams.metered, (short) mServingParams.linkMtu, 721 // TODO (b/144402437): advertise the URL if known 722 packet.mRapidCommit && mDhcpRapidCommitEnabled, null /* captivePortalApiUrl */); 723 724 return transmitOfferOrAckPacket(ackPacket, packet, lease, clientMac, broadcastFlag); 725 } 726 727 private boolean transmitNak(DhcpPacket request, String message) { 728 mLog.w("Transmitting NAK: " + message); 729 // Always set broadcast flag for NAK: client may not have a correct IP 730 final ByteBuffer nakPacket = DhcpPacket.buildNakPacket( 731 ENCAP_BOOTP, request.mTransId, mServingParams.getServerInet4Addr(), 732 request.mRelayIp, request.mClientMac, true /* broadcast */, message); 733 734 final Inet4Address dst = isEmpty(request.mRelayIp) 735 ? IPV4_ADDR_ALL 736 : request.mRelayIp; 737 return transmitPacket(nakPacket, DhcpNakPacket.class.getSimpleName(), dst); 738 } 739 740 private boolean transmitOfferOrAckPacket(@NonNull ByteBuffer buf, @NonNull DhcpPacket request, 741 @NonNull DhcpLease lease, @NonNull MacAddress clientMac, boolean broadcastFlag) { 742 mLog.logf("Transmitting %s with lease %s", request.getClass().getSimpleName(), lease); 743 // Client may not yet respond to ARP for the lease address, which may be the destination 744 // address. Add an entry to the ARP cache to save future ARP probes and make sure the 745 // packet reaches its destination. 746 if (!addArpEntry(clientMac, lease.getNetAddr())) { 747 // Logging for error already done 748 return false; 749 } 750 final Inet4Address dst = getAckOrOfferDst(request, lease, broadcastFlag); 751 return transmitPacket(buf, request.getClass().getSimpleName(), dst); 752 } 753 754 private boolean transmitPacket(@NonNull ByteBuffer buf, @NonNull String packetTypeTag, 755 @NonNull Inet4Address dst) { 756 try { 757 mDeps.sendPacket(mSocket, buf, dst); 758 } catch (ErrnoException | IOException e) { 759 mLog.e("Can't send packet " + packetTypeTag, e); 760 return false; 761 } 762 return true; 763 } 764 765 private boolean addArpEntry(@NonNull MacAddress macAddr, @NonNull Inet4Address inetAddr) { 766 try { 767 mDeps.addArpEntry(inetAddr, macAddr, mIfName, mSocket); 768 return true; 769 } catch (IOException e) { 770 mLog.e("Error adding client to ARP table", e); 771 return false; 772 } 773 } 774 775 /** 776 * Get the remaining lease time in seconds, starting from {@link Clock#elapsedRealtime()}. 777 * 778 * <p>This is an unsigned 32-bit integer, so it cannot be read as a standard (signed) Java int. 779 * The return value is only intended to be used to populate the lease time field in a DHCP 780 * response, considering that lease time is an unsigned 32-bit integer field in DHCP packets. 781 * 782 * <p>Lease expiration times are tracked internally with millisecond precision: this method 783 * returns a rounded down value. 784 */ 785 private int getLeaseTimeout(@NonNull DhcpLease lease) { 786 final long remainingTimeSecs = (lease.getExpTime() - mClock.elapsedRealtime()) / 1000; 787 if (remainingTimeSecs < 0) { 788 mLog.e("Processing expired lease " + lease); 789 return EXPIRED_FALLBACK_LEASE_TIME_SECS; 790 } 791 792 if (remainingTimeSecs >= toUnsignedLong(INFINITE_LEASE)) { 793 return INFINITE_LEASE; 794 } 795 796 return (int) remainingTimeSecs; 797 } 798 799 /** 800 * Get the client MAC address from a packet. 801 * 802 * @throws MalformedPacketException The address in the packet uses an unsupported format. 803 */ 804 @NonNull 805 private MacAddress getMacAddr(@NonNull DhcpPacket packet) throws MalformedPacketException { 806 try { 807 return MacAddress.fromBytes(packet.getClientMac()); 808 } catch (IllegalArgumentException e) { 809 final String message = "Invalid MAC address in packet: " 810 + HexDump.dumpHexString(packet.getClientMac()); 811 throw new MalformedPacketException(message, e); 812 } 813 } 814 815 private static boolean isEmpty(@Nullable Inet4Address address) { 816 return address == null || IPV4_ADDR_ANY.equals(address); 817 } 818 819 private class PacketListener extends DhcpPacketListener { 820 PacketListener(Handler handler) { 821 super(handler); 822 } 823 824 @Override 825 protected void onReceive(@NonNull DhcpPacket packet, @NonNull Inet4Address srcAddr, 826 int srcPort) { 827 if (srcPort != DHCP_CLIENT) { 828 final String packetType = packet.getClass().getSimpleName(); 829 mLog.logf("Ignored packet of type %s sent from client port %d", 830 packetType, srcPort); 831 return; 832 } 833 sendMessage(CMD_RECEIVE_PACKET, packet); 834 } 835 836 @Override 837 protected void logError(@NonNull String msg, Exception e) { 838 mLog.e("Error receiving packet: " + msg, e); 839 } 840 841 @Override 842 protected void logParseError(@NonNull byte[] packet, int length, 843 @NonNull DhcpPacket.ParseException e) { 844 mLog.e("Error parsing packet", e); 845 } 846 847 @Override 848 protected FileDescriptor createFd() { 849 // TODO: have and use an API to set a socket tag without going through the thread tag 850 final int oldTag = TrafficStats.getAndSetThreadStatsTag(TAG_SYSTEM_DHCP_SERVER); 851 try { 852 mSocket = Os.socket(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK, IPPROTO_UDP); 853 SocketUtils.bindSocketToInterface(mSocket, mIfName); 854 Os.setsockoptInt(mSocket, SOL_SOCKET, SO_REUSEADDR, 1); 855 Os.setsockoptInt(mSocket, SOL_SOCKET, SO_BROADCAST, 1); 856 Os.bind(mSocket, IPV4_ADDR_ANY, DHCP_SERVER); 857 858 return mSocket; 859 } catch (IOException | ErrnoException e) { 860 mLog.e("Error creating UDP socket", e); 861 return null; 862 } finally { 863 TrafficStats.setThreadStatsTag(oldTag); 864 } 865 } 866 } 867 } 868