Skip to content

Commit

Permalink
Add handling of scoped IPv6 link-local literal addresses per RFC 6874
Browse files Browse the repository at this point in the history
in ne_session_create(); but omit the zone ID in Host: headers used
over the wire due to the omission from RFC 9110.

* doc/ref/sess.xml, doc/using.xml,
  src/ne_session.h (ne_session_create): Update docs accordingly.

* src/ne_session.c (set_hostinfo): Parse and handle scoped literal v6
  addresses, but strip the zone from the stored hostname.
  • Loading branch information
notroj committed Jul 28, 2024
1 parent 85451d3 commit 532ba27
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 8 deletions.
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
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

0 comments on commit 532ba27

Please sign in to comment.