diff mbox series

[v2,1/6] netfilter: ipset: Add support for new bitmask parameter

Message ID 20220928182536.602688-2-vpai@akamai.com
State Changes Requested
Delegated to: Pablo Neira
Headers show
Series Add support for new bitmask parameter (userspace) | expand

Commit Message

Vishwanath Pai Sept. 28, 2022, 6:25 p.m. UTC
Add a new parameter to complement the existing 'netmask' option. The
main difference between netmask and bitmask is that bitmask takes any
arbitrary ip address as input, it does not have to be a valid netmask.

The name of the new parameter is 'bitmask'. This lets us mask out
arbitrary bits in the ip address, for example:
ipset create set1 hash:ip bitmask 255.128.255.0
ipset create set2 hash:ip,port family inet6 bitmask ffff::ff80

Signed-off-by: Vishwanath Pai <vpai@akamai.com>
Signed-off-by: Joshua Hunt <johunt@akamai.com>
---
 include/libipset/args.h         |  1 +
 include/libipset/data.h         |  6 ++++--
 include/libipset/linux_ip_set.h |  2 ++
 include/libipset/parse.h        |  2 ++
 lib/args.c                      |  8 +++++++
 lib/data.c                      | 10 +++++++++
 lib/debug.c                     |  1 +
 lib/errcode.c                   |  2 ++
 lib/parse.c                     | 37 +++++++++++++++++++++++++++++++++
 lib/print.c                     |  3 ++-
 lib/session.c                   |  8 +++++++
 11 files changed, 77 insertions(+), 3 deletions(-)

Comments

Jozsef Kadlecsik Nov. 7, 2022, 8:23 a.m. UTC | #1
On Wed, 28 Sep 2022, Vishwanath Pai wrote:

> Add a new parameter to complement the existing 'netmask' option. The
> main difference between netmask and bitmask is that bitmask takes any
> arbitrary ip address as input, it does not have to be a valid netmask.
> 
> The name of the new parameter is 'bitmask'. This lets us mask out
> arbitrary bits in the ip address, for example:
> ipset create set1 hash:ip bitmask 255.128.255.0
> ipset create set2 hash:ip,port family inet6 bitmask ffff::ff80
> 
> Signed-off-by: Vishwanath Pai <vpai@akamai.com>
> Signed-off-by: Joshua Hunt <johunt@akamai.com>
> ---
>  include/libipset/args.h         |  1 +
>  include/libipset/data.h         |  6 ++++--
>  include/libipset/linux_ip_set.h |  2 ++
>  include/libipset/parse.h        |  2 ++
>  lib/args.c                      |  8 +++++++
>  lib/data.c                      | 10 +++++++++
>  lib/debug.c                     |  1 +
>  lib/errcode.c                   |  2 ++
>  lib/parse.c                     | 37 +++++++++++++++++++++++++++++++++
>  lib/print.c                     |  3 ++-
>  lib/session.c                   |  8 +++++++
>  11 files changed, 77 insertions(+), 3 deletions(-)
> 
> diff --git a/include/libipset/args.h b/include/libipset/args.h
> index ef861c1..a549e42 100644
> --- a/include/libipset/args.h
> +++ b/include/libipset/args.h
> @@ -58,6 +58,7 @@ enum ipset_keywords {
>  	IPSET_ARG_SKBQUEUE,			/* skbqueue */
>  	IPSET_ARG_BUCKETSIZE,			/* bucketsize */
>  	IPSET_ARG_INITVAL,			/* initval */
> +	IPSET_ARG_BITMASK,			/* bitmask */
>  	IPSET_ARG_MAX,
>  };
>  
> diff --git a/include/libipset/data.h b/include/libipset/data.h
> index 0e33c67..afaf18c 100644
> --- a/include/libipset/data.h
> +++ b/include/libipset/data.h
> @@ -37,6 +37,7 @@ enum ipset_opt {
>  	IPSET_OPT_RESIZE,
>  	IPSET_OPT_SIZE,
>  	IPSET_OPT_FORCEADD,
> +	IPSET_OPT_BITMASK,
>  	/* Create-specific options, filled out by the kernel */
>  	IPSET_OPT_ELEMENTS,
>  	IPSET_OPT_REFERENCES,
> @@ -70,7 +71,7 @@ enum ipset_opt {
>  	IPSET_OPT_BUCKETSIZE,
>  	IPSET_OPT_INITVAL,
>  	/* Internal options */
> -	IPSET_OPT_FLAGS = 48,	/* IPSET_FLAG_EXIST| */
> +	IPSET_OPT_FLAGS = 49,	/* IPSET_FLAG_EXIST| */
>  	IPSET_OPT_CADT_FLAGS,	/* IPSET_FLAG_BEFORE| */
>  	IPSET_OPT_ELEM,
>  	IPSET_OPT_TYPE,
> @@ -105,7 +106,8 @@ enum ipset_opt {
>  	| IPSET_FLAG(IPSET_OPT_COUNTERS)\
>  	| IPSET_FLAG(IPSET_OPT_CREATE_COMMENT)\
>  	| IPSET_FLAG(IPSET_OPT_FORCEADD)\
> -	| IPSET_FLAG(IPSET_OPT_SKBINFO))
> +	| IPSET_FLAG(IPSET_OPT_SKBINFO)\
> +	| IPSET_FLAG(IPSET_OPT_BITMASK))
>  
>  #define IPSET_ADT_FLAGS			\
>  	(IPSET_FLAG(IPSET_OPT_IP)	\
> diff --git a/include/libipset/linux_ip_set.h b/include/libipset/linux_ip_set.h
> index 1852636..4e32a50 100644
> --- a/include/libipset/linux_ip_set.h
> +++ b/include/libipset/linux_ip_set.h
> @@ -89,6 +89,7 @@ enum {
>  	IPSET_ATTR_CADT_LINENO = IPSET_ATTR_LINENO,	/* 9 */
>  	IPSET_ATTR_MARK,	/* 10 */
>  	IPSET_ATTR_MARKMASK,	/* 11 */
> +	IPSET_ATTR_BITMASK,	/* 12 */
>  	/* Reserve empty slots */
>  	IPSET_ATTR_CADT_MAX = 16,
>  	/* Create-only specific attributes */
> @@ -157,6 +158,7 @@ enum ipset_errno {
>  	IPSET_ERR_COMMENT,
>  	IPSET_ERR_INVALID_MARKMASK,
>  	IPSET_ERR_SKBINFO,
> +	IPSET_ERR_BITMASK_NETMASK_EXCL,
>  
>  	/* Type specific error codes */
>  	IPSET_ERR_TYPE_SPECIFIC = 4352,
> diff --git a/include/libipset/parse.h b/include/libipset/parse.h
> index 3fa9129..0123d4b 100644
> --- a/include/libipset/parse.h
> +++ b/include/libipset/parse.h
> @@ -92,6 +92,8 @@ extern int ipset_parse_uint8(struct ipset_session *session,
>  			     enum ipset_opt opt, const char *str);
>  extern int ipset_parse_netmask(struct ipset_session *session,
>  			       enum ipset_opt opt, const char *str);
> +extern int ipset_parse_bitmask(struct ipset_session *session,
> +			       enum ipset_opt opt, const char *str);
>  extern int ipset_parse_flag(struct ipset_session *session,
>  			    enum ipset_opt opt, const char *str);
>  extern int ipset_parse_typename(struct ipset_session *session,
> diff --git a/lib/args.c b/lib/args.c
> index bab3b13..e47105c 100644
> --- a/lib/args.c
> +++ b/lib/args.c
> @@ -300,6 +300,14 @@ static const struct ipset_arg ipset_args[] = {
>  		.print = ipset_print_hexnumber,
>  		.help = "[initval VALUE]",
>  	},
> +	[IPSET_ARG_BITMASK] = {
> +		.name = { "bitmask", NULL },
> +		.has_arg = IPSET_MANDATORY_ARG,
> +		.opt = IPSET_OPT_BITMASK,
> +		.parse = ipset_parse_bitmask,
> +		.print = ipset_print_ip,
> +		.help = "[bitmask bitmask]",
> +	},
>  };
>  
>  const struct ipset_arg *
> diff --git a/lib/data.c b/lib/data.c
> index 7720178..72f1330 100644
> --- a/lib/data.c
> +++ b/lib/data.c
> @@ -53,6 +53,7 @@ struct ipset_data {
>  			uint8_t bucketsize;
>  			uint8_t resize;
>  			uint8_t netmask;
> +			union nf_inet_addr bitmask;
>  			uint32_t hashsize;
>  			uint32_t maxelem;
>  			uint32_t markmask;
> @@ -301,6 +302,12 @@ ipset_data_set(struct ipset_data *data, enum ipset_opt opt, const void *value)
>  	case IPSET_OPT_NETMASK:
>  		data->create.netmask = *(const uint8_t *) value;
>  		break;
> +	case IPSET_OPT_BITMASK:
> +		if (!(data->family == NFPROTO_IPV4 ||
> +		      data->family == NFPROTO_IPV6))
> +			return -1;
> +		copy_addr(data->family, &data->create.bitmask, value);
> +		break;
>  	case IPSET_OPT_BUCKETSIZE:
>  		data->create.bucketsize = *(const uint8_t *) value;
>  		break;
> @@ -508,6 +515,8 @@ ipset_data_get(const struct ipset_data *data, enum ipset_opt opt)
>  		return &data->create.markmask;
>  	case IPSET_OPT_NETMASK:
>  		return &data->create.netmask;
> +	case IPSET_OPT_BITMASK:
> +		return &data->create.bitmask;
>  	case IPSET_OPT_BUCKETSIZE:
>  		return &data->create.bucketsize;
>  	case IPSET_OPT_RESIZE:
> @@ -594,6 +603,7 @@ ipset_data_sizeof(enum ipset_opt opt, uint8_t family)
>  	case IPSET_OPT_IP_TO:
>  	case IPSET_OPT_IP2:
>  	case IPSET_OPT_IP2_TO:
> +	case IPSET_OPT_BITMASK:
>  		return family == NFPROTO_IPV4 ? sizeof(uint32_t)
>  					 : sizeof(struct in6_addr);
>  	case IPSET_OPT_MARK:
> diff --git a/lib/debug.c b/lib/debug.c
> index bf57a41..dbc5cfb 100644
> --- a/lib/debug.c
> +++ b/lib/debug.c
> @@ -40,6 +40,7 @@ static const struct ipset_attrname createattr2name[] = {
>  	[IPSET_ATTR_MAXELEM]	= { .name = "MAXELEM" },
>  	[IPSET_ATTR_MARKMASK]	= { .name = "MARKMASK" },
>  	[IPSET_ATTR_NETMASK]	= { .name = "NETMASK" },
> +	[IPSET_ATTR_BITMASK]    = { .name = "BITMASK" },
>  	[IPSET_ATTR_BUCKETSIZE]	= { .name = "BUCKETSIZE" },
>  	[IPSET_ATTR_RESIZE]	= { .name = "RESIZE" },
>  	[IPSET_ATTR_SIZE]	= { .name = "SIZE" },
> diff --git a/lib/errcode.c b/lib/errcode.c
> index 76bab74..49c97a1 100644
> --- a/lib/errcode.c
> +++ b/lib/errcode.c
> @@ -44,6 +44,8 @@ static const struct ipset_errcode_table core_errcode_table[] = {
>  	  "The value of the markmask parameter is invalid" },
>  	{ IPSET_ERR_INVALID_FAMILY, 0,
>  	  "Protocol family not supported by the set type" },
> +	{ IPSET_ERR_BITMASK_NETMASK_EXCL, 0,
> +	  "netmask and bitmask options are mutually exclusive, provide only one" },
>  
>  	/* DESTROY specific error codes */
>  	{ IPSET_ERR_BUSY, IPSET_CMD_DESTROY,
> diff --git a/lib/parse.c b/lib/parse.c
> index 974eaf8..c54bedf 100644
> --- a/lib/parse.c
> +++ b/lib/parse.c
> @@ -1721,6 +1721,43 @@ ipset_parse_netmask(struct ipset_session *session,
>  	return ipset_data_set(data, opt, &cidr);
>  }
>  
> +/**
> + * ipset_parse_bitmask - parse string as a bitmask
> + * @session: session structure
> + * @opt: option kind of the data
> + * @str: string to parse
> + *
> + * Parse string as a bitmask value, depending on family type.
> + * If family is not set yet, INET is assumed.
> + * The value is stored in the data blob of the session.
> + *
> + * Returns 0 on success or a negative error code.
> + */
> +int
> +ipset_parse_bitmask(struct ipset_session *session,
> +		    enum ipset_opt opt, const char *str)
> +{
> +	uint8_t family;
> +	struct ipset_data *data;
> +
> +	assert(session);
> +	assert(opt == IPSET_OPT_BITMASK);
> +	assert(str);
> +
> +	data = ipset_session_data(session);
> +	family = ipset_data_family(data);
> +	if (family == NFPROTO_UNSPEC) {
> +		family = NFPROTO_IPV4;
> +		ipset_data_set(data, IPSET_OPT_FAMILY, &family);
> +	}
> +
> +	if (parse_ipaddr(session, opt, str, family))
> +		return syntax_err("bitmask is not valid for family = %s",
> +				  family == NFPROTO_IPV4 ? "inet" : "inet6");
> +
> +	return 0;
> +}
> +

Please check in userspace in the parser functions (i.e. 
ipset_parse_bitmask() and ipset_parse_netmask()) too that "netmask" and 
"bitmask" options are mutually exclusive. Catch errors as early as 
possible. But leave the checking in the kernel space as well - it can 
receive any kind of data.

Best regards,
Jozsef

>  /**
>   * ipset_parse_flag - "parse" option flags
>   * @session: session structure
> diff --git a/lib/print.c b/lib/print.c
> index a7ffd81..50f0ad6 100644
> --- a/lib/print.c
> +++ b/lib/print.c
> @@ -265,7 +265,7 @@ ipset_print_ip(char *buf, unsigned int len,
>  	assert(buf);
>  	assert(len > 0);
>  	assert(data);
> -	assert(opt == IPSET_OPT_IP || opt == IPSET_OPT_IP2);
> +	assert(opt == IPSET_OPT_IP || opt == IPSET_OPT_IP2 || opt == IPSET_OPT_BITMASK);
>  
>  	D("len: %u", len);
>  	family = ipset_data_family(data);
> @@ -976,6 +976,7 @@ ipset_print_data(char *buf, unsigned int len,
>  		size = ipset_print_elem(buf, len, data, opt, env);
>  		break;
>  	case IPSET_OPT_IP:
> +	case IPSET_OPT_BITMASK:
>  		size = ipset_print_ip(buf, len, data, opt, env);
>  		break;
>  	case IPSET_OPT_PORT:
> diff --git a/lib/session.c b/lib/session.c
> index 1ca26ff..cdc59e0 100644
> --- a/lib/session.c
> +++ b/lib/session.c
> @@ -462,6 +462,10 @@ static const struct ipset_attr_policy create_attrs[] = {
>  		.type = MNL_TYPE_U32,
>  		.opt = IPSET_OPT_MEMSIZE,
>  	},
> +	[IPSET_ATTR_BITMASK] = {
> +		.type = MNL_TYPE_NESTED,
> +		.opt = IPSET_OPT_BITMASK,
> +	},
>  };
>  
>  static const struct ipset_attr_policy adt_attrs[] = {
> @@ -1721,6 +1725,10 @@ rawdata2attr(struct ipset_session *session, struct nlmsghdr *nlh,
>  	if (attr->type == MNL_TYPE_NESTED) {
>  		/* IP addresses */
>  		struct nlattr *nested;
> +
> +		if (type == IPSET_ATTR_BITMASK)
> +			family = ipset_data_family(session->data);
> +
>  		int atype = family == NFPROTO_IPV4 ? IPSET_ATTR_IPADDR_IPV4
>  					      : IPSET_ATTR_IPADDR_IPV6;
>  
> -- 
> 2.25.1
> 
> 

-
E-mail  : kadlec@blackhole.kfki.hu, kadlecsik.jozsef@wigner.hu
PGP key : https://wigner.hu/~kadlec/pgp_public_key.txt
Address : Wigner Research Centre for Physics
          H-1525 Budapest 114, POB. 49, Hungary
diff mbox series

Patch

diff --git a/include/libipset/args.h b/include/libipset/args.h
index ef861c1..a549e42 100644
--- a/include/libipset/args.h
+++ b/include/libipset/args.h
@@ -58,6 +58,7 @@  enum ipset_keywords {
 	IPSET_ARG_SKBQUEUE,			/* skbqueue */
 	IPSET_ARG_BUCKETSIZE,			/* bucketsize */
 	IPSET_ARG_INITVAL,			/* initval */
+	IPSET_ARG_BITMASK,			/* bitmask */
 	IPSET_ARG_MAX,
 };
 
diff --git a/include/libipset/data.h b/include/libipset/data.h
index 0e33c67..afaf18c 100644
--- a/include/libipset/data.h
+++ b/include/libipset/data.h
@@ -37,6 +37,7 @@  enum ipset_opt {
 	IPSET_OPT_RESIZE,
 	IPSET_OPT_SIZE,
 	IPSET_OPT_FORCEADD,
+	IPSET_OPT_BITMASK,
 	/* Create-specific options, filled out by the kernel */
 	IPSET_OPT_ELEMENTS,
 	IPSET_OPT_REFERENCES,
@@ -70,7 +71,7 @@  enum ipset_opt {
 	IPSET_OPT_BUCKETSIZE,
 	IPSET_OPT_INITVAL,
 	/* Internal options */
-	IPSET_OPT_FLAGS = 48,	/* IPSET_FLAG_EXIST| */
+	IPSET_OPT_FLAGS = 49,	/* IPSET_FLAG_EXIST| */
 	IPSET_OPT_CADT_FLAGS,	/* IPSET_FLAG_BEFORE| */
 	IPSET_OPT_ELEM,
 	IPSET_OPT_TYPE,
@@ -105,7 +106,8 @@  enum ipset_opt {
 	| IPSET_FLAG(IPSET_OPT_COUNTERS)\
 	| IPSET_FLAG(IPSET_OPT_CREATE_COMMENT)\
 	| IPSET_FLAG(IPSET_OPT_FORCEADD)\
-	| IPSET_FLAG(IPSET_OPT_SKBINFO))
+	| IPSET_FLAG(IPSET_OPT_SKBINFO)\
+	| IPSET_FLAG(IPSET_OPT_BITMASK))
 
 #define IPSET_ADT_FLAGS			\
 	(IPSET_FLAG(IPSET_OPT_IP)	\
diff --git a/include/libipset/linux_ip_set.h b/include/libipset/linux_ip_set.h
index 1852636..4e32a50 100644
--- a/include/libipset/linux_ip_set.h
+++ b/include/libipset/linux_ip_set.h
@@ -89,6 +89,7 @@  enum {
 	IPSET_ATTR_CADT_LINENO = IPSET_ATTR_LINENO,	/* 9 */
 	IPSET_ATTR_MARK,	/* 10 */
 	IPSET_ATTR_MARKMASK,	/* 11 */
+	IPSET_ATTR_BITMASK,	/* 12 */
 	/* Reserve empty slots */
 	IPSET_ATTR_CADT_MAX = 16,
 	/* Create-only specific attributes */
@@ -157,6 +158,7 @@  enum ipset_errno {
 	IPSET_ERR_COMMENT,
 	IPSET_ERR_INVALID_MARKMASK,
 	IPSET_ERR_SKBINFO,
+	IPSET_ERR_BITMASK_NETMASK_EXCL,
 
 	/* Type specific error codes */
 	IPSET_ERR_TYPE_SPECIFIC = 4352,
diff --git a/include/libipset/parse.h b/include/libipset/parse.h
index 3fa9129..0123d4b 100644
--- a/include/libipset/parse.h
+++ b/include/libipset/parse.h
@@ -92,6 +92,8 @@  extern int ipset_parse_uint8(struct ipset_session *session,
 			     enum ipset_opt opt, const char *str);
 extern int ipset_parse_netmask(struct ipset_session *session,
 			       enum ipset_opt opt, const char *str);
+extern int ipset_parse_bitmask(struct ipset_session *session,
+			       enum ipset_opt opt, const char *str);
 extern int ipset_parse_flag(struct ipset_session *session,
 			    enum ipset_opt opt, const char *str);
 extern int ipset_parse_typename(struct ipset_session *session,
diff --git a/lib/args.c b/lib/args.c
index bab3b13..e47105c 100644
--- a/lib/args.c
+++ b/lib/args.c
@@ -300,6 +300,14 @@  static const struct ipset_arg ipset_args[] = {
 		.print = ipset_print_hexnumber,
 		.help = "[initval VALUE]",
 	},
+	[IPSET_ARG_BITMASK] = {
+		.name = { "bitmask", NULL },
+		.has_arg = IPSET_MANDATORY_ARG,
+		.opt = IPSET_OPT_BITMASK,
+		.parse = ipset_parse_bitmask,
+		.print = ipset_print_ip,
+		.help = "[bitmask bitmask]",
+	},
 };
 
 const struct ipset_arg *
diff --git a/lib/data.c b/lib/data.c
index 7720178..72f1330 100644
--- a/lib/data.c
+++ b/lib/data.c
@@ -53,6 +53,7 @@  struct ipset_data {
 			uint8_t bucketsize;
 			uint8_t resize;
 			uint8_t netmask;
+			union nf_inet_addr bitmask;
 			uint32_t hashsize;
 			uint32_t maxelem;
 			uint32_t markmask;
@@ -301,6 +302,12 @@  ipset_data_set(struct ipset_data *data, enum ipset_opt opt, const void *value)
 	case IPSET_OPT_NETMASK:
 		data->create.netmask = *(const uint8_t *) value;
 		break;
+	case IPSET_OPT_BITMASK:
+		if (!(data->family == NFPROTO_IPV4 ||
+		      data->family == NFPROTO_IPV6))
+			return -1;
+		copy_addr(data->family, &data->create.bitmask, value);
+		break;
 	case IPSET_OPT_BUCKETSIZE:
 		data->create.bucketsize = *(const uint8_t *) value;
 		break;
@@ -508,6 +515,8 @@  ipset_data_get(const struct ipset_data *data, enum ipset_opt opt)
 		return &data->create.markmask;
 	case IPSET_OPT_NETMASK:
 		return &data->create.netmask;
+	case IPSET_OPT_BITMASK:
+		return &data->create.bitmask;
 	case IPSET_OPT_BUCKETSIZE:
 		return &data->create.bucketsize;
 	case IPSET_OPT_RESIZE:
@@ -594,6 +603,7 @@  ipset_data_sizeof(enum ipset_opt opt, uint8_t family)
 	case IPSET_OPT_IP_TO:
 	case IPSET_OPT_IP2:
 	case IPSET_OPT_IP2_TO:
+	case IPSET_OPT_BITMASK:
 		return family == NFPROTO_IPV4 ? sizeof(uint32_t)
 					 : sizeof(struct in6_addr);
 	case IPSET_OPT_MARK:
diff --git a/lib/debug.c b/lib/debug.c
index bf57a41..dbc5cfb 100644
--- a/lib/debug.c
+++ b/lib/debug.c
@@ -40,6 +40,7 @@  static const struct ipset_attrname createattr2name[] = {
 	[IPSET_ATTR_MAXELEM]	= { .name = "MAXELEM" },
 	[IPSET_ATTR_MARKMASK]	= { .name = "MARKMASK" },
 	[IPSET_ATTR_NETMASK]	= { .name = "NETMASK" },
+	[IPSET_ATTR_BITMASK]    = { .name = "BITMASK" },
 	[IPSET_ATTR_BUCKETSIZE]	= { .name = "BUCKETSIZE" },
 	[IPSET_ATTR_RESIZE]	= { .name = "RESIZE" },
 	[IPSET_ATTR_SIZE]	= { .name = "SIZE" },
diff --git a/lib/errcode.c b/lib/errcode.c
index 76bab74..49c97a1 100644
--- a/lib/errcode.c
+++ b/lib/errcode.c
@@ -44,6 +44,8 @@  static const struct ipset_errcode_table core_errcode_table[] = {
 	  "The value of the markmask parameter is invalid" },
 	{ IPSET_ERR_INVALID_FAMILY, 0,
 	  "Protocol family not supported by the set type" },
+	{ IPSET_ERR_BITMASK_NETMASK_EXCL, 0,
+	  "netmask and bitmask options are mutually exclusive, provide only one" },
 
 	/* DESTROY specific error codes */
 	{ IPSET_ERR_BUSY, IPSET_CMD_DESTROY,
diff --git a/lib/parse.c b/lib/parse.c
index 974eaf8..c54bedf 100644
--- a/lib/parse.c
+++ b/lib/parse.c
@@ -1721,6 +1721,43 @@  ipset_parse_netmask(struct ipset_session *session,
 	return ipset_data_set(data, opt, &cidr);
 }
 
+/**
+ * ipset_parse_bitmask - parse string as a bitmask
+ * @session: session structure
+ * @opt: option kind of the data
+ * @str: string to parse
+ *
+ * Parse string as a bitmask value, depending on family type.
+ * If family is not set yet, INET is assumed.
+ * The value is stored in the data blob of the session.
+ *
+ * Returns 0 on success or a negative error code.
+ */
+int
+ipset_parse_bitmask(struct ipset_session *session,
+		    enum ipset_opt opt, const char *str)
+{
+	uint8_t family;
+	struct ipset_data *data;
+
+	assert(session);
+	assert(opt == IPSET_OPT_BITMASK);
+	assert(str);
+
+	data = ipset_session_data(session);
+	family = ipset_data_family(data);
+	if (family == NFPROTO_UNSPEC) {
+		family = NFPROTO_IPV4;
+		ipset_data_set(data, IPSET_OPT_FAMILY, &family);
+	}
+
+	if (parse_ipaddr(session, opt, str, family))
+		return syntax_err("bitmask is not valid for family = %s",
+				  family == NFPROTO_IPV4 ? "inet" : "inet6");
+
+	return 0;
+}
+
 /**
  * ipset_parse_flag - "parse" option flags
  * @session: session structure
diff --git a/lib/print.c b/lib/print.c
index a7ffd81..50f0ad6 100644
--- a/lib/print.c
+++ b/lib/print.c
@@ -265,7 +265,7 @@  ipset_print_ip(char *buf, unsigned int len,
 	assert(buf);
 	assert(len > 0);
 	assert(data);
-	assert(opt == IPSET_OPT_IP || opt == IPSET_OPT_IP2);
+	assert(opt == IPSET_OPT_IP || opt == IPSET_OPT_IP2 || opt == IPSET_OPT_BITMASK);
 
 	D("len: %u", len);
 	family = ipset_data_family(data);
@@ -976,6 +976,7 @@  ipset_print_data(char *buf, unsigned int len,
 		size = ipset_print_elem(buf, len, data, opt, env);
 		break;
 	case IPSET_OPT_IP:
+	case IPSET_OPT_BITMASK:
 		size = ipset_print_ip(buf, len, data, opt, env);
 		break;
 	case IPSET_OPT_PORT:
diff --git a/lib/session.c b/lib/session.c
index 1ca26ff..cdc59e0 100644
--- a/lib/session.c
+++ b/lib/session.c
@@ -462,6 +462,10 @@  static const struct ipset_attr_policy create_attrs[] = {
 		.type = MNL_TYPE_U32,
 		.opt = IPSET_OPT_MEMSIZE,
 	},
+	[IPSET_ATTR_BITMASK] = {
+		.type = MNL_TYPE_NESTED,
+		.opt = IPSET_OPT_BITMASK,
+	},
 };
 
 static const struct ipset_attr_policy adt_attrs[] = {
@@ -1721,6 +1725,10 @@  rawdata2attr(struct ipset_session *session, struct nlmsghdr *nlh,
 	if (attr->type == MNL_TYPE_NESTED) {
 		/* IP addresses */
 		struct nlattr *nested;
+
+		if (type == IPSET_ATTR_BITMASK)
+			family = ipset_data_family(session->data);
+
 		int atype = family == NFPROTO_IPV4 ? IPSET_ATTR_IPADDR_IPV4
 					      : IPSET_ATTR_IPADDR_IPV6;