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