/* * 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.system.OsConstants.*; import android.net.IpPrefix; import android.net.LinkAddress; import android.net.LinkProperties; import android.net.NetworkUtils; 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 IPV6_MIN_MTU = 1280; 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 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 : dnses) { // 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; 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; } 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(); } } }