1 // SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
2
3 /*
4 * tcpstates Trace TCP session state changes with durations.
5 * Copyright (c) 2021 Hengqi Chen
6 *
7 * Based on tcpstates(8) from BCC by Brendan Gregg.
8 * 18-Dec-2021 Hengqi Chen Created this.
9 */
10 #include <argp.h>
11 #include <arpa/inet.h>
12 #include <errno.h>
13 #include <signal.h>
14 #include <string.h>
15 #include <sys/socket.h>
16 #include <time.h>
17
18 #include <bpf/libbpf.h>
19 #include <bpf/bpf.h>
20 #include "btf_helpers.h"
21 #include "tcpstates.h"
22 #include "tcpstates.skel.h"
23 #include "trace_helpers.h"
24
25 #define PERF_BUFFER_PAGES 16
26 #define PERF_POLL_TIMEOUT_MS 100
27 #define warn(...) fprintf(stderr, __VA_ARGS__)
28
29 static volatile sig_atomic_t exiting = 0;
30
31 static bool emit_timestamp = false;
32 static short target_family = 0;
33 static char *target_sports = NULL;
34 static char *target_dports = NULL;
35 static bool wide_output = false;
36 static bool verbose = false;
37 static const char *tcp_states[] = {
38 [1] = "ESTABLISHED",
39 [2] = "SYN_SENT",
40 [3] = "SYN_RECV",
41 [4] = "FIN_WAIT1",
42 [5] = "FIN_WAIT2",
43 [6] = "TIME_WAIT",
44 [7] = "CLOSE",
45 [8] = "CLOSE_WAIT",
46 [9] = "LAST_ACK",
47 [10] = "LISTEN",
48 [11] = "CLOSING",
49 [12] = "NEW_SYN_RECV",
50 [13] = "UNKNOWN",
51 };
52
53 const char *argp_program_version = "tcpstates 1.0";
54 const char *argp_program_bug_address =
55 "https://github.com/iovisor/bcc/tree/master/libbpf-tools";
56 const char argp_program_doc[] =
57 "Trace TCP session state changes and durations.\n"
58 "\n"
59 "USAGE: tcpstates [-4] [-6] [-T] [-L lport] [-D dport]\n"
60 "\n"
61 "EXAMPLES:\n"
62 " tcpstates # trace all TCP state changes\n"
63 " tcpstates -T # include timestamps\n"
64 " tcpstates -L 80 # only trace local port 80\n"
65 " tcpstates -D 80 # only trace remote port 80\n";
66
67 static const struct argp_option opts[] = {
68 { "verbose", 'v', NULL, 0, "Verbose debug output" },
69 { "timestamp", 'T', NULL, 0, "Include timestamp on output" },
70 { "ipv4", '4', NULL, 0, "Trace IPv4 family only" },
71 { "ipv6", '6', NULL, 0, "Trace IPv6 family only" },
72 { "wide", 'w', NULL, 0, "Wide column output (fits IPv6 addresses)" },
73 { "localport", 'L', "LPORT", 0, "Comma-separated list of local ports to trace." },
74 { "remoteport", 'D', "DPORT", 0, "Comma-separated list of remote ports to trace." },
75 { NULL, 'h', NULL, OPTION_HIDDEN, "Show the full help" },
76 {},
77 };
78
parse_arg(int key,char * arg,struct argp_state * state)79 static error_t parse_arg(int key, char *arg, struct argp_state *state)
80 {
81 long port_num;
82 char *port;
83
84 switch (key) {
85 case 'v':
86 verbose = true;
87 break;
88 case 'T':
89 emit_timestamp = true;
90 break;
91 case '4':
92 target_family = AF_INET;
93 break;
94 case '6':
95 target_family = AF_INET6;
96 break;
97 case 'w':
98 wide_output = true;
99 break;
100 case 'L':
101 if (!arg) {
102 warn("No ports specified\n");
103 argp_usage(state);
104 }
105 target_sports = strdup(arg);
106 port = strtok(arg, ",");
107 while (port) {
108 port_num = strtol(port, NULL, 10);
109 if (errno || port_num <= 0 || port_num > 65536) {
110 warn("Invalid ports: %s\n", arg);
111 argp_usage(state);
112 }
113 port = strtok(NULL, ",");
114 }
115 break;
116 case 'D':
117 if (!arg) {
118 warn("No ports specified\n");
119 argp_usage(state);
120 }
121 target_dports = strdup(arg);
122 port = strtok(arg, ",");
123 while (port) {
124 port_num = strtol(port, NULL, 10);
125 if (errno || port_num <= 0 || port_num > 65536) {
126 warn("Invalid ports: %s\n", arg);
127 argp_usage(state);
128 }
129 port = strtok(NULL, ",");
130 }
131 break;
132 case 'h':
133 argp_state_help(state, stderr, ARGP_HELP_STD_HELP);
134 break;
135 default:
136 return ARGP_ERR_UNKNOWN;
137 }
138 return 0;
139 }
140
libbpf_print_fn(enum libbpf_print_level level,const char * format,va_list args)141 static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args)
142 {
143 if (level == LIBBPF_DEBUG && !verbose)
144 return 0;
145
146 return vfprintf(stderr, format, args);
147 }
148
sig_int(int signo)149 static void sig_int(int signo)
150 {
151 exiting = 1;
152 }
153
handle_event(void * ctx,int cpu,void * data,__u32 data_sz)154 static void handle_event(void *ctx, int cpu, void *data, __u32 data_sz)
155 {
156 char ts[32], saddr[39], daddr[39];
157 struct event *e = data;
158 struct tm *tm;
159 int family;
160 time_t t;
161
162 if (emit_timestamp) {
163 time(&t);
164 tm = localtime(&t);
165 strftime(ts, sizeof(ts), "%H:%M:%S", tm);
166 printf("%8s ", ts);
167 }
168
169 inet_ntop(e->family, &e->saddr, saddr, sizeof(saddr));
170 inet_ntop(e->family, &e->daddr, daddr, sizeof(daddr));
171 if (wide_output) {
172 family = e->family == AF_INET ? 4 : 6;
173 printf("%-16llx %-7d %-16s %-2d %-39s %-5d %-39s %-5d %-11s -> %-11s %.3f\n",
174 e->skaddr, e->pid, e->task, family, saddr, e->sport, daddr, e->dport,
175 tcp_states[e->oldstate], tcp_states[e->newstate], (double)e->delta_us / 1000);
176 } else {
177 printf("%-16llx %-7d %-10.10s %-15s %-5d %-15s %-5d %-11s -> %-11s %.3f\n",
178 e->skaddr, e->pid, e->task, saddr, e->sport, daddr, e->dport,
179 tcp_states[e->oldstate], tcp_states[e->newstate], (double)e->delta_us / 1000);
180 }
181 }
182
handle_lost_events(void * ctx,int cpu,__u64 lost_cnt)183 static void handle_lost_events(void *ctx, int cpu, __u64 lost_cnt)
184 {
185 warn("lost %llu events on CPU #%d\n", lost_cnt, cpu);
186 }
187
main(int argc,char ** argv)188 int main(int argc, char **argv)
189 {
190 LIBBPF_OPTS(bpf_object_open_opts, open_opts);
191 static const struct argp argp = {
192 .options = opts,
193 .parser = parse_arg,
194 .doc = argp_program_doc,
195 };
196 struct perf_buffer *pb = NULL;
197 struct tcpstates_bpf *obj;
198 int err, port_map_fd;
199 short port_num;
200 char *port;
201
202 err = argp_parse(&argp, argc, argv, 0, NULL, NULL);
203 if (err)
204 return err;
205
206 libbpf_set_print(libbpf_print_fn);
207
208 err = ensure_core_btf(&open_opts);
209 if (err) {
210 warn("failed to fetch necessary BTF for CO-RE: %s\n", strerror(-err));
211 return 1;
212 }
213
214 obj = tcpstates_bpf__open_opts(&open_opts);
215 if (!obj) {
216 warn("failed to open BPF object\n");
217 return 1;
218 }
219
220 obj->rodata->filter_by_sport = target_sports != NULL;
221 obj->rodata->filter_by_dport = target_dports != NULL;
222 obj->rodata->target_family = target_family;
223
224 err = tcpstates_bpf__load(obj);
225 if (err) {
226 warn("failed to load BPF object: %d\n", err);
227 goto cleanup;
228 }
229
230 if (target_sports) {
231 port_map_fd = bpf_map__fd(obj->maps.sports);
232 port = strtok(target_sports, ",");
233 while (port) {
234 port_num = strtol(port, NULL, 10);
235 bpf_map_update_elem(port_map_fd, &port_num, &port_num, BPF_ANY);
236 port = strtok(NULL, ",");
237 }
238 }
239 if (target_dports) {
240 port_map_fd = bpf_map__fd(obj->maps.dports);
241 port = strtok(target_dports, ",");
242 while (port) {
243 port_num = strtol(port, NULL, 10);
244 bpf_map_update_elem(port_map_fd, &port_num, &port_num, BPF_ANY);
245 port = strtok(NULL, ",");
246 }
247 }
248
249 err = tcpstates_bpf__attach(obj);
250 if (err) {
251 warn("failed to attach BPF programs: %d\n", err);
252 goto cleanup;
253 }
254
255 pb = perf_buffer__new(bpf_map__fd(obj->maps.events), PERF_BUFFER_PAGES,
256 handle_event, handle_lost_events, NULL, NULL);
257 if (!pb) {
258 err = - errno;
259 warn("failed to open perf buffer: %d\n", err);
260 goto cleanup;
261 }
262
263 if (signal(SIGINT, sig_int) == SIG_ERR) {
264 warn("can't set signal handler: %s\n", strerror(errno));
265 err = 1;
266 goto cleanup;
267 }
268
269 if (emit_timestamp)
270 printf("%-8s ", "TIME(s)");
271 if (wide_output)
272 printf("%-16s %-7s %-16s %-2s %-39s %-5s %-39s %-5s %-11s -> %-11s %s\n",
273 "SKADDR", "PID", "COMM", "IP", "LADDR", "LPORT",
274 "RADDR", "RPORT", "OLDSTATE", "NEWSTATE", "MS");
275 else
276 printf("%-16s %-7s %-10s %-15s %-5s %-15s %-5s %-11s -> %-11s %s\n",
277 "SKADDR", "PID", "COMM", "LADDR", "LPORT",
278 "RADDR", "RPORT", "OLDSTATE", "NEWSTATE", "MS");
279
280 while (!exiting) {
281 err = perf_buffer__poll(pb, PERF_POLL_TIMEOUT_MS);
282 if (err < 0 && err != -EINTR) {
283 warn("error polling perf buffer: %s\n", strerror(-err));
284 goto cleanup;
285 }
286 /* reset err to return 0 if exiting */
287 err = 0;
288 }
289
290 cleanup:
291 perf_buffer__free(pb);
292 tcpstates_bpf__destroy(obj);
293 cleanup_core_btf(&open_opts);
294
295 return err != 0;
296 }
297