On Tue, Aug 18, 2026 at 5:19 PM Greg Sabino Mullane <[email protected]> wrote:
>
> Thanks for v4! Took a heavier look. Code applies cleanly to 
> 033f39e694872d8d74e670a20093db781eb0bf61, is indented properly, and passes 
> make check.

Hi Greg, cool, thanks for reviewing!

> > <term><literal>MPTCP</literal><indexterm><primary>MultiPath 
> > TCP</primary></indexterm></term>
>
> s/MultiPath/Multipath/ (other places like the commit message too)

Fixed

> > (multiplexing) over mulitple network paths, provided that remote also
>
> s/mulitple/multiple/

Fixed

> src/backend/libpq/pqcomm.c
[..]
> It's already 0 from the top of the loop, so it should be enough to do:
>
> #ifdef IPPROTO_MPTCP
>   if (ListenMPTCP && addr->ai_family != AF_UNIX)
>     socket_protocol = IPPROTO_MPTCP;
> #endif

Absolutely yes, fixed.

> src/backend/utils/misc/postgresql.conf.sample
> > #listen_mptcp = off                     # whether to enable Multipathing 
> > TCP or not
>
> s/Multipathing/Multipath/
>
> Or just: # enable Multipath TCP
>
> Can we move to a less prominent place - perhaps under TCP, after 
> client_connection_check_interval?

Sure thing, both things fixed

> src/interfaces/libpq/fe-connect.c
> > {"mptcp", "PGMPTCP", "0", NULL,
>
> Does this need freeing in freePGconn()?

Good catch, added it there.

> src/interfaces/libpq/fe-connect.c
> > if (addr_cur->family != AF_UNIX && conn->mptcp && conn->mptcp[0] == '1')
> > {
> > #ifdef IPPROTO_MPTCP
> >   fprintf(stderr, "enabling MPTCP client\n");
> >   ip_protocol = IPPROTO_MPTCP;
> > #else
> >   fprintf(stderr, "MPTCP client is not supported on this platform\n");
> > #endif
>
> We should throw a proper message to the user if they attempt mptcp via tcp, 
> but don't have it enabled by using the libpq_append_conn_error function.

Thanks for the hint, improved

> Don't know if a warning for attempting mptcp on via unix socket is worth it.

I don't think so, we should just ignore it I think.

> I'm not clear on the mptcp[0] == '1' bit - does that mean the only way to 
> invoke it is exactly this?:
>
> PGMPTCP=1
>
> (or I suppose, any other string starting with "1")

Exactly, I'm not sure if there's better way, should we parse "on" / "true"
maybe?

> Big picture: is it worth making this more generic, in case other protocols 
> appear some time in the future?
>
> listen_mptcp = on -> listen_protocol = mptcp
> PGMPTCP=1 -> PGSOCKETPROTOCOL=mptcp
>

Awesome question. I've started thinking and the hypothethical list could
include:
* SCTP (got no adoption, it's long time dead because it used different
  layer3 protocol than just TCP/UDP and corporate firewallls kiled it, sic!)
* QUIC (the thing for HTTP/3 which apparently has similiar MP-QUIC too;
  works over UDP but that would be different userspace beast and even if
  listen_quic=on/quic=on also looks good to me)
* and there's potentially kind of extreme/hard-core/sci-fi case of RDMA
  world (RoCEv2/Infiniband) which could be used for libpq, but that would
  seem  like great fit for low latency stuff, but probably would be very
  hard to integrate into libpq (it has no apparently no classic BSD sockets
  /fds API). Dunno what would be real usecase for that though (very-close-
  proximity backups/replicas/caches only? that thing requires lossless
  >=100GbE ethernet), but we could see it one day

You ask how we could extend PostgreSQL in future with this and I'm both
fine with listen_<X> and listen_protocols=tcp,[mptcp,quic,etc] which
would be more future proof for sure, just also I don't know which
path to take.

Anyway, client-side "PGSOCKETPROTOCOL" also sounds as cool name/way to
enable it.

> On re-reading this email, that doesn't allow us to handle different protocols 
> for different families, so listen_mptcp is fine.

I'm fine with both ways to be honest. Let's leave it like this and if
there are voices strictly in favor of PGSOCKETPROTOCOL/listen_protocols
we can adapt to that, ok?

> This new ENV should be added to the lists at 
> src/test/perl/PostgreSQL/Test/Utils.pm and src/test/regress/pg_regress.c

Done, thanks

> Ideally also some tests.

I've added some easy minimal testing to the postmaster's suite, where I think
this had best fit.

-J.
From e379c4ee3bcce23180b08437c0c0c857acc048af Mon Sep 17 00:00:00 2001
From: Jakub Wartak <[email protected]>
Date: Thu, 4 Sep 2025 11:39:54 +0200
Subject: [PATCH v5] Add MPTCP protocol support to server and libpq on Linux.

This adds new listen_mptcp configuration option and also exposes new
environment variable PGMPTCP and the mptcp libpq connection option,
which can be enabled to request Multipathed TCP connections.

Author: Jakub Wartak <[email protected]>
Reviewed-by: Greg Sabino Mullane <[email protected]>
Discussion: https://postgr.es/m/CAKZiRmy6j9PBzDHZwdgwHavwKDzv5GWtRSWOTj6-jv6SCOZ%3DYA%40mail.gmail.com
---
 doc/src/sgml/libpq.sgml                       | 26 ++++++++++++++++++
 src/backend/commands/variable.c               | 13 +++++++++
 src/backend/libpq/pqcomm.c                    | 13 ++++++++-
 src/backend/postmaster/postmaster.c           |  3 +++
 src/backend/utils/misc/guc_parameters.dat     |  7 +++++
 src/backend/utils/misc/postgresql.conf.sample |  2 ++
 src/include/postmaster/postmaster.h           |  1 +
 src/include/utils/guc_hooks.h                 |  1 +
 src/interfaces/libpq/fe-connect.c             | 24 ++++++++++++++++-
 src/interfaces/libpq/libpq-int.h              |  1 +
 src/test/perl/PostgreSQL/Test/Utils.pm        |  1 +
 src/test/postmaster/t/003_start_stop.pl       | 27 +++++++++++++++++++
 src/test/regress/pg_regress.c                 |  1 +
 13 files changed, 118 insertions(+), 2 deletions(-)

diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 7d3c3bb66d8..3ccc00b1121 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -2602,6 +2602,22 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
       </listitem>
      </varlistentry>
 
+     <varlistentry id="libpq-connect-mptcp" xreflabel="mptcp">
+      <term><literal>MPTCP</literal><indexterm><primary>Multipath TCP</primary></indexterm></term>
+      <listitem>
+       <para>
+        Controls whether client-side MPTCP protocol is used. The default
+        value is 0, meaning off, but you can change this to 1, meaning on.
+        This parameter is ignored for connections made via a Unix-domain socket.
+       </para>
+
+       <para>
+        MPTCP protocol is only supported on Linux and allows connection aggregation
+        (multiplexing) over multiple network paths, provided that remote also
+        supports MPTCP.
+       </para>
+      </listitem>
+     </varlistentry>
     </variablelist>
    </para>
   </sect2>
@@ -9097,6 +9113,16 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
      </para>
     </listitem>
 
+    <listitem>
+     <para>
+      <indexterm>
+       <primary><envar>PGMPTCP</envar></primary>
+      </indexterm>
+      <envar>PGMPTCP</envar> behaves the same as the <xref
+      linkend="libpq-connect-mptcp"/> connection parameter.
+     </para>
+    </listitem>
+
     <listitem>
      <para>
       <indexterm>
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 8afd252fc8c..ec0a61fdd6b 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -1230,6 +1230,19 @@ check_bonjour(bool *newval, void **extra, GucSource source)
 	return true;
 }
 
+bool
+check_listen_mptcp(bool *newval, void **extra, GucSource source)
+{
+#ifndef IPPROTO_MPTCP
+	if (*newval)
+	{
+		GUC_check_errmsg("setting the MPTCP listening socket is not supported on this platform");
+		return false;
+	}
+#endif
+	return true;
+}
+
 bool
 check_default_with_oids(bool *newval, void **extra, GucSource source)
 {
diff --git a/src/backend/libpq/pqcomm.c b/src/backend/libpq/pqcomm.c
index aaae7214f13..777ef68f55b 100644
--- a/src/backend/libpq/pqcomm.c
+++ b/src/backend/libpq/pqcomm.c
@@ -488,6 +488,8 @@ ListenServerPort(int family, const char *hostName, unsigned short portNumber,
 
 	for (addr = addrs; addr; addr = addr->ai_next)
 	{
+		int			ipprotocol = 0;
+
 		if (family != AF_UNIX && addr->ai_family == AF_UNIX)
 		{
 			/*
@@ -539,7 +541,16 @@ ListenServerPort(int family, const char *hostName, unsigned short portNumber,
 			addrDesc = addrBuf;
 		}
 
-		if ((fd = socket(addr->ai_family, SOCK_STREAM, 0)) == PGINVALID_SOCKET)
+		/*
+		 * enable MPTCP only on IP and IPv6 sockets and not for UNIX domain
+		 * sockets
+		 */
+#ifdef IPPROTO_MPTCP
+		if (addr->ai_family != AF_UNIX)
+			ipprotocol = ListenMPTCP ? IPPROTO_MPTCP : 0;
+#endif
+
+		if ((fd = socket(addr->ai_family, SOCK_STREAM, ipprotocol)) == PGINVALID_SOCKET)
 		{
 			ereport(LOG,
 					(errcode_for_socket_access(),
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 90c7c4528e8..f97419e0325 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -209,6 +209,9 @@ char	   *Unix_socket_directories;
 /* The TCP listen address(es) */
 char	   *ListenAddresses;
 
+/* Whether to use MPTCP */
+bool		ListenMPTCP;
+
 /*
  * SuperuserReservedConnections is the number of backends reserved for
  * superuser use, and ReservedConnections is the number of backends reserved
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 3c5e16ad1e7..7b1ab280401 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1599,6 +1599,13 @@
   boot_val => '"localhost"',
 },
 
+{ name => 'listen_mptcp', type => 'bool', context => 'PGC_POSTMASTER', group => 'CONN_AUTH_SETTINGS',
+  short_desc => 'Whether to enable MPTCP on the listening socket',
+  variable => 'ListenMPTCP',
+  boot_val => 'false',
+  check_hook => 'check_listen_mptcp',
+},
+
 { name => 'lo_compat_privileges', type => 'bool', context => 'PGC_SUSET', group => 'COMPAT_OPTIONS_PREVIOUS',
   short_desc => 'Enables backward compatibility mode for privilege checks on large objects.',
   long_desc => 'Skips privilege checks when reading or modifying large objects, for compatibility with PostgreSQL releases prior to 9.0.',
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index e759f06b50f..a6adc9ff9ca 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -92,6 +92,8 @@
 #client_connection_check_interval = 0   # time between checks for client
                                         # disconnection while running queries;
                                         # 0 for never
+#listen_mptcp = off                     # whether to enable Multipath TCP or not
+                                        # (change requires restart)
 
 # - Authentication -
 
diff --git a/src/include/postmaster/postmaster.h b/src/include/postmaster/postmaster.h
index 716b4c912b3..a183fefd2d8 100644
--- a/src/include/postmaster/postmaster.h
+++ b/src/include/postmaster/postmaster.h
@@ -60,6 +60,7 @@ extern PGDLLIMPORT int Unix_socket_permissions;
 extern PGDLLIMPORT char *Unix_socket_group;
 extern PGDLLIMPORT char *Unix_socket_directories;
 extern PGDLLIMPORT char *ListenAddresses;
+extern PGDLLIMPORT bool ListenMPTCP;
 extern PGDLLIMPORT bool ClientAuthInProgress;
 extern PGDLLIMPORT int PreAuthDelay;
 extern PGDLLIMPORT int AuthenticationTimeout;
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index 6a76f8d5ed6..b35fcf8f2be 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -66,6 +66,7 @@ extern bool check_huge_page_size(int *newval, void **extra, GucSource source);
 extern void assign_io_method(int newval, void *extra);
 extern bool check_io_max_concurrency(int *newval, void **extra, GucSource source);
 extern const char *show_in_hot_standby(void);
+extern bool check_listen_mptcp(bool *newval, void **extra, GucSource source);
 extern bool check_locale_messages(char **newval, void **extra, GucSource source);
 extern void assign_locale_messages(const char *newval, void *extra);
 extern bool check_locale_monetary(char **newval, void **extra, GucSource source);
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index ee398f13998..c36c829f438 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -421,6 +421,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
 		"SSL-Key-Log-File", "D", 64,
 	offsetof(struct pg_conn, sslkeylogfile)},
 
+	{"mptcp", "PGMPTCP", "0", NULL,
+		"MPTCP-Protocol", "", 1,
+	offsetof(struct pg_conn, mptcp)},
+
 	/* Terminating entry --- MUST BE LAST */
 	{NULL, NULL, NULL, NULL,
 	NULL, NULL, 0}
@@ -3253,6 +3257,7 @@ keep_going:						/* We will come back to here until there is
 					char		host_addr[NI_MAXHOST];
 					int			sock_type;
 					AddrInfo   *addr_cur;
+					int			ip_protocol = 0;
 
 					/*
 					 * Advance to next possible host, if we've tried all of
@@ -3338,7 +3343,23 @@ keep_going:						/* We will come back to here until there is
 					 */
 					sock_type |= SOCK_NONBLOCK;
 #endif
-					conn->sock = socket(addr_cur->family, sock_type, 0);
+
+					/*
+					 * enable MPTCP only on IP and IPv6 sockets and not for
+					 * UNIX domain sockets
+					 */
+					if (addr_cur->family != AF_UNIX && conn->mptcp && conn->mptcp[0] == '1')
+					{
+#ifdef IPPROTO_MPTCP
+						/* TODO: remove this, it's for devel/informational purposes only for now */
+						fprintf(stderr, "enabling MPTCP client\n");
+						ip_protocol = IPPROTO_MPTCP;
+#else
+						libpq_append_conn_error(conn, "MPTCP client is not supported on this platform");
+#endif
+
+					}
+					conn->sock = socket(addr_cur->family, sock_type, ip_protocol);
 					if (conn->sock == PGINVALID_SOCKET)
 					{
 						int			errorno = SOCK_ERRNO;
@@ -5160,6 +5181,7 @@ freePGconn(PGconn *conn)
 	free(conn->scram_client_key);
 	free(conn->scram_server_key);
 	free(conn->sslkeylogfile);
+	free(conn->mptcp);
 	free(conn->oauth_issuer);
 	free(conn->oauth_issuer_id);
 	free(conn->oauth_discovery_uri);
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index a737d1db457..9bbe59f384d 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -432,6 +432,7 @@ struct pg_conn
 	char	   *scram_client_key;	/* base64-encoded SCRAM client key */
 	char	   *scram_server_key;	/* base64-encoded SCRAM server key */
 	char	   *sslkeylogfile;	/* where should the client write ssl keylogs */
+	char	   *mptcp;			/* use MPTCP ? */
 
 	bool		cancelRequest;	/* true if this connection is used to send a
 								 * cancel request, instead of being a normal
diff --git a/src/test/perl/PostgreSQL/Test/Utils.pm b/src/test/perl/PostgreSQL/Test/Utils.pm
index d3e6abf7a68..cf418db4807 100644
--- a/src/test/perl/PostgreSQL/Test/Utils.pm
+++ b/src/test/perl/PostgreSQL/Test/Utils.pm
@@ -147,6 +147,7 @@ BEGIN
 	  PGSSLMODE
 	  PGSSLROOTCERT
 	  PGSSLSNI
+	  PGMPTCP
 	  PGTARGETSESSIONATTRS
 	  PGUSER
 	  PGPORT
diff --git a/src/test/postmaster/t/003_start_stop.pl b/src/test/postmaster/t/003_start_stop.pl
index 24a75362c0e..1d21893988a 100644
--- a/src/test/postmaster/t/003_start_stop.pl
+++ b/src/test/postmaster/t/003_start_stop.pl
@@ -8,6 +8,7 @@ use warnings FATAL => 'all';
 use PostgreSQL::Test::Cluster;
 use PostgreSQL::Test::Utils;
 use Test::More;
+use Config qw ( %Config );
 
 #
 # Test that dead-end backends don't prevent the server from shutting
@@ -106,4 +107,30 @@ foreach my $socket (@raw_connections)
 	$socket->close();
 }
 
+# Test the MPTCP where it is supported only
+SKIP:
+{
+	skip "MPTCP not supported by this platform" unless $Config{osname} eq 'linux';
+
+	$node->append_conf('postgresql.conf', "listen_addresses = '127.0.0.1'");
+	$node->append_conf('postgresql.conf', 'listen_mptcp=on');
+	$node->restart();
+
+	my $port = $node->port();
+	my $mptcpoff = "dbname=postgres hostaddr=127.0.0.1 host=localhost port=$port mptcp=0";
+	my $mptcpon = "dbname=postgres hostaddr=127.0.0.1 host=localhost port=$port mptcp=1";
+
+	$node->connect_ok(
+		$mptcpoff,
+		"connection without MPTCP works"
+	);
+
+	$node->connect_ok(
+		$mptcpon,
+		"connection with MPTCP works",
+		# TODO: just for now we expect it
+		expected_stderr => qr/enabling MPTCP client/
+	);
+}
+
 done_testing();
diff --git a/src/test/regress/pg_regress.c b/src/test/regress/pg_regress.c
index 21d00f792d2..49c08a6fd1a 100644
--- a/src/test/regress/pg_regress.c
+++ b/src/test/regress/pg_regress.c
@@ -850,6 +850,7 @@ initialize_environment(void)
 		unsetenv("PGSSLMODE");
 		unsetenv("PGSSLROOTCERT");
 		unsetenv("PGSSLSNI");
+		unsetenv("PGMPTCP");
 		unsetenv("PGTARGETSESSIONATTRS");
 		unsetenv("PGUSER");
 		/* PGPORT, see below */
-- 
2.43.0

Reply via email to