blob: d6180983e3e93a47a1215e016703c9f582b54917 [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.wifi;
import android.annotation.NonNull;
import android.net.DscpPolicy;
import android.net.NetworkAgent;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
import android.util.Pair;
import com.android.internal.annotations.VisibleForTesting;
import com.android.modules.utils.build.SdkLevel;
import com.android.server.wifi.SupplicantStaIfaceHal.QosPolicyRequest;
import com.android.server.wifi.SupplicantStaIfaceHal.QosPolicyStatus;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/*
* Handler for QoS policy requests.
*/
public class QosPolicyRequestHandler {
private static final String TAG = "QosPolicyRequestHandler";
@VisibleForTesting
public static final int PROCESSING_TIMEOUT_MILLIS = 500;
private final String mInterfaceName;
private final WifiNative mWifiNative;
private final ClientModeImpl mClientModeImpl;
private WifiNetworkAgent mNetworkAgent;
private Handler mHandler;
private boolean mVerboseLoggingEnabled;
private int mQosRequestDialogToken;
private int mNumQosPoliciesInRequest;
private boolean mQosResourcesAvailable;
private boolean mQosRequestIsProcessing = false;
private List<QosPolicyStatus> mQosPolicyStatusList = new ArrayList<>();
private List<Pair<Integer, List<QosPolicyRequest>>> mQosPolicyRequestQueue = new ArrayList<>();
public QosPolicyRequestHandler(
@NonNull String ifaceName, @NonNull WifiNative wifiNative,
@NonNull ClientModeImpl clientModeImpl, @NonNull HandlerThread handlerThread) {
mInterfaceName = ifaceName;
mWifiNative = wifiNative;
mClientModeImpl = clientModeImpl;
mHandler = new Handler(handlerThread.getLooper());
}
/**
* Enable/disable verbose logging.
*/
public void enableVerboseLogging(boolean verbose) {
mVerboseLoggingEnabled = verbose;
}
/**
* Dump internal state regarding the policy request queue, and the request which is
* currently being processed.
*/
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("mQosRequestDialogToken: " + mQosRequestDialogToken);
pw.println("mNumQosPoliciesInRequest: " + mNumQosPoliciesInRequest);
pw.println("mQosResourcesAvailable: " + mQosResourcesAvailable);
pw.println("mQosPolicyStatusList size: " + mQosPolicyStatusList.size());
for (QosPolicyStatus status : mQosPolicyStatusList) {
pw.println(" Policy id: " + status.policyId + ", status: "
+ status.dscpPolicyStatus);
}
pw.println("mQosPolicyRequestQueue size: " + mQosPolicyRequestQueue.size());
for (Pair<Integer, List<QosPolicyRequest>> request : mQosPolicyRequestQueue) {
pw.println(" Dialog token: " + request.first
+ ", Num policies: " + request.second.size());
for (QosPolicyRequest policy : request.second) {
pw.println(" " + policy);
}
}
}
/**
* Set the network agent.
*/
public void setNetworkAgent(WifiNetworkAgent wifiNetworkAgent) {
WifiNetworkAgent oldNetworkAgent = mNetworkAgent;
mNetworkAgent = wifiNetworkAgent;
if (mNetworkAgent == null) {
mQosPolicyStatusList.clear();
mQosPolicyRequestQueue.clear();
mQosRequestIsProcessing = false;
} else if (oldNetworkAgent != null) {
// Existing network agent was replaced by a new one.
resetProcessingState();
}
}
/**
* Queue a QoS policy request to be processed.
* @param dialogToken Token identifying the request.
* @param policies List of policies that we are requesting to set.
*/
public void queueQosPolicyRequest(int dialogToken, List<QosPolicyRequest> policies) {
if (mNetworkAgent == null) {
Log.e(TAG, "Attempted to call queueQosPolicyRequest, but mNetworkAgent is null");
return;
}
mQosPolicyRequestQueue.add(new Pair(dialogToken, policies));
processNextQosPolicyRequestIfPossible();
}
/**
* Set the status for a policy which was processed.
* @param policyId ID of the policy.
* @param status code received from the NetworkAgent.
*/
public void setQosPolicyStatus(int policyId, int status) {
if (mNetworkAgent == null) {
Log.e(TAG, "Attempted to call setQosPolicyStatus, but mNetworkAgent is null");
return;
}
mQosPolicyStatusList.add(new QosPolicyStatus(policyId, status));
if (status == NetworkAgent.DSCP_POLICY_STATUS_INSUFFICIENT_PROCESSING_RESOURCES) {
mQosResourcesAvailable = false;
}
sendQosPolicyResponseIfReady();
}
private void rejectQosPolicy(int policyId) {
mQosPolicyStatusList.add(new QosPolicyStatus(
policyId, NetworkAgent.DSCP_POLICY_STATUS_REQUEST_DECLINED));
sendQosPolicyResponseIfReady();
}
private void sendQosPolicyResponseIfReady() {
if (mQosRequestIsProcessing && mQosPolicyStatusList.size() == mNumQosPoliciesInRequest) {
mWifiNative.sendQosPolicyResponse(mInterfaceName, mQosRequestDialogToken,
mQosResourcesAvailable, mQosPolicyStatusList);
mQosRequestIsProcessing = false;
mHandler.post(() -> processNextQosPolicyRequestIfPossible());
}
}
private void processNextQosPolicyRequestIfPossible() {
if (!mQosRequestIsProcessing && mQosPolicyRequestQueue.size() != 0) {
Pair<Integer, List<QosPolicyRequest>> nextRequest = mQosPolicyRequestQueue.get(0);
mQosPolicyRequestQueue.remove(0);
mQosRequestIsProcessing = true;
processQosPolicyRequest(nextRequest.first, nextRequest.second);
}
}
private void checkForProcessingStall(int dialogToken) {
if (mQosRequestIsProcessing && dialogToken == mQosRequestDialogToken) {
Log.e(TAG, "Stop processing stalled QoS request " + dialogToken);
resetProcessingState();
}
}
private void resetProcessingState() {
mQosRequestIsProcessing = false;
mQosPolicyRequestQueue.clear();
mClientModeImpl.clearQueuedQosMessages();
mWifiNative.removeAllQosPolicies(mInterfaceName);
if (mNetworkAgent != null) {
mNetworkAgent.sendRemoveAllDscpPolicies();
}
}
private void processQosPolicyRequest(int dialogToken, List<QosPolicyRequest> policies) {
if (mNetworkAgent == null) {
Log.e(TAG, "Attempted to call processQosPolicyRequest, but mNetworkAgent is null");
return;
}
mQosRequestDialogToken = dialogToken;
mQosResourcesAvailable = true;
mNumQosPoliciesInRequest = policies.size();
mQosPolicyStatusList.clear();
if (policies.size() == 0) {
sendQosPolicyResponseIfReady();
return;
}
// Reject entire batch if any duplicate policy id's exist.
Set<Byte> uniquePolicyIds = new HashSet<>();
for (QosPolicyRequest policy : policies) {
uniquePolicyIds.add(policy.policyId);
}
if (policies.size() != uniquePolicyIds.size()) {
for (QosPolicyRequest policy : policies) {
rejectQosPolicy(policy.policyId);
}
return;
}
if (SdkLevel.isAtLeastT()) {
for (QosPolicyRequest policy : policies) {
if (policy.isRemoveRequest()) {
mNetworkAgent.sendRemoveDscpPolicy(policy.policyId);
} else if (policy.isAddRequest()) {
if (!policy.classifierParams.isValid) {
rejectQosPolicy(policy.policyId);
continue;
}
DscpPolicy.Builder builder = new DscpPolicy.Builder(
policy.policyId, policy.dscp)
.setSourcePort(policy.classifierParams.srcPort)
.setProtocol(policy.classifierParams.protocol)
.setDestinationPortRange(policy.classifierParams.dstPortRange);
// Only set src and dest IP if a value exists in classifierParams.
if (policy.classifierParams.hasSrcIp) {
builder.setSourceAddress(policy.classifierParams.srcIp);
}
if (policy.classifierParams.hasDstIp) {
builder.setDestinationAddress(policy.classifierParams.dstIp);
}
try {
mNetworkAgent.sendAddDscpPolicy(builder.build());
} catch (IllegalArgumentException e) {
Log.e(TAG, "Unable to send DSCP policy ", e);
rejectQosPolicy(policy.policyId);
continue;
}
} else {
Log.e(TAG, "Unknown request type received");
rejectQosPolicy(policy.policyId);
continue;
}
}
mHandler.postDelayed(() -> checkForProcessingStall(dialogToken),
PROCESSING_TIMEOUT_MILLIS);
}
}
}