blob: 5a7ec091ef30d08253cab62580a1d5245329cb6c [file] [log] [blame]
Chenbo Fengf2759682017-10-10 17:31:57 -07001/*
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
Chenbo Fengc10a8a42017-12-15 13:56:33 -080017#define LOG_TAG "TrafficController"
18
Chenbo Feng33cc1032017-10-23 15:16:37 -070019#include <inttypes.h>
Chenbo Fengf2759682017-10-10 17:31:57 -070020#include <linux/bpf.h>
21#include <linux/if_ether.h>
22#include <linux/in.h>
23#include <linux/inet_diag.h>
24#include <linux/unistd.h>
Chenbo Fengc10a8a42017-12-15 13:56:33 -080025#include <net/if.h>
Chenbo Fengf2759682017-10-10 17:31:57 -070026#include <stdlib.h>
27#include <string.h>
28#include <sys/socket.h>
Chenbo Fengc10a8a42017-12-15 13:56:33 -080029#include <sys/stat.h>
Chenbo Feng33cc1032017-10-23 15:16:37 -070030#include <sys/utsname.h>
Chenbo Fengc10a8a42017-12-15 13:56:33 -080031#include <unordered_set>
32#include <vector>
33
34#include <android-base/stringprintf.h>
35#include <android-base/unique_fd.h>
36#include <netdutils/StatusOr.h>
Chenbo Fengf2759682017-10-10 17:31:57 -070037
38#include "BpfProgSets.h"
Chenbo Fengf2759682017-10-10 17:31:57 -070039#include "TrafficController.h"
Chenbo Fengc10a8a42017-12-15 13:56:33 -080040#include "bpf/BpfUtils.h"
Chenbo Feng33cc1032017-10-23 15:16:37 -070041#include "qtaguid/qtaguid.h"
Chenbo Fengf2759682017-10-10 17:31:57 -070042
Chenbo Fengc10a8a42017-12-15 13:56:33 -080043using namespace android::bpf;
Chenbo Fengf2759682017-10-10 17:31:57 -070044using namespace android::net::bpf_prog;
45
46namespace android {
47namespace net {
48
Chenbo Fengc10a8a42017-12-15 13:56:33 -080049using base::StringPrintf;
50using base::unique_fd;
51using netdutils::Status;
52using netdutils::statusFromErrno;
53using netdutils::StatusOr;
54
55Status TrafficController::loadAndAttachProgram(bpf_attach_type type, const char* path,
56 const char* name, base::unique_fd& cg_fd) {
57 base::unique_fd fd;
58 int ret = access(path, R_OK);
59 if (ret == 0) {
60 // The program already exist and we can access it.
61 return netdutils::status::ok;
62 }
63
64 if (errno != ENOENT) {
65 // The program exist but we cannot access it.
66 return statusFromErrno(errno, StringPrintf("Cannot access %s at path: %s", name, path));
67 }
68
69 // Program does not exist yet. Load, attach and pin it.
70 if (type == BPF_CGROUP_INET_EGRESS) {
71 fd.reset(loadEgressProg(mCookieTagMap.get(), mUidStatsMap.get(), mTagStatsMap.get(),
72 mUidCounterSetMap.get()));
73 } else {
74 fd.reset(loadIngressProg(mCookieTagMap.get(), mUidStatsMap.get(), mTagStatsMap.get(),
75 mUidCounterSetMap.get()));
76 }
77 if (fd < 0) {
78 return statusFromErrno(errno, StringPrintf("load %s failed", name));
79 }
80
81 ret = attachProgram(type, fd, cg_fd);
82 if (ret) {
83 return statusFromErrno(errno, StringPrintf("%s attach failed", name));
84 }
85
86 ret = mapPin(fd, path);
87 if (ret) {
88 return statusFromErrno(errno, StringPrintf("Pin %s as file failed(%s)", name, path));
89 }
90 return netdutils::status::ok;
91}
92
93Status TrafficController::start() {
Chenbo Fengf2759682017-10-10 17:31:57 -070094 int ret;
Chenbo Feng33cc1032017-10-23 15:16:37 -070095 struct utsname buf;
96 int kernel_version_major;
97 int kernel_version_minor;
98
99 ret = uname(&buf);
100 if (ret) {
Chenbo Fengc10a8a42017-12-15 13:56:33 -0800101 return statusFromErrno(errno, "Get system information failed");
Chenbo Feng33cc1032017-10-23 15:16:37 -0700102 }
Chenbo Fengc10a8a42017-12-15 13:56:33 -0800103
104 char dummy;
105 ret = sscanf(buf.release, "%d.%d%c", &kernel_version_major, &kernel_version_minor, &dummy);
106 if (ret >= 2 &&
107 ((kernel_version_major == 4 && kernel_version_minor >= 9) || (kernel_version_major > 4))) {
Chenbo Feng33cc1032017-10-23 15:16:37 -0700108 // Turn off the eBPF feature temporarily since the selinux rules and kernel changes are not
109 // landed yet.
110 // TODO: turn back on when all the other dependencies are ready.
111 ebpfSupported = false;
Chenbo Fengc10a8a42017-12-15 13:56:33 -0800112 return netdutils::status::ok;
Chenbo Feng33cc1032017-10-23 15:16:37 -0700113 } else {
114 ebpfSupported = false;
Chenbo Fengc10a8a42017-12-15 13:56:33 -0800115 return netdutils::status::ok;
Chenbo Fengf2759682017-10-10 17:31:57 -0700116 }
117
118 /* When netd restart from a crash without total system reboot, the program
119 * is still attached to the cgroup, detach it so the program can be freed
120 * and we can load and attach new program into the target cgroup.
121 *
122 * TODO: Scrape existing socket when run-time restart and clean up the map
123 * if the socket no longer exist
124 */
125
Chenbo Fengc10a8a42017-12-15 13:56:33 -0800126 ALOGI("START to load TrafficController");
127 base::unique_fd cg_fd(open(CGROUP_ROOT_PATH, O_DIRECTORY | O_RDONLY | O_CLOEXEC));
Chenbo Fengf2759682017-10-10 17:31:57 -0700128 if (cg_fd < 0) {
Chenbo Fengc10a8a42017-12-15 13:56:33 -0800129 return statusFromErrno(errno, "Failed to open the cgroup directory");
Chenbo Fengf2759682017-10-10 17:31:57 -0700130 }
131
Chenbo Fengc10a8a42017-12-15 13:56:33 -0800132 ASSIGN_OR_RETURN(mCookieTagMap,
133 setUpBPFMap(sizeof(uint64_t), sizeof(struct UidTag), COOKIE_UID_MAP_SIZE,
134 COOKIE_UID_MAP_PATH, BPF_MAP_TYPE_HASH));
Chenbo Fengf2759682017-10-10 17:31:57 -0700135
Chenbo Fengc10a8a42017-12-15 13:56:33 -0800136 // Allow both netd and system server to obtain map fd from the path. Chown the group to
137 // net_bw_acct does not grant all process in that group the permission to access bpf maps. They
138 // still need correct sepolicy to read/write the map. And only system_server and netd have that
139 // permission for now.
140 ret = chown(COOKIE_UID_MAP_PATH, AID_ROOT, AID_NET_BW_ACCT);
Chenbo Fengf2759682017-10-10 17:31:57 -0700141 if (ret) {
Chenbo Fengc10a8a42017-12-15 13:56:33 -0800142 return statusFromErrno(errno, "change cookieTagMap group failed.");
143 }
144 ret = chmod(COOKIE_UID_MAP_PATH, S_IRWXU | S_IRGRP | S_IWGRP);
145 if (ret) {
146 return statusFromErrno(errno, "change cookieTagMap mode failed.");
Chenbo Fengf2759682017-10-10 17:31:57 -0700147 }
148
Chenbo Fengc10a8a42017-12-15 13:56:33 -0800149 ASSIGN_OR_RETURN(mUidCounterSetMap,
150 setUpBPFMap(sizeof(uint32_t), sizeof(uint32_t), UID_COUNTERSET_MAP_SIZE,
151 UID_COUNTERSET_MAP_PATH, BPF_MAP_TYPE_HASH));
152 // Only netd can access the file.
153 ret = chmod(UID_COUNTERSET_MAP_PATH, S_IRWXU);
Chenbo Fengf2759682017-10-10 17:31:57 -0700154 if (ret) {
Chenbo Fengc10a8a42017-12-15 13:56:33 -0800155 return statusFromErrno(errno, "change uidCounterSetMap mode failed.");
Chenbo Fengf2759682017-10-10 17:31:57 -0700156 }
Chenbo Fengc10a8a42017-12-15 13:56:33 -0800157
158 ASSIGN_OR_RETURN(mUidStatsMap,
159 setUpBPFMap(sizeof(struct StatsKey), sizeof(struct Stats), UID_STATS_MAP_SIZE,
160 UID_STATS_MAP_PATH, BPF_MAP_TYPE_HASH));
161 // Change the file mode of pinned map so both netd and system server can get the map fd
162 // from it.
163 ret = chown(UID_STATS_MAP_PATH, AID_ROOT, AID_NET_BW_ACCT);
164 if (ret) {
165 return statusFromErrno(errno, "change uidStatsMap group failed.");
166 }
167 ret = chmod(UID_STATS_MAP_PATH, S_IRWXU | S_IRGRP | S_IWGRP);
168 if (ret) {
169 return statusFromErrno(errno, "change uidStatsMap mode failed.");
170 }
171
172 ASSIGN_OR_RETURN(mTagStatsMap,
173 setUpBPFMap(sizeof(struct StatsKey), sizeof(struct Stats), TAG_STATS_MAP_SIZE,
174 TAG_STATS_MAP_PATH, BPF_MAP_TYPE_HASH));
175 // Change the file mode of pinned map so both netd and system server can get the map fd
176 // from the path.
177 ret = chown(TAG_STATS_MAP_PATH, AID_ROOT, AID_NET_BW_STATS);
178 if (ret) {
179 return statusFromErrno(errno, "change tagStatsMap group failed.");
180 }
181 ret = chmod(TAG_STATS_MAP_PATH, S_IRWXU | S_IRGRP | S_IWGRP);
182 if (ret) {
183 return statusFromErrno(errno, "change tagStatsMap mode failed.");
184 }
185
186 RETURN_IF_NOT_OK(loadAndAttachProgram(BPF_CGROUP_INET_INGRESS, BPF_INGRESS_PROG_PATH,
187 "Ingress_prog", cg_fd));
188 return loadAndAttachProgram(BPF_CGROUP_INET_EGRESS, BPF_EGRESS_PROG_PATH, "egress_prog", cg_fd);
Chenbo Fengf2759682017-10-10 17:31:57 -0700189}
190
191uint64_t getSocketCookie(int sockFd) {
192 uint64_t sock_cookie;
193 socklen_t cookie_len = sizeof(sock_cookie);
194 int res = getsockopt(sockFd, SOL_SOCKET, SO_COOKIE, &sock_cookie, &cookie_len);
195 if (res < 0) {
196 res = -errno;
197 ALOGE("Failed to get socket cookie: %s\n", strerror(errno));
198 errno = -res;
199 // 0 is an invalid cookie. See INET_DIAG_NOCOOKIE.
200 return 0;
201 }
202 return sock_cookie;
203}
204
205int TrafficController::tagSocket(int sockFd, uint32_t tag, uid_t uid) {
Chenbo Fengc10a8a42017-12-15 13:56:33 -0800206 if (legacy_tagSocket(sockFd, tag, uid)) return -errno;
207 if (!ebpfSupported) return 0;
Chenbo Feng33cc1032017-10-23 15:16:37 -0700208
Chenbo Fengf2759682017-10-10 17:31:57 -0700209 uint64_t sock_cookie = getSocketCookie(sockFd);
210 if (sock_cookie == INET_DIAG_NOCOOKIE) return -errno;
211 UidTag newKey = {.uid = (uint32_t)uid, .tag = tag};
212
213 // Update the tag information of a socket to the cookieUidMap. Use BPF_ANY
214 // flag so it will insert a new entry to the map if that value doesn't exist
215 // yet. And update the tag if there is already a tag stored. Since the eBPF
216 // program in kernel only read this map, and is protected by rcu read lock. It
217 // should be fine to cocurrently update the map while eBPF program is running.
218 int res = writeToMapEntry(mCookieTagMap, &sock_cookie, &newKey, BPF_ANY);
219 if (res < 0) {
220 res = -errno;
Chenbo Fengc10a8a42017-12-15 13:56:33 -0800221 ALOGE("Failed to tag the socket: %s, fd: %d", strerror(errno), mCookieTagMap.get());
Chenbo Fengf2759682017-10-10 17:31:57 -0700222 }
223
224 return res;
225}
226
227int TrafficController::untagSocket(int sockFd) {
Chenbo Fengc10a8a42017-12-15 13:56:33 -0800228 if (legacy_untagSocket(sockFd)) return -errno;
Chenbo Feng33cc1032017-10-23 15:16:37 -0700229 if (!ebpfSupported) return 0;
Chenbo Fengf2759682017-10-10 17:31:57 -0700230 uint64_t sock_cookie = getSocketCookie(sockFd);
231
232 if (sock_cookie == INET_DIAG_NOCOOKIE) return -errno;
233 int res = deleteMapEntry(mCookieTagMap, &sock_cookie);
234 if (res) {
235 res = -errno;
236 ALOGE("Failed to untag socket: %s\n", strerror(errno));
237 }
238 return res;
239}
240
241int TrafficController::setCounterSet(int counterSetNum, uid_t uid) {
242 if (counterSetNum < 0 || counterSetNum >= COUNTERSETS_LIMIT) return -EINVAL;
Chenbo Fengf2759682017-10-10 17:31:57 -0700243 int res;
Chenbo Fengc10a8a42017-12-15 13:56:33 -0800244 if (legacy_setCounterSet(counterSetNum, uid)) return -errno;
245 if (!ebpfSupported) return 0;
246
247 // The default counter set for all uid is 0, so deleting the current counterset for that uid
248 // will automatically set it to 0.
Chenbo Fengf2759682017-10-10 17:31:57 -0700249 if (counterSetNum == 0) {
250 res = deleteMapEntry(mUidCounterSetMap, &uid);
251 if (res == 0 || (res == -1 && errno == ENOENT)) {
252 return 0;
253 } else {
254 ALOGE("Failed to delete the counterSet: %s\n", strerror(errno));
255 return -errno;
256 }
257 }
258
259 res = writeToMapEntry(mUidCounterSetMap, &uid, &counterSetNum, BPF_ANY);
260 if (res < 0) {
261 res = -errno;
Chenbo Fengc10a8a42017-12-15 13:56:33 -0800262 ALOGE("Failed to set the counterSet: %s, fd: %d", strerror(errno), mUidCounterSetMap.get());
Chenbo Fengf2759682017-10-10 17:31:57 -0700263 }
264 return res;
265}
266
Chenbo Fengc10a8a42017-12-15 13:56:33 -0800267// TODO: Add a lock for delete tag Data so when several request for different uid comes in, they do
268// not race with each other.
Chenbo Fengf2759682017-10-10 17:31:57 -0700269int TrafficController::deleteTagData(uint32_t tag, uid_t uid) {
270 int res = 0;
Chenbo Feng33cc1032017-10-23 15:16:37 -0700271
Chenbo Fengc10a8a42017-12-15 13:56:33 -0800272 if (legacy_deleteTagData(tag, uid)) return -errno;
Chenbo Feng33cc1032017-10-23 15:16:37 -0700273 if (!ebpfSupported) return 0;
274
Chenbo Fengc10a8a42017-12-15 13:56:33 -0800275 uint64_t curCookie = NONEXIST_COOKIE;
Chenbo Fengf2759682017-10-10 17:31:57 -0700276 uint64_t nextCookie = 0;
277 UidTag tmp_uidtag;
Chenbo Fengc10a8a42017-12-15 13:56:33 -0800278 std::vector<uint64_t> cookieList;
279 // First we go through the cookieTagMap to delete the target uid tag combination. Or delete all
280 // the tags related to the uid if the tag is 0, we start the map iteration with a cookie of
281 // INET_DIAG_NOCOOKIE because it's guaranteed that that will not be in the map.
282 while (getNextMapKey(mCookieTagMap, &curCookie, &nextCookie) != -1) {
283 res = findMapEntry(mCookieTagMap, &nextCookie, &tmp_uidtag);
Chenbo Fengf2759682017-10-10 17:31:57 -0700284 if (res < 0) {
285 res = -errno;
Chenbo Fengc10a8a42017-12-15 13:56:33 -0800286 ALOGE("Failed to get tag info(cookie = %" PRIu64 ": %s\n", nextCookie, strerror(errno));
287 // Continue to look for next entry.
288 curCookie = nextCookie;
289 continue;
Chenbo Fengf2759682017-10-10 17:31:57 -0700290 }
291
292 if (tmp_uidtag.uid == uid && (tmp_uidtag.tag == tag || tag == 0)) {
Chenbo Fengc10a8a42017-12-15 13:56:33 -0800293 res = deleteMapEntry(mCookieTagMap, &nextCookie);
294 if (res < 0 && errno != ENOENT) {
Chenbo Fengf2759682017-10-10 17:31:57 -0700295 res = -errno;
Chenbo Fengc10a8a42017-12-15 13:56:33 -0800296 ALOGE("Failed to delete data(cookie = %" PRIu64 "): %s\n", nextCookie,
297 strerror(errno));
Chenbo Fengf2759682017-10-10 17:31:57 -0700298 }
Chenbo Fengc10a8a42017-12-15 13:56:33 -0800299 } else {
300 // Move forward to next cookie in the map.
Chenbo Fengf2759682017-10-10 17:31:57 -0700301 curCookie = nextCookie;
302 }
303 }
304
305 // Now we go through the Tag stats map and delete the data entry with correct uid and tag
Chenbo Fengc10a8a42017-12-15 13:56:33 -0800306 // combination. Or all tag stats under that uid if the target tag is 0. The initial key is
307 // set to the nonexist_statskey because it will never be in the map, and thus getNextMapKey will
308 // return 0 and set nextKey to the first key in the map.
Chenbo Fengf2759682017-10-10 17:31:57 -0700309 struct StatsKey curKey, nextKey;
Chenbo Fengc10a8a42017-12-15 13:56:33 -0800310 curKey = NONEXIST_STATSKEY;
311 while (getNextMapKey(mTagStatsMap, &curKey, &nextKey) != -1) {
312 if (nextKey.uid == uid && (nextKey.tag == tag || tag == 0)) {
313 res = deleteMapEntry(mTagStatsMap, &nextKey);
314 if (res < 0 && errno != ENOENT) {
315 // Skip the current entry if unexpected error happened.
316 ALOGE("Failed to delete data(uid=%u, tag=%u): %s\n", nextKey.uid, nextKey.tag,
317 strerror(errno));
318 curKey = nextKey;
Chenbo Fengf2759682017-10-10 17:31:57 -0700319 }
Chenbo Fengc10a8a42017-12-15 13:56:33 -0800320 } else {
Chenbo Fengf2759682017-10-10 17:31:57 -0700321 curKey = nextKey;
322 }
323 }
324
325 // If the tag is not zero, we already deleted all the data entry required. If tag is 0, we also
326 // need to delete the stats stored in uidStatsMap
Chenbo Fengc10a8a42017-12-15 13:56:33 -0800327 if (tag != 0) return 0;
328
329 res = deleteMapEntry(mUidCounterSetMap, &uid);
330 if (res < 0 && errno != ENOENT) {
331 ALOGE("Failed to delete counterSet data(uid=%u, tag=%u): %s\n", uid, tag, strerror(errno));
332 }
333
334 // For the uid stats deleted from the map, move them into a special
335 // removed uid entry. The removed uid is stored in uid 0, tag 0 and
336 // counterSet as COUNTERSETS_LIMIT.
337 StatsKey removedStatsKey = {0, 0, COUNTERSETS_LIMIT, 0};
338 Stats removedStatsTotal = {};
339 res = findMapEntry(mUidStatsMap, &removedStatsKey, &removedStatsTotal);
340 if (res < 0 && errno != ENOENT) {
341 ALOGE("Failed to get stats of removed uid: %s", strerror(errno));
342 }
343
344 curKey = NONEXIST_STATSKEY;
345 while (getNextMapKey(mUidStatsMap, &curKey, &nextKey) != -1) {
346 if (nextKey.uid == uid) {
347 Stats old_stats = {};
348 res = findMapEntry(mUidStatsMap, &nextKey, &old_stats);
349 if (res < 0) {
350 if (errno != ENOENT) {
351 // if errno is ENOENT Somebody else deleted nextKey. Lookup the next key from
352 // curKey. If we have other error. Skip this key to avoid an infinite loop.
353 curKey = nextKey;
354 }
355 continue;
Chenbo Fengf2759682017-10-10 17:31:57 -0700356 }
Chenbo Fengc10a8a42017-12-15 13:56:33 -0800357 res = deleteMapEntry(mUidStatsMap, &nextKey);
358 if (res < 0 && errno != ENOENT) {
359 ALOGE("Failed to delete data(uid=%u, tag=%u): %s\n", nextKey.uid, nextKey.tag,
360 strerror(errno));
361 curKey = nextKey;
362 continue;
363 }
364 removedStatsTotal.rxTcpPackets += old_stats.rxTcpPackets;
365 removedStatsTotal.rxTcpBytes += old_stats.rxTcpBytes;
366 removedStatsTotal.txTcpPackets += old_stats.txTcpPackets;
367 removedStatsTotal.txTcpBytes += old_stats.txTcpBytes;
368 removedStatsTotal.rxUdpPackets += old_stats.rxUdpPackets;
369 removedStatsTotal.rxUdpBytes += old_stats.rxUdpBytes;
370 removedStatsTotal.txUdpPackets += old_stats.txUdpPackets;
371 removedStatsTotal.txUdpBytes += old_stats.txUdpBytes;
372 removedStatsTotal.rxOtherPackets += old_stats.rxOtherPackets;
373 removedStatsTotal.rxOtherBytes += old_stats.rxOtherBytes;
374 removedStatsTotal.txOtherPackets += old_stats.txOtherPackets;
375 removedStatsTotal.txOtherBytes += old_stats.txOtherBytes;
376 } else {
Chenbo Fengf2759682017-10-10 17:31:57 -0700377 curKey = nextKey;
378 }
379 }
Chenbo Fengc10a8a42017-12-15 13:56:33 -0800380
381 res = writeToMapEntry(mUidStatsMap, &removedStatsKey, &removedStatsTotal, BPF_ANY);
382 if (res) {
383 res = -errno;
384 ALOGE("Failed to add deleting stats to removed uid: %s", strerror(errno));
385 }
Chenbo Fengf2759682017-10-10 17:31:57 -0700386 return res;
387}
388
Chenbo Fengf2759682017-10-10 17:31:57 -0700389} // namespace net
390} // namespace android