gcc is rather over-aggressive about optimising out static data structures
[people/asdlkf/gpxe.git] / src / proto / igmp.c
1 /*
2  * Eric Biederman wrote this code originally.
3  *
4  */
5
6 #include "ip.h"
7 #include "igmp.h"
8 #include "background.h"
9 #include "nic.h"
10 #include "etherboot.h"
11
12 static unsigned long last_igmpv1 = 0;
13 static struct igmptable_t igmptable[MAX_IGMP];
14
15 static long rfc1112_sleep_interval ( long base, int exp ) {
16         unsigned long divisor, tmo;
17
18         if ( exp > BACKOFF_LIMIT )
19                 exp = BACKOFF_LIMIT;
20         divisor = RAND_MAX / ( base << exp );
21         tmo = random() / divisor;
22         return tmo;
23 }
24
25 static void send_igmp_reports ( unsigned long now ) {
26         struct igmp_ip_t igmp;
27         int i;
28
29         for ( i = 0 ; i < MAX_IGMP ; i++ ) {
30                 if ( ! igmptable[i].time )
31                         continue;
32                 if ( now < igmptable[i].time )
33                         continue;
34
35                 igmp.router_alert[0] = 0x94;
36                 igmp.router_alert[1] = 0x04;
37                 igmp.router_alert[2] = 0;
38                 igmp.router_alert[3] = 0;
39                 build_ip_hdr ( igmptable[i].group.s_addr, 1, IP_IGMP,
40                                sizeof ( igmp.router_alert ),
41                                sizeof ( igmp ), &igmp );
42                 igmp.igmp.type = IGMPv2_REPORT;
43                 if ( last_igmpv1 && 
44                      ( now < last_igmpv1 + IGMPv1_ROUTER_PRESENT_TIMEOUT ) ) {
45                         igmp.igmp.type = IGMPv1_REPORT;
46                 }
47                 igmp.igmp.response_time = 0;
48                 igmp.igmp.chksum = 0;
49                 igmp.igmp.group.s_addr = igmptable[i].group.s_addr;
50                 igmp.igmp.chksum = ipchksum ( &igmp.igmp,
51                                               sizeof ( igmp.igmp ) );
52                 ip_transmit ( sizeof ( igmp ), &igmp );
53                 DBG ( "IGMP sent report to %@\n",
54                       igmp.igmp.group.s_addr );
55                 /* Don't send another igmp report until asked */
56                 igmptable[i].time = 0;
57         }
58 }
59
60 static void process_igmp ( unsigned long now, unsigned short ptype __unused,
61                            struct iphdr *ip ) {
62         struct igmp *igmp;
63         int i;
64         unsigned iplen;
65
66         if ( ( ! ip ) || ( ip->protocol != IP_IGMP ) ||
67              ( nic.packetlen < ( sizeof ( struct iphdr ) +
68                                  sizeof ( struct igmp ) ) ) ) {
69                 return;
70         }
71
72         iplen = ( ip->verhdrlen & 0xf ) * 4;
73         igmp = ( struct igmp * ) &nic.packet[ sizeof( struct iphdr ) ];
74         if ( ipchksum ( igmp, ntohs ( ip->len ) - iplen ) != 0 )
75                 return;
76
77         if ( ( igmp->type == IGMP_QUERY ) && 
78              ( ip->dest.s_addr == htonl ( GROUP_ALL_HOSTS ) ) ) {
79                 unsigned long interval = IGMP_INTERVAL;
80
81                 if ( igmp->response_time == 0 ) {
82                         last_igmpv1 = now;
83                 } else {
84                         interval = ( igmp->response_time * TICKS_PER_SEC ) /10;
85                 }
86                 
87                 DBG ( "IGMP received query for %@\n", igmp->group.s_addr );
88                 for ( i = 0 ; i < MAX_IGMP ; i++ ) {
89                         uint32_t group = igmptable[i].group.s_addr;
90                         if ( ( group == 0 ) ||
91                              ( group == igmp->group.s_addr ) ) {
92                                 unsigned long time;
93                                 time = currticks() +
94                                         rfc1112_sleep_interval ( interval, 0 );
95                                 if ( time < igmptable[i].time ) {
96                                         igmptable[i].time = time;
97                                 }
98                         }
99                 }
100         }
101         if ( ( ( igmp->type == IGMPv1_REPORT ) ||
102                ( igmp->type == IGMPv2_REPORT ) ) &&
103              ( ip->dest.s_addr == igmp->group.s_addr ) ) {
104                 DBG ( "IGMP received report for %@\n", igmp->group.s_addr);
105                 for ( i = 0 ; i < MAX_IGMP ; i++ ) {
106                         if ( ( igmptable[i].group.s_addr ==
107                                igmp->group.s_addr ) &&
108                              ( igmptable[i].time != 0 ) ) {
109                                 igmptable[i].time = 0;
110                         }
111                 }
112         }
113 }
114
115 struct background igmp_background __background = {
116         .send = send_igmp_reports,
117         .process = process_igmp,
118 };
119
120 void leave_group ( int slot ) {
121         /* Be very stupid and always send a leave group message if 
122          * I have subscribed.  Imperfect but it is standards
123          * compliant, easy and reliable to implement.
124          *
125          * The optimal group leave method is to only send leave when,
126          * we were the last host to respond to a query on this group,
127          * and igmpv1 compatibility is not enabled.
128          */
129         if ( igmptable[slot].group.s_addr ) {
130                 struct igmp_ip_t igmp;
131
132                 igmp.router_alert[0] = 0x94;
133                 igmp.router_alert[1] = 0x04;
134                 igmp.router_alert[2] = 0;
135                 igmp.router_alert[3] = 0;
136                 build_ip_hdr ( htonl ( GROUP_ALL_HOSTS ), 1, IP_IGMP,
137                                sizeof ( igmp.router_alert ), sizeof ( igmp ),
138                                &igmp);
139                 igmp.igmp.type = IGMP_LEAVE;
140                 igmp.igmp.response_time = 0;
141                 igmp.igmp.chksum = 0;
142                 igmp.igmp.group.s_addr = igmptable[slot].group.s_addr;
143                 igmp.igmp.chksum = ipchksum ( &igmp.igmp, sizeof ( igmp ) );
144                 ip_transmit ( sizeof ( igmp ), &igmp );
145                 DBG ( "IGMP left group %@\n", igmp.igmp.group.s_addr );
146         }
147         memset ( &igmptable[slot], 0, sizeof ( igmptable[0] ) );
148 }
149
150 void join_group ( int slot, unsigned long group ) {
151         /* I have already joined */
152         if ( igmptable[slot].group.s_addr == group )
153                 return;
154         if ( igmptable[slot].group.s_addr ) {
155                 leave_group ( slot );
156         }
157         /* Only join a group if we are given a multicast ip, this way
158          * code can be given a non-multicast (broadcast or unicast ip)
159          * and still work... 
160          */
161         if ( ( group & htonl ( MULTICAST_MASK ) ) ==
162              htonl ( MULTICAST_NETWORK ) ) {
163                 igmptable[slot].group.s_addr = group;
164                 igmptable[slot].time = currticks();
165         }
166 }