Hi, This patch is about Multi-Path TCP. Multi-Path TCP (MPTCP) had been in development for the past 15 years which started with MPTCP v0 (version 0) which initially had issues for middleboxes and NAT Gateways. The current iteration is MPTCP v1 which has a fallback mechanism to regular TCP to avoid issues with middleboxes and NAT Gateways. Started to add this code change as a need as i have large git codebases with around 50 gigabytes and i have multiple WAN links which i can aggregate bandwidth across and even when network one path (even in between my CPE router to internet) is down, i will not get interrupted. Also i am using a Linux laptop that has WiFi and 5G module. So this kind of adds my reason of adding support for git (on Linux) To get MPTCP to be fully working, both ends of client and server must implement MPTCP. My implementation adds support for the basic git protocol. MPTCP helps in situations when one of my WAN links have a high latency and automatically choose a link with a path with less latency. Also, MPTCP aggregates the MPTCP connection by using subflows where two or more links can be utilised with subflows. A single flow of data can have multiple subflows across different IP interfaces and thus increases network throughput. Apple for example had been using MPTCP for their cloud services since MPTCP v0 which had issues with middleboxes (not MPTCP v1) since 2013. The downside, even though i had never experienced it for other applications on Linux like Google Chromium[1], is that the fallback might induce delays in connectivity, if i've read it somewhere which i cannot recall where. How this patch works: This patch enables MPTCP protocol option only when it's built on Linux with IPPROTO_MPTCP support in netinet/in.h. On Linux, if IPPROTO_MPTCP is not defined in netinet/in.h, it will skipped. IPPROTO_MPTCP should and never be enabled when it detects being built on an OS other than Linux with defined(__linux__) check. Another challenge is that although "getaddrinfo()" is a POSIX function, not all glibc "getaddrinfo()" implementation is written with IPPROTO_MPTCP support out of the box, especially on older glibc versions. getaddrinfo() IPPROTO_MPTCP support had only been added to recent glibc in 2025 eventhough IPPROTO_MPTCP definition had been around for much longer in netinet/in.h. So we run getaddrinfo() which is a code in glibc and check for errors, specifically "EAI_SOCKTYPE" return value which tells us that the socket type is not supported and fallback to regular TCP (IPPROTO_TCP) Also we will also check that we are building on Linux and depending on version number of Linux we will initialize the socket() accordingly and if there is an error return value (like EINVAL/EPROTONOSUPPORT/ENOPROTOOPT), we will fall back to regular TCP. Enabling and disabling MPTCP: By default on the client side, MPTCP will not be enabled in git client, however MPTCP can be enabled by setting an environment variable "GIT_ENABLE_MPTCP" to any value. Persisting the configuration can be done in your shell. Also for server side git server (daemon.c), there is a flag to optionally enable mptcp with "--mptcp", example: git-daemon --base-path=/all/my/repos --export-all --mptcp This will tell the git server daemon to accept mptcp connections but fallback to regular tcp when mptcp connection is not available. PS: Can someone point me about having a "knob" in Makefile or is this already sufficient? [1] https://chromium-review.googlesource.com/c/chromium/src/+/6355767 Signed-off-by: Muhammad Nuzaihan Bin Kamal Luddin <zaihan@xxxxxxxxxxxxxx>
diff --git a/connect.c b/connect.c index 3280435331..846fa31853 100644 --- a/connect.c +++ b/connect.c @@ -23,6 +23,9 @@ #include "alias.h" #include "bundle-uri.h" #include "promisor-remote.h" +#ifdef __linux__ +#include <linux/version.h> +#endif static char *server_capabilities_v1; static struct strvec server_capabilities_v2 = STRVEC_INIT; @@ -793,6 +796,16 @@ static void enable_keepalive(int sockfd) error_errno(_("unable to set SO_KEEPALIVE on socket")); } +static const char *git_enable_mptcp(void) +{ + const char *mptcp; + + if ((mptcp = getenv("GIT_ENABLE_MPTCP"))) + return mptcp; + + return NULL; +} + #ifndef NO_IPV6 static const char *ai_name(const struct addrinfo *ai) @@ -816,6 +829,7 @@ static int git_tcp_connect_sock(char *host, int flags) struct addrinfo hints, *ai0, *ai; int gai; int cnt = 0; + const char *enable_mptcp; get_host_and_port(&host, &port); if (!*port) @@ -827,12 +841,28 @@ static int git_tcp_connect_sock(char *host, int flags) else if (flags & CONNECT_IPV6) hints.ai_family = AF_INET6; hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = IPPROTO_TCP; +#if defined(__linux__) && defined(IPPROTO_MPTCP) + enable_mptcp = git_enable_mptcp(); + if (enable_mptcp) + hints.ai_protocol = IPPROTO_MPTCP; + else + hints.ai_protocol = IPPROTO_TCP; +#else + hints.ai_protocol = IPPROTO_TCP; +#endif if (flags & CONNECT_VERBOSE) fprintf(stderr, _("Looking up %s ... "), host); - gai = getaddrinfo(host, port, &hints, &ai); + gai = getaddrinfo(host, port, &hints, &ai); + // If system's glibc getaddrinfo() does not have + // IPPROTO_MPTCP as member type in struct (like older + // glibc and other libc), we fallback to IPPROTO_TCP + if (gai == EAI_SOCKTYPE) { + hints.ai_protocol = IPPROTO_TCP; + gai = getaddrinfo(host, port, &hints, &ai); + } + if (gai) die(_("unable to look up %s (port %s) (%s)"), host, port, gai_strerror(gai)); @@ -889,6 +919,7 @@ static int git_tcp_connect_sock(char *host, int flags) char **ap; unsigned int nport; int cnt; + const char *enable_mptcp; get_host_and_port(&host, &port); @@ -917,6 +948,21 @@ static int git_tcp_connect_sock(char *host, int flags) sa.sin_port = htons(nport); memcpy(&sa.sin_addr, *ap, he->h_length); +#ifdef __linux__ + enable_mptcp = git_enable_mptcp(); + if (enable_mptcp) { + sockfd = socket(he->h_addrtype, SOCK_STREAM, IPPROTO_MPTCP); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,6,0) + // MPTCP check return value for Linux Kernel >= 5.6 + if (sockfd == EPROTONOSUPPORT || sockfd == ENOPROTOOPT) + continue; +#else + // MPTCP check return value for Linux Kernel < 5.6 + if (sockfd == EINVAL || sockfd == ENOPROTOOPT) + continue; +#endif + } +#endif sockfd = socket(he->h_addrtype, SOCK_STREAM, 0); if ((sockfd < 0) || connect(sockfd, (struct sockaddr *)&sa, sizeof sa) < 0) { diff --git a/daemon.c b/daemon.c index d1be61fd57..793f8a4219 100644 --- a/daemon.c +++ b/daemon.c @@ -25,6 +25,7 @@ static enum log_destination { } log_destination = LOG_DESTINATION_UNSET; static int verbose; static int reuseaddr; +static int mptcp; static int informative_errors; static const char daemon_usage[] = @@ -38,6 +39,7 @@ static const char daemon_usage[] = " [--access-hook=<path>]\n" " [--inetd | [--listen=<host_or_ipaddr>] [--port=<n>]\n" " [--detach] [--user=<user> [--group=<group>]]\n" +" [--mptcp]\n" " [--log-destination=(stderr|syslog|none)]\n" " [<directory>...]"; @@ -975,10 +977,24 @@ static int setup_named_sock(char *listen_addr, int listen_port, struct socketlis memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = IPPROTO_TCP; +#if defined(__linux__) && defined(IPPROTO_MPTCP) + if (mptcp) + hints.ai_protocol = IPPROTO_MPTCP; + else + hints.ai_protocol = IPPROTO_MPTCP; +#else + hints.ai_protocol = IPPROTO_TCP; +#endif hints.ai_flags = AI_PASSIVE; - gai = getaddrinfo(listen_addr, pbuf, &hints, &ai0); + gai = getaddrinfo(listen_addr, pbuf, &hints, &ai0); + // If system's glibc getaddrinfo() does not have + // IPPROTO_MPTCP as member type in struct (like older + // glibc and other libc), we fallback to IPPROTO_TCP + if (gai == EAI_SOCKTYPE) { + hints.ai_protocol = IPPROTO_TCP; + gai = getaddrinfo(listen_addr, pbuf, &hints, &ai0); + } if (gai) { logerror("getaddrinfo() for %s failed: %s", listen_addr, gai_strerror(gai)); return 0; @@ -1342,6 +1358,10 @@ int cmd_main(int argc, const char **argv) reuseaddr = 1; continue; } + if (!strcmp(arg, "--mptcp")) { + mptcp = 1; + continue; + } if (!strcmp(arg, "--user-path")) { user_path = ""; continue;