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