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