1 /* 2 * Copyright (C) 2023 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.MdnsSearchOptions.AGGRESSIVE_QUERY_MODE; 20 import static com.android.server.connectivity.mdns.MdnsSearchOptions.PASSIVE_QUERY_MODE; 21 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 25 import com.android.internal.annotations.VisibleForTesting; 26 27 /** 28 * A configuration for the PeriodicalQueryTask that contains parameters to build a query packet. 29 * Call to getConfigForNextRun returns a config that can be used to build the next query task. 30 */ 31 public class QueryTaskConfig { 32 33 private static final int INITIAL_TIME_BETWEEN_BURSTS_MS = 34 (int) MdnsConfigs.initialTimeBetweenBurstsMs(); 35 private static final int MAX_TIME_BETWEEN_ACTIVE_PASSIVE_BURSTS_MS = 36 (int) MdnsConfigs.timeBetweenBurstsMs(); 37 private static final int QUERIES_PER_BURST = (int) MdnsConfigs.queriesPerBurst(); 38 private static final int TIME_BETWEEN_QUERIES_IN_BURST_MS = 39 (int) MdnsConfigs.timeBetweenQueriesInBurstMs(); 40 private static final int QUERIES_PER_BURST_PASSIVE_MODE = 41 (int) MdnsConfigs.queriesPerBurstPassive(); 42 private static final int UNSIGNED_SHORT_MAX_VALUE = 65536; 43 @VisibleForTesting 44 // RFC 6762 5.2: The interval between the first two queries MUST be at least one second. 45 static final int INITIAL_AGGRESSIVE_TIME_BETWEEN_BURSTS_MS = 1000; 46 @VisibleForTesting 47 // Basically this tries to send one query per typical DTIM interval 100ms, to maximize the 48 // chances that a query will be received if devices are using a DTIM multiplier (in which case 49 // they only listen once every [multiplier] DTIM intervals). 50 static final int TIME_BETWEEN_RETRANSMISSION_QUERIES_IN_BURST_MS = 100; 51 static final int MAX_TIME_BETWEEN_AGGRESSIVE_BURSTS_MS = 60000; 52 private final boolean alwaysAskForUnicastResponse = 53 MdnsConfigs.alwaysAskForUnicastResponseInEachBurst(); 54 private final int queryMode; 55 final boolean onlyUseIpv6OnIpv6OnlyNetworks; 56 private final int numOfQueriesBeforeBackoff; 57 @VisibleForTesting 58 final int transactionId; 59 @VisibleForTesting 60 final boolean expectUnicastResponse; 61 private final int queriesPerBurst; 62 private final int timeBetweenBurstsInMs; 63 private final int burstCounter; 64 final long delayUntilNextTaskWithoutBackoffMs; 65 private final boolean isFirstBurst; 66 private final long queryCount; 67 @NonNull 68 final SocketKey socketKey; 69 QueryTaskConfig(@onNull QueryTaskConfig other, long queryCount, int transactionId, boolean expectUnicastResponse, boolean isFirstBurst, int burstCounter, int queriesPerBurst, int timeBetweenBurstsInMs, long delayUntilNextTaskWithoutBackoffMs)70 QueryTaskConfig(@NonNull QueryTaskConfig other, long queryCount, int transactionId, 71 boolean expectUnicastResponse, boolean isFirstBurst, int burstCounter, 72 int queriesPerBurst, int timeBetweenBurstsInMs, 73 long delayUntilNextTaskWithoutBackoffMs) { 74 this.queryMode = other.queryMode; 75 this.onlyUseIpv6OnIpv6OnlyNetworks = other.onlyUseIpv6OnIpv6OnlyNetworks; 76 this.numOfQueriesBeforeBackoff = other.numOfQueriesBeforeBackoff; 77 this.transactionId = transactionId; 78 this.expectUnicastResponse = expectUnicastResponse; 79 this.queriesPerBurst = queriesPerBurst; 80 this.timeBetweenBurstsInMs = timeBetweenBurstsInMs; 81 this.burstCounter = burstCounter; 82 this.delayUntilNextTaskWithoutBackoffMs = delayUntilNextTaskWithoutBackoffMs; 83 this.isFirstBurst = isFirstBurst; 84 this.queryCount = queryCount; 85 this.socketKey = other.socketKey; 86 } 87 QueryTaskConfig(int queryMode, boolean onlyUseIpv6OnIpv6OnlyNetworks, int numOfQueriesBeforeBackoff, @Nullable SocketKey socketKey)88 QueryTaskConfig(int queryMode, 89 boolean onlyUseIpv6OnIpv6OnlyNetworks, 90 int numOfQueriesBeforeBackoff, 91 @Nullable SocketKey socketKey) { 92 this.queryMode = queryMode; 93 this.onlyUseIpv6OnIpv6OnlyNetworks = onlyUseIpv6OnIpv6OnlyNetworks; 94 this.numOfQueriesBeforeBackoff = numOfQueriesBeforeBackoff; 95 this.queriesPerBurst = QUERIES_PER_BURST; 96 this.burstCounter = 0; 97 this.transactionId = 1; 98 this.expectUnicastResponse = true; 99 this.isFirstBurst = true; 100 // Config the scan frequency based on the scan mode. 101 if (this.queryMode == AGGRESSIVE_QUERY_MODE) { 102 this.timeBetweenBurstsInMs = INITIAL_AGGRESSIVE_TIME_BETWEEN_BURSTS_MS; 103 this.delayUntilNextTaskWithoutBackoffMs = 104 TIME_BETWEEN_RETRANSMISSION_QUERIES_IN_BURST_MS; 105 } else if (this.queryMode == PASSIVE_QUERY_MODE) { 106 // In passive scan mode, sends a single burst of QUERIES_PER_BURST queries, and then 107 // in each TIME_BETWEEN_BURSTS interval, sends QUERIES_PER_BURST_PASSIVE_MODE 108 // queries. 109 this.timeBetweenBurstsInMs = MAX_TIME_BETWEEN_ACTIVE_PASSIVE_BURSTS_MS; 110 this.delayUntilNextTaskWithoutBackoffMs = TIME_BETWEEN_QUERIES_IN_BURST_MS; 111 } else { 112 // In active scan mode, sends a burst of QUERIES_PER_BURST queries, 113 // TIME_BETWEEN_QUERIES_IN_BURST_MS apart, then waits for the scan interval, and 114 // then repeats. The scan interval starts as INITIAL_TIME_BETWEEN_BURSTS_MS and 115 // doubles until it maxes out at TIME_BETWEEN_BURSTS_MS. 116 this.timeBetweenBurstsInMs = INITIAL_TIME_BETWEEN_BURSTS_MS; 117 this.delayUntilNextTaskWithoutBackoffMs = TIME_BETWEEN_QUERIES_IN_BURST_MS; 118 } 119 this.socketKey = socketKey; 120 this.queryCount = 0; 121 } 122 getDelayUntilNextTaskWithoutBackoff(boolean isFirstQueryInBurst, boolean isLastQueryInBurst)123 long getDelayUntilNextTaskWithoutBackoff(boolean isFirstQueryInBurst, 124 boolean isLastQueryInBurst) { 125 if (isFirstQueryInBurst && queryMode == AGGRESSIVE_QUERY_MODE) { 126 return 0; 127 } 128 if (isLastQueryInBurst) { 129 return timeBetweenBurstsInMs; 130 } 131 return queryMode == AGGRESSIVE_QUERY_MODE 132 ? TIME_BETWEEN_RETRANSMISSION_QUERIES_IN_BURST_MS 133 : TIME_BETWEEN_QUERIES_IN_BURST_MS; 134 } 135 getNextExpectUnicastResponse(boolean isLastQueryInBurst)136 boolean getNextExpectUnicastResponse(boolean isLastQueryInBurst) { 137 if (!isLastQueryInBurst) { 138 return false; 139 } 140 if (queryMode == AGGRESSIVE_QUERY_MODE) { 141 return true; 142 } 143 return alwaysAskForUnicastResponse; 144 } 145 getNextTimeBetweenBurstsMs(boolean isLastQueryInBurst)146 int getNextTimeBetweenBurstsMs(boolean isLastQueryInBurst) { 147 if (!isLastQueryInBurst) { 148 return timeBetweenBurstsInMs; 149 } 150 final int maxTimeBetweenBursts = queryMode == AGGRESSIVE_QUERY_MODE 151 ? MAX_TIME_BETWEEN_AGGRESSIVE_BURSTS_MS : MAX_TIME_BETWEEN_ACTIVE_PASSIVE_BURSTS_MS; 152 return Math.min(timeBetweenBurstsInMs * 2, maxTimeBetweenBursts); 153 } 154 155 /** 156 * Get new QueryTaskConfig for next run. 157 */ getConfigForNextRun()158 public QueryTaskConfig getConfigForNextRun() { 159 long newQueryCount = queryCount + 1; 160 int newTransactionId = transactionId + 1; 161 if (newTransactionId > UNSIGNED_SHORT_MAX_VALUE) { 162 newTransactionId = 1; 163 } 164 165 int newQueriesPerBurst = queriesPerBurst; 166 int newBurstCounter = burstCounter + 1; 167 final boolean isFirstQueryInBurst = newBurstCounter == 1; 168 final boolean isLastQueryInBurst = newBurstCounter == queriesPerBurst; 169 boolean newIsFirstBurst = isFirstBurst && !isLastQueryInBurst; 170 if (isLastQueryInBurst) { 171 newBurstCounter = 0; 172 // In passive scan mode, sends a single burst of QUERIES_PER_BURST queries, and 173 // then in each TIME_BETWEEN_BURSTS interval, sends QUERIES_PER_BURST_PASSIVE_MODE 174 // queries. 175 if (isFirstBurst && queryMode == PASSIVE_QUERY_MODE) { 176 newQueriesPerBurst = QUERIES_PER_BURST_PASSIVE_MODE; 177 } 178 } 179 180 return new QueryTaskConfig(this, newQueryCount, newTransactionId, 181 getNextExpectUnicastResponse(isLastQueryInBurst), newIsFirstBurst, newBurstCounter, 182 newQueriesPerBurst, getNextTimeBetweenBurstsMs(isLastQueryInBurst), 183 getDelayUntilNextTaskWithoutBackoff(isFirstQueryInBurst, isLastQueryInBurst)); 184 } 185 186 /** 187 * Determine if the query backoff should be used. 188 */ shouldUseQueryBackoff()189 public boolean shouldUseQueryBackoff() { 190 // Don't enable backoff mode during the burst or in the first burst 191 if (burstCounter != 0 || isFirstBurst) { 192 return false; 193 } 194 return queryCount > numOfQueriesBeforeBackoff; 195 } 196 } 197