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