Line data Source code
1 : /* $Id: gmpp_proto.c 346474 2009-11-14 10:18:58Z ssiano $
2 : *
3 : * gmpp_proto.c - GMP generic packet handling
4 : *
5 : * Dave Katz, March 2008
6 : *
7 : * Copyright (c) 2008, Juniper Networks, Inc.
8 : * All rights reserved.
9 : *
10 : * This module handles generic packet I/O for GMP host and router functions.
11 : * It acts as an interface between the host/router modules on one side and
12 : * the IGMP/MLD modules on the other side.
13 : *
14 : * There are two instantiations of this function, one for each role
15 : * (Host and Router.)
16 : *
17 : * This function has no visibility into host- and router-specific data
18 : * structures, so that we can operate with only one side compiled in if
19 : * desired.
20 : *
21 : *
22 : * Registration
23 : *
24 : * The client (host/router) must first register via gmpp_register.
25 : * This establishes links between the two sides.
26 : *
27 : * Transmission
28 : *
29 : * When a client wishes to transmit a packet, it informs this module
30 : * via gmpp_start_xmit. This module then informs the protocol
31 : * module, which ultimately informs the I/O environment. A callback
32 : * percolates back through here to the client xmit_ready callback,
33 : * requesting a packet. The client passes back a pointer to a
34 : * generic packet.
35 : *
36 : * This module forms a packet out of some or all of the generic
37 : * packet data passed by the client, with the help of the
38 : * protocol-specific modules, and passes the packet to the I/O
39 : * environment. We delink each source address from its transmit
40 : * list as we process it. We call back the client group_done
41 : * routine as we finish processing the data for each group.
42 : *
43 : * In some cases, a single group may span a packet (an Include list
44 : * with a very large number of sources.) In this case we don't call
45 : * the group_done entry and leave some of the source addresses still
46 : * enqueued. When we call back for the next packet, the client will
47 : * pass us the same group again and we continue.
48 : *
49 : * Reception
50 : *
51 : * Packet reception is driven from the I/O environment. We receive
52 : * an indication from there and parse the packet, with the help of
53 : * the protocol modules, and pass the parsed packet to the client's
54 : * packet_rcv callback. The client is expected to process the
55 : * entire parsed packet atomically, as we free the parsed packet
56 : * immediately.
57 : *
58 : */
59 : #include "gmpx_basic_types.h"
60 : #include "gmp.h"
61 : #include "gmpx_environment.h"
62 : #include "gmp_externs.h"
63 : #include "gmp_private.h"
64 : #include "gmpp_private.h"
65 : #include "igmp_protocol.h"
66 : #include "mld_proto.h"
67 :
68 : /*
69 : * Context blocks
70 : *
71 : * Statically allocated, indexed by client role (host/router).
72 : */
73 : static gmpp_context gmpp_context_block[GMP_NUM_ROLES];
74 : static gmpp_io_context gmpp_io_context_block[GMP_NUM_PROTOS][GMP_NUM_ROLES];
75 : static gmpx_block_tag gmpp_packet_header_tag;
76 : static gmpx_block_tag gmpp_group_record_tag;
77 : static gmpx_block_tag gmpp_io_exception_tag;
78 :
79 : static boolean gmpp_initialized;
80 :
81 : /*
82 : * gmpp_init
83 : *
84 : * Do basic initialization.
85 : */
86 : void
87 125 : gmpp_init (void)
88 : {
89 125 : if (!gmpp_initialized) {
90 :
91 : /* Set up memory blocks. */
92 :
93 125 : gmpp_packet_header_tag =
94 125 : gmpx_malloc_block_create(sizeof(gmp_packet),
95 : "GMP generic packet header");
96 125 : gmpp_group_record_tag =
97 125 : gmpx_malloc_block_create(sizeof(gmp_report_group_record),
98 : "GMP generic packet group record");
99 125 : gmpp_io_exception_tag =
100 125 : gmpx_malloc_block_create(sizeof(gmpp_io_exception),
101 : "GMP I/O exception record");
102 125 : gmpp_initialized = TRUE;
103 : }
104 125 : }
105 :
106 :
107 : /*
108 : * gmpp_create_group_record
109 : *
110 : * Create a group record, initialize it, and link it into the packet.
111 : *
112 : * Returns a pointer to the group record, or NULL if out of memory.
113 : */
114 : gmp_report_group_record *
115 140 : gmpp_create_group_record (gmp_report_packet *report_packet, void *group_id,
116 : const uint8_t *group_addr, uint32_t addr_len)
117 : {
118 : gmp_report_group_record *group_record;
119 :
120 : /* Allocate the block. */
121 :
122 140 : group_record = gmpx_malloc_block(gmpp_group_record_tag);
123 140 : if (!group_record)
124 0 : return NULL; /* Out of memory */
125 :
126 : /* Initialize it. */
127 :
128 140 : group_record->gmp_rpt_group_id = group_id;
129 140 : memmove(group_record->gmp_rpt_group.gmp_addr, group_addr, addr_len);
130 :
131 : /* Put it into the thread. */
132 :
133 140 : thread_circular_add_bottom(&report_packet->gmp_report_group_head,
134 : &group_record->gmp_rpt_thread);
135 140 : report_packet->gmp_report_group_count++;
136 :
137 140 : return group_record;
138 : }
139 :
140 :
141 : /*
142 : * gmpp_create_packet_header
143 : *
144 : * Create a packet header.
145 : *
146 : * Returns a pointer to the packet header, or NULL if out of memory.
147 : */
148 : gmp_packet *
149 1055 : gmpp_create_packet_header (gmp_version version, gmp_message_type message_type,
150 : gmp_proto proto)
151 : {
152 : gmp_packet *packet;
153 : gmp_report_packet *report_packet;
154 :
155 1055 : packet = gmpx_malloc_block(gmpp_packet_header_tag);
156 1055 : if (packet) {
157 :
158 : /* Initialize it. */
159 :
160 1055 : packet->gmp_packet_version = version;
161 1055 : packet->gmp_packet_type = message_type;
162 1055 : packet->gmp_packet_proto = proto;
163 :
164 1055 : switch (message_type) {
165 :
166 919 : case GMP_QUERY_PACKET:
167 919 : break;
168 :
169 136 : case GMP_REPORT_PACKET:
170 136 : report_packet = &packet->gmp_packet_contents.gmp_packet_report;
171 136 : thread_new_circular_thread(&report_packet->gmp_report_group_head);
172 136 : break;
173 :
174 0 : default:
175 0 : gmpx_assert(FALSE);
176 : }
177 : }
178 :
179 1055 : return packet;
180 : }
181 :
182 :
183 : /*
184 : * gmpp_destroy_packet
185 : *
186 : * Destroy a generic packet. We clean up the contents and free it.
187 : *
188 : * Note that the address list pointers in query packets and group records
189 : * point to address lists owned elsewhere, and are not freed. The address
190 : * threads pointed to are destroyed, however.
191 : */
192 : void
193 1055 : gmpp_destroy_packet (gmp_packet *packet)
194 : {
195 : gmp_report_group_record *group_record;
196 : gmp_report_packet *report_packet;
197 : gmp_query_packet *query_packet;
198 : task_thread *thread_ptr;
199 :
200 : /* Clean up based on the packet type. */
201 :
202 1055 : switch (packet->gmp_packet_type) {
203 :
204 919 : case GMP_QUERY_PACKET:
205 :
206 : /* Query packet. Delete the address thread. */
207 :
208 919 : query_packet = &packet->gmp_packet_contents.gmp_packet_query;
209 919 : gmp_destroy_addr_thread(query_packet->gmp_query_rcv_srcs);
210 919 : break;
211 :
212 136 : case GMP_REPORT_PACKET:
213 :
214 : /* Report packet, toss all of the group records. */
215 :
216 136 : report_packet = &packet->gmp_packet_contents.gmp_packet_report;
217 :
218 : /* Walk all group records, and free them. */
219 :
220 : while (TRUE) {
221 140 : thread_ptr =
222 276 : thread_circular_top(&report_packet->gmp_report_group_head);
223 276 : if (!thread_ptr)
224 136 : break;
225 140 : thread_remove(thread_ptr);
226 140 : group_record = gmp_thread_to_report_group_record(thread_ptr);
227 140 : gmp_destroy_addr_thread(group_record->gmp_rpt_rcv_srcs);
228 140 : gmpx_free_block(gmpp_group_record_tag, group_record);
229 : }
230 136 : break;
231 :
232 0 : default:
233 0 : gmpx_assert(FALSE);
234 : break;
235 : }
236 :
237 : /* Now toss the packet header and we're done. */
238 :
239 1055 : gmpx_free_block(gmpp_packet_header_tag, packet);
240 1055 : }
241 :
242 :
243 : /*
244 : * gmpp_start_xmit
245 : *
246 : * Initiate transmission on an interface.
247 : */
248 : void
249 2627 : gmpp_start_xmit (gmp_role role, gmp_proto proto, gmpx_intf_id intf_id)
250 : {
251 : gmpp_io_context *io_ctx;
252 : gmpp_io_exception *io_except;
253 : patnode *node;
254 :
255 : /* Validate the parameters. */
256 :
257 2627 : gmpx_assert(role < GMP_NUM_ROLES);
258 2627 : gmpx_assert(proto < GMP_NUM_PROTOS);
259 :
260 : /* Call the registered callback. */
261 :
262 2627 : io_ctx = &gmpp_io_context_block[proto][role];
263 2627 : gmpx_assert(io_ctx->io_ctx_xmitready_cb);
264 :
265 : /* See if there's an exception for this interface. If so, use it. */
266 :
267 2627 : node = gmpx_patricia_lookup(io_ctx->io_ctx_exceptions, &intf_id);
268 2627 : io_except = gmpp_patnode_to_io_ex(node);
269 :
270 2627 : if (io_except) {
271 0 : (*io_except->io_ctx_alt_cb)(role, proto, intf_id);
272 : } else {
273 :
274 : /* No exception. use the general callback. */
275 :
276 2627 : (*io_ctx->io_ctx_xmitready_cb)(role, proto, intf_id);
277 : }
278 2627 : }
279 :
280 :
281 : /*
282 : * gmpp_register
283 : *
284 : * Register a client.
285 : */
286 : void
287 125 : gmpp_register (gmp_role role, gmp_xmit_callback_func xmit_callback,
288 : gmp_rcv_callback_func rcv_callback,
289 : gmp_group_done_callback_func group_done_callback,
290 : gmp_packet_free_callback_func packet_free_callback)
291 : {
292 : gmpp_context *ctx;
293 :
294 : /* Make sure the role is in range. */
295 :
296 125 : gmpx_assert(role < GMP_NUM_ROLES);
297 :
298 125 : ctx = &gmpp_context_block[role];
299 :
300 : /* Make sure we're not getting any duplicates. */
301 :
302 125 : gmpx_assert(ctx->ctx_xmit_cb == NULL);
303 :
304 : /* Squirrel away all of the context. */
305 :
306 125 : ctx->ctx_xmit_cb = xmit_callback;
307 125 : ctx->ctx_rcv_cb = rcv_callback;
308 125 : ctx->ctx_group_cb = group_done_callback;
309 125 : ctx->ctx_pkt_free_cb = packet_free_callback;
310 125 : }
311 :
312 :
313 : /*
314 : * gmpp_deregister
315 : *
316 : * Deregister a client.
317 : */
318 : void
319 125 : gmpp_deregister (gmp_role role)
320 : {
321 : gmpp_context *ctx;
322 :
323 : /* Make sure the role is in range. */
324 :
325 125 : gmpx_assert(role < GMP_NUM_ROLES);
326 :
327 125 : ctx = &gmpp_context_block[role];
328 :
329 : /* It better have been registered. */
330 :
331 125 : gmpx_assert(ctx->ctx_xmit_cb);
332 :
333 : /* Zap the context block. */
334 :
335 125 : memset(ctx, 0, sizeof(gmpp_context));
336 125 : }
337 :
338 :
339 : /*
340 : * gmpp_enab_disab_proto
341 : *
342 : * Enable or disable protocol processing for a client.
343 : */
344 : void
345 250 : gmpp_enab_disab_proto (gmp_role role, gmp_proto proto, boolean enabled)
346 : {
347 : /* Ensure that everything is in range. */
348 :
349 250 : gmpx_assert(role < GMP_NUM_ROLES && proto < GMP_NUM_PROTOS);
350 :
351 : /* Set the flag appropriately. */
352 :
353 250 : gmpp_context_block[role].ctx_proto_active[proto] = enabled;
354 250 : }
355 :
356 :
357 : /*
358 : * gmp_register_io
359 : *
360 : * Register packet I/O. This is called by the protocol formatting routines
361 : * to register themselves.
362 : */
363 : void
364 125 : gmp_register_io (gmp_role role, gmp_proto proto,
365 : gmpp_xmit_ready_func xmit_ready)
366 : {
367 : gmpp_io_context *io_ctx;
368 :
369 : /* Validate the parameters. */
370 :
371 125 : gmpx_assert(role < GMP_NUM_ROLES);
372 125 : gmpx_assert(proto < GMP_NUM_PROTOS);
373 :
374 : /* Set the context appropriately. */
375 :
376 125 : io_ctx = &gmpp_io_context_block[proto][role];
377 :
378 : /* Set the transmit-ready callback. */
379 :
380 125 : io_ctx->io_ctx_xmitready_cb = xmit_ready;
381 :
382 : /* Set up the patricia tree for exception interfaces. */
383 :
384 125 : if (!io_ctx->io_ctx_exceptions) {
385 125 : io_ctx->io_ctx_exceptions =
386 125 : gmpx_patroot_init(sizeof(gmpx_intf_id),
387 : GMPX_PATRICIA_OFFSET(gmpp_io_exception,
388 : io_ctx_node, io_ctx_intf));
389 : }
390 125 : }
391 :
392 :
393 : /*
394 : * gmp_register_io_exception
395 : *
396 : * Register an exception to the I/O transmit callback for a particular
397 : * interface. Transmit-ready notifications on the indicated interface
398 : * will go to the exception routine instead of the normal one.
399 : */
400 : void
401 0 : gmp_register_io_exception (gmp_role role, gmp_proto proto,
402 : gmpx_intf_id intf_id,
403 : gmpp_xmit_ready_func xmit_ready)
404 : {
405 : gmpp_io_context *io_ctx;
406 : gmpp_io_exception *io_except;
407 : gmpx_patnode *node;
408 :
409 : /*
410 : * We may get called before initialization has taken place. Cheat
411 : * and try to initialize now (it'll be a no-op if this has already
412 : * taken place.)
413 : */
414 0 : gmpp_init();
415 :
416 : /* Validate the parameters. */
417 :
418 0 : gmpx_assert(role < GMP_NUM_ROLES);
419 0 : gmpx_assert(proto < GMP_NUM_PROTOS);
420 :
421 : /* Get the context block. */
422 :
423 0 : io_ctx = &gmpp_io_context_block[proto][role];
424 :
425 : /* We better have already initialized it. */
426 :
427 0 : gmpx_assert(io_ctx->io_ctx_exceptions);
428 :
429 : /* Look up the interface. Use it if it's there. */
430 :
431 0 : node = gmpx_patricia_lookup(io_ctx->io_ctx_exceptions, &intf_id);
432 0 : io_except = gmpp_patnode_to_io_ex(node);
433 :
434 : /* If there's no exception block, allocate it and link it in. */
435 :
436 0 : if (!io_except) {
437 0 : io_except = gmpx_malloc_block(gmpp_io_exception_tag);
438 0 : io_except->io_ctx_intf = intf_id;
439 0 : gmpx_assert(gmpx_patricia_add(io_ctx->io_ctx_exceptions,
440 : &io_except->io_ctx_node));
441 : }
442 :
443 : /* Fill in the field. */
444 :
445 0 : io_except->io_ctx_alt_cb = xmit_ready;
446 0 : }
447 :
448 :
449 : /*
450 : * gmp_register_peek_function
451 : *
452 : * Register a transmit peek function for a client. This routine is called
453 : * whenever the client engine generates a generic packet to send.
454 : */
455 : void
456 125 : gmp_register_peek_function (gmp_role role,
457 : gmp_xmit_peek_callback_func xmit_peek_cb,
458 : gmp_rcv_peek_callback_func rcv_peek_cb)
459 : {
460 125 : gmpp_context_block[role].ctx_xmit_peek_cb = xmit_peek_cb;
461 125 : gmpp_context_block[role].ctx_rcv_peek_cb = rcv_peek_cb;
462 125 : }
463 :
464 :
465 : /*
466 : * gmpp_next_xmit_packet
467 : *
468 : * Get the next generic packet to transmit, given the interface, protocol,
469 : * and role. Returns a pointer to the generic packet, or NULL if there's
470 : * nothing to send.
471 : *
472 : * The buffer length is passed to the transmit callback to help it figure
473 : * out how many groups can fit within a packet. Zero values are tolerated,
474 : * meaning that the buffer size is unknown; the callback routines will always
475 : * return a single group in that case.
476 : */
477 : gmp_packet *
478 3546 : gmpp_next_xmit_packet (gmp_role role, gmp_proto proto, gmpx_intf_id intf_id,
479 : uint32_t buffer_len)
480 : {
481 : gmpp_context *ctx;
482 : gmp_packet *packet;
483 :
484 3546 : gmpx_assert(role < GMP_NUM_ROLES);
485 3546 : gmpx_assert(proto < GMP_NUM_PROTOS);
486 :
487 : /* Look up the context block. */
488 :
489 3546 : ctx = &gmpp_context_block[role];
490 3546 : gmpx_assert(ctx->ctx_proto_active[proto]);
491 :
492 : /* Call the callback to do the job. */
493 :
494 3546 : packet = (*ctx->ctx_xmit_cb)(intf_id, proto, buffer_len);
495 :
496 : /* If there is a transmit peek function, let it peek at the packet. */
497 :
498 3546 : if (packet && ctx->ctx_xmit_peek_cb)
499 919 : (*ctx->ctx_xmit_peek_cb)(intf_id, proto, packet);
500 :
501 3546 : return packet;
502 : }
503 :
504 :
505 : /*
506 : * gmpp_group_done
507 : *
508 : * Called by the protocol-specific routine to indicate that it is done
509 : * processing a group. We call the client engine callback routine in turn.
510 : */
511 : void
512 44 : gmpp_group_done (gmp_role role, gmp_proto proto, void *group_id)
513 : {
514 : gmpp_context *ctx;
515 :
516 44 : gmpx_assert(role < GMP_NUM_ROLES);
517 44 : gmpx_assert(proto < GMP_NUM_PROTOS);
518 :
519 44 : ctx = &gmpp_context_block[role];
520 44 : gmpx_assert(ctx->ctx_proto_active[proto]);
521 :
522 44 : (*ctx->ctx_group_cb)(group_id);
523 44 : }
524 :
525 :
526 : /*
527 : * gmpp_packet_done
528 : *
529 : * Called by the protocol-specific routine to indicate that it is done
530 : * with a packet. We call the client engine callback routine in turn.
531 : */
532 : void
533 919 : gmpp_packet_done (gmp_role role, gmp_proto proto, gmp_packet *packet)
534 : {
535 : gmpp_context *ctx;
536 :
537 919 : gmpx_assert(role < GMP_NUM_ROLES);
538 919 : gmpx_assert(proto < GMP_NUM_PROTOS);
539 :
540 919 : ctx = &gmpp_context_block[role];
541 919 : gmpx_assert(ctx->ctx_proto_active[proto]);
542 :
543 919 : (*ctx->ctx_pkt_free_cb)(packet);
544 919 : }
545 :
546 :
547 : /*
548 : * gmpp_process_rcv_packet
549 : *
550 : * Called by the protocol-specific routine when a packet is received.
551 : * The packet parsed cleanly, and we're passed a generic packet. We
552 : * pass it to each client, and to any registered peek routine.
553 : */
554 : void
555 136 : gmpp_process_rcv_packet (gmp_packet *packet, gmpx_intf_id intf_id)
556 : {
557 : gmp_role role;
558 : gmpp_context *ctx;
559 :
560 136 : gmpx_assert(packet->gmp_packet_proto < GMP_NUM_PROTOS);
561 :
562 408 : for (role = 0; role < GMP_NUM_ROLES; role++) {
563 272 : ctx = &gmpp_context_block[role];
564 272 : if (ctx->ctx_proto_active[packet->gmp_packet_proto]) {
565 136 : if (ctx->ctx_rcv_peek_cb) {
566 136 : (*ctx->ctx_rcv_peek_cb)(intf_id, packet->gmp_packet_proto,
567 : packet);
568 : }
569 136 : (*ctx->ctx_rcv_cb)(intf_id, packet);
570 : }
571 : }
572 136 : }
573 :
574 :
575 : /*
576 : * gmpp_max_group_count
577 : *
578 : * Get the max group count possible for a packet, given the protocol,
579 : * version, packet type, and buffer length.
580 : *
581 : * This is calculated based on having no sources, so it may well return
582 : * a number larger than what the packet can actually carry. This is OK,
583 : * as this is simply providing an upper bound for the packet building code,
584 : * which will only put into the packet what will fit.
585 : *
586 : * This doesn't exactly belong here, since it is multi-protocol specific
587 : * rather than generic, but by putting it here we can still compile only
588 : * MLD or IGMP without difficulty.
589 : *
590 : * If the buffer length is zero, we always return one group.
591 : */
592 : uint32_t
593 0 : gmpp_max_group_count (gmp_proto proto, gmp_version version,
594 : gmp_message_type msg_type, uint32_t buffer_len)
595 : {
596 : uint32_t overhead;
597 : uint32_t group_len;
598 : uint32_t max_group_count;
599 :
600 : /*
601 : * The only packets that carry more than one group are IGMPv3/
602 : * MLDv2 Report packets. Bail on the others first, and bail if
603 : * the buffer length is zero (meaning that we should always return
604 : * one group.)
605 : */
606 0 : if (version != GMP_VERSION_SOURCES || msg_type != GMP_REPORT_PACKET ||
607 : !buffer_len) {
608 0 : return 1;
609 : }
610 :
611 : /*
612 : * It's a sources-version report packet. Load up the overhead and
613 : * per-group cost based on the protocol.
614 : */
615 0 : switch (proto) {
616 0 : case GMP_PROTO_IGMP:
617 0 : overhead = sizeof(igmp_v3_report);
618 0 : group_len = sizeof(igmp_v3_rpt_rcrd);
619 0 : break;
620 :
621 0 : case GMP_PROTO_MLD:
622 0 : overhead = sizeof(mld_v2_report);
623 0 : group_len = sizeof(mld_v2_rpt_rcrd);
624 0 : break;
625 :
626 0 : default:
627 0 : overhead = 0; /* Satisfy the compiler. */
628 0 : group_len = 0;
629 0 : gmpx_assert(FALSE);
630 : break;
631 : }
632 :
633 : /* Got the parameters. Do the simple calculation. */
634 :
635 0 : gmpx_assert(buffer_len >= group_len + overhead);
636 0 : max_group_count = (buffer_len - overhead) / group_len;
637 :
638 0 : return max_group_count;
639 : }
|