iovisor / iovisor/bcc

Can't resolve resolve event data properly

Open
#2,972 4 comments 0 reactions 0 assignees View on GitHub
Dominant language
C
Stars
22.7k
Forks
4.1k
Avg merge
10d 4h
Merged PRs (30d)
3

Description

I'm writing a tool to record every TCP event. However, I can only resolve IPV4 events properly, IPV6 events can't be resolved properly.
Here is a sample result:
![image](https://user-images.githubusercontent.com/12099844/84860530-c506c580-b0a1-11ea-9aac-2d1d548e4050.png)

Here is my tool implementation. Anything wrong? I can record IPV6 events correctly if record upon tcp_sendmsg/tcp_recvmsg entries without BPF_TABLE applied. However, to record the end time of the event, I have to use the return kprobe too, thus BPF_TABLE is used.

I'm running this tool on Chromium OS, kernel V5.4.

tcptrans.c:
```
#include
#include
#include
#include

enum TCP_EVENT_TYPE {
SEND = 0,
RECV = 1
};

enum IP_TYPE {
IPV4 = 0,
IPV6 = 1
};

struct ipv4_event_t {
u32 pid;
u8 type;
u32 saddr;
u32 daddr;
u16 sport;
u16 dport;
u64 size;
u64 start;
u64 end;
};
BPF_PERF_OUTPUT(ipv4_events);

struct ipv6_event_t {
u32 pid;
u8 type;
unsigned __int128 saddr;
unsigned __int128 daddr;
u16 sport;
u16 dport;
u64 size;
u64 start;
u64 end;
};
BPF_PERF_OUTPUT(ipv6_events);

struct tcp_event_t {
u8 ip_type;
};

// Update event details on entry, restore on return.
BPF_HASH(tcp_event_tmp, u64, struct tcp_event_t);
BPF_HASH(ipv4_event_tmp, u64, struct ipv4_event_t);
BPF_HASH(ipv6_event_tmp, u64, struct ipv6_event_t);

static int read_ipv4_event(struct ipv4_event_t *tuple, struct sock *skp) {
u32 saddr = skp->__sk_common.skc_rcv_saddr;
u32 daddr = skp->__sk_common.skc_daddr;
struct inet_sock *sockp = (struct inet_sock *)skp;
u16 sport = sockp->inet_sport;
u16 dport = skp->__sk_common.skc_dport;

tuple->saddr = saddr;
tuple->daddr = daddr;
tuple->sport = sport;
tuple->dport = dport;

return 0;
}

static int read_ipv6_event(struct ipv6_event_t *tuple, struct sock *skp) {
unsigned __int128 saddr = 0, daddr = 0;
struct inet_sock *sockp = (struct inet_sock *)skp;
u16 sport = sockp->inet_sport;
u16 dport = skp->__sk_common.skc_dport;

bpf_probe_read(&saddr, sizeof(saddr),
skp->__sk_common.skc_v6_rcv_saddr.in6_u.u6_addr32);
bpf_probe_read(&daddr, sizeof(daddr),
skp->__sk_common.skc_v6_daddr.in6_u.u6_addr32);

tuple->saddr = saddr;
tuple->daddr = daddr;
tuple->sport = sport;
tuple->dport = dport;

return 0;
}

static void record_event(struct sock *sk, enum TCP_EVENT_TYPE event_type, size_t size) {
u64 id = bpf_get_current_pid_tgid();
u32 pid = id;
FILTER

struct tcp_event_t tcp_event = {};
u64 ts_ns = bpf_ktime_get_ns();

u16 family = sk->__sk_common.skc_family;
if (family == AF_INET) {
tcp_event.ip_type = IPV4;
tcp_event_tmp.update(&id, &tcp_event);

struct ipv4_event_t ipv4_event = {};
read_ipv4_event(&ipv4_event, sk);
ipv4_event.pid = pid;
ipv4_event.type = event_type;
ipv4_event.size = size;
ipv4_event.start = ts_ns;
ipv4_event.end = 0;
ipv4_event_tmp.update(&id, &ipv4_event);
} else if (family == AF_INET6) {
tcp_event.ip_type = IPV6;
tcp_event_tmp.update(&id, &tcp_event);

struct ipv6_event_t ipv6_event = {};
read_ipv6_event(&ipv6_event, sk);
ipv6_event.pid = pid;
ipv6_event.type = event_type;
ipv6_event.size = size;
ipv6_event.start = ts_ns;
ipv6_event.end = 0;
ipv6_event_tmp.update(&id, &ipv6_event);
}
// else drop
}

static void output_event(struct pt_regs *ctx, enum TCP_EVENT_TYPE event_type) {
u64 id = bpf_get_current_pid_tgid();
u32 pid = id;
FILTER

struct tcp_event_t *tcp_event = tcp_event_tmp.lookup(&id);
if (tcp_event == 0) {
return;
}

u64 ts_ns = bpf_ktime_get_ns();
int ret = PT_REGS_RC(ctx);
switch (tcp_event->ip_type) {
case IPV4: {
struct ipv4_event_t *ipv4_event = ipv4_event_tmp.lookup(&id);
if (ipv4_event == 0) {
return;
}
struct ipv4_event_t ipv4_data = {};
ipv4_data.pid = pid;
ipv4_data.type = event_type;
ipv4_data.saddr = ipv4_event->saddr;
ipv4_data.daddr = ipv4_event->daddr;
ipv4_data.sport = ipv4_event->sport;
ipv4_data.dport = ipv4_event->dport;
ipv4_data.size = ret;
ipv4_data.start = ipv4_event->start;
ipv4_data.end = ts_ns;
ipv4_events.perf_submit(ctx, &ipv4_data, sizeof(ipv4_data));
ipv4_event_tmp.delete(&id);
break;
}
case IPV6: {
struct ipv6_event_t *ipv6_event = ipv6_event_tmp.lookup(&id);
if (ipv6_event == 0) {
return;
}
struct ipv6_event_t ipv6_data = {};
ipv6_data.pid = pid;
ipv6_data.type = event_type;
ipv6_data.saddr = ipv6_event->saddr;
ipv6_data.daddr = ipv6_event->daddr;
ipv6_data.sport = ipv6_event->sport;
ipv6_data.dport = ipv6_event->dport;
ipv6_data.size = ret;
ipv6_data.start = ipv6_event->start;
ipv6_data.end = ts_ns;
ipv6_events.perf_submit(ctx, &ipv6_data, sizeof(ipv6_data));
ipv6_event_tmp.delete(&id);
break;
}
}
tcp_event_tmp.delete(&id);
}

int trace_tcp_sendmsg_entry(struct pt_regs *ctx, struct sock *sk,
struct msghdr *msg, size_t size) {
record_event(sk, SEND, size);
return 0;
}

int trace_tcp_sendmsg_return(struct pt_regs *ctx) {
output_event(ctx, SEND);
return 0;
}

int trace_tcp_recvmsg_entry(struct pt_regs *ctx, struct sock *sk, struct msghdr *msg,
size_t len, int nonblock, int flags, int *addr_len) {
record_event(sk, RECV, len);
return 0;
}

int trace_tcp_recvmsg_return(struct pt_regs *ctx) {
output_event(ctx, RECV);
return 0;
}
```
tcptrans.py:

```
#!/usr/bin/python

from __future__ import print_function
from bcc import BPF
import argparse
import os
from socket import inet_ntop, AF_INET, AF_INET6
from struct import pack
from time import sleep, strftime
from subprocess import call
from collections import namedtuple, defaultdict
import pandas as pd
import signal

"""
parser = argparse.ArgumentParser(
description="Trace TCP send/recv events by host",
formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument("-p", "--pid",
help="trace this PID only")
parser.add_argument("-o", "--output",
help="output file name")
parser.add_argument("--ebpf", action="store_true",
help=argparse.SUPPRESS)
args = parser.parse_args()
debug = 0

dir_path = os.path.dirname(os.path.realpath(__file__))
bcc_code_path = dir_path + "/tcptrans.c"
if not os.path.isfile(bcc_code_path):
print("Missing BCC probe code...")
exit()

with open(bcc_code_path, 'r') as f:
bpf_text = f.read()

# code substitutions
if args.pid:
bpf_text = bpf_text.replace('FILTER',
'if (pid != %s) { return; }' % args.pid)
else:
bpf_text = bpf_text.replace('FILTER', '')

output = "tcptrans"
if args.output:
output = args.output

if debug or args.ebpf:
print(bpf_text)
if args.ebpf:
exit()

# initialize BPF
b = BPF(text=bpf_text)
b.attach_kprobe(event="tcp_sendmsg", fn_name="trace_tcp_sendmsg_entry")
b.attach_kretprobe(event="tcp_sendmsg", fn_name="trace_tcp_sendmsg_return")
b.attach_kprobe(event="tcp_recvmsg", fn_name="trace_tcp_recvmsg_entry")
b.attach_kretprobe(event="tcp_recvmsg", fn_name="trace_tcp_recvmsg_return")

print("Tracing TCP SENV/RECV calls. Ctrl-C to end.")

tcp_events = []
event_types = {
0: "SEND",
1: "RECV"
}

def add_ipv4_event(cpu, data, size):
event = b["ipv4_events"].event(data)
v4_event = {
"pid": event.pid,
"cpu": cpu,
"type": event_types[event.type],
"saddr": inet_ntop(AF_INET, pack("I", event.saddr)),
"daddr": inet_ntop(AF_INET, pack("I", event.daddr)),
"sport": event.sport,
"dport": event.dport,
"size": event.size,
"start": event.start,
"end": event.end
}
tcp_events.append(v4_event)

def add_ipv6_event(cpu, data, size):
event = b["ipv6_events"].event(data)
v6_event = {
"pid": event.pid,
"cpu": cpu,
"type": event_types[event.type],
"saddr": inet_ntop(AF_INET6, event.saddr),
"daddr": inet_ntop(AF_INET6, event.daddr),
"sport": event.sport,
"dport": event.dport,
"size": event.size,
"start": event.start,
"end": event.end
}
tcp_events.append(v6_event)

b["ipv4_events"].open_perf_buffer(add_ipv4_event)
b["ipv6_events"].open_perf_buffer(add_ipv6_event)

def collect_data():
b.perf_buffer_poll()
df_entry = pd.DataFrame(tcp_events,
columns=["pid", "cpu", "type", "saddr", "daddr",
"sport", "dport", "size", "start", "end"])
df_entry.to_csv(output + ".csv", index=False)

def signal_handler(sig, frame):
collect_data()
exit()

signal.signal(signal.SIGINT, signal_handler)
while True:
try:
sleep(1)
except KeyboardInterrupt:
collect_data()
exit()

```

Contributor guide

No contributing guide indexed for this repository

Research direction

Start by reading tcptrans.c, especially record_event, output_event, and the IPv6 structures, then compare their data layout with add_ipv6_event in tcptrans.py. Reproduce the issue on the stated Chromium OS kernel 5.4 setup and trace the BPF_HASH and return-probe path. Done means IPv6 TCP events retain correctly resolved addresses and timing when recorded through the return probes.

Written by the indexing model from the issue text.

Assessment

Tech stack
c, linux, python
Domain
devtools, networking, observability
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.