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