Initial refactoring to group IP-related elements into an IpManager

Bug: 17345682
Change-Id: I88f3f4bd32d18cd8d4f1404493648c8bcc1deeec
This commit is contained in:
Erik Kline
2016-01-13 17:36:08 +09:00
parent 934b3b9c6c
commit a636761bd6

View File

@@ -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;
}
}
}