1 // SPDX-License-Identifier: GPL-2.0
2
3 /*
4 * tcplife Trace the lifespan of TCP sessions and summarize.
5 *
6 * Copyright (c) 2022 Hengqi Chen
7 *
8 * Based on tcplife(8) from BCC by Brendan Gregg.
9 * 02-Jun-2022 Hengqi Chen Created this.
10 */
11 #include <argp.h>
12 #include <errno.h>
13 #include <signal.h>
14 #include <time.h>
15 #include <arpa/inet.h>
16 #include <sys/socket.h>
17
18 #include "btf_helpers.h"
19 #include "tcplife.h"
20 #include "tcplife.skel.h"
21
22 #define PERF_BUFFER_PAGES 16
23 #define PERF_POLL_TIMEOUT_MS 100
24
25 static volatile sig_atomic_t exiting = 0;
26
27 static pid_t target_pid = 0;
28 static short target_family = 0;
29 static char *target_sports = NULL;
30 static char *target_dports = NULL;
31 static int column_width = 15;
32 static bool emit_timestamp = false;
33 static bool verbose = false;
34
35 const char *argp_program_version = "tcplife 0.1";
36 const char *argp_program_bug_address =
37 "https://github.com/iovisor/bcc/tree/master/libbpf-tools";
38 const char argp_program_doc[] =
39 "Trace the lifespan of TCP sessions and summarize.\n"
40 "\n"
41 "USAGE: tcplife [-h] [-p PID] [-4] [-6] [-L] [-D] [-T] [-w]\n"
42 "\n"
43 "EXAMPLES:\n"
44 " tcplife -p 1215 # only trace PID 1215\n"
45 " tcplife -p 1215 -4 # trace IPv4 only\n";
46
47 static const struct argp_option opts[] = {
48 { "pid", 'p', "PID", 0, "Process ID to trace" },
49 { "ipv4", '4', NULL, 0, "Trace IPv4 only" },
50 { "ipv6", '6', NULL, 0, "Trace IPv6 only" },
51 { "wide", 'w', NULL, 0, "Wide column output (fits IPv6 addresses)" },
52 { "time", 'T', NULL, 0, "Include timestamp on output" },
53 { "localport", 'L', "LOCALPORT", 0, "Comma-separated list of local ports to trace." },
54 { "remoteport", 'D', "REMOTEPORT", 0, "Comma-separated list of remote ports to trace." },
55 { "verbose", 'v', NULL, 0, "Verbose debug output" },
56 { NULL, 'h', NULL, OPTION_HIDDEN, "Show the full help" },
57 {},
58 };
59
parse_arg(int key,char * arg,struct argp_state * state)60 static error_t parse_arg(int key, char *arg, struct argp_state *state)
61 {
62 long n;
63
64 switch (key) {
65 case 'p':
66 errno = 0;
67 n = strtol(arg, NULL, 10);
68 if (errno || n <= 0) {
69 fprintf(stderr, "Invalid PID: %s\n", arg);
70 argp_usage(state);
71 }
72 target_pid = n;
73 break;
74 case '4':
75 target_family = AF_INET;
76 break;
77 case '6':
78 target_family = AF_INET6;
79 break;
80 case 'w':
81 column_width = 39;
82 break;
83 case 'L':
84 target_sports = strdup(arg);
85 break;
86 case 'D':
87 target_dports = strdup(arg);
88 break;
89 case 'T':
90 emit_timestamp = true;
91 break;
92 case 'v':
93 verbose = true;
94 break;
95 case 'h':
96 argp_state_help(state, stderr, ARGP_HELP_STD_HELP);
97 break;
98 default:
99 return ARGP_ERR_UNKNOWN;
100 }
101 return 0;
102 }
103
libbpf_print_fn(enum libbpf_print_level level,const char * format,va_list args)104 static int libbpf_print_fn(enum libbpf_print_level level, const char *format, va_list args)
105 {
106 if (level == LIBBPF_DEBUG && !verbose)
107 return 0;
108 return vfprintf(stderr, format, args);
109 }
110
sig_int(int signo)111 static void sig_int(int signo)
112 {
113 exiting = 1;
114 }
115
handle_event(void * ctx,int cpu,void * data,__u32 data_sz)116 static void handle_event(void *ctx, int cpu, void *data, __u32 data_sz)
117 {
118 char ts[32], saddr[48], daddr[48];
119 struct event *e = data;
120 struct tm *tm;
121 time_t t;
122
123 if (emit_timestamp) {
124 time(&t);
125 tm = localtime(&t);
126 strftime(ts, sizeof(ts), "%H:%M:%S", tm);
127 printf("%8s ", ts);
128 }
129
130 inet_ntop(e->family, &e->saddr, saddr, sizeof(saddr));
131 inet_ntop(e->family, &e->daddr, daddr, sizeof(daddr));
132
133 printf("%-7d %-16s %-*s %-5d %-*s %-5d %-6.2f %-6.2f %-.2f\n",
134 e->pid, e->comm, column_width, saddr, e->sport, column_width, daddr, e->dport,
135 (double)e->tx_b / 1024, (double)e->rx_b / 1024, (double)e->span_us / 1000);
136 }
137
handle_lost_events(void * ctx,int cpu,__u64 lost_cnt)138 static void handle_lost_events(void *ctx, int cpu, __u64 lost_cnt)
139 {
140 fprintf(stderr, "lost %llu events on CPU #%d\n", lost_cnt, cpu);
141 }
142
main(int argc,char ** argv)143 int main(int argc, char **argv)
144 {
145 LIBBPF_OPTS(bpf_object_open_opts, open_opts);
146 static const struct argp argp = {
147 .options = opts,
148 .parser = parse_arg,
149 .doc = argp_program_doc,
150 };
151 struct tcplife_bpf *obj;
152 struct perf_buffer *pb = NULL;
153 short port_num;
154 char *port;
155 int err, i;
156
157 err = argp_parse(&argp, argc, argv, 0, NULL, NULL);
158 if (err)
159 return err;
160
161 libbpf_set_print(libbpf_print_fn);
162
163 err = ensure_core_btf(&open_opts);
164 if (err) {
165 fprintf(stderr, "failed to fetch necessary BTF for CO-RE: %s\n", strerror(-err));
166 return 1;
167 }
168
169 obj = tcplife_bpf__open_opts(&open_opts);
170 if (!obj) {
171 fprintf(stderr, "failed to open BPF object\n");
172 return 1;
173 }
174
175 obj->rodata->target_pid = target_pid;
176 obj->rodata->target_family = target_family;
177
178 if (target_sports) {
179 i = 0;
180 port = strtok(target_sports, ",");
181 while (port && i < MAX_PORTS) {
182 port_num = strtol(port, NULL, 10);
183 obj->rodata->target_sports[i++] = port_num;
184 port = strtok(NULL, ",");
185 }
186 obj->rodata->filter_sport = true;
187 }
188
189 if (target_dports) {
190 i = 0;
191 port = strtok(target_dports, ",");
192 while (port && i < MAX_PORTS) {
193 port_num = strtol(port, NULL, 10);
194 obj->rodata->target_dports[i++] = port_num;
195 port = strtok(NULL, ",");
196 }
197 obj->rodata->filter_dport = true;
198 }
199
200 err = tcplife_bpf__load(obj);
201 if (err) {
202 fprintf(stderr, "failed to load BPF object: %d\n", err);
203 goto cleanup;
204 }
205
206 err = tcplife_bpf__attach(obj);
207 if (err) {
208 fprintf(stderr, "failed to attach BPF object: %d\n", err);
209 goto cleanup;
210 }
211
212 pb = perf_buffer__new(bpf_map__fd(obj->maps.events), PERF_BUFFER_PAGES,
213 handle_event, handle_lost_events, NULL, NULL);
214 if (!pb) {
215 err = -errno;
216 fprintf(stderr, "failed to open perf buffer: %d\n", err);
217 goto cleanup;
218 }
219
220 if (signal(SIGINT, sig_int) == SIG_ERR) {
221 fprintf(stderr, "can't set signal handler: %s\n", strerror(errno));
222 err = 1;
223 goto cleanup;
224 }
225
226 if (emit_timestamp)
227 printf("%-8s ", "TIME(s)");
228 printf("%-7s %-16s %-*s %-5s %-*s %-5s %-6s %-6s %-s\n",
229 "PID", "COMM", column_width, "LADDR", "LPORT", column_width, "RADDR", "RPORT",
230 "TX_KB", "RX_KB", "MS");
231
232 while (!exiting) {
233 err = perf_buffer__poll(pb, PERF_POLL_TIMEOUT_MS);
234 if (err < 0 && err != -EINTR) {
235 fprintf(stderr, "error polling perf buffer: %s\n", strerror(-err));
236 goto cleanup;
237 }
238 /* reset err to return 0 if exiting */
239 err = 0;
240 }
241
242 cleanup:
243 perf_buffer__free(pb);
244 tcplife_bpf__destroy(obj);
245 cleanup_core_btf(&open_opts);
246 return err != 0;
247 }
248