Merge "TetherController: Fix a memory and fd leak"
diff --git a/bpf_progs/Android.bp b/bpf_progs/Android.bp
new file mode 100644
index 0000000..4302129
--- /dev/null
+++ b/bpf_progs/Android.bp
@@ -0,0 +1,31 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+//
+// bpf kernel programs
+//
+bpf {
+ name: "netd.o",
+ srcs: ["netd.c"],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+ include_dirs: [
+ "system/netd/libnetdbpf/include",
+ "system/netd/libnetdutils/include",
+ ],
+}
diff --git a/bpf_progs/netd.c b/bpf_progs/netd.c
new file mode 100644
index 0000000..38782f9
--- /dev/null
+++ b/bpf_progs/netd.c
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "netd.h"
+#include <linux/bpf.h>
+
+SEC("cgroupskb/ingress/stats")
+int bpf_cgroup_ingress(struct __sk_buff* skb) {
+ return bpf_traffic_account(skb, BPF_INGRESS);
+}
+
+SEC("cgroupskb/egress/stats")
+int bpf_cgroup_egress(struct __sk_buff* skb) {
+ return bpf_traffic_account(skb, BPF_EGRESS);
+}
+
+SEC("skfilter/egress/xtbpf")
+int xt_bpf_egress_prog(struct __sk_buff* skb) {
+ uint32_t key = skb->ifindex;
+ bpf_update_stats(skb, &iface_stats_map, BPF_EGRESS, &key);
+ return BPF_MATCH;
+}
+
+SEC("skfilter/ingress/xtbpf")
+int xt_bpf_ingress_prog(struct __sk_buff* skb) {
+ uint32_t key = skb->ifindex;
+ bpf_update_stats(skb, &iface_stats_map, BPF_INGRESS, &key);
+ return BPF_MATCH;
+}
+
+SEC("skfilter/whitelist/xtbpf")
+int xt_bpf_whitelist_prog(struct __sk_buff* skb) {
+ uint32_t sock_uid = bpf_get_socket_uid(skb);
+ if (is_system_uid(sock_uid)) return BPF_MATCH;
+ uint8_t* whitelistMatch = bpf_map_lookup_elem(&uid_owner_map, &sock_uid);
+ if (whitelistMatch) return *whitelistMatch & HAPPY_BOX_MATCH;
+ return BPF_NOMATCH;
+}
+
+SEC("skfilter/blacklist/xtbpf")
+int xt_bpf_blacklist_prog(struct __sk_buff* skb) {
+ uint32_t sock_uid = bpf_get_socket_uid(skb);
+ uint8_t* blacklistMatch = bpf_map_lookup_elem(&uid_owner_map, &sock_uid);
+ if (blacklistMatch) return *blacklistMatch & PENALTY_BOX_MATCH;
+ return BPF_NOMATCH;
+}
+
+struct bpf_map_def SEC("maps") uid_permission_map = {
+ .type = BPF_MAP_TYPE_HASH,
+ .key_size = sizeof(uint32_t),
+ .value_size = sizeof(uint8_t),
+ .max_entries = UID_OWNER_MAP_SIZE,
+};
+
+SEC("cgroupsock/inet/creat")
+int inet_socket_create(struct bpf_sock* sk) {
+ uint64_t gid_uid = bpf_get_current_uid_gid();
+ /*
+ * A given app is guaranteed to have the same app ID in all the profiles in
+ * which it is installed, and install permission is granted to app for all
+ * user at install time so we only check the appId part of a request uid at
+ * run time. See UserHandle#isSameApp for detail.
+ */
+ uint32_t appId = (gid_uid & 0xffffffff) % PER_USER_RANGE;
+ uint8_t* internetPermission = bpf_map_lookup_elem(&uid_permission_map, &appId);
+ if (internetPermission) return *internetPermission & ALLOW_SOCK_CREATE;
+ return NO_PERMISSION;
+}
+
+char _license[] SEC("license") = "Apache 2.0";
diff --git a/bpf_progs/netd.h b/bpf_progs/netd.h
new file mode 100644
index 0000000..3c36807
--- /dev/null
+++ b/bpf_progs/netd.h
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * This h file together with netd.c is used for compiling the eBPF kernel
+ * program.
+ */
+
+#include <bpf_helpers.h>
+#include <linux/bpf.h>
+#include <linux/if.h>
+#include <linux/if_ether.h>
+#include <linux/in.h>
+#include <linux/in6.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include "netdbpf/bpf_shared.h"
+
+struct uid_tag {
+ uint32_t uid;
+ uint32_t tag;
+};
+
+struct stats_key {
+ uint32_t uid;
+ uint32_t tag;
+ uint32_t counterSet;
+ uint32_t ifaceIndex;
+};
+
+struct stats_value {
+ uint64_t rxPackets;
+ uint64_t rxBytes;
+ uint64_t txPackets;
+ uint64_t txBytes;
+};
+
+struct IfaceValue {
+ char name[IFNAMSIZ];
+};
+
+// This is defined for cgroup bpf filter only.
+#define BPF_PASS 1
+#define BPF_DROP 0
+
+// This is used for xt_bpf program only.
+#define BPF_NOMATCH 0
+#define BPF_MATCH 1
+
+#define BPF_EGRESS 0
+#define BPF_INGRESS 1
+
+#define IP_PROTO_OFF offsetof(struct iphdr, protocol)
+#define IPV6_PROTO_OFF offsetof(struct ipv6hdr, nexthdr)
+#define IPPROTO_IHL_OFF 0
+#define TCP_FLAG_OFF 13
+#define RST_OFFSET 2
+
+struct bpf_map_def SEC("maps") cookie_tag_map = {
+ .type = BPF_MAP_TYPE_HASH,
+ .key_size = sizeof(uint64_t),
+ .value_size = sizeof(struct uid_tag),
+ .max_entries = COOKIE_UID_MAP_SIZE,
+};
+
+struct bpf_map_def SEC("maps") uid_counterset_map = {
+ .type = BPF_MAP_TYPE_HASH,
+ .key_size = sizeof(uint32_t),
+ .value_size = sizeof(uint8_t),
+ .max_entries = UID_COUNTERSET_MAP_SIZE,
+};
+
+struct bpf_map_def SEC("maps") app_uid_stats_map = {
+ .type = BPF_MAP_TYPE_HASH,
+ .key_size = sizeof(uint32_t),
+ .value_size = sizeof(struct stats_value),
+ .max_entries = APP_STATS_MAP_SIZE,
+};
+
+struct bpf_map_def SEC("maps") stats_map_A = {
+ .type = BPF_MAP_TYPE_HASH,
+ .key_size = sizeof(struct stats_key),
+ .value_size = sizeof(struct stats_value),
+ .max_entries = STATS_MAP_SIZE,
+};
+
+struct bpf_map_def SEC("maps") stats_map_B = {
+ .type = BPF_MAP_TYPE_HASH,
+ .key_size = sizeof(struct stats_key),
+ .value_size = sizeof(struct stats_value),
+ .max_entries = STATS_MAP_SIZE,
+};
+
+struct bpf_map_def SEC("maps") iface_stats_map = {
+ .type = BPF_MAP_TYPE_HASH,
+ .key_size = sizeof(uint32_t),
+ .value_size = sizeof(struct stats_value),
+ .max_entries = IFACE_STATS_MAP_SIZE,
+};
+
+struct bpf_map_def SEC("maps") configuration_map = {
+ .type = BPF_MAP_TYPE_HASH,
+ .key_size = sizeof(uint32_t),
+ .value_size = sizeof(uint8_t),
+ .max_entries = CONFIGURATION_MAP_SIZE,
+};
+
+struct bpf_map_def SEC("maps") uid_owner_map = {
+ .type = BPF_MAP_TYPE_HASH,
+ .key_size = sizeof(uint32_t),
+ .value_size = sizeof(uint8_t),
+ .max_entries = UID_OWNER_MAP_SIZE,
+};
+
+struct bpf_map_def SEC("maps") iface_index_name_map = {
+ .type = BPF_MAP_TYPE_HASH,
+ .key_size = sizeof(uint32_t),
+ .value_size = sizeof(struct IfaceValue),
+ .max_entries = IFACE_INDEX_NAME_MAP_SIZE,
+};
+
+static __always_inline int is_system_uid(uint32_t uid) {
+ return (uid <= MAX_SYSTEM_UID) && (uid >= MIN_SYSTEM_UID);
+}
+
+static __always_inline inline void bpf_update_stats(struct __sk_buff* skb, struct bpf_map_def* map,
+ int direction, void* key) {
+ struct stats_value* value;
+ value = bpf_map_lookup_elem(map, key);
+ if (!value) {
+ struct stats_value newValue = {};
+ bpf_map_update_elem(map, key, &newValue, BPF_NOEXIST);
+ value = bpf_map_lookup_elem(map, key);
+ }
+ if (value) {
+ if (direction == BPF_EGRESS) {
+ __sync_fetch_and_add(&value->txPackets, 1);
+ __sync_fetch_and_add(&value->txBytes, skb->len);
+ } else if (direction == BPF_INGRESS) {
+ __sync_fetch_and_add(&value->rxPackets, 1);
+ __sync_fetch_and_add(&value->rxBytes, skb->len);
+ }
+ }
+}
+
+static inline bool skip_owner_match(struct __sk_buff* skb) {
+ int offset = -1;
+ int ret = 0;
+ if (skb->protocol == ETH_P_IP) {
+ offset = IP_PROTO_OFF;
+ uint8_t proto, ihl;
+ uint16_t flag;
+ ret = bpf_skb_load_bytes(skb, offset, &proto, 1);
+ if (!ret) {
+ if (proto == IPPROTO_ESP) {
+ return true;
+ } else if (proto == IPPROTO_TCP) {
+ ret = bpf_skb_load_bytes(skb, IPPROTO_IHL_OFF, &ihl, 1);
+ ihl = ihl & 0x0F;
+ ret = bpf_skb_load_bytes(skb, ihl * 4 + TCP_FLAG_OFF, &flag, 1);
+ if (ret == 0 && (flag >> RST_OFFSET & 1)) {
+ return true;
+ }
+ }
+ }
+ } else if (skb->protocol == ETH_P_IPV6) {
+ offset = IPV6_PROTO_OFF;
+ uint8_t proto;
+ ret = bpf_skb_load_bytes(skb, offset, &proto, 1);
+ if (!ret) {
+ if (proto == IPPROTO_ESP) {
+ return true;
+ } else if (proto == IPPROTO_TCP) {
+ uint16_t flag;
+ ret = bpf_skb_load_bytes(skb, sizeof(struct ipv6hdr) + TCP_FLAG_OFF, &flag, 1);
+ if (ret == 0 && (flag >> RST_OFFSET & 1)) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
+
+static __always_inline BpfConfig getConfig(uint32_t configKey) {
+ uint32_t mapSettingKey = configKey;
+ BpfConfig* config = bpf_map_lookup_elem(&configuration_map, &mapSettingKey);
+ if (!config) {
+ // Couldn't read configuration entry. Assume everything is disabled.
+ return DEFAULT_CONFIG;
+ }
+ return *config;
+}
+
+static inline int bpf_owner_match(struct __sk_buff* skb, uint32_t uid) {
+ if (skip_owner_match(skb)) return BPF_PASS;
+
+ if ((uid <= MAX_SYSTEM_UID) && (uid >= MIN_SYSTEM_UID)) return BPF_PASS;
+
+ BpfConfig enabledRules = getConfig(UID_RULES_CONFIGURATION_KEY);
+ if (!enabledRules) {
+ return BPF_PASS;
+ }
+
+ uint8_t* uidEntry = bpf_map_lookup_elem(&uid_owner_map, &uid);
+ uint8_t uidRules = uidEntry ? *uidEntry : 0;
+ if ((enabledRules & DOZABLE_MATCH) && !(uidRules & DOZABLE_MATCH)) {
+ return BPF_DROP;
+ }
+ if ((enabledRules & STANDBY_MATCH) && (uidRules & STANDBY_MATCH)) {
+ return BPF_DROP;
+ }
+ if ((enabledRules & POWERSAVE_MATCH) && !(uidRules & POWERSAVE_MATCH)) {
+ return BPF_DROP;
+ }
+ return BPF_PASS;
+}
+
+static __always_inline inline void update_stats_with_config(struct __sk_buff* skb, int direction,
+ void* key, uint8_t selectedMap) {
+ if (selectedMap == SELECT_MAP_A) {
+ bpf_update_stats(skb, &stats_map_A, direction, key);
+ } else if (selectedMap == SELECT_MAP_B) {
+ bpf_update_stats(skb, &stats_map_B, direction, key);
+ }
+}
+
+static __always_inline inline int bpf_traffic_account(struct __sk_buff* skb, int direction) {
+ uint32_t sock_uid = bpf_get_socket_uid(skb);
+ int match = bpf_owner_match(skb, sock_uid);
+ if ((direction == BPF_EGRESS) && (match == BPF_DROP)) {
+ // If an outbound packet is going to be dropped, we do not count that
+ // traffic.
+ return match;
+ }
+
+ uint64_t cookie = bpf_get_socket_cookie(skb);
+ struct uid_tag* utag = bpf_map_lookup_elem(&cookie_tag_map, &cookie);
+ uint32_t uid, tag;
+ if (utag) {
+ uid = utag->uid;
+ tag = utag->tag;
+ } else {
+ uid = sock_uid;
+ tag = 0;
+ }
+
+ struct stats_key key = {.uid = uid, .tag = tag, .counterSet = 0, .ifaceIndex = skb->ifindex};
+
+ uint8_t* counterSet = bpf_map_lookup_elem(&uid_counterset_map, &uid);
+ if (counterSet) key.counterSet = (uint32_t)*counterSet;
+
+ uint32_t mapSettingKey = CURRENT_STATS_MAP_CONFIGURATION_KEY;
+ uint8_t* selectedMap = bpf_map_lookup_elem(&configuration_map, &mapSettingKey);
+ if (!selectedMap) {
+ return match;
+ }
+
+ if (tag) {
+ update_stats_with_config(skb, direction, &key, *selectedMap);
+ }
+
+ key.tag = 0;
+ update_stats_with_config(skb, direction, &key, *selectedMap);
+ bpf_update_stats(skb, &app_uid_stats_map, direction, &uid);
+ return match;
+}
diff --git a/libnetdbpf/BpfNetworkStatsTest.cpp b/libnetdbpf/BpfNetworkStatsTest.cpp
index 0d17677..8f95787 100644
--- a/libnetdbpf/BpfNetworkStatsTest.cpp
+++ b/libnetdbpf/BpfNetworkStatsTest.cpp
@@ -38,11 +38,6 @@
#include "bpf/BpfUtils.h"
#include "netdbpf/BpfNetworkStats.h"
-using ::testing::_;
-using ::testing::ByMove;
-using ::testing::Invoke;
-using ::testing::Return;
-using ::testing::StrictMock;
using ::testing::Test;
namespace android {
diff --git a/server/MDnsSdListener.cpp b/server/MDnsSdListener.cpp
index f96df1a..83726c3 100644
--- a/server/MDnsSdListener.cpp
+++ b/server/MDnsSdListener.cpp
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+#include "MDnsSdListener.h"
+
#include <arpa/inet.h>
#include <dirent.h>
#include <errno.h>
@@ -21,12 +23,12 @@
#include <netdb.h>
#include <netinet/in.h>
#include <pthread.h>
+#include <resolv.h>
#include <stdlib.h>
+#include <string.h>
#include <sys/poll.h>
#include <sys/socket.h>
#include <sys/types.h>
-#include <string.h>
-#include <resolv.h>
#define LOG_TAG "MDnsDS"
#define DBG 1
@@ -36,7 +38,6 @@
#include <log/log.h>
#include <sysutils/SocketClient.h>
-#include "MDnsSdListener.h"
#include "ResponseCode.h"
#include "thread_util.h"
@@ -524,7 +525,6 @@
mPollRefs = nullptr;
mPollSize = 10;
socketpair(AF_LOCAL, SOCK_STREAM, 0, mCtrlSocketPair);
- pthread_mutex_init(&mHeadMutex, nullptr);
const int rval = ::android::net::threadLaunch(this);
if (rval != 0) {
@@ -554,35 +554,27 @@
}
int MDnsSdListener::Monitor::startService() {
- int result = 0;
char property_value[PROPERTY_VALUE_MAX];
- pthread_mutex_lock(&mHeadMutex);
+ std::lock_guard guard(mMutex);
property_get(MDNS_SERVICE_STATUS, property_value, "");
if (strcmp("running", property_value) != 0) {
ALOGD("Starting MDNSD");
property_set("ctl.start", MDNS_SERVICE_NAME);
wait_for_property(MDNS_SERVICE_STATUS, "running", 5);
- result = -1;
- } else {
- result = 0;
+ return -1;
}
- pthread_mutex_unlock(&mHeadMutex);
- return result;
+ return 0;
}
int MDnsSdListener::Monitor::stopService() {
- int result = 0;
- pthread_mutex_lock(&mHeadMutex);
+ std::lock_guard guard(mMutex);
if (mHead == nullptr) {
ALOGD("Stopping MDNSD");
property_set("ctl.stop", MDNS_SERVICE_NAME);
wait_for_property(MDNS_SERVICE_STATUS, "stopped", 5);
- result = -1;
- } else {
- result = 0;
+ return -1;
}
- pthread_mutex_unlock(&mHeadMutex);
- return result;
+ return 0;
}
void MDnsSdListener::Monitor::run() {
@@ -612,9 +604,8 @@
ALOGD("Monitor found [%d].revents = %d - calling ProcessResults",
i, mPollFds[i].revents);
}
- pthread_mutex_lock(&mHeadMutex);
+ std::lock_guard guard(mMutex);
DNSServiceProcessResult(*(mPollRefs[i]));
- pthread_mutex_unlock(&mHeadMutex);
mPollFds[i].revents = 0;
}
}
@@ -645,7 +636,7 @@
if (VDBG) {
ALOGD("MDnsSdListener::Monitor poll rescanning - size=%d, live=%d", mPollSize, mLiveCount);
}
- pthread_mutex_lock(&mHeadMutex);
+ std::lock_guard guard(mMutex);
Element **prevPtr = &mHead;
int i = 1;
if (mPollSize <= mLiveCount) {
@@ -690,7 +681,7 @@
prevPtr = &((*prevPtr)->mNext);
}
}
- pthread_mutex_unlock(&mHeadMutex);
+
return i;
}
@@ -700,53 +691,45 @@
return nullptr;
}
Element *e = new Element(id, context);
- pthread_mutex_lock(&mHeadMutex);
+ std::lock_guard guard(mMutex);
e->mNext = mHead;
mHead = e;
- pthread_mutex_unlock(&mHeadMutex);
return &(e->mRef);
}
DNSServiceRef *MDnsSdListener::Monitor::lookupServiceRef(int id) {
- pthread_mutex_lock(&mHeadMutex);
+ std::lock_guard guard(mMutex);
Element *cur = mHead;
while (cur != nullptr) {
if (cur->mId == id) {
DNSServiceRef *result = &(cur->mRef);
- pthread_mutex_unlock(&mHeadMutex);
return result;
}
cur = cur->mNext;
}
- pthread_mutex_unlock(&mHeadMutex);
return nullptr;
}
void MDnsSdListener::Monitor::startMonitoring(int id) {
if (VDBG) ALOGD("startMonitoring %d", id);
- pthread_mutex_lock(&mHeadMutex);
- Element *cur = mHead;
- while (cur != nullptr) {
+ std::lock_guard guard(mMutex);
+ for (Element* cur = mHead; cur != nullptr; cur = cur->mNext) {
if (cur->mId == id) {
if (DBG_RESCAN) ALOGD("marking %p as ready to be added", cur);
mLiveCount++;
cur->mReady = 1;
- pthread_mutex_unlock(&mHeadMutex);
write(mCtrlSocketPair[1], RESCAN, 1); // trigger a rescan for a fresh poll
if (VDBG) ALOGD("triggering rescan");
return;
}
- cur = cur->mNext;
}
- pthread_mutex_unlock(&mHeadMutex);
}
void MDnsSdListener::Monitor::freeServiceRef(int id) {
if (VDBG) ALOGD("freeServiceRef %d", id);
- pthread_mutex_lock(&mHeadMutex);
- Element **prevPtr = &mHead;
- Element *cur;
- while (*prevPtr != nullptr) {
+ std::lock_guard guard(mMutex);
+ Element* cur;
+ for (Element** prevPtr = &mHead; *prevPtr != nullptr; prevPtr = &(cur->mNext)) {
cur = *prevPtr;
if (cur->mId == id) {
if (DBG_RESCAN) ALOGD("marking %p as ready to be removed", cur);
@@ -760,16 +743,12 @@
*prevPtr = cur->mNext;
delete cur;
}
- pthread_mutex_unlock(&mHeadMutex);
return;
}
- prevPtr = &(cur->mNext);
}
- pthread_mutex_unlock(&mHeadMutex);
}
void MDnsSdListener::Monitor::deallocateServiceRef(DNSServiceRef* ref) {
- pthread_mutex_lock(&mHeadMutex);
+ std::lock_guard guard(mMutex);
DNSServiceRefDeallocate(*ref);
- pthread_mutex_unlock(&mHeadMutex);
}
diff --git a/server/MDnsSdListener.h b/server/MDnsSdListener.h
index adec855..47ddc28 100644
--- a/server/MDnsSdListener.h
+++ b/server/MDnsSdListener.h
@@ -17,10 +17,10 @@
#ifndef _MDNSSDLISTENER_H__
#define _MDNSSDLISTENER_H__
-#include <pthread.h>
-
+#include <android-base/thread_annotations.h>
#include <dns_sd.h>
#include <sysutils/FrameworkListener.h>
+#include <mutex>
#include "NetdCommand.h"
@@ -93,13 +93,13 @@
Context *mContext;
int mReady = 0;
};
- Element *mHead;
+ Element* mHead GUARDED_BY(mMutex);
int mLiveCount;
struct pollfd *mPollFds;
DNSServiceRef **mPollRefs;
int mPollSize;
int mCtrlSocketPair[2];
- pthread_mutex_t mHeadMutex;
+ std::mutex mMutex;
};
class Handler : public NetdCommand {