summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorSergey Kandaurov <pluknet@nginx.com>2025-10-16 15:22:56 +0000
committerSergey Kandaurov <s.kandaurov@f5.com>2026-03-19 01:13:51 +0400
commit920dc099c130e0ea23eb36becd157a95901aa5a2 (patch)
tree087aa0949e69c5196c325baa49f38603e7a8a1fc
parenta29476464cc86092135401bdcad91e4d38ac6b6d (diff)
downloadnginx-920dc099c130e0ea23eb36becd157a95901aa5a2.tar.gz
nginx-920dc099c130e0ea23eb36becd157a95901aa5a2.tar.bz2
The "multipath" parameter of the "listen" directive.
When configured, it enables Multipath TCP support on a listen socket. As of now it works on Linux starting with Linux 5.6 and glibc 2.32, where it is enabled with an IPPROTO_MPTCP socket(2) protocol. To avoid EADDRINUSE errors in bind() and listen() when transitioning between sockets with different protocols, SO_REUSEPORT is set on both sockets. See f7f1607bf for potential implications. Based on previous work by Maxime Dourov and Anthony Doeraene.
-rw-r--r--contrib/vim/syntax/nginx.vim2
-rw-r--r--src/core/ngx_connection.c36
-rw-r--r--src/core/ngx_connection.h3
-rw-r--r--src/core/ngx_cycle.c18
-rw-r--r--src/http/ngx_http.c1
-rw-r--r--src/http/ngx_http_core_module.c19
-rw-r--r--src/http/ngx_http_core_module.h1
-rw-r--r--src/mail/ngx_mail.c1
-rw-r--r--src/mail/ngx_mail.h1
-rw-r--r--src/mail/ngx_mail_core_module.c12
-rw-r--r--src/stream/ngx_stream.c1
-rw-r--r--src/stream/ngx_stream.h1
-rw-r--r--src/stream/ngx_stream_core_module.c19
13 files changed, 104 insertions, 11 deletions
diff --git a/contrib/vim/syntax/nginx.vim b/contrib/vim/syntax/nginx.vim
index 29eef7a23..ea7c58464 100644
--- a/contrib/vim/syntax/nginx.vim
+++ b/contrib/vim/syntax/nginx.vim
@@ -65,7 +65,7 @@ syn match ngxListenComment '#.*$'
\ contained
\ nextgroup=@ngxListenParams skipwhite skipempty
syn keyword ngxListenOptions contained
- \ default_server ssl quic proxy_protocol
+ \ default_server ssl quic proxy_protocol multipath
\ setfib fastopen backlog rcvbuf sndbuf accept_filter deferred bind
\ ipv6only reuseport so_keepalive
\ nextgroup=@ngxListenParams skipwhite skipempty
diff --git a/src/core/ngx_connection.c b/src/core/ngx_connection.c
index 7cae295eb..c269a97e6 100644
--- a/src/core/ngx_connection.c
+++ b/src/core/ngx_connection.c
@@ -315,6 +315,25 @@ ngx_set_inherited_sockets(ngx_cycle_t *cycle)
continue;
}
+#ifdef SO_PROTOCOL
+
+ olen = sizeof(int);
+
+ if (getsockopt(ls[i].fd, SOL_SOCKET, SO_PROTOCOL,
+ (void *) &ls[i].protocol, &olen)
+ == -1)
+ {
+ ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno,
+ "getsockopt(SO_PROTOCOL) %V failed, ignored",
+ &ls[i].addr_text);
+ ls[i].protocol = 0;
+
+ } else if (ls[i].protocol == IPPROTO_TCP) {
+ ls[i].protocol = 0;
+ }
+
+#endif
+
#if (NGX_HAVE_TCP_FASTOPEN)
olen = sizeof(int);
@@ -436,12 +455,16 @@ ngx_open_listening_sockets(ngx_cycle_t *cycle)
#if (NGX_HAVE_REUSEPORT)
- if (ls[i].add_reuseport) {
+ if (ls[i].add_reuseport || ls[i].change_protocol) {
/*
* to allow transition from a socket without SO_REUSEPORT
* to multiple sockets with SO_REUSEPORT, we have to set
* SO_REUSEPORT on the old socket before opening new ones
+ *
+ * to allow transition between different socket protocols
+ * (e.g. IPPROTO_MPTCP), SO_REUSEPORT is set on both old
+ * and new sockets
*/
int reuseport = 1;
@@ -474,7 +497,7 @@ ngx_open_listening_sockets(ngx_cycle_t *cycle)
}
#endif
- if (ls[i].fd != (ngx_socket_t) -1) {
+ if (ls[i].fd != (ngx_socket_t) -1 && !ls[i].change_protocol) {
continue;
}
@@ -487,7 +510,8 @@ ngx_open_listening_sockets(ngx_cycle_t *cycle)
continue;
}
- s = ngx_socket(ls[i].sockaddr->sa_family, ls[i].type, 0);
+ s = ngx_socket(ls[i].sockaddr->sa_family, ls[i].type,
+ ls[i].protocol);
if (s == (ngx_socket_t) -1) {
ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno,
@@ -517,7 +541,9 @@ ngx_open_listening_sockets(ngx_cycle_t *cycle)
#if (NGX_HAVE_REUSEPORT)
- if (ls[i].reuseport && !ngx_test_config) {
+ if ((ls[i].reuseport || ls[i].change_protocol)
+ && !ngx_test_config)
+ {
int reuseport;
reuseport = 1;
@@ -651,6 +677,7 @@ ngx_open_listening_sockets(ngx_cycle_t *cycle)
if (ls[i].type != SOCK_STREAM) {
ls[i].fd = s;
+ ls[i].open = 1;
continue;
}
@@ -689,6 +716,7 @@ ngx_open_listening_sockets(ngx_cycle_t *cycle)
ls[i].listen = 1;
ls[i].fd = s;
+ ls[i].open = 1;
}
if (!failed) {
diff --git a/src/core/ngx_connection.h b/src/core/ngx_connection.h
index 84dd80442..b7d236837 100644
--- a/src/core/ngx_connection.h
+++ b/src/core/ngx_connection.h
@@ -24,6 +24,7 @@ struct ngx_listening_s {
ngx_str_t addr_text;
int type;
+ int protocol;
int backlog;
int rcvbuf;
@@ -75,6 +76,8 @@ struct ngx_listening_s {
unsigned keepalive:2;
unsigned quic:1;
+ unsigned change_protocol:1;
+
unsigned deferred_accept:1;
unsigned delete_deferred:1;
unsigned add_deferred:1;
diff --git a/src/core/ngx_cycle.c b/src/core/ngx_cycle.c
index a75bdf878..e5fd40285 100644
--- a/src/core/ngx_cycle.c
+++ b/src/core/ngx_cycle.c
@@ -535,9 +535,15 @@ ngx_init_cycle(ngx_cycle_t *old_cycle)
== NGX_OK)
{
nls[n].fd = ls[i].fd;
- nls[n].inherited = ls[i].inherited;
nls[n].previous = &ls[i];
- ls[i].remain = 1;
+
+ if (ls[i].protocol != nls[n].protocol) {
+ nls[n].change_protocol = 1;
+
+ } else {
+ nls[n].inherited = ls[i].inherited;
+ ls[i].remain = 1;
+ }
if (ls[i].backlog != nls[n].backlog) {
nls[n].listen = 1;
@@ -590,7 +596,6 @@ ngx_init_cycle(ngx_cycle_t *old_cycle)
}
if (nls[n].fd == (ngx_socket_t) -1) {
- nls[n].open = 1;
#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER)
if (nls[n].accept_filter) {
nls[n].add_deferred = 1;
@@ -605,20 +610,21 @@ ngx_init_cycle(ngx_cycle_t *old_cycle)
}
} else {
+#if (NGX_HAVE_DEFERRED_ACCEPT)
ls = cycle->listening.elts;
for (i = 0; i < cycle->listening.nelts; i++) {
- ls[i].open = 1;
-#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER)
+#ifdef SO_ACCEPTFILTER
if (ls[i].accept_filter) {
ls[i].add_deferred = 1;
}
#endif
-#if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT)
+#ifdef TCP_DEFER_ACCEPT
if (ls[i].deferred_accept) {
ls[i].add_deferred = 1;
}
#endif
}
+#endif
}
if (ngx_open_listening_sockets(cycle) != NGX_OK) {
diff --git a/src/http/ngx_http.c b/src/http/ngx_http.c
index 7f2b4225a..a97cc35f1 100644
--- a/src/http/ngx_http.c
+++ b/src/http/ngx_http.c
@@ -1846,6 +1846,7 @@ ngx_http_add_listening(ngx_conf_t *cf, ngx_http_conf_addr_t *addr)
#endif
ls->type = addr->opt.type;
+ ls->protocol = addr->opt.protocol;
ls->backlog = addr->opt.backlog;
ls->rcvbuf = addr->opt.rcvbuf;
ls->sndbuf = addr->opt.sndbuf;
diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c
index c75ddb849..a2ff53f82 100644
--- a/src/http/ngx_http_core_module.c
+++ b/src/http/ngx_http_core_module.c
@@ -4223,6 +4223,19 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
continue;
}
+ if (ngx_strcmp(value[n].data, "multipath") == 0) {
+#ifdef IPPROTO_MPTCP
+ lsopt.protocol = IPPROTO_MPTCP;
+ lsopt.set = 1;
+ lsopt.bind = 1;
+#else
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "multipath is not supported "
+ "on this platform, ignored");
+#endif
+ continue;
+ }
+
if (ngx_strcmp(value[n].data, "ssl") == 0) {
#if (NGX_HTTP_SSL)
lsopt.ssl = 1;
@@ -4389,6 +4402,12 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
}
#endif
+#ifdef IPPROTO_MPTCP
+ if (lsopt.protocol == IPPROTO_MPTCP) {
+ return "\"multipath\" parameter is incompatible with \"quic\"";
+ }
+#endif
+
#if (NGX_HTTP_SSL)
if (lsopt.ssl) {
return "\"ssl\" parameter is incompatible with \"quic\"";
diff --git a/src/http/ngx_http_core_module.h b/src/http/ngx_http_core_module.h
index 9be565373..6062d3a23 100644
--- a/src/http/ngx_http_core_module.h
+++ b/src/http/ngx_http_core_module.h
@@ -88,6 +88,7 @@ typedef struct {
int rcvbuf;
int sndbuf;
int type;
+ int protocol;
#if (NGX_HAVE_SETFIB)
int setfib;
#endif
diff --git a/src/mail/ngx_mail.c b/src/mail/ngx_mail.c
index 890d8153a..cc34cb365 100644
--- a/src/mail/ngx_mail.c
+++ b/src/mail/ngx_mail.c
@@ -332,6 +332,7 @@ ngx_mail_optimize_servers(ngx_conf_t *cf, ngx_array_t *ports)
ls->log.data = &ls->addr_text;
ls->log.handler = ngx_accept_log_error;
+ ls->protocol = addr[i].opt.protocol;
ls->backlog = addr[i].opt.backlog;
ls->rcvbuf = addr[i].opt.rcvbuf;
ls->sndbuf = addr[i].opt.sndbuf;
diff --git a/src/mail/ngx_mail.h b/src/mail/ngx_mail.h
index e0c62b7ab..221faf67d 100644
--- a/src/mail/ngx_mail.h
+++ b/src/mail/ngx_mail.h
@@ -47,6 +47,7 @@ typedef struct {
int tcp_keepintvl;
int tcp_keepcnt;
#endif
+ int protocol;
int backlog;
int rcvbuf;
int sndbuf;
diff --git a/src/mail/ngx_mail_core_module.c b/src/mail/ngx_mail_core_module.c
index 228a8d0a8..5532ceeb1 100644
--- a/src/mail/ngx_mail_core_module.c
+++ b/src/mail/ngx_mail_core_module.c
@@ -447,6 +447,18 @@ ngx_mail_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
#endif
}
+ if (ngx_strcmp(value[i].data, "multipath") == 0) {
+#ifdef IPPROTO_MPTCP
+ ls->protocol = IPPROTO_MPTCP;
+ ls->bind = 1;
+#else
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "multipath is not supported "
+ "on this platform, ignored");
+#endif
+ continue;
+ }
+
if (ngx_strcmp(value[i].data, "ssl") == 0) {
#if (NGX_MAIL_SSL)
ngx_mail_ssl_conf_t *sslcf;
diff --git a/src/stream/ngx_stream.c b/src/stream/ngx_stream.c
index b6eeb23af..508ce6082 100644
--- a/src/stream/ngx_stream.c
+++ b/src/stream/ngx_stream.c
@@ -1010,6 +1010,7 @@ ngx_stream_add_listening(ngx_conf_t *cf, ngx_stream_conf_addr_t *addr)
ls->log.handler = ngx_accept_log_error;
ls->type = addr->opt.type;
+ ls->protocol = addr->opt.protocol;
ls->backlog = addr->opt.backlog;
ls->rcvbuf = addr->opt.rcvbuf;
ls->sndbuf = addr->opt.sndbuf;
diff --git a/src/stream/ngx_stream.h b/src/stream/ngx_stream.h
index dc05dc5ba..9bc689e99 100644
--- a/src/stream/ngx_stream.h
+++ b/src/stream/ngx_stream.h
@@ -62,6 +62,7 @@ typedef struct {
int rcvbuf;
int sndbuf;
int type;
+ int protocol;
#if (NGX_HAVE_SETFIB)
int setfib;
#endif
diff --git a/src/stream/ngx_stream_core_module.c b/src/stream/ngx_stream_core_module.c
index a09c7c634..6556c5a73 100644
--- a/src/stream/ngx_stream_core_module.c
+++ b/src/stream/ngx_stream_core_module.c
@@ -1202,6 +1202,19 @@ ngx_stream_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
continue;
}
+ if (ngx_strcmp(value[i].data, "multipath") == 0) {
+#ifdef IPPROTO_MPTCP
+ lsopt.protocol = IPPROTO_MPTCP;
+ lsopt.set = 1;
+ lsopt.bind = 1;
+#else
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "multipath is not supported "
+ "on this platform, ignored");
+#endif
+ continue;
+ }
+
if (ngx_strcmp(value[i].data, "ssl") == 0) {
#if (NGX_STREAM_SSL)
lsopt.ssl = 1;
@@ -1338,6 +1351,12 @@ ngx_stream_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
}
#endif
+#ifdef IPPROTO_MPTCP
+ if (lsopt.protocol == IPPROTO_MPTCP) {
+ return "\"multipath\" parameter is incompatible with \"udp\"";
+ }
+#endif
+
#if (NGX_STREAM_SSL)
if (lsopt.ssl) {
return "\"ssl\" parameter is incompatible with \"udp\"";