| /* |
| * 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.HashSet; |
| 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. |
| * |
| * - Support transmitting MAX_URGENT_RTR_ADVERTISEMENTS number of empty |
| * RAs with zero default router lifetime when transitioning from an |
| * advertising state to a non-advertising state. |
| * |
| * @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; |
| |
| 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(); |
| } |
| } |
| |
| |
| public RouterAdvertisementDaemon(String ifname, int ifindex, byte[] hwaddr) { |
| mIfName = ifname; |
| mIfIndex = ifindex; |
| mHwAddr = hwaddr; |
| mAllNodes = new InetSocketAddress(getAllNodesForScopeId(mIfIndex), 0); |
| } |
| |
| public void buildNewRa(RaParams params) { |
| if (params == null || params.prefixes.isEmpty()) { |
| // No RA to be served at this time. |
| clearRa(); |
| return; |
| } |
| |
| if (params.mtu < IPV6_MIN_MTU) { |
| params.mtu = IPV6_MIN_MTU; |
| } |
| |
| final ByteBuffer ra = ByteBuffer.wrap(mRA); |
| ra.order(ByteOrder.BIG_ENDIAN); |
| |
| synchronized (mLock) { |
| try { |
| putHeader(ra, params.hasDefaultRoute); |
| putSlla(ra, mHwAddr); |
| // 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); |
| putMtu(ra, params.mtu); |
| for (IpPrefix ipp : params.prefixes) { |
| putPio(ra, ipp); |
| } |
| if (params.dnses.size() > 0) { |
| putRdnss(ra, params.dnses); |
| } |
| mRaLength = ra.position(); |
| } catch (BufferOverflowException e) { |
| Log.e(TAG, "Could not construct new RA: " + e); |
| mRaLength = 0; |
| return; |
| } |
| } |
| |
| 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 clearRa() { |
| boolean notifySocket; |
| synchronized (mLock) { |
| notifySocket = (mRaLength != 0); |
| mRaLength = 0; |
| } |
| if (notifySocket) { |
| maybeNotifyMulticastTransmitter(); |
| } |
| } |
| |
| 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); |
| } |
| |
| private static void putPio(ByteBuffer ra, IpPrefix ipp) { |
| /** |
| 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; |
| |
| 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(DEFAULT_LIFETIME) |
| .putInt(DEFAULT_LIFETIME) |
| .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) { |
| /** |
| 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(DEFAULT_LIFETIME); |
| |
| for (Inet6Address dns : dnses) { |
| 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); |
| } |
| } |
| |
| 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() { |
| synchronized (mLock) { |
| if (mRaLength < MIN_RA_HEADER_SIZE) { |
| // No actual RA to send; just sleep for 1 day. |
| return DAY_IN_SECONDS; |
| } |
| } |
| |
| final int urgentPending = mUrgentAnnouncements.getAndDecrement(); |
| if (urgentPending > 0) { |
| 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(); |
| } |
| } |
| } |