This commit is contained in:
yunwei37
2023-09-18 03:27:00 +00:00
parent c830daeab7
commit e9ef88bc2f
58 changed files with 1893 additions and 664 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,6 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/* Copyright (c) 2021 Sartura */
#define BPF_NO_GLOBAL_DATA
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

9
23-http/.gitignore vendored Normal file
View File

@@ -0,0 +1,9 @@
.vscode
package.json
*.o
*.skel.json
*.skel.yaml
package.yaml
ecli
sockfilter
*.class

141
23-http/Makefile Normal file
View File

@@ -0,0 +1,141 @@
# SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
OUTPUT := .output
CLANG ?= clang
LIBBPF_SRC := $(abspath ../third_party/libbpf/src)
BPFTOOL_SRC := $(abspath ../third_party/bpftool/src)
LIBBPF_OBJ := $(abspath $(OUTPUT)/libbpf.a)
BPFTOOL_OUTPUT ?= $(abspath $(OUTPUT)/bpftool)
BPFTOOL ?= $(BPFTOOL_OUTPUT)/bootstrap/bpftool
LIBBLAZESYM_SRC := $(abspath ../third_party/blazesym/)
LIBBLAZESYM_OBJ := $(abspath $(OUTPUT)/libblazesym.a)
LIBBLAZESYM_HEADER := $(abspath $(OUTPUT)/blazesym.h)
ARCH ?= $(shell uname -m | sed 's/x86_64/x86/' \
| sed 's/arm.*/arm/' \
| sed 's/aarch64/arm64/' \
| sed 's/ppc64le/powerpc/' \
| sed 's/mips.*/mips/' \
| sed 's/riscv64/riscv/' \
| sed 's/loongarch64/loongarch/')
VMLINUX := ../third_party/vmlinux/$(ARCH)/vmlinux.h
# Use our own libbpf API headers and Linux UAPI headers distributed with
# libbpf to avoid dependency on system-wide headers, which could be missing or
# outdated
INCLUDES := -I$(OUTPUT) -I../third_party/libbpf/include/uapi -I$(dir $(VMLINUX))
CFLAGS := -g -Wall
ALL_LDFLAGS := $(LDFLAGS) $(EXTRA_LDFLAGS)
APPS = sockfilter # minimal minimal_legacy uprobe kprobe fentry usdt sockfilter tc ksyscall
CARGO ?= $(shell which cargo)
ifeq ($(strip $(CARGO)),)
BZS_APPS :=
else
BZS_APPS := # profile
APPS += $(BZS_APPS)
# Required by libblazesym
ALL_LDFLAGS += -lrt -ldl -lpthread -lm
endif
# Get Clang's default includes on this system. We'll explicitly add these dirs
# to the includes list when compiling with `-target bpf` because otherwise some
# architecture-specific dirs will be "missing" on some architectures/distros -
# headers such as asm/types.h, asm/byteorder.h, asm/socket.h, asm/sockios.h,
# sys/cdefs.h etc. might be missing.
#
# Use '-idirafter': Don't interfere with include mechanics except where the
# build would have failed anyways.
CLANG_BPF_SYS_INCLUDES ?= $(shell $(CLANG) -v -E - </dev/null 2>&1 \
| sed -n '/<...> search starts here:/,/End of search list./{ s| \(/.*\)|-idirafter \1|p }')
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
define allow-override
$(if $(or $(findstring environment,$(origin $(1))),\
$(findstring command line,$(origin $(1)))),,\
$(eval $(1) = $(2)))
endef
$(call allow-override,CC,$(CROSS_COMPILE)cc)
$(call allow-override,LD,$(CROSS_COMPILE)ld)
.PHONY: all
all: $(APPS)
.PHONY: clean
clean:
$(call msg,CLEAN)
$(Q)rm -rf $(OUTPUT) $(APPS)
$(OUTPUT) $(OUTPUT)/libbpf $(BPFTOOL_OUTPUT):
$(call msg,MKDIR,$@)
$(Q)mkdir -p $@
# Build libbpf
$(LIBBPF_OBJ): $(wildcard $(LIBBPF_SRC)/*.[ch] $(LIBBPF_SRC)/Makefile) | $(OUTPUT)/libbpf
$(call msg,LIB,$@)
$(Q)$(MAKE) -C $(LIBBPF_SRC) BUILD_STATIC_ONLY=1 \
OBJDIR=$(dir $@)/libbpf DESTDIR=$(dir $@) \
INCLUDEDIR= LIBDIR= UAPIDIR= \
install
# Build bpftool
$(BPFTOOL): | $(BPFTOOL_OUTPUT)
$(call msg,BPFTOOL,$@)
$(Q)$(MAKE) ARCH= CROSS_COMPILE= OUTPUT=$(BPFTOOL_OUTPUT)/ -C $(BPFTOOL_SRC) bootstrap
$(LIBBLAZESYM_SRC)/target/release/libblazesym.a::
$(Q)cd $(LIBBLAZESYM_SRC) && $(CARGO) build --features=cheader,dont-generate-test-files --release
$(LIBBLAZESYM_OBJ): $(LIBBLAZESYM_SRC)/target/release/libblazesym.a | $(OUTPUT)
$(call msg,LIB, $@)
$(Q)cp $(LIBBLAZESYM_SRC)/target/release/libblazesym.a $@
$(LIBBLAZESYM_HEADER): $(LIBBLAZESYM_SRC)/target/release/libblazesym.a | $(OUTPUT)
$(call msg,LIB,$@)
$(Q)cp $(LIBBLAZESYM_SRC)/target/release/blazesym.h $@
# Build BPF code
$(OUTPUT)/%.bpf.o: %.bpf.c $(LIBBPF_OBJ) $(wildcard %.h) $(VMLINUX) | $(OUTPUT) $(BPFTOOL)
$(call msg,BPF,$@)
$(Q)$(CLANG) -g -O2 -target bpf -D__TARGET_ARCH_$(ARCH) \
$(INCLUDES) $(CLANG_BPF_SYS_INCLUDES) \
-c $(filter %.c,$^) -o $(patsubst %.bpf.o,%.tmp.bpf.o,$@)
$(Q)$(BPFTOOL) gen object $@ $(patsubst %.bpf.o,%.tmp.bpf.o,$@)
# Generate BPF skeletons
$(OUTPUT)/%.skel.h: $(OUTPUT)/%.bpf.o | $(OUTPUT) $(BPFTOOL)
$(call msg,GEN-SKEL,$@)
$(Q)$(BPFTOOL) gen skeleton $< > $@
# Build user-space code
$(patsubst %,$(OUTPUT)/%.o,$(APPS)): %.o: %.skel.h
$(OUTPUT)/%.o: %.c $(wildcard %.h) | $(OUTPUT)
$(call msg,CC,$@)
$(Q)$(CC) $(CFLAGS) $(INCLUDES) -c $(filter %.c,$^) -o $@
$(patsubst %,$(OUTPUT)/%.o,$(BZS_APPS)): $(LIBBLAZESYM_HEADER)
$(BZS_APPS): $(LIBBLAZESYM_OBJ)
# Build application binary
$(APPS): %: $(OUTPUT)/%.o $(LIBBPF_OBJ) | $(OUTPUT)
$(call msg,BINARY,$@)
$(Q)$(CC) $(CFLAGS) $^ $(ALL_LDFLAGS) -lelf -lz -o $@
# delete failed targets
.DELETE_ON_ERROR:
# keep intermediate (.skel.h, .bpf.o, etc) targets
.SECONDARY:

211
23-http/accept.bpf.c Normal file
View File

@@ -0,0 +1,211 @@
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>
#include <bpf/bpf_core_read.h>
#include "accept.h"
struct conn_id_t
{
u32 pid;
int fd;
__u64 tsid;
};
struct conn_info_t
{
struct conn_id_t conn_id;
__s64 wr_bytes;
__s64 rd_bytes;
bool is_http;
};
// A struct describing the event that we send to the user mode upon a new connection.
struct socket_open_event_t
{
// The time of the event.
u64 timestamp_ns;
// A unique ID for the connection.
struct conn_id_t conn_id;
};
struct
{
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 131072);
__type(key, __u64);
__type(value, struct conn_info_t);
} conn_info_map SEC(".maps");
struct
{
__uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
__uint(key_size, sizeof(u32));
__uint(value_size, sizeof(u32));
} events SEC(".maps");
struct accept_args_t
{
struct sockaddr_in *addr;
};
struct
{
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 4096);
__type(key, u64);
__type(value, struct accept_args_t);
} active_accept_args_map SEC(".maps");
SEC("tracepoint/syscalls/sys_enter_accept")
int sys_enter_accept(struct trace_event_raw_sys_enter *ctx)
{
u64 id = bpf_get_current_pid_tgid();
struct accept_args_t accept_args = {};
accept_args.addr = (struct sockaddr_in *)BPF_CORE_READ(ctx, args[1]);
bpf_map_update_elem(&active_accept_args_map, &id, &accept_args, BPF_ANY);
bpf_printk("enter_accept accept_args.addr: %llx\n", accept_args.addr);
return 0;
}
SEC("tracepoint/syscalls/sys_exit_accept")
int sys_exit_accept(struct trace_event_raw_sys_exit *ctx)
{
u64 id = bpf_get_current_pid_tgid();
struct accept_args_t *args =
bpf_map_lookup_elem(&active_accept_args_map, &id);
if (args == NULL)
{
return 0;
}
bpf_printk("exit_accept accept_args.addr: %llx\n", args->addr);
int ret_fd = (int)BPF_CORE_READ(ctx, ret);
if (ret_fd <= 0)
{
return 0;
}
struct conn_info_t conn_info = {};
u32 pid = id >> 32;
conn_info.conn_id.pid = pid;
conn_info.conn_id.fd = ret_fd;
conn_info.conn_id.tsid = bpf_ktime_get_ns();
__u64 pid_fd = ((__u64)pid << 32) | (u32)ret_fd;
bpf_map_update_elem(&conn_info_map, &pid_fd, &conn_info, BPF_ANY);
struct socket_data_event_t open_event = {};
open_event.timestamp_ns = bpf_ktime_get_ns();
open_event.pid = conn_info.conn_id.pid;
open_event.fd = conn_info.conn_id.fd;
open_event.is_connection = true;
bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU,
&open_event, sizeof(struct socket_data_event_t));
bpf_map_delete_elem(&active_accept_args_map, &id);
}
struct data_args_t
{
__s32 fd;
const char *buf;
};
struct
{
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, 4096);
__type(key, u64);
__type(value, struct data_args_t);
} active_read_args_map SEC(".maps");
SEC("tracepoint/syscalls/sys_enter_read")
int sys_enter_read(struct trace_event_raw_sys_enter *ctx)
{
u64 id = bpf_get_current_pid_tgid();
struct data_args_t read_args = {};
read_args.fd = (int)BPF_CORE_READ(ctx, args[0]);
read_args.buf = (char *)BPF_CORE_READ(ctx, args[1]);
bpf_map_update_elem(&active_read_args_map, &id, &read_args, BPF_ANY);
return 0;
}
static inline bool is_http_connection(const char *line_buffer, u64 bytes_count)
{
if (bytes_count < 6)
{
return 0;
}
if (bpf_strncmp(line_buffer, 3, "GET") != 0 && bpf_strncmp(line_buffer, 4, "POST") != 0 && bpf_strncmp(line_buffer, 3, "PUT") != 0 && bpf_strncmp(line_buffer, 6, "DELETE") != 0 && bpf_strncmp(line_buffer, 4, "HTTP") != 0)
{
return 0;
}
return 1;
}
static inline void process_data(struct trace_event_raw_sys_exit *ctx,
u64 id, const struct data_args_t *args, u64 bytes_count)
{
if (args->buf == NULL)
{
return;
}
u32 pid = id >> 32;
u64 pid_fd = ((u64)pid << 32) | (u64)args->fd;
struct conn_info_t *conn_info = bpf_map_lookup_elem(&conn_info_map, &pid_fd);
if (conn_info == NULL)
{
return;
}
if (args->buf == NULL)
{
return;
}
char line_buffer[7];
bpf_probe_read_kernel(line_buffer, 7, args->buf);
if (is_http_connection(line_buffer, bytes_count))
{
u32 kZero = 0;
struct socket_data_event_t event = {};
event.timestamp_ns = bpf_ktime_get_ns();
event.is_connection = false;
event.pid = conn_info->conn_id.pid;
event.fd = conn_info->conn_id.fd;
unsigned int read_size = bytes_count > MAX_MSG_SIZE ? MAX_MSG_SIZE : bytes_count;
bpf_probe_read_kernel(&event.msg, read_size, args->buf);
bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU,
&event, sizeof(struct socket_data_event_t));
}
}
SEC("tracepoint/syscalls/sys_exit_read")
int sys_exit_read(struct trace_event_raw_sys_exit *ctx)
{
u64 bytes_count = (u64)BPF_CORE_READ(ctx, ret);
if (bytes_count <= 0)
{
return 0;
}
u64 id = bpf_get_current_pid_tgid();
struct data_args_t *read_args = bpf_map_lookup_elem(&active_read_args_map, &id);
if (read_args != NULL)
{
process_data(ctx, id, read_args, bytes_count);
}
bpf_map_delete_elem(&active_read_args_map, &id);
return 0;
}
char _license[] SEC("license") = "GPL";

17
23-http/accept.h Normal file
View File

@@ -0,0 +1,17 @@
#ifndef BPF_HTTP_ACCEPT_TRACE_H
#define BPF_HTTP_ACCEPT_TRACE_H
#define MAX_MSG_SIZE 256
struct socket_data_event_t
{
unsigned long long timestamp_ns;
unsigned int pid;
int fd;
bool is_connection;
unsigned int msg_size;
unsigned long long pos;
char msg[MAX_MSG_SIZE];
};
#endif // BPF_HTTP_ACCEPT_TRACE_H

File diff suppressed because one or more lines are too long

View File

@@ -1,103 +0,0 @@
/*
* Copyright 2018- The Pixie Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package main
import (
"fmt"
bpfwrapper2 "github.com/seek-ret/ebpf-training/workshop1/internal/bpfwrapper"
"github.com/seek-ret/ebpf-training/workshop1/internal/connections"
"github.com/seek-ret/ebpf-training/workshop1/internal/settings"
"io/ioutil"
"log"
"os"
"os/signal"
"os/user"
"runtime/debug"
"syscall"
"time"
"github.com/iovisor/gobpf/bcc"
)
// abortIfNotRoot checks the current user permissions, if the permissions are not elevated, we abort.
func abortIfNotRoot() {
current, err := user.Current()
if err != nil {
log.Panic(err)
}
if current.Uid != "0" {
log.Panic("sniffer must run under superuser privileges")
}
}
// recoverFromCrashes is a defer function that caches all panics being thrown from the application.
func recoverFromCrashes() {
if err := recover(); err != nil {
log.Printf("Application crashed: %v\nstack: %s\n", err, string(debug.Stack()))
}
}
func main() {
if len(os.Args) != 2 {
fmt.Println("Usage: go run main.go <path to bpf source code>")
os.Exit(1)
}
bpfSourceCodeFile := os.Args[1]
bpfSourceCodeContent, err := ioutil.ReadFile(bpfSourceCodeFile)
if err != nil {
log.Panic(err)
}
defer recoverFromCrashes()
abortIfNotRoot()
if err := settings.InitRealTimeOffset(); err != nil {
log.Printf("Failed fixing BPF clock, timings will be offseted: %v", err)
}
// Catching all termination signals to perform a cleanup when being stopped.
sig := make(chan os.Signal, 1)
signal.Notify(sig, syscall.SIGHUP, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM)
bpfModule := bcc.NewModule(string(bpfSourceCodeContent), nil)
if bpfModule == nil {
log.Panic("bpf is nil")
}
defer bpfModule.Close()
connectionFactory := connections.NewFactory(time.Minute)
go func() {
for {
connectionFactory.HandleReadyConnections()
time.Sleep(10 * time.Second)
}
}()
if err := bpfwrapper2.LaunchPerfBufferConsumers(bpfModule, connectionFactory); err != nil {
log.Panic(err)
}
// Lastly, after everything is ready and configured, attach the kprobes and start capturing traffic.
if err := bpfwrapper2.AttachKprobes(bpfModule); err != nil {
log.Panic(err)
}
log.Println("Sniffer is ready")
<-sig
log.Println("Signaled to terminate")
}

136
23-http/sockfilter.bpf.c Normal file
View File

@@ -0,0 +1,136 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/* Copyright (c) 2022 Jacky Yin */
#include <stddef.h>
#include <linux/bpf.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/in.h>
#include <linux/socket.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>
#include "sockfilter.h"
#define IP_MF 0x2000
#define IP_OFFSET 0x1FFF
#define IP_TCP 6
#define ETH_HLEN 14
char LICENSE[] SEC("license") = "Dual BSD/GPL";
struct
{
__uint(type, BPF_MAP_TYPE_RINGBUF);
__uint(max_entries, 256 * 1024);
} rb SEC(".maps");
// Taken from uapi/linux/tcp.h
struct __tcphdr
{
__be16 source;
__be16 dest;
__be32 seq;
__be32 ack_seq;
__u16 res1 : 4, doff : 4, fin : 1, syn : 1, rst : 1, psh : 1, ack : 1, urg : 1, ece : 1, cwr : 1;
__be16 window;
__sum16 check;
__be16 urg_ptr;
};
static inline int ip_is_fragment(struct __sk_buff *skb, __u32 nhoff)
{
__u16 frag_off;
bpf_skb_load_bytes(skb, nhoff + offsetof(struct iphdr, frag_off), &frag_off, 2);
frag_off = __bpf_ntohs(frag_off);
return frag_off & (IP_MF | IP_OFFSET);
}
SEC("socket")
int socket_handler(struct __sk_buff *skb)
{
struct so_event *e;
__u8 verlen;
__u16 proto;
__u32 nhoff = ETH_HLEN;
__u32 ip_proto = 0;
__u32 tcp_hdr_len = 0;
__u16 tlen;
__u32 payload_offset = 0;
__u32 payload_length = 0;
__u8 hdr_len;
bpf_skb_load_bytes(skb, 12, &proto, 2);
proto = __bpf_ntohs(proto);
if (proto != ETH_P_IP)
return 0;
if (ip_is_fragment(skb, nhoff))
return 0;
// ip4 header lengths are variable
// access ihl as a u8 (linux/include/linux/skbuff.h)
bpf_skb_load_bytes(skb, ETH_HLEN, &hdr_len, sizeof(hdr_len));
hdr_len &= 0x0f;
hdr_len *= 4;
/* verify hlen meets minimum size requirements */
if (hdr_len < sizeof(struct iphdr))
{
return 0;
}
bpf_skb_load_bytes(skb, nhoff + offsetof(struct iphdr, protocol), &ip_proto, 1);
if (ip_proto != IPPROTO_TCP)
{
return 0;
}
tcp_hdr_len = nhoff + hdr_len;
bpf_skb_load_bytes(skb, nhoff + 0, &verlen, 1);
bpf_skb_load_bytes(skb, nhoff + offsetof(struct iphdr, tot_len), &tlen, sizeof(tlen));
__u8 doff;
bpf_skb_load_bytes(skb, tcp_hdr_len + offsetof(struct __tcphdr, ack_seq) + 4, &doff, sizeof(doff)); // read the first byte past __tcphdr->ack_seq, we can't do offsetof bit fields
doff &= 0xf0; // clean-up res1
doff >>= 4; // move the upper 4 bits to low
doff *= 4; // convert to bytes length
payload_offset = ETH_HLEN + hdr_len + doff;
payload_length = __bpf_ntohs(tlen) - hdr_len - doff;
char line_buffer[7];
if (payload_length < 7 || payload_offset < 0)
{
return 0;
}
bpf_skb_load_bytes(skb, payload_offset, line_buffer, 7);
bpf_printk("%d len %d buffer: %s", payload_offset, payload_length, line_buffer);
if (bpf_strncmp(line_buffer, 3, "GET") != 0 &&
bpf_strncmp(line_buffer, 4, "POST") != 0 &&
bpf_strncmp(line_buffer, 3, "PUT") != 0 &&
bpf_strncmp(line_buffer, 6, "DELETE") != 0 &&
bpf_strncmp(line_buffer, 4, "HTTP") != 0)
{
return 0;
}
/* reserve sample from BPF ringbuf */
e = bpf_ringbuf_reserve(&rb, sizeof(*e), 0);
if (!e)
return 0;
e->ip_proto = ip_proto;
bpf_skb_load_bytes(skb, nhoff + hdr_len, &(e->ports), 4);
e->pkt_type = skb->pkt_type;
e->ifindex = skb->ifindex;
e->payload_length = payload_length;
bpf_skb_load_bytes(skb, payload_offset, e->payload, MAX_BUF_SIZE);
bpf_skb_load_bytes(skb, nhoff + offsetof(struct iphdr, saddr), &(e->src_addr), 4);
bpf_skb_load_bytes(skb, nhoff + offsetof(struct iphdr, daddr), &(e->dst_addr), 4);
bpf_ringbuf_submit(e, 0);
return skb->len;
}

149
23-http/sockfilter.c Normal file
View File

@@ -0,0 +1,149 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/* Copyright (c) 2022 Jacky Yin */
#include <argp.h>
#include <arpa/inet.h>
#include <assert.h>
#include <bpf/libbpf.h>
#include <linux/if_packet.h>
#include <linux/if_ether.h>
#include <linux/in.h>
#include <net/if.h>
#include <signal.h>
#include <stdio.h>
#include <sys/resource.h>
#include <sys/socket.h>
#include <unistd.h>
#include "sockfilter.h"
#include "sockfilter.skel.h"
static int open_raw_sock(const char *name)
{
struct sockaddr_ll sll;
int sock;
sock = socket(PF_PACKET, SOCK_RAW | SOCK_NONBLOCK | SOCK_CLOEXEC, htons(ETH_P_ALL));
if (sock < 0) {
fprintf(stderr, "Failed to create raw socket\n");
return -1;
}
memset(&sll, 0, sizeof(sll));
sll.sll_family = AF_PACKET;
sll.sll_ifindex = if_nametoindex(name);
sll.sll_protocol = htons(ETH_P_ALL);
if (bind(sock, (struct sockaddr *)&sll, sizeof(sll)) < 0) {
fprintf(stderr, "Failed to bind to %s: %s\n", name, strerror(errno));
close(sock);
return -1;
}
return sock;
}
static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args)
{
return vfprintf(stderr, format, args);
}
static inline void ltoa(uint32_t addr, char *dst)
{
snprintf(dst, 16, "%u.%u.%u.%u", (addr >> 24) & 0xFF, (addr >> 16) & 0xFF,
(addr >> 8) & 0xFF, (addr & 0xFF));
}
static int handle_event(void *ctx, void *data, size_t data_sz)
{
const struct so_event *e = data;
char ifname[IF_NAMESIZE];
char sstr[16] = {}, dstr[16] = {};
if (e->pkt_type != PACKET_HOST)
return 0;
if (e->ip_proto < 0 || e->ip_proto >= IPPROTO_MAX)
return 0;
if (!if_indextoname(e->ifindex, ifname))
return 0;
ltoa(ntohl(e->src_addr), sstr);
ltoa(ntohl(e->dst_addr), dstr);
printf("%s:%d(src) -> %s:%d(dst)\n", sstr, ntohs(e->port16[0]), dstr, ntohs(e->port16[1]));
printf("payload: %s\n", e->payload);
return 0;
}
static volatile bool exiting = false;
static void sig_handler(int sig)
{
exiting = true;
}
int main(int argc, char **argv)
{
struct ring_buffer *rb = NULL;
struct sockfilter_bpf *skel;
int err, prog_fd, sock;
const char* interface = "lo";
/* Set up libbpf errors and debug info callback */
libbpf_set_print(libbpf_print_fn);
/* Cleaner handling of Ctrl-C */
signal(SIGINT, sig_handler);
signal(SIGTERM, sig_handler);
/* Load and verify BPF programs*/
skel = sockfilter_bpf__open_and_load();
if (!skel) {
fprintf(stderr, "Failed to open and load BPF skeleton\n");
return 1;
}
/* Set up ring buffer polling */
rb = ring_buffer__new(bpf_map__fd(skel->maps.rb), handle_event, NULL, NULL);
if (!rb) {
err = -1;
fprintf(stderr, "Failed to create ring buffer\n");
goto cleanup;
}
/* Create raw socket for localhost interface */
sock = open_raw_sock(interface);
if (sock < 0) {
err = -2;
fprintf(stderr, "Failed to open raw socket\n");
goto cleanup;
}
/* Attach BPF program to raw socket */
prog_fd = bpf_program__fd(skel->progs.socket_handler);
if (setsockopt(sock, SOL_SOCKET, SO_ATTACH_BPF, &prog_fd, sizeof(prog_fd))) {
err = -3;
fprintf(stderr, "Failed to attach to raw socket\n");
goto cleanup;
}
/* Process events */
while (!exiting) {
err = ring_buffer__poll(rb, 100 /* timeout, ms */);
/* Ctrl-C will cause -EINTR */
if (err == -EINTR) {
err = 0;
break;
}
if (err < 0) {
fprintf(stderr, "Error polling perf buffer: %d\n", err);
break;
}
sleep(1);
}
cleanup:
ring_buffer__free(rb);
sockfilter_bpf__destroy(skel);
return -err;
}

22
23-http/sockfilter.h Normal file
View File

@@ -0,0 +1,22 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/* Copyright (c) 2022 Jacky Yin */
#ifndef __SOCKFILTER_H
#define __SOCKFILTER_H
#define MAX_BUF_SIZE 64
struct so_event {
__be32 src_addr;
__be32 dst_addr;
union {
__be32 ports;
__be16 port16[2];
};
__u32 ip_proto;
__u32 pkt_type;
__u32 ifindex;
__u32 payload_length;
__u8 payload[MAX_BUF_SIZE];
};
#endif /* __SOCKFILTER_H */

View File

@@ -1,497 +0,0 @@
// +build ignore
/*
* Copyright 2018- The Pixie Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <linux/in6.h>
#include <linux/net.h>
#include <linux/socket.h>
#include <net/inet_sock.h>
// Defines
#define socklen_t size_t
// Data buffer message size. BPF can submit at most this amount of data to a perf buffer.
// Kernel size limit is 32KiB. See https://github.com/iovisor/bcc/issues/2519 for more details.
#define MAX_MSG_SIZE 30720 // 30KiB
// This defines how many chunks a perf_submit can support.
// This applies to messages that are over MAX_MSG_SIZE,
// and effectively makes the maximum message size to be CHUNK_LIMIT*MAX_MSG_SIZE.
#define CHUNK_LIMIT 4
enum traffic_direction_t {
kEgress,
kIngress,
};
// Structs
// A struct representing a unique ID that is composed of the pid, the file
// descriptor and the creation time of the struct.
struct conn_id_t {
// Process ID
uint32_t pid;
// The file descriptor to the opened network connection.
int32_t fd;
// Timestamp at the initialization of the struct.
uint64_t tsid;
};
// This struct contains information collected when a connection is established,
// via an accept4() syscall.
struct conn_info_t {
// Connection identifier.
struct conn_id_t conn_id;
// The number of bytes written/read on this connection.
int64_t wr_bytes;
int64_t rd_bytes;
// A flag indicating we identified the connection as HTTP.
bool is_http;
};
// An helper struct that hold the addr argument of the syscall.
struct accept_args_t {
struct sockaddr_in* addr;
};
// An helper struct to cache input argument of read/write syscalls between the
// entry hook and the exit hook.
struct data_args_t {
int32_t fd;
const char* buf;
};
// An helper struct that hold the input arguments of the close syscall.
struct close_args_t {
int32_t fd;
};
// A struct describing the event that we send to the user mode upon a new connection.
struct socket_open_event_t {
// The time of the event.
uint64_t timestamp_ns;
// A unique ID for the connection.
struct conn_id_t conn_id;
// The address of the client.
struct sockaddr_in addr;
};
// Struct describing the close event being sent to the user mode.
struct socket_close_event_t {
// Timestamp of the close syscall
uint64_t timestamp_ns;
// The unique ID of the connection
struct conn_id_t conn_id;
// Total number of bytes written on that connection
int64_t wr_bytes;
// Total number of bytes read on that connection
int64_t rd_bytes;
};
struct socket_data_event_t {
// We split attributes into a separate struct, because BPF gets upset if you do lots of
// size arithmetic. This makes it so that it's attributes followed by message.
struct attr_t {
// The timestamp when syscall completed (return probe was triggered).
uint64_t timestamp_ns;
// Connection identifier (PID, FD, etc.).
struct conn_id_t conn_id;
// The type of the actual data that the msg field encodes, which is used by the caller
// to determine how to interpret the data.
enum traffic_direction_t direction;
// The size of the original message. We use this to truncate msg field to minimize the amount
// of data being transferred.
uint32_t msg_size;
// A 0-based position number for this event on the connection, in terms of byte position.
// The position is for the first byte of this message.
uint64_t pos;
} attr;
char msg[MAX_MSG_SIZE];
};
// Maps
// A map of the active connections. The name of the map is conn_info_map
// the key is of type uint64_t, the value is of type struct conn_info_t,
// and the map won't be bigger than 128KB.
BPF_HASH(conn_info_map, uint64_t, struct conn_info_t, 131072);
// An helper map that will help us cache the input arguments of the accept syscall
// between the entry hook and the return hook.
BPF_HASH(active_accept_args_map, uint64_t, struct accept_args_t);
// Perf buffer to send to the user-mode the data events.
BPF_PERF_OUTPUT(socket_data_events);
// A perf buffer that allows us send events from kernel to user mode.
// This perf buffer is dedicated for special type of events - open events.
BPF_PERF_OUTPUT(socket_open_events);
// Perf buffer to send to the user-mode the close events.
BPF_PERF_OUTPUT(socket_close_events);
BPF_PERCPU_ARRAY(socket_data_event_buffer_heap, struct socket_data_event_t, 1);
BPF_HASH(active_write_args_map, uint64_t, struct data_args_t);
// Helper map to store read syscall arguments between entry and exit hooks.
BPF_HASH(active_read_args_map, uint64_t, struct data_args_t);
// An helper map to store close syscall arguments between entry and exit syscalls.
BPF_HASH(active_close_args_map, uint64_t, struct close_args_t);
// Helper functions
// Generates a unique identifier using a tgid (Thread Global ID) and a fd (File Descriptor).
static __inline uint64_t gen_tgid_fd(uint32_t tgid, int fd) {
return ((uint64_t)tgid << 32) | (uint32_t)fd;
}
// An helper function that checks if the syscall finished successfully and if it did
// saves the new connection in a dedicated map of connections
static __inline void process_syscall_accept(struct pt_regs* ctx, uint64_t id, const struct accept_args_t* args) {
// Extracting the return code, and checking if it represent a failure,
// if it does, we abort the as we have nothing to do.
int ret_fd = PT_REGS_RC(ctx);
if (ret_fd <= 0) {
return;
}
struct conn_info_t conn_info = {};
uint32_t pid = id >> 32;
conn_info.conn_id.pid = pid;
conn_info.conn_id.fd = ret_fd;
conn_info.conn_id.tsid = bpf_ktime_get_ns();
uint64_t pid_fd = ((uint64_t)pid << 32) | (uint32_t)ret_fd;
// Saving the connection info in a global map, so in the other syscalls
// (read, write and close) we will be able to know that we have seen
// the connection
conn_info_map.update(&pid_fd, &conn_info);
// Sending an open event to the user mode, to let the user mode know that we
// have identified a new connection.
struct socket_open_event_t open_event = {};
open_event.timestamp_ns = bpf_ktime_get_ns();
open_event.conn_id = conn_info.conn_id;
bpf_probe_read(&open_event.addr, sizeof(open_event.addr), args->addr);
socket_open_events.perf_submit(ctx, &open_event, sizeof(struct socket_open_event_t));
}
static inline __attribute__((__always_inline__)) void process_syscall_close(struct pt_regs* ctx, uint64_t id,
const struct close_args_t* close_args) {
int ret_val = PT_REGS_RC(ctx);
if (ret_val < 0) {
return;
}
uint32_t tgid = id >> 32;
uint64_t tgid_fd = gen_tgid_fd(tgid, close_args->fd);
struct conn_info_t* conn_info = conn_info_map.lookup(&tgid_fd);
if (conn_info == NULL) {
// The FD being closed does not represent an IPv4 socket FD.
return;
}
// Send to the user mode an event indicating the connection was closed.
struct socket_close_event_t close_event = {};
close_event.timestamp_ns = bpf_ktime_get_ns();
close_event.conn_id = conn_info->conn_id;
close_event.rd_bytes = conn_info->rd_bytes;
close_event.wr_bytes = conn_info->wr_bytes;
socket_close_events.perf_submit(ctx, &close_event, sizeof(struct socket_close_event_t));
// Remove the connection from the mapping.
conn_info_map.delete(&tgid_fd);
}
static inline __attribute__((__always_inline__)) bool is_http_connection(struct conn_info_t* conn_info, const char* buf, size_t count) {
// If the connection was already identified as HTTP connection, no need to re-check it.
if (conn_info->is_http) {
return true;
}
// The minimum length of http request or response.
if (count < 16) {
return false;
}
bool res = false;
if (buf[0] == 'H' && buf[1] == 'T' && buf[2] == 'T' && buf[3] == 'P') {
res = true;
}
if (buf[0] == 'G' && buf[1] == 'E' && buf[2] == 'T') {
res = true;
}
if (buf[0] == 'P' && buf[1] == 'O' && buf[2] == 'S' && buf[3] == 'T') {
res = true;
}
if (res) {
conn_info->is_http = true;
}
return res;
}
static __inline void perf_submit_buf(struct pt_regs* ctx, const enum traffic_direction_t direction,
const char* buf, size_t buf_size, size_t offset,
struct conn_info_t* conn_info,
struct socket_data_event_t* event) {
switch (direction) {
case kEgress:
event->attr.pos = conn_info->wr_bytes + offset;
break;
case kIngress:
event->attr.pos = conn_info->rd_bytes + offset;
break;
}
// Note that buf_size_minus_1 will be positive due to the if-statement above.
size_t buf_size_minus_1 = buf_size - 1;
// Clang is too smart for us, and tries to remove some of the obvious hints we are leaving for the
// BPF verifier. So we add this NOP volatile statement, so clang can't optimize away some of our
// if-statements below.
// By telling clang that buf_size_minus_1 is both an input and output to some black box assembly
// code, clang has to discard any assumptions on what values this variable can take.
asm volatile("" : "+r"(buf_size_minus_1) :);
buf_size = buf_size_minus_1 + 1;
// 4.14 kernels reject bpf_probe_read with size that they may think is zero.
// Without the if statement, it somehow can't reason that the bpf_probe_read is non-zero.
size_t amount_copied = 0;
if (buf_size_minus_1 < MAX_MSG_SIZE) {
bpf_probe_read(&event->msg, buf_size, buf);
amount_copied = buf_size;
} else {
bpf_probe_read(&event->msg, MAX_MSG_SIZE, buf);
amount_copied = MAX_MSG_SIZE;
}
// If-statement is redundant, but is required to keep the 4.14 verifier happy.
if (amount_copied > 0) {
event->attr.msg_size = amount_copied;
socket_data_events.perf_submit(ctx, event, sizeof(event->attr) + amount_copied);
}
}
static __inline void perf_submit_wrapper(struct pt_regs* ctx,
const enum traffic_direction_t direction, const char* buf,
const size_t buf_size, struct conn_info_t* conn_info,
struct socket_data_event_t* event) {
int bytes_sent = 0;
unsigned int i;
#pragma unroll
for (i = 0; i < CHUNK_LIMIT; ++i) {
const int bytes_remaining = buf_size - bytes_sent;
const size_t current_size = (bytes_remaining > MAX_MSG_SIZE && (i != CHUNK_LIMIT - 1)) ? MAX_MSG_SIZE : bytes_remaining;
perf_submit_buf(ctx, direction, buf + bytes_sent, current_size, bytes_sent, conn_info, event);
bytes_sent += current_size;
if (buf_size == bytes_sent) {
return;
}
}
}
static inline __attribute__((__always_inline__)) void process_data(struct pt_regs* ctx, uint64_t id,
enum traffic_direction_t direction,
const struct data_args_t* args, ssize_t bytes_count) {
// Always check access to pointer before accessing them.
if (args->buf == NULL) {
return;
}
// For read and write syscall, the return code is the number of bytes written or read, so zero means nothing
// was written or read, and negative means that the syscall failed. Anyhow, we have nothing to do with that syscall.
if (bytes_count <= 0) {
return;
}
uint32_t pid = id >> 32;
uint64_t pid_fd = ((uint64_t)pid << 32) | (uint32_t)args->fd;
struct conn_info_t* conn_info = conn_info_map.lookup(&pid_fd);
if (conn_info == NULL) {
// The FD being read/written does not represent an IPv4 socket FD.
return;
}
// Check if the connection is already HTTP, or check if that's a new connection, check protocol and return true if that's HTTP.
if (is_http_connection(conn_info, args->buf, bytes_count)) {
// allocate new event.
uint32_t kZero = 0;
struct socket_data_event_t* event = socket_data_event_buffer_heap.lookup(&kZero);
if (event == NULL) {
return;
}
// Fill the metadata of the data event.
event->attr.timestamp_ns = bpf_ktime_get_ns();
event->attr.direction = direction;
event->attr.conn_id = conn_info->conn_id;
perf_submit_wrapper(ctx, direction, args->buf, bytes_count, conn_info, event);
}
// Update the conn_info total written/read bytes.
switch (direction) {
case kEgress:
conn_info->wr_bytes += bytes_count;
break;
case kIngress:
conn_info->rd_bytes += bytes_count;
break;
}
}
// Hooks
int syscall__probe_entry_accept(struct pt_regs* ctx, int sockfd, struct sockaddr* addr, socklen_t* addrlen) {
uint64_t id = bpf_get_current_pid_tgid();
// Keep the addr in a map to use during the exit method.
struct accept_args_t accept_args = {};
accept_args.addr = (struct sockaddr_in *)addr;
active_accept_args_map.update(&id, &accept_args);
return 0;
}
int syscall__probe_ret_accept(struct pt_regs* ctx) {
uint64_t id = bpf_get_current_pid_tgid();
// Pulling the addr from the map.
struct accept_args_t* accept_args = active_accept_args_map.lookup(&id);
if (accept_args != NULL) {
process_syscall_accept(ctx, id, accept_args);
}
active_accept_args_map.delete(&id);
return 0;
}
// Hooking the entry of accept4
// the signature of the syscall is int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
int syscall__probe_entry_accept4(struct pt_regs* ctx, int sockfd, struct sockaddr* addr, socklen_t* addrlen) {
// Getting a unique ID for the relevant thread in the relevant pid.
// That way we can link different calls from the same thread.
uint64_t id = bpf_get_current_pid_tgid();
// Keep the addr in a map to use during the accpet4 exit hook.
struct accept_args_t accept_args = {};
accept_args.addr = (struct sockaddr_in *)addr;
active_accept_args_map.update(&id, &accept_args);
return 0;
}
// Hooking the exit of accept4
int syscall__probe_ret_accept4(struct pt_regs* ctx) {
uint64_t id = bpf_get_current_pid_tgid();
// Pulling the addr from the map.
struct accept_args_t* accept_args = active_accept_args_map.lookup(&id);
// If the id exist in the map, we will get a non empty pointer that holds
// the input address argument from the entry of the syscall.
if (accept_args != NULL) {
process_syscall_accept(ctx, id, accept_args);
}
// Anyway, in the end clean the map.
active_accept_args_map.delete(&id);
return 0;
}
// original signature: ssize_t write(int fd, const void *buf, size_t count);
int syscall__probe_entry_write(struct pt_regs* ctx, int fd, char* buf, size_t count) {
uint64_t id = bpf_get_current_pid_tgid();
struct data_args_t write_args = {};
write_args.fd = fd;
write_args.buf = buf;
active_write_args_map.update(&id, &write_args);
return 0;
}
int syscall__probe_ret_write(struct pt_regs* ctx) {
uint64_t id = bpf_get_current_pid_tgid();
ssize_t bytes_count = PT_REGS_RC(ctx); // Also stands for return code.
// Unstash arguments, and process syscall.
struct data_args_t* write_args = active_write_args_map.lookup(&id);
if (write_args != NULL) {
process_data(ctx, id, kEgress, write_args, bytes_count);
}
active_write_args_map.delete(&id);
return 0;
}
// original signature: ssize_t read(int fd, void *buf, size_t count);
int syscall__probe_entry_read(struct pt_regs* ctx, int fd, char* buf, size_t count) {
uint64_t id = bpf_get_current_pid_tgid();
// Stash arguments.
struct data_args_t read_args = {};
read_args.fd = fd;
read_args.buf = buf;
active_read_args_map.update(&id, &read_args);
return 0;
}
int syscall__probe_ret_read(struct pt_regs* ctx) {
uint64_t id = bpf_get_current_pid_tgid();
// The return code the syscall is the number of bytes read as well.
ssize_t bytes_count = PT_REGS_RC(ctx);
struct data_args_t* read_args = active_read_args_map.lookup(&id);
if (read_args != NULL) {
// kIngress is an enum value that let's the process_data function
// to know whether the input buffer is incoming or outgoing.
process_data(ctx, id, kIngress, read_args, bytes_count);
}
active_read_args_map.delete(&id);
return 0;
}
// original signature: int close(int fd)
int syscall__probe_entry_close(struct pt_regs* ctx, int fd) {
uint64_t id = bpf_get_current_pid_tgid();
struct close_args_t close_args;
close_args.fd = fd;
active_close_args_map.update(&id, &close_args);
return 0;
}
int syscall__probe_ret_close(struct pt_regs* ctx) {
uint64_t id = bpf_get_current_pid_tgid();
const struct close_args_t* close_args = active_close_args_map.lookup(&id);
if (close_args != NULL) {
process_syscall_close(ctx, id, close_args);
}
active_close_args_map.delete(&id);
return 0;
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -3,7 +3,7 @@
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_core_read.h>
#include "common.h"
#include "replace.h"
char LICENSE[] SEC("license") = "Dual BSD/GPL";
@@ -268,7 +268,8 @@ int check_possible_addresses(struct trace_event_raw_sys_exit *ctx) {
// break;
// }
// }
// we can use bpf_strncmp here, but it's not available in the kernel version older
// we can use bpf_strncmp here,
// but it's not available in the kernel version older than 5.17
if (bpf_strncmp(name, text_len_max, (const char *)text_find) == 0) {
// ***********
// We've found out text!

View File

@@ -2,7 +2,7 @@
#include <argp.h>
#include <unistd.h>
#include "replace.skel.h"
#include "common.h"
#include "replace.h"
#include <bpf/bpf.h>

View File

@@ -26,14 +26,4 @@ struct event {
bool success;
};
struct tr_file {
char filename[FILENAME_LEN_MAX];
unsigned int filename_len;
};
struct tr_text {
char text[TEXT_LEN_MAX];
unsigned int text_len;
};
#endif // BAD_BPF_COMMON_H

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,6 @@
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/* Copyright (c) 2021 Sartura */
#define BPF_NO_GLOBAL_DATA
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +1,4 @@
#define BPF_NO_GLOBAL_DATA
#include <vmlinux.h>
#include <bpf/bpf_helpers.h>

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +1,4 @@
#define BPF_NO_GLOBAL_DATA
#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +1,4 @@
#define BPF_NO_GLOBAL_DATA
#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long