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.wifi;
18 
19 import android.annotation.NonNull;
20 import android.net.DscpPolicy;
21 import android.net.NetworkAgent;
22 import android.os.Handler;
23 import android.os.HandlerThread;
24 import android.util.Log;
25 import android.util.Pair;
26 
27 import com.android.internal.annotations.VisibleForTesting;
28 import com.android.modules.utils.build.SdkLevel;
29 import com.android.server.wifi.SupplicantStaIfaceHal.QosPolicyRequest;
30 import com.android.server.wifi.SupplicantStaIfaceHal.QosPolicyStatus;
31 
32 import java.io.FileDescriptor;
33 import java.io.PrintWriter;
34 import java.util.ArrayList;
35 import java.util.HashSet;
36 import java.util.List;
37 import java.util.Set;
38 
39 /*
40  * Handler for QoS policy requests.
41  */
42 public class QosPolicyRequestHandler {
43     private static final String TAG = "QosPolicyRequestHandler";
44     @VisibleForTesting
45     public static final int PROCESSING_TIMEOUT_MILLIS = 500;
46 
47     private final String mInterfaceName;
48     private final WifiNative mWifiNative;
49     private final ClientModeImpl mClientModeImpl;
50     private WifiNetworkAgent mNetworkAgent;
51     private Handler mHandler;
52     private boolean mVerboseLoggingEnabled;
53 
54     private int mQosRequestDialogToken;
55     private int mNumQosPoliciesInRequest;
56     private boolean mQosResourcesAvailable;
57     private boolean mQosRequestIsProcessing = false;
58     private List<QosPolicyStatus> mQosPolicyStatusList = new ArrayList<>();
59     private List<Pair<Integer, List<QosPolicyRequest>>> mQosPolicyRequestQueue = new ArrayList<>();
60 
QosPolicyRequestHandler( @onNull String ifaceName, @NonNull WifiNative wifiNative, @NonNull ClientModeImpl clientModeImpl, @NonNull HandlerThread handlerThread)61     public QosPolicyRequestHandler(
62             @NonNull String ifaceName, @NonNull WifiNative wifiNative,
63             @NonNull ClientModeImpl clientModeImpl, @NonNull HandlerThread handlerThread) {
64         mInterfaceName = ifaceName;
65         mWifiNative = wifiNative;
66         mClientModeImpl = clientModeImpl;
67         mHandler = new Handler(handlerThread.getLooper());
68     }
69 
70     /**
71      * Enable/disable verbose logging.
72      */
enableVerboseLogging(boolean verbose)73     public void enableVerboseLogging(boolean verbose) {
74         mVerboseLoggingEnabled = verbose;
75     }
76 
77     /**
78      * Dump internal state regarding the policy request queue, and the request which is
79      * currently being processed.
80      */
dump(FileDescriptor fd, PrintWriter pw, String[] args)81     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
82         pw.println("mQosRequestDialogToken: " + mQosRequestDialogToken);
83         pw.println("mNumQosPoliciesInRequest: " + mNumQosPoliciesInRequest);
84         pw.println("mQosResourcesAvailable: " + mQosResourcesAvailable);
85         pw.println("mQosPolicyStatusList size: " + mQosPolicyStatusList.size());
86         for (QosPolicyStatus status : mQosPolicyStatusList) {
87             pw.println("    Policy id: " + status.policyId + ", status: "
88                     + status.statusCode);
89         }
90         pw.println("mQosPolicyRequestQueue size: " + mQosPolicyRequestQueue.size());
91         for (Pair<Integer, List<QosPolicyRequest>> request : mQosPolicyRequestQueue) {
92             pw.println("    Dialog token: " + request.first
93                     + ", Num policies: " + request.second.size());
94             for (QosPolicyRequest policy : request.second) {
95                 pw.println("        " + policy);
96             }
97         }
98     }
99 
100     /**
101      * Set the network agent.
102      */
setNetworkAgent(WifiNetworkAgent wifiNetworkAgent)103     public void setNetworkAgent(WifiNetworkAgent wifiNetworkAgent) {
104         WifiNetworkAgent oldNetworkAgent = mNetworkAgent;
105         mNetworkAgent = wifiNetworkAgent;
106         if (mNetworkAgent == null) {
107             mQosPolicyStatusList.clear();
108             mQosPolicyRequestQueue.clear();
109             mQosRequestIsProcessing = false;
110         } else if (oldNetworkAgent != null) {
111             // Existing network agent was replaced by a new one.
112             resetProcessingState();
113         }
114     }
115 
116     /**
117      * Queue a QoS policy request to be processed.
118      * @param dialogToken Token identifying the request.
119      * @param policies List of policies that we are requesting to set.
120      */
queueQosPolicyRequest(int dialogToken, List<QosPolicyRequest> policies)121     public void queueQosPolicyRequest(int dialogToken, List<QosPolicyRequest> policies) {
122         if (mNetworkAgent == null) {
123             Log.e(TAG, "Attempted to call queueQosPolicyRequest, but mNetworkAgent is null");
124             return;
125         }
126         mQosPolicyRequestQueue.add(new Pair(dialogToken, policies));
127         processNextQosPolicyRequestIfPossible();
128     }
129 
130     /**
131      * Set the status for a policy which was processed.
132      * @param policyId ID of the policy.
133      * @param status code received from the NetworkAgent.
134      */
setQosPolicyStatus(int policyId, int status)135     public void setQosPolicyStatus(int policyId, int status) {
136         if (mNetworkAgent == null) {
137             Log.e(TAG, "Attempted to call setQosPolicyStatus, but mNetworkAgent is null");
138             return;
139         }
140 
141         mQosPolicyStatusList.add(new QosPolicyStatus(policyId, status));
142         if (status == NetworkAgent.DSCP_POLICY_STATUS_INSUFFICIENT_PROCESSING_RESOURCES) {
143             mQosResourcesAvailable = false;
144         }
145         sendQosPolicyResponseIfReady();
146     }
147 
rejectQosPolicy(int policyId)148     private void rejectQosPolicy(int policyId) {
149         mQosPolicyStatusList.add(new QosPolicyStatus(
150                 policyId, NetworkAgent.DSCP_POLICY_STATUS_REQUEST_DECLINED));
151         sendQosPolicyResponseIfReady();
152     }
153 
sendQosPolicyResponseIfReady()154     private void sendQosPolicyResponseIfReady() {
155         if (mQosRequestIsProcessing && mQosPolicyStatusList.size() == mNumQosPoliciesInRequest) {
156             mWifiNative.sendQosPolicyResponse(mInterfaceName, mQosRequestDialogToken,
157                     mQosResourcesAvailable, mQosPolicyStatusList);
158             mQosRequestIsProcessing = false;
159             mHandler.post(() -> processNextQosPolicyRequestIfPossible());
160         }
161     }
162 
processNextQosPolicyRequestIfPossible()163     private void processNextQosPolicyRequestIfPossible() {
164         if (!mQosRequestIsProcessing && mQosPolicyRequestQueue.size() != 0) {
165             Pair<Integer, List<QosPolicyRequest>> nextRequest = mQosPolicyRequestQueue.get(0);
166             mQosPolicyRequestQueue.remove(0);
167             mQosRequestIsProcessing = true;
168             processQosPolicyRequest(nextRequest.first, nextRequest.second);
169         }
170     }
171 
checkForProcessingStall(int dialogToken)172     private void checkForProcessingStall(int dialogToken) {
173         if (mQosRequestIsProcessing && dialogToken == mQosRequestDialogToken) {
174             Log.e(TAG, "Stop processing stalled QoS request " + dialogToken);
175             resetProcessingState();
176         }
177     }
178 
resetProcessingState()179     private void resetProcessingState() {
180         mQosRequestIsProcessing = false;
181         mQosPolicyRequestQueue.clear();
182         mClientModeImpl.clearQueuedQosMessages();
183         mWifiNative.removeAllQosPolicies(mInterfaceName);
184         if (mNetworkAgent != null) {
185             mNetworkAgent.sendRemoveAllDscpPolicies();
186         }
187     }
188 
processQosPolicyRequest(int dialogToken, List<QosPolicyRequest> policies)189     private void processQosPolicyRequest(int dialogToken, List<QosPolicyRequest> policies) {
190         if (mNetworkAgent == null) {
191             Log.e(TAG, "Attempted to call processQosPolicyRequest, but mNetworkAgent is null");
192             return;
193         }
194 
195         mQosRequestDialogToken = dialogToken;
196         mQosResourcesAvailable = true;
197         mNumQosPoliciesInRequest = policies.size();
198         mQosPolicyStatusList.clear();
199 
200         if (policies.size() == 0) {
201             sendQosPolicyResponseIfReady();
202             return;
203         }
204 
205         // Reject entire batch if any duplicate policy id's exist.
206         Set<Byte> uniquePolicyIds = new HashSet<>();
207         for (QosPolicyRequest policy : policies) {
208             uniquePolicyIds.add(policy.policyId);
209         }
210         if (policies.size() != uniquePolicyIds.size()) {
211             for (QosPolicyRequest policy : policies) {
212                 rejectQosPolicy(policy.policyId);
213             }
214             return;
215         }
216 
217         if (SdkLevel.isAtLeastT()) {
218             for (QosPolicyRequest policy : policies) {
219                 if (policy.isRemoveRequest()) {
220                     mNetworkAgent.sendRemoveDscpPolicy(policy.policyId);
221                 } else if (policy.isAddRequest()) {
222                     if (!policy.classifierParams.isValid) {
223                         rejectQosPolicy(policy.policyId);
224                         continue;
225                     }
226                     DscpPolicy.Builder builder = new DscpPolicy.Builder(
227                             policy.policyId, policy.dscp)
228                             .setSourcePort(policy.classifierParams.srcPort)
229                             .setProtocol(policy.classifierParams.protocol)
230                             .setDestinationPortRange(policy.classifierParams.dstPortRange);
231 
232                     // Only set src and dest IP if a value exists in classifierParams.
233                     if (policy.classifierParams.hasSrcIp) {
234                         builder.setSourceAddress(policy.classifierParams.srcIp);
235                     }
236                     if (policy.classifierParams.hasDstIp) {
237                         builder.setDestinationAddress(policy.classifierParams.dstIp);
238                     }
239 
240                     try {
241                         mNetworkAgent.sendAddDscpPolicy(builder.build());
242                     } catch (IllegalArgumentException e) {
243                         Log.e(TAG, "Unable to send DSCP policy ", e);
244                         rejectQosPolicy(policy.policyId);
245                         continue;
246                     }
247                 } else {
248                     Log.e(TAG, "Unknown request type received");
249                     rejectQosPolicy(policy.policyId);
250                     continue;
251                 }
252             }
253             mHandler.postDelayed(() -> checkForProcessingStall(dialogToken),
254                     PROCESSING_TIMEOUT_MILLIS);
255         }
256     }
257 }
258