LATER... ei_kerberos_kdc_session_key ...
[wireshark-sm.git] / ui / cli / tap-follow.c
blob97983deeae77db2869eddaafbf4c2b1f3d685546
1 /* tap-follow.c
3 * Copyright 2011-2013, QA Cafe <info@qacafe.com>
5 * Wireshark - Network traffic analyzer
6 * By Gerald Combs <gerald@wireshark.org>
7 * Copyright 1998 Gerald Combs
9 * SPDX-License-Identifier: GPL-2.0-or-later
12 /* This module provides udp and tcp follow stream capabilities to tshark.
13 * It is only used by tshark and not wireshark.
16 #include "config.h"
18 #include <stdio.h>
19 #include <stdlib.h>
21 #include <glib.h>
22 #include <epan/addr_resolv.h>
23 #include <wsutil/str_util.h>
24 #include <wsutil/unicode-utils.h>
25 #include <epan/follow.h>
26 #include <epan/stat_tap_ui.h>
27 #include <epan/tap.h>
28 #include <wsutil/ws_assert.h>
30 void register_tap_listener_follow(void);
32 /* Show Type */
33 typedef enum {
34 SHOW_ASCII,
35 SHOW_CARRAY,
36 SHOW_EBCDIC,
37 SHOW_HEXDUMP,
38 SHOW_RAW,
39 SHOW_CODEC, // Ordered to match UTF-8 combobox index
40 SHOW_YAML
41 } show_type_t;
43 typedef struct _cli_follow_info {
44 show_type_t show_type;
45 register_follow_t* follower;
47 /* range */
48 uint32_t chunkMin;
49 uint32_t chunkMax;
51 /* filter */
52 int stream_index;
53 int sub_stream_index;
54 int port[2];
55 address addr[2];
56 union {
57 uint32_t addrBuf_v4;
58 ws_in6_addr addrBuf_v6;
59 } addrBuf[2];
60 } cli_follow_info_t;
63 #define STR_FOLLOW "follow,"
65 #define STR_HEX ",hex"
66 #define STR_ASCII ",ascii"
67 #define STR_EBCDIC ",ebcdic"
68 #define STR_RAW ",raw"
69 #define STR_CODEC ",utf-8"
70 #define STR_YAML ",yaml"
72 WS_NORETURN static void follow_exit(const char *strp)
74 fprintf(stderr, "tshark: follow - %s\n", strp);
75 exit(1);
78 static const char * follow_str_type(cli_follow_info_t* cli_follow_info)
80 switch (cli_follow_info->show_type)
82 case SHOW_HEXDUMP: return "hex";
83 case SHOW_ASCII: return "ascii";
84 case SHOW_EBCDIC: return "ebcdic";
85 case SHOW_RAW: return "raw";
86 case SHOW_CODEC: return "utf-8";
87 case SHOW_YAML: return "yaml";
88 default:
89 ws_assert_not_reached();
90 break;
93 ws_assert_not_reached();
95 return "<unknown-mode>";
98 static void
99 follow_free(follow_info_t *follow_info)
101 cli_follow_info_t* cli_follow_info = (cli_follow_info_t*)follow_info->gui_data;
103 g_free(cli_follow_info);
104 follow_info_free(follow_info);
107 #define BYTES_PER_LINE 16
108 #define OFFSET_LEN 8
109 #define OFFSET_SPACE 2
110 #define HEX_START (OFFSET_LEN + OFFSET_SPACE)
111 #define HEX_LEN (BYTES_PER_LINE * 3) /* extra space at column 8 */
112 #define HEX_SPACE 2
113 #define ASCII_START (HEX_START + HEX_LEN + HEX_SPACE)
114 #define ASCII_LEN (BYTES_PER_LINE + 1) /* extra space at column 8 */
115 #define LINE_LEN (ASCII_START + ASCII_LEN)
117 static const char bin2hex[] = {'0', '1', '2', '3', '4', '5', '6', '7',
118 '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
120 static void follow_print_hex(const char *prefixp, uint32_t offset, void *datap, int len)
122 int ii;
123 int jj;
124 int kk;
125 uint8_t val;
126 char line[LINE_LEN + 1];
128 for (ii = 0, jj = 0, kk = 0; ii < len; )
130 if ((ii % BYTES_PER_LINE) == 0)
132 /* new line */
133 snprintf(line, LINE_LEN + 1, "%0*X", OFFSET_LEN, offset);
134 memset(line + HEX_START - OFFSET_SPACE, ' ',
135 HEX_LEN + OFFSET_SPACE + HEX_SPACE);
137 /* offset of hex */
138 jj = HEX_START;
140 /* offset of ascii */
141 kk = ASCII_START;
144 val = ((uint8_t *)datap)[ii];
146 line[jj++] = bin2hex[val >> 4];
147 line[jj++] = bin2hex[val & 0xf];
148 jj++;
150 line[kk++] = val >= ' ' && val < 0x7f ? val : '.';
152 /* extra space at column 8 */
153 if (++ii % BYTES_PER_LINE == BYTES_PER_LINE/2)
155 line[jj++] = ' ';
156 line[kk++] = ' ';
159 if ((ii % BYTES_PER_LINE) == 0 || ii == len)
161 /* end of line or buffer */
162 if (line[kk - 1] == ' ')
164 kk--;
166 line[kk] = 0;
167 printf("%s%s\n", prefixp, line);
168 offset += BYTES_PER_LINE;
173 static void follow_draw(void *contextp)
175 static const char separator[] =
176 "===================================================================\n";
178 follow_info_t *follow_info = (follow_info_t*)contextp;
179 cli_follow_info_t* cli_follow_info = (cli_follow_info_t*)follow_info->gui_data;
180 char buf[WS_INET6_ADDRSTRLEN];
181 uint32_t global_client_pos = 0, global_server_pos = 0;
182 uint32_t *global_pos;
183 uint32_t ii, jj;
184 char *buffer;
185 wmem_strbuf_t *strbuf;
186 GList *cur;
187 follow_record_t *follow_record;
188 unsigned chunk;
189 char *b64encoded;
190 const uint32_t base64_raw_len = 57; /* Encodes to 76 bytes, common in RFCs */
192 /* Print header */
193 switch (cli_follow_info->show_type)
195 case SHOW_YAML:
196 printf("peers:\n");
197 printf(" - peer: 0\n");
198 address_to_str_buf(&follow_info->client_ip, buf, sizeof buf);
199 printf(" host: %s\n", buf);
200 printf(" port: %d\n", follow_info->client_port);
201 printf(" - peer: 1\n");
202 address_to_str_buf(&follow_info->server_ip, buf, sizeof buf);
203 printf(" host: %s\n", buf);
204 printf(" port: %d\n", follow_info->server_port);
205 printf("packets:\n");
206 break;
208 default:
209 printf("\n%s", separator);
210 printf("Follow: %s,%s\n", proto_get_protocol_filter_name(get_follow_proto_id(cli_follow_info->follower)), follow_str_type(cli_follow_info));
211 printf("Filter: %s\n", follow_info->filter_out_filter);
213 address_to_str_buf(&follow_info->client_ip, buf, sizeof buf);
214 if (follow_info->client_ip.type == AT_IPv6)
215 printf("Node 0: [%s]:%u\n", buf, follow_info->client_port);
216 else
217 printf("Node 0: %s:%u\n", buf, follow_info->client_port);
219 address_to_str_buf(&follow_info->server_ip, buf, sizeof buf);
220 if (follow_info->server_ip.type == AT_IPv6)
221 printf("Node 1: [%s]:%u\n", buf, follow_info->server_port);
222 else
223 printf("Node 1: %s:%u\n", buf, follow_info->server_port);
224 break;
227 for (cur = g_list_last(follow_info->payload), chunk = 1;
228 cur != NULL;
229 cur = g_list_previous(cur), chunk++)
231 follow_record = (follow_record_t *)cur->data;
232 if (!follow_record->is_server) {
233 global_pos = &global_client_pos;
234 } else {
235 global_pos = &global_server_pos;
238 /* ignore chunks not in range */
239 if ((chunk < cli_follow_info->chunkMin) || (chunk > cli_follow_info->chunkMax)) {
240 (*global_pos) += follow_record->data->len;
241 continue;
244 /* Print start of line */
245 switch (cli_follow_info->show_type)
247 case SHOW_HEXDUMP:
248 case SHOW_YAML:
249 case SHOW_CODEC: /* The transformation to UTF-8 can change the length */
250 break;
252 case SHOW_ASCII:
253 case SHOW_EBCDIC:
254 printf("%s%u\n", follow_record->is_server ? "\t" : "", follow_record->data->len);
255 break;
257 case SHOW_RAW:
258 if (follow_record->is_server)
260 putchar('\t');
262 break;
264 default:
265 ws_assert_not_reached();
268 /* Print data */
269 switch (cli_follow_info->show_type)
271 case SHOW_HEXDUMP:
272 follow_print_hex(follow_record->is_server ? "\t" : "", *global_pos, follow_record->data->data, follow_record->data->len);
273 (*global_pos) += follow_record->data->len;
274 break;
276 case SHOW_ASCII:
277 case SHOW_EBCDIC:
278 buffer = (char *)g_malloc(follow_record->data->len+2);
280 for (ii = 0; ii < follow_record->data->len; ii++)
282 switch (follow_record->data->data[ii])
284 // XXX: qt/follow_stream_dialog.c sanitize_buffer() also passes
285 // tabs ('\t') through. Should we do that here too?
286 // The Qt code has automatic universal new line handling for reading
287 // so, e.g., \r\n in HTML becomes just \n, but we don't do that here.
288 // (The Qt version doesn't write the file as Text, so all files use
289 // Unix line endings, including on Windows.)
290 case '\r':
291 case '\n':
292 buffer[ii] = follow_record->data->data[ii];
293 break;
294 default:
295 buffer[ii] = g_ascii_isprint(follow_record->data->data[ii]) ? follow_record->data->data[ii] : '.';
296 break;
300 buffer[ii++] = '\n';
301 buffer[ii] = 0;
302 if (cli_follow_info->show_type == SHOW_EBCDIC) {
303 EBCDIC_to_ASCII(buffer, ii);
305 printf("%s", buffer);
306 g_free(buffer);
307 break;
309 case SHOW_CODEC:
310 // This does the same as the Show As UTF-8 code in the Qt version
311 // (passing through all legal UTF-8, including control codes and
312 // internal NULs, substituting illegal UTF-8 sequences with
313 // REPLACEMENT CHARACTER, and not handling valid UTF-8 sequences
314 // which are split between unreassembled frames), except for the
315 // end of line terminator issue as above.
316 strbuf = ws_utf8_make_valid_strbuf(NULL, follow_record->data->data, follow_record->data->len);
317 printf("%s%zu\n", follow_record->is_server ? "\t" : "", wmem_strbuf_get_len(strbuf));
318 fwrite(wmem_strbuf_get_str(strbuf), 1, wmem_strbuf_get_len(strbuf), stdout);
319 wmem_strbuf_destroy(strbuf);
320 putchar('\n');
321 break;
323 case SHOW_RAW:
324 buffer = (char *)g_malloc((follow_record->data->len*2)+2);
326 for (ii = 0, jj = 0; ii < follow_record->data->len; ii++)
328 buffer[jj++] = bin2hex[follow_record->data->data[ii] >> 4];
329 buffer[jj++] = bin2hex[follow_record->data->data[ii] & 0xf];
332 buffer[jj++] = '\n';
333 buffer[jj] = 0;
334 printf("%s", buffer);
335 g_free(buffer);
336 break;
338 case SHOW_YAML:
339 printf(" - packet: %d\n", follow_record->packet_num);
340 printf(" peer: %d\n", follow_record->is_server ? 1 : 0);
341 printf(" timestamp: %.9f\n", nstime_to_sec(&follow_record->abs_ts));
342 printf(" data: !!binary |\n");
343 ii = 0;
344 while (ii < follow_record->data->len) {
345 uint32_t len = ii + base64_raw_len < follow_record->data->len
346 ? base64_raw_len
347 : follow_record->data->len - ii;
348 b64encoded = g_base64_encode(&follow_record->data->data[ii], len);
349 printf(" %s\n", b64encoded);
350 g_free(b64encoded);
351 ii += len;
353 break;
355 default:
356 ws_assert_not_reached();
360 /* Print footer */
361 switch (cli_follow_info->show_type)
363 case SHOW_YAML:
364 break;
366 default:
367 printf("%s", separator);
368 break;
372 static bool follow_arg_strncmp(const char **opt_argp, const char *strp)
374 size_t len = strlen(strp);
376 if (strncmp(*opt_argp, strp, len) == 0)
378 *opt_argp += len;
379 return true;
381 return false;
384 static void
385 follow_arg_mode(const char **opt_argp, follow_info_t *follow_info)
387 cli_follow_info_t* cli_follow_info = (cli_follow_info_t*)follow_info->gui_data;
389 if (follow_arg_strncmp(opt_argp, STR_HEX))
391 cli_follow_info->show_type = SHOW_HEXDUMP;
393 else if (follow_arg_strncmp(opt_argp, STR_ASCII))
395 cli_follow_info->show_type = SHOW_ASCII;
397 else if (follow_arg_strncmp(opt_argp, STR_EBCDIC))
399 cli_follow_info->show_type = SHOW_EBCDIC;
401 else if (follow_arg_strncmp(opt_argp, STR_RAW))
403 cli_follow_info->show_type = SHOW_RAW;
405 else if (follow_arg_strncmp(opt_argp, STR_CODEC))
407 cli_follow_info->show_type = SHOW_CODEC;
409 else if (follow_arg_strncmp(opt_argp, STR_YAML))
411 cli_follow_info->show_type = SHOW_YAML;
413 else
415 follow_exit("Invalid display mode.");
419 #define _STRING(s) # s
420 #define STRING(s) _STRING(s)
422 #define ADDR_CHARS 80
423 #define ADDR_LEN (ADDR_CHARS + 1)
424 #define ADDRv6_FMT ",[%" STRING(ADDR_CHARS) "[^]]]:%d%n"
425 #define ADDRv4_FMT ",%" STRING(ADDR_CHARS) "[^:]:%d%n"
427 static void
428 follow_arg_filter(const char **opt_argp, follow_info_t *follow_info)
430 int len;
431 unsigned int ii;
432 char addr[ADDR_LEN];
433 cli_follow_info_t* cli_follow_info = (cli_follow_info_t*)follow_info->gui_data;
434 bool is_ipv6;
436 if (sscanf(*opt_argp, ",%d%n", &cli_follow_info->stream_index, &len) == 1 &&
437 ((*opt_argp)[len] == 0 || (*opt_argp)[len] == ','))
439 *opt_argp += len;
441 /* if it's HTTP2 or QUIC protocol we should read substream id otherwise it's a range parameter from follow_arg_range */
442 if (cli_follow_info->sub_stream_index == -1 && sscanf(*opt_argp, ",%d%n", &cli_follow_info->sub_stream_index, &len) == 1 &&
443 ((*opt_argp)[len] == 0 || (*opt_argp)[len] == ','))
445 *opt_argp += len;
446 follow_info->substream_id = cli_follow_info->sub_stream_index;
449 else
451 for (ii = 0; ii < array_length(cli_follow_info->addr); ii++)
453 if (sscanf(*opt_argp, ADDRv6_FMT, addr, &cli_follow_info->port[ii], &len) == 2)
455 is_ipv6 = true;
457 else if (sscanf(*opt_argp, ADDRv4_FMT, addr, &cli_follow_info->port[ii], &len) == 2)
459 is_ipv6 = false;
461 else
463 follow_exit("Invalid address.");
466 if (cli_follow_info->port[ii] <= 0 || cli_follow_info->port[ii] > UINT16_MAX)
468 follow_exit("Invalid port.");
471 if (is_ipv6)
473 if (!get_host_ipaddr6(addr, &cli_follow_info->addrBuf[ii].addrBuf_v6))
475 follow_exit("Can't get IPv6 address");
477 set_address(&cli_follow_info->addr[ii], AT_IPv6, 16, (void *)&cli_follow_info->addrBuf[ii].addrBuf_v6);
479 else
481 if (!get_host_ipaddr(addr, &cli_follow_info->addrBuf[ii].addrBuf_v4))
483 follow_exit("Can't get IPv4 address");
485 set_address(&cli_follow_info->addr[ii], AT_IPv4, 4, (void *)&cli_follow_info->addrBuf[ii].addrBuf_v4);
488 *opt_argp += len;
491 if (cli_follow_info->addr[0].type != cli_follow_info->addr[1].type)
493 follow_exit("Mismatched IP address types.");
495 cli_follow_info->stream_index = -1;
499 static void follow_arg_range(const char **opt_argp, cli_follow_info_t* cli_follow_info)
501 int len;
503 if (**opt_argp == 0)
505 cli_follow_info->chunkMin = 1;
506 cli_follow_info->chunkMax = UINT32_MAX;
508 else
510 if (sscanf(*opt_argp, ",%u-%u%n", &cli_follow_info->chunkMin, &cli_follow_info->chunkMax, &len) == 2)
512 *opt_argp += len;
514 else if (sscanf(*opt_argp, ",%u%n", &cli_follow_info->chunkMin, &len) == 1)
516 cli_follow_info->chunkMax = cli_follow_info->chunkMin;
517 *opt_argp += len;
519 else
521 follow_exit("Invalid range.");
524 if (cli_follow_info->chunkMin < 1 || cli_follow_info->chunkMin > cli_follow_info->chunkMax)
526 follow_exit("Invalid range value.");
531 static void
532 follow_arg_done(const char *opt_argp)
534 if (*opt_argp != 0)
536 follow_exit("Invalid parameter.");
540 static void follow_stream(const char *opt_argp, void *userdata)
542 follow_info_t *follow_info;
543 cli_follow_info_t* cli_follow_info;
544 GString *errp;
545 register_follow_t* follower = (register_follow_t*)userdata;
546 follow_index_filter_func index_filter;
547 follow_address_filter_func address_filter;
548 int proto_id = get_follow_proto_id(follower);
549 const char* proto_filter_name = proto_get_protocol_filter_name(proto_id);
551 opt_argp += strlen(STR_FOLLOW);
552 opt_argp += strlen(proto_filter_name);
554 cli_follow_info = g_new0(cli_follow_info_t, 1);
555 cli_follow_info->stream_index = -1;
556 /* use second parameter only for followers that have sub streams
557 * (currently HTTP2 or QUIC) */
558 if (get_follow_sub_stream_id_func(follower)) {
559 cli_follow_info->sub_stream_index = -1;
560 } else {
561 cli_follow_info->sub_stream_index = 0;
563 follow_info = g_new0(follow_info_t, 1);
564 follow_info->gui_data = cli_follow_info;
565 follow_info->substream_id = SUBSTREAM_UNUSED;
566 cli_follow_info->follower = follower;
568 follow_arg_mode(&opt_argp, follow_info);
569 follow_arg_filter(&opt_argp, follow_info);
570 follow_arg_range(&opt_argp, cli_follow_info);
571 follow_arg_done(opt_argp);
573 if (cli_follow_info->stream_index >= 0)
575 index_filter = get_follow_index_func(follower);
576 follow_info->filter_out_filter = index_filter(cli_follow_info->stream_index, cli_follow_info->sub_stream_index);
577 if (follow_info->filter_out_filter == NULL || cli_follow_info->sub_stream_index < 0)
579 follow_exit("Error creating filter for this stream.");
582 else
584 address_filter = get_follow_address_func(follower);
585 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]);
586 if (follow_info->filter_out_filter == NULL)
588 follow_exit("Error creating filter for this address/port pair.\n");
592 errp = register_tap_listener(get_follow_tap_string(follower), follow_info, follow_info->filter_out_filter, 0,
593 NULL, get_follow_tap_handler(follower), follow_draw, (tap_finish_cb)follow_free);
595 if (errp != NULL)
597 follow_free(follow_info);
598 g_string_free(errp, TRUE);
599 follow_exit("Error registering tap listener.");
603 static bool
604 follow_register(const void *key _U_, void *value, void *userdata _U_)
606 register_follow_t *follower = (register_follow_t*)value;
607 stat_tap_ui follow_ui;
608 char *cli_string;
610 cli_string = follow_get_stat_tap_string(follower);
611 follow_ui.group = REGISTER_STAT_GROUP_GENERIC;
612 follow_ui.title = NULL; /* construct this from the protocol info? */
613 follow_ui.cli_string = cli_string;
614 follow_ui.tap_init_cb = follow_stream;
615 follow_ui.nparams = 0;
616 follow_ui.params = NULL;
617 register_stat_tap_ui(&follow_ui, follower);
618 g_free(cli_string);
619 return false;
622 void
623 register_tap_listener_follow(void)
625 follow_iterate_followers(follow_register, NULL);
629 * Editor modelines - https://www.wireshark.org/tools/modelines.html
631 * Local Variables:
632 * c-basic-offset: 2
633 * tab-width: 8
634 * indent-tabs-mode: nil
635 * End:
637 * ex: set shiftwidth=2 tabstop=8 expandtab:
638 * :indentSize=2:tabSize=8:noTabs=true: