The basics: your first flow plugin
Contents
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.
...
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"
("myPlugin", "0.9.1", 0, 9); T2_PLUGIN_INIT
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):
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;
("myPlugin", "0.9.1", 0, 9);
T2_PLUGIN_INIT
void t2Init() {
(myPluginFlows);
T2_PLUGIN_STRUCT_NEW}
* t2PrintHeader() {
binary_value_t*bv = NULL;
binary_value_t (bv, "myPluginStat", "myPlugin status");
BV_APPEND_H8(bv, "myPluginNumTCP", "myPlugin number of TCP packets");
BV_APPEND_U32return bv;
}
void t2OnNewFlow(packet_t *packet, unsigned long flowIndex) {
* const myPluginFlowP = &myPluginFlows[flowIndex];
myPluginFlow_t (myPluginFlowP, '\0', sizeof(*myPluginFlowP));
memset
if (packet->l4Proto == L3_TCP) {
->stat |= MYPLUGIN_STAT_TCP;
myPluginFlowP}
}
void t2OnLayer4(packet_t *packet, unsigned long flowIndex) {
* const myPluginFlowP = &myPluginFlows[flowIndex];
myPluginFlow_t if (myPluginFlowP->stat & MYPLUGIN_STAT_TCP) {
->num_tcp++;
myPluginFlowP}
}
void t2OnFlowTerminate(unsigned long flowIndex, outputBuffer_t *buf) {
* const myPluginFlowP = &myPluginFlows[flowIndex];
myPluginFlow_t // Update global counters
+= myPluginFlowP->num_tcp;
num_tcp |= myPluginFlowP->status;
myPluginStat // Populate output buffer
(buf, myPluginFlowP->status);
OUTBUF_APPEND_U8(buf, myPluginFlowP->num_tcp);
OUTBUF_APPEND_U32}
void t2PluginReport(FILE *stream) {
(stream, plugin_name, myPluginStat);
T2_FPLOF_AGGR_HEX(stream, plugin_name, "Number of TCP packets", num_tcp, numPackets);
T2_FPLOG_NUMP}
void t2Finalize() {
(myPluginFlowP);
free}
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
void t2OnLayer2(packet_t *packet, unsigned long flowIndex)
void t2OnLayer4(packet_t *packet, unsigned long flowIndex)
void t2Monitoring(FILE *stream, uint8 state)
END flow: timeout or last packet detected
void t2OnFlowTerminate(unsigned long flowIndex, outputBuffer_t *buf)
void t2PluginReport(FILE *stream)
void t2BufferToSink(outputBuffer_t *buf, binary_value_t *bv)
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:
void t2Init()
binary_value_t* t2PrintHeader()
void t2OnNewFlow(packet_t *packet, unsigned long flowIndex)
void t2OnLayer2(packet_t *packet, unsigned long flowIndex)
void t2OnLayer4(packet_t *packet, unsigned long flowIndex)
void t2OnFlowTerminate(unsigned long flowIndex, outputBuffer_t *buf)
void t2Finalize()
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
):
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
*/
("tcpWin", "0.9.1", 0, 9);
T2_PLUGIN_INIT
...
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.
("tcpWin", "0.9.1", 0, 9, "tcpFlags,tcpStates"); T2_PLUGIN_INIT_WITH_DEPS
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;'
*/
*tcpWinFlows;
tcpWinFlow_t
...
/*
* This function is called before processing any packet.
*/
void t2Init() {
// allocate struct for all flows and initialize to 0
(tcpWinFlows);
T2_PLUGIN_STRUCT_NEW}
...
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
*/
* t2PrintHeader() {
binary_value_t*bv = NULL;
binary_value_t (bv, "tcpWinStat", "tcpWin status");
BV_APPEND_H8return 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
...
* t2PrintHeader() {
binary_value_t*bv = NULL;
binary_value_t (bv, "tcpWinStat", "TCP window size threshold status"); // <-- Updated description (TODO)
BV_APPEND_H8(bv, "tcpWinThCnt", "TCP window size threshold count"); // <-- New line (TODO)
BV_APPEND_U32return 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:
...
* t2PrintHeader() {
binary_value_t*bv = NULL;
binary_value_t = 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));
bv 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
* const tcpWinFlowP = &tcpWinFlows[flowIndex];
tcpWinFlow_t (tcpWinFlowP, '\0', sizeof(*tcpWinFlowP));
memset}
...
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) {
* const tcpWinFlowP = &tcpWinFlows[flowIndex];
tcpWinFlow_t
// only 1. frag packet will be processed
if (!t2_is_first_fragment(packet)) return;
->stat |= TCPWIN_STAT_MYPROT;
tcpWinFlowP++;
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;
* const tcpWinFlowP = &tcpWinFlows[flowIndex];
tcpWinFlow_t 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?
->winThCnt++; // count the packet
tcpWinFlowP->stat |= TCPWIN_STAT_THU; // set the status bit
tcpWinFlowP}
}
...
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];
|= tcpWinFlowP->stat;
tcpWinStat
(buf, tcpWinFlowP->stat);
OUTBUF_APPEND_U8}
...
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) {
* const tcpWinFlowP = &t2PSkelFlows[flowIndex];
tcpWinFlow_t
|= tcpWinFlowP->stat;
tcpWinStat
(buf, tcpWinFlowP->stat);
OUTBUF_APPEND_U8(buf, tcpWinFlowP->winThCnt); // <-- New line (TODO)
OUTBUF_APPEND_U32}
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) {
* const tcpWinFlowP = &tcpWinFlows[flowIndex];
tcpWinFlow_t
|= tcpWinFlowP->stat;
tcpWinStat
#if BLOCK_BUF == 0
(buf, (char*)&tcpWinFlowP->stat, sizeof(uint8_t));
outputBuffer_append(buf, (char*)&tcpWinFlowP->winThCnt, sizeof(uint32_t));
outputBuffer_append#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() {
(tcpWinFlows);
free}
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
:
Plugin 'tcpWin' make: Entering directory '/home/wurst/tranalyzer2-0.9.3/plugins/tcpWin/doc' make: Nothing to be done for 'clean'. make: Leaving directory '/home/wurst/tranalyzer2-0.9.3/plugins/tcpWin/doc' tcpWin successfully cleaned The Meson build system Version: 1.2.1 Source dir: /home/wurst/tranalyzer2-0.9.3/plugins/tcpWin Build dir: /home/wurst/tranalyzer2-0.9.3/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.3/plugins/tcpWin/build -j 32 ninja: Entering directory `/home/wurst/tranalyzer2-0.9.3/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.3/scripts/t2_aliases
Or open a new bash window.
Now invoke t2
with the annoloc2.pcap:
================================================================================ 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.3/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) {
* const tcpWinFlowP = &tcpWinFlows[flowIndex];
tcpWinFlow_t (tcpWinFlowP, '\0', sizeof(*tcpWinFlowP)); // set everything to 0
memset
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)
->ttl = ip6Header->ip_ttl; // <-- Store TTL in flow (TODO)
tcpWinFlowP} else { // <-- IPv4 (TODO)
const ipHeader_t * const ipHeader = IPV4_HEADER(packet); // <-- Get IPv4 header (TODO)
->ttl = ipHeader->ip_ttl; // <-- Store TTL in flow (TODO)
tcpWinFlowP}
if (flowP->l4Proto != L3_TCP) return; // process only TCP // <-- only TCP (TODO)
const tcpHeader_t * const tcpHeader = TCP_HEADER(packet); // <-- Get TCP header (TODO)
->tcpWinInit = ntohs(tcpHeader->window); // <-- convert window size to little endian and store in flow (TODO)
tcpWinFlowP}
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;
* const tcpWinFlowP = &tcpWinFlows[flowIndex];
tcpWinFlow_t const tcpHeader_t * const tcpHeader = (tcpHeader_t*)packet->l4HdrP;
const uint32_t tcpWin = ntohs(tcpHeader->window);
->pktTcpCnt++; // <-- count all TCP packets (TODO)
tcpWinFlowP
if (tcpWin < TCPWIN_THRES) { // is window size below threshold?
->winThCnt++; // count the packet
tcpWinFlowP->stat |= TCPWIN_THU; // set the status bit
tcpWinFlowP}
}
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
* t2PrintHeader() {
binary_value_t*bv = NULL;
binary_value_t
(bv, "tcpWinStat", "TCP window size threshold status"); // 8 bit hex variable
BV_APPEND_H8(bv, "tcpWinIpTTL", "IP TTL"); // <-- 8 bit unsigned variable
BV_APPEND_U8(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_U32(bv, "tcpWinSzThRt", "TCP packet count ratio below window size WINMIN threshold"); // <-- float variable
BV_APPEND_FLT
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) {
* const tcpWinFlowP = &t2PSkelFlows[flowIndex];
tcpWinFlow_t
(buf, tcpWinFlowP->stat);
OUTBUF_APPEND_U8(buf, tcpWinFlowP->ttl); // <-- add 8-bit ttl to output buffer
OUTBUF_APPEND_U8(buf, tcpWinFlowP->tcpWinInit); // <-- add init window size 32bit unsigned int to output buffer
OUTBUF_APPEND_U32(buf, tcpWinFlowP->winThCnt);
OUTBUF_APPEND_U32
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
(buf, f); // <-- add a float (32bit) result to output buffer // <-- Output f
OUTBUF_APPEND_FLT}
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
- Plugin programming cheatsheet
- Plugin end report
- Plugin monitoring
- Plugin packet mode
- Plugin summary files
- Plugin geo labeling
- Plugin dependencies
- Plugin alarm mode
- Plugin force mode
- Plugin pcap extraction
- Plugin flow timeout
- Plugin sink
- Developing Tranalyzer plugins in C++
- Developing Tranalyzer plugins in Rust