Farsight's Advanced Exchange Access: The C Programming API, Part Two
By Mike Schiffman
Introduction
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:
- Farsight's Advanced Exchange Access, Volume 1: Introduction to AXA
- Farsight's Advanced Exchange Access, Volume 2: Introduction to Sratool
- Farsight's Advanced Exchange Access, Volume 3: Introduction to Sratunnel
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 alibaxa
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 valueAXA_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
srvr_connect(void)
{
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:user@server[,port]
- transport: SRA supports either TLS or SSH as an encrypted transport.
Sratesttool
only supports TLS, so the argument here should betls
. - 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, printemsg
and quit.AXA_CONNECT_TEMP
: A temporary error occurred, printemsg
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 anAXA_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:
break;V
/* 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:
break;
/* 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);
}
else
{
axa_error_msg("\"-w %s\": %s", watch_str, emsg.c);
}
return (false);
}
else
{
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);
}
else
{
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),
AXA_P_OP_OK))
{
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),
emsg.c);
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