From 418a3a4f4d4e38abd1d691f81f2445590f02ecaf Mon Sep 17 00:00:00 2001 From: Jozsef Kadlecsik Date: Mon, 30 May 2011 17:48:01 +0200 Subject: hash:net,iface type introduced The hash:net,iface type makes possible to store network address and interface name pairs in a set. It's mostly suitable for egress and ingress filtering. Examples: # ipset create test hash:net,iface # ipset add test 192.168.0.0/16,eth0 # ipset add test 192.168.0.0/24,eth1 --- include/libipset/data.h | 6 +- include/libipset/linux_ip_set.h | 3 + include/libipset/parse.h | 6 +- include/libipset/print.h | 3 + kernel/include/linux/netfilter/ipset/ip_set.h | 5 + .../include/linux/netfilter/ipset/ip_set_ahash.h | 6 + kernel/net/netfilter/ipset/Kbuild | 1 + kernel/net/netfilter/ipset/ip_set_hash_netiface.c | 762 +++++++++++++++++++++ lib/data.c | 14 +- lib/debug.c | 1 + lib/parse.c | 35 + lib/print.c | 43 ++ lib/session.c | 6 + src/Makefile.am | 1 + src/ipset.8 | 71 +- src/ipset.c | 2 + src/ipset_hash_netiface.c | 120 ++++ tests/hash:net,iface.t | 57 ++ tests/hash:net,iface.t.list0 | 10 + tests/hash:net,iface.t.list2 | 10 + tests/runtest.sh | 1 + 21 files changed, 1157 insertions(+), 6 deletions(-) create mode 100644 kernel/net/netfilter/ipset/ip_set_hash_netiface.c create mode 100644 src/ipset_hash_netiface.c create mode 100644 tests/hash:net,iface.t create mode 100644 tests/hash:net,iface.t.list0 create mode 100644 tests/hash:net,iface.t.list2 diff --git a/include/libipset/data.h b/include/libipset/data.h index 8902ddf..7c7b5e1 100644 --- a/include/libipset/data.h +++ b/include/libipset/data.h @@ -46,11 +46,13 @@ enum ipset_opt { IPSET_OPT_CIDR2, IPSET_OPT_IP2_TO, IPSET_OPT_PROTO, + IPSET_OPT_IFACE, /* Swap/rename to */ IPSET_OPT_SETNAME2, /* Flags */ IPSET_OPT_EXIST, IPSET_OPT_BEFORE, + IPSET_OPT_PHYSDEV, /* Internal options */ IPSET_OPT_FLAGS = 48, /* IPSET_FLAG_EXIST| */ IPSET_OPT_CADT_FLAGS, /* IPSET_FLAG_BEFORE| */ @@ -96,8 +98,10 @@ enum ipset_opt { | IPSET_FLAG(IPSET_OPT_IP2) \ | IPSET_FLAG(IPSET_OPT_CIDR2) \ | IPSET_FLAG(IPSET_OPT_PROTO) \ + | IPSET_FLAG(IPSET_OPT_IFACE) \ | IPSET_FLAG(IPSET_OPT_CADT_FLAGS)\ - | IPSET_FLAG(IPSET_OPT_BEFORE)) + | IPSET_FLAG(IPSET_OPT_BEFORE) \ + | IPSET_FLAG(IPSET_OPT_PHYSDEV)) struct ipset_data; diff --git a/include/libipset/linux_ip_set.h b/include/libipset/linux_ip_set.h index e88f6d5..fb4b69e 100644 --- a/include/libipset/linux_ip_set.h +++ b/include/libipset/linux_ip_set.h @@ -105,6 +105,7 @@ enum { IPSET_ATTR_IP2, IPSET_ATTR_CIDR2, IPSET_ATTR_IP2_TO, + IPSET_ATTR_IFACE, __IPSET_ATTR_ADT_MAX, }; #define IPSET_ATTR_ADT_MAX (__IPSET_ATTR_ADT_MAX - 1) @@ -153,6 +154,8 @@ enum ipset_cmd_flags { enum ipset_cadt_flags { IPSET_FLAG_BIT_BEFORE = 0, IPSET_FLAG_BEFORE = (1 << IPSET_FLAG_BIT_BEFORE), + IPSET_FLAG_BIT_PHYSDEV = 1, + IPSET_FLAG_PHYSDEV = (1 << IPSET_FLAG_BIT_PHYSDEV), }; /* Commands with settype-specific attributes */ diff --git a/include/libipset/parse.h b/include/libipset/parse.h index bc96a6e..08f1089 100644 --- a/include/libipset/parse.h +++ b/include/libipset/parse.h @@ -80,6 +80,8 @@ extern int ipset_parse_flag(struct ipset_session *session, enum ipset_opt opt, const char *str); extern int ipset_parse_typename(struct ipset_session *session, enum ipset_opt opt, const char *str); +extern int ipset_parse_iface(struct ipset_session *session, + enum ipset_opt opt, const char *str); extern int ipset_parse_output(struct ipset_session *session, int opt, const char *str); extern int ipset_parse_ignored(struct ipset_session *session, @@ -87,8 +89,8 @@ extern int ipset_parse_ignored(struct ipset_session *session, extern int ipset_parse_elem(struct ipset_session *session, enum ipset_opt opt, const char *str); extern int ipset_call_parser(struct ipset_session *session, - const struct ipset_arg *arg, - const char *str); + const struct ipset_arg *arg, + const char *str); /* Compatibility parser functions */ extern int ipset_parse_iptimeout(struct ipset_session *session, diff --git a/include/libipset/print.h b/include/libipset/print.h index 963b42e..a3f5b95 100644 --- a/include/libipset/print.h +++ b/include/libipset/print.h @@ -37,6 +37,9 @@ extern int ipset_print_name(char *buf, unsigned int len, extern int ipset_print_port(char *buf, unsigned int len, const struct ipset_data *data, enum ipset_opt opt, uint8_t env); +extern int ipset_print_iface(char *buf, unsigned int len, + const struct ipset_data *data, + enum ipset_opt opt, uint8_t env); extern int ipset_print_proto(char *buf, unsigned int len, const struct ipset_data *data, enum ipset_opt opt, uint8_t env); diff --git a/kernel/include/linux/netfilter/ipset/ip_set.h b/kernel/include/linux/netfilter/ipset/ip_set.h index 8955165..e409173 100644 --- a/kernel/include/linux/netfilter/ipset/ip_set.h +++ b/kernel/include/linux/netfilter/ipset/ip_set.h @@ -105,6 +105,7 @@ enum { IPSET_ATTR_IP2, IPSET_ATTR_CIDR2, IPSET_ATTR_IP2_TO, + IPSET_ATTR_IFACE, __IPSET_ATTR_ADT_MAX, }; #define IPSET_ATTR_ADT_MAX (__IPSET_ATTR_ADT_MAX - 1) @@ -153,6 +154,8 @@ enum ipset_cmd_flags { enum ipset_cadt_flags { IPSET_FLAG_BIT_BEFORE = 0, IPSET_FLAG_BEFORE = (1 << IPSET_FLAG_BIT_BEFORE), + IPSET_FLAG_BIT_PHYSDEV = 1, + IPSET_FLAG_PHYSDEV = (1 << IPSET_FLAG_BIT_PHYSDEV), }; /* Commands with settype-specific attributes */ @@ -212,6 +215,8 @@ 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), + IPSET_TYPE_IFACE_FLAG = 5, + IPSET_TYPE_IFACE = (1 << IPSET_TYPE_IFACE_FLAG), /* Strictly speaking not a feature, but a flag for dumping: * this settype must be dumped last */ IPSET_DUMP_LAST_FLAG = 7, diff --git a/kernel/include/linux/netfilter/ipset/ip_set_ahash.h b/kernel/include/linux/netfilter/ipset/ip_set_ahash.h index 1c977e1..8a0999a 100644 --- a/kernel/include/linux/netfilter/ipset/ip_set_ahash.h +++ b/kernel/include/linux/netfilter/ipset/ip_set_ahash.h @@ -63,6 +63,9 @@ struct ip_set_hash { #ifdef IP_SET_HASH_WITH_NETMASK u8 netmask; /* netmask value for subnets to store */ #endif +#ifdef IP_SET_HASH_WITH_RBTREE + struct rb_root rbtree; +#endif #ifdef IP_SET_HASH_WITH_NETS struct ip_set_hash_nets nets[0]; /* book-keeping of prefixes */ #endif @@ -200,6 +203,9 @@ ip_set_hash_destroy(struct ip_set *set) del_timer_sync(&h->gc); ahash_destroy(h->table); +#ifdef IP_SET_HASH_WITH_RBTREE + rbtree_destroy(&h->rbtree); +#endif kfree(h); set->data = NULL; diff --git a/kernel/net/netfilter/ipset/Kbuild b/kernel/net/netfilter/ipset/Kbuild index 7900a5d..a04bb64 100644 --- a/kernel/net/netfilter/ipset/Kbuild +++ b/kernel/net/netfilter/ipset/Kbuild @@ -6,6 +6,7 @@ obj-m += ip_set.o obj-m += ip_set_bitmap_ip.o ip_set_bitmap_ipmac.o ip_set_bitmap_port.o obj-m += ip_set_hash_ip.o ip_set_hash_ipport.o ip_set_hash_ipportip.o obj-m += ip_set_hash_net.o ip_set_hash_ipportnet.o ip_set_hash_netport.o +obj-m += ip_set_hash_netiface.o obj-m += ip_set_list_set.o # It's for me... diff --git a/kernel/net/netfilter/ipset/ip_set_hash_netiface.c b/kernel/net/netfilter/ipset/ip_set_hash_netiface.c new file mode 100644 index 0000000..51e5df1 --- /dev/null +++ b/kernel/net/netfilter/ipset/ip_set_hash_netiface.c @@ -0,0 +1,762 @@ +/* Copyright (C) 2011 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 set type: the hash:net,iface type */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Jozsef Kadlecsik "); +MODULE_DESCRIPTION("hash:net,iface type of IP sets"); +MODULE_ALIAS("ip_set_hash:net,iface"); + +/* Interface name rbtree */ + +struct iface_node { + struct rb_node node; + char iface[IFNAMSIZ]; +}; + +#define iface_data(n) (rb_entry(n, struct iface_node, node)->iface) + +static inline long +ifname_compare(const char *_a, const char *_b) +{ + const long *a = (const long *)_a; + const long *b = (const long *)_b; + + BUILD_BUG_ON(IFNAMSIZ > 4 * sizeof(unsigned long)); + if (a[0] != b[0]) + return a[0] - b[0]; + if (IFNAMSIZ > sizeof(long)) { + if (a[1] != b[1]) + return a[1] - b[1]; + } + if (IFNAMSIZ > 2 * sizeof(long)) { + if (a[2] != b[2]) + return a[2] - b[2]; + } + if (IFNAMSIZ > 3 * sizeof(long)) { + if (a[3] != b[3]) + return a[3] - b[3]; + } + return 0; +} + +static void +rbtree_destroy(struct rb_root *root) +{ + struct rb_node *p, *n = root->rb_node; + struct iface_node *node; + + /* Non-recursive destroy, like in ext3 */ + while (n) { + if (n->rb_left) { + n = n->rb_left; + continue; + } + if (n->rb_right) { + n = n->rb_right; + continue; + } + p = rb_parent(n); + node = rb_entry(n, struct iface_node, node); + if (!p) + *root = RB_ROOT; + else if (p->rb_left == n) + p->rb_left = NULL; + else if (p->rb_right == n) + p->rb_right = NULL; + + kfree(node); + n = p; + } +} + +static int +iface_test(struct rb_root *root, const char **iface) +{ + struct rb_node *n = root->rb_node; + + while (n) { + const char *d = iface_data(n); + int res = ifname_compare(*iface, d); + + if (res < 0) + n = n->rb_left; + else if (res > 0) + n = n->rb_right; + else { + *iface = d; + return 1; + } + } + return 0; +} + +static int +iface_add(struct rb_root *root, const char **iface) +{ + struct rb_node **n = &(root->rb_node), *p = NULL; + struct iface_node *d; + + while (*n) { + char *ifname = iface_data(*n); + int res = ifname_compare(*iface, ifname); + + p = *n; + if (res < 0) + n = &((*n)->rb_left); + else if (res > 0) + n = &((*n)->rb_right); + else { + *iface = ifname; + return 0; + } + } + + d = kzalloc(sizeof(*d), GFP_ATOMIC); + if (!d) + return -ENOMEM; + strcpy(d->iface, *iface); + + rb_link_node(&d->node, p, n); + rb_insert_color(&d->node, root); + + *iface = d->iface; + return 0; +} + +/* Type specific function prefix */ +#define TYPE hash_netiface + +static bool +hash_netiface_same_set(const struct ip_set *a, const struct ip_set *b); + +#define hash_netiface4_same_set hash_netiface_same_set +#define hash_netiface6_same_set hash_netiface_same_set + +#define STREQ(a, b) (strcmp(a, b) == 0) + +/* The type variant functions: IPv4 */ + +/* Member elements without timeout */ +struct hash_netiface4_elem { + __be32 ip; + const char *iface; + u8 physdev; + u8 cidr; + u16 padding; +}; + +/* Member elements with timeout support */ +struct hash_netiface4_telem { + __be32 ip; + const char *iface; + u8 physdev; + u8 cidr; + u16 padding; + unsigned long timeout; +}; + +static inline bool +hash_netiface4_data_equal(const struct hash_netiface4_elem *ip1, + const struct hash_netiface4_elem *ip2) +{ + return ip1->ip == ip2->ip && + ip1->cidr == ip2->cidr && + ip1->physdev == ip2->physdev && + ip1->iface == ip2->iface; +} + +static inline bool +hash_netiface4_data_isnull(const struct hash_netiface4_elem *elem) +{ + return elem->cidr == 0; +} + +static inline void +hash_netiface4_data_copy(struct hash_netiface4_elem *dst, + const struct hash_netiface4_elem *src) { + dst->ip = src->ip; + dst->cidr = src->cidr; + dst->physdev = src->physdev; + dst->iface = src->iface; +} + +static inline void +hash_netiface4_data_netmask(struct hash_netiface4_elem *elem, u8 cidr) +{ + elem->ip &= ip_set_netmask(cidr); + elem->cidr = cidr; +} + +static inline void +hash_netiface4_data_zero_out(struct hash_netiface4_elem *elem) +{ + elem->cidr = 0; +} + +static bool +hash_netiface4_data_list(struct sk_buff *skb, + const struct hash_netiface4_elem *data) +{ + u32 flags = data->physdev ? IPSET_FLAG_PHYSDEV : 0; + + NLA_PUT_IPADDR4(skb, IPSET_ATTR_IP, data->ip); + NLA_PUT_U8(skb, IPSET_ATTR_CIDR, data->cidr); + NLA_PUT_STRING(skb, IPSET_ATTR_IFACE, data->iface); + if (flags) + NLA_PUT_NET32(skb, IPSET_ATTR_CADT_FLAGS, flags); + return 0; + +nla_put_failure: + return 1; +} + +static bool +hash_netiface4_data_tlist(struct sk_buff *skb, + const struct hash_netiface4_elem *data) +{ + const struct hash_netiface4_telem *tdata = + (const struct hash_netiface4_telem *)data; + u32 flags = data->physdev ? IPSET_FLAG_PHYSDEV : 0; + + NLA_PUT_IPADDR4(skb, IPSET_ATTR_IP, data->ip); + NLA_PUT_U8(skb, IPSET_ATTR_CIDR, data->cidr); + NLA_PUT_STRING(skb, IPSET_ATTR_IFACE, data->iface); + if (flags) + NLA_PUT_NET32(skb, IPSET_ATTR_CADT_FLAGS, flags); + NLA_PUT_NET32(skb, IPSET_ATTR_TIMEOUT, + htonl(ip_set_timeout_get(tdata->timeout))); + + return 0; + +nla_put_failure: + return 1; +} + +#define IP_SET_HASH_WITH_NETS +#define IP_SET_HASH_WITH_RBTREE + +#define PF 4 +#define HOST_MASK 32 +#include + +static inline void +hash_netiface4_data_next(struct ip_set_hash *h, + const struct hash_netiface4_elem *d) +{ + h->next.ip = ntohl(d->ip); +} + +static int +hash_netiface4_kadt(struct ip_set *set, const struct sk_buff *skb, + const struct xt_action_param *par, + enum ipset_adt adt, const struct ip_set_adt_opt *opt) +{ + struct ip_set_hash *h = set->data; + ipset_adtfn adtfn = set->variant->adt[adt]; + struct hash_netiface4_elem data = { + .cidr = h->nets[0].cidr ? h->nets[0].cidr : HOST_MASK + }; + int ret; + + if (data.cidr == 0) + return -EINVAL; + if (adt == IPSET_TEST) + data.cidr = HOST_MASK; + + ip4addrptr(skb, opt->flags & IPSET_DIM_ONE_SRC, &data.ip); + data.ip &= ip_set_netmask(data.cidr); + +#define IFACE(dir) (par->dir ? par->dir->name : NULL) +#define PHYSDEV(dir) (nf_bridge->dir ? nf_bridge->dir->name : NULL) +#define SRCDIR (opt->flags & IPSET_DIM_TWO_SRC) + + if (opt->cmdflags & IPSET_FLAG_PHYSDEV) { +#ifdef CONFIG_BRIDGE_NETFILTER + const struct nf_bridge_info *nf_bridge = skb->nf_bridge; + + if (!nf_bridge) + return -EINVAL; + data.iface = SRCDIR ? PHYSDEV(physindev): PHYSDEV(physoutdev); + data.physdev = 1; +#else + data.iface = NULL; +#endif + } else + data.iface = SRCDIR ? IFACE(in) : IFACE(out); + + if (!data.iface) + return -EINVAL; + ret = iface_test(&h->rbtree, &data.iface); + if (adt == IPSET_ADD) { + if (!ret) { + ret = iface_add(&h->rbtree, &data.iface); + if (ret) + return ret; + } + } else if (!ret) + return ret; + + return adtfn(set, &data, opt_timeout(opt, h), opt->cmdflags); +} + +static int +hash_netiface4_uadt(struct ip_set *set, struct nlattr *tb[], + enum ipset_adt adt, u32 *lineno, u32 flags, bool retried) +{ + struct ip_set_hash *h = set->data; + ipset_adtfn adtfn = set->variant->adt[adt]; + struct hash_netiface4_elem data = { .cidr = HOST_MASK }; + u32 ip = 0, ip_to, last; + u32 timeout = h->timeout; + char iface[IFNAMSIZ] = {}; + int ret; + + if (unlikely(!tb[IPSET_ATTR_IP] || + !tb[IPSET_ATTR_IFACE] || + !ip_set_optattr_netorder(tb, IPSET_ATTR_TIMEOUT) || + !ip_set_optattr_netorder(tb, IPSET_ATTR_CADT_FLAGS))) + return -IPSET_ERR_PROTOCOL; + + if (tb[IPSET_ATTR_LINENO]) + *lineno = nla_get_u32(tb[IPSET_ATTR_LINENO]); + + ret = ip_set_get_hostipaddr4(tb[IPSET_ATTR_IP], &ip); + if (ret) + return ret; + + if (tb[IPSET_ATTR_CIDR]) { + data.cidr = nla_get_u8(tb[IPSET_ATTR_CIDR]); + if (!data.cidr) + return -IPSET_ERR_INVALID_CIDR; + } + + if (tb[IPSET_ATTR_TIMEOUT]) { + if (!with_timeout(h->timeout)) + return -IPSET_ERR_TIMEOUT; + timeout = ip_set_timeout_uget(tb[IPSET_ATTR_TIMEOUT]); + } + + strcpy(iface, nla_data(tb[IPSET_ATTR_IFACE])); + data.iface = iface; + ret = iface_test(&h->rbtree, &data.iface); + if (adt == IPSET_ADD) { + if (!ret) { + ret = iface_add(&h->rbtree, &data.iface); + if (ret) + return ret; + } + } else if (!ret) + return ret; + + if (tb[IPSET_ATTR_CADT_FLAGS]) { + u32 flags = ip_set_get_h32(tb[IPSET_ATTR_CADT_FLAGS]); + if (flags & IPSET_FLAG_PHYSDEV) + data.physdev = 1; + } + + if (adt == IPSET_TEST || !tb[IPSET_ATTR_IP_TO]) { + data.ip = htonl(ip & ip_set_hostmask(data.cidr)); + ret = adtfn(set, &data, timeout, flags); + return ip_set_eexist(ret, flags) ? 0 : ret; + } + + if (tb[IPSET_ATTR_IP_TO]) { + ret = ip_set_get_hostipaddr4(tb[IPSET_ATTR_IP_TO], &ip_to); + if (ret) + return ret; + if (ip_to < ip) + swap(ip, ip_to); + if (ip + UINT_MAX == ip_to) + return -IPSET_ERR_HASH_RANGE; + } else { + ip_set_mask_from_to(ip, ip_to, data.cidr); + } + + if (retried) + ip = h->next.ip; + while (!after(ip, ip_to)) { + data.ip = htonl(ip); + last = ip_set_range_to_cidr(ip, ip_to, &data.cidr); + ret = adtfn(set, &data, timeout, flags); + + if (ret && !ip_set_eexist(ret, flags)) + return ret; + else + ret = 0; + ip = last + 1; + } + return ret; +} + +static bool +hash_netiface_same_set(const struct ip_set *a, const struct ip_set *b) +{ + const struct ip_set_hash *x = a->data; + const struct ip_set_hash *y = b->data; + + /* Resizing changes htable_bits, so we ignore it */ + return x->maxelem == y->maxelem && + x->timeout == y->timeout; +} + +/* The type variant functions: IPv6 */ + +struct hash_netiface6_elem { + union nf_inet_addr ip; + const char *iface; + u8 physdev; + u8 cidr; + u16 padding; +}; + +struct hash_netiface6_telem { + union nf_inet_addr ip; + const char *iface; + u8 physdev; + u8 cidr; + u16 padding; + unsigned long timeout; +}; + +static inline bool +hash_netiface6_data_equal(const struct hash_netiface6_elem *ip1, + const struct hash_netiface6_elem *ip2) +{ + return ipv6_addr_cmp(&ip1->ip.in6, &ip2->ip.in6) == 0 && + ip1->cidr == ip2->cidr && + ip1->physdev == ip2->physdev && + ip1->iface == ip2->iface; +} + +static inline bool +hash_netiface6_data_isnull(const struct hash_netiface6_elem *elem) +{ + return elem->cidr == 0; +} + +static inline void +hash_netiface6_data_copy(struct hash_netiface6_elem *dst, + const struct hash_netiface6_elem *src) +{ + memcpy(dst, src, sizeof(*dst)); +} + +static inline void +hash_netiface6_data_zero_out(struct hash_netiface6_elem *elem) +{ +} + +static inline void +ip6_netmask(union nf_inet_addr *ip, u8 prefix) +{ + ip->ip6[0] &= ip_set_netmask6(prefix)[0]; + ip->ip6[1] &= ip_set_netmask6(prefix)[1]; + ip->ip6[2] &= ip_set_netmask6(prefix)[2]; + ip->ip6[3] &= ip_set_netmask6(prefix)[3]; +} + +static inline void +hash_netiface6_data_netmask(struct hash_netiface6_elem *elem, u8 cidr) +{ + ip6_netmask(&elem->ip, cidr); + elem->cidr = cidr; +} + +static bool +hash_netiface6_data_list(struct sk_buff *skb, + const struct hash_netiface6_elem *data) +{ + u32 flags = data->physdev ? IPSET_FLAG_PHYSDEV : 0; + + NLA_PUT_IPADDR6(skb, IPSET_ATTR_IP, &data->ip); + NLA_PUT_U8(skb, IPSET_ATTR_CIDR, data->cidr); + NLA_PUT_STRING(skb, IPSET_ATTR_IFACE, data->iface); + if (flags) + NLA_PUT_NET32(skb, IPSET_ATTR_CADT_FLAGS, flags); + return 0; + +nla_put_failure: + return 1; +} + +static bool +hash_netiface6_data_tlist(struct sk_buff *skb, + const struct hash_netiface6_elem *data) +{ + const struct hash_netiface6_telem *e = + (const struct hash_netiface6_telem *)data; + u32 flags = data->physdev ? IPSET_FLAG_PHYSDEV : 0; + + NLA_PUT_IPADDR6(skb, IPSET_ATTR_IP, &e->ip); + NLA_PUT_U8(skb, IPSET_ATTR_CIDR, data->cidr); + NLA_PUT_STRING(skb, IPSET_ATTR_IFACE, data->iface); + if (flags) + NLA_PUT_NET32(skb, IPSET_ATTR_CADT_FLAGS, flags); + NLA_PUT_NET32(skb, IPSET_ATTR_TIMEOUT, + htonl(ip_set_timeout_get(e->timeout))); + return 0; + +nla_put_failure: + return 1; +} + +#undef PF +#undef HOST_MASK + +#define PF 6 +#define HOST_MASK 128 +#include + +static inline void +hash_netiface6_data_next(struct ip_set_hash *h, + const struct hash_netiface6_elem *d) +{ +} + +static int +hash_netiface6_kadt(struct ip_set *set, const struct sk_buff *skb, + const struct xt_action_param *par, + enum ipset_adt adt, const struct ip_set_adt_opt *opt) +{ + struct ip_set_hash *h = set->data; + ipset_adtfn adtfn = set->variant->adt[adt]; + struct hash_netiface6_elem data = { + .cidr = h->nets[0].cidr ? h->nets[0].cidr : HOST_MASK + }; + int ret; + + if (data.cidr == 0) + return -EINVAL; + if (adt == IPSET_TEST) + data.cidr = HOST_MASK; + + ip6addrptr(skb, opt->flags & IPSET_DIM_ONE_SRC, &data.ip.in6); + ip6_netmask(&data.ip, data.cidr); + + if (opt->cmdflags & IPSET_FLAG_PHYSDEV) { +#ifdef CONFIG_BRIDGE_NETFILTER + const struct nf_bridge_info *nf_bridge = skb->nf_bridge; + + if (!nf_bridge) + return -EINVAL; + data.iface = SRCDIR ? PHYSDEV(physindev): PHYSDEV(physoutdev); + data.physdev = 1; +#else + data.iface = NULL; +#endif + } else + data.iface = SRCDIR ? IFACE(in) : IFACE(out); + + if (!data.iface) + return -EINVAL; + ret = iface_test(&h->rbtree, &data.iface); + if (adt == IPSET_ADD) { + if (!ret) { + ret = iface_add(&h->rbtree, &data.iface); + if (ret) + return ret; + } + } else if (!ret) + return ret; + + return adtfn(set, &data, opt_timeout(opt, h), opt->cmdflags); +} + +static int +hash_netiface6_uadt(struct ip_set *set, struct nlattr *tb[], + enum ipset_adt adt, u32 *lineno, u32 flags, bool retried) +{ + struct ip_set_hash *h = set->data; + ipset_adtfn adtfn = set->variant->adt[adt]; + struct hash_netiface6_elem data = { .cidr = HOST_MASK }; + u32 timeout = h->timeout; + char iface[IFNAMSIZ] = {}; + int ret; + + if (unlikely(!tb[IPSET_ATTR_IP] || + !tb[IPSET_ATTR_IFACE] || + !ip_set_optattr_netorder(tb, IPSET_ATTR_TIMEOUT) || + !ip_set_optattr_netorder(tb, IPSET_ATTR_CADT_FLAGS))) + return -IPSET_ERR_PROTOCOL; + if (unlikely(tb[IPSET_ATTR_IP_TO])) + return -IPSET_ERR_HASH_RANGE_UNSUPPORTED; + + if (tb[IPSET_ATTR_LINENO]) + *lineno = nla_get_u32(tb[IPSET_ATTR_LINENO]); + + ret = ip_set_get_ipaddr6(tb[IPSET_ATTR_IP], &data.ip); + if (ret) + return ret; + + if (tb[IPSET_ATTR_CIDR]) + data.cidr = nla_get_u8(tb[IPSET_ATTR_CIDR]); + if (!data.cidr) + return -IPSET_ERR_INVALID_CIDR; + ip6_netmask(&data.ip, data.cidr); + + if (tb[IPSET_ATTR_TIMEOUT]) { + if (!with_timeout(h->timeout)) + return -IPSET_ERR_TIMEOUT; + timeout = ip_set_timeout_uget(tb[IPSET_ATTR_TIMEOUT]); + } + + strcpy(iface, nla_data(tb[IPSET_ATTR_IFACE])); + data.iface = iface; + ret = iface_test(&h->rbtree, &data.iface); + if (adt == IPSET_ADD) { + if (!ret) { + ret = iface_add(&h->rbtree, &data.iface); + if (ret) + return ret; + } + } else if (!ret) + return ret; + + if (tb[IPSET_ATTR_CADT_FLAGS]) { + u32 flags = ip_set_get_h32(tb[IPSET_ATTR_CADT_FLAGS]); + if (flags & IPSET_FLAG_PHYSDEV) + data.physdev = 1; + } + + ret = adtfn(set, &data, timeout, flags); + + return ip_set_eexist(ret, flags) ? 0 : ret; +} + +/* Create hash:ip type of sets */ + +static int +hash_netiface_create(struct ip_set *set, struct nlattr *tb[], u32 flags) +{ + struct ip_set_hash *h; + u32 hashsize = IPSET_DEFAULT_HASHSIZE, maxelem = IPSET_DEFAULT_MAXELEM; + u8 hbits; + + if (!(set->family == AF_INET || set->family == AF_INET6)) + return -IPSET_ERR_INVALID_FAMILY; + + if (unlikely(!ip_set_optattr_netorder(tb, IPSET_ATTR_HASHSIZE) || + !ip_set_optattr_netorder(tb, IPSET_ATTR_MAXELEM) || + !ip_set_optattr_netorder(tb, IPSET_ATTR_TIMEOUT))) + return -IPSET_ERR_PROTOCOL; + + 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) + + sizeof(struct ip_set_hash_nets) + * (set->family == AF_INET ? 32 : 128), GFP_KERNEL); + if (!h) + return -ENOMEM; + + h->maxelem = maxelem; + get_random_bytes(&h->initval, sizeof(h->initval)); + h->timeout = IPSET_NO_TIMEOUT; + + hbits = htable_bits(hashsize); + h->table = ip_set_alloc( + sizeof(struct htable) + + jhash_size(hbits) * sizeof(struct hbucket)); + if (!h->table) { + kfree(h); + return -ENOMEM; + } + h->table->htable_bits = hbits; + h->rbtree = RB_ROOT; + + 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_netiface4_tvariant : &hash_netiface6_tvariant; + + if (set->family == AF_INET) + hash_netiface4_gc_init(set); + else + hash_netiface6_gc_init(set); + } else { + set->variant = set->family == AF_INET + ? &hash_netiface4_variant : &hash_netiface6_variant; + } + + pr_debug("create %s hashsize %u (%u) maxelem %u: %p(%p)\n", + set->name, jhash_size(h->table->htable_bits), + h->table->htable_bits, h->maxelem, set->data, h->table); + + return 0; +} + +static struct ip_set_type hash_netiface_type __read_mostly = { + .name = "hash:net,iface", + .protocol = IPSET_PROTOCOL, + .features = IPSET_TYPE_IP | IPSET_TYPE_IFACE, + .dimension = IPSET_DIM_TWO, + .family = AF_UNSPEC, + .revision_min = 0, + .create = hash_netiface_create, + .create_policy = { + [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_PROTO] = { .type = NLA_U8 }, + [IPSET_ATTR_TIMEOUT] = { .type = NLA_U32 }, + }, + .adt_policy = { + [IPSET_ATTR_IP] = { .type = NLA_NESTED }, + [IPSET_ATTR_IP_TO] = { .type = NLA_NESTED }, + [IPSET_ATTR_IFACE] = { .type = NLA_NUL_STRING, + .len = IPSET_MAXNAMELEN - 1 }, + [IPSET_ATTR_CADT_FLAGS] = { .type = NLA_U32 }, + [IPSET_ATTR_CIDR] = { .type = NLA_U8 }, + [IPSET_ATTR_TIMEOUT] = { .type = NLA_U32 }, + [IPSET_ATTR_LINENO] = { .type = NLA_U32 }, + }, + .me = THIS_MODULE, +}; + +static int __init +hash_netiface_init(void) +{ + return ip_set_type_register(&hash_netiface_type); +} + +static void __exit +hash_netiface_fini(void) +{ + ip_set_type_unregister(&hash_netiface_type); +} + +module_init(hash_netiface_init); +module_exit(hash_netiface_fini); diff --git a/lib/data.c b/lib/data.c index 1541728..9663efb 100644 --- a/lib/data.c +++ b/lib/data.c @@ -7,6 +7,7 @@ #include /* assert */ #include /* ntoh* */ #include /* ETH_ALEN */ +#include /* IFNAMSIZ */ #include /* AF_ */ #include /* malloc, free */ #include /* memset */ @@ -72,6 +73,7 @@ struct ipset_data { char ether[ETH_ALEN]; char name[IPSET_MAXNAMELEN]; char nameref[IPSET_MAXNAMELEN]; + char iface[IFNAMSIZ]; } adt; }; }; @@ -301,6 +303,9 @@ ipset_data_set(struct ipset_data *data, enum ipset_opt opt, const void *value) case IPSET_OPT_PROTO: data->adt.proto = *(const uint8_t *) value; break; + case IPSET_OPT_IFACE: + ipset_strlcpy(data->adt.iface, value, IFNAMSIZ); + break; /* Swap/rename */ case IPSET_OPT_SETNAME2: ipset_strlcpy(data->setname2, value, IPSET_MAXNAMELEN); @@ -312,6 +317,9 @@ ipset_data_set(struct ipset_data *data, enum ipset_opt opt, const void *value) case IPSET_OPT_BEFORE: cadt_flag_type_attr(data, opt, IPSET_FLAG_BEFORE); break; + case IPSET_OPT_PHYSDEV: + cadt_flag_type_attr(data, opt, IPSET_FLAG_PHYSDEV); + break; case IPSET_OPT_FLAGS: data->flags = *(const uint32_t *)value; break; @@ -413,6 +421,8 @@ ipset_data_get(const struct ipset_data *data, enum ipset_opt opt) return &data->adt.cidr2; case IPSET_OPT_PROTO: return &data->adt.proto; + case IPSET_OPT_IFACE: + return &data->adt.iface; /* Swap/rename */ case IPSET_OPT_SETNAME2: return data->setname2; @@ -422,6 +432,7 @@ ipset_data_get(const struct ipset_data *data, enum ipset_opt opt) return &data->flags; case IPSET_OPT_CADT_FLAGS: case IPSET_OPT_BEFORE: + case IPSET_OPT_PHYSDEV: return &data->cadt_flags; default: return NULL; @@ -472,8 +483,9 @@ ipset_data_sizeof(enum ipset_opt opt, uint8_t family) return sizeof(uint8_t); case IPSET_OPT_ETHER: return ETH_ALEN; - /* Flags counted once */ + /* Flags doesn't counted once :-( */ case IPSET_OPT_BEFORE: + case IPSET_OPT_PHYSDEV: return sizeof(uint32_t); default: return 0; diff --git a/lib/debug.c b/lib/debug.c index 174567d..5b97e6e 100644 --- a/lib/debug.c +++ b/lib/debug.c @@ -63,6 +63,7 @@ static const struct ipset_attrname adtattr2name[] = { [IPSET_ATTR_IP2] = { .name = "IP2" }, [IPSET_ATTR_CIDR2] = { .name = "CIDR2" }, [IPSET_ATTR_IP2_TO] = { .name = "IP2_TO" }, + [IPSET_ATTR_IFACE] = { .name = "IFACE" }, }; static void diff --git a/lib/parse.c b/lib/parse.c index 091fc6d..3d2a12b 100644 --- a/lib/parse.c +++ b/lib/parse.c @@ -12,6 +12,7 @@ #include /* getaddrinfo */ #include /* getaddrinfo, AF_ */ #include /* ETH_ALEN */ +#include /* IFNAMSIZ */ #include /* IPPROTO_ */ #include /* D() */ @@ -1394,6 +1395,40 @@ ipset_parse_typename(struct ipset_session *session, return ipset_session_data_set(session, IPSET_OPT_TYPE, type); } +/** + * ipset_parse_iface - parse string as an interface name + * @session: session structure + * @opt: option kind of the data + * @str: string to parse + * + * Parse string as an interface name, optionally with 'physdev:' prefix. + * The value is stored in the data blob of the session. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_parse_iface(struct ipset_session *session, + enum ipset_opt opt, const char *str) +{ + struct ipset_data *data; + int offset = 0, err = 0; + + assert(session); + assert(opt == IPSET_OPT_IFACE); + assert(str); + + data = ipset_session_data(session); + if (STREQ(str, "physdev:")) { + offset = 8; + err = ipset_data_set(data, IPSET_OPT_PHYSDEV, str); + } + if (strlen(str + offset) > IFNAMSIZ - 1) + return syntax_err("interface name '%s' is longer than %u characters", + str + offset, IFNAMSIZ - 1); + + return ipset_data_set(data, opt, str + offset); +} + /** * ipset_parse_output - parse output format name * @session: session structure diff --git a/lib/print.c b/lib/print.c index 66b9c1a..bcccd3f 100644 --- a/lib/print.c +++ b/lib/print.c @@ -12,6 +12,7 @@ #include /* inet_ntop */ #include /* inet_ntop */ #include /* ETH_ALEN */ +#include /* IFNAMSIZ */ #include /* D() */ #include /* ipset_data_* */ @@ -443,6 +444,45 @@ ipset_print_port(char *buf, unsigned int len, return offset; } +/** + * ipset_print_iface - print interface element string + * @buf: printing buffer + * @len: length of available buffer space + * @data: data blob + * @opt: the option kind + * @env: environment flags + * + * Print interface element string to output buffer. + * + * Return lenght of printed string or error size. + */ +int +ipset_print_iface(char *buf, unsigned int len, + const struct ipset_data *data, enum ipset_opt opt, + uint8_t env UNUSED) +{ + const char *name; + int size, offset = 0; + + assert(buf); + assert(len > 0); + assert(data); + assert(opt == IPSET_OPT_IFACE); + + if (len < IFNAMSIZ + strlen("physdev:")) + return -1; + + if (ipset_data_test(data, IPSET_OPT_PHYSDEV)) { + size = snprintf(buf, len, "physdev:"); + SNPRINTF_FAILURE(size, len, offset); + } + name = ipset_data_get(data, opt); + assert(name); + size = snprintf(buf, len, "%s", name); + SNPRINTF_FAILURE(size, len, offset); + return offset; +} + /** * ipset_print_proto - print protocol name * @buf: printing buffer @@ -731,6 +771,9 @@ ipset_print_data(char *buf, unsigned int len, case IPSET_OPT_PORT: size = ipset_print_port(buf, len, data, opt, env); break; + case IPSET_OPT_IFACE: + size = ipset_print_iface(buf, len, data, opt, env); + break; case IPSET_OPT_GC: case IPSET_OPT_HASHSIZE: case IPSET_OPT_MAXELEM: diff --git a/lib/session.c b/lib/session.c index c03ed5d..93d33ff 100644 --- a/lib/session.c +++ b/lib/session.c @@ -13,6 +13,7 @@ #include /* str* */ #include /* getpagesize */ #include /* ETH_ALEN */ +#include /* IFNAMSIZ */ #include /* D() */ #include /* IPSET_OPT_* */ @@ -473,6 +474,11 @@ static const struct ipset_attr_policy adt_attrs[] = { .type = MNL_TYPE_NESTED, .opt = IPSET_OPT_IP2_TO, }, + [IPSET_ATTR_IFACE] = { + .type = MNL_TYPE_NUL_STRING, + .opt = IPSET_OPT_IFACE, + .len = IFNAMSIZ, + }, }; static const struct ipset_attr_policy ipaddr_attrs[] = { diff --git a/src/Makefile.am b/src/Makefile.am index 336145a..f3047f0 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -12,6 +12,7 @@ ipset_SOURCES = ipset.c \ ipset_hash_ipportnet.c \ ipset_hash_net.c \ ipset_hash_netport.c \ + ipset_hash_netiface.c \ ipset_list_set.c \ ui.c ipset_LDADD = ../lib/libipset.la diff --git a/src/ipset.8 b/src/ipset.8 index 90914f4..648e935 100644 --- a/src/ipset.8 +++ b/src/ipset.8 @@ -214,8 +214,8 @@ command follows the syntax where the current list of the methods are \fBbitmap\fR, \fBhash\fR, and \fBlist\fR and the possible data types -are \fBip\fR, \fBnet\fR, \fBmac\fR and \fBport\fR. The dimension of a set -is equal to the number of data types in its type name. +are \fBip\fR, \fBnet\fR, \fBmac\fR, \fBport\fR and \fBiface\fR. +The dimension of a set is equal to the number of data types in its type name. When adding, deleting or testing entries in a set, the same comma separated data syntax must be used for the entry parameter of the commands, i.e @@ -711,6 +711,73 @@ ipset add foo 192.168.1,80,10.0.0/24 ipset add foo 192.168.2,25,10.1.0.0/16 .IP ipset test foo 192.168.1,80.10.0.0/24 +.SS hash:net,iface +The \fBhash:net,iface\fR set type uses a hash to store different sized IP network +address and interface name pairs. Network address with zero prefix size is not +accepted. +.PP +\fICREATE\-OPTIONS\fR := [ \fBfamily\fR { \fBinet\fR | \fBinet6\fR } ] | [ \fBhashsize\fR \fIvalue\fR ] [ \fBmaxelem\fR \fIvalue\fR ] [ \fBtimeout\fR \fIvalue\fR ] +.PP +\fIADD\-ENTRY\fR := \fInetaddr\fR,[\fBphysdev\fR:]\fIiface\fR +.PP +\fIADD\-OPTIONS\fR := [ \fBtimeout\fR \fIvalue\fR ] +.PP +\fIDEL\-ENTRY\fR := \fInetaddr\fR,[\fBphysdev\fR:]\fIiface\fR +.PP +\fITEST\-ENTRY\fR := \fInetaddr\fR,[\fBphysdev\fR:]\fIiface\fR +.PP +where +\fInetaddr\fR := \fIip\fR[/\fIcidr\fR] +.PP +Optional \fBcreate\fR options: +.TP +\fBfamily\fR { \fBinet\fR | \fBinet6\fR } +The protocol family of the IP addresses to be stored in the set. The default is +\fBinet\fR, i.e IPv4. +.TP +\fBhashsize\fR \fIvalue\fR +The initial hash size for the set, default is 1024. The hash size must be a power +of two, the kernel automatically rounds up non power of two hash sizes to the first +correct value. +.TP +\fBmaxelem\fR \fIvalue\fR +The maximal number of elements which can be stored in the set, default 65536. +.PP +For the \fInetaddr\fR part of the elements +see the description at the \fBhash:net\fR set type. +.PP +When adding/deleting/testing entries, if the cidr prefix parameter is not specified, +then the host prefix value is assumed. When adding/deleting entries, the exact +element is added/deleted and overlapping elements are not checked by the kernel. +When testing entries, if a host address is tested, then the kernel tries to match +the host address in the networks added to the set and reports the result accordingly. +.PP +From the \fBset\fR netfilter match point of view the searching for a match +always starts from the smallest size of netblock (most specific +prefix) to the largest one (least specific prefix) added to the set. +When adding/deleting IP +addresses to the set by the \fBSET\fR netfilter target, it will be +added/deleted by the most specific prefix which can be found in the +set, or by the host prefix value if the set is empty. +.PP +The second direction parameter of the \fBset\fR match and +\fBSET\fR target modules corresponds to the incoming/outgoing interface +: \fBsrc\fR to the incoming, while \fBdst\fR to the outgoing. When +the interface is flagged with \fBphysdev:\fR, the interface is interpreted +as the incoming/outgoing bridge port. +.PP +The lookup time grows linearly with the number of the different prefix +values added to the set. +.PP +Examples: +.IP +ipset create foo hash:net,iface +.IP +ipset add foo 192.168.0/24,eth0 +.IP +ipset add foo 10.1.0.0/16,eth1 +.IP +ipset test foo 192.168.0/24,eth0 .SS list:set The \fBlist:set\fR type uses a simple list in which you can store set names. diff --git a/src/ipset.c b/src/ipset.c index 371d851..032564c 100644 --- a/src/ipset.c +++ b/src/ipset.c @@ -42,6 +42,7 @@ extern struct ipset_type ipset_hash_net0; extern struct ipset_type ipset_hash_net1; extern struct ipset_type ipset_hash_netport1; extern struct ipset_type ipset_hash_netport2; +extern struct ipset_type ipset_hash_netiface0; extern struct ipset_type ipset_hash_ipport1; extern struct ipset_type ipset_hash_ipportip1; extern struct ipset_type ipset_hash_ipportnet1; @@ -729,6 +730,7 @@ main(int argc, char *argv[]) ipset_type_add(&ipset_hash_net1); ipset_type_add(&ipset_hash_netport1); ipset_type_add(&ipset_hash_netport2); + ipset_type_add(&ipset_hash_netiface0); ipset_type_add(&ipset_hash_ipport1); ipset_type_add(&ipset_hash_ipportip1); ipset_type_add(&ipset_hash_ipportnet1); diff --git a/src/ipset_hash_netiface.c b/src/ipset_hash_netiface.c new file mode 100644 index 0000000..bac860b --- /dev/null +++ b/src/ipset_hash_netiface.c @@ -0,0 +1,120 @@ +/* Copyright 2011 Jozsef Kadlecsik (kadlec@blackhole.kfki.hu) + * + * 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. + */ +#include /* IPSET_OPT_* */ +#include /* parser functions */ +#include /* printing functions */ +#include /* ipset_port_usage */ +#include /* prototypes */ + +/* Parse commandline arguments */ +static const struct ipset_arg hash_netiface_create_args[] = { + { .name = { "family", NULL }, + .has_arg = IPSET_MANDATORY_ARG, .opt = IPSET_OPT_FAMILY, + .parse = ipset_parse_family, .print = ipset_print_family, + }, + /* Alias: family inet */ + { .name = { "-4", NULL }, + .has_arg = IPSET_NO_ARG, .opt = IPSET_OPT_FAMILY, + .parse = ipset_parse_family, + }, + /* Alias: family inet6 */ + { .name = { "-6", NULL }, + .has_arg = IPSET_NO_ARG, .opt = IPSET_OPT_FAMILY, + .parse = ipset_parse_family, + }, + { .name = { "hashsize", NULL }, + .has_arg = IPSET_MANDATORY_ARG, .opt = IPSET_OPT_HASHSIZE, + .parse = ipset_parse_uint32, .print = ipset_print_number, + }, + { .name = { "maxelem", NULL }, + .has_arg = IPSET_MANDATORY_ARG, .opt = IPSET_OPT_MAXELEM, + .parse = ipset_parse_uint32, .print = ipset_print_number, + }, + { .name = { "timeout", NULL }, + .has_arg = IPSET_MANDATORY_ARG, .opt = IPSET_OPT_TIMEOUT, + .parse = ipset_parse_uint32, .print = ipset_print_number, + }, + { }, +}; + +static const struct ipset_arg hash_netiface_add_args[] = { + { .name = { "timeout", NULL }, + .has_arg = IPSET_MANDATORY_ARG, .opt = IPSET_OPT_TIMEOUT, + .parse = ipset_parse_uint32, .print = ipset_print_number, + }, + { }, +}; + +static const char hash_netiface_usage[] = +"create SETNAME hash:net,iface\n" +" [family inet|inet6]\n" +" [hashsize VALUE] [maxelem VALUE]\n" +" [timeout VALUE]\n" +"add SETNAME IP[/CIDR]|FROM-TO,[physdev:]IFACE [timeout VALUE]\n" +"del SETNAME IP[/CIDR]|FROM-TO,[physdev:]IFACE\n" +"test SETNAME IP[/CIDR],[physdev:]IFACE\n\n" +"where depending on the INET family\n" +" IP is a valid IPv4 or IPv6 address (or hostname),\n" +" CIDR is a valid IPv4 or IPv6 CIDR prefix.\n" +" Adding/deleting multiple elements with IPv4 is supported.\n"; + +struct ipset_type ipset_hash_netiface0 = { + .name = "hash:net,iface", + .alias = { "netifacehash", NULL }, + .revision = 0, + .family = AF_INET46, + .dimension = IPSET_DIM_TWO, + .elem = { + [IPSET_DIM_ONE] = { + .parse = ipset_parse_ip4_net6, + .print = ipset_print_ip, + .opt = IPSET_OPT_IP + }, + [IPSET_DIM_TWO] = { + .parse = ipset_parse_iface, + .print = ipset_print_iface, + .opt = IPSET_OPT_IFACE + }, + }, + .args = { + [IPSET_CREATE] = hash_netiface_create_args, + [IPSET_ADD] = hash_netiface_add_args, + }, + .mandatory = { + [IPSET_CREATE] = 0, + [IPSET_ADD] = IPSET_FLAG(IPSET_OPT_IP) + | IPSET_FLAG(IPSET_OPT_IFACE), + [IPSET_DEL] = IPSET_FLAG(IPSET_OPT_IP) + | IPSET_FLAG(IPSET_OPT_IFACE), + [IPSET_TEST] = IPSET_FLAG(IPSET_OPT_IP) + | IPSET_FLAG(IPSET_OPT_IFACE), + }, + .full = { + [IPSET_CREATE] = IPSET_FLAG(IPSET_OPT_HASHSIZE) + | IPSET_FLAG(IPSET_OPT_MAXELEM) + | IPSET_FLAG(IPSET_OPT_TIMEOUT), + [IPSET_ADD] = IPSET_FLAG(IPSET_OPT_IP) + | IPSET_FLAG(IPSET_OPT_CIDR) + | IPSET_FLAG(IPSET_OPT_IP_TO) + | IPSET_FLAG(IPSET_OPT_IFACE) + | IPSET_FLAG(IPSET_OPT_PHYSDEV) + | IPSET_FLAG(IPSET_OPT_TIMEOUT), + [IPSET_DEL] = IPSET_FLAG(IPSET_OPT_IP) + | IPSET_FLAG(IPSET_OPT_CIDR) + | IPSET_FLAG(IPSET_OPT_IP_TO) + | IPSET_FLAG(IPSET_OPT_IFACE) + | IPSET_FLAG(IPSET_OPT_PHYSDEV), + [IPSET_TEST] = IPSET_FLAG(IPSET_OPT_IP) + | IPSET_FLAG(IPSET_OPT_CIDR) + | IPSET_FLAG(IPSET_OPT_IP_TO) + | IPSET_FLAG(IPSET_OPT_IFACE) + | IPSET_FLAG(IPSET_OPT_PHYSDEV), + }, + + .usage = hash_netiface_usage, +}; + diff --git a/tests/hash:net,iface.t b/tests/hash:net,iface.t new file mode 100644 index 0000000..7c73264 --- /dev/null +++ b/tests/hash:net,iface.t @@ -0,0 +1,57 @@ +# Create a set +0 ipset create test hash:net,iface hashsize 128 +# Add zero valued element +1 ipset add test 0.0.0.0/0,eth0 +# Test zero valued element +1 ipset test test 0.0.0.0/0,eth0 +# Delete zero valued element +1 ipset del test 0.0.0.0/0,eth0 +# Try to add /0 +1 ipset add test 1.1.1.1/0,eth0 +# Try to add /32 +0 ipset add test 1.1.1.1/32,eth0 +# Add almost zero valued element +0 ipset add test 0.0.0.0/1,eth0 +# Test almost zero valued element +0 ipset test test 0.0.0.0/1,eth0 +# Delete almost zero valued element +0 ipset del test 0.0.0.0/1,eth0 +# Test deleted element +1 ipset test test 0.0.0.0/1,eth0 +# Delete element not added to the set +1 ipset del test 0.0.0.0/1,eth0 +# Add first random network +0 ipset add test 2.0.0.1/24,eth0 +# Add second random network +0 ipset add test 192.168.68.69/27,eth1 +# Test first random value +0 ipset test test 2.0.0.255,eth0 +# Test second random value +0 ipset test test 192.168.68.95,eth1 +# Test value not added to the set +1 ipset test test 2.0.1.0,eth0 +# Test value not added to the set +1 ipset test test 2.0.0.255,eth1 +# Test value not added to the set +1 ipset test test 192.168.68.95,eth0 +# Try to add IP address +0 ipset add test 2.0.0.1,eth0 +# List set +0 ipset list test | sed 's/timeout ./timeout x/' > .foo0 && ./sort.sh .foo0 +# Check listing +0 diff -u -I 'Size in memory.*' .foo hash:net,iface.t.list0 +# Flush test set +0 ipset flush test +# Delete test set +0 ipset destroy test +# Create test set +0 ipset new test hash:net,iface +# Add networks in range notation +0 ipset add test 10.2.0.0-10.2.1.12,eth0 +# List set +0 ipset -L test 2>/dev/null > .foo0 && ./sort.sh .foo0 +# Check listing +0 diff -u -I 'Size in memory.*' .foo hash:net,iface.t.list2 +# Delete test set +0 ipset destroy test +# eof diff --git a/tests/hash:net,iface.t.list0 b/tests/hash:net,iface.t.list0 new file mode 100644 index 0000000..1771fca --- /dev/null +++ b/tests/hash:net,iface.t.list0 @@ -0,0 +1,10 @@ +Name: test +Type: hash:net,iface +Header: family inet hashsize 128 maxelem 65536 +Size in memory: 2928 +References: 0 +Members: +1.1.1.1,eth0 +192.168.68.64/27,eth1 +2.0.0.0/24,eth0 +2.0.0.1,eth0 diff --git a/tests/hash:net,iface.t.list2 b/tests/hash:net,iface.t.list2 new file mode 100644 index 0000000..89aa033 --- /dev/null +++ b/tests/hash:net,iface.t.list2 @@ -0,0 +1,10 @@ +Name: test +Type: hash:net,iface +Header: family inet hashsize 1024 maxelem 65536 +Size in memory: 17168 +References: 0 +Members: +10.2.0.0/24,eth0 +10.2.1.0/29,eth0 +10.2.1.12,eth0 +10.2.1.8/30,eth0 diff --git a/tests/runtest.sh b/tests/runtest.sh index a75cee2..ff5c492 100755 --- a/tests/runtest.sh +++ b/tests/runtest.sh @@ -10,6 +10,7 @@ tests="$tests ipporthash hash:ip,port hash:ip6,port" tests="$tests ipportiphash hash:ip,port,ip hash:ip6,port,ip6" tests="$tests nethash hash:net hash:net6 hash:net,port hash:net6,port" tests="$tests hash:ip,port,net hash:ip6,port,net6" +tests="$tests hash:net,iface.t" tests="$tests setlist restore" # tests="$tests iptree iptreemap" -- cgit v1.2.3