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