/* * 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 mQosPolicyStatusList = new ArrayList<>(); private List>> 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.statusCode); } pw.println("mQosPolicyRequestQueue size: " + mQosPolicyRequestQueue.size()); for (Pair> 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 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> 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 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 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); } } }