| /* |
| * Eric Biederman wrote this code originally. |
| * |
| */ |
| |
| #include "ip.h" |
| #include "igmp.h" |
| #include "background.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 ( unsigned long now, unsigned short ptype __unused, |
| struct iphdr *ip ) { |
| 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; |
| } |
| } |
| } |
| } |
| |
| static struct background igmp_background __background = { |
| .send = send_igmp_reports, |
| .process = process_igmp, |
| }; |
| |
| 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(); |
| } |
| } |