1 /* 2 * Copyright (C) 2022 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 android.annotation.NonNull; 20 import android.annotation.RequiresApi; 21 import android.os.Build; 22 import android.os.Looper; 23 24 import com.android.internal.annotations.VisibleForTesting; 25 import com.android.net.module.util.CollectionUtils; 26 import com.android.net.module.util.SharedLog; 27 import com.android.server.connectivity.mdns.util.MdnsUtils; 28 29 import java.util.ArrayList; 30 import java.util.Collections; 31 import java.util.List; 32 33 /** 34 * Sends mDns probe requests to verify service records are unique on the network. 35 * 36 * TODO: implement receiving replies and handling conflicts. 37 */ 38 @RequiresApi(Build.VERSION_CODES.TIRAMISU) 39 public class MdnsProber extends MdnsPacketRepeater<MdnsProber.ProbingInfo> { 40 private static final long CONFLICT_RETRY_DELAY_MS = 5_000L; 41 MdnsProber(@onNull Looper looper, @NonNull MdnsReplySender replySender, @NonNull PacketRepeaterCallback<ProbingInfo> cb, @NonNull SharedLog sharedLog)42 public MdnsProber(@NonNull Looper looper, @NonNull MdnsReplySender replySender, 43 @NonNull PacketRepeaterCallback<ProbingInfo> cb, @NonNull SharedLog sharedLog) { 44 super(looper, replySender, cb, sharedLog, MdnsAdvertiser.DBG); 45 } 46 47 /** Probing request to send with {@link MdnsProber}. */ 48 public static class ProbingInfo implements Request { 49 50 private final int mServiceId; 51 @NonNull 52 private final MdnsPacket mPacket; 53 54 /** 55 * Create a new ProbingInfo 56 * @param serviceId Service to probe for. 57 * @param probeRecords Records to be probed for uniqueness. 58 */ ProbingInfo(int serviceId, @NonNull List<MdnsRecord> probeRecords)59 ProbingInfo(int serviceId, @NonNull List<MdnsRecord> probeRecords) { 60 mServiceId = serviceId; 61 mPacket = makePacket(probeRecords); 62 } 63 getServiceId()64 public int getServiceId() { 65 return mServiceId; 66 } 67 68 @NonNull 69 @Override getPacket(int index)70 public MdnsPacket getPacket(int index) { 71 return mPacket; 72 } 73 74 @Override getDelayMs(int nextIndex)75 public long getDelayMs(int nextIndex) { 76 // As per https://datatracker.ietf.org/doc/html/rfc6762#section-8.1 77 return 250L; 78 } 79 80 @Override getNumSends()81 public int getNumSends() { 82 // 3 packets as per https://datatracker.ietf.org/doc/html/rfc6762#section-8.1 83 return 3; 84 } 85 makePacket(@onNull List<MdnsRecord> records)86 private static MdnsPacket makePacket(@NonNull List<MdnsRecord> records) { 87 final ArrayList<MdnsRecord> questions = new ArrayList<>(records.size()); 88 for (final MdnsRecord record : records) { 89 if (containsName(questions, record.getName())) { 90 // Already added this name 91 continue; 92 } 93 94 // TODO: legacy Android mDNS used to send the first probe (only) as unicast, even 95 // though https://datatracker.ietf.org/doc/html/rfc6762#section-8.1 says they 96 // SHOULD all be. rfc6762 15.1 says that if the port is shared with another 97 // responder unicast questions should not be used, and the legacy mdnsresponder may 98 // be running, so not using unicast at all may be better. Consider using legacy 99 // behavior if this causes problems. 100 questions.add(new MdnsAnyRecord(record.getName(), false /* unicast */)); 101 } 102 103 return new MdnsPacket( 104 MdnsConstants.FLAGS_QUERY, 105 questions, 106 Collections.emptyList() /* answers */, 107 records /* authorityRecords */, 108 Collections.emptyList() /* additionalRecords */); 109 } 110 111 /** 112 * Return whether the specified name is present in the list of records. 113 */ containsName(@onNull List<MdnsRecord> records, @NonNull String[] name)114 private static boolean containsName(@NonNull List<MdnsRecord> records, 115 @NonNull String[] name) { 116 return CollectionUtils.any(records, 117 r -> MdnsUtils.equalsDnsLabelIgnoreDnsCase(name, r.getName())); 118 } 119 } 120 121 122 @VisibleForTesting getInitialDelay()123 protected long getInitialDelay() { 124 // First wait for a random time in 0-250ms 125 // as per https://datatracker.ietf.org/doc/html/rfc6762#section-8.1 126 return (long) (Math.random() * 250); 127 } 128 129 /** 130 * Start sending packets for probing. 131 */ startProbing(@onNull ProbingInfo info)132 public void startProbing(@NonNull ProbingInfo info) { 133 startProbing(info, getInitialDelay()); 134 } 135 startProbing(@onNull ProbingInfo info, long delay)136 private void startProbing(@NonNull ProbingInfo info, long delay) { 137 startSending(info.getServiceId(), info, delay); 138 } 139 140 /** 141 * Restart probing with new service info as a conflict was found. 142 */ restartForConflict(@onNull ProbingInfo newInfo)143 public void restartForConflict(@NonNull ProbingInfo newInfo) { 144 stop(newInfo.getServiceId()); 145 146 /* RFC 6762 8.1: "If fifteen conflicts occur within any ten-second period, then the host 147 MUST wait at least five seconds before each successive additional probe attempt. [...] 148 For very simple devices, a valid way to comply with this requirement is to always wait 149 five seconds after any failed probe attempt before trying again. */ 150 // TODO: count 15 conflicts in 10s instead of waiting for 5s every time 151 startProbing(newInfo, CONFLICT_RETRY_DELAY_MS); 152 } 153 } 154