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