summaryrefslogtreecommitdiff
path: root/igmp.c
diff options
context:
space:
mode:
authorDavid Härdeman <david@hardeman.nu>2020-06-23 16:25:36 +0200
committerDavid Härdeman <david@hardeman.nu>2020-06-23 16:25:36 +0200
commit8c27290245b7bcc7cd2f72f3b4a7562294b43bbe (patch)
tree54bae7909a94bfc598df7b88d9794742daf0bb31 /igmp.c
parent973ae757342b91e3e6aafd07e0c0a24af84aad98 (diff)
Split directories better
Diffstat (limited to 'igmp.c')
-rw-r--r--igmp.c587
1 files changed, 0 insertions, 587 deletions
diff --git a/igmp.c b/igmp.c
deleted file mode 100644
index dc43a9f..0000000
--- a/igmp.c
+++ /dev/null
@@ -1,587 +0,0 @@
-#include <unistd.h>
-#include <string.h>
-#include <stdint.h>
-#include <sys/socket.h>
-#include <linux/if_packet.h>
-#include <net/ethernet.h>
-#include <net/if.h>
-#include <netinet/ip.h>
-#include <linux/bpf.h>
-#include <linux/filter.h>
-#include <arpa/inet.h>
-#include <errno.h>
-#include <sys/ioctl.h>
-
-#include "main.h"
-#include "uring.h"
-#include "igmp.h"
-#include "announce.h"
-
-struct igmp {
- struct uring_task task;
- struct uring_task_buf tbuf;
-};
-
-#define ETH_HDR_LEN 14
-#define IPV4_MIN_HDR_LEN 20
-#define IGMP_MIN_LEN 8
-
-struct __attribute__((packed, scalar_storage_order("big-endian"))) ipv4_hdr {
- unsigned version:4;
- unsigned ihl:4;
- unsigned dscp:6;
- unsigned ecn:2;
- unsigned length:16;
- unsigned id:16;
- unsigned flags:3;
- unsigned fragment_offset:13;
- unsigned ttl:8;
- unsigned protocol:8;
- unsigned checksum:16;
- unsigned src:32;
- unsigned dst:32;
- unsigned options[];
-};
-
-enum igmp_type {
- IGMP_MEMBERSHIP_QUERY = 0x11,
- IGMP_V1_MEMBERSHIP_REPORT = 0x12,
- IGMP_V2_MEMBERSHIP_REPORT = 0x16,
- IGMP_V3_MEMBERSHIP_REPORT = 0x22,
- IGMP_V2_LEAVE_GROUP = 0x17
-};
-
-union igmp_msg {
- struct __attribute__((packed, scalar_storage_order("big-endian"))) {
- enum igmp_type type:8;
- unsigned unknown:8;
- unsigned checksum:16;
- } common;
-
- struct __attribute__((packed, scalar_storage_order("big-endian"))) {
- enum igmp_type type:8;
- unsigned resptime:8;
- unsigned checksum:16;
- unsigned addr:32;
- } v2;
-
- struct __attribute__((packed, scalar_storage_order("big-endian"))) {
- enum igmp_type type:8;
- unsigned reserved1:8;
- unsigned checksum:16;
- unsigned reserved2:16;
- unsigned nrecs:16;
- uint8_t records[];
- } v3;
-};
-
-enum igmp_v3_record_type {
- IGMP_V3_REC_MODE_IS_INCL = 1,
- IGMP_V3_REC_MODE_IS_EXCL = 2,
- IGMP_V3_REC_MODE_CH_INCL = 3,
- IGMP_V3_REC_MODE_CH_EXCL = 4
-};
-
-union igmp_v3_record {
- struct __attribute__((packed, scalar_storage_order("big-endian"))) {
- enum igmp_v3_record_type type:8;
- unsigned auxlen:8;
- unsigned nsrcs:16;
- unsigned addr:32;
- uint32_t saddr[];
- };
-};
-
-static inline unsigned short
-from32to16(unsigned int x)
-{
- /* add up 16-bit and 16-bit for 16+c bit */
- x = (x & 0xffff) + (x >> 16);
- /* add up carry.. */
- x = (x & 0xffff) + (x >> 16);
- return x;
-}
-
-static unsigned int
-do_csum(const unsigned char *buf, int len)
-{
- int odd;
- unsigned int result = 0;
-
- assert_return(buf && len > 0, 0);
-
- odd = 1 & (unsigned long)buf;
- if (odd) {
-#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
- result += (*buf << 8);
-#else
- result = *buf;
-#endif
- len--;
- buf++;
- }
-
- if (len >= 2) {
- if (2 & (unsigned long)buf) {
- result += *(unsigned short *)buf;
- len -= 2;
- buf += 2;
- }
- if (len >= 4) {
- const unsigned char *end = buf + ((unsigned)len & ~3);
- unsigned int carry = 0;
- do {
- unsigned int w = *(unsigned int *)buf;
- buf += 4;
- result += carry;
- result += w;
- carry = (w > result);
- } while (buf < end);
- result += carry;
- result = (result & 0xffff) + (result >> 16);
- }
- if (len & 2) {
- result += *(unsigned short *)buf;
- buf += 2;
- }
- }
-
- if (len & 1)
-#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
- result += *buf;
-#else
- result += (*buf << 8);
-#endif
-
- result = from32to16(result);
- if (odd)
- result = ((result >> 8) & 0xff) | ((result & 0xff) << 8);
-
- return result;
-}
-
-static inline bool
-csum_valid(const char *buf, size_t len)
-{
- assert_return(buf && len > 0, false);
-
- return do_csum((unsigned const char *)buf, len) == 0xffff;
-}
-
-static void
-igmp_match()
-{
- debug(DBG_IGMP, "multicast request discovered");
- /*
- * IGMP messages are sent with approx 120-130 sec intervals,
- * so set time to 5 minutes to allow some slack.
- */
- announce_start(5 * 60);
-}
-
-static void
-igmp_parse(struct igmp *igmp)
-{
- char *buf;
- size_t len;
- struct ipv4_hdr *hdr;
- size_t body_len;
- union igmp_msg *igmp_msg;
-
- assert_return(igmp);
-
- buf = igmp->task.tbuf->buf;
- len = igmp->task.tbuf->len;
- hdr = (struct ipv4_hdr *)buf;
-
- if (len <= IPV4_MIN_HDR_LEN)
- return;
-
- if (hdr->version != 4)
- return;
-
- if (hdr->ihl * 4 < IPV4_MIN_HDR_LEN)
- return;
-
- if (hdr->length < hdr->ihl * 4)
- return;
-
- if (hdr->length != len)
- return;
-
- if (hdr->fragment_offset > 0)
- return;
-
- if (hdr->protocol != IPPROTO_IGMP)
- return;
-
- if (!csum_valid(buf, hdr->ihl * 4))
- return;
-
- body_len = hdr->length - hdr->ihl * 4;
- igmp_msg = (union igmp_msg *)(buf + hdr->ihl * 4);
-
- if (body_len < IGMP_MIN_LEN)
- return;
-
- switch (igmp_msg->common.type) {
- case IGMP_V1_MEMBERSHIP_REPORT:
- debug(DBG_IGMP, "igmp_v1_membership_report");
- /* fall through */
-
- case IGMP_V2_MEMBERSHIP_REPORT: {
- struct in_addr src;
- char src_str[INET_ADDRSTRLEN];
- struct in_addr dst;
- char dst_str[INET_ADDRSTRLEN];
- struct in_addr grp;
- char grp_str[INET_ADDRSTRLEN];
-
- src.s_addr = htonl(hdr->src);
- inet_ntop(AF_INET, &src, src_str, sizeof(src_str));
- dst.s_addr = htonl(hdr->dst);
- inet_ntop(AF_INET, &dst, dst_str, sizeof(dst_str));
- grp.s_addr = htonl(igmp_msg->v2.addr);
- inet_ntop(AF_INET, &grp, grp_str, sizeof(grp_str));
-
- debug(DBG_IGMP, "igmp_v2_membership_report %s -> %s (%s)",
- src_str, dst_str, grp_str);
-
- if (body_len != IGMP_MIN_LEN) {
- error("IGMPv2 invalid size");
- break;
- }
-
- if (!csum_valid((char *)igmp_msg, body_len)) {
- error("IGMPv2 invalid checksum");
- break;
- }
-
- debug(DBG_IGMP, "Inet addr: 0x%x", inet_addr("224.0.2.60"));
- debug(DBG_IGMP, "Inet addr: 0x%x", cinet_addr(224,0,2,60));
- debug(DBG_IGMP, "Inet addr: 0x%x", chtobe32(cinet_addr(224,0,2,60)));
- if (htonl(hdr->dst) != cinet_addr(224,0,2,60)) {
- debug(DBG_IGMP, "IGMPv2 invalid dst addr");
- break;
- }
-
- if (htonl(igmp_msg->v2.addr) != cinet_addr(224,0,2,60)) {
- debug(DBG_IGMP, "IGMPv2 invalid grp addr");
- break;
- }
-
- igmp_match();
- break;
- }
-
- case IGMP_V3_MEMBERSHIP_REPORT: {
- char *pos = (char *)igmp_msg;
- struct in_addr src;
- char src_str[INET_ADDRSTRLEN];
- struct in_addr dst;
- char dst_str[INET_ADDRSTRLEN];
-
- src.s_addr = htonl(hdr->src);
- inet_ntop(AF_INET, &src, src_str, sizeof(src_str));
- dst.s_addr = htonl(hdr->dst);
- inet_ntop(AF_INET, &dst, dst_str, sizeof(dst_str));
-
- debug(DBG_IGMP, "igmp_v3_membership_report %s -> %s",
- src_str, dst_str);
-
- debug(DBG_IGMP, "IGMPv3"
- " type: %x,"
- " reserved: %u,"
- " csum: %u,"
- " reserved: %u,"
- " nrecs: %u,"
- " size: %zu\n",
- igmp_msg->v3.type,
- igmp_msg->v3.reserved1,
- igmp_msg->v3.checksum,
- igmp_msg->v3.reserved2,
- igmp_msg->v3.nrecs,
- sizeof(igmp_msg->v3));
-
- if (!csum_valid(pos, body_len)) {
- error("IGMPv3 csum invalid");
- break;
- }
-
- if (htonl(hdr->dst) != cinet_addr(224,0,0,22)) {
- debug(DBG_IGMP, "IGMPv2 invalid dst addr");
- break;
- }
-
- body_len -= sizeof(igmp_msg->v3);
- pos += sizeof(igmp_msg->v3);
-
- for (unsigned rec = 0; rec < igmp_msg->v3.nrecs; rec++) {
- union igmp_v3_record *record = (union igmp_v3_record *)pos;
- struct in_addr grp;
- char grp_str[INET_ADDRSTRLEN];
-
- if (body_len < sizeof(*record)) {
- error("IGMPv3 too short");
- break;
- }
-
- grp.s_addr = htonl(record->addr);
- inet_ntop(AF_INET, &grp, grp_str, sizeof(grp_str));
- debug(DBG_IGMP, "received IGMPv3 record to %s", grp_str);
-
- debug(DBG_IGMP, "IGMPv3 rec, "
- " type: %u,"
- " auxlen: %u,"
- " nsrcs: %u,"
- " addr: %s,"
- " size: %zu bytes",
- record->type,
- record->auxlen,
- record->nsrcs,
- grp_str,
- sizeof(*record));
-
- body_len -= sizeof(*record);
- pos += sizeof(*record);
-
- if (body_len < record->nsrcs * sizeof(uint32_t) + record->auxlen) {
- error("IGMPv3 too short");
- break;
- }
-
- for (unsigned addr = 0; addr < record->nsrcs; addr++) {
- struct in_addr grp_src;
- char grp_src_str[INET_ADDRSTRLEN];
-
- grp_src.s_addr = htonl(record->saddr[addr]);
- inet_ntop(AF_INET, &grp_src, grp_src_str, sizeof(grp_src_str));
- debug(DBG_IGMP, "received IGMPv3 record src %s",
- grp_src_str);
-
- body_len -= sizeof(record->saddr[addr]);
- pos += sizeof(record->saddr[addr]);
- }
-
- /* Yes, EXCL, not INCL, see RFC3376 */
- if ((htonl(record->addr) == cinet_addr(224,0,2,60)) &&
- ((record->type == IGMP_V3_REC_MODE_IS_EXCL) ||
- (record->type == IGMP_V3_REC_MODE_CH_EXCL)))
- igmp_match();
-
- body_len -= record->auxlen;
- pos += record->auxlen;
- }
-
- break;
- }
-
- case IGMP_MEMBERSHIP_QUERY:
- debug(DBG_IGMP, "igmp_membership_query");
- break;
-
- case IGMP_V2_LEAVE_GROUP:
- debug(DBG_IGMP, "igmp_v2_leave_group");
- break;
-
- default:
- debug(DBG_IGMP, "IGMP msg type %02hhx", igmp_msg->common.type);
- break;
- }
-
- buf += hdr->length;
- len -= hdr->length;
-}
-
-static void
-igmp_read_cb(struct uring_task *task, int res)
-{
- struct igmp *igmp = container_of(task, struct igmp, task);
-
- assert_return(task);
- assert_task_alive(DBG_IGMP, task);
-
- debug(DBG_IGMP, "task %p, igmp %p, res %i", task, igmp, res);
- if (res < 0) {
- error("res: %i", res);
- return;
- }
-
- task->tbuf->len = res;
-
- if (task->saddr.storage.ss_family == AF_PACKET ||
- task->saddr.ll.sll_protocol == htons(ETH_P_IP))
- igmp_parse(igmp);
- else
- debug(DBG_IGMP, "invalid packet type received");
-
- uring_tbuf_read(&igmp->task, igmp_read_cb);
-}
-
-static void
-igmp_free(struct uring_task *task)
-{
- struct igmp *igmp = container_of(task, struct igmp, task);
-
- assert_return(task);
- debug(DBG_IGMP, "task %p, igmp %p", task, igmp);
- xfree(igmp);
-}
-
-void
-igmp_refdump()
-{
- assert_return_silent(cfg->igmp);
-
- uring_task_refdump(&cfg->igmp->task);
-}
-
-void
-igmp_delete()
-{
- assert_return_silent(cfg->igmp);
-
- debug(DBG_IGMP, "closing fd %i", cfg->igmp->task.fd);
- uring_task_destroy(&cfg->igmp->task);
- cfg->igmp = NULL;
-}
-
-void
-igmp_init()
-{
- static const struct sock_filter filter[] = {
- BPF_STMT(BPF_LD + BPF_W + BPF_LEN, 0), /* A <- packet length */
- BPF_JUMP(BPF_JMP + BPF_JGE + BPF_K, sizeof(struct iphdr), 1, 0), /* A < sizeof(iphdr) */
- BPF_STMT(BPF_RET + BPF_K, 0), /* drop packet */
-
- BPF_STMT(BPF_LD + BPF_B + BPF_ABS, 0 /* iphdr[0] */), /* A <- version + ihl */
- BPF_STMT(BPF_ALU + BPF_RSH + BPF_K, 4), /* A <- A >> 4 (version) */
- BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x04, 1, 0), /* A != 4 */
- BPF_STMT(BPF_RET + BPF_K, 0), /* drop packet */
-
- BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(struct iphdr, protocol)), /* A <- ip protocol */
- BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_IGMP, 1, 0), /* A != IPPROTO_IGMP */
- BPF_STMT(BPF_RET + BPF_K, 0), /* drop packet */
-
- BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct iphdr, daddr)), /* A <- ip dst addr */
- BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, chtobe32(cinet_addr(224,0,2,60)), 2, 0), /* A != 224.0.2.60 */
- //BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0xe000023c, 2, 0), /* A != 224.0.2.60 */
- BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, chtobe32(cinet_addr(224,0,0,22)), 1, 0), /* A != 224.0.0.22 */
- //BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0xe0000016, 1, 0), /* A != 224.0.0.22 */
- BPF_STMT(BPF_RET + BPF_K, 0), /* drop packet */
-
- BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, 0 /* iphdr[0] */), /* X <- pkt->ihl * 4 */
- BPF_STMT(BPF_LD + BPF_IMM, 20), /* A <- 20 */
- BPF_JUMP(BPF_JMP + BPF_JGT + BPF_X, 0, 0, 1), /* A > X */
- BPF_STMT(BPF_RET + BPF_K, 0), /* drop packet */
-
- BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct iphdr, tot_len)), /* A <- ip tot_len */
- BPF_JUMP(BPF_JMP + BPF_JGT + BPF_X, 0, 1, 0), /* A <= ip->ihl * 4 */
- BPF_STMT(BPF_RET + BPF_K, 0), /* drop packet */
- BPF_STMT(BPF_ALU + BPF_SUB + BPF_X, 0), /* A <- A - X (bodylen) */
- BPF_JUMP(BPF_JMP + BPF_JGE + BPF_K, 8, 1, 0), /* A < 8 */
- BPF_STMT(BPF_RET + BPF_K, 0), /* drop packet */
-
- BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct iphdr, tot_len)), /* A <- ip->tot_len */
- BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */
- BPF_STMT(BPF_LD + BPF_W + BPF_LEN, 0), /* A <- packet length */
- BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_X, 0, 1, 0), /* A != ip->tot_len */
- BPF_STMT(BPF_RET + BPF_K, 0), /* drop packet */
-
- BPF_STMT(BPF_RET + BPF_K, (uint32_t) -1), /* accept packet */
- };
- static const struct sock_fprog fprog = {
- .len = ARRAY_SIZE(filter),
- .filter = (struct sock_filter*) filter,
- };
- struct sockaddr_ll addr = {
- .sll_family = AF_PACKET,
- .sll_ifindex = 0,
- .sll_pkttype = PACKET_MULTICAST,
- };
- struct igmp *igmp;
- int sfd;
- int opt;
-
- if (!cfg->do_igmp) {
- debug(DBG_IGMP, "igmp snooping disabled");
- return;
- }
-
- assert_return(!cfg->igmp);
-
- igmp = zmalloc(sizeof(*igmp));
- if (!igmp)
- return;
-
- /*
- * Kernel limitation, must be ETH_P_ALL, not ETH_P_IP or we won't get
- * outgoing packets, https://lkml.org/lkml/1999/12/23/112
- */
- sfd = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC, htons(ETH_P_ALL));
- if (sfd < 0) {
- if (errno == EACCES || errno == EPERM)
- info("Unable to do IGMP snooping, permission denied");
- else
- error("socket: %m");
- goto error;
- }
-
- if (setsockopt(sfd, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)) < 0) {
- error("setsockopt(SO_ATTACH_FILTER): %m");
- goto error;
- }
-
- if (setsockopt(sfd, SOL_SOCKET, SO_LOCK_FILTER, &opt, sizeof(opt)) < 0) {
- error("setsockopt(SO_LOCK_FILTER): %m");
- goto error;
- }
-
- if (cfg->igmp_iface) {
- struct ifreq ifreq;
- int r;
-
- r = snprintf(ifreq.ifr_name, sizeof(ifreq.ifr_name),
- "%s", cfg->igmp_iface);
- if (r < 0 || r >= sizeof(ifreq.ifr_name))
- die("invalid interface name: %s", cfg->igmp_iface);
-
- if (ioctl(sfd, SIOCGIFINDEX, &ifreq) < 0)
- die("ioctl: %m");
-
- debug(DBG_IGMP, "using interface %s (%i)",
- cfg->igmp_iface, ifreq.ifr_ifindex);
-
- struct packet_mreq mreq = {
- .mr_ifindex = ifreq.ifr_ifindex,
- .mr_type = PACKET_MR_ALLMULTI
- };
-
- if (setsockopt(sfd, SOL_PACKET, PACKET_ADD_MEMBERSHIP,
- &mreq, sizeof(mreq)) < 0) {
- error("setsockopt(PACKET_ADD_MEMBERSHIP): %m");
- goto error;
- }
- }
-
- /* can't set .sll_protocol to htons(ETH_P_IP), see comment above */
- if (bind(sfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
- error("bind: %m");
- goto error;
- }
-
- debug(DBG_IGMP, "init successful, using fd %i", sfd);
- uring_task_init(&igmp->task, "igmp", uring_parent(), igmp_free);
- uring_task_set_fd(&igmp->task, sfd);
- uring_task_set_buf(&igmp->task, &igmp->tbuf);
- igmp->task.saddr.addrlen = sizeof(igmp->task.saddr.ll);
- uring_tbuf_recvmsg(&igmp->task, igmp_read_cb);
-
- cfg->igmp = igmp;
-
- return;
-
-error:
- close(sfd);
- xfree(igmp);
-}