MEDIUM: socket: add zero-terminated ABNS alternative

When an abstract unix socket is bound by HAProxy (using "abns@" prefix),
NUL bytes are appended at the end of its path until sun_path is filled
(for a total of 108 characters).

Here we add an alternative to pass only the non-NUL length of that path
to connect/bind calls, such that the effective path of the socket's name
is as humanly written. This may be useful to interconnect with existing
softwares that implement abstract sockets with this logic instead of the
default haproxy one.

This is achieved by implementing the "abnsz" socket prefix (instead of
"abns"), which stands for "zero-terminated ABNS". "abnsz" prefix may be
used anywhere "abns" is. Internally, haproxy uses the custom socket
family (AF_CUST_ABNS vs AF_CUST_ABNSZ) to differentiate default abns
sockets from zero-terminated ones.

Documentation was updated and regtest was added.

Fixes GH issues #977 and #2479

Co-authored-by: Aurelien DARRAGON <adarragon@haproxy.com>
This commit is contained in:
Tristan 2024-10-28 16:23:31 +01:00 committed by Aurelien DARRAGON
parent 43861e3234
commit 18582ede05
5 changed files with 84 additions and 13 deletions

View File

@ -6073,6 +6073,11 @@ bind /<path> [, ...] [param*]
only in log-forward sections.
- 'unix@' -> address is a path to a local unix socket
- 'abns@' -> address is in abstract namespace (Linux only).
- 'abnsz@' -> address is in abstract namespace (Linux only)
but it is explicitly zero-terminated. This means no \0
padding is used to complete sun_path. It is useful to
interconnect with programs that don't implement the
default abns naming logic that haproxy uses.
- 'fd@<n>' -> use file descriptor <n> inherited from the
parent. The fd must be bound and may or may not already
be listening.
@ -6165,11 +6170,12 @@ bind /<path> [, ...] [param*]
listen h3_quic_proxy
bind quic4@10.0.0.1:8888 ssl crt /etc/mycrt
Note: regarding Linux's abstract namespace sockets, HAProxy uses the whole
sun_path length is used for the address length. Some other programs
such as socat use the string length only by default. Pass the option
",unix-tightsocklen=0" to any abstract socket definition in socat to
make it compatible with HAProxy's.
Note: regarding Linux's abstract namespace sockets, "abns" HAProxy sockets
uses the whole sun_path length is used for the address length. Some
other programs such as socat use the string length only by default.
Pass the option ",unix-tightsocklen=0" to any abstract socket
definition in socat to make it compatible with HAProxy's, or use the
"abnsz" HAProxy socket family instead.
See also : "source", "option forwardfor", "unix-bind" and the PROXY protocol
documentation, and section 5 about bind options.
@ -11586,6 +11592,11 @@ server <name> <address>[:[port]] [param*]
- 'ipv6@' -> address is always IPv6
- 'unix@' -> address is a path to a local unix socket
- 'abns@' -> address is in abstract namespace (Linux only)
- 'abnsz@' -> address is in abstract namespace (Linux only)
but it is explicitly zero-terminated. This means no \0
padding is used to complete sun_path. It is useful to
interconnect with programs that don't implement the
default abns naming logic that haproxy uses.
- 'sockpair@' -> address is the FD of a connected unix
socket or of a socketpair. During a connection, the
backend creates a pair of connected sockets, and passes
@ -11620,11 +11631,12 @@ server <name> <address>[:[port]] [param*]
server www1_dc1 "${LAN_DC1}.101:80"
server www1_dc2 "${LAN_DC2}.101:80"
Note: regarding Linux's abstract namespace sockets, HAProxy uses the whole
sun_path length is used for the address length. Some other programs
such as socat use the string length only by default. Pass the option
",unix-tightsocklen=0" to any abstract socket definition in socat to
make it compatible with HAProxy's.
Note: regarding Linux's abstract namespace sockets, "abns" HAProxy sockets
uses the whole sun_path length is used for the address length. Some
other programs such as socat use the string length only by default.
Pass the option ",unix-tightsocklen=0" to any abstract socket
definition in socat to make it compatible with HAProxy's, or use the
"abnsz" HAProxy socket family instead.
See also: "default-server", "http-send-name-header" and section 5 about
server options
@ -11721,6 +11733,9 @@ source <addr>[:<port>] [interface <name>]
- 'ipv6@' -> address is always IPv6
- 'unix@' -> address is a path to a local unix socket
- 'abns@' -> address is in abstract namespace (Linux only)
- 'abnsz@' -> address is in zero-terminated abstract namespace
(Linux only)
You may want to reference some environment variables in the
address parameter, see section 2.3 about environment variables.
@ -28564,6 +28579,9 @@ socket type and the transport method.
'abns@<name>' following <name> is an abstract namespace (Linux only).
'abnsz@<name>' following <name> is a zero-terminated abstract namespace
(Linux only).
'fd@<n>' following address is a file descriptor <n> inherited from the
parent. The fd must be bound and may or may not already be
listening.

View File

@ -785,8 +785,15 @@ static inline int get_addr_len(const struct sockaddr_storage *addr)
return sizeof(struct sockaddr_in6);
case AF_UNIX:
case AF_CUST_ABNS:
case AF_CUST_ABNSZ:
return sizeof(struct sockaddr_un);
case AF_CUST_ABNSZ:
{
const struct sockaddr_un *un = (struct sockaddr_un *)addr;
/* stop at first NULL-byte */
return offsetof(struct sockaddr_un, sun_path) + 1 +
strnlen2(un->sun_path + 1, sizeof(un->sun_path) - 1);
}
}
return 0;
}

View File

@ -0,0 +1,37 @@
varnishtest "Abstract unix socket - zero terminated"
feature ignore_unknown_macro
feature cmd "command -v curl"
# abns@ sockets are not available on freebsd
#EXCLUDE_TARGETS=freebsd,osx,generic
#REGTEST_TYPE=devel
haproxy h1 -W -S -conf {
global
stats socket "${tmpdir}/h1/stats" level admin expose-fd listeners
defaults
mode http
log global
option httplog
timeout connect "${HAPROXY_TEST_TIMEOUT-5s}"
timeout client "${HAPROXY_TEST_TIMEOUT-5s}"
timeout server "${HAPROXY_TEST_TIMEOUT-5s}"
listen testme
bind "fd@${testme}"
server f2 abnsz@hap-f2
frontend f2
bind abnsz@hap-f2
http-request return status 200 content-type text/plain string "ok"
} -start
client c1 -connect ${h1_testme_sock} {
txreq -url "/"
rxresp
} -run
shell {
curl -sfS --abstract-unix-socket hap-f2 "http://host/" | grep "ok"
}

View File

@ -2684,13 +2684,22 @@ static void conn_calculate_hash_sockaddr(const struct sockaddr_storage *ss,
break;
case AF_CUST_ABNS:
case AF_CUST_ABNSZ:
conn_hash_update(hash,
&((struct sockaddr_un *)ss)->sun_path,
sizeof(((struct sockaddr_un *)ss)->sun_path),
hash_flags, param_type_addr);
break;
case AF_CUST_ABNSZ:
{
const struct sockaddr_un *un = (struct sockaddr_un *)ss;
conn_hash_update(hash,
&un->sun_path,
1 + strnlen2(un->sun_path + 1, sizeof(un->sun_path) - 1),
hash_flags, param_type_addr);
break;
}
case AF_CUST_SOCKPAIR:
/* simply hash the fd */
conn_hash_update(hash,

View File

@ -362,7 +362,7 @@ int sock_unix_bind_receiver(struct receiver *rx, char **errmsg)
goto bind_close_return;
}
if (!ext && bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
if (!ext && bind(fd, (struct sockaddr *)&addr, get_addr_len(&rx->addr)) < 0) {
/* note that bind() creates the socket <tempname> on the file system */
if (errno == EADDRINUSE) {
/* the old process might still own it, let's retry */