diff --git a/README.md b/README.md index 4059a14..c362577 100644 --- a/README.md +++ b/README.md @@ -57,10 +57,9 @@ Description: however strongly recommended to leave this setting commented out! > **Note:** the daemon needs an address on interfaces to operate, it is -> expected that querierd runs on top of a bridge, and that the bridge -> takes care of per-VLAN proxy queries. Also, currently the daemon does -> not react automatically to IP address changes, so it needs to be -> SIGHUP'ed to use any new interface or address. +> expected that querierd runs on top of a bridge. Also, currently the +> daemon does not react automatically to IP address changes, so it needs +> to be SIGHUP'ed to use any new interface or address. Motivation diff --git a/src/config.c b/src/config.c index 7d8ffb1..0e15ec1 100644 --- a/src/config.c +++ b/src/config.c @@ -76,6 +76,31 @@ struct ifi *config_find_iface(int ifindex) return NULL; } +static int getmac(const char *ifname, uint8_t *mac, size_t size) +{ + struct ifreq ifr; + int rc = 0; + int sock; + + if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + return 1; + + ifr.ifr_addr.sa_family = AF_INET; + strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); + + if (ioctl(sock, SIOCGIFHWADDR, &ifr) == -1) { + rc = 1; + goto done; + } + + memcpy(mac, ifr.ifr_hwaddr.sa_data, size); + +done: + close(sock); + + return rc; +} + /* * Called by parser to add an interface to start or watch for in the future */ @@ -106,6 +131,9 @@ struct ifi *config_iface_add(char *ifname) else ifi->ifi_flags |= IFIF_DOWN; + if (getmac(ifname, ifi->ifi_hwaddr, sizeof(ifi->ifi_hwaddr))) + logit(LOG_WARNING, errno, "failed finding hw address for iface %s", ifname); + TAILQ_INSERT_TAIL(&ifaces, ifi, ifi_link); return ifi; diff --git a/src/defs.h b/src/defs.h index 69854bf..27ce547 100644 --- a/src/defs.h +++ b/src/defs.h @@ -82,6 +82,11 @@ extern char s2[MAX_INET_BUF_LEN]; extern char s3[MAX_INET_BUF_LEN]; extern char s4[MAX_INET_BUF_LEN]; +#define IGMP_PROXY_QUERY_MAXLEN (sizeof(struct ether_header) + \ + sizeof(struct ip) + \ + 4 + \ + IGMP_MINLEN) + /* * Limit on length of route data */ @@ -145,8 +150,9 @@ extern void resetlogging(void *); extern void igmp_init(void); extern void igmp_exit(void); extern void accept_igmp(int, size_t); -extern size_t build_igmp(uint32_t, uint32_t, int, int, uint32_t, int); +extern size_t build_igmp(uint8_t *, uint32_t, uint32_t, int, int, uint32_t, int); extern void send_igmp(int, uint32_t, uint32_t, int, int, uint32_t, int); +extern void send_igmp_proxy(const struct ifi *); extern char * igmp_packet_kind(uint32_t, uint32_t); extern int igmp_debug_kind(uint32_t, uint32_t); diff --git a/src/iface.c b/src/iface.c index 7859fa6..8f21802 100644 --- a/src/iface.c +++ b/src/iface.c @@ -82,20 +82,37 @@ void iface_zero(struct ifi *ifi) ifi->ifi_igmpv1_warn = 0; } +static int iface_is_proxy(const struct ifi *ifi) +{ + return ifi->ifi_flags & IFIF_DISABLED; +} + /* * Restart IGMP Querier election * * Start by figuring out the best local address for the iface. Check if * the current address is better (RFC), make sure an IPv4LL doesn't win. * Usually we want a real address if available. 0.0.0.0 is reserved for - * proxy querys, which we cannot do on a plain UDP socket, and they must - * never win an election. (Proxy queries should be sent by the bridge.) + * proxy querys, which we resort to if proxy mode is active and no real + * querier has been seen. */ void iface_check_election(struct ifi *ifi) { in_addr_t curr = 0; struct phaddr *pa; + if (iface_is_proxy(ifi)) { + if (ifi->ifi_querier && ifi->ifi_querier->al_addr) + return; + + if (ifi->ifi_querier) { + pev_timer_del(ifi->ifi_querier->al_timerid); + free(ifi->ifi_querier); + ifi->ifi_querier = NULL; + } + goto elected; + } + TAILQ_FOREACH(pa, &ifi->ifi_addrs, pa_link) { in_addr_t cand = pa->pa_addr; @@ -142,7 +159,8 @@ void iface_check_election(struct ifi *ifi) * the first query. */ ifi->ifi_flags |= IFIF_QUERIER; - logit(LOG_DEBUG, 0, "Assuming querier duties on interface %s", ifi->ifi_name); + logit(LOG_DEBUG, 0, "Assuming %squerier duties on interface %s", + iface_is_proxy(ifi) ? "proxy " : "", ifi->ifi_name); send_query(ifi, allhosts_group, igmp_response_interval * IGMP_TIMER_SCALE, 0); } @@ -269,19 +287,6 @@ static void send_query(struct ifi *ifi, uint32_t dst, int code, uint32_t group) { int datalen = 4; - if (!ifi->ifi_curr_addr) { - /* - * If we send with source address 0.0.0.0 on a UDP socket the - * kernel will go dumpster diving to find a "suitable" address - * from another interface. Obviously we don't want that ... we - * would've liked to be able to send a proxy query, but that's - * not possible unless SOCK_RAW, so we delegate the proxy query - * mechanism to the bridge and bail out here. - */ -// logit(LOG_DEBUG, 0, "Skipping send of query on %s, no address yet.", ifi->ifi_name); - return; - } - /* * IGMP version to send depends on the compatibility mode of the * interface: @@ -297,13 +302,16 @@ static void send_query(struct ifi *ifi, uint32_t dst, int code, uint32_t group) code = 0; } - logit(LOG_DEBUG, 0, "Sending %squery on %s", + logit(LOG_DEBUG, 0, "Sending %squery on %s src %s", (ifi->ifi_flags & IFIF_IGMPV1) ? "v1 " : (ifi->ifi_flags & IFIF_IGMPV2) ? "v2 " : "v3 ", - ifi->ifi_name); + ifi->ifi_name, inet_name(ifi->ifi_curr_addr, 1)); - send_igmp(ifi->ifi_ifindex, ifi->ifi_curr_addr, dst, IGMP_MEMBERSHIP_QUERY, - code, group, datalen); + if (ifi->ifi_curr_addr) + send_igmp(ifi->ifi_ifindex, ifi->ifi_curr_addr, dst, IGMP_MEMBERSHIP_QUERY, + code, group, datalen); + else + send_igmp_proxy(ifi); } static void start_iface(struct ifi *ifi) @@ -328,8 +336,7 @@ static void start_iface(struct ifi *ifi) /* * Check if we should assume the querier role */ - if (!(ifi->ifi_flags & IFIF_DISABLED)) - iface_check_election(ifi); + iface_check_election(ifi); logit(LOG_INFO, 0, "Interface %s now in service", ifi->ifi_name); } @@ -382,7 +389,7 @@ static void query_groups(int period, void *arg) { struct ifi *ifi = (struct ifi *)arg; - if (ifi->ifi_flags & (IFIF_DOWN | IFIF_DISABLED)) + if (ifi->ifi_flags & IFIF_DOWN) return; if (ifi->ifi_flags & IFIF_QUERIER) diff --git a/src/iface.h b/src/iface.h index f1290cf..b794d90 100644 --- a/src/iface.h +++ b/src/iface.h @@ -22,6 +22,7 @@ struct ifi { struct listaddr *ifi_querier; /* IGMP querier (one or none) */ int ifi_timerid; /* IGMP query timer */ int ifi_igmpv1_warn; /* To rate-limit IGMPv1 warnings */ + uint8_t ifi_hwaddr[6]; /* MAC address of this interface */ }; #define IFIF_DOWN 0x000100 /* kernel state of interface */ diff --git a/src/igmp.c b/src/igmp.c index 0de0185..21444fc 100644 --- a/src/igmp.c +++ b/src/igmp.c @@ -3,6 +3,10 @@ * by the license in the accompanying file named "LICENSE". */ +#include +#include +#include + #include "defs.h" #define PIM_QUERY 0 @@ -20,6 +24,7 @@ uint8_t *recv_buf; /* input packet buffer */ uint8_t *send_buf; /* output packet buffer */ int igmp_socket; /* socket for all network I/O */ +int igmp_raw_pkt_socket; /* socket for ethernet frames */ int router_alert; /* IP option Router Alert */ uint32_t router_timeout; /* Other querier present intv. */ uint32_t igmp_query_interval; /* Default: 125 sec */ @@ -34,11 +39,15 @@ uint32_t allreports_group; /* IGMPv3 member reports */ * Private variables. */ static int igmp_sockid; +static uint8_t proxy_send_buf[IGMP_PROXY_QUERY_MAXLEN]; +static size_t proxy_send_len; /* * Local function definitions. */ static void igmp_read(int sd, void *arg); +static void ipv4_set_static_fields(uint8_t *buf); +static size_t build_ipv4(uint8_t *buf, uint32_t src, uint32_t dst, short unsigned int datalen); /* * Open and initialize the igmp socket, and fill in the non-changing @@ -48,8 +57,6 @@ void igmp_init(void) { const int BUFSZ = 256 * 1024; const int MINSZ = 48 * 1024; - struct ip *ip; - uint8_t *ip_opt; recv_buf = calloc(1, RECV_BUF_SIZE); send_buf = calloc(1, RECV_BUF_SIZE); @@ -63,34 +70,15 @@ void igmp_init(void) if (igmp_socket < 0) logit(LOG_ERR, errno, "Failed creating IGMP socket"); + igmp_raw_pkt_socket = socket(AF_PACKET, SOCK_RAW, IPPROTO_RAW); + if (igmp_raw_pkt_socket < 0) + logit(LOG_ERR, errno, "Failed creating IGMP raw packet socket"); + k_hdr_include(1); /* include IP header when sending */ k_set_pktinfo(1); /* ifindex in aux data on receive */ k_set_rcvbuf(BUFSZ, MINSZ); /* lots of input buffering */ k_set_ttl(1); /* restrict multicasts to one hop */ - /* - * Fields zeroed that aren't filled in later: - * - IP ID (let the kernel fill it in) - * - Offset (we don't send fragments) - * - Checksum (let the kernel fill it in) - */ - ip = (struct ip *)send_buf; - ip->ip_v = IPVERSION; - ip->ip_hl = IP_HEADER_RAOPT_LEN >> 2; - ip->ip_tos = 0xc0; /* Internet Control */ - ip->ip_ttl = MAXTTL; /* applies to unicasts only */ - ip->ip_p = IPPROTO_IGMP; - - /* - * RFC2113 IP Router Alert. Per spec this is required to - * force certain routers/switches to inspect this frame. - */ - ip_opt = send_buf + sizeof(struct ip); - ip_opt[0] = IPOPT_RA; - ip_opt[1] = 4; - ip_opt[2] = 0; - ip_opt[3] = 0; - allhosts_group = htonl(INADDR_ALLHOSTS_GROUP); allrtrs_group = htonl(INADDR_ALLRTRS_GROUP); allreports_group = htonl(INADDR_ALLRPTS_GROUP); @@ -102,6 +90,13 @@ void igmp_init(void) router_timeout = IGMP_OTHER_QUERIER_PRESENT_INTERVAL; router_alert = 1; + ipv4_set_static_fields(send_buf); + + ipv4_set_static_fields(proxy_send_buf + sizeof(struct ether_header)); + proxy_send_len = sizeof(struct ether_header); + proxy_send_len += build_ipv4(proxy_send_buf + proxy_send_len, 0, allhosts_group, sizeof(struct igmp)); + proxy_send_len += build_igmp(proxy_send_buf + proxy_send_len, 0, allhosts_group, IGMP_MEMBERSHIP_QUERY, 0, 0, 0); + igmp_sockid = pev_sock_add(igmp_socket, igmp_read, NULL); if (igmp_sockid == -1) logit(LOG_ERR, errno, "Failed registering IGMP handler"); @@ -110,6 +105,7 @@ void igmp_init(void) void igmp_exit(void) { pev_sock_del(igmp_sockid); + close(igmp_raw_pkt_socket); close(igmp_socket); free(recv_buf); free(send_buf); @@ -271,6 +267,70 @@ void accept_igmp(int ifindex, size_t recvlen) } } +static size_t build_ether_ipv4_mc(uint8_t *buf, const uint8_t *srcmac, const uint32_t *dst) +{ + struct ether_header *eh = (struct ether_header *)buf; + + memset(eh, 0, sizeof(*eh)); + + memcpy(eh->ether_shost, srcmac, sizeof(eh->ether_shost)); + ETHER_MAP_IP_MULTICAST(dst, eh->ether_dhost); + eh->ether_type = htons(ETH_P_IP); + + return sizeof(*eh); +} + +static void ipv4_set_static_fields(uint8_t *buf) +{ + struct ip *ip; + uint8_t *ip_opt; + + ip = (struct ip *)buf; + ip->ip_v = IPVERSION; + ip->ip_hl = IP_HEADER_RAOPT_LEN >> 2; + ip->ip_tos = 0xc0; /* Internet Control */ + ip->ip_ttl = MAXTTL; /* applies to unicasts only */ + ip->ip_p = IPPROTO_IGMP; + + /* + * RFC2113 IP Router Alert. Per spec this is required to + * force certain routers/switches to inspect this frame. + */ + ip_opt = buf + sizeof(struct ip); + ip_opt[0] = IPOPT_RA; + ip_opt[1] = 4; + ip_opt[2] = 0; + ip_opt[3] = 0; +} + +static size_t build_ipv4(uint8_t *buf, uint32_t src, uint32_t dst, short unsigned int datalen) +{ + struct ip *ip = (struct ip *)(buf); + size_t len = IP_HEADER_RAOPT_LEN; + + ip->ip_src.s_addr = src; + ip->ip_dst.s_addr = dst; + ip->ip_len = htons(len + datalen); + if (IN_MULTICAST(ntohl(dst))) + ip->ip_ttl = curttl; + else + ip->ip_ttl = MAXTTL; + + /* + * We don't have anything unique to set this to - for proxy queries, + * for other queries the kernel will step in and replace zero values + * in the header anyway. It shouldn't be a problem even for proxy + * queries though since the packet size is so small that it should + * hardly be subject to fragmentation. + */ + ip->ip_id = 0; + + ip->ip_sum = 0; + ip->ip_sum = inet_cksum((uint16_t *)buf, len); + + return len; +} + /* * RFC-3376 states that Max Resp Code (MRC) and Querier's Query Interval Code * (QQIC) should be presented in floating point value if their value exceeds @@ -344,23 +404,12 @@ static inline uint8_t igmp_floating_point(unsigned int mantissa) return exponent | (mantissa & 0x0000000F); } -size_t build_query(uint32_t src, uint32_t dst, int type, int code, uint32_t group, int datalen) +size_t build_query(uint8_t *buf, uint32_t src, uint32_t dst, int type, int code, uint32_t group, int datalen) { - struct igmpv3_query *igmp; + struct igmpv3_query *igmp = (struct igmpv3_query *)buf; struct ip *ip; size_t igmp_len = IGMP_MINLEN + datalen; - size_t len = IP_HEADER_RAOPT_LEN + igmp_len; - - ip = (struct ip *)send_buf; - ip->ip_src.s_addr = src; - ip->ip_dst.s_addr = dst; - ip->ip_len = htons(len); - if (IN_MULTICAST(ntohl(dst))) - ip->ip_ttl = curttl; - else - ip->ip_ttl = MAXTTL; - igmp = (struct igmpv3_query *)(send_buf + IP_HEADER_RAOPT_LEN); memset(igmp, 0, sizeof(*igmp)); igmp->type = type; @@ -379,37 +428,26 @@ size_t build_query(uint32_t src, uint32_t dst, int type, int code, uint32_t grou /* Note: calculate IGMP checksum last. */ igmp->csum = inet_cksum((uint16_t *)igmp, igmp_len); - return len; + return igmp_len; } /* * Construct an IGMP message in the output packet buffer. The caller may * have already placed data in that buffer, of length 'datalen'. */ -size_t build_igmp(uint32_t src, uint32_t dst, int type, int code, uint32_t group, int datalen) +size_t build_igmp(uint8_t *buf, uint32_t src, uint32_t dst, int type, int code, uint32_t group, int datalen) { - struct ip *ip; struct igmp *igmp; size_t igmp_len = IGMP_MINLEN + datalen; - size_t len = IP_HEADER_RAOPT_LEN + IGMP_MINLEN + datalen; - - ip = (struct ip *)send_buf; - ip->ip_src.s_addr = src; - ip->ip_dst.s_addr = dst; - ip->ip_len = htons(len); - if (IN_MULTICAST(ntohl(dst))) - ip->ip_ttl = curttl; - else - ip->ip_ttl = MAXTTL; - igmp = (struct igmp *)(send_buf + IP_HEADER_RAOPT_LEN); + igmp = (struct igmp *)buf; igmp->igmp_type = type; igmp->igmp_code = code; igmp->igmp_group.s_addr = group; igmp->igmp_cksum = 0; igmp->igmp_cksum = inet_cksum((uint16_t *)igmp, igmp_len); - return len; + return igmp_len; } /* @@ -421,17 +459,20 @@ void send_igmp(int ifindex, uint32_t src, uint32_t dst, int type, int code, uint { struct sockaddr_in sin; struct ip *ip; - size_t len; + size_t len = 0; int rc; /* Set IP header length, router-alert is optional */ ip = (struct ip *)send_buf; ip->ip_hl = IP_HEADER_RAOPT_LEN >> 2; + len += build_ipv4(send_buf, src, dst, datalen); + if (IGMP_MEMBERSHIP_QUERY == type) - len = build_query(src, dst, type, code, group, datalen); - else - len = build_igmp(src, dst, type, code, group, datalen); + len += build_query(send_buf + len, src, dst, type, code, group, datalen); + else { + len += build_igmp(send_buf + len, src, dst, type, code, group, datalen); + } /* For all IGMP, change egress interface (we have only one socket) */ if (IN_MULTICAST(ntohl(dst))) @@ -455,6 +496,28 @@ void send_igmp(int ifindex, uint32_t src, uint32_t dst, int type, int code, uint inet_fmt(dst, s2, sizeof(s2))); } +void send_igmp_proxy(const struct ifi *ifi) +{ + struct sockaddr_ll sa; + int rc; + + /* + * The IP header and IGMP payload are static for proxy queries and have + * already been set when proxy_send_buf was initilized + */ + build_ether_ipv4_mc(proxy_send_buf, ifi->ifi_hwaddr, &allhosts_group); + + sa.sll_ifindex = ifi->ifi_ifindex; + sa.sll_halen = ETH_ALEN; + + rc = sendto(igmp_raw_pkt_socket, proxy_send_buf, proxy_send_len, 0, (struct sockaddr *)&sa, sizeof(sa)); + if (rc < 0) { + logit(LOG_WARNING, errno, "sendto for proxy query failed"); + } + + logit(LOG_DEBUG, 0, "SENT proxy query from %s", ifi->ifi_name); +} + /** * Local Variables: * indent-tabs-mode: t