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