1 |
|
|
/* $OpenBSD: igmp.c,v 1.4 2015/12/07 19:14:49 mmcc Exp $ */ |
2 |
|
|
|
3 |
|
|
/* |
4 |
|
|
* Copyright (c) 2005, 2006 Esben Norby <norby@openbsd.org> |
5 |
|
|
* |
6 |
|
|
* Permission to use, copy, modify, and distribute this software for any |
7 |
|
|
* purpose with or without fee is hereby granted, provided that the above |
8 |
|
|
* copyright notice and this permission notice appear in all copies. |
9 |
|
|
* |
10 |
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
11 |
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
12 |
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
13 |
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
14 |
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
15 |
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
16 |
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
17 |
|
|
*/ |
18 |
|
|
|
19 |
|
|
#include <sys/types.h> |
20 |
|
|
#include <sys/socket.h> |
21 |
|
|
#include <netinet/in.h> |
22 |
|
|
#include <arpa/inet.h> |
23 |
|
|
#include <sys/time.h> |
24 |
|
|
#include <stdlib.h> |
25 |
|
|
#include <string.h> |
26 |
|
|
#include <event.h> |
27 |
|
|
|
28 |
|
|
#include "igmp.h" |
29 |
|
|
#include "dvmrpd.h" |
30 |
|
|
#include "dvmrp.h" |
31 |
|
|
#include "log.h" |
32 |
|
|
#include "dvmrpe.h" |
33 |
|
|
|
34 |
|
|
int igmp_chksum(struct igmp_hdr *); |
35 |
|
|
|
36 |
|
|
/* IGMP packet handling */ |
37 |
|
|
int |
38 |
|
|
send_igmp_query(struct iface *iface, struct group *group) |
39 |
|
|
{ |
40 |
|
|
struct igmp_hdr igmp_hdr; |
41 |
|
|
struct sockaddr_in dst; |
42 |
|
|
struct ibuf *buf; |
43 |
|
|
int ret = 0; |
44 |
|
|
|
45 |
|
|
log_debug("send_igmp_query: interface %s", iface->name); |
46 |
|
|
|
47 |
|
|
if (iface->passive) |
48 |
|
|
return (0); |
49 |
|
|
|
50 |
|
|
if ((buf = ibuf_open(iface->mtu - sizeof(struct ip))) == NULL) |
51 |
|
|
fatal("send_igmp_query"); |
52 |
|
|
|
53 |
|
|
/* IGMP header */ |
54 |
|
|
memset(&igmp_hdr, 0, sizeof(igmp_hdr)); |
55 |
|
|
igmp_hdr.type = PKT_TYPE_MEMBER_QUERY; |
56 |
|
|
|
57 |
|
|
if (group == NULL) { |
58 |
|
|
/* general query - version is configured */ |
59 |
|
|
igmp_hdr.grp_addr = 0; |
60 |
|
|
|
61 |
|
|
switch (iface->igmp_version) { |
62 |
|
|
case 1: |
63 |
|
|
break; |
64 |
|
|
case 2: |
65 |
|
|
igmp_hdr.max_resp_time = iface->query_resp_interval; |
66 |
|
|
break; |
67 |
|
|
default: |
68 |
|
|
fatal("send_igmp_query: invalid igmp version"); |
69 |
|
|
} |
70 |
|
|
} else { |
71 |
|
|
/* group specific query - only version 2 */ |
72 |
|
|
igmp_hdr.grp_addr = group->addr.s_addr; |
73 |
|
|
igmp_hdr.max_resp_time = iface->last_member_query_interval; |
74 |
|
|
} |
75 |
|
|
|
76 |
|
|
/* update chksum */ |
77 |
|
|
igmp_hdr.chksum = in_cksum(&igmp_hdr, sizeof(igmp_hdr)); |
78 |
|
|
|
79 |
|
|
ibuf_add(buf, &igmp_hdr, sizeof(igmp_hdr)); |
80 |
|
|
|
81 |
|
|
/* set destination address */ |
82 |
|
|
dst.sin_family = AF_INET; |
83 |
|
|
dst.sin_len = sizeof(struct sockaddr_in); |
84 |
|
|
inet_aton(AllSystems, &dst.sin_addr); |
85 |
|
|
|
86 |
|
|
ret = send_packet(iface, buf->buf, buf->wpos, &dst); |
87 |
|
|
ibuf_free(buf); |
88 |
|
|
return (ret); |
89 |
|
|
} |
90 |
|
|
|
91 |
|
|
void |
92 |
|
|
recv_igmp_query(struct iface *iface, struct in_addr src, char *buf, |
93 |
|
|
u_int16_t len) |
94 |
|
|
{ |
95 |
|
|
struct igmp_hdr igmp_hdr; |
96 |
|
|
struct group *group; |
97 |
|
|
|
98 |
|
|
log_debug("recv_igmp_query: interface %s", iface->name); |
99 |
|
|
|
100 |
|
|
if (len < sizeof(igmp_hdr)) { |
101 |
|
|
log_debug("recv_igmp_query: invalid IGMP report, interface %s", |
102 |
|
|
iface->name); |
103 |
|
|
return; |
104 |
|
|
} |
105 |
|
|
|
106 |
|
|
memcpy(&igmp_hdr, buf, sizeof(igmp_hdr)); |
107 |
|
|
iface->recv_query_resp_interval = igmp_hdr.max_resp_time; |
108 |
|
|
|
109 |
|
|
/* verify chksum */ |
110 |
|
|
if (igmp_chksum(&igmp_hdr) == -1) { |
111 |
|
|
log_debug("recv_igmp_query: invalid chksum, interface %s", |
112 |
|
|
iface->name); |
113 |
|
|
return; |
114 |
|
|
} |
115 |
|
|
|
116 |
|
|
if (src.s_addr < iface->addr.s_addr && igmp_hdr.grp_addr == 0) { |
117 |
|
|
/* we received a general query and we lost the election */ |
118 |
|
|
if_fsm(iface, IF_EVT_QRECVD); |
119 |
|
|
/* remember who is querier */ |
120 |
|
|
iface->querier = src; |
121 |
|
|
return; |
122 |
|
|
} |
123 |
|
|
|
124 |
|
|
if (iface->state == IF_STA_NONQUERIER && igmp_hdr.grp_addr != 0) { |
125 |
|
|
/* validate group id */ |
126 |
|
|
if (!IN_MULTICAST(ntohl(igmp_hdr.grp_addr))) { |
127 |
|
|
log_debug("recv_igmp_query: invalid group, " |
128 |
|
|
"interface %s", iface->name); |
129 |
|
|
return; |
130 |
|
|
} |
131 |
|
|
|
132 |
|
|
if ((group = group_list_add(iface, igmp_hdr.grp_addr)) |
133 |
|
|
!= NULL) |
134 |
|
|
group_fsm(group, GRP_EVT_QUERY_RCVD); |
135 |
|
|
} |
136 |
|
|
} |
137 |
|
|
|
138 |
|
|
void |
139 |
|
|
recv_igmp_report(struct iface *iface, struct in_addr src, char *buf, |
140 |
|
|
u_int16_t len, u_int8_t type) |
141 |
|
|
{ |
142 |
|
|
struct igmp_hdr igmp_hdr; |
143 |
|
|
struct group *group; |
144 |
|
|
|
145 |
|
|
log_debug("recv_igmp_report: interface %s", iface->name); |
146 |
|
|
|
147 |
|
|
if (len < sizeof(igmp_hdr)) { |
148 |
|
|
log_debug("recv_igmp_report: invalid IGMP report, interface %s", |
149 |
|
|
iface->name); |
150 |
|
|
return; |
151 |
|
|
} |
152 |
|
|
|
153 |
|
|
memcpy(&igmp_hdr, buf, sizeof(igmp_hdr)); |
154 |
|
|
|
155 |
|
|
/* verify chksum */ |
156 |
|
|
if (igmp_chksum(&igmp_hdr) == -1) { |
157 |
|
|
log_debug("recv_igmp_report: invalid chksum, interface %s", |
158 |
|
|
iface->name); |
159 |
|
|
return; |
160 |
|
|
} |
161 |
|
|
|
162 |
|
|
/* validate group id */ |
163 |
|
|
if (!IN_MULTICAST(ntohl(igmp_hdr.grp_addr))) { |
164 |
|
|
log_debug("recv_igmp_report: invalid group, interface %s", |
165 |
|
|
iface->name); |
166 |
|
|
return; |
167 |
|
|
} |
168 |
|
|
|
169 |
|
|
if ((group = group_list_add(iface, igmp_hdr.grp_addr)) == NULL) |
170 |
|
|
return; |
171 |
|
|
|
172 |
|
|
if (iface->state == IF_STA_QUERIER) { |
173 |
|
|
/* querier */ |
174 |
|
|
switch (type) { |
175 |
|
|
case PKT_TYPE_MEMBER_REPORTv1: |
176 |
|
|
group_fsm(group, GRP_EVT_V1_REPORT_RCVD); |
177 |
|
|
break; |
178 |
|
|
case PKT_TYPE_MEMBER_REPORTv2: |
179 |
|
|
group_fsm(group, GRP_EVT_V2_REPORT_RCVD); |
180 |
|
|
break; |
181 |
|
|
default: |
182 |
|
|
fatalx("recv_igmp_report: unknown IGMP report type"); |
183 |
|
|
} |
184 |
|
|
} else { |
185 |
|
|
/* non querier */ |
186 |
|
|
group_fsm(group, GRP_EVT_REPORT_RCVD); |
187 |
|
|
} |
188 |
|
|
} |
189 |
|
|
|
190 |
|
|
void |
191 |
|
|
recv_igmp_leave(struct iface *iface, struct in_addr src, char *buf, |
192 |
|
|
u_int16_t len) |
193 |
|
|
{ |
194 |
|
|
struct igmp_hdr igmp_hdr; |
195 |
|
|
struct group *group; |
196 |
|
|
|
197 |
|
|
log_debug("recv_igmp_leave: interface %s", iface->name); |
198 |
|
|
|
199 |
|
|
if (iface->state != IF_STA_QUERIER) |
200 |
|
|
return; |
201 |
|
|
|
202 |
|
|
if (len < sizeof(igmp_hdr)) { |
203 |
|
|
log_debug("recv_igmp_leave: invalid IGMP leave, interface %s", |
204 |
|
|
iface->name); |
205 |
|
|
return; |
206 |
|
|
} |
207 |
|
|
|
208 |
|
|
memcpy(&igmp_hdr, buf, sizeof(igmp_hdr)); |
209 |
|
|
|
210 |
|
|
/* verify chksum */ |
211 |
|
|
if (igmp_chksum(&igmp_hdr) == -1) { |
212 |
|
|
log_debug("recv_igmp_leave: invalid chksum, interface %s", |
213 |
|
|
iface->name); |
214 |
|
|
return; |
215 |
|
|
} |
216 |
|
|
|
217 |
|
|
/* validate group id */ |
218 |
|
|
if (!IN_MULTICAST(ntohl(igmp_hdr.grp_addr))) { |
219 |
|
|
log_debug("recv_igmp_leave: invalid group, interface %s", |
220 |
|
|
iface->name); |
221 |
|
|
return; |
222 |
|
|
} |
223 |
|
|
|
224 |
|
|
if ((group = group_list_find(iface, igmp_hdr.grp_addr)) != NULL) { |
225 |
|
|
group_fsm(group, GRP_EVT_LEAVE_RCVD); |
226 |
|
|
} |
227 |
|
|
} |
228 |
|
|
|
229 |
|
|
int |
230 |
|
|
igmp_chksum(struct igmp_hdr *igmp_hdr) |
231 |
|
|
{ |
232 |
|
|
u_int16_t chksum; |
233 |
|
|
|
234 |
|
|
chksum = igmp_hdr->chksum; |
235 |
|
|
igmp_hdr->chksum = 0; |
236 |
|
|
|
237 |
|
|
if (chksum != in_cksum(igmp_hdr, sizeof(*igmp_hdr))) |
238 |
|
|
return (-1); |
239 |
|
|
|
240 |
|
|
return (0); |
241 |
|
|
} |