Skip to content

Commit bff094a

Browse files
committed
ethtoolsnoop
Signed-off-by: Leon Hwang <hffilwlqm@gmail.com>
1 parent af038ac commit bff094a

18 files changed

+150219
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,5 @@
1919

2020
# Go workspace file
2121
go.work
22+
23+
*_bpfe*

README.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# ethtoolsnoop
2+
3+
`ethtoolsnoop` is a tool for tracing the execution of `ethtool`.
4+
5+
## Usage example
6+
7+
```bash
8+
# echo Execute `ethtool -i enp0s1; ethtool -l enp0s1; ethtool -g enp0s1` in another terminal.
9+
# ./ethtoolsnoop
10+
Interface PID:Process IOCTL_CMD/GENL_CMD ethtool args
11+
enp0s1 11198:ethtool(parent 6373:zsh) ETHTOOL_GDRVINFO -d|--register-dump(Do a register dump), -e|--eeprom-dump(Do a EEPROM dump), -i|--driver(Show driver information)
12+
enp0s1 11199:ethtool(parent 6373:zsh) ETHTOOL_MSG_CHANNELS_GET -l|--show-channels(Query Channels)
13+
enp0s1 11200:ethtool(parent 6373:zsh) ETHTOOL_MSG_RINGS_GET -g|--show-ring(Query RX/TX ring parameters)
14+
```
15+
16+
In the output:
17+
18+
- First column is the interface name.
19+
- Second column is the PID and process name of the process that executed
20+
`ethtool`, and the PID and process name of the parent process if the tracee
21+
process is `ethtool`.
22+
- Third column is the underneath command for kernel to execute, including ways
23+
of `ioctl()` syscall and genetlink message.
24+
- Fourth column is the arguments of `ethtool` command, which may be
25+
corresponding to the third column. *But this column maybe incomplete.*
26+
27+
## Download
28+
29+
Please download the latest release from this repo's release page.
30+
31+
`ethtoolsnoop` is expected to run on Linux kernel 5.2 or later with BTF support.
32+
33+
## Intenals
34+
35+
`ethtoolsnoop` uses `kprobe` on `dev_ethtool()` to trace the execution of
36+
`ethtool`'s `ioctl()` syscall.
37+
38+
And it uses `kprobe` on `ethnl_default_doit()`, `ethnl_parse_header_dev_get()`
39+
and `kretprobe` on `ethnl_parse_header_dev_get()` to trace the execution of
40+
`ethtool`'s genetlink message.
41+
42+
## License
43+
44+
`ethtoolsnoop` is licensed under the Apache 2.0 license, and its bpf code is
45+
licensed under the GPL 2.0 license.

bpf/LICENSE.GPL

Lines changed: 339 additions & 0 deletions
Large diffs are not rendered by default.

bpf/ethtool.c

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
/**
2+
* Copyright 2024 Leon Hwang.
3+
* SPDX-License-Identifier: GPL-2.0
4+
*/
5+
6+
#include "vmlinux.h"
7+
8+
#include <bpf/bpf_helpers.h>
9+
#include <bpf/bpf_tracing.h>
10+
#include <bpf/bpf_core_read.h>
11+
#include <bpf/bpf_compiler.h>
12+
#include <bpf/bpf_map_helpers.h>
13+
14+
#define IFNAMSIZ 16
15+
16+
// From include/uapi/linux/ethtool.h
17+
#define ETHTOOL_PERQUEUE 0x0000004b /* Set per queue options */
18+
19+
#define EVENT_TYPE_IOCTL 1
20+
#define EVENT_TYPE_GENL 2
21+
22+
struct event {
23+
u8 type;
24+
u8 genlhdr_cmd;
25+
u16 ethcmd;
26+
u32 pid;
27+
char ifname[IFNAMSIZ];
28+
char comm[TASK_COMM_LEN];
29+
30+
struct ethnl_req_info *req;
31+
} __attribute__((packed));
32+
33+
#define SIZEOF_EVENT (offsetof(struct event, req))
34+
35+
struct {
36+
__uint(type, BPF_MAP_TYPE_PERF_EVENT_ARRAY);
37+
} events SEC(".maps");
38+
39+
struct {
40+
__uint(type, BPF_MAP_TYPE_PERCPU_ARRAY);
41+
__type(key, u32);
42+
__type(value, struct event);
43+
__uint(max_entries, 1);
44+
} events_cache SEC(".maps");
45+
46+
static __always_inline struct event *
47+
__get_or_init_event(void)
48+
{
49+
struct event *ev, init = {};
50+
u32 key = 0;
51+
52+
ev = bpf_map_lookup_elem(&events_cache, &key);
53+
if (likely(ev))
54+
return ev;
55+
56+
bpf_map_update_elem(&events_cache, &key, &init, BPF_NOEXIST);
57+
return bpf_map_lookup_elem(&events_cache, &key);
58+
}
59+
60+
static __always_inline struct event *
61+
__get_event(void)
62+
{
63+
u32 key = 0;
64+
return bpf_map_lookup_elem(&events_cache, &key);
65+
}
66+
67+
static __always_inline struct event *
68+
__get_and_del_event(void)
69+
{
70+
struct event *ev;
71+
u32 key = 0;
72+
73+
ev = bpf_map_lookup_elem(&events_cache, &key);
74+
if (unlikely(!ev))
75+
return NULL;
76+
77+
bpf_map_delete_elem(&events_cache, &key);
78+
return ev;
79+
}
80+
81+
static __always_inline u32
82+
get_ethcmd(void *useraddr)
83+
{
84+
u32 cmd;
85+
86+
bpf_probe_read_user(&cmd, sizeof(cmd), useraddr);
87+
if (cmd == ETHTOOL_PERQUEUE)
88+
cmd = bpf_probe_read_user(&cmd, sizeof(cmd), useraddr + sizeof(cmd));
89+
90+
return cmd;
91+
}
92+
93+
static __always_inline int
94+
__kp_dev_ethtool(void *ctx, struct net *net, struct ifreq *ifr, void *useraddr)
95+
{
96+
struct event ev = {};
97+
98+
ev.type = EVENT_TYPE_IOCTL;
99+
ev.ethcmd = get_ethcmd(useraddr);
100+
101+
ev.pid = bpf_get_current_pid_tgid() >> 32;
102+
103+
bpf_probe_read_kernel_str(ev.ifname, sizeof(ev.ifname), ifr->ifr_ifrn.ifrn_name);
104+
bpf_get_current_comm(ev.comm, sizeof(ev.comm));
105+
106+
bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &ev, sizeof(ev));
107+
108+
return BPF_OK;
109+
}
110+
111+
SEC("kprobe/dev_ethtool")
112+
int kp_dev_ethtool(struct pt_regs *ctx)
113+
{
114+
struct net *net = (typeof(net))(void *)(u64) PT_REGS_PARM1(ctx);
115+
struct ifreq *ifr = (typeof(ifr))(void *)(u64) PT_REGS_PARM2(ctx);
116+
void *useraddr = (typeof(useraddr))(void *)(u64) PT_REGS_PARM3(ctx);
117+
return __kp_dev_ethtool(ctx, net, ifr, useraddr);
118+
}
119+
120+
static __always_inline void
121+
__get_dev_name(struct event *ev, struct ethnl_req_info *req)
122+
{
123+
struct net_device *dev = BPF_CORE_READ(req, dev);
124+
125+
if (likely(dev))
126+
bpf_probe_read_kernel_str(ev->ifname, sizeof(ev->ifname), dev->name);
127+
}
128+
129+
SEC("kprobe/ethnl_default_doit")
130+
int kp_ethnl_doit(struct pt_regs *ctx)
131+
{
132+
struct genl_info *info = (typeof(info))(void *)(u64) PT_REGS_PARM2(ctx);
133+
u8 cmd = BPF_CORE_READ(info, genlhdr, cmd);
134+
struct event *ev = __get_or_init_event();
135+
136+
if (unlikely(!ev))
137+
return BPF_OK;
138+
139+
ev->type = EVENT_TYPE_GENL;
140+
ev->genlhdr_cmd = cmd;
141+
ev->req = NULL;
142+
143+
return BPF_OK;
144+
}
145+
146+
SEC("kprobe/ethnl_parse_header_dev_get")
147+
int kp_ethnl_dev(struct pt_regs *ctx)
148+
{
149+
struct ethnl_req_info *req = (typeof(req))(void *)(u64) PT_REGS_PARM1(ctx);
150+
struct event *ev = __get_event();
151+
152+
if (unlikely(!ev))
153+
return BPF_OK;
154+
155+
ev->req = req;
156+
157+
return BPF_OK;
158+
}
159+
160+
SEC("kretprobe/ethnl_parse_header_dev_get")
161+
int krp_ethnl_dev(struct pt_regs *ctx)
162+
{
163+
struct event *ev = __get_and_del_event();
164+
165+
if (unlikely(!ev))
166+
return BPF_OK;
167+
168+
if (likely(ev->req))
169+
__get_dev_name(ev, ev->req);
170+
171+
ev->pid = bpf_get_current_pid_tgid() >> 32;
172+
bpf_get_current_comm(ev->comm, sizeof(ev->comm));
173+
174+
bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, ev, SIZEOF_EVENT);
175+
176+
return BPF_OK;
177+
}
178+
179+
char __license[] SEC("license") = "GPL";

bpf/headers/bpf/bpf_compiler.h

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
#ifndef __BPF_COMPILER_H__
2+
#define __BPF_COMPILER_H__
3+
4+
#include "vmlinux.h"
5+
6+
#include "bpf_helpers.h"
7+
8+
#ifndef barrier
9+
#define barrier() asm volatile("" \
10+
: \
11+
: \
12+
: "memory")
13+
#endif
14+
15+
#ifndef barrier_data
16+
#define barrier_data(ptr) asm volatile("" \
17+
: \
18+
: "r"(ptr) \
19+
: "memory")
20+
#endif
21+
22+
static __always_inline void bpf_barrier(void)
23+
{
24+
/* Workaround to avoid verifier complaint:
25+
* "dereference of modified ctx ptr R5 off=48+0, ctx+const is allowed,
26+
* ctx+const+const is not"
27+
*/
28+
barrier();
29+
}
30+
31+
#ifndef ARRAY_SIZE
32+
#define ARRAY_SIZE(A) (sizeof(A) / sizeof((A)[0]))
33+
#endif
34+
35+
#ifndef __READ_ONCE
36+
#define __READ_ONCE(X) (*(volatile typeof(X) *)&X)
37+
#endif
38+
39+
#ifndef __WRITE_ONCE
40+
#define __WRITE_ONCE(X, V) (*(volatile typeof(X) *)&X) = (V)
41+
#endif
42+
43+
/* {READ,WRITE}_ONCE() with verifier workaround via bpf_barrier(). */
44+
45+
#ifndef READ_ONCE
46+
#define READ_ONCE(X) \
47+
({ typeof(X) __val = __READ_ONCE(X); \
48+
bpf_barrier(); \
49+
__val; })
50+
#endif
51+
52+
#ifndef WRITE_ONCE
53+
#define WRITE_ONCE(X, V) \
54+
({ typeof(X) __val = (V); \
55+
__WRITE_ONCE(X, __val); \
56+
bpf_barrier(); \
57+
__val; })
58+
#endif
59+
60+
#ifndef __inline
61+
#define __inline inline __attribute__((always_inline))
62+
#endif
63+
64+
#ifndef likely
65+
#define likely(x) __builtin_expect(!!(x), 1)
66+
#endif /* likely */
67+
68+
#ifndef unlikely
69+
#define unlikely(x) __builtin_expect(!!(x), 0)
70+
#endif /* unlikely */
71+
72+
#define _ntohl __builtin_bswap32
73+
#define _htonl __builtin_bswap32
74+
#define _htons __builtin_bswap16
75+
#define _ntohs __builtin_bswap16
76+
77+
struct vxlan_hdr {
78+
79+
__be32 vx_flags;
80+
__be32 vx_vni;
81+
82+
} __attribute__((packed));
83+
84+
#define IP_CSUM_OFF (ETH_HLEN + offsetof(struct iphdr, check))
85+
#define TOS_OFF (ETH_HLEN + offsetof(struct iphdr, tos))
86+
87+
#endif /* __BPF_COMPILER_H__ */

0 commit comments

Comments
 (0)