/* ** igmpproxy - IGMP proxy based multicast router ** Copyright (C) 2005 Johnny Egeland ** ** This program is free software; you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation; either version 2 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program; if not, write to the Free Software ** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ** **---------------------------------------------------------------------------- ** ** This software is derived work from the following software. The original ** source code has been modified from it's original state by the author ** of igmpproxy. ** ** smcroute 0.92 - Copyright (C) 2001 Carsten Schill ** - Licensed under the GNU General Public License, version 2 ** ** mrouted 3.9-beta3 - COPYRIGHT 1989 by The Board of Trustees of ** Leland Stanford Junior University. ** - Original license can be found in the Stanford.txt file. ** */ /** * igmp.h - Recieves IGMP requests, and handle them * appropriately... */ #include "igmpproxy.h" // Globals uint32_t allhosts_group; /* All hosts addr in net order */ uint32_t allrouters_group; /* All hosts addr in net order */ extern int MRouterFD; /* * Open and initialize the igmp socket, and fill in the non-changing * IP header fields in the output packet buffer. */ void initIgmp() { struct ip *ip; recv_buf = malloc(RECV_BUF_SIZE); send_buf = malloc(RECV_BUF_SIZE); k_hdr_include(true); /* include IP header when sending */ k_set_rcvbuf(256*1024,48*1024); /* lots of input buffering */ k_set_ttl(1); /* restrict multicasts to one hop */ k_set_loop(false); /* disable multicast loopback */ ip = (struct ip *)send_buf; memset(ip, 0, sizeof(struct ip)); /* * 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->ip_v = IPVERSION; ip->ip_hl = sizeof(struct ip) >> 2; ip->ip_tos = 0xc0; /* Internet Control */ ip->ip_ttl = MAXTTL; /* applies to unicasts only */ ip->ip_p = IPPROTO_IGMP; allhosts_group = htonl(INADDR_ALLHOSTS_GROUP); allrouters_group = htonl(INADDR_ALLRTRS_GROUP); } /** * Finds the textual name of the supplied IGMP request. */ char *igmpPacketKind(u_int type, u_int code) { static char unknown[20]; switch (type) { case IGMP_MEMBERSHIP_QUERY: return "Membership query "; case IGMP_V1_MEMBERSHIP_REPORT: return "V1 member report "; case IGMP_V2_MEMBERSHIP_REPORT: return "V2 member report "; case IGMP_V2_LEAVE_GROUP: return "Leave message "; default: sprintf(unknown, "unk: 0x%02x/0x%02x ", type, code); return unknown; } } /** * Process a newly received IGMP packet that is sitting in the input * packet buffer. */ void acceptIgmp(int recvlen) { register uint32_t src, dst, group; struct ip *ip; struct igmp *igmp; int ipdatalen, iphdrlen, igmpdatalen; if (recvlen < sizeof(struct ip)) { my_log(LOG_WARNING, 0, "received packet too short (%u bytes) for IP header", recvlen); return; } ip = (struct ip *)recv_buf; src = ip->ip_src.s_addr; dst = ip->ip_dst.s_addr; /* * this is most likely a message from the kernel indicating that * a new src grp pair message has arrived and so, it would be * necessary to install a route into the kernel for this. */ if (ip->ip_p == 0) { if (src == 0 || dst == 0) { my_log(LOG_WARNING, 0, "kernel request not accurate"); } else { struct IfDesc *checkVIF; // Check if the source address matches a valid address on upstream vif. checkVIF = getIfByIx( upStreamVif ); if(checkVIF == 0) { my_log(LOG_ERR, 0, "Upstream VIF was null."); return; } else if(src == checkVIF->InAdr.s_addr) { my_log(LOG_NOTICE, 0, "Route activation request from %s for %s is from myself. Ignoring.", inetFmt(src, s1), inetFmt(dst, s2)); return; } else if(!isAdressValidForIf(checkVIF, src)) { my_log(LOG_WARNING, 0, "The source address %s for group %s, is not in any valid net for upstream VIF.", inetFmt(src, s1), inetFmt(dst, s2)); return; } // Activate the route. my_log(LOG_DEBUG, 0, "Route activate request from %s to %s", inetFmt(src,s1), inetFmt(dst,s2)); activateRoute(dst, src); } return; } iphdrlen = ip->ip_hl << 2; ipdatalen = ip_data_len(ip); if (iphdrlen + ipdatalen != recvlen) { my_log(LOG_WARNING, 0, "received packet from %s shorter (%u bytes) than hdr+data length (%u+%u)", inetFmt(src, s1), recvlen, iphdrlen, ipdatalen); return; } igmp = (struct igmp *)(recv_buf + iphdrlen); group = igmp->igmp_group.s_addr; igmpdatalen = ipdatalen - IGMP_MINLEN; if (igmpdatalen < 0) { my_log(LOG_WARNING, 0, "received IP data field too short (%u bytes) for IGMP, from %s", ipdatalen, inetFmt(src, s1)); return; } my_log(LOG_NOTICE, 0, "RECV %s from %-15s to %s", igmpPacketKind(igmp->igmp_type, igmp->igmp_code), inetFmt(src, s1), inetFmt(dst, s2) ); switch (igmp->igmp_type) { case IGMP_V1_MEMBERSHIP_REPORT: case IGMP_V2_MEMBERSHIP_REPORT: acceptGroupReport(src, group, igmp->igmp_type); return; case IGMP_V2_LEAVE_GROUP: acceptLeaveMessage(src, group); return; case IGMP_MEMBERSHIP_QUERY: return; default: my_log(LOG_INFO, 0, "ignoring unknown IGMP message type %x from %s to %s", igmp->igmp_type, inetFmt(src, s1), inetFmt(dst, s2)); return; } } /* * Construct an IGMP message in the output packet buffer. The caller may * have already placed data in that buffer, of length 'datalen'. */ void buildIgmp(uint32_t src, uint32_t dst, int type, int code, uint32_t group, int datalen) { struct ip *ip; struct igmp *igmp; extern int curttl; ip = (struct ip *)send_buf; ip->ip_src.s_addr = src; ip->ip_dst.s_addr = dst; ip_set_len(ip, MIN_IP_HEADER_LEN + IGMP_MINLEN + datalen); if (IN_MULTICAST(ntohl(dst))) { ip->ip_ttl = curttl; } else { ip->ip_ttl = MAXTTL; } igmp = (struct igmp *)(send_buf + MIN_IP_HEADER_LEN); igmp->igmp_type = type; igmp->igmp_code = code; igmp->igmp_group.s_addr = group; igmp->igmp_cksum = 0; igmp->igmp_cksum = inetChksum((u_short *)igmp, IGMP_MINLEN + datalen); } /* * Call build_igmp() to build an IGMP message in the output packet buffer. * Then send the message from the interface with IP address 'src' to * destination 'dst'. */ void sendIgmp(uint32_t src, uint32_t dst, int type, int code, uint32_t group, int datalen) { struct sockaddr_in sdst; int setloop = 0, setigmpsource = 0; buildIgmp(src, dst, type, code, group, datalen); if (IN_MULTICAST(ntohl(dst))) { k_set_if(src); setigmpsource = 1; if (type != IGMP_DVMRP || dst == allhosts_group) { setloop = 1; k_set_loop(true); } } memset(&sdst, 0, sizeof(sdst)); sdst.sin_family = AF_INET; #ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN sdst.sin_len = sizeof(sdst); #endif sdst.sin_addr.s_addr = dst; if (sendto(MRouterFD, send_buf, MIN_IP_HEADER_LEN + IGMP_MINLEN + datalen, 0, (struct sockaddr *)&sdst, sizeof(sdst)) < 0) { if (errno == ENETDOWN) my_log(LOG_ERR, errno, "Sender VIF was down."); else my_log(LOG_INFO, errno, "sendto to %s on %s", inetFmt(dst, s1), inetFmt(src, s2)); } if(setigmpsource) { if (setloop) { k_set_loop(false); } // Restore original... k_set_if(INADDR_ANY); } my_log(LOG_DEBUG, 0, "SENT %s from %-15s to %s", igmpPacketKind(type, code), src == INADDR_ANY ? "INADDR_ANY" : inetFmt(src, s1), inetFmt(dst, s2)); }