1 /*
2  * Copyright (C) 2010 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;
18 
19 import android.content.ContentResolver;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.database.ContentObserver;
23 import android.net.NetworkStack;
24 import android.net.Uri;
25 import android.net.nsd.INsdManager;
26 import android.net.nsd.NsdManager;
27 import android.net.nsd.NsdServiceInfo;
28 import android.os.Handler;
29 import android.os.HandlerThread;
30 import android.os.Message;
31 import android.os.Messenger;
32 import android.os.UserHandle;
33 import android.provider.Settings;
34 import android.util.Base64;
35 import android.util.Slog;
36 import android.util.SparseArray;
37 import android.util.SparseIntArray;
38 
39 import com.android.internal.annotations.VisibleForTesting;
40 import com.android.internal.util.AsyncChannel;
41 import com.android.internal.util.DumpUtils;
42 import com.android.internal.util.State;
43 import com.android.internal.util.StateMachine;
44 import com.android.net.module.util.DnsSdTxtRecord;
45 
46 import java.io.FileDescriptor;
47 import java.io.PrintWriter;
48 import java.net.InetAddress;
49 import java.util.Arrays;
50 import java.util.HashMap;
51 import java.util.concurrent.CountDownLatch;
52 
53 /**
54  * Network Service Discovery Service handles remote service discovery operation requests by
55  * implementing the INsdManager interface.
56  *
57  * @hide
58  */
59 public class NsdService extends INsdManager.Stub {
60     private static final String TAG = "NsdService";
61     private static final String MDNS_TAG = "mDnsConnector";
62 
63     private static final boolean DBG = true;
64     private static final long CLEANUP_DELAY_MS = 10000;
65 
66     private final Context mContext;
67     private final NsdSettings mNsdSettings;
68     private final NsdStateMachine mNsdStateMachine;
69     private final DaemonConnection mDaemon;
70     private final NativeCallbackReceiver mDaemonCallback;
71 
72     /**
73      * Clients receiving asynchronous messages
74      */
75     private final HashMap<Messenger, ClientInfo> mClients = new HashMap<>();
76 
77     /* A map from unique id to client info */
78     private final SparseArray<ClientInfo> mIdToClientInfoMap= new SparseArray<>();
79 
80     private final AsyncChannel mReplyChannel = new AsyncChannel();
81     private final long mCleanupDelayMs;
82 
83     private static final int INVALID_ID = 0;
84     private int mUniqueId = 1;
85     // The count of the connected legacy clients.
86     private int mLegacyClientCount = 0;
87 
88     private class NsdStateMachine extends StateMachine {
89 
90         private final DefaultState mDefaultState = new DefaultState();
91         private final DisabledState mDisabledState = new DisabledState();
92         private final EnabledState mEnabledState = new EnabledState();
93 
94         @Override
getWhatToString(int what)95         protected String getWhatToString(int what) {
96             return NsdManager.nameOf(what);
97         }
98 
maybeStartDaemon()99         private void maybeStartDaemon() {
100             mDaemon.maybeStart();
101             maybeScheduleStop();
102         }
103 
isAnyRequestActive()104         private boolean isAnyRequestActive() {
105             return mIdToClientInfoMap.size() != 0;
106         }
107 
scheduleStop()108         private void scheduleStop() {
109             sendMessageDelayed(NsdManager.DAEMON_CLEANUP, mCleanupDelayMs);
110         }
maybeScheduleStop()111         private void maybeScheduleStop() {
112             // The native daemon should stay alive and can't be cleanup
113             // if any legacy client connected.
114             if (!isAnyRequestActive() && mLegacyClientCount == 0) {
115                 scheduleStop();
116             }
117         }
118 
cancelStop()119         private void cancelStop() {
120             this.removeMessages(NsdManager.DAEMON_CLEANUP);
121         }
122 
123         /**
124          * Observes the NSD on/off setting, and takes action when changed.
125          */
registerForNsdSetting()126         private void registerForNsdSetting() {
127             final ContentObserver contentObserver = new ContentObserver(this.getHandler()) {
128                 @Override
129                 public void onChange(boolean selfChange) {
130                     notifyEnabled(isNsdEnabled());
131                 }
132             };
133 
134             final Uri uri = Settings.Global.getUriFor(Settings.Global.NSD_ON);
135             mNsdSettings.registerContentObserver(uri, contentObserver);
136         }
137 
NsdStateMachine(String name, Handler handler)138         NsdStateMachine(String name, Handler handler) {
139             super(name, handler);
140             addState(mDefaultState);
141                 addState(mDisabledState, mDefaultState);
142                 addState(mEnabledState, mDefaultState);
143             State initialState = isNsdEnabled() ? mEnabledState : mDisabledState;
144             setInitialState(initialState);
145             setLogRecSize(25);
146             registerForNsdSetting();
147         }
148 
149         class DefaultState extends State {
150             @Override
processMessage(Message msg)151             public boolean processMessage(Message msg) {
152                 ClientInfo cInfo = null;
153                 switch (msg.what) {
154                     case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
155                         if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
156                             AsyncChannel c = (AsyncChannel) msg.obj;
157                             if (DBG) Slog.d(TAG, "New client listening to asynchronous messages");
158                             c.sendMessage(AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED);
159                             cInfo = new ClientInfo(c, msg.replyTo);
160                             mClients.put(msg.replyTo, cInfo);
161                         } else {
162                             Slog.e(TAG, "Client connection failure, error=" + msg.arg1);
163                         }
164                         break;
165                     case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
166                         switch (msg.arg1) {
167                             case AsyncChannel.STATUS_SEND_UNSUCCESSFUL:
168                                 Slog.e(TAG, "Send failed, client connection lost");
169                                 break;
170                             case AsyncChannel.STATUS_REMOTE_DISCONNECTION:
171                                 if (DBG) Slog.d(TAG, "Client disconnected");
172                                 break;
173                             default:
174                                 if (DBG) Slog.d(TAG, "Client connection lost with reason: " + msg.arg1);
175                                 break;
176                         }
177 
178                         cInfo = mClients.get(msg.replyTo);
179                         if (cInfo != null) {
180                             cInfo.expungeAllRequests();
181                             mClients.remove(msg.replyTo);
182                             if (cInfo.isLegacy()) {
183                                 mLegacyClientCount -= 1;
184                             }
185                         }
186                         maybeScheduleStop();
187                         break;
188                     case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION:
189                         AsyncChannel ac = new AsyncChannel();
190                         ac.connect(mContext, getHandler(), msg.replyTo);
191                         break;
192                     case NsdManager.DISCOVER_SERVICES:
193                         replyToMessage(msg, NsdManager.DISCOVER_SERVICES_FAILED,
194                                 NsdManager.FAILURE_INTERNAL_ERROR);
195                        break;
196                     case NsdManager.STOP_DISCOVERY:
197                        replyToMessage(msg, NsdManager.STOP_DISCOVERY_FAILED,
198                                NsdManager.FAILURE_INTERNAL_ERROR);
199                         break;
200                     case NsdManager.REGISTER_SERVICE:
201                         replyToMessage(msg, NsdManager.REGISTER_SERVICE_FAILED,
202                                 NsdManager.FAILURE_INTERNAL_ERROR);
203                         break;
204                     case NsdManager.UNREGISTER_SERVICE:
205                         replyToMessage(msg, NsdManager.UNREGISTER_SERVICE_FAILED,
206                                 NsdManager.FAILURE_INTERNAL_ERROR);
207                         break;
208                     case NsdManager.RESOLVE_SERVICE:
209                         replyToMessage(msg, NsdManager.RESOLVE_SERVICE_FAILED,
210                                 NsdManager.FAILURE_INTERNAL_ERROR);
211                         break;
212                     case NsdManager.DAEMON_CLEANUP:
213                         mDaemon.maybeStop();
214                         break;
215                     // This event should be only sent by the legacy (target SDK < S) clients.
216                     // Mark the sending client as legacy.
217                     case NsdManager.DAEMON_STARTUP:
218                         cInfo = mClients.get(msg.replyTo);
219                         if (cInfo != null) {
220                             cancelStop();
221                             cInfo.setLegacy();
222                             mLegacyClientCount += 1;
223                             maybeStartDaemon();
224                         }
225                         break;
226                     case NsdManager.NATIVE_DAEMON_EVENT:
227                     default:
228                         Slog.e(TAG, "Unhandled " + msg);
229                         return NOT_HANDLED;
230                 }
231                 return HANDLED;
232             }
233         }
234 
235         class DisabledState extends State {
236             @Override
enter()237             public void enter() {
238                 sendNsdStateChangeBroadcast(false);
239             }
240 
241             @Override
processMessage(Message msg)242             public boolean processMessage(Message msg) {
243                 switch (msg.what) {
244                     case NsdManager.ENABLE:
245                         transitionTo(mEnabledState);
246                         break;
247                     default:
248                         return NOT_HANDLED;
249                 }
250                 return HANDLED;
251             }
252         }
253 
254         class EnabledState extends State {
255             @Override
enter()256             public void enter() {
257                 sendNsdStateChangeBroadcast(true);
258             }
259 
260             @Override
exit()261             public void exit() {
262                 // TODO: it is incorrect to stop the daemon without expunging all requests
263                 // and sending error callbacks to clients.
264                 scheduleStop();
265             }
266 
requestLimitReached(ClientInfo clientInfo)267             private boolean requestLimitReached(ClientInfo clientInfo) {
268                 if (clientInfo.mClientIds.size() >= ClientInfo.MAX_LIMIT) {
269                     if (DBG) Slog.d(TAG, "Exceeded max outstanding requests " + clientInfo);
270                     return true;
271                 }
272                 return false;
273             }
274 
storeRequestMap(int clientId, int globalId, ClientInfo clientInfo, int what)275             private void storeRequestMap(int clientId, int globalId, ClientInfo clientInfo, int what) {
276                 clientInfo.mClientIds.put(clientId, globalId);
277                 clientInfo.mClientRequests.put(clientId, what);
278                 mIdToClientInfoMap.put(globalId, clientInfo);
279                 // Remove the cleanup event because here comes a new request.
280                 cancelStop();
281             }
282 
removeRequestMap(int clientId, int globalId, ClientInfo clientInfo)283             private void removeRequestMap(int clientId, int globalId, ClientInfo clientInfo) {
284                 clientInfo.mClientIds.delete(clientId);
285                 clientInfo.mClientRequests.delete(clientId);
286                 mIdToClientInfoMap.remove(globalId);
287                 maybeScheduleStop();
288             }
289 
290             @Override
processMessage(Message msg)291             public boolean processMessage(Message msg) {
292                 ClientInfo clientInfo;
293                 NsdServiceInfo servInfo;
294                 int id;
295                 switch (msg.what) {
296                     case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
297                         return NOT_HANDLED;
298                     case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
299                         return NOT_HANDLED;
300                     case NsdManager.DISABLE:
301                         //TODO: cleanup clients
302                         transitionTo(mDisabledState);
303                         break;
304                     case NsdManager.DISCOVER_SERVICES:
305                         if (DBG) Slog.d(TAG, "Discover services");
306                         servInfo = (NsdServiceInfo) msg.obj;
307                         clientInfo = mClients.get(msg.replyTo);
308 
309                         if (requestLimitReached(clientInfo)) {
310                             replyToMessage(msg, NsdManager.DISCOVER_SERVICES_FAILED,
311                                     NsdManager.FAILURE_MAX_LIMIT);
312                             break;
313                         }
314 
315                         maybeStartDaemon();
316                         id = getUniqueId();
317                         if (discoverServices(id, servInfo.getServiceType())) {
318                             if (DBG) {
319                                 Slog.d(TAG, "Discover " + msg.arg2 + " " + id +
320                                         servInfo.getServiceType());
321                             }
322                             storeRequestMap(msg.arg2, id, clientInfo, msg.what);
323                             replyToMessage(msg, NsdManager.DISCOVER_SERVICES_STARTED, servInfo);
324                         } else {
325                             stopServiceDiscovery(id);
326                             replyToMessage(msg, NsdManager.DISCOVER_SERVICES_FAILED,
327                                     NsdManager.FAILURE_INTERNAL_ERROR);
328                         }
329                         break;
330                     case NsdManager.STOP_DISCOVERY:
331                         if (DBG) Slog.d(TAG, "Stop service discovery");
332                         clientInfo = mClients.get(msg.replyTo);
333 
334                         try {
335                             id = clientInfo.mClientIds.get(msg.arg2);
336                         } catch (NullPointerException e) {
337                             replyToMessage(msg, NsdManager.STOP_DISCOVERY_FAILED,
338                                     NsdManager.FAILURE_INTERNAL_ERROR);
339                             break;
340                         }
341                         removeRequestMap(msg.arg2, id, clientInfo);
342                         if (stopServiceDiscovery(id)) {
343                             replyToMessage(msg, NsdManager.STOP_DISCOVERY_SUCCEEDED);
344                         } else {
345                             replyToMessage(msg, NsdManager.STOP_DISCOVERY_FAILED,
346                                     NsdManager.FAILURE_INTERNAL_ERROR);
347                         }
348                         break;
349                     case NsdManager.REGISTER_SERVICE:
350                         if (DBG) Slog.d(TAG, "Register service");
351                         clientInfo = mClients.get(msg.replyTo);
352                         if (requestLimitReached(clientInfo)) {
353                             replyToMessage(msg, NsdManager.REGISTER_SERVICE_FAILED,
354                                     NsdManager.FAILURE_MAX_LIMIT);
355                             break;
356                         }
357 
358                         maybeStartDaemon();
359                         id = getUniqueId();
360                         if (registerService(id, (NsdServiceInfo) msg.obj)) {
361                             if (DBG) Slog.d(TAG, "Register " + msg.arg2 + " " + id);
362                             storeRequestMap(msg.arg2, id, clientInfo, msg.what);
363                             // Return success after mDns reports success
364                         } else {
365                             unregisterService(id);
366                             replyToMessage(msg, NsdManager.REGISTER_SERVICE_FAILED,
367                                     NsdManager.FAILURE_INTERNAL_ERROR);
368                         }
369                         break;
370                     case NsdManager.UNREGISTER_SERVICE:
371                         if (DBG) Slog.d(TAG, "unregister service");
372                         clientInfo = mClients.get(msg.replyTo);
373                         try {
374                             id = clientInfo.mClientIds.get(msg.arg2);
375                         } catch (NullPointerException e) {
376                             replyToMessage(msg, NsdManager.UNREGISTER_SERVICE_FAILED,
377                                     NsdManager.FAILURE_INTERNAL_ERROR);
378                             break;
379                         }
380                         removeRequestMap(msg.arg2, id, clientInfo);
381                         if (unregisterService(id)) {
382                             replyToMessage(msg, NsdManager.UNREGISTER_SERVICE_SUCCEEDED);
383                         } else {
384                             replyToMessage(msg, NsdManager.UNREGISTER_SERVICE_FAILED,
385                                     NsdManager.FAILURE_INTERNAL_ERROR);
386                         }
387                         break;
388                     case NsdManager.RESOLVE_SERVICE:
389                         if (DBG) Slog.d(TAG, "Resolve service");
390                         servInfo = (NsdServiceInfo) msg.obj;
391                         clientInfo = mClients.get(msg.replyTo);
392 
393 
394                         if (clientInfo.mResolvedService != null) {
395                             replyToMessage(msg, NsdManager.RESOLVE_SERVICE_FAILED,
396                                     NsdManager.FAILURE_ALREADY_ACTIVE);
397                             break;
398                         }
399 
400                         maybeStartDaemon();
401                         id = getUniqueId();
402                         if (resolveService(id, servInfo)) {
403                             clientInfo.mResolvedService = new NsdServiceInfo();
404                             storeRequestMap(msg.arg2, id, clientInfo, msg.what);
405                         } else {
406                             replyToMessage(msg, NsdManager.RESOLVE_SERVICE_FAILED,
407                                     NsdManager.FAILURE_INTERNAL_ERROR);
408                         }
409                         break;
410                     case NsdManager.NATIVE_DAEMON_EVENT:
411                         NativeEvent event = (NativeEvent) msg.obj;
412                         if (!handleNativeEvent(event.code, event.raw, event.cooked)) {
413                             return NOT_HANDLED;
414                         }
415                         break;
416                     default:
417                         return NOT_HANDLED;
418                 }
419                 return HANDLED;
420             }
421 
handleNativeEvent(int code, String raw, String[] cooked)422             private boolean handleNativeEvent(int code, String raw, String[] cooked) {
423                 NsdServiceInfo servInfo;
424                 int id = Integer.parseInt(cooked[1]);
425                 ClientInfo clientInfo = mIdToClientInfoMap.get(id);
426                 if (clientInfo == null) {
427                     String name = NativeResponseCode.nameOf(code);
428                     Slog.e(TAG, String.format("id %d for %s has no client mapping", id, name));
429                     return false;
430                 }
431 
432                 /* This goes in response as msg.arg2 */
433                 int clientId = clientInfo.getClientId(id);
434                 if (clientId < 0) {
435                     // This can happen because of race conditions. For example,
436                     // SERVICE_FOUND may race with STOP_SERVICE_DISCOVERY,
437                     // and we may get in this situation.
438                     String name = NativeResponseCode.nameOf(code);
439                     Slog.d(TAG, String.format(
440                             "Notification %s for listener id %d that is no longer active",
441                             name, id));
442                     return false;
443                 }
444                 if (DBG) {
445                     String name = NativeResponseCode.nameOf(code);
446                     Slog.d(TAG, String.format("Native daemon message %s: %s", name, raw));
447                 }
448                 switch (code) {
449                     case NativeResponseCode.SERVICE_FOUND:
450                         /* NNN uniqueId serviceName regType domain */
451                         servInfo = new NsdServiceInfo(cooked[2], cooked[3]);
452                         clientInfo.mChannel.sendMessage(NsdManager.SERVICE_FOUND, 0,
453                                 clientId, servInfo);
454                         break;
455                     case NativeResponseCode.SERVICE_LOST:
456                         /* NNN uniqueId serviceName regType domain */
457                         servInfo = new NsdServiceInfo(cooked[2], cooked[3]);
458                         clientInfo.mChannel.sendMessage(NsdManager.SERVICE_LOST, 0,
459                                 clientId, servInfo);
460                         break;
461                     case NativeResponseCode.SERVICE_DISCOVERY_FAILED:
462                         /* NNN uniqueId errorCode */
463                         clientInfo.mChannel.sendMessage(NsdManager.DISCOVER_SERVICES_FAILED,
464                                 NsdManager.FAILURE_INTERNAL_ERROR, clientId);
465                         break;
466                     case NativeResponseCode.SERVICE_REGISTERED:
467                         /* NNN regId serviceName regType */
468                         servInfo = new NsdServiceInfo(cooked[2], null);
469                         clientInfo.mChannel.sendMessage(NsdManager.REGISTER_SERVICE_SUCCEEDED,
470                                 id, clientId, servInfo);
471                         break;
472                     case NativeResponseCode.SERVICE_REGISTRATION_FAILED:
473                         /* NNN regId errorCode */
474                         clientInfo.mChannel.sendMessage(NsdManager.REGISTER_SERVICE_FAILED,
475                                NsdManager.FAILURE_INTERNAL_ERROR, clientId);
476                         break;
477                     case NativeResponseCode.SERVICE_UPDATED:
478                         /* NNN regId */
479                         break;
480                     case NativeResponseCode.SERVICE_UPDATE_FAILED:
481                         /* NNN regId errorCode */
482                         break;
483                     case NativeResponseCode.SERVICE_RESOLVED:
484                         /* NNN resolveId fullName hostName port txtlen txtdata */
485                         int index = 0;
486                         while (index < cooked[2].length() && cooked[2].charAt(index) != '.') {
487                             if (cooked[2].charAt(index) == '\\') {
488                                 ++index;
489                             }
490                             ++index;
491                         }
492                         if (index >= cooked[2].length()) {
493                             Slog.e(TAG, "Invalid service found " + raw);
494                             break;
495                         }
496                         String name = cooked[2].substring(0, index);
497                         String rest = cooked[2].substring(index);
498                         String type = rest.replace(".local.", "");
499 
500                         name = unescape(name);
501 
502                         clientInfo.mResolvedService.setServiceName(name);
503                         clientInfo.mResolvedService.setServiceType(type);
504                         clientInfo.mResolvedService.setPort(Integer.parseInt(cooked[4]));
505                         clientInfo.mResolvedService.setTxtRecords(cooked[6]);
506 
507                         stopResolveService(id);
508                         removeRequestMap(clientId, id, clientInfo);
509 
510                         int id2 = getUniqueId();
511                         if (getAddrInfo(id2, cooked[3])) {
512                             storeRequestMap(clientId, id2, clientInfo, NsdManager.RESOLVE_SERVICE);
513                         } else {
514                             clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_FAILED,
515                                     NsdManager.FAILURE_INTERNAL_ERROR, clientId);
516                             clientInfo.mResolvedService = null;
517                         }
518                         break;
519                     case NativeResponseCode.SERVICE_RESOLUTION_FAILED:
520                         /* NNN resolveId errorCode */
521                         stopResolveService(id);
522                         removeRequestMap(clientId, id, clientInfo);
523                         clientInfo.mResolvedService = null;
524                         clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_FAILED,
525                                 NsdManager.FAILURE_INTERNAL_ERROR, clientId);
526                         break;
527                     case NativeResponseCode.SERVICE_GET_ADDR_FAILED:
528                         /* NNN resolveId errorCode */
529                         stopGetAddrInfo(id);
530                         removeRequestMap(clientId, id, clientInfo);
531                         clientInfo.mResolvedService = null;
532                         clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_FAILED,
533                                 NsdManager.FAILURE_INTERNAL_ERROR, clientId);
534                         break;
535                     case NativeResponseCode.SERVICE_GET_ADDR_SUCCESS:
536                         /* NNN resolveId hostname ttl addr */
537                         try {
538                             clientInfo.mResolvedService.setHost(InetAddress.getByName(cooked[4]));
539                             clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_SUCCEEDED,
540                                    0, clientId, clientInfo.mResolvedService);
541                         } catch (java.net.UnknownHostException e) {
542                             clientInfo.mChannel.sendMessage(NsdManager.RESOLVE_SERVICE_FAILED,
543                                     NsdManager.FAILURE_INTERNAL_ERROR, clientId);
544                         }
545                         stopGetAddrInfo(id);
546                         removeRequestMap(clientId, id, clientInfo);
547                         clientInfo.mResolvedService = null;
548                         break;
549                     default:
550                         return false;
551                 }
552                 return true;
553             }
554        }
555     }
556 
unescape(String s)557     private String unescape(String s) {
558         StringBuilder sb = new StringBuilder(s.length());
559         for (int i = 0; i < s.length(); ++i) {
560             char c = s.charAt(i);
561             if (c == '\\') {
562                 if (++i >= s.length()) {
563                     Slog.e(TAG, "Unexpected end of escape sequence in: " + s);
564                     break;
565                 }
566                 c = s.charAt(i);
567                 if (c != '.' && c != '\\') {
568                     if (i + 2 >= s.length()) {
569                         Slog.e(TAG, "Unexpected end of escape sequence in: " + s);
570                         break;
571                     }
572                     c = (char) ((c-'0') * 100 + (s.charAt(i+1)-'0') * 10 + (s.charAt(i+2)-'0'));
573                     i += 2;
574                 }
575             }
576             sb.append(c);
577         }
578         return sb.toString();
579     }
580 
581     @VisibleForTesting
NsdService(Context ctx, NsdSettings settings, Handler handler, DaemonConnectionSupplier fn, long cleanupDelayMs)582     NsdService(Context ctx, NsdSettings settings, Handler handler,
583             DaemonConnectionSupplier fn, long cleanupDelayMs) {
584         mCleanupDelayMs = cleanupDelayMs;
585         mContext = ctx;
586         mNsdSettings = settings;
587         mNsdStateMachine = new NsdStateMachine(TAG, handler);
588         mNsdStateMachine.start();
589         mDaemonCallback = new NativeCallbackReceiver();
590         mDaemon = fn.get(mDaemonCallback);
591     }
592 
create(Context context)593     public static NsdService create(Context context) throws InterruptedException {
594         NsdSettings settings = NsdSettings.makeDefault(context);
595         HandlerThread thread = new HandlerThread(TAG);
596         thread.start();
597         Handler handler = new Handler(thread.getLooper());
598         NsdService service = new NsdService(context, settings, handler,
599                 DaemonConnection::new, CLEANUP_DELAY_MS);
600         service.mDaemonCallback.awaitConnection();
601         return service;
602     }
603 
getMessenger()604     public Messenger getMessenger() {
605         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INTERNET, "NsdService");
606         return new Messenger(mNsdStateMachine.getHandler());
607     }
608 
setEnabled(boolean isEnabled)609     public void setEnabled(boolean isEnabled) {
610         NetworkStack.checkNetworkStackPermission(mContext);
611         mNsdSettings.putEnabledStatus(isEnabled);
612         notifyEnabled(isEnabled);
613     }
614 
notifyEnabled(boolean isEnabled)615     private void notifyEnabled(boolean isEnabled) {
616         mNsdStateMachine.sendMessage(isEnabled ? NsdManager.ENABLE : NsdManager.DISABLE);
617     }
618 
sendNsdStateChangeBroadcast(boolean isEnabled)619     private void sendNsdStateChangeBroadcast(boolean isEnabled) {
620         final Intent intent = new Intent(NsdManager.ACTION_NSD_STATE_CHANGED);
621         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
622         int nsdState = isEnabled ? NsdManager.NSD_STATE_ENABLED : NsdManager.NSD_STATE_DISABLED;
623         intent.putExtra(NsdManager.EXTRA_NSD_STATE, nsdState);
624         mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
625     }
626 
isNsdEnabled()627     private boolean isNsdEnabled() {
628         boolean ret = mNsdSettings.isEnabled();
629         if (DBG) {
630             Slog.d(TAG, "Network service discovery is " + (ret ? "enabled" : "disabled"));
631         }
632         return ret;
633     }
634 
getUniqueId()635     private int getUniqueId() {
636         if (++mUniqueId == INVALID_ID) return ++mUniqueId;
637         return mUniqueId;
638     }
639 
640     /* These should be in sync with system/netd/server/ResponseCode.h */
641     static final class NativeResponseCode {
642         public static final int SERVICE_DISCOVERY_FAILED    =   602;
643         public static final int SERVICE_FOUND               =   603;
644         public static final int SERVICE_LOST                =   604;
645 
646         public static final int SERVICE_REGISTRATION_FAILED =   605;
647         public static final int SERVICE_REGISTERED          =   606;
648 
649         public static final int SERVICE_RESOLUTION_FAILED   =   607;
650         public static final int SERVICE_RESOLVED            =   608;
651 
652         public static final int SERVICE_UPDATED             =   609;
653         public static final int SERVICE_UPDATE_FAILED       =   610;
654 
655         public static final int SERVICE_GET_ADDR_FAILED     =   611;
656         public static final int SERVICE_GET_ADDR_SUCCESS    =   612;
657 
658         private static final SparseArray<String> CODE_NAMES = new SparseArray<>();
659         static {
CODE_NAMES.put(SERVICE_DISCOVERY_FAILED, R)660             CODE_NAMES.put(SERVICE_DISCOVERY_FAILED, "SERVICE_DISCOVERY_FAILED");
CODE_NAMES.put(SERVICE_FOUND, R)661             CODE_NAMES.put(SERVICE_FOUND, "SERVICE_FOUND");
CODE_NAMES.put(SERVICE_LOST, R)662             CODE_NAMES.put(SERVICE_LOST, "SERVICE_LOST");
CODE_NAMES.put(SERVICE_REGISTRATION_FAILED, R)663             CODE_NAMES.put(SERVICE_REGISTRATION_FAILED, "SERVICE_REGISTRATION_FAILED");
CODE_NAMES.put(SERVICE_REGISTERED, R)664             CODE_NAMES.put(SERVICE_REGISTERED, "SERVICE_REGISTERED");
CODE_NAMES.put(SERVICE_RESOLUTION_FAILED, R)665             CODE_NAMES.put(SERVICE_RESOLUTION_FAILED, "SERVICE_RESOLUTION_FAILED");
CODE_NAMES.put(SERVICE_RESOLVED, R)666             CODE_NAMES.put(SERVICE_RESOLVED, "SERVICE_RESOLVED");
CODE_NAMES.put(SERVICE_UPDATED, R)667             CODE_NAMES.put(SERVICE_UPDATED, "SERVICE_UPDATED");
CODE_NAMES.put(SERVICE_UPDATE_FAILED, R)668             CODE_NAMES.put(SERVICE_UPDATE_FAILED, "SERVICE_UPDATE_FAILED");
CODE_NAMES.put(SERVICE_GET_ADDR_FAILED, R)669             CODE_NAMES.put(SERVICE_GET_ADDR_FAILED, "SERVICE_GET_ADDR_FAILED");
CODE_NAMES.put(SERVICE_GET_ADDR_SUCCESS, R)670             CODE_NAMES.put(SERVICE_GET_ADDR_SUCCESS, "SERVICE_GET_ADDR_SUCCESS");
671         }
672 
nameOf(int code)673         static String nameOf(int code) {
674             String name = CODE_NAMES.get(code);
675             if (name == null) {
676                 return Integer.toString(code);
677             }
678             return name;
679         }
680     }
681 
682     private class NativeEvent {
683         final int code;
684         final String raw;
685         final String[] cooked;
686 
NativeEvent(int code, String raw, String[] cooked)687         NativeEvent(int code, String raw, String[] cooked) {
688             this.code = code;
689             this.raw = raw;
690             this.cooked = cooked;
691         }
692     }
693 
694     class NativeCallbackReceiver implements INativeDaemonConnectorCallbacks {
695         private final CountDownLatch connected = new CountDownLatch(1);
696 
awaitConnection()697         public void awaitConnection() throws InterruptedException {
698             connected.await();
699         }
700 
701         @Override
onDaemonConnected()702         public void onDaemonConnected() {
703             connected.countDown();
704         }
705 
706         @Override
onCheckHoldWakeLock(int code)707         public boolean onCheckHoldWakeLock(int code) {
708             return false;
709         }
710 
711         @Override
onEvent(int code, String raw, String[] cooked)712         public boolean onEvent(int code, String raw, String[] cooked) {
713             // TODO: NDC translates a message to a callback, we could enhance NDC to
714             // directly interact with a state machine through messages
715             NativeEvent event = new NativeEvent(code, raw, cooked);
716             mNsdStateMachine.sendMessage(NsdManager.NATIVE_DAEMON_EVENT, event);
717             return true;
718         }
719     }
720 
721     interface DaemonConnectionSupplier {
get(NativeCallbackReceiver callback)722         DaemonConnection get(NativeCallbackReceiver callback);
723     }
724 
725     @VisibleForTesting
726     public static class DaemonConnection {
727         final NativeDaemonConnector mNativeConnector;
728         boolean mIsStarted = false;
729 
DaemonConnection(NativeCallbackReceiver callback)730         DaemonConnection(NativeCallbackReceiver callback) {
731             mNativeConnector = new NativeDaemonConnector(callback, "mdns", 10, MDNS_TAG, 25, null);
732             new Thread(mNativeConnector, MDNS_TAG).start();
733         }
734 
735         /**
736          * Executes the specified cmd on the daemon.
737          */
execute(Object... args)738         public boolean execute(Object... args) {
739             if (DBG) {
740                 Slog.d(TAG, "mdnssd " + Arrays.toString(args));
741             }
742             try {
743                 mNativeConnector.execute("mdnssd", args);
744             } catch (NativeDaemonConnectorException e) {
745                 Slog.e(TAG, "Failed to execute mdnssd " + Arrays.toString(args), e);
746                 return false;
747             }
748             return true;
749         }
750 
751         /**
752          * Starts the daemon if it is not already started.
753          */
maybeStart()754         public void maybeStart() {
755             if (mIsStarted) {
756                 return;
757             }
758             execute("start-service");
759             mIsStarted = true;
760         }
761 
762         /**
763          * Stops the daemon if it is started.
764          */
maybeStop()765         public void maybeStop() {
766             if (!mIsStarted) {
767                 return;
768             }
769             execute("stop-service");
770             mIsStarted = false;
771         }
772     }
773 
registerService(int regId, NsdServiceInfo service)774     private boolean registerService(int regId, NsdServiceInfo service) {
775         if (DBG) {
776             Slog.d(TAG, "registerService: " + regId + " " + service);
777         }
778         String name = service.getServiceName();
779         String type = service.getServiceType();
780         int port = service.getPort();
781         byte[] textRecord = service.getTxtRecord();
782         String record = Base64.encodeToString(textRecord, Base64.DEFAULT).replace("\n", "");
783         return mDaemon.execute("register", regId, name, type, port, record);
784     }
785 
unregisterService(int regId)786     private boolean unregisterService(int regId) {
787         return mDaemon.execute("stop-register", regId);
788     }
789 
updateService(int regId, DnsSdTxtRecord t)790     private boolean updateService(int regId, DnsSdTxtRecord t) {
791         if (t == null) {
792             return false;
793         }
794         return mDaemon.execute("update", regId, t.size(), t.getRawData());
795     }
796 
discoverServices(int discoveryId, String serviceType)797     private boolean discoverServices(int discoveryId, String serviceType) {
798         return mDaemon.execute("discover", discoveryId, serviceType);
799     }
800 
stopServiceDiscovery(int discoveryId)801     private boolean stopServiceDiscovery(int discoveryId) {
802         return mDaemon.execute("stop-discover", discoveryId);
803     }
804 
resolveService(int resolveId, NsdServiceInfo service)805     private boolean resolveService(int resolveId, NsdServiceInfo service) {
806         String name = service.getServiceName();
807         String type = service.getServiceType();
808         return mDaemon.execute("resolve", resolveId, name, type, "local.");
809     }
810 
stopResolveService(int resolveId)811     private boolean stopResolveService(int resolveId) {
812         return mDaemon.execute("stop-resolve", resolveId);
813     }
814 
getAddrInfo(int resolveId, String hostname)815     private boolean getAddrInfo(int resolveId, String hostname) {
816         return mDaemon.execute("getaddrinfo", resolveId, hostname);
817     }
818 
stopGetAddrInfo(int resolveId)819     private boolean stopGetAddrInfo(int resolveId) {
820         return mDaemon.execute("stop-getaddrinfo", resolveId);
821     }
822 
823     @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)824     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
825         if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
826 
827         for (ClientInfo client : mClients.values()) {
828             pw.println("Client Info");
829             pw.println(client);
830         }
831 
832         mNsdStateMachine.dump(fd, pw, args);
833     }
834 
835     /* arg2 on the source message has an id that needs to be retained in replies
836      * see NsdManager for details */
obtainMessage(Message srcMsg)837     private Message obtainMessage(Message srcMsg) {
838         Message msg = Message.obtain();
839         msg.arg2 = srcMsg.arg2;
840         return msg;
841     }
842 
replyToMessage(Message msg, int what)843     private void replyToMessage(Message msg, int what) {
844         if (msg.replyTo == null) return;
845         Message dstMsg = obtainMessage(msg);
846         dstMsg.what = what;
847         mReplyChannel.replyToMessage(msg, dstMsg);
848     }
849 
replyToMessage(Message msg, int what, int arg1)850     private void replyToMessage(Message msg, int what, int arg1) {
851         if (msg.replyTo == null) return;
852         Message dstMsg = obtainMessage(msg);
853         dstMsg.what = what;
854         dstMsg.arg1 = arg1;
855         mReplyChannel.replyToMessage(msg, dstMsg);
856     }
857 
replyToMessage(Message msg, int what, Object obj)858     private void replyToMessage(Message msg, int what, Object obj) {
859         if (msg.replyTo == null) return;
860         Message dstMsg = obtainMessage(msg);
861         dstMsg.what = what;
862         dstMsg.obj = obj;
863         mReplyChannel.replyToMessage(msg, dstMsg);
864     }
865 
866     /* Information tracked per client */
867     private class ClientInfo {
868 
869         private static final int MAX_LIMIT = 10;
870         private final AsyncChannel mChannel;
871         private final Messenger mMessenger;
872         /* Remembers a resolved service until getaddrinfo completes */
873         private NsdServiceInfo mResolvedService;
874 
875         /* A map from client id to unique id sent to mDns */
876         private final SparseIntArray mClientIds = new SparseIntArray();
877 
878         /* A map from client id to the type of the request we had received */
879         private final SparseIntArray mClientRequests = new SparseIntArray();
880 
881         // The target SDK of this client < Build.VERSION_CODES.S
882         private boolean mIsLegacy = false;
883 
ClientInfo(AsyncChannel c, Messenger m)884         private ClientInfo(AsyncChannel c, Messenger m) {
885             mChannel = c;
886             mMessenger = m;
887             if (DBG) Slog.d(TAG, "New client, channel: " + c + " messenger: " + m);
888         }
889 
890         @Override
toString()891         public String toString() {
892             StringBuilder sb = new StringBuilder();
893             sb.append("mChannel ").append(mChannel).append("\n");
894             sb.append("mMessenger ").append(mMessenger).append("\n");
895             sb.append("mResolvedService ").append(mResolvedService).append("\n");
896             sb.append("mIsLegacy ").append(mIsLegacy).append("\n");
897             for(int i = 0; i< mClientIds.size(); i++) {
898                 int clientID = mClientIds.keyAt(i);
899                 sb.append("clientId ").append(clientID).
900                     append(" mDnsId ").append(mClientIds.valueAt(i)).
901                     append(" type ").append(mClientRequests.get(clientID)).append("\n");
902             }
903             return sb.toString();
904         }
905 
isLegacy()906         private boolean isLegacy() {
907             return mIsLegacy;
908         }
909 
setLegacy()910         private void setLegacy() {
911             mIsLegacy = true;
912         }
913 
914         // Remove any pending requests from the global map when we get rid of a client,
915         // and send cancellations to the daemon.
expungeAllRequests()916         private void expungeAllRequests() {
917             int globalId, clientId, i;
918             // TODO: to keep handler responsive, do not clean all requests for that client at once.
919             for (i = 0; i < mClientIds.size(); i++) {
920                 clientId = mClientIds.keyAt(i);
921                 globalId = mClientIds.valueAt(i);
922                 mIdToClientInfoMap.remove(globalId);
923                 if (DBG) Slog.d(TAG, "Terminating client-ID " + clientId +
924                         " global-ID " + globalId + " type " + mClientRequests.get(clientId));
925                 switch (mClientRequests.get(clientId)) {
926                     case NsdManager.DISCOVER_SERVICES:
927                         stopServiceDiscovery(globalId);
928                         break;
929                     case NsdManager.RESOLVE_SERVICE:
930                         stopResolveService(globalId);
931                         break;
932                     case NsdManager.REGISTER_SERVICE:
933                         unregisterService(globalId);
934                         break;
935                     default:
936                         break;
937                 }
938             }
939             mClientIds.clear();
940             mClientRequests.clear();
941         }
942 
943         // mClientIds is a sparse array of listener id -> mDnsClient id.  For a given mDnsClient id,
944         // return the corresponding listener id.  mDnsClient id is also called a global id.
getClientId(final int globalId)945         private int getClientId(final int globalId) {
946             int idx = mClientIds.indexOfValue(globalId);
947             if (idx < 0) {
948                 return idx;
949             }
950             return mClientIds.keyAt(idx);
951         }
952     }
953 
954     /**
955      * Interface which encapsulates dependencies of NsdService that are hard to mock, hard to
956      * override, or have side effects on global state in unit tests.
957      */
958     @VisibleForTesting
959     public interface NsdSettings {
isEnabled()960         boolean isEnabled();
putEnabledStatus(boolean isEnabled)961         void putEnabledStatus(boolean isEnabled);
registerContentObserver(Uri uri, ContentObserver observer)962         void registerContentObserver(Uri uri, ContentObserver observer);
963 
makeDefault(Context context)964         static NsdSettings makeDefault(Context context) {
965             final ContentResolver resolver = context.getContentResolver();
966             return new NsdSettings() {
967                 @Override
968                 public boolean isEnabled() {
969                     return Settings.Global.getInt(resolver, Settings.Global.NSD_ON, 1) == 1;
970                 }
971 
972                 @Override
973                 public void putEnabledStatus(boolean isEnabled) {
974                     Settings.Global.putInt(resolver, Settings.Global.NSD_ON, isEnabled ? 1 : 0);
975                 }
976 
977                 @Override
978                 public void registerContentObserver(Uri uri, ContentObserver observer) {
979                     resolver.registerContentObserver(uri, false, observer);
980                 }
981             };
982         }
983     }
984 }
985