Tutorial: Plugin Alarm Mode
As detailed in alarm mode tutorial T2 is capable to operate in a plugin controlled flow release. The regex_pcre and dnsDecode plugins are an example. In order to implement this feature in your own plugin, specialized macros are supplied, which will be detailed in this tutorial. First switch on the ALARM_MODE
in tranalyzer.h. This is how it looks like by default: (ALARM_MODE
off)
$ tranalyzer2
$ vi tranalyzer.h
...
// Tranalyzer User Operational modes
// Operation modes, Plugins which use these modes have to be recompiled
#define ALARM_MODE 0 // only flow output if an Alarm based plugin fires
#define ALARM_AND 0 // if (ALARM_MODE == 1) then 1: AND, 0: OR
...
Use the following t2conf
command to switch it on:
$ t2conf tranalyzer2 -D ALARM_MODE=1
$
Then reset your plugin directory into a pristine state and compile the following basic plugins:
$ t2build -e
Are you sure you want to empty the plugin folder '/home/wurst/.tranalyzer/plugins' (y/N)? y
Plugin folder emptied
$ t2build tranalyzer2 basicFlow tcpStates txtSink
...
$
If you did not read the tutorials before, here is the basis plugin which we will extend: tcpWin I did not supply a ready to go example as I want you to implement something and have some euphoria when it works.
The anonymized sample pcap can be downloaded here: faf-exercise.pcap. Please extract it under your data folder: ~/data
, if you haven’t already. Now you are all set for the alarm mode.
Implementing the alarm capability
If your plugin wants to contribute information to the alarm mode, then global variables and a T2_REPORT_ALARMS(numAlarms)
macro have to be added.
So open tcpWin.c in an editor and add two global variables after the tcpWinFlows definition. Look for the <--
markers below.
Add at the beginning in the global plugin block add a static definition of an alarm count variable for the end report:
...
/*
* Plugin variables that may be used by other plugins (MUST be declared in
* the header file as 'extern tcpWin_flow_t *tcpWin_flows;'
*/
tcpWinFlow_t *tcpWinFlows;
static uint32_t winAlarms, winThFlows; // <--add for Endreport: Number of Alarms, Number of flows which create an Alarm
...
Then add in the onFlowTerminate(...)
callback the lines marked by <--
which triggers an alarm if the threshold count is > 0. You can replace the variable in T2_REPORT_ALARMS(...)
by any other logical statement or variable.
void onFlowTerminate(unsigned long flowIndex) {
tcpWinFlow_t * const tcpWinFlowP = &tcpWinFlows[flowIndex];
if (tcpWinFlowP->winThCnt) { // <-- Only if there was a threshold count
winAlarms += tcpWinFlowP->winThCnt; // <-- endreport global alarm count
winThFlows++; // <-- count number of alarm flows
T2_REPORT_ALARMS(tcpWinFlowP->winThCnt); // <-- Report alarm to the core
T2_SET_STATUS(&flows[flowIndex], FL_ALARM); // <-- Set the alarm bit in flow and global
}
OUTBUF_APPEND_U8(main_output_buffer, tcpWinFlowP->stat);
OUTBUF_APPEND_U32(main_output_buffer, tcpWinFlowP->winThCnt);
}
In order to make the alarm mode work, you only need the macro T2_REPORT_ALARMS(...)
. It implements the following state-machine:
#if ALARM_MODE == 1
extern unsigned char supOut; // suppress output
#define T2_REPORT_ALARMS(num) { \
numAlarmFlows++; \
numAlarms += (num); \
if (!ALARM_AND) { \
if (num) supOut = 0; \
} else { \
if (!(num)) { \
supOut = 1; \
return; \
} \
} \
}
#else // ALARM_MODE == 0
#define T2_REPORT_ALARMS(num) { \
numAlarmFlows++; \
numAlarms += (num); \
}
#endif // ALARM_MODE == 0
That complicated code is kept from you now, just use the macro and don’t worry.
If you are indeed interested in its functionality, here is the explanation: The variable supOut
is a global switch suppressing the output. numAlarms
denotes the aggregate of all flow alarms. numAlarmFlows
denotes the number of flows which bear an alarm. If ALARM_MODE
is not active the latter global counts will still be reported. As we count the number of alarm flows in the macro, you may at the present version only use it ONCE in onFlowTerminate(...)
XOR onFlowGenerate()
, but NOT during packet callbacks. This will be fixed in a later 0.8.X version.
So your plugin can reimplement any state-machine you want and control the output suppression to your liking. The easiest would be to set suppress output supOut = 1
, then no flow would ever appear in the flow file, rather useless, right.
If you set ALARM_AND
to 1 in tranalyzer.h then any plugin can reset a flow terminate, so all plugins implementing ALARM_MODE
must agree.
For reporting add a pluginReport(...)
callback where the number of alarms and alarm flows are printed, e.g. after onFlowTerminate(...)
.
void pluginReport(FILE *stream) {
if (winThFlows) T2_FPLOG(stream, "tcpWin", "%"PRIu32" alarms in %"PRIu32" flows below win threshold %d", winAlarms, winThFlows, TCPWIN_THRES);
}
Now save your new tcpWin.c, compile it and execute T2
$ t2build tcpWin
...
$ t2 -r ~/data/faf-exercise.pcap -w ~/results/
================================================================================
Tranalyzer 0.8.11 (Anteater), Tarantula. PID: 13706
================================================================================
[INF] Creating flows for L2, IPv4, IPv6
Active plugins:
01: basicFlow, 0.8.11
02: tcpStates, 0.8.11
03: tcpWin, 0.8.11
04: txtSink, 0.8.11
[INF] IPv4 Ver: 5, Rev: 16122020, Range Mode: 0, subnet ranges loaded: 406077 (406.08 K)
[INF] IPv6 Ver: 5, Rev: 17122020, Range Mode: 0, subnet ranges loaded: 51069 (51.07 K)
...
--------------------------------------------------------------------------------
tcpStates: Aggregated tcpStatesAFlags=0x4a
tcpWin: 4 alarms in 1 flows below win threshold 1
--------------------------------------------------------------------------------
...
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Number of average processed flows/s: 0.00
Average full raw bandwidth: 795 b/s
Average full bandwidth : 792 b/s
Max number of flows in memory: 18 [0.01%]
Memory usage: 0.01 GB [0.02%]
Aggregated flowStat=0x0402000000004000
[WRN] 4 alarms in 1 flows [1.39%]
[INF] IPv4 flows
[INF] IPAlarm
$
Note the info in the plugin block and the warning at the end of the T2 report. Decoding the aggregated flow status, we see that you indeed set the FL_ALARM
bit in the global status.
$ tawk -V flowStat=0x0002000000004000
The flowStat column with value 0x0402000000004000 is to be interpreted as follows:
bit | flowStat | Description
=============================================================================
14 | 0x0000 0000 0000 4000 | IPv4 flow
49 | 0x0002 0000 0000 0000 | Alarm mode & pcapd dumps packets from this flow to new pcap if not -e option
58 | 0x0400 0000 0000 0000 | IPv4 packet
faf-exercise_flows.txt shows that out of 72 flows only one was released producing four alarms. Don’t worry about pcapd info under description, the alarm bit is used also for pcap extraction, we come to that in a later tutorial.
As you set the FL_ALARM
in onFlowTerminate(...)
and the flowStat
is printed by basicFlow, which is executed before tcpWin, we do not see the bit.
$ tcol faf-exercise_flows.txt
%dir flowInd flowStat timeFirst timeLast duration numHdrDesc numHdrs hdrDesc srcMac dstMac ethType ethVlanID srcIP srcIPCC srcIPOrg srcPort dstIP dstIPCC dstIPOrg dstPort l4Proto tcpStates tcpWinStat tcpWinThCnt
A 36 0x0400000000004000 1258594163.408285 1258594191.015208 27.606923 1 3 eth:ipv4:tcp 00:08:74:38:01:b4 00:19:e3:e7:5d:23 0x0800 192.168.1.105 07 "Private network" 49330 143.166.11.10 us "Dell" 64334 6 0x42 0x01 4
If you want to see the alarm bit set in the flowStat
you have to set in the packet callback. Just comment out the T2_SET_STATUS(...)
statement in onFlowTerminate(...)
and add it in claimLayer4Information(...)
below where the <--
is present.
void claimLayer4Information(packet_t *packet, unsigned long flowIndex) {
flow_t * const flowP = &flows[flowIndex];
if (flowP->layer4Protocol != 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->layer4Header;
const uint32_t tcpWin = ntohs(tcpHeader->window);
if (tcpWin < TCPWIN_THRES) { // is windowsize below threshold?
tcpWinFlowP->winThCnt++; // count the packet
tcpWinFlowP->stat |= TCPWIN_THU; // set the status bit
T2_SET_STATUS(flowP, FL_ALARM); // <-- Set the alarm bit in flow and global
}
}
Save the file, recompile, rerun t2
and reopen faf-exercise_flows.txt:
$ t2build tcpWin
...
$ t2 -r ~/data/faf-exercise.pcap -w ~/results/
...
$ tcol faf-exercise_flows.txt
%dir flowInd flowStat timeFirst timeLast duration numHdrDesc numHdrs hdrDesc srcMac dstMac ethType ethVlanID srcIP srcIPCC srcIPOrg srcPort dstIP dstIPCC dstIPOrg dstPort l4Proto tcpStates tcpWinStat tcpWinThCnt
A 36 0x0402000000004000 1258594163.408285 1258594191.015208 27.606923 1 3 eth:ipv4:tcp 00:08:74:38:01:b4 00:19:e3:e7:5d:23 0x0800 192.168.1.105 07 "Private network" 49330 143.166.11.10 us "Dell" 64334 6 0x42 0x01 4
See now it is set.
If you want to separate the functionality of your alarm mode from other modes such as force mode, then you have to surround your code with ALARM_MODE
pragmas as shown below:
C ... #if ALARM_MODE == 1 static uint32_t winAlarms, winThFlows; // <--add for Endreport: Number of Alarms, Number of flows which create an Alarm #endif // ALARM_MODE == 1 ...
C
void claimLayer4Information(packet_t *packet, unsigned long flowIndex) {
...
#if ALARM_MODE == 1
T2_SET_STATUS(flowP, FL_ALARM); // <-- Set the alarm bit in flow and global
#endif // ALARM_MODE == 1
void onFlowTerminate(unsigned long flowIndex) {
...
#if ALARM_MODE == 1
if (tcpWinFlowP->winThCnt) { // <-- Only if there was a threshold count
winAlarms += tcpWinFlowP->winThCnt; // <-- endreport global alarm count
winThFlows++; // <-- count number of alarm flows
T2_REPORT_ALARMS(tcpWinFlowP->winThCnt); // <-- Report alarm to the core
T2_SET_STATUS(&flows[flowIndex], FL_ALARM); // <-- Set the alarm bit in flow and global
}
#endif // ALARM_MODE == 1
...
void pluginReport(FILE *stream) {
#if ALARM_MODE == 1
if (winThFlows) T2_FPLOG(stream, "tcpWin", "%"PRIu32" alarms in %"PRIu32" flows below win threshold %d", winAlarms, winThFlows, TCPWIN_THRES);
#endif // ALARM_MODE == 1
}
Compile your plugin again and see whether something changed. What will happen if you switch off the ALARM bit in the core, with and without the ALARM_MODE
pragma? Try it out. But don’t forget to compile all plugins with t2build -R
as the ALARM mode is also implemented in your and other plugins, if loaded.
Play a bit around and improve the program, e.g. modify the alarm condition if more than one win threshold occurrences happen.
And do not forget to reset the alarm mode for the next tutorial
$ t2conf tranalyzer2 -D ALARM_MODE=0 && t2build -R
...
$
Have fun writing alarm mode plugins!
The next tutorial will teach you how to implement the Force mode
See Also
- Plugin Programming Cheatsheet
- The basics: your first flow plugin
- Adding plugin end report
- Adding plugin monitoring output
- Adding plugin packet output
- Producing summary files
- geo-whois-labeling
- All about plugin dependencies
- Plugin sinks
- Manipulating flow timeouts
- Force mode
- Pcap extraction
- Developing Tranalyzer plugins in C++
- Developing Tranalyzer plugins in Rust