Tutorial: The Basics, your first flow plugin

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. Actually we are in contact with HW developers shuch as NAPPA and TITAN IC.

Before we start, all unnecessary plugins should be deleted from the plugin folder ./tranalyzer/plugins and compile basicFlow and txtSink.

$ t2build -e
...
$ t2build basicFlow txtSink
...

Compiling the basicFlow took now a bit longer, because he had to rebuild the subnetfiles for geolocation. ‘-e’ also removes the subnetfile. You can also use a rm, which does not remove the old subnetfile:

$ rm ~/.tranalyzer/plugins/*.so
$ t2build basicFlow txtSink

Then the compilation will be considerable faster, as the subnetfile already exists. If you didn’t read the tutorials before, here is the basis plugin which we will extend: tcpWin

The annonymized sample pcap can be downloaded here: annoloc2.pcap. Please extract it under your data folder, if you not already have. Now you are all set for your first own plugin. Yeeeehaa!

Creating your Plugin from a template

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 realize that T2 plugins are organized in BEGIN, MIDDLE and END Blocks.

In T2 documentation you will find a chapter which explains all the nitty gritty info about programming plugins. Here we will concentrate on getting you started asap. For this reason we produced a skeleton plugin: t2PSkel which compiles and produces output, and serves as a template for your own plugin. Let’s look at the skeleton under trunk. The alias tran moves to trunk of your T2 installation, where you started the startup script.

$ tran 
$ ls
arpDecode          binSink           dhcpDecode  ftpDecode    ircDecode    mongoSink    nFrstPkts   pktSIATHisto    pwX           sctpDecode  sqliteSink    t2PSkel       tftpDecode   voipDetector
autogen_plugin.sh  cdpDecode         dnsDecode   geoip        jsonSink     mysqlSink    ntpDecode   popDecode       radiusDecode  smbDecode   sshDecode     tcpFlags      tp0f         vrrpDecode
autogen.sh         ChangeLog         doc         httpSniffer  lldpDecode   natNudel     ospfDecode  portClassifier  README.md     smtpDecode  sslDecode     tcpStates     tranalyzer2  wavelet
basicFlow          connStat          entropy     icmpDecode   macRecorder  nDPI         p0f         protoStats      regex_pcre    snmpDecode  stpDecode     telnetDecode  txtSink
basicStats         descriptiveStats  fnameLabel  igmpDecode   modbus       netflowSink  pcapd       psqlSink        scripts       socketSink  syslogDecode  tests         utils
$ cd t2PSkel/src
$ ls
Makefile.am  t2PSkel.c  t2PSkel.h

This is the simplest src plugin structure. The .c file containing the c code. The .h file containing all definitions, constants and includes and the Makefile.am file being used by gcc. Under documentation-chapter C: Creating a Custom Plugin all details for plugin development are described. We will here only concentrate on the very basics to get you started.

Plugins are loaded by T2 according to a predefined order, which is defined by the so called plugin number. I extracted its definition from the documentation below.

plugin range 	description
------------------------------------
000 - 099	global
100 - 199	basic L2/3/4 plugins
200 - 299	service and routing
300 - 699	L7 protocols
700 - 799	Math and statistics
800 - 899	classifier and AI
900 - 999	output

Certain ranges are predefined to cluster plugins, to preserve some order. Nevertheless, you may pick any arbitrary number, but don’t choose an already existing one. If you do they are loaded in alphabetic order. The loading order is important for dependencies between plugins, and the processing order. So a plugin with number 103 can deliver results to a plugin with number 200, vice versa would require a time machine. The documentation chapter E Status details all the existing plugins, their status and their plugin numbers. So you can look it up and see which numbers are already taken.

Let’s pick 150 because it is higher than the l4 plugins such as tcpFlags, which we will use later as dependencies. So we can benefit from their results. The new_plugin script helps you to produce a clone from the skeleton plugin where all the names and numbers match your choice.

The script reports if the number exists already. Then we needed to choose a different number. Now you have a working plugin residing under the trunk directory. I recommend that you remove the lines we do not need for this tutorial as we progress in the tutorial for simplicity and better bug detection.

The inner organs of a Plugin

Let’s start with the Makefile.am file. As outlined above, it contains compiler information. The important for now is the **_la_SOURCES** keyword, which defines all files to be compiled and linked. So, if you write additional.c or .h files for your plugin to be included by e.g. tcpWin.c, then you have to add it. The rest not important for now, it will be discussed later.

$ cat Makefile.am
lib_LTLIBRARIES = libtcpWin.la
  
libtcpWin_la_SOURCES = tcpWin.c tcpWin.h

#libtcpWin_la_CFLAGS =

The .c file of the plugin folder comprises in general of the following sections:

  • T2_PLUGIN_INIT
  • initialize()
  • printHeader()
  • onFlowGenerated(packet_t *packet __attribute__((unused)), unsigned long flowIndex)
  • claimLayer2Information(packet_t *packet, unsigned long flowIndex)
  • claimLayer3Information(packet_t *packet)
  • claimLayer4Information(packet_t *packet, unsigned long flowIndex)
  • onFlowTerminate(unsigned long flowIndex)
  • onApplicationTerminate()
  • bufferToSink(outputBuffer_t *buffer)
  • void pluginReport(FILE *stream)
  • void monitoring(FILE *stream, uint8 state)

But if you intend to produce only flow or packet files the bold ones apply for now. The rest will be discussed later.

Writing a tcp flow control window size threshold detector

Let’s say you want to extract the window size from the tcpHeader and count the number of times the window size undercuts a certain threshold defined in your .h file.

So open tcpWin.c in your editor of choice.

First your plugin must have a name, tcpWin, a version number, 0.8.2 and an minimal required acceptance limit of the T2 core, in our case 0.8.

if dependencies are required, dependent plugin names have to be added

In order to initialize your structure defined in .h implement the initialize function. In t2PSkel.c you will find a good example, but we will concentrate only on the first necessary steps. T2 appends your structure to the flow structure of other plugins residing before in memory and multiplies it with the amount of the size of the total hachChainTableSize. The latter defines the maximum amount of flows which can be hold by T2 at any given time. It is defined in tranalyzer.h HASHCHAINTABLE_BASE_SIZE by a multiplication factor defined either by HASHFACTOR or the -f option. For the time being you do not worry about that, just use the calloc function as shown below.

I deleted the packet mode and load file part because it is simpler for starters to produce just a flow file. It wil be discussed later under section …

So you are all set, your structure is initialize and good to go. The top line of the flow file contains the names of the columns which are needed for post processing by tawk. Moreover, T2 supplies the **_headers.txt** file which contains a detailed descriptiong of each colums. Both information is to be defined in the printHeader() function. I removed most of the lines in t2PSkel.c because we like to start simple.

For your windowsize undercut detector a eight Bit Hex status number and an uint32 count variable is required. A uint64 could also be picked, but remember, every byte is multiplied by HASHCHAINTABLE_BASE_SIZE. You just have to handle the overflow condition, we come back to that later:

The printHeader function should look like this, just delete the lines which are unnecessary from the skeleton code for clearity.

Each bv_append_bv function adds a new column in the _flows.txt file and a description in the _headers.txt file. The first parameter defines the full description in the _headers.txt file, the second the name of the column on top of the flows file. The third one is a reptition parameter, which defines that the following variables are of arbitrary repetitive nature. So it is ‘0’. We 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’. The BLOCK_BUFF switch around the function is needed if you want to inhibit T2 to fill the output buffer, because the creation of an outputbuffer is not needed, as you are using e.g. netflowSink, which creates its own buffer. You may use itevery time you write a new plugin. Nevertheless, with version T2 0.8.2 this is no longer necessary, unless you want to be backward compatible. We come back to that later, currently no factor.

Benoit wrote a few compiler functions which are more user friendly, also dealing with these BLOCK_BUFF mode:

Now we need to tell T2 what should happen at the first packet of a flow. The function onFlowGenerated has access to the pcap packet structure defined under networkHeaders.h and the flow structure. We will need the packet in this function, so we remove the unused definition in the skeleton. However, the flowindex is the most important tool for flow handling, specificly an internal index to access the flow via the flows[flowIndex] array. 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, if this flow dies. Next we need the pointer to the flow itself, defined in the bottom of networkHeaders.h. As we like to process L4 tcp we terminate further processing for Layer2 and non TCP Flow. Note, this is a difference to the skeleton code.

I left the flow pointer definition and the check for L2_FLOW status in the code, because later we need it. Currently these statements will be removed by the compiler, or you may delete them.

Whenever a packet for a flow arrives, your plugin receives a callback claimLayer4Information. All operations on the packet have to happen here.

When the flow timouts or the protocol demands an end, e.g. tcp FIN, RST, then your plugin receives onFlowTerminate. Here, all final calculations can be implemented and the output is performend according to your definition in the printHeader function. It begins with the standard pointer definition of flowP and your plugin struct. In order to honor the BLOCK_BUF mode encompass your output code in the said #IF pragmas. Note, that you do not have access anymore to the pcap packet structure.

Another method is to use the new user friendly wrappers from Benoit. Here the #if BLOCK_BUF == 0

Note, that if the output does not match your definition in printHeader, then you end up with exceptions or output which looks all right but the numbers might be wrong. If you are thoroughly about this, you will have no problems in future.

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

Now we need to take care about the definitions in tcpWin.h; An extract is listed below. It includes the definions from the global.h, which contains all definitions from the core about protocols, internal datastructures and definitions. We define WINTHRES, so that we can change it without changing the code. The bits of the status variable are defined below, only one bit. It denotes that packets of the flow undershot our threshold defined in WINTHRES.

The variables in the flow structure of the plugin tcpWinFlow_t should be always arranged according their size to reduce memory fragmentation. So all uint64_t, double are located before floats, uint32_t. At last there are uint8_t, chars, also char arrays. 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.

Because I’m cruel I tell you now, after you edited the skelton code, that the code discussed above can be downloaded here: tcpWin. But it is always better to write code yourself to acquainted to a new type of SW.

Compile, run, analyze and extend

Now your plugin is ready to compile with t2build or autogen.sh:

$ t2build tcpWin

Plugin 'tcpWin'

make: Entering directory '/home/wurst/tranalyzer2/trunk/tcpWin/doc'
make: Nothing to be done for 'clean'.
make: Leaving directory '/home/wurst/tranalyzer2/trunk/tcpWin/doc'
libtoolize: putting auxiliary files in AC_CONFIG_AUX_DIR, 'build-aux'.
...
make[2]: Leaving directory '/home/wurst/tranalyzer2/trunk/tcpWin/src'
make[2]: Entering directory '/home/wurst/tranalyzer2/trunk/tcpWin'
make[2]: Leaving directory '/home/wurst/tranalyzer2/trunk/tcpWin'
make[1]: Leaving directory '/home/wurst/tranalyzer2/trunk/tcpWin'

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


BUILD SUCCESSFUL

$

Now use the alias .tran, to move to the current T2 folder, by default under .tranalyzer/plugins. You will see your plugin, you actually did it, congratulations! Now move back to trunk with tran. See your plugin.so?

$.tran
$ ls 
100_basicFlow.so  150_tcpWin.so  901_txtSink.so  subnets4_HLP.bin  subnets6_HLP.bin
$ tran

Now invoke t2

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

Open another bash window, ‘cd’ to your results folder and look into the flow file:

$ cd ~/results
$ tcol annoloc2_flows.txt
%dir  flowInd  flowStat            timeFirst          timeLast           duration   numHdrDesc  numHdrs  hdrDesc                ethVlanID  srcIP                                    srcIPCC  srcIPWho                srcPort  dstIP                                    dstIPCC  dstIPWho                dstPort  l4Proto  tcpWinStat  tcpWinThCnt
A     59       0x0000000200004000  1022171701.692762  1022171701.692762  0.000000   1           3        eth:ipv4:icmp             0          138.212.187.10                           jp       "asahi kasei corpora"   0        201.116.148.149                          mx       "--"                    0        1        0x00        0
A     108      0x0000000200004000  1022171701.700133  1022171701.700133  0.000000   1           3        eth:ipv4:udp              0          138.212.184.165                          jp       "asahi kasei corpora"   8889     19.112.107.128                           us       "--"                    2001     17       0x00        0
A     138      0x0000000000004000  1022171701.700983  1022171701.700983  0.000000   1           3        eth:ipv4:tcp              0          138.212.189.36                           jp       "asahi kasei corpora"   1044     205.25.217.73                            au       "not allocated by ap"   29981    6        0x00        0
A     193      0x0000000000004000  1022171701.704267  1022171701.704267  0.000000   1           3        eth:ipv4:tcp              0          138.212.190.87                           jp       "asahi kasei corpora"   1068     70.128.194.122                           us       "--"                    1863     6        0x00        0
A     245      0x0000000200004000  1022171701.706591  1022171701.706591  0.000000   1           3        eth:ipv4:udp              0          138.212.188.99                           jp       "asahi kasei corpora"   7778     83.221.58.33                             de       "--"                    2009     17       0x00        0
A     262      0x0000080200028000  1022171701.707777  1022171701.707777  0.000000   1           4        eth:ipv4:ipv6:UNK(168)    0          cfb6:1c18:5010:faf0:7f66:0:101:80a       --       "--"                    0        6c2:6a7f:1:384b::c100                    --       "--"                    0        168      0x00        0
...
A     3999     0x0000000200008000  1022171706.884790  1022171707.007311  0.122521   1           3        eth:ipv6:tcp          	   0	      2001:70e8:d3ce:e202::30:26               --       "--"                    2128     2001:70e8:d3ce:e200:deaf:b9ff:1f0e:becf  --       "--"                    6668     6        0x01        2
B     3999     0x0000000200008001  1022171706.922668  1022171706.955234  0.032566   1           3        eth:ipv6:tcp              0          2001:70e8:d3ce:e200:deaf:b9ff:1f0e:becf  --       "--"                    6668     2001:70e8:d3ce:e202::30:26               --       "--"                    2128     6        0x01        2
...
A     1041     0x0000000000004000  1022171701.876636  1022171726.639226  24.762590  1           3        eth:ipv4:tcp              0          133.26.84.187                            jp       "meiji university"      4766     138.212.187.109                          jp       "asahi kasei corpora"   80       6        0x01        6
B     1041     0x0000000200004001  1022171701.877349  1022171726.639232  24.761883  1           3        eth:ipv4:tcp              0          138.212.187.109                          jp       "asahi kasei corpora"   80       133.26.84.187                            jp       "meiji university"      4766     6        0x00        0

If you scroll to the right, you will see your output column. 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 happens during the flow. So add basicStats and rerun T2

$ t2build basicStats
...
$ t2 -r ~/data/annoloc2.pcap -w ~/results

Change to your results shell and 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 the window size 0. But how greater? Let’s choose a tcpWinThCnt large enought so that we do not select al 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 '{ if ($tcpWinThCnt > 20 && $tcpWinThCnt/$numPktsSnt > 0.9) print }' annoloc2_flows.txt | tcol
%dir  flowInd  flowStat            timeFirst          timeLast           duration   numHdrDesc  numHdrs  hdrDesc       ethVlanID  srcIP            srcIPCC  srcIPWho               srcPort  dstIP            dstIPCC  dstIPWho               dstPort  l4Proto  numPktsSnt  numPktsRcvd  numBytesSnt  numBytesRcvd  minPktSz  maxPktSz  avePktSize  stdPktSize  minIAT  maxIAT    aveIAT     stdIAT      pktps     bytps     pktAsm       bytAsm     tcpWinStat  tcpWinThCnt
B     636      0x0000000200004001  1022171701.743464  1022171720.404545  18.661081  1           3        eth:ipv4:tcp  0          138.212.187.203  jp       "asahi kasei corpora"  6699     19.123.222.7     us       "--"                   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     151      0x0000000200004001  1022171701.801634  1022171726.463532  24.661898  1           3        eth:ipv4:tcp  0          36.152.156.46    cn       "--"                   3296     138.212.185.188  jp       "asahi kasei corpora"  6699     6        79          78           17495        37            0         1172      221.4557    243.5155    0       0.987917  0.3121759  0.119054    3.203322  709.3939  0.006369427  0.9957792  0x01        76
B     1151     0x0000000200004001  1022171701.939343  1022171726.416686  24.477343  1           3        eth:ipv4:tcp  0          138.212.187.203  jp       "asahi kasei corpora"  6699     68.239.17.59     us       "arin"                 4768     6        76          76           26384        0             11        1132      347.1579    237.4721    0       0.600271  0.3220703  0.07129291  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 flowindex in a environment variable and use it in another tawk where we select all the relevant A/B flows.

$ f="$(tawk -H '{ if ($tcpWinThCnt > 20 && $tcpWinThCnt/$numPktsSnt > 0.9) s=s";"$flowInd; }END{print substr(s,2)}' annoloc2_flows.txt)"
$ tawk -v f="$f" 'flow(f)' annoloc2_flows.txt | tcol
%dir  flowInd  flowStat            timeFirst          timeLast           duration   numHdrDesc  numHdrs  hdrDesc       ethVlanID  srcIP            srcIPCC  srcIPWho               srcPort  dstIP            dstIPCC  dstIPWho               dstPort  l4Proto  numPktsSnt  numPktsRcvd  numBytesSnt  numBytesRcvd  minPktSz  maxPktSz  avePktSize  stdPktSize  minIAT  maxIAT    aveIAT     stdIAT      pktps     bytps     pktAsm       bytAsm      tcpWinStat  tcpWinThCnt 
A     636      0x0000000000004000  1022171701.743423  1022171720.669713  18.926290  1           3        eth:ipv4:tcp  0          19.123.222.7     us       "--"                   1430     138.212.187.203  jp       "asahi kasei corpora"  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     636      0x0000000200004001  1022171701.743464  1022171720.404545  18.661081  1           3        eth:ipv4:tcp  0          138.212.187.203  jp       "asahi kasei corpora"  6699     19.123.222.7     us       "--"                   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     151      0x0000000200004000  1022171701.701921  1022171725.949576  24.247655  1           3        eth:ipv4:tcp  0          138.212.185.188  jp       "asahi kasei corpora"  6699     36.152.156.46    cn       "--"                   3296     6        78          79           37           17495         0         36        0.474359    3.579886    0       0.901739  0.3108674  0.1130646   3.216806  1.525921  -0.00636427    -0.9957792  0x00        0
B     151      0x0000000200004001  1022171701.801634  1022171726.463532  24.661898  1           3        eth:ipv4:tcp  0          36.152.156.46    cn       "--"                   3296     138.212.185.188  jp       "asahi kasei corpora"  6699     6        79          78           17495        37            0         1172      221.4557    243.5155    0       0.987917  0.3121759  0.119054    3.203322  709.3939  0.006369427    0.9957792   0x01        76
A     1151     0x0000000000004000  1022171701.937172  1022171726.415550  24.478378  1           3        eth:ipv4:tcp  0          68.239.17.59     us       "arin"                 4768     138.212.187.203  jp       "asahi kasei corpora"  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     1151     0x0000000200004001  1022171701.939343  1022171726.416686  24.477343  1           3        eth:ipv4:tcp  0          138.212.187.203  jp       "asahi kasei corpora"  6699     68.239.17.59     us       "arin"                 4768     6        76          76           26384        0             11        1132      347.1579    237.4721    0       0.600271  0.3220703  0.07129291  3.104912  1077.895  0              1           0x01        76
$

That confirms the picture, that must be a streaming of data, where the request IP is told not to send any data. To effectively select such flows we need a column, which tcpWinThCnt/numPktsSnt column which tells us the relative 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 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.

I oder to do so, we need to extract behind the L2_FLOW check the TTL from the ipHeader and the window size of the first packet. As T2 is currently in IPv4/6 and L2 mode the L3 Header is different for IPv4 or v6. In order to spare you guys some complicated if’s and pragmas, Benoit wrote compiler functions which return the type of IP in correlated with the current mode of T2 core. So that is achieved by the if (PACKET_IS_IPV6(packet)). It is always good practice to cast the layer3Header from the packet structure to the appropriate constant IPHeader structure defined in trunk/tranalyzer2/networkHeaders.h. Then we store the TTL into flow variable, later to be defined in the .h file. At last, only for TCP, we cast the layer4Header to the tcpHeader structure and 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, if you omit the TCP protocol check, then all other protocols will write to the tcpWinInit, thus creating false output instead of ‘0’.

You are all fixed for the first packet and the flow creation process. Sure, the TTL could change during the flow, in case of multiple path routing, lets 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 <–.

Next we need to add two columns marked by *<– in the flow file defined in printHeader. A 32 Bit unsigned integer for the window size and a 8bit TTL unsigned integer.

At flow timeout, or termination we like to see the TTL and the intial window size in the output. Moreover we so add the lines marked by // <–.

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

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 commands. Change the variable in the tawk to the new column $tcpWinSzThRt > 0.9 and invoke both tawk commands again:

$ f="$(tawk -H '{ if ($tcpWinThCnt > 20 && $tcpWinSzThRt > 0.9) s=s";"$flowInd; }END{print substr(s,2)}' annoloc2_flows.txt)"
$ tawk -v f="$f" 'flow(f)' annoloc2_flows.txt | tcol
%dir  flowInd  flowStat            timeFirst          timeLast           duration   numHdrDesc  numHdrs  hdrDesc       ethVlanID  srcIP            srcIPCC  srcIPWho               srcPort  dstIP            dstIPCC  dstIPWho               dstPort  l4Proto  numPktsSnt  numPktsRcvd  numBytesSnt  numBytesRcvd  minPktSz  maxPktSz  avePktSize  stdPktSize  minIAT  maxIAT    aveIAT     stdIAT      pktps     bytps     pktAsm        bytAsm      tcpWinStat  ipTTL  tcpInitWinSz  tcpWinThCnt  tcpWinSzThRt
A     636      0x0000000000004000  1022171701.743423  1022171720.669713  18.926290  1           3        eth:ipv4:tcp  0          19.123.222.7     us       "--"                   1430     138.212.187.203  jp       "asahi kasei corpora"  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     636      0x0000000200004001  1022171701.743464  1022171720.404545  18.661081  1           3        eth:ipv4:tcp  0          138.212.187.203  jp       "asahi kasei corpora"  6699     19.123.222.7     us       "--"                   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     151      0x0000000200004000  1022171701.701921  1022171725.949576  24.247655  1           3        eth:ipv4:tcp  0          138.212.185.188  jp       "asahi kasei corpora"  6699     36.152.156.46    cn       "--"                   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     151      0x0000000200004001  1022171701.801634  1022171726.463532  24.661898  1           3        eth:ipv4:tcp  0          36.152.156.46    cn       "--"                   3296     138.212.185.188  jp       "asahi kasei corpora"  6699     6        79          78           17495        37            0         1172      221.4557    243.5155    0       0.987917  0.3121759  0.119054    3.203322  709.3939  0.006369427   0.9957792   0x01        112    36            76           0.9620253
A     1151     0x0000000000004000  1022171701.937172  1022171726.415550  24.478378  1           3        eth:ipv4:tcp  0          68.239.17.59     us       "arin"                 4768     138.212.187.203  jp       "asahi kasei corpora"  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     1151     0x0000000200004001  1022171701.939343  1022171726.416686  24.477343  1           3        eth:ipv4:tcp  0          138.212.187.203  jp       "asahi kasei corpora"  6699     68.239.17.59     us       "arin"                 4768     6        76          76           26384        0             11        1132      347.1579    237.4721    0       0.600271  0.3220703  0.07129291  3.104912  1077.895  0             1           0x01        128    0             76           1
$

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