Loading services/net/java/android/net/ip/IpManager.java 0 → 100644 +461 −0 Original line number Diff line number Diff line /* * 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; } } } Loading
services/net/java/android/net/ip/IpManager.java 0 → 100644 +461 −0 Original line number Diff line number Diff line /* * 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; } } }