blob: d10834ad96956c0d220e8a85582ecb6a7eafbcf6 [file] [log] [blame]
/*
* 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 com.android.internal.util.MessageUtils;
import android.content.Context;
import android.net.apf.ApfCapabilities;
import android.net.apf.ApfFilter;
import android.net.DhcpResults;
import android.net.InterfaceConfiguration;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.LinkProperties.ProvisioningChange;
import android.net.ProxyInfo;
import android.net.RouteInfo;
import android.net.StaticIpConfiguration;
import android.net.dhcp.DhcpClient;
import android.os.INetworkManagementService;
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
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.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Objects;
/**
* 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 boolean DBG = false;
private static final boolean VDBG = 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) {}
// This is called whenever 464xlat is being enabled or disabled (i.e.
// started or stopped).
public void on464XlatChange(boolean enabled) {}
// 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) {}
}
public static class WaitForProvisioningCallback extends Callback {
private LinkProperties mCallbackLinkProperties;
public LinkProperties waitForProvisioning() {
synchronized (this) {
try {
wait();
} catch (InterruptedException e) {}
return mCallbackLinkProperties;
}
}
@Override
public void onProvisioningSuccess(LinkProperties newLp) {
synchronized (this) {
mCallbackLinkProperties = newLp;
notify();
}
}
@Override
public void onProvisioningFailure(LinkProperties newLp) {
synchronized (this) {
mCallbackLinkProperties = null;
notify();
}
}
}
/**
* 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()
* .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 {
public static class Builder {
private ProvisioningConfiguration mConfig = new ProvisioningConfiguration();
public Builder withoutIpReachabilityMonitor() {
mConfig.mUsingIpReachabilityMonitor = false;
return this;
}
public Builder withPreDhcpAction() {
mConfig.mRequestedPreDhcpAction = true;
return this;
}
public Builder withStaticConfiguration(StaticIpConfiguration staticConfig) {
mConfig.mStaticIpConfig = staticConfig;
return this;
}
public Builder withApfCapabilities(ApfCapabilities apfCapabilities) {
mConfig.mApfCapabilities = apfCapabilities;
return this;
}
public ProvisioningConfiguration build() {
return new ProvisioningConfiguration(mConfig);
}
}
/* package */ boolean mUsingIpReachabilityMonitor = true;
/* package */ boolean mRequestedPreDhcpAction;
/* package */ StaticIpConfiguration mStaticIpConfig;
/* package */ ApfCapabilities mApfCapabilities;
public ProvisioningConfiguration() {}
public ProvisioningConfiguration(ProvisioningConfiguration other) {
mUsingIpReachabilityMonitor = other.mUsingIpReachabilityMonitor;
mRequestedPreDhcpAction = other.mRequestedPreDhcpAction;
mStaticIpConfig = other.mStaticIpConfig;
mApfCapabilities = other.mApfCapabilities;
}
}
public static final String DUMP_ARG = "ipmanager";
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 EVENT_PRE_DHCP_ACTION_COMPLETE = 4;
// Sent by NetlinkTracker to communicate netlink events.
private static final int EVENT_NETLINK_LINKPROPERTIES_CHANGED = 5;
private static final int CMD_UPDATE_TCP_BUFFER_SIZES = 6;
private static final int CMD_UPDATE_HTTP_PROXY = 7;
private static final int MAX_LOG_RECORDS = 1000;
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 Object mLock = new Object();
private final State mStoppedState = new StoppedState();
private final State mStoppingState = new StoppingState();
private final State mStartedState = new StartedState();
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 NetworkInterface mNetworkInterface;
/**
* Non-final member variables accessed only from within our StateMachine.
*/
private ProvisioningConfiguration mConfiguration;
private IpReachabilityMonitor mIpReachabilityMonitor;
private DhcpClient mDhcpClient;
private DhcpResults mDhcpResults;
private String mTcpBufferSizes;
private ProxyInfo mHttpProxy;
private ApfFilter mApfFilter;
/**
* 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 {
this(context, ifName, callback, INetworkManagementService.Stub.asInterface(
ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE)));
}
/**
* An expanded constructor, useful for dependency injection.
*/
public IpManager(Context context, String ifName, Callback callback,
INetworkManagementService nwService) throws IllegalArgumentException {
super(IpManager.class.getSimpleName() + "." + ifName);
mTag = getName();
mContext = context;
mInterfaceName = ifName;
mClatInterfaceName = CLAT_PREFIX + ifName;
mCallback = callback;
mNwService = nwService;
mNetlinkTracker = new NetlinkTracker(
mInterfaceName,
new NetlinkTracker.Callback() {
@Override
public void update() {
sendMessage(EVENT_NETLINK_LINKPROPERTIES_CHANGED);
}
}) {
@Override
public void interfaceAdded(String iface) {
if (mClatInterfaceName.equals(iface)) {
mCallback.on464XlatChange(true);
}
}
@Override
public void interfaceRemoved(String iface) {
if (mClatInterfaceName.equals(iface)) {
mCallback.on464XlatChange(false);
}
}
};
try {
mNwService.registerObserver(mNetlinkTracker);
} catch (RemoteException e) {
Log.e(mTag, "Couldn't register NetlinkTracker: " + e.toString());
}
resetLinkProperties();
// Super simple StateMachine.
addState(mStoppedState);
addState(mStartedState);
addState(mStoppingState);
setInitialState(mStoppedState);
setLogRecSize(MAX_LOG_RECORDS);
super.start();
}
@Override
protected void onQuitting() {
mCallback.onQuit();
}
// Shut down this IpManager instance altogether.
public void shutdown() {
stop();
quit();
}
public static ProvisioningConfiguration.Builder buildProvisioningConfiguration() {
return new ProvisioningConfiguration.Builder();
}
public void startProvisioning(ProvisioningConfiguration req) {
getNetworkInterface();
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);
}
public LinkProperties getLinkProperties() {
synchronized (mLock) {
return new LinkProperties(mLinkProperties);
}
}
public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
pw.println("APF dump:");
pw.increaseIndent();
// Thread-unsafe access to mApfFilter but just used for debugging.
ApfFilter apfFilter = mApfFilter;
if (apfFilter != null) {
apfFilter.dump(pw);
} else {
pw.println("No apf support");
}
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(
"iface{%s/%d} arg1{%d} arg2{%d} obj{%s}",
mInterfaceName, mNetworkInterface == null ? -1 : mNetworkInterface.getIndex(),
msg.arg1, msg.arg2, Objects.toString(msg.obj));
if (VDBG) {
Log.d(mTag, getWhatToString(msg.what) + " " + logLine);
}
return logLine;
}
private void getNetworkInterface() {
try {
mNetworkInterface = NetworkInterface.getByName(mInterfaceName);
} catch (SocketException | NullPointerException e) {
// TODO: throw new IllegalStateException.
Log.e(mTag, "ALERT: Failed to get interface object: ", 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;
synchronized (mLock) {
mLinkProperties = new LinkProperties();
mLinkProperties.setInterfaceName(mInterfaceName);
}
}
// For now: use WifiStateMachine's historical notion of provisioned.
private static boolean isProvisioned(LinkProperties lp) {
// For historical reasons, we should connect even if all we have is
// an IPv4 address and nothing else.
return lp.isProvisioned() || lp.hasIPv4Address();
}
// 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 static ProvisioningChange compareProvisioning(
LinkProperties oldLp, LinkProperties newLp) {
ProvisioningChange delta;
final boolean wasProvisioned = isProvisioned(oldLp);
final boolean isProvisioned = isProvisioned(newLp);
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;
}
// 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 ((oldLp.hasIPv4Address() && !newLp.hasIPv4Address()) ||
(oldLp.isIPv6Provisioned() && !newLp.isIPv6Provisioned())) {
delta = ProvisioningChange.LOST_PROVISIONING;
}
return delta;
}
private void dispatchCallback(ProvisioningChange delta, LinkProperties newLp) {
switch (delta) {
case GAINED_PROVISIONING:
if (VDBG) { Log.d(mTag, "onProvisioningSuccess()"); }
mCallback.onProvisioningSuccess(newLp);
break;
case LOST_PROVISIONING:
if (VDBG) { Log.d(mTag, "onProvisioningFailure()"); }
mCallback.onProvisioningFailure(newLp);
break;
default:
if (VDBG) { Log.d(mTag, "onLinkPropertiesChange()"); }
mCallback.onLinkPropertiesChange(newLp);
break;
}
}
private ProvisioningChange setLinkProperties(LinkProperties newLp) {
if (mIpReachabilityMonitor != null) {
mIpReachabilityMonitor.updateLinkProperties(newLp);
}
ProvisioningChange delta;
synchronized (mLock) {
delta = compareProvisioning(mLinkProperties, newLp);
mLinkProperties = new LinkProperties(newLp);
}
if (DBG) {
switch (delta) {
case GAINED_PROVISIONING:
case LOST_PROVISIONING:
Log.d(mTag, "provisioning: " + delta);
break;
}
}
return delta;
}
private boolean linkPropertiesUnchanged(LinkProperties newLp) {
synchronized (mLock) {
return Objects.equals(newLp, mLinkProperties);
}
}
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 (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);
}
if (VDBG) {
Log.d(mTag, "newLp{" + newLp + "}");
}
return newLp;
}
// Returns false if we have lost provisioning, true otherwise.
private boolean handleLinkPropertiesUpdate(boolean sendCallbacks) {
final LinkProperties newLp = assembleLinkProperties();
if (linkPropertiesUnchanged(newLp)) {
return true;
}
final ProvisioningChange delta = setLinkProperties(newLp);
if (sendCallbacks) {
dispatchCallback(delta, newLp);
}
return (delta != ProvisioningChange.LOST_PROVISIONING);
}
private void clearIPv4Address() {
try {
final InterfaceConfiguration ifcg = new InterfaceConfiguration();
ifcg.setLinkAddress(new LinkAddress("0.0.0.0/0"));
mNwService.setInterfaceConfig(mInterfaceName, ifcg);
} catch (RemoteException e) {
Log.e(mTag, "ALERT: Failed to clear IPv4 address on interface " + mInterfaceName, e);
}
}
private void handleIPv4Success(DhcpResults dhcpResults) {
mDhcpResults = new DhcpResults(dhcpResults);
final LinkProperties newLp = assembleLinkProperties();
final ProvisioningChange delta = setLinkProperties(newLp);
if (VDBG) {
Log.d(mTag, "onNewDhcpResults(" + Objects.toString(dhcpResults) + ")");
}
mCallback.onNewDhcpResults(dhcpResults);
dispatchCallback(delta, newLp);
}
private void handleIPv4Failure() {
// TODO: Figure out to de-dup this and the same code in DhcpClient.
clearIPv4Address();
mDhcpResults = null;
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 the DHCPv4 timeout.
//
// Regardless: GAME OVER.
//
// TODO: Make the DHCP client not time out and just continue in
// exponential backoff. Callers such as Wi-Fi which need a timeout
// should implement it themselves.
if (delta == ProvisioningChange.STILL_NOT_PROVISIONED) {
delta = ProvisioningChange.LOST_PROVISIONING;
}
if (VDBG) { Log.d(mTag, "onNewDhcpResults(null)"); }
mCallback.onNewDhcpResults(null);
dispatchCallback(delta, newLp);
if (delta == ProvisioningChange.LOST_PROVISIONING) {
transitionTo(mStoppingState);
}
}
class StoppedState extends State {
@Override
public void enter() {
try {
mNwService.disableIpv6(mInterfaceName);
mNwService.clearInterfaceAddresses(mInterfaceName);
} catch (Exception e) {
Log.e(mTag, "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:
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 DhcpClient.CMD_ON_QUIT:
// Everything is already stopped.
Log.e(mTag, "Unexpected CMD_ON_QUIT (already stopped).");
break;
default:
return NOT_HANDLED;
}
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 DhcpClient.CMD_ON_QUIT:
mDhcpClient = null;
transitionTo(mStoppedState);
break;
default:
deferMessage(msg);
}
return HANDLED;
}
}
class StartedState extends State {
@Override
public void enter() {
mApfFilter = ApfFilter.maybeCreate(mConfiguration.mApfCapabilities, mNetworkInterface,
mCallback);
// Set privacy extensions.
try {
mNwService.setInterfaceIpv6PrivacyExtensions(mInterfaceName, true);
mNwService.enableIpv6(mInterfaceName);
// TODO: Perhaps clearIPv4Address() as well.
} catch (RemoteException re) {
Log.e(mTag, "Unable to change interface settings: " + re);
} catch (IllegalStateException ie) {
Log.e(mTag, "Unable to change interface settings: " + ie);
}
if (mConfiguration.mUsingIpReachabilityMonitor) {
mIpReachabilityMonitor = new IpReachabilityMonitor(
mContext,
mInterfaceName,
new IpReachabilityMonitor.Callback() {
@Override
public void notifyLost(InetAddress ip, String logMsg) {
mCallback.onReachabilityLost(logMsg);
}
});
}
// If we have a StaticIpConfiguration attempt to apply it and
// handle the result accordingly.
if (mConfiguration.mStaticIpConfig != null) {
if (applyStaticIpConfig()) {
handleIPv4Success(new DhcpResults(mConfiguration.mStaticIpConfig));
} else {
if (VDBG) { Log.d(mTag, "onProvisioningFailure()"); }
mCallback.onProvisioningFailure(getLinkProperties());
transitionTo(mStoppingState);
}
} else {
// Start DHCPv4.
mDhcpClient = DhcpClient.makeDhcpClient(
mContext,
IpManager.this,
mInterfaceName);
mDhcpClient.registerForPreDhcpNotification();
mDhcpClient.sendMessage(DhcpClient.CMD_START_DHCP);
}
}
@Override
public void exit() {
if (mIpReachabilityMonitor != null) {
mIpReachabilityMonitor.stop();
mIpReachabilityMonitor = null;
}
if (mDhcpClient != null) {
mDhcpClient.sendMessage(DhcpClient.CMD_STOP_DHCP);
mDhcpClient.doQuit();
}
if (mApfFilter != null) {
mApfFilter.shutdown();
mApfFilter = null;
}
resetLinkProperties();
}
@Override
public boolean processMessage(Message msg) {
switch (msg.what) {
case CMD_STOP:
transitionTo(mStoppedState);
break;
case CMD_START:
Log.e(mTag, "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(mStoppedState);
}
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 DhcpClient.CMD_PRE_DHCP_ACTION:
if (VDBG) { Log.d(mTag, "onPreDhcpAction()"); }
if (mConfiguration.mRequestedPreDhcpAction) {
mCallback.onPreDhcpAction();
} else {
sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE);
}
break;
case DhcpClient.CMD_POST_DHCP_ACTION: {
// Note that onPostDhcpAction() is likely to be
// asynchronous, and thus there is no guarantee that we
// will be able to observe any of its effects here.
if (VDBG) { Log.d(mTag, "onPostDhcpAction()"); }
mCallback.onPostDhcpAction();
final DhcpResults dhcpResults = (DhcpResults) msg.obj;
switch (msg.arg1) {
case DhcpClient.DHCP_SUCCESS:
handleIPv4Success(dhcpResults);
break;
case DhcpClient.DHCP_FAILURE:
handleIPv4Failure();
break;
default:
Log.e(mTag, "Unknown CMD_POST_DHCP_ACTION status:" + msg.arg1);
}
break;
}
case DhcpClient.CMD_ON_QUIT:
// DHCPv4 quit early for some reason.
Log.e(mTag, "Unexpected CMD_ON_QUIT.");
mDhcpClient = null;
break;
default:
return NOT_HANDLED;
}
return HANDLED;
}
private boolean applyStaticIpConfig() {
final InterfaceConfiguration ifcg = new InterfaceConfiguration();
ifcg.setLinkAddress(mConfiguration.mStaticIpConfig.ipAddress);
ifcg.setInterfaceUp();
try {
mNwService.setInterfaceConfig(mInterfaceName, ifcg);
if (DBG) Log.d(mTag, "Static IP configuration succeeded");
} catch (IllegalStateException | RemoteException e) {
Log.e(mTag, "Static IP configuration failed: ", e);
return false;
}
return true;
}
}
}