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