Merge "Pass all offload-exempt prefixes into OffloadController"
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user