]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
[MEDIUM] config: rework the IPv4/IPv6 address parser to support host-only addresses
authorWilly Tarreau <w@1wt.eu>
Fri, 4 Mar 2011 14:31:53 +0000 (15:31 +0100)
committerWilly Tarreau <w@1wt.eu>
Wed, 23 Mar 2011 18:01:18 +0000 (19:01 +0100)
The parser now distinguishes between pure addresses and address:port. This
is useful for some config items where only an address is required.

Raw IPv6 addresses are now parsed, but IPv6 host name resolution is still not
handled (gethostbyname does not resolve IPv6 names to addresses).

include/common/standard.h
src/cfgparse.c
src/standard.c

index 7cfd5e225b4e8724b34f0b9694d73b72f847aeb4..6675e47ac7f91589c2106255128b8c8d928dcb2c 100644 (file)
@@ -154,22 +154,39 @@ extern const char *invalid_domainchar(const char *name);
 struct sockaddr_un *str2sun(const char *str);
 
 /*
- * converts <str> to a struct sockaddr_in* which is locally allocated.
- * The format is "addr:port", where "addr" can be a dotted IPv4 address,
- * a host name, or empty or "*" to indicate INADDR_ANY.
+ * converts <str> to a struct sockaddr_storage* which is locally allocated. 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 *str2sa(char *str);
+struct sockaddr_storage *str2ip(const char *str);
 
 /*
- * converts <str> to a struct sockaddr_in* which is locally allocated, and a
+ * converts <str> to a locally allocated struct sockaddr_storage *.
+ * The format is "addr[:[port]]", where "addr" can be a dotted IPv4 address, an
+ * IPv6 address, a host name, or empty or "*" to indicate INADDR_ANY. If an IPv6
+ * address wants to ignore port, it must be terminated by a trailing colon (':').
+ * The IPv6 '::' address is IN6ADDR_ANY, so in order to bind to a given port on
+ * IPv6, use ":::port". NULL is returned if the host part cannot be resolved.
+ */
+struct sockaddr_storage *str2sa(const char *str);
+
+/*
+ * 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
- * port is set in the sockaddr_in. Thus, it is enough to check the size of the
+ * port is set in the sockaddr. Thus, it is enough to check the size of the
  * returned range to know if an array must be allocated or not. The format is
- * "addr[:port[-port]]", where "addr" can be a dotted IPv4 address, a host
- * name, or empty or "*" to indicate INADDR_ANY.
+ * "addr[:[port[-port]]]", where "addr" can be a dotted IPv4 address, an IPv6
+ * address, a host name, or empty or "*" to indicate INADDR_ANY. If an IPv6
+ * address wants to ignore port, it must be terminated by a trailing colon (':').
+ * The IPv6 '::' address is IN6ADDR_ANY, so in order to bind to a given port on
+ * IPv6, use ":::port". NULL is returned if the host part cannot be resolved.
  */
-struct sockaddr_storage *str2sa_range(char *str, int *low, int *high);
+struct sockaddr_storage *str2sa_range(const char *str, int *low, int *high);
 
 /* converts <str> to a struct in_addr containing a network mask. It can be
  * passed in dotted form (255.255.255.0) or in CIDR form (24). It returns 1
index 9cc23d138f8656674ef7fbd54645a9c4b3f7acea..dcea0f2179a813889f4a75bc279eb416053f6889 100644 (file)
@@ -1268,7 +1268,7 @@ int cfg_parse_peers(const char *file, int linenum, char **args, int kwm)
                newpeer->id = strdup(args[1]);
 
                raddr = strdup(args[2]);
-               rport = strchr(raddr, ':');
+               rport = strrchr(raddr, ':');
                if (rport) {
                        *rport++ = 0;
                        realport = atol(rport);
@@ -1279,7 +1279,7 @@ int cfg_parse_peers(const char *file, int linenum, char **args, int kwm)
                        goto out;
                }
 
-               sk = str2sa(raddr);
+               sk = str2ip(raddr);
                free(raddr);
                if (!sk) {
                        Alert("parsing [%s:%d] : Unknown host in '%s'\n", file, linenum, args[2]);
@@ -3965,7 +3965,7 @@ stats_error_parsing:
                         *  - IP:-N => port=-N, relative
                         */
                        raddr = strdup(args[2]);
-                       rport = strchr(raddr, ':');
+                       rport = strrchr(raddr, ':');
                        if (rport) {
                                *rport++ = 0;
                                realport = atol(rport);
@@ -3974,7 +3974,7 @@ stats_error_parsing:
                        } else
                                newsrv->state |= SRV_MAPPORTS;
 
-                       sk = str2sa(raddr);
+                       sk = str2ip(raddr);
                        free(raddr);
                        if (!sk) {
                                Alert("parsing [%s:%d] : Unknown host in '%s'\n", file, linenum, args[2]);
index 27fa374eac68d255934fd952a981e30fd5268cb2..033ece7bd09d0b5882d7dcfde03f10f075a7aac5 100644 (file)
@@ -214,70 +214,130 @@ const char *invalid_domainchar(const char *name) {
 }
 
 /*
- * converts <str> to a struct sockaddr_in* which is locally allocated.
- * The format is "addr:port", where "addr" can be a dotted IPv4 address,
- * a host name, or empty or "*" to indicate INADDR_ANY. NULL is returned
- * if the host part cannot be resolved.
+ * converts <str> to a struct sockaddr_storage* which is locally allocated. 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 *str2sa(char *str)
+struct sockaddr_storage *str2ip(const char *str)
 {
        static 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;
+               return &sa;
+       }
+
+       /* Any IPv4 address */
+       if (!str[0] || (str[0] == '*' && !str[1])) {
+               sa.ss_family = AF_INET;
+               return &sa;
+       }
+
+       /* check for IPv6 first */
+       if (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)) {
+               sa.ss_family = AF_INET;
+               return &sa;
+       }
+
+       /* try to resolve an IPv4/IPv6 hostname */
+       he = gethostbyname(str);
+       if (he) {
+               sa.ss_family = he->h_addrtype;
+               switch (sa.ss_family) {
+               case AF_INET:
+                       ((struct sockaddr_in *)&sa)->sin_addr = *(struct in_addr *) *(he->h_addr_list);
+                       return &sa;
+               case AF_INET6:
+                       ((struct sockaddr_in6 *)&sa)->sin6_addr = *(struct in6_addr *) *(he->h_addr_list);
+                       return &sa;
+               }
+               /* unsupported address family */
+       }
+
+       return NULL;
+}
+
+/*
+ * converts <str> to a locally allocated struct sockaddr_storage *.
+ * The format is "addr[:[port]]", where "addr" can be a dotted IPv4 address, an
+ * IPv6 address, a host name, or empty or "*" to indicate INADDR_ANY. If an IPv6
+ * address wants to ignore port, it must be terminated by a trailing colon (':').
+ * The IPv6 '::' address is IN6ADDR_ANY, so in order to bind to a given port on
+ * IPv6, use ":::port". NULL is returned if the host part cannot be resolved.
+ */
+struct sockaddr_storage *str2sa(const char *str)
+{
        struct sockaddr_storage *ret = NULL;
+       char *str2;
        char *c;
        int port;
 
-       memset(&sa, 0, sizeof(sa));
-       str = strdup(str);
-       if (str == NULL)
+       str2 = strdup(str);
+       if (str2 == NULL)
                goto out;
 
-       if ((c = strrchr(str,':')) != NULL) {
+       if ((c = strrchr(str2, ':')) != NULL) { /* Port */
                *c++ = '\0';
                port = atol(c);
        }
        else
                port = 0;
 
-       sa.ss_family = AF_INET;
-       ((struct sockaddr_in *)&sa)->sin_port = htons(port);
-       if (*str == '*' || *str == '\0') { /* INADDR_ANY */
-               ((struct sockaddr_in *)&sa)->sin_addr.s_addr = INADDR_ANY;
-       }
-       else if (!inet_pton(sa.ss_family, str, &((struct sockaddr_in *)&sa)->sin_addr)) {
-               struct hostent *he = gethostbyname(str);
-               if (!he)
-                       goto out;
-               ((struct sockaddr_in *)&sa)->sin_addr = *(struct in_addr *) *(he->h_addr_list);
+       ret = str2ip(str2);
+       if (!ret)
+               goto out;
+
+       switch (ret->ss_family) {
+       case AF_INET:
+               ((struct sockaddr_in *)ret)->sin_port = htons(port);
+               break;
+       case AF_INET6:
+               ((struct sockaddr_in6 *)ret)->sin6_port = htons(port);
+               break;
        }
-       ret = &sa;
  out:
-       free(str);
+       free(str2);
        return ret;
 }
 
 /*
- * converts <str> to a struct sockaddr_in* which is locally allocated, and a
+ * 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
- * port is set in the sockaddr_in. Thus, it is enough to check the size of the
+ * port is set in the sockaddr. Thus, it is enough to check the size of the
  * returned range to know if an array must be allocated or not. The format is
- * "addr[:port[-port]]", where "addr" can be a dotted IPv4 address, a host
- * name, or empty or "*" to indicate INADDR_ANY. NULL is returned if the host
- * part cannot be resolved.
+ * "addr[:[port[-port]]]", where "addr" can be a dotted IPv4 address, an IPv6
+ * address, a host name, or empty or "*" to indicate INADDR_ANY. If an IPv6
+ * address wants to ignore port, it must be terminated by a trailing colon (':').
+ * The IPv6 '::' address is IN6ADDR_ANY, so in order to bind to a given port on
+ * IPv6, use ":::port". NULL is returned if the host part cannot be resolved.
  */
-struct sockaddr_storage *str2sa_range(char *str, int *low, int *high)
+struct sockaddr_storage *str2sa_range(const char *str, int *low, int *high)
 {
-       static struct sockaddr_storage sa;
        struct sockaddr_storage *ret = NULL;
+       char *str2;
        char *c;
        int portl, porth;
 
-       memset(&sa, 0, sizeof(sa));
-       str = strdup(str);
-       if (str == NULL)
+       str2 = strdup(str);
+       if (str2 == NULL)
                goto out;
 
-       if ((c = strrchr(str,':')) != NULL) {
+       if ((c = strrchr(str2,':')) != NULL) { /* Port */
                char *sep;
                *c++ = '\0';
                sep = strchr(c, '-');
@@ -293,24 +353,23 @@ struct sockaddr_storage *str2sa_range(char *str, int *low, int *high)
                porth = 0;
        }
 
-       sa.ss_family = AF_INET;
-       ((struct sockaddr_in *)&sa)->sin_port = htonl(portl);
-       if (*str == '*' || *str == '\0') { /* INADDR_ANY */
-               ((struct sockaddr_in *)&sa)->sin_addr.s_addr = INADDR_ANY;
-       }
-       else if (!inet_pton(sa.ss_family, str, &((struct sockaddr_in *)&sa)->sin_addr)) {
-               struct hostent *he = gethostbyname(str);
-               if (!he)
-                       goto out;
-               ((struct sockaddr_in *)&sa)->sin_addr = *(struct in_addr *) *(he->h_addr_list);
+       ret = str2ip(str2);
+       if (!ret)
+               goto out;
+
+       switch (ret->ss_family) {
+       case AF_INET:
+               ((struct sockaddr_in *)ret)->sin_port = htons(portl);
+               break;
+       case AF_INET6:
+               ((struct sockaddr_in6 *)ret)->sin6_port = htons(portl);
+               break;
        }
-       ret = &sa;
 
        *low = portl;
        *high = porth;
-
  out:
-       free(str);
+       free(str2);
        return ret;
 }