]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.2.0021: channel: connection timeout fails to fall back to IPv4 v9.2.0021
authorthinca <thinca@gmail.com>
Wed, 18 Feb 2026 21:34:57 +0000 (21:34 +0000)
committerChristian Brabandt <cb@256bit.org>
Wed, 18 Feb 2026 21:34:57 +0000 (21:34 +0000)
Problem:  When ch_open() tries to connect to a hostname that resolves to
          multiple addresses (e.g., both IPv6 and IPv4), it uses a
          single waittime for all connection attempts. If the first IPv6
          connection attempt times out, it consumes almost all of the
          waittime, leaving insufficient time (often just 1ms) for the
          IPv4 attempt to succeed. (reporter)
Solution: Implement a simplified version of Happy Eyeballs (RFC 8305) to
          improve connection fallback behavior when IPv6 is unavailable
          or slow (thinca).

Distribute the waittime across multiple addresses:
- First address: use up to 250ms (RFC 8305 Connection Attempt Delay) or
  half of the total waittime, whichever is smaller
- Middle addresses: divide remaining time equally
- Last address: use all remaining time

This ensures that IPv4 fallback has sufficient time to succeed even when
IPv6 connection attempts fail or timeout.

closes: #19233

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: thinca <thinca@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
runtime/doc/channel.txt
src/channel.c
src/version.c

index e2259823b7203ef2ddc554e9e75a59bc9ccdd02b..5c1de248d6b37fda56161046c940c1abf31fc860 100644 (file)
@@ -1,4 +1,4 @@
-*channel.txt*  For Vim version 9.2.  Last change: 2026 Feb 14
+*channel.txt*  For Vim version 9.2.  Last change: 2026 Feb 18
 
 
                  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -130,6 +130,11 @@ a Unix-domain socket path prefixed by "unix:".  E.g. >
     [2001:db8::1]:8765   " IPv6 + port
     unix:/tmp/my-socket  " Unix-domain socket path
 
+When a domain name resolves to multiple addresses (e.g., both IPv6 and IPv4),
+Vim tries each address in order.  If a connection is slow or unreachable, it
+quickly falls back to the next address.  This helps when IPv6 or IPv4 is
+unreachable on the network.
+
 {options} is a dictionary with optional entries:       *channel-open-options*
 
 "mode" can be:                                         *channel-mode*
index 5c7fb07bd8a1f9e47868ff0441513b0c2987b606..a1c9bb7a91b26e7ccb62ab0f1b90d528cac25af8 100644 (file)
@@ -935,13 +935,31 @@ channel_open(
        return NULL;
     }
 
+    // Count the number of addresses for timeout distribution
+    int addr_count = 0;
     for (addr = res; addr != NULL; addr = addr->ai_next)
+       addr_count++;
+
+    // On Mac and Solaris a zero timeout almost never works.  Waiting for
+    // one millisecond already helps a lot.  Later Mac systems (using IPv6)
+    // need more time, 15 milliseconds appears to work well.
+    // Let's do it for all systems, because we don't know why this is
+    // needed.
+    if (waittime == 0)
+       waittime = 15;
+
+    int addr_index = 0;
+    for (addr = res; addr != NULL; addr = addr->ai_next, addr_index++)
     {
        const char  *dst = hostname;
 # ifdef HAVE_INET_NTOP
        const void  *src = NULL;
        char        buf[NUMBUFLEN];
 # endif
+       int         try_waittime;
+       int         before_waittime;
+       int         consumed;
+       int         remaining_addrs;
 
        if (addr->ai_family == AF_INET6)
        {
@@ -974,18 +992,44 @@ channel_open(
 
        ch_log(channel, "Trying to connect to %s port %d", dst, port);
 
-       // On Mac and Solaris a zero timeout almost never works.  Waiting for
-       // one millisecond already helps a lot.  Later Mac systems (using IPv6)
-       // need more time, 15 milliseconds appears to work well.
-       // Let's do it for all systems, because we don't know why this is
-       // needed.
-       if (waittime == 0)
-           waittime = 15;
+       // Distribute the timeout across addresses for better fallback behavior.
+       // This implements a simplified version of Happy Eyeballs (RFC 8305).
+       if (addr->ai_next == NULL)
+           try_waittime = waittime;
+       else if (addr_index == 0)
+       {
+           if (waittime > 500)
+               try_waittime = 250;
+           else if (waittime > 30)
+               try_waittime = waittime / 2;
+           else
+               try_waittime = waittime;
+       }
+       else
+       {
+           remaining_addrs = addr_count - addr_index;
+           try_waittime = waittime / remaining_addrs;
+       }
 
+       before_waittime = try_waittime;
        sd = channel_connect(channel, addr->ai_addr, (int)addr->ai_addrlen,
-                                                                  &waittime);
+                                                          &try_waittime);
+
+       // Update the overall waittime based on consumed time
+       consumed = before_waittime - try_waittime;
+       waittime -= consumed;
+       if (waittime < 0)
+           waittime = 0;
+
        if (sd >= 0)
            break;
+
+       // If we have no time left, stop trying
+       if (waittime <= 0 && addr->ai_next != NULL)
+       {
+           ch_log(channel, "Out of time, stopping connection attempts");
+           break;
+       }
     }
 
     freeaddrinfo(res);
index 394a536da0b0577f7df6c2a4f9aa66536f550edb..53c825fd0e2482fdd85e42efc88a5e4015d7548d 100644 (file)
@@ -734,6 +734,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    21,
 /**/
     20,
 /**/