tests: add regression tests for Follow TCP Stream
[metze/wireshark/wip.git] / wiretap / cosine.c
1 /* cosine.c
2  *
3  * CoSine IPNOS L2 debug output parsing
4  * Copyright (c) 2002 by Motonori Shindo <motonori@shin.do>
5  *
6  * Wiretap Library
7  * Copyright (c) 1998 by Gilbert Ramirez <gram@alumni.rice.edu>
8  *
9  * SPDX-License-Identifier: GPL-2.0-or-later
10  */
11
12 #include "config.h"
13 #include "wtap-int.h"
14 #include "cosine.h"
15 #include "file_wrappers.h"
16
17 #include <stdlib.h>
18 #include <string.h>
19
20 /*
21
22   IPNOS: CONFIG VPN(100) VR(1.1.1.1)# diags
23   ipnos diags: Control (1/0) :: layer-2 ?
24   Registered commands for area "layer-2"
25       apply-pkt-log-profile  Configure packet logging on an interface
26       create-pkt-log-profile  Set packet-log-profile to be used for packet logging (see layer-2 pkt-log)
27       detail                Get Layer 2 low-level details
28
29   ipnos diags: Control (1/0) :: layer-2 create ?
30       create-pkt-log-profile  <pkt-log-profile-id ctl-tx-trace-length ctl-rx-trace-length data-tx-trace-length data-rx-trace-length pe-logging-or-control-blade>
31
32   ipnos diags: Control (1/0) :: layer-2 create 1 32 32 0 0 0
33   ipnos diags: Control (1/0) :: layer-2 create 2 32 32 100 100 0
34   ipnos diags: Control (1/0) :: layer-2 apply ?
35       apply-pkt-log-profile  <slot port channel subif pkt-log-profile-id>
36
37   ipnos diags: Control (1/0) :: layer-2 apply 3 0x0701 100 0 1
38   Successfully applied packet-log-profile on LI
39
40   -- Note that only the control packets are logged because the data packet size parameters are 0 in profile 1
41   IPNOS: CONFIG VPN(200) VR(3.3.3.3)# ping 20.20.20.43
42   vpn 200 : [max tries 4, timeout 5 seconds, data length 64 bytes, ttl 255]
43   ping #1 ok, RTT 0.000 seconds
44   ping #2 ok, RTT 0.000 seconds
45   ping #3 ok, RTT 0.000 seconds
46   ping #4 ok, RTT 0.000 seconds
47   [finished]
48
49   IPNOS: CONFIG VPN(200) VR(3.3.3.3)# 2000-2-1,18:19:46.8:  l2-tx (PPP:3/7/1:100), Length:16, Pro:0, Off:0, Pri:0, RM:0, Err:0 [0x4000, 0x0]
50
51
52   2000-2-1,18:19:46.8:  l2-rx (PPP:3/7/1:100), Length:16, Pro:0, Off:0, Pri:0, RM:0, Err:0 [0x4001, 0x30000]
53
54   2000-2-1,18:19:46.8:  l2-tx (PPP:3/7/1:100), Length:16, Pro:0, Off:0, Pri:0, RM:0, Err:0 [0x4000, 0x0]
55
56   2000-2-1,18:19:46.8:  l2-rx (PPP:3/7/1:100), Length:16, Pro:0, Off:0, Pri:0, RM:0, Err:0 [0x4001, 0x8030000]
57
58   ipnos diags: Control (1/0) :: layer-2 apply 3 0x0701 100 0 0
59   Successfully applied packet-log-profile on LI
60   ipnos diags: Control (1/0) :: layer-2 apply 3 0x0701 100 0 2
61   Successfully applied packet-log-profile on LI
62
63   -- Note that both control and data packets are logged because the data packet size parameter is 100 in profile 2
64      Please ignore the event-log messages getting mixed up with the ping command
65   ping 20.20.20.43 cou2000-2-1,18:20:17.0:  l2-tx (PPP:3/7/1:100), Length:16, Pro:0, Off:0, Pri:0, RM:0, Err:0 [0x4000, 0x0]
66
67           00 D0 D8 D2 FF 03 C0 21  09 29 00 08 6B 60 84 AA
68
69   2000-2-1,18:20:17.0:  l2-rx (PPP:3/7/1:100), Length:16, Pro:0, Off:0, Pri:0, RM:0, Err:0 [0x4001, 0x30000]
70           00 D0 D8 D2 FF 03 C0 21  09 29 00 08 6D FE FA AA
71
72   2000-2-1,18:20:17.0:  l2-tx (PPP:3/7/1:100), Length:16, Pro:0, Off:0, Pri:0, RM:0, Err:0 [0x4000, 0x0]
73           00 D0 D8 D2 FF 03 C0 21  0A 29 00 08 6B 60 84 AA
74
75   2000-2-1,18:20:17.0:  l2-rx (PPP:3/7/1:100), Length:16, Pro:0, Off:0, Pri:0, RM:0, Err:0 [0x4001, 0x8030000]
76           00 D0 D8 D2 FF 03 C0 21  0A 29 00 08 6D FE FA AA
77
78   nt 1 length 500
79   vpn 200 : [max tries 1, timeout 5 seconds, data length 500 bytes, ttl 255]
80   2000-2-1,18:20:24.1:  l2-tx (PPP:3/7/1:100), Length:536, Pro:1, Off:8, Pri:7, RM:0, Err:0 [0x4070, 0x801]
81           00 D0 D8 D2 FF 03 00 21  45 00 02 10 00 27 00 00
82           FF 01 69 51 14 14 14 22  14 14 14 2B 08 00 AD B8
83           00 03 00 01 10 11 12 13  14 15 16 17 18 19 1A 1B
84           1C 1D 1E 1F 20 21 22 23  24 25 26 27 28 29 2A 2B
85           2C 2D 2E 2F 30 31 32 33  34 35 36 37 38 39 3A 3B
86           3C 3D 3E 3F 40 41 42 43  44 45 46 47 48 49 4A 4B
87           4C 4D 4E 4F
88
89   ping #1 ok, RTT 0.010 seconds
90   2000-2-1,18:20:24.1:  l2-rx (PPP:3/7/1:100), Length:536, Pro:1, Off:8, Pri:7, RM:0, Err:0 [0x4071, 0x30801]
91           00 D0 D8 D2 FF 03 00 21  45 00 02 10 00 23 00 00
92           FF 01 69 55 14 14 14 2B  14 14 14 22 00 00 B5 B8
93           00 03 00 01 10 11 12 13  14 15 16 17 18 19 1A 1B
94           1C 1D 1E 1F 20 21 22 23  24 25 26 27 28 29 2A 2B
95           2C 2D 2E 2F 30 31 32 33  34 35 36 37 38 39 3A 3B
96           3C 3D 3E 3F 40 41 42 43  44 45 46 47 48 49 4A 4B
97           4C 4D 4E 4F
98
99   [finished]
100
101   IPNOS: CONFIG VPN(200) VR(3.3.3.3)# 2000-2-1,18:20:27.0:  l2-tx (PPP:3/7/1:100), Length:16, Pro:0, Off:0, Pri:0, RM:0, Err:0 [0x4000, 0x0]
102
103           00 D0 D8 D2 FF 03 C0 21  09 2A 00 08 6B 60 84 AA
104
105   2000-2-1,18:20:27.0:  l2-rx (PPP:3/7/1:100), Length:16, Pro:0, Off:0, Pri:0, RM:0, Err:0 [0x4001, 0x30000]
106           00 D0 D8 D2 FF 03 C0 21  09 2A 00 08 6D FE FA AA
107
108   2000-2-1,18:20:27.0:  l2-tx (PPP:3/7/1:100), Length:16, Pro:0, Off:0, Pri:0, RM:0, Err:0 [0x4000, 0x0]
109           00 D0 D8 D2 FF 03 C0 21  0A 2A 00 08 6B 60 84 AA
110
111   2000-2-1,18:20:27.0:  l2-rx (PPP:3/7/1:100), Length:16, Pro:0, Off:0, Pri:0, RM:0, Err:0 [0x4001, 0x30000]
112           00 D0 D8 D2 FF 03 C0 21  0A 2A 00 08 6D FE FA AA
113
114
115   ipnos diags: Control (1/0) :: layer-2 apply 3 0x0701 100 0 0
116   Successfully applied packet-log-profile on LI
117   ipnos diags: Control (1/0) ::
118
119  */
120
121 /* XXX TODO:
122
123   o Handle a case where an empty line doesn't exists as a delimiter of
124     each packet. If the output is sent to a control blade and
125     displayed as an event log, there's always an empty line between
126     each packet output, but it may not be true when it is an PE
127     output.
128
129   o Some telnet client on Windows may put in a line break at 80
130     columns when it save the session to a text file ("CRT" is such an
131     example). I don't think it's a good idea for the telnet client to
132     do so, but CRT is widely used in Windows community, I should
133     take care of that in the future.
134
135 */
136
137 /* Magic text to check for CoSine L2 debug output */
138 #define COSINE_HDR_MAGIC_STR1   "l2-tx"
139 #define COSINE_HDR_MAGIC_STR2   "l2-rx"
140
141 /* Magic text for start of packet */
142 #define COSINE_REC_MAGIC_STR1   COSINE_HDR_MAGIC_STR1
143 #define COSINE_REC_MAGIC_STR2   COSINE_HDR_MAGIC_STR2
144
145 #define COSINE_HEADER_LINES_TO_CHECK    200
146 #define COSINE_LINE_LENGTH              240
147
148 static gboolean empty_line(const gchar *line);
149 static gint64 cosine_seek_next_packet(wtap *wth, int *err, gchar **err_info,
150         char *hdr);
151 static gboolean cosine_check_file_type(wtap *wth, int *err, gchar **err_info);
152 static gboolean cosine_read(wtap *wth, int *err, gchar **err_info,
153         gint64 *data_offset);
154 static gboolean cosine_seek_read(wtap *wth, gint64 seek_off,
155         wtap_rec *rec, Buffer *buf, int *err, gchar **err_info);
156 static int parse_cosine_packet(FILE_T fh, wtap_rec *rec, Buffer* buf,
157         char *line, int *err, gchar **err_info);
158 static int parse_single_hex_dump_line(char* rec, guint8 *buf,
159         guint byte_offset);
160
161 /* Returns TRUE if the line appears to be an empty line. Otherwise it
162    returns FALSE. */
163 static gboolean empty_line(const gchar *line)
164 {
165         while (*line) {
166                 if (g_ascii_isspace(*line)) {
167                         line++;
168                         continue;
169                 } else {
170                         break;
171                 }
172         }
173         if (*line == '\0')
174                 return TRUE;
175         else
176                 return FALSE;
177 }
178
179 /* Seeks to the beginning of the next packet, and returns the
180    byte offset. Copy the header line to hdr. Returns -1 on failure,
181    and sets "*err" to the error and sets "*err_info" to null or an
182    additional error string. */
183 static gint64 cosine_seek_next_packet(wtap *wth, int *err, gchar **err_info,
184         char *hdr)
185 {
186         gint64 cur_off;
187         char buf[COSINE_LINE_LENGTH];
188
189         while (1) {
190                 cur_off = file_tell(wth->fh);
191                 if (cur_off == -1) {
192                         /* Error */
193                         *err = file_error(wth->fh, err_info);
194                         return -1;
195                 }
196                 if (file_gets(buf, sizeof(buf), wth->fh) == NULL) {
197                         *err = file_error(wth->fh, err_info);
198                         return -1;
199                 }
200                 if (strstr(buf, COSINE_REC_MAGIC_STR1) ||
201                     strstr(buf, COSINE_REC_MAGIC_STR2)) {
202                         g_strlcpy(hdr, buf, COSINE_LINE_LENGTH);
203                         return cur_off;
204                 }
205         }
206         return -1;
207 }
208
209 /* Look through the first part of a file to see if this is
210  * a CoSine L2 debug output.
211  *
212  * Returns TRUE if it is, FALSE if it isn't or if we get an I/O error;
213  * if we get an I/O error, "*err" will be set to a non-zero value and
214  * "*err_info" will be set to null or an additional error string.
215  */
216 static gboolean cosine_check_file_type(wtap *wth, int *err, gchar **err_info)
217 {
218         char    buf[COSINE_LINE_LENGTH];
219         gsize   reclen;
220         guint   line;
221
222         buf[COSINE_LINE_LENGTH-1] = '\0';
223
224         for (line = 0; line < COSINE_HEADER_LINES_TO_CHECK; line++) {
225                 if (file_gets(buf, COSINE_LINE_LENGTH, wth->fh) == NULL) {
226                         /* EOF or error. */
227                         *err = file_error(wth->fh, err_info);
228                         return FALSE;
229                 }
230
231                 reclen = strlen(buf);
232                 if (reclen < MIN(strlen(COSINE_HDR_MAGIC_STR1), strlen(COSINE_HDR_MAGIC_STR2))) {
233                         continue;
234                 }
235
236                 if (strstr(buf, COSINE_HDR_MAGIC_STR1) ||
237                     strstr(buf, COSINE_HDR_MAGIC_STR2)) {
238                         return TRUE;
239                 }
240         }
241         *err = 0;
242         return FALSE;
243 }
244
245
246 wtap_open_return_val cosine_open(wtap *wth, int *err, gchar **err_info)
247 {
248         /* Look for CoSine header */
249         if (!cosine_check_file_type(wth, err, err_info)) {
250                 if (*err != 0 && *err != WTAP_ERR_SHORT_READ)
251                         return WTAP_OPEN_ERROR;
252                 return WTAP_OPEN_NOT_MINE;
253         }
254
255         if (file_seek(wth->fh, 0L, SEEK_SET, err) == -1)        /* rewind */
256                 return WTAP_OPEN_ERROR;
257
258         wth->file_encap = WTAP_ENCAP_COSINE;
259         wth->file_type_subtype = WTAP_FILE_TYPE_SUBTYPE_COSINE;
260         wth->snapshot_length = 0; /* not known */
261         wth->subtype_read = cosine_read;
262         wth->subtype_seek_read = cosine_seek_read;
263         wth->file_tsprec = WTAP_TSPREC_CSEC;
264
265         return WTAP_OPEN_MINE;
266 }
267
268 /* Find the next packet and parse it; called from wtap_read(). */
269 static gboolean cosine_read(wtap *wth, int *err, gchar **err_info,
270     gint64 *data_offset)
271 {
272         gint64  offset;
273         char    line[COSINE_LINE_LENGTH];
274
275         /* Find the next packet */
276         offset = cosine_seek_next_packet(wth, err, err_info, line);
277         if (offset < 0)
278                 return FALSE;
279         *data_offset = offset;
280
281         /* Parse the header and convert the ASCII hex dump to binary data */
282         return parse_cosine_packet(wth->fh, &wth->rec, wth->rec_data,
283             line, err, err_info);
284 }
285
286 /* Used to read packets in random-access fashion */
287 static gboolean
288 cosine_seek_read(wtap *wth, gint64 seek_off, wtap_rec *rec,
289         Buffer *buf, int *err, gchar **err_info)
290 {
291         char    line[COSINE_LINE_LENGTH];
292
293         if (file_seek(wth->random_fh, seek_off, SEEK_SET, err) == -1)
294                 return FALSE;
295
296         if (file_gets(line, COSINE_LINE_LENGTH, wth->random_fh) == NULL) {
297                 *err = file_error(wth->random_fh, err_info);
298                 if (*err == 0) {
299                         *err = WTAP_ERR_SHORT_READ;
300                 }
301                 return FALSE;
302         }
303
304         /* Parse the header and convert the ASCII hex dump to binary data */
305         return parse_cosine_packet(wth->random_fh, rec, buf, line, err,
306             err_info);
307 }
308
309 /* Parses a packet record header. There are two possible formats:
310     1) output to a control blade with date and time
311         2002-5-10,20:1:31.4:  l2-tx (FR:3/7/1:1), Length:18, Pro:0, Off:0, Pri:0, RM:0, Err:0 [0x4000, 0x0]
312     2) output to PE without date and time
313         l2-tx (FR:3/7/1:1), Length:18, Pro:0, Off:0, Pri:0, RM:0, Err:0 [0x4000, 0x0] */
314 static gboolean
315 parse_cosine_packet(FILE_T fh, wtap_rec *rec, Buffer *buf,
316     char *line, int *err, gchar **err_info)
317 {
318         union wtap_pseudo_header *pseudo_header = &rec->rec_header.packet_header.pseudo_header;
319         int     num_items_scanned;
320         int     yy, mm, dd, hr, min, sec, csec, pkt_len;
321         int     pro, off, pri, rm, error;
322         guint   code1, code2;
323         char    if_name[COSINE_MAX_IF_NAME_LEN] = "", direction[6] = "";
324         struct  tm tm;
325         guint8 *pd;
326         int     i, hex_lines, n, caplen = 0;
327
328         if (sscanf(line, "%4d-%2d-%2d,%2d:%2d:%2d.%9d:",
329                    &yy, &mm, &dd, &hr, &min, &sec, &csec) == 7) {
330                 /* appears to be output to a control blade */
331                 num_items_scanned = sscanf(line,
332                    "%4d-%2d-%2d,%2d:%2d:%2d.%9d: %5s (%127[A-Za-z0-9/:]), Length:%9d, Pro:%9d, Off:%9d, Pri:%9d, RM:%9d, Err:%9d [%8x, %8x]",
333                         &yy, &mm, &dd, &hr, &min, &sec, &csec,
334                                    direction, if_name, &pkt_len,
335                                    &pro, &off, &pri, &rm, &error,
336                                    &code1, &code2);
337
338                 if (num_items_scanned != 17) {
339                         *err = WTAP_ERR_BAD_FILE;
340                         *err_info = g_strdup("cosine: purported control blade line doesn't have code values");
341                         return FALSE;
342                 }
343         } else {
344                 /* appears to be output to PE */
345                 num_items_scanned = sscanf(line,
346                    "%5s (%127[A-Za-z0-9/:]), Length:%9d, Pro:%9d, Off:%9d, Pri:%9d, RM:%9d, Err:%9d [%8x, %8x]",
347                                    direction, if_name, &pkt_len,
348                                    &pro, &off, &pri, &rm, &error,
349                                    &code1, &code2);
350
351                 if (num_items_scanned != 10) {
352                         *err = WTAP_ERR_BAD_FILE;
353                         *err_info = g_strdup("cosine: header line is neither control blade nor PE output");
354                         return FALSE;
355                 }
356                 yy = mm = dd = hr = min = sec = csec = 0;
357         }
358         if (pkt_len < 0) {
359                 *err = WTAP_ERR_BAD_FILE;
360                 *err_info = g_strdup("cosine: packet header has a negative packet length");
361                 return FALSE;
362         }
363         if (pkt_len > WTAP_MAX_PACKET_SIZE_STANDARD) {
364                 /*
365                  * Probably a corrupt capture file; don't blow up trying
366                  * to allocate space for an immensely-large packet.
367                  */
368                 *err = WTAP_ERR_BAD_FILE;
369                 *err_info = g_strdup_printf("cosine: File has %u-byte packet, bigger than maximum of %u",
370                     pkt_len, WTAP_MAX_PACKET_SIZE_STANDARD);
371                 return FALSE;
372         }
373
374         rec->rec_type = REC_TYPE_PACKET;
375         rec->presence_flags = WTAP_HAS_TS|WTAP_HAS_CAP_LEN;
376         tm.tm_year = yy - 1900;
377         tm.tm_mon = mm - 1;
378         tm.tm_mday = dd;
379         tm.tm_hour = hr;
380         tm.tm_min = min;
381         tm.tm_sec = sec;
382         tm.tm_isdst = -1;
383         rec->ts.secs = mktime(&tm);
384         rec->ts.nsecs = csec * 10000000;
385         rec->rec_header.packet_header.len = pkt_len;
386
387         /* XXX need to handle other encapsulations like Cisco HDLC,
388            Frame Relay and ATM */
389         if (strncmp(if_name, "TEST:", 5) == 0) {
390                 pseudo_header->cosine.encap = COSINE_ENCAP_TEST;
391         } else if (strncmp(if_name, "PPoATM:", 7) == 0) {
392                 pseudo_header->cosine.encap = COSINE_ENCAP_PPoATM;
393         } else if (strncmp(if_name, "PPoFR:", 6) == 0) {
394                 pseudo_header->cosine.encap = COSINE_ENCAP_PPoFR;
395         } else if (strncmp(if_name, "ATM:", 4) == 0) {
396                 pseudo_header->cosine.encap = COSINE_ENCAP_ATM;
397         } else if (strncmp(if_name, "FR:", 3) == 0) {
398                 pseudo_header->cosine.encap = COSINE_ENCAP_FR;
399         } else if (strncmp(if_name, "HDLC:", 5) == 0) {
400                 pseudo_header->cosine.encap = COSINE_ENCAP_HDLC;
401         } else if (strncmp(if_name, "PPP:", 4) == 0) {
402                 pseudo_header->cosine.encap = COSINE_ENCAP_PPP;
403         } else if (strncmp(if_name, "ETH:", 4) == 0) {
404                 pseudo_header->cosine.encap = COSINE_ENCAP_ETH;
405         } else {
406                 pseudo_header->cosine.encap = COSINE_ENCAP_UNKNOWN;
407         }
408         if (strncmp(direction, "l2-tx", 5) == 0) {
409                 pseudo_header->cosine.direction = COSINE_DIR_TX;
410         } else if (strncmp(direction, "l2-rx", 5) == 0) {
411                 pseudo_header->cosine.direction = COSINE_DIR_RX;
412         }
413         g_strlcpy(pseudo_header->cosine.if_name, if_name,
414                 COSINE_MAX_IF_NAME_LEN);
415         pseudo_header->cosine.pro = pro;
416         pseudo_header->cosine.off = off;
417         pseudo_header->cosine.pri = pri;
418         pseudo_header->cosine.rm = rm;
419         pseudo_header->cosine.err = error;
420
421         /* Make sure we have enough room for the packet */
422         ws_buffer_assure_space(buf, pkt_len);
423         pd = ws_buffer_start_ptr(buf);
424
425         /* Calculate the number of hex dump lines, each
426          * containing 16 bytes of data */
427         hex_lines = pkt_len / 16 + ((pkt_len % 16) ? 1 : 0);
428
429         for (i = 0; i < hex_lines; i++) {
430                 if (file_gets(line, COSINE_LINE_LENGTH, fh) == NULL) {
431                         *err = file_error(fh, err_info);
432                         if (*err == 0) {
433                                 *err = WTAP_ERR_SHORT_READ;
434                         }
435                         return FALSE;
436                 }
437                 if (empty_line(line)) {
438                         break;
439                 }
440                 if ((n = parse_single_hex_dump_line(line, pd, i*16)) == -1) {
441                         *err = WTAP_ERR_BAD_FILE;
442                         *err_info = g_strdup("cosine: hex dump line doesn't have 16 numbers");
443                         return FALSE;
444                 }
445                 caplen += n;
446         }
447         rec->rec_header.packet_header.caplen = caplen;
448         return TRUE;
449 }
450
451 /* Take a string representing one line from a hex dump and converts
452  * the text to binary data. We place the bytes in the buffer at the
453  * specified offset.
454  *
455  * Returns number of bytes successfully read, -1 if bad.  */
456 static int
457 parse_single_hex_dump_line(char* rec, guint8 *buf, guint byte_offset)
458 {
459         int num_items_scanned, i;
460         unsigned int bytes[16];
461
462         num_items_scanned = sscanf(rec, "%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x",
463                                &bytes[0], &bytes[1], &bytes[2], &bytes[3],
464                                &bytes[4], &bytes[5], &bytes[6], &bytes[7],
465                                &bytes[8], &bytes[9], &bytes[10], &bytes[11],
466                                &bytes[12], &bytes[13], &bytes[14], &bytes[15]);
467         if (num_items_scanned == 0)
468                 return -1;
469
470         if (num_items_scanned > 16)
471                 num_items_scanned = 16;
472
473         for (i=0; i<num_items_scanned; i++) {
474                 buf[byte_offset + i] = (guint8)bytes[i];
475         }
476
477         return num_items_scanned;
478 }
479
480 /*
481  * Editor modelines  -  http://www.wireshark.org/tools/modelines.html
482  *
483  * Local variables:
484  * c-basic-offset: 8
485  * tab-width: 8
486  * indent-tabs-mode: t
487  * End:
488  *
489  * vi: set shiftwidth=8 tabstop=8 noexpandtab:
490  * :indentSize=8:tabSize=8:noTabs=false:
491  */