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