1 /*
2  * Copyright (C) 2016 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.ip;
18 
19 import com.android.internal.util.MessageUtils;
20 import com.android.internal.util.WakeupMessage;
21 
22 import android.content.Context;
23 import android.net.apf.ApfCapabilities;
24 import android.net.apf.ApfFilter;
25 import android.net.DhcpResults;
26 import android.net.InterfaceConfiguration;
27 import android.net.LinkAddress;
28 import android.net.LinkProperties;
29 import android.net.LinkProperties.ProvisioningChange;
30 import android.net.ProxyInfo;
31 import android.net.RouteInfo;
32 import android.net.StaticIpConfiguration;
33 import android.net.dhcp.DhcpClient;
34 import android.net.metrics.IpManagerEvent;
35 import android.os.INetworkManagementService;
36 import android.os.Message;
37 import android.os.RemoteException;
38 import android.os.ServiceManager;
39 import android.os.SystemClock;
40 import android.text.TextUtils;
41 import android.util.LocalLog;
42 import android.util.Log;
43 import android.util.SparseArray;
44 
45 import com.android.internal.annotations.VisibleForTesting;
46 import com.android.internal.util.IndentingPrintWriter;
47 import com.android.internal.util.State;
48 import com.android.internal.util.StateMachine;
49 import com.android.server.net.NetlinkTracker;
50 
51 import java.io.FileDescriptor;
52 import java.io.PrintWriter;
53 import java.net.InetAddress;
54 import java.net.NetworkInterface;
55 import java.net.SocketException;
56 import java.util.Objects;
57 import java.util.StringJoiner;
58 
59 
60 /**
61  * IpManager
62  *
63  * This class provides the interface to IP-layer provisioning and maintenance
64  * functionality that can be used by transport layers like Wi-Fi, Ethernet,
65  * et cetera.
66  *
67  * [ Lifetime ]
68  * IpManager is designed to be instantiated as soon as the interface name is
69  * known and can be as long-lived as the class containing it (i.e. declaring
70  * it "private final" is okay).
71  *
72  * @hide
73  */
74 public class IpManager extends StateMachine {
75     private static final boolean DBG = false;
76     private static final boolean VDBG = false;
77 
78     // For message logging.
79     private static final Class[] sMessageClasses = { IpManager.class, DhcpClient.class };
80     private static final SparseArray<String> sWhatToString =
81             MessageUtils.findMessageNames(sMessageClasses);
82 
83     /**
84      * Callbacks for handling IpManager events.
85      */
86     public static class Callback {
87         // In order to receive onPreDhcpAction(), call #withPreDhcpAction()
88         // when constructing a ProvisioningConfiguration.
89         //
90         // Implementations of onPreDhcpAction() must call
91         // IpManager#completedPreDhcpAction() to indicate that DHCP is clear
92         // to proceed.
onPreDhcpAction()93         public void onPreDhcpAction() {}
onPostDhcpAction()94         public void onPostDhcpAction() {}
95 
96         // This is purely advisory and not an indication of provisioning
97         // success or failure.  This is only here for callers that want to
98         // expose DHCPv4 results to other APIs (e.g., WifiInfo#setInetAddress).
99         // DHCPv4 or static IPv4 configuration failure or success can be
100         // determined by whether or not the passed-in DhcpResults object is
101         // null or not.
onNewDhcpResults(DhcpResults dhcpResults)102         public void onNewDhcpResults(DhcpResults dhcpResults) {}
103 
onProvisioningSuccess(LinkProperties newLp)104         public void onProvisioningSuccess(LinkProperties newLp) {}
onProvisioningFailure(LinkProperties newLp)105         public void onProvisioningFailure(LinkProperties newLp) {}
106 
107         // Invoked on LinkProperties changes.
onLinkPropertiesChange(LinkProperties newLp)108         public void onLinkPropertiesChange(LinkProperties newLp) {}
109 
110         // Called when the internal IpReachabilityMonitor (if enabled) has
111         // detected the loss of a critical number of required neighbors.
onReachabilityLost(String logMsg)112         public void onReachabilityLost(String logMsg) {}
113 
114         // Called when the IpManager state machine terminates.
onQuit()115         public void onQuit() {}
116 
117         // Install an APF program to filter incoming packets.
installPacketFilter(byte[] filter)118         public void installPacketFilter(byte[] filter) {}
119 
120         // If multicast filtering cannot be accomplished with APF, this function will be called to
121         // actuate multicast filtering using another means.
setFallbackMulticastFilter(boolean enabled)122         public void setFallbackMulticastFilter(boolean enabled) {}
123 
124         // Enabled/disable Neighbor Discover offload functionality. This is
125         // called, for example, whenever 464xlat is being started or stopped.
setNeighborDiscoveryOffload(boolean enable)126         public void setNeighborDiscoveryOffload(boolean enable) {}
127     }
128 
129     public static class WaitForProvisioningCallback extends Callback {
130         private LinkProperties mCallbackLinkProperties;
131 
waitForProvisioning()132         public LinkProperties waitForProvisioning() {
133             synchronized (this) {
134                 try {
135                     wait();
136                 } catch (InterruptedException e) {}
137                 return mCallbackLinkProperties;
138             }
139         }
140 
141         @Override
onProvisioningSuccess(LinkProperties newLp)142         public void onProvisioningSuccess(LinkProperties newLp) {
143             synchronized (this) {
144                 mCallbackLinkProperties = newLp;
145                 notify();
146             }
147         }
148 
149         @Override
onProvisioningFailure(LinkProperties newLp)150         public void onProvisioningFailure(LinkProperties newLp) {
151             synchronized (this) {
152                 mCallbackLinkProperties = null;
153                 notify();
154             }
155         }
156     }
157 
158     // Use a wrapper class to log in order to ensure complete and detailed
159     // logging. This method is lighter weight than annotations/reflection
160     // and has the following benefits:
161     //
162     //     - No invoked method can be forgotten.
163     //       Any new method added to IpManager.Callback must be overridden
164     //       here or it will never be called.
165     //
166     //     - No invoking call site can be forgotten.
167     //       Centralized logging in this way means call sites don't need to
168     //       remember to log, and therefore no call site can be forgotten.
169     //
170     //     - No variation in log format among call sites.
171     //       Encourages logging of any available arguments, and all call sites
172     //       are necessarily logged identically.
173     //
174     // TODO: Find an lighter weight approach.
175     private class LoggingCallbackWrapper extends Callback {
176         private static final String PREFIX = "INVOKE ";
177         private Callback mCallback;
178 
LoggingCallbackWrapper(Callback callback)179         public LoggingCallbackWrapper(Callback callback) {
180             mCallback = callback;
181         }
182 
log(String msg)183         private void log(String msg) {
184             mLocalLog.log(PREFIX + msg);
185         }
186 
187         @Override
onPreDhcpAction()188         public void onPreDhcpAction() {
189             mCallback.onPreDhcpAction();
190             log("onPreDhcpAction()");
191         }
192         @Override
onPostDhcpAction()193         public void onPostDhcpAction() {
194             mCallback.onPostDhcpAction();
195             log("onPostDhcpAction()");
196         }
197         @Override
onNewDhcpResults(DhcpResults dhcpResults)198         public void onNewDhcpResults(DhcpResults dhcpResults) {
199             mCallback.onNewDhcpResults(dhcpResults);
200             log("onNewDhcpResults({" + dhcpResults + "})");
201         }
202         @Override
onProvisioningSuccess(LinkProperties newLp)203         public void onProvisioningSuccess(LinkProperties newLp) {
204             mCallback.onProvisioningSuccess(newLp);
205             log("onProvisioningSuccess({" + newLp + "})");
206         }
207         @Override
onProvisioningFailure(LinkProperties newLp)208         public void onProvisioningFailure(LinkProperties newLp) {
209             mCallback.onProvisioningFailure(newLp);
210             log("onProvisioningFailure({" + newLp + "})");
211         }
212         @Override
onLinkPropertiesChange(LinkProperties newLp)213         public void onLinkPropertiesChange(LinkProperties newLp) {
214             mCallback.onLinkPropertiesChange(newLp);
215             log("onLinkPropertiesChange({" + newLp + "})");
216         }
217         @Override
onReachabilityLost(String logMsg)218         public void onReachabilityLost(String logMsg) {
219             mCallback.onReachabilityLost(logMsg);
220             log("onReachabilityLost(" + logMsg + ")");
221         }
222         @Override
onQuit()223         public void onQuit() {
224             mCallback.onQuit();
225             log("onQuit()");
226         }
227         @Override
installPacketFilter(byte[] filter)228         public void installPacketFilter(byte[] filter) {
229             mCallback.installPacketFilter(filter);
230             log("installPacketFilter(byte[" + filter.length + "])");
231         }
232         @Override
setFallbackMulticastFilter(boolean enabled)233         public void setFallbackMulticastFilter(boolean enabled) {
234             mCallback.setFallbackMulticastFilter(enabled);
235             log("setFallbackMulticastFilter(" + enabled + ")");
236         }
237         @Override
setNeighborDiscoveryOffload(boolean enable)238         public void setNeighborDiscoveryOffload(boolean enable) {
239             mCallback.setNeighborDiscoveryOffload(enable);
240             log("setNeighborDiscoveryOffload(" + enable + ")");
241         }
242     }
243 
244     /**
245      * This class encapsulates parameters to be passed to
246      * IpManager#startProvisioning(). A defensive copy is made by IpManager
247      * and the values specified herein are in force until IpManager#stop()
248      * is called.
249      *
250      * Example use:
251      *
252      *     final ProvisioningConfiguration config =
253      *             mIpManager.buildProvisioningConfiguration()
254      *                     .withPreDhcpAction()
255      *                     .withProvisioningTimeoutMs(36 * 1000)
256      *                     .build();
257      *     mIpManager.startProvisioning(config);
258      *     ...
259      *     mIpManager.stop();
260      *
261      * The specified provisioning configuration will only be active until
262      * IpManager#stop() is called. Future calls to IpManager#startProvisioning()
263      * must specify the configuration again.
264      */
265     public static class ProvisioningConfiguration {
266         // TODO: Delete this default timeout once those callers that care are
267         // fixed to pass in their preferred timeout.
268         //
269         // We pick 36 seconds so we can send DHCP requests at
270         //
271         //     t=0, t=2, t=6, t=14, t=30
272         //
273         // allowing for 10% jitter.
274         private static final int DEFAULT_TIMEOUT_MS = 36 * 1000;
275 
276         public static class Builder {
277             private ProvisioningConfiguration mConfig = new ProvisioningConfiguration();
278 
withoutIPv4()279             public Builder withoutIPv4() {
280                 mConfig.mEnableIPv4 = false;
281                 return this;
282             }
283 
withoutIPv6()284             public Builder withoutIPv6() {
285                 mConfig.mEnableIPv6 = false;
286                 return this;
287             }
288 
withoutIpReachabilityMonitor()289             public Builder withoutIpReachabilityMonitor() {
290                 mConfig.mUsingIpReachabilityMonitor = false;
291                 return this;
292             }
293 
withPreDhcpAction()294             public Builder withPreDhcpAction() {
295                 mConfig.mRequestedPreDhcpActionMs = DEFAULT_TIMEOUT_MS;
296                 return this;
297             }
298 
withPreDhcpAction(int dhcpActionTimeoutMs)299             public Builder withPreDhcpAction(int dhcpActionTimeoutMs) {
300                 mConfig.mRequestedPreDhcpActionMs = dhcpActionTimeoutMs;
301                 return this;
302             }
303 
withStaticConfiguration(StaticIpConfiguration staticConfig)304             public Builder withStaticConfiguration(StaticIpConfiguration staticConfig) {
305                 mConfig.mStaticIpConfig = staticConfig;
306                 return this;
307             }
308 
withApfCapabilities(ApfCapabilities apfCapabilities)309             public Builder withApfCapabilities(ApfCapabilities apfCapabilities) {
310                 mConfig.mApfCapabilities = apfCapabilities;
311                 return this;
312             }
313 
withProvisioningTimeoutMs(int timeoutMs)314             public Builder withProvisioningTimeoutMs(int timeoutMs) {
315                 mConfig.mProvisioningTimeoutMs = timeoutMs;
316                 return this;
317             }
318 
build()319             public ProvisioningConfiguration build() {
320                 return new ProvisioningConfiguration(mConfig);
321             }
322         }
323 
324         /* package */ boolean mEnableIPv4 = true;
325         /* package */ boolean mEnableIPv6 = true;
326         /* package */ boolean mUsingIpReachabilityMonitor = true;
327         /* package */ int mRequestedPreDhcpActionMs;
328         /* package */ StaticIpConfiguration mStaticIpConfig;
329         /* package */ ApfCapabilities mApfCapabilities;
330         /* package */ int mProvisioningTimeoutMs = DEFAULT_TIMEOUT_MS;
331 
ProvisioningConfiguration()332         public ProvisioningConfiguration() {}
333 
ProvisioningConfiguration(ProvisioningConfiguration other)334         public ProvisioningConfiguration(ProvisioningConfiguration other) {
335             mEnableIPv4 = other.mEnableIPv4;
336             mEnableIPv6 = other.mEnableIPv6;
337             mUsingIpReachabilityMonitor = other.mUsingIpReachabilityMonitor;
338             mRequestedPreDhcpActionMs = other.mRequestedPreDhcpActionMs;
339             mStaticIpConfig = other.mStaticIpConfig;
340             mApfCapabilities = other.mApfCapabilities;
341             mProvisioningTimeoutMs = other.mProvisioningTimeoutMs;
342         }
343 
344         @Override
toString()345         public String toString() {
346             return new StringJoiner(", ", getClass().getSimpleName() + "{", "}")
347                     .add("mEnableIPv4: " + mEnableIPv4)
348                     .add("mEnableIPv6: " + mEnableIPv6)
349                     .add("mUsingIpReachabilityMonitor: " + mUsingIpReachabilityMonitor)
350                     .add("mRequestedPreDhcpActionMs: " + mRequestedPreDhcpActionMs)
351                     .add("mStaticIpConfig: " + mStaticIpConfig)
352                     .add("mApfCapabilities: " + mApfCapabilities)
353                     .add("mProvisioningTimeoutMs: " + mProvisioningTimeoutMs)
354                     .toString();
355         }
356     }
357 
358     public static final String DUMP_ARG = "ipmanager";
359 
360     private static final int CMD_STOP = 1;
361     private static final int CMD_START = 2;
362     private static final int CMD_CONFIRM = 3;
363     private static final int EVENT_PRE_DHCP_ACTION_COMPLETE = 4;
364     // Sent by NetlinkTracker to communicate netlink events.
365     private static final int EVENT_NETLINK_LINKPROPERTIES_CHANGED = 5;
366     private static final int CMD_UPDATE_TCP_BUFFER_SIZES = 6;
367     private static final int CMD_UPDATE_HTTP_PROXY = 7;
368     private static final int CMD_SET_MULTICAST_FILTER = 8;
369     private static final int EVENT_PROVISIONING_TIMEOUT = 9;
370     private static final int EVENT_DHCPACTION_TIMEOUT = 10;
371 
372     private static final int MAX_LOG_RECORDS = 500;
373 
374     private static final boolean NO_CALLBACKS = false;
375     private static final boolean SEND_CALLBACKS = true;
376 
377     // This must match the interface prefix in clatd.c.
378     // TODO: Revert this hack once IpManager and Nat464Xlat work in concert.
379     private static final String CLAT_PREFIX = "v4-";
380 
381     private final State mStoppedState = new StoppedState();
382     private final State mStoppingState = new StoppingState();
383     private final State mStartedState = new StartedState();
384 
385     private final String mTag;
386     private final Context mContext;
387     private final String mInterfaceName;
388     private final String mClatInterfaceName;
389     @VisibleForTesting
390     protected final Callback mCallback;
391     private final INetworkManagementService mNwService;
392     private final NetlinkTracker mNetlinkTracker;
393     private final WakeupMessage mProvisioningTimeoutAlarm;
394     private final WakeupMessage mDhcpActionTimeoutAlarm;
395     private final LocalLog mLocalLog;
396 
397     private NetworkInterface mNetworkInterface;
398 
399     /**
400      * Non-final member variables accessed only from within our StateMachine.
401      */
402     private LinkProperties mLinkProperties;
403     private ProvisioningConfiguration mConfiguration;
404     private IpReachabilityMonitor mIpReachabilityMonitor;
405     private DhcpClient mDhcpClient;
406     private DhcpResults mDhcpResults;
407     private String mTcpBufferSizes;
408     private ProxyInfo mHttpProxy;
409     private ApfFilter mApfFilter;
410     private boolean mMulticastFiltering;
411     private long mStartTimeMillis;
412 
IpManager(Context context, String ifName, Callback callback)413     public IpManager(Context context, String ifName, Callback callback)
414                 throws IllegalArgumentException {
415         this(context, ifName, callback, INetworkManagementService.Stub.asInterface(
416                 ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE)));
417     }
418 
419     /**
420      * An expanded constructor, useful for dependency injection.
421      */
IpManager(Context context, String ifName, Callback callback, INetworkManagementService nwService)422     public IpManager(Context context, String ifName, Callback callback,
423             INetworkManagementService nwService) throws IllegalArgumentException {
424         super(IpManager.class.getSimpleName() + "." + ifName);
425         mTag = getName();
426 
427         mContext = context;
428         mInterfaceName = ifName;
429         mClatInterfaceName = CLAT_PREFIX + ifName;
430         mCallback = new LoggingCallbackWrapper(callback);
431         mNwService = nwService;
432 
433         mNetlinkTracker = new NetlinkTracker(
434                 mInterfaceName,
435                 new NetlinkTracker.Callback() {
436                     @Override
437                     public void update() {
438                         sendMessage(EVENT_NETLINK_LINKPROPERTIES_CHANGED);
439                     }
440                 }) {
441             @Override
442             public void interfaceAdded(String iface) {
443                 super.interfaceAdded(iface);
444                 if (mClatInterfaceName.equals(iface)) {
445                     mCallback.setNeighborDiscoveryOffload(false);
446                 }
447             }
448 
449             @Override
450             public void interfaceRemoved(String iface) {
451                 super.interfaceRemoved(iface);
452                 if (mClatInterfaceName.equals(iface)) {
453                     // TODO: consider sending a message to the IpManager main
454                     // StateMachine thread, in case "NDO enabled" state becomes
455                     // tied to more things that 464xlat operation.
456                     mCallback.setNeighborDiscoveryOffload(true);
457                 }
458             }
459         };
460 
461         try {
462             mNwService.registerObserver(mNetlinkTracker);
463         } catch (RemoteException e) {
464             Log.e(mTag, "Couldn't register NetlinkTracker: " + e.toString());
465         }
466 
467         resetLinkProperties();
468 
469         mProvisioningTimeoutAlarm = new WakeupMessage(mContext, getHandler(),
470                 mTag + ".EVENT_PROVISIONING_TIMEOUT", EVENT_PROVISIONING_TIMEOUT);
471         mDhcpActionTimeoutAlarm = new WakeupMessage(mContext, getHandler(),
472                 mTag + ".EVENT_DHCPACTION_TIMEOUT", EVENT_DHCPACTION_TIMEOUT);
473 
474         // Super simple StateMachine.
475         addState(mStoppedState);
476         addState(mStartedState);
477         addState(mStoppingState);
478 
479         setInitialState(mStoppedState);
480         mLocalLog = new LocalLog(MAX_LOG_RECORDS);
481         super.start();
482     }
483 
484     @Override
onQuitting()485     protected void onQuitting() {
486         mCallback.onQuit();
487     }
488 
489     // Shut down this IpManager instance altogether.
shutdown()490     public void shutdown() {
491         stop();
492         quit();
493     }
494 
buildProvisioningConfiguration()495     public static ProvisioningConfiguration.Builder buildProvisioningConfiguration() {
496         return new ProvisioningConfiguration.Builder();
497     }
498 
startProvisioning(ProvisioningConfiguration req)499     public void startProvisioning(ProvisioningConfiguration req) {
500         getNetworkInterface();
501 
502         mCallback.setNeighborDiscoveryOffload(true);
503         sendMessage(CMD_START, new ProvisioningConfiguration(req));
504     }
505 
506     // TODO: Delete this.
startProvisioning(StaticIpConfiguration staticIpConfig)507     public void startProvisioning(StaticIpConfiguration staticIpConfig) {
508         startProvisioning(buildProvisioningConfiguration()
509                 .withStaticConfiguration(staticIpConfig)
510                 .build());
511     }
512 
startProvisioning()513     public void startProvisioning() {
514         startProvisioning(new ProvisioningConfiguration());
515     }
516 
stop()517     public void stop() {
518         sendMessage(CMD_STOP);
519     }
520 
confirmConfiguration()521     public void confirmConfiguration() {
522         sendMessage(CMD_CONFIRM);
523     }
524 
completedPreDhcpAction()525     public void completedPreDhcpAction() {
526         sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE);
527     }
528 
529     /**
530      * Set the TCP buffer sizes to use.
531      *
532      * This may be called, repeatedly, at any time before or after a call to
533      * #startProvisioning(). The setting is cleared upon calling #stop().
534      */
setTcpBufferSizes(String tcpBufferSizes)535     public void setTcpBufferSizes(String tcpBufferSizes) {
536         sendMessage(CMD_UPDATE_TCP_BUFFER_SIZES, tcpBufferSizes);
537     }
538 
539     /**
540      * Set the HTTP Proxy configuration to use.
541      *
542      * This may be called, repeatedly, at any time before or after a call to
543      * #startProvisioning(). The setting is cleared upon calling #stop().
544      */
setHttpProxy(ProxyInfo proxyInfo)545     public void setHttpProxy(ProxyInfo proxyInfo) {
546         sendMessage(CMD_UPDATE_HTTP_PROXY, proxyInfo);
547     }
548 
549     /**
550      * Enable or disable the multicast filter.  Attempts to use APF to accomplish the filtering,
551      * if not, Callback.setFallbackMulticastFilter() is called.
552      */
setMulticastFilter(boolean enabled)553     public void setMulticastFilter(boolean enabled) {
554         sendMessage(CMD_SET_MULTICAST_FILTER, enabled);
555     }
556 
dump(FileDescriptor fd, PrintWriter writer, String[] args)557     public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
558         IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
559         pw.println("APF dump:");
560         pw.increaseIndent();
561         // Thread-unsafe access to mApfFilter but just used for debugging.
562         ApfFilter apfFilter = mApfFilter;
563         if (apfFilter != null) {
564             apfFilter.dump(pw);
565         } else {
566             pw.println("No apf support");
567         }
568         pw.decreaseIndent();
569 
570         pw.println();
571         pw.println("StateMachine dump:");
572         pw.increaseIndent();
573         mLocalLog.readOnlyLocalLog().dump(fd, pw, args);
574         pw.decreaseIndent();
575     }
576 
577 
578     /**
579      * Internals.
580      */
581 
582     @Override
getWhatToString(int what)583     protected String getWhatToString(int what) {
584         return sWhatToString.get(what, "UNKNOWN: " + Integer.toString(what));
585     }
586 
587     @Override
getLogRecString(Message msg)588     protected String getLogRecString(Message msg) {
589         final String logLine = String.format(
590                 "%s/%d %d %d %s",
591                 mInterfaceName, mNetworkInterface == null ? -1 : mNetworkInterface.getIndex(),
592                 msg.arg1, msg.arg2, Objects.toString(msg.obj));
593 
594         final String richerLogLine = getWhatToString(msg.what) + " " + logLine;
595         mLocalLog.log(richerLogLine);
596         if (VDBG) {
597             Log.d(mTag, richerLogLine);
598         }
599 
600         return logLine;
601     }
602 
603     @Override
recordLogRec(Message msg)604     protected boolean recordLogRec(Message msg) {
605         // Don't log EVENT_NETLINK_LINKPROPERTIES_CHANGED. They can be noisy,
606         // and we already log any LinkProperties change that results in an
607         // invocation of IpManager.Callback#onLinkPropertiesChange().
608         return (msg.what != EVENT_NETLINK_LINKPROPERTIES_CHANGED);
609     }
610 
getNetworkInterface()611     private void getNetworkInterface() {
612         try {
613             mNetworkInterface = NetworkInterface.getByName(mInterfaceName);
614         } catch (SocketException | NullPointerException e) {
615             // TODO: throw new IllegalStateException.
616             Log.e(mTag, "ALERT: Failed to get interface object: ", e);
617         }
618     }
619 
620     // This needs to be called with care to ensure that our LinkProperties
621     // are in sync with the actual LinkProperties of the interface. For example,
622     // we should only call this if we know for sure that there are no IP addresses
623     // assigned to the interface, etc.
resetLinkProperties()624     private void resetLinkProperties() {
625         mNetlinkTracker.clearLinkProperties();
626         mConfiguration = null;
627         mDhcpResults = null;
628         mTcpBufferSizes = "";
629         mHttpProxy = null;
630 
631         mLinkProperties = new LinkProperties();
632         mLinkProperties.setInterfaceName(mInterfaceName);
633     }
634 
recordMetric(final int type)635     private void recordMetric(final int type) {
636         if (mStartTimeMillis <= 0) { Log.wtf(mTag, "Start time undefined!"); }
637         IpManagerEvent.logEvent(type, mInterfaceName,
638                 SystemClock.elapsedRealtime() - mStartTimeMillis);
639     }
640 
641     // For now: use WifiStateMachine's historical notion of provisioned.
isProvisioned(LinkProperties lp)642     private static boolean isProvisioned(LinkProperties lp) {
643         // For historical reasons, we should connect even if all we have is
644         // an IPv4 address and nothing else.
645         return lp.isProvisioned() || lp.hasIPv4Address();
646     }
647 
648     // TODO: Investigate folding all this into the existing static function
649     // LinkProperties.compareProvisioning() or some other single function that
650     // takes two LinkProperties objects and returns a ProvisioningChange
651     // object that is a correct and complete assessment of what changed, taking
652     // account of the asymmetries described in the comments in this function.
653     // Then switch to using it everywhere (IpReachabilityMonitor, etc.).
compareProvisioning( LinkProperties oldLp, LinkProperties newLp)654     private static ProvisioningChange compareProvisioning(
655             LinkProperties oldLp, LinkProperties newLp) {
656         ProvisioningChange delta;
657 
658         final boolean wasProvisioned = isProvisioned(oldLp);
659         final boolean isProvisioned = isProvisioned(newLp);
660 
661         if (!wasProvisioned && isProvisioned) {
662             delta = ProvisioningChange.GAINED_PROVISIONING;
663         } else if (wasProvisioned && isProvisioned) {
664             delta = ProvisioningChange.STILL_PROVISIONED;
665         } else if (!wasProvisioned && !isProvisioned) {
666             delta = ProvisioningChange.STILL_NOT_PROVISIONED;
667         } else {
668             // (wasProvisioned && !isProvisioned)
669             //
670             // Note that this is true even if we lose a configuration element
671             // (e.g., a default gateway) that would not be required to advance
672             // into provisioned state. This is intended: if we have a default
673             // router and we lose it, that's a sure sign of a problem, but if
674             // we connect to a network with no IPv4 DNS servers, we consider
675             // that to be a network without DNS servers and connect anyway.
676             //
677             // See the comment below.
678             delta = ProvisioningChange.LOST_PROVISIONING;
679         }
680 
681         // Additionally:
682         //
683         // Partial configurations (e.g., only an IPv4 address with no DNS
684         // servers and no default route) are accepted as long as DHCPv4
685         // succeeds. On such a network, isProvisioned() will always return
686         // false, because the configuration is not complete, but we want to
687         // connect anyway. It might be a disconnected network such as a
688         // Chromecast or a wireless printer, for example.
689         //
690         // Because on such a network isProvisioned() will always return false,
691         // delta will never be LOST_PROVISIONING. So check for loss of
692         // provisioning here too.
693         if ((oldLp.hasIPv4Address() && !newLp.hasIPv4Address()) ||
694                 (oldLp.isIPv6Provisioned() && !newLp.isIPv6Provisioned())) {
695             delta = ProvisioningChange.LOST_PROVISIONING;
696         }
697 
698         // Additionally:
699         //
700         // If the previous link properties had a global IPv6 address and an
701         // IPv6 default route then also consider the loss of that default route
702         // to be a loss of provisioning. See b/27962810.
703         if (oldLp.hasGlobalIPv6Address() && oldLp.hasIPv6DefaultRoute() &&
704                 !newLp.hasIPv6DefaultRoute()) {
705             delta = ProvisioningChange.LOST_PROVISIONING;
706         }
707 
708         return delta;
709     }
710 
dispatchCallback(ProvisioningChange delta, LinkProperties newLp)711     private void dispatchCallback(ProvisioningChange delta, LinkProperties newLp) {
712         switch (delta) {
713             case GAINED_PROVISIONING:
714                 if (VDBG) { Log.d(mTag, "onProvisioningSuccess()"); }
715                 recordMetric(IpManagerEvent.PROVISIONING_OK);
716                 mCallback.onProvisioningSuccess(newLp);
717                 break;
718 
719             case LOST_PROVISIONING:
720                 if (VDBG) { Log.d(mTag, "onProvisioningFailure()"); }
721                 recordMetric(IpManagerEvent.PROVISIONING_FAIL);
722                 mCallback.onProvisioningFailure(newLp);
723                 break;
724 
725             default:
726                 if (VDBG) { Log.d(mTag, "onLinkPropertiesChange()"); }
727                 mCallback.onLinkPropertiesChange(newLp);
728                 break;
729         }
730     }
731 
732     // Updates all IpManager-related state concerned with LinkProperties.
733     // Returns a ProvisioningChange for possibly notifying other interested
734     // parties that are not fronted by IpManager.
setLinkProperties(LinkProperties newLp)735     private ProvisioningChange setLinkProperties(LinkProperties newLp) {
736         if (mApfFilter != null) {
737             mApfFilter.setLinkProperties(newLp);
738         }
739         if (mIpReachabilityMonitor != null) {
740             mIpReachabilityMonitor.updateLinkProperties(newLp);
741         }
742 
743         ProvisioningChange delta = compareProvisioning(mLinkProperties, newLp);
744         mLinkProperties = new LinkProperties(newLp);
745 
746         if (delta == ProvisioningChange.GAINED_PROVISIONING) {
747             // TODO: Add a proper ProvisionedState and cancel the alarm in
748             // its enter() method.
749             mProvisioningTimeoutAlarm.cancel();
750         }
751 
752         return delta;
753     }
754 
linkPropertiesUnchanged(LinkProperties newLp)755     private boolean linkPropertiesUnchanged(LinkProperties newLp) {
756         return Objects.equals(newLp, mLinkProperties);
757     }
758 
assembleLinkProperties()759     private LinkProperties assembleLinkProperties() {
760         // [1] Create a new LinkProperties object to populate.
761         LinkProperties newLp = new LinkProperties();
762         newLp.setInterfaceName(mInterfaceName);
763 
764         // [2] Pull in data from netlink:
765         //         - IPv4 addresses
766         //         - IPv6 addresses
767         //         - IPv6 routes
768         //         - IPv6 DNS servers
769         LinkProperties netlinkLinkProperties = mNetlinkTracker.getLinkProperties();
770         newLp.setLinkAddresses(netlinkLinkProperties.getLinkAddresses());
771         for (RouteInfo route : netlinkLinkProperties.getRoutes()) {
772             newLp.addRoute(route);
773         }
774         for (InetAddress dns : netlinkLinkProperties.getDnsServers()) {
775             // Only add likely reachable DNS servers.
776             // TODO: investigate deleting this.
777             if (newLp.isReachable(dns)) {
778                 newLp.addDnsServer(dns);
779             }
780         }
781 
782         // [3] Add in data from DHCPv4, if available.
783         //
784         // mDhcpResults is never shared with any other owner so we don't have
785         // to worry about concurrent modification.
786         if (mDhcpResults != null) {
787             for (RouteInfo route : mDhcpResults.getRoutes(mInterfaceName)) {
788                 newLp.addRoute(route);
789             }
790             for (InetAddress dns : mDhcpResults.dnsServers) {
791                 // Only add likely reachable DNS servers.
792                 // TODO: investigate deleting this.
793                 if (newLp.isReachable(dns)) {
794                     newLp.addDnsServer(dns);
795                 }
796             }
797             newLp.setDomains(mDhcpResults.domains);
798 
799             if (mDhcpResults.mtu != 0) {
800                 newLp.setMtu(mDhcpResults.mtu);
801             }
802         }
803 
804         // [4] Add in TCP buffer sizes and HTTP Proxy config, if available.
805         if (!TextUtils.isEmpty(mTcpBufferSizes)) {
806             newLp.setTcpBufferSizes(mTcpBufferSizes);
807         }
808         if (mHttpProxy != null) {
809             newLp.setHttpProxy(mHttpProxy);
810         }
811 
812         if (VDBG) {
813             Log.d(mTag, "newLp{" + newLp + "}");
814         }
815         return newLp;
816     }
817 
818     // Returns false if we have lost provisioning, true otherwise.
handleLinkPropertiesUpdate(boolean sendCallbacks)819     private boolean handleLinkPropertiesUpdate(boolean sendCallbacks) {
820         final LinkProperties newLp = assembleLinkProperties();
821         if (linkPropertiesUnchanged(newLp)) {
822             return true;
823         }
824         final ProvisioningChange delta = setLinkProperties(newLp);
825         if (sendCallbacks) {
826             dispatchCallback(delta, newLp);
827         }
828         return (delta != ProvisioningChange.LOST_PROVISIONING);
829     }
830 
setIPv4Address(LinkAddress address)831     private boolean setIPv4Address(LinkAddress address) {
832         final InterfaceConfiguration ifcg = new InterfaceConfiguration();
833         ifcg.setLinkAddress(address);
834         try {
835             mNwService.setInterfaceConfig(mInterfaceName, ifcg);
836             if (VDBG) Log.d(mTag, "IPv4 configuration succeeded");
837         } catch (IllegalStateException | RemoteException e) {
838             Log.e(mTag, "IPv4 configuration failed: ", e);
839             return false;
840         }
841         return true;
842     }
843 
clearIPv4Address()844     private void clearIPv4Address() {
845         try {
846             final InterfaceConfiguration ifcg = new InterfaceConfiguration();
847             ifcg.setLinkAddress(new LinkAddress("0.0.0.0/0"));
848             mNwService.setInterfaceConfig(mInterfaceName, ifcg);
849         } catch (IllegalStateException | RemoteException e) {
850             Log.e(mTag, "ALERT: Failed to clear IPv4 address on interface " + mInterfaceName, e);
851         }
852     }
853 
handleIPv4Success(DhcpResults dhcpResults)854     private void handleIPv4Success(DhcpResults dhcpResults) {
855         mDhcpResults = new DhcpResults(dhcpResults);
856         final LinkProperties newLp = assembleLinkProperties();
857         final ProvisioningChange delta = setLinkProperties(newLp);
858 
859         if (VDBG) {
860             Log.d(mTag, "onNewDhcpResults(" + Objects.toString(dhcpResults) + ")");
861         }
862         mCallback.onNewDhcpResults(dhcpResults);
863         dispatchCallback(delta, newLp);
864     }
865 
handleIPv4Failure()866     private void handleIPv4Failure() {
867         // TODO: Investigate deleting this clearIPv4Address() call.
868         //
869         // DhcpClient will send us CMD_CLEAR_LINKADDRESS in all circumstances
870         // that could trigger a call to this function. If we missed handling
871         // that message in StartedState for some reason we would still clear
872         // any addresses upon entry to StoppedState.
873         clearIPv4Address();
874         mDhcpResults = null;
875         if (VDBG) { Log.d(mTag, "onNewDhcpResults(null)"); }
876         mCallback.onNewDhcpResults(null);
877 
878         handleProvisioningFailure();
879     }
880 
handleProvisioningFailure()881     private void handleProvisioningFailure() {
882         final LinkProperties newLp = assembleLinkProperties();
883         ProvisioningChange delta = setLinkProperties(newLp);
884         // If we've gotten here and we're still not provisioned treat that as
885         // a total loss of provisioning.
886         //
887         // Either (a) static IP configuration failed or (b) DHCPv4 failed AND
888         // there was no usable IPv6 obtained before a non-zero provisioning
889         // timeout expired.
890         //
891         // Regardless: GAME OVER.
892         if (delta == ProvisioningChange.STILL_NOT_PROVISIONED) {
893             delta = ProvisioningChange.LOST_PROVISIONING;
894         }
895 
896         dispatchCallback(delta, newLp);
897         if (delta == ProvisioningChange.LOST_PROVISIONING) {
898             transitionTo(mStoppingState);
899         }
900     }
901 
startIPv4()902     private boolean startIPv4() {
903         // If we have a StaticIpConfiguration attempt to apply it and
904         // handle the result accordingly.
905         if (mConfiguration.mStaticIpConfig != null) {
906             if (setIPv4Address(mConfiguration.mStaticIpConfig.ipAddress)) {
907                 handleIPv4Success(new DhcpResults(mConfiguration.mStaticIpConfig));
908             } else {
909                 if (VDBG) { Log.d(mTag, "onProvisioningFailure()"); }
910                 recordMetric(IpManagerEvent.PROVISIONING_FAIL);
911                 mCallback.onProvisioningFailure(new LinkProperties(mLinkProperties));
912                 return false;
913             }
914         } else {
915             // Start DHCPv4.
916             mDhcpClient = DhcpClient.makeDhcpClient(mContext, IpManager.this, mInterfaceName);
917             mDhcpClient.registerForPreDhcpNotification();
918             mDhcpClient.sendMessage(DhcpClient.CMD_START_DHCP);
919 
920             if (mConfiguration.mProvisioningTimeoutMs > 0) {
921                 final long alarmTime = SystemClock.elapsedRealtime() +
922                         mConfiguration.mProvisioningTimeoutMs;
923                 mProvisioningTimeoutAlarm.schedule(alarmTime);
924             }
925         }
926 
927         return true;
928     }
929 
startIPv6()930     private boolean startIPv6() {
931         // Set privacy extensions.
932         try {
933             mNwService.setInterfaceIpv6PrivacyExtensions(mInterfaceName, true);
934             mNwService.enableIpv6(mInterfaceName);
935         } catch (RemoteException re) {
936             Log.e(mTag, "Unable to change interface settings: " + re);
937             return false;
938         } catch (IllegalStateException ie) {
939             Log.e(mTag, "Unable to change interface settings: " + ie);
940             return false;
941         }
942 
943         return true;
944     }
945 
946 
947     class StoppedState extends State {
948         @Override
enter()949         public void enter() {
950             try {
951                 mNwService.disableIpv6(mInterfaceName);
952                 mNwService.clearInterfaceAddresses(mInterfaceName);
953             } catch (Exception e) {
954                 Log.e(mTag, "Failed to clear addresses or disable IPv6" + e);
955             }
956 
957             resetLinkProperties();
958             if (mStartTimeMillis > 0) {
959                 recordMetric(IpManagerEvent.COMPLETE_LIFECYCLE);
960                 mStartTimeMillis = 0;
961             }
962         }
963 
964         @Override
processMessage(Message msg)965         public boolean processMessage(Message msg) {
966             switch (msg.what) {
967                 case CMD_STOP:
968                     break;
969 
970                 case CMD_START:
971                     mConfiguration = (ProvisioningConfiguration) msg.obj;
972                     transitionTo(mStartedState);
973                     break;
974 
975                 case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
976                     handleLinkPropertiesUpdate(NO_CALLBACKS);
977                     break;
978 
979                 case CMD_UPDATE_TCP_BUFFER_SIZES:
980                     mTcpBufferSizes = (String) msg.obj;
981                     handleLinkPropertiesUpdate(NO_CALLBACKS);
982                     break;
983 
984                 case CMD_UPDATE_HTTP_PROXY:
985                     mHttpProxy = (ProxyInfo) msg.obj;
986                     handleLinkPropertiesUpdate(NO_CALLBACKS);
987                     break;
988 
989                 case CMD_SET_MULTICAST_FILTER:
990                     mMulticastFiltering = (boolean) msg.obj;
991                     break;
992 
993                 case DhcpClient.CMD_ON_QUIT:
994                     // Everything is already stopped.
995                     Log.e(mTag, "Unexpected CMD_ON_QUIT (already stopped).");
996                     break;
997 
998                 default:
999                     return NOT_HANDLED;
1000             }
1001             return HANDLED;
1002         }
1003     }
1004 
1005     class StoppingState extends State {
1006         @Override
enter()1007         public void enter() {
1008             if (mDhcpClient == null) {
1009                 // There's no DHCPv4 for which to wait; proceed to stopped.
1010                 transitionTo(mStoppedState);
1011             }
1012         }
1013 
1014         @Override
processMessage(Message msg)1015         public boolean processMessage(Message msg) {
1016             switch (msg.what) {
1017                 case DhcpClient.CMD_ON_QUIT:
1018                     mDhcpClient = null;
1019                     transitionTo(mStoppedState);
1020                     break;
1021 
1022                 default:
1023                     deferMessage(msg);
1024             }
1025             return HANDLED;
1026         }
1027     }
1028 
1029     class StartedState extends State {
1030         private boolean mDhcpActionInFlight;
1031 
1032         @Override
enter()1033         public void enter() {
1034             mStartTimeMillis = SystemClock.elapsedRealtime();
1035 
1036             mApfFilter = ApfFilter.maybeCreate(mConfiguration.mApfCapabilities, mNetworkInterface,
1037                     mCallback, mMulticastFiltering);
1038             // TODO: investigate the effects of any multicast filtering racing/interfering with the
1039             // rest of this IP configuration startup.
1040             if (mApfFilter == null) {
1041                 mCallback.setFallbackMulticastFilter(mMulticastFiltering);
1042             }
1043 
1044             if (mConfiguration.mEnableIPv6) {
1045                 // TODO: Consider transitionTo(mStoppingState) if this fails.
1046                 startIPv6();
1047             }
1048 
1049             if (mConfiguration.mUsingIpReachabilityMonitor) {
1050                 mIpReachabilityMonitor = new IpReachabilityMonitor(
1051                         mContext,
1052                         mInterfaceName,
1053                         new IpReachabilityMonitor.Callback() {
1054                             @Override
1055                             public void notifyLost(InetAddress ip, String logMsg) {
1056                                 mCallback.onReachabilityLost(logMsg);
1057                             }
1058                         });
1059             }
1060 
1061             if (mConfiguration.mEnableIPv4) {
1062                 if (!startIPv4()) {
1063                     transitionTo(mStoppingState);
1064                 }
1065             }
1066         }
1067 
1068         @Override
exit()1069         public void exit() {
1070             mProvisioningTimeoutAlarm.cancel();
1071             stopDhcpAction();
1072 
1073             if (mIpReachabilityMonitor != null) {
1074                 mIpReachabilityMonitor.stop();
1075                 mIpReachabilityMonitor = null;
1076             }
1077 
1078             if (mDhcpClient != null) {
1079                 mDhcpClient.sendMessage(DhcpClient.CMD_STOP_DHCP);
1080                 mDhcpClient.doQuit();
1081             }
1082 
1083             if (mApfFilter != null) {
1084                 mApfFilter.shutdown();
1085                 mApfFilter = null;
1086             }
1087 
1088             resetLinkProperties();
1089         }
1090 
ensureDhcpAction()1091         private void ensureDhcpAction() {
1092             if (!mDhcpActionInFlight) {
1093                 mCallback.onPreDhcpAction();
1094                 mDhcpActionInFlight = true;
1095                 final long alarmTime = SystemClock.elapsedRealtime() +
1096                         mConfiguration.mRequestedPreDhcpActionMs;
1097                 mDhcpActionTimeoutAlarm.schedule(alarmTime);
1098             }
1099         }
1100 
stopDhcpAction()1101         private void stopDhcpAction() {
1102             mDhcpActionTimeoutAlarm.cancel();
1103             if (mDhcpActionInFlight) {
1104                 mCallback.onPostDhcpAction();
1105                 mDhcpActionInFlight = false;
1106             }
1107         }
1108 
1109         @Override
processMessage(Message msg)1110         public boolean processMessage(Message msg) {
1111             switch (msg.what) {
1112                 case CMD_STOP:
1113                     transitionTo(mStoppingState);
1114                     break;
1115 
1116                 case CMD_START:
1117                     Log.e(mTag, "ALERT: START received in StartedState. Please fix caller.");
1118                     break;
1119 
1120                 case CMD_CONFIRM:
1121                     // TODO: Possibly introduce a second type of confirmation
1122                     // that both probes (a) on-link neighbors and (b) does
1123                     // a DHCPv4 RENEW.  We used to do this on Wi-Fi framework
1124                     // roams.
1125                     if (mIpReachabilityMonitor != null) {
1126                         mIpReachabilityMonitor.probeAll();
1127                     }
1128                     break;
1129 
1130                 case EVENT_PRE_DHCP_ACTION_COMPLETE:
1131                     // It's possible to reach here if, for example, someone
1132                     // calls completedPreDhcpAction() after provisioning with
1133                     // a static IP configuration.
1134                     if (mDhcpClient != null) {
1135                         mDhcpClient.sendMessage(DhcpClient.CMD_PRE_DHCP_ACTION_COMPLETE);
1136                     }
1137                     break;
1138 
1139                 case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
1140                     if (!handleLinkPropertiesUpdate(SEND_CALLBACKS)) {
1141                         transitionTo(mStoppingState);
1142                     }
1143                     break;
1144 
1145                 case CMD_UPDATE_TCP_BUFFER_SIZES:
1146                     mTcpBufferSizes = (String) msg.obj;
1147                     // This cannot possibly change provisioning state.
1148                     handleLinkPropertiesUpdate(SEND_CALLBACKS);
1149                     break;
1150 
1151                 case CMD_UPDATE_HTTP_PROXY:
1152                     mHttpProxy = (ProxyInfo) msg.obj;
1153                     // This cannot possibly change provisioning state.
1154                     handleLinkPropertiesUpdate(SEND_CALLBACKS);
1155                     break;
1156 
1157                 case CMD_SET_MULTICAST_FILTER: {
1158                     mMulticastFiltering = (boolean) msg.obj;
1159                     if (mApfFilter != null) {
1160                         mApfFilter.setMulticastFilter(mMulticastFiltering);
1161                     } else {
1162                         mCallback.setFallbackMulticastFilter(mMulticastFiltering);
1163                     }
1164                     break;
1165                 }
1166 
1167                 case EVENT_PROVISIONING_TIMEOUT:
1168                     handleProvisioningFailure();
1169                     break;
1170 
1171                 case EVENT_DHCPACTION_TIMEOUT:
1172                     stopDhcpAction();
1173                     break;
1174 
1175                 case DhcpClient.CMD_PRE_DHCP_ACTION:
1176                     if (mConfiguration.mRequestedPreDhcpActionMs > 0) {
1177                         ensureDhcpAction();
1178                     } else {
1179                         sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE);
1180                     }
1181                     break;
1182 
1183                 case DhcpClient.CMD_CLEAR_LINKADDRESS:
1184                     clearIPv4Address();
1185                     break;
1186 
1187                 case DhcpClient.CMD_CONFIGURE_LINKADDRESS: {
1188                     final LinkAddress ipAddress = (LinkAddress) msg.obj;
1189                     if (setIPv4Address(ipAddress)) {
1190                         mDhcpClient.sendMessage(DhcpClient.EVENT_LINKADDRESS_CONFIGURED);
1191                     } else {
1192                         Log.e(mTag, "Failed to set IPv4 address!");
1193                         dispatchCallback(ProvisioningChange.LOST_PROVISIONING,
1194                                 new LinkProperties(mLinkProperties));
1195                         transitionTo(mStoppingState);
1196                     }
1197                     break;
1198                 }
1199 
1200                 // This message is only received when:
1201                 //
1202                 //     a) initial address acquisition succeeds,
1203                 //     b) renew succeeds or is NAK'd,
1204                 //     c) rebind succeeds or is NAK'd, or
1205                 //     c) the lease expires,
1206                 //
1207                 // but never when initial address acquisition fails. The latter
1208                 // condition is now governed by the provisioning timeout.
1209                 case DhcpClient.CMD_POST_DHCP_ACTION:
1210                     stopDhcpAction();
1211 
1212                     switch (msg.arg1) {
1213                         case DhcpClient.DHCP_SUCCESS:
1214                             handleIPv4Success((DhcpResults) msg.obj);
1215                             break;
1216                         case DhcpClient.DHCP_FAILURE:
1217                             handleIPv4Failure();
1218                             break;
1219                         default:
1220                             Log.e(mTag, "Unknown CMD_POST_DHCP_ACTION status:" + msg.arg1);
1221                     }
1222                     break;
1223 
1224                 case DhcpClient.CMD_ON_QUIT:
1225                     // DHCPv4 quit early for some reason.
1226                     Log.e(mTag, "Unexpected CMD_ON_QUIT.");
1227                     mDhcpClient = null;
1228                     break;
1229 
1230                 default:
1231                     return NOT_HANDLED;
1232             }
1233             return HANDLED;
1234         }
1235     }
1236 }
1237