1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.connectivity.mdns; 18 19 import static com.android.server.connectivity.mdns.MdnsConstants.NO_PACKET; 20 import static com.android.server.connectivity.mdns.MdnsInterfaceAdvertiser.CONFLICT_HOST; 21 import static com.android.server.connectivity.mdns.MdnsInterfaceAdvertiser.CONFLICT_SERVICE; 22 import static com.android.server.connectivity.mdns.MdnsRecord.MAX_LABEL_LENGTH; 23 24 import android.annotation.NonNull; 25 import android.annotation.Nullable; 26 import android.annotation.RequiresApi; 27 import android.content.Context; 28 import android.net.LinkAddress; 29 import android.net.Network; 30 import android.net.nsd.NsdManager; 31 import android.net.nsd.NsdServiceInfo; 32 import android.net.nsd.OffloadEngine; 33 import android.net.nsd.OffloadServiceInfo; 34 import android.os.Build; 35 import android.os.Looper; 36 import android.text.TextUtils; 37 import android.util.ArrayMap; 38 import android.util.Log; 39 import android.util.SparseArray; 40 41 import com.android.connectivity.resources.R; 42 import com.android.internal.annotations.VisibleForTesting; 43 import com.android.net.module.util.CollectionUtils; 44 import com.android.net.module.util.SharedLog; 45 import com.android.server.connectivity.ConnectivityResources; 46 import com.android.server.connectivity.mdns.util.MdnsUtils; 47 48 import java.util.ArrayList; 49 import java.util.Collections; 50 import java.util.List; 51 import java.util.Map; 52 import java.util.Objects; 53 import java.util.Set; 54 import java.util.UUID; 55 import java.util.function.BiPredicate; 56 import java.util.function.Consumer; 57 58 /** 59 * MdnsAdvertiser manages advertising services per {@link com.android.server.NsdService} requests. 60 * 61 * All methods except the constructor must be called on the looper thread. 62 */ 63 @RequiresApi(Build.VERSION_CODES.TIRAMISU) 64 public class MdnsAdvertiser { 65 private static final String TAG = MdnsAdvertiser.class.getSimpleName(); 66 static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); 67 68 // Top-level domain for link-local queries, as per RFC6762 3. 69 private static final String LOCAL_TLD = "local"; 70 71 72 private final Looper mLooper; 73 private final AdvertiserCallback mCb; 74 75 // Max-sized buffers to be used as temporary buffer to read/build packets. May be used by 76 // multiple components, but only for self-contained operations in the looper thread, so not 77 // concurrently. 78 // TODO: set according to MTU. 1300 should fit for ethernet MTU 1500 with some overhead. 79 private final byte[] mPacketCreationBuffer = new byte[1300]; 80 81 private final MdnsSocketProvider mSocketProvider; 82 private final ArrayMap<Network, InterfaceAdvertiserRequest> mAdvertiserRequests = 83 new ArrayMap<>(); 84 private final ArrayMap<MdnsInterfaceSocket, MdnsInterfaceAdvertiser> mAllAdvertisers = 85 new ArrayMap<>(); 86 private final SparseArray<Registration> mRegistrations = new SparseArray<>(); 87 private final Dependencies mDeps; 88 private String[] mDeviceHostName; 89 @NonNull private final SharedLog mSharedLog; 90 private final Map<String, List<OffloadServiceInfoWrapper>> mInterfaceOffloadServices = 91 new ArrayMap<>(); 92 private final MdnsFeatureFlags mMdnsFeatureFlags; 93 private final Map<String, Integer> mServiceTypeToOffloadPriority; 94 95 /** 96 * Dependencies for {@link MdnsAdvertiser}, useful for testing. 97 */ 98 @VisibleForTesting 99 public static class Dependencies { 100 /** 101 * @see MdnsInterfaceAdvertiser 102 */ makeAdvertiser(@onNull MdnsInterfaceSocket socket, @NonNull List<LinkAddress> initialAddresses, @NonNull Looper looper, @NonNull byte[] packetCreationBuffer, @NonNull MdnsInterfaceAdvertiser.Callback cb, @NonNull String[] deviceHostName, @NonNull SharedLog sharedLog, @NonNull MdnsFeatureFlags mdnsFeatureFlags)103 public MdnsInterfaceAdvertiser makeAdvertiser(@NonNull MdnsInterfaceSocket socket, 104 @NonNull List<LinkAddress> initialAddresses, 105 @NonNull Looper looper, @NonNull byte[] packetCreationBuffer, 106 @NonNull MdnsInterfaceAdvertiser.Callback cb, 107 @NonNull String[] deviceHostName, 108 @NonNull SharedLog sharedLog, 109 @NonNull MdnsFeatureFlags mdnsFeatureFlags) { 110 // Note NetworkInterface is final and not mockable 111 return new MdnsInterfaceAdvertiser(socket, initialAddresses, looper, 112 packetCreationBuffer, cb, deviceHostName, sharedLog, mdnsFeatureFlags); 113 } 114 115 /** 116 * Generates a unique hostname to be used by the device. 117 */ 118 @NonNull generateHostname()119 public String[] generateHostname() { 120 // Generate a very-probably-unique hostname. This allows minimizing possible conflicts 121 // to the point that probing for it is no longer necessary (as per RFC6762 8.1 last 122 // paragraph), and does not leak more information than what could already be obtained by 123 // looking at the mDNS packets source address. 124 // This differs from historical behavior that just used "Android.local" for many 125 // devices, creating a lot of conflicts. 126 // Having a different hostname per interface is an acceptable option as per RFC6762 14. 127 // This hostname will change every time the interface is reconnected, so this does not 128 // allow tracking the device. 129 // TODO: consider deriving a hostname from other sources, such as the IPv6 addresses 130 // (reusing the same privacy-protecting mechanics). 131 return new String[] { 132 "Android_" + UUID.randomUUID().toString().replace("-", ""), LOCAL_TLD }; 133 } 134 } 135 136 /** 137 * Gets the current status of the OffloadServiceInfos per interface. 138 * @param interfaceName the target interfaceName 139 * @return the list of current offloaded services. 140 */ 141 @NonNull getAllInterfaceOffloadServiceInfos( @onNull String interfaceName)142 public List<OffloadServiceInfoWrapper> getAllInterfaceOffloadServiceInfos( 143 @NonNull String interfaceName) { 144 return mInterfaceOffloadServices.getOrDefault(interfaceName, Collections.emptyList()); 145 } 146 147 private final MdnsInterfaceAdvertiser.Callback mInterfaceAdvertiserCb = 148 new MdnsInterfaceAdvertiser.Callback() { 149 @Override 150 public void onServiceProbingSucceeded( 151 @NonNull MdnsInterfaceAdvertiser advertiser, int serviceId) { 152 final Registration registration = mRegistrations.get(serviceId); 153 if (registration == null) { 154 mSharedLog.wtf("Register succeeded for unknown registration"); 155 return; 156 } 157 if (mMdnsFeatureFlags.mIsMdnsOffloadFeatureEnabled 158 // TODO: Enable offload when the serviceInfo contains a custom host. 159 && TextUtils.isEmpty(registration.getServiceInfo().getHostname())) { 160 final String interfaceName = advertiser.getSocketInterfaceName(); 161 final List<OffloadServiceInfoWrapper> existingOffloadServiceInfoWrappers = 162 mInterfaceOffloadServices.computeIfAbsent(interfaceName, 163 k -> new ArrayList<>()); 164 // Remove existing offload services from cache for update. 165 existingOffloadServiceInfoWrappers.removeIf(item -> item.mServiceId == serviceId); 166 167 byte[] rawOffloadPacket = advertiser.getRawOffloadPayload(serviceId); 168 final OffloadServiceInfoWrapper newOffloadServiceInfoWrapper = createOffloadService( 169 serviceId, registration, rawOffloadPacket); 170 existingOffloadServiceInfoWrappers.add(newOffloadServiceInfoWrapper); 171 mCb.onOffloadStartOrUpdate(interfaceName, 172 newOffloadServiceInfoWrapper.mOffloadServiceInfo); 173 } 174 175 // Wait for all current interfaces to be done probing before notifying of success. 176 if (any(mAllAdvertisers, (k, a) -> a.isProbing(serviceId))) return; 177 // The service may still be unregistered/renamed if a conflict is found on a later added 178 // interface, or if a conflicting announcement/reply is detected (RFC6762 9.) 179 180 if (!registration.mNotifiedRegistrationSuccess) { 181 mCb.onRegisterServiceSucceeded(serviceId, registration.getServiceInfo()); 182 registration.mNotifiedRegistrationSuccess = true; 183 } 184 } 185 186 @Override 187 public void onServiceConflict(@NonNull MdnsInterfaceAdvertiser advertiser, int serviceId, 188 int conflictType) { 189 mSharedLog.i("Found conflict, restarted probing for service " 190 + serviceId + " " 191 + conflictType); 192 193 final Registration registration = mRegistrations.get(serviceId); 194 if (registration == null) return; 195 if (registration.mNotifiedRegistrationSuccess) { 196 // TODO: consider notifying clients that the service is no longer registered with 197 // the old name (back to probing). The legacy implementation did not send any 198 // callback though; it only sent onServiceRegistered after re-probing finishes 199 // (with the old, conflicting, actually not used name as argument... The new 200 // implementation will send callbacks with the new name). 201 registration.mNotifiedRegistrationSuccess = false; 202 registration.mConflictAfterProbingCount++; 203 204 // The service was done probing, just reset it to probing state (RFC6762 9.) 205 forAllAdvertisers(a -> { 206 if (!a.maybeRestartProbingForConflict(serviceId)) { 207 return; 208 } 209 if (mMdnsFeatureFlags.mIsMdnsOffloadFeatureEnabled) { 210 maybeSendOffloadStop(a.getSocketInterfaceName(), serviceId); 211 } 212 }); 213 return; 214 } 215 216 if ((conflictType & CONFLICT_SERVICE) != 0) { 217 // Service conflict was found during probing; rename once to find a name that has no 218 // conflict 219 registration.updateForServiceConflict( 220 registration.makeNewServiceInfoForServiceConflict(1 /* renameCount */), 221 1 /* renameCount */); 222 } 223 224 if ((conflictType & CONFLICT_HOST) != 0) { 225 // Host conflict was found during probing; rename once to find a name that has no 226 // conflict 227 registration.updateForHostConflict( 228 registration.makeNewServiceInfoForHostConflict(1 /* renameCount */), 229 1 /* renameCount */); 230 } 231 232 registration.mConflictDuringProbingCount++; 233 234 // Keep renaming if the new name conflicts in local registrations 235 updateRegistrationUntilNoConflict((net, adv) -> adv.hasRegistration(registration), 236 registration); 237 238 // Update advertisers to use the new name 239 forAllAdvertisers(a -> a.renameServiceForConflict( 240 serviceId, registration.getServiceInfo())); 241 } 242 243 @Override 244 public void onAllServicesRemoved(@NonNull MdnsInterfaceSocket socket) { 245 if (DBG) { mSharedLog.i("onAllServicesRemoved: " + socket); } 246 // Try destroying the advertiser if all services has been removed 247 destroyAdvertiser(socket, false /* interfaceDestroyed */); 248 } 249 }; 250 hasAnyServiceConflict( @onNull BiPredicate<Network, InterfaceAdvertiserRequest> applicableAdvertiserFilter, @NonNull NsdServiceInfo newInfo, @NonNull Registration originalRegistration)251 private boolean hasAnyServiceConflict( 252 @NonNull BiPredicate<Network, InterfaceAdvertiserRequest> applicableAdvertiserFilter, 253 @NonNull NsdServiceInfo newInfo, 254 @NonNull Registration originalRegistration) { 255 return any( 256 mAdvertiserRequests, 257 (network, adv) -> 258 applicableAdvertiserFilter.test(network, adv) 259 && adv.hasServiceConflict(newInfo, originalRegistration)); 260 } 261 hasAnyHostConflict( @onNull BiPredicate<Network, InterfaceAdvertiserRequest> applicableAdvertiserFilter, @NonNull NsdServiceInfo newInfo, int clientUid)262 private boolean hasAnyHostConflict( 263 @NonNull BiPredicate<Network, InterfaceAdvertiserRequest> applicableAdvertiserFilter, 264 @NonNull NsdServiceInfo newInfo, 265 int clientUid) { 266 // Check if it conflicts with custom hosts. 267 if (any( 268 mAdvertiserRequests, 269 (network, adv) -> 270 applicableAdvertiserFilter.test(network, adv) 271 && adv.hasHostConflict(newInfo, clientUid))) { 272 return true; 273 } 274 // Check if it conflicts with the default hostname. 275 return MdnsUtils.equalsIgnoreDnsCase(newInfo.getHostname(), mDeviceHostName[0]); 276 } 277 updateRegistrationUntilNoConflict( @onNull BiPredicate<Network, InterfaceAdvertiserRequest> applicableAdvertiserFilter, @NonNull Registration registration)278 private void updateRegistrationUntilNoConflict( 279 @NonNull BiPredicate<Network, InterfaceAdvertiserRequest> applicableAdvertiserFilter, 280 @NonNull Registration registration) { 281 NsdServiceInfo newInfo = registration.getServiceInfo(); 282 283 int renameServiceCount = 0; 284 while (hasAnyServiceConflict(applicableAdvertiserFilter, newInfo, registration)) { 285 renameServiceCount++; 286 newInfo = registration.makeNewServiceInfoForServiceConflict(renameServiceCount); 287 } 288 registration.updateForServiceConflict(newInfo, renameServiceCount); 289 290 if (!TextUtils.isEmpty(registration.getServiceInfo().getHostname())) { 291 int renameHostCount = 0; 292 while (hasAnyHostConflict( 293 applicableAdvertiserFilter, newInfo, registration.mClientUid)) { 294 renameHostCount++; 295 newInfo = registration.makeNewServiceInfoForHostConflict(renameHostCount); 296 } 297 registration.updateForHostConflict(newInfo, renameHostCount); 298 } 299 } 300 maybeSendOffloadStop(final String interfaceName, int serviceId)301 private void maybeSendOffloadStop(final String interfaceName, int serviceId) { 302 final List<OffloadServiceInfoWrapper> existingOffloadServiceInfoWrappers = 303 mInterfaceOffloadServices.get(interfaceName); 304 if (existingOffloadServiceInfoWrappers == null) { 305 return; 306 } 307 // Stop the offloaded service by matching the service id 308 int idx = CollectionUtils.indexOf(existingOffloadServiceInfoWrappers, 309 item -> item.mServiceId == serviceId); 310 if (idx >= 0) { 311 mCb.onOffloadStop(interfaceName, 312 existingOffloadServiceInfoWrappers.get(idx).mOffloadServiceInfo); 313 existingOffloadServiceInfoWrappers.remove(idx); 314 } 315 } 316 317 /** 318 * Destroys the advertiser for the interface indicated by {@code socket}. 319 * 320 * {@code interfaceDestroyed} should be set to {@code true} if this method is called because 321 * the associated interface has been destroyed. 322 */ destroyAdvertiser(MdnsInterfaceSocket socket, boolean interfaceDestroyed)323 private void destroyAdvertiser(MdnsInterfaceSocket socket, boolean interfaceDestroyed) { 324 InterfaceAdvertiserRequest advertiserRequest; 325 326 MdnsInterfaceAdvertiser advertiser = mAllAdvertisers.remove(socket); 327 if (advertiser != null) { 328 advertiser.destroyNow(); 329 if (DBG) { mSharedLog.i("MdnsInterfaceAdvertiser is destroyed: " + advertiser); } 330 } 331 332 for (int i = mAdvertiserRequests.size() - 1; i >= 0; i--) { 333 advertiserRequest = mAdvertiserRequests.valueAt(i); 334 if (advertiserRequest.onAdvertiserDestroyed(socket, interfaceDestroyed)) { 335 if (DBG) { mSharedLog.i("AdvertiserRequest is removed: " + advertiserRequest); } 336 mAdvertiserRequests.removeAt(i); 337 } 338 } 339 } 340 341 /** 342 * A request for a {@link MdnsInterfaceAdvertiser}. 343 * 344 * This class tracks services to be advertised on all sockets provided via a registered 345 * {@link MdnsSocketProvider.SocketCallback}. 346 */ 347 private class InterfaceAdvertiserRequest implements MdnsSocketProvider.SocketCallback { 348 /** Registrations to add to newer MdnsInterfaceAdvertisers when sockets are created. */ 349 @NonNull 350 private final SparseArray<Registration> mPendingRegistrations = new SparseArray<>(); 351 @NonNull 352 private final ArrayMap<MdnsInterfaceSocket, MdnsInterfaceAdvertiser> mAdvertisers = 353 new ArrayMap<>(); 354 InterfaceAdvertiserRequest(@ullable Network requestedNetwork)355 InterfaceAdvertiserRequest(@Nullable Network requestedNetwork) { 356 mSocketProvider.requestSocket(requestedNetwork, this); 357 } 358 359 /** 360 * Called when the interface advertiser associated with {@code socket} has been destroyed. 361 * 362 * {@code interfaceDestroyed} should be set to {@code true} if this method is called because 363 * the associated interface has been destroyed. 364 * 365 * @return true if the {@link InterfaceAdvertiserRequest} should now be deleted 366 */ onAdvertiserDestroyed( @onNull MdnsInterfaceSocket socket, boolean interfaceDestroyed)367 boolean onAdvertiserDestroyed( 368 @NonNull MdnsInterfaceSocket socket, boolean interfaceDestroyed) { 369 final MdnsInterfaceAdvertiser removedAdvertiser = mAdvertisers.remove(socket); 370 if (removedAdvertiser != null 371 && !interfaceDestroyed && mPendingRegistrations.size() > 0) { 372 mSharedLog.wtf( 373 "unexpected onAdvertiserDestroyed() when there are pending registrations"); 374 } 375 376 if (mMdnsFeatureFlags.mIsMdnsOffloadFeatureEnabled && removedAdvertiser != null) { 377 final String interfaceName = removedAdvertiser.getSocketInterfaceName(); 378 // If the interface is destroyed, stop all hardware offloading on that 379 // interface. 380 final List<OffloadServiceInfoWrapper> offloadServiceInfoWrappers = 381 mInterfaceOffloadServices.remove(interfaceName); 382 if (offloadServiceInfoWrappers != null) { 383 for (OffloadServiceInfoWrapper offloadServiceInfoWrapper : 384 offloadServiceInfoWrappers) { 385 mCb.onOffloadStop(interfaceName, 386 offloadServiceInfoWrapper.mOffloadServiceInfo); 387 } 388 } 389 } 390 391 if (mAdvertisers.size() == 0 && mPendingRegistrations.size() == 0) { 392 // No advertiser is using sockets from this request anymore (in particular for exit 393 // announcements), and there is no registration so newer sockets will not be 394 // necessary, so the request can be unregistered. 395 mSocketProvider.unrequestSocket(this); 396 return true; 397 } 398 return false; 399 } 400 401 /** 402 * Return whether this {@link InterfaceAdvertiserRequest} has the given registration. 403 */ hasRegistration(@onNull Registration registration)404 boolean hasRegistration(@NonNull Registration registration) { 405 return mPendingRegistrations.indexOfValue(registration) >= 0; 406 } 407 408 /** 409 * Return whether using the proposed new {@link NsdServiceInfo} to add a registration would 410 * cause a conflict of the service in this {@link InterfaceAdvertiserRequest}. 411 */ hasServiceConflict( @onNull NsdServiceInfo newInfo, @NonNull Registration originalRegistration)412 boolean hasServiceConflict( 413 @NonNull NsdServiceInfo newInfo, @NonNull Registration originalRegistration) { 414 return getConflictingRegistrationDueToService(newInfo, originalRegistration) >= 0; 415 } 416 417 /** 418 * Return whether using the proposed new {@link NsdServiceInfo} to add a registration would 419 * cause a conflict of the host in this {@link InterfaceAdvertiserRequest}. 420 * 421 * @param clientUid UID of the user who wants to advertise the serviceInfo. 422 */ hasHostConflict(@onNull NsdServiceInfo newInfo, int clientUid)423 boolean hasHostConflict(@NonNull NsdServiceInfo newInfo, int clientUid) { 424 return getConflictingRegistrationDueToHost(newInfo, clientUid) >= 0; 425 } 426 427 /** Get the ID of a conflicting registration due to service, or -1 if none. */ getConflictingRegistrationDueToService( @onNull NsdServiceInfo info, @NonNull Registration originalRegistration)428 int getConflictingRegistrationDueToService( 429 @NonNull NsdServiceInfo info, @NonNull Registration originalRegistration) { 430 if (TextUtils.isEmpty(info.getServiceName())) { 431 return -1; 432 } 433 for (int i = 0; i < mPendingRegistrations.size(); i++) { 434 // Never conflict with itself 435 if (mPendingRegistrations.valueAt(i) == originalRegistration) { 436 continue; 437 } 438 final NsdServiceInfo other = mPendingRegistrations.valueAt(i).getServiceInfo(); 439 if (MdnsUtils.equalsIgnoreDnsCase(info.getServiceName(), other.getServiceName()) 440 && MdnsUtils.equalsIgnoreDnsCase(info.getServiceType(), 441 other.getServiceType())) { 442 return mPendingRegistrations.keyAt(i); 443 } 444 } 445 return -1; 446 } 447 448 /** 449 * Get the ID of a conflicting registration due to host, or -1 if none. 450 * 451 * <p>If there's already another registration with the same hostname requested by another 452 * user, this is a conflict. 453 * 454 * <p>If there're two registrations both containing address records using the same hostname, 455 * this is a conflict. 456 */ getConflictingRegistrationDueToHost(@onNull NsdServiceInfo info, int clientUid)457 int getConflictingRegistrationDueToHost(@NonNull NsdServiceInfo info, int clientUid) { 458 if (TextUtils.isEmpty(info.getHostname())) { 459 return -1; 460 } 461 for (int i = 0; i < mPendingRegistrations.size(); i++) { 462 final Registration otherRegistration = mPendingRegistrations.valueAt(i); 463 final NsdServiceInfo otherInfo = otherRegistration.getServiceInfo(); 464 final int otherServiceId = mPendingRegistrations.keyAt(i); 465 if (clientUid != otherRegistration.mClientUid 466 && MdnsUtils.equalsIgnoreDnsCase( 467 info.getHostname(), otherInfo.getHostname())) { 468 return otherServiceId; 469 } 470 if (!info.getHostAddresses().isEmpty() 471 && !otherInfo.getHostAddresses().isEmpty() 472 && MdnsUtils.equalsIgnoreDnsCase( 473 info.getHostname(), otherInfo.getHostname())) { 474 return otherServiceId; 475 } 476 } 477 return -1; 478 } 479 480 /** 481 * Add a service to advertise. 482 * 483 * <p>Conflicts must be checked via {@link #getConflictingRegistrationDueToService} and 484 * {@link #getConflictingRegistrationDueToHost} before attempting to add. 485 */ addService(int id, @NonNull Registration registration)486 void addService(int id, @NonNull Registration registration) { 487 mPendingRegistrations.put(id, registration); 488 for (int i = 0; i < mAdvertisers.size(); i++) { 489 try { 490 mAdvertisers.valueAt(i).addService(id, registration.getServiceInfo(), 491 registration.getAdvertisingOptions()); 492 } catch (NameConflictException e) { 493 mSharedLog.wtf("Name conflict adding services that should have unique names", 494 e); 495 } 496 } 497 } 498 499 /** 500 * Update an already registered service. 501 * The caller is expected to check that the service being updated doesn't change its name 502 */ updateService(int id, @NonNull Registration registration)503 void updateService(int id, @NonNull Registration registration) { 504 mPendingRegistrations.put(id, registration); 505 for (int i = 0; i < mAdvertisers.size(); i++) { 506 mAdvertisers.valueAt(i).updateService( 507 id, registration.getServiceInfo().getSubtypes()); 508 } 509 } 510 removeService(int id)511 void removeService(int id) { 512 mPendingRegistrations.remove(id); 513 for (int i = 0; i < mAdvertisers.size(); i++) { 514 final MdnsInterfaceAdvertiser advertiser = mAdvertisers.valueAt(i); 515 advertiser.removeService(id); 516 517 if (mMdnsFeatureFlags.mIsMdnsOffloadFeatureEnabled) { 518 maybeSendOffloadStop(advertiser.getSocketInterfaceName(), id); 519 } 520 } 521 } 522 getServiceRepliedRequestsCount(int id)523 int getServiceRepliedRequestsCount(int id) { 524 int repliedRequestsCount = NO_PACKET; 525 for (int i = 0; i < mAdvertisers.size(); i++) { 526 repliedRequestsCount += mAdvertisers.valueAt(i).getServiceRepliedRequestsCount(id); 527 } 528 return repliedRequestsCount; 529 } 530 getSentPacketCount(int id)531 int getSentPacketCount(int id) { 532 int sentPacketCount = NO_PACKET; 533 for (int i = 0; i < mAdvertisers.size(); i++) { 534 sentPacketCount += mAdvertisers.valueAt(i).getSentPacketCount(id); 535 } 536 return sentPacketCount; 537 } 538 539 @Override onSocketCreated(@onNull SocketKey socketKey, @NonNull MdnsInterfaceSocket socket, @NonNull List<LinkAddress> addresses)540 public void onSocketCreated(@NonNull SocketKey socketKey, 541 @NonNull MdnsInterfaceSocket socket, 542 @NonNull List<LinkAddress> addresses) { 543 MdnsInterfaceAdvertiser advertiser = mAllAdvertisers.get(socket); 544 if (advertiser == null) { 545 advertiser = mDeps.makeAdvertiser(socket, addresses, mLooper, mPacketCreationBuffer, 546 mInterfaceAdvertiserCb, mDeviceHostName, 547 mSharedLog.forSubComponent(socket.getInterface().getName()), 548 mMdnsFeatureFlags); 549 mAllAdvertisers.put(socket, advertiser); 550 advertiser.start(); 551 } 552 mAdvertisers.put(socket, advertiser); 553 for (int i = 0; i < mPendingRegistrations.size(); i++) { 554 final Registration registration = mPendingRegistrations.valueAt(i); 555 try { 556 advertiser.addService(mPendingRegistrations.keyAt(i), 557 registration.getServiceInfo(), registration.getAdvertisingOptions()); 558 } catch (NameConflictException e) { 559 mSharedLog.wtf("Name conflict adding services that should have unique names", 560 e); 561 } 562 } 563 } 564 565 @Override onInterfaceDestroyed(@onNull SocketKey socketKey, @NonNull MdnsInterfaceSocket socket)566 public void onInterfaceDestroyed(@NonNull SocketKey socketKey, 567 @NonNull MdnsInterfaceSocket socket) { 568 final MdnsInterfaceAdvertiser advertiser = mAdvertisers.get(socket); 569 if (advertiser != null) destroyAdvertiser(socket, true /* interfaceDestroyed */); 570 } 571 572 @Override onAddressesChanged(@onNull SocketKey socketKey, @NonNull MdnsInterfaceSocket socket, @NonNull List<LinkAddress> addresses)573 public void onAddressesChanged(@NonNull SocketKey socketKey, 574 @NonNull MdnsInterfaceSocket socket, @NonNull List<LinkAddress> addresses) { 575 final MdnsInterfaceAdvertiser advertiser = mAdvertisers.get(socket); 576 if (advertiser == null) { 577 return; 578 } 579 advertiser.updateAddresses(addresses); 580 581 if (mMdnsFeatureFlags.mIsMdnsOffloadFeatureEnabled) { 582 // Update address should trigger offload packet update. 583 final String interfaceName = advertiser.getSocketInterfaceName(); 584 final List<OffloadServiceInfoWrapper> existingOffloadServiceInfoWrappers = 585 mInterfaceOffloadServices.get(interfaceName); 586 if (existingOffloadServiceInfoWrappers == null) { 587 return; 588 } 589 final List<OffloadServiceInfoWrapper> updatedOffloadServiceInfoWrappers = 590 new ArrayList<>(existingOffloadServiceInfoWrappers.size()); 591 for (OffloadServiceInfoWrapper oldWrapper : existingOffloadServiceInfoWrappers) { 592 OffloadServiceInfoWrapper newWrapper = new OffloadServiceInfoWrapper( 593 oldWrapper.mServiceId, 594 oldWrapper.mOffloadServiceInfo.withOffloadPayload( 595 advertiser.getRawOffloadPayload(oldWrapper.mServiceId)) 596 ); 597 updatedOffloadServiceInfoWrappers.add(newWrapper); 598 mCb.onOffloadStartOrUpdate(interfaceName, newWrapper.mOffloadServiceInfo); 599 } 600 mInterfaceOffloadServices.put(interfaceName, updatedOffloadServiceInfoWrappers); 601 } 602 } 603 } 604 605 /** 606 * The wrapper class for OffloadServiceInfo including the serviceId. 607 */ 608 public static class OffloadServiceInfoWrapper { 609 public final @NonNull OffloadServiceInfo mOffloadServiceInfo; 610 public final int mServiceId; 611 OffloadServiceInfoWrapper(int serviceId, OffloadServiceInfo offloadServiceInfo)612 OffloadServiceInfoWrapper(int serviceId, OffloadServiceInfo offloadServiceInfo) { 613 mOffloadServiceInfo = offloadServiceInfo; 614 mServiceId = serviceId; 615 } 616 } 617 618 private static class Registration { 619 @Nullable 620 final String mOriginalServiceName; 621 @Nullable 622 final String mOriginalHostname; 623 boolean mNotifiedRegistrationSuccess; 624 private int mServiceNameConflictCount; 625 private int mHostnameConflictCount; 626 @NonNull 627 private NsdServiceInfo mServiceInfo; 628 final int mClientUid; 629 private final MdnsAdvertisingOptions mAdvertisingOptions; 630 int mConflictDuringProbingCount; 631 int mConflictAfterProbingCount; 632 Registration(@onNull NsdServiceInfo serviceInfo, int clientUid, @NonNull MdnsAdvertisingOptions advertisingOptions)633 private Registration(@NonNull NsdServiceInfo serviceInfo, int clientUid, 634 @NonNull MdnsAdvertisingOptions advertisingOptions) { 635 this.mOriginalServiceName = serviceInfo.getServiceName(); 636 this.mOriginalHostname = serviceInfo.getHostname(); 637 this.mServiceInfo = serviceInfo; 638 this.mClientUid = clientUid; 639 this.mAdvertisingOptions = advertisingOptions; 640 } 641 642 /** Check if the new {@link NsdServiceInfo} doesn't update any data other than subtypes. */ isSubtypeOnlyUpdate(@onNull NsdServiceInfo newInfo)643 public boolean isSubtypeOnlyUpdate(@NonNull NsdServiceInfo newInfo) { 644 return Objects.equals(newInfo.getServiceName(), mOriginalServiceName) 645 && Objects.equals(newInfo.getServiceType(), mServiceInfo.getServiceType()) 646 && newInfo.getPort() == mServiceInfo.getPort() 647 && Objects.equals(newInfo.getHostname(), mOriginalHostname) 648 && Objects.equals(newInfo.getHostAddresses(), mServiceInfo.getHostAddresses()) 649 && Objects.equals(newInfo.getNetwork(), mServiceInfo.getNetwork()); 650 } 651 652 /** 653 * Update subTypes for the registration. 654 */ updateSubtypes(@onNull Set<String> subtypes)655 public void updateSubtypes(@NonNull Set<String> subtypes) { 656 mServiceInfo = new NsdServiceInfo(mServiceInfo); 657 mServiceInfo.setSubtypes(subtypes); 658 } 659 660 /** 661 * Update the registration to use a different service name, after a conflict was found. 662 * 663 * @param newInfo New service info to use. 664 * @param renameCount How many renames were done before reaching the current name. 665 */ updateForServiceConflict(@onNull NsdServiceInfo newInfo, int renameCount)666 private void updateForServiceConflict(@NonNull NsdServiceInfo newInfo, int renameCount) { 667 mServiceNameConflictCount += renameCount; 668 mServiceInfo = newInfo; 669 } 670 671 /** 672 * Update the registration to use a different host name, after a conflict was found. 673 * 674 * @param newInfo New service info to use. 675 * @param renameCount How many renames were done before reaching the current name. 676 */ updateForHostConflict(@onNull NsdServiceInfo newInfo, int renameCount)677 private void updateForHostConflict(@NonNull NsdServiceInfo newInfo, int renameCount) { 678 mHostnameConflictCount += renameCount; 679 mServiceInfo = newInfo; 680 } 681 682 /** 683 * Make a new service name for the registration, after a conflict was found. 684 * 685 * If a name conflict was found during probing or because different advertising requests 686 * used the same name, the registration is attempted again with a new name (here using 687 * a number suffix, (1), (2) etc). Registration success is notified once probing succeeds 688 * with a new name. This matches legacy behavior based on mdnsresponder, and appendix D of 689 * RFC6763. 690 * 691 * @param renameCount How much to increase the number suffix for this conflict. 692 */ 693 @NonNull makeNewServiceInfoForServiceConflict(int renameCount)694 public NsdServiceInfo makeNewServiceInfoForServiceConflict(int renameCount) { 695 // In case of conflict choose a different service name. After the first conflict use 696 // "Name (2)", then "Name (3)" etc. 697 // TODO: use a hidden method in NsdServiceInfo once MdnsAdvertiser is moved to service-t 698 final NsdServiceInfo newInfo = new NsdServiceInfo(mServiceInfo); 699 newInfo.setServiceName(getUpdatedServiceName(renameCount)); 700 return newInfo; 701 } 702 703 /** 704 * Make a new hostname for the registration, after a conflict was found. 705 * 706 * <p>If a name conflict was found during probing or because different advertising requests 707 * used the same name, the registration is attempted again with a new name (here using a 708 * number suffix, -1, -2, etc). Registration success is notified once probing succeeds with 709 * a new name. 710 * 711 * @param renameCount How much to increase the number suffix for this conflict. 712 */ 713 @NonNull makeNewServiceInfoForHostConflict(int renameCount)714 public NsdServiceInfo makeNewServiceInfoForHostConflict(int renameCount) { 715 // In case of conflict choose a different hostname. After the first conflict use 716 // "Name-2", then "Name-3" etc. 717 final NsdServiceInfo newInfo = new NsdServiceInfo(mServiceInfo); 718 newInfo.setHostname(getUpdatedHostname(renameCount)); 719 return newInfo; 720 } 721 getUpdatedServiceName(int renameCount)722 private String getUpdatedServiceName(int renameCount) { 723 final String suffix = " (" + (mServiceNameConflictCount + renameCount + 1) + ")"; 724 final String truncatedServiceName = MdnsUtils.truncateServiceName(mOriginalServiceName, 725 MAX_LABEL_LENGTH - suffix.length()); 726 return truncatedServiceName + suffix; 727 } 728 getUpdatedHostname(int renameCount)729 private String getUpdatedHostname(int renameCount) { 730 final String suffix = "-" + (mHostnameConflictCount + renameCount + 1); 731 final String truncatedHostname = 732 MdnsUtils.truncateServiceName( 733 mOriginalHostname, MAX_LABEL_LENGTH - suffix.length()); 734 return truncatedHostname + suffix; 735 } 736 737 @NonNull getServiceInfo()738 public NsdServiceInfo getServiceInfo() { 739 return mServiceInfo; 740 } 741 742 @NonNull getAdvertisingOptions()743 public MdnsAdvertisingOptions getAdvertisingOptions() { 744 return mAdvertisingOptions; 745 } 746 } 747 748 /** 749 * Callbacks for advertising services. 750 * 751 * Every method is called on the MdnsAdvertiser looper thread. 752 */ 753 public interface AdvertiserCallback { 754 /** 755 * Called when a service was successfully registered, after probing. 756 * 757 * @param serviceId ID of the service provided when registering. 758 * @param registeredInfo Registered info, which may be different from the requested info, 759 * after probing and possibly choosing alternative service names. 760 */ onRegisterServiceSucceeded(int serviceId, NsdServiceInfo registeredInfo)761 void onRegisterServiceSucceeded(int serviceId, NsdServiceInfo registeredInfo); 762 763 /** 764 * Called when service registration failed. 765 * 766 * @param serviceId ID of the service provided when registering. 767 * @param errorCode One of {@code NsdManager.FAILURE_*} 768 */ onRegisterServiceFailed(int serviceId, int errorCode)769 void onRegisterServiceFailed(int serviceId, int errorCode); 770 771 // Unregistration is notified immediately as success in NsdService so no callback is needed 772 // here. 773 774 /** 775 * Called when a service is ready to be sent for hardware offloading. 776 * 777 * @param interfaceName the interface for sending the update to. 778 * @param offloadServiceInfo the offloading content. 779 */ onOffloadStartOrUpdate(@onNull String interfaceName, @NonNull OffloadServiceInfo offloadServiceInfo)780 void onOffloadStartOrUpdate(@NonNull String interfaceName, 781 @NonNull OffloadServiceInfo offloadServiceInfo); 782 783 /** 784 * Called when a service is removed or the MdnsInterfaceAdvertiser is destroyed. 785 * 786 * @param interfaceName the interface for sending the update to. 787 * @param offloadServiceInfo the offloading content. 788 */ onOffloadStop(@onNull String interfaceName, @NonNull OffloadServiceInfo offloadServiceInfo)789 void onOffloadStop(@NonNull String interfaceName, 790 @NonNull OffloadServiceInfo offloadServiceInfo); 791 } 792 793 /** 794 * Data class of avdverting metrics. 795 */ 796 public static class AdvertiserMetrics { 797 public final int mRepliedRequestsCount; 798 public final int mSentPacketCount; 799 public final int mConflictDuringProbingCount; 800 public final int mConflictAfterProbingCount; 801 AdvertiserMetrics(int repliedRequestsCount, int sentPacketCount, int conflictDuringProbingCount, int conflictAfterProbingCount)802 public AdvertiserMetrics(int repliedRequestsCount, int sentPacketCount, 803 int conflictDuringProbingCount, int conflictAfterProbingCount) { 804 mRepliedRequestsCount = repliedRequestsCount; 805 mSentPacketCount = sentPacketCount; 806 mConflictDuringProbingCount = conflictDuringProbingCount; 807 mConflictAfterProbingCount = conflictAfterProbingCount; 808 } 809 } 810 MdnsAdvertiser(@onNull Looper looper, @NonNull MdnsSocketProvider socketProvider, @NonNull AdvertiserCallback cb, @NonNull SharedLog sharedLog, @NonNull MdnsFeatureFlags mDnsFeatureFlags, @NonNull Context context)811 public MdnsAdvertiser(@NonNull Looper looper, @NonNull MdnsSocketProvider socketProvider, 812 @NonNull AdvertiserCallback cb, @NonNull SharedLog sharedLog, 813 @NonNull MdnsFeatureFlags mDnsFeatureFlags, @NonNull Context context) { 814 this(looper, socketProvider, cb, new Dependencies(), sharedLog, mDnsFeatureFlags, 815 context); 816 } 817 818 @VisibleForTesting MdnsAdvertiser(@onNull Looper looper, @NonNull MdnsSocketProvider socketProvider, @NonNull AdvertiserCallback cb, @NonNull Dependencies deps, @NonNull SharedLog sharedLog, @NonNull MdnsFeatureFlags mDnsFeatureFlags, @NonNull Context context)819 MdnsAdvertiser(@NonNull Looper looper, @NonNull MdnsSocketProvider socketProvider, 820 @NonNull AdvertiserCallback cb, @NonNull Dependencies deps, 821 @NonNull SharedLog sharedLog, @NonNull MdnsFeatureFlags mDnsFeatureFlags, 822 @NonNull Context context) { 823 mLooper = looper; 824 mCb = cb; 825 mSocketProvider = socketProvider; 826 mDeps = deps; 827 mDeviceHostName = deps.generateHostname(); 828 mSharedLog = sharedLog; 829 mMdnsFeatureFlags = mDnsFeatureFlags; 830 final ConnectivityResources res = new ConnectivityResources(context); 831 mServiceTypeToOffloadPriority = parseOffloadPriorityList( 832 res.get().getStringArray(R.array.config_nsdOffloadServicesPriority), sharedLog); 833 } 834 parseOffloadPriorityList( @onNull String[] resValues, SharedLog sharedLog)835 private static Map<String, Integer> parseOffloadPriorityList( 836 @NonNull String[] resValues, SharedLog sharedLog) { 837 final Map<String, Integer> priorities = new ArrayMap<>(resValues.length); 838 for (String entry : resValues) { 839 final String[] priorityAndType = entry.split(":", 2); 840 if (priorityAndType.length != 2) { 841 sharedLog.wtf("Invalid config_nsdOffloadServicesPriority ignored: " + entry); 842 continue; 843 } 844 845 final int priority; 846 try { 847 priority = Integer.parseInt(priorityAndType[0]); 848 } catch (NumberFormatException e) { 849 sharedLog.wtf("Invalid priority in config_nsdOffloadServicesPriority: " + entry); 850 continue; 851 } 852 priorities.put(MdnsUtils.toDnsLowerCase(priorityAndType[1]), priority); 853 } 854 return priorities; 855 } 856 checkThread()857 private void checkThread() { 858 if (Thread.currentThread() != mLooper.getThread()) { 859 throw new IllegalStateException("This must be called on the looper thread"); 860 } 861 } 862 863 /** 864 * Add or update a service to advertise. 865 * 866 * @param id A unique ID for the service. 867 * @param service The service info to advertise. 868 * @param advertisingOptions The advertising options. 869 * @param clientUid The UID who wants to advertise the service. 870 */ addOrUpdateService(int id, NsdServiceInfo service, MdnsAdvertisingOptions advertisingOptions, int clientUid)871 public void addOrUpdateService(int id, NsdServiceInfo service, 872 MdnsAdvertisingOptions advertisingOptions, int clientUid) { 873 checkThread(); 874 final Registration existingRegistration = mRegistrations.get(id); 875 final Network network = service.getNetwork(); 876 final Set<String> subtypes = service.getSubtypes(); 877 Registration registration; 878 if (advertisingOptions.isOnlyUpdate()) { 879 if (existingRegistration == null) { 880 mSharedLog.e("Update non existing registration for " + service); 881 mCb.onRegisterServiceFailed(id, NsdManager.FAILURE_INTERNAL_ERROR); 882 return; 883 } 884 if (!(existingRegistration.isSubtypeOnlyUpdate(service))) { 885 mSharedLog.e("Update request can only update subType, serviceInfo: " + service 886 + ", existing serviceInfo: " + existingRegistration.getServiceInfo()); 887 mCb.onRegisterServiceFailed(id, NsdManager.FAILURE_INTERNAL_ERROR); 888 return; 889 890 } 891 mSharedLog.i("Update service " + service + " with ID " + id + " and subtypes " 892 + subtypes + " advertisingOptions " + advertisingOptions); 893 registration = existingRegistration; 894 registration.updateSubtypes(subtypes); 895 } else { 896 if (existingRegistration != null) { 897 mSharedLog.e("Adding duplicate registration for " + service); 898 // TODO (b/264986328): add a more specific error code 899 mCb.onRegisterServiceFailed(id, NsdManager.FAILURE_INTERNAL_ERROR); 900 return; 901 } 902 mSharedLog.i("Adding service " + service + " with ID " + id + " and subtypes " 903 + subtypes + " advertisingOptions " + advertisingOptions); 904 registration = new Registration(service, clientUid, advertisingOptions); 905 final BiPredicate<Network, InterfaceAdvertiserRequest> checkConflictFilter; 906 if (network == null) { 907 // If registering on all networks, no advertiser must have conflicts 908 checkConflictFilter = (net, adv) -> true; 909 } else { 910 // If registering on one network, the matching network advertiser and the one 911 // for all networks must not have conflicts 912 checkConflictFilter = (net, adv) -> net == null || network.equals(net); 913 } 914 updateRegistrationUntilNoConflict(checkConflictFilter, registration); 915 } 916 917 InterfaceAdvertiserRequest advertiser = mAdvertiserRequests.get(network); 918 if (advertiser == null) { 919 advertiser = new InterfaceAdvertiserRequest(network); 920 mAdvertiserRequests.put(network, advertiser); 921 } 922 if (advertisingOptions.isOnlyUpdate()) { 923 advertiser.updateService(id, registration); 924 } else { 925 advertiser.addService(id, registration); 926 } 927 mRegistrations.put(id, registration); 928 } 929 930 /** 931 * Remove a previously added service. 932 * @param id ID used when registering. 933 */ removeService(int id)934 public void removeService(int id) { 935 checkThread(); 936 if (!mRegistrations.contains(id)) return; 937 mSharedLog.i("Removing service with ID " + id); 938 for (int i = mAdvertiserRequests.size() - 1; i >= 0; i--) { 939 final InterfaceAdvertiserRequest advertiser = mAdvertiserRequests.valueAt(i); 940 advertiser.removeService(id); 941 } 942 mRegistrations.remove(id); 943 // Regenerates host name when registrations removed. 944 if (mRegistrations.size() == 0) { 945 mDeviceHostName = mDeps.generateHostname(); 946 } 947 } 948 949 /** 950 * Get advertising metrics. 951 * 952 * @param id ID used when registering. 953 * @return The advertising metrics includes replied requests count, send packet count, conflict 954 * count during/after probing. 955 */ getAdvertiserMetrics(int id)956 public AdvertiserMetrics getAdvertiserMetrics(int id) { 957 checkThread(); 958 final Registration registration = mRegistrations.get(id); 959 if (registration == null) { 960 return new AdvertiserMetrics( 961 NO_PACKET /* repliedRequestsCount */, 962 NO_PACKET /* sentPacketCount */, 963 0 /* conflictDuringProbingCount */, 964 0 /* conflictAfterProbingCount */); 965 } 966 int repliedRequestsCount = NO_PACKET; 967 int sentPacketCount = NO_PACKET; 968 for (int i = 0; i < mAdvertiserRequests.size(); i++) { 969 repliedRequestsCount += 970 mAdvertiserRequests.valueAt(i).getServiceRepliedRequestsCount(id); 971 sentPacketCount += mAdvertiserRequests.valueAt(i).getSentPacketCount(id); 972 } 973 return new AdvertiserMetrics(repliedRequestsCount, sentPacketCount, 974 registration.mConflictDuringProbingCount, registration.mConflictAfterProbingCount); 975 } 976 any(@onNull ArrayMap<K, V> map, @NonNull BiPredicate<K, V> predicate)977 private static <K, V> boolean any(@NonNull ArrayMap<K, V> map, 978 @NonNull BiPredicate<K, V> predicate) { 979 for (int i = 0; i < map.size(); i++) { 980 if (predicate.test(map.keyAt(i), map.valueAt(i))) { 981 return true; 982 } 983 } 984 return false; 985 } 986 forAllAdvertisers(@onNull Consumer<MdnsInterfaceAdvertiser> consumer)987 private void forAllAdvertisers(@NonNull Consumer<MdnsInterfaceAdvertiser> consumer) { 988 any(mAllAdvertisers, (socket, advertiser) -> { 989 consumer.accept(advertiser); 990 return false; 991 }); 992 } 993 createOffloadService(int serviceId, @NonNull Registration registration, byte[] rawOffloadPacket)994 private OffloadServiceInfoWrapper createOffloadService(int serviceId, 995 @NonNull Registration registration, byte[] rawOffloadPacket) { 996 final NsdServiceInfo nsdServiceInfo = registration.getServiceInfo(); 997 final Integer mapPriority = mServiceTypeToOffloadPriority.get( 998 MdnsUtils.toDnsLowerCase(nsdServiceInfo.getServiceType())); 999 // Higher values of priority are less prioritized 1000 final int priority = mapPriority == null ? Integer.MAX_VALUE : mapPriority; 1001 final OffloadServiceInfo offloadServiceInfo = new OffloadServiceInfo( 1002 new OffloadServiceInfo.Key(nsdServiceInfo.getServiceName(), 1003 nsdServiceInfo.getServiceType()), 1004 new ArrayList<>(nsdServiceInfo.getSubtypes()), 1005 String.join(".", mDeviceHostName), 1006 rawOffloadPacket, 1007 priority, 1008 // TODO: set the offloadType based on the callback timing. 1009 OffloadEngine.OFFLOAD_TYPE_REPLY); 1010 return new OffloadServiceInfoWrapper(serviceId, offloadServiceInfo); 1011 } 1012 } 1013