summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--include/libipset/data.h6
-rw-r--r--include/libipset/linux_ip_set.h3
-rw-r--r--include/libipset/parse.h6
-rw-r--r--include/libipset/print.h3
-rw-r--r--kernel/include/linux/netfilter/ipset/ip_set.h5
-rw-r--r--kernel/include/linux/netfilter/ipset/ip_set_ahash.h6
-rw-r--r--kernel/net/netfilter/ipset/Kbuild1
-rw-r--r--kernel/net/netfilter/ipset/ip_set_hash_netiface.c762
-rw-r--r--lib/data.c14
-rw-r--r--lib/debug.c1
-rw-r--r--lib/parse.c35
-rw-r--r--lib/print.c43
-rw-r--r--lib/session.c6
-rw-r--r--src/Makefile.am1
-rw-r--r--src/ipset.871
-rw-r--r--src/ipset.c2
-rw-r--r--src/ipset_hash_netiface.c120
-rw-r--r--tests/hash:net,iface.t57
-rw-r--r--tests/hash:net,iface.t.list010
-rw-r--r--tests/hash:net,iface.t.list210
-rwxr-xr-xtests/runtest.sh1
21 files changed, 1157 insertions, 6 deletions
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 <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.
+ */
+
+/* Kernel module implementing an IP set type: the hash:net,iface type */
+
+#include <linux/jhash.h>
+#include <linux/module.h>
+#include <linux/ip.h>
+#include <linux/skbuff.h>
+#include <linux/errno.h>
+#include <linux/random.h>
+#include <linux/rbtree.h>
+#include <net/ip.h>
+#include <net/ipv6.h>
+#include <net/netlink.h>
+
+#include <linux/netfilter.h>
+#include <linux/netfilter/ipset/pfxlen.h>
+#include <linux/netfilter/ipset/ip_set.h>
+#include <linux/netfilter/ipset/ip_set_timeout.h>
+#include <linux/netfilter/ipset/ip_set_hash.h>
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>");
+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 <linux/netfilter/ipset/ip_set_ahash.h>
+
+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 <linux/netfilter/ipset/ip_set_ahash.h>
+
+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.h> /* assert */
#include <arpa/inet.h> /* ntoh* */
#include <net/ethernet.h> /* ETH_ALEN */
+#include <net/if.h> /* IFNAMSIZ */
#include <sys/socket.h> /* AF_ */
#include <stdlib.h> /* malloc, free */
#include <string.h> /* 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 <sys/types.h> /* getaddrinfo */
#include <sys/socket.h> /* getaddrinfo, AF_ */
#include <net/ethernet.h> /* ETH_ALEN */
+#include <net/if.h> /* IFNAMSIZ */
#include <netinet/in.h> /* IPPROTO_ */
#include <libipset/debug.h> /* D() */
@@ -1395,6 +1396,40 @@ ipset_parse_typename(struct ipset_session *session,
}
/**
+ * 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
* @opt: option kind of the data
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 <sys/socket.h> /* inet_ntop */
#include <arpa/inet.h> /* inet_ntop */
#include <net/ethernet.h> /* ETH_ALEN */
+#include <net/if.h> /* IFNAMSIZ */
#include <libipset/debug.h> /* D() */
#include <libipset/data.h> /* ipset_data_* */
@@ -444,6 +445,45 @@ ipset_print_port(char *buf, unsigned int len,
}
/**
+ * 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
* @len: length of available buffer space
@@ -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 <string.h> /* str* */
#include <unistd.h> /* getpagesize */
#include <net/ethernet.h> /* ETH_ALEN */
+#include <net/if.h> /* IFNAMSIZ */
#include <libipset/debug.h> /* D() */
#include <libipset/data.h> /* 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 <libipset/data.h> /* IPSET_OPT_* */
+#include <libipset/parse.h> /* parser functions */
+#include <libipset/print.h> /* printing functions */
+#include <libipset/ui.h> /* ipset_port_usage */
+#include <libipset/types.h> /* 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"