SnortTM diagrams for developers
by:
Andrés Felipe Arboleda
Charles Edward Bedón
Universidad del Cauca - Colombia
14th – April – 2005
Version 0.2 alpha
Contents
|
1.
2.
3.
4.
5. 6. 7. |
Introduction General operation Sequence diagrams 1, 2 and 3 – Snort initialization and rules file parsing Rules file parsing Function ParseRulesFile() Function ParseRule() Function ProcessHeadNode() Function ParseRuleOptions() Data structures after parsing Sequence diagram 4 – Fast packet detection engine initialization Initialization of the fast packet detection engine Sequence diagrams 5 and 6 – When a packet arrives Tools and resources Source code statistics To do References |
INTRODUCTION
|
Diagrams shown on next pages aim to represent a part of Snort functionality. Objects from UML sequence diagrams (rectangles on top of each diagram) represent source code files, and messages (arrows) represent calls to functions within such files.
All sequence diagrams are sorted by execution, in other words, Snort execution begins with the diagram shown in Figure 1 continuing with diagram in Figure 2 and so on.
This document does not describe in a detailed way Snort source code, it is just kinda map for people who want to know on which part of the code is located when is reading one of the Snort’s source files.
This diagrams were done for Snort-2.2.0, executed with the next command line:
snort -d -l <path to log directory> -c <path to configuration file>
This document is a sub-product of the degree work named “Intrusion Detection System Using Artificial Intelligence”, that is being developed by the authors under direction of Engineer Siler Amador Donado. Comments and suggestions are welcome. |
1. GENERAL OPERATION

Figure 1. Snort block diagram.
|
Each module is described as follows
Decoder: fits the captured packets into data structures and identifies link level protocols. Then, it takes the next level, decodes IP, and then TCP or UDP depending on the case in order to get useful information like ports and addresses. Snort will alert if it finds malformed headers, unusual length TCP options and things like that. |
|
Preprocessors: They could be seen like some kind of filter, which identifies things that should be checked later (in the next modules e.g. the Detection Engine), such as suspicious connection attempts to some TCP/UDP ports or too many UDP packets sent in a short period of time (port scan). Preprocessors function is to take packets potentially dangerous for the detection engine to try to find known patterns.
Rules Files: These are plane text files which contain a list of rules with a known syntax. This syntax includes protocols, addresses, output plug-ins associated and some other things. Those rules files are updated like the virus definition files are.
Detection Plug-ins: Those modules are referenced from its definition in the rules files, and they're intended to identify patterns whenever a rule is evaluated.
Detection Engine: Making use of the detection plug-ins, it matches packets against rules previously charged into memory since Snort initialization.
Output Plug-ins: These modules allow to format the notifications (alerts, logs) for the user to access them by many ways (console, extern files, databases, etc). |

Figure 2. Snort initialization (Sequence diagram 1).

Figure 3. Snort initialization (Sequence diagram 2).

Figure 4. Rules file parsing (Sequence diagram 3).
2. RULES FILE PARSING
|
Next functions are within the file ./parser.c.
This function analyses, through a cycle, each configuration file line (i.e.: snort.conf). If the line is a valid rule (is not a commentary), it is passed to the rule parser (the function ParseRule()).
This function is executed one time per each valid rule in the configuration file. Initially, it searches for lines that are not detection rules, in other words, instructions like include, var, preprocessor, output plugins, config, etc. In case of finding preprocessors and output plugins, it calls the initialization functions for each one.
If the rule is a detection one, it is to say, begins with alert, log, pass, activation or dynamic, the rule is verified and charged into memory by the function ProcessHeadNode().
The detection rules are stored in memory inside the structures RuleTreeNode (RTN) and OptTreeNode (OTN); such structures are declared in the file ./rules.h.
A detailed explanation can be found in question “3.17 How does rule ordering work?” of [SnortFAQ 03].
This is the function’s prototype:
ProcessHeadNode(RuleTreeNode *test_node, ListHead *list, protocol)
It takes a RTN pointed by test_node and attaches it at the end of the RTNs chain of the respective protocol, in the ListHead pointed by list [Schildt 90]. |

Figure 5. Data structures associated to ProcessHeadNode().
This is the function’s prototype:
ParseRuleOptions(char *rule, int rule_type, int protocol)
It creates OTNs and attaches them to the RTN pointed by the global variable rtn_tmp which is set by the function ProcessHeadNode(). This last was called previously by ParseRule().
In this manner gets formed the RTNs and OTNs linked matrix (we call linked matrix to a two dimensional linked list structure) which is the place where rules are stored in memory. RTNs keep data previously given by the rule header, while OTNs keep data given by the rule options section.
An example rule:
alert tcp any any -> 192.168.1.0/24 111 (content:”|00 01 86 a5|”; msg:”mountd access”;) |------------------- Header ------------------|---------------------- Options ------------------------|
|
|
The linked matrix is shown as follows. In the figure each square represents a data structure and each arrow, a pointer. |

Figure 6. Linked matrix.
3. DATA STRUCTURES AFTER PARSING
|
After the rules file is parsed, these rules keep stored in RTNs and OTNs forming the next structure. |

Figure 7. Where rules are stored.
|
RuleLists pointer is a global variable declared in the file ./parser.c, it is useful to go over all rules that are stored in memory. It points to the first element of a RuleListNode linked list. Each node of the list has a ListHead pointer, there is one for each rule type (Alert, Dynamic, Log, Pass and Activation). Finally, each ListHead has four pointers, one per protocol (Ip, Tcp, Udp and Icmp); each pointer points to a RTNs and OTNs linked matrixes where rules are. In other words, it could be up to four matrixes per rule type. |

Figure 8. Fast packet detection engine initialization (Sequence diagram 4).
4. INITIALIZATION OF THE FAST PACKET DETECTION ENGINE
|
Initialization begins with the calling to function fpCreateFastPacketDetection() in the file ./fpcreate.c from SnortMain(). Function fpCreateFastPacketDetection() goes over all rules stored in memory using the global variable RuleLists which is a RuleListNode pointer, each rule is classified according to its content (Content, UriContent o NoContent). Content is determined through the OTN associated with the rule. In this OTN exists a field named ds_list, it is an array of pointers pointing to diverse data structures, depending on type of these structures the content is set.
After that first classification, it is determined if the rule is bidirectional and either the function prmAddRule(), prmAddRuleUri() or prmAddRuleNC() is called depending on content type. These functions sort rules in tables according to source-port and destination-port given in the rule. The objective of all this is to make the packet comparison to rules faster as possible. |

Figure 9. Data structures associated to fast packet detection engine.
|
If we look into the function fpCreateFastPacketDetection(), we found declared one PORT_RULE_MAP for each protocol (tcp, udp, ip, icmp), inside each PORT_RULE_MAP there are three groups of PORT_GROUP: one is the source port table (prmSrcPort), other is the destination port table (prmDstPort) and last is the generic table (prmGeneric) which is used for rules with srcport=any and dstport=any. |

Figure 10. When a packet arrives (Sequence diagram 5).

Figure
11. When a packet arrives (Sequence diagram
6).
5. TOOLS AND RESOURCES
|
6. SOURCE CODE STATISTICS
|
For Snort-2.2.0.
|
|
Number of .c files |
135 |
|
Number of .h files |
154 |
|
Number of source code lines (approx.) |
99.317 |
|
Total size of files |
2’471.751 bytes |
|
Number of .c and .h files per directory: |
|
Directory |
Number of .c files |
Number of .h files |
Number of code lines in .c files |
Number of code lines in .h files |
Total code lines in .c and .h files |
|
./ |
27 |
41 |
26.794 |
5.821 |
32.615 |
|
./detection-plugins |
28 |
28 |
10.417 |
756 |
11.173 |
|
./output-plugins |
11 |
11 |
7.417 |
362 |
7.779 |
|
./parser |
1 |
1 |
312 |
48 |
360 |
|
./preprocessors |
18 |
19 |
17.724 |
951 |
18.675 |
|
./preprocessors/flow |
13 |
16 |
4.498 |
835 |
5.333 |
|
./preprocessors/HttpInspect |
14 |
19 |
5.885 |
923 |
6.808 |
|
./sfutil |
17 |
18 |
12.587 |
1.974 |
14.561 |
|
./win32/WIN32-Code |
6 |
1 |
1.887 |
126 |
2.013 |
|
TOTALS: |
135 |
154 |
87.521 |
11.796 |
99.317 |
|
Number of source code lines includes commentaries in each file. |
7. TO DO
|
REFERENCES
|
[Schildt 90] Herbert Schildt. “C: Manual de referencia”. Segunda edición, Ed. McGraw-Hill, España 1990.
[SnortFAQ 03] The Snort Core Team. “The Snort FAQ”, http://www.snort.org. 2005. |
Copyright © 2005 Andrés Felipe Arboleda, Charles Edward Bedón