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 }