Line data Source code
1 : /* $Id: gmpr_client.c 374940 2010-04-20 04:55:18Z weesan $
2 : *
3 : * gmpr_client.c - IGMP/MLD Router-Side Client Routines
4 : *
5 : * Dave Katz, March 2008
6 : *
7 : * Copyright (c) 2008, Juniper Networks, Inc.
8 : * All rights reserved.
9 : */
10 :
11 : /*
12 : * A note on the notification mechanism
13 : *
14 : * In order to keep state bounded, we provide notifications to the
15 : * clients by threading output groups and/or sources onto those
16 : * clients' notification threads. When the client calls
17 : * gmpr_client_get_notification(), we allocate client notification
18 : * blocks and pass them back to the client (with at most one client
19 : * notification block outstanding, so as to not grow memory.)
20 : *
21 : * There are two types of notifications--full notifications and
22 : * deltas. Full notifications always contain the group and all
23 : * sources in one structure. Deltas return per-source changes as
24 : * appropriate.
25 : *
26 : * The gmpr engine assumes that deltas are always in effect, and only
27 : * threads groups and/or sources that are changing. If only delta
28 : * notifications are being requested, these groups and sources are
29 : * translated one-to-one into notifications in the obvious way.
30 : *
31 : * If full notifications are in effect, the group send_full_notif flag
32 : * is set for the group, regardless of whether the entity being
33 : * enqueued is a group or a source. When notifications are generated,
34 : * this flag is used to trigger the delivery of a full notification.
35 : *
36 : * If *only* full notifications are in effect, the group is always
37 : * enqueued rather than the source, for efficiency (since we don't
38 : * care about the status of individual sources.)
39 : *
40 : * The net result of all of this is that a bunch of deltas enqueued
41 : * synchronously (on the receipt of a new Report with multiple
42 : * sources, for instance) will result in only a single full
43 : * notification being passed.
44 : */
45 :
46 : #include "gmpx_basic_types.h"
47 : #include "gmp.h"
48 : #include "gmpx_environment.h"
49 : #include "gmp_externs.h"
50 : #include "gmp_private.h"
51 : #include "gmp_router.h"
52 : #include "gmpr_private.h"
53 : #include "gmp_trace.h"
54 : #include "gmpr_trace.h"
55 :
56 : /* Forward references... */
57 :
58 : static void gmpr_flush_notifications_client(gmpr_client *client);
59 :
60 :
61 : /*
62 : * gmpr_get_client
63 : *
64 : * Return an client pointer, given a client ID.
65 : *
66 : * Verifies that the client ID is valid.
67 : */
68 : gmpr_client *
69 747 : gmpr_get_client (gmp_client_id client_id)
70 : {
71 : gmpr_client *client;
72 :
73 : /* Do the (trivial) conversion. */
74 :
75 747 : client = client_id;
76 :
77 : /* Verify the magic number. */
78 :
79 747 : gmpx_assert(client->rclient_magic == GMPR_CLIENT_MAGIC);
80 :
81 747 : return client;
82 : }
83 :
84 :
85 : /*
86 : * gmpr_client_startup_expiry
87 : *
88 : * Called when the client startup timer expires. We enqueue everything
89 : * for the client.
90 : */
91 : static void
92 125 : gmpr_client_startup_expiry (gmpx_timer *timer, void *context)
93 : {
94 : gmpr_client *client;
95 :
96 125 : client = context;
97 :
98 125 : gmpx_destroy_timer(timer);
99 125 : client->rclient_startup_timer = NULL;
100 :
101 : /* Enqueue everything for the client. */
102 :
103 125 : gmpr_client_enqueue_all_groups(client, TRUE);
104 125 : gmpr_alert_clients(client->rclient_instance);
105 125 : gmpr_client_enqueue_all_host_groups(client);
106 125 : gmpr_alert_host_clients(client->rclient_instance);
107 125 : }
108 :
109 :
110 : /*
111 : * gmpr_create_client
112 : *
113 : * Create a client entry.
114 : *
115 : * Returns a pointer to the client entry, or NULL if no memory.
116 : */
117 : gmpr_client *
118 125 : gmpr_create_client (gmpr_instance *instance)
119 : {
120 : gmpr_client *client;
121 : ordinal_t next_ord;
122 :
123 : /* Grab the next ordinal. */
124 :
125 125 : next_ord = ord_get_ordinal(instance->rinst_ord_handle);
126 125 : if (next_ord == ORD_BAD_ORDINAL)
127 0 : return NULL; /* Out of memory */
128 :
129 : /* If we've got too many clients, bail. */
130 :
131 125 : if (next_ord >= GMPX_MAX_RTR_CLIENTS) {
132 0 : ord_free_ordinal(instance->rinst_ord_handle, next_ord);
133 0 : return NULL; /* Too many clients */
134 : }
135 :
136 : /* Allocate a client block. */
137 :
138 125 : client = gmpx_malloc_block(gmpr_client_tag);
139 125 : if (!client) /* No memory */
140 0 : return NULL;
141 :
142 : /* Link the client into the instance. */
143 :
144 125 : client->rclient_magic = GMPR_CLIENT_MAGIC;
145 125 : thread_circular_add_top(&instance->rinst_client_thread,
146 : &client->rclient_thread);
147 125 : client->rclient_instance = instance;
148 125 : client->rclient_ordinal = next_ord;
149 :
150 : /* Initialize the notification threads. */
151 :
152 125 : thread_new_circular_thread(&client->rclient_notif_head);
153 125 : thread_new_circular_thread(&client->rclient_host_notif_head);
154 :
155 : /* Initialize the end-of-refresh notification block. */
156 :
157 125 : client->rclient_refresh_end_notif.gmpr_notify_type =
158 : GMPR_NOTIFY_REFRESH_END;
159 :
160 : /*
161 : * Create a timer and launch it with a zero delay. This is a cheap
162 : * way of deferring. The callback will enqueue all notifications for
163 : * the client.
164 : */
165 125 : client->rclient_startup_timer =
166 125 : gmpx_create_timer(instance->rinst_context, "GMP client startup_timer",
167 : gmpr_client_startup_expiry, client);
168 125 : if (client->rclient_startup_timer)
169 125 : gmpx_start_timer(client->rclient_startup_timer, 0, 0);
170 :
171 125 : return client;
172 : }
173 :
174 :
175 : /*
176 : * gmpr_destroy_client
177 : *
178 : * Destroy a client entry. Cleans up appropriately.
179 : */
180 : void
181 125 : gmpr_destroy_client (gmpr_client *client)
182 : {
183 : gmpr_instance *instance;
184 :
185 125 : instance = client->rclient_instance;
186 :
187 : /* Free the ordinal. */
188 :
189 125 : ord_free_ordinal(instance->rinst_ord_handle, client->rclient_ordinal);
190 :
191 : /* Flush the notification lists. */
192 :
193 125 : gmpr_flush_notifications_client(client);
194 125 : gmpr_flush_host_notifications_client(client);
195 :
196 : /* Destroy any timers. */
197 :
198 125 : gmpx_destroy_timer(client->rclient_startup_timer);
199 :
200 : /* Delink the block and free it. */
201 :
202 125 : thread_remove(&client->rclient_thread);
203 125 : client->rclient_instance = NULL;
204 125 : gmpx_free_block(gmpr_client_tag, client);
205 125 : }
206 :
207 :
208 : /*
209 : * gmpr_destroy_instance_clients
210 : *
211 : * Destroy all clients on an instance.
212 : */
213 : void
214 125 : gmpr_destroy_instance_clients (gmpr_instance *instance)
215 : {
216 : task_thread *thread_ptr;
217 : gmpr_client *client;
218 :
219 : /* Walk all clients on the instance. */
220 :
221 : while (TRUE) {
222 125 : thread_ptr = thread_circular_top(&instance->rinst_client_thread);
223 125 : client = gmpr_thread_to_client(thread_ptr);
224 125 : if (!thread_ptr)
225 125 : break;
226 :
227 : /* Destroy the client. */
228 :
229 0 : gmpr_destroy_client(client);
230 : }
231 125 : }
232 :
233 :
234 : /*
235 : * gmpr_notifications_active
236 : *
237 : * Returns TRUE if there are any active notifications on this
238 : * notification block array, or FALSE if not.
239 : */
240 : boolean
241 422 : gmpr_notifications_active (gmpr_notify_block *notify_block)
242 : {
243 : uint32_t client_ord;
244 :
245 : /* Walk the notification array. */
246 :
247 940 : for (client_ord = 0; client_ord < GMPX_MAX_RTR_CLIENTS; client_ord++) {
248 681 : if (thread_node_on_thread(¬ify_block->gmpr_notify_thread))
249 163 : return TRUE;
250 518 : notify_block++;
251 : }
252 :
253 259 : return FALSE;
254 : }
255 :
256 :
257 : /*
258 : * gmpr_source_notifications_active
259 : *
260 : * Returns TRUE if there are any active notifications on this source, or
261 : * FALSE if not.
262 : */
263 : static boolean
264 27 : gmpr_source_notifications_active (gmpr_ogroup_addr_entry *group_addr)
265 : {
266 27 : return gmpr_notifications_active(group_addr->rogroup_addr_client_thread);
267 : }
268 :
269 :
270 : /*
271 : * gmpr_attempt_free_deleted_addr_entry
272 : *
273 : * Attempt to free a deleted address entry. We assume that it is on
274 : * the group deleted list.
275 : *
276 : * The entry is free if there are no pending notifications left.
277 : *
278 : * returns TRUE if the gmpr_ogroup_addr_entry was freed. Otherwise FALSE.
279 : */
280 : static boolean
281 27 : gmpr_attempt_free_deleted_addr_entry (gmpr_ogroup_addr_entry *group_addr)
282 : {
283 : gmpr_ogroup *group;
284 27 : boolean deleted_addr_entry = FALSE;
285 :
286 27 : group = group_addr->rogroup_addr_group;
287 :
288 : /* Do it if there are no active notifications. */
289 :
290 27 : if (!gmpr_source_notifications_active(group_addr)) {
291 27 : deleted_addr_entry = TRUE;
292 27 : gmp_delete_addr_list_entry(&group_addr->rogroup_addr_entry);
293 :
294 : /* Try to free the group as well. */
295 :
296 27 : gmpr_attempt_ogroup_free(group);
297 : }
298 27 : return deleted_addr_entry;
299 : }
300 :
301 :
302 : /*
303 : * gmpr_delete_notification
304 : *
305 : * Delete a client notification, it having been removed from the
306 : * client notification list. Notifications aren't actually "deleted"
307 : * since they are embedded in other data structures. But we do any
308 : * necessary cleanup.
309 : *
310 : * return true is the gmpr_notify_block memory has been freed. This will
311 : * be true if the outer structure that the gmpr_notify_block is embedded
312 : * in is freed. Otherwise return false.
313 : */
314 : static boolean
315 118 : gmpr_delete_notification (gmpr_notify_block *notification,
316 : ordinal_t client_ord)
317 : {
318 : gmpr_ogroup *group;
319 : gmpr_ogroup_addr_entry *group_addr;
320 118 : boolean notify_block_freed = FALSE;
321 :
322 : /* See whether it is a group or source notification. */
323 :
324 118 : switch (notification->gmpr_notify_type) {
325 58 : case GMPR_NOTIFY_GROUP:
326 :
327 : /*
328 : * Group notification. Try to free the group, as we may have just
329 : * cleaned up the last thing keeping the group alive.
330 : */
331 58 : group = gmpr_client_notification_to_group(notification, client_ord);
332 58 : notify_block_freed = gmpr_attempt_ogroup_free(group);
333 58 : break;
334 :
335 60 : case GMPR_NOTIFY_SOURCE:
336 :
337 : /*
338 : * Source notification. If the entry is on the deleted list
339 : * (meaning that we're done with it other than notifications),
340 : * try to free the address entry if it is no longer on any
341 : * client notification list.
342 : */
343 60 : group_addr = gmpr_client_notification_to_addr_entry(notification,
344 : client_ord);
345 60 : if (gmpr_group_addr_deleted(group_addr)) {
346 : notify_block_freed =
347 27 : gmpr_attempt_free_deleted_addr_entry(group_addr);
348 : }
349 60 : break;
350 :
351 0 : case GMPR_NOTIFY_REFRESH_END:
352 :
353 : /* Refresh end. Nothing to do. */
354 :
355 0 : break;
356 :
357 0 : default:
358 0 : gmpx_assert(FALSE);
359 : }
360 118 : return notify_block_freed;
361 : }
362 :
363 :
364 : /*
365 : * gmpr_flush_notifications
366 : *
367 : * Flush all pending client notifications on a notification block array.
368 : *
369 : * If just_delink is TRUE, we simply delink the entries. If FALSE, we
370 : * call gmpr_delete_notification to try to clean up whatever the
371 : * client has embedded.
372 : */
373 : void
374 70 : gmpr_flush_notifications (gmpr_notify_block *notify_block, boolean just_delink)
375 : {
376 : ordinal_t client_ord;
377 : boolean nb_freed;
378 :
379 210 : for (client_ord = 0; client_ord < GMPX_MAX_RTR_CLIENTS; client_ord++) {
380 140 : if (thread_node_on_thread(¬ify_block->gmpr_notify_thread)) {
381 0 : thread_remove(¬ify_block->gmpr_notify_thread);
382 0 : if (!just_delink){
383 0 : nb_freed = gmpr_delete_notification(notify_block, client_ord);
384 0 : if (nb_freed){
385 0 : return;
386 : }
387 : }
388 : }
389 140 : notify_block++;
390 : }
391 : }
392 :
393 :
394 : /*
395 : * gmpr_flush_notifications_group_list
396 : *
397 : * Flush all pending client source notifications on a group address list.
398 : */
399 : static void
400 99 : gmpr_flush_notifications_group_list (gmp_addr_list *addr_list)
401 : {
402 : gmp_addr_list_entry *addr_entry, *addr_entry_next;
403 : gmpr_ogroup_addr_entry *group_addr;
404 :
405 99 : addr_entry = gmp_addr_list_next_entry(addr_list, NULL);
406 99 : addr_entry_next = NULL;
407 : /* Walk the list. */
408 :
409 : while (TRUE) {
410 99 : if (addr_entry)
411 0 : addr_entry_next = gmp_addr_list_next_entry(addr_list, addr_entry);
412 :
413 99 : group_addr = gmpr_addr_entry_to_ogroup_entry(addr_entry);
414 99 : if (!group_addr)
415 99 : break;
416 :
417 : /* Got an entry. Delink it from each client. */
418 :
419 0 : gmpr_flush_notifications(group_addr->rogroup_addr_client_thread,
420 : FALSE);
421 0 : addr_entry = addr_entry_next;
422 : }
423 99 : }
424 :
425 :
426 : /*
427 : * gmpr_flush_notifications_group
428 : *
429 : * Flush all pending client source notifications for a group. Note that it
430 : * does not remove the group itself from any notification list if it happens
431 : * to be there.
432 : */
433 : void
434 33 : gmpr_flush_notifications_group (gmpr_ogroup *ogroup)
435 : {
436 : /* Flush each of the lists where notifications may lie. */
437 :
438 33 : gmpr_flush_notifications_group_list(&ogroup->rogroup_incl_src_addr);
439 33 : gmpr_flush_notifications_group_list(&ogroup->rogroup_excl_src_addr);
440 33 : gmpr_flush_notifications_group_list(&ogroup->rogroup_src_addr_deleted);
441 33 : }
442 :
443 :
444 : /*
445 : * gmpr_flush_notifications_client
446 : *
447 : * Flush all pending notifications for a client.
448 : */
449 : static void
450 125 : gmpr_flush_notifications_client (gmpr_client *client)
451 : {
452 : gmpr_notify_block *notification;
453 :
454 : task_thread *thread_ptr;
455 :
456 : /* Walk the client notification list. */
457 :
458 : while (TRUE) {
459 125 : thread_ptr = thread_circular_dequeue_top(&client->rclient_notif_head);
460 125 : notification = gmpr_thread_to_notify_block(thread_ptr);
461 125 : if (!notification)
462 125 : break;
463 :
464 : /* Got a notification. Delete it. */
465 :
466 0 : gmpr_delete_notification(notification, client->rclient_ordinal);
467 : }
468 125 : }
469 :
470 :
471 : /*
472 : * gmpr_update_client_notify
473 : *
474 : * Update the notify-client flag in advance of starting to enqueue
475 : * notifications. We set it if it was previously clear, and if the
476 : * notification queue is currently empty. The net effect is that we
477 : * set it when enqueueing the first notification.
478 : */
479 : static void
480 140 : gmpr_update_client_notify (gmpr_client *client)
481 : {
482 140 : if (!client->rclient_notify) {
483 82 : client->rclient_notify =
484 82 : thread_circular_thread_empty(&client->rclient_notif_head);
485 82 : gmpr_trace(client->rclient_instance, GMPR_TRACE_CLIENT_NOTIFY,
486 : "Client %u notify set to %u", client->rclient_ordinal,
487 : client->rclient_notify);
488 : }
489 140 : }
490 :
491 :
492 : /*
493 : * gmpr_enqueue_refresh_end
494 : *
495 : * Enqueue a refresh end marker for a client. We use a notification
496 : * block embedded in the client block to carry it. We dequeue it and
497 : * move it to the end, in the off chance that it is already enqueued.
498 : */
499 : void
500 0 : gmpr_enqueue_refresh_end (gmpr_client *client)
501 : {
502 : gmpr_notify_block *notif;
503 :
504 : /* Grab the block, based on the client ID. */
505 :
506 0 : notif = &client->rclient_refresh_end_notif;
507 :
508 : /* Dequeue it, just in case. */
509 :
510 0 : thread_remove(¬if->gmpr_notify_thread);
511 :
512 : /* Update the notification flag. */
513 :
514 0 : gmpr_update_client_notify(client);
515 :
516 : /* Enqueue it. */
517 :
518 0 : thread_circular_add_bottom(&client->rclient_notif_head,
519 : ¬if->gmpr_notify_thread);
520 0 : }
521 :
522 :
523 : /*
524 : * gmpr_client_enqueue_group
525 : *
526 : * Enqueue one output group onto a client notification thread.
527 : *
528 : * If it was already enqueued, it is delinked and moved to the end.
529 : */
530 : static void
531 80 : gmpr_client_enqueue_group (gmpr_client *client, gmpr_ogroup *group)
532 : {
533 : ordinal_t client_ord;
534 : task_thread *thread_ptr;
535 :
536 : /*
537 : * Bail if the client startup timer is running. We'll be doing a full
538 : * state enqueue when it expires.
539 : */
540 80 : if (client->rclient_startup_timer)
541 0 : return;
542 :
543 : /* Note if the client's notification thread was empty. */
544 :
545 80 : gmpr_update_client_notify(client);
546 :
547 : /*
548 : * Delink the group from the client task_thread, in case it was already
549 : * on there, and then requeue it at the end.
550 : */
551 80 : client_ord = client->rclient_ordinal;
552 80 : thread_ptr = &group->rogroup_client_thread[client_ord].gmpr_notify_thread;
553 80 : thread_remove(thread_ptr);
554 80 : thread_circular_add_bottom(&client->rclient_notif_head, thread_ptr);
555 :
556 : /* If we're doing full notifications, flag that we need one. */
557 :
558 80 : if (client->rclient_cb_context.rctx_full_notifications)
559 0 : group->rogroup_send_full_notif[client->rclient_ordinal] = TRUE;
560 : }
561 :
562 :
563 : /*
564 : * gmpr_client_enqueue_source
565 : *
566 : * Enqueue one source address onto a client notification thread.
567 : *
568 : * If it was already enqueued, it is delinked and moved to the end.
569 : *
570 : * If we're doing only full notifications (and no deltas) we instead
571 : * enqueue the group. The net result of this is that the notification
572 : * thread will consist solely of group entries (and no sources.)
573 : */
574 : static void
575 60 : gmpr_client_enqueue_source (gmpr_client *client,
576 : gmpr_ogroup_addr_entry *group_addr)
577 : {
578 : gmpr_ogroup *group;
579 : ordinal_t client_ord;
580 : task_thread *thread_ptr;
581 :
582 : /*
583 : * If we're only doing full notifications, enqueue the group instead
584 : * and bail.
585 : */
586 60 : if (client->rclient_cb_context.rctx_full_notifications &&
587 0 : (!client->rclient_cb_context.rctx_delta_notifications)) {
588 0 : gmpr_client_enqueue_group(client, group_addr->rogroup_addr_group);
589 0 : return;
590 : }
591 :
592 : /*
593 : * Bail if the client startup timer is running. We'll be doing a full
594 : * state enqueue when it expires.
595 : */
596 60 : if (client->rclient_startup_timer)
597 0 : return;
598 :
599 : /* Note if the client's notification thread was empty. */
600 :
601 60 : gmpr_update_client_notify(client);
602 :
603 : /*
604 : * Delink the group from the client thread, in case it was already
605 : * on there, and then requeue it at the end.
606 : */
607 60 : client_ord = client->rclient_ordinal;
608 60 : thread_ptr =
609 : &group_addr->rogroup_addr_client_thread[client_ord].gmpr_notify_thread;
610 60 : thread_remove(thread_ptr);
611 60 : thread_circular_add_bottom(&client->rclient_notif_head, thread_ptr);
612 :
613 : /* If we're doing full notifications, flag that we need one. */
614 :
615 60 : if (client->rclient_cb_context.rctx_full_notifications) {
616 0 : group = group_addr->rogroup_addr_group;
617 0 : group->rogroup_send_full_notif[client->rclient_ordinal] = TRUE;
618 : }
619 : }
620 :
621 :
622 : /*
623 : * gmpr_group_notify_clients
624 : *
625 : * Enqueue a group notification for all clients.
626 : *
627 : * The group is threaded onto the notification thread for each client,
628 : * and a notification callback is made if the thread was previously
629 : * empty.
630 : */
631 : void
632 80 : gmpr_group_notify_clients (gmpr_ogroup *group)
633 : {
634 : gmpr_instance *instance;
635 : gmpr_client *client;
636 : task_thread *thread_ptr;
637 :
638 80 : instance = group->rogroup_intf->rintf_instance;
639 :
640 : /* Walk all clients. */
641 :
642 160 : FOR_ALL_CIRCULAR_THREAD_ENTRIES(&instance->rinst_client_thread,
643 : thread_ptr) {
644 80 : client = gmpr_thread_to_client(thread_ptr);
645 :
646 : /* Enqueue the group. */
647 :
648 80 : gmpr_client_enqueue_group(client, group);
649 : }
650 80 : }
651 :
652 :
653 : /*
654 : * gmpr_source_notify_clients
655 : *
656 : * Notify all clients that a source has changed state.
657 : *
658 : * The "flag" parameter says if the notification is conditional (based
659 : * on the setting of the rogroup_notify flag) or unconditional.
660 : *
661 : * The source is threaded onto the notification thread for each client,
662 : * and a notification callback is made if the thread was previously
663 : * empty.
664 : */
665 : void
666 60 : gmpr_source_notify_clients (gmpr_ogroup_addr_entry *group_addr,
667 : gmpr_source_notify_flag flag)
668 : {
669 : gmpr_instance *instance;
670 : gmpr_client *client;
671 : task_thread *thread_ptr;
672 : boolean client_found;
673 :
674 60 : instance = group_addr->rogroup_addr_group->rogroup_intf->rintf_instance;
675 :
676 : /* Do it if we're supposed to. */
677 :
678 60 : client_found = FALSE;
679 60 : if ((flag == NOTIFY_UNCONDITIONAL) || group_addr->rogroup_notify) {
680 :
681 : /* Walk all clients. */
682 :
683 120 : FOR_ALL_CIRCULAR_THREAD_ENTRIES(&instance->rinst_client_thread,
684 : thread_ptr) {
685 60 : client = gmpr_thread_to_client(thread_ptr);
686 60 : client_found = TRUE;
687 :
688 : /* Enqueue the notification. */
689 :
690 60 : gmpr_client_enqueue_source(client, group_addr);
691 : }
692 : }
693 60 : group_addr->rogroup_notify = FALSE;
694 :
695 : /*
696 : * If there were no clients, go ahead and attempt to free the
697 : * entry if it was deleted. This is a paranoia check, since a
698 : * lingering deleted entry will block ever freeing the group.
699 : */
700 60 : if (!client_found && gmpr_group_addr_deleted(group_addr)) {
701 0 : gmpr_attempt_free_deleted_addr_entry(group_addr);
702 : }
703 60 : }
704 :
705 :
706 : /*
707 : * gmpr_enqueue_all_source_notifications
708 : *
709 : * Enqueue all appropriate source notifications for an output group.
710 : *
711 : * We ignore the deleted list, and only look at the include or exclude
712 : * list as appropriate.
713 : *
714 : * If client is non-NULL, the notifications are enqueued only for that
715 : * client. Otherwise they are enqueued for all clients.
716 : */
717 : void
718 33 : gmpr_enqueue_all_source_notifications (gmpr_ogroup *group, gmpr_client *client)
719 : {
720 : gmp_addr_list *addr_list;
721 : gmpr_ogroup_addr_entry *group_addr;
722 : gmp_addr_list_entry *addr_entry;
723 :
724 : /* Select the address list based on the group filter mode. */
725 :
726 33 : addr_list = gmpr_ogroup_source_list(group);
727 :
728 : /* Walk the address list, enqueueing each active entry. */
729 :
730 33 : addr_entry = NULL;
731 : while (TRUE) {
732 33 : addr_entry = gmp_addr_list_next_entry(addr_list, addr_entry);
733 33 : group_addr = gmpr_addr_entry_to_ogroup_entry(addr_entry);
734 33 : if (!group_addr)
735 33 : break;
736 0 : if (gmpr_source_is_active(group, group_addr)) {
737 0 : if (client) {
738 0 : gmpr_client_enqueue_source(client, group_addr);
739 : } else {
740 0 : gmpr_source_notify_clients(group_addr, NOTIFY_UNCONDITIONAL);
741 : }
742 : }
743 : }
744 33 : }
745 :
746 :
747 : /*
748 : * gmpr_mode_change_notify_clients
749 : *
750 : * Notify all clients about an interface mode change for a group. This
751 : * consists of flushing all pending notifications for that group, and then
752 : * enqueueing the group and any sources.
753 : */
754 : void
755 33 : gmpr_mode_change_notify_clients (gmpr_ogroup *group)
756 : {
757 : /* Enqueue the group. */
758 :
759 33 : gmpr_group_notify_clients(group);
760 :
761 : /* Flush any pending notifications. */
762 :
763 33 : gmpr_flush_notifications_group(group);
764 :
765 : /* Enqueue all appropriate source notifications. */
766 :
767 33 : gmpr_enqueue_all_source_notifications(group, NULL);
768 33 : }
769 :
770 :
771 : /*
772 : * gmpr_client_enqueue_all_intf_groups
773 : *
774 : * Enqueue all groups and sources associated with a single interface onto
775 : * a client notification thread.
776 : *
777 : * If flush is TRUE, we flush out all notifications for the groups's
778 : * sources first. This cleans up any lingering source deletions.
779 : */
780 : void
781 3 : gmpr_client_enqueue_all_intf_groups (gmpr_client *client, gmpr_intf *intf,
782 : boolean flush)
783 : {
784 : gmpr_ogroup *group;
785 :
786 : /* Walk all groups on the interface. */
787 :
788 3 : group = NULL;
789 :
790 : while (TRUE) {
791 3 : group = gmpr_next_oif_group(intf, group);
792 :
793 : /* Bail if done. */
794 :
795 3 : if (!group)
796 3 : break;
797 :
798 : /*
799 : * If we're asked to, flush any notifications for this group
800 : * first. This serves to remove any pending source delete
801 : * notifications (which will be freed as a side effect).
802 : */
803 0 : if (flush)
804 0 : gmpr_flush_notifications_group(group);
805 :
806 : /* Got a group. Enqueue it. */
807 :
808 0 : gmpr_client_enqueue_group(client, group);
809 :
810 : /* Enqueue all of the sources as well. */
811 :
812 0 : gmpr_enqueue_all_source_notifications(group, client);
813 : }
814 3 : }
815 :
816 :
817 : /*
818 : * gmpr_client_enqueue_all_groups
819 : *
820 : * Enqueue all groups and sources onto a client notification thread.
821 : * We call this when a new client appears. Clients also use this to
822 : * refresh their state if they have to.
823 : *
824 : * If flush is TRUE, we flush out all source notifications first. This
825 : * gets rid of any pending source deletions.
826 : */
827 : void
828 125 : gmpr_client_enqueue_all_groups (gmpr_client *client, boolean flush)
829 : {
830 : gmpr_instance *instance;
831 : gmpr_intf *intf;
832 :
833 125 : instance = client->rclient_instance;
834 :
835 : /* Walk all interfaces on the instance. */
836 :
837 125 : intf = NULL;
838 :
839 : while (TRUE) {
840 128 : intf = gmpr_next_instance_intf(instance, intf);
841 128 : if (!intf)
842 125 : break;
843 :
844 : /* Enqueue everything on the interface. */
845 :
846 3 : gmpr_client_enqueue_all_intf_groups(client, intf, flush);
847 : }
848 125 : }
849 :
850 :
851 : /*
852 : * gmpr_free_notification
853 : *
854 : * Free up a notification.
855 : */
856 : void
857 62 : gmpr_free_notification (gmpr_client_notification *notification)
858 : {
859 62 : gmp_destroy_addr_thread(notification->notif_addr_thread);
860 62 : gmpx_free_block(gmpr_notification_tag, notification);
861 62 : }
862 :
863 :
864 : /*
865 : * gmpr_build_full_notification
866 : *
867 : * Build a full notification. We build an address thread from the source
868 : * addresses, if any.
869 : */
870 : static void
871 0 : gmpr_build_full_notification (gmpr_instance *instance,
872 : gmpr_client_notification *client_notif,
873 : gmpr_ogroup *group)
874 : {
875 : gmp_addr_list *addr_list;
876 : gmp_addr_thread *addr_thread;
877 : gmp_addr_list_entry *addr_entry;
878 : gmpr_ogroup_addr_entry *group_addr;
879 : gmp_addr_cat_entry *cat_entry;
880 :
881 : /* Set the notification type. */
882 :
883 0 : client_notif->notif_type = GMPR_NOTIF_GROUP_STATE;
884 :
885 : /* Create an address thread with all active sources, if present. */
886 :
887 0 : addr_list = gmpr_ogroup_source_list(group);
888 0 : if (!gmp_addr_list_empty(addr_list)) {
889 :
890 : /* List is there. Create an address thread. */
891 :
892 0 : addr_thread = gmp_alloc_addr_thread();
893 0 : client_notif->notif_addr_thread = addr_thread;
894 :
895 : /* Walk the address list. */
896 :
897 0 : addr_entry = NULL;
898 :
899 : while (TRUE) {
900 0 : addr_entry = gmp_addr_list_next_entry(addr_list, addr_entry);
901 0 : group_addr = gmpr_addr_entry_to_ogroup_entry(addr_entry);
902 0 : if (!group_addr)
903 0 : break;
904 :
905 : /*
906 : * Got a source address entry. Stick it in the thread if
907 : * it's active.
908 : */
909 0 : if (gmpr_source_is_active(group, group_addr)) {
910 : cat_entry =
911 0 : gmp_get_addr_cat_by_ordinal(&instance->rinst_addr_cat,
912 : addr_entry->addr_ent_ord);
913 0 : gmpx_assert(cat_entry);
914 0 : gmp_enqueue_addr_thread_addr(addr_thread,
915 0 : cat_entry->adcat_ent_addr.gmp_addr,
916 : instance->rinst_addrlen);
917 : }
918 : }
919 : }
920 0 : }
921 :
922 :
923 : /*
924 : * gmpr_build_delta_notification
925 : *
926 : * Build a delta notification.
927 : */
928 : static void
929 118 : gmpr_build_delta_notification (gmpr_instance *instance,
930 : gmpr_notify_block *notification,
931 : gmpr_client_notification *client_notif,
932 : gmpr_ogroup *group,
933 : gmpr_ogroup_addr_entry *group_addr)
934 : {
935 : gmp_addr_list_entry *addr_entry;
936 : gmp_addr_cat_entry *cat_entry;
937 :
938 : /* Switch based on notification type. */
939 :
940 118 : switch (notification->gmpr_notify_type) {
941 58 : case GMPR_NOTIFY_GROUP:
942 :
943 : /*
944 : * We've got a group notification. If the group state is
945 : * Include {}, or the interface is down, we're deleting the
946 : * group. Otherwise, we're adding the group in either Include
947 : * or Exclude state.
948 : */
949 58 : if (gmpr_ogroup_is_active(group) && group->rogroup_intf->rintf_up) {
950 37 : if (group->rogroup_filter_mode == GMP_FILTER_MODE_INCLUDE) {
951 15 : client_notif->notif_type = GMPR_NOTIF_GROUP_ADD_INCL;
952 : } else {
953 22 : client_notif->notif_type = GMPR_NOTIF_GROUP_ADD_EXCL;
954 : }
955 : } else {
956 21 : client_notif->notif_type = GMPR_NOTIF_GROUP_DELETE;
957 : }
958 58 : break;
959 :
960 60 : case GMPR_NOTIFY_SOURCE:
961 :
962 : /*
963 : * We've got a client notification. Look up the source
964 : * address and copy it to the client notification.
965 : */
966 60 : addr_entry = &group_addr->rogroup_addr_entry;
967 60 : cat_entry = gmp_get_addr_cat_by_ordinal(&instance->rinst_addr_cat,
968 : addr_entry->addr_ent_ord);
969 60 : gmpx_assert(cat_entry);
970 60 : memmove(client_notif->notif_source_addr.gmp_addr,
971 60 : cat_entry->adcat_ent_addr.gmp_addr,
972 60 : instance->rinst_addrlen);
973 :
974 : /*
975 : * Now split out in each combination of the current filter
976 : * mode, and which list the address entry is on in order to
977 : * determine the notification type. We treat a source as deleted
978 : * if the output interface is down.
979 : */
980 60 : switch (group->rogroup_filter_mode) {
981 60 : case GMP_FILTER_MODE_INCLUDE:
982 :
983 : /* Include mode. See which list the entry is on. */
984 :
985 60 : if (gmpr_group_addr_deleted(group_addr) ||
986 33 : !group->rogroup_intf->rintf_up) {
987 :
988 : /* Deleted. Send a BLOCK event. */
989 :
990 27 : client_notif->notif_type = GMPR_NOTIF_BLOCK_SOURCE;
991 :
992 33 : } else if (gmpr_group_addr_included(group_addr)) {
993 :
994 : /* Included. Send an ALLOW event. */
995 :
996 33 : client_notif->notif_type = GMPR_NOTIF_ALLOW_SOURCE;
997 :
998 : } else {
999 :
1000 : /*
1001 : * There can't be anything on the Exclude list in
1002 : * Include mode.
1003 : */
1004 0 : gmpx_assert(FALSE);
1005 : }
1006 60 : break;
1007 :
1008 0 : case GMP_FILTER_MODE_EXCLUDE:
1009 :
1010 : /* Exclude mode. See which list the entry is on. */
1011 :
1012 0 : if (gmpr_group_addr_deleted(group_addr) ||
1013 0 : gmpr_group_addr_included(group_addr) ||
1014 0 : !group->rogroup_intf->rintf_up) {
1015 :
1016 : /*
1017 : * Deleted or included or interface down. Send an
1018 : * ALLOW event.
1019 : */
1020 0 : client_notif->notif_type = GMPR_NOTIF_ALLOW_SOURCE;
1021 :
1022 : } else {
1023 :
1024 : /* Excluded. Send a BLOCK event. */
1025 :
1026 0 : client_notif->notif_type = GMPR_NOTIF_BLOCK_SOURCE;
1027 : }
1028 0 : break;
1029 :
1030 0 : default:
1031 0 : gmpx_assert(FALSE);
1032 : break;
1033 : }
1034 :
1035 60 : break;
1036 :
1037 0 : case GMPR_NOTIFY_REFRESH_END:
1038 :
1039 : /* Refresh end marker. Pass a refresh end notification. */
1040 :
1041 0 : client_notif->notif_type = GMPR_NOTIF_REFRESH_END;
1042 0 : break;
1043 :
1044 0 : default:
1045 0 : gmpx_assert(FALSE);
1046 : break;
1047 : }
1048 118 : }
1049 :
1050 :
1051 : /*
1052 : * gmpr_fill_client_notification
1053 : *
1054 : * Fill in the non-common client notification fields based on our internal
1055 : * notification type and other state information.
1056 : *
1057 : * Returns a pointer to the group entry.
1058 : */
1059 : static gmpr_ogroup *
1060 118 : gmpr_fill_client_notification (gmpr_instance *instance,
1061 : gmpr_notify_block *notification,
1062 : gmpr_client *client,
1063 : gmpr_client_notification *client_notif)
1064 : {
1065 : gmpr_ogroup *group;
1066 : gmpr_ogroup_addr_entry *group_addr;
1067 :
1068 : /* First, figure out the group. */
1069 :
1070 118 : switch (notification->gmpr_notify_type) {
1071 58 : case GMPR_NOTIFY_GROUP:
1072 58 : group_addr = NULL;
1073 58 : group = gmpr_client_notification_to_group(notification,
1074 : client->rclient_ordinal);
1075 58 : break;
1076 :
1077 60 : case GMPR_NOTIFY_SOURCE:
1078 : group_addr =
1079 60 : gmpr_client_notification_to_addr_entry(notification,
1080 : client->rclient_ordinal);
1081 60 : group = group_addr->rogroup_addr_group;
1082 60 : break;
1083 :
1084 0 : case GMPR_NOTIFY_REFRESH_END:
1085 0 : group_addr = NULL;
1086 0 : group = NULL;
1087 0 : break;
1088 :
1089 0 : default:
1090 0 : gmpx_assert(FALSE);
1091 : group = NULL; /* Quiet the compiler. */
1092 : group_addr = NULL;
1093 : break;
1094 :
1095 : }
1096 :
1097 : /*
1098 : * Got the group. If the send_full_notif flag is set for this
1099 : * client,bthe group is active, and the interface is up, build a
1100 : * full notification with all of the trimmings.
1101 : */
1102 118 : if (group && gmpr_ogroup_is_active(group) &&
1103 74 : group->rogroup_intf->rintf_up &&
1104 74 : group->rogroup_send_full_notif[client->rclient_ordinal]) {
1105 :
1106 : /* Clear the flag to show we're doing it. */
1107 :
1108 0 : group->rogroup_send_full_notif[client->rclient_ordinal] = FALSE;
1109 :
1110 : /* Build the notification. */
1111 :
1112 0 : gmpr_build_full_notification(instance, client_notif, group);
1113 :
1114 : } else {
1115 :
1116 : /* Not sending a full notification. Build a delta (or deletion). */
1117 :
1118 118 : gmpr_build_delta_notification(instance, notification, client_notif,
1119 : group, group_addr);
1120 : }
1121 :
1122 118 : return group;
1123 : }
1124 :
1125 :
1126 : /*
1127 : * gmpr_alert_clients
1128 : *
1129 : * Process pending notifications for all clients. This consists of
1130 : * calling the client callback for any clients flagged as needing to be
1131 : * called.
1132 : *
1133 : * We do this step separately so that we don't end up calling back the
1134 : * client from deep within our message processing.
1135 : */
1136 : void
1137 299 : gmpr_alert_clients (gmpr_instance *instance)
1138 : {
1139 : gmpr_client *client;
1140 : gmpr_client_context *cli_ctx;
1141 : task_thread *thread_ptr;
1142 :
1143 299 : gmpr_trace_agent("Alert Client : file : %s, line : %.",
1144 : __FILE__, __LINE__);
1145 :
1146 : /* Walk all clients. */
1147 :
1148 598 : FOR_ALL_CIRCULAR_THREAD_ENTRIES(&instance->rinst_client_thread,
1149 : thread_ptr) {
1150 299 : client = gmpr_thread_to_client(thread_ptr);
1151 :
1152 : /* If this is the first notification for the client, wake it up. */
1153 :
1154 299 : if (client->rclient_notify) {
1155 62 : gmpr_trace(client->rclient_instance, GMPR_TRACE_CLIENT_NOTIFY,
1156 : "Client %u callback", client->rclient_ordinal);
1157 62 : client->rclient_notify = FALSE;
1158 62 : cli_ctx = &client->rclient_cb_context;
1159 62 : (*cli_ctx->rctx_notif_cb)(client->rclient_context);
1160 : }
1161 : }
1162 299 : }
1163 :
1164 :
1165 : /*
1166 : * gmpr_client_get_notification
1167 : *
1168 : * Get the next notification for a client.
1169 : *
1170 : * Returns a pointer to the notification block, or NULL if there's nothing
1171 : * there.
1172 : *
1173 : * We do some funny games here if the client is interested in both
1174 : * delta and full notifications. Regardless of the notification type
1175 : * (group or source) we look at the group send_full_notif flag, and if
1176 : * TRUE, we send a full notification to the client, clear the flag,
1177 : * and leave the notification entry on the thread. The next time we
1178 : * get called we'll deliver the deltas.
1179 : */
1180 : gmpr_client_notification *
1181 272 : gmpr_client_get_notification (gmpr_client *client,
1182 : gmpr_client_notification *last_notification)
1183 : {
1184 : task_thread *thread_ptr;
1185 : gmpr_ogroup *group;
1186 : gmpr_instance *instance;
1187 : gmpr_client_notification *client_notif;
1188 : gmpr_notify_block *notification;
1189 : boolean both_notifs;
1190 :
1191 272 : instance = client->rclient_instance;
1192 :
1193 : /* If there is an old client notification there, reuse it. */
1194 :
1195 272 : client_notif = NULL;
1196 272 : if (last_notification) {
1197 118 : client_notif = last_notification;
1198 118 : gmp_destroy_addr_thread(client_notif->notif_addr_thread);
1199 118 : memset(client_notif, 0, sizeof(gmpr_client_notification));
1200 : }
1201 :
1202 : /* Note whether we're doing both kinds of notifications. */
1203 :
1204 544 : both_notifs = (client->rclient_cb_context.rctx_delta_notifications &&
1205 272 : client->rclient_cb_context.rctx_full_notifications);
1206 :
1207 : /* Pick up the top of the notification thread. */
1208 :
1209 272 : thread_ptr = thread_circular_top(&client->rclient_notif_head);
1210 272 : notification = gmpr_thread_to_notify_block(thread_ptr);
1211 :
1212 : /* Bail if there's nothing there. */
1213 :
1214 272 : if (!notification) {
1215 :
1216 : /* Free any old client notification. */
1217 :
1218 154 : if (client_notif)
1219 62 : gmpr_free_notification(client_notif);
1220 :
1221 154 : return NULL;
1222 : }
1223 :
1224 : /* If we don't have a client notification block, get one now. */
1225 :
1226 118 : if (!client_notif) {
1227 62 : client_notif = gmpx_malloc_block(gmpr_notification_tag);
1228 62 : if (!client_notif)
1229 0 : return NULL; /* Out of memory */
1230 : }
1231 :
1232 : /* Fill in the non-common fields. */
1233 :
1234 118 : group = gmpr_fill_client_notification(instance, notification, client,
1235 : client_notif);
1236 :
1237 : /* Fill in the common fields. */
1238 :
1239 118 : if (group) {
1240 118 : client_notif->notif_intf_id = group->rogroup_intf->rintf_id;
1241 118 : memmove(client_notif->notif_group_addr.gmp_addr,
1242 118 : group->rogroup_addr.gmp_addr,
1243 118 : instance->rinst_addrlen);
1244 118 : client_notif->notif_filter_mode = group->rogroup_filter_mode;
1245 : }
1246 :
1247 : /*
1248 : * If the client has requested both kinds of notifications and we're
1249 : * delivering a full notification, we leave the notification block
1250 : * on the thread (so that we'll generate the delta the next time around.)
1251 : * Otherwise, we dequeue the notification block and attempt to free
1252 : * its contents.
1253 : */
1254 118 : if (!both_notifs || client_notif->notif_type != GMPR_NOTIF_GROUP_STATE) {
1255 118 : thread_circular_dequeue_top(&client->rclient_notif_head);
1256 118 : gmpr_delete_notification(notification, client->rclient_ordinal);
1257 : }
1258 :
1259 : /*
1260 : * A couple of notes for PR 509013:
1261 : * 1. We add logic here to give hints to the clients, such as
1262 : * IGMP, about the last notification of the same group so that
1263 : * the clients would know to process a few more notifications
1264 : * even when its quantum has been reached.
1265 : * 2. This block of code is deliberately separated from the code
1266 : * above to maintain its independence and modularity.
1267 : * 3. mcsnoopd is a client that asks for both kinds of
1268 : * notifications. The fix here should work for mcsnoopd as
1269 : * well. However, in this set of fix, only IGMP and MLD are
1270 : * changed to take advantage of this. A separate PR is needed
1271 : * in order to add similar logic to mcsnoopd.
1272 : */
1273 :
1274 : /*
1275 : * Take a peek at the next notification to see if it is
1276 : * for a new (*,g). If yes, mark the current notification as the
1277 : * last (s,g).
1278 : */
1279 118 : client_notif->notif_last_sg = FALSE;
1280 118 : thread_ptr = thread_circular_top(&client->rclient_notif_head);
1281 : /*
1282 : * If the client has requested both kinds of notifications, the
1283 : * notification block is not popped from the top of the thread
1284 : * (see comments above), as such, we need to look further to its
1285 : * next block in the thread.
1286 : */
1287 118 : if (thread_ptr &&
1288 0 : both_notifs && client_notif->notif_type == GMPR_NOTIF_GROUP_STATE) {
1289 0 : thread_ptr = thread_circular_thread_next(&client->rclient_notif_head,
1290 : thread_ptr);
1291 : }
1292 118 : if (thread_ptr) {
1293 56 : notification = gmpr_thread_to_notify_block(thread_ptr);
1294 56 : if (notification &&
1295 56 : notification->gmpr_notify_type == GMPR_NOTIFY_GROUP) {
1296 11 : client_notif->notif_last_sg = TRUE;
1297 : }
1298 : } else {
1299 : /* Last notification will always be the last (s,g). */
1300 62 : client_notif->notif_last_sg = TRUE;
1301 : }
1302 :
1303 : /* Trace it. */
1304 :
1305 118 : gmpr_trace(instance, GMPR_TRACE_CLIENT_NOTIFY,
1306 : "Client %u notif %i %a %s %a",
1307 : client->rclient_ordinal, client_notif->notif_intf_id,
1308 : client_notif->notif_group_addr.gmp_addr,
1309 : gmpr_client_notif_string(client_notif->notif_type),
1310 : client_notif->notif_source_addr.gmp_addr);
1311 :
1312 118 : return client_notif;
1313 : }
|