From e2ca8408c43275b3cf36ed08f4503732cd8df3a5 Mon Sep 17 00:00:00 2001 From: ambikeesshh Date: Sat, 23 May 2026 00:27:18 +0530 Subject: [PATCH] cf-socket: set scope_id for IPv6 link-local addresses 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 | 68 ++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 65 insertions(+), 3 deletions(-) diff --git a/lib/cf-socket.c b/lib/cf-socket.c index ec158bddb5..eb782b65dc 100644 --- a/lib/cf-socket.c +++ b/lib/cf-socket.c @@ -44,6 +44,12 @@ #include #endif +#ifdef HAVE_IFADDRS_H +#include +#endif +#ifdef HAVE_NET_IF_H +#include +#endif #ifdef __VMS #include #include @@ -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