From 3752af6cd7a3cba0b95fbb357535e233bd164ab1 Mon Sep 17 00:00:00 2001 From: Gianni Tedesco Date: Thu, 12 Mar 2026 12:25:12 +0000 Subject: [PATCH 1/4] decode/erspan: move header struct to source file ErspanHdr is not used outside of the decode-erspan.c compilation unit, so define it there instead of in the header. Ticket: #8329 --- src/decode-erspan.c | 6 ++++++ src/decode-erspan.h | 7 +------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/decode-erspan.c b/src/decode-erspan.c index b87877a5ea..3d4a68e4c1 100644 --- a/src/decode-erspan.c +++ b/src/decode-erspan.c @@ -41,6 +41,12 @@ #include "util-debug.h" #include "conf.h" +typedef struct ErspanHdr_ { + uint16_t ver_vlan; + uint16_t flags_spanid; + uint32_t padding; +} __attribute__((__packed__)) ErspanHdr; + /** * \brief Functions to decode ERSPAN Type I and II packets */ diff --git a/src/decode-erspan.h b/src/decode-erspan.h index 350eb5ef30..62de969e67 100644 --- a/src/decode-erspan.h +++ b/src/decode-erspan.h @@ -25,11 +25,6 @@ #ifndef SURICATA_DECODE_ERSPAN_H #define SURICATA_DECODE_ERSPAN_H -typedef struct ErspanHdr_ { - uint16_t ver_vlan; - uint16_t flags_spanid; - uint32_t padding; -} __attribute__((__packed__)) ErspanHdr; - void DecodeERSPANConfig(void); + #endif /* SURICATA_DECODE_ERSPAN_H */ From caf832eadcc35a946213a2379f3e8d9d2f8d1437 Mon Sep 17 00:00:00 2001 From: Gianni Tedesco Date: Thu, 12 Mar 2026 12:25:36 +0000 Subject: [PATCH 2/4] decode/erspan: support ERSPAN Type III ERSPAN Type III is carried in GRE with protocol type 0x22EB. It has a 12 byte header with version 2, optionally followed by an 8 byte platform specific sub-header when the O flag is set. Decode the header, store the VLAN id as is done for Type II and pass the mirrored frame to the ethernet decoder. Frame types other than ethernet set the new erspan.unsupported_encapsulation event. Ticket: #8329 --- etc/schema.json | 4 +++ src/decode-erspan.c | 71 +++++++++++++++++++++++++++++++++++++++++++ src/decode-ethernet.h | 1 + src/decode-events.c | 4 +++ src/decode-events.h | 1 + src/decode-gre.c | 9 ++++++ src/decode.c | 2 ++ src/decode.h | 2 ++ 8 files changed, 94 insertions(+) diff --git a/etc/schema.json b/etc/schema.json index 4cff77cefb..fab5361eea 100644 --- a/etc/schema.json +++ b/etc/schema.json @@ -7109,6 +7109,10 @@ "type": "integer", "description": "Number of packets with too many VLAN layers for ERSPAN" }, + "unsupported_encapsulation": { + "type": "integer", + "description": "Number of packets with unsupported encapsulation for ERSPAN" + }, "unsupported_version": { "type": "integer", "description": "Number of packets with unsupported version for ERSPAN" diff --git a/src/decode-erspan.c b/src/decode-erspan.c index 3d4a68e4c1..88411914c8 100644 --- a/src/decode-erspan.c +++ b/src/decode-erspan.c @@ -47,6 +47,14 @@ typedef struct ErspanHdr_ { uint32_t padding; } __attribute__((__packed__)) ErspanHdr; +typedef struct Erspan3Hdr_ { + uint16_t ver_vlan; + uint16_t flags_spanid; + uint32_t timestamp; + uint16_t sgt; + uint16_t flags; +} __attribute__((__packed__)) Erspan3Hdr; + /** * \brief Functions to decode ERSPAN Type I and II packets */ @@ -117,6 +125,69 @@ int DecodeERSPAN(ThreadVars *tv, DecodeThreadVars *dtv, Packet *p, const uint8_t return DecodeEthernet(tv, dtv, p, pkt + sizeof(ErspanHdr), len - sizeof(ErspanHdr)); } +/** + * \brief ERSPAN Type III + */ +int DecodeERSPANTypeIII( + ThreadVars *tv, DecodeThreadVars *dtv, Packet *p, const uint8_t *pkt, uint32_t len) +{ + DEBUG_VALIDATE_BUG_ON(pkt == NULL); + + StatsCounterIncr(&tv->stats, dtv->counter_erspan); + + if (len < sizeof(Erspan3Hdr)) { + ENGINE_SET_EVENT(p, ERSPAN_HEADER_TOO_SMALL); + return TM_ECODE_FAILED; + } + if (!PacketIncreaseCheckLayers(p)) { + return TM_ECODE_FAILED; + } + + const Erspan3Hdr *ehdr = (const Erspan3Hdr *)pkt; + uint16_t version = SCNtohs(ehdr->ver_vlan) >> 12; + uint16_t vlan_id = SCNtohs(ehdr->ver_vlan) & 0x0fff; + uint16_t flags = SCNtohs(ehdr->flags); + + pkt += sizeof(Erspan3Hdr); + len -= sizeof(Erspan3Hdr); + + if (version != 2) { + ENGINE_SET_EVENT(p, ERSPAN_UNSUPPORTED_VERSION); + return TM_ECODE_FAILED; + } + + /* If O flag is set, then the optional platform specific header is present. + * We need to skip past it. + */ +#define PLATFORM_SPECIFIC_HDR_SIZE 8 + if (flags & 0x0001) { + if (len < PLATFORM_SPECIFIC_HDR_SIZE) { + ENGINE_SET_EVENT(p, ERSPAN_HEADER_TOO_SMALL); + return TM_ECODE_FAILED; + } + + pkt += PLATFORM_SPECIFIC_HDR_SIZE; + len -= PLATFORM_SPECIFIC_HDR_SIZE; + } + + if (vlan_id > 0) { + if (p->vlan_idx > VLAN_MAX_LAYER_IDX) { + ENGINE_SET_EVENT(p, ERSPAN_TOO_MANY_VLAN_LAYERS); + return TM_ECODE_FAILED; + } + p->vlan_id[p->vlan_idx] = vlan_id; + p->vlan_idx++; + } + + switch ((flags & 0x7c00) >> 10) { + case 0x00: /* ethernet */ + return DecodeEthernet(tv, dtv, p, pkt, len); + default: + ENGINE_SET_EVENT(p, ERSPAN_UNSUPPORTED_ENCAPSULATION); + return TM_ECODE_FAILED; + } +} + /** * @} */ diff --git a/src/decode-ethernet.h b/src/decode-ethernet.h index 992f7530bf..7b98c3f110 100644 --- a/src/decode-ethernet.h +++ b/src/decode-ethernet.h @@ -46,6 +46,7 @@ #define ETHERNET_TYPE_LOOP 0x9000 #define ETHERNET_TYPE_8021QINQ 0x9100 #define ETHERNET_TYPE_ERSPAN 0x88BE +#define ETHERNET_TYPE_ERSPAN_III 0x22EB /* ERSPAN III */ #define ETHERNET_TYPE_DCE 0x8903 /* Data center ethernet, * Cisco Fabric Path */ #define ETHERNET_TYPE_ETAG 0x893F /* 802.1BR */ diff --git a/src/decode-events.c b/src/decode-events.c index 3f37713dd3..c6cda0c93b 100644 --- a/src/decode-events.c +++ b/src/decode-events.c @@ -603,6 +603,10 @@ const struct DecodeEvents_ DEvents[] = { "decoder.erspan.too_many_vlan_layers", ERSPAN_TOO_MANY_VLAN_LAYERS, }, + { + "decoder.erspan.unsupported_encapsulation", + ERSPAN_UNSUPPORTED_ENCAPSULATION, + }, /* Cisco Fabric Path/DCE events. */ { diff --git a/src/decode-events.h b/src/decode-events.h index 45602235d5..15f2483edd 100644 --- a/src/decode-events.h +++ b/src/decode-events.h @@ -221,6 +221,7 @@ enum { ERSPAN_HEADER_TOO_SMALL, ERSPAN_UNSUPPORTED_VERSION, ERSPAN_TOO_MANY_VLAN_LAYERS, + ERSPAN_UNSUPPORTED_ENCAPSULATION, /* Cisco Fabric Path/DCE events. */ DCE_PKT_TOO_SMALL, diff --git a/src/decode-gre.c b/src/decode-gre.c index 7045cdf20f..b34edf81f0 100644 --- a/src/decode-gre.c +++ b/src/decode-gre.c @@ -262,6 +262,15 @@ int DecodeGRE(ThreadVars *tv, DecodeThreadVars *dtv, Packet *p, const uint8_t *p } break; } + case ETHERNET_TYPE_ERSPAN_III: { + Packet *tp = PacketTunnelPktSetup( + tv, dtv, p, pkt + header_len, len - header_len, DECODE_TUNNEL_ERSPANIII); + if (tp != NULL) { + PKT_SET_SRC(tp, PKT_SRC_DECODER_GRE); + PacketEnqueueNoLock(&tv->decode_pq, tp); + } + break; + } case ETHERNET_TYPE_BRIDGE: { diff --git a/src/decode.c b/src/decode.c index 9d7b2df244..32d7c4b2ab 100644 --- a/src/decode.c +++ b/src/decode.c @@ -198,6 +198,8 @@ static int DecodeTunnel(ThreadVars *tv, DecodeThreadVars *dtv, Packet *p, const return DecodeVLAN(tv, dtv, p, pkt, len); case DECODE_TUNNEL_ETHERNET: return DecodeEthernet(tv, dtv, p, pkt, len); + case DECODE_TUNNEL_ERSPANIII: + return DecodeERSPANTypeIII(tv, dtv, p, pkt, len); case DECODE_TUNNEL_ERSPANII: return DecodeERSPAN(tv, dtv, p, pkt, len); case DECODE_TUNNEL_ERSPANI: diff --git a/src/decode.h b/src/decode.h index fb7b1466ef..b9878ced7b 100644 --- a/src/decode.h +++ b/src/decode.h @@ -1142,6 +1142,7 @@ static inline void PacketTunnelSetVerdicted(Packet *p) enum DecodeTunnelProto { DECODE_TUNNEL_ETHERNET, + DECODE_TUNNEL_ERSPANIII, DECODE_TUNNEL_ERSPANII, DECODE_TUNNEL_ERSPANI, DECODE_TUNNEL_VXLAN, @@ -1206,6 +1207,7 @@ int DecodeIEEE8021ah(ThreadVars *, DecodeThreadVars *, Packet *, const uint8_t * int DecodeGeneve(ThreadVars *, DecodeThreadVars *, Packet *, const uint8_t *, uint32_t); int DecodeVXLAN(ThreadVars *, DecodeThreadVars *, Packet *, const uint8_t *, uint32_t); int DecodeMPLS(ThreadVars *, DecodeThreadVars *, Packet *, const uint8_t *, uint32_t); +int DecodeERSPANTypeIII(ThreadVars *, DecodeThreadVars *, Packet *, const uint8_t *, uint32_t); int DecodeERSPAN(ThreadVars *, DecodeThreadVars *, Packet *, const uint8_t *, uint32_t); int DecodeERSPANTypeI(ThreadVars *, DecodeThreadVars *, Packet *, const uint8_t *, uint32_t); int DecodeCHDLC(ThreadVars *, DecodeThreadVars *, Packet *, const uint8_t *, uint32_t); From 527530ed17480cefa22b7267ef1db25abbb15470 Mon Sep 17 00:00:00 2001 From: Johan Ryberg Date: Mon, 5 Oct 2026 08:49:19 +0200 Subject: [PATCH 3/4] rules/decoder: add ERSPAN encapsulation event rule Add a rule for the erspan.unsupported_encapsulation decoder event that was added with ERSPAN Type III support. Ticket: #8329 --- rules/decoder-events.rules | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rules/decoder-events.rules b/rules/decoder-events.rules index fe3b0027b6..8f5a03f818 100644 --- a/rules/decoder-events.rules +++ b/rules/decoder-events.rules @@ -148,6 +148,7 @@ alert pkthdr any any -> any any (msg:"SURICATA ERSPAN pkt too small"; decode-eve # packet has type not supported by Suricata's decoders alert pkthdr any any -> any any (msg:"SURICATA ERSPAN unsupported version"; decode-event:erspan.unsupported_version; classtype:protocol-command-decode; sid:2200106; rev:2;) alert pkthdr any any -> any any (msg:"SURICATA ERSPAN too many vlan layers"; decode-event:erspan.too_many_vlan_layers; classtype:protocol-command-decode; sid:2200107; rev:2;) +alert pkthdr any any -> any any (msg:"SURICATA ERSPAN unsupported encapsulation"; decode-event:erspan.unsupported_encapsulation; classtype:protocol-command-decode; sid:2200130; rev:1;) # Cisco Fabric Path/DCE alert pkthdr any any -> any any (msg:"SURICATA DCE packet too small"; decode-event:dce.pkt_too_small; classtype:protocol-command-decode; sid:2200110; rev:2;) @@ -166,5 +167,5 @@ alert igmp any any -> any any (msg:"SURICATA IGMPv3 packet too small"; decode-ev alert igmp any any -> any any (msg:"SURICATA IGMP malformed packet"; decode-event:igmp.malformed; classtype:protocol-command-decode; sid:2200129; rev:1;) alert ipv4 any any -> any any (msg:"SURICATA IPv4 unknown protocol"; decode-event:ipv4.unknown_protocol; threshold: type limit, track by_src, seconds 60, count 1;classtype:protocol-command-decode; sid:2200125;) -# next sid is 2200130 +# next sid is 2200131 From 057711fa522e1e7a5ce353bff158dce3a0c16188 Mon Sep 17 00:00:00 2001 From: Johan Ryberg Date: Mon, 5 Oct 2026 08:49:19 +0200 Subject: [PATCH 4/4] doc/upgrade: mention ERSPAN Type III support Ticket: #8329 --- doc/userguide/upgrade.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/userguide/upgrade.rst b/doc/userguide/upgrade.rst index 1673d0db8a..ed84e8b20a 100644 --- a/doc/userguide/upgrade.rst +++ b/doc/userguide/upgrade.rst @@ -46,6 +46,9 @@ Major Changes - Default value for ``stream.reassembly.depth`` when the value is not specified in suricata.yaml is now 1 MiB instead of 0/unlimited. - LLMNR protocol parser, logger and sticky buffers are implemented. +- ERSPAN Type III (GRE protocol type ``0x22EB``) is now decoded. Only + Ethernet frames are supported; other frame types set the + ``erspan.unsupported_encapsulation`` decoder event. - ``pgsql`` is no longer enabled by default. Previously, if the ``app-layer.protocols.pgsql`` section was absent from suricata.yaml, the parser would be enabled. It's now disabled by default. Simply enabling its EVE