Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 147 additions & 0 deletions cmd/conn_meta.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package main

import (
"encoding/binary"
"fmt"
"net"
"strconv"
"strings"

"github.com/cilium/ebpf"
)

// Match flag bits — keep in sync with enum match_flags in proxy.c.
const (
MatchCmd uint32 = 1 << 0
MatchPid uint32 = 1 << 1
MatchPgid uint32 = 1 << 2
MatchContainer uint32 = 1 << 3
MatchTracked uint32 = 1 << 4
)

// ConnMeta is userspace connection metadata read from eBPF maps for routing.
type ConnMeta struct {
Pid uint32
Tgid uint32
Pgid uint32
Pidns uint32
Mntns uint32
Netns uint32
MatchFlags uint32
Comm string
DstIP net.IP
DstPort uint16
}

func (m ConnMeta) TargetAddr() string {
if m.DstIP == nil {
return ""
}
return fmt.Sprintf("%s:%d", m.DstIP.String(), m.DstPort)
}

func (m ConnMeta) String() string {
return fmt.Sprintf("pid=%d tgid=%d pgid=%d comm=%q flags=0x%x ns(pid=%d mnt=%d net=%d) dst=%s",
m.Pid, m.Tgid, m.Pgid, m.Comm, m.MatchFlags, m.Pidns, m.Mntns, m.Netns, m.TargetAddr())
}

func connMetaFromBPF(meta proxyConnMeta, dstIP uint32, dstPort uint16) ConnMeta {
return ConnMeta{
Pid: meta.Pid,
Tgid: meta.Tgid,
Pgid: meta.Pgid,
Pidns: meta.PidnsInum,
Mntns: meta.MntnsInum,
Netns: meta.NetnsInum,
MatchFlags: meta.MatchFlags,
Comm: int8ArrayToString(meta.Comm[:]),
DstIP: hostOrderIPv4(dstIP),
DstPort: dstPort,
}
}

func int8ArrayToString(b []int8) string {
raw := make([]byte, len(b))
for i, v := range b {
if v == 0 {
return string(raw[:i])
}
raw[i] = byte(v)
}
return strings.TrimRight(string(raw), "\x00")
}

// hostOrderIPv4 converts a host-order IPv4 uint32 (as stored by bpf_ntohl) to net.IP.
func hostOrderIPv4(ip uint32) net.IP {
return net.IPv4(byte(ip>>24), byte(ip>>16), byte(ip>>8), byte(ip))
}

func parseRemoteIPv4Port(addr net.Addr) (ip uint32, port uint16, err error) {
host, portStr, err := net.SplitHostPort(addr.String())
if err != nil {
return 0, 0, err
}
ip4 := net.ParseIP(host).To4()
if ip4 == nil {
return 0, 0, fmt.Errorf("not IPv4: %s", host)
}
p, err := strconv.Atoi(portStr)
if err != nil {
return 0, 0, err
}
if p < 0 || p > 65535 {
return 0, 0, fmt.Errorf("invalid port: %s", portStr)
}
// map_ports keys use sockops local_ip4 / sk->dst_ip4, which are IPv4
// addresses in network byte order stored as __u32. On little-endian that
// numeric value matches NativeEndian over the wire-order byte slice
// (e.g. 127.0.0.1 -> 0x0100007f), not BigEndian (0x7f000001).
return binary.NativeEndian.Uint32(ip4), uint16(p), nil
}

// lookupTCPConnMeta resolves ConnMeta via map_ports → map_socks using the client RemoteAddr.
func lookupTCPConnMeta(remote net.Addr, portsMap, socksMap *ebpf.Map) (ConnMeta, error) {
if portsMap == nil || socksMap == nil {
return ConnMeta{}, fmt.Errorf("tcp meta maps not available")
}
srcIP, srcPort, err := parseRemoteIPv4Port(remote)
if err != nil {
return ConnMeta{}, err
}
key := proxyPortKey{
SrcIp: srcIP,
SrcPort: srcPort,
}
var cookie uint64
if err := portsMap.Lookup(&key, &cookie); err != nil {
return ConnMeta{}, err
}
var sock proxySocket
if err := socksMap.Lookup(&cookie, &sock); err != nil {
return ConnMeta{}, err
}
return connMetaFromBPF(sock.Meta, sock.DstAddr, sock.DstPort), nil
}

// lookupUDPConnMeta looks up map_udp_dest for the client address (with src_ip=0 fallback).
func lookupUDPConnMeta(clientAddr *net.UDPAddr, udpMap *ebpf.Map) (ConnMeta, error) {
if udpMap == nil {
return ConnMeta{}, fmt.Errorf("udp map not available")
}
ip4 := clientAddr.IP.To4()
if ip4 == nil {
return ConnMeta{}, fmt.Errorf("not IPv4")
}
key := proxyUdpDestKey{
SrcIp: binary.BigEndian.Uint32(ip4),
SrcPort: uint16(clientAddr.Port),
}
var val proxyUdpDestVal
if err := udpMap.Lookup(&key, &val); err != nil {
key.SrcIp = 0
if err := udpMap.Lookup(&key, &val); err != nil {
return ConnMeta{}, err
}
}
return connMetaFromBPF(val.Meta, val.DstIp, val.DstPort), nil
}
6 changes: 3 additions & 3 deletions cmd/load_bpf.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
"github.com/cilium/ebpf/rlimit"
)

//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cflags "-D ARCH_$TARGET" -target $TARGET -type Config proxy proxy.c -- -I../.output/ -I../libbpf/include/uapi -I../vmlinux/$TARGET
//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cflags "-D ARCH_$TARGET" -target $TARGET -type Config -type ConnMeta -type Socket -type UdpDestVal -type PortKey -type UdpDestKey proxy proxy.c -- -I../.output/ -I../libbpf/include/uapi -I../vmlinux/$TARGET

const (
CGROUP_PATH = "/sys/fs/cgroup" // Root cgroup path
Expand Down Expand Up @@ -85,9 +85,9 @@ func LoadBpf(options *Options) {
byte(proxyRedirectIP))
}

// Start TCP (and UDP) proxy so it can use objs.MapUdpDest for UDP original-dest lookup
// Start TCP (and UDP) proxy so it can use BPF maps for original-dest / ConnMeta lookup
if options.ProxyPid == 0 {
StartProxy(objs.MapUdpDest, options.EnableTCP, options.EnableUDP, proxyListenHost, options.Mirror)
StartProxy(objs.MapUdpDest, objs.MapPorts, objs.MapSocks, options.EnableTCP, options.EnableUDP, proxyListenHost, options.Mirror)
}

// Attach eBPF programs to the root cgroup
Expand Down
105 changes: 87 additions & 18 deletions cmd/proxy.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,34 @@ struct Config {
char command[TASK_COMM_LEN];
};

enum match_flags {
MATCH_CMD = 1 << 0,
MATCH_PID = 1 << 1,
MATCH_PGID = 1 << 2,
MATCH_CONTAINER = 1 << 3,
MATCH_TRACKED = 1 << 4,
};

/* Connection metadata for userspace routing (fixed-size, map-friendly). */
struct ConnMeta {
__u32 pid;
__u32 tgid;
__u32 pgid;
__u32 pidns_inum;
__u32 mntns_inum;
__u32 netns_inum;
__u32 match_flags;
char comm[TASK_COMM_LEN];
};

struct Socket {
__u32 src_addr;
__u16 src_port;
__u16 pad0;
__u32 dst_addr;
__u16 dst_port;
__u16 pad1;
struct ConnMeta meta;
};

struct PortKey {
Expand Down Expand Up @@ -114,11 +137,12 @@ struct UdpDestKey {
__u16 pad;
};

// Value: original destination that was redirected to proxy
// Value: original destination + ConnMeta for userspace routing
struct UdpDestVal {
__u32 dst_ip;
__u16 dst_port;
__u16 pad;
struct ConnMeta meta;
};

struct {
Expand Down Expand Up @@ -157,18 +181,25 @@ get_current_pgid(void)
return BPF_CORE_READ(pgid_pid, numbers[0].nr);
}

static __always_inline bool
match_container_ns(void)
static __always_inline void
read_current_ns(__u32 *pidns_inum, __u32 *mntns_inum, __u32 *netns_inum)
{
*pidns_inum = 0;
*mntns_inum = 0;
*netns_inum = 0;

struct task_struct *task = (struct task_struct *)bpf_get_current_task_btf();
if (!task) {
return false;
}
if (!task)
return;

__u32 pidns_id = BPF_CORE_READ(task, nsproxy, pid_ns_for_children, ns.inum);
__u32 mntns_id = BPF_CORE_READ(task, nsproxy, mnt_ns, ns.inum);
__u32 netns_id = BPF_CORE_READ(task, nsproxy, net_ns, ns.inum);
*pidns_inum = BPF_CORE_READ(task, nsproxy, pid_ns_for_children, ns.inum);
*mntns_inum = BPF_CORE_READ(task, nsproxy, mnt_ns, ns.inum);
*netns_inum = BPF_CORE_READ(task, nsproxy, net_ns, ns.inum);
}

static __always_inline bool
match_container_ns(__u32 pidns_id, __u32 mntns_id, __u32 netns_id)
{
if (pidns_id && bpf_map_lookup_elem(&filter_pidns_map, &pidns_id)) {
return true;
}
Expand All @@ -182,6 +213,19 @@ match_container_ns(void)
return false;
}

static __always_inline void
fill_conn_meta(struct ConnMeta *meta, __u32 match_flags)
{
__u64 pid_tgid = bpf_get_current_pid_tgid();
__builtin_memset(meta, 0, sizeof(*meta));
meta->tgid = pid_tgid >> 32;
meta->pid = (__u32)pid_tgid;
meta->pgid = get_current_pgid();
meta->match_flags = match_flags;
bpf_get_current_comm(meta->comm, sizeof(meta->comm));
read_current_ns(&meta->pidns_inum, &meta->mntns_inum, &meta->netns_inum);
}

static __always_inline bool
comm_matches_command(const char comm[TASK_COMM_LEN], const char command[TASK_COMM_LEN])
{
Expand Down Expand Up @@ -213,20 +257,24 @@ track_pid(__u32 pid)
bpf_map_update_elem(&filter_tracked_map, &pid, &val, BPF_ANY);
}

/* Returns whether the current process should be redirected; out_flags records why. */
static __always_inline bool
match_process(struct Config *conf)
match_process(struct Config *conf, __u32 *out_flags)
{
__u32 flags = 0;
bool has_cmd = conf->command[0] != '\0';
bool has_pid_filter = conf->filter_by_pid || conf->filter_by_pgid;
bool has_container_filter = conf->filter_by_container;

if (!has_cmd && !has_pid_filter && !has_container_filter) {
*out_flags = 0;
return true;
}

bool cmd_matched = false;
bool tracked_matched = false;
bool pid_matched = false;
bool pgid_matched = false;
bool container_matched = false;

if (has_cmd) {
Expand Down Expand Up @@ -261,17 +309,32 @@ match_process(struct Config *conf)
if (has_pid_filter && !pid_matched && conf->filter_by_pgid) {
__u32 current_pgid = get_current_pgid();
if (current_pgid && bpf_map_lookup_elem(&filter_pid_map, &current_pgid)) {
pid_matched = true;
pgid_matched = true;
}
}

if (has_container_filter) {
container_matched = match_container_ns();
__u32 pidns_id = 0, mntns_id = 0, netns_id = 0;
read_current_ns(&pidns_id, &mntns_id, &netns_id);
container_matched = match_container_ns(pidns_id, mntns_id, netns_id);
}

if (cmd_matched)
flags |= MATCH_CMD;
if (tracked_matched)
flags |= MATCH_TRACKED;
if (pid_matched)
flags |= MATCH_PID;
if (pgid_matched)
flags |= MATCH_PGID;
if (container_matched)
flags |= MATCH_CONTAINER;

*out_flags = flags;

if ((has_container_filter && container_matched) ||
(has_cmd && (cmd_matched || tracked_matched)) ||
(has_pid_filter && pid_matched)) {
(has_pid_filter && (pid_matched || pgid_matched))) {
return true;
}
return false;
Expand Down Expand Up @@ -371,7 +434,8 @@ int cg_connect4(struct bpf_sock_addr *ctx) {
}
if (current_pid == conf->proxy_pid) return 1;

if (!match_process(conf))
__u32 match_flags = 0;
if (!match_process(conf, &match_flags))
return 1;

if (conf->filter_ip)
Expand All @@ -388,19 +452,23 @@ int cg_connect4(struct bpf_sock_addr *ctx) {
if (dst_addr == conf->proxy_ip && dst_port == conf->proxy_port)
return 1;

struct ConnMeta meta;
fill_conn_meta(&meta, match_flags);

if (ctx->protocol == IPPROTO_TCP) {
if (!conf->enable_tcp) return 1;
__u64 cookie = bpf_get_socket_cookie(ctx);
struct Socket sock;
__builtin_memset(&sock, 0, sizeof(sock));
sock.dst_addr = dst_addr;
sock.dst_port = dst_port;
sock.meta = meta;
bpf_map_update_elem(&map_socks, &cookie, &sock, 0);

ctx->user_ip4 = bpf_htonl(conf->proxy_ip);
ctx->user_port = bpf_htonl(conf->proxy_port << 16);
BPF_LOG_DEBUG("connect4: tcp redirect dst=%x:%u -> proxy=%x:%u pid=%u\n",
dst_addr, dst_port, conf->proxy_ip, conf->proxy_port, current_pid);
BPF_LOG_DEBUG("connect4: tcp redirect dst=%x:%u -> proxy=%x:%u pid=%u flags=%u\n",
dst_addr, dst_port, conf->proxy_ip, conf->proxy_port, current_pid, match_flags);
return 1;
}

Expand Down Expand Up @@ -453,12 +521,13 @@ int cg_connect4(struct bpf_sock_addr *ctx) {
__builtin_memset(&dval, 0, sizeof(dval));
dval.dst_ip = dst_addr;
dval.dst_port = dst_port;
dval.meta = meta;
bpf_map_update_elem(&map_udp_dest, &dkey, &dval, 0);

ctx->user_ip4 = bpf_htonl(conf->proxy_ip);
ctx->user_port = bpf_htonl(conf->proxy_port << 16);
BPF_LOG_DEBUG("connect4: udp redirect dst=%x:%u src_port=%u proxy=%x:%u pid=%u\n",
dst_addr, dst_port, src_port, conf->proxy_ip, conf->proxy_port, current_pid);
BPF_LOG_DEBUG("connect4: udp redirect dst=%x:%u src_port=%u proxy=%x:%u pid=%u flags=%u\n",
dst_addr, dst_port, src_port, conf->proxy_ip, conf->proxy_port, current_pid, match_flags);
return 1;
}

Expand Down
Loading
Loading