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