Merge "Add NetworkTraceHandler with Start/Stop/ConsumeAll" am: 02cd131ab9

Original change: https://android-review.googlesource.com/c/platform/packages/modules/Connectivity/+/2381736

Change-Id: Ie2af1e9dcfa430444c7aacd439004c107809acc1
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
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