• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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