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_dscp_policy_schedcls_set_dscp"; 56 // Name is "map + *.o + map_name + map". Can probably shorten this 57 private static final String IPV4_POLICY_MAP_PATH = makeMapPath( 58 "dscp_policy_ipv4_dscp_policies"); 59 private static final String IPV6_POLICY_MAP_PATH = makeMapPath( 60 "dscp_policy_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.U32, DscpPolicyValue> mBpfDscpIpv4Policies; 70 private final BpfMap<Struct.U32, 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.U32, DscpPolicyValue>(IPV4_POLICY_MAP_PATH, 89 BpfMap.BPF_F_RDWR, Struct.U32.class, DscpPolicyValue.class); 90 mBpfDscpIpv6Policies = new BpfMap<Struct.U32, DscpPolicyValue>(IPV6_POLICY_MAP_PATH, 91 BpfMap.BPF_F_RDWR, Struct.U32.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.U32(addIndex), 185 new DscpPolicyValue(policy.getSourceAddress(), 186 policy.getDestinationAddress(), ifIndex, 187 policy.getSourcePort(), policy.getDestinationPortRange(), 188 (short) policy.getProtocol(), (short) 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.U32(addIndex), 196 new DscpPolicyValue(policy.getSourceAddress(), 197 policy.getDestinationAddress(), ifIndex, 198 policy.getSourcePort(), policy.getDestinationPortRange(), 199 (short) policy.getProtocol(), (short) 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 215 /** 216 * Add the provided DSCP policy to the bpf map. Attach bpf program dscp_policy to iface 217 * if not already attached. Response will be sent back to nai with status. 218 * 219 * DSCP_POLICY_STATUS_SUCCESS - if policy was added successfully 220 * DSCP_POLICY_STATUS_INSUFFICIENT_PROCESSING_RESOURCES - if max policies were already set 221 * DSCP_POLICY_STATUS_REQUEST_DECLINED - Interface index was invalid 222 */ addDscpPolicy(NetworkAgentInfo nai, DscpPolicy policy)223 public void addDscpPolicy(NetworkAgentInfo nai, DscpPolicy policy) { 224 if (!mAttachedIfaces.contains(nai.linkProperties.getInterfaceName())) { 225 if (!attachProgram(nai.linkProperties.getInterfaceName())) { 226 Log.e(TAG, "Unable to attach program"); 227 sendStatus(nai, policy.getPolicyId(), 228 DSCP_POLICY_STATUS_INSUFFICIENT_PROCESSING_RESOURCES); 229 return; 230 } 231 } 232 233 final int ifIndex = getIfaceIndex(nai); 234 if (ifIndex == 0) { 235 Log.e(TAG, "Iface index is invalid"); 236 sendStatus(nai, policy.getPolicyId(), DSCP_POLICY_STATUS_REQUEST_DECLINED); 237 return; 238 } 239 240 int status = addDscpPolicyInternal(policy, ifIndex); 241 sendStatus(nai, policy.getPolicyId(), status); 242 } 243 removePolicyFromMap(NetworkAgentInfo nai, int policyId, int index, boolean sendCallback)244 private void removePolicyFromMap(NetworkAgentInfo nai, int policyId, int index, 245 boolean sendCallback) { 246 int status = DSCP_POLICY_STATUS_POLICY_NOT_FOUND; 247 try { 248 mBpfDscpIpv4Policies.replaceEntry(new Struct.U32(index), DscpPolicyValue.NONE); 249 mBpfDscpIpv6Policies.replaceEntry(new Struct.U32(index), DscpPolicyValue.NONE); 250 status = DSCP_POLICY_STATUS_DELETED; 251 } catch (ErrnoException e) { 252 Log.e(TAG, "Failed to delete policy from map: ", e); 253 } 254 255 if (sendCallback) { 256 sendStatus(nai, policyId, status); 257 } 258 } 259 260 /** 261 * Remove specified DSCP policy and detach program if no other policies are active. 262 */ removeDscpPolicy(NetworkAgentInfo nai, int policyId)263 public void removeDscpPolicy(NetworkAgentInfo nai, int policyId) { 264 if (!mAttachedIfaces.contains(nai.linkProperties.getInterfaceName())) { 265 // Nothing to remove since program is not attached. Send update back for policy id. 266 sendStatus(nai, policyId, DSCP_POLICY_STATUS_POLICY_NOT_FOUND); 267 return; 268 } 269 270 SparseIntArray ifacePolicies = mIfaceIndexToPolicyIdBpfMapIndex.get(getIfaceIndex(nai)); 271 if (ifacePolicies == null) return; 272 273 final int existingIndex = ifacePolicies.get(policyId, -1); 274 if (existingIndex == -1) { 275 Log.e(TAG, "Policy " + policyId + " does not exist in map."); 276 sendStatus(nai, policyId, DSCP_POLICY_STATUS_POLICY_NOT_FOUND); 277 return; 278 } 279 280 removePolicyFromMap(nai, policyId, existingIndex, true); 281 ifacePolicies.delete(policyId); 282 283 if (ifacePolicies.size() == 0) { 284 detachProgram(nai.linkProperties.getInterfaceName()); 285 } 286 } 287 288 /** 289 * Remove all DSCP policies and detach program. Send callback if requested. 290 */ removeAllDscpPolicies(NetworkAgentInfo nai, boolean sendCallback)291 public void removeAllDscpPolicies(NetworkAgentInfo nai, boolean sendCallback) { 292 if (!mAttachedIfaces.contains(nai.linkProperties.getInterfaceName())) { 293 // Nothing to remove since program is not attached. Send update for policy 294 // id 0. The status update must contain a policy ID, and 0 is an invalid id. 295 if (sendCallback) { 296 sendStatus(nai, 0, DSCP_POLICY_STATUS_SUCCESS); 297 } 298 return; 299 } 300 301 SparseIntArray ifacePolicies = mIfaceIndexToPolicyIdBpfMapIndex.get(getIfaceIndex(nai)); 302 if (ifacePolicies == null) return; 303 for (int i = 0; i < ifacePolicies.size(); i++) { 304 removePolicyFromMap(nai, ifacePolicies.keyAt(i), ifacePolicies.valueAt(i), 305 sendCallback); 306 } 307 ifacePolicies.clear(); 308 detachProgram(nai.linkProperties.getInterfaceName()); 309 } 310 311 /** 312 * Attach BPF program 313 */ attachProgram(@onNull String iface)314 private boolean attachProgram(@NonNull String iface) { 315 try { 316 NetworkInterface netIface = NetworkInterface.getByName(iface); 317 boolean isEth = TcUtils.isEthernet(iface); 318 String path = PROG_PATH + (isEth ? "_ether" : "_raw_ip"); 319 TcUtils.tcFilterAddDevBpf(netIface.getIndex(), false, PRIO_DSCP, (short) ETH_P_ALL, 320 path); 321 } catch (IOException e) { 322 Log.e(TAG, "Unable to attach to TC on " + iface + ": " + e); 323 return false; 324 } 325 mAttachedIfaces.add(iface); 326 return true; 327 } 328 329 /** 330 * Detach BPF program 331 */ detachProgram(@onNull String iface)332 public void detachProgram(@NonNull String iface) { 333 try { 334 NetworkInterface netIface = NetworkInterface.getByName(iface); 335 if (netIface != null) { 336 TcUtils.tcFilterDelDev(netIface.getIndex(), false, PRIO_DSCP, (short) ETH_P_ALL); 337 } 338 mAttachedIfaces.remove(iface); 339 } catch (IOException e) { 340 Log.e(TAG, "Unable to detach to TC on " + iface + ": " + e); 341 } 342 } 343 } 344