• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 }