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