1 /*
2  * Copyright (C) 2021 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 static android.net.NetworkAgent.DSCP_POLICY_STATUS_DELETED;
20 import static android.net.NetworkAgent.DSCP_POLICY_STATUS_INSUFFICIENT_PROCESSING_RESOURCES;
21 import static android.net.NetworkAgent.DSCP_POLICY_STATUS_POLICY_NOT_FOUND;
22 import static android.net.NetworkAgent.DSCP_POLICY_STATUS_REQUEST_DECLINED;
23 import static android.net.NetworkAgent.DSCP_POLICY_STATUS_SUCCESS;
24 import static android.system.OsConstants.ETH_P_ALL;
25 
26 import android.annotation.NonNull;
27 import android.net.DscpPolicy;
28 import android.os.RemoteException;
29 import android.system.ErrnoException;
30 import android.util.Log;
31 import android.util.SparseIntArray;
32 
33 import com.android.net.module.util.BpfMap;
34 import com.android.net.module.util.Struct;
35 import com.android.net.module.util.TcUtils;
36 
37 import java.io.IOException;
38 import java.net.Inet4Address;
39 import java.net.Inet6Address;
40 import java.net.NetworkInterface;
41 import java.util.HashMap;
42 import java.util.HashSet;
43 import java.util.Set;
44 
45 /**
46  * DscpPolicyTracker has a single entry point from ConnectivityService handler.
47  * This guarantees that all code runs on the same thread and no locking is needed.
48  */
49 public class DscpPolicyTracker {
50     // After tethering and clat priorities.
51     static final short PRIO_DSCP = 5;
52 
53     private static final String TAG = DscpPolicyTracker.class.getSimpleName();
54     private static final String PROG_PATH =
55             "/sys/fs/bpf/net_shared/prog_dscpPolicy_schedcls_set_dscp_ether";
56     // Name is "map + *.o + map_name + map". Can probably shorten this
57     private static final String IPV4_POLICY_MAP_PATH = makeMapPath(
58             "dscpPolicy_ipv4_dscp_policies");
59     private static final String IPV6_POLICY_MAP_PATH = makeMapPath(
60             "dscpPolicy_ipv6_dscp_policies");
61     private static final int MAX_POLICIES = 16;
62 
makeMapPath(String which)63     private static String makeMapPath(String which) {
64         return "/sys/fs/bpf/net_shared/map_" + which + "_map";
65     }
66 
67     private final boolean mHaveProgram = TcUtils.isBpfProgramUsable(PROG_PATH);
68 
69     private Set<String> mAttachedIfaces;
70 
71     private final BpfMap<Struct.S32, DscpPolicyValue> mBpfDscpIpv4Policies;
72     private final BpfMap<Struct.S32, DscpPolicyValue> mBpfDscpIpv6Policies;
73 
74     // The actual policy rules used by the BPF code to process packets
75     // are in mBpfDscpIpv4Policies and mBpfDscpIpv4Policies. Both of
76     // these can contain up to MAX_POLICIES rules.
77     //
78     // A given policy always consumes one entry in both the IPv4 and
79     // IPv6 maps even if if's an IPv4-only or IPv6-only policy.
80     //
81     // Each interface index has a SparseIntArray of rules which maps a
82     // policy ID to the index of the corresponding rule in the maps.
83     // mIfaceIndexToPolicyIdBpfMapIndex maps the interface index to
84     // the per-interface SparseIntArray.
85     private final HashMap<Integer, SparseIntArray> mIfaceIndexToPolicyIdBpfMapIndex;
86 
DscpPolicyTracker()87     public DscpPolicyTracker() throws ErrnoException {
88         mAttachedIfaces = new HashSet<String>();
89         mIfaceIndexToPolicyIdBpfMapIndex = new HashMap<Integer, SparseIntArray>();
90         mBpfDscpIpv4Policies = new BpfMap<>(IPV4_POLICY_MAP_PATH,
91                 Struct.S32.class, DscpPolicyValue.class);
92         mBpfDscpIpv6Policies = new BpfMap<>(IPV6_POLICY_MAP_PATH,
93                 Struct.S32.class, DscpPolicyValue.class);
94     }
95 
isUnusedIndex(int index)96     private boolean isUnusedIndex(int index) {
97         for (SparseIntArray ifacePolicies : mIfaceIndexToPolicyIdBpfMapIndex.values()) {
98             if (ifacePolicies.indexOfValue(index) >= 0) return false;
99         }
100         return true;
101     }
102 
getFirstFreeIndex()103     private int getFirstFreeIndex() {
104         if (mIfaceIndexToPolicyIdBpfMapIndex.size() == 0) return 0;
105         for (int i = 0; i < MAX_POLICIES; i++) {
106             if (isUnusedIndex(i)) {
107                 return i;
108             }
109         }
110         return MAX_POLICIES;
111     }
112 
findIndex(int policyId, int ifIndex)113     private int findIndex(int policyId, int ifIndex) {
114         SparseIntArray ifacePolicies = mIfaceIndexToPolicyIdBpfMapIndex.get(ifIndex);
115         if (ifacePolicies != null) {
116             final int existingIndex = ifacePolicies.get(policyId, -1);
117             if (existingIndex != -1) {
118                 return existingIndex;
119             }
120         }
121 
122         final int firstIndex = getFirstFreeIndex();
123         if (firstIndex >= MAX_POLICIES) {
124             // New policy is being added, but max policies has already been reached.
125             return -1;
126         }
127         return firstIndex;
128     }
129 
sendStatus(NetworkAgentInfo nai, int policyId, int status)130     private void sendStatus(NetworkAgentInfo nai, int policyId, int status) {
131         try {
132             nai.networkAgent.onDscpPolicyStatusUpdated(policyId, status);
133         } catch (RemoteException e) {
134             Log.e(TAG, "Failed update policy status: ", e);
135         }
136     }
137 
matchesIpv4(DscpPolicy policy)138     private boolean matchesIpv4(DscpPolicy policy) {
139         return ((policy.getDestinationAddress() == null
140                        || policy.getDestinationAddress() instanceof Inet4Address)
141             && (policy.getSourceAddress() == null
142                         || policy.getSourceAddress() instanceof Inet4Address));
143     }
144 
matchesIpv6(DscpPolicy policy)145     private boolean matchesIpv6(DscpPolicy policy) {
146         return ((policy.getDestinationAddress() == null
147                        || policy.getDestinationAddress() instanceof Inet6Address)
148             && (policy.getSourceAddress() == null
149                         || policy.getSourceAddress() instanceof Inet6Address));
150     }
151 
getIfaceIndex(NetworkAgentInfo nai)152     private int getIfaceIndex(NetworkAgentInfo nai) {
153         String iface = nai.linkProperties.getInterfaceName();
154         NetworkInterface netIface;
155         try {
156             netIface = NetworkInterface.getByName(iface);
157         } catch (IOException e) {
158             Log.e(TAG, "Unable to get iface index for " + iface + ": " + e);
159             netIface = null;
160         }
161         return (netIface != null) ? netIface.getIndex() : 0;
162     }
163 
addDscpPolicyInternal(DscpPolicy policy, int ifIndex)164     private int addDscpPolicyInternal(DscpPolicy policy, int ifIndex) {
165         // If there is no existing policy with a matching ID, and we are already at
166         // the maximum number of policies then return INSUFFICIENT_PROCESSING_RESOURCES.
167         SparseIntArray ifacePolicies = mIfaceIndexToPolicyIdBpfMapIndex.get(ifIndex);
168         if (ifacePolicies == null) {
169             ifacePolicies = new SparseIntArray(MAX_POLICIES);
170         }
171 
172         // Currently all classifiers are supported, if any are removed return
173         // DSCP_POLICY_STATUS_REQUESTED_CLASSIFIER_NOT_SUPPORTED,
174         // and for any other generic error DSCP_POLICY_STATUS_REQUEST_DECLINED
175 
176         final int addIndex = findIndex(policy.getPolicyId(), ifIndex);
177         if (addIndex == -1) {
178             return DSCP_POLICY_STATUS_INSUFFICIENT_PROCESSING_RESOURCES;
179         }
180 
181         try {
182             // Add v4 policy to mBpfDscpIpv4Policies if source and destination address
183             // are both null or if they are both instances of Inet4Address.
184             if (matchesIpv4(policy)) {
185                 mBpfDscpIpv4Policies.insertOrReplaceEntry(
186                         new Struct.S32(addIndex),
187                         new DscpPolicyValue(policy.getSourceAddress(),
188                             policy.getDestinationAddress(), ifIndex,
189                             policy.getSourcePort(), policy.getDestinationPortRange(),
190                             (short) policy.getProtocol(), (byte) policy.getDscpValue()));
191             }
192 
193             // Add v6 policy to mBpfDscpIpv6Policies if source and destination address
194             // are both null or if they are both instances of Inet6Address.
195             if (matchesIpv6(policy)) {
196                 mBpfDscpIpv6Policies.insertOrReplaceEntry(
197                         new Struct.S32(addIndex),
198                         new DscpPolicyValue(policy.getSourceAddress(),
199                                 policy.getDestinationAddress(), ifIndex,
200                                 policy.getSourcePort(), policy.getDestinationPortRange(),
201                                 (short) policy.getProtocol(), (byte) policy.getDscpValue()));
202             }
203 
204             ifacePolicies.put(policy.getPolicyId(), addIndex);
205             // Only add the policy to the per interface map if the policy was successfully
206             // added to both bpf maps above. It is safe to assume that if insert fails for
207             // one map then it fails for both.
208             mIfaceIndexToPolicyIdBpfMapIndex.put(ifIndex, ifacePolicies);
209         } catch (ErrnoException e) {
210             Log.e(TAG, "Failed to insert policy into map: ", e);
211             return DSCP_POLICY_STATUS_INSUFFICIENT_PROCESSING_RESOURCES;
212         }
213 
214         return DSCP_POLICY_STATUS_SUCCESS;
215     }
216 
isEthernet(String iface)217     private boolean isEthernet(String iface) {
218         try {
219             return TcUtils.isEthernet(iface);
220         } catch (IOException e) {
221             Log.e(TAG, "Failed to check ether type", e);
222         }
223         return false;
224     }
225 
226     /**
227      * Add the provided DSCP policy to the bpf map. Attach bpf program dscpPolicy to iface
228      * if not already attached. Response will be sent back to nai with status.
229      *
230      * DSCP_POLICY_STATUS_SUCCESS - if policy was added successfully
231      * DSCP_POLICY_STATUS_INSUFFICIENT_PROCESSING_RESOURCES - if max policies were already set
232      * DSCP_POLICY_STATUS_REQUEST_DECLINED - Interface index was invalid
233      */
addDscpPolicy(NetworkAgentInfo nai, DscpPolicy policy)234     public void addDscpPolicy(NetworkAgentInfo nai, DscpPolicy policy) {
235         String iface = nai.linkProperties.getInterfaceName();
236         if (!isEthernet(iface)) {
237             Log.e(TAG, "DSCP policies are not supported on raw IP interfaces.");
238             sendStatus(nai, policy.getPolicyId(), DSCP_POLICY_STATUS_REQUEST_DECLINED);
239             return;
240         }
241         if (!mAttachedIfaces.contains(iface) && !attachProgram(iface)) {
242             Log.e(TAG, "Unable to attach program");
243             sendStatus(nai, policy.getPolicyId(),
244                     DSCP_POLICY_STATUS_INSUFFICIENT_PROCESSING_RESOURCES);
245             return;
246         }
247 
248         final int ifIndex = getIfaceIndex(nai);
249         if (ifIndex == 0) {
250             Log.e(TAG, "Iface index is invalid");
251             sendStatus(nai, policy.getPolicyId(), DSCP_POLICY_STATUS_REQUEST_DECLINED);
252             return;
253         }
254 
255         int status = addDscpPolicyInternal(policy, ifIndex);
256         sendStatus(nai, policy.getPolicyId(), status);
257     }
258 
removePolicyFromMap(NetworkAgentInfo nai, int policyId, int index, boolean sendCallback)259     private void removePolicyFromMap(NetworkAgentInfo nai, int policyId, int index,
260             boolean sendCallback) {
261         int status = DSCP_POLICY_STATUS_POLICY_NOT_FOUND;
262         try {
263             mBpfDscpIpv4Policies.replaceEntry(new Struct.S32(index), DscpPolicyValue.NONE);
264             mBpfDscpIpv6Policies.replaceEntry(new Struct.S32(index), DscpPolicyValue.NONE);
265             status = DSCP_POLICY_STATUS_DELETED;
266         } catch (ErrnoException e) {
267             Log.e(TAG, "Failed to delete policy from map: ", e);
268         }
269 
270         if (sendCallback) {
271             sendStatus(nai, policyId, status);
272         }
273     }
274 
275     /**
276      * Remove specified DSCP policy and detach program if no other policies are active.
277      */
removeDscpPolicy(NetworkAgentInfo nai, int policyId)278     public void removeDscpPolicy(NetworkAgentInfo nai, int policyId) {
279         if (!mAttachedIfaces.contains(nai.linkProperties.getInterfaceName())) {
280             // Nothing to remove since program is not attached. Send update back for policy id.
281             sendStatus(nai, policyId, DSCP_POLICY_STATUS_POLICY_NOT_FOUND);
282             return;
283         }
284 
285         SparseIntArray ifacePolicies = mIfaceIndexToPolicyIdBpfMapIndex.get(getIfaceIndex(nai));
286         if (ifacePolicies == null) return;
287 
288         final int existingIndex = ifacePolicies.get(policyId, -1);
289         if (existingIndex == -1) {
290             Log.e(TAG, "Policy " + policyId + " does not exist in map.");
291             sendStatus(nai, policyId, DSCP_POLICY_STATUS_POLICY_NOT_FOUND);
292             return;
293         }
294 
295         removePolicyFromMap(nai, policyId, existingIndex, true);
296         ifacePolicies.delete(policyId);
297 
298         if (ifacePolicies.size() == 0) {
299             detachProgram(nai.linkProperties.getInterfaceName());
300         }
301     }
302 
303     /**
304      * Remove all DSCP policies and detach program. Send callback if requested.
305      */
removeAllDscpPolicies(NetworkAgentInfo nai, boolean sendCallback)306     public void removeAllDscpPolicies(NetworkAgentInfo nai, boolean sendCallback) {
307         if (!mAttachedIfaces.contains(nai.linkProperties.getInterfaceName())) {
308             // Nothing to remove since program is not attached. Send update for policy
309             // id 0. The status update must contain a policy ID, and 0 is an invalid id.
310             if (sendCallback) {
311                 sendStatus(nai, 0, DSCP_POLICY_STATUS_SUCCESS);
312             }
313             return;
314         }
315 
316         SparseIntArray ifacePolicies = mIfaceIndexToPolicyIdBpfMapIndex.get(getIfaceIndex(nai));
317         if (ifacePolicies == null) return;
318         for (int i = 0; i < ifacePolicies.size(); i++) {
319             removePolicyFromMap(nai, ifacePolicies.keyAt(i), ifacePolicies.valueAt(i),
320                     sendCallback);
321         }
322         ifacePolicies.clear();
323         detachProgram(nai.linkProperties.getInterfaceName());
324     }
325 
326     /**
327      * Attach BPF program
328      */
attachProgram(@onNull String iface)329     private boolean attachProgram(@NonNull String iface) {
330         if (!mHaveProgram) return false;
331         try {
332             NetworkInterface netIface = NetworkInterface.getByName(iface);
333             TcUtils.tcFilterAddDevBpf(netIface.getIndex(), false, PRIO_DSCP, (short) ETH_P_ALL,
334                     PROG_PATH);
335         } catch (IOException e) {
336             Log.e(TAG, "Unable to attach to TC on " + iface + ": " + e);
337             return false;
338         }
339         mAttachedIfaces.add(iface);
340         return true;
341     }
342 
343     /**
344      * Detach BPF program
345      */
detachProgram(@onNull String iface)346     public void detachProgram(@NonNull String iface) {
347         try {
348             NetworkInterface netIface = NetworkInterface.getByName(iface);
349             if (netIface != null) {
350                 TcUtils.tcFilterDelDev(netIface.getIndex(), false, PRIO_DSCP, (short) ETH_P_ALL);
351             }
352             mAttachedIfaces.remove(iface);
353         } catch (IOException e) {
354             Log.e(TAG, "Unable to detach to TC on " + iface + ": " + e);
355         }
356     }
357 }
358