Merge "Gate exemptFromEentitlementCheck by Network_STACK permission"
diff --git a/Tethering/Android.bp b/Tethering/Android.bp
index eb72b81..4116afc 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -25,7 +25,7 @@
     ],
     static_libs: [
         "androidx.annotation_annotation",
-        "netd_aidl_interface-V3-java",
+        "netd_aidl_interface-unstable-java",
         "netlink-client",
         "networkstack-aidl-interfaces-java",
         "android.hardware.tetheroffload.config-V1.0-java",
@@ -33,7 +33,7 @@
         "net-utils-framework-common",
     ],
     libs: [
-        "framework-tethering",
+        "framework-tethering.impl",
         "unsupportedappusage",
     ],
     plugins: ["java_api_finder"],
@@ -96,7 +96,7 @@
         "res",
     ],
     libs: [
-        "framework-tethering",
+        "framework-tethering.impl",
     ],
     jarjar_rules: "jarjar-rules.txt",
     optimize: {
diff --git a/Tethering/common/TetheringLib/Android.bp b/Tethering/common/TetheringLib/Android.bp
index 79a1782..408725c 100644
--- a/Tethering/common/TetheringLib/Android.bp
+++ b/Tethering/common/TetheringLib/Android.bp
@@ -13,31 +13,28 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-java_library {
+java_sdk_library {
     name: "framework-tethering",
-    sdk_version: "module_current",
+    defaults: ["framework-module-defaults"],
     srcs: [
         ":framework-tethering-srcs",
     ],
+
+    // TODO(b/155480189) - Remove naming_scheme once references have been resolved.
+    // Temporary java_sdk_library component naming scheme to use to ease the transition from separate
+    // modules to java_sdk_library.
+    naming_scheme: "framework-modules",
+
     jarjar_rules: "jarjar-rules.txt",
     installable: true,
 
-    libs: [
-        "framework-annotations-lib",
-    ],
-
     hostdex: true, // for hiddenapi check
     visibility: ["//frameworks/base/packages/Tethering:__subpackages__"],
+    stubs_library_visibility: ["//visibility:public"],
     apex_available: ["com.android.tethering"],
     permitted_packages: ["android.net"],
 }
 
-stubs_defaults {
-    name: "framework-tethering-stubs-defaults",
-    srcs: [":framework-tethering-srcs"],
-    dist: { dest: "framework-tethering.txt" },
-}
-
 filegroup {
     name: "framework-tethering-srcs",
     srcs: [
@@ -55,56 +52,3 @@
     ],
     path: "src"
 }
-
-droidstubs {
-    name: "framework-tethering-stubs-srcs-publicapi",
-    defaults: [
-        "framework-module-stubs-defaults-publicapi",
-        "framework-tethering-stubs-defaults",
-    ],
-}
-
-droidstubs {
-    name: "framework-tethering-stubs-srcs-systemapi",
-    defaults: [
-        "framework-module-stubs-defaults-systemapi",
-        "framework-tethering-stubs-defaults",
-    ],
-}
-
-droidstubs {
-    name: "framework-tethering-api-module_libs_api",
-    defaults: [
-        "framework-module-api-defaults-module_libs_api",
-        "framework-tethering-stubs-defaults",
-    ],
-}
-
-droidstubs {
-    name: "framework-tethering-stubs-srcs-module_libs_api",
-    defaults: [
-        "framework-module-stubs-defaults-module_libs_api",
-        "framework-tethering-stubs-defaults",
-    ],
-}
-
-java_library {
-    name: "framework-tethering-stubs-publicapi",
-    srcs: [":framework-tethering-stubs-srcs-publicapi"],
-    defaults: ["framework-module-stubs-lib-defaults-publicapi"],
-    dist: { dest: "framework-tethering.jar" },
-}
-
-java_library {
-    name: "framework-tethering-stubs-systemapi",
-    srcs: [":framework-tethering-stubs-srcs-systemapi"],
-    defaults: ["framework-module-stubs-lib-defaults-systemapi"],
-    dist: { dest: "framework-tethering.jar" },
-}
-
-java_library {
-    name: "framework-tethering-stubs-module_libs_api",
-    srcs: [":framework-tethering-stubs-srcs-module_libs_api"],
-    defaults: ["framework-module-stubs-lib-defaults-module_libs_api"],
-    dist: { dest: "framework-tethering.jar" },
-}
diff --git a/Tethering/common/TetheringLib/api/module-lib-lint-baseline.txt b/Tethering/common/TetheringLib/api/module-lib-lint-baseline.txt
new file mode 100644
index 0000000..af7ad52
--- /dev/null
+++ b/Tethering/common/TetheringLib/api/module-lib-lint-baseline.txt
@@ -0,0 +1,39 @@
+// Baseline format: 1.0
+ActionValue: android.net.TetheringConstants#EXTRA_ADD_TETHER_TYPE:
+    Inconsistent extra value; expected `android.net.extra.ADD_TETHER_TYPE`, was `extraAddTetherType`
+ActionValue: android.net.TetheringConstants#EXTRA_PROVISION_CALLBACK:
+    Inconsistent extra value; expected `android.net.extra.PROVISION_CALLBACK`, was `extraProvisionCallback`
+ActionValue: android.net.TetheringConstants#EXTRA_REM_TETHER_TYPE:
+    Inconsistent extra value; expected `android.net.extra.REM_TETHER_TYPE`, was `extraRemTetherType`
+ActionValue: android.net.TetheringConstants#EXTRA_RUN_PROVISION:
+    Inconsistent extra value; expected `android.net.extra.RUN_PROVISION`, was `extraRunProvision`
+ActionValue: android.net.TetheringConstants#EXTRA_SET_ALARM:
+    Inconsistent extra value; expected `android.net.extra.SET_ALARM`, was `extraSetAlarm`
+ActionValue: android.net.TetheringManager#ACTION_TETHER_STATE_CHANGED:
+    Inconsistent action value; expected `android.net.action.TETHER_STATE_CHANGED`, was `android.net.conn.TETHER_STATE_CHANGED`
+ActionValue: android.net.TetheringManager#EXTRA_ACTIVE_TETHER:
+    Inconsistent extra value; expected `android.net.extra.ACTIVE_TETHER`, was `tetherArray`
+ActionValue: android.net.TetheringManager#EXTRA_AVAILABLE_TETHER:
+    Inconsistent extra value; expected `android.net.extra.AVAILABLE_TETHER`, was `availableArray`
+ActionValue: android.net.TetheringManager#EXTRA_ERRORED_TETHER:
+    Inconsistent extra value; expected `android.net.extra.ERRORED_TETHER`, was `erroredArray`
+
+
+CallbackInterface: android.net.TetheringManager.StartTetheringCallback:
+    Callbacks must be abstract class instead of interface to enable extension in future API levels: StartTetheringCallback
+CallbackInterface: android.net.TetheringManager.TetheringEventCallback:
+    Callbacks must be abstract class instead of interface to enable extension in future API levels: TetheringEventCallback
+
+
+ManagerConstructor: android.net.TetheringManager#TetheringManager(android.content.Context, java.util.function.Supplier<android.os.IBinder>):
+    Managers must always be obtained from Context; no direct constructors
+
+
+MissingGetterMatchingBuilder: android.net.TetheringManager.TetheringRequest.Builder#setShouldShowEntitlementUi(boolean):
+    android.net.TetheringManager.TetheringRequest does not declare a `shouldShowEntitlementUi()` method matching method android.net.TetheringManager.TetheringRequest.Builder.setShouldShowEntitlementUi(boolean)
+MissingGetterMatchingBuilder: android.net.TetheringManager.TetheringRequest.Builder#setStaticIpv4Addresses(android.net.LinkAddress, android.net.LinkAddress):
+    android.net.TetheringManager.TetheringRequest does not declare a `getStaticIpv4Addresses()` method matching method android.net.TetheringManager.TetheringRequest.Builder.setStaticIpv4Addresses(android.net.LinkAddress,android.net.LinkAddress)
+
+
+StaticFinalBuilder: android.net.TetheringManager.TetheringRequest.Builder:
+    Builder must be final: android.net.TetheringManager.TetheringRequest.Builder
diff --git a/Tethering/common/TetheringLib/api/system-lint-baseline.txt b/Tethering/common/TetheringLib/api/system-lint-baseline.txt
new file mode 100644
index 0000000..f8d291c
--- /dev/null
+++ b/Tethering/common/TetheringLib/api/system-lint-baseline.txt
@@ -0,0 +1,25 @@
+// Baseline format: 1.0
+ActionValue: android.net.TetheringManager#ACTION_TETHER_STATE_CHANGED:
+    Inconsistent action value; expected `android.net.action.TETHER_STATE_CHANGED`, was `android.net.conn.TETHER_STATE_CHANGED`
+ActionValue: android.net.TetheringManager#EXTRA_ACTIVE_TETHER:
+    Inconsistent extra value; expected `android.net.extra.ACTIVE_TETHER`, was `tetherArray`
+ActionValue: android.net.TetheringManager#EXTRA_AVAILABLE_TETHER:
+    Inconsistent extra value; expected `android.net.extra.AVAILABLE_TETHER`, was `availableArray`
+ActionValue: android.net.TetheringManager#EXTRA_ERRORED_TETHER:
+    Inconsistent extra value; expected `android.net.extra.ERRORED_TETHER`, was `erroredArray`
+
+
+CallbackInterface: android.net.TetheringManager.StartTetheringCallback:
+    Callbacks must be abstract class instead of interface to enable extension in future API levels: StartTetheringCallback
+CallbackInterface: android.net.TetheringManager.TetheringEventCallback:
+    Callbacks must be abstract class instead of interface to enable extension in future API levels: TetheringEventCallback
+
+
+MissingGetterMatchingBuilder: android.net.TetheringManager.TetheringRequest.Builder#setShouldShowEntitlementUi(boolean):
+    android.net.TetheringManager.TetheringRequest does not declare a `shouldShowEntitlementUi()` method matching method android.net.TetheringManager.TetheringRequest.Builder.setShouldShowEntitlementUi(boolean)
+MissingGetterMatchingBuilder: android.net.TetheringManager.TetheringRequest.Builder#setStaticIpv4Addresses(android.net.LinkAddress, android.net.LinkAddress):
+    android.net.TetheringManager.TetheringRequest does not declare a `getStaticIpv4Addresses()` method matching method android.net.TetheringManager.TetheringRequest.Builder.setStaticIpv4Addresses(android.net.LinkAddress,android.net.LinkAddress)
+
+
+StaticFinalBuilder: android.net.TetheringManager.TetheringRequest.Builder:
+    Builder must be final: android.net.TetheringManager.TetheringRequest.Builder
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index 659d344..088b88c 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -16,14 +16,13 @@
 
 package android.net.ip;
 
-import static android.net.InetAddresses.parseNumericAddress;
 import static android.net.RouteInfo.RTN_UNICAST;
 import static android.net.TetheringManager.TetheringRequest.checkStaticAddressConfiguration;
 import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
 import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH;
-import static android.net.util.NetworkConstants.FF;
 import static android.net.util.NetworkConstants.RFC7421_PREFIX_LENGTH;
 import static android.net.util.NetworkConstants.asByte;
+import static android.net.util.PrefixUtils.asIpPrefix;
 import static android.net.util.TetheringMessageBase.BASE_IPSERVER;
 import static android.system.OsConstants.RT_SCOPE_UNIVERSE;
 
@@ -34,7 +33,6 @@
 import android.net.LinkProperties;
 import android.net.MacAddress;
 import android.net.RouteInfo;
-import android.net.TetherOffloadRuleParcel;
 import android.net.TetheredClient;
 import android.net.TetheringManager;
 import android.net.TetheringRequestParcel;
@@ -66,11 +64,13 @@
 import com.android.internal.util.MessageUtils;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
+import com.android.networkstack.tethering.BpfCoordinator;
+import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule;
+import com.android.networkstack.tethering.PrivateAddressCoordinator;
 
 import java.io.IOException;
 import java.net.Inet4Address;
 import java.net.Inet6Address;
-import java.net.InetAddress;
 import java.net.NetworkInterface;
 import java.net.UnknownHostException;
 import java.util.ArrayList;
@@ -108,27 +108,8 @@
 
     private static final byte DOUG_ADAMS = (byte) 42;
 
-    private static final String USB_NEAR_IFACE_ADDR = "192.168.42.129";
-    private static final int USB_PREFIX_LENGTH = 24;
-    private static final String WIFI_HOST_IFACE_ADDR = "192.168.43.1";
-    private static final int WIFI_HOST_IFACE_PREFIX_LENGTH = 24;
-    private static final String WIFI_P2P_IFACE_ADDR = "192.168.49.1";
-    private static final int WIFI_P2P_IFACE_PREFIX_LENGTH = 24;
-    private static final String ETHERNET_IFACE_ADDR = "192.168.50.1";
-    private static final int ETHERNET_IFACE_PREFIX_LENGTH = 24;
-
-    // TODO: remove this constant after introducing PrivateAddressCoordinator.
-    private static final List<IpPrefix> NCM_PREFIXES = Collections.unmodifiableList(
-            Arrays.asList(
-                    new IpPrefix("192.168.42.0/24"),
-                    new IpPrefix("192.168.51.0/24"),
-                    new IpPrefix("192.168.52.0/24"),
-                    new IpPrefix("192.168.53.0/24")
-    ));
-
     // TODO: have PanService use some visible version of this constant
-    private static final String BLUETOOTH_IFACE_ADDR = "192.168.44.1";
-    private static final int BLUETOOTH_DHCP_PREFIX_LENGTH = 24;
+    private static final String BLUETOOTH_IFACE_ADDR = "192.168.44.1/24";
 
     // TODO: have this configurable
     private static final int DHCP_LEASE_TIME_SECS = 3600;
@@ -167,6 +148,14 @@
          * Notify that the DHCP leases changed in one of the IpServers.
          */
         public void dhcpLeasesChanged() { }
+
+        /**
+         * Request Tethering change.
+         *
+         * @param tetheringType the downstream type of this IpServer.
+         * @param enabled enable or disable tethering.
+         */
+        public void requestEnableTethering(int tetheringType, boolean enabled) { }
     }
 
     /** Capture IpServer dependencies, for injection. */
@@ -196,6 +185,7 @@
                 return 0;
             }
         }
+
         /** Create a DhcpServer instance to be used by IpServer. */
         public abstract void makeDhcpServer(String ifName, DhcpServingParamsParcel params,
                 DhcpServerCallbacks cb);
@@ -225,16 +215,22 @@
     public static final int CMD_NEIGHBOR_EVENT              = BASE_IPSERVER + 11;
     // request from DHCP server that it wants to have a new prefix
     public static final int CMD_NEW_PREFIX_REQUEST          = BASE_IPSERVER + 12;
+    // request from PrivateAddressCoordinator to restart tethering.
+    public static final int CMD_NOTIFY_PREFIX_CONFLICT      = BASE_IPSERVER + 13;
 
     private final State mInitialState;
     private final State mLocalHotspotState;
     private final State mTetheredState;
     private final State mUnavailableState;
+    private final State mWaitingForRestartState;
 
     private final SharedLog mLog;
     private final INetd mNetd;
+    @NonNull
+    private final BpfCoordinator mBpfCoordinator;
     private final Callback mCallback;
     private final InterfaceController mInterfaceCtrl;
+    private final PrivateAddressCoordinator mPrivateAddressCoordinator;
 
     private final String mIfaceName;
     private final int mInterfaceType;
@@ -261,7 +257,6 @@
     private int mDhcpServerStartIndex = 0;
     private IDhcpServer mDhcpServer;
     private RaParams mLastRaParams;
-    private LinkAddress mIpv4Address;
 
     private LinkAddress mStaticIpv4ServerAddr;
     private LinkAddress mStaticIpv4ClientAddr;
@@ -277,54 +272,24 @@
         }
     }
 
-    static class Ipv6ForwardingRule {
-        public final int upstreamIfindex;
-        public final int downstreamIfindex;
-        public final Inet6Address address;
-        public final MacAddress srcMac;
-        public final MacAddress dstMac;
-
-        Ipv6ForwardingRule(int upstreamIfindex, int downstreamIfIndex, Inet6Address address,
-                MacAddress srcMac, MacAddress dstMac) {
-            this.upstreamIfindex = upstreamIfindex;
-            this.downstreamIfindex = downstreamIfIndex;
-            this.address = address;
-            this.srcMac = srcMac;
-            this.dstMac = dstMac;
-        }
-
-        public Ipv6ForwardingRule onNewUpstream(int newUpstreamIfindex) {
-            return new Ipv6ForwardingRule(newUpstreamIfindex, downstreamIfindex, address, srcMac,
-                    dstMac);
-        }
-
-        // Don't manipulate TetherOffloadRuleParcel directly because implementing onNewUpstream()
-        // would be error-prone due to generated stable AIDL classes not having a copy constructor.
-        public TetherOffloadRuleParcel toTetherOffloadRuleParcel() {
-            final TetherOffloadRuleParcel parcel = new TetherOffloadRuleParcel();
-            parcel.inputInterfaceIndex = upstreamIfindex;
-            parcel.outputInterfaceIndex = downstreamIfindex;
-            parcel.destination = address.getAddress();
-            parcel.prefixLength = 128;
-            parcel.srcL2Address = srcMac.toByteArray();
-            parcel.dstL2Address = dstMac.toByteArray();
-            return parcel;
-        }
-    }
     private final LinkedHashMap<Inet6Address, Ipv6ForwardingRule> mIpv6ForwardingRules =
             new LinkedHashMap<>();
 
     private final IpNeighborMonitor mIpNeighborMonitor;
 
+    private LinkAddress mIpv4Address;
+
     // TODO: Add a dependency object to pass the data members or variables from the tethering
     // object. It helps to reduce the arguments of the constructor.
     public IpServer(
             String ifaceName, Looper looper, int interfaceType, SharedLog log,
-            INetd netd, Callback callback, boolean usingLegacyDhcp, boolean usingBpfOffload,
-            Dependencies deps) {
+            INetd netd, @NonNull BpfCoordinator coordinator, Callback callback,
+            boolean usingLegacyDhcp, boolean usingBpfOffload,
+            PrivateAddressCoordinator addressCoordinator, Dependencies deps) {
         super(ifaceName, looper);
         mLog = log.forSubComponent(ifaceName);
         mNetd = netd;
+        mBpfCoordinator = coordinator;
         mCallback = callback;
         mInterfaceCtrl = new InterfaceController(ifaceName, mNetd, mLog);
         mIfaceName = ifaceName;
@@ -332,6 +297,7 @@
         mLinkProperties = new LinkProperties();
         mUsingLegacyDhcp = usingLegacyDhcp;
         mUsingBpfOffload = usingBpfOffload;
+        mPrivateAddressCoordinator = addressCoordinator;
         mDeps = deps;
         resetLinkProperties();
         mLastError = TetheringManager.TETHER_ERROR_NO_ERROR;
@@ -352,9 +318,11 @@
         mLocalHotspotState = new LocalHotspotState();
         mTetheredState = new TetheredState();
         mUnavailableState = new UnavailableState();
+        mWaitingForRestartState = new WaitingForRestartState();
         addState(mInitialState);
         addState(mLocalHotspotState);
         addState(mTetheredState);
+        addState(mWaitingForRestartState, mTetheredState);
         addState(mUnavailableState);
 
         setInitialState(mInitialState);
@@ -387,6 +355,11 @@
         return new LinkProperties(mLinkProperties);
     }
 
+    /** The address which IpServer is using. */
+    public LinkAddress getAddress() {
+        return mIpv4Address;
+    }
+
     /**
      * Get the latest list of DHCP leases that was reported. Must be called on the IpServer looper
      * thread.
@@ -617,6 +590,7 @@
         // NOTE: All of configureIPv4() will be refactored out of existence
         // into calls to InterfaceController, shared with startIPv4().
         mInterfaceCtrl.clearIPv4Address();
+        mPrivateAddressCoordinator.releaseDownstream(this);
         mIpv4Address = null;
         mStaticIpv4ServerAddr = null;
         mStaticIpv4ClientAddr = null;
@@ -625,43 +599,24 @@
     private boolean configureIPv4(boolean enabled) {
         if (VDBG) Log.d(TAG, "configureIPv4(" + enabled + ")");
 
-        // TODO: Replace this hard-coded information with dynamically selected
-        // config passed down to us by a higher layer IP-coordinating element.
-        final Inet4Address srvAddr;
-        int prefixLen = 0;
-        try {
-            if (mStaticIpv4ServerAddr != null) {
-                srvAddr = (Inet4Address) mStaticIpv4ServerAddr.getAddress();
-                prefixLen = mStaticIpv4ServerAddr.getPrefixLength();
-            } else if (mInterfaceType == TetheringManager.TETHERING_USB
-                    || mInterfaceType == TetheringManager.TETHERING_NCM) {
-                srvAddr = (Inet4Address) parseNumericAddress(USB_NEAR_IFACE_ADDR);
-                prefixLen = USB_PREFIX_LENGTH;
-            } else if (mInterfaceType == TetheringManager.TETHERING_WIFI) {
-                srvAddr = (Inet4Address) parseNumericAddress(getRandomWifiIPv4Address());
-                prefixLen = WIFI_HOST_IFACE_PREFIX_LENGTH;
-            } else if (mInterfaceType == TetheringManager.TETHERING_WIFI_P2P) {
-                srvAddr = (Inet4Address) parseNumericAddress(WIFI_P2P_IFACE_ADDR);
-                prefixLen = WIFI_P2P_IFACE_PREFIX_LENGTH;
-            } else if (mInterfaceType == TetheringManager.TETHERING_ETHERNET) {
-                // TODO: randomize address for tethering too, similarly to wifi
-                srvAddr = (Inet4Address) parseNumericAddress(ETHERNET_IFACE_ADDR);
-                prefixLen = ETHERNET_IFACE_PREFIX_LENGTH;
-            } else {
-                // BT configures the interface elsewhere: only start DHCP.
-                // TODO: make all tethering types behave the same way, and delete the bluetooth
-                // code that calls into NetworkManagementService directly.
-                srvAddr = (Inet4Address) parseNumericAddress(BLUETOOTH_IFACE_ADDR);
-                mIpv4Address = new LinkAddress(srvAddr, BLUETOOTH_DHCP_PREFIX_LENGTH);
-                return configureDhcp(enabled, mIpv4Address, null /* clientAddress */);
-            }
-            mIpv4Address = new LinkAddress(srvAddr, prefixLen);
-        } catch (IllegalArgumentException e) {
-            mLog.e("Error selecting ipv4 address", e);
-            if (!enabled) stopDhcp();
+        if (enabled) {
+            mIpv4Address = requestIpv4Address();
+        }
+
+        if (mIpv4Address == null) {
+            mLog.e("No available ipv4 address");
             return false;
         }
 
+        if (mInterfaceType == TetheringManager.TETHERING_BLUETOOTH) {
+            // BT configures the interface elsewhere: only start DHCP.
+            // TODO: make all tethering types behave the same way, and delete the bluetooth
+            // code that calls into NetworkManagementService directly.
+            return configureDhcp(enabled, mIpv4Address, null /* clientAddress */);
+        }
+
+        final IpPrefix ipv4Prefix = asIpPrefix(mIpv4Address);
+
         final Boolean setIfaceUp;
         if (mInterfaceType == TetheringManager.TETHERING_WIFI
                 || mInterfaceType == TetheringManager.TETHERING_WIFI_P2P) {
@@ -688,21 +643,14 @@
         return configureDhcp(enabled, mIpv4Address, mStaticIpv4ClientAddr);
     }
 
-    private Inet4Address getRandomIPv4Address(@NonNull final byte[] rawAddr) {
-        final byte[] ipv4Addr = rawAddr;
-        ipv4Addr[3] = getRandomSanitizedByte(DOUG_ADAMS, asByte(0), asByte(1), FF);
-        try {
-            return (Inet4Address) InetAddress.getByAddress(ipv4Addr);
-        } catch (UnknownHostException e) {
-            mLog.e("Failed to construct Inet4Address from raw IPv4 addr");
-            return null;
-        }
-    }
+    private LinkAddress requestIpv4Address() {
+        if (mStaticIpv4ServerAddr != null) return mStaticIpv4ServerAddr;
 
-    private String getRandomWifiIPv4Address() {
-        final Inet4Address ipv4Addr =
-                getRandomIPv4Address(parseNumericAddress(WIFI_HOST_IFACE_ADDR).getAddress());
-        return ipv4Addr != null ? ipv4Addr.getHostAddress() : WIFI_HOST_IFACE_ADDR;
+        if (mInterfaceType == TetheringManager.TETHERING_BLUETOOTH) {
+            return new LinkAddress(BLUETOOTH_IFACE_ADDR);
+        }
+
+        return mPrivateAddressCoordinator.requestDownstreamAddress(this);
     }
 
     private boolean startIPv6() {
@@ -777,6 +725,14 @@
             }
 
             upstreamIfindex = mDeps.getIfindex(upstreamIface);
+
+            // Add upstream index to name mapping for the tether stats usage in the coordinator.
+            // Although this mapping could be added by both class Tethering and IpServer, adding
+            // mapping from IpServer guarantees that the mapping is added before the adding
+            // forwarding rules. That is because there are different state machines in both
+            // classes. It is hard to guarantee the link property update order between multiple
+            // state machines.
+            mBpfCoordinator.addUpstreamNameToLookupTable(upstreamIfindex, upstreamIface);
         }
 
         // If v6only is null, we pass in null to setRaParams(), which handles
@@ -978,19 +934,6 @@
         }
     }
 
-    // TODO: call PrivateAddressCoordinator.requestDownstreamAddress instead of this temporary
-    // logic.
-    private Inet4Address requestDownstreamAddress(@NonNull final IpPrefix currentPrefix) {
-        final int oldIndex = NCM_PREFIXES.indexOf(currentPrefix);
-        if (oldIndex == -1) {
-            mLog.e("current prefix isn't supported for NCM link: " + currentPrefix);
-            return null;
-        }
-
-        final IpPrefix newPrefix = NCM_PREFIXES.get((oldIndex + 1) % NCM_PREFIXES.size());
-        return getRandomIPv4Address(newPrefix.getRawAddress());
-    }
-
     private void handleNewPrefixRequest(@NonNull final IpPrefix currentPrefix) {
         if (!currentPrefix.contains(mIpv4Address.getAddress())
                 || currentPrefix.getPrefixLength() != mIpv4Address.getPrefixLength()) {
@@ -999,12 +942,12 @@
         }
 
         final LinkAddress deprecatedLinkAddress = mIpv4Address;
-        final Inet4Address srvAddr = requestDownstreamAddress(currentPrefix);
-        if (srvAddr == null) {
+        mIpv4Address = requestIpv4Address();
+        if (mIpv4Address == null) {
             mLog.e("Fail to request a new downstream prefix");
             return;
         }
-        mIpv4Address = new LinkAddress(srvAddr, currentPrefix.getPrefixLength());
+        final Inet4Address srvAddr = (Inet4Address) mIpv4Address.getAddress();
 
         // Add new IPv4 address on the interface.
         if (!mInterfaceCtrl.addAddress(srvAddr, currentPrefix.getPrefixLength())) {
@@ -1162,7 +1105,7 @@
             }
 
             try {
-                NetdUtils.tetherInterface(mNetd, mIfaceName, PrefixUtils.asIpPrefix(mIpv4Address));
+                NetdUtils.tetherInterface(mNetd, mIfaceName, asIpPrefix(mIpv4Address));
             } catch (RemoteException | ServiceSpecificException | IllegalStateException e) {
                 mLog.e("Error Tethering", e);
                 mLastError = TetheringManager.TETHER_ERROR_TETHER_IFACE_ERROR;
@@ -1222,6 +1165,11 @@
                 case CMD_NEW_PREFIX_REQUEST:
                     handleNewPrefixRequest((IpPrefix) message.obj);
                     break;
+                case CMD_NOTIFY_PREFIX_CONFLICT:
+                    mLog.i("restart tethering: " + mInterfaceType);
+                    mCallback.requestEnableTethering(mInterfaceType, false /* enabled */);
+                    transitionTo(mWaitingForRestartState);
+                    break;
                 default:
                     return false;
             }
@@ -1403,6 +1351,28 @@
         }
     }
 
+    class WaitingForRestartState extends State {
+        @Override
+        public boolean processMessage(Message message) {
+            logMessage(this, message.what);
+            switch (message.what) {
+                case CMD_TETHER_UNREQUESTED:
+                    transitionTo(mInitialState);
+                    mLog.i("Untethered (unrequested) and restarting " + mIfaceName);
+                    mCallback.requestEnableTethering(mInterfaceType, true /* enabled */);
+                    break;
+                case CMD_INTERFACE_DOWN:
+                    transitionTo(mUnavailableState);
+                    mLog.i("Untethered (interface down) and restarting" + mIfaceName);
+                    mCallback.requestEnableTethering(mInterfaceType, true /* enabled */);
+                    break;
+                default:
+                    return false;
+            }
+            return true;
+        }
+    }
+
     // Accumulate routes representing "prefixes to be assigned to the local
     // interface", for subsequent modification of local_network routing.
     private static ArrayList<RouteInfo> getLocalRoutesFor(
diff --git a/Tethering/src/android/net/util/TetheringUtils.java b/Tethering/src/android/net/util/TetheringUtils.java
index dd67ddd..b17b4ba 100644
--- a/Tethering/src/android/net/util/TetheringUtils.java
+++ b/Tethering/src/android/net/util/TetheringUtils.java
@@ -15,19 +15,94 @@
  */
 package android.net.util;
 
+import android.net.TetherStatsParcel;
 import android.net.TetheringRequestParcel;
 
+import androidx.annotation.NonNull;
+
 import java.io.FileDescriptor;
 import java.net.SocketException;
 import java.util.Objects;
 
 /**
- * Native methods for tethering utilization.
+ * The classes and the methods for tethering utilization.
  *
  * {@hide}
  */
 public class TetheringUtils {
     /**
+     *  The object which records offload Tx/Rx forwarded bytes/packets.
+     *  TODO: Replace the inner class ForwardedStats of class OffloadHardwareInterface with
+     *  this class as well.
+     */
+    public static class ForwardedStats {
+        public final long rxBytes;
+        public final long rxPackets;
+        public final long txBytes;
+        public final long txPackets;
+
+        public ForwardedStats() {
+            rxBytes = 0;
+            rxPackets = 0;
+            txBytes = 0;
+            txPackets = 0;
+        }
+
+        public ForwardedStats(long rxBytes, long txBytes) {
+            this.rxBytes = rxBytes;
+            this.rxPackets = 0;
+            this.txBytes = txBytes;
+            this.txPackets = 0;
+        }
+
+        public ForwardedStats(long rxBytes, long rxPackets, long txBytes, long txPackets) {
+            this.rxBytes = rxBytes;
+            this.rxPackets = rxPackets;
+            this.txBytes = txBytes;
+            this.txPackets = txPackets;
+        }
+
+        public ForwardedStats(@NonNull TetherStatsParcel tetherStats) {
+            rxBytes = tetherStats.rxBytes;
+            rxPackets = tetherStats.rxPackets;
+            txBytes = tetherStats.txBytes;
+            txPackets = tetherStats.txPackets;
+        }
+
+        public ForwardedStats(@NonNull ForwardedStats other) {
+            rxBytes = other.rxBytes;
+            rxPackets = other.rxPackets;
+            txBytes = other.txBytes;
+            txPackets = other.txPackets;
+        }
+
+        /** Add Tx/Rx bytes/packets and return the result as a new object. */
+        @NonNull
+        public ForwardedStats add(@NonNull ForwardedStats other) {
+            return new ForwardedStats(rxBytes + other.rxBytes, rxPackets + other.rxPackets,
+                    txBytes + other.txBytes, txPackets + other.txPackets);
+        }
+
+        /** Subtract Tx/Rx bytes/packets and return the result as a new object. */
+        @NonNull
+        public ForwardedStats subtract(@NonNull ForwardedStats other) {
+            // TODO: Perhaps throw an exception if any negative difference value just in case.
+            final long rxBytesDiff = Math.max(rxBytes - other.rxBytes, 0);
+            final long rxPacketsDiff = Math.max(rxPackets - other.rxPackets, 0);
+            final long txBytesDiff = Math.max(txBytes - other.txBytes, 0);
+            final long txPacketsDiff = Math.max(txPackets - other.txPackets, 0);
+            return new ForwardedStats(rxBytesDiff, rxPacketsDiff, txBytesDiff, txPacketsDiff);
+        }
+
+        /** Returns the string representation of this object. */
+        @NonNull
+        public String toString() {
+            return String.format("ForwardedStats(rxb: %d, rxp: %d, txb: %d, txp: %d)", rxBytes,
+                    rxPackets, txBytes, txPackets);
+        }
+    }
+
+    /**
      * Configures a socket for receiving ICMPv6 router solicitations and sending advertisements.
      * @param fd the socket's {@link FileDescriptor}.
      * @param ifIndex the interface index.
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
new file mode 100644
index 0000000..6b854f2
--- /dev/null
+++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
@@ -0,0 +1,329 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.networkstack.tethering;
+
+import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
+import static android.net.NetworkStats.METERED_NO;
+import static android.net.NetworkStats.ROAMING_NO;
+import static android.net.NetworkStats.SET_DEFAULT;
+import static android.net.NetworkStats.TAG_NONE;
+import static android.net.NetworkStats.UID_ALL;
+import static android.net.NetworkStats.UID_TETHERING;
+
+import android.app.usage.NetworkStatsManager;
+import android.net.INetd;
+import android.net.MacAddress;
+import android.net.NetworkStats;
+import android.net.NetworkStats.Entry;
+import android.net.TetherOffloadRuleParcel;
+import android.net.TetherStatsParcel;
+import android.net.netstats.provider.NetworkStatsProvider;
+import android.net.util.SharedLog;
+import android.net.util.TetheringUtils.ForwardedStats;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.util.Log;
+import android.util.SparseArray;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.net.Inet6Address;
+
+/**
+ *  This coordinator is responsible for providing BPF offload relevant functionality.
+ *  - Get tethering stats.
+ *
+ * @hide
+ */
+public class BpfCoordinator {
+    private static final String TAG = BpfCoordinator.class.getSimpleName();
+    @VisibleForTesting
+    static final int DEFAULT_PERFORM_POLL_INTERVAL_MS = 5000; // TODO: Make it customizable.
+
+    @VisibleForTesting
+    enum StatsType {
+        STATS_PER_IFACE,
+        STATS_PER_UID,
+    }
+
+    @NonNull
+    private final Handler mHandler;
+    @NonNull
+    private final INetd mNetd;
+    @NonNull
+    private final SharedLog mLog;
+    @NonNull
+    private final Dependencies mDeps;
+    @Nullable
+    private final BpfTetherStatsProvider mStatsProvider;
+    private boolean mStarted = false;
+
+    // Maps upstream interface index to offloaded traffic statistics.
+    // Always contains the latest total bytes/packets, since each upstream was started, received
+    // from the BPF maps for each interface.
+    private SparseArray<ForwardedStats> mStats = new SparseArray<>();
+
+    // Maps upstream interface index to interface names.
+    // Store all interface name since boot. Used for lookup what interface name it is from the
+    // tether stats got from netd because netd reports interface index to present an interface.
+    // TODO: Remove the unused interface name.
+    private SparseArray<String> mInterfaceNames = new SparseArray<>();
+
+    // Runnable that used by scheduling next polling of stats.
+    private final Runnable mScheduledPollingTask = () -> {
+        updateForwardedStatsFromNetd();
+        maybeSchedulePollingStats();
+    };
+
+    @VisibleForTesting
+    static class Dependencies {
+        int getPerformPollInterval() {
+            // TODO: Consider make this configurable.
+            return DEFAULT_PERFORM_POLL_INTERVAL_MS;
+        }
+    }
+
+    BpfCoordinator(@NonNull Handler handler, @NonNull INetd netd,
+            @NonNull NetworkStatsManager nsm, @NonNull SharedLog log, @NonNull Dependencies deps) {
+        mHandler = handler;
+        mNetd = netd;
+        mLog = log.forSubComponent(TAG);
+        BpfTetherStatsProvider provider = new BpfTetherStatsProvider();
+        try {
+            nsm.registerNetworkStatsProvider(getClass().getSimpleName(), provider);
+        } catch (RuntimeException e) {
+            // TODO: Perhaps not allow to use BPF offload because the reregistration failure
+            // implied that no data limit could be applies on a metered upstream if any.
+            Log.wtf(TAG, "Cannot register offload stats provider: " + e);
+            provider = null;
+        }
+        mStatsProvider = provider;
+        mDeps = deps;
+    }
+
+    /**
+     * Start BPF tethering offload stats polling when the first upstream is started.
+     * Note that this can be only called on handler thread.
+     * TODO: Perhaps check BPF support before starting.
+     * TODO: Start the stats polling only if there is any client on the downstream.
+     */
+    public void start() {
+        if (mStarted) return;
+
+        mStarted = true;
+        maybeSchedulePollingStats();
+
+        mLog.i("BPF tethering coordinator started");
+    }
+
+    /**
+     * Stop BPF tethering offload stats polling and cleanup upstream parameters.
+     * Note that this can be only called on handler thread.
+     */
+    public void stop() {
+        if (!mStarted) return;
+
+        // Stop scheduled polling tasks and poll the latest stats from BPF maps.
+        if (mHandler.hasCallbacks(mScheduledPollingTask)) {
+            mHandler.removeCallbacks(mScheduledPollingTask);
+        }
+        updateForwardedStatsFromNetd();
+
+        mStarted = false;
+
+        mLog.i("BPF tethering coordinator stopped");
+    }
+
+    /**
+     * Add upstream name to lookup table. The lookup table is used for tether stats interface name
+     * lookup because the netd only reports interface index in BPF tether stats but the service
+     * expects the interface name in NetworkStats object.
+     * Note that this can be only called on handler thread.
+     */
+    public void addUpstreamNameToLookupTable(int upstreamIfindex, String upstreamIface) {
+        if (upstreamIfindex == 0) return;
+
+        // The same interface index to name mapping may be added by different IpServer objects or
+        // re-added by reconnection on the same upstream interface. Ignore the duplicate one.
+        final String iface = mInterfaceNames.get(upstreamIfindex);
+        if (iface == null) {
+            mInterfaceNames.put(upstreamIfindex, upstreamIface);
+        } else if (iface != upstreamIface) {
+            Log.wtf(TAG, "The upstream interface name " + upstreamIface
+                    + " is different from the existing interface name "
+                    + iface + " for index " + upstreamIfindex);
+        }
+    }
+
+    /** IPv6 forwarding rule class. */
+    public static class Ipv6ForwardingRule {
+        public final int upstreamIfindex;
+        public final int downstreamIfindex;
+        public final Inet6Address address;
+        public final MacAddress srcMac;
+        public final MacAddress dstMac;
+
+        public Ipv6ForwardingRule(int upstreamIfindex, int downstreamIfIndex, Inet6Address address,
+                MacAddress srcMac, MacAddress dstMac) {
+            this.upstreamIfindex = upstreamIfindex;
+            this.downstreamIfindex = downstreamIfIndex;
+            this.address = address;
+            this.srcMac = srcMac;
+            this.dstMac = dstMac;
+        }
+
+        /** Return a new rule object which updates with new upstream index. */
+        public Ipv6ForwardingRule onNewUpstream(int newUpstreamIfindex) {
+            return new Ipv6ForwardingRule(newUpstreamIfindex, downstreamIfindex, address, srcMac,
+                    dstMac);
+        }
+
+        /**
+         * Don't manipulate TetherOffloadRuleParcel directly because implementing onNewUpstream()
+         * would be error-prone due to generated stable AIDL classes not having a copy constructor.
+         */
+        public TetherOffloadRuleParcel toTetherOffloadRuleParcel() {
+            final TetherOffloadRuleParcel parcel = new TetherOffloadRuleParcel();
+            parcel.inputInterfaceIndex = upstreamIfindex;
+            parcel.outputInterfaceIndex = downstreamIfindex;
+            parcel.destination = address.getAddress();
+            parcel.prefixLength = 128;
+            parcel.srcL2Address = srcMac.toByteArray();
+            parcel.dstL2Address = dstMac.toByteArray();
+            return parcel;
+        }
+    }
+
+    /**
+     * A BPF tethering stats provider to provide network statistics to the system.
+     * Note that this class's data may only be accessed on the handler thread.
+     */
+    @VisibleForTesting
+    class BpfTetherStatsProvider extends NetworkStatsProvider {
+        // The offloaded traffic statistics per interface that has not been reported since the
+        // last call to pushTetherStats. Only the interfaces that were ever tethering upstreams
+        // and has pending tether stats delta are included in this NetworkStats object.
+        private NetworkStats mIfaceStats = new NetworkStats(0L, 0);
+
+        // The same stats as above, but counts network stats per uid.
+        private NetworkStats mUidStats = new NetworkStats(0L, 0);
+
+        @Override
+        public void onRequestStatsUpdate(int token) {
+            mHandler.post(() -> pushTetherStats());
+        }
+
+        @Override
+        public void onSetAlert(long quotaBytes) {
+            // no-op
+        }
+
+        @Override
+        public void onSetLimit(@NonNull String iface, long quotaBytes) {
+            // no-op
+        }
+
+        @VisibleForTesting
+        void pushTetherStats() {
+            try {
+                // The token is not used for now. See b/153606961.
+                notifyStatsUpdated(0 /* token */, mIfaceStats, mUidStats);
+
+                // Clear the accumulated tether stats delta after reported. Note that create a new
+                // empty object because NetworkStats#clear is @hide.
+                mIfaceStats = new NetworkStats(0L, 0);
+                mUidStats = new NetworkStats(0L, 0);
+            } catch (RuntimeException e) {
+                mLog.e("Cannot report network stats: ", e);
+            }
+        }
+
+        private void accumulateDiff(@NonNull NetworkStats ifaceDiff,
+                @NonNull NetworkStats uidDiff) {
+            mIfaceStats = mIfaceStats.add(ifaceDiff);
+            mUidStats = mUidStats.add(uidDiff);
+        }
+    }
+
+    @NonNull
+    private NetworkStats buildNetworkStats(@NonNull StatsType type, int ifIndex,
+            @NonNull ForwardedStats diff) {
+        NetworkStats stats = new NetworkStats(0L, 0);
+        final String iface = mInterfaceNames.get(ifIndex);
+        if (iface == null) {
+            // TODO: Use Log.wtf once the coordinator owns full control of tether stats from netd.
+            // For now, netd may add the empty stats for the upstream which is not monitored by
+            // the coordinator. Silently ignore it.
+            return stats;
+        }
+        final int uid = (type == StatsType.STATS_PER_UID) ? UID_TETHERING : UID_ALL;
+        // Note that the argument 'metered', 'roaming' and 'defaultNetwork' are not recorded for
+        // network stats snapshot. See NetworkStatsRecorder#recordSnapshotLocked.
+        return stats.addEntry(new Entry(iface, uid, SET_DEFAULT, TAG_NONE, METERED_NO,
+                ROAMING_NO, DEFAULT_NETWORK_NO, diff.rxBytes, diff.rxPackets,
+                diff.txBytes, diff.txPackets, 0L /* operations */));
+    }
+
+    private void updateForwardedStatsFromNetd() {
+        final TetherStatsParcel[] tetherStatsList;
+        try {
+            // The reported tether stats are total data usage for all currently-active upstream
+            // interfaces since tethering start.
+            tetherStatsList = mNetd.tetherOffloadGetStats();
+        } catch (RemoteException | ServiceSpecificException e) {
+            mLog.e("Problem fetching tethering stats: ", e);
+            return;
+        }
+
+        for (TetherStatsParcel tetherStats : tetherStatsList) {
+            final Integer ifIndex = tetherStats.ifIndex;
+            final ForwardedStats curr = new ForwardedStats(tetherStats);
+            final ForwardedStats base = mStats.get(ifIndex);
+            final ForwardedStats diff = (base != null) ? curr.subtract(base) : curr;
+
+            // Update the local cache for counting tether stats delta.
+            mStats.put(ifIndex, curr);
+
+            // Update the accumulated tether stats delta to the stats provider for the service
+            // querying.
+            if (mStatsProvider != null) {
+                try {
+                    mStatsProvider.accumulateDiff(
+                            buildNetworkStats(StatsType.STATS_PER_IFACE, ifIndex, diff),
+                            buildNetworkStats(StatsType.STATS_PER_UID, ifIndex, diff));
+                } catch (ArrayIndexOutOfBoundsException e) {
+                    Log.wtf("Fail to update the accumulated stats delta for interface index "
+                            + ifIndex + " : ", e);
+                }
+            }
+        }
+    }
+
+    private void maybeSchedulePollingStats() {
+        if (!mStarted) return;
+
+        if (mHandler.hasCallbacks(mScheduledPollingTask)) {
+            mHandler.removeCallbacks(mScheduledPollingTask);
+        }
+
+        mHandler.postDelayed(mScheduledPollingTask, mDeps.getPerformPollInterval());
+    }
+}
diff --git a/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java b/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java
new file mode 100644
index 0000000..160a166
--- /dev/null
+++ b/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.networkstack.tethering;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.IpPrefix;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.ip.IpServer;
+import android.net.util.PrefixUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * This class coordinate IP addresses conflict problem.
+ *
+ * Tethering downstream IP addresses may conflict with network assigned addresses. This
+ * coordinator is responsible for recording all of network assigned addresses and dispatched
+ * free address to downstream interfaces.
+ *
+ * This class is not thread-safe and should be accessed on the same tethering internal thread.
+ * @hide
+ */
+public class PrivateAddressCoordinator {
+    public static final int PREFIX_LENGTH = 24;
+
+    private static final int MAX_UBYTE = 256;
+    private static final int BYTE_MASK = 0xff;
+    // reserved for bluetooth tethering.
+    private static final int BLUETOOTH_RESERVED = 44;
+    private static final byte DEFAULT_ID = (byte) 42;
+
+    // Upstream monitor would be stopped when tethering is down. When tethering restart, downstream
+    // address may be requested before coordinator get current upstream notification. To ensure
+    // coordinator do not select conflict downstream prefix, mUpstreamPrefixMap would not be cleared
+    // when tethering is down. Instead coordinator would remove all depcreted upstreams from
+    // mUpstreamPrefixMap when tethering is starting. See #maybeRemoveDeprectedUpstreams().
+    private final ArrayMap<Network, List<IpPrefix>> mUpstreamPrefixMap;
+    private final ArraySet<IpServer> mDownstreams;
+    // IANA has reserved the following three blocks of the IP address space for private intranets:
+    // 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16
+    // Tethering use 192.168.0.0/16 that has 256 contiguous class C network numbers.
+    private static final String DEFAULT_TETHERING_PREFIX = "192.168.0.0/16";
+    private final IpPrefix mTetheringPrefix;
+    private final ConnectivityManager mConnectivityMgr;
+
+    public PrivateAddressCoordinator(Context context) {
+        mDownstreams = new ArraySet<>();
+        mUpstreamPrefixMap = new ArrayMap<>();
+        mTetheringPrefix = new IpPrefix(DEFAULT_TETHERING_PREFIX);
+        mConnectivityMgr = (ConnectivityManager) context.getSystemService(
+                Context.CONNECTIVITY_SERVICE);
+    }
+
+    /**
+     * Record a new upstream IpPrefix which may conflict with tethering downstreams.
+     * The downstreams will be notified if a conflict is found.
+     */
+    public void updateUpstreamPrefix(final Network network, final LinkProperties lp) {
+        final ArrayList<IpPrefix> ipv4Prefixes = getIpv4Prefixes(lp.getAllLinkAddresses());
+        if (ipv4Prefixes.isEmpty()) {
+            removeUpstreamPrefix(network);
+            return;
+        }
+
+        mUpstreamPrefixMap.put(network, ipv4Prefixes);
+        handleMaybePrefixConflict(ipv4Prefixes);
+    }
+
+    private ArrayList<IpPrefix> getIpv4Prefixes(final List<LinkAddress> linkAddresses) {
+        final ArrayList<IpPrefix> list = new ArrayList<>();
+        for (LinkAddress address : linkAddresses) {
+            if (!address.isIpv4()) continue;
+
+            list.add(PrefixUtils.asIpPrefix(address));
+        }
+
+        return list;
+    }
+
+    private void handleMaybePrefixConflict(final List<IpPrefix> prefixes) {
+        for (IpServer downstream : mDownstreams) {
+            final IpPrefix target = getDownstreamPrefix(downstream);
+            if (target == null) continue;
+
+            for (IpPrefix source : prefixes) {
+                if (isConflictPrefix(source, target)) {
+                    downstream.sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
+                    break;
+                }
+            }
+        }
+    }
+
+    /** Remove IpPrefix records corresponding to input network. */
+    public void removeUpstreamPrefix(final Network network) {
+        mUpstreamPrefixMap.remove(network);
+    }
+
+    private void maybeRemoveDeprectedUpstreams() {
+        if (!mDownstreams.isEmpty() || mUpstreamPrefixMap.isEmpty()) return;
+
+        final ArrayList<Network> toBeRemoved = new ArrayList<>();
+        List<Network> allNetworks = Arrays.asList(mConnectivityMgr.getAllNetworks());
+        for (int i = 0; i < mUpstreamPrefixMap.size(); i++) {
+            final Network network = mUpstreamPrefixMap.keyAt(i);
+            if (!allNetworks.contains(network)) toBeRemoved.add(network);
+        }
+
+        mUpstreamPrefixMap.removeAll(toBeRemoved);
+    }
+
+    /**
+     * Pick a random available address and mark its prefix as in use for the provided IpServer,
+     * returns null if there is no available address.
+     */
+    @Nullable
+    public LinkAddress requestDownstreamAddress(final IpServer ipServer) {
+        maybeRemoveDeprectedUpstreams();
+
+        // Address would be 192.168.[subAddress]/24.
+        final byte[] bytes = mTetheringPrefix.getRawAddress();
+        final int subAddress = getRandomSubAddr();
+        final int subNet = (subAddress >> 8) & BYTE_MASK;
+        bytes[3] = getSanitizedAddressSuffix(subAddress, (byte) 0, (byte) 1, (byte) 0xff);
+        for (int i = 0; i < MAX_UBYTE; i++) {
+            final int newSubNet = (subNet + i) & BYTE_MASK;
+            if (newSubNet == BLUETOOTH_RESERVED) continue;
+
+            bytes[2] = (byte) newSubNet;
+            final InetAddress addr;
+            try {
+                addr = InetAddress.getByAddress(bytes);
+            } catch (UnknownHostException e) {
+                throw new IllegalStateException("Invalid address, shouldn't happen.", e);
+            }
+
+            final IpPrefix prefix = new IpPrefix(addr, PREFIX_LENGTH);
+            // Check whether this prefix is in use.
+            if (isDownstreamPrefixInUse(prefix)) continue;
+            // Check whether this prefix is conflict with any current upstream network.
+            if (isConflictWithUpstream(prefix)) continue;
+
+            mDownstreams.add(ipServer);
+            return new LinkAddress(addr, PREFIX_LENGTH);
+        }
+
+        // No available address.
+        return null;
+    }
+
+    /** Get random sub address value. Return value is in 0 ~ 0xffff. */
+    @VisibleForTesting
+    public int getRandomSubAddr() {
+        return ((new Random()).nextInt()) & 0xffff; // subNet is in 0 ~ 0xffff.
+    }
+
+    private byte getSanitizedAddressSuffix(final int source, byte... excluded) {
+        final byte subId = (byte) (source & BYTE_MASK);
+        for (byte value : excluded) {
+            if (subId == value) return DEFAULT_ID;
+        }
+
+        return subId;
+    }
+
+    /** Release downstream record for IpServer. */
+    public void releaseDownstream(final IpServer ipServer) {
+        mDownstreams.remove(ipServer);
+    }
+
+    /** Clear current upstream prefixes records. */
+    public void clearUpstreamPrefixes() {
+        mUpstreamPrefixMap.clear();
+    }
+
+    private boolean isConflictWithUpstream(final IpPrefix source) {
+        for (int i = 0; i < mUpstreamPrefixMap.size(); i++) {
+            final List<IpPrefix> list = mUpstreamPrefixMap.valueAt(i);
+            for (IpPrefix target : list) {
+                if (isConflictPrefix(source, target)) return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean isConflictPrefix(final IpPrefix prefix1, final IpPrefix prefix2) {
+        if (prefix2.getPrefixLength() < prefix1.getPrefixLength()) {
+            return prefix2.contains(prefix1.getAddress());
+        }
+
+        return prefix1.contains(prefix2.getAddress());
+    }
+
+    private boolean isDownstreamPrefixInUse(final IpPrefix source) {
+        // This class always generates downstream prefixes with the same prefix length, so
+        // prefixes cannot be contained in each other. They can only be equal to each other.
+        for (IpServer downstream : mDownstreams) {
+            final IpPrefix prefix = getDownstreamPrefix(downstream);
+            if (source.equals(prefix)) return true;
+        }
+        return false;
+    }
+
+    private IpPrefix getDownstreamPrefix(final IpServer downstream) {
+        final LinkAddress address = downstream.getAddress();
+        if (address == null) return null;
+
+        return PrefixUtils.asIpPrefix(address);
+    }
+
+    void dump(final IndentingPrintWriter pw) {
+        pw.decreaseIndent();
+        pw.println("mUpstreamPrefixMap:");
+        pw.increaseIndent();
+        for (int i = 0; i < mUpstreamPrefixMap.size(); i++) {
+            pw.println(mUpstreamPrefixMap.keyAt(i) + " - " + mUpstreamPrefixMap.valueAt(i));
+        }
+        pw.decreaseIndent();
+        pw.println("mDownstreams:");
+        pw.increaseIndent();
+        for (IpServer ipServer : mDownstreams) {
+            pw.println(ipServer.interfaceType() + " - " + ipServer.getAddress());
+        }
+        pw.decreaseIndent();
+    }
+}
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 3ce3b45..00723ac 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -207,6 +207,7 @@
             new SparseArray<>();
 
     // used to synchronize public access to members
+    // TODO(b/153621704): remove mPublicSync to make Tethering lock free
     private final Object mPublicSync;
     private final Context mContext;
     private final ArrayMap<String, TetherState> mTetherStates;
@@ -231,6 +232,8 @@
     private final TetheringThreadExecutor mExecutor;
     private final TetheringNotificationUpdater mNotificationUpdater;
     private final UserManager mUserManager;
+    private final BpfCoordinator mBpfCoordinator;
+    private final PrivateAddressCoordinator mPrivateAddressCoordinator;
     private int mActiveDataSubId = INVALID_SUBSCRIPTION_ID;
     // All the usage of mTetheringEventCallback should run in the same thread.
     private ITetheringEventCallback mTetheringEventCallback = null;
@@ -282,6 +285,8 @@
         mUpstreamNetworkMonitor = mDeps.getUpstreamNetworkMonitor(mContext, mTetherMasterSM, mLog,
                 TetherMasterSM.EVENT_UPSTREAM_CALLBACK);
         mForwardedDownstreams = new LinkedHashSet<>();
+        mBpfCoordinator = mDeps.getBpfCoordinator(
+                mHandler, mNetd, mLog, new BpfCoordinator.Dependencies());
 
         IntentFilter filter = new IntentFilter();
         filter.addAction(ACTION_CARRIER_CONFIG_CHANGED);
@@ -314,6 +319,7 @@
         mExecutor = new TetheringThreadExecutor(mHandler);
         mActiveDataSubIdListener = new ActiveDataSubIdListener(mExecutor);
         mNetdCallback = new NetdCallback();
+        mPrivateAddressCoordinator = new PrivateAddressCoordinator(mContext);
 
         // Load tethering configuration.
         updateConfiguration();
@@ -1616,6 +1622,14 @@
             }
         }
 
+        private void addUpstreamPrefixes(final UpstreamNetworkState ns) {
+            mPrivateAddressCoordinator.updateUpstreamPrefix(ns.network, ns.linkProperties);
+        }
+
+        private void removeUpstreamPrefixes(final UpstreamNetworkState ns) {
+            mPrivateAddressCoordinator.removeUpstreamPrefix(ns.network);
+        }
+
         @VisibleForTesting
         void handleUpstreamNetworkMonitorCallback(int arg1, Object o) {
             if (arg1 == UpstreamNetworkMonitor.NOTIFY_LOCAL_PREFIXES) {
@@ -1624,6 +1638,14 @@
             }
 
             final UpstreamNetworkState ns = (UpstreamNetworkState) o;
+            switch (arg1) {
+                case UpstreamNetworkMonitor.EVENT_ON_LINKPROPERTIES:
+                    addUpstreamPrefixes(ns);
+                    break;
+                case UpstreamNetworkMonitor.EVENT_ON_LOST:
+                    removeUpstreamPrefixes(ns);
+                    break;
+            }
 
             if (ns == null || !pertainsToCurrentUpstream(ns)) {
                 // TODO: In future, this is where upstream evaluation and selection
@@ -1685,6 +1707,9 @@
                     chooseUpstreamType(true);
                     mTryCell = false;
                 }
+
+                // TODO: Check the upstream interface if it is managed by BPF offload.
+                mBpfCoordinator.start();
             }
 
             @Override
@@ -1697,6 +1722,7 @@
                     mTetherUpstream = null;
                     reportUpstreamChanged(null);
                 }
+                mBpfCoordinator.stop();
             }
 
             private boolean updateUpstreamWanted() {
@@ -2190,6 +2216,11 @@
         mOffloadController.dump(pw);
         pw.decreaseIndent();
 
+        pw.println("Private address coordinator:");
+        pw.increaseIndent();
+        mPrivateAddressCoordinator.dump(pw);
+        pw.decreaseIndent();
+
         pw.println("Log:");
         pw.increaseIndent();
         if (argsContain(args, "--short")) {
@@ -2231,6 +2262,11 @@
             public void dhcpLeasesChanged() {
                 updateConnectedClients(null /* wifiClients */);
             }
+
+            @Override
+            public void requestEnableTethering(int tetheringType, boolean enabled) {
+                enableTetheringInternal(tetheringType, enabled, null);
+            }
         };
     }
 
@@ -2312,9 +2348,10 @@
 
         mLog.log("adding TetheringInterfaceStateMachine for: " + iface);
         final TetherState tetherState = new TetherState(
-                new IpServer(iface, mLooper, interfaceType, mLog, mNetd,
+                new IpServer(iface, mLooper, interfaceType, mLog, mNetd, mBpfCoordinator,
                              makeControlCallback(), mConfig.enableLegacyDhcpServer,
-                             mConfig.enableBpfOffload, mDeps.getIpServerDependencies()));
+                             mConfig.enableBpfOffload, mPrivateAddressCoordinator,
+                             mDeps.getIpServerDependencies()));
         mTetherStates.put(iface, tetherState);
         tetherState.ipServer.start();
     }
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
index ce546c7..d637c86 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
@@ -41,6 +41,17 @@
  */
 public abstract class TetheringDependencies {
     /**
+     * Get a reference to the BpfCoordinator to be used by tethering.
+     */
+    public @NonNull BpfCoordinator getBpfCoordinator(
+            @NonNull Handler handler, @NonNull INetd netd, @NonNull SharedLog log,
+            @NonNull BpfCoordinator.Dependencies deps) {
+        final NetworkStatsManager statsManager =
+                (NetworkStatsManager) getContext().getSystemService(Context.NETWORK_STATS_SERVICE);
+        return new BpfCoordinator(handler, netd, statsManager, log, deps);
+    }
+
+    /**
      * Get a reference to the offload hardware interface to be used by tethering.
      */
     public OffloadHardwareInterface getOffloadHardwareInterface(Handler h, SharedLog log) {
diff --git a/Tethering/tests/integration/Android.bp b/Tethering/tests/integration/Android.bp
index 3305ed0..ed69b7d 100644
--- a/Tethering/tests/integration/Android.bp
+++ b/Tethering/tests/integration/Android.bp
@@ -63,7 +63,6 @@
 // NetworkStackTests.
 android_test {
     name: "TetheringCoverageTests",
-    certificate: "platform",
     platform_apis: true,
     test_suites: ["device-tests", "mts"],
     test_config: "AndroidTest_Coverage.xml",
diff --git a/Tethering/tests/unit/Android.bp b/Tethering/tests/unit/Android.bp
index 9b7d683..1572768 100644
--- a/Tethering/tests/unit/Android.bp
+++ b/Tethering/tests/unit/Android.bp
@@ -29,7 +29,7 @@
     sdk_version: "core_platform",
     libs: [
         "framework-minus-apex",
-        "framework-tethering",
+        "framework-tethering.impl",
     ],
     visibility: ["//cts/tests/tests/tethering"],
 }
@@ -59,7 +59,7 @@
         "ext",
         "framework-minus-apex",
         "framework-res",
-        "framework-tethering",
+        "framework-tethering.impl",
     ],
     jni_libs: [
         // For mockito extended
@@ -82,7 +82,7 @@
 
 android_test {
     name: "TetheringTests",
-    certificate: "platform",
+    platform_apis: true,
     test_suites: [
         "device-tests",
         "mts",
diff --git a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index 307ebf1..433aacf 100644
--- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -78,6 +78,7 @@
 import android.net.ip.RouterAdvertisementDaemon.RaParams;
 import android.net.util.InterfaceParams;
 import android.net.util.InterfaceSet;
+import android.net.util.PrefixUtils;
 import android.net.util.SharedLog;
 import android.os.RemoteException;
 import android.os.test.TestLooper;
@@ -86,6 +87,9 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.networkstack.tethering.BpfCoordinator;
+import com.android.networkstack.tethering.PrivateAddressCoordinator;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -109,7 +113,7 @@
     private static final String UPSTREAM_IFACE2 = "upstream1";
     private static final int UPSTREAM_IFINDEX = 101;
     private static final int UPSTREAM_IFINDEX2 = 102;
-    private static final String BLUETOOTH_IFACE_ADDR = "192.168.42.1";
+    private static final String BLUETOOTH_IFACE_ADDR = "192.168.44.1";
     private static final int BLUETOOTH_DHCP_PREFIX_LENGTH = 24;
     private static final int DHCP_LEASE_TIME_SECS = 3600;
     private static final boolean DEFAULT_USING_BPF_OFFLOAD = true;
@@ -119,13 +123,18 @@
 
     private static final int MAKE_DHCPSERVER_TIMEOUT_MS = 1000;
 
+    private final LinkAddress mTestAddress = new LinkAddress("192.168.42.5/24");
+    private final IpPrefix mBluetoothPrefix = new IpPrefix("192.168.44.0/24");
+
     @Mock private INetd mNetd;
+    @Mock private BpfCoordinator mBpfCoordinator;
     @Mock private IpServer.Callback mCallback;
     @Mock private SharedLog mSharedLog;
     @Mock private IDhcpServer mDhcpServer;
     @Mock private RouterAdvertisementDaemon mRaDaemon;
     @Mock private IpNeighborMonitor mIpNeighborMonitor;
     @Mock private IpServer.Dependencies mDependencies;
+    @Mock private PrivateAddressCoordinator mAddressCoordinator;
 
     @Captor private ArgumentCaptor<DhcpServingParamsParcel> mDhcpParamsCaptor;
 
@@ -172,8 +181,8 @@
                 neighborCaptor.capture());
 
         mIpServer = new IpServer(
-                IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog, mNetd,
-                mCallback, usingLegacyDhcp, usingBpfOffload, mDependencies);
+                IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog, mNetd, mBpfCoordinator,
+                mCallback, usingLegacyDhcp, usingBpfOffload, mAddressCoordinator, mDependencies);
         mIpServer.start();
         mNeighborEventConsumer = neighborCaptor.getValue();
 
@@ -200,12 +209,14 @@
             lp.setInterfaceName(upstreamIface);
             dispatchTetherConnectionChanged(upstreamIface, lp, 0);
         }
-        reset(mNetd, mCallback);
+        reset(mNetd, mCallback, mAddressCoordinator);
+        when(mAddressCoordinator.requestDownstreamAddress(any())).thenReturn(mTestAddress);
     }
 
     @Before public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         when(mSharedLog.forSubComponent(anyString())).thenReturn(mSharedLog);
+        when(mAddressCoordinator.requestDownstreamAddress(any())).thenReturn(mTestAddress);
     }
 
     @Test
@@ -213,8 +224,8 @@
         when(mDependencies.getIpNeighborMonitor(any(), any(), any()))
                 .thenReturn(mIpNeighborMonitor);
         mIpServer = new IpServer(IFACE_NAME, mLooper.getLooper(), TETHERING_BLUETOOTH, mSharedLog,
-                mNetd, mCallback, false /* usingLegacyDhcp */, DEFAULT_USING_BPF_OFFLOAD,
-                mDependencies);
+                mNetd, mBpfCoordinator, mCallback, false /* usingLegacyDhcp */,
+                DEFAULT_USING_BPF_OFFLOAD, mAddressCoordinator, mDependencies);
         mIpServer.start();
         mLooper.dispatchAll();
         verify(mCallback).updateInterfaceState(
@@ -277,16 +288,17 @@
         initTetheredStateMachine(TETHERING_BLUETOOTH, null);
 
         dispatchCommand(IpServer.CMD_TETHER_UNREQUESTED);
-        InOrder inOrder = inOrder(mNetd, mCallback);
+        InOrder inOrder = inOrder(mCallback, mNetd, mAddressCoordinator);
         inOrder.verify(mNetd).tetherApplyDnsInterfaces();
         inOrder.verify(mNetd).tetherInterfaceRemove(IFACE_NAME);
         inOrder.verify(mNetd).networkRemoveInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
         inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg -> IFACE_NAME.equals(cfg.ifName)));
+        inOrder.verify(mAddressCoordinator).releaseDownstream(any());
         inOrder.verify(mCallback).updateInterfaceState(
                 mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
         inOrder.verify(mCallback).updateLinkProperties(
                 eq(mIpServer), any(LinkProperties.class));
-        verifyNoMoreInteractions(mNetd, mCallback);
+        verifyNoMoreInteractions(mNetd, mCallback, mAddressCoordinator);
     }
 
     @Test
@@ -294,7 +306,8 @@
         initStateMachine(TETHERING_USB);
 
         dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED);
-        InOrder inOrder = inOrder(mCallback, mNetd);
+        InOrder inOrder = inOrder(mCallback, mNetd, mAddressCoordinator);
+        inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any());
         inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg ->
                   IFACE_NAME.equals(cfg.ifName) && assertContainsFlag(cfg.flags, IF_STATE_UP)));
         inOrder.verify(mNetd).tetherInterfaceAdd(IFACE_NAME);
@@ -306,7 +319,7 @@
         inOrder.verify(mCallback).updateLinkProperties(
                 eq(mIpServer), mLinkPropertiesCaptor.capture());
         assertIPv4AddressAndDirectlyConnectedRoute(mLinkPropertiesCaptor.getValue());
-        verifyNoMoreInteractions(mNetd, mCallback);
+        verifyNoMoreInteractions(mNetd, mCallback, mAddressCoordinator);
     }
 
     @Test
@@ -314,7 +327,8 @@
         initStateMachine(TETHERING_WIFI_P2P);
 
         dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_LOCAL_ONLY);
-        InOrder inOrder = inOrder(mCallback, mNetd);
+        InOrder inOrder = inOrder(mCallback, mNetd, mAddressCoordinator);
+        inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any());
         inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg ->
                   IFACE_NAME.equals(cfg.ifName) && assertNotContainsFlag(cfg.flags, IF_STATE_UP)));
         inOrder.verify(mNetd).tetherInterfaceAdd(IFACE_NAME);
@@ -326,7 +340,7 @@
         inOrder.verify(mCallback).updateLinkProperties(
                 eq(mIpServer), mLinkPropertiesCaptor.capture());
         assertIPv4AddressAndDirectlyConnectedRoute(mLinkPropertiesCaptor.getValue());
-        verifyNoMoreInteractions(mNetd, mCallback);
+        verifyNoMoreInteractions(mNetd, mCallback, mAddressCoordinator);
     }
 
     @Test
@@ -392,18 +406,19 @@
         initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE);
 
         dispatchCommand(IpServer.CMD_TETHER_UNREQUESTED);
-        InOrder inOrder = inOrder(mNetd, mCallback);
+        InOrder inOrder = inOrder(mNetd, mCallback, mAddressCoordinator);
         inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
         inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE);
         inOrder.verify(mNetd).tetherApplyDnsInterfaces();
         inOrder.verify(mNetd).tetherInterfaceRemove(IFACE_NAME);
         inOrder.verify(mNetd).networkRemoveInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
         inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg -> IFACE_NAME.equals(cfg.ifName)));
+        inOrder.verify(mAddressCoordinator).releaseDownstream(any());
         inOrder.verify(mCallback).updateInterfaceState(
                 mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
         inOrder.verify(mCallback).updateLinkProperties(
                 eq(mIpServer), any(LinkProperties.class));
-        verifyNoMoreInteractions(mNetd, mCallback);
+        verifyNoMoreInteractions(mNetd, mCallback, mAddressCoordinator);
     }
 
     @Test
@@ -483,7 +498,7 @@
         initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE);
         dispatchTetherConnectionChanged(UPSTREAM_IFACE);
 
-        assertDhcpStarted(new IpPrefix("192.168.43.0/24"));
+        assertDhcpStarted(PrefixUtils.asIpPrefix(mTestAddress));
     }
 
     @Test
@@ -491,7 +506,7 @@
         initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE);
         dispatchTetherConnectionChanged(UPSTREAM_IFACE);
 
-        assertDhcpStarted(new IpPrefix("192.168.44.0/24"));
+        assertDhcpStarted(mBluetoothPrefix);
     }
 
     @Test
@@ -499,7 +514,7 @@
         initTetheredStateMachine(TETHERING_WIFI_P2P, UPSTREAM_IFACE);
         dispatchTetherConnectionChanged(UPSTREAM_IFACE);
 
-        assertDhcpStarted(new IpPrefix("192.168.49.0/24"));
+        assertDhcpStarted(PrefixUtils.asIpPrefix(mTestAddress));
     }
 
     @Test
@@ -524,21 +539,27 @@
         eventCallbacks = dhcpEventCbsCaptor.getValue();
         assertDhcpStarted(new IpPrefix("192.168.42.0/24"));
 
-        // Simulate the DHCP server receives DHCPDECLINE on MirrorLink and then signals
-        // onNewPrefixRequest callback.
-        eventCallbacks.onNewPrefixRequest(new IpPrefix("192.168.42.0/24"));
-        mLooper.dispatchAll();
-
         final ArgumentCaptor<LinkProperties> lpCaptor =
                 ArgumentCaptor.forClass(LinkProperties.class);
-        InOrder inOrder = inOrder(mNetd, mCallback);
-        inOrder.verify(mCallback).updateInterfaceState(
-                mIpServer, STATE_LOCAL_ONLY, TETHER_ERROR_NO_ERROR);
-        inOrder.verify(mCallback).updateLinkProperties(eq(mIpServer), lpCaptor.capture());
+        InOrder inOrder = inOrder(mNetd, mCallback, mAddressCoordinator);
+        inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any());
         inOrder.verify(mNetd).networkAddInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
         // One for ipv4 route, one for ipv6 link local route.
         inOrder.verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(IFACE_NAME),
                 any(), any());
+        inOrder.verify(mCallback).updateInterfaceState(
+                mIpServer, STATE_LOCAL_ONLY, TETHER_ERROR_NO_ERROR);
+        inOrder.verify(mCallback).updateLinkProperties(eq(mIpServer), lpCaptor.capture());
+        verifyNoMoreInteractions(mCallback, mAddressCoordinator);
+
+        // Simulate the DHCP server receives DHCPDECLINE on MirrorLink and then signals
+        // onNewPrefixRequest callback.
+        final LinkAddress newAddress = new LinkAddress("192.168.100.125/24");
+        when(mAddressCoordinator.requestDownstreamAddress(any())).thenReturn(newAddress);
+        eventCallbacks.onNewPrefixRequest(new IpPrefix("192.168.42.0/24"));
+        mLooper.dispatchAll();
+
+        inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any());
         inOrder.verify(mNetd).tetherApplyDnsInterfaces();
         inOrder.verify(mCallback).updateLinkProperties(eq(mIpServer), lpCaptor.capture());
         verifyNoMoreInteractions(mCallback);
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
new file mode 100644
index 0000000..b029b43
--- /dev/null
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.networkstack.tethering;
+
+import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
+import static android.net.NetworkStats.METERED_NO;
+import static android.net.NetworkStats.ROAMING_NO;
+import static android.net.NetworkStats.SET_DEFAULT;
+import static android.net.NetworkStats.TAG_NONE;
+import static android.net.NetworkStats.UID_ALL;
+import static android.net.NetworkStats.UID_TETHERING;
+
+import static com.android.networkstack.tethering.BpfCoordinator
+        .DEFAULT_PERFORM_POLL_INTERVAL_MS;
+import static com.android.networkstack.tethering.BpfCoordinator.StatsType;
+import static com.android.networkstack.tethering.BpfCoordinator.StatsType.STATS_PER_IFACE;
+import static com.android.networkstack.tethering.BpfCoordinator.StatsType.STATS_PER_UID;
+
+import static junit.framework.Assert.assertNotNull;
+
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.annotation.NonNull;
+import android.app.usage.NetworkStatsManager;
+import android.net.INetd;
+import android.net.NetworkStats;
+import android.net.TetherStatsParcel;
+import android.net.util.SharedLog;
+import android.os.Handler;
+import android.os.test.TestLooper;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.testutils.TestableNetworkStatsProviderCbBinder;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class BpfCoordinatorTest {
+    @Mock private NetworkStatsManager mStatsManager;
+    @Mock private INetd mNetd;
+    // Late init since methods must be called by the thread that created this object.
+    private TestableNetworkStatsProviderCbBinder mTetherStatsProviderCb;
+    private BpfCoordinator.BpfTetherStatsProvider mTetherStatsProvider;
+    private final ArgumentCaptor<ArrayList> mStringArrayCaptor =
+            ArgumentCaptor.forClass(ArrayList.class);
+    private final TestLooper mTestLooper = new TestLooper();
+    private BpfCoordinator.Dependencies mDeps =
+            new BpfCoordinator.Dependencies() {
+            @Override
+            int getPerformPollInterval() {
+                return DEFAULT_PERFORM_POLL_INTERVAL_MS;
+            }
+    };
+
+    @Before public void setUp() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    private void waitForIdle() {
+        mTestLooper.dispatchAll();
+    }
+
+    private void setupFunctioningNetdInterface() throws Exception {
+        when(mNetd.tetherOffloadGetStats()).thenReturn(new TetherStatsParcel[0]);
+    }
+
+    @NonNull
+    private BpfCoordinator makeBpfCoordinator() throws Exception {
+        BpfCoordinator coordinator = new BpfCoordinator(
+                new Handler(mTestLooper.getLooper()), mNetd, mStatsManager, new SharedLog("test"),
+                mDeps);
+        final ArgumentCaptor<BpfCoordinator.BpfTetherStatsProvider>
+                tetherStatsProviderCaptor =
+                ArgumentCaptor.forClass(BpfCoordinator.BpfTetherStatsProvider.class);
+        verify(mStatsManager).registerNetworkStatsProvider(anyString(),
+                tetherStatsProviderCaptor.capture());
+        mTetherStatsProvider = tetherStatsProviderCaptor.getValue();
+        assertNotNull(mTetherStatsProvider);
+        mTetherStatsProviderCb = new TestableNetworkStatsProviderCbBinder();
+        mTetherStatsProvider.setProviderCallbackBinder(mTetherStatsProviderCb);
+        return coordinator;
+    }
+
+    @NonNull
+    private static NetworkStats.Entry buildTestEntry(@NonNull StatsType how,
+            @NonNull String iface, long rxBytes, long rxPackets, long txBytes, long txPackets) {
+        return new NetworkStats.Entry(iface, how == STATS_PER_IFACE ? UID_ALL : UID_TETHERING,
+                SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, rxBytes,
+                rxPackets, txBytes, txPackets, 0L);
+    }
+
+    @NonNull
+    private static TetherStatsParcel buildTestTetherStatsParcel(@NonNull Integer ifIndex,
+            long rxBytes, long rxPackets, long txBytes, long txPackets) {
+        final TetherStatsParcel parcel = new TetherStatsParcel();
+        parcel.ifIndex = ifIndex;
+        parcel.rxBytes = rxBytes;
+        parcel.rxPackets = rxPackets;
+        parcel.txBytes = txBytes;
+        parcel.txPackets = txPackets;
+        return parcel;
+    }
+
+    private void setTetherOffloadStatsList(TetherStatsParcel[] tetherStatsList) throws Exception {
+        when(mNetd.tetherOffloadGetStats()).thenReturn(tetherStatsList);
+        mTestLooper.moveTimeForward(DEFAULT_PERFORM_POLL_INTERVAL_MS);
+        waitForIdle();
+    }
+
+    @Test
+    public void testGetForwardedStats() throws Exception {
+        setupFunctioningNetdInterface();
+
+        final BpfCoordinator coordinator = makeBpfCoordinator();
+        coordinator.start();
+
+        final String wlanIface = "wlan0";
+        final Integer wlanIfIndex = 100;
+        final String mobileIface = "rmnet_data0";
+        final Integer mobileIfIndex = 101;
+
+        // Add interface name to lookup table. In realistic case, the upstream interface name will
+        // be added by IpServer when IpServer has received with a new IPv6 upstream update event.
+        coordinator.addUpstreamNameToLookupTable(wlanIfIndex, wlanIface);
+        coordinator.addUpstreamNameToLookupTable(mobileIfIndex, mobileIface);
+
+        // [1] Both interface stats are changed.
+        // Setup the tether stats of wlan and mobile interface. Note that move forward the time of
+        // the looper to make sure the new tether stats has been updated by polling update thread.
+        setTetherOffloadStatsList(new TetherStatsParcel[] {
+                buildTestTetherStatsParcel(wlanIfIndex, 1000, 100, 2000, 200),
+                buildTestTetherStatsParcel(mobileIfIndex, 3000, 300, 4000, 400)});
+
+        final NetworkStats expectedIfaceStats = new NetworkStats(0L, 2)
+                .addEntry(buildTestEntry(STATS_PER_IFACE, wlanIface, 1000, 100, 2000, 200))
+                .addEntry(buildTestEntry(STATS_PER_IFACE, mobileIface, 3000, 300, 4000, 400));
+
+        final NetworkStats expectedUidStats = new NetworkStats(0L, 2)
+                .addEntry(buildTestEntry(STATS_PER_UID, wlanIface, 1000, 100, 2000, 200))
+                .addEntry(buildTestEntry(STATS_PER_UID, mobileIface, 3000, 300, 4000, 400));
+
+        // Force pushing stats update to verify the stats reported.
+        // TODO: Perhaps make #expectNotifyStatsUpdated to use test TetherStatsParcel object for
+        // verifying the notification.
+        mTetherStatsProvider.pushTetherStats();
+        mTetherStatsProviderCb.expectNotifyStatsUpdated(expectedIfaceStats, expectedUidStats);
+
+        // [2] Only one interface stats is changed.
+        // The tether stats of mobile interface is accumulated and The tether stats of wlan
+        // interface is the same.
+        setTetherOffloadStatsList(new TetherStatsParcel[] {
+                buildTestTetherStatsParcel(wlanIfIndex, 1000, 100, 2000, 200),
+                buildTestTetherStatsParcel(mobileIfIndex, 3010, 320, 4030, 440)});
+
+        final NetworkStats expectedIfaceStatsDiff = new NetworkStats(0L, 2)
+                .addEntry(buildTestEntry(STATS_PER_IFACE, wlanIface, 0, 0, 0, 0))
+                .addEntry(buildTestEntry(STATS_PER_IFACE, mobileIface, 10, 20, 30, 40));
+
+        final NetworkStats expectedUidStatsDiff = new NetworkStats(0L, 2)
+                .addEntry(buildTestEntry(STATS_PER_UID, wlanIface, 0, 0, 0, 0))
+                .addEntry(buildTestEntry(STATS_PER_UID, mobileIface, 10, 20, 30, 40));
+
+        // Force pushing stats update to verify that only diff of stats is reported.
+        mTetherStatsProvider.pushTetherStats();
+        mTetherStatsProviderCb.expectNotifyStatsUpdated(expectedIfaceStatsDiff,
+                expectedUidStatsDiff);
+
+        // [3] Stop coordinator.
+        // Shutdown the coordinator and clear the invocation history, especially the
+        // tetherOffloadGetStats() calls.
+        coordinator.stop();
+        clearInvocations(mNetd);
+
+        // Verify the polling update thread stopped.
+        mTestLooper.moveTimeForward(DEFAULT_PERFORM_POLL_INTERVAL_MS);
+        waitForIdle();
+        verify(mNetd, never()).tetherOffloadGetStats();
+    }
+}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java
new file mode 100644
index 0000000..93efd49
--- /dev/null
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.networkstack.tethering;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.InetAddresses;
+import android.net.IpPrefix;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.ip.IpServer;
+import android.net.util.NetworkConstants;
+import android.net.util.PrefixUtils;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class PrivateAddressCoordinatorTest {
+    private static final String TEST_MOBILE_IFNAME = "test_rmnet_data0";
+    private static final String TEST_WIFI_IFNAME = "test_wlan0";
+
+    @Mock private IpServer mHotspotIpServer;
+    @Mock private IpServer mUsbIpServer;
+    @Mock private IpServer mEthernetIpServer;
+    @Mock private Context mContext;
+    @Mock private ConnectivityManager mConnectivityMgr;
+
+    private PrivateAddressCoordinator mPrivateAddressCoordinator;
+    private final IpPrefix mBluetoothPrefix = new IpPrefix("192.168.44.0/24");
+    private final Network mWifiNetwork = new Network(1);
+    private final Network mMobileNetwork = new Network(2);
+    private final Network[] mAllNetworks = {mMobileNetwork, mWifiNetwork};
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        when(mContext.getSystemService(Context.CONNECTIVITY_SERVICE)).thenReturn(mConnectivityMgr);
+        when(mConnectivityMgr.getAllNetworks()).thenReturn(mAllNetworks);
+        mPrivateAddressCoordinator = spy(new PrivateAddressCoordinator(mContext));
+    }
+
+    @Test
+    public void testDownstreamPrefixRequest() throws Exception {
+        LinkAddress address = mPrivateAddressCoordinator.requestDownstreamAddress(
+                mHotspotIpServer);
+        final IpPrefix hotspotPrefix = PrefixUtils.asIpPrefix(address);
+        assertNotEquals(hotspotPrefix, mBluetoothPrefix);
+
+        address = mPrivateAddressCoordinator.requestDownstreamAddress(
+                mHotspotIpServer);
+        final IpPrefix testDupRequest = PrefixUtils.asIpPrefix(address);
+        assertNotEquals(hotspotPrefix, testDupRequest);
+        assertNotEquals(mBluetoothPrefix, testDupRequest);
+        mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
+
+        address = mPrivateAddressCoordinator.requestDownstreamAddress(
+                mUsbIpServer);
+        final IpPrefix usbPrefix = PrefixUtils.asIpPrefix(address);
+        assertNotEquals(usbPrefix, mBluetoothPrefix);
+        assertNotEquals(usbPrefix, hotspotPrefix);
+        mPrivateAddressCoordinator.releaseDownstream(mUsbIpServer);
+    }
+
+    @Test
+    public void testRequestDownstreamAddress() throws Exception {
+        LinkAddress expectedAddress = new LinkAddress("192.168.43.42/24");
+        int fakeSubAddr = 0x2b00;
+        when(mPrivateAddressCoordinator.getRandomSubAddr()).thenReturn(fakeSubAddr);
+        LinkAddress actualAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
+                mHotspotIpServer);
+        assertEquals(actualAddress, expectedAddress);
+        mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
+
+        fakeSubAddr = 0x2b01;
+        when(mPrivateAddressCoordinator.getRandomSubAddr()).thenReturn(fakeSubAddr);
+        actualAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
+                mHotspotIpServer);
+        assertEquals(actualAddress, expectedAddress);
+        mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
+
+        fakeSubAddr = 0x2bff;
+        when(mPrivateAddressCoordinator.getRandomSubAddr()).thenReturn(fakeSubAddr);
+        actualAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
+                mHotspotIpServer);
+        assertEquals(actualAddress, expectedAddress);
+        mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
+
+        expectedAddress = new LinkAddress("192.168.43.5/24");
+        fakeSubAddr = 0x2b05;
+        when(mPrivateAddressCoordinator.getRandomSubAddr()).thenReturn(fakeSubAddr);
+        actualAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
+                mHotspotIpServer);
+        assertEquals(actualAddress, expectedAddress);
+        mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
+    }
+
+    @Test
+    public void testReserveBluetoothPrefix() throws Exception {
+        final int fakeSubAddr = 0x2c05;
+        when(mPrivateAddressCoordinator.getRandomSubAddr()).thenReturn(fakeSubAddr);
+        LinkAddress address = mPrivateAddressCoordinator.requestDownstreamAddress(
+                mHotspotIpServer);
+        final IpPrefix hotspotPrefix = PrefixUtils.asIpPrefix(address);
+        assertNotEquals("Should not get reserved prefix: ", mBluetoothPrefix, hotspotPrefix);
+        mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
+    }
+
+    @Test
+    public void testNoConflictDownstreamPrefix() throws Exception {
+        final int fakeHotspotSubAddr = 0x2b05;
+        final IpPrefix predefinedPrefix = new IpPrefix("192.168.43.0/24");
+        when(mPrivateAddressCoordinator.getRandomSubAddr()).thenReturn(fakeHotspotSubAddr);
+        LinkAddress address = mPrivateAddressCoordinator.requestDownstreamAddress(
+                mHotspotIpServer);
+        final IpPrefix hotspotPrefix = PrefixUtils.asIpPrefix(address);
+        assertEquals("Wrong wifi perfix: ", predefinedPrefix, hotspotPrefix);
+        when(mHotspotIpServer.getAddress()).thenReturn(address);
+
+        address = mPrivateAddressCoordinator.requestDownstreamAddress(
+                mUsbIpServer);
+        final IpPrefix usbPrefix = PrefixUtils.asIpPrefix(address);
+        assertNotEquals(predefinedPrefix, usbPrefix);
+
+        mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
+        mPrivateAddressCoordinator.releaseDownstream(mUsbIpServer);
+        address = mPrivateAddressCoordinator.requestDownstreamAddress(
+                mUsbIpServer);
+        final IpPrefix allowUseFreePrefix = PrefixUtils.asIpPrefix(address);
+        assertEquals("Fail to reselect available perfix: ", predefinedPrefix, allowUseFreePrefix);
+    }
+
+    private LinkProperties buildUpstreamLinkProperties(boolean withIPv4, boolean withIPv6,
+            boolean isMobile) {
+        final String testIface;
+        final String testIpv4Address;
+        if (isMobile) {
+            testIface = TEST_MOBILE_IFNAME;
+            testIpv4Address = "10.0.0.1";
+        } else {
+            testIface = TEST_WIFI_IFNAME;
+            testIpv4Address = "192.168.43.5";
+        }
+
+        final LinkProperties prop = new LinkProperties();
+        prop.setInterfaceName(testIface);
+
+        if (withIPv4) {
+            prop.addLinkAddress(
+                    new LinkAddress(InetAddresses.parseNumericAddress(testIpv4Address),
+                            NetworkConstants.IPV4_ADDR_BITS));
+        }
+
+        if (withIPv6) {
+            prop.addLinkAddress(
+                    new LinkAddress(InetAddresses.parseNumericAddress("2001:db8::"),
+                            NetworkConstants.RFC7421_PREFIX_LENGTH));
+        }
+        return prop;
+    }
+
+    @Test
+    public void testNoConflictUpstreamPrefix() throws Exception {
+        final int fakeHotspotSubId = 43;
+        final int fakeHotspotSubAddr = 0x2b05;
+        final IpPrefix predefinedPrefix = new IpPrefix("192.168.43.0/24");
+        // Force always get subAddress "43.5" for conflict testing.
+        when(mPrivateAddressCoordinator.getRandomSubAddr()).thenReturn(fakeHotspotSubAddr);
+        // 1. Enable hotspot with prefix 192.168.43.0/24
+        final LinkAddress hotspotAddr = mPrivateAddressCoordinator.requestDownstreamAddress(
+                mHotspotIpServer);
+        final IpPrefix hotspotPrefix = PrefixUtils.asIpPrefix(hotspotAddr);
+        assertEquals("Wrong wifi perfix: ", predefinedPrefix, hotspotPrefix);
+        when(mHotspotIpServer.getAddress()).thenReturn(hotspotAddr);
+        // 2. Update v6 only mobile network, hotspot prefix should not be removed.
+        List<String> testConflicts;
+        final LinkProperties v6OnlyMobileProp = buildUpstreamLinkProperties(false, true, true);
+        mPrivateAddressCoordinator.updateUpstreamPrefix(mMobileNetwork, v6OnlyMobileProp);
+        verify(mHotspotIpServer, never()).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
+        mPrivateAddressCoordinator.removeUpstreamPrefix(mMobileNetwork);
+        // 3. Update v4 only mobile network, hotspot prefix should not be removed.
+        final LinkProperties v4OnlyMobileProp = buildUpstreamLinkProperties(true, false, true);
+        mPrivateAddressCoordinator.updateUpstreamPrefix(mMobileNetwork, v4OnlyMobileProp);
+        verify(mHotspotIpServer, never()).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
+        // 4. Update v4v6 mobile network, hotspot prefix should not be removed.
+        final LinkProperties v4v6MobileProp = buildUpstreamLinkProperties(true, true, true);
+        mPrivateAddressCoordinator.updateUpstreamPrefix(mMobileNetwork, v4v6MobileProp);
+        verify(mHotspotIpServer, never()).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
+        // 5. Update v6 only wifi network, hotspot prefix should not be removed.
+        final LinkProperties v6OnlyWifiProp = buildUpstreamLinkProperties(false, true, false);
+        mPrivateAddressCoordinator.updateUpstreamPrefix(mWifiNetwork, v6OnlyWifiProp);
+        verify(mHotspotIpServer, never()).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
+        mPrivateAddressCoordinator.removeUpstreamPrefix(mWifiNetwork);
+        // 6. Update v4 only wifi network, it conflict with hotspot prefix.
+        final LinkProperties v4OnlyWifiProp = buildUpstreamLinkProperties(true, false, false);
+        mPrivateAddressCoordinator.updateUpstreamPrefix(mWifiNetwork, v4OnlyWifiProp);
+        verify(mHotspotIpServer).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
+        reset(mHotspotIpServer);
+        // 7. Restart hotspot again and its prefix is different previous.
+        mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
+        final LinkAddress hotspotAddr2 = mPrivateAddressCoordinator.requestDownstreamAddress(
+                mHotspotIpServer);
+        final IpPrefix hotspotPrefix2 = PrefixUtils.asIpPrefix(hotspotAddr2);
+        assertNotEquals(hotspotPrefix, hotspotPrefix2);
+        when(mHotspotIpServer.getAddress()).thenReturn(hotspotAddr2);
+        mPrivateAddressCoordinator.updateUpstreamPrefix(mWifiNetwork, v4OnlyWifiProp);
+        verify(mHotspotIpServer, never()).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
+        // 7. Usb tethering can be enabled and its prefix is different with conflict one.
+        final LinkAddress usbAddr = mPrivateAddressCoordinator.requestDownstreamAddress(
+                mUsbIpServer);
+        final IpPrefix usbPrefix = PrefixUtils.asIpPrefix(usbAddr);
+        assertNotEquals(predefinedPrefix, usbPrefix);
+        assertNotEquals(hotspotPrefix2, usbPrefix);
+        when(mUsbIpServer.getAddress()).thenReturn(usbAddr);
+        // 8. Disable wifi upstream, then wifi's prefix can be selected again.
+        mPrivateAddressCoordinator.removeUpstreamPrefix(mWifiNetwork);
+        final LinkAddress ethAddr = mPrivateAddressCoordinator.requestDownstreamAddress(
+                mEthernetIpServer);
+        final IpPrefix ethPrefix = PrefixUtils.asIpPrefix(ethAddr);
+        assertEquals(predefinedPrefix, ethPrefix);
+    }
+}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt
index 745468f..7d5471f 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt
@@ -130,7 +130,7 @@
         context = TestContext(InstrumentationRegistry.getInstrumentation().context)
         doReturn(notificationManager).`when`(mockContext)
                 .getSystemService(Context.NOTIFICATION_SERVICE)
-        fakeTetheringThread = HandlerThread(this::class.simpleName)
+        fakeTetheringThread = HandlerThread(this::class.java.simpleName)
         fakeTetheringThread.start()
         notificationUpdater = WrappedNotificationUpdater(context, fakeTetheringThread.looper)
         setupResources()
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index 7734a3c6..329d8a5 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -32,6 +32,7 @@
 import static android.net.TetheringManager.TETHERING_NCM;
 import static android.net.TetheringManager.TETHERING_USB;
 import static android.net.TetheringManager.TETHERING_WIFI;
+import static android.net.TetheringManager.TETHER_ERROR_IFACE_CFG_ERROR;
 import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
 import static android.net.TetheringManager.TETHER_ERROR_UNKNOWN_IFACE;
 import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_FAILED;
@@ -84,6 +85,7 @@
 import android.hardware.usb.UsbManager;
 import android.net.ConnectivityManager;
 import android.net.EthernetManager;
+import android.net.EthernetManager.TetheredInterfaceCallback;
 import android.net.EthernetManager.TetheredInterfaceRequest;
 import android.net.IIntResultListener;
 import android.net.INetd;
@@ -169,9 +171,11 @@
     private static final String TEST_MOBILE_IFNAME = "test_rmnet_data0";
     private static final String TEST_XLAT_MOBILE_IFNAME = "v4-test_rmnet_data0";
     private static final String TEST_USB_IFNAME = "test_rndis0";
-    private static final String TEST_WLAN_IFNAME = "test_wlan0";
+    private static final String TEST_WIFI_IFNAME = "test_wlan0";
+    private static final String TEST_WLAN_IFNAME = "test_wlan1";
     private static final String TEST_P2P_IFNAME = "test_p2p-p2p0-0";
     private static final String TEST_NCM_IFNAME = "test_ncm0";
+    private static final String TEST_ETH_IFNAME = "test_eth0";
     private static final String TETHERING_NAME = "Tethering";
     private static final String[] PROVISIONING_APP_NAME = {"some", "app"};
     private static final String PROVISIONING_NO_UI_APP_NAME = "no_ui_app";
@@ -199,6 +203,7 @@
     @Mock private ConnectivityManager mCm;
     @Mock private EthernetManager mEm;
     @Mock private TetheringNotificationUpdater mNotificationUpdater;
+    @Mock private BpfCoordinator mBpfCoordinator;
 
     private final MockIpServerDependencies mIpServerDependencies =
             spy(new MockIpServerDependencies());
@@ -279,10 +284,11 @@
                             || ifName.equals(TEST_WLAN_IFNAME)
                             || ifName.equals(TEST_MOBILE_IFNAME)
                             || ifName.equals(TEST_P2P_IFNAME)
-                            || ifName.equals(TEST_NCM_IFNAME));
+                            || ifName.equals(TEST_NCM_IFNAME)
+                            || ifName.equals(TEST_ETH_IFNAME));
             final String[] ifaces = new String[] {
                     TEST_USB_IFNAME, TEST_WLAN_IFNAME, TEST_MOBILE_IFNAME, TEST_P2P_IFNAME,
-                    TEST_NCM_IFNAME};
+                    TEST_NCM_IFNAME, TEST_ETH_IFNAME};
             return new InterfaceParams(ifName, ArrayUtils.indexOf(ifaces, ifName) + IFINDEX_OFFSET,
                     MacAddress.ALL_ZEROS_ADDRESS);
         }
@@ -332,6 +338,12 @@
         }
 
         @Override
+        public BpfCoordinator getBpfCoordinator(Handler handler, INetd netd,
+                SharedLog log, BpfCoordinator.Dependencies deps) {
+            return mBpfCoordinator;
+        }
+
+        @Override
         public OffloadHardwareInterface getOffloadHardwareInterface(Handler h, SharedLog log) {
             return mOffloadHardwareInterface;
         }
@@ -490,7 +502,7 @@
         when(mNetd.interfaceGetList())
                 .thenReturn(new String[] {
                         TEST_MOBILE_IFNAME, TEST_WLAN_IFNAME, TEST_USB_IFNAME, TEST_P2P_IFNAME,
-                        TEST_NCM_IFNAME});
+                        TEST_NCM_IFNAME, TEST_ETH_IFNAME});
         when(mResources.getString(R.string.config_wifi_tether_enable)).thenReturn("");
         mInterfaceConfiguration = new InterfaceConfigurationParcel();
         mInterfaceConfiguration.flags = new String[0];
@@ -1836,6 +1848,109 @@
         mCarrierConfig.putBoolean(CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
         sendConfigurationChanged();
     }
+
+    private static UpstreamNetworkState buildV4WifiUpstreamState(final String ipv4Address,
+            final int prefixLength, final Network network) {
+        final LinkProperties prop = new LinkProperties();
+        prop.setInterfaceName(TEST_WIFI_IFNAME);
+
+        prop.addLinkAddress(
+                new LinkAddress(InetAddresses.parseNumericAddress(ipv4Address),
+                        prefixLength));
+
+        final NetworkCapabilities capabilities = new NetworkCapabilities()
+                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
+        return new UpstreamNetworkState(prop, capabilities, network);
+    }
+
+    @Test
+    public void testHandleIpConflict() throws Exception {
+        final Network wifiNetwork = new Network(200);
+        final Network[] allNetworks = { wifiNetwork };
+        when(mCm.getAllNetworks()).thenReturn(allNetworks);
+        UpstreamNetworkState upstreamNetwork = null;
+        runUsbTethering(upstreamNetwork);
+        final ArgumentCaptor<InterfaceConfigurationParcel> ifaceConfigCaptor =
+                ArgumentCaptor.forClass(InterfaceConfigurationParcel.class);
+        verify(mNetd).interfaceSetCfg(ifaceConfigCaptor.capture());
+        final String ipv4Address = ifaceConfigCaptor.getValue().ipv4Addr;
+        verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks(
+                any(), any());
+        reset(mNetd, mUsbManager);
+        upstreamNetwork = buildV4WifiUpstreamState(ipv4Address, 30, wifiNetwork);
+        mTetheringDependencies.mUpstreamNetworkMonitorMasterSM.sendMessage(
+                Tethering.TetherMasterSM.EVENT_UPSTREAM_CALLBACK,
+                UpstreamNetworkMonitor.EVENT_ON_LINKPROPERTIES,
+                0,
+                upstreamNetwork);
+        mLooper.dispatchAll();
+        // verify trun off usb tethering
+        verify(mUsbManager).setCurrentFunctions(UsbManager.FUNCTION_NONE);
+        mTethering.interfaceRemoved(TEST_USB_IFNAME);
+        mLooper.dispatchAll();
+        // verify restart usb tethering
+        verify(mUsbManager).setCurrentFunctions(UsbManager.FUNCTION_RNDIS);
+    }
+
+    @Test
+    public void testNoAddressAvailable() throws Exception {
+        final Network wifiNetwork = new Network(200);
+        final Network[] allNetworks = { wifiNetwork };
+        when(mCm.getAllNetworks()).thenReturn(allNetworks);
+        final String upstreamAddress = "192.168.0.100";
+        runUsbTethering(null);
+        verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks(
+                any(), any());
+        reset(mUsbManager);
+        final TetheredInterfaceRequest mockRequest = mock(TetheredInterfaceRequest.class);
+        when(mEm.requestTetheredInterface(any(), any())).thenReturn(mockRequest);
+        final ArgumentCaptor<TetheredInterfaceCallback> callbackCaptor =
+                ArgumentCaptor.forClass(TetheredInterfaceCallback.class);
+        mTethering.startTethering(createTetheringRequestParcel(TETHERING_ETHERNET), null);
+        mLooper.dispatchAll();
+        verify(mEm).requestTetheredInterface(any(), callbackCaptor.capture());
+        TetheredInterfaceCallback ethCallback = callbackCaptor.getValue();
+        ethCallback.onAvailable(TEST_ETH_IFNAME);
+        mLooper.dispatchAll();
+        reset(mUsbManager, mEm);
+
+        final UpstreamNetworkState upstreamNetwork = buildV4WifiUpstreamState(
+                upstreamAddress, 16, wifiNetwork);
+        mTetheringDependencies.mUpstreamNetworkMonitorMasterSM.sendMessage(
+                Tethering.TetherMasterSM.EVENT_UPSTREAM_CALLBACK,
+                UpstreamNetworkMonitor.EVENT_ON_LINKPROPERTIES,
+                0,
+                upstreamNetwork);
+        mLooper.dispatchAll();
+        // verify trun off usb tethering
+        verify(mUsbManager).setCurrentFunctions(UsbManager.FUNCTION_NONE);
+        // verify trun off ethernet tethering
+        verify(mockRequest).release();
+        mTethering.interfaceRemoved(TEST_USB_IFNAME);
+        ethCallback.onUnavailable();
+        mLooper.dispatchAll();
+        // verify restart usb tethering
+        verify(mUsbManager).setCurrentFunctions(UsbManager.FUNCTION_RNDIS);
+        // verify restart ethernet tethering
+        verify(mEm).requestTetheredInterface(any(), callbackCaptor.capture());
+        ethCallback = callbackCaptor.getValue();
+        ethCallback.onAvailable(TEST_ETH_IFNAME);
+
+        reset(mUsbManager, mEm);
+        when(mNetd.interfaceGetList())
+                .thenReturn(new String[] {
+                        TEST_MOBILE_IFNAME, TEST_WLAN_IFNAME, TEST_USB_IFNAME, TEST_P2P_IFNAME,
+                        TEST_NCM_IFNAME, TEST_ETH_IFNAME});
+
+        mTethering.interfaceStatusChanged(TEST_USB_IFNAME, true);
+        sendUsbBroadcast(true, true, true, TETHERING_USB);
+        mLooper.dispatchAll();
+        assertContains(Arrays.asList(mTethering.getTetherableIfaces()), TEST_USB_IFNAME);
+        assertContains(Arrays.asList(mTethering.getTetherableIfaces()), TEST_ETH_IFNAME);
+        assertEquals(TETHER_ERROR_IFACE_CFG_ERROR, mTethering.getLastTetherError(TEST_USB_IFNAME));
+        assertEquals(TETHER_ERROR_IFACE_CFG_ERROR, mTethering.getLastTetherError(TEST_ETH_IFNAME));
+    }
+
     // TODO: Test that a request for hotspot mode doesn't interfere with an
     // already operating tethering mode interface.
 }