summaryrefslogtreecommitdiffstats
path: root/src/proto/igmp.c
blob: aad530f74bfe4e7d94bcde7adff26287f4493ed1 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
/*
 * 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();
	}
}