diff --git a/doc/userguide/rules/header-keywords.rst b/doc/userguide/rules/header-keywords.rst index 4d52b347b854..090fbdb15795 100644 --- a/doc/userguide/rules/header-keywords.rst +++ b/doc/userguide/rules/header-keywords.rst @@ -52,6 +52,9 @@ esec IP Extended Security lsrr Loose Source Routing ssrr Strict Source Routing satid Stream Identifier +qs Quick-Start +rtralt Router Alert +cipso Commercial Security any any IP options are set ========= ============================= @@ -793,3 +796,59 @@ Example rule: .. container:: example-rule alert ip $EXTERNAL_NET any -> $HOME_NET any (:example-rule-emphasis:`icmpv6.mtu:<1280;` sid:1234; rev:5;) + + +IGMP keywords +------------- + +The Internet Group Management Protocol (IGMP) is the protocol used by IPv4 +systems to report their IP multicast group memberships to neighboring +multicast routers [RFC 9776]. + +Additionally, the RGMP protocol is a dialect of IGMP. The keywords below +also apply to RGMP. RGMP is defined in RFC 3488. + +igmp.hdr +^^^^^^^^ + +Sticky buffer to match on the whole IGMP header. + +Example rule: + +.. container:: example-rule + + alert igmp any any -> any any (:example-rule-emphasis:`igmp.hdr; content:"|22|"; startswith;` sid:1234; rev:5;) + + +igmp.type +^^^^^^^^^ + +Match on the IGMP type field. + +``igmp.type`` uses an :ref:`unsigned 8-bit integer `. + +Format:: + + igmp.type:0x11; + + +Example rule: + +.. container:: example-rule + + alert igmp any any -> any any (:example-rule-emphasis:`igmp.type:0x22;` sid:1234; rev:5;) + + +igmp-csum +^^^^^^^^^ + +Match on the validity of the checksum field. + +Format:: + + igmp-csum:valid; + igmp-csum:invalid; + +.. container:: example-rule + + alert igmp any any -> any any (:example-rule-emphasis:`igmp-csum:invalid;` sid:1234; rev:5;) diff --git a/etc/schema.json b/etc/schema.json index 9df20702acad..e55ed1f74619 100644 --- a/etc/schema.json +++ b/etc/schema.json @@ -2545,6 +2545,23 @@ ] } }, + "igmp": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "integer", + "suricata": { + "keywords": [ + "igmp.type" + ] + } + }, + "version": { + "type": "integer" + } + } + }, "ike": { "type": "object", "additionalProperties": false, @@ -5106,6 +5123,20 @@ }, "optional": true }, + "rgmp": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "integer", + "suricata": { + "keywords": [ + "igmp.type" + ] + } + } + } + }, "rpc": { "type": "object", "additionalProperties": false, @@ -6619,6 +6650,28 @@ } } }, + "igmp": { + "type": "object", + "additionalProperties": false, + "properties": { + "invalid_type": { + "type": "integer", + "description": "invalid IGMP type" + }, + "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 +7111,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" diff --git a/rules/decoder-events.rules b/rules/decoder-events.rules index 385d16228c0e..768878588435 100644 --- a/rules/decoder-events.rules +++ b/rules/decoder-events.rules @@ -120,6 +120,8 @@ alert icmp any any -> any any (msg:"SURICATA ICMPv4 invalid checksum"; icmpv4-cs alert tcp any any -> any any (msg:"SURICATA TCPv6 invalid checksum"; tcpv6-csum:invalid; classtype:protocol-command-decode; sid:2200077; rev:2;) alert udp any any -> any any (msg:"SURICATA UDPv6 invalid checksum"; udpv6-csum:invalid; classtype:protocol-command-decode; sid:2200078; rev:2;) alert icmp any any -> any any (msg:"SURICATA ICMPv6 invalid checksum"; icmpv6-csum:invalid; classtype:protocol-command-decode; sid:2200079; rev:2;) +alert igmp any any -> any any (msg:"SURICATA IGMP invalid checksum"; igmp-csum:invalid; classtype:protocol-command-decode; sid:2200126; rev:1;) + # IPv4 in IPv6 rules alert pkthdr any any -> any any (msg:"SURICATA IPv4-in-IPv6 packet too short"; decode-event:ipv6.ipv4_in_ipv6_too_small; classtype:protocol-command-decode; sid:2200082; rev:2;) alert pkthdr any any -> any any (msg:"SURICATA IPv4-in-IPv6 invalid protocol"; decode-event:ipv6.ipv4_in_ipv6_wrong_version; classtype:protocol-command-decode; sid:2200083; rev:2;) @@ -157,6 +159,11 @@ alert pkthdr any any -> any any (msg:"SURICATA packet with too many layers"; dec # Capture events. alert pkthdr any any -> any any (msg:"SURICATA AF-PACKET truncated packet"; decode-event:afpacket.trunc_pkt; classtype:protocol-command-decode; sid:2200122; rev:1;) +# IGMP +alert igmp any any -> any any (msg:"SURICATA IGMP packet too small"; decode-event:igmp.pkt_too_small; classtype:protocol-command-decode; sid:2200127; rev:1;) +alert igmp any any -> any any (msg:"SURICATA IGMPv3 packet too small"; decode-event:igmp.v3_pkt_too_small; classtype:protocol-command-decode; sid:2200128; rev:1;) +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 2200126 +# next sid is 2200130 diff --git a/src/Makefile.am b/src/Makefile.am index 161061a2c136..16064ce420cc 100755 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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 \ @@ -227,6 +228,8 @@ noinst_HEADERS = \ detect-icmpv6hdr.h \ detect-icode.h \ detect-id.h \ + detect-igmphdr.h \ + detect-igmp-type.h \ detect-ike-chosen-sa.h \ detect-ike-exch-type.h \ detect-ike-key-exchange-payload-length.h \ @@ -659,6 +662,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 \ @@ -812,6 +816,8 @@ libsuricata_c_a_SOURCES = \ detect-icmpv6hdr.c \ detect-icode.c \ detect-id.c \ + detect-igmphdr.c \ + detect-igmp-type.c \ detect-ike-chosen-sa.c \ detect-ike-exch-type.c \ detect-ike-key-exchange-payload-length.c \ @@ -1188,6 +1194,7 @@ EXTRA_DIST = \ tests/detect-http2.c \ tests/detect-icmpv6-mtu.c \ tests/detect-icmpv6hdr.c \ + tests/detect-igmphdr.c \ tests/detect-template.c \ tests/detect-transform-pcrexform.c \ tests/detect-ttl.c \ diff --git a/src/decode-events.c b/src/decode-events.c index 3af8fa048a61..32ce23dcda6b 100644 --- a/src/decode-events.c +++ b/src/decode-events.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", diff --git a/src/decode-events.h b/src/decode-events.h index 67c0f5259814..dc958c9c45f8 100644 --- a/src/decode-events.h +++ b/src/decode-events.h @@ -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, diff --git a/src/decode-igmp.c b/src/decode-igmp.c new file mode 100644 index 000000000000..d342a90835c3 --- /dev/null +++ b/src/decode-igmp.c @@ -0,0 +1,197 @@ +/* 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 + +/* RGMP requires a specific dest address 224.0.0.25. */ +#define RGMP_DEST_ADDRESS 0xe0000019 + +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; + + /* see if we're RGMP (RFC 3488): + * "All RGMP messages are sent with TTL 1, to destination address 224.0.0.25." + */ + bool rgmp = false; + if (PacketIsIPv4(p)) { + const IPV4Hdr *ip4h = PacketGetIPv4(p); + const uint8_t pttl = IPV4_GET_RAW_IPTTL(ip4h); + /* if packet is of the correct length (header size 8) to the correct address + * and has a ttl of 1, we consider it RGMP. */ + if (len == 8 && RGMP_DEST_ADDRESS == SCNtohl(p->dst.address.address_un_data32[0]) && + pttl == 1) { + SCLogDebug("RGMP (RFC 3488)"); + rgmp = true; + } + } + + /* 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; + p->l4.vars.igmp.rgmp = rgmp; + + return TM_ECODE_OK; +} diff --git a/src/decode-igmp.h b/src/decode-igmp.h new file mode 100644 index 000000000000..0a7c4ac11e41 --- /dev/null +++ b/src/decode-igmp.h @@ -0,0 +1,38 @@ +/* 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; + bool rgmp; /**< is this really a RFC 3488 RGMP packet */ +} IGMPVars; + +#endif /* SURICATA_DECODE_IGMP_H */ diff --git a/src/decode-ipv4.c b/src/decode-ipv4.c index 86ea095c6288..82c04580c289 100644 --- a/src/decode-ipv4.c +++ b/src/decode-ipv4.c @@ -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); diff --git a/src/decode.c b/src/decode.c index c1efed0d3be7..9fb93dadaf70 100644 --- a/src/decode.c +++ b/src/decode.c @@ -657,6 +657,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); diff --git a/src/decode.h b/src/decode.h index f9b493c3b1a0..16bdad28af82 100644 --- a/src/decode.h +++ b/src/decode.h @@ -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; @@ -1169,6 +1193,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, diff --git a/src/detect-csum.c b/src/detect-csum.c index 7a4b2ba44561..7139dea587c1 100644 --- a/src/detect-csum.c +++ b/src/detect-csum.c @@ -90,6 +90,12 @@ static int DetectICMPV6CsumMatch(DetectEngineThreadCtx *, static int DetectICMPV6CsumSetup(DetectEngineCtx *, Signature *, const char *); static void DetectICMPV6CsumFree(DetectEngineCtx *, void *); +/* prototypes for the "igmp-csum" rule keyword */ +static int DetectIGMPCsumMatch( + DetectEngineThreadCtx *, Packet *, const Signature *, const SigMatchCtx *); +static int DetectIGMPCsumSetup(DetectEngineCtx *, Signature *, const char *); +static void DetectIGMPCsumFree(DetectEngineCtx *, void *); + #ifdef UNITTESTS static void DetectCsumRegisterTests(void); #endif @@ -182,6 +188,12 @@ void DetectCsumRegister (void) sigmatch_table[DETECT_ICMPV6_CSUM].Setup = DetectICMPV6CsumSetup; sigmatch_table[DETECT_ICMPV6_CSUM].Free = DetectICMPV6CsumFree; sigmatch_table[DETECT_ICMPV6_CSUM].desc = "match on IPv6/ICMPv6 checksum"; + + sigmatch_table[DETECT_IGMP_CSUM].name = "igmp-csum"; + sigmatch_table[DETECT_IGMP_CSUM].Match = DetectIGMPCsumMatch; + sigmatch_table[DETECT_IGMP_CSUM].Setup = DetectIGMPCsumSetup; + sigmatch_table[DETECT_IGMP_CSUM].Free = DetectIGMPCsumFree; + sigmatch_table[DETECT_IGMP_CSUM].desc = "match on IPv4/IGMP checksum"; } /** @@ -796,6 +808,86 @@ static void DetectICMPV6CsumFree(DetectEngineCtx *de_ctx, void *ptr) SCFree(ptr); } +/** + * \brief Checks if the packet sent as the argument, has a valid or invalid + * igmp checksum, based on whether igmp-csum option for this rule + * has been supplied with "valid" or "invalid" argument + * + * \param t Pointer to the tv for this detection module instance + * \param det_ctx Pointer to the detection engine thread context + * \param p Pointer to the Packet currently being matched + * \param s Pointer to the Signature, the packet is being currently + * matched with + * \param m Pointer to the keyword_structure(SigMatch) from the above + * Signature, the Packet is being currently matched with + * + * \retval 1 if the Packet contents match the keyword option; 0 otherwise + */ +static int DetectIGMPCsumMatch( + DetectEngineThreadCtx *det_ctx, Packet *p, const Signature *s, const SigMatchCtx *ctx) +{ + const DetectCsumData *cd = (const DetectCsumData *)ctx; + + if (!PacketIsIPv4(p) || !PacketIsIGMP(p) || p->proto != IPPROTO_IGMP) + return 0; + + if (p->flags & PKT_IGNORE_CHECKSUM) { + return cd->valid; + } + + const IGMPHdr *igmph = PacketGetIGMP(p); + if (!p->l4.csum_set) { + const IPV4Hdr *ip4h = PacketGetIPv4(p); + p->l4.csum = ICMPV4CalculateChecksum( + (uint16_t *)igmph, IPV4_GET_RAW_IPLEN(ip4h) - IPV4_GET_RAW_HLEN(ip4h)); + p->l4.csum_set = true; + } + if (p->l4.csum == igmph->checksum && cd->valid == 1) + return 1; + else if (p->l4.csum != igmph->checksum && cd->valid == 0) + return 1; + else + return 0; +} + +/** + * \brief Creates a SigMatch for the icmpv4-csum keyword being sent as argument, + * and appends it to the Signature(s). Accepts 2 values for the + * keyword - "valid" and "invalid", both with and without quotes + * + * \param de_ctx Pointer to the detection engine context + * \param s Pointer to signature for the current Signature being parsed + * from the rules + * \param csum_str Pointer to the string holding the keyword value + * + * \retval 0 on success, -1 on failure + */ +static int DetectIGMPCsumSetup(DetectEngineCtx *de_ctx, Signature *s, const char *csum_str) +{ + DetectCsumData *cd = SCCalloc(1, sizeof(DetectCsumData)); + if (cd == NULL) + return -1; + + if (DetectCsumParseArg(csum_str, cd) == 0) + goto error; + + if (SCSigMatchAppendSMToList( + de_ctx, s, DETECT_IGMP_CSUM, (SigMatchCtx *)cd, DETECT_SM_LIST_MATCH) == NULL) { + goto error; + } + + return 0; + +error: + DetectIGMPCsumFree(de_ctx, cd); + return -1; +} + +static void DetectIGMPCsumFree(DetectEngineCtx *de_ctx, void *ptr) +{ + SCFree(ptr); +} + /* ---------------------------------- Unit Tests --------------------------- */ #ifdef UNITTESTS diff --git a/src/detect-engine-proto.c b/src/detect-engine-proto.c index 18cb3c43581c..77606b1b4dc1 100644 --- a/src/detect-engine-proto.c +++ b/src/detect-engine-proto.c @@ -57,6 +57,7 @@ struct { { "icmpv4", IPPROTO_ICMP, 0, 0 }, { "icmpv6", IPPROTO_ICMPV6, 0, 0 }, { "icmp", IPPROTO_ICMP, IPPROTO_ICMPV6, 0 }, + { "igmp", IPPROTO_IGMP, 0, 0 }, { "sctp", IPPROTO_SCTP, 0, 0 }, { "ipv4", 0, 0, DETECT_PROTO_IPV4 | DETECT_PROTO_ANY }, { "ip4", 0, 0, DETECT_PROTO_IPV4 | DETECT_PROTO_ANY }, diff --git a/src/detect-engine-register.c b/src/detect-engine-register.c index 477247642b04..f5fb811b2576 100644 --- a/src/detect-engine-register.c +++ b/src/detect-engine-register.c @@ -154,6 +154,8 @@ #include "detect-icmp-id.h" #include "detect-icmp-seq.h" #include "detect-icmpv4hdr.h" +#include "detect-igmphdr.h" +#include "detect-igmp-type.h" #include "detect-dce-iface.h" #include "detect-dce-opnum.h" #include "detect-dce-stub-data.h" @@ -681,6 +683,8 @@ void SigTableSetup(void) DetectIcmpIdRegister(); DetectIcmpSeqRegister(); DetectIcmpv4HdrRegister(); + DetectIGMPHdrRegister(); + DetectIGMPTypeRegister(); DetectDceIfaceRegister(); DetectDceOpnumRegister(); DetectDceStubDataRegister(); diff --git a/src/detect-engine-register.h b/src/detect-engine-register.h index c07a26dd97a5..36d8ce8984a8 100644 --- a/src/detect-engine-register.h +++ b/src/detect-engine-register.h @@ -49,6 +49,8 @@ enum DetectKeywordId { DETECT_ICMP_ID, DETECT_ICMP_SEQ, DETECT_ICMPV4HDR, + DETECT_IGMPHDR, + DETECT_IGMP_TYPE, DETECT_DSIZE, DETECT_FLOW, @@ -116,6 +118,7 @@ enum DetectKeywordId { DETECT_UDPV6_CSUM, DETECT_ICMPV4_CSUM, DETECT_ICMPV6_CSUM, + DETECT_IGMP_CSUM, DETECT_STREAM_SIZE, DETECT_DETECTION_FILTER, diff --git a/src/detect-igmp-type.c b/src/detect-igmp-type.c new file mode 100644 index 000000000000..fb3b3016014d --- /dev/null +++ b/src/detect-igmp-type.c @@ -0,0 +1,164 @@ +/* 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 + * + * Implements igmp.type keyword support + */ + +#include "suricata-common.h" +#include "decode.h" + +#include "detect.h" +#include "detect-parse.h" +#include "detect-engine-prefilter-common.h" +#include "detect-engine-build.h" + +#include "detect-igmp-type.h" +#include "detect-engine-uint.h" + +#include "util-byte.h" +#include "util-unittest.h" +#include "util-unittest-helper.h" +#include "util-debug.h" + +static int DetectIGMPTypeMatch( + DetectEngineThreadCtx *, Packet *, const Signature *, const SigMatchCtx *); +static int DetectIGMPTypeSetup(DetectEngineCtx *, Signature *, const char *); +void DetectIGMPTypeFree(DetectEngineCtx *, void *); + +static int PrefilterSetupIGMPType(DetectEngineCtx *de_ctx, SigGroupHead *sgh); +static bool PrefilterIGMPTypeIsPrefilterable(const Signature *s); + +/** + * \brief Registration function for igmp.type keyword + */ +void DetectIGMPTypeRegister(void) +{ + sigmatch_table[DETECT_IGMP_TYPE].name = "igmp.type"; + sigmatch_table[DETECT_IGMP_TYPE].desc = "match on a specific IGMP type"; + sigmatch_table[DETECT_IGMP_TYPE].url = "/rules/header-keywords.html#igmp.type"; + sigmatch_table[DETECT_IGMP_TYPE].Match = DetectIGMPTypeMatch; + sigmatch_table[DETECT_IGMP_TYPE].Setup = DetectIGMPTypeSetup; + sigmatch_table[DETECT_IGMP_TYPE].Free = DetectIGMPTypeFree; + sigmatch_table[DETECT_IGMP_TYPE].flags = SIGMATCH_INFO_UINT8; + sigmatch_table[DETECT_IGMP_TYPE].SupportsPrefilter = PrefilterIGMPTypeIsPrefilterable; + sigmatch_table[DETECT_IGMP_TYPE].SetupPrefilter = PrefilterSetupIGMPType; +} + +/** + * \brief This function is used to match igmp.type rule option + * + * \param t pointer to thread vars + * \param det_ctx pointer to the pattern matcher thread + * \param p pointer to the current packet + * \param m pointer to the sigmatch that we will cast into DetectU8Data + * + * \retval 0 no match + * \retval 1 match + */ +static int DetectIGMPTypeMatch( + DetectEngineThreadCtx *det_ctx, Packet *p, const Signature *s, const SigMatchCtx *ctx) +{ + DEBUG_VALIDATE_BUG_ON(PKT_IS_PSEUDOPKT(p)); + + if (!PacketIsIGMP(p)) { + /* Packet not IGMP */ + return 0; + } + + const IGMPHdr *igmph = PacketGetIGMP(p); + uint8_t type = igmph->type; + const DetectU8Data *itd = (const DetectU8Data *)ctx; + return DetectU8Match(type, itd); +} + +/** + * \brief this function is used to add the parsed igmp.type data into the current signature + * + * \param de_ctx pointer to the Detection Engine Context + * \param s pointer to the Current Signature + * \param str pointer to the user provided igmp.type options + * + * \retval 0 on Success + * \retval -1 on Failure + */ +static int DetectIGMPTypeSetup(DetectEngineCtx *de_ctx, Signature *s, const char *str) +{ + if (!(DetectProtoContainsProto(&s->proto, IPPROTO_IGMP))) + return -1; + + DetectU8Data *itd = DetectU8Parse(str); + if (itd == NULL) + return -1; + + if (SCSigMatchAppendSMToList( + de_ctx, s, DETECT_IGMP_TYPE, (SigMatchCtx *)itd, DETECT_SM_LIST_MATCH) == NULL) { + DetectIGMPTypeFree(de_ctx, itd); + return -1; + } + s->proto.flags |= DETECT_PROTO_IPV4; + s->flags |= SIG_FLAG_REQUIRE_PACKET; + + return 0; +} + +/** + * \brief this function will free memory associated with DetectU8Data + * + * \param ptr pointer to DetectU8Data + */ +void DetectIGMPTypeFree(DetectEngineCtx *de_ctx, void *ptr) +{ + DetectU8Data *itd = (DetectU8Data *)ptr; + SCDetectU8Free(itd); +} + +/* prefilter code + * + * Prefilter uses the U8Hash logic, where we setup a 256 entry array + * for each IGMP type. Each array element has the list of signatures + * that need to be inspected. */ + +static void PrefilterPacketIGMPTypeMatch( + DetectEngineThreadCtx *det_ctx, Packet *p, const void *pectx) +{ + DEBUG_VALIDATE_BUG_ON(PKT_IS_PSEUDOPKT(p)); + + if (PacketIsIGMP(p)) { + const IGMPHdr *igmph = PacketGetIGMP(p); + uint8_t type = igmph->type; + const PrefilterPacketU8HashCtx *h = pectx; + const SigsArray *sa = h->array[type]; + if (sa) { + PrefilterAddSids(&det_ctx->pmq, sa->sigs, sa->cnt); + } + } +} + +static int PrefilterSetupIGMPType(DetectEngineCtx *de_ctx, SigGroupHead *sgh) +{ + return PrefilterSetupPacketHeaderU8Hash(de_ctx, sgh, DETECT_IGMP_TYPE, + SIG_MASK_REQUIRE_REAL_PKT, PrefilterPacketU8Set, PrefilterPacketU8Compare, + PrefilterPacketIGMPTypeMatch); +} + +static bool PrefilterIGMPTypeIsPrefilterable(const Signature *s) +{ + return PrefilterIsPrefilterableById(s, DETECT_IGMP_TYPE); +} diff --git a/src/detect-igmp-type.h b/src/detect-igmp-type.h new file mode 100644 index 000000000000..4113745c7039 --- /dev/null +++ b/src/detect-igmp-type.h @@ -0,0 +1,29 @@ +/* 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_DETECT_IGMP_TYPE_H +#define SURICATA_DETECT_IGMP_TYPE_H + +/* prototypes */ +void DetectIGMPTypeRegister(void); + +#endif /* SURICATA_DETECT_IGMP_TYPE_H */ diff --git a/src/detect-igmphdr.c b/src/detect-igmphdr.c new file mode 100644 index 000000000000..f245e97770be --- /dev/null +++ b/src/detect-igmphdr.c @@ -0,0 +1,123 @@ +/* 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 "detect.h" +#include "detect-engine.h" +#include "detect-engine-buffer.h" +#include "detect-engine-mpm.h" +#include "detect-igmphdr.h" +#include "detect-engine-prefilter.h" + +/* prototypes */ +static int DetectIGMPHdrSetup(DetectEngineCtx *, Signature *, const char *); +#ifdef UNITTESTS +void DetectIGMPHdrRegisterTests(void); +#endif + +static int g_igmphdr_buffer_id = 0; + +static InspectionBuffer *GetData(DetectEngineThreadCtx *det_ctx, + const DetectEngineTransforms *transforms, Packet *p, const int list_id); + +/** + * \brief Registration function for igmp.hdr: keyword + */ +void DetectIGMPHdrRegister(void) +{ + sigmatch_table[DETECT_IGMPHDR].name = "igmp.hdr"; + sigmatch_table[DETECT_IGMPHDR].desc = "sticky buffer to match on the IGMP header"; + sigmatch_table[DETECT_IGMPHDR].url = "/rules/header-keywords.html#igmp-hdr"; + sigmatch_table[DETECT_IGMPHDR].Setup = DetectIGMPHdrSetup; + sigmatch_table[DETECT_IGMPHDR].flags |= SIGMATCH_NOOPT | SIGMATCH_INFO_STICKY_BUFFER; +#ifdef UNITTESTS + sigmatch_table[DETECT_IGMPHDR].RegisterTests = DetectIGMPHdrRegisterTests; +#endif + + g_igmphdr_buffer_id = DetectBufferTypeRegister("igmp.hdr"); + BUG_ON(g_igmphdr_buffer_id < 0); + + DetectBufferTypeSupportsPacket("igmp.hdr"); + + DetectPktMpmRegister("igmp.hdr", 2, PrefilterGenericMpmPktRegister, GetData); + + DetectPktInspectEngineRegister("igmp.hdr", GetData, DetectEngineInspectPktBufferGeneric); +} + +/** + * \brief setup igmp.hdr sticky buffer + * + * \param de_ctx pointer to the Detection Engine Context + * \param s pointer to the Current Signature + * \param _unused unused + * + * \retval 0 on Success + * \retval -1 on Failure + */ +static int DetectIGMPHdrSetup(DetectEngineCtx *de_ctx, Signature *s, const char *_unused) +{ + if (!(DetectProtoContainsProto(&s->proto, IPPROTO_IGMP))) + return -1; + + s->proto.flags |= DETECT_PROTO_IPV4; + s->flags |= SIG_FLAG_REQUIRE_PACKET; + + if (SCDetectBufferSetActiveList(de_ctx, s, g_igmphdr_buffer_id) < 0) + return -1; + + return 0; +} + +static InspectionBuffer *GetData(DetectEngineThreadCtx *det_ctx, + const DetectEngineTransforms *transforms, Packet *p, const int list_id) +{ + SCEnter(); + + if (!PacketIsIGMP(p)) { + SCReturnPtr(NULL, "InspectionBuffer"); + } + + InspectionBuffer *buffer = InspectionBufferGet(det_ctx, list_id); + if (buffer->inspect == NULL) { + const IGMPHdr *igmph = PacketGetIGMP(p); + const uint16_t hlen = p->l4.vars.igmp.hlen; + if (((uint8_t *)igmph + (ptrdiff_t)hlen) > + ((uint8_t *)GET_PKT_DATA(p) + (ptrdiff_t)GET_PKT_LEN(p))) { + SCLogDebug("data out of range: %p > %p", ((uint8_t *)igmph + (ptrdiff_t)hlen), + ((uint8_t *)GET_PKT_DATA(p) + (ptrdiff_t)GET_PKT_LEN(p))); + SCReturnPtr(NULL, "InspectionBuffer"); + } + + const uint32_t data_len = hlen; + const uint8_t *data = (const uint8_t *)igmph; + + InspectionBufferSetupAndApplyTransforms( + det_ctx, list_id, buffer, data, data_len, transforms); + } + + SCReturnPtr(buffer, "InspectionBuffer"); +} + +#ifdef UNITTESTS +#include "tests/detect-igmphdr.c" +#endif diff --git a/src/detect-igmphdr.h b/src/detect-igmphdr.h new file mode 100644 index 000000000000..feb193d7bd6b --- /dev/null +++ b/src/detect-igmphdr.h @@ -0,0 +1,27 @@ +/* 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 _DETECT_IGMPHDR_H +#define _DETECT_IGMPHDR_H + +void DetectIGMPHdrRegister(void); + +#endif /* _DETECT_IGMPHDR_H */ diff --git a/src/detect-ipopts.c b/src/detect-ipopts.c index 0a88e241ebb6..117bf11fd8d9 100644 --- a/src/detect-ipopts.c +++ b/src/detect-ipopts.c @@ -66,41 +66,53 @@ struct DetectIpOpts_ { const char *ipopt_name; /**< ip option name */ uint16_t code; /**< ip option flag value */ } ipopts[] = { + { + "ts", + IPV4_OPT_FLAG_TS, + }, { "rr", IPV4_OPT_FLAG_RR, }, { - "lsrr", - IPV4_OPT_FLAG_LSRR, + "qs", + IPV4_OPT_FLAG_QS, }, { - "eol", - IPV4_OPT_FLAG_EOL, + "sec", + IPV4_OPT_FLAG_SEC, }, { - "nop", - IPV4_OPT_FLAG_NOP, + "lsrr", + IPV4_OPT_FLAG_LSRR, }, { - "ts", - IPV4_OPT_FLAG_TS, + "esec", + IPV4_OPT_FLAG_ESEC, }, { - "sec", - IPV4_OPT_FLAG_SEC, + "cipso", + IPV4_OPT_FLAG_CIPSO, }, { - "esec", - IPV4_OPT_FLAG_ESEC, + "satid", + IPV4_OPT_FLAG_SID, }, { "ssrr", IPV4_OPT_FLAG_SSRR, }, { - "satid", - IPV4_OPT_FLAG_SID, + "rtralt", + IPV4_OPT_FLAG_RTRALT, + }, + { + "eol", + IPV4_OPT_FLAG_EOL, + }, + { + "nop", + IPV4_OPT_FLAG_NOP, }, { "any", @@ -117,24 +129,30 @@ struct DetectIpOpts_ { const char *IpOptsFlagToString(uint16_t flag) { switch (flag) { - case IPV4_OPT_FLAG_RR: - return "rr"; - case IPV4_OPT_FLAG_LSRR: - return "lsrr"; - case IPV4_OPT_FLAG_EOL: - return "eol"; - case IPV4_OPT_FLAG_NOP: - return "nop"; case IPV4_OPT_FLAG_TS: return "ts"; + case IPV4_OPT_FLAG_RR: + return "rr"; + case IPV4_OPT_FLAG_QS: + return "qs"; case IPV4_OPT_FLAG_SEC: return "sec"; + case IPV4_OPT_FLAG_LSRR: + return "lsrr"; case IPV4_OPT_FLAG_ESEC: return "esec"; - case IPV4_OPT_FLAG_SSRR: - return "ssrr"; + case IPV4_OPT_FLAG_CIPSO: + return "cipso"; case IPV4_OPT_FLAG_SID: return "satid"; + case IPV4_OPT_FLAG_SSRR: + return "ssrr"; + case IPV4_OPT_FLAG_RTRALT: + return "rtralt"; + case IPV4_OPT_FLAG_EOL: + return "eol"; + case IPV4_OPT_FLAG_NOP: + return "nop"; case 0xffff: return "any"; default: diff --git a/src/detect-tcp-flags.c b/src/detect-tcp-flags.c index 10140e02fc4e..1386c08173ce 100644 --- a/src/detect-tcp-flags.c +++ b/src/detect-tcp-flags.c @@ -74,7 +74,6 @@ void DetectFlagsRegister (void) sigmatch_table[DETECT_FLAGS].SupportsPrefilter = PrefilterTcpFlagsIsPrefilterable; sigmatch_table[DETECT_FLAGS].SetupPrefilter = PrefilterSetupTcpFlags; sigmatch_table[DETECT_FLAGS].flags = SIGMATCH_INFO_UINT8 | SIGMATCH_INFO_BITFLAGS_UINT; - ; } /** diff --git a/src/output-json.c b/src/output-json.c index 1b7464e35ee4..2d70b59ce413 100644 --- a/src/output-json.c +++ b/src/output-json.c @@ -924,6 +924,21 @@ SCJsonBuilder *CreateEveHeader(const Packet *p, enum SCOutputJsonLogDirection di SCJbSetUint(js, "icmp_code", PacketGetICMPv6(p)->code); } break; + case IPPROTO_IGMP: + if (PacketIsIGMP(p)) { + SCLogDebug("rgmp %s", BOOL2STR(p->l4.vars.igmp.rgmp)); + if (!p->l4.vars.igmp.rgmp) { + SCJbOpenObject(js, "igmp"); + SCJbSetUint(js, "type", PacketGetIGMP(p)->type); + SCJbSetUint(js, "version", p->l4.vars.igmp.version); + SCJbClose(js); + } else { + SCJbOpenObject(js, "rgmp"); + SCJbSetUint(js, "type", PacketGetIGMP(p)->type); + SCJbClose(js); + } + } + break; } SCJbSetString(js, "pkt_src", PktSrcToString(p->pkt_src)); diff --git a/src/tests/detect-igmphdr.c b/src/tests/detect-igmphdr.c new file mode 100644 index 000000000000..d987b24963cb --- /dev/null +++ b/src/tests/detect-igmphdr.c @@ -0,0 +1,45 @@ +/* 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. + */ + +#include "../suricata-common.h" + +#include "../detect.h" +#include "../detect-parse.h" + +#include "../detect-igmphdr.h" + +#include "../util-unittest.h" + +static int DetectIGMPHdrParseTest01(void) +{ + DetectEngineCtx *de_ctx = DetectEngineCtxInit(); + FAIL_IF_NULL(de_ctx); + + FAIL_IF_NULL(DetectEngineAppendSig( + de_ctx, "alert igmp any any -> any any (igmp.hdr; content:\"A\"; sid:1; rev:1;)")); + + DetectEngineCtxFree(de_ctx); + PASS; +} + +/** + * \brief register tests + */ +void DetectIGMPHdrRegisterTests(void) +{ + UtRegisterTest("DetectIGMPHdrParseTest01", DetectIGMPHdrParseTest01); +}