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