1 /* tap-follow.c
2 *
3 * Copyright 2011-2013, QA Cafe <info@qacafe.com>
4 *
5 * Wireshark - Network traffic analyzer
6 * By Gerald Combs <gerald@wireshark.org>
7 * Copyright 1998 Gerald Combs
8 *
9 * SPDX-License-Identifier: GPL-2.0-or-later
10 */
11
12 /* This module provides udp and tcp follow stream capabilities to tshark.
13 * It is only used by tshark and not wireshark.
14 */
15
16 #include "config.h"
17
18 #include <stdio.h>
19 #include <stdlib.h>
20
21 #include <glib.h>
22 #include <epan/addr_resolv.h>
23 #include <epan/charsets.h>
24 #include <epan/follow.h>
25 #include <epan/stat_tap_ui.h>
26 #include <epan/tap.h>
27 #include <wsutil/ws_assert.h>
28
29 void register_tap_listener_follow(void);
30
31 typedef struct _cli_follow_info {
32 show_type_t show_type;
33 register_follow_t* follower;
34
35 /* range */
36 guint32 chunkMin;
37 guint32 chunkMax;
38
39 /* filter */
40 int stream_index;
41 int sub_stream_index;
42 int port[2];
43 address addr[2];
44 union {
45 guint32 addrBuf_v4;
46 ws_in6_addr addrBuf_v6;
47 } addrBuf[2];
48 } cli_follow_info_t;
49
50
51 #define STR_FOLLOW "follow,"
52
53 #define STR_HEX ",hex"
54 #define STR_ASCII ",ascii"
55 #define STR_EBCDIC ",ebcdic"
56 #define STR_RAW ",raw"
57 #define STR_YAML ",yaml"
58
follow_exit(const char * strp)59 WS_NORETURN static void follow_exit(const char *strp)
60 {
61 fprintf(stderr, "tshark: follow - %s\n", strp);
62 exit(1);
63 }
64
follow_str_type(cli_follow_info_t * cli_follow_info)65 static const char * follow_str_type(cli_follow_info_t* cli_follow_info)
66 {
67 switch (cli_follow_info->show_type)
68 {
69 case SHOW_HEXDUMP: return "hex";
70 case SHOW_ASCII: return "ascii";
71 case SHOW_EBCDIC: return "ebcdic";
72 case SHOW_RAW: return "raw";
73 case SHOW_YAML: return "yaml";
74 default:
75 ws_assert_not_reached();
76 break;
77 }
78
79 ws_assert_not_reached();
80
81 return "<unknown-mode>";
82 }
83
84 static void
follow_free(follow_info_t * follow_info)85 follow_free(follow_info_t *follow_info)
86 {
87 cli_follow_info_t* cli_follow_info = (cli_follow_info_t*)follow_info->gui_data;
88
89 g_free(cli_follow_info);
90 follow_info_free(follow_info);
91 }
92
93 #define BYTES_PER_LINE 16
94 #define OFFSET_LEN 8
95 #define OFFSET_SPACE 2
96 #define HEX_START (OFFSET_LEN + OFFSET_SPACE)
97 #define HEX_LEN (BYTES_PER_LINE * 3) /* extra space at column 8 */
98 #define HEX_SPACE 2
99 #define ASCII_START (HEX_START + HEX_LEN + HEX_SPACE)
100 #define ASCII_LEN (BYTES_PER_LINE + 1) /* extra space at column 8 */
101 #define LINE_LEN (ASCII_START + ASCII_LEN)
102
103 static const char bin2hex[] = {'0', '1', '2', '3', '4', '5', '6', '7',
104 '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
105
follow_print_hex(const char * prefixp,guint32 offset,void * datap,int len)106 static void follow_print_hex(const char *prefixp, guint32 offset, void *datap, int len)
107 {
108 int ii;
109 int jj;
110 int kk;
111 guint8 val;
112 char line[LINE_LEN + 1];
113
114 for (ii = 0, jj = 0, kk = 0; ii < len; )
115 {
116 if ((ii % BYTES_PER_LINE) == 0)
117 {
118 /* new line */
119 g_snprintf(line, LINE_LEN + 1, "%0*X", OFFSET_LEN, offset);
120 memset(line + HEX_START - OFFSET_SPACE, ' ',
121 HEX_LEN + OFFSET_SPACE + HEX_SPACE);
122
123 /* offset of hex */
124 jj = HEX_START;
125
126 /* offset of ascii */
127 kk = ASCII_START;
128 }
129
130 val = ((guint8 *)datap)[ii];
131
132 line[jj++] = bin2hex[val >> 4];
133 line[jj++] = bin2hex[val & 0xf];
134 jj++;
135
136 line[kk++] = val >= ' ' && val < 0x7f ? val : '.';
137
138 /* extra space at column 8 */
139 if (++ii % BYTES_PER_LINE == BYTES_PER_LINE/2)
140 {
141 line[jj++] = ' ';
142 line[kk++] = ' ';
143 }
144
145 if ((ii % BYTES_PER_LINE) == 0 || ii == len)
146 {
147 /* end of line or buffer */
148 if (line[kk - 1] == ' ')
149 {
150 kk--;
151 }
152 line[kk] = 0;
153 printf("%s%s\n", prefixp, line);
154 offset += BYTES_PER_LINE;
155 }
156 }
157 }
158
follow_draw(void * contextp)159 static void follow_draw(void *contextp)
160 {
161 static const char separator[] =
162 "===================================================================\n";
163
164 follow_info_t *follow_info = (follow_info_t*)contextp;
165 cli_follow_info_t* cli_follow_info = (cli_follow_info_t*)follow_info->gui_data;
166 gchar buf[WS_INET6_ADDRSTRLEN];
167 guint32 global_client_pos = 0, global_server_pos = 0;
168 guint32 *global_pos;
169 guint32 ii, jj;
170 char *buffer;
171 GList *cur;
172 follow_record_t *follow_record;
173 guint chunk;
174 gchar *b64encoded;
175 const guint32 base64_raw_len = 57; /* Encodes to 76 bytes, common in RFCs */
176
177 /* Print header */
178 switch (cli_follow_info->show_type)
179 {
180 case SHOW_YAML:
181 printf("peers:\n");
182 printf(" - peer: 0\n");
183 address_to_str_buf(&follow_info->client_ip, buf, sizeof buf);
184 printf(" host: %s\n", buf);
185 printf(" port: %d\n", follow_info->client_port);
186 printf(" - peer: 1\n");
187 address_to_str_buf(&follow_info->server_ip, buf, sizeof buf);
188 printf(" host: %s\n", buf);
189 printf(" port: %d\n", follow_info->server_port);
190 printf("packets:\n");
191 break;
192
193 default:
194 printf("\n%s", separator);
195 printf("Follow: %s,%s\n", proto_get_protocol_filter_name(get_follow_proto_id(cli_follow_info->follower)), follow_str_type(cli_follow_info));
196 printf("Filter: %s\n", follow_info->filter_out_filter);
197
198 address_to_str_buf(&follow_info->client_ip, buf, sizeof buf);
199 if (follow_info->client_ip.type == AT_IPv6)
200 printf("Node 0: [%s]:%u\n", buf, follow_info->client_port);
201 else
202 printf("Node 0: %s:%u\n", buf, follow_info->client_port);
203
204 address_to_str_buf(&follow_info->server_ip, buf, sizeof buf);
205 if (follow_info->server_ip.type == AT_IPv6)
206 printf("Node 1: [%s]:%u\n", buf, follow_info->server_port);
207 else
208 printf("Node 1: %s:%u\n", buf, follow_info->server_port);
209 break;
210 }
211
212 for (cur = g_list_last(follow_info->payload), chunk = 1;
213 cur != NULL;
214 cur = g_list_previous(cur), chunk++)
215 {
216 follow_record = (follow_record_t *)cur->data;
217 if (!follow_record->is_server) {
218 global_pos = &global_client_pos;
219 } else {
220 global_pos = &global_server_pos;
221 }
222
223 /* ignore chunks not in range */
224 if ((chunk < cli_follow_info->chunkMin) || (chunk > cli_follow_info->chunkMax)) {
225 (*global_pos) += follow_record->data->len;
226 continue;
227 }
228
229 /* Print start of line */
230 switch (cli_follow_info->show_type)
231 {
232 case SHOW_HEXDUMP:
233 case SHOW_YAML:
234 break;
235
236 case SHOW_ASCII:
237 case SHOW_EBCDIC:
238 printf("%s%u\n", follow_record->is_server ? "\t" : "", follow_record->data->len);
239 break;
240
241 case SHOW_RAW:
242 if (follow_record->is_server)
243 {
244 putchar('\t');
245 }
246 break;
247
248 default:
249 ws_assert_not_reached();
250 }
251
252 /* Print data */
253 switch (cli_follow_info->show_type)
254 {
255 case SHOW_HEXDUMP:
256 follow_print_hex(follow_record->is_server ? "\t" : "", *global_pos, follow_record->data->data, follow_record->data->len);
257 (*global_pos) += follow_record->data->len;
258 break;
259
260 case SHOW_ASCII:
261 case SHOW_EBCDIC:
262 buffer = (char *)g_malloc(follow_record->data->len+2);
263
264 for (ii = 0; ii < follow_record->data->len; ii++)
265 {
266 switch (follow_record->data->data[ii])
267 {
268 case '\r':
269 case '\n':
270 buffer[ii] = follow_record->data->data[ii];
271 break;
272 default:
273 buffer[ii] = g_ascii_isprint(follow_record->data->data[ii]) ? follow_record->data->data[ii] : '.';
274 break;
275 }
276 }
277
278 buffer[ii++] = '\n';
279 buffer[ii] = 0;
280 if (cli_follow_info->show_type == SHOW_EBCDIC) {
281 EBCDIC_to_ASCII(buffer, ii);
282 }
283 printf("%s", buffer);
284 g_free(buffer);
285 break;
286
287 case SHOW_RAW:
288 buffer = (char *)g_malloc((follow_record->data->len*2)+2);
289
290 for (ii = 0, jj = 0; ii < follow_record->data->len; ii++)
291 {
292 buffer[jj++] = bin2hex[follow_record->data->data[ii] >> 4];
293 buffer[jj++] = bin2hex[follow_record->data->data[ii] & 0xf];
294 }
295
296 buffer[jj++] = '\n';
297 buffer[jj] = 0;
298 printf("%s", buffer);
299 g_free(buffer);
300 break;
301
302 case SHOW_YAML:
303 printf(" - packet: %d\n", follow_record->packet_num);
304 printf(" peer: %d\n", follow_record->is_server ? 1 : 0);
305 printf(" timestamp: %.9f\n", nstime_to_sec(&follow_record->abs_ts));
306 printf(" data: !!binary |\n");
307 ii = 0;
308 while (ii < follow_record->data->len) {
309 guint32 len = ii + base64_raw_len < follow_record->data->len
310 ? base64_raw_len
311 : follow_record->data->len - ii;
312 b64encoded = g_base64_encode(&follow_record->data->data[ii], len);
313 printf(" %s\n", b64encoded);
314 g_free(b64encoded);
315 ii += len;
316 }
317 break;
318
319 default:
320 ws_assert_not_reached();
321 }
322 }
323
324 /* Print footer */
325 switch (cli_follow_info->show_type)
326 {
327 case SHOW_YAML:
328 break;
329
330 default:
331 printf("%s", separator);
332 break;
333 }
334 }
335
follow_arg_strncmp(const char ** opt_argp,const char * strp)336 static gboolean follow_arg_strncmp(const char **opt_argp, const char *strp)
337 {
338 size_t len = strlen(strp);
339
340 if (strncmp(*opt_argp, strp, len) == 0)
341 {
342 *opt_argp += len;
343 return TRUE;
344 }
345 return FALSE;
346 }
347
348 static void
follow_arg_mode(const char ** opt_argp,follow_info_t * follow_info)349 follow_arg_mode(const char **opt_argp, follow_info_t *follow_info)
350 {
351 cli_follow_info_t* cli_follow_info = (cli_follow_info_t*)follow_info->gui_data;
352
353 if (follow_arg_strncmp(opt_argp, STR_HEX))
354 {
355 cli_follow_info->show_type = SHOW_HEXDUMP;
356 }
357 else if (follow_arg_strncmp(opt_argp, STR_ASCII))
358 {
359 cli_follow_info->show_type = SHOW_ASCII;
360 }
361 else if (follow_arg_strncmp(opt_argp, STR_EBCDIC))
362 {
363 cli_follow_info->show_type = SHOW_EBCDIC;
364 }
365 else if (follow_arg_strncmp(opt_argp, STR_RAW))
366 {
367 cli_follow_info->show_type = SHOW_RAW;
368 }
369 else if (follow_arg_strncmp(opt_argp, STR_YAML))
370 {
371 cli_follow_info->show_type = SHOW_YAML;
372 }
373 else
374 {
375 follow_exit("Invalid display mode.");
376 }
377 }
378
379 #define _STRING(s) # s
380 #define STRING(s) _STRING(s)
381
382 #define ADDR_CHARS 80
383 #define ADDR_LEN (ADDR_CHARS + 1)
384 #define ADDRv6_FMT ",[%" STRING(ADDR_CHARS) "[^]]]:%d%n"
385 #define ADDRv4_FMT ",%" STRING(ADDR_CHARS) "[^:]:%d%n"
386
387 static void
follow_arg_filter(const char ** opt_argp,follow_info_t * follow_info)388 follow_arg_filter(const char **opt_argp, follow_info_t *follow_info)
389 {
390 int len;
391 unsigned int ii;
392 char addr[ADDR_LEN];
393 cli_follow_info_t* cli_follow_info = (cli_follow_info_t*)follow_info->gui_data;
394 gboolean is_ipv6;
395
396 if (sscanf(*opt_argp, ",%d%n", &cli_follow_info->stream_index, &len) == 1 &&
397 ((*opt_argp)[len] == 0 || (*opt_argp)[len] == ','))
398 {
399 *opt_argp += len;
400
401 /* if it's HTTP2 or QUIC protocol we should read substream id otherwise it's a range parameter from follow_arg_range */
402 if (cli_follow_info->sub_stream_index == -1 && sscanf(*opt_argp, ",%d%n", &cli_follow_info->sub_stream_index, &len) == 1 &&
403 ((*opt_argp)[len] == 0 || (*opt_argp)[len] == ','))
404 {
405 *opt_argp += len;
406 follow_info->substream_id = cli_follow_info->sub_stream_index;
407 }
408 }
409 else
410 {
411 for (ii = 0; ii < sizeof cli_follow_info->addr/sizeof *cli_follow_info->addr; ii++)
412 {
413 if (sscanf(*opt_argp, ADDRv6_FMT, addr, &cli_follow_info->port[ii], &len) == 2)
414 {
415 is_ipv6 = TRUE;
416 }
417 else if (sscanf(*opt_argp, ADDRv4_FMT, addr, &cli_follow_info->port[ii], &len) == 2)
418 {
419 is_ipv6 = FALSE;
420 }
421 else
422 {
423 follow_exit("Invalid address.");
424 }
425
426 if (cli_follow_info->port[ii] <= 0 || cli_follow_info->port[ii] > G_MAXUINT16)
427 {
428 follow_exit("Invalid port.");
429 }
430
431 if (is_ipv6)
432 {
433 if (!get_host_ipaddr6(addr, &cli_follow_info->addrBuf[ii].addrBuf_v6))
434 {
435 follow_exit("Can't get IPv6 address");
436 }
437 set_address(&cli_follow_info->addr[ii], AT_IPv6, 16, (void *)&cli_follow_info->addrBuf[ii].addrBuf_v6);
438 }
439 else
440 {
441 if (!get_host_ipaddr(addr, &cli_follow_info->addrBuf[ii].addrBuf_v4))
442 {
443 follow_exit("Can't get IPv4 address");
444 }
445 set_address(&cli_follow_info->addr[ii], AT_IPv4, 4, (void *)&cli_follow_info->addrBuf[ii].addrBuf_v4);
446 }
447
448 *opt_argp += len;
449 }
450
451 if (cli_follow_info->addr[0].type != cli_follow_info->addr[1].type)
452 {
453 follow_exit("Mismatched IP address types.");
454 }
455 cli_follow_info->stream_index = -1;
456 }
457 }
458
follow_arg_range(const char ** opt_argp,cli_follow_info_t * cli_follow_info)459 static void follow_arg_range(const char **opt_argp, cli_follow_info_t* cli_follow_info)
460 {
461 int len;
462
463 if (**opt_argp == 0)
464 {
465 cli_follow_info->chunkMin = 1;
466 cli_follow_info->chunkMax = G_MAXUINT32;
467 }
468 else
469 {
470 if (sscanf(*opt_argp, ",%u-%u%n", &cli_follow_info->chunkMin, &cli_follow_info->chunkMax, &len) == 2)
471 {
472 *opt_argp += len;
473 }
474 else if (sscanf(*opt_argp, ",%u%n", &cli_follow_info->chunkMin, &len) == 1)
475 {
476 cli_follow_info->chunkMax = cli_follow_info->chunkMin;
477 *opt_argp += len;
478 }
479 else
480 {
481 follow_exit("Invalid range.");
482 }
483
484 if (cli_follow_info->chunkMin < 1 || cli_follow_info->chunkMin > cli_follow_info->chunkMax)
485 {
486 follow_exit("Invalid range value.");
487 }
488 }
489 }
490
491 static void
follow_arg_done(const char * opt_argp)492 follow_arg_done(const char *opt_argp)
493 {
494 if (*opt_argp != 0)
495 {
496 follow_exit("Invalid parameter.");
497 }
498 }
499
follow_stream(const char * opt_argp,void * userdata)500 static void follow_stream(const char *opt_argp, void *userdata)
501 {
502 follow_info_t *follow_info;
503 cli_follow_info_t* cli_follow_info;
504 GString *errp;
505 register_follow_t* follower = (register_follow_t*)userdata;
506 follow_index_filter_func index_filter;
507 follow_address_filter_func address_filter;
508 int proto_id = get_follow_proto_id(follower);
509 const char* proto_filter_name = proto_get_protocol_filter_name(proto_id);
510
511 opt_argp += strlen(STR_FOLLOW);
512 opt_argp += strlen(proto_filter_name);
513
514 cli_follow_info = g_new0(cli_follow_info_t, 1);
515 cli_follow_info->stream_index = -1;
516 /* use second parameter only for HTTP2 or QUIC substream */
517 if (g_str_equal(proto_filter_name, "http2") ||
518 g_str_equal(proto_filter_name, "quic")) {
519 cli_follow_info->sub_stream_index = -1;
520 } else {
521 cli_follow_info->sub_stream_index = 0;
522 }
523 follow_info = g_new0(follow_info_t, 1);
524 follow_info->gui_data = cli_follow_info;
525 follow_info->substream_id = SUBSTREAM_UNUSED;
526 cli_follow_info->follower = follower;
527
528 follow_arg_mode(&opt_argp, follow_info);
529 follow_arg_filter(&opt_argp, follow_info);
530 follow_arg_range(&opt_argp, cli_follow_info);
531 follow_arg_done(opt_argp);
532
533 if (cli_follow_info->stream_index >= 0)
534 {
535 index_filter = get_follow_index_func(follower);
536 follow_info->filter_out_filter = index_filter(cli_follow_info->stream_index, cli_follow_info->sub_stream_index);
537 if (follow_info->filter_out_filter == NULL || cli_follow_info->sub_stream_index < 0)
538 {
539 follow_exit("Error creating filter for this stream.");
540 }
541 }
542 else
543 {
544 address_filter = get_follow_address_func(follower);
545 follow_info->filter_out_filter = address_filter(&cli_follow_info->addr[0], &cli_follow_info->addr[1], cli_follow_info->port[0], cli_follow_info->port[1]);
546 if (follow_info->filter_out_filter == NULL)
547 {
548 follow_exit("Error creating filter for this address/port pair.\n");
549 }
550 }
551
552 errp = register_tap_listener(get_follow_tap_string(follower), follow_info, follow_info->filter_out_filter, 0,
553 NULL, get_follow_tap_handler(follower), follow_draw, (tap_finish_cb)follow_free);
554
555 if (errp != NULL)
556 {
557 follow_free(follow_info);
558 g_string_free(errp, TRUE);
559 follow_exit("Error registering tap listener.");
560 }
561 }
562
563 static gboolean
follow_register(const void * key _U_,void * value,void * userdata _U_)564 follow_register(const void *key _U_, void *value, void *userdata _U_)
565 {
566 register_follow_t *follower = (register_follow_t*)value;
567 stat_tap_ui follow_ui;
568 gchar *cli_string;
569
570 cli_string = follow_get_stat_tap_string(follower);
571 follow_ui.group = REGISTER_STAT_GROUP_GENERIC;
572 follow_ui.title = NULL; /* construct this from the protocol info? */
573 follow_ui.cli_string = cli_string;
574 follow_ui.tap_init_cb = follow_stream;
575 follow_ui.nparams = 0;
576 follow_ui.params = NULL;
577 register_stat_tap_ui(&follow_ui, follower);
578 g_free(cli_string);
579 return FALSE;
580 }
581
582 void
register_tap_listener_follow(void)583 register_tap_listener_follow(void)
584 {
585 follow_iterate_followers(follow_register, NULL);
586 }
587
588 /*
589 * Editor modelines - https://www.wireshark.org/tools/modelines.html
590 *
591 * Local Variables:
592 * c-basic-offset: 2
593 * tab-width: 8
594 * indent-tabs-mode: nil
595 * End:
596 *
597 * ex: set shiftwidth=2 tabstop=8 expandtab:
598 * :indentSize=2:tabSize=8:noTabs=true:
599 */
600