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.
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>
28 #include <wsutil/ws_assert.h>
30 void register_tap_listener_follow(void);
39 SHOW_CODEC
, // Ordered to match UTF-8 combobox index
43 typedef struct _cli_follow_info
{
44 show_type_t show_type
;
45 register_follow_t
* follower
;
58 ws_in6_addr addrBuf_v6
;
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
);
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";
89 ws_assert_not_reached();
93 ws_assert_not_reached();
95 return "<unknown-mode>";
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
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 */
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
)
126 char line
[LINE_LEN
+ 1];
128 for (ii
= 0, jj
= 0, kk
= 0; ii
< len
; )
130 if ((ii
% BYTES_PER_LINE
) == 0)
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
);
140 /* offset of ascii */
144 val
= ((uint8_t *)datap
)[ii
];
146 line
[jj
++] = bin2hex
[val
>> 4];
147 line
[jj
++] = bin2hex
[val
& 0xf];
150 line
[kk
++] = val
>= ' ' && val
< 0x7f ? val
: '.';
152 /* extra space at column 8 */
153 if (++ii
% BYTES_PER_LINE
== BYTES_PER_LINE
/2)
159 if ((ii
% BYTES_PER_LINE
) == 0 || ii
== len
)
161 /* end of line or buffer */
162 if (line
[kk
- 1] == ' ')
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
;
185 wmem_strbuf_t
*strbuf
;
187 follow_record_t
*follow_record
;
190 const uint32_t base64_raw_len
= 57; /* Encodes to 76 bytes, common in RFCs */
193 switch (cli_follow_info
->show_type
)
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");
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
);
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
);
223 printf("Node 1: %s:%u\n", buf
, follow_info
->server_port
);
227 for (cur
= g_list_last(follow_info
->payload
), chunk
= 1;
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
;
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
;
244 /* Print start of line */
245 switch (cli_follow_info
->show_type
)
249 case SHOW_CODEC
: /* The transformation to UTF-8 can change the length */
254 printf("%s%u\n", follow_record
->is_server
? "\t" : "", follow_record
->data
->len
);
258 if (follow_record
->is_server
)
265 ws_assert_not_reached();
269 switch (cli_follow_info
->show_type
)
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
;
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.)
292 buffer
[ii
] = follow_record
->data
->data
[ii
];
295 buffer
[ii
] = g_ascii_isprint(follow_record
->data
->data
[ii
]) ? follow_record
->data
->data
[ii
] : '.';
302 if (cli_follow_info
->show_type
== SHOW_EBCDIC
) {
303 EBCDIC_to_ASCII(buffer
, ii
);
305 printf("%s", buffer
);
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
);
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];
334 printf("%s", buffer
);
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");
344 while (ii
< follow_record
->data
->len
) {
345 uint32_t len
= ii
+ base64_raw_len
< follow_record
->data
->len
347 : follow_record
->data
->len
- ii
;
348 b64encoded
= g_base64_encode(&follow_record
->data
->data
[ii
], len
);
349 printf(" %s\n", b64encoded
);
356 ws_assert_not_reached();
361 switch (cli_follow_info
->show_type
)
367 printf("%s", separator
);
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)
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
;
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"
428 follow_arg_filter(const char **opt_argp
, follow_info_t
*follow_info
)
433 cli_follow_info_t
* cli_follow_info
= (cli_follow_info_t
*)follow_info
->gui_data
;
436 if (sscanf(*opt_argp
, ",%d%n", &cli_follow_info
->stream_index
, &len
) == 1 &&
437 ((*opt_argp
)[len
] == 0 || (*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
] == ','))
446 follow_info
->substream_id
= cli_follow_info
->sub_stream_index
;
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)
457 else if (sscanf(*opt_argp
, ADDRv4_FMT
, addr
, &cli_follow_info
->port
[ii
], &len
) == 2)
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.");
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
);
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
);
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
)
505 cli_follow_info
->chunkMin
= 1;
506 cli_follow_info
->chunkMax
= UINT32_MAX
;
510 if (sscanf(*opt_argp
, ",%u-%u%n", &cli_follow_info
->chunkMin
, &cli_follow_info
->chunkMax
, &len
) == 2)
514 else if (sscanf(*opt_argp
, ",%u%n", &cli_follow_info
->chunkMin
, &len
) == 1)
516 cli_follow_info
->chunkMax
= cli_follow_info
->chunkMin
;
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.");
532 follow_arg_done(const char *opt_argp
)
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
;
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;
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.");
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
);
597 follow_free(follow_info
);
598 g_string_free(errp
, TRUE
);
599 follow_exit("Error registering tap listener.");
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
;
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
);
623 register_tap_listener_follow(void)
625 follow_iterate_followers(follow_register
, NULL
);
629 * Editor modelines - https://www.wireshark.org/tools/modelines.html
634 * indent-tabs-mode: nil
637 * ex: set shiftwidth=2 tabstop=8 expandtab:
638 * :indentSize=2:tabSize=8:noTabs=true: