Farsight's Network Message, Volume 4: The C Programming API
Abstract
This article is the fourth in a multi-part blog series intended to introduce
and acquaint the user with Farsight Security’s NMSG suite. This article
introduces the libnmsg
C programming API.
Before reading this article, it is recommended that you read the following Farsight Security Blog articles:
- Farsight’s Network Message, Volume 1: Introduction to NMSG
- Farsight’s Network Message, Volume 2: Introduction to nmsgtool
- Farsight’s Network Message, Volume 3: Headers and Encoding
This article, geared towards intermediate-level C programmers, is by no means
an exhaustive API reference. For that, the reader is directed to the
accompanying Doxygen-based API manual.
This article covers NMSG (protocol) version 2
and nmsg
(C library)
version 0.9.1
.
The reader is also directed to explore the examples directory in the nmsg
repository, where
several other sample libnmsg
-based programs will be found.
Hello World, NMSG-style
To demonstrate the some of libnmsg
‘s functionality, we will examine a simple
example of how to instantiate an nmsg
session and plumb data through it. The
C source code for this program, nmsgpacket
, is detailed below. All of the
code is contained in a single source file that can be compiled into a fully
functional program. To build nmsgpacket
, you’ll need to link against
libnmsg
and libpcap
. As such, you’ll want to ensure recent versions of both
libraries are installed in a standard system location (such as
/usr/local/lib
).
nmsgpacket
nmsgpacket
is a simple program that reads network packets, converts them into
NMSG, and mirrors the payloads to a binary file and to stdout. It requires two
command-line arguments: the network interface from which to capture packets and
a count of the number of packets to capture and convert to NMSG payloads (each
captured packet will be converted into a single NMSG payload).
To wit:
$ ./nmsgpacket en0 5 [159] [2015-02-16 20:42:47.295350000] [1:12 base packet] [00000000] [] [] payload_type: IP payload: <BYTE ARRAY LEN=154> [159] [2015-02-16 20:42:47.296414000] [1:12 base packet] [00000000] [] [] payload_type: IP payload: <BYTE ARRAY LEN=154> [82] [2015-02-16 20:42:48.831159000] [1:12 base packet] [00000000] [] [] payload_type: IP payload: <BYTE ARRAY LEN=78> [333] [2015-02-16 20:42:48.832891000] [1:12 base packet] [00000000] [] [] payload_type: IP payload: <BYTE ARRAY LEN=328> [82] [2015-02-16 20:42:49.138423000] [1:12 base packet] [00000000] [] [] payload_type: IP payload: <BYTE ARRAY LEN=78>
We use nmsgtool
to verify the binary file’s payload contents are the same
as those display on the console:
$ nmsgtool -r nmsgpacket.nmsg [159] [2015-02-16 20:42:47.295350000] [1:12 base packet] [00000000] [] [] payload_type: IP payload: <BYTE ARRAY LEN=154> [159] [2015-02-16 20:42:47.296414000] [1:12 base packet] [00000000] [] [] payload_type: IP payload: <BYTE ARRAY LEN=154> [82] [2015-02-16 20:42:48.831159000] [1:12 base packet] [00000000] [] [] payload_type: IP payload: <BYTE ARRAY LEN=78> [333] [2015-02-16 20:42:48.832891000] [1:12 base packet] [00000000] [] [] payload_type: IP payload: <BYTE ARRAY LEN=328> [82] [2015-02-16 20:42:49.138423000] [1:12 base packet] [00000000] [] [] payload_type: IP payload: <BYTE ARRAY LEN=78>
nmsgpacket.c
The following is the entire 193 line C source code file with in-line
annotations explaining libnmsg
API calls.
The full source code is available for download from Farsight Security’s blog-code GitHub page.
Preamble
The first section contains the source code license and the standard C header file include progression:
/*
* Copyright (c) 2015 by Farsight Security, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <pcap.h>
#include <nmsg.h>
#include <nmsg/base/defs.h>
Declare NMSG objects
Outlier nmsg
-specific variables include the following:
nmsg_io_t io
: The opaquenmsg
io engine object. It handles multiplexing of NMSG data between inputs and outputs.nmsg_res res
: Thenmsg
result code object. It is used by thelibnmsg
API to return meaningful result codes that can be looked up withnmsg_res_lookup()
.nmsg_input_t input
: The opaquenmsg
input object. It is used bynmsg
to stream payloads from different input sources.nmsg_output_t output
: The opaquenmsg
output object. It is used bynmsg
to buffer and write payloads to output destinations.nmsg_msgmod_t mod
: The opaque message module object. It holds message module specific state.
int
main(int argc, char **argv)
{
int fd, rc, count;
nmsg_io_t io;
nmsg_res res;
pcap_t *phandle;
nmsg_pcap_t pcap;
nmsg_msgmod_t mod;
nmsg_input_t input;
nmsg_output_t output;
char errbuf[PCAP_ERRBUF_SIZE];
Verify command-line arguments
nmsgpacket
requires two command-line arguments, the network interface and
packet count. Anything more or less and it will throw an error:
if (argc != 3 || ((count = atoi(argv[2])) < 1))
{
printf("%s capture packets and encode as NMSG base:packet\n", argv[0]);
printf("usage: %s interface count\n", argv[0]);
printf(" interface:\tlook for packets here\n");
printf(" count:\tprocess this many payloads (positive integer)\n");
return EXIT_FAILURE;
}
Initialize libnmsg
Before any libnmsg
API functions can be called, the library has to be
initialized:
/* initialize nmsg */
res = nmsg_init();
if (res != nmsg_res_success)
{
fprintf(stderr, "nmsg_init(): failed: %s\n", nmsg_res_lookup(res));
return EXIT_FAILURE;
}
Initialize the IO engine
The program employs a key feature of libnmsg
, the multi-threaded IO engine.
It handles the multiplexing of data between inputs and outputs transparently
for the application programmer and makes libnmsg
programming much easier. It
is initialized here:
/* initialize the nmsg io engine */
io = nmsg_io_init();
if (io == NULL)
{
fprintf(stderr, "nmsg_io_init(): failed\n");
return EXIT_FAILURE;
}
Lookup message module
nmsgpacket
will be sourcing input as packets from a network interface. As
such, it will use the NMSG base
:packet
message module. It is looked up
here:
/* get the message module for base:packet */
mod = nmsg_msgmod_lookup(NMSG_VENDOR_BASE_ID, NMSG_VENDOR_BASE_PACKET_ID);
if (mod == NULL)
{
fprintf(stderr, "nmsg_msgmod_lookup(): unknown msgmod\n");
return EXIT_FAILURE;
}
Initialize libpcap
The next block of code is straightforward and mostly boilerplate libpcap
initialization, using the newer style interface API. Noteworthy here is the
snapshot length of NMSG_DEFAULT_SNAPLEN
(recommended by the libnmsg
API documentation) and the timeout of 1000 ms (according to libpcap
API
documentation, not setting a timeout can result in undefined behavior.):
/* initialize a pcap handle */
phandle = pcap_create(argv[1], errbuf);
if (phandle == NULL)
{
fprintf(stderr, "pcap_create (%s): %s\n", argv[1], errbuf);
return EXIT_FAILURE;
}
/* set the pcap snapshot length to the nmsg recommended value */
rc = pcap_set_snaplen(phandle, NMSG_DEFAULT_SNAPLEN);
if (rc != 0)
{
fprintf(stderr, "pcap_set_snaplen() failed: %d\n", rc);
return EXIT_FAILURE;
}
/* set the pcap read timeout to 1000ms */
rc = pcap_set_timeout(phandle, 1000);
if (rc != 0)
{
fprintf(stderr, "pcap_set_timeout() failed: %d\n", rc);
return EXIT_FAILURE;
}
/* start packet capture */
rc = pcap_activate(phandle);
if (rc != 0)
{
fprintf(stderr, "pcap_activate() failed with error code: %d\n", rc);
return EXIT_FAILURE;
}
NMSG pcap input
libnmsg
provides a reassembled IP datagram interface to libpcap
. This
interface is quite handy for libnmsg
programmers that write code to collect
network packets but don’t want to have to deal with the minutia of handling IP
fragments. nmsgpacket
uses this interface and opens a new libnmsg
pcap
input from the previously opened libpcap
handle. It then instantiates an
input object:
/* initialize a new nmsg pcap input */
pcap = nmsg_pcap_input_open(phandle);
if (pcap == NULL)
{
fprintf(stderr, "nmsg_pcap_input_open() failed\n");
return EXIT_FAILURE;
}
/* initialize a new NMSG pcap input from a pcap descriptor */
input = nmsg_input_open_pcap(pcap, mod);
if (input == NULL)
{
fprintf(stderr, "nmsg_input_open_pcap(): failed\n");
return EXIT_FAILURE;
}
Add pcap input
The input object is then attached to the IO engine:
/* add an nmsg input object to the io engine */
res = nmsg_io_add_input(io, input, NULL);
if (res != nmsg_res_success)
{
fprintf(stderr, "nmsg_io_add_intput() failed: %s\n",
nmsg_res_lookup(res));
return EXIT_FAILURE;
}
NMSG binary output
Next, the program sets up the binary output object. A file is opened and the file descriptor is used to instantiate a binary file output object which is then attached to the IO engine:
/* open a new file to write binary NMSGs */
fd = open("nmsgpacket.nmsg", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd == -1)
{
fprintf(stderr, "open() failed: %s\n", strerror(errno));
return EXIT_FAILURE;
}
/* initialize a new binary file nmsg output */
output = nmsg_output_open_file(fd, NMSG_WBUFSZ_MAX);
if (output == NULL)
{
fprintf(stderr, "nmsg_output_open_file(): failed\n");
return EXIT_FAILURE;
}
/* add an nmsg output object to the io engine */
res = nmsg_io_add_output(io, output, NULL);
if (res != nmsg_res_success)
{
fprintf(stderr, "nmsg_io_add_output() failed: %s\n",
nmsg_res_lookup(res));
return EXIT_FAILURE;
}
NMSG presentation output
Next, the same process is used for a presentation output object (without
opening a file — stdout
is already open). The presentation output object
is attached to the IO engine:
/* initialize a new stdout presentation format nmsg output */
output = nmsg_output_open_pres(STDOUT_FILENO);
if (output == NULL)
{
fprintf(stderr, "nmsg_output_open_pres(): failed\n");
return EXIT_FAILURE;
}
/* add an nmsg output object to the io engine */
res = nmsg_io_add_output(io, output, NULL);
if (res != nmsg_res_success)
{
fprintf(stderr, "nmsg_io_add_output() failed: %s\n",
nmsg_res_lookup(res));
return EXIT_FAILURE;
}
Tune the IO engine
First, the IO engine is configured to stop after processing the user specified number of payloads. Next, it is configured to mirror payloads across each output. Important to note here is that mirroring imposes the overhead of a per-output copy for each input payload (striping is default mode of operation).
/* configure the io engine to close after count payloads */
nmsg_io_set_count(io, atoi(argv[2]));
/* configure the io engine to mirror payloads to each output */
nmsg_io_set_output_mode(io, nmsg_io_output_mode_mirror);
Run the IO loop
Finally, the IO engine is started, which will read from the pcap input and
mirror NMSG payloads to both outputs. After count
payloads are processed,
the IO engine is shutdown and resources are freed.
/* start processing nmsg inputs and outputs */
res = nmsg_io_loop(io);
if (res != nmsg_res_success)
{
fprintf(stderr, "nmsg_io_loop() failed: %s\n", nmsg_res_lookup(res));
return EXIT_FAILURE;
}
/* deallocate the resources associated with the io engine */
nmsg_io_destroy(&io);
return EXIT_SUCCESS;
}
Coming up
The next article in the NMSG series will introduce the pynmsg
Python
programming API.
Mike Schiffman is a Protocol Legerdemainist for Farsight Security, Inc.
Read the next part in this series: Farsight’s Network Message, Volume 5: The Python Programming API