Middlebox Stats (midstat)

Introduction

netstat (network statistics) is a command-line tool in UNIX-like operating systems which displays the network connection status for the Transmission Control Protocol. It is often used for debugging the network and to determine the number of ongoing TCP connections.

The original netstat focuses on the end-host networking stack; we develop a middlebox version of netstat on top of mOS which we call midstat. midstat shows the live statistics of both sides (client and server side) network connection. Network statistics include the IP addresses, port numbers, and the running TCP states of each side of the ongoing connection.

Code Walkthrough

The following sections provide an explanation of the main components of the mOS midstat code. All mOS library functions used in the sample code are prefixed with mtcp_ and are explained in detail in the Programmer’s Guide - mOS Programming API. Note that we omit the error handling logic in the example code for brevity.

(1) The main() Function

The main() function performs the initialization and calls the execution threads for each CPU core.

The first task is to initialize mOS thread based on the mOS configuration file. fname is the file path to the mos.conf file which will be provided to mtcp_init().

/* parse mos configuration file */
ret = mtcp_init(fname);

In case the mOS configuration needs an update after mtcp_init() call (e.g., for changing the number of cores required for running the mOS application), we can use mtcp_getconf() to retrieve the current config from the mOS core. mtcp_setconf() function can then be used to re-set some of the selected parameters (as shown in the example below).

/* set the core limit */
mtcp_getconf(&mcfg);
mcfg.num_cores = g_max_cores;
mtcp_setconf(&mcfg);

Afterwards, the main() function will create mtcp context for each core and launch the per-core thread starting from the function InitMonitor(). InitMonitor() function will be discussed in the following section.

for (i = 0; i < g_max_cores; i++) {
        /* Run mOS for each CPU core */
        if (!(g_mctx[i] = mtcp_create_context(i))) {
                fprintf(stderr, "Failed to craete mtcp context.\n");
                return -1;
        }

        /* init monitor */
    InitMonitor(g_mctx[i], ev_new_syn);
}

(2) Per-thread Initialization Function

The InitMonitor() function is the core functional part of the mOS thread initialization. In order to allow midstat to track all the ongoing connections, it first initializes the memory for the global connection queue per each core.

/* Initialize internal memory structures */
TAILQ_INIT(&g_sockq[mctx->cpu]);

Afterwards, this application creates a MOS_SOCK_MONITOR_STREAM socket to monitor TCP-related events of ongoing TCP flows.

/* create socket */
sock = mtcp_socket(ctx->mctx, AF_INET, MOS_SOCK_MONITOR_STREAM, 0);

Since this program does not perform any payload monitoring, it disables the socket buffer of each side as follows:

mtcp_setsockopt(mctx, sock, SOL_MONSOCKET, MOS_CLIBUF, &optval, sizeof(optval));
mtcp_setsockopt(mctx, sock, SOL_MONSOCKET, MOS_SVRBUF, &optval, sizeof(optval));

The last step of the per-thread initialization is to register callback functions for the TCP events that this program interested in. The RegisterCallbacks function will be described further in the later sections.

RegisterCallbacks(mctx, sock, ev_new_syn);

(3) The RegisterCallbacks() Function

The RegisterCallbacks() function is used for registering callback handler functions for the TCP events in order to monitor the TCP-level behavior.

First, cb_creation() function is called whenever a new connection is created.

mtcp_register_callback(mctx, sock, MOS_ON_CONN_START, MOS_HK_SND, cb_creation);

The role of the cb_creation() function is twofold: (1) to retrieve the address of each side, and (2) to insert the connection metadata into the global connection queue.

static void
cb_creation(mctx_t mctx, int sock, int side, event_t events, filter_arg_t *arg)
{
        ...
        mtcp_getpeername(mctx, c->sock, (void *)c->addrs, &addrslen, MOS_SIDE_CLI);

        /* Insert the structure to the queue */
        TAILQ_INSERT_TAIL(&g_sockq[mctx->cpu], c, link);
}

Second, cb_st_chg() function is called whenever there is any TCP state change. We note that cb_st_chg() function is registered at both sender- and receiver-side MOS_ON_TCP_STATE_CHANGE events, since the TCP states of each side can be changed independently.

mtcp_register_callback(mctx, sock, MOS_ON_TCP_STATE_CHANGE, MOS_HK_SND, cb_st_chg);
mtcp_register_callback(mctx, sock, MOS_ON_TCP_STATE_CHANGE, MOS_HK_RCV, cb_st_chg);

The role of the cb_st_chg() function is to retrieve the changed TCP state using mtcp_getsockopt() function. The side parameter given to the callback function indicates the side whose TCP state changed.

static void
cb_st_chg(mctx_t mctx, int sock, int side, event_t events, filter_arg_t *arg)
{
        if (side == MOS_SIDE_CLI) {
                mtcp_getsockopt(mctx, c->sock, SOL_MONSOCKET, MOS_TCP_STATE_CLI,
                (void *)&c->cli_state, &intlen);
        }
        else {
                mtcp_getsockopt(mctx, c->sock, SOL_MONSOCKET, MOS_TCP_STATE_SVR,
                (void *)&c->svr_state, &intlen);
        }
}

Third, cb_destroy() function is called whenever there is any connection which is about to be closed.

mtcp_register_callback(mctx, sock, MOS_ON_CONN_END, MOS_HK_SND, cb_destroy);

The role of the cb_destroy() function is to find and delete the connection from the global connection queue, and release the allocated memory.

/* Destroy connection structure */
static void
cb_destroy(mctx_t mctx, int sock, int side, event_t events, filter_arg_t *arg)
{
        struct connection *c;
        if (!(c = find_connection(mctx->cpu, sock)))
                return;
        TAILQ_REMOVE(&g_sockq[mctx->cpu], c, link);
        free(c);
}

The last step of the RegisterCallbacks() function is to register timer callback for printing the network statistics every second. Note that this function should be registered only for CPU core id 0, to prevent duplicate printing.

/* CPU 0 is in charge of printing stats */
if (mctx->cpu == 0 && mtcp_settimer(mctx, sock, &tv_1sec, cb_printstat)
...