Line data Source code
1 : /* $Id: igmp_proto.c 346474 2009-11-14 10:18:58Z ssiano $
2 : *
3 : * igmp_proto.c - IGMP protocol encode/decode routines
4 : *
5 : * Dave Katz, March 2008
6 : *
7 : * Copyright (c) 2008, Juniper Networks, Inc.
8 : * All rights reserved.
9 : */
10 : #include "gmpx_basic_types.h"
11 : #include "gmp.h"
12 : #include "gmpx_environment.h"
13 : #include "gmp_externs.h"
14 : #include "gmp_private.h"
15 : #include "gmpp_private.h"
16 : #include "gmp_trace.h"
17 : #include "igmp_protocol.h"
18 :
19 : /*
20 : * Destination addresses
21 : */
22 : static uint8_t igmp_all_hosts[IPV4_ADDR_LEN] = {224, 0, 0, 1};
23 : static uint8_t igmp_all_routers[IPV4_ADDR_LEN] = {224, 0, 0, 2};
24 : static uint8_t igmp_all_v3_routers[IPV4_ADDR_LEN] = {224, 0, 0, 22};
25 :
26 :
27 :
28 : /*
29 : * igmp_packet_type_string
30 : *
31 : * Returns the IGMP packet type string given the packet type.
32 : */
33 : static const char *
34 0 : igmp_packet_type_string (igmp_hdr *hdr, uint32_t len)
35 : {
36 0 : switch (hdr->igmp_hdr_type) {
37 0 : case IGMP_TYPE_QUERY:
38 0 : if (len > sizeof(igmp_v1v2_pkt))
39 0 : return "V3 Query";
40 0 : if (hdr->igmp_hdr_maxresp)
41 0 : return "V2 Query";
42 0 : return "V1 Query";
43 :
44 0 : case IGMP_TYPE_V1_REPORT:
45 0 : return "V1 Report";
46 :
47 0 : case IGMP_TYPE_V2_REPORT:
48 0 : return "V2 Report";
49 :
50 0 : case IGMP_TYPE_V2_LEAVE:
51 0 : return "V2 Leave";
52 :
53 0 : case IGMP_TYPE_V3_REPORT:
54 0 : return "V3 Report";
55 :
56 0 : default:
57 0 : return "Invalid";
58 : }
59 : }
60 :
61 :
62 : /*
63 : * igmp_decode_fixfloat
64 : *
65 : * Decodes a fix/float field (Max Resp or QQIC). Returns the value in
66 : * native units.
67 : */
68 : static uint32_t
69 0 : igmp_decode_fixfloat (uint8_t value)
70 : {
71 : uint32_t mant, exp, result;
72 :
73 : /* If the flag is set, decode the value as floating point. */
74 :
75 0 : if (value & IGMP_FIXFLOAT_FLAG) {
76 0 : mant = (value & IGMP_FLOAT_MANT_MASK) >> IGMP_FLOAT_MANT_SHIFT;
77 0 : exp = (value & IGMP_FLOAT_EXP_MASK) >> IGMP_FLOAT_EXP_SHIFT;
78 0 : result = (mant | IGMP_FLOAT_MANT_HIGHBIT) <<
79 0 : (exp + IGMP_FLOAT_EXP_OFFSET);
80 : } else {
81 0 : result = value;
82 : }
83 :
84 0 : return result;
85 : }
86 :
87 :
88 : /*
89 : * igmp_generic_version
90 : *
91 : * Determine the generic version number of an IGMP packet
92 : *
93 : * Returns the version number, or GMP_VERSION_INVALID if an error occurs.
94 : *
95 : * Also returns the generic message type.
96 : */
97 : static gmp_version
98 136 : igmp_generic_version (igmp_packet *pkt, uint32_t pkt_len,
99 : gmp_message_type *msg_type)
100 : {
101 : gmp_version version;
102 :
103 : /* First, branch by packet type. */
104 :
105 136 : *msg_type = GMP_REPORT_PACKET; /* Reasonable default. */
106 136 : switch (pkt->igmp_pkt_naked.igmp_naked_header_hdr.igmp_hdr_type) {
107 :
108 : /* Do the easy ones first. */
109 :
110 1 : case IGMP_TYPE_V1_REPORT:
111 1 : version = GMP_VERSION_BASIC;
112 1 : break;
113 :
114 79 : case IGMP_TYPE_V2_REPORT:
115 : case IGMP_TYPE_V2_LEAVE:
116 79 : version = GMP_VERSION_LEAVES;
117 79 : break;
118 :
119 56 : case IGMP_TYPE_V3_REPORT:
120 56 : version = GMP_VERSION_SOURCES;
121 56 : break;
122 :
123 0 : case IGMP_TYPE_QUERY:
124 :
125 : /*
126 : * Now the queries. Queries are distinguished by length and
127 : * the Max Resp field.
128 : */
129 0 : *msg_type = GMP_QUERY_PACKET;
130 0 : if (pkt_len > sizeof(igmp_v1v2_pkt)) /* Long packet */
131 0 : version = GMP_VERSION_SOURCES;
132 0 : else if (pkt->igmp_pkt_v1v2.igmp_v1v2_pkt_hdr.igmp_hdr_maxresp != 0)
133 0 : version = GMP_VERSION_LEAVES;
134 : else
135 0 : version = GMP_VERSION_BASIC;
136 0 : break;
137 :
138 0 : default:
139 0 : version = GMP_VERSION_INVALID;
140 0 : break;
141 : }
142 :
143 136 : return version;
144 : }
145 :
146 :
147 : /*
148 : * igmp_max_resp_value
149 : *
150 : * Returns the value of the max resp field, in standard units (100 msec.)
151 : * If the value is zero, it returns the default value (for IGMP v1.)
152 : */
153 0 : static uint32_t igmp_max_resp_value (igmp_packet *pkt, gmp_version version)
154 : {
155 : uint32_t max_resp_field, value;
156 :
157 0 : max_resp_field =
158 0 : pkt->igmp_pkt_naked.igmp_naked_header_hdr.igmp_hdr_maxresp;
159 :
160 : /* Switch by IGMP version. */
161 :
162 0 : switch (version) {
163 0 : case GMP_VERSION_BASIC:
164 0 : value = IGMP_MAX_RESP_DEFAULT;
165 0 : break;
166 :
167 0 : case GMP_VERSION_LEAVES:
168 0 : value = max_resp_field;
169 0 : break;
170 :
171 0 : case GMP_VERSION_SOURCES:
172 0 : value = igmp_decode_fixfloat(max_resp_field);
173 0 : break;
174 :
175 0 : default:
176 0 : value = 0; /* Invalid */
177 : }
178 :
179 0 : return value;
180 : }
181 :
182 :
183 : /*
184 : * gmp_igmp_trace_bad_pkt
185 : *
186 : * Trace a bad IGMP packet
187 : */
188 : void
189 0 : gmp_igmp_trace_bad_pkt(uint32_t len, const uint8_t *addr, gmpx_intf_id intf_id,
190 : void *trace_context, uint32_t trace_flags)
191 : {
192 : /* Bail if not tracing bad packets. */
193 :
194 0 : if (!gmp_trace_set(trace_flags, GMP_TRACE_PACKET_BAD))
195 0 : return;
196 :
197 0 : gmpx_trace(trace_context, "RCV bad IGMP packet, len %u, from %a intf %i",
198 : len, addr, intf_id);
199 : }
200 :
201 :
202 : /*
203 : * gmp_igmp_trace_pkt
204 : *
205 : * Trace an IGMP packet
206 : */
207 : void
208 1055 : gmp_igmp_trace_pkt (void *packet, uint32_t len, const uint8_t *addr,
209 : gmpx_intf_id intf_id, boolean receive,
210 : void *trace_context, uint32_t trace_flags)
211 : {
212 : const char *direction;
213 : const char *op;
214 : igmp_hdr *hdr;
215 : igmp_v1v2_pkt *v1v2_pkt;
216 : igmp_v3_query *v3_query;
217 : igmp_v3_report *v3_report;
218 : igmp_v3_rpt_rcrd *rpt_rcrd;
219 : gmp_version version;
220 : gmp_message_type msg_type;
221 : uint8_t *byte_ptr;
222 : uint32_t source_count;
223 : uint32_t record_count;
224 : boolean detail;
225 : igmp_packet *pkt;
226 :
227 1055 : pkt = packet;
228 :
229 : /* See if this packet should be traced at all. */
230 :
231 1055 : if (!gmp_trace_set(trace_flags, GMP_TRACE_PACKET))
232 1055 : return;
233 :
234 0 : hdr = &pkt->igmp_pkt_naked.igmp_naked_header_hdr;
235 0 : detail = FALSE;
236 0 : switch (hdr->igmp_hdr_type) {
237 0 : case IGMP_TYPE_QUERY:
238 0 : if (!gmp_trace_set(trace_flags, GMP_TRACE_QUERY))
239 0 : return;
240 0 : if (gmp_trace_set(trace_flags, GMP_TRACE_QUERY_DETAIL))
241 0 : detail = TRUE;
242 0 : break;
243 :
244 0 : case IGMP_TYPE_V2_LEAVE:
245 0 : if (!gmp_trace_set(trace_flags, GMP_TRACE_LEAVE))
246 0 : return;
247 0 : if (gmp_trace_set(trace_flags, GMP_TRACE_LEAVE_DETAIL))
248 0 : detail = TRUE;
249 0 : break;
250 :
251 0 : case IGMP_TYPE_V1_REPORT:
252 : case IGMP_TYPE_V2_REPORT:
253 : case IGMP_TYPE_V3_REPORT:
254 0 : if (!gmp_trace_set(trace_flags, GMP_TRACE_REPORT))
255 0 : return;
256 0 : if (gmp_trace_set(trace_flags, GMP_TRACE_REPORT_DETAIL))
257 0 : detail = TRUE;
258 0 : break;
259 :
260 0 : default:
261 0 : if (!gmp_trace_set(trace_flags, GMP_TRACE_PACKET_BAD))
262 0 : return;
263 0 : break;
264 : }
265 :
266 : /*
267 : * If we've gotten this far, we're doing basic tracing of the packet.
268 : * Do so.
269 : */
270 0 : v1v2_pkt = &pkt->igmp_pkt_v1v2;
271 0 : v3_query = &pkt->igmp_pkt_v3_query;
272 0 : v3_report = &pkt->igmp_pkt_v3_rpt;
273 0 : version = igmp_generic_version(pkt, len, &msg_type);
274 :
275 0 : if (receive) {
276 0 : direction = "from";
277 0 : op = "RCV";
278 : } else {
279 0 : direction = "to";
280 0 : op = "XMT";
281 : }
282 0 : gmpx_trace(trace_context, "%s IGMP %s len %u %s %a intf %i", op,
283 : igmp_packet_type_string(hdr, len), len, direction, addr,
284 : intf_id);
285 :
286 : /* Bail if no detailed tracing going on. */
287 :
288 0 : if (!detail)
289 0 : return;
290 :
291 : /* Do any detailed tracing based on packet type. */
292 :
293 0 : switch (hdr->igmp_hdr_type) {
294 0 : case IGMP_TYPE_QUERY:
295 0 : gmpx_trace(trace_context, " Max_resp 0x%x (%u), group %a",
296 0 : hdr->igmp_hdr_maxresp, igmp_max_resp_value(pkt, version),
297 0 : v1v2_pkt->igmp_v1v2_pkt_group);
298 0 : if (version == GMP_VERSION_SOURCES) {
299 0 : source_count = get_short(&v3_query->igmp_v3_query_num_srcs);
300 0 : gmpx_trace(trace_context,
301 : " S %u, QRV %u, QQIC 0x%x (%u), sources %u",
302 0 : ((v3_query->igmp_v3_query_s_qrv &
303 : IGMP_SUPP_RTR_PROC_MASK) != 0),
304 0 : v3_query->igmp_v3_query_s_qrv & IGMP_QRV_MASK,
305 0 : v3_query->igmp_v3_query_qqic,
306 0 : igmp_decode_fixfloat(v3_query->igmp_v3_query_qqic),
307 : source_count);
308 0 : byte_ptr = v3_query->igmp_v3_query_source;
309 0 : while (source_count--) {
310 0 : gmpx_trace(trace_context, " Source %a", byte_ptr);
311 0 : byte_ptr += IPV4_ADDR_LEN;
312 : }
313 : }
314 0 : break;
315 :
316 0 : case IGMP_TYPE_V1_REPORT:
317 : case IGMP_TYPE_V2_REPORT:
318 : case IGMP_TYPE_V2_LEAVE:
319 0 : gmpx_trace(trace_context, " Group %a", v1v2_pkt->igmp_v1v2_pkt_group);
320 0 : break;
321 :
322 0 : case IGMP_TYPE_V3_REPORT:
323 0 : record_count = get_short(&v3_report->igmp_v3_report_num_rcrds);
324 0 : gmpx_trace(trace_context, " Records %u", record_count);
325 :
326 0 : rpt_rcrd = v3_report->igmp_v3_report_rcrd;
327 0 : while (record_count--) {
328 0 : source_count = get_short(&rpt_rcrd->igmp_v3_rpt_num_srcs);
329 0 : gmpx_trace(trace_context,
330 : " Group %a, type %s, aux_len %u, sources %u",
331 0 : rpt_rcrd->igmp_v3_rpt_group,
332 0 : gmp_report_type_string(rpt_rcrd->igmp_v3_rpt_rec_type),
333 0 : rpt_rcrd->igmp_v3_rpt_aux_len, source_count);
334 0 : byte_ptr = get_igmp_v3_rpt_source(rpt_rcrd);
335 0 : while (source_count--) {
336 0 : gmpx_trace(trace_context, " Source %a", byte_ptr);
337 0 : byte_ptr += IPV4_ADDR_LEN;
338 : }
339 0 : byte_ptr += rpt_rcrd->igmp_v3_rpt_aux_len;
340 0 : rpt_rcrd = (igmp_v3_rpt_rcrd *) byte_ptr;
341 : }
342 0 : break;
343 :
344 : }
345 : }
346 :
347 :
348 : /*
349 : * igmp_encode_fixfloat
350 : *
351 : * Encodes a fix/float field (Max Resp or QQIC), given the value in native
352 : * units. Returns the encoded value, or zero if the value is too large
353 : * to encode.
354 : *
355 : * The encoded value will be less than or equal to the provided value (since
356 : * we lose granularity in the encoding.)
357 : */
358 : static uint8_t
359 1700 : igmp_encode_fixfloat (uint32_t value)
360 : {
361 : uint32_t mant, exp;
362 :
363 : /* If the value is small enough, just use it. */
364 :
365 1700 : if (value < IGMP_FIXFLOAT_FLAG)
366 1700 : return (uint8_t) value;
367 :
368 : /* If the value is too big, bail. */
369 :
370 0 : if (value > IGMP_MAX_FLOAT_ENCODABLE)
371 0 : return 0;
372 :
373 : /* Big enough to encode. Do the job. */
374 :
375 0 : mant = value >> IGMP_FLOAT_EXP_OFFSET;
376 0 : for (exp = 0; exp <= IGMP_FLOAT_MAX_EXP; exp++) {
377 :
378 : /* Bail if the mantissa is small enough now. */
379 :
380 0 : if (mant <= IGMP_FLOAT_MAX_MANT)
381 0 : break;
382 :
383 : /* Shift the mantissa down a bit and try again. */
384 :
385 0 : mant >>= 1;
386 : }
387 :
388 : /* Got it. Form the value and return it. */
389 :
390 0 : return (IGMP_FIXFLOAT_FLAG | (exp << IGMP_FLOAT_EXP_SHIFT) |
391 0 : (mant & IGMP_FLOAT_MANT_MASK));
392 : }
393 :
394 :
395 : /*
396 : * igmp_format_v1_packet
397 : *
398 : * Format an IGMP V1 packet.
399 : *
400 : * Returns the formatted packet length, or 0 if nothing to send.
401 : */
402 : static uint32_t
403 6 : igmp_format_v1_packet (gmp_role role, gmp_packet *gen_packet,
404 : igmp_packet *packet, uint32_t packet_len)
405 : {
406 : igmp_v1v2_pkt *v1v2_pkt;
407 : igmp_hdr *pkt_hdr;
408 : gmp_query_packet *query_pkt;
409 : task_thread *thread_ptr;
410 : gmp_report_group_record *group_record;
411 : gmp_report_packet *report_pkt;
412 : uint32_t formatted_len;
413 :
414 : /* Version 1 packet. Pretty straightforward. */
415 :
416 6 : gmpx_assert(packet_len >= sizeof(igmp_v1v2_pkt));
417 6 : v1v2_pkt = &packet->igmp_pkt_v1v2;
418 6 : pkt_hdr = &v1v2_pkt->igmp_v1v2_pkt_hdr;
419 6 : memset(pkt_hdr, 0, sizeof(igmp_hdr));
420 6 : formatted_len = sizeof(igmp_v1v2_pkt);
421 :
422 6 : switch (gen_packet->gmp_packet_type) {
423 6 : case GMP_QUERY_PACKET:
424 :
425 : /* Format a query packet. */
426 :
427 6 : pkt_hdr->igmp_hdr_type = IGMP_TYPE_QUERY;
428 6 : query_pkt = &gen_packet->gmp_packet_contents.gmp_packet_query;
429 6 : memset(v1v2_pkt->igmp_v1v2_pkt_group, 0, IPV4_ADDR_LEN);
430 6 : memmove(gen_packet->gmp_packet_dest_addr.gmp_addr, igmp_all_hosts, IPV4_ADDR_LEN);
431 : /*
432 : * Flush the source list. We may end up here with a GSS query due
433 : * to a race condition while changing versions.
434 : */
435 6 : gmp_flush_xmit_list(query_pkt->gmp_query_xmit_srcs);
436 6 : break;
437 :
438 0 : case GMP_REPORT_PACKET:
439 :
440 : /*
441 : * Format a report packet. Note that there may be multiple
442 : * group records on this (since V3 supports that). We just
443 : * pull off the first one and format it. We'll be called back
444 : * later to send more packets for other groups.
445 : */
446 0 : pkt_hdr->igmp_hdr_type = IGMP_TYPE_V1_REPORT;
447 0 : report_pkt = &gen_packet->gmp_packet_contents.gmp_packet_report;
448 0 : gmpx_assert(report_pkt->gmp_report_group_count >= 1);
449 0 : thread_ptr = thread_circular_top(&report_pkt->gmp_report_group_head);
450 0 : group_record = gmp_thread_to_report_group_record(thread_ptr);
451 0 : gmpx_assert(group_record);
452 :
453 : /*
454 : * Don't send anything if this is a leave-equivalent (IS_IN or TO_IN
455 : * with a null list, or a BLOCK.
456 : */
457 0 : if (group_record->gmp_rpt_type == GMP_RPT_BLOCK ||
458 0 : ((group_record->gmp_rpt_type == GMP_RPT_IS_IN ||
459 0 : group_record->gmp_rpt_type == GMP_RPT_TO_IN) &&
460 0 : gmp_addr_list_empty(group_record->gmp_rpt_xmit_srcs))) {
461 0 : formatted_len = 0;
462 : }
463 0 : memmove(v1v2_pkt->igmp_v1v2_pkt_group, group_record->gmp_rpt_group.gmp_addr, IPV4_ADDR_LEN);
464 0 : memmove(gen_packet->gmp_packet_dest_addr.gmp_addr, group_record->gmp_rpt_group.gmp_addr, IPV4_ADDR_LEN);
465 :
466 : /* Flush the source list and flag that we're done with this group. */
467 :
468 0 : gmp_flush_xmit_list(group_record->gmp_rpt_xmit_srcs);
469 0 : gmpp_group_done(role, GMP_PROTO_IGMP, group_record->gmp_rpt_group_id);
470 0 : break;
471 :
472 0 : default:
473 0 : gmpx_assert(FALSE);
474 : }
475 :
476 6 : return formatted_len;
477 : }
478 :
479 :
480 : /*
481 : * igmp_format_v2_packet
482 : *
483 : * Format an IGMP V2 packet.
484 : *
485 : * Returns the formatted packet length, or 0 if nothing to send.
486 : */
487 : static uint32_t
488 63 : igmp_format_v2_packet (gmp_role role, gmp_packet *gen_packet,
489 : igmp_packet *packet, uint32_t packet_len)
490 : {
491 : igmp_v1v2_pkt *v1v2_pkt;
492 : igmp_hdr *pkt_hdr;
493 : gmp_query_packet *query_pkt;
494 : gmp_report_packet *report_pkt;
495 : task_thread *thread_ptr;
496 : gmp_report_group_record *group_record;
497 : uint32_t max_resp;
498 : uint32_t formatted_len;
499 :
500 : /* Initialize and idiot-proof. */
501 :
502 63 : gmpx_assert(packet_len >= sizeof(igmp_v1v2_pkt));
503 63 : v1v2_pkt = &packet->igmp_pkt_v1v2;
504 63 : pkt_hdr = &v1v2_pkt->igmp_v1v2_pkt_hdr;
505 63 : memset(pkt_hdr, 0, sizeof(igmp_hdr));
506 63 : formatted_len = sizeof(igmp_v1v2_pkt);
507 :
508 63 : switch (gen_packet->gmp_packet_type) {
509 :
510 63 : case GMP_QUERY_PACKET:
511 :
512 : /* Format a query packet. */
513 :
514 63 : pkt_hdr->igmp_hdr_type = IGMP_TYPE_QUERY;
515 63 : query_pkt = &gen_packet->gmp_packet_contents.gmp_packet_query;
516 :
517 : /*
518 : * If this is a group query, copy in the group address. Otherwise,
519 : * zero it. Set the destination address accordingly.
520 : */
521 63 : if (query_pkt->gmp_query_group_query) {
522 27 : memmove(v1v2_pkt->igmp_v1v2_pkt_group, query_pkt->gmp_query_group.gmp_addr,
523 : IPV4_ADDR_LEN);
524 27 : memmove(gen_packet->gmp_packet_dest_addr.gmp_addr, query_pkt->gmp_query_group.gmp_addr,
525 : IPV4_ADDR_LEN);
526 : } else {
527 36 : memset(v1v2_pkt->igmp_v1v2_pkt_group, 0, IPV4_ADDR_LEN);
528 36 : memmove(gen_packet->gmp_packet_dest_addr.gmp_addr, igmp_all_hosts, IPV4_ADDR_LEN);
529 : }
530 :
531 : /* Store the max resp value. */
532 :
533 63 : max_resp = query_pkt->gmp_query_max_resp / IGMP_MAX_RESP_MSEC;
534 63 : if (max_resp > IGMP_V2_MAX_MAX_RESP)
535 0 : max_resp = IGMP_V2_MAX_MAX_RESP;
536 63 : pkt_hdr->igmp_hdr_maxresp = max_resp;
537 :
538 : /*
539 : * Flush the source list. We may end up here with a GSS query due
540 : * to a race condition while changing versions.
541 : */
542 63 : gmp_flush_xmit_list(query_pkt->gmp_query_xmit_srcs);
543 :
544 63 : if (query_pkt->gmp_query_group_query) {
545 27 : gmpp_group_done(role, GMP_PROTO_IGMP,
546 : query_pkt->gmp_query_group_id);
547 : }
548 :
549 63 : break;
550 :
551 0 : case GMP_REPORT_PACKET:
552 :
553 : /*
554 : * Format a report packet. Note that there may be multiple
555 : * group records on this (since V3 supports that). We just
556 : * pull off the first one and format it. We'll be called back
557 : * later to send more packets for other groups.
558 : */
559 0 : report_pkt = &gen_packet->gmp_packet_contents.gmp_packet_report;
560 0 : gmpx_assert(report_pkt->gmp_report_group_count >= 1);
561 0 : thread_ptr = thread_circular_top(&report_pkt->gmp_report_group_head);
562 0 : group_record = gmp_thread_to_report_group_record(thread_ptr);
563 0 : gmpx_assert(group_record);
564 :
565 : /*
566 : * If the record type is TO_IN or IS_IN with an empty address
567 : * list, this is a Leave. Otherwise, it is a report. Set the
568 : * destination address accordingly.
569 : */
570 0 : if ((group_record->gmp_rpt_type == GMP_RPT_TO_IN ||
571 0 : group_record->gmp_rpt_type == GMP_RPT_IS_IN) &&
572 0 : gmp_addr_list_empty(group_record->gmp_rpt_xmit_srcs)) {
573 0 : pkt_hdr->igmp_hdr_type = IGMP_TYPE_V2_LEAVE;
574 0 : memmove(gen_packet->gmp_packet_dest_addr.gmp_addr, igmp_all_routers, IPV4_ADDR_LEN);
575 : } else {
576 0 : pkt_hdr->igmp_hdr_type = IGMP_TYPE_V2_REPORT;
577 0 : memmove(gen_packet->gmp_packet_dest_addr.gmp_addr, group_record->gmp_rpt_group.gmp_addr,
578 : IPV4_ADDR_LEN);
579 : }
580 :
581 0 : memmove(v1v2_pkt->igmp_v1v2_pkt_group, group_record->gmp_rpt_group.gmp_addr, IPV4_ADDR_LEN);
582 :
583 : /* If this is a BLOCK record, don't send anything. */
584 :
585 0 : if (group_record->gmp_rpt_type == GMP_RPT_BLOCK)
586 0 : formatted_len = 0;
587 :
588 : /* Flush the source list and flag that we're done with this group. */
589 :
590 0 : gmp_flush_xmit_list(group_record->gmp_rpt_xmit_srcs);
591 0 : gmpp_group_done(role, GMP_PROTO_IGMP, group_record->gmp_rpt_group_id);
592 0 : break;
593 :
594 0 : default:
595 0 : gmpx_assert(FALSE);
596 : }
597 :
598 63 : return formatted_len;
599 : }
600 :
601 :
602 : /*
603 : * igmp_format_v3_query
604 : *
605 : * Format an IGMPv3 Query packet.
606 : *
607 : * Returns the formatted packet length, or 0 if nothing to send.
608 : */
609 : static uint32_t
610 850 : igmp_format_v3_query (gmp_role role, gmp_packet *gen_packet,
611 : igmp_packet *packet, uint32_t packet_len)
612 : {
613 : igmp_v3_query *v3_query_pkt;
614 : igmp_hdr *pkt_hdr;
615 : gmp_query_packet *query_pkt;
616 : uint32_t formatted_len;
617 : uint32_t source_count;
618 : gmp_addr_list_entry *addr_entry;
619 : gmp_addr_list *addr_list;
620 : uint8_t *addr_ptr;
621 : gmp_addr_cat_entry *cat_entry;
622 : // boolean gss_query;
623 : boolean group_done;
624 :
625 : /* Format a query packet. */
626 :
627 850 : gmpx_assert(packet_len >= sizeof(igmp_v3_query) + IPV4_ADDR_LEN);
628 850 : query_pkt = &gen_packet->gmp_packet_contents.gmp_packet_query;
629 850 : v3_query_pkt = &packet->igmp_pkt_v3_query;
630 850 : memset(v3_query_pkt, 0, sizeof(igmp_v3_query));
631 850 : pkt_hdr = &v3_query_pkt->igmp_v3_query_hdr;
632 :
633 : /* Set up the header. */
634 :
635 850 : pkt_hdr->igmp_hdr_type = IGMP_TYPE_QUERY;
636 850 : pkt_hdr->igmp_hdr_maxresp =
637 850 : igmp_encode_fixfloat(query_pkt->gmp_query_max_resp /
638 : IGMP_MAX_RESP_MSEC);
639 :
640 : /* Note whether this is a GSS query. */
641 :
642 : // gss_query = query_pkt->gmp_query_group_query &&
643 : // query_pkt->gmp_query_xmit_srcs;
644 :
645 : /*
646 : * If this is a group query, copy in the group address. Otherwise,
647 : * zero it. Set the destination address accordingly.
648 : */
649 850 : if (query_pkt->gmp_query_group_query) {
650 17 : memmove(v3_query_pkt->igmp_v3_query_group, query_pkt->gmp_query_group.gmp_addr,
651 : IPV4_ADDR_LEN);
652 17 : memmove(gen_packet->gmp_packet_dest_addr.gmp_addr, query_pkt->gmp_query_group.gmp_addr,
653 : IPV4_ADDR_LEN);
654 : } else {
655 833 : memset(v3_query_pkt->igmp_v3_query_group, 0, IPV4_ADDR_LEN);
656 833 : memmove(gen_packet->gmp_packet_dest_addr.gmp_addr, igmp_all_hosts, IPV4_ADDR_LEN);
657 : }
658 :
659 : /* Set the S and QRV fields. */
660 :
661 850 : v3_query_pkt->igmp_v3_query_s_qrv = query_pkt->gmp_query_qrv;
662 850 : if (query_pkt->gmp_query_suppress)
663 0 : v3_query_pkt->igmp_v3_query_s_qrv |= IGMP_SUPP_RTR_PROC_MASK;
664 :
665 : /* Set the QQIC field. */
666 :
667 850 : v3_query_pkt->igmp_v3_query_qqic =
668 850 : igmp_encode_fixfloat(query_pkt->gmp_query_qqi / MSECS_PER_SEC);
669 :
670 850 : formatted_len = sizeof(igmp_v3_query);
671 850 : packet_len -= sizeof(igmp_v3_query);
672 850 : source_count = 0;
673 850 : addr_ptr = v3_query_pkt->igmp_v3_query_source;
674 :
675 : /* Add as may sources as possible if they are present. */
676 :
677 850 : addr_list = query_pkt->gmp_query_xmit_srcs;
678 850 : if (addr_list) {
679 :
680 : /*
681 : * Top of source loop. Add sources and delink them from the
682 : * transmit chain until either there are no more sources or
683 : * we've run out of space in the packet.
684 : */
685 17 : group_done = TRUE;
686 : while (TRUE) {
687 :
688 : /* Bail if out of space in the packet. */
689 :
690 53 : if (packet_len < IPV4_ADDR_LEN) {
691 0 : group_done = FALSE;
692 0 : break;
693 : }
694 :
695 : /* Got space. Grab the next address. */
696 :
697 53 : addr_entry = gmp_first_xmit_addr_entry(addr_list);
698 :
699 : /* Bail if there's nothing there. */
700 :
701 53 : if (!addr_entry)
702 17 : break;
703 :
704 : /*
705 : * We've got an address entry. Look up the catalog entry
706 : * and copy the address into the packet.
707 : */
708 : cat_entry =
709 36 : gmp_get_addr_cat_by_ordinal(addr_list->addr_vect.av_catalog,
710 : addr_entry->addr_ent_ord);
711 36 : gmpx_assert(cat_entry);
712 36 : memmove(addr_ptr, cat_entry->adcat_ent_addr.gmp_addr, IPV4_ADDR_LEN);
713 :
714 : /* Delink the address entry. */
715 :
716 36 : gmp_dequeue_xmit_addr_entry(addr_entry);
717 :
718 : /* Do housekeeping. */
719 :
720 36 : addr_ptr += IPV4_ADDR_LEN;
721 36 : formatted_len += IPV4_ADDR_LEN;
722 36 : packet_len -= IPV4_ADDR_LEN;
723 36 : source_count++;
724 : }
725 :
726 : /* If we're done with the group, call back. */
727 :
728 17 : if (group_done) {
729 17 : gmpp_group_done(role, GMP_PROTO_IGMP,
730 : query_pkt->gmp_query_group_id);
731 : }
732 :
733 : }
734 :
735 : /* Update the source count. */
736 :
737 850 : put_short(&v3_query_pkt->igmp_v3_query_num_srcs, source_count);
738 :
739 850 : return formatted_len;
740 : }
741 :
742 : /*
743 : * igmp_format_v3_report
744 : *
745 : * Format an IGMPv3 Report packet.
746 : *
747 : * Returns the formatted packet length, or 0 if nothing to send.
748 : */
749 : static uint32_t
750 0 : igmp_format_v3_report (gmp_role role, gmp_packet *gen_packet,
751 : igmp_packet *packet, uint32_t packet_len)
752 : {
753 : igmp_v3_report *v3_rpt_pkt;
754 : igmp_v3_rpt_rcrd *v3_group_rcrd;
755 : igmp_hdr *pkt_hdr;
756 : gmp_report_packet *report_pkt;
757 : gmp_report_group_record *group_record;
758 : uint32_t formatted_len;
759 : uint32_t source_count;
760 : uint32_t group_count;
761 : uint32_t groups_remaining;
762 : uint32_t group_space;
763 : gmp_addr_list_entry *addr_entry;
764 : gmp_addr_list *addr_list;
765 : uint8_t *byte_ptr;
766 : gmp_addr_cat_entry *cat_entry;
767 : task_thread *thread_ptr;
768 : boolean group_done;
769 :
770 : /* Format a report packet. */
771 :
772 0 : gmpx_assert(packet_len >= sizeof(igmp_v3_report) +
773 : sizeof(igmp_v3_rpt_rcrd) + IPV4_ADDR_LEN);
774 0 : report_pkt = &gen_packet->gmp_packet_contents.gmp_packet_report;
775 0 : v3_rpt_pkt = &packet->igmp_pkt_v3_rpt;
776 0 : memset(v3_rpt_pkt, 0, sizeof(igmp_v3_report));
777 0 : pkt_hdr = &v3_rpt_pkt->igmp_v3_report_hdr;
778 :
779 : /* Set up the header. */
780 :
781 0 : pkt_hdr->igmp_hdr_type = IGMP_TYPE_V3_REPORT;
782 :
783 0 : packet_len -= sizeof(igmp_v3_report);
784 0 : formatted_len = sizeof(igmp_v3_report);
785 0 : group_count = 0;
786 0 : v3_group_rcrd = v3_rpt_pkt->igmp_v3_report_rcrd;
787 0 : groups_remaining = report_pkt->gmp_report_group_count;
788 0 : thread_ptr = NULL;
789 :
790 : /*
791 : * Top of the group loop. Walk the groups until we run out of
792 : * groups, or run out of space in the packet.
793 : */
794 : while (TRUE) {
795 :
796 : /* Bail if there are no more groups. */
797 :
798 0 : if (!groups_remaining)
799 0 : break;
800 :
801 : /* Grab the next group. */
802 :
803 : thread_ptr =
804 0 : thread_circular_thread_next(&report_pkt->gmp_report_group_head,
805 : thread_ptr);
806 0 : group_record = gmp_thread_to_report_group_record(thread_ptr);
807 0 : gmpx_assert(group_record);
808 :
809 : /*
810 : * Got the group. Calculate the size of the group record we'll
811 : * generate, and see if it will fit.
812 : */
813 0 : group_space = sizeof(igmp_v3_rpt_rcrd);
814 0 : if (group_record->gmp_rpt_xmit_srcs) {
815 0 : group_space += group_record->gmp_rpt_xmit_srcs->xmit_addr_count *
816 : IPV4_ADDR_LEN;
817 : }
818 :
819 0 : if (group_space > packet_len) {
820 :
821 : /*
822 : * It's not going to fit. If this is not the first group,
823 : * bail. Otherwise, go ahead with it anyhow. We'll end
824 : * up truncating the list of sources in this case.
825 : */
826 0 : if (group_count)
827 0 : break;
828 : }
829 :
830 : /* Create the group record header. */
831 :
832 0 : memset(v3_group_rcrd, 0, sizeof(igmp_v3_rpt_rcrd));
833 0 : v3_group_rcrd->igmp_v3_rpt_rec_type = group_record->gmp_rpt_type;
834 0 : memmove(v3_group_rcrd->igmp_v3_rpt_group, group_record->gmp_rpt_group.gmp_addr,
835 : IPV4_ADDR_LEN);
836 0 : packet_len -= sizeof(igmp_v3_rpt_rcrd);
837 0 : formatted_len += sizeof(igmp_v3_rpt_rcrd);
838 0 : byte_ptr = get_igmp_v3_rpt_source(v3_group_rcrd);
839 0 : group_count++;
840 :
841 : /* Now walk the sources, if any, and add them to the record. */
842 :
843 0 : source_count = 0;
844 0 : addr_list = group_record->gmp_rpt_xmit_srcs;
845 0 : group_done = TRUE;
846 0 : if (addr_list) {
847 : while (TRUE) {
848 :
849 : /* Bail if out of space in the packet. */
850 :
851 0 : if (packet_len < IPV4_ADDR_LEN)
852 0 : break;
853 :
854 : /* Got space. Grab the next address. */
855 :
856 0 : addr_entry = gmp_first_xmit_addr_entry(addr_list);
857 :
858 : /* Bail if there's nothing there. */
859 :
860 0 : if (!addr_entry)
861 0 : break;
862 :
863 : /*
864 : * We've got an address entry. Look up the catalog
865 : * entry and copy the address into the packet.
866 : */
867 : cat_entry =
868 0 : gmp_get_addr_cat_by_ordinal(
869 : addr_list->addr_vect.av_catalog,
870 : addr_entry->addr_ent_ord);
871 0 : gmpx_assert(cat_entry);
872 0 : memmove(byte_ptr, cat_entry->adcat_ent_addr.gmp_addr, IPV4_ADDR_LEN);
873 :
874 : /* Delink the address entry. */
875 :
876 0 : gmp_dequeue_xmit_addr_entry(addr_entry);
877 :
878 : /* Do housekeeping. */
879 :
880 0 : byte_ptr += IPV4_ADDR_LEN;
881 0 : formatted_len += IPV4_ADDR_LEN;
882 0 : packet_len -= IPV4_ADDR_LEN;
883 0 : source_count++;
884 : }
885 :
886 : /* Update the source count in the group record. */
887 :
888 0 : put_short(&v3_group_rcrd->igmp_v3_rpt_num_srcs, source_count);
889 :
890 : /*
891 : * We've either run out of addresses or run out of space. If
892 : * we've run out of space, and the record is of type IS_EX or
893 : * TO_EX, we flush the address list, which effectively truncates
894 : * the list in the packet. Otherwise, we leave it alone, and
895 : * we'll send more sources in the next packet.
896 : */
897 0 : if (!gmp_xmit_addr_list_empty(addr_list)) {
898 :
899 : /* Not empty. Flush if the record is IS_EX or TO_EX. */
900 :
901 0 : if (group_record->gmp_rpt_type == GMP_RPT_IS_EX ||
902 0 : group_record->gmp_rpt_type == GMP_RPT_TO_EX) {
903 0 : gmp_flush_xmit_list(addr_list); /* Flush it. */
904 : } else {
905 0 : group_done = FALSE; /* More to go! */
906 : }
907 : }
908 : }
909 :
910 : /* If we finished that group, notify the authorities. */
911 :
912 0 : if (group_done) {
913 0 : gmpp_group_done(role, GMP_PROTO_IGMP,
914 : group_record->gmp_rpt_group_id);
915 : }
916 :
917 : /* Advance the group record pointer. */
918 :
919 0 : v3_group_rcrd = (igmp_v3_rpt_rcrd *) byte_ptr;
920 0 : groups_remaining--;
921 : }
922 :
923 : /* Set the destination address. */
924 :
925 0 : memmove(gen_packet->gmp_packet_dest_addr.gmp_addr, igmp_all_v3_routers, IPV4_ADDR_LEN);
926 :
927 : /* Update the group count in the packet header. */
928 :
929 0 : put_short(&v3_rpt_pkt->igmp_v3_report_num_rcrds, group_count);
930 :
931 0 : return formatted_len;
932 : }
933 :
934 :
935 : /*
936 : * igmp_format_v3_packet
937 : *
938 : * Format an IGMP V3 packet.
939 : *
940 : * Returns the formatted packet length, or 0 if nothing to send.
941 : */
942 : static uint32_t
943 850 : igmp_format_v3_packet (gmp_role role, gmp_packet *gen_packet,
944 : igmp_packet *packet, uint32_t packet_len)
945 : {
946 : uint32_t formatted_len;
947 :
948 : /* See what kind of packet we're sending. */
949 :
950 850 : switch (gen_packet->gmp_packet_type) {
951 :
952 850 : case GMP_QUERY_PACKET:
953 850 : formatted_len = igmp_format_v3_query(role, gen_packet, packet,
954 : packet_len);
955 :
956 850 : break;
957 :
958 :
959 0 : case GMP_REPORT_PACKET:
960 0 : formatted_len = igmp_format_v3_report(role, gen_packet, packet,
961 : packet_len);
962 0 : break;
963 :
964 0 : default:
965 0 : gmpx_assert(FALSE);
966 : formatted_len = 0; /* Quiet the compiler. */
967 : }
968 :
969 850 : return formatted_len;
970 : }
971 :
972 :
973 : /*
974 : * igmp_next_xmit_packet
975 : *
976 : * Format the next IGMP packet to transmit for a role (host/router).
977 : *
978 : * Returns the length of the formatted packet, or 0 if there was
979 : * nothing to send.
980 : *
981 : * Also returns a pointer to the destination address to send the packet to.
982 : *
983 : * The packet is formatted in the supplied buffer, which is assumed to be
984 : * large enough to carry at least a minimum packet of any type.
985 : *
986 : * Source and destination addresses are written through the supplied pointers.
987 : */
988 : uint32_t
989 3546 : igmp_next_xmit_packet (gmp_role role, gmpx_intf_id intf_id,
990 : void *packet, uint8_t *dest_addr, uint32_t packet_len,
991 : void *trace_context, uint32_t trace_flags)
992 : {
993 : gmp_packet *gen_packet;
994 : igmp_packet *igmp_pkt;
995 : igmp_hdr *pkt_hdr;
996 : uint32_t formatted_len;
997 : uint16_t checksum;
998 :
999 3546 : igmp_pkt = packet;
1000 3546 : formatted_len = 0;
1001 :
1002 : while (TRUE) {
1003 :
1004 : /* Get the next generic packet. */
1005 :
1006 3546 : gen_packet = gmpp_next_xmit_packet(role, GMP_PROTO_IGMP, intf_id,
1007 : packet_len);
1008 :
1009 : /* Bail if nothing to send. */
1010 :
1011 3546 : if (!gen_packet)
1012 2627 : break;
1013 :
1014 919 : gmpx_assert(gen_packet->gmp_packet_type < GMP_NUM_PACKET_TYPES);
1015 :
1016 : /* Got a packet. Split based on the version number. */
1017 :
1018 919 : switch (gen_packet->gmp_packet_version) {
1019 :
1020 6 : case GMP_VERSION_BASIC:
1021 6 : formatted_len = igmp_format_v1_packet(role, gen_packet, igmp_pkt,
1022 : packet_len);
1023 6 : break;
1024 :
1025 63 : case GMP_VERSION_LEAVES:
1026 63 : formatted_len = igmp_format_v2_packet(role, gen_packet, igmp_pkt,
1027 : packet_len);
1028 63 : break;
1029 :
1030 850 : case GMP_VERSION_SOURCES:
1031 850 : formatted_len = igmp_format_v3_packet(role, gen_packet, igmp_pkt,
1032 : packet_len);
1033 850 : break;
1034 :
1035 0 : default:
1036 0 : gmpx_assert(FALSE);
1037 : break;
1038 : }
1039 :
1040 : /*
1041 : * If the formatting routines returned a length of zero, it means
1042 : * that the packet was suppressed. Loop back to try another one
1043 : * in that case, or break out if not.
1044 : */
1045 919 : if (formatted_len)
1046 919 : break;
1047 :
1048 : /* Toss the packet that didn't build. */
1049 :
1050 0 : gmpp_packet_done(role, GMP_PROTO_IGMP, gen_packet);
1051 : }
1052 :
1053 : /*
1054 : * Checksum the packet and fill in the destination address field
1055 : * if it's there.
1056 : */
1057 3546 : if (formatted_len) {
1058 919 : pkt_hdr = &igmp_pkt->igmp_pkt_naked.igmp_naked_header_hdr;
1059 919 : checksum = gmpx_calculate_cksum(packet, formatted_len);
1060 919 : pkt_hdr->igmp_hdr_cksum = checksum;
1061 919 : memmove(dest_addr, gen_packet->gmp_packet_dest_addr.gmp_addr, IPV4_ADDR_LEN);
1062 :
1063 : /* Trace the packet. */
1064 :
1065 919 : gmp_igmp_trace_pkt(igmp_pkt, formatted_len, dest_addr, intf_id, FALSE,
1066 : trace_context, trace_flags);
1067 : }
1068 :
1069 : /* Notify that we're done with the packet. */
1070 :
1071 3546 : if (gen_packet)
1072 919 : gmpp_packet_done(role, GMP_PROTO_IGMP, gen_packet);
1073 :
1074 3546 : return formatted_len;
1075 : }
1076 :
1077 :
1078 : /*
1079 : * igmp_parse_v1_packet
1080 : *
1081 : * Parse an IGMP V1 packet.
1082 : *
1083 : * Returns TRUE if the packet parsed OK, or FALSE if not.
1084 : *
1085 : * Fills in the generic packet structure as appropriate.
1086 : */
1087 : static boolean
1088 1 : igmp_parse_v1_packet(igmp_packet *packet, gmp_packet *gen_packet,
1089 : uint32_t packet_len GMPX_UNUSED,
1090 : gmp_message_type msg_type)
1091 : {
1092 : gmp_query_packet *query_pkt;
1093 : gmp_report_packet *report_pkt;
1094 : gmp_report_group_record *group_rcrd;
1095 : igmp_v1v2_pkt *v1v2_pkt;
1096 :
1097 : /* Switch based on the message type. */
1098 :
1099 1 : v1v2_pkt = &packet->igmp_pkt_v1v2;
1100 :
1101 1 : switch (msg_type) {
1102 :
1103 0 : case GMP_QUERY_PACKET:
1104 :
1105 : /* Query packet. Not much to do here except get the max resp value. */
1106 :
1107 0 : query_pkt = &gen_packet->gmp_packet_contents.gmp_packet_query;
1108 0 : query_pkt->gmp_query_max_resp =
1109 0 : (igmp_max_resp_value(packet, GMP_VERSION_BASIC) *
1110 : IGMP_MAX_RESP_MSEC);
1111 0 : break;
1112 :
1113 1 : case GMP_REPORT_PACKET:
1114 :
1115 : /*
1116 : * Report packet. Grab a group record and fill it in. We use
1117 : * an IS_EX record with a null address list to signify a Join.
1118 : */
1119 1 : report_pkt = &gen_packet->gmp_packet_contents.gmp_packet_report;
1120 1 : group_rcrd = gmpp_create_group_record(report_pkt, NULL,
1121 1 : v1v2_pkt->igmp_v1v2_pkt_group,
1122 : IPV4_ADDR_LEN);
1123 1 : if (!group_rcrd)
1124 0 : return FALSE;
1125 1 : group_rcrd->gmp_rpt_type = GMP_RPT_IS_EX;
1126 :
1127 : /* Complain if the group address is wrong. */
1128 :
1129 1 : if (!igmp_addr_is_mcast(group_rcrd->gmp_rpt_group.gmp_v4_addr))
1130 0 : return FALSE;
1131 :
1132 1 : break;
1133 :
1134 0 : default:
1135 0 : gmpx_assert(FALSE);
1136 : }
1137 :
1138 1 : return TRUE;
1139 : }
1140 :
1141 :
1142 : /*
1143 : * igmp_parse_v2_packet
1144 : *
1145 : * Parse an IGMP V2 packet.
1146 : *
1147 : * Returns TRUE if the packet parsed OK, or FALSE if not.
1148 : *
1149 : * Fills in the generic packet structure as appropriate.
1150 : */
1151 : static boolean
1152 79 : igmp_parse_v2_packet(igmp_packet *packet, gmp_packet *gen_packet,
1153 : uint32_t packet_len GMPX_UNUSED,
1154 : gmp_message_type msg_type)
1155 : {
1156 : gmp_query_packet *query_pkt;
1157 : gmp_report_packet *report_pkt;
1158 : gmp_report_group_record *group_rcrd;
1159 : igmp_v1v2_pkt *v1v2_pkt;
1160 : uint32_t byte_offset;
1161 :
1162 : /* Switch based on the message type. */
1163 :
1164 79 : v1v2_pkt = &packet->igmp_pkt_v1v2;
1165 :
1166 79 : switch (msg_type) {
1167 :
1168 0 : case GMP_QUERY_PACKET:
1169 :
1170 : /* Query packet. Get the max resp value. */
1171 :
1172 0 : query_pkt = &gen_packet->gmp_packet_contents.gmp_packet_query;
1173 0 : query_pkt->gmp_query_max_resp =
1174 0 : (igmp_max_resp_value(packet, GMP_VERSION_LEAVES) *
1175 : IGMP_MAX_RESP_MSEC);
1176 :
1177 : /* Copy the group address. */
1178 :
1179 0 : memmove(query_pkt->gmp_query_group.gmp_addr, v1v2_pkt->igmp_v1v2_pkt_group, IPV4_ADDR_LEN);
1180 :
1181 : /*
1182 : * If the group address is nonzero, flag that we've got a
1183 : * group query.
1184 : */
1185 0 : for (byte_offset = 0; byte_offset < IPV4_ADDR_LEN; byte_offset ++) {
1186 0 : if (v1v2_pkt->igmp_v1v2_pkt_group[byte_offset]) {
1187 0 : query_pkt->gmp_query_group_query = TRUE;
1188 0 : break;
1189 : }
1190 : }
1191 :
1192 0 : break;
1193 :
1194 79 : case GMP_REPORT_PACKET:
1195 :
1196 : /* Report packet. Grab a group record and fill it in. */
1197 :
1198 79 : report_pkt = &gen_packet->gmp_packet_contents.gmp_packet_report;
1199 79 : group_rcrd = gmpp_create_group_record(report_pkt, NULL,
1200 79 : v1v2_pkt->igmp_v1v2_pkt_group,
1201 : IPV4_ADDR_LEN);
1202 79 : if (!group_rcrd)
1203 0 : return FALSE;
1204 :
1205 : /* Complain if the group address is wrong. */
1206 :
1207 79 : if (!igmp_addr_is_mcast(group_rcrd->gmp_rpt_group.gmp_v4_addr))
1208 0 : return FALSE;
1209 :
1210 : /*
1211 : * If this is a Leave, use record type TO_IN with a null
1212 : * address list. Otherwise (it's a Join) use IS_EX with a
1213 : * null address list.
1214 : */
1215 79 : if (v1v2_pkt->igmp_v1v2_pkt_hdr.igmp_hdr_type == IGMP_TYPE_V2_LEAVE) {
1216 31 : group_rcrd->gmp_rpt_type = GMP_RPT_TO_IN;
1217 : } else {
1218 48 : group_rcrd->gmp_rpt_type = GMP_RPT_IS_EX;
1219 : }
1220 79 : break;
1221 :
1222 0 : default:
1223 0 : gmpx_assert(FALSE);
1224 : }
1225 :
1226 79 : return TRUE;
1227 : }
1228 :
1229 :
1230 : /*
1231 : * igmp_parse_v3_report_packet
1232 : *
1233 : * Parse an IGMP V3 Report packet.
1234 : *
1235 : * Returns TRUE if the packet parsed OK, or FALSE if not.
1236 : *
1237 : * Fills in the generic packet structure as appropriate.
1238 : */
1239 : static boolean
1240 56 : igmp_parse_v3_report_packet(igmp_packet *packet, gmp_packet *gen_packet,
1241 : uint32_t packet_len)
1242 : {
1243 : igmp_v3_report *v3_rpt_pkt;
1244 : igmp_v3_rpt_rcrd *v3_rpt_rcrd;
1245 : gmp_report_packet *report_pkt;
1246 : gmp_report_group_record *group_rcrd;
1247 : uint32_t group_count;
1248 : uint32_t source_count;
1249 : uint32_t record_length;
1250 : uint8_t *byte_ptr;
1251 : uint8_t *addr_ptr;
1252 : gmp_addr_thread *addr_thread;
1253 :
1254 : /* Bail if the packet is too small. */
1255 :
1256 56 : if (packet_len < sizeof(igmp_v3_report))
1257 0 : return FALSE;
1258 :
1259 56 : v3_rpt_pkt = &packet->igmp_pkt_v3_rpt;
1260 56 : report_pkt = &gen_packet->gmp_packet_contents.gmp_packet_report;
1261 :
1262 : /* Extract the group count. */
1263 :
1264 56 : group_count = get_short(&v3_rpt_pkt->igmp_v3_report_num_rcrds);
1265 :
1266 : /* Loop for each group in the report. */
1267 :
1268 56 : packet_len -= sizeof(igmp_v3_report);
1269 56 : v3_rpt_rcrd = v3_rpt_pkt->igmp_v3_report_rcrd;
1270 56 : byte_ptr = (uint8_t *) v3_rpt_rcrd;
1271 116 : while (group_count--) {
1272 :
1273 : /*
1274 : * Looks like we have a group record. First make sure that there's
1275 : * enough left in the packet to examine the header.
1276 : */
1277 60 : if (packet_len < sizeof(igmp_v3_rpt_rcrd))
1278 0 : return FALSE;
1279 :
1280 : /* Validate the record type. */
1281 :
1282 60 : if (v3_rpt_rcrd->igmp_v3_rpt_rec_type < GMP_RPT_MIN ||
1283 60 : v3_rpt_rcrd->igmp_v3_rpt_rec_type > GMP_RPT_MAX)
1284 0 : return FALSE;
1285 :
1286 : /* Extract the source count. */
1287 :
1288 60 : source_count = get_short(&v3_rpt_rcrd->igmp_v3_rpt_num_srcs);
1289 :
1290 : /* Make sure there's enough packet left to hold the whole record. */
1291 :
1292 60 : record_length = sizeof(igmp_v3_rpt_rcrd) +
1293 60 : (source_count * IPV4_ADDR_LEN) + v3_rpt_rcrd->igmp_v3_rpt_aux_len;
1294 60 : if (packet_len < record_length)
1295 0 : return FALSE;
1296 :
1297 : /* Enough space. Create a group record. */
1298 :
1299 60 : group_rcrd = gmpp_create_group_record(report_pkt, NULL,
1300 60 : v3_rpt_rcrd->igmp_v3_rpt_group,
1301 : IPV4_ADDR_LEN);
1302 60 : if (!group_rcrd)
1303 0 : return FALSE; /* Out of memory */
1304 :
1305 : /* Complain if the group address is wrong. */
1306 :
1307 60 : if (!igmp_addr_is_mcast(group_rcrd->gmp_rpt_group.gmp_v4_addr))
1308 0 : return FALSE;
1309 :
1310 : /* Stash the record type. */
1311 :
1312 60 : group_rcrd->gmp_rpt_type = v3_rpt_rcrd->igmp_v3_rpt_rec_type;
1313 :
1314 : /* If there are any sources, add them to the record. */
1315 :
1316 60 : if (source_count) {
1317 59 : addr_ptr = get_igmp_v3_rpt_source(v3_rpt_rcrd);
1318 :
1319 : /* Allocate an address thread. */
1320 :
1321 59 : addr_thread = gmp_alloc_addr_thread();
1322 59 : if (!addr_thread)
1323 0 : return FALSE; /* Out of memory */
1324 :
1325 193 : while (source_count--) {
1326 :
1327 : /* Enqueue an address thread entry for the next address. */
1328 :
1329 134 : if (gmp_enqueue_addr_thread_addr(addr_thread, addr_ptr,
1330 : IPV4_ADDR_LEN) < 0) {
1331 0 : free(addr_thread);
1332 0 : return FALSE; /* Out of memory */
1333 : }
1334 134 : addr_ptr += IPV4_ADDR_LEN;
1335 : }
1336 59 : group_rcrd->gmp_rpt_rcv_srcs = addr_thread;
1337 : }
1338 :
1339 : /* Advance to the next group. */
1340 :
1341 60 : packet_len -= record_length;
1342 60 : byte_ptr += record_length;
1343 60 : v3_rpt_rcrd = (igmp_v3_rpt_rcrd *) byte_ptr;
1344 : }
1345 :
1346 56 : return TRUE;
1347 : }
1348 :
1349 :
1350 : /*
1351 : * igmp_parse_v3_query_packet
1352 : *
1353 : * Parse an IGMP V3 Query packet.
1354 : *
1355 : * Returns TRUE if the packet parsed OK, or FALSE if not.
1356 : *
1357 : * Fills in the generic packet structure as appropriate.
1358 : */
1359 : static boolean
1360 0 : igmp_parse_v3_query_packet(igmp_packet *packet, gmp_packet *gen_packet,
1361 : uint32_t packet_len)
1362 : {
1363 : igmp_v3_query *v3_query_pkt;
1364 : gmp_query_packet *query_pkt;
1365 : uint32_t byte_offset;
1366 : gmp_addr_thread *addr_thread;
1367 : uint32_t source_count;
1368 : uint8_t *addr_ptr;
1369 :
1370 : /* Bail if the packet is too small. */
1371 :
1372 0 : if (packet_len < sizeof(igmp_v3_query))
1373 0 : return FALSE;
1374 :
1375 0 : v3_query_pkt = &packet->igmp_pkt_v3_query;
1376 0 : query_pkt = &gen_packet->gmp_packet_contents.gmp_packet_query;
1377 :
1378 : /* Query packet. Get the max resp value. */
1379 :
1380 0 : query_pkt->gmp_query_max_resp =
1381 0 : igmp_max_resp_value(packet, GMP_VERSION_SOURCES) * IGMP_MAX_RESP_MSEC;
1382 :
1383 : /* Copy the group address. */
1384 :
1385 0 : memmove(query_pkt->gmp_query_group.gmp_addr, v3_query_pkt->igmp_v3_query_group, IPV4_ADDR_LEN);
1386 :
1387 : /*
1388 : * If the group address is nonzero, flag that we've got a
1389 : * group query.
1390 : */
1391 0 : for (byte_offset = 0; byte_offset < IPV4_ADDR_LEN; byte_offset ++) {
1392 0 : if (v3_query_pkt->igmp_v3_query_group[byte_offset]) {
1393 0 : query_pkt->gmp_query_group_query = TRUE;
1394 0 : break;
1395 : }
1396 : }
1397 :
1398 : /* Parse the S and QRV fields. */
1399 :
1400 0 : query_pkt->gmp_query_suppress =
1401 0 : ((v3_query_pkt->igmp_v3_query_s_qrv & IGMP_SUPP_RTR_PROC_MASK) != 0);
1402 0 : query_pkt->gmp_query_qrv =
1403 0 : v3_query_pkt->igmp_v3_query_s_qrv & IGMP_QRV_MASK;
1404 :
1405 : /* Parse the QQIC field. */
1406 :
1407 0 : query_pkt->gmp_query_qqi =
1408 0 : igmp_decode_fixfloat(v3_query_pkt->igmp_v3_query_qqic) * MSECS_PER_SEC;
1409 :
1410 : /*
1411 : * Parse the source count field. If nonzero and this is a general query,
1412 : * flag a parse error.
1413 : */
1414 0 : source_count = get_short(&v3_query_pkt->igmp_v3_query_num_srcs);
1415 0 : if (source_count && !query_pkt->gmp_query_group_query)
1416 0 : return FALSE;
1417 :
1418 0 : packet_len -= sizeof(igmp_v3_query);
1419 :
1420 : /*
1421 : * If the remaining packet length doesn't match the count of sources, flag
1422 : * a parse error.
1423 : */
1424 0 : if (packet_len != (source_count * IPV4_ADDR_LEN))
1425 0 : return FALSE;
1426 :
1427 : /*
1428 : * Looks kosher. Now pull the list of source addresses into an
1429 : * address thread, if they are there.
1430 : */
1431 0 : if (source_count) {
1432 0 : addr_ptr = v3_query_pkt->igmp_v3_query_source;
1433 0 : addr_thread = gmp_alloc_addr_thread();
1434 0 : query_pkt->gmp_query_rcv_srcs = addr_thread;
1435 0 : if (!addr_thread)
1436 0 : return FALSE; /* Out of memory */
1437 0 : while (source_count--) {
1438 0 : if (gmp_enqueue_addr_thread_addr(addr_thread, addr_ptr,
1439 : IPV4_ADDR_LEN) < 0) {
1440 0 : return FALSE; /* Out of memory */
1441 : }
1442 0 : addr_ptr += IPV4_ADDR_LEN;
1443 : }
1444 : }
1445 :
1446 0 : return TRUE;
1447 : }
1448 :
1449 :
1450 : /*
1451 : * igmp_parse_v3_packet
1452 : *
1453 : * Parse an IGMP V3 packet.
1454 : *
1455 : * Returns TRUE if the packet parsed OK, or FALSE if not.
1456 : *
1457 : * Fills in the generic packet structure as appropriate.
1458 : */
1459 : static boolean
1460 56 : igmp_parse_v3_packet(igmp_packet *packet, gmp_packet *gen_packet,
1461 : uint32_t packet_len, gmp_message_type msg_type)
1462 : {
1463 : boolean result;
1464 :
1465 : /* Switch based on message type. */
1466 :
1467 56 : switch (msg_type) {
1468 :
1469 0 : case GMP_QUERY_PACKET:
1470 0 : result = igmp_parse_v3_query_packet(packet, gen_packet, packet_len);
1471 0 : break;
1472 :
1473 :
1474 56 : case GMP_REPORT_PACKET:
1475 56 : result = igmp_parse_v3_report_packet(packet, gen_packet, packet_len);
1476 56 : break;
1477 :
1478 0 : default:
1479 0 : gmpx_assert(FALSE);
1480 : result = FALSE; /* Quiet the compiler. */
1481 : break;
1482 :
1483 : }
1484 :
1485 56 : return result;
1486 : }
1487 :
1488 :
1489 : /*
1490 : * igmp_process_pkt
1491 : *
1492 : * Process a received IGMP packet. We parse it into generic form and pass it
1493 : * to the clients.
1494 : *
1495 : * Returns TRUE if the packet parsed OK, or FALSE if there was a problem.
1496 : */
1497 : boolean
1498 136 : igmp_process_pkt (void *rcv_pkt, const uint8_t *src_addr,
1499 : const uint8_t *dest_addr, uint32_t packet_len,
1500 : gmpx_intf_id intf_id, gmpx_packet_attr attrib,
1501 : void *trace_context, uint32_t trace_flags)
1502 : {
1503 : igmp_packet *packet;
1504 : gmp_packet *gen_packet;
1505 : gmp_version version;
1506 : gmp_message_type msg_type;
1507 : boolean parse_ok;
1508 :
1509 136 : packet = rcv_pkt;
1510 136 : parse_ok = TRUE;
1511 :
1512 : /* Bail if the packet is too small. */
1513 :
1514 136 : if (packet_len < sizeof(igmp_naked_header)) {
1515 0 : parse_ok = FALSE;
1516 : }
1517 :
1518 : /*
1519 : * Bail if the version is bogus or the packet is otherwise
1520 : * unrecognizable.
1521 : */
1522 136 : version = igmp_generic_version(packet, packet_len, &msg_type);
1523 136 : if (version == GMP_VERSION_INVALID) {
1524 0 : parse_ok = FALSE;
1525 : }
1526 :
1527 : /*
1528 : * Looks like we have a recognizable header, message type, and version.
1529 : * Create a basic generic packet of the appropriate type.
1530 : */
1531 136 : gen_packet = gmpp_create_packet_header(version, msg_type, GMP_PROTO_IGMP);
1532 136 : if (!gen_packet)
1533 0 : return FALSE; /* Out of memory */
1534 :
1535 : /* Set up the address fields. */
1536 :
1537 136 : if (src_addr) {
1538 136 : memmove(gen_packet->gmp_packet_src_addr.gmp_addr, src_addr, IPV4_ADDR_LEN);
1539 : }
1540 :
1541 136 : if (dest_addr) {
1542 136 : memmove(gen_packet->gmp_packet_dest_addr.gmp_addr, dest_addr, IPV4_ADDR_LEN);
1543 : }
1544 :
1545 : /* If the packet looks OK so far, parse it further. */
1546 :
1547 136 : if (parse_ok) {
1548 :
1549 : /* Save the attribute. */
1550 :
1551 136 : gen_packet->gmp_packet_attr = attrib;
1552 :
1553 : /* Split off by version and by packet type for further parsing. */
1554 :
1555 136 : switch (version) {
1556 1 : case GMP_VERSION_BASIC:
1557 :
1558 : /* IGMPv1. Parse it. */
1559 :
1560 1 : parse_ok = igmp_parse_v1_packet(packet, gen_packet, packet_len,
1561 : msg_type);
1562 1 : break;
1563 :
1564 79 : case GMP_VERSION_LEAVES:
1565 :
1566 : /* IGMPv2. Parse it. */
1567 :
1568 79 : parse_ok = igmp_parse_v2_packet(packet, gen_packet, packet_len,
1569 : msg_type);
1570 79 : break;
1571 :
1572 56 : case GMP_VERSION_SOURCES:
1573 :
1574 : /* IGMPv3. Parse it. */
1575 :
1576 56 : parse_ok = igmp_parse_v3_packet(packet, gen_packet, packet_len,
1577 : msg_type);
1578 56 : break;
1579 :
1580 0 : default:
1581 0 : gmpx_assert(FALSE);
1582 : break;
1583 : }
1584 : }
1585 :
1586 : /*
1587 : * If the packet parsed OK, trace it and then pass it along.
1588 : * Otherwise, complain.
1589 : */
1590 136 : if (parse_ok) {
1591 136 : gmp_igmp_trace_pkt(packet, packet_len, src_addr, intf_id, TRUE,
1592 : trace_context, trace_flags);
1593 136 : gmpp_process_rcv_packet(gen_packet, intf_id);
1594 : } else {
1595 0 : gmp_igmp_trace_bad_pkt(packet_len, src_addr, intf_id, trace_context,
1596 : trace_flags);
1597 : }
1598 :
1599 : /* Toss the parsed packet. */
1600 :
1601 136 : gmpp_destroy_packet(gen_packet);
1602 :
1603 136 : return parse_ok;
1604 : }
|