diff options
Diffstat (limited to 'android/net/ip/IpManager.java')
-rw-r--r-- | android/net/ip/IpManager.java | 1689 |
1 files changed, 71 insertions, 1618 deletions
diff --git a/android/net/ip/IpManager.java b/android/net/ip/IpManager.java index bc07b810..b12cb32c 100644 --- a/android/net/ip/IpManager.java +++ b/android/net/ip/IpManager.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2016 The Android Open Source Project + * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,1708 +16,161 @@ package android.net.ip; -import com.android.internal.util.MessageUtils; -import com.android.internal.util.WakeupMessage; - import android.content.Context; -import android.net.DhcpResults; import android.net.INetd; -import android.net.IpPrefix; -import android.net.LinkAddress; -import android.net.LinkProperties.ProvisioningChange; import android.net.LinkProperties; -import android.net.ProxyInfo; -import android.net.RouteInfo; +import android.net.Network; import android.net.StaticIpConfiguration; import android.net.apf.ApfCapabilities; -import android.net.apf.ApfFilter; -import android.net.dhcp.DhcpClient; -import android.net.metrics.IpConnectivityLog; -import android.net.metrics.IpManagerEvent; -import android.net.util.MultinetworkPolicyTracker; import android.net.util.NetdService; -import android.net.util.NetworkConstants; -import android.net.util.SharedLog; import android.os.INetworkManagementService; -import android.os.Message; -import android.os.RemoteException; import android.os.ServiceManager; -import android.os.SystemClock; -import android.text.TextUtils; -import android.util.LocalLog; -import android.util.Log; -import android.util.SparseArray; +import android.net.apf.ApfCapabilities; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.R; -import com.android.internal.util.IndentingPrintWriter; -import com.android.internal.util.IState; -import com.android.internal.util.Preconditions; -import com.android.internal.util.State; -import com.android.internal.util.StateMachine; -import com.android.server.net.NetlinkTracker; -import java.io.FileDescriptor; -import java.io.PrintWriter; -import java.net.Inet4Address; -import java.net.Inet6Address; -import java.net.InetAddress; -import java.net.NetworkInterface; -import java.net.SocketException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.Objects; -import java.util.List; -import java.util.Set; -import java.util.StringJoiner; -import java.util.function.Predicate; -import java.util.stream.Collectors; - -/** - * IpManager - * - * This class provides the interface to IP-layer provisioning and maintenance - * functionality that can be used by transport layers like Wi-Fi, Ethernet, - * et cetera. - * - * [ Lifetime ] - * IpManager is designed to be instantiated as soon as the interface name is - * known and can be as long-lived as the class containing it (i.e. declaring - * it "private final" is okay). +/* + * TODO: Delete this altogether in favor of its renamed successor: IpClient. * * @hide */ -public class IpManager extends StateMachine { - private static final boolean DBG = false; - - // For message logging. - private static final Class[] sMessageClasses = { IpManager.class, DhcpClient.class }; - private static final SparseArray<String> sWhatToString = - MessageUtils.findMessageNames(sMessageClasses); - - /** - * Callbacks for handling IpManager events. - */ - public static class Callback { - // In order to receive onPreDhcpAction(), call #withPreDhcpAction() - // when constructing a ProvisioningConfiguration. - // - // Implementations of onPreDhcpAction() must call - // IpManager#completedPreDhcpAction() to indicate that DHCP is clear - // to proceed. - public void onPreDhcpAction() {} - public void onPostDhcpAction() {} - - // This is purely advisory and not an indication of provisioning - // success or failure. This is only here for callers that want to - // expose DHCPv4 results to other APIs (e.g., WifiInfo#setInetAddress). - // DHCPv4 or static IPv4 configuration failure or success can be - // determined by whether or not the passed-in DhcpResults object is - // null or not. - public void onNewDhcpResults(DhcpResults dhcpResults) {} - - public void onProvisioningSuccess(LinkProperties newLp) {} - public void onProvisioningFailure(LinkProperties newLp) {} - - // Invoked on LinkProperties changes. - public void onLinkPropertiesChange(LinkProperties newLp) {} - - // Called when the internal IpReachabilityMonitor (if enabled) has - // detected the loss of a critical number of required neighbors. - public void onReachabilityLost(String logMsg) {} - - // Called when the IpManager state machine terminates. - public void onQuit() {} - - // Install an APF program to filter incoming packets. - public void installPacketFilter(byte[] filter) {} - - // If multicast filtering cannot be accomplished with APF, this function will be called to - // actuate multicast filtering using another means. - public void setFallbackMulticastFilter(boolean enabled) {} - - // Enabled/disable Neighbor Discover offload functionality. This is - // called, for example, whenever 464xlat is being started or stopped. - public void setNeighborDiscoveryOffload(boolean enable) {} - } - - public static class WaitForProvisioningCallback extends Callback { - private LinkProperties mCallbackLinkProperties; - - public LinkProperties waitForProvisioning() { - synchronized (this) { - try { - wait(); - } catch (InterruptedException e) {} - return mCallbackLinkProperties; - } +public class IpManager extends IpClient { + public static class ProvisioningConfiguration extends IpClient.ProvisioningConfiguration { + public ProvisioningConfiguration(IpClient.ProvisioningConfiguration ipcConfig) { + super(ipcConfig); } - @Override - public void onProvisioningSuccess(LinkProperties newLp) { - synchronized (this) { - mCallbackLinkProperties = newLp; - notify(); - } - } - - @Override - public void onProvisioningFailure(LinkProperties newLp) { - synchronized (this) { - mCallbackLinkProperties = null; - notify(); - } - } - } - - // Use a wrapper class to log in order to ensure complete and detailed - // logging. This method is lighter weight than annotations/reflection - // and has the following benefits: - // - // - No invoked method can be forgotten. - // Any new method added to IpManager.Callback must be overridden - // here or it will never be called. - // - // - No invoking call site can be forgotten. - // Centralized logging in this way means call sites don't need to - // remember to log, and therefore no call site can be forgotten. - // - // - No variation in log format among call sites. - // Encourages logging of any available arguments, and all call sites - // are necessarily logged identically. - // - // TODO: Find an lighter weight approach. - private class LoggingCallbackWrapper extends Callback { - private static final String PREFIX = "INVOKE "; - private Callback mCallback; - - public LoggingCallbackWrapper(Callback callback) { - mCallback = callback; - } - - private void log(String msg) { - mLog.log(PREFIX + msg); - } - - @Override - public void onPreDhcpAction() { - mCallback.onPreDhcpAction(); - log("onPreDhcpAction()"); - } - @Override - public void onPostDhcpAction() { - mCallback.onPostDhcpAction(); - log("onPostDhcpAction()"); - } - @Override - public void onNewDhcpResults(DhcpResults dhcpResults) { - mCallback.onNewDhcpResults(dhcpResults); - log("onNewDhcpResults({" + dhcpResults + "})"); - } - @Override - public void onProvisioningSuccess(LinkProperties newLp) { - mCallback.onProvisioningSuccess(newLp); - log("onProvisioningSuccess({" + newLp + "})"); - } - @Override - public void onProvisioningFailure(LinkProperties newLp) { - mCallback.onProvisioningFailure(newLp); - log("onProvisioningFailure({" + newLp + "})"); - } - @Override - public void onLinkPropertiesChange(LinkProperties newLp) { - mCallback.onLinkPropertiesChange(newLp); - log("onLinkPropertiesChange({" + newLp + "})"); - } - @Override - public void onReachabilityLost(String logMsg) { - mCallback.onReachabilityLost(logMsg); - log("onReachabilityLost(" + logMsg + ")"); - } - @Override - public void onQuit() { - mCallback.onQuit(); - log("onQuit()"); - } - @Override - public void installPacketFilter(byte[] filter) { - mCallback.installPacketFilter(filter); - log("installPacketFilter(byte[" + filter.length + "])"); - } - @Override - public void setFallbackMulticastFilter(boolean enabled) { - mCallback.setFallbackMulticastFilter(enabled); - log("setFallbackMulticastFilter(" + enabled + ")"); - } - @Override - public void setNeighborDiscoveryOffload(boolean enable) { - mCallback.setNeighborDiscoveryOffload(enable); - log("setNeighborDiscoveryOffload(" + enable + ")"); - } - } - - /** - * This class encapsulates parameters to be passed to - * IpManager#startProvisioning(). A defensive copy is made by IpManager - * and the values specified herein are in force until IpManager#stop() - * is called. - * - * Example use: - * - * final ProvisioningConfiguration config = - * mIpManager.buildProvisioningConfiguration() - * .withPreDhcpAction() - * .withProvisioningTimeoutMs(36 * 1000) - * .build(); - * mIpManager.startProvisioning(config); - * ... - * mIpManager.stop(); - * - * The specified provisioning configuration will only be active until - * IpManager#stop() is called. Future calls to IpManager#startProvisioning() - * must specify the configuration again. - */ - public static class ProvisioningConfiguration { - // TODO: Delete this default timeout once those callers that care are - // fixed to pass in their preferred timeout. - // - // We pick 36 seconds so we can send DHCP requests at - // - // t=0, t=2, t=6, t=14, t=30 - // - // allowing for 10% jitter. - private static final int DEFAULT_TIMEOUT_MS = 36 * 1000; - - public static class Builder { - private ProvisioningConfiguration mConfig = new ProvisioningConfiguration(); - + public static class Builder extends IpClient.ProvisioningConfiguration.Builder { + @Override public Builder withoutIPv4() { - mConfig.mEnableIPv4 = false; + super.withoutIPv4(); return this; } - + @Override public Builder withoutIPv6() { - mConfig.mEnableIPv6 = false; + super.withoutIPv6(); return this; } - + @Override public Builder withoutIpReachabilityMonitor() { - mConfig.mUsingIpReachabilityMonitor = false; + super.withoutIpReachabilityMonitor(); return this; } - + @Override public Builder withPreDhcpAction() { - mConfig.mRequestedPreDhcpActionMs = DEFAULT_TIMEOUT_MS; + super.withPreDhcpAction(); return this; } - + @Override public Builder withPreDhcpAction(int dhcpActionTimeoutMs) { - mConfig.mRequestedPreDhcpActionMs = dhcpActionTimeoutMs; + super.withPreDhcpAction(dhcpActionTimeoutMs); return this; } - + // No Override; locally defined type. public Builder withInitialConfiguration(InitialConfiguration initialConfig) { - mConfig.mInitialConfig = initialConfig; + super.withInitialConfiguration((IpClient.InitialConfiguration) initialConfig); return this; } - + @Override public Builder withStaticConfiguration(StaticIpConfiguration staticConfig) { - mConfig.mStaticIpConfig = staticConfig; + super.withStaticConfiguration(staticConfig); return this; } - + @Override public Builder withApfCapabilities(ApfCapabilities apfCapabilities) { - mConfig.mApfCapabilities = apfCapabilities; + super.withApfCapabilities(apfCapabilities); return this; } - + @Override public Builder withProvisioningTimeoutMs(int timeoutMs) { - mConfig.mProvisioningTimeoutMs = timeoutMs; + super.withProvisioningTimeoutMs(timeoutMs); return this; } - + @Override public Builder withIPv6AddrGenModeEUI64() { - mConfig.mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_EUI64; + super.withIPv6AddrGenModeEUI64(); return this; } - + @Override public Builder withIPv6AddrGenModeStablePrivacy() { - mConfig.mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_STABLE_PRIVACY; + super.withIPv6AddrGenModeStablePrivacy(); return this; } - + @Override + public Builder withNetwork(Network network) { + super.withNetwork(network); + return this; + } + @Override + public Builder withDisplayName(String displayName) { + super.withDisplayName(displayName); + return this; + } + @Override public ProvisioningConfiguration build() { - return new ProvisioningConfiguration(mConfig); + return new ProvisioningConfiguration(super.build()); } } + } - /* package */ boolean mEnableIPv4 = true; - /* package */ boolean mEnableIPv6 = true; - /* package */ boolean mUsingIpReachabilityMonitor = true; - /* package */ int mRequestedPreDhcpActionMs; - /* package */ InitialConfiguration mInitialConfig; - /* package */ StaticIpConfiguration mStaticIpConfig; - /* package */ ApfCapabilities mApfCapabilities; - /* package */ int mProvisioningTimeoutMs = DEFAULT_TIMEOUT_MS; - /* package */ int mIPv6AddrGenMode = INetd.IPV6_ADDR_GEN_MODE_STABLE_PRIVACY; - - public ProvisioningConfiguration() {} // used by Builder - - public ProvisioningConfiguration(ProvisioningConfiguration other) { - mEnableIPv4 = other.mEnableIPv4; - mEnableIPv6 = other.mEnableIPv6; - mUsingIpReachabilityMonitor = other.mUsingIpReachabilityMonitor; - mRequestedPreDhcpActionMs = other.mRequestedPreDhcpActionMs; - mInitialConfig = InitialConfiguration.copy(other.mInitialConfig); - mStaticIpConfig = other.mStaticIpConfig; - mApfCapabilities = other.mApfCapabilities; - mProvisioningTimeoutMs = other.mProvisioningTimeoutMs; - } + public static ProvisioningConfiguration.Builder buildProvisioningConfiguration() { + return new ProvisioningConfiguration.Builder(); + } - @Override - public String toString() { - return new StringJoiner(", ", getClass().getSimpleName() + "{", "}") - .add("mEnableIPv4: " + mEnableIPv4) - .add("mEnableIPv6: " + mEnableIPv6) - .add("mUsingIpReachabilityMonitor: " + mUsingIpReachabilityMonitor) - .add("mRequestedPreDhcpActionMs: " + mRequestedPreDhcpActionMs) - .add("mInitialConfig: " + mInitialConfig) - .add("mStaticIpConfig: " + mStaticIpConfig) - .add("mApfCapabilities: " + mApfCapabilities) - .add("mProvisioningTimeoutMs: " + mProvisioningTimeoutMs) - .add("mIPv6AddrGenMode: " + mIPv6AddrGenMode) - .toString(); - } + public static class InitialConfiguration extends IpClient.InitialConfiguration { + } - public boolean isValid() { - return (mInitialConfig == null) || mInitialConfig.isValid(); - } + public static class Callback extends IpClient.Callback { } - public static class InitialConfiguration { - public final Set<LinkAddress> ipAddresses = new HashSet<>(); - public final Set<IpPrefix> directlyConnectedRoutes = new HashSet<>(); - public final Set<InetAddress> dnsServers = new HashSet<>(); - public Inet4Address gateway; // WiFi legacy behavior with static ipv4 config + public static class WaitForProvisioningCallback extends Callback { + private LinkProperties mCallbackLinkProperties; - public static InitialConfiguration copy(InitialConfiguration config) { - if (config == null) { - return null; + public LinkProperties waitForProvisioning() { + synchronized (this) { + try { + wait(); + } catch (InterruptedException e) {} + return mCallbackLinkProperties; } - InitialConfiguration configCopy = new InitialConfiguration(); - configCopy.ipAddresses.addAll(config.ipAddresses); - configCopy.directlyConnectedRoutes.addAll(config.directlyConnectedRoutes); - configCopy.dnsServers.addAll(config.dnsServers); - return configCopy; } @Override - public String toString() { - return String.format( - "InitialConfiguration(IPs: {%s}, prefixes: {%s}, DNS: {%s}, v4 gateway: %s)", - join(", ", ipAddresses), join(", ", directlyConnectedRoutes), - join(", ", dnsServers), gateway); - } - - public boolean isValid() { - if (ipAddresses.isEmpty()) { - return false; - } - - // For every IP address, there must be at least one prefix containing that address. - for (LinkAddress addr : ipAddresses) { - if (!any(directlyConnectedRoutes, (p) -> p.contains(addr.getAddress()))) { - return false; - } - } - // For every dns server, there must be at least one prefix containing that address. - for (InetAddress addr : dnsServers) { - if (!any(directlyConnectedRoutes, (p) -> p.contains(addr))) { - return false; - } - } - // All IPv6 LinkAddresses have an RFC7421-suitable prefix length - // (read: compliant with RFC4291#section2.5.4). - if (any(ipAddresses, not(InitialConfiguration::isPrefixLengthCompliant))) { - return false; - } - // If directlyConnectedRoutes contains an IPv6 default route - // then ipAddresses MUST contain at least one non-ULA GUA. - if (any(directlyConnectedRoutes, InitialConfiguration::isIPv6DefaultRoute) - && all(ipAddresses, not(InitialConfiguration::isIPv6GUA))) { - return false; - } - // The prefix length of routes in directlyConnectedRoutes be within reasonable - // bounds for IPv6: /48-/64 just as we’d accept in RIOs. - if (any(directlyConnectedRoutes, not(InitialConfiguration::isPrefixLengthCompliant))) { - return false; - } - // There no more than one IPv4 address - if (ipAddresses.stream().filter(Inet4Address.class::isInstance).count() > 1) { - return false; + public void onProvisioningSuccess(LinkProperties newLp) { + synchronized (this) { + mCallbackLinkProperties = newLp; + notify(); } - - return true; } - /** - * @return true if the given list of addressess and routes satisfies provisioning for this - * InitialConfiguration. LinkAddresses and RouteInfo objects are not compared with equality - * because addresses and routes seen by Netlink will contain additional fields like flags, - * interfaces, and so on. If this InitialConfiguration has no IP address specified, the - * provisioning check always fails. - * - * If the given list of routes is null, only addresses are taken into considerations. - */ - public boolean isProvisionedBy(List<LinkAddress> addresses, List<RouteInfo> routes) { - if (ipAddresses.isEmpty()) { - return false; - } - - for (LinkAddress addr : ipAddresses) { - if (!any(addresses, (addrSeen) -> addr.isSameAddressAs(addrSeen))) { - return false; - } - } - - if (routes != null) { - for (IpPrefix prefix : directlyConnectedRoutes) { - if (!any(routes, (routeSeen) -> isDirectlyConnectedRoute(routeSeen, prefix))) { - return false; - } - } + @Override + public void onProvisioningFailure(LinkProperties newLp) { + synchronized (this) { + mCallbackLinkProperties = null; + notify(); } - - return true; - } - - private static boolean isDirectlyConnectedRoute(RouteInfo route, IpPrefix prefix) { - return !route.hasGateway() && prefix.equals(route.getDestination()); - } - - private static boolean isPrefixLengthCompliant(LinkAddress addr) { - return addr.isIPv4() || isCompliantIPv6PrefixLength(addr.getPrefixLength()); - } - - private static boolean isPrefixLengthCompliant(IpPrefix prefix) { - return prefix.isIPv4() || isCompliantIPv6PrefixLength(prefix.getPrefixLength()); - } - - private static boolean isCompliantIPv6PrefixLength(int prefixLength) { - return (NetworkConstants.RFC6177_MIN_PREFIX_LENGTH <= prefixLength) - && (prefixLength <= NetworkConstants.RFC7421_PREFIX_LENGTH); - } - - private static boolean isIPv6DefaultRoute(IpPrefix prefix) { - return prefix.getAddress().equals(Inet6Address.ANY); - } - - private static boolean isIPv6GUA(LinkAddress addr) { - return addr.isIPv6() && addr.isGlobalPreferred(); } } - public static final String DUMP_ARG = "ipmanager"; - public static final String DUMP_ARG_CONFIRM = "confirm"; - - private static final int CMD_TERMINATE_AFTER_STOP = 1; - private static final int CMD_STOP = 2; - private static final int CMD_START = 3; - private static final int CMD_CONFIRM = 4; - private static final int EVENT_PRE_DHCP_ACTION_COMPLETE = 5; - // Sent by NetlinkTracker to communicate netlink events. - private static final int EVENT_NETLINK_LINKPROPERTIES_CHANGED = 6; - private static final int CMD_UPDATE_TCP_BUFFER_SIZES = 7; - private static final int CMD_UPDATE_HTTP_PROXY = 8; - private static final int CMD_SET_MULTICAST_FILTER = 9; - private static final int EVENT_PROVISIONING_TIMEOUT = 10; - private static final int EVENT_DHCPACTION_TIMEOUT = 11; - - private static final int MAX_LOG_RECORDS = 500; - private static final int MAX_PACKET_RECORDS = 100; - - private static final boolean NO_CALLBACKS = false; - private static final boolean SEND_CALLBACKS = true; - - // This must match the interface prefix in clatd.c. - // TODO: Revert this hack once IpManager and Nat464Xlat work in concert. - private static final String CLAT_PREFIX = "v4-"; - - private final State mStoppedState = new StoppedState(); - private final State mStoppingState = new StoppingState(); - private final State mStartedState = new StartedState(); - private final State mRunningState = new RunningState(); - - private final String mTag; - private final Context mContext; - private final String mInterfaceName; - private final String mClatInterfaceName; - @VisibleForTesting - protected final Callback mCallback; - private final INetworkManagementService mNwService; - private final NetlinkTracker mNetlinkTracker; - private final WakeupMessage mProvisioningTimeoutAlarm; - private final WakeupMessage mDhcpActionTimeoutAlarm; - private final MultinetworkPolicyTracker mMultinetworkPolicyTracker; - private final SharedLog mLog; - private final LocalLog mConnectivityPacketLog; - private final MessageHandlingLogger mMsgStateLogger; - private final IpConnectivityLog mMetricsLog = new IpConnectivityLog(); - private final InterfaceController mInterfaceCtrl; - - private NetworkInterface mNetworkInterface; - - /** - * Non-final member variables accessed only from within our StateMachine. - */ - private LinkProperties mLinkProperties; - private ProvisioningConfiguration mConfiguration; - private IpReachabilityMonitor mIpReachabilityMonitor; - private DhcpClient mDhcpClient; - private DhcpResults mDhcpResults; - private String mTcpBufferSizes; - private ProxyInfo mHttpProxy; - private ApfFilter mApfFilter; - private boolean mMulticastFiltering; - private long mStartTimeMillis; - public IpManager(Context context, String ifName, Callback callback) { this(context, ifName, callback, INetworkManagementService.Stub.asInterface( ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE)), NetdService.getInstance()); } - /** - * An expanded constructor, useful for dependency injection. - * TODO: migrate all test users to mock IpManager directly and remove this ctor. - */ public IpManager(Context context, String ifName, Callback callback, INetworkManagementService nwService) { this(context, ifName, callback, nwService, NetdService.getInstance()); } @VisibleForTesting - IpManager(Context context, String ifName, Callback callback, + public IpManager(Context context, String ifName, Callback callback, INetworkManagementService nwService, INetd netd) { - super(IpManager.class.getSimpleName() + "." + ifName); - mTag = getName(); - - mContext = context; - mInterfaceName = ifName; - mClatInterfaceName = CLAT_PREFIX + ifName; - mCallback = new LoggingCallbackWrapper(callback); - mNwService = nwService; - - mLog = new SharedLog(MAX_LOG_RECORDS, mTag); - mConnectivityPacketLog = new LocalLog(MAX_PACKET_RECORDS); - mMsgStateLogger = new MessageHandlingLogger(); - - mInterfaceCtrl = new InterfaceController(mInterfaceName, mNwService, netd, mLog); - - mNetlinkTracker = new NetlinkTracker( - mInterfaceName, - new NetlinkTracker.Callback() { - @Override - public void update() { - sendMessage(EVENT_NETLINK_LINKPROPERTIES_CHANGED); - } - }) { - @Override - public void interfaceAdded(String iface) { - super.interfaceAdded(iface); - if (mClatInterfaceName.equals(iface)) { - mCallback.setNeighborDiscoveryOffload(false); - } else if (!mInterfaceName.equals(iface)) { - return; - } - - final String msg = "interfaceAdded(" + iface +")"; - logMsg(msg); - } - - @Override - public void interfaceRemoved(String iface) { - super.interfaceRemoved(iface); - // TODO: Also observe mInterfaceName going down and take some - // kind of appropriate action. - if (mClatInterfaceName.equals(iface)) { - // TODO: consider sending a message to the IpManager main - // StateMachine thread, in case "NDO enabled" state becomes - // tied to more things that 464xlat operation. - mCallback.setNeighborDiscoveryOffload(true); - } else if (!mInterfaceName.equals(iface)) { - return; - } - - final String msg = "interfaceRemoved(" + iface +")"; - logMsg(msg); - } - - private void logMsg(String msg) { - Log.d(mTag, msg); - getHandler().post(() -> { mLog.log("OBSERVED " + msg); }); - } - }; - - mLinkProperties = new LinkProperties(); - mLinkProperties.setInterfaceName(mInterfaceName); - - mMultinetworkPolicyTracker = new MultinetworkPolicyTracker(mContext, getHandler(), - () -> { mLog.log("OBSERVED AvoidBadWifi changed"); }); - - mProvisioningTimeoutAlarm = new WakeupMessage(mContext, getHandler(), - mTag + ".EVENT_PROVISIONING_TIMEOUT", EVENT_PROVISIONING_TIMEOUT); - mDhcpActionTimeoutAlarm = new WakeupMessage(mContext, getHandler(), - mTag + ".EVENT_DHCPACTION_TIMEOUT", EVENT_DHCPACTION_TIMEOUT); - - // Anything the StateMachine may access must have been instantiated - // before this point. - configureAndStartStateMachine(); - - // Anything that may send messages to the StateMachine must only be - // configured to do so after the StateMachine has started (above). - startStateMachineUpdaters(); - } - - private void configureAndStartStateMachine() { - addState(mStoppedState); - addState(mStartedState); - addState(mRunningState, mStartedState); - addState(mStoppingState); - - setInitialState(mStoppedState); - - super.start(); - } - - private void startStateMachineUpdaters() { - try { - mNwService.registerObserver(mNetlinkTracker); - } catch (RemoteException e) { - logError("Couldn't register NetlinkTracker: %s", e); - } - - mMultinetworkPolicyTracker.start(); - } - - private void stopStateMachineUpdaters() { - try { - mNwService.unregisterObserver(mNetlinkTracker); - } catch (RemoteException e) { - logError("Couldn't unregister NetlinkTracker: %s", e); - } - - mMultinetworkPolicyTracker.shutdown(); - } - - @Override - protected void onQuitting() { - mCallback.onQuit(); - } - - // Shut down this IpManager instance altogether. - public void shutdown() { - stop(); - sendMessage(CMD_TERMINATE_AFTER_STOP); - } - - public static ProvisioningConfiguration.Builder buildProvisioningConfiguration() { - return new ProvisioningConfiguration.Builder(); + super(context, ifName, callback, nwService, netd); } public void startProvisioning(ProvisioningConfiguration req) { - if (!req.isValid()) { - doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING); - return; - } - - getNetworkInterface(); - - mCallback.setNeighborDiscoveryOffload(true); - sendMessage(CMD_START, new ProvisioningConfiguration(req)); - } - - // TODO: Delete this. - public void startProvisioning(StaticIpConfiguration staticIpConfig) { - startProvisioning(buildProvisioningConfiguration() - .withStaticConfiguration(staticIpConfig) - .build()); - } - - public void startProvisioning() { - startProvisioning(new ProvisioningConfiguration()); - } - - public void stop() { - sendMessage(CMD_STOP); - } - - public void confirmConfiguration() { - sendMessage(CMD_CONFIRM); - } - - public void completedPreDhcpAction() { - sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE); - } - - /** - * Set the TCP buffer sizes to use. - * - * This may be called, repeatedly, at any time before or after a call to - * #startProvisioning(). The setting is cleared upon calling #stop(). - */ - public void setTcpBufferSizes(String tcpBufferSizes) { - sendMessage(CMD_UPDATE_TCP_BUFFER_SIZES, tcpBufferSizes); - } - - /** - * Set the HTTP Proxy configuration to use. - * - * This may be called, repeatedly, at any time before or after a call to - * #startProvisioning(). The setting is cleared upon calling #stop(). - */ - public void setHttpProxy(ProxyInfo proxyInfo) { - sendMessage(CMD_UPDATE_HTTP_PROXY, proxyInfo); - } - - /** - * Enable or disable the multicast filter. Attempts to use APF to accomplish the filtering, - * if not, Callback.setFallbackMulticastFilter() is called. - */ - public void setMulticastFilter(boolean enabled) { - sendMessage(CMD_SET_MULTICAST_FILTER, enabled); - } - - public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { - if (args != null && args.length > 0 && DUMP_ARG_CONFIRM.equals(args[0])) { - // Execute confirmConfiguration() and take no further action. - confirmConfiguration(); - return; - } - - // Thread-unsafe access to mApfFilter but just used for debugging. - final ApfFilter apfFilter = mApfFilter; - final ProvisioningConfiguration provisioningConfig = mConfiguration; - final ApfCapabilities apfCapabilities = (provisioningConfig != null) - ? provisioningConfig.mApfCapabilities : null; - - IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); - pw.println(mTag + " APF dump:"); - pw.increaseIndent(); - if (apfFilter != null) { - apfFilter.dump(pw); - } else { - pw.print("No active ApfFilter; "); - if (provisioningConfig == null) { - pw.println("IpManager not yet started."); - } else if (apfCapabilities == null || apfCapabilities.apfVersionSupported == 0) { - pw.println("Hardware does not support APF."); - } else { - pw.println("ApfFilter not yet started, APF capabilities: " + apfCapabilities); - } - } - pw.decreaseIndent(); - - pw.println(); - pw.println(mTag + " current ProvisioningConfiguration:"); - pw.increaseIndent(); - pw.println(Objects.toString(provisioningConfig, "N/A")); - pw.decreaseIndent(); - - pw.println(); - pw.println(mTag + " StateMachine dump:"); - pw.increaseIndent(); - mLog.dump(fd, pw, args); - pw.decreaseIndent(); - - pw.println(); - pw.println(mTag + " connectivity packet log:"); - pw.println(); - pw.println("Debug with python and scapy via:"); - pw.println("shell$ python"); - pw.println(">>> from scapy import all as scapy"); - pw.println(">>> scapy.Ether(\"<paste_hex_string>\".decode(\"hex\")).show2()"); - pw.println(); - - pw.increaseIndent(); - mConnectivityPacketLog.readOnlyLocalLog().dump(fd, pw, args); - pw.decreaseIndent(); - } - - - /** - * Internals. - */ - - @Override - protected String getWhatToString(int what) { - return sWhatToString.get(what, "UNKNOWN: " + Integer.toString(what)); - } - - @Override - protected String getLogRecString(Message msg) { - final String logLine = String.format( - "%s/%d %d %d %s [%s]", - mInterfaceName, mNetworkInterface == null ? -1 : mNetworkInterface.getIndex(), - msg.arg1, msg.arg2, Objects.toString(msg.obj), mMsgStateLogger); - - final String richerLogLine = getWhatToString(msg.what) + " " + logLine; - mLog.log(richerLogLine); - if (DBG) { - Log.d(mTag, richerLogLine); - } - - mMsgStateLogger.reset(); - return logLine; - } - - @Override - protected boolean recordLogRec(Message msg) { - // Don't log EVENT_NETLINK_LINKPROPERTIES_CHANGED. They can be noisy, - // and we already log any LinkProperties change that results in an - // invocation of IpManager.Callback#onLinkPropertiesChange(). - final boolean shouldLog = (msg.what != EVENT_NETLINK_LINKPROPERTIES_CHANGED); - if (!shouldLog) { - mMsgStateLogger.reset(); - } - return shouldLog; - } - - private void logError(String fmt, Object... args) { - final String msg = "ERROR " + String.format(fmt, args); - Log.e(mTag, msg); - mLog.log(msg); - } - - private void getNetworkInterface() { - try { - mNetworkInterface = NetworkInterface.getByName(mInterfaceName); - } catch (SocketException | NullPointerException e) { - // TODO: throw new IllegalStateException. - logError("Failed to get interface object: %s", e); - } - } - - // This needs to be called with care to ensure that our LinkProperties - // are in sync with the actual LinkProperties of the interface. For example, - // we should only call this if we know for sure that there are no IP addresses - // assigned to the interface, etc. - private void resetLinkProperties() { - mNetlinkTracker.clearLinkProperties(); - mConfiguration = null; - mDhcpResults = null; - mTcpBufferSizes = ""; - mHttpProxy = null; - - mLinkProperties = new LinkProperties(); - mLinkProperties.setInterfaceName(mInterfaceName); - } - - private void recordMetric(final int type) { - if (mStartTimeMillis <= 0) { Log.wtf(mTag, "Start time undefined!"); } - final long duration = SystemClock.elapsedRealtime() - mStartTimeMillis; - mMetricsLog.log(mInterfaceName, new IpManagerEvent(type, duration)); - } - - // For now: use WifiStateMachine's historical notion of provisioned. - @VisibleForTesting - static boolean isProvisioned(LinkProperties lp, InitialConfiguration config) { - // For historical reasons, we should connect even if all we have is - // an IPv4 address and nothing else. - if (lp.hasIPv4Address() || lp.isProvisioned()) { - return true; - } - if (config == null) { - return false; - } - - // When an InitialConfiguration is specified, ignore any difference with previous - // properties and instead check if properties observed match the desired properties. - return config.isProvisionedBy(lp.getLinkAddresses(), lp.getRoutes()); - } - - // TODO: Investigate folding all this into the existing static function - // LinkProperties.compareProvisioning() or some other single function that - // takes two LinkProperties objects and returns a ProvisioningChange - // object that is a correct and complete assessment of what changed, taking - // account of the asymmetries described in the comments in this function. - // Then switch to using it everywhere (IpReachabilityMonitor, etc.). - private ProvisioningChange compareProvisioning(LinkProperties oldLp, LinkProperties newLp) { - ProvisioningChange delta; - InitialConfiguration config = mConfiguration != null ? mConfiguration.mInitialConfig : null; - final boolean wasProvisioned = isProvisioned(oldLp, config); - final boolean isProvisioned = isProvisioned(newLp, config); - - if (!wasProvisioned && isProvisioned) { - delta = ProvisioningChange.GAINED_PROVISIONING; - } else if (wasProvisioned && isProvisioned) { - delta = ProvisioningChange.STILL_PROVISIONED; - } else if (!wasProvisioned && !isProvisioned) { - delta = ProvisioningChange.STILL_NOT_PROVISIONED; - } else { - // (wasProvisioned && !isProvisioned) - // - // Note that this is true even if we lose a configuration element - // (e.g., a default gateway) that would not be required to advance - // into provisioned state. This is intended: if we have a default - // router and we lose it, that's a sure sign of a problem, but if - // we connect to a network with no IPv4 DNS servers, we consider - // that to be a network without DNS servers and connect anyway. - // - // See the comment below. - delta = ProvisioningChange.LOST_PROVISIONING; - } - - final boolean lostIPv6 = oldLp.isIPv6Provisioned() && !newLp.isIPv6Provisioned(); - final boolean lostIPv4Address = oldLp.hasIPv4Address() && !newLp.hasIPv4Address(); - final boolean lostIPv6Router = oldLp.hasIPv6DefaultRoute() && !newLp.hasIPv6DefaultRoute(); - - // If bad wifi avoidance is disabled, then ignore IPv6 loss of - // provisioning. Otherwise, when a hotspot that loses Internet - // access sends out a 0-lifetime RA to its clients, the clients - // will disconnect and then reconnect, avoiding the bad hotspot, - // instead of getting stuck on the bad hotspot. http://b/31827713 . - // - // This is incorrect because if the hotspot then regains Internet - // access with a different prefix, TCP connections on the - // deprecated addresses will remain stuck. - // - // Note that we can still be disconnected by IpReachabilityMonitor - // if the IPv6 default gateway (but not the IPv6 DNS servers; see - // accompanying code in IpReachabilityMonitor) is unreachable. - final boolean ignoreIPv6ProvisioningLoss = !mMultinetworkPolicyTracker.getAvoidBadWifi(); - - // Additionally: - // - // Partial configurations (e.g., only an IPv4 address with no DNS - // servers and no default route) are accepted as long as DHCPv4 - // succeeds. On such a network, isProvisioned() will always return - // false, because the configuration is not complete, but we want to - // connect anyway. It might be a disconnected network such as a - // Chromecast or a wireless printer, for example. - // - // Because on such a network isProvisioned() will always return false, - // delta will never be LOST_PROVISIONING. So check for loss of - // provisioning here too. - if (lostIPv4Address || (lostIPv6 && !ignoreIPv6ProvisioningLoss)) { - delta = ProvisioningChange.LOST_PROVISIONING; - } - - // Additionally: - // - // If the previous link properties had a global IPv6 address and an - // IPv6 default route then also consider the loss of that default route - // to be a loss of provisioning. See b/27962810. - if (oldLp.hasGlobalIPv6Address() && (lostIPv6Router && !ignoreIPv6ProvisioningLoss)) { - delta = ProvisioningChange.LOST_PROVISIONING; - } - - return delta; - } - - private void dispatchCallback(ProvisioningChange delta, LinkProperties newLp) { - switch (delta) { - case GAINED_PROVISIONING: - if (DBG) { Log.d(mTag, "onProvisioningSuccess()"); } - recordMetric(IpManagerEvent.PROVISIONING_OK); - mCallback.onProvisioningSuccess(newLp); - break; - - case LOST_PROVISIONING: - if (DBG) { Log.d(mTag, "onProvisioningFailure()"); } - recordMetric(IpManagerEvent.PROVISIONING_FAIL); - mCallback.onProvisioningFailure(newLp); - break; - - default: - if (DBG) { Log.d(mTag, "onLinkPropertiesChange()"); } - mCallback.onLinkPropertiesChange(newLp); - break; - } - } - - // Updates all IpManager-related state concerned with LinkProperties. - // Returns a ProvisioningChange for possibly notifying other interested - // parties that are not fronted by IpManager. - private ProvisioningChange setLinkProperties(LinkProperties newLp) { - if (mApfFilter != null) { - mApfFilter.setLinkProperties(newLp); - } - if (mIpReachabilityMonitor != null) { - mIpReachabilityMonitor.updateLinkProperties(newLp); - } - - ProvisioningChange delta = compareProvisioning(mLinkProperties, newLp); - mLinkProperties = new LinkProperties(newLp); - - if (delta == ProvisioningChange.GAINED_PROVISIONING) { - // TODO: Add a proper ProvisionedState and cancel the alarm in - // its enter() method. - mProvisioningTimeoutAlarm.cancel(); - } - - return delta; - } - - private LinkProperties assembleLinkProperties() { - // [1] Create a new LinkProperties object to populate. - LinkProperties newLp = new LinkProperties(); - newLp.setInterfaceName(mInterfaceName); - - // [2] Pull in data from netlink: - // - IPv4 addresses - // - IPv6 addresses - // - IPv6 routes - // - IPv6 DNS servers - // - // N.B.: this is fundamentally race-prone and should be fixed by - // changing NetlinkTracker from a hybrid edge/level model to an - // edge-only model, or by giving IpManager its own netlink socket(s) - // so as to track all required information directly. - LinkProperties netlinkLinkProperties = mNetlinkTracker.getLinkProperties(); - newLp.setLinkAddresses(netlinkLinkProperties.getLinkAddresses()); - for (RouteInfo route : netlinkLinkProperties.getRoutes()) { - newLp.addRoute(route); - } - addAllReachableDnsServers(newLp, netlinkLinkProperties.getDnsServers()); - - // [3] Add in data from DHCPv4, if available. - // - // mDhcpResults is never shared with any other owner so we don't have - // to worry about concurrent modification. - if (mDhcpResults != null) { - for (RouteInfo route : mDhcpResults.getRoutes(mInterfaceName)) { - newLp.addRoute(route); - } - addAllReachableDnsServers(newLp, mDhcpResults.dnsServers); - newLp.setDomains(mDhcpResults.domains); - - if (mDhcpResults.mtu != 0) { - newLp.setMtu(mDhcpResults.mtu); - } - } - - // [4] Add in TCP buffer sizes and HTTP Proxy config, if available. - if (!TextUtils.isEmpty(mTcpBufferSizes)) { - newLp.setTcpBufferSizes(mTcpBufferSizes); - } - if (mHttpProxy != null) { - newLp.setHttpProxy(mHttpProxy); - } - - // [5] Add data from InitialConfiguration - if (mConfiguration != null && mConfiguration.mInitialConfig != null) { - InitialConfiguration config = mConfiguration.mInitialConfig; - // Add InitialConfiguration routes and dns server addresses once all addresses - // specified in the InitialConfiguration have been observed with Netlink. - if (config.isProvisionedBy(newLp.getLinkAddresses(), null)) { - for (IpPrefix prefix : config.directlyConnectedRoutes) { - newLp.addRoute(new RouteInfo(prefix, null, mInterfaceName)); - } - } - addAllReachableDnsServers(newLp, config.dnsServers); - } - final LinkProperties oldLp = mLinkProperties; - if (DBG) { - Log.d(mTag, String.format("Netlink-seen LPs: %s, new LPs: %s; old LPs: %s", - netlinkLinkProperties, newLp, oldLp)); - } - - // TODO: also learn via netlink routes specified by an InitialConfiguration and specified - // from a static IP v4 config instead of manually patching them in in steps [3] and [5]. - return newLp; - } - - private static void addAllReachableDnsServers( - LinkProperties lp, Iterable<InetAddress> dnses) { - // TODO: Investigate deleting this reachability check. We should be - // able to pass everything down to netd and let netd do evaluation - // and RFC6724-style sorting. - for (InetAddress dns : dnses) { - if (!dns.isAnyLocalAddress() && lp.isReachable(dns)) { - lp.addDnsServer(dns); - } - } - } - - // Returns false if we have lost provisioning, true otherwise. - private boolean handleLinkPropertiesUpdate(boolean sendCallbacks) { - final LinkProperties newLp = assembleLinkProperties(); - if (Objects.equals(newLp, mLinkProperties)) { - return true; - } - final ProvisioningChange delta = setLinkProperties(newLp); - if (sendCallbacks) { - dispatchCallback(delta, newLp); - } - return (delta != ProvisioningChange.LOST_PROVISIONING); - } - - private void handleIPv4Success(DhcpResults dhcpResults) { - mDhcpResults = new DhcpResults(dhcpResults); - final LinkProperties newLp = assembleLinkProperties(); - final ProvisioningChange delta = setLinkProperties(newLp); - - if (DBG) { - Log.d(mTag, "onNewDhcpResults(" + Objects.toString(dhcpResults) + ")"); - } - mCallback.onNewDhcpResults(dhcpResults); - dispatchCallback(delta, newLp); - } - - private void handleIPv4Failure() { - // TODO: Investigate deleting this clearIPv4Address() call. - // - // DhcpClient will send us CMD_CLEAR_LINKADDRESS in all circumstances - // that could trigger a call to this function. If we missed handling - // that message in StartedState for some reason we would still clear - // any addresses upon entry to StoppedState. - mInterfaceCtrl.clearIPv4Address(); - mDhcpResults = null; - if (DBG) { Log.d(mTag, "onNewDhcpResults(null)"); } - mCallback.onNewDhcpResults(null); - - handleProvisioningFailure(); - } - - private void handleProvisioningFailure() { - final LinkProperties newLp = assembleLinkProperties(); - ProvisioningChange delta = setLinkProperties(newLp); - // If we've gotten here and we're still not provisioned treat that as - // a total loss of provisioning. - // - // Either (a) static IP configuration failed or (b) DHCPv4 failed AND - // there was no usable IPv6 obtained before a non-zero provisioning - // timeout expired. - // - // Regardless: GAME OVER. - if (delta == ProvisioningChange.STILL_NOT_PROVISIONED) { - delta = ProvisioningChange.LOST_PROVISIONING; - } - - dispatchCallback(delta, newLp); - if (delta == ProvisioningChange.LOST_PROVISIONING) { - transitionTo(mStoppingState); - } - } - - private void doImmediateProvisioningFailure(int failureType) { - logError("onProvisioningFailure(): %s", failureType); - recordMetric(failureType); - mCallback.onProvisioningFailure(new LinkProperties(mLinkProperties)); - } - - private boolean startIPv4() { - // If we have a StaticIpConfiguration attempt to apply it and - // handle the result accordingly. - if (mConfiguration.mStaticIpConfig != null) { - if (mInterfaceCtrl.setIPv4Address(mConfiguration.mStaticIpConfig.ipAddress)) { - handleIPv4Success(new DhcpResults(mConfiguration.mStaticIpConfig)); - } else { - return false; - } - } else { - // Start DHCPv4. - mDhcpClient = DhcpClient.makeDhcpClient(mContext, IpManager.this, mInterfaceName); - mDhcpClient.registerForPreDhcpNotification(); - mDhcpClient.sendMessage(DhcpClient.CMD_START_DHCP); - } - - return true; - } - - private boolean startIPv6() { - return mInterfaceCtrl.setIPv6PrivacyExtensions(true) && - mInterfaceCtrl.setIPv6AddrGenModeIfSupported(mConfiguration.mIPv6AddrGenMode) && - mInterfaceCtrl.enableIPv6(); - } - - private boolean applyInitialConfig(InitialConfiguration config) { - // TODO: also support specifying a static IPv4 configuration in InitialConfiguration. - for (LinkAddress addr : findAll(config.ipAddresses, LinkAddress::isIPv6)) { - if (!mInterfaceCtrl.addAddress(addr)) return false; - } - - return true; - } - - private boolean startIpReachabilityMonitor() { - try { - mIpReachabilityMonitor = new IpReachabilityMonitor( - mContext, - mInterfaceName, - mLog, - new IpReachabilityMonitor.Callback() { - @Override - public void notifyLost(InetAddress ip, String logMsg) { - mCallback.onReachabilityLost(logMsg); - } - }, - mMultinetworkPolicyTracker); - } catch (IllegalArgumentException iae) { - // Failed to start IpReachabilityMonitor. Log it and call - // onProvisioningFailure() immediately. - // - // See http://b/31038971. - logError("IpReachabilityMonitor failure: %s", iae); - mIpReachabilityMonitor = null; - } - - return (mIpReachabilityMonitor != null); - } - - private void stopAllIP() { - // We don't need to worry about routes, just addresses, because: - // - disableIpv6() will clear autoconf IPv6 routes as well, and - // - we don't get IPv4 routes from netlink - // so we neither react to nor need to wait for changes in either. - - mInterfaceCtrl.disableIPv6(); - mInterfaceCtrl.clearAllAddresses(); - } - - class StoppedState extends State { - @Override - public void enter() { - stopAllIP(); - - resetLinkProperties(); - if (mStartTimeMillis > 0) { - recordMetric(IpManagerEvent.COMPLETE_LIFECYCLE); - mStartTimeMillis = 0; - } - } - - @Override - public boolean processMessage(Message msg) { - switch (msg.what) { - case CMD_TERMINATE_AFTER_STOP: - stopStateMachineUpdaters(); - quit(); - break; - - case CMD_STOP: - break; - - case CMD_START: - mConfiguration = (ProvisioningConfiguration) msg.obj; - transitionTo(mStartedState); - break; - - case EVENT_NETLINK_LINKPROPERTIES_CHANGED: - handleLinkPropertiesUpdate(NO_CALLBACKS); - break; - - case CMD_UPDATE_TCP_BUFFER_SIZES: - mTcpBufferSizes = (String) msg.obj; - handleLinkPropertiesUpdate(NO_CALLBACKS); - break; - - case CMD_UPDATE_HTTP_PROXY: - mHttpProxy = (ProxyInfo) msg.obj; - handleLinkPropertiesUpdate(NO_CALLBACKS); - break; - - case CMD_SET_MULTICAST_FILTER: - mMulticastFiltering = (boolean) msg.obj; - break; - - case DhcpClient.CMD_ON_QUIT: - // Everything is already stopped. - logError("Unexpected CMD_ON_QUIT (already stopped)."); - break; - - default: - return NOT_HANDLED; - } - - mMsgStateLogger.handled(this, getCurrentState()); - return HANDLED; - } - } - - class StoppingState extends State { - @Override - public void enter() { - if (mDhcpClient == null) { - // There's no DHCPv4 for which to wait; proceed to stopped. - transitionTo(mStoppedState); - } - } - - @Override - public boolean processMessage(Message msg) { - switch (msg.what) { - case CMD_STOP: - break; - - case DhcpClient.CMD_CLEAR_LINKADDRESS: - mInterfaceCtrl.clearIPv4Address(); - break; - - case DhcpClient.CMD_ON_QUIT: - mDhcpClient = null; - transitionTo(mStoppedState); - break; - - default: - deferMessage(msg); - } - - mMsgStateLogger.handled(this, getCurrentState()); - return HANDLED; - } - } - - class StartedState extends State { - @Override - public void enter() { - mStartTimeMillis = SystemClock.elapsedRealtime(); - - if (mConfiguration.mProvisioningTimeoutMs > 0) { - final long alarmTime = SystemClock.elapsedRealtime() + - mConfiguration.mProvisioningTimeoutMs; - mProvisioningTimeoutAlarm.schedule(alarmTime); - } - - if (readyToProceed()) { - transitionTo(mRunningState); - } else { - // Clear all IPv4 and IPv6 before proceeding to RunningState. - // Clean up any leftover state from an abnormal exit from - // tethering or during an IpManager restart. - stopAllIP(); - } - } - - @Override - public void exit() { - mProvisioningTimeoutAlarm.cancel(); - } - - @Override - public boolean processMessage(Message msg) { - switch (msg.what) { - case CMD_STOP: - transitionTo(mStoppingState); - break; - - case EVENT_NETLINK_LINKPROPERTIES_CHANGED: - handleLinkPropertiesUpdate(NO_CALLBACKS); - if (readyToProceed()) { - transitionTo(mRunningState); - } - break; - - case EVENT_PROVISIONING_TIMEOUT: - handleProvisioningFailure(); - break; - - default: - // It's safe to process messages out of order because the - // only message that can both - // a) be received at this time and - // b) affect provisioning state - // is EVENT_NETLINK_LINKPROPERTIES_CHANGED (handled above). - deferMessage(msg); - } - - mMsgStateLogger.handled(this, getCurrentState()); - return HANDLED; - } - - boolean readyToProceed() { - return (!mLinkProperties.hasIPv4Address() && - !mLinkProperties.hasGlobalIPv6Address()); - } - } - - class RunningState extends State { - private ConnectivityPacketTracker mPacketTracker; - private boolean mDhcpActionInFlight; - - @Override - public void enter() { - // Get the Configuration for ApfFilter from Context - boolean filter802_3Frames = - mContext.getResources().getBoolean(R.bool.config_apfDrop802_3Frames); - - int[] ethTypeBlackList = mContext.getResources().getIntArray( - R.array.config_apfEthTypeBlackList); - - mApfFilter = ApfFilter.maybeCreate(mConfiguration.mApfCapabilities, mNetworkInterface, - mCallback, mMulticastFiltering, filter802_3Frames, ethTypeBlackList); - // TODO: investigate the effects of any multicast filtering racing/interfering with the - // rest of this IP configuration startup. - if (mApfFilter == null) { - mCallback.setFallbackMulticastFilter(mMulticastFiltering); - } - - mPacketTracker = createPacketTracker(); - if (mPacketTracker != null) mPacketTracker.start(); - - if (mConfiguration.mEnableIPv6 && !startIPv6()) { - doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV6); - transitionTo(mStoppingState); - return; - } - - if (mConfiguration.mEnableIPv4 && !startIPv4()) { - doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV4); - transitionTo(mStoppingState); - return; - } - - InitialConfiguration config = mConfiguration.mInitialConfig; - if ((config != null) && !applyInitialConfig(config)) { - // TODO introduce a new IpManagerEvent constant to distinguish this error case. - doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING); - transitionTo(mStoppingState); - return; - } - - if (mConfiguration.mUsingIpReachabilityMonitor && !startIpReachabilityMonitor()) { - doImmediateProvisioningFailure( - IpManagerEvent.ERROR_STARTING_IPREACHABILITYMONITOR); - transitionTo(mStoppingState); - return; - } - } - - @Override - public void exit() { - stopDhcpAction(); - - if (mIpReachabilityMonitor != null) { - mIpReachabilityMonitor.stop(); - mIpReachabilityMonitor = null; - } - - if (mDhcpClient != null) { - mDhcpClient.sendMessage(DhcpClient.CMD_STOP_DHCP); - mDhcpClient.doQuit(); - } - - if (mPacketTracker != null) { - mPacketTracker.stop(); - mPacketTracker = null; - } - - if (mApfFilter != null) { - mApfFilter.shutdown(); - mApfFilter = null; - } - - resetLinkProperties(); - } - - private ConnectivityPacketTracker createPacketTracker() { - try { - return new ConnectivityPacketTracker( - getHandler(), mNetworkInterface, mConnectivityPacketLog); - } catch (IllegalArgumentException e) { - return null; - } - } - - private void ensureDhcpAction() { - if (!mDhcpActionInFlight) { - mCallback.onPreDhcpAction(); - mDhcpActionInFlight = true; - final long alarmTime = SystemClock.elapsedRealtime() + - mConfiguration.mRequestedPreDhcpActionMs; - mDhcpActionTimeoutAlarm.schedule(alarmTime); - } - } - - private void stopDhcpAction() { - mDhcpActionTimeoutAlarm.cancel(); - if (mDhcpActionInFlight) { - mCallback.onPostDhcpAction(); - mDhcpActionInFlight = false; - } - } - - @Override - public boolean processMessage(Message msg) { - switch (msg.what) { - case CMD_STOP: - transitionTo(mStoppingState); - break; - - case CMD_START: - logError("ALERT: START received in StartedState. Please fix caller."); - break; - - case CMD_CONFIRM: - // TODO: Possibly introduce a second type of confirmation - // that both probes (a) on-link neighbors and (b) does - // a DHCPv4 RENEW. We used to do this on Wi-Fi framework - // roams. - if (mIpReachabilityMonitor != null) { - mIpReachabilityMonitor.probeAll(); - } - break; - - case EVENT_PRE_DHCP_ACTION_COMPLETE: - // It's possible to reach here if, for example, someone - // calls completedPreDhcpAction() after provisioning with - // a static IP configuration. - if (mDhcpClient != null) { - mDhcpClient.sendMessage(DhcpClient.CMD_PRE_DHCP_ACTION_COMPLETE); - } - break; - - case EVENT_NETLINK_LINKPROPERTIES_CHANGED: - if (!handleLinkPropertiesUpdate(SEND_CALLBACKS)) { - transitionTo(mStoppingState); - } - break; - - case CMD_UPDATE_TCP_BUFFER_SIZES: - mTcpBufferSizes = (String) msg.obj; - // This cannot possibly change provisioning state. - handleLinkPropertiesUpdate(SEND_CALLBACKS); - break; - - case CMD_UPDATE_HTTP_PROXY: - mHttpProxy = (ProxyInfo) msg.obj; - // This cannot possibly change provisioning state. - handleLinkPropertiesUpdate(SEND_CALLBACKS); - break; - - case CMD_SET_MULTICAST_FILTER: { - mMulticastFiltering = (boolean) msg.obj; - if (mApfFilter != null) { - mApfFilter.setMulticastFilter(mMulticastFiltering); - } else { - mCallback.setFallbackMulticastFilter(mMulticastFiltering); - } - break; - } - - case EVENT_DHCPACTION_TIMEOUT: - stopDhcpAction(); - break; - - case DhcpClient.CMD_PRE_DHCP_ACTION: - if (mConfiguration.mRequestedPreDhcpActionMs > 0) { - ensureDhcpAction(); - } else { - sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE); - } - break; - - case DhcpClient.CMD_CLEAR_LINKADDRESS: - mInterfaceCtrl.clearIPv4Address(); - break; - - case DhcpClient.CMD_CONFIGURE_LINKADDRESS: { - final LinkAddress ipAddress = (LinkAddress) msg.obj; - if (mInterfaceCtrl.setIPv4Address(ipAddress)) { - mDhcpClient.sendMessage(DhcpClient.EVENT_LINKADDRESS_CONFIGURED); - } else { - logError("Failed to set IPv4 address."); - dispatchCallback(ProvisioningChange.LOST_PROVISIONING, - new LinkProperties(mLinkProperties)); - transitionTo(mStoppingState); - } - break; - } - - // This message is only received when: - // - // a) initial address acquisition succeeds, - // b) renew succeeds or is NAK'd, - // c) rebind succeeds or is NAK'd, or - // c) the lease expires, - // - // but never when initial address acquisition fails. The latter - // condition is now governed by the provisioning timeout. - case DhcpClient.CMD_POST_DHCP_ACTION: - stopDhcpAction(); - - switch (msg.arg1) { - case DhcpClient.DHCP_SUCCESS: - handleIPv4Success((DhcpResults) msg.obj); - break; - case DhcpClient.DHCP_FAILURE: - handleIPv4Failure(); - break; - default: - logError("Unknown CMD_POST_DHCP_ACTION status: %s", msg.arg1); - } - break; - - case DhcpClient.CMD_ON_QUIT: - // DHCPv4 quit early for some reason. - logError("Unexpected CMD_ON_QUIT."); - mDhcpClient = null; - break; - - default: - return NOT_HANDLED; - } - - mMsgStateLogger.handled(this, getCurrentState()); - return HANDLED; - } - } - - private static class MessageHandlingLogger { - public String processedInState; - public String receivedInState; - - public void reset() { - processedInState = null; - receivedInState = null; - } - - public void handled(State processedIn, IState receivedIn) { - processedInState = processedIn.getClass().getSimpleName(); - receivedInState = receivedIn.getName(); - } - - public String toString() { - return String.format("rcvd_in=%s, proc_in=%s", - receivedInState, processedInState); - } - } - - // TODO: extract out into CollectionUtils. - static <T> boolean any(Iterable<T> coll, Predicate<T> fn) { - for (T t : coll) { - if (fn.test(t)) { - return true; - } - } - return false; - } - - static <T> boolean all(Iterable<T> coll, Predicate<T> fn) { - return !any(coll, not(fn)); - } - - static <T> Predicate<T> not(Predicate<T> fn) { - return (t) -> !fn.test(t); - } - - static <T> String join(String delimiter, Collection<T> coll) { - return coll.stream().map(Object::toString).collect(Collectors.joining(delimiter)); - } - - static <T> T find(Iterable<T> coll, Predicate<T> fn) { - for (T t: coll) { - if (fn.test(t)) { - return t; - } - } - return null; - } - - static <T> List<T> findAll(Collection<T> coll, Predicate<T> fn) { - return coll.stream().filter(fn).collect(Collectors.toList()); + super.startProvisioning((IpClient.ProvisioningConfiguration) req); } } |