The basics: your first flow plugin

plugin development C

Introduction

This tutorial gives you an introduction into plugin development for T2 in C. Why C? When you are in the business of time and memory performance, you have nothing to waste if you have to hold a million++ flows in memory. Sure, better would be assembler or HW.

If you are in a hurry and want to get down to business, head to Writing a TCP flow control window size threshold detector (but don’t forget to read the Getting started section!). Otherwise, keep on reading!

Getting started

Create folders for your data and results

If you have not created a separate data and results directory yet, please do it now. This will greatly facilitate your workflow:

mkdir ~/data ~/results

Reset tranalyzer2 and the plugins configuration

If you have followed the other tutorials, you may have modified some of the core and plugins configuration. To ensure your results match those in this tutorial, make sure to reset everything:

t2conf -a --reset

You can also clean all build files:

t2build -a -c

Empty the plugin folder

To ensure we are not left with some unneeded plugins or plugins which were built using different core configuration, it is safer to empty the plugins folder:

t2build -e -y

Are you sure you want to empty the plugin folder '/home/user/.tranalyzer/plugins' (y/N)? yes
Plugin folder emptied

Download the PCAP file

The PCAP file used in this tutorial can be downloaded here:

Please save it in your ~/data folder:

wget --no-check-certificate -P ~/data https://tranalyzer.com/download/data/annoloc2.pcap

Build tranalyzer2 and the required plugins

For this tutorial, we will need to build the core (tranalyzer2) and the following plugins:

As you may have modified some of the automatically generated files, it is safer to use the -r and -f options.

t2build -r -f tranalyzer2 basicFlow basicStats txtSink

...

BUILDING SUCCESSFUL

Source code

If you are impatient you can download the initial, intermediate and final versions of the tcpWin plugin we will develop in this tutorial. If you want to learn, keep on reading and you will create everything yourself (and learn a lot more in the process!). The initial version is automatically created by t2plugin, so don’t worry, you won’t have too much to type!

To use one of those plugins, just unpack it in the plugins folder of your T2 installation.

tranpl

tar -xf ~/Downloads/tcpWin02.tar.gz

And let t2_aliases know about it:

source "$T2HOME/scripts/t2_aliases"

What is a plugin

In this section, we discuss about what is a plugin, which callbacks are required, … If you are impatient and just want to develop a useful plugin now, jump directly to the Writing a TCP flow control window size threshold detector section.

Actually it is fairly simple to write plugins, if you have some knowledge about any programming language. Even if you write scripts, such as awk, you will quickly realize that T2 plugins are also organized in BEGIN, MIDDLE and END blocks. But a plugin is more than its source code! You also need to be able to build it, to display its documentation, … This may seem like a daunting task, but actually, witht the t2plugin script, it is a piece of cake! The script creates a plugin which can be compiled, whose documentation can be displayed, … Sadly, it does not write the plugin for you, but it does come with a lot of code examples showing you how to perform standard tasks. You will learn a lot, but will have to delete a lot of code as well. So alternatively, you can create a minimal plugin and copy the code you need from $T2PLHOME/t2PSkel/src/t2PSkel.c. We will use the t2plugin script in the next section! But for now, we will briefly look at how the source code of a plugin is organized.

The simplest plugin

The simplest plugin you can write has only one source file, no header file and does nothing:

cat src/myPlugin.c

#include "t2Plugin.h"

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

The simplest useful plugin

Let’s have a quick look at a more realistic plugin. Most plugins will have at least one source file and one header file. For this example, we will simply count the number of TCP packets. Note that this section just gives you a sneak peak into a very simple plugin, but does not discuss any implementation details. This will come with a more elaborate plugin in the next section.

Let us start by writing a simple tawk prograw to illustrate the functionality of our future plugin (Although not necessary, we have used the BEGIN clause to illustrate the similarity between awk and a plugin):

tawk 'BEGIN { tcpCount = 0; } tcp() { tcpCount++); } END { print tcpCount; }' ~/results/annoloc2_flows.txt

9985

We can use this value later to validate the functionality of our plugin!

A plugin is structured in a similar way to an awk program: there is a BEGIN, a MIDDLE and an END block:

awk T2 Plugin
BEGIN { } void t2Init() { }
{ } void t2OnNewFlow(packet_t *packet, unsigned long flowIndex) { }
{ } void t2OnLayer2(packet_t *packet, unsigned long flowIndex) { }
{ } void t2OnLayer4(packet_t *packet, unsigned long flowIndex) { }
END { } void t2PluginReport(FILE *stream) { }
END { } void t2Finalize() { }

cat src/myPlugin.h

#ifndef T2_MYPLUGIN_H_INCLUDED
#define T2_MYPLUGIN_H_INCLUDED

#include "t2Plugin.h"

#define MYPLUGIN_STAT_TCP 0x01 // Flow is TCP

typedef struct {
    uint32_t num_tcp; // Number of TCP packets for a given flow
    uint8_t  status;  // The status will keep track of TCP flows
} myPluginFlow_t;

#endif // T2_MYPLUGIN_H_INCLUDED

cat src/myPlugin.c

#include "myPlugin.h"

static uint8_t myPluginStat;
static uint64_t num_tcp;

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

void t2Init() {
    T2_PLUGIN_STRUCT_NEW(myPluginFlows);
}

binary_value_t* t2PrintHeader() {
    binary_value_t *bv = NULL;
    BV_APPEND_H8(bv, "myPluginStat", "myPlugin status");
    BV_APPEND_U32(bv, "myPluginNumTCP", "myPlugin number of TCP packets");
    return bv;
}

void t2OnNewFlow(packet_t *packet, unsigned long flowIndex) {
    myPluginFlow_t * const myPluginFlowP = &myPluginFlows[flowIndex];
    memset(myPluginFlowP, '\0', sizeof(*myPluginFlowP));

    if (packet->l4Proto == L3_TCP) {
        myPluginFlowP->stat |= MYPLUGIN_STAT_TCP;
    }
}

void t2OnLayer4(packet_t *packet, unsigned long flowIndex) {
    myPluginFlow_t * const myPluginFlowP = &myPluginFlows[flowIndex];
    if (myPluginFlowP->stat & MYPLUGIN_STAT_TCP) {
        myPluginFlowP->num_tcp++;
    }
}

void t2OnFlowTerminate(unsigned long flowIndex, outputBuffer_t *buf) {
    myPluginFlow_t * const myPluginFlowP = &myPluginFlows[flowIndex];
    // Update global counters
    num_tcp += myPluginFlowP->num_tcp;
    myPluginStat |= myPluginFlowP->status;
    // Populate output buffer
    OUTBUF_APPEND_U8(buf, myPluginFlowP->status);
    OUTBUF_APPEND_U32(buf, myPluginFlowP->num_tcp);
}

void t2PluginReport(FILE *stream) {
    T2_FPLOF_AGGR_HEX(stream, plugin_name, myPluginStat);
    T2_FPLOG_NUMP(stream, plugin_name, "Number of TCP packets", num_tcp, numPackets);
}

void t2Finalize() {
    free(myPluginFlowP);
}

The next section summarizes the callbacks available.

The callbacks

T2 supplies the programmer with a wealth of functionality. For your first steps not all are necessary. If you created your plugin with t2plugin then you might be forced to delete a lot of lines of code, because they are templates for many applications. Therefore it might be easier to use t2plugin -m to create a minimal plugin and copy code back from $T2PLHOME/t2PSkel/src/t2PSkel.[ch].

Here is a list of all callbacks including links to a tutorial chapter where they are discussed in detail using our tcpWin example.

INIT: Initialize the structures and variables and the output header in the flow file

BEGIN: First packet detected of a new flow

MIDDLE: packet lifetime

END flow: timeout or last packet detected

END PCAP or terminate session: finish packet processing and generate aummaries and end reports

The functions t2SaveState and t2RestoreState preserve the states and variables of your plugin in order to survive a halt of T2. After restart of T2, the info of your plugin will be automatically reloaded and it can continue where it was stopped.

In this tutorial, we will focus on the basic callbacks, namely:

The remaining ones will be discussed in the next tutorials.

Writing a TCP flow control window size threshold detector

The basics

Let’s say you want to extract the window size from the TCP header and count the number of times the window size undercuts a certain threshold.

First, we create a new plugin using the t2plugin script. Let’s call it tcpWin and give it the plugin number 150. As we will be adding callbacks as we go, we want to start with a minimal plugin (-m):

t2plugin -c tcpWin -n 150 -m

C plugin 'tcpWin' created
Make sure to update your aliases by calling 'source "$T2HOME/scripts/t2_aliases"'

That’s it! You should now have a working mininal plugin! Make sure to update the aliases by calling:

source "$T2HOME/scripts/t2_aliases"

Now, let’s head straight to our plugin:

tcpWin

Now it’s time to look into the code of our example plugin tcpWin.c and discuss the INIT block first.

Every plugin must implement one of the following macros:

  • T2_PLUGIN_INIT("pluginName", "pluginVersion", t2_version_major, t2_version_minor)

  • T2_PLUGIN_INIT_WITH_DEPS("pluginName", "pluginVersion", t2_version_major, t2_version_minor, "dep1,dep2")

If your plugin does not rely on information that other, lower numbered plugins provide, the first one should be implemented here.

If you open tcpWin.c in your editor of choice, you will notice the T2_PLUGIN_INIT() is already implemented:

vi src/tcpWin.c

...

// Tranalyzer functions

/*
 * This describes the plugin name, version, major and minor version of
 * Tranalyzer required and dependencies
 */
T2_PLUGIN_INIT("tcpWin", "0.9.1", 0, 9);

...

First, your plugin must have a name, tcpWin, a version number, and a minimal required acceptance limit of the T2 core version. In our case the plugin version is 0.9.1. The last number denotes the development subversion, which is not critical. The major and minor accepted version are 0 and 9, respectively. If you put anything else the plugin will be rejected by the core, as our current major version is 0.9. This mechanism protects T2 from loading incompatible plugins.

As mentioned above if dependencies are required, dependent plugin names have to be added such as tcpFlags or tcpStates, here we do not need any. Here is an example anyway, but if you want to learn more about that, refer to the Plugin Dependencies tutorial.

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

Implementing the t2Init() callback

Every plugin can store flow information in a structure. In this section, we will see how to initialize and access it. But first, let’s look at what such a structure definition looks like in our header file:

vi src/tcpWin.h

...

// Plugin structure

typedef struct { // always large variables first to limit memory fragmentation
    uint8_t stat;
} tcpWinFlow_t;

// plugin struct pointer for potential dependencies
extern tcpWinFlow_t *tcpWinFlows;

...

So every flow has a status variable stat. We also see that an array of such structures is defined elsewhere (extern). Thus, we need to define and allocate memory for this array in the C file. This is done in the t2Init() callback.

vi src/tcpWin.c

...

/*
 * Plugin variables that may be used by other plugins MUST be declared in
 * the header file as 'extern tcpWinFlow_t *tcpWinFlows;'
 */

tcpWinFlow_t *tcpWinFlows;



...

/*
 * This function is called before processing any packet.
 */
void t2Init() {
    // allocate struct for all flows and initialize to 0
    T2_PLUGIN_STRUCT_NEW(tcpWinFlows);
}

...

What T2_PLUGIN_STRUCT_NEW() macro does is simply allocating memory for an array of tcpWinFlows structure and initialize the memory to 0. The size of the array depends on the size of the mainHashMap, namely mainHashMap->hashChainTableSize. The variable defines the maximum amount of flows which can be held by T2 at any given time and is controlled by HASHCHAINTABLE_BASE_SIZE in $T2HOME/tranalyzer2/src/tranalyzer.h and by a multiplication factor defined either by HASHFACTOR or the command line -f option. For the time being you do not need to worry about that, just use the T2_PLUGIN_STRUCT_NEW() macro as shown above. In $T2PLHOME/t2PSkel/src/t2PSkel.c, you will more examples of what you might want to do in the t2Init() callback, for example, initializing the packet mode, but here we will concentrate only on the first necessary steps.

Now you are all set, your structure is initialized and good to go.

We will see how to access the structure for a specific flow when we’ll be implementing the t2OnNewFlow() callback.

Implementing the t2PrintHeader() callback

In order to output your data in t2OnFlowTerminate(), you need to define the data structure of the output buffer. Every column which you want to output must have a description, a type and a name (which will appear in the top line of the flow file and can then be used in tawk as $myColumnName). The txtSink plugin creates a _headers.txt file which contains a detailed description of each column.

The t2PrintHeader() function already has code to output an 8-bit status:

vi src/tcpWin.c

...

/*
 * This function is used to describe the columns output by the plugin
 */
binary_value_t* t2PrintHeader() {
    binary_value_t *bv = NULL;
    BV_APPEND_H8(bv, "tcpWinStat", "tcpWin status");
    return bv;
}

...

For your window size undercut detector, an eight bit hex status number and an uint32_t count variable are required. As we’ve just seen the status is already implemented, but you could give it a better description. Let’s add a uint32_t count variable, using BV_APPEND_U32(). If you are unsure about which macros to use, refer to Describe your flow output in the Plugin Programming Cheatsheet. Note that you could have used an uint64_t, but remember that every byte is multiplied by HASHCHAINTABLE_BASE_SIZE, defining the number of flow buckets available.

vi src/tcpWin.c

...
binary_value_t* t2PrintHeader() {
    binary_value_t *bv = NULL;
    BV_APPEND_H8(bv, "tcpWinStat", "TCP window size threshold status");   // <-- Updated description (TODO)
    BV_APPEND_U32(bv, "tcpWinThCnt", "TCP window size threshold count");  // <-- New line (TODO)
    return bv;
}
...

These macros are useful if you have standard scalar output like integer, float or strings. In case of more complex structures, such as matrices, or vectors of matrices, the original functions need to be used. So in good old T2 format the same header definition would look like this:

...
binary_value_t* t2PrintHeader() {
    binary_value_t *bv = NULL;
    bv = bv_append_bv(bv, bv_new_bv("TCP window size threshold status", "tcpWinStat", 0, 1, bt_hex_8));
    bv = bv_append_bv(bv, bv_new_bv("TCP window size threshold count", "tcpWinThCnt", 0, 1, bt_uint_32));
    return bv;
}
...

Each bv_append_bv(...) function adds a new column in the _flows.txt file and a description in the _headers.txt file. bv_new_bv() allocates memory for your new variables. The first parameter defines the full description in the _headers.txt file, the second the name of the column on top of the flow file. The third one is a repetition parameter, which denotes the following variables of arbitrary repetitive nature. So it is 0. We will come back to this when discussing the output of vectors. The next number defines how many variables are incorporated in a column definition. In our case, 1.

Here we go with the more user-friendly macros. That wasn’t so complicated, right?

Implementing the t2OnNewFlow() callback

We need to tell T2 what should happen at the first packet of a flow. Here you can initialize all your flow structures and state-machines, or store initial numbers as in our case, the TCP initial window size. The function t2OnNewFlow(packet_t *packet, unsigned long flowIndex) has access to the packet and flow structure defined in packet.h and flow.h respectively. For operations, which use header content being constant over the lifetime of a flow, this is the callback where you implement the appropriate code. (Note that the first packet of a flow produces an t2OnNewFlow() and a t2OnLayer[24]() callback.)

vi src/tcpWin.c

...

/*
 * This function is called every time a new flow is created.
 */
void t2OnNewFlow(packet_t *packet, unsigned long flowIndex) {
    // Reset the structure for this flow
    tcpWinFlow_t * const tcpWinFlowP = &tcpWinFlows[flowIndex];
    memset(tcpWinFlowP, '\0', sizeof(*tcpWinFlowP));
}

...

The flowIndex is the most important tool for flow handling, specifically an internal index to access the flow via the flows[flowIndex] array and the plugin structure for this flow via the tcpWinFlows[flowIndex]. The first line defines the pointer to your plugin structure of the newly created flow. Then, we need to clear your structure, because the memory will be reused, when the flow terminates.

As we do not need the packet structure in this function, we can add the UNUSED tag to silence compiler warnings:

vi src/tcpWin.c

void t2OnNewFlow(packet_t *packet UNUSED, unsigned long flowIndex) {  // <-- Added UNUSED attribute (TODO)
...

Implementing the t2OnLayer2() callback

Only for pure L2 flows, e.g., ARP, CDP, STP, … this callback is implemented. All operations pertaining the L2 packet have to be implemented here.

As we do not have pure L2 flows in our pcap and only produce a flow file, we can omit the callback. Unless, for the packet mode, then we need the callback and print the appropriate tabs to jump over the non-existing L3/4 packet output of other plugins. I added an example below, which we will need in the packet mode tutorial. Note that the flowIndex for L3/4 packets is not existing at the L2 callback. In our tcpWin.c this function is not implemented.

...

#if ETH_ACTIVATE > 0
/*
 * This function is called for every packet with a layer 2.
 * If flowIndex is HASHTABLE_ENTRY_NOT_FOUND, this means the packet also
 * has a layer 4 and thus a call to t2OnLayer4() will follow.
 */
void t2OnLayer2(packet_t *packet, unsigned long flowIndex) {
    if (flowIndex == HASHTABLE_ENTRY_NOT_FOUND) return;

    // This packet does not have a layer 4.
}
#endif // ETH_ACTIVATE > 0

...

Note that by using t2plugin -m option, we have automatically discarded that callback. So we skip layer 2 and move on to the t2OnLayer4() callback.

Implementing the t2OnLayer4() callback

Whenever a L3/4 packet for a flow arrives, your plugin receives the t2OnLayer4() callback. All operations on the present packet have to be implemented here. Plugins which only focus on flow parameters like basicFlow do not need to implement this callback. As we want to inspect every window size of every packet in a flow, we definitely have to implement our code here.

vi src/tcpWin.c


...

/*
 * This function is called for every packet with a layer 4.
 */
void t2OnLayer4(packet_t *packet, unsigned long flowIndex) {
    tcpWinFlow_t * const tcpWinFlowP = &tcpWinFlows[flowIndex];

    // only 1. frag packet will be processed
    if (!t2_is_first_fragment(packet)) return;

    tcpWinFlowP->stat |= TCPWIN_STAT_MYPROT;
    numTCPWINPkts++;
}

...

Let’s add some code! First of all, we want to process TCP only. We will use l4Proto field from the flow_t structure. We will also need to extract the window size from the TCP header. What else do we need? We need a lower threshold for the window size and we want the users to be able to tweak it. So we will define it in a macro in the header file. Let’s call it TCPWIN_THRES. We also want to count the number of packets per flow falling below the threshold. For that, we need to add a counter (uint32_t, as described in Implementing the t2PrintHeader() callback). We will also rename TCPWIN_STAT_MYPROT to TCPWIN_STAT_THU.

Let’s start by editing the header file:

vi src/tcpWin.h

...

/* ========================================================================== */
/* ------------------------ USER CONFIGURATION FLAGS ------------------------ */
/* ========================================================================== */

#define TCPWIN_THRES 1 // TCP window size threshold undershoot flag // <-- New line (TODO)

/* ========================================================================== */
/* ------------------------- DO NOT EDIT BELOW HERE ------------------------- */
/* ========================================================================== */


// plugin defines

// tcpWinStat status variable
#define TCPWIN_STAT_THU 0x01 // TCP window size threshold undershoot  // <-- Replaced existing line (TODO)


// Plugin structure

typedef struct {       // always large variables first to limit memory fragmentation
    uint32_t winThCnt; // win undershoot count  // <-- New line (TODO)
    uint8_t  stat;     // plugin flow status    // <-- Added comment (TODO)
} tcpWinFlow_t;

...

Now the C file where we reimplement the whole function:

vi src/tcpWin.c

...
void t2OnLayer4(packet_t *packet, unsigned long flowIndex) {
    const flow_t * const flowP = &flows[flowIndex];
    if (flowP->l4Proto != L3_TCP) return; // process only TCP

    // only 1. frag packet will be processed
    if (!t2_is_first_fragment(packet)) return;

    tcpWinFlow_t * const tcpWinFlowP = &tcpWinFlows[flowIndex];
    const tcpHeader_t * const tcpHeader = TCP_HEADER(packet);
    const uint32_t tcpWin = ntohs(tcpHeader->window);

    if (tcpWin < TCPWIN_THRES) {              // is the window size below the threshold?
        tcpWinFlowP->winThCnt++;              // count the packet
        tcpWinFlowP->stat |= TCPWIN_STAT_THU; // set the status bit
    }
}
...

Oh wait!! We fetch a tcpHeader pointer, right?! Don’t we need to care about the fact that it is located at a different position depending on different IP versions? NO! The Anteater takes care of this, just get your pointer, it will always point to the TCP header if there is one.

Could it be that there is no layer 4 header, e.g., due to a short snap length? Yes, it can, so it is good practice to check the packet structure status flags, s. plugindevcheatsheet, or whether the pointer is NULL before using it. This is valid for all layer 2/3/4/7 headers supplied by the core. Later. Now we do not need to worry about that, as I want to keep your first code simple.

Implementing the t2OnFlowTerminate() callback

When the flow hits the core timeout, or is terminated earlier, e.g., by an ALARM or FORCE condition, your plugin receives an t2OnFlowTerminate() callback.

Then all final calculations can be implemented and the output is performed according to your definition in the t2PrintHeader() function. In our case the code looks like this:

vi src/tcpWin.c

...

/*
 * This function is called once a flow is terminated.
 * Output all the statistics for the flow here.
 */
void t2OnFlowTerminate(unsigned long flowIndex, outputBuffer_t *buf) {
    const tcpWinFlow_t * const tcpWinFlowP = &tcpWinFlows[flowIndex];

    tcpWinStat |= tcpWinFlowP->stat;

    OUTBUF_APPEND_U8(buf, tcpWinFlowP->stat);
}

...

We update the global tcpWinStat, which is good practice if we want to report it in the t2PluginReport() callback. Earlier, we added an uint32_t variable in the t2PrintHeader() callback. No we need to feed it to the output buffer. If you recall the previous section, we called it winThCnt:

vi src/tcpWin.c

void t2OnFlowTerminate(unsigned long flowIndex, outputBuffer_t *buf) {
    tcpWinFlow_t * const tcpWinFlowP = &t2PSkelFlows[flowIndex];

    tcpWinStat |= tcpWinFlowP->stat;

    OUTBUF_APPEND_U8(buf, tcpWinFlowP->stat);
    OUTBUF_APPEND_U32(buf, tcpWinFlowP->winThCnt);  // <-- New line (TODO)
}

Pretty simple, he? The BLOCK_BUF pragma of earlier versions is now hidden by the new macros. It is used to prevent T2 from filling the output buffer. E.g. as a rapid output inhibitor, if other sink plugins do the output job.

For example, if you are using netflowSink (a plugin which creates its own buffer), then you do not need to fill in the outputBuffer_t *buf and can thus activate BLOCK_BUF to save space and especially time. In order to honor this mode, enclose your output code with the #if BLOCK_BUF == 0 and #endif // BLOCK_BUF == 0 pragmas. Note, that in the t2OnFlowTerminate(...) callback, you do NOT have access to the packet structure anymore.

So in old style coding the callback implementation looks like that:

void t2OnFlowTerminate(unsigned long flowIndex, outputBuffer_t *buf) {
    tcpWinFlow_t * const tcpWinFlowP = &tcpWinFlows[flowIndex];

    tcpWinStat |= tcpWinFlowP->stat;

#if BLOCK_BUF == 0
    outputBuffer_append(buf, (char*)&tcpWinFlowP->stat, sizeof(uint8_t));
    outputBuffer_append(buf, (char*)&tcpWinFlowP->winThCnt, sizeof(uint32_t));
#endif // BLOCK_BUF == 0
}

I let you decide whether you want to be an old school or a new age T2 hacker.

Note, that if the output does not match your definition in t2PrintHeader(), then you end up with exceptions or output which looks all right but the numbers might be wrong. If you are thorough about this, you will have no problems in the future. Otherwise, several debugging sessions will teach you to be thorough.

Implementing the t2Finalize() callback

When T2 terminates, the plugin flow structure needs to be cleaned up to avoid memory leaks. Later, we will add monitoring and end report features in this function. For starters, a simple free(...) is enough.

vi src/tcpWin.c

...

/*
 * This function is called once all the packets have been processed.
 * Cleanup all used memory here.
 */
void t2Finalize() {
    free(tcpWinFlows);
}

Now we need to take care about the definitions in tcpWin.h. An extract is listed below. It includes t2Plugin.h, which contains all definitions from the core about protocols, internal data structures and definitions. We define the constant TCPWIN_THRES being used in t2OnLayer4(), so that we can change it without changing the code. The bits of the status variable are defined below. We have only one bit. It denotes that packets of the flow undershot our threshold defined in TCPWIN_THRES.

vi src/tcpWin.h

...

// Local includes

#include "t2Plugin.h"


/* ========================================================================== */
/* ------------------------ USER CONFIGURATION FLAGS ------------------------ */
/* ========================================================================== */

#define TCPWIN_THRES 1 // TCP window size threshold undershoot flag

/* ========================================================================== */
/* ------------------------- DO NOT EDIT BELOW HERE ------------------------- */
/* ========================================================================== */


// plugin defines

// tcpWinStat status variable
#define TCPWIN_STAT_THU 0x01 // TCP window size threshold undershoot

// plugin structures
typedef struct {       // always large variables first to limit memory fragmentation
    uint32_t winThCnt; // win undershoot count
    uint8_t stat;      // plugin flow status
} tcpWinFlow_t;

// plugin struct pointer for potential dependencies
extern tcpWinFlow_t *tcpWinFlows;
...

We added a USER CONFIGURATION FLAGS comment, because we had users, not programmers, making edits outside the configuration area and messing up the functionality of a plugin. It happened in the good old times before t2conf.

The variables in the flow structure of the plugin tcpWinFlow_t should always be arranged according to their size to reduce memory fragmentation. So all uint64_t and double are located before float and uint32_t. And last, but not least, come the uint8_t, char and char []. The extern assures that if your plugin is a dependence of another plugin, it can include your .h file and have access to data tcpWin created at runtime.

After you edited the skeleton code you should compare your implementation with tcpWin01.tar.gz. It is always better to write code yourself to get acquainted with a new type of SW.

Compile, run, analyze and extend

Now your plugin is ready to compile with t2build:

t2build tcpWin

Plugin 'tcpWin'

make: Entering directory '/home/wurst/tranalyzer2-0.9.1/plugins/tcpWin/doc'
make: Nothing to be done for 'clean'.
make: Leaving directory '/home/wurst/tranalyzer2-0.9.1/plugins/tcpWin/doc'

tcpWin successfully cleaned

The Meson build system
Version: 1.2.1
Source dir: /home/wurst/tranalyzer2-0.9.1/plugins/tcpWin
Build dir: /home/wurst/tranalyzer2-0.9.1/plugins/tcpWin/build
Build type: native build
Project name: tcpWin
Project version: 0.9.1
C compiler for the host machine: cc (gcc 13.2.1 "cc (GCC) 13.2.1 20230801")
C linker for the host machine: cc ld.bfd 2.41.0
Host machine cpu family: x86_64
Host machine cpu: x86_64
Library m found: YES
Run-time dependency threads found: YES
Found pkg-config: /usr/bin/pkg-config (1.8.1)
Run-time dependency zlib found: YES 1.2.13
Message: ZLIB >= 1.2.8 found
Build targets in project: 1

Found ninja-1.11.1 at /usr/bin/ninja
INFO: autodetecting backend as ninja
INFO: calculating backend command to run: /usr/bin/ninja -C /home/wurst/tranalyzer2-0.9.1/plugins/tcpWin/build -j 32
ninja: Entering directory `/home/wurst/tranalyzer2-0.9.1/plugins/tcpWin/build'
[2/2] Linking target libtcpWin.so

tcpWin successfully built


Plugin tcpWin copied into /home/wurst/.tranalyzer/plugins


BUILDING SUCCESSFUL

See, as we had meson installed, t2build picked it and produced a .so file under your plugins folder.

Now use the alias .tran, to move to the current plugin folder, by default under ~/.tranalyzer/plugins`. You will see your plugin. You actually did it, congratulations!

.tran

ls

100_basicFlow.so   150_tcpWin.so   proto.txt         subnets6_HLP.bin
120_basicStats.so  901_txtSink.so  subnets4_HLP.bin

Then move back directly to your plugin with tcpWin

tcpWin

ls

autogen.sh  CMakeLists.txt  COPYING         doc          meson.build  t2plconf
build       configure.ac    default.config  Makefile.am  src          tests

Everything worked? Nice! If the last command did not work, as tcpWin could not be found, because you created your plugin manually, then invoke

source ~/tranalyzer2-0.9.1/scripts/t2_aliases

Or open a new bash window.

Now invoke t2 with the annoloc2.pcap:

t2 -r ~/data/annoloc2.pcap -w ~/results

================================================================================
Tranalyzer 0.9.1 (Anteater), Cobra. PID: 3928, SID: 666
================================================================================
[INF] Creating flows for L2, IPv4, IPv6
Active plugins:
    01: basicFlow, 0.9.1
    02: basicStats, 0.9.1
    03: tcpWin, 0.9.1
    04: txtSink, 0.9.1
[INF] IPv4 Ver: 5, Rev: 09082023, Range Mode: 0, subnet ranges loaded: 481503 (481.50 K)
[INF] IPv6 Ver: 5, Rev: 09082023, Range Mode: 0, subnet ranges loaded: 41497 (41.50 K)
Processing file: /home/wurst/data/annoloc2.pcap
Link layer type: Ethernet [EN10MB/1]
Snapshot length: 66
Dump start: 1022171701.691172000 sec (Thu 23 May 2002 16:35:01 GMT)
[WRN] snapL2Length: 54 - snapL3Length: 40 - IP length in header: 1500
Dump stop : 1022171726.640398000 sec (Thu 23 May 2002 16:35:26 GMT)
Total dump duration: 24.949226000 sec
Finished processing. Elapsed time: 0.263058000 sec
Finished unloading flow memory. Time: 0.352172000 sec
Percentage completed: 100.00%
Number of processed packets: 1219015 (1.22 M)
Number of processed bytes: 64082726 (64.08 M)
Number of raw bytes: 844642686 (844.64 M)
Number of pad bytes: 8591685635 (8.59 G)
Number of pcap bytes: 83586990 (83.59 M)
Number of IPv4 packets: 1218588 (1.22 M) [99.96%]
Number of IPv6 packets: 180 [0.01%]
Number of A packets: 564228 (564.23 K) [46.29%]
Number of B packets: 654787 (654.79 K) [53.71%]
Number of A bytes: 29447896 (29.45 M) [45.95%]
Number of B bytes: 34634830 (34.63 M) [54.05%]
Average A packet load: 52.19
Average B packet load: 52.89
--------------------------------------------------------------------------------
basicStats: Biggest L2 flow talker: 00:d0:02:6d:78:00: 57 [0.00%] packets
basicStats: Biggest L2 flow talker: 00:d0:02:6d:78:00: 2622 (2.62 K) [0.00%] bytes
basicStats: Biggest L3 flow talker: 138.212.189.38 (JP): 23601 (23.60 K) [1.94%] packets
basicStats: Biggest L3 flow talker: 138.212.189.38 (JP): 33731054 (33.73 M) [52.64%] bytes
tcpWin: Aggregated tcpWinStat=0x01
--------------------------------------------------------------------------------
Headers count: min: 2, max: 5, average: 3.01
Number of ARP packets: 247 [0.02%]
Number of GRE packets: 20 [0.00%]
Number of IGMP packets: 12 [0.00%]
Number of ICMP packets: 3059 (3.06 K) [0.25%]
Number of ICMPv6 packets: 11 [0.00%]
Number of TCP packets: 948743 (948.74 K) [77.83%]
Number of TCP bytes: 52643546 (52.64 M) [82.15%]
Number of UDP packets: 266900 (266.90 K) [21.89%]
Number of UDP bytes: 11234272 (11.23 M) [17.53%]
Number of IPv4 fragmented packets: 2284 (2.28 K) [0.19%]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Number of processed      flows: 17100 (17.10 K)
Number of processed L2   flows: 99 [0.58%]
Number of processed IPv4 flows: 16937 (16.94 K) [99.05%]
Number of processed IPv6 flows: 64 [0.37%]
Number of processed A    flows: 9719 (9.72 K) [56.84%]
Number of processed B    flows: 7381 (7.38 K) [43.16%]
Number of request        flows: 9676 (9.68 K) [56.58%]
Number of reply          flows: 7424 (7.42 K) [43.42%]
Total   A/B    flow asymmetry: 0.14
Total req/rply flow asymmetry: 0.13
Number of processed   packets/flows: 71.29
Number of processed A packets/flows: 58.05
Number of processed B packets/flows: 88.71
Number of processed total packets/s: 48859.83 (48.86 K)
Number of processed A+B   packets/s: 48859.83 (48.86 K)
Number of processed A     packets/s: 22615.05 (22.61 K)
Number of processed   B   packets/s: 26244.78 (26.24 K)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Number of average processed flows/s: 685.39
Average full raw bandwidth: 270835712 b/s (270.84 Mb/s)
Average snapped bandwidth : 20548206 b/s (20.55 Mb/s)
Average full bandwidth : 270268480 b/s (270.27 Mb/s)
Max number of flows in memory: 17100 (17.10 K) [6.52%]
Memory usage: 0.06 GB [0.09%]
Aggregated flowStat=0x0c0098fa0222d044
[WRN] L3 SnapLength < Length in IP header
[WRN] L4 header snapped
[WRN] Consecutive duplicate IP ID
[WRN] IPv4/6 payload length > framing length
[WRN] IPv4/6 fragmentation header packet missing
[WRN] IPv4/6 packet fragmentation sequence not finished
[INF] Stop dissecting: Clipped packet, unhandled protocol or subsequent fragment
[INF] Layer 2 flows
[INF] IPv4 flows
[INF] IPv6 flows
[INF] ARP
[INF] IPv4/6 fragmentation
[INF] IPv4/6 in IPv4/6
[INF] GRE encapsulation
[INF] GTP tunnel
[INF] SSDP/UPnP

And look into the ~/results folder:

ls ~/results/

annoloc2_flows.txt  annoloc2_headers.txt

Look into the header file and you should spot in column number 22,23 your variables defined in t2PrintHeader(), including type, name and description.

cat ~/results/annoloc2_headers.txt

# Date: 1692814321.415041 sec (Wed 23 Aug 2023 20:12:01 CEST)
# Tranalyzer 0.9.1 (Anteater), Cobra
# Core configuration: L2, IPv4, IPv6
# SensorID: 666
# PID: 73789
# Command line: /home/wurst/tranalyzer2-0.9.1/tranalyzer2/build/tranalyzer -r /home/wurst/data/annoloc2.pcap -w /home/wurst/results/
# HW info: nudel;Linux;5.15.25-kacke2-1;#1 SMP PREEMPT Wed Aug 9 06:31:14 UTC 2023;x86_64
# SW info: libpcap version 1.10.4 (with TPACKET_V3)
#
# Plugins loaded:
#   01: basicFlow, version 0.9.1
#   02: basicStats, version 0.9.1
#   03: tcpWin, version 0.9.1
#   04: txtSink, version 0.9.1
#
# Col No.      Type           Name           Description
1              C              dir            Flow direction
2              U64            flowInd        Flow index
3              H64            flowStat       Flow status and warnings
4              U64.U32        timeFirst      Date time of first packet
5              U64.U32        timeLast       Date time of last packet
6              U64.U32        duration       Flow duration
7              U8             numHdrDesc     Number of different headers descriptions
8              U16:R          numHdrs        Number of headers (depth) in hdrDesc
9              SC:R           hdrDesc        Headers description
10             MAC:R          srcMac         Mac source
11             MAC:R          dstMac         Mac destination
12             H16            ethType        Ethernet type
13             U16:R          vlanID         VLAN IDs
14             IPX            srcIP          Source IP address
15             SC             srcIPCC        Source IP country
16             S              srcIPOrg       Source IP organization
17             U16            srcPort        Source port
18             IPX            dstIP          Destination IP address
19             SC             dstIPCC        Destination IP country
20             S              dstIPOrg       Destination IP organization
21             U16            dstPort        Destination port
22             U8             l4Proto        Layer 4 protocol
23             U64            numPktsSnt     Number of transmitted packets
24             U64            numPktsRcvd    Number of received packets
25             U64            numBytesSnt    Number of transmitted bytes
26             U64            numBytesRcvd   Number of received bytes
27             U16            minPktSz       Minimum layer 3 packet size
28             U16            maxPktSz       Maximum layer 3 packet size
29             F              avePktSize     Average layer 3 packet size
30             F              stdPktSize     Standard deviation layer 3 packet size
31             F              minIAT         Minimum IAT
32             F              maxIAT         Maximum IAT
33             F              aveIAT         Average IAT
34             F              stdIAT         Standard deviation IAT
35             F              pktps          Sent packets per second
36             F              bytps          Sent bytes per second
37             F              pktAsm         Packet stream asymmetry
38             F              bytAsm         Byte stream asymmetry
39             H8             tcpWinStat     TCP window size threshold status
40             U32            tcpWinThCnt    TCP window size threshold count

In the flow file should be your output if you did everything right:

tcol ~/results/annoloc2_flows.txt

%dir  flowInd  flowStat            timeFirst             timeLast              duration     numHdrDesc  numHdrs  hdrDesc                 srcMac             dstMac             ethType  vlanID  srcIP                               srcIPCC  srcIPOrg                    srcPort  dstIP                  dstIPCC  dstIPOrg                   dstPort  l4Proto  numPktsSnt  numPktsRcvd  numBytesSnt  numBytesRcvd  minPktSz  maxPktSz  avePktSize  stdPktSize  minIAT  maxIAT    aveIAT     stdIAT        pktps     bytps     pktAsm  bytAsm  tcpWinStat  tcpWinThCnt
A     59       0x0400000200004000  1022171701.692762000  1022171701.692762000  0.000000000  1           3        eth:ipv4:icmp           00:80:48:b3:22:ef  00:d0:02:6d:78:00  0x0800           138.212.187.10                      jp       "ASAHI KASEI CORPORATION"   0        201.116.148.149        mx       "Uninet SA de CV"          0        1        1           0            28           0             28        28        28          0           0       0         0          0             0         0         1       1       0x00        0
A     107      0x0400000200004000  1022171701.700133000  1022171701.700133000  0.000000000  1           3        eth:ipv4:udp            00:00:1c:b6:1a:53  00:d0:02:6d:78:00  0x0800           138.212.184.165                     jp       "ASAHI KASEI CORPORATION"   8889     19.112.107.128         us       "MAINT-APNIC-AP"           2001     17       1           0            254          0             254       254       254         0           0       0         0          0             0         0         1       1       0x00        0
A     136      0x0400000000004000  1022171701.700983000  1022171701.700983000  0.000000000  1           3        eth:ipv4:tcp            00:01:02:b8:58:8a  00:d0:02:6d:78:00  0x0800           138.212.189.36                      jp       "ASAHI KASEI CORPORATION"   1044     205.25.217.73          us       "Not allocated by APNIC"   29981    6        1           0            0            0             0         0         0           0           0       0         0          0             0         0         1       0       0x00        0
A     191      0x0400000000004000  1022171701.704267000  1022171701.704267000  0.000000000  1           3        eth:ipv4:tcp            00:48:54:7a:06:6a  00:d0:02:6d:78:00  0x0800           138.212.190.87                      jp       "ASAHI KASEI CORPORATION"   1068     70.128.194.122         us       "AT&T Corp"                1863     6        1           0            0            0             0         0         0           0           0       0         0          0             0         0         1       0       0x00        0
A     243      0x0400000200004000  1022171701.706591000  1022171701.706591000  0.000000000  1           3        eth:ipv4:udp            00:04:76:24:0e:f4  00:d0:02:6d:78:00  0x0800           138.212.188.99                      jp       "ASAHI KASEI CORPORATION"   7778     83.221.58.33           de       "WPPS-MNT"                 2009     17       1           0            250          0             250       250       250         0           0       0         0          0             0         0         1       1       0x00        0
A     260      0x0c00880200028000  1022171701.707777000  1022171701.707777000  0.000000000  1           4        eth:ipv4:ipv6:UNK(168)  00:d0:02:6d:78:00  00:60:08:2c:ca:8e  0x86dd           cfb6:1c18:5010:faf0:7f66:0:101:80a  -        "-"                         0        6c2:6a7f:1:384b::c100  -        "-"                        0        168      1           0            12           0             12        12        12          0           0       0         0          0             0         0         1       1       0x00        0
A     102      0x0400000200004000  1022171701.699999000  1022171701.847857000  0.147858000  1           3        eth:ipv4:tcp            00:d0:02:6d:78:00  00:d0:b7:e8:9e:bb  0x0800           200.8.254.121                       ve       "Corporación Telemic CA"   1174     138.212.190.162        jp       "ASAHI KASEI CORPORATION"  6020     6        2           2            3            3             0         3         1.5         1.06066     0       0.147858  0.073929   0.05227569    13.52649  20.28974  0       0       0x00        0
B     102      0x0400000200004001  1022171701.707779000  1022171701.709106000  0.001327000  1           3        eth:ipv4:tcp            00:d0:b7:e8:9e:bb  00:d0:02:6d:78:00  0x0800           138.212.190.162                     jp       "ASAHI KASEI CORPORATION"   6020     200.8.254.121          ve       "Corporación Telemic CA"  1174     6        2           2            3            3             0         3         1.5         1.06066     0       0.001327  0.0006635  0.0004691654  1507.159  2260.739  0       0       0x00        0
A     265      0x0400000000004000  1022171701.709116000  1022171701.709116000  0.000000000  1           3        eth:ipv4:tcp            00:d0:02:6d:78:00  00:50:fc:0e:21:56  0x0800           209.171.12.143                      ca       "TELUS Communications Inc"  4987     138.212.185.230        jp       "ASAHI KASEI CORPORATION"  41250    6        1           0            0            0             0         0         0           0           0       0         0          0             0         0         1       0       0x01        1
...

If you scroll to the far right, you will see your output columns. So the flows where the status bit 0 is set have indeed suffered from a window size crunch to 0, meaning that the machine cannot swallow any packets anymore. The counter indicates how often this happened during the flow.

Now think! What are we interested in now? We want all the flows which are greater than a tcpWinThCnt relative to the packets being sent back to the sender with window size 0. But how greater? Let’s choose a tcpWinThCnt large enough so that we do not select all the short flows and the relative counts to 90%. So before you change the code of your plugin it is smart to understand the problem at hand and code first questions in tawk. The tawk would then look like this:

tawk 'hdr() || ($tcpWinThCnt > 20 && ($tcpWinThCnt / $numPktsSnt) > 0.9)' ~/results/annoloc2_flows.txt | tcol

%dir  flowInd  flowStat            timeFirst             timeLast              duration      numHdrDesc  numHdrs  hdrDesc       srcMac             dstMac             ethType  vlanID  srcIP            srcIPCC  srcIPOrg                          srcPort  dstIP            dstIPCC  dstIPOrg                       dstPort  l4Proto  numPktsSnt  numPktsRcvd  numBytesSnt  numBytesRcvd  minPktSz  maxPktSz  avePktSize  stdPktSize  minIAT  maxIAT    aveIAT     stdIAT      pktps     bytps     pktAsm       bytAsm     tcpWinStat  tcpWinThCnt
B     633      0x0400000200004001  1022171701.743464000  1022171720.404545000  18.661081000  1           3        eth:ipv4:tcp  00:60:08:78:1b:63  00:d0:02:6d:78:00  0x0800           138.212.187.203  jp       "ASAHI KASEI CORPORATION"         6699     19.123.222.7     us       "MAINT-APNIC-AP"               1430     6        72          72           22812        0             18        928       316.8333    207.2777    0       0.590107  0.2591818  0.0801546   3.858297  1222.437  0            1          0x01        72
B     149      0x0400000200004001  1022171701.801634000  1022171726.463532000  24.661898000  1           3        eth:ipv4:tcp  00:d0:02:6d:78:00  00:10:a7:02:4d:33  0x0800           36.152.156.46    cn       "China Mobile Communications Co"  3296     138.212.185.188  jp       "ASAHI KASEI CORPORATION"      6699     6        79          78           17495        37            0         1172      221.4557    243.5155    0       0.987917  0.312176   0.119054    3.203322  709.3939  0.006369427  0.9957792  0x01        76
B     1147     0x0400000200004001  1022171701.939343000  1022171726.416686000  24.477343000  1           3        eth:ipv4:tcp  00:60:08:78:1b:63  00:d0:02:6d:78:00  0x0800           138.212.187.203  jp       "ASAHI KASEI CORPORATION"         6699     68.239.17.59     us       "MCI Communications Services"  4768     6        76          76           26384        0             11        1132      347.1579    237.4721    0       0.600271  0.3220703  0.07129292  3.104912  1077.895  0            1          0x01        76

Hmmm, only B flows. The bytAsm is 1, so no content is sent from A to B. To see a clearer picture lets have a more general tawk and store the resulting flow index in an environment variable and use it in another tawk where we select all the relevant A/B flows.

f="$(tawk -H '$tcpWinThCnt > 20 && ($tcpWinThCnt / $numPktsSnt) > 0.9 { s = s ";" $flowInd } END { print substr(s,2 ) }' ~/results/annoloc2_flows.txt)"

tawk -v f="$f" 'flow(f)' ~/results/annoloc2_flows.txt | tcol

%dir  flowInd  flowStat            timeFirst             timeLast              duration      numHdrDesc  numHdrs  hdrDesc       srcMac             dstMac             ethType  vlanID  srcIP            srcIPCC  srcIPOrg                          srcPort  dstIP            dstIPCC  dstIPOrg                          dstPort  l4Proto  numPktsSnt  numPktsRcvd  numBytesSnt  numBytesRcvd  minPktSz  maxPktSz  avePktSize  stdPktSize  minIAT  maxIAT    aveIAT     stdIAT      pktps     bytps     pktAsm        bytAsm      tcpWinStat  tcpWinThCnt
A     633      0x0400000000004000  1022171701.743423000  1022171720.669713000  18.926290000  1           3        eth:ipv4:tcp  00:d0:02:6d:78:00  00:60:08:78:1b:63  0x0800           19.123.222.7     us       "MAINT-APNIC-AP"                  1430     138.212.187.203  jp       "ASAHI KASEI CORPORATION"         6699     6        72          72           0            22812         0         0         0           0           0       0.700571  0.2628651  0.1036983   3.804232  0         0             -1          0x01        1
B     633      0x0400000200004001  1022171701.743464000  1022171720.404545000  18.661081000  1           3        eth:ipv4:tcp  00:60:08:78:1b:63  00:d0:02:6d:78:00  0x0800           138.212.187.203  jp       "ASAHI KASEI CORPORATION"         6699     19.123.222.7     us       "MAINT-APNIC-AP"                  1430     6        72          72           22812        0             18        928       316.8333    207.2777    0       0.590107  0.2591818  0.0801546   3.858297  1222.437  0             1           0x01        72
A     149      0x0400000200004000  1022171701.701921000  1022171725.949576000  24.247655000  1           3        eth:ipv4:tcp  00:10:a7:02:4d:33  00:d0:02:6d:78:00  0x0800           138.212.185.188  jp       "ASAHI KASEI CORPORATION"         6699     36.152.156.46    cn       "China Mobile Communications Co"  3296     6        78          79           37           17495         0         36        0.474359    3.579886    0       0.901739  0.3108674  0.1130646   3.216806  1.525921  -0.006369427  -0.9957792  0x00        0
B     149      0x0400000200004001  1022171701.801634000  1022171726.463532000  24.661898000  1           3        eth:ipv4:tcp  00:d0:02:6d:78:00  00:10:a7:02:4d:33  0x0800           36.152.156.46    cn       "China Mobile Communications Co"  3296     138.212.185.188  jp       "ASAHI KASEI CORPORATION"         6699     6        79          78           17495        37            0         1172      221.4557    243.5155    0       0.987917  0.312176   0.119054    3.203322  709.3939  0.006369427   0.9957792   0x01        76
A     1147     0x0400000000004000  1022171701.937172000  1022171726.415550000  24.478378000  1           3        eth:ipv4:tcp  00:d0:02:6d:78:00  00:60:08:78:1b:63  0x0800           68.239.17.59     us       "MCI Communications Services"     4768     138.212.187.203  jp       "ASAHI KASEI CORPORATION"         6699     6        76          76           0            26384         0         0         0           0           0       0.838332  0.322084   0.09550799  3.104781  0         0             -1          0x00        0
B     1147     0x0400000200004001  1022171701.939343000  1022171726.416686000  24.477343000  1           3        eth:ipv4:tcp  00:60:08:78:1b:63  00:d0:02:6d:78:00  0x0800           138.212.187.203  jp       "ASAHI KASEI CORPORATION"         6699     68.239.17.59     us       "MCI Communications Services"     4768     6        76          76           26384        0             11        1132      347.1579    237.4721    0       0.600271  0.3220703  0.07129292  3.104912  1077.895  0             1           0x01        76

That confirms the picture. It must be data streaming, where the flow initiating IP is told not to send data anymore. To effectively select such flows we need the $tcpWinThCnt/$numPktsSnt column which tells us the packet rate relative to the total TCP packets. Moreover the initial window size might be beneficial to know, for two reasons. First, it gives us an indication of where the window size algorithm of a particular flow starts, and it contains information about the type of operations behind the flow or service. Second it also indicates, together with the TTL, the type of operating system, so let’s add this as well.

In order to do so, we need to extract the TTL and the window size behind the L2_FLOW check in the t2OnNewFlow() callback. As T2 is currently in IPv4/6 and L2 mode the L3 header is different for IPv4 or IPv6. In order to spare you guys some complicated if’s and pragmas, We wrote compiler functions which return the type of IP based on the current mode of T2 core. This is achieved by the command if (PACKET_IS_IPV6(packet)).

It is always good practice to cast the l3HdrP from the packet structure to the appropriate constant ipHeader structure defined in tranalyzer2/src/proto/ipv4.h. Then, we store the TTL into flow variable, later to be defined in the .h file. At last, only for TCP, we use the TCP_HEADER(packet) macro, which casts the l4HdrP to the tcpHeader_t structure. Then we store the window size into a yet not defined flow variable. Note, that we need to reverse the byte order, if you are working on a little endian machine, which is unfortunately 99% the case on this planet. Just add the lines marked with // <--. Note that if you omit the TCP protocol check, then all other protocols will write to tcpWinInit, thus creating false output instead of 0.

vi src/tcpWin.c

void t2OnNewFlow(packet_t *packet, unsigned long flowIndex) {
    tcpWinFlow_t * const tcpWinFlowP = &tcpWinFlows[flowIndex];
    memset(tcpWinFlowP, '\0', sizeof(*tcpWinFlowP)); // set everything to 0

    const flow_t * const flowP = &flows[flowIndex];                 // <-- (TODO)
    if (flowP->status & L2_FLOW) return; // Layer 2 flow. No L3/4 pointers, so return // <-- (TODO)

    if (PACKET_IS_IPV6(packet)) {                                   // <-- IPv6 (TODO)
        const ip6Header_t * const ip6Header = IPV6_HEADER(packet);  // <-- Get IPv6 header (TODO)
        tcpWinFlowP->ttl = ip6Header->ip_ttl;                       // <-- Store TTL in flow (TODO)
    } else {                                                        // <-- IPv4 (TODO)
        const ipHeader_t * const ipHeader = IPV4_HEADER(packet);    // <-- Get IPv4 header (TODO)
        tcpWinFlowP->ttl = ipHeader->ip_ttl;                        // <-- Store TTL in flow (TODO)
    }

    if (flowP->l4Proto != L3_TCP) return; // process only TCP       // <-- only TCP (TODO)

    const tcpHeader_t * const tcpHeader = TCP_HEADER(packet);       // <-- Get TCP header (TODO)
    tcpWinFlowP->tcpWinInit = ntohs(tcpHeader->window);             // <-- convert window size to little endian and store in flow (TODO)
}

You are all set for the first packet and the flow creation process. Sure, the TTL could change during the flow, in case of multiple path routing, let’s ignore that for the moment. For our relative window threshold count, we need the count of all TCP packets. Just add a pktTcpCnt variable and increment it, see line marked with // <--.

vi src/tcpWin.c

void t2OnLayer4(packet_t *packet, unsigned long flowIndex) {
    const flow_t * const flowP = &flows[flowIndex];
    if (flowP->l4Proto != L3_TCP) return; // process only TCP

    // only 1. frag packet will be processed
    if (!t2_is_first_fragment(packet)) return;

    tcpWinFlow_t * const tcpWinFlowP = &tcpWinFlows[flowIndex];
    const tcpHeader_t * const tcpHeader = (tcpHeader_t*)packet->l4HdrP;
    const uint32_t tcpWin = ntohs(tcpHeader->window);

    tcpWinFlowP->pktTcpCnt++;               // <-- count all TCP packets (TODO)

    if (tcpWin < TCPWIN_THRES) {            // is window size below threshold?
        tcpWinFlowP->winThCnt++;            // count the packet
        tcpWinFlowP->stat |= TCPWIN_THU;    // set the status bit
    }
}

Next we need to add two columns marked by // <-- in the flow file defined in t2PrintHeader(). A 32-bit unsigned integer for the window size and an 8-bit unsigned integer for the TTL.

vi src/tcpWin.c

binary_value_t* t2PrintHeader() {
    binary_value_t *bv = NULL;

    BV_APPEND_H8(bv, "tcpWinStat", "TCP window size threshold status");                             // 8 bit hex variable
    BV_APPEND_U8(bv, "tcpWinIpTTL", "IP TTL");                                                      // <-- 8 bit unsigned variable
    BV_APPEND_U32(bv, "tcpInitWinSz", "TCP initial effective window size");                         // <-- uint32 bit variable
    BV_APPEND_U32(bv, "tcpWinThCnt", "TCP window size threshold count");                            // 32 unsigned int variable
    BV_APPEND_FLT(bv, "tcpWinSzThRt", "TCP packet count ratio below window size WINMIN threshold"); // <-- float variable

    return bv;
}

At flow timeout or termination, we would like to see the TTL and the initial window size in the output. We do so by adding the lines marked by // <--. You may also use the new macros without the need of the BLOCK_BUF macro, s. OUTBUF cheatsheet

vi src/tcpWin.c

void t2OnFlowTerminate(unsigned long flowIndex, outputBuffer_t *buf) {
    tcpWinFlow_t * const tcpWinFlowP = &t2PSkelFlows[flowIndex];

    OUTBUF_APPEND_U8(buf, tcpWinFlowP->stat);
    OUTBUF_APPEND_U8(buf, tcpWinFlowP->ttl);         // <-- add 8-bit ttl to output buffer
    OUTBUF_APPEND_U32(buf, tcpWinFlowP->tcpWinInit); // <-- add init window size 32bit unsigned int to output buffer
    OUTBUF_APPEND_U32(buf, tcpWinFlowP->winThCnt);

    float f = 0.0;                                                                               // <-- set f to 0 if not TCP
    if (tcpWinFlowP->pktTcpCnt) f = (float)tcpWinFlowP->winThCnt/(float)tcpWinFlowP->pktTcpCnt;  // <-- calculate the relative threshold count

    OUTBUF_APPEND_FLT(buf, f); // <-- add a float (32bit) result to output buffer // <-- Output f
}

Open tcpWin.h and add the lines marked by // <--.

vi src/tcpWin.h

...
// plugin structures
typedef struct { // always large variables first to limit memory fragmentation
    uint32_t pktTcpCnt;   // <--
    uint32_t tcpWinInit;  // <--
    uint32_t winThCnt;
    uint8_t  ttl;         // <--
    uint8_t  stat;
} tcpWinFlow_t;
...

Note the position larger variables on top to avoid memory fragmentation.

After you edited the skeleton code you should compare your implementation with tcpWin02.tar.gz. It is always better to write code yourself to get acquainted with a new type of SW.

Now you are all set, compile your plugin and rerun T2.

t2build tcpWin

t2 -r ~/data/annoloc2.pcap -w ~/results

Change to your results window and get the previous tawk commands. Change the variable in the tawk to the new column $tcpWinSzThRt > 0.9 and invoke both tawk commands again:

f="$(tawk -H '$tcpWinThCnt > 20 && $tcpWinSzThRt > 0.9 { s = s ";" $flowInd } END { print substr(s, 2) }' ~/results/annoloc2_flows.txt)"

tawk -v f="$f" 'flow(f)' ~/results/annoloc2_flows.txt | tcol

%dir  flowInd  flowStat            timeFirst             timeLast              duration      numHdrDesc  numHdrs  hdrDesc       srcMac             dstMac             ethType  vlanID  srcIP            srcIPCC  srcIPOrg                          srcPort  dstIP            dstIPCC  dstIPOrg                          dstPort  l4Proto  numPktsSnt  numPktsRcvd  numBytesSnt  numBytesRcvd  minPktSz  maxPktSz  avePktSize  stdPktSize  minIAT  maxIAT    aveIAT     stdIAT      pktps     bytps     pktAsm        bytAsm      tcpWinStat  tcpWinIpTTL  tcpInitWinSz  tcpWinThCnt  tcpWinSzThRt
A     633      0x0400000000004000  1022171701.743423000  1022171720.669713000  18.926290000  1           3        eth:ipv4:tcp  00:d0:02:6d:78:00  00:60:08:78:1b:63  0x0800           19.123.222.7     us       "MAINT-APNIC-AP"                  1430     138.212.187.203  jp       "ASAHI KASEI CORPORATION"         6699     6        72          72           0            22812         0         0         0           0           0       0.700571  0.2628651  0.1036983   3.804232  0         0             -1          0x01        115          32378         1            0.01388889
B     633      0x0400000200004001  1022171701.743464000  1022171720.404545000  18.661081000  1           3        eth:ipv4:tcp  00:60:08:78:1b:63  00:d0:02:6d:78:00  0x0800           138.212.187.203  jp       "ASAHI KASEI CORPORATION"         6699     19.123.222.7     us       "MAINT-APNIC-AP"                  1430     6        72          72           22812        0             18        928       316.8333    207.2777    0       0.590107  0.2591818  0.0801546   3.858297  1222.437  0             1           0x01        128          0             72           1
A     149      0x0400000200004000  1022171701.701921000  1022171725.949576000  24.247655000  1           3        eth:ipv4:tcp  00:10:a7:02:4d:33  00:d0:02:6d:78:00  0x0800           138.212.185.188  jp       "ASAHI KASEI CORPORATION"         6699     36.152.156.46    cn       "China Mobile Communications Co"  3296     6        78          79           37           17495         0         36        0.474359    3.579886    0       0.901739  0.3108674  0.1130646   3.216806  1.525921  -0.006369427  -0.9957792  0x00        127          17520         0            0
B     149      0x0400000200004001  1022171701.801634000  1022171726.463532000  24.661898000  1           3        eth:ipv4:tcp  00:d0:02:6d:78:00  00:10:a7:02:4d:33  0x0800           36.152.156.46    cn       "China Mobile Communications Co"  3296     138.212.185.188  jp       "ASAHI KASEI CORPORATION"         6699     6        79          78           17495        37            0         1172      221.4557    243.5155    0       0.987917  0.312176   0.119054    3.203322  709.3939  0.006369427   0.9957792   0x01        112          36            76           0.9620253
A     1147     0x0400000000004000  1022171701.937172000  1022171726.415550000  24.478378000  1           3        eth:ipv4:tcp  00:d0:02:6d:78:00  00:60:08:78:1b:63  0x0800           68.239.17.59     us       "MCI Communications Services"     4768     138.212.187.203  jp       "ASAHI KASEI CORPORATION"         6699     6        76          76           0            26384         0         0         0           0           0       0.838332  0.322084   0.09550799  3.104781  0         0             -1          0x00        113          17095         0            0
B     1147     0x0400000200004001  1022171701.939343000  1022171726.416686000  24.477343000  1           3        eth:ipv4:tcp  00:60:08:78:1b:63  00:d0:02:6d:78:00  0x0800           138.212.187.203  jp       "ASAHI KASEI CORPORATION"         6699     68.239.17.59     us       "MCI Communications Services"     4768     6        76          76           26384        0             11        1132      347.1579    237.4721    0       0.600271  0.3220703  0.07129292  3.104912  1077.895  0             1           0x01        128          0             76           1

Conclusion

You see that you have your first operational flow mode plugin which you can extend to your liking.

You can download the final version of the tcpWin plugin.

The next tutorial will teach you how to add plugin end report.

Have fun!

See also