1 /*
2  * Copyright (C) 2020 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;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.net.IQosCallback;
22 import android.net.Network;
23 import android.net.QosCallbackException;
24 import android.net.QosFilter;
25 import android.net.QosSession;
26 import android.os.Binder;
27 import android.os.Handler;
28 import android.os.IBinder;
29 import android.telephony.data.EpsBearerQosSessionAttributes;
30 import android.telephony.data.NrQosSessionAttributes;
31 import android.util.Log;
32 
33 import com.android.net.module.util.CollectionUtils;
34 import com.android.server.ConnectivityService;
35 
36 import java.util.ArrayList;
37 import java.util.List;
38 
39 /**
40  * Tracks qos callbacks and handles the communication between the network agent and application.
41  * <p/>
42  * Any method prefixed by handle must be called from the
43  * {@link com.android.server.ConnectivityService} handler thread.
44  *
45  * @hide
46  */
47 public class QosCallbackTracker {
48     private static final String TAG = QosCallbackTracker.class.getSimpleName();
49     private static final boolean DBG = true;
50 
51     @NonNull
52     private final Handler mConnectivityServiceHandler;
53 
54     @NonNull
55     private final ConnectivityService.RequestInfoPerUidCounter mNetworkRequestCounter;
56 
57     /**
58      * Each agent gets a unique callback id that is used to proxy messages back to the original
59      * callback.
60      * <p/>
61      * Note: The fact that this is initialized to 0 is to ensure that the thread running
62      * {@link #handleRegisterCallback(IQosCallback, QosFilter, int, NetworkAgentInfo)} sees the
63      * initialized value. This would not necessarily be the case if the value was initialized to
64      * the non-default value.
65      * <p/>
66      * Note: The term previous does not apply to the first callback id that is assigned.
67      */
68     private int mPreviousAgentCallbackId = 0;
69 
70     @NonNull
71     private final List<QosCallbackAgentConnection> mConnections = new ArrayList<>();
72 
73     /**
74      *
75      * @param connectivityServiceHandler must be the same handler used with
76      *                {@link com.android.server.ConnectivityService}
77      * @param networkRequestCounter keeps track of the number of open requests under a given
78      *                              uid
79      */
QosCallbackTracker(@onNull final Handler connectivityServiceHandler, final ConnectivityService.RequestInfoPerUidCounter networkRequestCounter)80     public QosCallbackTracker(@NonNull final Handler connectivityServiceHandler,
81             final ConnectivityService.RequestInfoPerUidCounter networkRequestCounter) {
82         mConnectivityServiceHandler = connectivityServiceHandler;
83         mNetworkRequestCounter = networkRequestCounter;
84     }
85 
86     /**
87      * Registers the callback with the tracker
88      *
89      * @param callback the callback to register
90      * @param filter the filter being registered alongside the callback
91      */
registerCallback(@onNull final IQosCallback callback, @NonNull final QosFilter filter, @NonNull final NetworkAgentInfo networkAgentInfo)92     public void registerCallback(@NonNull final IQosCallback callback,
93             @NonNull final QosFilter filter, @NonNull final NetworkAgentInfo networkAgentInfo) {
94         final int uid = Binder.getCallingUid();
95 
96         // Enforce that the number of requests under this uid has exceeded the allowed number
97         mNetworkRequestCounter.incrementCountOrThrow(uid);
98 
99         mConnectivityServiceHandler.post(
100                 () -> handleRegisterCallback(callback, filter, uid, networkAgentInfo));
101     }
102 
handleRegisterCallback(@onNull final IQosCallback callback, @NonNull final QosFilter filter, final int uid, @NonNull final NetworkAgentInfo networkAgentInfo)103     private void handleRegisterCallback(@NonNull final IQosCallback callback,
104             @NonNull final QosFilter filter, final int uid,
105             @NonNull final NetworkAgentInfo networkAgentInfo) {
106         final QosCallbackAgentConnection ac =
107                 handleRegisterCallbackInternal(callback, filter, uid, networkAgentInfo);
108         if (ac != null) {
109             if (DBG) log("handleRegisterCallback: added callback " + ac.getAgentCallbackId());
110             mConnections.add(ac);
111         } else {
112             mNetworkRequestCounter.decrementCount(uid);
113         }
114     }
115 
handleRegisterCallbackInternal( @onNull final IQosCallback callback, @NonNull final QosFilter filter, final int uid, @NonNull final NetworkAgentInfo networkAgentInfo)116     private QosCallbackAgentConnection handleRegisterCallbackInternal(
117             @NonNull final IQosCallback callback,
118             @NonNull final QosFilter filter, final int uid,
119             @NonNull final NetworkAgentInfo networkAgentInfo) {
120         final IBinder binder = callback.asBinder();
121         if (CollectionUtils.any(mConnections, c -> c.getBinder().equals(binder))) {
122             // A duplicate registration would have only made this far due to a programming error.
123             logwtf("handleRegisterCallback: Callbacks can only be register once.");
124             return null;
125         }
126 
127         mPreviousAgentCallbackId = mPreviousAgentCallbackId + 1;
128         final int newCallbackId = mPreviousAgentCallbackId;
129 
130         final QosCallbackAgentConnection ac =
131                 new QosCallbackAgentConnection(this, newCallbackId, callback,
132                         filter, uid, networkAgentInfo);
133 
134         final int exceptionType = filter.validate();
135         if (exceptionType != QosCallbackException.EX_TYPE_FILTER_NONE) {
136             ac.sendEventQosCallbackError(exceptionType);
137             return null;
138         }
139 
140         // Only add to the callback maps if the NetworkAgent successfully registered it
141         if (!ac.sendCmdRegisterCallback()) {
142             // There was an issue when registering the agent
143             if (DBG) log("handleRegisterCallback: error sending register callback");
144             mNetworkRequestCounter.decrementCount(uid);
145             return null;
146         }
147         return ac;
148     }
149 
150     /**
151      * Unregisters callback
152      * @param callback callback to unregister
153      */
unregisterCallback(@onNull final IQosCallback callback)154     public void unregisterCallback(@NonNull final IQosCallback callback) {
155         mConnectivityServiceHandler.post(() -> handleUnregisterCallback(callback.asBinder(), true));
156     }
157 
handleUnregisterCallback(@onNull final IBinder binder, final boolean sendToNetworkAgent)158     private void handleUnregisterCallback(@NonNull final IBinder binder,
159             final boolean sendToNetworkAgent) {
160         final int connIndex =
161                 CollectionUtils.indexOf(mConnections, c -> c.getBinder().equals(binder));
162         if (connIndex < 0) {
163             logw("handleUnregisterCallback: no matching agentConnection");
164             return;
165         }
166         final QosCallbackAgentConnection agentConnection = mConnections.get(connIndex);
167 
168         if (DBG) {
169             log("handleUnregisterCallback: unregister "
170                     + agentConnection.getAgentCallbackId());
171         }
172 
173         mNetworkRequestCounter.decrementCount(agentConnection.getUid());
174         mConnections.remove(agentConnection);
175 
176         if (sendToNetworkAgent) {
177             agentConnection.sendCmdUnregisterCallback();
178         }
179         agentConnection.unlinkToDeathRecipient();
180     }
181 
182     /**
183      * Called when the NetworkAgent sends the qos session available event for EPS
184      *
185      * @param qosCallbackId the callback id that the qos session is now available to
186      * @param session the qos session that is now available
187      * @param attributes the qos attributes that are now available on the qos session
188      */
sendEventEpsQosSessionAvailable(final int qosCallbackId, final QosSession session, final EpsBearerQosSessionAttributes attributes)189     public void sendEventEpsQosSessionAvailable(final int qosCallbackId,
190             final QosSession session,
191             final EpsBearerQosSessionAttributes attributes) {
192         runOnAgentConnection(qosCallbackId, "sendEventEpsQosSessionAvailable: ",
193                 ac -> ac.sendEventEpsQosSessionAvailable(session, attributes));
194     }
195 
196     /**
197      * Called when the NetworkAgent sends the qos session available event for NR
198      *
199      * @param qosCallbackId the callback id that the qos session is now available to
200      * @param session the qos session that is now available
201      * @param attributes the qos attributes that are now available on the qos session
202      */
sendEventNrQosSessionAvailable(final int qosCallbackId, final QosSession session, final NrQosSessionAttributes attributes)203     public void sendEventNrQosSessionAvailable(final int qosCallbackId,
204             final QosSession session,
205             final NrQosSessionAttributes attributes) {
206         runOnAgentConnection(qosCallbackId, "sendEventNrQosSessionAvailable: ",
207                 ac -> ac.sendEventNrQosSessionAvailable(session, attributes));
208     }
209 
210     /**
211      * Called when the NetworkAgent sends the qos session lost event
212      *
213      * @param qosCallbackId the callback id that lost the qos session
214      * @param session the corresponding qos session
215      */
sendEventQosSessionLost(final int qosCallbackId, final QosSession session)216     public void sendEventQosSessionLost(final int qosCallbackId,
217             final QosSession session) {
218         runOnAgentConnection(qosCallbackId, "sendEventQosSessionLost: ",
219                 ac -> ac.sendEventQosSessionLost(session));
220     }
221 
222     /**
223      * Called when the NetworkAgent sends the qos session on error event
224      *
225      * @param qosCallbackId the callback id that should receive the exception
226      * @param exceptionType the type of exception that caused the callback to error
227      */
sendEventQosCallbackError(final int qosCallbackId, @QosCallbackException.ExceptionType final int exceptionType)228     public void sendEventQosCallbackError(final int qosCallbackId,
229             @QosCallbackException.ExceptionType final int exceptionType) {
230         runOnAgentConnection(qosCallbackId, "sendEventQosCallbackError: ",
231                 ac -> {
232                     ac.sendEventQosCallbackError(exceptionType);
233                     handleUnregisterCallback(ac.getBinder(), false);
234                 });
235     }
236 
237     /**
238      * Unregisters all callbacks associated to this network agent
239      *
240      * Note: Must be called on the connectivity service handler thread
241      *
242      * @param network the network that was released
243      */
handleNetworkReleased(@ullable final Network network)244     public void handleNetworkReleased(@Nullable final Network network) {
245         // Iterate in reverse order as agent connections will be removed when unregistering
246         for (int i = mConnections.size() - 1; i >= 0; i--) {
247             final QosCallbackAgentConnection agentConnection = mConnections.get(i);
248             if (!agentConnection.getNetwork().equals(network)) continue;
249             agentConnection.sendEventQosCallbackError(
250                     QosCallbackException.EX_TYPE_FILTER_NETWORK_RELEASED);
251 
252             // Call unregister workflow w\o sending anything to agent since it is disconnected.
253             handleUnregisterCallback(agentConnection.getBinder(), false);
254         }
255     }
256 
257     private interface AgentConnectionAction {
execute(@onNull QosCallbackAgentConnection agentConnection)258         void execute(@NonNull QosCallbackAgentConnection agentConnection);
259     }
260 
261     @Nullable
runOnAgentConnection(final int qosCallbackId, @NonNull final String logPrefix, @NonNull final AgentConnectionAction action)262     private void runOnAgentConnection(final int qosCallbackId,
263             @NonNull final String logPrefix,
264             @NonNull final AgentConnectionAction action) {
265         mConnectivityServiceHandler.post(() -> {
266             final int acIndex = CollectionUtils.indexOf(mConnections,
267                             c -> c.getAgentCallbackId() == qosCallbackId);
268             if (acIndex == -1) {
269                 loge(logPrefix + ": " + qosCallbackId + " missing callback id");
270                 return;
271             }
272 
273             action.execute(mConnections.get(acIndex));
274         });
275     }
276 
log(final String msg)277     private static void log(final String msg) {
278         Log.d(TAG, msg);
279     }
280 
logw(final String msg)281     private static void logw(final String msg) {
282         Log.w(TAG, msg);
283     }
284 
loge(final String msg)285     private static void loge(final String msg) {
286         Log.e(TAG, msg);
287     }
288 
logwtf(final String msg)289     private static void logwtf(final String msg) {
290         Log.wtf(TAG, msg);
291     }
292 }
293