Hi Willy,

Here are two patches, rebased on current HEAD.
The first allows IPv6 with square brackets, the second parses colon to look for a port. The logic is meant to be backward compatible with current behavior : as you suggested, starting from the end, it looks for either a colon or a closing bracket. If a colon is found, it’s considered as a port separator, thus behaving like previously. If something else is found, either a closing bracket or nothing at all, then it consider that no port were provided.

str2ip2 is then in charge of validating wether the string is a valid IP address or not.

Malformed IP addresses, whatever its format, will be rejected.


--
Mildis

Le 2015-10-13 21:05, Willy Tarreau a écrit :
On Tue, Oct 13, 2015 at 08:10:03PM +0200, Mildis wrote:

Le 2015-10-10 15:49, Willy Tarreau a écrit :
>Hi,

>@@ -856,11 +883,28 @@ struct sockaddr_storage *str2sa_range(const char
>*str, int *low, int *high, char
>    else { /* IPv4 and IPv6 */
>            int use_fqdn = 0;
>
>-           port1 = strrchr(str2, ':');
>-           if (port1)
>-                   *port1++ = '\0';
>-           else
>+           /* IPv6 wildcard */
>+           if (!strcmp(str2, "::")) {
>+                   port1 = "";
>+           }
>
>You're changing the parser here, because it now accepts "::" in a place
>where a port was expected.

May I ask : isn???t the parser just a parser ? Making the caller???s
responsibility to use or not the ports returned either in the
sockaddr_storage or the 2 int depending on the context ? (???bind???
requires a port but ???server??????s port is optional)
Is str2sa_range aware of the context it???s called ?

str2sa_range() parses ports, it relies on str2ip2() which only takes
addresses. So according to this it *is* the caller. Your change above
broke this because you're making the former ignore its own responsibility to properly consider the port which is mandatory if the address contains
a colon. And that's precisely because str2sa_range() doesn't have to be
aware of the context it's called that we want it to behave consistently.

Regards,
Willy
From 393ec4d4778c126e59fa7bd24e733bb32ca282a9 Mon Sep 17 00:00:00 2001
From: mildis <[email protected]>
Date: Sat, 17 Oct 2015 15:38:51 +0200
Subject: [PATCH 1/2] OPTIM: config: allow square-bracketed literal IPv6.

To enhance readability, allows IPv6 literal addresses with square brackets.
Last colon is still mandatory for address/port separation.
---
 src/standard.c | 45 ++++++++++++++++++++++++++++++++++-----------
 1 file changed, 34 insertions(+), 11 deletions(-)

diff --git a/src/standard.c b/src/standard.c
index b5f4bf4..2e064b5 100644
--- a/src/standard.c
+++ b/src/standard.c
@@ -620,9 +620,31 @@ const char *invalid_domainchar(const char *name) {
 struct sockaddr_storage *str2ip2(const char *str, struct sockaddr_storage *sa, int resolve)
 {
 	struct hostent *he;
+    /* add 2 chars for square brackets */
+    char tmpip[INET6_ADDRSTRLEN+2] = { '\0' };
+    /* point to str by default, if FQDN processing is required */
+    char *tmpipptr = (char *)str;
+    
+    /* check IPv6 with square brackets */
+    if (str[0] == '[') {
+        /* copy and strip the opening bracket */
+        strncpy(tmpip, str+1, sizeof(tmpip));
+        size_t iplength = strlen(tmpip);
+        if (tmpip[iplength - 1] != ']') {
+            /* if address started with bracket,
+             it should end with bracket */
+            goto fail;
+        }
+        else  {
+            /* switch to tmpip as we are working on a literal bracketed IPv6 */
+            tmpipptr = tmpip;
+            /* strip last bracket */
+            tmpipptr[iplength-1] = '\0';
+        }
+    }
 
 	/* Any IPv6 address */
-	if (str[0] == ':' && str[1] == ':' && !str[2]) {
+	if (tmpipptr[0] == ':' && tmpipptr[1] == ':' && !tmpipptr[2]) {
 		if (!sa->ss_family || sa->ss_family == AF_UNSPEC)
 			sa->ss_family = AF_INET6;
 		else if (sa->ss_family != AF_INET6)
@@ -631,7 +653,7 @@ struct sockaddr_storage *str2ip2(const char *str, struct sockaddr_storage *sa, i
 	}
 
 	/* Any address for the family, defaults to IPv4 */
-	if (!str[0] || (str[0] == '*' && !str[1])) {
+	if (!tmpipptr[0] || (tmpipptr[0] == '*' && !tmpipptr[1])) {
 		if (!sa->ss_family || sa->ss_family == AF_UNSPEC)
 			sa->ss_family = AF_INET;
 		return sa;
@@ -639,14 +661,14 @@ struct sockaddr_storage *str2ip2(const char *str, struct sockaddr_storage *sa, i
 
 	/* check for IPv6 first */
 	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)) {
+	    inet_pton(AF_INET6, tmpipptr, &((struct sockaddr_in6 *)sa)->sin6_addr)) {
 		sa->ss_family = AF_INET6;
 		return sa;
 	}
 
 	/* then check for IPv4 */
 	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)) {
+	    inet_pton(AF_INET, tmpipptr, &((struct sockaddr_in *)sa)->sin_addr)) {
 		sa->ss_family = AF_INET;
 		return sa;
 	}
@@ -654,7 +676,7 @@ struct sockaddr_storage *str2ip2(const char *str, struct sockaddr_storage *sa, i
 	if (!resolve)
 		return NULL;
 
-	if (!dns_hostname_validation(str, NULL))
+	if (!dns_hostname_validation(tmpipptr, NULL))
 		return NULL;
 
 #ifdef USE_GETADDRINFO
@@ -668,7 +690,7 @@ struct sockaddr_storage *str2ip2(const char *str, struct sockaddr_storage *sa, i
 		hints.ai_flags = 0;
 		hints.ai_protocol = 0;
 
-		if (getaddrinfo(str, NULL, &hints, &result) == 0) {
+		if (getaddrinfo(tmpipptr, NULL, &hints, &result) == 0) {
 			if (!sa->ss_family || sa->ss_family == AF_UNSPEC)
 				sa->ss_family = result->ai_family;
 			else if (sa->ss_family != result->ai_family)
@@ -689,7 +711,7 @@ struct sockaddr_storage *str2ip2(const char *str, struct sockaddr_storage *sa, i
 	}
 #endif
 	/* try to resolve an IPv4/IPv6 hostname */
-	he = gethostbyname(str);
+	he = gethostbyname(tmpipptr);
 	if (he) {
 		if (!sa->ss_family || sa->ss_family == AF_UNSPEC)
 			sa->ss_family = he->h_addrtype;
@@ -747,10 +769,11 @@ struct sockaddr_storage *str2ip2(const char *str, struct sockaddr_storage *sa, i
  *                  the first byte of the address.
  *    - "fd@"    => an integer must follow, and is a file descriptor number.
  *
- * 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, even for non-IP families.
+ * IPv6 addresses can be declared with or without square brackets. Also note 
+ * that in order to avoid any ambiguity with IPv6 addresses, the last colon ':'
+ * is mandatory 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, 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).
-- 
2.3.8 (Apple Git-58)

From d40c9d26e6db7855807ebfd48923c284e3769554 Mon Sep 17 00:00:00 2001
From: mildis <[email protected]>
Date: Thu, 22 Oct 2015 20:14:54 +0200
Subject: [PATCH 2/2] OPTIM: config: make last colon optional for IPv6 literal
 square-bracketed notation

---
 src/standard.c | 28 ++++++++++++++++++++++------
 1 file changed, 22 insertions(+), 6 deletions(-)

diff --git a/src/standard.c b/src/standard.c
index 2e064b5..e93375c 100644
--- a/src/standard.c
+++ b/src/standard.c
@@ -879,12 +879,28 @@ struct sockaddr_storage *str2sa_range(const char *str, int *low, int *high, char
 	else { /* IPv4 and IPv6 */
 		int use_fqdn = 0;
 
-		port1 = strrchr(str2, ':');
-		if (port1)
-			*port1++ = '\0';
-		else
-			port1 = "";
-
+        /* search for : or ] whatever comes first */
+        char *end = str2 + strlen(str2);
+        char *chr = end-1;
+        for (; chr >= str2; chr--) {
+            if (*chr == ']' || *chr == ':')
+                break;
+        }
+        
+        if (*chr == ':') {
+            /* Found a colon before a closing-bracket, must be a port separator.
+             * This guarantee backward compatibility.
+             */
+            *chr++ = '\0';
+            port1 = chr;
+        }
+        else {
+            /* Either no colon and no closing-bracket
+             * or directly ending with a closing-bracket.
+             * However, no port */
+            port1 = "";
+        }
+        
 		if (str2ip2(str2, &ss, 0) == NULL) {
 			use_fqdn = 1;
 			if (!resolve || str2ip2(str2, &ss, 1) == NULL) {
-- 
2.3.8 (Apple Git-58)

Reply via email to