]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
glue in the code to look up listeners by key.
authorAlan T. DeKok <aland@freeradius.org>
Thu, 1 Apr 2021 19:48:46 +0000 (15:48 -0400)
committerAlan T. DeKok <aland@freeradius.org>
Fri, 2 Apr 2021 19:14:44 +0000 (15:14 -0400)
* add a listen_free_all() function, which frees mainconfig.listen
and then also the listeners keyed by CoA

* put a linked list into the send_coa listeners, so that all
listeners of the same key can be found

* have each send_coa listener point to the main "key" data
structure

* the main "key" data structure contains the key name, the list
of listeners by this key, and a mutex

* added contents to init / free / add / delete / find functions

src/include/listen.h
src/include/radiusd.h
src/main/listen.c
src/main/mainconfig.c

index 36646e23c374427b62ae1457d1537797f93d8d08..47e6caae7dda68f7dcf2d6767ef5fe0e572cf1df 100644 (file)
@@ -92,6 +92,9 @@ struct rad_listen {
        uint32_t        coa_mrd;
 
        int             num_ids_used;   /* for proxying CoA packets */
+
+       void            *coa_key;       /* parent, to avoid more mutexes */
+       rad_listen_t    *next_key;      /* for lists of listeners with the same key */
 #endif
 #endif
 
index 923924822199d1f0cc302ef5f2f95bc6414ee143..92f400763e126395f78fc7c03564d92a2c312084 100644 (file)
@@ -562,6 +562,7 @@ void hup_logfile(void);
 
 /* listen.c */
 void listen_free(rad_listen_t **head);
+void listen_free_all(rad_listen_t **head);
 int listen_init(CONF_SECTION *cs, rad_listen_t **head, bool spawn_flag);
 rad_listen_t *proxy_new_listener(TALLOC_CTX *ctx, home_server_t *home, uint16_t src_port);
 RADCLIENT *client_listener_find(rad_listen_t *listener, fr_ipaddr_t const *ipaddr, uint16_t src_port);
index 0bebd3e007fc341a3a14b81a1ef39288616f674e..c60912db6d9be1172892d0c653e06e09f5d8218f 100644 (file)
@@ -77,6 +77,11 @@ static int command_write_magic(int newfd, listen_socket_t *sock);
 #endif
 #endif
 
+#ifdef WITH_COA_TUNNEL
+static int listen_coa_init(void);
+static void listen_coa_free(void);
+#endif
+
 static fr_protocol_t master_listen[];
 
 #ifdef WITH_DYNAMIC_CLIENTS
@@ -1364,6 +1369,11 @@ int common_socket_parse(CONF_SECTION *cs, rad_listen_t *this)
                 */
                this->recv = dual_tcp_accept;
 
+               /*
+                *      @todo - add a free function?  Though this only
+                *      matters when we're tearing down the server, so
+                *      perhaps it's less relevant.
+                */
                this->children = rbtree_create(this, listener_cmp, NULL, 0);
                if (!this->children) {
                        cf_log_err_cs(cs, "Failed to create child list for TCP socket.");
@@ -2818,6 +2828,10 @@ static int _listener_free(rad_listen_t *this)
 #endif
                }
 #endif /* WITH_TLS */
+
+#ifdef WITH_COA_TUNNEL
+               if (this->coa_key) listen_coa_delete(this);
+#endif
        }
 #endif                         /* WITH_TCP */
 
@@ -3561,6 +3575,10 @@ add_sockets:
         */
        if (!*head) return -1;
 
+#ifdef WITH_COA_TUNNEL
+       if (listen_coa_init() < 0) return -1;
+#endif
+
        return 0;
 }
 
@@ -3583,6 +3601,15 @@ void listen_free(rad_listen_t **head)
        *head = NULL;
 }
 
+void listen_free_all(rad_listen_t **head)
+{
+       listen_free(head);
+
+#ifdef WITH_COA_TUNNEL
+       listen_coa_free();
+#endif
+}
+
 #ifdef WITH_STATS
 RADCLIENT_LIST *listener_find_client_list(fr_ipaddr_t const *ipaddr, uint16_t port, int proto)
 {
@@ -3648,23 +3675,114 @@ rad_listen_t *listener_find_byipaddr(fr_ipaddr_t const *ipaddr, uint16_t port, i
 }
 
 #ifdef WITH_COA_TUNNEL
+/*
+ *     This is easier than putting ifdef's everywhere.  And
+ *     realistically, there aren't many systems which have OpenSSL,
+ *     but not pthreads.
+ */
+#ifndef HAVE_PTHREAD_H
+#error CoA tunnels require pthreads
+#endif
+
+#include <pthread.h>
+
+static rbtree_t *coa_tree = NULL;
+
+typedef struct {
+       char            *key;
+       rad_listen_t    *first;
+
+       pthread_mutex_t mutex;          /* per key, to lower contention */
+} coa_key_t;
+
+static int coa_key_cmp(void const *one, void const *two)
+{
+       coa_key_t const *a = one;
+       coa_key_t const *b = two;
+
+       return strcmp(a->key, b->key);
+}
+
+static void coa_key_free(void *data)
+{
+       coa_key_t *coa_key = data;
+
+       talloc_free(coa_key->key);
+       rad_assert(coa_key->first == NULL);
+       pthread_mutex_destroy(&coa_key->mutex);
+}
+
+static int listen_coa_init(void)
+{
+       /*
+        *      We will be looking up listeners by key.  Each key
+        *      points us to a list of listeners.  Each key has it's
+        *      own mutex, so that it's thread-safe.
+        */
+       coa_tree = rbtree_create(NULL, coa_key_cmp, coa_key_free, RBTREE_FLAG_LOCK);
+       if (!coa_tree) {
+               ERROR("Failed creating internal tracking tree for Originating-Realm-Key");
+               return -1;
+       }
+
+       return 0;
+}
+
+static void listen_coa_free(void)
+{
+       /*
+        *      If we are freeing the tree, then all of the listeners
+        *      must have been freed first.
+        */
+       rad_assert(rbtree_num_elements(coa_tree) == 0);
+
+       rbtree_free(coa_tree);
+       coa_tree = NULL;
+}
+
 /*
  *     Adds a listener to the hash of listeners, based on key.
  */
 void listen_coa_add(rad_listen_t *this, char const *key)
 {
+       coa_key_t my_key, *coa_key;
+
        rad_assert(this->send_coa);
        rad_assert(this->parent);
+       rad_assert(!this->key);
 
        /*
-        *      We can use "this" as a context, because it's created
-        *      in the NULL context, so it's thread-safe.
+        *      Find the key.  If we can't find it, then create it.
         */
-       this->key = talloc_strdup(this, key);
+       memcpy(&my_key.key, &key, sizeof(key)); /* const issues */
+       coa_key = rbtree_finddata(coa_tree, &my_key);
+       if (!coa_key) {
+               coa_key = talloc_zero(NULL, coa_key_t);
+               if (!coa_key) return;
+               coa_key->key = talloc_strdup(coa_key, key);
+               if (!coa_key->key) {
+                       talloc_free(coa_key);
+                       return;
+               }
+               (void) pthread_mutex_init(&coa_key->mutex, NULL);
+
+               if (!rbtree_insert(coa_tree, coa_key)) {
+                       talloc_free(coa_key);
+                       return;
+               }
+       }
+
+       this->key = coa_key->key; /* no reason to duplicate the key */
+       this->coa_key = coa_key;
 
        /*
-        *      Do more things here.
+        *      We have a linked list of listeners for this key.  It
+        *      typically won't be large.
         */
+       pthread_mutex_lock(&coa_key->mutex);
+       this->next_key = coa_key->first;
+       coa_key->first = this;
+       pthread_mutex_unlock(&coa_key->mutex);
 }
 
 /*
@@ -3675,13 +3793,33 @@ void listen_coa_add(rad_listen_t *this, char const *key)
  */
 void listen_coa_delete(rad_listen_t *this)
 {
+       coa_key_t *coa_key;
+       rad_listen_t **last;
+
        rad_assert(this->send_coa);
        rad_assert(this->parent);
+       rad_assert(this->key);
+       rad_assert(this->coa_key);
 
+       coa_key = this->coa_key;
+       last = &coa_key->first;
+
+       pthread_mutex_lock(&coa_key->mutex);
+       while (*last) {
+               if (*last == this) {
+                       *last = this->next_key;
+                       break;
+               }
+               last = &((*last)->next_key);
+       }
+       pthread_mutex_unlock(&coa_key->mutex);
 
        /*
-        *      Do more things here.
+        *      No longer used for anything
         */
+       this->key = NULL;
+       this->coa_key = NULL;
+       this->next_key = NULL;
 }
 
 /*
@@ -3690,11 +3828,61 @@ void listen_coa_delete(rad_listen_t *this)
  *     This function will update request->home_server, and
  *     request->proxy_listener.
  */
-int listen_coa_find(UNUSED REQUEST *request, UNUSED char const *key)
+int listen_coa_find(REQUEST *request, char const *key)
 {
+       coa_key_t my_key, *coa_key;
+       rad_listen_t *this, *found;
+       listen_socket_t *sock;
+
        /*
-        *      Do more things here.
+        *      Find the key.  If we can't find it, then error out.
         */
-       return -1;
+       memcpy(&my_key.key, &key, sizeof(key)); /* const issues */
+       coa_key = rbtree_finddata(coa_tree, &my_key);
+       if (!coa_key) return -1;
+
+       /*
+        *      We've found it.  Now find a listener which has free
+        *      IDs.  i.e. where the number of used IDs is less tahn
+        *      256.
+        */
+       found = NULL;
+       pthread_mutex_lock(&coa_key->mutex);
+       for (this = coa_key->first;
+            this != NULL;
+            this = this->next_key) {
+               if (!found && (this->num_ids_used < 256)) {
+                       found = this;
+                       continue;
+               }
+
+               /*
+                *      Try to spread the load across all available
+                *      sockets.
+                */
+               if (found->num_ids_used > this->num_ids_used) {
+                       found = this;
+                       continue;
+               }
+
+               /*
+                *      If they are equal, pick one at random.
+                */
+               if (found->num_ids_used == this->num_ids_used) {
+                       if ((fr_rand() & 0x01) == 0) {
+                               found = this;
+                               continue;
+                       }
+               }
+       }
+
+       pthread_mutex_lock(&coa_key->mutex);
+       if (!found) return -1;
+
+       request->proxy_listener = found;
+
+       sock = found->data;
+       request->home_server = sock->home;
+       return 0;
 }
 #endif
index db110ed6f54e9231e5dfd25d90a612d5af92c171..1e8627bea37eddab367777f575032f3abdc64033 100644 (file)
@@ -1205,7 +1205,7 @@ int main_config_free(void)
         */
        client_list_free(NULL);
        realms_free();
-       listen_free(&main_config.listen);
+       listen_free_all(&main_config.listen);
 
        /*
         *      Frees current config and any previous configs.