Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add supported for scoped IPv6 link-local address #160

Merged
merged 2 commits into from
Jul 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions doc/ref/sess.xml
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,18 @@ perform any global initialization needed by any libraries used by
linkend="ne_ssl_trust_default_ca"/>).</para>

<para>The <parameter>host</parameter> parameter must follow
the definition of 'host' in <ulink
the definition of host <literal>host</literal> in <ulink
url="https://www.rfc-editor.org/rfc/rfc3986">RFC 3986</ulink>,
which can be an IP-literal or registered (DNS) hostname. Valid
examples of each: <literal>"198.51.100.42"</literal> (IPv4
literal address), <literal>"[2001:db8::42]"</literal> (IPv6
literal, which <emphasis>MUST</emphasis> be enclosed in square
brackets), or <literal>"www.example.com"</literal> (DNS
hostname).</para>
hostname). The <ulink
url="https://www.rfc-editor.org/rfc/rfc6874">RFC 6874</ulink>
syntax for scoped IPv6 link-local literal addresses is also
permitted, for example <literal>"[fe80::1%25eth0]"</literal>.
</para>

<para>The <parameter>scheme</parameter> parameter is used to
determine the URI for resources identified during request
Expand Down
18 changes: 18 additions & 0 deletions doc/using.xml
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,24 @@
header with a value other than <quote>identity</quote> or
<quote>chunked</quote>.</para></sect2>

<sect2>
<title><ulink url="https://datatracker.ietf.org/doc/html/rfc3986">RFC 3986</ulink> Uniform Resource Identifier (URI): Generic Syntax and <ulink url="https://datatracker.ietf.org/doc/html/rfc6874">RFC 6874</ulink>, Representing IPv6 Zone Identifiers in Address Literals and Uniform Resource Identifiers</title>

<para>&neon; parses and handles scoped IPv6 link local literal
addresses passed to <xref linkend="refsess"/> since version
<literal>0.34</literal>, following the syntax in RFC 6874. An
example <literal>host</literal> argument would be
<literal>"[fe80::cafe%25eth0]"</literal> where
<literal>"eth0"</literal> is the scope ID. Since <ulink
url="https://datatracker.ietf.org/doc/html/rfc9110">RFC
9110</ulink> does not reference the extended syntax of scoped
IPv6 literals, and a scope ID has no meaningful interpretation
outside of the client host, it is omitted from the
<literal>Host</literal> header sent over the wire. So the
example URI used here translates to an HTTP/1.1 header field
of <literal>Host: [fe80::cafe]</literal>.</para>
</sect2>

<sect2>
<title>RFC 7616, HTTP Digest Access Authentication</title>

Expand Down
5 changes: 3 additions & 2 deletions macros/neon.m4
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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])
Expand Down
59 changes: 54 additions & 5 deletions src/ne_session.c
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,21 @@ static void set_hostport(struct host_info *host, unsigned int defaultport)
}
}

/* Stores the hostname/port in *info, setting up the "hostport"
* segment correctly. */
#define V6_ADDR_MINLEN strlen("[::1]") /* "[::]" never valid */
#define V6_SCOPE_SEP "%25"
#define V6_SCOPE_SEPLEN (strlen(V6_SCOPE_SEP))
/* Minimum length of link-local address with scope. */
#define V6_SCOPE_MINLEN (strlen("[fe80::%251]"))

/* Stores the hostname/port in *HI, setting up the "hostport" segment
* correctly. RFC 6874 syntax is allowed here but the scope ID is
* stripped from the hostname which is used in the Host header. RFC
* 9110's Host header uses uri-host, which references RFC 3986 and not
* RFC 6874, so it is pedantically correct; the scope ID also has no
* possible interpretation outside of the client host.
*
* TODO: This function also does not propagate parse failures or scope
* mapping failures, which is bad. */
static void set_hostinfo(struct host_info *hi, enum proxy_type type,
const char *hostname, unsigned int port)
{
Expand All @@ -162,12 +175,48 @@ static void set_hostinfo(struct host_info *hi, enum proxy_type type,

hlen = strlen(hi->hostname);

/* IP literal parsing */
/* IP literal parsing. */
ia = ne_iaddr_parse(hi->hostname, ne_iaddr_ipv4);
if (!ia && hlen > 4
if (!ia && hlen >= V6_ADDR_MINLEN
&& hi->hostname[0] == '[' && hi->hostname[hlen-1] == ']') {
char *v6lit = ne_strndup(hi->hostname + 1, hlen-2);
const char *v6end, *v6start = hi->hostname + 1;
char *v6lit, *scope;

/* Parse here, see if there is a Zone ID:
* IPv6addrzb => v6start = IPv6address "%25" ZoneID */

if (hlen >= V6_SCOPE_MINLEN
&& (scope = strstr(v6start, V6_SCOPE_SEP)) != NULL)
v6end = scope;
else
v6end = hi->hostname + hlen - 1; /* trailing ']' */

/* Extract the IPv6-literal part. */
v6lit = ne_strndup(v6start, v6end - v6start);
ia = ne_iaddr_parse(v6lit, ne_iaddr_ipv6);
if (ia && scope) {
/* => scope = "%25" scope "]" */
char *v6scope = ne_strndup(scope + V6_SCOPE_SEPLEN,
strlen(scope) - (V6_SCOPE_SEPLEN + 1));

if (ne_iaddr_set_scope(ia, v6scope) == 0) {
/* Strip scope from hostname since it's used in Host:
* headers and will be rejected. This is safe since
* strlen(scope) is assured by strstr() above. */
*scope++ = ']';
*scope = '\0';
NE_DEBUG(NE_DBG_HTTP, "sess: Using IPv6 scope '%s', "
"hostname rewritten to %s.\n", v6scope,
hi->hostname);
}
else {
NE_DEBUG(NE_DBG_HTTP, "sess: Failed to set IPv6 scope '%s' "
"for address %s.\n", v6scope, v6lit);
}

ne_free(v6scope);
}

ne_free(v6lit);
}

Expand Down
3 changes: 2 additions & 1 deletion src/ne_session.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ typedef struct ne_session_s ne_session;
* server. The host string must follow the definition of 'host' in RFC
* 3986, which can be an IP-literal or registered (DNS) hostname. An
* IPv6 literal address must be enclosed in square brackets (for
* example "[::1]"). */
* example "[::1]"). The RFC 6874 syntax for IPv6 link-local literal
* addresses is also supported, for example "[fe80::1%25eth0]". */
ne_session *ne_session_create(const char *scheme, const char *host,
unsigned int port);

Expand Down
37 changes: 37 additions & 0 deletions src/ne_socket.c
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@
#ifdef HAVE_NETDB_H
#include <netdb.h>
#endif
#ifdef HAVE_NET_IF_H
#include <net/if.h>
#endif

#ifdef WIN32
#include <winsock2.h>
Expand Down Expand Up @@ -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
Expand Down
10 changes: 9 additions & 1 deletion src/ne_socket.h
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand All @@ -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);
Expand Down
9 changes: 6 additions & 3 deletions src/ne_uri.c
Original file line number Diff line number Diff line change
Expand Up @@ -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++;

Expand All @@ -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);

Expand Down
3 changes: 3 additions & 0 deletions src/neon.vers
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,7 @@ NEON_0_33 {

NEON_0_34 {
ne_get_response_location;
ne_iaddr_set_scope;
ne_iaddr_get_scope;
};

19 changes: 19 additions & 0 deletions test/socket.c
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -1620,5 +1638,6 @@ ne_test tests[] = {
T(block_timeout),
T(socks_proxy),
T(fail_socks),
T(scopes),
T(NULL)
};
4 changes: 4 additions & 0 deletions test/uri-tests.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading