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




This article is the second in a multi-part blog series intended to introduce and acquaint the reader with Farsight Security's AXA C API. This article continues 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 article in the series:

We pick up right where we left in the first article.

Connecting To The Server And Setting State

The first srvr_* function we'll dissect is a big one, responsible for connecting to the SRA server, setting the watch and enabling the channel. First, we explain important AXA-specific data types / variables:

  • axa_p_watch_t watch: An AXA watch object; contains all of the state for an AXA watch specification.
  • axa_p_channel_t channel: An AXA channel object; contains all of the state for an AXA channel specification.
  • axa_emsg_t emsg: An AXA error message object; if an error occurs in a libaxa function, this will contain details on what caused it.
  • axa_tag_t cur_tag: A tag is an identifier used to uniquely "tag" specific events during the lifetime of an AXA session. To refer to these events, the client or server will use the tag. Some AXA messages do not require tags, in which case we will use the value AXA_TAG_NONE. Required tags must be unique during the lifetime of the corresponding client request. Some client requests such as a watch can last indefinitely and will elicit many server responses all with the same tag.
static bool
    axa_p_watch_t watch;
    size_t watch_len;
    axa_p_channel_t channel;
    axa_emsg_t emsg;
    bool res;
    axa_tag_t cur_tag;
Connect To The Server

The axa_client_open() function attempts to open a server connection and establish state for our client's connection. The first two arguments, passed by reference, will be filled in by libaxa (emsg if something goes wrong, client if something doesn't). The next argument, server_str is a specially crafted text string of the following format:

  • transport: SRA supports either TLS or SSH as an encrypted transport. Sratesttool only supports TLS, so the argument here should be tls.
  • user: The username of the user. This is provisioned by Farsight Security when your account is turned up.
  • server: The SRA server IP address or hostname.
  • port: Used only for TLS, to specify the port the SRA server listens for TLS connections.

The next boolean argument specifies whether or not to expect to connect to a RAD server or an SRA server. Since this is an SRA tool, "RAD" (Real-time Anomaly Detection) mode is not enabled. Likewise, since we are using TLS, SSH tunnel debugging is not enabled.

The input socket buffer size is set to be 256 * 1024, a larger than normal value due to the potentially high-volume of SIE data transited by SRA. Internally, this will be requested to be set by setsockopt(..SO_RCVBUF..).

The return value from axa_client_open() is evaluated in a small switch table which checks for errors or non sequitur as per the following:

  • AXA_CONNECT_ERR: Something went terribly wrong, print emsg and quit.
  • AXA_CONNECT_TEMP: A temporary error occurred, print emsg and quit. In more robust implementations, we would probably back off and try again.
  • AXA_CONNECT_DONE: The connection is complete.
  • AXA_CONNECT_NOP: The connection is complete and an AXA_P_OP_NOP is sent to the server. This is done so the client can tell the server the version of the AXA protocol that the client is using. If need be, the server can down shifting to an old version of the protocol.
  • AXA_CONNECT_USER: For TCP-based socket connections, does not apply to TLS (or SSH) connections and should be an error in this case.
    axa_trace_msg("connecting to %s...", server_str);
    switch (axa_client_open(&emsg,      /* if func fails, will contain error */
                            &client,    /* client context */
                            server_str, /* server address string */
                            false,      /* true to enable RAD mode */
                            false,      /* true when SSH tunnel debugging */
                            256 * 1024, /* socket buffer size */
                            false))     /* true for non-blocking */
        /* permanent failure, connection has been closed, check emsg */
        case AXA_CONNECT_ERR:
            axa_error_msg("%s", emsg.c);
            return (false);
        /* A temporary failure, connection has been closed, check emsg.
         * In more robust implementations, we would try to reconnect to the
         * server.
        case AXA_CONNECT_TEMP:
            axa_error_msg("%s", emsg.c);
            srvr_disconnect("%s", emsg.c);
            return (false);
        /* connect is complete */
        case AXA_CONNECT_DONE:
        /* non-blocking connection waiting for TCP SYN/ACK or TLS handshake */
        case AXA_CONNECT_INCOM:
            AXA_FAIL("impossible result from axa_client_open");
        /* connect is complete (incl xmit of AXA_P_OP_NOP) */
        case AXA_CONNECT_NOP:
        /* connect is complete (incl xmit of AXA_P_OP_USER) */
        case AXA_CONNECT_USER:
            srvr_disconnect("%s", emsg.c);
            return (false);
Pause The Output

After successfully connecting to the server but before any watches are set or channels are enabled, an AXA_P_OP_PAUSE command is sent. This "pause" command tells the server to stop sending data and to actually discard it, server side. This is a global switch the pauses data output for all channels for the current user.

This is the first time we encounter the srvr_cmd() function which is used to send commands and data to the SRA server. We'll cover it in detail later but for now it's important to note that it accepts an AXA tag argument:

  • AXA_TAG_MIN: The "minimum" tag value, this is a non-specific tag value used when we need a tag but don't care about which one.

And two AXA opcode arguments:

  • AXA_P_OP_PAUSE: The opcode representing the action requested of the server. In this case to pause the data flow.
  • AXA_P_OP_OK: The opcode expected from the server the for the operation to be considered a success.
    /* block any watch hits until we are ready */
    if (!srvr_cmd(AXA_TAG_MIN, AXA_P_OP_PAUSE, NULL, 0, AXA_P_OP_OK))
        return (false);
Parse and Set the Watch

Next, the watch string is parsed with a call to axa_parse_watch(). If there is a problem parsing it, libaxa will do its best to emit a helpful error message in emsg. However, if the watch string makes no sense, the function will fail with an empty emsg.

For a verbose treatment of what SRA watches can look like, see sratool(1).

After successfully parsing the watch, it is set on the server with a call to srvr_cmd() with an AXA opcode of AXA_P_OP_WATCH and an expected server response of AXA_P_OP_OK.

    cur_tag = 1;
    /* parse the watch string */
    axa_trace_msg("parsing watch: %s...\n", watch_str);
    res = axa_parse_watch(&emsg, &watch, &watch_len, watch_str);
    if (!res)
        if (emsg.c[0] == '\0')
            axa_error_msg("unrecognized \"-w %s\"", watch_str);
            axa_error_msg("\"-w %s\": %s", watch_str, emsg.c);
        return (false);
        axa_trace_msg("parse %s OK\n", watch_str);

    /* set the watch on the server */
    axa_trace_msg("setting watch on server...\n");
    if (!srvr_cmd(cur_tag, AXA_P_OP_WATCH, &watch, watch_len, AXA_P_OP_OK))
        return (false);
    axa_trace_msg("watch set OK\n");
Parse and Enable the Channel

Next, in the same manner as above, the channel string is parsed and enabled on the server.

    memset(&channel, 0, sizeof(channel));
    /* parse the channel string */
    axa_trace_msg("parsing channel: %s...\n", channel_str);
    if (!axa_parse_ch(&emsg, &channel.ch, channel_str,
                strlen(channel_str), true, true))
        axa_error_msg("\"-c %s\": %s", channel_str, emsg.c);
        return (false);
        axa_trace_msg("parse %s OK\n", channel_str);

    channel.on = 1;
    axa_trace_msg("enabling channel on server...\n");
    if (!srvr_cmd(AXA_TAG_MIN, AXA_P_OP_CHANNEL, &channel, sizeof(channel),
        return (false);
Un-pause The Output

After the channel is enabled, sratesttool is ready to start streaming watch hits. It lets the server know by sending the opcode AXA_P_OP_GO which tells the server its time to un-pause the output.

Now the server connection process is complete and the function returns successfully.

    if (!srvr_cmd(AXA_TAG_MIN, AXA_P_OP_GO, NULL, 0, AXA_P_OP_OK))
        return (false);
    axa_trace_msg("channel enabled OK\n");
    return (true);

Run a Command on The Server

The next function, as we've already seen, srvr_cmd(), is used to send opcodes and data to the SRA server. It is a wrapper to axa_client_send() with additional code to disconnect via srvr_disconnect() if a problem occurs.

We've already covered the tag and opcode arguments, but the srvr_cmd() function also accepts data in the form of an opaque pointer and its length. The call to axa_client_send() fires off the request to the server and, if it doesn't fail, a call to srvr_wait_resp() is made to process the response.

For that, and the rest of the code, you'll have to wait until the third and final installment of this series.

static bool
srvr_cmd(axa_tag_t tag, axa_p_op_t op, const void *b, size_t b_len,
     axa_p_op_t resp_op)
    axa_p_hdr_t hdr;
    axa_emsg_t emsg;
    char pbuf[AXA_P_STRLEN];

    if (!axa_client_send(&emsg, &client, tag, op, &hdr, b, b_len))
        srvr_disconnect("sending %s failed: %s",
                axa_p_to_str(pbuf, sizeof(pbuf), true, &hdr, b),
        return (false);

    return (srvr_wait_resp(resp_op, op));

Coming up

The third and final article in the AXA C API series will conclude the discussion of sratesttool's internals.

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

Read the next part in this series: Farsight's Advanced Exchange Access: The C Programming API, Part Three