diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 20:34:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 20:34:10 +0000 |
commit | e4ba6dbc3f1e76890b22773807ea37fe8fa2b1bc (patch) | |
tree | 68cb5ef9081156392f1dd62a00c6ccc1451b93df /ui/cli/tap-follow.c | |
parent | Initial commit. (diff) | |
download | wireshark-e4ba6dbc3f1e76890b22773807ea37fe8fa2b1bc.tar.xz wireshark-e4ba6dbc3f1e76890b22773807ea37fe8fa2b1bc.zip |
Adding upstream version 4.2.2.upstream/4.2.2
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ui/cli/tap-follow.c')
-rw-r--r-- | ui/cli/tap-follow.c | 639 |
1 files changed, 639 insertions, 0 deletions
diff --git a/ui/cli/tap-follow.c b/ui/cli/tap-follow.c new file mode 100644 index 00000000..51f8f0f7 --- /dev/null +++ b/ui/cli/tap-follow.c @@ -0,0 +1,639 @@ +/* tap-follow.c + * + * Copyright 2011-2013, QA Cafe <info@qacafe.com> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +/* This module provides udp and tcp follow stream capabilities to tshark. + * It is only used by tshark and not wireshark. + */ + +#include "config.h" + +#include <stdio.h> +#include <stdlib.h> + +#include <glib.h> +#include <epan/addr_resolv.h> +#include <wsutil/str_util.h> +#include <wsutil/unicode-utils.h> +#include <epan/follow.h> +#include <epan/stat_tap_ui.h> +#include <epan/tap.h> +#include <wsutil/ws_assert.h> + +void register_tap_listener_follow(void); + +/* Show Type */ +typedef enum { + SHOW_ASCII, + SHOW_CARRAY, + SHOW_EBCDIC, + SHOW_HEXDUMP, + SHOW_RAW, + SHOW_CODEC, // Ordered to match UTF-8 combobox index + SHOW_YAML +} show_type_t; + +typedef struct _cli_follow_info { + show_type_t show_type; + register_follow_t* follower; + + /* range */ + guint32 chunkMin; + guint32 chunkMax; + + /* filter */ + int stream_index; + int sub_stream_index; + int port[2]; + address addr[2]; + union { + guint32 addrBuf_v4; + ws_in6_addr addrBuf_v6; + } addrBuf[2]; +} cli_follow_info_t; + + +#define STR_FOLLOW "follow," + +#define STR_HEX ",hex" +#define STR_ASCII ",ascii" +#define STR_EBCDIC ",ebcdic" +#define STR_RAW ",raw" +#define STR_CODEC ",utf-8" +#define STR_YAML ",yaml" + +WS_NORETURN static void follow_exit(const char *strp) +{ + fprintf(stderr, "tshark: follow - %s\n", strp); + exit(1); +} + +static const char * follow_str_type(cli_follow_info_t* cli_follow_info) +{ + switch (cli_follow_info->show_type) + { + case SHOW_HEXDUMP: return "hex"; + case SHOW_ASCII: return "ascii"; + case SHOW_EBCDIC: return "ebcdic"; + case SHOW_RAW: return "raw"; + case SHOW_CODEC: return "utf-8"; + case SHOW_YAML: return "yaml"; + default: + ws_assert_not_reached(); + break; + } + + ws_assert_not_reached(); + + return "<unknown-mode>"; +} + +static void +follow_free(follow_info_t *follow_info) +{ + cli_follow_info_t* cli_follow_info = (cli_follow_info_t*)follow_info->gui_data; + + g_free(cli_follow_info); + follow_info_free(follow_info); +} + +#define BYTES_PER_LINE 16 +#define OFFSET_LEN 8 +#define OFFSET_SPACE 2 +#define HEX_START (OFFSET_LEN + OFFSET_SPACE) +#define HEX_LEN (BYTES_PER_LINE * 3) /* extra space at column 8 */ +#define HEX_SPACE 2 +#define ASCII_START (HEX_START + HEX_LEN + HEX_SPACE) +#define ASCII_LEN (BYTES_PER_LINE + 1) /* extra space at column 8 */ +#define LINE_LEN (ASCII_START + ASCII_LEN) + +static const char bin2hex[] = {'0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + +static void follow_print_hex(const char *prefixp, guint32 offset, void *datap, int len) +{ + int ii; + int jj; + int kk; + guint8 val; + char line[LINE_LEN + 1]; + + for (ii = 0, jj = 0, kk = 0; ii < len; ) + { + if ((ii % BYTES_PER_LINE) == 0) + { + /* new line */ + snprintf(line, LINE_LEN + 1, "%0*X", OFFSET_LEN, offset); + memset(line + HEX_START - OFFSET_SPACE, ' ', + HEX_LEN + OFFSET_SPACE + HEX_SPACE); + + /* offset of hex */ + jj = HEX_START; + + /* offset of ascii */ + kk = ASCII_START; + } + + val = ((guint8 *)datap)[ii]; + + line[jj++] = bin2hex[val >> 4]; + line[jj++] = bin2hex[val & 0xf]; + jj++; + + line[kk++] = val >= ' ' && val < 0x7f ? val : '.'; + + /* extra space at column 8 */ + if (++ii % BYTES_PER_LINE == BYTES_PER_LINE/2) + { + line[jj++] = ' '; + line[kk++] = ' '; + } + + if ((ii % BYTES_PER_LINE) == 0 || ii == len) + { + /* end of line or buffer */ + if (line[kk - 1] == ' ') + { + kk--; + } + line[kk] = 0; + printf("%s%s\n", prefixp, line); + offset += BYTES_PER_LINE; + } + } +} + +static void follow_draw(void *contextp) +{ + static const char separator[] = + "===================================================================\n"; + + follow_info_t *follow_info = (follow_info_t*)contextp; + cli_follow_info_t* cli_follow_info = (cli_follow_info_t*)follow_info->gui_data; + gchar buf[WS_INET6_ADDRSTRLEN]; + guint32 global_client_pos = 0, global_server_pos = 0; + guint32 *global_pos; + guint32 ii, jj; + char *buffer; + wmem_strbuf_t *strbuf; + GList *cur; + follow_record_t *follow_record; + guint chunk; + gchar *b64encoded; + const guint32 base64_raw_len = 57; /* Encodes to 76 bytes, common in RFCs */ + + /* Print header */ + switch (cli_follow_info->show_type) + { + case SHOW_YAML: + printf("peers:\n"); + printf(" - peer: 0\n"); + address_to_str_buf(&follow_info->client_ip, buf, sizeof buf); + printf(" host: %s\n", buf); + printf(" port: %d\n", follow_info->client_port); + printf(" - peer: 1\n"); + address_to_str_buf(&follow_info->server_ip, buf, sizeof buf); + printf(" host: %s\n", buf); + printf(" port: %d\n", follow_info->server_port); + printf("packets:\n"); + break; + + default: + printf("\n%s", separator); + printf("Follow: %s,%s\n", proto_get_protocol_filter_name(get_follow_proto_id(cli_follow_info->follower)), follow_str_type(cli_follow_info)); + printf("Filter: %s\n", follow_info->filter_out_filter); + + address_to_str_buf(&follow_info->client_ip, buf, sizeof buf); + if (follow_info->client_ip.type == AT_IPv6) + printf("Node 0: [%s]:%u\n", buf, follow_info->client_port); + else + printf("Node 0: %s:%u\n", buf, follow_info->client_port); + + address_to_str_buf(&follow_info->server_ip, buf, sizeof buf); + if (follow_info->server_ip.type == AT_IPv6) + printf("Node 1: [%s]:%u\n", buf, follow_info->server_port); + else + printf("Node 1: %s:%u\n", buf, follow_info->server_port); + break; + } + + for (cur = g_list_last(follow_info->payload), chunk = 1; + cur != NULL; + cur = g_list_previous(cur), chunk++) + { + follow_record = (follow_record_t *)cur->data; + if (!follow_record->is_server) { + global_pos = &global_client_pos; + } else { + global_pos = &global_server_pos; + } + + /* ignore chunks not in range */ + if ((chunk < cli_follow_info->chunkMin) || (chunk > cli_follow_info->chunkMax)) { + (*global_pos) += follow_record->data->len; + continue; + } + + /* Print start of line */ + switch (cli_follow_info->show_type) + { + case SHOW_HEXDUMP: + case SHOW_YAML: + case SHOW_CODEC: /* The transformation to UTF-8 can change the length */ + break; + + case SHOW_ASCII: + case SHOW_EBCDIC: + printf("%s%u\n", follow_record->is_server ? "\t" : "", follow_record->data->len); + break; + + case SHOW_RAW: + if (follow_record->is_server) + { + putchar('\t'); + } + break; + + default: + ws_assert_not_reached(); + } + + /* Print data */ + switch (cli_follow_info->show_type) + { + case SHOW_HEXDUMP: + follow_print_hex(follow_record->is_server ? "\t" : "", *global_pos, follow_record->data->data, follow_record->data->len); + (*global_pos) += follow_record->data->len; + break; + + case SHOW_ASCII: + case SHOW_EBCDIC: + buffer = (char *)g_malloc(follow_record->data->len+2); + + for (ii = 0; ii < follow_record->data->len; ii++) + { + switch (follow_record->data->data[ii]) + { + // XXX: qt/follow_stream_dialog.c sanitize_buffer() also passes + // tabs ('\t') through. Should we do that here too? + // The Qt code has automatic universal new line handling for reading + // so, e.g., \r\n in HTML becomes just \n, but we don't do that here. + // (The Qt version doesn't write the file as Text, so all files use + // Unix line endings, including on Windows.) + case '\r': + case '\n': + buffer[ii] = follow_record->data->data[ii]; + break; + default: + buffer[ii] = g_ascii_isprint(follow_record->data->data[ii]) ? follow_record->data->data[ii] : '.'; + break; + } + } + + buffer[ii++] = '\n'; + buffer[ii] = 0; + if (cli_follow_info->show_type == SHOW_EBCDIC) { + EBCDIC_to_ASCII(buffer, ii); + } + printf("%s", buffer); + g_free(buffer); + break; + + case SHOW_CODEC: + // This does the same as the Show As UTF-8 code in the Qt version + // (passing through all legal UTF-8, including control codes and + // internal NULs, substituting illegal UTF-8 sequences with + // REPLACEMENT CHARACTER, and not handling valid UTF-8 sequences + // which are split between unreassembled frames), except for the + // end of line terminator issue as above. + strbuf = ws_utf8_make_valid_strbuf(NULL, follow_record->data->data, follow_record->data->len); + printf("%s%zu\n", follow_record->is_server ? "\t" : "", wmem_strbuf_get_len(strbuf)); + fwrite(wmem_strbuf_get_str(strbuf), 1, wmem_strbuf_get_len(strbuf), stdout); + wmem_strbuf_destroy(strbuf); + putchar('\n'); + break; + + case SHOW_RAW: + buffer = (char *)g_malloc((follow_record->data->len*2)+2); + + for (ii = 0, jj = 0; ii < follow_record->data->len; ii++) + { + buffer[jj++] = bin2hex[follow_record->data->data[ii] >> 4]; + buffer[jj++] = bin2hex[follow_record->data->data[ii] & 0xf]; + } + + buffer[jj++] = '\n'; + buffer[jj] = 0; + printf("%s", buffer); + g_free(buffer); + break; + + case SHOW_YAML: + printf(" - packet: %d\n", follow_record->packet_num); + printf(" peer: %d\n", follow_record->is_server ? 1 : 0); + printf(" timestamp: %.9f\n", nstime_to_sec(&follow_record->abs_ts)); + printf(" data: !!binary |\n"); + ii = 0; + while (ii < follow_record->data->len) { + guint32 len = ii + base64_raw_len < follow_record->data->len + ? base64_raw_len + : follow_record->data->len - ii; + b64encoded = g_base64_encode(&follow_record->data->data[ii], len); + printf(" %s\n", b64encoded); + g_free(b64encoded); + ii += len; + } + break; + + default: + ws_assert_not_reached(); + } + } + + /* Print footer */ + switch (cli_follow_info->show_type) + { + case SHOW_YAML: + break; + + default: + printf("%s", separator); + break; + } +} + +static gboolean follow_arg_strncmp(const char **opt_argp, const char *strp) +{ + size_t len = strlen(strp); + + if (strncmp(*opt_argp, strp, len) == 0) + { + *opt_argp += len; + return TRUE; + } + return FALSE; +} + +static void +follow_arg_mode(const char **opt_argp, follow_info_t *follow_info) +{ + cli_follow_info_t* cli_follow_info = (cli_follow_info_t*)follow_info->gui_data; + + if (follow_arg_strncmp(opt_argp, STR_HEX)) + { + cli_follow_info->show_type = SHOW_HEXDUMP; + } + else if (follow_arg_strncmp(opt_argp, STR_ASCII)) + { + cli_follow_info->show_type = SHOW_ASCII; + } + else if (follow_arg_strncmp(opt_argp, STR_EBCDIC)) + { + cli_follow_info->show_type = SHOW_EBCDIC; + } + else if (follow_arg_strncmp(opt_argp, STR_RAW)) + { + cli_follow_info->show_type = SHOW_RAW; + } + else if (follow_arg_strncmp(opt_argp, STR_CODEC)) + { + cli_follow_info->show_type = SHOW_CODEC; + } + else if (follow_arg_strncmp(opt_argp, STR_YAML)) + { + cli_follow_info->show_type = SHOW_YAML; + } + else + { + follow_exit("Invalid display mode."); + } +} + +#define _STRING(s) # s +#define STRING(s) _STRING(s) + +#define ADDR_CHARS 80 +#define ADDR_LEN (ADDR_CHARS + 1) +#define ADDRv6_FMT ",[%" STRING(ADDR_CHARS) "[^]]]:%d%n" +#define ADDRv4_FMT ",%" STRING(ADDR_CHARS) "[^:]:%d%n" + +static void +follow_arg_filter(const char **opt_argp, follow_info_t *follow_info) +{ + int len; + unsigned int ii; + char addr[ADDR_LEN]; + cli_follow_info_t* cli_follow_info = (cli_follow_info_t*)follow_info->gui_data; + gboolean is_ipv6; + + if (sscanf(*opt_argp, ",%d%n", &cli_follow_info->stream_index, &len) == 1 && + ((*opt_argp)[len] == 0 || (*opt_argp)[len] == ',')) + { + *opt_argp += len; + + /* if it's HTTP2 or QUIC protocol we should read substream id otherwise it's a range parameter from follow_arg_range */ + if (cli_follow_info->sub_stream_index == -1 && sscanf(*opt_argp, ",%d%n", &cli_follow_info->sub_stream_index, &len) == 1 && + ((*opt_argp)[len] == 0 || (*opt_argp)[len] == ',')) + { + *opt_argp += len; + follow_info->substream_id = cli_follow_info->sub_stream_index; + } + } + else + { + for (ii = 0; ii < sizeof cli_follow_info->addr/sizeof *cli_follow_info->addr; ii++) + { + if (sscanf(*opt_argp, ADDRv6_FMT, addr, &cli_follow_info->port[ii], &len) == 2) + { + is_ipv6 = TRUE; + } + else if (sscanf(*opt_argp, ADDRv4_FMT, addr, &cli_follow_info->port[ii], &len) == 2) + { + is_ipv6 = FALSE; + } + else + { + follow_exit("Invalid address."); + } + + if (cli_follow_info->port[ii] <= 0 || cli_follow_info->port[ii] > G_MAXUINT16) + { + follow_exit("Invalid port."); + } + + if (is_ipv6) + { + if (!get_host_ipaddr6(addr, &cli_follow_info->addrBuf[ii].addrBuf_v6)) + { + follow_exit("Can't get IPv6 address"); + } + set_address(&cli_follow_info->addr[ii], AT_IPv6, 16, (void *)&cli_follow_info->addrBuf[ii].addrBuf_v6); + } + else + { + if (!get_host_ipaddr(addr, &cli_follow_info->addrBuf[ii].addrBuf_v4)) + { + follow_exit("Can't get IPv4 address"); + } + set_address(&cli_follow_info->addr[ii], AT_IPv4, 4, (void *)&cli_follow_info->addrBuf[ii].addrBuf_v4); + } + + *opt_argp += len; + } + + if (cli_follow_info->addr[0].type != cli_follow_info->addr[1].type) + { + follow_exit("Mismatched IP address types."); + } + cli_follow_info->stream_index = -1; + } +} + +static void follow_arg_range(const char **opt_argp, cli_follow_info_t* cli_follow_info) +{ + int len; + + if (**opt_argp == 0) + { + cli_follow_info->chunkMin = 1; + cli_follow_info->chunkMax = G_MAXUINT32; + } + else + { + if (sscanf(*opt_argp, ",%u-%u%n", &cli_follow_info->chunkMin, &cli_follow_info->chunkMax, &len) == 2) + { + *opt_argp += len; + } + else if (sscanf(*opt_argp, ",%u%n", &cli_follow_info->chunkMin, &len) == 1) + { + cli_follow_info->chunkMax = cli_follow_info->chunkMin; + *opt_argp += len; + } + else + { + follow_exit("Invalid range."); + } + + if (cli_follow_info->chunkMin < 1 || cli_follow_info->chunkMin > cli_follow_info->chunkMax) + { + follow_exit("Invalid range value."); + } + } +} + +static void +follow_arg_done(const char *opt_argp) +{ + if (*opt_argp != 0) + { + follow_exit("Invalid parameter."); + } +} + +static void follow_stream(const char *opt_argp, void *userdata) +{ + follow_info_t *follow_info; + cli_follow_info_t* cli_follow_info; + GString *errp; + register_follow_t* follower = (register_follow_t*)userdata; + follow_index_filter_func index_filter; + follow_address_filter_func address_filter; + int proto_id = get_follow_proto_id(follower); + const char* proto_filter_name = proto_get_protocol_filter_name(proto_id); + + opt_argp += strlen(STR_FOLLOW); + opt_argp += strlen(proto_filter_name); + + cli_follow_info = g_new0(cli_follow_info_t, 1); + cli_follow_info->stream_index = -1; + /* use second parameter only for followers that have sub streams + * (currently HTTP2 or QUIC) */ + if (get_follow_sub_stream_id_func(follower)) { + cli_follow_info->sub_stream_index = -1; + } else { + cli_follow_info->sub_stream_index = 0; + } + follow_info = g_new0(follow_info_t, 1); + follow_info->gui_data = cli_follow_info; + follow_info->substream_id = SUBSTREAM_UNUSED; + cli_follow_info->follower = follower; + + follow_arg_mode(&opt_argp, follow_info); + follow_arg_filter(&opt_argp, follow_info); + follow_arg_range(&opt_argp, cli_follow_info); + follow_arg_done(opt_argp); + + if (cli_follow_info->stream_index >= 0) + { + index_filter = get_follow_index_func(follower); + follow_info->filter_out_filter = index_filter(cli_follow_info->stream_index, cli_follow_info->sub_stream_index); + if (follow_info->filter_out_filter == NULL || cli_follow_info->sub_stream_index < 0) + { + follow_exit("Error creating filter for this stream."); + } + } + else + { + address_filter = get_follow_address_func(follower); + 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]); + if (follow_info->filter_out_filter == NULL) + { + follow_exit("Error creating filter for this address/port pair.\n"); + } + } + + errp = register_tap_listener(get_follow_tap_string(follower), follow_info, follow_info->filter_out_filter, 0, + NULL, get_follow_tap_handler(follower), follow_draw, (tap_finish_cb)follow_free); + + if (errp != NULL) + { + follow_free(follow_info); + g_string_free(errp, TRUE); + follow_exit("Error registering tap listener."); + } +} + +static bool +follow_register(const void *key _U_, void *value, void *userdata _U_) +{ + register_follow_t *follower = (register_follow_t*)value; + stat_tap_ui follow_ui; + gchar *cli_string; + + cli_string = follow_get_stat_tap_string(follower); + follow_ui.group = REGISTER_STAT_GROUP_GENERIC; + follow_ui.title = NULL; /* construct this from the protocol info? */ + follow_ui.cli_string = cli_string; + follow_ui.tap_init_cb = follow_stream; + follow_ui.nparams = 0; + follow_ui.params = NULL; + register_stat_tap_ui(&follow_ui, follower); + g_free(cli_string); + return FALSE; +} + +void +register_tap_listener_follow(void) +{ + follow_iterate_followers(follow_register, NULL); +} + +/* + * Editor modelines - https://www.wireshark.org/tools/modelines.html + * + * Local Variables: + * c-basic-offset: 2 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=2 tabstop=8 expandtab: + * :indentSize=2:tabSize=8:noTabs=true: + */ |