MEDIUM: sample: Add IPv6 support to the ipmask converter

Add an optional second parameter to the ipmask converter that specifies
the number of bits to mask off IPv6 addresses.

If the second parameter is not given IPv6 addresses fail to mask (resulting
in an empty string), preserving backwards compatibility: Previously
a sample like `src,ipmask(24)` failed to give a result for IPv6 addresses.

This feature can be tested like this:

  defaults
  	log	global
  	mode	http
  	option	httplog
  	option	dontlognull
  	timeout connect 5000
  	timeout client  50000
  	timeout server  50000

  frontend fe
  	bind :::8080 v4v6

  	# Masked IPv4 for IPv4, empty for IPv6 (with and without this commit)
  	http-response set-header Test %[src,ipmask(24)]
  	# Correctly masked IP addresses for both IPv4 and IPv6
  	http-response set-header Test2 %[src,ipmask(24,ffff:ffff:ffff:ffff::)]
  	# Correctly masked IP addresses for both IPv4 and IPv6
  	http-response set-header Test3 %[src,ipmask(24,64)]

  	default_backend be

  backend be
  	server s example.com:80

Tested-By: Jarno Huuskonen <jarno.huuskonen@uef.fi>
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 6f6c1bc..1ce423b 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -12870,11 +12870,14 @@
   the presence of a certain key in a table tracking some elements (e.g. whether
   or not a source IP address or an Authorization header was already seen).
 
-ipmask(<mask>)
-  Apply a mask to an IPv4 address, and use the result for lookups and storage.
+ipmask(<mask4>, [<mask6>])
+  Apply a mask to an IP address, and use the result for lookups and storage.
   This can be used to make all hosts within a certain mask to share the same
-  table entries and as such use the same server. The mask can be passed in
-  dotted form (e.g. 255.255.255.0) or in CIDR form (e.g. 24).
+  table entries and as such use the same server. The mask4 can be passed in
+  dotted form (e.g. 255.255.255.0) or in CIDR form (e.g. 24). The mask6 can
+  be passed in quadruplet form (e.g. ffff:ffff::) or in CIDR form (e.g. 64).
+  If no mask6 is given IPv6 addresses will fail to convert for backwards
+  compatibility reasons.
 
 json([<input-code>])
   Escapes the input string and produces an ASCII output string ready to use as a
diff --git a/src/sample.c b/src/sample.c
index 8de076b..655e734 100644
--- a/src/sample.c
+++ b/src/sample.c
@@ -1603,11 +1603,28 @@
 	return 1;
 }
 
-/* takes the netmask in arg_p */
-static int sample_conv_ipmask(const struct arg *arg_p, struct sample *smp, void *private)
+/* takes the IPv4 mask in args[0] and an optional IPv6 mask in args[1] */
+static int sample_conv_ipmask(const struct arg *args, struct sample *smp, void *private)
 {
-	smp->data.u.ipv4.s_addr &= arg_p->data.ipv4.s_addr;
-	smp->data.type = SMP_T_IPV4;
+	/* Attempt to convert to IPv4 to apply the correct mask. */
+	c_ipv62ip(smp);
+
+	if (smp->data.type == SMP_T_IPV4) {
+		smp->data.u.ipv4.s_addr &= args[0].data.ipv4.s_addr;
+		smp->data.type = SMP_T_IPV4;
+	}
+	else if (smp->data.type == SMP_T_IPV6) {
+		/* IPv6 cannot be converted without an IPv6 mask. */
+		if (args[1].type != ARGT_IPV6)
+			return 0;
+
+		*(uint32_t*)&smp->data.u.ipv6.s6_addr[0]  &= *(uint32_t*)&args[1].data.ipv6.s6_addr[0];
+		*(uint32_t*)&smp->data.u.ipv6.s6_addr[4]  &= *(uint32_t*)&args[1].data.ipv6.s6_addr[4];
+		*(uint32_t*)&smp->data.u.ipv6.s6_addr[8]  &= *(uint32_t*)&args[1].data.ipv6.s6_addr[8];
+		*(uint32_t*)&smp->data.u.ipv6.s6_addr[12] &= *(uint32_t*)&args[1].data.ipv6.s6_addr[12];
+		smp->data.type = SMP_T_IPV6;
+	}
+
 	return 1;
 }
 
@@ -2809,7 +2826,7 @@
 	{ "length", sample_conv_length,    0,            NULL, SMP_T_STR,  SMP_T_SINT },
 	{ "hex",    sample_conv_bin2hex,   0,            NULL, SMP_T_BIN,  SMP_T_STR  },
 	{ "hex2i",  sample_conv_hex2int,   0,            NULL, SMP_T_STR,  SMP_T_SINT },
-	{ "ipmask", sample_conv_ipmask,    ARG1(1,MSK4), NULL, SMP_T_IPV4, SMP_T_IPV4 },
+	{ "ipmask", sample_conv_ipmask,    ARG2(1,MSK4,MSK6), NULL, SMP_T_ADDR, SMP_T_IPV4 },
 	{ "ltime",  sample_conv_ltime,     ARG2(1,STR,SINT), NULL, SMP_T_SINT, SMP_T_STR },
 	{ "utime",  sample_conv_utime,     ARG2(1,STR,SINT), NULL, SMP_T_SINT, SMP_T_STR },
 	{ "crc32",  sample_conv_crc32,     ARG1(0,SINT), NULL, SMP_T_BIN,  SMP_T_SINT  },