feat: dae trace (#435)

Co-authored-by: Sumire (菫) <151038614+sumire88@users.noreply.github.com>
This commit is contained in:
/gray 2024-01-27 13:33:00 +08:00 committed by GitHub
parent e04b16fdea
commit 5f3249bcb3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 738 additions and 5 deletions

1
.gitignore vendored
View File

@ -9,3 +9,4 @@ outline.json
go-mod/ go-mod/
node_modules/ node_modules/
*.log *.log
.build_tags

3
.gitmodules vendored
View File

@ -1,3 +1,6 @@
[submodule "control/kern/headers"] [submodule "control/kern/headers"]
path = control/kern/headers path = control/kern/headers
url = https://github.com/daeuniverse/dae_bpf_headers url = https://github.com/daeuniverse/dae_bpf_headers
[submodule "trace/kern/headers"]
path = trace/kern/headers
url = https://github.com/daeuniverse/dae_bpf_headers

View File

@ -1 +1,2 @@
submodule_paths=control/kern/headers submodule_paths=control/kern/headers
submodule_paths=trace/kern/headers

View File

@ -15,6 +15,7 @@ MAX_MATCH_SET_LEN ?= 64
CFLAGS := -DMAX_MATCH_SET_LEN=$(MAX_MATCH_SET_LEN) $(CFLAGS) CFLAGS := -DMAX_MATCH_SET_LEN=$(MAX_MATCH_SET_LEN) $(CFLAGS)
NOSTRIP ?= n NOSTRIP ?= n
STRIP_PATH := $(shell command -v $(STRIP) 2>/dev/null) STRIP_PATH := $(shell command -v $(STRIP) 2>/dev/null)
BUILD_TAGS_FILE := .build_tags
ifeq ($(strip $(NOSTRIP)),y) ifeq ($(strip $(NOSTRIP)),y)
STRIP_FLAG := -no-strip STRIP_FLAG := -no-strip
else ifeq ($(wildcard $(STRIP_PATH)),) else ifeq ($(wildcard $(STRIP_PATH)),)
@ -47,7 +48,7 @@ dae: export CGO_ENABLED=0
endif endif
dae: ebpf dae: ebpf
@echo $(CFLAGS) @echo $(CFLAGS)
go build -o $(OUTPUT) $(BUILD_ARGS) . go build -tags=$(shell cat $(BUILD_TAGS_FILE)) -o $(OUTPUT) $(BUILD_ARGS) .
## End Dae Build ## End Dae Build
## Begin Git Submodules ## Begin Git Submodules
@ -74,6 +75,8 @@ submodule submodules: $(submodule_paths)
clean-ebpf: clean-ebpf:
@rm -f control/bpf_bpf*.go && \ @rm -f control/bpf_bpf*.go && \
rm -f control/bpf_bpf*.o rm -f control/bpf_bpf*.o
@rm -f trace/bpf_bpf*.go && \
rm -f trace/bpf_bpf*.o
fmt: fmt:
go fmt ./... go fmt ./...
@ -82,10 +85,13 @@ ebpf: export BPF_CLANG := $(CLANG)
ebpf: export BPF_STRIP_FLAG := $(STRIP_FLAG) ebpf: export BPF_STRIP_FLAG := $(STRIP_FLAG)
ebpf: export BPF_CFLAGS := $(CFLAGS) ebpf: export BPF_CFLAGS := $(CFLAGS)
ebpf: export BPF_TARGET := $(TARGET) ebpf: export BPF_TARGET := $(TARGET)
ebpf: export BPF_TRACE_TARGET := $(GOARCH)
ebpf: submodule clean-ebpf ebpf: submodule clean-ebpf
@unset GOOS && \ @unset GOOS && \
unset GOARCH && \ unset GOARCH && \
unset GOARM && \ unset GOARM && \
echo $(STRIP_FLAG) && \ echo $(STRIP_FLAG) && \
go generate ./control/control.go go generate ./control/control.go && \
go generate ./trace/trace.go && echo trace > $(BUILD_TAGS_FILE) || echo > $(BUILD_TAGS_FILE)
## End Ebpf ## End Ebpf

72
cmd/trace.go Normal file
View File

@ -0,0 +1,72 @@
//go:build trace
// +build trace
/*
* SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) 2022-2024, daeuniverse Organization <dae@v2raya.org>
*/
package cmd
import (
"context"
"os/signal"
"syscall"
"github.com/daeuniverse/dae/cmd/internal"
"github.com/daeuniverse/dae/trace"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
var (
IPv4, IPv6 bool
L4Proto string
Port int
OutputFile string
)
func init() {
traceCmd := &cobra.Command{
Use: "trace",
Short: "To trace traffic",
Run: func(cmd *cobra.Command, args []string) {
internal.AutoSu()
if IPv4 && IPv6 {
logrus.Fatalln("IPv4 and IPv6 cannot be set at the same time")
}
if !IPv4 && !IPv6 {
IPv4 = true
}
IPVersion := 4
if IPv6 {
IPVersion = 6
}
var L4ProtoNo uint16
switch L4Proto {
case "tcp":
L4ProtoNo = syscall.IPPROTO_TCP
case "udp":
L4ProtoNo = syscall.IPPROTO_UDP
default:
logrus.Fatalf("Unknown L4 protocol: %s\n", L4Proto)
}
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer cancel()
if err := trace.StartTrace(ctx, IPVersion, L4ProtoNo, Port, OutputFile); err != nil {
logrus.Fatalln(err)
}
},
}
traceCmd.PersistentFlags().BoolVarP(&IPv4, "ipv4", "4", false, "Capture IPv4 traffic")
traceCmd.PersistentFlags().BoolVarP(&IPv6, "ipv6", "6", false, "Capture IPv6 traffic")
traceCmd.PersistentFlags().StringVarP(&L4Proto, "l4-proto", "p", "tcp", "Layer 4 protocol")
traceCmd.PersistentFlags().IntVarP(&Port, "port", "P", 80, "Port")
traceCmd.PersistentFlags().StringVarP(&OutputFile, "output", "o", "/dev/stdout", "Output file")
rootCmd.AddCommand(traceCmd)
}

@ -1 +1 @@
Subproject commit d72c67ed8f5a7d11774b5cd88734e2ffe6847721 Subproject commit e4da1c9601e1c3797d02c481a462b66588477495

4
go.mod
View File

@ -8,7 +8,7 @@ require (
github.com/adrg/xdg v0.4.0 github.com/adrg/xdg v0.4.0
github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230305170008-8188dc5388df
github.com/bits-and-blooms/bloom/v3 v3.5.0 github.com/bits-and-blooms/bloom/v3 v3.5.0
github.com/cilium/ebpf v0.11.0 github.com/cilium/ebpf v0.12.3
github.com/daeuniverse/dae-config-dist/go/dae_config v0.0.0-20230604120805-1c27619b592d github.com/daeuniverse/dae-config-dist/go/dae_config v0.0.0-20230604120805-1c27619b592d
github.com/daeuniverse/outbound v0.0.0-20240101085641-7932e7df927d github.com/daeuniverse/outbound v0.0.0-20240101085641-7932e7df927d
github.com/daeuniverse/softwind v0.0.0-20231230065827-eed67f20d2c1 github.com/daeuniverse/softwind v0.0.0-20231230065827-eed67f20d2c1
@ -24,7 +24,7 @@ require (
github.com/x-cray/logrus-prefixed-formatter v0.5.2 github.com/x-cray/logrus-prefixed-formatter v0.5.2
golang.org/x/crypto v0.12.0 golang.org/x/crypto v0.12.0
golang.org/x/exp v0.0.0-20230728194245-b0cb94b80691 golang.org/x/exp v0.0.0-20230728194245-b0cb94b80691
golang.org/x/sys v0.11.0 golang.org/x/sys v0.14.1-0.20231108175955-e4099bfacb8c
google.golang.org/protobuf v1.31.0 google.golang.org/protobuf v1.31.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/natefinch/lumberjack.v2 v2.2.1
) )

4
go.sum
View File

@ -10,6 +10,8 @@ github.com/bits-and-blooms/bloom/v3 v3.5.0 h1:AKDvi1V3xJCmSR6QhcBfHbCN4Vf8FfxeWk
github.com/bits-and-blooms/bloom/v3 v3.5.0/go.mod h1:Y8vrn7nk1tPIlmLtW2ZPV+W7StdVMor6bC1xgpjMZFs= github.com/bits-and-blooms/bloom/v3 v3.5.0/go.mod h1:Y8vrn7nk1tPIlmLtW2ZPV+W7StdVMor6bC1xgpjMZFs=
github.com/cilium/ebpf v0.11.0 h1:V8gS/bTCCjX9uUnkUFUpPsksM8n1lXBAvHcpiFk1X2Y= github.com/cilium/ebpf v0.11.0 h1:V8gS/bTCCjX9uUnkUFUpPsksM8n1lXBAvHcpiFk1X2Y=
github.com/cilium/ebpf v0.11.0/go.mod h1:WE7CZAnqOL2RouJ4f1uyNhqr2P4CCvXFIqdRDUgWsVs= github.com/cilium/ebpf v0.11.0/go.mod h1:WE7CZAnqOL2RouJ4f1uyNhqr2P4CCvXFIqdRDUgWsVs=
github.com/cilium/ebpf v0.12.3 h1:8ht6F9MquybnY97at+VDZb3eQQr8ev79RueWeVaEcG4=
github.com/cilium/ebpf v0.12.3/go.mod h1:TctK1ivibvI3znr66ljgi4hqOT8EYQjz1KWBfb1UVgM=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/daeuniverse/dae-config-dist/go/dae_config v0.0.0-20230604120805-1c27619b592d h1:hnC39MjR7xt5kZjrKlef7DXKFDkiX8MIcDXYC/6Jf9Q= github.com/daeuniverse/dae-config-dist/go/dae_config v0.0.0-20230604120805-1c27619b592d h1:hnC39MjR7xt5kZjrKlef7DXKFDkiX8MIcDXYC/6Jf9Q=
github.com/daeuniverse/dae-config-dist/go/dae_config v0.0.0-20230604120805-1c27619b592d/go.mod h1:VGWGgv7pCP5WGyHGUyb9+nq/gW0yBm+i/GfCNATOJ1M= github.com/daeuniverse/dae-config-dist/go/dae_config v0.0.0-20230604120805-1c27619b592d/go.mod h1:VGWGgv7pCP5WGyHGUyb9+nq/gW0yBm+i/GfCNATOJ1M=
@ -200,6 +202,8 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.1-0.20231108175955-e4099bfacb8c h1:3kC/TjQ+xzIblQv39bCOyRk8fbEeJcDHwbyxPUU2BpA=
golang.org/x/sys v0.14.1-0.20231108175955-e4099bfacb8c/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0= golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0=
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

71
trace/kallsyms.go Normal file
View File

@ -0,0 +1,71 @@
/*
* SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) 2022-2024, daeuniverse Organization <dae@v2raya.org>
*/
package trace
import (
"bufio"
"os"
"sort"
"strconv"
"strings"
"github.com/sirupsen/logrus"
"golang.org/x/exp/slices"
)
type Symbol struct {
Type string
Name string
Addr uint64
}
var kallsyms []Symbol
var kallsymsByName map[string]Symbol = make(map[string]Symbol)
var kallsymsByAddr map[uint64]Symbol = make(map[uint64]Symbol)
func init() {
readKallsyms()
}
func readKallsyms() {
file, err := os.Open("/proc/kallsyms")
if err != nil {
logrus.Fatalf("failed to open /proc/kallsyms: %v", err)
}
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
parts := strings.Fields(line)
if len(parts) < 3 {
continue
}
addr, err := strconv.ParseUint(parts[0], 16, 64)
if err != nil {
continue
}
typ, name := parts[1], parts[2]
kallsyms = append(kallsyms, Symbol{typ, name, addr})
kallsymsByName[name] = Symbol{typ, name, addr}
kallsymsByAddr[addr] = Symbol{typ, name, addr}
}
sort.Slice(kallsyms, func(i, j int) bool {
return kallsyms[i].Addr < kallsyms[j].Addr
})
}
func NearestSymbol(addr uint64) Symbol {
idx, _ := slices.BinarySearchFunc(kallsyms, addr, func(x Symbol, addr uint64) int { return int(x.Addr - addr) })
if idx == len(kallsyms) {
return kallsyms[idx-1]
}
if kallsyms[idx].Addr == addr {
return kallsyms[idx]
}
if idx == 0 {
return kallsyms[0]
}
return kallsyms[idx-1]
}

1
trace/kern/headers Submodule

@ -0,0 +1 @@
Subproject commit e4da1c9601e1c3797d02c481a462b66588477495

241
trace/kern/trace.c Normal file
View File

@ -0,0 +1,241 @@
#include "headers/if_ether_defs.h"
#include "headers/vmlinux.h"
#include "headers/bpf_core_read.h"
#include "headers/bpf_endian.h"
#include "headers/bpf_helpers.h"
#include "headers/bpf_tracing.h"
#define IFNAMSIZ 16
#define PNAME_LEN 32
static const bool TRUE = true;
union addr {
u32 v4addr;
struct {
u64 d1;
u64 d2;
} v6addr;
} __attribute__((packed));
struct meta {
u64 pc;
u64 skb;
u64 second_param;
u32 mark;
u32 netns;
u32 ifindex;
u32 pid;
unsigned char ifname[IFNAMSIZ];
unsigned char pname[PNAME_LEN];
} __attribute__((packed));
struct tuple {
union addr saddr;
union addr daddr;
u16 sport;
u16 dport;
u16 l3_proto;
u8 l4_proto;
u8 tcp_flags;
u16 payload_len;
} __attribute__((packed));
struct event {
struct meta meta;
struct tuple tuple;
} __attribute__((packed));
const struct event *_ __attribute__((unused));
struct tracing_config {
u16 port;
u16 l4_proto;
u8 ip_vsn;
};
static volatile const struct tracing_config tracing_cfg;
struct {
__uint(type, BPF_MAP_TYPE_HASH);
__type(key, __u64);
__type(value, bool);
__uint(max_entries, 1024);
} skb_addresses SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_RINGBUF);
__uint(max_entries, 1<<29);
} events SEC(".maps");
static __always_inline u32
get_netns(struct sk_buff *skb)
{
u32 netns = BPF_CORE_READ(skb, dev, nd_net.net, ns.inum);
// if skb->dev is not initialized, try to get ns from sk->__sk_common.skc_net.net->ns.inum
if (netns == 0) {
struct sock *sk = BPF_CORE_READ(skb, sk);
if (sk != NULL)
netns = BPF_CORE_READ(sk, __sk_common.skc_net.net, ns.inum);
}
return netns;
}
static __always_inline bool
filter_l3_and_l4(struct sk_buff *skb)
{
void *skb_head = BPF_CORE_READ(skb, head);
u16 l3_off = BPF_CORE_READ(skb, network_header);
u16 l4_off = BPF_CORE_READ(skb, transport_header);
struct iphdr *l3_hdr = (struct iphdr *) (skb_head + l3_off);
u8 ip_vsn = BPF_CORE_READ_BITFIELD_PROBED(l3_hdr, version);
if (ip_vsn != tracing_cfg.ip_vsn)
return false;
u16 l4_proto;
if (ip_vsn == 4) {
struct iphdr *ip4 = (struct iphdr *) l3_hdr;
l4_proto = BPF_CORE_READ(ip4, protocol);
} else if (ip_vsn == 6) {
struct ipv6hdr *ip6 = (struct ipv6hdr *) l3_hdr;
l4_proto = BPF_CORE_READ(ip6, nexthdr);
} else {
return false;
}
if (l4_proto != tracing_cfg.l4_proto)
return false;
u16 sport, dport;
if (l4_proto == IPPROTO_TCP) {
struct tcphdr *tcp = (struct tcphdr *) (skb_head + l4_off);
sport = BPF_CORE_READ(tcp, source);
dport = BPF_CORE_READ(tcp, dest);
} else if (l4_proto == IPPROTO_UDP) {
struct udphdr *udp = (struct udphdr *) (skb_head + l4_off);
sport = BPF_CORE_READ(udp, source);
dport = BPF_CORE_READ(udp, dest);
} else {
return false;
}
if (dport != tracing_cfg.port && sport != tracing_cfg.port)
return false;
return true;
}
static __always_inline void
set_meta(struct meta *meta, struct sk_buff *skb, struct pt_regs *ctx)
{
meta->pc = BPF_CORE_READ(ctx, ip);
meta->skb = (__u64)skb;
meta->second_param = PT_REGS_PARM2(ctx);
meta->mark = BPF_CORE_READ(skb, mark);
meta->netns = get_netns(skb);
meta->ifindex = BPF_CORE_READ(skb, dev, ifindex);
BPF_CORE_READ_STR_INTO(&meta->ifname, skb, dev, name);
struct task_struct *current = (void *)bpf_get_current_task();
meta->pid = BPF_CORE_READ(current, pid);
u64 arg_start = BPF_CORE_READ(current, mm, arg_start);
bpf_probe_read_user_str(&meta->pname, PNAME_LEN, (void *)arg_start);
}
static __always_inline void
set_tuple(struct tuple *tpl, struct sk_buff *skb)
{
void *skb_head = BPF_CORE_READ(skb, head);
u16 l3_off = BPF_CORE_READ(skb, network_header);
u16 l4_off = BPF_CORE_READ(skb, transport_header);
struct iphdr *l3_hdr = (struct iphdr *) (skb_head + l3_off);
u8 ip_vsn = BPF_CORE_READ_BITFIELD_PROBED(l3_hdr, version);
u16 l3_total_len;
if (ip_vsn == 4) {
struct iphdr *ip4 = (struct iphdr *) l3_hdr;
BPF_CORE_READ_INTO(&tpl->saddr, ip4, saddr);
BPF_CORE_READ_INTO(&tpl->daddr, ip4, daddr);
tpl->l4_proto = BPF_CORE_READ(ip4, protocol);
tpl->l3_proto = ETH_P_IP;
l3_total_len = bpf_ntohs(BPF_CORE_READ(ip4, tot_len));
} else if (ip_vsn == 6) {
struct ipv6hdr *ip6 = (struct ipv6hdr *) l3_hdr;
BPF_CORE_READ_INTO(&tpl->saddr, ip6, saddr);
BPF_CORE_READ_INTO(&tpl->daddr, ip6, daddr);
tpl->l4_proto = BPF_CORE_READ(ip6, nexthdr);
tpl->l3_proto = ETH_P_IPV6;
l3_total_len = bpf_ntohs(BPF_CORE_READ(ip6, payload_len));
}
u16 l3_hdr_len = l4_off - l3_off;
u16 l4_hdr_len;
if (tpl->l4_proto == IPPROTO_TCP) {
struct tcphdr *tcp = (struct tcphdr *) (skb_head + l4_off);
tpl->sport= BPF_CORE_READ(tcp, source);
tpl->dport= BPF_CORE_READ(tcp, dest);
bpf_probe_read_kernel(&tpl->tcp_flags, sizeof(tpl->tcp_flags),
(void *)tcp + offsetof(struct tcphdr, ack_seq) + 5);
l4_hdr_len = BPF_CORE_READ_BITFIELD_PROBED(tcp, doff) * 4;
tpl->payload_len = l3_total_len - l3_hdr_len - l4_hdr_len;
} else if (tpl->l4_proto == IPPROTO_UDP) {
struct udphdr *udp = (struct udphdr *) (skb_head + l4_off);
tpl->sport= BPF_CORE_READ(udp, source);
tpl->dport= BPF_CORE_READ(udp, dest);
tpl->payload_len = bpf_ntohs(BPF_CORE_READ(udp, len)) - sizeof(struct udphdr);
}
}
static __always_inline int
handle_skb(struct sk_buff *skb, struct pt_regs *ctx)
{
bool tracked = false;
u64 skb_addr = (u64) skb;
struct event ev = {};
if (bpf_map_lookup_elem(&skb_addresses, &skb_addr)) {
tracked = true;
goto cont;
}
if (!filter_l3_and_l4(skb))
return 0;
if (!tracked)
bpf_map_update_elem(&skb_addresses, &skb_addr, &TRUE, BPF_ANY);
cont:
set_meta(&ev.meta, skb, ctx);
set_tuple(&ev.tuple, skb);
bpf_ringbuf_output(&events, &ev, sizeof(ev), 0);
return 0;
}
#define KPROBE_SKB_AT(X) \
SEC("kprobe/skb-" #X) \
int kprobe_skb_##X(struct pt_regs *ctx) \
{ \
struct sk_buff *skb = (struct sk_buff *) PT_REGS_PARM##X(ctx); \
return handle_skb(skb, ctx); \
}
KPROBE_SKB_AT(1)
KPROBE_SKB_AT(2)
KPROBE_SKB_AT(3)
KPROBE_SKB_AT(4)
KPROBE_SKB_AT(5)
SEC("kprobe/skb_lifetime_termination")
int kprobe_skb_lifetime_termination(struct pt_regs *ctx)
{
u64 skb = (u64) PT_REGS_PARM1(ctx);
bpf_map_delete_elem(&skb_addresses, &skb);
return 0;
}
SEC("license") const char __license[] = "Dual BSD/GPL";

283
trace/trace.go Normal file
View File

@ -0,0 +1,283 @@
/*
* SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) 2022-2024, daeuniverse Organization <dae@v2raya.org>
*/
package trace
import (
"bytes"
"context"
"encoding/binary"
"errors"
"fmt"
"net"
"os"
"syscall"
"unsafe"
"github.com/cilium/ebpf"
"github.com/cilium/ebpf/btf"
"github.com/cilium/ebpf/link"
"github.com/cilium/ebpf/ringbuf"
"github.com/sirupsen/logrus"
)
//go:generate go run -mod=mod github.com/cilium/ebpf/cmd/bpf2go -cc "$BPF_CLANG" "$BPF_STRIP_FLAG" -cflags "$BPF_CFLAGS" -target "$BPF_TRACE_TARGET" -type event bpf kern/trace.c -- -I./headers
var nativeEndian binary.ByteOrder
func init() {
buf := [2]byte{}
*(*uint16)(unsafe.Pointer(&buf[0])) = uint16(0xABCD)
switch buf {
case [2]byte{0xCD, 0xAB}:
nativeEndian = binary.LittleEndian
case [2]byte{0xAB, 0xCD}:
nativeEndian = binary.BigEndian
default:
panic("Could not determine native endianness.")
}
}
func StartTrace(ctx context.Context, ipVersion int, l4ProtoNo uint16, port int, outputFile string) (err error) {
objs, err := rewriteAndLoadBpf(ipVersion, l4ProtoNo, port)
if err != nil {
return
}
defer objs.Close()
targets, kfreeSkbReasons, err := searchAvailableTargets()
if err != nil {
return
}
links, err := attachBpfToTargets(objs, targets)
if err != nil {
return
}
defer func() {
i := 0
fmt.Printf("\n")
for _, link := range links {
i++
fmt.Printf("detaching kprobes: %04d/%04d\r", i, len(links))
link.Close()
}
fmt.Printf("\n")
}()
fmt.Printf("\nstart tracing\n")
if err = handleEvents(ctx, objs, outputFile, kfreeSkbReasons); err != nil {
return
}
return
}
func rewriteAndLoadBpf(ipVersion int, l4ProtoNo uint16, port int) (_ *bpfObjects, err error) {
spec, err := loadBpf()
if err != nil {
return nil, fmt.Errorf("failed to load BPF: %+v\n", err)
}
if err := spec.RewriteConstants(map[string]interface{}{
"tracing_cfg": struct {
port uint16
l4Proto uint16
ipVersion uint8
pad uint8
}{
port: Htons(uint16(port)),
l4Proto: uint16(l4ProtoNo),
ipVersion: uint8(ipVersion),
pad: 0,
},
}); err != nil {
return nil, fmt.Errorf("failed to rewrite constants: %+v\n", err)
}
var opts ebpf.CollectionOptions
opts.Programs.LogLevel = ebpf.LogLevelInstruction
opts.Programs.LogSize = ebpf.DefaultVerifierLogSize * 100
objs := bpfObjects{}
if err := spec.LoadAndAssign(&objs, &opts); err != nil {
var (
ve *ebpf.VerifierError
verifierLog string
)
if errors.As(err, &ve) {
verifierLog = fmt.Sprintf("Verifier error: %+v\n", ve)
}
return nil, fmt.Errorf("failed to load BPF: %+v\n%s", err, verifierLog)
}
return &objs, nil
}
func searchAvailableTargets() (targets map[string]int, kfreeSkbReasons map[uint64]string, err error) {
targets = map[string]int{}
btfSpec, err := btf.LoadKernelSpec()
if err != nil {
return nil, nil, fmt.Errorf("failed to load kernel BTF: %+v\n", err)
}
if kfreeSkbReasons, err = getKFreeSKBReasons(btfSpec); err != nil {
return
}
iter := btfSpec.Iterate()
for iter.Next() {
typ := iter.Type
fn, ok := typ.(*btf.Func)
if !ok {
continue
}
fnName := string(fn.Name)
fnProto := fn.Type.(*btf.FuncProto)
i := 1
for _, p := range fnProto.Params {
if ptr, ok := p.Type.(*btf.Pointer); ok {
if strct, ok := ptr.Target.(*btf.Struct); ok {
if strct.Name == "sk_buff" && i <= 5 {
name := fnName
targets[name] = i
continue
}
}
}
i += 1
}
}
return targets, kfreeSkbReasons, nil
}
func getKFreeSKBReasons(spec *btf.Spec) (map[uint64]string, error) {
if _, err := spec.AnyTypeByName("kfree_skb_reason"); err != nil {
// Kernel is too old to have kfree_skb_reason
return nil, nil
}
var dropReasonsEnum *btf.Enum
if err := spec.TypeByName("skb_drop_reason", &dropReasonsEnum); err != nil {
return nil, fmt.Errorf("failed to find 'skb_drop_reason' enum: %v", err)
}
ret := map[uint64]string{}
for _, val := range dropReasonsEnum.Values {
ret[uint64(val.Value)] = val.Name
}
return ret, nil
}
func attachBpfToTargets(objs *bpfObjects, targets map[string]int) (links []link.Link, err error) {
kp, err := link.Kprobe("kfree_skbmem", objs.KprobeSkbLifetimeTermination, nil)
if err != nil {
logrus.Warnf("failed to attach kprobe to kfree_skbmem: %+v\n", err)
}
i := 0
for fn, pos := range targets {
i++
fmt.Printf("attaching kprobes: %04d/%04d\r", i, len(targets))
var kp link.Link
switch pos {
case 1:
kp, err = link.Kprobe(fn, objs.KprobeSkb1, nil)
case 2:
kp, err = link.Kprobe(fn, objs.KprobeSkb2, nil)
case 3:
kp, err = link.Kprobe(fn, objs.KprobeSkb3, nil)
case 4:
kp, err = link.Kprobe(fn, objs.KprobeSkb4, nil)
case 5:
kp, err = link.Kprobe(fn, objs.KprobeSkb5, nil)
}
if err != nil {
logrus.Debugf("failed to attach kprobe to %s: %+v\n", fn, err)
continue
}
links = append(links, kp)
}
if len(links) == 0 {
err = fmt.Errorf("failed to attach kprobes to any target")
}
links = append(links, kp)
return links, nil
}
func handleEvents(ctx context.Context, objs *bpfObjects, outputFile string, kfreeSkbReasons map[uint64]string) (err error) {
writer, err := os.Create(outputFile)
if err != nil {
return
}
eventsReader, err := ringbuf.NewReader(objs.Events)
if err != nil {
return fmt.Errorf("failed to create ringbuf reader: %+v\n", err)
}
defer eventsReader.Close()
go func() {
<-ctx.Done()
eventsReader.Close()
}()
type bpfEvent struct {
Pc uint64
Skb uint64
SecondParam uint64
Mark uint32
Netns uint32
Ifindex uint32
Pid uint32
Ifname [16]uint8
Pname [32]uint8
Saddr [16]byte
Daddr [16]byte
Sport uint16
Dport uint16
L3Proto uint16
L4Proto uint8
TcpFlags uint8
PayloadLen uint16
}
for {
rec, err := eventsReader.Read()
if err != nil {
if errors.Is(err, ringbuf.ErrClosed) {
return nil
}
logrus.Debugf("failed to read ringbuf: %+v", err)
continue
}
var event bpfEvent
if err = binary.Read(bytes.NewBuffer(rec.RawSample), nativeEndian, &event); err != nil {
logrus.Debugf("failed to parse ringbuf event: %+v", err)
continue
}
fmt.Fprintf(writer, "%x mark=%x netns=%010d if=%d(%s) proc=%d(%s) ", event.Skb, event.Mark, event.Netns, event.Ifindex, TrimNull(string(event.Ifname[:])), event.Pid, TrimNull(string(event.Pname[:])))
if event.L3Proto == syscall.ETH_P_IP {
fmt.Fprintf(writer, "%s:%d > %s:%d ", net.IP(event.Saddr[:4]).String(), Ntohs(event.Sport), net.IP(event.Daddr[:4]).String(), Ntohs(event.Dport))
} else {
fmt.Fprintf(writer, "[%s]:%d > [%s]:%d ", net.IP(event.Saddr[:]).String(), Ntohs(event.Sport), net.IP(event.Daddr[:]).String(), Ntohs(event.Dport))
}
if event.L4Proto == syscall.IPPROTO_TCP {
fmt.Fprintf(writer, "tcp_flags=%s ", TcpFlags(event.TcpFlags))
}
fmt.Fprintf(writer, "payload_len=%d ", event.PayloadLen)
sym := NearestSymbol(event.Pc)
fmt.Fprintf(writer, "%s", sym.Name)
if sym.Name == "kfree_skb_reason" {
fmt.Fprintf(writer, "(%s)", kfreeSkbReasons[event.SecondParam])
}
fmt.Fprintf(writer, "\n")
}
}

50
trace/utils.go Normal file
View File

@ -0,0 +1,50 @@
/*
* SPDX-License-Identifier: AGPL-3.0-only
* Copyright (c) 2022-2024, daeuniverse Organization <dae@v2raya.org>
*/
package trace
import (
"encoding/binary"
"strings"
)
func Htons(x uint16) uint16 {
data := make([]byte, 2)
nativeEndian.PutUint16(data, x)
return binary.BigEndian.Uint16(data)
}
func Ntohs(x uint16) uint16 {
data := make([]byte, 2)
binary.BigEndian.PutUint16(data, x)
return nativeEndian.Uint16(data)
}
func TrimNull(s string) string {
return strings.TrimRight(s, "\x00")
}
func TcpFlags(data uint8) string {
flags := []string{}
if data&0b00100000 != 0 {
flags = append(flags, "U")
}
if data&0b00010000 != 0 {
flags = append(flags, ".")
}
if data&0b00001000 != 0 {
flags = append(flags, "P")
}
if data&0b00000100 != 0 {
flags = append(flags, "R")
}
if data&0b00000010 != 0 {
flags = append(flags, "S")
}
if data&0b00000001 != 0 {
flags = append(flags, "F")
}
return strings.Join(flags, "")
}