diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java index 3bc09db0c8..2078c42791 100644 --- a/service-t/src/com/android/server/NsdService.java +++ b/service-t/src/com/android/server/NsdService.java @@ -20,6 +20,7 @@ import static android.net.ConnectivityManager.NETID_UNSET; import static android.net.nsd.NsdManager.MDNS_DISCOVERY_MANAGER_EVENT; import static android.net.nsd.NsdManager.MDNS_SERVICE_EVENT; import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY; +import static android.provider.DeviceConfig.NAMESPACE_TETHERING; import android.annotation.NonNull; import android.annotation.Nullable; @@ -110,6 +111,34 @@ public class NsdService extends INsdManager.Stub { */ private static final String MDNS_ADVERTISER_VERSION = "mdns_advertiser_version"; + /** + * Comma-separated list of type:flag mappings indicating the flags to use to allowlist + * discovery/advertising using MdnsDiscoveryManager / MdnsAdvertiser for a given type. + * + * For example _mytype._tcp.local and _othertype._tcp.local would be configured with: + * _mytype._tcp:mytype,_othertype._tcp.local:othertype + * + * In which case the flags: + * "mdns_discovery_manager_allowlist_mytype_version", + * "mdns_advertiser_allowlist_mytype_version", + * "mdns_discovery_manager_allowlist_othertype_version", + * "mdns_advertiser_allowlist_othertype_version" + * would be used to toggle MdnsDiscoveryManager / MdnsAdvertiser for each type. The flags will + * be read with + * {@link DeviceConfigUtils#isFeatureEnabled(Context, String, String, String, boolean)}. + * + * @see #MDNS_DISCOVERY_MANAGER_ALLOWLIST_FLAG_PREFIX + * @see #MDNS_ADVERTISER_ALLOWLIST_FLAG_PREFIX + * @see #MDNS_ALLOWLIST_FLAG_SUFFIX + */ + private static final String MDNS_TYPE_ALLOWLIST_FLAGS = "mdns_type_allowlist_flags"; + + private static final String MDNS_DISCOVERY_MANAGER_ALLOWLIST_FLAG_PREFIX = + "mdns_discovery_manager_allowlist_"; + private static final String MDNS_ADVERTISER_ALLOWLIST_FLAG_PREFIX = + "mdns_advertiser_allowlist_"; + private static final String MDNS_ALLOWLIST_FLAG_SUFFIX = "_version"; + public static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); private static final long CLEANUP_DELAY_MS = 10000; private static final int IFACE_IDX_ANY = 0; @@ -572,8 +601,9 @@ public class NsdService extends INsdManager.Stub { final NsdServiceInfo info = args.serviceInfo; id = getUniqueId(); - if (mDeps.isMdnsDiscoveryManagerEnabled(mContext)) { - final String serviceType = constructServiceType(info.getServiceType()); + final String serviceType = constructServiceType(info.getServiceType()); + if (mDeps.isMdnsDiscoveryManagerEnabled(mContext) + || useDiscoveryManagerForType(serviceType)) { if (serviceType == null) { clientInfo.onDiscoverServicesFailed(clientId, NsdManager.FAILURE_INTERNAL_ERROR); @@ -667,10 +697,11 @@ public class NsdService extends INsdManager.Stub { } id = getUniqueId(); - if (mDeps.isMdnsAdvertiserEnabled(mContext)) { - final NsdServiceInfo serviceInfo = args.serviceInfo; - final String serviceType = serviceInfo.getServiceType(); - final String registerServiceType = constructServiceType(serviceType); + final NsdServiceInfo serviceInfo = args.serviceInfo; + final String serviceType = serviceInfo.getServiceType(); + final String registerServiceType = constructServiceType(serviceType); + if (mDeps.isMdnsAdvertiserEnabled(mContext) + || useAdvertiserForType(registerServiceType)) { if (registerServiceType == null) { Log.e(TAG, "Invalid service type: " + serviceType); clientInfo.onRegisterServiceFailed(clientId, @@ -686,7 +717,7 @@ public class NsdService extends INsdManager.Stub { storeAdvertiserRequestMap(clientId, id, clientInfo); } else { maybeStartDaemon(); - if (registerService(id, args.serviceInfo)) { + if (registerService(id, serviceInfo)) { if (DBG) Log.d(TAG, "Register " + clientId + " " + id); storeLegacyRequestMap(clientId, id, clientInfo, msg.what); // Return success after mDns reports success @@ -748,8 +779,9 @@ public class NsdService extends INsdManager.Stub { final NsdServiceInfo info = args.serviceInfo; id = getUniqueId(); - if (mDeps.isMdnsDiscoveryManagerEnabled(mContext)) { - final String serviceType = constructServiceType(info.getServiceType()); + final String serviceType = constructServiceType(info.getServiceType()); + if (mDeps.isMdnsDiscoveryManagerEnabled(mContext) + || useDiscoveryManagerForType(serviceType)) { if (serviceType == null) { clientInfo.onResolveServiceFailed(clientId, NsdManager.FAILURE_INTERNAL_ERROR); @@ -1280,6 +1312,24 @@ public class NsdService extends INsdManager.Stub { MDNS_ADVERTISER_VERSION, false /* defaultEnabled */); } + /** + * Get the type allowlist flag value. + * @see #MDNS_TYPE_ALLOWLIST_FLAGS + */ + @Nullable + public String getTypeAllowlistFlags() { + return DeviceConfigUtils.getDeviceConfigProperty(NAMESPACE_TETHERING, + MDNS_TYPE_ALLOWLIST_FLAGS, null); + } + + /** + * @see DeviceConfigUtils#isFeatureEnabled(Context, String, String, String, boolean) + */ + public boolean isFeatureEnabled(Context context, String feature) { + return DeviceConfigUtils.isFeatureEnabled(context, NAMESPACE_TETHERING, + feature, DeviceConfigUtils.TETHERING_MODULE_NAME, false /* defaultEnabled */); + } + /** * @see MdnsDiscoveryManager */ @@ -1305,6 +1355,41 @@ public class NsdService extends INsdManager.Stub { } } + /** + * Return whether a type is allowlisted to use the Java backend. + * @param type The service type + * @param flagPrefix One of {@link #MDNS_ADVERTISER_ALLOWLIST_FLAG_PREFIX} or + * {@link #MDNS_DISCOVERY_MANAGER_ALLOWLIST_FLAG_PREFIX}. + */ + private boolean isTypeAllowlistedForJavaBackend(@Nullable String type, + @NonNull String flagPrefix) { + if (type == null) return false; + final String typesConfig = mDeps.getTypeAllowlistFlags(); + if (TextUtils.isEmpty(typesConfig)) return false; + + final String mappingPrefix = type + ":"; + String mappedFlag = null; + for (String mapping : TextUtils.split(typesConfig, ",")) { + if (mapping.startsWith(mappingPrefix)) { + mappedFlag = mapping.substring(mappingPrefix.length()); + break; + } + } + + if (mappedFlag == null) return false; + + return mDeps.isFeatureEnabled(mContext, + flagPrefix + mappedFlag + MDNS_ALLOWLIST_FLAG_SUFFIX); + } + + private boolean useDiscoveryManagerForType(@Nullable String type) { + return isTypeAllowlistedForJavaBackend(type, MDNS_DISCOVERY_MANAGER_ALLOWLIST_FLAG_PREFIX); + } + + private boolean useAdvertiserForType(@Nullable String type) { + return isTypeAllowlistedForJavaBackend(type, MDNS_ADVERTISER_ALLOWLIST_FLAG_PREFIX); + } + public static NsdService create(Context context) { HandlerThread thread = new HandlerThread(TAG); thread.start(); diff --git a/tests/unit/java/com/android/server/NsdServiceTest.java b/tests/unit/java/com/android/server/NsdServiceTest.java index 053212b185..db2759446e 100644 --- a/tests/unit/java/com/android/server/NsdServiceTest.java +++ b/tests/unit/java/com/android/server/NsdServiceTest.java @@ -1056,6 +1056,54 @@ public class NsdServiceTest { verify(mAdvertiser).removeService(serviceIdCaptor.getValue()); } + @Test + public void testTypeSpecificFeatureFlagging() { + doReturn("_type1._tcp:flag1,_type2._tcp:flag2").when(mDeps).getTypeAllowlistFlags(); + doReturn(true).when(mDeps).isFeatureEnabled(any(), + eq("mdns_discovery_manager_allowlist_flag1_version")); + doReturn(true).when(mDeps).isFeatureEnabled(any(), + eq("mdns_advertiser_allowlist_flag2_version")); + + final NsdManager client = connectClient(mService); + final NsdServiceInfo service1 = new NsdServiceInfo(SERVICE_NAME, "_type1._tcp"); + service1.setHostAddresses(List.of(parseNumericAddress("2001:db8::123"))); + service1.setPort(1234); + final NsdServiceInfo service2 = new NsdServiceInfo(SERVICE_NAME, "_type2._tcp"); + service2.setHostAddresses(List.of(parseNumericAddress("2001:db8::123"))); + service2.setPort(1234); + + client.discoverServices(service1.getServiceType(), + NsdManager.PROTOCOL_DNS_SD, mock(DiscoveryListener.class)); + client.discoverServices(service2.getServiceType(), + NsdManager.PROTOCOL_DNS_SD, mock(DiscoveryListener.class)); + waitForIdle(); + + // The DiscoveryManager is enabled for _type1 but not _type2 + verify(mDiscoveryManager).registerListener(eq("_type1._tcp.local"), any(), any()); + verify(mDiscoveryManager, never()).registerListener( + eq("_type2._tcp.local"), any(), any()); + + client.resolveService(service1, mock(ResolveListener.class)); + client.resolveService(service2, mock(ResolveListener.class)); + waitForIdle(); + + // Same behavior for resolve + verify(mDiscoveryManager, times(2)).registerListener( + eq("_type1._tcp.local"), any(), any()); + verify(mDiscoveryManager, never()).registerListener( + eq("_type2._tcp.local"), any(), any()); + + client.registerService(service1, NsdManager.PROTOCOL_DNS_SD, + mock(RegistrationListener.class)); + client.registerService(service2, NsdManager.PROTOCOL_DNS_SD, + mock(RegistrationListener.class)); + waitForIdle(); + + // The advertiser is enabled for _type2 but not _type1 + verify(mAdvertiser, never()).addService(anyInt(), argThat(info -> matches(info, service1))); + verify(mAdvertiser).addService(anyInt(), argThat(info -> matches(info, service2))); + } + @Test public void testAdvertiseWithMdnsAdvertiser() { setMdnsAdvertiserEnabled();