mirror of
https://github.com/eunomia-bpf/bpf-developer-tutorial.git
synced 2026-02-03 18:24:27 +08:00
Deploying to gh-pages from @ eunomia-bpf/bpf-developer-tutorial@426e993507 🚀
This commit is contained in:
9
23-http/.gitignore
vendored
Normal file
9
23-http/.gitignore
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
.vscode
|
||||
package.json
|
||||
*.o
|
||||
*.skel.json
|
||||
*.skel.yaml
|
||||
package.yaml
|
||||
ecli
|
||||
sockfilter
|
||||
*.class
|
||||
141
23-http/Makefile
Normal file
141
23-http/Makefile
Normal 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
211
23-http/accept.bpf.c
Normal 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
17
23-http/accept.h
Normal 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
103
23-http/main.go
103
23-http/main.go
@@ -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
136
23-http/sockfilter.bpf.c
Normal 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
149
23-http/sockfilter.c
Normal 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
22
23-http/sockfilter.h
Normal 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 */
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user