• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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