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@xxxxxxxxxx> > Signed-off-by: Joshua Hunt <johunt@xxxxxxxxxx> > --- > > Notes: > Changes in v2 based on code review comments: > * bitmask and netmask options are mutually exclusive now > * Added tests for the bitmask feature to all three set types > * IP_SET_HASH_WITH_BITMASK is a separate macro now > * netmask is now converted and stored as 'bitmask' and bitmask is > applied to the entries directly for better efficiency > * fixed comment for revision 6 in hash:ip > * removed netmask from hash:net,net, it has a cidr option already > > include/linux/netfilter.h | 9 +++ > include/uapi/linux/netfilter/ipset/ip_set.h | 2 + > net/netfilter/ipset/ip_set_hash_gen.h | 75 +++++++++++++++++---- > net/netfilter/ipset/ip_set_hash_ip.c | 19 +++--- > net/netfilter/ipset/ip_set_hash_ipport.c | 24 ++++++- > net/netfilter/ipset/ip_set_hash_netnet.c | 24 +++++-- > 6 files changed, 124 insertions(+), 29 deletions(-) > > diff --git a/include/linux/netfilter.h b/include/linux/netfilter.h > index c2c6f332fb90..c7155eaebc84 100644 > --- a/include/linux/netfilter.h > +++ b/include/linux/netfilter.h > @@ -56,6 +56,15 @@ static inline void nf_inet_addr_mask(const union nf_inet_addr *a1, > #endif > } > > +static inline void nf_inet_addr_mask_inplace(union nf_inet_addr *a1, > + const union nf_inet_addr *mask) > +{ > + a1->all[0] &= mask->all[0]; > + a1->all[1] &= mask->all[1]; > + a1->all[2] &= mask->all[2]; > + a1->all[3] &= mask->all[3]; > +} > + I see that the natural place for the new inline function is in include/linux/netfilter.h but the only user is ipset. So probably it'd be better to move it into include/linux/netfilter/ipset/ip_set.h. I leave it up to you. > int netfilter_init(void); > > struct sk_buff; > diff --git a/include/uapi/linux/netfilter/ipset/ip_set.h b/include/uapi/linux/netfilter/ipset/ip_set.h > index 6397d75899bc..1bfa765a2191 100644 > --- a/include/uapi/linux/netfilter/ipset/ip_set.h > +++ b/include/uapi/linux/netfilter/ipset/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/net/netfilter/ipset/ip_set_hash_gen.h b/net/netfilter/ipset/ip_set_hash_gen.h > index 6e391308431d..1f1b106a2cd8 100644 > --- a/net/netfilter/ipset/ip_set_hash_gen.h > +++ b/net/netfilter/ipset/ip_set_hash_gen.h > @@ -182,6 +182,17 @@ htable_size(u8 hbits) > (SET_WITH_TIMEOUT(set) && \ > ip_set_timeout_expired(ext_timeout(d, set))) > > +#if defined(IP_SET_HASH_WITH_NETMASK) || defined(IP_SET_HASH_WITH_BITMASK) > +static const union nf_inet_addr onesmask = { > + .all[0] = 0xffffffff, > + .all[1] = 0xffffffff, > + .all[2] = 0xffffffff, > + .all[3] = 0xffffffff > +}; > + > +static const union nf_inet_addr zeromask; I might be paranoid but I'd like to see an explicit setting to zero, i.e. static const union nf_inet_addr zeromask = {}; > +#endif > + > #endif /* _IP_SET_HASH_GEN_H */ > > #ifndef MTYPE > @@ -306,8 +317,9 @@ struct htype { > u32 markmask; /* markmask value for mark mask to store */ > #endif > u8 bucketsize; /* max elements in an array block */ > -#ifdef IP_SET_HASH_WITH_NETMASK > +#if defined(IP_SET_HASH_WITH_NETMASK) || defined(IP_SET_HASH_WITH_BITMASK) > u8 netmask; /* netmask value for subnets to store */ > + union nf_inet_addr bitmask; /* stores bitmask */ > #endif > struct list_head ad; /* Resize add|del backlist */ > struct mtype_elem next; /* temporary storage for uadd */ > @@ -482,8 +494,8 @@ mtype_same_set(const struct ip_set *a, const struct ip_set *b) > /* Resizing changes htable_bits, so we ignore it */ > return x->maxelem == y->maxelem && > a->timeout == b->timeout && > -#ifdef IP_SET_HASH_WITH_NETMASK > - x->netmask == y->netmask && > +#if defined(IP_SET_HASH_WITH_NETMASK) || defined(IP_SET_HASH_WITH_BITMASK) > + nf_inet_addr_cmp(&x->bitmask, &y->bitmask) && > #endif > #ifdef IP_SET_HASH_WITH_MARKMASK > x->markmask == y->markmask && > @@ -1282,9 +1294,21 @@ mtype_head(struct ip_set *set, struct sk_buff *skb) > htonl(jhash_size(htable_bits))) || > nla_put_net32(skb, IPSET_ATTR_MAXELEM, htonl(h->maxelem))) > goto nla_put_failure; > +#ifdef IP_SET_HASH_WITH_BITMASK > + /* if netmask is set to anything other than HOST_MASK we know that the user supplied netmask > + * and not bitmask. These two are mutually exclusive. */ > + if (h->netmask == HOST_MASK && !nf_inet_addr_cmp(&onesmask, &h->bitmask)) { > + if (set->family == NFPROTO_IPV4) { > + if (nla_put_ipaddr4(skb, IPSET_ATTR_BITMASK, h->bitmask.ip)) > + goto nla_put_failure; > + } else if (set->family == NFPROTO_IPV6) { > + if (nla_put_ipaddr6(skb, IPSET_ATTR_BITMASK, &h->bitmask.in6)) > + goto nla_put_failure; > + } > + } > +#endif > #ifdef IP_SET_HASH_WITH_NETMASK > - if (h->netmask != HOST_MASK && > - nla_put_u8(skb, IPSET_ATTR_NETMASK, h->netmask)) > + if (h->netmask != HOST_MASK && nla_put_u8(skb, IPSET_ATTR_NETMASK, h->netmask)) > goto nla_put_failure; > #endif > #ifdef IP_SET_HASH_WITH_MARKMASK > @@ -1447,8 +1471,10 @@ IPSET_TOKEN(HTYPE, _create)(struct net *net, struct ip_set *set, > u32 markmask; > #endif > u8 hbits; > -#ifdef IP_SET_HASH_WITH_NETMASK > - u8 netmask; > +#if defined(IP_SET_HASH_WITH_NETMASK) || defined(IP_SET_HASH_WITH_BITMASK) > + int ret __attribute__((unused)) = 0; > + u8 netmask = set->family == NFPROTO_IPV4 ? 32 : 128; > + union nf_inet_addr bitmask = onesmask; > #endif > size_t hsize; > struct htype *h; > @@ -1486,13 +1512,37 @@ IPSET_TOKEN(HTYPE, _create)(struct net *net, struct ip_set *set, > #endif > > #ifdef IP_SET_HASH_WITH_NETMASK > - netmask = set->family == NFPROTO_IPV4 ? 32 : 128; > if (tb[IPSET_ATTR_NETMASK]) { > netmask = nla_get_u8(tb[IPSET_ATTR_NETMASK]); > - > if ((set->family == NFPROTO_IPV4 && netmask > 32) || > - (set->family == NFPROTO_IPV6 && netmask > 128) || > - netmask == 0) Why is the checking against zero value removed? Best regards, Jozsef > + (set->family == NFPROTO_IPV6 && netmask > 128)) > + return -IPSET_ERR_INVALID_NETMASK; > + > + /* we convert netmask to bitmask and store it */ > + if (set->family == NFPROTO_IPV4) > + bitmask.ip = ip_set_netmask(netmask); > + else > + ip6_netmask(&bitmask, netmask); > + } > +#endif > + > +#ifdef IP_SET_HASH_WITH_BITMASK > + if (tb[IPSET_ATTR_BITMASK]) { > + /* bitmask and netmask do the same thing, allow only one of these options */ > + if (tb[IPSET_ATTR_NETMASK]) > + return -IPSET_ERR_BITMASK_NETMASK_EXCL; > + > + if (set->family == NFPROTO_IPV4) { > + ret = ip_set_get_ipaddr4(tb[IPSET_ATTR_BITMASK], &bitmask.ip); > + if (ret || !bitmask.ip) > + return -IPSET_ERR_INVALID_NETMASK; > + } else if (set->family == NFPROTO_IPV6) { > + ret = ip_set_get_ipaddr6(tb[IPSET_ATTR_BITMASK], &bitmask); > + if (ret || ipv6_addr_any(&bitmask.in6)) > + return -IPSET_ERR_INVALID_NETMASK; > + } > + > + if (nf_inet_addr_cmp(&bitmask, &zeromask)) > return -IPSET_ERR_INVALID_NETMASK; > } > #endif > @@ -1536,7 +1586,8 @@ IPSET_TOKEN(HTYPE, _create)(struct net *net, struct ip_set *set, > for (i = 0; i < ahash_numof_locks(hbits); i++) > spin_lock_init(&t->hregion[i].lock); > h->maxelem = maxelem; > -#ifdef IP_SET_HASH_WITH_NETMASK > +#if defined(IP_SET_HASH_WITH_NETMASK) || defined(IP_SET_HASH_WITH_BITMASK) > + h->bitmask = bitmask; > h->netmask = netmask; > #endif > #ifdef IP_SET_HASH_WITH_MARKMASK > diff --git a/net/netfilter/ipset/ip_set_hash_ip.c b/net/netfilter/ipset/ip_set_hash_ip.c > index 75d556d71652..e30513cefd90 100644 > --- a/net/netfilter/ipset/ip_set_hash_ip.c > +++ b/net/netfilter/ipset/ip_set_hash_ip.c > @@ -24,7 +24,8 @@ > /* 2 Comments support */ > /* 3 Forceadd support */ > /* 4 skbinfo support */ > -#define IPSET_TYPE_REV_MAX 5 /* bucketsize, initval support */ > +/* 5 bucketsize, initval support */ > +#define IPSET_TYPE_REV_MAX 6 /* bitmask support */ > > MODULE_LICENSE("GPL"); > MODULE_AUTHOR("Jozsef Kadlecsik <kadlec@xxxxxxxxxxxxx>"); > @@ -34,6 +35,7 @@ MODULE_ALIAS("ip_set_hash:ip"); > /* Type specific function prefix */ > #define HTYPE hash_ip > #define IP_SET_HASH_WITH_NETMASK > +#define IP_SET_HASH_WITH_BITMASK > > /* IPv4 variant */ > > @@ -86,7 +88,7 @@ hash_ip4_kadt(struct ip_set *set, const struct sk_buff *skb, > __be32 ip; > > ip4addrptr(skb, opt->flags & IPSET_DIM_ONE_SRC, &ip); > - ip &= ip_set_netmask(h->netmask); > + ip &= h->bitmask.ip; > if (ip == 0) > return -EINVAL; > > @@ -119,7 +121,7 @@ hash_ip4_uadt(struct ip_set *set, struct nlattr *tb[], > if (ret) > return ret; > > - ip &= ip_set_hostmask(h->netmask); > + ip &= ntohl(h->bitmask.ip); > e.ip = htonl(ip); > if (e.ip == 0) > return -IPSET_ERR_HASH_ELEM; > @@ -185,12 +187,6 @@ hash_ip6_data_equal(const struct hash_ip6_elem *ip1, > return ipv6_addr_equal(&ip1->ip.in6, &ip2->ip.in6); > } > > -static void > -hash_ip6_netmask(union nf_inet_addr *ip, u8 prefix) > -{ > - ip6_netmask(ip, prefix); > -} > - > static bool > hash_ip6_data_list(struct sk_buff *skb, const struct hash_ip6_elem *e) > { > @@ -227,7 +223,7 @@ hash_ip6_kadt(struct ip_set *set, const struct sk_buff *skb, > struct ip_set_ext ext = IP_SET_INIT_KEXT(skb, opt, set); > > ip6addrptr(skb, opt->flags & IPSET_DIM_ONE_SRC, &e.ip.in6); > - hash_ip6_netmask(&e.ip, h->netmask); > + nf_inet_addr_mask_inplace(&e.ip, &h->bitmask); > if (ipv6_addr_any(&e.ip.in6)) > return -EINVAL; > > @@ -266,7 +262,7 @@ hash_ip6_uadt(struct ip_set *set, struct nlattr *tb[], > if (ret) > return ret; > > - hash_ip6_netmask(&e.ip, h->netmask); > + nf_inet_addr_mask_inplace(&e.ip, &h->bitmask); > if (ipv6_addr_any(&e.ip.in6)) > return -IPSET_ERR_HASH_ELEM; > > @@ -293,6 +289,7 @@ static struct ip_set_type hash_ip_type __read_mostly = { > [IPSET_ATTR_RESIZE] = { .type = NLA_U8 }, > [IPSET_ATTR_TIMEOUT] = { .type = NLA_U32 }, > [IPSET_ATTR_NETMASK] = { .type = NLA_U8 }, > + [IPSET_ATTR_BITMASK] = { .type = NLA_NESTED }, > [IPSET_ATTR_CADT_FLAGS] = { .type = NLA_U32 }, > }, > .adt_policy = { > diff --git a/net/netfilter/ipset/ip_set_hash_ipport.c b/net/netfilter/ipset/ip_set_hash_ipport.c > index 7303138e46be..2ffbd0b78a8c 100644 > --- a/net/netfilter/ipset/ip_set_hash_ipport.c > +++ b/net/netfilter/ipset/ip_set_hash_ipport.c > @@ -26,7 +26,8 @@ > /* 3 Comments support added */ > /* 4 Forceadd support added */ > /* 5 skbinfo support added */ > -#define IPSET_TYPE_REV_MAX 6 /* bucketsize, initval support added */ > +/* 6 bucketsize, initval support added */ > +#define IPSET_TYPE_REV_MAX 7 /* bitmask support added */ > > MODULE_LICENSE("GPL"); > MODULE_AUTHOR("Jozsef Kadlecsik <kadlec@xxxxxxxxxxxxx>"); > @@ -35,6 +36,8 @@ MODULE_ALIAS("ip_set_hash:ip,port"); > > /* Type specific function prefix */ > #define HTYPE hash_ipport > +#define IP_SET_HASH_WITH_NETMASK > +#define IP_SET_HASH_WITH_BITMASK > > /* IPv4 variant */ > > @@ -92,12 +95,16 @@ hash_ipport4_kadt(struct ip_set *set, const struct sk_buff *skb, > ipset_adtfn adtfn = set->variant->adt[adt]; > struct hash_ipport4_elem e = { .ip = 0 }; > struct ip_set_ext ext = IP_SET_INIT_KEXT(skb, opt, set); > + const struct MTYPE *h = set->data; > > if (!ip_set_get_ip4_port(skb, opt->flags & IPSET_DIM_TWO_SRC, > &e.port, &e.proto)) > return -EINVAL; > > ip4addrptr(skb, opt->flags & IPSET_DIM_ONE_SRC, &e.ip); > + e.ip &= h->bitmask.ip; > + if (e.ip == 0) > + return -EINVAL; > return adtfn(set, &e, &ext, &opt->ext, opt->cmdflags); > } > > @@ -129,6 +136,10 @@ hash_ipport4_uadt(struct ip_set *set, struct nlattr *tb[], > if (ret) > return ret; > > + e.ip &= h->bitmask.ip; > + if (e.ip == 0) > + return -EINVAL; > + > e.port = nla_get_be16(tb[IPSET_ATTR_PORT]); > > if (tb[IPSET_ATTR_PROTO]) { > @@ -253,12 +264,17 @@ hash_ipport6_kadt(struct ip_set *set, const struct sk_buff *skb, > ipset_adtfn adtfn = set->variant->adt[adt]; > struct hash_ipport6_elem e = { .ip = { .all = { 0 } } }; > struct ip_set_ext ext = IP_SET_INIT_KEXT(skb, opt, set); > + const struct MTYPE *h = set->data; > > if (!ip_set_get_ip6_port(skb, opt->flags & IPSET_DIM_TWO_SRC, > &e.port, &e.proto)) > return -EINVAL; > > ip6addrptr(skb, opt->flags & IPSET_DIM_ONE_SRC, &e.ip.in6); > + nf_inet_addr_mask_inplace(&e.ip, &h->bitmask); > + if (ipv6_addr_any(&e.ip.in6)) > + return -EINVAL; > + > return adtfn(set, &e, &ext, &opt->ext, opt->cmdflags); > } > > @@ -298,6 +314,10 @@ hash_ipport6_uadt(struct ip_set *set, struct nlattr *tb[], > if (ret) > return ret; > > + nf_inet_addr_mask_inplace(&e.ip, &h->bitmask); > + if (ipv6_addr_any(&e.ip.in6)) > + return -EINVAL; > + > e.port = nla_get_be16(tb[IPSET_ATTR_PORT]); > > if (tb[IPSET_ATTR_PROTO]) { > @@ -356,6 +376,8 @@ static struct ip_set_type hash_ipport_type __read_mostly = { > [IPSET_ATTR_PROTO] = { .type = NLA_U8 }, > [IPSET_ATTR_TIMEOUT] = { .type = NLA_U32 }, > [IPSET_ATTR_CADT_FLAGS] = { .type = NLA_U32 }, > + [IPSET_ATTR_NETMASK] = { .type = NLA_U8 }, > + [IPSET_ATTR_BITMASK] = { .type = NLA_NESTED }, > }, > .adt_policy = { > [IPSET_ATTR_IP] = { .type = NLA_NESTED }, > diff --git a/net/netfilter/ipset/ip_set_hash_netnet.c b/net/netfilter/ipset/ip_set_hash_netnet.c > index 3d09eefe998a..c514ac19486d 100644 > --- a/net/netfilter/ipset/ip_set_hash_netnet.c > +++ b/net/netfilter/ipset/ip_set_hash_netnet.c > @@ -23,7 +23,8 @@ > #define IPSET_TYPE_REV_MIN 0 > /* 1 Forceadd support added */ > /* 2 skbinfo support added */ > -#define IPSET_TYPE_REV_MAX 3 /* bucketsize, initval support added */ > +/* 3 bucketsize, initval support added */ > +#define IPSET_TYPE_REV_MAX 4 /* bitmask support added */ > > MODULE_LICENSE("GPL"); > MODULE_AUTHOR("Oliver Smith <oliver@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx>"); > @@ -33,6 +34,7 @@ MODULE_ALIAS("ip_set_hash:net,net"); > /* Type specific function prefix */ > #define HTYPE hash_netnet > #define IP_SET_HASH_WITH_NETS > +#define IP_SET_HASH_WITH_BITMASK > #define IPSET_NET_COUNT 2 > > /* IPv4 variants */ > @@ -153,8 +155,8 @@ hash_netnet4_kadt(struct ip_set *set, const struct sk_buff *skb, > > ip4addrptr(skb, opt->flags & IPSET_DIM_ONE_SRC, &e.ip[0]); > ip4addrptr(skb, opt->flags & IPSET_DIM_TWO_SRC, &e.ip[1]); > - e.ip[0] &= ip_set_netmask(e.cidr[0]); > - e.ip[1] &= ip_set_netmask(e.cidr[1]); > + e.ip[0] &= (ip_set_netmask(e.cidr[0]) & h->bitmask.ip); > + e.ip[1] &= (ip_set_netmask(e.cidr[1]) & h->bitmask.ip); > > return adtfn(set, &e, &ext, &opt->ext, opt->cmdflags); > } > @@ -213,8 +215,8 @@ hash_netnet4_uadt(struct ip_set *set, struct nlattr *tb[], > > if (adt == IPSET_TEST || !(tb[IPSET_ATTR_IP_TO] || > tb[IPSET_ATTR_IP2_TO])) { > - e.ip[0] = htonl(ip & ip_set_hostmask(e.cidr[0])); > - e.ip[1] = htonl(ip2_from & ip_set_hostmask(e.cidr[1])); > + e.ip[0] = htonl(ip & ntohl(h->bitmask.ip) & ip_set_hostmask(e.cidr[0])); > + e.ip[1] = htonl(ip2_from & ntohl(h->bitmask.ip) & ip_set_hostmask(e.cidr[1])); > ret = adtfn(set, &e, &ext, &ext, flags); > return ip_set_enomatch(ret, flags, adt, set) ? -ret : > ip_set_eexist(ret, flags) ? 0 : ret; > @@ -404,6 +406,11 @@ hash_netnet6_kadt(struct ip_set *set, const struct sk_buff *skb, > ip6_netmask(&e.ip[0], e.cidr[0]); > ip6_netmask(&e.ip[1], e.cidr[1]); > > + nf_inet_addr_mask_inplace(&e.ip[0], &h->bitmask); > + nf_inet_addr_mask_inplace(&e.ip[1], &h->bitmask); > + if (e.cidr[0] == HOST_MASK && ipv6_addr_any(&e.ip[0].in6)) > + return -EINVAL; > + > return adtfn(set, &e, &ext, &opt->ext, opt->cmdflags); > } > > @@ -414,6 +421,7 @@ hash_netnet6_uadt(struct ip_set *set, struct nlattr *tb[], > ipset_adtfn adtfn = set->variant->adt[adt]; > struct hash_netnet6_elem e = { }; > struct ip_set_ext ext = IP_SET_INIT_UEXT(set); > + const struct hash_netnet6 *h = set->data; > int ret; > > if (tb[IPSET_ATTR_LINENO]) > @@ -453,6 +461,11 @@ hash_netnet6_uadt(struct ip_set *set, struct nlattr *tb[], > ip6_netmask(&e.ip[0], e.cidr[0]); > ip6_netmask(&e.ip[1], e.cidr[1]); > > + nf_inet_addr_mask_inplace(&e.ip[0], &h->bitmask); > + nf_inet_addr_mask_inplace(&e.ip[1], &h->bitmask); > + if (e.cidr[0] == HOST_MASK && ipv6_addr_any(&e.ip[0].in6)) > + return -IPSET_ERR_HASH_ELEM; > + > if (tb[IPSET_ATTR_CADT_FLAGS]) { > u32 cadt_flags = ip_set_get_h32(tb[IPSET_ATTR_CADT_FLAGS]); > > @@ -484,6 +497,7 @@ static struct ip_set_type hash_netnet_type __read_mostly = { > [IPSET_ATTR_RESIZE] = { .type = NLA_U8 }, > [IPSET_ATTR_TIMEOUT] = { .type = NLA_U32 }, > [IPSET_ATTR_CADT_FLAGS] = { .type = NLA_U32 }, > + [IPSET_ATTR_BITMASK] = { .type = NLA_NESTED }, > }, > .adt_policy = { > [IPSET_ATTR_IP] = { .type = NLA_NESTED }, > -- > 2.25.1 > > - E-mail : kadlec@xxxxxxxxxxxxxxxxx, kadlecsik.jozsef@xxxxxxxxx PGP key : https://wigner.hu/~kadlec/pgp_public_key.txt Address : Wigner Research Centre for Physics H-1525 Budapest 114, POB. 49, Hungary