summaryrefslogblamecommitdiffstats
path: root/src/proto/igmp.c
blob: 4d4df900a4853ab6fa9f6f15bf116a3d0a4b3bbf (plain) (tree)
1
2
3
4
5
6
7
8






                                             
                       


















































                                                                              

                                                                            




















































                                                                               
                                                  



                                  














































                                                                             
/*
 * 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;
			}
		}
	}
}

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();
	}
}