LCOV - code coverage report
Current view: top level - vnsw/agent/services/multicast/grpmgmt - gmpr_host.c (source / functions) Hit Total Coverage
Test: OpenSDN C/C++ coverage (all TARGET_SET jobs) Lines: 367 487 75.4 %
Date: 2026-08-03 02:19:58 Functions: 35 41 85.4 %
Legend: Lines: hit not hit

          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(&notify_block->gmpr_notify_thread)) {
      93           0 :             thread_remove(&notify_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 : }

Generated by: LCOV version 1.14