diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java index 1012f9aa031f1..4a0e81b42734c 100644 --- a/services/core/java/com/android/server/connectivity/Tethering.java +++ b/services/core/java/com/android/server/connectivity/Tethering.java @@ -34,8 +34,6 @@ import android.hardware.usb.UsbManager; import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; import android.net.INetworkStatsService; -import android.net.InterfaceConfiguration; -import android.net.LinkAddress; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; @@ -58,18 +56,20 @@ import android.provider.Settings; import android.telephony.CarrierConfigManager; import android.telephony.TelephonyManager; import android.text.TextUtils; +import android.util.ArrayMap; import android.util.Log; import android.util.SparseArray; import com.android.internal.telephony.IccCardConstants; import com.android.internal.telephony.TelephonyIntents; -import com.android.internal.util.IState; import com.android.internal.util.IndentingPrintWriter; 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 com.android.server.IoThread; +import com.android.server.connectivity.tethering.IControlsTethering; +import com.android.server.connectivity.tethering.TetherInterfaceStateMachine; import com.android.server.net.BaseNetworkObserver; import java.io.FileDescriptor; @@ -81,6 +81,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; +import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; @@ -88,11 +89,10 @@ import java.util.concurrent.atomic.AtomicInteger; /** * @hide * - * Timeout - * - * TODO - look for parent classes and code sharing + * This class holds much of the business logic to allow Android devices + * to act as IP gateways via USB, BT, and WiFi interfaces. */ -public class Tethering extends BaseNetworkObserver { +public class Tethering extends BaseNetworkObserver implements IControlsTethering { private final Context mContext; private final static String TAG = "Tethering"; @@ -100,7 +100,7 @@ public class Tethering extends BaseNetworkObserver { private final static boolean VDBG = false; private static final Class[] messageClasses = { - Tethering.class, TetherMasterSM.class, TetherInterfaceSM.class + Tethering.class, TetherMasterSM.class, TetherInterfaceStateMachine.class }; private static final SparseArray sMagicDecoderRing = MessageUtils.findMessageNames(messageClasses); @@ -126,17 +126,14 @@ public class Tethering extends BaseNetworkObserver { private final INetworkStatsService mStatsService; private final Looper mLooper; - private HashMap mIfaces; // all tethered/tetherable ifaces + private Map mIfaces; // all tethered/tetherable ifaces - private BroadcastReceiver mStateReceiver; + private final BroadcastReceiver mStateReceiver; // {@link ComponentName} of the Service used to run tether provisioning. private static final ComponentName TETHER_SERVICE = ComponentName.unflattenFromString(Resources .getSystem().getString(com.android.internal.R.string.config_wifi_tether_enable)); - private static final String USB_NEAR_IFACE_ADDR = "192.168.42.129"; - private static final int USB_PREFIX_LENGTH = 24; - // USB is 192.168.42.1 and 255.255.255.0 // Wifi is 192.168.43.1 and 255.255.255.0 // BT is limited to max default of 5 connections. 192.168.44.1 to 192.168.48.1 @@ -166,6 +163,9 @@ public class Tethering extends BaseNetworkObserver { private boolean mUsbTetherRequested; // true if USB tethering should be started // when RNDIS is enabled + // True iff WiFi tethering should be started when soft AP is ready. + private boolean mWifiTetherRequested; + public Tethering(Context context, INetworkManagementService nmService, INetworkStatsService statsService) { mContext = context; @@ -174,7 +174,7 @@ public class Tethering extends BaseNetworkObserver { mPublicSync = new Object(); - mIfaces = new HashMap(); + mIfaces = new ArrayMap(); // make our own thread so we don't anr the system mLooper = IoThread.get().getLooper(); @@ -187,6 +187,7 @@ public class Tethering extends BaseNetworkObserver { IntentFilter filter = new IntentFilter(); filter.addAction(UsbManager.ACTION_USB_STATE); filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); + filter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION); filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); mContext.registerReceiver(mStateReceiver, filter); @@ -227,7 +228,7 @@ public class Tethering extends BaseNetworkObserver { int ifaceTypes[] = mContext.getResources().getIntArray( com.android.internal.R.array.config_tether_upstream_types); - Collection upstreamIfaceTypes = new ArrayList(); + Collection upstreamIfaceTypes = new ArrayList<>(); for (int i : ifaceTypes) { upstreamIfaceTypes.add(new Integer(i)); } @@ -248,33 +249,27 @@ public class Tethering extends BaseNetworkObserver { // Never called directly: only called from interfaceLinkStateChanged. // See NetlinkHandler.cpp:71. if (VDBG) Log.d(TAG, "interfaceStatusChanged " + iface + ", " + up); - boolean found = false; - boolean usb = false; synchronized (mPublicSync) { - if (isWifi(iface)) { - found = true; - } else if (isUsb(iface)) { - found = true; - usb = true; - } else if (isBluetooth(iface)) { - found = true; + int interfaceType = ifaceNameToType(iface); + if (interfaceType == ConnectivityManager.TETHERING_INVALID) { + return; } - if (found == false) return; - TetherInterfaceSM sm = mIfaces.get(iface); + TetherInterfaceStateMachine sm = mIfaces.get(iface); if (up) { if (sm == null) { - sm = new TetherInterfaceSM(iface, mLooper, usb); + sm = new TetherInterfaceStateMachine(iface, mLooper, interfaceType, + mNMService, mStatsService, this); mIfaces.put(iface, sm); sm.start(); } } else { - if (isUsb(iface)) { + if (interfaceType == ConnectivityManager.TETHERING_USB) { // ignore usb0 down after enabling RNDIS // we will handle disconnect in interfaceRemoved instead if (VDBG) Log.d(TAG, "ignore interface down for " + iface); } else if (sm != null) { - sm.sendMessage(TetherInterfaceSM.CMD_INTERFACE_DOWN); + sm.sendMessage(TetherInterfaceStateMachine.CMD_INTERFACE_DOWN); mIfaces.remove(iface); } } @@ -295,7 +290,7 @@ public class Tethering extends BaseNetworkObserver { } } - public boolean isWifi(String iface) { + private boolean isWifi(String iface) { synchronized (mPublicSync) { for (String regex : mTetherableWifiRegexs) { if (iface.matches(regex)) return true; @@ -304,7 +299,7 @@ public class Tethering extends BaseNetworkObserver { } } - public boolean isBluetooth(String iface) { + private boolean isBluetooth(String iface) { synchronized (mPublicSync) { for (String regex : mTetherableBluetoothRegexs) { if (iface.matches(regex)) return true; @@ -313,33 +308,34 @@ public class Tethering extends BaseNetworkObserver { } } + private int ifaceNameToType(String iface) { + if (isWifi(iface)) { + return ConnectivityManager.TETHERING_WIFI; + } else if (isUsb(iface)) { + return ConnectivityManager.TETHERING_USB; + } else if (isBluetooth(iface)) { + return ConnectivityManager.TETHERING_BLUETOOTH; + } + return ConnectivityManager.TETHERING_INVALID; + } + @Override public void interfaceAdded(String iface) { if (VDBG) Log.d(TAG, "interfaceAdded " + iface); - boolean found = false; - boolean usb = false; synchronized (mPublicSync) { - if (isWifi(iface)) { - found = true; - } - if (isUsb(iface)) { - found = true; - usb = true; - } - if (isBluetooth(iface)) { - found = true; - } - if (found == false) { + int interfaceType = ifaceNameToType(iface); + if (interfaceType == ConnectivityManager.TETHERING_INVALID) { if (VDBG) Log.d(TAG, iface + " is not a tetherable iface, ignoring"); return; } - TetherInterfaceSM sm = mIfaces.get(iface); + TetherInterfaceStateMachine sm = mIfaces.get(iface); if (sm != null) { if (VDBG) Log.d(TAG, "active iface (" + iface + ") reported as added, ignoring"); return; } - sm = new TetherInterfaceSM(iface, mLooper, usb); + sm = new TetherInterfaceStateMachine(iface, mLooper, interfaceType, + mNMService, mStatsService, this); mIfaces.put(iface, sm); sm.start(); } @@ -349,14 +345,14 @@ public class Tethering extends BaseNetworkObserver { public void interfaceRemoved(String iface) { if (VDBG) Log.d(TAG, "interfaceRemoved " + iface); synchronized (mPublicSync) { - TetherInterfaceSM sm = mIfaces.get(iface); + TetherInterfaceStateMachine sm = mIfaces.get(iface); if (sm == null) { if (VDBG) { Log.e(TAG, "attempting to remove unknown iface (" + iface + "), ignoring"); } return; } - sm.sendMessage(TetherInterfaceSM.CMD_INTERFACE_DOWN); + sm.sendMessage(TetherInterfaceStateMachine.CMD_INTERFACE_DOWN); mIfaces.remove(iface); } } @@ -413,24 +409,19 @@ public class Tethering extends BaseNetworkObserver { * for the specified interface. */ private void enableTetheringInternal(int type, boolean enable, ResultReceiver receiver) { - boolean isProvisioningRequired = isTetherProvisioningRequired(); + boolean isProvisioningRequired = enable && isTetherProvisioningRequired(); + int result; switch (type) { case ConnectivityManager.TETHERING_WIFI: - final WifiManager wifiManager = - (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); - if (wifiManager.setWifiApEnabled(null, enable)) { - sendTetherResult(receiver, ConnectivityManager.TETHER_ERROR_NO_ERROR); - if (enable && isProvisioningRequired) { - scheduleProvisioningRechecks(type); - } - } else{ - sendTetherResult(receiver, ConnectivityManager.TETHER_ERROR_MASTER_ERROR); + result = setWifiTethering(enable); + if (isProvisioningRequired && result == ConnectivityManager.TETHER_ERROR_NO_ERROR) { + scheduleProvisioningRechecks(type); } + sendTetherResult(receiver, result); break; case ConnectivityManager.TETHERING_USB: - int result = setUsbTethering(enable); - if (enable && isProvisioningRequired && - result == ConnectivityManager.TETHER_ERROR_NO_ERROR) { + result = setUsbTethering(enable); + if (isProvisioningRequired && result == ConnectivityManager.TETHER_ERROR_NO_ERROR) { scheduleProvisioningRechecks(type); } sendTetherResult(receiver, result); @@ -450,6 +441,20 @@ public class Tethering extends BaseNetworkObserver { } } + private int setWifiTethering(final boolean enable) { + synchronized (mPublicSync) { + // Note that we're maintaining a predicate that mWifiTetherRequested always matches + // our last request to WifiManager re: its AP enabled status. + mWifiTetherRequested = enable; + final WifiManager wifiManager = + (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); + if (wifiManager.setWifiApEnabled(null /* use existing wifi config */, enable)) { + return ConnectivityManager.TETHER_ERROR_NO_ERROR; + } + return ConnectivityManager.TETHER_ERROR_MASTER_ERROR; + } + } + private void setBluetoothTethering(final boolean enable, final ResultReceiver receiver) { final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); if (adapter == null || !adapter.isEnabled()) { @@ -576,38 +581,39 @@ public class Tethering extends BaseNetworkObserver { public int tether(String iface) { if (DBG) Log.d(TAG, "Tethering " + iface); - TetherInterfaceSM sm = null; synchronized (mPublicSync) { - sm = mIfaces.get(iface); + TetherInterfaceStateMachine sm = mIfaces.get(iface); + if (sm == null) { + Log.e(TAG, "Tried to Tether an unknown iface :" + iface + ", ignoring"); + return ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE; + } + // Ignore the error status of the interface. If the interface is available, + // the errors are referring to past tethering attempts anyway. + if (!sm.isAvailable()) { + Log.e(TAG, "Tried to Tether an unavailable iface :" + iface + ", ignoring"); + return ConnectivityManager.TETHER_ERROR_UNAVAIL_IFACE; + + } + sm.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED); + return ConnectivityManager.TETHER_ERROR_NO_ERROR; } - if (sm == null) { - Log.e(TAG, "Tried to Tether an unknown iface :" + iface + ", ignoring"); - return ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE; - } - if (!sm.isAvailable() && !sm.isErrored()) { - Log.e(TAG, "Tried to Tether an unavailable iface :" + iface + ", ignoring"); - return ConnectivityManager.TETHER_ERROR_UNAVAIL_IFACE; - } - sm.sendMessage(TetherInterfaceSM.CMD_TETHER_REQUESTED); - return ConnectivityManager.TETHER_ERROR_NO_ERROR; } public int untether(String iface) { if (DBG) Log.d(TAG, "Untethering " + iface); - TetherInterfaceSM sm = null; synchronized (mPublicSync) { - sm = mIfaces.get(iface); + TetherInterfaceStateMachine sm = mIfaces.get(iface); + if (sm == null) { + Log.e(TAG, "Tried to Untether an unknown iface :" + iface + ", ignoring"); + return ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE; + } + if (!sm.isTethered()) { + Log.e(TAG, "Tried to Untethered an errored iface :" + iface + ", ignoring"); + return ConnectivityManager.TETHER_ERROR_UNAVAIL_IFACE; + } + sm.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED); + return ConnectivityManager.TETHER_ERROR_NO_ERROR; } - if (sm == null) { - Log.e(TAG, "Tried to Untether an unknown iface :" + iface + ", ignoring"); - return ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE; - } - if (sm.isErrored()) { - Log.e(TAG, "Tried to Untethered an errored iface :" + iface + ", ignoring"); - return ConnectivityManager.TETHER_ERROR_UNAVAIL_IFACE; - } - sm.sendMessage(TetherInterfaceSM.CMD_TETHER_UNREQUESTED); - return ConnectivityManager.TETHER_ERROR_NO_ERROR; } public void untetherAll() { @@ -618,9 +624,8 @@ public class Tethering extends BaseNetworkObserver { } public int getLastTetherError(String iface) { - TetherInterfaceSM sm = null; synchronized (mPublicSync) { - sm = mIfaces.get(iface); + TetherInterfaceStateMachine sm = mIfaces.get(iface); if (sm == null) { Log.e(TAG, "Tried to getLastTetherError on an unknown iface :" + iface + ", ignoring"); @@ -632,7 +637,8 @@ public class Tethering extends BaseNetworkObserver { // TODO - move all private methods used only by the state machine into the state machine // to clarify what needs synchronized protection. - private void sendTetherStateChangedBroadcast() { + @Override + public void sendTetherStateChangedBroadcast() { if (!getConnectivityManager().isTetheringSupported()) return; ArrayList availableList = new ArrayList(); @@ -644,23 +650,23 @@ public class Tethering extends BaseNetworkObserver { boolean bluetoothTethered = false; synchronized (mPublicSync) { - Set ifaces = mIfaces.keySet(); - for (Object iface : ifaces) { - TetherInterfaceSM sm = mIfaces.get(iface); + Set ifaces = mIfaces.keySet(); + for (String iface : ifaces) { + TetherInterfaceStateMachine sm = mIfaces.get(iface); if (sm != null) { if (sm.isErrored()) { - erroredList.add((String)iface); + erroredList.add(iface); } else if (sm.isAvailable()) { - availableList.add((String)iface); + availableList.add(iface); } else if (sm.isTethered()) { - if (isUsb((String)iface)) { + if (isUsb(iface)) { usbTethered = true; - } else if (isWifi((String)iface)) { + } else if (isWifi(iface)) { wifiTethered = true; - } else if (isBluetooth((String)iface)) { + } else if (isBluetooth(iface)) { bluetoothTethered = true; } - activeList.add((String)iface); + activeList.add(iface); } } } @@ -770,7 +776,7 @@ public class Tethering extends BaseNetworkObserver { mRndisEnabled = intent.getBooleanExtra(UsbManager.USB_FUNCTION_RNDIS, false); // start tethering if we have a request pending if (usbConnected && mRndisEnabled && mUsbTetherRequested) { - tetherUsb(true); + tetherMatchingInterfaces(true, ConnectivityManager.TETHERING_USB); } mUsbTetherRequested = false; } @@ -782,69 +788,72 @@ public class Tethering extends BaseNetworkObserver { if (VDBG) Log.d(TAG, "Tethering got CONNECTIVITY_ACTION"); mTetherMasterSM.sendMessage(TetherMasterSM.CMD_UPSTREAM_CHANGED); } + } else if (action.equals(WifiManager.WIFI_AP_STATE_CHANGED_ACTION)) { + synchronized (Tethering.this.mPublicSync) { + if (!mWifiTetherRequested) { + // We only care when we're trying to tether via our WiFi interface. + return; + } + int curState = intent.getIntExtra(WifiManager.EXTRA_WIFI_AP_STATE, + WifiManager.WIFI_AP_STATE_DISABLED); + switch (curState) { + case WifiManager.WIFI_AP_STATE_ENABLING: + // We can see this state on the way to both enabled and failure states. + break; + case WifiManager.WIFI_AP_STATE_ENABLED: + // Tell an appropriate interface state machine that it should tether. + tetherMatchingInterfaces(true, ConnectivityManager.TETHERING_WIFI); + break; + case WifiManager.WIFI_AP_STATE_DISABLED: + case WifiManager.WIFI_AP_STATE_DISABLING: + case WifiManager.WIFI_AP_STATE_FAILED: + default: + if (DBG) { + Log.d(TAG, "Canceling WiFi tethering request - AP_STATE=" + + curState); + } + // Tell an appropriate interface state machine that + // it needs to tear itself down. + tetherMatchingInterfaces(false, ConnectivityManager.TETHERING_WIFI); + setWifiTethering(false); + break; + } + } } else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) { updateConfiguration(); } } } - private void tetherUsb(boolean enable) { - if (VDBG) Log.d(TAG, "tetherUsb " + enable); + private void tetherMatchingInterfaces(boolean enable, int interfaceType) { + if (VDBG) Log.d(TAG, "tetherMatchingInterfaces(" + enable + ", " + interfaceType + ")"); - String[] ifaces = new String[0]; + String[] ifaces = null; try { ifaces = mNMService.listInterfaces(); } catch (Exception e) { Log.e(TAG, "Error listing Interfaces", e); return; } - for (String iface : ifaces) { - if (isUsb(iface)) { - int result = (enable ? tether(iface) : untether(iface)); - if (result == ConnectivityManager.TETHER_ERROR_NO_ERROR) { - return; + String chosenIface = null; + if (ifaces != null) { + for (String iface : ifaces) { + if (ifaceNameToType(iface) == interfaceType) { + chosenIface = iface; + break; } } } - Log.e(TAG, "unable start or stop USB tethering"); - } - - // configured when we start tethering and unconfig'd on error or conclusion - private boolean configureUsbIface(boolean enabled) { - if (VDBG) Log.d(TAG, "configureUsbIface(" + enabled + ")"); - - // toggle the USB interfaces - String[] ifaces = new String[0]; - try { - ifaces = mNMService.listInterfaces(); - } catch (Exception e) { - Log.e(TAG, "Error listing Interfaces", e); - return false; + if (chosenIface == null) { + Log.e(TAG, "could not find iface of type " + interfaceType); + return; } - for (String iface : ifaces) { - if (isUsb(iface)) { - InterfaceConfiguration ifcg = null; - try { - ifcg = mNMService.getInterfaceConfig(iface); - if (ifcg != null) { - InetAddress addr = NetworkUtils.numericToInetAddress(USB_NEAR_IFACE_ADDR); - ifcg.setLinkAddress(new LinkAddress(addr, USB_PREFIX_LENGTH)); - if (enabled) { - ifcg.setInterfaceUp(); - } else { - ifcg.setInterfaceDown(); - } - ifcg.clearFlag("running"); - mNMService.setInterfaceConfig(iface, ifcg); - } - } catch (Exception e) { - Log.e(TAG, "Error configuring interface " + iface, e); - return false; - } - } - } - return true; + int result = (enable ? tether(chosenIface) : untether(chosenIface)); + if (result != ConnectivityManager.TETHER_ERROR_NO_ERROR) { + Log.e(TAG, "unable start or stop tethering on iface " + chosenIface); + return; + } } // TODO - return copies so people can't tamper @@ -869,7 +878,7 @@ public class Tethering extends BaseNetworkObserver { if (mRndisEnabled) { final long ident = Binder.clearCallingIdentity(); try { - tetherUsb(true); + tetherMatchingInterfaces(true, ConnectivityManager.TETHERING_USB); } finally { Binder.restoreCallingIdentity(ident); } @@ -880,7 +889,7 @@ public class Tethering extends BaseNetworkObserver { } else { final long ident = Binder.clearCallingIdentity(); try { - tetherUsb(false); + tetherMatchingInterfaces(false, ConnectivityManager.TETHERING_USB); } finally { Binder.restoreCallingIdentity(ident); } @@ -952,11 +961,11 @@ public class Tethering extends BaseNetworkObserver { public String[] getTetheredIfaces() { ArrayList list = new ArrayList(); synchronized (mPublicSync) { - Set keys = mIfaces.keySet(); - for (Object key : keys) { - TetherInterfaceSM sm = mIfaces.get(key); + Set keys = mIfaces.keySet(); + for (String key : keys) { + TetherInterfaceStateMachine sm = mIfaces.get(key); if (sm.isTethered()) { - list.add((String)key); + list.add(key); } } } @@ -970,11 +979,11 @@ public class Tethering extends BaseNetworkObserver { public String[] getTetherableIfaces() { ArrayList list = new ArrayList(); synchronized (mPublicSync) { - Set keys = mIfaces.keySet(); - for (Object key : keys) { - TetherInterfaceSM sm = mIfaces.get(key); + Set keys = mIfaces.keySet(); + for (String key : keys) { + TetherInterfaceStateMachine sm = mIfaces.get(key); if (sm.isAvailable()) { - list.add((String)key); + list.add(key); } } } @@ -992,11 +1001,11 @@ public class Tethering extends BaseNetworkObserver { public String[] getErroredIfaces() { ArrayList list = new ArrayList(); synchronized (mPublicSync) { - Set keys = mIfaces.keySet(); - for (Object key : keys) { - TetherInterfaceSM sm = mIfaces.get(key); + Set keys = mIfaces.keySet(); + for (String key : keys) { + TetherInterfaceStateMachine sm = mIfaces.get(key); if (sm.isErrored()) { - list.add((String)key); + list.add(key); } } } @@ -1014,401 +1023,6 @@ public class Tethering extends BaseNetworkObserver { } } - class TetherInterfaceSM extends StateMachine { - private static final int BASE_IFACE = Protocol.BASE_TETHERING + 100; - // notification from the master SM that it's not in tether mode - static final int CMD_TETHER_MODE_DEAD = BASE_IFACE + 1; - // request from the user that it wants to tether - static final int CMD_TETHER_REQUESTED = BASE_IFACE + 2; - // request from the user that it wants to untether - static final int CMD_TETHER_UNREQUESTED = BASE_IFACE + 3; - // notification that this interface is down - static final int CMD_INTERFACE_DOWN = BASE_IFACE + 4; - // notification that this interface is up - static final int CMD_INTERFACE_UP = BASE_IFACE + 5; - // notification from the master SM that it had an error turning on cellular dun - static final int CMD_CELL_DUN_ERROR = BASE_IFACE + 6; - // notification from the master SM that it had trouble enabling IP Forwarding - static final int CMD_IP_FORWARDING_ENABLE_ERROR = BASE_IFACE + 7; - // notification from the master SM that it had trouble disabling IP Forwarding - static final int CMD_IP_FORWARDING_DISABLE_ERROR = BASE_IFACE + 8; - // notification from the master SM that it had trouble starting tethering - static final int CMD_START_TETHERING_ERROR = BASE_IFACE + 9; - // notification from the master SM that it had trouble stopping tethering - static final int CMD_STOP_TETHERING_ERROR = BASE_IFACE + 10; - // notification from the master SM that it had trouble setting the DNS forwarders - static final int CMD_SET_DNS_FORWARDERS_ERROR = BASE_IFACE + 11; - // the upstream connection has changed - static final int CMD_TETHER_CONNECTION_CHANGED = BASE_IFACE + 12; - - private State mDefaultState; - - private State mInitialState; - private State mStartingState; - private State mTetheredState; - - private State mUnavailableState; - - private boolean mAvailable; - private boolean mTethered; - int mLastError; - - String mIfaceName; - String mMyUpstreamIfaceName; // may change over time - - boolean mUsb; - - TetherInterfaceSM(String name, Looper looper, boolean usb) { - super(name, looper); - mIfaceName = name; - mUsb = usb; - setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR); - - mInitialState = new InitialState(); - addState(mInitialState); - mStartingState = new StartingState(); - addState(mStartingState); - mTetheredState = new TetheredState(); - addState(mTetheredState); - mUnavailableState = new UnavailableState(); - addState(mUnavailableState); - - setInitialState(mInitialState); - } - - public String toString() { - String res = new String(); - res += mIfaceName + " - "; - IState current = getCurrentState(); - if (current == mInitialState) res += "InitialState"; - if (current == mStartingState) res += "StartingState"; - if (current == mTetheredState) res += "TetheredState"; - if (current == mUnavailableState) res += "UnavailableState"; - if (mAvailable) res += " - Available"; - if (mTethered) res += " - Tethered"; - res += " - lastError =" + mLastError; - return res; - } - - public int getLastError() { - synchronized (Tethering.this.mPublicSync) { - return mLastError; - } - } - - private void setLastError(int error) { - synchronized (Tethering.this.mPublicSync) { - mLastError = error; - - if (isErrored()) { - if (mUsb) { - // note everything's been unwound by this point so nothing to do on - // further error.. - Tethering.this.configureUsbIface(false); - } - } - } - } - - public boolean isAvailable() { - synchronized (Tethering.this.mPublicSync) { - return mAvailable; - } - } - - private void setAvailable(boolean available) { - synchronized (Tethering.this.mPublicSync) { - mAvailable = available; - } - } - - public boolean isTethered() { - synchronized (Tethering.this.mPublicSync) { - return mTethered; - } - } - - private void setTethered(boolean tethered) { - synchronized (Tethering.this.mPublicSync) { - mTethered = tethered; - } - } - - public boolean isErrored() { - synchronized (Tethering.this.mPublicSync) { - return (mLastError != ConnectivityManager.TETHER_ERROR_NO_ERROR); - } - } - - class InitialState extends State { - @Override - public void enter() { - setAvailable(true); - setTethered(false); - sendTetherStateChangedBroadcast(); - } - - @Override - public boolean processMessage(Message message) { - maybeLogMessage(this, message.what); - boolean retValue = true; - switch (message.what) { - case CMD_TETHER_REQUESTED: - setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR); - mTetherMasterSM.sendMessage(TetherMasterSM.CMD_TETHER_MODE_REQUESTED, - TetherInterfaceSM.this); - transitionTo(mStartingState); - break; - case CMD_INTERFACE_DOWN: - transitionTo(mUnavailableState); - break; - default: - retValue = false; - break; - } - return retValue; - } - } - - class StartingState extends State { - @Override - public void enter() { - setAvailable(false); - if (mUsb) { - if (!Tethering.this.configureUsbIface(true)) { - mTetherMasterSM.sendMessage(TetherMasterSM.CMD_TETHER_MODE_UNREQUESTED, - TetherInterfaceSM.this); - setLastError(ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR); - - transitionTo(mInitialState); - return; - } - } - sendTetherStateChangedBroadcast(); - - // Skipping StartingState - transitionTo(mTetheredState); - } - @Override - public boolean processMessage(Message message) { - maybeLogMessage(this, message.what); - boolean retValue = true; - switch (message.what) { - // maybe a parent class? - case CMD_TETHER_UNREQUESTED: - mTetherMasterSM.sendMessage(TetherMasterSM.CMD_TETHER_MODE_UNREQUESTED, - TetherInterfaceSM.this); - if (mUsb) { - if (!Tethering.this.configureUsbIface(false)) { - setLastErrorAndTransitionToInitialState( - ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR); - break; - } - } - transitionTo(mInitialState); - break; - case CMD_CELL_DUN_ERROR: - 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: - setLastErrorAndTransitionToInitialState( - ConnectivityManager.TETHER_ERROR_MASTER_ERROR); - break; - case CMD_INTERFACE_DOWN: - mTetherMasterSM.sendMessage(TetherMasterSM.CMD_TETHER_MODE_UNREQUESTED, - TetherInterfaceSM.this); - transitionTo(mUnavailableState); - break; - default: - retValue = false; - } - return retValue; - } - } - - class TetheredState extends State { - @Override - public void enter() { - try { - mNMService.tetherInterface(mIfaceName); - } catch (Exception e) { - Log.e(TAG, "Error Tethering: " + e.toString()); - setLastError(ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR); - - try { - mNMService.untetherInterface(mIfaceName); - } catch (Exception ee) { - Log.e(TAG, "Error untethering after failure!" + ee.toString()); - } - transitionTo(mInitialState); - return; - } - if (DBG) Log.d(TAG, "Tethered " + mIfaceName); - setAvailable(false); - setTethered(true); - sendTetherStateChangedBroadcast(); - } - - private void cleanupUpstream() { - if (mMyUpstreamIfaceName != null) { - // 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, mMyUpstreamIfaceName); - } catch (Exception e) { - if (VDBG) Log.e( - TAG, "Exception in removeInterfaceForward: " + e.toString()); - } - try { - mNMService.disableNat(mIfaceName, mMyUpstreamIfaceName); - } catch (Exception e) { - if (VDBG) Log.e(TAG, "Exception in disableNat: " + e.toString()); - } - mMyUpstreamIfaceName = null; - } - return; - } - - @Override - public boolean processMessage(Message message) { - maybeLogMessage(this, message.what); - boolean retValue = true; - boolean error = false; - switch (message.what) { - case CMD_TETHER_UNREQUESTED: - case CMD_INTERFACE_DOWN: - cleanupUpstream(); - try { - mNMService.untetherInterface(mIfaceName); - } catch (Exception e) { - setLastErrorAndTransitionToInitialState( - ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR); - break; - } - mTetherMasterSM.sendMessage(TetherMasterSM.CMD_TETHER_MODE_UNREQUESTED, - TetherInterfaceSM.this); - if (message.what == CMD_TETHER_UNREQUESTED) { - if (mUsb) { - if (!Tethering.this.configureUsbIface(false)) { - setLastError( - ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR); - } - } - transitionTo(mInitialState); - } else if (message.what == CMD_INTERFACE_DOWN) { - transitionTo(mUnavailableState); - } - if (DBG) Log.d(TAG, "Untethered " + mIfaceName); - 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()); - try { - mNMService.disableNat(mIfaceName, newUpstreamIfaceName); - } catch (Exception ee) {} - try { - mNMService.untetherInterface(mIfaceName); - } catch (Exception ee) {} - - setLastError(ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR); - transitionTo(mInitialState); - return true; - } - } - mMyUpstreamIfaceName = newUpstreamIfaceName; - break; - case CMD_CELL_DUN_ERROR: - 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: - error = true; - // fall through - case CMD_TETHER_MODE_DEAD: - cleanupUpstream(); - try { - mNMService.untetherInterface(mIfaceName); - } catch (Exception e) { - setLastErrorAndTransitionToInitialState( - ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR); - break; - } - if (error) { - setLastErrorAndTransitionToInitialState( - ConnectivityManager.TETHER_ERROR_MASTER_ERROR); - break; - } - if (DBG) Log.d(TAG, "Tether lost upstream connection " + mIfaceName); - sendTetherStateChangedBroadcast(); - if (mUsb) { - if (!Tethering.this.configureUsbIface(false)) { - setLastError(ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR); - } - } - transitionTo(mInitialState); - break; - default: - retValue = false; - break; - } - return retValue; - } - } - - class UnavailableState extends State { - @Override - public void enter() { - setAvailable(false); - setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR); - setTethered(false); - sendTetherStateChangedBroadcast(); - } - @Override - public boolean processMessage(Message message) { - boolean retValue = true; - switch (message.what) { - case CMD_INTERFACE_UP: - transitionTo(mInitialState); - break; - default: - retValue = false; - break; - } - return retValue; - } - } - - void setLastErrorAndTransitionToInitialState(int error) { - setLastError(error); - transitionTo(mInitialState); - } - - } - /** * A NetworkCallback class that relays information of interest to the * tethering master state machine thread for subsequent processing. @@ -1442,7 +1056,7 @@ public class Tethering extends BaseNetworkObserver { * could/should be moved here. */ class UpstreamNetworkMonitor { - final HashMap mNetworkMap = new HashMap(); + final HashMap mNetworkMap = new HashMap<>(); NetworkCallback mDefaultNetworkCallback; NetworkCallback mDunTetheringCallback; @@ -1520,12 +1134,6 @@ public class Tethering extends BaseNetworkObserver { static final int EVENT_UPSTREAM_LINKPROPERTIES_CHANGED = BASE_MASTER + 5; static final int EVENT_UPSTREAM_LOST = BASE_MASTER + 6; - // This indicates what a timeout event relates to. A state that - // sends itself a delayed timeout event and handles incoming timeout events - // should inc this when it is entered and whenever it sends a new timeout event. - // We do not flush the old ones. - private int mSequenceNumber; - private State mInitialState; private State mTetherModeAliveState; @@ -1535,7 +1143,7 @@ public class Tethering extends BaseNetworkObserver { private State mStopTetheringErrorState; private State mSetDnsForwardersErrorState; - private ArrayList mNotifyList; + private ArrayList mNotifyList; private int mMobileApnReserved = ConnectivityManager.TYPE_NONE; private NetworkCallback mMobileUpstreamCallback; @@ -1562,7 +1170,7 @@ public class Tethering extends BaseNetworkObserver { mSetDnsForwardersErrorState = new SetDnsForwardersErrorState(); addState(mSetDnsForwardersErrorState); - mNotifyList = new ArrayList(); + mNotifyList = new ArrayList<>(); setInitialState(mInitialState); } @@ -1777,8 +1385,8 @@ public class Tethering extends BaseNetworkObserver { protected void notifyTetheredOfNewUpstreamIface(String ifaceName) { if (DBG) Log.d(TAG, "Notifying tethered with upstream=" + ifaceName); mCurrentUpstreamIface = ifaceName; - for (TetherInterfaceSM sm : mNotifyList) { - sm.sendMessage(TetherInterfaceSM.CMD_TETHER_CONNECTION_CHANGED, + for (TetherInterfaceStateMachine sm : mNotifyList) { + sm.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_CONNECTION_CHANGED, ifaceName); } } @@ -1845,19 +1453,14 @@ public class Tethering extends BaseNetworkObserver { config_mobile_hotspot_provision_app_no_ui).isEmpty() == false) { ArrayList tethered = new ArrayList(); synchronized (mPublicSync) { - Set ifaces = mIfaces.keySet(); - for (Object iface : ifaces) { - TetherInterfaceSM sm = mIfaces.get(iface); + Set ifaces = mIfaces.keySet(); + for (String iface : ifaces) { + TetherInterfaceStateMachine sm = mIfaces.get(iface); if (sm != null && sm.isTethered()) { - if (isUsb((String)iface)) { - tethered.add(new Integer( - ConnectivityManager.TETHERING_USB)); - } else if (isWifi((String)iface)) { - tethered.add(new Integer( - ConnectivityManager.TETHERING_WIFI)); - } else if (isBluetooth((String)iface)) { - tethered.add(new Integer( - ConnectivityManager.TETHERING_BLUETOOTH)); + int interfaceType = ifaceNameToType(iface); + if (interfaceType != + ConnectivityManager.TETHERING_INVALID) { + tethered.add(new Integer(interfaceType)); } } } @@ -1893,13 +1496,13 @@ public class Tethering extends BaseNetworkObserver { boolean retValue = true; switch (message.what) { case CMD_TETHER_MODE_REQUESTED: - TetherInterfaceSM who = (TetherInterfaceSM)message.obj; + TetherInterfaceStateMachine who = (TetherInterfaceStateMachine)message.obj; if (VDBG) Log.d(TAG, "Tether Mode requested by " + who); mNotifyList.add(who); transitionTo(mTetherModeAliveState); break; case CMD_TETHER_MODE_UNREQUESTED: - who = (TetherInterfaceSM)message.obj; + who = (TetherInterfaceStateMachine)message.obj; if (VDBG) Log.d(TAG, "Tether Mode unrequested by " + who); int index = mNotifyList.indexOf(who); if (index != -1) { @@ -1941,14 +1544,14 @@ public class Tethering extends BaseNetworkObserver { boolean retValue = true; switch (message.what) { case CMD_TETHER_MODE_REQUESTED: - TetherInterfaceSM who = (TetherInterfaceSM)message.obj; + TetherInterfaceStateMachine who = (TetherInterfaceStateMachine)message.obj; if (VDBG) Log.d(TAG, "Tether Mode requested by " + who); mNotifyList.add(who); - who.sendMessage(TetherInterfaceSM.CMD_TETHER_CONNECTION_CHANGED, + who.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_CONNECTION_CHANGED, mCurrentUpstreamIface); break; case CMD_TETHER_MODE_UNREQUESTED: - who = (TetherInterfaceSM)message.obj; + who = (TetherInterfaceStateMachine)message.obj; if (VDBG) Log.d(TAG, "Tether Mode unrequested by " + who); int index = mNotifyList.indexOf(who); if (index != -1) { @@ -2010,7 +1613,7 @@ public class Tethering extends BaseNetworkObserver { boolean retValue = true; switch (message.what) { case CMD_TETHER_MODE_REQUESTED: - TetherInterfaceSM who = (TetherInterfaceSM)message.obj; + TetherInterfaceStateMachine who = (TetherInterfaceStateMachine)message.obj; who.sendMessage(mErrorNotification); break; default: @@ -2021,7 +1624,7 @@ public class Tethering extends BaseNetworkObserver { void notify(int msgType) { mErrorNotification = msgType; for (Object o : mNotifyList) { - TetherInterfaceSM sm = (TetherInterfaceSM)o; + TetherInterfaceStateMachine sm = (TetherInterfaceStateMachine)o; sm.sendMessage(msgType); } } @@ -2031,7 +1634,7 @@ public class Tethering extends BaseNetworkObserver { @Override public void enter() { Log.e(TAG, "Error in setIpForwardingEnabled"); - notify(TetherInterfaceSM.CMD_IP_FORWARDING_ENABLE_ERROR); + notify(TetherInterfaceStateMachine.CMD_IP_FORWARDING_ENABLE_ERROR); } } @@ -2039,7 +1642,7 @@ public class Tethering extends BaseNetworkObserver { @Override public void enter() { Log.e(TAG, "Error in setIpForwardingDisabled"); - notify(TetherInterfaceSM.CMD_IP_FORWARDING_DISABLE_ERROR); + notify(TetherInterfaceStateMachine.CMD_IP_FORWARDING_DISABLE_ERROR); } } @@ -2047,7 +1650,7 @@ public class Tethering extends BaseNetworkObserver { @Override public void enter() { Log.e(TAG, "Error in startTethering"); - notify(TetherInterfaceSM.CMD_START_TETHERING_ERROR); + notify(TetherInterfaceStateMachine.CMD_START_TETHERING_ERROR); try { mNMService.setIpForwardingEnabled(false); } catch (Exception e) {} @@ -2058,7 +1661,7 @@ public class Tethering extends BaseNetworkObserver { @Override public void enter() { Log.e(TAG, "Error in stopTethering"); - notify(TetherInterfaceSM.CMD_STOP_TETHERING_ERROR); + notify(TetherInterfaceStateMachine.CMD_STOP_TETHERING_ERROR); try { mNMService.setIpForwardingEnabled(false); } catch (Exception e) {} @@ -2069,7 +1672,7 @@ public class Tethering extends BaseNetworkObserver { @Override public void enter() { Log.e(TAG, "Error in setDnsForwarders"); - notify(TetherInterfaceSM.CMD_SET_DNS_FORWARDERS_ERROR); + notify(TetherInterfaceStateMachine.CMD_SET_DNS_FORWARDERS_ERROR); try { mNMService.stopTethering(); } catch (Exception e) {} @@ -2080,9 +1683,11 @@ public class Tethering extends BaseNetworkObserver { } } + @Override public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { + // Binder.java closes the resource for us. + @SuppressWarnings("resource") final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); - if (mContext.checkCallingOrSelfPermission( android.Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) { pw.println("Permission Denial: can't dump ConnectivityService.Tether " + @@ -2108,6 +1713,12 @@ public class Tethering extends BaseNetworkObserver { pw.decreaseIndent(); } pw.decreaseIndent(); - return; + } + + @Override + public void notifyInterfaceTetheringReadiness(boolean isReady, + TetherInterfaceStateMachine who) { + mTetherMasterSM.sendMessage((isReady) ? TetherMasterSM.CMD_TETHER_MODE_REQUESTED + : TetherMasterSM.CMD_TETHER_MODE_UNREQUESTED, who); } } diff --git a/services/core/java/com/android/server/connectivity/tethering/IControlsTethering.java b/services/core/java/com/android/server/connectivity/tethering/IControlsTethering.java new file mode 100644 index 0000000000000..7677daf808e80 --- /dev/null +++ b/services/core/java/com/android/server/connectivity/tethering/IControlsTethering.java @@ -0,0 +1,27 @@ +/* + * 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; + +/** + * @hide + * + * Interface with methods necessary to notify that a given interface is ready for tethering. + */ +public interface IControlsTethering { + void sendTetherStateChangedBroadcast(); + void notifyInterfaceTetheringReadiness(boolean isReady, TetherInterfaceStateMachine who); +} diff --git a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java new file mode 100644 index 0000000000000..b8bea60a5ccb4 --- /dev/null +++ b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java @@ -0,0 +1,360 @@ +/* + * 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.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.IState; +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 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; + + private final State mInitialState; + 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 int mLastError; + private String mMyUpstreamIfaceName; // may change over time + + public TetherInterfaceStateMachine(String ifaceName, Looper looper, int interfaceType, + INetworkManagementService nMService, INetworkStatsService statsService, + IControlsTethering tetherController) { + super(ifaceName, looper); + mNMService = nMService; + mStatsService = statsService; + mTetherController = tetherController; + mIfaceName = ifaceName; + mInterfaceType = interfaceType; + setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR); + + mInitialState = new InitialState(); + addState(mInitialState); + mTetheredState = new TetheredState(); + addState(mTetheredState); + mUnavailableState = new UnavailableState(); + addState(mUnavailableState); + + setInitialState(mInitialState); + } + + @Override + public String toString() { + String res = new String(); + res += mIfaceName + " - "; + IState current = getCurrentState(); + if (current == mInitialState) res += "InitialState"; + if (current == mTetheredState) res += "TetheredState"; + if (current == mUnavailableState) res += "UnavailableState"; + if (isAvailable()) res += " - Available"; + if (isTethered()) res += " - Tethered"; + res += " - lastError =" + getLastError(); + return res; + } + + public int getLastError() { + return mLastError; + } + + private void setLastError(int error) { + mLastError = error; + } + + public boolean isAvailable() { + return getCurrentState() == mInitialState; + } + + public boolean isTethered() { + return getCurrentState() == mTetheredState; + } + + public boolean isErrored() { + return (mLastError != ConnectivityManager.TETHER_ERROR_NO_ERROR); + } + + // 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 (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))); + } + } + + class InitialState extends State { + @Override + public void enter() { + mTetherController.sendTetherStateChangedBroadcast(); + } + + @Override + public boolean processMessage(Message message) { + maybeLogMessage(this, message.what); + boolean retValue = true; + switch (message.what) { + case CMD_TETHER_REQUESTED: + setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR); + mTetherController.notifyInterfaceTetheringReadiness(true, TetherInterfaceStateMachine.this); + transitionTo(mTetheredState); + break; + case CMD_INTERFACE_DOWN: + transitionTo(mUnavailableState); + break; + default: + retValue = false; + break; + } + return retValue; + } + } + + class TetheredState extends State { + @Override + public void enter() { + if (!configureIfaceIp(true)) { + setLastError(ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR); + transitionTo(mInitialState); + return; + } + + try { + mNMService.tetherInterface(mIfaceName); + } catch (Exception e) { + Log.e(TAG, "Error Tethering: " + e.toString()); + setLastError(ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR); + transitionTo(mInitialState); + return; + } + if (DBG) Log.d(TAG, "Tethered " + mIfaceName); + mTetherController.sendTetherStateChangedBroadcast(); + } + + @Override + public void exit() { + mTetherController.notifyInterfaceTetheringReadiness(false, + TetherInterfaceStateMachine.this); + + // 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. + cleanupUpstream(); + + try { + mNMService.untetherInterface(mIfaceName); + } catch (Exception ee) { + setLastError(ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR); + Log.e(TAG, "Failed to untether interface: " + ee.toString()); + } + + configureIfaceIp(false); + } + + private void cleanupUpstream() { + if (mMyUpstreamIfaceName != null) { + // 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, mMyUpstreamIfaceName); + } catch (Exception e) { + if (VDBG) Log.e( + TAG, "Exception in removeInterfaceForward: " + e.toString()); + } + try { + mNMService.disableNat(mIfaceName, mMyUpstreamIfaceName); + } catch (Exception e) { + if (VDBG) Log.e(TAG, "Exception in disableNat: " + e.toString()); + } + mMyUpstreamIfaceName = null; + } + return; + } + + @Override + public boolean processMessage(Message message) { + maybeLogMessage(this, message.what); + boolean retValue = true; + 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_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()); + setLastError(ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR); + transitionTo(mInitialState); + return true; + } + } + mMyUpstreamIfaceName = newUpstreamIfaceName; + 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: + setLastErrorAndTransitionToInitialState( + ConnectivityManager.TETHER_ERROR_MASTER_ERROR); + 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() { + setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR); + mTetherController.sendTetherStateChangedBroadcast(); + } + } + + void setLastErrorAndTransitionToInitialState(int error) { + setLastError(error); + transitionTo(mInitialState); + } +} diff --git a/services/tests/servicestests/Android.mk b/services/tests/servicestests/Android.mk index 59c6970d1feb8..50e0662ad4d40 100644 --- a/services/tests/servicestests/Android.mk +++ b/services/tests/servicestests/Android.mk @@ -12,6 +12,7 @@ LOCAL_MODULE_TAGS := tests LOCAL_SRC_FILES := $(call all-java-files-under, src) LOCAL_STATIC_JAVA_LIBRARIES := \ + frameworks-base-testutils \ services.core \ services.devicepolicy \ services.net \ diff --git a/services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java b/services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java new file mode 100644 index 0000000000000..30a7dbc80977c --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java @@ -0,0 +1,298 @@ +/* + * 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 static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.net.ConnectivityManager; +import android.net.INetworkStatsService; +import android.net.InterfaceConfiguration; +import android.os.INetworkManagementService; +import android.os.RemoteException; +import android.os.test.TestLooper; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class TetherInterfaceStateMachineTest { + private static final String IFACE_NAME = "testnet1"; + private static final String UPSTREAM_IFACE = "upstream0"; + private static final String UPSTREAM_IFACE2 = "upstream1"; + + @Mock private INetworkManagementService mNMService; + @Mock private INetworkStatsService mStatsService; + @Mock private IControlsTethering mTetherHelper; + @Mock private InterfaceConfiguration mInterfaceConfiguration; + + private final TestLooper mLooper = new TestLooper(); + private TetherInterfaceStateMachine mTestedSm; + + private void initStateMachine(int interfaceType) throws Exception { + mTestedSm = new TetherInterfaceStateMachine(IFACE_NAME, mLooper.getLooper(), interfaceType, + mNMService, mStatsService, mTetherHelper); + mTestedSm.start(); + // Starting the state machine always puts us in a consistent state and notifies + // the test of the world that we've changed from an unknown to available state. + mLooper.dispatchAll(); + reset(mNMService, mStatsService, mTetherHelper); + when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration); + } + + private void initTetheredStateMachine(int interfaceType, String upstreamIface) throws Exception { + initStateMachine(interfaceType); + dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED); + if (upstreamIface != null) { + dispatchTetherConnectionChanged(upstreamIface); + } + reset(mNMService, mStatsService, mTetherHelper); + when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration); + } + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + } + + @Test + public void startsOutAvailable() { + mTestedSm = new TetherInterfaceStateMachine(IFACE_NAME, mLooper.getLooper(), + ConnectivityManager.TETHERING_BLUETOOTH, mNMService, mStatsService, mTetherHelper); + mTestedSm.start(); + mLooper.dispatchAll(); + assertTrue("Should start out available for tethering", mTestedSm.isAvailable()); + assertFalse("Should not be tethered initially", mTestedSm.isTethered()); + assertFalse("Should have no errors initially", mTestedSm.isErrored()); + verify(mTetherHelper).sendTetherStateChangedBroadcast(); + verifyNoMoreInteractions(mTetherHelper, mNMService, mStatsService); + } + + @Test + public void shouldDoNothingUntilRequested() throws Exception { + initStateMachine(ConnectivityManager.TETHERING_BLUETOOTH); + final int [] NOOP_COMMANDS = { + TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED, + TetherInterfaceStateMachine.CMD_IP_FORWARDING_ENABLE_ERROR, + TetherInterfaceStateMachine.CMD_IP_FORWARDING_DISABLE_ERROR, + TetherInterfaceStateMachine.CMD_START_TETHERING_ERROR, + TetherInterfaceStateMachine.CMD_STOP_TETHERING_ERROR, + TetherInterfaceStateMachine.CMD_SET_DNS_FORWARDERS_ERROR, + TetherInterfaceStateMachine.CMD_TETHER_CONNECTION_CHANGED + }; + for (int command : NOOP_COMMANDS) { + // None of these commands should trigger us to request action from + // the rest of the system. + dispatchCommand(command); + verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper); + } + } + + @Test + public void handlesImmediateInterfaceDown() throws Exception { + initStateMachine(ConnectivityManager.TETHERING_BLUETOOTH); + dispatchCommand(TetherInterfaceStateMachine.CMD_INTERFACE_DOWN); + verify(mTetherHelper).sendTetherStateChangedBroadcast(); + verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper); + assertFalse("Should not be tetherable when the interface is down", mTestedSm.isAvailable()); + assertFalse("Should not be tethered when the interface is down", mTestedSm.isTethered()); + assertFalse("Should have no errors when the interface goes immediately down", + mTestedSm.isErrored()); + } + + @Test + public void canBeTethered() throws Exception { + initStateMachine(ConnectivityManager.TETHERING_BLUETOOTH); + dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED); + InOrder inOrder = inOrder(mTetherHelper, mNMService); + inOrder.verify(mTetherHelper).notifyInterfaceTetheringReadiness(true, mTestedSm); + inOrder.verify(mNMService).tetherInterface(IFACE_NAME); + inOrder.verify(mTetherHelper).sendTetherStateChangedBroadcast(); + + verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper); + assertFalse("Should not be tetherable when tethered", mTestedSm.isAvailable()); + assertTrue("Should be in a tethered state", mTestedSm.isTethered()); + assertFalse("Should have no errors when tethered", mTestedSm.isErrored()); + } + + @Test + public void canUnrequestTethering() throws Exception { + initTetheredStateMachine(ConnectivityManager.TETHERING_BLUETOOTH, null); + + dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED); + InOrder inOrder = inOrder(mNMService, mStatsService, mTetherHelper); + inOrder.verify(mTetherHelper).notifyInterfaceTetheringReadiness(false, mTestedSm); + inOrder.verify(mNMService).untetherInterface(IFACE_NAME); + inOrder.verify(mTetherHelper).sendTetherStateChangedBroadcast(); + verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper); + assertTrue("Should be ready for tethering again", mTestedSm.isAvailable()); + assertFalse("Should not be tethered", mTestedSm.isTethered()); + assertFalse("Should have no errors", mTestedSm.isErrored()); + } + + @Test + public void canBeTetheredAsUsb() throws Exception { + initStateMachine(ConnectivityManager.TETHERING_USB); + + dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED); + InOrder inOrder = inOrder(mTetherHelper, mNMService); + inOrder.verify(mTetherHelper).notifyInterfaceTetheringReadiness(true, mTestedSm); + inOrder.verify(mNMService).getInterfaceConfig(IFACE_NAME); + inOrder.verify(mNMService).setInterfaceConfig(IFACE_NAME, mInterfaceConfiguration); + inOrder.verify(mNMService).tetherInterface(IFACE_NAME); + inOrder.verify(mTetherHelper).sendTetherStateChangedBroadcast(); + + verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper); + assertFalse("Should not be tetherable when tethered", mTestedSm.isAvailable()); + assertTrue("Should be in a tethered state", mTestedSm.isTethered()); + assertFalse("Should have no errors when tethered", mTestedSm.isErrored()); + } + + @Test + public void handlesFirstUpstreamChange() throws Exception { + initTetheredStateMachine(ConnectivityManager.TETHERING_BLUETOOTH, null); + + // Telling the state machine about its upstream interface triggers a little more configuration. + dispatchTetherConnectionChanged(UPSTREAM_IFACE); + InOrder inOrder = inOrder(mNMService); + inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE); + inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE); + verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper); + assertFalse("Should not be tetherable when tethered", mTestedSm.isAvailable()); + assertTrue("Should be in a tethered state", mTestedSm.isTethered()); + assertFalse("Should have no errors when tethered", mTestedSm.isErrored()); + } + + @Test + public void handlesChangingUpstream() throws Exception { + initTetheredStateMachine(ConnectivityManager.TETHERING_BLUETOOTH, UPSTREAM_IFACE); + + dispatchTetherConnectionChanged(UPSTREAM_IFACE2); + InOrder inOrder = inOrder(mNMService, mStatsService); + inOrder.verify(mStatsService).forceUpdate(); + inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE); + inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE); + inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2); + inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2); + verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper); + assertFalse("Should not be tetherable when tethered", mTestedSm.isAvailable()); + assertTrue("Should be in a tethered state", mTestedSm.isTethered()); + assertFalse("Should have no errors when tethered", mTestedSm.isErrored()); + } + + @Test + public void canUnrequestTetheringWithUpstream() throws Exception { + initTetheredStateMachine(ConnectivityManager.TETHERING_BLUETOOTH, UPSTREAM_IFACE); + + dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED); + InOrder inOrder = inOrder(mNMService, mStatsService, mTetherHelper); + inOrder.verify(mTetherHelper).notifyInterfaceTetheringReadiness(false, mTestedSm); + inOrder.verify(mStatsService).forceUpdate(); + inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE); + inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE); + inOrder.verify(mNMService).untetherInterface(IFACE_NAME); + inOrder.verify(mTetherHelper).sendTetherStateChangedBroadcast(); + verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper); + assertTrue("Should be ready for tethering again", mTestedSm.isAvailable()); + assertFalse("Should not be tethered", mTestedSm.isTethered()); + assertFalse("Should have no errors", mTestedSm.isErrored()); + } + + @Test + public void interfaceDownLeadsToUnavailable() throws Exception { + for (boolean shouldThrow : new boolean[]{true, false}) { + initTetheredStateMachine(ConnectivityManager.TETHERING_USB, null); + + if (shouldThrow) { + doThrow(RemoteException.class).when(mNMService).untetherInterface(IFACE_NAME); + } + dispatchCommand(TetherInterfaceStateMachine.CMD_INTERFACE_DOWN); + InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration); + usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown(); + usbTeardownOrder.verify(mNMService).setInterfaceConfig( + IFACE_NAME, mInterfaceConfiguration); + verify(mTetherHelper).notifyInterfaceTetheringReadiness(false, mTestedSm); + assertFalse("Should not be available", mTestedSm.isAvailable()); + assertFalse("Should not be tethered", mTestedSm.isTethered()); + } + } + + @Test + public void usbShouldBeTornDownOnTetherError() throws Exception { + initStateMachine(ConnectivityManager.TETHERING_USB); + + doThrow(RemoteException.class).when(mNMService).tetherInterface(IFACE_NAME); + dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED); + InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration); + usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown(); + usbTeardownOrder.verify(mNMService).setInterfaceConfig( + IFACE_NAME, mInterfaceConfiguration); + // Initial call is when we transition to the tethered state on request. + verify(mTetherHelper).notifyInterfaceTetheringReadiness(true, mTestedSm); + // And this call is to notify that we really aren't requested tethering. + verify(mTetherHelper).notifyInterfaceTetheringReadiness(false, mTestedSm); + assertTrue("Expected to see an error reported", mTestedSm.isErrored()); + } + + @Test + public void shouldTearDownUsbOnUpstreamError() throws Exception { + initTetheredStateMachine(ConnectivityManager.TETHERING_USB, null); + + doThrow(RemoteException.class).when(mNMService).enableNat(anyString(), anyString()); + dispatchTetherConnectionChanged(UPSTREAM_IFACE); + InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration); + usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown(); + usbTeardownOrder.verify(mNMService).setInterfaceConfig(IFACE_NAME, mInterfaceConfiguration); + verify(mTetherHelper).notifyInterfaceTetheringReadiness(false, mTestedSm); + } + + /** + * Send a command to the state machine under test, and run the event loop to idle. + * + * @param command One of the TetherInterfaceStateMachine.CMD_* constants. + */ + private void dispatchCommand(int command) { + mTestedSm.sendMessage(command); + mLooper.dispatchAll(); + } + + /** + * Special override to tell the state machine that the upstream interface has changed. + * + * @see #dispatchCommand(int) + * @param upstreamIface String name of upstream interface (or null) + */ + private void dispatchTetherConnectionChanged(String upstreamIface) { + mTestedSm.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_CONNECTION_CHANGED, + upstreamIface); + mLooper.dispatchAll(); + } +} \ No newline at end of file