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();
+ }
+ }
+}