Merge changes from topic "refresh-record" am: c823dc2259

Original change: https://android-review.googlesource.com/c/platform/packages/modules/Connectivity/+/2612849

Change-Id: I06edabae4d039860a884ac7ef5a12b2bb21d859c
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Yuyang Huang
2023-06-02 08:49:34 +00:00
committed by Automerger Merge Worker
6 changed files with 130 additions and 59 deletions

View File

@@ -619,6 +619,7 @@ public class NsdService extends INsdManager.Stub {
final MdnsSearchOptions.Builder optionsBuilder =
MdnsSearchOptions.newBuilder()
.setNetwork(info.getNetwork())
.setRemoveExpiredService(true)
.setIsPassiveMode(true);
if (typeAndSubtype.second != null) {
// The parsing ensures subtype starts with an underscore.
@@ -813,6 +814,7 @@ public class NsdService extends INsdManager.Stub {
.setNetwork(info.getNetwork())
.setIsPassiveMode(true)
.setResolveInstanceName(info.getServiceName())
.setRemoveExpiredService(true)
.build();
mMdnsDiscoveryManager.registerListener(
resolveServiceType, listener, options);
@@ -906,6 +908,7 @@ public class NsdService extends INsdManager.Stub {
.setNetwork(info.getNetwork())
.setIsPassiveMode(true)
.setResolveInstanceName(info.getServiceName())
.setRemoveExpiredService(true)
.build();
mMdnsDiscoveryManager.registerListener(
resolveServiceType, listener, options);

View File

@@ -24,6 +24,7 @@ import android.util.Log;
import android.util.Pair;
import com.android.server.connectivity.mdns.util.MdnsLogger;
import com.android.server.connectivity.mdns.util.MdnsUtils;
import java.io.IOException;
import java.lang.ref.WeakReference;
@@ -75,6 +76,8 @@ public class EnqueueMdnsQueryCallable implements Callable<Pair<Integer, List<Str
private final boolean sendDiscoveryQueries;
@NonNull
private final List<MdnsResponse> servicesToResolve;
@NonNull
private final MdnsResponseDecoder.Clock clock;
EnqueueMdnsQueryCallable(
@NonNull MdnsSocketClientBase requestSender,
@@ -85,7 +88,8 @@ public class EnqueueMdnsQueryCallable implements Callable<Pair<Integer, List<Str
int transactionId,
@Nullable Network network,
boolean sendDiscoveryQueries,
@NonNull Collection<MdnsResponse> servicesToResolve) {
@NonNull Collection<MdnsResponse> servicesToResolve,
@NonNull MdnsResponseDecoder.Clock clock) {
weakRequestSender = new WeakReference<>(requestSender);
this.packetWriter = packetWriter;
serviceTypeLabels = TextUtils.split(serviceType, "\\.");
@@ -95,6 +99,7 @@ public class EnqueueMdnsQueryCallable implements Callable<Pair<Integer, List<Str
this.network = network;
this.sendDiscoveryQueries = sendDiscoveryQueries;
this.servicesToResolve = new ArrayList<>(servicesToResolve);
this.clock = clock;
}
// Incompatible return type for override of Callable#call().
@@ -119,22 +124,24 @@ public class EnqueueMdnsQueryCallable implements Callable<Pair<Integer, List<Str
// List of (name, type) to query
final ArrayList<Pair<String[], Integer>> missingKnownAnswerRecords = new ArrayList<>();
final long now = clock.elapsedRealtime();
for (MdnsResponse response : servicesToResolve) {
// TODO: also send queries to renew record TTL (as per RFC6762 7.1 no need to query
// if remaining TTL is more than half the original one, so send the queries if half
// the TTL has passed).
if (response.isComplete()) continue;
final String[] serviceName = response.getServiceName();
if (serviceName == null) continue;
if (!response.hasTextRecord()) {
if (!response.hasTextRecord() || MdnsUtils.isRecordRenewalNeeded(
response.getTextRecord(), now)) {
missingKnownAnswerRecords.add(new Pair<>(serviceName, MdnsRecord.TYPE_TXT));
}
if (!response.hasServiceRecord()) {
if (!response.hasServiceRecord() || MdnsUtils.isRecordRenewalNeeded(
response.getServiceRecord(), now)) {
missingKnownAnswerRecords.add(new Pair<>(serviceName, MdnsRecord.TYPE_SRV));
// The hostname is not yet known, so queries for address records will be sent
// the next time the EnqueueMdnsQueryCallable is enqueued if the reply does not
// contain them. In practice, advertisers should include the address records
// when queried for SRV, although it's not a MUST requirement (RFC6763 12.2).
// TODO: Figure out how to renew the A/AAAA record. Usually A/AAAA record will
// be included in the response to the SRV record so in high chances there is
// no need to renew them individually.
} else if (!response.hasInet4AddressRecord() && !response.hasInet6AddressRecord()) {
final String[] host = response.getServiceRecord().getServiceHost();
missingKnownAnswerRecords.add(new Pair<>(host, MdnsRecord.TYPE_A));

View File

@@ -94,10 +94,6 @@ public class MdnsConfigs {
return false;
}
public static boolean allowSearchOptionsToRemoveExpiredService() {
return false;
}
public static boolean allowNetworkInterfaceIndexPropagation() {
return true;
}

View File

@@ -66,9 +66,6 @@ public class MdnsServiceTypeClient {
private final Map<String, MdnsResponse> instanceNameToResponse = new HashMap<>();
private final boolean removeServiceAfterTtlExpires =
MdnsConfigs.removeServiceAfterTtlExpires();
private final boolean allowSearchOptionsToRemoveExpiredService =
MdnsConfigs.allowSearchOptionsToRemoveExpiredService();
private final MdnsResponseDecoder.Clock clock;
@Nullable private MdnsSearchOptions searchOptions;
@@ -374,9 +371,7 @@ public class MdnsServiceTypeClient {
if (removeServiceAfterTtlExpires) {
return true;
}
return allowSearchOptionsToRemoveExpiredService
&& searchOptions != null
&& searchOptions.removeExpiredService();
return searchOptions != null && searchOptions.removeExpiredService();
}
@VisibleForTesting
@@ -537,7 +532,8 @@ public class MdnsServiceTypeClient {
config.transactionId,
config.network,
sendDiscoveryQueries,
servicesToResolve)
servicesToResolve,
clock)
.call();
} catch (RuntimeException e) {
sharedLog.e(String.format("Failed to run EnqueueMdnsQueryCallable for subtype: %s",

View File

@@ -152,4 +152,15 @@ public class MdnsUtils {
encoder.encode(CharBuffer.wrap(originalName), out, true /* endOfInput */);
return new String(out.array(), 0, out.position(), utf8);
}
/**
* Checks if the MdnsRecord needs to be renewed or not.
*
* <p>As per RFC6762 7.1 no need to query if remaining TTL is more than half the original one,
* so send the queries if half the TTL has passed.
*/
public static boolean isRecordRenewalNeeded(@NonNull MdnsRecord mdnsRecord, final long now) {
return mdnsRecord.getTtl() > 0
&& mdnsRecord.getRemainingTTL(now) <= mdnsRecord.getTtl() / 2;
}
}

View File

@@ -705,34 +705,8 @@ public class MdnsServiceTypeClientTests {
}
@Test
public void processResponse_notAllowRemoveSearch_shouldNotRemove() throws Exception {
final String serviceInstanceName = "service-instance-1";
client.startSendAndReceive(
mockListenerOne,
MdnsSearchOptions.newBuilder().build());
Runnable firstMdnsTask = currentThreadExecutor.getAndClearSubmittedRunnable();
// Process the initial response.
client.processResponse(createResponse(
serviceInstanceName, "192.168.1.1", 5353, /* subtype= */ "ABCDE",
Collections.emptyMap(), TEST_TTL), INTERFACE_INDEX, mockNetwork);
// Clear the scheduled runnable.
currentThreadExecutor.getAndClearLastScheduledRunnable();
// Simulate the case where the response is after TTL.
doReturn(TEST_ELAPSED_REALTIME + TEST_TTL + 1L).when(mockDecoderClock).elapsedRealtime();
firstMdnsTask.run();
// Verify removed callback was not called.
verifyServiceRemovedNoCallback(mockListenerOne);
}
@Test
@Ignore("MdnsConfigs is not configurable currently.")
public void processResponse_allowSearchOptionsToRemoveExpiredService_shouldRemove()
public void processResponse_searchOptionsEnableServiceRemoval_shouldRemove()
throws Exception {
//MdnsConfigsFlagsImpl.allowSearchOptionsToRemoveExpiredService.override(true);
final String serviceInstanceName = "service-instance-1";
client =
new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
@@ -742,7 +716,9 @@ public class MdnsServiceTypeClientTests {
return mockPacketWriter;
}
};
client.startSendAndReceive(mockListenerOne, MdnsSearchOptions.getDefaultOptions());
MdnsSearchOptions searchOptions = MdnsSearchOptions.newBuilder().setRemoveExpiredService(
true).build();
client.startSendAndReceive(mockListenerOne, searchOptions);
Runnable firstMdnsTask = currentThreadExecutor.getAndClearSubmittedRunnable();
// Process the initial response.
@@ -956,15 +932,11 @@ public class MdnsServiceTypeClientTests {
final MdnsPacket srvTxtQueryPacket = MdnsPacket.parse(
new MdnsPacketReader(srvTxtQueryCaptor.getValue()));
final List<MdnsRecord> srvTxtQuestions = srvTxtQueryPacket.questions;
final String[] serviceName = Stream.concat(Stream.of(instanceName),
Arrays.stream(SERVICE_TYPE_LABELS)).toArray(String[]::new);
assertFalse(srvTxtQuestions.stream().anyMatch(q -> q.getType() == MdnsRecord.TYPE_PTR));
assertTrue(srvTxtQuestions.stream().anyMatch(q ->
q.getType() == MdnsRecord.TYPE_SRV && Arrays.equals(q.name, serviceName)));
assertTrue(srvTxtQuestions.stream().anyMatch(q ->
q.getType() == MdnsRecord.TYPE_TXT && Arrays.equals(q.name, serviceName)));
final String[] serviceName = getTestServiceName(instanceName);
assertFalse(hasQuestion(srvTxtQueryPacket, MdnsRecord.TYPE_PTR));
assertTrue(hasQuestion(srvTxtQueryPacket, MdnsRecord.TYPE_SRV, serviceName));
assertTrue(hasQuestion(srvTxtQueryPacket, MdnsRecord.TYPE_TXT, serviceName));
// Process a response with SRV+TXT
final MdnsPacket srvTxtResponse = new MdnsPacket(
@@ -991,11 +963,8 @@ public class MdnsServiceTypeClientTests {
final MdnsPacket addressQueryPacket = MdnsPacket.parse(
new MdnsPacketReader(addressQueryCaptor.getValue()));
final List<MdnsRecord> addressQueryQuestions = addressQueryPacket.questions;
assertTrue(addressQueryQuestions.stream().anyMatch(q ->
q.getType() == MdnsRecord.TYPE_A && Arrays.equals(q.name, hostname)));
assertTrue(addressQueryQuestions.stream().anyMatch(q ->
q.getType() == MdnsRecord.TYPE_AAAA && Arrays.equals(q.name, hostname)));
assertTrue(hasQuestion(addressQueryPacket, MdnsRecord.TYPE_A, hostname));
assertTrue(hasQuestion(addressQueryPacket, MdnsRecord.TYPE_AAAA, hostname));
// Process a response with address records
final MdnsPacket addressResponse = new MdnsPacket(
@@ -1027,6 +996,81 @@ public class MdnsServiceTypeClientTests {
mockNetwork);
}
@Test
public void testRenewTxtSrvInResolve() throws Exception {
client = new MdnsServiceTypeClient(SERVICE_TYPE, mockSocketClient, currentThreadExecutor,
mockDecoderClock, mockNetwork, mockSharedLog);
final String instanceName = "service-instance";
final String[] hostname = new String[] { "testhost "};
final String ipV4Address = "192.0.2.0";
final String ipV6Address = "2001:db8::";
final MdnsSearchOptions resolveOptions = MdnsSearchOptions.newBuilder()
.setResolveInstanceName(instanceName).build();
client.startSendAndReceive(mockListenerOne, resolveOptions);
InOrder inOrder = inOrder(mockListenerOne, mockSocketClient);
// Get the query for SRV/TXT
final ArgumentCaptor<DatagramPacket> srvTxtQueryCaptor =
ArgumentCaptor.forClass(DatagramPacket.class);
currentThreadExecutor.getAndClearLastScheduledRunnable().run();
// Send twice for IPv4 and IPv6
inOrder.verify(mockSocketClient, times(2)).sendUnicastPacket(srvTxtQueryCaptor.capture(),
eq(mockNetwork));
final MdnsPacket srvTxtQueryPacket = MdnsPacket.parse(
new MdnsPacketReader(srvTxtQueryCaptor.getValue()));
final String[] serviceName = getTestServiceName(instanceName);
assertTrue(hasQuestion(srvTxtQueryPacket, MdnsRecord.TYPE_SRV, serviceName));
assertTrue(hasQuestion(srvTxtQueryPacket, MdnsRecord.TYPE_TXT, serviceName));
// Process a response with all records
final MdnsPacket srvTxtResponse = new MdnsPacket(
0 /* flags */,
Collections.emptyList() /* questions */,
List.of(
new MdnsServiceRecord(serviceName, TEST_ELAPSED_REALTIME,
true /* cacheFlush */, TEST_TTL, 0 /* servicePriority */,
0 /* serviceWeight */, 1234 /* servicePort */, hostname),
new MdnsTextRecord(serviceName, TEST_ELAPSED_REALTIME,
true /* cacheFlush */, TEST_TTL,
Collections.emptyList() /* entries */),
new MdnsInetAddressRecord(hostname, TEST_ELAPSED_REALTIME,
true /* cacheFlush */, TEST_TTL,
InetAddresses.parseNumericAddress(ipV4Address)),
new MdnsInetAddressRecord(hostname, TEST_ELAPSED_REALTIME,
true /* cacheFlush */, TEST_TTL,
InetAddresses.parseNumericAddress(ipV6Address))),
Collections.emptyList() /* authorityRecords */,
Collections.emptyList() /* additionalRecords */);
client.processResponse(srvTxtResponse, INTERFACE_INDEX, mockNetwork);
inOrder.verify(mockListenerOne).onServiceNameDiscovered(any());
inOrder.verify(mockListenerOne).onServiceFound(any());
// Expect no query on the next run
currentThreadExecutor.getAndClearLastScheduledRunnable().run();
inOrder.verifyNoMoreInteractions();
// Advance time so 75% of TTL passes and re-execute
doReturn(TEST_ELAPSED_REALTIME + (long) (TEST_TTL * 0.75))
.when(mockDecoderClock).elapsedRealtime();
currentThreadExecutor.getAndClearLastScheduledRunnable().run();
// Expect a renewal query
final ArgumentCaptor<DatagramPacket> renewalQueryCaptor =
ArgumentCaptor.forClass(DatagramPacket.class);
// Second and later sends are sent as "expect multicast response" queries
inOrder.verify(mockSocketClient, times(2)).sendMulticastPacket(renewalQueryCaptor.capture(),
eq(mockNetwork));
final MdnsPacket renewalPacket = MdnsPacket.parse(
new MdnsPacketReader(renewalQueryCaptor.getValue()));
assertTrue(hasQuestion(renewalPacket, MdnsRecord.TYPE_SRV, serviceName));
assertTrue(hasQuestion(renewalPacket, MdnsRecord.TYPE_TXT, serviceName));
}
@Test
public void testProcessResponse_ResolveExcludesOtherServices() {
client = new MdnsServiceTypeClient(
@@ -1270,6 +1314,20 @@ public class MdnsServiceTypeClientTests {
}
}
private static String[] getTestServiceName(String instanceName) {
return Stream.concat(Stream.of(instanceName),
Arrays.stream(SERVICE_TYPE_LABELS)).toArray(String[]::new);
}
private static boolean hasQuestion(MdnsPacket packet, int type) {
return hasQuestion(packet, type, null);
}
private static boolean hasQuestion(MdnsPacket packet, int type, @Nullable String[] name) {
return packet.questions.stream().anyMatch(q -> q.getType() == type
&& (name == null || Arrays.equals(q.name, name)));
}
// A fake ScheduledExecutorService that keeps tracking the last scheduled Runnable and its delay
// time.
private class FakeExecutor extends ScheduledThreadPoolExecutor {