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