diff --git a/firewall/interception/ebpf/bpf_bpfeb.go b/firewall/interception/ebpf/bpf_bpfeb.go index a1eee1cb..32c45c38 100644 --- a/firewall/interception/ebpf/bpf_bpfeb.go +++ b/firewall/interception/ebpf/bpf_bpfeb.go @@ -20,7 +20,8 @@ type bpfEvent struct { Dport uint16 Pid uint32 IpVersion uint8 - _ [3]byte + Protocol uint8 + _ [2]byte } // loadBpf returns the embedded CollectionSpec for bpf. @@ -66,6 +67,8 @@ type bpfSpecs struct { type bpfProgramSpecs struct { TcpV4Connect *ebpf.ProgramSpec `ebpf:"tcp_v4_connect"` TcpV6Connect *ebpf.ProgramSpec `ebpf:"tcp_v6_connect"` + UdpSendmsg *ebpf.ProgramSpec `ebpf:"udp_sendmsg"` + Udpv6Sendmsg *ebpf.ProgramSpec `ebpf:"udpv6_sendmsg"` } // bpfMapSpecs contains maps before they are loaded into the kernel. @@ -109,12 +112,16 @@ func (m *bpfMaps) Close() error { type bpfPrograms struct { TcpV4Connect *ebpf.Program `ebpf:"tcp_v4_connect"` TcpV6Connect *ebpf.Program `ebpf:"tcp_v6_connect"` + UdpSendmsg *ebpf.Program `ebpf:"udp_sendmsg"` + Udpv6Sendmsg *ebpf.Program `ebpf:"udpv6_sendmsg"` } func (p *bpfPrograms) Close() error { return _BpfClose( p.TcpV4Connect, p.TcpV6Connect, + p.UdpSendmsg, + p.Udpv6Sendmsg, ) } diff --git a/firewall/interception/ebpf/bpf_bpfel.go b/firewall/interception/ebpf/bpf_bpfel.go index e04d9cc3..59acb8d2 100644 --- a/firewall/interception/ebpf/bpf_bpfel.go +++ b/firewall/interception/ebpf/bpf_bpfel.go @@ -20,7 +20,8 @@ type bpfEvent struct { Dport uint16 Pid uint32 IpVersion uint8 - _ [3]byte + Protocol uint8 + _ [2]byte } // loadBpf returns the embedded CollectionSpec for bpf. @@ -66,6 +67,8 @@ type bpfSpecs struct { type bpfProgramSpecs struct { TcpV4Connect *ebpf.ProgramSpec `ebpf:"tcp_v4_connect"` TcpV6Connect *ebpf.ProgramSpec `ebpf:"tcp_v6_connect"` + UdpSendmsg *ebpf.ProgramSpec `ebpf:"udp_sendmsg"` + Udpv6Sendmsg *ebpf.ProgramSpec `ebpf:"udpv6_sendmsg"` } // bpfMapSpecs contains maps before they are loaded into the kernel. @@ -109,12 +112,16 @@ func (m *bpfMaps) Close() error { type bpfPrograms struct { TcpV4Connect *ebpf.Program `ebpf:"tcp_v4_connect"` TcpV6Connect *ebpf.Program `ebpf:"tcp_v6_connect"` + UdpSendmsg *ebpf.Program `ebpf:"udp_sendmsg"` + Udpv6Sendmsg *ebpf.Program `ebpf:"udpv6_sendmsg"` } func (p *bpfPrograms) Close() error { return _BpfClose( p.TcpV4Connect, p.TcpV6Connect, + p.UdpSendmsg, + p.Udpv6Sendmsg, ) } diff --git a/firewall/interception/ebpf/packet.go b/firewall/interception/ebpf/packet.go index 0e48ba3e..fffd5558 100644 --- a/firewall/interception/ebpf/packet.go +++ b/firewall/interception/ebpf/packet.go @@ -3,6 +3,8 @@ package ebpf import ( + "fmt" + pmpacket "github.com/safing/portmaster/network/packet" ) @@ -13,19 +15,19 @@ type infoPacket struct { // LoadPacketData does nothing on Linux, as data is always fully parsed. func (pkt *infoPacket) LoadPacketData() error { - return nil // fmt.Errorf("can't load data info only packet") + return fmt.Errorf("can't load data in info only packet") } func (pkt *infoPacket) Accept() error { - return nil // fmt.Errorf("can't accept info only packet") + return nil } func (pkt *infoPacket) Block() error { - return nil // fmt.Errorf("can't block info only packet") + return nil } func (pkt *infoPacket) Drop() error { - return nil // fmt.Errorf("can't block info only packet") + return nil } func (pkt *infoPacket) PermanentAccept() error { @@ -37,13 +39,13 @@ func (pkt *infoPacket) PermanentBlock() error { } func (pkt *infoPacket) PermanentDrop() error { - return nil // fmt.Errorf("can't drop info only packet") + return nil } func (pkt *infoPacket) RerouteToNameserver() error { - return nil // fmt.Errorf("can't reroute info only packet") + return nil } func (pkt *infoPacket) RerouteToTunnel() error { - return nil // fmt.Errorf("can't reroute info only packet") + return nil } diff --git a/firewall/interception/ebpf/program/monitor.c b/firewall/interception/ebpf/program/monitor.c index dd0b8681..8b74e3c4 100644 --- a/firewall/interception/ebpf/program/monitor.c +++ b/firewall/interception/ebpf/program/monitor.c @@ -2,105 +2,213 @@ #include "bpf/bpf_helpers.h" #include "bpf/bpf_tracing.h" -typedef unsigned char u8; -typedef short int s16; -typedef short unsigned int u16; -typedef int s32; -typedef unsigned int u32; -typedef long long int s64; -typedef long long unsigned int u64; -typedef u16 le16; -typedef u16 be16; -typedef u32 be32; -typedef u64 be64; -typedef u32 wsum; - - +// IP Version #define AF_INET 2 #define AF_INET6 10 -#define TASK_COMM_LEN 16 -char __license[] SEC("license") = "Dual MIT/GPL"; +// Protocols +#define TCP 6 +#define UDP 17 +#define UDPLite 136 -struct -{ +char __license[] SEC("license") = "GPL"; + +// Ring buffer for all connection events +struct { __uint(type, BPF_MAP_TYPE_RINGBUF); __uint(max_entries, 1 << 24); } events SEC(".maps"); -/** - * The sample submitted to userspace over a ring buffer. - * Emit struct event's type info into the ELF's BTF so bpf2go - * can generate a Go type from it. - */ -struct event { - be32 saddr[4]; - be32 daddr[4]; +// Event struct that will be sent to Go on each new connection. (The name should be the same as the go generate command) +struct Event { + u32 saddr[4]; + u32 daddr[4]; u16 sport; u16 dport; u32 pid; u8 ipVersion; + u8 protocol; }; -struct event *unused __attribute__((unused)); +struct Event *unused __attribute__((unused)); +// Fexit of tcp_v4_connect will be executed when equivalent kernel function returns. +// In the kernel function all IPs and ports are set and then tcp_connect is called. tcp_v4_connect -> tcp_connect -> [this-function] SEC("fexit/tcp_v4_connect") int BPF_PROG(tcp_v4_connect, struct sock *sk) { + // Ignore everything else then IPv4 if (sk->__sk_common.skc_family != AF_INET) { return 0; } + // Make sure it's tcp sock struct tcp_sock *ts = bpf_skc_to_tcp_sock(sk); if (!ts) { return 0; } - struct event *tcp_info; - tcp_info = bpf_ringbuf_reserve(&events, sizeof(struct event), 0); + // Alloc space for the event + struct Event *tcp_info; + tcp_info = bpf_ringbuf_reserve(&events, sizeof(struct Event), 0); if (!tcp_info) { return 0; } + // Read PID tcp_info->pid = __builtin_bswap32((u32)bpf_get_current_pid_tgid()); + + // Set src and dist ports tcp_info->dport = sk->__sk_common.skc_dport; tcp_info->sport = sk->__sk_common.skc_num; + + // Set src and dist IPs tcp_info->saddr[0] = __builtin_bswap32(sk->__sk_common.skc_rcv_saddr); tcp_info->daddr[0] = __builtin_bswap32(sk->__sk_common.skc_daddr); + + // Set IP version tcp_info->ipVersion = 4; + // Set protocol + tcp_info->protocol = TCP; + + // Send event bpf_ringbuf_submit(tcp_info, 0); return 0; }; +// Fexit(function exit) of tcp_v6_connect will be executed when equivalent kernel function returns. +// In the kernel function all IPs and ports are set and then tcp_connect is called. tcp_v6_connect -> tcp_connect -> [this-function] SEC("fexit/tcp_v6_connect") int BPF_PROG(tcp_v6_connect, struct sock *sk) { + // Ignore everything else then IPv6 if (sk->__sk_common.skc_family != AF_INET6) { return 0; } - struct tcp_sock *ts = bpf_skc_to_tcp_sock(sk); + // Make sure its a tcp6 sock + struct tcp6_sock *ts = bpf_skc_to_tcp6_sock(sk); if (!ts) { return 0; } - struct event *tcp_info; - tcp_info = bpf_ringbuf_reserve(&events, sizeof(struct event), 0); + // Alloc space for the event + struct Event *tcp_info; + tcp_info = bpf_ringbuf_reserve(&events, sizeof(struct Event), 0); if (!tcp_info) { return 0; } + // Read PID tcp_info->pid = __builtin_bswap32((u32)bpf_get_current_pid_tgid()); + + // Set src and dist ports + tcp_info->dport = sk->__sk_common.skc_dport; + tcp_info->sport = sk->__sk_common.skc_num; + + // Set src and dist IPs for(int i = 0; i < 4; i++) { tcp_info->saddr[i] = __builtin_bswap32(sk->__sk_common.skc_v6_rcv_saddr.in6_u.u6_addr32[i]); } for(int i = 0; i < 4; i++) { tcp_info->daddr[i] = __builtin_bswap32(sk->__sk_common.skc_v6_daddr.in6_u.u6_addr32[i]); } - tcp_info->dport = sk->__sk_common.skc_dport; - tcp_info->sport = sk->__sk_common.skc_num; + + // Set IP version tcp_info->ipVersion = 6; + // Set protocol + tcp_info->protocol = TCP; + + // Send event bpf_ringbuf_submit(tcp_info, 0); return 0; }; -// SEC("fentry/udp_sendmsg") \ No newline at end of file +// Fentry(function enter) of udp_sendmsg will be executed before equivalent kernel function is called. +// [this-function] -> udp_sendmsg +SEC("fentry/udp_sendmsg") +int BPF_PROG(udp_sendmsg, struct sock *sk) { + // Ignore everything else then IPv4 + if (sk->__sk_common.skc_family != AF_INET) { + return 0; + } + + // Allocate space for the event. + struct Event *udp_info; + udp_info = bpf_ringbuf_reserve(&events, sizeof(struct Event), 0); + if (!udp_info) { + return 0; + } + + // Read PID + udp_info->pid = __builtin_bswap32((u32)bpf_get_current_pid_tgid()); + + // Set src and dist ports + udp_info->dport = sk->__sk_common.skc_dport; + udp_info->sport = sk->__sk_common.skc_num; + + // Set src and dist IPs + udp_info->saddr[0] = __builtin_bswap32(sk->__sk_common.skc_rcv_saddr); + udp_info->daddr[0] = __builtin_bswap32(sk->__sk_common.skc_daddr); + + // Set IP version + udp_info->ipVersion = 4; + + // Set protocol. No way to detect udplite for ipv4 + udp_info->protocol = UDP; + + // Send event + bpf_ringbuf_submit(udp_info, 0); + return 0; +} + +// Fentry(function enter) of udpv6_sendmsg will be executed before equivalent kernel function is called. +// [this-function] -> udpv6_sendmsg +SEC("fentry/udpv6_sendmsg") +int BPF_PROG(udpv6_sendmsg, struct sock *sk) { + // Ignore everything else then IPv6 + if (sk->__sk_common.skc_family != AF_INET6) { + return 0; + } + + // Make sure its udp6 socket + struct udp6_sock *us = bpf_skc_to_udp6_sock(sk); + if (!us) { + return 0; + } + + // Allocate space for the event. + struct Event *udp_info; + udp_info = bpf_ringbuf_reserve(&events, sizeof(struct Event), 0); + if (!udp_info) { + return 0; + } + + // Read PID + udp_info->pid = __builtin_bswap32((u32)bpf_get_current_pid_tgid()); + + // Set src and dist ports + udp_info->dport = sk->__sk_common.skc_dport; + udp_info->sport = sk->__sk_common.skc_num; + + // Set src and dist IPs + for(int i = 0; i < 4; i++) { + udp_info->saddr[i] = __builtin_bswap32(sk->__sk_common.skc_v6_rcv_saddr.in6_u.u6_addr32[i]); + } + for(int i = 0; i < 4; i++) { + udp_info->daddr[i] = __builtin_bswap32(sk->__sk_common.skc_v6_daddr.in6_u.u6_addr32[i]); + } + + // IP version + udp_info->ipVersion = 6; + + // Set protocol for UDPLite + if(us->udp.pcflag == 0) { + udp_info->protocol = UDP; + } else { + udp_info->protocol = UDPLite; + } + + // Send event + bpf_ringbuf_submit(udp_info, 0); + return 0; +} \ No newline at end of file diff --git a/firewall/interception/ebpf/worker.go b/firewall/interception/ebpf/worker.go index 0c425358..c9de3c2e 100644 --- a/firewall/interception/ebpf/worker.go +++ b/firewall/interception/ebpf/worker.go @@ -4,7 +4,6 @@ import ( "bytes" "encoding/binary" "errors" - "fmt" "net" "unsafe" @@ -15,7 +14,7 @@ import ( "github.com/safing/portmaster/network/packet" ) -//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cc clang -cflags "-O2 -g -Wall -Werror" -type event bpf program/monitor.c +//go:generate go run github.com/cilium/ebpf/cmd/bpf2go -cc clang -cflags "-O2 -g -Wall -Werror" -type Event bpf program/monitor.c var stopper chan struct{} func StartEBPFWorker(ch chan packet.Packet) { @@ -51,6 +50,15 @@ func StartEBPFWorker(ch chan packet.Packet) { } defer linkv6.Close() + // Create a link to the tcp_v6_connect program. + linkudp, err := link.AttachTracing(link.TracingOptions{ + Program: objs.bpfPrograms.UdpSendmsg, + }) + if err != nil { + log.Errorf("ebpf: failed to attach to udp_sendmsg: %s ", err) + } + defer linkudp.Close() + rd, err := ringbuf.NewReader(objs.bpfMaps.Events) if err != nil { log.Errorf("ebpf: failed to open ring buffer: %s", err) @@ -83,22 +91,23 @@ func StartEBPFWorker(ch chan packet.Packet) { log.Errorf("ebpf: failed to parse ringbuf event: %s", err) continue } - ipVersion := packet.IPVersion(event.IpVersion) - log.Debugf("ebpf: pid:%d connection %s -> %s ", int(event.Pid), intToIP(event.Saddr, ipVersion).String()+":"+fmt.Sprintf("%d", event.Sport), intToIP(event.Daddr, ipVersion).String()+":"+fmt.Sprintf("%d", event.Dport)) - pack := &infoPacket{} - pack.SetPacketInfo(packet.Info{ + info := packet.Info{ Inbound: false, InTunnel: false, Version: packet.IPVersion(event.IpVersion), - Protocol: packet.TCP, + Protocol: packet.IPProtocol(event.Protocol), SrcPort: event.Sport, DstPort: event.Dport, - Src: intToIP(event.Saddr, ipVersion), - Dst: intToIP(event.Daddr, ipVersion), + Src: arrayToIP(event.Saddr, packet.IPVersion(event.IpVersion)), + Dst: arrayToIP(event.Daddr, packet.IPVersion(event.IpVersion)), PID: event.Pid, - }) - ch <- pack + } + log.Debugf("ebpf: PID: %d conn: %s:%d -> %s:%d %s %s", info.PID, info.LocalIP(), info.LocalPort(), info.RemoteIP(), info.LocalPort(), info.Version.String(), info.Protocol.String()) + + p := &infoPacket{} + p.SetPacketInfo(info) + ch <- p } }() } @@ -107,8 +116,8 @@ func StopEBPFWorker() { close(stopper) } -// intToIP converts IPv4 number to net.IP -func intToIP(ipNum [4]uint32, ipVersion packet.IPVersion) net.IP { +// arrayToIP converts IPv4 number to net.IP +func arrayToIP(ipNum [4]uint32, ipVersion packet.IPVersion) net.IP { if ipVersion == packet.IPv4 { return unsafe.Slice((*byte)(unsafe.Pointer(&ipNum)), 4) } else { diff --git a/process/find.go b/process/find.go index 21810e0e..beb2a57e 100644 --- a/process/find.go +++ b/process/find.go @@ -34,7 +34,7 @@ func GetProcessByConnection(ctx context.Context, pktInfo *packet.Info) (process return nil, pktInfo.Inbound, err } } else { - log.Tracer(ctx).Tracef("process: pid already set in packet (by ebpf or kext ALE layer)") + log.Tracer(ctx).Tracef("process: pid already set in packet (by ebpf or kext)") pid = int(pktInfo.PID) }