When ranges are added to hash types, the elements may trigger rehashing the set.
However, the last successfully added element was not kept track so the adding
started again with the first element after the rehashing. Bug reported by Mr Dash Four.
* zero for no match/success to add/delete
* positive for matching element */
int (*uadt)(struct ip_set *set, struct nlattr *tb[],
- enum ipset_adt adt, u32 *lineno, u32 flags);
+ enum ipset_adt adt, u32 *lineno, u32 flags, bool retried);
/* Low level add/del/test functions */
ipset_adtfn adt[IPSET_ADT_MAX];
#include <linux/jhash.h>
#include <linux/netfilter/ipset/ip_set_timeout.h>
+#define CONCAT(a, b, c) a##b##c
+#define TOKEN(a, b, c) CONCAT(a, b, c)
+
+#define type_pf_next TOKEN(TYPE, PF, _elem)
+
/* Hashing which uses arrays to resolve clashing. The hash table is resized
* (doubled) when searching becomes too long.
* Internally jhash is used with the assumption that the size of the
u32 initval; /* random jhash init value */
u32 timeout; /* timeout value, if enabled */
struct timer_list gc; /* garbage collection when timeout enabled */
+ struct type_pf_next next; /* temporary storage for uadd */
#ifdef IP_SET_HASH_WITH_NETMASK
u8 netmask; /* netmask value for subnets to store */
#endif
#define type_pf_data_netmask TOKEN(TYPE, PF, _data_netmask)
#define type_pf_data_list TOKEN(TYPE, PF, _data_list)
#define type_pf_data_tlist TOKEN(TYPE, PF, _data_tlist)
+#define type_pf_data_next TOKEN(TYPE, PF, _data_next)
#define type_pf_elem TOKEN(TYPE, PF, _elem)
#define type_pf_telem TOKEN(TYPE, PF, _telem)
return 0;
}
+static inline void
+type_pf_data_next(struct ip_set_hash *h, const struct type_pf_elem *d);
+
/* Add an element to a hash and update the internal counters when succeeded,
* otherwise report the proper error code. */
static int
}
ret = type_pf_elem_add(n, value);
- if (ret != 0)
+ if (ret != 0) {
+ if (ret == -EAGAIN)
+ type_pf_data_next(h, d);
goto out;
+ }
#ifdef IP_SET_HASH_WITH_NETS
add_cidr(h, d->cidr, HOST_MASK);
enum ipset_adt adt, const struct ip_set_adt_opt *opt);
static int
type_pf_uadt(struct ip_set *set, struct nlattr *tb[],
- enum ipset_adt adt, u32 *lineno, u32 flags);
+ enum ipset_adt adt, u32 *lineno, u32 flags, bool retried);
static const struct ip_set_type_variant type_pf_variant = {
.kadt = type_pf_kadt,
goto out;
}
ret = type_pf_elem_tadd(n, d, timeout);
- if (ret != 0)
+ if (ret != 0) {
+ if (ret == -EEXIST)
+ type_pf_data_next(h, d);
goto out;
+ }
#ifdef IP_SET_HASH_WITH_NETS
add_cidr(h, d->cidr, HOST_MASK);
static int
bitmap_ip_uadt(struct ip_set *set, struct nlattr *tb[],
- enum ipset_adt adt, u32 *lineno, u32 flags)
+ enum ipset_adt adt, u32 *lineno, u32 flags, bool retried)
{
struct bitmap_ip *map = set->data;
ipset_adtfn adtfn = set->variant->adt[adt];
static int
bitmap_ipmac_uadt(struct ip_set *set, struct nlattr *tb[],
- enum ipset_adt adt, u32 *lineno, u32 flags)
+ enum ipset_adt adt, u32 *lineno, u32 flags, bool retried)
{
const struct bitmap_ipmac *map = set->data;
ipset_adtfn adtfn = set->variant->adt[adt];
static int
bitmap_port_uadt(struct ip_set *set, struct nlattr *tb[],
- enum ipset_adt adt, u32 *lineno, u32 flags)
+ enum ipset_adt adt, u32 *lineno, u32 flags, bool retried)
{
struct bitmap_port *map = set->data;
ipset_adtfn adtfn = set->variant->adt[adt];
struct nlattr *tb[], enum ipset_adt adt,
u32 flags, bool use_lineno)
{
- int ret, retried = 0;
+ int ret;
u32 lineno = 0;
- bool eexist = flags & IPSET_FLAG_EXIST;
+ bool eexist = flags & IPSET_FLAG_EXIST, retried = false;
do {
write_lock_bh(&set->lock);
- ret = set->variant->uadt(set, tb, adt, &lineno, flags);
+ ret = set->variant->uadt(set, tb, adt, &lineno, flags, retried);
write_unlock_bh(&set->lock);
+ retried = true;
} while (ret == -EAGAIN &&
set->variant->resize &&
- (ret = set->variant->resize(set, retried++)) == 0);
+ (ret = set->variant->resize(set, retried)) == 0);
if (!ret || (ret == -IPSET_ERR_EXIST && eexist))
return 0;
return -IPSET_ERR_PROTOCOL;
read_lock_bh(&set->lock);
- ret = set->variant->uadt(set, tb, IPSET_TEST, NULL, 0);
+ ret = set->variant->uadt(set, tb, IPSET_TEST, NULL, 0, 0);
read_unlock_bh(&set->lock);
/* Userspace can't trigger element to be re-added */
if (ret == -EAGAIN)
#define HOST_MASK 32
#include <linux/netfilter/ipset/ip_set_ahash.h>
+static inline void
+hash_ip4_data_next(struct ip_set_hash *h, const struct hash_ip4_elem *d)
+{
+ h->next.ip = ntohl(d->ip);
+}
+
static int
hash_ip4_kadt(struct ip_set *set, const struct sk_buff *skb,
enum ipset_adt adt, const struct ip_set_adt_opt *opt)
static int
hash_ip4_uadt(struct ip_set *set, struct nlattr *tb[],
- enum ipset_adt adt, u32 *lineno, u32 flags)
+ enum ipset_adt adt, u32 *lineno, u32 flags, bool retried)
{
const struct ip_set_hash *h = set->data;
ipset_adtfn adtfn = set->variant->adt[adt];
hosts = h->netmask == 32 ? 1 : 2 << (32 - h->netmask - 1);
+ if (retried)
+ ip = h->next.ip;
for (; !before(ip_to, ip); ip += hosts) {
nip = htonl(ip);
if (nip == 0)
#define HOST_MASK 128
#include <linux/netfilter/ipset/ip_set_ahash.h>
+static inline void
+hash_ip6_data_next(struct ip_set_hash *h, const struct hash_ip6_elem *d)
+{
+}
+
static int
hash_ip6_kadt(struct ip_set *set, const struct sk_buff *skb,
enum ipset_adt adt, const struct ip_set_adt_opt *opt)
static int
hash_ip6_uadt(struct ip_set *set, struct nlattr *tb[],
- enum ipset_adt adt, u32 *lineno, u32 flags)
+ enum ipset_adt adt, u32 *lineno, u32 flags, bool retried)
{
const struct ip_set_hash *h = set->data;
ipset_adtfn adtfn = set->variant->adt[adt];
#define HOST_MASK 32
#include <linux/netfilter/ipset/ip_set_ahash.h>
+static inline void
+hash_ipport4_data_next(struct ip_set_hash *h,
+ const struct hash_ipport4_elem *d)
+{
+ h->next.ip = ntohl(d->ip);
+ h->next.port = ntohs(d->port);
+}
+
static int
hash_ipport4_kadt(struct ip_set *set, const struct sk_buff *skb,
enum ipset_adt adt, const struct ip_set_adt_opt *opt)
static int
hash_ipport4_uadt(struct ip_set *set, struct nlattr *tb[],
- enum ipset_adt adt, u32 *lineno, u32 flags)
+ enum ipset_adt adt, u32 *lineno, u32 flags, bool retried)
{
const struct ip_set_hash *h = set->data;
ipset_adtfn adtfn = set->variant->adt[adt];
struct hash_ipport4_elem data = { };
- u32 ip, ip_to, p, port, port_to;
+ u32 ip, ip_to, p = 0, port, port_to;
u32 timeout = h->timeout;
bool with_ports = false;
int ret;
swap(port, port_to);
}
- for (; !before(ip_to, ip); ip++)
- for (p = port; p <= port_to; p++) {
+ if (retried)
+ ip = h->next.ip;
+ for (; !before(ip_to, ip); ip++) {
+ p = retried && ip == h->next.ip ? h->next.port : port;
+ for (; p <= port_to; p++) {
data.ip = htonl(ip);
data.port = htons(p);
ret = adtfn(set, &data, timeout, flags);
else
ret = 0;
}
+ }
return ret;
}
#define HOST_MASK 128
#include <linux/netfilter/ipset/ip_set_ahash.h>
+static inline void
+hash_ipport6_data_next(struct ip_set_hash *h,
+ const struct hash_ipport6_elem *d)
+{
+ h->next.port = ntohs(d->port);
+}
+
static int
hash_ipport6_kadt(struct ip_set *set, const struct sk_buff *skb,
enum ipset_adt adt, const struct ip_set_adt_opt *opt)
static int
hash_ipport6_uadt(struct ip_set *set, struct nlattr *tb[],
- enum ipset_adt adt, u32 *lineno, u32 flags)
+ enum ipset_adt adt, u32 *lineno, u32 flags, bool retried)
{
const struct ip_set_hash *h = set->data;
ipset_adtfn adtfn = set->variant->adt[adt];
if (port > port_to)
swap(port, port_to);
+ if (retried)
+ port = h->next.port;
for (; port <= port_to; port++) {
data.port = htons(port);
ret = adtfn(set, &data, timeout, flags);
#define HOST_MASK 32
#include <linux/netfilter/ipset/ip_set_ahash.h>
+static inline void
+hash_ipportip4_data_next(struct ip_set_hash *h,
+ const struct hash_ipportip4_elem *d)
+{
+ h->next.ip = ntohl(d->ip);
+ h->next.port = ntohs(d->port);
+}
+
static int
hash_ipportip4_kadt(struct ip_set *set, const struct sk_buff *skb,
enum ipset_adt adt, const struct ip_set_adt_opt *opt)
static int
hash_ipportip4_uadt(struct ip_set *set, struct nlattr *tb[],
- enum ipset_adt adt, u32 *lineno, u32 flags)
+ enum ipset_adt adt, u32 *lineno, u32 flags, bool retried)
{
const struct ip_set_hash *h = set->data;
ipset_adtfn adtfn = set->variant->adt[adt];
struct hash_ipportip4_elem data = { };
- u32 ip, ip_to, p, port, port_to;
+ u32 ip, ip_to, p = 0, port, port_to;
u32 timeout = h->timeout;
bool with_ports = false;
int ret;
swap(port, port_to);
}
- for (; !before(ip_to, ip); ip++)
- for (p = port; p <= port_to; p++) {
+ if (retried)
+ ip = h->next.ip;
+ for (; !before(ip_to, ip); ip++) {
+ p = retried && ip == h->next.ip ? h->next.port : port;
+ for (; p <= port_to; p++) {
data.ip = htonl(ip);
data.port = htons(p);
ret = adtfn(set, &data, timeout, flags);
else
ret = 0;
}
+ }
return ret;
}
#define HOST_MASK 128
#include <linux/netfilter/ipset/ip_set_ahash.h>
+static inline void
+hash_ipportip6_data_next(struct ip_set_hash *h,
+ const struct hash_ipportip6_elem *d)
+{
+ h->next.port = ntohs(d->port);
+}
+
static int
hash_ipportip6_kadt(struct ip_set *set, const struct sk_buff *skb,
enum ipset_adt adt, const struct ip_set_adt_opt *opt)
static int
hash_ipportip6_uadt(struct ip_set *set, struct nlattr *tb[],
- enum ipset_adt adt, u32 *lineno, u32 flags)
+ enum ipset_adt adt, u32 *lineno, u32 flags, bool retried)
{
const struct ip_set_hash *h = set->data;
ipset_adtfn adtfn = set->variant->adt[adt];
if (port > port_to)
swap(port, port_to);
+ if (retried)
+ port = h->next.port;
for (; port <= port_to; port++) {
data.port = htons(port);
ret = adtfn(set, &data, timeout, flags);
#define HOST_MASK 32
#include <linux/netfilter/ipset/ip_set_ahash.h>
+static inline void
+hash_ipportnet4_data_next(struct ip_set_hash *h,
+ const struct hash_ipportnet4_elem *d)
+{
+ h->next.ip = ntohl(d->ip);
+ h->next.port = ntohs(d->port);
+}
+
static int
hash_ipportnet4_kadt(struct ip_set *set, const struct sk_buff *skb,
enum ipset_adt adt, const struct ip_set_adt_opt *opt)
static int
hash_ipportnet4_uadt(struct ip_set *set, struct nlattr *tb[],
- enum ipset_adt adt, u32 *lineno, u32 flags)
+ enum ipset_adt adt, u32 *lineno, u32 flags, bool retried)
{
const struct ip_set_hash *h = set->data;
ipset_adtfn adtfn = set->variant->adt[adt];
struct hash_ipportnet4_elem data = { .cidr = HOST_MASK };
- u32 ip, ip_to, p, port, port_to;
+ u32 ip, ip_to, p = 0, port, port_to;
u32 timeout = h->timeout;
bool with_ports = false;
int ret;
swap(port, port_to);
}
- for (; !before(ip_to, ip); ip++)
- for (p = port; p <= port_to; p++) {
+ if (retried)
+ ip = h->next.ip;
+ for (; !before(ip_to, ip); ip++) {
+ p = retried && ip == h->next.ip ? h->next.port : port;
+ for (; p <= port_to; p++) {
data.ip = htonl(ip);
data.port = htons(p);
ret = adtfn(set, &data, timeout, flags);
else
ret = 0;
}
+ }
return ret;
}
#define HOST_MASK 128
#include <linux/netfilter/ipset/ip_set_ahash.h>
+static inline void
+hash_ipportnet6_data_next(struct ip_set_hash *h,
+ const struct hash_ipportnet6_elem *d)
+{
+ h->next.port = ntohs(d->port);
+}
+
static int
hash_ipportnet6_kadt(struct ip_set *set, const struct sk_buff *skb,
enum ipset_adt adt, const struct ip_set_adt_opt *opt)
static int
hash_ipportnet6_uadt(struct ip_set *set, struct nlattr *tb[],
- enum ipset_adt adt, u32 *lineno, u32 flags)
+ enum ipset_adt adt, u32 *lineno, u32 flags, bool retried)
{
const struct ip_set_hash *h = set->data;
ipset_adtfn adtfn = set->variant->adt[adt];
if (port > port_to)
swap(port, port_to);
+ if (retried)
+ port = h->next.port;
for (; port <= port_to; port++) {
data.port = htons(port);
ret = adtfn(set, &data, timeout, flags);
#define HOST_MASK 32
#include <linux/netfilter/ipset/ip_set_ahash.h>
+static inline void
+hash_net4_data_next(struct ip_set_hash *h,
+ const struct hash_net4_elem *d)
+{
+}
+
static int
hash_net4_kadt(struct ip_set *set, const struct sk_buff *skb,
enum ipset_adt adt, const struct ip_set_adt_opt *opt)
static int
hash_net4_uadt(struct ip_set *set, struct nlattr *tb[],
- enum ipset_adt adt, u32 *lineno, u32 flags)
+ enum ipset_adt adt, u32 *lineno, u32 flags, bool retried)
{
const struct ip_set_hash *h = set->data;
ipset_adtfn adtfn = set->variant->adt[adt];
#define HOST_MASK 128
#include <linux/netfilter/ipset/ip_set_ahash.h>
+static inline void
+hash_net6_data_next(struct ip_set_hash *h,
+ const struct hash_net6_elem *d)
+{
+}
+
static int
hash_net6_kadt(struct ip_set *set, const struct sk_buff *skb,
enum ipset_adt adt, const struct ip_set_adt_opt *opt)
static int
hash_net6_uadt(struct ip_set *set, struct nlattr *tb[],
- enum ipset_adt adt, u32 *lineno, u32 flags)
+ enum ipset_adt adt, u32 *lineno, u32 flags, bool retried)
{
const struct ip_set_hash *h = set->data;
ipset_adtfn adtfn = set->variant->adt[adt];
#define HOST_MASK 32
#include <linux/netfilter/ipset/ip_set_ahash.h>
+static inline void
+hash_netport4_data_next(struct ip_set_hash *h,
+ const struct hash_netport4_elem *d)
+{
+ h->next.port = ntohs(d->port);
+}
+
static int
hash_netport4_kadt(struct ip_set *set, const struct sk_buff *skb,
enum ipset_adt adt, const struct ip_set_adt_opt *opt)
static int
hash_netport4_uadt(struct ip_set *set, struct nlattr *tb[],
- enum ipset_adt adt, u32 *lineno, u32 flags)
+ enum ipset_adt adt, u32 *lineno, u32 flags, bool retried)
{
const struct ip_set_hash *h = set->data;
ipset_adtfn adtfn = set->variant->adt[adt];
if (port > port_to)
swap(port, port_to);
+ if (retried)
+ port = h->next.port;
for (; port <= port_to; port++) {
data.port = htons(port);
ret = adtfn(set, &data, timeout, flags);
#define HOST_MASK 128
#include <linux/netfilter/ipset/ip_set_ahash.h>
+static inline void
+hash_netport6_data_next(struct ip_set_hash *h,
+ const struct hash_netport6_elem *d)
+{
+ h->next.port = ntohs(d->port);
+}
+
static int
hash_netport6_kadt(struct ip_set *set, const struct sk_buff *skb,
enum ipset_adt adt, const struct ip_set_adt_opt *opt)
static int
hash_netport6_uadt(struct ip_set *set, struct nlattr *tb[],
- enum ipset_adt adt, u32 *lineno, u32 flags)
+ enum ipset_adt adt, u32 *lineno, u32 flags, bool retried)
{
const struct ip_set_hash *h = set->data;
ipset_adtfn adtfn = set->variant->adt[adt];
if (port > port_to)
swap(port, port_to);
+ if (retried)
+ port = h->next.port;
for (; port <= port_to; port++) {
data.port = htons(port);
ret = adtfn(set, &data, timeout, flags);
static int
list_set_uadt(struct ip_set *set, struct nlattr *tb[],
- enum ipset_adt adt, u32 *lineno, u32 flags)
+ enum ipset_adt adt, u32 *lineno, u32 flags, bool retried)
{
struct list_set *map = set->data;
bool with_timeout = with_timeout(map->timeout);
0 n=`ipset save test|wc -l` && test $n -eq 17
# Delete test set
0 ipset destroy test
+# Create set to add a range
+0 ipset new test hash:ip,port,ip hashsize 64
+# Add a range which forces a resizing
+0 ipset add test 10.0.0.0-10.0.3.255,tcp:80-82,192.168.0.1
+# Check that correct number of elements are added
+0 n=`ipset list test|grep 10.0|wc -l` && test $n -eq 3072
+# Destroy set
+0 ipset -X test
# eof
0 ipset flush test
# Delete set
0 ipset destroy test
+# Create set to add a range
+0 ipset new test hash:ip,port,net hashsize 64
+# Add a range which forces a resizing
+0 ipset add test 10.0.0.0-10.0.3.255,tcp:80-82,192.168.0.1/24
+# Check that correct number of elements are added
+0 n=`ipset list test|grep 10.0|wc -l` && test $n -eq 3072
+# Destroy set
+0 ipset -X test
# eof
0 diff -u -I 'Size in memory.*' .foo hash:ip,port.t.list2
# Delete set
0 ipset destroy test
+# Create set to add a range
+0 ipset new test hash:ip,port hashsize 64
+# Add a range which forces a resizing
+0 ipset add test 10.0.0.0-10.0.3.255,tcp:80-82
+# Check that correct number of elements are added
+0 n=`ipset list test|grep 10.0|wc -l` && test $n -eq 3072
+# Destroy set
+0 ipset -X test
# eof
0 n=`ipset -S test | wc -l` && test $n -eq 8161
# IP: Destroy sets
0 ipset -X
+# IP: Create set to add a range
+0 ipset new test hash:ip hashsize 64
+# IP: Add a range which forces a resizing
+0 ipset add test 10.0.0.0-10.0.3.255
+# IP: Check that correct number of elements are added
+0 n=`ipset list test|grep 10.0|wc -l` && test $n -eq 1024
+# IP: Destroy sets
+0 ipset -X
# Network: Create a set with timeout
0 ipset -N test iphash --hashsize 128 --netmask 24 timeout 5
# Network: Add zero valued element
0 ipset test test 1::1,udp:80,2::2
# Delete test set
0 ipset destroy test
+# Create set to add a range
+0 ipset new test hash:ip,port,ip -6 hashsize 64
+# Add a range which forces a resizing
+0 ipset add test 1::1,tcp:80-1105,2::2
+# Check that correct number of elements are added
+0 n=`ipset list test|grep 1::1|wc -l` && test $n -eq 1026
+# Destroy set
+0 ipset -X test
# eof
0 ipset -F test
# Range: Delete test set
0 ipset -X test
+# Create set to add a range
+0 ipset new test hash:ip,port,net -6 hashsize 64
+# Add a range which forces a resizing
+0 ipset add test 1::1,tcp:80-1105,2::2/12
+# Check that correct number of elements are added
+0 n=`ipset list test|grep 1::1|wc -l` && test $n -eq 1026
+# Destroy set
+0 ipset -X test
# eof
0 ipset test test 1::1,udp:80
# Delete test set
0 ipset destroy test
+# Create set to add a range
+0 ipset new test hash:ip,port -6 hashsize 64
+# Add a range which forces a resizing
+0 ipset add test 1::1,tcp:80-1105
+# Check that correct number of elements are added
+0 n=`ipset list test|grep 1::1|wc -l` && test $n -eq 1026
+# Destroy set
+0 ipset -X test
# eof
0 n=`ipset save test|wc -l` && test $n -eq 4
# Delete test set
0 ipset destroy test
+# Create set to add a range
+0 ipset new test hash:net,port hashsize 64
+# Add a range which forces a resizing
+0 ipset add test 10.0.0.0/24,tcp:80-1105
+# Check that correct number of elements are added
+0 n=`ipset list test|grep 10.0|wc -l` && test $n -eq 1026
+# Destroy set
+0 ipset -X test
# eof
0 n=`ipset save test|wc -l` && test $n -eq 4
# Delete test set
0 ipset destroy test
+# Create set to add a range
+0 ipset new test hash:net,port -6 hashsize 64
+# Add a range which forces a resizing
+0 ipset add test 1::1/64,tcp:80-1105
+# Check that correct number of elements are added
+0 n=`ipset list test|grep 1::|wc -l` && test $n -eq 1026
+# Destroy set
+0 ipset -X test
# eof