Plugin programming cheatsheet

Bash C C++ cheatsheet plugin development Rust

Contents

This cheatsheet summarizes the most important callbacks, functions, macros and structures required to write your own plugins. For more details about the different aspects of writing your own plugins, refer to the t2PSkel plugin or to one of the tutorials.

Tutorials

Creating a new plugin

Creating a new C plugin

t2plugin -c pluginName

Creating a new minimal C plugin

t2plugin -c pluginName -m

Creating a new minimal sink plugin

t2plugin -c pluginName -s

Creating a new minimal t2buf plugin

t2plugin -c pluginName -t

Creating a new C plugin with a specific plugin number

t2plugin -c pluginName -n 123

Creating a new C++ plugin

[Tutorial] t2plugin -c pluginName --cpp

Creating a new Rust plugin

[Tutorial] t2plugin -c pluginName --rust

Plugin initialization

Without any dependencies to other plugins

T2_PLUGIN_INIT("yourPluginName", "0.9.1", 0, 9);

With dependency to one plugin

T2_PLUGIN_INIT_WITH_DEPS("pluginName", "0.9.1", 0, 9, "tcpFlags");

With dependency to several plugins

T2_PLUGIN_INIT_WITH_DEPS("pluginName", "0.9.1", 0, 9, "tcpFlags,tcpStates");

Callbacks

void t2Init()

Initialization: allocate memory here [Tutorial]

void t2Init() {
    // allocate struct for all flows, initialize to 0 and check for errors
    T2_PLUGIN_STRUCT_NEW(pluginNameFlows);
}

binary_value_t* t2PrintHeader()

Describe your flow output here [Tutorial]

binary_value_t* t2PrintHeader() {
    binary_value_t *bv = NULL;
    // Describe your output fields with the BV_APPEND macros detailed in the next section, e.g.:
    BV_APPEND_H8(bv, "mypluginStat" , "description...");
    BV_APPEND_U64(bv, "mypluginCnt" , "description...");
    return bv;
}

void t2OnNewFlow(packet_t *packet, unsigned long flowIndex)

A new flow has been generated: reset its memory and try to determine if it is relevant for the plugin (protocol, ports, …) [Tutorial]

void t2OnNewFlow(packet_t *packet, unsigned long flowIndex) {
    // Reset the structure for this flow
    T2_PLUGIN_STRUCT_RESET_ITEM(pluginNameFlows, flowIndex);
    //pluginNameFlow_t * const pnFlowP = &pluginNameFlows[flowIndex];
    //memset(pnFlowP, '\0', sizeof(*pnFlowP));
}

void t2OnLayer2(packet_t *packet, unsigned long flowIndex)

A new packet with a layer 2 (if it also has a layer 4, then flowIndex is equal to HASHTABLE_ENTRY_NOT_FOUND) [Tutorial]

void t2OnLayer2(packet_t *packet, unsigned long flowIndex) {
    if (flowIndex == HASHTABLE_ENTRY_NOT_FOUND) return;
    // This packet does not have a layer 4.
}

void t2OnLayer4(packet_t *packet, unsigned long flowIndex)

A new packet with a layer 4 [Tutorial]

void t2OnLayer4(packet_t *packet, unsigned long flowIndex)

void t2OnFlowTerminate(unsigned long flowIndex, outputBuffer_t *buf)

A flow has been terminated: finish computations for this flow and push your data to the output buffer [Tutorial]

void t2OnFlowTerminate(unsigned long flowIndex, outputBuffer_t *buf) {
    pluginNameFlow_t * const pnFlowP = &pluginNameFlows[flowIndex];
    OUTBUF_APPEND_U8(buf, pnFlowP->mypluginStat);
    OUTBUF_APPEND_U64(buf, pnFlowP->mypluginCnt);
}

void t2Finalize()

Free the memory here [Tutorial]

void t2Finalize() {
    free(pluginNameFlows);
}

void t2PluginReport(FILE *stream)

Contribute plugin specific information to the end report [Tutorial]

static uint8_t mypluginStat;
static uint64_t mypluginCnt;

void t2PluginReport(FILE *stream) {
    if (!mypluginCnt) return;
    T2_FPLOG_NUMP0(stream, plugin_name, "Number of myplugin packets", mypluginCnt);
    T2_FPLOG_AGGR_HEX(stream, plugin_name, mypluginStat);
    // => T2_FPLOG(stream, plugin_name, "Aggregated mypluginStat=0x%02" B2T_PRIX8, mypluginStat);
}

void t2Monitoring(FILE *stream, uint8_t state)

Monitoring mode [Tutorial]

static uint8_t mypluginStat;
static uint64_t mypluginCnt, mypluginCnt0;

void t2Monitoring(FILE *stream, uint8_t state) {
    switch (state) {

        case T2_MON_PRI_HDR:  // Print the name of the variables that will be output
            fputs("mypluginStat\tmypluginCnt\t", stream); // Note the trailing tab (\t)
            return;

        case T2_MON_PRI_VAL:  // Print the variables to monitor
            fprintf(stream, "%" PRIu64 "\t0x%02" B2T_PRIX8 "\t", // Note the trailing tab (\t)
                  mypluginCnt - mypluginCnt0, mypluginStat);
            break;

        case T2_MON_PRI_REPORT:  // print a report similar to t2PluginReport()
            T2_FPLOG_DIFFNUMP(stream, plugin_name, "Number of myplugin packets", mypluginCnt);
            T2_FPLOG_AGGR_HEX(stream, plugin_name, mypluginStat);
            // => T2_FPLOG(stream, plugin_name, "Aggregated mypluginStat=0x%02" B2T_PRIX8, mypluginStat);
            break;

        default:  // Invalid state, do nothing
            return;
    }

#if DIFF_REPORT == 1
    mypluginCnt0 = mypluginCnt;
#endif
}

void t2SaveState(FILE *stream)

Save the state of your plugin

static uint8_t mypluginStat;
static uint64_t mypluginCnt;

void t2SaveState(FILE *stream)
    fprintf(stream, "0x%02" PRIx8 "\t%" PRIu64, mypluginStat, mypluginCnt);
}

void t2RestoreState(const char *str)

Restore the state of your plugin

static uint8_t mypluginStat;
static uint64_t mypluginCnt;

void t2RestoreState(const char *str)
    sscanf(str, "0x%02" SCNx8 "\t%" SCNu64, &mypluginStat, &mypluginCnt);
}

void t2BufferToSink(outputBuffer_t buf, binary_value_t bv)

Dump the content of buf to a file, stream, … Use bv to decode it. (Sink plugins only) [Tutorial]

void t2BufferToSink(outputBuffer_t *buf, binary_value_t *bv)

Accessing the plugin name

The T2_PLUGIN_INIT() and T2_PLUGIN_INIT_WITH_DEPS() macros both define the following static variable:

static const char * const plugin_name = "pluginName";

This variable being static, it can only be accessed in the pluginName.c file. However, nothing prevents you from redefining it in a different file! As an example, the plugin_name variable can be used with the macros defined here, e.g., T2_PLOG(plugin_name, "Andy was here!");

Accessing the various layers

Layer 2 header

The layer 2 header (Ethernet) can be accessed as follows:

const uint16_t ethType = L2_PROTO(packet);
//const uint16_t ethType = packet->ethType;

const ethernetHeader_t * const ethHdrP = ETH_HEADER(packet);
//const ethernetHeader_t * const ethHdrP = (ethernetHeader_t*) packet->l2HdrP;

const lapdHdr_t * const lapdHdrP = LAPD_HEADER(packet);
//const lapdHdr_t * const lapdHdrP = (ethernetHeader_t*) packet->l2HdrP;

const uint8_t * const l2HdrP = L2_HEADER(packet);
//const uint8_t * const l2HdrP = packet->l2HdrP;

Layer 3 header

The layer 3 header (IPv4, IPv6) can be accessed as follows:

if (PACKET_IS_IPV6(packet)) {
    const ip6Header_t * const ip6HdrP = IPV6_HEADER(packet);             // IPv6
    //const ip6Header_t * const ip6HdrP = (ip6Header_t*) packet->l3HdrP;   // IPv6
} else if (PACKET_IS_IPV4(packet)) {
    const ipHeader_t * const ipHdrP = IPV4_HEADER(packet);               // IPv4
    //const ipHeader_t * const ipHdrP = (ipHeader_t*) packet->l3HdrP;      // IPv4
} else {
    const uint8_t * const l3HdrP = L3_HEADER(packet);
    //const uint8_t * const l3HdrP = packet->l3HdrP;
}

Layer 4 header

The layer 4 header (TCP, UDP, ICMP, SCTP, …) can be accessed as follows:

switch (L4_PROTO(packet)) {
//switch (packet->l4Proto) {
    case L3_TCP: {
        const tcpHeader_t * const tcpHdrP = TCP_HEADER(packet);
        //const tcpHeader_t * const tcpHdrP = (tcpHeader_t*) packet->l4HdrP;
        break;
    }
    case L3_UDP: {
        const udpHeader_t * const udpHdrP = UDP_HEADER(packet);
        //const udpHeader_t * const udpHdrP = (udpHeader_t*) packet->l4HdrP;
        break;
    }
    case L3_ICMP: {
        const icmpHeader_t * const icmpHdrP = ICMP_HEADER(packet);
        //const icmpHeader_t * const icmpHdrP = (icmpHeader_t*) packet->l4HdrP;
        break;
    }
    case L3_SCTP: {
        const sctpHeader_t * const sctpHdrP = SCTP_HEADER(packet);
        //const sctpHeader_t * const sctpHdrP = (sctpHeader_t*) packet->l4HdrP;
        break;
    }
    case L3_PIM: {
        const pimHeader_t * const pimHdrP = PIM_HEADER(packet);
        //const pimHeader_t * const pimHdrP = (pimHeader_t*) packet->l4HdrP;
        break;
    }
    default: {
        const uint8_t * const l4HdrP = L4_HEADER(packet);
        //const uint8_t * const l4HdrP = packet->l4HdrP;
        break;
    }
}

Layer 7 header

The layer 7 header can be accessed as follows:

const myProto_t * const myProtoHdrP = (myProto_t*) L7_HEADER(packet);
//const myProto_t * const myProtoHdrP = (myProto_t*) packet->l7HdrP;

Selecting flows by port/protocol

Selecting flows by port

Accessing the source/destination port:

const uint16_t srcPort = flowP->srcPort;
const uint16_t dstPort = flowP->dstPort;

Selecting flows by protocol

  • Accessing the layer 2 protocol:

    const uint16_t ethType = L2_PROTO(packet);
    //const uint16_t ethType = packet->ethType;
    
    const uint16_t ethType = L2_PROTO(flowP);
    //const uint16_t ethType = flowP->ethType;
  • Accessing the layer 3 protocol:

    const uint8_t proto = L3_PROTO(packet);
    //const uint8_t proto = packet->l3Proto;
    if (proto == ETHERTYPE_IP) {
        // ...
    } else if (proto == ETHERTYPE_IPV6) {
        // ...
    }
  • Accessing the layer 4 protocol (TCP, UDP, ICMP, …):

    const uint8_t proto = L4_PROTO(packet);
    //const uint8_t proto = packet->l4Proto;
    
    const uint8_t proto = L4_PROTO(flowP);
    //const uint8_t proto = flowP->l4Proto;

Identifying flows with flow index and direction, e.g., for filenames

// Flow index
const uint64_t flowInd = flowP->findex;

// Flow direction
const char * const dir_s = FLOW_DIR_S(flowP);  // "A" or "B"
const char dir_c = FLOW_DIR_C(flowP);          // 'A' or 'B'

// Flow direction (alternative)
char *dir_s;
char dir_c;
if (FLOW_IS_B(flowP)) {
    dir_s = FLOW_DIR_S_B;
    dir_c = FLOW_DIR_C_B;
} else {  // FLOW_IS_A(flowP))
    dir_s = FLOW_DIR_S_A;
    dir_c = FLOW_DIR_C_A;
}

// Filename example
char filename[FILENAME_MAX] = {};
snprintf(filename, sizeof(filename), "/tmp/prefix_%" PRIu64 "_%c.bin", flowInd, dir_c);  // direction as char
snprintf(filename, sizeof(filename), "/tmp/prefix_%" PRIu64 "_%s.bin", flowInd, dir_s);  // direction as string

Describing flow file output in t2PrintHeader()

Bitfields and hexadecimal numbers (8, 16, 32 and 64 bits)

// Single values, e.g., 0x01

BV_APPEND_H8( bv, "myHex8Field" , "description...");
BV_APPEND_H16(bv, "myHex16Field", "description...");
BV_APPEND_H32(bv, "myHex32Field", "description...");
BV_APPEND_H64(bv, "myHex64Field", "description...");

// Repetitive values, e.g., 0x01;0x02

BV_APPEND_H8_R( bv, "myHex8Field" , "description...");
BV_APPEND_H16_R(bv, "myHex16Field", "description...");
BV_APPEND_H32_R(bv, "myHex32Field", "description...");
BV_APPEND_H64_R(bv, "myHex64Field", "description...");

Signed integers (8, 16, 32 and 64 bits)

// Single values, e.g., -1

BV_APPEND_I8( bv, "myInt8Field" , "description...");
BV_APPEND_I16(bv, "myInt16Field", "description...");
BV_APPEND_I32(bv, "myInt32Field", "description...");
BV_APPEND_I64(bv, "myInt64Field", "description...");

// Repetitive values, e.g., -1;-2

BV_APPEND_I8_R( bv, "myInt8Field" , "description...");
BV_APPEND_I16_R(bv, "myInt16Field", "description...");
BV_APPEND_I32_R(bv, "myInt32Field", "description...");
BV_APPEND_I64_R(bv, "myInt64Field", "description...");

Unsigned integers (8, 16, 32 and 64 bits)

// Single values, e.g., 1

BV_APPEND_U8( bv, "myInt8Field" , "description...");
BV_APPEND_U16(bv, "myInt16Field", "description...");
BV_APPEND_U32(bv, "myInt32Field", "description...");
BV_APPEND_U64(bv, "myInt64Field", "description...");

// Repetitive values, e.g., 1;2

BV_APPEND_U8_R( bv, "myInt8Field" , "description...");
BV_APPEND_U16_R(bv, "myInt16Field", "description...");
BV_APPEND_U32_R(bv, "myInt32Field", "description...");
BV_APPEND_U64_R(bv, "myInt64Field", "description...");

Floating point (float, double)

// Single values, e.g., 1.1

BV_APPEND_FLT(bv, "myFloatField" , "description...");
BV_APPEND_DBL(bv, "myDoubleField", "description...");

// Repetitive values, e.g., 1.1;2.2

BV_APPEND_FLT_R(bv, "myFloatField" , "description...");
BV_APPEND_DBL_R(bv, "myDoubleField", "description...");

MAC addresses

// Single values, e.g., 00:11:22:33:44:55

BV_APPEND_MAC(bv, "myMACField" , "description...");

// Repetitive values, e.g., 00:11:22:33:44:55;01:23:45:67:89:01

BV_APPEND_MAC_R(bv, "myMACField" , "description...");

IPv4, IPv6 and IPvX (IP version and IP) addresses

// Single values, e.g., 1.2.3.4

BV_APPEND_IP4(bv, "myIPv4Field" , "description...");
BV_APPEND_IP6(bv, "myIPv6Field" , "description...");
BV_APPEND_IPX(bv, "myIPvXField" , "description...");

// Repetitive values, e.g., 1.2.3.4;2.3.4.5

BV_APPEND_IP4_R(bv, "myIPv4Field" , "description...");
BV_APPEND_IP6_R(bv, "myIPv6Field" , "description...");
BV_APPEND_IPX_R(bv, "myIPvXField" , "description...");

Timestamp, duration

// Single values, e.g., 0.1

BV_APPEND_TIMESTAMP(bv, "myTimestampField", "description...");
BV_APPEND_DURATION( bv, "myDurationField" , "description...");

// Repetitive values, e.g., 0.1;0.2

BV_APPEND_TIMESTAMP_R(bv, "myTimestampField", "description...");
BV_APPEND_DURATION_R( bv, "myDurationField" , "description...");

Characters, strings and string classes (strings without quotes)

// Single values, e.g., "string1"

BV_APPEND_CHAR(bv, "myCharField"       , "description...");
BV_APPEND_STR( bv, "myStringField"     , "description...");
BV_APPEND_STRC(bv, "myStringClassField", "description...");

// Repetitive values, e.g., "string1";"string2"

BV_APPEND_CHAR_R(bv, "myCharField"       , "description...");
BV_APPEND_STR_R( bv, "myStringField"     , "description...");
BV_APPEND_STRC_R(bv, "myStringClassField", "description...");

If the type of the column varies

#if VAL_UINT8 == 1
binary_type_t myType = bt_hex_8;
#else // VAL_UINT8 == 0
binary_type_t myType = bt_hex_16;
#endif // VAL_UINT8 == 0

// Single values, e.g., val1

BV_APPEND_TYPE(bv, "myField", "description...", myType);

// Repetitive values, e.g., val1;val2

BV_APPEND_TYPE_R(bv, "myField", "description...", myType);

Compounds, e.g., field1_field2

// Single values, e.g., 1_2

BV_APPEND(bv, "myCompoundPart1_Part2", "description...", number_of_fields, type1, type2, ...);
BV_APPEND(bv, "myCompoundPart1_Part2", "description...", number_of_fields, type1, type2, ...);

// Repetitive values, e.g., 1_2;3_4

BV_APPEND_R(bv, "myCompoundPart1_Part2", "description...", number_of_fields, type1, type2, ...);
BV_APPEND_R(bv, "myCompoundPart1_Part2", "description...", number_of_fields, type1, type2, ...);

Existing types

type description
bt_int_8 8-bit signed integer
bt_int_16 16-bit signed integer
bt_int_32 32-bit signed integer
bt_int_64 64-bit signed integer
bt_uint_8 8-bit unsigned integer
bt_uint_16 16-bit unsigned integer
bt_uint_32 32-bit unsigned integer
bt_uint_64 64-bit unsigned integer
bt_hex_8 8-bit hexadecimal number (unsigned integer)
bt_hex_16 16-bit hexadecimal number (unsigned integer)
bt_hex_32 32-bit hexadecimal number (unsigned integer)
bt_hex_64 64-bit hexadecimal number (unsigned integer)
bt_float Floating point number
bt_double Double precision floating point number
bt_char Single character
bt_string String
bt_string_class Unquoted string
bt_flow_direction A or B (see FLOW_DIR_[CS]_[AB])
bt_timestamp Date whose representation depends on B2T_TIMESTR in utils/bin2txt.h
bt_duration One uint64_t value for the seconds and one uint32_t value for the micro-/nano-seconds
bt_mac_addr MAC address
bt_ip4_addr IPv4 address
bt_ip6_addr IPv6 address
bt_ipx_addr IP address: version (8 bits), address (0, 4 or 16 bytes)

Generating flow file output in t2OnFlowTerminate()

Unsigned integers, bitfields and hexadecimal numbers (8, 16, 32 and 64 bits)

uint8_t val8 = 0;
OUTBUF_APPEND_U8(buf, val8);

uint16_t val16 = 0;
OUTBUF_APPEND_U16(buf, val16);

uint32_t val32 = 0;
OUTBUF_APPEND_U32(buf, val32);

uint64_t val64 = 0;
OUTBUF_APPEND_U64(buf, val64);

// Automatically converted from network to host order

OUTBUF_APPEND_U16_NTOH(buf, val16);
OUTBUF_APPEND_U32_NTOH(buf, val32);
OUTBUF_APPEND_U64_NTOH(buf, val64);

// Special cases: append unsigned value 0

OUTBUF_APPEND_U8_ZERO(buf);
OUTBUF_APPEND_U16_ZERO(buf);
OUTBUF_APPEND_U32_ZERO(buf);
OUTBUF_APPEND_U64_ZERO(buf);

Signed integers (8, 16, 32 and 64 bits)

int8_t val8 = 0;
OUTBUF_APPEND_I8(buf, val8);

int16_t val16 = 0;
OUTBUF_APPEND_I16(buf, val16);

int32_t val32 = 0;
OUTBUF_APPEND_I32(buf, val32);

int64_t val64 = 0;
OUTBUF_APPEND_I64(buf, val64);

// Special cases: append signed value 0

OUTBUF_APPEND_I8_ZERO(buf);
OUTBUF_APPEND_I16_ZERO(buf);
OUTBUF_APPEND_I32_ZERO(buf);
OUTBUF_APPEND_I64_ZERO(buf);

Floating point (float, double)

float valf = 0.0f;
OUTBUF_APPEND_FLT(buf, valf);

double vald = 0.0;
OUTBUF_APPEND_DBL(buf, vald);

// Special cases: append floating point value 0

OUTBUF_APPEND_FLT_ZERO(buf);
OUTBUF_APPEND_DBL_ZERO(buf);

MAC

MAC addresses structures.

uint8_t mac[ETH_ALEN] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55 };
OUTBUF_APPEND_MAC(buf, mac);

// Special case: append MAC address 00:00:00:00:00:00

OUTBUF_APPEND_MAC_ZERO(buf);

IPv4, IPv6 and IPvX (IP version and IP) addresses

IP addresses structures

OUTBUF_APPEND_IP4(buf, ip);           // ip MUST be of type ipAddr_t or ip4Addr_t
OUTBUF_APPEND_IP6(buf, ip);           // ip MUST be of type ipAddr_t
OUTBUF_APPEND_IPV(buf, ip);           // ip MUST be of type ipVAddr_t
OUTBUF_APPEND_IPVX(buf, version, ip); // ip MUST be of type ipAddr_t or ip4Addr_t

// Alternatively:

uint32_t val32 = 0x04030201;
OUTBUF_APPEND_U32(buf, val32);        // val MUST be an IPv4 address (uint32_t) in network order

uint8_t ip6[16] = {};
OUTBUF_APPEND(buf, ip6, sizeof(ip6)); // ip6 MUST be an IPv6 address (uint8_t array of size 16) in network order

// Special cases: append IPv4 address 0.0.0.0 or IPv6 address ::

OUTBUF_APPEND_IP4_ZERO(buf);
OUTBUF_APPEND_IP6_ZERO(buf);

Timestamp, duration

uint64_t sec = 0;
uint32_t usec = 0;
OUTBUF_APPEND_TIME(buf, sec, usec);

// Seconds only

OUTBUF_APPEND_TIME_SEC(buf, sec);

// Special case: append timestamp 0

OUTBUF_APPEND_TIME_ZERO(buf);

Characters, strings and string classes (strings without quotes)

char *str = "string";
OUTBUF_APPEND_STR(buf, str);

// If there is no string to append:

OUTBUF_APPEND_STR_EMPTY(buf);
//OUTBUF_APPEND_STR(buf, "");  // Alternative

// If the string needs to be free'd:

char *str = strdup("string");
OUTBUF_APPEND_STR_AND_FREE(buf, str); // Also works if str is NULL

If the type of the column varies

#if VAL_UINT8 == 1
uint8_t val = 0;
#else // VAL_UINT8 == 0
uint16_t val = 0;
#endif // VAL_UINT8 == 0
OUTBUF_APPEND(buf, val, sizeof(val));

Repetitive values

For repetitive values, use one of the OUTBUF_APPEND_ARRAY_*() macros

uint32_t size = 3; // size MUST be a uint32_t variable

uint8_t array[size] = {};
OUTBUF_APPEND_ARRAY_U8(buf , array, size);

uint16_t array[size] = {};
OUTBUF_APPEND_ARRAY_U16(buf, array, size);

uint32_t array[size] = {};
OUTBUF_APPEND_ARRAY_U32(buf, array, size);

uint64_t array[size] = {};
OUTBUF_APPEND_ARRAY_U64(buf, array, size);

int8_t array[size] = {};
OUTBUF_APPEND_ARRAY_I8(buf , array, size);

int16_t array[size] = {};
OUTBUF_APPEND_ARRAY_I16(buf, array, size);

int32_t array[size] = {};
OUTBUF_APPEND_ARRAY_I32(buf, array, size);

int64_t array[size] = {};
OUTBUF_APPEND_ARRAY_I64(buf, array, size);

float array[size] = {};
OUTBUF_APPEND_ARRAY_FLT(buf, array, size);

double array[size] = {};
OUTBUF_APPEND_ARRAY_DBL(buf, array, size);

uint8_t array[size][ETH_ALEN] = {};
OUTBUF_APPEND_ARRAY_MAC(buf, array, size);

ip4Addr_t array[size] = {}; // or ipAddr_t array[size];
OUTBUF_APPEND_ARRAY_IP4(buf, array, size);

ipAddr_t array[size] = {};
OUTBUF_APPEND_ARRAY_IP6(buf, array, size);

ipVAddr_t array[size] = {};
OUTBUF_APPEND_ARRAY_IPV(buf, array, size);

ipAddr_t array[size] = {};
const uint_fast8_t version = FLOW_IPVER(flowP);
OUTBUF_APPEND_ARRAY_IPVX(buf, version, array, size);

char *array[size] = {};
OUTBUF_APPEND_ARRAY_STR(buf, array, size);

char *array[size] = {};
array[0] = strdup("string1");
array[1] = strdup("string2");
OUTBUF_APPEND_ARRAY_STR_AND_FREE(buf, array, size);

Optional strings (repetitive with 0 or 1 repetition)

char *str = "andy";
OUTBUF_APPEND_OPT_STR(buf, str);
//=> OUTBUF_APPEND_NUMREP(buf, ONE);
//=> OUTBUF_APPEND_STR(buf, str);

char *str = "";
OUTBUF_APPEND_OPT_STR(buf, str);
//=> OUTBUF_APPEND_NUMREP(buf, ZERO);

More complex cases of repetitive values

For more complex cases of repetitive values, start by adding the number of repetitions, then add the values in a for loop

uint32_t size = 5;
OUTBUF_APPEND_NUMREP(buf, size);
//OUTBUF_APPEND_U32(buf, size);  // Alternative
for (uint_fast32_t i = 0; i < size; i++) {
    // Append repetition 'i'
    //OUTBUF_APPEND...(buf, array[i]);
}

Special cases: 0 or 1 repetition

OUTBUF_APPEND_NUMREP_ZERO(buf);

OUTBUF_APPEND_NUMREP_ONE(buf);
OUTBUF_APPEND...(buf, ...) // append the actual value

Adding support for environment/runtime configuration flags

  • In your header file, e.g., myPlugin.h:

    /* ========================================================================== */
    /* ------------------------ USER CONFIGURATION FLAGS ------------------------ */
    /* ========================================================================== */
    
    ...
    
    /* +++++++++++++++++++++ ENV / RUNTIME - conf Variables +++++++++++++++++++++ */
    
    #define MYPLUGIN_FLAG_UINT  1     // Description
    #define MYPLUGIN_FLAG_INT  -1     // Description
    #define MYPLUGIN_FLAG_STR  "text" // Description
    
    /* ========================================================================== */
    /* ------------------------- DO NOT EDIT BELOW HERE ------------------------- */
    /* ========================================================================== */
    
    
    // The elements in the enum below MUST be in the same order as above
    enum {
        ENV_MYPLUGIN_FLAG_UINT, // Name of the configurable flag, preceded with ENV_
        ENV_MYPLUGIN_FLAG_INT,
        ENV_MYPLUGIN_FLAG_STR,
        ENV_MYPLUGIN_N          // Sentinel to be used as number of environment/runtime config flags
    };
  • In your source file, e.g., myPlugin.c:

    • Case 1: declare global/local variables:

      #if ENVCNTRL > 0
      t2_env_t env[ENV_MYPLUGIN_N] = {};                                // => !! This array MUST be called env for the macros to work
      t2_get_env(PLUGIN_SRCH, ENV_MYPLUGIN_N, env);
      const uint32_t flag_uint = T2_ENV_VAL_UINT(MYPLUGIN_FLAG_UINT):
      const int32_t flag_int = T2_ENV_VAL_INT(MYPLUGIN_FLAG_UINT):
      const char * const flag_str = T2_ENV_VAL(MYPLUGIN_FLAG_STR);      // => !! flag_str will be free'd when t2_free_env() is called
      // To preserve the value of flag_str after the call to t2_free_env() use one of the next two instructions:
      //char * const flag_str = T2_STEAL_ENV_VAL(MYPLUGIN_FLAG_STR);    // => !! flag_str MUST be free'd with free()
      //char * const flag_str = T2_DUP_ENV_VAL(MYPLUGIN_FLAG_STR);      // => !! flag_str MUST be free'd with free()
      #else // ENVCNTRL == 0
      const uint32_t flag_uint = MYPLUGIN_FLAG_UINT:
      const int32_t flag_int = MYPLUGIN_FLAG_UINT:
      const char * const flag_str = MYPLUGIN_FLAG_STR;
      #endif // ENVCNTRL
      
      ...
      
      // Use flag_uint, flag_int and flag_str as normal variables
      
      ...
      
      #if ENVCNTRL > 0
      // This releases the memory used by env variables
      // => flag_str will NOT point to a valid address after this call!!
      t2_free_env(ENV_MYPLUGIN_N, env);
      #endif
    • Case 2: use the env array to access variables:

      t2_env_t env[ENV_MYPLUGIN_N] = {};  // => !! This array MUST be called env for the macros to work
      
      #if ENVCNTRL > 0
      t2_get_env(PLUGIN_SRCH, ENV_MYPLUGIN_N, env);
      #else // ENVCNTRL == 0
      T2_SET_ENV_NUM(MYPLUGIN_FLAG_UINT);
      T2_SET_ENV_NUM(MYPLUGIN_FLAG_INT);
      T2_SET_ENV_STR(MYPLUGIN_FLAG_STR);
      #endif // ENVCNTRL
      
      ...
      
      // Access the variables with the same macros as before:
      // - T2_ENV_VAL_UINT(MYPLUGIN_FLAG_UINT)
      // - T2_ENV_VAL_INT(MYPLUGIN_FLAG_INT)
      // - T2_ENV_VAL(MYPLUGIN_FLAG_STR)
      
      ...
      
      #if ENVCNTRL > 0
      // This releases the memory used by env variables
      t2_free_env(ENV_MYPLUGIN_N, env);
      #endif // ENVCNTRL > 0

Using T2 file manager

  • Opening a file:

    const char * const filename = "/path/to/file.txt";
    file_object_t *file_object = file_manager_open(t2_file_manager, filename, "w");
  • Writing a single character:

    file_manager_fputc(t2_file_manager, file_object, '\n');
    
    // Alternative:
    
    FILE * const file = file_manager_fp(t2_file_manager, file_object);
    fputc('\n', file);
  • Writing text:

    file_manager_fputs(t2_file_manager, file_object, "Hello Andy!\n");
    
    // Alternative:
    
    FILE * const file = file_manager_fp(t2_file_manager, file_object);
    fputs("Hello Andy!\n", file);
  • Writing formatted text:

    file_manager_fprintf(t2_file_manager, file_object, "Hello %s!\n", "Andy");
    
    // Alternative:
    
    FILE * const file = file_manager_fp(t2_file_manager, file_object);
    fprintf(file, "Hello %s!\n", "Andy");
  • Writing binary:

    const uint8_t * const ptr = packet->l7HdrP;
    const uint16_t snaplen = packet->snapL7Len;
    
    file_manager_fwrite(t2_file_manager, file_object, ptr, snaplen, 1);
    
    // Alternative:
    
    FILE * const file = file_manager_fp(t2_file_manager, file_object);
    fwrite(ptr, snaplen, 1, file);
  • Getting the filename:

    const char * const path = file_object_get_path(file_object);
  • Closing the file:

    file_manager_close(t2_file_manager, file_object);

Parsing data with t2buf

  • Include the t2buf header

    #include "t2buf.h"
  • Let your build system know about t2buf.c:

    • meson.build:

      ...
      src = [
          join_paths('src', plugin_name + '.c'),
          join_paths('..', '..', 'utils', 't2buf.c'),
      ]
      ...
    • CMakeLists.txt:

      ...
      add_library(${_plugin_name}
          MODULE
              src/${_plugin_name}.c
              ../../utils/t2buf.c
      )
      ...
    • src/Makefile.am:

      ...
      libyourPluginName_la_SOURCES = \
          yourPluginName.c \
          ../../../utils/t2buf.c
      ...

Initialization in t2OnLayer2() or t2OnLayer4()

const uint16_t snaplen = packet->snapL7Len;
const uint8_t * const l7Hdr = packet->l7HdrP;
t2buf_t t2buf = t2buf_create(l7Hdr, snaplen);
while (t2buf_left(&t2buf) > 0) { /* Read some data */ }
// Get the current position in the buffer
const long pos = t2buf_tell(&t2buf);

// Get a pointer to the current position in the buffer
uint8_t *ptr = t2buf_ptr(&t2buf);

const long offset = 5;

// Seek from the start of the buffer
if (!t2buf_seek(&t2buf, offset, SEEK_SET)) return;  // => new position = 5

// Seek from the current position in the buffer
if (!t2buf_seek(&t2buf, offset, SEEK_CUR)) return;  // => new position = pos + 5

// Seek from the end of the buffer
if (!t2buf_seek(&t2buf, -offset, SEEK_END)) return; // => new position = snaplen - 5

// Seek to the start of the buffer
if (!t2buf_rewind(&t2buf)) return;  // => new position = 0

// Seek to the start of the buffer (alternative)
if (!t2buf_seek(&t2buf, 0, SEEK_SET)) return;  // => new position = 0

Reading data

uint8_t  u8;
uint16_t u16;
uint32_t u32;
uint64_t u64;

// Big endian (network order)

if (!t2buf_read_u8( &t2buf, &u8))  return;
if (!t2buf_read_u16(&t2buf, &u16)) return;
if (!t2buf_read_u24(&t2buf, &u32)) return;
if (!t2buf_read_u32(&t2buf, &u32)) return;
if (!t2buf_read_u48(&t2buf, &u64)) return;
if (!t2buf_read_u64(&t2buf, &u64)) return;

uint8_t ip[16];
if (!t2buf_read_n(&t2buf, ip, sizeof(ip))) return;

// Little endian

if (!t2buf_read_le_u8( &t2buf, &u8))  return;
if (!t2buf_read_le_u16(&t2buf, &u16)) return;
if (!t2buf_read_le_u24(&t2buf, &u32)) return;
if (!t2buf_read_le_u32(&t2buf, &u32)) return;
if (!t2buf_read_le_u48(&t2buf, &u64)) return;
if (!t2buf_read_le_u64(&t2buf, &u64)) return;

Peeking into data (reading without consuming any bytes)

uint8_t  u8;
uint16_t u16;
uint32_t u32;
uint64_t u64;

// Big endian (network order)

if (!t2buf_peek_u8( &t2buf, &u8))  return;
if (!t2buf_peek_u16(&t2buf, &u16)) return;
if (!t2buf_peek_u24(&t2buf, &u32)) return;
if (!t2buf_peek_u32(&t2buf, &u32)) return;
if (!t2buf_peek_u48(&t2buf, &u64)) return;
if (!t2buf_peek_u64(&t2buf, &u64)) return;

// Little endian

if (!t2buf_peek_le_u16(&t2buf, &u16)) return;
if (!t2buf_peek_le_u24(&t2buf, &u32)) return;
if (!t2buf_peek_le_u32(&t2buf, &u32)) return;
if (!t2buf_peek_le_u48(&t2buf, &u64)) return;
if (!t2buf_peek_le_u64(&t2buf, &u64)) return;

String encodings

const t2buf_encoding encoding = T2BUF_ASCII;
const t2buf_encoding encoding = T2BUF_ANSI;
const t2buf_encoding encoding = T2BUF_UTF8;
const t2buf_encoding encoding = T2BUF_UTF16;
const t2buf_encoding encoding = T2BUF_UTF16_LE;

Read strings

#define MYDST_SIZE 5                            // includes NULL terminator
uint8_t dst[MYDST_SIZE] = {};
const bool ignore_errors = false;               // continue reading on encoding errors
                                                // (invalid characters are replaced with '.')
const long read = t2buf_readstr(&t2buf, dst, MYDST_SIZE, encoding, ignore_errors);
//const long read = t2buf_readnstr(&t2buf, dst, MYDST_SIZE, (size_t)((MYDST_SIZE-1) / 2), T2BUF_UTF16, true);
if (read == T2BUF_EMPTY) return;                // Nothing was read
else if (read == T2BUF_DST_FULL) return;        // Destination buffer was not big enough
else if (read == T2BUF_ENC_ERROR) exit(1);      // Provided encoding parameter not supported or unknown

Hexdecode bytes

const size_t src_bytes = 4; // Number of source bytes to read
//char hex[2 * src_bytes + 1];
//char sep = 0;   // No separator
char hex[3 * src_bytes];
char sep = ':';
const size_t read = t2buf_hexdecode(&t2buf, src_bytes, hex, sep); // 01:02:03:04
if (read != src_bytes) return; // Could not read enough bytes => snapped

Read entire lines

#define MYLINELEN 64                     // includes NULL terminator
uint8_t line[MYLINELEN];
//bool trim = false;                     // Store the line return in line
bool trim = true;                        // Do not store the line return in line
const long read = t2buf_readline(&t2buf, line, MYLINELEN, trim);
if (read == T2BUF_EMPTY) return;         // Nothing was read
else if (read == T2BUF_DST_FULL) return; // Destination buffer was not big enough
else if (read == T2BUF_NULL) return;     // NULL character was encountered before end of line

Skipping data

if (!t2buf_skip_u8( &t2buf)) return;
if (!t2buf_skip_u16(&t2buf)) return;
if (!t2buf_skip_u24(&t2buf)) return;
if (!t2buf_skip_u32(&t2buf)) return;
if (!t2buf_skip_u48(&t2buf)) return;
if (!t2buf_skip_u64(&t2buf)) return;

const size_t n = 32;
if (!t2buf_skip_n(&t2buf, n)) return;

// Skip strings

const long skipped = t2buf_skipstr(&t2buf, encoding); // 'skipped' bytes were skipped
if (skipped == T2BUF_EMPTY) return;                   // Nothing was read
else if (skipped == T2BUF_ENC_ERROR) exit(1);         // Provided encoding parameter not supported or unknown

const long skipped = t2buf_skipnstr(&t2buf, skipped_max, encoding); // 'skipped' bytes were skipped (<= skipped_max)
if (skipped == T2BUF_EMPTY) return;                                 // Nothing was read
else if (skipped == T2BUF_ENC_ERROR) exit(1);                       // Provided encoding parameter not supported or unknown

// Skip entire lines

const long skipped = t2buf_skipline(&t2buf); // 'skipped' bytes were skipped
if (skipped == T2BUF_EMPTY) return;          // Nothing was skipped

const size_t skipped_max = 5;
const long skipped = t2buf_skipnline(&t2buf, skipped_max); // 'skipped' bytes were skipped (<= skipped_max)
if (skipped == T2BUF_EMPTY) return;                        // Nothing was skipped

Computing lengths

const long len = t2buf_strlen(&t2buf, T2BUF_UTF8); // NULL character is included in 'len'

const long len = t2buf_linelen(&t2buf);  // 'len' includes terminating character(s)
if (len == T2BUF_EMPTY) return;          // Nothing was read

Searching for patterns

const void *needle = "ANTEATER";
const size_t needle_len = strlen(needle);
const bool found = t2buf_memmem(&t2buf, needle, needle_len); // search for 'needle' of 'needle_len' in the buffer
if (found) {
    // t2buf now points to the start of needle
}

Structures

flow_t

The flow_t structure is defined in tranalyzer2/src/flow.h as follows:

typedef struct flow_s {
    // Pointers to the next and previous flow in LRU list
    struct flow_s *  lruNextFlow;
    struct flow_s *  lruPrevFlow;

    uint64_t         findex;                // flow index
    uint64_t         status;                // status of flow, e.g., fragmentation processing

    unsigned long    flowIndex;
    unsigned long    oppositeFlowIndex;

#if (SCTP_ACTIVATE > 0 && SCTP_STATFINDEX == 1)
    unsigned long    sctpFindex;
#endif // (SCTP_ACTIVATE > 0 && SCTP_STATFINDEX == 1)

    struct timeval   lastSeen;   // last time we've seen this flow
    struct timeval   firstSeen;  // first time we've seen this flow
    struct timeval   duration;   // lastSeen - firstSeen
                                 // (NOT available before flow completely terminated)

    /* --------------------------------------------------------------------- */
    /* Begin flow identification                                             */
    /* --------------------------------------------------------------------- */

#if IPV6_ACTIVATE > 0
    ipAddr_t         srcIP;
    ipAddr_t         dstIP;
#else // IPV6_ACTIVATE == 0
    ip4Addr_t        srcIP;
    ip4Addr_t        dstIP;
#endif // IPV6_ACTIVATE == 0

#if ETH_ACTIVATE > 0
    ethDS_t          ethDS;
#endif

#if (IPV6_ACTIVATE == 2 || ETH_ACTIVATE > 0 || LAPD_ACTIVATE == 1)
    uint16_t         ethType;
#endif

    uint16_t         vlanId;

    union {
        struct {
            uint16_t srcPort;
            uint16_t dstPort;
        };
        uint32_t     fragID;
    };

#if SCTP_ACTIVATE & 2
    uint32_t         sctpVtag;
#endif // SCTP_ACTIVATE & 2

#if SCTP_ACTIVATE & 1
    uint16_t         sctpStrm;
#endif // SCTP_ACTIVATE & 1

    uint8_t          l4Proto;

    /* --------------------------------------------------------------------- */
    /* End flow identification                                               */
    /* --------------------------------------------------------------------- */

    float            timeout;               // timeout of this flow in seconds

    uint32_t         lastIPID;              // for duplicate IP ID detection

#if SUBNET_INIT != 0
    uint32_t         subnetNrSrc;
    uint32_t         subnetNrDst;
#endif // SUBNET_INIT != 0

    // for fragPend hash cleanup
#if IPV6_ACTIVATE > 0
    uint32_t        lastFragIPID;
#else // IPV6_ACTIVATE == 0
    uint16_t        lastFragIPID;
#endif // IPV6_ACTIVATE == 0

} __attribute__((packed)) flow_t;

packet_t

The packet_t structure is defined in tranalyzer2/src/packet.h as follows:

typedef struct {
#if (FDURLIMIT > 0 && FDLSFINDEX == 1)
    uint64_t findex;
#endif //(FDURLIMIT > 0 && FDLSFINDEX == 1)

    uint64_t status;

    /* --------------------------------------------------------------------- */
    /* Pointers (network order)                                              */
    /* --------------------------------------------------------------------- */

    const struct pcap_pkthdr * const pcapHdrP;

    const uint8_t * const   raw_packet;    // Beginning of the packet
    const uint8_t * const   end_packet;    // Beyond the end of the packet
                                           // (raw_packet + snapLen);

    const uint8_t *         l2HdrP;        // Layer 2 (Ethernet, LLC, LAPD, ...)
    const uint8_t *         l3HdrP;        // Layer 3 (IPv4, IPv6, ...)
    const uint8_t *         l4HdrP;        // Layer 4 (TCP, UDP, ICMP, IGMP, SCTP, ...)
    const uint8_t *         l7HdrP;        // Layer 7 (payload)

    const etherLLCHeader_t* etherLLC;      // Ethernet header LLC part if present (set but not used)
    const pppHu_t *         pppHdrP;       // PPP header
    const pppoEH_t *        pppoeHdrP;     // PPPoE header
    const uint32_t *        mplsHdrP;      // MPLS pointer
    const uint32_t *        vlanHdrP;      // VLAN pointer

    // GRE headers
    const greHeader_t *     greHdrP;       // GRE v1,2 header
    const uint8_t     *     greL3HdrP;     // L3 header before GRE header

    // GTP headers
    const uint8_t *         gtpHdrP;       // GTP v0,1,2 header (set but no used)

    // L2TP headers
    const uint16_t *        l2tpHdrP;      // L2TPv2 header
    const uint8_t  *        l2tpL3HdrP;    // L3 header before L2TP header

    // IPv6 headers
    const ip6OptHdr_t *     ip6HHOptHdrP;  // IPv6 Hop-by-Hop Option header
    const ip6OptHdr_t *     ip6DOptHdrP;   // IPv6 Destination Option header
    const ip6FragHdr_t *    ip6FragHdrP;   // IPv6 Fragment header
    const ip6RouteHdr_t *   ip6RouteHdrP;  // IPv6 Routing header (set but not used)

    // Teredo headers
    const uint8_t *         trdoOIHdrP;    // Teredo Origin Indication header
    const uint8_t *         trdoAHdrP;     // Teredo Authentication header

#if SCTP_ACTIVATE > 0
    const uint8_t *         l7SctpHdrP;    // First SCTP payload
#endif // SCTP_ACTIVATE > 0

    /* --------------------------------------------------------------------- */
    /* Lengths (host order)                                                  */
    /*   - Snap lengths can be truncated due to limited snaplength           */
    /*     (derived by header dissection)                                    */
    /* --------------------------------------------------------------------- */

    const uint32_t          rawLen;          // extracted from pcapHdrP
    uint32_t                l2Len;           // derived from IP header length field + length of L2 header (set but not used)
    uint32_t                len;             // derived from IP header length field, defined by PACKETLENGTH in packetCapture.h:
                                             //   0: Including L2-4 header,
                                             //   1: including L3-4 header,
                                             //   2: Including L4 header,
                                             //   3: Only payload L7

    // Snap lengths (can be truncated due to limited snaplength, derived by header dissection)
    const uint32_t          snapLen;         // extracted from pcapHdrP
    uint32_t                snapL2Len;       // includes L2 header
    uint32_t                snapL3Len;       // includes L3 header
    uint16_t                snapL4Len;       // includes L4 header
    uint16_t                snapL7Len;       // only higher packet payload (L7)

    uint16_t                l7Len;           // L7 length

#if SCTP_ACTIVATE > 0
    uint16_t                snapSctpL7Len;   // only higher packet payload (L7)
#endif // SCTP_ACTIVATE > 0

    uint16_t                l2HdrLen;        // set but not used
    uint16_t                l3HdrLen;
    uint16_t                l4HdrLen;

    /* --------------------------------------------------------------------- */
    /* Flow identification (IP addresses, ports and protocols (host order),  */
    /* headers count and description, ...                                    */
    /* --------------------------------------------------------------------- */

#if IPV6_ACTIVATE > 0
    ipAddr_t                srcIP;
    ipAddr_t                dstIP;
#else // IPV6_ACTIVATE == 0
    ip4Addr_t               srcIP;
    ip4Addr_t               dstIP;
#endif // IPV6_ACTIVATE == 0

#if ((SUBNET_INIT != 0) || (AGGREGATIONFLAG & (SUBNET | SRCIP | DSTIP)))
#if IPV6_ACTIVATE > 0
    ipAddr_t                srcIPC;
    ipAddr_t                dstIPC;
#else // IPV6_ACTIVATE == 0
    ip4Addr_t               srcIPC;
    ip4Addr_t               dstIPC;
#endif // IPV6_ACTIVATE == 0

    uint32_t                subnetNrSrc;
    uint32_t                subnetNrDst;
    uint16_t                srcPortC;
    uint16_t                dstPortC;
    uint8_t                 l4ProtoC;
#endif // ((SUBNET_INIT != 0) || (AGGREGATIONFLAG & (SUBNET | SRCIP | DSTIP)))

    uint16_t                vlanId;
    uint16_t                srcPort;
    uint16_t                dstPort;
    uint16_t                ethType;
    uint16_t                outerEthType;
    uint16_t                l3Proto;                    // IPv4, IPv6, L2TPv2/3, ...

    // Headers description, e.g., eth:ipv4:tcp
#if T2_PRI_HDRDESC == 1
    uint16_t                numHdrDesc;                 // Number of headers description
    uint16_t                hdrDescPos;                 // Headers description position
    char                    hdrDesc[T2_HDRDESC_LEN];    // Headers description
#endif // T2_PRI_HDRDESC == 1

    uint8_t                 l4Proto;                    // TCP, UDP, ICMP, IGMP, ...
    uint8_t                 mplsHdrCnt;
    uint8_t                 vlanHdrCnt;

#if SCTP_ACTIVATE > 0
    uint8_t                 sctpPad;                    // SCTP padding of content
#endif // SCTP_ACTIVATE > 0

} packet_t;

MAC addresses

The ethDS_t structure is defined in tranalyzer2/src/networkHeaders.h as follows:

typedef struct {
    uint8_t ether_dhost[ETH_ALEN]; // destination eth addr
    uint8_t ether_shost[ETH_ALEN]; // source ether addr
} __attribute__((packed)) ethDS_t;

IP addresses

The ipAddr_t, ipVAddr_t and ip4Addr_t, structures are defined in tranalyzer2/src/ipaddr.h as follows:

typedef union {
    uint32_t        IPv4x[4];
    struct in_addr  IPv4;       // IPv4 address
    struct in6_addr IPv6;       // IPv6 address
    uint64_t        IPv6L[2];   // IPv6 address 2*64 bit max chunk for masking ops
} __attribute__((packed)) ipAddr_t;

typedef struct {
    uint8_t  ver;    // version
    ipAddr_t addr;
} ipVAddr_t;

typedef union {
    uint32_t       IPv4x[1];
    struct in_addr IPv4;
} __attribute__((packed)) ip4Addr_t;

ethernetHeader_t

The ethernetHeader_t structure is defined in tranalyzer2/src/networkHeaders.h as follows:

typedef struct {
    ethDS_t  ethDS;
    uint16_t ether_type; // packet type ID field or length
    uint16_t data;
} __attribute__((packed)) ethernetHeader_t;

ipHeader_t

The ipHeader_t structure is defined in tranalyzer2/src/proto/ipv4.h as follows:

#define IP_V(ip) (((ip)->ip_vhl & 0xf0) >> 4) // Version
#define IP_HL(ip) ((ip)->ip_vhl & 0x0f)       // Header length

...

typedef struct {
    uint8_t        ip_vhl;  // version, header length
    uint8_t        ip_tos;  // type of service
    uint16_t       ip_len;  // total length
    uint16_t       ip_id;   // identification
    uint16_t       ip_off;  // fragment offset field
    uint8_t        ip_ttl;  // time to live
    uint8_t        ip_p;    // protocol
    uint16_t       ip_sum;  // checksum
    struct in_addr ip_src;  // source address
    struct in_addr ip_dst;  // destination address
} __attribute__((packed)) ipHeader_t;

ip6Header_t

The ip6Header_t structure is defined in tranalyzer2/src/proto/ipv6.h as follows:

typedef struct {
    uint32_t    vtc_flw_lbl;  // first word: ver, tcl, flow
    uint16_t    payload_len;  // payload length
    uint8_t     next_header;  // next protocol
    union {
        uint8_t hop_limit;    // hop limit
        uint8_t ip_ttl;       // TTL
    };
    ipAddr_t    ip_src;       // source address
    ipAddr_t    ip_dst;       // destination address
} __attribute__((packed)) ip6Header_t;

tcpHeader_t

The tcpHeader_t structure is defined in tranalyzer2/src/proto/tcp.h as follows:

typedef struct {
    uint16_t source;
    uint16_t dest;
    uint32_t seq;
    uint32_t ack_seq;
    uint8_t  res1:4;
    uint8_t  doff:4;
    uint8_t  flags;
    uint16_t window;
    uint16_t check;
    uint16_t urg_ptr;
} __attribute__((packed)) tcpHeader_t;

udpHeader_t

The udpHeader_t structure is defined in tranalyzer2/src/proto/udp.h as follows:

typedef struct {
    uint16_t source;
    uint16_t dest;
    uint16_t len;
    uint16_t check;
} __attribute__((packed)) udpHeader_t;

icmpHeader_t

The icmpHeader_t structure is defined in tranalyzer2/src/proto/icmp.h as follows:

typedef struct {
    uint8_t  type;  // message type
    uint8_t  code;  // type sub-code
    uint16_t checksum;
    union {
        // echo datagram
        struct {
            uint16_t id;
            uint16_t sequence;
        } echo;
        // gateway address
        uint32_t gateway;
        // path mtu discovery
        struct {
            uint16_t unused;
            uint16_t mtu;
        } frag;
    };
} __attribute__((packed)) icmpHeader_t;

sctpHeader_t

The sctpHeader_t structure is defined in tranalyzer2/src/sctp.h as follows:

typedef struct {
    uint16_t source;    // Source port
    uint16_t dest;      // Destination port
    uint32_t verTag;    // Verification tag
    uint32_t chkSum;    // Checksum
    uint32_t data;
} __attribute__((packed)) sctpHeader_t;

Reporting errors, warnings and other information

char *msg = "text";
  • The following macros output to stdout or to the _log.txt file if t2 -l option is used:

    T2_PLOG("pluginName", "Your message is %s", msg); // Message (black) : pluginName: Your message is text
    T2_PINF("pluginName", "Your message is %s", msg); // Info    (blue)  : [INF] pluginName: Your message is text
    T2_POK( "pluginName", "Your message is %s", msg); // OK      (green) : [OK] pluginName: Your message is text
    T2_PWRN("pluginName", "Your message is %s", msg); // Warning (orange): [WRN] pluginName: Your message is text
    
    // The following macro always output to stderr
    
    T2_PERR("pluginName", "Your message is %s", msg); // Error   (red)   : [ERR] pluginName: Your message is text
  • If you do not want to output the [INF], [OK], [WRN] or [ERR] prefix:

    T2_PINF_NP("pluginName", "Your message is %s", msg); // Info    (blue)  : pluginName: Your message is text
    T2_POK_NP( "pluginName", "Your message is %s", msg); // OK      (green) : pluginName: Your message is text
    T2_PWRN_NP("pluginName", "Your message is %s", msg); // Warning (orange): pluginName: Your message is text
    
    // The following macro always output to stderr
    
    T2_PERR_NP("pluginName", "Your message is %s", msg); // Error   (red)   : pluginName: Your message is text
  • Alternatively, specify the output stream:

    FILE *stream = stdout;
    
    T2_FPLOG(stream, "pluginName", "Your message is %s", msg); // Message (black) : pluginName: Your message is text
    T2_FPINF(stream, "pluginName", "Your message is %s", msg); // Info    (blue)  : [INF] pluginName: Your message is text
    T2_FPOK( stream, "pluginName", "Your message is %s", msg); // OK      (green) : [OK] pluginName: Your message is text
    T2_FPWRN(stream, "pluginName", "Your message is %s", msg); // Warning (orange): [WRN] pluginName: Your message is text
    T2_FPERR(stream, "pluginName", "Your message is %s", msg); // Error   (red)   : [ERR] pluginName: Your message is text
  • If you do not want to output the [INF], [OK], [WRN] or [ERR] prefix:

    T2_FPINF_NP(stream, "pluginName", "Your message is %s", msg); // Info    (blue)  : pluginName: Your message is text
    T2_FPOK_NP( stream, "pluginName", "Your message is %s", msg); // OK      (green) : pluginName: Your message is text
    T2_FPWRN_NP(stream, "pluginName", "Your message is %s", msg); // Warning (orange): pluginName: Your message is text
    T2_FPERR_NP(stream, "pluginName", "Your message is %s", msg); // Error   (red)   : pluginName: Your message is text

Reporting aggregated bitfields

  • pluginName.h:

    ...
    
    typedef struct {
          uint8_t  hex8;
          uint16_t hex16;
          uint32_t hex32;
          uint64_t hex64;
    } pluginNameFlow_t *pnFlows;
    
    ...
  • pluginName.c:

    ...
    
    // Global variables for aggregated bitfields
    uint8_t  myProtHex8;
    uint16_t myProtHex16;
    uint32_t myProtHex32;
    uint64_t myProtHex64;
    
    ...
    
    void t2OnFlowTerminate(unsigned long flowIndex, outputBuffer_t *buf) {
        pluginNameFlow_t *pnFlowP = &pnFlows[flowIndex];
        ...
        // Aggregate flow bitfields into global bitfields
        myProtHex8  |= pnFlowP->hex8;
        myProtHex16 |= pnFlowP->hex16;
        myProtHex32 |= pnFlowP->hex32;
        myProtHex64 |= pnFlowP->hex64;
    }
    
    void t2PluginReport(FILE *stream) {
        // The T2_FPLOG_AGGR_HEX() macro use sizeof() to determine the size of the variable to print
        T2_FPLOG_AGGR_HEX(stream, plugin_name, myProtHex8);  // pluginName: Aggregated myProtHex8=0xff
        T2_FPLOG_AGGR_HEX(stream, plugin_name, myProtHex16); // pluginName: Aggregated myProtHex16=0xffff
        T2_FPLOG_AGGR_HEX(stream, plugin_name, myProtHex32); // pluginName: Aggregated myProtHex32=0xffffffff
        T2_FPLOG_AGGR_HEX(stream, plugin_name, myProtHex64); // pluginName: Aggregated myProtHex64=0xffffffffffffffff
    
        // If the value is known to be different from 0, use T2_FPLOG_AGGR_HEX0() to omit the test for val > 0
        if (myProtHex8)  T2_FPLOG_AGGR_HEX0(stream, plugin_name, myProtHex8);  // pluginName: Aggregated myProtHex8=0xff
        if (myProtHex16) T2_FPLOG_AGGR_HEX0(stream, plugin_name, myProtHex16); // pluginName: Aggregated myProtHex16=0xffff
        if (myProtHex32) T2_FPLOG_AGGR_HEX0(stream, plugin_name, myProtHex32); // pluginName: Aggregated myProtHex32=0xffffffff
        if (myProtHex64) T2_FPLOG_AGGR_HEX0(stream, plugin_name, myProtHex64); // pluginName: Aggregated myProtHex64=0xffffffffffffffff
    
        // Alternatively, if you want to enforce a specific size
        T2_FPLOG_AGGR_H8( stream, plugin_name, myProtHex8);  // pluginName: Aggregated myProtHex8=0xff
        T2_FPLOG_AGGR_H16(stream, plugin_name, myProtHex16); // pluginName: Aggregated myProtHex16=0xffff
        T2_FPLOG_AGGR_H32(stream, plugin_name, myProtHex32); // pluginName: Aggregated myProtHex32=0xffffffff
        T2_FPLOG_AGGR_H64(stream, plugin_name, myProtHex64); // pluginName: Aggregated myProtHex64=0xffffffffffffffff
    }

Reporting hexadecimal numbers

  • T2 has a HEX_CAPITAL flag to output hex numbers in upper or lower case (default).

  • In order to respect this flag, use the following B2T_PRIX{8,16,32,64} macros:

    uint8_t hex8 = 0x12;
    T2_LOG("Aggregated hex8=0x%02" B2T_PRIX8, hex8);
    
    uint16_t hex16 = 0x1234;
    T2_LOG("Aggregated hex16=0x%04" B2T_PRIX16, hex16);
    
    uint32_t hex32 = 0x12345678;
    T2_LOG("Aggregated hex32=0x%08" B2T_PRIX32, hex32);
    
    uint64_t hex64 = 0x0123456789abcdef;
    T2_LOG("Aggregated hex64=0x%016" B2T_PRIX64, hex64);

Reporting numbers

  • Simplest way to report a number (converted to human readable if necessary):

    uint64_t num = 12345678;
    T2_PLOG_NUM("pluginName", "Your number is", num); // pluginName: Your number is 12345678 (12.35M)
    
    // Alternatively, specify the output stream:
    
    FILE *stream = stdout;
    T2_FPLOG_NUM(stream, "pluginName", "Your number is", num); // pluginName: Your number is 12345678 (12.35M)
  • Report the percentage as well:

    uint64_t num = 12345678;
    uint64_t tot = 987654321;
    T2_PLOG_NUMP("pluginName", "Your number is", num, tot); // pluginName: Your number is 12345678 (12.35M) [1.25%]
    
    // Alternatively, specify the output stream:
    
    FILE *stream = stdout;
    T2_FPLOG_NUMP(stream, "pluginName", "Your number is", num, tot); // pluginName: Your number is 12345678 (12.35M) [1.25%]
  • For more flexibility, convert numbers to human readable manually:

    uint64_t num = 12345678;
    char str[64];
    T2_CONV_NUM(num, str);
    printf("%" PRIu64 "%s\n", num, str); // 12345678 (12.35 M)
    
    T2_CONV_NUM_SFX(num, str, "b/s");
    printf("%" PRIu64 "%s\n", num, str); // 12345678 (12.35 Mb/s)
    
    t2_conv_readable_num(num, str, sizeof(str), "b/s");
    printf("%" PRIu64 "%s\n", num, str); // 12345678 (12.35 Mb/s)

Reporting MAC addresses

  • Convert a MAC address to string. Note that the actual representation depends on the value of MAC_FORMAT and MAC_SEP in utils/bin2txt.h (see table below).

    uint8_t mac[ETH_ALEN] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 };
    char dest[T2_MAC_STRLEN+1];
    t2_mac_to_str(mac, dest, sizeof(dest));
  • To force a specific representation use one of the functions below:

    Function MAC_FORMAT Output
    t2_mac_to_mac(mac, dest, sizeof(dest)) 0 01:02:03:04:05:06
    t2_mac_to_hex(mac, dest, sizeof(dest)) 1 0x0000010203040506
    t2_mac_to_uint(mac, dest, sizeof(dest)) 2 1108152157446

Reporting IP addresses

  • Convert an IPv4 address to string. Note that the actual representation depends on the value of IP4_FORMAT in utils/bin2txt.h (see table below).

    struct in_addr ipv4 = {};
    char dest[INET_ADDRSTRLEN];
    
    t2_ipv4_to_str(ipv4, dest, sizeof(dest));
  • To force a specific representation use one of the functions below:

    Function IP4_FORMAT Output
    t2_ipv4_to_compressed(ipv4, dest, sizeof(dest)) 0 1.2.3.4
    t2_ipv4_to_uncompressed(ipv4, dest, sizeof(dest)) 1 001.002.003.004
    t2_ipv4_to_hex(ipv4, dest, sizeof(dest)) 2 0x01020304
    t2_ipv4_to_uint(ipv4, dest, sizeof(dest)) 3 16909060
  • Convert an IPv6 address to string. Note that the actual representation depends on the value of IP6_FORMAT in utils/bin2txt.h (see table below).

    struct in6_addr ipv6 = {};
    char dest[INET6_ADDRSTRLEN];
    
    t2_ipv6_to_str(ipv6, dest, sizeof(dest));
  • To force a specific representation use one of the functions below:

    Function IP6_FORMAT Output
    t2_ipv6_to_compressed(ipv6, dest, sizeof(dest)) 0 2001:db8::1
    t2_ipv6_to_uncompressed(ipv6, dest, sizeof(dest)) 1 2001:0db8:0000:0000:0000:0000:0000:0001
    t2_ipv6_to_hex128(ipv6, dest, sizeof(dest)) 2 0x20010db8000000000000000000000001
    t2_ipv6_to_hex64_hex64(ipv6, dest, sizeof(dest)) 3 0x20010db800000000_0x0000000000000001
  • IPv4 or IPv6:

    char dest[INET6_ADDRSTRLEN];
    const uint_fast8_t version = PACKET_IPVER(packet);
    
    T2_IP_TO_STR(packet->srcIP, version, dest, sizeof(dest));
    
    if (PACKET_IS_IPV6(packet)) {
        T2_IPV4_TO_STR(packet->srcIP, dest, sizeof(dest)); // for IPv4
    } else if (PACKET_IS_IPV6(packet)) {
        T2_IPV6_TO_STR(packet->srcIP, dest, sizeof(dest)); // for IPv6
    }

Reporting subnet information

// Tranalyzer2 already looked up source and destination address for each packet
const uint32_t num = packet->subnetNrSrc;
//const uint32_t num = packet->subnetNrDst;
const uint_fast8_t ipver = PACKET_IPVER(packet);

// To lookup other addresses:
uint32_t netNum;
ipAddr_t ip = packet->srcIP;
SUBNET_TEST_IP(netNum, ip, ipver);
//SUBNET_TEST_IP4(netNum, ip); // for IPv4
//SUBNET_TEST_IP6(netNum, ip); // for IPv6

// Autonomous System Number
uint32_t asn;
SUBNET_ASN(asn, ipver, num);
//SUBNET4_ASN(asn, num); // for IPv4
//SUBNET6_ASN(asn, num); // for IPv6

// Country
char *country;
SUBNET_LOC(country, ipver, num);
//SUBNET4_LOC(country, num); // for IPv4
//SUBNET6_LOC(country, num); // for IPv6

// County
char *county;
SUBNET_CNTY(county, ipver, num);
//SUBNET_CNTY(county, num); // for IPv4
//SUBNET_CNTY(county, num); // for IPv6

// City
char *city;
SUBNET_CTY(city, ipver, num);
//SUBNET4_CTY(city, num); // for IPv4
//SUBNET6_CTY(city, num); // for IPv6

// Organization
char *org;
SUBNET_ORG(org, ipver, num);
//SUBNET4_ORG(org, num); // for IPv4
//SUBNET6_ORG(org, num); // for IPv6

// Hexadecimal code
uint32_t netID;
SUBNET_NETID(netID, ipver, num);
//SUBNET4_NETID(netID, num); // for IPv4
//SUBNET6_NETID(netID, num); // for IPv6

// Latitude, longitude and precision
float lat, lng, prec;
SUBNET_LAT(dest, ipver, num);     // Latitude
SUBNET_LNG(dest, ipver, num);     // Longitude
SUBNET_PREC(dest, ipver, num);    // Precision
//SUBNET4_LAT(dest, num);  // Latitude for IPv4
//SUBNET4_LNG(dest, num);  // Longitude for IPv4
//SUBNET4_PREC(dest, num)  // Precision for IPv4
//SUBNET6_LAT(dest, num);  // Latitude for IPv6
//SUBNET6_LNG(dest, num);  // Longitude for IPv6
//SUBNET6_PREC(dest, num)  // Precision for IPv6

Working with strings

const char * const str = "Andy was here";

if (t2_str_has_prefix(str, "Andy")) {
    // 'str' starts with 'Andy'
}

if (t2_str_has_suffix(str, "here")) {
    // 'str' ends with 'here'
}

char dest[9];
t2_strcpy(dest, str, sizeof(dest), T2_STRCPY_EXIT);     // abort if dest too small
t2_strcpy(dest, str, sizeof(dest), T2_STRCPY_EMPTY);    // dest will be empty if dest too small: dest[0] = '\0'
t2_strcpy(dest, str, sizeof(dest), T2_STRCPY_TRUNC);    // truncate string if dest too small: dest = "Andy was"
t2_strcpy(dest, str, sizeof(dest), T2_STRCPY_ELLIPSIS); // truncate and ellipsize string if dest too small: dest = "Andy ..."

const char * const andy = "Andy";
const char * const was_here = "was here";
char *andy_was_here = t2_strdup_printf("%s %s", andy, was_here);
printf("%s\n", andy_was_here); // -> Andy was here
free(andy_was_here);

Miscellaneous macros

Reporting alarms (alarm mode)

The following snippet of code can be implemented in any callback receiving the flowIndex, i.e., t2OnNewFlow(), t2OnLayer2(), t2OnLayer4() and/or t2OnFlowTerminate().

void t2OnFlowTerminate(unsigned long flowIndex, outputBuffer_t *buf) {
    flow_t * const flowP = &flows[flowIndex];
    mypluginFlow_t *mypluginFlowP = &mypluginFlows[flowIndex];

#if ALARM_MODE == 1
    T2_SET_STATUS(flowP, FL_ALARM);           // Set the alarm bit in flowStat
    T2_REPORT_ALARMS(mypluginFlowP->alarms);  // Report alarm(s) to the core
#endif // ALARM_MODE == 1

    ...
}

For more details, refer to the Implementing the alarm mode capability tutorial.

Forcing termination of a flow (force mode)

flow_t *flowP = &flows[flowIndex];
T2_RM_FLOW(flowP);

For more details, refer to the Implementing the force mode capability tutorial.

Handy C snippets

Creating (and deleting) folder for saving data

  • src/myPlugin.h
#define MYPLUGIN_SAVE  0 // Save data
#define MYPLUGIN_RMDIR 1 // Empty MYPLUGIN_PATH before starting

#define MYPLUGIN_PATH "/tmp/myPluginData/" // Path for extracted files
  • src/myPlugin.c
#if MYPLUGIN_SAVE == 1
    T2_MKPATH(MYPLUGIN_PATH, MYPLUGIN_RMDIR);
#endif // MYPLUGIN_SAVE == 1

Loading a file from the plugin folder

FILE *file = t2_fopen_in_dir(pluginFolder, "myFile.txt", "r");
if (UNLIKELY(!file)) exit(EXIT_FAILURE);

size_t len;
ssize_t read;
char *line = NULL;
while ((read = getline(&line, &len, file)) != -1) {
    if (line[0] == '#') continue; // skip comments
    if (line[read-1] == '\n') line[--read] = '\0';
    // ...
}

free(line);
fclose(file);

Saving data in a new file

FILE *file = t2_fopen_with_suffix(baseFileName, "_mySuffix.txt", "w");
if (UNLIKELY(!file)) exit(EXIT_FAILURE);
fputs("Write something in the file...\n", file);
fprintf(file, "Number of %s packets: %" PRIu64 " packets\n", plugin_name, numPackets);
fclose(file);

Searching for a needle in a haystack

const uint8_t * const payload = packet->l7HdrP;
const uint16_t snaplen = packet->snapL7Len;

const char *needle = "Andy";
char *andy = memmem(payload, snaplen, needle, strlen(needle));
if (andy) {
    // Andy was here
} else {
    // where is Andy? Not in the payload...
}

Working with Perl Compatible Regular Expressions (PCRE)

  • meson.build:

    ...
    pcre_dep = dependency('libpcre')
    deps = [
        pcre_dep,
    ]
    ...
    shared_module(plugin_name,
        sources: src,
        dependencies: deps,
        include_directories: inc,
        name_suffix: 'so',
    )
  • myplugin.c:

    #include <pcre.h>
    
    const uint8_t * const payload = packet->l7HdrP;
    const uint16_t snaplen = packet->snapL7Len;
    
    const char *error;
    int erroffset;
    int ovecsize = 30;
    int ovector[ovecsize];
    
    // The regex to search for
    const char *code = "^[A-Z][a-z]+[0-9]+$";
    
    // Initialization
    pcre *regex = pcre_compile(code, 0, &error, &erroffset, NULL);
    
    // Search
    const int num_matches = pcre_exec(regex, NULL, payload, snaplen, 0, 0, ovector, ovecsize);
    if (num_matches == PCRE_ERROR_NOMATCH) {
        // No match found
    } else if (num_matches < 0) {
        // An error occurred...
        printf("%s\n", error);
    } else {
        // Results
        for (int i = 0; i < num_matches; i++) {
            const char *substring;
            pcre_get_substring(payload, ovector, num_matches, i, &substring);
            printf("Regex was matched: '%s'\n", substring);
            pcre_free_substring(substring);
        }
    }
    
    // Release memory
    pcre_free(regex);

Debugging

  1. Compile in debug mode:

    t2build -d myPlugin

  2. Run t2 in the gdb (Linux) or lldb (macOS) debugger:

    gt2 -r file.pcap

  3. Set a breakpoint:

    • On a specific line: b myFile.c:1234
    • On a specific function: b myFunc
  4. Set a conditional breakpoint:

    • On a specific line: b myFile.c:1234 if numPackets == 3
    • On a specific function: b myFunc if numPackets == 3
  5. Add a condition to an existing breakpoint:

    b myFile.c:1234

    Breakpoint 1 at 0x5674

    cond 1 numPackets == 3

    b myFunc

    Breakpoint 2 at 0x8535

    cond 2 numPackets == 3

  6. Start debugging:

    r

  7. Print the program trace (backtrace):

    bt

  8. Inspect the content of a variable:

    • Default format: p myVar
    • Hexadecimal: p /x myHexVar
  9. Jump to the next program line:

    n

  10. Step into a function:

s

  1. Continue running the program:

    c

  2. Quit gdb:

    q

Toggle test

  1. List the flags to toggle in tests/myPlugin.flags:

    cat $T2PLHOME/myPlugin/tests/myPlugin.flags

    ...
    ETH_ACTIVATE     ../../tranalyzer2/src/networkHeaders.h
    ...
    MYBOOLEAN_FLAG   src/myPlugin.h
  2. Run t2test:

    t2test myPlugin

    Or if you have a lot of flags, start with:

    t2test myPlugin -J

    which is not exhaustive, but faster.

Scripts

If you need to write supporting scripts, the t2utils.sh helper file provides easy access to Tranalyzer2 scripts and programs and help ensures portability for different OS. Use it as follows (assuming your script resides in ${T2PLHOME}/myPlugin/scripts/, otherwise you will need to adapt the path in the third line (source ...)):

#!/usr/bin/env bash

source "$(dirname "${0}")/../../../scripts/t2utils.sh"

check_dependency gnuplot

usage() {
    echo "Usage:"
    echo "    ${SNAME} [OPTION...] <FILE>"
    echo
    echo "Optional arguments:"
    echo "    -v, --verbose     Enable verbose output"
    echo
    echo "Help and documentation arguments:"
    echo "    -h, --help        Show this help, then exit"
}

while [[ "${#}" -gt 0 ]]; do
    case "${1}" in

        -v|--verbose)
            VERBOSE=1
            ;;

        -h|-\?|--help)
            usage
            exit 0
            ;;

        *)
            if [[ ! -f "${1}" ]]; then
                abort_option_unknown "${1}"
            fi
            FILE="${1}"
            ;;
    esac
    shift
done

if [[ ! -f "${FILE}" ]]; then
    abort_required_file
elif [[ ! -s "${FILE}" ]]; then
    printerr "File size MUST be greater than zero"
    abort_with_help
fi

Access Tranalyzer scripts and utilities

T2 alias/script/utility T2 variable/function Description Example
t2 T2 tranalyzer executable T2 -r file.pcap
t2whois T2WHOIS query the anteaters subnet file T2WHOIS 1.2.3.4
t2b2t T2B2T convert T2 binary files T2B2T -l f_flows.bin
tran "${T2HOME}" location of tranalyzer2 source files, e.g., /home/user/tranalyzer2-0.9.3/ cd "${T2PLHOME}"
tranpl "${T2PLHOME}" location of the plugins source files, e.g., ${T2HOME}/plugins/ cd "${T2PLHOME}/basicFlow"
"${SHOME}" location of the script, e.g., ${T2PLHOME}/myPlugin/scripts/ cd "${SHOME}"
"${SNAME}" name of the script echo "${SNAME}"
t2build "${T2BUILD}" build plugins "${T2BUILD}" basicFlow
t2conf "${T2CONF}" configure plugins "${T2CONF}" basicFlow -D BFO_VLAN=1
t2plot "${T2PLOT} 2D/3D plot "${T2PLOT}" file.txt
tawk "${TAWK}" post-processing with Tawk "${TAWK}" 'aggr($l4Proto)' f_flows.txt

Function/variable wrappers for common utitilies

For maximum portability, make sure to replace your calls to awk, sed, … as follows:

Program T2 variable/function Example
awk AWK AWK '{ print $1 }' file.txt
gawk AWK AWK '{ print $1 }' file.txt
awk -v OFS='\t' -F '\t' AWKF AWKF '{ print $2 }' file.txt
gawk -v OFS='\t' -F '\t' AWKF AWKF '{ print $2 }' file.txt
sed "${SED}" "${SED}" 's/$/ Andy/' <<< "Hello"
gsed "${SED}" "${SED}" 's/$/ Andy/' <<< "Hello"
open (macOS) "${OPEN}" "${OPEN}" file.txt
xdg-open "${OPEN}" "${OPEN}" file.txt
python3 "${PYTHON}" "${PYTHON}" file.py
python "${PYTHON}" "${PYTHON}" file.py
python2 "${PYTHON}" "${PYTHON}" file.py
readlink "${READLINK}" "${READLINK}" -f /tmp/../tmp/file.txt
greadlink "${READLINK}" "${READLINK}" -f /tmp/../tmp/file.txt

t2utils.sh: list of available functions

For usage details, refer to the well documented source code of t2utils.sh in ${T2HOME}/scripts/t2utils.sh:

Functions

  • printbold
  • printerr
  • printinf
  • printok
  • printwrn
  • printfbold
  • printferr
  • printfinf
  • printfok
  • printfwrn
  • fatal
  • check_dependency
  • check_dependency_linux
  • check_dependency_macos
  • test_min_version
  • has_define
  • get_define
  • set_define
  • ask_default_no
  • ask_default_yes
  • find_most_recent_dir
  • find_most_recent_file
  • t2_build_exec
  • get_t2_exec
  • get_t2b2t_exec
  • get_t2whois_exec
  • abort_if_t2_exec_not_found
  • abort_if_t2b2t_exec_not_found
  • abort_if_t2whois_exec_not_found
  • t2_wget
  • t2_wget_n
  • replace_suffix
  • get_nproc
  • arg_is_option
  • validate_next_arg
  • validate_next_arg_exists
  • validate_next_dir
  • validate_next_file
  • validate_next_file_or_dir
  • validate_next_pcap
  • validate_next_num
  • validate_next_int
  • validate_next_float
  • validate_float
  • validate_int
  • validate_num
  • validate_ip
  • validate_pcap
  • abort_missing_arg
  • abort_option_unknown
  • abort_required_file
  • abort_required_dir
  • abort_required_file_or_dir
  • abort_with_help

Scripts and programs

Note that all the variables are defined as readonly.

Functions
  • AWK
  • AWKF
  • T2
  • T2B2T
  • T2WHOIS
Variables
  • ${AWK_EXEC}
  • ${OPEN}
  • ${READLINK}
  • ${SED}
  • ${T2BUILD}
  • ${T2CONF}
  • ${T2PLOT}
  • ${TAWK}

Folders

  • ${SHOME}
  • ${T2HOME}
  • ${T2PLHOME}

Colors

  • ${BLUE}
  • ${GREEN}
  • ${ORANGE}
  • ${RED}
  • ${BLUE_BOLD}
  • ${GREEN_BOLD}
  • ${ORANGE_BOLD}
  • ${RED_BOLD}
  • ${BLUE_ITALIC}
  • ${GREEN_ITALIC}
  • ${ORANGE_ITALIC}
  • ${RED_ITALIC}
  • ${BLUE_UNDERLINE}
  • ${GREEN_UNDERLINE}
  • ${ORANGE_UNDERLINE}
  • ${RED_UNDERLINE}
  • ${BOLD}
  • ${ITALIC}
  • ${STRIKETHROUGH}
  • ${UNDERLINE}
  • ${NOCOLOR}

OS detection

  • ${IS_LINUX}
  • ${IS_MACOS}

Variables

  • ${SNAME}

Adding support for Bash/ZSH completion

Completions files for T2 scripts and utilities reside in ${T2HOME}/scripts/completions/{bash,zsh}/ and are named according to the name of the script/program prepended by an underscore, e.g., for myScript: ${T2HOME}/scripts/completions/bash/_myScript and ${T2HOME}/scripts/completions/zsh/_myScript. Make sure to replace all occurences of myScript with the name of your script.

Bash

Bash completions reside in ${T2HOME}/scripts/completions/bash/.

# Bash completion for myScript

_myScript() {
    local cur prev words cword

    if type -t _init_completion &> /dev/null; then
        _init_completion -n = || return
    else
        COMPREPLY=()
        cur="${COMP_WORDS[COMP_CWORD]}"
        prev="${COMP_WORDS[COMP_CWORD-1]}"
    fi

    case "${prev}" in
        # -f file
        -f)
            if type -t _filedir &> /dev/null; then
                _filedir
            else
                compopt -o filenames 2> /dev/null
                COMPREPLY=($(compgen -f -- "${cur}"))
            fi
            return
            ;;
        # -d directory
        -d)
            if type -t _filedir &> /dev/null; then
                _filedir -d
            else
                compopt -o dirnames 2> /dev/null
                COMPREPLY=($(compgen -d -- "${cur}"))
            fi
            return
            ;;
    esac

    if [[ "${cur}" == -* ]]; then
        COMPREPLY=($(compgen -W "$(_parse_help "${T2PLHOME}/myPlugin/scripts/myScript" -h)" -- "${cur}"))
        # Or if you want more control:
        #local opts=(
        #    -f --file
        #    -d --dir
        #    -v --verbose
        #    -\? -h --help
        #)
        #COMPREPLY=($(compgen -W "${opts[*]}" -- "${cur}"))
    fi
}

complete -F _myScript myScript

ZSH

ZSH completions reside in ${T2HOME}/scripts/completions/zsh/.

# ZSH completion for myScript

_myScript() {
    #compdef myScript

    local cur="${words[CURRENT]}"
    [[ "${CURRENT}" -gt 2 ]] && local prev="${words[CURRENT-1]}"

    case "${prev}" in
        # -f file
        -f)
            _files
            return
            ;;
        # -d directory
        -d)
            _files -/
            return
            ;;
    esac

    if [[ "${cur}" == -* ]]; then
        _arguments --
        # Or if you want more control:
        #local opts=(
        #    -f --file
        #    -d --dir
        #    -v --verbose
        #    -\? -h --help
        #)
        #compadd "${opts[@]}"
    fi
}

compdef _myScript myScript

Documentation

tawk -V

In order for tawk to know about your variables, you need to create a file with the name of your variable in $T2HOME/scripts/tawk/vars/. The easiest is to copy one of the existing files, e.g.:

  • Bitfields:

    bt_hex_8 $T2HOME/scripts/tawk/vars/arpStat
    bt_hex_16 $T2HOME/scripts/tawk/vars/dhcpStat
    bt_hex_32 $T2HOME/scripts/tawk/vars/tcpOptions
    bt_hex_64 $T2HOME/scripts/tawk/vars/flowStat
  • Fixed values:

    Hexadecimals $T2HOME/scripts/tawk/vars/vtpCode
    Integers $T2HOME/scripts/tawk/vars/tftpErrC
    Strings $T2HOME/scripts/tawk/vars/tftpOpcode

Workflow

tran

cd scripts/tawk/vars

cp arpStat myprotoStat

vi myprotoStat

#H bit | myprotoStat | Description
#H =============================================================================
     0 | 0x01        | Myproto detected
#I   1 | 0x02        | This is an information (blue)
#W   2 | 0x04        | This is a warning (orange)
#E   3 | 0x08        | This is an error (red)

     4 | 0x10        | Use a dash ("-") if a bit is not used
     5 | 0x20        | -
     6 | 0x40        | -
     7 | 0x80        | -

tawk -V myprotoStat



   The myprotoStat column is to be interpreted as follows:

      bit | myprotoStat | Description
      =============================================================================
        0 | 0x01        | Myproto detected
        1 | 0x02        | This is an information (blue)
        2 | 0x04        | This is a warning (orange)
        3 | 0x08        | This is an error (red)
        4 | 0x10        | Use a dash ("-") if a bit is not used
        5 | 0x20        | -
        6 | 0x40        | -
        7 | 0x80        | -
   
tawk -V myprotoStat=0x13


   The myprotoStat column with value 0x13 is to be interpreted as follows:

      bit | myprotoStat | Description
      =============================================================================
        0 | 0x01        | Myproto detected
        1 | 0x02        | This is an information (blue)
        4 | 0x10        | Use a dash ("-") if a bit is not used