1 /* 2 * Copyright (C) 2023 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 17 package com.android.server.connectivity; 18 19 import static android.net.MulticastRoutingConfig.FORWARD_NONE; 20 import static android.net.MulticastRoutingConfig.FORWARD_SELECTED; 21 import static android.net.MulticastRoutingConfig.FORWARD_WITH_MIN_SCOPE; 22 import static android.system.OsConstants.AF_INET6; 23 import static android.system.OsConstants.EADDRINUSE; 24 import static android.system.OsConstants.IPPROTO_ICMPV6; 25 import static android.system.OsConstants.IPPROTO_IPV6; 26 import static android.system.OsConstants.SOCK_CLOEXEC; 27 import static android.system.OsConstants.SOCK_NONBLOCK; 28 import static android.system.OsConstants.SOCK_RAW; 29 30 import static com.android.net.module.util.CollectionUtils.getIndexForValue; 31 32 import android.annotation.NonNull; 33 import android.annotation.Nullable; 34 import android.net.MulticastRoutingConfig; 35 import android.net.NetworkUtils; 36 import android.os.Handler; 37 import android.os.Looper; 38 import android.system.ErrnoException; 39 import android.system.Os; 40 import android.util.ArrayMap; 41 import android.util.ArraySet; 42 import android.util.Log; 43 import android.util.SparseArray; 44 45 import com.android.internal.annotations.VisibleForTesting; 46 import com.android.net.module.util.LinkPropertiesUtils.CompareResult; 47 import com.android.net.module.util.PacketReader; 48 import com.android.net.module.util.SocketUtils; 49 import com.android.net.module.util.netlink.NetlinkUtils; 50 import com.android.net.module.util.netlink.RtNetlinkRouteMessage; 51 import com.android.net.module.util.structs.StructMf6cctl; 52 import com.android.net.module.util.structs.StructMif6ctl; 53 import com.android.net.module.util.structs.StructMrt6Msg; 54 55 import java.io.FileDescriptor; 56 import java.io.IOException; 57 import java.net.Inet6Address; 58 import java.net.InetSocketAddress; 59 import java.net.MulticastSocket; 60 import java.net.NetworkInterface; 61 import java.net.SocketException; 62 import java.nio.ByteBuffer; 63 import java.time.Clock; 64 import java.time.Instant; 65 import java.time.ZoneId; 66 import java.util.HashMap; 67 import java.util.Iterator; 68 import java.util.LinkedHashMap; 69 import java.util.List; 70 import java.util.Map; 71 import java.util.Objects; 72 import java.util.Set; 73 74 /** 75 * Class to coordinate multicast routing between network interfaces. 76 * 77 * <p>Supports IPv6 multicast routing. 78 * 79 * <p>Note that usage of this class is not thread-safe. All public methods must be called from the 80 * same thread that the handler from {@code dependencies.getHandler} is associated. 81 */ 82 public class MulticastRoutingCoordinatorService { 83 private static final String TAG = MulticastRoutingCoordinatorService.class.getSimpleName(); 84 private static final int ICMP6_FILTER = 1; 85 private static final int MRT6_INIT = 200; 86 private static final int MRT6_ADD_MIF = 202; 87 private static final int MRT6_DEL_MIF = 203; 88 private static final int MRT6_ADD_MFC = 204; 89 private static final int MRT6_DEL_MFC = 205; 90 private static final int ONE = 1; 91 92 private final Dependencies mDependencies; 93 94 private final Handler mHandler; 95 private final MulticastNocacheUpcallListener mMulticastNoCacheUpcallListener; 96 @NonNull private final FileDescriptor mMulticastRoutingFd; // For multicast routing config 97 @NonNull private final MulticastSocket mMulticastSocket; // For join group and leave group 98 99 @VisibleForTesting public static final int MFC_INACTIVE_CHECK_INTERVAL_MS = 60_000; 100 @VisibleForTesting public static final int MFC_INACTIVE_TIMEOUT_MS = 300_000; 101 @VisibleForTesting public static final int MFC_MAX_NUMBER_OF_ENTRIES = 1_000; 102 103 // The kernel supports max 32 virtual interfaces per multicast routing table. 104 private static final int MAX_NUM_OF_MULTICAST_VIRTUAL_INTERFACES = 32; 105 106 /** Tracks if checking for inactive MFC has been scheduled */ 107 private boolean mMfcPollingScheduled = false; 108 109 /** Mapping from multicast virtual interface index to interface name */ 110 private SparseArray<String> mVirtualInterfaces = 111 new SparseArray<>(MAX_NUM_OF_MULTICAST_VIRTUAL_INTERFACES); 112 /** Mapping from physical interface index to interface name */ 113 private SparseArray<String> mInterfaces = 114 new SparseArray<>(MAX_NUM_OF_MULTICAST_VIRTUAL_INTERFACES); 115 116 /** Mapping of iif to PerInterfaceMulticastRoutingConfig */ 117 private Map<String, PerInterfaceMulticastRoutingConfig> mMulticastRoutingConfigs = 118 new HashMap<String, PerInterfaceMulticastRoutingConfig>(); 119 120 private static final class PerInterfaceMulticastRoutingConfig { 121 // mapping of oif name to MulticastRoutingConfig 122 public Map<String, MulticastRoutingConfig> oifConfigs = 123 new HashMap<String, MulticastRoutingConfig>(); 124 } 125 126 /** Tracks the MFCs added to kernel. Using LinkedHashMap to keep the added order, so 127 // when the number of MFCs reaches the max limit then the earliest added one is removed. */ 128 private LinkedHashMap<MfcKey, MfcValue> mMfcs = new LinkedHashMap<>(); 129 MulticastRoutingCoordinatorService(Handler h)130 public MulticastRoutingCoordinatorService(Handler h) { 131 this(h, new Dependencies()); 132 } 133 134 @VisibleForTesting 135 /* @throws UnsupportedOperationException if multicast routing is not supported */ MulticastRoutingCoordinatorService(Handler h, Dependencies dependencies)136 public MulticastRoutingCoordinatorService(Handler h, Dependencies dependencies) { 137 mDependencies = dependencies; 138 mMulticastRoutingFd = mDependencies.createMulticastRoutingSocket(); 139 mMulticastSocket = mDependencies.createMulticastSocket(); 140 mHandler = h; 141 mMulticastNoCacheUpcallListener = 142 new MulticastNocacheUpcallListener(mHandler, mMulticastRoutingFd); 143 mHandler.post(() -> mMulticastNoCacheUpcallListener.start()); 144 } 145 checkOnHandlerThread()146 private void checkOnHandlerThread() { 147 if (Looper.myLooper() != mHandler.getLooper()) { 148 throw new IllegalStateException( 149 "Not running on ConnectivityService thread (" + mHandler.getLooper() + ") : " 150 + Looper.myLooper()); 151 } 152 } 153 getInterfaceIndex(String ifName)154 private Integer getInterfaceIndex(String ifName) { 155 int mapIndex = getIndexForValue(mInterfaces, ifName); 156 if (mapIndex < 0) return null; 157 return mInterfaces.keyAt(mapIndex); 158 } 159 160 /** 161 * Apply multicast routing configuration 162 * 163 * @param iifName name of the incoming interface 164 * @param oifName name of the outgoing interface 165 * @param newConfig the multicast routing configuration to be applied from iif to oif 166 * @throws MulticastRoutingException when failed to apply the config 167 */ applyMulticastRoutingConfig( final String iifName, final String oifName, final MulticastRoutingConfig newConfig)168 public void applyMulticastRoutingConfig( 169 final String iifName, final String oifName, final MulticastRoutingConfig newConfig) { 170 checkOnHandlerThread(); 171 Objects.requireNonNull(iifName, "IifName can't be null"); 172 Objects.requireNonNull(oifName, "OifName can't be null"); 173 174 if (newConfig.getForwardingMode() != FORWARD_NONE) { 175 // Make sure iif and oif are added as multicast forwarding interfaces 176 if (!maybeAddAndTrackInterface(iifName) || !maybeAddAndTrackInterface(oifName)) { 177 Log.e( 178 TAG, 179 "Failed to apply multicast routing config from " 180 + iifName 181 + " to " 182 + oifName); 183 return; 184 } 185 } 186 187 final MulticastRoutingConfig oldConfig = getMulticastRoutingConfig(iifName, oifName); 188 189 if (oldConfig.equals(newConfig)) return; 190 191 int oldMode = oldConfig.getForwardingMode(); 192 int newMode = newConfig.getForwardingMode(); 193 Integer iifIndex = getInterfaceIndex(iifName); 194 if (iifIndex == null) { 195 // This cannot happen unless the new config has FORWARD_NONE but is not the same 196 // as the old config. This is not possible in current code. 197 Log.wtf(TAG, "Adding multicast configuration on null interface?"); 198 return; 199 } 200 201 // When new addresses are added to FORWARD_SELECTED mode, join these multicast groups 202 // on their upstream interface, so upstream multicast routers know about the subscription. 203 // When addresses are removed from FORWARD_SELECTED mode, leave the multicast groups. 204 final Set<Inet6Address> oldListeningAddresses = 205 (oldMode == FORWARD_SELECTED) 206 ? oldConfig.getListeningAddresses() 207 : new ArraySet<>(); 208 final Set<Inet6Address> newListeningAddresses = 209 (newMode == FORWARD_SELECTED) 210 ? newConfig.getListeningAddresses() 211 : new ArraySet<>(); 212 final CompareResult<Inet6Address> addressDiff = 213 new CompareResult<>(oldListeningAddresses, newListeningAddresses); 214 joinGroups(iifIndex, addressDiff.added); 215 leaveGroups(iifIndex, addressDiff.removed); 216 217 setMulticastRoutingConfig(iifName, oifName, newConfig); 218 Log.d( 219 TAG, 220 "Applied multicast routing config for iif " 221 + iifName 222 + " to oif " 223 + oifName 224 + " with Config " 225 + newConfig); 226 227 // Update existing MFCs to make sure they align with the updated configuration 228 updateMfcs(); 229 230 if (newConfig.getForwardingMode() == FORWARD_NONE) { 231 if (!hasActiveMulticastConfig(iifName)) { 232 removeInterfaceFromMulticastRouting(iifName); 233 } 234 if (!hasActiveMulticastConfig(oifName)) { 235 removeInterfaceFromMulticastRouting(oifName); 236 } 237 } 238 } 239 240 /** 241 * Removes an network interface from multicast routing. 242 * 243 * <p>Remove the network interface from multicast configs and remove it from the list of 244 * multicast routing interfaces in the kernel 245 * 246 * @param ifName name of the interface that should be removed 247 */ 248 @VisibleForTesting removeInterfaceFromMulticastRouting(final String ifName)249 public void removeInterfaceFromMulticastRouting(final String ifName) { 250 checkOnHandlerThread(); 251 final Integer virtualIndex = getVirtualInterfaceIndex(ifName); 252 if (virtualIndex == null) return; 253 254 updateMfcs(); 255 mInterfaces.removeAt(getIndexForValue(mInterfaces, ifName)); 256 mVirtualInterfaces.remove(virtualIndex); 257 try { 258 mDependencies.setsockoptMrt6DelMif(mMulticastRoutingFd, virtualIndex); 259 Log.d(TAG, "Removed mifi " + virtualIndex + " from MIF"); 260 } catch (ErrnoException e) { 261 Log.e(TAG, "failed to remove multicast virtual interface" + virtualIndex, e); 262 } 263 } 264 265 /** 266 * Returns the next available virtual index for multicast routing, or -1 if the number of 267 * virtual interfaces has reached max value. 268 */ getNextAvailableVirtualIndex()269 private int getNextAvailableVirtualIndex() { 270 if (mVirtualInterfaces.size() >= MAX_NUM_OF_MULTICAST_VIRTUAL_INTERFACES) { 271 Log.e(TAG, "Can't allocate new multicast virtual interface"); 272 return -1; 273 } 274 for (int i = 0; i < mVirtualInterfaces.size(); i++) { 275 if (!mVirtualInterfaces.contains(i)) { 276 return i; 277 } 278 } 279 return mVirtualInterfaces.size(); 280 } 281 282 @VisibleForTesting getVirtualInterfaceIndex(String ifName)283 public Integer getVirtualInterfaceIndex(String ifName) { 284 int mapIndex = getIndexForValue(mVirtualInterfaces, ifName); 285 if (mapIndex < 0) return null; 286 return mVirtualInterfaces.keyAt(mapIndex); 287 } 288 getVirtualInterfaceIndex(int physicalIndex)289 private Integer getVirtualInterfaceIndex(int physicalIndex) { 290 String ifName = mInterfaces.get(physicalIndex); 291 if (ifName == null) { 292 // This is only used to match MFCs from kernel to MFCs we know about. 293 // Unknown MFCs should be ignored. 294 return null; 295 } 296 return getVirtualInterfaceIndex(ifName); 297 } 298 getInterfaceName(int virtualIndex)299 private String getInterfaceName(int virtualIndex) { 300 return mVirtualInterfaces.get(virtualIndex); 301 } 302 303 /** 304 * Returns {@code true} if the interfaces is added and tracked, or {@code false} when failed 305 * to add the interface. 306 */ maybeAddAndTrackInterface(String ifName)307 private boolean maybeAddAndTrackInterface(String ifName) { 308 checkOnHandlerThread(); 309 if (getIndexForValue(mVirtualInterfaces, ifName) >= 0) return true; 310 311 int nextVirtualIndex = getNextAvailableVirtualIndex(); 312 if (nextVirtualIndex < 0) { 313 return false; 314 } 315 int ifIndex = mDependencies.getInterfaceIndex(ifName); 316 if (ifIndex == 0) { 317 Log.e(TAG, "Can't get interface index for " + ifName); 318 return false; 319 } 320 final StructMif6ctl mif6ctl = 321 new StructMif6ctl( 322 nextVirtualIndex, 323 (short) 0 /* mif6c_flags */, 324 (short) 1 /* vifc_threshold */, 325 ifIndex, 326 0 /* vifc_rate_limit */); 327 try { 328 mDependencies.setsockoptMrt6AddMif(mMulticastRoutingFd, mif6ctl); 329 Log.d(TAG, "Added mifi " + nextVirtualIndex + " to MIF"); 330 } catch (ErrnoException e) { 331 Log.e(TAG, "failed to add multicast virtual interface", e); 332 return false; 333 } 334 mVirtualInterfaces.put(nextVirtualIndex, ifName); 335 mInterfaces.put(ifIndex, ifName); 336 return true; 337 } 338 339 @VisibleForTesting getMulticastRoutingConfig(String iifName, String oifName)340 public MulticastRoutingConfig getMulticastRoutingConfig(String iifName, String oifName) { 341 PerInterfaceMulticastRoutingConfig configs = mMulticastRoutingConfigs.get(iifName); 342 final MulticastRoutingConfig defaultConfig = MulticastRoutingConfig.CONFIG_FORWARD_NONE; 343 if (configs == null) { 344 return defaultConfig; 345 } else { 346 return configs.oifConfigs.getOrDefault(oifName, defaultConfig); 347 } 348 } 349 setMulticastRoutingConfig( final String iifName, final String oifName, final MulticastRoutingConfig config)350 private void setMulticastRoutingConfig( 351 final String iifName, final String oifName, final MulticastRoutingConfig config) { 352 checkOnHandlerThread(); 353 PerInterfaceMulticastRoutingConfig iifConfig = mMulticastRoutingConfigs.get(iifName); 354 355 if (config.getForwardingMode() == FORWARD_NONE) { 356 if (iifConfig != null) { 357 iifConfig.oifConfigs.remove(oifName); 358 } 359 if (iifConfig.oifConfigs.isEmpty()) { 360 mMulticastRoutingConfigs.remove(iifName); 361 } 362 return; 363 } 364 365 if (iifConfig == null) { 366 iifConfig = new PerInterfaceMulticastRoutingConfig(); 367 mMulticastRoutingConfigs.put(iifName, iifConfig); 368 } 369 iifConfig.oifConfigs.put(oifName, config); 370 } 371 372 /** Returns whether an interface has multicast routing config */ hasActiveMulticastConfig(final String ifName)373 private boolean hasActiveMulticastConfig(final String ifName) { 374 // FORWARD_NONE configs are not saved in the config tables, so 375 // any existing config is an active multicast routing config 376 if (mMulticastRoutingConfigs.containsKey(ifName)) return true; 377 for (var pic : mMulticastRoutingConfigs.values()) { 378 if (pic.oifConfigs.containsKey(ifName)) return true; 379 } 380 return false; 381 } 382 383 /** 384 * A multicast forwarding cache (MFC) entry holds a multicast forwarding route where packet from 385 * incoming interface(iif) with source address(S) to group address (G) are forwarded to outgoing 386 * interfaces(oifs). 387 * 388 * <p>iif, S and G identifies an MFC entry. For example an MFC1 is added: [iif1, S1, G1, oifs1] 389 * Adding another MFC2 of [iif1, S1, G1, oifs2] to the kernel overwrites MFC1. 390 */ 391 private static final class MfcKey { 392 public final int mIifVirtualIdx; 393 public final Inet6Address mSrcAddr; 394 public final Inet6Address mDstAddr; 395 MfcKey(int iif, Inet6Address src, Inet6Address dst)396 public MfcKey(int iif, Inet6Address src, Inet6Address dst) { 397 mIifVirtualIdx = iif; 398 mSrcAddr = src; 399 mDstAddr = dst; 400 } 401 equals(Object other)402 public boolean equals(Object other) { 403 if (other == this) { 404 return true; 405 } else if (!(other instanceof MfcKey)) { 406 return false; 407 } else { 408 MfcKey otherKey = (MfcKey) other; 409 return mIifVirtualIdx == otherKey.mIifVirtualIdx 410 && mSrcAddr.equals(otherKey.mSrcAddr) 411 && mDstAddr.equals(otherKey.mDstAddr); 412 } 413 } 414 hashCode()415 public int hashCode() { 416 return Objects.hash(mIifVirtualIdx, mSrcAddr, mDstAddr); 417 } 418 toString()419 public String toString() { 420 return "{iifVirtualIndex: " 421 + Integer.toString(mIifVirtualIdx) 422 + ", sourceAddress: " 423 + mSrcAddr.toString() 424 + ", destinationAddress: " 425 + mDstAddr.toString() 426 + "}"; 427 } 428 } 429 430 private static final class MfcValue { 431 private Set<Integer> mOifVirtualIndices; 432 // timestamp of when the mfc was last used in the kernel 433 // (e.g. created, or used to forward a packet) 434 private Instant mLastUsedAt; 435 MfcValue(Set<Integer> oifs, Instant timestamp)436 public MfcValue(Set<Integer> oifs, Instant timestamp) { 437 mOifVirtualIndices = oifs; 438 mLastUsedAt = timestamp; 439 } 440 hasSameOifsAs(MfcValue other)441 public boolean hasSameOifsAs(MfcValue other) { 442 return this.mOifVirtualIndices.equals(other.mOifVirtualIndices); 443 } 444 equals(Object other)445 public boolean equals(Object other) { 446 if (other == this) { 447 return true; 448 } else if (!(other instanceof MfcValue)) { 449 return false; 450 } else { 451 MfcValue otherValue = (MfcValue) other; 452 return mOifVirtualIndices.equals(otherValue.mOifVirtualIndices) 453 && mLastUsedAt.equals(otherValue.mLastUsedAt); 454 } 455 } 456 hashCode()457 public int hashCode() { 458 return Objects.hash(mOifVirtualIndices, mLastUsedAt); 459 } 460 getOifIndices()461 public Set<Integer> getOifIndices() { 462 return mOifVirtualIndices; 463 } 464 setLastUsedAt(Instant timestamp)465 public void setLastUsedAt(Instant timestamp) { 466 mLastUsedAt = timestamp; 467 } 468 getLastUsedAt()469 public Instant getLastUsedAt() { 470 return mLastUsedAt; 471 } 472 toString()473 public String toString() { 474 return "{oifVirtualIdxes: " 475 + mOifVirtualIndices.toString() 476 + ", lastUsedAt: " 477 + mLastUsedAt.toString() 478 + "}"; 479 } 480 } 481 482 /** 483 * Returns the MFC value for the given MFC key according to current multicast routing config. If 484 * the MFC should be removed return null. 485 */ computeMfcValue(int iif, Inet6Address dst)486 private MfcValue computeMfcValue(int iif, Inet6Address dst) { 487 final int dstScope = getGroupAddressScope(dst); 488 Set<Integer> forwardingOifs = new ArraySet<>(); 489 490 PerInterfaceMulticastRoutingConfig iifConfig = 491 mMulticastRoutingConfigs.get(getInterfaceName(iif)); 492 493 if (iifConfig == null) { 494 // An iif may have been removed from multicast routing, in this 495 // case remove the MFC directly 496 return null; 497 } 498 499 for (var config : iifConfig.oifConfigs.entrySet()) { 500 if ((config.getValue().getForwardingMode() == FORWARD_WITH_MIN_SCOPE 501 && config.getValue().getMinimumScope() <= dstScope) 502 || (config.getValue().getForwardingMode() == FORWARD_SELECTED 503 && config.getValue().getListeningAddresses().contains(dst))) { 504 forwardingOifs.add(getVirtualInterfaceIndex(config.getKey())); 505 } 506 } 507 508 return new MfcValue(forwardingOifs, Instant.now(mDependencies.getClock())); 509 } 510 511 /** 512 * Given the iif, source address and group destination address, add an MFC entry or update the 513 * existing MFC according to the multicast routing config. If such an MFC should not exist, 514 * return null for caller of the function to remove it. 515 * 516 * <p>Note that if a packet has no matching MFC entry in the kernel, kernel creates an 517 * unresolved route and notifies multicast socket with a NOCACHE upcall message. The unresolved 518 * route is kept for no less than 10s. If packets with the same source and destination arrives 519 * before the 10s timeout, they will not be notified. Thus we need to add a 'blocking' MFC which 520 * is an MFC with an empty oif list. When the multicast configs changes, the 'blocking' MFC 521 * will be updated to a 'forwarding' MFC so that corresponding multicast traffic can be 522 * forwarded instantly. 523 * 524 * @return {@code true} if the MFC is updated and no operation is needed from caller. 525 * {@code false} if the MFC should not be added, caller of the function should remove 526 * the MFC if needed. 527 */ addOrUpdateMfc(int vif, Inet6Address src, Inet6Address dst)528 private boolean addOrUpdateMfc(int vif, Inet6Address src, Inet6Address dst) { 529 checkOnHandlerThread(); 530 final MfcKey key = new MfcKey(vif, src, dst); 531 final MfcValue value = mMfcs.get(key); 532 final MfcValue updatedValue = computeMfcValue(vif, dst); 533 534 if (updatedValue == null) { 535 return false; 536 } 537 538 if (value != null && value.hasSameOifsAs(updatedValue)) { 539 // no updates to make 540 return true; 541 } 542 543 final StructMf6cctl mf6cctl = 544 new StructMf6cctl(src, dst, vif, updatedValue.getOifIndices()); 545 try { 546 mDependencies.setsockoptMrt6AddMfc(mMulticastRoutingFd, mf6cctl); 547 } catch (ErrnoException e) { 548 Log.e(TAG, "failed to add MFC: " + e); 549 return false; 550 } 551 mMfcs.put(key, updatedValue); 552 String operation = (value == null ? "Added" : "Updated"); 553 Log.d(TAG, operation + " MFC key: " + key + " value: " + updatedValue); 554 return true; 555 } 556 checkMfcsExpiration()557 private void checkMfcsExpiration() { 558 checkOnHandlerThread(); 559 // Check if there are inactive MFCs that can be removed 560 refreshMfcInactiveDuration(); 561 maybeExpireMfcs(); 562 if (mMfcs.size() > 0) { 563 mHandler.postDelayed(() -> checkMfcsExpiration(), MFC_INACTIVE_CHECK_INTERVAL_MS); 564 mMfcPollingScheduled = true; 565 } else { 566 mMfcPollingScheduled = false; 567 } 568 } 569 checkMfcEntriesLimit()570 private void checkMfcEntriesLimit() { 571 checkOnHandlerThread(); 572 // If the max number of MFC entries is reached, remove the first MFC entry. This can be 573 // any entry, as if this entry is needed again there will be a NOCACHE upcall to add it 574 // back. 575 if (mMfcs.size() == MFC_MAX_NUMBER_OF_ENTRIES) { 576 Log.w(TAG, "Reached max number of MFC entries " + MFC_MAX_NUMBER_OF_ENTRIES); 577 var iter = mMfcs.entrySet().iterator(); 578 MfcKey firstMfcKey = iter.next().getKey(); 579 removeMfcFromKernel(firstMfcKey); 580 iter.remove(); 581 } 582 } 583 584 /** 585 * Reads multicast routes information from the kernel, and update the last used timestamp for 586 * each multicast route save in this class. 587 */ refreshMfcInactiveDuration()588 private void refreshMfcInactiveDuration() { 589 checkOnHandlerThread(); 590 final List<RtNetlinkRouteMessage> multicastRoutes = NetlinkUtils.getIpv6MulticastRoutes(); 591 592 for (var route : multicastRoutes) { 593 if (!route.isResolved()) { 594 continue; // Don't handle unresolved mfc, the kernel will recycle in 10s 595 } 596 Integer iif = getVirtualInterfaceIndex(route.getIifIndex()); 597 if (iif == null) { 598 Log.e(TAG, "Can't find kernel returned IIF " + route.getIifIndex()); 599 return; 600 } 601 final MfcKey key = 602 new MfcKey( 603 iif, 604 (Inet6Address) route.getSource().getAddress(), 605 (Inet6Address) route.getDestination().getAddress()); 606 MfcValue value = mMfcs.get(key); 607 if (value == null) { 608 Log.e(TAG, "Can't find kernel returned MFC " + key); 609 continue; 610 } 611 value.setLastUsedAt( 612 Instant.now(mDependencies.getClock()) 613 .minusMillis(route.getSinceLastUseMillis())); 614 } 615 } 616 617 /** Remove MFC entry from mMfcs map and the kernel if exists. */ removeMfcFromKernel(MfcKey key)618 private void removeMfcFromKernel(MfcKey key) { 619 checkOnHandlerThread(); 620 621 final MfcValue value = mMfcs.get(key); 622 final Set<Integer> oifs = new ArraySet<>(); 623 final StructMf6cctl mf6cctl = 624 new StructMf6cctl(key.mSrcAddr, key.mDstAddr, key.mIifVirtualIdx, oifs); 625 try { 626 mDependencies.setsockoptMrt6DelMfc(mMulticastRoutingFd, mf6cctl); 627 } catch (ErrnoException e) { 628 Log.e(TAG, "failed to remove MFC: " + e); 629 return; 630 } 631 Log.d(TAG, "Removed MFC key: " + key + " value: " + value); 632 } 633 634 /** 635 * This is called every MFC_INACTIVE_CHECK_INTERVAL_MS milliseconds to remove any MFC that is 636 * inactive for more than MFC_INACTIVE_TIMEOUT_MS milliseconds. 637 */ maybeExpireMfcs()638 private void maybeExpireMfcs() { 639 checkOnHandlerThread(); 640 641 for (var it = mMfcs.entrySet().iterator(); it.hasNext(); ) { 642 var entry = it.next(); 643 if (entry.getValue() 644 .getLastUsedAt() 645 .plusMillis(MFC_INACTIVE_TIMEOUT_MS) 646 .isBefore(Instant.now(mDependencies.getClock()))) { 647 removeMfcFromKernel(entry.getKey()); 648 it.remove(); 649 } 650 } 651 } 652 updateMfcs()653 private void updateMfcs() { 654 checkOnHandlerThread(); 655 656 for (Iterator<Map.Entry<MfcKey, MfcValue>> it = mMfcs.entrySet().iterator(); 657 it.hasNext(); ) { 658 MfcKey key = it.next().getKey(); 659 if (!addOrUpdateMfc(key.mIifVirtualIdx, key.mSrcAddr, key.mDstAddr)) { 660 removeMfcFromKernel(key); 661 it.remove(); 662 } 663 } 664 665 refreshMfcInactiveDuration(); 666 } 667 joinGroups(int ifIndex, List<Inet6Address> addresses)668 private void joinGroups(int ifIndex, List<Inet6Address> addresses) { 669 for (Inet6Address address : addresses) { 670 InetSocketAddress socketAddress = new InetSocketAddress(address, 0); 671 try { 672 mMulticastSocket.joinGroup( 673 socketAddress, mDependencies.getNetworkInterface(ifIndex)); 674 } catch (IOException e) { 675 if (e.getCause() instanceof ErrnoException) { 676 ErrnoException ee = (ErrnoException) e.getCause(); 677 if (ee.errno == EADDRINUSE) { 678 // The list of added address are calculated from address changes, 679 // repeated join group is unexpected 680 Log.e(TAG, "Already joined group" + e); 681 continue; 682 } 683 } 684 Log.e(TAG, "failed to join group: " + e); 685 } 686 } 687 } 688 leaveGroups(int ifIndex, List<Inet6Address> addresses)689 private void leaveGroups(int ifIndex, List<Inet6Address> addresses) { 690 for (Inet6Address address : addresses) { 691 InetSocketAddress socketAddress = new InetSocketAddress(address, 0); 692 try { 693 mMulticastSocket.leaveGroup( 694 socketAddress, mDependencies.getNetworkInterface(ifIndex)); 695 } catch (IOException e) { 696 Log.e(TAG, "failed to leave group: " + e); 697 } 698 } 699 } 700 getGroupAddressScope(Inet6Address address)701 private int getGroupAddressScope(Inet6Address address) { 702 return address.getAddress()[1] & 0xf; 703 } 704 705 /** 706 * Handles a NoCache upcall that indicates a multicast packet is received and requires 707 * a multicast forwarding cache to be added. 708 * 709 * A forwarding or blocking MFC is added according to the multicast config. 710 * 711 * The number of MFCs is checked to make sure it doesn't exceed the 712 * {@code MFC_MAX_NUMBER_OF_ENTRIES} limit. 713 */ 714 @VisibleForTesting handleMulticastNocacheUpcall(final StructMrt6Msg mrt6Msg)715 public void handleMulticastNocacheUpcall(final StructMrt6Msg mrt6Msg) { 716 final int iifVid = mrt6Msg.mif; 717 718 // add MFC to forward the packet or add blocking MFC to not forward the packet 719 // If the packet comes from an interface the service doesn't care about, the 720 // addOrUpdateMfc function will return null and not MFC will be added. 721 if (!addOrUpdateMfc(iifVid, mrt6Msg.src, mrt6Msg.dst)) return; 722 // If the list of MFCs is not empty and there is no MFC check scheduled, 723 // schedule one now 724 if (!mMfcPollingScheduled) { 725 mHandler.postDelayed(() -> checkMfcsExpiration(), MFC_INACTIVE_CHECK_INTERVAL_MS); 726 mMfcPollingScheduled = true; 727 } 728 729 checkMfcEntriesLimit(); 730 } 731 732 /** 733 * A packet reader that handles the packets sent to the multicast routing socket 734 */ 735 private final class MulticastNocacheUpcallListener extends PacketReader { 736 private final FileDescriptor mFd; 737 MulticastNocacheUpcallListener(Handler h, FileDescriptor fd)738 public MulticastNocacheUpcallListener(Handler h, FileDescriptor fd) { 739 super(h); 740 mFd = fd; 741 } 742 743 @Override createFd()744 protected FileDescriptor createFd() { 745 return mFd; 746 } 747 748 @Override handlePacket(byte[] recvbuf, int length)749 protected void handlePacket(byte[] recvbuf, int length) { 750 final ByteBuffer buf = ByteBuffer.wrap(recvbuf); 751 final StructMrt6Msg mrt6Msg = StructMrt6Msg.parse(buf); 752 if (mrt6Msg.msgType != StructMrt6Msg.MRT6MSG_NOCACHE) { 753 return; 754 } 755 handleMulticastNocacheUpcall(mrt6Msg); 756 } 757 } 758 759 /** Dependencies of RoutingCoordinatorService, for test injections. */ 760 @VisibleForTesting 761 public static class Dependencies { 762 private final Clock mClock = Clock.system(ZoneId.systemDefault()); 763 764 /** 765 * Creates a socket to configure multicast routing in the kernel. 766 * 767 * <p>If the kernel doesn't support multicast routing, then the {@code setsockoptInt} with 768 * {@code MRT6_INIT} method would fail. 769 * 770 * @return the multicast routing socket, or null if it fails to be created/configured. 771 */ createMulticastRoutingSocket()772 public FileDescriptor createMulticastRoutingSocket() { 773 FileDescriptor sock = null; 774 byte[] filter = new byte[32]; // filter all ICMPv6 messages 775 try { 776 sock = Os.socket(AF_INET6, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, IPPROTO_ICMPV6); 777 Os.setsockoptInt(sock, IPPROTO_IPV6, MRT6_INIT, ONE); 778 NetworkUtils.setsockoptBytes(sock, IPPROTO_ICMPV6, ICMP6_FILTER, filter); 779 } catch (ErrnoException e) { 780 Log.e(TAG, "failed to create multicast socket: " + e); 781 if (sock != null) { 782 SocketUtils.closeSocketQuietly(sock); 783 } 784 throw new UnsupportedOperationException("Multicast routing is not supported ", e); 785 } 786 Log.i(TAG, "socket created for multicast routing: " + sock); 787 return sock; 788 } 789 createMulticastSocket()790 public MulticastSocket createMulticastSocket() { 791 try { 792 return new MulticastSocket(); 793 } catch (IOException e) { 794 Log.wtf(TAG, "Failed to create multicast socket " + e); 795 throw new IllegalStateException(e); 796 } 797 } 798 setsockoptMrt6AddMif(FileDescriptor fd, StructMif6ctl mif6ctl)799 public void setsockoptMrt6AddMif(FileDescriptor fd, StructMif6ctl mif6ctl) 800 throws ErrnoException { 801 final byte[] bytes = mif6ctl.writeToBytes(); 802 NetworkUtils.setsockoptBytes(fd, IPPROTO_IPV6, MRT6_ADD_MIF, bytes); 803 } 804 setsockoptMrt6DelMif(FileDescriptor fd, int virtualIfIndex)805 public void setsockoptMrt6DelMif(FileDescriptor fd, int virtualIfIndex) 806 throws ErrnoException { 807 Os.setsockoptInt(fd, IPPROTO_IPV6, MRT6_DEL_MIF, virtualIfIndex); 808 } 809 setsockoptMrt6AddMfc(FileDescriptor fd, StructMf6cctl mf6cctl)810 public void setsockoptMrt6AddMfc(FileDescriptor fd, StructMf6cctl mf6cctl) 811 throws ErrnoException { 812 final byte[] bytes = mf6cctl.writeToBytes(); 813 NetworkUtils.setsockoptBytes(fd, IPPROTO_IPV6, MRT6_ADD_MFC, bytes); 814 } 815 setsockoptMrt6DelMfc(FileDescriptor fd, StructMf6cctl mf6cctl)816 public void setsockoptMrt6DelMfc(FileDescriptor fd, StructMf6cctl mf6cctl) 817 throws ErrnoException { 818 final byte[] bytes = mf6cctl.writeToBytes(); 819 NetworkUtils.setsockoptBytes(fd, IPPROTO_IPV6, MRT6_DEL_MFC, bytes); 820 } 821 822 /** 823 * Returns the interface index for an interface name, or 0 if the interface index could 824 * not be found. 825 */ getInterfaceIndex(String ifName)826 public int getInterfaceIndex(String ifName) { 827 return Os.if_nametoindex(ifName); 828 } 829 getNetworkInterface(int physicalIndex)830 public NetworkInterface getNetworkInterface(int physicalIndex) { 831 try { 832 return NetworkInterface.getByIndex(physicalIndex); 833 } catch (SocketException e) { 834 return null; 835 } 836 } 837 getClock()838 public Clock getClock() { 839 return mClock; 840 } 841 } 842 } 843