xref: /aosp_15_r20/external/bcc/libbpf-tools/tcpstates.c (revision 387f9dfdfa2baef462e92476d413c7bc2470293e)
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