Home | History | Annotate | Download | only in ip
      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.
     93         public void onPreDhcpAction() {}
     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.
    102         public void onNewDhcpResults(DhcpResults dhcpResults) {}
    103 
    104         public void onProvisioningSuccess(LinkProperties newLp) {}
    105         public void onProvisioningFailure(LinkProperties newLp) {}
    106 
    107         // Invoked on LinkProperties changes.
    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.
    112         public void onReachabilityLost(String logMsg) {}
    113 
    114         // Called when the IpManager state machine terminates.
    115         public void onQuit() {}
    116 
    117         // Install an APF program to filter incoming packets.
    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.
    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.
    126         public void setNeighborDiscoveryOffload(boolean enable) {}
    127     }
    128 
    129     public static class WaitForProvisioningCallback extends Callback {
    130         private LinkProperties mCallbackLinkProperties;
    131 
    132         public LinkProperties waitForProvisioning() {
    133             synchronized (this) {
    134                 try {
    135                     wait();
    136                 } catch (InterruptedException e) {}
    137                 return mCallbackLinkProperties;
    138             }
    139         }
    140 
    141         @Override
    142         public void onProvisioningSuccess(LinkProperties newLp) {
    143             synchronized (this) {
    144                 mCallbackLinkProperties = newLp;
    145                 notify();
    146             }
    147         }
    148 
    149         @Override
    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 
    179         public LoggingCallbackWrapper(Callback callback) {
    180             mCallback = callback;
    181         }
    182 
    183         private void log(String msg) {
    184             mLocalLog.log(PREFIX + msg);
    185         }
    186 
    187         @Override
    188         public void onPreDhcpAction() {
    189             mCallback.onPreDhcpAction();
    190             log("onPreDhcpAction()");
    191         }
    192         @Override
    193         public void onPostDhcpAction() {
    194             mCallback.onPostDhcpAction();
    195             log("onPostDhcpAction()");
    196         }
    197         @Override
    198         public void onNewDhcpResults(DhcpResults dhcpResults) {
    199             mCallback.onNewDhcpResults(dhcpResults);
    200             log("onNewDhcpResults({" + dhcpResults + "})");
    201         }
    202         @Override
    203         public void onProvisioningSuccess(LinkProperties newLp) {
    204             mCallback.onProvisioningSuccess(newLp);
    205             log("onProvisioningSuccess({" + newLp + "})");
    206         }
    207         @Override
    208         public void onProvisioningFailure(LinkProperties newLp) {
    209             mCallback.onProvisioningFailure(newLp);
    210             log("onProvisioningFailure({" + newLp + "})");
    211         }
    212         @Override
    213         public void onLinkPropertiesChange(LinkProperties newLp) {
    214             mCallback.onLinkPropertiesChange(newLp);
    215             log("onLinkPropertiesChange({" + newLp + "})");
    216         }
    217         @Override
    218         public void onReachabilityLost(String logMsg) {
    219             mCallback.onReachabilityLost(logMsg);
    220             log("onReachabilityLost(" + logMsg + ")");
    221         }
    222         @Override
    223         public void onQuit() {
    224             mCallback.onQuit();
    225             log("onQuit()");
    226         }
    227         @Override
    228         public void installPacketFilter(byte[] filter) {
    229             mCallback.installPacketFilter(filter);
    230             log("installPacketFilter(byte[" + filter.length + "])");
    231         }
    232         @Override
    233         public void setFallbackMulticastFilter(boolean enabled) {
    234             mCallback.setFallbackMulticastFilter(enabled);
    235             log("setFallbackMulticastFilter(" + enabled + ")");
    236         }
    237         @Override
    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 
    279             public Builder withoutIPv4() {
    280                 mConfig.mEnableIPv4 = false;
    281                 return this;
    282             }
    283 
    284             public Builder withoutIPv6() {
    285                 mConfig.mEnableIPv6 = false;
    286                 return this;
    287             }
    288 
    289             public Builder withoutIpReachabilityMonitor() {
    290                 mConfig.mUsingIpReachabilityMonitor = false;
    291                 return this;
    292             }
    293 
    294             public Builder withPreDhcpAction() {
    295                 mConfig.mRequestedPreDhcpActionMs = DEFAULT_TIMEOUT_MS;
    296                 return this;
    297             }
    298 
    299             public Builder withPreDhcpAction(int dhcpActionTimeoutMs) {
    300                 mConfig.mRequestedPreDhcpActionMs = dhcpActionTimeoutMs;
    301                 return this;
    302             }
    303 
    304             public Builder withStaticConfiguration(StaticIpConfiguration staticConfig) {
    305                 mConfig.mStaticIpConfig = staticConfig;
    306                 return this;
    307             }
    308 
    309             public Builder withApfCapabilities(ApfCapabilities apfCapabilities) {
    310                 mConfig.mApfCapabilities = apfCapabilities;
    311                 return this;
    312             }
    313 
    314             public Builder withProvisioningTimeoutMs(int timeoutMs) {
    315                 mConfig.mProvisioningTimeoutMs = timeoutMs;
    316                 return this;
    317             }
    318 
    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 
    332         public ProvisioningConfiguration() {}
    333 
    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
    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 
    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      */
    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
    485     protected void onQuitting() {
    486         mCallback.onQuit();
    487     }
    488 
    489     // Shut down this IpManager instance altogether.
    490     public void shutdown() {
    491         stop();
    492         quit();
    493     }
    494 
    495     public static ProvisioningConfiguration.Builder buildProvisioningConfiguration() {
    496         return new ProvisioningConfiguration.Builder();
    497     }
    498 
    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.
    507     public void startProvisioning(StaticIpConfiguration staticIpConfig) {
    508         startProvisioning(buildProvisioningConfiguration()
    509                 .withStaticConfiguration(staticIpConfig)
    510                 .build());
    511     }
    512 
    513     public void startProvisioning() {
    514         startProvisioning(new ProvisioningConfiguration());
    515     }
    516 
    517     public void stop() {
    518         sendMessage(CMD_STOP);
    519     }
    520 
    521     public void confirmConfiguration() {
    522         sendMessage(CMD_CONFIRM);
    523     }
    524 
    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      */
    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      */
    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      */
    553     public void setMulticastFilter(boolean enabled) {
    554         sendMessage(CMD_SET_MULTICAST_FILTER, enabled);
    555     }
    556 
    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
    583     protected String getWhatToString(int what) {
    584         return sWhatToString.get(what, "UNKNOWN: " + Integer.toString(what));
    585     }
    586 
    587     @Override
    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
    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 
    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.
    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 
    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.
    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.).
    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 
    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.
    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 
    755     private boolean linkPropertiesUnchanged(LinkProperties newLp) {
    756         return Objects.equals(newLp, mLinkProperties);
    757     }
    758 
    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.
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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
    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
    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
   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
   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
   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
   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 
   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 
   1101         private void stopDhcpAction() {
   1102             mDhcpActionTimeoutAlarm.cancel();
   1103             if (mDhcpActionInFlight) {
   1104                 mCallback.onPostDhcpAction();
   1105                 mDhcpActionInFlight = false;
   1106             }
   1107         }
   1108 
   1109         @Override
   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