IpManager.java revision 19e84f7b750c97d5d7440af16c7ba18bf759e5dc
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
17package android.net.ip;
18
19import com.android.internal.util.MessageUtils;
20
21import android.content.Context;
22import android.net.apf.ApfCapabilities;
23import android.net.apf.ApfFilter;
24import android.net.DhcpResults;
25import android.net.InterfaceConfiguration;
26import android.net.LinkAddress;
27import android.net.LinkProperties;
28import android.net.LinkProperties.ProvisioningChange;
29import android.net.ProxyInfo;
30import android.net.RouteInfo;
31import android.net.StaticIpConfiguration;
32import android.net.dhcp.DhcpClient;
33import android.os.INetworkManagementService;
34import android.os.Message;
35import android.os.RemoteException;
36import android.os.ServiceManager;
37import android.text.TextUtils;
38import android.util.Log;
39import android.util.SparseArray;
40
41import com.android.internal.annotations.GuardedBy;
42import com.android.internal.annotations.VisibleForTesting;
43import com.android.internal.util.IndentingPrintWriter;
44import com.android.internal.util.State;
45import com.android.internal.util.StateMachine;
46import com.android.server.net.NetlinkTracker;
47
48import java.io.FileDescriptor;
49import java.io.PrintWriter;
50import java.net.InetAddress;
51import java.net.NetworkInterface;
52import java.net.SocketException;
53import java.util.Objects;
54
55
56/**
57 * IpManager
58 *
59 * This class provides the interface to IP-layer provisioning and maintenance
60 * functionality that can be used by transport layers like Wi-Fi, Ethernet,
61 * et cetera.
62 *
63 * [ Lifetime ]
64 * IpManager is designed to be instantiated as soon as the interface name is
65 * known and can be as long-lived as the class containing it (i.e. declaring
66 * it "private final" is okay).
67 *
68 * @hide
69 */
70public class IpManager extends StateMachine {
71    private static final boolean DBG = false;
72    private static final boolean VDBG = false;
73
74    // For message logging.
75    private static final Class[] sMessageClasses = { IpManager.class, DhcpClient.class };
76    private static final SparseArray<String> sWhatToString =
77            MessageUtils.findMessageNames(sMessageClasses);
78
79    /**
80     * Callbacks for handling IpManager events.
81     */
82    public static class Callback {
83        // In order to receive onPreDhcpAction(), call #withPreDhcpAction()
84        // when constructing a ProvisioningConfiguration.
85        //
86        // Implementations of onPreDhcpAction() must call
87        // IpManager#completedPreDhcpAction() to indicate that DHCP is clear
88        // to proceed.
89        public void onPreDhcpAction() {}
90        public void onPostDhcpAction() {}
91
92        // This is purely advisory and not an indication of provisioning
93        // success or failure.  This is only here for callers that want to
94        // expose DHCPv4 results to other APIs (e.g., WifiInfo#setInetAddress).
95        // DHCPv4 or static IPv4 configuration failure or success can be
96        // determined by whether or not the passed-in DhcpResults object is
97        // null or not.
98        public void onNewDhcpResults(DhcpResults dhcpResults) {}
99
100        public void onProvisioningSuccess(LinkProperties newLp) {}
101        public void onProvisioningFailure(LinkProperties newLp) {}
102
103        // Invoked on LinkProperties changes.
104        public void onLinkPropertiesChange(LinkProperties newLp) {}
105
106        // Called when the internal IpReachabilityMonitor (if enabled) has
107        // detected the loss of a critical number of required neighbors.
108        public void onReachabilityLost(String logMsg) {}
109
110        // Called when the IpManager state machine terminates.
111        public void onQuit() {}
112
113        // Install an APF program to filter incoming packets.
114        public void installPacketFilter(byte[] filter) {}
115
116        // If multicast filtering cannot be accomplished with APF, this function will be called to
117        // actuate multicast filtering using another means.
118        public void setFallbackMulticastFilter(boolean enabled) {}
119
120        // Enabled/disable Neighbor Discover offload functionality. This is
121        // called, for example, whenever 464xlat is being started or stopped.
122        public void setNeighborDiscoveryOffload(boolean enable) {}
123    }
124
125    public static class WaitForProvisioningCallback extends Callback {
126        private LinkProperties mCallbackLinkProperties;
127
128        public LinkProperties waitForProvisioning() {
129            synchronized (this) {
130                try {
131                    wait();
132                } catch (InterruptedException e) {}
133                return mCallbackLinkProperties;
134            }
135        }
136
137        @Override
138        public void onProvisioningSuccess(LinkProperties newLp) {
139            synchronized (this) {
140                mCallbackLinkProperties = newLp;
141                notify();
142            }
143        }
144
145        @Override
146        public void onProvisioningFailure(LinkProperties newLp) {
147            synchronized (this) {
148                mCallbackLinkProperties = null;
149                notify();
150            }
151        }
152    }
153
154    /**
155     * This class encapsulates parameters to be passed to
156     * IpManager#startProvisioning(). A defensive copy is made by IpManager
157     * and the values specified herein are in force until IpManager#stop()
158     * is called.
159     *
160     * Example use:
161     *
162     *     final ProvisioningConfiguration config =
163     *             mIpManager.buildProvisioningConfiguration()
164     *                     .withPreDhcpAction()
165     *                     .build();
166     *     mIpManager.startProvisioning(config);
167     *     ...
168     *     mIpManager.stop();
169     *
170     * The specified provisioning configuration will only be active until
171     * IpManager#stop() is called. Future calls to IpManager#startProvisioning()
172     * must specify the configuration again.
173     */
174    public static class ProvisioningConfiguration {
175
176        public static class Builder {
177            private ProvisioningConfiguration mConfig = new ProvisioningConfiguration();
178
179            public Builder withoutIpReachabilityMonitor() {
180                mConfig.mUsingIpReachabilityMonitor = false;
181                return this;
182            }
183
184            public Builder withPreDhcpAction() {
185                mConfig.mRequestedPreDhcpAction = true;
186                return this;
187            }
188
189            public Builder withStaticConfiguration(StaticIpConfiguration staticConfig) {
190                mConfig.mStaticIpConfig = staticConfig;
191                return this;
192            }
193
194            public Builder withApfCapabilities(ApfCapabilities apfCapabilities) {
195                mConfig.mApfCapabilities = apfCapabilities;
196                return this;
197            }
198
199            public ProvisioningConfiguration build() {
200                return new ProvisioningConfiguration(mConfig);
201            }
202        }
203
204        /* package */ boolean mUsingIpReachabilityMonitor = true;
205        /* package */ boolean mRequestedPreDhcpAction;
206        /* package */ StaticIpConfiguration mStaticIpConfig;
207        /* package */ ApfCapabilities mApfCapabilities;
208
209        public ProvisioningConfiguration() {}
210
211        public ProvisioningConfiguration(ProvisioningConfiguration other) {
212            mUsingIpReachabilityMonitor = other.mUsingIpReachabilityMonitor;
213            mRequestedPreDhcpAction = other.mRequestedPreDhcpAction;
214            mStaticIpConfig = other.mStaticIpConfig;
215            mApfCapabilities = other.mApfCapabilities;
216        }
217    }
218
219    public static final String DUMP_ARG = "ipmanager";
220
221    private static final int CMD_STOP = 1;
222    private static final int CMD_START = 2;
223    private static final int CMD_CONFIRM = 3;
224    private static final int EVENT_PRE_DHCP_ACTION_COMPLETE = 4;
225    // Sent by NetlinkTracker to communicate netlink events.
226    private static final int EVENT_NETLINK_LINKPROPERTIES_CHANGED = 5;
227    private static final int CMD_UPDATE_TCP_BUFFER_SIZES = 6;
228    private static final int CMD_UPDATE_HTTP_PROXY = 7;
229    private static final int CMD_SET_MULTICAST_FILTER = 8;
230
231    private static final int MAX_LOG_RECORDS = 1000;
232
233    private static final boolean NO_CALLBACKS = false;
234    private static final boolean SEND_CALLBACKS = true;
235
236    // This must match the interface prefix in clatd.c.
237    // TODO: Revert this hack once IpManager and Nat464Xlat work in concert.
238    private static final String CLAT_PREFIX = "v4-";
239
240    private final Object mLock = new Object();
241    private final State mStoppedState = new StoppedState();
242    private final State mStoppingState = new StoppingState();
243    private final State mStartedState = new StartedState();
244
245    private final String mTag;
246    private final Context mContext;
247    private final String mInterfaceName;
248    private final String mClatInterfaceName;
249    @VisibleForTesting
250    protected final Callback mCallback;
251    private final INetworkManagementService mNwService;
252    private final NetlinkTracker mNetlinkTracker;
253
254    private NetworkInterface mNetworkInterface;
255
256    /**
257     * Non-final member variables accessed only from within our StateMachine.
258     */
259    private ProvisioningConfiguration mConfiguration;
260    private IpReachabilityMonitor mIpReachabilityMonitor;
261    private DhcpClient mDhcpClient;
262    private DhcpResults mDhcpResults;
263    private String mTcpBufferSizes;
264    private ProxyInfo mHttpProxy;
265    private ApfFilter mApfFilter;
266    private boolean mMulticastFiltering;
267
268    /**
269     * Member variables accessed both from within the StateMachine thread
270     * and via accessors from other threads.
271     */
272    @GuardedBy("mLock")
273    private LinkProperties mLinkProperties;
274
275    public IpManager(Context context, String ifName, Callback callback)
276                throws IllegalArgumentException {
277        this(context, ifName, callback, INetworkManagementService.Stub.asInterface(
278                ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE)));
279    }
280
281    /**
282     * An expanded constructor, useful for dependency injection.
283     */
284    public IpManager(Context context, String ifName, Callback callback,
285            INetworkManagementService nwService) throws IllegalArgumentException {
286        super(IpManager.class.getSimpleName() + "." + ifName);
287        mTag = getName();
288
289        mContext = context;
290        mInterfaceName = ifName;
291        mClatInterfaceName = CLAT_PREFIX + ifName;
292        mCallback = callback;
293        mNwService = nwService;
294
295        mNetlinkTracker = new NetlinkTracker(
296                mInterfaceName,
297                new NetlinkTracker.Callback() {
298                    @Override
299                    public void update() {
300                        sendMessage(EVENT_NETLINK_LINKPROPERTIES_CHANGED);
301                    }
302                }) {
303            @Override
304            public void interfaceAdded(String iface) {
305                super.interfaceAdded(iface);
306                if (mClatInterfaceName.equals(iface)) {
307                    mCallback.setNeighborDiscoveryOffload(false);
308                }
309            }
310
311            @Override
312            public void interfaceRemoved(String iface) {
313                super.interfaceRemoved(iface);
314                if (mClatInterfaceName.equals(iface)) {
315                    // TODO: consider sending a message to the IpManager main
316                    // StateMachine thread, in case "NDO enabled" state becomes
317                    // tied to more things that 464xlat operation.
318                    mCallback.setNeighborDiscoveryOffload(true);
319                }
320            }
321        };
322
323        try {
324            mNwService.registerObserver(mNetlinkTracker);
325        } catch (RemoteException e) {
326            Log.e(mTag, "Couldn't register NetlinkTracker: " + e.toString());
327        }
328
329        resetLinkProperties();
330
331        // Super simple StateMachine.
332        addState(mStoppedState);
333        addState(mStartedState);
334        addState(mStoppingState);
335
336        setInitialState(mStoppedState);
337        setLogRecSize(MAX_LOG_RECORDS);
338        super.start();
339    }
340
341    @Override
342    protected void onQuitting() {
343        mCallback.onQuit();
344    }
345
346    // Shut down this IpManager instance altogether.
347    public void shutdown() {
348        stop();
349        quit();
350    }
351
352    public static ProvisioningConfiguration.Builder buildProvisioningConfiguration() {
353        return new ProvisioningConfiguration.Builder();
354    }
355
356    public void startProvisioning(ProvisioningConfiguration req) {
357        getNetworkInterface();
358        sendMessage(CMD_START, new ProvisioningConfiguration(req));
359    }
360
361    // TODO: Delete this.
362    public void startProvisioning(StaticIpConfiguration staticIpConfig) {
363        startProvisioning(buildProvisioningConfiguration()
364                .withStaticConfiguration(staticIpConfig)
365                .build());
366    }
367
368    public void startProvisioning() {
369        startProvisioning(new ProvisioningConfiguration());
370    }
371
372    public void stop() {
373        sendMessage(CMD_STOP);
374    }
375
376    public void confirmConfiguration() {
377        sendMessage(CMD_CONFIRM);
378    }
379
380    public void completedPreDhcpAction() {
381        sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE);
382    }
383
384    /**
385     * Set the TCP buffer sizes to use.
386     *
387     * This may be called, repeatedly, at any time before or after a call to
388     * #startProvisioning(). The setting is cleared upon calling #stop().
389     */
390    public void setTcpBufferSizes(String tcpBufferSizes) {
391        sendMessage(CMD_UPDATE_TCP_BUFFER_SIZES, tcpBufferSizes);
392    }
393
394    /**
395     * Set the HTTP Proxy configuration to use.
396     *
397     * This may be called, repeatedly, at any time before or after a call to
398     * #startProvisioning(). The setting is cleared upon calling #stop().
399     */
400    public void setHttpProxy(ProxyInfo proxyInfo) {
401        sendMessage(CMD_UPDATE_HTTP_PROXY, proxyInfo);
402    }
403
404    /**
405     * Enable or disable the multicast filter.  Attempts to use APF to accomplish the filtering,
406     * if not, Callback.setFallbackMulticastFilter() is called.
407     */
408    public void setMulticastFilter(boolean enabled) {
409        sendMessage(CMD_SET_MULTICAST_FILTER, enabled);
410    }
411
412    public LinkProperties getLinkProperties() {
413        synchronized (mLock) {
414            return new LinkProperties(mLinkProperties);
415        }
416    }
417
418    public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
419        IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
420        pw.println("APF dump:");
421        pw.increaseIndent();
422        // Thread-unsafe access to mApfFilter but just used for debugging.
423        ApfFilter apfFilter = mApfFilter;
424        if (apfFilter != null) {
425            apfFilter.dump(pw);
426        } else {
427            pw.println("No apf support");
428        }
429        pw.decreaseIndent();
430    }
431
432
433    /**
434     * Internals.
435     */
436
437    @Override
438    protected String getWhatToString(int what) {
439        return sWhatToString.get(what, "UNKNOWN: " + Integer.toString(what));
440    }
441
442    @Override
443    protected String getLogRecString(Message msg) {
444        final String logLine = String.format(
445                "iface{%s/%d} arg1{%d} arg2{%d} obj{%s}",
446                mInterfaceName, mNetworkInterface == null ? -1 : mNetworkInterface.getIndex(),
447                msg.arg1, msg.arg2, Objects.toString(msg.obj));
448        if (VDBG) {
449            Log.d(mTag, getWhatToString(msg.what) + " " + logLine);
450        }
451        return logLine;
452    }
453
454    private void getNetworkInterface() {
455        try {
456            mNetworkInterface = NetworkInterface.getByName(mInterfaceName);
457        } catch (SocketException | NullPointerException e) {
458            // TODO: throw new IllegalStateException.
459            Log.e(mTag, "ALERT: Failed to get interface object: ", e);
460        }
461    }
462
463    // This needs to be called with care to ensure that our LinkProperties
464    // are in sync with the actual LinkProperties of the interface. For example,
465    // we should only call this if we know for sure that there are no IP addresses
466    // assigned to the interface, etc.
467    private void resetLinkProperties() {
468        mNetlinkTracker.clearLinkProperties();
469        mConfiguration = null;
470        mDhcpResults = null;
471        mTcpBufferSizes = "";
472        mHttpProxy = null;
473
474        synchronized (mLock) {
475            mLinkProperties = new LinkProperties();
476            mLinkProperties.setInterfaceName(mInterfaceName);
477        }
478    }
479
480    // For now: use WifiStateMachine's historical notion of provisioned.
481    private static boolean isProvisioned(LinkProperties lp) {
482        // For historical reasons, we should connect even if all we have is
483        // an IPv4 address and nothing else.
484        return lp.isProvisioned() || lp.hasIPv4Address();
485    }
486
487    // TODO: Investigate folding all this into the existing static function
488    // LinkProperties.compareProvisioning() or some other single function that
489    // takes two LinkProperties objects and returns a ProvisioningChange
490    // object that is a correct and complete assessment of what changed, taking
491    // account of the asymmetries described in the comments in this function.
492    // Then switch to using it everywhere (IpReachabilityMonitor, etc.).
493    private static ProvisioningChange compareProvisioning(
494            LinkProperties oldLp, LinkProperties newLp) {
495        ProvisioningChange delta;
496
497        final boolean wasProvisioned = isProvisioned(oldLp);
498        final boolean isProvisioned = isProvisioned(newLp);
499
500        if (!wasProvisioned && isProvisioned) {
501            delta = ProvisioningChange.GAINED_PROVISIONING;
502        } else if (wasProvisioned && isProvisioned) {
503            delta = ProvisioningChange.STILL_PROVISIONED;
504        } else if (!wasProvisioned && !isProvisioned) {
505            delta = ProvisioningChange.STILL_NOT_PROVISIONED;
506        } else {
507            // (wasProvisioned && !isProvisioned)
508            //
509            // Note that this is true even if we lose a configuration element
510            // (e.g., a default gateway) that would not be required to advance
511            // into provisioned state. This is intended: if we have a default
512            // router and we lose it, that's a sure sign of a problem, but if
513            // we connect to a network with no IPv4 DNS servers, we consider
514            // that to be a network without DNS servers and connect anyway.
515            //
516            // See the comment below.
517            delta = ProvisioningChange.LOST_PROVISIONING;
518        }
519
520        // Additionally:
521        //
522        // Partial configurations (e.g., only an IPv4 address with no DNS
523        // servers and no default route) are accepted as long as DHCPv4
524        // succeeds. On such a network, isProvisioned() will always return
525        // false, because the configuration is not complete, but we want to
526        // connect anyway. It might be a disconnected network such as a
527        // Chromecast or a wireless printer, for example.
528        //
529        // Because on such a network isProvisioned() will always return false,
530        // delta will never be LOST_PROVISIONING. So check for loss of
531        // provisioning here too.
532        if ((oldLp.hasIPv4Address() && !newLp.hasIPv4Address()) ||
533                (oldLp.isIPv6Provisioned() && !newLp.isIPv6Provisioned())) {
534            delta = ProvisioningChange.LOST_PROVISIONING;
535        }
536
537        return delta;
538    }
539
540    private void dispatchCallback(ProvisioningChange delta, LinkProperties newLp) {
541        if (mApfFilter != null) mApfFilter.setLinkProperties(newLp);
542        switch (delta) {
543            case GAINED_PROVISIONING:
544                if (VDBG) { Log.d(mTag, "onProvisioningSuccess()"); }
545                mCallback.onProvisioningSuccess(newLp);
546                break;
547
548            case LOST_PROVISIONING:
549                if (VDBG) { Log.d(mTag, "onProvisioningFailure()"); }
550                mCallback.onProvisioningFailure(newLp);
551                break;
552
553            default:
554                if (VDBG) { Log.d(mTag, "onLinkPropertiesChange()"); }
555                mCallback.onLinkPropertiesChange(newLp);
556                break;
557        }
558    }
559
560    private ProvisioningChange setLinkProperties(LinkProperties newLp) {
561        if (mIpReachabilityMonitor != null) {
562            mIpReachabilityMonitor.updateLinkProperties(newLp);
563        }
564
565        ProvisioningChange delta;
566        synchronized (mLock) {
567            delta = compareProvisioning(mLinkProperties, newLp);
568            mLinkProperties = new LinkProperties(newLp);
569        }
570
571        if (DBG) {
572            switch (delta) {
573                case GAINED_PROVISIONING:
574                case LOST_PROVISIONING:
575                    Log.d(mTag, "provisioning: " + delta);
576                    break;
577            }
578        }
579
580        return delta;
581    }
582
583    private boolean linkPropertiesUnchanged(LinkProperties newLp) {
584        synchronized (mLock) {
585            return Objects.equals(newLp, mLinkProperties);
586        }
587    }
588
589    private LinkProperties assembleLinkProperties() {
590        // [1] Create a new LinkProperties object to populate.
591        LinkProperties newLp = new LinkProperties();
592        newLp.setInterfaceName(mInterfaceName);
593
594        // [2] Pull in data from netlink:
595        //         - IPv4 addresses
596        //         - IPv6 addresses
597        //         - IPv6 routes
598        //         - IPv6 DNS servers
599        LinkProperties netlinkLinkProperties = mNetlinkTracker.getLinkProperties();
600        newLp.setLinkAddresses(netlinkLinkProperties.getLinkAddresses());
601        for (RouteInfo route : netlinkLinkProperties.getRoutes()) {
602            newLp.addRoute(route);
603        }
604        for (InetAddress dns : netlinkLinkProperties.getDnsServers()) {
605            // Only add likely reachable DNS servers.
606            // TODO: investigate deleting this.
607            if (newLp.isReachable(dns)) {
608                newLp.addDnsServer(dns);
609            }
610        }
611
612        // [3] Add in data from DHCPv4, if available.
613        //
614        // mDhcpResults is never shared with any other owner so we don't have
615        // to worry about concurrent modification.
616        if (mDhcpResults != null) {
617            for (RouteInfo route : mDhcpResults.getRoutes(mInterfaceName)) {
618                newLp.addRoute(route);
619            }
620            for (InetAddress dns : mDhcpResults.dnsServers) {
621                // Only add likely reachable DNS servers.
622                // TODO: investigate deleting this.
623                if (newLp.isReachable(dns)) {
624                    newLp.addDnsServer(dns);
625                }
626            }
627            newLp.setDomains(mDhcpResults.domains);
628
629            if (mDhcpResults.mtu != 0) {
630                newLp.setMtu(mDhcpResults.mtu);
631            }
632        }
633
634        // [4] Add in TCP buffer sizes and HTTP Proxy config, if available.
635        if (!TextUtils.isEmpty(mTcpBufferSizes)) {
636            newLp.setTcpBufferSizes(mTcpBufferSizes);
637        }
638        if (mHttpProxy != null) {
639            newLp.setHttpProxy(mHttpProxy);
640        }
641
642        if (VDBG) {
643            Log.d(mTag, "newLp{" + newLp + "}");
644        }
645        return newLp;
646    }
647
648    // Returns false if we have lost provisioning, true otherwise.
649    private boolean handleLinkPropertiesUpdate(boolean sendCallbacks) {
650        final LinkProperties newLp = assembleLinkProperties();
651        if (linkPropertiesUnchanged(newLp)) {
652            return true;
653        }
654        final ProvisioningChange delta = setLinkProperties(newLp);
655        if (sendCallbacks) {
656            dispatchCallback(delta, newLp);
657        }
658        return (delta != ProvisioningChange.LOST_PROVISIONING);
659    }
660
661    private void clearIPv4Address() {
662        try {
663            final InterfaceConfiguration ifcg = new InterfaceConfiguration();
664            ifcg.setLinkAddress(new LinkAddress("0.0.0.0/0"));
665            mNwService.setInterfaceConfig(mInterfaceName, ifcg);
666        } catch (RemoteException e) {
667            Log.e(mTag, "ALERT: Failed to clear IPv4 address on interface " + mInterfaceName, e);
668        }
669    }
670
671    private void handleIPv4Success(DhcpResults dhcpResults) {
672        mDhcpResults = new DhcpResults(dhcpResults);
673        final LinkProperties newLp = assembleLinkProperties();
674        final ProvisioningChange delta = setLinkProperties(newLp);
675
676        if (VDBG) {
677            Log.d(mTag, "onNewDhcpResults(" + Objects.toString(dhcpResults) + ")");
678        }
679        mCallback.onNewDhcpResults(dhcpResults);
680
681        dispatchCallback(delta, newLp);
682    }
683
684    private void handleIPv4Failure() {
685        // TODO: Figure out to de-dup this and the same code in DhcpClient.
686        clearIPv4Address();
687        mDhcpResults = null;
688        final LinkProperties newLp = assembleLinkProperties();
689        ProvisioningChange delta = setLinkProperties(newLp);
690        // If we've gotten here and we're still not provisioned treat that as
691        // a total loss of provisioning.
692        //
693        // Either (a) static IP configuration failed or (b) DHCPv4 failed AND
694        // there was no usable IPv6 obtained before the DHCPv4 timeout.
695        //
696        // Regardless: GAME OVER.
697        //
698        // TODO: Make the DHCP client not time out and just continue in
699        // exponential backoff. Callers such as Wi-Fi which need a timeout
700        // should implement it themselves.
701        if (delta == ProvisioningChange.STILL_NOT_PROVISIONED) {
702            delta = ProvisioningChange.LOST_PROVISIONING;
703        }
704
705        if (VDBG) { Log.d(mTag, "onNewDhcpResults(null)"); }
706        mCallback.onNewDhcpResults(null);
707
708        dispatchCallback(delta, newLp);
709        if (delta == ProvisioningChange.LOST_PROVISIONING) {
710            transitionTo(mStoppingState);
711        }
712    }
713
714    class StoppedState extends State {
715        @Override
716        public void enter() {
717            try {
718                mNwService.disableIpv6(mInterfaceName);
719                mNwService.clearInterfaceAddresses(mInterfaceName);
720            } catch (Exception e) {
721                Log.e(mTag, "Failed to clear addresses or disable IPv6" + e);
722            }
723
724            resetLinkProperties();
725        }
726
727        @Override
728        public boolean processMessage(Message msg) {
729            switch (msg.what) {
730                case CMD_STOP:
731                    break;
732
733                case CMD_START:
734                    mConfiguration = (ProvisioningConfiguration) msg.obj;
735                    transitionTo(mStartedState);
736                    break;
737
738                case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
739                    handleLinkPropertiesUpdate(NO_CALLBACKS);
740                    break;
741
742                case CMD_UPDATE_TCP_BUFFER_SIZES:
743                    mTcpBufferSizes = (String) msg.obj;
744                    handleLinkPropertiesUpdate(NO_CALLBACKS);
745                    break;
746
747                case CMD_UPDATE_HTTP_PROXY:
748                    mHttpProxy = (ProxyInfo) msg.obj;
749                    handleLinkPropertiesUpdate(NO_CALLBACKS);
750                    break;
751
752                case CMD_SET_MULTICAST_FILTER:
753                    mMulticastFiltering = (boolean) msg.obj;
754                    break;
755
756                case DhcpClient.CMD_ON_QUIT:
757                    // Everything is already stopped.
758                    Log.e(mTag, "Unexpected CMD_ON_QUIT (already stopped).");
759                    break;
760
761                default:
762                    return NOT_HANDLED;
763            }
764            return HANDLED;
765        }
766    }
767
768    class StoppingState extends State {
769        @Override
770        public void enter() {
771            if (mDhcpClient == null) {
772                // There's no DHCPv4 for which to wait; proceed to stopped.
773                transitionTo(mStoppedState);
774            }
775        }
776
777        @Override
778        public boolean processMessage(Message msg) {
779            switch (msg.what) {
780                case DhcpClient.CMD_ON_QUIT:
781                    mDhcpClient = null;
782                    transitionTo(mStoppedState);
783                    break;
784
785                default:
786                    deferMessage(msg);
787            }
788            return HANDLED;
789        }
790    }
791
792    class StartedState extends State {
793        @Override
794        public void enter() {
795            mApfFilter = ApfFilter.maybeCreate(mConfiguration.mApfCapabilities, mNetworkInterface,
796                    mCallback, mMulticastFiltering);
797            // TODO: investigate the effects of any multicast filtering racing/interfering with the
798            // rest of this IP configuration startup.
799            if (mApfFilter == null) mCallback.setFallbackMulticastFilter(mMulticastFiltering);
800            // Set privacy extensions.
801            try {
802                mNwService.setInterfaceIpv6PrivacyExtensions(mInterfaceName, true);
803                mNwService.enableIpv6(mInterfaceName);
804                // TODO: Perhaps clearIPv4Address() as well.
805            } catch (RemoteException re) {
806                Log.e(mTag, "Unable to change interface settings: " + re);
807            } catch (IllegalStateException ie) {
808                Log.e(mTag, "Unable to change interface settings: " + ie);
809            }
810
811            if (mConfiguration.mUsingIpReachabilityMonitor) {
812                mIpReachabilityMonitor = new IpReachabilityMonitor(
813                        mContext,
814                        mInterfaceName,
815                        new IpReachabilityMonitor.Callback() {
816                            @Override
817                            public void notifyLost(InetAddress ip, String logMsg) {
818                                mCallback.onReachabilityLost(logMsg);
819                            }
820                        });
821            }
822
823            // If we have a StaticIpConfiguration attempt to apply it and
824            // handle the result accordingly.
825            if (mConfiguration.mStaticIpConfig != null) {
826                if (applyStaticIpConfig()) {
827                    handleIPv4Success(new DhcpResults(mConfiguration.mStaticIpConfig));
828                } else {
829                    if (VDBG) { Log.d(mTag, "onProvisioningFailure()"); }
830                    mCallback.onProvisioningFailure(getLinkProperties());
831                    transitionTo(mStoppingState);
832                }
833            } else {
834                // Start DHCPv4.
835                mDhcpClient = DhcpClient.makeDhcpClient(
836                        mContext,
837                        IpManager.this,
838                        mInterfaceName);
839                mDhcpClient.registerForPreDhcpNotification();
840                mDhcpClient.sendMessage(DhcpClient.CMD_START_DHCP);
841            }
842        }
843
844        @Override
845        public void exit() {
846            if (mIpReachabilityMonitor != null) {
847                mIpReachabilityMonitor.stop();
848                mIpReachabilityMonitor = null;
849            }
850
851            if (mDhcpClient != null) {
852                mDhcpClient.sendMessage(DhcpClient.CMD_STOP_DHCP);
853                mDhcpClient.doQuit();
854            }
855
856            if (mApfFilter != null) {
857                mApfFilter.shutdown();
858                mApfFilter = null;
859            }
860
861            resetLinkProperties();
862        }
863
864        @Override
865        public boolean processMessage(Message msg) {
866            switch (msg.what) {
867                case CMD_STOP:
868                    transitionTo(mStoppedState);
869                    break;
870
871                case CMD_START:
872                    Log.e(mTag, "ALERT: START received in StartedState. Please fix caller.");
873                    break;
874
875                case CMD_CONFIRM:
876                    // TODO: Possibly introduce a second type of confirmation
877                    // that both probes (a) on-link neighbors and (b) does
878                    // a DHCPv4 RENEW.  We used to do this on Wi-Fi framework
879                    // roams.
880                    if (mIpReachabilityMonitor != null) {
881                        mIpReachabilityMonitor.probeAll();
882                    }
883                    break;
884
885                case EVENT_PRE_DHCP_ACTION_COMPLETE:
886                    // It's possible to reach here if, for example, someone
887                    // calls completedPreDhcpAction() after provisioning with
888                    // a static IP configuration.
889                    if (mDhcpClient != null) {
890                        mDhcpClient.sendMessage(DhcpClient.CMD_PRE_DHCP_ACTION_COMPLETE);
891                    }
892                    break;
893
894                case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
895                    if (!handleLinkPropertiesUpdate(SEND_CALLBACKS)) {
896                        transitionTo(mStoppedState);
897                    }
898                    break;
899
900                case CMD_UPDATE_TCP_BUFFER_SIZES:
901                    mTcpBufferSizes = (String) msg.obj;
902                    // This cannot possibly change provisioning state.
903                    handleLinkPropertiesUpdate(SEND_CALLBACKS);
904                    break;
905
906                case CMD_UPDATE_HTTP_PROXY:
907                    mHttpProxy = (ProxyInfo) msg.obj;
908                    // This cannot possibly change provisioning state.
909                    handleLinkPropertiesUpdate(SEND_CALLBACKS);
910                    break;
911
912                case CMD_SET_MULTICAST_FILTER: {
913                    mMulticastFiltering = (boolean) msg.obj;
914                    if (mApfFilter != null) {
915                        mApfFilter.setMulticastFilter(mMulticastFiltering);
916                    } else {
917                        mCallback.setFallbackMulticastFilter(mMulticastFiltering);
918                    }
919                    break;
920                }
921
922                case DhcpClient.CMD_PRE_DHCP_ACTION:
923                    if (VDBG) { Log.d(mTag, "onPreDhcpAction()"); }
924                    if (mConfiguration.mRequestedPreDhcpAction) {
925                        mCallback.onPreDhcpAction();
926                    } else {
927                        sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE);
928                    }
929                    break;
930
931                case DhcpClient.CMD_POST_DHCP_ACTION: {
932                    // Note that onPostDhcpAction() is likely to be
933                    // asynchronous, and thus there is no guarantee that we
934                    // will be able to observe any of its effects here.
935                    if (VDBG) { Log.d(mTag, "onPostDhcpAction()"); }
936                    mCallback.onPostDhcpAction();
937
938                    final DhcpResults dhcpResults = (DhcpResults) msg.obj;
939                    switch (msg.arg1) {
940                        case DhcpClient.DHCP_SUCCESS:
941                            handleIPv4Success(dhcpResults);
942                            break;
943                        case DhcpClient.DHCP_FAILURE:
944                            handleIPv4Failure();
945                            break;
946                        default:
947                            Log.e(mTag, "Unknown CMD_POST_DHCP_ACTION status:" + msg.arg1);
948                    }
949                    break;
950                }
951
952                case DhcpClient.CMD_ON_QUIT:
953                    // DHCPv4 quit early for some reason.
954                    Log.e(mTag, "Unexpected CMD_ON_QUIT.");
955                    mDhcpClient = null;
956                    break;
957
958                default:
959                    return NOT_HANDLED;
960            }
961            return HANDLED;
962        }
963
964        private boolean applyStaticIpConfig() {
965            final InterfaceConfiguration ifcg = new InterfaceConfiguration();
966            ifcg.setLinkAddress(mConfiguration.mStaticIpConfig.ipAddress);
967            ifcg.setInterfaceUp();
968            try {
969                mNwService.setInterfaceConfig(mInterfaceName, ifcg);
970                if (DBG) Log.d(mTag, "Static IP configuration succeeded");
971            } catch (IllegalStateException | RemoteException e) {
972                Log.e(mTag, "Static IP configuration failed: ", e);
973                return false;
974            }
975
976            return true;
977        }
978    }
979}
980