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