doc/userguide/devguide/extending/app-layer/overview.rst
Application Layer Overview ##########################
.. contents:: Table of Contents
This section aims to give an overview of what is needed to add an application-layer protocol to Suricata.
After a generic first step of collecting data about this application-layer protocol, especially pcaps for testing, we can dive into the Suricata specifics.
An application-layer protocol has three logic components in Suricata:
Both detection engine and logger will depend on the processing done by the parser.
For security reasons, we now develop application-layer protocol code only in Rust and not in C.
The script scripts/setup-app-layer.py may help you get started for adding
a new app-layer protocol.
Parser
The parser is described by an instance of the structure RustParser.
A parser has:
APP_LAYER_PARSER_OPT_ACCEPT_GAPSProtocol detection.So each app-layer protocol needs to define two structures: one State and one Transaction.
A State will live throughout the flow (or until there is a protocol change in the flow).
As such, it is useful to retain data that needs such a scope (for example HTTP2 dynamic headers table).
And it is also useful if the parsing uses a state-machine logic, for example for file streaming.
A State will own a list of :doc:transactions.
Transactions are the basic logical unit used by Suricata for an application-layer protocol.
The big decision is how to assemble PDUs into a transaction. Simplest design is to have one transaction per PDU (like DNS). But it may add value for example to combine request and response into a single transaction (like HTTP).
The RustParser structure contains callbacks to parse network traffic in both directions.
These callbacks will create the transactions.
For protocols over TCP, this callback has to loop as one callback may run for a network traffic containing multiple PDUs, and thus resulting in the creation of multiple transactions.
.. note:: If a protocol may have multiple long-lived transactions, it is good to enforce limits on the number of live transactions, and bound any other data owned by the State.
In case of parsing anomalies, a transaction can set anomaly events, which are specific to the application-layer protocol. There is currently no good standardized way to have this kind of event outside transactions.
It is good to develop an app-layer support first without gap support, then improve it by adding gap support.
Pcaps for testing can be created by removing some packets in previous testing pcaps.
After adding the flag APP_LAYER_PARSER_OPT_ACCEPT_GAPS, a generic way to handle this is:
if stream_slice.is_gap().. note:: This generic best-effort approach is vulnerable to request/response smuggling.
Another less generic approach is to handle gaps only in the case the gap happens in the middle of a known-length PDU (like HTTP1 content-length).
Logger
Besides the logging function, the logger also has a direction which may be
either LOG_DIR_PACKET or LOG_DIR_FLOW.
UDP unidirectional transactions will be better interpreted using LOG_DIR_PACKET
while TCP transactions are usually better interpreted using LOG_DIR_FLOW.
The logging function returns a boolean which must be false if there is nothing to log, for example if the resulting json object is empty.
Support for application-layer specific logging options is not yet standardized, especially for alerts.
Detection engine
A simple callback should register the keywords matching the log output fields.