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.
This commit is contained in:
Willy Tarreau 2013-03-10 21:32:12 +01:00
parent 902636fd73
commit 24709286fe
4 changed files with 90 additions and 35 deletions

View File

@ -1609,6 +1609,13 @@ bind /<path> [, ...] [param*]
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 /<path> [, ...] [param*]
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 @@ server <name> <address>[:[port]] [param*]
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 @@ server <name> <address>[:[port]] [param*]
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 @@ source <addr>[:<port>] [interface <name>]
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

View File

@ -211,17 +211,6 @@ extern const char *invalid_char(const char *name);
*/
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

View File

@ -1928,7 +1928,7 @@ int cfg_parse_listen(const char *file, int linenum, char **args, int kwm)
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;

View File

@ -505,6 +505,10 @@ const char *invalid_domainchar(const char *name) {
/*
* 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 @@ const char *invalid_domainchar(const char *name) {
* 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 @@ struct sockaddr_storage *str2ip(const char *str, struct sockaddr_storage *sa)
/* 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 @@ struct sockaddr_storage *str2ip(const char *str, struct sockaddr_storage *sa)
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 @@ struct sockaddr_storage *str2ip(const char *str, struct sockaddr_storage *sa)
}
#endif
/* unsupported address family */
fail:
return NULL;
}
@ -611,10 +627,16 @@ struct sockaddr_storage *str2ip(const char *str, struct sockaddr_storage *sa)
* - "::" => 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 @@ struct sockaddr_storage *str2sa_range(const char *str, int *low, int *high, char
{
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 @@ struct sockaddr_storage *str2sa_range(const char *str, int *low, int *high, char
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 @@ struct sockaddr_storage *str2sa_range(const char *str, int *low, int *high, char
strcpy(((struct sockaddr_un *)&ss)->sun_path, str2);
}
}
else {
else { /* IPv4 and IPv6 */
port1 = strrchr(str2, ':');
if (port1)
*port1++ = '\0';
@ -705,7 +744,7 @@ struct sockaddr_storage *str2sa_range(const char *str, int *low, int *high, char
*low = portl;
if (high)
*high = porth;
free(str2);
free(back);
return ret;
}