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; 18 19 import static android.net.NetworkStats.DEFAULT_NETWORK_NO; 20 import static android.net.NetworkStats.METERED_NO; 21 import static android.net.NetworkStats.ROAMING_NO; 22 import static android.net.NetworkStats.SET_DEFAULT; 23 import static android.net.NetworkStats.TAG_NONE; 24 import static android.net.NetworkStats.UID_ALL; 25 import static android.net.NetworkStats.UID_TETHERING; 26 import static android.net.netstats.provider.NetworkStatsProvider.QUOTA_UNLIMITED; 27 28 import static com.android.networkstack.tethering.TetheringConfiguration.DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS; 29 30 import android.app.usage.NetworkStatsManager; 31 import android.net.INetd; 32 import android.net.MacAddress; 33 import android.net.NetworkStats; 34 import android.net.NetworkStats.Entry; 35 import android.net.TetherOffloadRuleParcel; 36 import android.net.TetherStatsParcel; 37 import android.net.ip.IpServer; 38 import android.net.netstats.provider.NetworkStatsProvider; 39 import android.net.util.SharedLog; 40 import android.net.util.TetheringUtils.ForwardedStats; 41 import android.os.ConditionVariable; 42 import android.os.Handler; 43 import android.os.RemoteException; 44 import android.os.ServiceSpecificException; 45 import android.text.TextUtils; 46 import android.util.Log; 47 import android.util.SparseArray; 48 49 import androidx.annotation.NonNull; 50 import androidx.annotation.Nullable; 51 52 import com.android.internal.annotations.VisibleForTesting; 53 import com.android.internal.util.IndentingPrintWriter; 54 55 import java.net.Inet6Address; 56 import java.util.ArrayList; 57 import java.util.HashMap; 58 import java.util.LinkedHashMap; 59 import java.util.Map; 60 import java.util.Objects; 61 62 /** 63 * This coordinator is responsible for providing BPF offload relevant functionality. 64 * - Get tethering stats. 65 * - Set data limit. 66 * - Set global alert. 67 * - Add/remove forwarding rules. 68 * 69 * @hide 70 */ 71 public class BpfCoordinator { 72 private static final String TAG = BpfCoordinator.class.getSimpleName(); 73 private static final int DUMP_TIMEOUT_MS = 10_000; 74 75 @VisibleForTesting 76 enum StatsType { 77 STATS_PER_IFACE, 78 STATS_PER_UID, 79 } 80 81 @NonNull 82 private final Handler mHandler; 83 @NonNull 84 private final INetd mNetd; 85 @NonNull 86 private final SharedLog mLog; 87 @NonNull 88 private final Dependencies mDeps; 89 @Nullable 90 private final BpfTetherStatsProvider mStatsProvider; 91 92 // True if BPF offload is supported, false otherwise. The BPF offload could be disabled by 93 // a runtime resource overlay package or device configuration. This flag is only initialized 94 // in the constructor because it is hard to unwind all existing change once device 95 // configuration is changed. Especially the forwarding rules. Keep the same setting 96 // to make it simpler. See also TetheringConfiguration. 97 private final boolean mIsBpfEnabled; 98 99 // Tracks whether BPF tethering is started or not. This is set by tethering before it 100 // starts the first IpServer and is cleared by tethering shortly before the last IpServer 101 // is stopped. Note that rule updates (especially deletions, but sometimes additions as 102 // well) may arrive when this is false. If they do, they must be communicated to netd. 103 // Changes in data limits may also arrive when this is false, and if they do, they must 104 // also be communicated to netd. 105 private boolean mPollingStarted = false; 106 107 // Tracking remaining alert quota. Unlike limit quota is subject to interface, the alert 108 // quota is interface independent and global for tether offload. 109 private long mRemainingAlertQuota = QUOTA_UNLIMITED; 110 111 // Maps upstream interface index to offloaded traffic statistics. 112 // Always contains the latest total bytes/packets, since each upstream was started, received 113 // from the BPF maps for each interface. 114 private final SparseArray<ForwardedStats> mStats = new SparseArray<>(); 115 116 // Maps upstream interface names to interface quotas. 117 // Always contains the latest value received from the framework for each interface, regardless 118 // of whether offload is currently running (or is even supported) on that interface. Only 119 // includes interfaces that have a quota set. Note that this map is used for storing the quota 120 // which is set from the service. Because the service uses the interface name to present the 121 // interface, this map uses the interface name to be the mapping index. 122 private final HashMap<String, Long> mInterfaceQuotas = new HashMap<>(); 123 124 // Maps upstream interface index to interface names. 125 // Store all interface name since boot. Used for lookup what interface name it is from the 126 // tether stats got from netd because netd reports interface index to present an interface. 127 // TODO: Remove the unused interface name. 128 private final SparseArray<String> mInterfaceNames = new SparseArray<>(); 129 130 // Map of downstream rule maps. Each of these maps represents the IPv6 forwarding rules for a 131 // given downstream. Each map: 132 // - Is owned by the IpServer that is responsible for that downstream. 133 // - Must only be modified by that IpServer. 134 // - Is created when the IpServer adds its first rule, and deleted when the IpServer deletes 135 // its last rule (or clears its rules). 136 // TODO: Perhaps seal the map and rule operations which communicates with netd into a class. 137 // TODO: Does this need to be a LinkedHashMap or can it just be a HashMap? Also, could it be 138 // a ConcurrentHashMap, in order to avoid the copies in tetherOffloadRuleClear 139 // and tetherOffloadRuleUpdate? 140 // TODO: Perhaps use one-dimensional map and access specific downstream rules via downstream 141 // index. For doing that, IpServer must guarantee that it always has a valid IPv6 downstream 142 // interface index while calling function to clear all rules. IpServer may be calling clear 143 // rules function without a valid IPv6 downstream interface index even if it may have one 144 // before. IpServer would need to call getInterfaceParams() in the constructor instead of when 145 // startIpv6() is called, and make mInterfaceParams final. 146 private final HashMap<IpServer, LinkedHashMap<Inet6Address, Ipv6ForwardingRule>> 147 mIpv6ForwardingRules = new LinkedHashMap<>(); 148 149 // Runnable that used by scheduling next polling of stats. 150 private final Runnable mScheduledPollingTask = () -> { 151 updateForwardedStatsFromNetd(); 152 maybeSchedulePollingStats(); 153 }; 154 155 @VisibleForTesting 156 public abstract static class Dependencies { 157 /** Get handler. */ getHandler()158 @NonNull public abstract Handler getHandler(); 159 160 /** Get netd. */ getNetd()161 @NonNull public abstract INetd getNetd(); 162 163 /** Get network stats manager. */ getNetworkStatsManager()164 @NonNull public abstract NetworkStatsManager getNetworkStatsManager(); 165 166 /** Get shared log. */ getSharedLog()167 @NonNull public abstract SharedLog getSharedLog(); 168 169 /** Get tethering configuration. */ getTetherConfig()170 @Nullable public abstract TetheringConfiguration getTetherConfig(); 171 } 172 173 @VisibleForTesting BpfCoordinator(@onNull Dependencies deps)174 public BpfCoordinator(@NonNull Dependencies deps) { 175 mDeps = deps; 176 mHandler = mDeps.getHandler(); 177 mNetd = mDeps.getNetd(); 178 mLog = mDeps.getSharedLog().forSubComponent(TAG); 179 mIsBpfEnabled = isBpfEnabled(); 180 BpfTetherStatsProvider provider = new BpfTetherStatsProvider(); 181 try { 182 mDeps.getNetworkStatsManager().registerNetworkStatsProvider( 183 getClass().getSimpleName(), provider); 184 } catch (RuntimeException e) { 185 // TODO: Perhaps not allow to use BPF offload because the reregistration failure 186 // implied that no data limit could be applies on a metered upstream if any. 187 Log.wtf(TAG, "Cannot register offload stats provider: " + e); 188 provider = null; 189 } 190 mStatsProvider = provider; 191 } 192 193 /** 194 * Start BPF tethering offload stats polling when the first upstream is started. 195 * Note that this can be only called on handler thread. 196 * TODO: Perhaps check BPF support before starting. 197 * TODO: Start the stats polling only if there is any client on the downstream. 198 */ startPolling()199 public void startPolling() { 200 if (mPollingStarted) return; 201 202 if (!mIsBpfEnabled) { 203 mLog.i("Offload disabled"); 204 return; 205 } 206 207 mPollingStarted = true; 208 maybeSchedulePollingStats(); 209 210 mLog.i("Polling started"); 211 } 212 213 /** 214 * Stop BPF tethering offload stats polling. 215 * The data limit cleanup and the tether stats maps cleanup are not implemented here. 216 * These cleanups rely on all IpServers calling #tetherOffloadRuleRemove. After the 217 * last rule is removed from the upstream, #tetherOffloadRuleRemove does the cleanup 218 * functionality. 219 * Note that this can be only called on handler thread. 220 */ stopPolling()221 public void stopPolling() { 222 if (!mPollingStarted) return; 223 224 // Stop scheduled polling tasks and poll the latest stats from BPF maps. 225 if (mHandler.hasCallbacks(mScheduledPollingTask)) { 226 mHandler.removeCallbacks(mScheduledPollingTask); 227 } 228 updateForwardedStatsFromNetd(); 229 mPollingStarted = false; 230 231 mLog.i("Polling stopped"); 232 } 233 234 /** 235 * Add forwarding rule. After adding the first rule on a given upstream, must add the data 236 * limit on the given upstream. 237 * Note that this can be only called on handler thread. 238 */ tetherOffloadRuleAdd( @onNull final IpServer ipServer, @NonNull final Ipv6ForwardingRule rule)239 public void tetherOffloadRuleAdd( 240 @NonNull final IpServer ipServer, @NonNull final Ipv6ForwardingRule rule) { 241 if (!mIsBpfEnabled) return; 242 243 try { 244 // TODO: Perhaps avoid to add a duplicate rule. 245 mNetd.tetherOffloadRuleAdd(rule.toTetherOffloadRuleParcel()); 246 } catch (RemoteException | ServiceSpecificException e) { 247 mLog.e("Could not add IPv6 forwarding rule: ", e); 248 return; 249 } 250 251 if (!mIpv6ForwardingRules.containsKey(ipServer)) { 252 mIpv6ForwardingRules.put(ipServer, new LinkedHashMap<Inet6Address, 253 Ipv6ForwardingRule>()); 254 } 255 LinkedHashMap<Inet6Address, Ipv6ForwardingRule> rules = mIpv6ForwardingRules.get(ipServer); 256 257 // Setup the data limit on the given upstream if the first rule is added. 258 final int upstreamIfindex = rule.upstreamIfindex; 259 if (!isAnyRuleOnUpstream(upstreamIfindex)) { 260 // If failed to set a data limit, probably should not use this upstream, because 261 // the upstream may not want to blow through the data limit that was told to apply. 262 // TODO: Perhaps stop the coordinator. 263 boolean success = updateDataLimit(upstreamIfindex); 264 if (!success) { 265 final String iface = mInterfaceNames.get(upstreamIfindex); 266 mLog.e("Setting data limit for " + iface + " failed."); 267 } 268 } 269 270 // Must update the adding rule after calling #isAnyRuleOnUpstream because it needs to 271 // check if it is about adding a first rule for a given upstream. 272 rules.put(rule.address, rule); 273 } 274 275 /** 276 * Remove forwarding rule. After removing the last rule on a given upstream, must clear 277 * data limit, update the last tether stats and remove the tether stats in the BPF maps. 278 * Note that this can be only called on handler thread. 279 */ tetherOffloadRuleRemove( @onNull final IpServer ipServer, @NonNull final Ipv6ForwardingRule rule)280 public void tetherOffloadRuleRemove( 281 @NonNull final IpServer ipServer, @NonNull final Ipv6ForwardingRule rule) { 282 if (!mIsBpfEnabled) return; 283 284 try { 285 // TODO: Perhaps avoid to remove a non-existent rule. 286 mNetd.tetherOffloadRuleRemove(rule.toTetherOffloadRuleParcel()); 287 } catch (RemoteException | ServiceSpecificException e) { 288 mLog.e("Could not remove IPv6 forwarding rule: ", e); 289 return; 290 } 291 292 LinkedHashMap<Inet6Address, Ipv6ForwardingRule> rules = mIpv6ForwardingRules.get(ipServer); 293 if (rules == null) return; 294 295 // Must remove rules before calling #isAnyRuleOnUpstream because it needs to check if 296 // the last rule is removed for a given upstream. If no rule is removed, return early. 297 // Avoid unnecessary work on a non-existent rule which may have never been added or 298 // removed already. 299 if (rules.remove(rule.address) == null) return; 300 301 // Remove the downstream entry if it has no more rule. 302 if (rules.isEmpty()) { 303 mIpv6ForwardingRules.remove(ipServer); 304 } 305 306 // Do cleanup functionality if there is no more rule on the given upstream. 307 final int upstreamIfindex = rule.upstreamIfindex; 308 if (!isAnyRuleOnUpstream(upstreamIfindex)) { 309 try { 310 final TetherStatsParcel stats = 311 mNetd.tetherOffloadGetAndClearStats(upstreamIfindex); 312 // Update the last stats delta and delete the local cache for a given upstream. 313 updateQuotaAndStatsFromSnapshot(new TetherStatsParcel[] {stats}); 314 mStats.remove(upstreamIfindex); 315 } catch (RemoteException | ServiceSpecificException e) { 316 Log.wtf(TAG, "Exception when cleanup tether stats for upstream index " 317 + upstreamIfindex + ": ", e); 318 } 319 } 320 } 321 322 /** 323 * Clear all forwarding rules for a given downstream. 324 * Note that this can be only called on handler thread. 325 */ tetherOffloadRuleClear(@onNull final IpServer ipServer)326 public void tetherOffloadRuleClear(@NonNull final IpServer ipServer) { 327 if (!mIsBpfEnabled) return; 328 329 final LinkedHashMap<Inet6Address, Ipv6ForwardingRule> rules = mIpv6ForwardingRules.get( 330 ipServer); 331 if (rules == null) return; 332 333 // Need to build a rule list because the rule map may be changed in the iteration. 334 for (final Ipv6ForwardingRule rule : new ArrayList<Ipv6ForwardingRule>(rules.values())) { 335 tetherOffloadRuleRemove(ipServer, rule); 336 } 337 } 338 339 /** 340 * Update existing forwarding rules to new upstream for a given downstream. 341 * Note that this can be only called on handler thread. 342 */ tetherOffloadRuleUpdate(@onNull final IpServer ipServer, int newUpstreamIfindex)343 public void tetherOffloadRuleUpdate(@NonNull final IpServer ipServer, int newUpstreamIfindex) { 344 if (!mIsBpfEnabled) return; 345 346 final LinkedHashMap<Inet6Address, Ipv6ForwardingRule> rules = mIpv6ForwardingRules.get( 347 ipServer); 348 if (rules == null) return; 349 350 // Need to build a rule list because the rule map may be changed in the iteration. 351 for (final Ipv6ForwardingRule rule : new ArrayList<Ipv6ForwardingRule>(rules.values())) { 352 // Remove the old rule before adding the new one because the map uses the same key for 353 // both rules. Reversing the processing order causes that the new rule is removed as 354 // unexpected. 355 // TODO: Add new rule first to reduce the latency which has no rule. 356 tetherOffloadRuleRemove(ipServer, rule); 357 tetherOffloadRuleAdd(ipServer, rule.onNewUpstream(newUpstreamIfindex)); 358 } 359 } 360 361 /** 362 * Add upstream name to lookup table. The lookup table is used for tether stats interface name 363 * lookup because the netd only reports interface index in BPF tether stats but the service 364 * expects the interface name in NetworkStats object. 365 * Note that this can be only called on handler thread. 366 */ addUpstreamNameToLookupTable(int upstreamIfindex, @NonNull String upstreamIface)367 public void addUpstreamNameToLookupTable(int upstreamIfindex, @NonNull String upstreamIface) { 368 if (!mIsBpfEnabled) return; 369 370 if (upstreamIfindex == 0 || TextUtils.isEmpty(upstreamIface)) return; 371 372 // The same interface index to name mapping may be added by different IpServer objects or 373 // re-added by reconnection on the same upstream interface. Ignore the duplicate one. 374 final String iface = mInterfaceNames.get(upstreamIfindex); 375 if (iface == null) { 376 mInterfaceNames.put(upstreamIfindex, upstreamIface); 377 } else if (!TextUtils.equals(iface, upstreamIface)) { 378 Log.wtf(TAG, "The upstream interface name " + upstreamIface 379 + " is different from the existing interface name " 380 + iface + " for index " + upstreamIfindex); 381 } 382 } 383 384 /** 385 * Dump information. 386 * Block the function until all the data are dumped on the handler thread or timed-out. The 387 * reason is that dumpsys invokes this function on the thread of caller and the data may only 388 * be allowed to be accessed on the handler thread. 389 */ dump(@onNull IndentingPrintWriter pw)390 public void dump(@NonNull IndentingPrintWriter pw) { 391 final ConditionVariable dumpDone = new ConditionVariable(); 392 mHandler.post(() -> { 393 pw.println("mIsBpfEnabled: " + mIsBpfEnabled); 394 pw.println("Polling " + (mPollingStarted ? "started" : "not started")); 395 pw.println("Stats provider " + (mStatsProvider != null 396 ? "registered" : "not registered")); 397 pw.println("Upstream quota: " + mInterfaceQuotas.toString()); 398 pw.println("Polling interval: " + getPollingInterval() + " ms"); 399 400 pw.println("Forwarding stats:"); 401 pw.increaseIndent(); 402 if (mStats.size() == 0) { 403 pw.println("<empty>"); 404 } else { 405 dumpStats(pw); 406 } 407 pw.decreaseIndent(); 408 409 pw.println("Forwarding rules:"); 410 pw.increaseIndent(); 411 if (mIpv6ForwardingRules.size() == 0) { 412 pw.println("<empty>"); 413 } else { 414 dumpIpv6ForwardingRules(pw); 415 } 416 pw.decreaseIndent(); 417 418 dumpDone.open(); 419 }); 420 if (!dumpDone.block(DUMP_TIMEOUT_MS)) { 421 pw.println("... dump timed-out after " + DUMP_TIMEOUT_MS + "ms"); 422 } 423 } 424 dumpStats(@onNull IndentingPrintWriter pw)425 private void dumpStats(@NonNull IndentingPrintWriter pw) { 426 for (int i = 0; i < mStats.size(); i++) { 427 final int upstreamIfindex = mStats.keyAt(i); 428 final ForwardedStats stats = mStats.get(upstreamIfindex); 429 pw.println(String.format("%d(%s) - %s", upstreamIfindex, mInterfaceNames.get( 430 upstreamIfindex), stats.toString())); 431 } 432 } 433 dumpIpv6ForwardingRules(@onNull IndentingPrintWriter pw)434 private void dumpIpv6ForwardingRules(@NonNull IndentingPrintWriter pw) { 435 for (Map.Entry<IpServer, LinkedHashMap<Inet6Address, Ipv6ForwardingRule>> entry : 436 mIpv6ForwardingRules.entrySet()) { 437 IpServer ipServer = entry.getKey(); 438 // The rule downstream interface index is paired with the interface name from 439 // IpServer#interfaceName. See #startIPv6, #updateIpv6ForwardingRules in IpServer. 440 final String downstreamIface = ipServer.interfaceName(); 441 pw.println("[" + downstreamIface + "]: iif(iface) oif(iface) v6addr srcmac dstmac"); 442 443 pw.increaseIndent(); 444 LinkedHashMap<Inet6Address, Ipv6ForwardingRule> rules = entry.getValue(); 445 for (Ipv6ForwardingRule rule : rules.values()) { 446 final int upstreamIfindex = rule.upstreamIfindex; 447 pw.println(String.format("%d(%s) %d(%s) %s %s %s", upstreamIfindex, 448 mInterfaceNames.get(upstreamIfindex), rule.downstreamIfindex, 449 downstreamIface, rule.address, rule.srcMac, rule.dstMac)); 450 } 451 pw.decreaseIndent(); 452 } 453 } 454 455 /** IPv6 forwarding rule class. */ 456 public static class Ipv6ForwardingRule { 457 public final int upstreamIfindex; 458 public final int downstreamIfindex; 459 460 @NonNull 461 public final Inet6Address address; 462 @NonNull 463 public final MacAddress srcMac; 464 @NonNull 465 public final MacAddress dstMac; 466 Ipv6ForwardingRule(int upstreamIfindex, int downstreamIfIndex, @NonNull Inet6Address address, @NonNull MacAddress srcMac, @NonNull MacAddress dstMac)467 public Ipv6ForwardingRule(int upstreamIfindex, int downstreamIfIndex, 468 @NonNull Inet6Address address, @NonNull MacAddress srcMac, 469 @NonNull MacAddress dstMac) { 470 this.upstreamIfindex = upstreamIfindex; 471 this.downstreamIfindex = downstreamIfIndex; 472 this.address = address; 473 this.srcMac = srcMac; 474 this.dstMac = dstMac; 475 } 476 477 /** Return a new rule object which updates with new upstream index. */ 478 @NonNull onNewUpstream(int newUpstreamIfindex)479 public Ipv6ForwardingRule onNewUpstream(int newUpstreamIfindex) { 480 return new Ipv6ForwardingRule(newUpstreamIfindex, downstreamIfindex, address, srcMac, 481 dstMac); 482 } 483 484 /** 485 * Don't manipulate TetherOffloadRuleParcel directly because implementing onNewUpstream() 486 * would be error-prone due to generated stable AIDL classes not having a copy constructor. 487 */ 488 @NonNull toTetherOffloadRuleParcel()489 public TetherOffloadRuleParcel toTetherOffloadRuleParcel() { 490 final TetherOffloadRuleParcel parcel = new TetherOffloadRuleParcel(); 491 parcel.inputInterfaceIndex = upstreamIfindex; 492 parcel.outputInterfaceIndex = downstreamIfindex; 493 parcel.destination = address.getAddress(); 494 parcel.prefixLength = 128; 495 parcel.srcL2Address = srcMac.toByteArray(); 496 parcel.dstL2Address = dstMac.toByteArray(); 497 return parcel; 498 } 499 500 @Override equals(Object o)501 public boolean equals(Object o) { 502 if (!(o instanceof Ipv6ForwardingRule)) return false; 503 Ipv6ForwardingRule that = (Ipv6ForwardingRule) o; 504 return this.upstreamIfindex == that.upstreamIfindex 505 && this.downstreamIfindex == that.downstreamIfindex 506 && Objects.equals(this.address, that.address) 507 && Objects.equals(this.srcMac, that.srcMac) 508 && Objects.equals(this.dstMac, that.dstMac); 509 } 510 511 @Override hashCode()512 public int hashCode() { 513 // TODO: if this is ever used in production code, don't pass ifindices 514 // to Objects.hash() to avoid autoboxing overhead. 515 return Objects.hash(upstreamIfindex, downstreamIfindex, address, srcMac, dstMac); 516 } 517 } 518 519 /** 520 * A BPF tethering stats provider to provide network statistics to the system. 521 * Note that this class' data may only be accessed on the handler thread. 522 */ 523 @VisibleForTesting 524 class BpfTetherStatsProvider extends NetworkStatsProvider { 525 // The offloaded traffic statistics per interface that has not been reported since the 526 // last call to pushTetherStats. Only the interfaces that were ever tethering upstreams 527 // and has pending tether stats delta are included in this NetworkStats object. 528 private NetworkStats mIfaceStats = new NetworkStats(0L, 0); 529 530 // The same stats as above, but counts network stats per uid. 531 private NetworkStats mUidStats = new NetworkStats(0L, 0); 532 533 @Override onRequestStatsUpdate(int token)534 public void onRequestStatsUpdate(int token) { 535 mHandler.post(() -> pushTetherStats()); 536 } 537 538 @Override onSetAlert(long quotaBytes)539 public void onSetAlert(long quotaBytes) { 540 mHandler.post(() -> updateAlertQuota(quotaBytes)); 541 } 542 543 @Override onSetLimit(@onNull String iface, long quotaBytes)544 public void onSetLimit(@NonNull String iface, long quotaBytes) { 545 if (quotaBytes < QUOTA_UNLIMITED) { 546 throw new IllegalArgumentException("invalid quota value " + quotaBytes); 547 } 548 549 mHandler.post(() -> { 550 final Long curIfaceQuota = mInterfaceQuotas.get(iface); 551 552 if (null == curIfaceQuota && QUOTA_UNLIMITED == quotaBytes) return; 553 554 if (quotaBytes == QUOTA_UNLIMITED) { 555 mInterfaceQuotas.remove(iface); 556 } else { 557 mInterfaceQuotas.put(iface, quotaBytes); 558 } 559 maybeUpdateDataLimit(iface); 560 }); 561 } 562 563 @VisibleForTesting pushTetherStats()564 void pushTetherStats() { 565 try { 566 // The token is not used for now. See b/153606961. 567 notifyStatsUpdated(0 /* token */, mIfaceStats, mUidStats); 568 569 // Clear the accumulated tether stats delta after reported. Note that create a new 570 // empty object because NetworkStats#clear is @hide. 571 mIfaceStats = new NetworkStats(0L, 0); 572 mUidStats = new NetworkStats(0L, 0); 573 } catch (RuntimeException e) { 574 mLog.e("Cannot report network stats: ", e); 575 } 576 } 577 accumulateDiff(@onNull NetworkStats ifaceDiff, @NonNull NetworkStats uidDiff)578 private void accumulateDiff(@NonNull NetworkStats ifaceDiff, 579 @NonNull NetworkStats uidDiff) { 580 mIfaceStats = mIfaceStats.add(ifaceDiff); 581 mUidStats = mUidStats.add(uidDiff); 582 } 583 } 584 isBpfEnabled()585 private boolean isBpfEnabled() { 586 final TetheringConfiguration config = mDeps.getTetherConfig(); 587 return (config != null) ? config.isBpfOffloadEnabled() : true /* default value */; 588 } 589 getInterfaceIndexFromRules(@onNull String ifName)590 private int getInterfaceIndexFromRules(@NonNull String ifName) { 591 for (LinkedHashMap<Inet6Address, Ipv6ForwardingRule> rules : mIpv6ForwardingRules 592 .values()) { 593 for (Ipv6ForwardingRule rule : rules.values()) { 594 final int upstreamIfindex = rule.upstreamIfindex; 595 if (TextUtils.equals(ifName, mInterfaceNames.get(upstreamIfindex))) { 596 return upstreamIfindex; 597 } 598 } 599 } 600 return 0; 601 } 602 getQuotaBytes(@onNull String iface)603 private long getQuotaBytes(@NonNull String iface) { 604 final Long limit = mInterfaceQuotas.get(iface); 605 final long quotaBytes = (limit != null) ? limit : QUOTA_UNLIMITED; 606 607 return quotaBytes; 608 } 609 sendDataLimitToNetd(int ifIndex, long quotaBytes)610 private boolean sendDataLimitToNetd(int ifIndex, long quotaBytes) { 611 if (ifIndex == 0) { 612 Log.wtf(TAG, "Invalid interface index."); 613 return false; 614 } 615 616 try { 617 mNetd.tetherOffloadSetInterfaceQuota(ifIndex, quotaBytes); 618 } catch (RemoteException | ServiceSpecificException e) { 619 mLog.e("Exception when updating quota " + quotaBytes + ": ", e); 620 return false; 621 } 622 623 return true; 624 } 625 626 // Handle the data limit update from the service which is the stats provider registered for. maybeUpdateDataLimit(@onNull String iface)627 private void maybeUpdateDataLimit(@NonNull String iface) { 628 // Set data limit only on a given upstream which has at least one rule. If we can't get 629 // an interface index for a given interface name, it means either there is no rule for 630 // a given upstream or the interface name is not an upstream which is monitored by the 631 // coordinator. 632 final int ifIndex = getInterfaceIndexFromRules(iface); 633 if (ifIndex == 0) return; 634 635 final long quotaBytes = getQuotaBytes(iface); 636 sendDataLimitToNetd(ifIndex, quotaBytes); 637 } 638 639 // Handle the data limit update while adding forwarding rules. updateDataLimit(int ifIndex)640 private boolean updateDataLimit(int ifIndex) { 641 final String iface = mInterfaceNames.get(ifIndex); 642 if (iface == null) { 643 mLog.e("Fail to get the interface name for index " + ifIndex); 644 return false; 645 } 646 final long quotaBytes = getQuotaBytes(iface); 647 return sendDataLimitToNetd(ifIndex, quotaBytes); 648 } 649 isAnyRuleOnUpstream(int upstreamIfindex)650 private boolean isAnyRuleOnUpstream(int upstreamIfindex) { 651 for (LinkedHashMap<Inet6Address, Ipv6ForwardingRule> rules : mIpv6ForwardingRules 652 .values()) { 653 for (Ipv6ForwardingRule rule : rules.values()) { 654 if (upstreamIfindex == rule.upstreamIfindex) return true; 655 } 656 } 657 return false; 658 } 659 660 @NonNull buildNetworkStats(@onNull StatsType type, int ifIndex, @NonNull final ForwardedStats diff)661 private NetworkStats buildNetworkStats(@NonNull StatsType type, int ifIndex, 662 @NonNull final ForwardedStats diff) { 663 NetworkStats stats = new NetworkStats(0L, 0); 664 final String iface = mInterfaceNames.get(ifIndex); 665 if (iface == null) { 666 // TODO: Use Log.wtf once the coordinator owns full control of tether stats from netd. 667 // For now, netd may add the empty stats for the upstream which is not monitored by 668 // the coordinator. Silently ignore it. 669 return stats; 670 } 671 final int uid = (type == StatsType.STATS_PER_UID) ? UID_TETHERING : UID_ALL; 672 // Note that the argument 'metered', 'roaming' and 'defaultNetwork' are not recorded for 673 // network stats snapshot. See NetworkStatsRecorder#recordSnapshotLocked. 674 return stats.addEntry(new Entry(iface, uid, SET_DEFAULT, TAG_NONE, METERED_NO, 675 ROAMING_NO, DEFAULT_NETWORK_NO, diff.rxBytes, diff.rxPackets, 676 diff.txBytes, diff.txPackets, 0L /* operations */)); 677 } 678 updateAlertQuota(long newQuota)679 private void updateAlertQuota(long newQuota) { 680 if (newQuota < QUOTA_UNLIMITED) { 681 throw new IllegalArgumentException("invalid quota value " + newQuota); 682 } 683 if (mRemainingAlertQuota == newQuota) return; 684 685 mRemainingAlertQuota = newQuota; 686 if (mRemainingAlertQuota == 0) { 687 mLog.i("onAlertReached"); 688 if (mStatsProvider != null) mStatsProvider.notifyAlertReached(); 689 } 690 } 691 updateQuotaAndStatsFromSnapshot( @onNull final TetherStatsParcel[] tetherStatsList)692 private void updateQuotaAndStatsFromSnapshot( 693 @NonNull final TetherStatsParcel[] tetherStatsList) { 694 long usedAlertQuota = 0; 695 for (TetherStatsParcel tetherStats : tetherStatsList) { 696 final Integer ifIndex = tetherStats.ifIndex; 697 final ForwardedStats curr = new ForwardedStats(tetherStats); 698 final ForwardedStats base = mStats.get(ifIndex); 699 final ForwardedStats diff = (base != null) ? curr.subtract(base) : curr; 700 usedAlertQuota += diff.rxBytes + diff.txBytes; 701 702 // Update the local cache for counting tether stats delta. 703 mStats.put(ifIndex, curr); 704 705 // Update the accumulated tether stats delta to the stats provider for the service 706 // querying. 707 if (mStatsProvider != null) { 708 try { 709 mStatsProvider.accumulateDiff( 710 buildNetworkStats(StatsType.STATS_PER_IFACE, ifIndex, diff), 711 buildNetworkStats(StatsType.STATS_PER_UID, ifIndex, diff)); 712 } catch (ArrayIndexOutOfBoundsException e) { 713 Log.wtf(TAG, "Fail to update the accumulated stats delta for interface index " 714 + ifIndex + " : ", e); 715 } 716 } 717 } 718 719 if (mRemainingAlertQuota > 0 && usedAlertQuota > 0) { 720 // Trim to zero if overshoot. 721 final long newQuota = Math.max(mRemainingAlertQuota - usedAlertQuota, 0); 722 updateAlertQuota(newQuota); 723 } 724 725 // TODO: Count the used limit quota for notifying data limit reached. 726 } 727 updateForwardedStatsFromNetd()728 private void updateForwardedStatsFromNetd() { 729 final TetherStatsParcel[] tetherStatsList; 730 try { 731 // The reported tether stats are total data usage for all currently-active upstream 732 // interfaces since tethering start. 733 tetherStatsList = mNetd.tetherOffloadGetStats(); 734 } catch (RemoteException | ServiceSpecificException e) { 735 mLog.e("Problem fetching tethering stats: ", e); 736 return; 737 } 738 updateQuotaAndStatsFromSnapshot(tetherStatsList); 739 } 740 741 @VisibleForTesting getPollingInterval()742 int getPollingInterval() { 743 // The valid range of interval is DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS..max_long. 744 // Ignore the config value is less than the minimum polling interval. Note that the 745 // minimum interval definition is invoked as OffloadController#isPollingStatsNeeded does. 746 // TODO: Perhaps define a minimum polling interval constant. 747 final TetheringConfiguration config = mDeps.getTetherConfig(); 748 final int configInterval = (config != null) ? config.getOffloadPollInterval() : 0; 749 return Math.max(DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS, configInterval); 750 } 751 maybeSchedulePollingStats()752 private void maybeSchedulePollingStats() { 753 if (!mPollingStarted) return; 754 755 if (mHandler.hasCallbacks(mScheduledPollingTask)) { 756 mHandler.removeCallbacks(mScheduledPollingTask); 757 } 758 759 mHandler.postDelayed(mScheduledPollingTask, getPollingInterval()); 760 } 761 762 // Return forwarding rule map. This is used for testing only. 763 // Note that this can be only called on handler thread. 764 @NonNull 765 @VisibleForTesting 766 final HashMap<IpServer, LinkedHashMap<Inet6Address, Ipv6ForwardingRule>> getForwardingRulesForTesting()767 getForwardingRulesForTesting() { 768 return mIpv6ForwardingRules; 769 } 770 771 // Return upstream interface name map. This is used for testing only. 772 // Note that this can be only called on handler thread. 773 @NonNull 774 @VisibleForTesting getInterfaceNamesForTesting()775 final SparseArray<String> getInterfaceNamesForTesting() { 776 return mInterfaceNames; 777 } 778 } 779