mirror of
https://github.com/OISF/suricata.git
synced 2026-10-11 22:09:53 +00:00
decoder/igmp: initial decoder support for IGMP
Basic v1, v2 and v3 header validation. Ticket: #8262.
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 */
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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));
|
||||
|
||||
Reference in New Issue
Block a user