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
This commit is contained in:
ambikeesshh 2026-05-23 00:27:18 +05:30 committed by Daniel Stenberg
parent 6597e6d461
commit e2ca8408c4
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2

View file

@ -44,6 +44,12 @@
#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