From 85451d37cd584fd9608e879e221977b8987dbf0a Mon Sep 17 00:00:00 2001 From: Joe Orton Date: Sun, 28 Jul 2024 14:05:13 +0100 Subject: [PATCH] Add low-level handling of scoped IPv6 link-local addresses: * src/ne_socket.c (ne_iaddr_set_scope, ne_iaddr_get_scope): New functions. * src/ne_socket.h: Declare above; note that ne_iaddr_cmp() ignores scopes. * macros/neon.m4 (NEON_COMMON_CHECKS): Check for if_nametoindex & if_indextoname functions, . * src/neon.vers: Add new functions for 0.34. * test/uri-tests.c (parse): Test parsing RFC 6874 syntax. * src/ne_uri.c (ne_uri_parse): Comment updates only. * test/socket.c (scopes): Add minimal tests. --- macros/neon.m4 | 5 +++-- src/ne_socket.c | 37 +++++++++++++++++++++++++++++++++++++ src/ne_socket.h | 10 +++++++++- src/ne_uri.c | 9 ++++++--- src/neon.vers | 3 +++ test/socket.c | 19 +++++++++++++++++++ test/uri-tests.c | 4 ++++ 7 files changed, 81 insertions(+), 6 deletions(-) diff --git a/macros/neon.m4 b/macros/neon.m4 index 7ae27a70..b0873f38 100644 --- a/macros/neon.m4 +++ b/macros/neon.m4 @@ -672,7 +672,7 @@ AC_REQUIRE([AC_FUNC_STRERROR_R]) AC_CHECK_HEADERS([sys/time.h limits.h sys/select.h arpa/inet.h libintl.h \ signal.h sys/socket.h netinet/in.h netinet/tcp.h netdb.h sys/poll.h \ - sys/limits.h fcntl.h iconv.h],,, + sys/limits.h fcntl.h iconv.h net/if.h],,, [AC_INCLUDES_DEFAULT /* netinet/tcp.h requires netinet/in.h on some platforms. */ #ifdef HAVE_NETINET_IN_H @@ -697,7 +697,8 @@ NE_LARGEFILE AC_REPLACE_FUNCS(strcasecmp) AC_CHECK_FUNCS([signal setvbuf setsockopt stpcpy poll fcntl getsockopt \ - explicit_bzero sendmsg gettimeofday gmtime_r]) + explicit_bzero sendmsg gettimeofday gmtime_r if_nametoindex \ + if_indextoname]) if test "x${ac_cv_func_poll}${ac_cv_header_sys_poll_h}y" = "xyesyesy"; then AC_DEFINE([NE_USE_POLL], 1, [Define if poll() should be used]) diff --git a/src/ne_socket.c b/src/ne_socket.c index 4a382c9e..e610b785 100644 --- a/src/ne_socket.c +++ b/src/ne_socket.c @@ -56,6 +56,9 @@ #ifdef HAVE_NETDB_H #include #endif +#ifdef HAVE_NET_IF_H +#include +#endif #ifdef WIN32 #include @@ -1258,6 +1261,40 @@ int ne_iaddr_reverse(const ne_inet_addr *ia, char *buf, size_t bufsiz) #endif } +int ne_iaddr_set_scope(ne_inet_addr *ia, const char *scope) +{ +#ifdef HAVE_IF_NAMETOINDEX + unsigned int idx; + + if (ia->ai_family != AF_INET6) return EINVAL; + + idx = if_nametoindex(scope); + if (idx) { + struct sockaddr_in6 *in6 = SACAST(in6, ia->ai_addr); + in6->sin6_scope_id = idx; + return 0; + } + else + return errno; +#else + return ENODEV; +#endif +} + +char *ne_iaddr_get_scope(const ne_inet_addr *ia) +{ +#ifdef HAVE_IF_INDEXTONAME + struct sockaddr_in6 *in6 = SACAST(in6, ia->ai_addr); + char buf[IF_NAMESIZE]; + + if (ia->ai_family != AF_INET6) return NULL; + + if (in6->sin6_scope_id && if_indextoname(in6->sin6_scope_id, buf) == buf) + return ne_strdup(buf); +#endif + return NULL; +} + void ne_addr_destroy(ne_sock_addr *addr) { #ifdef USE_GETADDRINFO diff --git a/src/ne_socket.h b/src/ne_socket.h index 9fecf465..53b9a0f5 100644 --- a/src/ne_socket.h +++ b/src/ne_socket.h @@ -113,7 +113,9 @@ typedef enum { ne_inet_addr *ne_iaddr_make(ne_iaddr_type type, const unsigned char *raw); /* Compare two network address objects i1 and i2; returns zero if they - * are equivalent or non-zero otherwise. */ + * are equivalent or non-zero otherwise. For an IPv6 literal, the + * scope is ignored by this function if set, so must be compared + * seperately if required. */ int ne_iaddr_cmp(const ne_inet_addr *i1, const ne_inet_addr *i2); /* Return the type of the given network address object. */ @@ -139,6 +141,12 @@ int ne_iaddr_reverse(const ne_inet_addr *ia, char *buf, size_t bufsiz); * non-NULL, return value must be freed using ne_iaddr_free. */ ne_inet_addr *ne_iaddr_parse(const char *addr, ne_iaddr_type type); +/* Set the scope_id for an IPv6 address from 'scope'. */ +int ne_iaddr_set_scope(ne_inet_addr *ia, const char *scope); + +/* Returns the scope_id for an IPv6 address. */ +char *ne_iaddr_get_scope(const ne_inet_addr *ia); + /* Destroy a network address object created using ne_iaddr_make or * ne_iaddr_parse. */ void ne_iaddr_free(ne_inet_addr *addr); diff --git a/src/ne_uri.c b/src/ne_uri.c index 4a9ce0e5..68559f25 100644 --- a/src/ne_uri.c +++ b/src/ne_uri.c @@ -198,6 +198,9 @@ int ne_uri_parse(const char *uri, ne_uri *parsed) if (s[0] == '[') { p = s + 1; + /* This allows any characters in IP-literal which is too + * broad, however e.g. zone identifiers per RFC 6874 are + * allowed through. */ while (*p != ']' && p < pa) p++; @@ -206,14 +209,14 @@ int ne_uri_parse(const char *uri, ne_uri *parsed) return -1; } - p++; /* => p = colon */ + p++; /* => p = [ ':' port ] */ } else { - /* Find the colon. */ + /* Find any colon before reaching path-abempty. */ p = s; while (*p != ':' && p < pa) p++; } - /* => p = colon */ + /* => p = [ ":" port ] */ parsed->host = ne_strndup(s, p - s); diff --git a/src/neon.vers b/src/neon.vers index 59c8488f..51bde7f4 100644 --- a/src/neon.vers +++ b/src/neon.vers @@ -42,4 +42,7 @@ NEON_0_33 { NEON_0_34 { ne_get_response_location; + ne_iaddr_set_scope; + ne_iaddr_get_scope; }; + diff --git a/test/socket.c b/test/socket.c index 39ca66b4..50299158 100644 --- a/test/socket.c +++ b/test/socket.c @@ -1560,6 +1560,24 @@ static int fail_socks(void) return OK; } +static int scopes(void) +{ + ne_inet_addr *ia; + +#ifdef TEST_IPV6 + ia = ne_iaddr_parse("fe80::cafe", ne_iaddr_ipv6); +#else + ia = ne_iaddr_parse("127.0.0.1", ne_iaddr_ipv4); +#endif + + (void) ne_iaddr_set_scope(ia, "foobar"); + (void) ne_iaddr_get_scope(ia); + + ne_iaddr_free(ia); + + return OK; +} + ne_test tests[] = { T(multi_init), T_LEAKY(resolve), @@ -1620,5 +1638,6 @@ ne_test tests[] = { T(block_timeout), T(socks_proxy), T(fail_socks), + T(scopes), T(NULL) }; diff --git a/test/uri-tests.c b/test/uri-tests.c index d2e46006..060c8952 100644 --- a/test/uri-tests.c +++ b/test/uri-tests.c @@ -321,6 +321,10 @@ static int parse(void) { "http://[::1]:8080/bar", "http", "[::1]", 8080, "/bar", NULL, NULL, NULL }, { "ftp://[feed::cafe]:555", "ftp", "[feed::cafe]", 555, "/", NULL, NULL, NULL }, + /* Test RFC 6874 syntax, an extension of RFC 3987. */ + { "http://[fe80::cafe%25eth0]:555", "http", "[fe80::cafe%25eth0]", 555, "/", NULL, NULL, NULL }, + { "http://[fe80::cafe%251]:555", "http", "[fe80::cafe%251]", 555, "/", NULL, NULL, NULL }, + { "DAV:", "DAV", NULL, 0, "", NULL, NULL, NULL }, /* Some odd cases: heir-part and relative-ref will both match