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