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