4 * Copyright (c) 1998 by Gilbert Ramirez <gram@alumni.rice.edu>
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
10 * SPDX-License-Identifier: GPL-2.0-or-later
13 /* File format reference:
15 * https://tools.ietf.org/rfc/rfc5655
16 * https://tools.ietf.org/rfc/rfc5101
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:
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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
27 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
29 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
30 | Observation Domain ID |
31 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
33 Figure F: IPFIX Message Header Format
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
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.
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.
51 #define WS_LOG_DOMAIN LOG_DOMAIN_WIRETAP
57 #include "file_wrappers.h"
59 #include <wsutil/strtoi.h>
60 #include <wsutil/wslog.h>
62 #define RECORDS_FOR_IPFIX_CHECK 20
65 ipfix_read(wtap
*wth
, wtap_rec
*rec
, Buffer
*buf
, int *err
,
66 char **err_info
, int64_t *data_offset
);
68 ipfix_seek_read(wtap
*wth
, int64_t seek_off
,
69 wtap_rec
*rec
, Buffer
*buf
, int *err
, char **err_info
);
71 #define IPFIX_VERSION 10
73 /* ipfix: message header */
74 typedef struct ipfix_message_header_s
{
76 uint16_t message_length
;
77 uint32_t export_time_secs
;
78 uint32_t sequence_number
;
79 uint32_t observation_id
; /* might be 0 for none */
80 /* x bytes msg_body */
81 } ipfix_message_header_t
;
82 #define IPFIX_MSG_HDR_SIZE 16
84 /* ipfix: common Set header for every Set type */
85 typedef struct ipfix_set_header_s
{
88 /* x bytes set_body */
90 #define IPFIX_SET_HDR_SIZE 4
93 static int ipfix_file_type_subtype
= -1;
95 void register_ipfix(void);
97 /* Read IPFIX message header from file. Return true on success. Set *err to
98 * 0 on EOF, any other value for "real" errors (EOF is ok, since return
99 * value is still false)
102 ipfix_read_message_header(ipfix_message_header_t
*pfx_hdr
, FILE_T fh
, int *err
, char **err_info
)
104 if (!wtap_read_bytes_or_eof(fh
, pfx_hdr
, IPFIX_MSG_HDR_SIZE
, err
, err_info
))
107 /* fix endianness, because IPFIX files are always big-endian */
108 pfx_hdr
->version
= g_ntohs(pfx_hdr
->version
);
109 pfx_hdr
->message_length
= g_ntohs(pfx_hdr
->message_length
);
110 pfx_hdr
->export_time_secs
= g_ntohl(pfx_hdr
->export_time_secs
);
111 pfx_hdr
->sequence_number
= g_ntohl(pfx_hdr
->sequence_number
);
112 pfx_hdr
->observation_id
= g_ntohl(pfx_hdr
->observation_id
);
114 /* is the version number one we expect? */
115 if (pfx_hdr
->version
!= IPFIX_VERSION
) {
116 /* Not an ipfix file. */
117 *err
= WTAP_ERR_BAD_FILE
;
118 *err_info
= ws_strdup_printf("ipfix: wrong version %d", pfx_hdr
->version
);
122 if (pfx_hdr
->message_length
< 16) {
123 *err
= WTAP_ERR_BAD_FILE
;
124 *err_info
= ws_strdup_printf("ipfix: message length %u is too short", pfx_hdr
->message_length
);
128 /* go back to before header */
129 if (file_seek(fh
, 0 - IPFIX_MSG_HDR_SIZE
, SEEK_CUR
, err
) == -1) {
130 ws_debug("couldn't go back in file before header");
138 /* Read IPFIX message header from file and fill in the struct wtap_rec
139 * for the packet, and, if that succeeds, read the packet data.
140 * Return true on success. Set *err to 0 on EOF, any other value for "real"
141 * errors (EOF is ok, since return value is still false).
144 ipfix_read_message(FILE_T fh
, wtap_rec
*rec
, Buffer
*buf
, int *err
, char **err_info
)
146 ipfix_message_header_t msg_hdr
;
148 if (!ipfix_read_message_header(&msg_hdr
, fh
, err
, err_info
))
151 * The maximum value of msg_hdr.message_length is 65535, which is
152 * less than WTAP_MAX_PACKET_SIZE_STANDARD will ever be, so we don't need
156 rec
->rec_type
= REC_TYPE_PACKET
;
157 rec
->block
= wtap_block_create(WTAP_BLOCK_PACKET
);
158 rec
->presence_flags
= WTAP_HAS_TS
;
159 rec
->rec_header
.packet_header
.len
= msg_hdr
.message_length
;
160 rec
->rec_header
.packet_header
.caplen
= msg_hdr
.message_length
;
161 rec
->ts
.secs
= msg_hdr
.export_time_secs
;
164 return wtap_read_packet_bytes(fh
, buf
, msg_hdr
.message_length
, err
, err_info
);
169 /* classic wtap: open capture file. Return WTAP_OPEN_MINE on success,
170 * WTAP_OPEN_NOT_MINE on normal failure like malformed format,
171 * WTAP_OPEN_ERROR on bad error like file system
174 ipfix_open(wtap
*wth
, int *err
, char **err_info
)
176 int i
, n
, records_for_ipfix_check
= RECORDS_FOR_IPFIX_CHECK
;
178 uint16_t checked_len
;
179 ipfix_message_header_t msg_hdr
;
180 ipfix_set_header_t set_hdr
;
182 ws_debug("opening file");
184 /* number of records to scan before deciding if this really is IPFIX */
185 if ((s
= getenv("IPFIX_RECORDS_TO_CHECK")) != NULL
) {
186 if (ws_strtoi32(s
, NULL
, &n
) && n
> 0 && n
< 101) {
187 records_for_ipfix_check
= n
;
192 * IPFIX is a little hard because there's no magic number; we look at
193 * the first few records and see if they look enough like IPFIX
196 for (i
= 0; i
< records_for_ipfix_check
; i
++) {
197 /* read first message header to check version */
198 if (!ipfix_read_message_header(&msg_hdr
, wth
->fh
, err
, err_info
)) {
199 ws_debug("couldn't read message header #%d with err code #%d (%s)",
201 if (*err
== WTAP_ERR_BAD_FILE
) {
202 *err
= 0; /* not actually an error in this case */
205 return WTAP_OPEN_NOT_MINE
;
207 if (*err
!= 0 && *err
!= WTAP_ERR_SHORT_READ
)
208 return WTAP_OPEN_ERROR
; /* real failure */
211 /* we haven't seen enough to prove this is a ipfix file */
212 return WTAP_OPEN_NOT_MINE
;
215 * If we got here, it's EOF and we haven't yet seen anything
216 * that doesn't look like an IPFIX record - i.e. everything
217 * we've seen looks like an IPFIX record - so we assume this
222 if (file_seek(wth
->fh
, IPFIX_MSG_HDR_SIZE
, SEEK_CUR
, err
) == -1) {
223 ws_debug("failed seek to next message in file, %d bytes away",
224 msg_hdr
.message_length
);
225 return WTAP_OPEN_NOT_MINE
;
227 checked_len
= IPFIX_MSG_HDR_SIZE
;
229 /* check each Set in IPFIX Message for sanity */
230 while (checked_len
< msg_hdr
.message_length
) {
231 if (!wtap_read_bytes(wth
->fh
, &set_hdr
, IPFIX_SET_HDR_SIZE
,
233 if (*err
== WTAP_ERR_SHORT_READ
) {
234 /* Not a valid IPFIX Set, so not an IPFIX file. */
235 ws_debug("error %d reading set", *err
);
236 return WTAP_OPEN_NOT_MINE
;
239 /* A real I/O error; fail. */
240 return WTAP_OPEN_ERROR
;
242 set_hdr
.set_length
= g_ntohs(set_hdr
.set_length
);
243 if ((set_hdr
.set_length
< IPFIX_SET_HDR_SIZE
) ||
244 ((set_hdr
.set_length
+ checked_len
) > msg_hdr
.message_length
)) {
245 ws_debug("found invalid set_length of %d",
247 return WTAP_OPEN_NOT_MINE
;
250 if (file_seek(wth
->fh
, set_hdr
.set_length
- IPFIX_SET_HDR_SIZE
,
251 SEEK_CUR
, err
) == -1)
253 ws_debug("failed seek to next set in file, %d bytes away",
254 set_hdr
.set_length
- IPFIX_SET_HDR_SIZE
);
255 return WTAP_OPEN_ERROR
;
257 checked_len
+= set_hdr
.set_length
;
261 /* go back to beginning of file */
262 if (file_seek (wth
->fh
, 0, SEEK_SET
, err
) != 0)
264 return WTAP_OPEN_ERROR
;
267 /* all's good, this is a IPFIX file */
268 wth
->file_encap
= WTAP_ENCAP_RAW_IPFIX
;
269 wth
->snapshot_length
= 0;
270 wth
->file_tsprec
= WTAP_TSPREC_SEC
;
271 wth
->subtype_read
= ipfix_read
;
272 wth
->subtype_seek_read
= ipfix_seek_read
;
273 wth
->file_type_subtype
= ipfix_file_type_subtype
;
276 * Add an IDB; we don't know how many interfaces were
277 * involved, so we just say one interface, about which
278 * we only know the link-layer type, snapshot length,
279 * and time stamp resolution.
281 wtap_add_generated_idb(wth
);
283 return WTAP_OPEN_MINE
;
287 /* classic wtap: read packet */
289 ipfix_read(wtap
*wth
, wtap_rec
*rec
, Buffer
*buf
, int *err
,
290 char **err_info
, int64_t *data_offset
)
292 *data_offset
= file_tell(wth
->fh
);
293 ws_debug("offset is initially %" PRId64
, *data_offset
);
295 if (!ipfix_read_message(wth
->fh
, rec
, buf
, err
, err_info
)) {
296 ws_debug("couldn't read message header with code: %d\n, and error '%s'",
305 /* classic wtap: seek to file position and read packet */
307 ipfix_seek_read(wtap
*wth
, int64_t seek_off
, wtap_rec
*rec
,
308 Buffer
*buf
, int *err
, char **err_info
)
310 /* seek to the right file position */
311 if (file_seek(wth
->random_fh
, seek_off
, SEEK_SET
, err
) == -1) {
312 ws_debug("couldn't read message header with code: %d\n, and error '%s'",
314 return false; /* Seek error */
317 ws_debug("reading at offset %" PRIu64
, seek_off
);
319 if (!ipfix_read_message(wth
->random_fh
, rec
, buf
, err
, err_info
)) {
320 ws_debug("couldn't read message header");
322 *err
= WTAP_ERR_SHORT_READ
;
328 static const struct supported_block_type ipfix_blocks_supported
[] = {
330 * We support packet blocks, with no comments or other options.
332 { WTAP_BLOCK_PACKET
, MULTIPLE_BLOCKS_SUPPORTED
, NO_OPTIONS_SUPPORTED
}
335 static const struct file_type_subtype_info ipfix_info
= {
336 "IPFIX File Format", "ipfix", "pfx", "ipfix",
337 false, BLOCKS_SUPPORTED(ipfix_blocks_supported
),
341 void register_ipfix(void)
343 ipfix_file_type_subtype
= wtap_register_file_type_subtype(&ipfix_info
);
346 * Register name for backwards compatibility with the
347 * wtap_filetypes table in Lua.
349 wtap_register_backwards_compatibility_lua_name("IPFIX",
350 ipfix_file_type_subtype
);
354 * Editor modelines - https://www.wireshark.org/tools/modelines.html
359 * indent-tabs-mode: nil
362 * vi: set shiftwidth=4 tabstop=8 expandtab:
363 * :indentSize=4:tabSize=8:noTabs=true: