blob: d3cfd875faae3669d98cb811634621e3e912dc54 [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 com.android.server.connectivity.tethering;
import android.net.ConnectivityManager;
import android.net.INetworkStatsService;
import android.net.InterfaceConfiguration;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.NetworkUtils;
import android.os.INetworkManagementService;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.util.SparseArray;
import com.android.internal.util.MessageUtils;
import com.android.internal.util.Protocol;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
import java.net.InetAddress;
/**
* @hide
*
* Tracks the eligibility of a given network interface for tethering.
*/
public class TetherInterfaceStateMachine extends StateMachine {
private static final String USB_NEAR_IFACE_ADDR = "192.168.42.129";
private static final int USB_PREFIX_LENGTH = 24;
private static final String WIFI_HOST_IFACE_ADDR = "192.168.43.1";
private static final int WIFI_HOST_IFACE_PREFIX_LENGTH = 24;
private final static String TAG = "TetherInterfaceSM";
private final static boolean DBG = false;
private final static boolean VDBG = false;
private static final Class[] messageClasses = {
TetherInterfaceStateMachine.class
};
private static final SparseArray<String> sMagicDecoderRing =
MessageUtils.findMessageNames(messageClasses);
private static final int BASE_IFACE = Protocol.BASE_TETHERING + 100;
// request from the user that it wants to tether
public static final int CMD_TETHER_REQUESTED = BASE_IFACE + 2;
// request from the user that it wants to untether
public static final int CMD_TETHER_UNREQUESTED = BASE_IFACE + 3;
// notification that this interface is down
public static final int CMD_INTERFACE_DOWN = BASE_IFACE + 4;
// notification from the master SM that it had trouble enabling IP Forwarding
public static final int CMD_IP_FORWARDING_ENABLE_ERROR = BASE_IFACE + 7;
// notification from the master SM that it had trouble disabling IP Forwarding
public static final int CMD_IP_FORWARDING_DISABLE_ERROR = BASE_IFACE + 8;
// notification from the master SM that it had trouble starting tethering
public static final int CMD_START_TETHERING_ERROR = BASE_IFACE + 9;
// notification from the master SM that it had trouble stopping tethering
public static final int CMD_STOP_TETHERING_ERROR = BASE_IFACE + 10;
// notification from the master SM that it had trouble setting the DNS forwarders
public static final int CMD_SET_DNS_FORWARDERS_ERROR = BASE_IFACE + 11;
// the upstream connection has changed
public static final int CMD_TETHER_CONNECTION_CHANGED = BASE_IFACE + 12;
// new IPv6 tethering parameters need to be processed
public static final int CMD_IPV6_TETHER_UPDATE = BASE_IFACE + 13;
private final State mInitialState;
private final State mLocalHotspotState;
private final State mTetheredState;
private final State mUnavailableState;
private final INetworkManagementService mNMService;
private final INetworkStatsService mStatsService;
private final IControlsTethering mTetherController;
private final String mIfaceName;
private final int mInterfaceType;
private final IPv6TetheringInterfaceServices mIPv6TetherSvc;
private int mLastError;
private String mMyUpstreamIfaceName; // may change over time
public TetherInterfaceStateMachine(String ifaceName, Looper looper, int interfaceType,
INetworkManagementService nMService, INetworkStatsService statsService,
IControlsTethering tetherController, IPv6TetheringInterfaceServices ipv6Svc) {
super(ifaceName, looper);
mNMService = nMService;
mStatsService = statsService;
mTetherController = tetherController;
mIfaceName = ifaceName;
mInterfaceType = interfaceType;
mIPv6TetherSvc = ipv6Svc;
mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
mInitialState = new InitialState();
mLocalHotspotState = new LocalHotspotState();
mTetheredState = new TetheredState();
mUnavailableState = new UnavailableState();
addState(mInitialState);
addState(mLocalHotspotState);
addState(mTetheredState);
addState(mUnavailableState);
setInitialState(mInitialState);
}
public String interfaceName() { return mIfaceName; }
public int interfaceType() { return mInterfaceType; }
public int lastError() { return mLastError; }
// configured when we start tethering and unconfig'd on error or conclusion
private boolean configureIfaceIp(boolean enabled) {
if (VDBG) Log.d(TAG, "configureIfaceIp(" + enabled + ")");
String ipAsString = null;
int prefixLen = 0;
if (mInterfaceType == ConnectivityManager.TETHERING_USB) {
ipAsString = USB_NEAR_IFACE_ADDR;
prefixLen = USB_PREFIX_LENGTH;
} else if (mInterfaceType == ConnectivityManager.TETHERING_WIFI) {
ipAsString = WIFI_HOST_IFACE_ADDR;
prefixLen = WIFI_HOST_IFACE_PREFIX_LENGTH;
} else {
// Nothing to do, BT does this elsewhere.
return true;
}
InterfaceConfiguration ifcg = null;
try {
ifcg = mNMService.getInterfaceConfig(mIfaceName);
if (ifcg != null) {
InetAddress addr = NetworkUtils.numericToInetAddress(ipAsString);
ifcg.setLinkAddress(new LinkAddress(addr, prefixLen));
if (mInterfaceType == ConnectivityManager.TETHERING_WIFI) {
// The WiFi stack has ownership of the interface up/down state.
// It is unclear whether the bluetooth or USB stacks will manage their own
// state.
ifcg.ignoreInterfaceUpDownStatus();
} else {
if (enabled) {
ifcg.setInterfaceUp();
} else {
ifcg.setInterfaceDown();
}
}
ifcg.clearFlag("running");
mNMService.setInterfaceConfig(mIfaceName, ifcg);
}
} catch (Exception e) {
Log.e(TAG, "Error configuring interface " + mIfaceName, e);
return false;
}
return true;
}
private void maybeLogMessage(State state, int what) {
if (DBG) {
Log.d(TAG, state.getName() + " got " +
sMagicDecoderRing.get(what, Integer.toString(what)) + ", Iface = " +
mIfaceName);
}
}
private void sendInterfaceState(int newInterfaceState) {
mTetherController.notifyInterfaceStateChange(
mIfaceName, TetherInterfaceStateMachine.this, newInterfaceState, mLastError);
}
class InitialState extends State {
@Override
public void enter() {
sendInterfaceState(IControlsTethering.STATE_AVAILABLE);
}
@Override
public boolean processMessage(Message message) {
maybeLogMessage(this, message.what);
boolean retValue = true;
switch (message.what) {
case CMD_TETHER_REQUESTED:
mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
switch (message.arg1) {
case IControlsTethering.STATE_LOCAL_ONLY:
transitionTo(mLocalHotspotState);
break;
case IControlsTethering.STATE_TETHERED:
transitionTo(mTetheredState);
break;
default:
Log.e(TAG, "Invalid tethering interface serving state specified.");
}
break;
case CMD_INTERFACE_DOWN:
transitionTo(mUnavailableState);
break;
case CMD_IPV6_TETHER_UPDATE:
mIPv6TetherSvc.updateUpstreamIPv6LinkProperties(
(LinkProperties) message.obj);
break;
default:
retValue = false;
break;
}
return retValue;
}
}
class BaseServingState extends State {
@Override
public void enter() {
if (!configureIfaceIp(true)) {
mLastError = ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR;
return;
}
try {
mNMService.tetherInterface(mIfaceName);
} catch (Exception e) {
Log.e(TAG, "Error Tethering: " + e.toString());
mLastError = ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR;
return;
}
if (!mIPv6TetherSvc.start()) {
Log.e(TAG, "Failed to start IPv6TetheringInterfaceServices");
// TODO: Make this a fatal error once Bluetooth IPv6 is sorted.
return;
}
}
@Override
public void exit() {
// Note that at this point, we're leaving the tethered state. We can fail any
// of these operations, but it doesn't really change that we have to try them
// all in sequence.
mIPv6TetherSvc.stop();
try {
mNMService.untetherInterface(mIfaceName);
} catch (Exception e) {
mLastError = ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR;
Log.e(TAG, "Failed to untether interface: " + e.toString());
}
configureIfaceIp(false);
}
@Override
public boolean processMessage(Message message) {
maybeLogMessage(this, message.what);
switch (message.what) {
case CMD_TETHER_UNREQUESTED:
transitionTo(mInitialState);
if (DBG) Log.d(TAG, "Untethered (unrequested)" + mIfaceName);
break;
case CMD_INTERFACE_DOWN:
transitionTo(mUnavailableState);
if (DBG) Log.d(TAG, "Untethered (ifdown)" + mIfaceName);
break;
case CMD_IPV6_TETHER_UPDATE:
mIPv6TetherSvc.updateUpstreamIPv6LinkProperties(
(LinkProperties) message.obj);
break;
case CMD_IP_FORWARDING_ENABLE_ERROR:
case CMD_IP_FORWARDING_DISABLE_ERROR:
case CMD_START_TETHERING_ERROR:
case CMD_STOP_TETHERING_ERROR:
case CMD_SET_DNS_FORWARDERS_ERROR:
mLastError = ConnectivityManager.TETHER_ERROR_MASTER_ERROR;
transitionTo(mInitialState);
break;
default:
return false;
}
return true;
}
}
// Handling errors in BaseServingState.enter() by transitioning is
// problematic because transitioning during a multi-state jump yields
// a Log.wtf(). Ultimately, there should be only one ServingState,
// and forwarding and NAT rules should be handled by a coordinating
// functional element outside of TetherInterfaceStateMachine.
class LocalHotspotState extends BaseServingState {
@Override
public void enter() {
super.enter();
if (mLastError != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
transitionTo(mInitialState);
}
if (DBG) Log.d(TAG, "Local hotspot " + mIfaceName);
sendInterfaceState(IControlsTethering.STATE_LOCAL_ONLY);
}
@Override
public boolean processMessage(Message message) {
if (super.processMessage(message)) return true;
maybeLogMessage(this, message.what);
switch (message.what) {
case CMD_TETHER_REQUESTED:
Log.e(TAG, "CMD_TETHER_REQUESTED while in local hotspot mode.");
break;
case CMD_TETHER_CONNECTION_CHANGED:
// Ignored in local hotspot state.
break;
default:
return false;
}
return true;
}
}
// Handling errors in BaseServingState.enter() by transitioning is
// problematic because transitioning during a multi-state jump yields
// a Log.wtf(). Ultimately, there should be only one ServingState,
// and forwarding and NAT rules should be handled by a coordinating
// functional element outside of TetherInterfaceStateMachine.
class TetheredState extends BaseServingState {
@Override
public void enter() {
super.enter();
if (mLastError != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
transitionTo(mInitialState);
}
if (DBG) Log.d(TAG, "Tethered " + mIfaceName);
sendInterfaceState(IControlsTethering.STATE_TETHERED);
}
@Override
public void exit() {
cleanupUpstream();
super.exit();
}
private void cleanupUpstream() {
if (mMyUpstreamIfaceName == null) return;
cleanupUpstreamInterface(mMyUpstreamIfaceName);
mMyUpstreamIfaceName = null;
}
private void cleanupUpstreamInterface(String upstreamIface) {
// Note that we don't care about errors here.
// Sometimes interfaces are gone before we get
// to remove their rules, which generates errors.
// Just do the best we can.
try {
// About to tear down NAT; gather remaining statistics.
mStatsService.forceUpdate();
} catch (Exception e) {
if (VDBG) Log.e(TAG, "Exception in forceUpdate: " + e.toString());
}
try {
mNMService.stopInterfaceForwarding(mIfaceName, upstreamIface);
} catch (Exception e) {
if (VDBG) Log.e(TAG, "Exception in removeInterfaceForward: " + e.toString());
}
try {
mNMService.disableNat(mIfaceName, upstreamIface);
} catch (Exception e) {
if (VDBG) Log.e(TAG, "Exception in disableNat: " + e.toString());
}
}
@Override
public boolean processMessage(Message message) {
if (super.processMessage(message)) return true;
maybeLogMessage(this, message.what);
boolean retValue = true;
switch (message.what) {
case CMD_TETHER_REQUESTED:
Log.e(TAG, "CMD_TETHER_REQUESTED while already tethering.");
break;
case CMD_TETHER_CONNECTION_CHANGED:
String newUpstreamIfaceName = (String)(message.obj);
if ((mMyUpstreamIfaceName == null && newUpstreamIfaceName == null) ||
(mMyUpstreamIfaceName != null &&
mMyUpstreamIfaceName.equals(newUpstreamIfaceName))) {
if (VDBG) Log.d(TAG, "Connection changed noop - dropping");
break;
}
cleanupUpstream();
if (newUpstreamIfaceName != null) {
try {
mNMService.enableNat(mIfaceName, newUpstreamIfaceName);
mNMService.startInterfaceForwarding(mIfaceName,
newUpstreamIfaceName);
} catch (Exception e) {
Log.e(TAG, "Exception enabling Nat: " + e.toString());
cleanupUpstreamInterface(newUpstreamIfaceName);
mLastError = ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR;
transitionTo(mInitialState);
return true;
}
}
mMyUpstreamIfaceName = newUpstreamIfaceName;
break;
default:
retValue = false;
break;
}
return retValue;
}
}
/**
* This state is terminal for the per interface state machine. At this
* point, the master state machine should have removed this interface
* specific state machine from its list of possible recipients of
* tethering requests. The state machine itself will hang around until
* the garbage collector finds it.
*/
class UnavailableState extends State {
@Override
public void enter() {
mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
sendInterfaceState(IControlsTethering.STATE_UNAVAILABLE);
}
}
}