Skip to content

Commit 4da44bd

Browse files
committed
libbpf-tools: add tcpdrop to trace TCP packet drops
Added tcpdrop tool, consisting of tcpdrop.bpf.c and tcpdrop.c, to trace TCP kernel-dropped packets using eBPF. Supports IPv4/IPv6 filtering and network namespace filtering, with output including timestamp, PID, IP addresses, ports, TCP state, and drop reason. Based on tcptop(8) from BCC. Signed-off-by: Lance Yang <[email protected]> Signed-off-by: Zi Li <[email protected]> Signed-off-by: Amaindex <[email protected]>
1 parent ee3ba78 commit 4da44bd

File tree

3 files changed

+676
-0
lines changed

3 files changed

+676
-0
lines changed

libbpf-tools/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ APPS = \
8686
tcptracer \
8787
tcpconnect \
8888
tcpconnlat \
89+
tcpdrop \
8990
tcplife \
9091
tcppktlat \
9192
tcprtt \

libbpf-tools/tcpdrop.bpf.c

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
#include "vmlinux.h"
2+
#include <bpf/bpf_helpers.h>
3+
#include <bpf/bpf_core_read.h>
4+
#include <bpf/bpf_tracing.h>
5+
#include <bpf/bpf_endian.h>
6+
7+
#ifndef AF_INET
8+
#define AF_INET 2
9+
#endif
10+
#ifndef AF_INET6
11+
#define AF_INET6 10
12+
#endif
13+
#ifndef TASK_COMM_LEN
14+
#define TASK_COMM_LEN 16
15+
#endif
16+
#ifndef ETH_P_IP
17+
#define ETH_P_IP 0x0800
18+
#endif
19+
#ifndef ETH_P_IPV6
20+
#define ETH_P_IPV6 0x86dd
21+
#endif
22+
23+
struct event {
24+
u64 timestamp;
25+
u32 pid;
26+
u32 drop_reason;
27+
u32 ip_version; // 4 for IPv4, 6 for IPv6
28+
union {
29+
u32 saddr_v4;
30+
unsigned __int128 saddr_v6;
31+
};
32+
union {
33+
u32 daddr_v4;
34+
unsigned __int128 daddr_v6;
35+
};
36+
u16 sport;
37+
u16 dport;
38+
u8 state;
39+
u8 tcpflags;
40+
char comm[TASK_COMM_LEN];
41+
u32 stack_id;
42+
};
43+
44+
struct {
45+
__uint(type, BPF_MAP_TYPE_RINGBUF);
46+
__uint(max_entries, 512);
47+
} events SEC(".maps");
48+
49+
#define MAX_STACK_DEPTH 15
50+
struct {
51+
__uint(type, BPF_MAP_TYPE_STACK_TRACE);
52+
__uint(max_entries, 512);
53+
__uint(key_size, sizeof(u32));
54+
__uint(value_size, MAX_STACK_DEPTH * sizeof(u64));
55+
} stack_traces SEC(".maps");
56+
57+
char ipv4_only = 0;
58+
char ipv6_only = 0;
59+
__u32 netns_id = 0;
60+
61+
SEC("tracepoint/skb/kfree_skb")
62+
int tp__skb_free_skb(struct trace_event_raw_kfree_skb *args)
63+
{
64+
if (args->reason <= SKB_DROP_REASON_NOT_SPECIFIED) {
65+
return 0;
66+
}
67+
68+
if (bpf_ringbuf_query(&events, BPF_RB_AVAIL_DATA) >= 511) {
69+
bpf_printk("Ring buffer is almost full\n");
70+
return 0;
71+
}
72+
73+
u64 pid_tgid = bpf_get_current_pid_tgid();
74+
u32 pid = pid_tgid >> 32;
75+
76+
struct sk_buff *skb = args->skbaddr;
77+
if (!skb) {
78+
return 0;
79+
}
80+
struct sock *sk = NULL;
81+
bpf_core_read(&sk, sizeof(sk), &skb->sk);
82+
83+
// Get packet headers
84+
void *head;
85+
u16 network_header, transport_header;
86+
if (bpf_core_read(&head, sizeof(head), &skb->head) ||
87+
bpf_core_read(&network_header, sizeof(network_header), &skb->network_header) ||
88+
bpf_core_read(&transport_header, sizeof(transport_header), &skb->transport_header)) {
89+
bpf_printk("Failed to read skb headers\n");
90+
return 0;
91+
}
92+
93+
// Check protocol and filter by IP family
94+
u16 protocol = args->protocol;
95+
if (protocol != ETH_P_IP && protocol != ETH_P_IPV6) {
96+
bpf_printk("Unsupported protocol: %u\n", protocol);
97+
return 0;
98+
}
99+
if (ipv4_only && protocol != ETH_P_IP) {
100+
return 0;
101+
}
102+
if (ipv6_only && protocol != ETH_P_IPV6) {
103+
return 0;
104+
}
105+
106+
// Filter by network namespace
107+
if (netns_id && sk) {
108+
struct net *net = NULL;
109+
bpf_core_read(&net, sizeof(net), &sk->__sk_common.skc_net.net);
110+
if (net) {
111+
u32 inum;
112+
bpf_core_read(&inum, sizeof(inum), &net->ns.inum);
113+
if (inum != netns_id) {
114+
bpf_printk("Skipping packet from different netns: %u != %u\n", inum, netns_id);
115+
return 0;
116+
}
117+
}
118+
}
119+
120+
struct event *event = bpf_ringbuf_reserve(&events, sizeof(*event), 0);
121+
if (!event) {
122+
return 0;
123+
}
124+
125+
event->timestamp = bpf_ktime_get_ns();
126+
event->pid = pid;
127+
event->drop_reason = args->reason;
128+
bpf_get_current_comm(&event->comm, sizeof(event->comm));
129+
event->stack_id = bpf_get_stackid(args, &stack_traces, 0);
130+
event->state = 127;
131+
if (sk) {
132+
u8 state;
133+
if (!bpf_core_read(&state, sizeof(state), &sk->__sk_common.skc_state)) {
134+
event->state = state;
135+
}
136+
}
137+
138+
if (protocol == ETH_P_IP) {
139+
struct iphdr ip;
140+
if (bpf_core_read(&ip, sizeof(ip), head + network_header)) {
141+
bpf_ringbuf_discard(event, 0);
142+
return 0;
143+
}
144+
if (ip.protocol != IPPROTO_TCP) {
145+
bpf_ringbuf_discard(event, 0);
146+
return 0;
147+
}
148+
struct tcphdr tcp;
149+
if (bpf_core_read(&tcp, sizeof(tcp), head + transport_header)) {
150+
bpf_ringbuf_discard(event, 0);
151+
return 0;
152+
}
153+
event->ip_version = 4;
154+
event->saddr_v4 = ip.saddr;
155+
event->daddr_v4 = ip.daddr;
156+
event->sport = bpf_ntohs(tcp.source);
157+
event->dport = bpf_ntohs(tcp.dest);
158+
event->tcpflags = ((u8 *)&tcp)[13];
159+
} else {
160+
struct ipv6hdr ip6;
161+
if (bpf_core_read(&ip6, sizeof(ip6), head + network_header)) {
162+
bpf_ringbuf_discard(event, 0);
163+
return 0;
164+
}
165+
if (ip6.nexthdr != IPPROTO_TCP) {
166+
bpf_ringbuf_discard(event, 0);
167+
return 0;
168+
}
169+
struct tcphdr tcp;
170+
if (bpf_core_read(&tcp, sizeof(tcp), head + transport_header)) {
171+
bpf_ringbuf_discard(event, 0);
172+
return 0;
173+
}
174+
event->ip_version = 6;
175+
bpf_core_read(&event->saddr_v6, sizeof(event->saddr_v6), &ip6.saddr);
176+
bpf_core_read(&event->daddr_v6, sizeof(event->daddr_v6), &ip6.daddr);
177+
event->sport = bpf_ntohs(tcp.source);
178+
event->dport = bpf_ntohs(tcp.dest);
179+
event->tcpflags = ((u8 *)&tcp)[13];
180+
}
181+
182+
bpf_ringbuf_submit(event, 0);
183+
return 0;
184+
}
185+
186+
char _license[] SEC("license") = "Dual BSD/GPL";

0 commit comments

Comments
 (0)