content: /*
   * 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;
  
      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();
      }
  
      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();
      }
  
      public boolean start() {
          if (!createSocket()) {
              return false;
          }
  
          mMulticastTransmitter = new MulticastTransmitter();
          mMulticastTransmitter.start();
  
          mUnicastResponder = new UnicastResponder();
          mUnicastResponder.start();
  
          return true;
      }
  
      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_8OCTETS = 1;
          ra.put(ND_OPTION_MTU)
            .put(MTU_NUM_8OCTETS)
            .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_TIMEOUT_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_TIMEOUT_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 solicitor = 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 mSolication[] = new byte[IPV6_MIN_MTU];
  
          @Override
          public void run() {
              while (isSocketValid()) {
                  try {
                      // Blocking receive.
                      final int rval = Os.recvfrom(
                              mSocket, mSolication, 0, mSolication.length, 0, solicitor);
                      // Do the least possible amount of validation.
                      if (rval < 1 || mSolication[0] != ICMPV6_ND_ROUTER_SOLICIT) {
                          continue;
                      }
                  } catch (ErrnoException | SocketException e) {
                      if (isSocketValid()) {
                          Log.e(TAG, "recvfrom error: " + e);
                      }
                      continue;
                  }
  
                  maybeSendRA(solicitor);
              }
          }
      }
  
      // 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();
          }
      }
  }
  