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