• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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.MdnsServiceTypeClient.INVALID_TRANSACTION_ID;
20 
21 import android.annotation.NonNull;
22 import android.os.Build;
23 import android.text.TextUtils;
24 import android.util.Pair;
25 
26 import com.android.net.module.util.CollectionUtils;
27 import com.android.net.module.util.SharedLog;
28 import com.android.server.connectivity.mdns.util.MdnsUtils;
29 
30 import java.io.IOException;
31 import java.lang.ref.WeakReference;
32 import java.net.DatagramPacket;
33 import java.net.InetSocketAddress;
34 import java.util.ArrayList;
35 import java.util.Collection;
36 import java.util.Collections;
37 import java.util.List;
38 import java.util.concurrent.Callable;
39 
40 /**
41  * A {@link Callable} that builds and enqueues a mDNS query to send over the multicast socket. If a
42  * query is built and enqueued successfully, then call to {@link #call()} returns the transaction ID
43  * and the list of the subtypes in the query as a {@link Pair}. If a query is failed to build, or if
44  * it can not be enqueued, then call to {@link #call()} returns {@code null}.
45  */
46 public class EnqueueMdnsQueryCallable implements Callable<Pair<Integer, List<String>>> {
47 
48     private static final String TAG = "MdnsQueryCallable";
49     private static final List<Integer> castShellEmulatorMdnsPorts;
50 
51     static {
52         castShellEmulatorMdnsPorts = new ArrayList<>();
53         String[] stringPorts = MdnsConfigs.castShellEmulatorMdnsPorts();
54 
55         for (String port : stringPorts) {
56             try {
Integer.parseInt(port)57                 castShellEmulatorMdnsPorts.add(Integer.parseInt(port));
58             } catch (NumberFormatException e) {
59                 // Ignore.
60             }
61         }
62     }
63 
64     @NonNull
65     private final WeakReference<MdnsSocketClientBase> weakRequestSender;
66     @NonNull
67     private final String[] serviceTypeLabels;
68     @NonNull
69     private final List<String> subtypes;
70     private final boolean expectUnicastResponse;
71     private final int transactionId;
72     @NonNull
73     private final SocketKey socketKey;
74     private final boolean sendDiscoveryQueries;
75     @NonNull
76     private final List<MdnsResponse> servicesToResolve;
77     @NonNull
78     private final MdnsUtils.Clock clock;
79     @NonNull
80     private final SharedLog sharedLog;
81     @NonNull
82     private final MdnsServiceTypeClient.Dependencies dependencies;
83     private final boolean onlyUseIpv6OnIpv6OnlyNetworks;
84     private final byte[] packetCreationBuffer = new byte[1500]; // TODO: use interface MTU
85     @NonNull
86     private final List<MdnsResponse> existingServices;
87     private final boolean isQueryWithKnownAnswer;
88 
EnqueueMdnsQueryCallable( @onNull MdnsSocketClientBase requestSender, @NonNull String serviceType, @NonNull Collection<String> subtypes, boolean expectUnicastResponse, int transactionId, @NonNull SocketKey socketKey, boolean onlyUseIpv6OnIpv6OnlyNetworks, boolean sendDiscoveryQueries, @NonNull Collection<MdnsResponse> servicesToResolve, @NonNull MdnsUtils.Clock clock, @NonNull SharedLog sharedLog, @NonNull MdnsServiceTypeClient.Dependencies dependencies, @NonNull Collection<MdnsResponse> existingServices, boolean isQueryWithKnownAnswer)89     EnqueueMdnsQueryCallable(
90             @NonNull MdnsSocketClientBase requestSender,
91             @NonNull String serviceType,
92             @NonNull Collection<String> subtypes,
93             boolean expectUnicastResponse,
94             int transactionId,
95             @NonNull SocketKey socketKey,
96             boolean onlyUseIpv6OnIpv6OnlyNetworks,
97             boolean sendDiscoveryQueries,
98             @NonNull Collection<MdnsResponse> servicesToResolve,
99             @NonNull MdnsUtils.Clock clock,
100             @NonNull SharedLog sharedLog,
101             @NonNull MdnsServiceTypeClient.Dependencies dependencies,
102             @NonNull Collection<MdnsResponse> existingServices,
103             boolean isQueryWithKnownAnswer) {
104         weakRequestSender = new WeakReference<>(requestSender);
105         serviceTypeLabels = TextUtils.split(serviceType, "\\.");
106         this.subtypes = new ArrayList<>(subtypes);
107         this.expectUnicastResponse = expectUnicastResponse;
108         this.transactionId = transactionId;
109         this.socketKey = socketKey;
110         this.onlyUseIpv6OnIpv6OnlyNetworks = onlyUseIpv6OnIpv6OnlyNetworks;
111         this.sendDiscoveryQueries = sendDiscoveryQueries;
112         this.servicesToResolve = new ArrayList<>(servicesToResolve);
113         this.clock = clock;
114         this.sharedLog = sharedLog;
115         this.dependencies = dependencies;
116         this.existingServices = new ArrayList<>(existingServices);
117         this.isQueryWithKnownAnswer = isQueryWithKnownAnswer;
118     }
119 
120     /**
121      * Call to execute the mdns query.
122      *
123      * @return The pair of transaction id and the subtypes for the query.
124      */
125     // Incompatible return type for override of Callable#call().
126     @SuppressWarnings("nullness:override.return.invalid")
127     @Override
call()128     public Pair<Integer, List<String>> call() {
129         try {
130             MdnsSocketClientBase requestSender = weakRequestSender.get();
131             if (requestSender == null) {
132                 return Pair.create(INVALID_TRANSACTION_ID, new ArrayList<>());
133             }
134 
135             final List<MdnsRecord> questions = new ArrayList<>();
136 
137             if (sendDiscoveryQueries) {
138                 // Base service type
139                 questions.add(new MdnsPointerRecord(serviceTypeLabels, expectUnicastResponse));
140                 for (String subtype : subtypes) {
141                     final String[] labels = new String[serviceTypeLabels.length + 2];
142                     labels[0] = MdnsConstants.SUBTYPE_PREFIX + subtype;
143                     labels[1] = MdnsConstants.SUBTYPE_LABEL;
144                     System.arraycopy(serviceTypeLabels, 0, labels, 2, serviceTypeLabels.length);
145 
146                     questions.add(new MdnsPointerRecord(labels, expectUnicastResponse));
147                 }
148             }
149 
150             // List of (name, type) to query
151             final long now = clock.elapsedRealtime();
152             for (MdnsResponse response : servicesToResolve) {
153                 final String[] serviceName = response.getServiceName();
154                 if (serviceName == null) continue;
155                 boolean renewTxt = !response.hasTextRecord() || MdnsUtils.isRecordRenewalNeeded(
156                         response.getTextRecord(), now);
157                 boolean renewSrv = !response.hasServiceRecord() || MdnsUtils.isRecordRenewalNeeded(
158                         response.getServiceRecord(), now);
159                 if (renewSrv && renewTxt) {
160                     questions.add(new MdnsAnyRecord(serviceName, expectUnicastResponse));
161                 } else {
162                     if (renewTxt) {
163                         questions.add(new MdnsTextRecord(serviceName, expectUnicastResponse));
164                     }
165                     if (renewSrv) {
166                         questions.add(new MdnsServiceRecord(serviceName, expectUnicastResponse));
167                         // The hostname is not yet known, so queries for address records will be
168                         // sent the next time the EnqueueMdnsQueryCallable is enqueued if the reply
169                         // does not contain them. In practice, advertisers should include the
170                         // address records when queried for SRV, although it's not a MUST
171                         // requirement (RFC6763 12.2).
172                     } else if (!response.hasInet4AddressRecord()
173                             && !response.hasInet6AddressRecord()) {
174                         final String[] host = response.getServiceRecord().getServiceHost();
175                         questions.add(new MdnsInetAddressRecord(
176                                 host, MdnsRecord.TYPE_A, expectUnicastResponse));
177                         questions.add(new MdnsInetAddressRecord(
178                                 host, MdnsRecord.TYPE_AAAA, expectUnicastResponse));
179                     }
180                 }
181             }
182 
183             if (questions.size() == 0) {
184                 // No query to send
185                 return Pair.create(INVALID_TRANSACTION_ID, new ArrayList<>());
186             }
187 
188             // Put the existing ptr records into known-answer section.
189             final List<MdnsRecord> knownAnswers = new ArrayList<>();
190             if (sendDiscoveryQueries) {
191                 for (MdnsResponse existingService : existingServices) {
192                     for (MdnsPointerRecord ptrRecord : existingService.getPointerRecords()) {
193                         // Ignore any PTR records that don't match the current query.
194                         if (!CollectionUtils.any(questions,
195                                 q -> q instanceof MdnsPointerRecord
196                                         && MdnsUtils.equalsDnsLabelIgnoreDnsCase(
197                                                 q.getName(), ptrRecord.getName()))) {
198                             continue;
199                         }
200 
201                         knownAnswers.add(new MdnsPointerRecord(
202                                 ptrRecord.getName(),
203                                 ptrRecord.getReceiptTime(),
204                                 ptrRecord.getCacheFlush(),
205                                 ptrRecord.getRemainingTTL(now), // Put the remaining ttl.
206                                 ptrRecord.getPointer()));
207                     }
208                 }
209             }
210 
211             final MdnsPacket queryPacket = new MdnsPacket(
212                     transactionId,
213                     MdnsConstants.FLAGS_QUERY,
214                     questions,
215                     knownAnswers,
216                     Collections.emptyList(), /* authorityRecords */
217                     Collections.emptyList() /* additionalRecords */);
218             sendPacketToIpv4AndIpv6(requestSender, MdnsConstants.MDNS_PORT, queryPacket);
219             for (Integer emulatorPort : castShellEmulatorMdnsPorts) {
220                 sendPacketToIpv4AndIpv6(requestSender, emulatorPort, queryPacket);
221             }
222             return Pair.create(transactionId, subtypes);
223         } catch (Exception e) {
224             sharedLog.e(String.format("Failed to create mDNS packet for subtype: %s.",
225                     TextUtils.join(",", subtypes)), e);
226             return Pair.create(INVALID_TRANSACTION_ID, new ArrayList<>());
227         }
228     }
229 
sendPacket(MdnsSocketClientBase requestSender, InetSocketAddress address, MdnsPacket mdnsPacket)230     private void sendPacket(MdnsSocketClientBase requestSender, InetSocketAddress address,
231             MdnsPacket mdnsPacket) throws IOException {
232         final List<DatagramPacket> packets = dependencies.getDatagramPacketsFromMdnsPacket(
233                 packetCreationBuffer, mdnsPacket, address, isQueryWithKnownAnswer);
234         if (expectUnicastResponse) {
235             // MdnsMultinetworkSocketClient is only available on T+
236             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
237                     && requestSender instanceof MdnsMultinetworkSocketClient) {
238                 ((MdnsMultinetworkSocketClient) requestSender).sendPacketRequestingUnicastResponse(
239                         packets, socketKey, onlyUseIpv6OnIpv6OnlyNetworks);
240             } else {
241                 requestSender.sendPacketRequestingUnicastResponse(
242                         packets, onlyUseIpv6OnIpv6OnlyNetworks);
243             }
244         } else {
245             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
246                     && requestSender instanceof MdnsMultinetworkSocketClient) {
247                 ((MdnsMultinetworkSocketClient) requestSender)
248                         .sendPacketRequestingMulticastResponse(
249                                 packets, socketKey, onlyUseIpv6OnIpv6OnlyNetworks);
250             } else {
251                 requestSender.sendPacketRequestingMulticastResponse(
252                         packets, onlyUseIpv6OnIpv6OnlyNetworks);
253             }
254         }
255     }
256 
sendPacketToIpv4AndIpv6(MdnsSocketClientBase requestSender, int port, MdnsPacket mdnsPacket)257     private void sendPacketToIpv4AndIpv6(MdnsSocketClientBase requestSender, int port,
258             MdnsPacket mdnsPacket) {
259         try {
260             sendPacket(requestSender,
261                     new InetSocketAddress(MdnsConstants.getMdnsIPv4Address(), port), mdnsPacket);
262         } catch (IOException e) {
263             sharedLog.e("Can't send packet to IPv4", e);
264         }
265         try {
266             sendPacket(requestSender,
267                     new InetSocketAddress(MdnsConstants.getMdnsIPv6Address(), port), mdnsPacket);
268         } catch (IOException e) {
269             sharedLog.e("Can't send packet to IPv6", e);
270         }
271     }
272 }