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.server.connectivity.mdns.util.MdnsLogger; 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 static final MdnsLogger LOGGER = new MdnsLogger(TAG); 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)54 public MulticastNetworkInterfaceProvider(@NonNull Context context) { 55 // IMPORT CHANGED 56 this.connectivityMonitor = new ConnectivityMonitorWithConnectivityManager( 57 context, this::onConnectivityChanged); 58 } 59 onConnectivityChanged()60 private synchronized void onConnectivityChanged() { 61 connectivityChanged = true; 62 } 63 64 /** 65 * Starts monitoring changes of connectivity of this device, which may indicate that the list of 66 * network interfaces available for multi-cast messaging has changed. 67 */ startWatchingConnectivityChanges()68 public void startWatchingConnectivityChanges() { 69 connectivityMonitor.startWatchingConnectivityChanges(); 70 } 71 72 /** Stops monitoring changes of connectivity. */ stopWatchingConnectivityChanges()73 public void stopWatchingConnectivityChanges() { 74 connectivityMonitor.stopWatchingConnectivityChanges(); 75 } 76 77 /** 78 * Returns the list of {@link NetworkInterfaceWrapper} instances available for multi-cast 79 * messaging. 80 */ getMulticastNetworkInterfaces()81 public synchronized List<NetworkInterfaceWrapper> getMulticastNetworkInterfaces() { 82 if (connectivityChanged) { 83 connectivityChanged = false; 84 updateMulticastNetworkInterfaces(); 85 if (multicastNetworkInterfaces.isEmpty()) { 86 LOGGER.log("No network interface available for mDNS scanning."); 87 } 88 } 89 return new ArrayList<>(multicastNetworkInterfaces); 90 } 91 updateMulticastNetworkInterfaces()92 private void updateMulticastNetworkInterfaces() { 93 multicastNetworkInterfaces.clear(); 94 List<NetworkInterfaceWrapper> networkInterfaceWrappers = getNetworkInterfaces(); 95 for (NetworkInterfaceWrapper interfaceWrapper : networkInterfaceWrappers) { 96 if (canScanOnInterface(interfaceWrapper)) { 97 multicastNetworkInterfaces.add(interfaceWrapper); 98 } 99 } 100 } 101 isOnIpV6OnlyNetwork(List<NetworkInterfaceWrapper> networkInterfaces)102 public boolean isOnIpV6OnlyNetwork(List<NetworkInterfaceWrapper> networkInterfaces) { 103 if (networkInterfaces.isEmpty()) { 104 return false; 105 } 106 107 // TODO(b/79866499): Remove this when the bug is resolved. 108 if (PREFER_IPV6) { 109 return true; 110 } 111 boolean hasAtleastOneIPv6Address = false; 112 for (NetworkInterfaceWrapper interfaceWrapper : networkInterfaces) { 113 for (InterfaceAddress ifAddr : interfaceWrapper.getInterfaceAddresses()) { 114 if (!(ifAddr.getAddress() instanceof Inet6Address)) { 115 return false; 116 } else { 117 hasAtleastOneIPv6Address = true; 118 } 119 } 120 } 121 return hasAtleastOneIPv6Address; 122 } 123 124 @VisibleForTesting getNetworkInterfaces()125 List<NetworkInterfaceWrapper> getNetworkInterfaces() { 126 List<NetworkInterfaceWrapper> networkInterfaceWrappers = new ArrayList<>(); 127 try { 128 Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces(); 129 if (interfaces != null) { 130 while (interfaces.hasMoreElements()) { 131 networkInterfaceWrappers.add( 132 new NetworkInterfaceWrapper(interfaces.nextElement())); 133 } 134 } 135 } catch (SocketException e) { 136 LOGGER.e("Failed to get network interfaces.", e); 137 } catch (NullPointerException e) { 138 // Android R has a bug that could lead to a NPE. See b/159277702. 139 LOGGER.e("Failed to call getNetworkInterfaces API", e); 140 } 141 142 return networkInterfaceWrappers; 143 } 144 145 @Nullable getAvailableNetwork()146 public Network getAvailableNetwork() { 147 return connectivityMonitor.getAvailableNetwork(); 148 } 149 150 /*** Check whether given network interface can support mdns */ canScanOnInterface(@ullable NetworkInterfaceWrapper networkInterface)151 private static boolean canScanOnInterface(@Nullable NetworkInterfaceWrapper networkInterface) { 152 try { 153 if ((networkInterface == null) 154 || networkInterface.isLoopback() 155 || networkInterface.isPointToPoint() 156 || networkInterface.isVirtual() 157 || !networkInterface.isUp() 158 || !networkInterface.supportsMulticast()) { 159 return false; 160 } 161 return hasInet4Address(networkInterface) || hasInet6Address(networkInterface); 162 } catch (IOException e) { 163 LOGGER.e(String.format("Failed to check interface %s.", 164 networkInterface.getNetworkInterface().getDisplayName()), e); 165 } 166 167 return false; 168 } 169 hasInet4Address(@onNull NetworkInterfaceWrapper networkInterface)170 private static boolean hasInet4Address(@NonNull NetworkInterfaceWrapper networkInterface) { 171 for (InterfaceAddress ifAddr : networkInterface.getInterfaceAddresses()) { 172 if (ifAddr.getAddress() instanceof Inet4Address) { 173 return true; 174 } 175 } 176 return false; 177 } 178 hasInet6Address(@onNull NetworkInterfaceWrapper networkInterface)179 private static boolean hasInet6Address(@NonNull NetworkInterfaceWrapper networkInterface) { 180 for (InterfaceAddress ifAddr : networkInterface.getInterfaceAddresses()) { 181 if (ifAddr.getAddress() instanceof Inet6Address) { 182 return true; 183 } 184 } 185 return false; 186 } 187 }