wireshark/wiretap/ipfix.c

366 lines
12 KiB
C
Raw Normal View History

/* ipfix.c
*
* Wiretap Library
* Copyright (c) 1998 by Gilbert Ramirez <gram@alumni.rice.edu>
*
* File format support for ipfix file format
* Copyright (c) 2010 by Hadriel Kaplan <hadrielk@yahoo.com>
* with generous copying from other wiretaps, such as pcapng
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
/* File format reference:
* RFC 5655 and 5101
* https://tools.ietf.org/rfc/rfc5655
* https://tools.ietf.org/rfc/rfc5101
*
* This wiretap is for an ipfix file format reader, per RFC 5655/5101.
* All "records" in the file are IPFIX messages, beginning with an IPFIX
* message header of 16 bytes as follows from RFC 5101:
0 1 2 3
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Version Number | Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Export Time |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequence Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Observation Domain ID |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Figure F: IPFIX Message Header Format
* which is then followed by one or more "Sets": Data Sets, Template Sets,
* and Options Template Sets. Each Set then has one or more Records in
* it.
*
* All IPFIX files are recorded in big-endian form (network byte order),
* per the RFCs. That means if we're on a little-endian system, all
* hell will break loose if we don't g_ntohX.
*
* Since wireshark already has an IPFIX dissector (implemented in
* packet-netflow.c), this reader will just set that dissector upon
* reading each message. Thus, an IPFIX Message is treated as a packet
* as far as the dissector is concerned.
*/
#include "config.h"
Refactor our logging and extend the wslog API Experience has shown that: 1. The current logging methods are not very reliable or practical. A logging bitmask makes little sense as the user-facing interface (who would want debug but not crtical messages for example?); it's computer-friendly and user-unfriendly. More importantly the console log level preference is initialized too late in the startup process to be used for the logging subsystem and that fact raises a number of annoying and hard-to-fix usability issues. 2. Coding around G_MESSAGES_DEBUG to comply with our log level mask and not clobber the user's settings or not create unexpected log misses is unworkable and generally follows the principle of most surprise. The fact that G_MESSAGES_DEBUG="all" can leak to other programs using GLib is also annoying. 3. The non-structured GLib logging API is very opinionated and lacks configurability beyond replacing the log handler. 4. Windows GUI has some special code to attach to a console, but it would be nice to abstract away the rest under a single interface. 5. Using this logger seems to be noticeably faster. Deprecate the console log level preference and extend our API to implement a log handler in wsutil/wslog.h to provide easy-to-use, flexible and dependable logging during all execution phases. Log levels have a hierarchy, from most verbose to least verbose (debug to error). When a given level is set everything above that is also enabled. The log level can be set with an environment variable or a command line option (parsed as soon as possible but still later than the environment). The default log level is "message". Dissector logging is not included because it is not clear what log domain they should use. An explosion to thousands of domains is not desirable and putting everything in a single domain is probably too coarse and noisy. For now I think it makes sense to let them do their own thing using g_log_default_handler() and continue using the G_MESSAGES_DEBUG mechanism with specific domains for each individual dissector. In the future a mechanism may be added to selectively enable these domains at runtime while trying to avoid the problems introduced by G_MESSAGES_DEBUG.
2021-06-08 01:46:52 +00:00
#define WS_LOG_DOMAIN LOG_DOMAIN_WIRETAP
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "wtap-int.h"
#include "file_wrappers.h"
#include "ipfix.h"
#include <wsutil/strtoi.h>
#include <wsutil/wslog.h>
#define RECORDS_FOR_IPFIX_CHECK 20
static gboolean
ipfix_read(wtap *wth, wtap_rec *rec, Buffer *buf, int *err,
gchar **err_info, gint64 *data_offset);
static gboolean
ipfix_seek_read(wtap *wth, gint64 seek_off,
wtap_rec *rec, Buffer *buf, int *err, gchar **err_info);
#define IPFIX_VERSION 10
/* ipfix: message header */
typedef struct ipfix_message_header_s {
guint16 version;
guint16 message_length;
guint32 export_time_secs;
guint32 sequence_number;
guint32 observation_id; /* might be 0 for none */
/* x bytes msg_body */
} ipfix_message_header_t;
#define IPFIX_MSG_HDR_SIZE 16
/* ipfix: common Set header for every Set type */
typedef struct ipfix_set_header_s {
guint16 set_type;
guint16 set_length;
/* x bytes set_body */
} ipfix_set_header_t;
#define IPFIX_SET_HDR_SIZE 4
static int ipfix_file_type_subtype = -1;
void register_ipfix(void);
/* Read IPFIX message header from file. Return true on success. Set *err to
* 0 on EOF, any other value for "real" errors (EOF is ok, since return
* value is still FALSE)
*/
static gboolean
ipfix_read_message_header(ipfix_message_header_t *pfx_hdr, FILE_T fh, int *err, gchar **err_info)
{
if (!wtap_read_bytes_or_eof(fh, pfx_hdr, IPFIX_MSG_HDR_SIZE, err, err_info))
return FALSE;
2020-10-10 23:42:05 +00:00
/* fix endianness, because IPFIX files are always big-endian */
pfx_hdr->version = g_ntohs(pfx_hdr->version);
pfx_hdr->message_length = g_ntohs(pfx_hdr->message_length);
pfx_hdr->export_time_secs = g_ntohl(pfx_hdr->export_time_secs);
pfx_hdr->sequence_number = g_ntohl(pfx_hdr->sequence_number);
pfx_hdr->observation_id = g_ntohl(pfx_hdr->observation_id);
/* is the version number one we expect? */
if (pfx_hdr->version != IPFIX_VERSION) {
/* Not an ipfix file. */
*err = WTAP_ERR_BAD_FILE;
*err_info = g_strdup_printf("ipfix: wrong version %d", pfx_hdr->version);
return FALSE;
}
if (pfx_hdr->message_length < 16) {
*err = WTAP_ERR_BAD_FILE;
*err_info = g_strdup_printf("ipfix: message length %u is too short", pfx_hdr->message_length);
return FALSE;
}
/* go back to before header */
if (file_seek(fh, 0 - IPFIX_MSG_HDR_SIZE, SEEK_CUR, err) == -1) {
ws_debug("couldn't go back in file before header");
return FALSE;
}
return TRUE;
}
/* Read IPFIX message header from file and fill in the struct wtap_rec
* for the packet, and, if that succeeds, read the packet data.
* Return true on success. Set *err to 0 on EOF, any other value for "real"
* errors (EOF is ok, since return value is still FALSE).
*/
static gboolean
ipfix_read_message(FILE_T fh, wtap_rec *rec, Buffer *buf, int *err, gchar **err_info)
{
ipfix_message_header_t msg_hdr;
if (!ipfix_read_message_header(&msg_hdr, fh, err, err_info))
return FALSE;
/*
* The maximum value of msg_hdr.message_length is 65535, which is
* less than WTAP_MAX_PACKET_SIZE_STANDARD will ever be, so we don't need
* to check it.
*/
rec->rec_type = REC_TYPE_PACKET;
rec->block = wtap_block_create(WTAP_BLOCK_PACKET);
rec->presence_flags = WTAP_HAS_TS;
rec->rec_header.packet_header.len = msg_hdr.message_length;
rec->rec_header.packet_header.caplen = msg_hdr.message_length;
rec->ts.secs = msg_hdr.export_time_secs;
rec->ts.nsecs = 0;
return wtap_read_packet_bytes(fh, buf, msg_hdr.message_length, err, err_info);
}
/* classic wtap: open capture file. Return WTAP_OPEN_MINE on success,
* WTAP_OPEN_NOT_MINE on normal failure like malformed format,
* WTAP_OPEN_ERROR on bad error like file system
*/
wtap_open_return_val
ipfix_open(wtap *wth, int *err, gchar **err_info)
{
gint i, n, records_for_ipfix_check = RECORDS_FOR_IPFIX_CHECK;
gchar *s;
guint16 checked_len = 0;
ipfix_message_header_t msg_hdr;
ipfix_set_header_t set_hdr;
ws_debug("opening file");
/* number of records to scan before deciding if this really is IPFIX */
if ((s = getenv("IPFIX_RECORDS_TO_CHECK")) != NULL) {
if (ws_strtoi32(s, NULL, &n) && n > 0 && n < 101) {
records_for_ipfix_check = n;
}
}
/*
* IPFIX is a little hard because there's no magic number; we look at
* the first few records and see if they look enough like IPFIX
* records.
*/
for (i = 0; i < records_for_ipfix_check; i++) {
/* read first message header to check version */
if (!ipfix_read_message_header(&msg_hdr, wth->fh, err, err_info)) {
ws_debug("couldn't read message header #%d with err code #%d (%s)",
i, *err, *err_info);
if (*err == WTAP_ERR_BAD_FILE) {
*err = 0; /* not actually an error in this case */
g_free(*err_info);
*err_info = NULL;
return WTAP_OPEN_NOT_MINE;
}
if (*err != 0 && *err != WTAP_ERR_SHORT_READ)
return WTAP_OPEN_ERROR; /* real failure */
/* else it's EOF */
if (i < 1) {
/* we haven't seen enough to prove this is a ipfix file */
return WTAP_OPEN_NOT_MINE;
}
/*
* If we got here, it's EOF and we haven't yet seen anything
* that doesn't look like an IPFIX record - i.e. everything
* we've seen looks like an IPFIX record - so we assume this
* is an IPFIX file.
*/
break;
}
if (file_seek(wth->fh, IPFIX_MSG_HDR_SIZE, SEEK_CUR, err) == -1) {
ws_debug("failed seek to next message in file, %d bytes away",
msg_hdr.message_length);
return WTAP_OPEN_NOT_MINE;
}
checked_len = IPFIX_MSG_HDR_SIZE;
/* check each Set in IPFIX Message for sanity */
while (checked_len < msg_hdr.message_length) {
if (!wtap_read_bytes(wth->fh, &set_hdr, IPFIX_SET_HDR_SIZE,
err, err_info)) {
if (*err == WTAP_ERR_SHORT_READ) {
/* Not a valid IPFIX Set, so not an IPFIX file. */
ws_debug("error %d reading set", *err);
return WTAP_OPEN_NOT_MINE;
}
/* A real I/O error; fail. */
return WTAP_OPEN_ERROR;
}
set_hdr.set_length = g_ntohs(set_hdr.set_length);
if ((set_hdr.set_length < IPFIX_SET_HDR_SIZE) ||
((set_hdr.set_length + checked_len) > msg_hdr.message_length)) {
ws_debug("found invalid set_length of %d",
set_hdr.set_length);
return WTAP_OPEN_NOT_MINE;
}
if (file_seek(wth->fh, set_hdr.set_length - IPFIX_SET_HDR_SIZE,
SEEK_CUR, err) == -1)
{
ws_debug("failed seek to next set in file, %d bytes away",
set_hdr.set_length - IPFIX_SET_HDR_SIZE);
return WTAP_OPEN_ERROR;
}
checked_len += set_hdr.set_length;
}
}
/* go back to beginning of file */
if (file_seek (wth->fh, 0, SEEK_SET, err) != 0)
{
return WTAP_OPEN_ERROR;
}
/* all's good, this is a IPFIX file */
wth->file_encap = WTAP_ENCAP_RAW_IPFIX;
wth->snapshot_length = 0;
wth->file_tsprec = WTAP_TSPREC_SEC;
wth->subtype_read = ipfix_read;
wth->subtype_seek_read = ipfix_seek_read;
wth->file_type_subtype = ipfix_file_type_subtype;
/*
* Add an IDB; we don't know how many interfaces were
* involved, so we just say one interface, about which
* we only know the link-layer type, snapshot length,
* and time stamp resolution.
*/
wtap_add_generated_idb(wth);
return WTAP_OPEN_MINE;
}
/* classic wtap: read packet */
static gboolean
ipfix_read(wtap *wth, wtap_rec *rec, Buffer *buf, int *err,
gchar **err_info, gint64 *data_offset)
{
*data_offset = file_tell(wth->fh);
ws_debug("offset is initially %" PRId64, *data_offset);
if (!ipfix_read_message(wth->fh, rec, buf, err, err_info)) {
ws_debug("couldn't read message header with code: %d\n, and error '%s'",
*err, *err_info);
return FALSE;
}
return TRUE;
}
/* classic wtap: seek to file position and read packet */
static gboolean
ipfix_seek_read(wtap *wth, gint64 seek_off, wtap_rec *rec,
Buffer *buf, int *err, gchar **err_info)
{
/* seek to the right file position */
if (file_seek(wth->random_fh, seek_off, SEEK_SET, err) == -1) {
ws_debug("couldn't read message header with code: %d\n, and error '%s'",
*err, *err_info);
return FALSE; /* Seek error */
}
ws_debug("reading at offset %" PRIu64, seek_off);
if (!ipfix_read_message(wth->random_fh, rec, buf, err, err_info)) {
ws_debug("couldn't read message header");
if (*err == 0)
*err = WTAP_ERR_SHORT_READ;
return FALSE;
}
return TRUE;
}
static const struct supported_block_type ipfix_blocks_supported[] = {
/*
* We support packet blocks, with no comments or other options.
*/
{ WTAP_BLOCK_PACKET, MULTIPLE_BLOCKS_SUPPORTED, NO_OPTIONS_SUPPORTED }
};
static const struct file_type_subtype_info ipfix_info = {
"IPFIX File Format", "ipfix", "pfx", "ipfix",
FALSE, BLOCKS_SUPPORTED(ipfix_blocks_supported),
NULL, NULL, NULL
};
void register_ipfix(void)
{
ipfix_file_type_subtype = wtap_register_file_type_subtype(&ipfix_info);
wiretap: more work on file type/subtypes. Provide a wiretap routine to get an array of all savable file type/subtypes, sorted with pcap and pcapng at the top, followed by the other types, sorted either by the name or the description. Use that routine to list options for the -F flag for various commands Rename wtap_get_savable_file_types_subtypes() to wtap_get_savable_file_types_subtypes_for_file(), to indicate that it provides an array of all file type/subtypes in which a given file can be saved. Have it sort all types, other than the default type/subtype and, if there is one, the "other" type (both of which are put at the top), by the name or the description. Don't allow wtap_register_file_type_subtypes() to override any existing registrations; have them always register a new type. In that routine, if there are any emply slots in the table, due to an entry being unregistered, use it rather than allocating a new slot. Don't allow unregistration of built-in types. Rename the "dump open table" to the "file type/subtype table", as it has entries for all types/subtypes, even if we can't write them. Initialize that table in a routine that pre-allocates the GArray before filling it with built-in types/subtypes, so it doesn't keep getting reallocated. Get rid of wtap_num_file_types_subtypes - it's just a copy of the size of the GArray. Don't have wtap_file_type_subtype_description() crash if handed an file type/subtype that isn't a valid array index - just return NULL, as we do with wtap_file_type_subtype_name(). In wtap_name_to_file_type_subtype(), don't use WTAP_FILE_TYPE_SUBTYPE_ names for the backwards-compatibility names - map those names to the current names, and then look them up. This reduces the number of uses of hardwired WTAP_FILE_TYPE_SUBTYPE_ values. Clean up the type of wtap_module_count - it has no need to be a gulong. Have built-in wiretap file handlers register names to be used for their file type/subtypes, rather than building the table in init.lua. Add a new Lua C function get_wtap_filetypes() to construct the wtap_filetypes table, based on the registered names, and use it in init.lua. Add a #define WSLUA_INTERNAL_FUNCTION to register functions intended only for internal use in init.lua, so they can be made available from Lua without being documented. Get rid of WTAP_NUM_FILE_TYPES_SUBTYPES - most code has no need to use it, as it can just request arrays of types, and the space of type/subtype codes can be sparse due to registration in any case, so code has to be careful using it. wtap_get_num_file_types_subtypes() is no longer used, so remove it. It returns the number of elements in the file type/subtype array, which is not necessarily the name of known file type/subtypes, as there may have been some deregistered types, and those types do *not* get removed from the array, they just get cleared so that they're available for future allocation (we don't want the indices of any registered types to changes if another type is deregistered, as those indicates are the type/subtype values, so we can't shrink the array). Clean up white space and remove some comments that shouldn't have been added.
2021-02-17 06:24:47 +00:00
/*
* Register name for backwards compatibility with the
* wtap_filetypes table in Lua.
*/
wtap_register_backwards_compatibility_lua_name("IPFIX",
ipfix_file_type_subtype);
}
/*
* Editor modelines - https://www.wireshark.org/tools/modelines.html
*
* Local variables:
* c-basic-offset: 4
* tab-width: 8
* indent-tabs-mode: nil
* End:
*
* vi: set shiftwidth=4 tabstop=8 expandtab:
* :indentSize=4:tabSize=8:noTabs=true:
*/