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