summaryrefslogtreecommitdiff
path: root/proto_igmp.c
diff options
context:
space:
mode:
authorDaniel Borkmann <dborkman@redhat.com>2013-03-15 10:41:48 +0100
committerDaniel Borkmann <dborkman@redhat.com>2013-03-15 10:41:48 +0100
commit1a9fbac03c684f29cff9ac44875bd9504a89f54e (patch)
tree1b2e40dbe5dc1899ef5b62c4325c9b94c9c450fc /proto_igmp.c
all: import netsniff-ng 0.5.8-rc0 source
We decided to get rid of the old Git history and start a new one for several reasons: *) Allow / enforce only high-quality commits (which was not the case for many commits in the history), have a policy that is more close to the one from the Linux kernel. With high quality commits, we mean code that is logically split into commits and commit messages that are signed-off and have a proper subject and message body. We do not allow automatic Github merges anymore, since they are total bullshit. However, we will either cherry-pick your patches or pull them manually. *) The old archive was about ~27MB for no particular good reason. This basically derived from the bad decision that also some PDF files where stored there. From this moment onwards, no binary objects are allowed to be stored in this repository anymore. The old archive is not wiped away from the Internet. You will still be able to find it, e.g. on git.cryptoism.org etc. Signed-off-by: Daniel Borkmann <dborkman@redhat.com> Signed-off-by: Tobias Klauser <tklauser@distanz.ch>
Diffstat (limited to 'proto_igmp.c')
-rw-r--r--proto_igmp.c560
1 files changed, 560 insertions, 0 deletions
diff --git a/proto_igmp.c b/proto_igmp.c
new file mode 100644
index 0000000..30fb527
--- /dev/null
+++ b/proto_igmp.c
@@ -0,0 +1,560 @@
+/*
+ * netsniff-ng - the packet sniffing beast
+ * Copyright (C) 2012 Christoph Jaeger <christoph@netsniff-ng.org>
+ * Subject to the GPL, version 2.
+ */
+
+#include <arpa/inet.h>
+#include <asm/byteorder.h>
+#include <netinet/in.h>
+
+#include "proto.h"
+#include "protos.h"
+#include "csum.h"
+#include "dissector_eth.h"
+#include "built_in.h"
+#include "pkt_buff.h"
+
+/* IGMPv0 (RFC-988) */
+struct igmp_v0_msg {
+ uint8_t type;
+ uint8_t code;
+ uint16_t checksum;
+ uint32_t identifier;
+ uint32_t group_address;
+ uint64_t access_key;
+} __packed;
+
+/* igmp_v0_msg.type */
+#define IGMP_V0_CREATE_GROUP_REQUEST 0x01
+#define IGMP_V0_CREATE_GROUP_REPLY 0x02
+#define IGMP_V0_JOIN_GROUP_REQUEST 0x03
+#define IGMP_V0_JOIN_GROUP_REPLY 0x04
+#define IGMP_V0_LEAVE_GROUP_REQUEST 0x05
+#define IGMP_V0_LEAVE_GROUP_REPLY 0x06
+#define IGMP_V0_CONFIRM_GROUP_REQUEST 0x07
+#define IGMP_V0_CONFIRM_GROUP_REPLY 0x08
+
+/* IGMPv1 (RFC-1054/RFC-1112, obsoletes RFC-988) */
+struct igmp_v1_msg {
+ union {
+ uint8_t version__type;
+ struct {
+#if defined(__LITTLE_ENDIAN_BITFIELD)
+ uint8_t type :4,
+ version :4;
+#elif defined(__BIG_ENDIAN_BITFIELD)
+ uint8_t version :4,
+ type :4;
+#else
+# error "Please fix <asm/byteorder.h>"
+#endif
+ };
+ };
+ uint8_t unused; /* always zero */
+ uint16_t checksum;
+ uint32_t group_address;
+} __attribute__((packed));
+
+/* igmp_v1_msg.version__type (!) */
+/* IGMP_V1_MEMBERSHIP_QUERY 0x11 */
+#define IGMP_V1_MEMBERSHIP_REPORT 0x12
+
+/* IGMPv2 (RFC-2236) */
+struct igmp_v2_msg {
+ uint8_t type;
+ uint8_t max_resp_time;
+ uint16_t checksum;
+ uint32_t group_address;
+} __attribute__((packed));
+
+/* igmp_v2_msg.type */
+/* IGMP_V2_MEMBERSHIP_QUERY 0x11 */
+#define IGMP_V2_MEMBERSHIP_REPORT 0x16
+#define IGMP_V2_LEAVE_GROUP 0x17
+
+/*
+ * RGMP (RFC-3488)
+ * The RGMP message format resembles the IGMPv2 message format. All RGMP
+ * messages are sent with TTL 1, to destination address 224.0.0.25.
+ */
+#define RGMP_LEAVE_GROUP 0xFC
+#define RGMP_JOIN_GROUP 0xFD
+#define RGMP_BYE 0xFE
+#define RGMP_HELLO 0xFF
+
+/* IGMPv3 (RFC-3376) */
+struct igmp_v3_group_record {
+ uint8_t record_type;
+ uint8_t aux_data_len; /* always zero */
+ uint16_t number_of_sources;
+ uint32_t multicast_address;
+ uint32_t source_addresses[0];
+ /* auxiliary data (IGMPv3 does not define any) */
+} __attribute__((packed));
+
+/* igmp_v3_group_record.record_type */
+#define IGMP_V3_MODE_IS_INCLUDE 1
+#define IGMP_V3_MODE_IS_EXCLUDE 2
+#define IGMP_V3_CHANGE_TO_INCLUDE_MODE 3
+#define IGMP_V3_CHANGE_TO_EXCLUDE_MODE 4
+#define IGMP_V3_ALLOW_NEW_SOURCES 5
+#define IGMP_V3_BLOCK_OLD_SOURCES 6
+
+struct igmp_v3_membership_report {
+ uint8_t type;
+ uint8_t reserved1;
+ uint16_t checksum;
+ uint16_t reserved2;
+ uint16_t number_of_group_records;
+ struct igmp_v3_group_record group_records[0];
+} __attribute__((packed));
+
+struct igmp_v3_membership_query {
+ uint8_t type;
+ uint8_t max_resp_code;
+ uint16_t checksum;
+ uint32_t group_address;
+#if defined(__LITTLE_ENDIAN_BITFIELD)
+ uint8_t qrv :3,
+ s_flag :1,
+ :4;
+#elif defined(__BIG_ENDIAN_BITFIELD)
+ uint8_t :4,
+ s_flag :1,
+ qrv :3;
+#else
+# error "Please fix <asm/byteorder.h>"
+#endif
+ uint8_t qqic;
+ uint16_t number_of_sources;
+ uint32_t source_addresses[0];
+} __attribute__((packed));
+
+#define IGMP_MEMBERSHIP_QUERY 0x11 /* v1/v2/v3 */
+#define IGMP_V3_MEMBERSHIP_REPORT 0x22
+
+#define EXP(x) (((x) & 0x70) >> 4)
+#define MANT(x) ((x) & 0x0F)
+
+#define DECODE_MAX_RESP_CODE(x) ((x) < 128 ? (x) : (MANT(x) | 0x10) << (EXP(x) + 3))
+#define DECODE_QQIC(x) ((x) < 128 ? (x) : (MANT(x) | 0x10) << (EXP(x) + 3))
+
+static char *friendly_msg_type_name(uint8_t msg_type)
+{
+ switch (msg_type) {
+ case IGMP_V0_CREATE_GROUP_REQUEST:
+ return "Create Group Request";
+ case IGMP_V0_CREATE_GROUP_REPLY:
+ return "Create Group Reply";
+ case IGMP_V0_JOIN_GROUP_REQUEST:
+ return "Join Group Request";
+ case IGMP_V0_JOIN_GROUP_REPLY:
+ return "Join Group Reply";
+ case IGMP_V0_LEAVE_GROUP_REQUEST:
+ return "Leave Group Request";
+ case IGMP_V0_LEAVE_GROUP_REPLY:
+ return "Leave Group Reply";
+ case IGMP_V0_CONFIRM_GROUP_REQUEST:
+ return "Confirm Group Request";
+ case IGMP_V0_CONFIRM_GROUP_REPLY:
+ return "Confirm Group Reply";
+ case IGMP_MEMBERSHIP_QUERY:
+ return "Membership Query";
+ case IGMP_V1_MEMBERSHIP_REPORT:
+ case IGMP_V2_MEMBERSHIP_REPORT:
+ case IGMP_V3_MEMBERSHIP_REPORT:
+ return "Membership Report";
+ case IGMP_V2_LEAVE_GROUP:
+ return "Leave Group";
+ case RGMP_HELLO:
+ return "Hello";
+ case RGMP_BYE:
+ return "Bye";
+ case RGMP_JOIN_GROUP:
+ return "Join Group";
+ case RGMP_LEAVE_GROUP:
+ return "Leave Group";
+ default:
+ return NULL;
+ }
+}
+
+#define PRINT_FRIENDLY_NAMED_MSG_TYPE(type) \
+ do { \
+ if (friendly_msg_type_name(type)) \
+ tprintf(" Type (0x%.2x, %s)", type, \
+ friendly_msg_type_name(type)); \
+ else \
+ tprintf(" Type (0x%.2x)", type); \
+ } while (0)
+
+static char *friendly_group_rec_type_name(uint8_t rec_type)
+{
+ switch (rec_type) {
+ case IGMP_V3_MODE_IS_INCLUDE:
+ return "Mode Is Include";
+ case IGMP_V3_MODE_IS_EXCLUDE:
+ return "Mode Is Exclude";
+ case IGMP_V3_CHANGE_TO_INCLUDE_MODE:
+ return "Change To Include Mode";
+ case IGMP_V3_CHANGE_TO_EXCLUDE_MODE:
+ return "Change To Exclude Mode";
+ case IGMP_V3_ALLOW_NEW_SOURCES:
+ return "Allow New Sources";
+ case IGMP_V3_BLOCK_OLD_SOURCES:
+ return "Block Old Sources";
+ default:
+ return NULL;
+ }
+}
+
+static void dissect_igmp_v0(struct pkt_buff *pkt)
+{
+ char addr[INET_ADDRSTRLEN];
+ uint16_t csum;
+
+ static const char *reply_codes[] = {
+ "Request Granted",
+ "Request Denied, No Resources",
+ "Request Denied, Invalid Code",
+ "Request Denied, Invalid Group Address",
+ "Request Denied, Invalid Access Key"
+ };
+
+ struct igmp_v0_msg *msg =
+ (struct igmp_v0_msg *) pkt_pull(pkt, sizeof(*msg));
+
+ if (msg == NULL)
+ return;
+
+ tprintf(" [ IGMPv0");
+ PRINT_FRIENDLY_NAMED_MSG_TYPE(msg->type);
+
+ switch (msg->type) {
+ case IGMP_V0_CREATE_GROUP_REQUEST:
+ switch (msg->code) {
+ case 0:
+ tprintf(", Code (%u, %s)", msg->code, "Public");
+ break;
+ case 1:
+ tprintf(", Code (%u, %s)", msg->code, "Private");
+ break;
+ default:
+ tprintf(", Code (%u)", msg->code);
+ }
+ break;
+ case IGMP_V0_CREATE_GROUP_REPLY:
+ case IGMP_V0_JOIN_GROUP_REPLY:
+ case IGMP_V0_LEAVE_GROUP_REPLY:
+ case IGMP_V0_CONFIRM_GROUP_REPLY:
+ if (msg->code < 5)
+ tprintf(", Code (%u, %s)", msg->code, reply_codes[msg->code]);
+ else
+ tprintf(", Code (%u, Request Pending, Retry In %u Seconds)",
+ msg->code, msg->code);
+ break;
+ default:
+ tprintf(", Code (%u)", msg->code);
+ }
+
+ csum = calc_csum(msg, sizeof(*msg) + pkt_len(pkt), 0);
+ tprintf(", CSum (0x%.4x) is %s", ntohs(msg->checksum), csum ?
+ colorize_start_full(black, red) "bogus (!)" colorize_end() : "ok");
+ if (csum)
+ tprintf(" - %s should be %x%s", colorize_start_full(black, red),
+ csum_expected(msg->checksum, csum), colorize_end());
+ tprintf(", Id (%u)", ntohs(msg->identifier));
+ inet_ntop(AF_INET, &msg->group_address, addr, sizeof(addr));
+ tprintf(", Group Addr (%s)", addr);
+ tprintf(", Access Key (0x%.16lx)", msg->access_key);
+ tprintf(" ]\n");
+}
+
+static void dissect_igmp_v1(struct pkt_buff *pkt)
+{
+ char addr[INET_ADDRSTRLEN];
+ uint16_t csum;
+
+ struct igmp_v1_msg *msg =
+ (struct igmp_v1_msg *) pkt_pull(pkt, sizeof(*msg));
+
+ if (msg == NULL)
+ return;
+
+ tprintf(" [ IGMPv1");
+ PRINT_FRIENDLY_NAMED_MSG_TYPE(msg->version__type);
+ csum = calc_csum(msg, sizeof(*msg) + pkt_len(pkt), 0);
+ tprintf(", CSum (0x%.4x) is %s", ntohs(msg->checksum), csum ?
+ colorize_start_full(black, red) "bogus (!)" colorize_end() : "ok");
+ if (csum)
+ tprintf(" - %s should be %x%s", colorize_start_full(black, red),
+ csum_expected(msg->checksum, csum), colorize_end());
+ inet_ntop(AF_INET, &msg->group_address, addr, sizeof(addr));
+ tprintf(", Group Addr (%s)", addr);
+ tprintf(" ]\n");
+}
+
+static void dissect_igmp_v2(struct pkt_buff *pkt)
+{
+ char addr[INET_ADDRSTRLEN];
+ uint16_t csum;
+
+ struct igmp_v2_msg *msg =
+ (struct igmp_v2_msg *) pkt_pull(pkt, sizeof(*msg));
+
+ if (msg == NULL)
+ return;
+
+ switch (msg->type) {
+ case RGMP_HELLO:
+ case RGMP_BYE:
+ case RGMP_JOIN_GROUP:
+ case RGMP_LEAVE_GROUP:
+ tprintf(" [ IGMPv2 (RGMP)");
+ break;
+ default:
+ tprintf(" [ IGMPv2");
+ break;
+ }
+
+ PRINT_FRIENDLY_NAMED_MSG_TYPE(msg->type);
+ tprintf(", Max Resp Time (%u)", msg->max_resp_time);
+ csum = calc_csum(msg, sizeof(*msg) + pkt_len(pkt), 0);
+ tprintf(", CSum (0x%.4x) is %s", ntohs(msg->checksum), csum ?
+ colorize_start_full(black, red) "bogus (!)" colorize_end() : "ok");
+ if (csum)
+ tprintf(" - %s should be %x%s", colorize_start_full(black, red),
+ csum_expected(msg->checksum, csum), colorize_end());
+ inet_ntop(AF_INET, &msg->group_address, addr, sizeof(addr));
+ tprintf(", Group Addr (%s)", addr);
+ tprintf(" ]\n");
+}
+
+static void dissect_igmp_v3_membership_query(struct pkt_buff *pkt)
+{
+ char addr[INET_ADDRSTRLEN];
+ size_t n;
+ uint16_t csum;
+ uint32_t *src_addr;
+
+ struct igmp_v3_membership_query *msg =
+ (struct igmp_v3_membership_query *) pkt_pull(pkt, sizeof(*msg));
+
+ if (msg == NULL)
+ return;
+
+ tprintf(" [ IGMPv3");
+ PRINT_FRIENDLY_NAMED_MSG_TYPE(msg->type);
+ tprintf(", Max Resp Code (0x%.2x => %u)", msg->max_resp_code,
+ DECODE_MAX_RESP_CODE(msg->max_resp_code));
+ csum = calc_csum(msg, sizeof(*msg) + pkt_len(pkt), 0);
+ tprintf(", CSum (0x%.4x) is %s", ntohs(msg->checksum), csum ?
+ colorize_start_full(black, red) "bogus (!)" colorize_end() : "ok");
+ if (csum)
+ tprintf(" - %s should be %x%s", colorize_start_full(black, red),
+ csum_expected(msg->checksum, csum), colorize_end());
+ inet_ntop(AF_INET, &msg->group_address, addr, sizeof(addr));
+ /* S Flag (Suppress Router-Side Processing) */
+ tprintf(", Suppress (%u)", msg->s_flag ? 1 : 0);
+ /* QRV (Querier's Robustness Variable) */
+ tprintf(", QRV (%u)", msg->qrv);
+ /* QQIC (Querier's Query Interval Code) */
+ tprintf(", QQIC (0x%.2x => %u)", msg->qqic, DECODE_QQIC(msg->qqic));
+ tprintf(", Group Addr (%s)", addr);
+ n = ntohs(msg->number_of_sources);
+ tprintf(", Num Src (%zu)", n);
+
+ if (n--) {
+ src_addr = (uint32_t *) pkt_pull(pkt, sizeof(*src_addr));
+ if (src_addr != NULL) {
+ inet_ntop(AF_INET, src_addr, addr, sizeof(addr));
+ tprintf(", Src Addr (%s", addr);
+ while (n--) {
+ src_addr = (uint32_t *)
+ pkt_pull(pkt, sizeof(*src_addr));
+ if (src_addr != NULL)
+ break;
+ inet_ntop(AF_INET, src_addr, addr, sizeof(addr));
+ tprintf(", %s", addr);
+ }
+ tprintf(")");
+ }
+ }
+ tprintf(" ]\n");
+}
+
+static void dissect_igmp_v3_membership_report(struct pkt_buff *pkt)
+{
+ char addr[INET_ADDRSTRLEN];
+ size_t m, n;
+ uint16_t csum;
+ uint32_t *src_addr;
+
+ struct igmp_v3_group_record *rec;
+ struct igmp_v3_membership_report *msg =
+ (struct igmp_v3_membership_report *) pkt_pull(pkt, sizeof(*msg));
+
+ if (msg == NULL)
+ return;
+
+ tprintf(" [ IGMPv3");
+ PRINT_FRIENDLY_NAMED_MSG_TYPE(msg->type);
+ csum = calc_csum(msg, sizeof(*msg) + pkt_len(pkt), 0);
+ tprintf(", CSum (0x%.4x) is %s", ntohs(msg->checksum), csum ?
+ colorize_start_full(black, red) "bogus (!)" colorize_end() : "ok");
+ if (csum)
+ tprintf(" - %s should be %x%s", colorize_start_full(black, red),
+ csum_expected(msg->checksum, csum), colorize_end());
+ m = ntohs(msg->number_of_group_records);
+ tprintf(", Num Group Rec (%zu)", m);
+ tprintf(" ]\n");
+
+ while (m--) {
+ rec = (struct igmp_v3_group_record *) pkt_pull(pkt, sizeof(*rec));
+
+ if (rec == NULL)
+ break;
+
+ tprintf(" [ Group Record");
+ if (friendly_group_rec_type_name(rec->record_type))
+ tprintf(" Type (%u, %s)", rec->record_type,
+ friendly_group_rec_type_name(rec->record_type));
+ else
+ tprintf(" Type (%u)", rec->record_type);
+ n = ntohs(rec->number_of_sources);
+ tprintf(", Num Src (%zu)", n);
+ inet_ntop(AF_INET, &rec->multicast_address, addr, sizeof(addr));
+ tprintf(", Multicast Addr (%s)", addr);
+
+ if (n--) {
+ src_addr = (uint32_t *) pkt_pull(pkt, sizeof(*src_addr));
+ if (src_addr != NULL) {
+ inet_ntop(AF_INET, src_addr, addr, sizeof(addr));
+ tprintf(", Src Addr (%s", addr);
+ while (n--) {
+ src_addr = (uint32_t *)
+ pkt_pull(pkt, sizeof(*src_addr));
+ if (src_addr != NULL)
+ break;
+ inet_ntop(AF_INET, src_addr, addr, sizeof(addr));
+ tprintf(", %s", addr);
+ }
+ tprintf(")");
+ }
+ }
+
+ tprintf(" ]\n");
+ }
+ tprintf("\n");
+}
+
+static void igmp(struct pkt_buff *pkt)
+{
+ switch (*pkt_peek(pkt)) {
+ case IGMP_V0_CREATE_GROUP_REQUEST:
+ case IGMP_V0_CREATE_GROUP_REPLY:
+ case IGMP_V0_JOIN_GROUP_REQUEST:
+ case IGMP_V0_JOIN_GROUP_REPLY:
+ case IGMP_V0_LEAVE_GROUP_REQUEST:
+ case IGMP_V0_LEAVE_GROUP_REPLY:
+ case IGMP_V0_CONFIRM_GROUP_REQUEST:
+ case IGMP_V0_CONFIRM_GROUP_REPLY:
+ if (pkt_len(pkt) == sizeof(struct igmp_v0_msg))
+ dissect_igmp_v0(pkt);
+ break;
+ case IGMP_MEMBERSHIP_QUERY: /* v1/v2/v3 */
+ if (pkt_len(pkt) >= sizeof(struct igmp_v3_membership_query))
+ dissect_igmp_v3_membership_query(pkt);
+ else if (pkt_len(pkt) == sizeof(struct igmp_v2_msg)
+ && *(pkt_peek(pkt) + 1))
+ dissect_igmp_v2(pkt);
+ else if (pkt_len(pkt) == sizeof(struct igmp_v1_msg))
+ dissect_igmp_v1(pkt);
+ break;
+ case IGMP_V1_MEMBERSHIP_REPORT:
+ if (pkt_len(pkt) == sizeof(struct igmp_v1_msg))
+ dissect_igmp_v1(pkt);
+ break;
+ case RGMP_HELLO:
+ case RGMP_BYE:
+ case RGMP_JOIN_GROUP:
+ case RGMP_LEAVE_GROUP:
+ case IGMP_V2_MEMBERSHIP_REPORT:
+ case IGMP_V2_LEAVE_GROUP:
+ if (pkt_len(pkt) == sizeof(struct igmp_v2_msg))
+ dissect_igmp_v2(pkt);
+ break;
+ case IGMP_V3_MEMBERSHIP_REPORT:
+ if (pkt_len(pkt) >= sizeof(struct igmp_v3_membership_report))
+ dissect_igmp_v3_membership_report(pkt);
+ break;
+ }
+}
+
+static void igmp_less(struct pkt_buff *pkt)
+{
+ int version = -1;
+
+ switch (*pkt_peek(pkt)) {
+ case IGMP_V0_CREATE_GROUP_REQUEST:
+ case IGMP_V0_CREATE_GROUP_REPLY:
+ case IGMP_V0_JOIN_GROUP_REQUEST:
+ case IGMP_V0_JOIN_GROUP_REPLY:
+ case IGMP_V0_LEAVE_GROUP_REQUEST:
+ case IGMP_V0_LEAVE_GROUP_REPLY:
+ case IGMP_V0_CONFIRM_GROUP_REQUEST:
+ case IGMP_V0_CONFIRM_GROUP_REPLY:
+ if (pkt_len(pkt) == sizeof(struct igmp_v0_msg))
+ version = 0;
+ break;
+ case IGMP_MEMBERSHIP_QUERY: /* v1/v2/v3 */
+ if (pkt_len(pkt) >= sizeof(struct igmp_v3_membership_query))
+ version = 3;
+ else if (pkt_len(pkt) == sizeof(struct igmp_v2_msg)
+ && *(pkt_peek(pkt) + 1))
+ version = 2;
+ else if (pkt_len(pkt) == sizeof(struct igmp_v1_msg))
+ version = 1;
+ break;
+ case IGMP_V1_MEMBERSHIP_REPORT:
+ if (pkt_len(pkt) == sizeof(struct igmp_v1_msg))
+ version = 1;
+ break;
+ case RGMP_HELLO:
+ case RGMP_BYE:
+ case RGMP_JOIN_GROUP:
+ case RGMP_LEAVE_GROUP:
+ case IGMP_V2_MEMBERSHIP_REPORT:
+ case IGMP_V2_LEAVE_GROUP:
+ if (pkt_len(pkt) == sizeof(struct igmp_v2_msg))
+ version = 2;
+ break;
+ case IGMP_V3_MEMBERSHIP_REPORT:
+ if (pkt_len(pkt) >= sizeof(struct igmp_v3_membership_report))
+ version = 3;
+ break;
+ }
+
+ if (version < 0 || version > 3)
+ return;
+
+ switch (*pkt_peek(pkt)) {
+ case RGMP_HELLO:
+ case RGMP_BYE:
+ case RGMP_JOIN_GROUP:
+ case RGMP_LEAVE_GROUP:
+ tprintf(" IGMPv2 (RGMP)");
+ break;
+ default:
+ tprintf(" IGMPv%u", version);
+ break;
+ }
+ PRINT_FRIENDLY_NAMED_MSG_TYPE(*pkt_peek(pkt));
+}
+
+struct protocol igmp_ops = {
+ .key = 0x02,
+ .print_full = igmp,
+ .print_less = igmp_less,
+};