summaryrefslogtreecommitdiff
path: root/proto_igmp.c
diff options
context:
space:
mode:
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,
+};