1 /* 2 * Copyright (C) 2020 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.networkstack.tethering.apishim.api31; 18 19 import static android.net.netstats.provider.NetworkStatsProvider.QUOTA_UNLIMITED; 20 21 import static com.android.net.module.util.NetworkStackConstants.RFC7421_PREFIX_LENGTH; 22 23 import android.system.ErrnoException; 24 import android.system.Os; 25 import android.system.OsConstants; 26 import android.util.Log; 27 import android.util.SparseArray; 28 29 import androidx.annotation.NonNull; 30 import androidx.annotation.Nullable; 31 32 import com.android.net.module.util.IBpfMap; 33 import com.android.net.module.util.IBpfMap.ThrowingBiConsumer; 34 import com.android.net.module.util.SharedLog; 35 import com.android.net.module.util.bpf.Tether4Key; 36 import com.android.net.module.util.bpf.Tether4Value; 37 import com.android.net.module.util.bpf.TetherStatsKey; 38 import com.android.net.module.util.bpf.TetherStatsValue; 39 import com.android.networkstack.tethering.BpfCoordinator.Dependencies; 40 import com.android.networkstack.tethering.BpfCoordinator.Ipv6DownstreamRule; 41 import com.android.networkstack.tethering.BpfCoordinator.Ipv6UpstreamRule; 42 import com.android.networkstack.tethering.BpfUtils; 43 import com.android.networkstack.tethering.Tether6Value; 44 import com.android.networkstack.tethering.TetherDevKey; 45 import com.android.networkstack.tethering.TetherDevValue; 46 import com.android.networkstack.tethering.TetherDownstream6Key; 47 import com.android.networkstack.tethering.TetherLimitKey; 48 import com.android.networkstack.tethering.TetherLimitValue; 49 import com.android.networkstack.tethering.TetherUpstream6Key; 50 51 import java.io.FileDescriptor; 52 import java.io.IOException; 53 54 /** 55 * Bpf coordinator class for API shims. 56 */ 57 public class BpfCoordinatorShimImpl 58 extends com.android.networkstack.tethering.apishim.common.BpfCoordinatorShim { 59 private static final String TAG = "api31.BpfCoordinatorShimImpl"; 60 61 // AF_KEY socket type. See include/linux/socket.h. 62 private static final int AF_KEY = 15; 63 // PFKEYv2 constants. See include/uapi/linux/pfkeyv2.h. 64 private static final int PF_KEY_V2 = 2; 65 66 @NonNull 67 private final SharedLog mLog; 68 69 // BPF map for downstream IPv4 forwarding. 70 @Nullable 71 private final IBpfMap<Tether4Key, Tether4Value> mBpfDownstream4Map; 72 73 // BPF map for upstream IPv4 forwarding. 74 @Nullable 75 private final IBpfMap<Tether4Key, Tether4Value> mBpfUpstream4Map; 76 77 // BPF map for downstream IPv6 forwarding. 78 @Nullable 79 private final IBpfMap<TetherDownstream6Key, Tether6Value> mBpfDownstream6Map; 80 81 // BPF map for upstream IPv6 forwarding. 82 @Nullable 83 private final IBpfMap<TetherUpstream6Key, Tether6Value> mBpfUpstream6Map; 84 85 // BPF map of tethering statistics of the upstream interface since tethering startup. 86 @Nullable 87 private final IBpfMap<TetherStatsKey, TetherStatsValue> mBpfStatsMap; 88 89 // BPF map of per-interface quota for tethering offload. 90 @Nullable 91 private final IBpfMap<TetherLimitKey, TetherLimitValue> mBpfLimitMap; 92 93 // BPF map of interface index mapping for XDP. 94 @Nullable 95 private final IBpfMap<TetherDevKey, TetherDevValue> mBpfDevMap; 96 97 // Tracking IPv4 rule count while any rule is using the given upstream interfaces. Used for 98 // reducing the BPF map iteration query. The count is increased or decreased when the rule is 99 // added or removed successfully on mBpfDownstream4Map. Counting the rules on downstream4 map 100 // is because tetherOffloadRuleRemove can't get upstream interface index from upstream key, 101 // unless pass upstream value which is not required for deleting map entry. The upstream 102 // interface index is the same in Upstream4Value.oif and Downstream4Key.iif. For now, it is 103 // okay to count on Downstream4Key. See BpfConntrackEventConsumer#accept. 104 // Note that except the constructor, any calls to mBpfDownstream4Map.clear() need to clear 105 // this counter as well. 106 // TODO: Count the rule on upstream if multi-upstream is supported and the 107 // packet needs to be sent and responded on different upstream interfaces. 108 // TODO: Add IPv6 rule count. 109 private final SparseArray<Integer> mRule4CountOnUpstream = new SparseArray<>(); 110 BpfCoordinatorShimImpl(@onNull final Dependencies deps)111 public BpfCoordinatorShimImpl(@NonNull final Dependencies deps) { 112 mLog = deps.getSharedLog().forSubComponent(TAG); 113 114 mBpfDownstream4Map = deps.getBpfDownstream4Map(); 115 mBpfUpstream4Map = deps.getBpfUpstream4Map(); 116 mBpfDownstream6Map = deps.getBpfDownstream6Map(); 117 mBpfUpstream6Map = deps.getBpfUpstream6Map(); 118 mBpfStatsMap = deps.getBpfStatsMap(); 119 mBpfLimitMap = deps.getBpfLimitMap(); 120 mBpfDevMap = deps.getBpfDevMap(); 121 122 // Clear the stubs of the maps for handling the system service crash if any. 123 // Doesn't throw the exception and clear the stubs as many as possible. 124 try { 125 if (mBpfDownstream4Map != null) mBpfDownstream4Map.clear(); 126 } catch (ErrnoException e) { 127 mLog.e("Could not clear mBpfDownstream4Map: " + e); 128 } 129 try { 130 if (mBpfUpstream4Map != null) mBpfUpstream4Map.clear(); 131 } catch (ErrnoException e) { 132 mLog.e("Could not clear mBpfUpstream4Map: " + e); 133 } 134 try { 135 if (mBpfDownstream6Map != null) mBpfDownstream6Map.clear(); 136 } catch (ErrnoException e) { 137 mLog.e("Could not clear mBpfDownstream6Map: " + e); 138 } 139 try { 140 if (mBpfUpstream6Map != null) mBpfUpstream6Map.clear(); 141 } catch (ErrnoException e) { 142 mLog.e("Could not clear mBpfUpstream6Map: " + e); 143 } 144 try { 145 if (mBpfStatsMap != null) mBpfStatsMap.clear(); 146 } catch (ErrnoException e) { 147 mLog.e("Could not clear mBpfStatsMap: " + e); 148 } 149 try { 150 if (mBpfLimitMap != null) mBpfLimitMap.clear(); 151 } catch (ErrnoException e) { 152 mLog.e("Could not clear mBpfLimitMap: " + e); 153 } 154 try { 155 if (mBpfDevMap != null) mBpfDevMap.clear(); 156 } catch (ErrnoException e) { 157 mLog.e("Could not clear mBpfDevMap: " + e); 158 } 159 } 160 161 @Override isInitialized()162 public boolean isInitialized() { 163 return mBpfDownstream4Map != null && mBpfUpstream4Map != null && mBpfDownstream6Map != null 164 && mBpfUpstream6Map != null && mBpfStatsMap != null && mBpfLimitMap != null 165 && mBpfDevMap != null; 166 } 167 168 @Override addIpv6UpstreamRule(@onNull final Ipv6UpstreamRule rule)169 public boolean addIpv6UpstreamRule(@NonNull final Ipv6UpstreamRule rule) { 170 // RFC7421_PREFIX_LENGTH = 64 which is the most commonly used IPv6 subnet prefix length. 171 if (rule.sourcePrefix.getPrefixLength() != RFC7421_PREFIX_LENGTH) return false; 172 173 final TetherUpstream6Key key = rule.makeTetherUpstream6Key(); 174 final Tether6Value value = rule.makeTether6Value(); 175 176 try { 177 mBpfUpstream6Map.insertEntry(key, value); 178 } catch (ErrnoException | IllegalStateException e) { 179 mLog.e("Could not insert upstream IPv6 entry: " + e); 180 return false; 181 } 182 return true; 183 } 184 185 @Override removeIpv6UpstreamRule(@onNull final Ipv6UpstreamRule rule)186 public boolean removeIpv6UpstreamRule(@NonNull final Ipv6UpstreamRule rule) { 187 // RFC7421_PREFIX_LENGTH = 64 which is the most commonly used IPv6 subnet prefix length. 188 if (rule.sourcePrefix.getPrefixLength() != RFC7421_PREFIX_LENGTH) return false; 189 190 try { 191 mBpfUpstream6Map.deleteEntry(rule.makeTetherUpstream6Key()); 192 } catch (ErrnoException e) { 193 mLog.e("Could not delete upstream IPv6 entry: " + e); 194 return false; 195 } 196 return true; 197 } 198 199 @Override addIpv6DownstreamRule(@onNull final Ipv6DownstreamRule rule)200 public boolean addIpv6DownstreamRule(@NonNull final Ipv6DownstreamRule rule) { 201 final TetherDownstream6Key key = rule.makeTetherDownstream6Key(); 202 final Tether6Value value = rule.makeTether6Value(); 203 204 try { 205 mBpfDownstream6Map.updateEntry(key, value); 206 } catch (ErrnoException e) { 207 mLog.e("Could not update entry: ", e); 208 return false; 209 } 210 211 return true; 212 } 213 214 @Override removeIpv6DownstreamRule(@onNull final Ipv6DownstreamRule rule)215 public boolean removeIpv6DownstreamRule(@NonNull final Ipv6DownstreamRule rule) { 216 try { 217 mBpfDownstream6Map.deleteEntry(rule.makeTetherDownstream6Key()); 218 } catch (ErrnoException e) { 219 // Silent if the rule did not exist. 220 if (e.errno != OsConstants.ENOENT) { 221 mLog.e("Could not update entry: ", e); 222 return false; 223 } 224 } 225 return true; 226 } 227 228 @Override 229 @Nullable tetherOffloadGetStats()230 public SparseArray<TetherStatsValue> tetherOffloadGetStats() { 231 final SparseArray<TetherStatsValue> tetherStatsList = new SparseArray<TetherStatsValue>(); 232 try { 233 // The reported tether stats are total data usage for all currently-active upstream 234 // interfaces since tethering start. 235 mBpfStatsMap.forEach((key, value) -> tetherStatsList.put((int) key.ifindex, value)); 236 } catch (ErrnoException e) { 237 mLog.e("Fail to fetch tethering stats from BPF map: ", e); 238 return null; 239 } 240 return tetherStatsList; 241 } 242 243 @Override tetherOffloadSetInterfaceQuota(int ifIndex, long quotaBytes)244 public boolean tetherOffloadSetInterfaceQuota(int ifIndex, long quotaBytes) { 245 // The common case is an update, where the stats already exist, 246 // hence we read first, even though writing with BPF_NOEXIST 247 // first would make the code simpler. 248 long rxBytes, txBytes; 249 TetherStatsValue statsValue = null; 250 251 try { 252 statsValue = mBpfStatsMap.getValue(new TetherStatsKey(ifIndex)); 253 } catch (ErrnoException e) { 254 // The BpfMap#getValue doesn't throw an errno ENOENT exception. Catch other error 255 // while trying to get stats entry. 256 mLog.e("Could not get stats entry of interface index " + ifIndex + ": ", e); 257 return false; 258 } 259 260 if (statsValue != null) { 261 // Ok, there was a stats entry. 262 rxBytes = statsValue.rxBytes; 263 txBytes = statsValue.txBytes; 264 } else { 265 // No stats entry - create one with zeroes. 266 try { 267 // This function is the *only* thing that can create entries. 268 // BpfMap#insertEntry use BPF_NOEXIST to create the entry. The entry is created 269 // if and only if it doesn't exist. 270 mBpfStatsMap.insertEntry(new TetherStatsKey(ifIndex), new TetherStatsValue( 271 0 /* rxPackets */, 0 /* rxBytes */, 0 /* rxErrors */, 0 /* txPackets */, 272 0 /* txBytes */, 0 /* txErrors */)); 273 } catch (ErrnoException | IllegalArgumentException e) { 274 mLog.e("Could not create stats entry: ", e); 275 return false; 276 } 277 rxBytes = 0; 278 txBytes = 0; 279 } 280 281 // rxBytes + txBytes won't overflow even at 5gbps for ~936 years. 282 long newLimit = rxBytes + txBytes + quotaBytes; 283 284 // if adding limit (e.g., if limit is QUOTA_UNLIMITED) caused overflow: clamp to 'infinity' 285 if (newLimit < rxBytes + txBytes) newLimit = QUOTA_UNLIMITED; 286 287 try { 288 mBpfLimitMap.updateEntry(new TetherLimitKey(ifIndex), new TetherLimitValue(newLimit)); 289 } catch (ErrnoException e) { 290 mLog.e("Fail to set quota " + quotaBytes + " for interface index " + ifIndex + ": ", e); 291 return false; 292 } 293 294 return true; 295 } 296 297 @Override 298 @Nullable tetherOffloadGetAndClearStats(int ifIndex)299 public TetherStatsValue tetherOffloadGetAndClearStats(int ifIndex) { 300 // getAndClearTetherOffloadStats is called after all offload rules have already been 301 // deleted for the given upstream interface. Before starting to do cleanup stuff in this 302 // function, use synchronizeKernelRCU to make sure that all the current running eBPF 303 // programs are finished on all CPUs, especially the unfinished packet processing. After 304 // synchronizeKernelRCU returned, we can safely read or delete on the stats map or the 305 // limit map. 306 final int res = synchronizeKernelRCU(); 307 if (res != 0) { 308 // Error log but don't return. Do as much cleanup as possible. 309 mLog.e("synchronize_rcu() failed: " + res); 310 } 311 312 TetherStatsValue statsValue = null; 313 try { 314 statsValue = mBpfStatsMap.getValue(new TetherStatsKey(ifIndex)); 315 } catch (ErrnoException e) { 316 mLog.e("Could not get stats entry for interface index " + ifIndex + ": ", e); 317 return null; 318 } 319 320 if (statsValue == null) { 321 mLog.e("Could not get stats entry for interface index " + ifIndex); 322 return null; 323 } 324 325 try { 326 mBpfStatsMap.deleteEntry(new TetherStatsKey(ifIndex)); 327 } catch (ErrnoException e) { 328 mLog.e("Could not delete stats entry for interface index " + ifIndex + ": ", e); 329 return null; 330 } 331 332 try { 333 mBpfLimitMap.deleteEntry(new TetherLimitKey(ifIndex)); 334 } catch (ErrnoException e) { 335 mLog.e("Could not delete limit for interface index " + ifIndex + ": ", e); 336 return null; 337 } 338 339 return statsValue; 340 } 341 342 @Override tetherOffloadRuleAdd(boolean downstream, @NonNull Tether4Key key, @NonNull Tether4Value value)343 public boolean tetherOffloadRuleAdd(boolean downstream, @NonNull Tether4Key key, 344 @NonNull Tether4Value value) { 345 try { 346 if (downstream) { 347 mBpfDownstream4Map.insertEntry(key, value); 348 349 // Increase the rule count while a adding rule is using a given upstream interface. 350 final int upstreamIfindex = (int) key.iif; 351 int count = mRule4CountOnUpstream.get(upstreamIfindex, 0 /* default */); 352 mRule4CountOnUpstream.put(upstreamIfindex, ++count); 353 } else { 354 mBpfUpstream4Map.insertEntry(key, value); 355 } 356 } catch (ErrnoException e) { 357 mLog.e("Could not insert entry (" + key + ", " + value + "): " + e); 358 return false; 359 } catch (IllegalStateException e) { 360 // Silent if the rule already exists. Note that the errno EEXIST was rethrown as 361 // IllegalStateException. See BpfMap#insertEntry. 362 } 363 return true; 364 } 365 366 @Override tetherOffloadRuleRemove(boolean downstream, @NonNull Tether4Key key)367 public boolean tetherOffloadRuleRemove(boolean downstream, @NonNull Tether4Key key) { 368 try { 369 if (downstream) { 370 if (!mBpfDownstream4Map.deleteEntry(key)) return false; // Rule did not exist 371 372 // Decrease the rule count while a deleting rule is not using a given upstream 373 // interface anymore. 374 final int upstreamIfindex = (int) key.iif; 375 Integer count = mRule4CountOnUpstream.get(upstreamIfindex); 376 if (count == null) { 377 Log.wtf(TAG, "Could not delete count for interface " + upstreamIfindex); 378 return false; 379 } 380 381 if (--count == 0) { 382 // Remove the entry if the count decreases to zero. 383 mRule4CountOnUpstream.remove(upstreamIfindex); 384 } else { 385 mRule4CountOnUpstream.put(upstreamIfindex, count); 386 } 387 } else { 388 if (!mBpfUpstream4Map.deleteEntry(key)) return false; // Rule did not exist 389 } 390 } catch (ErrnoException e) { 391 mLog.e("Could not delete entry (key: " + key + ")", e); 392 return false; 393 } 394 return true; 395 } 396 397 @Override tetherOffloadRuleForEach(boolean downstream, @NonNull ThrowingBiConsumer<Tether4Key, Tether4Value> action)398 public void tetherOffloadRuleForEach(boolean downstream, 399 @NonNull ThrowingBiConsumer<Tether4Key, Tether4Value> action) { 400 try { 401 if (downstream) { 402 mBpfDownstream4Map.forEach(action); 403 } else { 404 mBpfUpstream4Map.forEach(action); 405 } 406 } catch (ErrnoException e) { 407 mLog.e("Could not iterate map: ", e); 408 } 409 } 410 411 @Override attachProgram(String iface, boolean downstream, boolean ipv4)412 public boolean attachProgram(String iface, boolean downstream, boolean ipv4) { 413 try { 414 BpfUtils.attachProgram(iface, downstream, ipv4); 415 } catch (IOException e) { 416 mLog.e("Could not attach program: " + e); 417 return false; 418 } 419 return true; 420 } 421 422 @Override detachProgram(String iface, boolean ipv4)423 public boolean detachProgram(String iface, boolean ipv4) { 424 try { 425 BpfUtils.detachProgram(iface, ipv4); 426 } catch (IOException e) { 427 mLog.e("Could not detach program: " + e); 428 return false; 429 } 430 return true; 431 } 432 433 @Override isAnyIpv4RuleOnUpstream(int ifIndex)434 public boolean isAnyIpv4RuleOnUpstream(int ifIndex) { 435 // No entry means no rule for the given interface because 0 has never been stored. 436 return mRule4CountOnUpstream.get(ifIndex) != null; 437 } 438 439 @Override addDevMap(int ifIndex)440 public boolean addDevMap(int ifIndex) { 441 try { 442 mBpfDevMap.updateEntry(new TetherDevKey(ifIndex), new TetherDevValue(ifIndex)); 443 } catch (ErrnoException e) { 444 mLog.e("Could not add interface " + ifIndex + ": " + e); 445 return false; 446 } 447 return true; 448 } 449 450 @Override removeDevMap(int ifIndex)451 public boolean removeDevMap(int ifIndex) { 452 try { 453 mBpfDevMap.deleteEntry(new TetherDevKey(ifIndex)); 454 } catch (ErrnoException e) { 455 mLog.e("Could not delete interface " + ifIndex + ": " + e); 456 return false; 457 } 458 return true; 459 } 460 mapStatus(IBpfMap m, String name)461 private String mapStatus(IBpfMap m, String name) { 462 return name + "{" + (m != null ? "OK" : "ERROR") + "}"; 463 } 464 465 @Override toString()466 public String toString() { 467 return String.join(", ", new String[] { 468 mapStatus(mBpfDownstream6Map, "mBpfDownstream6Map"), 469 mapStatus(mBpfUpstream6Map, "mBpfUpstream6Map"), 470 mapStatus(mBpfDownstream4Map, "mBpfDownstream4Map"), 471 mapStatus(mBpfUpstream4Map, "mBpfUpstream4Map"), 472 mapStatus(mBpfStatsMap, "mBpfStatsMap"), 473 mapStatus(mBpfLimitMap, "mBpfLimitMap"), 474 mapStatus(mBpfDevMap, "mBpfDevMap") 475 }); 476 } 477 478 /** 479 * Call synchronize_rcu() to block until all existing RCU read-side critical sections have 480 * been completed. 481 * Note that BpfCoordinatorTest have no permissions to create or close pf_key socket. It is 482 * okay for now because the caller #bpfGetAndClearStats doesn't care the result of this 483 * function. The tests don't be broken. 484 * TODO: Wrap this function into Dependencies for mocking in tests. 485 */ synchronizeKernelRCU()486 private int synchronizeKernelRCU() { 487 // This is a temporary hack for network stats map swap on devices running 488 // 4.9 kernels. The kernel code of socket release on pf_key socket will 489 // explicitly call synchronize_rcu() which is exactly what we need. 490 FileDescriptor pfSocket; 491 try { 492 pfSocket = Os.socket(AF_KEY, OsConstants.SOCK_RAW | OsConstants.SOCK_CLOEXEC, 493 PF_KEY_V2); 494 } catch (ErrnoException e) { 495 mLog.e("create PF_KEY socket failed: ", e); 496 return e.errno; 497 } 498 499 // When closing socket, synchronize_rcu() gets called in sock_release(). 500 try { 501 Os.close(pfSocket); 502 } catch (ErrnoException e) { 503 mLog.e("failed to close the PF_KEY socket: ", e); 504 return e.errno; 505 } 506 507 return 0; 508 } 509 } 510