diff options
| author | 2017-07-04 05:58:46 +0000 | |
|---|---|---|
| committer | 2017-07-04 05:58:46 +0000 | |
| commit | 1d8d34fa3eff2c6f9ed23909e1688af6cc4560d0 (patch) | |
| tree | e5fc18574c9a007ac523a74efaffac588d2d285f | |
| parent | b2b73e4956fa2ffebc80893b68f37989a631eca1 (diff) | |
| parent | 3a5278f2c4d71a8f69ce1085da42904283d62785 (diff) | |
Merge "Pass all offload-exempt prefixes into OffloadController" into oc-dr1-dev
4 files changed, 387 insertions, 179 deletions
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java index e5fc4b1d1775..e2956ddfd432 100644 --- a/services/core/java/com/android/server/connectivity/Tethering.java +++ b/services/core/java/com/android/server/connectivity/Tethering.java @@ -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; @@ -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; @@ -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()); @@ -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)) { @@ -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 @@ -1203,146 +1211,138 @@ public class Tethering extends BaseNetworkObserver { } } - class TetherMasterUtilState extends State { - @Override - public boolean processMessage(Message m) { + protected boolean turnOnMasterTetherSettings() { + final TetheringConfiguration cfg = mConfig; + try { + mNMService.setIpForwardingEnabled(true); + } catch (Exception e) { + mLog.e(e); + transitionTo(mSetIpForwardingEnabledErrorState); return false; } - - protected boolean turnOnMasterTetherSettings() { - final TetheringConfiguration cfg = mConfig; - try { - mNMService.setIpForwardingEnabled(true); - } catch (Exception e) { - mLog.e(e); - transitionTo(mSetIpForwardingEnabledErrorState); - return false; - } - // TODO: Randomize DHCPv4 ranges, especially in hotspot mode. - try { - // TODO: Find a more accurate method name (startDHCPv4()?). - mNMService.startTethering(cfg.dhcpRanges); - } catch (Exception e) { - try { - mNMService.stopTethering(); - mNMService.startTethering(cfg.dhcpRanges); - } catch (Exception ee) { - mLog.e(ee); - transitionTo(mStartTetheringErrorState); - return false; - } - } - mLog.log("SET master tether settings: ON"); - return true; - } - - protected boolean turnOffMasterTetherSettings() { + // TODO: Randomize DHCPv4 ranges, especially in hotspot mode. + try { + // TODO: Find a more accurate method name (startDHCPv4()?). + mNMService.startTethering(cfg.dhcpRanges); + } catch (Exception e) { try { mNMService.stopTethering(); - } catch (Exception e) { - mLog.e(e); - transitionTo(mStopTetheringErrorState); - return false; - } - try { - mNMService.setIpForwardingEnabled(false); - } catch (Exception e) { - mLog.e(e); - transitionTo(mSetIpForwardingDisabledErrorState); + mNMService.startTethering(cfg.dhcpRanges); + } catch (Exception ee) { + mLog.e(ee); + transitionTo(mStartTetheringErrorState); return false; } - transitionTo(mInitialState); - mLog.log("SET master tether settings: OFF"); - return true; } + mLog.log("SET master tether settings: ON"); + return true; + } - protected void chooseUpstreamType(boolean tryCell) { - updateConfiguration(); // TODO - remove? + protected boolean turnOffMasterTetherSettings() { + try { + mNMService.stopTethering(); + } catch (Exception e) { + mLog.e(e); + transitionTo(mStopTetheringErrorState); + return false; + } + try { + mNMService.setIpForwardingEnabled(false); + } catch (Exception e) { + mLog.e(e); + transitionTo(mSetIpForwardingDisabledErrorState); + return false; + } + transitionTo(mInitialState); + mLog.log("SET master tether settings: OFF"); + return true; + } - final NetworkState ns = mUpstreamNetworkMonitor.selectPreferredUpstreamType( - mConfig.preferredUpstreamIfaceTypes); - if (ns == null) { - if (tryCell) { - mUpstreamNetworkMonitor.registerMobileNetworkRequest(); - // We think mobile should be coming up; don't set a retry. - } else { - sendMessageDelayed(CMD_RETRY_UPSTREAM, UPSTREAM_SETTLE_TIME_MS); - } - } - setUpstreamNetwork(ns); - } - - protected void setUpstreamNetwork(NetworkState ns) { - String iface = null; - if (ns != null && ns.linkProperties != null) { - // Find the interface with the default IPv4 route. It may be the - // interface described by linkProperties, or one of the interfaces - // stacked on top of it. - Log.i(TAG, "Finding IPv4 upstream interface on: " + ns.linkProperties); - RouteInfo ipv4Default = RouteInfo.selectBestRoute( - ns.linkProperties.getAllRoutes(), Inet4Address.ANY); - if (ipv4Default != null) { - iface = ipv4Default.getInterface(); - Log.i(TAG, "Found interface " + ipv4Default.getInterface()); - } else { - Log.i(TAG, "No IPv4 upstream interface, giving up."); - } - } + protected void chooseUpstreamType(boolean tryCell) { + updateConfiguration(); // TODO - remove? - if (iface != null) { - setDnsForwarders(ns.network, ns.linkProperties); + final NetworkState ns = mUpstreamNetworkMonitor.selectPreferredUpstreamType( + mConfig.preferredUpstreamIfaceTypes); + if (ns == null) { + if (tryCell) { + mUpstreamNetworkMonitor.registerMobileNetworkRequest(); + // We think mobile should be coming up; don't set a retry. + } else { + sendMessageDelayed(CMD_RETRY_UPSTREAM, UPSTREAM_SETTLE_TIME_MS); } - notifyTetheredOfNewUpstreamIface(iface); - if (ns != null && pertainsToCurrentUpstream(ns)) { - // If we already have NetworkState for this network examine - // it immediately, because there likely will be no second - // EVENT_ON_AVAILABLE (it was already received). - handleNewUpstreamNetworkState(ns); - } else if (mCurrentUpstreamIface == null) { - // There are no available upstream networks, or none that - // have an IPv4 default route (current metric for success). - handleNewUpstreamNetworkState(null); + } + setUpstreamNetwork(ns); + } + + protected void setUpstreamNetwork(NetworkState ns) { + String iface = null; + if (ns != null && ns.linkProperties != null) { + // Find the interface with the default IPv4 route. It may be the + // interface described by linkProperties, or one of the interfaces + // stacked on top of it. + Log.i(TAG, "Finding IPv4 upstream interface on: " + ns.linkProperties); + RouteInfo ipv4Default = RouteInfo.selectBestRoute( + ns.linkProperties.getAllRoutes(), Inet4Address.ANY); + if (ipv4Default != null) { + iface = ipv4Default.getInterface(); + Log.i(TAG, "Found interface " + ipv4Default.getInterface()); + } else { + Log.i(TAG, "No IPv4 upstream interface, giving up."); } } - protected void setDnsForwarders(final Network network, final LinkProperties lp) { - // TODO: Set v4 and/or v6 DNS per available connectivity. - String[] dnsServers = mConfig.defaultIPv4DNS; - final Collection<InetAddress> dnses = lp.getDnsServers(); - // TODO: Properly support the absence of DNS servers. - if (dnses != null && !dnses.isEmpty()) { - // TODO: remove this invocation of NetworkUtils.makeStrings(). - dnsServers = NetworkUtils.makeStrings(dnses); - } - try { - mNMService.setDnsForwarders(network, dnsServers); - mLog.log(String.format( - "SET DNS forwarders: network=%s dnsServers=%s", - network, Arrays.toString(dnsServers))); - } catch (Exception e) { - // TODO: Investigate how this can fail and what exactly - // happens if/when such failures occur. - mLog.e("setting DNS forwarders failed, " + e); - transitionTo(mSetDnsForwardersErrorState); - } + if (iface != null) { + setDnsForwarders(ns.network, ns.linkProperties); } + notifyTetheredOfNewUpstreamIface(iface); + if (ns != null && pertainsToCurrentUpstream(ns)) { + // If we already have NetworkState for this network examine + // it immediately, because there likely will be no second + // EVENT_ON_AVAILABLE (it was already received). + handleNewUpstreamNetworkState(ns); + } else if (mCurrentUpstreamIface == null) { + // There are no available upstream networks, or none that + // have an IPv4 default route (current metric for success). + handleNewUpstreamNetworkState(null); + } + } - protected void notifyTetheredOfNewUpstreamIface(String ifaceName) { - if (DBG) Log.d(TAG, "Notifying tethered with upstream=" + ifaceName); - mCurrentUpstreamIface = ifaceName; - for (TetherInterfaceStateMachine sm : mNotifyList) { - sm.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_CONNECTION_CHANGED, - ifaceName); - } + protected void setDnsForwarders(final Network network, final LinkProperties lp) { + // TODO: Set v4 and/or v6 DNS per available connectivity. + String[] dnsServers = mConfig.defaultIPv4DNS; + final Collection<InetAddress> dnses = lp.getDnsServers(); + // TODO: Properly support the absence of DNS servers. + if (dnses != null && !dnses.isEmpty()) { + // TODO: remove this invocation of NetworkUtils.makeStrings(). + dnsServers = NetworkUtils.makeStrings(dnses); } + try { + mNMService.setDnsForwarders(network, dnsServers); + mLog.log(String.format( + "SET DNS forwarders: network=%s dnsServers=%s", + network, Arrays.toString(dnsServers))); + } catch (Exception e) { + // TODO: Investigate how this can fail and what exactly + // happens if/when such failures occur. + mLog.e("setting DNS forwarders failed, " + e); + transitionTo(mSetDnsForwardersErrorState); + } + } - protected void handleNewUpstreamNetworkState(NetworkState ns) { - mIPv6TetheringCoordinator.updateUpstreamNetworkState(ns); - mOffloadController.setUpstreamLinkProperties( - (ns != null) ? ns.linkProperties : null); + protected void notifyTetheredOfNewUpstreamIface(String ifaceName) { + if (DBG) Log.d(TAG, "Notifying tethered with upstream=" + ifaceName); + mCurrentUpstreamIface = ifaceName; + for (TetherInterfaceStateMachine sm : mNotifyList) { + sm.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_CONNECTION_CHANGED, + ifaceName); } } + protected void handleNewUpstreamNetworkState(NetworkState ns) { + mIPv6TetheringCoordinator.updateUpstreamNetworkState(ns); + mOffloadController.setUpstreamLinkProperties((ns != null) ? ns.linkProperties : null); + } + private void handleInterfaceServingStateActive(int mode, TetherInterfaceStateMachine who) { if (mNotifyList.indexOf(who) < 0) { mNotifyList.add(who); @@ -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; @@ -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; } @@ -1427,7 +1481,7 @@ public class Tethering extends BaseNetworkObserver { mUpstreamWanted = upstreamWanted(); if (mUpstreamWanted != previousUpstreamWanted) { if (mUpstreamWanted) { - mOffloadController.start(); + startOffloadController(); } else { mOffloadController.stop(); } @@ -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; } diff --git a/services/core/java/com/android/server/connectivity/tethering/OffloadController.java b/services/core/java/com/android/server/connectivity/tethering/OffloadController.java index 08deef84f3ae..78487b713f2e 100644 --- a/services/core/java/com/android/server/connectivity/tethering/OffloadController.java +++ b/services/core/java/com/android/server/connectivity/tethering/OffloadController.java @@ -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; @@ -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 @@ -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) { @@ -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; diff --git a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java index 9ebfaf7b3893..eb66767bfdfa 100644 --- a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java +++ b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java @@ -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; /** @@ -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; @@ -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; @@ -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; } @@ -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); @@ -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) { @@ -376,6 +402,7 @@ public class UpstreamNetworkMonitor { @Override public void onLinkPropertiesChanged(Network network, LinkProperties newLp) { handleLinkProp(network, newLp); + recomputeOffloadExemptPrefixes(); } // TODO: Handle onNetworkSuspended(); @@ -384,6 +411,7 @@ public class UpstreamNetworkMonitor { @Override public void onLost(Network network) { handleLost(mCallbackType, network); + recomputeOffloadExemptPrefixes(); } } @@ -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(); @@ -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); + } } diff --git a/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java b/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java index fb5c5778d05b..69c93b14a486 100644 --- a/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java +++ b/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java @@ -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; @@ -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; @@ -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; @@ -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 { @@ -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); @@ -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 { @@ -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))); + } + } } |