featured image, abstract image
Blog Farsight TXT Record

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:

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 opaque nmsg io engine object. It handles multiplexing of NMSG data between inputs and outputs.
  • nmsg_res res: The nmsg result code object. It is used by the libnmsg API to return meaningful result codes that can be looked up with nmsg_res_lookup().
  • nmsg_input_t input: The opaque nmsg input object. It is used by nmsg to stream payloads from different input sources.
  • nmsg_output_t output: The opaque nmsg output object. It is used by nmsg 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