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