]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MAJOR: listener: support inheriting a listening fd from the parent
authorWilly Tarreau <w@1wt.eu>
Sun, 10 Mar 2013 22:51:38 +0000 (23:51 +0100)
committerWilly Tarreau <w@1wt.eu>
Mon, 11 Mar 2013 00:30:01 +0000 (01:30 +0100)
Using the address syntax "fd@<num>", a listener may inherit a file
descriptor that the caller process has already bound and passed as
this number. The fd's socket family is detected using getsockname(),
and the usual initialization is performed through the existing code
for that family, but the socket creation is skipped.

Whether the parent has performed the listen() call or not is not
important as this is detected.

For UNIX sockets, we immediately clear the path after preparing a
socket so that we never remove it in case an abort would happen due
to a late error during startup.

doc/configuration.txt
src/cfgparse.c
src/proto_tcp.c
src/proto_uxst.c
src/standard.c

index 4d575856e035eb53a8b0dd6174381df815509b60..86d88a9b0c3aee27a0ce1fb63a48e328c680f5ed 100644 (file)
@@ -1616,6 +1616,9 @@ bind /<path> [, ...] [param*]
                     - 'ipv4@'  -> address is always IPv4
                     - 'ipv6@'  -> address is always IPv6
                     - 'unix@'  -> address is a path to a local unix socket
+                    - 'fd@<n>' -> use file descriptor <n> inherited from the
+                      parent. The fd must be bound and may or may not already
+                      be listening.
 
     <port_range>  is either a unique TCP port, or a port range for which the
                   proxy will accept connections for the IP address specified
index 3821edb77dafc2f6a48686d76f783f1f2998ca93..2c2faf558637efe45e7ba498d132f9f27214f19d 100644 (file)
@@ -207,6 +207,7 @@ int str2listener(char *str, struct proxy *curproxy, struct bind_conf *bind_conf,
 
        while (next && *next) {
                struct sockaddr_storage ss, *ss2;
+               int fd = -1;
 
                str = next;
                /* 1) look for the end of the first address */
@@ -240,6 +241,24 @@ int str2listener(char *str, struct proxy *curproxy, struct bind_conf *bind_conf,
                                goto fail;
                        }
                }
+               else if (ss2->ss_family == AF_UNSPEC) {
+                       socklen_t addr_len;
+
+                       /* We want to attach to an already bound fd whose number
+                        * is in the addr part of ss2 when cast to sockaddr_in.
+                        * Note that by definition there is a single listener.
+                        * We still have to determine the address family to
+                        * register the correct protocol.
+                        */
+                       fd = ((struct sockaddr_in *)ss2)->sin_addr.s_addr;
+                       addr_len = sizeof(*ss2);
+                       if (getsockname(fd, (struct sockaddr *)ss2, &addr_len) == -1) {
+                               memprintf(err, "cannot use file descriptor '%d' : %s.\n", fd, strerror(errno));
+                               goto fail;
+                       }
+
+                       port = end = get_host_port(ss2);
+               }
 
                /* OK the address looks correct */
                ss = *ss2;
@@ -252,7 +271,7 @@ int str2listener(char *str, struct proxy *curproxy, struct bind_conf *bind_conf,
                        l->frontend = curproxy;
                        l->bind_conf = bind_conf;
 
-                       l->fd = -1;
+                       l->fd = fd;
                        l->addr = ss;
                        l->xprt = &raw_sock;
                        l->state = LI_INIT;
index 6bd576179576fe47a8176c7ad2914eaefedc739b..38b9bdc2f23e61b6a30933ec8c3d8065de5bab2e 100644 (file)
@@ -566,6 +566,8 @@ int tcp_bind_listener(struct listener *listener, char *errmsg, int errlen)
 {
        __label__ tcp_return, tcp_close_return;
        int fd, err;
+       int ext, ready;
+       socklen_t ready_len;
        const char *msg = NULL;
 
        /* ensure we never return garbage */
@@ -577,7 +579,15 @@ int tcp_bind_listener(struct listener *listener, char *errmsg, int errlen)
 
        err = ERR_NONE;
 
-       if ((fd = socket(listener->addr.ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) {
+       /* if the listener already has an fd assigned, then we were offered the
+        * fd by an external process (most likely the parent), and we don't want
+        * to create a new socket. However we still want to set a few flags on
+        * the socket.
+        */
+       fd = listener->fd;
+       ext = (fd >= 0);
+
+       if (!ext && (fd = socket(listener->addr.ss_family, SOCK_STREAM, IPPROTO_TCP)) == -1) {
                err |= ERR_RETRYABLE | ERR_ALERT;
                msg = "cannot create listening socket";
                goto tcp_return;
@@ -595,7 +605,7 @@ int tcp_bind_listener(struct listener *listener, char *errmsg, int errlen)
                goto tcp_close_return;
        }
 
-       if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) == -1) {
+       if (!ext && setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) == -1) {
                /* not fatal but should be reported */
                msg = "cannot do so_reuseaddr";
                err |= ERR_ALERT;
@@ -608,10 +618,11 @@ int tcp_bind_listener(struct listener *listener, char *errmsg, int errlen)
        /* OpenBSD supports this. As it's present in old libc versions of Linux,
         * it might return an error that we will silently ignore.
         */
-       setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one));
+       if (!ext)
+               setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one));
 #endif
 #ifdef CONFIG_HAP_LINUX_TPROXY
-       if (listener->options & LI_O_FOREIGN) {
+       if (!ext && (listener->options & LI_O_FOREIGN)) {
                switch (listener->addr.ss_family) {
                case AF_INET:
                        if ((setsockopt(fd, SOL_IP, IP_TRANSPARENT, &one, sizeof(one)) == -1)
@@ -631,7 +642,7 @@ int tcp_bind_listener(struct listener *listener, char *errmsg, int errlen)
 #endif
 #ifdef SO_BINDTODEVICE
        /* Note: this might fail if not CAP_NET_RAW */
-       if (listener->interface) {
+       if (!ext && listener->interface) {
                if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE,
                               listener->interface, strlen(listener->interface) + 1) == -1) {
                        msg = "cannot bind listener to device";
@@ -675,13 +686,19 @@ int tcp_bind_listener(struct listener *listener, char *errmsg, int errlen)
                 setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &zero, sizeof(zero));
 #endif
 
-       if (bind(fd, (struct sockaddr *)&listener->addr, listener->proto->sock_addrlen) == -1) {
+       if (!ext && bind(fd, (struct sockaddr *)&listener->addr, listener->proto->sock_addrlen) == -1) {
                err |= ERR_RETRYABLE | ERR_ALERT;
                msg = "cannot bind socket";
                goto tcp_close_return;
        }
 
-       if (listen(fd, listener->backlog ? listener->backlog : listener->maxconn) == -1) {
+       ready = 0;
+       ready_len = sizeof(ready);
+       if (getsockopt(fd, SOL_SOCKET, SO_ACCEPTCONN, &ready, &ready_len) == -1)
+               ready = 0;
+
+       if (!(ext && ready) && /* only listen if not already done by external process */
+           listen(fd, listener->backlog ? listener->backlog : listener->maxconn) == -1) {
                err |= ERR_RETRYABLE | ERR_ALERT;
                msg = "cannot listen to socket";
                goto tcp_close_return;
index 3263ea4ab1bccf0e7b28a8391a75b818d86c4f43..85eb3ee8610b7a8389e78b3f342f6101467d31c3 100644 (file)
@@ -111,6 +111,10 @@ static void destroy_uxst_socket(const char *path)
        struct sockaddr_un addr;
        int sock, ret;
 
+       /* if the path was cleared, we do nothing */
+       if (!*path)
+               return;
+
        /* We might have been chrooted, so we may not be able to access the
         * socket. In order to avoid bothering the other end, we connect with a
         * wrong protocol, namely SOCK_DGRAM. The return code from connect()
@@ -157,6 +161,8 @@ static int uxst_bind_listener(struct listener *listener, char *errmsg, int errle
        struct sockaddr_un addr;
        const char *msg = NULL;
        const char *path;
+       int ext, ready;
+       socklen_t ready_len;
 
        int ret;
 
@@ -169,6 +175,16 @@ static int uxst_bind_listener(struct listener *listener, char *errmsg, int errle
                
        path = ((struct sockaddr_un *)&listener->addr)->sun_path;
 
+       /* if the listener already has an fd assigned, then we were offered the
+        * fd by an external process (most likely the parent), and we don't want
+        * to create a new socket. However we still want to set a few flags on
+        * the socket.
+        */
+       fd = listener->fd;
+       ext = (fd >= 0);
+       if (ext)
+               goto fd_ready;
+
        /* 1. create socket names */
        if (!path[0]) {
                msg = "Invalid empty name for a UNIX socket";
@@ -215,6 +231,7 @@ static int uxst_bind_listener(struct listener *listener, char *errmsg, int errle
                goto err_unlink_back;
        }
 
+ fd_ready:
        if (fd >= global.maxsock) {
                msg = "socket(): not enough free sockets, raise -n argument";
                goto err_unlink_temp;
@@ -225,7 +242,7 @@ static int uxst_bind_listener(struct listener *listener, char *errmsg, int errle
                goto err_unlink_temp;
        }
        
-       if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
+       if (!ext && bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
                /* note that bind() creates the socket <tempname> on the file system */
                msg = "cannot bind UNIX socket";
                goto err_unlink_temp;
@@ -236,14 +253,21 @@ static int uxst_bind_listener(struct listener *listener, char *errmsg, int errle
         * While it is known not to be portable on every OS, it's still useful
         * where it works.
         */
-       if (((listener->bind_conf->ux.uid != -1 || listener->bind_conf->ux.gid != -1) &&
-            (chown(tempname, listener->bind_conf->ux.uid, listener->bind_conf->ux.gid) == -1)) ||
-           (listener->bind_conf->ux.mode != 0 && chmod(tempname, listener->bind_conf->ux.mode) == -1)) {
+       if (!ext &&
+           (((listener->bind_conf->ux.uid != -1 || listener->bind_conf->ux.gid != -1) &&
+             (chown(tempname, listener->bind_conf->ux.uid, listener->bind_conf->ux.gid) == -1)) ||
+            (listener->bind_conf->ux.mode != 0 && chmod(tempname, listener->bind_conf->ux.mode) == -1))) {
                msg = "cannot change UNIX socket ownership";
                goto err_unlink_temp;
        }
 
-       if (listen(fd, listener->backlog ? listener->backlog : listener->maxconn) < 0) {
+       ready = 0;
+       ready_len = sizeof(ready);
+       if (getsockopt(fd, SOL_SOCKET, SO_ACCEPTCONN, &ready, &ready_len) == -1)
+               ready = 0;
+
+       if (!(ext && ready) && /* only listen if not already done by external process */
+           listen(fd, listener->backlog ? listener->backlog : listener->maxconn) < 0) {
                msg = "cannot listen to UNIX socket";
                goto err_unlink_temp;
        }
@@ -253,13 +277,19 @@ static int uxst_bind_listener(struct listener *listener, char *errmsg, int errle
         * fear loosing the socket <path> because we have a copy of it in
         * backname.
         */
-       if (rename(tempname, path) < 0) {
+       if (!ext && rename(tempname, path) < 0) {
                msg = "cannot switch final and temporary UNIX sockets";
                goto err_rename;
        }
 
-       /* 6. cleanup */
-       unlink(backname); /* no need to keep this one either */
+       /* 6. cleanup. If we're bound to an fd inherited from the parent, we
+        * want to ensure that destroy_uxst_socket() will never remove the
+        * path, and for this we simply clear the path to the socket.
+        */
+       if (!ext)
+               unlink(backname);
+       else
+               ((struct sockaddr_un *)&listener->addr)->sun_path[0] = 0;
 
        /* the socket is now listening */
        listener->fd = fd;
@@ -275,13 +305,19 @@ static int uxst_bind_listener(struct listener *listener, char *errmsg, int errle
        if (ret < 0 && errno == ENOENT)
                unlink(path);
  err_unlink_temp:
-       unlink(tempname);
+       if (!ext)
+               unlink(tempname);
        close(fd);
  err_unlink_back:
-       unlink(backname);
+       if (!ext)
+               unlink(backname);
  err_return:
-       if (msg && errlen)
-               snprintf(errmsg, errlen, "%s [%s]", msg, path);
+       if (msg && errlen) {
+               if (!ext)
+                       snprintf(errmsg, errlen, "%s [%s]", msg, path);
+               else
+                       snprintf(errmsg, errlen, "%s [fd %d]", msg, fd);
+       }
        return ERR_FATAL | ERR_ALERT;
 }
 
index c670be0ac0124a48a4e620c5112b3dc491cbe531..cc22ba7fc16430e79a152c0443d448fe2e57f0c9 100644 (file)
@@ -632,6 +632,7 @@ static struct sockaddr_storage *str2ip(const char *str, struct sockaddr_storage
  *    - "ipv6@"  => force address to resolve as IPv6 and fail if not possible.
  *    - "unix@"  => force address to be a path to a UNIX socket even if the
  *                  path does not start with a '/'
+ *    - "fd@"    => an integer must follow, and is a file descriptor number.
  *
  * Also note that in order to avoid any ambiguity with IPv6 addresses, the ':'
  * is mandatory after the IP address even when no port is specified. NULL is
@@ -640,6 +641,9 @@ static struct sockaddr_storage *str2ip(const char *str, struct sockaddr_storage
  *
  * If <pfx> is non-null, it is used as a string prefix before any path-based
  * address (typically the path to a unix socket).
+ *
+ * When a file descriptor is passed, its value is put into the s_addr part of
+ * the address when cast to sockaddr_in and the address family is AF_UNSPEC.
  */
 struct sockaddr_storage *str2sa_range(const char *str, int *low, int *high, char **err, const char *pfx)
 {
@@ -677,7 +681,21 @@ struct sockaddr_storage *str2sa_range(const char *str, int *low, int *high, char
        else
                ss.ss_family = AF_UNSPEC;
 
-       if (ss.ss_family == AF_UNIX) {
+       if (ss.ss_family == AF_UNSPEC && strncmp(str2, "fd@", 3) == 0) {
+               char *endptr;
+
+               str2 += 3;
+               ((struct sockaddr_in *)&ss)->sin_addr.s_addr = strtol(str2, &endptr, 10);
+
+               if (!*str2 || *endptr) {
+                       memprintf(err, "file descriptor '%s' is not a valid integer\n", str2);
+                       goto out;
+               }
+
+               /* we return AF_UNSPEC if we use a file descriptor number */
+               ss.ss_family = AF_UNSPEC;
+       }
+       else if (ss.ss_family == AF_UNIX) {
                int prefix_path_len;
                int max_path_len;