RouterAdvertisementDaemon.java revision e33daf12957417547efb7896aa81c1289eb80b81
1/* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.net.ip; 18 19import static android.system.OsConstants.*; 20 21import android.net.IpPrefix; 22import android.net.LinkAddress; 23import android.net.LinkProperties; 24import android.net.NetworkUtils; 25import android.system.ErrnoException; 26import android.system.Os; 27import android.system.StructGroupReq; 28import android.system.StructTimeval; 29import android.util.Log; 30 31import com.android.internal.annotations.GuardedBy; 32 33import libcore.io.IoBridge; 34import libcore.util.HexEncoding; 35 36import java.io.FileDescriptor; 37import java.io.InterruptedIOException; 38import java.io.IOException; 39import java.net.Inet6Address; 40import java.net.InetAddress; 41import java.net.InetSocketAddress; 42import java.net.SocketException; 43import java.net.UnknownHostException; 44import java.nio.BufferOverflowException; 45import java.nio.ByteBuffer; 46import java.nio.ByteOrder; 47import java.util.ArrayList; 48import java.util.HashSet; 49import java.util.Random; 50import java.util.Set; 51import java.util.concurrent.atomic.AtomicInteger; 52 53 54/** 55 * Basic IPv6 Router Advertisement Daemon. 56 * 57 * TODO: 58 * 59 * - Rewrite using Handler (and friends) so that AlarmManager can deliver 60 * "kick" messages when it's time to send a multicast RA. 61 * 62 * - Support transmitting MAX_URGENT_RTR_ADVERTISEMENTS number of empty 63 * RAs with zero default router lifetime when transitioning from an 64 * advertising state to a non-advertising state. 65 * 66 * @hide 67 */ 68public class RouterAdvertisementDaemon { 69 private static final String TAG = RouterAdvertisementDaemon.class.getSimpleName(); 70 private static final byte ICMPV6_ND_ROUTER_SOLICIT = asByte(133); 71 private static final byte ICMPV6_ND_ROUTER_ADVERT = asByte(134); 72 private static final int IPV6_MIN_MTU = 1280; 73 private static final int MIN_RA_HEADER_SIZE = 16; 74 75 // Summary of various timers and lifetimes. 76 private static final int MIN_RTR_ADV_INTERVAL_SEC = 300; 77 private static final int MAX_RTR_ADV_INTERVAL_SEC = 600; 78 // In general, router, prefix, and DNS lifetimes are all advised to be 79 // greater than or equal to 3 * MAX_RTR_ADV_INTERVAL. Here, we double 80 // that to allow for multicast packet loss. 81 // 82 // This MAX_RTR_ADV_INTERVAL_SEC and DEFAULT_LIFETIME are also consistent 83 // with the https://tools.ietf.org/html/rfc7772#section-4 discussion of 84 // "approximately 7 RAs per hour". 85 private static final int DEFAULT_LIFETIME = 6 * MAX_RTR_ADV_INTERVAL_SEC; 86 // From https://tools.ietf.org/html/rfc4861#section-10 . 87 private static final int MIN_DELAY_BETWEEN_RAS_SEC = 3; 88 // Both initial and final RAs, but also for changes in RA contents. 89 // From https://tools.ietf.org/html/rfc4861#section-10 . 90 private static final int MAX_URGENT_RTR_ADVERTISEMENTS = 5; 91 92 private static final int DAY_IN_SECONDS = 86_400; 93 94 private static final byte[] ALL_NODES = new byte[] { 95 (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 96 }; 97 98 private final String mIfName; 99 private final int mIfIndex; 100 private final byte[] mHwAddr; 101 private final InetSocketAddress mAllNodes; 102 103 // This lock is to protect the RA from being updated while being 104 // transmitted on another thread (multicast or unicast). 105 // 106 // TODO: This should be handled with a more RCU-like approach. 107 private final Object mLock = new Object(); 108 @GuardedBy("mLock") 109 private final byte[] mRA = new byte[IPV6_MIN_MTU]; 110 @GuardedBy("mLock") 111 private int mRaLength; 112 113 private volatile FileDescriptor mSocket; 114 private volatile MulticastTransmitter mMulticastTransmitter; 115 private volatile UnicastResponder mUnicastResponder; 116 117 public static class RaParams { 118 public boolean hasDefaultRoute; 119 public int mtu; 120 public HashSet<IpPrefix> prefixes; 121 public HashSet<Inet6Address> dnses; 122 123 public RaParams() { 124 hasDefaultRoute = false; 125 mtu = IPV6_MIN_MTU; 126 prefixes = new HashSet<IpPrefix>(); 127 dnses = new HashSet<Inet6Address>(); 128 } 129 130 public RaParams(RaParams other) { 131 hasDefaultRoute = other.hasDefaultRoute; 132 mtu = other.mtu; 133 prefixes = (HashSet) other.prefixes.clone(); 134 dnses = (HashSet) other.dnses.clone(); 135 } 136 } 137 138 139 public RouterAdvertisementDaemon(String ifname, int ifindex, byte[] hwaddr) { 140 mIfName = ifname; 141 mIfIndex = ifindex; 142 mHwAddr = hwaddr; 143 mAllNodes = new InetSocketAddress(getAllNodesForScopeId(mIfIndex), 0); 144 } 145 146 public void buildNewRa(RaParams params) { 147 if (params == null || params.prefixes.isEmpty()) { 148 // No RA to be served at this time. 149 clearRa(); 150 return; 151 } 152 153 if (params.mtu < IPV6_MIN_MTU) { 154 params.mtu = IPV6_MIN_MTU; 155 } 156 157 final ByteBuffer ra = ByteBuffer.wrap(mRA); 158 ra.order(ByteOrder.BIG_ENDIAN); 159 160 synchronized (mLock) { 161 try { 162 putHeader(ra, params.hasDefaultRoute); 163 putSlla(ra, mHwAddr); 164 // https://tools.ietf.org/html/rfc5175#section-4 says: 165 // 166 // "MUST NOT be added to a Router Advertisement message 167 // if no flags in the option are set." 168 // 169 // putExpandedFlagsOption(ra); 170 putMtu(ra, params.mtu); 171 for (IpPrefix ipp : params.prefixes) { 172 putPio(ra, ipp); 173 } 174 if (params.dnses.size() > 0) { 175 putRdnss(ra, params.dnses); 176 } 177 mRaLength = ra.position(); 178 } catch (BufferOverflowException e) { 179 Log.e(TAG, "Could not construct new RA: " + e); 180 mRaLength = 0; 181 return; 182 } 183 } 184 185 maybeNotifyMulticastTransmitter(); 186 } 187 188 public boolean start() { 189 if (!createSocket()) { 190 return false; 191 } 192 193 mMulticastTransmitter = new MulticastTransmitter(); 194 mMulticastTransmitter.start(); 195 196 mUnicastResponder = new UnicastResponder(); 197 mUnicastResponder.start(); 198 199 return true; 200 } 201 202 public void stop() { 203 closeSocket(); 204 mMulticastTransmitter = null; 205 mUnicastResponder = null; 206 } 207 208 private void clearRa() { 209 boolean notifySocket; 210 synchronized (mLock) { 211 notifySocket = (mRaLength != 0); 212 mRaLength = 0; 213 } 214 if (notifySocket) { 215 maybeNotifyMulticastTransmitter(); 216 } 217 } 218 219 private void maybeNotifyMulticastTransmitter() { 220 final MulticastTransmitter m = mMulticastTransmitter; 221 if (m != null) { 222 m.hup(); 223 } 224 } 225 226 private static Inet6Address getAllNodesForScopeId(int scopeId) { 227 try { 228 return Inet6Address.getByAddress("ff02::1", ALL_NODES, scopeId); 229 } catch (UnknownHostException uhe) { 230 Log.wtf(TAG, "Failed to construct ff02::1 InetAddress: " + uhe); 231 return null; 232 } 233 } 234 235 private static byte asByte(int value) { return (byte) value; } 236 private static short asShort(int value) { return (short) value; } 237 238 private static void putHeader(ByteBuffer ra, boolean hasDefaultRoute) { 239 /** 240 Router Advertisement Message Format 241 242 0 1 2 3 243 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 244 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 245 | Type | Code | Checksum | 246 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 247 | Cur Hop Limit |M|O|H|Prf|P|R|R| Router Lifetime | 248 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 249 | Reachable Time | 250 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 251 | Retrans Timer | 252 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 253 | Options ... 254 +-+-+-+-+-+-+-+-+-+-+-+- 255 */ 256 final byte DEFAULT_HOPLIMIT = 64; 257 ra.put(ICMPV6_ND_ROUTER_ADVERT) 258 .put(asByte(0)) 259 .putShort(asShort(0)) 260 .put(DEFAULT_HOPLIMIT) 261 // RFC 4191 "high" preference, iff. advertising a default route. 262 .put(hasDefaultRoute ? asByte(0x08) : asByte(0)) 263 .putShort(hasDefaultRoute ? asShort(DEFAULT_LIFETIME) : asShort(0)) 264 .putInt(0) 265 .putInt(0); 266 } 267 268 private static void putSlla(ByteBuffer ra, byte[] slla) { 269 /** 270 Source/Target Link-layer Address 271 272 0 1 2 3 273 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 274 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 275 | Type | Length | Link-Layer Address ... 276 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 277 */ 278 if (slla == null || slla.length != 6) { 279 // Only IEEE 802.3 6-byte addresses are supported. 280 return; 281 } 282 final byte ND_OPTION_SLLA = 1; 283 final byte SLLA_NUM_8OCTETS = 1; 284 ra.put(ND_OPTION_SLLA) 285 .put(SLLA_NUM_8OCTETS) 286 .put(slla); 287 } 288 289 private static void putExpandedFlagsOption(ByteBuffer ra) { 290 /** 291 Router Advertisement Expanded Flags Option 292 293 0 1 2 3 294 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 295 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 296 | Type | Length | Bit fields available .. 297 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 298 ... for assignment | 299 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 300 */ 301 302 final byte ND_OPTION_EFO = 26; 303 final byte EFO_NUM_8OCTETS = 1; 304 305 ra.put(ND_OPTION_EFO) 306 .put(EFO_NUM_8OCTETS) 307 .putShort(asShort(0)) 308 .putInt(0); 309 } 310 311 private static void putMtu(ByteBuffer ra, int mtu) { 312 /** 313 MTU 314 315 0 1 2 3 316 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 317 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 318 | Type | Length | Reserved | 319 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 320 | MTU | 321 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 322 */ 323 final byte ND_OPTION_MTU = 5; 324 final byte MTU_NUM_8OCTETS = 1; 325 ra.put(ND_OPTION_MTU) 326 .put(MTU_NUM_8OCTETS) 327 .putShort(asShort(0)) 328 .putInt(mtu); 329 } 330 331 private static void putPio(ByteBuffer ra, IpPrefix ipp) { 332 /** 333 Prefix Information 334 335 0 1 2 3 336 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 337 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 338 | Type | Length | Prefix Length |L|A| Reserved1 | 339 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 340 | Valid Lifetime | 341 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 342 | Preferred Lifetime | 343 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 344 | Reserved2 | 345 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 346 | | 347 + + 348 | | 349 + Prefix + 350 | | 351 + + 352 | | 353 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 354 */ 355 final int prefixLength = ipp.getPrefixLength(); 356 if (prefixLength != 64) { 357 return; 358 } 359 final byte ND_OPTION_PIO = 3; 360 final byte PIO_NUM_8OCTETS = 4; 361 362 final byte[] addr = ipp.getAddress().getAddress(); 363 ra.put(ND_OPTION_PIO) 364 .put(PIO_NUM_8OCTETS) 365 .put(asByte(prefixLength)) 366 .put(asByte(0xc0)) // L&A set 367 .putInt(DEFAULT_LIFETIME) 368 .putInt(DEFAULT_LIFETIME) 369 .putInt(0) 370 .put(addr); 371 } 372 373 private static void putRio(ByteBuffer ra, IpPrefix ipp) { 374 /** 375 Route Information Option 376 377 0 1 2 3 378 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 379 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 380 | Type | Length | Prefix Length |Resvd|Prf|Resvd| 381 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 382 | Route Lifetime | 383 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 384 | Prefix (Variable Length) | 385 . . 386 . . 387 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 388 */ 389 final int prefixLength = ipp.getPrefixLength(); 390 if (prefixLength > 64) { 391 return; 392 } 393 final byte ND_OPTION_RIO = 24; 394 final byte RIO_NUM_8OCTETS = asByte( 395 (prefixLength == 0) ? 1 : (prefixLength <= 8) ? 2 : 3); 396 397 final byte[] addr = ipp.getAddress().getAddress(); 398 ra.put(ND_OPTION_RIO) 399 .put(RIO_NUM_8OCTETS) 400 .put(asByte(prefixLength)) 401 .put(asByte(0x18)) 402 .putInt(DEFAULT_LIFETIME); 403 404 // Rely upon an IpPrefix's address being properly zeroed. 405 if (prefixLength > 0) { 406 ra.put(addr, 0, (prefixLength <= 64) ? 8 : 16); 407 } 408 } 409 410 private static void putRdnss(ByteBuffer ra, Set<Inet6Address> dnses) { 411 /** 412 Recursive DNS Server (RDNSS) Option 413 414 0 1 2 3 415 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 416 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 417 | Type | Length | Reserved | 418 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 419 | Lifetime | 420 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 421 | | 422 : Addresses of IPv6 Recursive DNS Servers : 423 | | 424 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 425 */ 426 427 final byte ND_OPTION_RDNSS = 25; 428 final byte RDNSS_NUM_8OCTETS = asByte(dnses.size() * 2 + 1); 429 ra.put(ND_OPTION_RDNSS) 430 .put(RDNSS_NUM_8OCTETS) 431 .putShort(asShort(0)) 432 .putInt(DEFAULT_LIFETIME); 433 434 for (Inet6Address dns : dnses) { 435 ra.put(dns.getAddress()); 436 } 437 } 438 439 private boolean createSocket() { 440 final int SEND_TIMEOUT_MS = 300; 441 442 try { 443 mSocket = Os.socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6); 444 // Setting SNDTIMEO is purely for defensive purposes. 445 Os.setsockoptTimeval( 446 mSocket, SOL_SOCKET, SO_SNDTIMEO, StructTimeval.fromMillis(SEND_TIMEOUT_MS)); 447 Os.setsockoptIfreq(mSocket, SOL_SOCKET, SO_BINDTODEVICE, mIfName); 448 NetworkUtils.protectFromVpn(mSocket); 449 NetworkUtils.setupRaSocket(mSocket, mIfIndex); 450 } catch (ErrnoException | IOException e) { 451 Log.e(TAG, "Failed to create RA daemon socket: " + e); 452 return false; 453 } 454 455 return true; 456 } 457 458 private void closeSocket() { 459 if (mSocket != null) { 460 try { 461 IoBridge.closeAndSignalBlockedThreads(mSocket); 462 } catch (IOException ignored) {} 463 } 464 mSocket = null; 465 } 466 467 private boolean isSocketValid() { 468 final FileDescriptor s = mSocket; 469 return (s != null) && s.valid(); 470 } 471 472 private boolean isSuitableDestination(InetSocketAddress dest) { 473 if (mAllNodes.equals(dest)) { 474 return true; 475 } 476 477 final InetAddress destip = dest.getAddress(); 478 return (destip instanceof Inet6Address) && 479 destip.isLinkLocalAddress() && 480 (((Inet6Address) destip).getScopeId() == mIfIndex); 481 } 482 483 private void maybeSendRA(InetSocketAddress dest) { 484 if (dest == null || !isSuitableDestination(dest)) { 485 dest = mAllNodes; 486 } 487 488 try { 489 synchronized (mLock) { 490 if (mRaLength < MIN_RA_HEADER_SIZE) { 491 // No actual RA to send. 492 return; 493 } 494 Os.sendto(mSocket, mRA, 0, mRaLength, 0, dest); 495 } 496 Log.d(TAG, "RA sendto " + dest.getAddress().getHostAddress()); 497 } catch (ErrnoException | SocketException e) { 498 if (isSocketValid()) { 499 Log.e(TAG, "sendto error: " + e); 500 } 501 } 502 } 503 504 private final class UnicastResponder extends Thread { 505 private final InetSocketAddress solicitor = new InetSocketAddress(); 506 // The recycled buffer for receiving Router Solicitations from clients. 507 // If the RS is larger than IPV6_MIN_MTU the packets are truncated. 508 // This is fine since currently only byte 0 is examined anyway. 509 private final byte mSolication[] = new byte[IPV6_MIN_MTU]; 510 511 @Override 512 public void run() { 513 while (isSocketValid()) { 514 try { 515 // Blocking receive. 516 final int rval = Os.recvfrom( 517 mSocket, mSolication, 0, mSolication.length, 0, solicitor); 518 // Do the least possible amount of validation. 519 if (rval < 1 || mSolication[0] != ICMPV6_ND_ROUTER_SOLICIT) { 520 continue; 521 } 522 } catch (ErrnoException | SocketException e) { 523 if (isSocketValid()) { 524 Log.e(TAG, "recvfrom error: " + e); 525 } 526 continue; 527 } 528 529 maybeSendRA(solicitor); 530 } 531 } 532 } 533 534 // TODO: Consider moving this to run on a provided Looper as a Handler, 535 // with WakeupMessage-style messages providing the timer driven input. 536 private final class MulticastTransmitter extends Thread { 537 private final Random mRandom = new Random(); 538 private final AtomicInteger mUrgentAnnouncements = new AtomicInteger(0); 539 540 @Override 541 public void run() { 542 while (isSocketValid()) { 543 try { 544 Thread.sleep(getNextMulticastTransmitDelayMs()); 545 } catch (InterruptedException ignored) { 546 // Stop sleeping, immediately send an RA, and continue. 547 } 548 549 maybeSendRA(mAllNodes); 550 } 551 } 552 553 public void hup() { 554 // Set to one fewer that the desired number, because as soon as 555 // the thread interrupt is processed we immediately send an RA 556 // and mUrgentAnnouncements is not examined until the subsequent 557 // sleep interval computation (i.e. this way we send 3 and not 4). 558 mUrgentAnnouncements.set(MAX_URGENT_RTR_ADVERTISEMENTS - 1); 559 interrupt(); 560 } 561 562 private int getNextMulticastTransmitDelaySec() { 563 synchronized (mLock) { 564 if (mRaLength < MIN_RA_HEADER_SIZE) { 565 // No actual RA to send; just sleep for 1 day. 566 return DAY_IN_SECONDS; 567 } 568 } 569 570 final int urgentPending = mUrgentAnnouncements.getAndDecrement(); 571 if (urgentPending > 0) { 572 return MIN_DELAY_BETWEEN_RAS_SEC; 573 } 574 575 return MIN_RTR_ADV_INTERVAL_SEC + mRandom.nextInt( 576 MAX_RTR_ADV_INTERVAL_SEC - MIN_RTR_ADV_INTERVAL_SEC); 577 } 578 579 private long getNextMulticastTransmitDelayMs() { 580 return 1000 * (long) getNextMulticastTransmitDelaySec(); 581 } 582 } 583} 584