]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
cf-socket: set scope_id for IPv6 link-local addresses
authorambikeesshh <ambikesh2206@gmail.com>
Fri, 22 May 2026 18:57:18 +0000 (00:27 +0530)
committerDaniel Stenberg <daniel@haxx.se>
Thu, 28 May 2026 06:43:53 +0000 (08:43 +0200)
When connecting to an mDNS hostname that resolves to an IPv6 link-local
address, connect() fails with EINVAL because sin6_scope_id is 0. This is
a regression since 8.20.0 where the threaded resolver started splitting
A and AAAA queries into separate getaddrinfo calls. The AAAA-only call
with PF_INET6 may not set scope_id on systems where the same call with
PF_UNSPEC did.

When the resolver does not provide scope_id for a link-local address,
try to determine it from the system's network interfaces using
getifaddrs(). Also add scope_id to verbose connect output so the value
can be seen in curl -v logs.

Built and tested locally on Linux. checksrc passes.

Fixes #21669
Reported-by: Bartel Sielski
Closes #21728

lib/cf-socket.c

index ec158bddb5799b03090c0ed111c8780680dd45bc..eb782b65dca6e9c0d14e873e542958f000582b21 100644 (file)
 #include <arpa/inet.h>
 #endif
 
+#ifdef HAVE_IFADDRS_H
+#include <ifaddrs.h>
+#endif
+#ifdef HAVE_NET_IF_H
+#include <net/if.h>
+#endif
 #ifdef __VMS
 #include <in.h>
 #include <inet.h>
@@ -297,6 +303,49 @@ int Curl_sock_nosigpipe(curl_socket_t sockfd)
 }
 #endif /* USE_SO_NOSIGPIPE */
 
+#if defined(USE_IPV6) && defined(HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID)
+static uint32_t get_scope_id(struct Curl_easy *data,
+                             struct sockaddr_in6 *sa6)
+{
+  uint32_t scope_id = 0;
+  if(data->conn->scope_id)
+    return data->conn->scope_id;
+  /* NOLINTNEXTLINE(clang-analyzer-core.uninitialized.Assign) */
+  scope_id = sa6->sin6_scope_id;
+  if(!scope_id && IN6_IS_ADDR_LINKLOCAL(&sa6->sin6_addr)) {
+    /* The resolver did not set scope_id for this link-local address.
+     * Try to determine it from the system's network interfaces.
+     * Without a scope_id, connect() to a link-local address fails
+     * with EINVAL on Linux.
+     * NOTE: On multi-homed hosts with several interfaces having
+     * link-local addresses, this picks the first one found, which
+     * may not be the correct outgoing interface. */
+#if defined(HAVE_GETIFADDRS) && defined(HAVE_NET_IF_H)
+    struct ifaddrs *ifa, *ifa_list;
+    if(getifaddrs(&ifa_list) == 0) {
+      for(ifa = ifa_list; ifa; ifa = ifa->ifa_next) {
+        if(ifa->ifa_addr && ifa->ifa_addr->sa_family == AF_INET6 &&
+           (ifa->ifa_flags & IFF_UP) &&
+           !(ifa->ifa_flags & IFF_LOOPBACK)) {
+          struct sockaddr_in6 *s6 = (void *)ifa->ifa_addr;
+          if(IN6_IS_ADDR_LINKLOCAL(&s6->sin6_addr) && s6->sin6_scope_id) {
+            scope_id = s6->sin6_scope_id;
+            infof(data,
+                  "determined scope_id=%lu for link-local address "
+                  "from local interface",
+                  (unsigned long)scope_id);
+            break;
+          }
+        }
+      }
+      freeifaddrs(ifa_list);
+    }
+#endif /* HAVE_GETIFADDRS && HAVE_NET_IF_H */
+  }
+  return scope_id;
+}
+#endif
+
 static CURLcode socket_open(struct Curl_easy *data,
                             struct Curl_sockaddr_ex *addr,
                             curl_socket_t *sockfd)
@@ -366,9 +415,9 @@ static CURLcode socket_open(struct Curl_easy *data,
 #endif
 
 #if defined(USE_IPV6) && defined(HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID)
-  if(data->conn->scope_id && (addr->family == AF_INET6)) {
+  if(addr->family == AF_INET6) {
     struct sockaddr_in6 * const sa6 = (void *)&addr->curl_sa_addr;
-    sa6->sin6_scope_id = data->conn->scope_id;
+    sa6->sin6_scope_id = get_scope_id(data, sa6);
   }
 #endif
   return CURLE_OK;
@@ -1085,7 +1134,20 @@ static CURLcode cf_socket_open(struct Curl_cfilter *cf,
     (void)setsockopt(ctx->sock, IPPROTO_IPV6, IPV6_V6ONLY,
                      (void *)&on, sizeof(on));
 #endif
-    infof(data, "  Trying [%s]:%d...", ctx->ip.remote_ip, ctx->ip.remote_port);
+#ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID
+    {
+      struct sockaddr_in6 *sa6 = (void *)&ctx->addr.curl_sa_addr;
+      if(sa6->sin6_scope_id)
+        infof(data, "  Trying [%s]:%d scope_id=%lu...",
+              ctx->ip.remote_ip, ctx->ip.remote_port,
+              (unsigned long)sa6->sin6_scope_id);
+      else
+#endif
+        infof(data, "  Trying [%s]:%d...",
+              ctx->ip.remote_ip, ctx->ip.remote_port);
+#ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID
+    }
+#endif
   }
   else
 #endif