summaryrefslogtreecommitdiffstats
path: root/src/proto
diff options
context:
space:
mode:
authorMichael Brown2005-06-01 23:18:31 +0200
committerMichael Brown2005-06-01 23:18:31 +0200
commite0cf14421825e07ca331015eed18ec622864443c (patch)
treeae7f3e6194f9334101fc0e692317757e6e4a1deb /src/proto
parentDon't try to fetch another packet once we've reached EOF. (diff)
downloadipxe-e0cf14421825e07ca331015eed18ec622864443c.tar.gz
ipxe-e0cf14421825e07ca331015eed18ec622864443c.tar.xz
ipxe-e0cf14421825e07ca331015eed18ec622864443c.zip
IGMP functions separated out from nic.c
Diffstat (limited to 'src/proto')
-rw-r--r--src/proto/igmp.c160
1 files changed, 160 insertions, 0 deletions
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();
+ }
+}
+