diff --git a/doc/ref/sess.xml b/doc/ref/sess.xml index 31135f99..4bd3770d 100644 --- a/doc/ref/sess.xml +++ b/doc/ref/sess.xml @@ -64,14 +64,18 @@ perform any global initialization needed by any libraries used by linkend="ne_ssl_trust_default_ca"/>). The host parameter must follow - the definition of 'host' in host in RFC 3986, which can be an IP-literal or registered (DNS) hostname. Valid examples of each: "198.51.100.42" (IPv4 literal address), "[2001:db8::42]" (IPv6 literal, which MUST be enclosed in square brackets), or "www.example.com" (DNS - hostname). + hostname). The RFC 6874 + syntax for scoped IPv6 link-local literal addresses is also + permitted, for example "[fe80::1%25eth0]". + The scheme parameter is used to determine the URI for resources identified during request diff --git a/doc/using.xml b/doc/using.xml index f38d63d7..abc9bbc6 100644 --- a/doc/using.xml +++ b/doc/using.xml @@ -103,6 +103,24 @@ header with a value other than identity or chunked. + + <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 + + &neon; parses and handles scoped IPv6 link local literal + addresses passed to since version + 0.34, following the syntax in RFC 6874. An + example host argument would be + "[fe80::cafe%25eth0]" where + "eth0" is the scope ID. Since RFC + 9110 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 + Host header sent over the wire. So the + example URI used here translates to an HTTP/1.1 header field + of Host: [fe80::cafe]. + + RFC 7616, HTTP Digest Access Authentication diff --git a/src/ne_session.c b/src/ne_session.c index 5e1ee363..9d7aff25 100644 --- a/src/ne_session.c +++ b/src/ne_session.c @@ -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) { @@ -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); } diff --git a/src/ne_session.h b/src/ne_session.h index 992aaecd..e32bedf8 100644 --- a/src/ne_session.h +++ b/src/ne_session.h @@ -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);