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