Line data Source code
1 : /* $Id: gmpr_host.c 429362 2011-03-09 10:55:35Z ib-builder $
2 : *
3 : * gmpr_host.c - IGMP/MLD Router-Side host management
4 : *
5 : * Dave Katz, March 2008
6 : *
7 : * Copyright (c) 2008, Juniper Networks, Inc.
8 : * All rights reserved.
9 : *
10 : * This module contains host management support for router-side GMP. We do
11 : * host tracking for accounting purposes, and to allow for query suppression
12 : * and fast Leaves.
13 : */
14 : #include "gmpx_basic_types.h"
15 : #include "gmp.h"
16 : #include "gmpx_environment.h"
17 : #include "gmp_private.h"
18 : #include "gmp_router.h"
19 : #include "gmpr_private.h"
20 : #include "gmp_trace.h"
21 : #include "gmpr_trace.h"
22 :
23 : /* Forward references */
24 :
25 : static void gmpr_host_source_expiry(gmpx_timer *timer, void *context);
26 : static void gmpr_host_group_expiry(gmpx_timer *timer, void *context);
27 : static void gmpr_delete_host_notification(gmpr_notify_block *notification,
28 : ordinal_t client_ord,
29 : boolean delete_any);
30 : static void gmpr_flush_notifications_host_group(gmpr_host_group *host_group);
31 :
32 :
33 : /*
34 : * gmpr_host_source_notifications_active
35 : *
36 : * Returns TRUE if there are any active host notifications on this source, or
37 : * FALSE if not.
38 : */
39 : static boolean
40 118 : gmpr_host_source_notifications_active (gmpr_host_group_addr *group_addr)
41 : {
42 118 : return gmpr_notifications_active(group_addr->rhga_notify);
43 : }
44 :
45 :
46 : /*
47 : * gmpr_host_group_addr_alloc
48 : *
49 : * Allocate a host group address entry.
50 : *
51 : * Returns NULL if out of memory.
52 : */
53 : static gmp_addr_list_entry *
54 59 : gmpr_host_group_addr_alloc (void *context)
55 : {
56 : gmpr_host_group *host_group;
57 : gmpr_host_group_addr *hg_addr;
58 : gmpr_instance *instance;
59 :
60 59 : host_group = context;
61 59 : instance = host_group->rhgroup_host->rhost_intf->rintf_instance;
62 :
63 : /* Allocate a block. */
64 :
65 59 : hg_addr = gmpx_malloc_block(gmpr_host_group_addr_tag);
66 59 : if (!hg_addr)
67 0 : return NULL;
68 :
69 : /* Initialize a wee bit. */
70 :
71 59 : gmpr_set_notification_type(hg_addr->rhga_notify, GMPR_NOTIFY_HOST_SOURCE);
72 59 : hg_addr->rhga_host_group = host_group;
73 59 : hg_addr->rhga_timer =
74 59 : gmpx_create_timer(instance->rinst_context, "GMP router host source",
75 : gmpr_host_source_expiry, hg_addr);
76 :
77 59 : return &hg_addr->rhga_addr_ent;
78 : }
79 :
80 :
81 : /*
82 : * gmpr_flush_host_notifications
83 : *
84 : * Flush all pending client notifications on a notification block array.
85 : */
86 : static void
87 132 : gmpr_flush_host_notifications (gmpr_notify_block *notify_block)
88 : {
89 : ordinal_t client_ord;
90 :
91 396 : for (client_ord = 0; client_ord < GMPX_MAX_RTR_CLIENTS; client_ord++) {
92 264 : if (thread_node_on_thread(¬ify_block->gmpr_notify_thread)) {
93 0 : thread_remove(¬ify_block->gmpr_notify_thread);
94 : }
95 264 : notify_block++;
96 : }
97 132 : }
98 :
99 :
100 : /*
101 : * gmpr_host_group_addr_free
102 : *
103 : * Free a host group address entry.
104 : */
105 : static void
106 59 : gmpr_host_group_addr_free (gmp_addr_list_entry *addr_entry)
107 : {
108 : gmpr_host_group_addr *hg_addr;
109 :
110 59 : hg_addr = gmpr_addr_entry_to_host_group_entry(addr_entry);
111 :
112 : /* Flush the notification list. */
113 :
114 59 : gmpr_flush_host_notifications(hg_addr->rhga_notify);
115 :
116 : /* Delink it. */
117 :
118 59 : thread_remove(&hg_addr->rhga_thread);
119 :
120 : /* Free the timer. */
121 :
122 59 : gmpx_destroy_timer(hg_addr->rhga_timer);
123 :
124 : /* Free the block. */
125 :
126 59 : gmpx_free_block(gmpr_host_group_addr_tag, addr_entry);
127 59 : }
128 :
129 :
130 : /*
131 : * gmpr_alert_host_clients
132 : *
133 : * Call back all clients with pending host notifications.
134 : */
135 : void
136 263 : gmpr_alert_host_clients (gmpr_instance *instance)
137 : {
138 : gmpr_client *client;
139 : gmpr_client_context *cli_ctx;
140 : task_thread *thread_ptr;
141 :
142 : /* Bail if not doing host tracking. */
143 :
144 263 : if (!instance->rinst_host_tracking)
145 0 : return;
146 :
147 : /* Walk all clients. */
148 :
149 526 : FOR_ALL_CIRCULAR_THREAD_ENTRIES(&instance->rinst_client_thread,
150 : thread_ptr) {
151 263 : client = gmpr_thread_to_client(thread_ptr);
152 :
153 : /* Process the client if it has a host notification callback. */
154 :
155 263 : cli_ctx = &client->rclient_cb_context;
156 263 : if (cli_ctx->rctx_host_notif_cb) {
157 :
158 : /* If this is the first notification for the client, wake it up. */
159 :
160 263 : if (client->rclient_host_notify) {
161 132 : gmpr_trace(instance, GMPR_TRACE_HOST_NOTIFY,
162 : "Client %u host callback", client->rclient_ordinal);
163 132 : client->rclient_host_notify = FALSE;
164 132 : (*cli_ctx->rctx_host_notif_cb)(client->rclient_context);
165 : }
166 : }
167 : }
168 : }
169 :
170 :
171 : /*
172 : * gmpr_host_group_notifications_active
173 : *
174 : * Returns TRUE if there are any active notifications on this host_group, or
175 : * FALSE if not.
176 : */
177 : static boolean
178 143 : gmpr_host_group_notifications_active (gmpr_host_group *host_group)
179 : {
180 143 : return gmpr_notifications_active(host_group->rhgroup_notify);
181 : }
182 :
183 :
184 : /*
185 : * gmpr_destroy_host_group
186 : *
187 : * Destroy a host group entry. If the entry is locked (the lock count is
188 : * nonzero) we flag the entry as deleted. This routine will very shortly
189 : * be called back with the lock removed.
190 : */
191 : static void
192 73 : gmpr_destroy_host_group (gmpr_host_group *host_group)
193 : {
194 : gmpr_host *host;
195 : gmpr_instance *instance;
196 :
197 73 : host = host_group->rhgroup_host;
198 73 : instance = host->rhost_intf->rintf_instance;
199 :
200 73 : gmpr_trace_agent("Destroy host group : file : %s, line : %.",
201 : __FILE__, __LINE__);
202 :
203 : /* If the refcount is nonzero, flag that we're deleted and bail. */
204 :
205 73 : if (host_group->rhgroup_lock_count) {
206 0 : host_group->rhgroup_is_deleted = TRUE;
207 :
208 : /* Trace it. */
209 :
210 0 : gmpr_trace(instance, GMPR_TRACE_HOST_NOTIFY,
211 : "Host group %a redundant destroy",
212 : host_group->rhgroup_addr.gmp_addr);
213 0 : return;
214 : }
215 :
216 : /* Flush the notification lists. */
217 :
218 73 : gmpr_flush_notifications_host_group(host_group);
219 73 : gmpr_flush_host_notifications(host_group->rhgroup_notify);
220 :
221 : /* Flush the address lists. */
222 :
223 73 : gmp_addr_list_clean(&host_group->rhgroup_addrs);
224 73 : gmp_addr_list_clean(&host_group->rhgroup_deleted);
225 :
226 : /* Delink. */
227 :
228 73 : thread_remove(&host_group->rhgroup_thread);
229 73 : gmpx_assert(gmpx_patricia_delete(host->rhost_group_root,
230 : &host_group->rhgroup_node));
231 :
232 : /* Free the timer. */
233 :
234 73 : gmpx_destroy_timer(host_group->rhgroup_timer);
235 :
236 : /* Free the block. */
237 :
238 73 : gmpx_free_block(gmpr_host_group_tag, host_group);
239 : }
240 :
241 :
242 : /*
243 : * gmpr_lock_host_group
244 : *
245 : * Temporarily lock a host group to keep it from being deleted.
246 : * These locks are expected to be very short-lived (within the scope
247 : * of a single execution) and to never be nested (though we allow
248 : * a limited amount of nesting.)
249 : */
250 : static void
251 0 : gmpr_lock_host_group (gmpr_host_group *host_group)
252 : {
253 0 : host_group->rhgroup_lock_count++;
254 0 : }
255 :
256 :
257 : /*
258 : * gmpr_unlock_host_group
259 : *
260 : * Unlock the host group. If the refcount is zero and the deleted flag
261 : * is set, go ahead and destroy it now.
262 : */
263 : static void
264 0 : gmpr_unlock_host_group (gmpr_host_group *host_group)
265 : {
266 0 : gmpx_assert(host_group->rhgroup_lock_count);
267 0 : host_group->rhgroup_lock_count--;
268 :
269 0 : if (!host_group->rhgroup_lock_count && host_group->rhgroup_is_deleted)
270 0 : gmpr_destroy_host_group(host_group);
271 0 : }
272 :
273 :
274 : /*
275 : * gmpr_destroy_host
276 : *
277 : * Destroy a host entry. Things should be normally be pretty well
278 : * cleaned up first, but may not be if we're cleaning up with extreme
279 : * prejudice.
280 : */
281 : static void
282 84 : gmpr_destroy_host (gmpr_host *host)
283 : {
284 : gmpr_host_group *host_group;
285 : gmpx_patnode *node;
286 :
287 : /* Flush all of the host groups. */
288 :
289 : while (TRUE) {
290 84 : node = gmpx_patricia_lookup_least(host->rhost_group_root);
291 84 : if (!node)
292 68 : break;
293 16 : host_group = gmpr_patnode_to_host_group(node);
294 16 : gmpr_destroy_host_group(host_group);
295 : }
296 68 : gmpx_patroot_destroy(host->rhost_group_root);
297 :
298 : /* Delink us from the interface. */
299 :
300 68 : gmpr_trace_agent("Destroy host : file : %s, line : %.",
301 : __FILE__, __LINE__);
302 :
303 68 : gmpx_assert(gmpx_patricia_delete(host->rhost_intf->rintf_host_root,
304 : &host->rhost_node));
305 :
306 : /* Free the block. */
307 :
308 68 : gmpx_free_block(gmpr_host_tag, host);
309 68 : }
310 :
311 :
312 : /*
313 : * gmpr_destroy_intf_hosts
314 : *
315 : * Destroy all hosts on an interface.
316 : */
317 : void
318 1724 : gmpr_destroy_intf_hosts (gmpr_intf *intf)
319 : {
320 : gmpr_host *host;
321 : gmpx_patnode *node;
322 :
323 : /* Walk the host tree, destroying everything in our path. */
324 :
325 : while (TRUE) {
326 1724 : node = gmpx_patricia_lookup_least(intf->rintf_host_root);
327 1724 : if (!node)
328 1708 : break;
329 16 : host = gmpr_patnode_to_host(node);
330 16 : gmpr_destroy_host(host);
331 : }
332 1708 : }
333 :
334 :
335 : /*
336 : * gmpr_attempt_host_free
337 : *
338 : * Try to free a host. It will be freed if everything has been cleaned up.
339 : */
340 : static void
341 57 : gmpr_attempt_host_free (gmpr_host *host)
342 : {
343 : /* Bail if there are any groups. */
344 :
345 57 : gmpr_trace_agent("Attempt host free : file : %s, line : %.",
346 : __FILE__, __LINE__);
347 :
348 57 : if (gmpx_patricia_lookup_least(host->rhost_group_root))
349 5 : return;
350 :
351 : /* That was easy. Destroy the host. */
352 :
353 52 : gmpr_destroy_host(host);
354 : }
355 :
356 :
357 : /*
358 : * gmpr_attempt_host_group_free
359 : *
360 : * Attempt to free the host_group entry. We will do so if there is no
361 : * client interest in the host_group and there's nothing more to send
362 : * for the host_group.
363 : */
364 : static void
365 333 : gmpr_attempt_host_group_free (gmpr_host_group *host_group)
366 : {
367 : gmpr_host *host;
368 :
369 333 : host = host_group->rhgroup_host;
370 :
371 333 : gmpr_trace_agent("Attempt host group free : file : %s, line : %.",
372 : __FILE__, __LINE__);
373 :
374 : /* Bail if the entry is active. */
375 :
376 333 : if (gmpr_host_group_active(host_group))
377 190 : return;
378 :
379 : /* Bail if there are any pending client notifications. */
380 :
381 143 : if (gmpr_host_group_notifications_active(host_group))
382 29 : return;
383 :
384 : /* Bail if there's anything on any of the lists. */
385 :
386 114 : if (!gmp_addr_list_empty(&host_group->rhgroup_addrs))
387 0 : return;
388 114 : if (!gmp_addr_list_empty(&host_group->rhgroup_deleted))
389 57 : return;
390 :
391 : /* Looks safe. Destroy the host_group. */
392 :
393 57 : gmpr_destroy_host_group(host_group);
394 :
395 : /* Try to delete the host. */
396 :
397 57 : gmpr_attempt_host_free(host);
398 : }
399 :
400 :
401 : /*
402 : * gmpr_attempt_free_host_addr_entry
403 : *
404 : * Attempt to free a host group address entry.
405 : *
406 : * The entry is freed if there are no pending notifications left.
407 : */
408 : static void
409 118 : gmpr_attempt_free_host_addr_entry (gmpr_host_group_addr *group_addr)
410 : {
411 : gmpr_host_group *host_group;
412 :
413 118 : host_group = group_addr->rhga_host_group;
414 :
415 : /* Do it if there are no active notifications. */
416 :
417 118 : if (!gmpr_host_source_notifications_active(group_addr))
418 59 : gmp_delete_addr_list_entry(&group_addr->rhga_addr_ent);
419 :
420 : /* Try to free the host group. */
421 :
422 118 : gmpr_attempt_host_group_free(host_group);
423 118 : }
424 :
425 :
426 : /*
427 : * gmpr_delete_host_notification
428 : *
429 : * Delete a host notification, it having been removed from the
430 : * client notification list. Notifications aren't actually "deleted"
431 : * since they are embedded in other data structures. But we do any
432 : * necessary cleanup.
433 : *
434 : * If "delete_any" is set, we will attempt to free any kind of block
435 : * carrying a source wnotification. Otherwise, we'll only try to free
436 : * deleted entries.
437 : */
438 : static void
439 196 : gmpr_delete_host_notification (gmpr_notify_block *notification,
440 : ordinal_t client_ord, boolean delete_any)
441 : {
442 : gmpr_host_group *host_group;
443 : gmpr_host_group_addr *host_group_addr;
444 :
445 : /* See whether it is a host_group or source notification. */
446 :
447 196 : switch (notification->gmpr_notify_type) {
448 78 : case GMPR_NOTIFY_HOST_GROUP:
449 :
450 : /*
451 : * Host_Group notification. Try to free the host_group, as we
452 : * may have just cleaned up the last thing keeping the
453 : * host_group alive.
454 : */
455 78 : host_group = gmpr_client_notification_to_host_group(notification,
456 : client_ord);
457 78 : gmpr_attempt_host_group_free(host_group);
458 78 : break;
459 :
460 118 : case GMPR_NOTIFY_HOST_SOURCE:
461 :
462 : /*
463 : * Source notification. If the entry is on the deleted list
464 : * (meaning that we're done with it other than notifications),
465 : * try to free the address entry if it is no longer on any
466 : * client notification list.
467 : */
468 : host_group_addr =
469 118 : gmpr_client_notification_to_host_group_addr(notification,
470 : client_ord);
471 118 : if (gmpr_host_group_addr_deleted(host_group_addr) || delete_any) {
472 59 : gmpr_attempt_free_host_addr_entry(host_group_addr);
473 : }
474 118 : break;
475 :
476 0 : default:
477 0 : gmpx_assert(FALSE);
478 : }
479 196 : }
480 :
481 :
482 : /*
483 : * gmpr_flush_notifications_host_group_list
484 : *
485 : * Flush all pending client source notifications on a host_group
486 : * address list.
487 : */
488 : static void
489 146 : gmpr_flush_notifications_host_group_list (gmp_addr_list *addr_list)
490 : {
491 : gmp_addr_list_entry *addr_entry;
492 : gmpr_host_group_addr *host_group_addr;
493 :
494 146 : addr_entry = NULL;
495 :
496 : /* Walk the list. */
497 :
498 : while (TRUE) {
499 146 : addr_entry = gmp_addr_list_next_entry(addr_list, addr_entry);
500 146 : host_group_addr = gmpr_addr_entry_to_host_group_entry(addr_entry);
501 146 : if (!host_group_addr)
502 146 : break;
503 :
504 : /* Got an entry. Delink it from each client. */
505 :
506 0 : gmpr_flush_host_notifications(host_group_addr->rhga_notify);
507 : }
508 146 : }
509 :
510 :
511 : /*
512 : * gmpr_flush_notifications_host_group
513 : *
514 : * Flush all pending client source notifications for a host_group.
515 : * Note that it does not remove the host_group itself from any
516 : * notification list if it happens to be there.
517 : */
518 : static void
519 73 : gmpr_flush_notifications_host_group (gmpr_host_group *host_group)
520 : {
521 : /* Flush each of the lists where notifications may lie. */
522 :
523 73 : gmpr_flush_notifications_host_group_list(&host_group->rhgroup_addrs);
524 73 : gmpr_flush_notifications_host_group_list(&host_group->rhgroup_deleted);
525 73 : }
526 :
527 :
528 : /*
529 : * gmpr_flush_host_notifications_client
530 : *
531 : * Flush all pending notifications for a client. This is done when the
532 : * client is being destroyed.
533 : */
534 : void
535 125 : gmpr_flush_host_notifications_client (gmpr_client *client)
536 : {
537 : gmpr_notify_block *notification;
538 :
539 : task_thread *thread_ptr;
540 :
541 : /* Walk the client notification list. */
542 :
543 : while (TRUE) {
544 0 : thread_ptr =
545 125 : thread_circular_dequeue_top(&client->rclient_host_notif_head);
546 125 : notification = gmpr_thread_to_notify_block(thread_ptr);
547 125 : if (!notification)
548 125 : break;
549 :
550 : /* Got a notification. Delete it. */
551 :
552 0 : gmpr_delete_host_notification(notification, client->rclient_ordinal,
553 : TRUE);
554 : }
555 125 : }
556 :
557 :
558 : /*
559 : * gmpr_update_client_host_notify
560 : *
561 : * Update the notify-client flag in advance of starting to enqueue
562 : * host notifications. We set it if it was previously clear, and if
563 : * the notification queue is currently empty. The net effect is that
564 : * we set it when enqueueing the first notification.
565 : */
566 : static void
567 196 : gmpr_update_client_host_notify (gmpr_client *client)
568 : {
569 196 : if (!client->rclient_host_notify) {
570 132 : client->rclient_host_notify =
571 132 : thread_circular_thread_empty(&client->rclient_host_notif_head);
572 132 : gmpr_trace(client->rclient_instance, GMPR_TRACE_HOST_NOTIFY,
573 : "Client %u host notify set to %u",
574 : client->rclient_ordinal, client->rclient_host_notify);
575 : }
576 196 : }
577 :
578 :
579 : /*
580 : * gmpr_client_enqueue_host_group
581 : *
582 : * Enqueue one host_group onto a client notification thread.
583 : *
584 : * If it was already enqueued, it is delinked and moved to the end.
585 : */
586 : static void
587 78 : gmpr_client_enqueue_host_group (gmpr_client *client,
588 : gmpr_host_group *host_group)
589 : {
590 : ordinal_t client_ord;
591 :
592 : /* Bail if there is no host notification callback for this client. */
593 :
594 78 : if (!client->rclient_cb_context.rctx_host_notif_cb)
595 0 : return;
596 :
597 : /*
598 : * Bail if the client startup timer is running. We'll be doing a full
599 : * state enqueue when it expires.
600 : */
601 78 : if (client->rclient_startup_timer)
602 0 : return;
603 :
604 : /* Update the notification flag. */
605 :
606 78 : gmpr_update_client_host_notify(client);
607 :
608 : /*
609 : * Delink the host_group from the client thread, in case it was already
610 : * on there, and then requeue it at the end.
611 : */
612 78 : client_ord = client->rclient_ordinal;
613 78 : thread_remove(&host_group->rhgroup_notify[client_ord].gmpr_notify_thread);
614 78 : thread_circular_add_bottom(&client->rclient_host_notif_head,
615 : &host_group->rhgroup_notify[client_ord].gmpr_notify_thread);
616 : }
617 :
618 :
619 : /*
620 : * gmpr_client_enqueue_host_source
621 : *
622 : * Enqueue one source address onto a client notification thread.
623 : *
624 : * If it was already enqueued, it is delinked and moved to the end.
625 : */
626 : static void
627 118 : gmpr_client_enqueue_host_source (gmpr_client *client,
628 : gmpr_host_group_addr *host_group_addr)
629 : {
630 : ordinal_t client_ord;
631 :
632 : /* Bail if there is no host notification callback for this client. */
633 :
634 118 : if (!client->rclient_cb_context.rctx_host_notif_cb)
635 0 : return;
636 :
637 : /*
638 : * Bail if the client startup timer is running. We'll be doing a full
639 : * state enqueue when it expires.
640 : */
641 118 : if (client->rclient_startup_timer)
642 0 : return;
643 :
644 : /* Update the client host notify flag. */
645 :
646 118 : gmpr_update_client_host_notify(client);
647 :
648 : /*
649 : * Delink the host_group from the client thread, in case it was already
650 : * on there, and then requeue it at the end.
651 : */
652 118 : client_ord = client->rclient_ordinal;
653 118 : thread_remove(
654 : &host_group_addr->rhga_notify[client_ord].gmpr_notify_thread);
655 118 : thread_circular_add_bottom(&client->rclient_host_notif_head,
656 : &host_group_addr->rhga_notify[client_ord].gmpr_notify_thread);
657 : }
658 :
659 :
660 : /*
661 : * gmpr_host_group_notify_clients
662 : *
663 : * Enqueue a host_group notification for all clients.
664 : *
665 : * The host_group is threaded onto the notification thread for each client,
666 : * and a notification callback is made if the thread was previously
667 : * empty.
668 : */
669 : static void
670 78 : gmpr_host_group_notify_clients (gmpr_host_group *host_group)
671 : {
672 : gmpr_instance *instance;
673 : gmpr_client *client;
674 : task_thread *thread_ptr;
675 :
676 78 : instance = host_group->rhgroup_host->rhost_intf->rintf_instance;
677 :
678 : /* Walk all clients. */
679 :
680 156 : FOR_ALL_CIRCULAR_THREAD_ENTRIES(&instance->rinst_client_thread,
681 : thread_ptr) {
682 78 : client = gmpr_thread_to_client(thread_ptr);
683 :
684 : /* Enqueue the host_group. */
685 :
686 78 : gmpr_client_enqueue_host_group(client, host_group);
687 : }
688 78 : }
689 :
690 :
691 : /*
692 : * gmpr_host_source_notify_clients
693 : *
694 : * Notify all clients that a source has changed state.
695 : *
696 : * The source is threaded onto the notification thread for each client,
697 : * and a notification callback is made if the thread was previously
698 : * empty.
699 : */
700 : static void
701 118 : gmpr_host_source_notify_clients (gmpr_host_group_addr *host_group_addr)
702 : {
703 : gmpr_instance *instance;
704 : gmpr_client *client;
705 : task_thread *thread_ptr;
706 : gmpr_host_group *host_group;
707 :
708 118 : host_group = host_group_addr->rhga_host_group;
709 118 : instance = host_group->rhgroup_host->rhost_intf->rintf_instance;
710 :
711 : /* Walk all clients. */
712 :
713 236 : FOR_ALL_CIRCULAR_THREAD_ENTRIES(&instance->rinst_client_thread,
714 : thread_ptr) {
715 118 : client = gmpr_thread_to_client(thread_ptr);
716 :
717 : /* Enqueue the notification. */
718 :
719 118 : gmpr_client_enqueue_host_source(client, host_group_addr);
720 : }
721 :
722 : /*
723 : * Attempt to free the entry if it was deleted. If we actually
724 : * enqueued a notification, this will do nothing.
725 : */
726 118 : if (gmpr_host_group_addr_deleted(host_group_addr)) {
727 59 : gmpr_attempt_free_host_addr_entry(host_group_addr);
728 : }
729 118 : }
730 :
731 :
732 : /*
733 : * gmpr_enqueue_all_source_notifications
734 : *
735 : * Enqueue all appropriate source notifications for a host_group.
736 : *
737 : * We assume that the deleted list for the host_group is empty at this point,
738 : * so we only look at the running and deleted lists.
739 : *
740 : * If client is non-NULL, the notifications are enqueued only for that
741 : * client. Otherwise they are enqueued for all clients.
742 : */
743 : static void
744 0 : gmpr_enqueue_all_host_source_notifications (gmpr_host_group *host_group,
745 : gmpr_client *client)
746 : {
747 : gmp_addr_list *addr_list;
748 : gmpr_host_group_addr *host_group_addr;
749 : gmp_addr_list_entry *addr_entry;
750 :
751 0 : addr_list = &host_group->rhgroup_addrs;
752 :
753 : /* Walk the address list, enqueueing each entry. */
754 :
755 0 : addr_entry = NULL;
756 : while (TRUE) {
757 0 : addr_entry = gmp_addr_list_next_entry(addr_list, addr_entry);
758 0 : host_group_addr = gmpr_addr_entry_to_host_group_entry(addr_entry);
759 0 : if (host_group_addr)
760 0 : break;
761 0 : if (client) {
762 0 : gmpr_client_enqueue_host_source(client, host_group_addr);
763 : } else {
764 0 : gmpr_host_source_notify_clients(host_group_addr);
765 : }
766 : }
767 0 : }
768 :
769 :
770 : /*
771 : * gmpr_client_enqueue_all_host_groups
772 : *
773 : * Enqueue all host_groups and sources onto a client notification thread.
774 : * We call this when a new client appears. Clients also use this to
775 : * refresh their state if they have to.
776 : */
777 : void
778 125 : gmpr_client_enqueue_all_host_groups (gmpr_client *client)
779 : {
780 : gmpr_instance *instance;
781 : gmpr_host *host;
782 : gmpr_host_group *host_group;
783 : gmpr_intf *intf;
784 : gmpx_patnode *host_node, *host_group_node;
785 :
786 125 : instance = client->rclient_instance;
787 :
788 : /* Bail if there is no host notification callback for this client. */
789 :
790 125 : if (!client->rclient_cb_context.rctx_host_notif_cb)
791 0 : return;
792 :
793 : /* Walk all interfaces on the instance. */
794 :
795 125 : intf = NULL;
796 :
797 : while (TRUE) {
798 128 : intf = gmpr_next_instance_intf(instance, intf);
799 :
800 : /* Bail if done. */
801 :
802 128 : if (!intf)
803 125 : break;
804 :
805 : /* Walk all hosts on the interface. */
806 :
807 3 : host_node = NULL;
808 :
809 : while (TRUE) {
810 3 : host_node = gmpx_patricia_get_next(intf->rintf_host_root,
811 : host_node);
812 3 : host = gmpr_patnode_to_host(host_node);
813 :
814 : /* Bail if done. */
815 :
816 3 : if (!host)
817 3 : break;
818 :
819 : /* Got a host. Walk all host groups on the host. */
820 :
821 0 : host_group_node = NULL;
822 :
823 : while (TRUE) {
824 0 : host_group_node =
825 0 : gmpx_patricia_get_next(host->rhost_group_root,
826 : host_group_node);
827 0 : host_group = gmpr_patnode_to_host_group(host_group_node);
828 :
829 : /* Bail if done. */
830 :
831 0 : if (!host_group)
832 0 : break;
833 :
834 : /* Got a host group. See if it is active. */
835 :
836 0 : if (gmpr_host_group_active(host_group)) {
837 :
838 : /*
839 : * It's active. If it has sources, just enqueue
840 : * them. Otherwise, enqueue the group.
841 : */
842 0 : if (gmp_addr_list_empty(&host_group->rhgroup_addrs)) {
843 0 : gmpr_client_enqueue_host_group(client, host_group);
844 : } else {
845 0 : gmpr_enqueue_all_host_source_notifications(host_group,
846 : client);
847 : }
848 : }
849 : }
850 : }
851 : }
852 : }
853 :
854 :
855 : /*
856 : * gmpr_fill_client_host_notif
857 : *
858 : * Fill in the non-common client notification fields based on our internal
859 : * notification type and other state information.
860 : *
861 : * Returns a pointer to the host_group entry.
862 : */
863 : static gmpr_host_group *
864 196 : gmpr_fill_client_host_notif (gmpr_instance *instance,
865 : gmpr_notify_block *notification,
866 : gmpr_client *client,
867 : gmpr_client_host_notification *client_notif)
868 : {
869 : gmpr_host_group *host_group;
870 : gmp_addr_cat_entry *cat_entry;
871 : gmpr_host_group_addr *host_group_addr;
872 : gmp_addr_list_entry *addr_entry;
873 : // gmpr_host *host;
874 :
875 : /* Switch based on notification type. */
876 :
877 196 : switch (notification->gmpr_notify_type) {
878 78 : case GMPR_NOTIFY_HOST_GROUP:
879 :
880 : /*
881 : * We've got a host_group notification. If the host group
882 : * isn't active, we're deleting the host_group.
883 : */
884 : host_group =
885 78 : gmpr_client_notification_to_host_group(notification,
886 : client->rclient_ordinal);
887 : // host = host_group->rhgroup_host;
888 78 : if (!gmpr_host_group_active(host_group)) {
889 :
890 : /*
891 : * Host group is being deleted. If the host group timer
892 : * isn't running, the group has timed out.
893 : */
894 29 : if (!gmpx_timer_running(host_group->rhgroup_timer)) {
895 2 : client_notif->host_notif_type = GMPR_NOTIF_HOST_TIMEOUT;
896 : } else {
897 :
898 : /* Not a timeout. If the interface is down, say so. */
899 :
900 27 : if (!host_group->rhgroup_host->rhost_intf->rintf_up) {
901 0 : client_notif->host_notif_type = GMPR_NOTIF_HOST_IFDOWN;
902 : } else {
903 27 : client_notif->host_notif_type = GMPR_NOTIF_HOST_LEAVE;
904 : }
905 : }
906 :
907 : } else {
908 :
909 : /* Not deleted. It's a join. */
910 :
911 49 : client_notif->host_notif_type = GMPR_NOTIF_HOST_JOIN;
912 : }
913 78 : client_notif->host_notif_source_present = FALSE;
914 78 : break;
915 :
916 118 : case GMPR_NOTIFY_HOST_SOURCE:
917 :
918 : /*
919 : * We've got a client notification. Look up the source
920 : * address and copy it to the client notification.
921 : */
922 : host_group_addr =
923 118 : gmpr_client_notification_to_host_group_addr(notification,
924 : client->rclient_ordinal);
925 118 : host_group = host_group_addr->rhga_host_group;
926 : // host = host_group->rhgroup_host;
927 118 : addr_entry = &host_group_addr->rhga_addr_ent;
928 118 : cat_entry = gmp_get_addr_cat_by_ordinal(&instance->rinst_addr_cat,
929 : addr_entry->addr_ent_ord);
930 118 : gmpx_assert(cat_entry);
931 118 : memmove(client_notif->host_notif_source_addr.gmp_addr,
932 118 : cat_entry->adcat_ent_addr.gmp_addr,
933 118 : instance->rinst_addrlen);
934 118 : client_notif->host_notif_source_present = TRUE;
935 :
936 : /* Set the notification type. */
937 :
938 118 : if (gmpr_host_group_addr_deleted(host_group_addr)) {
939 :
940 : /*
941 : * Host group address is being deleted. If the host group
942 : * source timer isn't running, the group has timed out.
943 : */
944 59 : if (!gmpx_timer_running(host_group_addr->rhga_timer)) {
945 0 : client_notif->host_notif_type = GMPR_NOTIF_HOST_TIMEOUT;
946 : } else {
947 :
948 : /* Not a timeout. If the interface is down, say so. */
949 :
950 59 : if (!host_group->rhgroup_host->rhost_intf->rintf_up) {
951 0 : client_notif->host_notif_type = GMPR_NOTIF_HOST_IFDOWN;
952 : } else {
953 59 : client_notif->host_notif_type = GMPR_NOTIF_HOST_LEAVE;
954 : }
955 : }
956 :
957 : } else {
958 :
959 : /* Not deleted. It's a join. */
960 :
961 59 : client_notif->host_notif_type = GMPR_NOTIF_HOST_JOIN;
962 : }
963 118 : break;
964 :
965 0 : default:
966 0 : gmpx_assert(FALSE);
967 : host_group = NULL; /* Quiet the compiler */
968 : break;
969 : }
970 :
971 196 : return host_group;
972 : }
973 :
974 :
975 : /*
976 : * gmpr_client_free_host_notification
977 : *
978 : * Free a host notification block.
979 : */
980 : void
981 154 : gmpr_client_free_host_notification (gmpr_client_host_notification *host_notif)
982 : {
983 154 : if (host_notif)
984 132 : gmpx_free_block(gmpr_host_notification_tag, host_notif);
985 154 : }
986 :
987 :
988 : /*
989 : * gmpr_client_host_notif_string
990 : *
991 : * Returns a string for the host notification type, given the type.
992 : */
993 : static const char *
994 0 : gmpr_client_host_notif_string (gmpr_client_host_notification_type type)
995 : {
996 0 : switch (type) {
997 0 : case GMPR_NOTIF_HOST_JOIN:
998 0 : return "Join";
999 0 : case GMPR_NOTIF_HOST_LEAVE:
1000 0 : return "Leave";
1001 0 : case GMPR_NOTIF_HOST_TIMEOUT:
1002 0 : return "Timeout";
1003 0 : case GMPR_NOTIF_HOST_IFDOWN:
1004 0 : return "Ifdown";
1005 0 : default:
1006 0 : return "Unknown";
1007 : }
1008 : }
1009 :
1010 :
1011 : /*
1012 : * gmpr_client_get_host_notification
1013 : *
1014 : * Get the next host notification for a client.
1015 : *
1016 : * Returns a pointer to the notification block, or NULL if there's nothing
1017 : * there.
1018 : */
1019 : gmpr_client_host_notification *
1020 350 : gmpr_client_get_host_notification (gmpr_client *client,
1021 : gmpr_client_host_notification *last_notification)
1022 : {
1023 : task_thread *thread_ptr;
1024 : gmpr_host_group *host_group;
1025 : gmpr_host *host;
1026 : gmpr_instance *instance;
1027 : gmpr_client_host_notification *client_notif;
1028 : gmpr_notify_block *notification;
1029 : gmpr_intf *intf;
1030 :
1031 350 : instance = client->rclient_instance;
1032 :
1033 : /* If there is an old client notification there, reuse it. */
1034 :
1035 350 : client_notif = NULL;
1036 350 : if (last_notification) {
1037 196 : client_notif = last_notification;
1038 196 : memset(client_notif, 0, sizeof(gmpr_client_host_notification));
1039 : }
1040 :
1041 : /* Dequeue the top of the notification thread. */
1042 :
1043 350 : thread_ptr = thread_circular_dequeue_top(&client->rclient_host_notif_head);
1044 350 : notification = gmpr_thread_to_notify_block(thread_ptr);
1045 :
1046 : /* Bail if there's nothing there. */
1047 :
1048 350 : if (!notification) {
1049 :
1050 : /* Free any old client notification. */
1051 :
1052 154 : gmpr_client_free_host_notification(client_notif);
1053 :
1054 154 : return NULL;
1055 : }
1056 :
1057 : /* If we don't have a client notification block, get one now. */
1058 :
1059 196 : if (!client_notif) {
1060 132 : client_notif = gmpx_malloc_block(gmpr_host_notification_tag);
1061 132 : if (!client_notif)
1062 0 : return NULL; /* Out of memory */
1063 : }
1064 :
1065 : /* Fill in the non-common fields. */
1066 :
1067 196 : host_group = gmpr_fill_client_host_notif(instance, notification,
1068 : client, client_notif);
1069 196 : host = host_group->rhgroup_host;
1070 :
1071 : /* Fill in the common fields. */
1072 :
1073 196 : intf = host->rhost_intf;
1074 196 : client_notif->host_notif_intf_id = intf->rintf_id;
1075 196 : memmove(client_notif->host_notif_group_addr.gmp_addr,
1076 196 : host_group->rhgroup_addr.gmp_addr,
1077 196 : instance->rinst_addrlen);
1078 196 : memmove(client_notif->host_notif_host_addr.gmp_addr,
1079 196 : host_group->rhgroup_host->rhost_addr.gmp_addr,
1080 196 : instance->rinst_addrlen);
1081 :
1082 : /* Trace it. */
1083 :
1084 196 : gmpr_trace(instance, GMPR_TRACE_HOST_NOTIFY,
1085 : "Client %u host notif %s %i (%a, %a) %s host %a",
1086 : client->rclient_ordinal,
1087 : gmpr_client_host_notif_string(client_notif->host_notif_type),
1088 : client_notif->host_notif_intf_id,
1089 : client_notif->host_notif_group_addr.gmp_addr,
1090 : client_notif->host_notif_source_addr.gmp_addr,
1091 : (client_notif->host_notif_source_present ? "(SP)":""),
1092 : client_notif->host_notif_host_addr.gmp_addr);
1093 :
1094 : /*
1095 : * Delete the notification, which cleans up a bunch of stuff and
1096 : * may free the address entry, host group, and host.
1097 : */
1098 196 : gmpr_delete_host_notification(notification, client->rclient_ordinal,
1099 : FALSE);
1100 :
1101 196 : return client_notif;
1102 : }
1103 :
1104 :
1105 : /*
1106 : * gmpr_lookup_host_group
1107 : *
1108 : * Look up a host group entry, given the host and group address.
1109 : *
1110 : * Returns a pointer to the host group entry, or NULL if not found.
1111 : */
1112 : static gmpr_host_group *
1113 135 : gmpr_lookup_host_group (gmpr_host *host, gmp_addr_string *group_addr)
1114 : {
1115 : gmpr_host_group *host_group;
1116 : gmpx_patnode *node;
1117 :
1118 : /* Look up the host group in the host tree. */
1119 :
1120 135 : node = gmpx_patricia_lookup(host->rhost_group_root, group_addr);
1121 135 : host_group = gmpr_patnode_to_host_group(node);
1122 :
1123 135 : return host_group;
1124 : }
1125 :
1126 :
1127 : /*
1128 : * gmpr_create_host_group
1129 : *
1130 : * Create a host_group entry.
1131 : *
1132 : * Returns a pointer to the host_group entry, or NULL if out of memory.
1133 : */
1134 : static gmpr_host_group *
1135 73 : gmpr_create_host_group (gmpr_host *host, gmp_addr_string *group_addr)
1136 : {
1137 : gmpr_host_group *host_group;
1138 : gmpr_instance *instance;
1139 :
1140 73 : instance = host->rhost_intf->rintf_instance;
1141 73 : host_group = gmpx_malloc_block(gmpr_host_group_tag);
1142 73 : if (!host_group)
1143 0 : return NULL; /* Out of memory */
1144 :
1145 : /* Got the block. Initialize it. */
1146 :
1147 73 : memmove(host_group->rhgroup_addr.gmp_addr, group_addr->gmp_addr, instance->rinst_addrlen);
1148 73 : gmp_addr_list_init(&host_group->rhgroup_addrs, &instance->rinst_addr_cat,
1149 : gmpr_host_group_addr_alloc, gmpr_host_group_addr_free,
1150 : host_group);
1151 73 : gmp_addr_list_init(&host_group->rhgroup_deleted, &instance->rinst_addr_cat,
1152 : gmpr_host_group_addr_alloc, gmpr_host_group_addr_free,
1153 : host_group);
1154 73 : host_group->rhgroup_host = host;
1155 73 : gmpr_set_notification_type(host_group->rhgroup_notify,
1156 : GMPR_NOTIFY_HOST_GROUP);
1157 73 : host_group->rhgroup_timer =
1158 73 : gmpx_create_timer(instance->rinst_context, "GMP router host group",
1159 : gmpr_host_group_expiry, host_group);
1160 :
1161 : /* Put it into the tree. */
1162 :
1163 73 : gmpx_assert(gmpx_patricia_add(host->rhost_group_root,
1164 : &host_group->rhgroup_node));
1165 :
1166 73 : return host_group;
1167 : }
1168 :
1169 :
1170 : /*
1171 : * gmpr_lookup_host
1172 : *
1173 : * Look up a host entry, given the interface and host address.
1174 : *
1175 : * Returns a pointer to the host entry, or NULL if not found.
1176 : */
1177 : gmpr_host *
1178 135 : gmpr_lookup_host (gmpr_intf *intf, const uint8_t *host_addr)
1179 : {
1180 : gmpr_host *host;
1181 : gmpx_patnode *node;
1182 :
1183 : /* Look up the host in the tree. */
1184 :
1185 135 : node = gmpx_patricia_lookup(intf->rintf_host_root, host_addr);
1186 135 : host = gmpr_patnode_to_host(node);
1187 :
1188 135 : return host;
1189 : }
1190 :
1191 :
1192 : /*
1193 : * gmpr_create_host
1194 : *
1195 : * Create a host entry.
1196 : *
1197 : * Returns a pointer to the host entry, or NULL if out of memory.
1198 : */
1199 : static gmpr_host *
1200 68 : gmpr_create_host (gmpr_intf *intf, uint8_t *host_addr)
1201 : {
1202 : gmpr_host *host;
1203 : gmpr_instance *instance;
1204 :
1205 68 : instance = intf->rintf_instance;
1206 68 : host = gmpx_malloc_block(gmpr_host_tag);
1207 68 : if (!host)
1208 0 : return NULL; /* Out of memory */
1209 :
1210 : /* Got the block. Initialize it. */
1211 :
1212 68 : memmove(host->rhost_addr.gmp_addr, host_addr, instance->rinst_addrlen);
1213 :
1214 68 : host->rhost_group_root =
1215 68 : gmpx_patroot_init(instance->rinst_addrlen,
1216 : GMPX_PATRICIA_OFFSET(gmpr_host_group, rhgroup_node,
1217 : rhgroup_addr));
1218 68 : host->rhost_intf = intf;
1219 :
1220 : /* Put it into the tree. */
1221 :
1222 68 : gmpx_assert(gmpx_patricia_add(intf->rintf_host_root, &host->rhost_node));
1223 :
1224 68 : return host;
1225 : }
1226 :
1227 :
1228 : /*
1229 : * gmpr_delete_host_source
1230 : *
1231 : * Delete a source from a host group. We don't actually delete it, but
1232 : * instead move it to the deleted list, do associated cleanup, and enqueue
1233 : * a notification for the clients.
1234 : */
1235 : static void
1236 59 : gmpr_delete_host_source (gmpr_host_group_addr *hg_addr)
1237 : {
1238 : gmpr_host *host;
1239 : gmpr_host_group *host_group;
1240 : gmpr_group_addr_entry *group_addr_entry;
1241 :
1242 59 : host_group = hg_addr->rhga_host_group;
1243 59 : host = host_group->rhgroup_host;
1244 :
1245 : /* Delink us from the main address entry. */
1246 :
1247 59 : group_addr_entry = hg_addr->rhga_source;
1248 59 : thread_remove(&hg_addr->rhga_thread);
1249 59 : hg_addr->rhga_source = NULL;
1250 :
1251 : /*
1252 : * If we are doing fast leaves and we just deleted the last
1253 : * host group address from the group address, it's time to
1254 : * stop forwarding from this source.
1255 : */
1256 59 : if (group_addr_entry && host->rhost_intf->rintf_fast_leaves &&
1257 0 : thread_circular_thread_empty(
1258 : &group_addr_entry->rgroup_host_addr_head)) {
1259 0 : gmpr_last_host_addr_ref_gone(group_addr_entry);
1260 : }
1261 :
1262 : /* Move the entry to the deleted list. */
1263 :
1264 59 : gmp_move_addr_list_entry(&host_group->rhgroup_deleted,
1265 : &hg_addr->rhga_addr_ent);
1266 :
1267 : /* Enqueue the notification. */
1268 :
1269 59 : gmpr_host_source_notify_clients(hg_addr);
1270 59 : }
1271 :
1272 :
1273 : /*
1274 : * gmpr_delete_source_cb
1275 : *
1276 : * Vector callback to delete a source from the host group entry.
1277 : */
1278 : static boolean
1279 59 : gmpr_delete_source_cb (void *context, bv_bitnum_t bitnum,
1280 : boolean new_val GMPX_UNUSED,
1281 : boolean old_val GMPX_UNUSED)
1282 : {
1283 : gmpr_host_group *host_group;
1284 : gmpr_host_group_addr *hg_addr;
1285 : gmp_addr_list_entry *host_addr_entry;
1286 : gmp_addr_list *active_list;
1287 :
1288 59 : host_group = context;
1289 59 : active_list = &host_group->rhgroup_addrs;
1290 :
1291 : /* See if the source is there. */
1292 :
1293 59 : if (gmp_addr_in_list(active_list, bitnum)) {
1294 :
1295 : /* Source is there. Get the address entry. */
1296 :
1297 : host_addr_entry =
1298 59 : gmp_lookup_addr_entry(active_list, bitnum);
1299 59 : hg_addr = gmpr_addr_entry_to_host_group_entry(host_addr_entry);
1300 59 : gmpx_assert(hg_addr);
1301 :
1302 : /* Delete the host address. */
1303 :
1304 59 : gmpr_delete_host_source(hg_addr);
1305 : }
1306 :
1307 59 : return FALSE;
1308 : }
1309 :
1310 :
1311 : /*
1312 : * gmpr_add_source_cb
1313 : *
1314 : * Vector callback to add a source to the host group entry.
1315 : * The source may already be on the list.
1316 : */
1317 : static boolean
1318 59 : gmpr_add_source_cb (void *context, bv_bitnum_t bitnum,
1319 : boolean new_val GMPX_UNUSED, boolean old_val GMPX_UNUSED)
1320 : {
1321 : // gmpr_instance *instance;
1322 : gmpr_host_group *host_group;
1323 : gmpr_host_group_addr *hg_addr;
1324 : gmp_addr_list_entry *host_addr_entry;
1325 : gmp_addr_list_entry *main_addr_entry;
1326 : gmpr_group_addr_entry *group_addr_entry;
1327 : gmpr_host *host;
1328 : gmpr_group *group;
1329 : gmp_addr_list *active_list;
1330 : gmp_addr_list *delete_list;
1331 :
1332 59 : host_group = context;
1333 59 : host = host_group->rhgroup_host;
1334 : // instance = host->rhost_intf->rintf_instance;
1335 59 : active_list = &host_group->rhgroup_addrs;
1336 59 : delete_list = &host_group->rhgroup_deleted;
1337 :
1338 : /* See if the source is already present. */
1339 :
1340 59 : if (gmp_addr_in_list(active_list, bitnum)) {
1341 :
1342 : /* Already there. Get the address. */
1343 :
1344 : host_addr_entry =
1345 0 : gmp_lookup_addr_entry(active_list, bitnum);
1346 0 : hg_addr = gmpr_addr_entry_to_host_group_entry(host_addr_entry);
1347 0 : gmpx_assert(hg_addr);
1348 :
1349 : } else {
1350 :
1351 : /* Not there. See if it's on the delete list. */
1352 :
1353 59 : if (gmp_addr_in_list(delete_list, bitnum)) {
1354 :
1355 : /* On the delete list. Move it to the active list. */
1356 :
1357 : host_addr_entry =
1358 0 : gmp_lookup_addr_entry(delete_list, bitnum);
1359 0 : hg_addr = gmpr_addr_entry_to_host_group_entry(host_addr_entry);
1360 0 : gmpx_assert(hg_addr);
1361 :
1362 0 : gmp_move_addr_list_entry(active_list, host_addr_entry);
1363 :
1364 : } else {
1365 :
1366 : /* Not there. Allocate a new one. */
1367 :
1368 : host_addr_entry =
1369 59 : gmp_create_addr_list_entry(active_list, bitnum);
1370 59 : hg_addr = gmpr_addr_entry_to_host_group_entry(host_addr_entry);
1371 59 : if (!hg_addr)
1372 0 : return TRUE; /* Out of memory */
1373 : }
1374 : }
1375 :
1376 : /*
1377 : * If the host address entry isn't linked to a main address entry,
1378 : * do so now. This can happen for existing host addresses if the
1379 : * interface channel limit was previously hit but the channel has
1380 : * just been created.
1381 : */
1382 59 : if (!thread_node_on_thread(&hg_addr->rhga_thread)) {
1383 :
1384 : /*
1385 : * Look up the address entry on the main group running list and
1386 : * add this entry to the thread. This builds a list of host
1387 : * contributions to each running timer entry, so we can do fast
1388 : * leave processing when all of the contributions go away.
1389 : */
1390 59 : group = host_group->rhgroup_group;
1391 59 : if (group) {
1392 : main_addr_entry =
1393 59 : gmp_lookup_addr_entry(&group->rgroup_src_addr_running, bitnum);
1394 59 : group_addr_entry = gmpr_addr_entry_to_group_entry(main_addr_entry);
1395 59 : hg_addr->rhga_source = group_addr_entry;
1396 59 : if (group_addr_entry) {
1397 :
1398 : /* Found the entry. Add the new entry to the thread. */
1399 :
1400 59 : thread_circular_add_bottom(
1401 : &group_addr_entry->rgroup_host_addr_head,
1402 : &hg_addr->rhga_thread);
1403 : }
1404 : }
1405 :
1406 : /* Enqueue the notification. */
1407 :
1408 59 : gmpr_host_source_notify_clients(hg_addr);
1409 : }
1410 :
1411 : /* (Re)start the source timer. */
1412 :
1413 59 : gmpx_start_timer(hg_addr->rhga_timer,
1414 59 : host->rhost_intf->rintf_group_membership_ivl, 0);
1415 :
1416 59 : return FALSE;
1417 : }
1418 :
1419 :
1420 : /*
1421 : * gmpr_delink_host_group
1422 : *
1423 : * Delink a host group from the main group entry, which makes it inactive.
1424 : * Tolerates already being unlinked.
1425 : */
1426 : static void
1427 57 : gmpr_delink_host_group (gmpr_host_group *host_group)
1428 : {
1429 57 : gmpr_trace_agent("Delink host group : file : %s, line : %.",
1430 : __FILE__, __LINE__);
1431 :
1432 57 : host_group->rhgroup_group = NULL;
1433 57 : thread_remove(&host_group->rhgroup_thread);
1434 57 : }
1435 :
1436 :
1437 : /*
1438 : * gmpr_host_process_report
1439 : *
1440 : * Do host-level processing on a report group record.
1441 : *
1442 : * We're doing host tracking for accounting purposes, and to potentially
1443 : * speed up leaves by not waiting for query timeouts. In both cases, we
1444 : * don't care about non-null Exclude state.
1445 : */
1446 : void
1447 135 : gmpr_host_process_report (uint8_t *src_addr, gmp_report_rectype rec_type,
1448 : gmpr_group *group, gmp_addr_vect *source_vect)
1449 : {
1450 : // gmpr_instance *instance;
1451 : gmpr_host *host;
1452 : gmpr_host_group *host_group;
1453 : gmpr_intf *intf;
1454 : boolean delete_group;
1455 : boolean notify_group;
1456 : boolean was_empty;
1457 : boolean new_group;
1458 : boolean group_was_active;
1459 :
1460 135 : gmpx_assert(group);
1461 : // instance = group->rgroup_intf->rintf_instance;
1462 135 : intf = group->rgroup_intf;
1463 :
1464 135 : gmpr_trace_agent("Host process report : file : %s, line : %.",
1465 : __FILE__, __LINE__);
1466 :
1467 :
1468 : /* Don't bother if we're not doing host processing. */
1469 :
1470 135 : if (!intf->rintf_instance->rinst_host_tracking)
1471 0 : return;
1472 :
1473 : /* Look up the host. */
1474 :
1475 135 : host = gmpr_lookup_host(intf, src_addr);
1476 :
1477 : /* If not there, create one. */
1478 :
1479 135 : if (!host) {
1480 68 : host = gmpr_create_host(intf, src_addr);
1481 68 : if (!host)
1482 0 : return; /* Out of memory */
1483 : }
1484 :
1485 : /* Look up the host group. */
1486 :
1487 135 : host_group = gmpr_lookup_host_group(host, &group->rgroup_addr);
1488 :
1489 : /* If not there, create one. */
1490 :
1491 135 : new_group = !host_group;
1492 135 : if (!host_group) {
1493 73 : host_group = gmpr_create_host_group(host, &group->rgroup_addr);
1494 73 : if (!host_group)
1495 0 : return; /* Out of memory */
1496 : }
1497 :
1498 : /*
1499 : * If the entry isn't active (either we just created it or we
1500 : * reanimated an inactive entry) link to the main group, which
1501 : * makes it active. We may undo this later.
1502 : */
1503 135 : group_was_active = gmpr_host_group_active(host_group);
1504 135 : if (!group_was_active) {
1505 77 : gmpx_assert(!thread_node_on_thread(&host_group->rhgroup_thread));
1506 77 : host_group->rhgroup_group = group;
1507 77 : thread_circular_add_bottom(&group->rgroup_host_group_head,
1508 : &host_group->rhgroup_thread);
1509 : }
1510 :
1511 : /* Fork based on the report type. */
1512 :
1513 135 : delete_group = FALSE;
1514 135 : notify_group = FALSE;
1515 135 : was_empty = gmp_addr_list_empty(&host_group->rhgroup_addrs);
1516 135 : switch (rec_type) {
1517 :
1518 56 : case GMP_RPT_IS_IN:
1519 : case GMP_RPT_TO_IN:
1520 :
1521 : /*
1522 : * Host is either in include mode or is switching to it. If we're
1523 : * switching, the record contains all sources for the group and we
1524 : * can delete any not listed. If we're not switching, the record is
1525 : * not idempotent, so we can't delete unlisted sources.
1526 : *
1527 : * Note that if everything is working the way it's supposed to, there
1528 : * will be no sources when switching from Exclude to Include mode
1529 : * since we don't track Exclude sources. And when a redundant TO_IN
1530 : * is received, nothing will change with all of the machinations
1531 : * below. But we go through the effort in case the host is broken
1532 : * or we've lost a bunch of host reports or something.
1533 : *
1534 : * First, if we're switching, let's delete those unlisted sources
1535 : * by forming the set A-B and deleting all addresses in the result.
1536 : */
1537 56 : if (rec_type == GMP_RPT_TO_IN) {
1538 27 : if (gmp_addr_vect_minus(&host_group->rhgroup_addrs.addr_vect,
1539 : source_vect, NULL,
1540 : gmpr_delete_source_cb, host_group,
1541 : BV_CALL_SET) < 0)
1542 0 : return; /* Out of memory */
1543 : }
1544 :
1545 : /*
1546 : * Now walk all sources listed in the report and add them to the
1547 : * group or refresh the source timer as appropriate.
1548 : */
1549 56 : if (gmp_addr_vect_walk(source_vect, gmpr_add_source_cb,
1550 : host_group) < 0) {
1551 0 : return; /* Out of memory */
1552 : }
1553 :
1554 : /* Now see if there are any more sources left. */
1555 :
1556 56 : if (gmp_addr_list_empty(&host_group->rhgroup_addrs)) {
1557 :
1558 : /*
1559 : * Nothing left in the host group, which means we got a
1560 : * Leave or its equivalent. Flag it for deletion. If
1561 : * there were no sources in the group before, and the
1562 : * group wasn't just created, flag it for notification.
1563 : * (If there were sources, the client will be getting
1564 : * notifications for each source, so we don't need to
1565 : * notify for the group.) The new_group check keeps us
1566 : * from generating a host notification when the host sends
1567 : * a redundant Leave (which it is supposed to do.)
1568 : */
1569 27 : delete_group = TRUE;
1570 27 : if (was_empty && !new_group) {
1571 27 : notify_group = TRUE;
1572 : }
1573 : }
1574 56 : break;
1575 :
1576 49 : case GMP_RPT_IS_EX:
1577 : case GMP_RPT_TO_EX:
1578 :
1579 : /*
1580 : * Host is in Exclude mode. We treat these as equivalent to (*,G)
1581 : * joins, so we clear all sources. We flush the delete list as
1582 : * well, since the (*,G) join subsumes any source blocks.
1583 : */
1584 49 : gmp_flush_addr_list(&host_group->rhgroup_addrs);
1585 49 : gmp_flush_addr_list(&host_group->rhgroup_deleted);
1586 :
1587 : /*
1588 : * Notify the clients if either the list wasn't empty before
1589 : * (meaning we've gone from (S,G) to (*,G)) or the group wasn't
1590 : * active before (meaning that it's a new group join.)
1591 : */
1592 49 : if (!was_empty || !group_was_active)
1593 49 : notify_group = TRUE;
1594 49 : break;
1595 :
1596 0 : case GMP_RPT_ALLOW:
1597 :
1598 : /*
1599 : * Host wants to allow traffic for some sources. If the
1600 : * address list is empty, we're already listening to all
1601 : * sources (in exclude mode) so we ignore it. Otherwise we
1602 : * add the sources to the list (and send source notifications
1603 : * as necessary.)
1604 : */
1605 0 : if (!group_was_active ||
1606 0 : !gmp_addr_list_empty(&host_group->rhgroup_addrs)) {
1607 :
1608 : /* Something there (or the group is inactive.) Add the sources. */
1609 :
1610 0 : if (gmp_addr_vect_walk(source_vect, gmpr_add_source_cb,
1611 : host_group) < 0) {
1612 0 : return; /* Out of memory */
1613 : }
1614 :
1615 : }
1616 0 : break;
1617 :
1618 30 : case GMP_RPT_BLOCK:
1619 :
1620 : /*
1621 : * Host wants to block traffic for some source. If the group
1622 : * is inactive, we're already blocking everything (just
1623 : * waiting for the notification to go out) so we ignore it.
1624 : * If active but the address list is empty, we're listening to
1625 : * all sources in Exclude mode and ignore it (since we don't
1626 : * track non-null exclude lists.) Otherwise, the host is in
1627 : * Include mode, and we delete entries on the list.
1628 : */
1629 59 : if (group_was_active &&
1630 29 : !gmp_addr_list_empty(&host_group->rhgroup_addrs)) {
1631 :
1632 : /* Something there. Delete the sources. */
1633 :
1634 29 : if (gmp_addr_vect_inter(source_vect,
1635 : &host_group->rhgroup_addrs.addr_vect, NULL,
1636 : gmpr_delete_source_cb, host_group,
1637 : BV_CALL_SET) < 0)
1638 0 : return; /* Out of memory */
1639 : }
1640 :
1641 : /*
1642 : * If the list is now empty, delete the group. No need for a
1643 : * group notification, since we've sent source notifications.
1644 : * This also catches the case where we've received a redundant
1645 : * BLOCK after the group has been deleted (we would have
1646 : * created an empty group above.)
1647 : */
1648 30 : if (gmp_addr_list_empty(&host_group->rhgroup_addrs)) {
1649 28 : delete_group = TRUE;
1650 : }
1651 :
1652 30 : break;
1653 :
1654 0 : default:
1655 0 : gmpx_assert(FALSE);
1656 : }
1657 :
1658 : /* See if we're deleting the group. */
1659 :
1660 135 : if (delete_group) {
1661 :
1662 : /*
1663 : * Deleting the group. Delink the host group from the main group,
1664 : * which makes it inactive.
1665 : */
1666 55 : gmpr_delink_host_group(host_group);
1667 :
1668 : /*
1669 : * If we're doing fast leaves, and there are no host groups
1670 : * associated with this group any longer, trigger a deletion
1671 : * of the main group.
1672 : */
1673 55 : if (intf->rintf_fast_leaves &&
1674 0 : thread_circular_thread_empty(&group->rgroup_host_group_head)) {
1675 0 : gmpr_last_host_group_ref_gone(group);
1676 : }
1677 :
1678 : } else {
1679 :
1680 : /*
1681 : * The group is still alive. Start the group timer if there
1682 : * are no sources, or stop the group timer if there are
1683 : * sources.
1684 : */
1685 80 : if (gmp_addr_list_empty(&host_group->rhgroup_addrs)) {
1686 49 : gmpx_start_timer(host_group->rhgroup_timer,
1687 : intf->rintf_group_membership_ivl, 0);
1688 : } else {
1689 31 : gmpx_stop_timer(host_group->rhgroup_timer);
1690 : }
1691 : }
1692 :
1693 : /* If we're supposed to do a group notification, do so. */
1694 :
1695 135 : if (notify_group)
1696 76 : gmpr_host_group_notify_clients(host_group);
1697 :
1698 : /* Try to free it (we probably can't just yet, but maybe.) */
1699 :
1700 135 : gmpr_attempt_host_group_free(host_group);
1701 : }
1702 :
1703 :
1704 : /*
1705 : * gmpr_host_group_expiry
1706 : *
1707 : * Process a host group timer expiration.
1708 : */
1709 : static void
1710 2 : gmpr_host_group_expiry (gmpx_timer *timer, void *context)
1711 : {
1712 : gmpr_instance *instance;
1713 : gmpr_host_group *host_group;
1714 :
1715 2 : host_group = context;
1716 2 : instance = host_group->rhgroup_host->rhost_intf->rintf_instance;
1717 :
1718 : /* Stop the timer. */
1719 :
1720 2 : gmpx_stop_timer(timer);
1721 :
1722 : /* There better not be any sources. */
1723 :
1724 2 : gmpx_assert(gmp_addr_list_empty(&host_group->rhgroup_addrs));
1725 :
1726 : /* Delink the host group from the main group (making it inactive). */
1727 :
1728 2 : gmpr_delink_host_group(host_group);
1729 :
1730 : /* Notify the clients. */
1731 :
1732 2 : gmpr_host_group_notify_clients(host_group);
1733 :
1734 : /* Try to free the host group. Probably won't work, but it might. */
1735 :
1736 2 : gmpr_attempt_host_group_free(host_group);
1737 :
1738 : /* Alert the clients. */
1739 :
1740 2 : gmpr_alert_host_clients(instance);
1741 2 : }
1742 :
1743 :
1744 : /*
1745 : * gmpr_host_source_expiry
1746 : *
1747 : * Process a host source address timer expiration.
1748 : */
1749 : static void
1750 0 : gmpr_host_source_expiry (gmpx_timer *timer, void *context)
1751 : {
1752 : gmpr_instance *instance;
1753 : gmpr_host *host;
1754 : gmpr_host_group *host_group;
1755 : gmpr_host_group_addr *hg_addr;
1756 :
1757 0 : hg_addr = context;
1758 0 : host_group = hg_addr->rhga_host_group;
1759 0 : host = host_group->rhgroup_host;
1760 0 : instance = host->rhost_intf->rintf_instance;
1761 :
1762 : /* Stop the timer. */
1763 :
1764 0 : gmpx_stop_timer(timer);
1765 :
1766 : /*
1767 : * Lock the host group, so that it won't be released out from
1768 : * under us as a side effect of deleting the source.
1769 : */
1770 0 : gmpr_lock_host_group(host_group);
1771 :
1772 : /* Mark the source as deleted. */
1773 :
1774 0 : gmpr_delete_host_source(hg_addr);
1775 :
1776 : /* Try to free the host group (we probably can't just yet, but maybe.) */
1777 :
1778 0 : gmpr_attempt_host_group_free(host_group);
1779 :
1780 : /* Undo the lock. This might free the host group. */
1781 :
1782 0 : gmpr_unlock_host_group(host_group);
1783 :
1784 : /* Alert the clients. */
1785 :
1786 0 : gmpr_alert_host_clients(instance);
1787 0 : }
1788 :
1789 :
1790 : /*
1791 : * gmpr_host_notify_oif_map_change
1792 : *
1793 : * An oif-map changed on a group, so notify the affected hosts. Add either
1794 : * the (*,g) or all the (s,g) entries to the host notification thread for
1795 : * each client.
1796 : */
1797 : void
1798 0 : gmpr_host_notify_oif_map_change(gmpr_group *group)
1799 : {
1800 : task_thread *group_thread_ptr;
1801 : gmpr_host_group *host_group;
1802 : gmp_addr_list_entry *addr_entry;
1803 : gmpr_host_group_addr *host_group_addr;
1804 :
1805 : /* Walk the host group thread to create the notifications. */
1806 :
1807 0 : group_thread_ptr = NULL;
1808 : while (TRUE) {
1809 0 : group_thread_ptr = thread_circular_thread_next(
1810 : &group->rgroup_host_group_head, group_thread_ptr);
1811 0 : host_group = gmpr_thread_to_host_group(group_thread_ptr);
1812 0 : if (!host_group)
1813 0 : break;
1814 :
1815 : /*
1816 : * Found a host group. If there are no sources, enqueue the group. If
1817 : * there are sources, enqueue the list of sources (and not the group).
1818 : */
1819 0 : if (gmp_addr_list_empty(&host_group->rhgroup_addrs)) {
1820 0 : gmpr_host_group_notify_clients(host_group);
1821 : } else {
1822 0 : addr_entry = NULL;
1823 : while (TRUE) {
1824 0 : addr_entry = gmp_addr_list_next_entry(
1825 : &host_group->rhgroup_addrs, addr_entry);
1826 0 : host_group_addr = gmpr_addr_entry_to_host_group_entry(
1827 : addr_entry);
1828 0 : if (!host_group_addr)
1829 0 : break;
1830 :
1831 : /*
1832 : * Found a host group address. Enqueue the source.
1833 : */
1834 0 : gmpr_host_source_notify_clients(host_group_addr);
1835 : }
1836 : }
1837 : }
1838 0 : }
|