MEDIUM: http-ana: Add IPv6 support for forwardfor and orignialto options

A network may be specified to avoid header addition for "forwardfor" and
"orignialto" option via the "except" parameter. However, only IPv4
networks/addresses are supported. This patch adds the support of IPv6.

To do so, the net_addr structure is used to store the parameter value in the
proxy structure. And ipcmp2net() function is used to perform the comparison.

This patch should fix the issue #1145. It depends on the following commit:

  * c6ce0ab MINOR: tools: Add function to compare an address to a network address
  * 5587287 MINOR: tools: Add net_addr structure describing a network addess
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 06d0480..7f344d6 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -7973,7 +7973,7 @@
   header for a known source address or network by adding the "except" keyword
   followed by the network address. In this case, any source IP matching the
   network will not cause an addition of this header. Most common uses are with
-  private networks or 127.0.0.1.
+  private networks or 127.0.0.1. IPv4 and IPv6 are both supported.
 
   Alternatively, the keyword "if-none" states that the header will only be
   added if it is not present. This should only be used in perfectly trusted
@@ -8773,7 +8773,7 @@
   header for a known source address or network by adding the "except" keyword
   followed by the network address. In this case, any source IP matching the
   network will not cause an addition of this header. Most common uses are with
-  private networks or 127.0.0.1.
+  private networks or 127.0.0.1.  IPv4 and IPv6 are both supported.
 
   This option may be specified either in the frontend or in the backend. If at
   least one of them uses it, the header will be added. Note that the backend's
diff --git a/include/haproxy/proxy-t.h b/include/haproxy/proxy-t.h
index 3fdd09d..009a7bd 100644
--- a/include/haproxy/proxy-t.h
+++ b/include/haproxy/proxy-t.h
@@ -41,6 +41,7 @@
 #include <haproxy/stats-t.h>
 #include <haproxy/tcpcheck-t.h>
 #include <haproxy/thread-t.h>
+#include <haproxy/tools-t.h>
 #include <haproxy/uri_auth-t.h>
 
 /* values for proxy->mode */
@@ -341,9 +342,8 @@
 	unsigned int fe_sps_lim;		/* limit on new sessions per second on the frontend */
 	unsigned int fullconn;			/* #conns on backend above which servers are used at full load */
 	unsigned int tot_fe_maxconn;		/* #maxconn of frontends linked to that backend, it is used to compute fullconn */
-	struct in_addr except_net, except_mask; /* don't x-forward-for for this address. FIXME: should support IPv6 */
-	struct in_addr except_to;		/* don't x-original-to for this address. */
-	struct in_addr except_mask_to;		/* the netmask for except_to. */
+	struct net_addr except_xff_net;         /* don't x-forward-for for this address. */
+	struct net_addr except_xot_net;         /* don't x-original-to for this address. */
 	char *fwdfor_hdr_name;			/* header to use - default: "x-forwarded-for" */
 	char *orgto_hdr_name;			/* header to use - default: "x-original-to" */
 	int fwdfor_hdr_len;			/* length of "x-forwarded-for" header */
diff --git a/src/cfgparse-listen.c b/src/cfgparse-listen.c
index bb57be9..34816c5 100644
--- a/src/cfgparse-listen.c
+++ b/src/cfgparse-listen.c
@@ -2107,20 +2107,35 @@
 			free(curproxy->fwdfor_hdr_name);
 			curproxy->fwdfor_hdr_name = strdup(DEF_XFORWARDFOR_HDR);
 			curproxy->fwdfor_hdr_len  = strlen(DEF_XFORWARDFOR_HDR);
+			curproxy->except_xff_net.family = AF_UNSPEC;
 
 			/* loop to go through arguments - start at 2, since 0+1 = "option" "forwardfor" */
 			cur_arg = 2;
 			while (*(args[cur_arg])) {
 				if (strcmp(args[cur_arg], "except") == 0) {
+					unsigned char mask;
+					int i;
+
 					/* suboption except - needs additional argument for it */
-					if (!*(args[cur_arg+1]) || !str2net(args[cur_arg+1], 1, &curproxy->except_net, &curproxy->except_mask)) {
+					if (*(args[cur_arg+1]) &&
+					    str2net(args[cur_arg+1], 1, &curproxy->except_xff_net.addr.v4.ip, &curproxy->except_xff_net.addr.v4.mask)) {
+						curproxy->except_xff_net.family = AF_INET;
+						curproxy->except_xff_net.addr.v4.ip.s_addr &= curproxy->except_xff_net.addr.v4.mask.s_addr;
+					}
+					else if (*(args[cur_arg+1]) &&
+						 str62net(args[cur_arg+1], &curproxy->except_xff_net.addr.v6.ip, &mask)) {
+						curproxy->except_xff_net.family = AF_INET6;
+						len2mask6(mask, &curproxy->except_xff_net.addr.v6.mask);
+						for (i = 0; i < 16; i++)
+							curproxy->except_xff_net.addr.v6.ip.s6_addr[i] &= curproxy->except_xff_net.addr.v6.mask.s6_addr[i];
+					}
+					else {
 						ha_alert("parsing [%s:%d] : '%s %s %s' expects <address>[/mask] as argument.\n",
 							 file, linenum, args[0], args[1], args[cur_arg]);
 						err_code |= ERR_ALERT | ERR_FATAL;
 						goto out;
 					}
 					/* flush useless bits */
-					curproxy->except_net.s_addr &= curproxy->except_mask.s_addr;
 					cur_arg += 2;
 				} else if (strcmp(args[cur_arg], "header") == 0) {
 					/* suboption header - needs additional argument for it */
@@ -2158,20 +2173,34 @@
 			free(curproxy->orgto_hdr_name);
 			curproxy->orgto_hdr_name = strdup(DEF_XORIGINALTO_HDR);
 			curproxy->orgto_hdr_len  = strlen(DEF_XORIGINALTO_HDR);
+			curproxy->except_xot_net.family = AF_UNSPEC;
 
 			/* loop to go through arguments - start at 2, since 0+1 = "option" "originalto" */
 			cur_arg = 2;
 			while (*(args[cur_arg])) {
 				if (strcmp(args[cur_arg], "except") == 0) {
+					unsigned char mask;
+					int i;
+
 					/* suboption except - needs additional argument for it */
-					if (!*(args[cur_arg+1]) || !str2net(args[cur_arg+1], 1, &curproxy->except_to, &curproxy->except_mask_to)) {
+					if (*(args[cur_arg+1]) &&
+					    str2net(args[cur_arg+1], 1, &curproxy->except_xot_net.addr.v4.ip, &curproxy->except_xot_net.addr.v4.mask)) {
+						curproxy->except_xot_net.family = AF_INET;
+						curproxy->except_xot_net.addr.v4.ip.s_addr &= curproxy->except_xot_net.addr.v4.mask.s_addr;
+					}
+					else if (*(args[cur_arg+1]) &&
+						 str62net(args[cur_arg+1], &curproxy->except_xot_net.addr.v6.ip, &mask)) {
+						curproxy->except_xot_net.family = AF_INET6;
+						len2mask6(mask, &curproxy->except_xot_net.addr.v6.mask);
+						for (i = 0; i < 16; i++)
+							curproxy->except_xot_net.addr.v6.ip.s6_addr[i] &= curproxy->except_xot_net.addr.v6.mask.s6_addr[i];
+					}
+					else {
 						ha_alert("parsing [%s:%d] : '%s %s %s' expects <address>[/mask] as argument.\n",
 							 file, linenum, args[0], args[1], args[cur_arg]);
 						err_code |= ERR_ALERT | ERR_FATAL;
 						goto out;
 					}
-					/* flush useless bits */
-					curproxy->except_to.s_addr &= curproxy->except_mask_to.s_addr;
 					cur_arg += 2;
 				} else if (strcmp(args[cur_arg], "header") == 0) {
 					/* suboption header - needs additional argument for it */
diff --git a/src/http_ana.c b/src/http_ana.c
index d18b4cc..5536c04 100644
--- a/src/http_ana.c
+++ b/src/http_ana.c
@@ -673,12 +673,8 @@
 			/* Add an X-Forwarded-For header unless the source IP is
 			 * in the 'except' network range.
 			 */
-			if ((!sess->fe->except_mask.s_addr ||
-			     (((struct sockaddr_in *)cli_conn->src)->sin_addr.s_addr & sess->fe->except_mask.s_addr)
-			     != sess->fe->except_net.s_addr) &&
-			    (!s->be->except_mask.s_addr ||
-			     (((struct sockaddr_in *)cli_conn->src)->sin_addr.s_addr & s->be->except_mask.s_addr)
-			     != s->be->except_net.s_addr)) {
+			if (ipcmp2net(cli_conn->src, &sess->fe->except_xff_net) &&
+			    ipcmp2net(cli_conn->src, &s->be->except_xff_net)) {
 				unsigned char *pn = (unsigned char *)&((struct sockaddr_in *)cli_conn->src)->sin_addr;
 
 				/* Note: we rely on the backend to get the header name to be used for
@@ -692,23 +688,26 @@
 			}
 		}
 		else if (cli_conn && conn_get_src(cli_conn) && cli_conn->src->ss_family == AF_INET6) {
-			/* FIXME: for the sake of completeness, we should also support
-			 * 'except' here, although it is mostly useless in this case.
+			/* Add an X-Forwarded-For header unless the source IP is
+			 * in the 'except' network range.
 			 */
-			char pn[INET6_ADDRSTRLEN];
+			if (ipcmp2net(cli_conn->src, &sess->fe->except_xff_net) &&
+			    ipcmp2net(cli_conn->src, &s->be->except_xff_net)) {
+				char pn[INET6_ADDRSTRLEN];
 
-			inet_ntop(AF_INET6,
-				  (const void *)&((struct sockaddr_in6 *)(cli_conn->src))->sin6_addr,
-				  pn, sizeof(pn));
+				inet_ntop(AF_INET6,
+					  (const void *)&((struct sockaddr_in6 *)(cli_conn->src))->sin6_addr,
+					  pn, sizeof(pn));
 
-			/* Note: we rely on the backend to get the header name to be used for
-			 * x-forwarded-for, because the header is really meant for the backends.
-			 * However, if the backend did not specify any option, we have to rely
-			 * on the frontend's header name.
-			 */
-			chunk_printf(&trash, "%s", pn);
-			if (unlikely(!http_add_header(htx, hdr, ist2(trash.area, trash.data))))
-				goto return_int_err;
+				/* Note: we rely on the backend to get the header name to be used for
+				 * x-forwarded-for, because the header is really meant for the backends.
+				 * However, if the backend did not specify any option, we have to rely
+				 * on the frontend's header name.
+				 */
+				chunk_printf(&trash, "%s", pn);
+				if (unlikely(!http_add_header(htx, hdr, ist2(trash.area, trash.data))))
+					goto return_int_err;
+			}
 		}
 	}
 
@@ -717,20 +716,15 @@
 	 * asks for it.
 	 */
 	if ((sess->fe->options | s->be->options) & PR_O_ORGTO) {
+		struct ist hdr = ist2(s->be->orgto_hdr_len ? s->be->orgto_hdr_name : sess->fe->orgto_hdr_name,
+				      s->be->orgto_hdr_len ? s->be->orgto_hdr_len  : sess->fe->orgto_hdr_len);
 
-		/* FIXME: don't know if IPv6 can handle that case too. */
 		if (cli_conn && conn_get_dst(cli_conn) && cli_conn->dst->ss_family == AF_INET) {
 			/* Add an X-Original-To header unless the destination IP is
 			 * in the 'except' network range.
 			 */
-			if (cli_conn->dst->ss_family == AF_INET &&
-			    ((!sess->fe->except_mask_to.s_addr ||
-			      (((struct sockaddr_in *)cli_conn->dst)->sin_addr.s_addr & sess->fe->except_mask_to.s_addr)
-			      != sess->fe->except_to.s_addr) &&
-			     (!s->be->except_mask_to.s_addr ||
-			      (((struct sockaddr_in *)cli_conn->dst)->sin_addr.s_addr & s->be->except_mask_to.s_addr)
-			      != s->be->except_to.s_addr))) {
-				struct ist hdr;
+			if (ipcmp2net(cli_conn->dst, &sess->fe->except_xot_net) &&
+			    ipcmp2net(cli_conn->dst, &s->be->except_xot_net)) {
 				unsigned char *pn = (unsigned char *)&((struct sockaddr_in *)cli_conn->dst)->sin_addr;
 
 				/* Note: we rely on the backend to get the header name to be used for
@@ -738,16 +732,33 @@
 				 * However, if the backend did not specify any option, we have to rely
 				 * on the frontend's header name.
 				 */
-				if (s->be->orgto_hdr_len)
-					hdr = ist2(s->be->orgto_hdr_name, s->be->orgto_hdr_len);
-				else
-					hdr = ist2(sess->fe->orgto_hdr_name, sess->fe->orgto_hdr_len);
-
 				chunk_printf(&trash, "%d.%d.%d.%d", pn[0], pn[1], pn[2], pn[3]);
 				if (unlikely(!http_add_header(htx, hdr, ist2(trash.area, trash.data))))
 					goto return_int_err;
 			}
 		}
+		else if (cli_conn && conn_get_dst(cli_conn) && cli_conn->dst->ss_family == AF_INET6) {
+			/* Add an X-Original-To header unless the source IP is
+			 * in the 'except' network range.
+			 */
+			if (ipcmp2net(cli_conn->dst, &sess->fe->except_xot_net) &&
+			    ipcmp2net(cli_conn->dst, &s->be->except_xot_net)) {
+				char pn[INET6_ADDRSTRLEN];
+
+				inet_ntop(AF_INET6,
+					  (const void *)&((struct sockaddr_in6 *)(cli_conn->dst))->sin6_addr,
+					  pn, sizeof(pn));
+
+				/* Note: we rely on the backend to get the header name to be used for
+				 * x-forwarded-for, because the header is really meant for the backends.
+				 * However, if the backend did not specify any option, we have to rely
+				 * on the frontend's header name.
+				 */
+				chunk_printf(&trash, "%s", pn);
+				if (unlikely(!http_add_header(htx, hdr, ist2(trash.area, trash.data))))
+					goto return_int_err;
+			}
+		}
 	}
 
 	/* If we have no server assigned yet and we're balancing on url_param
diff --git a/src/proxy.c b/src/proxy.c
index 5edbbfe..abca609 100644
--- a/src/proxy.c
+++ b/src/proxy.c
@@ -1229,10 +1229,8 @@
 	curproxy->no_options = defproxy->no_options;
 	curproxy->no_options2 = defproxy->no_options2;
 	curproxy->bind_proc = defproxy->bind_proc;
-	curproxy->except_net = defproxy->except_net;
-	curproxy->except_mask = defproxy->except_mask;
-	curproxy->except_to = defproxy->except_to;
-	curproxy->except_mask_to = defproxy->except_mask_to;
+	curproxy->except_xff_net = defproxy->except_xff_net;
+	curproxy->except_xot_net = defproxy->except_xot_net;
 	curproxy->retry_type = defproxy->retry_type;
 
 	if (defproxy->fwdfor_hdr_len) {