Merge "Create Cronet's CTS for CronetEngine APIs"
diff --git a/OWNERS_core_networking b/OWNERS_core_networking
index 172670e..6d17476 100644
--- a/OWNERS_core_networking
+++ b/OWNERS_core_networking
@@ -1,4 +1,3 @@
-chenbruce@google.com
chiachangwang@google.com
cken@google.com
huangaaron@google.com
diff --git a/bpf_progs/bpf_net_helpers.h b/bpf_progs/bpf_net_helpers.h
index c39269e..b7ca3af 100644
--- a/bpf_progs/bpf_net_helpers.h
+++ b/bpf_progs/bpf_net_helpers.h
@@ -21,6 +21,18 @@
#include <stdbool.h>
#include <stdint.h>
+// bionic kernel uapi linux/udp.h header is munged...
+#define __kernel_udphdr udphdr
+#include <linux/udp.h>
+
+// Offsets from beginning of L4 (TCP/UDP) header
+#define TCP_OFFSET(field) offsetof(struct tcphdr, field)
+#define UDP_OFFSET(field) offsetof(struct udphdr, field)
+
+// Offsets from beginning of L3 (IPv4/IPv6) header
+#define IP4_OFFSET(field) offsetof(struct iphdr, field)
+#define IP6_OFFSET(field) offsetof(struct ipv6hdr, field)
+
// this returns 0 iff skb->sk is NULL
static uint64_t (*bpf_get_socket_cookie)(struct __sk_buff* skb) = (void*)BPF_FUNC_get_socket_cookie;
diff --git a/bpf_progs/netd.c b/bpf_progs/netd.c
index 1272f96..84da79d 100644
--- a/bpf_progs/netd.c
+++ b/bpf_progs/netd.c
@@ -46,8 +46,9 @@
static const bool INGRESS = false;
static const bool EGRESS = true;
-#define IP_PROTO_OFF offsetof(struct iphdr, protocol)
-#define IPV6_PROTO_OFF offsetof(struct ipv6hdr, nexthdr)
+// Used for 'bool enable_tracing'
+static const bool TRACE_ON = true;
+static const bool TRACE_OFF = false;
// offsetof(struct iphdr, ihl) -- but that's a bitfield
#define IPPROTO_IHL_OFF 0
@@ -99,6 +100,19 @@
/* never actually used from ebpf */
DEFINE_BPF_MAP_NO_NETD(iface_index_name_map, HASH, uint32_t, IfaceValue, IFACE_INDEX_NAME_MAP_SIZE)
+// A single-element configuration array, packet tracing is enabled when 'true'.
+DEFINE_BPF_MAP_EXT(packet_trace_enabled_map, ARRAY, uint32_t, bool, 1,
+ AID_ROOT, AID_SYSTEM, 0060, "fs_bpf_net_shared", "", false,
+ BPFLOADER_IGNORED_ON_VERSION, BPFLOADER_MAX_VER, /*ignore_on_eng*/false,
+ /*ignore_on_user*/true, /*ignore_on_userdebug*/false)
+
+// A ring buffer on which packet information is pushed. This map will only be loaded
+// on eng and userdebug devices. User devices won't load this to save memory.
+DEFINE_BPF_RINGBUF_EXT(packet_trace_ringbuf, PacketTrace, PACKET_TRACE_BUF_SIZE,
+ AID_ROOT, AID_SYSTEM, 0060, "fs_bpf_net_shared", "", false,
+ BPFLOADER_IGNORED_ON_VERSION, BPFLOADER_MAX_VER, /*ignore_on_eng*/false,
+ /*ignore_on_user*/true, /*ignore_on_userdebug*/false);
+
// iptables xt_bpf programs need to be usable by both netd and netutils_wrappers
// selinux contexts, because even non-xt_bpf iptables mutations are implemented as
// a full table dump, followed by an update in userspace, and then a reload into the kernel,
@@ -226,12 +240,72 @@
: bpf_skb_load_bytes(skb, L3_off, to, len);
}
+static __always_inline inline void do_packet_tracing(
+ const struct __sk_buff* const skb, const bool egress, const uint32_t uid,
+ const uint32_t tag, const bool enable_tracing, const unsigned kver) {
+ if (!enable_tracing) return;
+ if (kver < KVER(5, 8, 0)) return;
+
+ uint32_t mapKey = 0;
+ bool* traceConfig = bpf_packet_trace_enabled_map_lookup_elem(&mapKey);
+ if (traceConfig == NULL) return;
+ if (*traceConfig == false) return;
+
+ PacketTrace* pkt = bpf_packet_trace_ringbuf_reserve();
+ if (pkt == NULL) return;
+
+ // Errors from bpf_skb_load_bytes_net are ignored to favor returning something
+ // over returning nothing. In the event of an error, the kernel will fill in
+ // zero for the destination memory. Do not change the default '= 0' below.
+
+ uint8_t proto = 0;
+ uint8_t L4_off = 0;
+ uint8_t ipVersion = 0;
+ if (skb->protocol == htons(ETH_P_IP)) {
+ (void)bpf_skb_load_bytes_net(skb, IP4_OFFSET(protocol), &proto, sizeof(proto), kver);
+ (void)bpf_skb_load_bytes_net(skb, IPPROTO_IHL_OFF, &L4_off, sizeof(L4_off), kver);
+ L4_off = (L4_off & 0x0F) * 4; // IHL calculation.
+ ipVersion = 4;
+ } else if (skb->protocol == htons(ETH_P_IPV6)) {
+ (void)bpf_skb_load_bytes_net(skb, IP6_OFFSET(nexthdr), &proto, sizeof(proto), kver);
+ L4_off = sizeof(struct ipv6hdr);
+ ipVersion = 6;
+ }
+
+ uint8_t flags = 0;
+ __be16 sport = 0, dport = 0;
+ if (proto == IPPROTO_TCP && L4_off >= 20) {
+ (void)bpf_skb_load_bytes_net(skb, L4_off + TCP_FLAG32_OFF + 1, &flags, sizeof(flags), kver);
+ (void)bpf_skb_load_bytes_net(skb, L4_off + TCP_OFFSET(source), &sport, sizeof(sport), kver);
+ (void)bpf_skb_load_bytes_net(skb, L4_off + TCP_OFFSET(dest), &dport, sizeof(dport), kver);
+ } else if (proto == IPPROTO_UDP && L4_off >= 20) {
+ (void)bpf_skb_load_bytes_net(skb, L4_off + UDP_OFFSET(source), &sport, sizeof(sport), kver);
+ (void)bpf_skb_load_bytes_net(skb, L4_off + UDP_OFFSET(dest), &dport, sizeof(dport), kver);
+ }
+
+ pkt->timestampNs = bpf_ktime_get_boot_ns();
+ pkt->ifindex = skb->ifindex;
+ pkt->length = skb->len;
+
+ pkt->uid = uid;
+ pkt->tag = tag;
+ pkt->sport = sport;
+ pkt->dport = dport;
+
+ pkt->egress = egress;
+ pkt->ipProto = proto;
+ pkt->tcpFlags = flags;
+ pkt->ipVersion = ipVersion;
+
+ bpf_packet_trace_ringbuf_submit(pkt);
+}
+
static __always_inline inline bool skip_owner_match(struct __sk_buff* skb, const unsigned kver) {
uint32_t flag = 0;
if (skb->protocol == htons(ETH_P_IP)) {
uint8_t proto;
// no need to check for success, proto will be zeroed if bpf_skb_load_bytes_net() fails
- (void)bpf_skb_load_bytes_net(skb, IP_PROTO_OFF, &proto, sizeof(proto), kver);
+ (void)bpf_skb_load_bytes_net(skb, IP4_OFFSET(protocol), &proto, sizeof(proto), kver);
if (proto == IPPROTO_ESP) return true;
if (proto != IPPROTO_TCP) return false; // handles read failure above
uint8_t ihl;
@@ -247,7 +321,7 @@
} else if (skb->protocol == htons(ETH_P_IPV6)) {
uint8_t proto;
// no need to check for success, proto will be zeroed if bpf_skb_load_bytes_net() fails
- (void)bpf_skb_load_bytes_net(skb, IPV6_PROTO_OFF, &proto, sizeof(proto), kver);
+ (void)bpf_skb_load_bytes_net(skb, IP6_OFFSET(nexthdr), &proto, sizeof(proto), kver);
if (proto == IPPROTO_ESP) return true;
if (proto != IPPROTO_TCP) return false; // handles read failure above
// if the read below fails, we'll just assume no TCP flags are set, which is fine.
@@ -319,6 +393,7 @@
}
static __always_inline inline int bpf_traffic_account(struct __sk_buff* skb, bool egress,
+ const bool enable_tracing,
const unsigned kver) {
uint32_t sock_uid = bpf_get_socket_uid(skb);
uint64_t cookie = bpf_get_socket_cookie(skb);
@@ -378,34 +453,51 @@
key.tag = 0;
}
+ do_packet_tracing(skb, egress, uid, tag, enable_tracing, kver);
update_stats_with_config(skb, egress, &key, *selectedMap);
update_app_uid_stats_map(skb, egress, &uid);
asm("%0 &= 1" : "+r"(match));
return match;
}
+DEFINE_BPF_PROG_EXT("cgroupskb/ingress/stats$trace", AID_ROOT, AID_SYSTEM,
+ bpf_cgroup_ingress_trace, KVER(5, 8, 0), KVER_INF,
+ BPFLOADER_IGNORED_ON_VERSION, BPFLOADER_MAX_VER, false,
+ "fs_bpf_netd_readonly", "", false, true, false)
+(struct __sk_buff* skb) {
+ return bpf_traffic_account(skb, INGRESS, TRACE_ON, KVER(5, 8, 0));
+}
+
DEFINE_NETD_BPF_PROG_KVER_RANGE("cgroupskb/ingress/stats$4_19", AID_ROOT, AID_SYSTEM,
bpf_cgroup_ingress_4_19, KVER(4, 19, 0), KVER_INF)
(struct __sk_buff* skb) {
- return bpf_traffic_account(skb, INGRESS, KVER(4, 19, 0));
+ return bpf_traffic_account(skb, INGRESS, TRACE_OFF, KVER(4, 19, 0));
}
DEFINE_NETD_BPF_PROG_KVER_RANGE("cgroupskb/ingress/stats$4_14", AID_ROOT, AID_SYSTEM,
bpf_cgroup_ingress_4_14, KVER_NONE, KVER(4, 19, 0))
(struct __sk_buff* skb) {
- return bpf_traffic_account(skb, INGRESS, KVER_NONE);
+ return bpf_traffic_account(skb, INGRESS, TRACE_OFF, KVER_NONE);
+}
+
+DEFINE_BPF_PROG_EXT("cgroupskb/egress/stats$trace", AID_ROOT, AID_SYSTEM,
+ bpf_cgroup_egress_trace, KVER(5, 8, 0), KVER_INF,
+ BPFLOADER_IGNORED_ON_VERSION, BPFLOADER_MAX_VER, false,
+ "fs_bpf_netd_readonly", "", false, true, false)
+(struct __sk_buff* skb) {
+ return bpf_traffic_account(skb, EGRESS, TRACE_ON, KVER(5, 8, 0));
}
DEFINE_NETD_BPF_PROG_KVER_RANGE("cgroupskb/egress/stats$4_19", AID_ROOT, AID_SYSTEM,
bpf_cgroup_egress_4_19, KVER(4, 19, 0), KVER_INF)
(struct __sk_buff* skb) {
- return bpf_traffic_account(skb, EGRESS, KVER(4, 19, 0));
+ return bpf_traffic_account(skb, EGRESS, TRACE_OFF, KVER(4, 19, 0));
}
DEFINE_NETD_BPF_PROG_KVER_RANGE("cgroupskb/egress/stats$4_14", AID_ROOT, AID_SYSTEM,
bpf_cgroup_egress_4_14, KVER_NONE, KVER(4, 19, 0))
(struct __sk_buff* skb) {
- return bpf_traffic_account(skb, EGRESS, KVER_NONE);
+ return bpf_traffic_account(skb, EGRESS, TRACE_OFF, KVER_NONE);
}
// WARNING: Android T's non-updatable netd depends on the name of this program.
diff --git a/bpf_progs/netd.h b/bpf_progs/netd.h
index cc88680..be604f9 100644
--- a/bpf_progs/netd.h
+++ b/bpf_progs/netd.h
@@ -69,6 +69,24 @@
uint64_t tcpTxPackets;
} Stats;
+typedef struct {
+ uint64_t timestampNs;
+ uint32_t ifindex;
+ uint32_t length;
+
+ uint32_t uid;
+ uint32_t tag;
+
+ __be16 sport;
+ __be16 dport;
+
+ bool egress;
+ uint8_t ipProto;
+ uint8_t tcpFlags;
+ uint8_t ipVersion; // 4=IPv4, 6=IPv6, 0=unknown
+} PacketTrace;
+STRUCT_SIZE(PacketTrace, 8+4+4 + 4+4 + 2+2 + 1+1+1+1);
+
// Since we cannot garbage collect the stats map since device boot, we need to make these maps as
// large as possible. The maximum size of number of map entries we can have is depend on the rlimit
// of MEM_LOCK granted to netd. The memory space needed by each map can be calculated by the
@@ -87,7 +105,8 @@
// dozable_uid_map: key: 4 bytes, value: 1 bytes, cost: 145216 bytes = 145Kbytes
// standby_uid_map: key: 4 bytes, value: 1 bytes, cost: 145216 bytes = 145Kbytes
// powersave_uid_map: key: 4 bytes, value: 1 bytes, cost: 145216 bytes = 145Kbytes
-// total: 4930Kbytes
+// packet_trace_ringbuf:key: 0 bytes, value: 24 bytes, cost: 32768 bytes = 32Kbytes
+// total: 4962Kbytes
// It takes maximum 4.9MB kernel memory space if all maps are full, which requires any devices
// running this module to have a memlock rlimit to be larger then 5MB. In the old qtaguid module,
// we don't have a total limit for data entries but only have limitation of tags each uid can have.
@@ -102,6 +121,7 @@
static const int IFACE_STATS_MAP_SIZE = 1000;
static const int CONFIGURATION_MAP_SIZE = 2;
static const int UID_OWNER_MAP_SIZE = 4000;
+static const int PACKET_TRACE_BUF_SIZE = 32 * 1024;
#ifdef __cplusplus
@@ -145,6 +165,8 @@
#define CONFIGURATION_MAP_PATH BPF_NETD_PATH "map_netd_configuration_map"
#define UID_OWNER_MAP_PATH BPF_NETD_PATH "map_netd_uid_owner_map"
#define UID_PERMISSION_MAP_PATH BPF_NETD_PATH "map_netd_uid_permission_map"
+#define PACKET_TRACE_RINGBUF_PATH BPF_NETD_PATH "map_netd_packet_trace_ringbuf"
+#define PACKET_TRACE_ENABLED_MAP_PATH BPF_NETD_PATH "map_netd_packet_trace_enabled_map"
#endif // __cplusplus
diff --git a/service-t/native/libs/libnetworkstats/Android.bp b/service-t/native/libs/libnetworkstats/Android.bp
index 5b3d314..5cb27e6 100644
--- a/service-t/native/libs/libnetworkstats/Android.bp
+++ b/service-t/native/libs/libnetworkstats/Android.bp
@@ -24,7 +24,8 @@
host_supported: false,
header_libs: ["bpf_connectivity_headers"],
srcs: [
- "BpfNetworkStats.cpp"
+ "BpfNetworkStats.cpp",
+ "NetworkTraceHandler.cpp",
],
shared_libs: [
"libbase",
@@ -54,6 +55,7 @@
header_libs: ["bpf_connectivity_headers"],
srcs: [
"BpfNetworkStatsTest.cpp",
+ "NetworkTraceHandlerTest.cpp",
],
cflags: [
"-Wall",
@@ -68,6 +70,7 @@
shared_libs: [
"libbase",
"liblog",
+ "libandroid_net",
],
compile_multilib: "both",
multilib: {
diff --git a/service-t/native/libs/libnetworkstats/NetworkTraceHandler.cpp b/service-t/native/libs/libnetworkstats/NetworkTraceHandler.cpp
new file mode 100644
index 0000000..c679357
--- /dev/null
+++ b/service-t/native/libs/libnetworkstats/NetworkTraceHandler.cpp
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#define LOG_TAG "NetworkTrace"
+
+#include "netdbpf/NetworkTraceHandler.h"
+
+#include <bpf/BpfUtils.h>
+#include <log/log.h>
+
+namespace android {
+namespace bpf {
+
+bool NetworkTraceHandler::Start() {
+ ALOGD("Starting datasource");
+
+ auto status = mConfigurationMap.init(PACKET_TRACE_ENABLED_MAP_PATH);
+ if (!status.ok()) {
+ ALOGW("Failed to bind config map: %s", status.error().message().c_str());
+ return false;
+ }
+
+ auto rb = BpfRingbuf<PacketTrace>::Create(PACKET_TRACE_RINGBUF_PATH);
+ if (!rb.ok()) {
+ ALOGW("Failed to create ringbuf: %s", rb.error().message().c_str());
+ return false;
+ }
+
+ mRingBuffer = std::move(*rb);
+
+ auto res = mConfigurationMap.writeValue(0, true, BPF_ANY);
+ if (!res.ok()) {
+ ALOGW("Failed to enable tracing: %s", res.error().message().c_str());
+ return false;
+ }
+
+ return true;
+}
+
+bool NetworkTraceHandler::Stop() {
+ ALOGD("Stopping datasource");
+
+ auto res = mConfigurationMap.writeValue(0, false, BPF_ANY);
+ if (!res.ok()) {
+ ALOGW("Failed to disable tracing: %s", res.error().message().c_str());
+ return false;
+ }
+
+ mRingBuffer.reset();
+
+ return true;
+}
+
+bool NetworkTraceHandler::ConsumeAll() {
+ if (mRingBuffer == nullptr) {
+ ALOGW("Tracing is not active");
+ return false;
+ }
+
+ base::Result<int> ret = mRingBuffer->ConsumeAll(mCallback);
+ if (!ret.ok()) {
+ ALOGW("Failed to poll ringbuf: %s", ret.error().message().c_str());
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace bpf
+} // namespace android
diff --git a/service-t/native/libs/libnetworkstats/NetworkTraceHandlerTest.cpp b/service-t/native/libs/libnetworkstats/NetworkTraceHandlerTest.cpp
new file mode 100644
index 0000000..760ae91
--- /dev/null
+++ b/service-t/native/libs/libnetworkstats/NetworkTraceHandlerTest.cpp
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2023 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 <android-base/unique_fd.h>
+#include <android/multinetwork.h>
+#include <arpa/inet.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <inttypes.h>
+#include <net/if.h>
+#include <netinet/tcp.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <vector>
+
+#include "netdbpf/NetworkTraceHandler.h"
+
+using ::testing::AllOf;
+using ::testing::AnyOf;
+using ::testing::Each;
+using ::testing::Eq;
+using ::testing::Field;
+using ::testing::Test;
+
+namespace android {
+namespace bpf {
+
+__be16 bindAndListen(int s) {
+ sockaddr_in sin = {.sin_family = AF_INET};
+ socklen_t len = sizeof(sin);
+ if (bind(s, (sockaddr*)&sin, sizeof(sin))) return 0;
+ if (listen(s, 1)) return 0;
+ if (getsockname(s, (sockaddr*)&sin, &len)) return 0;
+ return sin.sin_port;
+}
+
+// This takes tcp flag constants from the standard library and makes them usable
+// with the flags we get from BPF. The standard library flags are big endian
+// whereas the BPF flags are reported in host byte order. BPF also trims the
+// flags down to the 8 single-bit flag bits (fin, syn, rst, etc).
+constexpr inline uint8_t FlagToHost(__be32 be_unix_flags) {
+ return ntohl(be_unix_flags) >> 16;
+}
+
+// Pretty prints all fields for a list of packets (useful for debugging).
+struct PacketPrinter {
+ const std::vector<PacketTrace>& data;
+ static constexpr char kTcpFlagNames[] = "FSRPAUEC";
+
+ friend std::ostream& operator<<(std::ostream& os, const PacketPrinter& d) {
+ os << "Packet count: " << d.data.size();
+ for (const PacketTrace& info : d.data) {
+ os << "\nifidx=" << info.ifindex;
+ os << ", len=" << info.length;
+ os << ", uid=" << info.uid;
+ os << ", tag=" << info.tag;
+ os << ", sport=" << info.sport;
+ os << ", dport=" << info.dport;
+ os << ", direction=" << (info.egress ? "egress" : "ingress");
+ os << ", proto=" << static_cast<int>(info.ipProto);
+ os << ", ip=" << static_cast<int>(info.ipVersion);
+ os << ", flags=";
+ for (int i = 0; i < 8; i++) {
+ os << ((info.tcpFlags & (1 << i)) ? kTcpFlagNames[i] : '.');
+ }
+ }
+ return os;
+ }
+};
+
+class NetworkTraceHandlerTest : public testing::Test {
+ protected:
+ void SetUp() {
+ if (access(PACKET_TRACE_RINGBUF_PATH, R_OK)) {
+ GTEST_SKIP() << "Network tracing is not enabled/loaded on this build";
+ }
+ }
+};
+
+TEST_F(NetworkTraceHandlerTest, PollWhileInactive) {
+ NetworkTraceHandler handler([&](const PacketTrace& pkt) {});
+
+ // One succeed after start and before stop.
+ EXPECT_FALSE(handler.ConsumeAll());
+ ASSERT_TRUE(handler.Start());
+ EXPECT_TRUE(handler.ConsumeAll());
+ ASSERT_TRUE(handler.Stop());
+ EXPECT_FALSE(handler.ConsumeAll());
+}
+
+TEST_F(NetworkTraceHandlerTest, TraceTcpSession) {
+ __be16 server_port = 0;
+ std::vector<PacketTrace> packets;
+
+ // Record all packets with the bound address and current uid. This callback is
+ // involked only within ConsumeAll, at which point the port should have
+ // already been filled in and all packets have been processed.
+ NetworkTraceHandler handler([&](const PacketTrace& pkt) {
+ if (pkt.sport != server_port && pkt.dport != server_port) return;
+ if (pkt.uid != getuid()) return;
+ packets.push_back(pkt);
+ });
+
+ ASSERT_TRUE(handler.Start());
+ const uint32_t kClientTag = 2468;
+ const uint32_t kServerTag = 1357;
+
+ // Go through a typical connection sequence between two v4 sockets using tcp.
+ // This covers connection handshake, shutdown, and one data packet.
+ {
+ android::base::unique_fd clientsocket(socket(AF_INET, SOCK_STREAM, 0));
+ ASSERT_NE(-1, clientsocket) << "Failed to open client socket";
+ ASSERT_EQ(android_tag_socket(clientsocket, kClientTag), 0);
+
+ android::base::unique_fd serversocket(socket(AF_INET, SOCK_STREAM, 0));
+ ASSERT_NE(-1, serversocket) << "Failed to open server socket";
+ ASSERT_EQ(android_tag_socket(serversocket, kServerTag), 0);
+
+ server_port = bindAndListen(serversocket);
+ ASSERT_NE(0, server_port) << "Can't bind to server port";
+
+ sockaddr_in addr = {.sin_family = AF_INET, .sin_port = server_port};
+ ASSERT_EQ(0, connect(clientsocket, (sockaddr*)&addr, sizeof(addr)))
+ << "connect to loopback failed: " << strerror(errno);
+
+ int accepted = accept(serversocket, nullptr, nullptr);
+ ASSERT_NE(-1, accepted) << "accept connection failed: " << strerror(errno);
+
+ const char data[] = "abcdefghijklmnopqrstuvwxyz";
+ EXPECT_EQ(send(clientsocket, data, sizeof(data), 0), sizeof(data))
+ << "failed to send message: " << strerror(errno);
+
+ char buff[100] = {};
+ EXPECT_EQ(recv(accepted, buff, sizeof(buff), 0), sizeof(data))
+ << "failed to receive message: " << strerror(errno);
+
+ EXPECT_EQ(std::string(data), std::string(buff));
+ }
+
+ ASSERT_TRUE(handler.ConsumeAll());
+ ASSERT_TRUE(handler.Stop());
+
+ // There are 12 packets in total (6 messages: each seen by client & server):
+ // 1. Client connects to server with syn
+ // 2. Server responds with syn ack
+ // 3. Client responds with ack
+ // 4. Client sends data with psh ack
+ // 5. Server acks the data packet
+ // 6. Client closes connection with fin ack
+ ASSERT_EQ(packets.size(), 12) << PacketPrinter{packets};
+
+ // All packets should be TCP packets.
+ EXPECT_THAT(packets, Each(Field(&PacketTrace::ipProto, Eq(IPPROTO_TCP))));
+
+ // Packet 1: client requests connection with server.
+ EXPECT_EQ(packets[0].egress, 1) << PacketPrinter{packets};
+ EXPECT_EQ(packets[0].dport, server_port) << PacketPrinter{packets};
+ EXPECT_EQ(packets[0].tag, kClientTag) << PacketPrinter{packets};
+ EXPECT_EQ(packets[0].tcpFlags, FlagToHost(TCP_FLAG_SYN))
+ << PacketPrinter{packets};
+
+ // Packet 2: server receives request from client.
+ EXPECT_EQ(packets[1].egress, 0) << PacketPrinter{packets};
+ EXPECT_EQ(packets[1].dport, server_port) << PacketPrinter{packets};
+ EXPECT_EQ(packets[1].tag, kServerTag) << PacketPrinter{packets};
+ EXPECT_EQ(packets[1].tcpFlags, FlagToHost(TCP_FLAG_SYN))
+ << PacketPrinter{packets};
+
+ // Packet 3: server replies back with syn ack.
+ EXPECT_EQ(packets[2].egress, 1) << PacketPrinter{packets};
+ EXPECT_EQ(packets[2].sport, server_port) << PacketPrinter{packets};
+ EXPECT_EQ(packets[2].tcpFlags, FlagToHost(TCP_FLAG_SYN | TCP_FLAG_ACK))
+ << PacketPrinter{packets};
+
+ // Packet 4: client receives the server's syn ack.
+ EXPECT_EQ(packets[3].egress, 0) << PacketPrinter{packets};
+ EXPECT_EQ(packets[3].sport, server_port) << PacketPrinter{packets};
+ EXPECT_EQ(packets[3].tcpFlags, FlagToHost(TCP_FLAG_SYN | TCP_FLAG_ACK))
+ << PacketPrinter{packets};
+}
+
+} // namespace bpf
+} // namespace android
diff --git a/service-t/native/libs/libnetworkstats/include/netdbpf/NetworkTraceHandler.h b/service-t/native/libs/libnetworkstats/include/netdbpf/NetworkTraceHandler.h
new file mode 100644
index 0000000..67fcf41
--- /dev/null
+++ b/service-t/native/libs/libnetworkstats/include/netdbpf/NetworkTraceHandler.h
@@ -0,0 +1,54 @@
+/**
+ * Copyright (c) 2023, 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.
+ */
+
+#pragma once
+
+#include <string>
+#include <unordered_map>
+
+#include "bpf/BpfMap.h"
+#include "bpf/BpfRingbuf.h"
+
+// For PacketTrace struct definition
+#include "netd.h"
+
+namespace android {
+namespace bpf {
+
+class NetworkTraceHandler {
+ public:
+ // Initialize with a callback capable of intercepting data.
+ NetworkTraceHandler(std::function<void(const PacketTrace&)> callback)
+ : mCallback(std::move(callback)) {}
+
+ // Standalone functions without perfetto dependency.
+ bool Start();
+ bool Stop();
+ bool ConsumeAll();
+
+ private:
+ // The function to process PacketTrace, typically a Perfetto sink.
+ std::function<void(const PacketTrace&)> mCallback;
+
+ // The BPF ring buffer handle.
+ std::unique_ptr<BpfRingbuf<PacketTrace>> mRingBuffer;
+
+ // The packet tracing config map (really a 1-element array).
+ BpfMap<uint32_t, bool> mConfigurationMap;
+};
+
+} // namespace bpf
+} // namespace android
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index b361720..5dcf860 100644
--- a/service-t/src/com/android/server/NsdService.java
+++ b/service-t/src/com/android/server/NsdService.java
@@ -919,6 +919,12 @@
// interfaces that do not have an associated Network.
break;
}
+ if (foundNetId == INetd.DUMMY_NET_ID) {
+ // Ignore services on the dummy0 interface: they are only seen when
+ // discovering locally advertised services, and are not reachable
+ // through that interface.
+ break;
+ }
setServiceNetworkForCallback(servInfo, info.netId, info.interfaceIdx);
clientInfo.onServiceFound(clientId, servInfo);
break;
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index b9d2760..f5c6fb7 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -6245,9 +6245,7 @@
if (mOemNetworkPreferences.getNetworkPreferences().size() > 0) {
handleSetOemNetworkPreference(mOemNetworkPreferences, null);
}
- if (!mProfileNetworkPreferences.isEmpty()) {
- updateProfileAllowedNetworks();
- }
+ updateProfileAllowedNetworks();
}
private void onUserRemoved(@NonNull final UserHandle user) {
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index 61b597a..f596b79 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -96,6 +96,7 @@
import static com.android.net.module.util.NetworkStackConstants.TEST_CAPTIVE_PORTAL_HTTP_URL;
import static com.android.networkstack.apishim.ConstantsShim.BLOCKED_REASON_LOCKDOWN_VPN;
import static com.android.networkstack.apishim.ConstantsShim.BLOCKED_REASON_NONE;
+import static com.android.networkstack.apishim.ConstantsShim.RECEIVER_EXPORTED;
import static com.android.testutils.Cleanup.testAndCleanup;
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
import static com.android.testutils.MiscAsserts.assertThrows;
@@ -1131,7 +1132,8 @@
final ConnectivityActionReceiver receiver = new ConnectivityActionReceiver(
mCm, ConnectivityManager.TYPE_WIFI, NetworkInfo.State.CONNECTED);
- mContext.registerReceiver(receiver, filter);
+ final int flags = SdkLevel.isAtLeastT() ? RECEIVER_EXPORTED : 0;
+ mContext.registerReceiver(receiver, filter, flags);
// Create a broadcast PendingIntent for NETWORK_CALLBACK_ACTION.
final Intent intent = new Intent(NETWORK_CALLBACK_ACTION)
@@ -1225,7 +1227,8 @@
networkFuture.complete(intent.getParcelableExtra(EXTRA_NETWORK));
}
};
- mContext.registerReceiver(receiver, filter);
+ final int flags = SdkLevel.isAtLeastT() ? RECEIVER_EXPORTED : 0;
+ mContext.registerReceiver(receiver, filter, flags);
final Network wifiNetwork = mCtsNetUtils.ensureWifiConnected();
try {
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 17e769c..c9783ba 100755
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -15760,6 +15760,39 @@
}
@Test
+ public void testProfileNetworkPreferenceBlocking_addUser() throws Exception {
+ final InOrder inOrder = inOrder(mMockNetd);
+ doReturn(asList(PRIMARY_USER_HANDLE)).when(mUserManager).getUserHandles(anyBoolean());
+
+ // Only one network
+ mCellAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
+ mCellAgent.connect(true);
+
+ // Verify uid ranges 0~99999 are allowed
+ final ArraySet<UidRange> allowedRanges = new ArraySet<>();
+ allowedRanges.add(PRIMARY_UIDRANGE);
+ final NativeUidRangeConfig config1User = new NativeUidRangeConfig(
+ mCellAgent.getNetwork().netId,
+ toUidRangeStableParcels(allowedRanges),
+ 0 /* subPriority */);
+ inOrder.verify(mMockNetd).setNetworkAllowlist(new NativeUidRangeConfig[] { config1User });
+
+ doReturn(asList(PRIMARY_USER_HANDLE, SECONDARY_USER_HANDLE))
+ .when(mUserManager).getUserHandles(anyBoolean());
+ final Intent addedIntent = new Intent(ACTION_USER_ADDED);
+ addedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(SECONDARY_USER));
+ processBroadcast(addedIntent);
+
+ // Make sure the allow list has been updated.
+ allowedRanges.add(UidRange.createForUser(SECONDARY_USER_HANDLE));
+ final NativeUidRangeConfig config2Users = new NativeUidRangeConfig(
+ mCellAgent.getNetwork().netId,
+ toUidRangeStableParcels(allowedRanges),
+ 0 /* subPriority */);
+ inOrder.verify(mMockNetd).setNetworkAllowlist(new NativeUidRangeConfig[] { config2Users });
+ }
+
+ @Test
public void testProfileNetworkPreferenceBlocking_changePreference() throws Exception {
final InOrder inOrder = inOrder(mMockNetd);
final UserHandle testHandle = setupEnterpriseNetwork();
diff --git a/tests/unit/java/com/android/server/NsdServiceTest.java b/tests/unit/java/com/android/server/NsdServiceTest.java
index c7a6639..98a8ed2 100644
--- a/tests/unit/java/com/android/server/NsdServiceTest.java
+++ b/tests/unit/java/com/android/server/NsdServiceTest.java
@@ -413,6 +413,35 @@
}
@Test
+ public void testDiscoverOnBlackholeNetwork() throws Exception {
+ final NsdManager client = connectClient(mService);
+ final DiscoveryListener discListener = mock(DiscoveryListener.class);
+ client.discoverServices(SERVICE_TYPE, PROTOCOL, discListener);
+ waitForIdle();
+
+ final IMDnsEventListener eventListener = getEventListener();
+ final ArgumentCaptor<Integer> discIdCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(mMockMDnsM).discover(discIdCaptor.capture(), eq(SERVICE_TYPE),
+ eq(0) /* interfaceIdx */);
+ // NsdManager uses a separate HandlerThread to dispatch callbacks (on ServiceHandler), so
+ // this needs to use a timeout
+ verify(discListener, timeout(TIMEOUT_MS)).onDiscoveryStarted(SERVICE_TYPE);
+
+ final DiscoveryInfo discoveryInfo = new DiscoveryInfo(
+ discIdCaptor.getValue(),
+ IMDnsEventListener.SERVICE_FOUND,
+ SERVICE_NAME,
+ SERVICE_TYPE,
+ DOMAIN_NAME,
+ 123 /* interfaceIdx */,
+ INetd.DUMMY_NET_ID); // netId of the blackhole network
+ eventListener.onServiceDiscoveryStatus(discoveryInfo);
+ waitForIdle();
+
+ verify(discListener, never()).onServiceFound(any());
+ }
+
+ @Test
public void testServiceRegistrationSuccessfulAndFailed() throws Exception {
final NsdManager client = connectClient(mService);
final NsdServiceInfo request = new NsdServiceInfo(SERVICE_NAME, SERVICE_TYPE);