MEDIUM: listeners: split the thread mask between receiver and bind_conf

With groups at some point we'll have to have distinct masks/groups in the
receiver and the bind_conf, because a single bind_conf might require to
instantiate multiple receivers (one per group).

Let's split the thread mask and group to have one for the bind_conf and
another one for the receiver while it remains easy to do. This will later
allow to use different storage for the bind_conf if needed (e.g. support
multiple groups).
diff --git a/include/haproxy/listener-t.h b/include/haproxy/listener-t.h
index 66cabee..fa1042e 100644
--- a/include/haproxy/listener-t.h
+++ b/include/haproxy/listener-t.h
@@ -185,6 +185,8 @@
 	char *file;                /* file where the section appears */
 	int line;                  /* line where the section appears */
 	__decl_thread(HA_RWLOCK_T sni_lock); /* lock the SNI trees during add/del operations */
+	unsigned long bind_thread; /* bitmask of threads allowed on this bind_conf */
+	uint bind_tgroup;          /* thread group ID: 0=global IDs, non-zero=local IDs */
 	struct rx_settings settings; /* all the settings needed for the listening socket */
 };
 
diff --git a/include/haproxy/receiver-t.h b/include/haproxy/receiver-t.h
index 9f14af3..481ac64 100644
--- a/include/haproxy/receiver-t.h
+++ b/include/haproxy/receiver-t.h
@@ -41,8 +41,6 @@
 
 /* All the settings that are used to configure a receiver */
 struct rx_settings {
-	unsigned long bind_thread;        /* bitmask of threads allowed to use these listeners */
-	uint bind_tgroup;                 /* thread group ID: 0=global IDs, non-zero=local IDs */
 	struct {                          /* UNIX socket permissions */
 		uid_t uid;                /* -1 to leave unchanged */
 		gid_t gid;                /* -1 to leave unchanged */
@@ -60,6 +58,8 @@
 	struct protocol *proto;          /* protocol this receiver belongs to */
 	void *owner;                     /* receiver's owner (usually a listener) */
 	void (*iocb)(int fd);            /* generic I/O handler (typically accept callback) */
+	unsigned long bind_thread;       /* bitmask of threads allowed on this receiver */
+	uint bind_tgroup;                /* thread group ID: 0=global IDs, non-zero=local IDs */
 	struct rx_settings *settings;    /* points to the settings used by this receiver */
 	struct list proto_list;          /* list in the protocol header */
 #ifdef USE_QUIC
diff --git a/src/cfgparse.c b/src/cfgparse.c
index 09154f4..ada344d 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -2517,6 +2517,7 @@
 		/* check and reduce the bind-proc of each listener */
 		list_for_each_entry(bind_conf, &curproxy->conf.bind, by_fe) {
 			unsigned long mask;
+			struct listener *li;
 
 			/* HTTP frontends with "h2" as ALPN/NPN will work in
 			 * HTTP/2 and absolutely require buffers 16kB or larger.
@@ -2544,13 +2545,13 @@
 
 			/* detect and address thread affinity inconsistencies */
 			err = NULL;
-			if (thread_resolve_group_mask(bind_conf->settings.bind_tgroup, bind_conf->settings.bind_thread,
-			                              &bind_conf->settings.bind_tgroup, &bind_conf->settings.bind_thread, &err) < 0) {
+			if (thread_resolve_group_mask(bind_conf->bind_tgroup, bind_conf->bind_thread,
+			                              &bind_conf->bind_tgroup, &bind_conf->bind_thread, &err) < 0) {
 				ha_alert("Proxy '%s': %s in 'bind %s' at [%s:%d].\n",
 					   curproxy->id, err, bind_conf->arg, bind_conf->file, bind_conf->line);
 				free(err);
 				cfgerr++;
-			} else if (!((mask = bind_conf->settings.bind_thread) & all_threads_mask)) {
+			} else if (!((mask = bind_conf->bind_thread) & all_threads_mask)) {
 				unsigned long new_mask = 0;
 
 				while (mask) {
@@ -2558,10 +2559,16 @@
 					mask >>= global.nbthread;
 				}
 
-				bind_conf->settings.bind_thread = new_mask;
+				bind_conf->bind_thread = new_mask;
 				ha_warning("Proxy '%s': the thread range specified on the 'thread' directive of 'bind %s' at [%s:%d] only refers to thread numbers out of the range defined by the global 'nbthread' directive. The thread numbers were remapped to existing threads instead (mask 0x%lx).\n",
 					   curproxy->id, bind_conf->arg, bind_conf->file, bind_conf->line, new_mask);
 			}
+
+			/* apply thread masks and groups to all receivers */
+			list_for_each_entry(li, &bind_conf->listeners, by_bind) {
+				li->rx.bind_thread = bind_conf->bind_thread;
+				li->rx.bind_tgroup = bind_conf->bind_tgroup;
+			}
 		}
 
 		switch (curproxy->mode) {
diff --git a/src/listener.c b/src/listener.c
index b89aa98..112455a 100644
--- a/src/listener.c
+++ b/src/listener.c
@@ -906,7 +906,7 @@
 
 
 #if defined(USE_THREAD)
-		mask = thread_mask(l->rx.settings->bind_thread) & all_threads_mask;
+		mask = thread_mask(l->rx.bind_thread) & all_threads_mask;
 		if (atleast2(mask) && (global.tune.options & GTUNE_LISTENER_MQ) && !stopping) {
 			struct accept_queue_ring *ring;
 			unsigned int t, t0, t1, t2;
@@ -1563,7 +1563,7 @@
 		*slash = '/';
 	}
 
-	conf->settings.bind_thread |= thread;
+	conf->bind_thread |= thread;
 
 	memprintf(err, "'process %s' on 'bind' lines is deprecated and will be removed in 2.7.", args[cur_arg+1]);
 	if (slash)
@@ -1612,8 +1612,8 @@
 		sep = args[cur_arg + 1];
 	}
 
-	if ((conf->settings.bind_tgroup || conf->settings.bind_thread) &&
-	    conf->settings.bind_tgroup != tgroup) {
+	if ((conf->bind_tgroup || conf->bind_thread) &&
+	    conf->bind_tgroup != tgroup) {
 		memprintf(err, "'%s' multiple thread-groups are not supported", args[cur_arg + 1]);
 		return ERR_ALERT | ERR_FATAL;
 	}
@@ -1623,8 +1623,8 @@
 		return ERR_ALERT | ERR_FATAL;
 	}
 
-	conf->settings.bind_thread |= thread;
-	conf->settings.bind_tgroup  = tgroup;
+	conf->bind_thread |= thread;
+	conf->bind_tgroup  = tgroup;
 	return 0;
 }
 
diff --git a/src/proto_sockpair.c b/src/proto_sockpair.c
index 11cf353..7d4f7ee 100644
--- a/src/proto_sockpair.c
+++ b/src/proto_sockpair.c
@@ -157,7 +157,7 @@
 
 	rx->flags |= RX_F_BOUND;
 
-	fd_insert(rx->fd, rx->owner, rx->iocb, thread_mask(rx->settings->bind_thread) & all_threads_mask);
+	fd_insert(rx->fd, rx->owner, rx->iocb, thread_mask(rx->bind_thread) & all_threads_mask);
 	return err;
 
  bind_return:
diff --git a/src/sock_inet.c b/src/sock_inet.c
index fd8f648..fa04dfe 100644
--- a/src/sock_inet.c
+++ b/src/sock_inet.c
@@ -391,7 +391,7 @@
 	rx->fd = fd;
 	rx->flags |= RX_F_BOUND;
 
-	fd_insert(fd, rx->owner, rx->iocb, thread_mask(rx->settings->bind_thread) & all_threads_mask);
+	fd_insert(fd, rx->owner, rx->iocb, thread_mask(rx->bind_thread) & all_threads_mask);
 
 	/* for now, all regularly bound TCP listeners are exportable */
 	if (!(rx->flags & RX_F_INHERITED))
diff --git a/src/sock_unix.c b/src/sock_unix.c
index 9913f4f..45ba89f 100644
--- a/src/sock_unix.c
+++ b/src/sock_unix.c
@@ -285,7 +285,7 @@
 	rx->fd = fd;
 	rx->flags |= RX_F_BOUND;
 
-	fd_insert(fd, rx->owner, rx->iocb, thread_mask(rx->settings->bind_thread) & all_threads_mask);
+	fd_insert(fd, rx->owner, rx->iocb, thread_mask(rx->bind_thread) & all_threads_mask);
 
 	/* for now, all regularly bound TCP listeners are exportable */
 	if (!(rx->flags & RX_F_INHERITED))