Loading services/core/java/com/android/server/connectivity/Tethering.java +180 −170 Original line number Diff line number Diff line Loading @@ -47,6 +47,7 @@ import android.hardware.usb.UsbManager; import android.net.ConnectivityManager; import android.net.INetworkPolicyManager; import android.net.INetworkStatsService; import android.net.IpPrefix; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; Loading Loading @@ -107,6 +108,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; Loading Loading @@ -213,7 +215,7 @@ public class Tethering extends BaseNetworkObserver { mContext.getContentResolver(), mLog); mUpstreamNetworkMonitor = new UpstreamNetworkMonitor( mContext, mTetherMasterSM, TetherMasterSM.EVENT_UPSTREAM_CALLBACK, mLog); mContext, mTetherMasterSM, mLog, TetherMasterSM.EVENT_UPSTREAM_CALLBACK ); mForwardedDownstreams = new HashSet<>(); mSimChange = new SimChangeListener( mContext, mTetherMasterSM.getHandler(), () -> reevaluateSimCardProvisioning()); Loading Loading @@ -1076,7 +1078,7 @@ public class Tethering extends BaseNetworkObserver { // Needed because the canonical source of upstream truth is just the // upstream interface name, |mCurrentUpstreamIface|. This is ripe for // future simplification, once the upstream Network is canonical. boolean pertainsToCurrentUpstream(NetworkState ns) { private boolean pertainsToCurrentUpstream(NetworkState ns) { if (ns != null && ns.linkProperties != null && mCurrentUpstreamIface != null) { for (String ifname : ns.linkProperties.getAllInterfaceNames()) { if (mCurrentUpstreamIface.equals(ifname)) { Loading Loading @@ -1110,6 +1112,12 @@ public class Tethering extends BaseNetworkObserver { } } private void startOffloadController() { mOffloadController.start(); mOffloadController.updateExemptPrefixes( mUpstreamNetworkMonitor.getOffloadExemptPrefixes()); } class TetherMasterSM extends StateMachine { private static final int BASE_MASTER = Protocol.BASE_TETHERING; // an interface SM has requested Tethering/Local Hotspot Loading Loading @@ -1203,12 +1211,6 @@ public class Tethering extends BaseNetworkObserver { } } class TetherMasterUtilState extends State { @Override public boolean processMessage(Message m) { return false; } protected boolean turnOnMasterTetherSettings() { final TetheringConfiguration cfg = mConfig; try { Loading Loading @@ -1338,9 +1340,7 @@ public class Tethering extends BaseNetworkObserver { protected void handleNewUpstreamNetworkState(NetworkState ns) { mIPv6TetheringCoordinator.updateUpstreamNetworkState(ns); mOffloadController.setUpstreamLinkProperties( (ns != null) ? ns.linkProperties : null); } mOffloadController.setUpstreamLinkProperties((ns != null) ? ns.linkProperties : null); } private void handleInterfaceServingStateActive(int mode, TetherInterfaceStateMachine who) { Loading Loading @@ -1389,7 +1389,61 @@ public class Tethering extends BaseNetworkObserver { } } class TetherModeAliveState extends TetherMasterUtilState { private void handleUpstreamNetworkMonitorCallback(int arg1, Object o) { if (arg1 == UpstreamNetworkMonitor.NOTIFY_EXEMPT_PREFIXES) { mOffloadController.updateExemptPrefixes((Set<IpPrefix>) o); return; } final NetworkState ns = (NetworkState) o; if (ns == null || !pertainsToCurrentUpstream(ns)) { // TODO: In future, this is where upstream evaluation and selection // could be handled for notifications which include sufficient data. // For example, after CONNECTIVITY_ACTION listening is removed, here // is where we could observe a Wi-Fi network becoming available and // passing validation. if (mCurrentUpstreamIface == null) { // If we have no upstream interface, try to run through upstream // selection again. If, for example, IPv4 connectivity has shown up // after IPv6 (e.g., 464xlat became available) we want the chance to // notice and act accordingly. chooseUpstreamType(false); } return; } switch (arg1) { case UpstreamNetworkMonitor.EVENT_ON_AVAILABLE: // The default network changed, or DUN connected // before this callback was processed. Updates // for the current NetworkCapabilities and // LinkProperties have been requested (default // request) or are being sent shortly (DUN). Do // nothing until they arrive; if no updates // arrive there's nothing to do. break; case UpstreamNetworkMonitor.EVENT_ON_CAPABILITIES: handleNewUpstreamNetworkState(ns); break; case UpstreamNetworkMonitor.EVENT_ON_LINKPROPERTIES: setDnsForwarders(ns.network, ns.linkProperties); handleNewUpstreamNetworkState(ns); break; case UpstreamNetworkMonitor.EVENT_ON_LOST: // TODO: Re-evaluate possible upstreams. Currently upstream // reevaluation is triggered via received CONNECTIVITY_ACTION // broadcasts that result in being passed a // TetherMasterSM.CMD_UPSTREAM_CHANGED. handleNewUpstreamNetworkState(null); break; default: mLog.e("Unknown arg1 value: " + arg1); break; } } class TetherModeAliveState extends State { boolean mUpstreamWanted = false; boolean mTryCell = true; Loading @@ -1407,7 +1461,7 @@ public class Tethering extends BaseNetworkObserver { // TODO: De-duplicate with updateUpstreamWanted() below. if (upstreamWanted()) { mUpstreamWanted = true; mOffloadController.start(); startOffloadController(); chooseUpstreamType(true); mTryCell = false; } Loading @@ -1427,7 +1481,7 @@ public class Tethering extends BaseNetworkObserver { mUpstreamWanted = upstreamWanted(); if (mUpstreamWanted != previousUpstreamWanted) { if (mUpstreamWanted) { mOffloadController.start(); startOffloadController(); } else { mOffloadController.stop(); } Loading Loading @@ -1507,52 +1561,8 @@ public class Tethering extends BaseNetworkObserver { break; case EVENT_UPSTREAM_CALLBACK: { updateUpstreamWanted(); if (!mUpstreamWanted) break; final NetworkState ns = (NetworkState) message.obj; if (ns == null || !pertainsToCurrentUpstream(ns)) { // TODO: In future, this is where upstream evaluation and selection // could be handled for notifications which include sufficient data. // For example, after CONNECTIVITY_ACTION listening is removed, here // is where we could observe a Wi-Fi network becoming available and // passing validation. if (mCurrentUpstreamIface == null) { // If we have no upstream interface, try to run through upstream // selection again. If, for example, IPv4 connectivity has shown up // after IPv6 (e.g., 464xlat became available) we want the chance to // notice and act accordingly. chooseUpstreamType(false); } break; } switch (message.arg1) { case UpstreamNetworkMonitor.EVENT_ON_AVAILABLE: // The default network changed, or DUN connected // before this callback was processed. Updates // for the current NetworkCapabilities and // LinkProperties have been requested (default // request) or are being sent shortly (DUN). Do // nothing until they arrive; if no updates // arrive there's nothing to do. break; case UpstreamNetworkMonitor.EVENT_ON_CAPABILITIES: handleNewUpstreamNetworkState(ns); break; case UpstreamNetworkMonitor.EVENT_ON_LINKPROPERTIES: setDnsForwarders(ns.network, ns.linkProperties); handleNewUpstreamNetworkState(ns); break; case UpstreamNetworkMonitor.EVENT_ON_LOST: // TODO: Re-evaluate possible upstreams. Currently upstream // reevaluation is triggered via received CONNECTIVITY_ACTION // broadcasts that result in being passed a // TetherMasterSM.CMD_UPSTREAM_CHANGED. handleNewUpstreamNetworkState(null); break; default: break; if (mUpstreamWanted) { handleUpstreamNetworkMonitorCallback(message.arg1, message.obj); } break; } Loading services/core/java/com/android/server/connectivity/tethering/OffloadController.java +14 −0 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.server.connectivity.tethering; import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED; import android.content.ContentResolver; import android.net.IpPrefix; import android.net.LinkProperties; import android.net.RouteInfo; import android.net.util.SharedLog; Loading @@ -28,6 +29,7 @@ import android.provider.Settings; import java.net.Inet4Address; import java.net.InetAddress; import java.util.ArrayList; import java.util.Set; /** * A class to encapsulate the business logic of programming the tethering Loading @@ -45,6 +47,7 @@ public class OffloadController { private boolean mConfigInitialized; private boolean mControlInitialized; private LinkProperties mUpstreamLinkProperties; private Set<IpPrefix> mExemptPrefixes; public OffloadController(Handler h, OffloadHardwareInterface hwi, ContentResolver contentResolver, SharedLog log) { Loading Loading @@ -108,6 +111,17 @@ public class OffloadController { pushUpstreamParameters(); } public void updateExemptPrefixes(Set<IpPrefix> exemptPrefixes) { if (!started()) return; mExemptPrefixes = exemptPrefixes; // TODO: // - add IP addresses from all downstream link properties // - add routes from all non-tethering downstream link properties // - remove any 64share prefixes // - push this to the HAL } public void notifyDownstreamLinkProperties(LinkProperties lp) { if (!started()) return; Loading services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java +68 −8 Original line number Diff line number Diff line Loading @@ -26,18 +26,24 @@ import android.os.Handler; import android.os.Looper; import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; import android.net.IpPrefix; import android.net.LinkAddress; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkRequest; import android.net.NetworkState; import android.net.util.NetworkConstants; import android.net.util.SharedLog; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.StateMachine; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Set; /** Loading Loading @@ -66,10 +72,16 @@ public class UpstreamNetworkMonitor { private static final boolean DBG = false; private static final boolean VDBG = false; private static final IpPrefix[] MINIMUM_LOCAL_PREFIXES_SET = { prefix("127.0.0.0/8"), prefix("169.254.0.0/16"), prefix("::/3"), prefix("fe80::/64"), prefix("fc00::/7"), prefix("ff00::/8"), }; public static final int EVENT_ON_AVAILABLE = 1; public static final int EVENT_ON_CAPABILITIES = 2; public static final int EVENT_ON_LINKPROPERTIES = 3; public static final int EVENT_ON_LOST = 4; public static final int NOTIFY_EXEMPT_PREFIXES = 10; private static final int CALLBACK_LISTEN_ALL = 1; private static final int CALLBACK_TRACK_DEFAULT = 2; Loading @@ -81,6 +93,7 @@ public class UpstreamNetworkMonitor { private final Handler mHandler; private final int mWhat; private final HashMap<Network, NetworkState> mNetworkMap = new HashMap<>(); private HashSet<IpPrefix> mOffloadExemptPrefixes; private ConnectivityManager mCM; private NetworkCallback mListenAllCallback; private NetworkCallback mDefaultNetworkCallback; Loading @@ -88,18 +101,19 @@ public class UpstreamNetworkMonitor { private boolean mDunRequired; private Network mCurrentDefault; public UpstreamNetworkMonitor(Context ctx, StateMachine tgt, int what, SharedLog log) { public UpstreamNetworkMonitor(Context ctx, StateMachine tgt, SharedLog log, int what) { mContext = ctx; mTarget = tgt; mHandler = mTarget.getHandler(); mWhat = what; mLog = log.forSubComponent(TAG); mWhat = what; mOffloadExemptPrefixes = allOffloadExemptPrefixes(mNetworkMap.values()); } @VisibleForTesting public UpstreamNetworkMonitor( StateMachine tgt, int what, ConnectivityManager cm, SharedLog log) { this(null, tgt, what, log); ConnectivityManager cm, StateMachine tgt, SharedLog log, int what) { this((Context) null, tgt, log, what); mCM = cm; } Loading Loading @@ -209,6 +223,10 @@ public class UpstreamNetworkMonitor { return typeStatePair.ns; } public Set<IpPrefix> getOffloadExemptPrefixes() { return (Set<IpPrefix>) mOffloadExemptPrefixes.clone(); } private void handleAvailable(int callbackType, Network network) { if (VDBG) Log.d(TAG, "EVENT_ON_AVAILABLE for " + network); Loading Loading @@ -342,6 +360,14 @@ public class UpstreamNetworkMonitor { notifyTarget(EVENT_ON_LOST, mNetworkMap.remove(network)); } private void recomputeOffloadExemptPrefixes() { final HashSet<IpPrefix> exemptPrefixes = allOffloadExemptPrefixes(mNetworkMap.values()); if (!mOffloadExemptPrefixes.equals(exemptPrefixes)) { mOffloadExemptPrefixes = exemptPrefixes; notifyTarget(NOTIFY_EXEMPT_PREFIXES, exemptPrefixes.clone()); } } // Fetch (and cache) a ConnectivityManager only if and when we need one. private ConnectivityManager cm() { if (mCM == null) { Loading Loading @@ -376,6 +402,7 @@ public class UpstreamNetworkMonitor { @Override public void onLinkPropertiesChanged(Network network, LinkProperties newLp) { handleLinkProp(network, newLp); recomputeOffloadExemptPrefixes(); } // TODO: Handle onNetworkSuspended(); Loading @@ -384,6 +411,7 @@ public class UpstreamNetworkMonitor { @Override public void onLost(Network network) { handleLost(mCallbackType, network); recomputeOffloadExemptPrefixes(); } } Loading @@ -395,16 +423,16 @@ public class UpstreamNetworkMonitor { notifyTarget(which, mNetworkMap.get(network)); } private void notifyTarget(int which, NetworkState netstate) { mTarget.sendMessage(mWhat, which, 0, netstate); private void notifyTarget(int which, Object obj) { mTarget.sendMessage(mWhat, which, 0, obj); } static private class TypeStatePair { private static class TypeStatePair { public int type = TYPE_NONE; public NetworkState ns = null; } static private TypeStatePair findFirstAvailableUpstreamByType( private static TypeStatePair findFirstAvailableUpstreamByType( Iterable<NetworkState> netStates, Iterable<Integer> preferredTypes) { final TypeStatePair result = new TypeStatePair(); Loading @@ -431,4 +459,36 @@ public class UpstreamNetworkMonitor { return result; } private static HashSet<IpPrefix> allOffloadExemptPrefixes(Iterable<NetworkState> netStates) { final HashSet<IpPrefix> prefixSet = new HashSet<>(); addDefaultLocalPrefixes(prefixSet); for (NetworkState ns : netStates) { addOffloadExemptPrefixes(prefixSet, ns.linkProperties); } return prefixSet; } private static void addDefaultLocalPrefixes(Set<IpPrefix> prefixSet) { Collections.addAll(prefixSet, MINIMUM_LOCAL_PREFIXES_SET); } private static void addOffloadExemptPrefixes(Set<IpPrefix> prefixSet, LinkProperties lp) { if (lp == null) return; for (LinkAddress linkAddr : lp.getAllLinkAddresses()) { prefixSet.add(new IpPrefix(linkAddr.getAddress(), linkAddr.getPrefixLength())); } // TODO: Consider adding other non-default routes associated with this // network. Traffic to these destinations should perhaps not go through // the Internet (upstream). } private static IpPrefix prefix(String prefixStr) { return new IpPrefix(prefixStr); } } tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java +125 −1 Original line number Diff line number Diff line Loading @@ -44,6 +44,9 @@ import android.os.Message; import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; import android.net.IConnectivityManager; import android.net.IpPrefix; import android.net.LinkAddress; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkRequest; Loading @@ -66,6 +69,7 @@ import org.mockito.MockitoAnnotations; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; Loading @@ -77,6 +81,9 @@ import java.util.Set; public class UpstreamNetworkMonitorTest { private static final int EVENT_UNM_UPDATE = 1; private static final boolean INCLUDES = true; private static final boolean EXCLUDES = false; @Mock private Context mContext; @Mock private IConnectivityManager mCS; @Mock private SharedLog mLog; Loading @@ -94,7 +101,8 @@ public class UpstreamNetworkMonitorTest { mCM = spy(new TestConnectivityManager(mContext, mCS)); mSM = new TestStateMachine(); mUNM = new UpstreamNetworkMonitor(mSM, EVENT_UNM_UPDATE, (ConnectivityManager) mCM, mLog); mUNM = new UpstreamNetworkMonitor( (ConnectivityManager) mCM, mSM, mLog, EVENT_UNM_UPDATE); } @After public void tearDown() throws Exception { Loading Loading @@ -315,6 +323,101 @@ public class UpstreamNetworkMonitorTest { assertTrue(netReq.networkCapabilities.hasCapability(NET_CAPABILITY_DUN)); } @Test public void testOffloadExemptPrefixes() throws Exception { mUNM.start(); // [0] Test minimum set of exempt prefixes. Set<IpPrefix> exempt = mUNM.getOffloadExemptPrefixes(); final String[] MINSET = { "127.0.0.0/8", "169.254.0.0/16", "::/3", "fe80::/64", "fc00::/7", "ff00::/8", }; assertPrefixSet(exempt, INCLUDES, MINSET); final Set<String> alreadySeen = new HashSet<>(); Collections.addAll(alreadySeen, MINSET); assertEquals(alreadySeen.size(), exempt.size()); // [1] Pretend Wi-Fi connects. final TestNetworkAgent wifiAgent = new TestNetworkAgent(mCM, TRANSPORT_WIFI); final LinkProperties wifiLp = new LinkProperties(); wifiLp.setInterfaceName("wlan0"); final String[] WIFI_ADDRS = { "fe80::827a:bfff:fe6f:374d", "100.112.103.18", "2001:db8:4:fd00:827a:bfff:fe6f:374d", "2001:db8:4:fd00:6dea:325a:fdae:4ef4", "fd6a:a640:60bf:e985::123", // ULA address for good measure. }; for (String addrStr : WIFI_ADDRS) { final String cidr = addrStr.contains(":") ? "/64" : "/20"; wifiLp.addLinkAddress(new LinkAddress(addrStr + cidr)); } wifiAgent.fakeConnect(); wifiAgent.sendLinkProperties(wifiLp); exempt = mUNM.getOffloadExemptPrefixes(); assertPrefixSet(exempt, INCLUDES, alreadySeen); final String[] wifiLinkPrefixes = { // Excludes link-local as that's already tested within MINSET. "100.112.96.0/20", "2001:db8:4:fd00::/64", "fd6a:a640:60bf:e985::/64", }; assertPrefixSet(exempt, INCLUDES, wifiLinkPrefixes); Collections.addAll(alreadySeen, wifiLinkPrefixes); assertEquals(alreadySeen.size(), exempt.size()); // [2] Pretend mobile connects. final TestNetworkAgent cellAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR); final LinkProperties cellLp = new LinkProperties(); cellLp.setInterfaceName("rmnet_data0"); final String[] CELL_ADDRS = { "10.102.211.48", "2001:db8:0:1:b50e:70d9:10c9:433d", }; for (String addrStr : CELL_ADDRS) { final String cidr = addrStr.contains(":") ? "/64" : "/27"; cellLp.addLinkAddress(new LinkAddress(addrStr + cidr)); } cellAgent.fakeConnect(); cellAgent.sendLinkProperties(cellLp); exempt = mUNM.getOffloadExemptPrefixes(); assertPrefixSet(exempt, INCLUDES, alreadySeen); final String[] cellLinkPrefixes = { "10.102.211.32/27", "2001:db8:0:1::/64" }; assertPrefixSet(exempt, INCLUDES, cellLinkPrefixes); Collections.addAll(alreadySeen, cellLinkPrefixes); assertEquals(alreadySeen.size(), exempt.size()); // [3] Pretend DUN connects. final TestNetworkAgent dunAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR); dunAgent.networkCapabilities.addCapability(NET_CAPABILITY_DUN); final LinkProperties dunLp = new LinkProperties(); dunLp.setInterfaceName("rmnet_data1"); final String[] DUN_ADDRS = { "192.0.2.48", "2001:db8:1:2:b50e:70d9:10c9:433d", }; for (String addrStr : DUN_ADDRS) { final String cidr = addrStr.contains(":") ? "/64" : "/27"; cellLp.addLinkAddress(new LinkAddress(addrStr + cidr)); } dunAgent.fakeConnect(); dunAgent.sendLinkProperties(dunLp); exempt = mUNM.getOffloadExemptPrefixes(); assertPrefixSet(exempt, INCLUDES, alreadySeen); final String[] dunLinkPrefixes = { "192.0.2.32/27", "2001:db8:1:2::/64" }; assertPrefixSet(exempt, INCLUDES, dunLinkPrefixes); Collections.addAll(alreadySeen, dunLinkPrefixes); assertEquals(alreadySeen.size(), exempt.size()); // [4] Pretend Wi-Fi disconnected. It's addresses/prefixes should no // longer be included (should be properly removed). wifiAgent.fakeDisconnect(); exempt = mUNM.getOffloadExemptPrefixes(); assertPrefixSet(exempt, INCLUDES, MINSET); assertPrefixSet(exempt, EXCLUDES, wifiLinkPrefixes); assertPrefixSet(exempt, INCLUDES, cellLinkPrefixes); assertPrefixSet(exempt, INCLUDES, dunLinkPrefixes); } private void assertSatisfiesLegacyType(int legacyType, NetworkState ns) { if (legacyType == TYPE_NONE) { assertTrue(ns == null); Loading Loading @@ -476,6 +579,12 @@ public class UpstreamNetworkMonitorTest { cb.onLost(networkId); } } public void sendLinkProperties(LinkProperties lp) { for (NetworkCallback cb : cm.listening.keySet()) { cb.onLinkPropertiesChanged(networkId, lp); } } } public static class TestStateMachine extends StateMachine { Loading Loading @@ -504,4 +613,19 @@ public class UpstreamNetworkMonitorTest { static NetworkCapabilities copy(NetworkCapabilities nc) { return new NetworkCapabilities(nc); } static void assertPrefixSet(Set<IpPrefix> prefixes, boolean expectation, String... expected) { final Set<String> expectedSet = new HashSet<>(); Collections.addAll(expectedSet, expected); assertPrefixSet(prefixes, expectation, expectedSet); } static void assertPrefixSet(Set<IpPrefix> prefixes, boolean expectation, Set<String> expected) { for (String expectedPrefix : expected) { final String errStr = expectation ? "did not find" : "found"; assertEquals( String.format("Failed expectation: %s prefix: %s", errStr, expectedPrefix), expectation, prefixes.contains(new IpPrefix(expectedPrefix))); } } } Loading
services/core/java/com/android/server/connectivity/Tethering.java +180 −170 Original line number Diff line number Diff line Loading @@ -47,6 +47,7 @@ import android.hardware.usb.UsbManager; import android.net.ConnectivityManager; import android.net.INetworkPolicyManager; import android.net.INetworkStatsService; import android.net.IpPrefix; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; Loading Loading @@ -107,6 +108,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; Loading Loading @@ -213,7 +215,7 @@ public class Tethering extends BaseNetworkObserver { mContext.getContentResolver(), mLog); mUpstreamNetworkMonitor = new UpstreamNetworkMonitor( mContext, mTetherMasterSM, TetherMasterSM.EVENT_UPSTREAM_CALLBACK, mLog); mContext, mTetherMasterSM, mLog, TetherMasterSM.EVENT_UPSTREAM_CALLBACK ); mForwardedDownstreams = new HashSet<>(); mSimChange = new SimChangeListener( mContext, mTetherMasterSM.getHandler(), () -> reevaluateSimCardProvisioning()); Loading Loading @@ -1076,7 +1078,7 @@ public class Tethering extends BaseNetworkObserver { // Needed because the canonical source of upstream truth is just the // upstream interface name, |mCurrentUpstreamIface|. This is ripe for // future simplification, once the upstream Network is canonical. boolean pertainsToCurrentUpstream(NetworkState ns) { private boolean pertainsToCurrentUpstream(NetworkState ns) { if (ns != null && ns.linkProperties != null && mCurrentUpstreamIface != null) { for (String ifname : ns.linkProperties.getAllInterfaceNames()) { if (mCurrentUpstreamIface.equals(ifname)) { Loading Loading @@ -1110,6 +1112,12 @@ public class Tethering extends BaseNetworkObserver { } } private void startOffloadController() { mOffloadController.start(); mOffloadController.updateExemptPrefixes( mUpstreamNetworkMonitor.getOffloadExemptPrefixes()); } class TetherMasterSM extends StateMachine { private static final int BASE_MASTER = Protocol.BASE_TETHERING; // an interface SM has requested Tethering/Local Hotspot Loading Loading @@ -1203,12 +1211,6 @@ public class Tethering extends BaseNetworkObserver { } } class TetherMasterUtilState extends State { @Override public boolean processMessage(Message m) { return false; } protected boolean turnOnMasterTetherSettings() { final TetheringConfiguration cfg = mConfig; try { Loading Loading @@ -1338,9 +1340,7 @@ public class Tethering extends BaseNetworkObserver { protected void handleNewUpstreamNetworkState(NetworkState ns) { mIPv6TetheringCoordinator.updateUpstreamNetworkState(ns); mOffloadController.setUpstreamLinkProperties( (ns != null) ? ns.linkProperties : null); } mOffloadController.setUpstreamLinkProperties((ns != null) ? ns.linkProperties : null); } private void handleInterfaceServingStateActive(int mode, TetherInterfaceStateMachine who) { Loading Loading @@ -1389,7 +1389,61 @@ public class Tethering extends BaseNetworkObserver { } } class TetherModeAliveState extends TetherMasterUtilState { private void handleUpstreamNetworkMonitorCallback(int arg1, Object o) { if (arg1 == UpstreamNetworkMonitor.NOTIFY_EXEMPT_PREFIXES) { mOffloadController.updateExemptPrefixes((Set<IpPrefix>) o); return; } final NetworkState ns = (NetworkState) o; if (ns == null || !pertainsToCurrentUpstream(ns)) { // TODO: In future, this is where upstream evaluation and selection // could be handled for notifications which include sufficient data. // For example, after CONNECTIVITY_ACTION listening is removed, here // is where we could observe a Wi-Fi network becoming available and // passing validation. if (mCurrentUpstreamIface == null) { // If we have no upstream interface, try to run through upstream // selection again. If, for example, IPv4 connectivity has shown up // after IPv6 (e.g., 464xlat became available) we want the chance to // notice and act accordingly. chooseUpstreamType(false); } return; } switch (arg1) { case UpstreamNetworkMonitor.EVENT_ON_AVAILABLE: // The default network changed, or DUN connected // before this callback was processed. Updates // for the current NetworkCapabilities and // LinkProperties have been requested (default // request) or are being sent shortly (DUN). Do // nothing until they arrive; if no updates // arrive there's nothing to do. break; case UpstreamNetworkMonitor.EVENT_ON_CAPABILITIES: handleNewUpstreamNetworkState(ns); break; case UpstreamNetworkMonitor.EVENT_ON_LINKPROPERTIES: setDnsForwarders(ns.network, ns.linkProperties); handleNewUpstreamNetworkState(ns); break; case UpstreamNetworkMonitor.EVENT_ON_LOST: // TODO: Re-evaluate possible upstreams. Currently upstream // reevaluation is triggered via received CONNECTIVITY_ACTION // broadcasts that result in being passed a // TetherMasterSM.CMD_UPSTREAM_CHANGED. handleNewUpstreamNetworkState(null); break; default: mLog.e("Unknown arg1 value: " + arg1); break; } } class TetherModeAliveState extends State { boolean mUpstreamWanted = false; boolean mTryCell = true; Loading @@ -1407,7 +1461,7 @@ public class Tethering extends BaseNetworkObserver { // TODO: De-duplicate with updateUpstreamWanted() below. if (upstreamWanted()) { mUpstreamWanted = true; mOffloadController.start(); startOffloadController(); chooseUpstreamType(true); mTryCell = false; } Loading @@ -1427,7 +1481,7 @@ public class Tethering extends BaseNetworkObserver { mUpstreamWanted = upstreamWanted(); if (mUpstreamWanted != previousUpstreamWanted) { if (mUpstreamWanted) { mOffloadController.start(); startOffloadController(); } else { mOffloadController.stop(); } Loading Loading @@ -1507,52 +1561,8 @@ public class Tethering extends BaseNetworkObserver { break; case EVENT_UPSTREAM_CALLBACK: { updateUpstreamWanted(); if (!mUpstreamWanted) break; final NetworkState ns = (NetworkState) message.obj; if (ns == null || !pertainsToCurrentUpstream(ns)) { // TODO: In future, this is where upstream evaluation and selection // could be handled for notifications which include sufficient data. // For example, after CONNECTIVITY_ACTION listening is removed, here // is where we could observe a Wi-Fi network becoming available and // passing validation. if (mCurrentUpstreamIface == null) { // If we have no upstream interface, try to run through upstream // selection again. If, for example, IPv4 connectivity has shown up // after IPv6 (e.g., 464xlat became available) we want the chance to // notice and act accordingly. chooseUpstreamType(false); } break; } switch (message.arg1) { case UpstreamNetworkMonitor.EVENT_ON_AVAILABLE: // The default network changed, or DUN connected // before this callback was processed. Updates // for the current NetworkCapabilities and // LinkProperties have been requested (default // request) or are being sent shortly (DUN). Do // nothing until they arrive; if no updates // arrive there's nothing to do. break; case UpstreamNetworkMonitor.EVENT_ON_CAPABILITIES: handleNewUpstreamNetworkState(ns); break; case UpstreamNetworkMonitor.EVENT_ON_LINKPROPERTIES: setDnsForwarders(ns.network, ns.linkProperties); handleNewUpstreamNetworkState(ns); break; case UpstreamNetworkMonitor.EVENT_ON_LOST: // TODO: Re-evaluate possible upstreams. Currently upstream // reevaluation is triggered via received CONNECTIVITY_ACTION // broadcasts that result in being passed a // TetherMasterSM.CMD_UPSTREAM_CHANGED. handleNewUpstreamNetworkState(null); break; default: break; if (mUpstreamWanted) { handleUpstreamNetworkMonitorCallback(message.arg1, message.obj); } break; } Loading
services/core/java/com/android/server/connectivity/tethering/OffloadController.java +14 −0 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.server.connectivity.tethering; import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED; import android.content.ContentResolver; import android.net.IpPrefix; import android.net.LinkProperties; import android.net.RouteInfo; import android.net.util.SharedLog; Loading @@ -28,6 +29,7 @@ import android.provider.Settings; import java.net.Inet4Address; import java.net.InetAddress; import java.util.ArrayList; import java.util.Set; /** * A class to encapsulate the business logic of programming the tethering Loading @@ -45,6 +47,7 @@ public class OffloadController { private boolean mConfigInitialized; private boolean mControlInitialized; private LinkProperties mUpstreamLinkProperties; private Set<IpPrefix> mExemptPrefixes; public OffloadController(Handler h, OffloadHardwareInterface hwi, ContentResolver contentResolver, SharedLog log) { Loading Loading @@ -108,6 +111,17 @@ public class OffloadController { pushUpstreamParameters(); } public void updateExemptPrefixes(Set<IpPrefix> exemptPrefixes) { if (!started()) return; mExemptPrefixes = exemptPrefixes; // TODO: // - add IP addresses from all downstream link properties // - add routes from all non-tethering downstream link properties // - remove any 64share prefixes // - push this to the HAL } public void notifyDownstreamLinkProperties(LinkProperties lp) { if (!started()) return; Loading
services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java +68 −8 Original line number Diff line number Diff line Loading @@ -26,18 +26,24 @@ import android.os.Handler; import android.os.Looper; import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; import android.net.IpPrefix; import android.net.LinkAddress; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkRequest; import android.net.NetworkState; import android.net.util.NetworkConstants; import android.net.util.SharedLog; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.StateMachine; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Set; /** Loading Loading @@ -66,10 +72,16 @@ public class UpstreamNetworkMonitor { private static final boolean DBG = false; private static final boolean VDBG = false; private static final IpPrefix[] MINIMUM_LOCAL_PREFIXES_SET = { prefix("127.0.0.0/8"), prefix("169.254.0.0/16"), prefix("::/3"), prefix("fe80::/64"), prefix("fc00::/7"), prefix("ff00::/8"), }; public static final int EVENT_ON_AVAILABLE = 1; public static final int EVENT_ON_CAPABILITIES = 2; public static final int EVENT_ON_LINKPROPERTIES = 3; public static final int EVENT_ON_LOST = 4; public static final int NOTIFY_EXEMPT_PREFIXES = 10; private static final int CALLBACK_LISTEN_ALL = 1; private static final int CALLBACK_TRACK_DEFAULT = 2; Loading @@ -81,6 +93,7 @@ public class UpstreamNetworkMonitor { private final Handler mHandler; private final int mWhat; private final HashMap<Network, NetworkState> mNetworkMap = new HashMap<>(); private HashSet<IpPrefix> mOffloadExemptPrefixes; private ConnectivityManager mCM; private NetworkCallback mListenAllCallback; private NetworkCallback mDefaultNetworkCallback; Loading @@ -88,18 +101,19 @@ public class UpstreamNetworkMonitor { private boolean mDunRequired; private Network mCurrentDefault; public UpstreamNetworkMonitor(Context ctx, StateMachine tgt, int what, SharedLog log) { public UpstreamNetworkMonitor(Context ctx, StateMachine tgt, SharedLog log, int what) { mContext = ctx; mTarget = tgt; mHandler = mTarget.getHandler(); mWhat = what; mLog = log.forSubComponent(TAG); mWhat = what; mOffloadExemptPrefixes = allOffloadExemptPrefixes(mNetworkMap.values()); } @VisibleForTesting public UpstreamNetworkMonitor( StateMachine tgt, int what, ConnectivityManager cm, SharedLog log) { this(null, tgt, what, log); ConnectivityManager cm, StateMachine tgt, SharedLog log, int what) { this((Context) null, tgt, log, what); mCM = cm; } Loading Loading @@ -209,6 +223,10 @@ public class UpstreamNetworkMonitor { return typeStatePair.ns; } public Set<IpPrefix> getOffloadExemptPrefixes() { return (Set<IpPrefix>) mOffloadExemptPrefixes.clone(); } private void handleAvailable(int callbackType, Network network) { if (VDBG) Log.d(TAG, "EVENT_ON_AVAILABLE for " + network); Loading Loading @@ -342,6 +360,14 @@ public class UpstreamNetworkMonitor { notifyTarget(EVENT_ON_LOST, mNetworkMap.remove(network)); } private void recomputeOffloadExemptPrefixes() { final HashSet<IpPrefix> exemptPrefixes = allOffloadExemptPrefixes(mNetworkMap.values()); if (!mOffloadExemptPrefixes.equals(exemptPrefixes)) { mOffloadExemptPrefixes = exemptPrefixes; notifyTarget(NOTIFY_EXEMPT_PREFIXES, exemptPrefixes.clone()); } } // Fetch (and cache) a ConnectivityManager only if and when we need one. private ConnectivityManager cm() { if (mCM == null) { Loading Loading @@ -376,6 +402,7 @@ public class UpstreamNetworkMonitor { @Override public void onLinkPropertiesChanged(Network network, LinkProperties newLp) { handleLinkProp(network, newLp); recomputeOffloadExemptPrefixes(); } // TODO: Handle onNetworkSuspended(); Loading @@ -384,6 +411,7 @@ public class UpstreamNetworkMonitor { @Override public void onLost(Network network) { handleLost(mCallbackType, network); recomputeOffloadExemptPrefixes(); } } Loading @@ -395,16 +423,16 @@ public class UpstreamNetworkMonitor { notifyTarget(which, mNetworkMap.get(network)); } private void notifyTarget(int which, NetworkState netstate) { mTarget.sendMessage(mWhat, which, 0, netstate); private void notifyTarget(int which, Object obj) { mTarget.sendMessage(mWhat, which, 0, obj); } static private class TypeStatePair { private static class TypeStatePair { public int type = TYPE_NONE; public NetworkState ns = null; } static private TypeStatePair findFirstAvailableUpstreamByType( private static TypeStatePair findFirstAvailableUpstreamByType( Iterable<NetworkState> netStates, Iterable<Integer> preferredTypes) { final TypeStatePair result = new TypeStatePair(); Loading @@ -431,4 +459,36 @@ public class UpstreamNetworkMonitor { return result; } private static HashSet<IpPrefix> allOffloadExemptPrefixes(Iterable<NetworkState> netStates) { final HashSet<IpPrefix> prefixSet = new HashSet<>(); addDefaultLocalPrefixes(prefixSet); for (NetworkState ns : netStates) { addOffloadExemptPrefixes(prefixSet, ns.linkProperties); } return prefixSet; } private static void addDefaultLocalPrefixes(Set<IpPrefix> prefixSet) { Collections.addAll(prefixSet, MINIMUM_LOCAL_PREFIXES_SET); } private static void addOffloadExemptPrefixes(Set<IpPrefix> prefixSet, LinkProperties lp) { if (lp == null) return; for (LinkAddress linkAddr : lp.getAllLinkAddresses()) { prefixSet.add(new IpPrefix(linkAddr.getAddress(), linkAddr.getPrefixLength())); } // TODO: Consider adding other non-default routes associated with this // network. Traffic to these destinations should perhaps not go through // the Internet (upstream). } private static IpPrefix prefix(String prefixStr) { return new IpPrefix(prefixStr); } }
tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java +125 −1 Original line number Diff line number Diff line Loading @@ -44,6 +44,9 @@ import android.os.Message; import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; import android.net.IConnectivityManager; import android.net.IpPrefix; import android.net.LinkAddress; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; import android.net.NetworkRequest; Loading @@ -66,6 +69,7 @@ import org.mockito.MockitoAnnotations; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; Loading @@ -77,6 +81,9 @@ import java.util.Set; public class UpstreamNetworkMonitorTest { private static final int EVENT_UNM_UPDATE = 1; private static final boolean INCLUDES = true; private static final boolean EXCLUDES = false; @Mock private Context mContext; @Mock private IConnectivityManager mCS; @Mock private SharedLog mLog; Loading @@ -94,7 +101,8 @@ public class UpstreamNetworkMonitorTest { mCM = spy(new TestConnectivityManager(mContext, mCS)); mSM = new TestStateMachine(); mUNM = new UpstreamNetworkMonitor(mSM, EVENT_UNM_UPDATE, (ConnectivityManager) mCM, mLog); mUNM = new UpstreamNetworkMonitor( (ConnectivityManager) mCM, mSM, mLog, EVENT_UNM_UPDATE); } @After public void tearDown() throws Exception { Loading Loading @@ -315,6 +323,101 @@ public class UpstreamNetworkMonitorTest { assertTrue(netReq.networkCapabilities.hasCapability(NET_CAPABILITY_DUN)); } @Test public void testOffloadExemptPrefixes() throws Exception { mUNM.start(); // [0] Test minimum set of exempt prefixes. Set<IpPrefix> exempt = mUNM.getOffloadExemptPrefixes(); final String[] MINSET = { "127.0.0.0/8", "169.254.0.0/16", "::/3", "fe80::/64", "fc00::/7", "ff00::/8", }; assertPrefixSet(exempt, INCLUDES, MINSET); final Set<String> alreadySeen = new HashSet<>(); Collections.addAll(alreadySeen, MINSET); assertEquals(alreadySeen.size(), exempt.size()); // [1] Pretend Wi-Fi connects. final TestNetworkAgent wifiAgent = new TestNetworkAgent(mCM, TRANSPORT_WIFI); final LinkProperties wifiLp = new LinkProperties(); wifiLp.setInterfaceName("wlan0"); final String[] WIFI_ADDRS = { "fe80::827a:bfff:fe6f:374d", "100.112.103.18", "2001:db8:4:fd00:827a:bfff:fe6f:374d", "2001:db8:4:fd00:6dea:325a:fdae:4ef4", "fd6a:a640:60bf:e985::123", // ULA address for good measure. }; for (String addrStr : WIFI_ADDRS) { final String cidr = addrStr.contains(":") ? "/64" : "/20"; wifiLp.addLinkAddress(new LinkAddress(addrStr + cidr)); } wifiAgent.fakeConnect(); wifiAgent.sendLinkProperties(wifiLp); exempt = mUNM.getOffloadExemptPrefixes(); assertPrefixSet(exempt, INCLUDES, alreadySeen); final String[] wifiLinkPrefixes = { // Excludes link-local as that's already tested within MINSET. "100.112.96.0/20", "2001:db8:4:fd00::/64", "fd6a:a640:60bf:e985::/64", }; assertPrefixSet(exempt, INCLUDES, wifiLinkPrefixes); Collections.addAll(alreadySeen, wifiLinkPrefixes); assertEquals(alreadySeen.size(), exempt.size()); // [2] Pretend mobile connects. final TestNetworkAgent cellAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR); final LinkProperties cellLp = new LinkProperties(); cellLp.setInterfaceName("rmnet_data0"); final String[] CELL_ADDRS = { "10.102.211.48", "2001:db8:0:1:b50e:70d9:10c9:433d", }; for (String addrStr : CELL_ADDRS) { final String cidr = addrStr.contains(":") ? "/64" : "/27"; cellLp.addLinkAddress(new LinkAddress(addrStr + cidr)); } cellAgent.fakeConnect(); cellAgent.sendLinkProperties(cellLp); exempt = mUNM.getOffloadExemptPrefixes(); assertPrefixSet(exempt, INCLUDES, alreadySeen); final String[] cellLinkPrefixes = { "10.102.211.32/27", "2001:db8:0:1::/64" }; assertPrefixSet(exempt, INCLUDES, cellLinkPrefixes); Collections.addAll(alreadySeen, cellLinkPrefixes); assertEquals(alreadySeen.size(), exempt.size()); // [3] Pretend DUN connects. final TestNetworkAgent dunAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR); dunAgent.networkCapabilities.addCapability(NET_CAPABILITY_DUN); final LinkProperties dunLp = new LinkProperties(); dunLp.setInterfaceName("rmnet_data1"); final String[] DUN_ADDRS = { "192.0.2.48", "2001:db8:1:2:b50e:70d9:10c9:433d", }; for (String addrStr : DUN_ADDRS) { final String cidr = addrStr.contains(":") ? "/64" : "/27"; cellLp.addLinkAddress(new LinkAddress(addrStr + cidr)); } dunAgent.fakeConnect(); dunAgent.sendLinkProperties(dunLp); exempt = mUNM.getOffloadExemptPrefixes(); assertPrefixSet(exempt, INCLUDES, alreadySeen); final String[] dunLinkPrefixes = { "192.0.2.32/27", "2001:db8:1:2::/64" }; assertPrefixSet(exempt, INCLUDES, dunLinkPrefixes); Collections.addAll(alreadySeen, dunLinkPrefixes); assertEquals(alreadySeen.size(), exempt.size()); // [4] Pretend Wi-Fi disconnected. It's addresses/prefixes should no // longer be included (should be properly removed). wifiAgent.fakeDisconnect(); exempt = mUNM.getOffloadExemptPrefixes(); assertPrefixSet(exempt, INCLUDES, MINSET); assertPrefixSet(exempt, EXCLUDES, wifiLinkPrefixes); assertPrefixSet(exempt, INCLUDES, cellLinkPrefixes); assertPrefixSet(exempt, INCLUDES, dunLinkPrefixes); } private void assertSatisfiesLegacyType(int legacyType, NetworkState ns) { if (legacyType == TYPE_NONE) { assertTrue(ns == null); Loading Loading @@ -476,6 +579,12 @@ public class UpstreamNetworkMonitorTest { cb.onLost(networkId); } } public void sendLinkProperties(LinkProperties lp) { for (NetworkCallback cb : cm.listening.keySet()) { cb.onLinkPropertiesChanged(networkId, lp); } } } public static class TestStateMachine extends StateMachine { Loading Loading @@ -504,4 +613,19 @@ public class UpstreamNetworkMonitorTest { static NetworkCapabilities copy(NetworkCapabilities nc) { return new NetworkCapabilities(nc); } static void assertPrefixSet(Set<IpPrefix> prefixes, boolean expectation, String... expected) { final Set<String> expectedSet = new HashSet<>(); Collections.addAll(expectedSet, expected); assertPrefixSet(prefixes, expectation, expectedSet); } static void assertPrefixSet(Set<IpPrefix> prefixes, boolean expectation, Set<String> expected) { for (String expectedPrefix : expected) { final String errStr = expectation ? "did not find" : "found"; assertEquals( String.format("Failed expectation: %s prefix: %s", errStr, expectedPrefix), expectation, prefixes.contains(new IpPrefix(expectedPrefix))); } } }