diff options
Diffstat (limited to 'proto_igmp.c')
-rw-r--r-- | proto_igmp.c | 560 |
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, +}; |