]> git.ipfire.org Git - thirdparty/openldap.git/commitdiff
ITS#10265 Allow runtime reconfig of olcBkLloadListen
authorOndřej Kuzník <ondra@mistotebe.net>
Wed, 14 Aug 2024 09:57:42 +0000 (10:57 +0100)
committerQuanah Gibson-Mount <quanah@openldap.org>
Wed, 19 Feb 2025 18:39:09 +0000 (18:39 +0000)
servers/lloadd/client.c
servers/lloadd/config.c
servers/lloadd/daemon.c
servers/lloadd/lload.h
servers/lloadd/main.c
servers/lloadd/module_init.c
servers/lloadd/proto-lload.h
servers/slapd/daemon.c

index 3ff6d3b0bc776caa318f02e48faf612d1b3fcc55..58472fdd7ad5e05066d55d247d883ab9e51c4c7d 100644 (file)
@@ -538,7 +538,7 @@ fail:
 LloadConnection *
 client_init(
         ber_socket_t s,
-        struct berval *localname,
+        LloadListenerSocket *ls,
         struct berval *peername,
         struct event_base *base,
         int flags )
@@ -549,7 +549,7 @@ client_init(
                       write_cb = connection_write_cb;
 
     if ( (c = lload_connection_init(
-                    s, localname, peername, flags )) == NULL ) {
+                    s, &ls->ls_name, peername, flags )) == NULL ) {
         return NULL;
     }
 
@@ -559,6 +559,7 @@ client_init(
     }
 
     c->c_state = LLOAD_C_READY;
+    c->c_listener = ls;
 
     if ( flags & CONN_IS_TLS ) {
 #ifdef HAVE_TLS
index e986b79123a5a4ba7893c3bf1eed9e63474bff6d..4fbb66cca699388cd5fe3bae4155cc15f03b3cb5 100644 (file)
@@ -276,7 +276,7 @@ static ConfigTable config_back_cf_table[] = {
         "( OLcfgBkAt:13.5 "
             "NAME 'olcBkLloadListen' "
             "DESC 'A listener adress' "
-            /* We don't handle adding/removing a value, so no EQUALITY yet */
+            "EQUALITY caseExactMatch "
             "SYNTAX OMsDirectoryString )",
         NULL, NULL
     },
@@ -800,7 +800,6 @@ static ConfigOCs lloadocs[] = {
         "SUP olcBackendConfig "
         "MUST ( olcBkLloadBindconf "
             "$ olcBkLloadIOThreads "
-            "$ olcBkLloadListen "
             "$ olcBkLloadSockbufMaxClient "
             "$ olcBkLloadSockbufMaxUpstream "
             "$ olcBkLloadMaxPDUPerCycle "
@@ -824,6 +823,7 @@ static ConfigOCs lloadocs[] = {
             "$ olcBkLloadWriteCoherence "
             "$ olcBkLloadRestrictExop "
             "$ olcBkLloadRestrictControl "
+            "$ olcBkLloadListen "
         ") )",
         Cft_Backend, config_back_cf_table,
         NULL,
@@ -884,12 +884,6 @@ config_generic( ConfigArgs *c )
                 struct berval bv = BER_BVNULL;
 
                 for ( ; ll && *ll; ll++ ) {
-                    /* The same url could have spawned several consecutive
-                     * listeners */
-                    if ( !BER_BVISNULL( &bv ) &&
-                            !ber_bvcmp( &bv, &(*ll)->sl_url ) ) {
-                        continue;
-                    }
                     ber_dupbv( &bv, &(*ll)->sl_url );
                     ber_bvarray_add( &c->rvalue_vals, &bv );
                 }
@@ -918,8 +912,47 @@ config_generic( ConfigArgs *c )
 
     } else if ( c->op == LDAP_MOD_DELETE ) {
         /* We only need to worry about deletions to multi-value or MAY
-         * attributes that belong to the lloadd module - we don't have any at
-         * the moment */
+         * attributes that belong to the lloadd module */
+        switch ( c->type ) {
+            case CFG_LISTEN_URI: {
+                LloadListener **ll = lloadd_get_listeners();
+                int i;
+
+                lload_change.type = LLOAD_CHANGE_MODIFY;
+                lload_change.object = LLOAD_DAEMON;
+                lload_change.flags.daemon |= LLOAD_DAEMON_MOD_LISTENER;
+
+                /*
+                 * Be as non-destructive as possible, the modify could be
+                 * aborted later and if we can't open the socket again, the
+                 * only alternative would be to stop the server.
+                 *
+                 * This prohibits changes where exchanging urls that aren't the
+                 * same but overlap. People can always split them into multiple
+                 * operations - make simple things easy and complex possible I
+                 * guess?
+                 */
+                if ( c->valx == -1 ) {
+                    for ( i = 0; ll[i]; i++ ) {
+                        ll[i]->sl_removed = 1;
+                    }
+                } else {
+                    /* We don't keep listeners in the same order, need to check
+                     * which one it is */
+                    struct berval bv;
+                    ber_str2bv( c->line, 0, 0, &bv );
+
+                    for ( i = 0; ll[i]; i++ ) {
+                        if ( ber_bvcmp( &ll[i]->sl_url, &bv ) == 0 ) break;
+                    }
+
+                    assert( ll[i] && !ll[i]->sl_removed );
+                    ll[i]->sl_removed = 1;
+                }
+            } break;
+            default:
+                break;
+        }
         return rc;
     }
 
@@ -948,7 +981,10 @@ config_generic( ConfigArgs *c )
             break;
         case CFG_LISTEN_URI: {
             LDAPURLDesc *lud;
-            LloadListener *l;
+            LloadListener *l, **ll;
+            struct berval bv;
+
+            ber_str2bv( c->line, 0, 0, &bv );
 
             if ( ldap_url_parse_ext(
                          c->line, &lud, LDAP_PVT_URL_PARSE_DEF_PORT ) ) {
@@ -964,21 +1000,29 @@ config_generic( ConfigArgs *c )
                         "Load Balancer already configured to listen on %s "
                         "(while adding %s)",
                         l->sl_url.bv_val, c->line );
+                ldap_free_urldesc( lud );
                 goto fail;
             }
 
-            if ( !lloadd_inited ) {
-                if ( lload_open_new_listener( c->line, lud ) ) {
-                    snprintf( c->cr_msg, sizeof(c->cr_msg),
-                            "could not open a listener for %s", c->line );
-                    goto fail;
-                }
-            } else {
+            ll = lloadd_get_listeners();
+            for ( ; ll && *ll; ll++ ) {
+                if ( !(*ll)->sl_removed ||
+                        ber_bvcmp( &(*ll)->sl_url, &bv ) ) continue;
+                /* Restoring a removed listener URL */
+                (*ll)->sl_removed = 0;
+                break;
+            }
+
+            l = lload_configure_listener( c->line, lud );
+            if ( !l ) {
                 snprintf( c->cr_msg, sizeof(c->cr_msg),
-                        "listener changes will not take effect until restart: "
-                        "%s",
-                        c->line );
-                Debug( LDAP_DEBUG_ANY, "%s: %s\n", c->log, c->cr_msg );
+                        "could not configure a listener for %s", c->line );
+                goto fail;
+            }
+            if ( lload_open_new_listener( l ) ) {
+                snprintf( c->cr_msg, sizeof(c->cr_msg),
+                        "could not open a listener for %s", c->line );
+                goto fail;
             }
         } break;
         case CFG_THREADS:
index 56c244fd943488217c4cbdf7d9a942ac3d5e3d28..af05c4c4018939d90369cd0dc4c7c0caabb1568a 100644 (file)
@@ -87,6 +87,16 @@ ldap_pvt_thread_cond_t lload_pause_cond;
 int lload_daemon_threads = 1;
 int lload_daemon_mask;
 
+/*
+ * We might be a module, so concerns about listeners are different from slapd,
+ * instead they are set up in three phases:
+ * 1. parse urls to set up (LloadListener *) in configuration/main()
+ * 2. resolve socket names and bind() just before going online
+ *    Unlike slapd or standalone, module lloadd cannot see configuration
+ *    (acquire sockets) prior to privileges being dropped. Admins should use
+ *    CAP_NET_BIND_SERVICE on Linux, or similar elsewhere
+ * 3. as we go online, allocate them to the listener base
+ */
 struct event_base *listener_base = NULL;
 LloadListener **lload_listeners = NULL;
 static ldap_pvt_thread_t listener_tid, *daemon_tid;
@@ -152,33 +162,19 @@ lloadd_close( ber_socket_t s )
     tcp_close( s );
 }
 
-static void
-lload_free_listener_addresses( struct sockaddr **sal )
-{
-    struct sockaddr **sap;
-    if ( sal == NULL ) return;
-    for ( sap = sal; *sap != NULL; sap++ )
-        ch_free(*sap);
-    ch_free( sal );
-}
-
 #if defined(LDAP_PF_LOCAL) || defined(SLAP_X_LISTENER_MOD)
 static int
-get_url_perms( char **exts, mode_t *perms, int *crit )
+get_url_perms( char **exts, mode_t *perms )
 {
     int i;
 
     assert( exts != NULL );
     assert( perms != NULL );
-    assert( crit != NULL );
 
-    *crit = 0;
     for ( i = 0; exts[i]; i++ ) {
         char *type = exts[i];
-        int c = 0;
 
         if ( type[0] == '!' ) {
-            c = 1;
             type++;
         }
 
@@ -226,7 +222,6 @@ get_url_perms( char **exts, mode_t *perms, int *crit )
                     return LDAP_OTHER;
             }
 
-            *crit = c;
             *perms = p;
 
             return LDAP_SUCCESS;
@@ -237,45 +232,99 @@ get_url_perms( char **exts, mode_t *perms, int *crit )
 }
 #endif /* LDAP_PF_LOCAL || SLAP_X_LISTENER_MOD */
 
-/* port = 0 indicates AF_LOCAL */
+void
+lload_listener_free( LloadListener *l )
+{
+    LloadListenerSocket *next, *ls = l->sl_sockets;
+
+    for ( ; ls; ls = next ) {
+        next = ls->ls_next;
+
+        if ( ls->listener ) {
+            evconnlistener_free( ls->listener );
+        }
+
+#ifdef LDAP_PF_LOCAL
+        if ( ls->ls_sa.sa_addr.sa_family == AF_LOCAL ) {
+            unlink( ls->ls_sa.sa_un_addr.sun_path );
+        }
+#endif /* LDAP_PF_LOCAL */
+        lloadd_close( ls->ls_sd );
+
+        if ( ls->ls_name.bv_val ) {
+            ber_memfree( ls->ls_name.bv_val );
+        }
+        ch_free( ls );
+    }
+
+    if ( l->sl_url.bv_val ) {
+        ber_memfree( l->sl_url.bv_val );
+    }
+    ch_free( l );
+}
+
 static int
 lload_get_listener_addresses(
-        const char *host,
-        unsigned short port,
-        struct sockaddr ***sal )
+        LloadListener *l,
+        LDAPURLDesc *lud,
+        LloadListenerSocket **lsp )
 {
-    struct sockaddr **sap;
+    LloadListenerSocket **lsp_orig = lsp, *ls = NULL;
+    Sockaddr *sa;
+    char ebuf[LDAP_IPADDRLEN];
+    struct berval namebv = BER_BVC(ebuf);
+    char *host = lud->lud_host;
+    int proto = ldap_pvt_url_scheme2proto( lud->lud_scheme );
 
+    if ( proto == LDAP_PROTO_IPC ) {
+        struct sockaddr_un sun;
 #ifdef LDAP_PF_LOCAL
-    if ( port == 0 ) {
-        sap = *sal = ch_malloc( 2 * sizeof(void *) );
-
-        *sap = ch_calloc( 1, sizeof(struct sockaddr_un) );
-        sap[1] = NULL;
+        if ( !host || !*host ) {
+            host = LDAPI_SOCK;
+        }
 
-        if ( strlen( host ) >
-                ( sizeof( ((struct sockaddr_un *)*sap)->sun_path ) - 1 ) ) {
+        if ( strlen( host ) > ( sizeof(sun.sun_path) - 1 ) ) {
             Debug( LDAP_DEBUG_ANY, "lload_get_listener_addresses: "
                     "domain socket path (%s) too long in URL\n",
                     host );
-            goto errexit;
+            return -1;
         }
 
-        (*sap)->sa_family = AF_LOCAL;
-        strcpy( ((struct sockaddr_un *)*sap)->sun_path, host );
-    } else
-#endif /* LDAP_PF_LOCAL */
+        *lsp = ls = ch_calloc( 1, sizeof(LloadListenerSocket) );
+        ls->ls_lr = l;
+        ls->ls_sd = AC_SOCKET_INVALID;
+
+        sa = &ls->ls_sa;
+        ((struct sockaddr *)sa)->sa_family = AF_LOCAL;
+        strcpy( ((struct sockaddr_un *)sa)->sun_path, host );
+        ldap_pvt_sockaddrstr( sa, &namebv );
+        ber_dupbv( &ls->ls_name, &namebv );
+
+        return 0;
+#else /* ! LDAP_PF_LOCAL */
+
+        Debug( LDAP_DEBUG_ANY, "lload_get_listener_addresses: "
+                "URL scheme not supported: %s\n",
+                l->sl_url.bv_val );
+        return -1;
+#endif /* ! LDAP_PF_LOCAL */
+    }
+
+    if ( !host || !*host || strcmp( host, "*" ) == 0 ) {
+        host = NULL;
+    }
+
     {
 #ifdef HAVE_GETADDRINFO
         struct addrinfo hints, *res, *sai;
-        int n, err;
+        int err;
         char serv[7];
 
         memset( &hints, '\0', sizeof(hints) );
         hints.ai_flags = AI_PASSIVE;
         hints.ai_socktype = SOCK_STREAM;
         hints.ai_family = slap_inet4or6;
-        snprintf( serv, sizeof(serv), "%d", port );
+        snprintf( serv, sizeof(serv), "%d", lud->lud_port );
 
         if ( (err = getaddrinfo( host, serv, &hints, &res )) ) {
             Debug( LDAP_DEBUG_ANY, "lload_get_listener_addresses: "
@@ -284,53 +333,49 @@ lload_get_listener_addresses(
             return -1;
         }
 
-        sai = res;
-        for ( n = 2; ( sai = sai->ai_next ) != NULL; n++ ) {
-            /* EMPTY */;
-        }
-        sap = *sal = ch_calloc( n, sizeof(void *) );
-
-        *sap = NULL;
-
         for ( sai = res; sai; sai = sai->ai_next ) {
             if ( sai->ai_addr == NULL ) {
                 Debug( LDAP_DEBUG_ANY, "lload_get_listener_addresses: "
                         "getaddrinfo ai_addr is NULL?\n" );
                 freeaddrinfo( res );
-                goto errexit;
+                return -1;
             }
 
+            ls = ch_calloc( 1, sizeof(LloadListenerSocket) );
+            ls->ls_lr = l;
+            ls->ls_sd = AC_SOCKET_INVALID;
+            sa = &ls->ls_sa;
+
             switch ( sai->ai_family ) {
 #ifdef LDAP_PF_INET6
                 case AF_INET6:
-                    *sap = ch_malloc( sizeof(struct sockaddr_in6) );
-                    *(struct sockaddr_in6 *)*sap =
-                            *((struct sockaddr_in6 *)sai->ai_addr);
+                    *(struct sockaddr_in6 *)sa =
+                        *((struct sockaddr_in6 *)sai->ai_addr);
                     break;
 #endif /* LDAP_PF_INET6 */
                 case AF_INET:
-                    *sap = ch_malloc( sizeof(struct sockaddr_in) );
-                    *(struct sockaddr_in *)*sap =
-                            *((struct sockaddr_in *)sai->ai_addr);
+                    *(struct sockaddr_in *)sa =
+                        *((struct sockaddr_in *)sai->ai_addr);
                     break;
                 default:
-                    *sap = NULL;
-                    break;
+                    /* We don't know how to use this one, skip */
+                    goto skip;
             }
+            ((struct sockaddr *)sa)->sa_family = sai->ai_family;
+            ldap_pvt_sockaddrstr( sa, &namebv );
+            ber_dupbv( &ls->ls_name, &namebv );
 
-            if ( *sap != NULL ) {
-                (*sap)->sa_family = sai->ai_family;
-                sap++;
-                *sap = NULL;
-            }
+            *lsp = ls;
+            lsp = &ls->ls_next;
         }
 
         freeaddrinfo( res );
 
 #else /* ! HAVE_GETADDRINFO */
-        int i, n = 1;
+        int i = 0;
         struct in_addr in;
         struct hostent *he = NULL;
+        struct sockaddr_in *sin;
 
         if ( host == NULL ) {
             in.s_addr = htonl( INADDR_ANY );
@@ -343,139 +388,98 @@ lload_get_listener_addresses(
                         host );
                 return -1;
             }
-            for ( n = 0; he->h_addr_list[n]; n++ ) /* empty */;
         }
 
-        sap = *sal = ch_malloc( ( n + 1 ) * sizeof(void *) );
+        do {
+            *lsp = ls = ch_calloc( 1, sizeof(LloadListenerSocket) );
+            ls->ls_lr = l;
 
-        for ( i = 0; i < n; i++ ) {
-            sap[i] = ch_calloc( 1, sizeof(struct sockaddr_in) );
-            sap[i]->sa_family = AF_INET;
-            ((struct sockaddr_in *)sap[i])->sin_port = htons( port );
-            AC_MEMCPY( &((struct sockaddr_in *)sap[i])->sin_addr,
+            sin = (struct sockaddr_in *)&ls->ls_sa;
+            sin->sa_family = AF_INET;
+            sin->sin_port = htons( lud->lud_port );
+
+            AC_MEMCPY( &sin->sin_addr,
                     he ? (struct in_addr *)he->h_addr_list[i] : &in,
                     sizeof(struct in_addr) );
-        }
-        sap[i] = NULL;
+
+            ldap_pvt_sockaddrstr( (Sockaddr *)sin, &namebv );
+            ber_dupbv( &ls->ls_name, &namebv );
+            i++;
+        } while ( he && he->h_addr_list[i] );
 #endif /* ! HAVE_GETADDRINFO */
     }
 
-    return 0;
+    return !ls;
 
-errexit:
-    lload_free_listener_addresses(*sal);
+skip:
+    ls = *lsp_orig;
+    while ( ls ) {
+        LloadListenerSocket *next = ls->ls_next;
+        ch_free( ls );
+        ls = next;
+    }
     return -1;
 }
 
-static int
-lload_open_listener(
+LloadListener *
+lload_configure_listener(
         const char *url,
-        LDAPURLDesc *lud,
-        int *listeners,
-        int *cur )
+        LDAPURLDesc *lud )
 {
-    int num, tmp, rc;
-    LloadListener l;
-    LloadListener *li;
-    unsigned short port;
-    int err, addrlen = 0;
-    struct sockaddr **sal = NULL, **psal;
-    int socktype = SOCK_STREAM; /* default to COTS */
-    ber_socket_t s;
+    LloadListener *l;
+    LloadListenerSocket *ls, *next, **prev;
     char ebuf[LDAP_IPADDRLEN];
     struct berval namebv = BER_BVC(ebuf);
-
-#if defined(LDAP_PF_LOCAL) || defined(SLAP_X_LISTENER_MOD)
-    /*
-     * use safe defaults
-     */
-    int crit = 1;
-#endif /* LDAP_PF_LOCAL || SLAP_X_LISTENER_MOD */
+    int socktype = SOCK_STREAM; /* default to COTS */
+    int addrlen = 0;
+    int tmp, rc;
 
     assert( url );
     assert( lud );
 
-    l.sl_url.bv_val = NULL;
-    l.sl_mute = 0;
-    l.sl_busy = 0;
+    l = ch_calloc( 1, sizeof(LloadListener) );
+
+    if ( !lud->lud_port ) lud->lud_port = LDAP_PORT;
 
 #ifndef HAVE_TLS
     if ( ldap_pvt_url_scheme2tls( lud->lud_scheme ) ) {
-        Debug( LDAP_DEBUG_ANY, "lload_open_listener: "
+        Debug( LDAP_DEBUG_ANY, "lload_configure_listener: "
                 "TLS not supported (%s)\n",
                 url );
         ldap_free_urldesc( lud );
-        return -1;
+        return NULL;
     }
 
-    if ( !lud->lud_port ) lud->lud_port = LDAP_PORT;
-
 #else /* HAVE_TLS */
-    l.sl_is_tls = ldap_pvt_url_scheme2tls( lud->lud_scheme );
+    l->sl_is_tls = ldap_pvt_url_scheme2tls( lud->lud_scheme );
 #endif /* HAVE_TLS */
 
-    l.sl_is_proxied = ldap_pvt_url_scheme2proxied( lud->lud_scheme );
-
-#ifdef LDAP_TCP_BUFFER
-    l.sl_tcp_rmem = 0;
-    l.sl_tcp_wmem = 0;
-#endif /* LDAP_TCP_BUFFER */
-
-    port = (unsigned short)lud->lud_port;
-
-    tmp = ldap_pvt_url_scheme2proto( lud->lud_scheme );
-    if ( tmp == LDAP_PROTO_IPC ) {
-#ifdef LDAP_PF_LOCAL
-        if ( lud->lud_host == NULL || lud->lud_host[0] == '\0' ) {
-            err = lload_get_listener_addresses( LDAPI_SOCK, 0, &sal );
-        } else {
-            err = lload_get_listener_addresses( lud->lud_host, 0, &sal );
-        }
-#else /* ! LDAP_PF_LOCAL */
-
-        Debug( LDAP_DEBUG_ANY, "lload_open_listener: "
-                "URL scheme not supported: %s\n",
-                url );
-        ldap_free_urldesc( lud );
-        return -1;
-#endif /* ! LDAP_PF_LOCAL */
-    } else {
-        if ( lud->lud_host == NULL || lud->lud_host[0] == '\0' ||
-                strcmp( lud->lud_host, "*" ) == 0 ) {
-            err = lload_get_listener_addresses( NULL, port, &sal );
-        } else {
-            err = lload_get_listener_addresses( lud->lud_host, port, &sal );
-        }
-    }
-
 #if defined(LDAP_PF_LOCAL) || defined(SLAP_X_LISTENER_MOD)
     if ( lud->lud_exts ) {
-        err = get_url_perms( lud->lud_exts, &l.sl_perms, &crit );
+        if ( get_url_perms( lud->lud_exts, &l->sl_perms ) ) {
+            ldap_free_urldesc( lud );
+            return NULL;
+        }
     } else {
-        l.sl_perms = S_IRWXU | S_IRWXO;
+        l->sl_perms = S_IRWXU | S_IRWXO;
     }
 #endif /* LDAP_PF_LOCAL || SLAP_X_LISTENER_MOD */
 
-    ldap_free_urldesc( lud );
-    if ( err ) {
-        lload_free_listener_addresses( sal );
-        return -1;
-    }
+    l->sl_is_proxied = ldap_pvt_url_scheme2proxied( lud->lud_scheme );
 
-    /* If we got more than one address returned, we need to make space
-     * for it in the lload_listeners array.
-     */
-    for ( num = 0; sal[num]; num++ ) /* empty */;
-    if ( num > 1 ) {
-        *listeners += num - 1;
-        lload_listeners = ch_realloc( lload_listeners,
-                ( *listeners + 1 ) * sizeof(LloadListener *) );
+    if ( lload_get_listener_addresses( l, lud, &l->sl_sockets ) ) {
+        ldap_free_urldesc( lud );
+        return NULL;
     }
+    ldap_free_urldesc( lud );
 
-    psal = sal;
-    while ( *sal != NULL ) {
+    for ( ls = l->sl_sockets, prev = &l->sl_sockets; ls; ls = next ) {
+        struct sockaddr *sa = (struct sockaddr *)&ls->ls_sa;
+        ber_socket_t s;
         char *af;
-        switch ( (*sal)->sa_family ) {
+
+        next = ls->ls_next;
+        switch ( sa->sa_family ) {
             case AF_INET:
                 af = "IPv4";
                 break;
@@ -490,25 +494,34 @@ lload_open_listener(
                 break;
 #endif /* LDAP_PF_LOCAL */
             default:
-                sal++;
-                continue;
+                Debug( LDAP_DEBUG_ANY, "lload_configure_listener: "
+                        "unsupported address family (%d)\n",
+                        (int)sa->sa_family );
+                goto skip;
         }
 
-        s = socket( (*sal)->sa_family, socktype, 0 );
+#ifdef BALANCER_MODULE
+        if ( !(slapMode & SLAP_SERVER_MODE) ) {
+            /* This is as much validation as we can (safely) do short of proper
+             * startup */
+            continue;
+        }
+#endif
+
+        s = socket( sa->sa_family, socktype, 0 );
         if ( s == AC_SOCKET_INVALID ) {
             int err = sock_errno();
-            Debug( LDAP_DEBUG_ANY, "lload_open_listener: "
+            Debug( LDAP_DEBUG_ANY, "lload_configure_listener: "
                     "%s socket() failed errno=%d (%s)\n",
                     af, err, sock_errstr( err, ebuf, sizeof(ebuf) ) );
-            sal++;
-            continue;
+            goto skip;
         }
         ber_pvt_socket_set_nonblock( s, 1 );
-        l.sl_sd = s;
+        ls->ls_sd = s;
 
 #ifdef LDAP_PF_LOCAL
-        if ( (*sal)->sa_family == AF_LOCAL ) {
-            unlink( ((struct sockaddr_un *)*sal)->sun_path );
+        if ( sa->sa_family == AF_LOCAL ) {
+            unlink( ((struct sockaddr_un *)sa)->sun_path );
         } else
 #endif /* LDAP_PF_LOCAL */
         {
@@ -519,15 +532,14 @@ lload_open_listener(
                     s, SOL_SOCKET, SO_REUSEADDR, (char *)&tmp, sizeof(tmp) );
             if ( rc == AC_SOCKET_ERROR ) {
                 int err = sock_errno();
-                Debug( LDAP_DEBUG_ANY, "lload_open_listener(%ld): "
+                Debug( LDAP_DEBUG_ANY, "lload_configure_listener(%ld): "
                         "setsockopt(SO_REUSEADDR) failed errno=%d (%s)\n",
-                        (long)l.sl_sd, err,
-                        sock_errstr( err, ebuf, sizeof(ebuf) ) );
+                        (long)s, err, sock_errstr( err, ebuf, sizeof(ebuf) ) );
             }
 #endif /* SO_REUSEADDR */
         }
 
-        switch ( (*sal)->sa_family ) {
+        switch ( sa->sa_family ) {
             case AF_INET:
                 addrlen = sizeof(struct sockaddr_in);
                 break;
@@ -540,9 +552,9 @@ lload_open_listener(
                         sizeof(tmp) );
                 if ( rc == AC_SOCKET_ERROR ) {
                     int err = sock_errno();
-                    Debug( LDAP_DEBUG_ANY, "lload_open_listener(%ld): "
+                    Debug( LDAP_DEBUG_ANY, "lload_configure_listener(%ld): "
                             "setsockopt(IPV6_V6ONLY) failed errno=%d (%s)\n",
-                            (long)l.sl_sd, err,
+                            (long)s, err,
                             sock_errstr( err, ebuf, sizeof(ebuf) ) );
                 }
 #endif /* IPV6_V6ONLY */
@@ -575,11 +587,11 @@ lload_open_listener(
         {
             mode_t old_umask = 0;
 
-            if ( (*sal)->sa_family == AF_LOCAL ) {
+            if ( sa->sa_family == AF_LOCAL ) {
                 old_umask = umask( 0 );
             }
 #endif /* LDAP_PF_LOCAL */
-            rc = bind( s, *sal, addrlen );
+            rc = bind( s, sa, addrlen );
 #ifdef LDAP_PF_LOCAL
             if ( old_umask != 0 ) {
                 umask( old_umask );
@@ -587,156 +599,72 @@ lload_open_listener(
         }
 #endif /* LDAP_PF_LOCAL */
         if ( rc ) {
-            err = sock_errno();
-            Debug( LDAP_DEBUG_ANY, "lload_open_listener: "
+            int err = sock_errno();
+            Debug( LDAP_DEBUG_ANY, "lload_configure_listener: "
                     "bind(%ld) failed errno=%d (%s)\n",
-                    (long)l.sl_sd, err,
-                    sock_errstr( err, ebuf, sizeof(ebuf) ) );
+                    (long)s, err, sock_errstr( err, ebuf, sizeof(ebuf) ) );
             tcp_close( s );
-            sal++;
-            continue;
-        }
-
-        switch ( (*sal)->sa_family ) {
-#ifdef LDAP_PF_LOCAL
-            case AF_LOCAL: /* {
-                char *path = ((struct sockaddr_un *)*sal)->sun_path;
-                l.sl_name.bv_len = strlen( path ) + STRLENOF("PATH=");
-                l.sl_name.bv_val = ch_malloc( l.sl_name.bv_len + 1 );
-                snprintf( l.sl_name.bv_val, l.sl_name.bv_len + 1, "PATH=%s",
-                        path );
-            } break; */
-#endif /* LDAP_PF_LOCAL */
-
-            case AF_INET:
-#ifdef LDAP_PF_INET6
-            case AF_INET6:
-#endif /* LDAP_PF_INET6 */
-                ldap_pvt_sockaddrstr( (Sockaddr *)*sal, &namebv );
-                ber_dupbv( &l.sl_name, &namebv );
-                break;
-
-            default:
-                Debug( LDAP_DEBUG_ANY, "lload_open_listener: "
-                        "unsupported address family (%d)\n",
-                        (int)(*sal)->sa_family );
-                break;
+            ls->ls_sd = AC_SOCKET_INVALID;
+            goto skip;
         }
 
-        AC_MEMCPY( &l.sl_sa, *sal, addrlen );
-        ber_str2bv( url, 0, 1, &l.sl_url );
-        li = ch_malloc( sizeof(LloadListener) );
-        *li = l;
-        lload_listeners[*cur] = li;
-        (*cur)++;
-        sal++;
+        prev = &ls->ls_next;
+        continue;
+skip:
+        ber_memfree( ls->ls_name.bv_val );
+        ch_free( ls );
+        *prev = next;
     }
 
-    lload_free_listener_addresses( psal );
-
-    if ( l.sl_url.bv_val == NULL ) {
-        Debug( LDAP_DEBUG_ANY, "lload_open_listener: "
+    if ( !l->sl_sockets ) {
+        Debug( LDAP_DEBUG_ANY, "lload_configure_listener: "
                 "failed on %s\n",
                 url );
-        return -1;
+        return NULL;
     }
+    ber_str2bv( url, 0, 1, &l->sl_url );
 
-    Debug( LDAP_DEBUG_TRACE, "lload_open_listener: "
+    Debug( LDAP_DEBUG_TRACE, "lload_configure_listener: "
             "listener initialized %s\n",
-            l.sl_url.bv_val );
-
-    return 0;
-}
-
-int
-lload_open_new_listener( const char *url, LDAPURLDesc *lud )
-{
-    int rc, i, j = 0;
-
-    for ( i = 0; lload_listeners && lload_listeners[i] != NULL;
-            i++ ) /* count */
-        ;
-    j = i;
-
-    i++;
-    lload_listeners = ch_realloc(
-            lload_listeners, ( i + 1 ) * sizeof(LloadListener *) );
+            l->sl_url.bv_val );
 
-    rc = lload_open_listener( url, lud, &i, &j );
-    lload_listeners[j] = NULL;
-    return rc;
+    return l;
 }
 
 int lloadd_inited = 0;
 
-int
-lloadd_listeners_init( const char *urls )
+static void
+listener_error_cb( struct evconnlistener *lev, void *arg )
 {
-    int i, j, n;
-    char **u;
-    LDAPURLDesc *lud;
-
-    Debug( LDAP_DEBUG_ARGS, "lloadd_listeners_init: %s\n",
-            urls ? urls : "<null>" );
-
-#ifdef HAVE_TCPD
-    ldap_pvt_thread_mutex_init( &sd_tcpd_mutex );
-#endif /* TCP Wrappers */
-
-    if ( urls == NULL ) urls = "ldap:///";
-
-    u = ldap_str2charray( urls, " " );
-
-    if ( u == NULL || u[0] == NULL ) {
-        Debug( LDAP_DEBUG_ANY, "lloadd_listeners_init: "
-                "no urls (%s) provided\n",
-                urls );
-        if ( u ) ldap_charray_free( u );
-        return -1;
-    }
-
-    for ( i = 0; u[i] != NULL; i++ ) {
-        Debug( LDAP_DEBUG_TRACE, "lloadd_listeners_init: "
-                "listen on %s\n",
-                u[i] );
-    }
-
-    if ( i == 0 ) {
-        Debug( LDAP_DEBUG_ANY, "lloadd_listeners_init: "
-                "no listeners to open (%s)\n",
-                urls );
-        ldap_charray_free( u );
-        return -1;
-    }
-
-    Debug( LDAP_DEBUG_TRACE, "lloadd_listeners_init: "
-            "%d listeners to open...\n",
-            i );
-    lload_listeners = ch_malloc( ( i + 1 ) * sizeof(LloadListener *) );
-
-    for ( n = 0, j = 0; u[n]; n++ ) {
-        if ( ldap_url_parse_ext( u[n], &lud, LDAP_PVT_URL_PARSE_DEF_PORT ) ) {
-            Debug( LDAP_DEBUG_ANY, "lloadd_listeners_init: "
-                    "could not parse url %s\n",
-                    u[n] );
-            ldap_charray_free( u );
-            return -1;
-        }
+    LloadListenerSocket *ls = arg;
+    int err = EVUTIL_SOCKET_ERROR();
 
-        if ( lload_open_listener( u[n], lud, &i, &j ) ) {
-            ldap_charray_free( u );
-            return -1;
-        }
+    assert( ls->listener == lev );
+    if (
+#ifdef EMFILE
+            err == EMFILE ||
+#endif /* EMFILE */
+#ifdef ENFILE
+            err == ENFILE ||
+#endif /* ENFILE */
+            0 ) {
+        ldap_pvt_thread_mutex_lock( &lload_daemon[0].sd_mutex );
+        emfile++;
+        /* Stop listening until an existing session closes */
+        ls->ls_mute = 1;
+        evconnlistener_disable( lev );
+        ldap_pvt_thread_mutex_unlock( &lload_daemon[0].sd_mutex );
+        Debug( LDAP_DEBUG_ANY, "listener_error_cb: "
+                "too many open files, cannot accept new connections on "
+                "url=%s\n",
+                ls->ls_lr->sl_url.bv_val );
+    } else {
+        char ebuf[128];
+        Debug( LDAP_DEBUG_ANY, "listener_error_cb: "
+                "received an error on a listener, shutting down: '%s'\n",
+                sock_errstr( err, ebuf, sizeof(ebuf) ) );
+        event_base_loopexit( ls->base, NULL );
     }
-    lload_listeners[j] = NULL;
-
-    Debug( LDAP_DEBUG_TRACE, "lloadd_listeners_init: "
-            "%d listeners opened\n",
-            i );
-
-    ldap_charray_free( u );
-
-    return !i;
 }
 
 int
@@ -774,33 +702,17 @@ lloadd_daemon_destroy( void )
 static void
 destroy_listeners( void )
 {
-    LloadListener *lr, **ll = lload_listeners;
+    LloadListener *l, **ll = lload_listeners;
 
     if ( ll == NULL ) return;
 
     ldap_pvt_thread_join( listener_tid, (void *)NULL );
 
-    while ( (lr = *ll++) != NULL ) {
-        if ( lr->sl_url.bv_val ) {
-            ber_memfree( lr->sl_url.bv_val );
-        }
-
-        if ( lr->sl_name.bv_val ) {
-            ber_memfree( lr->sl_name.bv_val );
-        }
-
-#ifdef LDAP_PF_LOCAL
-        if ( lr->sl_sa.sa_addr.sa_family == AF_LOCAL ) {
-            unlink( lr->sl_sa.sa_un_addr.sun_path );
-        }
-#endif /* LDAP_PF_LOCAL */
-
-        evconnlistener_free( lr->listener );
-
-        free( lr );
+    while ( (l = *ll++) != NULL ) {
+        lload_listener_free( l );
     }
 
-    free( lload_listeners );
+    ch_free( lload_listeners );
     lload_listeners = NULL;
 
     if ( listener_base ) {
@@ -816,29 +728,22 @@ lload_listener(
         int len,
         void *arg )
 {
-    LloadListener *sl = arg;
+    LloadListenerSocket *ls = arg;
+    LloadListener *l = ls->ls_lr;
     LloadConnection *c;
     Sockaddr *from = (Sockaddr *)a;
     char peername[LDAP_IPADDRLEN];
     struct berval peerbv = BER_BVC(peername);
     int cflag;
-    int tid;
+    int tid = DAEMON_ID(s);
     char ebuf[128];
 
-    Debug( LDAP_DEBUG_TRACE, ">>> lload_listener(%s)\n", sl->sl_url.bv_val );
-
     peername[0] = '\0';
 
-    /* Resume the listener FD to allow concurrent-processing of
-     * additional incoming connections.
-     */
-    sl->sl_busy = 0;
-
-    tid = DAEMON_ID(s);
-
+    Debug( LDAP_DEBUG_TRACE, ">>> lload_listener(%s)\n", l->sl_url.bv_val );
     Debug( LDAP_DEBUG_CONNS, "lload_listener: "
             "listen=%ld, new connection fd=%ld\n",
-            (long)sl->sl_sd, (long)s );
+            (long)ls->ls_sd, (long)s );
 
 #if defined(SO_KEEPALIVE) || defined(TCP_NODELAY)
 #ifdef LDAP_PF_LOCAL
@@ -875,7 +780,7 @@ lload_listener(
     }
 #endif /* SO_KEEPALIVE || TCP_NODELAY */
 
-    if ( sl->sl_is_proxied ) {
+    if ( l->sl_is_proxied ) {
         if ( !proxyp( s, from ) ) {
             Debug( LDAP_DEBUG_ANY, "lload_listener: "
                     "proxyp(%ld) failed\n",
@@ -891,9 +796,9 @@ lload_listener(
         case AF_LOCAL:
             cflag |= CONN_IS_IPC;
 
-            /* FIXME: apparently accept doesn't fill the sun_path member */
-            peerbv.bv_len = sprintf(
-                    peername, "PATH=%s", sl->sl_sa.sa_un_addr.sun_path );
+            /* apparently accept doesn't fill the sun_path member, use
+             * listener name */
+            peerbv = ls->ls_name;
             break;
 #endif /* LDAP_PF_LOCAL */
 
@@ -910,275 +815,350 @@ lload_listener(
     }
 
 #ifdef HAVE_TLS
-    if ( sl->sl_is_tls ) cflag |= CONN_IS_TLS;
+    if ( l->sl_is_tls ) cflag |= CONN_IS_TLS;
 #endif
-    c = client_init( s, &sl->sl_name, &peerbv, lload_daemon[tid].base, cflag );
+    c = client_init( s, ls, &peerbv, lload_daemon[tid].base, cflag );
 
     if ( !c ) {
         Debug( LDAP_DEBUG_ANY, "lload_listener: "
                 "client_init(%ld, %s, %s) failed\n",
-                (long)s, peername, sl->sl_name.bv_val );
+                (long)s, peername, ls->ls_name.bv_val );
         lloadd_close( s );
     }
 
     return;
 }
 
-static void *
-lload_listener_thread( void *ctx )
-{
-    /* ITS#9984 Survive the listeners being paused if we run out of fds */
-    int rc = event_base_loop( listener_base, EVLOOP_NO_EXIT_ON_EMPTY );
-    Debug( LDAP_DEBUG_ANY, "lload_listener_thread: "
-            "event loop finished: rc=%d\n",
-            rc );
-
-    return (void *)NULL;
-}
-
-static void
-listener_error_cb( struct evconnlistener *lev, void *arg )
-{
-    LloadListener *l = arg;
-    int err = EVUTIL_SOCKET_ERROR();
-
-    assert( l->listener == lev );
-    if (
-#ifdef EMFILE
-            err == EMFILE ||
-#endif /* EMFILE */
-#ifdef ENFILE
-            err == ENFILE ||
-#endif /* ENFILE */
-            0 ) {
-        ldap_pvt_thread_mutex_lock( &lload_daemon[0].sd_mutex );
-        emfile++;
-        /* Stop listening until an existing session closes */
-        l->sl_mute = 1;
-        evconnlistener_disable( lev );
-        ldap_pvt_thread_mutex_unlock( &lload_daemon[0].sd_mutex );
-        Debug( LDAP_DEBUG_ANY, "listener_error_cb: "
-                "too many open files, cannot accept new connections on "
-                "url=%s\n",
-                l->sl_url.bv_val );
-    } else {
-        char ebuf[128];
-        Debug( LDAP_DEBUG_ANY, "listener_error_cb: "
-                "received an error on a listener, shutting down: '%s'\n",
-                sock_errstr( err, ebuf, sizeof(ebuf) ) );
-        event_base_loopexit( l->base, NULL );
-    }
-}
-
-void
-listeners_reactivate( void )
-{
-    int i;
-
-    ldap_pvt_thread_mutex_lock( &lload_daemon[0].sd_mutex );
-    for ( i = 0; emfile && lload_listeners[i] != NULL; i++ ) {
-        LloadListener *lr = lload_listeners[i];
-
-        if ( lr->sl_sd == AC_SOCKET_INVALID ) continue;
-        if ( lr->sl_mute ) {
-            emfile--;
-            evconnlistener_enable( lr->listener );
-            lr->sl_mute = 0;
-            Debug( LDAP_DEBUG_CONNS, "listeners_reactivate: "
-                    "reactivated listener url=%s\n",
-                    lr->sl_url.bv_val );
-        }
-    }
-    if ( emfile && lload_listeners[i] == NULL ) {
-        /* Walked the entire list without enabling anything; emfile
-         * counter is stale. Reset it. */
-        emfile = 0;
-    }
-    ldap_pvt_thread_mutex_unlock( &lload_daemon[0].sd_mutex );
-}
-
 static int
-lload_listener_activate( void )
+lload_sockets_activate( LloadListener *l )
 {
-    struct evconnlistener *listener;
-    int l, rc;
+    LloadListenerSocket *ls;
     char ebuf[128];
+    struct evconnlistener *listener;
+    int rc;
 
-    listener_base = event_base_new();
-    if ( !listener_base ) return -1;
-
-    for ( l = 0; lload_listeners[l] != NULL; l++ ) {
-        if ( lload_listeners[l]->sl_sd == AC_SOCKET_INVALID ) continue;
-
-            /* FIXME: TCP-only! */
+    for ( ls = l->sl_sockets; ls; ls = ls->ls_next ) {
 #ifdef LDAP_TCP_BUFFER
-        if ( 1 ) {
-            int origsize, size, realsize, rc;
-            socklen_t optlen;
-
-            size = 0;
-            if ( lload_listeners[l]->sl_tcp_rmem > 0 ) {
-                size = lload_listeners[l]->sl_tcp_rmem;
-            } else if ( slapd_tcp_rmem > 0 ) {
-                size = slapd_tcp_rmem;
-            }
+        /* FIXME: TCP-only! */
+        int origsize, size, realsize;
+        socklen_t optlen;
+
+        size = 0;
+        if ( l->sl_tcp_rmem > 0 ) {
+            size = l->sl_tcp_rmem;
+        } else if ( slapd_tcp_rmem > 0 ) {
+            size = slapd_tcp_rmem;
+        }
 
-            if ( size > 0 ) {
-                optlen = sizeof(origsize);
-                rc = getsockopt( lload_listeners[l]->sl_sd, SOL_SOCKET,
-                        SO_RCVBUF, (void *)&origsize, &optlen );
+        if ( size > 0 ) {
+            optlen = sizeof(origsize);
+            rc = getsockopt( ls->ls_sd, SOL_SOCKET,
+                    SO_RCVBUF, (void *)&origsize, &optlen );
 
-                if ( rc ) {
-                    int err = sock_errno();
-                    Debug( LDAP_DEBUG_ANY, "lload_listener_activate: "
-                            "getsockopt(SO_RCVBUF) failed errno=%d (%s)\n",
-                            err, AC_STRERROR_R( err, ebuf, sizeof(ebuf) ) );
-                }
+            if ( rc ) {
+                int err = sock_errno();
+                Debug( LDAP_DEBUG_ANY, "lload_sockets_activate: "
+                        "getsockopt(SO_RCVBUF) failed errno=%d (%s)\n",
+                        err, AC_STRERROR_R( err, ebuf, sizeof(ebuf) ) );
+            }
 
-                optlen = sizeof(size);
-                rc = setsockopt( lload_listeners[l]->sl_sd, SOL_SOCKET,
-                        SO_RCVBUF, (const void *)&size, optlen );
+            optlen = sizeof(size);
+            rc = setsockopt( ls->ls_sd, SOL_SOCKET,
+                    SO_RCVBUF, (const void *)&size, optlen );
 
-                if ( rc ) {
-                    int err = sock_errno();
-                    Debug( LDAP_DEBUG_ANY, "lload_listener_activate: "
-                            "setsockopt(SO_RCVBUF) failed errno=%d (%s)\n",
-                            err, sock_errstr( err, ebuf, sizeof(ebuf) ) );
-                }
+            if ( rc ) {
+                int err = sock_errno();
+                Debug( LDAP_DEBUG_ANY, "lload_sockets_activate: "
+                        "setsockopt(SO_RCVBUF) failed errno=%d (%s)\n",
+                        err, sock_errstr( err, ebuf, sizeof(ebuf) ) );
+            }
 
-                optlen = sizeof(realsize);
-                rc = getsockopt( lload_listeners[l]->sl_sd, SOL_SOCKET,
-                        SO_RCVBUF, (void *)&realsize, &optlen );
+            optlen = sizeof(realsize);
+            rc = getsockopt( ls->ls_sd, SOL_SOCKET,
+                    SO_RCVBUF, (void *)&realsize, &optlen );
 
-                if ( rc ) {
-                    int err = sock_errno();
-                    Debug( LDAP_DEBUG_ANY, "lload_listener_activate: "
-                            "getsockopt(SO_RCVBUF) failed errno=%d (%s)\n",
-                            err, sock_errstr( err, ebuf, sizeof(ebuf) ) );
-                }
-
-                Debug( LDAP_DEBUG_ANY, "lload_listener_activate: "
-                        "url=%s (#%d) RCVBUF original size=%d requested "
-                        "size=%d real size=%d\n",
-                        lload_listeners[l]->sl_url.bv_val, l, origsize, size,
-                        realsize );
+            if ( rc ) {
+                int err = sock_errno();
+                Debug( LDAP_DEBUG_ANY, "lload_sockets_activate: "
+                        "getsockopt(SO_RCVBUF) failed errno=%d (%s)\n",
+                        err, sock_errstr( err, ebuf, sizeof(ebuf) ) );
             }
 
-            size = 0;
-            if ( lload_listeners[l]->sl_tcp_wmem > 0 ) {
-                size = lload_listeners[l]->sl_tcp_wmem;
-            } else if ( slapd_tcp_wmem > 0 ) {
-                size = slapd_tcp_wmem;
-            }
+            Debug( LDAP_DEBUG_ANY, "lload_sockets_activate: "
+                    "url=%s RCVBUF original size=%d requested "
+                    "size=%d real size=%d\n",
+                    l->sl_url.bv_val, origsize, size, realsize );
+        }
 
-            if ( size > 0 ) {
-                optlen = sizeof(origsize);
-                rc = getsockopt( lload_listeners[l]->sl_sd, SOL_SOCKET,
-                        SO_SNDBUF, (void *)&origsize, &optlen );
+        size = 0;
+        if ( l->sl_tcp_wmem > 0 ) {
+            size = l->sl_tcp_wmem;
+        } else if ( slapd_tcp_wmem > 0 ) {
+            size = slapd_tcp_wmem;
+        }
 
-                if ( rc ) {
-                    int err = sock_errno();
-                    Debug( LDAP_DEBUG_ANY, "lload_listener_activate: "
-                            "getsockopt(SO_SNDBUF) failed errno=%d (%s)\n",
-                            err, sock_errstr( err, ebuf, sizeof(ebuf) ) );
-                }
+        if ( size > 0 ) {
+            optlen = sizeof(origsize);
+            rc = getsockopt( ls->ls_sd, SOL_SOCKET,
+                    SO_SNDBUF, (void *)&origsize, &optlen );
 
-                optlen = sizeof(size);
-                rc = setsockopt( lload_listeners[l]->sl_sd, SOL_SOCKET,
-                        SO_SNDBUF, (const void *)&size, optlen );
+            if ( rc ) {
+                int err = sock_errno();
+                Debug( LDAP_DEBUG_ANY, "lload_sockets_activate: "
+                        "getsockopt(SO_SNDBUF) failed errno=%d (%s)\n",
+                        err, sock_errstr( err, ebuf, sizeof(ebuf) ) );
+            }
 
-                if ( rc ) {
-                    int err = sock_errno();
-                    Debug( LDAP_DEBUG_ANY, "lload_listener_activate: "
-                            "setsockopt(SO_SNDBUF) failed errno=%d (%s)\n",
-                            err, sock_errstr( err, ebuf, sizeof(ebuf) ) );
-                }
+            optlen = sizeof(size);
+            rc = setsockopt( ls->ls_sd, SOL_SOCKET,
+                    SO_SNDBUF, (const void *)&size, optlen );
 
-                optlen = sizeof(realsize);
-                rc = getsockopt( lload_listeners[l]->sl_sd, SOL_SOCKET,
-                        SO_SNDBUF, (void *)&realsize, &optlen );
+            if ( rc ) {
+                int err = sock_errno();
+                Debug( LDAP_DEBUG_ANY, "lload_sockets_activate: "
+                        "setsockopt(SO_SNDBUF) failed errno=%d (%s)\n",
+                        err, sock_errstr( err, ebuf, sizeof(ebuf) ) );
+            }
 
-                if ( rc ) {
-                    int err = sock_errno();
-                    Debug( LDAP_DEBUG_ANY, "lload_listener_activate: "
-                            "getsockopt(SO_SNDBUF) failed errno=%d (%s)\n",
-                            err, sock_errstr( err, ebuf, sizeof(ebuf) ) );
-                }
+            optlen = sizeof(realsize);
+            rc = getsockopt( ls->ls_sd, SOL_SOCKET,
+                    SO_SNDBUF, (void *)&realsize, &optlen );
 
-                Debug( LDAP_DEBUG_ANY, "lload_listener_activate: "
-                        "url=%s (#%d) SNDBUF original size=%d requested "
-                        "size=%d real size=%d\n",
-                        lload_listeners[l]->sl_url.bv_val, l, origsize, size,
-                        realsize );
+            if ( rc ) {
+                int err = sock_errno();
+                Debug( LDAP_DEBUG_ANY, "lload_sockets_activate: "
+                        "getsockopt(SO_SNDBUF) failed errno=%d (%s)\n",
+                        err, sock_errstr( err, ebuf, sizeof(ebuf) ) );
             }
+
+            Debug( LDAP_DEBUG_ANY, "lload_sockets_activate: "
+                    "url=%s SNDBUF original size=%d requested "
+                    "size=%d real size=%d\n",
+                    l->sl_url.bv_val, origsize, size, realsize );
         }
 #endif /* LDAP_TCP_BUFFER */
 
-        lload_listeners[l]->sl_busy = 1;
-        listener = evconnlistener_new( listener_base, lload_listener,
-                lload_listeners[l],
+        listener = evconnlistener_new( listener_base, lload_listener, ls,
                 LEV_OPT_THREADSAFE|LEV_OPT_DEFERRED_ACCEPT,
-                SLAPD_LISTEN_BACKLOG, lload_listeners[l]->sl_sd );
+                SLAPD_LISTEN_BACKLOG, ls->ls_sd );
         if ( !listener ) {
             int err = sock_errno();
 
 #ifdef LDAP_PF_INET6
-            /* If error is EADDRINUSE, we are trying to listen to INADDR_ANY and
+            /*
+             * If error is EADDRINUSE, we are trying to listen to INADDR_ANY and
              * we are already listening to in6addr_any, then we want to ignore
              * this and continue.
              */
             if ( err == EADDRINUSE ) {
-                int i;
-                struct sockaddr_in sa = lload_listeners[l]->sl_sa.sa_in_addr;
+                LloadListenerSocket *ls2 = l->sl_sockets;
+                struct sockaddr_in sa = ls->ls_sa.sa_in_addr;
                 struct sockaddr_in6 sa6;
 
                 if ( sa.sin_family == AF_INET &&
                         sa.sin_addr.s_addr == htonl( INADDR_ANY ) ) {
-                    for ( i = 0; i < l; i++ ) {
-                        sa6 = lload_listeners[i]->sl_sa.sa_in6_addr;
+                    for ( ; ls2 != ls; ls2 = ls2->ls_next ) {
+                        sa6 = ls2->ls_sa.sa_in6_addr;
                         if ( sa6.sin6_family == AF_INET6 &&
                                 !memcmp( &sa6.sin6_addr, &in6addr_any,
-                                        sizeof(struct in6_addr) ) ) {
+                                    sizeof(struct in6_addr) ) ) {
                             break;
                         }
                     }
 
-                    if ( i < l ) {
+                    if ( ls2 != ls ) {
                         /* We are already listening to in6addr_any */
-                        Debug( LDAP_DEBUG_CONNS, "lload_listener_activate: "
+                        Debug( LDAP_DEBUG_CONNS, "lload_sockets_activate: "
                                 "Attempt to listen to 0.0.0.0 failed, "
                                 "already listening on ::, assuming IPv4 "
                                 "included\n" );
-                        lloadd_close( lload_listeners[l]->sl_sd );
-                        lload_listeners[l]->sl_sd = AC_SOCKET_INVALID;
+
+                        for ( ; ls2->ls_next != ls; ls2 = ls2->ls_next )
+                            /* scroll to ls's prev */;
+
+                        ls2->ls_next = ls->ls_next;
+                        lloadd_close( ls->ls_sd );
+                        ber_memfree( ls->ls_name.bv_val );
+                        ch_free( ls );
                         continue;
                     }
                 }
             }
 #endif /* LDAP_PF_INET6 */
-            Debug( LDAP_DEBUG_ANY, "lload_listener_activate: "
-                    "listen(%s, 5) failed errno=%d (%s)\n",
-                    lload_listeners[l]->sl_url.bv_val, err,
+            Debug( LDAP_DEBUG_ANY, "lload_sockets_activate: "
+                    "listen(%s, " LDAP_XSTRING(SLAPD_LISTEN_BACKLOG)
+                    ") failed errno=%d (%s)\n",
+                    l->sl_url.bv_val, err,
                     sock_errstr( err, ebuf, sizeof(ebuf) ) );
             return -1;
         }
 
-        lload_listeners[l]->base = listener_base;
-        lload_listeners[l]->listener = listener;
         evconnlistener_set_error_cb( listener, listener_error_cb );
+        ls->base = listener_base;
+        ls->listener = listener;
+    }
+
+    return 0;
+}
+
+int
+lload_open_new_listener( LloadListener *l )
+{
+    int rc, i;
+
+    /* If we started up already, also activate it */
+    if ( lloadd_inited && (rc = lload_sockets_activate( l )) ) {
+        return rc;
+    }
+
+    for ( i = 0; lload_listeners && lload_listeners[i] != NULL;
+            i++ ) /* count */
+        ;
+
+    lload_listeners = ch_realloc(
+            lload_listeners, ( i + 2 ) * sizeof(LloadListener *) );
+    lload_listeners[i] = l;
+    lload_listeners[i+1] = NULL;
+
+    return 0;
+}
+
+int
+lloadd_listeners_init( const char *urls )
+{
+    int i;
+    char **u;
+
+    Debug( LDAP_DEBUG_ARGS, "lloadd_listeners_init: %s\n",
+            urls ? urls : "<null>" );
+
+#ifdef HAVE_TCPD
+    ldap_pvt_thread_mutex_init( &sd_tcpd_mutex );
+#endif /* TCP Wrappers */
+
+    if ( urls == NULL ) urls = "ldap:///";
+
+    u = ldap_str2charray( urls, " " );
+
+    if ( u == NULL || u[0] == NULL ) {
+        Debug( LDAP_DEBUG_ANY, "lloadd_listeners_init: "
+                "no urls (%s) provided\n",
+                urls );
+        if ( u ) ldap_charray_free( u );
+        return -1;
+    }
+
+    for ( i = 0; u[i] != NULL; i++ ) {
+        Debug( LDAP_DEBUG_TRACE, "lloadd_listeners_init: "
+                "listen on %s\n",
+                u[i] );
+    }
+
+    Debug( LDAP_DEBUG_TRACE, "lloadd_listeners_init: "
+            "%d listeners to open...\n",
+            i );
+    lload_listeners = ch_malloc( ( i + 1 ) * sizeof(LloadListener *) );
+
+    for ( i = 0; u[i]; i++ ) {
+        LDAPURLDesc *lud;
+
+        if ( ldap_url_parse_ext( u[i], &lud, LDAP_PVT_URL_PARSE_DEF_PORT ) ) {
+            Debug( LDAP_DEBUG_ANY, "lloadd_listeners_init: "
+                    "could not parse url %s\n",
+                    u[i] );
+            goto fail;
+        }
+
+        if ( !(lload_listeners[i] = lload_configure_listener( u[i], lud )) ) {
+            goto fail;
+        }
+    }
+    lload_listeners[i] = NULL;
+
+    ldap_charray_free( u );
+    return 0;
+
+fail:
+    ldap_charray_free( u );
+
+    for ( ; i >= 0; i-- ) {
+        if ( lload_listeners[i] ) {
+            lload_listener_free( lload_listeners[i] );
+        }
+    }
+    ch_free( lload_listeners );
+    lload_listeners = NULL;
+    return -1;
+}
+
+static void *
+lload_listener_thread( void *ctx )
+{
+    /* ITS#9984 Survive the listeners being paused if we run out of fds */
+    int rc = event_base_loop( listener_base, EVLOOP_NO_EXIT_ON_EMPTY );
+    Debug( LDAP_DEBUG_ANY, "lload_listener_thread: "
+            "event loop finished: rc=%d\n",
+            rc );
+
+    return (void *)NULL;
+}
+
+static int
+lload_listener_activate( void )
+{
+    int i, rc;
+
+    listener_base = event_base_new();
+    if ( !listener_base ) return -1;
+
+    for ( i = 0; lload_listeners[i] != NULL; i++ ) {
+        LloadListener *l = lload_listeners[i];
+
+        if ( (rc = lload_sockets_activate( l )) ) {
+            return rc;
+        }
     }
 
     rc = ldap_pvt_thread_create(
-            &listener_tid, 0, lload_listener_thread, lload_listeners[l] );
+            &listener_tid, 0, lload_listener_thread, NULL );
 
     if ( rc != 0 ) {
-        Debug( LDAP_DEBUG_ANY, "lload_listener_activate(%d): "
-                "submit failed (%d)\n",
-                lload_listeners[l]->sl_sd, rc );
+        Debug( LDAP_DEBUG_ANY, "lload_listener_activate(): "
+                "could not start listener thread (%d)\n",
+                rc );
     }
     return rc;
 }
 
+void
+listeners_reactivate( void )
+{
+    int i;
+
+    ldap_pvt_thread_mutex_lock( &lload_daemon[0].sd_mutex );
+    for ( i = 0; emfile && lload_listeners[i] != NULL; i++ ) {
+        LloadListener *l = lload_listeners[i];
+        LloadListenerSocket *ls = l->sl_sockets;
+
+        for ( ; emfile && ls; ls = ls->ls_next ) {
+            if ( ls->ls_mute ) {
+                emfile--;
+                evconnlistener_enable( ls->listener );
+                ls->ls_mute = 0;
+                Debug( LDAP_DEBUG_CONNS, "listeners_reactivate: "
+                        "reactivated listener url=%s\n",
+                        l->sl_url.bv_val );
+            }
+        }
+    }
+    if ( emfile && lload_listeners[i] == NULL ) {
+        /* Walked the entire list without enabling anything; emfile
+         * counter is stale. Reset it. */
+        emfile = 0;
+    }
+    ldap_pvt_thread_mutex_unlock( &lload_daemon[0].sd_mutex );
+}
+
 static void *
 lloadd_io_task( void *ptr )
 {
@@ -1771,6 +1751,46 @@ lload_handle_global_invalidation( LloadChange *change )
             c->c_type = privileged ? LLOAD_C_PRIVILEGED : LLOAD_C_OPEN;
         }
     }
+
+    if ( change->flags.daemon & LLOAD_DAEMON_MOD_LISTENER ) {
+        LloadListener **lp, *l;
+        int i;
+
+        /* Mark clients linked to the disappearing listeners closing */
+        if ( !LDAP_CIRCLEQ_EMPTY( &clients ) ) {
+            LloadConnection *c = LDAP_CIRCLEQ_FIRST( &clients );
+            unsigned long first_connid = c->c_connid;
+
+            while ( c ) {
+                LloadConnection *next =
+                    LDAP_CIRCLEQ_LOOP_NEXT( &clients, c, c_next );
+                if ( c->c_listener && c->c_listener->ls_lr->sl_removed ) {
+                    int gentle = 1;
+                    c->c_listener = NULL;
+                    lload_connection_close( c, &gentle );
+                }
+                c = next;
+                if ( c->c_connid <= first_connid ) {
+                    c = NULL;
+                }
+            }
+        }
+
+        /* Go through listeners that have been removed and dispose of them */
+        assert( lload_listeners );
+        lp = lload_listeners;
+
+        for ( i = 0; lload_listeners[i]; i++ ) {
+            l = lload_listeners[i];
+
+            if ( l->sl_removed ) {
+                lload_listener_free( l );
+                continue;
+            }
+            *(lp++) = l;
+        }
+        *lp = NULL;
+    }
 }
 
 int
@@ -1918,10 +1938,6 @@ lload_get_base( ber_socket_t s )
 LloadListener **
 lloadd_get_listeners( void )
 {
-    /* Could return array with no listeners if !listening, but current
-     * callers mostly look at the URLs.  E.g. syncrepl uses this to
-     * identify the server, which means it wants the startup arguments.
-     */
     return lload_listeners;
 }
 
@@ -1931,9 +1947,13 @@ lload_suspend_listeners( void )
 {
     int i;
     for ( i = 0; lload_listeners[i]; i++ ) {
-        lload_listeners[i]->sl_mute = 1;
-        evconnlistener_disable( lload_listeners[i]->listener );
-        listen( lload_listeners[i]->sl_sd, 0 );
+        LloadListenerSocket *ls = lload_listeners[i]->sl_sockets;
+
+        for ( ; ls; ls = ls->ls_next ) {
+            ls->ls_mute = 1;
+            evconnlistener_disable( ls->listener );
+            listen( ls->ls_sd, 0 );
+        }
     }
 }
 
@@ -1943,8 +1963,12 @@ lload_resume_listeners( void )
 {
     int i;
     for ( i = 0; lload_listeners[i]; i++ ) {
-        lload_listeners[i]->sl_mute = 0;
-        listen( lload_listeners[i]->sl_sd, SLAPD_LISTEN_BACKLOG );
-        evconnlistener_enable( lload_listeners[i]->listener );
+        LloadListenerSocket *ls = lload_listeners[i]->sl_sockets;
+
+        for ( ; ls; ls = ls->ls_next ) {
+            ls->ls_mute = 0;
+            listen( ls->ls_sd, SLAPD_LISTEN_BACKLOG );
+            evconnlistener_enable( ls->listener );
+        }
     }
 }
index 72dee6a9d73f01099685a24783c76f578a6fffea..8f0d293aab08df7b4aebc1ed2da1af98c67794bd 100644 (file)
@@ -104,6 +104,8 @@ typedef struct LloadPendingConnection LloadPendingConnection;
 typedef struct LloadConnection LloadConnection;
 typedef struct LloadOperation LloadOperation;
 typedef struct LloadChange LloadChange;
+typedef struct LloadListenerSocket LloadListenerSocket;
+typedef struct LloadListener LloadListener;
 /* end of forward declarations */
 
 typedef LDAP_STAILQ_HEAD(TierSt, LloadTier) lload_t_head;
@@ -149,9 +151,8 @@ enum lcf_daemon {
     LLOAD_DAEMON_MOD_THREADS = 1 << 0,
     LLOAD_DAEMON_MOD_FEATURES = 1 << 1,
     LLOAD_DAEMON_MOD_TLS = 1 << 2,
-    LLOAD_DAEMON_MOD_LISTENER_ADD = 1 << 3,
-    LLOAD_DAEMON_MOD_LISTENER_REPLACE = 1 << 4,
-    LLOAD_DAEMON_MOD_BINDCONF = 1 << 5,
+    LLOAD_DAEMON_MOD_LISTENER = 1 << 3,
+    LLOAD_DAEMON_MOD_BINDCONF = 1 << 4,
 };
 
 enum lcf_tier {
@@ -495,6 +496,7 @@ struct LloadConnection {
     time_t c_restricted_at;
     LloadBackend *c_backend;
     LloadConnection *c_linked_upstream;
+    LloadListenerSocket *c_listener;
 
     TAvlnode *c_linked;
 
@@ -568,23 +570,18 @@ struct restriction_entry {
 };
 
 /*
- * listener; need to access it from monitor backend
+ * listener, unlike slapd, we have exactly one per url, then bunch the sockets
+ * under it
  */
 struct LloadListener {
     struct berval sl_url;
-    struct berval sl_name;
     mode_t sl_perms;
+    int sl_removed;
 #ifdef HAVE_TLS
     int sl_is_tls;
 #endif
     int sl_is_proxied;
-    struct event_base *base;
-    struct evconnlistener *listener;
-    int sl_mute; /* Listener is temporarily disabled due to emfile */
-    int sl_busy; /* Listener is busy (accept thread activated) */
-    ber_socket_t sl_sd;
-    Sockaddr sl_sa;
-#define sl_addr sl_sa.sa_in_addr
+    LloadListenerSocket *sl_sockets;
 #define LDAP_TCP_BUFFER
 #ifdef LDAP_TCP_BUFFER
     int sl_tcp_rmem; /* custom TCP read buffer size */
@@ -592,6 +589,22 @@ struct LloadListener {
 #endif
 };
 
+struct LloadListenerSocket {
+    LloadListener *ls_lr;
+    struct berval ls_name;
+
+    struct evconnlistener *listener;
+    struct event_base *base;
+
+    int ls_mute; /* listener is temporarily disabled due to emfile */
+    ber_socket_t ls_sd;
+
+    LloadListenerSocket *ls_next;
+
+    Sockaddr ls_sa;
+#define ls_addr ls_sa.sa_in_addr
+};
+
 typedef int (*CONNCB)( LloadConnection *c, void *arg );
 
 /* config requires a bi_private with configuration data - dummy for now */
index b9d83dcb4f7a52962194bf994a8fdcdf0a2e0d9a..0e76c176563ec975ec92f452c4e65b805000fa3a 100644 (file)
@@ -827,12 +827,6 @@ unhandled_option:;
         slapd_args_file_unlink = 1;
     }
 
-    /*
-     * FIXME: moved here from lloadd_daemon_task()
-     * because back-monitor db_open() needs it
-     */
-    time( &starttime );
-
     Debug( LDAP_DEBUG_ANY, "lloadd starting\n" );
 
 #ifndef HAVE_WINSOCK
index 0cd2e586dde1ddde41082a0f1ba50f4d258dbe9b..8ec34766671587662269a1bab1733a4ada67c2bb 100644 (file)
@@ -106,8 +106,6 @@ lload_back_open( BackendInfo *bi )
         return -1;
     }
 
-    assert( lloadd_get_listeners() );
-
     checked_lock( &lload_wait_mutex );
     rc = ldap_pvt_thread_create( &lloadd_main_thread,
             0, lload_start_daemon, NULL );
index ac8ce5d246c583654c4e685ddff28f3c2cebc41c..2887021b017760d33930cb110d3e7a8d23d627d2 100644 (file)
@@ -63,7 +63,7 @@ LDAP_SLAPD_F (int) request_process( LloadConnection *c, LloadOperation *op );
 LDAP_SLAPD_F (int) handle_one_request( LloadConnection *c );
 LDAP_SLAPD_F (void) client_tls_handshake_cb( evutil_socket_t s, short what, void *arg );
 LDAP_SLAPD_F (LloadConnection *) client_init( ber_socket_t s,
-        struct berval *localname,
+        LloadListenerSocket *ls,
         struct berval *peername,
         struct event_base *base,
         int use_tls );
@@ -113,7 +113,8 @@ LDAP_SLAPD_F (void) connections_walk( ldap_pvt_thread_mutex_t *cq_mutex, lload_c
 /*
  * daemon.c
  */
-LDAP_SLAPD_F (int) lload_open_new_listener( const char *urls, LDAPURLDesc *lud );
+LDAP_SLAPD_F (LloadListener *) lload_configure_listener( const char *url, LDAPURLDesc *lud );
+LDAP_SLAPD_F (int) lload_open_new_listener( LloadListener *lr );
 LDAP_SLAPD_F (int) lloadd_listeners_init( const char *urls );
 LDAP_SLAPD_F (int) lloadd_daemon_destroy( void );
 LDAP_SLAPD_F (int) lloadd_daemon( struct event_base *daemon_base );
index c08e560fcd1688956dece981624e7ba826cac04e..158711497357ba90016f678aa09fe1047c769391 100644 (file)
@@ -1299,22 +1299,17 @@ slap_free_listener_addresses( struct sockaddr **sal )
 static int
 get_url_perms(
        char    **exts,
-       mode_t  *perms,
-       int     *crit )
+       mode_t  *perms )
 {
        int     i;
 
        assert( exts != NULL );
        assert( perms != NULL );
-       assert( crit != NULL );
 
-       *crit = 0;
        for ( i = 0; exts[ i ]; i++ ) {
                char    *type = exts[ i ];
-               int     c = 0;
 
                if ( type[ 0 ] == '!' ) {
-                       c = 1;
                        type++;
                }
 
@@ -1365,7 +1360,6 @@ get_url_perms(
                                return LDAP_OTHER;
                        } 
 
-                       *crit = c;
                        *perms = p;
 
                        return LDAP_SUCCESS;
@@ -1523,13 +1517,6 @@ slap_open_listener(
        ber_socket_t s;
        char ebuf[128];
 
-#if defined(LDAP_PF_LOCAL) || defined(SLAP_X_LISTENER_MOD)
-       /*
-        * use safe defaults
-        */
-       int     crit = 1;
-#endif /* LDAP_PF_LOCAL || SLAP_X_LISTENER_MOD */
-
        rc = ldap_url_parse_ext( url, &lud, LDAP_PVT_URL_PARSE_DEF_PORT );
 
        if( rc != LDAP_URL_SUCCESS ) {
@@ -1596,7 +1583,7 @@ slap_open_listener(
 
 #if defined(LDAP_PF_LOCAL) || defined(SLAP_X_LISTENER_MOD)
        if ( lud->lud_exts ) {
-               err = get_url_perms( lud->lud_exts, &l.sl_perms, &crit );
+               err = get_url_perms( lud->lud_exts, &l.sl_perms );
        } else {
                l.sl_perms = S_IRWXU | S_IRWXO;
        }