blob: cb3123ce466a9655996b105a9178b82a9b5eff3d [file] [log] [blame]
/*
* 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.*;
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.NetworkUtils;
import android.net.TrafficStats;
import android.system.ErrnoException;
import android.system.Os;
import android.system.StructGroupReq;
import android.system.StructTimeval;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import libcore.io.IoBridge;
import libcore.util.HexEncoding;
import java.io.FileDescriptor;
import java.io.InterruptedIOException;
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.ArrayList;
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 String mIfName;
private final int mIfIndex;
private final byte[] mHwAddr;
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 {
public boolean hasDefaultRoute;
public int mtu;
public HashSet<IpPrefix> prefixes;
public HashSet<Inet6Address> dnses;
public RaParams() {
hasDefaultRoute = false;
mtu = IPV6_MIN_MTU;
prefixes = new HashSet<IpPrefix>();
dnses = new HashSet<Inet6Address>();
}
public RaParams(RaParams other) {
hasDefaultRoute = other.hasDefaultRoute;
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(String ifname, int ifindex, byte[] hwaddr) {
mIfName = ifname;
mIfIndex = ifindex;
mHwAddr = hwaddr;
mAllNodes = new InetSocketAddress(getAllNodesForScopeId(mIfIndex), 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();
mMulticastTransmitter = null;
mUnicastResponder = null;
}
private void assembleRaLocked() {
final ByteBuffer ra = ByteBuffer.wrap(mRA);
ra.order(ByteOrder.BIG_ENDIAN);
boolean shouldSendRA = false;
try {
putHeader(ra, mRaParams != null && mRaParams.hasDefaultRoute);
putSlla(ra, mHwAddr);
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 (mRaParams != null) {
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) {
/**
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 ...
+-+-+-+-+-+-+-+-+-+-+-+-
*/
final byte DEFAULT_HOPLIMIT = 64;
ra.put(ICMPV6_ND_ROUTER_ADVERT)
.put(asByte(0))
.putShort(asShort(0))
.put(DEFAULT_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(TrafficStats.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, mIfName);
NetworkUtils.protectFromVpn(mSocket);
NetworkUtils.setupRaSocket(mSocket, mIfIndex);
} 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() == mIfIndex);
}
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();
}
}
}