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