Merge "Pass all offload-exempt prefixes into OffloadController"

This commit is contained in:
Treehugger Robot
2017-07-03 08:30:30 +00:00
committed by Gerrit Code Review
4 changed files with 386 additions and 178 deletions

View File

@@ -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;
@@ -104,6 +105,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;
@@ -210,7 +212,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());
@@ -1066,7 +1068,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)) {
@@ -1100,6 +1102,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
@@ -1193,146 +1201,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);
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);
@@ -1379,7 +1379,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;
@@ -1397,7 +1451,7 @@ public class Tethering extends BaseNetworkObserver {
// TODO: De-duplicate with updateUpstreamWanted() below.
if (upstreamWanted()) {
mUpstreamWanted = true;
mOffloadController.start();
startOffloadController();
chooseUpstreamType(true);
mTryCell = false;
}
@@ -1417,7 +1471,7 @@ public class Tethering extends BaseNetworkObserver {
mUpstreamWanted = upstreamWanted();
if (mUpstreamWanted != previousUpstreamWanted) {
if (mUpstreamWanted) {
mOffloadController.start();
startOffloadController();
} else {
mOffloadController.stop();
}
@@ -1497,52 +1551,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;
}

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -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)));
}
}
}