2 * Copyright (C) 2016 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package android.net.ip;
19 import com.android.internal.util.MessageUtils;
20 import com.android.internal.util.WakeupMessage;
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.IpConnectivityLog;
35 import android.net.metrics.IpManagerEvent;
36 import android.net.util.AvoidBadWifiTracker;
37 import android.os.INetworkManagementService;
38 import android.os.Message;
39 import android.os.RemoteException;
40 import android.os.ServiceManager;
41 import android.os.SystemClock;
42 import android.text.TextUtils;
43 import android.util.LocalLog;
44 import android.util.Log;
45 import android.util.SparseArray;
47 import com.android.internal.annotations.VisibleForTesting;
48 import com.android.internal.util.IndentingPrintWriter;
49 import com.android.internal.util.State;
50 import com.android.internal.util.StateMachine;
51 import com.android.server.net.NetlinkTracker;
53 import java.io.FileDescriptor;
54 import java.io.PrintWriter;
55 import java.net.InetAddress;
56 import java.net.NetworkInterface;
57 import java.net.SocketException;
58 import java.util.Objects;
59 import java.util.StringJoiner;
65 * This class provides the interface to IP-layer provisioning and maintenance
66 * functionality that can be used by transport layers like Wi-Fi, Ethernet,
70 * IpManager is designed to be instantiated as soon as the interface name is
71 * known and can be as long-lived as the class containing it (i.e. declaring
72 * it "private final" is okay).
76 public class IpManager extends StateMachine {
77 private static final boolean DBG = false;
78 private static final boolean VDBG = false;
80 // For message logging.
81 private static final Class[] sMessageClasses = { IpManager.class, DhcpClient.class };
82 private static final SparseArray<String> sWhatToString =
83 MessageUtils.findMessageNames(sMessageClasses);
86 * Callbacks for handling IpManager events.
88 public static class Callback {
89 // In order to receive onPreDhcpAction(), call #withPreDhcpAction()
90 // when constructing a ProvisioningConfiguration.
92 // Implementations of onPreDhcpAction() must call
93 // IpManager#completedPreDhcpAction() to indicate that DHCP is clear
95 public void onPreDhcpAction() {}
96 public void onPostDhcpAction() {}
98 // This is purely advisory and not an indication of provisioning
99 // success or failure. This is only here for callers that want to
100 // expose DHCPv4 results to other APIs (e.g., WifiInfo#setInetAddress).
101 // DHCPv4 or static IPv4 configuration failure or success can be
102 // determined by whether or not the passed-in DhcpResults object is
104 public void onNewDhcpResults(DhcpResults dhcpResults) {}
106 public void onProvisioningSuccess(LinkProperties newLp) {}
107 public void onProvisioningFailure(LinkProperties newLp) {}
109 // Invoked on LinkProperties changes.
110 public void onLinkPropertiesChange(LinkProperties newLp) {}
112 // Called when the internal IpReachabilityMonitor (if enabled) has
113 // detected the loss of a critical number of required neighbors.
114 public void onReachabilityLost(String logMsg) {}
116 // Called when the IpManager state machine terminates.
117 public void onQuit() {}
119 // Install an APF program to filter incoming packets.
120 public void installPacketFilter(byte[] filter) {}
122 // If multicast filtering cannot be accomplished with APF, this function will be called to
123 // actuate multicast filtering using another means.
124 public void setFallbackMulticastFilter(boolean enabled) {}
126 // Enabled/disable Neighbor Discover offload functionality. This is
127 // called, for example, whenever 464xlat is being started or stopped.
128 public void setNeighborDiscoveryOffload(boolean enable) {}
131 public static class WaitForProvisioningCallback extends Callback {
132 private LinkProperties mCallbackLinkProperties;
134 public LinkProperties waitForProvisioning() {
135 synchronized (this) {
138 } catch (InterruptedException e) {}
139 return mCallbackLinkProperties;
144 public void onProvisioningSuccess(LinkProperties newLp) {
145 synchronized (this) {
146 mCallbackLinkProperties = newLp;
152 public void onProvisioningFailure(LinkProperties newLp) {
153 synchronized (this) {
154 mCallbackLinkProperties = null;
160 // Use a wrapper class to log in order to ensure complete and detailed
161 // logging. This method is lighter weight than annotations/reflection
162 // and has the following benefits:
164 // - No invoked method can be forgotten.
165 // Any new method added to IpManager.Callback must be overridden
166 // here or it will never be called.
168 // - No invoking call site can be forgotten.
169 // Centralized logging in this way means call sites don't need to
170 // remember to log, and therefore no call site can be forgotten.
172 // - No variation in log format among call sites.
173 // Encourages logging of any available arguments, and all call sites
174 // are necessarily logged identically.
176 // TODO: Find an lighter weight approach.
177 private class LoggingCallbackWrapper extends Callback {
178 private static final String PREFIX = "INVOKE ";
179 private Callback mCallback;
181 public LoggingCallbackWrapper(Callback callback) {
182 mCallback = callback;
185 private void log(String msg) {
186 mLocalLog.log(PREFIX + msg);
190 public void onPreDhcpAction() {
191 mCallback.onPreDhcpAction();
192 log("onPreDhcpAction()");
195 public void onPostDhcpAction() {
196 mCallback.onPostDhcpAction();
197 log("onPostDhcpAction()");
200 public void onNewDhcpResults(DhcpResults dhcpResults) {
201 mCallback.onNewDhcpResults(dhcpResults);
202 log("onNewDhcpResults({" + dhcpResults + "})");
205 public void onProvisioningSuccess(LinkProperties newLp) {
206 mCallback.onProvisioningSuccess(newLp);
207 log("onProvisioningSuccess({" + newLp + "})");
210 public void onProvisioningFailure(LinkProperties newLp) {
211 mCallback.onProvisioningFailure(newLp);
212 log("onProvisioningFailure({" + newLp + "})");
215 public void onLinkPropertiesChange(LinkProperties newLp) {
216 mCallback.onLinkPropertiesChange(newLp);
217 log("onLinkPropertiesChange({" + newLp + "})");
220 public void onReachabilityLost(String logMsg) {
221 mCallback.onReachabilityLost(logMsg);
222 log("onReachabilityLost(" + logMsg + ")");
225 public void onQuit() {
230 public void installPacketFilter(byte[] filter) {
231 mCallback.installPacketFilter(filter);
232 log("installPacketFilter(byte[" + filter.length + "])");
235 public void setFallbackMulticastFilter(boolean enabled) {
236 mCallback.setFallbackMulticastFilter(enabled);
237 log("setFallbackMulticastFilter(" + enabled + ")");
240 public void setNeighborDiscoveryOffload(boolean enable) {
241 mCallback.setNeighborDiscoveryOffload(enable);
242 log("setNeighborDiscoveryOffload(" + enable + ")");
247 * This class encapsulates parameters to be passed to
248 * IpManager#startProvisioning(). A defensive copy is made by IpManager
249 * and the values specified herein are in force until IpManager#stop()
254 * final ProvisioningConfiguration config =
255 * mIpManager.buildProvisioningConfiguration()
256 * .withPreDhcpAction()
257 * .withProvisioningTimeoutMs(36 * 1000)
259 * mIpManager.startProvisioning(config);
263 * The specified provisioning configuration will only be active until
264 * IpManager#stop() is called. Future calls to IpManager#startProvisioning()
265 * must specify the configuration again.
267 public static class ProvisioningConfiguration {
268 // TODO: Delete this default timeout once those callers that care are
269 // fixed to pass in their preferred timeout.
271 // We pick 36 seconds so we can send DHCP requests at
273 // t=0, t=2, t=6, t=14, t=30
275 // allowing for 10% jitter.
276 private static final int DEFAULT_TIMEOUT_MS = 36 * 1000;
278 public static class Builder {
279 private ProvisioningConfiguration mConfig = new ProvisioningConfiguration();
281 public Builder withoutIPv4() {
282 mConfig.mEnableIPv4 = false;
286 public Builder withoutIPv6() {
287 mConfig.mEnableIPv6 = false;
291 public Builder withoutIpReachabilityMonitor() {
292 mConfig.mUsingIpReachabilityMonitor = false;
296 public Builder withPreDhcpAction() {
297 mConfig.mRequestedPreDhcpActionMs = DEFAULT_TIMEOUT_MS;
301 public Builder withPreDhcpAction(int dhcpActionTimeoutMs) {
302 mConfig.mRequestedPreDhcpActionMs = dhcpActionTimeoutMs;
306 public Builder withStaticConfiguration(StaticIpConfiguration staticConfig) {
307 mConfig.mStaticIpConfig = staticConfig;
311 public Builder withApfCapabilities(ApfCapabilities apfCapabilities) {
312 mConfig.mApfCapabilities = apfCapabilities;
316 public Builder withProvisioningTimeoutMs(int timeoutMs) {
317 mConfig.mProvisioningTimeoutMs = timeoutMs;
321 public ProvisioningConfiguration build() {
322 return new ProvisioningConfiguration(mConfig);
326 /* package */ boolean mEnableIPv4 = true;
327 /* package */ boolean mEnableIPv6 = true;
328 /* package */ boolean mUsingIpReachabilityMonitor = true;
329 /* package */ int mRequestedPreDhcpActionMs;
330 /* package */ StaticIpConfiguration mStaticIpConfig;
331 /* package */ ApfCapabilities mApfCapabilities;
332 /* package */ int mProvisioningTimeoutMs = DEFAULT_TIMEOUT_MS;
334 public ProvisioningConfiguration() {}
336 public ProvisioningConfiguration(ProvisioningConfiguration other) {
337 mEnableIPv4 = other.mEnableIPv4;
338 mEnableIPv6 = other.mEnableIPv6;
339 mUsingIpReachabilityMonitor = other.mUsingIpReachabilityMonitor;
340 mRequestedPreDhcpActionMs = other.mRequestedPreDhcpActionMs;
341 mStaticIpConfig = other.mStaticIpConfig;
342 mApfCapabilities = other.mApfCapabilities;
343 mProvisioningTimeoutMs = other.mProvisioningTimeoutMs;
347 public String toString() {
348 return new StringJoiner(", ", getClass().getSimpleName() + "{", "}")
349 .add("mEnableIPv4: " + mEnableIPv4)
350 .add("mEnableIPv6: " + mEnableIPv6)
351 .add("mUsingIpReachabilityMonitor: " + mUsingIpReachabilityMonitor)
352 .add("mRequestedPreDhcpActionMs: " + mRequestedPreDhcpActionMs)
353 .add("mStaticIpConfig: " + mStaticIpConfig)
354 .add("mApfCapabilities: " + mApfCapabilities)
355 .add("mProvisioningTimeoutMs: " + mProvisioningTimeoutMs)
360 public static final String DUMP_ARG = "ipmanager";
362 private static final int CMD_STOP = 1;
363 private static final int CMD_START = 2;
364 private static final int CMD_CONFIRM = 3;
365 private static final int EVENT_PRE_DHCP_ACTION_COMPLETE = 4;
366 // Sent by NetlinkTracker to communicate netlink events.
367 private static final int EVENT_NETLINK_LINKPROPERTIES_CHANGED = 5;
368 private static final int CMD_UPDATE_TCP_BUFFER_SIZES = 6;
369 private static final int CMD_UPDATE_HTTP_PROXY = 7;
370 private static final int CMD_SET_MULTICAST_FILTER = 8;
371 private static final int EVENT_PROVISIONING_TIMEOUT = 9;
372 private static final int EVENT_DHCPACTION_TIMEOUT = 10;
374 private static final int MAX_LOG_RECORDS = 500;
376 private static final boolean NO_CALLBACKS = false;
377 private static final boolean SEND_CALLBACKS = true;
379 // This must match the interface prefix in clatd.c.
380 // TODO: Revert this hack once IpManager and Nat464Xlat work in concert.
381 private static final String CLAT_PREFIX = "v4-";
383 private final State mStoppedState = new StoppedState();
384 private final State mStoppingState = new StoppingState();
385 private final State mStartedState = new StartedState();
387 private final String mTag;
388 private final Context mContext;
389 private final String mInterfaceName;
390 private final String mClatInterfaceName;
392 protected final Callback mCallback;
393 private final INetworkManagementService mNwService;
394 private final NetlinkTracker mNetlinkTracker;
395 private final WakeupMessage mProvisioningTimeoutAlarm;
396 private final WakeupMessage mDhcpActionTimeoutAlarm;
397 private final AvoidBadWifiTracker mAvoidBadWifiTracker;
398 private final LocalLog mLocalLog;
399 private final IpConnectivityLog mMetricsLog = new IpConnectivityLog();
401 private NetworkInterface mNetworkInterface;
404 * Non-final member variables accessed only from within our StateMachine.
406 private LinkProperties mLinkProperties;
407 private ProvisioningConfiguration mConfiguration;
408 private IpReachabilityMonitor mIpReachabilityMonitor;
409 private DhcpClient mDhcpClient;
410 private DhcpResults mDhcpResults;
411 private String mTcpBufferSizes;
412 private ProxyInfo mHttpProxy;
413 private ApfFilter mApfFilter;
414 private boolean mMulticastFiltering;
415 private long mStartTimeMillis;
417 public IpManager(Context context, String ifName, Callback callback)
418 throws IllegalArgumentException {
419 this(context, ifName, callback, INetworkManagementService.Stub.asInterface(
420 ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE)));
424 * An expanded constructor, useful for dependency injection.
426 public IpManager(Context context, String ifName, Callback callback,
427 INetworkManagementService nwService) throws IllegalArgumentException {
428 super(IpManager.class.getSimpleName() + "." + ifName);
432 mInterfaceName = ifName;
433 mClatInterfaceName = CLAT_PREFIX + ifName;
434 mCallback = new LoggingCallbackWrapper(callback);
435 mNwService = nwService;
437 mNetlinkTracker = new NetlinkTracker(
439 new NetlinkTracker.Callback() {
441 public void update() {
442 sendMessage(EVENT_NETLINK_LINKPROPERTIES_CHANGED);
446 public void interfaceAdded(String iface) {
447 super.interfaceAdded(iface);
448 if (mClatInterfaceName.equals(iface)) {
449 mCallback.setNeighborDiscoveryOffload(false);
454 public void interfaceRemoved(String iface) {
455 super.interfaceRemoved(iface);
456 if (mClatInterfaceName.equals(iface)) {
457 // TODO: consider sending a message to the IpManager main
458 // StateMachine thread, in case "NDO enabled" state becomes
459 // tied to more things that 464xlat operation.
460 mCallback.setNeighborDiscoveryOffload(true);
466 mNwService.registerObserver(mNetlinkTracker);
467 } catch (RemoteException e) {
468 Log.e(mTag, "Couldn't register NetlinkTracker: " + e.toString());
471 mAvoidBadWifiTracker = new AvoidBadWifiTracker(mContext, getHandler());
473 resetLinkProperties();
475 mProvisioningTimeoutAlarm = new WakeupMessage(mContext, getHandler(),
476 mTag + ".EVENT_PROVISIONING_TIMEOUT", EVENT_PROVISIONING_TIMEOUT);
477 mDhcpActionTimeoutAlarm = new WakeupMessage(mContext, getHandler(),
478 mTag + ".EVENT_DHCPACTION_TIMEOUT", EVENT_DHCPACTION_TIMEOUT);
480 // Super simple StateMachine.
481 addState(mStoppedState);
482 addState(mStartedState);
483 addState(mStoppingState);
485 setInitialState(mStoppedState);
486 mLocalLog = new LocalLog(MAX_LOG_RECORDS);
491 protected void onQuitting() {
495 // Shut down this IpManager instance altogether.
496 public void shutdown() {
501 public static ProvisioningConfiguration.Builder buildProvisioningConfiguration() {
502 return new ProvisioningConfiguration.Builder();
505 public void startProvisioning(ProvisioningConfiguration req) {
506 getNetworkInterface();
508 mCallback.setNeighborDiscoveryOffload(true);
509 sendMessage(CMD_START, new ProvisioningConfiguration(req));
512 // TODO: Delete this.
513 public void startProvisioning(StaticIpConfiguration staticIpConfig) {
514 startProvisioning(buildProvisioningConfiguration()
515 .withStaticConfiguration(staticIpConfig)
519 public void startProvisioning() {
520 startProvisioning(new ProvisioningConfiguration());
524 sendMessage(CMD_STOP);
527 public void confirmConfiguration() {
528 sendMessage(CMD_CONFIRM);
531 public void completedPreDhcpAction() {
532 sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE);
536 * Set the TCP buffer sizes to use.
538 * This may be called, repeatedly, at any time before or after a call to
539 * #startProvisioning(). The setting is cleared upon calling #stop().
541 public void setTcpBufferSizes(String tcpBufferSizes) {
542 sendMessage(CMD_UPDATE_TCP_BUFFER_SIZES, tcpBufferSizes);
546 * Set the HTTP Proxy configuration to use.
548 * This may be called, repeatedly, at any time before or after a call to
549 * #startProvisioning(). The setting is cleared upon calling #stop().
551 public void setHttpProxy(ProxyInfo proxyInfo) {
552 sendMessage(CMD_UPDATE_HTTP_PROXY, proxyInfo);
556 * Enable or disable the multicast filter. Attempts to use APF to accomplish the filtering,
557 * if not, Callback.setFallbackMulticastFilter() is called.
559 public void setMulticastFilter(boolean enabled) {
560 sendMessage(CMD_SET_MULTICAST_FILTER, enabled);
563 public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
564 IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
565 pw.println("APF dump:");
567 // Thread-unsafe access to mApfFilter but just used for debugging.
568 ApfFilter apfFilter = mApfFilter;
569 if (apfFilter != null) {
572 pw.println("No apf support");
577 pw.println("StateMachine dump:");
579 mLocalLog.readOnlyLocalLog().dump(fd, pw, args);
589 protected String getWhatToString(int what) {
590 return sWhatToString.get(what, "UNKNOWN: " + Integer.toString(what));
594 protected String getLogRecString(Message msg) {
595 final String logLine = String.format(
597 mInterfaceName, mNetworkInterface == null ? -1 : mNetworkInterface.getIndex(),
598 msg.arg1, msg.arg2, Objects.toString(msg.obj));
600 final String richerLogLine = getWhatToString(msg.what) + " " + logLine;
601 mLocalLog.log(richerLogLine);
603 Log.d(mTag, richerLogLine);
610 protected boolean recordLogRec(Message msg) {
611 // Don't log EVENT_NETLINK_LINKPROPERTIES_CHANGED. They can be noisy,
612 // and we already log any LinkProperties change that results in an
613 // invocation of IpManager.Callback#onLinkPropertiesChange().
614 return (msg.what != EVENT_NETLINK_LINKPROPERTIES_CHANGED);
617 private void getNetworkInterface() {
619 mNetworkInterface = NetworkInterface.getByName(mInterfaceName);
620 } catch (SocketException | NullPointerException e) {
621 // TODO: throw new IllegalStateException.
622 Log.e(mTag, "ALERT: Failed to get interface object: ", e);
626 // This needs to be called with care to ensure that our LinkProperties
627 // are in sync with the actual LinkProperties of the interface. For example,
628 // we should only call this if we know for sure that there are no IP addresses
629 // assigned to the interface, etc.
630 private void resetLinkProperties() {
631 mNetlinkTracker.clearLinkProperties();
632 mConfiguration = null;
634 mTcpBufferSizes = "";
637 mLinkProperties = new LinkProperties();
638 mLinkProperties.setInterfaceName(mInterfaceName);
641 private void recordMetric(final int type) {
642 if (mStartTimeMillis <= 0) { Log.wtf(mTag, "Start time undefined!"); }
643 final long duration = SystemClock.elapsedRealtime() - mStartTimeMillis;
644 mMetricsLog.log(new IpManagerEvent(mInterfaceName, type, duration));
647 // For now: use WifiStateMachine's historical notion of provisioned.
648 private static boolean isProvisioned(LinkProperties lp) {
649 // For historical reasons, we should connect even if all we have is
650 // an IPv4 address and nothing else.
651 return lp.isProvisioned() || lp.hasIPv4Address();
654 // TODO: Investigate folding all this into the existing static function
655 // LinkProperties.compareProvisioning() or some other single function that
656 // takes two LinkProperties objects and returns a ProvisioningChange
657 // object that is a correct and complete assessment of what changed, taking
658 // account of the asymmetries described in the comments in this function.
659 // Then switch to using it everywhere (IpReachabilityMonitor, etc.).
660 private static ProvisioningChange compareProvisioning(
661 LinkProperties oldLp, LinkProperties newLp) {
662 ProvisioningChange delta;
664 final boolean wasProvisioned = isProvisioned(oldLp);
665 final boolean isProvisioned = isProvisioned(newLp);
667 if (!wasProvisioned && isProvisioned) {
668 delta = ProvisioningChange.GAINED_PROVISIONING;
669 } else if (wasProvisioned && isProvisioned) {
670 delta = ProvisioningChange.STILL_PROVISIONED;
671 } else if (!wasProvisioned && !isProvisioned) {
672 delta = ProvisioningChange.STILL_NOT_PROVISIONED;
674 // (wasProvisioned && !isProvisioned)
676 // Note that this is true even if we lose a configuration element
677 // (e.g., a default gateway) that would not be required to advance
678 // into provisioned state. This is intended: if we have a default
679 // router and we lose it, that's a sure sign of a problem, but if
680 // we connect to a network with no IPv4 DNS servers, we consider
681 // that to be a network without DNS servers and connect anyway.
683 // See the comment below.
684 delta = ProvisioningChange.LOST_PROVISIONING;
687 final boolean lostIPv6 = oldLp.isIPv6Provisioned() && !newLp.isIPv6Provisioned();
688 final boolean lostIPv4Address = oldLp.hasIPv4Address() && !newLp.hasIPv4Address();
689 final boolean lostIPv6Router = oldLp.hasIPv6DefaultRoute() && !newLp.hasIPv6DefaultRoute();
693 // Partial configurations (e.g., only an IPv4 address with no DNS
694 // servers and no default route) are accepted as long as DHCPv4
695 // succeeds. On such a network, isProvisioned() will always return
696 // false, because the configuration is not complete, but we want to
697 // connect anyway. It might be a disconnected network such as a
698 // Chromecast or a wireless printer, for example.
700 // Because on such a network isProvisioned() will always return false,
701 // delta will never be LOST_PROVISIONING. So check for loss of
702 // provisioning here too.
703 if (lostIPv4Address || lostIPv6) {
704 delta = ProvisioningChange.LOST_PROVISIONING;
709 // If the previous link properties had a global IPv6 address and an
710 // IPv6 default route then also consider the loss of that default route
711 // to be a loss of provisioning. See b/27962810.
712 if (oldLp.hasGlobalIPv6Address() && lostIPv6Router) {
713 delta = ProvisioningChange.LOST_PROVISIONING;
719 private void dispatchCallback(ProvisioningChange delta, LinkProperties newLp) {
721 case GAINED_PROVISIONING:
722 if (VDBG) { Log.d(mTag, "onProvisioningSuccess()"); }
723 recordMetric(IpManagerEvent.PROVISIONING_OK);
724 mCallback.onProvisioningSuccess(newLp);
727 case LOST_PROVISIONING:
728 if (VDBG) { Log.d(mTag, "onProvisioningFailure()"); }
729 recordMetric(IpManagerEvent.PROVISIONING_FAIL);
730 mCallback.onProvisioningFailure(newLp);
734 if (VDBG) { Log.d(mTag, "onLinkPropertiesChange()"); }
735 mCallback.onLinkPropertiesChange(newLp);
740 // Updates all IpManager-related state concerned with LinkProperties.
741 // Returns a ProvisioningChange for possibly notifying other interested
742 // parties that are not fronted by IpManager.
743 private ProvisioningChange setLinkProperties(LinkProperties newLp) {
744 if (mApfFilter != null) {
745 mApfFilter.setLinkProperties(newLp);
747 if (mIpReachabilityMonitor != null) {
748 mIpReachabilityMonitor.updateLinkProperties(newLp);
751 ProvisioningChange delta = compareProvisioning(mLinkProperties, newLp);
752 mLinkProperties = new LinkProperties(newLp);
754 if (delta == ProvisioningChange.GAINED_PROVISIONING) {
755 // TODO: Add a proper ProvisionedState and cancel the alarm in
756 // its enter() method.
757 mProvisioningTimeoutAlarm.cancel();
763 private boolean linkPropertiesUnchanged(LinkProperties newLp) {
764 return Objects.equals(newLp, mLinkProperties);
767 private LinkProperties assembleLinkProperties() {
768 // [1] Create a new LinkProperties object to populate.
769 LinkProperties newLp = new LinkProperties();
770 newLp.setInterfaceName(mInterfaceName);
772 // [2] Pull in data from netlink:
776 // - IPv6 DNS servers
777 LinkProperties netlinkLinkProperties = mNetlinkTracker.getLinkProperties();
778 newLp.setLinkAddresses(netlinkLinkProperties.getLinkAddresses());
779 for (RouteInfo route : netlinkLinkProperties.getRoutes()) {
780 newLp.addRoute(route);
782 for (InetAddress dns : netlinkLinkProperties.getDnsServers()) {
783 // Only add likely reachable DNS servers.
784 // TODO: investigate deleting this.
785 if (newLp.isReachable(dns)) {
786 newLp.addDnsServer(dns);
790 // [3] Add in data from DHCPv4, if available.
792 // mDhcpResults is never shared with any other owner so we don't have
793 // to worry about concurrent modification.
794 if (mDhcpResults != null) {
795 for (RouteInfo route : mDhcpResults.getRoutes(mInterfaceName)) {
796 newLp.addRoute(route);
798 for (InetAddress dns : mDhcpResults.dnsServers) {
799 // Only add likely reachable DNS servers.
800 // TODO: investigate deleting this.
801 if (newLp.isReachable(dns)) {
802 newLp.addDnsServer(dns);
805 newLp.setDomains(mDhcpResults.domains);
807 if (mDhcpResults.mtu != 0) {
808 newLp.setMtu(mDhcpResults.mtu);
812 // [4] Add in TCP buffer sizes and HTTP Proxy config, if available.
813 if (!TextUtils.isEmpty(mTcpBufferSizes)) {
814 newLp.setTcpBufferSizes(mTcpBufferSizes);
816 if (mHttpProxy != null) {
817 newLp.setHttpProxy(mHttpProxy);
821 Log.d(mTag, "newLp{" + newLp + "}");
826 // Returns false if we have lost provisioning, true otherwise.
827 private boolean handleLinkPropertiesUpdate(boolean sendCallbacks) {
828 final LinkProperties newLp = assembleLinkProperties();
829 if (linkPropertiesUnchanged(newLp)) {
832 final ProvisioningChange delta = setLinkProperties(newLp);
834 dispatchCallback(delta, newLp);
836 return (delta != ProvisioningChange.LOST_PROVISIONING);
839 private boolean setIPv4Address(LinkAddress address) {
840 final InterfaceConfiguration ifcg = new InterfaceConfiguration();
841 ifcg.setLinkAddress(address);
843 mNwService.setInterfaceConfig(mInterfaceName, ifcg);
844 if (VDBG) Log.d(mTag, "IPv4 configuration succeeded");
845 } catch (IllegalStateException | RemoteException e) {
846 Log.e(mTag, "IPv4 configuration failed: ", e);
852 private void clearIPv4Address() {
854 final InterfaceConfiguration ifcg = new InterfaceConfiguration();
855 ifcg.setLinkAddress(new LinkAddress("0.0.0.0/0"));
856 mNwService.setInterfaceConfig(mInterfaceName, ifcg);
857 } catch (IllegalStateException | RemoteException e) {
858 Log.e(mTag, "ALERT: Failed to clear IPv4 address on interface " + mInterfaceName, e);
862 private void handleIPv4Success(DhcpResults dhcpResults) {
863 mDhcpResults = new DhcpResults(dhcpResults);
864 final LinkProperties newLp = assembleLinkProperties();
865 final ProvisioningChange delta = setLinkProperties(newLp);
868 Log.d(mTag, "onNewDhcpResults(" + Objects.toString(dhcpResults) + ")");
870 mCallback.onNewDhcpResults(dhcpResults);
871 dispatchCallback(delta, newLp);
874 private void handleIPv4Failure() {
875 // TODO: Investigate deleting this clearIPv4Address() call.
877 // DhcpClient will send us CMD_CLEAR_LINKADDRESS in all circumstances
878 // that could trigger a call to this function. If we missed handling
879 // that message in StartedState for some reason we would still clear
880 // any addresses upon entry to StoppedState.
883 if (VDBG) { Log.d(mTag, "onNewDhcpResults(null)"); }
884 mCallback.onNewDhcpResults(null);
886 handleProvisioningFailure();
889 private void handleProvisioningFailure() {
890 final LinkProperties newLp = assembleLinkProperties();
891 ProvisioningChange delta = setLinkProperties(newLp);
892 // If we've gotten here and we're still not provisioned treat that as
893 // a total loss of provisioning.
895 // Either (a) static IP configuration failed or (b) DHCPv4 failed AND
896 // there was no usable IPv6 obtained before a non-zero provisioning
899 // Regardless: GAME OVER.
900 if (delta == ProvisioningChange.STILL_NOT_PROVISIONED) {
901 delta = ProvisioningChange.LOST_PROVISIONING;
904 dispatchCallback(delta, newLp);
905 if (delta == ProvisioningChange.LOST_PROVISIONING) {
906 transitionTo(mStoppingState);
910 private boolean startIPv4() {
911 // If we have a StaticIpConfiguration attempt to apply it and
912 // handle the result accordingly.
913 if (mConfiguration.mStaticIpConfig != null) {
914 if (setIPv4Address(mConfiguration.mStaticIpConfig.ipAddress)) {
915 handleIPv4Success(new DhcpResults(mConfiguration.mStaticIpConfig));
917 if (VDBG) { Log.d(mTag, "onProvisioningFailure()"); }
918 recordMetric(IpManagerEvent.PROVISIONING_FAIL);
919 mCallback.onProvisioningFailure(new LinkProperties(mLinkProperties));
924 mDhcpClient = DhcpClient.makeDhcpClient(mContext, IpManager.this, mInterfaceName);
925 mDhcpClient.registerForPreDhcpNotification();
926 mDhcpClient.sendMessage(DhcpClient.CMD_START_DHCP);
932 private boolean startIPv6() {
933 // Set privacy extensions.
935 mNwService.setInterfaceIpv6PrivacyExtensions(mInterfaceName, true);
936 mNwService.enableIpv6(mInterfaceName);
937 } catch (RemoteException re) {
938 Log.e(mTag, "Unable to change interface settings: " + re);
940 } catch (IllegalStateException ie) {
941 Log.e(mTag, "Unable to change interface settings: " + ie);
949 class StoppedState extends State {
951 public void enter() {
953 mNwService.disableIpv6(mInterfaceName);
954 mNwService.clearInterfaceAddresses(mInterfaceName);
955 } catch (Exception e) {
956 Log.e(mTag, "Failed to clear addresses or disable IPv6" + e);
959 resetLinkProperties();
960 if (mStartTimeMillis > 0) {
961 recordMetric(IpManagerEvent.COMPLETE_LIFECYCLE);
962 mStartTimeMillis = 0;
967 public boolean processMessage(Message msg) {
973 mConfiguration = (ProvisioningConfiguration) msg.obj;
974 transitionTo(mStartedState);
977 case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
978 handleLinkPropertiesUpdate(NO_CALLBACKS);
981 case CMD_UPDATE_TCP_BUFFER_SIZES:
982 mTcpBufferSizes = (String) msg.obj;
983 handleLinkPropertiesUpdate(NO_CALLBACKS);
986 case CMD_UPDATE_HTTP_PROXY:
987 mHttpProxy = (ProxyInfo) msg.obj;
988 handleLinkPropertiesUpdate(NO_CALLBACKS);
991 case CMD_SET_MULTICAST_FILTER:
992 mMulticastFiltering = (boolean) msg.obj;
995 case DhcpClient.CMD_ON_QUIT:
996 // Everything is already stopped.
997 Log.e(mTag, "Unexpected CMD_ON_QUIT (already stopped).");
1007 class StoppingState extends State {
1009 public void enter() {
1010 if (mDhcpClient == null) {
1011 // There's no DHCPv4 for which to wait; proceed to stopped.
1012 transitionTo(mStoppedState);
1017 public boolean processMessage(Message msg) {
1019 case DhcpClient.CMD_ON_QUIT:
1021 transitionTo(mStoppedState);
1031 class StartedState extends State {
1032 private boolean mDhcpActionInFlight;
1035 public void enter() {
1036 mStartTimeMillis = SystemClock.elapsedRealtime();
1038 mApfFilter = ApfFilter.maybeCreate(mConfiguration.mApfCapabilities, mNetworkInterface,
1039 mCallback, mMulticastFiltering);
1040 // TODO: investigate the effects of any multicast filtering racing/interfering with the
1041 // rest of this IP configuration startup.
1042 if (mApfFilter == null) {
1043 mCallback.setFallbackMulticastFilter(mMulticastFiltering);
1046 if (mConfiguration.mProvisioningTimeoutMs > 0) {
1047 final long alarmTime = SystemClock.elapsedRealtime() +
1048 mConfiguration.mProvisioningTimeoutMs;
1049 mProvisioningTimeoutAlarm.schedule(alarmTime);
1052 if (mConfiguration.mEnableIPv6) {
1053 // TODO: Consider transitionTo(mStoppingState) if this fails.
1057 if (mConfiguration.mEnableIPv4) {
1059 transitionTo(mStoppingState);
1064 if (mConfiguration.mUsingIpReachabilityMonitor) {
1065 mIpReachabilityMonitor = new IpReachabilityMonitor(
1068 new IpReachabilityMonitor.Callback() {
1070 public void notifyLost(InetAddress ip, String logMsg) {
1071 mCallback.onReachabilityLost(logMsg);
1074 mAvoidBadWifiTracker);
1079 public void exit() {
1080 mProvisioningTimeoutAlarm.cancel();
1083 if (mIpReachabilityMonitor != null) {
1084 mIpReachabilityMonitor.stop();
1085 mIpReachabilityMonitor = null;
1088 if (mDhcpClient != null) {
1089 mDhcpClient.sendMessage(DhcpClient.CMD_STOP_DHCP);
1090 mDhcpClient.doQuit();
1093 if (mApfFilter != null) {
1094 mApfFilter.shutdown();
1098 resetLinkProperties();
1101 private void ensureDhcpAction() {
1102 if (!mDhcpActionInFlight) {
1103 mCallback.onPreDhcpAction();
1104 mDhcpActionInFlight = true;
1105 final long alarmTime = SystemClock.elapsedRealtime() +
1106 mConfiguration.mRequestedPreDhcpActionMs;
1107 mDhcpActionTimeoutAlarm.schedule(alarmTime);
1111 private void stopDhcpAction() {
1112 mDhcpActionTimeoutAlarm.cancel();
1113 if (mDhcpActionInFlight) {
1114 mCallback.onPostDhcpAction();
1115 mDhcpActionInFlight = false;
1120 public boolean processMessage(Message msg) {
1123 transitionTo(mStoppingState);
1127 Log.e(mTag, "ALERT: START received in StartedState. Please fix caller.");
1131 // TODO: Possibly introduce a second type of confirmation
1132 // that both probes (a) on-link neighbors and (b) does
1133 // a DHCPv4 RENEW. We used to do this on Wi-Fi framework
1135 if (mIpReachabilityMonitor != null) {
1136 mIpReachabilityMonitor.probeAll();
1140 case EVENT_PRE_DHCP_ACTION_COMPLETE:
1141 // It's possible to reach here if, for example, someone
1142 // calls completedPreDhcpAction() after provisioning with
1143 // a static IP configuration.
1144 if (mDhcpClient != null) {
1145 mDhcpClient.sendMessage(DhcpClient.CMD_PRE_DHCP_ACTION_COMPLETE);
1149 case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
1150 if (!handleLinkPropertiesUpdate(SEND_CALLBACKS)) {
1151 transitionTo(mStoppingState);
1155 case CMD_UPDATE_TCP_BUFFER_SIZES:
1156 mTcpBufferSizes = (String) msg.obj;
1157 // This cannot possibly change provisioning state.
1158 handleLinkPropertiesUpdate(SEND_CALLBACKS);
1161 case CMD_UPDATE_HTTP_PROXY:
1162 mHttpProxy = (ProxyInfo) msg.obj;
1163 // This cannot possibly change provisioning state.
1164 handleLinkPropertiesUpdate(SEND_CALLBACKS);
1167 case CMD_SET_MULTICAST_FILTER: {
1168 mMulticastFiltering = (boolean) msg.obj;
1169 if (mApfFilter != null) {
1170 mApfFilter.setMulticastFilter(mMulticastFiltering);
1172 mCallback.setFallbackMulticastFilter(mMulticastFiltering);
1177 case EVENT_PROVISIONING_TIMEOUT:
1178 handleProvisioningFailure();
1181 case EVENT_DHCPACTION_TIMEOUT:
1185 case DhcpClient.CMD_PRE_DHCP_ACTION:
1186 if (mConfiguration.mRequestedPreDhcpActionMs > 0) {
1189 sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE);
1193 case DhcpClient.CMD_CLEAR_LINKADDRESS:
1197 case DhcpClient.CMD_CONFIGURE_LINKADDRESS: {
1198 final LinkAddress ipAddress = (LinkAddress) msg.obj;
1199 if (setIPv4Address(ipAddress)) {
1200 mDhcpClient.sendMessage(DhcpClient.EVENT_LINKADDRESS_CONFIGURED);
1202 Log.e(mTag, "Failed to set IPv4 address!");
1203 dispatchCallback(ProvisioningChange.LOST_PROVISIONING,
1204 new LinkProperties(mLinkProperties));
1205 transitionTo(mStoppingState);
1210 // This message is only received when:
1212 // a) initial address acquisition succeeds,
1213 // b) renew succeeds or is NAK'd,
1214 // c) rebind succeeds or is NAK'd, or
1215 // c) the lease expires,
1217 // but never when initial address acquisition fails. The latter
1218 // condition is now governed by the provisioning timeout.
1219 case DhcpClient.CMD_POST_DHCP_ACTION:
1223 case DhcpClient.DHCP_SUCCESS:
1224 handleIPv4Success((DhcpResults) msg.obj);
1226 case DhcpClient.DHCP_FAILURE:
1227 handleIPv4Failure();
1230 Log.e(mTag, "Unknown CMD_POST_DHCP_ACTION status:" + msg.arg1);
1234 case DhcpClient.CMD_ON_QUIT:
1235 // DHCPv4 quit early for some reason.
1236 Log.e(mTag, "Unexpected CMD_ON_QUIT.");