From 24709286fed4b5154cd620d5bc7e767a950f0a3c Mon Sep 17 00:00:00 2001 From: Willy Tarreau Date: Sun, 10 Mar 2013 21:32:12 +0100 Subject: [PATCH] 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. --- doc/configuration.txt | 31 ++++++++++++++- include/common/standard.h | 11 ------ src/cfgparse.c | 2 +- src/standard.c | 81 +++++++++++++++++++++++++++++---------- 4 files changed, 90 insertions(+), 35 deletions(-) diff --git a/doc/configuration.txt b/doc/configuration.txt index 195f330ec..4d575856e 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -1609,6 +1609,13 @@ bind / [, ...] [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 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 / [, ...] [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
[:[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 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
[:[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 [:] [interface ] Arguments : 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 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 318e10f9e..f9f21b06a 100644 --- a/include/common/standard.h +++ b/include/common/standard.h @@ -211,17 +211,6 @@ extern const char *invalid_char(const char *name); */ extern const char *invalid_domainchar(const char *name); -/* - * converts 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 to a locally allocated struct sockaddr_storage *, and a * port range consisting in two integers. The low and high end are always set diff --git a/src/cfgparse.c b/src/cfgparse.c index 2c8faadb3..3821edb77 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -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 {|[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 c491534eb..c670be0ac 100644 --- a/src/standard.c +++ b/src/standard.c @@ -505,6 +505,10 @@ const char *invalid_domainchar(const char *name) { /* * converts to a struct sockaddr_storage* provided by the caller. The + * caller must have zeroed 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 and ports are - * always initialized if non-null. + * always initialized if non-null, even for non-IP families. * * If 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; }