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/ip_set_hash_ipport.c | 538 ++++++++++++++++++++++++++++++++------------ 1 file changed, 398 insertions(+), 140 deletions(-) (limited to 'kernel/ip_set_hash_ipport.c') diff --git a/kernel/ip_set_hash_ipport.c b/kernel/ip_set_hash_ipport.c index 36e68b0..8210f67 100644 --- a/kernel/ip_set_hash_ipport.c +++ b/kernel/ip_set_hash_ipport.c @@ -1,197 +1,455 @@ -/* Copyright (C) 2003-2008 Jozsef Kadlecsik +/* 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. */ -/* Kernel module implementing an ip+port hash set */ +/* Kernel module implementing an IP set type: the hash:ip,port type */ +#include +#include #include -#include #include -#include -#include #include -#include #include #include #include #include #include - #include +#include +#include +#include -#include -#include +#include +#include +#include +#include +#include -static int limit = MAX_RANGE; +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Jozsef Kadlecsik "); +MODULE_DESCRIPTION("hash:ip,port type of IP sets"); +MODULE_ALIAS("ip_set_hash:ip,port"); + +/* Type specific function prefix */ +#define TYPE hash_ipport + +static bool +hash_ipport_same_set(const struct ip_set *a, const struct ip_set *b); + +#define hash_ipport4_same_set hash_ipport_same_set +#define hash_ipport6_same_set hash_ipport_same_set + +/* The type variant functions: IPv4 */ + +/* Member elements without timeout */ +struct hash_ipport4_elem { + u32 ip; + u16 port; + u16 match; +}; + +/* Member elements with timeout support */ +struct hash_ipport4_telem { + u32 ip; + u16 port; + u16 match; + unsigned long timeout; +}; + +static inline bool +hash_ipport4_data_equal(const struct hash_ipport4_elem *ip1, + const struct hash_ipport4_elem *ip2) +{ + return ip1->ip == ip2->ip && ip1->port == ip2->port; +} -static inline __u32 -ipporthash_id(struct ip_set *set, ip_set_ip_t ip, ip_set_ip_t port) +static inline bool +hash_ipport4_data_isnull(const struct hash_ipport4_elem *elem) { - struct ip_set_ipporthash *map = set->data; - __u32 id; - u_int16_t i; - ip_set_ip_t *elem; + return elem->match == 0; +} - ip = pack_ip_port(map, ip, port); - - if (!ip) - return UINT_MAX; - - for (i = 0; i < map->probes; i++) { - id = jhash_ip(map, i, ip) % map->hashsize; - DP("hash key: %u", id); - elem = HARRAY_ELEM(map->members, ip_set_ip_t *, id); - if (*elem == ip) - return id; - /* No shortcut - there can be deleted entries. */ - } - return UINT_MAX; +static inline void +hash_ipport4_data_copy(struct hash_ipport4_elem *dst, + const struct hash_ipport4_elem *src) +{ + dst->ip = src->ip; + dst->port = src->port; + dst->match = 1; } -static inline int -ipporthash_test(struct ip_set *set, ip_set_ip_t ip, ip_set_ip_t port) +static inline void +hash_ipport4_data_swap(struct hash_ipport4_elem *dst, + struct hash_ipport4_elem *src) { - struct ip_set_ipporthash *map = set->data; - - if (ip < map->first_ip || ip > map->last_ip) - return -ERANGE; - - return (ipporthash_id(set, ip, port) != UINT_MAX); -} - -#define KADT_CONDITION \ - ip_set_ip_t port; \ - \ - if (flags[1] == 0) \ - return 0; \ - \ - port = get_port(skb, flags++); \ - \ - if (port == INVALID_PORT) \ - return 0; - -UADT(ipporthash, test, req->port) -KADT(ipporthash, test, ipaddr, port) - -static inline int -__ipporthash_add(struct ip_set_ipporthash *map, ip_set_ip_t *ip) -{ - __u32 probe; - u_int16_t i; - ip_set_ip_t *elem, *slot = NULL; - - for (i = 0; i < map->probes; i++) { - probe = jhash_ip(map, i, *ip) % map->hashsize; - elem = HARRAY_ELEM(map->members, ip_set_ip_t *, probe); - if (*elem == *ip) - return -EEXIST; - if (!(slot || *elem)) - slot = elem; - /* There can be deleted entries, must check all slots */ + swap(dst->ip, src->ip); + swap(dst->port, src->port); +} + +static inline void +hash_ipport4_data_zero_out(struct hash_ipport4_elem *elem) +{ + elem->match = 0; +} + +static inline bool +hash_ipport4_data_list(struct sk_buff *skb, + const struct hash_ipport4_elem *data) +{ + NLA_PUT_NET32(skb, IPSET_ATTR_IP, data->ip); + NLA_PUT_NET16(skb, IPSET_ATTR_PORT, data->port); + return 0; + +nla_put_failure: + return 1; +} + +static inline bool +hash_ipport4_data_tlist(struct sk_buff *skb, + const struct hash_ipport4_elem *data) +{ + const struct hash_ipport4_telem *tdata = + (const struct hash_ipport4_telem *)data; + + NLA_PUT_NET32(skb, IPSET_ATTR_IP, tdata->ip); + NLA_PUT_NET16(skb, IPSET_ATTR_PORT, tdata->port); + NLA_PUT_NET32(skb, IPSET_ATTR_TIMEOUT, + htonl(ip_set_timeout_get(tdata->timeout))); + + return 0; + +nla_put_failure: + return 1; +} + +#define PF 4 +#define HOST_MASK 32 +#include + +static int +hash_ipport4_kadt(struct ip_set *set, const struct sk_buff *skb, + enum ipset_adt adt, u8 pf, u8 dim, u8 flags) +{ + struct chash *h = set->data; + ipset_adtfn adtfn = set->variant->adt[adt]; + struct hash_ipport4_elem data = {}; + + ip4addrptr(skb, flags & IPSET_DIM_ONE_SRC, &data.ip); + if (!get_port(AF_INET, skb, flags & IPSET_DIM_TWO_SRC, &data.port)) + return -EINVAL; + + return adtfn(set, &data, GFP_ATOMIC, h->timeout); +} + +static const struct nla_policy +hash_ipport4_adt_policy[IPSET_ATTR_ADT_MAX + 1] __read_mostly = { + [IPSET_ATTR_IP] = { .type = NLA_U32 }, + [IPSET_ATTR_PORT] = { .type = NLA_U16 }, + [IPSET_ATTR_TIMEOUT] = { .type = NLA_U32 }, +}; + +static int +hash_ipport4_uadt(struct ip_set *set, struct nlattr *head, int len, + enum ipset_adt adt, u32 *lineno, u32 flags) +{ + struct chash *h = set->data; + struct nlattr *tb[IPSET_ATTR_ADT_MAX]; + bool eexist = flags & IPSET_FLAG_EXIST; + ipset_adtfn adtfn = set->variant->adt[adt]; + struct hash_ipport4_elem data = {}; + u32 timeout = h->timeout; + int ret; + + if (nla_parse(tb, IPSET_ATTR_ADT_MAX, head, len, + hash_ipport4_adt_policy)) + return -IPSET_ERR_PROTOCOL; + + if (tb[IPSET_ATTR_IP]) + data.ip = ip_set_get_n32(tb[IPSET_ATTR_IP]); + else + return -IPSET_ERR_PROTOCOL; + + if (tb[IPSET_ATTR_PORT]) + data.port = ip_set_get_n16(tb[IPSET_ATTR_PORT]); + else + return -IPSET_ERR_PROTOCOL; + + if (tb[IPSET_ATTR_TIMEOUT]) { + if (!with_timeout(h->timeout)) + return -IPSET_ERR_TIMEOUT; + timeout = ip_set_timeout_uget(tb[IPSET_ATTR_TIMEOUT]); } - if (slot) { - *slot = *ip; - map->elements++; - return 0; + + ret = adtfn(set, &data, GFP_KERNEL, timeout); + + if (ret && !(ret == -IPSET_ERR_EXIST && eexist)) { + if (tb[IPSET_ATTR_LINENO]) + *lineno = nla_get_u32(tb[IPSET_ATTR_LINENO]); } - /* Trigger rehashing */ - return -EAGAIN; + return ret; } -static inline int -ipporthash_add(struct ip_set *set, ip_set_ip_t ip, ip_set_ip_t port) +static bool +hash_ipport_same_set(const struct ip_set *a, const struct ip_set *b) { - struct ip_set_ipporthash *map = set->data; - if (map->elements > limit) - return -ERANGE; - if (ip < map->first_ip || ip > map->last_ip) - return -ERANGE; + struct chash *x = a->data; + struct chash *y = b->data; + + return x->maxelem == y->maxelem + && x->timeout == y->timeout + && x->htable_bits == y->htable_bits /* resizing ? */ + && x->array_size == y->array_size + && x->chain_limit == y->chain_limit; +} - ip = pack_ip_port(map, ip, port); +/* The type variant functions: IPv6 */ - if (!ip) - return -ERANGE; - - return __ipporthash_add(map, &ip); +struct hash_ipport6_elem { + union nf_inet_addr ip; + u16 port; + u16 match; +}; + +struct hash_ipport6_telem { + union nf_inet_addr ip; + u16 port; + u16 match; + unsigned long timeout; +}; + +static inline bool +hash_ipport6_data_equal(const struct hash_ipport6_elem *ip1, + const struct hash_ipport6_elem *ip2) +{ + return ipv6_addr_cmp(&ip1->ip.in6, &ip2->ip.in6) == 0 + && ip1->port == ip2->port; } -UADT(ipporthash, add, req->port) -KADT(ipporthash, add, ipaddr, port) +static inline bool +hash_ipport6_data_isnull(const struct hash_ipport6_elem *elem) +{ + return elem->match == 0; +} static inline void -__ipporthash_retry(struct ip_set_ipporthash *tmp, - struct ip_set_ipporthash *map) +hash_ipport6_data_copy(struct hash_ipport6_elem *dst, + const struct hash_ipport6_elem *src) { - tmp->first_ip = map->first_ip; - tmp->last_ip = map->last_ip; + memcpy(dst, src, sizeof(*dst)); + dst->match = 1; } -HASH_RETRY(ipporthash, ip_set_ip_t) - -static inline int -ipporthash_del(struct ip_set *set, ip_set_ip_t ip, ip_set_ip_t port) +static inline void +hash_ipport6_data_swap(struct hash_ipport6_elem *dst, + struct hash_ipport6_elem *src) { - struct ip_set_ipporthash *map = set->data; - ip_set_ip_t id; - ip_set_ip_t *elem; + struct hash_ipport6_elem tmp; - if (ip < map->first_ip || ip > map->last_ip) - return -ERANGE; + memcpy(&tmp, dst, sizeof(tmp)); + memcpy(dst, src, sizeof(tmp)); + memcpy(src, &tmp, sizeof(tmp)); +} - id = ipporthash_id(set, ip, port); +static inline void +hash_ipport6_data_zero_out(struct hash_ipport6_elem *elem) +{ + elem->match = 0; +} - if (id == UINT_MAX) - return -EEXIST; - - elem = HARRAY_ELEM(map->members, ip_set_ip_t *, id); - *elem = 0; - map->elements--; +static inline bool +hash_ipport6_data_list(struct sk_buff *skb, + const struct hash_ipport6_elem *data) +{ + NLA_PUT(skb, IPSET_ATTR_IP, sizeof(struct in6_addr), &data->ip); + NLA_PUT_NET16(skb, IPSET_ATTR_PORT, data->port); + return 0; +nla_put_failure: + return 1; +} + +static inline bool +hash_ipport6_data_tlist(struct sk_buff *skb, + const struct hash_ipport6_elem *data) +{ + const struct hash_ipport6_telem *e = + (const struct hash_ipport6_telem *)data; + + NLA_PUT(skb, IPSET_ATTR_IP, sizeof(struct in6_addr), &e->ip); + NLA_PUT_NET16(skb, IPSET_ATTR_PORT, data->port); + NLA_PUT_NET32(skb, IPSET_ATTR_TIMEOUT, + htonl(ip_set_timeout_get(e->timeout))); return 0; + +nla_put_failure: + return 1; } -UADT(ipporthash, del, req->port) -KADT(ipporthash, del, ipaddr, port) +#undef PF +#undef HOST_MASK + +#define PF 6 +#define HOST_MASK 128 +#include + +static int +hash_ipport6_kadt(struct ip_set *set, const struct sk_buff *skb, + enum ipset_adt adt, u8 pf, u8 dim, u8 flags) +{ + struct chash *h = set->data; + ipset_adtfn adtfn = set->variant->adt[adt]; + struct hash_ipport6_elem data = {}; + + ip6addrptr(skb, flags & IPSET_DIM_ONE_SRC, &data.ip.in6); + if (!get_port(AF_INET, skb, flags & IPSET_DIM_TWO_SRC, &data.port)) + return -EINVAL; + + return adtfn(set, &data, GFP_ATOMIC, h->timeout); +} -static inline int -__ipporthash_create(const struct ip_set_req_ipporthash_create *req, - struct ip_set_ipporthash *map) +static const struct nla_policy +hash_ipport6_adt_policy[IPSET_ATTR_ADT_MAX + 1] __read_mostly = { + [IPSET_ATTR_IP] = { .type = NLA_BINARY, + .len = sizeof(struct in6_addr) }, + [IPSET_ATTR_PORT] = { .type = NLA_U16 }, + [IPSET_ATTR_TIMEOUT] = { .type = NLA_U32 }, +}; + +static int +hash_ipport6_uadt(struct ip_set *set, struct nlattr *head, int len, + enum ipset_adt adt, u32 *lineno, u32 flags) { - if (req->to - req->from > MAX_RANGE) { - ip_set_printk("range too big, %d elements (max %d)", - req->to - req->from + 1, MAX_RANGE+1); - return -ENOEXEC; + struct chash *h = set->data; + struct nlattr *tb[IPSET_ATTR_ADT_MAX]; + ipset_adtfn adtfn = set->variant->adt[adt]; + struct hash_ipport6_elem data = {}; + u32 timeout = h->timeout; + + if (nla_parse(tb, IPSET_ATTR_ADT_MAX, head, len, + hash_ipport6_adt_policy)) + return -IPSET_ERR_PROTOCOL; + + if (tb[IPSET_ATTR_IP]) + memcpy(&data.ip, nla_data(tb[IPSET_ATTR_IP]), + sizeof(struct in6_addr)); + else + return -IPSET_ERR_PROTOCOL; + + if (tb[IPSET_ATTR_PORT]) + data.port = ip_set_get_n16(tb[IPSET_ATTR_PORT]); + else + return -IPSET_ERR_PROTOCOL; + + if (tb[IPSET_ATTR_TIMEOUT]) { + if (!with_timeout(h->timeout)) + return -IPSET_ERR_TIMEOUT; + timeout = ip_set_timeout_uget(tb[IPSET_ATTR_TIMEOUT]); } - map->first_ip = req->from; - map->last_ip = req->to; - return 0; + + return adtfn(set, &data, GFP_KERNEL, timeout); } -HASH_CREATE(ipporthash, ip_set_ip_t) -HASH_DESTROY(ipporthash) -HASH_FLUSH(ipporthash, ip_set_ip_t) +/* Create hash:ip type of sets */ -static inline void -__ipporthash_list_header(const struct ip_set_ipporthash *map, - struct ip_set_req_ipporthash_create *header) +static const struct nla_policy +hash_ipport_create_policy[IPSET_ATTR_CREATE_MAX+1] __read_mostly = { + [IPSET_ATTR_HASHSIZE] = { .type = NLA_U32 }, + [IPSET_ATTR_MAXELEM] = { .type = NLA_U32 }, + [IPSET_ATTR_PROBES] = { .type = NLA_U8 }, + [IPSET_ATTR_RESIZE] = { .type = NLA_U8 }, + [IPSET_ATTR_TIMEOUT] = { .type = NLA_U32 }, +}; + +static int +hash_ipport_create(struct ip_set *set, struct nlattr *head, int len, u32 flags) { - header->from = map->first_ip; - header->to = map->last_ip; -} + struct nlattr *tb[IPSET_ATTR_CREATE_MAX]; + u32 hashsize = IPSET_DEFAULT_HASHSIZE, maxelem = IPSET_DEFAULT_MAXELEM; + struct chash *h; -HASH_LIST_HEADER(ipporthash) -HASH_LIST_MEMBERS_SIZE(ipporthash, ip_set_ip_t) -HASH_LIST_MEMBERS(ipporthash, ip_set_ip_t) + if (!(set->family == AF_INET || set->family == AF_INET6)) + return -IPSET_ERR_INVALID_FAMILY; -IP_SET_RTYPE(ipporthash, IPSET_TYPE_IP | IPSET_TYPE_PORT | IPSET_DATA_DOUBLE) + if (nla_parse(tb, IPSET_ATTR_CREATE_MAX, head, len, + hash_ipport_create_policy)) + return -IPSET_ERR_PROTOCOL; -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Jozsef Kadlecsik "); -MODULE_DESCRIPTION("ipporthash type of IP sets"); -module_param(limit, int, 0600); -MODULE_PARM_DESC(limit, "maximal number of elements stored in the sets"); + if (tb[IPSET_ATTR_HASHSIZE]) { + hashsize = ip_set_get_h32(tb[IPSET_ATTR_HASHSIZE]); + if (hashsize < IPSET_MIMINAL_HASHSIZE) + hashsize = IPSET_MIMINAL_HASHSIZE; + } + + if (tb[IPSET_ATTR_MAXELEM]) + maxelem = ip_set_get_h32(tb[IPSET_ATTR_MAXELEM]); + + h = kzalloc(sizeof(*h), GFP_KERNEL); + if (!h) + return -ENOMEM; + + h->maxelem = maxelem; + h->htable_bits = htable_bits(hashsize); + h->array_size = CHASH_DEFAULT_ARRAY_SIZE; + h->chain_limit = CHASH_DEFAULT_CHAIN_LIMIT; + get_random_bytes(&h->initval, sizeof(h->initval)); + h->timeout = IPSET_NO_TIMEOUT; + + h->htable = ip_set_alloc(jhash_size(h->htable_bits) * sizeof(struct slist), + GFP_KERNEL, &set->flags); + if (!h->htable) { + kfree(h); + return -ENOMEM; + } + + set->data = h; + + if (tb[IPSET_ATTR_TIMEOUT]) { + h->timeout = ip_set_timeout_uget(tb[IPSET_ATTR_TIMEOUT]); + + set->variant = set->family == AF_INET + ? &hash_ipport4_tvariant : &hash_ipport6_tvariant; + + if (set->family == AF_INET) + hash_ipport4_gc_init(set); + else + hash_ipport6_gc_init(set); + } else { + set->variant = set->family == AF_INET + ? &hash_ipport4_variant : &hash_ipport6_variant; + } + + pr_debug("create %s hashsize %u (%u) maxelem %u: %p(%p)", + set->name, jhash_size(h->htable_bits), + h->htable_bits, h->maxelem, set->data, h->htable); + + return 0; +} + +static struct ip_set_type hash_ipport_type = { + .name = "hash:ip,port", + .protocol = IPSET_PROTOCOL, + .features = IPSET_TYPE_IP | IPSET_TYPE_PORT, + .dimension = IPSET_DIM_TWO, + .family = AF_UNSPEC, + .revision = 0, + .create = hash_ipport_create, + .me = THIS_MODULE, +}; + +static int __init +hash_ipport_init(void) +{ + return ip_set_type_register(&hash_ipport_type); +} + +static void __exit +hash_ipport_fini(void) +{ + ip_set_type_unregister(&hash_ipport_type); +} -REGISTER_MODULE(ipporthash) +module_init(hash_ipport_init); +module_exit(hash_ipport_fini); -- cgit v1.2.3