• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "netdbpf/BpfNetworkStats.h"
30 #include "netdbpf/bpf_shared.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 netdutils::Status;
42 
43 // The target map for stats reading should be the inactive map, which is oppsite
44 // from the config value.
45 static constexpr char const* STATS_MAP_PATH[] = {STATS_MAP_B_PATH, STATS_MAP_A_PATH};
46 
47 static constexpr uint32_t BPF_OPEN_FLAGS = BPF_F_RDONLY;
48 
bpfGetUidStatsInternal(uid_t uid,Stats * stats,const BpfMap<uint32_t,StatsValue> & appUidStatsMap)49 int bpfGetUidStatsInternal(uid_t uid, Stats* stats,
50                            const BpfMap<uint32_t, StatsValue>& appUidStatsMap) {
51     auto statsEntry = appUidStatsMap.readValue(uid);
52     if (isOk(statsEntry)) {
53         stats->rxPackets = statsEntry.value().rxPackets;
54         stats->txPackets = statsEntry.value().txPackets;
55         stats->rxBytes = statsEntry.value().rxBytes;
56         stats->txBytes = statsEntry.value().txBytes;
57     }
58     return statsEntry.status().code() == ENOENT ? 0 : -statsEntry.status().code();
59 }
60 
bpfGetUidStats(uid_t uid,Stats * stats)61 int bpfGetUidStats(uid_t uid, Stats* stats) {
62     BpfMap<uint32_t, StatsValue> appUidStatsMap(
63         mapRetrieve(APP_UID_STATS_MAP_PATH, BPF_OPEN_FLAGS));
64 
65     if (!appUidStatsMap.isValid()) {
66         int ret = -errno;
67         ALOGE("Opening appUidStatsMap(%s) failed: %s", APP_UID_STATS_MAP_PATH, strerror(errno));
68         return ret;
69     }
70     return bpfGetUidStatsInternal(uid, stats, appUidStatsMap);
71 }
72 
bpfGetIfaceStatsInternal(const char * iface,Stats * stats,const BpfMap<uint32_t,StatsValue> & ifaceStatsMap,const BpfMap<uint32_t,IfaceValue> & ifaceNameMap)73 int bpfGetIfaceStatsInternal(const char* iface, Stats* stats,
74                              const BpfMap<uint32_t, StatsValue>& ifaceStatsMap,
75                              const BpfMap<uint32_t, IfaceValue>& ifaceNameMap) {
76     int64_t unknownIfaceBytesTotal = 0;
77     stats->tcpRxPackets = -1;
78     stats->tcpTxPackets = -1;
79     const auto processIfaceStats = [iface, stats, &ifaceNameMap, &unknownIfaceBytesTotal]
80                                    (const uint32_t& key,
81                                     const BpfMap<uint32_t, StatsValue>& ifaceStatsMap) {
82         char ifname[IFNAMSIZ];
83         if (getIfaceNameFromMap(ifaceNameMap, ifaceStatsMap, key, ifname, key,
84                                 &unknownIfaceBytesTotal)) {
85             return netdutils::status::ok;
86         }
87         if (!iface || !strcmp(iface, ifname)) {
88             StatsValue statsEntry;
89             ASSIGN_OR_RETURN(statsEntry, ifaceStatsMap.readValue(key));
90             stats->rxPackets += statsEntry.rxPackets;
91             stats->txPackets += statsEntry.txPackets;
92             stats->rxBytes += statsEntry.rxBytes;
93             stats->txBytes += statsEntry.txBytes;
94         }
95         return netdutils::status::ok;
96     };
97     return -ifaceStatsMap.iterate(processIfaceStats).code();
98 }
99 
bpfGetIfaceStats(const char * iface,Stats * stats)100 int bpfGetIfaceStats(const char* iface, Stats* stats) {
101     BpfMap<uint32_t, StatsValue> ifaceStatsMap(mapRetrieve(IFACE_STATS_MAP_PATH, BPF_OPEN_FLAGS));
102     int ret;
103     if (!ifaceStatsMap.isValid()) {
104         ret = -errno;
105         ALOGE("get ifaceStats map fd failed: %s", strerror(errno));
106         return ret;
107     }
108     BpfMap<uint32_t, IfaceValue> ifaceIndexNameMap(
109         mapRetrieve(IFACE_INDEX_NAME_MAP_PATH, BPF_OPEN_FLAGS));
110     if (!ifaceIndexNameMap.isValid()) {
111         ret = -errno;
112         ALOGE("get ifaceIndexName map fd failed: %s", strerror(errno));
113         return ret;
114     }
115     return bpfGetIfaceStatsInternal(iface, stats, ifaceStatsMap, ifaceIndexNameMap);
116 }
117 
populateStatsEntry(const StatsKey & statsKey,const StatsValue & statsEntry,const char * ifname)118 stats_line populateStatsEntry(const StatsKey& statsKey, const StatsValue& statsEntry,
119                               const char* ifname) {
120     stats_line newLine;
121     strlcpy(newLine.iface, ifname, sizeof(newLine.iface));
122     newLine.uid = (int32_t)statsKey.uid;
123     newLine.set = (int32_t)statsKey.counterSet;
124     newLine.tag = (int32_t)statsKey.tag;
125     newLine.rxPackets = statsEntry.rxPackets;
126     newLine.txPackets = statsEntry.txPackets;
127     newLine.rxBytes = statsEntry.rxBytes;
128     newLine.txBytes = statsEntry.txBytes;
129     return newLine;
130 }
131 
parseBpfNetworkStatsDetailInternal(std::vector<stats_line> * lines,const std::vector<std::string> & limitIfaces,int limitTag,int limitUid,const BpfMap<StatsKey,StatsValue> & statsMap,const BpfMap<uint32_t,IfaceValue> & ifaceMap)132 int parseBpfNetworkStatsDetailInternal(std::vector<stats_line>* lines,
133                                        const std::vector<std::string>& limitIfaces, int limitTag,
134                                        int limitUid, const BpfMap<StatsKey, StatsValue>& statsMap,
135                                        const BpfMap<uint32_t, IfaceValue>& ifaceMap) {
136     int64_t unknownIfaceBytesTotal = 0;
137     const auto processDetailUidStats = [lines, &limitIfaces, &limitTag, &limitUid,
138                                         &unknownIfaceBytesTotal,
139                                         &ifaceMap](const StatsKey& key,
140                                                    const BpfMap<StatsKey, StatsValue>& statsMap) {
141         char ifname[IFNAMSIZ];
142         if (getIfaceNameFromMap(ifaceMap, statsMap, key.ifaceIndex, ifname, key,
143                                 &unknownIfaceBytesTotal)) {
144             return netdutils::status::ok;
145         }
146         std::string ifnameStr(ifname);
147         if (limitIfaces.size() > 0 &&
148             std::find(limitIfaces.begin(), limitIfaces.end(), ifnameStr) == limitIfaces.end()) {
149             // Nothing matched; skip this line.
150             return netdutils::status::ok;
151         }
152         if (limitTag != TAG_ALL && uint32_t(limitTag) != key.tag) {
153             return netdutils::status::ok;
154         }
155         if (limitUid != UID_ALL && uint32_t(limitUid) != key.uid) {
156             return netdutils::status::ok;
157         }
158         StatsValue statsEntry;
159         ASSIGN_OR_RETURN(statsEntry, statsMap.readValue(key));
160         lines->push_back(populateStatsEntry(key, statsEntry, ifname));
161         return netdutils::status::ok;
162     };
163     Status res = statsMap.iterate(processDetailUidStats);
164     if (!isOk(res)) {
165         ALOGE("failed to iterate per uid Stats map for detail traffic stats: %s",
166               strerror(res.code()));
167         return -res.code();
168     }
169 
170     // Since eBPF use hash map to record stats, network stats collected from
171     // eBPF will be out of order. And the performance of findIndexHinted in
172     // NetworkStats will also be impacted.
173     //
174     // Furthermore, since the StatsKey contains iface index, the network stats
175     // reported to framework would create items with the same iface, uid, tag
176     // and set, which causes NetworkStats maps wrong item to subtract.
177     //
178     // Thus, the stats needs to be properly sorted and grouped before reported.
179     groupNetworkStats(lines);
180     return 0;
181 }
182 
parseBpfNetworkStatsDetail(std::vector<stats_line> * lines,const std::vector<std::string> & limitIfaces,int limitTag,int limitUid)183 int parseBpfNetworkStatsDetail(std::vector<stats_line>* lines,
184                                const std::vector<std::string>& limitIfaces, int limitTag,
185                                int limitUid) {
186     BpfMap<uint32_t, IfaceValue> ifaceIndexNameMap(
187         mapRetrieve(IFACE_INDEX_NAME_MAP_PATH, BPF_OPEN_FLAGS));
188     if (!ifaceIndexNameMap.isValid()) {
189         int ret = -errno;
190         ALOGE("get ifaceIndexName map fd failed: %s", strerror(errno));
191         return ret;
192     }
193 
194     BpfMap<uint32_t, uint8_t> configurationMap(mapRetrieve(CONFIGURATION_MAP_PATH, 0));
195     if (!configurationMap.isValid()) {
196         int ret = -errno;
197         ALOGE("get configuration map fd failed: %s", strerror(errno));
198         return ret;
199     }
200     auto configuration = configurationMap.readValue(CURRENT_STATS_MAP_CONFIGURATION_KEY);
201     if (!isOk(configuration)) {
202         ALOGE("Cannot read the old configuration from map: %s",
203               configuration.status().msg().c_str());
204         return -configuration.status().code();
205     }
206     const char* statsMapPath = STATS_MAP_PATH[configuration.value()];
207     BpfMap<StatsKey, StatsValue> statsMap(mapRetrieve(statsMapPath, 0));
208     if (!statsMap.isValid()) {
209         int ret = -errno;
210         ALOGE("get stats map fd failed: %s, path: %s", strerror(errno), statsMapPath);
211         return ret;
212     }
213 
214     // It is safe to read and clear the old map now since the
215     // networkStatsFactory should call netd to swap the map in advance already.
216     int ret = parseBpfNetworkStatsDetailInternal(lines, limitIfaces, limitTag, limitUid, statsMap,
217                                                  ifaceIndexNameMap);
218     if (ret) {
219         ALOGE("parse detail network stats failed: %s", strerror(errno));
220         return ret;
221     }
222 
223     Status res = statsMap.clear();
224     if (!isOk(res)) {
225         ALOGE("Clean up current stats map failed: %s", strerror(res.code()));
226         return -res.code();
227     }
228 
229     return 0;
230 }
231 
parseBpfNetworkStatsDevInternal(std::vector<stats_line> * lines,const BpfMap<uint32_t,StatsValue> & statsMap,const BpfMap<uint32_t,IfaceValue> & ifaceMap)232 int parseBpfNetworkStatsDevInternal(std::vector<stats_line>* lines,
233                                     const BpfMap<uint32_t, StatsValue>& statsMap,
234                                     const BpfMap<uint32_t, IfaceValue>& ifaceMap) {
235     int64_t unknownIfaceBytesTotal = 0;
236     const auto processDetailIfaceStats = [lines, &unknownIfaceBytesTotal, &ifaceMap, &statsMap](
237                                              const uint32_t& key, const StatsValue& value,
238                                              const BpfMap<uint32_t, StatsValue>&) {
239         char ifname[IFNAMSIZ];
240         if (getIfaceNameFromMap(ifaceMap, statsMap, key, ifname, key, &unknownIfaceBytesTotal)) {
241             return netdutils::status::ok;
242         }
243         StatsKey fakeKey = {
244             .uid = (uint32_t)UID_ALL, .counterSet = (uint32_t)SET_ALL, .tag = (uint32_t)TAG_NONE};
245         lines->push_back(populateStatsEntry(fakeKey, value, ifname));
246         return netdutils::status::ok;
247     };
248     Status res = statsMap.iterateWithValue(processDetailIfaceStats);
249     if (!isOk(res)) {
250         ALOGE("failed to iterate per uid Stats map for detail traffic stats: %s",
251               strerror(res.code()));
252         return -res.code();
253     }
254 
255     groupNetworkStats(lines);
256     return 0;
257 }
258 
parseBpfNetworkStatsDev(std::vector<stats_line> * lines)259 int parseBpfNetworkStatsDev(std::vector<stats_line>* lines) {
260     int ret = 0;
261     BpfMap<uint32_t, IfaceValue> ifaceIndexNameMap(
262         mapRetrieve(IFACE_INDEX_NAME_MAP_PATH, BPF_OPEN_FLAGS));
263     if (!ifaceIndexNameMap.isValid()) {
264         ret = -errno;
265         ALOGE("get ifaceIndexName map fd failed: %s", strerror(errno));
266         return ret;
267     }
268 
269     BpfMap<uint32_t, StatsValue> ifaceStatsMap(mapRetrieve(IFACE_STATS_MAP_PATH, BPF_OPEN_FLAGS));
270     if (!ifaceStatsMap.isValid()) {
271         ret = -errno;
272         ALOGE("get ifaceStats map fd failed: %s", strerror(errno));
273         return ret;
274     }
275     return parseBpfNetworkStatsDevInternal(lines, ifaceStatsMap, ifaceIndexNameMap);
276 }
277 
combineUidTag(const uid_t uid,const uint32_t tag)278 uint64_t combineUidTag(const uid_t uid, const uint32_t tag) {
279     return (uint64_t)uid << 32 | tag;
280 }
281 
groupNetworkStats(std::vector<stats_line> * lines)282 void groupNetworkStats(std::vector<stats_line>* lines) {
283     if (lines->size() <= 1) return;
284     std::sort(lines->begin(), lines->end());
285 
286     // Similar to std::unique(), but aggregates the duplicates rather than discarding them.
287     size_t nextOutput = 0;
288     for (size_t i = 1; i < lines->size(); i++) {
289         if (lines->at(nextOutput) == lines->at(i)) {
290             lines->at(nextOutput) += lines->at(i);
291         } else {
292             nextOutput++;
293             if (nextOutput != i) {
294                 lines->at(nextOutput) = lines->at(i);
295             }
296         }
297     }
298 
299     if (lines->size() != nextOutput + 1) {
300         lines->resize(nextOutput + 1);
301     }
302 }
303 
304 // True if lhs equals to rhs, only compare iface, uid, tag and set.
operator ==(const stats_line & lhs,const stats_line & rhs)305 bool operator==(const stats_line& lhs, const stats_line& rhs) {
306     return ((lhs.uid == rhs.uid) && (lhs.tag == rhs.tag) && (lhs.set == rhs.set) &&
307             !strncmp(lhs.iface, rhs.iface, sizeof(lhs.iface)));
308 }
309 
310 // True if lhs is smaller then rhs, only compare iface, uid, tag and set.
operator <(const stats_line & lhs,const stats_line & rhs)311 bool operator<(const stats_line& lhs, const stats_line& rhs) {
312     int ret = strncmp(lhs.iface, rhs.iface, sizeof(lhs.iface));
313     if (ret != 0) return ret < 0;
314     if (lhs.uid < rhs.uid) return true;
315     if (lhs.uid > rhs.uid) return false;
316     if (lhs.tag < rhs.tag) return true;
317     if (lhs.tag > rhs.tag) return false;
318     if (lhs.set < rhs.set) return true;
319     if (lhs.set > rhs.set) return false;
320     return false;
321 }
322 
operator =(const stats_line & rhs)323 stats_line& stats_line::operator=(const stats_line& rhs) {
324     strlcpy(iface, rhs.iface, sizeof(iface));
325     uid = rhs.uid;
326     set = rhs.set;
327     tag = rhs.tag;
328     rxPackets = rhs.rxPackets;
329     txPackets = rhs.txPackets;
330     rxBytes = rhs.rxBytes;
331     txBytes = rhs.txBytes;
332     return *this;
333 }
334 
operator +=(const stats_line & rhs)335 stats_line& stats_line::operator+=(const stats_line& rhs) {
336     rxPackets += rhs.rxPackets;
337     txPackets += rhs.txPackets;
338     rxBytes += rhs.rxBytes;
339     txBytes += rhs.txBytes;
340     return *this;
341 }
342 
343 }  // namespace bpf
344 }  // namespace android
345