1 /* ipfix.c
2  *
3  * Wiretap Library
4  * Copyright (c) 1998 by Gilbert Ramirez <gram@alumni.rice.edu>
5  *
6  * File format support for ipfix file format
7  * Copyright (c) 2010 by Hadriel Kaplan <hadrielk@yahoo.com>
8  *   with generous copying from other wiretaps, such as pcapng
9  *
10  * SPDX-License-Identifier: GPL-2.0-or-later
11  */
12 
13 /* File format reference:
14  *   RFC 5655 and 5101
15  *   https://tools.ietf.org/rfc/rfc5655
16  *   https://tools.ietf.org/rfc/rfc5101
17  *
18  * This wiretap is for an ipfix file format reader, per RFC 5655/5101.
19  * All "records" in the file are IPFIX messages, beginning with an IPFIX
20  *  message header of 16 bytes as follows from RFC 5101:
21     0                   1                   2                   3
22     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
23    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
24    |       Version Number          |            Length             |
25    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
26    |                           Export Time                         |
27    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
28    |                       Sequence Number                         |
29    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
30    |                    Observation Domain ID                      |
31    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
32 
33    Figure F: IPFIX Message Header Format
34 
35  * which is then followed by one or more "Sets": Data Sets, Template Sets,
36  * and Options Template Sets.  Each Set then has one or more Records in
37  * it.
38  *
39  * All IPFIX files are recorded in big-endian form (network byte order),
40  * per the RFCs.  That means if we're on a little-endian system, all
41  * hell will break loose if we don't g_ntohX.
42  *
43  * Since wireshark already has an IPFIX dissector (implemented in
44  * packet-netflow.c), this reader will just set that dissector upon
45  * reading each message.  Thus, an IPFIX Message is treated as a packet
46  * as far as the dissector is concerned.
47  */
48 
49 #include "config.h"
50 
51 #define WS_LOG_DOMAIN LOG_DOMAIN_WIRETAP
52 
53 #include <stdlib.h>
54 #include <string.h>
55 #include <errno.h>
56 #include "wtap-int.h"
57 #include "file_wrappers.h"
58 #include "ipfix.h"
59 
60 #include <wsutil/strtoi.h>
61 #include <wsutil/wslog.h>
62 
63 #define RECORDS_FOR_IPFIX_CHECK 20
64 
65 static gboolean
66 ipfix_read(wtap *wth, wtap_rec *rec, Buffer *buf, int *err,
67     gchar **err_info, gint64 *data_offset);
68 static gboolean
69 ipfix_seek_read(wtap *wth, gint64 seek_off,
70     wtap_rec *rec, Buffer *buf, int *err, gchar **err_info);
71 
72 #define IPFIX_VERSION 10
73 
74 /* ipfix: message header */
75 typedef struct ipfix_message_header_s {
76     guint16 version;
77     guint16 message_length;
78     guint32 export_time_secs;
79     guint32 sequence_number;
80     guint32 observation_id; /* might be 0 for none */
81     /* x bytes msg_body */
82 } ipfix_message_header_t;
83 #define IPFIX_MSG_HDR_SIZE 16
84 
85 /* ipfix: common Set header for every Set type */
86 typedef struct ipfix_set_header_s {
87     guint16 set_type;
88     guint16 set_length;
89     /* x bytes set_body */
90 } ipfix_set_header_t;
91 #define IPFIX_SET_HDR_SIZE 4
92 
93 
94 static int ipfix_file_type_subtype = -1;
95 
96 void register_ipfix(void);
97 
98 /* Read IPFIX message header from file.  Return true on success.  Set *err to
99  * 0 on EOF, any other value for "real" errors (EOF is ok, since return
100  * value is still FALSE)
101  */
102 static gboolean
ipfix_read_message_header(ipfix_message_header_t * pfx_hdr,FILE_T fh,int * err,gchar ** err_info)103 ipfix_read_message_header(ipfix_message_header_t *pfx_hdr, FILE_T fh, int *err, gchar **err_info)
104 {
105     if (!wtap_read_bytes_or_eof(fh, pfx_hdr, IPFIX_MSG_HDR_SIZE, err, err_info))
106         return FALSE;
107 
108     /* fix endianness, because IPFIX files are always big-endian */
109     pfx_hdr->version = g_ntohs(pfx_hdr->version);
110     pfx_hdr->message_length = g_ntohs(pfx_hdr->message_length);
111     pfx_hdr->export_time_secs = g_ntohl(pfx_hdr->export_time_secs);
112     pfx_hdr->sequence_number = g_ntohl(pfx_hdr->sequence_number);
113     pfx_hdr->observation_id = g_ntohl(pfx_hdr->observation_id);
114 
115     /* is the version number one we expect? */
116     if (pfx_hdr->version != IPFIX_VERSION) {
117         /* Not an ipfix file. */
118         *err = WTAP_ERR_BAD_FILE;
119         *err_info = g_strdup_printf("ipfix: wrong version %d", pfx_hdr->version);
120         return FALSE;
121     }
122 
123     if (pfx_hdr->message_length < 16) {
124         *err = WTAP_ERR_BAD_FILE;
125         *err_info = g_strdup_printf("ipfix: message length %u is too short", pfx_hdr->message_length);
126         return FALSE;
127     }
128 
129     /* go back to before header */
130     if (file_seek(fh, 0 - IPFIX_MSG_HDR_SIZE, SEEK_CUR, err) == -1) {
131         ws_debug("couldn't go back in file before header");
132         return FALSE;
133     }
134 
135     return TRUE;
136 }
137 
138 
139 /* Read IPFIX message header from file and fill in the struct wtap_rec
140  * for the packet, and, if that succeeds, read the packet data.
141  * Return true on success.  Set *err to 0 on EOF, any other value for "real"
142  * errors (EOF is ok, since return value is still FALSE).
143  */
144 static gboolean
ipfix_read_message(FILE_T fh,wtap_rec * rec,Buffer * buf,int * err,gchar ** err_info)145 ipfix_read_message(FILE_T fh, wtap_rec *rec, Buffer *buf, int *err, gchar **err_info)
146 {
147     ipfix_message_header_t msg_hdr;
148 
149     if (!ipfix_read_message_header(&msg_hdr, fh, err, err_info))
150         return FALSE;
151     /*
152      * The maximum value of msg_hdr.message_length is 65535, which is
153      * less than WTAP_MAX_PACKET_SIZE_STANDARD will ever be, so we don't need
154      * to check it.
155      */
156 
157     rec->rec_type = REC_TYPE_PACKET;
158     rec->block = wtap_block_create(WTAP_BLOCK_PACKET);
159     rec->presence_flags = WTAP_HAS_TS;
160     rec->rec_header.packet_header.len = msg_hdr.message_length;
161     rec->rec_header.packet_header.caplen = msg_hdr.message_length;
162     rec->ts.secs = msg_hdr.export_time_secs;
163     rec->ts.nsecs = 0;
164 
165     return wtap_read_packet_bytes(fh, buf, msg_hdr.message_length, err, err_info);
166 }
167 
168 
169 
170 /* classic wtap: open capture file.  Return WTAP_OPEN_MINE on success,
171  * WTAP_OPEN_NOT_MINE on normal failure like malformed format,
172  * WTAP_OPEN_ERROR on bad error like file system
173  */
174 wtap_open_return_val
ipfix_open(wtap * wth,int * err,gchar ** err_info)175 ipfix_open(wtap *wth, int *err, gchar **err_info)
176 {
177     gint i, n, records_for_ipfix_check = RECORDS_FOR_IPFIX_CHECK;
178     gchar *s;
179     guint16 checked_len = 0;
180     ipfix_message_header_t msg_hdr;
181     ipfix_set_header_t set_hdr;
182 
183     ws_debug("opening file");
184 
185     /* number of records to scan before deciding if this really is IPFIX */
186     if ((s = getenv("IPFIX_RECORDS_TO_CHECK")) != NULL) {
187         if (ws_strtoi32(s, NULL, &n) && n > 0 && n < 101) {
188             records_for_ipfix_check = n;
189         }
190     }
191 
192     /*
193      * IPFIX is a little hard because there's no magic number; we look at
194      * the first few records and see if they look enough like IPFIX
195      * records.
196      */
197     for (i = 0; i < records_for_ipfix_check; i++) {
198         /* read first message header to check version */
199         if (!ipfix_read_message_header(&msg_hdr, wth->fh, err, err_info)) {
200             ws_debug("couldn't read message header #%d with err code #%d (%s)",
201                          i, *err, *err_info);
202             if (*err == WTAP_ERR_BAD_FILE) {
203                 *err = 0;            /* not actually an error in this case */
204                 g_free(*err_info);
205                 *err_info = NULL;
206                 return WTAP_OPEN_NOT_MINE;
207             }
208             if (*err != 0 && *err != WTAP_ERR_SHORT_READ)
209                 return WTAP_OPEN_ERROR; /* real failure */
210             /* else it's EOF */
211             if (i < 1) {
212                 /* we haven't seen enough to prove this is a ipfix file */
213                 return WTAP_OPEN_NOT_MINE;
214             }
215             /*
216              * If we got here, it's EOF and we haven't yet seen anything
217              * that doesn't look like an IPFIX record - i.e. everything
218              * we've seen looks like an IPFIX record - so we assume this
219              * is an IPFIX file.
220              */
221             break;
222         }
223         if (file_seek(wth->fh, IPFIX_MSG_HDR_SIZE, SEEK_CUR, err) == -1) {
224             ws_debug("failed seek to next message in file, %d bytes away",
225                          msg_hdr.message_length);
226             return WTAP_OPEN_NOT_MINE;
227         }
228         checked_len = IPFIX_MSG_HDR_SIZE;
229 
230         /* check each Set in IPFIX Message for sanity */
231         while (checked_len < msg_hdr.message_length) {
232             if (!wtap_read_bytes(wth->fh, &set_hdr, IPFIX_SET_HDR_SIZE,
233                                  err, err_info)) {
234                 if (*err == WTAP_ERR_SHORT_READ) {
235                     /* Not a valid IPFIX Set, so not an IPFIX file. */
236                     ws_debug("error %d reading set", *err);
237                     return WTAP_OPEN_NOT_MINE;
238                 }
239 
240                 /* A real I/O error; fail. */
241                 return WTAP_OPEN_ERROR;
242             }
243             set_hdr.set_length = g_ntohs(set_hdr.set_length);
244             if ((set_hdr.set_length < IPFIX_SET_HDR_SIZE) ||
245                 ((set_hdr.set_length + checked_len) > msg_hdr.message_length))  {
246                 ws_debug("found invalid set_length of %d",
247                              set_hdr.set_length);
248                 return WTAP_OPEN_NOT_MINE;
249             }
250 
251             if (file_seek(wth->fh, set_hdr.set_length - IPFIX_SET_HDR_SIZE,
252                  SEEK_CUR, err) == -1)
253             {
254                 ws_debug("failed seek to next set in file, %d bytes away",
255                              set_hdr.set_length - IPFIX_SET_HDR_SIZE);
256                 return WTAP_OPEN_ERROR;
257             }
258             checked_len += set_hdr.set_length;
259         }
260     }
261 
262     /* go back to beginning of file */
263     if (file_seek (wth->fh, 0, SEEK_SET, err) != 0)
264     {
265         return WTAP_OPEN_ERROR;
266     }
267 
268     /* all's good, this is a IPFIX file */
269     wth->file_encap = WTAP_ENCAP_RAW_IPFIX;
270     wth->snapshot_length = 0;
271     wth->file_tsprec = WTAP_TSPREC_SEC;
272     wth->subtype_read = ipfix_read;
273     wth->subtype_seek_read = ipfix_seek_read;
274     wth->file_type_subtype = ipfix_file_type_subtype;
275 
276     /*
277      * Add an IDB; we don't know how many interfaces were
278      * involved, so we just say one interface, about which
279      * we only know the link-layer type, snapshot length,
280      * and time stamp resolution.
281      */
282     wtap_add_generated_idb(wth);
283 
284     return WTAP_OPEN_MINE;
285 }
286 
287 
288 /* classic wtap: read packet */
289 static gboolean
ipfix_read(wtap * wth,wtap_rec * rec,Buffer * buf,int * err,gchar ** err_info,gint64 * data_offset)290 ipfix_read(wtap *wth, wtap_rec *rec, Buffer *buf, int *err,
291     gchar **err_info, gint64 *data_offset)
292 {
293     *data_offset = file_tell(wth->fh);
294     ws_debug("offset is initially %" G_GINT64_MODIFIER "d", *data_offset);
295 
296     if (!ipfix_read_message(wth->fh, rec, buf, err, err_info)) {
297         ws_debug("couldn't read message header with code: %d\n, and error '%s'",
298                      *err, *err_info);
299         return FALSE;
300     }
301 
302     return TRUE;
303 }
304 
305 
306 /* classic wtap: seek to file position and read packet */
307 static gboolean
ipfix_seek_read(wtap * wth,gint64 seek_off,wtap_rec * rec,Buffer * buf,int * err,gchar ** err_info)308 ipfix_seek_read(wtap *wth, gint64 seek_off, wtap_rec *rec,
309     Buffer *buf, int *err, gchar **err_info)
310 {
311     /* seek to the right file position */
312     if (file_seek(wth->random_fh, seek_off, SEEK_SET, err) == -1) {
313         ws_debug("couldn't read message header with code: %d\n, and error '%s'",
314                      *err, *err_info);
315         return FALSE;   /* Seek error */
316     }
317 
318     ws_debug("reading at offset %" G_GINT64_MODIFIER "u", seek_off);
319 
320     if (!ipfix_read_message(wth->random_fh, rec, buf, err, err_info)) {
321         ws_debug("couldn't read message header");
322         if (*err == 0)
323             *err = WTAP_ERR_SHORT_READ;
324         return FALSE;
325     }
326     return TRUE;
327 }
328 
329 static const struct supported_block_type ipfix_blocks_supported[] = {
330     /*
331      * We support packet blocks, with no comments or other options.
332      */
333     { WTAP_BLOCK_PACKET, MULTIPLE_BLOCKS_SUPPORTED, NO_OPTIONS_SUPPORTED }
334 };
335 
336 static const struct file_type_subtype_info ipfix_info = {
337     "IPFIX File Format", "ipfix", "pfx", "ipfix",
338     FALSE, BLOCKS_SUPPORTED(ipfix_blocks_supported),
339     NULL, NULL, NULL
340 };
341 
register_ipfix(void)342 void register_ipfix(void)
343 {
344     ipfix_file_type_subtype = wtap_register_file_type_subtype(&ipfix_info);
345 
346     /*
347      * Register name for backwards compatibility with the
348      * wtap_filetypes table in Lua.
349      */
350     wtap_register_backwards_compatibility_lua_name("IPFIX",
351                                                    ipfix_file_type_subtype);
352 }
353 
354 /*
355  * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
356  *
357  * Local variables:
358  * c-basic-offset: 4
359  * tab-width: 8
360  * indent-tabs-mode: nil
361  * End:
362  *
363  * vi: set shiftwidth=4 tabstop=8 expandtab:
364  * :indentSize=4:tabSize=8:noTabs=true:
365  */
366