feat(ebpf-poc): redirect eligible connections to the proxy

Turns the recorded ACTION_REDIRECT decision into an actual rewrite of
user_ip4 and user_port, so a connection that would have reached the
registry directly lands on the configured target instead.

The target address is copied out of the map as is. It is already held
in network byte order, the same layout as ctx->user_ip4, so converting
it again silently corrupts the destination: 127.0.0.1 becomes 1.0.0.127,
which is routable, never answers, and shows up as a two minute hang
rather than an error. Only the port is converted, since it is kept in
host order for the userspace side.

Verified against a dummy listener. curl to an external HTTPS host is
logged as REDIRECT against its original destination, and the TLS
ClientHello arrives on the local target.
This commit is contained in:
Sahilb315
2026-07-27 14:18:47 +05:30
parent 05a484ea5e
commit a4b99e40e7
5 changed files with 21 additions and 5 deletions
+4
View File
@@ -35,3 +35,7 @@ packages/*/bin/
# Build artifacts in wrapper package
packages/pmg/dist/
packages/pmg/README.md
ebpf-poc/pmgwatch
go.work.sum
pmgwatch
Binary file not shown.
Binary file not shown.
+16 -4
View File
@@ -12,8 +12,8 @@ char __license[] SEC("license") = "Dual MIT/GPL";
// steered into a proxy that cannot speak their protocol.
#define REDIRECT_DPORT 443
// What the hook decided. Recorded on every event so the ladder can be checked
// against real traffic before any rewrite is switched on.
// What the hook decided. Recorded on every event so the ladder stays auditable:
// a connection that was not redirected always reports which check stopped it.
#define ACTION_REDIRECT 0
#define ACTION_SKIP_PROTO 1
#define ACTION_SKIP_LOOPBACK 2
@@ -106,7 +106,19 @@ int connect4(struct bpf_sock_addr *ctx) {
bpf_ringbuf_submit(e, 0);
}
// Shadow mode. The decision is recorded and never acted on. Turning
// ACTION_REDIRECT into a rewrite of user_ip4 and user_port is the next step.
if (action == ACTION_REDIRECT) {
__u32 key = 0;
struct target *t = bpf_map_lookup_elem(&target_map, &key);
if (!t || t->port == 0) {
return 1;
}
// t->ip is already network byte order, the same layout as user_ip4, so it
// is copied as is. Byte swapping here would corrupt the address. Only the
// port is converted, since it is stored host order for the userspace side.
ctx->user_ip4 = t->ip;
ctx->user_port = bpf_htons(t->port);
}
return 1;
}
+1 -1
View File
@@ -88,7 +88,7 @@ func run(proxyAddr, exemptUIDs string, tcpOnly bool) error {
rd.Close()
}()
fmt.Println("Shadow mode: decisions are recorded, nothing is redirected. Ctrl+C to exit.")
fmt.Println("Attached to /sys/fs/cgroup. Ctrl+C to exit.")
fmt.Printf("%-15s %-17s %-7s %-8s %-22s %s\n", "ACTION", "COMMAND", "UID", "PID", "DESTINATION", "PROTO")
return drain(rd, tcpOnly)