Introduce port_listener

Brought code from ChromiumOS under confirmation of opensource licensing
team. Modification of these files to be built would be happened in the
following change.

Bug: 340126051
Test: N/A
Change-Id: I7afe9a15f0782b3a50aa6676f078aca2656079ec
diff --git a/build/debian/port_listener/src/common.h b/build/debian/port_listener/src/common.h
new file mode 100644
index 0000000..05386a4
--- /dev/null
+++ b/build/debian/port_listener/src/common.h
@@ -0,0 +1,31 @@
+// Copyright 2024 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.
+
+// Copied from ChromiumOS with relicensing:
+// src/platform2/vm_tools/port_listener/common.h
+
+#ifndef VM_TOOLS_PORT_LISTENER_COMMON_H_
+#define VM_TOOLS_PORT_LISTENER_COMMON_H_
+
+enum State {
+  kPortListenerUp,
+  kPortListenerDown,
+};
+
+struct event {
+  enum State state;
+  uint16_t port;
+};
+
+#endif  // VM_TOOLS_PORT_LISTENER_COMMON_H_
diff --git a/build/debian/port_listener/src/listen_tracker.ebpf.c b/build/debian/port_listener/src/listen_tracker.ebpf.c
new file mode 100644
index 0000000..4ecc423
--- /dev/null
+++ b/build/debian/port_listener/src/listen_tracker.ebpf.c
@@ -0,0 +1,83 @@
+// Copyright 2024 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.
+
+// Copied from ChromiumOS with relicensing:
+// src/platform2/vm_tools/port_listener/listen_tracker.ebpf.c
+
+// bpf_helpers.h uses types defined here
+#include "include/vm_tools/port_listener/vmlinux/vmlinux.h"
+
+#include <bpf/bpf_helpers.h>
+
+#include "vm_tools/port_listener/common.h"
+
+// For some reason 6.1 doesn't include these symbols in the debug build
+// so they don't get included in vmlinux.h. These features have existed since
+// well before 6.1.
+#define BPF_F_NO_PREALLOC (1U << 0)
+#define BPF_ANY 0
+
+struct {
+  __uint(type, BPF_MAP_TYPE_RINGBUF);
+  __uint(max_entries, 1 << 24);
+} events SEC(".maps");
+
+struct {
+  __uint(type, BPF_MAP_TYPE_HASH);
+  __type(key, struct sock*);
+  __type(value, __u8);
+  __uint(max_entries, 65535);
+  __uint(map_flags, BPF_F_NO_PREALLOC);
+} sockmap SEC(".maps");
+
+const __u8 set_value = 0;
+
+SEC("tp/sock/inet_sock_set_state")
+int tracepoint_inet_sock_set_state(
+    struct trace_event_raw_inet_sock_set_state* ctx) {
+  // We don't support anything other than TCP.
+  if (ctx->protocol != IPPROTO_TCP) {
+    return 0;
+  }
+  struct sock* sk = (struct sock*)ctx->skaddr;
+  // If we're transitioning away from LISTEN but we don't know about this
+  // socket yet then don't report anything.
+  if (ctx->oldstate == BPF_TCP_LISTEN &&
+      bpf_map_lookup_elem(&sockmap, &sk) == NULL) {
+    return 0;
+  }
+  // If we aren't transitioning to or from TCP_LISTEN then we don't care.
+  if (ctx->newstate != BPF_TCP_LISTEN && ctx->oldstate != BPF_TCP_LISTEN) {
+    return 0;
+  }
+
+  struct event* ev;
+  ev = bpf_ringbuf_reserve(&events, sizeof(*ev), 0);
+  if (!ev) {
+    return 0;
+  }
+  ev->port = ctx->sport;
+
+  if (ctx->newstate == BPF_TCP_LISTEN) {
+    bpf_map_update_elem(&sockmap, &sk, &set_value, BPF_ANY);
+    ev->state = kPortListenerUp;
+  }
+  if (ctx->oldstate == BPF_TCP_LISTEN) {
+    bpf_map_delete_elem(&sockmap, &sk);
+    ev->state = kPortListenerDown;
+  }
+  bpf_ringbuf_submit(ev, 0);
+
+  return 0;
+}
diff --git a/build/debian/port_listener/src/main.cc b/build/debian/port_listener/src/main.cc
new file mode 100644
index 0000000..cc1f91e
--- /dev/null
+++ b/build/debian/port_listener/src/main.cc
@@ -0,0 +1,197 @@
+// Copyright 2024 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.
+
+// Copied from ChromiumOS with relicensing:
+// src/platform2/vm_tools/port_listener/main.cc
+
+#include <sys/socket.h>
+
+#include <linux/vm_sockets.h>  // Needs to come after sys/socket.h
+
+#include <memory>
+
+#include <base/logging.h>
+#include <base/memory/ptr_util.h>
+#include <base/strings/stringprintf.h>
+#include <bpf/libbpf.h>
+#include <bpf/libbpf_legacy.h>
+#include <chromeos/constants/vm_tools.h>
+#include <grpcpp/grpcpp.h>
+#include <vm_protos/proto_bindings/common.pb.h>
+#include <vm_protos/proto_bindings/tremplin.grpc.pb.h>
+
+#include "vm_tools/port_listener/bpf/generated/skeleton_listen_tracker.ebpf.h"
+#include "vm_tools/port_listener/common.h"
+
+typedef std::unordered_map<int, int> port_usage_map;
+
+namespace port_listener {
+namespace {
+
+int HandleEvent(void* ctx, void* const data, size_t size) {
+  port_usage_map* map = reinterpret_cast<port_usage_map*>(ctx);
+  const struct event* ev = (struct event*)data;
+
+  switch (ev->state) {
+    case kPortListenerUp:
+      (*map)[ev->port]++;
+      break;
+
+    case kPortListenerDown:
+      if ((*map)[ev->port] > 0) {
+        (*map)[ev->port]--;
+      } else {
+        LOG(INFO) << "Received down event while port count was 0; ignoring";
+      }
+
+      break;
+
+    default:
+      LOG(ERROR) << "Unknown event state " << ev->state;
+  }
+
+  LOG(INFO) << "Listen event: port=" << ev->port << " state=" << ev->state;
+
+  return 0;
+}
+
+typedef std::unique_ptr<struct ring_buffer, decltype(&ring_buffer__free)>
+    ring_buffer_ptr;
+typedef std::unique_ptr<listen_tracker_ebpf,
+                        decltype(&listen_tracker_ebpf__destroy)>
+    listen_tracker_ptr;
+
+// BPFProgram tracks the state and resources of the listen_tracker BPF program.
+class BPFProgram {
+ public:
+  // Default movable but not copyable.
+  BPFProgram(BPFProgram&& other) = default;
+  BPFProgram(const BPFProgram& other) = delete;
+  BPFProgram& operator=(BPFProgram&& other) = default;
+  BPFProgram& operator=(const BPFProgram& other) = delete;
+
+  // Load loads the listen_tracker BPF program and prepares it for polling. On
+  // error nullptr is returned.
+  static std::unique_ptr<BPFProgram> Load() {
+    auto* skel = listen_tracker_ebpf__open();
+    if (!skel) {
+      PLOG(ERROR) << "Failed to open listen_tracker BPF skeleton";
+      return nullptr;
+    }
+    listen_tracker_ptr skeleton(skel, listen_tracker_ebpf__destroy);
+
+    int err = listen_tracker_ebpf__load(skeleton.get());
+    if (err) {
+      PLOG(ERROR) << "Failed to load listen_tracker BPF program";
+      return nullptr;
+    }
+
+    auto map = std::make_unique<port_usage_map>();
+    auto* rb = ring_buffer__new(bpf_map__fd(skel->maps.events), HandleEvent,
+                                map.get(), NULL);
+    if (!rb) {
+      PLOG(ERROR) << "Failed to open ring buffer for listen_tracker";
+      return nullptr;
+    }
+    ring_buffer_ptr ringbuf(rb, ring_buffer__free);
+
+    err = listen_tracker_ebpf__attach(skeleton.get());
+    if (err) {
+      PLOG(ERROR) << "Failed to attach listen_tracker";
+      return nullptr;
+    }
+
+    return base::WrapUnique(new BPFProgram(std::move(skeleton),
+                                           std::move(ringbuf), std::move(map)));
+  }
+
+  // Poll waits for the listen_tracker BPF program to post a new event to the
+  // ring buffer. BPFProgram handles integrating this new event into the
+  // port_usage map and callers should consult port_usage() after Poll returns
+  // for the latest data.
+  const bool Poll() {
+    int err = ring_buffer__poll(rb_.get(), -1);
+    if (err < 0) {
+      LOG(ERROR) << "Error polling ring buffer ret=" << err;
+      return false;
+    }
+
+    return true;
+  }
+
+  const port_usage_map& port_usage() { return *port_usage_; }
+
+ private:
+  BPFProgram(listen_tracker_ptr&& skeleton,
+             ring_buffer_ptr&& rb,
+             std::unique_ptr<port_usage_map>&& port_usage)
+      : skeleton_(std::move(skeleton)),
+        rb_(std::move(rb)),
+        port_usage_(std::move(port_usage)) {}
+
+  listen_tracker_ptr skeleton_;
+  ring_buffer_ptr rb_;
+  std::unique_ptr<port_usage_map> port_usage_;
+};
+
+}  // namespace
+}  // namespace port_listener
+
+int main(int argc, char** argv) {
+  logging::InitLogging(logging::LoggingSettings());
+  libbpf_set_strict_mode(LIBBPF_STRICT_ALL);
+
+  // Load our BPF program.
+  auto program = port_listener::BPFProgram::Load();
+  if (program == nullptr) {
+    LOG(ERROR) << "Failed to load BPF program";
+    return EXIT_FAILURE;
+  }
+
+  // Connect back to TremplinListener
+  vm_tools::tremplin::TremplinListener::Stub tremplin_listener(
+      grpc::CreateChannel(base::StringPrintf("vsock:%u:%u", VMADDR_CID_HOST,
+                                             vm_tools::kTremplinListenerPort),
+                          grpc::InsecureChannelCredentials()));
+
+  // main loop: poll for listen updates, when an update comes send an rpc to
+  // tremplin listener letting it know.
+  for (;;) {
+    if (!program->Poll()) {
+      LOG(ERROR) << "Failure while polling BPF program";
+      return EXIT_FAILURE;
+    }
+    // port_usage will be updated with the latest usage data
+
+    vm_tools::tremplin::ListeningPortInfo_ContainerPortInfo cpi;
+    for (auto it : program->port_usage()) {
+      if (it.second <= 0) {
+        continue;
+      }
+      cpi.add_listening_tcp4_ports(it.first);
+    }
+
+    vm_tools::tremplin::ListeningPortInfo lpi;
+    (*lpi.mutable_container_ports())["penguin"] = cpi;
+
+    grpc::ClientContext ctx;
+    vm_tools::tremplin::EmptyMessage empty;
+    grpc::Status status =
+        tremplin_listener.UpdateListeningPorts(&ctx, lpi, &empty);
+    if (!status.ok()) {
+      LOG(WARNING) << "Failed to notify tremplin of new listening ports: "
+                   << status.error_message();
+    }
+  }
+}