Farsight's Advanced Exchange Access: The C Programming API, Part Three

← Blog Home

RSS

By

Introduction

This article is the third and final in a multi-part blog series intended to introduce and acquaint the reader with Farsight Security’s AXA C API. This article concludes the introduction of the libaxa C programming API.

This article assumes the reader is familiar with AXA and related technologies. To brush up, the following Farsight Security Blog articles are recommended reading:

Additionally, if you haven’t yet, you’ll want to read the first and second articles in the series:

We will pick up right where we left in the second article.

Wait for a Response From the Server

The main event loop where sratesttool receives input from the SRA server and decides what to do with it, called srvr_wait_resp() is covered next. The function can be broken down into three parts:

  1. Check to see if it’s time to quit
  2. Receive input from the server
  3. Determine what to do with the input
Check to See If It’s Time To Quit

The first thing srvr_wait_resp() does is check to see if the global sentry value “terminated” is set to true. For this to be the case, one of the signals sratesttool was initialized to catch, has been caught. Most of the time, this will be SIGINT from the user pressing “ctrl-c” at the console. In this case, stop() (covered below) is called which begins the shutdown process.

static bool
srvr_wait_resp(axa_p_op_t resp_op, axa_p_op_t orig_op)
{
    bool result, done;
    axa_emsg_t emsg;

    result = false;
    done = false;
    do
    {
        if (terminated != 0)
        {
            stop(terminated);
        }
Receive Input From the Server

If it’s not time to quit, control is passed to axa_input(). This function accepts the standard emsg argument we’ve already seen, a pointer to a client.io context, and the number of milliseconds to block and wait for input which in this case is INT_MAX (this can be considered an indefinite block while waiting for data).

The result is switched and the following cases are evaluated:

  • AXA_IO_ERR: A fatal error occurred, dump the error and return false which will result in the upper layer terminating the program.
  • AXA_IO_BUSY: There was no input before the poll() timer expired or some other non-fatal condition occurred. Control continues back up to the top.
  • AXA_IO_OK: Valid data was received, time to figure out what to do with it.
        switch (axa_input(&emsg, &client.io, INT_MAX))
        {
            case AXA_IO_ERR:
                axa_error_msg("%s", emsg.c);
                goto out;
            case AXA_IO_BUSY:
                continue;
            case AXA_IO_OK:
                break;
            default:
                AXA_FAIL("impossible axa_input() result");
        }
Determine What to Do With the Input

Next, sratesttool evaluates the received AXA protocol message header’s opcode inside of a large switch table. For posterity, all of the possible opcodes are listed and in a more robust implementation, each code path would likely be populated. In the case of sratesttool only two server responses are interesting:

  • AXA_P_OP_HELLO: Process the “hello” message from the server. The AXA protocol version used by the server is saved and the client attempts to adjust to a version it can understand.
  • AXA_P_OP_OK: Process a “result” message from the server. Here sratesttool ensures that the opcode received is the expected one.

After a processing the response, axa_recv_flush() is called to purge (free()) the AXA protocol message from the IO context.

If something went wrong, axa_client_backoff() is called which will result in the client closing the connection to the server and the shutting down of the IO context.

        switch ((axa_p_op_t)client.io.recv_hdr.op)
        {
            case AXA_P_OP_NOP:
                break;
            case AXA_P_OP_HELLO:
                if (!axa_client_hello(&emsg, &client, NULL))
                {
                    axa_error_msg("%s", emsg.c);
                }
                else
                {
                    axa_trace_msg("connected OK");
                }
                break;
            case AXA_P_OP_OPT:
                done = true;
                break;
            case AXA_P_OP_OK:
                if (resp_op == client.io.recv_hdr.op &&
                        orig_op == client.io.recv_body->result.orig_op)
                {
                    result = true;
                    done = true;
                }
                break;
            case AXA_P_OP_ERROR:
                axa_error_msg("server returned error");
                done = true;
                break;
            case AXA_P_OP_MISSED:
            case AXA_P_OP_MISSED_RAD:
            case AXA_P_OP_WHIT:
            case AXA_P_OP_AHIT:
            case AXA_P_OP_WLIST:
            case AXA_P_OP_ALIST:
            case AXA_P_OP_CLIST:
                /* in this function, these are all unexpected op codes */
                break;
            case AXA_P_OP_USER:
            case AXA_P_OP_JOIN:
            case AXA_P_OP_PAUSE:
            case AXA_P_OP_GO:
            case AXA_P_OP_WATCH:
            case AXA_P_OP_WGET:
            case AXA_P_OP_ANOM:
            case AXA_P_OP_AGET:
            case AXA_P_OP_STOP:
            case AXA_P_OP_ALL_STOP:
            case AXA_P_OP_CHANNEL:
            case AXA_P_OP_CGET:
            case AXA_P_OP_ACCT:
            case AXA_P_OP_RADU:
            default:
                AXA_FAIL("impossible AXA op of %d from %s",
                     client.io.recv_hdr.op, client.io.label);
        }
        axa_recv_flush(&client.io);
    }
    while (!done);

out:
    if (!result)
    {
        /* disconnect for now if we failed to get the right response */
        axa_client_backoff(&client);
    }
    return (result);
}

Disconnect From the Server

If something egregious went wrong, sratesttool will emit an error message via axa_verror_msg() and shutdown the server connection via axa_client_backoff(). As has been mentioned before, a more robust implementation may make use of the internal backoff timers and attempt a reconnect.

static void AXA_PF(1,2)
srvr_disconnect(const char *p, ...)
{
    va_list args;

    va_start(args, p);
    axa_verror_msg(p, args);
    va_end(args);

    axa_client_backoff(&client);
}

Stop and Shutdown

The stop() function is called any time the interrupted sentinel evaluates to not 0. It performs an orderly shutdown, reports the number of watch hits, and then exits the program.

static void AXA_NORETURN
stop(int s)
{
    axa_client_close(&client);
    axa_io_cleanup();

    axa_trace_msg("%"PRIu64" total watch hits\n", hits);

    exit(s);
}

Read SIE Data From The Server

The function that is called from the main event loop everytime SIE data is ready is called srvr_process(). First, axa_recv_buf() is called and the result is evaluated. Important to note, the axa_recv_buf() can block so if this is undesirable, use of the other server read functions covered earlier such as axa_io_wait() or axa_input(). Once an AXA_IO_OK is returned, control passes to another switch table where the response opcode is evaluated.

static void
srvr_process(void)
{
    int n;
    char buf[BUFSIZ];
    axa_emsg_t emsg;
    struct timespec ts;
    struct tm *tm_info;

    switch (axa_recv_buf(&emsg, &client.io))
    {
        case AXA_IO_OK:
            break;
        case AXA_IO_ERR:
            srvr_disconnect("%s", emsg.c);
            return;
        case AXA_IO_BUSY:
            return;             /* wait for the rest */
        case AXA_IO_KEEPALIVE:  /* NA for this example */
        case AXA_IO_TUNERR:     /* NA for this example */
            break;
    }

Process the Watch Hit

The only case sratesttool is interested in is AXA_P_OP_WHIT. When the received header opcode is a “watch hit”, sratesttool next figures out the type of watch hit being reported to the client, NMSG or IP. While sratesttool is not interested in the contents of the watch hit, it does need to extract the timestamp, which is stored in different places depending on the type. Next, a human readable string is constructed using the type of watch hit, the SIE channel it occurred on, and the timestamp of the watch hit (including nanoseconds). The hits counter is incremented and control proceeds to the end of the switch table where, as seen above, axa_recv_flush() is called to purge the AXA protocol message from the IO context.

    n = 0;
    switch ((axa_p_op_t)client.io.recv_hdr.op)
    {
        case AXA_P_OP_NOP:
            break;
        case AXA_P_OP_ERROR:
            srvr_disconnect(" ");
            return;
        case AXA_P_OP_MISSED:
        case AXA_P_OP_MISSED_RAD:
            break;
        case AXA_P_OP_WHIT:
            switch (client.io.recv_body->whit.hdr.type)
            {
                case AXA_P_WHIT_NMSG:
                    ts.tv_sec = client.io.recv_body->whit.nmsg.hdr.ts.tv_sec;
                    ts.tv_nsec = client.io.recv_body->whit.nmsg.hdr.ts.tv_nsec;
                    break;
                case AXA_P_WHIT_IP:
                    ts.tv_sec =
                        AXA_P2H32(client.io.recv_body->whit.ip.hdr.tv.tv_sec);
                    ts.tv_nsec = 1000 *
                        AXA_P2H32(client.io.recv_body->whit.ip.hdr.tv.tv_usec);
                    break;
            }
            tm_info = localtime((time_t *)&ts.tv_sec);
            n = strftime(buf + n, 26, "%Y-%m-%dT%H:%M:%S", tm_info);
            snprintf(buf + n, BUFSIZ - n, ".%ld", ts.tv_nsec);
            axa_trace_msg("%s watch hit, channel: %3d @ %s\n",
                    client.io.recv_body->whit.hdr.type == AXA_P_WHIT_NMSG ?
                    "NMSG" : "IP",
                    client.io.recv_body->whit.hdr.ch,
                    buf);
            hits++;
            break;
        case AXA_P_OP_AHIT:
        case AXA_P_OP_OK:
        case AXA_P_OP_HELLO:
        case AXA_P_OP_WLIST:
        case AXA_P_OP_ALIST:
        case AXA_P_OP_OPT:
        case AXA_P_OP_CLIST:
            break;
        case AXA_P_OP_USER:
        case AXA_P_OP_JOIN:
        case AXA_P_OP_PAUSE:
        case AXA_P_OP_GO:
        case AXA_P_OP_WATCH:
        case AXA_P_OP_WGET:
        case AXA_P_OP_ANOM:
        case AXA_P_OP_AGET:
        case AXA_P_OP_STOP:
        case AXA_P_OP_ALL_STOP:
        case AXA_P_OP_CHANNEL:
        case AXA_P_OP_CGET:
        case AXA_P_OP_ACCT:
        case AXA_P_OP_RADU:
        default:
            AXA_FAIL("impossible AXA op of %d from %s",
                 client.io.recv_hdr.op, client.io.label);
    }

    axa_recv_flush(&client.io);
}

Signal Handler

The signal handler simply sets the global sentinel terminated to the integral value of the signal. This allows the uppers layers to:

  1. Know when something asynchronous has happened and it’s time to quit
  2. Know which signal was sent to sratesttool

If a signal is sent repeatedly, it is considered urgent and sratesttool will reset the default signal handler via signal(sig, SIG_DFL) which will immediately terminate the program.

void
sigterm(int sig)
{
    terminated = sig;

    signal(sig, SIG_DFL);       /* quit early on repeated signals */
}

Usage

Finally, we conclude with a simple usage function instructing the user how to invoke sratesttool.

static void
usage(const char *name)
{
    printf("SRA Test Tool (c) 2015 Farsight Security, Inc.\n");
    printf("\nUsage: %s [options]\n", name);
    printf("[-s tls:user@server,port]\n");
    printf("\t\tuser:      SRA username\n");
    printf("\t\tserver:    SRA server\n");
    printf("\t\tport:      TCP port (typically 1021)\n");
    printf("[-h]");
    printf("\t\tthis prose\n");
    printf("[-c channel]");
    printf("\tenable channel (default: \"255\", SIE Heartbeat)\n");
    printf("[-w watch]");
    printf("\tset watch (default: \"ch=255\", SIE Heartbeat channel)\n");
}

Conclusion

In the last three articles, we’ve covered the internals of sratesttool, a very simple “hello world” SRA client. If you are interested in learning more, please check out the AXA distribution which contains sratool and radtool which are more robust implementations of SRA and RAD clients built on top of libaxa.

Mike Schiffman is a Packet Esotericist for Farsight Security, Inc.

← Blog Home

Protect against cybercriminal activity in real-time.

Request demo

Email: sales@farsightsecurity.com Phone: +1-650-489-7919