MEDIUM: tools: support specifying explicit address families in str2sa_range()
This change allows one to force the address family in any address parsed
by str2sa_range() by specifying it as a prefix followed by '@' then the
address. Currently supported address prefixes are 'ipv4@', 'ipv6@', 'unix@'.
This also helps forcing resolving for host names (when getaddrinfo is used),
and force the family of the empty address (eg: 'ipv4@' = 0.0.0.0 while
'ipv6@' = ::).
The main benefits is that unix sockets can now get a local name without
being forced to begin with a slash. This is useful during development as
it is no longer necessary to have stats socket sent to /tmp.
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 195f330..4d57585 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -1609,6 +1609,13 @@
listen on. If unset, all IPv4 addresses of the system will be
listened on. The same will apply for '*' or the system's
special address "0.0.0.0". The IPv6 equivalent is '::'.
+ Optionally, an address family prefix may be used before the
+ address to force the family regardless of the address format,
+ which can be useful to specify a path to a unix socket with
+ no slash ('/'). Currently supported prefixes are :
+ - 'ipv4@' -> address is always IPv4
+ - 'ipv6@' -> address is always IPv6
+ - 'unix@' -> address is a path to a local unix socket
<port_range> is either a unique TCP port, or a port range for which the
proxy will accept connections for the IP address specified
@@ -1660,6 +1667,11 @@
bind :80
bind :443 ssl crt /etc/haproxy/site.pem
+ listen http_https_proxy_explicit
+ bind ipv6@:80
+ bind ipv4@public_ssl:443 ssl crt /etc/haproxy/site.pem
+ bind unix@ssl-frontend.sock user root mode 600 accept-proxy
+
See also : "source", "option forwardfor", "unix-bind" and the PROXY protocol
documentation, and section 5 about bind options.
@@ -5072,7 +5084,13 @@
intercepted and haproxy must forward to the original destination
address. This is more or less what the "transparent" keyword does
except that with a server it's possible to limit concurrency and
- to report statistics.
+ to report statistics. Optionally, an address family prefix may be
+ used before the address to force the family regardless of the
+ address format, which can be useful to specify a path to a unix
+ socket with no slash ('/'). Currently supported prefixes are :
+ - 'ipv4@' -> address is always IPv4
+ - 'ipv6@' -> address is always IPv6
+ - 'unix@' -> address is a path to a local unix socket
<port> is an optional port specification. If set, all connections will
be sent to this port. If unset, the same port the client
@@ -5087,6 +5105,7 @@
Examples :
server first 10.1.1.1:1080 cookie first check inter 1000
server second 10.1.1.2:1080 cookie second check inter 1000
+ server transp ipv4@
See also: "default-server", "http-send-name-header" and section 5 about
server options
@@ -5101,8 +5120,16 @@
Arguments :
<addr> is the IPv4 address HAProxy will bind to before connecting to a
server. This address is also used as a source for health checks.
+
The default value of 0.0.0.0 means that the system will select
- the most appropriate address to reach its destination.
+ the most appropriate address to reach its destination. Optionally
+ an address family prefix may be used before the address to force
+ the family regardless of the address format, which can be useful
+ to specify a path to a unix socket with no slash ('/'). Currently
+ supported prefixes are :
+ - 'ipv4@' -> address is always IPv4
+ - 'ipv6@' -> address is always IPv6
+ - 'unix@' -> address is a path to a local unix socket
<port> is an optional port. It is normally not needed but may be useful
in some very specific contexts. The default value of zero means
diff --git a/include/common/standard.h b/include/common/standard.h
index 318e10f..f9f21b0 100644
--- a/include/common/standard.h
+++ b/include/common/standard.h
@@ -212,17 +212,6 @@
extern const char *invalid_domainchar(const char *name);
/*
- * converts <str> to a struct sockaddr_storage* provided by the caller. The
- * string is assumed to contain only an address, no port. The address can be a
- * dotted IPv4 address, an IPv6 address, a host name, or empty or "*" to
- * indicate INADDR_ANY. NULL is returned if the host part cannot be resolved.
- * The return address will only have the address family and the address set,
- * all other fields remain zero. The string is not supposed to be modified.
- * The IPv6 '::' address is IN6ADDR_ANY.
- */
-struct sockaddr_storage *str2ip(const char *str, struct sockaddr_storage *sa);
-
-/*
* converts <str> to a locally allocated struct sockaddr_storage *, and a
* port range consisting in two integers. The low and high end are always set
* even if the port is unspecified, in which case (0,0) is returned. The low
diff --git a/src/cfgparse.c b/src/cfgparse.c
index 2c8faad..3821edb 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -1928,7 +1928,7 @@
if (warnifnotcap(curproxy, PR_CAP_FE, file, linenum, args[0], NULL))
err_code |= ERR_WARN;
- if ( *(args[1]) != '/' && strchr(args[1], ':') == NULL) {
+ if (!*(args[1])) {
Alert("parsing [%s:%d] : '%s' expects {<path>|[addr1]:port1[-end1]}{,[addr]:port[-end]}... as arguments.\n",
file, linenum, args[0]);
err_code |= ERR_ALERT | ERR_FATAL;
diff --git a/src/standard.c b/src/standard.c
index c491534..c670be0 100644
--- a/src/standard.c
+++ b/src/standard.c
@@ -505,6 +505,10 @@
/*
* converts <str> to a struct sockaddr_storage* provided by the caller. The
+ * caller must have zeroed <sa> first, and may have set sa->ss_family to force
+ * parse a specific address format. If the ss_family is 0 or AF_UNSPEC, then
+ * the function tries to guess the address family from the syntax. If the
+ * family is forced and the format doesn't match, an error is returned. The
* string is assumed to contain only an address, no port. The address can be a
* dotted IPv4 address, an IPv6 address, a host name, or empty or "*" to
* indicate INADDR_ANY. NULL is returned if the host part cannot be resolved.
@@ -512,32 +516,36 @@
* all other fields remain zero. The string is not supposed to be modified.
* The IPv6 '::' address is IN6ADDR_ANY.
*/
-struct sockaddr_storage *str2ip(const char *str, struct sockaddr_storage *sa)
+static struct sockaddr_storage *str2ip(const char *str, struct sockaddr_storage *sa)
{
struct hostent *he;
- memset(sa, 0, sizeof(sa));
-
/* Any IPv6 address */
if (str[0] == ':' && str[1] == ':' && !str[2]) {
- sa->ss_family = AF_INET6;
+ if (!sa->ss_family || sa->ss_family == AF_UNSPEC)
+ sa->ss_family = AF_INET6;
+ else if (sa->ss_family != AF_INET6)
+ goto fail;
return sa;
}
- /* Any IPv4 address */
+ /* Any address for the family, defaults to IPv4 */
if (!str[0] || (str[0] == '*' && !str[1])) {
- sa->ss_family = AF_INET;
+ if (!sa->ss_family || sa->ss_family == AF_UNSPEC)
+ sa->ss_family = AF_INET;
return sa;
}
/* check for IPv6 first */
- if (inet_pton(AF_INET6, str, &((struct sockaddr_in6 *)sa)->sin6_addr)) {
+ if ((!sa->ss_family || sa->ss_family == AF_UNSPEC || sa->ss_family == AF_INET6) &&
+ inet_pton(AF_INET6, str, &((struct sockaddr_in6 *)sa)->sin6_addr)) {
sa->ss_family = AF_INET6;
return sa;
}
/* then check for IPv4 */
- if (inet_pton(AF_INET, str, &((struct sockaddr_in *)sa)->sin_addr)) {
+ if ((!sa->ss_family || sa->ss_family == AF_UNSPEC || sa->ss_family == AF_INET) &&
+ inet_pton(AF_INET, str, &((struct sockaddr_in *)sa)->sin_addr)) {
sa->ss_family = AF_INET;
return sa;
}
@@ -545,7 +553,11 @@
/* try to resolve an IPv4/IPv6 hostname */
he = gethostbyname(str);
if (he) {
- sa->ss_family = he->h_addrtype;
+ if (!sa->ss_family || sa->ss_family == AF_UNSPEC)
+ sa->ss_family = he->h_addrtype;
+ else if (sa->ss_family != he->h_addrtype)
+ goto fail;
+
switch (sa->ss_family) {
case AF_INET:
((struct sockaddr_in *)sa)->sin_addr = *(struct in_addr *) *(he->h_addr_list);
@@ -561,13 +573,17 @@
memset(&result, 0, sizeof(result));
memset(&hints, 0, sizeof(hints));
- hints.ai_family = AF_UNSPEC;
+ hints.ai_family = sa->ss_family ? sa->ss_family : AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_flags = AI_PASSIVE;
hints.ai_protocol = 0;
if (getaddrinfo(str, NULL, &hints, &result) == 0) {
- sa->ss_family = result->ai_family;
+ if (!sa->ss_family || sa->ss_family == AF_UNSPEC)
+ sa->ss_family = result->ai_family;
+ else if (sa->ss_family != result->ai_family)
+ goto fail;
+
switch (result->ai_family) {
case AF_INET:
memcpy((struct sockaddr_in *)sa, result->ai_addr, result->ai_addrlen);
@@ -583,7 +599,7 @@
}
#endif
/* unsupported address family */
-
+ fail:
return NULL;
}
@@ -611,10 +627,16 @@
* - "::" => family will be AF_INET6 and address will be IN6ADDR_ANY
* - a host name => family and address will depend on host name resolving.
*
+ * A prefix may be passed in before the address above to force the family :
+ * - "ipv4@" => force address to resolve as IPv4 and fail if not possible.
+ * - "ipv6@" => force address to resolve as IPv6 and fail if not possible.
+ * - "unix@" => force address to be a path to a UNIX socket even if the
+ * path does not start with a '/'
+ *
* Also note that in order to avoid any ambiguity with IPv6 addresses, the ':'
* is mandatory after the IP address even when no port is specified. NULL is
* returned if the address cannot be parsed. The <low> and <high> ports are
- * always initialized if non-null.
+ * always initialized if non-null, even for non-IP families.
*
* If <pfx> is non-null, it is used as a string prefix before any path-based
* address (typically the path to a unix socket).
@@ -623,20 +645,39 @@
{
static struct sockaddr_storage ss;
struct sockaddr_storage *ret = NULL;
- char *str2;
+ char *back, *str2;
char *port1, *port2;
int portl, porth, porta;
portl = porth = porta = 0;
- str2 = strdup(str);
+ str2 = back = strdup(str);
if (str2 == NULL) {
memprintf(err, "out of memory in '%s'\n", __FUNCTION__);
goto out;
}
- if (*str2 == '/') {
- /* unix socket */
+ memset(&ss, 0, sizeof(ss));
+
+ if (strncmp(str2, "unix@", 5) == 0) {
+ str2 += 5;
+ ss.ss_family = AF_UNIX;
+ }
+ else if (strncmp(str2, "ipv4@", 5) == 0) {
+ str2 += 5;
+ ss.ss_family = AF_INET;
+ }
+ else if (strncmp(str2, "ipv6@", 5) == 0) {
+ str2 += 5;
+ ss.ss_family = AF_INET6;
+ }
+ else if (*str2 == '/') {
+ ss.ss_family = AF_UNIX;
+ }
+ else
+ ss.ss_family = AF_UNSPEC;
+
+ if (ss.ss_family == AF_UNIX) {
int prefix_path_len;
int max_path_len;
@@ -652,8 +693,6 @@
goto out;
}
- memset(&ss, 0, sizeof(ss));
- ss.ss_family = AF_UNIX;
if (pfx) {
memcpy(((struct sockaddr_un *)&ss)->sun_path, pfx, prefix_path_len);
strcpy(((struct sockaddr_un *)&ss)->sun_path + prefix_path_len, str2);
@@ -662,7 +701,7 @@
strcpy(((struct sockaddr_un *)&ss)->sun_path, str2);
}
}
- else {
+ else { /* IPv4 and IPv6 */
port1 = strrchr(str2, ':');
if (port1)
*port1++ = '\0';
@@ -705,7 +744,7 @@
*low = portl;
if (high)
*high = porth;
- free(str2);
+ free(back);
return ret;
}