Merge "Make interface IP serving code set LinkProperties" am: 1dae9a4709

am: 9461f78541

Change-Id: I753ab701f2109c0972816724e73119dc1dbc4dca
This commit is contained in:
Erik Kline
2017-06-19 13:26:13 +00:00
committed by android-build-merger
6 changed files with 141 additions and 46 deletions

View File

@@ -1268,10 +1268,10 @@ public class Tethering extends BaseNetworkObserver {
sendMessageDelayed(CMD_RETRY_UPSTREAM, UPSTREAM_SETTLE_TIME_MS); sendMessageDelayed(CMD_RETRY_UPSTREAM, UPSTREAM_SETTLE_TIME_MS);
} }
} }
setUpstreamByType(ns); setUpstreamNetwork(ns);
} }
protected void setUpstreamByType(NetworkState ns) { protected void setUpstreamNetwork(NetworkState ns) {
String iface = null; String iface = null;
if (ns != null && ns.linkProperties != null) { if (ns != null && ns.linkProperties != null) {
// Find the interface with the default IPv4 route. It may be the // Find the interface with the default IPv4 route. It may be the
@@ -1790,7 +1790,9 @@ public class Tethering extends BaseNetworkObserver {
} }
} }
mLog.log(String.format("OBSERVED LinkProperties update iface=%s state=%s", iface, state)); mLog.log(String.format(
"OBSERVED LinkProperties update iface=%s state=%s lp=%s",
iface, IControlsTethering.getStateString(state), newLp));
final int which = TetherMasterSM.EVENT_IFACE_UPDATE_LINKPROPERTIES; final int which = TetherMasterSM.EVENT_IFACE_UPDATE_LINKPROPERTIES;
mTetherMasterSM.sendMessage(which, state, 0, newLp); mTetherMasterSM.sendMessage(which, state, 0, newLp);
} }

View File

@@ -33,6 +33,16 @@ public class IControlsTethering {
public static final int STATE_TETHERED = 2; public static final int STATE_TETHERED = 2;
public static final int STATE_LOCAL_ONLY = 3; public static final int STATE_LOCAL_ONLY = 3;
public static String getStateString(int state) {
switch (state) {
case STATE_UNAVAILABLE: return "UNAVAILABLE";
case STATE_AVAILABLE: return "AVAILABLE";
case STATE_TETHERED: return "TETHERED";
case STATE_LOCAL_ONLY: return "LOCAL_ONLY";
}
return "UNKNOWN: " + state;
}
/** /**
* Notify that |who| has changed its tethering state. * Notify that |who| has changed its tethering state.
* *

View File

@@ -85,13 +85,16 @@ public class OffloadController {
mLog.i("tethering offload control not supported"); mLog.i("tethering offload control not supported");
stop(); stop();
} }
mLog.log("tethering offload started");
} }
public void stop() { public void stop() {
final boolean wasStarted = started();
mUpstreamLinkProperties = null; mUpstreamLinkProperties = null;
mHwInterface.stopOffloadControl(); mHwInterface.stopOffloadControl();
mControlInitialized = false; mControlInitialized = false;
mConfigInitialized = false; mConfigInitialized = false;
if (wasStarted) mLog.log("tethering offload stopped");
} }
public void setUpstreamLinkProperties(LinkProperties lp) { public void setUpstreamLinkProperties(LinkProperties lp) {

View File

@@ -56,9 +56,10 @@ import java.util.Objects;
import java.util.Random; import java.util.Random;
/** /**
* @hide * Provides the interface to IP-layer serving functionality for a given network
* interface, e.g. for tethering or "local-only hotspot" mode.
* *
* Tracks the eligibility of a given network interface for tethering. * @hide
*/ */
public class TetherInterfaceStateMachine extends StateMachine { public class TetherInterfaceStateMachine extends StateMachine {
private static final IpPrefix LINK_LOCAL_PREFIX = new IpPrefix("fe80::/64"); private static final IpPrefix LINK_LOCAL_PREFIX = new IpPrefix("fe80::/64");
@@ -117,6 +118,12 @@ public class TetherInterfaceStateMachine extends StateMachine {
private String mMyUpstreamIfaceName; // may change over time private String mMyUpstreamIfaceName; // may change over time
private NetworkInterface mNetworkInterface; private NetworkInterface mNetworkInterface;
private byte[] mHwAddr; private byte[] mHwAddr;
// TODO: De-duplicate this with mLinkProperties above. Currently, these link
// properties are those selected by the IPv6TetheringCoordinator and relayed
// to us. By comparison, mLinkProperties contains the addresses and directly
// connected routes that have been formed from these properties iff. we have
// succeeded in configuring them and are able to announce them within Router
// Advertisements (otherwise, we do not add them to mLinkProperties at all).
private LinkProperties mLastIPv6LinkProperties; private LinkProperties mLastIPv6LinkProperties;
private RouterAdvertisementDaemon mRaDaemon; private RouterAdvertisementDaemon mRaDaemon;
private RaParams mLastRaParams; private RaParams mLastRaParams;
@@ -133,7 +140,7 @@ public class TetherInterfaceStateMachine extends StateMachine {
mIfaceName = ifaceName; mIfaceName = ifaceName;
mInterfaceType = interfaceType; mInterfaceType = interfaceType;
mLinkProperties = new LinkProperties(); mLinkProperties = new LinkProperties();
mLinkProperties.setInterfaceName(mIfaceName); resetLinkProperties();
mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR; mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
mInitialState = new InitialState(); mInitialState = new InitialState();
@@ -162,10 +169,15 @@ public class TetherInterfaceStateMachine extends StateMachine {
* Internals. * Internals.
*/ */
// configured when we start tethering and unconfig'd on error or conclusion private boolean startIPv4() { return configureIPv4(true); }
private boolean configureIfaceIp(boolean enabled) {
if (VDBG) Log.d(TAG, "configureIfaceIp(" + enabled + ")");
private void stopIPv4() { configureIPv4(false); }
private boolean configureIPv4(boolean enabled) {
if (VDBG) Log.d(TAG, "configureIPv4(" + enabled + ")");
// TODO: Replace this hard-coded information with dynamically selected
// config passed down to us by a higher layer IP-coordinating element.
String ipAsString = null; String ipAsString = null;
int prefixLen = 0; int prefixLen = 0;
if (mInterfaceType == ConnectivityManager.TETHERING_USB) { if (mInterfaceType == ConnectivityManager.TETHERING_USB) {
@@ -179,32 +191,45 @@ public class TetherInterfaceStateMachine extends StateMachine {
return true; return true;
} }
InterfaceConfiguration ifcg = null; final LinkAddress linkAddr;
try { try {
ifcg = mNMService.getInterfaceConfig(mIfaceName); final InterfaceConfiguration ifcg = mNMService.getInterfaceConfig(mIfaceName);
if (ifcg != null) { if (ifcg == null) {
InetAddress addr = NetworkUtils.numericToInetAddress(ipAsString); mLog.e("Received null interface config");
ifcg.setLinkAddress(new LinkAddress(addr, prefixLen)); return false;
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);
} }
InetAddress addr = NetworkUtils.numericToInetAddress(ipAsString);
linkAddr = new LinkAddress(addr, prefixLen);
ifcg.setLinkAddress(linkAddr);
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) { } catch (Exception e) {
mLog.e("Error configuring interface " + e); mLog.e("Error configuring interface " + e);
return false; return false;
} }
// Directly-connected route.
final RouteInfo route = new RouteInfo(linkAddr);
if (enabled) {
mLinkProperties.addLinkAddress(linkAddr);
mLinkProperties.addRoute(route);
} else {
mLinkProperties.removeLinkAddress(linkAddr);
mLinkProperties.removeRoute(route);
}
return true; return true;
} }
@@ -294,7 +319,7 @@ public class TetherInterfaceStateMachine extends StateMachine {
mLastIPv6LinkProperties = v6only; mLastIPv6LinkProperties = v6only;
} }
private void configureLocalRoutes( private void configureLocalIPv6Routes(
HashSet<IpPrefix> deprecatedPrefixes, HashSet<IpPrefix> newPrefixes) { HashSet<IpPrefix> deprecatedPrefixes, HashSet<IpPrefix> newPrefixes) {
// [1] Remove the routes that are deprecated. // [1] Remove the routes that are deprecated.
if (!deprecatedPrefixes.isEmpty()) { if (!deprecatedPrefixes.isEmpty()) {
@@ -309,6 +334,8 @@ public class TetherInterfaceStateMachine extends StateMachine {
} catch (RemoteException e) { } catch (RemoteException e) {
mLog.e("Failed to remove IPv6 routes from local table: " + e); mLog.e("Failed to remove IPv6 routes from local table: " + e);
} }
for (RouteInfo route : toBeRemoved) mLinkProperties.removeRoute(route);
} }
// [2] Add only the routes that have not previously been added. // [2] Add only the routes that have not previously been added.
@@ -340,11 +367,13 @@ public class TetherInterfaceStateMachine extends StateMachine {
} catch (RemoteException e) { } catch (RemoteException e) {
mLog.e("Failed to add IPv6 routes to local table: " + e); mLog.e("Failed to add IPv6 routes to local table: " + e);
} }
for (RouteInfo route : toBeAdded) mLinkProperties.addRoute(route);
} }
} }
} }
private void configureLocalDns( private void configureLocalIPv6Dns(
HashSet<Inet6Address> deprecatedDnses, HashSet<Inet6Address> newDnses) { HashSet<Inet6Address> deprecatedDnses, HashSet<Inet6Address> newDnses) {
final INetd netd = NetdService.getInstance(); final INetd netd = NetdService.getInstance();
if (netd == null) { if (netd == null) {
@@ -362,6 +391,8 @@ public class TetherInterfaceStateMachine extends StateMachine {
} catch (ServiceSpecificException | RemoteException e) { } catch (ServiceSpecificException | RemoteException e) {
mLog.e("Failed to remove local dns IP " + dnsString + ": " + e); mLog.e("Failed to remove local dns IP " + dnsString + ": " + e);
} }
mLinkProperties.removeLinkAddress(new LinkAddress(dns, RFC7421_PREFIX_LENGTH));
} }
} }
@@ -380,6 +411,8 @@ public class TetherInterfaceStateMachine extends StateMachine {
mLog.e("Failed to add local dns IP " + dnsString + ": " + e); mLog.e("Failed to add local dns IP " + dnsString + ": " + e);
newDnses.remove(dns); newDnses.remove(dns);
} }
mLinkProperties.addLinkAddress(new LinkAddress(dns, RFC7421_PREFIX_LENGTH));
} }
} }
@@ -396,10 +429,10 @@ public class TetherInterfaceStateMachine extends StateMachine {
final RaParams deprecatedParams = final RaParams deprecatedParams =
RaParams.getDeprecatedRaParams(mLastRaParams, newParams); RaParams.getDeprecatedRaParams(mLastRaParams, newParams);
configureLocalRoutes(deprecatedParams.prefixes, configureLocalIPv6Routes(deprecatedParams.prefixes,
(newParams != null) ? newParams.prefixes : null); (newParams != null) ? newParams.prefixes : null);
configureLocalDns(deprecatedParams.dnses, configureLocalIPv6Dns(deprecatedParams.dnses,
(newParams != null) ? newParams.dnses : null); (newParams != null) ? newParams.dnses : null);
mRaDaemon.buildNewRa(deprecatedParams, newParams); mRaDaemon.buildNewRa(deprecatedParams, newParams);
@@ -419,12 +452,19 @@ public class TetherInterfaceStateMachine extends StateMachine {
private void sendInterfaceState(int newInterfaceState) { private void sendInterfaceState(int newInterfaceState) {
mTetherController.updateInterfaceState( mTetherController.updateInterfaceState(
TetherInterfaceStateMachine.this, newInterfaceState, mLastError); TetherInterfaceStateMachine.this, newInterfaceState, mLastError);
// TODO: Populate mLinkProperties correctly, and send more sensible sendLinkProperties();
// updates more frequently (not just here). }
private void sendLinkProperties() {
mTetherController.updateLinkProperties( mTetherController.updateLinkProperties(
TetherInterfaceStateMachine.this, new LinkProperties(mLinkProperties)); TetherInterfaceStateMachine.this, new LinkProperties(mLinkProperties));
} }
private void resetLinkProperties() {
mLinkProperties.clear();
mLinkProperties.setInterfaceName(mIfaceName);
}
class InitialState extends State { class InitialState extends State {
@Override @Override
public void enter() { public void enter() {
@@ -464,7 +504,7 @@ public class TetherInterfaceStateMachine extends StateMachine {
class BaseServingState extends State { class BaseServingState extends State {
@Override @Override
public void enter() { public void enter() {
if (!configureIfaceIp(true)) { if (!startIPv4()) {
mLastError = ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR; mLastError = ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR;
return; return;
} }
@@ -498,7 +538,9 @@ public class TetherInterfaceStateMachine extends StateMachine {
mLog.e("Failed to untether interface: " + e); mLog.e("Failed to untether interface: " + e);
} }
configureIfaceIp(false); stopIPv4();
resetLinkProperties();
} }
@Override @Override
@@ -515,6 +557,7 @@ public class TetherInterfaceStateMachine extends StateMachine {
break; break;
case CMD_IPV6_TETHER_UPDATE: case CMD_IPV6_TETHER_UPDATE:
updateUpstreamIPv6LinkProperties((LinkProperties) message.obj); updateUpstreamIPv6LinkProperties((LinkProperties) message.obj);
sendLinkProperties();
break; break;
case CMD_IP_FORWARDING_ENABLE_ERROR: case CMD_IP_FORWARDING_ENABLE_ERROR:
case CMD_IP_FORWARDING_DISABLE_ERROR: case CMD_IP_FORWARDING_DISABLE_ERROR:
@@ -625,7 +668,6 @@ public class TetherInterfaceStateMachine extends StateMachine {
if (super.processMessage(message)) return true; if (super.processMessage(message)) return true;
maybeLogMessage(this, message.what); maybeLogMessage(this, message.what);
boolean retValue = true;
switch (message.what) { switch (message.what) {
case CMD_TETHER_REQUESTED: case CMD_TETHER_REQUESTED:
mLog.e("CMD_TETHER_REQUESTED while already tethering."); mLog.e("CMD_TETHER_REQUESTED while already tethering.");
@@ -655,10 +697,9 @@ public class TetherInterfaceStateMachine extends StateMachine {
mMyUpstreamIfaceName = newUpstreamIfaceName; mMyUpstreamIfaceName = newUpstreamIfaceName;
break; break;
default: default:
retValue = false; return false;
break;
} }
return retValue; return true;
} }
} }

View File

@@ -62,7 +62,8 @@ public class OffloadControllerTest {
@Mock private OffloadHardwareInterface mHardware; @Mock private OffloadHardwareInterface mHardware;
@Mock private ApplicationInfo mApplicationInfo; @Mock private ApplicationInfo mApplicationInfo;
@Mock private Context mContext; @Mock private Context mContext;
final ArgumentCaptor<ArrayList> mStringArrayCaptor = ArgumentCaptor.forClass(ArrayList.class); private final ArgumentCaptor<ArrayList> mStringArrayCaptor =
ArgumentCaptor.forClass(ArrayList.class);
private MockContentResolver mContentResolver; private MockContentResolver mContentResolver;
@Before public void setUp() throws Exception { @Before public void setUp() throws Exception {

View File

@@ -16,6 +16,8 @@
package com.android.server.connectivity.tethering; package com.android.server.connectivity.tethering;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any; import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq; import static org.mockito.Matchers.eq;
@@ -40,17 +42,23 @@ import static com.android.server.connectivity.tethering.IControlsTethering.STATE
import android.net.ConnectivityManager; import android.net.ConnectivityManager;
import android.net.INetworkStatsService; import android.net.INetworkStatsService;
import android.net.InterfaceConfiguration; import android.net.InterfaceConfiguration;
import android.net.LinkAddress;
import android.net.LinkProperties; import android.net.LinkProperties;
import android.net.RouteInfo;
import android.net.util.SharedLog; import android.net.util.SharedLog;
import android.os.INetworkManagementService; import android.os.INetworkManagementService;
import android.os.RemoteException; import android.os.RemoteException;
import android.os.test.TestLooper; import android.os.test.TestLooper;
import android.support.test.filters.SmallTest; import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4; import android.support.test.runner.AndroidJUnit4;
import android.text.TextUtils;
import java.net.Inet4Address;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InOrder; import org.mockito.InOrder;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
@@ -69,6 +77,8 @@ public class TetherInterfaceStateMachineTest {
@Mock private SharedLog mSharedLog; @Mock private SharedLog mSharedLog;
private final TestLooper mLooper = new TestLooper(); private final TestLooper mLooper = new TestLooper();
private final ArgumentCaptor<LinkProperties> mLinkPropertiesCaptor =
ArgumentCaptor.forClass(LinkProperties.class);
private TetherInterfaceStateMachine mTestedSm; private TetherInterfaceStateMachine mTestedSm;
private void initStateMachine(int interfaceType) throws Exception { private void initStateMachine(int interfaceType) throws Exception {
@@ -77,7 +87,7 @@ public class TetherInterfaceStateMachineTest {
mNMService, mStatsService, mTetherHelper); mNMService, mStatsService, mTetherHelper);
mTestedSm.start(); mTestedSm.start();
// Starting the state machine always puts us in a consistent state and notifies // 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. // the rest of the world that we've changed from an unknown to available state.
mLooper.dispatchAll(); mLooper.dispatchAll();
reset(mNMService, mStatsService, mTetherHelper); reset(mNMService, mStatsService, mTetherHelper);
when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration); when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration);
@@ -181,7 +191,8 @@ public class TetherInterfaceStateMachineTest {
inOrder.verify(mTetherHelper).updateInterfaceState( inOrder.verify(mTetherHelper).updateInterfaceState(
mTestedSm, STATE_TETHERED, TETHER_ERROR_NO_ERROR); mTestedSm, STATE_TETHERED, TETHER_ERROR_NO_ERROR);
inOrder.verify(mTetherHelper).updateLinkProperties( inOrder.verify(mTetherHelper).updateLinkProperties(
eq(mTestedSm), any(LinkProperties.class)); eq(mTestedSm), mLinkPropertiesCaptor.capture());
assertIPv4AddressAndDirectlyConnectedRoute(mLinkPropertiesCaptor.getValue());
verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper); verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
} }
@@ -281,7 +292,8 @@ public class TetherInterfaceStateMachineTest {
usbTeardownOrder.verify(mTetherHelper).updateInterfaceState( usbTeardownOrder.verify(mTetherHelper).updateInterfaceState(
mTestedSm, STATE_UNAVAILABLE, TETHER_ERROR_NO_ERROR); mTestedSm, STATE_UNAVAILABLE, TETHER_ERROR_NO_ERROR);
usbTeardownOrder.verify(mTetherHelper).updateLinkProperties( usbTeardownOrder.verify(mTetherHelper).updateLinkProperties(
eq(mTestedSm), any(LinkProperties.class)); eq(mTestedSm), mLinkPropertiesCaptor.capture());
assertNoAddressesNorRoutes(mLinkPropertiesCaptor.getValue());
} }
} }
@@ -298,7 +310,8 @@ public class TetherInterfaceStateMachineTest {
usbTeardownOrder.verify(mTetherHelper).updateInterfaceState( usbTeardownOrder.verify(mTetherHelper).updateInterfaceState(
mTestedSm, STATE_AVAILABLE, TETHER_ERROR_TETHER_IFACE_ERROR); mTestedSm, STATE_AVAILABLE, TETHER_ERROR_TETHER_IFACE_ERROR);
usbTeardownOrder.verify(mTetherHelper).updateLinkProperties( usbTeardownOrder.verify(mTetherHelper).updateLinkProperties(
eq(mTestedSm), any(LinkProperties.class)); eq(mTestedSm), mLinkPropertiesCaptor.capture());
assertNoAddressesNorRoutes(mLinkPropertiesCaptor.getValue());
} }
@Test @Test
@@ -313,7 +326,8 @@ public class TetherInterfaceStateMachineTest {
usbTeardownOrder.verify(mTetherHelper).updateInterfaceState( usbTeardownOrder.verify(mTetherHelper).updateInterfaceState(
mTestedSm, STATE_AVAILABLE, TETHER_ERROR_ENABLE_NAT_ERROR); mTestedSm, STATE_AVAILABLE, TETHER_ERROR_ENABLE_NAT_ERROR);
usbTeardownOrder.verify(mTetherHelper).updateLinkProperties( usbTeardownOrder.verify(mTetherHelper).updateLinkProperties(
eq(mTestedSm), any(LinkProperties.class)); eq(mTestedSm), mLinkPropertiesCaptor.capture());
assertNoAddressesNorRoutes(mLinkPropertiesCaptor.getValue());
} }
@Test @Test
@@ -360,4 +374,28 @@ public class TetherInterfaceStateMachineTest {
upstreamIface); upstreamIface);
mLooper.dispatchAll(); mLooper.dispatchAll();
} }
private void assertIPv4AddressAndDirectlyConnectedRoute(LinkProperties lp) {
// Find the first IPv4 LinkAddress.
LinkAddress addr4 = null;
for (LinkAddress addr : lp.getLinkAddresses()) {
if (!(addr.getAddress() instanceof Inet4Address)) continue;
addr4 = addr;
break;
}
assertTrue("missing IPv4 address", addr4 != null);
// Assert the presence of the associated directly connected route.
final RouteInfo directlyConnected = new RouteInfo(addr4, null, lp.getInterfaceName());
assertTrue("missing directly connected route: '" + directlyConnected.toString() + "'",
lp.getRoutes().contains(directlyConnected));
}
private void assertNoAddressesNorRoutes(LinkProperties lp) {
assertTrue(lp.getLinkAddresses().isEmpty());
assertTrue(lp.getRoutes().isEmpty());
// We also check that interface name is non-empty, because we should
// never see an empty interface name in any LinkProperties update.
assertFalse(TextUtils.isEmpty(lp.getInterfaceName()));
}
} }