1 /*
2  * Copyright (C) 2017 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 #include <inttypes.h>
18 #include <net/if.h>
19 #include <string.h>
20 #include <unordered_set>
21 
22 #include <utils/Log.h>
23 #include <utils/misc.h>
24 
25 #include "android-base/file.h"
26 #include "android-base/strings.h"
27 #include "android-base/unique_fd.h"
28 #include "bpf/BpfMap.h"
29 #include "netd.h"
30 #include "netdbpf/BpfNetworkStats.h"
31 
32 #ifdef LOG_TAG
33 #undef LOG_TAG
34 #endif
35 
36 #define LOG_TAG "BpfNetworkStats"
37 
38 namespace android {
39 namespace bpf {
40 
41 using base::Result;
42 
getIfaceIndexNameMap()43 BpfMap<uint32_t, IfaceValue>& getIfaceIndexNameMap() {
44     static BpfMap<uint32_t, IfaceValue> ifaceIndexNameMap(IFACE_INDEX_NAME_MAP_PATH);
45     return ifaceIndexNameMap;
46 }
47 
getIfaceStatsMap()48 const BpfMapRO<uint32_t, StatsValue>& getIfaceStatsMap() {
49     static BpfMapRO<uint32_t, StatsValue> ifaceStatsMap(IFACE_STATS_MAP_PATH);
50     return ifaceStatsMap;
51 }
52 
ifindex2name(const uint32_t ifindex)53 Result<IfaceValue> ifindex2name(const uint32_t ifindex) {
54     Result<IfaceValue> v = getIfaceIndexNameMap().readValue(ifindex);
55     if (v.ok()) return v;
56     IfaceValue iv = {};
57     if (!if_indextoname(ifindex, iv.name)) return v;
58     getIfaceIndexNameMap().writeValue(ifindex, iv, BPF_ANY);
59     return iv;
60 }
61 
bpfRegisterIface(const char * iface)62 void bpfRegisterIface(const char* iface) {
63     if (!iface) return;
64     if (strlen(iface) >= sizeof(IfaceValue)) return;
65     uint32_t ifindex = if_nametoindex(iface);
66     if (!ifindex) return;
67     IfaceValue ifname = {};
68     strlcpy(ifname.name, iface, sizeof(ifname.name));
69     getIfaceIndexNameMap().writeValue(ifindex, ifname, BPF_ANY);
70 }
71 
bpfGetUidStatsInternal(uid_t uid,StatsValue * stats,const BpfMapRO<uint32_t,StatsValue> & appUidStatsMap)72 int bpfGetUidStatsInternal(uid_t uid, StatsValue* stats,
73                            const BpfMapRO<uint32_t, StatsValue>& appUidStatsMap) {
74     auto statsEntry = appUidStatsMap.readValue(uid);
75     if (!statsEntry.ok()) {
76         *stats = {};
77         return (statsEntry.error().code() == ENOENT) ? 0 : -statsEntry.error().code();
78     }
79     *stats = statsEntry.value();
80     return 0;
81 }
82 
bpfGetUidStats(uid_t uid,StatsValue * stats)83 int bpfGetUidStats(uid_t uid, StatsValue* stats) {
84     static BpfMapRO<uint32_t, StatsValue> appUidStatsMap(APP_UID_STATS_MAP_PATH);
85     return bpfGetUidStatsInternal(uid, stats, appUidStatsMap);
86 }
87 
bpfGetIfaceStatsInternal(const char * iface,StatsValue * stats,const BpfMapRO<uint32_t,StatsValue> & ifaceStatsMap,const IfIndexToNameFunc ifindex2name)88 int bpfGetIfaceStatsInternal(const char* iface, StatsValue* stats,
89                              const BpfMapRO<uint32_t, StatsValue>& ifaceStatsMap,
90                              const IfIndexToNameFunc ifindex2name) {
91     *stats = {};
92     int64_t unknownIfaceBytesTotal = 0;
93     const auto processIfaceStats =
94             [iface, stats, ifindex2name, &unknownIfaceBytesTotal](
95                     const uint32_t& key,
96                     const BpfMapRO<uint32_t, StatsValue>& ifaceStatsMap) -> Result<void> {
97         Result<IfaceValue> ifname = ifindex2name(key);
98         if (!ifname.ok()) {
99             maybeLogUnknownIface(key, ifaceStatsMap, key, &unknownIfaceBytesTotal);
100             return Result<void>();
101         }
102         if (!iface || !strcmp(iface, ifname.value().name)) {
103             Result<StatsValue> statsEntry = ifaceStatsMap.readValue(key);
104             if (!statsEntry.ok()) {
105                 return statsEntry.error();
106             }
107             *stats += statsEntry.value();
108         }
109         return Result<void>();
110     };
111     auto res = ifaceStatsMap.iterate(processIfaceStats);
112     return res.ok() ? 0 : -res.error().code();
113 }
114 
bpfGetIfaceStats(const char * iface,StatsValue * stats)115 int bpfGetIfaceStats(const char* iface, StatsValue* stats) {
116     return bpfGetIfaceStatsInternal(iface, stats, getIfaceStatsMap(), ifindex2name);
117 }
118 
bpfGetIfIndexStatsInternal(uint32_t ifindex,StatsValue * stats,const BpfMapRO<uint32_t,StatsValue> & ifaceStatsMap)119 int bpfGetIfIndexStatsInternal(uint32_t ifindex, StatsValue* stats,
120                                const BpfMapRO<uint32_t, StatsValue>& ifaceStatsMap) {
121     auto statsEntry = ifaceStatsMap.readValue(ifindex);
122     if (!statsEntry.ok()) {
123         *stats = {};
124         return (statsEntry.error().code() == ENOENT) ? 0 : -statsEntry.error().code();
125     }
126     *stats = statsEntry.value();
127     return 0;
128 }
129 
bpfGetIfIndexStats(int ifindex,StatsValue * stats)130 int bpfGetIfIndexStats(int ifindex, StatsValue* stats) {
131     return bpfGetIfIndexStatsInternal(ifindex, stats, getIfaceStatsMap());
132 }
133 
populateStatsEntry(const StatsKey & statsKey,const StatsValue & statsEntry,const IfaceValue & ifname)134 stats_line populateStatsEntry(const StatsKey& statsKey, const StatsValue& statsEntry,
135                               const IfaceValue& ifname) {
136     stats_line newLine;
137     strlcpy(newLine.iface, ifname.name, sizeof(newLine.iface));
138     newLine.uid = (int32_t)statsKey.uid;
139     newLine.set = (int32_t)statsKey.counterSet;
140     newLine.tag = (int32_t)statsKey.tag;
141     newLine.rxPackets = statsEntry.rxPackets;
142     newLine.txPackets = statsEntry.txPackets;
143     newLine.rxBytes = statsEntry.rxBytes;
144     newLine.txBytes = statsEntry.txBytes;
145     return newLine;
146 }
147 
parseBpfNetworkStatsDetailInternal(std::vector<stats_line> & lines,const BpfMapRO<StatsKey,StatsValue> & statsMap,const IfIndexToNameFunc ifindex2name)148 int parseBpfNetworkStatsDetailInternal(std::vector<stats_line>& lines,
149                                        const BpfMapRO<StatsKey, StatsValue>& statsMap,
150                                        const IfIndexToNameFunc ifindex2name) {
151     int64_t unknownIfaceBytesTotal = 0;
152     const auto processDetailUidStats =
153             [&lines, &unknownIfaceBytesTotal, &ifindex2name](
154                     const StatsKey& key,
155                     const BpfMapRO<StatsKey, StatsValue>& statsMap) -> Result<void> {
156         Result<IfaceValue> ifname = ifindex2name(key.ifaceIndex);
157         if (!ifname.ok()) {
158             maybeLogUnknownIface(key.ifaceIndex, statsMap, key, &unknownIfaceBytesTotal);
159             return Result<void>();
160         }
161         Result<StatsValue> statsEntry = statsMap.readValue(key);
162         if (!statsEntry.ok()) {
163             return base::ResultError(statsEntry.error().message(), statsEntry.error().code());
164         }
165         stats_line newLine = populateStatsEntry(key, statsEntry.value(), ifname.value());
166         lines.push_back(newLine);
167         if (newLine.tag) {
168             // account tagged traffic in the untagged stats (for historical reasons?)
169             newLine.tag = 0;
170             lines.push_back(newLine);
171         }
172         return Result<void>();
173     };
174     Result<void> res = statsMap.iterate(processDetailUidStats);
175     if (!res.ok()) {
176         ALOGE("failed to iterate per uid Stats map for detail traffic stats: %s",
177               strerror(res.error().code()));
178         return -res.error().code();
179     }
180 
181     // Since eBPF use hash map to record stats, network stats collected from
182     // eBPF will be out of order. And the performance of findIndexHinted in
183     // NetworkStats will also be impacted.
184     //
185     // Furthermore, since the StatsKey contains iface index, the network stats
186     // reported to framework would create items with the same iface, uid, tag
187     // and set, which causes NetworkStats maps wrong item to subtract.
188     //
189     // Thus, the stats needs to be properly sorted and grouped before reported.
190     groupNetworkStats(lines);
191     return 0;
192 }
193 
parseBpfNetworkStatsDetail(std::vector<stats_line> * lines)194 int parseBpfNetworkStatsDetail(std::vector<stats_line>* lines) {
195     static BpfMapRO<uint32_t, uint32_t> configurationMap(CONFIGURATION_MAP_PATH);
196     static BpfMap<StatsKey, StatsValue> statsMapA(STATS_MAP_A_PATH);
197     static BpfMap<StatsKey, StatsValue> statsMapB(STATS_MAP_B_PATH);
198     auto configuration = configurationMap.readValue(CURRENT_STATS_MAP_CONFIGURATION_KEY);
199     if (!configuration.ok()) {
200         ALOGE("Cannot read the old configuration from map: %s",
201               configuration.error().message().c_str());
202         return -configuration.error().code();
203     }
204     // The target map for stats reading should be the inactive map, which is opposite
205     // from the config value.
206     BpfMap<StatsKey, StatsValue> *inactiveStatsMap;
207     switch (configuration.value()) {
208       case SELECT_MAP_A:
209         inactiveStatsMap = &statsMapB;
210         break;
211       case SELECT_MAP_B:
212         inactiveStatsMap = &statsMapA;
213         break;
214       default:
215         ALOGE("%s unknown configuration value: %d", __func__, configuration.value());
216         return -EINVAL;
217     }
218 
219     // It is safe to read and clear the old map now since the
220     // networkStatsFactory should call netd to swap the map in advance already.
221     // TODO: the above comment feels like it may be obsolete / out of date,
222     // since we no longer swap the map via netd binder rpc - though we do
223     // still swap it.
224     int ret = parseBpfNetworkStatsDetailInternal(*lines, *inactiveStatsMap, ifindex2name);
225     if (ret) {
226         ALOGE("parse detail network stats failed: %s", strerror(errno));
227         return ret;
228     }
229 
230     Result<void> res = inactiveStatsMap->clear();
231     if (!res.ok()) {
232         ALOGE("Clean up current stats map failed: %s", strerror(res.error().code()));
233         return -res.error().code();
234     }
235 
236     return 0;
237 }
238 
parseBpfNetworkStatsDevInternal(std::vector<stats_line> & lines,const BpfMapRO<uint32_t,StatsValue> & statsMap,const IfIndexToNameFunc ifindex2name)239 int parseBpfNetworkStatsDevInternal(std::vector<stats_line>& lines,
240                                     const BpfMapRO<uint32_t, StatsValue>& statsMap,
241                                     const IfIndexToNameFunc ifindex2name) {
242     int64_t unknownIfaceBytesTotal = 0;
243     const auto processDetailIfaceStats = [&lines, &unknownIfaceBytesTotal, ifindex2name, &statsMap](
244                                              const uint32_t& key, const StatsValue& value,
245                                              const BpfMapRO<uint32_t, StatsValue>&) {
246         Result<IfaceValue> ifname = ifindex2name(key);
247         if (!ifname.ok()) {
248             maybeLogUnknownIface(key, statsMap, key, &unknownIfaceBytesTotal);
249             return Result<void>();
250         }
251         StatsKey fakeKey = {
252                 .uid = (uint32_t)UID_ALL,
253                 .tag = (uint32_t)TAG_NONE,
254                 .counterSet = (uint32_t)SET_ALL,
255         };
256         lines.push_back(populateStatsEntry(fakeKey, value, ifname.value()));
257         return Result<void>();
258     };
259     Result<void> res = statsMap.iterateWithValue(processDetailIfaceStats);
260     if (!res.ok()) {
261         ALOGE("failed to iterate per uid Stats map for detail traffic stats: %s",
262               strerror(res.error().code()));
263         return -res.error().code();
264     }
265 
266     groupNetworkStats(lines);
267     return 0;
268 }
269 
parseBpfNetworkStatsDev(std::vector<stats_line> * lines)270 int parseBpfNetworkStatsDev(std::vector<stats_line>* lines) {
271     return parseBpfNetworkStatsDevInternal(*lines, getIfaceStatsMap(), ifindex2name);
272 }
273 
groupNetworkStats(std::vector<stats_line> & lines)274 void groupNetworkStats(std::vector<stats_line>& lines) {
275     if (lines.size() <= 1) return;
276     std::sort(lines.begin(), lines.end());
277 
278     // Similar to std::unique(), but aggregates the duplicates rather than discarding them.
279     size_t currentOutput = 0;
280     for (size_t i = 1; i < lines.size(); i++) {
281         // note that == operator only compares the 'key' portion: iface/uid/tag/set
282         if (lines[currentOutput] == lines[i]) {
283             // while += operator only affects the 'data' portion: {rx,tx}{Bytes,Packets}
284             lines[currentOutput] += lines[i];
285         } else {
286             // okay, we're done aggregating the current line, move to the next one
287             lines[++currentOutput] = lines[i];
288         }
289     }
290 
291     // possibly shrink the vector - currentOutput is the last line with valid data
292     lines.resize(currentOutput + 1);
293 }
294 
295 // True if lhs equals to rhs, only compare iface, uid, tag and set.
operator ==(const stats_line & lhs,const stats_line & rhs)296 bool operator==(const stats_line& lhs, const stats_line& rhs) {
297     return ((lhs.uid == rhs.uid) && (lhs.tag == rhs.tag) && (lhs.set == rhs.set) &&
298             !strncmp(lhs.iface, rhs.iface, sizeof(lhs.iface)));
299 }
300 
301 // True if lhs is smaller than rhs, only compare iface, uid, tag and set.
operator <(const stats_line & lhs,const stats_line & rhs)302 bool operator<(const stats_line& lhs, const stats_line& rhs) {
303     int ret = strncmp(lhs.iface, rhs.iface, sizeof(lhs.iface));
304     if (ret != 0) return ret < 0;
305     if (lhs.uid < rhs.uid) return true;
306     if (lhs.uid > rhs.uid) return false;
307     if (lhs.tag < rhs.tag) return true;
308     if (lhs.tag > rhs.tag) return false;
309     if (lhs.set < rhs.set) return true;
310     if (lhs.set > rhs.set) return false;
311     return false;
312 }
313 
operator =(const stats_line & rhs)314 stats_line& stats_line::operator=(const stats_line& rhs) {
315     if (this == &rhs) return *this;
316 
317     strlcpy(iface, rhs.iface, sizeof(iface));
318     uid = rhs.uid;
319     set = rhs.set;
320     tag = rhs.tag;
321     rxPackets = rhs.rxPackets;
322     txPackets = rhs.txPackets;
323     rxBytes = rhs.rxBytes;
324     txBytes = rhs.txBytes;
325     return *this;
326 }
327 
operator +=(const stats_line & rhs)328 stats_line& stats_line::operator+=(const stats_line& rhs) {
329     rxPackets += rhs.rxPackets;
330     txPackets += rhs.txPackets;
331     rxBytes += rhs.rxBytes;
332     txBytes += rhs.txBytes;
333     return *this;
334 }
335 
336 }  // namespace bpf
337 }  // namespace android
338