From 50108d0a2045b6648e11a623bcd47c1fb57fc60f Mon Sep 17 00:00:00 2001 From: Shuyi Cheng Date: Thu, 10 Feb 2022 15:21:40 +0800 Subject: [PATCH 1/9] rtrace: inital add rtrace tools Add rtrace lib and tools. Signed-off-by: Shuyi Cheng --- .../tools/detect/net_diag/rtrace/.gitignore | 3 + source/tools/detect/net_diag/rtrace/Makefile | 33 +- source/tools/detect/net_diag/rtrace/README.md | 19 + .../detect/net_diag/rtrace/ebpf/Makefile | 68 ++ .../detect/net_diag/rtrace/ebpf/README.md | 5 + .../detect/net_diag/rtrace/ebpf/common.bpf.h | 181 +++++ .../detect/net_diag/rtrace/ebpf/common.def.h | 201 +++++ .../detect/net_diag/rtrace/ebpf/common.usr.h | 51 ++ .../detect/net_diag/rtrace/ebpf/rtrace.bpf.c | 749 ++++++++++++++++++ .../detect/net_diag/rtrace/ebpf/rtrace.c | 464 +++++++++++ .../detect/net_diag/rtrace/ebpf/rtrace.h | 48 ++ .../detect/net_diag/rtrace/ebpf/utils/btf.c | 220 +++++ .../detect/net_diag/rtrace/ebpf/utils/btf.h | 17 + .../net_diag/rtrace/ebpf/utils/disasm.c | 311 ++++++++ .../net_diag/rtrace/ebpf/utils/disasm.h | 41 + .../detect/net_diag/rtrace/ebpf/utils/insn.c | 94 +++ .../detect/net_diag/rtrace/ebpf/utils/insn.h | 7 + .../net_diag/rtrace/ebpf/utils/object.c | 16 + .../net_diag/rtrace/ebpf/utils/object.h | 6 + .../net_diag/rtrace/rtrace-delay/README.md | 17 + .../net_diag/rtrace/rtrace-drop/README.md | 3 + .../net_diag/rtrace/rtrace-parser/Cargo.toml | 15 + .../net_diag/rtrace/rtrace-parser/README.md | 3 + .../net_diag/rtrace/rtrace-parser/src/func.rs | 224 ++++++ .../rtrace/rtrace-parser/src/ksyms.rs | 116 +++ .../net_diag/rtrace/rtrace-parser/src/lib.rs | 8 + .../net_diag/rtrace/rtrace-parser/src/net.rs | 53 ++ .../net_diag/rtrace/rtrace-parser/src/perf.rs | 280 +++++++ .../net_diag/rtrace/rtrace-parser/src/skb.rs | 270 +++++++ .../net_diag/rtrace/rtrace-parser/src/sock.rs | 140 ++++ .../rtrace/rtrace-parser/src/utils.rs | 92 +++ .../net_diag/rtrace/rtrace-rs/Cargo.toml | 19 + .../net_diag/rtrace/rtrace-rs/README.md | 3 + .../detect/net_diag/rtrace/rtrace-rs/build.rs | 12 + .../net_diag/rtrace/rtrace-rs/src/bindings.rs | 261 ++++++ .../rtrace/rtrace-rs/src/builtin/builtin.rs | 56 ++ .../rtrace/rtrace-rs/src/builtin/mod.rs | 1 + .../rtrace/rtrace-rs/src/dynamic/dynamic.rs | 73 ++ .../rtrace/rtrace-rs/src/dynamic/mod.rs | 4 + .../rtrace/rtrace-rs/src/dynamic/offset.rs | 67 ++ .../rtrace/rtrace-rs/src/dynamic/parser.rs | 223 ++++++ .../rtrace/rtrace-rs/src/filter/filter.rs | 66 ++ .../rtrace/rtrace-rs/src/filter/mod.rs | 1 + .../net_diag/rtrace/rtrace-rs/src/lib.rs | 24 + .../net_diag/rtrace/rtrace-rs/src/rtrace.rs | 416 ++++++++++ .../rtrace/rtrace-rs/src/trace/mod.rs | 2 + .../rtrace/rtrace-rs/src/trace/prog.rs | 236 ++++++ .../rtrace/rtrace-rs/src/trace/trace.rs | 118 +++ .../rtrace/rtrace-rs/src/utils/gdb.rs | 87 ++ .../rtrace/rtrace-rs/src/utils/mod.rs | 1 + 50 files changed, 5423 insertions(+), 2 deletions(-) create mode 100644 source/tools/detect/net_diag/rtrace/.gitignore create mode 100644 source/tools/detect/net_diag/rtrace/README.md create mode 100644 source/tools/detect/net_diag/rtrace/ebpf/Makefile create mode 100644 source/tools/detect/net_diag/rtrace/ebpf/README.md create mode 100644 source/tools/detect/net_diag/rtrace/ebpf/common.bpf.h create mode 100644 source/tools/detect/net_diag/rtrace/ebpf/common.def.h create mode 100644 source/tools/detect/net_diag/rtrace/ebpf/common.usr.h create mode 100644 source/tools/detect/net_diag/rtrace/ebpf/rtrace.bpf.c create mode 100644 source/tools/detect/net_diag/rtrace/ebpf/rtrace.c create mode 100644 source/tools/detect/net_diag/rtrace/ebpf/rtrace.h create mode 100644 source/tools/detect/net_diag/rtrace/ebpf/utils/btf.c create mode 100644 source/tools/detect/net_diag/rtrace/ebpf/utils/btf.h create mode 100644 source/tools/detect/net_diag/rtrace/ebpf/utils/disasm.c create mode 100644 source/tools/detect/net_diag/rtrace/ebpf/utils/disasm.h create mode 100644 source/tools/detect/net_diag/rtrace/ebpf/utils/insn.c create mode 100644 source/tools/detect/net_diag/rtrace/ebpf/utils/insn.h create mode 100644 source/tools/detect/net_diag/rtrace/ebpf/utils/object.c create mode 100644 source/tools/detect/net_diag/rtrace/ebpf/utils/object.h create mode 100644 source/tools/detect/net_diag/rtrace/rtrace-delay/README.md create mode 100644 source/tools/detect/net_diag/rtrace/rtrace-drop/README.md create mode 100644 source/tools/detect/net_diag/rtrace/rtrace-parser/Cargo.toml create mode 100644 source/tools/detect/net_diag/rtrace/rtrace-parser/README.md create mode 100644 source/tools/detect/net_diag/rtrace/rtrace-parser/src/func.rs create mode 100644 source/tools/detect/net_diag/rtrace/rtrace-parser/src/ksyms.rs create mode 100644 source/tools/detect/net_diag/rtrace/rtrace-parser/src/lib.rs create mode 100644 source/tools/detect/net_diag/rtrace/rtrace-parser/src/net.rs create mode 100644 source/tools/detect/net_diag/rtrace/rtrace-parser/src/perf.rs create mode 100644 source/tools/detect/net_diag/rtrace/rtrace-parser/src/skb.rs create mode 100644 source/tools/detect/net_diag/rtrace/rtrace-parser/src/sock.rs create mode 100644 source/tools/detect/net_diag/rtrace/rtrace-parser/src/utils.rs create mode 100644 source/tools/detect/net_diag/rtrace/rtrace-rs/Cargo.toml create mode 100644 source/tools/detect/net_diag/rtrace/rtrace-rs/README.md create mode 100644 source/tools/detect/net_diag/rtrace/rtrace-rs/build.rs create mode 100644 source/tools/detect/net_diag/rtrace/rtrace-rs/src/bindings.rs create mode 100644 source/tools/detect/net_diag/rtrace/rtrace-rs/src/builtin/builtin.rs create mode 100644 source/tools/detect/net_diag/rtrace/rtrace-rs/src/builtin/mod.rs create mode 100644 source/tools/detect/net_diag/rtrace/rtrace-rs/src/dynamic/dynamic.rs create mode 100644 source/tools/detect/net_diag/rtrace/rtrace-rs/src/dynamic/mod.rs create mode 100644 source/tools/detect/net_diag/rtrace/rtrace-rs/src/dynamic/offset.rs create mode 100644 source/tools/detect/net_diag/rtrace/rtrace-rs/src/dynamic/parser.rs create mode 100644 source/tools/detect/net_diag/rtrace/rtrace-rs/src/filter/filter.rs create mode 100644 source/tools/detect/net_diag/rtrace/rtrace-rs/src/filter/mod.rs create mode 100644 source/tools/detect/net_diag/rtrace/rtrace-rs/src/lib.rs create mode 100644 source/tools/detect/net_diag/rtrace/rtrace-rs/src/rtrace.rs create mode 100644 source/tools/detect/net_diag/rtrace/rtrace-rs/src/trace/mod.rs create mode 100755 source/tools/detect/net_diag/rtrace/rtrace-rs/src/trace/prog.rs create mode 100644 source/tools/detect/net_diag/rtrace/rtrace-rs/src/trace/trace.rs create mode 100644 source/tools/detect/net_diag/rtrace/rtrace-rs/src/utils/gdb.rs create mode 100644 source/tools/detect/net_diag/rtrace/rtrace-rs/src/utils/mod.rs diff --git a/source/tools/detect/net_diag/rtrace/.gitignore b/source/tools/detect/net_diag/rtrace/.gitignore new file mode 100644 index 00000000..110d0105 --- /dev/null +++ b/source/tools/detect/net_diag/rtrace/.gitignore @@ -0,0 +1,3 @@ +target +Cargo.lock +.vscode \ No newline at end of file diff --git a/source/tools/detect/net_diag/rtrace/Makefile b/source/tools/detect/net_diag/rtrace/Makefile index 47f0faa0..db0a869d 100644 --- a/source/tools/detect/net_diag/rtrace/Makefile +++ b/source/tools/detect/net_diag/rtrace/Makefile @@ -1,5 +1,34 @@ -target := rtrace +ifndef KERNEL_VERSION +KERNEL_VERSION = $(shell uname -r) +SRC := /work/gitee/sysak/source +OBJPATH = /work/gitee/sysak/out +OBJ_LIB_PATH := $(OBJPATH)/.sysak_compoents/lib/$(KERNEL_VERSION) +OBJ_TOOLS_ROOT := $(OBJPATH)/.sysak_compoents/tools +OBJ_TOOLS_PATH := $(OBJPATH)/.sysak_compoents/tools/$(KERNEL_VERSION) +SYSAK_RULES := .sysak.rules + +export SRC +export OBJPATH +export OBJ_LIB_PATH +export OBJ_TOOLS_ROOT +export OBJ_TOOLS_PATH +endif + +TARGET_PATH := $(OBJ_TOOLS_ROOT) + +.PHONY: rtrace +rtrace: lib bin -include $(SRC)/mk/bin.mk +lib: + make -C ebpf +bin: + cd rtrace-delay && cargo build --release + cd rtrace-drop && cargo build --release + + @echo "rtrace-delay" >> $(TARGET_PATH)/$(SYSAK_RULES) + @echo "rtrace-drop" >> $(TARGET_PATH)/$(SYSAK_RULES) + @echo "rtrace-basic" >> $(TARGET_PATH)/$(SYSAK_RULES) + +target := rtrace diff --git a/source/tools/detect/net_diag/rtrace/README.md b/source/tools/detect/net_diag/rtrace/README.md new file mode 100644 index 00000000..30d07cdb --- /dev/null +++ b/source/tools/detect/net_diag/rtrace/README.md @@ -0,0 +1,19 @@ + + +# rtrace + +rtrace is an eBPF-based tool focused on network diagnostics. It contains three libraries and two diagnostic tools. + +Three libraries: + +* ebpf +* rtrace-rs +* rtrace-parser + +Two diagnostic tools: + +* rtrace-delay +* rtrace-drop + + + diff --git a/source/tools/detect/net_diag/rtrace/ebpf/Makefile b/source/tools/detect/net_diag/rtrace/ebpf/Makefile new file mode 100644 index 00000000..ccfc7a15 --- /dev/null +++ b/source/tools/detect/net_diag/rtrace/ebpf/Makefile @@ -0,0 +1,68 @@ +CLANG ?= clang +LLVM_STRIP ?= llvm-strip +BPFTOOL ?= $(SRC)/lib/internal/ebpf/tools/bpftool +ARCH := $(shell uname -m | sed 's/x86_64/x86/') +LIBBPF_OBJ := $(OBJ_LIB_PATH)/libbpf.a + + +CFLAGS = -g -O2 -Wall -fPIC + +INCLUDES = -I$(OBJPATH) -I$(SRC)/lib/internal/ebpf -I$(SRC)/lib/internal/ebpf/libbpf/include -I$(SRC)/lib/internal/ebpf/libbpf/include/uapi -I$(OBJ_LIB_PATH) -I. + + +newdirs := $(shell find ./ -type d) +bpfsrcs := rtrace.bpf.c +csrcs := rtrace.c utils/btf.c utils/disasm.c utils/insn.c utils/object.c + +newdirs := $(addprefix $(OBJPATH)/, $(newdirs)) +cobjs := $(patsubst %.c, %.o, $(csrcs)) +target_cobjs := $(foreach n, $(cobjs), $(OBJPATH)/$(n)) + +bpfobjs := $(patsubst %.c, %.o, $(bpfsrcs)) +target_bpfobjs := $(foreach n, $(bpfobjs), $(OBJPATH)/$(n)) + +bpfskel := $(patsubst %.bpf.o, %.skel.h, $(target_bpfobjs)) + +ifeq ($(V),1) + Q = + msg = +else + Q = @ + msg = @printf ' %-8s %s%s\n' \ + "$(1)" \ + "$(patsubst $(abspath $(OUTPUT))/%,%,$(2))" \ + "$(if $(3), $(3))"; + MAKEFLAGS += --no-print-directory +endif + + +librtrace: $(OBJ_LIB_PATH)/librtrace.so + +$(OBJ_LIB_PATH)/librtrace.a: $(target_cobjs) + $(Q) ar -rcs $@ $^ + +$(target_cobjs): $(cobjs) + +$(cobjs): %.o : %.c $(bpfskel) + $(call msg,CC,$@) + $(Q)$(CC) $(CFLAGS) $(INCLUDES) -c $< -o $(OBJPATH)/$@ + +$(bpfskel): %.skel.h : %.bpf.o $(target_bpfobjs) + $(call msg,GEN-SKEL,$@) + $(Q)$(BPFTOOL) gen skeleton $< > $@ + +$(target_bpfobjs): $(bpfobjs) + +$(bpfobjs) : %.o : %.c dirs + $(call msg,BPF,$@) + $(Q)$(CLANG) -g -O2 -target bpf -D__TARGET_ARCH_$(ARCH) $(INCLUDES) -c $< -o $(OBJPATH)/$@ + $(Q)$(LLVM_STRIP) -g $(OBJPATH)/$@ # strip useless DWARF info + +dirs: + mkdir -p $(newdirs) + +# delete failed targets +.DELETE_ON_ERROR: + +# keep intermediate (.skel.h, .bpf.o, etc) targets +# .SECONDARY: diff --git a/source/tools/detect/net_diag/rtrace/ebpf/README.md b/source/tools/detect/net_diag/rtrace/ebpf/README.md new file mode 100644 index 00000000..13dca20b --- /dev/null +++ b/source/tools/detect/net_diag/rtrace/ebpf/README.md @@ -0,0 +1,5 @@ + +## rtrace eBPF program + +The eBPF program included in the rtrace tool, the eBPF program is convenient for third-party programs to carry out secondary development in the form of a library. + diff --git a/source/tools/detect/net_diag/rtrace/ebpf/common.bpf.h b/source/tools/detect/net_diag/rtrace/ebpf/common.bpf.h new file mode 100644 index 00000000..dc60c1aa --- /dev/null +++ b/source/tools/detect/net_diag/rtrace/ebpf/common.bpf.h @@ -0,0 +1,181 @@ +#ifndef _RTRACE_COMMON_BPF_H +#define _RTRACE_COMMON_BPF_H + +#include "common.def.h" + +struct +{ + __uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY); + __type(key, int); + __type(value, int); +} perf SEC(".maps"); + +struct +{ + __uint(type, BPF_MAP_TYPE_PROG_ARRAY); + __uint(max_entries, 8); + __type(key, uint32_t); + __type(value, uint32_t); +} jmp_table SEC(".maps"); + +struct +{ + __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY); + __uint(max_entries, 1); + __type(key, uint32_t); + __type(value, struct buffer); +} buffer_map SEC(".maps"); + +static __always_inline void set_pid_info(struct pid_info *pi) +{ + uint64_t pid_tgid = bpf_get_current_pid_tgid(); + uint32_t pid = pid_tgid >> 32; + + pi->pid = pid; + bpf_get_current_comm(pi->comm, TASK_COMM_LEN); +} + +union kernfs_node_id___419 +{ + struct + { + u32 ino; + u32 generation; + }; + u64 id; +}; + +struct kernfs_node___419 +{ + struct kernfs_node___419 *parent; + union kernfs_node_id___419 id; +}; + +static __always_inline void read_cgroup_id(uint64_t *target_id) +{ + struct task_struct *curr_task = (struct task_struct *)bpf_get_current_task(); + struct kernfs_node *kn; + BPF_CORE_READ_INTO(&kn, curr_task, cgroups, subsys[0], cgroup, kn); + + if (!kn) + return; + struct kernfs_node___419 *kn_old = (void *)kn; + if (bpf_core_field_exists(kn_old->id)) + BPF_CORE_READ_INTO(target_id, kn_old, id.id); + else + BPF_CORE_READ_INTO(target_id, kn, id); +} + +// for centos 3.10 kernel +struct net___310 +{ + unsigned int proc_inum; +}; + +struct net_device___310 +{ + struct net___310 *nd_net; + int ifindex; +}; + +static __always_inline void read_ns_inum(struct sk_buff *skb, u32 *inum) +{ + struct net *net; + if (bpf_core_field_exists(net->ns.inum)) + { + struct net_device *dev; + bpf_core_read(&dev, sizeof(dev), &skb->dev); + bpf_core_read(&net, sizeof(net), &dev->nd_net.net); + bpf_core_read(inum, sizeof(*inum), &net->ns.inum); + } + else + { + struct net___310 *net310; + struct net_device___310 *dev310; + bpf_core_read(&dev310, sizeof(dev310), &skb->dev); + bpf_core_read(&net310, sizeof(net310), &dev310->nd_net); + bpf_core_read(inum, sizeof(*inum), &net310->proc_inum); + } +} + +static __always_inline void read_ns_inum_by_sk(struct sock *sk, u32 *inum) +{ + struct net *net; + if (bpf_core_field_exists(net->ns.inum)) + BPF_CORE_READ_INTO(inum, sk, __sk_common.skc_net.net, ns.inum); + else + { + struct net___310 *net310; + BPF_CORE_READ_INTO(&net310, sk, __sk_common.skc_net.net); + BPF_CORE_READ_INTO(inum, net310, proc_inum); + } +} + +// 不依赖于sock或sk_buff,获取入参 +static __always_inline void send_context(void *ctx, void *perf, uint8_t send) +{ + ENUM_TO_STRUCT(CONTEXT) + ct = {0}; + ct.types = SET_MAJOR_TYPE(ct.types, MEMORY); + ct.args[0] = (uint64_t)PT_REGS_PARM1((struct pt_regs *)ctx); + ct.args[1] = (uint64_t)PT_REGS_PARM2((struct pt_regs *)ctx); + ct.args[2] = (uint64_t)PT_REGS_PARM3((struct pt_regs *)ctx); + ct.args[3] = (uint64_t)PT_REGS_PARM4((struct pt_regs *)ctx); + ct.args[4] = (uint64_t)PT_REGS_PARM5((struct pt_regs *)ctx); + bpf_perf_event_output(ctx, perf, BPF_F_CURRENT_CPU, &ct, sizeof(ENUM_TO_STRUCT(CONTEXT))); +} + +static __always_inline void send_tcp_wind(void *ctx, void *perf, struct sock *sk, uint8_t send) +{ + ENUM_TO_STRUCT(TCP_WINDOW) + tw = {0}; + struct tcp_sock *ts = (struct tcp_sock *)sk; + tw.types = SET_MAJOR_TYPE(tw.types, TCP_WINDOW); + + BPF_CORE_READ_INTO(&tw.rcv_nxt, ts, rcv_nxt); + BPF_CORE_READ_INTO(&tw.rcv_wup, ts, rcv_wup); + BPF_CORE_READ_INTO(&tw.snd_nxt, ts, snd_nxt); + BPF_CORE_READ_INTO(&tw.snd_una, ts, snd_una); + BPF_CORE_READ_INTO(&tw.copied_seq, ts, copied_seq); + BPF_CORE_READ_INTO(&tw.snd_wnd, ts, snd_wnd); + BPF_CORE_READ_INTO(&tw.rcv_wnd, ts, rcv_wnd); + + BPF_CORE_READ_INTO(&tw.lost_out, ts, lost_out); + BPF_CORE_READ_INTO(&tw.packets_out, ts, packets_out); + BPF_CORE_READ_INTO(&tw.retrans_out, ts, retrans_out); + BPF_CORE_READ_INTO(&tw.sacked_out, ts, sacked_out); + bpf_perf_event_output(ctx, perf, BPF_F_CURRENT_CPU, &tw, sizeof(ENUM_TO_STRUCT(TCP_WINDOW))); +} + +static __always_inline void send_memory(void *ctx, void *perf, struct sock *sk, uint8_t send) +{ + ENUM_TO_STRUCT(MEMORY) + mm = {0}; + atomic_long_t *map; + mm.types = SET_MAJOR_TYPE(mm.types, MEMORY); + BPF_CORE_READ_INTO(&map, sk, __sk_common.skc_prot, memory_allocated); + bpf_probe_read(&mm.allocated, sizeof(atomic_long_t), map); + BPF_CORE_READ_INTO(&mm.rmem_alloc, sk, sk_backlog.rmem_alloc.counter); + BPF_CORE_READ_INTO(&mm.wmem_alloc, sk, sk_wmem_alloc); + BPF_CORE_READ_INTO(&mm.forward_alloc, sk, sk_forward_alloc); + BPF_CORE_READ_INTO(&mm.rcvbuf, sk, sk_rcvbuf); + BPF_CORE_READ_INTO(&mm.sndbuf, sk, sk_sndbuf); + bpf_perf_event_output(ctx, perf, BPF_F_CURRENT_CPU, &mm, sizeof(ENUM_TO_STRUCT(MEMORY))); +} + +// Declare a map with key type uint64 according to enum type. +#define DECLARE_HASH_MAP(enum_type, entries) \ + struct \ + { \ + __uint(type, BPF_MAP_TYPE_HASH); \ + __uint(max_entries, entries); \ + __type(key, u64); \ + __type(value, ENUM_TO_STRUCT(enum_type)); \ + } ENUM_TO_MAP_NAME(enum_type) SEC(".maps"); + +DECLARE_HASH_MAP(BASIC_INFO, MAX_ENTRIES) + +#define UPDATE_HASH_MAP(enum_type, key, value) \ + bpf_map_update_elem(ENUM_TO_REF_MAP(enum_type), key, value, BPF_ANY); + +#endif diff --git a/source/tools/detect/net_diag/rtrace/ebpf/common.def.h b/source/tools/detect/net_diag/rtrace/ebpf/common.def.h new file mode 100644 index 00000000..a339df6c --- /dev/null +++ b/source/tools/detect/net_diag/rtrace/ebpf/common.def.h @@ -0,0 +1,201 @@ +#ifndef _RTRACE_COMMON_BPF_H_ +#define _RTRACE_COMMON_BPF_H_ + +#define TASK_COMM_LEN 16 +#define FUNCNAME_MAX_LEN 32 +#define MAC_HEADER_SIZE 14 +#define FILTER_RULES_MAX_NUM 10 + +#define USR_DEUBG + +#if defined(__VMLINUX_H__) && defined(BPF_DEBUG) +#define output(...) __bpf_printk(__VA_ARGS__); +#elif !defined(__VMLINUX_H__) && defined(USR_DEUBG) +#define output(...) printf(__VA_ARGS__); +#else +#define output(...) +#endif + +#define pr_err(fmt, ...) \ + do \ + { \ + output("ERROR: " fmt, ##__VA_ARGS__); \ + } while (0) + +#define pr_dbg(fmt, ...) \ + do \ + { \ + output("DEBUG: " fmt, ##__VA_ARGS__); \ + } while (0) + +#define TO_STR(a) #a +#define TO_STRING(a) TO_STR(a) + +#define FILTER_MAP_DEFAULT_KEY 0 +#define FLOW_MAP_DEFAULT_VAL ((__u64)(0x0123456776543210)) +typedef uint64_t rtrace_mask_t; +#define MAX_NUM_BYTES (0xff + 1) +#define MAX_STACK 5 +#define MAX_ENTRIES 10240 +#define RTRACE_LSHIFT(nbits) ((rtrace_mask_t)1 << (nbits)) +#define RTRACE_MASK(nbits) (RTRACE_LSHIFT(nbits) - 1) +#define RTRACE_ALIGN(n) n +#define TEST_NBITS_SET(num, nbits) ((num)&RTRACE_LSHIFT(nbits)) +// #define ADDREF(a) &##a ERROR +#define ADDREF(a) &a +#define ENUM_TO_MAP_NAME(enum_type) enum_type##_map +#define ENUM_TO_REF_MAP(enum_type) ADDREF(ENUM_TO_MAP_NAME(enum_type)) +#define ENUM_TO_STRUCT(enum_type) \ + struct enum_type##_struct + +#define ENUM_TO_FUNC_NAME(prefix, enum_type) prefix_##enum_type + +#define TYPE_TO_ENUM(type) (((type) << 16) >> 16) +#define TYPE_TO_CPU(type) ((type) >> 16) + +#define TYPE_SET_CPU(type, cpu) ((type) + ((cpu) << 16)) + +#define IPHDR_VALID(iphdr) ((iphdr)->saddr != 0) +#define TCPHDR_VALID(cd) ((cd)->transport_header != (u16)~0) + +// 0 - 6 bit 6 - 12 bit 31 - 32 bit +#define SET_MAJOR_TYPE(num, val) (((num) & (~(0x3f))) | ((val) & (0x3f))) +#define SET_MINOR_TYPE(num, val) (((num) & (~(0x3f << 6))) | (((val) & (0x3f)) << 6)) +#define SET_SEND_RECV(num, val) ((num) & (~(1u << 31)) | (((val)&0x1)) << 31) +#define GET_MAJOR_TYPE(num) ((num) & (0x3f)) +#define GET_MINOR_TYPE(num) (((num) >> 6) & (0x3f)) +#define GET_SEND_RECV(num) (((num) >> 31) & 0x1) + +#ifndef __CONCAT //for bpf program. +#define __CONCAT(a, b) a##b +#endif +#define CONCATENATE(a, b) __CONCAT(a, b) + +#define PLACEHOLDER_NUM1 0x888 +#define PLACEHOLDER_NUM(no) CONCATENATE(PLACEHOLDER_NUM, no) +#define INSTERT_PLACEHOLDER(type, no) \ + type placeholder_##no; \ + asm volatile("%0 = %1" \ + : "=r"(placeholder_##no) \ + : "i"(PLACEHOLDER_NUM(no))); + +#define LOOKUP_PLACEHOLDER(no) placeholder_##no +#define CONTAINER_ID_LEN 128 + +#define KPROBE_NAME(func) kprobe__##func +#define ZERO_OR_EQUAL(a, b) ((a) == 0 || (a) == (b)) + +enum +{ + BASIC_INFO = 0, + CGROUP, + STACK, + KRETPROBE, // Get the return parameter of the function + LINEPROBE, + ENUM_END, +}; + +#define MAX_BUFFER_SIZE 512 +#define BUFFER_START_OFFSET 8 +struct buffer +{ + uint64_t offset; + uint8_t buffer[MAX_BUFFER_SIZE]; +}; + +struct cache_data +{ + void *ctx; + struct sock *sk; + struct sk_buff *skb; + struct buffer *buffer; + char *head; + // char *data; +#if defined(__VMLINUX_H__) + struct iphdr ih; + struct tcphdr th; + struct tcp_skb_cb tsc; +#else + int ih[5]; + int th[5]; + int tsc[12]; +#endif + uint16_t transport_header; + uint16_t network_header; + uint8_t send; + uint32_t sk_protocol; +}; + +struct addr_pair +{ + uint32_t saddr; + uint32_t daddr; + uint16_t sport; + uint16_t dport; +}; + +struct pid_info +{ + uint32_t pid; + char comm[TASK_COMM_LEN]; +}; + +// The addition of s is to avoid duplication with stack_info of vmlinux.h. +ENUM_TO_STRUCT(STACK) +{ + uint64_t kern_stack[MAX_STACK]; +}; + +struct filter_meta { + int pid; + struct addr_pair ap; +}; + +struct filter_map_key +{ + struct filter_meta fm[FILTER_RULES_MAX_NUM]; + uint32_t protocol; + int cnt; +}; + +struct tid_map_key +{ + uint32_t tid; + uint32_t bp; +}; + +#define CONSTRUCT_BPF_PROGRAM_NAME(sk_pos, skb_pos) \ + kprobe_sk_##sk_pos##skb_##skb_pos + +ENUM_TO_STRUCT(BASIC_INFO) +{ + uint64_t mask; + uint64_t ip; + uint64_t ts; + uint32_t seq; + uint32_t end_seq; + uint32_t rseq; + uint32_t rend_seq; + struct addr_pair ap; + struct pid_info pi; + uint64_t ret; +}; + +ENUM_TO_STRUCT(CGROUP) +{ + uint32_t inum; + uint64_t cgroupid; +}; + +#define DECLARE_AND_INIT_STRUCT(enum_type, name) \ + ENUM_TO_STRUCT(enum_type) \ + name = {0} + +#define DECLARE_STRUCT_PTR(enum_type, name) \ + ENUM_TO_STRUCT(enum_type) * name + +#define DECLARE_STRUCT(enum_type, name) \ + ENUM_TO_STRUCT(enum_type) \ + name + +#endif diff --git a/source/tools/detect/net_diag/rtrace/ebpf/common.usr.h b/source/tools/detect/net_diag/rtrace/ebpf/common.usr.h new file mode 100644 index 00000000..58e1503b --- /dev/null +++ b/source/tools/detect/net_diag/rtrace/ebpf/common.usr.h @@ -0,0 +1,51 @@ +#ifndef _RTRACE_COMMON_USR_H +#define _RTRACE_COMMON_USR_H +#include +#include +#include +#include "common.def.h" + +extern bool gdebug; + +#ifndef zfree +#define zfree(ptr) ( \ + { \ + free(*ptr); \ + *ptr = NULL; \ + }) +#endif + +#ifndef zclose +#define zclose(fd) ( \ + { \ + int ___err = 0; \ + if ((fd) >= 0) \ + ___err = close((fd)); \ + fd = -1; \ + ___err; \ + }) +#endif + +#define DEBUG_LINE printf("debug: %s:%d:1 fun:%s\n", __FILE__, __LINE__, __FUNCTION__); +#define ERROR_LINE printf("error: %s:%d:1 fun:%s\n", __FILE__, __LINE__, __FUNCTION__); + +static char special_funcs[][50] = { + "tcp_sendmsg", + "tcp_cleanup_rbuf", + "kretprobe_common", + "kprobe_lines", + "raw_sendmsg", +}; + +static inline bool is_special_func(char *func) +{ + int i; + int len = sizeof(special_funcs) / sizeof(special_funcs[0]); + + for (i = 0; i < len; i++) + if (strcmp(func, special_funcs[i]) == 0) + return true; + return false; +} + +#endif diff --git a/source/tools/detect/net_diag/rtrace/ebpf/rtrace.bpf.c b/source/tools/detect/net_diag/rtrace/ebpf/rtrace.bpf.c new file mode 100644 index 00000000..a716fa6a --- /dev/null +++ b/source/tools/detect/net_diag/rtrace/ebpf/rtrace.bpf.c @@ -0,0 +1,749 @@ +#define BPF_NO_GLOBAL_DATA +#include "vmlinux.h" +#include +#include +#include +#include +#include "common.bpf.h" + +#ifndef NULL +#define NULL ((void *)0) +#endif + +// from linux/icmp.h +#define ICMP_ECHOREPLY 0 /* Echo Reply */ +#define ICMP_DEST_UNREACH 3 /* Destination Unreachable */ +#define ICMP_SOURCE_QUENCH 4 /* Source Quench */ +#define ICMP_REDIRECT 5 /* Redirect (change route) */ +#define ICMP_ECHO 8 /* Echo Request */ +#define ICMP_TIME_EXCEEDED 11 /* Time Exceeded */ +#define ICMP_PARAMETERPROB 12 /* Parameter Problem */ +#define ICMP_TIMESTAMP 13 /* Timestamp Request */ +#define ICMP_TIMESTAMPREPLY 14 /* Timestamp Reply */ +#define ICMP_INFO_REQUEST 15 /* Information Request */ +#define ICMP_INFO_REPLY 16 /* Information Reply */ +#define ICMP_ADDRESS 17 /* Address Mask Request */ +#define ICMP_ADDRESSREPLY 18 /* Address Mask Reply */ +#define NR_ICMP_TYPES 18 + +/* Codes for UNREACH. */ +#define ICMP_NET_UNREACH 0 /* Network Unreachable */ +#define ICMP_HOST_UNREACH 1 /* Host Unreachable */ +#define ICMP_PROT_UNREACH 2 /* Protocol Unreachable */ +#define ICMP_PORT_UNREACH 3 /* Port Unreachable */ +#define ICMP_FRAG_NEEDED 4 /* Fragmentation Needed/DF set */ +#define ICMP_SR_FAILED 5 /* Source Route failed */ +#define ICMP_NET_UNKNOWN 6 +#define ICMP_HOST_UNKNOWN 7 +#define ICMP_HOST_ISOLATED 8 +#define ICMP_NET_ANO 9 +#define ICMP_HOST_ANO 10 +#define ICMP_NET_UNR_TOS 11 +#define ICMP_HOST_UNR_TOS 12 +#define ICMP_PKT_FILTERED 13 /* Packet filtered */ +#define ICMP_PREC_VIOLATION 14 /* Precedence violation */ +#define ICMP_PREC_CUTOFF 15 /* Precedence cut off */ +#define NR_ICMP_UNREACH 15 /* instead of hardcoding immediate value */ + +/* Codes for REDIRECT. */ +#define ICMP_REDIR_NET 0 /* Redirect Net */ +#define ICMP_REDIR_HOST 1 /* Redirect Host */ +#define ICMP_REDIR_NETTOS 2 /* Redirect Net for TOS */ +#define ICMP_REDIR_HOSTTOS 3 /* Redirect Host for TOS */ + +/* Codes for TIME_EXCEEDED. */ +#define ICMP_EXC_TTL 0 /* TTL count exceeded */ +#define ICMP_EXC_FRAGTIME 1 /* Fragment Reass time exceeded */ + +struct +{ + __uint(type, BPF_MAP_TYPE_ARRAY); + __uint(max_entries, 10); + __type(key, u32); + __type(value, struct filter_map_key); +} filter_map SEC(".maps"); + +struct +{ + __uint(type, BPF_MAP_TYPE_LRU_HASH); + __uint(max_entries, 10240); + __type(key, struct addr_pair); + __type(value, struct sock *); +} flow_map SEC(".maps"); + +struct +{ + __uint(type, BPF_MAP_TYPE_LRU_HASH); + __uint(max_entries, 512); + __type(key, struct tid_map_key); + __type(value, ENUM_TO_STRUCT(BASIC_INFO)); +} tid_map SEC(".maps"); + +/** + * @brief save data into buffer + * + * @param cd + * @param ptr + * @param size + * @return __always_inline + */ +static __always_inline void buffer_input(struct cache_data *cd, void *ptr, uint32_t size) +{ + if (size == 0) + return; + + if (cd->buffer->offset < MAX_BUFFER_SIZE - size) + { + bpf_probe_read(&(cd->buffer->buffer[cd->buffer->offset]), size, ptr); + cd->buffer->offset += size; + } +} + +/** + * @brief output buffer to userspace + * + * @param cd + * @return __always_inline + */ +static __always_inline void buffer_output(struct cache_data *cd) +{ + int size = cd->buffer->offset & (MAX_BUFFER_SIZE - 1); + bpf_perf_event_output(cd->ctx, &perf, BPF_F_CURRENT_CPU, cd->buffer->buffer, size); + cd->buffer->offset = 0; +} + +/** + * @brief Compares two addresses for equality + * + * @param skb_ap + * @param sk_ap + * @return int + */ +static int addr_pair_cmp(struct addr_pair *skb_ap, struct addr_pair *sk_ap) +{ + if (sk_ap->dport == skb_ap->dport && sk_ap->sport == skb_ap->sport) + return 0; + + return -1; +} + +/** + * @brief Set the seq object + * + * @param cd + * @param seq + * @param end_seq + * @param rseq + * @param rend_seq + * @return __always_inline + */ +static __always_inline void set_seq(struct cache_data *cd, uint32_t *seq, uint32_t *end_seq, uint32_t *rseq, uint32_t *rend_seq) +{ + char *data; + uint32_t len, tmp_seq, tmp_end_seq, tmp_rseq, tmp_rend_seq; + struct tcp_skb_cb *tsc; + uint32_t protocol = cd->sk_protocol & 0xff; + // uint16_t offset; + // handle icmp + if (protocol == IPPROTO_ICMP) + { + struct icmphdr *ih = ((struct icmphdr *)(&cd->th)); + uint16_t sequence; + uint8_t type = ih->type; + sequence = ih->un.echo.sequence; + if (cd->send) // send path + { + if (type == ICMP_ECHO) + { + // sender + *seq = sequence; + *end_seq = sequence + 1; + } + else if (type == ICMP_ECHOREPLY) + { + // receiver + *rseq = sequence; + *rend_seq = sequence + 1; + } + } + else // receive path + { + if (type == ICMP_ECHOREPLY) + { + // sender + *seq = sequence; + *end_seq = sequence + 1; + } + else if (type == ICMP_ECHO) + { + // receiver + *rseq = sequence; + *rend_seq = sequence + 1; + } + } + return; + } + + if (cd->transport_header != (uint16_t)~0) + { + struct sk_buff *skb = cd->skb; + BPF_CORE_READ_INTO(&data, skb, data); + BPF_CORE_READ_INTO(&len, skb, len); + + if (cd->send) + { + *seq = bpf_ntohl(cd->th.seq); + *end_seq = *seq + len - cd->transport_header + (data - cd->head) - cd->th.doff * 4; + *rend_seq = bpf_ntohl(cd->th.ack_seq); + } + else + { + *rseq = bpf_ntohl(cd->th.seq); + *rend_seq = *rseq + len - cd->transport_header + (data - cd->head) - cd->th.doff * 4; + struct tcp_sock *ts = (struct tcp_sock *)cd->sk; + BPF_CORE_READ_INTO(seq, ts, snd_una); + *end_seq = bpf_ntohl(cd->th.ack_seq); + } + } + else + { + tsc = (struct tcp_skb_cb *)((unsigned long)cd->skb + offsetof(struct sk_buff, cb[0])); +#define TCPHDR_ACK 0x10 + if (cd->send) + { + uint8_t tcp_flags; + BPF_CORE_READ_INTO(&tcp_flags, tsc, tcp_flags); + BPF_CORE_READ_INTO(seq, tsc, seq); + BPF_CORE_READ_INTO(end_seq, tsc, end_seq); + if (tcp_flags & TCPHDR_ACK) + { + BPF_CORE_READ_INTO(rend_seq, tsc, ack_seq); + } + } + else + { + uint8_t tcp_flags; + BPF_CORE_READ_INTO(&tcp_flags, tsc, tcp_flags); + BPF_CORE_READ_INTO(rseq, tsc, seq); + BPF_CORE_READ_INTO(rend_seq, tsc, end_seq); + if (tcp_flags & TCPHDR_ACK) + { + struct tcp_sock *ts = (struct tcp_sock *)cd->sk; + BPF_CORE_READ_INTO(seq, ts, snd_una); + BPF_CORE_READ_INTO(end_seq, tsc, ack_seq); + } + } + } +} + +/** + * @brief Set the seq by tsc object. Some functions may not have tcp headers, + * such as __tcp_transmit_skb, so seq needs to be obtained from tcp_skb_cb. + * + * @param skb + * @param seq + * @param end_seq + */ +static void set_seq_by_tsc(struct sk_buff *skb, uint32_t *seq, uint32_t *end_seq) +{ + struct tcp_skb_cb *tsc; + tsc = (struct tcp_skb_cb *)((unsigned long)skb + offsetof(struct sk_buff, cb[0])); + BPF_CORE_READ_INTO(seq, tsc, seq); + BPF_CORE_READ_INTO(end_seq, tsc, end_seq); +} + +/** + * @brief Set the addr pair by hdr object + * + * @param cd cache_data structure pointer + * @param ap addr_pair structure pointer + */ +static void set_addr_pair_by_hdr(struct cache_data *cd, struct addr_pair *ap) +{ + ap->saddr = cd->ih.saddr; + ap->daddr = cd->ih.daddr; + + switch (cd->sk_protocol) + { + case IPPROTO_ICMP: + ap->sport = 0; + ap->dport = 0; + break; + case IPPROTO_TCP: + ap->sport = bpf_ntohs(cd->th.source); + ap->dport = bpf_ntohs(cd->th.dest); + break; + default: + break; + } +} + +/** + * @brief Set the addr pair by sock object + * + * @param sk sock object pointer + * @param ap addr_pair object pointer + */ +static __always_inline void set_addr_pair_by_sock(struct sock *sk, struct addr_pair *ap) +{ + BPF_CORE_READ_INTO(&ap->daddr, sk, __sk_common.skc_daddr); + BPF_CORE_READ_INTO(&ap->dport, sk, __sk_common.skc_dport); + ap->dport = bpf_ntohs(ap->dport); + BPF_CORE_READ_INTO(&ap->saddr, sk, __sk_common.skc_rcv_saddr); + BPF_CORE_READ_INTO(&ap->sport, sk, __sk_common.skc_num); +} + +/** + * @brief Bind sock and addr_pair, and update the sk pointer + * + * @param skp sock's secondary pointer + * @param ap + * @return int + */ +static int set_sock(struct sock **skp, struct addr_pair *ap) +{ + struct sock **skp_tmp; + skp_tmp = bpf_map_lookup_elem(&flow_map, ap); + + // not found. + if (!skp_tmp) + { + if (*skp) + { + bpf_map_update_elem(&flow_map, ap, skp, BPF_ANY); + return 0; + } + return -1; + } + + if (!*skp) + { + if (*skp_tmp == (struct sock *)FLOW_MAP_DEFAULT_VAL) + return -1; + // assign sock pointer. + *skp = *skp_tmp; + } + else if (*skp != *skp_tmp) + bpf_map_update_elem(&flow_map, ap, skp, BPF_ANY); + return 0; +} + +/** + * @brief Set the cache data object + * + * @param cd cache_data structure pointer + * @param skb sk_buff structure pointer + * @return void + */ +static __always_inline void set_cache_data(struct cache_data *cd, struct sk_buff *skb) +{ + char *head, *l3_header_addr, *l4_header_addr = NULL; + u16 mac_header, network_header, transport_header, size; + uint32_t protocol; + + BPF_CORE_READ_INTO(&transport_header, skb, transport_header); + BPF_CORE_READ_INTO(&head, skb, head); + cd->transport_header = transport_header; + + BPF_CORE_READ_INTO(&network_header, skb, network_header); + cd->network_header = network_header; + if (network_header == 0) + { + BPF_CORE_READ_INTO(&mac_header, skb, mac_header); + network_header = mac_header + MAC_HEADER_SIZE; + } + l3_header_addr = head + network_header; + bpf_probe_read(&cd->ih, sizeof(struct iphdr), l3_header_addr); + if (transport_header == (u16)~0) + l4_header_addr = l3_header_addr + cd->ih.ihl * 4; + else + l4_header_addr = head + transport_header; + + if (!l4_header_addr) + return; + + protocol = cd->sk_protocol == 0 ? cd->ih.protocol : cd->sk_protocol; + cd->head = head; + switch (protocol) + { + case IPPROTO_ICMP: + size = sizeof(struct icmphdr); + break; + case IPPROTO_TCP: + size = sizeof(struct tcphdr); + break; + default: + size = 0; + break; + } + if (size) + bpf_probe_read(&cd->th, size, l4_header_addr); + cd->sk_protocol = protocol; + // BPF_CORE_READ_INTO(&head, cd, skb); // to generate cache_data btf info. +} + +/** + * @brief Filter out unwanted packets + * + * @param protocol + * @param pid + * @param ap + * @return __always_inline + */ +static __always_inline int builtin_filter(uint32_t protocol, int pid, struct addr_pair *ap) +{ + u32 key = FILTER_MAP_DEFAULT_KEY; + struct filter_map_key *fmkp; + struct filter_meta *fm; + struct addr_pair *app; + int i, cnt; + + fmkp = bpf_map_lookup_elem(&filter_map, &key); + if (!fmkp) + return -1; + + // compare major protocol + if ((fmkp->protocol & 0xff) != (protocol & 0xff)) + return -1; + + // compare minor protocol + i = (fmkp->protocol >> 8) & 0xff; + if (i && i != ((protocol >> 8) & 0xff)) + return -1; + +#pragma unroll + for (i = 0; i < FILTER_RULES_MAX_NUM; i++) + { + fm = &fmkp->fm[i]; + app = &fm->ap; + + if (ZERO_OR_EQUAL(fm->pid, pid) && + ZERO_OR_EQUAL(app->daddr, ap->daddr) && + ZERO_OR_EQUAL(app->dport, ap->dport) && + ZERO_OR_EQUAL(app->saddr, ap->saddr) && + ZERO_OR_EQUAL(app->sport, ap->sport)) + break; + } + + if (i && i >= fmkp->cnt) + return -1; + return 0; +} + +/** + * @brief Main processing function entry + * + * @param ctx + * @param sk + * @param skb + * @return __always_inline + */ +static __always_inline int do_trace_sk_skb(void *ctx, struct sock *sk, struct sk_buff *skb) +{ + INSTERT_PLACEHOLDER(rtrace_mask_t, 1); + struct addr_pair skb_ap = {0}; + struct addr_pair sk_ap = {0}; + struct cache_data cd = {0}; + uint64_t pid_tgid = bpf_get_current_pid_tgid(); + uint32_t pid = pid_tgid >> 32; + uint32_t tid = pid_tgid; + uint32_t default_buffer_map_key = 0; + + if (!sk) + BPF_CORE_READ_INTO(&sk, skb, sk); + + cd.sk_protocol = 0; + if (sk) + cd.sk_protocol = BPF_CORE_READ_BITFIELD_PROBED(sk, sk_protocol); + + set_cache_data(&cd, skb); + switch (cd.sk_protocol) + { + case IPPROTO_TCP: + set_addr_pair_by_hdr(&cd, &skb_ap); + if (sk) + { + // 1. May be the sending path + // 2. May be the upper layer of the protocol stack + set_addr_pair_by_sock(sk, &sk_ap); + set_sock(&sk, &sk_ap); + // todo: Consider the impact of nat + if (addr_pair_cmp(&skb_ap, &sk_ap) == 0) + cd.send = 1; + } + else + { + // may be the receive path + sk_ap.daddr = skb_ap.saddr; + sk_ap.dport = skb_ap.sport; + sk_ap.saddr = skb_ap.daddr; + sk_ap.sport = skb_ap.dport; + set_sock(&sk, &sk_ap); + } + if (cd.th.syn) + cd.sk_protocol |= (1 << 8); + break; + case IPPROTO_ICMP: + sk_ap.sport = ((struct icmphdr *)&cd.th)->un.echo.id; + sk_ap.dport = sk_ap.sport; + set_sock(&sk, &sk_ap); + break; + default: + return 0; + } + + if (!sk) + return -1; + if (builtin_filter(cd.sk_protocol, pid, &sk_ap) < 0) + return -1; + + // Here, we have captured the message we want. + cd.skb = skb; + cd.sk = sk; + cd.ctx = ctx; + cd.buffer = bpf_map_lookup_elem(&buffer_map, &default_buffer_map_key); + + if (!cd.buffer) + return -1; + + if (TEST_NBITS_SET(LOOKUP_PLACEHOLDER(1), BASIC_INFO)) + { + DECLARE_AND_INIT_STRUCT(BASIC_INFO, bi); + set_seq(&cd, &bi.seq, &bi.end_seq, &bi.rseq, &bi.rend_seq); + bi.mask = LOOKUP_PLACEHOLDER(1); + bi.mask &= (~(1ull << KRETPROBE)); + bi.mask &= (~(1ull << LINEPROBE)); + bi.ip = PT_REGS_IP((struct pt_regs *)cd.ctx); + bi.ts = bpf_ktime_get_ns(); + bi.ap = sk_ap; + bi.pi.pid = pid; + bpf_get_current_comm(bi.pi.comm, TASK_COMM_LEN); + buffer_input(&cd, &bi, sizeof(bi)); + + if (TEST_NBITS_SET(LOOKUP_PLACEHOLDER(1), KRETPROBE) || TEST_NBITS_SET(LOOKUP_PLACEHOLDER(1), LINEPROBE)) + { + struct tid_map_key tmk = {0}; + tmk.tid = tid; + // todo: lineprobe and multi-level Kretprobe nesting + // tmk.bp = ((struct pt_regs *)cd.ctx)->bp; + tmk.bp = 0; + bi.mask = LOOKUP_PLACEHOLDER(1); + bpf_map_update_elem(&tid_map, &tmk, &bi, BPF_ANY); + } + } + + if (TEST_NBITS_SET(LOOKUP_PLACEHOLDER(1), CGROUP)) + { + DECLARE_AND_INIT_STRUCT(CGROUP, cg); + read_ns_inum_by_sk(sk, &cg.inum); + read_cgroup_id(&cg.cgroupid); + buffer_input(&cd, &cg, sizeof(cg)); + } + + if (TEST_NBITS_SET(LOOKUP_PLACEHOLDER(1), STACK)) + { + int size = sizeof(ENUM_TO_STRUCT(STACK)); + if (cd.buffer->offset < (MAX_BUFFER_SIZE - size)) + { + bpf_get_stack(ctx, &(cd.buffer->buffer[cd.buffer->offset]), size, BPF_ANY); + cd.buffer->offset += size; + } + } + + void *ctxp; + asm volatile("%0 = %1" + : "=r"(ctxp) + : "r"(&cd)); + buffer_output(&cd); + return 0; +} + +SEC("kretprobe/common") +int kretprobe_common(struct pt_regs *ctx) +{ + struct tid_map_key tmk = {0}; + uint64_t mask; + tmk.tid = (uint32_t)bpf_get_current_pid_tgid(); + tmk.bp = 0; + ENUM_TO_STRUCT(BASIC_INFO) *bi = bpf_map_lookup_elem(&tid_map, &tmk); + if (!bi) + return 0; + mask = bi->mask; + if (TEST_NBITS_SET(mask, KRETPROBE)) + { + bi->mask = 1ull << KRETPROBE; + // bi->ip = ctx->ip; // cannot cover ip. rip now pointer to kretprobe_trampoline. + bi->ts = bpf_ktime_get_ns(); + bi->ret = PT_REGS_RC(ctx); + bpf_perf_event_output(ctx, &perf, BPF_F_CURRENT_CPU, bi, sizeof(ENUM_TO_STRUCT(BASIC_INFO))); + } + bpf_map_delete_elem(&tid_map, &tmk); + return 0; +} + +SEC("kprobe/lines") +int kprobe_lines(struct pt_regs *ctx) +{ + struct tid_map_key tmk = {0}; + uint64_t bp; + // todo: find upper function rbp + // bpf_probe_read_kernel(&bp, sizeof(bp), (void *)(ctx->bp)) + tmk.tid = (uint32_t)bpf_get_current_pid_tgid(); + tmk.bp = 0; + ENUM_TO_STRUCT(BASIC_INFO) *bi = bpf_map_lookup_elem(&tid_map, &tmk); + if (!bi) + return 0; + + bi->mask = 1ull << LINEPROBE; + bi->ip = ctx->ip; + bi->ts = bpf_ktime_get_ns(); + bpf_perf_event_output(ctx, &perf, BPF_F_CURRENT_CPU, bi, sizeof(ENUM_TO_STRUCT(BASIC_INFO))); + return 0; +} + +SEC("kprobe/tcp_cleanup_rbuf") +int BPF_KPROBE(tcp_cleanup_rbuf, struct sock *sk, int copied) +{ + ENUM_TO_STRUCT(BASIC_INFO) + bi = {0}; + INSTERT_PLACEHOLDER(rtrace_mask_t, 1); + struct tcp_sock *ts = (struct tcp_sock *)sk; + uint32_t copied_seq; + uint64_t pid_tgid = bpf_get_current_pid_tgid(); + uint32_t pid = pid_tgid >> 32; + uint32_t tid = pid_tgid; + + set_addr_pair_by_sock(sk, &bi.ap); + set_sock(&sk, &bi.ap); + + if (builtin_filter(IPPROTO_TCP, pid, &bi.ap) < 0) + return -1; + + bi.mask = 1 << BASIC_INFO; + bi.ip = ctx->ip; + bi.ts = bpf_ktime_get_ns(); + bi.seq = 0; + bi.end_seq = 0; + BPF_CORE_READ_INTO(&copied_seq, ts, copied_seq); + bi.rseq = copied_seq - copied; + bi.rend_seq = copied_seq; + bi.pi.pid = pid; + bpf_get_current_comm(bi.pi.comm, TASK_COMM_LEN); + if (TEST_NBITS_SET(LOOKUP_PLACEHOLDER(1), KRETPROBE)) + { + struct tid_map_key tmk = {0}; + tmk.tid = tid; + tmk.bp = ctx->bp; + bpf_map_update_elem(&tid_map, &tmk, &bi, BPF_ANY); + } + bpf_perf_event_output(ctx, &perf, BPF_F_CURRENT_CPU, &bi, sizeof(ENUM_TO_STRUCT(BASIC_INFO))); + return 0; +} + +SEC("kprobe/tcp_sendmsg") +int BPF_KPROBE(tcp_sendmsg, struct sock *sk, struct msghdr *msg, size_t size) +{ + ENUM_TO_STRUCT(BASIC_INFO) + bi = {0}; + INSTERT_PLACEHOLDER(rtrace_mask_t, 1); + struct tcp_sock *ts = (struct tcp_sock *)sk; + uint64_t pid_tgid = bpf_get_current_pid_tgid(); + uint32_t pid = pid_tgid >> 32; + uint32_t tid = pid_tgid; + + set_addr_pair_by_sock(sk, &bi.ap); + set_sock(&sk, &bi.ap); + if (builtin_filter(IPPROTO_TCP, pid, &bi.ap) < 0) + return -1; + + bi.mask = 1 << BASIC_INFO; + bi.ip = ctx->ip; + bi.ts = bpf_ktime_get_ns(); + BPF_CORE_READ_INTO(&bi.seq, ts, write_seq); + bi.end_seq = bi.seq + size; + bi.pi.pid = pid; + bpf_get_current_comm(bi.pi.comm, TASK_COMM_LEN); + if (TEST_NBITS_SET(LOOKUP_PLACEHOLDER(1), KRETPROBE)) + { + struct tid_map_key tmk = {0}; + tmk.tid = tid; + tmk.bp = ctx->bp; + bpf_map_update_elem(&tid_map, &tmk, &bi, BPF_ANY); + } + bpf_perf_event_output(ctx, &perf, BPF_F_CURRENT_CPU, &bi, sizeof(ENUM_TO_STRUCT(BASIC_INFO))); + return 0; +} + +SEC("kprobe/raw_sendmsg") +int BPF_KPROBE(raw_sendmsg, struct sock *sk, struct msghdr *msg, size_t len) +{ + ENUM_TO_STRUCT(BASIC_INFO) + bi = {0}; + INSTERT_PLACEHOLDER(rtrace_mask_t, 1); + uint64_t pid_tgid = bpf_get_current_pid_tgid(); + uint32_t pid = pid_tgid >> 32; + uint32_t tid = pid_tgid; + struct icmphdr ih; + uint32_t protocol; + char *ptr; + + protocol = BPF_CORE_READ_BITFIELD_PROBED(sk, sk_protocol); + if (protocol != IPPROTO_ICMP) + return 0; + + BPF_CORE_READ_INTO(&ptr, msg, msg_iter.iov, iov_base); + bpf_probe_read(&ih, sizeof(ih), ptr); + bi.ap.sport = ih.un.echo.id; + bi.ap.dport = bi.ap.sport; + set_sock(&sk, &bi.ap); + if (builtin_filter(IPPROTO_ICMP, pid, &bi.ap) < 0) + return -1; + + bi.mask = 1 << BASIC_INFO; + bi.ip = ctx->ip; + bi.ts = bpf_ktime_get_ns(); + bi.seq = ih.un.echo.sequence; + bi.end_seq = bi.seq + 1; + bi.rseq = bi.seq; + bi.rend_seq = bi.end_seq; + bpf_get_current_comm(bi.pi.comm, TASK_COMM_LEN); + if (TEST_NBITS_SET(LOOKUP_PLACEHOLDER(1), KRETPROBE)) + { + struct tid_map_key tmk = {0}; + tmk.tid = tid; + tmk.bp = ctx->bp; + bpf_map_update_elem(&tid_map, &tmk, &bi, BPF_ANY); + } + bpf_perf_event_output(ctx, &perf, BPF_F_CURRENT_CPU, &bi, sizeof(ENUM_TO_STRUCT(BASIC_INFO))); + return 0; +} + +#define SK0_SKB_ARG_FN(pos) \ + SEC("kprobe/sk0_skb" #pos) \ + int kprobe_sk0_skb##pos(struct pt_regs *pt) \ + { \ + struct sk_buff *skb = (struct sk_buff *)PT_REGS_PARM##pos(pt); \ + do_trace_sk_skb(pt, NULL, skb); \ + return 0; \ + } + +#define SK_SKB_ARG_FN(skpos, skbpos) \ + SEC("kprobe/sk" #skpos "_skb" #skbpos) \ + int kprobe_sk##skpos##_skb##skbpos(struct pt_regs *pt) \ + { \ + struct sock *sk = (struct sock *)PT_REGS_PARM##skpos(pt); \ + struct sk_buff *skb = (struct sk_buff *)PT_REGS_PARM##skbpos(pt); \ + do_trace_sk_skb(pt, sk, skb); \ + return 0; \ + } + +SK0_SKB_ARG_FN(1) +SK0_SKB_ARG_FN(2) +SK0_SKB_ARG_FN(3) +SK0_SKB_ARG_FN(4) +SK0_SKB_ARG_FN(5) + +SK_SKB_ARG_FN(1, 2) +SK_SKB_ARG_FN(2, 3) + +char LICENSE[] SEC("license") = "GPL"; \ No newline at end of file diff --git a/source/tools/detect/net_diag/rtrace/ebpf/rtrace.c b/source/tools/detect/net_diag/rtrace/ebpf/rtrace.c new file mode 100644 index 00000000..8b0afa6b --- /dev/null +++ b/source/tools/detect/net_diag/rtrace/ebpf/rtrace.c @@ -0,0 +1,464 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.usr.h" +#include "utils/btf.h" +#include + +#include "rtrace.h" +#include "rtrace.skel.h" + +#define RTRACE_DYNAMIC_CTX_REG BPF_REG_6 +#define JMP_ERR_CODE 4096 + +#define BPF_ALU64_REG(OP, DST, SRC) \ + ((struct bpf_insn){ \ + .code = BPF_ALU64 | BPF_OP(OP) | BPF_X, \ + .dst_reg = DST, \ + .src_reg = SRC, \ + .off = 0, \ + .imm = 0}) + +struct rtrace +{ + struct rtrace_bpf *obj; + char *pin_path; + char *btf_custom_path; + struct btf *btf; +}; + +bool gdebug = false; + +static int libbpf_print_fn(enum libbpf_print_level level, + const char *format, va_list args) +{ + if (gdebug) + return vfprintf(stderr, format, args); + return 0; +} + +int bump_memlock_rlimit(void) +{ + struct rlimit rlim_new = { + .rlim_cur = RLIM_INFINITY, + .rlim_max = RLIM_INFINITY, + }; + + return setrlimit(RLIMIT_MEMLOCK, &rlim_new); +} + + +/** + * @brief enable debug or not + * + * @param debug debug output or not + */ +void rtrace_set_debug(bool debug) +{ + gdebug = debug; +} + +/** + * @brief Get the fd of the perf map + * + * @param r rtrace context + * @return int map fd of type BPF_MAP_TYPE_PERF_EVENT_ARRAY + */ +int rtrace_perf_map_fd(struct rtrace *r) +{ + return bpf_map__fd(r->obj->maps.perf); +} +/** + * @brief Get the fd of the filter map + * + * @param r rtrace context + * @return int fd of filter map + */ +int rtrace_filter_map_fd(struct rtrace *r) +{ + return bpf_map__fd(r->obj->maps.filter_map); +} + +static int rtrace_init(struct rtrace *r, char *btf_custom_path, char *pin_path) +{ + int err; + DECLARE_LIBBPF_OPTS(bpf_object_open_opts, open_opts); + + libbpf_set_print(libbpf_print_fn); + bump_memlock_rlimit(); + + r->btf_custom_path = btf_custom_path; + r->pin_path = pin_path; + open_opts.btf_custom_path = btf_custom_path; + r->obj = rtrace_bpf__open_opts(&open_opts); + if (!r->obj) + { + pr_err("failed to open BPF object\n"); + err = -EINVAL; + goto err_out; + } + + err = rtrace_bpf__load(r->obj); + if (err) + { + pr_err("failed to load bpf, err: %s\n", strerror(-err)); + goto err_out; + } + + if (pin_path) + { + err = bpf_object__pin_maps(r->obj->obj, pin_path); + if (err) + { + pr_err("failed to pin maps\n"); + goto err_out; + } + } + + r->btf = btf_load(btf_custom_path); + if (!r->btf) + { + pr_err("Failed to load vmlinux BTF: %d, err msg: %s\n", -errno, strerror(errno)); + err = -errno; + goto err_out; + } + + return 0; + +err_out: + + return err; +} + +/** + * @brief + * + * @param btf_custom_path + * @param pin_path + * @return struct rtrace* + */ +struct rtrace *rtrace_alloc_and_init(char *btf_custom_path, char *pin_path) +{ + struct rtrace *r; + int err; + + r = malloc(sizeof(struct rtrace)); + if (!r) + { + errno = ENOMEM; + return NULL; + } + + err = rtrace_init(r, btf_custom_path, pin_path); + if (err) + { + errno = -err; + goto err_out; + } + return r; + +err_out: + // rtrace_free(r); + return NULL; +} + +/** + * @brief Find the corresponding ebpf program according to the function name and the sk, + * skb parameter positions + * + * @param r rtrace context + * @param func function name, eg. __ip_queue_xmit + * @param sk optional, the parameter position of the sk parameter in the function prototype + * @param skb optional, the parameter position of the skb parameter in the function prototype + * @return struct bpf_program* ebpf program + */ +struct bpf_program *rtrace_trace_program(struct rtrace *r, char *func, int sk, int skb) +{ + struct bpf_program *prog; + int func_proto_id; + if (is_special_func(func)) + { + prog = bpf_object__find_program_by_name(r->obj->obj, func); + } + else + { + if (sk == 0 && skb == 0) + { + func_proto_id = btf_find_func_proto_id(r->btf, func); + sk = btf_func_proto_find_param_pos(r->btf, func_proto_id, "sock", NULL); + sk = sk < 0 ? 0 : sk; + skb = btf_func_proto_find_param_pos(r->btf, func_proto_id, "sk_buff", NULL); + if (skb < 0) + { + pr_err("func-%s prog is null, sk = %d, skb = %d.\n", func, sk, skb); + return NULL; + } + } + prog = object_find_program(r->obj->obj, sk, skb); + } + // if (gdebug) + // insns_dump(bpf_program__insns(prog), bpf_program__insn_cnt(prog)); + return prog; +} + +/** + * @brief Load the incoming ebpf instruction, after verification by the kernel, + * return the corresponding file descriptor + * + * @param r rtrace context + * @param prog bpf program to laod + * @param insns the ebpf instruction that really needs to be loaded + * @param insns_cnt instruction count + * @return int fd + */ +int rtrace_trace_load_prog(struct rtrace *r, struct bpf_program *prog, + struct bpf_insn *insns, size_t insns_cnt) +{ + struct bpf_load_program_attr attr; + static const int log_buf_size = 1024 * 1024; + char log_buf[log_buf_size]; + int fd; + + if (gdebug) + insns_dump(insns, insns_cnt); + + memset(&attr, 0, sizeof(attr)); + attr.prog_type = bpf_program__get_type(prog); + attr.expected_attach_type = bpf_program__get_expected_attach_type(prog); + attr.name = bpf_program__name((const struct bpf_program *)prog); + attr.insns = insns; + attr.insns_cnt = insns_cnt; + attr.license = "Dual BSD/GPL"; + attr.kern_version = bpf_object__kversion(r->obj->obj); + attr.prog_ifindex = 0; + + if (gdebug) + fd = bpf_load_program_xattr(&attr, log_buf, log_buf_size); + else + fd = bpf_load_program_xattr(&attr, NULL, 0); + if (fd < 0) + { + printf("%s\n", log_buf); + return fd; + } + bpf_program__set_fd(prog, fd); + return fd; +} + +struct dynamic_parse +{ + struct + { + int offset; // in bits + int size; + int elem_size; + bool is_ptr; + } attr[10]; + int cnt; + + int offsets[10]; + int offset_cnt; + int size; + int arg_pos; +}; + +#define OFFSET_REGS_PARM1 offsetof(struct pt_regs, rdi) +#define OFFSET_REGS_PARM2 offsetof(struct pt_regs, rsi) +#define OFFSET_REGS_PARM3 offsetof(struct pt_regs, rdx) +#define OFFSET_REGS_PARM4 offsetof(struct pt_regs, rcx) +#define OFFSET_REGS_PARM5 offsetof(struct pt_regs, r8) + +static int dynamic_ptregs_param_offset(int param_pos) +{ + if (param_pos >= 5 || param_pos <= 0) + return -EINVAL; + + switch (param_pos) + { + case 1: + return OFFSET_REGS_PARM1; + case 2: + return OFFSET_REGS_PARM2; + case 3: + return OFFSET_REGS_PARM3; + case 4: + return OFFSET_REGS_PARM4; + case 5: + return OFFSET_REGS_PARM5; + default: + return -EINVAL; + } + return -EINVAL; +} + +/** + * @brief Calculate the corresponding offset according to the accessed structure member + * + * @param r rtrace context + * @param df array of members accessed by the structure + * @param df_cnt array length + * @param func_proto_id btf id + * @param dos offsets for struct members + * @return int 0 is ok + */ +int rtrace_dynamic_gen_offset(struct rtrace *r, struct dynamic_fields *df, + int df_cnt, int func_proto_id, struct dynamic_offsets *dos) +{ + struct dynamic_parse dp = {0}; + const struct btf_member *mem; + int i, err, offset, pre_typeid, root_typeid, cnt, off_sum; + + if (!r || !r->btf || df_cnt <= 0) + return -EINVAL; + + root_typeid = btf_func_proto_find_param(r->btf, func_proto_id, NULL, df[0].ident); + if (root_typeid < 0) + { + pr_dbg("failed to find param: %s in function", df[0].ident); + err = root_typeid; + goto err_out; + } + + err = btf_func_proto_find_param_pos(r->btf, func_proto_id, NULL, df[0].ident); + if (err <= 0) + goto err_out; + + cnt = 0; + + dp.attr[cnt].offset = err; + if (df[0].cast_type > 0) + { + root_typeid = btf__find_by_name_kind(r->btf, df[0].cast_name, df[0].cast_type); + if (root_typeid < 0) + { + err = root_typeid; + goto err_out; + } + if (df[0].pointer == 1) + dp.attr[cnt].is_ptr = true; + else + dp.attr[cnt].is_ptr = false; + } + else + dp.attr[cnt].is_ptr = btf_typeid_has_ptr(r->btf, root_typeid); + + cnt++; + pre_typeid = root_typeid; + for (i = 1; i < df_cnt; i++) + { + offset = 0; + mem = btf_find_member(r->btf, pre_typeid, df[i].ident, &offset); + if (mem == NULL) + { + err = -errno; + goto err_out; + } + dp.attr[cnt].offset = offset; + if (df[i].cast_type > 0) + { + pre_typeid = btf__find_by_name_kind(r->btf, df[i].cast_name, df[i].cast_type); + if (pre_typeid < 0) + { + err = pre_typeid; + goto err_out; + } + if (df[i].pointer == 1) + dp.attr[cnt].is_ptr = true; + else + dp.attr[cnt].is_ptr = false; + } + else + { + dp.attr[cnt].is_ptr = btf_typeid_has_ptr(r->btf, mem->type); + pre_typeid = mem->type; + } + + cnt++; + } + + dp.cnt = cnt; + + off_sum = 0; + cnt = 0; + for (i = 1; i < dp.cnt - 1; i++) + { + off_sum += dp.attr[i].offset; + if (dp.attr[i].is_ptr) + { + dp.offsets[cnt++] = off_sum / 8; + off_sum = 0; + } + } + dos->offs[cnt++] = (dp.attr[dp.cnt - 1].offset + off_sum) / 8; + dos->cnt = cnt; + dos->size = btf__resolve_size(r->btf, pre_typeid); + dos->arg = dp.attr[0].offset; + + pr_dbg("offset array:\n"); + for (i = 0; i < dos->cnt; i++) + pr_dbg("%d %s", dos->offs[i], i == dos->cnt - 1 ? "\n" : ""); + + return 0; + +err_out: + return err; +} + +/** + * @brief + * + * @param r rtrace context + * @param dos offsets for struct members + * @param insns pointer to save instructions + * @param cd_off struct cache_data offset in stack + * @return int instruction count + */ +int rtrace_dynamic_gen_insns(struct rtrace *r, struct dynamic_offsets *dos, struct bpf_insn *insns, int cd_off) +{ + int i, insns_cnt, ctx_off, regs_off, buff_off; + + insns_cnt = 0; + ctx_off = cd_off + offsetof(struct cache_data, ctx); + buff_off = cd_off + offsetof(struct cache_data, buffer); + insns[insns_cnt++] = BPF_LDX_MEM(BPF_DW, RTRACE_DYNAMIC_CTX_REG, BPF_REG_10, ctx_off); + + regs_off = dynamic_ptregs_param_offset(dos->arg); + insns[insns_cnt++] = BPF_LDX_MEM(BPF_DW, BPF_REG_3, RTRACE_DYNAMIC_CTX_REG, regs_off); + for (i = 0; i < dos->cnt - 1; i++) + { + insns[insns_cnt++] = BPF_ALU64_IMM(BPF_ADD, BPF_REG_3, dos->offs[i]); + insns[insns_cnt++] = BPF_MOV64_REG(BPF_REG_1, BPF_REG_10); + insns[insns_cnt++] = BPF_ALU64_IMM(BPF_ADD, BPF_REG_1, -8); + insns[insns_cnt++] = BPF_MOV64_IMM(BPF_REG_2, 8); + insns[insns_cnt++] = BPF_EMIT_CALL(BPF_FUNC_probe_read); + insns[insns_cnt++] = BPF_LDX_MEM(BPF_DW, BPF_REG_3, BPF_REG_10, -8); + } + insns[insns_cnt++] = BPF_ALU64_IMM(BPF_ADD, BPF_REG_3, dos->offs[i]); + insns[insns_cnt++] = BPF_LDX_MEM(BPF_DW, BPF_REG_1, BPF_REG_10, buff_off); + insns[insns_cnt++] = BPF_LDX_MEM(BPF_DW, BPF_REG_2, BPF_REG_1, 0); + insns[insns_cnt++] = BPF_JMP_IMM(BPF_JGT, BPF_REG_2, MAX_BUFFER_SIZE - dos->size, JMP_ERR_CODE); + insns[insns_cnt++] = BPF_ALU64_REG(BPF_ADD, BPF_REG_1, BPF_REG_2); + insns[insns_cnt++] = BPF_ALU64_IMM(BPF_ADD, BPF_REG_1, 8); + insns[insns_cnt++] = BPF_MOV64_IMM(BPF_REG_2, dos->size); + insns[insns_cnt++] = BPF_EMIT_CALL(BPF_FUNC_probe_read); + insns[insns_cnt++] = BPF_LDX_MEM(BPF_DW, BPF_REG_1, BPF_REG_10, buff_off); + insns[insns_cnt++] = BPF_LDX_MEM(BPF_DW, BPF_REG_2, BPF_REG_1, 0); + insns[insns_cnt++] = BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, dos->size); + insns[insns_cnt++] = BPF_STX_MEM(BPF_DW, BPF_REG_1, BPF_REG_2, 0); + + pr_dbg("generate new insns, insns cnt: %d\n", insns_cnt); + if (gdebug) + insns_dump(insns, insns_cnt); + return insns_cnt; +} + +struct btf *rtrace_dynamic_btf(struct rtrace *r) +{ + return r->btf; +} \ No newline at end of file diff --git a/source/tools/detect/net_diag/rtrace/ebpf/rtrace.h b/source/tools/detect/net_diag/rtrace/ebpf/rtrace.h new file mode 100644 index 00000000..a7286ed7 --- /dev/null +++ b/source/tools/detect/net_diag/rtrace/ebpf/rtrace.h @@ -0,0 +1,48 @@ +#ifndef __RTRACE_RTRACE_H +#define __RTRACE_RTRACE_H + +#define MAX_PROBE_NUM 1024 + +#include "common.usr.h" +#include "utils/btf.h" +#include "utils/disasm.h" +#include "utils/insn.h" +#include "utils/object.h" + +struct dynamic_offsets +{ + int offs[10]; + int cnt; + int arg; + int size; +}; + +struct dynamic_fields +{ + char *ident; + char* cast_name; + int cast_type; + int index; + int pointer; +}; + +struct rtrace; + +struct rtrace *rtrace_alloc_and_init(char *pin_path, char *btf_custom_path); +int rtrace_perf_map_fd(struct rtrace *r); +int rtrace_filter_map_fd(struct rtrace *r); +void rtrace_set_debug(bool debug); + + +// dynamic module. +int rtrace_dynamic_gen_offset(struct rtrace *r, struct dynamic_fields *df, + int df_cnt, int func_proto_id, struct dynamic_offsets *dos); +int rtrace_dynamic_gen_insns(struct rtrace *r, struct dynamic_offsets *dos, struct bpf_insn *insns, int cd_off); +struct btf *rtrace_dynamic_btf(struct rtrace *r); + +// trace module. +int rtrace_trace_load_prog(struct rtrace *r, struct bpf_program *prog, + struct bpf_insn *insns, size_t insns_cnt); +struct bpf_program *rtrace_trace_program(struct rtrace *r, char *func, int sk, int skb); + +#endif \ No newline at end of file diff --git a/source/tools/detect/net_diag/rtrace/ebpf/utils/btf.c b/source/tools/detect/net_diag/rtrace/ebpf/utils/btf.c new file mode 100644 index 00000000..ab8cbc5a --- /dev/null +++ b/source/tools/detect/net_diag/rtrace/ebpf/utils/btf.c @@ -0,0 +1,220 @@ +#include +#include +#include +#include + +#include "common.usr.h" +#include "utils/btf.h" + +static bool btf_type_is_modifier(const struct btf_type *t) +{ + /* Some of them is not strictly a C modifier + * but they are grouped into the same bucket + * for BTF concern: + * A type (t) that refers to another + * type through t->type AND its size cannot + * be determined without following the t->type. + * + * ptr does not fall into this bucket + * because its size is always sizeof(void *). + */ + switch (BTF_INFO_KIND(t->info)) + { + case BTF_KIND_TYPEDEF: + case BTF_KIND_VOLATILE: + case BTF_KIND_CONST: + case BTF_KIND_RESTRICT: + // case BTF_KIND_TYPE_TAG: + return true; + } + + return false; +} + +const struct btf_type *btf_type_skip_modifiers(const struct btf *btf, + uint32_t id, uint32_t *res_id) +{ + const struct btf_type *t = btf__type_by_id(btf, id); + + while (btf_type_is_modifier(t)) + { + id = t->type; + t = btf__type_by_id(btf, t->type); + } + + if (res_id) + *res_id = id; + + return t; +} + +const struct btf_type *btf_type_skip_ptr(const struct btf *btf, uint32_t id) +{ + const struct btf_type *t = btf__type_by_id(btf, id); + while (btf_is_ptr(t)) + t = btf__type_by_id(btf, t->type); + + return t; +} + +/* Similar to btf_type_skip_modifiers() but does not skip typedefs. */ +static const struct btf_type *btf_type_skip_qualifiers(const struct btf *btf, + uint32_t id) +{ + const struct btf_type *t = btf__type_by_id(btf, id); + + while (btf_type_is_modifier(t) && + BTF_INFO_KIND(t->info) != BTF_KIND_TYPEDEF) + { + t = btf__type_by_id(btf, t->type); + } + + return t; +} + +bool btf_typeid_has_ptr(const struct btf *btf, int id) +{ + const struct btf_type *t; + t = btf_type_skip_modifiers(btf, id, NULL); + + if (!btf_is_ptr(t)) + return false; + return true; +} + +const struct btf_member *btf_find_member(struct btf *btf, int typeid, + const char *target_member_name, int *offset) +{ + const struct btf_type *t; + const struct btf_member *m, *tmpm; + const char *name; + int i; + t = btf__type_by_id(btf, typeid); + t = btf_type_skip_modifiers(btf, typeid, (uint32_t *)&typeid); + t = btf_type_skip_ptr(btf, typeid); + m = btf_members(t); + for (i = 0; i < btf_vlen(t); i++, m++) + { + name = btf__name_by_offset(btf, m->name_off); + if (!name || !name[0]) + { + // find embedded struct/union + tmpm = btf_find_member(btf, m->type, target_member_name, offset); + if (tmpm) + { + pr_dbg("find member: name-%s, off-%u, size-%llu\n", btf__name_by_offset(btf, tmpm->name_off), tmpm->offset, btf__resolve_size(btf, tmpm->type)); + *offset += m->offset; + return tmpm; + } + } + else if (strcmp(name, target_member_name) == 0) + { + pr_dbg("find member: name-%s, off-%u, size-%llu\n", btf__name_by_offset(btf, m->name_off), m->offset, btf__resolve_size(btf, m->type)); + *offset += m->offset; + return m; + } + } + + pr_dbg("Unable to find %s(member) in %s(struct)\n", target_member_name, btf__name_by_offset(btf, t->name_off)); + return NULL; +} + +struct btf *btf_load(char *btf_custom_path) +{ + struct btf *btf; + int err; + if (btf_custom_path != NULL) + btf = btf__parse(btf_custom_path, NULL); + else + btf = libbpf_find_kernel_btf(); + + err = libbpf_get_error(btf); + if (err) + { + errno = -err; + return NULL; + } + + return btf; +} + +static const char *btf_param_type_name(struct btf *btf, const struct btf_param *p) +{ + const struct btf_type *t; + __s32 id = p->type; + t = btf__type_by_id(btf, id); + // todo: 过滤掉名字不是结构体类型的参数 + if (BTF_INFO_KIND(t->info) == BTF_KIND_PTR) + t = btf__type_by_id(btf, t->type); + + if (BTF_INFO_KIND(t->info) == BTF_KIND_CONST) + t = btf__type_by_id(btf, t->type); + return btf__name_by_offset(btf, t->name_off); +} + +int btf_func_proto_find_param(struct btf *btf, int func_proto_id, + const char *type_name, const char *param_name) +{ + const struct btf_type *t; + const struct btf_param *p; + const char *tmp_param_name, *tmp_type_name; + int i; + + t = btf__type_by_id(btf, func_proto_id); + if (t == NULL) + return -EINVAL; + + for (i = 0; i < btf_vlen(t); i++) + { + p = btf_params(t) + i; + tmp_param_name = btf__name_by_offset(btf, p->name_off); + if (param_name && tmp_param_name && strcmp(param_name, tmp_param_name) == 0) + return p->type; + + tmp_type_name = btf_param_type_name(btf, p); + if (type_name && tmp_type_name && strcmp(type_name, tmp_type_name) == 0) + return p->type; + } + return -ENOENT; +} + +int btf_func_proto_find_param_pos(struct btf *btf, int func_proto_id, + const char *type_name, const char *param_name) +{ + const struct btf_type *t; + const struct btf_param *p; + const char *tmp_param_name, *tmp_type_name; + int i; + + t = btf__type_by_id(btf, func_proto_id); + if (t == NULL) + return -EINVAL; + + for (i = 0; i < btf_vlen(t); i++) + { + p = btf_params(t) + i; + tmp_param_name = btf__name_by_offset(btf, p->name_off); + if (param_name && tmp_param_name && strcmp(param_name, tmp_param_name) == 0) + return i + 1; + + tmp_type_name = btf_param_type_name(btf, p); + if (type_name && tmp_type_name && strcmp(type_name, tmp_type_name) == 0) + return i + 1; + } + return -ENOENT; +} + +int btf_find_func_proto_id(struct btf *btf, const char *func_name) +{ + const struct btf_type *t; + int id; + + if (!btf && !func_name) + return -EINVAL; + + id = btf__find_by_name_kind(btf, func_name, BTF_KIND_FUNC); + if (id <= 0) + return id; + t = btf__type_by_id(btf, id); + return t->type; +} \ No newline at end of file diff --git a/source/tools/detect/net_diag/rtrace/ebpf/utils/btf.h b/source/tools/detect/net_diag/rtrace/ebpf/utils/btf.h new file mode 100644 index 00000000..43ce22bf --- /dev/null +++ b/source/tools/detect/net_diag/rtrace/ebpf/utils/btf.h @@ -0,0 +1,17 @@ +#ifndef __RTRACE_UTILS_BTF_H +#define __RTRACE_UTILS_BTF_H + +struct btf *btf_load(char *btf_custom_path); + +int btf_func_proto_find_param(struct btf *btf, int func_proto_id, + const char *type_name, const char *param_name); +int btf_func_proto_find_param_pos(struct btf *btf, int func_proto_id, + const char *type_name, const char *param_name); +// Find func proto type id by func name. +int btf_find_func_proto_id(struct btf *btf, const char *func_name); +// Find member in struct/union by member name. +const struct btf_member *btf_find_member(struct btf *btf, int typeid, + const char *target_member_name, int *offset); +bool btf_typeid_has_ptr(const struct btf *btf, int id); + +#endif diff --git a/source/tools/detect/net_diag/rtrace/ebpf/utils/disasm.c b/source/tools/detect/net_diag/rtrace/ebpf/utils/disasm.c new file mode 100644 index 00000000..130ff880 --- /dev/null +++ b/source/tools/detect/net_diag/rtrace/ebpf/utils/disasm.c @@ -0,0 +1,311 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +/* Copyright (c) 2011-2014 PLUMgrid, http://plumgrid.com + * Copyright (c) 2016 Facebook + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "disasm.h" + +#define __BPF_FUNC_STR_FN(x) [BPF_FUNC_ ## x] = __stringify(bpf_ ## x) +static const char * const func_id_str[] = { + __BPF_FUNC_MAPPER(__BPF_FUNC_STR_FN) +}; +#undef __BPF_FUNC_STR_FN + +static const char *__func_get_name(const struct bpf_insn_cbs *cbs, + const struct bpf_insn *insn, + char *buff, size_t len) +{ + // BUILD_BUG_ON(ARRAY_SIZE(func_id_str) != __BPF_FUNC_MAX_ID); + + if (!insn->src_reg && + insn->imm >= 0 && insn->imm < __BPF_FUNC_MAX_ID && + func_id_str[insn->imm]) + return func_id_str[insn->imm]; + + if (cbs && cbs->cb_call) { + const char *res; + + res = cbs->cb_call(cbs->private_data, insn); + if (res) + return res; + } + + if (insn->src_reg == BPF_PSEUDO_CALL) + snprintf(buff, len, "%+d", insn->imm); + // else if (insn->src_reg == BPF_PSEUDO_KFUNC_CALL) + // snprintf(buff, len, "kernel-function"); + + return buff; +} + +static const char *__func_imm_name(const struct bpf_insn_cbs *cbs, + const struct bpf_insn *insn, + uint64_t full_imm, char *buff, size_t len) +{ + if (cbs && cbs->cb_imm) + return cbs->cb_imm(cbs->private_data, insn, full_imm); + + snprintf(buff, len, "0x%llx", (unsigned long long)full_imm); + return buff; +} + +const char *func_id_name(int id) +{ + if (id >= 0 && id < __BPF_FUNC_MAX_ID && func_id_str[id]) + return func_id_str[id]; + else + return "unknown"; +} + +const char *const bpf_class_string[8] = { + [BPF_LD] = "ld", + [BPF_LDX] = "ldx", + [BPF_ST] = "st", + [BPF_STX] = "stx", + [BPF_ALU] = "alu", + [BPF_JMP] = "jmp", + [BPF_JMP32] = "jmp32", + [BPF_ALU64] = "alu64", +}; + +const char *const bpf_alu_string[16] = { + [BPF_ADD >> 4] = "+=", + [BPF_SUB >> 4] = "-=", + [BPF_MUL >> 4] = "*=", + [BPF_DIV >> 4] = "/=", + [BPF_OR >> 4] = "|=", + [BPF_AND >> 4] = "&=", + [BPF_LSH >> 4] = "<<=", + [BPF_RSH >> 4] = ">>=", + [BPF_NEG >> 4] = "neg", + [BPF_MOD >> 4] = "%=", + [BPF_XOR >> 4] = "^=", + [BPF_MOV >> 4] = "=", + [BPF_ARSH >> 4] = "s>>=", + [BPF_END >> 4] = "endian", +}; + +static const char *const bpf_atomic_alu_string[16] = { + [BPF_ADD >> 4] = "add", + [BPF_AND >> 4] = "and", + [BPF_OR >> 4] = "or", + [BPF_XOR >> 4] = "xor", +}; + +static const char *const bpf_ldst_string[] = { + [BPF_W >> 3] = "u32", + [BPF_H >> 3] = "u16", + [BPF_B >> 3] = "u8", + [BPF_DW >> 3] = "u64", +}; + +static const char *const bpf_jmp_string[16] = { + [BPF_JA >> 4] = "jmp", + [BPF_JEQ >> 4] = "==", + [BPF_JGT >> 4] = ">", + [BPF_JLT >> 4] = "<", + [BPF_JGE >> 4] = ">=", + [BPF_JLE >> 4] = "<=", + [BPF_JSET >> 4] = "&", + [BPF_JNE >> 4] = "!=", + [BPF_JSGT >> 4] = "s>", + [BPF_JSLT >> 4] = "s<", + [BPF_JSGE >> 4] = "s>=", + [BPF_JSLE >> 4] = "s<=", + [BPF_CALL >> 4] = "call", + [BPF_EXIT >> 4] = "exit", +}; + +static void print_bpf_end_insn(bpf_insn_print_t verbose, + void *private_data, + const struct bpf_insn *insn) +{ + verbose(private_data, "(%02x) r%d = %s%d r%d\n", + insn->code, insn->dst_reg, + BPF_SRC(insn->code) == BPF_TO_BE ? "be" : "le", + insn->imm, insn->dst_reg); +} + +void print_bpf_insn(const struct bpf_insn_cbs *cbs, + const struct bpf_insn *insn, + bool allow_ptr_leaks) +{ + const bpf_insn_print_t verbose = cbs->cb_print; + uint8_t class = BPF_CLASS(insn->code); + + if (class == BPF_ALU || class == BPF_ALU64) { + if (BPF_OP(insn->code) == BPF_END) { + if (class == BPF_ALU64) + verbose(cbs->private_data, "BUG_alu64_%02x\n", insn->code); + else + print_bpf_end_insn(verbose, cbs->private_data, insn); + } else if (BPF_OP(insn->code) == BPF_NEG) { + verbose(cbs->private_data, "(%02x) %c%d = -%c%d\n", + insn->code, class == BPF_ALU ? 'w' : 'r', + insn->dst_reg, class == BPF_ALU ? 'w' : 'r', + insn->dst_reg); + } else if (BPF_SRC(insn->code) == BPF_X) { + verbose(cbs->private_data, "(%02x) %c%d %s %c%d\n", + insn->code, class == BPF_ALU ? 'w' : 'r', + insn->dst_reg, + bpf_alu_string[BPF_OP(insn->code) >> 4], + class == BPF_ALU ? 'w' : 'r', + insn->src_reg); + } else { + verbose(cbs->private_data, "(%02x) %c%d %s %d\n", + insn->code, class == BPF_ALU ? 'w' : 'r', + insn->dst_reg, + bpf_alu_string[BPF_OP(insn->code) >> 4], + insn->imm); + } + } else if (class == BPF_STX) { + if (BPF_MODE(insn->code) == BPF_MEM) + verbose(cbs->private_data, "(%02x) *(%s *)(r%d %+d) = r%d\n", + insn->code, + bpf_ldst_string[BPF_SIZE(insn->code) >> 3], + insn->dst_reg, + insn->off, insn->src_reg); + else if (BPF_MODE(insn->code) == BPF_ATOMIC && + (insn->imm == BPF_ADD || insn->imm == BPF_AND || + insn->imm == BPF_OR || insn->imm == BPF_XOR)) { + verbose(cbs->private_data, "(%02x) lock *(%s *)(r%d %+d) %s r%d\n", + insn->code, + bpf_ldst_string[BPF_SIZE(insn->code) >> 3], + insn->dst_reg, insn->off, + bpf_alu_string[BPF_OP(insn->imm) >> 4], + insn->src_reg); + } else if (BPF_MODE(insn->code) == BPF_ATOMIC && + (insn->imm == (BPF_ADD | BPF_FETCH) || + insn->imm == (BPF_AND | BPF_FETCH) || + insn->imm == (BPF_OR | BPF_FETCH) || + insn->imm == (BPF_XOR | BPF_FETCH))) { + verbose(cbs->private_data, "(%02x) r%d = atomic%s_fetch_%s((%s *)(r%d %+d), r%d)\n", + insn->code, insn->src_reg, + BPF_SIZE(insn->code) == BPF_DW ? "64" : "", + bpf_atomic_alu_string[BPF_OP(insn->imm) >> 4], + bpf_ldst_string[BPF_SIZE(insn->code) >> 3], + insn->dst_reg, insn->off, insn->src_reg); + } else if (BPF_MODE(insn->code) == BPF_ATOMIC && + insn->imm == BPF_CMPXCHG) { + verbose(cbs->private_data, "(%02x) r0 = atomic%s_cmpxchg((%s *)(r%d %+d), r0, r%d)\n", + insn->code, + BPF_SIZE(insn->code) == BPF_DW ? "64" : "", + bpf_ldst_string[BPF_SIZE(insn->code) >> 3], + insn->dst_reg, insn->off, + insn->src_reg); + } else if (BPF_MODE(insn->code) == BPF_ATOMIC && + insn->imm == BPF_XCHG) { + verbose(cbs->private_data, "(%02x) r%d = atomic%s_xchg((%s *)(r%d %+d), r%d)\n", + insn->code, insn->src_reg, + BPF_SIZE(insn->code) == BPF_DW ? "64" : "", + bpf_ldst_string[BPF_SIZE(insn->code) >> 3], + insn->dst_reg, insn->off, insn->src_reg); + } else { + verbose(cbs->private_data, "BUG_%02x\n", insn->code); + } + } else if (class == BPF_ST) { + if (BPF_MODE(insn->code) == BPF_MEM) { + verbose(cbs->private_data, "(%02x) *(%s *)(r%d %+d) = %d\n", + insn->code, + bpf_ldst_string[BPF_SIZE(insn->code) >> 3], + insn->dst_reg, + insn->off, insn->imm); + } else if (BPF_MODE(insn->code) == 0xc0 /* BPF_NOSPEC, no UAPI */) { + verbose(cbs->private_data, "(%02x) nospec\n", insn->code); + } else { + verbose(cbs->private_data, "BUG_st_%02x\n", insn->code); + } + } else if (class == BPF_LDX) { + if (BPF_MODE(insn->code) != BPF_MEM) { + verbose(cbs->private_data, "BUG_ldx_%02x\n", insn->code); + return; + } + verbose(cbs->private_data, "(%02x) r%d = *(%s *)(r%d %+d)\n", + insn->code, insn->dst_reg, + bpf_ldst_string[BPF_SIZE(insn->code) >> 3], + insn->src_reg, insn->off); + } else if (class == BPF_LD) { + if (BPF_MODE(insn->code) == BPF_ABS) { + verbose(cbs->private_data, "(%02x) r0 = *(%s *)skb[%d]\n", + insn->code, + bpf_ldst_string[BPF_SIZE(insn->code) >> 3], + insn->imm); + } else if (BPF_MODE(insn->code) == BPF_IND) { + verbose(cbs->private_data, "(%02x) r0 = *(%s *)skb[r%d + %d]\n", + insn->code, + bpf_ldst_string[BPF_SIZE(insn->code) >> 3], + insn->src_reg, insn->imm); + } else if (BPF_MODE(insn->code) == BPF_IMM && + BPF_SIZE(insn->code) == BPF_DW) { + /* At this point, we already made sure that the second + * part of the ldimm64 insn is accessible. + */ + uint64_t imm = ((uint64_t)(insn + 1)->imm << 32) | (uint32_t)insn->imm; + bool is_ptr = insn->src_reg == BPF_PSEUDO_MAP_FD || + insn->src_reg == BPF_PSEUDO_MAP_VALUE; + char tmp[64]; + + if (is_ptr && !allow_ptr_leaks) + imm = 0; + + verbose(cbs->private_data, "(%02x) r%d = %s\n", + insn->code, insn->dst_reg, + __func_imm_name(cbs, insn, imm, + tmp, sizeof(tmp))); + } else { + verbose(cbs->private_data, "BUG_ld_%02x\n", insn->code); + return; + } + } else if (class == BPF_JMP32 || class == BPF_JMP) { + uint8_t opcode = BPF_OP(insn->code); + + if (opcode == BPF_CALL) { + char tmp[64]; + + if (insn->src_reg == BPF_PSEUDO_CALL) { + verbose(cbs->private_data, "(%02x) call pc%s\n", + insn->code, + __func_get_name(cbs, insn, + tmp, sizeof(tmp))); + } else { + strcpy(tmp, "unknown"); + verbose(cbs->private_data, "(%02x) call %s#%d\n", insn->code, + __func_get_name(cbs, insn, + tmp, sizeof(tmp)), + insn->imm); + } + } else if (insn->code == (BPF_JMP | BPF_JA)) { + verbose(cbs->private_data, "(%02x) goto pc%+d\n", + insn->code, insn->off); + } else if (insn->code == (BPF_JMP | BPF_EXIT)) { + verbose(cbs->private_data, "(%02x) exit\n", insn->code); + } else if (BPF_SRC(insn->code) == BPF_X) { + verbose(cbs->private_data, + "(%02x) if %c%d %s %c%d goto pc%+d\n", + insn->code, class == BPF_JMP32 ? 'w' : 'r', + insn->dst_reg, + bpf_jmp_string[BPF_OP(insn->code) >> 4], + class == BPF_JMP32 ? 'w' : 'r', + insn->src_reg, insn->off); + } else { + verbose(cbs->private_data, + "(%02x) if %c%d %s 0x%x goto pc%+d\n", + insn->code, class == BPF_JMP32 ? 'w' : 'r', + insn->dst_reg, + bpf_jmp_string[BPF_OP(insn->code) >> 4], + insn->imm, insn->off); + } + } else { + verbose(cbs->private_data, "(%02x) %s\n", + insn->code, bpf_class_string[class]); + } +} diff --git a/source/tools/detect/net_diag/rtrace/ebpf/utils/disasm.h b/source/tools/detect/net_diag/rtrace/ebpf/utils/disasm.h new file mode 100644 index 00000000..ad5884b1 --- /dev/null +++ b/source/tools/detect/net_diag/rtrace/ebpf/utils/disasm.h @@ -0,0 +1,41 @@ +#ifndef __RTRACE_DISASM_H +#define __RTRACE_DISASM_H + + +#include +#include +#ifndef __KERNEL__ +#include +#include +#endif + +#define __stringify_1(x...) #x +#define __stringify(x...) __stringify_1(x) + +#define __printf(a, b) __attribute__((format(printf, a, b))) + +extern const char *const bpf_alu_string[16]; +extern const char *const bpf_class_string[8]; + +const char *func_id_name(int id); + +typedef __printf(2, 3) void (*bpf_insn_print_t)(void *private_data, + const char *, ...); +typedef const char *(*bpf_insn_revmap_call_t)(void *private_data, + const struct bpf_insn *insn); +typedef const char *(*bpf_insn_print_imm_t)(void *private_data, + const struct bpf_insn *insn, + __u64 full_imm); + +struct bpf_insn_cbs { + bpf_insn_print_t cb_print; + bpf_insn_revmap_call_t cb_call; + bpf_insn_print_imm_t cb_imm; + void *private_data; +}; + +void print_bpf_insn(const struct bpf_insn_cbs *cbs, + const struct bpf_insn *insn, + bool allow_ptr_leaks); + +#endif diff --git a/source/tools/detect/net_diag/rtrace/ebpf/utils/insn.c b/source/tools/detect/net_diag/rtrace/ebpf/utils/insn.c new file mode 100644 index 00000000..99eb5ef1 --- /dev/null +++ b/source/tools/detect/net_diag/rtrace/ebpf/utils/insn.c @@ -0,0 +1,94 @@ + +#include +#include +#include +#include + +#include "utils/insn.h" +#include "utils/disasm.h" + +#define SYM_MAX_NAME 256 +struct dump_data +{ + char scratch_buff[SYM_MAX_NAME + 8]; +}; + +uint64_t insn_get_imm(struct bpf_insn *insn) +{ + uint64_t imm = 0; + imm = insn[0].imm + ((uint64_t)insn[1].imm << 32); + return imm; +} + +void insn_set_imm(struct bpf_insn *insn, uint64_t imm) +{ + insn[0].imm = (int)((imm << 32) >> 32); + insn[1].imm = (int)(imm >> 32); +} + +static const char *print_call(void *private_data, + const struct bpf_insn *insn) +{ + struct dump_data *dd = private_data; + snprintf(dd->scratch_buff, sizeof(dd->scratch_buff), "funccall"); + return dd->scratch_buff; +} + +static void print_insn(void *private_data, const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + vprintf(fmt, args); + va_end(args); +} + +static const char *print_imm(void *private_data, + const struct bpf_insn *insn, + __u64 full_imm) +{ + struct dump_data *dd = private_data; + + if (insn->src_reg == BPF_PSEUDO_MAP_FD) + snprintf(dd->scratch_buff, sizeof(dd->scratch_buff), + "map[id:%u]", insn->imm); + else if (insn->src_reg == BPF_PSEUDO_MAP_VALUE) + snprintf(dd->scratch_buff, sizeof(dd->scratch_buff), + "map[id:%u][0]+%u", insn->imm, (insn + 1)->imm); + // else if (insn->src_reg == BPF_PSEUDO_MAP_IDX_VALUE) + // snprintf(dd->scratch_buff, sizeof(dd->scratch_buff), + // "map[idx:%u]+%u", insn->imm, (insn + 1)->imm); + // else if (insn->src_reg == BPF_PSEUDO_FUNC) + // snprintf(dd->scratch_buff, sizeof(dd->scratch_buff), + // "subprog[%+d]", insn->imm); + else + snprintf(dd->scratch_buff, sizeof(dd->scratch_buff), + "0x%llx", (unsigned long long)full_imm); + return dd->scratch_buff; +} + +void insns_dump(struct bpf_insn *insns, int cnt) +{ + struct dump_data dd = {0}; + const struct bpf_insn_cbs cbs = { + .cb_print = print_insn, + .cb_call = print_call, + .cb_imm = print_imm, + .private_data = &dd, + }; + int i; + bool double_insn = false; + + for (i = 0; i < cnt; i++) + { + if (double_insn) + { + double_insn = false; + continue; + } + + double_insn = insns[i].code == (BPF_LD | BPF_IMM | BPF_DW); + printf("% 4d: ", i); + print_bpf_insn(&cbs, insns + i, true); + } +} \ No newline at end of file diff --git a/source/tools/detect/net_diag/rtrace/ebpf/utils/insn.h b/source/tools/detect/net_diag/rtrace/ebpf/utils/insn.h new file mode 100644 index 00000000..96fb5c64 --- /dev/null +++ b/source/tools/detect/net_diag/rtrace/ebpf/utils/insn.h @@ -0,0 +1,7 @@ +#ifndef __RTRACE_UTILS_INSN_H +#define __RTRACE_UTILS_INSN_H + +extern uint64_t insn_get_imm(struct bpf_insn *insn); +void insn_set_imm(struct bpf_insn *insn, uint64_t imm); +void insns_dump(struct bpf_insn *insns, int cnt); +#endif diff --git a/source/tools/detect/net_diag/rtrace/ebpf/utils/object.c b/source/tools/detect/net_diag/rtrace/ebpf/utils/object.c new file mode 100644 index 00000000..8c7fedba --- /dev/null +++ b/source/tools/detect/net_diag/rtrace/ebpf/utils/object.c @@ -0,0 +1,16 @@ + +#include +#include +#include +#include + +#include "common.usr.h" +#include "utils/object.h" + + +struct bpf_program *object_find_program(struct bpf_object *obj, int sk, int skb) +{ + char func_name[FUNCNAME_MAX_LEN]; + sprintf(func_name, "kprobe_sk%d_skb%d", sk, skb); + return bpf_object__find_program_by_name(obj, func_name); +} \ No newline at end of file diff --git a/source/tools/detect/net_diag/rtrace/ebpf/utils/object.h b/source/tools/detect/net_diag/rtrace/ebpf/utils/object.h new file mode 100644 index 00000000..113dc0c2 --- /dev/null +++ b/source/tools/detect/net_diag/rtrace/ebpf/utils/object.h @@ -0,0 +1,6 @@ +#ifndef __RTRACE_UTILS_OBJECT_H +#define __RTRACE_UTILS_OBJECT_H + +struct bpf_program *object_find_program(struct bpf_object *obj, int sk, int skb); + +#endif \ No newline at end of file diff --git a/source/tools/detect/net_diag/rtrace/rtrace-delay/README.md b/source/tools/detect/net_diag/rtrace/rtrace-delay/README.md new file mode 100644 index 00000000..ed4a1223 --- /dev/null +++ b/source/tools/detect/net_diag/rtrace/rtrace-delay/README.md @@ -0,0 +1,17 @@ + + + +## 时延诊断原理 + +### 三次握手报文时延诊断 + +### 数据报文时延诊断 + +## 使用说明 + + + +## 相关文章 + + +sudo RUST_LOG=debug cargo run -- --config ./config/syn-sender.toml --delay 0 --latency 2000 \ No newline at end of file diff --git a/source/tools/detect/net_diag/rtrace/rtrace-drop/README.md b/source/tools/detect/net_diag/rtrace/rtrace-drop/README.md new file mode 100644 index 00000000..b9268f7f --- /dev/null +++ b/source/tools/detect/net_diag/rtrace/rtrace-drop/README.md @@ -0,0 +1,3 @@ +# rtrace-drop + +rtrace-parser is a network packet loss tracing and diagnosis tool. \ No newline at end of file diff --git a/source/tools/detect/net_diag/rtrace/rtrace-parser/Cargo.toml b/source/tools/detect/net_diag/rtrace/rtrace-parser/Cargo.toml new file mode 100644 index 00000000..6434d13a --- /dev/null +++ b/source/tools/detect/net_diag/rtrace/rtrace-parser/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "rtrace-parser" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +libbpf-sys = { git = "https://github.com/chengshuyi/libbpf-sys" } +rtrace-rs = { version = "0.1.0", path = "../rtrace-rs"} +anyhow = "1.0" +once_cell = "1.8.0" +crossbeam-channel = "0.5" +log = "0.4.0" +nix = "0.22" diff --git a/source/tools/detect/net_diag/rtrace/rtrace-parser/README.md b/source/tools/detect/net_diag/rtrace/rtrace-parser/README.md new file mode 100644 index 00000000..4c5943f3 --- /dev/null +++ b/source/tools/detect/net_diag/rtrace/rtrace-parser/README.md @@ -0,0 +1,3 @@ +# rtrace-parser + +rtrace-parser is a network data parsing library, which mainly processes the data obtained by ebpf. \ No newline at end of file diff --git a/source/tools/detect/net_diag/rtrace/rtrace-parser/src/func.rs b/source/tools/detect/net_diag/rtrace/rtrace-parser/src/func.rs new file mode 100644 index 00000000..6f965d2a --- /dev/null +++ b/source/tools/detect/net_diag/rtrace/rtrace-parser/src/func.rs @@ -0,0 +1,224 @@ +use crate::ksyms::ksyms_addr_to_name; +use anyhow::anyhow; +use anyhow::Result; +use rtrace_rs::bindings::*; +use std::fmt; +use std::os::raw::{c_char, c_int}; + +#[derive(Clone, Debug, Default)] +pub struct Func { + // Structure data contained in a single trace function. + // Such as basic information, context or tcp window. + name: String, + kretname: String, + data: Vec, + mask: u64, + types: Vec<*const u8>, + extra: usize, +} + +impl fmt::Display for Func { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut ty = INFO_TYPE::BASIC_INFO; + match self.get_struct(ty) { + Ok(x) => { + let bi = x as *const BASIC_INFO_struct; + unsafe { + writeln!(f, "{:?}", *bi); + } + } + _ => {} + } + write!(f, "a") + } +} + +impl Func { + pub fn new(data: Vec) -> Func { + let mut f = Func { + data: Vec::new(), + name: String::new(), + kretname: String::new(), + mask: 0, + types: Vec::with_capacity(64), + extra: 0, + }; + let mut off = 0; + f.data = data; + f.types.resize(64, std::ptr::null()); + f.mask = f.get_u64_by_off(0); + + for i in 0..64 { + if ((1 << i) & f.mask) != 0 { + let ty = INFO_TYPE::from_u32(i); + match ty { + INFO_TYPE::BASIC_INFO => { + f.types[i as usize] = &f.data[off] as *const u8; + off += ty.get_size(); + } + INFO_TYPE::CGROUP => { + f.types[i as usize] = &f.data[off] as *const u8; + off += ty.get_size(); + } + INFO_TYPE::STACK => { + f.types[i as usize] = &f.data[off] as *const u8; + off += ty.get_size(); + } + INFO_TYPE::KRETPROBE | INFO_TYPE::LINEPROBE => { + assert_eq!(f.types[INFO_TYPE::BASIC_INFO as usize], std::ptr::null()); + f.types[INFO_TYPE::BASIC_INFO as usize] = &f.data[off] as *const u8; + off += ty.get_size(); + } + _ => panic!("not support type"), + } + } + } + + f.extra = off; + if f.types[INFO_TYPE::BASIC_INFO as usize] != std::ptr::null() { + let bi = f.types[INFO_TYPE::BASIC_INFO as usize] as *const BASIC_INFO_struct; + unsafe { f.name = ksyms_addr_to_name((*bi).ip) }; + f.kretname = f.name.clone(); + if f.is_kretprobe() { + unsafe { f.kretname.push_str(&format!("({})", (*bi).ret)[..]) }; + } + } + + f + } + fn get_u32_by_off(&self, off: usize) -> u32 { + let ptr = &self.data[off] as *const u8 as *const u32; + unsafe { *ptr } + } + + fn get_u64_by_off(&self, off: usize) -> u64 { + let ptr = &self.data[off] as *const u8 as *const u64; + unsafe { *ptr } + } + + pub fn get_struct(&self, ty: INFO_TYPE) -> Result<*const u8> { + let ptr = self.types[ty as usize]; + if ptr == std::ptr::null() { + return Err(anyhow!("{:?} not exist", ty)); + } + Ok(ptr) + } + + pub fn get_name_no_offset(&self) -> String { + let mut name = self.name.clone(); + if let Some(x) = name.find('+') { + name.truncate(x); + } + name + } + + pub fn get_name(&self) -> &String { + &self.name + } + + pub fn get_kretname(&self) -> &String { + &self.kretname + } + + pub fn get_ts(&self) -> u64 { + let bi = self.get_struct(INFO_TYPE::BASIC_INFO).unwrap() as *const BASIC_INFO_struct; + unsafe { (*bi).ts } + } + + pub fn is_send(&self) -> bool { + (self.get_ts() & 0x1) == 1 + } + + pub fn is_kretprobe(&self) -> bool { + (self.mask & (1 << INFO_TYPE::KRETPROBE as u64)) != 0 + } + + pub fn get_seq(&self) -> (usize, usize) { + let bi = self.get_struct(INFO_TYPE::BASIC_INFO).unwrap() as *const BASIC_INFO_struct; + unsafe { + let seq = (*bi).seq as usize; + let end_seq = (*bi).end_seq as usize; + (seq, end_seq) + } + } + + pub fn get_rseq(&self) -> (usize, usize) { + let bi = self.get_struct(INFO_TYPE::BASIC_INFO).unwrap() as *const BASIC_INFO_struct; + unsafe { + let rseq = (*bi).rseq as usize; + let rend_seq = (*bi).rend_seq as usize; + (rseq, rend_seq) + } + } + + pub fn get_ap(&self) -> addr_pair { + let bi = self.get_struct(INFO_TYPE::BASIC_INFO).unwrap() as *const BASIC_INFO_struct; + unsafe { (*bi).ap } + } + + /// extra mean data of expression statement. + pub fn get_extra(&self, off: usize) -> *const u8 { + &self.data[off + self.extra] as *const u8 + } + + pub fn show_brief(&self) { + // println!( + // "func: {}, seq: {:?}, rseq: {:?}, ts: {}", + // self.get_func_name_with_kret(), + // self.get_seq(), + // self.get_rseq(), + // self.get_ts() + // ); + } + + pub fn get_stack_string(&self) -> Result { + let st = self.get_struct(INFO_TYPE::STACK)? as *const STACK_struct; + let mut vec_str = Vec::new(); + for i in 0..5 { + let mut tmp = unsafe {ksyms_addr_to_name((*st).kern_stack[i])}; + tmp.insert(0, '\t'); + vec_str.push(tmp); + } + Ok(format!("{}", vec_str.join("\n"))) + } + + pub fn show_stack(&self) { + unsafe { println!("{}\n", self.get_stack_string().unwrap()) }; + } + + pub fn show(&self) { + // for (i, item) in self.data.iter().enumerate() { + // let typ = get_type_from_ptr(*item); + // println!("{}: {:?}", i, typ); + // match get_type_from_ptr(*item) { + // INFO_TYPE::BASIC_INFO => { + // let bi = *item as *const BASIC_INFO_struct; + // unsafe { + // println!("{:#?}", *bi); + // } + // } + // INFO_TYPE::CONTEXT => { + // let ct = *item as *const CONTEXT_struct; + // unsafe { + // println!("{:#?}", *ct); + // } + // } + // INFO_TYPE::MEMORY => { + // let mm = *item as *const MEMORY_struct; + // unsafe { + // println!("{:#?}", *mm); + // } + // } + // INFO_TYPE::TCP_WINDOW => { + // let tw = *item as *const TCP_WINDOW_struct; + // unsafe { + // println!("{:#?}", *tw); + // } + // } + // _ => { + // panic!("Unknown format\n"); + // } + // } + // } + } +} diff --git a/source/tools/detect/net_diag/rtrace/rtrace-parser/src/ksyms.rs b/source/tools/detect/net_diag/rtrace/rtrace-parser/src/ksyms.rs new file mode 100644 index 00000000..a5b0a054 --- /dev/null +++ b/source/tools/detect/net_diag/rtrace/rtrace-parser/src/ksyms.rs @@ -0,0 +1,116 @@ +use anyhow::Result; +use once_cell::sync::Lazy; +use std::fs::File; +use std::io::{self, BufRead}; +use std::sync::Mutex; +use log::*; +#[derive(Debug, Default)] +pub struct Ksyms { + syms: Vec<(String, u64)>, +} + +impl Ksyms { + pub fn new() -> Self { + Ksyms { syms: Vec::new() } + } + + pub(crate) fn insert(&mut self, sym_name: String, sym_addr: u64) { + self.syms.push((sym_name, sym_addr)); + } + + pub(crate) fn get_ksyms_num(&self) -> usize { + self.syms.len() + } + + pub fn load(&mut self, filename: &String) -> Result<()> { + self.syms.clear(); + let file = File::open(filename)?; + let lines = io::BufReader::new(file).lines(); + for line in lines { + if let Ok(l) = line { + let mut iter = l.trim().split_whitespace(); + if let Some(x) = iter.next() { + iter.next(); + if let Some(y) = iter.next() { + self.insert(y.to_string(), u64::from_str_radix(x, 16).unwrap()); + } + } + } + } + self.syms.sort_by(|a, b| a.1.cmp(&b.1)); + debug!( + "Load ksyms done from {:?}, symbols length: {}", + filename, + self.syms.len() + ); + Ok(()) + } + + pub fn addr_to_name(&self, addr: u64) -> String { + let mut start = 0; + let mut end = self.syms.len() - 1; + let mut mid; + let mut sym_addr; + + while start < end { + mid = start + (end - start + 1) / 2; + sym_addr = self.syms[mid].1; + + if sym_addr <= addr { + start = mid; + } else { + end = mid - 1; + } + } + + if start == end && self.syms[start].1 <= addr { + let mut name = self.syms[start].0.clone(); + name.push_str(&format!("+{}", addr - self.syms[start].1 - 1)); + return name; + } + + return String::from("Not Found"); + } +} + +/// +static GLOBAL_KSYMS: Lazy> = Lazy::new(|| { + let ksyms = Ksyms::new(); + Mutex::new(ksyms) +}); + +/// load all kernel symbols +pub fn ksyms_load(filename: &String) { + GLOBAL_KSYMS.lock().unwrap().load(filename).unwrap(); +} + +/// Convert the kernel symbol address to the form of function name + offset +pub fn ksyms_addr_to_name(addr: u64) -> String { + GLOBAL_KSYMS.lock().unwrap().addr_to_name(addr) +} + +#[cfg(test)] +mod tests { + + use super::*; + #[test] + fn test_ksyms_load() { + let mut ksym = Ksyms::new(); + let err = ksym.load(&PathBuf::from("/proc/kallsyms")); + assert_eq!(err.is_ok(), true); + let pre_len = ksym.get_ksyms_num(); + + let err = ksym.load(&PathBuf::from("/3124/2123")); + assert_eq!(err.is_ok(), false); + let aft_len = ksym.get_ksyms_num(); + assert_ne!(pre_len, aft_len); + } + + #[test] + fn test_ksyms_search() { + let mut ksym = Ksyms::new(); + ksym.insert(String::from("test3"), 3); + ksym.insert(String::from("test1"), 1); + ksym.insert(String::from("test2"), 2); + } +} diff --git a/source/tools/detect/net_diag/rtrace/rtrace-parser/src/lib.rs b/source/tools/detect/net_diag/rtrace/rtrace-parser/src/lib.rs new file mode 100644 index 00000000..d388c6d3 --- /dev/null +++ b/source/tools/detect/net_diag/rtrace/rtrace-parser/src/lib.rs @@ -0,0 +1,8 @@ +pub mod func; +pub mod ksyms; +pub mod net; +pub mod perf; +pub mod skb; +pub mod sock; +pub mod utils; + diff --git a/source/tools/detect/net_diag/rtrace/rtrace-parser/src/net.rs b/source/tools/detect/net_diag/rtrace/rtrace-parser/src/net.rs new file mode 100644 index 00000000..98cbe9be --- /dev/null +++ b/source/tools/detect/net_diag/rtrace/rtrace-parser/src/net.rs @@ -0,0 +1,53 @@ +use crate::func::Func; +use crate::sock::Sock; +use anyhow::Result; +use log::*; +use rtrace_rs::bindings::*; +use std::collections::HashMap; +use std::rc::Rc; + +pub struct Net { + sockmap: HashMap, + funcs: Vec, + recv: bool, +} + +impl Net { + pub fn new(recv: bool) -> Net { + let mut n = Net { + sockmap: HashMap::new(), + funcs: Vec::new(), + recv, + }; + n + } + + // 1. Get the network quadruple according to Func + // 2. Get sock + // 3. Send func to the sock + pub fn push_func(&mut self, func: Func) { + // if self.recv { + // println!("name: {}, seq:{:?}", func.get_name(), func.get_rseq()); + // } else { + // println!("name: {}, seq:{:?}", func.get_name(), func.get_seq()); + // } + // unsafe {println!("{:?}", (*(func.get_struct(INFO_TYPE::BASIC_INFO).unwrap() as *const BASIC_INFO_struct)).into_string());} + let ap = func.get_ap(); + let sk = self.sockmap.entry(ap).or_insert(Sock::new(ap, self.recv)); + if log_enabled!(Level::Info) { + func.show_brief(); + } + sk.push_func(func); + } + + pub fn group(&mut self, max_ts: u64) -> Vec>> { + let mut res = Vec::new(); + for (_, sk) in &mut self.sockmap { + let tmp = sk.group_funcs(max_ts); + if tmp.len() != 0 { + res.push(tmp); + } + } + res + } +} diff --git a/source/tools/detect/net_diag/rtrace/rtrace-parser/src/perf.rs b/source/tools/detect/net_diag/rtrace/rtrace-parser/src/perf.rs new file mode 100644 index 00000000..dd5b0eca --- /dev/null +++ b/source/tools/detect/net_diag/rtrace/rtrace-parser/src/perf.rs @@ -0,0 +1,280 @@ +use anyhow::anyhow; +use anyhow::Result; +use core::ffi::c_void; +use libbpf_sys; +use std::boxed::Box; +use std::slice; +use std::time::Duration; +use log::*; +use once_cell::sync::Lazy; +use std::sync::Arc; +use std::sync::Mutex; +use std::thread; +use crossbeam_channel::{Sender, Receiver}; + +fn is_power_of_two(i: usize) -> bool { + i > 0 && (i & (i - 1)) == 0 +} + +// Workaround for `trait_alias` +// (https://doc.rust-lang.org/unstable-book/language-features/trait-alias.html) +// not being available yet. This is just a custom trait plus a blanket implementation. +pub trait SampleCb: FnMut(i32, &[u8]) + 'static {} +impl SampleCb for T where T: FnMut(i32, &[u8]) + 'static {} + +pub trait LostCb: FnMut(i32, u64) + 'static {} +impl LostCb for T where T: FnMut(i32, u64) + 'static {} + +struct CbStruct { + sample_cb: Option>, + lost_cb: Option>, +} + +/// Builds [`PerfBuffer`] instances. +pub struct PerfBufferBuilder { + mapfd: i32, + pages: usize, + sample_cb: Option>, + lost_cb: Option>, +} + +impl PerfBufferBuilder { + pub fn new(mapfd: i32) -> Self { + Self { + mapfd, + pages: 128, + sample_cb: None, + lost_cb: None, + } + } +} + +impl PerfBufferBuilder { + /// Callback to run when a sample is received. + /// + /// This callback provides a raw byte slice. You may find libraries such as + /// [`plain`](https://crates.io/crates/plain) helpful. + /// + /// Callback arguments are: `(cpu, data)`. + pub fn sample_cb(self, cb: NewCb) -> PerfBufferBuilder { + PerfBufferBuilder { + mapfd: self.mapfd, + pages: self.pages, + sample_cb: Some(Box::new(cb)), + lost_cb: self.lost_cb, + } + } + + /// Callback to run when a sample is received. + /// + /// Callback arguments are: `(cpu, lost_count)`. + pub fn lost_cb(self, cb: NewCb) -> PerfBufferBuilder { + PerfBufferBuilder { + mapfd: self.mapfd, + pages: self.pages, + sample_cb: self.sample_cb, + lost_cb: Some(Box::new(cb)), + } + } + + /// The number of pages to size the ring buffer. + pub fn pages(&mut self, pages: usize) -> &mut Self { + self.pages = pages; + self + } + + pub fn build(self) -> Result { + if !is_power_of_two(self.pages) { + return Err(anyhow!("Page count must be power of two")); + } + + let c_sample_cb: libbpf_sys::perf_buffer_sample_fn = if self.sample_cb.is_some() { + Some(Self::call_sample_cb) + } else { + None + }; + + let c_lost_cb: libbpf_sys::perf_buffer_lost_fn = if self.lost_cb.is_some() { + Some(Self::call_lost_cb) + } else { + None + }; + + let callback_struct_ptr = Box::into_raw(Box::new(CbStruct { + sample_cb: self.sample_cb, + lost_cb: self.lost_cb, + })); + + let ptr = unsafe { + libbpf_sys::perf_buffer__new( + self.mapfd, + self.pages as libbpf_sys::size_t, + c_sample_cb, + c_lost_cb, + callback_struct_ptr as *mut _, + std::ptr::null(), + ) + }; + let err = unsafe { libbpf_sys::libbpf_get_error(ptr as *const _) }; + if err != 0 { + Err(anyhow!("Unable to create perf buffer")) + } else { + Ok(PerfBuffer { + ptr, + _cb_struct: unsafe { Box::from_raw(callback_struct_ptr) }, + }) + } + } + + unsafe extern "C" fn call_sample_cb(ctx: *mut c_void, cpu: i32, data: *mut c_void, size: u32) { + let callback_struct = ctx as *mut CbStruct; + + if let Some(cb) = &mut (*callback_struct).sample_cb { + cb(cpu, slice::from_raw_parts(data as *const u8, size as usize)); + } + } + + unsafe extern "C" fn call_lost_cb(ctx: *mut c_void, cpu: i32, count: u64) { + let callback_struct = ctx as *mut CbStruct; + + if let Some(cb) = &mut (*callback_struct).lost_cb { + cb(cpu, count); + } + } +} + +/// Represents a special kind of [`Map`]. Typically used to transfer data between +/// [`Program`]s and userspace. +pub struct PerfBuffer { + pub ptr: *mut libbpf_sys::perf_buffer, + // Hold onto the box so it'll get dropped when PerfBuffer is dropped + _cb_struct: Box, +} + +unsafe impl Send for PerfBuffer {} + +impl PerfBuffer { + pub fn poll(&self, timeout: Duration) -> Result<()> { + let ret = unsafe { libbpf_sys::perf_buffer__poll(self.ptr, timeout.as_millis() as i32) }; + if ret < 0 { + Err(anyhow!("Err({}) occurs on perf poll", ret)) + } else { + Ok(()) + } + } +} + +impl Drop for PerfBuffer { + fn drop(&mut self) { + unsafe { + libbpf_sys::perf_buffer__free(self.ptr); + } + } +} + +static GLOBAL_TX: Lazy)>>>> = + Lazy::new(|| Mutex::new(None)); + +static GLOBAL_RX: Lazy)>>>> = + Lazy::new(|| Mutex::new(None)); + +fn handle_lost_events(cpu: i32, count: u64) { + error!("Lost {} events on CPU {}", count, cpu); +} + +fn handle_event(_cpu: i32, data: &[u8]) { + let event = Vec::from(data); + GLOBAL_TX + .lock() + .unwrap() + .as_ref() + .unwrap() + .send((_cpu as usize, event)) + .unwrap(); +} + +fn thread_perf_handle(fd: i32) { + if fd < 0 { + return; + } + + let perf = Arc::new(Mutex::new( + PerfBufferBuilder::new(fd) + .sample_cb(handle_event) + .lost_cb(handle_lost_events) + .build() + .unwrap(), + )); + let clone_perf = perf.clone(); + + thread::spawn(move || loop { + unsafe { + libbpf_sys::perf_buffer__consume(perf.lock().unwrap().ptr); + } + thread::sleep(Duration::from_millis(100)); + }); + + loop { + clone_perf + .lock() + .unwrap() + .poll(Duration::from_millis(100)) + .unwrap(); + } +} + +pub fn perf_inital_thread(fd: i32) { + let (tx, rx) = crossbeam_channel::unbounded(); + *GLOBAL_TX.lock().unwrap() = Some(tx); + *GLOBAL_RX.lock().unwrap() = Some(rx); + thread::spawn(move || thread_perf_handle(fd)); +} + +pub fn perf_inital_thread2(fd: i32, cs: (Sender<(usize, Vec)>, Receiver<(usize, Vec)>)) { + *GLOBAL_TX.lock().unwrap() = Some(cs.0); + *GLOBAL_RX.lock().unwrap() = Some(cs.1); + thread::spawn(move || thread_perf_handle(fd)); +} + +pub fn perf_recv() -> (usize, Vec) { + GLOBAL_RX.lock().unwrap().as_ref().unwrap().recv().unwrap() +} + +pub fn perf_recv_timeout( + timeout: Duration, +) -> std::result::Result<(usize, Vec), crossbeam_channel::RecvTimeoutError> { + GLOBAL_RX + .lock() + .unwrap() + .as_ref() + .unwrap() + .recv_timeout(timeout) +} + + +#[cfg(test)] +mod tests { + use super::*; + + fn is_power_of_two_slow(i: usize) -> bool { + if i == 0 { + return false; + } + + let mut n = i; + while n > 1 { + if n & 0x01 as usize == 1 { + return false; + } + n >>= 1; + } + true + } + + #[test] + fn test_is_power_of_two() { + for i in 0..=256 { + assert_eq!(is_power_of_two(i), is_power_of_two_slow(i)); + } + } +} diff --git a/source/tools/detect/net_diag/rtrace/rtrace-parser/src/skb.rs b/source/tools/detect/net_diag/rtrace/rtrace-parser/src/skb.rs new file mode 100644 index 00000000..bdb0dfde --- /dev/null +++ b/source/tools/detect/net_diag/rtrace/rtrace-parser/src/skb.rs @@ -0,0 +1,270 @@ +use crate::func::Func; +use crate::sock::Sock; +use crate::utils::*; +use rtrace_rs::bindings::*; +use std::collections::HashMap; +use std::rc::Rc; + +#[derive(Debug, Default)] +pub struct Skb { + min_seq: usize, + end_seq: usize, + delay: u64, + funcs: Vec>, + max_ts: u64, + recv: bool, +} + +impl Skb { + pub fn new(recv: bool) -> Skb { + Skb { + min_seq: usize::MAX, + end_seq: 0, + delay: u64::MAX, + funcs: Vec::new(), + max_ts: 0, + recv, + } + } + + pub fn funcs_len(&self) -> usize { + self.funcs.len() + } + + pub fn push_func(&mut self, func: Rc) { + let tmp; + if self.recv { + tmp = func.get_rseq(); + } else { + tmp = func.get_seq(); + } + if tmp.0 != 0 { + self.min_seq = std::cmp::min(self.min_seq, tmp.0); + } + if tmp.0 == 0 && self.min_seq == usize::MAX { + self.min_seq = 0; + } + self.max_ts = std::cmp::max(self.max_ts, func.get_ts()); + self.funcs.push(func); + } + + pub fn from_funcs(funcs: Vec>, recv: bool) -> Skb { + let mut skb = Skb::new(recv); + for func in funcs { + skb.push_func(func); + } + skb + } + + pub fn get_funcs_by_name(&self, name: &String) -> Vec> { + let mut funcs = Vec::new(); + for func in &self.funcs { + if name.eq(func.get_name()) { + funcs.push(func.clone()); + } + } + funcs + } + + pub fn get_delay(&mut self) -> u64 { + if self.delay == u64::MAX { + self.funcs.sort_by(|a, b| a.get_ts().cmp(&b.get_ts())); + self.delay = self.funcs.last().unwrap().get_ts() - self.funcs.first().unwrap().get_ts(); + } + self.delay + } + + pub fn get_delay_ms(&mut self) -> u64 { + let delay = self.get_delay(); + delay / 1000_000 + } + + pub fn get_max_ts(&self) -> u64 { + self.max_ts + } + + pub fn show(&self) { + if self.funcs.len() == 0 { + return; + } + println!("FUNCTION DELAY: {}\n", self.funcs[0].get_ap().into_string()); + let mut row = (self.funcs.len() as f64).sqrt() as usize; + if row * row < self.funcs.len() { + row += 1; + } + let index_table = get_index_table(row); + + for i in 0..row { + // first line + for j in 0..row { + let index = index_table[i][j]; + if index >= self.funcs.len() { + continue; + } + + let mut seq; + if self.recv { + seq = self.funcs[index].get_rseq(); + if seq.0 == 0 { + seq.0 = self.min_seq; + } + } else { + seq = self.funcs[index].get_seq(); + } + let name = self.funcs[index].get_kretname(); + // let ts = self.funcs[index].get_ts(); + + if i != 0 && i != row - 1 && j != 0 { + print!("{:^10}", " "); + } + + if i == 0 && j != 0 { + if j % 2 == 0 { + let ts = self.funcs[index].get_ts() - self.funcs[index - 1].get_ts(); + let tmp_str = format!("→{}us→", ts / 1000); + print!("{:^10}", tmp_str) + } else { + print!("{:^10}", " "); + } + } + + if i == row - 1 && j != 0 { + if j % 2 == 1 { + let ts = self.funcs[index].get_ts() - self.funcs[index - 1].get_ts(); + let tmp_str = format!("→{}us→", ts / 1000); + print!("{:^10}", tmp_str); + } else { + print!("{:^10}", " "); + } + } + let tmp_str = format!( + "({},{}){}", + seq.0 - self.min_seq, + seq.1 - self.min_seq, + name + ); + print!("{:^30}", tmp_str); + } + println!(""); + if i != row - 1 { + // second line + for j in 0..row { + let index = index_table[i][j]; + if index >= self.funcs.len() { + continue; + } + if j % 2 == 0 { + print!("{:^30}", "↓"); + } else { + print!("{:^30}", "↑"); + } + print!("{:^10}", " "); + } + println!(""); + // thrid line + for j in 0..row { + let index = index_table[i][j]; + let nxt_index = index_table[i + 1][j]; + if index >= self.funcs.len() || nxt_index >= self.funcs.len() { + continue; + } + let ts = self.funcs[index].get_ts(); + let nxt_ts = self.funcs[nxt_index].get_ts(); + let val; + if ts < nxt_ts { + val = nxt_ts - ts; + } else { + val = ts - nxt_ts; + } + let tmp_str = format!("{}us", val / 1000); + print!("{:^30}", tmp_str); + print!("{:^10}", " "); + } + println!(""); + // fourth line + for j in 0..row { + let index = index_table[i][j]; + if index >= self.funcs.len() { + continue; + } + if j % 2 == 0 { + print!("{:^30}", "↓"); + } else { + print!("{:^30}", "↑"); + } + print!("{:^10}", " "); + } + println!(""); + } + } + println!("\n"); + } + + pub fn show_brief(&self) {} +} + +pub fn funcs_to_skbs(funcs: Vec>, raw: bool, recv: bool) -> Vec { + let mut skbs = Vec::new(); + if raw { + skbs.push(Skb::from_funcs(funcs, recv)); + } else { + let mut seqs = Vec::new(); + if recv { + for func in &funcs { + let (sseq, eseq) = func.get_rseq(); + if sseq != 0 { + seqs.push(sseq); + } + seqs.push(eseq); + } + } else { + for func in &funcs { + let (sseq, eseq) = func.get_seq(); + seqs.push(sseq); + seqs.push(eseq); + } + } + seqs.sort_unstable(); + seqs.dedup(); + + if seqs.len() == 1 { + seqs.push(seqs[0]); + } + + for i in 1..seqs.len() { + let mut skb = Skb::new(recv); + if recv { + for func in &funcs { + let (sseq, eseq) = func.get_rseq(); + if seqs[i - 1] >= sseq && seqs[i] <= eseq { + skb.push_func(func.clone()); + } + } + } else { + for func in &funcs { + let (sseq, eseq) = func.get_seq(); + if seqs[i - 1] >= sseq && seqs[i] <= eseq { + skb.push_func(func.clone()); + } + } + } + if skb.funcs_len() != 0 { + skbs.push(skb); + } + } + } + + skbs +} + +// if self.skb_raw { +// skbs.push(Skb::from_vec(funcs, true)); +// } else { +// for func in funcs { +// let (sseq, eseq) = func.get_seq(); +// seqs.push(sseq); +// seqs.push(eseq); +// } +// seqs.sort_unstable(); +// seqs.dedup(); +// } diff --git a/source/tools/detect/net_diag/rtrace/rtrace-parser/src/sock.rs b/source/tools/detect/net_diag/rtrace/rtrace-parser/src/sock.rs new file mode 100644 index 00000000..281478d0 --- /dev/null +++ b/source/tools/detect/net_diag/rtrace/rtrace-parser/src/sock.rs @@ -0,0 +1,140 @@ +use crate::func::Func; +use anyhow::Result; +use log::*; +use rtrace_rs::bindings::*; +use std::collections::BTreeMap; +use std::rc::Rc; + +#[derive(Debug)] +pub struct Sock { + ap: addr_pair, + seq: Vec, + + max_send_seq: usize, + max_recv_seq: usize, + // usize: Max end seq + // u64: max ts + send: BTreeMap>, usize, u64)>, + recv: BTreeMap>, usize, u64)>, + recv_path: bool, +} + +fn bm_insert( + bm: &mut BTreeMap>, usize, u64)>, + seq: (usize, usize), + func: Rc, +) { + let mut item; + if seq.0 == 0 { + item = bm.entry(seq.1 - 1).or_insert((Vec::new(), 0, 0)); + } else { + item = bm.entry(seq.0).or_insert((Vec::new(), 0, 0)); + } + (*item).1 = std::cmp::max((*item).1, seq.1); + (*item).2 = std::cmp::max((*item).2, func.get_ts()); + (*item).0.push(func); +} + +fn bm_group(bm: &mut BTreeMap>, usize, u64)>, max_ts: u64) -> Vec> { + let mut funcs = Vec::new(); + let mut keys = Vec::new(); + let mut max_end_seq = 0; + + // println!("bm_group: bmap len is {}, max ts is {}", bm.len(), max_ts); + for (key, value) in bm.iter() { + if value.2 > max_ts { + // println!("{} {}", value.2, max_ts); + return funcs; + } + + if max_end_seq == 0 { + max_end_seq = value.1; + keys.push(*key); + continue; + } + + if *key >= max_end_seq { + // All func data from min_start_seq to max_end_seq have been found. + // And the key is not included. + break; + } else { + max_end_seq = std::cmp::max(max_end_seq, value.1); + } + // println!( + // "seq ({}, {}), max end seq: {}, max_ts: {}", + // *key, value.1, max_end_seq, value.2 + // ); + keys.push(*key); + } + + for key in keys { + let val = bm.remove(&key); + if let Some(v) = val { + funcs.extend(v.0); + } + } + // println!("funcs len is {}", funcs.len()); + funcs +} + +impl Sock { + pub fn new(ap: addr_pair, recv: bool) -> Sock { + Sock { + ap: ap, + max_send_seq: 0, + max_recv_seq: 0, + send: BTreeMap::new(), + recv: BTreeMap::new(), + seq: Vec::default(), + recv_path: recv, + } + } + + // 1. get avaliable max ts. + // 2. Call the bm_group function to get the func list, + // the func in the list meets the maximum timestamp less than tmp_max_ts, + // and the maximum end seq no longer appears in subsequent func. + // 3. Call push_skb, pending subsequent processing. + pub fn group_funcs(&mut self, max_ts: u64) -> Vec> { + if self.recv_path { + bm_group(&mut self.recv, max_ts) + } else { + bm_group(&mut self.send, max_ts) + } + } + + // 1. insert func into btreemap + // 2. update max ts + // 2. try to build one skb + pub fn push_func(&mut self, func: Func) { + let rc_func = Rc::new(func); + if self.recv_path { + let (rseq, rend_seq) = rc_func.get_rseq(); + bm_insert(&mut self.recv, (rseq, rend_seq), rc_func.clone()); + } else { + let (seq, end_seq) = rc_func.get_seq(); + bm_insert(&mut self.send, (seq, end_seq), rc_func.clone()); + } + } + + pub fn get_ap(&self) -> addr_pair { + self.ap + } + + pub fn get_rap(&self) -> addr_pair { + addr_pair { + saddr: self.ap.daddr, + sport: u16::from_be(self.ap.dport), + daddr: self.ap.saddr, + dport: self.ap.sport.to_be(), + } + } +} + +#[cfg(test)] +mod sock_tests { + use super::*; + + #[test] + fn bm_test() {} +} diff --git a/source/tools/detect/net_diag/rtrace/rtrace-parser/src/utils.rs b/source/tools/detect/net_diag/rtrace/rtrace-parser/src/utils.rs new file mode 100644 index 00000000..e49e1a6c --- /dev/null +++ b/source/tools/detect/net_diag/rtrace/rtrace-parser/src/utils.rs @@ -0,0 +1,92 @@ +use anyhow::anyhow; +use anyhow::Result; +use std::ffi::{CStr, CString}; +use std::os::raw::c_char; +use std::path::Path; +use nix::libc; + +pub fn str_to_cstring(s: &str) -> Result { + CString::new(s).map_err(|e| anyhow!(e.to_string())) +} + +pub fn path_to_cstring>(path: P) -> Result { + let path_str = path + .as_ref() + .to_str() + .ok_or_else(|| anyhow!(format!("{} is not valid unicode", path.as_ref().display())))?; + + str_to_cstring(path_str) +} + +pub fn c_ptr_to_string(p: *const c_char) -> Result { + if p.is_null() { + return Err(anyhow!("Null string".to_owned())); + } + + let c_str = unsafe { CStr::from_ptr(p) }; + Ok(c_str + .to_str() + .map_err(|e| anyhow!(e.to_string()))? + .to_owned()) +} + +pub fn get_timestamp() -> u64 { + let mut ts = libc::timespec { + tv_sec: 0, + tv_nsec: 0, + }; + unsafe { + libc::clock_gettime(libc::CLOCK_MONOTONIC, &mut ts); + } + (ts.tv_sec as u64) * 1000000000 + (ts.tv_nsec as u64) +} + +pub fn get_index_table(row: usize) -> Vec> { + let mut index_table = Vec::new(); + + for i in 0..row { + let mut tmp = Vec::new(); + for j in 0..row { + tmp.push(0); + } + index_table.push(tmp); + } + // 4tt + // 0 7 8 + // 1 6 9 + // 2 5 10 + // 3 4 + let mut index = 0; + for i in 0..row { + let add1 = (row * 2 - 1) - i * 2; + let add2 = row * 2 - add1; + let mut switch = false; + for j in 0..row { + if j == 0 { + index = i; + } else { + if switch { + index += add1; + } else { + index += add2; + } + } + switch = !switch; + index_table[i][j] = index; + } + } + index_table +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_get_index_table() { + let table = get_index_table(2); + println!("{:#?}", table); + let table = get_index_table(5); + println!("{:#?}", table); + } +} diff --git a/source/tools/detect/net_diag/rtrace/rtrace-rs/Cargo.toml b/source/tools/detect/net_diag/rtrace/rtrace-rs/Cargo.toml new file mode 100644 index 00000000..b061d5ac --- /dev/null +++ b/source/tools/detect/net_diag/rtrace/rtrace-rs/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "rtrace-rs" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +libbpf-sys = { git = "https://github.com/chengshuyi/libbpf-sys" } +anyhow = "1.0" +serde = "1.0" +serde_derive = "1.0.32" +toml = "0.5" +gdb = "0.1.0" +regex = "1" +once_cell = "1.8.0" +tree-sitter = "0.20.3" +tree-sitter-c = "0.20.1" +log = "0.4.14" diff --git a/source/tools/detect/net_diag/rtrace/rtrace-rs/README.md b/source/tools/detect/net_diag/rtrace/rtrace-rs/README.md new file mode 100644 index 00000000..a786515c --- /dev/null +++ b/source/tools/detect/net_diag/rtrace/rtrace-rs/README.md @@ -0,0 +1,3 @@ +# rtrace-rs + +rtrace-rs is the rust package of the rtrace ebpf library. On this basis, it provides four core functions, namely dynamic tracing, builtin parameters, dynamic parameters and filtering. \ No newline at end of file diff --git a/source/tools/detect/net_diag/rtrace/rtrace-rs/build.rs b/source/tools/detect/net_diag/rtrace/rtrace-rs/build.rs new file mode 100644 index 00000000..df678c2e --- /dev/null +++ b/source/tools/detect/net_diag/rtrace/rtrace-rs/build.rs @@ -0,0 +1,12 @@ +use std::path::PathBuf; +fn main() { + let libpath = PathBuf::from(env!("OBJ_LIB_PATH")); + let mut librtrace_path = libpath.clone(); + librtrace_path.push("librtrace.a"); + + println!("cargo:rerun-if-changed={}", librtrace_path.display()); + + println!("cargo:rustc-link-search={}", libpath.display()); + println!("cargo:rustc-link-lib=static=rtrace"); +} + \ No newline at end of file diff --git a/source/tools/detect/net_diag/rtrace/rtrace-rs/src/bindings.rs b/source/tools/detect/net_diag/rtrace/rtrace-rs/src/bindings.rs new file mode 100644 index 00000000..879a5a4a --- /dev/null +++ b/source/tools/detect/net_diag/rtrace/rtrace-rs/src/bindings.rs @@ -0,0 +1,261 @@ +use anyhow::anyhow; +use anyhow::Result; +use libbpf_sys::{bpf_insn, bpf_program, btf, size_t}; +use std::net::{Ipv4Addr, SocketAddrV4}; +use std::os::raw::{c_char, c_int}; + +#[derive(PartialEq, Debug, Copy, Clone)] +#[repr(u32)] +#[allow(non_camel_case_types)] +pub enum Protocol { + IPPROTO_ICMP = 1, + IPPROTO_TCP = 6, + IPPROTO_UDP = 17, + + IPPROTO_TCP_SYN = (1 << 8) + 6, +} + +impl Protocol { + pub fn from_string(protocol: &String) -> Result { + match &protocol[..] { + "icmp" => Ok(Protocol::IPPROTO_ICMP), + "tcp" => Ok(Protocol::IPPROTO_TCP), + "udp" => Ok(Protocol::IPPROTO_UDP), + "tcp-syn" => Ok(Protocol::IPPROTO_TCP_SYN), + _ => Err(anyhow!("could not parse protocol type")), + } + } + + pub fn into_str(ty: &Protocol) -> &str { + match ty { + Protocol::IPPROTO_ICMP => "icmp", + Protocol::IPPROTO_TCP => "tcp", + Protocol::IPPROTO_UDP => "udp", + Protocol::IPPROTO_TCP_SYN => "tcp-syn", + _ => "unknown", + } + } +} + +#[allow(non_camel_case_types)] +#[derive(PartialEq, Eq, Hash, Copy, Clone, PartialOrd, Ord, Debug)] +#[repr(C)] +pub struct addr_pair { + pub saddr: u32, + pub daddr: u32, + pub sport: u16, + pub dport: u16, +} + +impl addr_pair { + pub fn from_string(src: &String, dst: &String) -> Result { + let s: SocketAddrV4 = src.parse()?; + let d: SocketAddrV4 = dst.parse()?; + Ok(addr_pair { + saddr: u32::from_le_bytes(s.ip().octets()), + daddr: u32::from_le_bytes(d.ip().octets()), + sport: s.port(), + dport: d.port(), + }) + } + + pub fn into_string(&self) -> String { + format!( + "{} - {}", + SocketAddrV4::new(Ipv4Addr::from(u32::from_be(self.saddr)), self.sport), + SocketAddrV4::new( + Ipv4Addr::from(u32::from_be(self.daddr)), + self.dport + ) + ) + } +} + +#[allow(non_camel_case_types)] +#[derive(Default, Copy, Clone, Debug)] +#[repr(C)] +pub struct pid_info { + pub pid: u32, + pub comm: [u8; 16], +} + +#[allow(non_camel_case_types)] +#[repr(C)] +#[derive(Debug)] +pub struct BASIC_INFO_struct { + pub mask: u64, + pub ip: u64, + pub ts: u64, + pub seq: u32, + pub end_seq: u32, + pub rseq: u32, + pub rend_seq: u32, + pub ap: addr_pair, + pub pi: pid_info, + pub ret: u64, +} + +#[allow(non_camel_case_types)] +#[derive(Debug, Default, Copy, Clone)] +#[repr(C)] +pub struct CGROUP_struct { + pub inum: u32, + __pad_4: [u8; 4], + pub cgroupid: u64, +} + +#[allow(non_camel_case_types)] +#[derive(Default, Copy, Clone)] +#[repr(C)] +pub struct STACK_struct { + pub kern_stack: [u64; 5], +} + +#[allow(non_camel_case_types)] +#[derive(PartialEq, Debug, Copy, Clone)] +#[repr(u32)] +pub enum INFO_TYPE { + BASIC_INFO = 0, + CGROUP, + STACK, + KRETPROBE, // Get the return parameter of the function + LINEPROBE, + ENUM_END, +} + +impl INFO_TYPE { + pub fn from_string(string: &String) -> Result { + match string.as_str() { + "basic" => Ok(INFO_TYPE::BASIC_INFO), + "cgroup" => Ok(INFO_TYPE::CGROUP), + "stack" => Ok(INFO_TYPE::STACK), + "kretprobe" => Ok(INFO_TYPE::KRETPROBE), + "lineprobe" => Ok(INFO_TYPE::LINEPROBE), + _ => Err(anyhow!("{} -> INFO_TYPE not support", string)), + } + } + + pub fn from_u32(value: u32) -> INFO_TYPE { + match value { + 0 => INFO_TYPE::BASIC_INFO, + 1 => INFO_TYPE::CGROUP, + 2 => INFO_TYPE::STACK, + 3 => INFO_TYPE::KRETPROBE, + 4 => INFO_TYPE::LINEPROBE, + 5 => INFO_TYPE::ENUM_END, + _ => panic!("Unknown value: {}", value), + } + } + + pub fn get_size(&self) -> usize { + let sz; + match self { + INFO_TYPE::BASIC_INFO => sz = std::mem::size_of::(), + INFO_TYPE::CGROUP => sz = std::mem::size_of::(), + INFO_TYPE::STACK => sz = std::mem::size_of::(), + INFO_TYPE::KRETPROBE => sz = std::mem::size_of::(), + INFO_TYPE::LINEPROBE => sz = std::mem::size_of::(), + _ => sz = 0, + } + sz + } +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct filter_meta { + pub pid: c_int, + pub ap: addr_pair, +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct filter_params { + pub fm: [filter_meta; 10usize], + pub protocol: u32, + pub cnt: c_int, +} + +#[repr(C)] +#[derive(Debug, Copy, Clone, Default)] +pub struct dynamic_offsets { + pub offs: [c_int; 10usize], + pub cnt: c_int, + pub arg: c_int, + pub size: c_int, +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct dynamic_fields { + pub ident: *mut c_char, + pub cast_name: *mut c_char, + pub cast_type: c_int, + pub index: c_int, + pub pointer: c_int, +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct rtrace { + _unused: [u8; 0], +} + +extern "C" { + pub fn rtrace_dynamic_gen_offset( + r: *mut rtrace, + df: *mut dynamic_fields, + df_cnt: c_int, + func_proto_id: c_int, + dos: *mut dynamic_offsets, + ) -> c_int; +} + +extern "C" { + pub fn rtrace_dynamic_gen_insns( + r: *mut rtrace, + dos: *mut dynamic_offsets, + insns: *mut bpf_insn, + cd_off: c_int, + ) -> c_int; +} + +extern "C" { + pub fn rtrace_dynamic_btf(r: *mut rtrace) -> *mut btf; +} + +extern "C" { + pub fn rtrace_alloc_and_init( + pin_path: *mut ::std::os::raw::c_char, + btf_custom_path: *mut ::std::os::raw::c_char, + ) -> *mut rtrace; +} + +extern "C" { + pub fn rtrace_perf_map_fd(r: *mut rtrace) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn rtrace_filter_map_fd(r: *mut rtrace) -> ::std::os::raw::c_int; +} + +extern "C" { + pub fn rtrace_trace_load_prog( + r: *mut rtrace, + prog: *const bpf_program, + insns: *const bpf_insn, + insns_cnt: size_t, + ) -> c_int; +} + +extern "C" { + pub fn rtrace_trace_program( + r: *mut rtrace, + func: *const c_char, + sk: c_int, + skb: c_int, + ) -> *mut bpf_program; +} + +extern "C" { + pub fn rtrace_set_debug(debug: bool); +} diff --git a/source/tools/detect/net_diag/rtrace/rtrace-rs/src/builtin/builtin.rs b/source/tools/detect/net_diag/rtrace/rtrace-rs/src/builtin/builtin.rs new file mode 100644 index 00000000..25813032 --- /dev/null +++ b/source/tools/detect/net_diag/rtrace/rtrace-rs/src/builtin/builtin.rs @@ -0,0 +1,56 @@ +use anyhow::Result; + +use crate::bindings::*; +use crate::rtrace::Function; + +/// Builtin paramerters module +/// +/// Use this structure to convert the built-in parameter list +/// to a 64-bit mask. +pub struct Builtin { + mask: u64, +} + +impl Builtin { + /// + pub fn new(function: &Function) -> Result { + let mut mask = 0; + for param in &function.params { + mask |= 1 << (INFO_TYPE::from_string(param)? as u64); + } + // LINEPROBE parameter type is implicit + if let Some(_) = &function.lines { + mask |= 1 << (INFO_TYPE::LINEPROBE as u64); + } + + Ok(Builtin { mask: mask }) + } + + /// Get built-in parameter mask. + pub fn get_mask(&self) -> u64 { + self.mask + } + + /// Whether the built-in parameter list contains the KRETPROBE type. + pub fn has_kretprobe(&self) -> bool { + (self.mask & (1 << INFO_TYPE::KRETPROBE as u64)) != 0 + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_builtin_basic() { + let text = r#" + name = "test" + params = ["basic", "kretprobe"] + "#; + let function = Function::from_str(text).unwrap(); + let b = Builtin::new(&function).unwrap(); + + assert_eq!(b.get_mask(), (1 << 0) | (1 << 3)); + assert_eq!(b.has_kretprobe(), true); + } +} diff --git a/source/tools/detect/net_diag/rtrace/rtrace-rs/src/builtin/mod.rs b/source/tools/detect/net_diag/rtrace/rtrace-rs/src/builtin/mod.rs new file mode 100644 index 00000000..afbf8e21 --- /dev/null +++ b/source/tools/detect/net_diag/rtrace/rtrace-rs/src/builtin/mod.rs @@ -0,0 +1 @@ +pub mod builtin; \ No newline at end of file diff --git a/source/tools/detect/net_diag/rtrace/rtrace-rs/src/dynamic/dynamic.rs b/source/tools/detect/net_diag/rtrace/rtrace-rs/src/dynamic/dynamic.rs new file mode 100644 index 00000000..04e11c05 --- /dev/null +++ b/source/tools/detect/net_diag/rtrace/rtrace-rs/src/dynamic/dynamic.rs @@ -0,0 +1,73 @@ +use crate::bindings::*; +use crate::dynamic::offset::Offset; +use crate::dynamic::parser::Parser; +use crate::rtrace::Function; +use anyhow::anyhow; +use anyhow::Result; +use libbpf_sys::{bpf_insn, btf, btf_type}; +use std::ffi::CString; +use log::*; + +pub struct Dynamic { + func: CString, + exprs: Vec, + + sz: Vec, +} + +impl Dynamic { + pub fn new(function: &Function) -> Result { + let mut exprs = Vec::new(); + if let Some(xs) = &function.exprs { + exprs = xs.clone(); + } + + Ok(Dynamic { + func: CString::new(function.name.clone())?, + exprs, + sz: Vec::new(), + }) + } + + pub fn codegen(&mut self, r: *mut rtrace, cd_off: i32) -> Result> { + let mut insns = vec![bpf_insn::default(); 4096usize]; + let btf = unsafe { rtrace_dynamic_btf(r) }; + let func_id = unsafe { + libbpf_sys::btf__find_by_name_kind(btf, self.func.as_ptr(), libbpf_sys::BTF_KIND_FUNC) + }; + if func_id <= 0 { + return Err(anyhow!("unable to find function: {:?} in btf", self.func)); + } + let bt = unsafe { libbpf_sys::btf__type_by_id(btf, func_id as u32) }; + let func_proto_id = unsafe { (*bt).__bindgen_anon_1.type_ }; + let mut p = Parser::new(); + let mut o = Offset::new(r); + let mut insns_cnt = 0; + + for expr in &self.exprs { + debug!("expr: {}", expr); + let fields = p.parse(expr)?; + debug!("fields: {:?}", fields); + let mut offsets = o.parse(func_proto_id, &fields)?; + self.sz.push(offsets.size as u8); + debug!("expr size: {}", offsets.size); + let ret = + unsafe { rtrace_dynamic_gen_insns(r, &mut offsets, &mut insns[insns_cnt], cd_off) }; + if ret <= 0 { + return Err(anyhow!("failed to generate insns")); + } + + insns_cnt += ret as usize; + } + insns.resize(insns_cnt, bpf_insn::default()); + Ok(insns) + } + + pub fn get_sz(&self) -> &Vec{ + &self.sz + } + + pub fn get_exprs(&self) -> &Vec{ + &self.exprs + } +} diff --git a/source/tools/detect/net_diag/rtrace/rtrace-rs/src/dynamic/mod.rs b/source/tools/detect/net_diag/rtrace/rtrace-rs/src/dynamic/mod.rs new file mode 100644 index 00000000..52d80c6c --- /dev/null +++ b/source/tools/detect/net_diag/rtrace/rtrace-rs/src/dynamic/mod.rs @@ -0,0 +1,4 @@ +pub mod dynamic; + +pub mod parser; +pub mod offset; \ No newline at end of file diff --git a/source/tools/detect/net_diag/rtrace/rtrace-rs/src/dynamic/offset.rs b/source/tools/detect/net_diag/rtrace/rtrace-rs/src/dynamic/offset.rs new file mode 100644 index 00000000..ff5ce096 --- /dev/null +++ b/source/tools/detect/net_diag/rtrace/rtrace-rs/src/dynamic/offset.rs @@ -0,0 +1,67 @@ +use crate::bindings::*; +use crate::dynamic::parser::{CastType, Field, Parser}; +use anyhow::anyhow; +use anyhow::Result; +use std::ffi::CString; +use std::os::raw::c_char; + +pub struct OffsetInfo { + offs: Vec, + arg: i32, //position + size: i32, +} + +pub struct Offset { + r: *mut rtrace, +} + +impl Offset { + pub fn new(r: *mut rtrace) -> Offset { + Offset { r } + } + + pub fn parse(&mut self, func_proto_id: u32, fields: &Vec) -> Result { + let mut dos = dynamic_offsets::default(); + let mut dfs = Vec::new(); + for field in fields { + let mut cast_name_ptr = std::ptr::null_mut(); + let mut pointer = 0; + let mut index = -1; + let mut cast_type = 0; + if let Some(cast) = &field.cast { + match &cast.ct { + CastType::Struct(name) => { + cast_name_ptr = name.as_ptr() as *mut c_char; + cast_type = libbpf_sys::BTF_KIND_STRUCT; + } + _ => return Err(anyhow!("CastType: {:?} not support", cast)), + } + pointer = cast.pointer; + } + if let Some(x) = field.index { + index = x; + } + dfs.push(dynamic_fields { + ident: field.ident.as_ptr() as *mut c_char, + cast_name: cast_name_ptr, + cast_type: cast_type as i32, + index: index, + pointer: pointer, + }); + } + let ret = unsafe { + rtrace_dynamic_gen_offset( + self.r, + dfs.as_ptr() as *mut dynamic_fields, + dfs.len() as i32, + func_proto_id as i32, + &mut dos, + ) + }; + + if ret != 0 { + return Err(anyhow!("rtrace dynamic gen offsets failed: err {}", ret)); + } + Ok(dos) + } +} diff --git a/source/tools/detect/net_diag/rtrace/rtrace-rs/src/dynamic/parser.rs b/source/tools/detect/net_diag/rtrace/rtrace-rs/src/dynamic/parser.rs new file mode 100644 index 00000000..079c82e0 --- /dev/null +++ b/source/tools/detect/net_diag/rtrace/rtrace-rs/src/dynamic/parser.rs @@ -0,0 +1,223 @@ +use anyhow::anyhow; +use anyhow::Result; +use std::ops::Range; +use tree_sitter; +use tree_sitter_c; +use std::ffi::CString; + +#[derive(PartialEq, Debug, Clone)] +#[repr(u32)] +#[allow(non_camel_case_types)] +enum Ast { + identifier = 1, + lparen = 5, + rparen = 8, + star = 23, + semi = 39, + lbrack = 61, + rbrack = 62, + struct_ = 78, + dot = 107, + number_literal = 109, + translation_unit = 128, + abstract_pointer_declarator = 178, + struct_specifier = 195, + expression_statement = 208, + cast_expression = 227, + type_descriptor = 228, + subscript_expression = 230, + field_expression = 233, + parenthesized_expression = 235, + field_identifier = 267, + type_identifier = 269, +} + +impl Ast { + pub fn from_u16(val: u16) -> Result { + match val { + 1 => Ok(Ast::identifier), + 5 => Ok(Ast::lparen), + 8 => Ok(Ast::rparen), + 23 => Ok(Ast::star), + 39 => Ok(Ast::semi), + 61 => Ok(Ast::lbrack), + 62 => Ok(Ast::rbrack), + 78 => Ok(Ast::struct_), + 107 => Ok(Ast::dot), + 109 => Ok(Ast::number_literal), + 128 => Ok(Ast::translation_unit), + 178 => Ok(Ast::abstract_pointer_declarator), + 195 => Ok(Ast::struct_specifier), + 208 => Ok(Ast::expression_statement), + 227 => Ok(Ast::cast_expression), + 228 => Ok(Ast::type_descriptor), + 230 => Ok(Ast::subscript_expression), + 233 => Ok(Ast::field_expression), + 235 => Ok(Ast::parenthesized_expression), + 267 => Ok(Ast::field_identifier), + 269 => Ok(Ast::type_identifier), + _ => Err(anyhow!("unable to transmit u16({}) to Ast", val)), + } + } +} + +#[derive(PartialEq, Debug, Clone)] +pub enum CastType { + Struct(CString), + Invalid, +} + +#[derive(Debug, Clone)] +pub struct Cast { + pub ct: CastType, + pub pointer: i32, +} + +#[derive(Debug, Clone)] +pub struct Field { + pub ident: CString, + pub cast: Option, + pub index: Option, +} + +#[derive(Debug, Clone)] +pub struct Parser { + fields: Vec, + // parser context + expr: String, + cast: Cast, +} + +impl Parser { + pub fn new() -> Parser { + Parser { + fields: Vec::new(), + expr: String::default(), + cast: Cast { + ct: CastType::Invalid, + pointer: 0, + }, + } + } + + pub fn parse(&mut self, expr: &String) -> Result>{ + self.fields.clear(); + self.expr = expr.clone(); + self.cast.ct = CastType::Invalid; + self.cast.pointer = 0; + + let mut parser = tree_sitter::Parser::new(); + parser + .set_language(tree_sitter_c::language()) + .expect("Error loading C grammar"); + if let Some(parsed) = parser.parse(expr, None) { + let walker = parsed.walk(); + self.visitor(walker.node())?; + } + + Ok(self.fields.clone()) + } + + fn range_to_str(&self, range: Range) -> CString { + CString::new(self.expr[range].to_owned()).expect("CString new failed") + } + + fn visitor(&mut self, node: tree_sitter::Node) -> Result<()> { + for child in 0..node.child_count() { + let cd = node.child(child); + if let Some(x) = cd { + self.visitor(x)?; + } + } + + let ty = Ast::from_u16(node.kind_id())?; + match ty { + Ast::number_literal => { + let mut index: Option = None; + if let Some(_) = self.fields.last_mut() { + index = Some(self.range_to_str(node.byte_range()).into_string().expect("CString new failed").parse()?); + } + if let Some(item) = self.fields.last_mut() { + item.index = index; + } + } + Ast::identifier => self.fields.push(Field { + ident: self.range_to_str(node.byte_range()), + cast: None, + index: None, + }), + Ast::field_identifier => self.fields.push(Field { + ident: self.range_to_str(node.byte_range()), + cast: None, + index: None, + }), + Ast::cast_expression => { + if let Some(item) = self.fields.last_mut() { + item.cast = Some(self.cast.clone()); + } + self.cast.ct = CastType::Invalid; + self.cast.pointer = 0; + } + Ast::struct_ => self.cast.ct = CastType::Struct(CString::default()), + Ast::type_identifier => match &self.cast.ct { + CastType::Struct(_) => { + self.cast.ct = CastType::Struct(self.range_to_str(node.byte_range())) + } + CastType::Invalid => { + return Err(anyhow!( + "unknow cast type for type_identifier: {:?}", + self.range_to_str(node.byte_range()) + )) + } + }, + Ast::abstract_pointer_declarator => self.cast.pointer += 1, + _ => {} + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_basic() { + let code = "a.b.c;".to_owned(); + let p = Parser::new(); + println!("{:?}", p); + } + + #[test] + fn test_array_basic1() { + let code = "a.b[2].c;".to_owned(); + let p = Parser::new(); + println!("{:?}", p); + } + #[test] + fn test_array_basic2() { + let code = "a[2].b.c;".to_owned(); + let p = Parser::new(); + println!("{:?}", p); + } + #[test] + fn test_array_basic3() { + let code = "a.b.c[2];".to_owned(); + let p = Parser::new(); + println!("{:?}", p); + } + + #[test] + fn test_cast_basic1() { + let code = "((struct d *)a).b.c;".to_owned(); + let p = Parser::new(); + println!("{:?}", p); + } + + #[test] + fn test_cast_basic2() { + let code = "((struct d *)a.b).c;".to_owned(); + let p = Parser::new(); + println!("{:?}", p); + } +} diff --git a/source/tools/detect/net_diag/rtrace/rtrace-rs/src/filter/filter.rs b/source/tools/detect/net_diag/rtrace/rtrace-rs/src/filter/filter.rs new file mode 100644 index 00000000..2eed7cf4 --- /dev/null +++ b/source/tools/detect/net_diag/rtrace/rtrace-rs/src/filter/filter.rs @@ -0,0 +1,66 @@ +use crate::bindings::*; +use crate::rtrace::Filterx; +use anyhow::anyhow; +use anyhow::Result; +use libbpf_sys::*; +use log::*; +use std::os::raw::{c_int, c_void}; + +/// filter module +/// +/// Used to process filter maps in eBPF programs. +pub struct Filter { + fd: i32, + key: i32, +} + +impl Filter { + pub fn new(fd: i32) -> Filter { + // value of default key of Filter is 0 + Filter { fd, key: 0 } + } + + fn raw_update(&self, key: *const c_void, value: *const c_void) -> Result<()> { + let ret = unsafe { bpf_map_update_elem(self.fd, key, value, BPF_ANY as u64) }; + if ret < 0 { + return Err(anyhow!("update err, errno: {}", ret)); + } + Ok(()) + } + + pub fn update(&self, filters: &Vec, protocol: Protocol) -> Result<()> { + let zero_fm = filter_meta { + pid: 0, + ap: addr_pair { + saddr: 0, + daddr: 0, + sport: 0, + dport: 0, + }, + }; + let mut tmp_filter_metas = [zero_fm; 10usize]; + debug!("protocol: {:?}", protocol); + for (idx, filter) in filters.iter().enumerate() { + tmp_filter_metas[idx] = filter_meta { + pid: filter.pid as i32, + ap: addr_pair::from_string(&filter.src, &filter.dst)?, + }; + debug!( + "pid: {}, ap: {}", + tmp_filter_metas[idx].pid, + tmp_filter_metas[idx].ap.into_string() + ); + } + + let fp = filter_params { + protocol: protocol as u32, + cnt: filters.len() as i32, + fm: tmp_filter_metas, + }; + + self.raw_update( + &self.key as *const c_int as *const c_void, + &fp as *const filter_params as *const c_void, + ) + } +} diff --git a/source/tools/detect/net_diag/rtrace/rtrace-rs/src/filter/mod.rs b/source/tools/detect/net_diag/rtrace/rtrace-rs/src/filter/mod.rs new file mode 100644 index 00000000..3a1545df --- /dev/null +++ b/source/tools/detect/net_diag/rtrace/rtrace-rs/src/filter/mod.rs @@ -0,0 +1 @@ +pub mod filter; \ No newline at end of file diff --git a/source/tools/detect/net_diag/rtrace/rtrace-rs/src/lib.rs b/source/tools/detect/net_diag/rtrace/rtrace-rs/src/lib.rs new file mode 100644 index 00000000..ce27af50 --- /dev/null +++ b/source/tools/detect/net_diag/rtrace/rtrace-rs/src/lib.rs @@ -0,0 +1,24 @@ +//! # rtrace-rs +//! +//! +//! +//! ## High level workflow +//! +//! +//! ## Alternate workflow +//! +//! ## Design +//! +//! +//! ## Example +//! +//! + +pub mod bindings; +pub mod builtin; +pub mod dynamic; +pub mod filter; +pub mod rtrace; +pub mod trace; +pub mod utils; + diff --git a/source/tools/detect/net_diag/rtrace/rtrace-rs/src/rtrace.rs b/source/tools/detect/net_diag/rtrace/rtrace-rs/src/rtrace.rs new file mode 100644 index 00000000..56ab3ed7 --- /dev/null +++ b/source/tools/detect/net_diag/rtrace/rtrace-rs/src/rtrace.rs @@ -0,0 +1,416 @@ +use crate::bindings::*; +use crate::builtin::builtin::Builtin; +use crate::dynamic::dynamic::Dynamic; +use crate::filter::filter::Filter; +use crate::trace::prog::Prog; +use crate::trace::trace::Trace; +use crate::utils::gdb::Gdb; +use anyhow::anyhow; +use anyhow::Result; +use serde_derive::Deserialize; +use std::collections::HashMap; +use std::ffi::CString; +use std::os::raw::{c_char, c_int, c_void}; +use std::path::PathBuf; +use toml; + +#[derive(Debug, Deserialize)] +pub struct Basic { + pub debug: bool, + pub btf_path: Option, + pub pin_path: Option, + pub vmlinux: Option, + pub ksyms: Option, + pub duration: usize, + pub protocol: String, + pub recv: bool, +} + +#[derive(Debug, Deserialize)] +pub struct Filterx { + pub pid: usize, + pub dst: String, + pub src: String, +} + +#[derive(Clone, Debug, Deserialize)] +pub struct Function { + pub name: String, + pub enable: Option, + pub sk: Option, + pub skb: Option, + pub params: Vec, + pub exprs: Option>, + pub lines: Option>, + + offsets: Option>, +} +// see: https://github.com/alexcrichton/toml-rs/issues/395 +#[derive(Clone, Debug, Deserialize)] +struct FunctionContainer { + function: Vec, +} + +impl Function { + pub fn from_str(s: &str) -> Result { + match toml::from_str(s) { + Ok(x) => Ok(x), + Err(y) => Err(anyhow!("str to Function failed: {}", y)), + } + } +} + +#[derive(Debug, Deserialize)] +pub struct Config { + pub basic: Basic, + pub filter: Option>, + pub function: Option>, +} + +impl Config { + pub fn from_str(s: &str) -> Result { + match toml::from_str(s) { + Ok(x) => Ok(x), + Err(_) => Err(anyhow!("str to Config failed")), + } + } +} + +pub struct Probe { + builtin: Builtin, + trace: Trace, + dynamic: Dynamic, +} + +impl Probe { + + pub fn get_exprs(&self) -> &Vec { + self.dynamic.get_exprs() + } + + pub fn get_expr_sz(&self) -> &Vec { + self.dynamic.get_sz() + } +} + +pub struct Rtrace { + ptr: *mut rtrace, + filter: Filter, + probes: HashMap, + progs: HashMap, + config: Option, +} + +impl Rtrace { + pub fn new(btf_path: Option, pin_path: Option) -> Result { + let ptr = Rtrace::init(btf_path, pin_path)?; + let filter_map_fd = unsafe { rtrace_filter_map_fd(ptr) }; + let mut r = Rtrace { + ptr, + filter: Filter::new(filter_map_fd), + probes: HashMap::new(), + progs: HashMap::new(), + config: None, + }; + Ok(r) + } + + fn init(btf_path: Option, pin_path: Option) -> Result<*mut rtrace> { + let mut tmp_btf = CString::default(); + let mut tmp_pin = CString::default(); + let mut tmp_btf_ptr = std::ptr::null_mut(); + let mut tmp_pin_ptr = std::ptr::null_mut(); + if let Some(x) = btf_path { + tmp_btf = CString::new(x.clone())?; + tmp_btf_ptr = tmp_btf.as_ptr() as *mut c_char; + } + + if let Some(x) = pin_path { + tmp_pin = CString::new(x.clone())?; + tmp_pin_ptr = tmp_pin.as_ptr() as *mut c_char; + } + + let ptr = unsafe { rtrace_alloc_and_init(tmp_btf_ptr, tmp_pin_ptr) }; + if ptr == std::ptr::null_mut() { + return Err(anyhow!("unable to open rtrace object")); + } + Ok(ptr) + } + + pub fn insert_prog(&mut self, name: &String, prog: Prog) { + self.progs.insert(name.clone(), prog); + } + + pub fn get_prog(&mut self, name: &String, sk: Option, skb: Option) -> Result { + let mut skv = 0; + let mut skbv = 0; + if let Some(x) = sk { + skv = x; + } + if let Some(x) = skb { + skbv = x; + } + + if let Some(x) = self.progs.remove(name) { + return Ok(x); + } + + let prog = unsafe { + rtrace_trace_program(self.ptr, CString::new(name.clone())?.as_ptr(), skv, skbv) + }; + + if prog == std::ptr::null_mut() { + return Err(anyhow!( + "failed to find bpf program for function: {}, sk-{}, skb-{}", + name, + skv, + skbv + )); + } + Ok(Prog::new(prog)) + } + + pub fn from_file(path: &PathBuf) -> Result { + let contents = + std::fs::read_to_string(path).expect("Something went wrong reading config file"); + Rtrace::from_str(&contents[..]) + } + + pub fn from_str(s: &str) -> Result { + let config: Config = toml::from_str(s).expect("Config file parsed failed"); + unsafe { + rtrace_set_debug(config.basic.debug); + } + let ptr = Rtrace::init(config.basic.btf_path.clone(), config.basic.pin_path.clone())?; + let filter_map_fd = unsafe { rtrace_filter_map_fd(ptr) }; + let r = Rtrace { + ptr, + filter: Filter::new(filter_map_fd), + probes: HashMap::new(), + progs: HashMap::new(), + config: Some(config), + }; + Ok(r) + } + + fn probe_function(&mut self, function: &Function, offsets: Option>) -> Result { + let builtin = Builtin::new(function)?; + let trace = Trace::new(self.ptr, function)?; + let mut dynamic = Dynamic::new(function)?; + + let mut prog = self.get_prog(&function.name, function.sk, function.skb)?; + let kretprog = self.get_prog(&"kretprobe_common".to_owned(), None, None)?; + let lineprog = self.get_prog(&"kprobe_lines".to_owned(), None, None)?; + prog.patch_builtin_insn(builtin.get_mask())?; + if let Some(_) = function.exprs { + prog.patch_dynamic_insn(&dynamic.codegen(self.ptr, prog.cd_off())?)?; + } + trace.attach_kprobe(&prog)?; + if builtin.has_kretprobe() { + trace.attach_kretprobe(&kretprog)?; + } + + if let Some(offs) = &offsets { + trace.attach_lines(&lineprog, offs)?; + if !builtin.has_kretprobe() { + trace.attach_kretprobe(&kretprog)?; // to clear tid_map + } + } + + self.insert_prog(&function.name, prog); + self.insert_prog(&"kretprobe_common".to_owned(), kretprog); + self.insert_prog(&"kprobe_lines".to_owned(), lineprog); + + let p = Probe { + builtin: builtin, + trace: trace, + dynamic: dynamic, + }; + + Ok(p) + } + + fn __probe_functions(&mut self, functions: &Vec) -> Result<()> { + let mut gdb = None; + let mut vmlinux = None; + if let Some(config) = &self.config { + vmlinux = config.basic.vmlinux.clone(); + } + for function in functions { + if let Some(enable) = function.enable { + if enable == false { + continue; + } + } + let mut offsets = None; + if let Some(lines) = &function.lines { + let mut offs = Vec::new(); + for line in lines { + let off = line.parse::(); + match off { + Ok(x) => { + offs.push(x); + continue; + } + _ => {} + } + + match gdb { + None => { + if let Some(x) = &vmlinux { + gdb = Some(Gdb::new(x)?); + } + } + _ => {} + } + if let Some(g) = &mut gdb { + offs.push(g.infoline(line)?); + } + } + offsets = Some(offs); + } + let p = self.probe_function(function, offsets)?; + self.probes.entry(function.name.clone()).or_insert(p); + } + Ok(()) + } + + pub fn probe_functions(&mut self) -> Result<()> { + let mut funtions = None; + if let Some(config) = &self.config { + if let Some(funcs) = &config.function { + funtions = Some(funcs.clone()); + } + } + + if let Some(x) = &funtions { + self.__probe_functions(x)?; + } + Ok(()) + } + + pub fn probe_functions_from_str(&mut self, s: &str) -> Result<()> { + let functions: FunctionContainer = toml::from_str(s).expect("functions str parsed failed"); + self.__probe_functions(&functions.function)?; + Ok(()) + } + + pub fn get_probe(&self, func: &String) -> Option<&Probe> { + if self.probes.contains_key(func) { + return Some(&self.probes[func]); + } + None + } + + pub fn probe_filter(&mut self) -> Result<()> { + if let Some(config) = &self.config { + if let Some(filter) = &config.filter { + self.filter + .update(&filter, Protocol::from_string(&config.basic.protocol)?)?; + } + } + Ok(()) + } + + pub fn probe_filter_from_str(&mut self, protocol: Protocol, s: &str) -> Result<()> { + let filters: Vec = toml::from_str(s).expect("filter str parsed failed"); + self.filter.update(&filters, protocol) + } + + pub fn perf_fd(&self) -> i32 { + unsafe { rtrace_perf_map_fd(self.ptr) as i32 } + } + + pub fn protocol(&self) -> Result { + if let Some(config) = &self.config { + return Protocol::from_string(&config.basic.protocol); + } + Err(anyhow!("Please specified config info")) + } + + pub fn is_recv(&self) -> bool { + if let Some(config) = &self.config { + return config.basic.recv; + } + false + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_rtrace_from_str() { + let text = r#" + [basic] + debug = false + btf_path = "/boot/vmlinux-4.19.91-007.ali4000.alios7.x86_64" + duration = 0 + protocol = "tcp" + "#; + let r = Rtrace::from_str(text).unwrap(); + } + + #[test] + fn test_rtrace_probe_functions() { + let text = r#" + [basic] + debug = false + btf_path = "/boot/vmlinux-4.19.91-007.ali4000.alios7.x86_64" + duration = 0 + protocol = "tcp" + + [[filter]] + pid = 0 + dst = "0.0.0.0:0" + src = "0.0.0.0:0" + + [[function]] + name = "__ip_queue_xmit" + enable = true + params = ["basic", "stack", "kretprobe"] + + [[function]] + name = "dev_hard_start_xmit" + enable = true + params = ["basic"] + + [[function]] + name = "__netif_receive_skb_core" + enable = true + params = ["basic"] + + [[function]] + name = "tcp_rcv_state_process" + enable = true + params = ["basic"] + "#; + let mut r = Rtrace::from_str(text).unwrap(); + r.probe_functions().unwrap(); + } + + #[test] + fn test_probe_functions_from_str_basic1() { + let text = r#" + [[function]] + name = "tcp_rcv_state_process" + enable = true + params = ["basic"] + "#; + let mut r = Rtrace::new(None, None).unwrap(); + r.probe_functions_from_str(text).unwrap(); + } + + #[test] + fn test_probe_functions_from_str_basic2() { + let text = r#" + [[function]] + name = "tcp_rcv_state_process" + enable = true + params = ["basic"] + expr = ["skb.data"] + "#; + let mut r = Rtrace::new(None, None).unwrap(); + r.probe_functions_from_str(text).unwrap(); + } +} diff --git a/source/tools/detect/net_diag/rtrace/rtrace-rs/src/trace/mod.rs b/source/tools/detect/net_diag/rtrace/rtrace-rs/src/trace/mod.rs new file mode 100644 index 00000000..f0921f85 --- /dev/null +++ b/source/tools/detect/net_diag/rtrace/rtrace-rs/src/trace/mod.rs @@ -0,0 +1,2 @@ +pub mod trace; +pub mod prog; \ No newline at end of file diff --git a/source/tools/detect/net_diag/rtrace/rtrace-rs/src/trace/prog.rs b/source/tools/detect/net_diag/rtrace/rtrace-rs/src/trace/prog.rs new file mode 100755 index 00000000..29d5a05c --- /dev/null +++ b/source/tools/detect/net_diag/rtrace/rtrace-rs/src/trace/prog.rs @@ -0,0 +1,236 @@ +use crate::bindings::*; +use anyhow::anyhow; +use anyhow::Result; +use libbpf_sys::{ + bpf_insn, bpf_link, bpf_program, BPF_ALU64, BPF_CALL, BPF_DW, BPF_EXIT, BPF_IMM, BPF_JMP, + BPF_LD, BPF_MOV, BPF_REG_1, BPF_REG_10, BPF_X, +}; + +pub const INSNS_SPLIT_POS: usize = 18; + +#[derive(Clone, Debug)] +pub struct Prog { + ptr: *const bpf_program, + + builtin_insn_pos: i32, + cd_off: i32, + origin_insns: Vec, + + insns: Vec, + bl: *mut bpf_link, +} + +impl Prog { + pub fn new(ptr: *const bpf_program) -> Prog { + let mut p = Prog { + ptr, + builtin_insn_pos: -1, + cd_off: i32::MAX, + origin_insns: Vec::new(), + insns: Vec::with_capacity(4096), + bl: std::ptr::null_mut(), + }; + p.clone_insns(); + p.builtin_insn_pos = p.find_builtin_insn_pos(); + p.cd_off = p.find_cd_off(); + p + } + + /// delete patched instructions, and reset original status. + pub fn reset(&mut self) { + self.insns.clear(); + } + + fn sys_prog_insns(&self) -> *const bpf_insn { + unsafe { libbpf_sys::bpf_program__insns(self.ptr) } + } + + fn sys_prog_insns_cnt(&self) -> usize { + unsafe { libbpf_sys::bpf_program__insn_cnt(self.ptr) as usize } + } + + pub fn is_double_insn(&self, insn: bpf_insn) -> bool { + insn.code as u32 == (BPF_LD | BPF_IMM | BPF_DW) + } + + fn clone_insns(&mut self) { + let cnt = self.sys_prog_insns_cnt(); + let insns = self.sys_prog_insns(); + + for i in 0..cnt { + unsafe { self.origin_insns.push(*insns.offset(i as isize)) }; + } + } + + fn find_cd_off(&mut self) -> i32 { + // * 1139: (bf) r1 = r10 + // * 1140: (07) r1 += -280 + // * 1141: (bf) r1 = r1 + // * 1142: (b7) r0 = 0 + // * 1143: (95) exit + let tmp_insn = self.origin_insns[self.origin_insns.len() - INSNS_SPLIT_POS]; + if tmp_insn.code == (BPF_ALU64 | BPF_MOV | BPF_X) as u8 + && tmp_insn.dst_reg() == BPF_REG_1 as u8 + && tmp_insn.src_reg() == BPF_REG_10 as u8 + { + return self.origin_insns[self.origin_insns.len() - INSNS_SPLIT_POS + 1].imm; + } + i32::MAX + } + + fn find_builtin_insn_pos(&mut self) -> i32 { + let mut double_insn = false; + for i in 0..self.origin_insns.len() { + if double_insn { + double_insn = false; + continue; + } + // 0: (79) r9 = *(u64 *)(r1 +104) + // 1: (7b) *(u64 *)(r10 -296) = r1 + // 2: (79) r8 = *(u64 *)(r1 +112) + // 3: (7b) *(u64 *)(r10 -32) = r8 + // 4: (b7) r7 = 2184 + // 5: (b7) r6 = 0 + double_insn = self.is_double_insn(self.origin_insns[i]); + // notice: not double insns. + if !double_insn { + let imm = self.origin_insns[i].imm as u64; + if imm == 0x888 { + return i as i32; + } + } + } + -1 + } + + pub fn patch_builtin_insn(&mut self, mask: u64) -> Result<()> { + if self.builtin_insn_pos < 0 { + return Err(anyhow!("unable to find target builtin insn")); + } + self.origin_insns[self.builtin_insn_pos as usize].imm = mask as i32; + Ok(()) + } + + /// Merge the instructions of the ebpf program with the newly + /// generated instructions. + /// + /// 1. copy insns. + /// 1. Calculate the instruction split point based on the previous buried + /// point position. + /// 1. fixup jmp. + /// 1. Merge instructions. + pub fn patch_dynamic_insn(&mut self, insns: &Vec) -> Result<()> { + let mark_off = self.origin_insns.len() - INSNS_SPLIT_POS; + // copy insns + for i in 0..mark_off { + self.insns.push(self.origin_insns[i]); + } + // fixup jmp + for i in 0..self.insns.len() { + let class = self.insns[i].code & 0x07; + let opcode; + + if class != BPF_JMP as u8 { + continue; + } + + opcode = self.insns[i].code & 0xf0; + if opcode == BPF_CALL as u8 || opcode == BPF_EXIT as u8 { + continue; + } + + if self.insns[i].off as usize + i + 1 >= mark_off + 3 { + self.insns[i].off += insns.len() as i16; + } + } + // merge insns + for i in 0..insns.len() { + self.insns.push(insns[i]); + } + // copy left insns + for i in mark_off..self.origin_insns.len() { + self.insns.push(self.origin_insns[i]); + } + // fix err code + for i in 0..self.insns.len() { + let class = self.insns[i].code & 0x07; + let opcode; + + if class != BPF_JMP as u8 { + continue; + } + + opcode = self.insns[i].code & 0xf0; + if opcode == BPF_CALL as u8 || opcode == BPF_EXIT as u8 { + continue; + } + + if self.insns[i].off == 4096 { + self.insns[i].off = (self.insns.len() - 5 - i - 1) as i16; + } + } + Ok(()) + } + + pub fn insns(&self) -> *const bpf_insn { + if self.insns.len() == 0 { + return self.origin_insns.as_ptr() as *const bpf_insn; + } + self.insns.as_ptr() as *const bpf_insn + } + + pub fn insns_cnt(&self) -> usize { + if self.insns.len() == 0 { + return self.origin_insns.len(); + } + self.insns.len() + } + + /// return struct cache_data offset in eBPF program stack + pub fn cd_off(&self) -> i32 { + self.cd_off + } + + /// return builtin mask instruction position + pub fn builtin_insn_pos(&self) -> i32 { + self.builtin_insn_pos + } + + /// c raw pointer + pub fn raw_ptr(&self) -> *const bpf_program { + self.ptr + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::ffi::CString; + #[test] + fn test_prog_find_builtin_insn_pos() { + let ptr = unsafe { rtrace_alloc_and_init(std::ptr::null_mut(), std::ptr::null_mut()) }; + let name = CString::new("ip_queue_xmit").unwrap(); + let prog_ptr = unsafe { rtrace_trace_program(ptr, name.as_ptr(), 0, 0) }; + let prog = Prog::new(prog_ptr); + assert_eq!(prog.builtin_insn_pos() > 0, true); + + let name = CString::new("kretprobe_common").unwrap(); + let prog_ptr = unsafe { rtrace_trace_program(ptr, name.as_ptr(), 0, 0) }; + let prog = Prog::new(prog_ptr); + assert_eq!(prog.builtin_insn_pos() > 0, false); + } + + #[test] + fn test_prog_find_cd_off() { + let ptr = unsafe { rtrace_alloc_and_init(std::ptr::null_mut(), std::ptr::null_mut()) }; + let name = CString::new("ip_queue_xmit").unwrap(); + let prog_ptr = unsafe { rtrace_trace_program(ptr, name.as_ptr(), 0, 0) }; + let prog = Prog::new(prog_ptr); + assert_ne!(prog.cd_off(), i32::MAX); + + let name = CString::new("kretprobe_common").unwrap(); + let prog_ptr = unsafe { rtrace_trace_program(ptr, name.as_ptr(), 0, 0) }; + let prog = Prog::new(prog_ptr); + assert_eq!(prog.cd_off(), i32::MAX); + } +} diff --git a/source/tools/detect/net_diag/rtrace/rtrace-rs/src/trace/trace.rs b/source/tools/detect/net_diag/rtrace/rtrace-rs/src/trace/trace.rs new file mode 100644 index 00000000..464c06fc --- /dev/null +++ b/source/tools/detect/net_diag/rtrace/rtrace-rs/src/trace/trace.rs @@ -0,0 +1,118 @@ +use crate::bindings::*; +use crate::rtrace::Function; +use crate::trace::prog::Prog; +use anyhow::anyhow; +use anyhow::Result; +use libbpf_sys::{bpf_kprobe_opts, size_t}; +use std::ffi::{CStr, CString}; +use std::os::raw::{c_char, c_int, c_void}; +use log::*; + +/// dynamic trace module +/// +/// +pub struct Trace { + r: *mut rtrace, + + func: CString, + sk: c_int, + skb: c_int, +} + +impl Trace { + pub fn new(r: *mut rtrace, function: &Function) -> Result { + let func = CString::new(function.name.clone())?; + let mut sk = 0; + let mut skb = 0; + if let Some(x) = function.sk { + sk = x; + } + if let Some(x) = function.skb { + skb = x; + } + Ok(Trace { + r, + func, + sk, + skb, + }) + } + + /// load and attach kprobe type eBPF program for this func. + pub fn attach_kprobe(&self, prog: &Prog) -> Result<()> { + let err = unsafe { + rtrace_trace_load_prog(self.r, prog.raw_ptr(), prog.insns(), prog.insns_cnt() as size_t) + }; + + if err < 0 { + return Err(anyhow!("unable to load kprobe -> {:?}, err: {}", self.func, err)); + } + + let bl = unsafe { + libbpf_sys::bpf_program__attach_kprobe( + prog.raw_ptr(), + false, + self.func.as_ptr() as *const c_char, + ) + }; + let err = unsafe { libbpf_sys::libbpf_get_error(bl as *const c_void) }; + if err < 0 { + return Err(anyhow!("failed to attach kprobe -> {:?}", self.func)); + } + + debug!("attach kprobe ({:?}) successfully.", self.func); + Ok(()) + } + + pub fn attach_kretprobe(&self, prog: &Prog) -> Result<()> { + let bl = unsafe { + libbpf_sys::bpf_program__attach_kprobe( + prog.raw_ptr(), + true, + self.func.as_ptr() as *const c_char, + ) + }; + let err = unsafe { libbpf_sys::libbpf_get_error(bl as *const c_void) }; + if err < 0 { + return Err(anyhow!("failed to attach kretprobe -> {:?}", self.func,)); + } + debug!("attach kretprobe ({:?}) successfully.", self.func); + Ok(()) + } + + fn attach_line(&self, prog: &Prog, offset: u64) -> Result<()> { + let mut opts = bpf_kprobe_opts::default(); + opts.sz = std::mem::size_of::() as u64; + opts.bpf_cookie = 0; + opts.offset = offset; + opts.retprobe = false; + + unsafe { + let bl = libbpf_sys::bpf_program__attach_kprobe_opts( + prog.raw_ptr(), + self.func.as_ptr() as *mut c_char, + &opts as *const libbpf_sys::bpf_kprobe_opts, + ); + + let err = libbpf_sys::libbpf_get_error(bl as *const c_void); + if err < 0 { + return Err(anyhow!( + "failed to attach kprobe+{} -> {:?} ", + opts.offset, + self.func + )); + } + } + debug!("attach kprobe ({:?}+{}) successfully.", self.func, offset); + Ok(()) + } + + pub fn attach_lines(&self, prog: &Prog, offsets: &Vec) -> Result<()> { + for offset in offsets { + self.attach_line(prog, *offset)?; + } + Ok(()) + } +} + + diff --git a/source/tools/detect/net_diag/rtrace/rtrace-rs/src/utils/gdb.rs b/source/tools/detect/net_diag/rtrace/rtrace-rs/src/utils/gdb.rs new file mode 100644 index 00000000..ea5fdb3e --- /dev/null +++ b/source/tools/detect/net_diag/rtrace/rtrace-rs/src/utils/gdb.rs @@ -0,0 +1,87 @@ +use anyhow::Result; +use std::io; +use anyhow::anyhow; +use std::io::{BufRead, BufReader, BufWriter, Write}; +use std::process; +use regex::Regex; + +pub struct Gdb { + stdin: BufWriter, + stdout: BufReader, +} + +impl Gdb { + pub fn new(vmlinux: &String) -> Result { + let mut child = process::Command::new("gdb") + .args(&["--interpreter=mi"]) + .stdout(process::Stdio::piped()) + .stdin(process::Stdio::piped()) + .stderr(process::Stdio::piped()) + .spawn()?; + let mut gdb = Gdb { + stdin: BufWriter::new(child.stdin.take().expect("broken stdin")), + stdout: BufReader::new(child.stdout.take().expect("broken stdout")), + }; + gdb.read_response()?; + let output = gdb.send_cmd_raw(&format!("file {}\n", vmlinux)[..]); + // println!("{:?}", output); + Ok(gdb) + } + + fn read_sequence(&mut self) -> Result> { + let mut result = Vec::new(); + let mut line = String::new(); + self.stdout.read_line(&mut line)?; + while line != "(gdb) \n" { + result.push(line.clone()); + line.clear(); + self.stdout.read_line(&mut line)?; + } + Ok(result) + } + + fn read_response(&mut self) -> Result> { + loop { + let sequence = self.read_sequence(); + if let Some(resp) = sequence.into_iter().nth(0) { + return Ok(resp); + } + } + } + + fn send_cmd_raw(&mut self, cmd: &str) -> Result> { + self.stdin.write_all(cmd.as_ref())?; + self.stdin.flush()?; + self.read_response() + } + + pub fn infoline(&mut self, line: &String) -> Result { + let string = format!("info line {}\n", line); + let output = self.send_cmd_raw(&string)?; + let regex = Regex::new(r"\+(\d+)")?; + for cap in regex.captures_iter(&output[1]) { + return Ok(*(&cap[1].parse::()?)); + } + Err(anyhow!("unable to get offset")) + } +} + +impl Drop for Gdb { + fn drop(&mut self) { + let _ = self.stdin.write_all(b"-gdb-exit\n"); + } +} + +#[cfg(test)] +mod tests { + + use super::*; + #[test] + fn test_gdb() { + let mut g = Gdb::new( + &"/work/vmlinux-btf/vmlinux/vmlinux-4.19.91-007.ali4000.alios7.x86_64".to_owned(), + ) + .unwrap(); + g.infoline(&"net/ipv4/tcp.c:400".to_owned()).unwrap(); + } +} diff --git a/source/tools/detect/net_diag/rtrace/rtrace-rs/src/utils/mod.rs b/source/tools/detect/net_diag/rtrace/rtrace-rs/src/utils/mod.rs new file mode 100644 index 00000000..5fb1942e --- /dev/null +++ b/source/tools/detect/net_diag/rtrace/rtrace-rs/src/utils/mod.rs @@ -0,0 +1 @@ +pub mod gdb; \ No newline at end of file -- Gitee From d09f625d333d3441cd9e1f6febc463d818265200 Mon Sep 17 00:00:00 2001 From: "chengshuyi.csy" Date: Thu, 10 Feb 2022 10:13:00 +0800 Subject: [PATCH 2/9] makefile: Add compile dependencies Add target to compile rtrace-rs and rtrace-parser libraries separately. Divide the bin target into two separate targets rtrace-delay and rtrace-drop. Signed-off-by: chengshuyi.csy --- source/tools/detect/net_diag/rtrace/Makefile | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/source/tools/detect/net_diag/rtrace/Makefile b/source/tools/detect/net_diag/rtrace/Makefile index db0a869d..1e0e7896 100644 --- a/source/tools/detect/net_diag/rtrace/Makefile +++ b/source/tools/detect/net_diag/rtrace/Makefile @@ -18,17 +18,25 @@ TARGET_PATH := $(OBJ_TOOLS_ROOT) .PHONY: rtrace -rtrace: lib bin +rtrace: lib bin rs parser delay drop lib: make -C ebpf -bin: - cd rtrace-delay && cargo build --release - cd rtrace-drop && cargo build --release +bin: delay drop + +rs: + cd rtrace-rs && cargo build --release + +parser: + cd rtrace-parser && cargo build --release +delay: + cd rtrace-delay && cargo build --release @echo "rtrace-delay" >> $(TARGET_PATH)/$(SYSAK_RULES) + +drop: + cd rtrace-drop && cargo build --release @echo "rtrace-drop" >> $(TARGET_PATH)/$(SYSAK_RULES) - @echo "rtrace-basic" >> $(TARGET_PATH)/$(SYSAK_RULES) target := rtrace -- Gitee From cfa6a88000f4d4465c9b16b75d6651f5906ecbe9 Mon Sep 17 00:00:00 2001 From: "chengshuyi.csy" Date: Thu, 10 Feb 2022 11:28:45 +0800 Subject: [PATCH 3/9] rtrace-rs: support Config Serialization. Support config serialization, which can easily rewrite the configuration information back to the configuration file. Signed-off-by: chengshuyi.csy --- .../net_diag/rtrace/rtrace-rs/src/rtrace.rs | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/source/tools/detect/net_diag/rtrace/rtrace-rs/src/rtrace.rs b/source/tools/detect/net_diag/rtrace/rtrace-rs/src/rtrace.rs index 56ab3ed7..02def72c 100644 --- a/source/tools/detect/net_diag/rtrace/rtrace-rs/src/rtrace.rs +++ b/source/tools/detect/net_diag/rtrace/rtrace-rs/src/rtrace.rs @@ -7,14 +7,14 @@ use crate::trace::trace::Trace; use crate::utils::gdb::Gdb; use anyhow::anyhow; use anyhow::Result; -use serde_derive::Deserialize; +use serde_derive::{Deserialize, Serialize}; use std::collections::HashMap; use std::ffi::CString; -use std::os::raw::{c_char, c_int, c_void}; +use std::os::raw::{c_char, c_int}; use std::path::PathBuf; use toml; -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, Serialize)] pub struct Basic { pub debug: bool, pub btf_path: Option, @@ -26,14 +26,14 @@ pub struct Basic { pub recv: bool, } -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, Serialize)] pub struct Filterx { pub pid: usize, pub dst: String, pub src: String, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Clone, Debug, Deserialize, Serialize)] pub struct Function { pub name: String, pub enable: Option, @@ -46,7 +46,7 @@ pub struct Function { offsets: Option>, } // see: https://github.com/alexcrichton/toml-rs/issues/395 -#[derive(Clone, Debug, Deserialize)] +#[derive(Clone, Debug, Deserialize, Serialize)] struct FunctionContainer { function: Vec, } @@ -60,7 +60,7 @@ impl Function { } } -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, Serialize)] pub struct Config { pub basic: Basic, pub filter: Option>, @@ -74,6 +74,13 @@ impl Config { Err(_) => Err(anyhow!("str to Config failed")), } } + + pub fn to_string(&self) -> Result { + match toml::to_string(self) { + Ok(x) => Ok(x), + Err(_) => Err(anyhow!("config to string failed")), + } + } } pub struct Probe { @@ -83,7 +90,6 @@ pub struct Probe { } impl Probe { - pub fn get_exprs(&self) -> &Vec { self.dynamic.get_exprs() } -- Gitee From 184cc25b9345370cc4a866ffec88ea89327dfa02 Mon Sep 17 00:00:00 2001 From: "chengshuyi.csy" Date: Thu, 10 Feb 2022 11:30:24 +0800 Subject: [PATCH 4/9] gitignore: update untrack .toml untrack .toml file. Signed-off-by: chengshuyi.csy --- source/tools/detect/net_diag/rtrace/.gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/tools/detect/net_diag/rtrace/.gitignore b/source/tools/detect/net_diag/rtrace/.gitignore index 110d0105..0969291a 100644 --- a/source/tools/detect/net_diag/rtrace/.gitignore +++ b/source/tools/detect/net_diag/rtrace/.gitignore @@ -1,3 +1,4 @@ target Cargo.lock -.vscode \ No newline at end of file +.vscode +*.toml -- Gitee From d4aa01f94be00bf7838ca989c07770d6dc28f33b Mon Sep 17 00:00:00 2001 From: "chengshuyi.csy" Date: Thu, 10 Feb 2022 14:38:17 +0800 Subject: [PATCH 5/9] makefile: fix typo. change librtrace.so to librtrace.a Signed-off-by: chengshuyi.csy --- source/tools/detect/net_diag/rtrace/ebpf/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/tools/detect/net_diag/rtrace/ebpf/Makefile b/source/tools/detect/net_diag/rtrace/ebpf/Makefile index ccfc7a15..bf480a0b 100644 --- a/source/tools/detect/net_diag/rtrace/ebpf/Makefile +++ b/source/tools/detect/net_diag/rtrace/ebpf/Makefile @@ -36,7 +36,7 @@ else endif -librtrace: $(OBJ_LIB_PATH)/librtrace.so +librtrace: $(OBJ_LIB_PATH)/librtrace.a $(OBJ_LIB_PATH)/librtrace.a: $(target_cobjs) $(Q) ar -rcs $@ $^ -- Gitee From 67b7cf69df99cff57f3b2a00bc7365e6ac438c1f Mon Sep 17 00:00:00 2001 From: "chengshuyi.csy" Date: Thu, 10 Feb 2022 14:40:15 +0800 Subject: [PATCH 6/9] ebpf: fix icmp seq and rseq. seq of sender and receiver is same, and we have no way to distiguish sender path and receiver path. Signed-off-by: chengshuyi.csy --- .../detect/net_diag/rtrace/ebpf/common.bpf.h | 52 ----------------- .../detect/net_diag/rtrace/ebpf/rtrace.bpf.c | 56 +++++-------------- 2 files changed, 14 insertions(+), 94 deletions(-) diff --git a/source/tools/detect/net_diag/rtrace/ebpf/common.bpf.h b/source/tools/detect/net_diag/rtrace/ebpf/common.bpf.h index dc60c1aa..a8d8a4b5 100644 --- a/source/tools/detect/net_diag/rtrace/ebpf/common.bpf.h +++ b/source/tools/detect/net_diag/rtrace/ebpf/common.bpf.h @@ -111,58 +111,6 @@ static __always_inline void read_ns_inum_by_sk(struct sock *sk, u32 *inum) } } -// 不依赖于sock或sk_buff,获取入参 -static __always_inline void send_context(void *ctx, void *perf, uint8_t send) -{ - ENUM_TO_STRUCT(CONTEXT) - ct = {0}; - ct.types = SET_MAJOR_TYPE(ct.types, MEMORY); - ct.args[0] = (uint64_t)PT_REGS_PARM1((struct pt_regs *)ctx); - ct.args[1] = (uint64_t)PT_REGS_PARM2((struct pt_regs *)ctx); - ct.args[2] = (uint64_t)PT_REGS_PARM3((struct pt_regs *)ctx); - ct.args[3] = (uint64_t)PT_REGS_PARM4((struct pt_regs *)ctx); - ct.args[4] = (uint64_t)PT_REGS_PARM5((struct pt_regs *)ctx); - bpf_perf_event_output(ctx, perf, BPF_F_CURRENT_CPU, &ct, sizeof(ENUM_TO_STRUCT(CONTEXT))); -} - -static __always_inline void send_tcp_wind(void *ctx, void *perf, struct sock *sk, uint8_t send) -{ - ENUM_TO_STRUCT(TCP_WINDOW) - tw = {0}; - struct tcp_sock *ts = (struct tcp_sock *)sk; - tw.types = SET_MAJOR_TYPE(tw.types, TCP_WINDOW); - - BPF_CORE_READ_INTO(&tw.rcv_nxt, ts, rcv_nxt); - BPF_CORE_READ_INTO(&tw.rcv_wup, ts, rcv_wup); - BPF_CORE_READ_INTO(&tw.snd_nxt, ts, snd_nxt); - BPF_CORE_READ_INTO(&tw.snd_una, ts, snd_una); - BPF_CORE_READ_INTO(&tw.copied_seq, ts, copied_seq); - BPF_CORE_READ_INTO(&tw.snd_wnd, ts, snd_wnd); - BPF_CORE_READ_INTO(&tw.rcv_wnd, ts, rcv_wnd); - - BPF_CORE_READ_INTO(&tw.lost_out, ts, lost_out); - BPF_CORE_READ_INTO(&tw.packets_out, ts, packets_out); - BPF_CORE_READ_INTO(&tw.retrans_out, ts, retrans_out); - BPF_CORE_READ_INTO(&tw.sacked_out, ts, sacked_out); - bpf_perf_event_output(ctx, perf, BPF_F_CURRENT_CPU, &tw, sizeof(ENUM_TO_STRUCT(TCP_WINDOW))); -} - -static __always_inline void send_memory(void *ctx, void *perf, struct sock *sk, uint8_t send) -{ - ENUM_TO_STRUCT(MEMORY) - mm = {0}; - atomic_long_t *map; - mm.types = SET_MAJOR_TYPE(mm.types, MEMORY); - BPF_CORE_READ_INTO(&map, sk, __sk_common.skc_prot, memory_allocated); - bpf_probe_read(&mm.allocated, sizeof(atomic_long_t), map); - BPF_CORE_READ_INTO(&mm.rmem_alloc, sk, sk_backlog.rmem_alloc.counter); - BPF_CORE_READ_INTO(&mm.wmem_alloc, sk, sk_wmem_alloc); - BPF_CORE_READ_INTO(&mm.forward_alloc, sk, sk_forward_alloc); - BPF_CORE_READ_INTO(&mm.rcvbuf, sk, sk_rcvbuf); - BPF_CORE_READ_INTO(&mm.sndbuf, sk, sk_sndbuf); - bpf_perf_event_output(ctx, perf, BPF_F_CURRENT_CPU, &mm, sizeof(ENUM_TO_STRUCT(MEMORY))); -} - // Declare a map with key type uint64 according to enum type. #define DECLARE_HASH_MAP(enum_type, entries) \ struct \ diff --git a/source/tools/detect/net_diag/rtrace/ebpf/rtrace.bpf.c b/source/tools/detect/net_diag/rtrace/ebpf/rtrace.bpf.c index a716fa6a..764cf667 100644 --- a/source/tools/detect/net_diag/rtrace/ebpf/rtrace.bpf.c +++ b/source/tools/detect/net_diag/rtrace/ebpf/rtrace.bpf.c @@ -143,44 +143,16 @@ static __always_inline void set_seq(struct cache_data *cd, uint32_t *seq, uint32 uint32_t len, tmp_seq, tmp_end_seq, tmp_rseq, tmp_rend_seq; struct tcp_skb_cb *tsc; uint32_t protocol = cd->sk_protocol & 0xff; - // uint16_t offset; - // handle icmp if (protocol == IPPROTO_ICMP) { struct icmphdr *ih = ((struct icmphdr *)(&cd->th)); uint16_t sequence; uint8_t type = ih->type; sequence = ih->un.echo.sequence; - if (cd->send) // send path - { - if (type == ICMP_ECHO) - { - // sender - *seq = sequence; - *end_seq = sequence + 1; - } - else if (type == ICMP_ECHOREPLY) - { - // receiver - *rseq = sequence; - *rend_seq = sequence + 1; - } - } - else // receive path - { - if (type == ICMP_ECHOREPLY) - { - // sender - *seq = sequence; - *end_seq = sequence + 1; - } - else if (type == ICMP_ECHO) - { - // receiver - *rseq = sequence; - *rend_seq = sequence + 1; - } - } + *seq = sequence; + *end_seq = sequence + 1; + *rseq = sequence; + *rend_seq = sequence + 1; return; } @@ -384,11 +356,11 @@ static __always_inline void set_cache_data(struct cache_data *cd, struct sk_buff /** * @brief Filter out unwanted packets - * - * @param protocol - * @param pid - * @param ap - * @return __always_inline + * + * @param protocol + * @param pid + * @param ap + * @return __always_inline */ static __always_inline int builtin_filter(uint32_t protocol, int pid, struct addr_pair *ap) { @@ -432,11 +404,11 @@ static __always_inline int builtin_filter(uint32_t protocol, int pid, struct add /** * @brief Main processing function entry - * - * @param ctx - * @param sk - * @param skb - * @return __always_inline + * + * @param ctx + * @param sk + * @param skb + * @return __always_inline */ static __always_inline int do_trace_sk_skb(void *ctx, struct sock *sk, struct sk_buff *skb) { -- Gitee From fd9bf8562ebbc045172b4fa44733a98c11a20fc1 Mon Sep 17 00:00:00 2001 From: "chengshuyi.csy" Date: Thu, 10 Feb 2022 14:46:29 +0800 Subject: [PATCH 7/9] rtrace-delay: first commit add rtrace-delay tools, and only support icmp protocol now. Signed-off-by: chengshuyi.csy --- .../net_diag/rtrace/image/ping-delay.png | Bin 0 -> 97550 bytes .../net_diag/rtrace/rtrace-delay/README.md | 148 +++++++++++++++++- .../net_diag/rtrace/rtrace-delay/src/fin.rs | 0 .../net_diag/rtrace/rtrace-delay/src/gen.rs | 125 +++++++++++++++ .../net_diag/rtrace/rtrace-delay/src/main.rs | 95 +++++++++++ .../net_diag/rtrace/rtrace-delay/src/mid.rs | 133 ++++++++++++++++ .../net_diag/rtrace/rtrace-delay/src/syn.rs | 75 +++++++++ 7 files changed, 571 insertions(+), 5 deletions(-) create mode 100644 source/tools/detect/net_diag/rtrace/image/ping-delay.png create mode 100644 source/tools/detect/net_diag/rtrace/rtrace-delay/src/fin.rs create mode 100644 source/tools/detect/net_diag/rtrace/rtrace-delay/src/gen.rs create mode 100644 source/tools/detect/net_diag/rtrace/rtrace-delay/src/main.rs create mode 100644 source/tools/detect/net_diag/rtrace/rtrace-delay/src/mid.rs create mode 100644 source/tools/detect/net_diag/rtrace/rtrace-delay/src/syn.rs diff --git a/source/tools/detect/net_diag/rtrace/image/ping-delay.png b/source/tools/detect/net_diag/rtrace/image/ping-delay.png new file mode 100644 index 0000000000000000000000000000000000000000..56f7d25b6f8fed2c6c0d62cb2ad7497aa9e639b0 GIT binary patch literal 97550 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 %( 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%0D&eOL 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

;emc}wovJZX%BuAtnfU?E62 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 literal 0 HcmV?d00001 diff --git a/source/tools/detect/net_diag/rtrace/rtrace-delay/README.md b/source/tools/detect/net_diag/rtrace/rtrace-delay/README.md index ed4a1223..5b7921c4 100644 --- a/source/tools/detect/net_diag/rtrace/rtrace-delay/README.md +++ b/source/tools/detect/net_diag/rtrace/rtrace-delay/README.md @@ -1,17 +1,155 @@ +# rtrace-delay 网络抖动诊断 +rtrace-delay是一款基于eBPF的网络抖动诊断工具,目前icmp(ping)、tcp等协议的网络抖动。 -## 时延诊断原理 -### 三次握手报文时延诊断 +## 原理简介 -### 数据报文时延诊断 +一般地,网络抖动问题定位过程包括: + +1. 确定是抖动发生在发送端还是接收端; +2. 确定是否在内核; +3. 确定导致抖动的代码片段; + +为了确定抖动发生在发送端还是接收端,rtrace将一个完整的报文路径分成两个部分:**发送端报文路径和接收端报文路径**。 + +为了确定抖动是否发生在内核,rtrace在**内核出入口处的地方进行打点监控**。 + +为了确定导致抖动的代码片段,rtrace实现了动态打点功能,即可以随时**新增打点函数**,进而逐步缩减抖动范围。 + +接下来从这三点出发,介绍ping报文和tcp报文的抖动诊断方案。 + +### icmp(ping)抖动诊断 + +下图是ping的ICMP_ECHO和ICMP_ECHOREPLY报文路径。黑色箭头是ICMP_ECHO报文路径,红色箭头是ICMP_ECHOREPLY报文路径。 + +其中,发送端报文路径包括:ICMP_ECHO报文发送路径及ICMP_ECHOREPLY报文接收路径: + +* ICMP_ECHO报文发送路径是:raw_sendmsg->dev_hard_start_xmit->驱动; +* ICMP_ECHOREPLY报文接收路径:驱动->__netif_receive_skb_core->ping_rcv->ping_recvmsg。 + +其中,接收端报文路径包括:ICMP_ECHO报文接收路径及ICMP_ECHOREPLY报文发送路径: + +* ICMP_ECHO报文接收路径:驱动->__netif_receive_skb_core->icmp_rcv; +* ICMP_ECHOREPLY报文发送路径:dev_hard_start_xmit->驱动。 + +![ping抖动诊断默认打点路径](../image/ping-delay.png) + +### tcp抖动诊断原理 ## 使用说明 +使用流程: + +1. 生成配置文件:通过命令`sysak rtrace-delay --gen ./config`生成toml配置文件到config目录下; +2. 修改配置文件:根据自己的需要修改配置; +3. 运行诊断程序,可输入的参数如下: + +```shell +OPTIONS: + --config configuration file path + --delay show packet routine when delay > DELAY(ms) [default: 200] + --gen generate default configuration file + --latency latency(ms) in processing data [default: 3000] +``` + +### 配置文件 + +配置文件包含三个配置表,分别是基础配置表、过滤器配置表和函数配置表。 + +#### 基础配置表 + +`basic`包含六种键值,分别是: + +* debug:是否开启debug日志输出; +* btf_path:vmlinux btf文件的路径; +* duration:程序运行时间; +* protocol:协议类型,目前包含icmp、tcp和tcp-syn; +* recv:诊断发送端路径还是接收端路径; +* vmlinux:debuginfo中vmlinux的路径。 + +```toml +[basic] +debug = false +btf_path = "" +duration = 0 +protocol = "icmp" +recv = true +vmlinux = "" +``` + +其中,`btf_path`和`vmlinux`是可选参数。 + +目前提供了五个默认的配置文件,分别是:`ping-sender.toml`、`ping-receiver.toml`、`syn-sender.toml`、`tcp-sender.toml`和`tcp-receiver.toml`。 + +#### 过滤器配置表 + +`filter`是数组形式,单个`filter`内部过滤规则取交集,`filter`间过滤规则取并集。`filter`支持三种键值,分别是: + +* pid:进程id; +* dst:目的地址,包含ip和端口; +* src:源地址,包含ip和端口。 + +```toml +[[filter]] +pid = 0 +dst = "0.0.0.0:0" +src = "0.0.0.0:0" +``` + +#### 函数配置表 + +函数配置表中`function`是数组形式。`function`目前支持五种键值,分别是: + +* name:打点的内核函数名字; +* enable:是否使能; +* params:内置参数列表; +* exprs:动态参数表达式列表; +* line:指定打点行数。 + +```toml +[[function]] +name = "raw_sendmsg" +enable = true +params = ["basic"] +lines = ["net/ipv4/raw.c:455"] + +[[function]] +name = "dev_hard_start_xmit" +enable = true +params = ["basic"] + +[[function]] +name = "__netif_receive_skb_core" +enable = true +params = ["basic"] +``` + +### icmp(ping)抖动诊断 + +#### 步骤一 + +运行`sysak rtrace-delay --gen ./config`生成toml配置文件到config目录下。这里我们主要关注`./config/ping-sender.toml`和`./config/ping-receiver.toml`两个配置文件。 + +#### 步骤二: + +根据需求修改配置文件 + +#### 步骤三 + +* 诊断发送端路径,`sysak rtrace-delay --config ./config/ping-sender.toml --delay 20 --latency 1000`; +* 诊断接收端路径,`sysak rtrace-delay --config ./config/ping-receiver.toml`; +下面是诊断发送端路径的输出,执行的命令是`sysak rtrace-delay --config ./config/ping-sender.toml --delay 20 --latency 1000`。表示延迟1秒处理数据,打印抖动超过20毫秒的报文路径: -## 相关文章 +```plain + (0,1)raw_sendmsg+0 (0,1)ping_rcv+0 + ↓ ↑ + 33us 43us + ↓ ↑ + (0,1)dev_hard_start_xmit+0 →27479us→ (0,1)__netif_receive_skb_core+0 +``` +### tcp抖动诊断 -sudo RUST_LOG=debug cargo run -- --config ./config/syn-sender.toml --delay 0 --latency 2000 \ No newline at end of file diff --git a/source/tools/detect/net_diag/rtrace/rtrace-delay/src/fin.rs b/source/tools/detect/net_diag/rtrace/rtrace-delay/src/fin.rs new file mode 100644 index 00000000..e69de29b diff --git a/source/tools/detect/net_diag/rtrace/rtrace-delay/src/gen.rs b/source/tools/detect/net_diag/rtrace/rtrace-delay/src/gen.rs new file mode 100644 index 00000000..21b9b642 --- /dev/null +++ b/source/tools/detect/net_diag/rtrace/rtrace-delay/src/gen.rs @@ -0,0 +1,125 @@ +use anyhow::Result; +use rtrace_rs::rtrace::Config; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; +use uname::uname; + +fn get_btf_path() -> String { + let mut default = String::from("/boot/vmlinux-"); + let info = uname().expect("uname failed"); + default.push_str(&info.release[..]); + default +} + +fn gen_config_common(path: &PathBuf, text: &str) -> Result<()> { + let mut config = Config::from_str(text)?; + config.basic.btf_path = Some(get_btf_path()); + let string = Config::to_string(&config)?; + let mut output = File::create(path)?; + write!(output, "{}", string); + Ok(()) +} + +fn gen_config_ping_receiver(path: &mut PathBuf) -> Result<()> { + let text = r#" +[basic] +debug = false +duration = 0 +protocol = "icmp" +recv = true + +[[filter]] +pid = 0 +dst = "0.0.0.0:0" +src = "0.0.0.0:0" + +[[function]] +name = "dev_hard_start_xmit" +enable = true +params = ["basic"] + +[[function]] +name = "__netif_receive_skb_core" +enable = true +params = ["basic"] + +[[function]] +name = "icmp_rcv" +enable = true +params = ["basic"] + "#; + + path.push("ping-receiver.toml"); + gen_config_common(path, &text)?; + path.pop(); + Ok(()) +} + +fn gen_config_ping_sender(path: &mut PathBuf) -> Result<()> { + let text = r#" +[basic] +debug = false +duration = 0 +protocol = "icmp" +recv = false + +[[filter]] +pid = 0 +dst = "0.0.0.0:0" +src = "0.0.0.0:0" + +[[function]] +name = "raw_sendmsg" +enable = true +params = ["basic"] + +[[function]] +name = "dev_hard_start_xmit" +enable = true +params = ["basic"] + +[[function]] +name = "__netif_receive_skb_core" +enable = true +params = ["basic"] + +[[function]] +name = "ping_rcv" +enable = true +params = ["basic"] + "#; + path.push("ping-sender.toml"); + gen_config_common(path, &text)?; + path.pop(); + Ok(()) +} + +fn gen_config_syn_sender(path: &mut PathBuf) -> Result<()> { + path.push("syn-sender.toml"); + path.pop(); + Ok(()) +} + +fn gen_config_tcp_receiver(path: &mut PathBuf) -> Result<()> { + path.push("tcp-receiver.toml"); + path.pop(); + Ok(()) +} + +fn gen_config_tcp_sender(path: &mut PathBuf) -> Result<()> { + path.push("tcp-sender.toml"); + path.pop(); + Ok(()) +} + +pub fn gen_config(path: &str) -> Result<()> { + let mut p = PathBuf::from(path); + std::fs::create_dir_all(&p)?; + gen_config_ping_receiver(&mut p)?; + gen_config_ping_sender(&mut p)?; + gen_config_syn_sender(&mut p)?; + gen_config_tcp_receiver(&mut p)?; + gen_config_tcp_sender(&mut p)?; + Ok(()) +} diff --git a/source/tools/detect/net_diag/rtrace/rtrace-delay/src/main.rs b/source/tools/detect/net_diag/rtrace/rtrace-delay/src/main.rs new file mode 100644 index 00000000..19d9679f --- /dev/null +++ b/source/tools/detect/net_diag/rtrace/rtrace-delay/src/main.rs @@ -0,0 +1,95 @@ +mod gen; +mod mid; +mod syn; + +use crate::gen::gen_config; +use crate::mid::Mid; +use crate::syn::Syn; +use crossbeam_channel; +use log::*; +use rtrace_parser::func::Func; +use rtrace_parser::ksyms::ksyms_load; +use rtrace_parser::perf::{perf_inital_thread2, perf_recv_timeout}; +use rtrace_parser::utils; +use rtrace_rs::bindings::*; +use rtrace_rs::rtrace::Rtrace; +use std::path::PathBuf; +use std::time::Duration; +use structopt::StructOpt; + +#[derive(Debug, StructOpt)] +#[structopt(name = "rtrace_delay", about = "Network delay diagnosis.")] +pub struct Cli { + #[structopt(long, help = "configuration file path")] + config: Option, + #[structopt(long, help = "generate default configuration file")] + gen: Option, + #[structopt(long, default_value = "3000", help = "latency(ms) in processing data")] + latency: u64, + #[structopt( + long, + default_value = "200", + help = "show packet routine when delay > DELAY(ms)" + )] + delay: u64, +} + +fn main() { + let mut cli = Cli::from_args(); + if let Some(path) = cli.gen { + gen_config(&path).expect("unable to generate config file"); + return; + } + env_logger::init(); + cli.latency = cli.latency * 1_000_000; + cli.delay = cli.delay * 1_000_000; + ksyms_load(&"/proc/kallsyms".to_owned()); + let mut rtrace; + match &cli.config { + None => { + println!("Please input config file path"); + return; + } + Some(config) => rtrace = Rtrace::from_file(config).expect("rtrace init failed"), + } + rtrace.probe_filter().expect("init filter failed"); + rtrace.probe_functions().expect("unable to trace function."); + let protocol = rtrace.protocol().expect("protocol not specified"); + let recv = rtrace.is_recv(); + let mut syn = Syn::new(protocol, recv); + let mut mid = Mid::new(protocol, recv); + + let (rx, tx) = crossbeam_channel::unbounded(); + perf_inital_thread2(rtrace.perf_fd(), (rx, tx)); + + let mut pre_checktimeout_ts = 0; + loop { + let res = perf_recv_timeout(Duration::from_millis(100)); + let cur_ts = utils::get_timestamp(); + match res { + Ok(data) => { + let f = Func::new(data.1); + match protocol { + Protocol::IPPROTO_ICMP | Protocol::IPPROTO_TCP => { + mid.push_func(f); + if cur_ts - pre_checktimeout_ts > 100_000_000 { + mid.check_timeout(cur_ts - cli.latency, cli.delay); + pre_checktimeout_ts = cur_ts; + } + } + Protocol::IPPROTO_TCP_SYN => { + syn.push_func(f); + if cur_ts - pre_checktimeout_ts > 100_000_000 { + syn.check_timeout(cur_ts - cli.latency, cli.delay); + pre_checktimeout_ts = cur_ts; + } + } + _ => { + panic!("rtrace_delay only support tcp or syn") + } + } + } + _ => {} + } + } +} diff --git a/source/tools/detect/net_diag/rtrace/rtrace-delay/src/mid.rs b/source/tools/detect/net_diag/rtrace/rtrace-delay/src/mid.rs new file mode 100644 index 00000000..e63ad85d --- /dev/null +++ b/source/tools/detect/net_diag/rtrace/rtrace-delay/src/mid.rs @@ -0,0 +1,133 @@ +use rtrace_parser::func::Func; +use rtrace_rs::rtrace::Rtrace; +use rtrace_parser::skb::{funcs_to_skbs, Skb}; +use rtrace_parser::net::Net; +use rtrace_rs::bindings::*; +use anyhow::Result; +use std::rc::Rc; +use log::*; + +pub struct Mid { + dis: Vec, + skb: Option, + ts: u64, + recv: bool, + net : Net, +} + +impl Mid { + pub fn new(protocol: Protocol, recv: bool) -> Mid { + // match protocol { + // Protocol::IPPROTO_TCP | Protocol::IPPROTO_ICMP => {} + // _ => { + // panic!("only support tcp or icmp(ping) protocol"); + // } + // } + Mid { + dis: Vec::new(), + skb: None, + ts: 0, + recv, + net: Net::new(recv), + } + } + + pub fn push_func(&mut self, func: Func) { + debug!("{}: {}, {:?}, {:?}", func.get_name(),func.get_ap().into_string(), func.get_seq(), func.get_rseq()); + self.net.push_func(func); + } + + pub fn check_timeout(&mut self, timeout: u64, delay: u64) -> bool { + let vec_funcs = self.net.group(timeout); + for funcs in vec_funcs { + let skbs = funcs_to_skbs(funcs, false, self.recv); + for mut skb in skbs { + let delay_tmp = skb.get_delay(); + if delay_tmp > delay { + skb.show(); + } + } + } + true + } + + // pub fn check(&mut self, f: Vec>, ts: u64) { + // let skbs = funcs_to_skbs(f, false, self.recv); + // for mut skb in skbs { + // let delay = skb.get_delay_ms(); + // if self.insert_delay(delay as usize) { + // self.skb = Some(skb); + // } + // } + + // self.show(ts); + // } + + // fn insert_delay(&mut self, delay: usize) -> bool { + // let mut larger = false; + // if delay + 1 > self.dis.len() { + // self.dis.resize(delay + 1, 0); + // larger = true; + // } + // self.dis[delay as usize] += 1; + // larger + // } + + // fn sum_dis(&self, start: usize, mut end: usize) -> u32 { + // let mut res = 0; + // end = std::cmp::min(self.dis.len(), end); + // for i in start..end { + // res += self.dis[i]; + // } + // res + // } + + // fn show_dis(&self) { + // let default_width = 10; + // let mut print = Vec::with_capacity(default_width); + // let multiple = std::cmp::max(self.dis.len() / default_width, 1); + // let mut total_cnt = 0; + // println!("DISTRIBUTION:\n"); + // for i in 0..default_width { + // let start = i * multiple; + // let end = (i + 1) * multiple; + // let res = self.sum_dis(start, end); + // total_cnt += res; + // print.push(res); + // } + // if total_cnt == 0 { + // return; + // } + // for i in 0..default_width { + // let cnt = print[i] * 50 / total_cnt; + // println!( + // "{:>5}-{:<5} {:<50} {}", + // i * multiple, + // (i + 1) * multiple, + // "*".repeat(cnt as usize), + // print[i] + // ); + // } + // } + + // fn show_skb(&self) { + // if let Some(s) = &self.skb { + // s.show(); + // } + // } + + // pub fn show(&mut self, ts: u64) { + // if self.dis.len() == 0 { + // return; + // } + + // if ts - self.ts > 1_000_000_000 { + // println!("\n"); + // self.show_dis(); + // println!("\n"); + // self.show_skb(); + // println!("\n\n\n\n\n"); + // self.ts = ts; + // } + // } +} diff --git a/source/tools/detect/net_diag/rtrace/rtrace-delay/src/syn.rs b/source/tools/detect/net_diag/rtrace/rtrace-delay/src/syn.rs new file mode 100644 index 00000000..2e984ca1 --- /dev/null +++ b/source/tools/detect/net_diag/rtrace/rtrace-delay/src/syn.rs @@ -0,0 +1,75 @@ +use rtrace_parser::func::Func; +use rtrace_parser::skb::Skb; +use rtrace_rs::bindings::*; +use std::collections::HashMap; +use std::rc::Rc; +use log::*; + +struct SynInfo { + skb: Skb, + max_ts: u64, + recv: bool, +} + +impl SynInfo { + pub fn new(recv: bool) -> SynInfo { + SynInfo { + skb: Skb::new(recv), + max_ts: 0, + recv, + } + } + + pub fn push_func(&mut self, func: Func) { + self.max_ts = std::cmp::max(self.max_ts, func.get_ts()); + self.skb.push_func(Rc::new(func)); + } + + pub fn check_timeout(&mut self, timeout: u64, delay: u64) -> bool { + if self.max_ts < timeout { + if self.skb.get_delay() > delay { + self.skb.show(); + } + return true; + } + false + } +} + +pub struct Syn { + conn: HashMap, + recv: bool, +} + +impl Syn { + pub fn new(protocol: Protocol, recv: bool) -> Syn { + // match protocol { + // Protocol::IPPROTO_TCP_SYN => {} + // _ => { + // panic!("syn packet only support tcp-syn protocol"); + // } + // } + // if recv { + // panic!("syn packet diagnoise now only support send path"); + // } + Syn { + conn: HashMap::new(), + recv, + } + } + + pub fn push_func(&mut self, func: Func) { + debug!("{}: {:?}", func.get_name(),func.get_seq()); + let si = self + .conn + .entry(func.get_ap()) + .or_insert(SynInfo::new(self.recv)); + si.push_func(func); + } + + /// Process timeout data and output + pub fn check_timeout(&mut self, timeout: u64, delay: u64) { + self.conn + .retain(|_, value| value.check_timeout(timeout, delay) == false); + } +} -- Gitee From b81dd8ac4281acae8ff47bc42b7fc0bc6f8ed381 Mon Sep 17 00:00:00 2001 From: "chengshuyi.csy" Date: Thu, 10 Feb 2022 15:38:42 +0800 Subject: [PATCH 8/9] rtrace-delay: add Cargo.toml file. Add Cargo.toml file. Signed-off-by: chengshuyi.csy --- .../net_diag/rtrace/rtrace-delay/Cargo.toml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 source/tools/detect/net_diag/rtrace/rtrace-delay/Cargo.toml diff --git a/source/tools/detect/net_diag/rtrace/rtrace-delay/Cargo.toml b/source/tools/detect/net_diag/rtrace/rtrace-delay/Cargo.toml new file mode 100644 index 00000000..93d4f02c --- /dev/null +++ b/source/tools/detect/net_diag/rtrace/rtrace-delay/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "rtrace-delay" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0" +structopt = "0.3" +rtrace-rs = { version = "0.1.0", path = "../rtrace-rs"} +rtrace-parser = { version = "0.1.0", path = "../rtrace-parser"} +crossbeam-channel = "0.5" +log = "0.4.14" +env_logger = "0.9.0" +uname = "0.1.1" -- Gitee From 236f54a3b21bf4a64835418f5d307e3c67f40377 Mon Sep 17 00:00:00 2001 From: Shuyi Cheng Date: Thu, 10 Feb 2022 15:56:02 +0800 Subject: [PATCH 9/9] makefile: fix wrong dependency. rtrace target only depend lib and bin. bin target only depends delay and drop target. Signed-off-by: Shuyi Cheng --- source/tools/detect/net_diag/rtrace/Makefile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/source/tools/detect/net_diag/rtrace/Makefile b/source/tools/detect/net_diag/rtrace/Makefile index 1e0e7896..6445e616 100644 --- a/source/tools/detect/net_diag/rtrace/Makefile +++ b/source/tools/detect/net_diag/rtrace/Makefile @@ -18,7 +18,7 @@ TARGET_PATH := $(OBJ_TOOLS_ROOT) .PHONY: rtrace -rtrace: lib bin rs parser delay drop +rtrace: lib bin lib: make -C ebpf @@ -33,10 +33,12 @@ parser: delay: cd rtrace-delay && cargo build --release + cp rtrace-delay/target/release/rtrace-delay $(TARGET_PATH)/ @echo "rtrace-delay" >> $(TARGET_PATH)/$(SYSAK_RULES) drop: cd rtrace-drop && cargo build --release + cp rtrace-drop/target/release/rtrace-drop $(TARGET_PATH)/ @echo "rtrace-drop" >> $(TARGET_PATH)/$(SYSAK_RULES) target := rtrace -- Gitee