1 /* 2 * Copyright (C) 2017 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 static libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges; 20 import static libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; 21 22 import static org.junit.Assert.assertEquals; 23 import static org.junit.Assert.assertNotNull; 24 import static org.junit.Assert.assertNull; 25 import static org.mockito.ArgumentMatchers.anyInt; 26 import static org.mockito.ArgumentMatchers.anyString; 27 import static org.mockito.Mockito.any; 28 import static org.mockito.Mockito.doReturn; 29 import static org.mockito.Mockito.eq; 30 import static org.mockito.Mockito.mock; 31 import static org.mockito.Mockito.never; 32 import static org.mockito.Mockito.reset; 33 import static org.mockito.Mockito.timeout; 34 import static org.mockito.Mockito.times; 35 import static org.mockito.Mockito.verify; 36 import static org.mockito.Mockito.when; 37 38 import android.compat.testing.PlatformCompatChangeRule; 39 import android.content.ContentResolver; 40 import android.content.Context; 41 import android.net.INetd; 42 import android.net.InetAddresses; 43 import android.net.mdns.aidl.DiscoveryInfo; 44 import android.net.mdns.aidl.GetAddressInfo; 45 import android.net.mdns.aidl.IMDnsEventListener; 46 import android.net.mdns.aidl.ResolutionInfo; 47 import android.net.nsd.INsdManagerCallback; 48 import android.net.nsd.INsdServiceConnector; 49 import android.net.nsd.MDnsManager; 50 import android.net.nsd.NsdManager; 51 import android.net.nsd.NsdServiceInfo; 52 import android.os.Binder; 53 import android.os.Build; 54 import android.os.Handler; 55 import android.os.HandlerThread; 56 import android.os.IBinder; 57 import android.os.Looper; 58 import android.os.Message; 59 60 import androidx.annotation.NonNull; 61 import androidx.test.filters.SmallTest; 62 63 import com.android.testutils.DevSdkIgnoreRule; 64 import com.android.testutils.DevSdkIgnoreRunner; 65 import com.android.testutils.HandlerUtils; 66 67 import org.junit.After; 68 import org.junit.Before; 69 import org.junit.Rule; 70 import org.junit.Test; 71 import org.junit.rules.TestRule; 72 import org.junit.runner.RunWith; 73 import org.mockito.AdditionalAnswers; 74 import org.mockito.ArgumentCaptor; 75 import org.mockito.Mock; 76 import org.mockito.MockitoAnnotations; 77 78 import java.util.LinkedList; 79 import java.util.Queue; 80 81 // TODOs: 82 // - test client can send requests and receive replies 83 // - test NSD_ON ENABLE/DISABLED listening 84 @RunWith(DevSdkIgnoreRunner.class) 85 @SmallTest 86 @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R) 87 public class NsdServiceTest { 88 89 static final int PROTOCOL = NsdManager.PROTOCOL_DNS_SD; 90 private static final long CLEANUP_DELAY_MS = 500; 91 private static final long TIMEOUT_MS = 500; 92 93 // Records INsdManagerCallback created when NsdService#connect is called. 94 // Only accessed on the test thread, since NsdService#connect is called by the NsdManager 95 // constructor called on the test thread. 96 private final Queue<INsdManagerCallback> mCreatedCallbacks = new LinkedList<>(); 97 98 @Rule 99 public TestRule compatChangeRule = new PlatformCompatChangeRule(); 100 @Mock Context mContext; 101 @Mock ContentResolver mResolver; 102 @Mock MDnsManager mMockMDnsM; 103 HandlerThread mThread; 104 TestHandler mHandler; 105 106 private static class LinkToDeathRecorder extends Binder { 107 IBinder.DeathRecipient mDr; 108 109 @Override linkToDeath(@onNull DeathRecipient recipient, int flags)110 public void linkToDeath(@NonNull DeathRecipient recipient, int flags) { 111 super.linkToDeath(recipient, flags); 112 mDr = recipient; 113 } 114 } 115 116 @Before setUp()117 public void setUp() throws Exception { 118 MockitoAnnotations.initMocks(this); 119 mThread = new HandlerThread("mock-service-handler"); 120 mThread.start(); 121 mHandler = new TestHandler(mThread.getLooper()); 122 when(mContext.getContentResolver()).thenReturn(mResolver); 123 doReturn(MDnsManager.MDNS_SERVICE).when(mContext) 124 .getSystemServiceName(MDnsManager.class); 125 doReturn(mMockMDnsM).when(mContext).getSystemService(MDnsManager.MDNS_SERVICE); 126 doReturn(true).when(mMockMDnsM).registerService( 127 anyInt(), anyString(), anyString(), anyInt(), any(), anyInt()); 128 doReturn(true).when(mMockMDnsM).stopOperation(anyInt()); 129 doReturn(true).when(mMockMDnsM).discover(anyInt(), anyString(), anyInt()); 130 doReturn(true).when(mMockMDnsM).resolve( 131 anyInt(), anyString(), anyString(), anyString(), anyInt()); 132 } 133 134 @After tearDown()135 public void tearDown() throws Exception { 136 if (mThread != null) { 137 mThread.quit(); 138 mThread = null; 139 } 140 } 141 142 @Test 143 @DisableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS) testPreSClients()144 public void testPreSClients() throws Exception { 145 NsdService service = makeService(); 146 147 // Pre S client connected, the daemon should be started. 148 connectClient(service); 149 waitForIdle(); 150 final INsdManagerCallback cb1 = getCallback(); 151 final IBinder.DeathRecipient deathRecipient1 = verifyLinkToDeath(cb1); 152 verify(mMockMDnsM, times(1)).registerEventListener(any()); 153 verify(mMockMDnsM, times(1)).startDaemon(); 154 155 connectClient(service); 156 waitForIdle(); 157 final INsdManagerCallback cb2 = getCallback(); 158 final IBinder.DeathRecipient deathRecipient2 = verifyLinkToDeath(cb2); 159 // Daemon has been started, it should not try to start it again. 160 verify(mMockMDnsM, times(1)).registerEventListener(any()); 161 verify(mMockMDnsM, times(1)).startDaemon(); 162 163 deathRecipient1.binderDied(); 164 // Still 1 client remains, daemon shouldn't be stopped. 165 waitForIdle(); 166 verify(mMockMDnsM, never()).stopDaemon(); 167 168 deathRecipient2.binderDied(); 169 // All clients are disconnected, the daemon should be stopped. 170 verifyDelayMaybeStopDaemon(CLEANUP_DELAY_MS); 171 } 172 173 @Test 174 @EnableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS) testNoDaemonStartedWhenClientsConnect()175 public void testNoDaemonStartedWhenClientsConnect() throws Exception { 176 final NsdService service = makeService(); 177 178 // Creating an NsdManager will not cause daemon startup. 179 connectClient(service); 180 waitForIdle(); 181 verify(mMockMDnsM, never()).registerEventListener(any()); 182 verify(mMockMDnsM, never()).startDaemon(); 183 final INsdManagerCallback cb1 = getCallback(); 184 final IBinder.DeathRecipient deathRecipient1 = verifyLinkToDeath(cb1); 185 186 // Creating another NsdManager will not cause daemon startup either. 187 connectClient(service); 188 waitForIdle(); 189 verify(mMockMDnsM, never()).registerEventListener(any()); 190 verify(mMockMDnsM, never()).startDaemon(); 191 final INsdManagerCallback cb2 = getCallback(); 192 final IBinder.DeathRecipient deathRecipient2 = verifyLinkToDeath(cb2); 193 194 // If there is no active request, try to clean up the daemon but should not do it because 195 // daemon has not been started. 196 deathRecipient1.binderDied(); 197 verify(mMockMDnsM, never()).unregisterEventListener(any()); 198 verify(mMockMDnsM, never()).stopDaemon(); 199 deathRecipient2.binderDied(); 200 verify(mMockMDnsM, never()).unregisterEventListener(any()); 201 verify(mMockMDnsM, never()).stopDaemon(); 202 } 203 verifyLinkToDeath(INsdManagerCallback cb)204 private IBinder.DeathRecipient verifyLinkToDeath(INsdManagerCallback cb) 205 throws Exception { 206 final IBinder.DeathRecipient dr = ((LinkToDeathRecorder) cb.asBinder()).mDr; 207 assertNotNull(dr); 208 return dr; 209 } 210 211 @Test 212 @EnableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS) testClientRequestsAreGCedAtDisconnection()213 public void testClientRequestsAreGCedAtDisconnection() throws Exception { 214 NsdService service = makeService(); 215 216 NsdManager client = connectClient(service); 217 waitForIdle(); 218 final INsdManagerCallback cb1 = getCallback(); 219 final IBinder.DeathRecipient deathRecipient = verifyLinkToDeath(cb1); 220 verify(mMockMDnsM, never()).registerEventListener(any()); 221 verify(mMockMDnsM, never()).startDaemon(); 222 223 NsdServiceInfo request = new NsdServiceInfo("a_name", "a_type"); 224 request.setPort(2201); 225 226 // Client registration request 227 NsdManager.RegistrationListener listener1 = mock(NsdManager.RegistrationListener.class); 228 client.registerService(request, PROTOCOL, listener1); 229 waitForIdle(); 230 verify(mMockMDnsM, times(1)).registerEventListener(any()); 231 verify(mMockMDnsM, times(1)).startDaemon(); 232 verify(mMockMDnsM, times(1)).registerService( 233 eq(2), eq("a_name"), eq("a_type"), eq(2201), any(), eq(0)); 234 235 // Client discovery request 236 NsdManager.DiscoveryListener listener2 = mock(NsdManager.DiscoveryListener.class); 237 client.discoverServices("a_type", PROTOCOL, listener2); 238 waitForIdle(); 239 verify(mMockMDnsM, times(1)).discover(eq(3), eq("a_type"), eq(0)); 240 241 // Client resolve request 242 NsdManager.ResolveListener listener3 = mock(NsdManager.ResolveListener.class); 243 client.resolveService(request, listener3); 244 waitForIdle(); 245 verify(mMockMDnsM, times(1)).resolve( 246 eq(4), eq("a_name"), eq("a_type"), eq("local."), eq(0)); 247 248 // Client disconnects, stop the daemon after CLEANUP_DELAY_MS. 249 deathRecipient.binderDied(); 250 verifyDelayMaybeStopDaemon(CLEANUP_DELAY_MS); 251 // checks that request are cleaned 252 verify(mMockMDnsM, times(1)).stopOperation(eq(2)); 253 verify(mMockMDnsM, times(1)).stopOperation(eq(3)); 254 verify(mMockMDnsM, times(1)).stopOperation(eq(4)); 255 } 256 257 @Test 258 @EnableCompatChanges(NsdManager.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS) testCleanupDelayNoRequestActive()259 public void testCleanupDelayNoRequestActive() throws Exception { 260 NsdService service = makeService(); 261 NsdManager client = connectClient(service); 262 263 NsdServiceInfo request = new NsdServiceInfo("a_name", "a_type"); 264 request.setPort(2201); 265 NsdManager.RegistrationListener listener1 = mock(NsdManager.RegistrationListener.class); 266 client.registerService(request, PROTOCOL, listener1); 267 waitForIdle(); 268 verify(mMockMDnsM, times(1)).registerEventListener(any()); 269 verify(mMockMDnsM, times(1)).startDaemon(); 270 final INsdManagerCallback cb1 = getCallback(); 271 final IBinder.DeathRecipient deathRecipient = verifyLinkToDeath(cb1); 272 verify(mMockMDnsM, times(1)).registerService( 273 eq(2), eq("a_name"), eq("a_type"), eq(2201), any(), eq(0)); 274 275 client.unregisterService(listener1); 276 waitForIdle(); 277 verify(mMockMDnsM, times(1)).stopOperation(eq(2)); 278 279 verifyDelayMaybeStopDaemon(CLEANUP_DELAY_MS); 280 reset(mMockMDnsM); 281 deathRecipient.binderDied(); 282 // Client disconnects, daemon should not be stopped after CLEANUP_DELAY_MS. 283 verify(mMockMDnsM, never()).unregisterEventListener(any()); 284 verify(mMockMDnsM, never()).stopDaemon(); 285 } 286 287 @Test testDiscoverOnTetheringDownstream()288 public void testDiscoverOnTetheringDownstream() throws Exception { 289 NsdService service = makeService(); 290 NsdManager client = connectClient(service); 291 292 final String serviceType = "a_type"; 293 final String serviceName = "a_name"; 294 final String domainName = "mytestdevice.local"; 295 final int interfaceIdx = 123; 296 final NsdManager.DiscoveryListener discListener = mock(NsdManager.DiscoveryListener.class); 297 client.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, discListener); 298 waitForIdle(); 299 300 final ArgumentCaptor<IMDnsEventListener> listenerCaptor = 301 ArgumentCaptor.forClass(IMDnsEventListener.class); 302 verify(mMockMDnsM).registerEventListener(listenerCaptor.capture()); 303 final ArgumentCaptor<Integer> discIdCaptor = ArgumentCaptor.forClass(Integer.class); 304 verify(mMockMDnsM).discover(discIdCaptor.capture(), eq(serviceType), 305 eq(0) /* interfaceIdx */); 306 // NsdManager uses a separate HandlerThread to dispatch callbacks (on ServiceHandler), so 307 // this needs to use a timeout 308 verify(discListener, timeout(TIMEOUT_MS)).onDiscoveryStarted(serviceType); 309 310 final DiscoveryInfo discoveryInfo = new DiscoveryInfo( 311 discIdCaptor.getValue(), 312 IMDnsEventListener.SERVICE_FOUND, 313 serviceName, 314 serviceType, 315 domainName, 316 interfaceIdx, 317 INetd.LOCAL_NET_ID); // LOCAL_NET_ID (99) used on tethering downstreams 318 final IMDnsEventListener eventListener = listenerCaptor.getValue(); 319 eventListener.onServiceDiscoveryStatus(discoveryInfo); 320 waitForIdle(); 321 322 final ArgumentCaptor<NsdServiceInfo> discoveredInfoCaptor = 323 ArgumentCaptor.forClass(NsdServiceInfo.class); 324 verify(discListener, timeout(TIMEOUT_MS)).onServiceFound(discoveredInfoCaptor.capture()); 325 final NsdServiceInfo foundInfo = discoveredInfoCaptor.getValue(); 326 assertEquals(serviceName, foundInfo.getServiceName()); 327 assertEquals(serviceType, foundInfo.getServiceType()); 328 assertNull(foundInfo.getHost()); 329 assertNull(foundInfo.getNetwork()); 330 assertEquals(interfaceIdx, foundInfo.getInterfaceIndex()); 331 332 // After discovering the service, verify resolving it 333 final NsdManager.ResolveListener resolveListener = mock(NsdManager.ResolveListener.class); 334 client.resolveService(foundInfo, resolveListener); 335 waitForIdle(); 336 337 final ArgumentCaptor<Integer> resolvIdCaptor = ArgumentCaptor.forClass(Integer.class); 338 verify(mMockMDnsM).resolve(resolvIdCaptor.capture(), eq(serviceName), eq(serviceType), 339 eq("local.") /* domain */, eq(interfaceIdx)); 340 341 final int servicePort = 10123; 342 final String serviceFullName = serviceName + "." + serviceType; 343 final ResolutionInfo resolutionInfo = new ResolutionInfo( 344 resolvIdCaptor.getValue(), 345 IMDnsEventListener.SERVICE_RESOLVED, 346 null /* serviceName */, 347 null /* serviceType */, 348 null /* domain */, 349 serviceFullName, 350 domainName, 351 servicePort, 352 new byte[0] /* txtRecord */, 353 interfaceIdx); 354 355 doReturn(true).when(mMockMDnsM).getServiceAddress(anyInt(), any(), anyInt()); 356 eventListener.onServiceResolutionStatus(resolutionInfo); 357 waitForIdle(); 358 359 final ArgumentCaptor<Integer> getAddrIdCaptor = ArgumentCaptor.forClass(Integer.class); 360 verify(mMockMDnsM).getServiceAddress(getAddrIdCaptor.capture(), eq(domainName), 361 eq(interfaceIdx)); 362 363 final String serviceAddress = "192.0.2.123"; 364 final GetAddressInfo addressInfo = new GetAddressInfo( 365 getAddrIdCaptor.getValue(), 366 IMDnsEventListener.SERVICE_GET_ADDR_SUCCESS, 367 serviceFullName, 368 serviceAddress, 369 interfaceIdx, 370 INetd.LOCAL_NET_ID); 371 eventListener.onGettingServiceAddressStatus(addressInfo); 372 waitForIdle(); 373 374 final ArgumentCaptor<NsdServiceInfo> resInfoCaptor = 375 ArgumentCaptor.forClass(NsdServiceInfo.class); 376 verify(resolveListener, timeout(TIMEOUT_MS)).onServiceResolved(resInfoCaptor.capture()); 377 final NsdServiceInfo resolvedService = resInfoCaptor.getValue(); 378 assertEquals(serviceName, resolvedService.getServiceName()); 379 assertEquals("." + serviceType, resolvedService.getServiceType()); 380 assertEquals(InetAddresses.parseNumericAddress(serviceAddress), resolvedService.getHost()); 381 assertEquals(servicePort, resolvedService.getPort()); 382 assertNull(resolvedService.getNetwork()); 383 assertEquals(interfaceIdx, resolvedService.getInterfaceIndex()); 384 } 385 waitForIdle()386 private void waitForIdle() { 387 HandlerUtils.waitForIdle(mHandler, TIMEOUT_MS); 388 } 389 makeService()390 NsdService makeService() { 391 final NsdService service = new NsdService(mContext, mHandler, CLEANUP_DELAY_MS) { 392 @Override 393 public INsdServiceConnector connect(INsdManagerCallback baseCb) { 394 // Wrap the callback in a transparent mock, to mock asBinder returning a 395 // LinkToDeathRecorder. This will allow recording the binder death recipient 396 // registered on the callback. Use a transparent mock and not a spy as the actual 397 // implementation class is not public and cannot be spied on by Mockito. 398 final INsdManagerCallback cb = mock(INsdManagerCallback.class, 399 AdditionalAnswers.delegatesTo(baseCb)); 400 doReturn(new LinkToDeathRecorder()).when(cb).asBinder(); 401 mCreatedCallbacks.add(cb); 402 return super.connect(cb); 403 } 404 }; 405 return service; 406 } 407 getCallback()408 private INsdManagerCallback getCallback() { 409 return mCreatedCallbacks.remove(); 410 } 411 connectClient(NsdService service)412 NsdManager connectClient(NsdService service) { 413 return new NsdManager(mContext, service); 414 } 415 verifyDelayMaybeStopDaemon(long cleanupDelayMs)416 void verifyDelayMaybeStopDaemon(long cleanupDelayMs) throws Exception { 417 waitForIdle(); 418 // Stop daemon shouldn't be called immediately. 419 verify(mMockMDnsM, never()).unregisterEventListener(any()); 420 verify(mMockMDnsM, never()).stopDaemon(); 421 422 // Clean up the daemon after CLEANUP_DELAY_MS. 423 verify(mMockMDnsM, timeout(cleanupDelayMs + TIMEOUT_MS)).unregisterEventListener(any()); 424 verify(mMockMDnsM, timeout(cleanupDelayMs + TIMEOUT_MS)).stopDaemon(); 425 } 426 427 public static class TestHandler extends Handler { 428 public Message lastMessage; 429 TestHandler(Looper looper)430 TestHandler(Looper looper) { 431 super(looper); 432 } 433 434 @Override handleMessage(Message msg)435 public void handleMessage(Message msg) { 436 lastMessage = obtainMessage(); 437 lastMessage.copyFrom(msg); 438 } 439 } 440 } 441