1 /*
2  * Copyright (C) 2021 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.mdns;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.content.Context;
22 import android.net.Network;
23 
24 import com.android.internal.annotations.VisibleForTesting;
25 import com.android.net.module.util.SharedLog;
26 
27 import java.io.IOException;
28 import java.net.Inet4Address;
29 import java.net.Inet6Address;
30 import java.net.InterfaceAddress;
31 import java.net.NetworkInterface;
32 import java.net.SocketException;
33 import java.util.ArrayList;
34 import java.util.Enumeration;
35 import java.util.List;
36 
37 /**
38  * This class is used by the {@link MdnsSocket} to monitor the list of {@link NetworkInterface}
39  * instances that are currently available for multi-cast messaging.
40  */
41 public class MulticastNetworkInterfaceProvider {
42 
43     private static final String TAG = "MdnsNIProvider";
44     private final SharedLog sharedLog;
45     private static final boolean PREFER_IPV6 = MdnsConfigs.preferIpv6();
46 
47     private final List<NetworkInterfaceWrapper> multicastNetworkInterfaces = new ArrayList<>();
48     // Only modifiable from tests.
49     @VisibleForTesting
50     ConnectivityMonitor connectivityMonitor;
51     private volatile boolean connectivityChanged = true;
52 
53     @SuppressWarnings("nullness:methodref.receiver.bound")
MulticastNetworkInterfaceProvider(@onNull Context context, @NonNull SharedLog sharedLog)54     public MulticastNetworkInterfaceProvider(@NonNull Context context,
55             @NonNull SharedLog sharedLog) {
56         this.sharedLog = sharedLog;
57         // IMPORT CHANGED
58         this.connectivityMonitor = new ConnectivityMonitorWithConnectivityManager(
59                 context, this::onConnectivityChanged, sharedLog);
60     }
61 
onConnectivityChanged()62     private synchronized void onConnectivityChanged() {
63         connectivityChanged = true;
64     }
65 
66     /**
67      * Starts monitoring changes of connectivity of this device, which may indicate that the list of
68      * network interfaces available for multi-cast messaging has changed.
69      */
startWatchingConnectivityChanges()70     public void startWatchingConnectivityChanges() {
71         connectivityMonitor.startWatchingConnectivityChanges();
72     }
73 
74     /** Stops monitoring changes of connectivity. */
stopWatchingConnectivityChanges()75     public void stopWatchingConnectivityChanges() {
76         connectivityMonitor.stopWatchingConnectivityChanges();
77     }
78 
79     /**
80      * Returns the list of {@link NetworkInterfaceWrapper} instances available for multi-cast
81      * messaging.
82      */
getMulticastNetworkInterfaces()83     public synchronized List<NetworkInterfaceWrapper> getMulticastNetworkInterfaces() {
84         if (connectivityChanged) {
85             connectivityChanged = false;
86             updateMulticastNetworkInterfaces();
87             if (multicastNetworkInterfaces.isEmpty()) {
88                 sharedLog.log("No network interface available for mDNS scanning.");
89             }
90         }
91         return new ArrayList<>(multicastNetworkInterfaces);
92     }
93 
updateMulticastNetworkInterfaces()94     private void updateMulticastNetworkInterfaces() {
95         multicastNetworkInterfaces.clear();
96         List<NetworkInterfaceWrapper> networkInterfaceWrappers = getNetworkInterfaces();
97         for (NetworkInterfaceWrapper interfaceWrapper : networkInterfaceWrappers) {
98             if (canScanOnInterface(interfaceWrapper, sharedLog)) {
99                 multicastNetworkInterfaces.add(interfaceWrapper);
100             }
101         }
102     }
103 
isOnIpV6OnlyNetwork(List<NetworkInterfaceWrapper> networkInterfaces)104     public boolean isOnIpV6OnlyNetwork(List<NetworkInterfaceWrapper> networkInterfaces) {
105         if (networkInterfaces.isEmpty()) {
106             return false;
107         }
108 
109         // TODO(b/79866499): Remove this when the bug is resolved.
110         if (PREFER_IPV6) {
111             return true;
112         }
113         boolean hasAtleastOneIPv6Address = false;
114         for (NetworkInterfaceWrapper interfaceWrapper : networkInterfaces) {
115             for (InterfaceAddress ifAddr : interfaceWrapper.getInterfaceAddresses()) {
116                 if (!(ifAddr.getAddress() instanceof Inet6Address)) {
117                     return false;
118                 } else {
119                     hasAtleastOneIPv6Address = true;
120                 }
121             }
122         }
123         return hasAtleastOneIPv6Address;
124     }
125 
126     @VisibleForTesting
getNetworkInterfaces()127     List<NetworkInterfaceWrapper> getNetworkInterfaces() {
128         List<NetworkInterfaceWrapper> networkInterfaceWrappers = new ArrayList<>();
129         try {
130             Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
131             if (interfaces != null) {
132                 while (interfaces.hasMoreElements()) {
133                     networkInterfaceWrappers.add(
134                             new NetworkInterfaceWrapper(interfaces.nextElement()));
135                 }
136             }
137         } catch (SocketException e) {
138             sharedLog.e("Failed to get network interfaces.", e);
139         } catch (NullPointerException e) {
140             // Android R has a bug that could lead to a NPE. See b/159277702.
141             sharedLog.e("Failed to call getNetworkInterfaces API", e);
142         }
143 
144         return networkInterfaceWrappers;
145     }
146 
147     @Nullable
getAvailableNetwork()148     public Network getAvailableNetwork() {
149         return connectivityMonitor.getAvailableNetwork();
150     }
151 
152     /*** Check whether given network interface can support mdns */
canScanOnInterface(@ullable NetworkInterfaceWrapper networkInterface, @NonNull SharedLog sharedLog)153     private static boolean canScanOnInterface(@Nullable NetworkInterfaceWrapper networkInterface,
154             @NonNull SharedLog sharedLog) {
155         try {
156             if ((networkInterface == null)
157                     || networkInterface.isLoopback()
158                     || networkInterface.isPointToPoint()
159                     || networkInterface.isVirtual()
160                     || !networkInterface.isUp()
161                     || !networkInterface.supportsMulticast()) {
162                 return false;
163             }
164             return hasInet4Address(networkInterface) || hasInet6Address(networkInterface);
165         } catch (IOException e) {
166             sharedLog.e(String.format("Failed to check interface %s.",
167                     networkInterface.getNetworkInterface().getDisplayName()), e);
168         }
169 
170         return false;
171     }
172 
hasInet4Address(@onNull NetworkInterfaceWrapper networkInterface)173     private static boolean hasInet4Address(@NonNull NetworkInterfaceWrapper networkInterface) {
174         for (InterfaceAddress ifAddr : networkInterface.getInterfaceAddresses()) {
175             if (ifAddr.getAddress() instanceof Inet4Address) {
176                 return true;
177             }
178         }
179         return false;
180     }
181 
hasInet6Address(@onNull NetworkInterfaceWrapper networkInterface)182     private static boolean hasInet6Address(@NonNull NetworkInterfaceWrapper networkInterface) {
183         for (InterfaceAddress ifAddr : networkInterface.getInterfaceAddresses()) {
184             if (ifAddr.getAddress() instanceof Inet6Address) {
185                 return true;
186             }
187         }
188         return false;
189     }
190 }