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