[Tether03] Migrate IpServer into module

Add IpServer which is used to serve ip configuration, dhcp, dns proxy
and nat for downstream interface.

Bug: 136040414
Test: -build, flash, boot
      -atest TetheringTests
      -atest FrameworksNetTests

Change-Id: I23652ae0b9509abe7d38da96d523eb22ab00a343
diff --git a/Tethering/Android.bp b/Tethering/Android.bp
index dc88fd4..ca69c18 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -24,6 +24,8 @@
     ],
     static_libs: [
         "androidx.annotation_annotation",
+        "netd_aidl_interface-java",
+        "networkstack-aidl-interfaces-java",
         "tethering-client",
     ],
     manifest: "AndroidManifestBase.xml",
@@ -68,5 +70,10 @@
     name: "tethering-services-srcs",
     srcs: [
         "src/com/android/server/connectivity/tethering/TetheringConfiguration.java",
+        "src/android/net/dhcp/DhcpServerCallbacks.java",
+        "src/android/net/dhcp/DhcpServingParamsParcelExt.java",
+        "src/android/net/ip/IpServer.java",
+        "src/android/net/ip/RouterAdvertisementDaemon.java",
+        "src/android/net/util/InterfaceSet.java",
     ],
 }
diff --git a/Tethering/src/android/net/dhcp/DhcpServerCallbacks.java b/Tethering/src/android/net/dhcp/DhcpServerCallbacks.java
new file mode 100644
index 0000000..7c41377
--- /dev/null
+++ b/Tethering/src/android/net/dhcp/DhcpServerCallbacks.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2018 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 android.net.dhcp;
+
+/**
+ * Convenience wrapper around IDhcpServerCallbacks.Stub that implements getInterfaceVersion().
+ * @hide
+ */
+public abstract class DhcpServerCallbacks extends IDhcpServerCallbacks.Stub {
+    /**
+     * Get the version of the aidl interface implemented by the callbacks.
+     */
+    @Override
+    public int getInterfaceVersion() {
+        return IDhcpServerCallbacks.VERSION;
+    }
+}
diff --git a/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java b/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java
new file mode 100644
index 0000000..1fe2328
--- /dev/null
+++ b/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2018 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 android.net.dhcp;
+
+import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTH;
+
+import android.annotation.NonNull;
+import android.net.LinkAddress;
+
+import com.google.android.collect.Sets;
+
+import java.net.Inet4Address;
+import java.util.Collection;
+import java.util.Set;
+
+/**
+ * Subclass of {@link DhcpServingParamsParcel} with additional utility methods for building.
+ *
+ * <p>This utility class does not check for validity of the parameters: invalid parameters are
+ * reported by the receiving module when unparceling the parcel.
+ *
+ * @see DhcpServingParams
+ * @hide
+ */
+public class DhcpServingParamsParcelExt extends DhcpServingParamsParcel {
+    public static final int MTU_UNSET = 0;
+
+    /**
+     * Set the server address and served prefix for the DHCP server.
+     *
+     * <p>This parameter is required.
+     */
+    public DhcpServingParamsParcelExt setServerAddr(@NonNull LinkAddress serverAddr) {
+        this.serverAddr = inet4AddressToIntHTH((Inet4Address) serverAddr.getAddress());
+        this.serverAddrPrefixLength = serverAddr.getPrefixLength();
+        return this;
+    }
+
+    /**
+     * Set the default routers to be advertised to DHCP clients.
+     *
+     * <p>Each router must be inside the served prefix. This may be an empty set, but it must
+     * always be set explicitly.
+     */
+    public DhcpServingParamsParcelExt setDefaultRouters(@NonNull Set<Inet4Address> defaultRouters) {
+        this.defaultRouters = toIntArray(defaultRouters);
+        return this;
+    }
+
+    /**
+     * Set the default routers to be advertised to DHCP clients.
+     *
+     * <p>Each router must be inside the served prefix. This may be an empty list of routers,
+     * but it must always be set explicitly.
+     */
+    public DhcpServingParamsParcelExt setDefaultRouters(@NonNull Inet4Address... defaultRouters) {
+        return setDefaultRouters(Sets.newArraySet(defaultRouters));
+    }
+
+    /**
+     * Convenience method to build the parameters with no default router.
+     *
+     * <p>Equivalent to calling {@link #setDefaultRouters(Inet4Address...)} with no address.
+     */
+    public DhcpServingParamsParcelExt setNoDefaultRouter() {
+        return setDefaultRouters();
+    }
+
+    /**
+     * Set the DNS servers to be advertised to DHCP clients.
+     *
+     * <p>This may be an empty set, but it must always be set explicitly.
+     */
+    public DhcpServingParamsParcelExt setDnsServers(@NonNull Set<Inet4Address> dnsServers) {
+        this.dnsServers = toIntArray(dnsServers);
+        return this;
+    }
+
+    /**
+     * Set the DNS servers to be advertised to DHCP clients.
+     *
+     * <p>This may be an empty list of servers, but it must always be set explicitly.
+     */
+    public DhcpServingParamsParcelExt setDnsServers(@NonNull Inet4Address... dnsServers) {
+        return setDnsServers(Sets.newArraySet(dnsServers));
+    }
+
+    /**
+     * Convenience method to build the parameters with no DNS server.
+     *
+     * <p>Equivalent to calling {@link #setDnsServers(Inet4Address...)} with no address.
+     */
+    public DhcpServingParamsParcelExt setNoDnsServer() {
+        return setDnsServers();
+    }
+
+    /**
+     * Set excluded addresses that the DHCP server is not allowed to assign to clients.
+     *
+     * <p>This parameter is optional. DNS servers and default routers are always excluded
+     * and do not need to be set here.
+     */
+    public DhcpServingParamsParcelExt setExcludedAddrs(@NonNull Set<Inet4Address> excludedAddrs) {
+        this.excludedAddrs = toIntArray(excludedAddrs);
+        return this;
+    }
+
+    /**
+     * Set excluded addresses that the DHCP server is not allowed to assign to clients.
+     *
+     * <p>This parameter is optional. DNS servers and default routers are always excluded
+     * and do not need to be set here.
+     */
+    public DhcpServingParamsParcelExt setExcludedAddrs(@NonNull Inet4Address... excludedAddrs) {
+        return setExcludedAddrs(Sets.newArraySet(excludedAddrs));
+    }
+
+    /**
+     * Set the lease time for leases assigned by the DHCP server.
+     *
+     * <p>This parameter is required.
+     */
+    public DhcpServingParamsParcelExt setDhcpLeaseTimeSecs(long dhcpLeaseTimeSecs) {
+        this.dhcpLeaseTimeSecs = dhcpLeaseTimeSecs;
+        return this;
+    }
+
+    /**
+     * Set the link MTU to be advertised to DHCP clients.
+     *
+     * <p>If set to {@link #MTU_UNSET}, no MTU will be advertised to clients. This parameter
+     * is optional and defaults to {@link #MTU_UNSET}.
+     */
+    public DhcpServingParamsParcelExt setLinkMtu(int linkMtu) {
+        this.linkMtu = linkMtu;
+        return this;
+    }
+
+    /**
+     * Set whether the DHCP server should send the ANDROID_METERED vendor-specific option.
+     *
+     * <p>If not set, the default value is false.
+     */
+    public DhcpServingParamsParcelExt setMetered(boolean metered) {
+        this.metered = metered;
+        return this;
+    }
+
+    private static int[] toIntArray(@NonNull Collection<Inet4Address> addrs) {
+        int[] res = new int[addrs.size()];
+        int i = 0;
+        for (Inet4Address addr : addrs) {
+            res[i] = inet4AddressToIntHTH(addr);
+            i++;
+        }
+        return res;
+    }
+}
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
new file mode 100644
index 0000000..ff3d7bc
--- /dev/null
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -0,0 +1,1039 @@
+/*
+ * Copyright (C) 2016 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 android.net.ip;
+
+import static android.net.InetAddresses.parseNumericAddress;
+import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
+import static android.net.util.NetworkConstants.FF;
+import static android.net.util.NetworkConstants.RFC7421_PREFIX_LENGTH;
+import static android.net.util.NetworkConstants.asByte;
+
+import android.net.ConnectivityManager;
+import android.net.INetd;
+import android.net.INetworkStackStatusCallback;
+import android.net.INetworkStatsService;
+import android.net.InterfaceConfiguration;
+import android.net.IpPrefix;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.NetworkStackClient;
+import android.net.RouteInfo;
+import android.net.dhcp.DhcpServerCallbacks;
+import android.net.dhcp.DhcpServingParamsParcel;
+import android.net.dhcp.DhcpServingParamsParcelExt;
+import android.net.dhcp.IDhcpServer;
+import android.net.ip.RouterAdvertisementDaemon.RaParams;
+import android.net.util.InterfaceParams;
+import android.net.util.InterfaceSet;
+import android.net.util.NetdService;
+import android.net.util.SharedLog;
+import android.os.INetworkManagementService;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.util.MessageUtils;
+import com.android.internal.util.Protocol;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Random;
+import java.util.Set;
+
+/**
+ * Provides the interface to IP-layer serving functionality for a given network
+ * interface, e.g. for tethering or "local-only hotspot" mode.
+ *
+ * @hide
+ */
+public class IpServer extends StateMachine {
+    public static final int STATE_UNAVAILABLE = 0;
+    public static final int STATE_AVAILABLE   = 1;
+    public static final int STATE_TETHERED    = 2;
+    public static final int STATE_LOCAL_ONLY  = 3;
+
+    /** Get string name of |state|.*/
+    public static String getStateString(int state) {
+        switch (state) {
+            case STATE_UNAVAILABLE: return "UNAVAILABLE";
+            case STATE_AVAILABLE:   return "AVAILABLE";
+            case STATE_TETHERED:    return "TETHERED";
+            case STATE_LOCAL_ONLY:  return "LOCAL_ONLY";
+        }
+        return "UNKNOWN: " + state;
+    }
+
+    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;
+
+    // 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;
+
+    // TODO: have this configurable
+    private static final int DHCP_LEASE_TIME_SECS = 3600;
+
+    private static final String TAG = "IpServer";
+    private static final boolean DBG = false;
+    private static final boolean VDBG = false;
+    private static final Class[] sMessageClasses = {
+            IpServer.class
+    };
+    private static final SparseArray<String> sMagicDecoderRing =
+            MessageUtils.findMessageNames(sMessageClasses);
+
+    /** IpServer callback. */
+    public static class Callback {
+        /**
+         * Notify that |who| has changed its tethering state.
+         *
+         * @param who the calling instance of IpServer
+         * @param state one of STATE_*
+         * @param lastError one of ConnectivityManager.TETHER_ERROR_*
+         */
+        public void updateInterfaceState(IpServer who, int state, int lastError) {}
+
+        /**
+         * Notify that |who| has new LinkProperties.
+         *
+         * @param who the calling instance of IpServer
+         * @param newLp the new LinkProperties to report
+         */
+        public void updateLinkProperties(IpServer who, LinkProperties newLp) {}
+    }
+
+    /** Capture IpServer dependencies, for injection. */
+    public static class Dependencies {
+        /** Create a RouterAdvertisementDaemon instance to be used by IpServer.*/
+        public RouterAdvertisementDaemon getRouterAdvertisementDaemon(InterfaceParams ifParams) {
+            return new RouterAdvertisementDaemon(ifParams);
+        }
+
+        /** Get |ifName|'s interface information.*/
+        public InterfaceParams getInterfaceParams(String ifName) {
+            return InterfaceParams.getByName(ifName);
+        }
+
+        public INetd getNetdService() {
+            return NetdService.getInstance();
+        }
+
+        /**
+         * Create a DhcpServer instance to be used by IpServer.
+         */
+        public void makeDhcpServer(String ifName, DhcpServingParamsParcel params,
+                DhcpServerCallbacks cb) {
+            NetworkStackClient.getInstance().makeDhcpServer(ifName, params, cb);
+        }
+    }
+
+    private static final int BASE_IFACE              = Protocol.BASE_TETHERING + 100;
+    // request from the user that it wants to tether
+    public static final int CMD_TETHER_REQUESTED            = BASE_IFACE + 2;
+    // request from the user that it wants to untether
+    public static final int CMD_TETHER_UNREQUESTED          = BASE_IFACE + 3;
+    // notification that this interface is down
+    public static final int CMD_INTERFACE_DOWN              = BASE_IFACE + 4;
+    // notification from the master SM that it had trouble enabling IP Forwarding
+    public static final int CMD_IP_FORWARDING_ENABLE_ERROR  = BASE_IFACE + 7;
+    // notification from the master SM that it had trouble disabling IP Forwarding
+    public static final int CMD_IP_FORWARDING_DISABLE_ERROR = BASE_IFACE + 8;
+    // notification from the master SM that it had trouble starting tethering
+    public static final int CMD_START_TETHERING_ERROR       = BASE_IFACE + 9;
+    // notification from the master SM that it had trouble stopping tethering
+    public static final int CMD_STOP_TETHERING_ERROR        = BASE_IFACE + 10;
+    // notification from the master SM that it had trouble setting the DNS forwarders
+    public static final int CMD_SET_DNS_FORWARDERS_ERROR    = BASE_IFACE + 11;
+    // the upstream connection has changed
+    public static final int CMD_TETHER_CONNECTION_CHANGED   = BASE_IFACE + 12;
+    // new IPv6 tethering parameters need to be processed
+    public static final int CMD_IPV6_TETHER_UPDATE          = BASE_IFACE + 13;
+
+    private final State mInitialState;
+    private final State mLocalHotspotState;
+    private final State mTetheredState;
+    private final State mUnavailableState;
+
+    private final SharedLog mLog;
+    private final INetworkManagementService mNMService;
+    private final INetd mNetd;
+    private final INetworkStatsService mStatsService;
+    private final Callback mCallback;
+    private final InterfaceController mInterfaceCtrl;
+
+    private final String mIfaceName;
+    private final int mInterfaceType;
+    private final LinkProperties mLinkProperties;
+    private final boolean mUsingLegacyDhcp;
+
+    private final Dependencies mDeps;
+
+    private int mLastError;
+    private int mServingMode;
+    private InterfaceSet mUpstreamIfaceSet;  // may change over time
+    private InterfaceParams mInterfaceParams;
+    // TODO: De-duplicate this with mLinkProperties above. Currently, these link
+    // properties are those selected by the IPv6TetheringCoordinator and relayed
+    // to us. By comparison, mLinkProperties contains the addresses and directly
+    // connected routes that have been formed from these properties iff. we have
+    // succeeded in configuring them and are able to announce them within Router
+    // Advertisements (otherwise, we do not add them to mLinkProperties at all).
+    private LinkProperties mLastIPv6LinkProperties;
+    private RouterAdvertisementDaemon mRaDaemon;
+
+    // To be accessed only on the handler thread
+    private int mDhcpServerStartIndex = 0;
+    private IDhcpServer mDhcpServer;
+    private RaParams mLastRaParams;
+
+    public IpServer(
+            String ifaceName, Looper looper, int interfaceType, SharedLog log,
+            INetworkManagementService nMService, INetworkStatsService statsService,
+            Callback callback, boolean usingLegacyDhcp, Dependencies deps) {
+        super(ifaceName, looper);
+        mLog = log.forSubComponent(ifaceName);
+        mNMService = nMService;
+        mNetd = deps.getNetdService();
+        mStatsService = statsService;
+        mCallback = callback;
+        mInterfaceCtrl = new InterfaceController(ifaceName, mNetd, mLog);
+        mIfaceName = ifaceName;
+        mInterfaceType = interfaceType;
+        mLinkProperties = new LinkProperties();
+        mUsingLegacyDhcp = usingLegacyDhcp;
+        mDeps = deps;
+        resetLinkProperties();
+        mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
+        mServingMode = STATE_AVAILABLE;
+
+        mInitialState = new InitialState();
+        mLocalHotspotState = new LocalHotspotState();
+        mTetheredState = new TetheredState();
+        mUnavailableState = new UnavailableState();
+        addState(mInitialState);
+        addState(mLocalHotspotState);
+        addState(mTetheredState);
+        addState(mUnavailableState);
+
+        setInitialState(mInitialState);
+    }
+
+    /** Interface name which IpServer served.*/
+    public String interfaceName() {
+        return mIfaceName;
+    }
+
+    /**
+     * Tethering downstream type. It would be one of ConnectivityManager#TETHERING_*.
+     */
+    public int interfaceType() {
+        return mInterfaceType;
+    }
+
+    /** Last error from this IpServer. */
+    public int lastError() {
+        return mLastError;
+    }
+
+    /** Serving mode is the current state of IpServer state machine. */
+    public int servingMode() {
+        return mServingMode;
+    }
+
+    /** The properties of the network link which IpServer is serving. */
+    public LinkProperties linkProperties() {
+        return new LinkProperties(mLinkProperties);
+    }
+
+    /** Stop this IpServer. After this is called this IpServer should not be used any more. */
+    public void stop() {
+        sendMessage(CMD_INTERFACE_DOWN);
+    }
+
+    /**
+     * Tethering is canceled. IpServer state machine will be available and wait for
+     * next tethering request.
+     */
+    public void unwanted() {
+        sendMessage(CMD_TETHER_UNREQUESTED);
+    }
+
+    /** Internals. */
+
+    private boolean startIPv4() {
+        return configureIPv4(true);
+    }
+
+    /**
+     * Convenience wrapper around INetworkStackStatusCallback to run callbacks on the IpServer
+     * handler.
+     *
+     * <p>Different instances of this class can be created for each call to IDhcpServer methods,
+     * with different implementations of the callback, to differentiate handling of success/error in
+     * each call.
+     */
+    private abstract class OnHandlerStatusCallback extends INetworkStackStatusCallback.Stub {
+        @Override
+        public void onStatusAvailable(int statusCode) {
+            getHandler().post(() -> callback(statusCode));
+        }
+
+        public abstract void callback(int statusCode);
+
+        @Override
+        public int getInterfaceVersion() {
+            return this.VERSION;
+        }
+    }
+
+    private class DhcpServerCallbacksImpl extends DhcpServerCallbacks {
+        private final int mStartIndex;
+
+        private DhcpServerCallbacksImpl(int startIndex) {
+            mStartIndex = startIndex;
+        }
+
+        @Override
+        public void onDhcpServerCreated(int statusCode, IDhcpServer server) throws RemoteException {
+            getHandler().post(() -> {
+                // We are on the handler thread: mDhcpServerStartIndex can be read safely.
+                if (mStartIndex != mDhcpServerStartIndex) {
+                    // This start request is obsolete. When the |server| binder token goes out of
+                    // scope, the garbage collector will finalize it, which causes the network stack
+                    // process garbage collector to collect the server itself.
+                    return;
+                }
+
+                if (statusCode != STATUS_SUCCESS) {
+                    mLog.e("Error obtaining DHCP server: " + statusCode);
+                    handleError();
+                    return;
+                }
+
+                mDhcpServer = server;
+                try {
+                    mDhcpServer.start(new OnHandlerStatusCallback() {
+                        @Override
+                        public void callback(int startStatusCode) {
+                            if (startStatusCode != STATUS_SUCCESS) {
+                                mLog.e("Error starting DHCP server: " + startStatusCode);
+                                handleError();
+                            }
+                        }
+                    });
+                } catch (RemoteException e) {
+                    e.rethrowFromSystemServer();
+                }
+            });
+        }
+
+        private void handleError() {
+            mLastError = ConnectivityManager.TETHER_ERROR_DHCPSERVER_ERROR;
+            transitionTo(mInitialState);
+        }
+    }
+
+    private boolean startDhcp(Inet4Address addr, int prefixLen) {
+        if (mUsingLegacyDhcp) {
+            return true;
+        }
+        final DhcpServingParamsParcel params;
+        params = new DhcpServingParamsParcelExt()
+                .setDefaultRouters(addr)
+                .setDhcpLeaseTimeSecs(DHCP_LEASE_TIME_SECS)
+                .setDnsServers(addr)
+                .setServerAddr(new LinkAddress(addr, prefixLen))
+                .setMetered(true);
+        // TODO: also advertise link MTU
+
+        mDhcpServerStartIndex++;
+        mDeps.makeDhcpServer(
+                mIfaceName, params, new DhcpServerCallbacksImpl(mDhcpServerStartIndex));
+        return true;
+    }
+
+    private void stopDhcp() {
+        // Make all previous start requests obsolete so servers are not started later
+        mDhcpServerStartIndex++;
+
+        if (mDhcpServer != null) {
+            try {
+                mDhcpServer.stop(new OnHandlerStatusCallback() {
+                    @Override
+                    public void callback(int statusCode) {
+                        if (statusCode != STATUS_SUCCESS) {
+                            mLog.e("Error stopping DHCP server: " + statusCode);
+                            mLastError = ConnectivityManager.TETHER_ERROR_DHCPSERVER_ERROR;
+                            // Not much more we can do here
+                        }
+                    }
+                });
+                mDhcpServer = null;
+            } catch (RemoteException e) {
+                e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    private boolean configureDhcp(boolean enable, Inet4Address addr, int prefixLen) {
+        if (enable) {
+            return startDhcp(addr, prefixLen);
+        } else {
+            stopDhcp();
+            return true;
+        }
+    }
+
+    private void stopIPv4() {
+        configureIPv4(false);
+        // NOTE: All of configureIPv4() will be refactored out of existence
+        // into calls to InterfaceController, shared with startIPv4().
+        mInterfaceCtrl.clearIPv4Address();
+    }
+
+    // TODO: Refactor this in terms of calls to InterfaceController.
+    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.
+        String ipAsString = null;
+        int prefixLen = 0;
+        if (mInterfaceType == ConnectivityManager.TETHERING_USB) {
+            ipAsString = USB_NEAR_IFACE_ADDR;
+            prefixLen = USB_PREFIX_LENGTH;
+        } else if (mInterfaceType == ConnectivityManager.TETHERING_WIFI) {
+            ipAsString = getRandomWifiIPv4Address();
+            prefixLen = WIFI_HOST_IFACE_PREFIX_LENGTH;
+        } else if (mInterfaceType == ConnectivityManager.TETHERING_WIFI_P2P) {
+            ipAsString = WIFI_P2P_IFACE_ADDR;
+            prefixLen = WIFI_P2P_IFACE_PREFIX_LENGTH;
+        } else {
+            // BT configures the interface elsewhere: only start DHCP.
+            final Inet4Address srvAddr = (Inet4Address) parseNumericAddress(BLUETOOTH_IFACE_ADDR);
+            return configureDhcp(enabled, srvAddr, BLUETOOTH_DHCP_PREFIX_LENGTH);
+        }
+
+        final LinkAddress linkAddr;
+        try {
+            final InterfaceConfiguration ifcg = mNMService.getInterfaceConfig(mIfaceName);
+            if (ifcg == null) {
+                mLog.e("Received null interface config");
+                return false;
+            }
+
+            InetAddress addr = parseNumericAddress(ipAsString);
+            linkAddr = new LinkAddress(addr, prefixLen);
+            ifcg.setLinkAddress(linkAddr);
+            if (mInterfaceType == ConnectivityManager.TETHERING_WIFI) {
+                // The WiFi stack has ownership of the interface up/down state.
+                // It is unclear whether the Bluetooth or USB stacks will manage their own
+                // state.
+                ifcg.ignoreInterfaceUpDownStatus();
+            } else {
+                if (enabled) {
+                    ifcg.setInterfaceUp();
+                } else {
+                    ifcg.setInterfaceDown();
+                }
+            }
+            ifcg.clearFlag("running");
+
+            // TODO: this may throw if the interface is already gone. Do proper handling and
+            // simplify the DHCP server start/stop.
+            mNMService.setInterfaceConfig(mIfaceName, ifcg);
+
+            if (!configureDhcp(enabled, (Inet4Address) addr, prefixLen)) {
+                return false;
+            }
+        } catch (Exception e) {
+            mLog.e("Error configuring interface " + e);
+            if (!enabled) {
+                try {
+                    // Calling stopDhcp several times is fine
+                    stopDhcp();
+                } catch (Exception dhcpError) {
+                    mLog.e("Error stopping DHCP", dhcpError);
+                }
+            }
+            return false;
+        }
+
+        // Directly-connected route.
+        final RouteInfo route = new RouteInfo(linkAddr);
+        if (enabled) {
+            mLinkProperties.addLinkAddress(linkAddr);
+            mLinkProperties.addRoute(route);
+        } else {
+            mLinkProperties.removeLinkAddress(linkAddr);
+            mLinkProperties.removeRoute(route);
+        }
+        return true;
+    }
+
+    private String getRandomWifiIPv4Address() {
+        try {
+            byte[] bytes = parseNumericAddress(WIFI_HOST_IFACE_ADDR).getAddress();
+            bytes[3] = getRandomSanitizedByte(DOUG_ADAMS, asByte(0), asByte(1), FF);
+            return InetAddress.getByAddress(bytes).getHostAddress();
+        } catch (Exception e) {
+            return WIFI_HOST_IFACE_ADDR;
+        }
+    }
+
+    private boolean startIPv6() {
+        mInterfaceParams = mDeps.getInterfaceParams(mIfaceName);
+        if (mInterfaceParams == null) {
+            mLog.e("Failed to find InterfaceParams");
+            stopIPv6();
+            return false;
+        }
+
+        mRaDaemon = mDeps.getRouterAdvertisementDaemon(mInterfaceParams);
+        if (!mRaDaemon.start()) {
+            stopIPv6();
+            return false;
+        }
+
+        return true;
+    }
+
+    private void stopIPv6() {
+        mInterfaceParams = null;
+        setRaParams(null);
+
+        if (mRaDaemon != null) {
+            mRaDaemon.stop();
+            mRaDaemon = null;
+        }
+    }
+
+    // IPv6TetheringCoordinator sends updates with carefully curated IPv6-only
+    // LinkProperties. These have extraneous data filtered out and only the
+    // necessary prefixes included (per its prefix distribution policy).
+    //
+    // TODO: Evaluate using a data structure than is more directly suited to
+    // communicating only the relevant information.
+    private void updateUpstreamIPv6LinkProperties(LinkProperties v6only) {
+        if (mRaDaemon == null) return;
+
+        // Avoid unnecessary work on spurious updates.
+        if (Objects.equals(mLastIPv6LinkProperties, v6only)) {
+            return;
+        }
+
+        RaParams params = null;
+
+        if (v6only != null) {
+            params = new RaParams();
+            params.mtu = v6only.getMtu();
+            params.hasDefaultRoute = v6only.hasIpv6DefaultRoute();
+
+            if (params.hasDefaultRoute) params.hopLimit = getHopLimit(v6only.getInterfaceName());
+
+            for (LinkAddress linkAddr : v6only.getLinkAddresses()) {
+                if (linkAddr.getPrefixLength() != RFC7421_PREFIX_LENGTH) continue;
+
+                final IpPrefix prefix = new IpPrefix(
+                        linkAddr.getAddress(), linkAddr.getPrefixLength());
+                params.prefixes.add(prefix);
+
+                final Inet6Address dnsServer = getLocalDnsIpFor(prefix);
+                if (dnsServer != null) {
+                    params.dnses.add(dnsServer);
+                }
+            }
+        }
+        // If v6only is null, we pass in null to setRaParams(), which handles
+        // deprecation of any existing RA data.
+
+        setRaParams(params);
+        mLastIPv6LinkProperties = v6only;
+    }
+
+    private void configureLocalIPv6Routes(
+            HashSet<IpPrefix> deprecatedPrefixes, HashSet<IpPrefix> newPrefixes) {
+        // [1] Remove the routes that are deprecated.
+        if (!deprecatedPrefixes.isEmpty()) {
+            final ArrayList<RouteInfo> toBeRemoved =
+                    getLocalRoutesFor(mIfaceName, deprecatedPrefixes);
+            try {
+                final int removalFailures = mNMService.removeRoutesFromLocalNetwork(toBeRemoved);
+                if (removalFailures > 0) {
+                    mLog.e(String.format("Failed to remove %d IPv6 routes from local table.",
+                            removalFailures));
+                }
+            } catch (RemoteException e) {
+                mLog.e("Failed to remove IPv6 routes from local table: " + e);
+            }
+
+            for (RouteInfo route : toBeRemoved) mLinkProperties.removeRoute(route);
+        }
+
+        // [2] Add only the routes that have not previously been added.
+        if (newPrefixes != null && !newPrefixes.isEmpty()) {
+            HashSet<IpPrefix> addedPrefixes = (HashSet) newPrefixes.clone();
+            if (mLastRaParams != null) {
+                addedPrefixes.removeAll(mLastRaParams.prefixes);
+            }
+
+            if (!addedPrefixes.isEmpty()) {
+                final ArrayList<RouteInfo> toBeAdded =
+                        getLocalRoutesFor(mIfaceName, addedPrefixes);
+                try {
+                    // It's safe to call addInterfaceToLocalNetwork() even if
+                    // the interface is already in the local_network. Note also
+                    // that adding routes that already exist does not cause an
+                    // error (EEXIST is silently ignored).
+                    mNMService.addInterfaceToLocalNetwork(mIfaceName, toBeAdded);
+                } catch (Exception e) {
+                    mLog.e("Failed to add IPv6 routes to local table: " + e);
+                }
+
+                for (RouteInfo route : toBeAdded) mLinkProperties.addRoute(route);
+            }
+        }
+    }
+
+    private void configureLocalIPv6Dns(
+            HashSet<Inet6Address> deprecatedDnses, HashSet<Inet6Address> newDnses) {
+        // TODO: Is this really necessary? Can we not fail earlier if INetd cannot be located?
+        if (mNetd == null) {
+            if (newDnses != null) newDnses.clear();
+            mLog.e("No netd service instance available; not setting local IPv6 addresses");
+            return;
+        }
+
+        // [1] Remove deprecated local DNS IP addresses.
+        if (!deprecatedDnses.isEmpty()) {
+            for (Inet6Address dns : deprecatedDnses) {
+                if (!mInterfaceCtrl.removeAddress(dns, RFC7421_PREFIX_LENGTH)) {
+                    mLog.e("Failed to remove local dns IP " + dns);
+                }
+
+                mLinkProperties.removeLinkAddress(new LinkAddress(dns, RFC7421_PREFIX_LENGTH));
+            }
+        }
+
+        // [2] Add only the local DNS IP addresses that have not previously been added.
+        if (newDnses != null && !newDnses.isEmpty()) {
+            final HashSet<Inet6Address> addedDnses = (HashSet) newDnses.clone();
+            if (mLastRaParams != null) {
+                addedDnses.removeAll(mLastRaParams.dnses);
+            }
+
+            for (Inet6Address dns : addedDnses) {
+                if (!mInterfaceCtrl.addAddress(dns, RFC7421_PREFIX_LENGTH)) {
+                    mLog.e("Failed to add local dns IP " + dns);
+                    newDnses.remove(dns);
+                }
+
+                mLinkProperties.addLinkAddress(new LinkAddress(dns, RFC7421_PREFIX_LENGTH));
+            }
+        }
+
+        try {
+            mNetd.tetherApplyDnsInterfaces();
+        } catch (ServiceSpecificException | RemoteException e) {
+            mLog.e("Failed to update local DNS caching server");
+            if (newDnses != null) newDnses.clear();
+        }
+    }
+
+    private byte getHopLimit(String upstreamIface) {
+        try {
+            int upstreamHopLimit = Integer.parseUnsignedInt(
+                    mNetd.getProcSysNet(INetd.IPV6, INetd.CONF, upstreamIface, "hop_limit"));
+            // Add one hop to account for this forwarding device
+            upstreamHopLimit++;
+            // Cap the hop limit to 255.
+            return (byte) Integer.min(upstreamHopLimit, 255);
+        } catch (Exception e) {
+            mLog.e("Failed to find upstream interface hop limit", e);
+        }
+        return RaParams.DEFAULT_HOPLIMIT;
+    }
+
+    private void setRaParams(RaParams newParams) {
+        if (mRaDaemon != null) {
+            final RaParams deprecatedParams =
+                    RaParams.getDeprecatedRaParams(mLastRaParams, newParams);
+
+            configureLocalIPv6Routes(deprecatedParams.prefixes,
+                    (newParams != null) ? newParams.prefixes : null);
+
+            configureLocalIPv6Dns(deprecatedParams.dnses,
+                    (newParams != null) ? newParams.dnses : null);
+
+            mRaDaemon.buildNewRa(deprecatedParams, newParams);
+        }
+
+        mLastRaParams = newParams;
+    }
+
+    private void logMessage(State state, int what) {
+        mLog.log(state.getName() + " got " + sMagicDecoderRing.get(what, Integer.toString(what)));
+    }
+
+    private void sendInterfaceState(int newInterfaceState) {
+        mServingMode = newInterfaceState;
+        mCallback.updateInterfaceState(this, newInterfaceState, mLastError);
+        sendLinkProperties();
+    }
+
+    private void sendLinkProperties() {
+        mCallback.updateLinkProperties(this, new LinkProperties(mLinkProperties));
+    }
+
+    private void resetLinkProperties() {
+        mLinkProperties.clear();
+        mLinkProperties.setInterfaceName(mIfaceName);
+    }
+
+    class InitialState extends State {
+        @Override
+        public void enter() {
+            sendInterfaceState(STATE_AVAILABLE);
+        }
+
+        @Override
+        public boolean processMessage(Message message) {
+            logMessage(this, message.what);
+            switch (message.what) {
+                case CMD_TETHER_REQUESTED:
+                    mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
+                    switch (message.arg1) {
+                        case STATE_LOCAL_ONLY:
+                            transitionTo(mLocalHotspotState);
+                            break;
+                        case STATE_TETHERED:
+                            transitionTo(mTetheredState);
+                            break;
+                        default:
+                            mLog.e("Invalid tethering interface serving state specified.");
+                    }
+                    break;
+                case CMD_INTERFACE_DOWN:
+                    transitionTo(mUnavailableState);
+                    break;
+                case CMD_IPV6_TETHER_UPDATE:
+                    updateUpstreamIPv6LinkProperties((LinkProperties) message.obj);
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            return HANDLED;
+        }
+    }
+
+    class BaseServingState extends State {
+        @Override
+        public void enter() {
+            if (!startIPv4()) {
+                mLastError = ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR;
+                return;
+            }
+
+            try {
+                mNMService.tetherInterface(mIfaceName);
+            } catch (Exception e) {
+                mLog.e("Error Tethering: " + e);
+                mLastError = ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR;
+                return;
+            }
+
+            if (!startIPv6()) {
+                mLog.e("Failed to startIPv6");
+                // TODO: Make this a fatal error once Bluetooth IPv6 is sorted.
+                return;
+            }
+        }
+
+        @Override
+        public void exit() {
+            // Note that at this point, we're leaving the tethered state.  We can fail any
+            // of these operations, but it doesn't really change that we have to try them
+            // all in sequence.
+            stopIPv6();
+
+            try {
+                mNMService.untetherInterface(mIfaceName);
+            } catch (Exception e) {
+                mLastError = ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR;
+                mLog.e("Failed to untether interface: " + e);
+            }
+
+            stopIPv4();
+
+            resetLinkProperties();
+        }
+
+        @Override
+        public boolean processMessage(Message message) {
+            logMessage(this, message.what);
+            switch (message.what) {
+                case CMD_TETHER_UNREQUESTED:
+                    transitionTo(mInitialState);
+                    if (DBG) Log.d(TAG, "Untethered (unrequested)" + mIfaceName);
+                    break;
+                case CMD_INTERFACE_DOWN:
+                    transitionTo(mUnavailableState);
+                    if (DBG) Log.d(TAG, "Untethered (ifdown)" + mIfaceName);
+                    break;
+                case CMD_IPV6_TETHER_UPDATE:
+                    updateUpstreamIPv6LinkProperties((LinkProperties) message.obj);
+                    sendLinkProperties();
+                    break;
+                case CMD_IP_FORWARDING_ENABLE_ERROR:
+                case CMD_IP_FORWARDING_DISABLE_ERROR:
+                case CMD_START_TETHERING_ERROR:
+                case CMD_STOP_TETHERING_ERROR:
+                case CMD_SET_DNS_FORWARDERS_ERROR:
+                    mLastError = ConnectivityManager.TETHER_ERROR_MASTER_ERROR;
+                    transitionTo(mInitialState);
+                    break;
+                default:
+                    return false;
+            }
+            return true;
+        }
+    }
+
+    // Handling errors in BaseServingState.enter() by transitioning is
+    // problematic because transitioning during a multi-state jump yields
+    // a Log.wtf(). Ultimately, there should be only one ServingState,
+    // and forwarding and NAT rules should be handled by a coordinating
+    // functional element outside of IpServer.
+    class LocalHotspotState extends BaseServingState {
+        @Override
+        public void enter() {
+            super.enter();
+            if (mLastError != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
+                transitionTo(mInitialState);
+            }
+
+            if (DBG) Log.d(TAG, "Local hotspot " + mIfaceName);
+            sendInterfaceState(STATE_LOCAL_ONLY);
+        }
+
+        @Override
+        public boolean processMessage(Message message) {
+            if (super.processMessage(message)) return true;
+
+            logMessage(this, message.what);
+            switch (message.what) {
+                case CMD_TETHER_REQUESTED:
+                    mLog.e("CMD_TETHER_REQUESTED while in local-only hotspot mode.");
+                    break;
+                case CMD_TETHER_CONNECTION_CHANGED:
+                    // Ignored in local hotspot state.
+                    break;
+                default:
+                    return false;
+            }
+            return true;
+        }
+    }
+
+    // Handling errors in BaseServingState.enter() by transitioning is
+    // problematic because transitioning during a multi-state jump yields
+    // a Log.wtf(). Ultimately, there should be only one ServingState,
+    // and forwarding and NAT rules should be handled by a coordinating
+    // functional element outside of IpServer.
+    class TetheredState extends BaseServingState {
+        @Override
+        public void enter() {
+            super.enter();
+            if (mLastError != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
+                transitionTo(mInitialState);
+            }
+
+            if (DBG) Log.d(TAG, "Tethered " + mIfaceName);
+            sendInterfaceState(STATE_TETHERED);
+        }
+
+        @Override
+        public void exit() {
+            cleanupUpstream();
+            super.exit();
+        }
+
+        private void cleanupUpstream() {
+            if (mUpstreamIfaceSet == null) return;
+
+            for (String ifname : mUpstreamIfaceSet.ifnames) cleanupUpstreamInterface(ifname);
+            mUpstreamIfaceSet = null;
+        }
+
+        private void cleanupUpstreamInterface(String upstreamIface) {
+            // Note that we don't care about errors here.
+            // Sometimes interfaces are gone before we get
+            // to remove their rules, which generates errors.
+            // Just do the best we can.
+            try {
+                // About to tear down NAT; gather remaining statistics.
+                mStatsService.forceUpdate();
+            } catch (Exception e) {
+                if (VDBG) Log.e(TAG, "Exception in forceUpdate: " + e.toString());
+            }
+            try {
+                mNMService.stopInterfaceForwarding(mIfaceName, upstreamIface);
+            } catch (Exception e) {
+                if (VDBG) Log.e(TAG, "Exception in removeInterfaceForward: " + e.toString());
+            }
+            try {
+                mNMService.disableNat(mIfaceName, upstreamIface);
+            } catch (Exception e) {
+                if (VDBG) Log.e(TAG, "Exception in disableNat: " + e.toString());
+            }
+        }
+
+        @Override
+        public boolean processMessage(Message message) {
+            if (super.processMessage(message)) return true;
+
+            logMessage(this, message.what);
+            switch (message.what) {
+                case CMD_TETHER_REQUESTED:
+                    mLog.e("CMD_TETHER_REQUESTED while already tethering.");
+                    break;
+                case CMD_TETHER_CONNECTION_CHANGED:
+                    final InterfaceSet newUpstreamIfaceSet = (InterfaceSet) message.obj;
+                    if (noChangeInUpstreamIfaceSet(newUpstreamIfaceSet)) {
+                        if (VDBG) Log.d(TAG, "Connection changed noop - dropping");
+                        break;
+                    }
+
+                    if (newUpstreamIfaceSet == null) {
+                        cleanupUpstream();
+                        break;
+                    }
+
+                    for (String removed : upstreamInterfacesRemoved(newUpstreamIfaceSet)) {
+                        cleanupUpstreamInterface(removed);
+                    }
+
+                    final Set<String> added = upstreamInterfacesAdd(newUpstreamIfaceSet);
+                    // This makes the call to cleanupUpstream() in the error
+                    // path for any interface neatly cleanup all the interfaces.
+                    mUpstreamIfaceSet = newUpstreamIfaceSet;
+
+                    for (String ifname : added) {
+                        try {
+                            mNMService.enableNat(mIfaceName, ifname);
+                            mNMService.startInterfaceForwarding(mIfaceName, ifname);
+                        } catch (Exception e) {
+                            mLog.e("Exception enabling NAT: " + e);
+                            cleanupUpstream();
+                            mLastError = ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR;
+                            transitionTo(mInitialState);
+                            return true;
+                        }
+                    }
+                    break;
+                default:
+                    return false;
+            }
+            return true;
+        }
+
+        private boolean noChangeInUpstreamIfaceSet(InterfaceSet newIfaces) {
+            if (mUpstreamIfaceSet == null && newIfaces == null) return true;
+            if (mUpstreamIfaceSet != null && newIfaces != null) {
+                return mUpstreamIfaceSet.equals(newIfaces);
+            }
+            return false;
+        }
+
+        private Set<String> upstreamInterfacesRemoved(InterfaceSet newIfaces) {
+            if (mUpstreamIfaceSet == null) return new HashSet<>();
+
+            final HashSet<String> removed = new HashSet<>(mUpstreamIfaceSet.ifnames);
+            removed.removeAll(newIfaces.ifnames);
+            return removed;
+        }
+
+        private Set<String> upstreamInterfacesAdd(InterfaceSet newIfaces) {
+            final HashSet<String> added = new HashSet<>(newIfaces.ifnames);
+            if (mUpstreamIfaceSet != null) added.removeAll(mUpstreamIfaceSet.ifnames);
+            return added;
+        }
+    }
+
+    /**
+     * This state is terminal for the per interface state machine.  At this
+     * point, the master state machine should have removed this interface
+     * specific state machine from its list of possible recipients of
+     * tethering requests.  The state machine itself will hang around until
+     * the garbage collector finds it.
+     */
+    class UnavailableState extends State {
+        @Override
+        public void enter() {
+            mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
+            sendInterfaceState(STATE_UNAVAILABLE);
+        }
+    }
+
+    // Accumulate routes representing "prefixes to be assigned to the local
+    // interface", for subsequent modification of local_network routing.
+    private static ArrayList<RouteInfo> getLocalRoutesFor(
+            String ifname, HashSet<IpPrefix> prefixes) {
+        final ArrayList<RouteInfo> localRoutes = new ArrayList<RouteInfo>();
+        for (IpPrefix ipp : prefixes) {
+            localRoutes.add(new RouteInfo(ipp, null, ifname));
+        }
+        return localRoutes;
+    }
+
+    // Given a prefix like 2001:db8::/64 return an address like 2001:db8::1.
+    private static Inet6Address getLocalDnsIpFor(IpPrefix localPrefix) {
+        final byte[] dnsBytes = localPrefix.getRawAddress();
+        dnsBytes[dnsBytes.length - 1] = getRandomSanitizedByte(DOUG_ADAMS, asByte(0), asByte(1));
+        try {
+            return Inet6Address.getByAddress(null, dnsBytes, 0);
+        } catch (UnknownHostException e) {
+            Slog.wtf(TAG, "Failed to construct Inet6Address from: " + localPrefix);
+            return null;
+        }
+    }
+
+    private static byte getRandomSanitizedByte(byte dflt, byte... excluded) {
+        final byte random = (byte) (new Random()).nextInt();
+        for (int value : excluded) {
+            if (random == value) return dflt;
+        }
+        return random;
+    }
+}
diff --git a/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java b/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java
new file mode 100644
index 0000000..4147413
--- /dev/null
+++ b/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java
@@ -0,0 +1,761 @@
+/*
+ * Copyright (C) 2016 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 android.net.ip;
+
+import static android.net.util.NetworkConstants.IPV6_MIN_MTU;
+import static android.net.util.NetworkConstants.RFC7421_PREFIX_LENGTH;
+import static android.system.OsConstants.AF_INET6;
+import static android.system.OsConstants.IPPROTO_ICMPV6;
+import static android.system.OsConstants.SOCK_RAW;
+import static android.system.OsConstants.SOL_SOCKET;
+import static android.system.OsConstants.SO_BINDTODEVICE;
+import static android.system.OsConstants.SO_SNDTIMEO;
+
+import android.net.IpPrefix;
+import android.net.LinkAddress;
+import android.net.NetworkUtils;
+import android.net.TrafficStats;
+import android.net.util.InterfaceParams;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.StructTimeval;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.TrafficStatsConstants;
+
+import libcore.io.IoBridge;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.nio.BufferOverflowException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
+
+/**
+ * Basic IPv6 Router Advertisement Daemon.
+ *
+ * TODO:
+ *
+ *     - Rewrite using Handler (and friends) so that AlarmManager can deliver
+ *       "kick" messages when it's time to send a multicast RA.
+ *
+ * @hide
+ */
+public class RouterAdvertisementDaemon {
+    private static final String TAG = RouterAdvertisementDaemon.class.getSimpleName();
+    private static final byte ICMPV6_ND_ROUTER_SOLICIT = asByte(133);
+    private static final byte ICMPV6_ND_ROUTER_ADVERT  = asByte(134);
+    private static final int MIN_RA_HEADER_SIZE = 16;
+
+    // Summary of various timers and lifetimes.
+    private static final int MIN_RTR_ADV_INTERVAL_SEC = 300;
+    private static final int MAX_RTR_ADV_INTERVAL_SEC = 600;
+    // In general, router, prefix, and DNS lifetimes are all advised to be
+    // greater than or equal to 3 * MAX_RTR_ADV_INTERVAL.  Here, we double
+    // that to allow for multicast packet loss.
+    //
+    // This MAX_RTR_ADV_INTERVAL_SEC and DEFAULT_LIFETIME are also consistent
+    // with the https://tools.ietf.org/html/rfc7772#section-4 discussion of
+    // "approximately 7 RAs per hour".
+    private static final int DEFAULT_LIFETIME = 6 * MAX_RTR_ADV_INTERVAL_SEC;
+    // From https://tools.ietf.org/html/rfc4861#section-10 .
+    private static final int MIN_DELAY_BETWEEN_RAS_SEC = 3;
+    // Both initial and final RAs, but also for changes in RA contents.
+    // From https://tools.ietf.org/html/rfc4861#section-10 .
+    private static final int  MAX_URGENT_RTR_ADVERTISEMENTS = 5;
+
+    private static final int DAY_IN_SECONDS = 86_400;
+
+    private static final byte[] ALL_NODES = new byte[] {
+            (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1
+    };
+
+    private final InterfaceParams mInterface;
+    private final InetSocketAddress mAllNodes;
+
+    // This lock is to protect the RA from being updated while being
+    // transmitted on another thread  (multicast or unicast).
+    //
+    // TODO: This should be handled with a more RCU-like approach.
+    private final Object mLock = new Object();
+    @GuardedBy("mLock")
+    private final byte[] mRA = new byte[IPV6_MIN_MTU];
+    @GuardedBy("mLock")
+    private int mRaLength;
+    @GuardedBy("mLock")
+    private final DeprecatedInfoTracker mDeprecatedInfoTracker;
+    @GuardedBy("mLock")
+    private RaParams mRaParams;
+
+    private volatile FileDescriptor mSocket;
+    private volatile MulticastTransmitter mMulticastTransmitter;
+    private volatile UnicastResponder mUnicastResponder;
+
+    /** Encapsulate the RA parameters for RouterAdvertisementDaemon.*/
+    public static class RaParams {
+        // Tethered traffic will have the hop limit properly decremented.
+        // Consequently, set the hoplimit greater by one than the upstream
+        // unicast hop limit.
+        //
+        // TODO: Dynamically pass down the IPV6_UNICAST_HOPS value from the
+        // upstream interface for more correct behaviour.
+        static final byte DEFAULT_HOPLIMIT = 65;
+
+        public boolean hasDefaultRoute;
+        public byte hopLimit;
+        public int mtu;
+        public HashSet<IpPrefix> prefixes;
+        public HashSet<Inet6Address> dnses;
+
+        public RaParams() {
+            hasDefaultRoute = false;
+            hopLimit = DEFAULT_HOPLIMIT;
+            mtu = IPV6_MIN_MTU;
+            prefixes = new HashSet<IpPrefix>();
+            dnses = new HashSet<Inet6Address>();
+        }
+
+        public RaParams(RaParams other) {
+            hasDefaultRoute = other.hasDefaultRoute;
+            hopLimit = other.hopLimit;
+            mtu = other.mtu;
+            prefixes = (HashSet) other.prefixes.clone();
+            dnses = (HashSet) other.dnses.clone();
+        }
+
+        /**
+         * Returns the subset of RA parameters that become deprecated when
+         * moving from announcing oldRa to announcing newRa.
+         *
+         * Currently only tracks differences in |prefixes| and |dnses|.
+         */
+        public static RaParams getDeprecatedRaParams(RaParams oldRa, RaParams newRa) {
+            RaParams newlyDeprecated = new RaParams();
+
+            if (oldRa != null) {
+                for (IpPrefix ipp : oldRa.prefixes) {
+                    if (newRa == null || !newRa.prefixes.contains(ipp)) {
+                        newlyDeprecated.prefixes.add(ipp);
+                    }
+                }
+
+                for (Inet6Address dns : oldRa.dnses) {
+                    if (newRa == null || !newRa.dnses.contains(dns)) {
+                        newlyDeprecated.dnses.add(dns);
+                    }
+                }
+            }
+
+            return newlyDeprecated;
+        }
+    }
+
+    private static class DeprecatedInfoTracker {
+        private final HashMap<IpPrefix, Integer> mPrefixes = new HashMap<>();
+        private final HashMap<Inet6Address, Integer> mDnses = new HashMap<>();
+
+        Set<IpPrefix> getPrefixes() {
+            return mPrefixes.keySet();
+        }
+
+        void putPrefixes(Set<IpPrefix> prefixes) {
+            for (IpPrefix ipp : prefixes) {
+                mPrefixes.put(ipp, MAX_URGENT_RTR_ADVERTISEMENTS);
+            }
+        }
+
+        void removePrefixes(Set<IpPrefix> prefixes) {
+            for (IpPrefix ipp : prefixes) {
+                mPrefixes.remove(ipp);
+            }
+        }
+
+        Set<Inet6Address> getDnses() {
+            return mDnses.keySet();
+        }
+
+        void putDnses(Set<Inet6Address> dnses) {
+            for (Inet6Address dns : dnses) {
+                mDnses.put(dns, MAX_URGENT_RTR_ADVERTISEMENTS);
+            }
+        }
+
+        void removeDnses(Set<Inet6Address> dnses) {
+            for (Inet6Address dns : dnses) {
+                mDnses.remove(dns);
+            }
+        }
+
+        boolean isEmpty() {
+            return mPrefixes.isEmpty() && mDnses.isEmpty();
+        }
+
+        private boolean decrementCounters() {
+            boolean removed = decrementCounter(mPrefixes);
+            removed |= decrementCounter(mDnses);
+            return removed;
+        }
+
+        private <T> boolean decrementCounter(HashMap<T, Integer> map) {
+            boolean removed = false;
+
+            for (Iterator<Map.Entry<T, Integer>> it = map.entrySet().iterator();
+                    it.hasNext();) {
+                Map.Entry<T, Integer> kv = it.next();
+                if (kv.getValue() == 0) {
+                    it.remove();
+                    removed = true;
+                } else {
+                    kv.setValue(kv.getValue() - 1);
+                }
+            }
+
+            return removed;
+        }
+    }
+
+
+    public RouterAdvertisementDaemon(InterfaceParams ifParams) {
+        mInterface = ifParams;
+        mAllNodes = new InetSocketAddress(getAllNodesForScopeId(mInterface.index), 0);
+        mDeprecatedInfoTracker = new DeprecatedInfoTracker();
+    }
+
+    /** Build new RA.*/
+    public void buildNewRa(RaParams deprecatedParams, RaParams newParams) {
+        synchronized (mLock) {
+            if (deprecatedParams != null) {
+                mDeprecatedInfoTracker.putPrefixes(deprecatedParams.prefixes);
+                mDeprecatedInfoTracker.putDnses(deprecatedParams.dnses);
+            }
+
+            if (newParams != null) {
+                // Process information that is no longer deprecated.
+                mDeprecatedInfoTracker.removePrefixes(newParams.prefixes);
+                mDeprecatedInfoTracker.removeDnses(newParams.dnses);
+            }
+
+            mRaParams = newParams;
+            assembleRaLocked();
+        }
+
+        maybeNotifyMulticastTransmitter();
+    }
+
+    /** Start router advertisement daemon. */
+    public boolean start() {
+        if (!createSocket()) {
+            return false;
+        }
+
+        mMulticastTransmitter = new MulticastTransmitter();
+        mMulticastTransmitter.start();
+
+        mUnicastResponder = new UnicastResponder();
+        mUnicastResponder.start();
+
+        return true;
+    }
+
+    /** Stop router advertisement daemon. */
+    public void stop() {
+        closeSocket();
+        // Wake up mMulticastTransmitter thread to interrupt a potential 1 day sleep before
+        // the thread's termination.
+        maybeNotifyMulticastTransmitter();
+        mMulticastTransmitter = null;
+        mUnicastResponder = null;
+    }
+
+    @GuardedBy("mLock")
+    private void assembleRaLocked() {
+        final ByteBuffer ra = ByteBuffer.wrap(mRA);
+        ra.order(ByteOrder.BIG_ENDIAN);
+
+        final boolean haveRaParams = (mRaParams != null);
+        boolean shouldSendRA = false;
+
+        try {
+            putHeader(ra, haveRaParams && mRaParams.hasDefaultRoute,
+                    haveRaParams ? mRaParams.hopLimit : RaParams.DEFAULT_HOPLIMIT);
+            putSlla(ra, mInterface.macAddr.toByteArray());
+            mRaLength = ra.position();
+
+            // https://tools.ietf.org/html/rfc5175#section-4 says:
+            //
+            //     "MUST NOT be added to a Router Advertisement message
+            //      if no flags in the option are set."
+            //
+            // putExpandedFlagsOption(ra);
+
+            if (haveRaParams) {
+                putMtu(ra, mRaParams.mtu);
+                mRaLength = ra.position();
+
+                for (IpPrefix ipp : mRaParams.prefixes) {
+                    putPio(ra, ipp, DEFAULT_LIFETIME, DEFAULT_LIFETIME);
+                    mRaLength = ra.position();
+                    shouldSendRA = true;
+                }
+
+                if (mRaParams.dnses.size() > 0) {
+                    putRdnss(ra, mRaParams.dnses, DEFAULT_LIFETIME);
+                    mRaLength = ra.position();
+                    shouldSendRA = true;
+                }
+            }
+
+            for (IpPrefix ipp : mDeprecatedInfoTracker.getPrefixes()) {
+                putPio(ra, ipp, 0, 0);
+                mRaLength = ra.position();
+                shouldSendRA = true;
+            }
+
+            final Set<Inet6Address> deprecatedDnses = mDeprecatedInfoTracker.getDnses();
+            if (!deprecatedDnses.isEmpty()) {
+                putRdnss(ra, deprecatedDnses, 0);
+                mRaLength = ra.position();
+                shouldSendRA = true;
+            }
+        } catch (BufferOverflowException e) {
+            // The packet up to mRaLength  is valid, since it has been updated
+            // progressively as the RA was built. Log an error, and continue
+            // on as best as possible.
+            Log.e(TAG, "Could not construct new RA: " + e);
+        }
+
+        // We have nothing worth announcing; indicate as much to maybeSendRA().
+        if (!shouldSendRA) {
+            mRaLength = 0;
+        }
+    }
+
+    private void maybeNotifyMulticastTransmitter() {
+        final MulticastTransmitter m = mMulticastTransmitter;
+        if (m != null) {
+            m.hup();
+        }
+    }
+
+    private static Inet6Address getAllNodesForScopeId(int scopeId) {
+        try {
+            return Inet6Address.getByAddress("ff02::1", ALL_NODES, scopeId);
+        } catch (UnknownHostException uhe) {
+            Log.wtf(TAG, "Failed to construct ff02::1 InetAddress: " + uhe);
+            return null;
+        }
+    }
+
+    private static byte asByte(int value) {
+        return (byte) value;
+    }
+    private static short asShort(int value) {
+        return (short) value;
+    }
+
+    private static void putHeader(ByteBuffer ra, boolean hasDefaultRoute, byte hopLimit) {
+        /**
+            Router Advertisement Message Format
+
+             0                   1                   2                   3
+             0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+            |     Type      |     Code      |          Checksum             |
+            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+            | Cur Hop Limit |M|O|H|Prf|P|R|R|       Router Lifetime         |
+            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+            |                         Reachable Time                        |
+            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+            |                          Retrans Timer                        |
+            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+            |   Options ...
+            +-+-+-+-+-+-+-+-+-+-+-+-
+        */
+        ra.put(ICMPV6_ND_ROUTER_ADVERT)
+                .put(asByte(0))
+                .putShort(asShort(0))
+                .put(hopLimit)
+                // RFC 4191 "high" preference, iff. advertising a default route.
+                .put(hasDefaultRoute ? asByte(0x08) : asByte(0))
+                .putShort(hasDefaultRoute ? asShort(DEFAULT_LIFETIME) : asShort(0))
+                .putInt(0)
+                .putInt(0);
+    }
+
+    private static void putSlla(ByteBuffer ra, byte[] slla) {
+        /**
+            Source/Target Link-layer Address
+
+             0                   1                   2                   3
+             0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+            |     Type      |    Length     |    Link-Layer Address ...
+            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+        */
+        if (slla == null || slla.length != 6) {
+            // Only IEEE 802.3 6-byte addresses are supported.
+            return;
+        }
+
+        final byte nd_option_slla = 1;
+        final byte slla_num_8octets = 1;
+        ra.put(nd_option_slla)
+            .put(slla_num_8octets)
+            .put(slla);
+    }
+
+    private static void putExpandedFlagsOption(ByteBuffer ra) {
+        /**
+            Router Advertisement Expanded Flags Option
+
+             0                   1                   2                   3
+             0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+            |     Type      |    Length     |         Bit fields available ..
+            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+            ... for assignment                                              |
+            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+         */
+
+        final byte nd_option__efo = 26;
+        final byte efo_num_8octets = 1;
+
+        ra.put(nd_option__efo)
+            .put(efo_num_8octets)
+            .putShort(asShort(0))
+            .putInt(0);
+    }
+
+    private static void putMtu(ByteBuffer ra, int mtu) {
+        /**
+            MTU
+
+             0                   1                   2                   3
+             0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+            |     Type      |    Length     |           Reserved            |
+            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+            |                              MTU                              |
+            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+        */
+        final byte nd_option_mtu = 5;
+        final byte mtu_num_8octs = 1;
+        ra.put(nd_option_mtu)
+            .put(mtu_num_8octs)
+            .putShort(asShort(0))
+            .putInt((mtu < IPV6_MIN_MTU) ? IPV6_MIN_MTU : mtu);
+    }
+
+    private static void putPio(ByteBuffer ra, IpPrefix ipp,
+                               int validTime, int preferredTime) {
+        /**
+            Prefix Information
+
+             0                   1                   2                   3
+             0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+            |     Type      |    Length     | Prefix Length |L|A| Reserved1 |
+            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+            |                         Valid Lifetime                        |
+            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+            |                       Preferred Lifetime                      |
+            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+            |                           Reserved2                           |
+            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+            |                                                               |
+            +                                                               +
+            |                                                               |
+            +                            Prefix                             +
+            |                                                               |
+            +                                                               +
+            |                                                               |
+            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+        */
+        final int prefixLength = ipp.getPrefixLength();
+        if (prefixLength != 64) {
+            return;
+        }
+        final byte nd_option_pio = 3;
+        final byte pio_num_8octets = 4;
+
+        if (validTime < 0) validTime = 0;
+        if (preferredTime < 0) preferredTime = 0;
+        if (preferredTime > validTime) preferredTime = validTime;
+
+        final byte[] addr = ipp.getAddress().getAddress();
+        ra.put(nd_option_pio)
+            .put(pio_num_8octets)
+            .put(asByte(prefixLength))
+            .put(asByte(0xc0)) /* L & A set */
+            .putInt(validTime)
+            .putInt(preferredTime)
+            .putInt(0)
+            .put(addr);
+    }
+
+    private static void putRio(ByteBuffer ra, IpPrefix ipp) {
+        /**
+            Route Information Option
+
+             0                   1                   2                   3
+             0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+            |     Type      |    Length     | Prefix Length |Resvd|Prf|Resvd|
+            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+            |                        Route Lifetime                         |
+            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+            |                   Prefix (Variable Length)                    |
+            .                                                               .
+            .                                                               .
+            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+         */
+        final int prefixLength = ipp.getPrefixLength();
+        if (prefixLength > 64) {
+            return;
+        }
+        final byte nd_option_rio = 24;
+        final byte rio_num_8octets = asByte(
+                (prefixLength == 0) ? 1 : (prefixLength <= 8) ? 2 : 3);
+
+        final byte[] addr = ipp.getAddress().getAddress();
+        ra.put(nd_option_rio)
+            .put(rio_num_8octets)
+            .put(asByte(prefixLength))
+            .put(asByte(0x18))
+            .putInt(DEFAULT_LIFETIME);
+
+        // Rely upon an IpPrefix's address being properly zeroed.
+        if (prefixLength > 0) {
+            ra.put(addr, 0, (prefixLength <= 64) ? 8 : 16);
+        }
+    }
+
+    private static void putRdnss(ByteBuffer ra, Set<Inet6Address> dnses, int lifetime) {
+        /**
+            Recursive DNS Server (RDNSS) Option
+
+             0                   1                   2                   3
+             0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+            |     Type      |     Length    |           Reserved            |
+            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+            |                           Lifetime                            |
+            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+            |                                                               |
+            :            Addresses of IPv6 Recursive DNS Servers            :
+            |                                                               |
+            +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+         */
+
+        final HashSet<Inet6Address> filteredDnses = new HashSet<>();
+        for (Inet6Address dns : dnses) {
+            if ((new LinkAddress(dns, RFC7421_PREFIX_LENGTH)).isGlobalPreferred()) {
+                filteredDnses.add(dns);
+            }
+        }
+        if (filteredDnses.isEmpty()) return;
+
+        final byte nd_option_rdnss = 25;
+        final byte rdnss_num_8octets = asByte(dnses.size() * 2 + 1);
+        ra.put(nd_option_rdnss)
+            .put(rdnss_num_8octets)
+            .putShort(asShort(0))
+            .putInt(lifetime);
+
+        for (Inet6Address dns : filteredDnses) {
+            // NOTE: If the full of list DNS servers doesn't fit in the packet,
+            // this code will cause a buffer overflow and the RA won't include
+            // this instance of the option at all.
+            //
+            // TODO: Consider looking at ra.remaining() to determine how many
+            // DNS servers will fit, and adding only those.
+            ra.put(dns.getAddress());
+        }
+    }
+
+    private boolean createSocket() {
+        final int send_timout_ms = 300;
+
+        final int oldTag = TrafficStats.getAndSetThreadStatsTag(
+                TrafficStatsConstants.TAG_SYSTEM_NEIGHBOR);
+        try {
+            mSocket = Os.socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
+            // Setting SNDTIMEO is purely for defensive purposes.
+            Os.setsockoptTimeval(
+                    mSocket, SOL_SOCKET, SO_SNDTIMEO, StructTimeval.fromMillis(send_timout_ms));
+            Os.setsockoptIfreq(mSocket, SOL_SOCKET, SO_BINDTODEVICE, mInterface.name);
+            NetworkUtils.protectFromVpn(mSocket);
+            NetworkUtils.setupRaSocket(mSocket, mInterface.index);
+        } catch (ErrnoException | IOException e) {
+            Log.e(TAG, "Failed to create RA daemon socket: " + e);
+            return false;
+        } finally {
+            TrafficStats.setThreadStatsTag(oldTag);
+        }
+
+        return true;
+    }
+
+    private void closeSocket() {
+        if (mSocket != null) {
+            try {
+                IoBridge.closeAndSignalBlockedThreads(mSocket);
+            } catch (IOException ignored) { }
+        }
+        mSocket = null;
+    }
+
+    private boolean isSocketValid() {
+        final FileDescriptor s = mSocket;
+        return (s != null) && s.valid();
+    }
+
+    private boolean isSuitableDestination(InetSocketAddress dest) {
+        if (mAllNodes.equals(dest)) {
+            return true;
+        }
+
+        final InetAddress destip = dest.getAddress();
+        return (destip instanceof Inet6Address)
+               && destip.isLinkLocalAddress()
+               && (((Inet6Address) destip).getScopeId() == mInterface.index);
+    }
+
+    private void maybeSendRA(InetSocketAddress dest) {
+        if (dest == null || !isSuitableDestination(dest)) {
+            dest = mAllNodes;
+        }
+
+        try {
+            synchronized (mLock) {
+                if (mRaLength < MIN_RA_HEADER_SIZE) {
+                    // No actual RA to send.
+                    return;
+                }
+                Os.sendto(mSocket, mRA, 0, mRaLength, 0, dest);
+            }
+            Log.d(TAG, "RA sendto " + dest.getAddress().getHostAddress());
+        } catch (ErrnoException | SocketException e) {
+            if (isSocketValid()) {
+                Log.e(TAG, "sendto error: " + e);
+            }
+        }
+    }
+
+    private final class UnicastResponder extends Thread {
+        private final InetSocketAddress mSolicitor = new InetSocketAddress();
+        // The recycled buffer for receiving Router Solicitations from clients.
+        // If the RS is larger than IPV6_MIN_MTU the packets are truncated.
+        // This is fine since currently only byte 0 is examined anyway.
+        private final byte[] mSolicitation = new byte[IPV6_MIN_MTU];
+
+        @Override
+        public void run() {
+            while (isSocketValid()) {
+                try {
+                    // Blocking receive.
+                    final int rval = Os.recvfrom(
+                            mSocket, mSolicitation, 0, mSolicitation.length, 0, mSolicitor);
+                    // Do the least possible amount of validation.
+                    if (rval < 1 || mSolicitation[0] != ICMPV6_ND_ROUTER_SOLICIT) {
+                        continue;
+                    }
+                } catch (ErrnoException | SocketException e) {
+                    if (isSocketValid()) {
+                        Log.e(TAG, "recvfrom error: " + e);
+                    }
+                    continue;
+                }
+
+                maybeSendRA(mSolicitor);
+            }
+        }
+    }
+
+    // TODO: Consider moving this to run on a provided Looper as a Handler,
+    // with WakeupMessage-style messages providing the timer driven input.
+    private final class MulticastTransmitter extends Thread {
+        private final Random mRandom = new Random();
+        private final AtomicInteger mUrgentAnnouncements = new AtomicInteger(0);
+
+        @Override
+        public void run() {
+            while (isSocketValid()) {
+                try {
+                    Thread.sleep(getNextMulticastTransmitDelayMs());
+                } catch (InterruptedException ignored) {
+                    // Stop sleeping, immediately send an RA, and continue.
+                }
+
+                maybeSendRA(mAllNodes);
+                synchronized (mLock) {
+                    if (mDeprecatedInfoTracker.decrementCounters()) {
+                        // At least one deprecated PIO has been removed;
+                        // reassemble the RA.
+                        assembleRaLocked();
+                    }
+                }
+            }
+        }
+
+        public void hup() {
+            // Set to one fewer that the desired number, because as soon as
+            // the thread interrupt is processed we immediately send an RA
+            // and mUrgentAnnouncements is not examined until the subsequent
+            // sleep interval computation (i.e. this way we send 3 and not 4).
+            mUrgentAnnouncements.set(MAX_URGENT_RTR_ADVERTISEMENTS - 1);
+            interrupt();
+        }
+
+        private int getNextMulticastTransmitDelaySec() {
+            boolean deprecationInProgress = false;
+            synchronized (mLock) {
+                if (mRaLength < MIN_RA_HEADER_SIZE) {
+                    // No actual RA to send; just sleep for 1 day.
+                    return DAY_IN_SECONDS;
+                }
+                deprecationInProgress = !mDeprecatedInfoTracker.isEmpty();
+            }
+
+            final int urgentPending = mUrgentAnnouncements.getAndDecrement();
+            if ((urgentPending > 0) || deprecationInProgress) {
+                return MIN_DELAY_BETWEEN_RAS_SEC;
+            }
+
+            return MIN_RTR_ADV_INTERVAL_SEC + mRandom.nextInt(
+                    MAX_RTR_ADV_INTERVAL_SEC - MIN_RTR_ADV_INTERVAL_SEC);
+        }
+
+        private long getNextMulticastTransmitDelayMs() {
+            return 1000 * (long) getNextMulticastTransmitDelaySec();
+        }
+    }
+}
diff --git a/Tethering/src/android/net/util/InterfaceSet.java b/Tethering/src/android/net/util/InterfaceSet.java
new file mode 100644
index 0000000..7589787
--- /dev/null
+++ b/Tethering/src/android/net/util/InterfaceSet.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2018 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 android.net.util;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.StringJoiner;
+
+
+/**
+ * @hide
+ */
+public class InterfaceSet {
+    public final Set<String> ifnames;
+
+    public InterfaceSet(String... names) {
+        final Set<String> nameSet = new HashSet<>();
+        for (String name : names) {
+            if (name != null) nameSet.add(name);
+        }
+        ifnames = Collections.unmodifiableSet(nameSet);
+    }
+
+    @Override
+    public String toString() {
+        final StringJoiner sj = new StringJoiner(",", "[", "]");
+        for (String ifname : ifnames) sj.add(ifname);
+        return sj.toString();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        return obj != null
+                && obj instanceof InterfaceSet
+                && ifnames.equals(((InterfaceSet) obj).ifnames);
+    }
+}
diff --git a/Tethering/tests/unit/Android.bp b/Tethering/tests/unit/Android.bp
index 089bbd3..da62107 100644
--- a/Tethering/tests/unit/Android.bp
+++ b/Tethering/tests/unit/Android.bp
@@ -43,5 +43,8 @@
     name: "tethering-tests-src",
     srcs: [
         "src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java",
+        "src/android/net/dhcp/DhcpServingParamsParcelExtTest.java",
+        "src/android/net/ip/IpServerTest.java",
+        "src/android/net/util/InterfaceSetTest.java",
     ],
 }
diff --git a/Tethering/tests/unit/src/android/net/dhcp/DhcpServingParamsParcelExtTest.java b/Tethering/tests/unit/src/android/net/dhcp/DhcpServingParamsParcelExtTest.java
new file mode 100644
index 0000000..e01ac7f
--- /dev/null
+++ b/Tethering/tests/unit/src/android/net/dhcp/DhcpServingParamsParcelExtTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2018 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 android.net.dhcp;
+
+import static android.net.InetAddresses.parseNumericAddress;
+
+import static com.google.android.collect.Sets.newHashSet;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.net.LinkAddress;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.Inet4Address;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class DhcpServingParamsParcelExtTest {
+    private static final Inet4Address TEST_ADDRESS = inet4Addr("192.168.0.123");
+    private static final int TEST_ADDRESS_PARCELED = 0xc0a8007b;
+    private static final int TEST_PREFIX_LENGTH = 17;
+    private static final int TEST_LEASE_TIME_SECS = 120;
+    private static final int TEST_MTU = 1000;
+    private static final Set<Inet4Address> TEST_ADDRESS_SET =
+            newHashSet(inet4Addr("192.168.1.123"), inet4Addr("192.168.1.124"));
+    private static final Set<Integer> TEST_ADDRESS_SET_PARCELED =
+            newHashSet(0xc0a8017b, 0xc0a8017c);
+
+    private DhcpServingParamsParcelExt mParcel;
+
+    @Before
+    public void setUp() {
+        mParcel = new DhcpServingParamsParcelExt();
+    }
+
+    @Test
+    public void testSetServerAddr() {
+        mParcel.setServerAddr(new LinkAddress(TEST_ADDRESS, TEST_PREFIX_LENGTH));
+
+        assertEquals(TEST_ADDRESS_PARCELED, mParcel.serverAddr);
+        assertEquals(TEST_PREFIX_LENGTH, mParcel.serverAddrPrefixLength);
+    }
+
+    @Test
+    public void testSetDefaultRouters() {
+        mParcel.setDefaultRouters(TEST_ADDRESS_SET);
+        assertEquals(TEST_ADDRESS_SET_PARCELED, asSet(mParcel.defaultRouters));
+    }
+
+    @Test
+    public void testSetDnsServers() {
+        mParcel.setDnsServers(TEST_ADDRESS_SET);
+        assertEquals(TEST_ADDRESS_SET_PARCELED, asSet(mParcel.dnsServers));
+    }
+
+    @Test
+    public void testSetExcludedAddrs() {
+        mParcel.setExcludedAddrs(TEST_ADDRESS_SET);
+        assertEquals(TEST_ADDRESS_SET_PARCELED, asSet(mParcel.excludedAddrs));
+    }
+
+    @Test
+    public void testSetDhcpLeaseTimeSecs() {
+        mParcel.setDhcpLeaseTimeSecs(TEST_LEASE_TIME_SECS);
+        assertEquals(TEST_LEASE_TIME_SECS, mParcel.dhcpLeaseTimeSecs);
+    }
+
+    @Test
+    public void testSetLinkMtu() {
+        mParcel.setLinkMtu(TEST_MTU);
+        assertEquals(TEST_MTU, mParcel.linkMtu);
+    }
+
+    @Test
+    public void testSetMetered() {
+        mParcel.setMetered(true);
+        assertTrue(mParcel.metered);
+        mParcel.setMetered(false);
+        assertFalse(mParcel.metered);
+    }
+
+    private static Inet4Address inet4Addr(String addr) {
+        return (Inet4Address) parseNumericAddress(addr);
+    }
+
+    private static Set<Integer> asSet(int[] ints) {
+        return IntStream.of(ints).boxed().collect(Collectors.toSet());
+    }
+}
diff --git a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
new file mode 100644
index 0000000..4358cd6
--- /dev/null
+++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -0,0 +1,526 @@
+/*
+ * Copyright (C) 2016 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 android.net.ip;
+
+import static android.net.ConnectivityManager.TETHERING_BLUETOOTH;
+import static android.net.ConnectivityManager.TETHERING_USB;
+import static android.net.ConnectivityManager.TETHERING_WIFI;
+import static android.net.ConnectivityManager.TETHERING_WIFI_P2P;
+import static android.net.ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR;
+import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR;
+import static android.net.ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR;
+import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
+import static android.net.ip.IpServer.STATE_AVAILABLE;
+import static android.net.ip.IpServer.STATE_LOCAL_ONLY;
+import static android.net.ip.IpServer.STATE_TETHERED;
+import static android.net.ip.IpServer.STATE_UNAVAILABLE;
+import static android.net.shared.Inet4AddressUtils.intToInet4AddressHTH;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.net.INetd;
+import android.net.INetworkStatsService;
+import android.net.InterfaceConfiguration;
+import android.net.IpPrefix;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.MacAddress;
+import android.net.RouteInfo;
+import android.net.dhcp.DhcpServingParamsParcel;
+import android.net.dhcp.IDhcpServer;
+import android.net.dhcp.IDhcpServerCallbacks;
+import android.net.util.InterfaceParams;
+import android.net.util.InterfaceSet;
+import android.net.util.SharedLog;
+import android.os.INetworkManagementService;
+import android.os.RemoteException;
+import android.os.test.TestLooper;
+import android.text.TextUtils;
+
+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.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.net.Inet4Address;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class IpServerTest {
+    private static final String IFACE_NAME = "testnet1";
+    private static final String UPSTREAM_IFACE = "upstream0";
+    private static final String UPSTREAM_IFACE2 = "upstream1";
+    private static final int DHCP_LEASE_TIME_SECS = 3600;
+
+    private static final InterfaceParams TEST_IFACE_PARAMS = new InterfaceParams(
+            IFACE_NAME, 42 /* index */, MacAddress.ALL_ZEROS_ADDRESS, 1500 /* defaultMtu */);
+
+    private static final int MAKE_DHCPSERVER_TIMEOUT_MS = 1000;
+
+    @Mock private INetworkManagementService mNMService;
+    @Mock private INetd mNetd;
+    @Mock private INetworkStatsService mStatsService;
+    @Mock private IpServer.Callback mCallback;
+    @Mock private InterfaceConfiguration mInterfaceConfiguration;
+    @Mock private SharedLog mSharedLog;
+    @Mock private IDhcpServer mDhcpServer;
+    @Mock private RouterAdvertisementDaemon mRaDaemon;
+    @Mock private IpServer.Dependencies mDependencies;
+
+    @Captor private ArgumentCaptor<DhcpServingParamsParcel> mDhcpParamsCaptor;
+
+    private final TestLooper mLooper = new TestLooper();
+    private final ArgumentCaptor<LinkProperties> mLinkPropertiesCaptor =
+            ArgumentCaptor.forClass(LinkProperties.class);
+    private IpServer mIpServer;
+
+    private void initStateMachine(int interfaceType) throws Exception {
+        initStateMachine(interfaceType, false /* usingLegacyDhcp */);
+    }
+
+    private void initStateMachine(int interfaceType, boolean usingLegacyDhcp) throws Exception {
+        doAnswer(inv -> {
+            final IDhcpServerCallbacks cb = inv.getArgument(2);
+            new Thread(() -> {
+                try {
+                    cb.onDhcpServerCreated(STATUS_SUCCESS, mDhcpServer);
+                } catch (RemoteException e) {
+                    fail(e.getMessage());
+                }
+            }).run();
+            return null;
+        }).when(mDependencies).makeDhcpServer(any(), mDhcpParamsCaptor.capture(), any());
+        when(mDependencies.getRouterAdvertisementDaemon(any())).thenReturn(mRaDaemon);
+        when(mDependencies.getInterfaceParams(IFACE_NAME)).thenReturn(TEST_IFACE_PARAMS);
+        when(mDependencies.getNetdService()).thenReturn(mNetd);
+
+        mIpServer = new IpServer(
+                IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog,
+                mNMService, mStatsService, mCallback, usingLegacyDhcp, mDependencies);
+        mIpServer.start();
+        // Starting the state machine always puts us in a consistent state and notifies
+        // the rest of the world that we've changed from an unknown to available state.
+        mLooper.dispatchAll();
+        reset(mNMService, mStatsService, mCallback);
+        when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration);
+
+        when(mRaDaemon.start()).thenReturn(true);
+    }
+
+    private void initTetheredStateMachine(int interfaceType, String upstreamIface)
+            throws Exception {
+        initTetheredStateMachine(interfaceType, upstreamIface, false);
+    }
+
+    private void initTetheredStateMachine(int interfaceType, String upstreamIface,
+            boolean usingLegacyDhcp) throws Exception {
+        initStateMachine(interfaceType, usingLegacyDhcp);
+        dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED);
+        if (upstreamIface != null) {
+            dispatchTetherConnectionChanged(upstreamIface);
+        }
+        reset(mNMService, mStatsService, mCallback);
+        when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration);
+    }
+
+    @Before public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        when(mSharedLog.forSubComponent(anyString())).thenReturn(mSharedLog);
+    }
+
+    @Test
+    public void startsOutAvailable() {
+        mIpServer = new IpServer(IFACE_NAME, mLooper.getLooper(),
+                TETHERING_BLUETOOTH, mSharedLog, mNMService, mStatsService, mCallback,
+                false /* usingLegacyDhcp */, mDependencies);
+        mIpServer.start();
+        mLooper.dispatchAll();
+        verify(mCallback).updateInterfaceState(
+                mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
+        verify(mCallback).updateLinkProperties(eq(mIpServer), any(LinkProperties.class));
+        verifyNoMoreInteractions(mCallback, mNMService, mStatsService);
+    }
+
+    @Test
+    public void shouldDoNothingUntilRequested() throws Exception {
+        initStateMachine(TETHERING_BLUETOOTH);
+        final int [] noOp_commands = {
+            IpServer.CMD_TETHER_UNREQUESTED,
+            IpServer.CMD_IP_FORWARDING_ENABLE_ERROR,
+            IpServer.CMD_IP_FORWARDING_DISABLE_ERROR,
+            IpServer.CMD_START_TETHERING_ERROR,
+            IpServer.CMD_STOP_TETHERING_ERROR,
+            IpServer.CMD_SET_DNS_FORWARDERS_ERROR,
+            IpServer.CMD_TETHER_CONNECTION_CHANGED
+        };
+        for (int command : noOp_commands) {
+            // None of these commands should trigger us to request action from
+            // the rest of the system.
+            dispatchCommand(command);
+            verifyNoMoreInteractions(mNMService, mStatsService, mCallback);
+        }
+    }
+
+    @Test
+    public void handlesImmediateInterfaceDown() throws Exception {
+        initStateMachine(TETHERING_BLUETOOTH);
+
+        dispatchCommand(IpServer.CMD_INTERFACE_DOWN);
+        verify(mCallback).updateInterfaceState(
+                mIpServer, STATE_UNAVAILABLE, TETHER_ERROR_NO_ERROR);
+        verify(mCallback).updateLinkProperties(eq(mIpServer), any(LinkProperties.class));
+        verifyNoMoreInteractions(mNMService, mStatsService, mCallback);
+    }
+
+    @Test
+    public void canBeTethered() throws Exception {
+        initStateMachine(TETHERING_BLUETOOTH);
+
+        dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED);
+        InOrder inOrder = inOrder(mCallback, mNMService);
+        inOrder.verify(mNMService).tetherInterface(IFACE_NAME);
+        inOrder.verify(mCallback).updateInterfaceState(
+                mIpServer, STATE_TETHERED, TETHER_ERROR_NO_ERROR);
+        inOrder.verify(mCallback).updateLinkProperties(
+                eq(mIpServer), any(LinkProperties.class));
+        verifyNoMoreInteractions(mNMService, mStatsService, mCallback);
+    }
+
+    @Test
+    public void canUnrequestTethering() throws Exception {
+        initTetheredStateMachine(TETHERING_BLUETOOTH, null);
+
+        dispatchCommand(IpServer.CMD_TETHER_UNREQUESTED);
+        InOrder inOrder = inOrder(mNMService, mNetd, mStatsService, mCallback);
+        inOrder.verify(mNMService).untetherInterface(IFACE_NAME);
+        inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg -> IFACE_NAME.equals(cfg.ifName)));
+        inOrder.verify(mCallback).updateInterfaceState(
+                mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
+        inOrder.verify(mCallback).updateLinkProperties(
+                eq(mIpServer), any(LinkProperties.class));
+        verifyNoMoreInteractions(mNMService, mStatsService, mCallback);
+    }
+
+    @Test
+    public void canBeTetheredAsUsb() throws Exception {
+        initStateMachine(TETHERING_USB);
+
+        dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED);
+        InOrder inOrder = inOrder(mCallback, mNMService);
+        inOrder.verify(mNMService).getInterfaceConfig(IFACE_NAME);
+        inOrder.verify(mNMService).setInterfaceConfig(IFACE_NAME, mInterfaceConfiguration);
+        inOrder.verify(mNMService).tetherInterface(IFACE_NAME);
+        inOrder.verify(mCallback).updateInterfaceState(
+                mIpServer, STATE_TETHERED, TETHER_ERROR_NO_ERROR);
+        inOrder.verify(mCallback).updateLinkProperties(
+                eq(mIpServer), mLinkPropertiesCaptor.capture());
+        assertIPv4AddressAndDirectlyConnectedRoute(mLinkPropertiesCaptor.getValue());
+        verifyNoMoreInteractions(mNMService, mStatsService, mCallback);
+    }
+
+    @Test
+    public void canBeTetheredAsWifiP2p() throws Exception {
+        initStateMachine(TETHERING_WIFI_P2P);
+
+        dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_LOCAL_ONLY);
+        InOrder inOrder = inOrder(mCallback, mNMService);
+        inOrder.verify(mNMService).getInterfaceConfig(IFACE_NAME);
+        inOrder.verify(mNMService).setInterfaceConfig(IFACE_NAME, mInterfaceConfiguration);
+        inOrder.verify(mNMService).tetherInterface(IFACE_NAME);
+        inOrder.verify(mCallback).updateInterfaceState(
+                mIpServer, STATE_LOCAL_ONLY, TETHER_ERROR_NO_ERROR);
+        inOrder.verify(mCallback).updateLinkProperties(
+                eq(mIpServer), mLinkPropertiesCaptor.capture());
+        assertIPv4AddressAndDirectlyConnectedRoute(mLinkPropertiesCaptor.getValue());
+        verifyNoMoreInteractions(mNMService, mStatsService, mCallback);
+    }
+
+    @Test
+    public void handlesFirstUpstreamChange() throws Exception {
+        initTetheredStateMachine(TETHERING_BLUETOOTH, null);
+
+        // Telling the state machine about its upstream interface triggers
+        // a little more configuration.
+        dispatchTetherConnectionChanged(UPSTREAM_IFACE);
+        InOrder inOrder = inOrder(mNMService);
+        inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE);
+        inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
+        verifyNoMoreInteractions(mNMService, mStatsService, mCallback);
+    }
+
+    @Test
+    public void handlesChangingUpstream() throws Exception {
+        initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE);
+
+        dispatchTetherConnectionChanged(UPSTREAM_IFACE2);
+        InOrder inOrder = inOrder(mNMService, mStatsService);
+        inOrder.verify(mStatsService).forceUpdate();
+        inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
+        inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE);
+        inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2);
+        inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2);
+        verifyNoMoreInteractions(mNMService, mStatsService, mCallback);
+    }
+
+    @Test
+    public void handlesChangingUpstreamNatFailure() throws Exception {
+        initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE);
+
+        doThrow(RemoteException.class).when(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2);
+
+        dispatchTetherConnectionChanged(UPSTREAM_IFACE2);
+        InOrder inOrder = inOrder(mNMService, mStatsService);
+        inOrder.verify(mStatsService).forceUpdate();
+        inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
+        inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE);
+        inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2);
+        inOrder.verify(mStatsService).forceUpdate();
+        inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2);
+        inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE2);
+    }
+
+    @Test
+    public void handlesChangingUpstreamInterfaceForwardingFailure() throws Exception {
+        initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE);
+
+        doThrow(RemoteException.class).when(mNMService).startInterfaceForwarding(
+                IFACE_NAME, UPSTREAM_IFACE2);
+
+        dispatchTetherConnectionChanged(UPSTREAM_IFACE2);
+        InOrder inOrder = inOrder(mNMService, mStatsService);
+        inOrder.verify(mStatsService).forceUpdate();
+        inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
+        inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE);
+        inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2);
+        inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2);
+        inOrder.verify(mStatsService).forceUpdate();
+        inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2);
+        inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE2);
+    }
+
+    @Test
+    public void canUnrequestTetheringWithUpstream() throws Exception {
+        initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE);
+
+        dispatchCommand(IpServer.CMD_TETHER_UNREQUESTED);
+        InOrder inOrder = inOrder(mNMService, mNetd, mStatsService, mCallback);
+        inOrder.verify(mStatsService).forceUpdate();
+        inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
+        inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE);
+        inOrder.verify(mNMService).untetherInterface(IFACE_NAME);
+        inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg -> IFACE_NAME.equals(cfg.ifName)));
+        inOrder.verify(mCallback).updateInterfaceState(
+                mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
+        inOrder.verify(mCallback).updateLinkProperties(
+                eq(mIpServer), any(LinkProperties.class));
+        verifyNoMoreInteractions(mNMService, mStatsService, mCallback);
+    }
+
+    @Test
+    public void interfaceDownLeadsToUnavailable() throws Exception {
+        for (boolean shouldThrow : new boolean[]{true, false}) {
+            initTetheredStateMachine(TETHERING_USB, null);
+
+            if (shouldThrow) {
+                doThrow(RemoteException.class).when(mNMService).untetherInterface(IFACE_NAME);
+            }
+            dispatchCommand(IpServer.CMD_INTERFACE_DOWN);
+            InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration, mCallback);
+            usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown();
+            usbTeardownOrder.verify(mNMService).setInterfaceConfig(
+                    IFACE_NAME, mInterfaceConfiguration);
+            usbTeardownOrder.verify(mCallback).updateInterfaceState(
+                    mIpServer, STATE_UNAVAILABLE, TETHER_ERROR_NO_ERROR);
+            usbTeardownOrder.verify(mCallback).updateLinkProperties(
+                    eq(mIpServer), mLinkPropertiesCaptor.capture());
+            assertNoAddressesNorRoutes(mLinkPropertiesCaptor.getValue());
+        }
+    }
+
+    @Test
+    public void usbShouldBeTornDownOnTetherError() throws Exception {
+        initStateMachine(TETHERING_USB);
+
+        doThrow(RemoteException.class).when(mNMService).tetherInterface(IFACE_NAME);
+        dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED);
+        InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration, mCallback);
+        usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown();
+        usbTeardownOrder.verify(mNMService).setInterfaceConfig(
+                IFACE_NAME, mInterfaceConfiguration);
+        usbTeardownOrder.verify(mCallback).updateInterfaceState(
+                mIpServer, STATE_AVAILABLE, TETHER_ERROR_TETHER_IFACE_ERROR);
+        usbTeardownOrder.verify(mCallback).updateLinkProperties(
+                eq(mIpServer), mLinkPropertiesCaptor.capture());
+        assertNoAddressesNorRoutes(mLinkPropertiesCaptor.getValue());
+    }
+
+    @Test
+    public void shouldTearDownUsbOnUpstreamError() throws Exception {
+        initTetheredStateMachine(TETHERING_USB, null);
+
+        doThrow(RemoteException.class).when(mNMService).enableNat(anyString(), anyString());
+        dispatchTetherConnectionChanged(UPSTREAM_IFACE);
+        InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration, mCallback);
+        usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown();
+        usbTeardownOrder.verify(mNMService).setInterfaceConfig(IFACE_NAME, mInterfaceConfiguration);
+        usbTeardownOrder.verify(mCallback).updateInterfaceState(
+                mIpServer, STATE_AVAILABLE, TETHER_ERROR_ENABLE_NAT_ERROR);
+        usbTeardownOrder.verify(mCallback).updateLinkProperties(
+                eq(mIpServer), mLinkPropertiesCaptor.capture());
+        assertNoAddressesNorRoutes(mLinkPropertiesCaptor.getValue());
+    }
+
+    @Test
+    public void ignoresDuplicateUpstreamNotifications() throws Exception {
+        initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE);
+
+        verifyNoMoreInteractions(mNMService, mStatsService, mCallback);
+
+        for (int i = 0; i < 5; i++) {
+            dispatchTetherConnectionChanged(UPSTREAM_IFACE);
+            verifyNoMoreInteractions(mNMService, mStatsService, mCallback);
+        }
+    }
+
+    @Test
+    public void startsDhcpServer() throws Exception {
+        initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE);
+        dispatchTetherConnectionChanged(UPSTREAM_IFACE);
+
+        assertDhcpStarted(new IpPrefix("192.168.43.0/24"));
+    }
+
+    @Test
+    public void startsDhcpServerOnBluetooth() throws Exception {
+        initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE);
+        dispatchTetherConnectionChanged(UPSTREAM_IFACE);
+
+        assertDhcpStarted(new IpPrefix("192.168.44.0/24"));
+    }
+
+    @Test
+    public void startsDhcpServerOnWifiP2p() throws Exception {
+        initTetheredStateMachine(TETHERING_WIFI_P2P, UPSTREAM_IFACE);
+        dispatchTetherConnectionChanged(UPSTREAM_IFACE);
+
+        assertDhcpStarted(new IpPrefix("192.168.49.0/24"));
+    }
+
+    @Test
+    public void doesNotStartDhcpServerIfDisabled() throws Exception {
+        initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, true /* usingLegacyDhcp */);
+        dispatchTetherConnectionChanged(UPSTREAM_IFACE);
+
+        verify(mDependencies, never()).makeDhcpServer(any(), any(), any());
+    }
+
+    private void assertDhcpStarted(IpPrefix expectedPrefix) throws Exception {
+        verify(mDependencies, times(1)).makeDhcpServer(eq(IFACE_NAME), any(), any());
+        verify(mDhcpServer, timeout(MAKE_DHCPSERVER_TIMEOUT_MS).times(1)).start(any());
+        final DhcpServingParamsParcel params = mDhcpParamsCaptor.getValue();
+        // Last address byte is random
+        assertTrue(expectedPrefix.contains(intToInet4AddressHTH(params.serverAddr)));
+        assertEquals(expectedPrefix.getPrefixLength(), params.serverAddrPrefixLength);
+        assertEquals(1, params.defaultRouters.length);
+        assertEquals(params.serverAddr, params.defaultRouters[0]);
+        assertEquals(1, params.dnsServers.length);
+        assertEquals(params.serverAddr, params.dnsServers[0]);
+        assertEquals(DHCP_LEASE_TIME_SECS, params.dhcpLeaseTimeSecs);
+    }
+
+    /**
+     * Send a command to the state machine under test, and run the event loop to idle.
+     *
+     * @param command One of the IpServer.CMD_* constants.
+     * @param arg1 An additional argument to pass.
+     */
+    private void dispatchCommand(int command, int arg1) {
+        mIpServer.sendMessage(command, arg1);
+        mLooper.dispatchAll();
+    }
+
+    /**
+     * Send a command to the state machine under test, and run the event loop to idle.
+     *
+     * @param command One of the IpServer.CMD_* constants.
+     */
+    private void dispatchCommand(int command) {
+        mIpServer.sendMessage(command);
+        mLooper.dispatchAll();
+    }
+
+    /**
+     * Special override to tell the state machine that the upstream interface has changed.
+     *
+     * @see #dispatchCommand(int)
+     * @param upstreamIface String name of upstream interface (or null)
+     */
+    private void dispatchTetherConnectionChanged(String upstreamIface) {
+        mIpServer.sendMessage(IpServer.CMD_TETHER_CONNECTION_CHANGED,
+                new InterfaceSet(upstreamIface));
+        mLooper.dispatchAll();
+    }
+
+    private void assertIPv4AddressAndDirectlyConnectedRoute(LinkProperties lp) {
+        // Find the first IPv4 LinkAddress.
+        LinkAddress addr4 = null;
+        for (LinkAddress addr : lp.getLinkAddresses()) {
+            if (!(addr.getAddress() instanceof Inet4Address)) continue;
+            addr4 = addr;
+            break;
+        }
+        assertNotNull("missing IPv4 address", addr4);
+
+        // Assert the presence of the associated directly connected route.
+        final RouteInfo directlyConnected = new RouteInfo(addr4, null, lp.getInterfaceName());
+        assertTrue("missing directly connected route: '" + directlyConnected.toString() + "'",
+                   lp.getRoutes().contains(directlyConnected));
+    }
+
+    private void assertNoAddressesNorRoutes(LinkProperties lp) {
+        assertTrue(lp.getLinkAddresses().isEmpty());
+        assertTrue(lp.getRoutes().isEmpty());
+        // We also check that interface name is non-empty, because we should
+        // never see an empty interface name in any LinkProperties update.
+        assertFalse(TextUtils.isEmpty(lp.getInterfaceName()));
+    }
+}
diff --git a/Tethering/tests/unit/src/android/net/util/InterfaceSetTest.java b/Tethering/tests/unit/src/android/net/util/InterfaceSetTest.java
new file mode 100644
index 0000000..ea084b6
--- /dev/null
+++ b/Tethering/tests/unit/src/android/net/util/InterfaceSetTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2018 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 android.net.util;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class InterfaceSetTest {
+    @Test
+    public void testNullNamesIgnored() {
+        final InterfaceSet set = new InterfaceSet(null, "if1", null, "if2", null);
+        assertEquals(2, set.ifnames.size());
+        assertTrue(set.ifnames.contains("if1"));
+        assertTrue(set.ifnames.contains("if2"));
+    }
+
+    @Test
+    public void testToString() {
+        final InterfaceSet set = new InterfaceSet("if1", "if2");
+        final String setString = set.toString();
+        assertTrue(setString.equals("[if1,if2]") || setString.equals("[if2,if1]"));
+    }
+
+    @Test
+    public void testToString_Empty() {
+        final InterfaceSet set = new InterfaceSet(null, null);
+        assertEquals("[]", set.toString());
+    }
+
+    @Test
+    public void testEquals() {
+        assertEquals(new InterfaceSet(null, "if1", "if2"), new InterfaceSet("if2", "if1"));
+        assertEquals(new InterfaceSet(null, null), new InterfaceSet());
+        assertFalse(new InterfaceSet("if1", "if3").equals(new InterfaceSet("if1", "if2")));
+        assertFalse(new InterfaceSet("if1", "if2").equals(new InterfaceSet("if1")));
+        assertFalse(new InterfaceSet().equals(null));
+    }
+}