From 3fd6b24ace319b139ec3c4e3031a5f05d21e304e Mon Sep 17 00:00:00 2001 From: Jozsef Kadlecsik Date: Tue, 15 Jun 2010 13:30:55 +0200 Subject: ipset 5 in an almost ready state - milestone Reworked protocol and internal interfaces, missing set types added, backward compatibility verified, lots of tests added (and thanks to the tests, bugs fixed), even the manpage is rewritten ;-). Countless changes everywhere... The missing bits before announcing ipset 5: - net namespace support - new iptables/ip6tables extension library - iptables/ip6tables match and target tests (backward/forward compatibility) - tests on catching syntax errors --- kernel/include/linux/netfilter/ip_set.h | 246 ++--- kernel/include/linux/netfilter/ip_set_bitmap.h | 6 +- kernel/include/linux/netfilter/ip_set_chash.h | 1096 +++++++++++++++++++++++ kernel/include/linux/netfilter/ip_set_getport.h | 25 +- kernel/include/linux/netfilter/ip_set_hash.h | 5 +- kernel/include/linux/netfilter/ip_set_jhash.h | 152 ++-- kernel/include/linux/netfilter/ip_set_kernel.h | 20 + kernel/include/linux/netfilter/ip_set_list.h | 21 + kernel/include/linux/netfilter/ip_set_slist.h | 86 ++ kernel/include/linux/netfilter/ip_set_timeout.h | 35 +- kernel/include/linux/netfilter/ipt_set.h | 21 - kernel/include/linux/netfilter/xt_set.h | 55 ++ 12 files changed, 1503 insertions(+), 265 deletions(-) create mode 100644 kernel/include/linux/netfilter/ip_set_chash.h create mode 100644 kernel/include/linux/netfilter/ip_set_kernel.h create mode 100644 kernel/include/linux/netfilter/ip_set_list.h create mode 100644 kernel/include/linux/netfilter/ip_set_slist.h delete mode 100644 kernel/include/linux/netfilter/ipt_set.h create mode 100644 kernel/include/linux/netfilter/xt_set.h (limited to 'kernel/include/linux') diff --git a/kernel/include/linux/netfilter/ip_set.h b/kernel/include/linux/netfilter/ip_set.h index d0b47a0..e700503 100644 --- a/kernel/include/linux/netfilter/ip_set.h +++ b/kernel/include/linux/netfilter/ip_set.h @@ -11,14 +11,10 @@ * published by the Free Software Foundation. */ -#if 1 -#define IP_SET_DEBUG -#endif - /* The protocol version */ #define IPSET_PROTOCOL 5 -/* The max length of strings: set and type identifiers */ +/* The max length of strings including NUL: set and type identifiers */ #define IPSET_MAXNAMELEN 32 /* Message types and commands */ @@ -43,6 +39,7 @@ enum ipset_cmd { IPSET_CMD_RESTORE = IPSET_MSG_MAX, /* Enter restore mode */ IPSET_CMD_HELP, /* Get help */ IPSET_CMD_VERSION, /* Get program version */ + IPSET_CMD_QUIT, /* Quit from interactive mode */ IPSET_CMD_MAX, @@ -58,6 +55,7 @@ enum { IPSET_ATTR_SETNAME2 = IPSET_ATTR_TYPENAME, /* rename/swap */ IPSET_ATTR_REVISION, /* Settype revision */ IPSET_ATTR_FAMILY, /* Settype family */ + IPSET_ATTR_FLAGS, /* Flags at command level */ IPSET_ATTR_DATA, /* Nested attributes */ IPSET_ATTR_ADT, /* Multiple data containers */ IPSET_ATTR_LINENO, /* Restore lineno */ @@ -77,8 +75,8 @@ enum { IPSET_ATTR_PORT_FROM = IPSET_ATTR_PORT, IPSET_ATTR_PORT_TO, IPSET_ATTR_TIMEOUT, - IPSET_ATTR_FLAGS, - /* IPSET_ATTR_LINENO */ + IPSET_ATTR_CADT_FLAGS, + IPSET_ATTR_CADT_LINENO = IPSET_ATTR_LINENO, /* Reserve empty slots */ IPSET_ATTR_CADT_MAX = 16, /* Create-only specific attributes */ @@ -123,15 +121,19 @@ enum ipset_errno { IPSET_ERR_INVALID_NETMASK, IPSET_ERR_INVALID_FAMILY, IPSET_ERR_TIMEOUT, + IPSET_ERR_REFERENCED, + /* Type specific error codes */ IPSET_ERR_TYPE_SPECIFIC = 160, }; - -enum ipset_data_flags { + +enum ipset_cmd_flags { IPSET_FLAG_BIT_EXIST = 0, IPSET_FLAG_EXIST = (1 << IPSET_FLAG_BIT_EXIST), - - IPSET_FLAG_BIT_BEFORE = 2, +}; + +enum ipset_cadt_flags { + IPSET_FLAG_BIT_BEFORE = 0, IPSET_FLAG_BEFORE = (1 << IPSET_FLAG_BIT_BEFORE), }; @@ -140,35 +142,13 @@ enum ipset_adt { IPSET_ADD, IPSET_DEL, IPSET_TEST, - IPSET_CREATE, + IPSET_ADT_MAX, + IPSET_CREATE = IPSET_ADT_MAX, IPSET_CADT_MAX, }; -#ifndef __KERNEL__ -#ifdef IP_SET_DEBUG -#include -#include -#include -#define D(format, args...) do { \ - fprintf(stderr, "%s: %s: ", __FILE__, __FUNCTION__); \ - fprintf(stderr, format "\n" , ## args); \ -} while (0) -static inline void -dump_nla(struct nlattr *nla[], int maxlen) -{ - int i; - - for (i = 0; i < maxlen; i++) - D("nla[%u] does%s exist", i, !nla[i] ? " NOT" : ""); -} - -#else -#define D(format, args...) -#define dump_nla(nla, maxlen) -#endif -#endif /* !__KERNEL__ */ - #ifdef __KERNEL__ +#include #include #include #include @@ -176,19 +156,27 @@ dump_nla(struct nlattr *nla[], int maxlen) /* Sets are identified by an index in kernel space. Tweak with ip_set_id_t * and IPSET_INVALID_ID if you want to increase the max number of sets. */ -typedef uint16_t ip_set_id_t; +typedef u16 ip_set_id_t; #define IPSET_INVALID_ID 65535 +enum ip_set_dim { + IPSET_DIM_ZERO = 0, + IPSET_DIM_ONE, + IPSET_DIM_TWO, + IPSET_DIM_THREE, + /* Max dimension in elements. + * If changed, new revision of iptables match/target is required. + */ + IPSET_DIM_MAX = 6, +}; + /* Option flags for kernel operations */ enum ip_set_kopt { - /* Bit 0 is reserved */ - IPSET_SRC_FLAG = 1, - IPSET_SRC = (1 << IPSET_SRC_FLAG), - IPSET_DST_FLAG = 2, - IPSET_DST = (1 << IPSET_DST_FLAG), - IPSET_INV_FLAG = 3, - IPSET_INV = (1 << IPSET_INV_FLAG), + IPSET_INV_MATCH = (1 << IPSET_DIM_ZERO), + IPSET_DIM_ONE_SRC = (1 << IPSET_DIM_ONE), + IPSET_DIM_TWO_SRC = (1 << IPSET_DIM_TWO), + IPSET_DIM_THREE_SRC = (1 << IPSET_DIM_THREE), }; /* Set features */ @@ -203,72 +191,60 @@ enum ip_set_feature { IPSET_TYPE_IP2 = (1 << IPSET_TYPE_IP2_FLAG), IPSET_TYPE_NAME_FLAG = 4, IPSET_TYPE_NAME = (1 << IPSET_TYPE_NAME_FLAG), + /* Actually just a flag for dumping */ + IPSET_DUMP_LAST_FLAG = 7, + IPSET_DUMP_LAST = (1 << IPSET_DUMP_LAST_FLAG), }; +/* Calculate the bytes required to store the inclusive range of a-b */ static inline int -bitmap_bytes(uint32_t a, uint32_t b) +bitmap_bytes(u32 a, u32 b) { return 4 * ((((b - a + 8) / 8) + 3) / 4); } -#define ip_set_printk(format, args...) \ - do { \ - printk("%s: %s: ", __FILE__, __FUNCTION__); \ - printk(format "\n" , ## args); \ - } while (0) - -#if defined(IP_SET_DEBUG) -#define D(format, args...) \ - do { \ - printk("%s: %s (DBG): ", __FILE__, __FUNCTION__);\ - printk(format "\n" , ## args); \ - } while (0) - -static inline void -dump_nla(const struct nlattr * const nla[], int maxlen) -{ - int i; - - for (i = 0; i < maxlen; i++) - printk("nlattr[%u] does%s exist\n", i, nla[i] ? "" : " NOT"); -} -#else -#define D(format, args...) -#define dump_nla(nla, maxlen) -#endif - struct ip_set; +typedef int (*ipset_adtfn)(struct ip_set *set, void *value, + gfp_t gfp_flags, u32 timeout); + /* Set type, variant-specific part */ struct ip_set_type_variant { /* Kernelspace: test/add/del entries */ int (*kadt)(struct ip_set *set, const struct sk_buff * skb, - enum ipset_adt adt, uint8_t pf, const uint8_t *flags); + enum ipset_adt adt, u8 pf, u8 dim, u8 flags); /* Userspace: test/add/del entries */ int (*uadt)(struct ip_set *set, struct nlattr *head, int len, - enum ipset_adt adt, uint32_t *lineno, uint32_t flags); + enum ipset_adt adt, u32 *lineno, u32 flags); + + /* Low level add/del/test entries */ + ipset_adtfn adt[IPSET_ADT_MAX]; /* When adding entries and set is full, try to resize the set */ - int (*resize)(struct ip_set *set, uint8_t retried); + int (*resize)(struct ip_set *set, gfp_t gfp_flags, bool retried); /* Destroy the set */ void (*destroy)(struct ip_set *set); /* Flush the elements */ void (*flush)(struct ip_set *set); - + /* Expire entries before listing */ + void (*expire)(struct ip_set *set); /* List set header data */ int (*head)(struct ip_set *set, struct sk_buff *skb); /* List elements */ int (*list)(struct ip_set *set, struct sk_buff *skb, struct netlink_callback *cb); + + /* Return true if "b" set is the same as "a" + * according to the set parameters */ + bool (*same_set)(const struct ip_set *a, const struct ip_set *b); }; /* Flags for the set type variants */ enum ip_set_type_flags { - IP_SET_FLAG_VMALLOC_BIT = 0, - IP_SET_FLAG_VMALLOC = (1 << IP_SET_FLAG_VMALLOC_BIT), - IP_SET_FLAG_TIMEOUT_BIT = 1, - IP_SET_FLAG_TIMEOUT = (1 << IP_SET_FLAG_TIMEOUT_BIT), + /* Set members created by kmalloc */ + IP_SET_FLAG_KMALLOC_BIT = 0, + IP_SET_FLAG_KMALLOC = (1 << IP_SET_FLAG_KMALLOC_BIT), }; /* The core set type structure */ @@ -278,17 +254,19 @@ struct ip_set_type { /* Typename */ char name[IPSET_MAXNAMELEN]; /* Protocol version */ - uint8_t protocol; + u8 protocol; /* Set features to control swapping */ - uint8_t features; + u8 features; + /* Set type dimension */ + u8 dimension; /* Supported family: may be AF_UNSPEC for both AF_INET/AF_INET6 */ - uint8_t family; + u8 family; /* Type revision */ - uint8_t revision; + u8 revision; /* Create set */ int (*create)(struct ip_set *set, - struct nlattr *head, int len, uint32_t flags); + struct nlattr *head, int len, u32 flags); /* Set this to THIS_MODULE if you are a module, otherwise NULL */ struct module *me; @@ -310,86 +288,90 @@ struct ip_set { /* The type variant doing the real job */ const struct ip_set_type_variant *variant; /* The actual INET family */ - uint8_t family; + u8 family; /* Set type flags, filled/modified by create/resize */ - uint8_t flags; + u8 flags; /* The type specific data */ void *data; }; /* register and unregister set references */ -extern ip_set_id_t ip_set_get_byname(const char name[IPSET_MAXNAMELEN]); +extern ip_set_id_t ip_set_get_byname(const char *name, struct ip_set **set); extern void ip_set_put_byindex(ip_set_id_t index); +extern const char * ip_set_name_byindex(ip_set_id_t index); +extern ip_set_id_t ip_set_nfnl_get(const char *name); +extern ip_set_id_t ip_set_nfnl_get_byindex(ip_set_id_t index); +extern void ip_set_nfnl_put(ip_set_id_t index); /* API for iptables set match, and SET target */ extern int ip_set_add(ip_set_id_t id, const struct sk_buff *skb, - uint8_t family, const uint8_t *flags); + u8 family, u8 dim, u8 flags); extern int ip_set_del(ip_set_id_t id, const struct sk_buff *skb, - uint8_t family, const uint8_t *flags); + u8 family, u8 dim, u8 flags); extern int ip_set_test(ip_set_id_t id, const struct sk_buff *skb, - uint8_t family, const uint8_t *flags); + u8 family, u8 dim, u8 flags); /* Allocate members */ static inline void * -ip_set_alloc(size_t size, gfp_t gfp_mask, uint8_t *flags) +ip_set_alloc(size_t size, gfp_t gfp_mask, u8 *flags) { - void *members = kzalloc(size, gfp_mask); + void *members = kzalloc(size, gfp_mask | __GFP_NOWARN); if (members) { - *flags &= ~IP_SET_FLAG_VMALLOC; - D("allocated with kmalloc %p", members); + *flags |= IP_SET_FLAG_KMALLOC; + pr_debug("%p: allocated with kmalloc", members); return members; } members = __vmalloc(size, gfp_mask | __GFP_ZERO, PAGE_KERNEL); if (!members) return NULL; - *flags |= IP_SET_FLAG_VMALLOC; - D("allocated with vmalloc %p", members); + *flags &= ~IP_SET_FLAG_KMALLOC; + pr_debug("%p: allocated with vmalloc", members); return members; } static inline void -ip_set_free(void *members, uint8_t flags) +ip_set_free(void *members, u8 flags) { - D("free with %s %p", flags & IP_SET_FLAG_VMALLOC ? "vmalloc" : "kmalloc", - members); - if (flags & IP_SET_FLAG_VMALLOC) - vfree(members); - else + pr_debug("%p: free with %s", members, + flags & IP_SET_FLAG_KMALLOC ? "kmalloc" : "vmalloc"); + if (flags & IP_SET_FLAG_KMALLOC) kfree(members); + else + vfree(members); } /* Useful converters */ -static inline uint32_t +static inline u32 ip_set_get_h32(const struct nlattr *attr) { - uint32_t value = nla_get_u32(attr); + u32 value = nla_get_u32(attr); return attr->nla_type & NLA_F_NET_BYTEORDER ? ntohl(value) : value; } -static inline uint16_t +static inline u16 ip_set_get_h16(const struct nlattr *attr) { - uint16_t value = nla_get_u16(attr); + u16 value = nla_get_u16(attr); return attr->nla_type & NLA_F_NET_BYTEORDER ? ntohs(value) : value; } -static inline uint32_t +static inline u32 ip_set_get_n32(const struct nlattr *attr) { - uint32_t value = nla_get_u32(attr); + u32 value = nla_get_u32(attr); return attr->nla_type & NLA_F_NET_BYTEORDER ? value : htonl(value); } -static inline uint16_t +static inline u16 ip_set_get_n16(const struct nlattr *attr) { - uint16_t value = nla_get_u16(attr); + u16 value = nla_get_u16(attr); return attr->nla_type & NLA_F_NET_BYTEORDER ? value : htons(value); } @@ -404,31 +386,49 @@ ip_set_get_n16(const struct nlattr *attr) NLA_PUT_BE16(skb, type | NLA_F_NET_BYTEORDER, value) /* Get address from skbuff */ -static inline uint32_t -ip4addr(const struct sk_buff *skb, const uint8_t *flags) +static inline u32 +ip4addr(const struct sk_buff *skb, bool src) { - return flags[0] & IPSET_SRC ? ip_hdr(skb)->saddr - : ip_hdr(skb)->daddr; + return src ? ip_hdr(skb)->saddr : ip_hdr(skb)->daddr; } static inline void -ip4addrptr(const struct sk_buff *skb, const uint8_t *flags, uint32_t *addr) +ip4addrptr(const struct sk_buff *skb, bool src, u32 *addr) { - *addr = flags[0] & IPSET_SRC ? ip_hdr(skb)->saddr - : ip_hdr(skb)->daddr; + *addr = src ? ip_hdr(skb)->saddr : ip_hdr(skb)->daddr; } static inline void -ip6addrptr(const struct sk_buff *skb, const uint8_t *flags, - struct in6_addr *addr) +ip6addrptr(const struct sk_buff *skb, bool src, struct in6_addr *addr) { - memcpy(addr, flags[0] & IPSET_SRC ? &ipv6_hdr(skb)->saddr - : &ipv6_hdr(skb)->daddr, + memcpy(addr, src ? &ipv6_hdr(skb)->saddr : &ipv6_hdr(skb)->daddr, sizeof(*addr)); } -#define pack_ip_port(map, ip, port) \ - (port + ((ip - ((map)->first_ip)) << 16)) +/* Interface to iptables/ip6tables */ + +#define SO_IP_SET 83 + +union ip_set_name_index { + char name[IPSET_MAXNAMELEN]; + ip_set_id_t index; +}; + +#define IP_SET_OP_GET_BYNAME 0x00000006 /* Get set index by name */ +struct ip_set_req_get_set { + unsigned op; + unsigned version; + union ip_set_name_index set; +}; + +#define IP_SET_OP_GET_BYINDEX 0x00000007 /* Get set name by index */ +/* Uses ip_set_req_get_set */ + +#define IP_SET_OP_VERSION 0x00000100 /* Ask kernel version */ +struct ip_set_req_version { + unsigned op; + unsigned version; +}; #endif /* __KERNEL__ */ diff --git a/kernel/include/linux/netfilter/ip_set_bitmap.h b/kernel/include/linux/netfilter/ip_set_bitmap.h index 49d0f5c..0d067d0 100644 --- a/kernel/include/linux/netfilter/ip_set_bitmap.h +++ b/kernel/include/linux/netfilter/ip_set_bitmap.h @@ -12,10 +12,10 @@ enum { /* Common functions */ -static inline uint32_t -range_to_mask(uint32_t from, uint32_t to, uint8_t *bits) +static inline u32 +range_to_mask(u32 from, u32 to, u8 *bits) { - uint32_t mask = 0xFFFFFFFE; + u32 mask = 0xFFFFFFFE; *bits = 32; while (--(*bits) > 0 && mask && (to & mask) != from) diff --git a/kernel/include/linux/netfilter/ip_set_chash.h b/kernel/include/linux/netfilter/ip_set_chash.h new file mode 100644 index 0000000..0d77a5d --- /dev/null +++ b/kernel/include/linux/netfilter/ip_set_chash.h @@ -0,0 +1,1096 @@ +#ifndef _IP_SET_CHASH_H +#define _IP_SET_CHASH_H + +#include +#include +#include + +#define CONCAT(a, b, c) a##b##c +#define TOKEN(a, b, c) CONCAT(a, b, c) + +/* Cache friendly hash with resizing when linear searching becomes too long. + * Internally jhash is used with the assumption that the size of the stored + * data is a multiple of sizeof(u32). If storage supports timeout, the + * timeout field must be the last one in the data structure. + */ + +/* Number of elements to store in an array block */ +#define CHASH_DEFAULT_ARRAY_SIZE 4 +/* Number of arrays: max ARRAY_SIZE * CHAIN_LIMIT "long" chains */ +#define CHASH_DEFAULT_CHAIN_LIMIT 3 + +struct chash_nets { + u32 nets; /* number of elements per cidr */ + u8 cidr; /* the cidr values added to the set */ +}; + +struct chash { + struct slist *htable; /* Hashtable of single linked lists */ + u32 maxelem; /* Max elements in the hash */ + u32 elements; /* Current element (vs timeout) */ + u32 initval; /* random jhash init value */ + u32 timeout; /* timeout value, if enabled */ + struct timer_list gc; /* garbage collection when timeout enabled */ + u8 htable_bits; /* size of hash table == 2^htable_bits */ + u8 array_size; /* number of elements in an array */ + u8 chain_limit; /* max number of arrays */ +#ifdef IP_SET_HASH_WITH_NETMASK + u8 netmask; /* netmask value for subnets to store */ +#endif +#ifdef IP_SET_HASH_WITH_NETS + struct chash_nets nets[0]; /* book keeping of networks */ +#endif +}; + +static inline u8 +htable_bits(u32 hashsize) +{ + /* Assume that hashsize == 2^htable_bits */ + u8 bits = fls(hashsize - 1); + if (jhash_size(bits) != hashsize) + /* Round up to the first 2^n value */ + bits = fls(hashsize); + + return bits; +} + +static inline void +add_cidr(struct chash_nets *nets, u8 host_mask, u8 cidr) +{ + u8 i; + + pr_debug("add_cidr %u", cidr); + for (i = 0; i < host_mask - 1 && nets[i].cidr; i++) { + /* Add in increasing prefix order, so larger cidr first */ + if (nets[i].cidr < cidr) + swap(nets[i].cidr, cidr); + } + if (i < host_mask - 1) + nets[i].cidr = cidr; +} + +static inline void +del_cidr(struct chash_nets *nets, u8 host_mask, u8 cidr) +{ + u8 i; + + pr_debug("del_cidr %u", cidr); + for (i = 0; i < host_mask - 2 && nets[i].cidr; i++) { + if (nets[i].cidr == cidr) + nets[i].cidr = cidr = nets[i+1].cidr; + } + nets[host_mask - 2].cidr = 0; +} + +static void +chash_destroy(struct slist *t, u8 htable_bits, u8 flags) +{ + struct slist *n, *tmp; + u32 i; + + for (i = 0; i < jhash_size(htable_bits); i++) + slist_for_each_safe(n, tmp, &t[i]) + /* FIXME: slab cache */ + kfree(n); + + ip_set_free(t, flags); +} + +static size_t +chash_memsize(const struct chash *h, size_t dsize, u8 host_mask) +{ + struct slist *n; + u32 i; + size_t memsize = sizeof(*h) +#ifdef IP_SET_HASH_WITH_NETS + + sizeof(struct chash_nets) * (host_mask - 1) +#endif + + jhash_size(h->htable_bits) * sizeof(struct slist); + + for (i = 0; i < jhash_size(h->htable_bits); i++) + slist_for_each(n, &h->htable[i]) + memsize += sizeof(struct slist) + + h->array_size * dsize; + + return memsize; +} + +static void +ip_set_hash_flush(struct ip_set *set) +{ + struct chash *h = set->data; + struct slist *n, *tmp; + u32 i; + + for (i = 0; i < jhash_size(h->htable_bits); i++) { + slist_for_each_safe(n, tmp, &h->htable[i]) + /* FIXME: slab cache */ + kfree(n); + h->htable[i].next = NULL; + } +#ifdef IP_SET_HASH_WITH_NETS + memset(h->nets, 0, sizeof(struct chash_nets) + * (set->family == AF_INET ? 31 : 127)); +#endif + h->elements = 0; +} + +static void +ip_set_hash_destroy(struct ip_set *set) +{ + struct chash *h = set->data; + + if (with_timeout(h->timeout)) + del_timer_sync(&h->gc); + + chash_destroy(h->htable, h->htable_bits, set->flags); + kfree(h); + + set->data = NULL; +} + +#define JHASH2(data, initval, htable_bits) \ +jhash2((u32 *)(data), sizeof(struct type_pf_elem)/sizeof(u32), initval) \ + & jhash_mask(htable_bits) + +#endif /* _IP_SET_CHASH_H */ + +/* Type/family dependent function prototypes */ + +#define type_pf_data_equal TOKEN(TYPE, PF, _data_equal) +#define type_pf_data_isnull TOKEN(TYPE, PF, _data_isnull) +#define type_pf_data_copy TOKEN(TYPE, PF, _data_copy) +#define type_pf_data_swap TOKEN(TYPE, PF, _data_swap) +#define type_pf_data_zero_out TOKEN(TYPE, PF, _data_zero_out) +#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_elem TOKEN(TYPE, PF, _elem) +#define type_pf_telem TOKEN(TYPE, PF, _telem) +#define type_pf_data_timeout TOKEN(TYPE, PF, _data_timeout) +#define type_pf_data_expired TOKEN(TYPE, PF, _data_expired) +#define type_pf_data_swap_timeout TOKEN(TYPE, PF, _data_swap_timeout) +#define type_pf_data_timeout_set TOKEN(TYPE, PF, _data_timeout_set) + +#define type_pf_chash_readd TOKEN(TYPE, PF, _chash_readd) +#define type_pf_chash_del_elem TOKEN(TYPE, PF, _chash_del_elem) +#define type_pf_chash_add TOKEN(TYPE, PF, _chash_add) +#define type_pf_chash_del TOKEN(TYPE, PF, _chash_del) +#define type_pf_chash_test_cidrs TOKEN(TYPE, PF, _chash_test_cidrs) +#define type_pf_chash_test TOKEN(TYPE, PF, _chash_test) + +#define type_pf_chash_treadd TOKEN(TYPE, PF, _chash_treadd) +#define type_pf_chash_del_telem TOKEN(TYPE, PF, _chash_del_telem) +#define type_pf_chash_expire TOKEN(TYPE, PF, _chash_expire) +#define type_pf_chash_tadd TOKEN(TYPE, PF, _chash_tadd) +#define type_pf_chash_tdel TOKEN(TYPE, PF, _chash_tdel) +#define type_pf_chash_ttest_cidrs TOKEN(TYPE, PF, _chash_ttest_cidrs) +#define type_pf_chash_ttest TOKEN(TYPE, PF, _chash_ttest) + +#define type_pf_resize TOKEN(TYPE, PF, _resize) +#define type_pf_tresize TOKEN(TYPE, PF, _tresize) +#define type_pf_flush ip_set_hash_flush +#define type_pf_destroy ip_set_hash_destroy +#define type_pf_head TOKEN(TYPE, PF, _head) +#define type_pf_list TOKEN(TYPE, PF, _list) +#define type_pf_tlist TOKEN(TYPE, PF, _tlist) +#define type_pf_same_set TOKEN(TYPE, PF, _same_set) +#define type_pf_kadt TOKEN(TYPE, PF, _kadt) +#define type_pf_uadt TOKEN(TYPE, PF, _uadt) +#define type_pf_gc TOKEN(TYPE, PF, _gc) +#define type_pf_gc_init TOKEN(TYPE, PF, _gc_init) +#define type_pf_variant TOKEN(TYPE, PF, _variant) +#define type_pf_tvariant TOKEN(TYPE, PF, _tvariant) + +/* Flavour without timeout */ + +#define chash_data(n, i) \ +(struct type_pf_elem *)((char *)(n) + sizeof(struct slist) + (i)*sizeof(struct type_pf_elem)) + +static int +type_pf_chash_readd(struct chash *h, struct slist *t, u8 htable_bits, + const struct type_pf_elem *value, gfp_t gfp_flags) +{ + struct slist *n, *prev; + struct type_pf_elem *data; + void *tmp; + int i = 0, j = 0; + u32 hash = JHASH2(value, h->initval, htable_bits); + + slist_for_each_prev(prev, n, &t[hash]) { + for (i = 0; i < h->array_size; i++) { + data = chash_data(n, i); + if (type_pf_data_isnull(data)) { + tmp = n; + goto found; + } + } + j++; + } + if (j < h->chain_limit) { + tmp = kzalloc(h->array_size * sizeof(struct type_pf_elem) + + sizeof(struct slist), gfp_flags); + if (!tmp) + return -ENOMEM; + prev->next = (struct slist *) tmp; + data = chash_data(tmp, 0); + } else { + /* Rehashing */ + return -EAGAIN; + } +found: + type_pf_data_copy(data, value); + return 0; +} + +static void +type_pf_chash_del_elem(struct chash *h, struct slist *prev, + struct slist *n, int i) +{ + struct type_pf_elem *data = chash_data(n, i); + struct slist *tmp; + int j; + + if (n->next != NULL) { + for (prev = n, tmp = n->next; + tmp->next != NULL; + prev = tmp, tmp = tmp->next) + /* Find last array */; + j = 0; + } else { + /* Already at last array */ + tmp = n; + j = i; + } + /* Find last non-empty element */ + for (; j < h->array_size - 1; j++) + if (type_pf_data_isnull(chash_data(tmp, j + 1))) + break; + + if (!(tmp == n && i == j)) { + type_pf_data_swap(data, chash_data(tmp, j)); + } +#ifdef IP_SET_HASH_WITH_NETS + if (--h->nets[data->cidr-1].nets == 0) + del_cidr(h->nets, HOST_MASK, data->cidr); +#endif + if (j == 0) { + prev->next = NULL; + kfree(tmp); + } else + type_pf_data_zero_out(chash_data(tmp, j)); + + h->elements--; +} + +static int +type_pf_resize(struct ip_set *set, gfp_t gfp_flags, bool retried) +{ + struct chash *h = set->data; + u8 htable_bits = h->htable_bits; + struct slist *t, *n; + const struct type_pf_elem *data; + u32 i, j; + u8 oflags, flags; + int ret; + +retry: + ret = 0; + htable_bits++; + if (!htable_bits) + /* In case we have plenty of memory :-) */ + return -IPSET_ERR_HASH_FULL; + t = ip_set_alloc(jhash_size(htable_bits) * sizeof(struct slist), + gfp_flags, &flags); + if (!t) + return -ENOMEM; + + write_lock_bh(&set->lock); + flags = oflags = set->flags; + for (i = 0; i < jhash_size(h->htable_bits); i++) { +next_slot: + slist_for_each(n, &h->htable[i]) { + for (j = 0; j < h->array_size; j++) { + data = chash_data(n, j); + if (type_pf_data_isnull(data)) { + i++; + goto next_slot; + } + ret = type_pf_chash_readd(h, t, htable_bits, + data, gfp_flags); + if (ret < 0) { + write_unlock_bh(&set->lock); + chash_destroy(t, htable_bits, flags); + if (ret == -EAGAIN) + goto retry; + return ret; + } + } + } + } + + n = h->htable; + i = h->htable_bits; + + h->htable = t; + h->htable_bits = htable_bits; + set->flags = flags; + write_unlock_bh(&set->lock); + + chash_destroy(n, i, oflags); + + return 0; +} + +static int +type_pf_chash_add(struct ip_set *set, void *value, + gfp_t gfp_flags, u32 timeout) +{ + struct chash *h = set->data; + const struct type_pf_elem *d = value; + struct slist *n, *prev, *t = h->htable; + struct type_pf_elem *data; + void *tmp; + int i = 0, j = 0; + u32 hash; + +#ifdef IP_SET_HASH_WITH_NETS + if (h->elements >= h->maxelem || h->nets[d->cidr-1].nets == UINT_MAX) +#else + if (h->elements >= h->maxelem) +#endif + return -IPSET_ERR_HASH_FULL; + + hash = JHASH2(value, h->initval, h->htable_bits); + slist_for_each_prev(prev, n, &t[hash]) { + for (i = 0; i < h->array_size; i++) { + data = chash_data(n, i); + if (type_pf_data_isnull(data)) { + tmp = n; + goto found; + } + if (type_pf_data_equal(data, d)) + return -IPSET_ERR_EXIST; + } + j++; + } + if (j < h->chain_limit) { + tmp = kzalloc(h->array_size * sizeof(struct type_pf_elem) + + sizeof(struct slist), gfp_flags); + if (!tmp) + return -ENOMEM; + prev->next = (struct slist *) tmp; + data = chash_data(tmp, 0); + } else { + /* Rehashing */ + return -EAGAIN; + } +found: + type_pf_data_copy(data, d); +#ifdef IP_SET_HASH_WITH_NETS + if (h->nets[d->cidr-1].nets++ == 0) + add_cidr(h->nets, HOST_MASK, d->cidr); +#endif + h->elements++; + return 0; +} + +static int +type_pf_chash_del(struct ip_set *set, void *value, + gfp_t gfp_flags, u32 timeout) +{ + struct chash *h = set->data; + const struct type_pf_elem *d = value; + struct slist *n, *prev; + int i; + struct type_pf_elem *data; + u32 hash = JHASH2(value, h->initval, h->htable_bits); + + slist_for_each_prev(prev, n, &h->htable[hash]) + for (i = 0; i < h->array_size; i++) { + data = chash_data(n, i); + if (type_pf_data_isnull(data)) + return -IPSET_ERR_EXIST; + if (type_pf_data_equal(data, d)) { + type_pf_chash_del_elem(h, prev, n, i); + return 0; + } + } + + return -IPSET_ERR_EXIST; +} + +#ifdef IP_SET_HASH_WITH_NETS +static inline int +type_pf_chash_test_cidrs(struct ip_set *set, + struct type_pf_elem *d, + gfp_t gfp_flags, u32 timeout) +{ + struct chash *h = set->data; + struct slist *n; + const struct type_pf_elem *data; + int i, j = 0; + u32 hash; + u8 host_mask = set->family == AF_INET ? 32 : 128; + +retry: + pr_debug("test by nets"); + for (; j < host_mask - 1 && h->nets[j].cidr; j++) { + type_pf_data_netmask(d, h->nets[j].cidr); + hash = JHASH2(d, h->initval, h->htable_bits); + slist_for_each(n, &h->htable[hash]) + for (i = 0; i < h->array_size; i++) { + data = chash_data(n, i); + if (type_pf_data_isnull(data)) { + j++; + goto retry; + } + if (type_pf_data_equal(data, d)) + return 1; + } + } + return 0; +} +#endif + +static inline int +type_pf_chash_test(struct ip_set *set, void *value, + gfp_t gfp_flags, u32 timeout) +{ + struct chash *h = set->data; + struct type_pf_elem *d = value; + struct slist *n; + const struct type_pf_elem *data; + int i; + u32 hash; +#ifdef IP_SET_HASH_WITH_NETS + u8 host_mask = set->family == AF_INET ? 32 : 128; + + if (d->cidr == host_mask) + return type_pf_chash_test_cidrs(set, d, gfp_flags, timeout); +#endif + + hash = JHASH2(d, h->initval, h->htable_bits); + slist_for_each(n, &h->htable[hash]) + for (i = 0; i < h->array_size; i++) { + data = chash_data(n, i); + if (type_pf_data_isnull(data)) + return 0; + if (type_pf_data_equal(data, d)) + return 1; + } + return 0; +} + +static int +type_pf_head(struct ip_set *set, struct sk_buff *skb) +{ + const struct chash *h = set->data; + struct nlattr *nested; + size_t memsize; + + read_lock_bh(&set->lock); + memsize = chash_memsize(h, with_timeout(h->timeout) + ? sizeof(struct type_pf_telem) + : sizeof(struct type_pf_elem), + set->family == AF_INET ? 32 : 128); + read_unlock_bh(&set->lock); + + nested = ipset_nest_start(skb, IPSET_ATTR_DATA); + if (!nested) + goto nla_put_failure; + NLA_PUT_NET32(skb, IPSET_ATTR_HASHSIZE, + htonl(jhash_size(h->htable_bits))); + NLA_PUT_NET32(skb, IPSET_ATTR_MAXELEM, htonl(h->maxelem)); +#ifdef IP_SET_HASH_WITH_NETMASK + if (h->netmask != HOST_MASK) + NLA_PUT_U8(skb, IPSET_ATTR_NETMASK, h->netmask); +#endif + NLA_PUT_NET32(skb, IPSET_ATTR_REFERENCES, + htonl(atomic_read(&set->ref) - 1)); + NLA_PUT_NET32(skb, IPSET_ATTR_MEMSIZE, htonl(memsize)); + if (with_timeout(h->timeout)) + NLA_PUT_NET32(skb, IPSET_ATTR_TIMEOUT, htonl(h->timeout)); + ipset_nest_end(skb, nested); + + return 0; +nla_put_failure: + return -EFAULT; +} + +static int +type_pf_list(struct ip_set *set, + struct sk_buff *skb, struct netlink_callback *cb) +{ + const struct chash *h = set->data; + struct nlattr *atd, *nested; + struct slist *n; + const struct type_pf_elem *data; + u32 first = cb->args[2]; + int i; + + atd = ipset_nest_start(skb, IPSET_ATTR_ADT); + if (!atd) + return -EFAULT; + pr_debug("list hash set %s", set->name); + for (; cb->args[2] < jhash_size(h->htable_bits); cb->args[2]++) { + slist_for_each(n, &h->htable[cb->args[2]]) { + for (i = 0; i < h->array_size; i++) { + data = chash_data(n, i); + if (type_pf_data_isnull(data)) + break; + pr_debug("list hash %lu slist %p i %u", + cb->args[2], n, i); + nested = ipset_nest_start(skb, IPSET_ATTR_DATA); + if (!nested) { + if (cb->args[2] == first) { + nla_nest_cancel(skb, atd); + return -EFAULT; + } else + goto nla_put_failure; + } + if (type_pf_data_list(skb, data)) + goto nla_put_failure; + ipset_nest_end(skb, nested); + } + } + } + ipset_nest_end(skb, atd); + /* Set listing finished */ + cb->args[2] = 0; + + return 0; + +nla_put_failure: + nla_nest_cancel(skb, nested); + ipset_nest_end(skb, atd); + return 0; +} + +static int +type_pf_kadt(struct ip_set *set, const struct sk_buff * skb, + enum ipset_adt adt, u8 pf, u8 dim, u8 flags); +static int +type_pf_uadt(struct ip_set *set, struct nlattr *head, int len, + enum ipset_adt adt, u32 *lineno, u32 flags); + +static const struct ip_set_type_variant type_pf_variant __read_mostly = { + .kadt = type_pf_kadt, + .uadt = type_pf_uadt, + .adt = { + [IPSET_ADD] = type_pf_chash_add, + [IPSET_DEL] = type_pf_chash_del, + [IPSET_TEST] = type_pf_chash_test, + }, + .destroy = type_pf_destroy, + .flush = type_pf_flush, + .head = type_pf_head, + .list = type_pf_list, + .resize = type_pf_resize, + .same_set = type_pf_same_set, +}; + +/* Flavour with timeout support */ + +#define chash_tdata(n, i) \ +(struct type_pf_elem *)((char *)(n) + sizeof(struct slist) + (i)*sizeof(struct type_pf_telem)) + +static inline u32 +type_pf_data_timeout(const struct type_pf_elem *data) +{ + const struct type_pf_telem *tdata = + (const struct type_pf_telem *) data; + + return tdata->timeout; +} + +static inline bool +type_pf_data_expired(const struct type_pf_elem *data) +{ + const struct type_pf_telem *tdata = + (const struct type_pf_telem *) data; + + return ip_set_timeout_expired(tdata->timeout); +} + +static inline void +type_pf_data_swap_timeout(struct type_pf_elem *src, + struct type_pf_elem *dst) +{ + struct type_pf_telem *x = (struct type_pf_telem *) src; + struct type_pf_telem *y = (struct type_pf_telem *) dst; + + swap(x->timeout, y->timeout); +} + +static inline void +type_pf_data_timeout_set(struct type_pf_elem *data, u32 timeout) +{ + struct type_pf_telem *tdata = (struct type_pf_telem *) data; + + tdata->timeout = ip_set_timeout_set(timeout); +} + +static int +type_pf_chash_treadd(struct chash *h, struct slist *t, u8 htable_bits, + const struct type_pf_elem *value, + gfp_t gfp_flags, u32 timeout) +{ + struct slist *n, *prev; + struct type_pf_elem *data; + void *tmp; + int i = 0, j = 0; + u32 hash = JHASH2(value, h->initval, htable_bits); + + slist_for_each_prev(prev, n, &t[hash]) { + for (i = 0; i < h->array_size; i++) { + data = chash_tdata(n, i); + if (type_pf_data_isnull(data)) { + tmp = n; + goto found; + } + } + j++; + } + if (j < h->chain_limit) { + tmp = kzalloc(h->array_size * sizeof(struct type_pf_telem) + + sizeof(struct slist), gfp_flags); + if (!tmp) + return -ENOMEM; + prev->next = (struct slist *) tmp; + data = chash_tdata(tmp, 0); + } else { + /* Rehashing */ + return -EAGAIN; + } +found: + type_pf_data_copy(data, value); + type_pf_data_timeout_set(data, timeout); + return 0; +} + +static void +type_pf_chash_del_telem(struct chash *h, struct slist *prev, + struct slist *n, int i) +{ + struct type_pf_elem *d, *data = chash_tdata(n, i); + struct slist *tmp; + int j; + + pr_debug("del %u", i); + if (n->next != NULL) { + for (prev = n, tmp = n->next; + tmp->next != NULL; + prev = tmp, tmp = tmp->next) + /* Find last array */; + j = 0; + } else { + /* Already at last array */ + tmp = n; + j = i; + } + /* Find last non-empty element */ + for (; j < h->array_size - 1; j++) + if (type_pf_data_isnull(chash_tdata(tmp, j + 1))) + break; + + d = chash_tdata(tmp, j); + if (!(tmp == n && i == j)) { + type_pf_data_swap(data, d); + type_pf_data_swap_timeout(data, d); + } +#ifdef IP_SET_HASH_WITH_NETS + if (--h->nets[data->cidr-1].nets == 0) + del_cidr(h->nets, HOST_MASK, data->cidr); +#endif + if (j == 0) { + prev->next = NULL; + kfree(tmp); + } else + type_pf_data_zero_out(d); + + h->elements--; +} + +static void +type_pf_chash_expire(struct chash *h) +{ + struct slist *n, *prev; + struct type_pf_elem *data; + u32 i; + int j; + + for (i = 0; i < jhash_size(h->htable_bits); i++) + slist_for_each_prev(prev, n, &h->htable[i]) + for (j = 0; j < h->array_size; j++) { + data = chash_tdata(n, j); + if (type_pf_data_isnull(data)) + break; + if (type_pf_data_expired(data)) { + pr_debug("expire %u/%u", i, j); + type_pf_chash_del_telem(h, prev, n, j); + } + } +} + +static int +type_pf_tresize(struct ip_set *set, gfp_t gfp_flags, bool retried) +{ + struct chash *h = set->data; + u8 htable_bits = h->htable_bits; + struct slist *t, *n; + const struct type_pf_elem *data; + u32 i, j; + u8 oflags, flags; + int ret; + + /* Try to cleanup once */ + if (!retried) { + i = h->elements; + write_lock_bh(&set->lock); + type_pf_chash_expire(set->data); + write_unlock_bh(&set->lock); + if (h->elements < i) + return 0; + } + +retry: + ret = 0; + htable_bits++; + if (!htable_bits) + /* In case we have plenty of memory :-) */ + return -IPSET_ERR_HASH_FULL; + t = ip_set_alloc(jhash_size(htable_bits) * sizeof(struct slist), + gfp_flags, &flags); + if (!t) + return -ENOMEM; + + write_lock_bh(&set->lock); + flags = oflags = set->flags; + for (i = 0; i < jhash_size(h->htable_bits); i++) { +next_slot: + slist_for_each(n, &h->htable[i]) { + for (j = 0; j < h->array_size; j++) { + data = chash_tdata(n, j); + if (type_pf_data_isnull(data)) { + i++; + goto next_slot; + } + ret = type_pf_chash_treadd(h, t, htable_bits, + data, gfp_flags, + type_pf_data_timeout(data)); + if (ret < 0) { + write_unlock_bh(&set->lock); + chash_destroy(t, htable_bits, flags); + if (ret == -EAGAIN) + goto retry; + return ret; + } + } + } + } + + n = h->htable; + i = h->htable_bits; + + h->htable = t; + h->htable_bits = htable_bits; + set->flags = flags; + write_unlock_bh(&set->lock); + + chash_destroy(n, i, oflags); + + return 0; +} + +static int +type_pf_chash_tadd(struct ip_set *set, void *value, + gfp_t gfp_flags, u32 timeout) +{ + struct chash *h = set->data; + const struct type_pf_elem *d = value; + struct slist *n, *prev, *t = h->htable; + struct type_pf_elem *data; + void *tmp; + int i = 0, j = 0; + u32 hash; + + if (h->elements >= h->maxelem) + /* FIXME: when set is full, we slow down here */ + type_pf_chash_expire(h); +#ifdef IP_SET_HASH_WITH_NETS + if (h->elements >= h->maxelem || h->nets[d->cidr-1].nets == UINT_MAX) +#else + if (h->elements >= h->maxelem) +#endif + return -IPSET_ERR_HASH_FULL; + + hash = JHASH2(d, h->initval, h->htable_bits); + slist_for_each_prev(prev, n, &t[hash]) { + for (i = 0; i < h->array_size; i++) { + data = chash_tdata(n, i); + if (type_pf_data_isnull(data) + || type_pf_data_expired(data)) { + tmp = n; + goto found; + } + if (type_pf_data_equal(data, d)) + return -IPSET_ERR_EXIST; + } + j++; + } + if (j < h->chain_limit) { + tmp = kzalloc(h->array_size * sizeof(struct type_pf_telem) + + sizeof(struct slist), gfp_flags); + if (!tmp) + return -ENOMEM; + prev->next = (struct slist *) tmp; + data = chash_tdata(tmp, 0); + } else { + /* Rehashing */ + return -EAGAIN; + } +found: + if (type_pf_data_isnull(data)) { + h->elements++; +#ifdef IP_SET_HASH_WITH_NETS + } else { + if (--h->nets[data->cidr-1].nets == 0) + del_cidr(h->nets, HOST_MASK, data->cidr); + } + if (h->nets[d->cidr-1].nets++ == 0) { + add_cidr(h->nets, HOST_MASK, d->cidr); +#endif + } + type_pf_data_copy(data, d); + type_pf_data_timeout_set(data, timeout); + return 0; +} + +static int +type_pf_chash_tdel(struct ip_set *set, void *value, + gfp_t gfp_flags, u32 timeout) +{ + struct chash *h = set->data; + const struct type_pf_elem *d = value; + struct slist *n, *prev; + int i, ret = 0; + struct type_pf_elem *data; + u32 hash = JHASH2(value, h->initval, h->htable_bits); + + slist_for_each_prev(prev, n, &h->htable[hash]) + for (i = 0; i < h->array_size; i++) { + data = chash_tdata(n, i); + if (type_pf_data_isnull(data)) + return -IPSET_ERR_EXIST; + if (type_pf_data_equal(data, d)) { + if (type_pf_data_expired(data)) + ret = -IPSET_ERR_EXIST; + type_pf_chash_del_telem(h, prev, n, i); + return ret; + } + } + + return -IPSET_ERR_EXIST; +} + +#ifdef IP_SET_HASH_WITH_NETS +static inline int +type_pf_chash_ttest_cidrs(struct ip_set *set, + struct type_pf_elem *d, + gfp_t gfp_flags, u32 timeout) +{ + struct chash *h = set->data; + struct type_pf_elem *data; + struct slist *n; + int i, j = 0; + u32 hash; + u8 host_mask = set->family == AF_INET ? 32 : 128; + +retry: + for (; j < host_mask - 1 && h->nets[j].cidr; j++) { + type_pf_data_netmask(d, h->nets[j].cidr); + hash = JHASH2(d, h->initval, h->htable_bits); + slist_for_each(n, &h->htable[hash]) + for (i = 0; i < h->array_size; i++) { + data = chash_tdata(n, i); + if (type_pf_data_isnull(data)) { + j++; + goto retry; + } + if (type_pf_data_equal(data, d)) + return !type_pf_data_expired(data); + } + } + return 0; +} +#endif + +static inline int +type_pf_chash_ttest(struct ip_set *set, void *value, + gfp_t gfp_flags, u32 timeout) +{ + struct chash *h = set->data; + struct type_pf_elem *data, *d = value; + struct slist *n; + int i; + u32 hash; +#ifdef IP_SET_HASH_WITH_NETS + u8 host_mask = set->family == AF_INET ? 32 : 128; + + if (d->cidr == host_mask) + return type_pf_chash_ttest_cidrs(set, d, gfp_flags, + timeout); +#endif + hash = JHASH2(d, h->initval, h->htable_bits); + slist_for_each(n, &h->htable[hash]) + for (i = 0; i < h->array_size; i++) { + data = chash_tdata(n, i); + if (type_pf_data_isnull(data)) + return 0; + if (type_pf_data_equal(data, d)) + return !type_pf_data_expired(data); + } + return 0; +} + +static int +type_pf_tlist(struct ip_set *set, + struct sk_buff *skb, struct netlink_callback *cb) +{ + const struct chash *h = set->data; + struct nlattr *atd, *nested; + struct slist *n; + const struct type_pf_elem *data; + u32 first = cb->args[2]; + int i; + + atd = ipset_nest_start(skb, IPSET_ATTR_ADT); + if (!atd) + return -EFAULT; + for (; cb->args[2] < jhash_size(h->htable_bits); cb->args[2]++) { + slist_for_each(n, &h->htable[cb->args[2]]) { + for (i = 0; i < h->array_size; i++) { + data = chash_tdata(n, i); + pr_debug("list %p %u", n, i); + if (type_pf_data_isnull(data)) + break; + if (type_pf_data_expired(data)) + continue; + pr_debug("do list %p %u", n, i); + nested = ipset_nest_start(skb, IPSET_ATTR_DATA); + if (!nested) { + if (cb->args[2] == first) { + nla_nest_cancel(skb, atd); + return -EFAULT; + } else + goto nla_put_failure; + } + if (type_pf_data_tlist(skb, data)) + goto nla_put_failure; + ipset_nest_end(skb, nested); + } + } + } + ipset_nest_end(skb, atd); + /* Set listing finished */ + cb->args[2] = 0; + + return 0; + +nla_put_failure: + nla_nest_cancel(skb, nested); + ipset_nest_end(skb, atd); + return 0; +} + +static const struct ip_set_type_variant type_pf_tvariant __read_mostly = { + .kadt = type_pf_kadt, + .uadt = type_pf_uadt, + .adt = { + [IPSET_ADD] = type_pf_chash_tadd, + [IPSET_DEL] = type_pf_chash_tdel, + [IPSET_TEST] = type_pf_chash_ttest, + }, + .destroy = type_pf_destroy, + .flush = type_pf_flush, + .head = type_pf_head, + .list = type_pf_tlist, + .resize = type_pf_tresize, + .same_set = type_pf_same_set, +}; + +static void +type_pf_gc(unsigned long ul_set) +{ + struct ip_set *set = (struct ip_set *) ul_set; + struct chash *h = set->data; + + pr_debug("called"); + write_lock_bh(&set->lock); + type_pf_chash_expire(h); + write_unlock_bh(&set->lock); + + h->gc.expires = jiffies + IPSET_GC_PERIOD(h->timeout) * HZ; + add_timer(&h->gc); +} + +static inline void +type_pf_gc_init(struct ip_set *set) +{ + struct chash *h = set->data; + + init_timer(&h->gc); + h->gc.data = (unsigned long) set; + h->gc.function = type_pf_gc; + h->gc.expires = jiffies + IPSET_GC_PERIOD(h->timeout) * HZ; + add_timer(&h->gc); + pr_debug("gc initialized, run in every %u", IPSET_GC_PERIOD(h->timeout)); +} + +#undef type_pf_data_equal +#undef type_pf_data_isnull +#undef type_pf_data_copy +#undef type_pf_data_swap +#undef type_pf_data_zero_out +#undef type_pf_data_list +#undef type_pf_data_tlist + +#undef type_pf_elem +#undef type_pf_telem +#undef type_pf_data_timeout +#undef type_pf_data_expired +#undef type_pf_data_swap_timeout +#undef type_pf_data_netmask +#undef type_pf_data_timeout_set + +#undef type_pf_chash_readd +#undef type_pf_chash_del_elem +#undef type_pf_chash_add +#undef type_pf_chash_del +#undef type_pf_chash_test_cidrs +#undef type_pf_chash_test + +#undef type_pf_chash_treadd +#undef type_pf_chash_del_telem +#undef type_pf_chash_expire +#undef type_pf_chash_tadd +#undef type_pf_chash_tdel +#undef type_pf_chash_ttest_cidrs +#undef type_pf_chash_ttest + +#undef type_pf_resize +#undef type_pf_tresize +#undef type_pf_flush +#undef type_pf_destroy +#undef type_pf_head +#undef type_pf_list +#undef type_pf_tlist +#undef type_pf_same_set +#undef type_pf_kadt +#undef type_pf_uadt +#undef type_pf_gc +#undef type_pf_gc_init +#undef type_pf_variant +#undef type_pf_tvariant diff --git a/kernel/include/linux/netfilter/ip_set_getport.h b/kernel/include/linux/netfilter/ip_set_getport.h index 855f12a..ffa89f1 100644 --- a/kernel/include/linux/netfilter/ip_set_getport.h +++ b/kernel/include/linux/netfilter/ip_set_getport.h @@ -8,8 +8,8 @@ #define IPSET_INVALID_PORT 65536 /* We must handle non-linear skbs */ -static uint32_t -get_port(uint8_t pf, const struct sk_buff *skb, const uint8_t *flags) +static bool +get_port(u8 pf, const struct sk_buff *skb, bool src, u16 *port) { unsigned short protocol; unsigned int protoff; @@ -30,19 +30,19 @@ get_port(uint8_t pf, const struct sk_buff *skb, const uint8_t *flags) protohdr = ipv6_find_hdr(skb, &protoff, -1, &frag_off); if (protohdr < 0) - return IPSET_INVALID_PORT; + return false; protocol = protohdr; fragoff = frag_off; break; } default: - return IPSET_INVALID_PORT; + return false; } /* See comments at tcp_match in ip_tables.c */ if (fragoff) - return IPSET_INVALID_PORT; + return false; switch (protocol) { case IPPROTO_TCP: { @@ -52,9 +52,10 @@ get_port(uint8_t pf, const struct sk_buff *skb, const uint8_t *flags) th = skb_header_pointer(skb, protoff, sizeof(_tcph), &_tcph); if (th == NULL) /* No choice either */ - return IPSET_INVALID_PORT; + return false; - return flags[0] & IPSET_SRC ? th->source : th->dest; + *port = src ? th->source : th->dest; + break; } case IPPROTO_UDP: { struct udphdr _udph; @@ -63,14 +64,16 @@ get_port(uint8_t pf, const struct sk_buff *skb, const uint8_t *flags) uh = skb_header_pointer(skb, protoff, sizeof(_udph), &_udph); if (uh == NULL) /* No choice either */ - return IPSET_INVALID_PORT; + return false; - return flags[0] & IPSET_SRC ? uh->source : uh->dest; + *port = src ? uh->source : uh->dest; + break; } default: - return IPSET_INVALID_PORT; + return false; } + return true; } -#endif /* __KERNEL__ */ +#endif /* __KERNEL__ */ #endif /*_IP_SET_GETPORT_H*/ diff --git a/kernel/include/linux/netfilter/ip_set_hash.h b/kernel/include/linux/netfilter/ip_set_hash.h index dd183b7..c1a6964 100644 --- a/kernel/include/linux/netfilter/ip_set_hash.h +++ b/kernel/include/linux/netfilter/ip_set_hash.h @@ -9,12 +9,11 @@ enum { #ifdef __KERNEL__ -#define initval_t uint32_t - #define IPSET_DEFAULT_HASHSIZE 1024 +#define IPSET_MIMINAL_HASHSIZE 64 #define IPSET_DEFAULT_MAXELEM 65536 #define IPSET_DEFAULT_PROBES 4 -#define IPSET_DEFAULT_RESIZE 50 +#define IPSET_DEFAULT_RESIZE 100 #endif /* __KERNEL__ */ diff --git a/kernel/include/linux/netfilter/ip_set_jhash.h b/kernel/include/linux/netfilter/ip_set_jhash.h index 90bfcc3..d5e0d6d 100644 --- a/kernel/include/linux/netfilter/ip_set_jhash.h +++ b/kernel/include/linux/netfilter/ip_set_jhash.h @@ -1,7 +1,6 @@ #ifndef _LINUX_JHASH_H #define _LINUX_JHASH_H - -/* jhash.h: Jenkins hash support. +/* jhash.c: Jenkins hash support. * * Copyright (C) 2006. Bob Jenkins (bob_jenkins@burtleburtle.net) * @@ -17,141 +16,106 @@ * if SELF_TEST is defined. You can use this free for any purpose. It's in * the public domain. It has no warranty. * - * Copyright (C) 2009 Jozsef Kadlecsik (kadlec@blackhole.kfki.hu) + * Copyright (C) 2009-2010 Jozsef Kadlecsik (kadlec@blackhole.kfki.hu) * * I've modified Bob's hash to be useful in the Linux kernel, and - * any bugs present are my fault. Jozsef + * any bugs present are my fault. The generic jhash is left out intentionally. + * Jozsef */ - -#define __rot(x,k) (((x)<<(k)) | ((x)>>(32-(k)))) - -/* __jhash_mix - mix 3 32-bit values reversibly. */ -#define __jhash_mix(a,b,c) \ -{ \ - a -= c; a ^= __rot(c, 4); c += b; \ - b -= a; b ^= __rot(a, 6); a += c; \ - c -= b; c ^= __rot(b, 8); b += a; \ - a -= c; a ^= __rot(c,16); c += b; \ - b -= a; b ^= __rot(a,19); a += c; \ - c -= b; c ^= __rot(b, 4); b += a; \ +#ifdef __KERNEL__ +#include + +/* Best hash sizes are of power of two */ +#define jhash_size(n) ((u32)1<<(n)) +/* Mask the hash value, i.e (value & jhash_mask(n)) instead of (value % n) */ +#define jhash_mask(n) (jhash_size(n)-1) + +/* __jhash_rot - rotate 32 bit */ +#define __jhash_rot(x,k) (((x)<<(k)) | ((x)>>(32-(k)))) + +/* __jhash_mix -- mix 3 32-bit values reversibly. */ +#define __jhash_mix(a,b,c) \ +{ \ + a -= c; a ^= __jhash_rot(c, 4); c += b; \ + b -= a; b ^= __jhash_rot(a, 6); a += c; \ + c -= b; c ^= __jhash_rot(b, 8); b += a; \ + a -= c; a ^= __jhash_rot(c,16); c += b; \ + b -= a; b ^= __jhash_rot(a,19); a += c; \ + c -= b; c ^= __jhash_rot(b, 4); b += a; \ } /* __jhash_final - final mixing of 3 32-bit values (a,b,c) into c */ -#define __jhash_final(a,b,c) \ -{ \ - c ^= b; c -= __rot(b,14); \ - a ^= c; a -= __rot(c,11); \ - b ^= a; b -= __rot(a,25); \ - c ^= b; c -= __rot(b,16); \ - a ^= c; a -= __rot(c,4); \ - b ^= a; b -= __rot(a,14); \ - c ^= b; c -= __rot(b,24); \ +#define __jhash_final(a,b,c) \ +{ \ + c ^= b; c -= __jhash_rot(b,14); \ + a ^= c; a -= __jhash_rot(c,11); \ + b ^= a; b -= __jhash_rot(a,25); \ + c ^= b; c -= __jhash_rot(b,16); \ + a ^= c; a -= __jhash_rot(c,4); \ + b ^= a; b -= __jhash_rot(a,14); \ + c ^= b; c -= __jhash_rot(b,24); \ } -/* An arbitrary value */ -#define JHASH_RANDOM_PARAM 0xdeadbeef - -/* The most generic version, hashes an arbitrary sequence - * of bytes. No alignment or length assumptions are made about - * the input key. The result depends on endianness. - */ -static inline u32 jhash(const void *key, u32 length, u32 initval) -{ - u32 a,b,c; - const u8 *k = key; - - /* Set up the internal state */ - a = b = c = JHASH_RANDOM_PARAM + length + initval; - - /* all but the last block: affect some 32 bits of (a,b,c) */ - while (length > 12) { - a += (k[0] + ((u32)k[1]<<8) + ((u32)k[2]<<16) + ((u32)k[3]<<24)); - b += (k[4] + ((u32)k[5]<<8) + ((u32)k[6]<<16) + ((u32)k[7]<<24)); - c += (k[8] + ((u32)k[9]<<8) + ((u32)k[10]<<16) + ((u32)k[11]<<24)); - __jhash_mix(a, b, c); - length -= 12; - k += 12; - } - - /* last block: affect all 32 bits of (c) */ - /* all the case statements fall through */ - switch (length) { - case 12: c += (u32)k[11]<<24; - case 11: c += (u32)k[10]<<16; - case 10: c += (u32)k[9]<<8; - case 9 : c += k[8]; - case 8 : b += (u32)k[7]<<24; - case 7 : b += (u32)k[6]<<16; - case 6 : b += (u32)k[5]<<8; - case 5 : b += k[4]; - case 4 : a += (u32)k[3]<<24; - case 3 : a += (u32)k[2]<<16; - case 2 : a += (u32)k[1]<<8; - case 1 : a += k[0]; - __jhash_final(a, b, c); - case 0 : - break; - } - - return c; -} +#define JHASH_INITVAL 0xdeadbeef -/* A special optimized version that handles 1 or more of u32s. - * The length parameter here is the number of u32s in the key. +/* jhash2 - hash an array of u32's + * @k: the key which must be an array of u32's + * @length: the number of u32's in the key + * @initval: the previous hash, or an arbitray value + * + * Returns the hash value of the key. */ static inline u32 jhash2(const u32 *k, u32 length, u32 initval) { u32 a, b, c; /* Set up the internal state */ - a = b = c = JHASH_RANDOM_PARAM + (length<<2) + initval; + a = b = c = JHASH_INITVAL + (length<<2) + initval; - /* handle most of the key */ + /* Handle most of the key */ while (length > 3) { a += k[0]; b += k[1]; c += k[2]; - __jhash_mix(a, b, c); + __jhash_mix(a,b,c); length -= 3; k += 3; } - - /* handle the last 3 u32's */ - /* all the case statements fall through */ - switch (length) { + + /* Handle the last 3 u32's: all the case statements fall through */ + switch(length) { case 3: c += k[2]; case 2: b += k[1]; case 1: a += k[0]; - __jhash_final(a, b, c); - case 0: /* case 0: nothing left to add */ + __jhash_final(a,b,c); + case 0: /* Nothing left to add */ break; } return c; } -/* A special ultra-optimized versions that knows they are hashing exactly - * 3, 2 or 1 word(s). - */ +/* jhash_3words - hash exactly 3, 2 or 1 word(s) */ static inline u32 jhash_3words(u32 a, u32 b, u32 c, u32 initval) { - a += JHASH_RANDOM_PARAM + initval; - b += JHASH_RANDOM_PARAM + initval; - c += JHASH_RANDOM_PARAM + initval; - - __jhash_final(a, b, c); + a += JHASH_INITVAL; + b += JHASH_INITVAL; + c += initval; + __jhash_final(a,b,c); + return c; } - static inline u32 jhash_2words(u32 a, u32 b, u32 initval) { - return jhash_3words(0, a, b, initval); + return jhash_3words(a, b, 0, initval); } static inline u32 jhash_1word(u32 a, u32 initval) { - return jhash_3words(0, 0, a, initval); + return jhash_3words(a, 0, 0, initval); } +#endif /* __KERNEL__ */ + #endif /* _LINUX_JHASH_H */ diff --git a/kernel/include/linux/netfilter/ip_set_kernel.h b/kernel/include/linux/netfilter/ip_set_kernel.h new file mode 100644 index 0000000..d6e033b --- /dev/null +++ b/kernel/include/linux/netfilter/ip_set_kernel.h @@ -0,0 +1,20 @@ +#ifndef _IP_SET_KERNEL_H +#define _IP_SET_KERNEL_H + +/* Copyright (C) 2003-2010 Jozsef Kadlecsik + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifdef __KERNEL__ + +/* Complete debug messages */ +#define pr_fmt(fmt) "%s %s[%i]: " fmt "\n", __FILE__, __func__, __LINE__ + +#include + +#endif /* __KERNEL__ */ + +#endif /*_IP_SET_H */ diff --git a/kernel/include/linux/netfilter/ip_set_list.h b/kernel/include/linux/netfilter/ip_set_list.h new file mode 100644 index 0000000..c40643e --- /dev/null +++ b/kernel/include/linux/netfilter/ip_set_list.h @@ -0,0 +1,21 @@ +#ifndef __IP_SET_LIST_H +#define __IP_SET_LIST_H + +/* List type specific error codes */ +enum { + IPSET_ERR_NAME = IPSET_ERR_TYPE_SPECIFIC, + IPSET_ERR_LOOP, + IPSET_ERR_BEFORE, + IPSET_ERR_NAMEREF, + IPSET_ERR_LIST_FULL, + IPSET_ERR_REF_EXIST, +}; + +#ifdef __KERNEL__ + +#define IP_SET_LIST_DEFAULT_SIZE 8 +#define IP_SET_LIST_MIN_SIZE 4 + +#endif /* __KERNEL__ */ + +#endif /* __IP_SET_LIST_H */ diff --git a/kernel/include/linux/netfilter/ip_set_slist.h b/kernel/include/linux/netfilter/ip_set_slist.h new file mode 100644 index 0000000..abc5afe --- /dev/null +++ b/kernel/include/linux/netfilter/ip_set_slist.h @@ -0,0 +1,86 @@ +#ifndef _IP_SET_SLIST_H +#define _IP_SET_SLIST_H + +#include +#include +#include + +/* + * Single linked lists with a single pointer. + * Mostly useful for hash tables where the two pointer list head + * and list node is too wasteful. + */ + +struct slist { + struct slist *next; +}; + +#define SLIST(name) struct slist name = { .next = NULL } +#define INIT_SLIST(ptr) ((ptr)->next = NULL) + +#define slist_entry(ptr, type, member) container_of(ptr,type,member) + +#define slist_for_each(pos, head) \ + for (pos = (head)->next; pos && ({ prefetch(pos->next); 1; }); \ + pos = pos->next) + +#define slist_for_each_prev(prev, pos, head) \ + for (prev = head, pos = (head)->next; pos && ({ prefetch(pos->next); 1; }); \ + prev = pos, pos = pos->next) + +#define slist_for_each_safe(pos, n, head) \ + for (pos = (head)->next; pos && ({ n = pos->next; 1; }); \ + pos = n) + +/** + * slist_for_each_entry - iterate over list of given type + * @tpos: the type * to use as a loop cursor. + * @pos: the &struct slist to use as a loop cursor. + * @head: the head for your list. + * @member: the name of the slist within the struct. + */ +#define slist_for_each_entry(tpos, pos, head, member) \ + for (pos = (head)->next; \ + pos && ({ prefetch(pos->next); 1;}) && \ + ({ tpos = slist_entry(pos, typeof(*tpos), member); 1;}); \ + pos = pos->next) + +/** + * slist_for_each_entry_continue - iterate over a hlist continuing after current point + * @tpos: the type * to use as a loop cursor. + * @pos: the &struct slist to use as a loop cursor. + * @member: the name of the slist within the struct. + */ +#define slist_for_each_entry_continue(tpos, pos, member) \ + for (pos = (pos)->next; \ + pos && ({ prefetch(pos->next); 1;}) && \ + ({ tpos = slist_entry(pos, typeof(*tpos), member); 1;}); \ + pos = pos->next) + +/** + * slist_for_each_entry_from - iterate over a hlist continuing from current point + * @tpos: the type * to use as a loop cursor. + * @pos: the &struct slist to use as a loop cursor. + * @member: the name of the slist within the struct. + */ +#define slist_for_each_entry_from(tpos, pos, member) \ + for (; pos && ({ prefetch(pos->next); 1;}) && \ + ({ tpos = slist_entry(pos, typeof(*tpos), member); 1;}); \ + pos = pos->next) + +/** + * slist_for_each_entry_safe - iterate over list of given type safe against + * removal of list entry + * @tpos: the type * to use as a loop cursor. + * @pos: the &struct slist to use as a loop cursor. + * @n: another &struct slist to use as temporary storage + * @head: the head for your list. + * @member: the name of the slist within the struct. + */ +#define slist_for_each_entry_safe(tpos, pos, n, head, member) \ + for (pos = (head)->next; \ + pos && ({ n = pos->next; 1; }) && \ + ({ tpos = slist_entry(pos, typeof(*tpos), member); 1;}); \ + pos = n) + +#endif /* _IP_SET_SLIST_H */ diff --git a/kernel/include/linux/netfilter/ip_set_timeout.h b/kernel/include/linux/netfilter/ip_set_timeout.h index da18875..bf1cbf6 100644 --- a/kernel/include/linux/netfilter/ip_set_timeout.h +++ b/kernel/include/linux/netfilter/ip_set_timeout.h @@ -10,21 +10,33 @@ #ifdef __KERNEL__ -/* How often should the gc be run at a minimum */ +/* How often should the gc be run by default */ #define IPSET_GC_TIME (3 * 60) /* Timeout period depending on the timeout value of the given set */ #define IPSET_GC_PERIOD(timeout) \ - max_t(uint32_t, (timeout)/10, IPSET_GC_TIME) + ((timeout/3) ? min_t(u32, (timeout)/3, IPSET_GC_TIME) : 1) -/* How much msec to sleep before retrying to destroy gc timer */ -#define IPSET_DESTROY_TIMER_SLEEP 10 +/* Set is defined without timeout support */ +#define IPSET_NO_TIMEOUT UINT_MAX -/* Timing out etries: unset and permanent */ +#define with_timeout(timeout) ((timeout) != IPSET_NO_TIMEOUT) + +static inline unsigned int +ip_set_timeout_uget(struct nlattr *tb) +{ + unsigned int timeout = ip_set_get_h32(tb); + + return timeout == IPSET_NO_TIMEOUT ? IPSET_NO_TIMEOUT - 1 : timeout; +} + +#ifdef IP_SET_BITMAP_TIMEOUT + +/* Bitmap entry is unset */ #define IPSET_ELEM_UNSET 0 +/* Bitmap entry is set with no timeout value */ #define IPSET_ELEM_PERMANENT UINT_MAX/2 -#ifdef IP_SET_BITMAP_TIMEOUT static inline bool ip_set_timeout_test(unsigned long timeout) { @@ -42,7 +54,7 @@ ip_set_timeout_expired(unsigned long timeout) } static inline unsigned long -ip_set_timeout_set(uint32_t timeout) +ip_set_timeout_set(u32 timeout) { unsigned long t; @@ -56,7 +68,7 @@ ip_set_timeout_set(uint32_t timeout) return t; } -static inline uint32_t +static inline u32 ip_set_timeout_get(unsigned long timeout) { return timeout == IPSET_ELEM_PERMANENT ? 0 : (timeout - jiffies)/HZ; @@ -64,6 +76,9 @@ ip_set_timeout_get(unsigned long timeout) #else +/* Hash entry is set with no timeout value */ +#define IPSET_ELEM_UNSET 0 + static inline bool ip_set_timeout_test(unsigned long timeout) { @@ -77,7 +92,7 @@ ip_set_timeout_expired(unsigned long timeout) } static inline unsigned long -ip_set_timeout_set(uint32_t timeout) +ip_set_timeout_set(u32 timeout) { unsigned long t; @@ -91,7 +106,7 @@ ip_set_timeout_set(uint32_t timeout) return t; } -static inline uint32_t +static inline u32 ip_set_timeout_get(unsigned long timeout) { return timeout == IPSET_ELEM_UNSET ? 0 : (timeout - jiffies)/HZ; diff --git a/kernel/include/linux/netfilter/ipt_set.h b/kernel/include/linux/netfilter/ipt_set.h deleted file mode 100644 index 2a18b93..0000000 --- a/kernel/include/linux/netfilter/ipt_set.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef _IPT_SET_H -#define _IPT_SET_H - -#include - -struct ipt_set_info { - ip_set_id_t index; - u_int32_t flags[IP_SET_MAX_BINDINGS + 1]; -}; - -/* match info */ -struct ipt_set_info_match { - struct ipt_set_info match_set; -}; - -struct ipt_set_info_target { - struct ipt_set_info add_set; - struct ipt_set_info del_set; -}; - -#endif /*_IPT_SET_H*/ diff --git a/kernel/include/linux/netfilter/xt_set.h b/kernel/include/linux/netfilter/xt_set.h new file mode 100644 index 0000000..949fa59 --- /dev/null +++ b/kernel/include/linux/netfilter/xt_set.h @@ -0,0 +1,55 @@ +#ifndef _XT_SET_H +#define _XT_SET_H + +#include + +/* Revision 0 interface: backward compatible with netfilter/iptables */ + +/* + * Option flags for kernel operations (xt_set_info_v0) + */ +#define IPSET_SRC 0x01 /* Source match/add */ +#define IPSET_DST 0x02 /* Destination match/add */ +#define IPSET_MATCH_INV 0x04 /* Inverse matching */ + +struct xt_set_info_v0 { + ip_set_id_t index; + union { + u_int32_t flags[IPSET_DIM_MAX + 1]; + struct { + u_int32_t __flags[IPSET_DIM_MAX]; + u_int8_t dim; + u_int8_t flags; + } compat; + } u; +}; + +/* match and target infos */ +struct xt_set_info_match_v0 { + struct xt_set_info_v0 match_set; +}; + +struct xt_set_info_target_v0 { + struct xt_set_info_v0 add_set; + struct xt_set_info_v0 del_set; +}; + +/* Revision 1: current interface to netfilter/iptables */ + +struct xt_set_info { + ip_set_id_t index; + u_int8_t dim; + u_int8_t flags; +}; + +/* match and target infos */ +struct xt_set_info_match { + struct xt_set_info match_set; +}; + +struct xt_set_info_target { + struct xt_set_info add_set; + struct xt_set_info del_set; +}; + +#endif /*_XT_SET_H*/ -- cgit v1.2.3