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