• 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.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 }