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.Manifest.permission; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.annotation.RequiresPermission; 23 import android.content.Context; 24 import android.net.Network; 25 import android.net.wifi.WifiManager.MulticastLock; 26 import android.os.SystemClock; 27 import android.text.format.DateUtils; 28 import android.util.Log; 29 30 import com.android.internal.annotations.VisibleForTesting; 31 import com.android.net.module.util.SharedLog; 32 33 import java.io.IOException; 34 import java.net.DatagramPacket; 35 import java.net.Inet4Address; 36 import java.net.Inet6Address; 37 import java.net.InetSocketAddress; 38 import java.util.ArrayDeque; 39 import java.util.ArrayList; 40 import java.util.List; 41 import java.util.Queue; 42 import java.util.Timer; 43 import java.util.TimerTask; 44 import java.util.concurrent.atomic.AtomicBoolean; 45 import java.util.concurrent.atomic.AtomicInteger; 46 47 /** 48 * The {@link MdnsSocketClient} maintains separate threads to send and receive mDNS packets for all 49 * the requested service types. 50 * 51 * <p>See https://tools.ietf.org/html/rfc6763 (namely sections 4 and 5). 52 */ 53 public class MdnsSocketClient implements MdnsSocketClientBase { 54 55 private static final String TAG = "MdnsClient"; 56 // TODO: The following values are copied from cast module. We need to think about the 57 // better way to share those. 58 private static final String CAST_SENDER_LOG_SOURCE = "CAST_SENDER_SDK"; 59 private static final String CAST_PREFS_NAME = "google_cast"; 60 private static final String PREF_CAST_SENDER_ID = "PREF_CAST_SENDER_ID"; 61 private static final String MULTICAST_TYPE = "multicast"; 62 private static final String UNICAST_TYPE = "unicast"; 63 64 private static final long SLEEP_TIME_FOR_SOCKET_THREAD_MS = 65 MdnsConfigs.sleepTimeForSocketThreadMs(); 66 // A value of 0 leads to an infinite wait. 67 private static final long THREAD_JOIN_TIMEOUT_MS = DateUtils.SECOND_IN_MILLIS; 68 private static final int RECEIVER_BUFFER_SIZE = 2048; 69 @VisibleForTesting 70 final Queue<DatagramPacket> multicastPacketQueue = new ArrayDeque<>(); 71 @VisibleForTesting 72 final Queue<DatagramPacket> unicastPacketQueue = new ArrayDeque<>(); 73 private final Context context; 74 private final byte[] multicastReceiverBuffer = new byte[RECEIVER_BUFFER_SIZE]; 75 @Nullable private final byte[] unicastReceiverBuffer; 76 private final MulticastLock multicastLock; 77 private final boolean useSeparateSocketForUnicast = 78 MdnsConfigs.useSeparateSocketToSendUnicastQuery(); 79 private final boolean checkMulticastResponse = MdnsConfigs.checkMulticastResponse(); 80 private final long checkMulticastResponseIntervalMs = 81 MdnsConfigs.checkMulticastResponseIntervalMs(); 82 private final boolean propagateInterfaceIndex = 83 MdnsConfigs.allowNetworkInterfaceIndexPropagation(); 84 private final Object socketLock = new Object(); 85 private final Object timerObject = new Object(); 86 // If multicast response was received in the current session. The value is reset in the 87 // beginning of each session. 88 @VisibleForTesting 89 boolean receivedMulticastResponse; 90 // If unicast response was received in the current session. The value is reset in the beginning 91 // of each session. 92 @VisibleForTesting 93 boolean receivedUnicastResponse; 94 // If the phone is the bad state where it can't receive any multicast response. 95 @VisibleForTesting 96 AtomicBoolean cannotReceiveMulticastResponse = new AtomicBoolean(false); 97 @VisibleForTesting @Nullable volatile Thread sendThread; 98 @VisibleForTesting @Nullable Thread multicastReceiveThread; 99 @VisibleForTesting @Nullable Thread unicastReceiveThread; 100 private volatile boolean shouldStopSocketLoop; 101 @Nullable private Callback callback; 102 @Nullable private MdnsSocket multicastSocket; 103 @Nullable private MdnsSocket unicastSocket; 104 private int receivedPacketNumber = 0; 105 @Nullable private Timer logMdnsPacketTimer; 106 private AtomicInteger packetsCount; 107 @Nullable private Timer checkMulticastResponseTimer; 108 private final SharedLog sharedLog; 109 @NonNull private final MdnsFeatureFlags mdnsFeatureFlags; 110 private final MulticastNetworkInterfaceProvider interfaceProvider; 111 MdnsSocketClient(@onNull Context context, @NonNull MulticastLock multicastLock, SharedLog sharedLog, @NonNull MdnsFeatureFlags mdnsFeatureFlags)112 public MdnsSocketClient(@NonNull Context context, @NonNull MulticastLock multicastLock, 113 SharedLog sharedLog, @NonNull MdnsFeatureFlags mdnsFeatureFlags) { 114 this.sharedLog = sharedLog; 115 this.context = context; 116 this.multicastLock = multicastLock; 117 if (useSeparateSocketForUnicast) { 118 unicastReceiverBuffer = new byte[RECEIVER_BUFFER_SIZE]; 119 } else { 120 unicastReceiverBuffer = null; 121 } 122 this.mdnsFeatureFlags = mdnsFeatureFlags; 123 this.interfaceProvider = new MulticastNetworkInterfaceProvider(context, sharedLog); 124 } 125 126 @Override setCallback(@ullable Callback callback)127 public synchronized void setCallback(@Nullable Callback callback) { 128 this.callback = callback; 129 } 130 131 @RequiresPermission(permission.CHANGE_WIFI_MULTICAST_STATE) 132 @Override startDiscovery()133 public synchronized void startDiscovery() throws IOException { 134 if (multicastSocket != null) { 135 sharedLog.w("Discovery is already in progress."); 136 return; 137 } 138 139 receivedMulticastResponse = false; 140 receivedUnicastResponse = false; 141 cannotReceiveMulticastResponse.set(false); 142 143 shouldStopSocketLoop = false; 144 interfaceProvider.startWatchingConnectivityChanges(); 145 try { 146 // TODO (changed when importing code): consider setting thread stats tag 147 multicastSocket = createMdnsSocket(MdnsConstants.MDNS_PORT, sharedLog); 148 multicastSocket.joinGroup(); 149 if (useSeparateSocketForUnicast) { 150 // For unicast, use port 0 and the system will assign it with any available port. 151 unicastSocket = createMdnsSocket(0, sharedLog); 152 } 153 multicastLock.acquire(); 154 } catch (IOException e) { 155 multicastLock.release(); 156 if (multicastSocket != null) { 157 multicastSocket.close(); 158 multicastSocket = null; 159 } 160 if (unicastSocket != null) { 161 unicastSocket.close(); 162 unicastSocket = null; 163 } 164 throw e; 165 } finally { 166 // TODO (changed when importing code): consider resetting thread stats tag 167 } 168 createAndStartSendThread(); 169 createAndStartReceiverThreads(); 170 } 171 172 @RequiresPermission(permission.CHANGE_WIFI_MULTICAST_STATE) 173 @Override stopDiscovery()174 public void stopDiscovery() { 175 sharedLog.log("Stop discovery."); 176 if (multicastSocket == null && unicastSocket == null) { 177 return; 178 } 179 180 if (MdnsConfigs.clearMdnsPacketQueueAfterDiscoveryStops()) { 181 synchronized (multicastPacketQueue) { 182 multicastPacketQueue.clear(); 183 } 184 synchronized (unicastPacketQueue) { 185 unicastPacketQueue.clear(); 186 } 187 } 188 189 multicastLock.release(); 190 interfaceProvider.stopWatchingConnectivityChanges(); 191 192 shouldStopSocketLoop = true; 193 waitForSendThreadToStop(); 194 waitForReceiverThreadsToStop(); 195 196 synchronized (socketLock) { 197 multicastSocket = null; 198 unicastSocket = null; 199 } 200 201 synchronized (timerObject) { 202 if (checkMulticastResponseTimer != null) { 203 checkMulticastResponseTimer.cancel(); 204 checkMulticastResponseTimer = null; 205 } 206 } 207 } 208 209 @Override sendPacketRequestingMulticastResponse(@onNull List<DatagramPacket> packets, boolean onlyUseIpv6OnIpv6OnlyNetworks)210 public void sendPacketRequestingMulticastResponse(@NonNull List<DatagramPacket> packets, 211 boolean onlyUseIpv6OnIpv6OnlyNetworks) { 212 sendMdnsPackets(packets, multicastPacketQueue, onlyUseIpv6OnIpv6OnlyNetworks); 213 } 214 215 @Override sendPacketRequestingUnicastResponse(@onNull List<DatagramPacket> packets, boolean onlyUseIpv6OnIpv6OnlyNetworks)216 public void sendPacketRequestingUnicastResponse(@NonNull List<DatagramPacket> packets, 217 boolean onlyUseIpv6OnIpv6OnlyNetworks) { 218 if (useSeparateSocketForUnicast) { 219 sendMdnsPackets(packets, unicastPacketQueue, onlyUseIpv6OnIpv6OnlyNetworks); 220 } else { 221 sendMdnsPackets(packets, multicastPacketQueue, onlyUseIpv6OnIpv6OnlyNetworks); 222 } 223 } 224 225 @Override notifyNetworkRequested( @onNull MdnsServiceBrowserListener listener, @Nullable Network network, @NonNull SocketCreationCallback socketCreationCallback)226 public void notifyNetworkRequested( 227 @NonNull MdnsServiceBrowserListener listener, 228 @Nullable Network network, 229 @NonNull SocketCreationCallback socketCreationCallback) { 230 if (network != null) { 231 throw new IllegalArgumentException("This socket client does not support requesting " 232 + "specific networks"); 233 } 234 socketCreationCallback.onSocketCreated(new SocketKey(multicastSocket.getInterfaceIndex())); 235 } 236 237 @Override supportsRequestingSpecificNetworks()238 public boolean supportsRequestingSpecificNetworks() { 239 return false; 240 } 241 sendMdnsPackets(List<DatagramPacket> packets, Queue<DatagramPacket> packetQueueToUse, boolean onlyUseIpv6OnIpv6OnlyNetworks)242 private void sendMdnsPackets(List<DatagramPacket> packets, 243 Queue<DatagramPacket> packetQueueToUse, boolean onlyUseIpv6OnIpv6OnlyNetworks) { 244 if (shouldStopSocketLoop && !MdnsConfigs.allowAddMdnsPacketAfterDiscoveryStops()) { 245 sharedLog.w("sendMdnsPacket() is called after discovery already stopped"); 246 return; 247 } 248 if (packets.isEmpty()) { 249 Log.wtf(TAG, "No mDns packets to send"); 250 return; 251 } 252 253 final boolean isIpv4 = ((InetSocketAddress) packets.get(0).getSocketAddress()) 254 .getAddress() instanceof Inet4Address; 255 final boolean isIpv6 = ((InetSocketAddress) packets.get(0).getSocketAddress()) 256 .getAddress() instanceof Inet6Address; 257 final boolean ipv6Only = multicastSocket != null && multicastSocket.isOnIPv6OnlyNetwork(); 258 if (isIpv4 && ipv6Only) { 259 return; 260 } 261 if (isIpv6 && !ipv6Only && onlyUseIpv6OnIpv6OnlyNetworks) { 262 return; 263 } 264 265 synchronized (packetQueueToUse) { 266 while ((packetQueueToUse.size() + packets.size()) 267 > MdnsConfigs.mdnsPacketQueueMaxSize()) { 268 packetQueueToUse.remove(); 269 } 270 packetQueueToUse.addAll(packets); 271 } 272 triggerSendThread(); 273 } 274 createAndStartSendThread()275 private void createAndStartSendThread() { 276 if (sendThread != null) { 277 sharedLog.w("A socket thread already exists."); 278 return; 279 } 280 sendThread = new Thread(this::sendThreadMain); 281 sendThread.setName("mdns-send"); 282 sendThread.start(); 283 } 284 createAndStartReceiverThreads()285 private void createAndStartReceiverThreads() { 286 if (multicastReceiveThread != null) { 287 sharedLog.w("A multicast receiver thread already exists."); 288 return; 289 } 290 multicastReceiveThread = 291 new Thread(() -> receiveThreadMain(multicastReceiverBuffer, multicastSocket)); 292 multicastReceiveThread.setName("mdns-multicast-receive"); 293 multicastReceiveThread.start(); 294 295 if (useSeparateSocketForUnicast) { 296 unicastReceiveThread = 297 new Thread( 298 () -> { 299 if (unicastReceiverBuffer != null) { 300 receiveThreadMain(unicastReceiverBuffer, unicastSocket); 301 } 302 }); 303 unicastReceiveThread.setName("mdns-unicast-receive"); 304 unicastReceiveThread.start(); 305 } 306 } 307 triggerSendThread()308 private void triggerSendThread() { 309 sharedLog.log("Trigger send thread."); 310 Thread sendThread = this.sendThread; 311 if (sendThread != null) { 312 sendThread.interrupt(); 313 } else { 314 sharedLog.w("Socket thread is null"); 315 } 316 } 317 waitForReceiverThreadsToStop()318 private void waitForReceiverThreadsToStop() { 319 if (multicastReceiveThread != null) { 320 waitForThread(multicastReceiveThread); 321 multicastReceiveThread = null; 322 } 323 324 if (unicastReceiveThread != null) { 325 waitForThread(unicastReceiveThread); 326 unicastReceiveThread = null; 327 } 328 } 329 waitForSendThreadToStop()330 private void waitForSendThreadToStop() { 331 sharedLog.log("wait For Send Thread To Stop"); 332 if (sendThread == null) { 333 sharedLog.w("socket thread is already dead."); 334 return; 335 } 336 waitForThread(sendThread); 337 sendThread = null; 338 } 339 waitForThread(Thread thread)340 private void waitForThread(Thread thread) { 341 long startMs = SystemClock.elapsedRealtime(); 342 long waitMs = THREAD_JOIN_TIMEOUT_MS; 343 while (thread.isAlive() && (waitMs > 0)) { 344 try { 345 thread.interrupt(); 346 thread.join(waitMs); 347 if (thread.isAlive()) { 348 sharedLog.w("Failed to join thread: " + thread); 349 } 350 break; 351 } catch (InterruptedException e) { 352 // Compute remaining time after at least a single join call, in case the clock 353 // resolution is poor. 354 waitMs = THREAD_JOIN_TIMEOUT_MS - (SystemClock.elapsedRealtime() - startMs); 355 } 356 } 357 } 358 sendThreadMain()359 private void sendThreadMain() { 360 List<DatagramPacket> multicastPacketsToSend = new ArrayList<>(); 361 List<DatagramPacket> unicastPacketsToSend = new ArrayList<>(); 362 boolean shouldThreadSleep; 363 try { 364 while (!shouldStopSocketLoop) { 365 try { 366 // Make a local copy of all packets, and clear the queue. 367 // Send packets that ask for multicast response. 368 multicastPacketsToSend.clear(); 369 synchronized (multicastPacketQueue) { 370 multicastPacketsToSend.addAll(multicastPacketQueue); 371 multicastPacketQueue.clear(); 372 } 373 374 // Send packets that ask for unicast response. 375 if (useSeparateSocketForUnicast) { 376 unicastPacketsToSend.clear(); 377 synchronized (unicastPacketQueue) { 378 unicastPacketsToSend.addAll(unicastPacketQueue); 379 unicastPacketQueue.clear(); 380 } 381 if (unicastSocket != null) { 382 sendPackets(unicastPacketsToSend, unicastSocket); 383 } 384 } 385 386 // Send multicast packets. 387 if (multicastSocket != null) { 388 sendPackets(multicastPacketsToSend, multicastSocket); 389 } 390 391 // Sleep ONLY if no more packets have been added to the queue, while packets 392 // were being sent. 393 synchronized (multicastPacketQueue) { 394 synchronized (unicastPacketQueue) { 395 shouldThreadSleep = 396 multicastPacketQueue.isEmpty() && unicastPacketQueue.isEmpty(); 397 } 398 } 399 if (shouldThreadSleep) { 400 Thread.sleep(SLEEP_TIME_FOR_SOCKET_THREAD_MS); 401 } 402 } catch (InterruptedException e) { 403 // Don't log the interruption as it's expected. 404 } 405 } 406 } finally { 407 sharedLog.log("Send thread stopped."); 408 try { 409 if (multicastSocket != null) { 410 multicastSocket.leaveGroup(); 411 } 412 } catch (Exception t) { 413 sharedLog.e("Failed to leave the group.", t); 414 } 415 416 // Close the socket first. This is the only way to interrupt a blocking receive. 417 try { 418 // This is a race with the use of the file descriptor (b/27403984). 419 if (multicastSocket != null) { 420 multicastSocket.close(); 421 } 422 if (unicastSocket != null) { 423 unicastSocket.close(); 424 } 425 } catch (RuntimeException t) { 426 sharedLog.e("Failed to close the mdns socket.", t); 427 } 428 } 429 } 430 receiveThreadMain(byte[] receiverBuffer, @Nullable MdnsSocket socket)431 private void receiveThreadMain(byte[] receiverBuffer, @Nullable MdnsSocket socket) { 432 DatagramPacket packet = new DatagramPacket(receiverBuffer, receiverBuffer.length); 433 434 while (!shouldStopSocketLoop) { 435 try { 436 // This is a race with the use of the file descriptor (b/27403984). 437 synchronized (socketLock) { 438 // This checks is to make sure the socket was not set to null. 439 if (socket != null && (socket == multicastSocket || socket == unicastSocket)) { 440 socket.receive(packet); 441 } 442 } 443 444 if (!shouldStopSocketLoop) { 445 String responseType = socket == multicastSocket ? MULTICAST_TYPE : UNICAST_TYPE; 446 processResponsePacket( 447 packet, 448 responseType, 449 /* interfaceIndex= */ (socket == null || !propagateInterfaceIndex) 450 ? MdnsSocket.INTERFACE_INDEX_UNSPECIFIED 451 : socket.getInterfaceIndex(), 452 /* network= */ socket.getNetwork()); 453 } 454 } catch (IOException e) { 455 if (!shouldStopSocketLoop) { 456 sharedLog.e("Failed to receive mDNS packets.", e); 457 } 458 } 459 } 460 sharedLog.log("Receive thread stopped."); 461 } 462 processResponsePacket(@onNull DatagramPacket packet, String responseType, int interfaceIndex, @Nullable Network network)463 private int processResponsePacket(@NonNull DatagramPacket packet, String responseType, 464 int interfaceIndex, @Nullable Network network) { 465 int packetNumber = ++receivedPacketNumber; 466 467 final MdnsPacket response; 468 try { 469 response = MdnsResponseDecoder.parseResponse( 470 packet.getData(), packet.getLength(), mdnsFeatureFlags); 471 } catch (MdnsPacket.ParseException e) { 472 sharedLog.w(String.format("Error while decoding %s packet (%d): %d", 473 responseType, packetNumber, e.code)); 474 if (callback != null) { 475 callback.onFailedToParseMdnsResponse(packetNumber, e.code, 476 new SocketKey(network, interfaceIndex)); 477 } 478 return e.code; 479 } 480 481 if (response == null) { 482 return MdnsResponseErrorCode.ERROR_NOT_RESPONSE_MESSAGE; 483 } 484 485 if (callback != null) { 486 callback.onResponseReceived( 487 response, new SocketKey(network, interfaceIndex)); 488 } 489 490 return MdnsResponseErrorCode.SUCCESS; 491 } 492 493 @VisibleForTesting createMdnsSocket(int port, SharedLog sharedLog)494 MdnsSocket createMdnsSocket(int port, SharedLog sharedLog) throws IOException { 495 return new MdnsSocket(interfaceProvider, port, sharedLog); 496 } 497 sendPackets(List<DatagramPacket> packets, MdnsSocket socket)498 private void sendPackets(List<DatagramPacket> packets, MdnsSocket socket) { 499 String requestType = socket == multicastSocket ? "multicast" : "unicast"; 500 for (DatagramPacket packet : packets) { 501 if (shouldStopSocketLoop) { 502 break; 503 } 504 try { 505 sharedLog.log(String.format("Sending a %s mDNS packet...", requestType)); 506 socket.send(packet); 507 508 // Start the timer task to monitor the response. 509 synchronized (timerObject) { 510 if (socket == multicastSocket) { 511 if (cannotReceiveMulticastResponse.get()) { 512 // Don't schedule the timer task if we are already in the bad state. 513 return; 514 } 515 if (checkMulticastResponseTimer != null) { 516 // Don't schedule the timer task if it's already scheduled. 517 return; 518 } 519 if (checkMulticastResponse && useSeparateSocketForUnicast) { 520 // Only when useSeparateSocketForUnicast is true, we can tell if we 521 // received a multicast or unicast response. 522 checkMulticastResponseTimer = new Timer(); 523 checkMulticastResponseTimer.schedule( 524 new TimerTask() { 525 @Override 526 public void run() { 527 synchronized (timerObject) { 528 if (checkMulticastResponseTimer == null) { 529 // Discovery already stopped. 530 return; 531 } 532 if ((!receivedMulticastResponse) 533 && receivedUnicastResponse) { 534 sharedLog.e(String.format( 535 "Haven't received multicast response" 536 + " in the last %d ms.", 537 checkMulticastResponseIntervalMs)); 538 cannotReceiveMulticastResponse.set(true); 539 } 540 checkMulticastResponseTimer = null; 541 } 542 } 543 }, 544 checkMulticastResponseIntervalMs); 545 } 546 } 547 } 548 } catch (IOException e) { 549 sharedLog.e(String.format("Failed to send a %s mDNS packet.", requestType), e); 550 } 551 } 552 packets.clear(); 553 } 554 }