I backported the necessary patchset for seamless reloads on top of 1.7.9 a
while back.  It was used in production without issue for quite some time.

I just rebased those patches on top of haproxy-1.7 development and pushed
the result to seamless_reload branch that I pushed to github.  They apply
cleanly, but I have not built or tested them, nor do I have the time to do
so at the moment.
https://github.com/chiluk/haproxy-1.7

I'm also attached the patchset for completeness.  Happy reloading.

I think the 1.7 maintainer should pick these patches up, as the hard work
has already been done.

Dave.



On Mon, Apr 30, 2018 at 4:26 AM William Lallemand <wlallem...@irq6.net>
wrote:

> On Mon, Apr 30, 2018 at 10:35:37AM +0300, Veiko Kukk wrote:
> > On 26/04/18 17:11, Veiko Kukk wrote:
> > > Hi,
> > >
> > > According to
> > >
> https://www.haproxy.com/blog/truly-seamless-reloads-with-haproxy-no-more-hacks/
> > > :
> > >
> > > "The patchset has already been merged into the HAProxy 1.8 development
> > > branch and will soon be backported to HAProxy Enterprise Edition 1.7r1
> > > and possibly 1.6r2."
> > >
> > > Has it been backported to 1.7 and/or 1.6?
> > >
> > > If yes, then should seamless reload also work with multiprocess
> > > configurations? (nbproc > 1).
> >
> > Can i assume the answer is no for both questions?
> >
> >
> > Veiko
> >
>
> Hello Veiko,
>
> Indeed, the seamless reload is only available since HAProxy 1.8.
>
> It supports multiprocess configuration.
>
>
> --
> William Lallemand
>
>
From cd0e6748ad7ce13ff9db07b7e32e56a0c77f1afe Mon Sep 17 00:00:00 2001
From: William Lallemand <wlallem...@haproxy.com>
Date: Fri, 26 May 2017 18:19:55 +0200
Subject: [PATCH 10/10] MEDIUM: proxy: zombify proxies only when the expose-fd
 socket is bound

When HAProxy is running with multiple processes and some listeners
arebound to processes, the unused sockets were not closed in the other
processes. The aim was to be able to send those listening sockets using
the -x option.

However to ensure the previous behavior which was to close those
sockets, we provided the "no-unused-socket" global option.

This patch changes this behavior, it will close unused sockets which are
not in the same process as an expose-fd socket, making the
"no-unused-socket" option useless.

The "no-unused-socket" option was removed in this patch.

(cherry picked from commit 7f80eb2383bb54ddafecf0e7df6b3b3ef4b4f6e5)
Signed-off-by: Dave Chiluk <dchi...@indeed.com>
---
 doc/configuration.txt |  7 -------
 src/cfgparse.c        |  5 -----
 src/haproxy.c         | 19 ++++++++++++++++++-
 3 files changed, 18 insertions(+), 13 deletions(-)

diff --git a/doc/configuration.txt b/doc/configuration.txt
index 9bb9cb9..980b253 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -587,7 +587,6 @@ The following keywords are supported in the "global" section :
    - nosplice
    - nogetaddrinfo
    - noreuseport
-   - no-unused-socket
    - spread-checks
    - server-state-base
    - server-state-file
@@ -1250,12 +1249,6 @@ noreuseport
   Disables the use of SO_REUSEPORT - see socket(7). It is equivalent to the
   command line argument "-dR".
 
-no-unused-socket
-  By default, each haproxy process keeps all sockets opened, event those that
-  are only used by another processes, so that any process can provide all the
-  sockets, to make reloads seamless. This option disables this, and close all
-  unused sockets, to save some file descriptors.
-
 spread-checks <0..50, in percent>
   Sometimes it is desirable to avoid sending agent and health checks to
   servers at exact intervals, for instance when many logical servers are
diff --git a/src/cfgparse.c b/src/cfgparse.c
index be21088..8c0906b 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -671,11 +671,6 @@ int cfg_parse_global(const char *file, int linenum, char **args, int kwm)
 			goto out;
 		global.tune.options &= ~GTUNE_USE_REUSEPORT;
 	}
-	else if (!strcmp(args[0], "no-unused-socket")) {
-		if (alertif_too_many_args(0, file, linenum, args, &err_code))
-			goto out;
-		global.tune.options &= ~GTUNE_SOCKET_TRANSFER;
-	}
 	else if (!strcmp(args[0], "quiet")) {
 		if (alertif_too_many_args(0, file, linenum, args, &err_code))
 			goto out;
diff --git a/src/haproxy.c b/src/haproxy.c
index 2091573..f7605e0 100644
--- a/src/haproxy.c
+++ b/src/haproxy.c
@@ -975,7 +975,6 @@ void init(int argc, char **argv)
 #if defined(SO_REUSEPORT)
 	global.tune.options |= GTUNE_USE_REUSEPORT;
 #endif
-	global.tune.options |= GTUNE_SOCKET_TRANSFER;
 
 	pid = getpid();
 	progname = *argv;
@@ -2306,6 +2305,24 @@ int main(int argc, char **argv)
 			exit(0); /* parent must leave */
 		}
 
+		/* pass through every cli socket, and check if it's bound to
+		 * the current process and if it exposes listeners sockets.
+		 * Caution: the GTUNE_SOCKET_TRANSFER is now set after the fork.
+		 * */
+
+		if (global.stats_fe) {
+			struct bind_conf *bind_conf;
+
+			list_for_each_entry(bind_conf, &global.stats_fe->conf.bind, by_fe) {
+				if (bind_conf->level & ACCESS_FD_LISTENERS) {
+					if (!bind_conf->bind_proc || bind_conf->bind_proc & (1UL << proc)) {
+						global.tune.options |= GTUNE_SOCKET_TRANSFER;
+						break;
+					}
+				}
+			}
+		}
+
 		/* we might have to unbind some proxies from some processes */
 		px = proxy;
 		while (px != NULL) {
-- 
2.14.1

From 109eb33d0fb4b4d904a1cb4be6d3a845c6c6af52 Mon Sep 17 00:00:00 2001
From: Olivier Houchard <ohouch...@haproxy.com>
Date: Thu, 6 Apr 2017 14:45:14 +0200
Subject: [PATCH 06/10] MINOR: socket transfer: Set a timeout on the socket.

Make sure we're not stuck forever by setting a timeout on the socket.

(cherry picked from commit 547408787ffe1c03b975e94f01b64492d15ca97d)
Signed-off-by: Dave Chiluk <dchi...@indeed.com>
---
 src/cli.c     | 2 ++
 src/haproxy.c | 2 ++
 2 files changed, 4 insertions(+)

diff --git a/src/cli.c b/src/cli.c
index d3cb751..7110021 100644
--- a/src/cli.c
+++ b/src/cli.c
@@ -919,6 +919,7 @@ static int _getsocks(char **args, struct appctx *appctx, void *private)
 	struct connection *remote = objt_conn(si_opposite(si)->end);
 	struct msghdr msghdr;
 	struct iovec iov;
+	struct timeval tv = { .tv_sec = 1, .tv_usec = 0 };
 	int *tmpfd;
 	int tot_fd_nb = 0;
 	struct proxy *px;
@@ -943,6 +944,7 @@ static int _getsocks(char **args, struct appctx *appctx, void *private)
 		Warning("Cannot make the unix socket blocking\n");
 		goto out;
 	}
+	setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (void *)&tv, sizeof(tv));
 	iov.iov_base = &tot_fd_nb;
 	iov.iov_len = sizeof(tot_fd_nb);
 	if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
diff --git a/src/haproxy.c b/src/haproxy.c
index 034d315..2091573 100644
--- a/src/haproxy.c
+++ b/src/haproxy.c
@@ -691,6 +691,7 @@ static int get_old_sockets(const char *unixsocket)
 	struct msghdr msghdr;
 	struct iovec iov;
 	struct xfer_sock_list *xfer_sock = NULL;
+	struct timeval tv = { .tv_sec = 1, .tv_usec = 0 };
 	int sock = -1;
 	int ret = -1;
 	int ret2 = -1;
@@ -720,6 +721,7 @@ static int get_old_sockets(const char *unixsocket)
 		    unixsocket);
 		goto out;
 	}
+	setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (void *)&tv, sizeof(tv));
 	iov.iov_base = &fd_nb;
 	iov.iov_len = sizeof(fd_nb);
 	msghdr.msg_iov = &iov;
-- 
2.14.1

From 7d3289f78ed8ea1058f841c93cf9fe9a43100248 Mon Sep 17 00:00:00 2001
From: William Lallemand <wlallem...@haproxy.com>
Date: Fri, 26 May 2017 17:42:10 +0200
Subject: [PATCH 09/10] MINOR: cli: add 'expose-fd listeners' to pass listeners
 FDs

This patch changes the stats socket rights for allowing the sending of
listening sockets.

The previous behavior was to allow any unix stats socket with admin
level to send sockets. It's not possible anymore, you have to set this
option to activate the socket sending.

Example:
   stats socket /var/run/haproxy4.sock mode 666 expose-fd listeners level user process 4
(cherry picked from commit f6975e9f76112c375af8ff98a22d5886541faeb3)
Signed-off-by: Dave Chiluk <dchi...@indeed.com>
---
 doc/configuration.txt  |  5 +++++
 doc/management.txt     |  3 ++-
 include/types/global.h |  2 ++
 src/cli.c              | 24 ++++++++++++++++++++++--
 4 files changed, 31 insertions(+), 3 deletions(-)

diff --git a/doc/configuration.txt b/doc/configuration.txt
index 193f8b8..9bb9cb9 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -10325,6 +10325,11 @@ defer-accept
   an established connection while the proxy will only see it in SYN_RECV. This
   option is only supported on TCPv4/TCPv6 sockets and ignored by other ones.
 
+expose-fd listeners
+  This option is only usable with the stats socket. It gives your stats socket
+  the capability to pass listeners FD to another HAProxy process.
+  See alors "-x" in the management guide.
+
 force-sslv3
   This option enforces use of SSLv3 only on SSL connections instantiated from
   this listener. SSLv3 is generally less expensive than the TLS counterparts
diff --git a/doc/management.txt b/doc/management.txt
index 353f3b7..4017344 100644
--- a/doc/management.txt
+++ b/doc/management.txt
@@ -280,7 +280,8 @@ list of options is :
   -x <unix_socket> : connect to the specified socket and try to retrieve any
     listening sockets from the old process, and use them instead of trying to
     bind new ones. This is useful to avoid missing any new connection when
-    reloading the configuration on Linux.
+    reloading the configuration on Linux. The capability must be enable on the
+    stats socket using "expose-fd listeners" in your configuration.
 
 A safe way to start HAProxy from an init file consists in forcing the daemon
 mode, storing existing pids to a pid file and using this pid file to notify
diff --git a/include/types/global.h b/include/types/global.h
index b78e154..89282e6 100644
--- a/include/types/global.h
+++ b/include/types/global.h
@@ -77,6 +77,8 @@
 #define ACCESS_LVL_MASK     0x3
 
 
+#define ACCESS_FD_LISTENERS 0x4  /* expose listeners FDs on stats socket */
+
 /* SSL server verify mode */
 enum {
 	SSL_SERVER_VERIFY_NONE = 0,
diff --git a/src/cli.c b/src/cli.c
index 6035bac..f6cab16 100644
--- a/src/cli.c
+++ b/src/cli.c
@@ -887,6 +887,24 @@ static int cli_parse_set_ratelimit(char **args, struct appctx *appctx, void *pri
 	return 1;
 }
 
+/* parse the "expose-fd" argument on the bind lines */
+static int bind_parse_expose_fd(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err)
+{
+	if (!*args[cur_arg + 1]) {
+		memprintf(err, "'%s' : missing fd type", args[cur_arg]);
+		return ERR_ALERT | ERR_FATAL;
+	}
+	if (!strcmp(args[cur_arg+1], "listeners")) {
+		conf->level |= ACCESS_FD_LISTENERS;
+	} else {
+		memprintf(err, "'%s' only supports 'listeners' (got '%s')",
+			  args[cur_arg], args[cur_arg+1]);
+		return ERR_ALERT | ERR_FATAL;
+	}
+
+	return 0;
+}
+
 /* parse the "level" argument on the bind lines */
 static int bind_parse_level(char **args, int cur_arg, struct proxy *px, struct bind_conf *conf, char **err)
 {
@@ -920,6 +938,7 @@ static int _getsocks(char **args, struct appctx *appctx, void *private)
 	unsigned char *tmpbuf = NULL;
 	struct cmsghdr *cmsg;
 	struct stream_interface *si = appctx->owner;
+	struct stream *s = si_strm(si);
 	struct connection *remote = objt_conn(si_opposite(si)->end);
 	struct msghdr msghdr;
 	struct iovec iov;
@@ -951,7 +970,7 @@ static int _getsocks(char **args, struct appctx *appctx, void *private)
 	setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (void *)&tv, sizeof(tv));
 	iov.iov_base = &tot_fd_nb;
 	iov.iov_len = sizeof(tot_fd_nb);
-	if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
+	if (!(strm_li(s)->bind_conf->level & ACCESS_FD_LISTENERS))
 		goto out;
 	memset(&msghdr, 0, sizeof(msghdr));
 	/*
@@ -1117,7 +1136,8 @@ static struct cfg_kw_list cfg_kws = {ILH, {
 }};
 
 static struct bind_kw_list bind_kws = { "STAT", { }, {
-	{ "level",    bind_parse_level,    1 }, /* set the unix socket admin level */
+	{ "level",     bind_parse_level,    1 }, /* set the unix socket admin level */
+	{ "expose-fd", bind_parse_expose_fd, 1 }, /* set the unix socket expose fd rights */
 	{ NULL, NULL, 0 },
 }};
 
-- 
2.14.1

From e55e1f5e2a663b52ea7f924116af4731af96e22f Mon Sep 17 00:00:00 2001
From: William Lallemand <wlallem...@haproxy.com>
Date: Wed, 24 May 2017 00:57:40 +0200
Subject: [PATCH 08/10] MINOR: cli: add ACCESS_LVL_MASK to store the access
 level

The current level variable use only 2 bits for storing the 3 access
level (user, oper and admin).

This patch add a bitmask which allows to use the remaining bits for
other usage.

(cherry picked from commit 07a62f7a7ee1caf24283ba73df1467be2517ae88)
Signed-off-by: Dave Chiluk <dchi...@indeed.com>
---
 include/types/global.h |  2 ++
 src/cli.c              | 22 +++++++++++++---------
 src/stats.c            |  2 +-
 src/stick_table.c      |  4 ++--
 4 files changed, 18 insertions(+), 12 deletions(-)

diff --git a/include/types/global.h b/include/types/global.h
index bc7e4dd..b78e154 100644
--- a/include/types/global.h
+++ b/include/types/global.h
@@ -74,6 +74,8 @@
 #define ACCESS_LVL_USER     1
 #define ACCESS_LVL_OPER     2
 #define ACCESS_LVL_ADMIN    3
+#define ACCESS_LVL_MASK     0x3
+
 
 /* SSL server verify mode */
 enum {
diff --git a/src/cli.c b/src/cli.c
index 7110021..6035bac 100644
--- a/src/cli.c
+++ b/src/cli.c
@@ -218,7 +218,8 @@ static int stats_parse_global(char **args, int section_type, struct proxy *curpx
 		}
 
 		bind_conf = bind_conf_alloc(&global.stats_fe->conf.bind, file, line, args[2]);
-		bind_conf->level = ACCESS_LVL_OPER; /* default access level */
+		bind_conf->level &= ~ACCESS_LVL_MASK;
+		bind_conf->level |= ACCESS_LVL_OPER; /* default access level */
 
 		if (!str2listener(args[2], global.stats_fe, bind_conf, file, line, err)) {
 			memprintf(err, "parsing [%s:%d] : '%s %s' : %s\n",
@@ -384,7 +385,7 @@ int cli_has_level(struct appctx *appctx, int level)
 	struct stream_interface *si = appctx->owner;
 	struct stream *s = si_strm(si);
 
-	if (strm_li(s)->bind_conf->level < level) {
+	if ((strm_li(s)->bind_conf->level & ACCESS_LVL_MASK) < level) {
 		appctx->ctx.cli.msg = stats_permission_denied_msg;
 		appctx->st0 = CLI_ST_PRINT;
 		return 0;
@@ -894,13 +895,16 @@ static int bind_parse_level(char **args, int cur_arg, struct proxy *px, struct b
 		return ERR_ALERT | ERR_FATAL;
 	}
 
-	if (!strcmp(args[cur_arg+1], "user"))
-		conf->level = ACCESS_LVL_USER;
-	else if (!strcmp(args[cur_arg+1], "operator"))
-		conf->level = ACCESS_LVL_OPER;
-	else if (!strcmp(args[cur_arg+1], "admin"))
-		conf->level = ACCESS_LVL_ADMIN;
-	else {
+	if (!strcmp(args[cur_arg+1], "user")) {
+		conf->level &= ~ACCESS_LVL_MASK;
+		conf->level |= ACCESS_LVL_USER;
+	} else if (!strcmp(args[cur_arg+1], "operator")) {
+		conf->level &= ~ACCESS_LVL_MASK;
+		conf->level |= ACCESS_LVL_OPER;
+	} else if (!strcmp(args[cur_arg+1], "admin")) {
+		conf->level &= ~ACCESS_LVL_MASK;
+		conf->level |= ACCESS_LVL_ADMIN;
+	} else {
 		memprintf(err, "'%s' only supports 'user', 'operator', and 'admin' (got '%s')",
 			  args[cur_arg], args[cur_arg+1]);
 		return ERR_ALERT | ERR_FATAL;
diff --git a/src/stats.c b/src/stats.c
index 60b3756..e76055c 100644
--- a/src/stats.c
+++ b/src/stats.c
@@ -1726,7 +1726,7 @@ int stats_dump_proxy_to_buffer(struct stream_interface *si, struct proxy *px, st
 
 	if (uri)
 		flags = uri->flags;
-	else if (strm_li(s)->bind_conf->level >= ACCESS_LVL_OPER)
+	else if ((strm_li(s)->bind_conf->level & ACCESS_LVL_MASK) >= ACCESS_LVL_OPER)
 		flags = ST_SHLGNDS | ST_SHNODE | ST_SHDESC;
 	else
 		flags = ST_SHNODE | ST_SHDESC;
diff --git a/src/stick_table.c b/src/stick_table.c
index 7e6e0e8..cd2e526 100644
--- a/src/stick_table.c
+++ b/src/stick_table.c
@@ -2253,7 +2253,7 @@ static int table_dump_head_to_buffer(struct chunk *msg, struct stream_interface
 
 	/* any other information should be dumped here */
 
-	if (target && strm_li(s)->bind_conf->level < ACCESS_LVL_OPER)
+	if (target && (strm_li(s)->bind_conf->level & ACCESS_LVL_MASK) < ACCESS_LVL_OPER)
 		chunk_appendf(msg, "# contents not dumped due to insufficient privileges\n");
 
 	if (bi_putchk(si_ic(si), msg) == -1) {
@@ -2673,7 +2673,7 @@ static int cli_io_handler_table(struct appctx *appctx)
 					return 0;
 
 				if (appctx->ctx.table.target &&
-				    strm_li(s)->bind_conf->level >= ACCESS_LVL_OPER) {
+				    (strm_li(s)->bind_conf->level & ACCESS_LVL_MASK) >= ACCESS_LVL_OPER) {
 					/* dump entries only if table explicitly requested */
 					eb = ebmb_first(&appctx->ctx.table.proxy->table.keys);
 					if (eb) {
-- 
2.14.1

From 4de14753ea888594ae3568acf3185ee8721edc7c Mon Sep 17 00:00:00 2001
From: Olivier Houchard <ohouch...@haproxy.com>
Date: Sun, 9 Apr 2017 16:28:10 +0200
Subject: [PATCH 07/10] MINOR: systemd wrapper: add support for passing the -x
 option.

Make the systemd wrapper chech if HAPROXY_STATS_SOCKET if set.
If set, it will use it as an argument to the "-x" option, which makes
haproxy asks for any listening socket, on the stats socket, in order
to achieve reloads with no new connection lost.

(cherry picked from commit 2c9744fe56825c83d62fd94db52fbb489227f4cd)
Signed-off-by: Dave Chiluk <dchi...@indeed.com>
---
 contrib/systemd/haproxy.service.in |  2 ++
 src/haproxy-systemd-wrapper.c      | 10 +++++++++-
 2 files changed, 11 insertions(+), 1 deletion(-)

diff --git a/contrib/systemd/haproxy.service.in b/contrib/systemd/haproxy.service.in
index dca81a2..05bb716 100644
--- a/contrib/systemd/haproxy.service.in
+++ b/contrib/systemd/haproxy.service.in
@@ -3,6 +3,8 @@ Description=HAProxy Load Balancer
 After=network.target
 
 [Service]
+# You can point the environment variable HAPROXY_STATS_SOCKET to a stats
+# socket if you want seamless reloads.
 Environment="CONFIG=/etc/haproxy/haproxy.cfg" "PIDFILE=/run/haproxy.pid"
 ExecStartPre=@SBINDIR@/haproxy -f $CONFIG -c -q
 ExecStart=@SBINDIR@/haproxy-systemd-wrapper -f $CONFIG -p $PIDFILE
diff --git a/src/haproxy-systemd-wrapper.c b/src/haproxy-systemd-wrapper.c
index f6a9c85..457f5bd 100644
--- a/src/haproxy-systemd-wrapper.c
+++ b/src/haproxy-systemd-wrapper.c
@@ -92,11 +92,15 @@ static void spawn_haproxy(char **pid_strv, int nb_pid)
 	pid = fork();
 	if (!pid) {
 		char **argv;
+		char *stats_socket = NULL;
 		int i;
 		int argno = 0;
 
 		/* 3 for "haproxy -Ds -sf" */
-		argv = calloc(4 + main_argc + nb_pid + 1, sizeof(char *));
+		if (nb_pid > 0)
+			stats_socket = getenv("HAPROXY_STATS_SOCKET");
+		argv = calloc(4 + main_argc + nb_pid + 1 +
+		    (stats_socket != NULL ? 2 : 0), sizeof(char *));
 		if (!argv) {
 			fprintf(stderr, SD_NOTICE "haproxy-systemd-wrapper: failed to calloc(), please try again later.\n");
 			exit(1);
@@ -121,6 +125,10 @@ static void spawn_haproxy(char **pid_strv, int nb_pid)
 			argv[argno++] = "-sf";
 			for (i = 0; i < nb_pid; ++i)
 				argv[argno++] = pid_strv[i];
+			if (stats_socket != NULL) {
+				argv[argno++] = "-x";
+				argv[argno++] = stats_socket;
+			}
 		}
 		argv[argno] = NULL;
 
-- 
2.14.1

From 34a2a67a10b87bdd25a5fef25b36e032e90143c0 Mon Sep 17 00:00:00 2001
From: Olivier Houchard <ohouch...@haproxy.com>
Date: Wed, 5 Apr 2017 22:50:59 +0200
Subject: [PATCH 04/10] MINOR: doc: document the -x flag

(cherry picked from commit d33fc3a7f5882add601725e71163539eb1163847)
Signed-off-by: Dave Chiluk <dchi...@indeed.com>
---
 doc/haproxy.1      | 8 +++++++-
 doc/management.txt | 5 +++++
 2 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/doc/haproxy.1 b/doc/haproxy.1
index 0a1b2f5..91f58a3 100644
--- a/doc/haproxy.1
+++ b/doc/haproxy.1
@@ -6,7 +6,7 @@ HAProxy \- fast and reliable http reverse proxy and load balancer
 
 .SH SYNOPSIS
 
-haproxy \-f <configuration\ file|dir> [\-L\ <name>] [\-n\ maxconn] [\-N\ maxconn] [\-C\ <dir>] [\-v|\-vv] [\-d] [\-D] [\-q] [\-V] [\-c] [\-p\ <pidfile>] [\-dk] [\-ds] [\-de] [\-dp] [\-db] [\-dM[<byte>]] [\-m\ <megs>] [{\-sf|\-st}\ pidlist...]
+haproxy \-f <configuration\ file|dir> [\-L\ <name>] [\-n\ maxconn] [\-N\ maxconn] [\-C\ <dir>] [\-v|\-vv] [\-d] [\-D] [\-q] [\-V] [\-c] [\-p\ <pidfile>] [\-dk] [\-ds] [\-de] [\-dp] [\-db] [\-dM[<byte>]] [\-m\ <megs>] [\-x <unix_socket>] [{\-sf|\-st}\ pidlist...]
 
 .SH DESCRIPTION
 
@@ -155,6 +155,12 @@ which receive this signal will terminate immediately, closing all active
 sessions. This option must be specified last, followed by any number of
 PIDs. Technically speaking, \fBSIGTTOU\fP and \fBSIGTERM\fP are sent.
 
+.TP
+\f8\-x <unix_socket>\fP
+Attempt to connect to the unix socket, and retrieve all the listening sockets
+from the old process. Those sockets will then be used if possible instead of
+binding new ones.
+
 .SH LOGGING
 Since HAProxy can run inside a chroot, it cannot reliably access /dev/log.
 For this reason, it uses the UDP protocol to send its logs to the server,
diff --git a/doc/management.txt b/doc/management.txt
index 06a5c0a..353f3b7 100644
--- a/doc/management.txt
+++ b/doc/management.txt
@@ -277,6 +277,11 @@ list of options is :
   -vv : display the version, build options, libraries versions and usable
     pollers. This output is systematically requested when filing a bug report.
 
+  -x <unix_socket> : connect to the specified socket and try to retrieve any
+    listening sockets from the old process, and use them instead of trying to
+    bind new ones. This is useful to avoid missing any new connection when
+    reloading the configuration on Linux.
+
 A safe way to start HAProxy from an init file consists in forcing the daemon
 mode, storing existing pids to a pid file and using this pid file to notify
 older processes to finish before leaving :
-- 
2.14.1

From 650f71a344487c685294d774c250c046b7c9769a Mon Sep 17 00:00:00 2001
From: Olivier Houchard <ohouch...@haproxy.com>
Date: Thu, 6 Apr 2017 01:05:05 +0200
Subject: [PATCH 05/10] MINOR: proxy: Don't close FDs if not our proxy.

When running with multiple process, if some proxies are just assigned
to some processes, the other processes will just close the file descriptors
for the listening sockets. However, we may still have to provide those
sockets when reloading, so instead we just try hard to pretend those proxies
are dead, while keeping the sockets opened.
A new global option, no-reused-socket", has been added, to restore the old
behavior of closing the sockets not bound to this process.

(cherry picked from commit 1fc0516516defd57574efb677d7f698757e62d68)
Signed-off-by: Dave Chiluk <dchi...@indeed.com>
---
 doc/configuration.txt    |  7 +++++++
 include/proto/fd.h       |  5 +++++
 include/proto/listener.h |  5 +++++
 include/proto/proxy.h    |  1 +
 include/types/global.h   |  2 ++
 include/types/listener.h |  1 +
 src/cfgparse.c           |  5 +++++
 src/cli.c                |  9 +++++----
 src/fd.c                 | 21 +++++++++++++++++--
 src/haproxy.c            | 18 +++++++++++++++--
 src/listener.c           | 44 ++++++++++++++++++++++++++++++----------
 src/proxy.c              | 52 ++++++++++++++++++++++++++++++++++++++++++++++++
 12 files changed, 151 insertions(+), 19 deletions(-)

diff --git a/doc/configuration.txt b/doc/configuration.txt
index 37bf44f..193f8b8 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -587,6 +587,7 @@ The following keywords are supported in the "global" section :
    - nosplice
    - nogetaddrinfo
    - noreuseport
+   - no-unused-socket
    - spread-checks
    - server-state-base
    - server-state-file
@@ -1249,6 +1250,12 @@ noreuseport
   Disables the use of SO_REUSEPORT - see socket(7). It is equivalent to the
   command line argument "-dR".
 
+no-unused-socket
+  By default, each haproxy process keeps all sockets opened, event those that
+  are only used by another processes, so that any process can provide all the
+  sockets, to make reloads seamless. This option disables this, and close all
+  unused sockets, to save some file descriptors.
+
 spread-checks <0..50, in percent>
   Sometimes it is desirable to avoid sending agent and health checks to
   servers at exact intervals, for instance when many logical servers are
diff --git a/include/proto/fd.h b/include/proto/fd.h
index 87309bf..1efe323 100644
--- a/include/proto/fd.h
+++ b/include/proto/fd.h
@@ -41,6 +41,11 @@ extern int fd_nbupdt;               // number of updates in the list
  */
 void fd_delete(int fd);
 
+/* Deletes an FD from the fdsets, and recomputes the maxfd limit.
+ * The file descriptor is kept open.
+ */
+void fd_remove(int fd);
+
 /* disable the specified poller */
 void disable_poller(const char *poller_name);
 
diff --git a/include/proto/listener.h b/include/proto/listener.h
index 1f042af..c3d8d95 100644
--- a/include/proto/listener.h
+++ b/include/proto/listener.h
@@ -88,6 +88,11 @@ void dequeue_all_listeners(struct list *list);
  */
 int unbind_listener(struct listener *listener);
 
+/* This function pretends the listener is dead, but keeps the FD opened, so
+ * that we can provide it, for conf reloading.
+ */
+int unbind_listener_no_close(struct listener *listener);
+
 /* This function closes all listening sockets bound to the protocol <proto>,
  * and the listeners end in LI_ASSIGNED state if they were higher. It does not
  * detach them from the protocol. It always returns ERR_NONE.
diff --git a/include/proto/proxy.h b/include/proto/proxy.h
index a0fa454..3b05650 100644
--- a/include/proto/proxy.h
+++ b/include/proto/proxy.h
@@ -42,6 +42,7 @@ void soft_stop(void);
 int pause_proxy(struct proxy *p);
 int resume_proxy(struct proxy *p);
 void stop_proxy(struct proxy *p);
+void zombify_proxy(struct proxy *p);
 void pause_proxies(void);
 void resume_proxies(void);
 int  stream_set_backend(struct stream *s, struct proxy *be);
diff --git a/include/types/global.h b/include/types/global.h
index 10f3a3c..bc7e4dd 100644
--- a/include/types/global.h
+++ b/include/types/global.h
@@ -67,6 +67,8 @@
 #define GTUNE_USE_REUSEPORT      (1<<6)
 #define GTUNE_RESOLVE_DONTFAIL   (1<<7)
 
+#define GTUNE_SOCKET_TRANSFER	 (1<<8)
+
 /* Access level for a stats socket */
 #define ACCESS_LVL_NONE     0
 #define ACCESS_LVL_USER     1
diff --git a/include/types/listener.h b/include/types/listener.h
index 015852b..1d39ace 100644
--- a/include/types/listener.h
+++ b/include/types/listener.h
@@ -47,6 +47,7 @@ enum li_state {
 	LI_INIT,        /* all parameters filled in, but not assigned yet */
 	LI_ASSIGNED,    /* assigned to the protocol, but not listening yet */
 	LI_PAUSED,      /* listener was paused, it's bound but not listening  */
+	LI_ZOMBIE,	/* The listener doesn't belong to the process, but is kept opened */
 	LI_LISTEN,      /* started, listening but not enabled */
 	LI_READY,       /* started, listening and enabled */
 	LI_FULL,        /* reached its connection limit */
diff --git a/src/cfgparse.c b/src/cfgparse.c
index 8c0906b..be21088 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -671,6 +671,11 @@ int cfg_parse_global(const char *file, int linenum, char **args, int kwm)
 			goto out;
 		global.tune.options &= ~GTUNE_USE_REUSEPORT;
 	}
+	else if (!strcmp(args[0], "no-unused-socket")) {
+		if (alertif_too_many_args(0, file, linenum, args, &err_code))
+			goto out;
+		global.tune.options &= ~GTUNE_SOCKET_TRANSFER;
+	}
 	else if (!strcmp(args[0], "quiet")) {
 		if (alertif_too_many_args(0, file, linenum, args, &err_code))
 			goto out;
diff --git a/src/cli.c b/src/cli.c
index 5fbf1e4..d3cb751 100644
--- a/src/cli.c
+++ b/src/cli.c
@@ -957,10 +957,11 @@ static int _getsocks(char **args, struct appctx *appctx, void *private)
 		struct listener *l;
 
 		list_for_each_entry(l, &px->conf.listeners, by_fe) {
-			/* Only transfer IPv4/IPv6 sockets */
-			if (l->proto->sock_family == AF_INET ||
+			/* Only transfer IPv4/IPv6/UNIX sockets */
+			if (l->state >= LI_ZOMBIE &&
+			    (l->proto->sock_family == AF_INET ||
 			    l->proto->sock_family == AF_INET6 ||
-			    l->proto->sock_family == AF_UNIX)
+			    l->proto->sock_family == AF_UNIX))
 				tot_fd_nb++;
 		}
 		px = px->next;
@@ -1011,7 +1012,7 @@ static int _getsocks(char **args, struct appctx *appctx, void *private)
 		list_for_each_entry(l, &px->conf.listeners, by_fe) {
 			int ret;
 			/* Only transfer IPv4/IPv6 sockets */
-			if (l->state >= LI_LISTEN &&
+			if (l->state >= LI_ZOMBIE &&
 			    (l->proto->sock_family == AF_INET ||
 			    l->proto->sock_family == AF_INET6 ||
 			    l->proto->sock_family == AF_UNIX)) {
diff --git a/src/fd.c b/src/fd.c
index aeee602..1a62f9a 100644
--- a/src/fd.c
+++ b/src/fd.c
@@ -175,7 +175,7 @@ int fd_nbupdt = 0;             // number of updates in the list
 /* Deletes an FD from the fdsets, and recomputes the maxfd limit.
  * The file descriptor is also closed.
  */
-void fd_delete(int fd)
+static void fd_dodelete(int fd, int do_close)
 {
 	if (fdtab[fd].linger_risk) {
 		/* this is generally set when connecting to servers */
@@ -190,7 +190,8 @@ void fd_delete(int fd)
 
 	port_range_release_port(fdinfo[fd].port_range, fdinfo[fd].local_port);
 	fdinfo[fd].port_range = NULL;
-	close(fd);
+	if (do_close)
+		close(fd);
 	fdtab[fd].owner = NULL;
 	fdtab[fd].new = 0;
 
@@ -198,6 +199,22 @@ void fd_delete(int fd)
 		maxfd--;
 }
 
+/* Deletes an FD from the fdsets, and recomputes the maxfd limit.
+ * The file descriptor is also closed.
+ */
+void fd_delete(int fd)
+{
+	fd_dodelete(fd, 1);
+}
+
+/* Deletes an FD from the fdsets, and recomputes the maxfd limit.
+ * The file descriptor is kept open.
+ */
+void fd_remove(int fd)
+{
+	fd_dodelete(fd, 0);
+}
+
 /* Scan and process the cached events. This should be called right after
  * the poller. The loop may cause new entries to be created, for example
  * if a listener causes an accept() to initiate a new incoming connection
diff --git a/src/haproxy.c b/src/haproxy.c
index 9dabe3f..034d315 100644
--- a/src/haproxy.c
+++ b/src/haproxy.c
@@ -973,6 +973,7 @@ void init(int argc, char **argv)
 #if defined(SO_REUSEPORT)
 	global.tune.options |= GTUNE_USE_REUSEPORT;
 #endif
+	global.tune.options |= GTUNE_SOCKET_TRANSFER;
 
 	pid = getpid();
 	progname = *argv;
@@ -1808,6 +1809,15 @@ void deinit(void)
 		}/* end while(s) */
 
 		list_for_each_entry_safe(l, l_next, &p->conf.listeners, by_fe) {
+			/*
+			 * Zombie proxy, the listener just pretend to be up
+			 * because they still hold an opened fd.
+			 * Close it and give the listener its real state.
+			 */
+			if (p->state == PR_STSTOPPED && l->state >= LI_ZOMBIE) {
+				close(l->fd);
+				l->state = LI_INIT;
+			}
 			unbind_listener(l);
 			delete_listener(l);
 			LIST_DEL(&l->by_fe);
@@ -2298,8 +2308,12 @@ int main(int argc, char **argv)
 		px = proxy;
 		while (px != NULL) {
 			if (px->bind_proc && px->state != PR_STSTOPPED) {
-				if (!(px->bind_proc & (1UL << proc)))
-					stop_proxy(px);
+				if (!(px->bind_proc & (1UL << proc))) {
+					if (global.tune.options & GTUNE_SOCKET_TRANSFER)
+						zombify_proxy(px);
+					else
+						stop_proxy(px);
+				}
 			}
 			px = px->next;
 		}
diff --git a/src/listener.c b/src/listener.c
index 2c9359f..4600bc8 100644
--- a/src/listener.c
+++ b/src/listener.c
@@ -59,7 +59,12 @@ void enable_listener(struct listener *listener)
 			/* we don't want to enable this listener and don't
 			 * want any fd event to reach it.
 			 */
-			unbind_listener(listener);
+			if (!(global.tune.options & GTUNE_SOCKET_TRANSFER))
+				unbind_listener(listener);
+			else {
+				unbind_listener_no_close(listener);
+				listener->state = LI_LISTEN;
+			}
 		}
 		else if (listener->nbconn < listener->maxconn) {
 			fd_want_recv(listener->fd);
@@ -95,7 +100,7 @@ void disable_listener(struct listener *listener)
  */
 int pause_listener(struct listener *l)
 {
-	if (l->state <= LI_PAUSED)
+	if (l->state <= LI_ZOMBIE)
 		return 1;
 
 	if (l->proto->pause) {
@@ -149,7 +154,7 @@ int resume_listener(struct listener *l)
 			return 0;
 	}
 
-	if (l->state < LI_PAUSED)
+	if (l->state < LI_PAUSED || l->state == LI_ZOMBIE)
 		return 0;
 
 	if (l->proto->sock_prot == IPPROTO_TCP &&
@@ -242,12 +247,7 @@ void dequeue_all_listeners(struct list *list)
 	}
 }
 
-/* This function closes the listening socket for the specified listener,
- * provided that it's already in a listening state. The listener enters the
- * LI_ASSIGNED state. It always returns ERR_NONE. This function is intended
- * to be used as a generic function for standard protocols.
- */
-int unbind_listener(struct listener *listener)
+static int do_unbind_listener(struct listener *listener, int do_close)
 {
 	if (listener->state == LI_READY)
 		fd_stop_recv(listener->fd);
@@ -256,13 +256,35 @@ int unbind_listener(struct listener *listener)
 		LIST_DEL(&listener->wait_queue);
 
 	if (listener->state >= LI_PAUSED) {
-		fd_delete(listener->fd);
-		listener->fd = -1;
+		if (do_close) {
+			fd_delete(listener->fd);
+			listener->fd = -1;
+		}
+		else
+			fd_remove(listener->fd);
 		listener->state = LI_ASSIGNED;
 	}
 	return ERR_NONE;
 }
 
+/* This function closes the listening socket for the specified listener,
+ * provided that it's already in a listening state. The listener enters the
+ * LI_ASSIGNED state. It always returns ERR_NONE. This function is intended
+ * to be used as a generic function for standard protocols.
+ */
+int unbind_listener(struct listener *listener)
+{
+	return do_unbind_listener(listener, 1);
+}
+
+/* This function pretends the listener is dead, but keeps the FD opened, so
+ * that we can provide it, for conf reloading.
+ */
+int unbind_listener_no_close(struct listener *listener)
+{
+	return do_unbind_listener(listener, 0);
+}
+
 /* This function closes all listening sockets bound to the protocol <proto>,
  * and the listeners end in LI_ASSIGNED state if they were higher. It does not
  * detach them from the protocol. It always returns ERR_NONE.
diff --git a/src/proxy.c b/src/proxy.c
index 78120d9..1e2f22e 100644
--- a/src/proxy.c
+++ b/src/proxy.c
@@ -990,6 +990,19 @@ void soft_stop(void)
 	p = proxy;
 	tv_update_date(0,1); /* else, the old time before select will be used */
 	while (p) {
+		/* Zombie proxy, let's close the file descriptors */
+		if (p->state == PR_STSTOPPED &&
+		    !LIST_ISEMPTY(&p->conf.listeners) &&
+		    LIST_ELEM(p->conf.listeners.n,
+		    struct listener *, by_fe)->state >= LI_ZOMBIE) {
+			struct listener *l;
+			list_for_each_entry(l, &p->conf.listeners, by_fe) {
+				if (l->state >= LI_ZOMBIE)
+					close(l->fd);
+				l->state = LI_INIT;
+			}
+		}
+
 		if (p->state != PR_STSTOPPED) {
 			Warning("Stopping %s %s in %d ms.\n", proxy_cap_str(p->cap), p->id, p->grace);
 			send_log(p, LOG_WARNING, "Stopping %s %s in %d ms.\n", proxy_cap_str(p->cap), p->id, p->grace);
@@ -1050,6 +1063,45 @@ int pause_proxy(struct proxy *p)
 	return 1;
 }
 
+/* This function makes the proxy unusable, but keeps the listening sockets
+ * opened, so that if any process requests them, we are able to serve them.
+ * This should only be called early, before we started accepting requests.
+ */
+void zombify_proxy(struct proxy *p)
+{
+	struct listener *l;
+	struct listener *first_to_listen = NULL;
+
+	list_for_each_entry(l, &p->conf.listeners, by_fe) {
+		enum li_state oldstate = l->state;
+
+		unbind_listener_no_close(l);
+		if (l->state >= LI_ASSIGNED) {
+			delete_listener(l);
+			listeners--;
+			jobs--;
+		}
+		/*
+		 * Pretend we're still up and running so that the fd
+		 * will be sent if asked.
+		 */
+		l->state = LI_ZOMBIE;
+		if (!first_to_listen && oldstate >= LI_LISTEN)
+			first_to_listen = l;
+	}
+	/* Quick hack : at stop time, to know we have to close the sockets
+	 * despite the proxy being marked as stopped, make the first listener
+	 * of the listener list an active one, so that we don't have to
+	 * parse the whole list to be sure.
+	 */
+	if (first_to_listen && LIST_ELEM(p->conf.listeners.n,
+	    struct listener *, by_fe) != first_to_listen) {
+		LIST_DEL(&l->by_fe);
+		LIST_ADD(&p->conf.listeners, &l->by_fe);
+	}
+
+	p->state = PR_STSTOPPED;
+}
 
 /*
  * This function completely stops a proxy and releases its listeners. It has
-- 
2.14.1

From 8ed3e013fd59c06838d5862f2e12b8a52b197e74 Mon Sep 17 00:00:00 2001
From: Olivier Houchard <ohouch...@haproxy.com>
Date: Wed, 5 Apr 2017 22:39:56 +0200
Subject: [PATCH 03/10] MINOR: tcp: When binding socket, attempt to reuse one
 from the old proc.

Try to reuse any socket from the old process, provided by the "-x" flag,
before binding a new one, assuming it is compatible.
"Compatible" here means same address and port, same namspace if any,
same interface if any, and that the following flags are the same :
LI_O_FOREIGN, LI_O_V6ONLY and LI_O_V4V6.
Also change tcp_bind_listener() to always enable/disable socket options,
instead of just doing so if it is in the configuration file, as the option
may have been removed, ie TCP_FASTOPEN may have been set in the old process,
and removed from the new configuration, so we have to disable it.

(cherry picked from commit 153659f1ae69a1741109fcb95cac2c7d64f99a29)
Signed-off-by: Dave Chiluk <dchi...@indeed.com>
---
 src/proto_tcp.c | 169 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 167 insertions(+), 2 deletions(-)

diff --git a/src/proto_tcp.c b/src/proto_tcp.c
index a2bb9d7..72a60aa 100644
--- a/src/proto_tcp.c
+++ b/src/proto_tcp.c
@@ -110,6 +110,12 @@ static struct protocol proto_tcpv6 = {
 	.nb_listeners = 0,
 };
 
+/* Default TCP parameters, got by opening a temporary TCP socket. */
+#ifdef TCP_MAXSEG
+static int default_tcp_maxseg = -1;
+static int default_tcp6_maxseg = -1;
+#endif
+
 /* Binds ipv4/ipv6 address <local> to socket <fd>, unless <flags> is set, in which
  * case we try to bind <remote>. <flags> is a 2-bit field consisting of :
  *  - 0 : ignore remote address (may even be a NULL pointer)
@@ -731,6 +737,83 @@ int tcp_connect_probe(struct connection *conn)
 	return 0;
 }
 
+/* XXX: Should probably be elsewhere */
+static int compare_sockaddr(struct sockaddr_storage *a, struct sockaddr_storage *b)
+{
+	if (a->ss_family != b->ss_family) {
+		return (-1);
+	}
+	switch (a->ss_family) {
+	case AF_INET:
+		{
+			struct sockaddr_in *a4 = (void *)a, *b4 = (void *)b;
+			if (a4->sin_port != b4->sin_port)
+				return (-1);
+			return (memcmp(&a4->sin_addr, &b4->sin_addr,
+			    sizeof(a4->sin_addr)));
+		}
+	case AF_INET6:
+		{
+			struct sockaddr_in6 *a6 = (void *)a, *b6 = (void *)b;
+			if (a6->sin6_port != b6->sin6_port)
+				return (-1);
+			return (memcmp(&a6->sin6_addr, &b6->sin6_addr,
+			    sizeof(a6->sin6_addr)));
+		}
+	default:
+		return (-1);
+	}
+
+}
+
+#define LI_MANDATORY_FLAGS	(LI_O_FOREIGN | LI_O_V6ONLY | LI_O_V4V6)
+/* When binding the listeners, check if a socket has been sent to us by the
+ * previous process that we could reuse, instead of creating a new one.
+ */
+static int tcp_find_compatible_fd(struct listener *l)
+{
+	struct xfer_sock_list *xfer_sock = xfer_sock_list;
+	int ret = -1;
+
+	while (xfer_sock) {
+		if (!compare_sockaddr(&xfer_sock->addr, &l->addr)) {
+			if ((l->interface == NULL && xfer_sock->iface == NULL) ||
+			    (l->interface != NULL && xfer_sock->iface != NULL &&
+			     !strcmp(l->interface, xfer_sock->iface))) {
+				if ((l->options & LI_MANDATORY_FLAGS) ==
+				    (xfer_sock->options & LI_MANDATORY_FLAGS)) {
+					if ((xfer_sock->namespace == NULL &&
+					    l->netns == NULL)
+#ifdef CONFIG_HAP_NS
+					    || (xfer_sock->namespace != NULL &&
+					    l->netns != NULL &&
+					    !strcmp(xfer_sock->namespace,
+					    l->netns->node.key))
+#endif
+					   ) {
+						break;
+					}
+
+				}
+			}
+		}
+		xfer_sock = xfer_sock->next;
+	}
+	if (xfer_sock != NULL) {
+		ret = xfer_sock->fd;
+		if (xfer_sock == xfer_sock_list)
+			xfer_sock_list = xfer_sock->next;
+		if (xfer_sock->prev)
+			xfer_sock->prev->next = xfer_sock->next;
+		if (xfer_sock->next)
+			xfer_sock->next->prev = xfer_sock->prev;
+		free(xfer_sock->iface);
+		free(xfer_sock->namespace);
+		free(xfer_sock);
+	}
+	return ret;
+}
+#undef L1_MANDATORY_FLAGS
 
 /* This function tries to bind a TCPv4/v6 listener. It may return a warning or
  * an error message in <errmsg> if the message is at most <errlen> bytes long
@@ -752,6 +835,36 @@ int tcp_bind_listener(struct listener *listener, char *errmsg, int errlen)
 	int ext, ready;
 	socklen_t ready_len;
 	const char *msg = NULL;
+#ifdef TCP_MAXSEG
+
+	/* Create a temporary TCP socket to get default parameters we can't
+	 * guess.
+	 * */
+	ready_len = sizeof(default_tcp_maxseg);
+	if (default_tcp_maxseg == -1) {
+		default_tcp_maxseg = -2;
+		fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+		if (fd < 0)
+			Warning("Failed to create a temporary socket!\n");
+		else {
+			if (getsockopt(fd, IPPROTO_TCP, TCP_MAXSEG, &default_tcp_maxseg,
+			    &ready_len) == -1)
+				Warning("Failed to get the default value of TCP_MAXSEG\n");
+		}
+		close(fd);
+	}
+	if (default_tcp6_maxseg == -1) {
+		default_tcp6_maxseg = -2;
+		fd = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
+		if (fd >= 0) {
+			if (getsockopt(fd, IPPROTO_TCP, TCP_MAXSEG, &default_tcp6_maxseg,
+			    &ready_len) == -1)
+				Warning("Failed ot get the default value of TCP_MAXSEG for IPv6\n");
+			close(fd);
+		}
+	}
+#endif
+
 
 	/* ensure we never return garbage */
 	if (errlen)
@@ -762,6 +875,9 @@ int tcp_bind_listener(struct listener *listener, char *errmsg, int errlen)
 
 	err = ERR_NONE;
 
+	if (listener->fd == -1)
+		listener->fd = tcp_find_compatible_fd(listener);
+
 	/* 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
@@ -800,6 +916,17 @@ int tcp_bind_listener(struct listener *listener, char *errmsg, int errlen)
 
 	if (listener->options & LI_O_NOLINGER)
 		setsockopt(fd, SOL_SOCKET, SO_LINGER, &nolinger, sizeof(struct linger));
+	else {
+		struct linger tmplinger;
+		socklen_t len = sizeof(tmplinger);
+		if (getsockopt(fd, SOL_SOCKET, SO_LINGER, &tmplinger, &len) == 0 &&
+		    (tmplinger.l_onoff == 1 || tmplinger.l_linger == 0)) {
+			tmplinger.l_onoff = 0;
+			tmplinger.l_linger = 0;
+			setsockopt(fd, SOL_SOCKET, SO_LINGER, &tmplinger,
+			    sizeof(tmplinger));
+		}
+	}
 
 #ifdef SO_REUSEPORT
 	/* OpenBSD and Linux 3.9 support this. As it's present in old libc versions of
@@ -869,6 +996,23 @@ int tcp_bind_listener(struct listener *listener, char *errmsg, int errlen)
 			msg = "cannot set MSS";
 			err |= ERR_WARN;
 		}
+	} else if (ext) {
+		int tmpmaxseg = -1;
+		int defaultmss;
+		socklen_t len = sizeof(tmpmaxseg);
+
+		if (listener->addr.ss_family == AF_INET)
+			defaultmss = default_tcp_maxseg;
+		else
+			defaultmss = default_tcp6_maxseg;
+
+		getsockopt(fd, IPPROTO_TCP, TCP_MAXSEG, &tmpmaxseg, &len);
+		if (tmpmaxseg != defaultmss && setsockopt(fd, IPPROTO_TCP,
+						TCP_MAXSEG, &defaultmss,
+						sizeof(defaultmss)) == -1) {
+			msg = "cannot set MSS";
+			err |= ERR_WARN;
+		}
 	}
 #endif
 #if defined(TCP_USER_TIMEOUT)
@@ -878,7 +1022,9 @@ int tcp_bind_listener(struct listener *listener, char *errmsg, int errlen)
 			msg = "cannot set TCP User Timeout";
 			err |= ERR_WARN;
 		}
-	}
+	} else
+		setsockopt(fd, IPPROTO_TCP, TCP_USER_TIMEOUT, &zero,
+		    sizeof(zero));
 #endif
 #if defined(TCP_DEFER_ACCEPT)
 	if (listener->options & LI_O_DEF_ACCEPT) {
@@ -888,7 +1034,9 @@ int tcp_bind_listener(struct listener *listener, char *errmsg, int errlen)
 			msg = "cannot enable DEFER_ACCEPT";
 			err |= ERR_WARN;
 		}
-	}
+	} else
+		setsockopt(fd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &zero,
+		    sizeof(zero));
 #endif
 #if defined(TCP_FASTOPEN)
 	if (listener->options & LI_O_TCP_FO) {
@@ -898,6 +1046,21 @@ int tcp_bind_listener(struct listener *listener, char *errmsg, int errlen)
 			msg = "cannot enable TCP_FASTOPEN";
 			err |= ERR_WARN;
 		}
+	} else {
+		socklen_t len;
+		int qlen;
+		len = sizeof(qlen);
+		/* Only disable fast open if it was enabled, we don't want
+		 * the kernel to create a fast open queue if there's none.
+		 */
+		if (getsockopt(fd, IPPROTO_TCP, TCP_FASTOPEN, &qlen, &len) == 0 &&
+		    qlen != 0) {
+			if (setsockopt(fd, IPPROTO_TCP, TCP_FASTOPEN, &zero,
+			    sizeof(zero)) == -1) {
+				msg = "cannot disable TCP_FASTOPEN";
+				err |= ERR_WARN;
+			}
+		}
 	}
 #endif
 #if defined(IPV6_V6ONLY)
@@ -928,6 +1091,8 @@ int tcp_bind_listener(struct listener *listener, char *errmsg, int errlen)
 #if defined(TCP_QUICKACK)
 	if (listener->options & LI_O_NOQUICKACK)
 		setsockopt(fd, IPPROTO_TCP, TCP_QUICKACK, &zero, sizeof(zero));
+	else
+		setsockopt(fd, IPPROTO_TCP, TCP_QUICKACK, &one, sizeof(one));
 #endif
 
 	/* the socket is ready */
-- 
2.14.1

From a6f8369b19e48fa409d3747f4e3ad9af6f78d174 Mon Sep 17 00:00:00 2001
From: Olivier Houchard <ohouch...@haproxy.com>
Date: Wed, 5 Apr 2017 22:33:04 +0200
Subject: [PATCH 02/10] MINOR: global: Add an option to get the old listening
 sockets.

Add the "-x" flag, that takes a path to a unix socket as an argument. If
used, haproxy will connect to the socket, and asks to get all the
listening sockets from the old process. Any failure is fatal.
This is needed to get seamless reloads on linux.

(cherry picked from commit f73629d23a44995f84887859fcbc7d22a2227eec)
Signed-off-by: Dave Chiluk <dchi...@indeed.com>
---
 include/proto/listener.h |   1 +
 include/types/listener.h |  10 ++
 src/haproxy.c            | 240 ++++++++++++++++++++++++++++++++++++++++++++++-
 src/listener.c           |   2 +
 4 files changed, 252 insertions(+), 1 deletion(-)

diff --git a/include/proto/listener.h b/include/proto/listener.h
index 75bae86..1f042af 100644
--- a/include/proto/listener.h
+++ b/include/proto/listener.h
@@ -142,6 +142,7 @@ static inline struct bind_conf *bind_conf_alloc(struct list *lh, const char *fil
 	return bind_conf;
 }
 
+extern struct xfer_sock_list *xfer_sock_list;
 #endif /* _PROTO_LISTENER_H */
 
 /*
diff --git a/include/types/listener.h b/include/types/listener.h
index d06e4e7..015852b 100644
--- a/include/types/listener.h
+++ b/include/types/listener.h
@@ -227,6 +227,16 @@ struct bind_kw_list {
 };
 
 
+struct xfer_sock_list {
+	int fd;
+	char *iface;
+	char *namespace;
+	int options; /* socket options LI_O_* */
+	struct xfer_sock_list *prev;
+	struct xfer_sock_list *next;
+	struct sockaddr_storage addr;
+};
+
 #endif /* _TYPES_LISTENER_H */
 
 /*
diff --git a/src/haproxy.c b/src/haproxy.c
index 6d09aed..9dabe3f 100644
--- a/src/haproxy.c
+++ b/src/haproxy.c
@@ -39,6 +39,7 @@
 #include <netinet/tcp.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
+#include <net/if.h>
 #include <netdb.h>
 #include <fcntl.h>
 #include <errno.h>
@@ -240,6 +241,9 @@ int jobs = 0;   /* number of active jobs (conns, listeners, active tasks, ...) *
 static int *oldpids = NULL;
 static int oldpids_sig; /* use USR1 or TERM */
 
+/* Path to the unix socket we use to retrieve listener sockets from the old process */
+static const char *old_unixsocket;
+
 /* this is used to drain data, and as a temporary buffer for sprintf()... */
 struct chunk trash = { };
 
@@ -488,6 +492,7 @@ void usage(char *name)
 		"        -dr ignores server address resolution failures\n"
 		"        -dV disables SSL verify on servers side\n"
 		"        -sf/-st [pid ]* finishes/terminates old pids.\n"
+		"        -x <unix_socket> get listening sockets from a unix socket\n"
 		"\n",
 		name, DEFAULT_MAXCONN, cfg_maxpconn);
 	exit(1);
@@ -677,6 +682,214 @@ next_dir_entry:
 	free(err);
 }
 
+static int get_old_sockets(const char *unixsocket)
+{
+	char *cmsgbuf = NULL, *tmpbuf = NULL;
+	int *tmpfd = NULL;
+	struct sockaddr_un addr;
+	struct cmsghdr *cmsg;
+	struct msghdr msghdr;
+	struct iovec iov;
+	struct xfer_sock_list *xfer_sock = NULL;
+	int sock = -1;
+	int ret = -1;
+	int ret2 = -1;
+	int fd_nb;
+	int got_fd = 0;
+	int i = 0;
+	size_t maxoff = 0, curoff = 0;
+
+	memset(&msghdr, 0, sizeof(msghdr));
+	cmsgbuf = malloc(CMSG_SPACE(sizeof(int)) * MAX_SEND_FD);
+	if (!cmsgbuf) {
+		Warning("Failed to allocate memory to send sockets\n");
+		goto out;
+	}
+	sock = socket(PF_UNIX, SOCK_STREAM, 0);
+	if (sock < 0) {
+		Warning("Failed to connect to the old process socket '%s'\n",
+		    unixsocket);
+		goto out;
+	}
+	strncpy(addr.sun_path, unixsocket, sizeof(addr.sun_path));
+	addr.sun_path[sizeof(addr.sun_path) - 1] = 0;
+	addr.sun_family = PF_UNIX;
+	ret = connect(sock, (struct sockaddr *)&addr, sizeof(addr));
+	if (ret < 0) {
+		Warning("Failed to connect to the old process socket '%s'\n",
+		    unixsocket);
+		goto out;
+	}
+	iov.iov_base = &fd_nb;
+	iov.iov_len = sizeof(fd_nb);
+	msghdr.msg_iov = &iov;
+	msghdr.msg_iovlen = 1;
+	send(sock, "_getsocks\n", strlen("_getsocks\n"), 0);
+	/* First, get the number of file descriptors to be received */
+	if (recvmsg(sock, &msghdr, MSG_WAITALL) != sizeof(fd_nb)) {
+		Warning("Failed to get the number of sockets to be transferred !\n");
+		goto out;
+	}
+	if (fd_nb == 0) {
+		ret = 0;
+		goto out;
+	}
+	tmpbuf = malloc(fd_nb * (1 + NAME_MAX + 1 + IFNAMSIZ + sizeof(int)));
+	if (tmpbuf == NULL) {
+		Warning("Failed to allocate memory while receiving sockets\n");
+		goto out;
+	}
+	tmpfd = malloc(fd_nb * sizeof(int));
+	if (tmpfd == NULL) {
+		Warning("Failed to allocate memory while receiving sockets\n");
+		goto out;
+	}
+	msghdr.msg_control = cmsgbuf;
+	msghdr.msg_controllen = CMSG_SPACE(sizeof(int)) * MAX_SEND_FD;
+	iov.iov_len = MAX_SEND_FD * (1 + NAME_MAX + 1 + IFNAMSIZ + sizeof(int));
+	do {
+		int ret3;
+
+		iov.iov_base = tmpbuf + curoff;
+		ret = recvmsg(sock, &msghdr, 0);
+		if (ret == -1 && errno == EINTR)
+			continue;
+		if (ret <= 0)
+			break;
+		/* Send an ack to let the sender know we got the sockets
+		 * and it can send some more
+		 */
+		do {
+			ret3 = send(sock, &got_fd, sizeof(got_fd), 0);
+		} while (ret3 == -1 && errno == EINTR);
+		for (cmsg = CMSG_FIRSTHDR(&msghdr); cmsg != NULL;
+		    cmsg = CMSG_NXTHDR(&msghdr, cmsg)) {
+			if (cmsg->cmsg_level == SOL_SOCKET &&
+			    cmsg->cmsg_type == SCM_RIGHTS) {
+				size_t totlen = cmsg->cmsg_len -
+				    CMSG_LEN(0);
+				if (totlen / sizeof(int) + got_fd > fd_nb) {
+					Warning("Got to many sockets !\n");
+					goto out;
+				}
+				/*
+				 * Be paranoid and use memcpy() to avoid any
+				 * potential alignement issue.
+				 */
+				memcpy(&tmpfd[got_fd], CMSG_DATA(cmsg), totlen);
+				got_fd += totlen / sizeof(int);
+			}
+		}
+		curoff += ret;
+	} while (got_fd < fd_nb);
+
+	if (got_fd != fd_nb) {
+		Warning("We didn't get the expected number of sockets (expecting %d got %d)\n",
+		    fd_nb, got_fd);
+		goto out;
+	}
+	maxoff = curoff;
+	curoff = 0;
+	for (i = 0; i < got_fd; i++) {
+		int fd = tmpfd[i];
+		socklen_t socklen;
+		int len;
+
+		xfer_sock = calloc(1, sizeof(*xfer_sock));
+		if (!xfer_sock) {
+			Warning("Failed to allocate memory in get_old_sockets() !\n");
+			break;
+		}
+		xfer_sock->fd = -1;
+
+		socklen = sizeof(xfer_sock->addr);
+		if (getsockname(fd, (struct sockaddr *)&xfer_sock->addr, &socklen) != 0) {
+			Warning("Failed to get socket address\n");
+			free(xfer_sock);
+			continue;
+		}
+		if (curoff >= maxoff) {
+			Warning("Inconsistency while transferring sockets\n");
+			goto out;
+		}
+		len = tmpbuf[curoff++];
+		if (len > 0) {
+			/* We have a namespace */
+			if (curoff + len > maxoff) {
+				Warning("Inconsistency while transferring sockets\n");
+				goto out;
+			}
+			xfer_sock->namespace = malloc(len + 1);
+			if (!xfer_sock->namespace) {
+				Warning("Failed to allocate memory while transferring sockets\n");
+				goto out;
+			}
+			memcpy(xfer_sock->namespace, &tmpbuf[curoff], len);
+			xfer_sock->namespace[len] = 0;
+			curoff += len;
+		}
+		if (curoff >= maxoff) {
+			Warning("Inconsistency while transferring sockets\n");
+			goto out;
+		}
+		len = tmpbuf[curoff++];
+		if (len > 0) {
+			/* We have an interface */
+			if (curoff + len > maxoff) {
+				Warning("Inconsistency while transferring sockets\n");
+				goto out;
+			}
+			xfer_sock->iface = malloc(len + 1);
+			if (!xfer_sock->iface) {
+				Warning("Failed to allocate memory while transferring sockets\n");
+				goto out;
+			}
+			memcpy(xfer_sock->iface, &tmpbuf[curoff], len);
+			xfer_sock->namespace[len] = 0;
+			curoff += len;
+		}
+		if (curoff + sizeof(int) > maxoff) {
+			Warning("Inconsistency while transferring sockets\n");
+			goto out;
+		}
+		memcpy(&xfer_sock->options, &tmpbuf[curoff],
+		    sizeof(xfer_sock->options));
+		curoff += sizeof(xfer_sock->options);
+
+		xfer_sock->fd = fd;
+		if (xfer_sock_list)
+			xfer_sock_list->prev = xfer_sock;
+		xfer_sock->next = xfer_sock_list;
+		xfer_sock->prev = NULL;
+		xfer_sock_list = xfer_sock;
+		xfer_sock = NULL;
+	}
+
+	ret2 = 0;
+out:
+	/* If we failed midway make sure to close the remaining
+	 * file descriptors
+	 */
+	if (tmpfd != NULL && i < got_fd) {
+		for (; i < got_fd; i++) {
+			close(tmpfd[i]);
+		}
+	}
+	free(tmpbuf);
+	free(tmpfd);
+	free(cmsgbuf);
+	if (sock != -1)
+		close(sock);
+	if (xfer_sock) {
+		free(xfer_sock->namespace);
+		free(xfer_sock->iface);
+		if (xfer_sock->fd != -1)
+			close(xfer_sock->fd);
+		free(xfer_sock);
+	}
+	return (ret2);
+}
+
 /*
  * This function initializes all the necessary variables. It only returns
  * if everything is OK. If something fails, it exits.
@@ -828,6 +1041,15 @@ void init(int argc, char **argv)
 			}
 			else if (*flag == 'q')
 				arg_mode |= MODE_QUIET;
+			else if (*flag == 'x') {
+				if (argv[1][0] == '-') {
+					Alert("Unix socket path expected with the -x flag\n");
+					exit(1);
+				}
+				old_unixsocket = argv[1];
+				argv++;
+				argc--;
+			}
 			else if (*flag == 's' && (flag[1] == 'f' || flag[1] == 't')) {
 				/* list of pids to finish ('f') or terminate ('t') */
 
@@ -835,7 +1057,6 @@ void init(int argc, char **argv)
 					oldpids_sig = SIGUSR1; /* finish then exit */
 				else
 					oldpids_sig = SIGTERM; /* terminate immediately */
-
 				while (argc > 1 && argv[1][0] != '-') {
 					oldpids = realloc(oldpids, (nb_oldpids + 1) * sizeof(int));
 					if (!oldpids) {
@@ -1837,6 +2058,12 @@ int main(int argc, char **argv)
 #endif
 	}
 
+	if (old_unixsocket) {
+		if (get_old_sockets(old_unixsocket) != 0) {
+			Alert("Failed to get the sockets from the old process!\n");
+			exit(1);
+		}
+	}
 	/* We will loop at most 100 times with 10 ms delay each time.
 	 * That's at most 1 second. We only send a signal to old pids
 	 * if we cannot grab at least one port.
@@ -1897,6 +2124,17 @@ int main(int argc, char **argv)
 	} else if (err & ERR_WARN) {
 		Alert("[%s.main()] %s.\n", argv[0], errmsg);
 	}
+	/* Ok, all listener should now be bound, close any leftover sockets
+	 * the previous process gave us, we don't need them anymore
+	 */
+	while (xfer_sock_list != NULL) {
+		struct xfer_sock_list *tmpxfer = xfer_sock_list->next;
+		close(xfer_sock_list->fd);
+		free(xfer_sock_list->iface);
+		free(xfer_sock_list->namespace);
+		free(xfer_sock_list);
+		xfer_sock_list = tmpxfer;
+	}
 
 	/* prepare pause/play signals */
 	signal_register_fct(SIGTTOU, sig_pause, SIGTTOU);
diff --git a/src/listener.c b/src/listener.c
index 3c043ba..2c9359f 100644
--- a/src/listener.c
+++ b/src/listener.c
@@ -42,6 +42,8 @@ static struct bind_kw_list bind_keywords = {
 	.list = LIST_HEAD_INIT(bind_keywords.list)
 };
 
+struct xfer_sock_list *xfer_sock_list = NULL;
+
 /* This function adds the specified listener's file descriptor to the polling
  * lists if it is in the LI_LISTEN state. The listener enters LI_READY or
  * LI_FULL state depending on its number of connections. In deamon mode, we
-- 
2.14.1

From 39809571a41a238026568b70bd28fc1b80a6a70d Mon Sep 17 00:00:00 2001
From: Olivier Houchard <ohouch...@haproxy.com>
Date: Wed, 5 Apr 2017 22:24:59 +0200
Subject: [PATCH 01/10] MINOR: cli: Add a command to send listening sockets.

Add a new command that will send all the listening sockets, via the
stats socket, and their properties.
This is a first step to workaround the linux problem when reloading
haproxy.

(cherry picked from commit f886e3478def3afe645b086243a919246a4102a4)
Signed-off-by: Dave Chiluk <dchi...@indeed.com>
---
 include/types/connection.h |   8 ++
 src/cli.c                  | 181 +++++++++++++++++++++++++++++++++++++++++++++
 src/proto_uxst.c           |  50 +++++++++++++
 3 files changed, 239 insertions(+)

diff --git a/include/types/connection.h b/include/types/connection.h
index 02eac93..470941e 100644
--- a/include/types/connection.h
+++ b/include/types/connection.h
@@ -377,6 +377,14 @@ struct tlv_ssl {
 #define PP2_CLIENT_CERT_CONN     0x02
 #define PP2_CLIENT_CERT_SESS     0x04
 
+
+/*
+ * Linux seems to be able to send 253 fds per sendmsg(), not sure
+ * about the other OSes.
+ */
+/* Max number of file descriptors we send in one sendmsg() */
+#define MAX_SEND_FD 253
+
 #endif /* _TYPES_CONNECTION_H */
 
 /*
diff --git a/src/cli.c b/src/cli.c
index 4d0eb4a..5fbf1e4 100644
--- a/src/cli.c
+++ b/src/cli.c
@@ -24,6 +24,8 @@
 #include <sys/stat.h>
 #include <sys/types.h>
 
+#include <net/if.h>
+
 #include <common/cfgparse.h>
 #include <common/compat.h>
 #include <common/config.h>
@@ -907,6 +909,184 @@ static int bind_parse_level(char **args, int cur_arg, struct proxy *px, struct b
 	return 0;
 }
 
+/* Send all the bound sockets, always returns 1 */
+static int _getsocks(char **args, struct appctx *appctx, void *private)
+{
+	char *cmsgbuf = NULL;
+	unsigned char *tmpbuf = NULL;
+	struct cmsghdr *cmsg;
+	struct stream_interface *si = appctx->owner;
+	struct connection *remote = objt_conn(si_opposite(si)->end);
+	struct msghdr msghdr;
+	struct iovec iov;
+	int *tmpfd;
+	int tot_fd_nb = 0;
+	struct proxy *px;
+	int i = 0;
+	int fd = remote->t.sock.fd;
+	int curoff = 0;
+	int old_fcntl;
+	int ret;
+
+	/* Temporary set the FD in blocking mode, that will make our life easier */
+	old_fcntl = fcntl(fd, F_GETFL);
+	if (old_fcntl < 0) {
+		Warning("Couldn't get the flags for the unix socket\n");
+		goto out;
+	}
+	cmsgbuf = malloc(CMSG_SPACE(sizeof(int) * MAX_SEND_FD));
+	if (!cmsgbuf) {
+		Warning("Failed to allocate memory to send sockets\n");
+		goto out;
+	}
+	if (fcntl(fd, F_SETFL, old_fcntl &~ O_NONBLOCK) == -1) {
+		Warning("Cannot make the unix socket blocking\n");
+		goto out;
+	}
+	iov.iov_base = &tot_fd_nb;
+	iov.iov_len = sizeof(tot_fd_nb);
+	if (!cli_has_level(appctx, ACCESS_LVL_ADMIN))
+		goto out;
+	memset(&msghdr, 0, sizeof(msghdr));
+	/*
+	 * First, calculates the total number of FD, so that we can let
+	 * the caller know how much he should expects.
+	 */
+	px = proxy;
+	while (px) {
+		struct listener *l;
+
+		list_for_each_entry(l, &px->conf.listeners, by_fe) {
+			/* Only transfer IPv4/IPv6 sockets */
+			if (l->proto->sock_family == AF_INET ||
+			    l->proto->sock_family == AF_INET6 ||
+			    l->proto->sock_family == AF_UNIX)
+				tot_fd_nb++;
+		}
+		px = px->next;
+	}
+	if (tot_fd_nb == 0)
+		goto out;
+
+	/* First send the total number of file descriptors, so that the
+	 * receiving end knows what to expect.
+	 */
+	msghdr.msg_iov = &iov;
+	msghdr.msg_iovlen = 1;
+	ret = sendmsg(fd, &msghdr, 0);
+	if (ret != sizeof(tot_fd_nb)) {
+		Warning("Failed to send the number of sockets to send\n");
+		goto out;
+	}
+
+	/* Now send the fds */
+	msghdr.msg_control = cmsgbuf;
+	msghdr.msg_controllen = CMSG_SPACE(sizeof(int) * MAX_SEND_FD);
+	cmsg = CMSG_FIRSTHDR(&msghdr);
+	cmsg->cmsg_len = CMSG_LEN(MAX_SEND_FD * sizeof(int));
+	cmsg->cmsg_level = SOL_SOCKET;
+	cmsg->cmsg_type = SCM_RIGHTS;
+	tmpfd = (int *)CMSG_DATA(cmsg);
+
+	px = proxy;
+	/* For each socket, e message is sent, containing the following :
+	 *  Size of the namespace name (or 0 if none), as an unsigned char.
+	 *  The namespace name, if any
+	 *  Size of the interface name (or 0 if none), as an unsigned char
+	 *  The interface name, if any
+	 *  Listener options, as an int.
+	 */
+	/* We will send sockets MAX_SEND_FD per MAX_SEND_FD, allocate a
+	 * buffer big enough to store the socket informations.
+	 */
+	tmpbuf = malloc(MAX_SEND_FD * (1 + NAME_MAX + 1 + IFNAMSIZ + sizeof(int)));
+	if (tmpbuf == NULL) {
+		Warning("Failed to allocate memory to transfer socket informations\n");
+		goto out;
+	}
+	iov.iov_base = tmpbuf;
+	while (px) {
+		struct listener *l;
+
+		list_for_each_entry(l, &px->conf.listeners, by_fe) {
+			int ret;
+			/* Only transfer IPv4/IPv6 sockets */
+			if (l->state >= LI_LISTEN &&
+			    (l->proto->sock_family == AF_INET ||
+			    l->proto->sock_family == AF_INET6 ||
+			    l->proto->sock_family == AF_UNIX)) {
+				memcpy(&tmpfd[i % MAX_SEND_FD], &l->fd, sizeof(l->fd));
+				if (!l->netns)
+					tmpbuf[curoff++] = 0;
+#ifdef CONFIG_HAP_NS
+				else {
+					char *name = l->netns->node.key;
+					unsigned char len = l->netns->name_len;
+					tmpbuf[curoff++] = len;
+					memcpy(tmpbuf + curoff, name, len);
+					curoff += len;
+				}
+#endif
+				if (l->interface) {
+					unsigned char len = strlen(l->interface);
+					tmpbuf[curoff++] = len;
+					memcpy(tmpbuf + curoff, l->interface, len);
+				curoff += len;
+				} else
+					tmpbuf[curoff++] = 0;
+				memcpy(tmpbuf + curoff, &l->options,
+				    sizeof(l->options));
+				curoff += sizeof(l->options);
+
+
+				i++;
+			} else
+				continue;
+			if ((!(i % MAX_SEND_FD))) {
+				iov.iov_len = curoff;
+				if (sendmsg(fd, &msghdr, 0) != curoff) {
+					Warning("Failed to transfer sockets\n");
+					printf("errno %d\n", errno);
+					goto out;
+				}
+				/* Wait for an ack */
+				do {
+					ret = recv(fd, &tot_fd_nb,
+					    sizeof(tot_fd_nb), 0);
+				} while (ret == -1 && errno == EINTR);
+				if (ret <= 0) {
+					Warning("Unexpected error while transferring sockets\n");
+					goto out;
+				}
+				curoff = 0;
+			}
+
+		}
+		px = px->next;
+	}
+	if (i % MAX_SEND_FD) {
+		iov.iov_len = curoff;
+		cmsg->cmsg_len = CMSG_LEN((i % MAX_SEND_FD) * sizeof(int));
+		msghdr.msg_controllen = CMSG_SPACE(sizeof(int) *  (i % MAX_SEND_FD));
+		if (sendmsg(fd, &msghdr, 0) != curoff) {
+			Warning("Failed to transfer sockets\n");
+			goto out;
+		}
+	}
+
+out:
+	if (old_fcntl >= 0 && fcntl(fd, F_SETFL, old_fcntl) == -1) {
+		Warning("Cannot make the unix socket non-blocking\n");
+		goto out;
+	}
+	appctx->st0 = CLI_ST_END;
+	free(cmsgbuf);
+	free(tmpbuf);
+	return 1;
+}
+
+
+
 static struct applet cli_applet = {
 	.obj_type = OBJ_TYPE_APPLET,
 	.name = "<CLI>", /* used for logging */
@@ -920,6 +1100,7 @@ static struct cli_kw_list cli_kws = {{ },{
 	{ { "set", "rate-limit", NULL }, "set rate-limit : change a rate limiting value", cli_parse_set_ratelimit, NULL },
 	{ { "set", "timeout",  NULL }, "set timeout    : change a timeout setting", cli_parse_set_timeout, NULL, NULL },
 	{ { "show", "env",  NULL }, "show env [var] : dump environment variables known to the process", cli_parse_show_env, cli_io_handler_show_env, NULL },
+	{ { "_getsocks", NULL }, NULL,  _getsocks, NULL },
 	{{},}
 }};
 
diff --git a/src/proto_uxst.c b/src/proto_uxst.c
index 429ee4e..5aa45a7 100644
--- a/src/proto_uxst.c
+++ b/src/proto_uxst.c
@@ -112,6 +112,54 @@ int uxst_get_dst(int fd, struct sockaddr *sa, socklen_t salen, int dir)
  ********************************/
 
 
+static int uxst_find_compatible_fd(struct listener *l)
+{
+	struct xfer_sock_list *xfer_sock = xfer_sock_list;
+	int ret = -1;
+
+	while (xfer_sock) {
+		struct sockaddr_un *un1 = (void *)&l->addr;
+		struct sockaddr_un *un2 = (void *)&xfer_sock->addr;
+
+		/*
+		 * The bound socket's path as returned by getsockaddr
+		 * will be the temporary name <sockname>.XXXXX.tmp,
+		 * so we can't just compare the two names
+		 */
+		if (xfer_sock->addr.ss_family == AF_UNIX &&
+		    strncmp(un1->sun_path, un2->sun_path,
+		    strlen(un1->sun_path)) == 0) {
+			char *after_sockname = un2->sun_path +
+			    strlen(un1->sun_path);
+			/* Make a reasonnable effort to check that
+			 * it is indeed a haproxy-generated temporary
+			 * name, it's not perfect, but probably good enough.
+			 */
+			if (after_sockname[0] == '.') {
+				after_sockname++;
+				while (after_sockname[0] >= '0' &&
+				    after_sockname[0] <= '9')
+					after_sockname++;
+				if (!strcmp(after_sockname, ".tmp"))
+					break;
+			}
+		}
+		xfer_sock = xfer_sock->next;
+	}
+	if (xfer_sock != NULL) {
+		ret = xfer_sock->fd;
+		if (xfer_sock == xfer_sock_list)
+			xfer_sock_list = xfer_sock->next;
+		if (xfer_sock->prev)
+			xfer_sock->prev->next = xfer_sock->next;
+		if (xfer_sock->next)
+			xfer_sock->next->prev = xfer_sock->next->prev;
+		free(xfer_sock);
+	}
+	return ret;
+
+}
+
 /* This function creates a UNIX socket associated to the listener. It changes
  * the state from ASSIGNED to LISTEN. The socket is NOT enabled for polling.
  * The return value is composed from ERR_NONE, ERR_RETRYABLE and ERR_FATAL. It
@@ -141,6 +189,8 @@ static int uxst_bind_listener(struct listener *listener, char *errmsg, int errle
 	if (listener->state != LI_ASSIGNED)
 		return ERR_NONE; /* already bound */
 		
+	if (listener->fd == -1)
+		listener->fd = uxst_find_compatible_fd(listener);
 	path = ((struct sockaddr_un *)&listener->addr)->sun_path;
 
 	/* if the listener already has an fd assigned, then we were offered the
-- 
2.14.1

Reply via email to