]> git.kaiwu.me - nginx.git/commitdiff
The "multipath" parameter of the "listen" directive.
authorSergey Kandaurov <pluknet@nginx.com>
Thu, 16 Oct 2025 15:22:56 +0000 (15:22 +0000)
committerSergey Kandaurov <s.kandaurov@f5.com>
Wed, 18 Mar 2026 21:13:51 +0000 (01:13 +0400)
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.

13 files changed:
contrib/vim/syntax/nginx.vim
src/core/ngx_connection.c
src/core/ngx_connection.h
src/core/ngx_cycle.c
src/http/ngx_http.c
src/http/ngx_http_core_module.c
src/http/ngx_http_core_module.h
src/mail/ngx_mail.c
src/mail/ngx_mail.h
src/mail/ngx_mail_core_module.c
src/stream/ngx_stream.c
src/stream/ngx_stream.h
src/stream/ngx_stream_core_module.c

index 29eef7a23cd716a1640f32e8d80f63db4e36d41d..ea7c58464bf9c6dd63a3cebc853724fa9b14a213 100644 (file)
@@ -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
index 7cae295eb483569c221b2268f5152eda505cd178..c269a97e6124cfa40540e878eac2fd8250b59ece 100644 (file)
@@ -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) {
index 84dd80442fb9530900e58656a4312d7c59d9e60e..b7d2368374cae9debd2883df30e0e4039b7045bc 100644 (file)
@@ -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;
index a75bdf8786fa4f2c4dd45129741bfcf4b5849263..e5fd40285311108eadb1d9ac4da07e71fe84272b 100644 (file)
@@ -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) {
index 7f2b4225aae23fd16021576335e43e4599680207..a97cc35f18d47f34bdf2288b18a098fe08d5827f 100644 (file)
@@ -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;
index c75ddb849fc753cc99a3e23b29f9d6194b024c0b..a2ff53f82fbfca3ddcd98d1fffe17d7ad0a0b8ca 100644 (file)
@@ -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\"";
index 9be565373a68e2732d0ad592729e07d7dd2b2be6..6062d3a23205ea3681604885257638487ff21936 100644 (file)
@@ -88,6 +88,7 @@ typedef struct {
     int                        rcvbuf;
     int                        sndbuf;
     int                        type;
+    int                        protocol;
 #if (NGX_HAVE_SETFIB)
     int                        setfib;
 #endif
index 890d8153a0505f4c856fb3e550b6c4b3754a8c6b..cc34cb3659ce9193e7c3be7be1019b68ab9acd52 100644 (file)
@@ -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;
index e0c62b7abfd809f6b36103e2f19ac33e97d37cf0..221faf67d5d90bb92c716fc603fdc27477dfac32 100644 (file)
@@ -47,6 +47,7 @@ typedef struct {
     int                     tcp_keepintvl;
     int                     tcp_keepcnt;
 #endif
+    int                     protocol;
     int                     backlog;
     int                     rcvbuf;
     int                     sndbuf;
index 228a8d0a8beebd3ce79a783534796bfd45adfc09..5532ceeb1be9fa6d6ae6cf032209d17286b6cf44 100644 (file)
@@ -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;
index b6eeb23af31070f32208702b7018dbe2c75a4be1..508ce608218fcb2513c1d323dec0b1ee8d39a8f1 100644 (file)
@@ -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;
index dc05dc5bac61e28da1b9f56d2fde41e95addaa9c..9bc689e9998cdb672581c9d6ce442f5b7582c055 100644 (file)
@@ -62,6 +62,7 @@ typedef struct {
     int                            rcvbuf;
     int                            sndbuf;
     int                            type;
+    int                            protocol;
 #if (NGX_HAVE_SETFIB)
     int                            setfib;
 #endif
index a09c7c634f45530c19cd7087f7db7f32cae02469..6556c5a73cffa7cbeea577e655cd9a33f3f85135 100644 (file)
@@ -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\"";