diff options
| -rw-r--r-- | services/net/java/android/net/ip/IpManager.java | 461 |
1 files changed, 461 insertions, 0 deletions
diff --git a/services/net/java/android/net/ip/IpManager.java b/services/net/java/android/net/ip/IpManager.java new file mode 100644 index 000000000000..87ac846a2a43 --- /dev/null +++ b/services/net/java/android/net/ip/IpManager.java @@ -0,0 +1,461 @@ +/* + * Copyright (C) 2016 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.ip; + +import android.content.Context; +import android.net.DhcpResults; +import android.net.LinkProperties; +import android.net.LinkProperties.ProvisioningChange; +import android.net.RouteInfo; +import android.net.StaticIpConfiguration; +import android.os.INetworkManagementService; +import android.os.Message; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.Log; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.State; +import com.android.internal.util.StateMachine; +import com.android.server.net.NetlinkTracker; + +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; + + +/** + * 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). + * + * @hide + */ +public class IpManager extends StateMachine { + private static final String TAG = IpManager.class.getSimpleName(); + private static final boolean DBG = true; + private static final boolean VDBG = false; + + /** + * Callbacks for both configuration of IpManager and for handling + * events as desired. + */ + public static class Callback { + /** + * Configuration callbacks. + * + * Override methods as desired in order to control which features + * IpManager will use at run time. + */ + + // An IpReachabilityMonitor will always be started, if only for logging. + // This method is checked before probing neighbors and before calling + // onProvisioningLost() (see below). + public boolean usingIpReachabilityMonitor() { + return false; + } + + /** + * Event callbacks. + * + * Override methods as desired in order to handle event callbacks + * as IpManager invokes them. + */ + + // TODO: Kill with fire once DHCP and static configuration are moved + // out of WifiStateMachine. + public void onIPv4ProvisioningSuccess(DhcpResults dhcpResults, int reason) {} + public void onIPv4ProvisioningFailure(int reason) {} + + 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) {} + } + + private static final int CMD_STOP = 1; + private static final int CMD_START = 2; + private static final int CMD_CONFIRM = 3; + private static final int CMD_UPDATE_DHCPV4_RESULTS = 4; + // Sent by NetlinkTracker to communicate netlink events. + private static final int EVENT_NETLINK_LINKPROPERTIES_CHANGED = 5; + + private static final int MAX_LOG_RECORDS = 1000; + + private final Object mLock = new Object(); + private final State mStoppedState = new StoppedState(); + private final State mStartedState = new StartedState(); + + private final Context mContext; + private final String mInterfaceName; + @VisibleForTesting + protected final Callback mCallback; + private final INetworkManagementService mNwService; + private final NetlinkTracker mNetlinkTracker; + + private int mInterfaceIndex; + + /** + * Non-final member variables accessed only from within our StateMachine. + */ + private IpReachabilityMonitor mIpReachabilityMonitor; + private DhcpResults mDhcpResults; + private StaticIpConfiguration mStaticIpConfig; + + /** + * Member variables accessed both from within the StateMachine thread + * and via accessors from other threads. + */ + @GuardedBy("mLock") + private LinkProperties mLinkProperties; + + public IpManager(Context context, String ifName, Callback callback) + throws IllegalArgumentException { + super(TAG + "." + ifName); + + mContext = context; + mInterfaceName = ifName; + + mCallback = callback; + + mNwService = INetworkManagementService.Stub.asInterface( + ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE)); + + mNetlinkTracker = new NetlinkTracker( + mInterfaceName, + new NetlinkTracker.Callback() { + @Override + public void update() { + sendMessage(EVENT_NETLINK_LINKPROPERTIES_CHANGED); + } + }); + try { + mNwService.registerObserver(mNetlinkTracker); + } catch (RemoteException e) { + Log.e(TAG, "Couldn't register NetlinkTracker: " + e.toString()); + } + + resetLinkProperties(); + + // Super simple StateMachine. + addState(mStoppedState); + addState(mStartedState); + setInitialState(mStoppedState); + setLogRecSize(MAX_LOG_RECORDS); + super.start(); + } + + /** + * A special constructor for use in testing that bypasses some of the more + * complicated setup bits. + * + * TODO: Figure out how to delete this yet preserve testability. + */ + @VisibleForTesting + protected IpManager(String ifName, Callback callback) { + super(TAG + ".test-" + ifName); + mInterfaceName = ifName; + mCallback = callback; + + mContext = null; + mNwService = null; + mNetlinkTracker = null; + } + + public void startProvisioning(StaticIpConfiguration staticIpConfig) { + getInterfaceIndex(); + + sendMessage(CMD_START, staticIpConfig); + } + + public void startProvisioning() { + getInterfaceIndex(); + + sendMessage(CMD_START); + } + + public void stop() { + sendMessage(CMD_STOP); + } + + public void confirmConfiguration() { + sendMessage(CMD_CONFIRM); + } + + public LinkProperties getLinkProperties() { + synchronized (mLock) { + return new LinkProperties(mLinkProperties); + } + } + + // TODO: Kill with fire once DHCPv4/static config is moved into IpManager. + public void updateWithDhcpResults(DhcpResults dhcpResults, int reason) { + sendMessage(CMD_UPDATE_DHCPV4_RESULTS, reason, 0, dhcpResults); + } + + + /** + * Internals. + */ + + private void getInterfaceIndex() { + try { + mInterfaceIndex = NetworkInterface.getByName(mInterfaceName).getIndex(); + } catch (SocketException | NullPointerException e) { + // TODO: throw new IllegalStateException. + Log.e(TAG, "ALERT: Failed to get interface index: ", 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(); + mDhcpResults = null; + mStaticIpConfig = null; + + synchronized (mLock) { + mLinkProperties = new LinkProperties(); + mLinkProperties.setInterfaceName(mInterfaceName); + } + } + + private ProvisioningChange setLinkProperties(LinkProperties newLp) { + if (mIpReachabilityMonitor != null) { + mIpReachabilityMonitor.updateLinkProperties(newLp); + } + + // TODO: Figure out whether and how to incorporate static configuration + // into the notion of provisioning. + ProvisioningChange delta; + synchronized (mLock) { + delta = LinkProperties.compareProvisioning(mLinkProperties, newLp); + mLinkProperties = new LinkProperties(newLp); + } + + if (DBG) { + switch (delta) { + case GAINED_PROVISIONING: + case LOST_PROVISIONING: + Log.d(TAG, "provisioning: " + delta); + break; + } + } + + 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 + LinkProperties netlinkLinkProperties = mNetlinkTracker.getLinkProperties(); + newLp.setLinkAddresses(netlinkLinkProperties.getLinkAddresses()); + for (RouteInfo route : netlinkLinkProperties.getRoutes()) { + newLp.addRoute(route); + } + for (InetAddress dns : netlinkLinkProperties.getDnsServers()) { + // Only add likely reachable DNS servers. + // TODO: investigate deleting this. + if (newLp.isReachable(dns)) { + newLp.addDnsServer(dns); + } + } + + // [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); + } + for (InetAddress dns : mDhcpResults.dnsServers) { + // Only add likely reachable DNS servers. + // TODO: investigate deleting this. + if (newLp.isReachable(dns)) { + newLp.addDnsServer(dns); + } + } + newLp.setDomains(mDhcpResults.domains); + } + + if (VDBG) { + Log.d(TAG, "newLp{" + newLp + "}"); + } + + return newLp; + } + + class StoppedState extends State { + @Override + public void enter() { + try { + mNwService.disableIpv6(mInterfaceName); + mNwService.clearInterfaceAddresses(mInterfaceName); + } catch (Exception e) { + Log.e(TAG, "Failed to clear addresses or disable IPv6" + e); + } + + resetLinkProperties(); + } + + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { + case CMD_STOP: + break; + + case CMD_START: + mStaticIpConfig = (StaticIpConfiguration) msg.obj; + transitionTo(mStartedState); + break; + + case EVENT_NETLINK_LINKPROPERTIES_CHANGED: + setLinkProperties(assembleLinkProperties()); + break; + + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + class StartedState extends State { + @Override + public void enter() { + // Set privacy extensions. + try { + mNwService.setInterfaceIpv6PrivacyExtensions(mInterfaceName, true); + mNwService.enableIpv6(mInterfaceName); + } catch (RemoteException re) { + Log.e(TAG, "Unable to change interface settings: " + re); + } catch (IllegalStateException ie) { + Log.e(TAG, "Unable to change interface settings: " + ie); + } + + mIpReachabilityMonitor = new IpReachabilityMonitor( + mContext, + mInterfaceName, + new IpReachabilityMonitor.Callback() { + @Override + public void notifyLost(InetAddress ip, String logMsg) { + if (mCallback.usingIpReachabilityMonitor()) { + mCallback.onReachabilityLost(logMsg); + } + } + }); + + // TODO: Check mStaticIpConfig and handle accordingly. + } + + @Override + public void exit() { + mIpReachabilityMonitor.stop(); + mIpReachabilityMonitor = null; + + resetLinkProperties(); + } + + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { + case CMD_STOP: + transitionTo(mStoppedState); + break; + + case CMD_START: + // TODO: Defer this message to be delivered after a state transition + // to StoppedState. That way, receiving CMD_START in StartedState + // effects a restart. + Log.e(TAG, "ALERT: START received in StartedState."); + break; + + case CMD_CONFIRM: + if (mCallback.usingIpReachabilityMonitor()) { + mIpReachabilityMonitor.probeAll(); + } + break; + + case CMD_UPDATE_DHCPV4_RESULTS: + final DhcpResults dhcpResults = (DhcpResults) msg.obj; + final int reason = msg.arg1; + if (dhcpResults != null) { + mDhcpResults = new DhcpResults(dhcpResults); + setLinkProperties(assembleLinkProperties()); + mCallback.onIPv4ProvisioningSuccess(dhcpResults, reason); + } else { + mDhcpResults = null; + setLinkProperties(assembleLinkProperties()); + mCallback.onIPv4ProvisioningFailure(reason); + } + break; + + case EVENT_NETLINK_LINKPROPERTIES_CHANGED: + final LinkProperties newLp = assembleLinkProperties(); + final ProvisioningChange delta = setLinkProperties(newLp); + + // NOTE: The only receiver of these callbacks currently + // treats all three of them identically, namely it calls + // IpManager#getLinkProperties() and makes its own determination. + switch (delta) { + case GAINED_PROVISIONING: + mCallback.onProvisioningSuccess(newLp); + break; + + case LOST_PROVISIONING: + mCallback.onProvisioningFailure(newLp); + break; + + default: + // TODO: Only notify on STILL_PROVISIONED? + mCallback.onLinkPropertiesChange(newLp); + break; + } + + break; + + default: + return NOT_HANDLED; + } + return HANDLED; + } + + } +} |