Add udp process detection with ebpf

This commit is contained in:
Vladimir Stoilov
2023-06-07 19:10:41 +03:00
parent 26042ac935
commit 0164463ee5
6 changed files with 191 additions and 58 deletions

View File

@@ -20,7 +20,8 @@ type bpfEvent struct {
Dport uint16 Dport uint16
Pid uint32 Pid uint32
IpVersion uint8 IpVersion uint8
_ [3]byte Protocol uint8
_ [2]byte
} }
// loadBpf returns the embedded CollectionSpec for bpf. // loadBpf returns the embedded CollectionSpec for bpf.
@@ -66,6 +67,8 @@ type bpfSpecs struct {
type bpfProgramSpecs struct { type bpfProgramSpecs struct {
TcpV4Connect *ebpf.ProgramSpec `ebpf:"tcp_v4_connect"` TcpV4Connect *ebpf.ProgramSpec `ebpf:"tcp_v4_connect"`
TcpV6Connect *ebpf.ProgramSpec `ebpf:"tcp_v6_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. // bpfMapSpecs contains maps before they are loaded into the kernel.
@@ -109,12 +112,16 @@ func (m *bpfMaps) Close() error {
type bpfPrograms struct { type bpfPrograms struct {
TcpV4Connect *ebpf.Program `ebpf:"tcp_v4_connect"` TcpV4Connect *ebpf.Program `ebpf:"tcp_v4_connect"`
TcpV6Connect *ebpf.Program `ebpf:"tcp_v6_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 { func (p *bpfPrograms) Close() error {
return _BpfClose( return _BpfClose(
p.TcpV4Connect, p.TcpV4Connect,
p.TcpV6Connect, p.TcpV6Connect,
p.UdpSendmsg,
p.Udpv6Sendmsg,
) )
} }

View File

@@ -20,7 +20,8 @@ type bpfEvent struct {
Dport uint16 Dport uint16
Pid uint32 Pid uint32
IpVersion uint8 IpVersion uint8
_ [3]byte Protocol uint8
_ [2]byte
} }
// loadBpf returns the embedded CollectionSpec for bpf. // loadBpf returns the embedded CollectionSpec for bpf.
@@ -66,6 +67,8 @@ type bpfSpecs struct {
type bpfProgramSpecs struct { type bpfProgramSpecs struct {
TcpV4Connect *ebpf.ProgramSpec `ebpf:"tcp_v4_connect"` TcpV4Connect *ebpf.ProgramSpec `ebpf:"tcp_v4_connect"`
TcpV6Connect *ebpf.ProgramSpec `ebpf:"tcp_v6_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. // bpfMapSpecs contains maps before they are loaded into the kernel.
@@ -109,12 +112,16 @@ func (m *bpfMaps) Close() error {
type bpfPrograms struct { type bpfPrograms struct {
TcpV4Connect *ebpf.Program `ebpf:"tcp_v4_connect"` TcpV4Connect *ebpf.Program `ebpf:"tcp_v4_connect"`
TcpV6Connect *ebpf.Program `ebpf:"tcp_v6_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 { func (p *bpfPrograms) Close() error {
return _BpfClose( return _BpfClose(
p.TcpV4Connect, p.TcpV4Connect,
p.TcpV6Connect, p.TcpV6Connect,
p.UdpSendmsg,
p.Udpv6Sendmsg,
) )
} }

View File

@@ -3,6 +3,8 @@
package ebpf package ebpf
import ( import (
"fmt"
pmpacket "github.com/safing/portmaster/network/packet" 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. // LoadPacketData does nothing on Linux, as data is always fully parsed.
func (pkt *infoPacket) LoadPacketData() error { 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 { func (pkt *infoPacket) Accept() error {
return nil // fmt.Errorf("can't accept info only packet") return nil
} }
func (pkt *infoPacket) Block() error { func (pkt *infoPacket) Block() error {
return nil // fmt.Errorf("can't block info only packet") return nil
} }
func (pkt *infoPacket) Drop() error { func (pkt *infoPacket) Drop() error {
return nil // fmt.Errorf("can't block info only packet") return nil
} }
func (pkt *infoPacket) PermanentAccept() error { func (pkt *infoPacket) PermanentAccept() error {
@@ -37,13 +39,13 @@ func (pkt *infoPacket) PermanentBlock() error {
} }
func (pkt *infoPacket) PermanentDrop() error { func (pkt *infoPacket) PermanentDrop() error {
return nil // fmt.Errorf("can't drop info only packet") return nil
} }
func (pkt *infoPacket) RerouteToNameserver() error { func (pkt *infoPacket) RerouteToNameserver() error {
return nil // fmt.Errorf("can't reroute info only packet") return nil
} }
func (pkt *infoPacket) RerouteToTunnel() error { func (pkt *infoPacket) RerouteToTunnel() error {
return nil // fmt.Errorf("can't reroute info only packet") return nil
} }

View File

@@ -2,105 +2,213 @@
#include "bpf/bpf_helpers.h" #include "bpf/bpf_helpers.h"
#include "bpf/bpf_tracing.h" #include "bpf/bpf_tracing.h"
typedef unsigned char u8; // IP Version
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;
#define AF_INET 2 #define AF_INET 2
#define AF_INET6 10 #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(type, BPF_MAP_TYPE_RINGBUF);
__uint(max_entries, 1 << 24); __uint(max_entries, 1 << 24);
} events SEC(".maps"); } events SEC(".maps");
/** // Event struct that will be sent to Go on each new connection. (The name should be the same as the go generate command)
* The sample submitted to userspace over a ring buffer. struct Event {
* Emit struct event's type info into the ELF's BTF so bpf2go u32 saddr[4];
* can generate a Go type from it. u32 daddr[4];
*/
struct event {
be32 saddr[4];
be32 daddr[4];
u16 sport; u16 sport;
u16 dport; u16 dport;
u32 pid; u32 pid;
u8 ipVersion; 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") SEC("fexit/tcp_v4_connect")
int BPF_PROG(tcp_v4_connect, struct sock *sk) { int BPF_PROG(tcp_v4_connect, struct sock *sk) {
// Ignore everything else then IPv4
if (sk->__sk_common.skc_family != AF_INET) { if (sk->__sk_common.skc_family != AF_INET) {
return 0; return 0;
} }
// Make sure it's tcp sock
struct tcp_sock *ts = bpf_skc_to_tcp_sock(sk); struct tcp_sock *ts = bpf_skc_to_tcp_sock(sk);
if (!ts) { if (!ts) {
return 0; return 0;
} }
struct event *tcp_info; // Alloc space for the event
tcp_info = bpf_ringbuf_reserve(&events, sizeof(struct event), 0); struct Event *tcp_info;
tcp_info = bpf_ringbuf_reserve(&events, sizeof(struct Event), 0);
if (!tcp_info) { if (!tcp_info) {
return 0; return 0;
} }
// Read PID
tcp_info->pid = __builtin_bswap32((u32)bpf_get_current_pid_tgid()); 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->dport = sk->__sk_common.skc_dport;
tcp_info->sport = sk->__sk_common.skc_num; 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->saddr[0] = __builtin_bswap32(sk->__sk_common.skc_rcv_saddr);
tcp_info->daddr[0] = __builtin_bswap32(sk->__sk_common.skc_daddr); tcp_info->daddr[0] = __builtin_bswap32(sk->__sk_common.skc_daddr);
// Set IP version
tcp_info->ipVersion = 4; tcp_info->ipVersion = 4;
// Set protocol
tcp_info->protocol = TCP;
// Send event
bpf_ringbuf_submit(tcp_info, 0); bpf_ringbuf_submit(tcp_info, 0);
return 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") SEC("fexit/tcp_v6_connect")
int BPF_PROG(tcp_v6_connect, struct sock *sk) { int BPF_PROG(tcp_v6_connect, struct sock *sk) {
// Ignore everything else then IPv6
if (sk->__sk_common.skc_family != AF_INET6) { if (sk->__sk_common.skc_family != AF_INET6) {
return 0; 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) { if (!ts) {
return 0; return 0;
} }
struct event *tcp_info; // Alloc space for the event
tcp_info = bpf_ringbuf_reserve(&events, sizeof(struct event), 0); struct Event *tcp_info;
tcp_info = bpf_ringbuf_reserve(&events, sizeof(struct Event), 0);
if (!tcp_info) { if (!tcp_info) {
return 0; return 0;
} }
// Read PID
tcp_info->pid = __builtin_bswap32((u32)bpf_get_current_pid_tgid()); 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++) { 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]); 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++) { 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->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; tcp_info->ipVersion = 6;
// Set protocol
tcp_info->protocol = TCP;
// Send event
bpf_ringbuf_submit(tcp_info, 0); bpf_ringbuf_submit(tcp_info, 0);
return 0; return 0;
}; };
// SEC("fentry/udp_sendmsg") // 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;
}

View File

@@ -4,7 +4,6 @@ import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt"
"net" "net"
"unsafe" "unsafe"
@@ -15,7 +14,7 @@ import (
"github.com/safing/portmaster/network/packet" "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{} var stopper chan struct{}
func StartEBPFWorker(ch chan packet.Packet) { func StartEBPFWorker(ch chan packet.Packet) {
@@ -51,6 +50,15 @@ func StartEBPFWorker(ch chan packet.Packet) {
} }
defer linkv6.Close() 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) rd, err := ringbuf.NewReader(objs.bpfMaps.Events)
if err != nil { if err != nil {
log.Errorf("ebpf: failed to open ring buffer: %s", err) 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) log.Errorf("ebpf: failed to parse ringbuf event: %s", err)
continue 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, Inbound: false,
InTunnel: false, InTunnel: false,
Version: packet.IPVersion(event.IpVersion), Version: packet.IPVersion(event.IpVersion),
Protocol: packet.TCP, Protocol: packet.IPProtocol(event.Protocol),
SrcPort: event.Sport, SrcPort: event.Sport,
DstPort: event.Dport, DstPort: event.Dport,
Src: intToIP(event.Saddr, ipVersion), Src: arrayToIP(event.Saddr, packet.IPVersion(event.IpVersion)),
Dst: intToIP(event.Daddr, ipVersion), Dst: arrayToIP(event.Daddr, packet.IPVersion(event.IpVersion)),
PID: event.Pid, 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) close(stopper)
} }
// intToIP converts IPv4 number to net.IP // arrayToIP converts IPv4 number to net.IP
func intToIP(ipNum [4]uint32, ipVersion packet.IPVersion) net.IP { func arrayToIP(ipNum [4]uint32, ipVersion packet.IPVersion) net.IP {
if ipVersion == packet.IPv4 { if ipVersion == packet.IPv4 {
return unsafe.Slice((*byte)(unsafe.Pointer(&ipNum)), 4) return unsafe.Slice((*byte)(unsafe.Pointer(&ipNum)), 4)
} else { } else {

View File

@@ -34,7 +34,7 @@ func GetProcessByConnection(ctx context.Context, pktInfo *packet.Info) (process
return nil, pktInfo.Inbound, err return nil, pktInfo.Inbound, err
} }
} else { } 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) pid = int(pktInfo.PID)
} }