decoder/igmp: initial decoder support for IGMP

Basic v1, v2 and v3 header validation.

Ticket: #8262.
This commit is contained in:
Victor Julien
2026-03-01 06:45:44 +00:00
parent 90823fa90e
commit 1b7cc2b323
10 changed files with 294 additions and 0 deletions
+22
View File
@@ -6619,6 +6619,24 @@
}
}
},
"igmp": {
"type": "object",
"additionalProperties": false,
"properties": {
"malformed": {
"type": "integer",
"description": "IGMP with malformed data"
},
"pkt_too_small": {
"type": "integer",
"description": "IGMP packets too small to fit a IGMP header"
},
"v3_pkt_too_small": {
"type": "integer",
"description": "IGMPv3 packets too small to fit a IGMP header"
}
}
},
"ipraw": {
"type": "object",
"additionalProperties": false,
@@ -7058,6 +7076,10 @@
"type": "integer",
"description": "Number of IEEE802.1ah packets decoded"
},
"igmp": {
"type": "integer",
"description": "Number of IGMP packets decoded"
},
"invalid": {
"type": "integer",
"description": "Number of invalid packets decoded"
+2
View File
@@ -74,6 +74,7 @@ noinst_HEADERS = \
decode-gre.h \
decode-icmpv4.h \
decode-icmpv6.h \
decode-igmp.h \
decode-ipv4.h \
decode-ipv6.h \
decode-mpls.h \
@@ -651,6 +652,7 @@ libsuricata_c_a_SOURCES = \
decode-gre.c \
decode-icmpv4.c \
decode-icmpv6.c \
decode-igmp.c \
decode-ipv4.c \
decode-ipv6.c \
decode-mpls.c \
+14
View File
@@ -614,6 +614,20 @@ const struct DecodeEvents_ DEvents[] = {
NSH_UNKNOWN_PAYLOAD,
},
/* IGMP events */
{
"decoder.igmp.pkt_too_small",
IGMP_PKT_TOO_SMALL,
},
{
"decoder.igmp.v3_pkt_too_small",
IGMP_V3_PKT_TOO_SMALL,
},
{
"decoder.igmp.malformed",
IGMP_MALFORMED,
},
/* GENERIC EVENTS */
{
"decoder.too_many_layers",
+5
View File
@@ -229,6 +229,11 @@ enum {
NSH_UNSUPPORTED_TYPE,
NSH_UNKNOWN_PAYLOAD,
/* IGMP events */
IGMP_PKT_TOO_SMALL, /**< packet too small to fit the basic IGMP header */
IGMP_V3_PKT_TOO_SMALL, /**< packet too small to fit the IGMPv3 header */
IGMP_MALFORMED,
/* generic events */
GENERIC_TOO_MANY_LAYERS,
+177
View File
@@ -0,0 +1,177 @@
/* Copyright (C) 2026 Open Information Security Foundation
*
* You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
/**
* \file
*/
#include "suricata-common.h"
#include "decode.h"
#include "util-debug.h"
#define IGMP_TYPE_MEMBERSHIP_QUERY 0x11
#define IGMP_TYPE_MEMBERSHIP_REPORT_V1 0x12
#define IGMP_TYPE_MEMBERSHIP_REPORT_V2 0x16
#define IGMP_TYPE_LEAVE_GROUP_V2 0x17
#define IGMP_TYPE_MEMBERSHIP_REPORT_V3 0x22
/* Data size: IP segment size (64k), minus IP header + RA option (24),
* minus IGMPv3 header (12).
* Divided by 4 bytes per address. */
#define IGMP_V3_MAX_N_SOURCES (65535 - 24 - 12) / 4
typedef struct IGMPv3MemberQueryHdr_ {
uint8_t type;
uint8_t max_resp_time;
uint16_t checksum;
uint32_t group_address;
uint8_t s_qrv;
uint8_t qqic;
uint16_t n_sources;
/* Followed by source addresses */
} IGMPv3MemberQueryHdr;
typedef struct IGMPv3MemberReportGroupRecord_ {
uint8_t type;
uint8_t aux_data_len; /**< in units of 32-bit words */
uint16_t n_sources;
uint32_t mcast_addr;
// source address
// aux data
} IGMPv3MemberReportGroupRecord;
typedef struct IGMPv3MemberReportHdr_ {
uint8_t type;
uint8_t res;
uint16_t checksum;
uint16_t flags;
uint16_t n_group_recs;
/* Followed by group records */
} IGMPv3MemberReportHdr;
int DecodeIGMP(ThreadVars *tv, DecodeThreadVars *dtv, Packet *p, const uint8_t *pkt, uint32_t len)
{
StatsCounterIncr(&tv->stats, dtv->counter_igmp);
if (len < sizeof(IGMPHdr)) {
ENGINE_SET_INVALID_EVENT(p, IGMP_PKT_TOO_SMALL);
return TM_ECODE_FAILED;
}
const IGMPHdr *igmp = PacketSetIGMP(p, pkt);
p->proto = IPPROTO_IGMP;
uint8_t version;
/* For IGMPv3 Membership Query, we need to handle additional fields */
if (igmp->type == IGMP_TYPE_MEMBERSHIP_QUERY) {
if (len >= 12) {
const IGMPv3MemberQueryHdr *igmpv3 = (const IGMPv3MemberQueryHdr *)pkt;
const uint32_t n_sources = SCNtohs(igmpv3->n_sources);
if (n_sources > IGMP_V3_MAX_N_SOURCES) {
ENGINE_SET_INVALID_EVENT(p, IGMP_MALFORMED);
return TM_ECODE_FAILED;
}
const uint32_t header_len =
sizeof(IGMPv3MemberQueryHdr) + (n_sources * sizeof(uint32_t));
if (len < header_len) {
SCLogDebug("len %u < header_len %u", len, header_len);
ENGINE_SET_INVALID_EVENT(p, IGMP_V3_PKT_TOO_SMALL);
return TM_ECODE_FAILED;
}
if (header_len >= UINT16_MAX) {
ENGINE_SET_INVALID_EVENT(p, IGMP_MALFORMED);
return TM_ECODE_FAILED;
}
p->l4.vars.igmp.hlen = (uint16_t)header_len;
p->payload = (uint8_t *)pkt + header_len;
p->payload_len = (uint16_t)(len - header_len);
version = 3;
} else {
if (igmp->max_resp_time == 0) {
version = 1;
} else {
version = 2;
}
p->l4.vars.igmp.hlen = (uint16_t)sizeof(IGMPHdr);
p->payload = (uint8_t *)pkt + sizeof(IGMPHdr);
p->payload_len = (uint16_t)(len - sizeof(IGMPHdr));
}
} else if (igmp->type == IGMP_TYPE_MEMBERSHIP_REPORT_V3) {
const IGMPv3MemberReportHdr *igmpv3 = (const IGMPv3MemberReportHdr *)pkt;
const uint32_t n_group_recs = SCNtohs(igmpv3->n_group_recs);
uint32_t header_len = sizeof(IGMPv3MemberReportHdr);
/* parse group records */
for (uint32_t i = 0; i < n_group_recs; i++) {
if (len - header_len < sizeof(IGMPv3MemberReportGroupRecord)) {
SCLogDebug("len %u - header_len %u < IGMPv3MemberReportGroupRecord %u", len,
header_len, (uint32_t)sizeof(IGMPv3MemberReportGroupRecord));
ENGINE_SET_INVALID_EVENT(p, IGMP_V3_PKT_TOO_SMALL);
return TM_ECODE_FAILED;
}
const IGMPv3MemberReportGroupRecord *grec =
(const IGMPv3MemberReportGroupRecord *)(pkt + header_len);
header_len += sizeof(IGMPv3MemberReportGroupRecord);
if (len - header_len < (uint32_t)(grec->aux_data_len * sizeof(uint32_t))) {
SCLogDebug("len %u - header_len %u aux_data_len %u", len, header_len,
(uint32_t)grec->aux_data_len);
ENGINE_SET_INVALID_EVENT(p, IGMP_MALFORMED);
return TM_ECODE_FAILED;
}
header_len += (uint32_t)(grec->aux_data_len * sizeof(uint32_t));
uint32_t sources_len = (uint32_t)SCNtohs(grec->n_sources) * (uint32_t)sizeof(uint32_t);
if (len - header_len < sources_len) {
SCLogDebug("len %u - header_len %u sources_len %u", len, header_len, sources_len);
ENGINE_SET_INVALID_EVENT(p, IGMP_MALFORMED);
return TM_ECODE_FAILED;
}
header_len += sources_len;
}
if (header_len >= UINT16_MAX) {
ENGINE_SET_INVALID_EVENT(p, IGMP_MALFORMED);
return TM_ECODE_FAILED;
}
p->l4.vars.igmp.hlen = (uint16_t)header_len;
p->payload = (uint8_t *)pkt + header_len;
p->payload_len = (uint16_t)(len - header_len);
version = 3;
} else {
if (igmp->type == IGMP_TYPE_MEMBERSHIP_REPORT_V1) {
version = 1;
} else if (igmp->type == IGMP_TYPE_MEMBERSHIP_REPORT_V2 ||
igmp->type == IGMP_TYPE_LEAVE_GROUP_V2) {
version = 2;
} else {
version = 255;
}
p->l4.vars.igmp.hlen = (uint16_t)sizeof(IGMPHdr);
p->payload = (uint8_t *)pkt + sizeof(IGMPHdr);
p->payload_len = (uint16_t)(len - sizeof(IGMPHdr));
}
p->l4.vars.igmp.version = version;
return TM_ECODE_OK;
}
+37
View File
@@ -0,0 +1,37 @@
/* Copyright (C) 2026 Open Information Security Foundation
*
* You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free
* Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/
/**
* \file
*/
#ifndef SURICATA_DECODE_IGMP_H
#define SURICATA_DECODE_IGMP_H
typedef struct IGMPHdr_ {
uint8_t type;
uint8_t max_resp_time;
uint16_t checksum;
uint32_t group_address;
} IGMPHdr;
typedef struct IGMPVars_ {
uint16_t hlen; /**< length of the header */
uint8_t version;
} IGMPVars;
#endif /* SURICATA_DECODE_IGMP_H */
+3
View File
@@ -611,6 +611,9 @@ int DecodeIPV4(ThreadVars *tv, DecodeThreadVars *dtv, Packet *p,
case IPPROTO_ICMPV6:
ENGINE_SET_INVALID_EVENT(p, IPV4_WITH_ICMPV6);
break;
case IPPROTO_IGMP:
DecodeIGMP(tv, dtv, p, data, data_len);
break;
default:
SCLogDebug("unknown protocol type: %" PRIx8 "", p->proto);
+1
View File
@@ -659,6 +659,7 @@ void DecodeRegisterPerfCounters(DecodeThreadVars *dtv, ThreadVars *tv)
dtv->counter_esp = StatsRegisterCounter("decoder.esp", &tv->stats);
dtv->counter_icmpv4 = StatsRegisterCounter("decoder.icmpv4", &tv->stats);
dtv->counter_icmpv6 = StatsRegisterCounter("decoder.icmpv6", &tv->stats);
dtv->counter_igmp = StatsRegisterCounter("decoder.igmp", &tv->stats);
dtv->counter_ppp = StatsRegisterCounter("decoder.ppp", &tv->stats);
dtv->counter_pppoe = StatsRegisterCounter("decoder.pppoe", &tv->stats);
dtv->counter_geneve = StatsRegisterCounter("decoder.geneve", &tv->stats);
+25
View File
@@ -85,6 +85,7 @@ enum PktSrcEnum {
#include "decode-ipv6.h"
#include "decode-icmpv4.h"
#include "decode-icmpv6.h"
#include "decode-igmp.h"
#include "decode-tcp.h"
#include "decode-udp.h"
#include "decode-sctp.h"
@@ -456,6 +457,7 @@ enum PacketL4Types {
PACKET_L4_UDP,
PACKET_L4_ICMPV4,
PACKET_L4_ICMPV6,
PACKET_L4_IGMP,
PACKET_L4_SCTP,
PACKET_L4_GRE,
PACKET_L4_ESP,
@@ -473,11 +475,13 @@ struct PacketL4 {
SCTPHdr *sctph;
GREHdr *greh;
ESPHdr *esph;
IGMPHdr *igmph;
} hdrs;
union L4Vars {
TCPVars tcp;
ICMPV4Vars icmpv4;
ICMPV6Vars icmpv6;
IGMPVars igmp;
} vars;
};
@@ -954,6 +958,25 @@ static inline bool PacketIsARP(const Packet *p)
return p->l3.type == PACKET_L3_ARP;
}
static inline IGMPHdr *PacketSetIGMP(Packet *p, const uint8_t *buf)
{
DEBUG_VALIDATE_BUG_ON(p->l4.type != PACKET_L4_UNKNOWN);
p->l4.type = PACKET_L4_IGMP;
p->l4.hdrs.igmph = (IGMPHdr *)buf;
return p->l4.hdrs.igmph;
}
static inline const IGMPHdr *PacketGetIGMP(const Packet *p)
{
DEBUG_VALIDATE_BUG_ON(p->l4.type != PACKET_L4_IGMP);
return p->l4.hdrs.igmph;
}
static inline bool PacketIsIGMP(const Packet *p)
{
return p->l4.type == PACKET_L4_IGMP;
}
/** \brief Structure to hold thread specific data for all decode modules */
typedef struct DecodeThreadVars_
{
@@ -982,6 +1005,7 @@ typedef struct DecodeThreadVars_
StatsCounterId counter_udp;
StatsCounterId counter_icmpv4;
StatsCounterId counter_icmpv6;
StatsCounterId counter_igmp;
StatsCounterId counter_arp;
StatsCounterId counter_ethertype_unknown;
@@ -1170,6 +1194,7 @@ int DecodeCHDLC(ThreadVars *, DecodeThreadVars *, Packet *, const uint8_t *, uin
int DecodeTEMPLATE(ThreadVars *, DecodeThreadVars *, Packet *, const uint8_t *, uint32_t);
int DecodeNSH(ThreadVars *, DecodeThreadVars *, Packet *, const uint8_t *, uint32_t);
int DecodeARP(ThreadVars *, DecodeThreadVars *, Packet *, const uint8_t *, uint32_t);
int DecodeIGMP(ThreadVars *, DecodeThreadVars *, Packet *, const uint8_t *, uint32_t);
#ifdef UNITTESTS
void DecodeIPV6FragHeader(Packet *p, const uint8_t *pkt,
+8
View File
@@ -924,6 +924,14 @@ SCJsonBuilder *CreateEveHeader(const Packet *p, enum SCOutputJsonLogDirection di
SCJbSetUint(js, "icmp_code", PacketGetICMPv6(p)->code);
}
break;
case IPPROTO_IGMP:
if (PacketIsIGMP(p)) {
SCJbOpenObject(js, "igmp");
SCJbSetUint(js, "type", PacketGetIGMP(p)->type);
SCJbSetUint(js, "version", p->l4.vars.igmp.version);
SCJbClose(js);
}
break;
}
SCJbSetString(js, "pkt_src", PktSrcToString(p->pkt_src));