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