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