diff --git a/src/proto/igmp.c b/src/proto/igmp.c new file mode 100644 index 00000000..17dae336 --- /dev/null +++ b/src/proto/igmp.c @@ -0,0 +1,160 @@ +/* + * Eric Biederman wrote this code originally. + * + */ + +#include "ip.h" +#include "igmp.h" +#include "nic.h" +#include "etherboot.h" + +static unsigned long last_igmpv1 = 0; +static struct igmptable_t igmptable[MAX_IGMP]; + +static long rfc1112_sleep_interval ( long base, int exp ) { + unsigned long divisor, tmo; + + if ( exp > BACKOFF_LIMIT ) + exp = BACKOFF_LIMIT; + divisor = RAND_MAX / ( base << exp ); + tmo = random() / divisor; + return tmo; +} + +static void send_igmp_reports ( unsigned long now ) { + struct igmp_ip_t igmp; + int i; + + for ( i = 0 ; i < MAX_IGMP ; i++ ) { + if ( ! igmptable[i].time ) + continue; + if ( now < igmptable[i].time ) + continue; + + igmp.router_alert[0] = 0x94; + igmp.router_alert[1] = 0x04; + igmp.router_alert[2] = 0; + igmp.router_alert[3] = 0; + build_ip_hdr ( igmptable[i].group.s_addr, 1, IP_IGMP, + sizeof ( igmp.router_alert ), + sizeof ( igmp ), &igmp ); + igmp.igmp.type = IGMPv2_REPORT; + if ( last_igmpv1 && + ( now < last_igmpv1 + IGMPv1_ROUTER_PRESENT_TIMEOUT ) ) { + igmp.igmp.type = IGMPv1_REPORT; + } + igmp.igmp.response_time = 0; + igmp.igmp.chksum = 0; + igmp.igmp.group.s_addr = igmptable[i].group.s_addr; + igmp.igmp.chksum = ipchksum ( &igmp.igmp, + sizeof ( igmp.igmp ) ); + ip_transmit ( sizeof ( igmp ), &igmp ); + DBG ( "IGMP sent report to %@\n", + igmp.igmp.group.s_addr ); + /* Don't send another igmp report until asked */ + igmptable[i].time = 0; + } +} + +static void process_igmp ( struct iphdr *ip, unsigned long now ) { + struct igmp *igmp; + int i; + unsigned iplen; + + if ( ( ! ip ) || ( ip->protocol != IP_IGMP ) || + ( nic.packetlen < ( sizeof ( struct iphdr ) + + sizeof ( struct igmp ) ) ) ) { + return; + } + + iplen = ( ip->verhdrlen & 0xf ) * 4; + igmp = ( struct igmp * ) &nic.packet[ sizeof( struct iphdr ) ]; + if ( ipchksum ( igmp, ntohs ( ip->len ) - iplen ) != 0 ) + return; + + if ( ( igmp->type == IGMP_QUERY ) && + ( ip->dest.s_addr == htonl ( GROUP_ALL_HOSTS ) ) ) { + unsigned long interval = IGMP_INTERVAL; + + if ( igmp->response_time == 0 ) { + last_igmpv1 = now; + } else { + interval = ( igmp->response_time * TICKS_PER_SEC ) /10; + } + + DBG ( "IGMP received query for %@\n", igmp->group.s_addr ); + for ( i = 0 ; i < MAX_IGMP ; i++ ) { + uint32_t group = igmptable[i].group.s_addr; + if ( ( group == 0 ) || + ( group == igmp->group.s_addr ) ) { + unsigned long time; + time = currticks() + + rfc1112_sleep_interval ( interval, 0 ); + if ( time < igmptable[i].time ) { + igmptable[i].time = time; + } + } + } + } + if ( ( ( igmp->type == IGMPv1_REPORT ) || + ( igmp->type == IGMPv2_REPORT ) ) && + ( ip->dest.s_addr == igmp->group.s_addr ) ) { + DBG ( "IGMP received report for %@\n", igmp->group.s_addr); + for ( i = 0 ; i < MAX_IGMP ; i++ ) { + if ( ( igmptable[i].group.s_addr == + igmp->group.s_addr ) && + ( igmptable[i].time != 0 ) ) { + igmptable[i].time = 0; + } + } + } +} + +void leave_group ( int slot ) { + /* Be very stupid and always send a leave group message if + * I have subscribed. Imperfect but it is standards + * compliant, easy and reliable to implement. + * + * The optimal group leave method is to only send leave when, + * we were the last host to respond to a query on this group, + * and igmpv1 compatibility is not enabled. + */ + if ( igmptable[slot].group.s_addr ) { + struct igmp_ip_t igmp; + + igmp.router_alert[0] = 0x94; + igmp.router_alert[1] = 0x04; + igmp.router_alert[2] = 0; + igmp.router_alert[3] = 0; + build_ip_hdr ( htonl ( GROUP_ALL_HOSTS ), 1, IP_IGMP, + sizeof ( igmp.router_alert ), sizeof ( igmp ), + &igmp); + igmp.igmp.type = IGMP_LEAVE; + igmp.igmp.response_time = 0; + igmp.igmp.chksum = 0; + igmp.igmp.group.s_addr = igmptable[slot].group.s_addr; + igmp.igmp.chksum = ipchksum ( &igmp.igmp, sizeof ( igmp ) ); + ip_transmit ( sizeof ( igmp ), &igmp ); + DBG ( "IGMP left group %@\n", igmp.igmp.group.s_addr ); + } + memset ( &igmptable[slot], 0, sizeof ( igmptable[0] ) ); +} + +void join_group ( int slot, unsigned long group ) { + /* I have already joined */ + if ( igmptable[slot].group.s_addr == group ) + return; + if ( igmptable[slot].group.s_addr ) { + leave_group ( slot ); + } + /* Only join a group if we are given a multicast ip, this way + * code can be given a non-multicast (broadcast or unicast ip) + * and still work... + */ + if ( ( group & htonl ( MULTICAST_MASK ) ) == + htonl ( MULTICAST_NETWORK ) ) { + igmptable[slot].group.s_addr = group; + igmptable[slot].time = currticks(); + } +} +