Merge "Pass all offload-exempt prefixes into OffloadController" into oc-dr1-dev
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index e5fc4b1..e2956dd 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.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.HashSet;
import java.util.Iterator;
import java.util.Map;
+import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
@@ -213,7 +215,7 @@
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 @@
// 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 @@
}
}
+ 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 @@
}
}
- 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);
+ mNMService.startTethering(cfg.dhcpRanges);
+ } catch (Exception ee) {
+ mLog.e(ee);
+ transitionTo(mStartTetheringErrorState);
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;
}
+ mLog.log("SET master tether settings: ON");
+ return true;
+ }
- protected void chooseUpstreamType(boolean tryCell) {
- updateConfiguration(); // TODO - remove?
-
- 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 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;
+ }
- 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 @@
}
}
- 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 @@
// TODO: De-duplicate with updateUpstreamWanted() below.
if (upstreamWanted()) {
mUpstreamWanted = true;
- mOffloadController.start();
+ startOffloadController();
chooseUpstreamType(true);
mTryCell = false;
}
@@ -1427,7 +1481,7 @@
mUpstreamWanted = upstreamWanted();
if (mUpstreamWanted != previousUpstreamWanted) {
if (mUpstreamWanted) {
- mOffloadController.start();
+ startOffloadController();
} else {
mOffloadController.stop();
}
@@ -1507,52 +1561,8 @@
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 08deef8..78487b7 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 @@
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 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 @@
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 @@
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 9ebfaf7..eb66767 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.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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
@Override
public void onLinkPropertiesChanged(Network network, LinkProperties newLp) {
handleLinkProp(network, newLp);
+ recomputeOffloadExemptPrefixes();
}
// TODO: Handle onNetworkSuspended();
@@ -384,6 +411,7 @@
@Override
public void onLost(Network network) {
handleLost(mCallbackType, network);
+ recomputeOffloadExemptPrefixes();
}
}
@@ -395,16 +423,16 @@
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 @@
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 fb5c577..69c93b1 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.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 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 @@
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 @@
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 @@
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 @@
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 @@
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)));
+ }
+ }
}