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