Welcome! Log In Create A New Profile

Advanced

Re: [PATCH] Avoiding mixed socket families in PROXY protocol v1 (ticket #2594)

Sergey Kandaurov
March 06, 2024 09:52AM
On Thu, Feb 22, 2024 at 07:17:26PM +0400, Roman Arutyunyan wrote:
> Hi,
>
> On Thu, Feb 22, 2024 at 01:59:25AM +0000, J Carter wrote:
> > Hello Roman,
> >
> > On Wed, 21 Feb 2024 17:29:52 +0400
> > Roman Arutyunyan <arut@nginx.com> wrote:
> >
> > > Hi,
> > >
> >

> > > On Wed, Jan 24, 2024 at 12:03:06AM +0300, Maxim Dounin wrote:
> > > > [..]
> > > > Also, as suggested, using the server address as obtained via PROXY
> > > > protocol from the client might be a better solution as long as the
> > > > client address was set via PROXY protocol (regardless of whether
> > > > address families match or not), and what users expect from the
> > > > "proty_protocol on;" when chaining stream proxies in the first
> > > > place.
> >
> > > Checking whether the address used in PROXY writer is in fact the address
> > > that was passed in the PROXY header, is complicated. This will either require
> > > setting a flag when PROXY address is set by realip, which is ugly.
> > > Another approach is checking if the client address written to a PROXY header
> > > matches the client address in the received PROXY header. However since
> > > currently PROXY protocol addresses are stored as text, and not all addresses
> > > have unique text repersentations, this approach would require refactoring all
> > > PROXY protocol code + realip modules to switch from text to sockaddr.

Checking for the realip context should do the trick, it is created
once the client address was successfully substituted. We could then
parse the text repr of server address in the PROXY header and use it
in PROXY writer. This will allow not to break API.

Something like this should work called from the stream proxy module,
it allows to preserve an original server address in the PROXY header
keeping both addresses unmutated
(which seems to be the best way to solve this problem).

: extern ngx_module_t ngx_stream_realip_module;
:
: if (ngx_stream_get_module_ctx(s, ngx_stream_realip_module)) {
:
: local_sockaddr = c->local_sockaddr;
: local_socklen = c->local_socklen;
:
: if (ngx_parse_addr(c->pool, &addr, c->proxy_protocol->dst_addr.data,
: c->proxy_protocol->dst_addr.len)
: != NGX_OK)
: {
: ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR);
: return;
: }
:
: ngx_inet_set_port(addr.sockaddr, c->proxy_protocol->dst_port);
:
: c->local_sockaddr = addr.sockaddr;
: c->local_socklen = addr.socklen;
: }
:
: # call ngx_proxy_protocol_write(), other existing stuff
:
: if (ngx_stream_get_module_ctx(s, ngx_stream_realip_module)) {
: c->local_sockaddr = local_sockaddr;
: c->local_socklen = local_socklen;
: }

Can't say I am excited from this code, though.

Another way is stick to the INADDR_ANY plan removing the server
address part from the PROXY header if not applicable.

> > >
> > > I suggest that we follow the first plan (INADDR_ANY etc).

I agree in general, but see below.

> > >
> > > > [...]
> > >
> > > Updated patch attached.
> > >

> # HG changeset patch
> # User Roman Arutyunyan <arut@nginx.com>
> # Date 1708522464 -14400
> # Wed Feb 21 17:34:24 2024 +0400
> # Node ID 2d9bb7b49d64576fa29a673133129f16de3cfbbe
> # Parent 44da04c2d4db94ad4eefa84b299e07c5fa4a00b9
> Avoiding mixed socket families in PROXY protocol v1 (ticket #2010).
>
> When using realip module, remote and local addresses of a connection can belong
> to different address families. This previously resulted in generating PROXY
> protocol headers like this:
>
> PROXY TCP4 127.0.0.1 unix:/tmp/nginx1.sock 55544 0

Well, UNIX-domain socket addresses are somewhat rather exsotic,
especially in the PROXY protocol. I'd use an example with IPv4/IPv6.

>
> The PROXY protocol v1 specification does not allow mixed families. The change
> substitutes server address with zero address in this case:
>
> PROXY TCP4 127.0.0.1 0.0.0.0 55544 0
>
> As an alternative, "PROXY UNKNOWN" header could be used, which unlike this
> header does not contain any useful information about the client.
>
> Also, the above mentioned format for unix socket address is not specified in
> PROXY protocol v1 and is a by-product of internal nginx representation of it.
> The change eliminates such addresses from PROXY protocol headers as well.
>
> diff --git a/src/core/ngx_proxy_protocol.c b/src/core/ngx_proxy_protocol.c
> --- a/src/core/ngx_proxy_protocol.c
> +++ b/src/core/ngx_proxy_protocol.c
> @@ -279,7 +279,13 @@ ngx_proxy_protocol_read_port(u_char *p,
> u_char *
> ngx_proxy_protocol_write(ngx_connection_t *c, u_char *buf, u_char *last)
> {
> - ngx_uint_t port, lport;
> + socklen_t local_socklen;
> + ngx_uint_t port, lport;
> + struct sockaddr *local_sockaddr;
> + struct sockaddr_in sin;
> +#if (NGX_HAVE_INET6)
> + struct sockaddr_in6 sin6;
> +#endif
>
> if (last - buf < NGX_PROXY_PROTOCOL_V1_MAX_HEADER) {
> ngx_log_error(NGX_LOG_ALERT, c->log, 0,
> @@ -312,11 +318,35 @@ ngx_proxy_protocol_write(ngx_connection_
>
> *buf++ = ' ';
>
> - buf += ngx_sock_ntop(c->local_sockaddr, c->local_socklen, buf, last - buf,
> - 0);
> + if (c->sockaddr->sa_family == c->local_sockaddr->sa_family) {
> + local_sockaddr = c->local_sockaddr;
> + local_socklen = c->local_socklen;
> +
> + } else {
> + switch (c->sockaddr->sa_family) {
> +
> +#if (NGX_HAVE_INET6)
> + case AF_INET6:
> + ngx_memzero(&sin6, sizeof(struct sockaddr_in6));
> + sin6.sin6_family = AF_INET6;
> + local_sockaddr = (struct sockaddr *) &sin6;
> + local_socklen = sizeof(struct sockaddr_in6);
> + break;
> +#endif
> +
> + default: /* AF_INET */
> + ngx_memzero(&sin, sizeof(struct sockaddr));

Note that although wildcard adresses are typically implemented
using all-zeroes, this is not obligated by standards and hence
is an implementation detail. POSIX mandates just the following:

: The <netinet/in.h> header shall define the following symbolic
: constant for use as a local address in the structure passed to bind():
:
: INADDR_ANY
: IPv4 wildcard address.

(Note: it is "IPv4 local host address." in previous editions.)

: The <netinet/in.h> header shall declare the following external
: variable:
:
: const struct in6_addr in6addr_any
:
: This variable is initialized by the system to contain the wildcard
: IPv6 address. The <netinet/in.h> header also defines the
: IN6ADDR_ANY_INIT macro. This macro must be constant at compile time
: and can be used to initialize a variable of type struct in6_addr to
: the IPv6 wildcard address.

: RATIONALE
:
: The INADDR_ANY and INADDR_BROADCAST values are byte-order-neutral
: and thus their byte order is not specified. Many implementations
: have additional constants as extensions, such as INADDR_LOOPBACK,
: that are not byte-order-neutral. Traditionally, these constants are
: in host byte order, requiring the use of htonl() when using them in
: a sockaddr_in structure.

So it seems quite legitimate to use a different binary representation.
To be on the safe side (and improve code readability), it makes sense
to consistently set wildcard addresses explicitly, using INADDR_ANY
and in6addr_any. It is not so important in this patch though, because
addresses do not cross the kernel, used solely for ngx_sock_ntop().

> + sin.sin_family = AF_INET;
> + local_sockaddr = (struct sockaddr *) &sin;
> + local_socklen = sizeof(struct sockaddr_in);
> + break;
> + }
> + }
> +
> + buf += ngx_sock_ntop(local_sockaddr, local_socklen, buf, last - buf, 0);
>
> port = ngx_inet_get_port(c->sockaddr);
> - lport = ngx_inet_get_port(c->local_sockaddr);
> + lport = ngx_inet_get_port(local_sockaddr);
>
> return ngx_slprintf(buf, last, " %ui %ui" CRLF, port, lport);
> }

Picking INADDR_ANY (and IPv6 equivalent) to substitute non-applicable
addresses due to mismatching address family may be unnecessarily
strict hiding addresses that can be represented in a different family.

IPv4 address can be represented in the IPv4-mapped IPv6 address format.
IPv6 address can be represented in the IPv4 format if it's IPv4-mapped.
Such format is supported in various nginx modules and may be enabled
by "listen ... ipv6only=off" to accept IPv4-mapped IPv6 addresses on a
wildcard listen socket.

For example, if nginx receives "PROXY TCP6" on "listen 127.0.0.1:8081"
and is configured to send PROXY protocol further in the stream proxy
chain, the server address will be updated to "::ffff:127.0.0.1 8081".
And opposite, receiving "PROXY TCP4" on "listen [::]:8082 ipv6only=off"
will update the PROXY header server part to "127.0.0.1 8082".

That said, something like this should work on top off your change:

diff --git a/src/core/ngx_proxy_protocol.c b/src/core/ngx_proxy_protocol.c
--- a/src/core/ngx_proxy_protocol.c
+++ b/src/core/ngx_proxy_protocol.c
@@ -279,12 +279,13 @@ ngx_proxy_protocol_read_port(u_char *p,
u_char *
ngx_proxy_protocol_write(ngx_connection_t *c, u_char *buf, u_char *last)
{
- socklen_t local_socklen;
- ngx_uint_t port, lport;
- struct sockaddr *local_sockaddr;
- struct sockaddr_in sin;
+ socklen_t local_socklen;
+ ngx_uint_t port, lport;
+ ngx_sockaddr_t sa;
+ struct sockaddr *local_sockaddr;
#if (NGX_HAVE_INET6)
- struct sockaddr_in6 sin6;
+ in_addr_t addr4;
+ struct in6_addr *addr6;
#endif

if (last - buf < NGX_PROXY_PROTOCOL_V1_MAX_HEADER) {
@@ -323,22 +324,62 @@ ngx_proxy_protocol_write(ngx_connection_
local_socklen = c->local_socklen;

} else {
+ local_sockaddr = &sa.sockaddr;
+
+ ngx_memzero(&sa, sizeof(ngx_sockaddr_t));
+ sa.sockaddr.sa_family = c->sockaddr->sa_family;
+ ngx_inet_set_port(&sa.sockaddr, ngx_inet_get_port(c->local_sockaddr));
+
switch (c->sockaddr->sa_family) {

#if (NGX_HAVE_INET6)
case AF_INET6:
- ngx_memzero(&sin6, sizeof(struct sockaddr_in6));
- sin6.sin6_family = AF_INET6;
- local_sockaddr = (struct sockaddr *) &sin6;
local_socklen = sizeof(struct sockaddr_in6);
+
+ if (c->local_sockaddr->sa_family == AF_INET) {
+ addr4 = ntohl(((struct sockaddr_in *) c->local_sockaddr)
+ ->sin_addr.s_addr);
+
+ addr6 = &sa.sockaddr_in6.sin6_addr;
+ addr6->s6_addr[10] = 0xff;
+ addr6->s6_addr[11] = 0xff;
+ addr6->s6_addr[12] = addr4 >> 24;
+ addr6->s6_addr[13] = addr4 >> 16;
+ addr6->s6_addr[14] = addr4 >> 8;
+ addr6->s6_addr[15] = addr4;
+
+ } else {
+ sa.sockaddr_in6.sin6_addr = in6addr_any;
+ }
+
break;
#endif

default: /* AF_INET */
- ngx_memzero(&sin, sizeof(struct sockaddr));
- sin.sin_family = AF_INET;
- local_sockaddr = (struct sockaddr *) &sin;
local_socklen = sizeof(struct sockaddr_in);
+
+#if (NGX_HAVE_INET6)
+ if (c->local_sockaddr->sa_family == AF_INET6) {
+ addr6 = &((struct sockaddr_in6 *) c->local_sockaddr)->sin6_addr;
+
+ if (IN6_IS_ADDR_V4MAPPED(addr6)) {
+ addr4 = addr6->s6_addr[12] << 24;
+ addr4 += addr6->s6_addr[13] << 16;
+ addr4 += addr6->s6_addr[14] << 8;
+ addr4 += addr6->s6_addr[15];
+
+ sa.sockaddr_in.sin_addr.s_addr = htonl(addr4);
+
+ } else {
+ sa.sockaddr_in.sin_addr.s_addr = INADDR_ANY;
+ }
+
+ } else
+#endif
+ {
+ sa.sockaddr_in.sin_addr.s_addr = INADDR_ANY;
+ }
+
break;
}
}
_______________________________________________
nginx-devel mailing list
nginx-devel@nginx.org
https://mailman.nginx.org/mailman/listinfo/nginx-devel
Subject Author Views Posted

[PATCH] Avoiding mixed socket families in PROXY protocol v1 (ticket #2594)

Roman Arutyunyan 463 January 22, 2024 05:52AM

Re: [PATCH] Avoiding mixed socket families in PROXY protocol v1 (ticket #2594)

Maxim Dounin 116 January 22, 2024 07:00AM

Re: [PATCH] Avoiding mixed socket families in PROXY protocol v1 (ticket #2594)

Roman Arutyunyan 98 January 22, 2024 10:50AM

Re: [PATCH] Avoiding mixed socket families in PROXY protocol v1 (ticket #2594)

Maxim Dounin 113 January 23, 2024 04:04PM

Re: [PATCH] Avoiding mixed socket families in PROXY protocol v1 (ticket #2594)

Roman Arutyunyan 111 February 21, 2024 08:32AM

Re: [PATCH] Avoiding mixed socket families in PROXY protocol v1 (ticket #2594)

J Carter 116 February 21, 2024 09:00PM

Re: [PATCH] Avoiding mixed socket families in PROXY protocol v1 (ticket #2594)

Roman Arutyunyan 124 February 22, 2024 10:18AM

Re: [PATCH] Avoiding mixed socket families in PROXY protocol v1 (ticket #2594)

Sergey Kandaurov 105 March 06, 2024 09:52AM

Re: [PATCH] Avoiding mixed socket families in PROXY protocol v1 (ticket #2594)

Roman Arutyunyan 127 March 11, 2024 08:46AM

Re: [PATCH] Avoiding mixed socket families in PROXY protocol v1 (ticket #2594)

Sergey Kandaurov 116 March 13, 2024 01:10PM

Re: [PATCH] Avoiding mixed socket families in PROXY protocol v1 (ticket #2594)

Roman Arutyunyan 133 March 21, 2024 10:58AM



Sorry, you do not have permission to post/reply in this forum.

Online Users

Guests: 185
Record Number of Users: 8 on April 13, 2023
Record Number of Guests: 500 on July 15, 2024
Powered by nginx      Powered by FreeBSD      PHP Powered      Powered by MariaDB      ipv6 ready