Fix service resolve on tethering downstreams

Tethering downstreams do not have NetworkAgents, and although they have
a netid of 99, Networks with netId 99 are not usable by apps for most
connectivity APIs.

Recent refactoring in NsdService adds the Network of a found service
into its NsdServiceInfo, and uses that network to resolve the service.
In that case the Network has netId 99 and resolving the service fails.

Avoid that problem by:
 - Keeping the Network field null when a service is found on a tethering
   downstream; this avoids giving apps a confusing and unusable Network
   with netId 99
 - Using the interface index found during discovery to resolve the
   service, if the app uses the NsdServiceInfo that was obtained from
   discovery to resolve. If not, all interfaces will be used to resolve,
   as per legacy APIs.

Bug: 233979892
Test: atest NsdServiceTest
      Also manual test with 2 devices connected via hotspot
Change-Id: Idd176153b67ccbd1d4f1b1fd66dafaa2f3a9e27a
(cherry picked from commit 1a8ee102d3)
Merged-In: Idd176153b67ccbd1d4f1b1fd66dafaa2f3a9e27a
This commit is contained in:
Remi NGUYEN VAN
2022-05-30 12:42:24 +09:00
committed by Cherrypicker Worker
parent d3cd66e224
commit 0102c9440a
6 changed files with 211 additions and 22 deletions

View File

@@ -19,7 +19,9 @@ package com.android.server;
import static libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
import static libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.any;
@@ -36,6 +38,12 @@ import static org.mockito.Mockito.when;
import android.compat.testing.PlatformCompatChangeRule;
import android.content.ContentResolver;
import android.content.Context;
import android.net.INetd;
import android.net.InetAddresses;
import android.net.mdns.aidl.DiscoveryInfo;
import android.net.mdns.aidl.GetAddressInfo;
import android.net.mdns.aidl.IMDnsEventListener;
import android.net.mdns.aidl.ResolutionInfo;
import android.net.nsd.INsdManagerCallback;
import android.net.nsd.INsdServiceConnector;
import android.net.nsd.MDnsManager;
@@ -63,6 +71,7 @@ import org.junit.Test;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import org.mockito.AdditionalAnswers;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -275,6 +284,105 @@ public class NsdServiceTest {
verify(mMockMDnsM, never()).stopDaemon();
}
@Test
public void testDiscoverOnTetheringDownstream() throws Exception {
NsdService service = makeService();
NsdManager client = connectClient(service);
final String serviceType = "a_type";
final String serviceName = "a_name";
final String domainName = "mytestdevice.local";
final int interfaceIdx = 123;
final NsdManager.DiscoveryListener discListener = mock(NsdManager.DiscoveryListener.class);
client.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, discListener);
waitForIdle();
final ArgumentCaptor<IMDnsEventListener> listenerCaptor =
ArgumentCaptor.forClass(IMDnsEventListener.class);
verify(mMockMDnsM).registerEventListener(listenerCaptor.capture());
final ArgumentCaptor<Integer> discIdCaptor = ArgumentCaptor.forClass(Integer.class);
verify(mMockMDnsM).discover(discIdCaptor.capture(), eq(serviceType),
eq(0) /* interfaceIdx */);
// NsdManager uses a separate HandlerThread to dispatch callbacks (on ServiceHandler), so
// this needs to use a timeout
verify(discListener, timeout(TIMEOUT_MS)).onDiscoveryStarted(serviceType);
final DiscoveryInfo discoveryInfo = new DiscoveryInfo(
discIdCaptor.getValue(),
IMDnsEventListener.SERVICE_FOUND,
serviceName,
serviceType,
domainName,
interfaceIdx,
INetd.LOCAL_NET_ID); // LOCAL_NET_ID (99) used on tethering downstreams
final IMDnsEventListener eventListener = listenerCaptor.getValue();
eventListener.onServiceDiscoveryStatus(discoveryInfo);
waitForIdle();
final ArgumentCaptor<NsdServiceInfo> discoveredInfoCaptor =
ArgumentCaptor.forClass(NsdServiceInfo.class);
verify(discListener, timeout(TIMEOUT_MS)).onServiceFound(discoveredInfoCaptor.capture());
final NsdServiceInfo foundInfo = discoveredInfoCaptor.getValue();
assertEquals(serviceName, foundInfo.getServiceName());
assertEquals(serviceType, foundInfo.getServiceType());
assertNull(foundInfo.getHost());
assertNull(foundInfo.getNetwork());
assertEquals(interfaceIdx, foundInfo.getInterfaceIndex());
// After discovering the service, verify resolving it
final NsdManager.ResolveListener resolveListener = mock(NsdManager.ResolveListener.class);
client.resolveService(foundInfo, resolveListener);
waitForIdle();
final ArgumentCaptor<Integer> resolvIdCaptor = ArgumentCaptor.forClass(Integer.class);
verify(mMockMDnsM).resolve(resolvIdCaptor.capture(), eq(serviceName), eq(serviceType),
eq("local.") /* domain */, eq(interfaceIdx));
final int servicePort = 10123;
final String serviceFullName = serviceName + "." + serviceType;
final ResolutionInfo resolutionInfo = new ResolutionInfo(
resolvIdCaptor.getValue(),
IMDnsEventListener.SERVICE_RESOLVED,
null /* serviceName */,
null /* serviceType */,
null /* domain */,
serviceFullName,
domainName,
servicePort,
new byte[0] /* txtRecord */,
interfaceIdx);
doReturn(true).when(mMockMDnsM).getServiceAddress(anyInt(), any(), anyInt());
eventListener.onServiceResolutionStatus(resolutionInfo);
waitForIdle();
final ArgumentCaptor<Integer> getAddrIdCaptor = ArgumentCaptor.forClass(Integer.class);
verify(mMockMDnsM).getServiceAddress(getAddrIdCaptor.capture(), eq(domainName),
eq(interfaceIdx));
final String serviceAddress = "192.0.2.123";
final GetAddressInfo addressInfo = new GetAddressInfo(
getAddrIdCaptor.getValue(),
IMDnsEventListener.SERVICE_GET_ADDR_SUCCESS,
serviceFullName,
serviceAddress,
interfaceIdx,
INetd.LOCAL_NET_ID);
eventListener.onGettingServiceAddressStatus(addressInfo);
waitForIdle();
final ArgumentCaptor<NsdServiceInfo> resInfoCaptor =
ArgumentCaptor.forClass(NsdServiceInfo.class);
verify(resolveListener, timeout(TIMEOUT_MS)).onServiceResolved(resInfoCaptor.capture());
final NsdServiceInfo resolvedService = resInfoCaptor.getValue();
assertEquals(serviceName, resolvedService.getServiceName());
assertEquals("." + serviceType, resolvedService.getServiceType());
assertEquals(InetAddresses.parseNumericAddress(serviceAddress), resolvedService.getHost());
assertEquals(servicePort, resolvedService.getPort());
assertNull(resolvedService.getNetwork());
assertEquals(interfaceIdx, resolvedService.getInterfaceIndex());
}
private void waitForIdle() {
HandlerUtils.waitForIdle(mHandler, TIMEOUT_MS);
}