diff --git a/.gitignore b/.gitignore index e88c536..e7a2d63 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ *.loT *.o .deps +.dirstamp .libs Makefile Makefile.in diff --git a/configure.ac b/configure.ac index eb682c3..86eeaf1 100644 --- a/configure.ac +++ b/configure.ac @@ -25,6 +25,7 @@ AC_CHECK_HEADERS([linux/netfilter/x_tables.h], [], [AC_MSG_ERROR([You need to have linux/netfilter/x_tables.h, see INSTALL file for details])]) PKG_CHECK_MODULES([libxtables], [xtables >= 1.4.3]) xtlibdir="$(pkg-config --variable=xtlibdir xtables)" +PKG_CHECK_MODULES([libmnl], [libmnl >= 1]) AC_ARG_WITH([xtlibdir], AS_HELP_STRING([--with-xtlibdir=PATH], @@ -86,5 +87,6 @@ AC_SUBST([kbuilddir]) AC_SUBST([xtlibdir]) AC_CONFIG_FILES([Makefile Makefile.iptrules Makefile.mans geoip/Makefile extensions/Makefile extensions/ACCOUNT/Makefile - extensions/ipset/Makefile extensions/pknock/Makefile]) + extensions/ipset-4/Makefile extensions/ipset-5/Makefile + extensions/pknock/Makefile]) AC_OUTPUT diff --git a/doc/changelog.txt b/doc/changelog.txt index 6b28002..cb9eda2 100644 --- a/doc/changelog.txt +++ b/doc/changelog.txt @@ -4,6 +4,9 @@ HEAD Fixes: - Update to ipset 4.5 * the iptreemap type used wrong gfp flags when deleting entries +- Include ipset 5.2 with genetlink patch (beta) + * no kernel patch needed, but requires Linux >= 2.6.35 + and thus needs to be manually enabled in mconfig v1.31 (2010-11-05) diff --git a/extensions/Kbuild b/extensions/Kbuild index e4e2490..4a5f57c 100644 --- a/extensions/Kbuild +++ b/extensions/Kbuild @@ -26,7 +26,8 @@ obj-${build_fuzzy} += xt_fuzzy.o obj-${build_geoip} += xt_geoip.o obj-${build_iface} += xt_iface.o obj-${build_ipp2p} += xt_ipp2p.o -obj-${build_ipset} += ipset/ +obj-${build_ipset4} += ipset-4/ +obj-${build_ipset5} += ipset-5/ obj-${build_ipv4options} += xt_ipv4options.o obj-${build_length2} += xt_length2.o obj-${build_lscan} += xt_lscan.o diff --git a/extensions/Mbuild b/extensions/Mbuild index 3e5557c..5a1eeba 100644 --- a/extensions/Mbuild +++ b/extensions/Mbuild @@ -18,7 +18,8 @@ obj-${build_fuzzy} += libxt_fuzzy.so obj-${build_geoip} += libxt_geoip.so obj-${build_iface} += libxt_iface.so obj-${build_ipp2p} += libxt_ipp2p.so -obj-${build_ipset} += ipset/ +obj-${build_ipset4} += ipset-4/ +obj-${build_ipset5} += ipset-5/ obj-${build_ipv4options} += libxt_ipv4options.so obj-${build_length2} += libxt_length2.so obj-${build_lscan} += libxt_lscan.so diff --git a/extensions/ipset/.gitignore b/extensions/ipset-4/.gitignore similarity index 100% rename from extensions/ipset/.gitignore rename to extensions/ipset-4/.gitignore diff --git a/extensions/ipset/Kbuild b/extensions/ipset-4/Kbuild similarity index 100% rename from extensions/ipset/Kbuild rename to extensions/ipset-4/Kbuild diff --git a/extensions/ipset/Makefile.am b/extensions/ipset-4/Makefile.am similarity index 100% rename from extensions/ipset/Makefile.am rename to extensions/ipset-4/Makefile.am diff --git a/extensions/ipset/Mbuild b/extensions/ipset-4/Mbuild similarity index 100% rename from extensions/ipset/Mbuild rename to extensions/ipset-4/Mbuild diff --git a/extensions/ipset/ip_set.c b/extensions/ipset-4/ip_set.c similarity index 100% rename from extensions/ipset/ip_set.c rename to extensions/ipset-4/ip_set.c diff --git a/extensions/ipset/ip_set.h b/extensions/ipset-4/ip_set.h similarity index 100% rename from extensions/ipset/ip_set.h rename to extensions/ipset-4/ip_set.h diff --git a/extensions/ipset/ip_set_bitmaps.h b/extensions/ipset-4/ip_set_bitmaps.h similarity index 100% rename from extensions/ipset/ip_set_bitmaps.h rename to extensions/ipset-4/ip_set_bitmaps.h diff --git a/extensions/ipset/ip_set_compat.h b/extensions/ipset-4/ip_set_compat.h similarity index 100% rename from extensions/ipset/ip_set_compat.h rename to extensions/ipset-4/ip_set_compat.h diff --git a/extensions/ipset/ip_set_getport.h b/extensions/ipset-4/ip_set_getport.h similarity index 100% rename from extensions/ipset/ip_set_getport.h rename to extensions/ipset-4/ip_set_getport.h diff --git a/extensions/ipset/ip_set_hashes.h b/extensions/ipset-4/ip_set_hashes.h similarity index 100% rename from extensions/ipset/ip_set_hashes.h rename to extensions/ipset-4/ip_set_hashes.h diff --git a/extensions/ipset/ip_set_iphash.c b/extensions/ipset-4/ip_set_iphash.c similarity index 100% rename from extensions/ipset/ip_set_iphash.c rename to extensions/ipset-4/ip_set_iphash.c diff --git a/extensions/ipset/ip_set_iphash.h b/extensions/ipset-4/ip_set_iphash.h similarity index 100% rename from extensions/ipset/ip_set_iphash.h rename to extensions/ipset-4/ip_set_iphash.h diff --git a/extensions/ipset/ip_set_ipmap.c b/extensions/ipset-4/ip_set_ipmap.c similarity index 100% rename from extensions/ipset/ip_set_ipmap.c rename to extensions/ipset-4/ip_set_ipmap.c diff --git a/extensions/ipset/ip_set_ipmap.h b/extensions/ipset-4/ip_set_ipmap.h similarity index 100% rename from extensions/ipset/ip_set_ipmap.h rename to extensions/ipset-4/ip_set_ipmap.h diff --git a/extensions/ipset/ip_set_ipporthash.c b/extensions/ipset-4/ip_set_ipporthash.c similarity index 100% rename from extensions/ipset/ip_set_ipporthash.c rename to extensions/ipset-4/ip_set_ipporthash.c diff --git a/extensions/ipset/ip_set_ipporthash.h b/extensions/ipset-4/ip_set_ipporthash.h similarity index 100% rename from extensions/ipset/ip_set_ipporthash.h rename to extensions/ipset-4/ip_set_ipporthash.h diff --git a/extensions/ipset/ip_set_ipportiphash.c b/extensions/ipset-4/ip_set_ipportiphash.c similarity index 100% rename from extensions/ipset/ip_set_ipportiphash.c rename to extensions/ipset-4/ip_set_ipportiphash.c diff --git a/extensions/ipset/ip_set_ipportiphash.h b/extensions/ipset-4/ip_set_ipportiphash.h similarity index 100% rename from extensions/ipset/ip_set_ipportiphash.h rename to extensions/ipset-4/ip_set_ipportiphash.h diff --git a/extensions/ipset/ip_set_ipportnethash.c b/extensions/ipset-4/ip_set_ipportnethash.c similarity index 100% rename from extensions/ipset/ip_set_ipportnethash.c rename to extensions/ipset-4/ip_set_ipportnethash.c diff --git a/extensions/ipset/ip_set_ipportnethash.h b/extensions/ipset-4/ip_set_ipportnethash.h similarity index 100% rename from extensions/ipset/ip_set_ipportnethash.h rename to extensions/ipset-4/ip_set_ipportnethash.h diff --git a/extensions/ipset/ip_set_iptree.c b/extensions/ipset-4/ip_set_iptree.c similarity index 100% rename from extensions/ipset/ip_set_iptree.c rename to extensions/ipset-4/ip_set_iptree.c diff --git a/extensions/ipset/ip_set_iptree.h b/extensions/ipset-4/ip_set_iptree.h similarity index 100% rename from extensions/ipset/ip_set_iptree.h rename to extensions/ipset-4/ip_set_iptree.h diff --git a/extensions/ipset/ip_set_iptreemap.c b/extensions/ipset-4/ip_set_iptreemap.c similarity index 100% rename from extensions/ipset/ip_set_iptreemap.c rename to extensions/ipset-4/ip_set_iptreemap.c diff --git a/extensions/ipset/ip_set_iptreemap.h b/extensions/ipset-4/ip_set_iptreemap.h similarity index 100% rename from extensions/ipset/ip_set_iptreemap.h rename to extensions/ipset-4/ip_set_iptreemap.h diff --git a/extensions/ipset/ip_set_jhash.h b/extensions/ipset-4/ip_set_jhash.h similarity index 100% rename from extensions/ipset/ip_set_jhash.h rename to extensions/ipset-4/ip_set_jhash.h diff --git a/extensions/ipset/ip_set_macipmap.c b/extensions/ipset-4/ip_set_macipmap.c similarity index 100% rename from extensions/ipset/ip_set_macipmap.c rename to extensions/ipset-4/ip_set_macipmap.c diff --git a/extensions/ipset/ip_set_macipmap.h b/extensions/ipset-4/ip_set_macipmap.h similarity index 100% rename from extensions/ipset/ip_set_macipmap.h rename to extensions/ipset-4/ip_set_macipmap.h diff --git a/extensions/ipset/ip_set_malloc.h b/extensions/ipset-4/ip_set_malloc.h similarity index 100% rename from extensions/ipset/ip_set_malloc.h rename to extensions/ipset-4/ip_set_malloc.h diff --git a/extensions/ipset/ip_set_nethash.c b/extensions/ipset-4/ip_set_nethash.c similarity index 100% rename from extensions/ipset/ip_set_nethash.c rename to extensions/ipset-4/ip_set_nethash.c diff --git a/extensions/ipset/ip_set_nethash.h b/extensions/ipset-4/ip_set_nethash.h similarity index 100% rename from extensions/ipset/ip_set_nethash.h rename to extensions/ipset-4/ip_set_nethash.h diff --git a/extensions/ipset/ip_set_portmap.c b/extensions/ipset-4/ip_set_portmap.c similarity index 100% rename from extensions/ipset/ip_set_portmap.c rename to extensions/ipset-4/ip_set_portmap.c diff --git a/extensions/ipset/ip_set_portmap.h b/extensions/ipset-4/ip_set_portmap.h similarity index 100% rename from extensions/ipset/ip_set_portmap.h rename to extensions/ipset-4/ip_set_portmap.h diff --git a/extensions/ipset/ip_set_setlist.c b/extensions/ipset-4/ip_set_setlist.c similarity index 100% rename from extensions/ipset/ip_set_setlist.c rename to extensions/ipset-4/ip_set_setlist.c diff --git a/extensions/ipset/ip_set_setlist.h b/extensions/ipset-4/ip_set_setlist.h similarity index 100% rename from extensions/ipset/ip_set_setlist.h rename to extensions/ipset-4/ip_set_setlist.h diff --git a/extensions/ipset/ipset.8 b/extensions/ipset-4/ipset.8 similarity index 100% rename from extensions/ipset/ipset.8 rename to extensions/ipset-4/ipset.8 diff --git a/extensions/ipset/ipset.c b/extensions/ipset-4/ipset.c similarity index 100% rename from extensions/ipset/ipset.c rename to extensions/ipset-4/ipset.c diff --git a/extensions/ipset/ipset.h b/extensions/ipset-4/ipset.h similarity index 100% rename from extensions/ipset/ipset.h rename to extensions/ipset-4/ipset.h diff --git a/extensions/ipset/ipset_iphash.c b/extensions/ipset-4/ipset_iphash.c similarity index 100% rename from extensions/ipset/ipset_iphash.c rename to extensions/ipset-4/ipset_iphash.c diff --git a/extensions/ipset/ipset_ipmap.c b/extensions/ipset-4/ipset_ipmap.c similarity index 100% rename from extensions/ipset/ipset_ipmap.c rename to extensions/ipset-4/ipset_ipmap.c diff --git a/extensions/ipset/ipset_ipporthash.c b/extensions/ipset-4/ipset_ipporthash.c similarity index 100% rename from extensions/ipset/ipset_ipporthash.c rename to extensions/ipset-4/ipset_ipporthash.c diff --git a/extensions/ipset/ipset_ipportiphash.c b/extensions/ipset-4/ipset_ipportiphash.c similarity index 100% rename from extensions/ipset/ipset_ipportiphash.c rename to extensions/ipset-4/ipset_ipportiphash.c diff --git a/extensions/ipset/ipset_ipportnethash.c b/extensions/ipset-4/ipset_ipportnethash.c similarity index 100% rename from extensions/ipset/ipset_ipportnethash.c rename to extensions/ipset-4/ipset_ipportnethash.c diff --git a/extensions/ipset/ipset_iptree.c b/extensions/ipset-4/ipset_iptree.c similarity index 100% rename from extensions/ipset/ipset_iptree.c rename to extensions/ipset-4/ipset_iptree.c diff --git a/extensions/ipset/ipset_iptreemap.c b/extensions/ipset-4/ipset_iptreemap.c similarity index 100% rename from extensions/ipset/ipset_iptreemap.c rename to extensions/ipset-4/ipset_iptreemap.c diff --git a/extensions/ipset/ipset_macipmap.c b/extensions/ipset-4/ipset_macipmap.c similarity index 100% rename from extensions/ipset/ipset_macipmap.c rename to extensions/ipset-4/ipset_macipmap.c diff --git a/extensions/ipset/ipset_nethash.c b/extensions/ipset-4/ipset_nethash.c similarity index 100% rename from extensions/ipset/ipset_nethash.c rename to extensions/ipset-4/ipset_nethash.c diff --git a/extensions/ipset/ipset_portmap.c b/extensions/ipset-4/ipset_portmap.c similarity index 100% rename from extensions/ipset/ipset_portmap.c rename to extensions/ipset-4/ipset_portmap.c diff --git a/extensions/ipset/ipset_setlist.c b/extensions/ipset-4/ipset_setlist.c similarity index 100% rename from extensions/ipset/ipset_setlist.c rename to extensions/ipset-4/ipset_setlist.c diff --git a/extensions/ipset/ipt_SET.c b/extensions/ipset-4/ipt_SET.c similarity index 100% rename from extensions/ipset/ipt_SET.c rename to extensions/ipset-4/ipt_SET.c diff --git a/extensions/ipset/ipt_set.c b/extensions/ipset-4/ipt_set.c similarity index 100% rename from extensions/ipset/ipt_set.c rename to extensions/ipset-4/ipt_set.c diff --git a/extensions/ipset/ipt_set.h b/extensions/ipset-4/ipt_set.h similarity index 100% rename from extensions/ipset/ipt_set.h rename to extensions/ipset-4/ipt_set.h diff --git a/extensions/ipset-5/.gitignore b/extensions/ipset-5/.gitignore new file mode 100644 index 0000000..6166aba --- /dev/null +++ b/extensions/ipset-5/.gitignore @@ -0,0 +1 @@ +/ipset diff --git a/extensions/ipset-5/Kbuild b/extensions/ipset-5/Kbuild new file mode 100644 index 0000000..86397ca --- /dev/null +++ b/extensions/ipset-5/Kbuild @@ -0,0 +1,7 @@ +# -*- Makefile -*- + +obj-m += xt_set.o +obj-m += ip_set.o ip_set_bitmap_ip.o ip_set_bitmap_ipmac.o +obj-m += ip_set_bitmap_port.o ip_set_hash_ip.o ip_set_hash_ipport.o +obj-m += ip_set_hash_ipportip.o ip_set_hash_ipportnet.o ip_set_hash_net.o +obj-m += ip_set_hash_netport.o ip_set_list_set.o diff --git a/extensions/ipset-5/Makefile.am b/extensions/ipset-5/Makefile.am new file mode 100644 index 0000000..9f17de3 --- /dev/null +++ b/extensions/ipset-5/Makefile.am @@ -0,0 +1,23 @@ +# -*- Makefile -*- + +AM_CFLAGS = ${regular_CFLAGS} ${libmnl_CFLAGS} -Iinclude + +include ../../Makefile.extra + +lib_LTLIBRARIES = libipset.la +libipset_la_SOURCES = libipset/data.c libipset/icmp.c libipset/icmpv6.c \ + libipset/mnl.c libipset/parse.c libipset/print.c \ + libipset/session.c libipset/types.c +libipset_la_LIBADD = ${libmnl_LIBS} +libipset_la_LDFLAGS = -version-info 1:0:0 + +sbin_PROGRAMS = ipset +ipset_SOURCES = src/ipset.c src/errcode.c src/ui.c src/ipset_bitmap_ip.c \ + src/ipset_bitmap_ipmac.c src/ipset_bitmap_port.c \ + src/ipset_hash_ip.c src/ipset_hash_ipport.c \ + src/ipset_hash_ipportip.c src/ipset_hash_ipportnet.c \ + src/ipset_hash_net.c src/ipset_hash_netport.c \ + src/ipset_list_set.c +ipset_LDADD = libipset.la + +man_MANS = ipset.8 diff --git a/extensions/ipset-5/Mbuild b/extensions/ipset-5/Mbuild new file mode 100644 index 0000000..8a69de2 --- /dev/null +++ b/extensions/ipset-5/Mbuild @@ -0,0 +1,2 @@ +# -*- Makefile -*- + diff --git a/extensions/ipset-5/include/libipset/data.h b/extensions/ipset-5/include/libipset/data.h new file mode 100644 index 0000000..4710963 --- /dev/null +++ b/extensions/ipset-5/include/libipset/data.h @@ -0,0 +1,133 @@ +/* Copyright 2007-2010 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. + */ +#ifndef LIBIPSET_DATA_H +#define LIBIPSET_DATA_H + +#include /* bool */ +#include /* union nf_inet_addr */ + +/* Data options */ +enum ipset_opt { + IPSET_OPT_NONE = 0, + /* Common ones */ + IPSET_SETNAME, + IPSET_OPT_TYPENAME, + IPSET_OPT_FAMILY, + /* CADT options */ + IPSET_OPT_IP, + IPSET_OPT_IP_FROM = IPSET_OPT_IP, + IPSET_OPT_IP_TO, + IPSET_OPT_CIDR, + IPSET_OPT_PORT, + IPSET_OPT_PORT_FROM = IPSET_OPT_PORT, + IPSET_OPT_PORT_TO, + IPSET_OPT_TIMEOUT, + /* Create-specific options */ + IPSET_OPT_GC, + IPSET_OPT_HASHSIZE, + IPSET_OPT_MAXELEM, + IPSET_OPT_NETMASK, + IPSET_OPT_PROBES, + IPSET_OPT_RESIZE, + IPSET_OPT_SIZE, + /* Create-specific options, filled out by the kernel */ + IPSET_OPT_ELEMENTS, + IPSET_OPT_REFERENCES, + IPSET_OPT_MEMSIZE, + /* ADT-specific options */ + IPSET_OPT_ETHER, + IPSET_OPT_NAME, + IPSET_OPT_NAMEREF, + IPSET_OPT_IP2, + IPSET_OPT_CIDR2, + IPSET_OPT_PROTO, + /* Swap/rename to */ + IPSET_OPT_SETNAME2, + /* Flags */ + IPSET_OPT_EXIST, + IPSET_OPT_BEFORE, + /* Internal options */ + IPSET_OPT_FLAGS = 48, /* IPSET_FLAG_EXIST| */ + IPSET_OPT_CADT_FLAGS, /* IPSET_FLAG_BEFORE| */ + IPSET_OPT_ELEM, + IPSET_OPT_TYPE, + IPSET_OPT_LINENO, + IPSET_OPT_REVISION, + IPSET_OPT_REVISION_MIN, + IPSET_OPT_MAX, +}; + +#define IPSET_FLAG(opt) (1LL << (opt)) +#define IPSET_FLAGS_ALL (~0LL) + +#define IPSET_CREATE_FLAGS \ + ( IPSET_FLAG(IPSET_OPT_FAMILY) \ + | IPSET_FLAG(IPSET_OPT_TYPENAME)\ + | IPSET_FLAG(IPSET_OPT_TYPE) \ + | IPSET_FLAG(IPSET_OPT_IP) \ + | IPSET_FLAG(IPSET_OPT_IP_TO) \ + | IPSET_FLAG(IPSET_OPT_CIDR) \ + | IPSET_FLAG(IPSET_OPT_PORT) \ + | IPSET_FLAG(IPSET_OPT_PORT_TO) \ + | IPSET_FLAG(IPSET_OPT_TIMEOUT) \ + | IPSET_FLAG(IPSET_OPT_GC) \ + | IPSET_FLAG(IPSET_OPT_HASHSIZE)\ + | IPSET_FLAG(IPSET_OPT_MAXELEM) \ + | IPSET_FLAG(IPSET_OPT_NETMASK) \ + | IPSET_FLAG(IPSET_OPT_PROBES) \ + | IPSET_FLAG(IPSET_OPT_RESIZE) \ + | IPSET_FLAG(IPSET_OPT_SIZE)) + +#define IPSET_ADT_FLAGS \ + ( IPSET_FLAG(IPSET_OPT_IP) \ + | IPSET_FLAG(IPSET_OPT_IP_TO) \ + | IPSET_FLAG(IPSET_OPT_CIDR) \ + | IPSET_FLAG(IPSET_OPT_PORT) \ + | IPSET_FLAG(IPSET_OPT_PORT_TO) \ + | IPSET_FLAG(IPSET_OPT_TIMEOUT) \ + | IPSET_FLAG(IPSET_OPT_ETHER) \ + | IPSET_FLAG(IPSET_OPT_NAME) \ + | IPSET_FLAG(IPSET_OPT_NAMEREF) \ + | IPSET_FLAG(IPSET_OPT_IP2) \ + | IPSET_FLAG(IPSET_OPT_CIDR2) \ + | IPSET_FLAG(IPSET_OPT_PROTO) \ + | IPSET_FLAG(IPSET_OPT_CADT_FLAGS)\ + | IPSET_FLAG(IPSET_OPT_BEFORE)) + +struct ipset_data; + +extern void ipset_strlcpy(char *dst, const char *src, size_t len); +extern bool ipset_data_flags_test(const struct ipset_data *data, + uint64_t flags); +extern void ipset_data_flags_set(struct ipset_data *data, uint64_t flags); +extern void ipset_data_flags_unset(struct ipset_data *data, uint64_t flags); +extern bool ipset_data_ignored(struct ipset_data *data, enum ipset_opt opt); + +extern int ipset_data_set(struct ipset_data *data, enum ipset_opt opt, + const void *value); +extern const void * ipset_data_get(const struct ipset_data *data, + enum ipset_opt opt); + +static inline bool +ipset_data_test(const struct ipset_data *data, enum ipset_opt opt) +{ + return ipset_data_flags_test(data, IPSET_FLAG(opt)); +} + +/* Shortcuts */ +extern const char * ipset_data_setname(const struct ipset_data *data); +extern uint8_t ipset_data_family(const struct ipset_data *data); +extern uint8_t ipset_data_cidr(const struct ipset_data *data); +extern uint64_t ipset_data_flags(const struct ipset_data *data); + +extern void ipset_data_reset(struct ipset_data *data); +extern struct ipset_data * ipset_data_init(void); +extern void ipset_data_fini(struct ipset_data *data); + +extern size_t ipset_data_sizeof(enum ipset_opt opt, uint8_t family); + +#endif /* LIBIPSET_DATA_H */ diff --git a/extensions/ipset-5/include/libipset/debug.h b/extensions/ipset-5/include/libipset/debug.h new file mode 100644 index 0000000..b0f4dfd --- /dev/null +++ b/extensions/ipset-5/include/libipset/debug.h @@ -0,0 +1,33 @@ +/* Copyright 2007-2010 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. + */ +#ifndef LIBIPSET_DEBUG_H +#define LIBIPSET_DEBUG_H + +#ifdef IPSET_DEBUG +#include +#include +#include +#define D(fmt, args...) \ + fprintf(stderr, "%s: %s: " fmt "\n", __FILE__, __FUNCTION__ , ## args) +#define IF_D(test, fmt, args...) \ + if (test) \ + D(fmt , ## args) + +static inline void +dump_nla(struct nlattr *nla[], int maxlen) +{ + int i; + for (i = 0; i < maxlen; i++) + D("nla[%u] does%s exist", i, nla[i] ? "" : " NOT"); +} +#else +#define D(fmt, args...) +#define IF_D(test, fmt, args...) +#define dump_nla(nla, maxlen) +#endif + +#endif /* LIBIPSET_DEBUG_H */ diff --git a/extensions/ipset-5/include/libipset/errcode.h b/extensions/ipset-5/include/libipset/errcode.h new file mode 100644 index 0000000..ed56eb5 --- /dev/null +++ b/extensions/ipset-5/include/libipset/errcode.h @@ -0,0 +1,24 @@ +/* Copyright 2007-2008 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. + */ +#ifndef LIBIPSET_ERRCODE_H +#define LIBIPSET_ERRCODE_H + +#include /* enum ipset_cmd */ + +struct ipset_session; + +/* Kernel error code to message table */ +struct ipset_errcode_table { + int errcode; /* error code returned by the kernel */ + enum ipset_cmd cmd; /* issued command */ + const char *message; /* error message the code translated to */ +}; + +extern int ipset_errcode(struct ipset_session *session, enum ipset_cmd cmd, + int errcode); + +#endif /* LIBIPSET_ERRCODE_H */ diff --git a/extensions/ipset-5/include/libipset/icmp.h b/extensions/ipset-5/include/libipset/icmp.h new file mode 100644 index 0000000..89604cd --- /dev/null +++ b/extensions/ipset-5/include/libipset/icmp.h @@ -0,0 +1,16 @@ +/* Copyright 2007-2010 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. + */ +#ifndef LIBIPSET_ICMP_H +#define LIBIPSET_ICMP_H + +#include /* uintxx_t */ + +extern const char * id_to_icmp(uint8_t id); +extern const char * icmp_to_name(uint8_t type, uint8_t code); +extern int name_to_icmp(const char *str, uint16_t *typecode); + +#endif /* LIBIPSET_ICMP_H */ diff --git a/extensions/ipset-5/include/libipset/icmpv6.h b/extensions/ipset-5/include/libipset/icmpv6.h new file mode 100644 index 0000000..b23c822 --- /dev/null +++ b/extensions/ipset-5/include/libipset/icmpv6.h @@ -0,0 +1,16 @@ +/* Copyright 2007-2010 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. + */ +#ifndef LIBIPSET_ICMPV6_H +#define LIBIPSET_ICMPV6_H + +#include /* uintxx_t */ + +extern const char * id_to_icmpv6(uint8_t id); +extern const char * icmpv6_to_name(uint8_t type, uint8_t code); +extern int name_to_icmpv6(const char *str, uint16_t *typecode); + +#endif /* LIBIPSET_ICMPV6_H */ diff --git a/extensions/ipset-5/include/libipset/linux_ip_set.h b/extensions/ipset-5/include/libipset/linux_ip_set.h new file mode 100644 index 0000000..1fb7ea2 --- /dev/null +++ b/extensions/ipset-5/include/libipset/linux_ip_set.h @@ -0,0 +1,163 @@ +#ifndef _IP_SET_H +#define _IP_SET_H + +/* Copyright (C) 2000-2002 Joakim Axelsson + * Patrick Schaaf + * Martin Josefsson + * 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. + */ + +/* The protocol version */ +#define IPSET_PROTOCOL 5 + +/* The max length of strings including NUL: set and type identifiers */ +#define IPSET_MAXNAMELEN 32 + +/* Message types and commands */ +enum ipset_cmd { + IPSET_CMD_NONE, + IPSET_CMD_PROTOCOL, /* 1: Return protocol version */ + IPSET_CMD_CREATE, /* 2: Create a new (empty) set */ + IPSET_CMD_DESTROY, /* 3: Destroy a (empty) set */ + IPSET_CMD_FLUSH, /* 4: Remove all elements from a set */ + IPSET_CMD_RENAME, /* 5: Rename a set */ + IPSET_CMD_SWAP, /* 6: Swap two sets */ + IPSET_CMD_LIST, /* 7: List sets */ + IPSET_CMD_SAVE, /* 8: Save sets */ + IPSET_CMD_ADD, /* 9: Add an element to a set */ + IPSET_CMD_DEL, /* 10: Delete an element from a set */ + IPSET_CMD_TEST, /* 11: Test an element in a set */ + IPSET_CMD_HEADER, /* 12: Get set header data only */ + IPSET_CMD_TYPE, /* 13: Get set type */ + IPSET_MSG_MAX, /* Netlink message commands */ + + /* Commands in userspace: */ + IPSET_CMD_RESTORE = IPSET_MSG_MAX, /* 14: Enter restore mode */ + IPSET_CMD_HELP, /* 15: Get help */ + IPSET_CMD_VERSION, /* 16: Get program version */ + IPSET_CMD_QUIT, /* 17: Quit from interactive mode */ + + IPSET_CMD_MAX, + + IPSET_CMD_COMMIT = IPSET_CMD_MAX, /* 18: Commit buffered commands */ +}; + +/* Attributes at command level */ +enum { + IPSET_ATTR_UNSPEC, + IPSET_ATTR_PROTOCOL, /* 1: Protocol version */ + IPSET_ATTR_SETNAME, /* 2: Name of the set */ + IPSET_ATTR_TYPENAME, /* 3: Typename */ + IPSET_ATTR_SETNAME2 = IPSET_ATTR_TYPENAME, /* Setname at rename/swap */ + IPSET_ATTR_REVISION, /* 4: Settype revision */ + IPSET_ATTR_FAMILY, /* 5: Settype family */ + IPSET_ATTR_FLAGS, /* 6: Flags at command level */ + IPSET_ATTR_DATA, /* 7: Nested attributes */ + IPSET_ATTR_ADT, /* 8: Multiple data containers */ + IPSET_ATTR_LINENO, /* 9: Restore lineno */ + IPSET_ATTR_PROTOCOL_MIN, /* 10: Minimal supported version number */ + IPSET_ATTR_REVISION_MIN = IPSET_ATTR_PROTOCOL_MIN, /* type rev min */ + __IPSET_ATTR_CMD_MAX, +}; +#define IPSET_ATTR_CMD_MAX (__IPSET_ATTR_CMD_MAX - 1) + +/* CADT specific attributes */ +enum { + IPSET_ATTR_IP = IPSET_ATTR_UNSPEC + 1, + IPSET_ATTR_IP_FROM = IPSET_ATTR_IP, + IPSET_ATTR_IP_TO, /* 2 */ + IPSET_ATTR_CIDR, /* 3 */ + IPSET_ATTR_PORT, /* 4 */ + IPSET_ATTR_PORT_FROM = IPSET_ATTR_PORT, + IPSET_ATTR_PORT_TO, /* 5 */ + IPSET_ATTR_TIMEOUT, /* 6 */ + IPSET_ATTR_PROTO, /* 7 */ + IPSET_ATTR_CADT_FLAGS, /* 8 */ + IPSET_ATTR_CADT_LINENO = IPSET_ATTR_LINENO, /* 9 */ + /* Reserve empty slots */ + IPSET_ATTR_CADT_MAX = 16, + /* Create-only specific attributes */ + IPSET_ATTR_GC, + IPSET_ATTR_HASHSIZE, + IPSET_ATTR_MAXELEM, + IPSET_ATTR_NETMASK, + IPSET_ATTR_PROBES, + IPSET_ATTR_RESIZE, + IPSET_ATTR_SIZE, + /* Kernel-only */ + IPSET_ATTR_ELEMENTS, + IPSET_ATTR_REFERENCES, + IPSET_ATTR_MEMSIZE, + + __IPSET_ATTR_CREATE_MAX, +}; +#define IPSET_ATTR_CREATE_MAX (__IPSET_ATTR_CREATE_MAX - 1) + +/* ADT specific attributes */ +enum { + IPSET_ATTR_ETHER = IPSET_ATTR_CADT_MAX + 1, + IPSET_ATTR_NAME, + IPSET_ATTR_NAMEREF, + IPSET_ATTR_IP2, + IPSET_ATTR_CIDR2, + __IPSET_ATTR_ADT_MAX, +}; +#define IPSET_ATTR_ADT_MAX (__IPSET_ATTR_ADT_MAX - 1) + +/* IP specific attributes */ +enum { + IPSET_ATTR_IPADDR_IPV4 = IPSET_ATTR_UNSPEC + 1, + IPSET_ATTR_IPADDR_IPV6, + __IPSET_ATTR_IPADDR_MAX, +}; +#define IPSET_ATTR_IPADDR_MAX (__IPSET_ATTR_IPADDR_MAX - 1) + +/* Error codes */ +enum ipset_errno { + IPSET_ERR_PRIVATE = 128, + IPSET_ERR_PROTOCOL, + IPSET_ERR_FIND_TYPE, + IPSET_ERR_MAX_SETS, + IPSET_ERR_BUSY, + IPSET_ERR_EXIST_SETNAME2, + IPSET_ERR_TYPE_MISMATCH, + IPSET_ERR_EXIST, + IPSET_ERR_INVALID_CIDR, + IPSET_ERR_INVALID_NETMASK, + IPSET_ERR_INVALID_FAMILY, + IPSET_ERR_TIMEOUT, + IPSET_ERR_REFERENCED, + IPSET_ERR_IPADDR_IPV4, + IPSET_ERR_IPADDR_IPV6, + + /* Type specific error codes */ + IPSET_ERR_TYPE_SPECIFIC = 160, +}; + +/* Flags at command level */ +enum ipset_cmd_flags { + IPSET_FLAG_BIT_EXIST = 0, + IPSET_FLAG_EXIST = (1 << IPSET_FLAG_BIT_EXIST), +}; + +/* Flags at CADT attribute level */ +enum ipset_cadt_flags { + IPSET_FLAG_BIT_BEFORE = 0, + IPSET_FLAG_BEFORE = (1 << IPSET_FLAG_BIT_BEFORE), +}; + +/* Commands with settype-specific attributes */ +enum ipset_adt { + IPSET_ADD, + IPSET_DEL, + IPSET_TEST, + IPSET_ADT_MAX, + IPSET_CREATE = IPSET_ADT_MAX, + IPSET_CADT_MAX, +}; + +#endif /* __IP_SET_H */ diff --git a/extensions/ipset-5/include/libipset/linux_ip_set_bitmap.h b/extensions/ipset-5/include/libipset/linux_ip_set_bitmap.h new file mode 100644 index 0000000..95fb963 --- /dev/null +++ b/extensions/ipset-5/include/libipset/linux_ip_set_bitmap.h @@ -0,0 +1,12 @@ +#ifndef __IP_SET_BITMAP_H +#define __IP_SET_BITMAP_H + +/* Bitmap type specific error codes */ +enum { + /* The element is out of the range of the set */ + IPSET_ERR_BITMAP_RANGE = IPSET_ERR_TYPE_SPECIFIC, + /* The range exceeds the size limit of the set type */ + IPSET_ERR_BITMAP_RANGE_SIZE, +}; + +#endif /* __IP_SET_BITMAP_H */ diff --git a/extensions/ipset-5/include/libipset/linux_ip_set_hash.h b/extensions/ipset-5/include/libipset/linux_ip_set_hash.h new file mode 100644 index 0000000..7c6336a --- /dev/null +++ b/extensions/ipset-5/include/libipset/linux_ip_set_hash.h @@ -0,0 +1,16 @@ +#ifndef __IP_SET_HASH_H +#define __IP_SET_HASH_H + +/* Hash type specific error codes */ +enum { + /* Hash is full */ + IPSET_ERR_HASH_FULL = IPSET_ERR_TYPE_SPECIFIC, + /* Null-valued element */ + IPSET_ERR_HASH_ELEM, + /* Invalid protocol */ + IPSET_ERR_INVALID_PROTO, + /* Protocol missing but must be specified */ + IPSET_ERR_MISSING_PROTO, +}; + +#endif /* __IP_SET_HASH_H */ diff --git a/extensions/ipset-5/include/libipset/linux_ip_set_list.h b/extensions/ipset-5/include/libipset/linux_ip_set_list.h new file mode 100644 index 0000000..2395aa2 --- /dev/null +++ b/extensions/ipset-5/include/libipset/linux_ip_set_list.h @@ -0,0 +1,20 @@ +#ifndef __IP_SET_LIST_H +#define __IP_SET_LIST_H + +/* List type specific error codes */ +enum { + /* Set name to be added/deleted/tested does not exist. */ + IPSET_ERR_NAME = IPSET_ERR_TYPE_SPECIFIC, + /* list:set type is not permitted to add */ + IPSET_ERR_LOOP, + /* Missing reference set */ + IPSET_ERR_BEFORE, + /* Reference set does not exist */ + IPSET_ERR_NAMEREF, + /* Set is full */ + IPSET_ERR_LIST_FULL, + /* Reference set is not added to the set */ + IPSET_ERR_REF_EXIST, +}; + +#endif /* __IP_SET_LIST_H */ diff --git a/extensions/ipset-5/include/libipset/mnl.h b/extensions/ipset-5/include/libipset/mnl.h new file mode 100644 index 0000000..c2b6d4c --- /dev/null +++ b/extensions/ipset-5/include/libipset/mnl.h @@ -0,0 +1,29 @@ +/* Copyright 2007-2010 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. + */ +#ifndef LIBIPSET_MNL_H +#define LIBIPSET_MNL_H + +#include /* uintxx_t */ +#include /* libmnl backend */ + +#include /* struct ipset_transport */ + +#ifndef NFNETLINK_V0 +#define NFNETLINK_V0 0 + +struct nfgenmsg { + uint8_t nfgen_family; + uint8_t version; + uint16_t res_id; +}; +#endif + +extern int ipset_get_nlmsg_type(const struct nlmsghdr *nlh); + +extern const struct ipset_transport ipset_mnl_transport; + +#endif /* LIBIPSET_MNL_H */ diff --git a/extensions/ipset-5/include/libipset/nf_inet_addr.h b/extensions/ipset-5/include/libipset/nf_inet_addr.h new file mode 100644 index 0000000..0e0701e --- /dev/null +++ b/extensions/ipset-5/include/libipset/nf_inet_addr.h @@ -0,0 +1,22 @@ +/* Copyright 2007-2010 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. + */ +#ifndef LIBIPSET_NF_INET_ADDR_H +#define LIBIPSET_NF_INET_ADDR_H + +#include /* uint32_t */ +#include /* struct in[6]_addr */ + +/* The structure to hold IP addresses, same as in linux/netfilter.h */ +union nf_inet_addr { + uint32_t all[4]; + uint32_t ip; + uint32_t ip6[4]; + struct in_addr in; + struct in6_addr in6; +}; + +#endif /* LIBIPSET_NF_INET_ADDR_H */ diff --git a/extensions/ipset-5/include/libipset/parse.h b/extensions/ipset-5/include/libipset/parse.h new file mode 100644 index 0000000..e87a60d --- /dev/null +++ b/extensions/ipset-5/include/libipset/parse.h @@ -0,0 +1,96 @@ +/* Copyright 2007-2010 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. + */ +#ifndef LIBIPSET_PARSE_H +#define LIBIPSET_PARSE_H + +#include /* enum ipset_opt */ + +/* For parsing/printing data */ +#define IPSET_CIDR_SEPARATOR "/" +#define IPSET_RANGE_SEPARATOR "-" +#define IPSET_ELEM_SEPARATOR "," +#define IPSET_NAME_SEPARATOR "," +#define IPSET_PROTO_SEPARATOR ":" + +struct ipset_session; + +typedef int (*ipset_parsefn)(struct ipset_session *s, + enum ipset_opt opt, const char *str); + +extern int ipset_parse_ether(struct ipset_session *session, + enum ipset_opt opt, const char *str); +extern int ipset_parse_port(struct ipset_session *session, + enum ipset_opt opt, const char *str, + const char *proto); +extern int ipset_parse_tcpudp_port(struct ipset_session *session, + enum ipset_opt opt, const char *str, + const char *proto); +extern int ipset_parse_tcp_port(struct ipset_session *session, + enum ipset_opt opt, const char *str); +extern int ipset_parse_single_tcp_port(struct ipset_session *session, + enum ipset_opt opt, const char *str); +extern int ipset_parse_proto(struct ipset_session *session, + enum ipset_opt opt, const char *str); +extern int ipset_parse_icmp(struct ipset_session *session, + enum ipset_opt opt, const char *str); +extern int ipset_parse_icmpv6(struct ipset_session *session, + enum ipset_opt opt, const char *str); +extern int ipset_parse_proto_port(struct ipset_session *session, + enum ipset_opt opt, const char *str); +extern int ipset_parse_family(struct ipset_session *session, + enum ipset_opt opt, const char *str); +extern int ipset_parse_ip(struct ipset_session *session, + enum ipset_opt opt, const char *str); +extern int ipset_parse_single_ip(struct ipset_session *session, + enum ipset_opt opt, const char *str); +extern int ipset_parse_net(struct ipset_session *session, + enum ipset_opt opt, const char *str); +extern int ipset_parse_range(struct ipset_session *session, + enum ipset_opt opt, const char *str); +extern int ipset_parse_netrange(struct ipset_session *session, + enum ipset_opt opt, const char *str); +extern int ipset_parse_iprange(struct ipset_session *session, + enum ipset_opt opt, const char *str); +extern int ipset_parse_ipnet(struct ipset_session *session, + enum ipset_opt opt, const char *str); +extern int ipset_parse_ip4_single6(struct ipset_session *session, + enum ipset_opt opt, const char *str); +extern int ipset_parse_name(struct ipset_session *session, + enum ipset_opt opt, const char *str); +extern int ipset_parse_before(struct ipset_session *session, + enum ipset_opt opt, const char *str); +extern int ipset_parse_after(struct ipset_session *session, + enum ipset_opt opt, const char *str); +extern int ipset_parse_setname(struct ipset_session *session, + enum ipset_opt opt, const char *str); +extern int ipset_parse_uint32(struct ipset_session *session, + enum ipset_opt opt, const char *str); +extern int ipset_parse_uint8(struct ipset_session *session, + enum ipset_opt opt, const char *str); +extern int ipset_parse_netmask(struct ipset_session *session, + enum ipset_opt opt, const char *str); +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_output(struct ipset_session *session, + int opt, const char *str); +extern int ipset_parse_ignored(struct ipset_session *session, + enum ipset_opt opt, const char *str); +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, + ipset_parsefn parse, const char *optstr, + enum ipset_opt optional, const char *str); + +/* Compatibility parser functions */ +extern int ipset_parse_iptimeout(struct ipset_session *session, + enum ipset_opt opt, const char *str); +extern int ipset_parse_name_compat(struct ipset_session *session, + enum ipset_opt opt, const char *str); + +#endif /* LIBIPSET_PARSE_H */ diff --git a/extensions/ipset-5/include/libipset/pfxlen.h b/extensions/ipset-5/include/libipset/pfxlen.h new file mode 100644 index 0000000..ba94dd9 --- /dev/null +++ b/extensions/ipset-5/include/libipset/pfxlen.h @@ -0,0 +1,157 @@ +#ifndef _NET_PFXLEN_H +#define _NET_PFXLEN_H 1 + +#include +#ifdef HAVE_PFXLEN_H +#include +#else + +#include /* union nf_inet_addr */ + +#define E(a, b, c, d) \ + {.ip6 = { \ + __constant_htonl(a), __constant_htonl(b), \ + __constant_htonl(c), __constant_htonl(d), \ + }} + +/* + * This table works for both IPv4 and IPv6; + * just use prefixlen_netmask_map[prefixlength].ip. + */ +const union nf_inet_addr prefixlen_netmask_map[] = { + E(0x00000000, 0x00000000, 0x00000000, 0x00000000), + E(0x80000000, 0x00000000, 0x00000000, 0x00000000), + E(0xC0000000, 0x00000000, 0x00000000, 0x00000000), + E(0xE0000000, 0x00000000, 0x00000000, 0x00000000), + E(0xF0000000, 0x00000000, 0x00000000, 0x00000000), + E(0xF8000000, 0x00000000, 0x00000000, 0x00000000), + E(0xFC000000, 0x00000000, 0x00000000, 0x00000000), + E(0xFE000000, 0x00000000, 0x00000000, 0x00000000), + E(0xFF000000, 0x00000000, 0x00000000, 0x00000000), + E(0xFF800000, 0x00000000, 0x00000000, 0x00000000), + E(0xFFC00000, 0x00000000, 0x00000000, 0x00000000), + E(0xFFE00000, 0x00000000, 0x00000000, 0x00000000), + E(0xFFF00000, 0x00000000, 0x00000000, 0x00000000), + E(0xFFF80000, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFC0000, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFE0000, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFF0000, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFF8000, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFFC000, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFFE000, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFFF000, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFFF800, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFFFC00, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFFFE00, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFFFF00, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFFFF80, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFFFFC0, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFFFFE0, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFFFFF0, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFFFFF8, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFFFFFC, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFFFFFE, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0x80000000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xC0000000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xE0000000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xF0000000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xF8000000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFC000000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFE000000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFF000000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFF800000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFC00000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFE00000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFF00000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFF80000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFC0000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFE0000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFF0000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFF8000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFC000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFE000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFF000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFF800, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFC00, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFE00, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFF00, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFF80, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFC0, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFE0, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFF0, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFF8, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFC, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFE, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0x80000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xC0000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xE0000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xF0000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xF8000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFC000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFE000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFF800000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFC00000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFE00000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFF00000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFF80000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFC0000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFE0000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFF0000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFF8000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFC000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFE000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFF000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFF800, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFC00, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFE00, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFF00, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFF80, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFC0, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFE0, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFF0, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFF8, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFC, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFE, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x80000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xC0000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xE0000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xF0000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xF8000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFC000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFE000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF800000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFC00000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFE00000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFF00000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFF80000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFC0000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFE0000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFF0000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFF8000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFC000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFE000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFF000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFF800), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFC00), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFE00), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFF00), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFF80), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFC0), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFE0), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFF0), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFF8), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFC), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFE), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF), +}; +#endif /* !HAVE_PFXLEN_H */ + +#define PFXLEN(n) prefixlen_netmask_map[n].ip +#define PFXLEN6(n) prefixlen_netmask_map[n].ip6 + +#endif diff --git a/extensions/ipset-5/include/libipset/print.h b/extensions/ipset-5/include/libipset/print.h new file mode 100644 index 0000000..963b42e --- /dev/null +++ b/extensions/ipset-5/include/libipset/print.h @@ -0,0 +1,65 @@ +/* Copyright 2007-2010 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. + */ +#ifndef LIBIPSET_PRINT_H +#define LIBIPSET_PRINT_H + +#include /* enum ipset_opt */ + +typedef int (*ipset_printfn)(char *buf, unsigned int len, + const struct ipset_data *data, + enum ipset_opt opt, uint8_t env); + +extern int ipset_print_ether(char *buf, unsigned int len, + const struct ipset_data *data, + enum ipset_opt opt, uint8_t env); +extern int ipset_print_family(char *buf, unsigned int len, + const struct ipset_data *data, + enum ipset_opt opt, uint8_t env); +extern int ipset_print_type(char *buf, unsigned int len, + const struct ipset_data *data, + enum ipset_opt opt, uint8_t env); +extern int ipset_print_ip(char *buf, unsigned int len, + const struct ipset_data *data, + enum ipset_opt opt, uint8_t env); +extern int ipset_print_ipaddr(char *buf, unsigned int len, + const struct ipset_data *data, + enum ipset_opt opt, uint8_t env); +extern int ipset_print_number(char *buf, unsigned int len, + const struct ipset_data *data, + enum ipset_opt opt, uint8_t env); +extern int ipset_print_name(char *buf, unsigned int len, + const struct ipset_data *data, + enum ipset_opt opt, uint8_t env); +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_proto(char *buf, unsigned int len, + const struct ipset_data *data, + enum ipset_opt opt, uint8_t env); +extern int ipset_print_icmp(char *buf, unsigned int len, + const struct ipset_data *data, + enum ipset_opt opt, uint8_t env); +extern int ipset_print_icmpv6(char *buf, unsigned int len, + const struct ipset_data *data, + enum ipset_opt opt, uint8_t env); +extern int ipset_print_proto_port(char *buf, unsigned int len, + const struct ipset_data *data, + enum ipset_opt opt, uint8_t env); +extern int ipset_print_flag(char *buf, unsigned int len, + const struct ipset_data *data, + enum ipset_opt opt, uint8_t env); +extern int ipset_print_elem(char *buf, unsigned int len, + const struct ipset_data *data, + enum ipset_opt opt, uint8_t env); + +#define ipset_print_portnum ipset_print_number + +extern int ipset_print_data(char *buf, unsigned int len, + const struct ipset_data *data, + enum ipset_opt opt, uint8_t env); + +#endif /* LIBIPSET_PRINT_H */ diff --git a/extensions/ipset-5/include/libipset/session.h b/extensions/ipset-5/include/libipset/session.h new file mode 100644 index 0000000..02e8b36 --- /dev/null +++ b/extensions/ipset-5/include/libipset/session.h @@ -0,0 +1,94 @@ +/* Copyright 2007-2010 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. + */ +#ifndef LIBIPSET_SESSION_H +#define LIBIPSET_SESSION_H + +#include /* bool */ +#include /* uintxx_t */ +#include /* printf */ + +#include /* enum ipset_cmd */ + +/* Report and output buffer sizes */ +#define IPSET_ERRORBUFLEN 1024 +#define IPSET_OUTBUFLEN 8192 + +struct ipset_session; +struct ipset_data; +struct ipset_handle; + +extern struct ipset_data * ipset_session_data(const struct ipset_session *session); +extern struct ipset_handle * ipset_session_handle(const struct ipset_session *session); +extern const struct ipset_type * ipset_saved_type(const struct ipset_session *session); + +enum ipset_err_type { + IPSET_ERROR, + IPSET_WARNING, +}; + +extern int ipset_session_report(struct ipset_session *session, + enum ipset_err_type type, + const char *fmt, ...); + +#define ipset_err(session, fmt, args...) \ + ipset_session_report(session, IPSET_ERROR, fmt , ## args) + +#define ipset_warn(session, fmt, args...) \ + ipset_session_report(session, IPSET_WARNING, fmt , ## args) + +#define ipset_errptr(session, fmt, args...) ({ \ + ipset_session_report(session, IPSET_ERROR, fmt , ## args); \ + NULL; \ +}) + +extern void ipset_session_report_reset(struct ipset_session *session); +extern const char * ipset_session_error(const struct ipset_session *session); +extern const char * ipset_session_warning(const struct ipset_session *session); + +#define ipset_session_data_set(session, opt, value) \ + ipset_data_set(ipset_session_data(session), opt, value) +#define ipset_session_data_get(session, opt) \ + ipset_data_get(ipset_session_data(session), opt) + +/* Environment option flags */ +enum ipset_envopt { + IPSET_ENV_BIT_SORTED = 0, + IPSET_ENV_SORTED = (1 << IPSET_ENV_BIT_SORTED), + IPSET_ENV_BIT_QUIET = 1, + IPSET_ENV_QUIET = (1 << IPSET_ENV_BIT_QUIET), + IPSET_ENV_BIT_RESOLVE = 2, + IPSET_ENV_RESOLVE = (1 << IPSET_ENV_BIT_RESOLVE), + IPSET_ENV_BIT_EXIST = 3, + IPSET_ENV_EXIST = (1 << IPSET_ENV_BIT_EXIST), +}; + +extern int ipset_envopt_parse(struct ipset_session *session, + int env, const char *str); +extern bool ipset_envopt_test(struct ipset_session *session, + enum ipset_envopt env); + +enum ipset_output_mode { + IPSET_LIST_NONE, + IPSET_LIST_PLAIN, + IPSET_LIST_SAVE, + IPSET_LIST_XML, +}; + +extern int ipset_session_output(struct ipset_session *session, + enum ipset_output_mode mode); + +extern int ipset_commit(struct ipset_session *session); +extern int ipset_cmd(struct ipset_session *session, enum ipset_cmd cmd, + uint32_t lineno); + +typedef int (*ipset_outfn)(const char *fmt, ...) + __attribute__ ((format (printf, 1, 2))); + +extern struct ipset_session * ipset_session_init(ipset_outfn outfn); +extern int ipset_session_fini(struct ipset_session *session); + +#endif /* LIBIPSET_SESSION_H */ diff --git a/extensions/ipset-5/include/libipset/transport.h b/extensions/ipset-5/include/libipset/transport.h new file mode 100644 index 0000000..b22e073 --- /dev/null +++ b/extensions/ipset-5/include/libipset/transport.h @@ -0,0 +1,27 @@ +/* Copyright 2007-2010 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. + */ +#ifndef LIBIPSET_TRANSPORT_H +#define LIBIPSET_TRANSPORT_H + +#include /* uintxx_t */ +#include /* struct nlmsghdr */ + +#include /* mnl_cb_t */ + +#include /* enum ipset_cmd */ + +struct ipset_handle; + +struct ipset_transport { + struct ipset_handle * (*init)(mnl_cb_t *cb_ctl, void *data); + int (*fini)(struct ipset_handle *handle); + void (*fill_hdr)(struct ipset_handle *handle, enum ipset_cmd cmd, + void *buffer, size_t len, uint8_t envflags); + int (*query)(struct ipset_handle *handle, void *buffer, size_t len); +}; + +#endif /* LIBIPSET_TRANSPORT_H */ diff --git a/extensions/ipset-5/include/libipset/types.h b/extensions/ipset-5/include/libipset/types.h new file mode 100644 index 0000000..f9c8f2d --- /dev/null +++ b/extensions/ipset-5/include/libipset/types.h @@ -0,0 +1,110 @@ +/* Copyright 2007-2010 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. + */ +#ifndef LIBIPSET_TYPES_H +#define LIBIPSET_TYPES_H + +#include /* NULL */ +#include /* uintxx_t */ + +#include /* enum ipset_opt */ +#include /* ipset_parsefn */ +#include /* ipset_printfn */ +#include /* IPSET_MAXNAMELEN */ + +#define AF_INET46 255 + +/* Family rules: + * - AF_UNSPEC: type is family-neutral + * - AF_INET: type supports IPv4 only + * - AF_INET6: type supports IPv6 only + * - AF_INET46: type supports both IPv4 and IPv6 + */ + +/* Set dimensions */ +enum { + IPSET_DIM_ONE, /* foo */ + IPSET_DIM_TWO, /* foo,bar */ + IPSET_DIM_THREE, /* foo,bar,fie */ + IPSET_DIM_MAX, +}; + +/* Parser options */ +enum { + IPSET_NO_ARG = -1, + IPSET_OPTIONAL_ARG, + IPSET_MANDATORY_ARG, + IPSET_MANDATORY_ARG2, +}; + +struct ipset_session; + +/* Parse and print type-specific arguments */ +struct ipset_arg { + const char *name[2]; /* option names */ + int has_arg; /* mandatory/optional/no arg */ + enum ipset_opt opt; /* argumentum type */ + ipset_parsefn parse; /* parser function */ + ipset_printfn print; /* printing function */ +}; + +/* Type check against the kernel */ +enum { + IPSET_KERNEL_MISMATCH = -1, + IPSET_KERNEL_CHECK_NEEDED, + IPSET_KERNEL_OK, +}; + +/* How element parts are parsed */ +struct ipset_elem { + ipset_parsefn parse; /* elem parser function */ + ipset_printfn print; /* elem print function */ + enum ipset_opt opt; /* elem option */ +}; + +/* The set types in userspace + * we could collapse 'args' and 'mandatory' to two-element lists + * but for the readability the full list is supported. + */ +struct ipset_type { + char name[IPSET_MAXNAMELEN]; /* type name */ + uint8_t revision; /* revision number */ + uint8_t family; /* supported family */ + uint8_t dimension; /* elem dimension */ + int8_t kernel_check; /* kernel check */ + bool last_elem_optional; /* last element optional */ + struct ipset_elem elem[IPSET_DIM_MAX]; /* parse elem */ + ipset_parsefn compat_parse_elem; /* compatibility parser */ + const struct ipset_arg *args[IPSET_CADT_MAX]; /* create/ADT args besides elem */ + uint64_t mandatory[IPSET_CADT_MAX]; /* create/ADT mandatory flags */ + uint64_t full[IPSET_CADT_MAX]; /* full args flags */ + const char *usage; /* terse usage */ + void (*usagefn)(void); /* additional usage */ + + struct ipset_type *next; + const char *alias[]; /* name alias(es) */ +}; + +extern int ipset_cache_add(const char *name, const struct ipset_type *type, + uint8_t family); +extern int ipset_cache_del(const char *name); +extern int ipset_cache_rename(const char *from, const char *to); +extern int ipset_cache_swap(const char *from, const char *to); + +extern int ipset_cache_init(void); +extern void ipset_cache_fini(void); + +extern const struct ipset_type * ipset_type_get(struct ipset_session *session, + enum ipset_cmd cmd); +extern const struct ipset_type * ipset_type_check(struct ipset_session *session); + +extern int ipset_type_add(struct ipset_type *type); +extern const struct ipset_type * ipset_types(void); +extern const char * ipset_typename_resolve(const char *str); +extern bool ipset_match_typename(const char *str, + const struct ipset_type *t); + +#endif /* LIBIPSET_TYPES_H */ diff --git a/extensions/ipset-5/include/libipset/ui.h b/extensions/ipset-5/include/libipset/ui.h new file mode 100644 index 0000000..b05b737 --- /dev/null +++ b/extensions/ipset-5/include/libipset/ui.h @@ -0,0 +1,44 @@ +/* Copyright 2007-2010 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. + */ +#ifndef LIBIPSET_UI_H +#define LIBIPSET_UI_H + +#include /* enum ipset_cmd */ + +/* Commands in userspace */ +struct ipset_commands { + enum ipset_cmd cmd; + int has_arg; + const char *name[2]; + const char *help; +}; + +extern const struct ipset_commands ipset_commands[]; + +struct ipset_session; +struct ipset_data; + +/* Environment options */ +struct ipset_envopts { + int flag; + int has_arg; + const char *name[2]; + const char *help; + int (*parse)(struct ipset_session *s, int flag, const char *str); + int (*print)(char *buf, unsigned int len, + const struct ipset_data *data, int flag, uint8_t env); +}; + +extern const struct ipset_envopts ipset_envopts[]; + +extern bool ipset_match_cmd(const char *arg, const char * const name[]); +extern bool ipset_match_option(const char *arg, const char * const name[]); +extern bool ipset_match_envopt(const char *arg, const char * const name[]); +extern void ipset_shift_argv(int *argc, char *argv[], int from); +extern void ipset_port_usage(void); + +#endif /* LIBIPSET_UI_H */ diff --git a/extensions/ipset-5/include/libipset/utils.h b/extensions/ipset-5/include/libipset/utils.h new file mode 100644 index 0000000..c22687f --- /dev/null +++ b/extensions/ipset-5/include/libipset/utils.h @@ -0,0 +1,45 @@ +/* Copyright 2007-2010 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. + */ +#ifndef LIBIPSET_UTILS_H +#define LIBIPSET_UTILS_H + +#include /* strcmp */ +#include /* struct in[6]_addr */ + +/* String equality tests */ +#define STREQ(a,b) (strcmp(a,b) == 0) +#define STRNEQ(a,b,n) (strncmp(a,b,n) == 0) +#define STRCASEQ(a,b) (strcasecmp(a,b) == 0) +#define STRNCASEQ(a,b,n) (strncasecmp(a,b,n) == 0) + +/* Stringify tokens */ +#define _STR(c) #c +#define STR(c) _STR(c) + +/* Min/max */ +#define MIN(a, b) (a < b ? a : b) +#define MAX(a, b) (a > b ? a : b) + +#define UNUSED __attribute__ ((unused)) + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(x) (sizeof(x) / sizeof(*(x))) +#endif + +static inline void +in4cpy(struct in_addr *dest, const struct in_addr *src) +{ + dest->s_addr = src->s_addr; +} + +static inline void +in6cpy(struct in6_addr *dest, const struct in6_addr *src) +{ + memcpy(dest, src, sizeof(struct in6_addr)); +} + +#endif /* LIBIPSET_UTILS_H */ diff --git a/extensions/ipset-5/ip_set.c b/extensions/ipset-5/ip_set.c new file mode 100644 index 0000000..f152156 --- /dev/null +++ b/extensions/ipset-5/ip_set.c @@ -0,0 +1,1863 @@ +/* Copyright (C) 2000-2002 Joakim Axelsson + * Patrick Schaaf + * 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 for IP set management */ + +#include "ip_set_kernel.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "ip_set.h" +#include +#define GENLMSG_DEFAULT_SIZE (NLMSG_DEFAULT_SIZE - GENL_HDRLEN) + +static struct list_head ip_set_type_list; /* all registered set types */ +static DEFINE_MUTEX(ip_set_type_mutex); /* protects ip_set_type_list */ + +static struct ip_set **ip_set_list; /* all individual sets */ +static ip_set_id_t ip_set_max = 256; /* max number of sets */ + +#define STREQ(a, b) (strncmp(a, b, IPSET_MAXNAMELEN) == 0) + +static int max_sets; + +module_param(max_sets, int, 0600); +MODULE_PARM_DESC(max_sets, "maximal number of sets"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Jozsef Kadlecsik "); +MODULE_DESCRIPTION("core IP set support"); + +/* + * Prefixlen maps for fast conversions + */ + +#define E(a, b, c, d) \ + {.ip6 = { \ + __constant_htonl(a), __constant_htonl(b), \ + __constant_htonl(c), __constant_htonl(d), \ + } } + +/* + * This table works for both IPv4 and IPv6; + * just use prefixlen_netmask_map[prefixlength].ip. + */ +const union nf_inet_addr prefixlen_netmask_map[] = { + E(0x00000000, 0x00000000, 0x00000000, 0x00000000), + E(0x80000000, 0x00000000, 0x00000000, 0x00000000), + E(0xC0000000, 0x00000000, 0x00000000, 0x00000000), + E(0xE0000000, 0x00000000, 0x00000000, 0x00000000), + E(0xF0000000, 0x00000000, 0x00000000, 0x00000000), + E(0xF8000000, 0x00000000, 0x00000000, 0x00000000), + E(0xFC000000, 0x00000000, 0x00000000, 0x00000000), + E(0xFE000000, 0x00000000, 0x00000000, 0x00000000), + E(0xFF000000, 0x00000000, 0x00000000, 0x00000000), + E(0xFF800000, 0x00000000, 0x00000000, 0x00000000), + E(0xFFC00000, 0x00000000, 0x00000000, 0x00000000), + E(0xFFE00000, 0x00000000, 0x00000000, 0x00000000), + E(0xFFF00000, 0x00000000, 0x00000000, 0x00000000), + E(0xFFF80000, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFC0000, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFE0000, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFF0000, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFF8000, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFFC000, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFFE000, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFFF000, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFFF800, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFFFC00, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFFFE00, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFFFF00, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFFFF80, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFFFFC0, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFFFFE0, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFFFFF0, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFFFFF8, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFFFFFC, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFFFFFE, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0x80000000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xC0000000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xE0000000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xF0000000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xF8000000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFC000000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFE000000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFF000000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFF800000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFC00000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFE00000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFF00000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFF80000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFC0000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFE0000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFF0000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFF8000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFC000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFE000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFF000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFF800, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFC00, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFE00, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFF00, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFF80, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFC0, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFE0, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFF0, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFF8, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFC, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFE, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0x80000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xC0000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xE0000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xF0000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xF8000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFC000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFE000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFF800000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFC00000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFE00000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFF00000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFF80000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFC0000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFE0000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFF0000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFF8000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFC000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFE000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFF000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFF800, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFC00, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFE00, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFF00, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFF80, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFC0, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFE0, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFF0, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFF8, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFC, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFE, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x80000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xC0000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xE0000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xF0000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xF8000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFC000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFE000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF800000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFC00000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFE00000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFF00000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFF80000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFC0000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFE0000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFF0000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFF8000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFC000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFE000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFF000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFF800), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFC00), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFE00), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFF00), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFF80), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFC0), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFE0), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFF0), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFF8), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFC), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFE), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF), +}; +EXPORT_SYMBOL_GPL(prefixlen_netmask_map); + +#undef E +#define E(a, b, c, d) \ + {.ip6 = { a, b, c, d } } + +/* + * This table works for both IPv4 and IPv6; + * just use prefixlen_hostmask_map[prefixlength].ip. + */ +const union nf_inet_addr prefixlen_hostmask_map[] = { + E(0x00000000, 0x00000000, 0x00000000, 0x00000000), + E(0x80000000, 0x00000000, 0x00000000, 0x00000000), + E(0xC0000000, 0x00000000, 0x00000000, 0x00000000), + E(0xE0000000, 0x00000000, 0x00000000, 0x00000000), + E(0xF0000000, 0x00000000, 0x00000000, 0x00000000), + E(0xF8000000, 0x00000000, 0x00000000, 0x00000000), + E(0xFC000000, 0x00000000, 0x00000000, 0x00000000), + E(0xFE000000, 0x00000000, 0x00000000, 0x00000000), + E(0xFF000000, 0x00000000, 0x00000000, 0x00000000), + E(0xFF800000, 0x00000000, 0x00000000, 0x00000000), + E(0xFFC00000, 0x00000000, 0x00000000, 0x00000000), + E(0xFFE00000, 0x00000000, 0x00000000, 0x00000000), + E(0xFFF00000, 0x00000000, 0x00000000, 0x00000000), + E(0xFFF80000, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFC0000, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFE0000, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFF0000, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFF8000, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFFC000, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFFE000, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFFF000, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFFF800, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFFFC00, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFFFE00, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFFFF00, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFFFF80, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFFFFC0, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFFFFE0, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFFFFF0, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFFFFF8, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFFFFFC, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFFFFFE, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0x00000000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0x80000000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xC0000000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xE0000000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xF0000000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xF8000000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFC000000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFE000000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFF000000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFF800000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFC00000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFE00000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFF00000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFF80000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFC0000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFE0000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFF0000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFF8000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFC000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFE000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFF000, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFF800, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFC00, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFE00, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFF00, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFF80, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFC0, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFE0, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFF0, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFF8, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFC, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFE, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0x80000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xC0000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xE0000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xF0000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xF8000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFC000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFE000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFF800000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFC00000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFE00000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFF00000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFF80000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFC0000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFE0000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFF0000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFF8000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFC000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFE000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFF000, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFF800, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFC00, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFE00, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFF00, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFF80, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFC0, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFE0, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFF0, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFF8, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFC, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFE, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x80000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xC0000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xE0000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xF0000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xF8000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFC000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFE000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF000000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFF800000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFC00000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFE00000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFF00000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFF80000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFC0000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFE0000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFF0000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFF8000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFC000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFE000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFF000), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFF800), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFC00), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFE00), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFF00), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFF80), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFC0), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFE0), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFF0), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFF8), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFC), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFE), + E(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF), +}; +EXPORT_SYMBOL_GPL(prefixlen_hostmask_map); + +static struct genl_family ip_set_netlink_subsys; + +/* + * The set types are implemented in modules and registered set types + * can be found in ip_set_type_list. Adding/deleting types is + * serialized by ip_set_type_mutex. + */ + +static inline void +ip_set_type_lock(void) +{ + mutex_lock(&ip_set_type_mutex); +} + +static inline void +ip_set_type_unlock(void) +{ + mutex_unlock(&ip_set_type_mutex); +} + +/* Register and deregister settype */ + +static inline struct ip_set_type * +find_set_type(const char *name, u8 family, u8 revision) +{ + struct ip_set_type *type; + + list_for_each_entry_rcu(type, &ip_set_type_list, list) + if (STREQ(type->name, name) + && (type->family == family || type->family == AF_UNSPEC) + && type->revision == revision) + return type; + return NULL; +} + +/* Find a set type so that rcu_read_lock() is called by the function. + * If we succeeded, the RCU lock is NOT released and the caller + * must release it later. + */ +static struct ip_set_type * +find_set_type_rcu(const char *name, u8 family, u8 revision) +{ + struct ip_set_type *type; + + rcu_read_lock(); + type = find_set_type(name, family, revision); + if (type == NULL) + rcu_read_unlock(); + + return type; +} + +/* Find a given set type by name and family. + * If we succeeded, the supported minimal and maximum revisions are + * filled out. + */ +static bool +find_set_type_minmax(const char *name, u8 family, + u8 *min, u8 *max) +{ + struct ip_set_type *type; + bool ret = false; + + *min = *max = 0; + rcu_read_lock(); + list_for_each_entry_rcu(type, &ip_set_type_list, list) + if (STREQ(type->name, name) + && (type->family == family || type->family == AF_UNSPEC)) { + ret = true; + if (type->revision < *min) + *min = type->revision; + else if (type->revision > *max) + *max = type->revision; + } + rcu_read_unlock(); + + return ret; +} + +#define family_name(f) ((f) == AF_INET ? "inet" : \ + (f) == AF_INET6 ? "inet6" : "any") + +/* Register a set type structure. The type is identified by + * the unique triple of name, family and revision. + */ +int +ip_set_type_register(struct ip_set_type *type) +{ + int ret = 0; + + if (type->protocol != IPSET_PROTOCOL) { + pr_warning("ip_set type %s, family %s, revision %u uses " + "wrong protocol version %u (want %u)\n", + type->name, family_name(type->family), + type->revision, type->protocol, IPSET_PROTOCOL); + return -EINVAL; + } + + ip_set_type_lock(); + if (find_set_type(type->name, type->family, type->revision)) { + /* Duplicate! */ + pr_warning("ip_set type %s, family %s, revision %u " + "already registered!\n", type->name, + family_name(type->family), type->revision); + ret = -EINVAL; + goto unlock; + } + list_add_rcu(&type->list, &ip_set_type_list); + pr_debug("type %s, family %s, revision %u registered.", + type->name, family_name(type->family), type->revision); +unlock: + ip_set_type_unlock(); + return ret; +} +EXPORT_SYMBOL_GPL(ip_set_type_register); + +/* Unregister a set type. There's a small race with ip_set_create */ +void +ip_set_type_unregister(struct ip_set_type *type) +{ + ip_set_type_lock(); + if (!find_set_type(type->name, type->family, type->revision)) { + pr_warning("ip_set type %s, family %s, revision %u " + "not registered\n", type->name, + family_name(type->family), type->revision); + goto unlock; + } + list_del_rcu(&type->list); + pr_debug("type %s, family %s, revision %u unregistered.", + type->name, family_name(type->family), type->revision); +unlock: + ip_set_type_unlock(); + + synchronize_rcu(); +} +EXPORT_SYMBOL_GPL(ip_set_type_unregister); + +/* + * Creating/destroying/renaming/swapping affect the existence and + * the properties of a set. All of these can be executed from userspace + * only and serialized by the nfnl mutex indirectly from nfnetlink. + * + * Sets are identified by their index in ip_set_list and the index + * is used by the external references (set/SET netfilter modules). + * + * The set behind an index may change by swapping only, from userspace. + */ + +static inline void +__ip_set_get(ip_set_id_t index) +{ + atomic_inc(&ip_set_list[index]->ref); +} + +static inline void +__ip_set_put(ip_set_id_t index) +{ + atomic_dec(&ip_set_list[index]->ref); +} + +/* + * Add, del and test set entries from kernel. + * + * The set behind the index must exist and must be referenced + * so it can't be destroyed (or changed) under our foot. + */ + +int +ip_set_test(ip_set_id_t index, const struct sk_buff *skb, + u8 family, u8 dim, u8 flags) +{ + struct ip_set *set = ip_set_list[index]; + int ret = 0; + + BUG_ON(set == NULL || atomic_read(&set->ref) == 0); + pr_debug("set %s, index %u", set->name, index); + + if (dim < set->type->dimension + || !(family == set->family || set->family == AF_UNSPEC)) + return 0; + + read_lock_bh(&set->lock); + ret = set->variant->kadt(set, skb, IPSET_TEST, family, dim, flags); + read_unlock_bh(&set->lock); + + if (ret == -EAGAIN) { + /* Type requests element to be completed */ + pr_debug("element must be competed, ADD is triggered"); + write_lock_bh(&set->lock); + set->variant->kadt(set, skb, IPSET_ADD, family, dim, flags); + write_unlock_bh(&set->lock); + ret = 1; + } + + /* Convert error codes to nomatch */ + return (ret < 0 ? 0 : ret); +} +EXPORT_SYMBOL_GPL(ip_set_test); + +int +ip_set_add(ip_set_id_t index, const struct sk_buff *skb, + u8 family, u8 dim, u8 flags) +{ + struct ip_set *set = ip_set_list[index]; + int ret; + + BUG_ON(set == NULL || atomic_read(&set->ref) == 0); + pr_debug("set %s, index %u", set->name, index); + + if (dim < set->type->dimension + || !(family == set->family || set->family == AF_UNSPEC)) + return 0; + + write_lock_bh(&set->lock); + ret = set->variant->kadt(set, skb, IPSET_ADD, family, dim, flags); + write_unlock_bh(&set->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(ip_set_add); + +int +ip_set_del(ip_set_id_t index, const struct sk_buff *skb, + u8 family, u8 dim, u8 flags) +{ + struct ip_set *set = ip_set_list[index]; + int ret = 0; + + BUG_ON(set == NULL || atomic_read(&set->ref) == 0); + pr_debug("set %s, index %u", set->name, index); + + if (dim < set->type->dimension + || !(family == set->family || set->family == AF_UNSPEC)) + return 0; + + write_lock_bh(&set->lock); + ret = set->variant->kadt(set, skb, IPSET_DEL, family, dim, flags); + write_unlock_bh(&set->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(ip_set_del); + +/* + * Find set by name, reference it once. The reference makes sure the + * thing pointed to, does not go away under our feet. + * + * The nfnl mutex must already be activated. + */ +ip_set_id_t +ip_set_get_byname(const char *name, struct ip_set **set) +{ + ip_set_id_t i, index = IPSET_INVALID_ID; + struct ip_set *s; + + for (i = 0; i < ip_set_max; i++) { + s = ip_set_list[i]; + if (s != NULL && STREQ(s->name, name)) { + __ip_set_get(i); + index = i; + *set = s; + } + } + + return index; +} +EXPORT_SYMBOL_GPL(ip_set_get_byname); + +/* + * If the given set pointer points to a valid set, decrement + * reference count by 1. The caller shall not assume the index + * to be valid, after calling this function. + * + * The nfnl mutex must already be activated. + */ +void +ip_set_put_byindex(ip_set_id_t index) +{ + if (ip_set_list[index] != NULL) { + BUG_ON(atomic_read(&ip_set_list[index]->ref) == 0); + __ip_set_put(index); + } +} +EXPORT_SYMBOL_GPL(ip_set_put_byindex); + +/* + * Get the name of a set behind a set index. + * We assume the set is referenced, so it does exist and + * can't be destroyed. The set cannot be renamed due to + * the referencing either. + * + * The nfnl mutex must already be activated. + */ +const char * +ip_set_name_byindex(ip_set_id_t index) +{ + const struct ip_set *set = ip_set_list[index]; + + BUG_ON(set == NULL); + BUG_ON(atomic_read(&set->ref) == 0); + + /* Referenced, so it's safe */ + return set->name; +} +EXPORT_SYMBOL_GPL(ip_set_name_byindex); + +/* + * Routines to call by external subsystems, which do not + * call nfnl_lock for us. + */ + +/* + * Find set by name, reference it once. The reference makes sure the + * thing pointed to, does not go away under our feet. + * + * The nfnl mutex is used in the function. + */ +ip_set_id_t +ip_set_nfnl_get(const char *name) +{ + struct ip_set *s; + ip_set_id_t index; + + nfnl_lock(); + index = ip_set_get_byname(name, &s); + nfnl_unlock(); + + return index; +} +EXPORT_SYMBOL_GPL(ip_set_nfnl_get); + +/* + * Find set by index, reference it once. The reference makes sure the + * thing pointed to, does not go away under our feet. + * + * The nfnl mutex is used in the function. + */ +ip_set_id_t +ip_set_nfnl_get_byindex(ip_set_id_t index) +{ + if (index > ip_set_max) + return IPSET_INVALID_ID; + + nfnl_lock(); + if (ip_set_list[index]) + __ip_set_get(index); + else + index = IPSET_INVALID_ID; + nfnl_unlock(); + + return index; +} +EXPORT_SYMBOL_GPL(ip_set_nfnl_get_byindex); + +/* + * If the given set pointer points to a valid set, decrement + * reference count by 1. The caller shall not assume the index + * to be valid, after calling this function. + * + * The nfnl mutex is used in the function. + */ +void +ip_set_nfnl_put(ip_set_id_t index) +{ + nfnl_lock(); + if (ip_set_list[index] != NULL) { + BUG_ON(atomic_read(&ip_set_list[index]->ref) == 0); + __ip_set_put(index); + } + nfnl_unlock(); +} +EXPORT_SYMBOL_GPL(ip_set_nfnl_put); + +/* + * Communication protocol with userspace over netlink. + * + * We already locked by nfnl_lock. + */ + +static inline bool +protocol_failed(struct nlattr *const *tb) +{ + return !tb[IPSET_ATTR_PROTOCOL] + || nla_get_u8(tb[IPSET_ATTR_PROTOCOL]) != IPSET_PROTOCOL; +} + +static inline u32 +flag_exist(const struct genlmsghdr *ghdr) +{ + return ghdr->reserved & NLM_F_EXCL ? 0 : IPSET_FLAG_EXIST; +} + +static inline bool +flag_nested(const struct nlattr *nla) +{ + return nla->nla_type & NLA_F_NESTED; +} + +static void * +start_msg(struct sk_buff *skb, u32 pid, u32 seq, unsigned int flags, + enum ipset_cmd cmd) +{ + void *nlh; + + nlh = genlmsg_put(skb, pid, seq, &ip_set_netlink_subsys, flags, cmd); + if (nlh == NULL) + return NULL; + return nlh; +} + +/* Create a set */ + +static const struct nla_policy ip_set_create_policy[IPSET_ATTR_CMD_MAX + 1] = { + [IPSET_ATTR_PROTOCOL] = { .type = NLA_U8 }, + [IPSET_ATTR_SETNAME] = { .type = NLA_NUL_STRING, + .len = IPSET_MAXNAMELEN - 1 }, + [IPSET_ATTR_TYPENAME] = { .type = NLA_NUL_STRING, + .len = IPSET_MAXNAMELEN - 1}, + [IPSET_ATTR_REVISION] = { .type = NLA_U8 }, + [IPSET_ATTR_FAMILY] = { .type = NLA_U8 }, + [IPSET_ATTR_DATA] = { .type = NLA_NESTED }, +}; + +static ip_set_id_t +find_set_id(const char *name) +{ + ip_set_id_t i, index = IPSET_INVALID_ID; + const struct ip_set *set; + + for (i = 0; index == IPSET_INVALID_ID && i < ip_set_max; i++) { + set = ip_set_list[i]; + if (set != NULL && STREQ(set->name, name)) + index = i; + } + return index; +} + +static inline struct ip_set * +find_set(const char *name) +{ + ip_set_id_t index = find_set_id(name); + + return index == IPSET_INVALID_ID ? NULL : ip_set_list[index]; +} + +static int +find_free_id(const char *name, ip_set_id_t *index, struct ip_set **set) +{ + ip_set_id_t i; + + *index = IPSET_INVALID_ID; + for (i = 0; i < ip_set_max; i++) { + if (ip_set_list[i] == NULL) { + if (*index == IPSET_INVALID_ID) + *index = i; + } else if (STREQ(name, ip_set_list[i]->name)) { + /* Name clash */ + *set = ip_set_list[i]; + return -EEXIST; + } + } + if (*index == IPSET_INVALID_ID) + /* No free slot remained */ + return -IPSET_ERR_MAX_SETS; + return 0; +} + +static inline void +load_type_module(const char *typename) +{ + pr_debug("try to load ip_set_%s", typename); + request_module("ip_set_%s", typename); +} + +static int +ip_set_create(struct sk_buff *skb, struct genl_info *info) +{ + struct nlattr *const *attr = info->attrs; + + struct ip_set *set, *clash; + ip_set_id_t index = IPSET_INVALID_ID; + const char *name, *typename; + u8 family, revision; + u32 flags = flag_exist(info->genlhdr); + int ret = 0, len; + + if (unlikely(protocol_failed(attr) + || attr[IPSET_ATTR_SETNAME] == NULL + || attr[IPSET_ATTR_TYPENAME] == NULL + || attr[IPSET_ATTR_REVISION] == NULL + || attr[IPSET_ATTR_FAMILY] == NULL + || (attr[IPSET_ATTR_DATA] != NULL + && !flag_nested(attr[IPSET_ATTR_DATA])))) + return -IPSET_ERR_PROTOCOL; + + name = nla_data(attr[IPSET_ATTR_SETNAME]); + typename = nla_data(attr[IPSET_ATTR_TYPENAME]); + family = nla_get_u8(attr[IPSET_ATTR_FAMILY]); + revision = nla_get_u8(attr[IPSET_ATTR_REVISION]); + pr_debug("setname: %s, typename: %s, family: %s, revision: %u", + name, typename, family_name(family), revision); + + /* + * First, and without any locks, allocate and initialize + * a normal base set structure. + */ + set = kzalloc(sizeof(struct ip_set), GFP_KERNEL); + if (!set) + return -ENOMEM; + rwlock_init(&set->lock); + strncpy(set->name, name, IPSET_MAXNAMELEN); + atomic_set(&set->ref, 0); + set->family = family; + + /* + * Next, check that we know the type, and take + * a reference on the type, to make sure it stays available + * while constructing our new set. + * + * After referencing the type, we try to create the type + * specific part of the set without holding any locks. + */ + set->type = find_set_type_rcu(typename, family, revision); + if (set->type == NULL) { + /* Try loading the module */ + load_type_module(typename); + set->type = find_set_type_rcu(typename, family, revision); + if (set->type == NULL) { + pr_warning("Can't find ip_set type %s, family %s, " + "revision %u: set '%s' not created", + typename, family_name(family), revision, + name); + ret = -IPSET_ERR_FIND_TYPE; + goto out; + } + } + if (!try_module_get(set->type->me)) { + rcu_read_unlock(); + ret = -EFAULT; + goto out; + } + rcu_read_unlock(); + + /* + * Without holding any locks, create private part. + */ + len = attr[IPSET_ATTR_DATA] ? nla_len(attr[IPSET_ATTR_DATA]) : 0; + pr_debug("data len: %u", len); + ret = set->type->create(set, attr[IPSET_ATTR_DATA] ? + nla_data(attr[IPSET_ATTR_DATA]) : NULL, len, + flags); + if (ret != 0) + goto put_out; + + /* BTW, ret==0 here. */ + + /* + * Here, we have a valid, constructed set and we are protected + * by nfnl_lock. Find the first free index in ip_set_list and + * check clashing. + */ + if ((ret = find_free_id(set->name, &index, &clash)) != 0) { + /* If this is the same set and requested, ignore error */ + if (ret == -EEXIST + && (flags & IPSET_FLAG_EXIST) + && STREQ(set->type->name, clash->type->name) + && set->type->family == clash->type->family + && set->type->revision == clash->type->revision + && set->variant->same_set(set, clash)) + ret = 0; + goto cleanup; + } + + /* + * Finally! Add our shiny new set to the list, and be done. + */ + pr_debug("create: '%s' created with index %u!", set->name, index); + ip_set_list[index] = set; + + return ret; + +cleanup: + set->variant->destroy(set); +put_out: + module_put(set->type->me); +out: + kfree(set); + return ret; +} + +/* Destroy sets */ + +static const struct nla_policy +ip_set_setname_policy[IPSET_ATTR_CMD_MAX + 1] = { + [IPSET_ATTR_PROTOCOL] = { .type = NLA_U8 }, + [IPSET_ATTR_SETNAME] = { .type = NLA_NUL_STRING, + .len = IPSET_MAXNAMELEN - 1 }, +}; + +static inline void +ip_set_destroy_set(ip_set_id_t index) +{ + struct ip_set *set = ip_set_list[index]; + + pr_debug("set: %s", set->name); + ip_set_list[index] = NULL; + + /* Must call it without holding any lock */ + set->variant->destroy(set); + module_put(set->type->me); + kfree(set); +} + +static int +ip_set_destroy(struct sk_buff *skb, struct genl_info *info) +{ + struct nlattr *const *attr = info->attrs; + ip_set_id_t i; + + if (unlikely(protocol_failed(attr))) + return -IPSET_ERR_PROTOCOL; + + /* References are protected by the nfnl mutex */ + if (!attr[IPSET_ATTR_SETNAME]) { + for (i = 0; i < ip_set_max; i++) { + if (ip_set_list[i] != NULL + && (atomic_read(&ip_set_list[i]->ref))) + return -IPSET_ERR_BUSY; + } + for (i = 0; i < ip_set_max; i++) { + if (ip_set_list[i] != NULL) + ip_set_destroy_set(i); + } + } else { + i = find_set_id(nla_data(attr[IPSET_ATTR_SETNAME])); + if (i == IPSET_INVALID_ID) + return -EEXIST; + else if (atomic_read(&ip_set_list[i]->ref)) + return -IPSET_ERR_BUSY; + + ip_set_destroy_set(i); + } + return 0; +} + +/* Flush sets */ + +static inline void +ip_set_flush_set(struct ip_set *set) +{ + pr_debug("set: %s", set->name); + + write_lock_bh(&set->lock); + set->variant->flush(set); + write_unlock_bh(&set->lock); +} + +static int +ip_set_flush(struct sk_buff *skb, struct genl_info *info) +{ + struct nlattr *const *attr = info->attrs; + ip_set_id_t i; + + if (unlikely(protocol_failed(attr))) + return -EPROTO; + + if (!attr[IPSET_ATTR_SETNAME]) { + for (i = 0; i < ip_set_max; i++) + if (ip_set_list[i] != NULL) + ip_set_flush_set(ip_set_list[i]); + } else { + i = find_set_id(nla_data(attr[IPSET_ATTR_SETNAME])); + if (i == IPSET_INVALID_ID) + return -EEXIST; + + ip_set_flush_set(ip_set_list[i]); + } + + return 0; +} + +/* Rename a set */ + +static const struct nla_policy +ip_set_setname2_policy[IPSET_ATTR_CMD_MAX + 1] = { + [IPSET_ATTR_PROTOCOL] = { .type = NLA_U8 }, + [IPSET_ATTR_SETNAME] = { .type = NLA_NUL_STRING, + .len = IPSET_MAXNAMELEN - 1 }, + [IPSET_ATTR_SETNAME2] = { .type = NLA_NUL_STRING, + .len = IPSET_MAXNAMELEN - 1 }, +}; + +static int +ip_set_rename(struct sk_buff *skb, struct genl_info *info) +{ + struct nlattr *const *attr = info->attrs; + struct ip_set *set; + const char *name2; + ip_set_id_t i; + + if (unlikely(protocol_failed(attr) + || attr[IPSET_ATTR_SETNAME] == NULL + || attr[IPSET_ATTR_SETNAME2] == NULL)) + return -IPSET_ERR_PROTOCOL; + + set = find_set(nla_data(attr[IPSET_ATTR_SETNAME])); + if (set == NULL) + return -EEXIST; + if (atomic_read(&set->ref) != 0) + return -IPSET_ERR_REFERENCED; + + name2 = nla_data(attr[IPSET_ATTR_SETNAME2]); + for (i = 0; i < ip_set_max; i++) { + if (ip_set_list[i] != NULL + && STREQ(ip_set_list[i]->name, name2)) + return -IPSET_ERR_EXIST_SETNAME2; + } + strncpy(set->name, name2, IPSET_MAXNAMELEN); + + return 0; +} + +/* Swap two sets so that name/index points to the other. + * References and set names are also swapped. + * + * We are protected by the nfnl mutex and references are + * manipulated only by holding the mutex. The kernel interfaces + * do not hold the mutex but the pointer settings are atomic + * so the ip_set_list always contains valid pointers to the sets. + */ + +static int +ip_set_swap(struct sk_buff *skb, struct genl_info *info) +{ + struct nlattr *const *attr = info->attrs; + struct ip_set *from, *to; + ip_set_id_t from_id, to_id; + char from_name[IPSET_MAXNAMELEN]; + u32 from_ref; + + if (unlikely(protocol_failed(attr) + || attr[IPSET_ATTR_SETNAME] == NULL + || attr[IPSET_ATTR_SETNAME2] == NULL)) + return -IPSET_ERR_PROTOCOL; + + from_id = find_set_id(nla_data(attr[IPSET_ATTR_SETNAME])); + if (from_id == IPSET_INVALID_ID) + return -EEXIST; + + to_id = find_set_id(nla_data(attr[IPSET_ATTR_SETNAME2])); + if (to_id == IPSET_INVALID_ID) + return -IPSET_ERR_EXIST_SETNAME2; + + from = ip_set_list[from_id]; + to = ip_set_list[to_id]; + + /* Features must not change. + * Not an artifical restriction anymore, as we must prevent + * possible loops created by swapping in setlist type of sets. */ + if (!(from->type->features == to->type->features + && from->type->family == to->type->family)) + return -IPSET_ERR_TYPE_MISMATCH; + + /* No magic here: ref munging protected by the nfnl_lock */ + strncpy(from_name, from->name, IPSET_MAXNAMELEN); + from_ref = atomic_read(&from->ref); + + strncpy(from->name, to->name, IPSET_MAXNAMELEN); + atomic_set(&from->ref, atomic_read(&to->ref)); + strncpy(to->name, from_name, IPSET_MAXNAMELEN); + atomic_set(&to->ref, from_ref); + + ip_set_list[from_id] = to; + ip_set_list[to_id] = from; + + /* Avoid possible race between ongoing slow add/del in kernel space + * and next destroy command. */ + synchronize_net(); + + return 0; +} + +/* List/save set data */ + +#define DUMP_INIT 0L +#define DUMP_ALL 1L +#define DUMP_ONE 2L +#define DUMP_LAST 3L + +static int +ip_set_dump_done(struct netlink_callback *cb) +{ + if (cb->args[2]) { + pr_debug("release set %s", ip_set_list[cb->args[1]]->name); + __ip_set_put((ip_set_id_t) cb->args[1]); + } + return 0; +} + +static inline void +dump_attrs(void *phdr) +{ + const struct nlattr *attr; + const struct nlmsghdr *nlh = phdr - GENL_HDRLEN - NLMSG_HDRLEN; + int rem; + + pr_debug("nlmsg_len: %u", nlh->nlmsg_len); + pr_debug("dump nlmsg"); + nlmsg_for_each_attr(attr, nlh, sizeof(struct genlmsghdr), rem) { + pr_debug("type: %u, len %u", nla_type(attr), attr->nla_len); + } +} + +static inline int +dump_init(struct netlink_callback *cb) +{ + struct nlmsghdr *nlh = nlmsg_hdr(cb->skb); + int min_len = NLMSG_SPACE(sizeof(struct genlmsghdr)); + struct nlattr *cda[IPSET_ATTR_CMD_MAX+1]; + struct nlattr *attr = (void *)nlh + min_len; + ip_set_id_t index; + + /* Second pass, so parser can't fail */ + nla_parse(cda, IPSET_ATTR_CMD_MAX, + attr, nlh->nlmsg_len - min_len, ip_set_setname_policy); + + /* cb->args[0] : dump single set/all sets + * [1] : set index + * [..]: type specific + */ + + if (!cda[IPSET_ATTR_SETNAME]) { + cb->args[0] = DUMP_ALL; + return 0; + } + + index = find_set_id(nla_data(cda[IPSET_ATTR_SETNAME])); + if (index == IPSET_INVALID_ID) + return -EEXIST; + + cb->args[0] = DUMP_ONE; + cb->args[1] = index; + return 0; +} + +static int +ip_set_dump_start(struct sk_buff *skb, struct netlink_callback *cb) +{ + ip_set_id_t index = IPSET_INVALID_ID, max; + struct ip_set *set = NULL; + void *nlh = NULL; + unsigned int flags = NETLINK_CB(cb->skb).pid ? NLM_F_MULTI : 0; + int ret = 0; + + if (cb->args[0] == DUMP_INIT) { + ret = dump_init(cb); + if (ret < 0) { + /* We have to create and send the error message + * manually :-( */ + netlink_ack(cb->skb, nlmsg_hdr(cb->skb), ret); + return ret; + } + } + + if (cb->args[1] >= ip_set_max) + goto out; + + pr_debug("args[0]: %ld args[1]: %ld\n", cb->args[0], cb->args[1]); + max = cb->args[0] == DUMP_ONE ? cb->args[1] + 1 : ip_set_max; + for (; cb->args[1] < max; cb->args[1]++) { + index = (ip_set_id_t) cb->args[1]; + set = ip_set_list[index]; + if (set == NULL) { + if (cb->args[0] == DUMP_ONE) { + ret = -EEXIST; + goto out; + } + continue; + } + /* When dumping all sets, we must dump "sorted" + * so that lists (unions of sets) are dumped last. + */ + if (cb->args[0] != DUMP_ONE + && !((cb->args[0] == DUMP_ALL) + ^ (set->type->features & IPSET_DUMP_LAST))) + continue; + pr_debug("List set: %s", set->name); + if (!cb->args[2]) { + /* Start listing: make sure set won't be destroyed */ + pr_debug("reference set"); + __ip_set_get(index); + } + nlh = start_msg(skb, NETLINK_CB(cb->skb).pid, + cb->nlh->nlmsg_seq, flags, + IPSET_CMD_LIST); + if (!nlh) { + ret = -EFAULT; + goto release_refcount; + } + NLA_PUT_U8(skb, IPSET_ATTR_PROTOCOL, IPSET_PROTOCOL); + NLA_PUT_STRING(skb, IPSET_ATTR_SETNAME, set->name); + switch (cb->args[2]) { + case 0: + /* Core header data */ + NLA_PUT_STRING(skb, IPSET_ATTR_TYPENAME, + set->type->name); + NLA_PUT_U8(skb, IPSET_ATTR_FAMILY, + set->family); + NLA_PUT_U8(skb, IPSET_ATTR_REVISION, + set->type->revision); + ret = set->variant->head(set, skb); + if (ret < 0) + goto release_refcount; + /* Fall through and add elements */ + default: + read_lock_bh(&set->lock); + ret = set->variant->list(set, skb, cb); + read_unlock_bh(&set->lock); + if (!cb->args[2]) { + /* Set is done, proceed with next one */ + if (cb->args[0] == DUMP_ONE) + cb->args[1] = IPSET_INVALID_ID; + else + cb->args[1]++; + } + goto release_refcount; + } + } + goto out; + +nla_put_failure: + ret = -EFAULT; +release_refcount: + /* If there was an error or set is done, release set */ + if (ret || !cb->args[2]) { + pr_debug("release set %s", ip_set_list[index]->name); + __ip_set_put(index); + } + + /* If we dump all sets, continue with dumping last ones */ + if (cb->args[0] == DUMP_ALL && cb->args[1] >= max && !cb->args[2]) + cb->args[0] = DUMP_LAST; + +out: + if (nlh) { + genlmsg_end(skb, nlh); + dump_attrs(nlh); + } + + return ret < 0 ? ret : skb->len; +} + +static int +ip_set_dump(struct sk_buff *skb, struct genl_info *info) +{ + struct nlattr *const *attr = info->attrs; + struct nlmsghdr *nlh = info->nlhdr; + struct sock *ctnl = genl_info_net(info)->genl_sock; + int ret; + + if (unlikely(protocol_failed(attr))) + return -IPSET_ERR_PROTOCOL; + + genl_unlock(); + ret = netlink_dump_start(ctnl, skb, nlh, + ip_set_dump_start, + ip_set_dump_done); + genl_lock(); + return ret; +} + +/* Add, del and test */ + +static const struct nla_policy ip_set_adt_policy[IPSET_ATTR_CMD_MAX + 1] = { + [IPSET_ATTR_PROTOCOL] = { .type = NLA_U8 }, + [IPSET_ATTR_SETNAME] = { .type = NLA_NUL_STRING, + .len = IPSET_MAXNAMELEN - 1 }, + [IPSET_ATTR_LINENO] = { .type = NLA_U32 }, + [IPSET_ATTR_DATA] = { .type = NLA_NESTED }, + [IPSET_ATTR_ADT] = { .type = NLA_NESTED }, +}; + +static int +call_ad(struct sk_buff *skb, struct nlattr *const attr[], + struct ip_set *set, const struct nlattr *nla, + enum ipset_adt adt, u32 flags) +{ + struct nlattr *head = nla_data(nla); + int ret, len = nla_len(nla), retried = 0; + u32 lineno = 0; + bool eexist = flags & IPSET_FLAG_EXIST; + + do { + write_lock_bh(&set->lock); + ret = set->variant->uadt(set, head, len, adt, + &lineno, flags); + write_unlock_bh(&set->lock); + } while (ret == -EAGAIN + && set->variant->resize + && (ret = set->variant->resize(set, retried++)) == 0); + + if (!ret || (ret == -IPSET_ERR_EXIST && eexist)) + return 0; + if (lineno && attr[IPSET_ATTR_LINENO]) { + /* Error in restore/batch mode: send back lineno */ + u32 *errline = nla_data(attr[IPSET_ATTR_LINENO]); + + *errline = lineno; + } + + return ret; +} + +static int +ip_set_uadd(struct sk_buff *skb, struct genl_info *info) +{ + struct nlattr *const *attr = info->attrs; + + struct ip_set *set; + const struct nlattr *nla; + u32 flags = flag_exist(info->genlhdr); + int ret = 0; + + if (unlikely(protocol_failed(attr) + || attr[IPSET_ATTR_SETNAME] == NULL + || !((attr[IPSET_ATTR_DATA] != NULL) + ^ (attr[IPSET_ATTR_ADT] != NULL)) + || (attr[IPSET_ATTR_DATA] != NULL + && !flag_nested(attr[IPSET_ATTR_DATA])) + || (attr[IPSET_ATTR_ADT] != NULL + && (!flag_nested(attr[IPSET_ATTR_ADT]) + || attr[IPSET_ATTR_LINENO] == NULL)))) + return -IPSET_ERR_PROTOCOL; + + set = find_set(nla_data(attr[IPSET_ATTR_SETNAME])); + if (set == NULL) + return -EEXIST; + + if (attr[IPSET_ATTR_DATA]) { + ret = call_ad(skb, attr, + set, attr[IPSET_ATTR_DATA], IPSET_ADD, flags); + } else { + int nla_rem; + + nla_for_each_nested(nla, attr[IPSET_ATTR_ADT], nla_rem) { + if (nla_type(nla) != IPSET_ATTR_DATA + || !flag_nested(nla)) + return -IPSET_ERR_PROTOCOL; + ret = call_ad(skb, attr, + set, nla, IPSET_ADD, flags); + if (ret < 0) + return ret; + } + } + return ret; +} + +static int +ip_set_udel(struct sk_buff *skb, struct genl_info *info) +{ + struct nlattr *const *attr = info->attrs; + + struct ip_set *set; + const struct nlattr *nla; + u32 flags = flag_exist(info->genlhdr); + int ret = 0; + + if (unlikely(protocol_failed(attr) + || attr[IPSET_ATTR_SETNAME] == NULL + || !((attr[IPSET_ATTR_DATA] != NULL) + ^ (attr[IPSET_ATTR_ADT] != NULL)) + || (attr[IPSET_ATTR_DATA] != NULL + && !flag_nested(attr[IPSET_ATTR_DATA])) + || (attr[IPSET_ATTR_ADT] != NULL + && (!flag_nested(attr[IPSET_ATTR_ADT]) + || attr[IPSET_ATTR_LINENO] == NULL)))) + return -IPSET_ERR_PROTOCOL; + + set = find_set(nla_data(attr[IPSET_ATTR_SETNAME])); + if (set == NULL) + return -EEXIST; + + if (attr[IPSET_ATTR_DATA]) { + ret = call_ad(skb, attr, + set, attr[IPSET_ATTR_DATA], IPSET_DEL, flags); + } else { + int nla_rem; + + nla_for_each_nested(nla, attr[IPSET_ATTR_ADT], nla_rem) { + if (nla_type(nla) != IPSET_ATTR_DATA + || !flag_nested(nla)) + return -IPSET_ERR_PROTOCOL; + ret = call_ad(skb, attr, + set, nla, IPSET_DEL, flags); + if (ret < 0) + return ret; + } + } + return ret; +} + +static int +ip_set_utest(struct sk_buff *skb, struct genl_info *info) +{ + struct nlattr *const *attr = info->attrs; + + struct ip_set *set; + int ret = 0; + + if (unlikely(protocol_failed(attr) + || attr[IPSET_ATTR_SETNAME] == NULL + || attr[IPSET_ATTR_DATA] == NULL + || !flag_nested(attr[IPSET_ATTR_DATA]))) + return -IPSET_ERR_PROTOCOL; + + set = find_set(nla_data(attr[IPSET_ATTR_SETNAME])); + if (set == NULL) + return -EEXIST; + + read_lock_bh(&set->lock); + ret = set->variant->uadt(set, + nla_data(attr[IPSET_ATTR_DATA]), + nla_len(attr[IPSET_ATTR_DATA]), + IPSET_TEST, NULL, 0); + read_unlock_bh(&set->lock); + /* Userspace can't trigger element to be re-added */ + if (ret == -EAGAIN) + ret = 1; + + return ret < 0 ? ret : ret > 0 ? 0 : -IPSET_ERR_EXIST; +} + +/* Get headed data of a set */ + +static int +ip_set_header(struct sk_buff *skb, struct genl_info *info) +{ + const struct ip_set *set; + struct nlattr *const *attr = info->attrs; + const struct nlmsghdr *nlh = info->nlhdr; + struct sk_buff *skb2; + struct nlmsghdr *nlh2; + ip_set_id_t index; + int ret = 0; + + if (unlikely(protocol_failed(attr) + || attr[IPSET_ATTR_SETNAME] == NULL)) + return -IPSET_ERR_PROTOCOL; + + index = find_set_id(nla_data(attr[IPSET_ATTR_SETNAME])); + if (index == IPSET_INVALID_ID) + return -EEXIST; + set = ip_set_list[index]; + + skb2 = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); + if (skb2 == NULL) + return -ENOMEM; + + nlh2 = start_msg(skb2, NETLINK_CB(skb).pid, nlh->nlmsg_seq, 0, + IPSET_CMD_HEADER); + if (!nlh2) + goto nlmsg_failure; + NLA_PUT_U8(skb2, IPSET_ATTR_PROTOCOL, IPSET_PROTOCOL); + NLA_PUT_STRING(skb2, IPSET_ATTR_SETNAME, set->name); + NLA_PUT_STRING(skb2, IPSET_ATTR_TYPENAME, set->type->name); + NLA_PUT_U8(skb2, IPSET_ATTR_FAMILY, set->family); + NLA_PUT_U8(skb2, IPSET_ATTR_REVISION, set->type->revision); + genlmsg_end(skb2, nlh2); + + ret = genlmsg_unicast(genl_info_net(info), skb2, NETLINK_CB(skb).pid); + if (ret < 0) + return -EFAULT; + + return 0; + +nla_put_failure: + nlmsg_cancel(skb2, nlh2); +nlmsg_failure: + kfree_skb(skb2); + return -EFAULT; +} + +/* Get type data */ + +static const struct nla_policy ip_set_type_policy[IPSET_ATTR_CMD_MAX + 1] = { + [IPSET_ATTR_PROTOCOL] = { .type = NLA_U8 }, + [IPSET_ATTR_TYPENAME] = { .type = NLA_NUL_STRING, + .len = IPSET_MAXNAMELEN - 1 }, + [IPSET_ATTR_FAMILY] = { .type = NLA_U8 }, +}; + +static int +ip_set_type(struct sk_buff *skb, struct genl_info *info) +{ + struct nlattr *const *attr = info->attrs; + const struct nlmsghdr *nlh = info->nlhdr; + + struct sk_buff *skb2; + void *nlh2; + u8 family, min, max; + const char *typename; + int ret = 0; + + if (unlikely(protocol_failed(attr) + || attr[IPSET_ATTR_TYPENAME] == NULL + || attr[IPSET_ATTR_FAMILY] == NULL)) + return -IPSET_ERR_PROTOCOL; + + family = nla_get_u8(attr[IPSET_ATTR_FAMILY]); + typename = nla_data(attr[IPSET_ATTR_TYPENAME]); + if (!find_set_type_minmax(typename, family, &min, &max)) { + /* Try to load in the type module */ + load_type_module(typename); + if (!find_set_type_minmax(typename, family, &min, &max)) { + pr_debug("can't find: %s, family: %u", + typename, family); + return -EEXIST; + } + } + + skb2 = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL); + if (skb2 == NULL) + return -ENOMEM; + + nlh2 = start_msg(skb2, NETLINK_CB(skb).pid, nlh->nlmsg_seq, 0, + IPSET_CMD_TYPE); + if (!nlh2) + goto nlmsg_failure; + NLA_PUT_U8(skb2, IPSET_ATTR_PROTOCOL, IPSET_PROTOCOL); + NLA_PUT_STRING(skb2, IPSET_ATTR_TYPENAME, typename); + NLA_PUT_U8(skb2, IPSET_ATTR_FAMILY, family); + NLA_PUT_U8(skb2, IPSET_ATTR_REVISION, max); + NLA_PUT_U8(skb2, IPSET_ATTR_REVISION_MIN, min); + genlmsg_end(skb2, nlh2); + + ret = genlmsg_unicast(genl_info_net(info), skb2, NETLINK_CB(skb).pid); + if (ret < 0) + return -EFAULT; + + return 0; + +nla_put_failure: + genlmsg_cancel(skb2, nlh2); +nlmsg_failure: + kfree_skb(skb2); + return -EFAULT; +} + +/* Get protocol version */ + +static const struct nla_policy +ip_set_protocol_policy[IPSET_ATTR_CMD_MAX + 1] = { + [IPSET_ATTR_PROTOCOL] = { .type = NLA_U8 }, +}; + +static int +ip_set_protocol(struct sk_buff *skb, struct genl_info *info) +{ + struct nlattr *const *attr = info->attrs; + const struct nlmsghdr *nlh = info->nlhdr; + + struct sk_buff *skb2; + void *nlh2; + int ret = 0; + + if (unlikely(attr[IPSET_ATTR_PROTOCOL] == NULL)) + return -IPSET_ERR_PROTOCOL; + + skb2 = genlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); + if (skb2 == NULL) + return -ENOMEM; + + nlh2 = start_msg(skb2, NETLINK_CB(skb).pid, nlh->nlmsg_seq, 0, + IPSET_CMD_PROTOCOL); + if (!nlh2) + goto nlmsg_failure; + NLA_PUT_U8(skb2, IPSET_ATTR_PROTOCOL, IPSET_PROTOCOL); + genlmsg_end(skb2, nlh2); + + ret = genlmsg_unicast(genl_info_net(info), skb2, NETLINK_CB(skb).pid); + if (ret < 0) + return -EFAULT; + + return 0; + +nla_put_failure: + genlmsg_cancel(skb2, nlh2); +nlmsg_failure: + kfree_skb(skb2); + return -EFAULT; +} + +static struct genl_ops ip_set_netlink_subsys_cb[] __read_mostly = { + { + .cmd = IPSET_CMD_CREATE, + .flags = GENL_ADMIN_PERM, + .doit = ip_set_create, + .policy = ip_set_create_policy, + }, + { + .cmd = IPSET_CMD_DESTROY, + .flags = GENL_ADMIN_PERM, + .doit = ip_set_destroy, + .policy = ip_set_setname_policy, + }, + { + .cmd = IPSET_CMD_FLUSH, + .flags = GENL_ADMIN_PERM, + .doit = ip_set_flush, + .policy = ip_set_setname_policy, + }, + { + .cmd = IPSET_CMD_RENAME, + .flags = GENL_ADMIN_PERM, + .doit = ip_set_rename, + .policy = ip_set_setname2_policy, + }, + { + .cmd = IPSET_CMD_SWAP, + .flags = GENL_ADMIN_PERM, + .doit = ip_set_swap, + .policy = ip_set_setname2_policy, + }, + { + .cmd = IPSET_CMD_LIST, + .flags = GENL_ADMIN_PERM, + .doit = ip_set_dump, + .policy = ip_set_setname_policy, + }, + { + .cmd = IPSET_CMD_SAVE, + .flags = GENL_ADMIN_PERM, + .doit = ip_set_dump, + .policy = ip_set_setname_policy, + }, + { + .cmd = IPSET_CMD_ADD, + .flags = GENL_ADMIN_PERM, + .doit = ip_set_uadd, + .policy = ip_set_adt_policy, + }, + { + .cmd = IPSET_CMD_DEL, + .flags = GENL_ADMIN_PERM, + .doit = ip_set_udel, + .policy = ip_set_adt_policy, + }, + { + .cmd = IPSET_CMD_TEST, + .flags = GENL_ADMIN_PERM, + .doit = ip_set_utest, + .policy = ip_set_adt_policy, + }, + { + .cmd = IPSET_CMD_HEADER, + .flags = GENL_ADMIN_PERM, + .doit = ip_set_header, + .policy = ip_set_setname_policy, + }, + { + .cmd = IPSET_CMD_TYPE, + .flags = GENL_ADMIN_PERM, + .doit = ip_set_type, + .policy = ip_set_type_policy, + }, + { + .cmd = IPSET_CMD_PROTOCOL, + .flags = GENL_ADMIN_PERM, + .doit = ip_set_protocol, + .policy = ip_set_protocol_policy, + }, +}; + +static struct genl_family ip_set_netlink_subsys __read_mostly = { + .name = "ip_set", + .id = GENL_ID_GENERATE, + .hdrsize = 0, + .version = 5, + .maxattr = IPSET_ATTR_CMD_MAX, +}; + +/* Interface to iptables/ip6tables */ + +static int +ip_set_sockfn_get(struct sock *sk, int optval, void *user, int *len) +{ + unsigned *op; + void *data; + int copylen = *len, ret = 0; + + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + if (optval != SO_IP_SET) + return -EBADF; + if (*len < sizeof(unsigned)) + return -EINVAL; + + data = vmalloc(*len); + if (!data) + return -ENOMEM; + if (copy_from_user(data, user, *len) != 0) { + ret = -EFAULT; + goto done; + } + op = (unsigned *) data; + + if (*op < IP_SET_OP_VERSION) { + /* Check the version at the beginning of operations */ + struct ip_set_req_version *req_version = data; + if (req_version->version != IPSET_PROTOCOL) { + ret = -EPROTO; + goto done; + } + } + + switch (*op) { + case IP_SET_OP_VERSION: { + struct ip_set_req_version *req_version = data; + + if (*len != sizeof(struct ip_set_req_version)) { + ret = -EINVAL; + goto done; + } + + req_version->version = IPSET_PROTOCOL; + ret = copy_to_user(user, req_version, + sizeof(struct ip_set_req_version)); + goto done; + } + case IP_SET_OP_GET_BYNAME: { + struct ip_set_req_get_set *req_get = data; + + if (*len != sizeof(struct ip_set_req_get_set)) { + ret = -EINVAL; + goto done; + } + req_get->set.name[IPSET_MAXNAMELEN - 1] = '\0'; + nfnl_lock(); + req_get->set.index = find_set_id(req_get->set.name); + nfnl_unlock(); + goto copy; + } + case IP_SET_OP_GET_BYINDEX: { + struct ip_set_req_get_set *req_get = data; + + if (*len != sizeof(struct ip_set_req_get_set) + || req_get->set.index >= ip_set_max) { + ret = -EINVAL; + goto done; + } + nfnl_lock(); + strncpy(req_get->set.name, + ip_set_list[req_get->set.index] + ? ip_set_list[req_get->set.index]->name : "", + IPSET_MAXNAMELEN); + nfnl_unlock(); + goto copy; + } + default: + ret = -EBADMSG; + goto done; + } /* end of switch(op) */ + +copy: + ret = copy_to_user(user, data, copylen); + +done: + vfree(data); + if (ret > 0) + ret = 0; + return ret; +} + +static struct nf_sockopt_ops so_set __read_mostly = { + .pf = PF_INET, + .get_optmin = SO_IP_SET, + .get_optmax = SO_IP_SET + 1, + .get = &ip_set_sockfn_get, + .owner = THIS_MODULE, +}; + +static int __init +ip_set_init(void) +{ + int ret; + + if (max_sets) + ip_set_max = max_sets; + if (ip_set_max >= IPSET_INVALID_ID) + ip_set_max = IPSET_INVALID_ID - 1; + + ip_set_list = kzalloc(sizeof(struct ip_set *) * ip_set_max, + GFP_KERNEL); + if (!ip_set_list) { + pr_err("ip_set: Unable to create ip_set_list"); + return -ENOMEM; + } + + INIT_LIST_HEAD(&ip_set_type_list); + + ret = genl_register_family_with_ops(&ip_set_netlink_subsys, + ip_set_netlink_subsys_cb, ARRAY_SIZE(ip_set_netlink_subsys_cb)); + if (ret != 0) { + pr_err("ip_set: cannot register with genetlink."); + kfree(ip_set_list); + return ret; + } + ret = nf_register_sockopt(&so_set); + if (ret != 0) { + pr_err("SO_SET registry failed: %d", ret); + genl_unregister_family(&ip_set_netlink_subsys); + kfree(ip_set_list); + return ret; + } + + pr_notice("ip_set: protocol %u", IPSET_PROTOCOL); + return 0; +} + +static void __exit +ip_set_fini(void) +{ + /* There can't be any existing set */ + nf_unregister_sockopt(&so_set); + genl_unregister_family(&ip_set_netlink_subsys); + kfree(ip_set_list); + pr_debug("these are the famous last words"); +} + +module_init(ip_set_init); +module_exit(ip_set_fini); diff --git a/extensions/ipset-5/ip_set.h b/extensions/ipset-5/ip_set.h new file mode 100644 index 0000000..72ff624 --- /dev/null +++ b/extensions/ipset-5/ip_set.h @@ -0,0 +1,530 @@ +#ifndef _IP_SET_H +#define _IP_SET_H + +/* Copyright (C) 2000-2002 Joakim Axelsson + * Patrick Schaaf + * Martin Josefsson + * 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. + */ + +/* The protocol version */ +#define IPSET_PROTOCOL 5 + +/* The max length of strings including NUL: set and type identifiers */ +#define IPSET_MAXNAMELEN 32 + +/* Message types and commands */ +enum ipset_cmd { + IPSET_CMD_NONE, + IPSET_CMD_PROTOCOL, /* 1: Return protocol version */ + IPSET_CMD_CREATE, /* 2: Create a new (empty) set */ + IPSET_CMD_DESTROY, /* 3: Destroy a (empty) set */ + IPSET_CMD_FLUSH, /* 4: Remove all elements from a set */ + IPSET_CMD_RENAME, /* 5: Rename a set */ + IPSET_CMD_SWAP, /* 6: Swap two sets */ + IPSET_CMD_LIST, /* 7: List sets */ + IPSET_CMD_SAVE, /* 8: Save sets */ + IPSET_CMD_ADD, /* 9: Add an element to a set */ + IPSET_CMD_DEL, /* 10: Delete an element from a set */ + IPSET_CMD_TEST, /* 11: Test an element in a set */ + IPSET_CMD_HEADER, /* 12: Get set header data only */ + IPSET_CMD_TYPE, /* 13: Get set type */ + IPSET_MSG_MAX, /* Netlink message commands */ + + /* Commands in userspace: */ + IPSET_CMD_RESTORE = IPSET_MSG_MAX, /* 14: Enter restore mode */ + IPSET_CMD_HELP, /* 15: Get help */ + IPSET_CMD_VERSION, /* 16: Get program version */ + IPSET_CMD_QUIT, /* 17: Quit from interactive mode */ + + IPSET_CMD_MAX, + + IPSET_CMD_COMMIT = IPSET_CMD_MAX, /* 18: Commit buffered commands */ +}; + +/* Attributes at command level */ +enum { + IPSET_ATTR_UNSPEC, + IPSET_ATTR_PROTOCOL, /* 1: Protocol version */ + IPSET_ATTR_SETNAME, /* 2: Name of the set */ + IPSET_ATTR_TYPENAME, /* 3: Typename */ + IPSET_ATTR_SETNAME2 = IPSET_ATTR_TYPENAME, /* Setname at rename/swap */ + IPSET_ATTR_REVISION, /* 4: Settype revision */ + IPSET_ATTR_FAMILY, /* 5: Settype family */ + IPSET_ATTR_FLAGS, /* 6: Flags at command level */ + IPSET_ATTR_DATA, /* 7: Nested attributes */ + IPSET_ATTR_ADT, /* 8: Multiple data containers */ + IPSET_ATTR_LINENO, /* 9: Restore lineno */ + IPSET_ATTR_PROTOCOL_MIN, /* 10: Minimal supported version number */ + IPSET_ATTR_REVISION_MIN = IPSET_ATTR_PROTOCOL_MIN, /* type rev min */ + __IPSET_ATTR_CMD_MAX, +}; +#define IPSET_ATTR_CMD_MAX (__IPSET_ATTR_CMD_MAX - 1) + +/* CADT specific attributes */ +enum { + IPSET_ATTR_IP = IPSET_ATTR_UNSPEC + 1, + IPSET_ATTR_IP_FROM = IPSET_ATTR_IP, + IPSET_ATTR_IP_TO, /* 2 */ + IPSET_ATTR_CIDR, /* 3 */ + IPSET_ATTR_PORT, /* 4 */ + IPSET_ATTR_PORT_FROM = IPSET_ATTR_PORT, + IPSET_ATTR_PORT_TO, /* 5 */ + IPSET_ATTR_TIMEOUT, /* 6 */ + IPSET_ATTR_PROTO, /* 7 */ + IPSET_ATTR_CADT_FLAGS, /* 8 */ + IPSET_ATTR_CADT_LINENO = IPSET_ATTR_LINENO, /* 9 */ + /* Reserve empty slots */ + IPSET_ATTR_CADT_MAX = 16, + /* Create-only specific attributes */ + IPSET_ATTR_GC, + IPSET_ATTR_HASHSIZE, + IPSET_ATTR_MAXELEM, + IPSET_ATTR_NETMASK, + IPSET_ATTR_PROBES, + IPSET_ATTR_RESIZE, + IPSET_ATTR_SIZE, + /* Kernel-only */ + IPSET_ATTR_ELEMENTS, + IPSET_ATTR_REFERENCES, + IPSET_ATTR_MEMSIZE, + + __IPSET_ATTR_CREATE_MAX, +}; +#define IPSET_ATTR_CREATE_MAX (__IPSET_ATTR_CREATE_MAX - 1) + +/* ADT specific attributes */ +enum { + IPSET_ATTR_ETHER = IPSET_ATTR_CADT_MAX + 1, + IPSET_ATTR_NAME, + IPSET_ATTR_NAMEREF, + IPSET_ATTR_IP2, + IPSET_ATTR_CIDR2, + __IPSET_ATTR_ADT_MAX, +}; +#define IPSET_ATTR_ADT_MAX (__IPSET_ATTR_ADT_MAX - 1) + +/* IP specific attributes */ +enum { + IPSET_ATTR_IPADDR_IPV4 = IPSET_ATTR_UNSPEC + 1, + IPSET_ATTR_IPADDR_IPV6, + __IPSET_ATTR_IPADDR_MAX, +}; +#define IPSET_ATTR_IPADDR_MAX (__IPSET_ATTR_IPADDR_MAX - 1) + +/* Error codes */ +enum ipset_errno { + IPSET_ERR_PRIVATE = 128, + IPSET_ERR_PROTOCOL, + IPSET_ERR_FIND_TYPE, + IPSET_ERR_MAX_SETS, + IPSET_ERR_BUSY, + IPSET_ERR_EXIST_SETNAME2, + IPSET_ERR_TYPE_MISMATCH, + IPSET_ERR_EXIST, + IPSET_ERR_INVALID_CIDR, + IPSET_ERR_INVALID_NETMASK, + IPSET_ERR_INVALID_FAMILY, + IPSET_ERR_TIMEOUT, + IPSET_ERR_REFERENCED, + IPSET_ERR_IPADDR_IPV4, + IPSET_ERR_IPADDR_IPV6, + + /* Type specific error codes */ + IPSET_ERR_TYPE_SPECIFIC = 160, +}; + +/* Flags at command level */ +enum ipset_cmd_flags { + IPSET_FLAG_BIT_EXIST = 0, + IPSET_FLAG_EXIST = (1 << IPSET_FLAG_BIT_EXIST), +}; + +/* Flags at CADT attribute level */ +enum ipset_cadt_flags { + IPSET_FLAG_BIT_BEFORE = 0, + IPSET_FLAG_BEFORE = (1 << IPSET_FLAG_BIT_BEFORE), +}; + +/* Commands with settype-specific attributes */ +enum ipset_adt { + IPSET_ADD, + IPSET_DEL, + IPSET_TEST, + IPSET_ADT_MAX, + IPSET_CREATE = IPSET_ADT_MAX, + IPSET_CADT_MAX, +}; + +#ifdef __KERNEL__ +#include +#include +#include +#include +#include +#include + +/* Sets are identified by an index in kernel space. Tweak with ip_set_id_t + * and IPSET_INVALID_ID if you want to increase the max number of sets. + */ +typedef u16 ip_set_id_t; + +#define IPSET_INVALID_ID 65535 + +enum ip_set_dim { + IPSET_DIM_ZERO = 0, + IPSET_DIM_ONE, + IPSET_DIM_TWO, + IPSET_DIM_THREE, + /* Max dimension in elements. + * If changed, new revision of iptables match/target is required. + */ + IPSET_DIM_MAX = 6, +}; + +/* Option flags for kernel operations */ +enum ip_set_kopt { + IPSET_INV_MATCH = (1 << IPSET_DIM_ZERO), + IPSET_DIM_ONE_SRC = (1 << IPSET_DIM_ONE), + IPSET_DIM_TWO_SRC = (1 << IPSET_DIM_TWO), + IPSET_DIM_THREE_SRC = (1 << IPSET_DIM_THREE), +}; + +/* Set features */ +enum ip_set_feature { + IPSET_TYPE_IP_FLAG = 0, + IPSET_TYPE_IP = (1 << IPSET_TYPE_IP_FLAG), + IPSET_TYPE_PORT_FLAG = 1, + IPSET_TYPE_PORT = (1 << IPSET_TYPE_PORT_FLAG), + IPSET_TYPE_MAC_FLAG = 2, + IPSET_TYPE_MAC = (1 << IPSET_TYPE_MAC_FLAG), + IPSET_TYPE_IP2_FLAG = 3, + IPSET_TYPE_IP2 = (1 << IPSET_TYPE_IP2_FLAG), + IPSET_TYPE_NAME_FLAG = 4, + IPSET_TYPE_NAME = (1 << IPSET_TYPE_NAME_FLAG), + /* Strictly speaking not a feature, but a flag for dumping: + * this settype must be dumped last */ + IPSET_DUMP_LAST_FLAG = 7, + IPSET_DUMP_LAST = (1 << IPSET_DUMP_LAST_FLAG), +}; + +struct ip_set; + +typedef int (*ipset_adtfn)(struct ip_set *set, void *value, u32 timeout); + +/* Set type, variant-specific part */ +struct ip_set_type_variant { + /* Kernelspace: test/add/del entries + * returns negative error code, + * zero for no match/success to add/delete + * positive for matching element */ + int (*kadt)(struct ip_set *set, const struct sk_buff * skb, + enum ipset_adt adt, u8 pf, u8 dim, u8 flags); + + /* Userspace: test/add/del entries + * returns negative error code, + * zero for no match/success to add/delete + * positive for matching element */ + int (*uadt)(struct ip_set *set, struct nlattr *head, int len, + enum ipset_adt adt, u32 *lineno, u32 flags); + + /* Low level add/del/test functions */ + ipset_adtfn adt[IPSET_ADT_MAX]; + + /* When adding entries and set is full, try to resize the set */ + int (*resize)(struct ip_set *set, bool retried); + /* Destroy the set */ + void (*destroy)(struct ip_set *set); + /* Flush the elements */ + void (*flush)(struct ip_set *set); + /* Expire entries before listing */ + void (*expire)(struct ip_set *set); + /* List set header data */ + int (*head)(struct ip_set *set, struct sk_buff *skb); + /* List elements */ + int (*list)(const struct ip_set *set, struct sk_buff *skb, + struct netlink_callback *cb); + + /* Return true if "b" set is the same as "a" + * according to the create set parameters */ + bool (*same_set)(const struct ip_set *a, const struct ip_set *b); +}; + +/* The core set type structure */ +struct ip_set_type { + struct list_head list; + + /* Typename */ + char name[IPSET_MAXNAMELEN]; + /* Protocol version */ + u8 protocol; + /* Set features to control swapping */ + u8 features; + /* Set type dimension */ + u8 dimension; + /* Supported family: may be AF_UNSPEC for both AF_INET/AF_INET6 */ + u8 family; + /* Type revision */ + u8 revision; + + /* Create set */ + int (*create)(struct ip_set *set, + struct nlattr *head, int len, u32 flags); + + /* Set this to THIS_MODULE if you are a module, otherwise NULL */ + struct module *me; +}; + +extern int ip_set_type_register(struct ip_set_type *set_type); +extern void ip_set_type_unregister(struct ip_set_type *set_type); + +/* A generic IP set */ +struct ip_set { + /* The name of the set */ + char name[IPSET_MAXNAMELEN]; + /* Lock protecting the set data */ + rwlock_t lock; + /* References to the set */ + atomic_t ref; + /* The core set type */ + const struct ip_set_type *type; + /* The type variant doing the real job */ + const struct ip_set_type_variant *variant; + /* The actual INET family of the set */ + u8 family; + /* The type specific data */ + void *data; +}; + +/* register and unregister set references */ +extern ip_set_id_t ip_set_get_byname(const char *name, struct ip_set **set); +extern void ip_set_put_byindex(ip_set_id_t index); +extern const char * ip_set_name_byindex(ip_set_id_t index); +extern ip_set_id_t ip_set_nfnl_get(const char *name); +extern ip_set_id_t ip_set_nfnl_get_byindex(ip_set_id_t index); +extern void ip_set_nfnl_put(ip_set_id_t index); + +/* API for iptables set match, and SET target */ +extern int ip_set_add(ip_set_id_t id, const struct sk_buff *skb, + u8 family, u8 dim, u8 flags); +extern int ip_set_del(ip_set_id_t id, const struct sk_buff *skb, + u8 family, u8 dim, u8 flags); +extern int ip_set_test(ip_set_id_t id, const struct sk_buff *skb, + u8 family, u8 dim, u8 flags); + +/* Allocate members */ +static inline void * +ip_set_alloc(size_t size, gfp_t gfp_mask) +{ + void *members = NULL; + + if (size < KMALLOC_MAX_SIZE) + members = kzalloc(size, gfp_mask | __GFP_NOWARN); + + if (members) { + pr_debug("%p: allocated with kmalloc", members); + return members; + } + + members = __vmalloc(size, gfp_mask | __GFP_ZERO, PAGE_KERNEL); + if (!members) + return NULL; + pr_debug("%p: allocated with vmalloc", members); + + return members; +} + +static inline void +ip_set_free(void *members) +{ + pr_debug("%p: free with %s", members, + is_vmalloc_addr(members) ? "vfree" : "kfree"); + if (is_vmalloc_addr(members)) + vfree(members); + else + kfree(members); +} + +/* Ignore IPSET_ERR_EXIST errors if asked to do so? */ +static inline bool +ip_set_eexist(int ret, u32 flags) +{ + return ret == -IPSET_ERR_EXIST && (flags & IPSET_FLAG_EXIST); +} + +/* Useful converters */ +static inline u32 +ip_set_get_h32(const struct nlattr *attr) +{ + u32 value = nla_get_u32(attr); + + return attr->nla_type & NLA_F_NET_BYTEORDER ? ntohl(value) : value; +} + +static inline u16 +ip_set_get_h16(const struct nlattr *attr) +{ + u16 value = nla_get_u16(attr); + + return attr->nla_type & NLA_F_NET_BYTEORDER ? ntohs(value) : value; +} + +static inline u32 +ip_set_get_n32(const struct nlattr *attr) +{ + u32 value = nla_get_u32(attr); + + return attr->nla_type & NLA_F_NET_BYTEORDER ? value : htonl(value); +} + +static inline u16 +ip_set_get_n16(const struct nlattr *attr) +{ + u16 value = nla_get_u16(attr); + + return attr->nla_type & NLA_F_NET_BYTEORDER ? value : htons(value); +} + +static const struct nla_policy ipaddr_policy[IPSET_ATTR_IPADDR_MAX + 1] = { + [IPSET_ATTR_IPADDR_IPV4] = { .type = NLA_U32 }, + [IPSET_ATTR_IPADDR_IPV6] = { .type = NLA_BINARY, + .len = sizeof(struct in6_addr) }, +}; + +static inline int +ip_set_get_ipaddr4(struct nlattr *attr[], int type, u32 *ipaddr) +{ + struct nlattr *tb[IPSET_ATTR_IPADDR_MAX+1] = {}; + + if (!attr[type]) + return -IPSET_ERR_PROTOCOL; + + if (nla_parse(tb, IPSET_ATTR_IPADDR_MAX, + nla_data(attr[type]), nla_len(attr[type]), + ipaddr_policy)) + return -IPSET_ERR_PROTOCOL; + if (!tb[IPSET_ATTR_IPADDR_IPV4]) + return -IPSET_ERR_IPADDR_IPV4; + + *ipaddr = ip_set_get_n32(tb[IPSET_ATTR_IPADDR_IPV4]); + return 0; +} + +static inline int +ip_set_get_ipaddr6(struct nlattr *attr[], int type, union nf_inet_addr *ipaddr) +{ + struct nlattr *tb[IPSET_ATTR_IPADDR_MAX+1] = {}; + + if (!attr[type]) + return -IPSET_ERR_PROTOCOL; + + if (nla_parse(tb, IPSET_ATTR_IPADDR_MAX, + nla_data(attr[type]), nla_len(attr[type]), + ipaddr_policy)) + return -IPSET_ERR_PROTOCOL; + if (!tb[IPSET_ATTR_IPADDR_IPV6]) + return -IPSET_ERR_IPADDR_IPV6; + + memcpy(ipaddr, nla_data(tb[IPSET_ATTR_IPADDR_IPV6]), + sizeof(struct in6_addr)); + return 0; +} + +#define ipset_nest_start(skb, attr) nla_nest_start(skb, attr | NLA_F_NESTED) +#define ipset_nest_end(skb, start) nla_nest_end(skb, start) + +#define NLA_PUT_NET32(skb, type, value) \ + NLA_PUT_BE32(skb, type | NLA_F_NET_BYTEORDER, value) + +#define NLA_PUT_NET16(skb, type, value) \ + NLA_PUT_BE16(skb, type | NLA_F_NET_BYTEORDER, value) + +#define NLA_PUT_IPADDR4(skb, type, ipaddr) \ +do { \ + struct nlattr *__nested = ipset_nest_start(skb, type); \ + \ + if (!__nested) \ + goto nla_put_failure; \ + NLA_PUT_NET32(skb, IPSET_ATTR_IPADDR_IPV4, ipaddr); \ + ipset_nest_end(skb, __nested); \ +} while (0) + +#define NLA_PUT_IPADDR6(skb, type, ipaddrptr) \ +do { \ + struct nlattr *__nested = ipset_nest_start(skb, type); \ + \ + if (!__nested) \ + goto nla_put_failure; \ + NLA_PUT(skb, IPSET_ATTR_IPADDR_IPV6, \ + sizeof(struct in6_addr), ipaddrptr); \ + ipset_nest_end(skb, __nested); \ +} while (0) + +/* Get address from skbuff */ +static inline u32 +ip4addr(const struct sk_buff *skb, bool src) +{ + return src ? ip_hdr(skb)->saddr : ip_hdr(skb)->daddr; +} + +static inline void +ip4addrptr(const struct sk_buff *skb, bool src, u32 *addr) +{ + *addr = src ? ip_hdr(skb)->saddr : ip_hdr(skb)->daddr; +} + +static inline void +ip6addrptr(const struct sk_buff *skb, bool src, struct in6_addr *addr) +{ + memcpy(addr, src ? &ipv6_hdr(skb)->saddr : &ipv6_hdr(skb)->daddr, + sizeof(*addr)); +} + +/* Calculate the bytes required to store the inclusive range of a-b */ +static inline int +bitmap_bytes(u32 a, u32 b) +{ + return 4 * ((((b - a + 8) / 8) + 3) / 4); +} + +/* Prefixlen maps */ +extern const union nf_inet_addr prefixlen_netmask_map[]; +extern const union nf_inet_addr prefixlen_hostmask_map[]; + +#define NETMASK(n) prefixlen_netmask_map[n].ip +#define NETMASK6(n) prefixlen_netmask_map[n].ip6 +#define HOSTMASK(n) prefixlen_hostmask_map[n].ip +#define HOSTMASK6(n) prefixlen_hostmask_map[n].ip6 + +/* Interface to iptables/ip6tables */ + +#define SO_IP_SET 83 + +union ip_set_name_index { + char name[IPSET_MAXNAMELEN]; + ip_set_id_t index; +}; + +#define IP_SET_OP_GET_BYNAME 0x00000006 /* Get set index by name */ +struct ip_set_req_get_set { + unsigned op; + unsigned version; + union ip_set_name_index set; +}; + +#define IP_SET_OP_GET_BYINDEX 0x00000007 /* Get set name by index */ +/* Uses ip_set_req_get_set */ + +#define IP_SET_OP_VERSION 0x00000100 /* Ask kernel version */ +struct ip_set_req_version { + unsigned op; + unsigned version; +}; + +#endif /* __KERNEL__ */ + +#endif /*_IP_SET_H */ diff --git a/extensions/ipset-5/ip_set_ahash.h b/extensions/ipset-5/ip_set_ahash.h new file mode 100644 index 0000000..7c36f63 --- /dev/null +++ b/extensions/ipset-5/ip_set_ahash.h @@ -0,0 +1,1074 @@ +#ifndef _IP_SET_AHASH_H +#define _IP_SET_AHASH_H + +#include +#include "jhash.h" +#include "ip_set_timeout.h" + +/* Hashing which uses arrays to resolve clashing. The hash table is resized + * (doubled) when searching becomes too long. + * Internally jhash is used with the assumption that the size of the + * stored data is a multiple of sizeof(u32). If storage supports timeout, + * the timeout field must be the last one in the data structure - that field + * is ignored when computing the hash key. + * + * Readers and resizing + * + * Resizing can be triggered by userspace command only, and those + * are serialized by the nfnl mutex. During resizing the set is + * read-locked, so the only possible concurrent operations are + * the kernel side readers. Those must be protected by proper RCU locking. + */ + +/* Number of elements to store in an initial array block */ +#define AHASH_INIT_SIZE 4 +/* Max number of elements to store in an array block */ +#define AHASH_MAX_SIZE (3*4) + +/* A hash bucket */ +struct hbucket { + void *value; /* the array of the values */ + u8 size; /* size of the array */ + u8 pos; /* position of the first free entry */ +}; + +/* The hash table: the table size stored here in order to make resizing easy */ +struct htable { + u8 htable_bits; /* size of hash table == 2^htable_bits */ + struct hbucket bucket[0]; /* hashtable buckets */ +}; + +#define hbucket(h, i) &((h)->bucket[i]) + +/* Book-keeping of the prefixes added to the set */ +struct ip_set_hash_nets { + u8 cidr; /* the different cidr values in the set */ + u32 nets; /* number of elements per cidr */ +}; + +/* The generic ip_set hash structure */ +struct ip_set_hash { + struct htable *table; /* the hash table */ + u32 maxelem; /* max elements in the hash */ + u32 elements; /* current element (vs timeout) */ + u32 initval; /* random jhash init value */ + u32 timeout; /* timeout value, if enabled */ + struct timer_list gc; /* garbage collection when timeout enabled */ +#ifdef IP_SET_HASH_WITH_NETMASK + u8 netmask; /* netmask value for subnets to store */ +#endif +#ifdef IP_SET_HASH_WITH_NETS + struct ip_set_hash_nets nets[0]; /* book-keeping of prefixes */ +#endif +}; + +/* Compute htable_bits from the user input parameter hashsize */ +static inline u8 +htable_bits(u32 hashsize) +{ + /* Assume that hashsize == 2^htable_bits */ + u8 bits = fls(hashsize - 1); + if (jhash_size(bits) != hashsize) + /* Round up to the first 2^n value */ + bits = fls(hashsize); + + return bits; +} + +#ifdef IP_SET_HASH_WITH_NETS + +#define SET_HOST_MASK(family) (family == AF_INET ? 32 : 128) + +/* Network cidr size book keeping when the hash stores different + * sized networks */ +static inline void +add_cidr(struct ip_set_hash *h, u8 cidr, u8 host_mask) +{ + u8 i; + + ++h->nets[cidr-1].nets; + + pr_debug("add_cidr added %u: %u", cidr, h->nets[cidr-1].nets); + + if (h->nets[cidr-1].nets > 1) + return; + + /* New cidr size */ + for (i = 0; i < host_mask && h->nets[i].cidr; i++) { + /* Add in increasing prefix order, so larger cidr first */ + if (h->nets[i].cidr < cidr) + swap(h->nets[i].cidr, cidr); + } + if (i < host_mask) + h->nets[i].cidr = cidr; +} + +static inline void +del_cidr(struct ip_set_hash *h, u8 cidr, u8 host_mask) +{ + u8 i; + + --h->nets[cidr-1].nets; + + pr_debug("del_cidr deleted %u: %u", cidr, h->nets[cidr-1].nets); + + if (h->nets[cidr-1].nets != 0) + return; + + /* All entries with this cidr size deleted, so cleanup h->cidr[] */ + for (i = 0; i < host_mask - 1 && h->nets[i].cidr; i++) { + if (h->nets[i].cidr == cidr) + h->nets[i].cidr = cidr = h->nets[i+1].cidr; + } + h->nets[i - 1].cidr = 0; +} +#endif + +/* Destroy the hashtable part of the set */ +static void +ahash_destroy(struct htable *t) +{ + struct hbucket *n; + u32 i; + + for (i = 0; i < jhash_size(t->htable_bits); i++) { + n = hbucket(t, i); + if (n->size) + /* FIXME: use slab cache */ + kfree(n->value); + } + + ip_set_free(t); +} + +/* Calculate the actual memory size of the set data */ +static inline size_t +ahash_memsize(const struct ip_set_hash *h, size_t dsize, u8 host_mask) +{ + u32 i; + struct htable *t = h->table; + size_t memsize = sizeof(*h) + + sizeof(*t) +#ifdef IP_SET_HASH_WITH_NETS + + sizeof(struct ip_set_hash_nets) * host_mask +#endif + + jhash_size(t->htable_bits) * sizeof(struct hbucket); + + for (i = 0; i < jhash_size(t->htable_bits); i++) + memsize += t->bucket[i].size * dsize; + + return memsize; +} + +/* Flush a hash type of set: destroy all elements */ +static void +ip_set_hash_flush(struct ip_set *set) +{ + struct ip_set_hash *h = set->data; + struct htable *t = h->table; + struct hbucket *n; + u32 i; + + for (i = 0; i < jhash_size(t->htable_bits); i++) { + n = hbucket(t, i); + if (n->size) { + n->size = n->pos = 0; + /* FIXME: use slab cache */ + kfree(n->value); + } + } +#ifdef IP_SET_HASH_WITH_NETS + memset(h->nets, 0, sizeof(struct ip_set_hash_nets) + * SET_HOST_MASK(set->family)); +#endif + h->elements = 0; +} + +/* Destroy a hash type of set */ +static void +ip_set_hash_destroy(struct ip_set *set) +{ + struct ip_set_hash *h = set->data; + + if (with_timeout(h->timeout)) + del_timer_sync(&h->gc); + + ahash_destroy(h->table); + kfree(h); + + set->data = NULL; +} + +#define HKEY(data, initval, htable_bits) \ +(jhash2((u32 *)(data), sizeof(struct type_pf_elem)/sizeof(u32), initval) \ + & jhash_mask(htable_bits)) + +#endif /* _IP_SET_AHASH_H */ + +#define CONCAT(a, b, c) a##b##c +#define TOKEN(a, b, c) CONCAT(a, b, c) + +/* Type/family dependent function prototypes */ + +#define type_pf_data_equal TOKEN(TYPE, PF, _data_equal) +#define type_pf_data_isnull TOKEN(TYPE, PF, _data_isnull) +#define type_pf_data_copy TOKEN(TYPE, PF, _data_copy) +#define type_pf_data_zero_out TOKEN(TYPE, PF, _data_zero_out) +#define type_pf_data_netmask TOKEN(TYPE, PF, _data_netmask) +#define type_pf_data_list TOKEN(TYPE, PF, _data_list) +#define type_pf_data_tlist TOKEN(TYPE, PF, _data_tlist) + +#define type_pf_elem TOKEN(TYPE, PF, _elem) +#define type_pf_telem TOKEN(TYPE, PF, _telem) +#define type_pf_data_timeout TOKEN(TYPE, PF, _data_timeout) +#define type_pf_data_expired TOKEN(TYPE, PF, _data_expired) +#define type_pf_data_timeout_set TOKEN(TYPE, PF, _data_timeout_set) + +#define type_pf_elem_add TOKEN(TYPE, PF, _elem_add) +#define type_pf_add TOKEN(TYPE, PF, _add) +#define type_pf_del TOKEN(TYPE, PF, _del) +#define type_pf_test_cidrs TOKEN(TYPE, PF, _test_cidrs) +#define type_pf_test TOKEN(TYPE, PF, _test) + +#define type_pf_elem_tadd TOKEN(TYPE, PF, _elem_tadd) +#define type_pf_del_telem TOKEN(TYPE, PF, _ahash_del_telem) +#define type_pf_expire TOKEN(TYPE, PF, _expire) +#define type_pf_tadd TOKEN(TYPE, PF, _tadd) +#define type_pf_tdel TOKEN(TYPE, PF, _tdel) +#define type_pf_ttest_cidrs TOKEN(TYPE, PF, _ahash_ttest_cidrs) +#define type_pf_ttest TOKEN(TYPE, PF, _ahash_ttest) + +#define type_pf_resize TOKEN(TYPE, PF, _resize) +#define type_pf_tresize TOKEN(TYPE, PF, _tresize) +#define type_pf_flush ip_set_hash_flush +#define type_pf_destroy ip_set_hash_destroy +#define type_pf_head TOKEN(TYPE, PF, _head) +#define type_pf_list TOKEN(TYPE, PF, _list) +#define type_pf_tlist TOKEN(TYPE, PF, _tlist) +#define type_pf_same_set TOKEN(TYPE, PF, _same_set) +#define type_pf_kadt TOKEN(TYPE, PF, _kadt) +#define type_pf_uadt TOKEN(TYPE, PF, _uadt) +#define type_pf_gc TOKEN(TYPE, PF, _gc) +#define type_pf_gc_init TOKEN(TYPE, PF, _gc_init) +#define type_pf_variant TOKEN(TYPE, PF, _variant) +#define type_pf_tvariant TOKEN(TYPE, PF, _tvariant) + +/* Flavour without timeout */ + +/* Get the ith element from the array block n */ +#define ahash_data(n, i) \ + ((struct type_pf_elem *)((n)->value) + (i)) + +/* Add an element to the hash table when resizing the set: + * we spare the maintenance of the internal counters. */ +static int +type_pf_elem_add(struct hbucket *n, const struct type_pf_elem *value) +{ + if (n->pos >= n->size) { + void *tmp; + + if (n->size >= AHASH_MAX_SIZE) + /* Trigger rehashing */ + return -EAGAIN; + + tmp = kzalloc((n->size + AHASH_INIT_SIZE) + * sizeof(struct type_pf_elem), + GFP_ATOMIC); + if (!tmp) + return -ENOMEM; + if (n->size) { + memcpy(tmp, n->value, + sizeof(struct type_pf_elem) * n->size); + kfree(n->value); + } + n->value = tmp; + n->size += AHASH_INIT_SIZE; + } + type_pf_data_copy(ahash_data(n, n->pos++), value); + return 0; +} + +/* Resize a hash: create a new hash table with doubling the hashsize + * and inserting the elements to it. Repeat until we succeed or + * fail due to memory pressures. */ +static int +type_pf_resize(struct ip_set *set, bool retried) +{ + struct ip_set_hash *h = set->data; + struct htable *t, *orig = h->table; + u8 htable_bits = orig->htable_bits; + const struct type_pf_elem *data; + struct hbucket *n, *m; + u32 i, j; + int ret; + +retry: + ret = 0; + htable_bits++; + pr_debug("attempt to resize set %s from %u to %u, t %p\n", + set->name, orig->htable_bits, htable_bits, orig); + if (!htable_bits) + /* In case we have plenty of memory :-) */ + return -IPSET_ERR_HASH_FULL; + t = ip_set_alloc(sizeof(*t) + + jhash_size(htable_bits) * sizeof(struct hbucket), + GFP_KERNEL); + if (!t) + return -ENOMEM; + t->htable_bits = htable_bits; + + read_lock_bh(&set->lock); + for (i = 0; i < jhash_size(orig->htable_bits); i++) { + n = hbucket(orig, i); + for (j = 0; j < n->pos; j++) { + data = ahash_data(n, j); + m = hbucket(t, HKEY(data, h->initval, htable_bits)); + ret = type_pf_elem_add(m, data); + if (ret < 0) { + read_unlock_bh(&set->lock); + ahash_destroy(t); + if (ret == -EAGAIN) + goto retry; + return ret; + } + } + } + + rcu_assign_pointer(h->table, t); + read_unlock_bh(&set->lock); + + /* Give time to other readers of the set */ + synchronize_rcu_bh(); + + pr_debug("set %s resized from %u (%p) to %u (%p)", set->name, + orig->htable_bits, orig, t->htable_bits, t); + ahash_destroy(orig); + + return 0; +} + +/* Add an element to a hash and update the internal counters when succeeded, + * otherwise report the proper error code. */ +static int +type_pf_add(struct ip_set *set, void *value, u32 timeout) +{ + struct ip_set_hash *h = set->data; + struct htable *t; + const struct type_pf_elem *d = value; + struct hbucket *n; + int i, ret = 0; + u32 key; + + if (h->elements >= h->maxelem) + return -IPSET_ERR_HASH_FULL; + + rcu_read_lock_bh(); + t = rcu_dereference_bh(h->table); + key = HKEY(value, h->initval, t->htable_bits); + n = hbucket(t, key); + for (i = 0; i < n->pos; i++) + if (type_pf_data_equal(ahash_data(n, i), d)) { + ret = -IPSET_ERR_EXIST; + goto out; + } + + ret = type_pf_elem_add(n, value); + if (ret != 0) + goto out; + +#ifdef IP_SET_HASH_WITH_NETS + add_cidr(h, d->cidr, HOST_MASK); +#endif + h->elements++; +out: + rcu_read_unlock_bh(); + return ret; +} + +/* Delete an element from the hash: swap it with the last element + * and free up space if possible. + */ +static int +type_pf_del(struct ip_set *set, void *value, u32 timeout) +{ + struct ip_set_hash *h = set->data; + struct htable *t = h->table; + const struct type_pf_elem *d = value; + struct hbucket *n; + int i; + struct type_pf_elem *data; + u32 key; + + key = HKEY(value, h->initval, t->htable_bits); + n = hbucket(t, key); + for (i = 0; i < n->pos; i++) { + data = ahash_data(n, i); + if (!type_pf_data_equal(data, d)) + continue; + if (i != n->pos - 1) + /* Not last one */ + type_pf_data_copy(data, ahash_data(n, n->pos - 1)); + + n->pos--; + h->elements--; +#ifdef IP_SET_HASH_WITH_NETS + del_cidr(h, d->cidr, HOST_MASK); +#endif + if (n->pos + AHASH_INIT_SIZE < n->size) { + void *tmp = kzalloc((n->size - AHASH_INIT_SIZE) + * sizeof(struct type_pf_elem), + GFP_ATOMIC); + if (!tmp) + return 0; + n->size -= AHASH_INIT_SIZE; + memcpy(tmp, n->value, + n->size * sizeof(struct type_pf_elem)); + kfree(n->value); + n->value = tmp; + } + return 0; + } + + return -IPSET_ERR_EXIST; +} + +#ifdef IP_SET_HASH_WITH_NETS + +/* Special test function which takes into account the different network + * sizes added to the set */ +static inline int +type_pf_test_cidrs(struct ip_set *set, struct type_pf_elem *d, u32 timeout) +{ + struct ip_set_hash *h = set->data; + struct htable *t = h->table; + struct hbucket *n; + const struct type_pf_elem *data; + int i, j = 0; + u32 key; + u8 host_mask = SET_HOST_MASK(set->family); + + pr_debug("test by nets"); + for (; j < host_mask && h->nets[j].cidr; j++) { + type_pf_data_netmask(d, h->nets[j].cidr); + key = HKEY(d, h->initval, t->htable_bits); + n = hbucket(t, key); + for (i = 0; i < n->pos; i++) { + data = ahash_data(n, i); + if (type_pf_data_equal(data, d)) + return 1; + } + } + return 0; +} +#endif + +/* Test whether the element is added to the set */ +static int +type_pf_test(struct ip_set *set, void *value, u32 timeout) +{ + struct ip_set_hash *h = set->data; + struct htable *t = h->table; + struct type_pf_elem *d = value; + struct hbucket *n; + const struct type_pf_elem *data; + int i; + u32 key; + +#ifdef IP_SET_HASH_WITH_NETS + /* If we test an IP address and not a network address, + * try all possible network sizes */ + if (d->cidr == SET_HOST_MASK(set->family)) + return type_pf_test_cidrs(set, d, timeout); +#endif + + key = HKEY(d, h->initval, t->htable_bits); + n = hbucket(t, key); + for (i = 0; i < n->pos; i++) { + data = ahash_data(n, i); + if (type_pf_data_equal(data, d)) + return 1; + } + return 0; +} + +/* Reply a HEADER request: fill out the header part of the set */ +static int +type_pf_head(struct ip_set *set, struct sk_buff *skb) +{ + const struct ip_set_hash *h = set->data; + struct nlattr *nested; + size_t memsize; + + read_lock_bh(&set->lock); + memsize = ahash_memsize(h, with_timeout(h->timeout) + ? sizeof(struct type_pf_telem) + : sizeof(struct type_pf_elem), + set->family == AF_INET ? 32 : 128); + read_unlock_bh(&set->lock); + + nested = ipset_nest_start(skb, IPSET_ATTR_DATA); + if (!nested) + goto nla_put_failure; + NLA_PUT_NET32(skb, IPSET_ATTR_HASHSIZE, + htonl(jhash_size(h->table->htable_bits))); + NLA_PUT_NET32(skb, IPSET_ATTR_MAXELEM, htonl(h->maxelem)); +#ifdef IP_SET_HASH_WITH_NETMASK + if (h->netmask != HOST_MASK) + NLA_PUT_U8(skb, IPSET_ATTR_NETMASK, h->netmask); +#endif + NLA_PUT_NET32(skb, IPSET_ATTR_REFERENCES, + htonl(atomic_read(&set->ref) - 1)); + NLA_PUT_NET32(skb, IPSET_ATTR_MEMSIZE, htonl(memsize)); + if (with_timeout(h->timeout)) + NLA_PUT_NET32(skb, IPSET_ATTR_TIMEOUT, htonl(h->timeout)); + ipset_nest_end(skb, nested); + + return 0; +nla_put_failure: + return -EFAULT; +} + +/* Reply a LIST/SAVE request: dump the elements of the specified set */ +static int +type_pf_list(const struct ip_set *set, + struct sk_buff *skb, struct netlink_callback *cb) +{ + const struct ip_set_hash *h = set->data; + const struct htable *t = h->table; + struct nlattr *atd, *nested; + const struct hbucket *n; + const struct type_pf_elem *data; + u32 first = cb->args[2]; + /* We assume that one hash bucket fills into one page */ + void *incomplete; + int i; + + atd = ipset_nest_start(skb, IPSET_ATTR_ADT); + if (!atd) + return -EFAULT; + pr_debug("list hash set %s", set->name); + for (; cb->args[2] < jhash_size(t->htable_bits); cb->args[2]++) { + incomplete = skb_tail_pointer(skb); + n = hbucket(t, cb->args[2]); + pr_debug("cb->args[2]: %lu, t %p n %p", cb->args[2], t, n); + for (i = 0; i < n->pos; i++) { + data = ahash_data(n, i); + pr_debug("list hash %lu hbucket %p i %u, data %p", + cb->args[2], n, i, data); + nested = ipset_nest_start(skb, IPSET_ATTR_DATA); + if (!nested) { + if (cb->args[2] == first) { + nla_nest_cancel(skb, atd); + return -EFAULT; + } else + goto nla_put_failure; + } + if (type_pf_data_list(skb, data)) + goto nla_put_failure; + ipset_nest_end(skb, nested); + } + } + ipset_nest_end(skb, atd); + /* Set listing finished */ + cb->args[2] = 0; + + return 0; + +nla_put_failure: + nlmsg_trim(skb, incomplete); + ipset_nest_end(skb, atd); + if (unlikely(first == cb->args[2])) { + pr_warning("Can't list set %s: one bucket does not fit into " + "a message. Please report it!\n", set->name); + cb->args[2] = 0; + } + return 0; +} + +static int +type_pf_kadt(struct ip_set *set, const struct sk_buff * skb, + enum ipset_adt adt, u8 pf, u8 dim, u8 flags); +static int +type_pf_uadt(struct ip_set *set, struct nlattr *head, int len, + enum ipset_adt adt, u32 *lineno, u32 flags); + +static const struct ip_set_type_variant type_pf_variant = { + .kadt = type_pf_kadt, + .uadt = type_pf_uadt, + .adt = { + [IPSET_ADD] = type_pf_add, + [IPSET_DEL] = type_pf_del, + [IPSET_TEST] = type_pf_test, + }, + .destroy = type_pf_destroy, + .flush = type_pf_flush, + .head = type_pf_head, + .list = type_pf_list, + .resize = type_pf_resize, + .same_set = type_pf_same_set, +}; + +/* Flavour with timeout support */ + +#define ahash_tdata(n, i) \ + (struct type_pf_elem *)((struct type_pf_telem *)((n)->value) + (i)) + +static inline u32 +type_pf_data_timeout(const struct type_pf_elem *data) +{ + const struct type_pf_telem *tdata = + (const struct type_pf_telem *) data; + + return tdata->timeout; +} + +static inline bool +type_pf_data_expired(const struct type_pf_elem *data) +{ + const struct type_pf_telem *tdata = + (const struct type_pf_telem *) data; + + return ip_set_timeout_expired(tdata->timeout); +} + +static inline void +type_pf_data_timeout_set(struct type_pf_elem *data, u32 timeout) +{ + struct type_pf_telem *tdata = (struct type_pf_telem *) data; + + tdata->timeout = ip_set_timeout_set(timeout); +} + +static int +type_pf_elem_tadd(struct hbucket *n, const struct type_pf_elem *value, + u32 timeout) +{ + struct type_pf_elem *data; + + if (n->pos >= n->size) { + void *tmp; + + if (n->size >= AHASH_MAX_SIZE) + /* Trigger rehashing */ + return -EAGAIN; + + tmp = kzalloc((n->size + AHASH_INIT_SIZE) + * sizeof(struct type_pf_telem), + GFP_ATOMIC); + if (!tmp) + return -ENOMEM; + if (n->size) { + memcpy(tmp, n->value, + sizeof(struct type_pf_telem) * n->size); + kfree(n->value); + } + n->value = tmp; + n->size += AHASH_INIT_SIZE; + } + data = ahash_tdata(n, n->pos++); + type_pf_data_copy(data, value); + type_pf_data_timeout_set(data, timeout); + return 0; +} + +/* Delete expired elements from the hashtable */ +static void +type_pf_expire(struct ip_set_hash *h) +{ + struct htable *t = h->table; + struct hbucket *n; + struct type_pf_elem *data; + u32 i; + int j; + + for (i = 0; i < jhash_size(t->htable_bits); i++) { + n = hbucket(t, i); + for (j = 0; j < n->pos; j++) { + data = ahash_tdata(n, j); + if (type_pf_data_expired(data)) { + pr_debug("expired %u/%u", i, j); +#ifdef IP_SET_HASH_WITH_NETS + del_cidr(h, data->cidr, HOST_MASK); +#endif + if (j != n->pos - 1) + /* Not last one */ + type_pf_data_copy(data, + ahash_tdata(n, n->pos - 1)); + n->pos--; + h->elements--; + } + } + if (n->pos + AHASH_INIT_SIZE < n->size) { + void *tmp = kzalloc((n->size - AHASH_INIT_SIZE) + * sizeof(struct type_pf_telem), + GFP_KERNEL); + if (!tmp) + /* Still try to delete expired elements */ + continue; + n->size -= AHASH_INIT_SIZE; + memcpy(tmp, n->value, + n->size * sizeof(struct type_pf_telem)); + kfree(n->value); + n->value = tmp; + } + } +} + +static int +type_pf_tresize(struct ip_set *set, bool retried) +{ + struct ip_set_hash *h = set->data; + struct htable *t, *orig = h->table; + u8 htable_bits = orig->htable_bits; + const struct type_pf_elem *data; + struct hbucket *n, *m; + u32 i, j; + int ret; + + /* Try to cleanup once */ + if (!retried) { + i = h->elements; + write_lock_bh(&set->lock); + type_pf_expire(set->data); + write_unlock_bh(&set->lock); + if (h->elements < i) + return 0; + } + +retry: + ret = 0; + htable_bits++; + if (!htable_bits) + /* In case we have plenty of memory :-) */ + return -IPSET_ERR_HASH_FULL; + t = ip_set_alloc(sizeof(*t) + + jhash_size(htable_bits) * sizeof(struct hbucket), + GFP_KERNEL); + if (!t) + return -ENOMEM; + t->htable_bits = htable_bits; + + read_lock_bh(&set->lock); + for (i = 0; i < jhash_size(orig->htable_bits); i++) { + n = hbucket(orig, i); + for (j = 0; j < n->pos; j++) { + data = ahash_tdata(n, j); + m = hbucket(t, HKEY(data, h->initval, htable_bits)); + ret = type_pf_elem_tadd(m, data, + type_pf_data_timeout(data)); + if (ret < 0) { + read_unlock_bh(&set->lock); + ahash_destroy(t); + if (ret == -EAGAIN) + goto retry; + return ret; + } + } + } + + rcu_assign_pointer(h->table, t); + read_unlock_bh(&set->lock); + + /* Give time to other readers of the set */ + synchronize_rcu_bh(); + + ahash_destroy(orig); + + return 0; +} + +static int +type_pf_tadd(struct ip_set *set, void *value, u32 timeout) +{ + struct ip_set_hash *h = set->data; + struct htable *t = h->table; + const struct type_pf_elem *d = value; + struct hbucket *n; + struct type_pf_elem *data; + int ret = 0, i, j = AHASH_MAX_SIZE + 1; + u32 key; + + if (h->elements >= h->maxelem) + /* FIXME: when set is full, we slow down here */ + type_pf_expire(h); + if (h->elements >= h->maxelem) + return -IPSET_ERR_HASH_FULL; + + rcu_read_lock_bh(); + t = rcu_dereference_bh(h->table); + key = HKEY(d, h->initval, t->htable_bits); + n = hbucket(t, key); + for (i = 0; i < n->pos; i++) { + data = ahash_tdata(n, i); + if (type_pf_data_equal(data, d)) { + if (type_pf_data_expired(data)) + j = i; + else { + ret = -IPSET_ERR_EXIST; + goto out; + } + } else if (j == AHASH_MAX_SIZE + 1 + && type_pf_data_expired(data)) + j = i; + } + if (j != AHASH_MAX_SIZE + 1) { + data = ahash_tdata(n, j); +#ifdef IP_SET_HASH_WITH_NETS + del_cidr(h, data->cidr, HOST_MASK); + add_cidr(h, d->cidr, HOST_MASK); +#endif + type_pf_data_copy(data, d); + type_pf_data_timeout_set(data, timeout); + goto out; + } + ret = type_pf_elem_tadd(n, d, timeout); + if (ret != 0) + goto out; + +#ifdef IP_SET_HASH_WITH_NETS + add_cidr(h, d->cidr, HOST_MASK); +#endif + h->elements++; +out: + rcu_read_unlock_bh(); + return ret; +} + +static int +type_pf_tdel(struct ip_set *set, void *value, u32 timeout) +{ + struct ip_set_hash *h = set->data; + struct htable *t = h->table; + const struct type_pf_elem *d = value; + struct hbucket *n; + int i, ret = 0; + struct type_pf_elem *data; + u32 key; + + key = HKEY(value, h->initval, t->htable_bits); + n = hbucket(t, key); + for (i = 0; i < n->pos; i++) { + data = ahash_tdata(n, i); + if (!type_pf_data_equal(data, d)) + continue; + if (type_pf_data_expired(data)) + ret = -IPSET_ERR_EXIST; + if (i != n->pos - 1) + /* Not last one */ + type_pf_data_copy(data, ahash_tdata(n, n->pos - 1)); + + n->pos--; + h->elements--; +#ifdef IP_SET_HASH_WITH_NETS + del_cidr(h, d->cidr, HOST_MASK); +#endif + if (n->pos + AHASH_INIT_SIZE < n->size) { + void *tmp = kzalloc((n->size - AHASH_INIT_SIZE) + * sizeof(struct type_pf_telem), + GFP_ATOMIC); + if (!tmp) + return 0; + n->size -= AHASH_INIT_SIZE; + memcpy(tmp, n->value, + n->size * sizeof(struct type_pf_telem)); + kfree(n->value); + n->value = tmp; + } + return 0; + } + + return -IPSET_ERR_EXIST; +} + +#ifdef IP_SET_HASH_WITH_NETS +static inline int +type_pf_ttest_cidrs(struct ip_set *set, struct type_pf_elem *d, u32 timeout) +{ + struct ip_set_hash *h = set->data; + struct htable *t = h->table; + struct type_pf_elem *data; + struct hbucket *n; + int i, j = 0; + u32 key; + u8 host_mask = SET_HOST_MASK(set->family); + + for (; j < host_mask && h->nets[j].cidr; j++) { + type_pf_data_netmask(d, h->nets[j].cidr); + key = HKEY(d, h->initval, t->htable_bits); + n = hbucket(t, key); + for (i = 0; i < n->pos; i++) { + data = ahash_tdata(n, i); + if (type_pf_data_equal(data, d)) + return !type_pf_data_expired(data); + } + } + return 0; +} +#endif + +static int +type_pf_ttest(struct ip_set *set, void *value, u32 timeout) +{ + struct ip_set_hash *h = set->data; + struct htable *t = h->table; + struct type_pf_elem *data, *d = value; + struct hbucket *n; + int i; + u32 key; + +#ifdef IP_SET_HASH_WITH_NETS + if (d->cidr == SET_HOST_MASK(set->family)) + return type_pf_ttest_cidrs(set, d, timeout); +#endif + key = HKEY(d, h->initval, t->htable_bits); + n = hbucket(t, key); + for (i = 0; i < n->pos; i++) { + data = ahash_tdata(n, i); + if (type_pf_data_equal(data, d)) + return !type_pf_data_expired(data); + } + return 0; +} + +static int +type_pf_tlist(const struct ip_set *set, + struct sk_buff *skb, struct netlink_callback *cb) +{ + const struct ip_set_hash *h = set->data; + const struct htable *t = h->table; + struct nlattr *atd, *nested; + const struct hbucket *n; + const struct type_pf_elem *data; + u32 first = cb->args[2]; + /* We assume that one hash bucket fills into one page */ + void *incomplete; + int i; + + atd = ipset_nest_start(skb, IPSET_ATTR_ADT); + if (!atd) + return -EFAULT; + for (; cb->args[2] < jhash_size(t->htable_bits); cb->args[2]++) { + incomplete = skb_tail_pointer(skb); + n = hbucket(t, cb->args[2]); + for (i = 0; i < n->pos; i++) { + data = ahash_tdata(n, i); + pr_debug("list %p %u", n, i); + if (type_pf_data_expired(data)) + continue; + pr_debug("do list %p %u", n, i); + nested = ipset_nest_start(skb, IPSET_ATTR_DATA); + if (!nested) { + if (cb->args[2] == first) { + nla_nest_cancel(skb, atd); + return -EFAULT; + } else + goto nla_put_failure; + } + if (type_pf_data_tlist(skb, data)) + goto nla_put_failure; + ipset_nest_end(skb, nested); + } + } + ipset_nest_end(skb, atd); + /* Set listing finished */ + cb->args[2] = 0; + + return 0; + +nla_put_failure: + nlmsg_trim(skb, incomplete); + ipset_nest_end(skb, atd); + if (unlikely(first == cb->args[2])) { + pr_warning("Can't list set %s: one bucket does not fit into " + "a message. Please report it!\n", set->name); + cb->args[2] = 0; + } + return 0; +} + +static const struct ip_set_type_variant type_pf_tvariant = { + .kadt = type_pf_kadt, + .uadt = type_pf_uadt, + .adt = { + [IPSET_ADD] = type_pf_tadd, + [IPSET_DEL] = type_pf_tdel, + [IPSET_TEST] = type_pf_ttest, + }, + .destroy = type_pf_destroy, + .flush = type_pf_flush, + .head = type_pf_head, + .list = type_pf_tlist, + .resize = type_pf_tresize, + .same_set = type_pf_same_set, +}; + +static void +type_pf_gc(unsigned long ul_set) +{ + struct ip_set *set = (struct ip_set *) ul_set; + struct ip_set_hash *h = set->data; + + pr_debug("called"); + write_lock_bh(&set->lock); + type_pf_expire(h); + write_unlock_bh(&set->lock); + + h->gc.expires = jiffies + IPSET_GC_PERIOD(h->timeout) * HZ; + add_timer(&h->gc); +} + +static inline void +type_pf_gc_init(struct ip_set *set) +{ + struct ip_set_hash *h = set->data; + + init_timer(&h->gc); + h->gc.data = (unsigned long) set; + h->gc.function = type_pf_gc; + h->gc.expires = jiffies + IPSET_GC_PERIOD(h->timeout) * HZ; + add_timer(&h->gc); + pr_debug("gc initialized, run in every %u", + IPSET_GC_PERIOD(h->timeout)); +} + +#undef type_pf_data_equal +#undef type_pf_data_isnull +#undef type_pf_data_copy +#undef type_pf_data_zero_out +#undef type_pf_data_list +#undef type_pf_data_tlist + +#undef type_pf_elem +#undef type_pf_telem +#undef type_pf_data_timeout +#undef type_pf_data_expired +#undef type_pf_data_netmask +#undef type_pf_data_timeout_set + +#undef type_pf_elem_add +#undef type_pf_add +#undef type_pf_del +#undef type_pf_test_cidrs +#undef type_pf_test + +#undef type_pf_elem_tadd +#undef type_pf_expire +#undef type_pf_tadd +#undef type_pf_tdel +#undef type_pf_ttest_cidrs +#undef type_pf_ttest + +#undef type_pf_resize +#undef type_pf_tresize +#undef type_pf_flush +#undef type_pf_destroy +#undef type_pf_head +#undef type_pf_list +#undef type_pf_tlist +#undef type_pf_same_set +#undef type_pf_kadt +#undef type_pf_uadt +#undef type_pf_gc +#undef type_pf_gc_init +#undef type_pf_variant +#undef type_pf_tvariant diff --git a/extensions/ipset-5/ip_set_bitmap.h b/extensions/ipset-5/ip_set_bitmap.h new file mode 100644 index 0000000..61a9e87 --- /dev/null +++ b/extensions/ipset-5/ip_set_bitmap.h @@ -0,0 +1,31 @@ +#ifndef __IP_SET_BITMAP_H +#define __IP_SET_BITMAP_H + +/* Bitmap type specific error codes */ +enum { + /* The element is out of the range of the set */ + IPSET_ERR_BITMAP_RANGE = IPSET_ERR_TYPE_SPECIFIC, + /* The range exceeds the size limit of the set type */ + IPSET_ERR_BITMAP_RANGE_SIZE, +}; + +#ifdef __KERNEL__ +#define IPSET_BITMAP_MAX_RANGE 0x0000FFFF + +/* Common functions */ + +static inline u32 +range_to_mask(u32 from, u32 to, u8 *bits) +{ + u32 mask = 0xFFFFFFFE; + + *bits = 32; + while (--(*bits) > 0 && mask && (to & mask) != from) + mask <<= 1; + + return mask; +} + +#endif /* __KERNEL__ */ + +#endif /* __IP_SET_BITMAP_H */ diff --git a/extensions/ipset-5/ip_set_bitmap_ip.c b/extensions/ipset-5/ip_set_bitmap_ip.c new file mode 100644 index 0000000..d94f0b4 --- /dev/null +++ b/extensions/ipset-5/ip_set_bitmap_ip.c @@ -0,0 +1,727 @@ +/* Copyright (C) 2000-2002 Joakim Axelsson + * Patrick Schaaf + * 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 set type: the bitmap:ip type */ + +#include "ip_set_kernel.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ip_set.h" +#include "ip_set_bitmap.h" +#define IP_SET_BITMAP_TIMEOUT +#include "ip_set_timeout.h" + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Jozsef Kadlecsik "); +MODULE_DESCRIPTION("bitmap:ip type of IP sets"); +MODULE_ALIAS("ip_set_bitmap:ip"); + +/* Base variant */ + +struct bitmap_ip { + void *members; /* the set members */ + u32 first_ip; /* host byte order, included in range */ + u32 last_ip; /* host byte order, included in range */ + u32 elements; /* number of max elements in the set */ + u32 hosts; /* number of hosts in a subnet */ + size_t memsize; /* members size */ + u8 netmask; /* subnet netmask */ +}; + +static inline u32 +ip_to_id(const struct bitmap_ip *map, u32 ip) +{ + return ((ip & HOSTMASK(map->netmask)) - map->first_ip)/map->hosts; +} + +static inline int +bitmap_ip_test(const struct bitmap_ip *map, u32 id) +{ + return !!test_bit(id, map->members); +} + +static inline int +bitmap_ip_add(struct bitmap_ip *map, u32 id) +{ + if (test_and_set_bit(id, map->members)) + return -IPSET_ERR_EXIST; + + return 0; +} + +static inline int +bitmap_ip_del(struct bitmap_ip *map, u32 id) +{ + if (!test_and_clear_bit(id, map->members)) + return -IPSET_ERR_EXIST; + + return 0; +} + +static int +bitmap_ip_kadt(struct ip_set *set, const struct sk_buff *skb, + enum ipset_adt adt, u8 pf, u8 dim, u8 flags) +{ + struct bitmap_ip *map = set->data; + u32 ip; + + ip = ntohl(ip4addr(skb, flags & IPSET_DIM_ONE_SRC)); + if (ip < map->first_ip || ip > map->last_ip) + return -IPSET_ERR_BITMAP_RANGE; + + ip = ip_to_id(map, ip); + + switch (adt) { + case IPSET_TEST: + return bitmap_ip_test(map, ip); + case IPSET_ADD: + return bitmap_ip_add(map, ip); + case IPSET_DEL: + return bitmap_ip_del(map, ip); + default: + return -EINVAL; + } +} + +static const struct nla_policy bitmap_ip_adt_policy[IPSET_ATTR_ADT_MAX+1] = { + [IPSET_ATTR_IP] = { .type = NLA_NESTED }, + [IPSET_ATTR_IP_TO] = { .type = NLA_NESTED }, + [IPSET_ATTR_CIDR] = { .type = NLA_U8 }, + [IPSET_ATTR_TIMEOUT] = { .type = NLA_U32 }, + [IPSET_ATTR_LINENO] = { .type = NLA_U32 }, +}; + +static int +bitmap_ip_uadt(struct ip_set *set, struct nlattr *head, int len, + enum ipset_adt adt, u32 *lineno, u32 flags) +{ + struct bitmap_ip *map = set->data; + struct nlattr *tb[IPSET_ATTR_ADT_MAX+1]; + u32 ip, ip_to, id; + int ret = 0; + + if (nla_parse(tb, IPSET_ATTR_ADT_MAX, head, len, + bitmap_ip_adt_policy)) + return -IPSET_ERR_PROTOCOL; + + if (tb[IPSET_ATTR_LINENO]) + *lineno = nla_get_u32(tb[IPSET_ATTR_LINENO]); + + ret = ip_set_get_ipaddr4(tb, IPSET_ATTR_IP, &ip); + if (ret) + return ret; + ip = ntohl(ip); + + if (ip < map->first_ip || ip > map->last_ip) + return -IPSET_ERR_BITMAP_RANGE; + + /* Set was defined without timeout support: + * don't ignore the attribute silently */ + if (tb[IPSET_ATTR_TIMEOUT]) + return -IPSET_ERR_TIMEOUT; + + if (adt == IPSET_TEST) + return bitmap_ip_test(map, ip_to_id(map, ip)); + + if (tb[IPSET_ATTR_IP_TO]) { + ret = ip_set_get_ipaddr4(tb, IPSET_ATTR_IP_TO, &ip_to); + if (ret) + return ret; + ip_to = ntohl(ip_to); + if (ip > ip_to) { + swap(ip, ip_to); + if (ip < map->first_ip) + return -IPSET_ERR_BITMAP_RANGE; + } + } else if (tb[IPSET_ATTR_CIDR]) { + u8 cidr = nla_get_u8(tb[IPSET_ATTR_CIDR]); + + if (cidr > 32) + return -IPSET_ERR_INVALID_CIDR; + ip &= HOSTMASK(cidr); + ip_to = ip | ~HOSTMASK(cidr); + } else + ip_to = ip; + + if (ip_to > map->last_ip) + return -IPSET_ERR_BITMAP_RANGE; + + for (; !before(ip_to, ip); ip += map->hosts) { + id = ip_to_id(map, ip); + ret = adt == IPSET_ADD ? bitmap_ip_add(map, id) + : bitmap_ip_del(map, id); + + if (ret && !ip_set_eexist(ret, flags)) + return ret; + else + ret = 0; + } + return ret; +} + +static void +bitmap_ip_destroy(struct ip_set *set) +{ + struct bitmap_ip *map = set->data; + + ip_set_free(map->members); + kfree(map); + + set->data = NULL; +} + +static void +bitmap_ip_flush(struct ip_set *set) +{ + struct bitmap_ip *map = set->data; + + memset(map->members, 0, map->memsize); +} + +static int +bitmap_ip_head(struct ip_set *set, struct sk_buff *skb) +{ + const struct bitmap_ip *map = set->data; + struct nlattr *nested; + + nested = ipset_nest_start(skb, IPSET_ATTR_DATA); + if (!nested) + goto nla_put_failure; + NLA_PUT_IPADDR4(skb, IPSET_ATTR_IP, htonl(map->first_ip)); + NLA_PUT_IPADDR4(skb, IPSET_ATTR_IP_TO, htonl(map->last_ip)); + if (map->netmask != 32) + NLA_PUT_U8(skb, IPSET_ATTR_NETMASK, map->netmask); + NLA_PUT_NET32(skb, IPSET_ATTR_REFERENCES, + htonl(atomic_read(&set->ref) - 1)); + NLA_PUT_NET32(skb, IPSET_ATTR_MEMSIZE, + htonl(sizeof(*map) + map->memsize)); + ipset_nest_end(skb, nested); + + return 0; +nla_put_failure: + return -EFAULT; +} + +static int +bitmap_ip_list(const struct ip_set *set, + struct sk_buff *skb, struct netlink_callback *cb) +{ + const struct bitmap_ip *map = set->data; + struct nlattr *atd, *nested; + u32 id, first = cb->args[2]; + + atd = ipset_nest_start(skb, IPSET_ATTR_ADT); + if (!atd) + return -EFAULT; + for (; cb->args[2] < map->elements; cb->args[2]++) { + id = cb->args[2]; + if (!bitmap_ip_test(map, id)) + continue; + nested = ipset_nest_start(skb, IPSET_ATTR_DATA); + if (!nested) { + if (id == first) { + nla_nest_cancel(skb, atd); + return -EFAULT; + } else + goto nla_put_failure; + } + NLA_PUT_IPADDR4(skb, IPSET_ATTR_IP, + htonl(map->first_ip + id * map->hosts)); + ipset_nest_end(skb, nested); + } + ipset_nest_end(skb, atd); + /* Set listing finished */ + cb->args[2] = 0; + return 0; + +nla_put_failure: + nla_nest_cancel(skb, nested); + ipset_nest_end(skb, atd); + return 0; +} + +static bool +bitmap_ip_same_set(const struct ip_set *a, const struct ip_set *b) +{ + const struct bitmap_ip *x = a->data; + const struct bitmap_ip *y = b->data; + + return x->first_ip == y->first_ip + && x->last_ip == y->last_ip + && x->netmask == y->netmask; +} + +static const struct ip_set_type_variant bitmap_ip = { + .kadt = bitmap_ip_kadt, + .uadt = bitmap_ip_uadt, + .destroy = bitmap_ip_destroy, + .flush = bitmap_ip_flush, + .head = bitmap_ip_head, + .list = bitmap_ip_list, + .same_set = bitmap_ip_same_set, +}; + +/* Timeout variant */ + +struct bitmap_ip_timeout { + unsigned long *members; /* the set members */ + u32 first_ip; /* host byte order, included in range */ + u32 last_ip; /* host byte order, included in range */ + u32 elements; /* number of max elements in the set */ + u32 hosts; /* number of hosts in a subnet */ + size_t memsize; /* members size */ + u8 netmask; /* subnet netmask */ + + u32 timeout; /* timeout parameter */ + struct timer_list gc; /* garbage collection */ +}; + +static inline bool +bitmap_ip_timeout_test(const struct bitmap_ip_timeout *map, u32 id) +{ + return ip_set_timeout_test(map->members[id]); +} + +static inline int +bitmap_ip_timeout_add(struct bitmap_ip_timeout *map, + u32 id, u32 timeout) +{ + if (bitmap_ip_timeout_test(map, id)) + return -IPSET_ERR_EXIST; + + map->members[id] = ip_set_timeout_set(timeout); + + return 0; +} + +static inline int +bitmap_ip_timeout_del(struct bitmap_ip_timeout *map, u32 id) +{ + int ret = -IPSET_ERR_EXIST; + + if (bitmap_ip_timeout_test(map, id)) + ret = 0; + + map->members[id] = IPSET_ELEM_UNSET; + return ret; +} + +static int +bitmap_ip_timeout_kadt(struct ip_set *set, const struct sk_buff *skb, + enum ipset_adt adt, u8 pf, u8 dim, u8 flags) +{ + struct bitmap_ip_timeout *map = set->data; + u32 ip; + + ip = ntohl(ip4addr(skb, flags & IPSET_DIM_ONE_SRC)); + if (ip < map->first_ip || ip > map->last_ip) + return -IPSET_ERR_BITMAP_RANGE; + + ip = ip_to_id((const struct bitmap_ip *)map, ip); + + switch (adt) { + case IPSET_TEST: + return bitmap_ip_timeout_test(map, ip); + case IPSET_ADD: + return bitmap_ip_timeout_add(map, ip, map->timeout); + case IPSET_DEL: + return bitmap_ip_timeout_del(map, ip); + default: + return -EINVAL; + } +} + +static int +bitmap_ip_timeout_uadt(struct ip_set *set, struct nlattr *head, int len, + enum ipset_adt adt, u32 *lineno, u32 flags) +{ + struct bitmap_ip_timeout *map = set->data; + struct nlattr *tb[IPSET_ATTR_ADT_MAX+1]; + u32 ip, ip_to, id, timeout = map->timeout; + int ret = 0; + + if (nla_parse(tb, IPSET_ATTR_ADT_MAX, head, len, + bitmap_ip_adt_policy)) + return -IPSET_ERR_PROTOCOL; + + if (tb[IPSET_ATTR_LINENO]) + *lineno = nla_get_u32(tb[IPSET_ATTR_LINENO]); + + ret = ip_set_get_ipaddr4(tb, IPSET_ATTR_IP, &ip); + if (ret) + return ret; + ip = ntohl(ip); + + if (ip < map->first_ip || ip > map->last_ip) + return -IPSET_ERR_BITMAP_RANGE; + + if (adt == IPSET_TEST) + return bitmap_ip_timeout_test(map, + ip_to_id((const struct bitmap_ip *)map, ip)); + + if (tb[IPSET_ATTR_IP_TO]) { + ret = ip_set_get_ipaddr4(tb, IPSET_ATTR_IP_TO, &ip_to); + if (ret) + return ret; + ip_to = ntohl(ip_to); + if (ip > ip_to) { + swap(ip, ip_to); + if (ip < map->first_ip) + return -IPSET_ERR_BITMAP_RANGE; + } + } else if (tb[IPSET_ATTR_CIDR]) { + u8 cidr = nla_get_u8(tb[IPSET_ATTR_CIDR]); + + if (cidr > 32) + return -IPSET_ERR_INVALID_CIDR; + ip &= HOSTMASK(cidr); + ip_to = ip | ~HOSTMASK(cidr); + } else + ip_to = ip; + + if (ip_to > map->last_ip) + return -IPSET_ERR_BITMAP_RANGE; + + if (tb[IPSET_ATTR_TIMEOUT]) + timeout = ip_set_timeout_uget(tb[IPSET_ATTR_TIMEOUT]); + + for (; !before(ip_to, ip); ip += map->hosts) { + id = ip_to_id((const struct bitmap_ip *)map, ip); + ret = adt == IPSET_ADD + ? bitmap_ip_timeout_add(map, id, timeout) + : bitmap_ip_timeout_del(map, id); + + if (ret && !ip_set_eexist(ret, flags)) + return ret; + else + ret = 0; + } + return ret; +} + +static void +bitmap_ip_timeout_destroy(struct ip_set *set) +{ + struct bitmap_ip_timeout *map = set->data; + + del_timer_sync(&map->gc); + ip_set_free(map->members); + kfree(map); + + set->data = NULL; +} + +static void +bitmap_ip_timeout_flush(struct ip_set *set) +{ + struct bitmap_ip_timeout *map = set->data; + + memset(map->members, IPSET_ELEM_UNSET, map->memsize); +} + +static int +bitmap_ip_timeout_head(struct ip_set *set, struct sk_buff *skb) +{ + const struct bitmap_ip_timeout *map = set->data; + struct nlattr *nested; + + nested = ipset_nest_start(skb, IPSET_ATTR_DATA); + if (!nested) + goto nla_put_failure; + NLA_PUT_IPADDR4(skb, IPSET_ATTR_IP, htonl(map->first_ip)); + NLA_PUT_IPADDR4(skb, IPSET_ATTR_IP_TO, htonl(map->last_ip)); + if (map->netmask != 32) + NLA_PUT_U8(skb, IPSET_ATTR_NETMASK, map->netmask); + NLA_PUT_NET32(skb, IPSET_ATTR_TIMEOUT, htonl(map->timeout)); + NLA_PUT_NET32(skb, IPSET_ATTR_REFERENCES, + htonl(atomic_read(&set->ref) - 1)); + NLA_PUT_NET32(skb, IPSET_ATTR_MEMSIZE, + htonl(sizeof(*map) + map->memsize)); + ipset_nest_end(skb, nested); + + return 0; +nla_put_failure: + return -EFAULT; +} + +static int +bitmap_ip_timeout_list(const struct ip_set *set, + struct sk_buff *skb, struct netlink_callback *cb) +{ + const struct bitmap_ip_timeout *map = set->data; + struct nlattr *adt, *nested; + u32 id, first = cb->args[2]; + const unsigned long *table = map->members; + + adt = ipset_nest_start(skb, IPSET_ATTR_ADT); + if (!adt) + return -EFAULT; + for (; cb->args[2] < map->elements; cb->args[2]++) { + id = cb->args[2]; + if (!bitmap_ip_timeout_test(map, id)) + continue; + nested = ipset_nest_start(skb, IPSET_ATTR_DATA); + if (!nested) { + if (id == first) { + nla_nest_cancel(skb, adt); + return -EFAULT; + } else + goto nla_put_failure; + } + NLA_PUT_IPADDR4(skb, IPSET_ATTR_IP, + htonl(map->first_ip + id * map->hosts)); + NLA_PUT_NET32(skb, IPSET_ATTR_TIMEOUT, + htonl(ip_set_timeout_get(table[id]))); + ipset_nest_end(skb, nested); + } + ipset_nest_end(skb, adt); + + /* Set listing finished */ + cb->args[2] = 0; + + return 0; + +nla_put_failure: + nla_nest_cancel(skb, nested); + ipset_nest_end(skb, adt); + return 0; +} + +static bool +bitmap_ip_timeout_same_set(const struct ip_set *a, const struct ip_set *b) +{ + const struct bitmap_ip_timeout *x = a->data; + const struct bitmap_ip_timeout *y = b->data; + + return x->first_ip == y->first_ip + && x->last_ip == y->last_ip + && x->netmask == y->netmask + && x->timeout == y->timeout; +} + +static const struct ip_set_type_variant bitmap_ip_timeout = { + .kadt = bitmap_ip_timeout_kadt, + .uadt = bitmap_ip_timeout_uadt, + .destroy = bitmap_ip_timeout_destroy, + .flush = bitmap_ip_timeout_flush, + .head = bitmap_ip_timeout_head, + .list = bitmap_ip_timeout_list, + .same_set = bitmap_ip_timeout_same_set, +}; + +static void +bitmap_ip_gc(unsigned long ul_set) +{ + struct ip_set *set = (struct ip_set *) ul_set; + struct bitmap_ip_timeout *map = set->data; + unsigned long *table = map->members; + u32 id; + + /* We run parallel with other readers (test element) + * but adding/deleting new entries is locked out */ + read_lock_bh(&set->lock); + for (id = 0; id < map->elements; id++) + if (ip_set_timeout_expired(table[id])) + table[id] = IPSET_ELEM_UNSET; + read_unlock_bh(&set->lock); + + map->gc.expires = jiffies + IPSET_GC_PERIOD(map->timeout) * HZ; + add_timer(&map->gc); +} + +static inline void +bitmap_ip_gc_init(struct ip_set *set) +{ + struct bitmap_ip_timeout *map = set->data; + + init_timer(&map->gc); + map->gc.data = (unsigned long) set; + map->gc.function = bitmap_ip_gc; + map->gc.expires = jiffies + IPSET_GC_PERIOD(map->timeout) * HZ; + add_timer(&map->gc); +} + +/* Create bitmap:ip type of sets */ + +static const struct nla_policy +bitmap_ip_create_policy[IPSET_ATTR_CREATE_MAX+1] = { + [IPSET_ATTR_IP] = { .type = NLA_NESTED }, + [IPSET_ATTR_IP_TO] = { .type = NLA_NESTED }, + [IPSET_ATTR_CIDR] = { .type = NLA_U8 }, + [IPSET_ATTR_NETMASK] = { .type = NLA_U8 }, + [IPSET_ATTR_TIMEOUT] = { .type = NLA_U32 }, +}; + +static bool +init_map_ip(struct ip_set *set, struct bitmap_ip *map, + u32 first_ip, u32 last_ip, + u32 elements, u32 hosts, u8 netmask) +{ + map->members = ip_set_alloc(map->memsize, GFP_KERNEL); + if (!map->members) + return false; + map->first_ip = first_ip; + map->last_ip = last_ip; + map->elements = elements; + map->hosts = hosts; + map->netmask = netmask; + + set->data = map; + set->family = AF_INET; + + return true; +} + +static int +bitmap_ip_create(struct ip_set *set, struct nlattr *head, int len, + u32 flags) +{ + struct nlattr *tb[IPSET_ATTR_CREATE_MAX+1]; + u32 first_ip, last_ip, hosts, elements; + u8 netmask = 32; + int ret; + + if (nla_parse(tb, IPSET_ATTR_CREATE_MAX, head, len, + bitmap_ip_create_policy)) + return -IPSET_ERR_PROTOCOL; + + ret = ip_set_get_ipaddr4(tb, IPSET_ATTR_IP, &first_ip); + if (ret) + return ret; + first_ip = ntohl(first_ip); + + if (tb[IPSET_ATTR_IP_TO]) { + ret = ip_set_get_ipaddr4(tb, IPSET_ATTR_IP_TO, &last_ip); + if (ret) + return ret; + last_ip = htonl(last_ip); + if (first_ip > last_ip) { + u32 tmp = first_ip; + + first_ip = last_ip; + last_ip = tmp; + } + } else if (tb[IPSET_ATTR_CIDR]) { + u8 cidr = nla_get_u8(tb[IPSET_ATTR_CIDR]); + + if (cidr >= 32) + return -IPSET_ERR_INVALID_CIDR; + last_ip = first_ip | ~HOSTMASK(cidr); + } else + return -IPSET_ERR_PROTOCOL; + + if (tb[IPSET_ATTR_NETMASK]) { + netmask = nla_get_u8(tb[IPSET_ATTR_NETMASK]); + + if (netmask > 32) + return -IPSET_ERR_INVALID_NETMASK; + + first_ip &= HOSTMASK(netmask); + last_ip |= ~HOSTMASK(netmask); + } + + if (netmask == 32) { + hosts = 1; + elements = last_ip - first_ip + 1; + } else { + u8 mask_bits; + u32 mask; + + mask = range_to_mask(first_ip, last_ip, &mask_bits); + + if ((!mask && (first_ip || last_ip != 0xFFFFFFFF)) + || netmask <= mask_bits) + return -IPSET_ERR_BITMAP_RANGE; + + pr_debug("mask_bits %u, netmask %u", mask_bits, netmask); + hosts = 2 << (32 - netmask - 1); + elements = 2 << (netmask - mask_bits - 1); + } + if (elements > IPSET_BITMAP_MAX_RANGE + 1) + return -IPSET_ERR_BITMAP_RANGE_SIZE; + + pr_debug("hosts %u, elements %u", hosts, elements); + + if (tb[IPSET_ATTR_TIMEOUT]) { + struct bitmap_ip_timeout *map; + + map = kzalloc(sizeof(*map), GFP_KERNEL); + if (!map) + return -ENOMEM; + + map->memsize = elements * sizeof(unsigned long); + + if (!init_map_ip(set, (struct bitmap_ip *)map, + first_ip, last_ip, + elements, hosts, netmask)) { + kfree(map); + return -ENOMEM; + } + + map->timeout = ip_set_timeout_uget(tb[IPSET_ATTR_TIMEOUT]); + set->variant = &bitmap_ip_timeout; + + bitmap_ip_gc_init(set); + } else { + struct bitmap_ip *map; + + map = kzalloc(sizeof(*map), GFP_KERNEL); + if (!map) + return -ENOMEM; + + map->memsize = bitmap_bytes(0, elements - 1); + + if (!init_map_ip(set, map, + first_ip, last_ip, + elements, hosts, netmask)) { + kfree(map); + return -ENOMEM; + } + + set->variant = &bitmap_ip; + } + return 0; +} + +static struct ip_set_type bitmap_ip_type __read_mostly = { + .name = "bitmap:ip", + .protocol = IPSET_PROTOCOL, + .features = IPSET_TYPE_IP, + .dimension = IPSET_DIM_ONE, + .family = AF_INET, + .revision = 0, + .create = bitmap_ip_create, + .me = THIS_MODULE, +}; + +static int __init +bitmap_ip_init(void) +{ + return ip_set_type_register(&bitmap_ip_type); +} + +static void __exit +bitmap_ip_fini(void) +{ + ip_set_type_unregister(&bitmap_ip_type); +} + +module_init(bitmap_ip_init); +module_exit(bitmap_ip_fini); diff --git a/extensions/ipset-5/ip_set_bitmap_ipmac.c b/extensions/ipset-5/ip_set_bitmap_ipmac.c new file mode 100644 index 0000000..6b8f80c --- /dev/null +++ b/extensions/ipset-5/ip_set_bitmap_ipmac.c @@ -0,0 +1,660 @@ +/* Copyright (C) 2000-2002 Joakim Axelsson + * Patrick Schaaf + * Martin Josefsson + * 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 set type: the bitmap:ip,mac type */ + +#include "ip_set_kernel.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ip_set.h" +#include "ip_set_timeout.h" +#include "ip_set_bitmap.h" + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Jozsef Kadlecsik "); +MODULE_DESCRIPTION("bitmap:ip,mac type of IP sets"); +MODULE_ALIAS("ip_set_bitmap:ip,mac"); + +enum { + MAC_EMPTY, /* element is not set */ + MAC_FILLED, /* element is set with MAC */ + MAC_UNSET, /* element is set, without MAC */ +}; + +/* Type structure */ +struct bitmap_ipmac { + void *members; /* the set members */ + u32 first_ip; /* host byte order, included in range */ + u32 last_ip; /* host byte order, included in range */ + u32 timeout; /* timeout value */ + struct timer_list gc; /* garbage collector */ + size_t dsize; /* size of element */ +}; + +/* ADT structure for generic function args */ +struct ipmac { + u32 id; /* id in array */ + unsigned char *ether; /* ethernet address */ +}; + +/* Member element without and with timeout */ + +struct ipmac_elem { + unsigned char ether[ETH_ALEN]; + unsigned char match; +} __attribute__ ((aligned)); + +struct ipmac_telem { + unsigned char ether[ETH_ALEN]; + unsigned char match; + unsigned long timeout; +} __attribute__ ((aligned)); + +static inline void * +bitmap_ipmac_elem(const struct bitmap_ipmac *map, u32 id) +{ + return (void *)((char *)map->members + id * map->dsize); +} + +static inline bool +bitmap_timeout(const struct bitmap_ipmac *map, u32 id) +{ + const struct ipmac_telem *elem = bitmap_ipmac_elem(map, id); + + return ip_set_timeout_test(elem->timeout); +} + +static inline bool +bitmap_expired(const struct bitmap_ipmac *map, u32 id) +{ + const struct ipmac_telem *elem = bitmap_ipmac_elem(map, id); + + return ip_set_timeout_expired(elem->timeout); +} + +static inline int +bitmap_ipmac_exist(const struct ipmac_telem *elem) +{ + return elem->match == MAC_UNSET + || (elem->match == MAC_FILLED + && !ip_set_timeout_expired(elem->timeout)); +} + +/* Base variant */ + +static int +bitmap_ipmac_test(struct ip_set *set, void *value, u32 timeout) +{ + const struct bitmap_ipmac *map = set->data; + const struct ipmac *data = value; + const struct ipmac_elem *elem = bitmap_ipmac_elem(map, data->id); + + switch (elem->match) { + case MAC_UNSET: + /* Trigger kernel to fill out the ethernet address */ + return -EAGAIN; + case MAC_FILLED: + return data->ether == NULL + || compare_ether_addr(data->ether, elem->ether) == 0; + } + return 0; +} + +static int +bitmap_ipmac_add(struct ip_set *set, void *value, u32 timeout) +{ + struct bitmap_ipmac *map = set->data; + const struct ipmac *data = value; + struct ipmac_elem *elem = bitmap_ipmac_elem(map, data->id); + + switch (elem->match) { + case MAC_UNSET: + if (!data->ether) + /* Already added without ethernet address */ + return -IPSET_ERR_EXIST; + /* Fill the MAC address */ + memcpy(elem->ether, data->ether, ETH_ALEN); + elem->match = MAC_FILLED; + break; + case MAC_FILLED: + return -IPSET_ERR_EXIST; + case MAC_EMPTY: + if (data->ether) { + memcpy(elem->ether, data->ether, ETH_ALEN); + elem->match = MAC_FILLED; + } else + elem->match = MAC_UNSET; + } + + return 0; +} + +static int +bitmap_ipmac_del(struct ip_set *set, void *value, u32 timeout) +{ + struct bitmap_ipmac *map = set->data; + const struct ipmac *data = value; + struct ipmac_elem *elem = bitmap_ipmac_elem(map, data->id); + + if (elem->match == MAC_EMPTY) + return -IPSET_ERR_EXIST; + + elem->match = MAC_EMPTY; + + return 0; +} + +static int +bitmap_ipmac_list(const struct ip_set *set, + struct sk_buff *skb, struct netlink_callback *cb) +{ + const struct bitmap_ipmac *map = set->data; + const struct ipmac_elem *elem; + struct nlattr *atd, *nested; + u32 id, first = cb->args[2]; + u32 last = map->last_ip - map->first_ip; + + atd = ipset_nest_start(skb, IPSET_ATTR_ADT); + if (!atd) + return -EFAULT; + for (; cb->args[2] <= last; cb->args[2]++) { + id = cb->args[2]; + elem = bitmap_ipmac_elem(map, id); + if (elem->match == MAC_EMPTY) + continue; + nested = ipset_nest_start(skb, IPSET_ATTR_DATA); + if (!nested) { + if (id == first) { + nla_nest_cancel(skb, atd); + return -EFAULT; + } else + goto nla_put_failure; + } + NLA_PUT_IPADDR4(skb, IPSET_ATTR_IP, + htonl(map->first_ip + id)); + if (elem->match == MAC_FILLED) + NLA_PUT(skb, IPSET_ATTR_ETHER, ETH_ALEN, + elem->ether); + ipset_nest_end(skb, nested); + } + ipset_nest_end(skb, atd); + /* Set listing finished */ + cb->args[2] = 0; + + return 0; + +nla_put_failure: + nla_nest_cancel(skb, nested); + ipset_nest_end(skb, atd); + return 0; +} + +/* Timeout variant */ + +static int +bitmap_ipmac_ttest(struct ip_set *set, void *value, u32 timeout) +{ + const struct bitmap_ipmac *map = set->data; + const struct ipmac *data = value; + const struct ipmac_elem *elem = bitmap_ipmac_elem(map, data->id); + + switch (elem->match) { + case MAC_UNSET: + /* Trigger kernel to fill out the ethernet address */ + return -EAGAIN; + case MAC_FILLED: + return (data->ether == NULL + || compare_ether_addr(data->ether, elem->ether) == 0) + && !bitmap_expired(map, data->id); + } + return 0; +} + +static int +bitmap_ipmac_tadd(struct ip_set *set, void *value, u32 timeout) +{ + struct bitmap_ipmac *map = set->data; + const struct ipmac *data = value; + struct ipmac_telem *elem = bitmap_ipmac_elem(map, data->id); + + switch (elem->match) { + case MAC_UNSET: + if (!data->ether) + /* Already added without ethernet address */ + return -IPSET_ERR_EXIST; + /* Fill the MAC address and activate the timer */ + memcpy(elem->ether, data->ether, ETH_ALEN); + elem->match = MAC_FILLED; + if (timeout == map->timeout) + /* Timeout was not specified, get stored one */ + timeout = elem->timeout; + elem->timeout = ip_set_timeout_set(timeout); + break; + case MAC_FILLED: + if (!bitmap_expired(map, data->id)) + return -IPSET_ERR_EXIST; + /* Fall through */ + case MAC_EMPTY: + if (data->ether) { + memcpy(elem->ether, data->ether, ETH_ALEN); + elem->match = MAC_FILLED; + } else + elem->match = MAC_UNSET; + /* If MAC is unset yet, we store plain timeout value + * because the timer is not activated yet + * and we can reuse it later when MAC is filled out, + * possibly by the kernel */ + elem->timeout = data->ether ? ip_set_timeout_set(timeout) + : timeout; + break; + } + + return 0; +} + +static int +bitmap_ipmac_tdel(struct ip_set *set, void *value, u32 timeout) +{ + struct bitmap_ipmac *map = set->data; + const struct ipmac *data = value; + struct ipmac_telem *elem = bitmap_ipmac_elem(map, data->id); + + if (elem->match == MAC_EMPTY || bitmap_expired(map, data->id)) + return -IPSET_ERR_EXIST; + + elem->match = MAC_EMPTY; + + return 0; +} + +static int +bitmap_ipmac_tlist(const struct ip_set *set, + struct sk_buff *skb, struct netlink_callback *cb) +{ + const struct bitmap_ipmac *map = set->data; + const struct ipmac_telem *elem; + struct nlattr *atd, *nested; + u32 id, first = cb->args[2]; + u32 timeout, last = map->last_ip - map->first_ip; + + atd = ipset_nest_start(skb, IPSET_ATTR_ADT); + if (!atd) + return -EFAULT; + for (; cb->args[2] <= last; cb->args[2]++) { + id = cb->args[2]; + elem = bitmap_ipmac_elem(map, id); + if (!bitmap_ipmac_exist(elem)) + continue; + nested = ipset_nest_start(skb, IPSET_ATTR_DATA); + if (!nested) { + if (id == first) { + nla_nest_cancel(skb, atd); + return -EFAULT; + } else + goto nla_put_failure; + } + NLA_PUT_IPADDR4(skb, IPSET_ATTR_IP, + htonl(map->first_ip + id)); + if (elem->match == MAC_FILLED) + NLA_PUT(skb, IPSET_ATTR_ETHER, ETH_ALEN, + elem->ether); + timeout = elem->match == MAC_UNSET ? elem->timeout + : ip_set_timeout_get(elem->timeout); + NLA_PUT_NET32(skb, IPSET_ATTR_TIMEOUT, htonl(timeout)); + ipset_nest_end(skb, nested); + } + ipset_nest_end(skb, atd); + /* Set listing finished */ + cb->args[2] = 0; + + return 0; + +nla_put_failure: + nla_nest_cancel(skb, nested); + ipset_nest_end(skb, atd); + return 0; +} + +static int +bitmap_ipmac_kadt(struct ip_set *set, const struct sk_buff *skb, + enum ipset_adt adt, u8 pf, u8 dim, u8 flags) +{ + struct bitmap_ipmac *map = set->data; + ipset_adtfn adtfn = set->variant->adt[adt]; + struct ipmac data; + + data.id = ntohl(ip4addr(skb, flags & IPSET_DIM_ONE_SRC)); + if (data.id < map->first_ip || data.id > map->last_ip) + return -IPSET_ERR_BITMAP_RANGE; + + /* Backward compatibility: we don't check the second flag */ + if (skb_mac_header(skb) < skb->head + || (skb_mac_header(skb) + ETH_HLEN) > skb->data) + return -EINVAL; + + data.id -= map->first_ip; + data.ether = eth_hdr(skb)->h_source; + + return adtfn(set, &data, map->timeout); +} + +static const struct nla_policy +bitmap_ipmac_adt_policy[IPSET_ATTR_ADT_MAX + 1] = { + [IPSET_ATTR_IP] = { .type = NLA_NESTED }, + [IPSET_ATTR_ETHER] = { .type = NLA_BINARY, .len = ETH_ALEN }, + [IPSET_ATTR_TIMEOUT] = { .type = NLA_U32 }, + [IPSET_ATTR_LINENO] = { .type = NLA_U32 }, +}; + +static int +bitmap_ipmac_uadt(struct ip_set *set, struct nlattr *head, int len, + enum ipset_adt adt, u32 *lineno, u32 flags) +{ + const struct bitmap_ipmac *map = set->data; + struct nlattr *tb[IPSET_ATTR_ADT_MAX+1]; + ipset_adtfn adtfn = set->variant->adt[adt]; + struct ipmac data; + u32 timeout = map->timeout; + int ret = 0; + + if (nla_parse(tb, IPSET_ATTR_ADT_MAX, head, len, + bitmap_ipmac_adt_policy)) + return -IPSET_ERR_PROTOCOL; + + if (tb[IPSET_ATTR_LINENO]) + *lineno = nla_get_u32(tb[IPSET_ATTR_LINENO]); + + ret = ip_set_get_ipaddr4(tb, IPSET_ATTR_IP, &data.id); + if (ret) + return ret; + data.id = ntohl(data.id); + + if (data.id < map->first_ip || data.id > map->last_ip) + return -IPSET_ERR_BITMAP_RANGE; + + if (tb[IPSET_ATTR_ETHER]) + data.ether = nla_data(tb[IPSET_ATTR_ETHER]); + else + data.ether = NULL; + + if (tb[IPSET_ATTR_TIMEOUT]) { + if (!with_timeout(map->timeout)) + return -IPSET_ERR_TIMEOUT; + timeout = ip_set_timeout_uget(tb[IPSET_ATTR_TIMEOUT]); + } + + data.id -= map->first_ip; + + ret = adtfn(set, &data, timeout); + + return ip_set_eexist(ret, flags) ? 0 : ret; +} + +static void +bitmap_ipmac_destroy(struct ip_set *set) +{ + struct bitmap_ipmac *map = set->data; + + if (with_timeout(map->timeout)) + del_timer_sync(&map->gc); + + ip_set_free(map->members); + kfree(map); + + set->data = NULL; +} + +static void +bitmap_ipmac_flush(struct ip_set *set) +{ + struct bitmap_ipmac *map = set->data; + + memset(map->members, 0, + (map->last_ip - map->first_ip + 1) * map->dsize); +} + +static int +bitmap_ipmac_head(struct ip_set *set, struct sk_buff *skb) +{ + const struct bitmap_ipmac *map = set->data; + struct nlattr *nested; + + nested = ipset_nest_start(skb, IPSET_ATTR_DATA); + if (!nested) + goto nla_put_failure; + NLA_PUT_IPADDR4(skb, IPSET_ATTR_IP, htonl(map->first_ip)); + NLA_PUT_IPADDR4(skb, IPSET_ATTR_IP_TO, htonl(map->last_ip)); + NLA_PUT_NET32(skb, IPSET_ATTR_REFERENCES, + htonl(atomic_read(&set->ref) - 1)); + NLA_PUT_NET32(skb, IPSET_ATTR_MEMSIZE, + htonl(sizeof(*map) + + (map->last_ip - map->first_ip + 1) * map->dsize)); + if (with_timeout(map->timeout)) + NLA_PUT_NET32(skb, IPSET_ATTR_TIMEOUT, htonl(map->timeout)); + ipset_nest_end(skb, nested); + + return 0; +nla_put_failure: + return -EFAULT; +} + +static bool +bitmap_ipmac_same_set(const struct ip_set *a, const struct ip_set *b) +{ + const struct bitmap_ipmac *x = a->data; + const struct bitmap_ipmac *y = b->data; + + return x->first_ip == y->first_ip + && x->last_ip == y->last_ip + && x->timeout == y->timeout; +} + +const struct ip_set_type_variant bitmap_ipmac = { + .kadt = bitmap_ipmac_kadt, + .uadt = bitmap_ipmac_uadt, + .adt = { + [IPSET_ADD] = bitmap_ipmac_add, + [IPSET_DEL] = bitmap_ipmac_del, + [IPSET_TEST] = bitmap_ipmac_test, + }, + .destroy = bitmap_ipmac_destroy, + .flush = bitmap_ipmac_flush, + .head = bitmap_ipmac_head, + .list = bitmap_ipmac_list, + .same_set = bitmap_ipmac_same_set, +}; + +const struct ip_set_type_variant bitmap_tipmac = { + .kadt = bitmap_ipmac_kadt, + .uadt = bitmap_ipmac_uadt, + .adt = { + [IPSET_ADD] = bitmap_ipmac_tadd, + [IPSET_DEL] = bitmap_ipmac_tdel, + [IPSET_TEST] = bitmap_ipmac_ttest, + }, + .destroy = bitmap_ipmac_destroy, + .flush = bitmap_ipmac_flush, + .head = bitmap_ipmac_head, + .list = bitmap_ipmac_tlist, + .same_set = bitmap_ipmac_same_set, +}; + +static void +bitmap_ipmac_gc(unsigned long ul_set) +{ + struct ip_set *set = (struct ip_set *) ul_set; + struct bitmap_ipmac *map = set->data; + struct ipmac_telem *elem; + u32 id, last = map->last_ip - map->first_ip; + + /* We run parallel with other readers (test element) + * but adding/deleting new entries is locked out */ + read_lock_bh(&set->lock); + for (id = 0; id <= last; id++) { + elem = bitmap_ipmac_elem(map, id); + if (elem->match == MAC_FILLED + && ip_set_timeout_expired(elem->timeout)) + elem->match = MAC_EMPTY; + } + read_unlock_bh(&set->lock); + + map->gc.expires = jiffies + IPSET_GC_PERIOD(map->timeout) * HZ; + add_timer(&map->gc); +} + +static inline void +bitmap_ipmac_gc_init(struct ip_set *set) +{ + struct bitmap_ipmac *map = set->data; + + init_timer(&map->gc); + map->gc.data = (unsigned long) set; + map->gc.function = bitmap_ipmac_gc; + map->gc.expires = jiffies + IPSET_GC_PERIOD(map->timeout) * HZ; + add_timer(&map->gc); +} + +/* Create bitmap:ip,mac type of sets */ + +static const struct nla_policy +bitmap_ipmac_create_policy[IPSET_ATTR_CREATE_MAX+1] = { + [IPSET_ATTR_IP] = { .type = NLA_NESTED }, + [IPSET_ATTR_IP_TO] = { .type = NLA_NESTED }, + [IPSET_ATTR_TIMEOUT] = { .type = NLA_U32 }, +}; + +static bool +init_map_ipmac(struct ip_set *set, struct bitmap_ipmac *map, + u32 first_ip, u32 last_ip) +{ + map->members = ip_set_alloc((last_ip - first_ip + 1) * map->dsize, + GFP_KERNEL); + if (!map->members) + return false; + map->first_ip = first_ip; + map->last_ip = last_ip; + map->timeout = IPSET_NO_TIMEOUT; + + set->data = map; + set->family = AF_INET; + + return true; +} + +static int +bitmap_ipmac_create(struct ip_set *set, struct nlattr *head, int len, + u32 flags) +{ + struct nlattr *tb[IPSET_ATTR_CREATE_MAX+1]; + u32 first_ip, last_ip, elements; + struct bitmap_ipmac *map; + int ret; + + if (nla_parse(tb, IPSET_ATTR_CREATE_MAX, head, len, + bitmap_ipmac_create_policy)) + return -IPSET_ERR_PROTOCOL; + + ret = ip_set_get_ipaddr4(tb, IPSET_ATTR_IP, &first_ip); + if (ret) + return ret; + first_ip = ntohl(first_ip); + + if (tb[IPSET_ATTR_IP_TO]) { + ret = ip_set_get_ipaddr4(tb, IPSET_ATTR_IP_TO, &last_ip); + if (ret) + return ret; + last_ip = ntohl(last_ip); + if (first_ip > last_ip) { + u32 tmp = first_ip; + + first_ip = last_ip; + last_ip = tmp; + } + } else if (tb[IPSET_ATTR_CIDR]) { + u8 cidr = nla_get_u8(tb[IPSET_ATTR_CIDR]); + + if (cidr >= 32) + return -IPSET_ERR_INVALID_CIDR; + last_ip = first_ip | ~HOSTMASK(cidr); + } else + return -IPSET_ERR_PROTOCOL; + + elements = last_ip - first_ip + 1; + + if (elements > IPSET_BITMAP_MAX_RANGE + 1) + return -IPSET_ERR_BITMAP_RANGE_SIZE; + + map = kzalloc(sizeof(*map), GFP_KERNEL); + if (!map) + return -ENOMEM; + + if (tb[IPSET_ATTR_TIMEOUT]) { + map->dsize = sizeof(struct ipmac_telem); + + if (!init_map_ipmac(set, map, first_ip, last_ip)) { + kfree(map); + return -ENOMEM; + } + + map->timeout = ip_set_timeout_uget(tb[IPSET_ATTR_TIMEOUT]); + + set->variant = &bitmap_tipmac; + + bitmap_ipmac_gc_init(set); + } else { + map->dsize = sizeof(struct ipmac_elem); + + if (!init_map_ipmac(set, map, first_ip, last_ip)) { + kfree(map); + return -ENOMEM; + } + set->variant = &bitmap_ipmac; + + } + return 0; +} + +struct ip_set_type bitmap_ipmac_type = { + .name = "bitmap:ip,mac", + .protocol = IPSET_PROTOCOL, + .features = IPSET_TYPE_IP | IPSET_TYPE_MAC, + .dimension = IPSET_DIM_TWO, + .family = AF_INET, + .revision = 0, + .create = bitmap_ipmac_create, + .me = THIS_MODULE, +}; + +static int __init +bitmap_ipmac_init(void) +{ + return ip_set_type_register(&bitmap_ipmac_type); +} + +static void __exit +bitmap_ipmac_fini(void) +{ + ip_set_type_unregister(&bitmap_ipmac_type); +} + +module_init(bitmap_ipmac_init); +module_exit(bitmap_ipmac_fini); diff --git a/extensions/ipset-5/ip_set_bitmap_port.c b/extensions/ipset-5/ip_set_bitmap_port.c new file mode 100644 index 0000000..b493a5b --- /dev/null +++ b/extensions/ipset-5/ip_set_bitmap_port.c @@ -0,0 +1,649 @@ +/* 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 set type: the bitmap:port type */ + +#include "ip_set_kernel.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ip_set.h" +#include "ip_set_bitmap.h" +#include "ip_set_getport.h" +#define IP_SET_BITMAP_TIMEOUT +#include "ip_set_timeout.h" + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Jozsef Kadlecsik "); +MODULE_DESCRIPTION("bitmap:port type of IP sets"); +MODULE_ALIAS("ip_set_bitmap:port"); + +/* Base variant */ + +struct bitmap_port { + void *members; /* the set members */ + u16 first_port; /* host byte order, included in range */ + u16 last_port; /* host byte order, included in range */ + size_t memsize; /* members size */ +}; + +static inline int +bitmap_port_test(const struct bitmap_port *map, u16 id) +{ + return !!test_bit(id, map->members); +} + +static inline int +bitmap_port_add(struct bitmap_port *map, u16 id) +{ + if (test_and_set_bit(id, map->members)) + return -IPSET_ERR_EXIST; + + return 0; +} + +static int +bitmap_port_del(struct bitmap_port *map, u16 id) +{ + if (!test_and_clear_bit(id, map->members)) + return -IPSET_ERR_EXIST; + + return 0; +} + +static int +bitmap_port_kadt(struct ip_set *set, const struct sk_buff *skb, + enum ipset_adt adt, u8 pf, u8 dim, u8 flags) +{ + struct bitmap_port *map = set->data; + u16 port = 0; + + if (!get_ip_port(skb, pf, flags & IPSET_DIM_ONE_SRC, &port)) + return -EINVAL; + + port = ntohs(port); + + if (port < map->first_port || port > map->last_port) + return -IPSET_ERR_BITMAP_RANGE; + + port -= map->first_port; + + switch (adt) { + case IPSET_TEST: + return bitmap_port_test(map, port); + case IPSET_ADD: + return bitmap_port_add(map, port); + case IPSET_DEL: + return bitmap_port_del(map, port); + default: + return -EINVAL; + } +} + +static const struct nla_policy bitmap_port_adt_policy[IPSET_ATTR_ADT_MAX+1] = { + [IPSET_ATTR_PORT] = { .type = NLA_U16 }, + [IPSET_ATTR_PORT_TO] = { .type = NLA_U16 }, + [IPSET_ATTR_TIMEOUT] = { .type = NLA_U32 }, + [IPSET_ATTR_LINENO] = { .type = NLA_U32 }, +}; + +static int +bitmap_port_uadt(struct ip_set *set, struct nlattr *head, int len, + enum ipset_adt adt, u32 *lineno, u32 flags) +{ + struct bitmap_port *map = set->data; + struct nlattr *tb[IPSET_ATTR_ADT_MAX+1]; + u32 port; /* wraparound */ + u16 id, port_to; + int ret = 0; + + if (nla_parse(tb, IPSET_ATTR_ADT_MAX, head, len, + bitmap_port_adt_policy)) + return -IPSET_ERR_PROTOCOL; + + if (tb[IPSET_ATTR_LINENO]) + *lineno = nla_get_u32(tb[IPSET_ATTR_LINENO]); + + if (tb[IPSET_ATTR_PORT]) + port = ip_set_get_h16(tb[IPSET_ATTR_PORT]); + else + return -IPSET_ERR_PROTOCOL; + + if (port < map->first_port || port > map->last_port) + return -IPSET_ERR_BITMAP_RANGE; + + if (tb[IPSET_ATTR_TIMEOUT]) + return -IPSET_ERR_TIMEOUT; + + if (adt == IPSET_TEST) + return bitmap_port_test(map, port - map->first_port); + + if (tb[IPSET_ATTR_PORT_TO]) { + port_to = ip_set_get_h16(tb[IPSET_ATTR_PORT_TO]); + if (port > port_to) { + swap(port, port_to); + if (port < map->first_port) + return -IPSET_ERR_BITMAP_RANGE; + } + } else + port_to = port; + + if (port_to > map->last_port) + return -IPSET_ERR_BITMAP_RANGE; + + for (; port <= port_to; port++) { + id = port - map->first_port; + ret = adt == IPSET_ADD ? bitmap_port_add(map, id) + : bitmap_port_del(map, id); + + if (ret && !ip_set_eexist(ret, flags)) + return ret; + else + ret = 0; + } + return ret; +} + +static void +bitmap_port_destroy(struct ip_set *set) +{ + struct bitmap_port *map = set->data; + + ip_set_free(map->members); + kfree(map); + + set->data = NULL; +} + +static void +bitmap_port_flush(struct ip_set *set) +{ + struct bitmap_port *map = set->data; + + memset(map->members, 0, map->memsize); +} + +static int +bitmap_port_head(struct ip_set *set, struct sk_buff *skb) +{ + const struct bitmap_port *map = set->data; + struct nlattr *nested; + + nested = ipset_nest_start(skb, IPSET_ATTR_DATA); + if (!nested) + goto nla_put_failure; + NLA_PUT_NET16(skb, IPSET_ATTR_PORT, htons(map->first_port)); + NLA_PUT_NET16(skb, IPSET_ATTR_PORT_TO, htons(map->last_port)); + NLA_PUT_NET32(skb, IPSET_ATTR_REFERENCES, + htonl(atomic_read(&set->ref) - 1)); + NLA_PUT_NET32(skb, IPSET_ATTR_MEMSIZE, + htonl(sizeof(*map) + map->memsize)); + ipset_nest_end(skb, nested); + + return 0; +nla_put_failure: + return -EFAULT; +} + +static int +bitmap_port_list(const struct ip_set *set, + struct sk_buff *skb, struct netlink_callback *cb) +{ + const struct bitmap_port *map = set->data; + struct nlattr *atd, *nested; + u16 id, first = cb->args[2]; + u16 last = map->last_port - map->first_port; + + atd = ipset_nest_start(skb, IPSET_ATTR_ADT); + if (!atd) + return -EFAULT; + for (; cb->args[2] <= last; cb->args[2]++) { + id = cb->args[2]; + if (!test_bit(id, map->members)) + continue; + nested = ipset_nest_start(skb, IPSET_ATTR_DATA); + if (!nested) { + if (id == first) { + nla_nest_cancel(skb, atd); + return -EFAULT; + } else + goto nla_put_failure; + } + NLA_PUT_NET16(skb, IPSET_ATTR_PORT, + htons(map->first_port + id)); + ipset_nest_end(skb, nested); + } + ipset_nest_end(skb, atd); + /* Set listing finished */ + cb->args[2] = 0; + + return 0; + +nla_put_failure: + nla_nest_cancel(skb, nested); + ipset_nest_end(skb, atd); + return 0; +} + +static bool +bitmap_port_same_set(const struct ip_set *a, const struct ip_set *b) +{ + const struct bitmap_port *x = a->data; + const struct bitmap_port *y = b->data; + + return x->first_port == y->first_port + && x->last_port == y->last_port; +} + +const struct ip_set_type_variant bitmap_port = { + .kadt = bitmap_port_kadt, + .uadt = bitmap_port_uadt, + .destroy = bitmap_port_destroy, + .flush = bitmap_port_flush, + .head = bitmap_port_head, + .list = bitmap_port_list, + .same_set = bitmap_port_same_set, +}; + +/* Timeout variant */ + +struct bitmap_port_timeout { + unsigned long *members; /* the set members */ + u16 first_port; /* host byte order, included in range */ + u16 last_port; /* host byte order, included in range */ + size_t memsize; /* members size */ + + u32 timeout; /* timeout parameter */ + struct timer_list gc; /* garbage collection */ +}; + +static inline bool +bitmap_port_timeout_test(const struct bitmap_port_timeout *map, u16 id) +{ + return ip_set_timeout_test(map->members[id]); +} + +static int +bitmap_port_timeout_add(const struct bitmap_port_timeout *map, + u16 id, u32 timeout) +{ + if (bitmap_port_timeout_test(map, id)) + return -IPSET_ERR_EXIST; + + map->members[id] = ip_set_timeout_set(timeout); + + return 0; +} + +static int +bitmap_port_timeout_del(const struct bitmap_port_timeout *map, + u16 id) +{ + int ret = -IPSET_ERR_EXIST; + + if (bitmap_port_timeout_test(map, id)) + ret = 0; + + map->members[id] = IPSET_ELEM_UNSET; + return ret; +} + +static int +bitmap_port_timeout_kadt(struct ip_set *set, const struct sk_buff *skb, + enum ipset_adt adt, u8 pf, u8 dim, u8 flags) +{ + struct bitmap_port_timeout *map = set->data; + u16 port = 0; + + if (!get_ip_port(skb, pf, flags & IPSET_DIM_ONE_SRC, &port)) + return -EINVAL; + + port = ntohs(port); + + if (port < map->first_port || port > map->last_port) + return -IPSET_ERR_BITMAP_RANGE; + + port -= map->first_port; + + switch (adt) { + case IPSET_TEST: + return bitmap_port_timeout_test(map, port); + case IPSET_ADD: + return bitmap_port_timeout_add(map, port, map->timeout); + case IPSET_DEL: + return bitmap_port_timeout_del(map, port); + default: + return -EINVAL; + } +} + +static int +bitmap_port_timeout_uadt(struct ip_set *set, struct nlattr *head, int len, + enum ipset_adt adt, u32 *lineno, u32 flags) +{ + const struct bitmap_port_timeout *map = set->data; + struct nlattr *tb[IPSET_ATTR_ADT_MAX+1]; + u16 id, port_to; + u32 port, timeout = map->timeout; /* wraparound */ + int ret = 0; + + if (nla_parse(tb, IPSET_ATTR_ADT_MAX, head, len, + bitmap_port_adt_policy)) + return -IPSET_ERR_PROTOCOL; + + if (tb[IPSET_ATTR_LINENO]) + *lineno = nla_get_u32(tb[IPSET_ATTR_LINENO]); + + if (tb[IPSET_ATTR_PORT]) + port = ip_set_get_h16(tb[IPSET_ATTR_PORT]); + else + return -IPSET_ERR_PROTOCOL; + + if (port < map->first_port || port > map->last_port) + return -IPSET_ERR_BITMAP_RANGE; + + if (adt == IPSET_TEST) + return bitmap_port_timeout_test(map, port - map->first_port); + + if (tb[IPSET_ATTR_PORT_TO]) { + port_to = ip_set_get_h16(tb[IPSET_ATTR_PORT_TO]); + if (port > port_to) { + swap(port, port_to); + if (port < map->first_port) + return -IPSET_ERR_BITMAP_RANGE; + } + } else + port_to = port; + + if (port_to > map->last_port) + return -IPSET_ERR_BITMAP_RANGE; + + if (tb[IPSET_ATTR_TIMEOUT]) + timeout = ip_set_timeout_uget(tb[IPSET_ATTR_TIMEOUT]); + + for (; port <= port_to; port++) { + id = port - map->first_port; + ret = adt == IPSET_ADD + ? bitmap_port_timeout_add(map, id, timeout) + : bitmap_port_timeout_del(map, id); + + if (ret && !ip_set_eexist(ret, flags)) + return ret; + else + ret = 0; + } + return ret; +} + +static void +bitmap_port_timeout_destroy(struct ip_set *set) +{ + struct bitmap_port_timeout *map = set->data; + + del_timer_sync(&map->gc); + ip_set_free(map->members); + kfree(map); + + set->data = NULL; +} + +static void +bitmap_port_timeout_flush(struct ip_set *set) +{ + struct bitmap_port_timeout *map = set->data; + + memset(map->members, 0, map->memsize); +} + +static int +bitmap_port_timeout_head(struct ip_set *set, struct sk_buff *skb) +{ + const struct bitmap_port_timeout *map = set->data; + struct nlattr *nested; + + nested = ipset_nest_start(skb, IPSET_ATTR_DATA); + if (!nested) + goto nla_put_failure; + NLA_PUT_NET16(skb, IPSET_ATTR_PORT, htons(map->first_port)); + NLA_PUT_NET16(skb, IPSET_ATTR_PORT_TO, htons(map->last_port)); + NLA_PUT_NET32(skb, IPSET_ATTR_TIMEOUT , htonl(map->timeout)); + NLA_PUT_NET32(skb, IPSET_ATTR_REFERENCES, + htonl(atomic_read(&set->ref) - 1)); + NLA_PUT_NET32(skb, IPSET_ATTR_MEMSIZE, + htonl(sizeof(*map) + map->memsize)); + ipset_nest_end(skb, nested); + + return 0; +nla_put_failure: + return -EFAULT; +} + +static int +bitmap_port_timeout_list(const struct ip_set *set, + struct sk_buff *skb, struct netlink_callback *cb) +{ + const struct bitmap_port_timeout *map = set->data; + struct nlattr *adt, *nested; + u16 id, first = cb->args[2]; + u16 last = map->last_port - map->first_port; + const unsigned long *table = map->members; + + adt = ipset_nest_start(skb, IPSET_ATTR_ADT); + if (!adt) + return -EFAULT; + for (; cb->args[2] <= last; cb->args[2]++) { + id = cb->args[2]; + if (!bitmap_port_timeout_test(map, id)) + continue; + nested = ipset_nest_start(skb, IPSET_ATTR_DATA); + if (!nested) { + if (id == first) { + nla_nest_cancel(skb, adt); + return -EFAULT; + } else + goto nla_put_failure; + } + NLA_PUT_NET16(skb, IPSET_ATTR_PORT, + htons(map->first_port + id)); + NLA_PUT_NET32(skb, IPSET_ATTR_TIMEOUT, + htonl(ip_set_timeout_get(table[id]))); + ipset_nest_end(skb, nested); + } + ipset_nest_end(skb, adt); + + /* Set listing finished */ + cb->args[2] = 0; + + return 0; + +nla_put_failure: + nla_nest_cancel(skb, nested); + ipset_nest_end(skb, adt); + return 0; +} + +static bool +bitmap_port_timeout_same_set(const struct ip_set *a, const struct ip_set *b) +{ + const struct bitmap_port_timeout *x = a->data; + const struct bitmap_port_timeout *y = b->data; + + return x->first_port == y->first_port + && x->last_port == y->last_port + && x->timeout == y->timeout; +} + +const struct ip_set_type_variant bitmap_port_timeout = { + .kadt = bitmap_port_timeout_kadt, + .uadt = bitmap_port_timeout_uadt, + .destroy = bitmap_port_timeout_destroy, + .flush = bitmap_port_timeout_flush, + .head = bitmap_port_timeout_head, + .list = bitmap_port_timeout_list, + .same_set = bitmap_port_timeout_same_set, +}; + +static void +bitmap_port_gc(unsigned long ul_set) +{ + struct ip_set *set = (struct ip_set *) ul_set; + struct bitmap_port_timeout *map = set->data; + unsigned long *table = map->members; + u32 id; /* wraparound */ + u16 last = map->last_port - map->first_port; + + /* We run parallel with other readers (test element) + * but adding/deleting new entries is locked out */ + read_lock_bh(&set->lock); + for (id = 0; id <= last; id++) + if (ip_set_timeout_expired(table[id])) + table[id] = IPSET_ELEM_UNSET; + read_unlock_bh(&set->lock); + + map->gc.expires = jiffies + IPSET_GC_PERIOD(map->timeout) * HZ; + add_timer(&map->gc); +} + +static inline void +bitmap_port_gc_init(struct ip_set *set) +{ + struct bitmap_port_timeout *map = set->data; + + init_timer(&map->gc); + map->gc.data = (unsigned long) set; + map->gc.function = bitmap_port_gc; + map->gc.expires = jiffies + IPSET_GC_PERIOD(map->timeout) * HZ; + add_timer(&map->gc); +} + +/* Create bitmap:ip type of sets */ + +static const struct nla_policy +bitmap_port_create_policy[IPSET_ATTR_CREATE_MAX+1] = { + [IPSET_ATTR_PORT] = { .type = NLA_U16 }, + [IPSET_ATTR_PORT_TO] = { .type = NLA_U16 }, + [IPSET_ATTR_TIMEOUT] = { .type = NLA_U32 }, +}; + +static bool +init_map_port(struct ip_set *set, struct bitmap_port *map, + u16 first_port, u16 last_port) +{ + map->members = ip_set_alloc(map->memsize, GFP_KERNEL); + if (!map->members) + return false; + map->first_port = first_port; + map->last_port = last_port; + + set->data = map; + set->family = AF_UNSPEC; + + return true; +} + +static int +bitmap_port_create(struct ip_set *set, struct nlattr *head, int len, + u32 flags) +{ + struct nlattr *tb[IPSET_ATTR_CREATE_MAX+1]; + u16 first_port, last_port; + + if (nla_parse(tb, IPSET_ATTR_CREATE_MAX, head, len, + bitmap_port_create_policy)) + return -IPSET_ERR_PROTOCOL; + + if (tb[IPSET_ATTR_PORT]) + first_port = ip_set_get_h16(tb[IPSET_ATTR_PORT]); + else + return -IPSET_ERR_PROTOCOL; + + if (tb[IPSET_ATTR_PORT_TO]) { + last_port = ip_set_get_h16(tb[IPSET_ATTR_PORT_TO]); + if (first_port > last_port) { + u16 tmp = first_port; + + first_port = last_port; + last_port = tmp; + } + } else + return -IPSET_ERR_PROTOCOL; + + if (tb[IPSET_ATTR_TIMEOUT]) { + struct bitmap_port_timeout *map; + + map = kzalloc(sizeof(*map), GFP_KERNEL); + if (!map) + return -ENOMEM; + + map->memsize = (last_port - first_port + 1) + * sizeof(unsigned long); + + if (!init_map_port(set, (struct bitmap_port *) map, + first_port, last_port)) { + kfree(map); + return -ENOMEM; + } + + map->timeout = ip_set_timeout_uget(tb[IPSET_ATTR_TIMEOUT]); + set->variant = &bitmap_port_timeout; + + bitmap_port_gc_init(set); + } else { + struct bitmap_port *map; + + map = kzalloc(sizeof(*map), GFP_KERNEL); + if (!map) + return -ENOMEM; + + map->memsize = bitmap_bytes(0, last_port - first_port); + pr_debug("memsize: %zu", map->memsize); + if (!init_map_port(set, map, first_port, last_port)) { + kfree(map); + return -ENOMEM; + } + + set->variant = &bitmap_port; + } + return 0; +} + +struct ip_set_type bitmap_port_type = { + .name = "bitmap:port", + .protocol = IPSET_PROTOCOL, + .features = IPSET_TYPE_PORT, + .dimension = IPSET_DIM_ONE, + .family = AF_UNSPEC, + .revision = 0, + .create = bitmap_port_create, + .me = THIS_MODULE, +}; + +static int __init +bitmap_port_init(void) +{ + return ip_set_type_register(&bitmap_port_type); +} + +static void __exit +bitmap_port_fini(void) +{ + ip_set_type_unregister(&bitmap_port_type); +} + +module_init(bitmap_port_init); +module_exit(bitmap_port_fini); diff --git a/extensions/ipset-5/ip_set_chash.h b/extensions/ipset-5/ip_set_chash.h new file mode 100644 index 0000000..dc8a9ab --- /dev/null +++ b/extensions/ipset-5/ip_set_chash.h @@ -0,0 +1,1164 @@ +#ifndef _IP_SET_CHASH_H +#define _IP_SET_CHASH_H + +#include "jhash.h" +#include "slist.h" +#include "ip_set_timeout.h" + +/* Cacheline friendly hash with resizing when linear searching becomes too + * long. Internally jhash is used with the assumption that the size of the + * stored data is a multiple of sizeof(u32). If storage supports timeout, + * the timeout field must be the last one in the data structure - that field + * is ignored when computing the hash key. + */ + +/* Number of elements to store in an array block */ +#define CHASH_DEFAULT_ARRAY_SIZE 4 +/* Number of arrays: max ARRAY_SIZE * CHAIN_LIMIT "long" chains */ +#define CHASH_DEFAULT_CHAIN_LIMIT 3 + +/* Book-keeping of the prefixes added to the set */ +struct chash_nets { + u8 cidr; /* the different cidr values in the set */ + u32 nets; /* number of elements per cidr */ +}; + +struct htable { + u8 htable_bits; /* size of hash table == 2^htable_bits */ + struct slist htable[0]; /* hashtable of single linked lists */ +}; + +struct chash { + struct htable *table; /* the hash table */ + u32 maxelem; /* max elements in the hash */ + u32 elements; /* current element (vs timeout) */ + u32 initval; /* random jhash init value */ + u32 timeout; /* timeout value, if enabled */ + struct timer_list gc; /* garbage collection when timeout enabled */ + u8 array_size; /* number of elements in an array */ + u8 chain_limit; /* max number of arrays */ +#ifdef IP_SET_HASH_WITH_NETMASK + u8 netmask; /* netmask value for subnets to store */ +#endif +#ifdef IP_SET_HASH_WITH_NETS + struct chash_nets nets[0]; /* book-keeping of prefixes */ +#endif +}; + +/* Compute htable_bits from the user input parameter hashsize */ +static inline u8 +htable_bits(u32 hashsize) +{ + /* Assume that hashsize == 2^htable_bits */ + u8 bits = fls(hashsize - 1); + if (jhash_size(bits) != hashsize) + /* Round up to the first 2^n value */ + bits = fls(hashsize); + + return bits; +} + +#ifdef IP_SET_HASH_WITH_NETS + +#define SET_HOST_MASK(family) (family == AF_INET ? 32 : 128) + +/* Network cidr size book keeping when the hash stores different + * sized networks */ +static inline void +add_cidr(struct chash *h, u8 cidr, u8 host_mask) +{ + u8 i; + + ++h->nets[cidr-1].nets; + + pr_debug("add_cidr added %u: %u", cidr, h->nets[cidr-1].nets); + + if (h->nets[cidr-1].nets > 1) + return; + + /* New cidr size */ + for (i = 0; i < host_mask && h->nets[i].cidr; i++) { + /* Add in increasing prefix order, so larger cidr first */ + if (h->nets[i].cidr < cidr) + swap(h->nets[i].cidr, cidr); + } + if (i < host_mask) + h->nets[i].cidr = cidr; +} + +static inline void +del_cidr(struct chash *h, u8 cidr, u8 host_mask) +{ + u8 i; + + --h->nets[cidr-1].nets; + + pr_debug("del_cidr deleted %u: %u", cidr, h->nets[cidr-1].nets); + + if (h->nets[cidr-1].nets != 0) + return; + + /* All entries with this cidr size deleted, so cleanup h->cidr[] */ + for (i = 0; i < host_mask - 1 && h->nets[i].cidr; i++) { + if (h->nets[i].cidr == cidr) + h->nets[i].cidr = cidr = h->nets[i+1].cidr; + } + h->nets[i - 1].cidr = 0; +} +#endif + +/* Destroy the hashtable part of the set */ +static void +chash_destroy(struct htable *ht) +{ + struct slist *n, *tmp; + u32 i; + + for (i = 0; i < jhash_size(ht->htable_bits); i++) + slist_for_each_safe(n, tmp, &ht->htable[i]) + /* FIXME: use slab cache */ + kfree(n); + + ip_set_free(ht); +} + +/* Calculate the actual memory size of the set data */ +static size_t +chash_memsize(const struct chash *h, size_t dsize, u8 host_mask) +{ + struct slist *n; + u32 i; + struct htable *ht = h->table; + size_t memsize = sizeof(*h) +#ifdef IP_SET_HASH_WITH_NETS + + sizeof(struct chash_nets) * host_mask +#endif + + jhash_size(ht->htable_bits) * sizeof(struct slist); + + for (i = 0; i < jhash_size(ht->htable_bits); i++) + slist_for_each(n, &ht->htable[i]) + memsize += sizeof(struct slist) + + h->array_size * dsize; + + return memsize; +} + +/* Flush a hash type of set: destroy all elements */ +static void +ip_set_hash_flush(struct ip_set *set) +{ + struct chash *h = set->data; + struct htable *ht = h->table; + struct slist *n, *tmp; + u32 i; + + for (i = 0; i < jhash_size(ht->htable_bits); i++) { + slist_for_each_safe(n, tmp, &ht->htable[i]) + /* FIXME: slab cache */ + kfree(n); + ht->htable[i].next = NULL; + } +#ifdef IP_SET_HASH_WITH_NETS + memset(h->nets, 0, sizeof(struct chash_nets) + * SET_HOST_MASK(set->family)); +#endif + h->elements = 0; +} + +/* Destroy a hash type of set */ +static void +ip_set_hash_destroy(struct ip_set *set) +{ + struct chash *h = set->data; + + if (with_timeout(h->timeout)) + del_timer_sync(&h->gc); + + chash_destroy(h->table); + kfree(h); + + set->data = NULL; +} + +#define JHASH2(data, initval, htable_bits) \ +(jhash2((u32 *)(data), sizeof(struct type_pf_elem)/sizeof(u32), initval) \ + & jhash_mask(htable_bits)) + +#endif /* _IP_SET_CHASH_H */ + +#define CONCAT(a, b, c) a##b##c +#define TOKEN(a, b, c) CONCAT(a, b, c) + +/* Type/family dependent function prototypes */ + +#define type_pf_data_equal TOKEN(TYPE, PF, _data_equal) +#define type_pf_data_isnull TOKEN(TYPE, PF, _data_isnull) +#define type_pf_data_copy TOKEN(TYPE, PF, _data_copy) +#define type_pf_data_swap TOKEN(TYPE, PF, _data_swap) +#define type_pf_data_zero_out TOKEN(TYPE, PF, _data_zero_out) +#define type_pf_data_netmask TOKEN(TYPE, PF, _data_netmask) +#define type_pf_data_list TOKEN(TYPE, PF, _data_list) +#define type_pf_data_tlist TOKEN(TYPE, PF, _data_tlist) + +#define type_pf_elem TOKEN(TYPE, PF, _elem) +#define type_pf_telem TOKEN(TYPE, PF, _telem) +#define type_pf_data_timeout TOKEN(TYPE, PF, _data_timeout) +#define type_pf_data_expired TOKEN(TYPE, PF, _data_expired) +#define type_pf_data_swap_timeout TOKEN(TYPE, PF, _data_swap_timeout) +#define type_pf_data_timeout_set TOKEN(TYPE, PF, _data_timeout_set) + +#define type_pf_chash_readd TOKEN(TYPE, PF, _chash_readd) +#define type_pf_chash_del_elem TOKEN(TYPE, PF, _chash_del_elem) +#define type_pf_chash_add TOKEN(TYPE, PF, _chash_add) +#define type_pf_chash_del TOKEN(TYPE, PF, _chash_del) +#define type_pf_chash_test_cidrs TOKEN(TYPE, PF, _chash_test_cidrs) +#define type_pf_chash_test TOKEN(TYPE, PF, _chash_test) + +#define type_pf_chash_treadd TOKEN(TYPE, PF, _chash_treadd) +#define type_pf_chash_del_telem TOKEN(TYPE, PF, _chash_del_telem) +#define type_pf_chash_expire TOKEN(TYPE, PF, _chash_expire) +#define type_pf_chash_tadd TOKEN(TYPE, PF, _chash_tadd) +#define type_pf_chash_tdel TOKEN(TYPE, PF, _chash_tdel) +#define type_pf_chash_ttest_cidrs TOKEN(TYPE, PF, _chash_ttest_cidrs) +#define type_pf_chash_ttest TOKEN(TYPE, PF, _chash_ttest) + +#define type_pf_resize TOKEN(TYPE, PF, _resize) +#define type_pf_tresize TOKEN(TYPE, PF, _tresize) +#define type_pf_flush ip_set_hash_flush +#define type_pf_destroy ip_set_hash_destroy +#define type_pf_head TOKEN(TYPE, PF, _head) +#define type_pf_list TOKEN(TYPE, PF, _list) +#define type_pf_tlist TOKEN(TYPE, PF, _tlist) +#define type_pf_same_set TOKEN(TYPE, PF, _same_set) +#define type_pf_kadt TOKEN(TYPE, PF, _kadt) +#define type_pf_uadt TOKEN(TYPE, PF, _uadt) +#define type_pf_gc TOKEN(TYPE, PF, _gc) +#define type_pf_gc_init TOKEN(TYPE, PF, _gc_init) +#define type_pf_variant TOKEN(TYPE, PF, _variant) +#define type_pf_tvariant TOKEN(TYPE, PF, _tvariant) + +/* Flavour without timeout */ + +/* Get the ith element from the array block n */ +#define chash_data(n, i) \ +(struct type_pf_elem *)((char *)(n) + sizeof(struct slist) \ + + (i)*sizeof(struct type_pf_elem)) + +/* Add an element to the hash table when resizing the set: + * we spare the maintenance of the internal counters. */ +static int +type_pf_chash_readd(struct chash *h, struct htable *ht, + const struct type_pf_elem *value, + gfp_t gfp_flags) +{ + struct slist *n, *prev; + struct type_pf_elem *data; + void *tmp; + int i = 0, j = 0; + u32 hash = JHASH2(value, h->initval, ht->htable_bits); + + slist_for_each_prev(prev, n, &ht->htable[hash]) { + for (i = 0; i < h->array_size; i++) { + data = chash_data(n, i); + if (type_pf_data_isnull(data)) { + tmp = n; + goto found; + } + } + j++; + } + if (j < h->chain_limit) { + tmp = kzalloc(h->array_size * sizeof(struct type_pf_elem) + + sizeof(struct slist), gfp_flags); + if (!tmp) + return -ENOMEM; + prev->next = (struct slist *) tmp; + data = chash_data(tmp, 0); + } else { + /* Trigger rehashing */ + return -EAGAIN; + } +found: + type_pf_data_copy(data, value); + return 0; +} + +/* Delete an element from the hash table: swap it with the last + * element in the hash bucket and free up the array if it was + * completely emptied */ +static void +type_pf_chash_del_elem(struct chash *h, struct slist *prev, + struct slist *n, int i) +{ + struct type_pf_elem *data = chash_data(n, i); + struct slist *tmp; + int j; /* Index in array */ + + if (n->next != NULL) { + for (prev = n, tmp = n->next; + tmp->next != NULL; + prev = tmp, tmp = tmp->next) + /* Find last array */; + j = 0; + } else { + /* Already at last array */ + tmp = n; + j = i; + } + /* Find last non-empty element */ + for (; j < h->array_size - 1; j++) + if (type_pf_data_isnull(chash_data(tmp, j + 1))) + break; + + if (!(tmp == n && i == j)) + type_pf_data_swap(data, chash_data(tmp, j)); + +#ifdef IP_SET_HASH_WITH_NETS + del_cidr(h, data->cidr, HOST_MASK); +#endif + if (j == 0) { + prev->next = NULL; + kfree(tmp); + } else + type_pf_data_zero_out(chash_data(tmp, j)); + + h->elements--; +} + +/* Resize a hash: create a new hash table with doubling the hashsize + * and inserting the elements to it. Repeat until we succeed or + * fail due to memory pressures. */ +static int +type_pf_resize(struct ip_set *set, gfp_t gfp_flags, bool retried) +{ + struct chash *h = set->data; + struct htable *ht, *orig = h->table; + u8 htable_bits = orig->htable_bits; + struct slist *n; + const struct type_pf_elem *data; + u32 i, j; + int ret; + +retry: + ret = i = 0; + htable_bits++; + if (!htable_bits) + /* In case we have plenty of memory :-) */ + return -IPSET_ERR_HASH_FULL; + ht = ip_set_alloc(sizeof(*ht) + + jhash_size(htable_bits) * sizeof(struct slist), + GFP_KERNEL); + if (!ht) + return -ENOMEM; + ht->htable_bits = htable_bits; + + read_lock_bh(&set->lock); +next_slot: + for (; i < jhash_size(orig->htable_bits); i++) { + slist_for_each(n, &orig->htable[i]) { + for (j = 0; j < h->array_size; j++) { + data = chash_data(n, j); + if (type_pf_data_isnull(data)) { + i++; + goto next_slot; + } + ret = type_pf_chash_readd(h, ht, + data, gfp_flags); + if (ret < 0) { + read_unlock_bh(&set->lock); + chash_destroy(ht); + if (ret == -EAGAIN) + goto retry; + return ret; + } + } + } + } + + h->table = ht; + read_unlock_bh(&set->lock); + + /* Give time to other users of the set */ + synchronize_net(); + + chash_destroy(orig); + + return 0; +} + +/* Add an element to a hash and update the internal counters when succeeded, + * otherwise report the proper error code. */ +static int +type_pf_chash_add(struct ip_set *set, void *value, + gfp_t gfp_flags, u32 timeout) +{ + struct chash *h = set->data; + const struct type_pf_elem *d = value; + struct slist *n, *prev; + struct htable *ht = h->table; + struct type_pf_elem *data; + void *tmp; + int i = 0, j = 0; + u32 hash; + + if (h->elements >= h->maxelem) + return -IPSET_ERR_HASH_FULL; + + hash = JHASH2(value, h->initval, ht->htable_bits); + slist_for_each_prev(prev, n, &ht->htable[hash]) { + for (i = 0; i < h->array_size; i++) { + data = chash_data(n, i); + if (type_pf_data_isnull(data)) { + tmp = n; + goto found; + } + if (type_pf_data_equal(data, d)) + return -IPSET_ERR_EXIST; + } + j++; + } + if (j < h->chain_limit) { + tmp = kzalloc(h->array_size * sizeof(struct type_pf_elem) + + sizeof(struct slist), gfp_flags); + if (!tmp) + return -ENOMEM; + prev->next = (struct slist *) tmp; + data = chash_data(tmp, 0); + } else { + /* Rehashing */ + return -EAGAIN; + } +found: + type_pf_data_copy(data, d); +#ifdef IP_SET_HASH_WITH_NETS + add_cidr(h, d->cidr, HOST_MASK); +#endif + h->elements++; + return 0; +} + +/* Delete an element from the hash */ +static int +type_pf_chash_del(struct ip_set *set, void *value, + gfp_t gfp_flags, u32 timeout) +{ + struct chash *h = set->data; + const struct type_pf_elem *d = value; + struct htable *ht = h->table; + struct slist *n, *prev; + int i; + struct type_pf_elem *data; + u32 hash = JHASH2(value, h->initval, ht->htable_bits); + + slist_for_each_prev(prev, n, &ht->htable[hash]) + for (i = 0; i < h->array_size; i++) { + data = chash_data(n, i); + if (type_pf_data_isnull(data)) + return -IPSET_ERR_EXIST; + if (type_pf_data_equal(data, d)) { + type_pf_chash_del_elem(h, prev, n, i); + return 0; + } + } + + return -IPSET_ERR_EXIST; +} + +#ifdef IP_SET_HASH_WITH_NETS + +/* Special test function which takes into account the different network + * sizes added to the set */ +static inline int +type_pf_chash_test_cidrs(struct ip_set *set, + struct type_pf_elem *d, + gfp_t gfp_flags, u32 timeout) +{ + struct chash *h = set->data; + struct htable *ht = h->table; + struct slist *n; + const struct type_pf_elem *data; + int i, j = 0; + u32 hash; + u8 host_mask = SET_HOST_MASK(set->family); + +retry: + pr_debug("test by nets"); + for (; j < host_mask && h->nets[j].cidr; j++) { + type_pf_data_netmask(d, h->nets[j].cidr); + hash = JHASH2(d, h->initval, ht->htable_bits); + slist_for_each(n, &ht->htable[hash]) + for (i = 0; i < h->array_size; i++) { + data = chash_data(n, i); + if (type_pf_data_isnull(data)) { + j++; + goto retry; + } + if (type_pf_data_equal(data, d)) + return 1; + } + } + return 0; +} +#endif + +/* Test whether the element is added to the set */ +static inline int +type_pf_chash_test(struct ip_set *set, void *value, + gfp_t gfp_flags, u32 timeout) +{ + struct chash *h = set->data; + struct htable *ht = h->table; + struct type_pf_elem *d = value; + struct slist *n; + const struct type_pf_elem *data; + int i; + u32 hash; + +#ifdef IP_SET_HASH_WITH_NETS + /* If we test an IP address and not a network address, + * try all possible network sizes */ + if (d->cidr == SET_HOST_MASK(set->family)) + return type_pf_chash_test_cidrs(set, d, gfp_flags, timeout); +#endif + + hash = JHASH2(d, h->initval, ht->htable_bits); + slist_for_each(n, &ht->htable[hash]) + for (i = 0; i < h->array_size; i++) { + data = chash_data(n, i); + if (type_pf_data_isnull(data)) + return 0; + if (type_pf_data_equal(data, d)) + return 1; + } + return 0; +} + +/* Reply a HEADER request: fill out the header part of the set */ +static int +type_pf_head(struct ip_set *set, struct sk_buff *skb) +{ + const struct chash *h = set->data; + struct nlattr *nested; + size_t memsize; + + read_lock_bh(&set->lock); + memsize = chash_memsize(h, with_timeout(h->timeout) + ? sizeof(struct type_pf_telem) + : sizeof(struct type_pf_elem), + set->family == AF_INET ? 32 : 128); + read_unlock_bh(&set->lock); + + nested = ipset_nest_start(skb, IPSET_ATTR_DATA); + if (!nested) + goto nla_put_failure; + NLA_PUT_NET32(skb, IPSET_ATTR_HASHSIZE, + htonl(jhash_size(h->table->htable_bits))); + NLA_PUT_NET32(skb, IPSET_ATTR_MAXELEM, htonl(h->maxelem)); +#ifdef IP_SET_HASH_WITH_NETMASK + if (h->netmask != HOST_MASK) + NLA_PUT_U8(skb, IPSET_ATTR_NETMASK, h->netmask); +#endif + NLA_PUT_NET32(skb, IPSET_ATTR_REFERENCES, + htonl(atomic_read(&set->ref) - 1)); + NLA_PUT_NET32(skb, IPSET_ATTR_MEMSIZE, htonl(memsize)); + if (with_timeout(h->timeout)) + NLA_PUT_NET32(skb, IPSET_ATTR_TIMEOUT, htonl(h->timeout)); + ipset_nest_end(skb, nested); + + return 0; +nla_put_failure: + return -EFAULT; +} + +/* Reply a LIST/SAVE request: dump the elements of the specified set */ +static int +type_pf_list(struct ip_set *set, + struct sk_buff *skb, struct netlink_callback *cb) +{ + const struct chash *h = set->data; + const struct htable *ht = h->table; + struct nlattr *atd, *nested; + struct slist *n; + const struct type_pf_elem *data; + u32 first = cb->args[2]; + /* We assume that one hash bucket fills into one page */ + void *incomplete; + int i; + + atd = ipset_nest_start(skb, IPSET_ATTR_ADT); + if (!atd) + return -EFAULT; + pr_debug("list hash set %s", set->name); + for (; cb->args[2] < jhash_size(ht->htable_bits); cb->args[2]++) { + incomplete = skb_tail_pointer(skb); + slist_for_each(n, &ht->htable[cb->args[2]]) { + for (i = 0; i < h->array_size; i++) { + data = chash_data(n, i); + if (type_pf_data_isnull(data)) + break; + pr_debug("list hash %lu slist %p i %u", + cb->args[2], n, i); + nested = ipset_nest_start(skb, IPSET_ATTR_DATA); + if (!nested) { + if (cb->args[2] == first) { + nla_nest_cancel(skb, atd); + return -EFAULT; + } else + goto nla_put_failure; + } + if (type_pf_data_list(skb, data)) + goto nla_put_failure; + ipset_nest_end(skb, nested); + } + } + } + ipset_nest_end(skb, atd); + /* Set listing finished */ + cb->args[2] = 0; + + return 0; + +nla_put_failure: + nlmsg_trim(skb, incomplete); + ipset_nest_end(skb, atd); + if (unlikely(first == cb->args[2])) { + pr_warn("Can't list set %s: one bucket does not fit into " + "a message. Please report it!\n", set->name); + cb->args[2] = 0; + } + return 0; +} + +static int +type_pf_kadt(struct ip_set *set, const struct sk_buff * skb, + enum ipset_adt adt, u8 pf, u8 dim, u8 flags); +static int +type_pf_uadt(struct ip_set *set, struct nlattr *head, int len, + enum ipset_adt adt, u32 *lineno, u32 flags); + +static const struct ip_set_type_variant type_pf_variant = { + .kadt = type_pf_kadt, + .uadt = type_pf_uadt, + .adt = { + [IPSET_ADD] = type_pf_chash_add, + [IPSET_DEL] = type_pf_chash_del, + [IPSET_TEST] = type_pf_chash_test, + }, + .destroy = type_pf_destroy, + .flush = type_pf_flush, + .head = type_pf_head, + .list = type_pf_list, + .resize = type_pf_resize, + .same_set = type_pf_same_set, +}; + +/* Flavour with timeout support */ + +#define chash_tdata(n, i) \ +(struct type_pf_elem *)((char *)(n) + sizeof(struct slist) \ + + (i)*sizeof(struct type_pf_telem)) + +static inline u32 +type_pf_data_timeout(const struct type_pf_elem *data) +{ + const struct type_pf_telem *tdata = + (const struct type_pf_telem *) data; + + return tdata->timeout; +} + +static inline bool +type_pf_data_expired(const struct type_pf_elem *data) +{ + const struct type_pf_telem *tdata = + (const struct type_pf_telem *) data; + + return ip_set_timeout_expired(tdata->timeout); +} + +static inline void +type_pf_data_swap_timeout(struct type_pf_elem *src, + struct type_pf_elem *dst) +{ + struct type_pf_telem *x = (struct type_pf_telem *) src; + struct type_pf_telem *y = (struct type_pf_telem *) dst; + + swap(x->timeout, y->timeout); +} + +static inline void +type_pf_data_timeout_set(struct type_pf_elem *data, u32 timeout) +{ + struct type_pf_telem *tdata = (struct type_pf_telem *) data; + + tdata->timeout = ip_set_timeout_set(timeout); +} + +static int +type_pf_chash_treadd(struct chash *h, struct htable *ht, + const struct type_pf_elem *value, + gfp_t gfp_flags, u32 timeout) +{ + struct slist *n, *prev; + struct type_pf_elem *data; + void *tmp; + int i = 0, j = 0; + u32 hash = JHASH2(value, h->initval, ht->htable_bits); + + slist_for_each_prev(prev, n, &ht->htable[hash]) { + for (i = 0; i < h->array_size; i++) { + data = chash_tdata(n, i); + if (type_pf_data_isnull(data)) { + tmp = n; + goto found; + } + } + j++; + } + if (j < h->chain_limit) { + tmp = kzalloc(h->array_size * sizeof(struct type_pf_telem) + + sizeof(struct slist), gfp_flags); + if (!tmp) + return -ENOMEM; + prev->next = (struct slist *) tmp; + data = chash_tdata(tmp, 0); + } else { + /* Trigger rehashing */ + return -EAGAIN; + } +found: + type_pf_data_copy(data, value); + type_pf_data_timeout_set(data, timeout); + return 0; +} + +static void +type_pf_chash_del_telem(struct chash *h, struct slist *prev, + struct slist *n, int i) +{ + struct type_pf_elem *d, *data = chash_tdata(n, i); + struct slist *tmp; + int j; /* Index in array */ + + pr_debug("del %u", i); + if (n->next != NULL) { + for (prev = n, tmp = n->next; + tmp->next != NULL; + prev = tmp, tmp = tmp->next) + /* Find last array */; + j = 0; + } else { + /* Already at last array */ + tmp = n; + j = i; + } + /* Find last non-empty element */ + for (; j < h->array_size - 1; j++) + if (type_pf_data_isnull(chash_tdata(tmp, j + 1))) + break; + + d = chash_tdata(tmp, j); + if (!(tmp == n && i == j)) { + type_pf_data_swap(data, d); + type_pf_data_swap_timeout(data, d); + } +#ifdef IP_SET_HASH_WITH_NETS + del_cidr(h, data->cidr, HOST_MASK); +#endif + if (j == 0) { + prev->next = NULL; + kfree(tmp); + } else + type_pf_data_zero_out(d); + + h->elements--; +} + +/* Delete expired elements from the hashtable */ +static void +type_pf_chash_expire(struct chash *h) +{ + struct htable *ht = h->table; + struct slist *n, *prev; + struct type_pf_elem *data; + u32 i; + int j; + + for (i = 0; i < jhash_size(ht->htable_bits); i++) + slist_for_each_prev(prev, n, &ht->htable[i]) + for (j = 0; j < h->array_size; j++) { + data = chash_tdata(n, j); + if (type_pf_data_isnull(data)) + break; + if (type_pf_data_expired(data)) { + pr_debug("expire %u/%u", i, j); + type_pf_chash_del_telem(h, prev, n, j); + } + } +} + +static int +type_pf_tresize(struct ip_set *set, gfp_t gfp_flags, bool retried) +{ + struct chash *h = set->data; + struct htable *ht, *orig = h->table; + u8 htable_bits = orig->htable_bits; + struct slist *n; + const struct type_pf_elem *data; + u32 i, j; + int ret; + + /* Try to cleanup once */ + if (!retried) { + i = h->elements; + write_lock_bh(&set->lock); + type_pf_chash_expire(set->data); + write_unlock_bh(&set->lock); + if (h->elements < i) + return 0; + } + +retry: + ret = i = 0; + htable_bits++; + if (!htable_bits) + /* In case we have plenty of memory :-) */ + return -IPSET_ERR_HASH_FULL; + ht = ip_set_alloc(sizeof(*ht) + + jhash_size(htable_bits) * sizeof(struct slist), + GFP_KERNEL); + if (!ht) + return -ENOMEM; + ht->htable_bits = htable_bits; + + read_lock_bh(&set->lock); +next_slot: + for (; i < jhash_size(orig->htable_bits); i++) { + slist_for_each(n, &orig->htable[i]) { + for (j = 0; j < h->array_size; j++) { + data = chash_tdata(n, j); + if (type_pf_data_isnull(data)) { + i++; + goto next_slot; + } + ret = type_pf_chash_treadd(h, ht, + data, gfp_flags, + type_pf_data_timeout(data)); + if (ret < 0) { + read_unlock_bh(&set->lock); + chash_destroy(ht); + if (ret == -EAGAIN) + goto retry; + return ret; + } + } + } + } + + h->table = ht; + read_unlock_bh(&set->lock); + + /* Give time to other users of the set */ + synchronize_net(); + + chash_destroy(orig); + + return 0; +} + +static int +type_pf_chash_tadd(struct ip_set *set, void *value, + gfp_t gfp_flags, u32 timeout) +{ + struct chash *h = set->data; + const struct type_pf_elem *d = value; + struct slist *n, *prev; + struct htable *ht = h->table; + struct type_pf_elem *data; + void *tmp; + int i = 0, j = 0; + u32 hash; + + if (h->elements >= h->maxelem) + /* FIXME: when set is full, we slow down here */ + type_pf_chash_expire(h); + if (h->elements >= h->maxelem) + return -IPSET_ERR_HASH_FULL; + + hash = JHASH2(d, h->initval, ht->htable_bits); + slist_for_each_prev(prev, n, &ht->htable[hash]) { + for (i = 0; i < h->array_size; i++) { + data = chash_tdata(n, i); + if (type_pf_data_isnull(data) + || type_pf_data_expired(data)) { + tmp = n; + goto found; + } + if (type_pf_data_equal(data, d)) + return -IPSET_ERR_EXIST; + } + j++; + } + if (j < h->chain_limit) { + tmp = kzalloc(h->array_size * sizeof(struct type_pf_telem) + + sizeof(struct slist), gfp_flags); + if (!tmp) + return -ENOMEM; + prev->next = (struct slist *) tmp; + data = chash_tdata(tmp, 0); + } else { + /* Rehashing */ + return -EAGAIN; + } +found: + if (type_pf_data_isnull(data)) + h->elements++; +#ifdef IP_SET_HASH_WITH_NETS + else + del_cidr(h, data->cidr, HOST_MASK); + + add_cidr(h, d->cidr, HOST_MASK); +#endif + type_pf_data_copy(data, d); + type_pf_data_timeout_set(data, timeout); + return 0; +} + +static int +type_pf_chash_tdel(struct ip_set *set, void *value, + gfp_t gfp_flags, u32 timeout) +{ + struct chash *h = set->data; + struct htable *ht = h->table; + const struct type_pf_elem *d = value; + struct slist *n, *prev; + int i, ret = 0; + struct type_pf_elem *data; + u32 hash = JHASH2(value, h->initval, ht->htable_bits); + + slist_for_each_prev(prev, n, &ht->htable[hash]) + for (i = 0; i < h->array_size; i++) { + data = chash_tdata(n, i); + if (type_pf_data_isnull(data)) + return -IPSET_ERR_EXIST; + if (type_pf_data_equal(data, d)) { + if (type_pf_data_expired(data)) + ret = -IPSET_ERR_EXIST; + type_pf_chash_del_telem(h, prev, n, i); + return ret; + } + } + + return -IPSET_ERR_EXIST; +} + +#ifdef IP_SET_HASH_WITH_NETS +static inline int +type_pf_chash_ttest_cidrs(struct ip_set *set, + struct type_pf_elem *d, + gfp_t gfp_flags, u32 timeout) +{ + struct chash *h = set->data; + struct htable *ht = h->table; + struct type_pf_elem *data; + struct slist *n; + int i, j = 0; + u32 hash; + u8 host_mask = SET_HOST_MASK(set->family); + +retry: + for (; j < host_mask && h->nets[j].cidr; j++) { + type_pf_data_netmask(d, h->nets[j].cidr); + hash = JHASH2(d, h->initval, ht->htable_bits); + slist_for_each(n, &ht->htable[hash]) + for (i = 0; i < h->array_size; i++) { + data = chash_tdata(n, i); + if (type_pf_data_isnull(data)) { + j++; + goto retry; + } + if (type_pf_data_equal(data, d)) + return !type_pf_data_expired(data); + } + } + return 0; +} +#endif + +static inline int +type_pf_chash_ttest(struct ip_set *set, void *value, + gfp_t gfp_flags, u32 timeout) +{ + struct chash *h = set->data; + struct htable *ht = h->table; + struct type_pf_elem *data, *d = value; + struct slist *n; + int i; + u32 hash; + +#ifdef IP_SET_HASH_WITH_NETS + if (d->cidr == SET_HOST_MASK(set->family)) + return type_pf_chash_ttest_cidrs(set, d, gfp_flags, + timeout); +#endif + hash = JHASH2(d, h->initval, ht->htable_bits); + slist_for_each(n, &ht->htable[hash]) + for (i = 0; i < h->array_size; i++) { + data = chash_tdata(n, i); + if (type_pf_data_isnull(data)) + return 0; + if (type_pf_data_equal(data, d)) + return !type_pf_data_expired(data); + } + return 0; +} + +static int +type_pf_tlist(struct ip_set *set, + struct sk_buff *skb, struct netlink_callback *cb) +{ + const struct chash *h = set->data; + const struct htable *ht = h->table; + struct nlattr *atd, *nested; + struct slist *n; + const struct type_pf_elem *data; + u32 first = cb->args[2]; + /* We assume that one hash bucket fills into one page */ + void *incomplete; + int i; + + atd = ipset_nest_start(skb, IPSET_ATTR_ADT); + if (!atd) + return -EFAULT; + for (; cb->args[2] < jhash_size(ht->htable_bits); cb->args[2]++) { + incomplete = skb_tail_pointer(skb); + slist_for_each(n, &ht->htable[cb->args[2]]) { + for (i = 0; i < h->array_size; i++) { + data = chash_tdata(n, i); + pr_debug("list %p %u", n, i); + if (type_pf_data_isnull(data)) + break; + if (type_pf_data_expired(data)) + continue; + pr_debug("do list %p %u", n, i); + nested = ipset_nest_start(skb, IPSET_ATTR_DATA); + if (!nested) { + if (cb->args[2] == first) { + nla_nest_cancel(skb, atd); + return -EFAULT; + } else + goto nla_put_failure; + } + if (type_pf_data_tlist(skb, data)) + goto nla_put_failure; + ipset_nest_end(skb, nested); + } + } + } + ipset_nest_end(skb, atd); + /* Set listing finished */ + cb->args[2] = 0; + + return 0; + +nla_put_failure: + nlmsg_trim(skb, incomplete); + ipset_nest_end(skb, atd); + if (unlikely(first == cb->args[2])) { + pr_warn("Can't list set %s: one bucket does not fit into " + "a message. Please report it!\n", set->name); + cb->args[2] = 0; + } + return 0; +} + +static const struct ip_set_type_variant type_pf_tvariant = { + .kadt = type_pf_kadt, + .uadt = type_pf_uadt, + .adt = { + [IPSET_ADD] = type_pf_chash_tadd, + [IPSET_DEL] = type_pf_chash_tdel, + [IPSET_TEST] = type_pf_chash_ttest, + }, + .destroy = type_pf_destroy, + .flush = type_pf_flush, + .head = type_pf_head, + .list = type_pf_tlist, + .resize = type_pf_tresize, + .same_set = type_pf_same_set, +}; + +static void +type_pf_gc(unsigned long ul_set) +{ + struct ip_set *set = (struct ip_set *) ul_set; + struct chash *h = set->data; + + pr_debug("called"); + write_lock_bh(&set->lock); + type_pf_chash_expire(h); + write_unlock_bh(&set->lock); + + h->gc.expires = jiffies + IPSET_GC_PERIOD(h->timeout) * HZ; + add_timer(&h->gc); +} + +static inline void +type_pf_gc_init(struct ip_set *set) +{ + struct chash *h = set->data; + + init_timer(&h->gc); + h->gc.data = (unsigned long) set; + h->gc.function = type_pf_gc; + h->gc.expires = jiffies + IPSET_GC_PERIOD(h->timeout) * HZ; + add_timer(&h->gc); + pr_debug("gc initialized, run in every %u", + IPSET_GC_PERIOD(h->timeout)); +} + +#undef type_pf_data_equal +#undef type_pf_data_isnull +#undef type_pf_data_copy +#undef type_pf_data_swap +#undef type_pf_data_zero_out +#undef type_pf_data_list +#undef type_pf_data_tlist + +#undef type_pf_elem +#undef type_pf_telem +#undef type_pf_data_timeout +#undef type_pf_data_expired +#undef type_pf_data_swap_timeout +#undef type_pf_data_netmask +#undef type_pf_data_timeout_set + +#undef type_pf_chash_readd +#undef type_pf_chash_del_elem +#undef type_pf_chash_add +#undef type_pf_chash_del +#undef type_pf_chash_test_cidrs +#undef type_pf_chash_test + +#undef type_pf_chash_treadd +#undef type_pf_chash_del_telem +#undef type_pf_chash_expire +#undef type_pf_chash_tadd +#undef type_pf_chash_tdel +#undef type_pf_chash_ttest_cidrs +#undef type_pf_chash_ttest + +#undef type_pf_resize +#undef type_pf_tresize +#undef type_pf_flush +#undef type_pf_destroy +#undef type_pf_head +#undef type_pf_list +#undef type_pf_tlist +#undef type_pf_same_set +#undef type_pf_kadt +#undef type_pf_uadt +#undef type_pf_gc +#undef type_pf_gc_init +#undef type_pf_variant +#undef type_pf_tvariant diff --git a/extensions/ipset-5/ip_set_getport.h b/extensions/ipset-5/ip_set_getport.h new file mode 100644 index 0000000..8be8ecf --- /dev/null +++ b/extensions/ipset-5/ip_set_getport.h @@ -0,0 +1,126 @@ +#ifndef _IP_SET_GETPORT_H +#define _IP_SET_GETPORT_H + +#ifdef __KERNEL__ +#include +#include +#include +#include + +#define IPSET_INVALID_PORT 65536 + +/* We must handle non-linear skbs */ +static inline bool +get_port(const struct sk_buff *skb, int protocol, unsigned int protooff, + bool src, u16 *port, u8 *proto) +{ + switch (protocol) { + case IPPROTO_TCP: { + struct tcphdr _tcph; + const struct tcphdr *th; + + th = skb_header_pointer(skb, protooff, sizeof(_tcph), &_tcph); + if (th == NULL) + /* No choice either */ + return false; + + *port = src ? th->source : th->dest; + break; + } + case IPPROTO_UDP: { + struct udphdr _udph; + const struct udphdr *uh; + + uh = skb_header_pointer(skb, protooff, sizeof(_udph), &_udph); + if (uh == NULL) + /* No choice either */ + return false; + + *port = src ? uh->source : uh->dest; + break; + } + case IPPROTO_ICMP: { + struct icmphdr _icmph; + const struct icmphdr *ic; + + ic = skb_header_pointer(skb, protooff, sizeof(_icmph), &_icmph); + if (ic == NULL) + return false; + + *port = (ic->type << 8) & ic->code; + break; + } + case IPPROTO_ICMPV6: { + struct icmp6hdr _icmph; + const struct icmp6hdr *ic; + + ic = skb_header_pointer(skb, protooff, sizeof(_icmph), &_icmph); + if (ic == NULL) + return false; + + *port = (ic->icmp6_type << 8) & ic->icmp6_code; + break; + } + default: + break; + } + *proto = protocol; + + return true; +} + +static inline bool +get_ip4_port(const struct sk_buff *skb, bool src, u16 *port, u8 *proto) +{ + const struct iphdr *iph = ip_hdr(skb); + unsigned int protooff = ip_hdrlen(skb); + int protocol = iph->protocol; + + /* See comments at tcp_match in ip_tables.c */ + if (protocol <= 0 || (ntohs(iph->frag_off) & IP_OFFSET)) + return false; + + return get_port(skb, protocol, protooff, src, port, proto); +} + +static inline bool +get_ip6_port(const struct sk_buff *skb, bool src, u16 *port, u8 *proto) +{ + unsigned int protooff = 0; + int protocol; + unsigned short fragoff; + + protocol = ipv6_find_hdr(skb, &protooff, -1, &fragoff); + if (protocol <= 0 || fragoff) + return false; + + return get_port(skb, protocol, protooff, src, port, proto); +} + +static inline bool +get_ip_port(const struct sk_buff *skb, u8 pf, bool src, u16 *port) +{ + bool ret; + u8 proto; + + switch (pf) { + case AF_INET: + ret = get_ip4_port(skb, src, port, &proto); + case AF_INET6: + ret = get_ip6_port(skb, src, port, &proto); + default: + return false; + } + if (!ret) + return ret; + switch (proto) { + case IPPROTO_TCP: + case IPPROTO_UDP: + return true; + default: + return false; + } +} +#endif /* __KERNEL__ */ + +#endif /*_IP_SET_GETPORT_H*/ diff --git a/extensions/ipset-5/ip_set_hash.h b/extensions/ipset-5/ip_set_hash.h new file mode 100644 index 0000000..b86f15c --- /dev/null +++ b/extensions/ipset-5/ip_set_hash.h @@ -0,0 +1,26 @@ +#ifndef __IP_SET_HASH_H +#define __IP_SET_HASH_H + +/* Hash type specific error codes */ +enum { + /* Hash is full */ + IPSET_ERR_HASH_FULL = IPSET_ERR_TYPE_SPECIFIC, + /* Null-valued element */ + IPSET_ERR_HASH_ELEM, + /* Invalid protocol */ + IPSET_ERR_INVALID_PROTO, + /* Protocol missing but must be specified */ + IPSET_ERR_MISSING_PROTO, +}; + +#ifdef __KERNEL__ + +#define IPSET_DEFAULT_HASHSIZE 1024 +#define IPSET_MIMINAL_HASHSIZE 64 +#define IPSET_DEFAULT_MAXELEM 65536 +#define IPSET_DEFAULT_PROBES 4 +#define IPSET_DEFAULT_RESIZE 100 + +#endif /* __KERNEL__ */ + +#endif /* __IP_SET_HASH_H */ diff --git a/extensions/ipset-5/ip_set_hash_ip.c b/extensions/ipset-5/ip_set_hash_ip.c new file mode 100644 index 0000000..de01841 --- /dev/null +++ b/extensions/ipset-5/ip_set_hash_ip.c @@ -0,0 +1,484 @@ +/* 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 set type: the hash:ip type */ + +#include "ip_set_kernel.h" +#include "jhash.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "ip_set.h" +#include "ip_set_timeout.h" +#include "ip_set_hash.h" + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Jozsef Kadlecsik "); +MODULE_DESCRIPTION("hash:ip type of IP sets"); +MODULE_ALIAS("ip_set_hash:ip"); + +/* Type specific function prefix */ +#define TYPE hash_ip + +static bool +hash_ip_same_set(const struct ip_set *a, const struct ip_set *b); + +#define hash_ip4_same_set hash_ip_same_set +#define hash_ip6_same_set hash_ip_same_set + +/* The type variant functions: IPv4 */ + +/* Member elements without timeout */ +struct hash_ip4_elem { + u32 ip; +}; + +/* Member elements with timeout support */ +struct hash_ip4_telem { + u32 ip; + unsigned long timeout; +}; + +static inline bool +hash_ip4_data_equal(const struct hash_ip4_elem *ip1, + const struct hash_ip4_elem *ip2) +{ + return ip1->ip == ip2->ip; +} + +static inline bool +hash_ip4_data_isnull(const struct hash_ip4_elem *elem) +{ + return elem->ip == 0; +} + +static inline void +hash_ip4_data_copy(struct hash_ip4_elem *dst, const struct hash_ip4_elem *src) +{ + dst->ip = src->ip; +} + +static inline void +hash_ip4_data_swap(struct hash_ip4_elem *dst, struct hash_ip4_elem *src) +{ + swap(dst->ip, src->ip); +} + +/* Zero valued IP addresses cannot be stored */ +static inline void +hash_ip4_data_zero_out(struct hash_ip4_elem *elem) +{ + elem->ip = 0; +} + +static inline bool +hash_ip4_data_list(struct sk_buff *skb, const struct hash_ip4_elem *data) +{ + NLA_PUT_IPADDR4(skb, IPSET_ATTR_IP, data->ip); + return 0; + +nla_put_failure: + return 1; +} + +static inline bool +hash_ip4_data_tlist(struct sk_buff *skb, const struct hash_ip4_elem *data) +{ + const struct hash_ip4_telem *tdata = + (const struct hash_ip4_telem *)data; + + NLA_PUT_IPADDR4(skb, IPSET_ATTR_IP, tdata->ip); + 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_NETMASK +#define PF 4 +#define HOST_MASK 32 +#include "ip_set_ahash.h" + +static int +hash_ip4_kadt(struct ip_set *set, const struct sk_buff *skb, + enum ipset_adt adt, u8 pf, u8 dim, u8 flags) +{ + const struct ip_set_hash *h = set->data; + ipset_adtfn adtfn = set->variant->adt[adt]; + u32 ip; + + ip4addrptr(skb, flags & IPSET_DIM_ONE_SRC, &ip); + ip &= NETMASK(h->netmask); + if (ip == 0) + return -EINVAL; + + return adtfn(set, &ip, h->timeout); +} + +static const struct nla_policy hash_ip4_adt_policy[IPSET_ATTR_ADT_MAX + 1] = { + [IPSET_ATTR_IP] = { .type = NLA_NESTED }, + [IPSET_ATTR_IP_TO] = { .type = NLA_NESTED }, + [IPSET_ATTR_CIDR] = { .type = NLA_U8 }, + [IPSET_ATTR_TIMEOUT] = { .type = NLA_U32 }, + [IPSET_ATTR_LINENO] = { .type = NLA_U32 }, +}; + +static int +hash_ip4_uadt(struct ip_set *set, struct nlattr *head, int len, + enum ipset_adt adt, u32 *lineno, u32 flags) +{ + const struct ip_set_hash *h = set->data; + struct nlattr *tb[IPSET_ATTR_ADT_MAX+1]; + ipset_adtfn adtfn = set->variant->adt[adt]; + u32 ip, nip, ip_to, hosts, timeout = h->timeout; + int ret = 0; + + if (nla_parse(tb, IPSET_ATTR_ADT_MAX, head, len, + hash_ip4_adt_policy)) + return -IPSET_ERR_PROTOCOL; + + if (tb[IPSET_ATTR_LINENO]) + *lineno = nla_get_u32(tb[IPSET_ATTR_LINENO]); + + ret = ip_set_get_ipaddr4(tb, IPSET_ATTR_IP, &ip); + if (ret) + return ret; + + ip &= NETMASK(h->netmask); + if (ip == 0) + return -IPSET_ERR_HASH_ELEM; + + if (tb[IPSET_ATTR_TIMEOUT]) { + if (!with_timeout(h->timeout)) + return -IPSET_ERR_TIMEOUT; + timeout = ip_set_timeout_uget(tb[IPSET_ATTR_TIMEOUT]); + } + + if (adt == IPSET_TEST) + return adtfn(set, &ip, timeout); + + ip = ntohl(ip); + if (tb[IPSET_ATTR_IP_TO]) { + ret = ip_set_get_ipaddr4(tb, IPSET_ATTR_IP_TO, &ip_to); + if (ret) + return ret; + ip_to = ntohl(ip_to); + if (ip > ip_to) + swap(ip, ip_to); + } else if (tb[IPSET_ATTR_CIDR]) { + u8 cidr = nla_get_u8(tb[IPSET_ATTR_CIDR]); + + if (cidr > 32) + return -IPSET_ERR_INVALID_CIDR; + ip &= HOSTMASK(cidr); + ip_to = ip | ~HOSTMASK(cidr); + } else + ip_to = ip; + + hosts = h->netmask == 32 ? 1 : 2 << (32 - h->netmask - 1); + + for (; !before(ip_to, ip); ip += hosts) { + nip = htonl(ip); + ret = adtfn(set, &nip, timeout); + + if (ret && !ip_set_eexist(ret, flags)) + return ret; + else + ret = 0; + } + return ret; +} + +static bool +hash_ip_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 + && x->netmask == y->netmask; +} + +/* The type variant functions: IPv6 */ + +struct hash_ip6_elem { + union nf_inet_addr ip; +}; + +struct hash_ip6_telem { + union nf_inet_addr ip; + unsigned long timeout; +}; + +static inline bool +hash_ip6_data_equal(const struct hash_ip6_elem *ip1, + const struct hash_ip6_elem *ip2) +{ + return ipv6_addr_cmp(&ip1->ip.in6, &ip2->ip.in6) == 0; +} + +static inline bool +hash_ip6_data_isnull(const struct hash_ip6_elem *elem) +{ + return ipv6_addr_any(&elem->ip.in6); +} + +static inline void +hash_ip6_data_copy(struct hash_ip6_elem *dst, const struct hash_ip6_elem *src) +{ + ipv6_addr_copy(&dst->ip.in6, &src->ip.in6); +} + +static inline void +hash_ip6_data_swap(struct hash_ip6_elem *dst, struct hash_ip6_elem *src) +{ + struct in6_addr tmp; + + ipv6_addr_copy(&tmp, &dst->ip.in6); + ipv6_addr_copy(&dst->ip.in6, &src->ip.in6); + ipv6_addr_copy(&src->ip.in6, &tmp); +} + +static inline void +hash_ip6_data_zero_out(struct hash_ip6_elem *elem) +{ + ipv6_addr_set(&elem->ip.in6, 0, 0, 0, 0); +} + +static inline void +ip6_netmask(union nf_inet_addr *ip, u8 prefix) +{ + ip->ip6[0] &= NETMASK6(prefix)[0]; + ip->ip6[1] &= NETMASK6(prefix)[1]; + ip->ip6[2] &= NETMASK6(prefix)[2]; + ip->ip6[3] &= NETMASK6(prefix)[3]; +} + +static inline bool +hash_ip6_data_list(struct sk_buff *skb, const struct hash_ip6_elem *data) +{ + NLA_PUT_IPADDR6(skb, IPSET_ATTR_IP, &data->ip); + return 0; + +nla_put_failure: + return 1; +} + +static inline bool +hash_ip6_data_tlist(struct sk_buff *skb, const struct hash_ip6_elem *data) +{ + const struct hash_ip6_telem *e = + (const struct hash_ip6_telem *)data; + + NLA_PUT_IPADDR6(skb, IPSET_ATTR_IP, &e->ip); + 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 "ip_set_ahash.h" + +static int +hash_ip6_kadt(struct ip_set *set, const struct sk_buff *skb, + enum ipset_adt adt, u8 pf, u8 dim, u8 flags) +{ + const struct ip_set_hash *h = set->data; + ipset_adtfn adtfn = set->variant->adt[adt]; + union nf_inet_addr ip; + + ip6addrptr(skb, flags & IPSET_DIM_ONE_SRC, &ip.in6); + ip6_netmask(&ip, h->netmask); + if (ipv6_addr_any(&ip.in6)) + return -EINVAL; + + return adtfn(set, &ip, h->timeout); +} + +static const struct nla_policy hash_ip6_adt_policy[IPSET_ATTR_ADT_MAX + 1] = { + [IPSET_ATTR_IP] = { .type = NLA_NESTED }, + [IPSET_ATTR_TIMEOUT] = { .type = NLA_U32 }, + [IPSET_ATTR_LINENO] = { .type = NLA_U32 }, +}; + +static int +hash_ip6_uadt(struct ip_set *set, struct nlattr *head, int len, + enum ipset_adt adt, u32 *lineno, u32 flags) +{ + const struct ip_set_hash *h = set->data; + struct nlattr *tb[IPSET_ATTR_ADT_MAX+1]; + ipset_adtfn adtfn = set->variant->adt[adt]; + union nf_inet_addr ip; + u32 timeout = h->timeout; + int ret; + + if (nla_parse(tb, IPSET_ATTR_ADT_MAX, head, len, + hash_ip6_adt_policy)) + return -IPSET_ERR_PROTOCOL; + + if (tb[IPSET_ATTR_LINENO]) + *lineno = nla_get_u32(tb[IPSET_ATTR_LINENO]); + + ret = ip_set_get_ipaddr6(tb, IPSET_ATTR_IP, &ip); + if (ret) + return ret; + + ip6_netmask(&ip, h->netmask); + if (ipv6_addr_any(&ip.in6)) + return -IPSET_ERR_HASH_ELEM; + + if (tb[IPSET_ATTR_TIMEOUT]) { + if (!with_timeout(h->timeout)) + return -IPSET_ERR_TIMEOUT; + timeout = ip_set_timeout_uget(tb[IPSET_ATTR_TIMEOUT]); + } + + ret = adtfn(set, &ip, timeout); + + return ip_set_eexist(ret, flags) ? 0 : ret; +} + +/* Create hash:ip type of sets */ + +static const struct nla_policy +hash_ip_create_policy[IPSET_ATTR_CREATE_MAX+1] = { + [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 }, + [IPSET_ATTR_NETMASK] = { .type = NLA_U8 }, +}; + +static int +hash_ip_create(struct ip_set *set, struct nlattr *head, int len, u32 flags) +{ + struct nlattr *tb[IPSET_ATTR_CREATE_MAX+1]; + u32 hashsize = IPSET_DEFAULT_HASHSIZE, maxelem = IPSET_DEFAULT_MAXELEM; + u8 netmask, hbits; + struct ip_set_hash *h; + + if (!(set->family == AF_INET || set->family == AF_INET6)) + return -IPSET_ERR_INVALID_FAMILY; + netmask = set->family == AF_INET ? 32 : 128; + pr_debug("Create set %s with family %s", + set->name, set->family == AF_INET ? "inet" : "inet6"); + + if (nla_parse(tb, IPSET_ATTR_CREATE_MAX, head, len, + hash_ip_create_policy)) + 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]); + + if (tb[IPSET_ATTR_NETMASK]) { + netmask = nla_get_u8(tb[IPSET_ATTR_NETMASK]); + + if ((set->family == AF_INET && netmask > 32) + || (set->family == AF_INET6 && netmask > 128) + || netmask == 0) + return -IPSET_ERR_INVALID_NETMASK; + } + + h = kzalloc(sizeof(*h), GFP_KERNEL); + if (!h) + return -ENOMEM; + + h->maxelem = maxelem; + h->netmask = netmask; + 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), + GFP_KERNEL); + if (!h->table) { + kfree(h); + return -ENOMEM; + } + h->table->htable_bits = hbits; + + 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_ip4_tvariant : &hash_ip6_tvariant; + + if (set->family == AF_INET) + hash_ip4_gc_init(set); + else + hash_ip6_gc_init(set); + } else { + set->variant = set->family == AF_INET + ? &hash_ip4_variant : &hash_ip6_variant; + } + + pr_debug("create %s hashsize %u (%u) maxelem %u: %p(%p)", + 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_ip_type __read_mostly = { + .name = "hash:ip", + .protocol = IPSET_PROTOCOL, + .features = IPSET_TYPE_IP, + .dimension = IPSET_DIM_ONE, + .family = AF_UNSPEC, + .revision = 0, + .create = hash_ip_create, + .me = THIS_MODULE, +}; + +static int __init +hash_ip_init(void) +{ + return ip_set_type_register(&hash_ip_type); +} + +static void __exit +hash_ip_fini(void) +{ + ip_set_type_unregister(&hash_ip_type); +} + +module_init(hash_ip_init); +module_exit(hash_ip_fini); diff --git a/extensions/ipset-5/ip_set_hash_ipport.c b/extensions/ipset-5/ip_set_hash_ipport.c new file mode 100644 index 0000000..57e35f3 --- /dev/null +++ b/extensions/ipset-5/ip_set_hash_ipport.c @@ -0,0 +1,569 @@ +/* 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 set type: the hash:ip,port type */ + +#include "ip_set_kernel.h" +#include "jhash.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "ip_set.h" +#include "ip_set_timeout.h" +#include "ip_set_getport.h" +#include "ip_set_hash.h" + +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; + u8 proto; + u8 padding; +}; + +/* Member elements with timeout support */ +struct hash_ipport4_telem { + u32 ip; + u16 port; + u8 proto; + u8 padding; + 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 + && ip1->proto == ip2->proto; +} + +static inline bool +hash_ipport4_data_isnull(const struct hash_ipport4_elem *elem) +{ + return elem->proto == 0; +} + +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->proto = src->proto; +} + +static inline void +hash_ipport4_data_swap(struct hash_ipport4_elem *dst, + struct hash_ipport4_elem *src) +{ + swap(dst->ip, src->ip); + swap(dst->port, src->port); + swap(dst->proto, src->proto); +} + +static inline void +hash_ipport4_data_zero_out(struct hash_ipport4_elem *elem) +{ + elem->proto = 0; +} + +static inline bool +hash_ipport4_data_list(struct sk_buff *skb, + const struct hash_ipport4_elem *data) +{ + NLA_PUT_IPADDR4(skb, IPSET_ATTR_IP, data->ip); + NLA_PUT_NET16(skb, IPSET_ATTR_PORT, data->port); + NLA_PUT_U8(skb, IPSET_ATTR_PROTO, data->proto); + 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_IPADDR4(skb, IPSET_ATTR_IP, tdata->ip); + NLA_PUT_NET16(skb, IPSET_ATTR_PORT, tdata->port); + NLA_PUT_U8(skb, IPSET_ATTR_PROTO, data->proto); + 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 "ip_set_ahash.h" + +static int +hash_ipport4_kadt(struct ip_set *set, const struct sk_buff *skb, + enum ipset_adt adt, u8 pf, u8 dim, u8 flags) +{ + const struct ip_set_hash *h = set->data; + ipset_adtfn adtfn = set->variant->adt[adt]; + struct hash_ipport4_elem data = { }; + + if (!get_ip4_port(skb, flags & IPSET_DIM_TWO_SRC, + &data.port, &data.proto)) + return -EINVAL; + + ip4addrptr(skb, flags & IPSET_DIM_ONE_SRC, &data.ip); + + return adtfn(set, &data, h->timeout); +} + +static const struct nla_policy +hash_ipport_adt_policy[IPSET_ATTR_ADT_MAX + 1] = { + [IPSET_ATTR_IP] = { .type = NLA_NESTED }, + [IPSET_ATTR_IP_TO] = { .type = NLA_NESTED }, + [IPSET_ATTR_PORT] = { .type = NLA_U16 }, + [IPSET_ATTR_PORT_TO] = { .type = NLA_U16 }, + [IPSET_ATTR_CIDR] = { .type = NLA_U8 }, + [IPSET_ATTR_PROTO] = { .type = NLA_U8 }, + [IPSET_ATTR_TIMEOUT] = { .type = NLA_U32 }, + [IPSET_ATTR_LINENO] = { .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) +{ + const struct ip_set_hash *h = set->data; + struct nlattr *tb[IPSET_ATTR_ADT_MAX+1]; + ipset_adtfn adtfn = set->variant->adt[adt]; + struct hash_ipport4_elem data = { }; + u32 ip, ip_to, p, port, port_to; + u32 timeout = h->timeout; + int ret; + + if (nla_parse(tb, IPSET_ATTR_ADT_MAX, head, len, + hash_ipport_adt_policy)) + return -IPSET_ERR_PROTOCOL; + + if (tb[IPSET_ATTR_LINENO]) + *lineno = nla_get_u32(tb[IPSET_ATTR_LINENO]); + + ret = ip_set_get_ipaddr4(tb, IPSET_ATTR_IP, &data.ip); + if (ret) + return ret; + + if (tb[IPSET_ATTR_PORT]) + data.port = ip_set_get_n16(tb[IPSET_ATTR_PORT]); + else + return -IPSET_ERR_PROTOCOL; + + if (tb[IPSET_ATTR_PROTO]) { + data.proto = nla_get_u8(tb[IPSET_ATTR_PROTO]); + + if (data.proto == 0) + return -IPSET_ERR_INVALID_PROTO; + } else + return -IPSET_ERR_MISSING_PROTO; + + switch (data.proto) { + case IPPROTO_UDP: + case IPPROTO_TCP: + case IPPROTO_ICMP: + break; + default: + data.port = 0; + break; + } + + if (tb[IPSET_ATTR_TIMEOUT]) { + if (!with_timeout(h->timeout)) + return -IPSET_ERR_TIMEOUT; + timeout = ip_set_timeout_uget(tb[IPSET_ATTR_TIMEOUT]); + } + + if (adt == IPSET_TEST + || !(data.proto == IPPROTO_TCP || data.proto == IPPROTO_UDP) + || !(tb[IPSET_ATTR_IP_TO] || tb[IPSET_ATTR_CIDR] + || tb[IPSET_ATTR_PORT_TO])) { + ret = adtfn(set, &data, timeout); + return ip_set_eexist(ret, flags) ? 0 : ret; + } + + ip = ntohl(data.ip); + if (tb[IPSET_ATTR_IP_TO]) { + ret = ip_set_get_ipaddr4(tb, IPSET_ATTR_IP_TO, &ip_to); + if (ret) + return ret; + ip_to = ntohl(ip_to); + if (ip > ip_to) + swap(ip, ip_to); + } else if (tb[IPSET_ATTR_CIDR]) { + u8 cidr = nla_get_u8(tb[IPSET_ATTR_CIDR]); + + if (cidr > 32) + return -IPSET_ERR_INVALID_CIDR; + ip &= HOSTMASK(cidr); + ip_to = ip | ~HOSTMASK(cidr); + } else + ip_to = ip; + + port = ntohs(data.port); + if (tb[IPSET_ATTR_PORT_TO]) { + port_to = ip_set_get_h16(tb[IPSET_ATTR_PORT_TO]); + if (port > port_to) + swap(port, port_to); + } else + port_to = port; + + for (; !before(ip_to, ip); ip++) + for (p = port; p <= port_to; p++) { + data.ip = htonl(ip); + data.port = htons(p); + ret = adtfn(set, &data, timeout); + + if (ret && !ip_set_eexist(ret, flags)) + return ret; + else + ret = 0; + } + return ret; +} + +static bool +hash_ipport_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_ipport6_elem { + union nf_inet_addr ip; + u16 port; + u8 proto; + u8 padding; +}; + +struct hash_ipport6_telem { + union nf_inet_addr ip; + u16 port; + u8 proto; + u8 padding; + 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 + && ip1->proto == ip2->proto; +} + +static inline bool +hash_ipport6_data_isnull(const struct hash_ipport6_elem *elem) +{ + return elem->proto == 0; +} + +static inline void +hash_ipport6_data_copy(struct hash_ipport6_elem *dst, + const struct hash_ipport6_elem *src) +{ + memcpy(dst, src, sizeof(*dst)); +} + +static inline void +hash_ipport6_data_swap(struct hash_ipport6_elem *dst, + struct hash_ipport6_elem *src) +{ + struct hash_ipport6_elem tmp; + + memcpy(&tmp, dst, sizeof(tmp)); + memcpy(dst, src, sizeof(tmp)); + memcpy(src, &tmp, sizeof(tmp)); +} + +static inline void +hash_ipport6_data_zero_out(struct hash_ipport6_elem *elem) +{ + elem->proto = 0; +} + +static inline bool +hash_ipport6_data_list(struct sk_buff *skb, + const struct hash_ipport6_elem *data) +{ + NLA_PUT_IPADDR6(skb, IPSET_ATTR_IP, &data->ip); + NLA_PUT_NET16(skb, IPSET_ATTR_PORT, data->port); + NLA_PUT_U8(skb, IPSET_ATTR_PROTO, data->proto); + 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_IPADDR6(skb, IPSET_ATTR_IP, &e->ip); + NLA_PUT_NET16(skb, IPSET_ATTR_PORT, data->port); + NLA_PUT_U8(skb, IPSET_ATTR_PROTO, data->proto); + 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 "ip_set_ahash.h" + +static int +hash_ipport6_kadt(struct ip_set *set, const struct sk_buff *skb, + enum ipset_adt adt, u8 pf, u8 dim, u8 flags) +{ + const struct ip_set_hash *h = set->data; + ipset_adtfn adtfn = set->variant->adt[adt]; + struct hash_ipport6_elem data = { }; + + if (!get_ip6_port(skb, flags & IPSET_DIM_TWO_SRC, + &data.port, &data.proto)) + return -EINVAL; + + ip6addrptr(skb, flags & IPSET_DIM_ONE_SRC, &data.ip.in6); + + return adtfn(set, &data, h->timeout); +} + +static int +hash_ipport6_uadt(struct ip_set *set, struct nlattr *head, int len, + enum ipset_adt adt, u32 *lineno, u32 flags) +{ + const struct ip_set_hash *h = set->data; + struct nlattr *tb[IPSET_ATTR_ADT_MAX+1]; + ipset_adtfn adtfn = set->variant->adt[adt]; + struct hash_ipport6_elem data = { }; + u32 port, port_to; + u32 timeout = h->timeout; + int ret; + + if (nla_parse(tb, IPSET_ATTR_ADT_MAX, head, len, + hash_ipport_adt_policy)) + return -IPSET_ERR_PROTOCOL; + + 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_PORT]) + data.port = ip_set_get_n16(tb[IPSET_ATTR_PORT]); + else + return -IPSET_ERR_PROTOCOL; + + if (tb[IPSET_ATTR_PROTO]) { + data.proto = nla_get_u8(tb[IPSET_ATTR_PROTO]); + + if (data.proto == 0) + return -IPSET_ERR_INVALID_PROTO; + } else + return -IPSET_ERR_MISSING_PROTO; + + switch (data.proto) { + case IPPROTO_UDP: + case IPPROTO_TCP: + case IPPROTO_ICMPV6: + break; + default: + data.port = 0; + break; + } + + if (tb[IPSET_ATTR_TIMEOUT]) { + if (!with_timeout(h->timeout)) + return -IPSET_ERR_TIMEOUT; + timeout = ip_set_timeout_uget(tb[IPSET_ATTR_TIMEOUT]); + } + + if (adt == IPSET_TEST + || !(data.proto == IPPROTO_TCP || data.proto == IPPROTO_UDP) + || !tb[IPSET_ATTR_PORT_TO]) { + ret = adtfn(set, &data, timeout); + return ip_set_eexist(ret, flags) ? 0 : ret; + } + + port = ntohs(data.port); + port_to = ip_set_get_h16(tb[IPSET_ATTR_PORT_TO]); + if (port > port_to) + swap(port, port_to); + + for (; port <= port_to; port++) { + data.port = htons(port); + ret = adtfn(set, &data, timeout); + + if (ret && !ip_set_eexist(ret, flags)) + return ret; + else + ret = 0; + } + return ret; +} + +/* Create hash:ip type of sets */ + +static const struct nla_policy +hash_ipport_create_policy[IPSET_ATTR_CREATE_MAX+1] = { + [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 }, +}; + +static int +hash_ipport_create(struct ip_set *set, struct nlattr *head, int len, u32 flags) +{ + struct nlattr *tb[IPSET_ATTR_CREATE_MAX+1]; + 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 (nla_parse(tb, IPSET_ATTR_CREATE_MAX, head, len, + hash_ipport_create_policy)) + 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), 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), + GFP_KERNEL); + if (!h->table) { + kfree(h); + return -ENOMEM; + } + h->table->htable_bits = hbits; + + 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->table->htable_bits), + h->table->htable_bits, h->maxelem, set->data, h->table); + + return 0; +} + +static struct ip_set_type hash_ipport_type __read_mostly = { + .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); +} + +module_init(hash_ipport_init); +module_exit(hash_ipport_fini); diff --git a/extensions/ipset-5/ip_set_hash_ipportip.c b/extensions/ipset-5/ip_set_hash_ipportip.c new file mode 100644 index 0000000..f9a8802 --- /dev/null +++ b/extensions/ipset-5/ip_set_hash_ipportip.c @@ -0,0 +1,590 @@ +/* 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 set type: the hash:ip,port,ip type */ + +#include "ip_set_kernel.h" +#include "jhash.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "ip_set.h" +#include "ip_set_timeout.h" +#include "ip_set_getport.h" +#include "ip_set_hash.h" + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Jozsef Kadlecsik "); +MODULE_DESCRIPTION("hash:ip,port,ip type of IP sets"); +MODULE_ALIAS("ip_set_hash:ip,port,ip"); + +/* Type specific function prefix */ +#define TYPE hash_ipportip + +static bool +hash_ipportip_same_set(const struct ip_set *a, const struct ip_set *b); + +#define hash_ipportip4_same_set hash_ipportip_same_set +#define hash_ipportip6_same_set hash_ipportip_same_set + +/* The type variant functions: IPv4 */ + +/* Member elements without timeout */ +struct hash_ipportip4_elem { + u32 ip; + u32 ip2; + u16 port; + u8 proto; + u8 padding; +}; + +/* Member elements with timeout support */ +struct hash_ipportip4_telem { + u32 ip; + u32 ip2; + u16 port; + u8 proto; + u8 padding; + unsigned long timeout; +}; + +static inline bool +hash_ipportip4_data_equal(const struct hash_ipportip4_elem *ip1, + const struct hash_ipportip4_elem *ip2) +{ + return ip1->ip == ip2->ip + && ip1->ip2 == ip2->ip2 + && ip1->port == ip2->port + && ip1->proto == ip2->proto; +} + +static inline bool +hash_ipportip4_data_isnull(const struct hash_ipportip4_elem *elem) +{ + return elem->proto == 0; +} + +static inline void +hash_ipportip4_data_copy(struct hash_ipportip4_elem *dst, + const struct hash_ipportip4_elem *src) +{ + memcpy(dst, src, sizeof(*dst)); +} + +static inline void +hash_ipportip4_data_swap(struct hash_ipportip4_elem *dst, + struct hash_ipportip4_elem *src) +{ + struct hash_ipportip4_elem tmp; + + memcpy(&tmp, dst, sizeof(tmp)); + memcpy(dst, src, sizeof(tmp)); + memcpy(src, &tmp, sizeof(tmp)); +} + +static inline void +hash_ipportip4_data_zero_out(struct hash_ipportip4_elem *elem) +{ + elem->proto = 0; +} + +static inline bool +hash_ipportip4_data_list(struct sk_buff *skb, + const struct hash_ipportip4_elem *data) +{ + NLA_PUT_IPADDR4(skb, IPSET_ATTR_IP, data->ip); + NLA_PUT_IPADDR4(skb, IPSET_ATTR_IP2, data->ip2); + NLA_PUT_NET16(skb, IPSET_ATTR_PORT, data->port); + NLA_PUT_U8(skb, IPSET_ATTR_PROTO, data->proto); + return 0; + +nla_put_failure: + return 1; +} + +static inline bool +hash_ipportip4_data_tlist(struct sk_buff *skb, + const struct hash_ipportip4_elem *data) +{ + const struct hash_ipportip4_telem *tdata = + (const struct hash_ipportip4_telem *)data; + + NLA_PUT_IPADDR4(skb, IPSET_ATTR_IP, tdata->ip); + NLA_PUT_IPADDR4(skb, IPSET_ATTR_IP2, tdata->ip2); + NLA_PUT_NET16(skb, IPSET_ATTR_PORT, tdata->port); + NLA_PUT_U8(skb, IPSET_ATTR_PROTO, data->proto); + 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 "ip_set_ahash.h" + +static int +hash_ipportip4_kadt(struct ip_set *set, const struct sk_buff *skb, + enum ipset_adt adt, u8 pf, u8 dim, u8 flags) +{ + const struct ip_set_hash *h = set->data; + ipset_adtfn adtfn = set->variant->adt[adt]; + struct hash_ipportip4_elem data = { }; + + if (!get_ip4_port(skb, flags & IPSET_DIM_TWO_SRC, + &data.port, &data.proto)) + return -EINVAL; + + ip4addrptr(skb, flags & IPSET_DIM_ONE_SRC, &data.ip); + ip4addrptr(skb, flags & IPSET_DIM_THREE_SRC, &data.ip2); + + return adtfn(set, &data, h->timeout); +} + +static const struct nla_policy +hash_ipportip_adt_policy[IPSET_ATTR_ADT_MAX + 1] = { + [IPSET_ATTR_IP] = { .type = NLA_NESTED }, + [IPSET_ATTR_IP_TO] = { .type = NLA_NESTED }, + [IPSET_ATTR_IP2] = { .type = NLA_NESTED }, + [IPSET_ATTR_PORT] = { .type = NLA_U16 }, + [IPSET_ATTR_PORT_TO] = { .type = NLA_U16 }, + [IPSET_ATTR_CIDR] = { .type = NLA_U8 }, + [IPSET_ATTR_PROTO] = { .type = NLA_U8 }, + [IPSET_ATTR_TIMEOUT] = { .type = NLA_U32 }, + [IPSET_ATTR_LINENO] = { .type = NLA_U32 }, +}; + +static int +hash_ipportip4_uadt(struct ip_set *set, struct nlattr *head, int len, + enum ipset_adt adt, u32 *lineno, u32 flags) +{ + const struct ip_set_hash *h = set->data; + struct nlattr *tb[IPSET_ATTR_ADT_MAX+1]; + ipset_adtfn adtfn = set->variant->adt[adt]; + struct hash_ipportip4_elem data = { }; + u32 ip, ip_to, p, port, port_to; + u32 timeout = h->timeout; + int ret; + + if (nla_parse(tb, IPSET_ATTR_ADT_MAX, head, len, + hash_ipportip_adt_policy)) + return -IPSET_ERR_PROTOCOL; + + if (tb[IPSET_ATTR_LINENO]) + *lineno = nla_get_u32(tb[IPSET_ATTR_LINENO]); + + ret = ip_set_get_ipaddr4(tb, IPSET_ATTR_IP, &data.ip); + if (ret) + return ret; + + ret = ip_set_get_ipaddr4(tb, IPSET_ATTR_IP2, &data.ip2); + if (ret) + return ret; + + if (tb[IPSET_ATTR_PORT]) + data.port = ip_set_get_n16(tb[IPSET_ATTR_PORT]); + else + return -IPSET_ERR_PROTOCOL; + + if (tb[IPSET_ATTR_PROTO]) { + data.proto = nla_get_u8(tb[IPSET_ATTR_PROTO]); + + if (data.proto == 0) + return -IPSET_ERR_INVALID_PROTO; + } else + return -IPSET_ERR_MISSING_PROTO; + + switch (data.proto) { + case IPPROTO_UDP: + case IPPROTO_TCP: + case IPPROTO_ICMP: + break; + default: + data.port = 0; + break; + } + + if (tb[IPSET_ATTR_TIMEOUT]) { + if (!with_timeout(h->timeout)) + return -IPSET_ERR_TIMEOUT; + timeout = ip_set_timeout_uget(tb[IPSET_ATTR_TIMEOUT]); + } + + if (adt == IPSET_TEST + || !(data.proto == IPPROTO_TCP || data.proto == IPPROTO_UDP) + || !(tb[IPSET_ATTR_IP_TO] || tb[IPSET_ATTR_CIDR] + || tb[IPSET_ATTR_PORT_TO])) { + ret = adtfn(set, &data, timeout); + return ip_set_eexist(ret, flags) ? 0 : ret; + } + + ip = ntohl(data.ip); + if (tb[IPSET_ATTR_IP_TO]) { + ret = ip_set_get_ipaddr4(tb, IPSET_ATTR_IP_TO, &ip_to); + if (ret) + return ret; + ip_to = ntohl(ip_to); + if (ip > ip_to) + swap(ip, ip_to); + } else if (tb[IPSET_ATTR_CIDR]) { + u8 cidr = nla_get_u8(tb[IPSET_ATTR_CIDR]); + + if (cidr > 32) + return -IPSET_ERR_INVALID_CIDR; + ip &= HOSTMASK(cidr); + ip_to = ip | ~HOSTMASK(cidr); + } else + ip_to = ip; + + port = ntohs(data.port); + if (tb[IPSET_ATTR_PORT_TO]) { + port_to = ip_set_get_h16(tb[IPSET_ATTR_PORT_TO]); + if (port > port_to) + swap(port, port_to); + } else + port_to = port; + + for (; !before(ip_to, ip); ip++) + for (p = port; p <= port_to; p++) { + data.ip = htonl(ip); + data.port = htons(p); + ret = adtfn(set, &data, timeout); + + if (ret && !ip_set_eexist(ret, flags)) + return ret; + else + ret = 0; + } + return ret; +} + +static bool +hash_ipportip_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_ipportip6_elem { + union nf_inet_addr ip; + union nf_inet_addr ip2; + u16 port; + u8 proto; + u8 padding; +}; + +struct hash_ipportip6_telem { + union nf_inet_addr ip; + union nf_inet_addr ip2; + u16 port; + u8 proto; + u8 padding; + unsigned long timeout; +}; + +static inline bool +hash_ipportip6_data_equal(const struct hash_ipportip6_elem *ip1, + const struct hash_ipportip6_elem *ip2) +{ + return ipv6_addr_cmp(&ip1->ip.in6, &ip2->ip.in6) == 0 + && ipv6_addr_cmp(&ip1->ip2.in6, &ip2->ip2.in6) == 0 + && ip1->port == ip2->port + && ip1->proto == ip2->proto; +} + +static inline bool +hash_ipportip6_data_isnull(const struct hash_ipportip6_elem *elem) +{ + return elem->proto == 0; +} + +static inline void +hash_ipportip6_data_copy(struct hash_ipportip6_elem *dst, + const struct hash_ipportip6_elem *src) +{ + memcpy(dst, src, sizeof(*dst)); +} + +static inline void +hash_ipportip6_data_swap(struct hash_ipportip6_elem *dst, + struct hash_ipportip6_elem *src) +{ + struct hash_ipportip6_elem tmp; + + memcpy(&tmp, dst, sizeof(tmp)); + memcpy(dst, src, sizeof(tmp)); + memcpy(src, &tmp, sizeof(tmp)); +} + +static inline void +hash_ipportip6_data_zero_out(struct hash_ipportip6_elem *elem) +{ + elem->proto = 0; +} + +static inline bool +hash_ipportip6_data_list(struct sk_buff *skb, + const struct hash_ipportip6_elem *data) +{ + NLA_PUT_IPADDR6(skb, IPSET_ATTR_IP, &data->ip); + NLA_PUT_IPADDR6(skb, IPSET_ATTR_IP2, &data->ip2); + NLA_PUT_NET16(skb, IPSET_ATTR_PORT, data->port); + NLA_PUT_U8(skb, IPSET_ATTR_PROTO, data->proto); + return 0; + +nla_put_failure: + return 1; +} + +static inline bool +hash_ipportip6_data_tlist(struct sk_buff *skb, + const struct hash_ipportip6_elem *data) +{ + const struct hash_ipportip6_telem *e = + (const struct hash_ipportip6_telem *)data; + + NLA_PUT_IPADDR6(skb, IPSET_ATTR_IP, &e->ip); + NLA_PUT_IPADDR6(skb, IPSET_ATTR_IP2, &data->ip2); + NLA_PUT_NET16(skb, IPSET_ATTR_PORT, data->port); + NLA_PUT_U8(skb, IPSET_ATTR_PROTO, data->proto); + 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 "ip_set_ahash.h" + +static int +hash_ipportip6_kadt(struct ip_set *set, const struct sk_buff *skb, + enum ipset_adt adt, u8 pf, u8 dim, u8 flags) +{ + const struct ip_set_hash *h = set->data; + ipset_adtfn adtfn = set->variant->adt[adt]; + struct hash_ipportip6_elem data = { }; + + if (!get_ip6_port(skb, flags & IPSET_DIM_TWO_SRC, + &data.port, &data.proto)) + return -EINVAL; + + ip6addrptr(skb, flags & IPSET_DIM_ONE_SRC, &data.ip.in6); + ip6addrptr(skb, flags & IPSET_DIM_THREE_SRC, &data.ip2.in6); + + return adtfn(set, &data, h->timeout); +} + +static int +hash_ipportip6_uadt(struct ip_set *set, struct nlattr *head, int len, + enum ipset_adt adt, u32 *lineno, u32 flags) +{ + const struct ip_set_hash *h = set->data; + struct nlattr *tb[IPSET_ATTR_ADT_MAX+1]; + ipset_adtfn adtfn = set->variant->adt[adt]; + struct hash_ipportip6_elem data = { }; + u32 port, port_to; + u32 timeout = h->timeout; + int ret; + + if (nla_parse(tb, IPSET_ATTR_ADT_MAX, head, len, + hash_ipportip_adt_policy)) + return -IPSET_ERR_PROTOCOL; + + 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; + + ret = ip_set_get_ipaddr6(tb, IPSET_ATTR_IP2, &data.ip2); + if (ret) + return ret; + + if (tb[IPSET_ATTR_PORT]) + data.port = ip_set_get_n16(tb[IPSET_ATTR_PORT]); + else + return -IPSET_ERR_PROTOCOL; + + if (tb[IPSET_ATTR_PROTO]) { + data.proto = nla_get_u8(tb[IPSET_ATTR_PROTO]); + + if (data.proto == 0) + return -IPSET_ERR_INVALID_PROTO; + } else + return -IPSET_ERR_MISSING_PROTO; + + switch (data.proto) { + case IPPROTO_UDP: + case IPPROTO_TCP: + case IPPROTO_ICMPV6: + break; + default: + data.port = 0; + break; + } + + if (tb[IPSET_ATTR_TIMEOUT]) { + if (!with_timeout(h->timeout)) + return -IPSET_ERR_TIMEOUT; + timeout = ip_set_timeout_uget(tb[IPSET_ATTR_TIMEOUT]); + } + + if (adt == IPSET_TEST + || !(data.proto == IPPROTO_TCP || data.proto == IPPROTO_UDP) + || !tb[IPSET_ATTR_PORT_TO]) { + ret = adtfn(set, &data, timeout); + return ip_set_eexist(ret, flags) ? 0 : ret; + } + + port = ntohs(data.port); + port_to = ip_set_get_h16(tb[IPSET_ATTR_PORT_TO]); + if (port > port_to) + swap(port, port_to); + + for (; port <= port_to; port++) { + data.port = htons(port); + ret = adtfn(set, &data, timeout); + + if (ret && !ip_set_eexist(ret, flags)) + return ret; + else + ret = 0; + } + return ret; +} + +/* Create hash:ip type of sets */ + +static const struct nla_policy +hash_ipportip_create_policy[IPSET_ATTR_CREATE_MAX+1] = { + [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_ipportip_create(struct ip_set *set, struct nlattr *head, + int len, u32 flags) +{ + struct nlattr *tb[IPSET_ATTR_CREATE_MAX+1]; + 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 (nla_parse(tb, IPSET_ATTR_CREATE_MAX, head, len, + hash_ipportip_create_policy)) + 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), 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), + GFP_KERNEL); + if (!h->table) { + kfree(h); + return -ENOMEM; + } + h->table->htable_bits = hbits; + + 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_ipportip4_tvariant : &hash_ipportip6_tvariant; + + if (set->family == AF_INET) + hash_ipportip4_gc_init(set); + else + hash_ipportip6_gc_init(set); + } else { + set->variant = set->family == AF_INET + ? &hash_ipportip4_variant : &hash_ipportip6_variant; + } + + pr_debug("create %s hashsize %u (%u) maxelem %u: %p(%p)", + 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_ipportip_type __read_mostly = { + .name = "hash:ip,port,ip", + .protocol = IPSET_PROTOCOL, + .features = IPSET_TYPE_IP | IPSET_TYPE_PORT | IPSET_TYPE_IP2, + .dimension = IPSET_DIM_THREE, + .family = AF_UNSPEC, + .revision = 0, + .create = hash_ipportip_create, + .me = THIS_MODULE, +}; + +static int __init +hash_ipportip_init(void) +{ + return ip_set_type_register(&hash_ipportip_type); +} + +static void __exit +hash_ipportip_fini(void) +{ + ip_set_type_unregister(&hash_ipportip_type); +} + +module_init(hash_ipportip_init); +module_exit(hash_ipportip_fini); diff --git a/extensions/ipset-5/ip_set_hash_ipportnet.c b/extensions/ipset-5/ip_set_hash_ipportnet.c new file mode 100644 index 0000000..92f22cf --- /dev/null +++ b/extensions/ipset-5/ip_set_hash_ipportnet.c @@ -0,0 +1,656 @@ +/* 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 set type: the hash:ip,port,net type */ + +#include "ip_set_kernel.h" +#include "jhash.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "ip_set.h" +#include "ip_set_timeout.h" +#include "ip_set_getport.h" +#include "ip_set_hash.h" + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Jozsef Kadlecsik "); +MODULE_DESCRIPTION("hash:ip,port,net type of IP sets"); +MODULE_ALIAS("ip_set_hash:ip,port,net"); + +/* Type specific function prefix */ +#define TYPE hash_ipportnet + +static bool +hash_ipportnet_same_set(const struct ip_set *a, const struct ip_set *b); + +#define hash_ipportnet4_same_set hash_ipportnet_same_set +#define hash_ipportnet6_same_set hash_ipportnet_same_set + +/* The type variant functions: IPv4 */ + +/* Member elements without timeout */ +struct hash_ipportnet4_elem { + u32 ip; + u32 ip2; + u16 port; + u8 cidr; + u8 proto; +}; + +/* Member elements with timeout support */ +struct hash_ipportnet4_telem { + u32 ip; + u32 ip2; + u16 port; + u8 cidr; + u8 proto; + unsigned long timeout; +}; + +static inline bool +hash_ipportnet4_data_equal(const struct hash_ipportnet4_elem *ip1, + const struct hash_ipportnet4_elem *ip2) +{ + return ip1->ip == ip2->ip + && ip1->ip2 == ip2->ip2 + && ip1->cidr == ip2->cidr + && ip1->port == ip2->port + && ip1->proto == ip2->proto; +} + +static inline bool +hash_ipportnet4_data_isnull(const struct hash_ipportnet4_elem *elem) +{ + return elem->proto == 0; +} + +static inline void +hash_ipportnet4_data_copy(struct hash_ipportnet4_elem *dst, + const struct hash_ipportnet4_elem *src) +{ + memcpy(dst, src, sizeof(*dst)); +} + +static inline void +hash_ipportnet4_data_swap(struct hash_ipportnet4_elem *dst, + struct hash_ipportnet4_elem *src) +{ + struct hash_ipportnet4_elem tmp; + + memcpy(&tmp, dst, sizeof(tmp)); + memcpy(dst, src, sizeof(tmp)); + memcpy(src, &tmp, sizeof(tmp)); +} + +static inline void +hash_ipportnet4_data_netmask(struct hash_ipportnet4_elem *elem, u8 cidr) +{ + elem->ip2 &= NETMASK(cidr); + elem->cidr = cidr; +} + +static inline void +hash_ipportnet4_data_zero_out(struct hash_ipportnet4_elem *elem) +{ + elem->proto = 0; +} + +static inline bool +hash_ipportnet4_data_list(struct sk_buff *skb, + const struct hash_ipportnet4_elem *data) +{ + NLA_PUT_IPADDR4(skb, IPSET_ATTR_IP, data->ip); + NLA_PUT_IPADDR4(skb, IPSET_ATTR_IP2, data->ip2); + NLA_PUT_NET16(skb, IPSET_ATTR_PORT, data->port); + NLA_PUT_U8(skb, IPSET_ATTR_CIDR2, data->cidr); + NLA_PUT_U8(skb, IPSET_ATTR_PROTO, data->proto); + return 0; + +nla_put_failure: + return 1; +} + +static inline bool +hash_ipportnet4_data_tlist(struct sk_buff *skb, + const struct hash_ipportnet4_elem *data) +{ + const struct hash_ipportnet4_telem *tdata = + (const struct hash_ipportnet4_telem *)data; + + NLA_PUT_IPADDR4(skb, IPSET_ATTR_IP, tdata->ip); + NLA_PUT_IPADDR4(skb, IPSET_ATTR_IP2, tdata->ip2); + NLA_PUT_NET16(skb, IPSET_ATTR_PORT, tdata->port); + NLA_PUT_U8(skb, IPSET_ATTR_CIDR2, data->cidr); + NLA_PUT_U8(skb, IPSET_ATTR_PROTO, data->proto); + 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_PROTO +#define IP_SET_HASH_WITH_NETS + +#define PF 4 +#define HOST_MASK 32 +#include "ip_set_ahash.h" + +static int +hash_ipportnet4_kadt(struct ip_set *set, const struct sk_buff *skb, + enum ipset_adt adt, u8 pf, u8 dim, u8 flags) +{ + const struct ip_set_hash *h = set->data; + ipset_adtfn adtfn = set->variant->adt[adt]; + struct hash_ipportnet4_elem data = + { .cidr = h->nets[0].cidr || HOST_MASK }; + + if (data.cidr == 0) + return -EINVAL; + if (adt == IPSET_TEST) + data.cidr = HOST_MASK; + + if (!get_ip4_port(skb, flags & IPSET_DIM_TWO_SRC, + &data.port, &data.proto)) + return -EINVAL; + + ip4addrptr(skb, flags & IPSET_DIM_ONE_SRC, &data.ip); + ip4addrptr(skb, flags & IPSET_DIM_THREE_SRC, &data.ip2); + data.ip2 &= NETMASK(data.cidr); + + return adtfn(set, &data, h->timeout); +} + +static const struct nla_policy +hash_ipportnet_adt_policy[IPSET_ATTR_ADT_MAX + 1] = { + [IPSET_ATTR_IP] = { .type = NLA_NESTED }, + [IPSET_ATTR_IP_TO] = { .type = NLA_NESTED }, + [IPSET_ATTR_IP2] = { .type = NLA_NESTED }, + [IPSET_ATTR_PORT] = { .type = NLA_U16 }, + [IPSET_ATTR_PORT_TO] = { .type = NLA_U16 }, + [IPSET_ATTR_CIDR] = { .type = NLA_U8 }, + [IPSET_ATTR_CIDR2] = { .type = NLA_U8 }, + [IPSET_ATTR_PROTO] = { .type = NLA_U8 }, + [IPSET_ATTR_TIMEOUT] = { .type = NLA_U32 }, + [IPSET_ATTR_LINENO] = { .type = NLA_U32 }, +}; + +static int +hash_ipportnet4_uadt(struct ip_set *set, struct nlattr *head, int len, + enum ipset_adt adt, u32 *lineno, u32 flags) +{ + const struct ip_set_hash *h = set->data; + struct nlattr *tb[IPSET_ATTR_ADT_MAX+1]; + ipset_adtfn adtfn = set->variant->adt[adt]; + struct hash_ipportnet4_elem data = { .cidr = HOST_MASK }; + u32 ip, ip_to, p, port, port_to; + u32 timeout = h->timeout; + int ret; + + if (nla_parse(tb, IPSET_ATTR_ADT_MAX, head, len, + hash_ipportnet_adt_policy)) + return -IPSET_ERR_PROTOCOL; + + if (tb[IPSET_ATTR_LINENO]) + *lineno = nla_get_u32(tb[IPSET_ATTR_LINENO]); + + ret = ip_set_get_ipaddr4(tb, IPSET_ATTR_IP, &data.ip); + if (ret) + return ret; + + ret = ip_set_get_ipaddr4(tb, IPSET_ATTR_IP2, &data.ip2); + if (ret) + return ret; + + if (tb[IPSET_ATTR_CIDR2]) + data.cidr = nla_get_u8(tb[IPSET_ATTR_CIDR2]); + + if (!data.cidr) + return -IPSET_ERR_INVALID_CIDR; + + data.ip2 &= NETMASK(data.cidr); + + if (tb[IPSET_ATTR_PORT]) + data.port = ip_set_get_n16(tb[IPSET_ATTR_PORT]); + else + return -IPSET_ERR_PROTOCOL; + + if (tb[IPSET_ATTR_PROTO]) { + data.proto = nla_get_u8(tb[IPSET_ATTR_PROTO]); + + if (data.proto == 0) + return -IPSET_ERR_INVALID_PROTO; + } else + return -IPSET_ERR_MISSING_PROTO; + + switch (data.proto) { + case IPPROTO_UDP: + case IPPROTO_TCP: + case IPPROTO_ICMP: + break; + default: + data.port = 0; + break; + } + + if (tb[IPSET_ATTR_TIMEOUT]) { + if (!with_timeout(h->timeout)) + return -IPSET_ERR_TIMEOUT; + timeout = ip_set_timeout_uget(tb[IPSET_ATTR_TIMEOUT]); + } + + if (adt == IPSET_TEST + || !(data.proto == IPPROTO_TCP || data.proto == IPPROTO_UDP) + || !(tb[IPSET_ATTR_IP_TO] || tb[IPSET_ATTR_CIDR] + || tb[IPSET_ATTR_PORT_TO])) { + ret = adtfn(set, &data, timeout); + return ip_set_eexist(ret, flags) ? 0 : ret; + } + + ip = ntohl(data.ip); + if (tb[IPSET_ATTR_IP_TO]) { + ret = ip_set_get_ipaddr4(tb, IPSET_ATTR_IP_TO, &ip_to); + if (ret) + return ret; + ip_to = ntohl(ip_to); + if (ip > ip_to) + swap(ip, ip_to); + } else if (tb[IPSET_ATTR_CIDR]) { + u8 cidr = nla_get_u8(tb[IPSET_ATTR_CIDR]); + + if (cidr > 32) + return -IPSET_ERR_INVALID_CIDR; + ip &= HOSTMASK(cidr); + ip_to = ip | ~HOSTMASK(cidr); + } else + ip_to = ip; + + port = ntohs(data.port); + if (tb[IPSET_ATTR_PORT_TO]) { + port_to = ip_set_get_h16(tb[IPSET_ATTR_PORT_TO]); + if (port > port_to) + swap(port, port_to); + } else + port_to = port; + + for (; !before(ip_to, ip); ip++) + for (p = port; p <= port_to; p++) { + data.ip = htonl(ip); + data.port = htons(p); + ret = adtfn(set, &data, timeout); + + if (ret && !ip_set_eexist(ret, flags)) + return ret; + else + ret = 0; + } + return ret; +} + +static bool +hash_ipportnet_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_ipportnet6_elem { + union nf_inet_addr ip; + union nf_inet_addr ip2; + u16 port; + u8 cidr; + u8 proto; +}; + +struct hash_ipportnet6_telem { + union nf_inet_addr ip; + union nf_inet_addr ip2; + u16 port; + u8 cidr; + u8 proto; + unsigned long timeout; +}; + +static inline bool +hash_ipportnet6_data_equal(const struct hash_ipportnet6_elem *ip1, + const struct hash_ipportnet6_elem *ip2) +{ + return ipv6_addr_cmp(&ip1->ip.in6, &ip2->ip.in6) == 0 + && ipv6_addr_cmp(&ip1->ip2.in6, &ip2->ip2.in6) == 0 + && ip1->cidr == ip2->cidr + && ip1->port == ip2->port + && ip1->proto == ip2->proto; +} + +static inline bool +hash_ipportnet6_data_isnull(const struct hash_ipportnet6_elem *elem) +{ + return elem->proto == 0; +} + +static inline void +hash_ipportnet6_data_copy(struct hash_ipportnet6_elem *dst, + const struct hash_ipportnet6_elem *src) +{ + memcpy(dst, src, sizeof(*dst)); +} + +static inline void +hash_ipportnet6_data_swap(struct hash_ipportnet6_elem *dst, + struct hash_ipportnet6_elem *src) +{ + struct hash_ipportnet6_elem tmp; + + memcpy(&tmp, dst, sizeof(tmp)); + memcpy(dst, src, sizeof(tmp)); + memcpy(src, &tmp, sizeof(tmp)); +} + +static inline void +hash_ipportnet6_data_zero_out(struct hash_ipportnet6_elem *elem) +{ + elem->proto = 0; +} + +static inline void +ip6_netmask(union nf_inet_addr *ip, u8 prefix) +{ + ip->ip6[0] &= NETMASK6(prefix)[0]; + ip->ip6[1] &= NETMASK6(prefix)[1]; + ip->ip6[2] &= NETMASK6(prefix)[2]; + ip->ip6[3] &= NETMASK6(prefix)[3]; +} + +static inline void +hash_ipportnet6_data_netmask(struct hash_ipportnet6_elem *elem, u8 cidr) +{ + ip6_netmask(&elem->ip2, cidr); + elem->cidr = cidr; +} + +static inline bool +hash_ipportnet6_data_list(struct sk_buff *skb, + const struct hash_ipportnet6_elem *data) +{ + NLA_PUT_IPADDR6(skb, IPSET_ATTR_IP, &data->ip); + NLA_PUT_IPADDR6(skb, IPSET_ATTR_IP2, &data->ip2); + NLA_PUT_NET16(skb, IPSET_ATTR_PORT, data->port); + NLA_PUT_U8(skb, IPSET_ATTR_CIDR2, data->cidr); + NLA_PUT_U8(skb, IPSET_ATTR_PROTO, data->proto); + return 0; + +nla_put_failure: + return 1; +} + +static inline bool +hash_ipportnet6_data_tlist(struct sk_buff *skb, + const struct hash_ipportnet6_elem *data) +{ + const struct hash_ipportnet6_telem *e = + (const struct hash_ipportnet6_telem *)data; + + NLA_PUT_IPADDR6(skb, IPSET_ATTR_IP, &e->ip); + NLA_PUT_IPADDR6(skb, IPSET_ATTR_IP2, &data->ip2); + NLA_PUT_NET16(skb, IPSET_ATTR_PORT, data->port); + NLA_PUT_U8(skb, IPSET_ATTR_CIDR2, data->cidr); + NLA_PUT_U8(skb, IPSET_ATTR_PROTO, data->proto); + 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 "ip_set_ahash.h" + +static int +hash_ipportnet6_kadt(struct ip_set *set, const struct sk_buff *skb, + enum ipset_adt adt, u8 pf, u8 dim, u8 flags) +{ + const struct ip_set_hash *h = set->data; + ipset_adtfn adtfn = set->variant->adt[adt]; + struct hash_ipportnet6_elem data = + { .cidr = h->nets[0].cidr || HOST_MASK }; + + if (data.cidr == 0) + return -EINVAL; + if (adt == IPSET_TEST) + data.cidr = HOST_MASK; + + if (!get_ip6_port(skb, flags & IPSET_DIM_TWO_SRC, + &data.port, &data.proto)) + return -EINVAL; + + ip6addrptr(skb, flags & IPSET_DIM_ONE_SRC, &data.ip.in6); + ip6addrptr(skb, flags & IPSET_DIM_THREE_SRC, &data.ip2.in6); + ip6_netmask(&data.ip2, data.cidr); + + return adtfn(set, &data, h->timeout); +} + +static int +hash_ipportnet6_uadt(struct ip_set *set, struct nlattr *head, int len, + enum ipset_adt adt, u32 *lineno, u32 flags) +{ + const struct ip_set_hash *h = set->data; + struct nlattr *tb[IPSET_ATTR_ADT_MAX+1]; + ipset_adtfn adtfn = set->variant->adt[adt]; + struct hash_ipportnet6_elem data = { .cidr = HOST_MASK }; + u32 port, port_to; + u32 timeout = h->timeout; + int ret; + + if (nla_parse(tb, IPSET_ATTR_ADT_MAX, head, len, + hash_ipportnet_adt_policy)) + return -IPSET_ERR_PROTOCOL; + + 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; + + ret = ip_set_get_ipaddr6(tb, IPSET_ATTR_IP2, &data.ip2); + if (ret) + return ret; + + if (tb[IPSET_ATTR_CIDR2]) + data.cidr = nla_get_u8(tb[IPSET_ATTR_CIDR2]); + + if (!data.cidr) + return -IPSET_ERR_INVALID_CIDR; + + ip6_netmask(&data.ip2, data.cidr); + + if (tb[IPSET_ATTR_PORT]) + data.port = ip_set_get_n16(tb[IPSET_ATTR_PORT]); + else + return -IPSET_ERR_PROTOCOL; + + if (tb[IPSET_ATTR_PROTO]) { + data.proto = nla_get_u8(tb[IPSET_ATTR_PROTO]); + + if (data.proto == 0) + return -IPSET_ERR_INVALID_PROTO; + } else + return -IPSET_ERR_MISSING_PROTO; + + switch (data.proto) { + case IPPROTO_UDP: + case IPPROTO_TCP: + case IPPROTO_ICMPV6: + break; + default: + data.port = 0; + break; + } + + if (tb[IPSET_ATTR_TIMEOUT]) { + if (!with_timeout(h->timeout)) + return -IPSET_ERR_TIMEOUT; + timeout = ip_set_timeout_uget(tb[IPSET_ATTR_TIMEOUT]); + } + + if (adt == IPSET_TEST + || !(data.proto == IPPROTO_TCP || data.proto == IPPROTO_UDP) + || !tb[IPSET_ATTR_PORT_TO]) { + ret = adtfn(set, &data, timeout); + return ip_set_eexist(ret, flags) ? 0 : ret; + } + + port = ntohs(data.port); + port_to = ip_set_get_h16(tb[IPSET_ATTR_PORT_TO]); + if (port > port_to) + swap(port, port_to); + + for (; port <= port_to; port++) { + data.port = htons(port); + ret = adtfn(set, &data, timeout); + + if (ret && !ip_set_eexist(ret, flags)) + return ret; + else + ret = 0; + } + return ret; +} + +/* Create hash:ip type of sets */ + +static const struct nla_policy +hash_ipportnet_create_policy[IPSET_ATTR_CREATE_MAX+1] = { + [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_ipportnet_create(struct ip_set *set, struct nlattr *head, + int len, u32 flags) +{ + struct nlattr *tb[IPSET_ATTR_CREATE_MAX+1]; + 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 (nla_parse(tb, IPSET_ATTR_CREATE_MAX, head, len, + hash_ipportnet_create_policy)) + 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), + GFP_KERNEL); + if (!h->table) { + kfree(h); + return -ENOMEM; + } + h->table->htable_bits = hbits; + + 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_ipportnet4_tvariant + : &hash_ipportnet6_tvariant; + + if (set->family == AF_INET) + hash_ipportnet4_gc_init(set); + else + hash_ipportnet6_gc_init(set); + } else { + set->variant = set->family == AF_INET + ? &hash_ipportnet4_variant : &hash_ipportnet6_variant; + } + + pr_debug("create %s hashsize %u (%u) maxelem %u: %p(%p)", + 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_ipportnet_type __read_mostly = { + .name = "hash:ip,port,net", + .protocol = IPSET_PROTOCOL, + .features = IPSET_TYPE_IP | IPSET_TYPE_PORT | IPSET_TYPE_IP2, + .dimension = IPSET_DIM_THREE, + .family = AF_UNSPEC, + .revision = 0, + .create = hash_ipportnet_create, + .me = THIS_MODULE, +}; + +static int __init +hash_ipportnet_init(void) +{ + return ip_set_type_register(&hash_ipportnet_type); +} + +static void __exit +hash_ipportnet_fini(void) +{ + ip_set_type_unregister(&hash_ipportnet_type); +} + +module_init(hash_ipportnet_init); +module_exit(hash_ipportnet_fini); diff --git a/extensions/ipset-5/ip_set_hash_net.c b/extensions/ipset-5/ip_set_hash_net.c new file mode 100644 index 0000000..d71bafd --- /dev/null +++ b/extensions/ipset-5/ip_set_hash_net.c @@ -0,0 +1,485 @@ +/* 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 set type: the hash:net type */ + +#include "ip_set_kernel.h" +#include "jhash.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "ip_set.h" +#include "ip_set_timeout.h" +#include "ip_set_hash.h" + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Jozsef Kadlecsik "); +MODULE_DESCRIPTION("hash:net type of IP sets"); +MODULE_ALIAS("ip_set_hash:net"); + +/* Type specific function prefix */ +#define TYPE hash_net + +static bool +hash_net_same_set(const struct ip_set *a, const struct ip_set *b); + +#define hash_net4_same_set hash_net_same_set +#define hash_net6_same_set hash_net_same_set + +/* The type variant functions: IPv4 */ + +/* Member elements without timeout */ +struct hash_net4_elem { + u32 ip; + u16 padding0; + u8 padding1; + u8 cidr; +}; + +/* Member elements with timeout support */ +struct hash_net4_telem { + u32 ip; + u16 padding0; + u8 padding1; + u8 cidr; + unsigned long timeout; +}; + +static inline bool +hash_net4_data_equal(const struct hash_net4_elem *ip1, + const struct hash_net4_elem *ip2) +{ + return ip1->ip == ip2->ip && ip1->cidr == ip2->cidr; +} + +static inline bool +hash_net4_data_isnull(const struct hash_net4_elem *elem) +{ + return elem->cidr == 0; +} + +static inline void +hash_net4_data_copy(struct hash_net4_elem *dst, + const struct hash_net4_elem *src) +{ + dst->ip = src->ip; + dst->cidr = src->cidr; +} + +static inline void +hash_net4_data_swap(struct hash_net4_elem *dst, + struct hash_net4_elem *src) +{ + swap(dst->ip, src->ip); + swap(dst->cidr, src->cidr); +} + +static inline void +hash_net4_data_netmask(struct hash_net4_elem *elem, u8 cidr) +{ + elem->ip &= NETMASK(cidr); + elem->cidr = cidr; +} + +/* Zero CIDR values cannot be stored */ +static inline void +hash_net4_data_zero_out(struct hash_net4_elem *elem) +{ + elem->cidr = 0; +} + +static inline bool +hash_net4_data_list(struct sk_buff *skb, const struct hash_net4_elem *data) +{ + NLA_PUT_IPADDR4(skb, IPSET_ATTR_IP, data->ip); + NLA_PUT_U8(skb, IPSET_ATTR_CIDR, data->cidr); + return 0; + +nla_put_failure: + return 1; +} + +static inline bool +hash_net4_data_tlist(struct sk_buff *skb, const struct hash_net4_elem *data) +{ + const struct hash_net4_telem *tdata = + (const struct hash_net4_telem *)data; + + NLA_PUT_IPADDR4(skb, IPSET_ATTR_IP, tdata->ip); + NLA_PUT_U8(skb, IPSET_ATTR_CIDR, tdata->cidr); + 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 PF 4 +#define HOST_MASK 32 +#include "ip_set_ahash.h" + +static int +hash_net4_kadt(struct ip_set *set, const struct sk_buff *skb, + enum ipset_adt adt, u8 pf, u8 dim, u8 flags) +{ + const struct ip_set_hash *h = set->data; + ipset_adtfn adtfn = set->variant->adt[adt]; + struct hash_net4_elem data = { .cidr = h->nets[0].cidr || HOST_MASK }; + + if (data.cidr == 0) + return -EINVAL; + if (adt == IPSET_TEST) + data.cidr = HOST_MASK; + + ip4addrptr(skb, flags & IPSET_DIM_ONE_SRC, &data.ip); + data.ip &= NETMASK(data.cidr); + + return adtfn(set, &data, h->timeout); +} + +static const struct nla_policy hash_net_adt_policy[IPSET_ATTR_ADT_MAX + 1] = { + [IPSET_ATTR_IP] = { .type = NLA_NESTED }, + [IPSET_ATTR_CIDR] = { .type = NLA_U8 }, + [IPSET_ATTR_TIMEOUT] = { .type = NLA_U32 }, +}; + +static int +hash_net4_uadt(struct ip_set *set, struct nlattr *head, int len, + enum ipset_adt adt, u32 *lineno, u32 flags) +{ + const struct ip_set_hash *h = set->data; + struct nlattr *tb[IPSET_ATTR_ADT_MAX+1]; + ipset_adtfn adtfn = set->variant->adt[adt]; + struct hash_net4_elem data = { .cidr = HOST_MASK }; + u32 timeout = h->timeout; + int ret; + + if (nla_parse(tb, IPSET_ATTR_ADT_MAX, head, len, + hash_net_adt_policy)) + return -IPSET_ERR_PROTOCOL; + + if (tb[IPSET_ATTR_LINENO]) + *lineno = nla_get_u32(tb[IPSET_ATTR_LINENO]); + + ret = ip_set_get_ipaddr4(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; + + data.ip &= NETMASK(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]); + } + + ret = adtfn(set, &data, timeout); + + return ip_set_eexist(ret, flags) ? 0 : ret; +} + +static bool +hash_net_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_net6_elem { + union nf_inet_addr ip; + u16 padding0; + u8 padding1; + u8 cidr; +}; + +struct hash_net6_telem { + union nf_inet_addr ip; + u16 padding0; + u8 padding1; + u8 cidr; + unsigned long timeout; +}; + +static inline bool +hash_net6_data_equal(const struct hash_net6_elem *ip1, + const struct hash_net6_elem *ip2) +{ + return ipv6_addr_cmp(&ip1->ip.in6, &ip2->ip.in6) == 0 + && ip1->cidr == ip2->cidr; +} + +static inline bool +hash_net6_data_isnull(const struct hash_net6_elem *elem) +{ + return elem->cidr == 0; +} + +static inline void +hash_net6_data_copy(struct hash_net6_elem *dst, + const struct hash_net6_elem *src) +{ + ipv6_addr_copy(&dst->ip.in6, &src->ip.in6); + dst->cidr = src->cidr; +} + +static inline void +hash_net6_data_swap(struct hash_net6_elem *dst, struct hash_net6_elem *src) +{ + struct hash_net6_elem tmp; + + memcpy(&tmp, dst, sizeof(tmp)); + memcpy(dst, src, sizeof(tmp)); + memcpy(src, &tmp, sizeof(tmp)); +} + +static inline void +hash_net6_data_zero_out(struct hash_net6_elem *elem) +{ + elem->cidr = 0; +} + +static inline void +ip6_netmask(union nf_inet_addr *ip, u8 prefix) +{ + ip->ip6[0] &= NETMASK6(prefix)[0]; + ip->ip6[1] &= NETMASK6(prefix)[1]; + ip->ip6[2] &= NETMASK6(prefix)[2]; + ip->ip6[3] &= NETMASK6(prefix)[3]; +} + +static inline void +hash_net6_data_netmask(struct hash_net6_elem *elem, u8 cidr) +{ + ip6_netmask(&elem->ip, cidr); + elem->cidr = cidr; +} + +static inline bool +hash_net6_data_list(struct sk_buff *skb, const struct hash_net6_elem *data) +{ + NLA_PUT_IPADDR6(skb, IPSET_ATTR_IP, &data->ip); + NLA_PUT_U8(skb, IPSET_ATTR_CIDR, data->cidr); + return 0; + +nla_put_failure: + return 1; +} + +static inline bool +hash_net6_data_tlist(struct sk_buff *skb, const struct hash_net6_elem *data) +{ + const struct hash_net6_telem *e = + (const struct hash_net6_telem *)data; + + NLA_PUT_IPADDR6(skb, IPSET_ATTR_IP, &e->ip); + NLA_PUT_U8(skb, IPSET_ATTR_CIDR, e->cidr); + 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 "ip_set_ahash.h" + +static int +hash_net6_kadt(struct ip_set *set, const struct sk_buff *skb, + enum ipset_adt adt, u8 pf, u8 dim, u8 flags) +{ + const struct ip_set_hash *h = set->data; + ipset_adtfn adtfn = set->variant->adt[adt]; + struct hash_net6_elem data = { .cidr = h->nets[0].cidr || HOST_MASK }; + + if (data.cidr == 0) + return -EINVAL; + if (adt == IPSET_TEST) + data.cidr = HOST_MASK; + + ip6addrptr(skb, flags & IPSET_DIM_ONE_SRC, &data.ip.in6); + ip6_netmask(&data.ip, data.cidr); + + return adtfn(set, &data, h->timeout); +} + +static int +hash_net6_uadt(struct ip_set *set, struct nlattr *head, int len, + enum ipset_adt adt, u32 *lineno, u32 flags) +{ + const struct ip_set_hash *h = set->data; + struct nlattr *tb[IPSET_ATTR_ADT_MAX+1]; + ipset_adtfn adtfn = set->variant->adt[adt]; + struct hash_net6_elem data = { .cidr = HOST_MASK }; + u32 timeout = h->timeout; + int ret; + + if (nla_parse(tb, IPSET_ATTR_ADT_MAX, head, len, + hash_net_adt_policy)) + return -IPSET_ERR_PROTOCOL; + + 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]); + } + + ret = adtfn(set, &data, timeout); + + return ip_set_eexist(ret, flags) ? 0 : ret; +} + +/* Create hash:ip type of sets */ + +static const struct nla_policy +hash_net_create_policy[IPSET_ATTR_CREATE_MAX+1] = { + [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_net_create(struct ip_set *set, struct nlattr *head, int len, u32 flags) +{ + struct nlattr *tb[IPSET_ATTR_CREATE_MAX+1]; + u32 hashsize = IPSET_DEFAULT_HASHSIZE, maxelem = IPSET_DEFAULT_MAXELEM; + struct ip_set_hash *h; + u8 hbits; + + if (!(set->family == AF_INET || set->family == AF_INET6)) + return -IPSET_ERR_INVALID_FAMILY; + + if (nla_parse(tb, IPSET_ATTR_CREATE_MAX, head, len, + hash_net_create_policy)) + 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), + GFP_KERNEL); + if (!h->table) { + kfree(h); + return -ENOMEM; + } + h->table->htable_bits = hbits; + + 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_net4_tvariant : &hash_net6_tvariant; + + if (set->family == AF_INET) + hash_net4_gc_init(set); + else + hash_net6_gc_init(set); + } else { + set->variant = set->family == AF_INET + ? &hash_net4_variant : &hash_net6_variant; + } + + pr_debug("create %s hashsize %u (%u) maxelem %u: %p(%p)", + 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_net_type __read_mostly = { + .name = "hash:net", + .protocol = IPSET_PROTOCOL, + .features = IPSET_TYPE_IP, + .dimension = IPSET_DIM_ONE, + .family = AF_UNSPEC, + .revision = 0, + .create = hash_net_create, + .me = THIS_MODULE, +}; + +static int __init +hash_net_init(void) +{ + return ip_set_type_register(&hash_net_type); +} + +static void __exit +hash_net_fini(void) +{ + ip_set_type_unregister(&hash_net_type); +} + +module_init(hash_net_init); +module_exit(hash_net_fini); diff --git a/extensions/ipset-5/ip_set_hash_netport.c b/extensions/ipset-5/ip_set_hash_netport.c new file mode 100644 index 0000000..15b121e --- /dev/null +++ b/extensions/ipset-5/ip_set_hash_netport.c @@ -0,0 +1,605 @@ +/* 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 set type: the hash:net,port type */ + +#include "ip_set_kernel.h" +#include "jhash.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "ip_set.h" +#include "ip_set_timeout.h" +#include "ip_set_getport.h" +#include "ip_set_hash.h" + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Jozsef Kadlecsik "); +MODULE_DESCRIPTION("hash:net,port type of IP sets"); +MODULE_ALIAS("ip_set_hash:net,port"); + +/* Type specific function prefix */ +#define TYPE hash_netport + +static bool +hash_netport_same_set(const struct ip_set *a, const struct ip_set *b); + +#define hash_netport4_same_set hash_netport_same_set +#define hash_netport6_same_set hash_netport_same_set + +/* The type variant functions: IPv4 */ + +/* Member elements without timeout */ +struct hash_netport4_elem { + u32 ip; + u16 port; + u8 proto; + u8 cidr; +}; + +/* Member elements with timeout support */ +struct hash_netport4_telem { + u32 ip; + u16 port; + u8 proto; + u8 cidr; + unsigned long timeout; +}; + +static inline bool +hash_netport4_data_equal(const struct hash_netport4_elem *ip1, + const struct hash_netport4_elem *ip2) +{ + return ip1->ip == ip2->ip + && ip1->port == ip2->port + && ip1->proto == ip2->proto + && ip1->cidr == ip2->cidr; +} + +static inline bool +hash_netport4_data_isnull(const struct hash_netport4_elem *elem) +{ + return elem->proto == 0; +} + +static inline void +hash_netport4_data_copy(struct hash_netport4_elem *dst, + const struct hash_netport4_elem *src) +{ + dst->ip = src->ip; + dst->port = src->port; + dst->proto = src->proto; + dst->cidr = src->cidr; +} + +static inline void +hash_netport4_data_swap(struct hash_netport4_elem *dst, + struct hash_netport4_elem *src) +{ + swap(dst->ip, src->ip); + swap(dst->port, src->port); + swap(dst->proto, src->proto); + swap(dst->cidr, src->cidr); +} + +static inline void +hash_netport4_data_netmask(struct hash_netport4_elem *elem, u8 cidr) +{ + elem->ip &= NETMASK(cidr); + elem->cidr = cidr; +} + +static inline void +hash_netport4_data_zero_out(struct hash_netport4_elem *elem) +{ + elem->proto = 0; +} + +static inline bool +hash_netport4_data_list(struct sk_buff *skb, + const struct hash_netport4_elem *data) +{ + NLA_PUT_IPADDR4(skb, IPSET_ATTR_IP, data->ip); + NLA_PUT_NET16(skb, IPSET_ATTR_PORT, data->port); + NLA_PUT_U8(skb, IPSET_ATTR_CIDR, data->cidr); + NLA_PUT_U8(skb, IPSET_ATTR_PROTO, data->proto); + return 0; + +nla_put_failure: + return 1; +} + +static inline bool +hash_netport4_data_tlist(struct sk_buff *skb, + const struct hash_netport4_elem *data) +{ + const struct hash_netport4_telem *tdata = + (const struct hash_netport4_telem *)data; + + NLA_PUT_IPADDR4(skb, IPSET_ATTR_IP, tdata->ip); + NLA_PUT_NET16(skb, IPSET_ATTR_PORT, tdata->port); + NLA_PUT_U8(skb, IPSET_ATTR_CIDR, data->cidr); + NLA_PUT_U8(skb, IPSET_ATTR_PROTO, data->proto); + 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_PROTO +#define IP_SET_HASH_WITH_NETS + +#define PF 4 +#define HOST_MASK 32 +#include "ip_set_ahash.h" + +static int +hash_netport4_kadt(struct ip_set *set, const struct sk_buff *skb, + enum ipset_adt adt, u8 pf, u8 dim, u8 flags) +{ + const struct ip_set_hash *h = set->data; + ipset_adtfn adtfn = set->variant->adt[adt]; + struct hash_netport4_elem data = { + .cidr = h->nets[0].cidr || HOST_MASK }; + + if (data.cidr == 0) + return -EINVAL; + if (adt == IPSET_TEST) + data.cidr = HOST_MASK; + + if (!get_ip4_port(skb, flags & IPSET_DIM_TWO_SRC, + &data.port, &data.proto)) + return -EINVAL; + + ip4addrptr(skb, flags & IPSET_DIM_ONE_SRC, &data.ip); + data.ip &= NETMASK(data.cidr); + + return adtfn(set, &data, h->timeout); +} + +static const struct nla_policy +hash_netport_adt_policy[IPSET_ATTR_ADT_MAX + 1] = { + [IPSET_ATTR_IP] = { .type = NLA_NESTED }, + [IPSET_ATTR_PORT] = { .type = NLA_U16 }, + [IPSET_ATTR_PORT_TO] = { .type = NLA_U16 }, + [IPSET_ATTR_PROTO] = { .type = NLA_U8 }, + [IPSET_ATTR_CIDR] = { .type = NLA_U8 }, + [IPSET_ATTR_TIMEOUT] = { .type = NLA_U32 }, + [IPSET_ATTR_LINENO] = { .type = NLA_U32 }, +}; + +static int +hash_netport4_uadt(struct ip_set *set, struct nlattr *head, int len, + enum ipset_adt adt, u32 *lineno, u32 flags) +{ + const struct ip_set_hash *h = set->data; + struct nlattr *tb[IPSET_ATTR_ADT_MAX+1]; + ipset_adtfn adtfn = set->variant->adt[adt]; + struct hash_netport4_elem data = { .cidr = HOST_MASK }; + u32 port, port_to; + u32 timeout = h->timeout; + int ret; + + if (nla_parse(tb, IPSET_ATTR_ADT_MAX, head, len, + hash_netport_adt_policy)) + return -IPSET_ERR_PROTOCOL; + + if (tb[IPSET_ATTR_LINENO]) + *lineno = nla_get_u32(tb[IPSET_ATTR_LINENO]); + + ret = ip_set_get_ipaddr4(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; + data.ip &= NETMASK(data.cidr); + + if (tb[IPSET_ATTR_PORT]) + data.port = ip_set_get_n16(tb[IPSET_ATTR_PORT]); + else + return -IPSET_ERR_PROTOCOL; + + if (tb[IPSET_ATTR_PROTO]) { + data.proto = nla_get_u8(tb[IPSET_ATTR_PROTO]); + + if (data.proto == 0) + return -IPSET_ERR_INVALID_PROTO; + } else + return -IPSET_ERR_MISSING_PROTO; + + switch (data.proto) { + case IPPROTO_UDP: + case IPPROTO_TCP: + case IPPROTO_ICMP: + break; + default: + data.port = 0; + break; + } + + if (tb[IPSET_ATTR_TIMEOUT]) { + if (!with_timeout(h->timeout)) + return -IPSET_ERR_TIMEOUT; + timeout = ip_set_timeout_uget(tb[IPSET_ATTR_TIMEOUT]); + } + + if (adt == IPSET_TEST + || !(data.proto == IPPROTO_TCP || data.proto == IPPROTO_UDP) + || !tb[IPSET_ATTR_PORT_TO]) { + ret = adtfn(set, &data, timeout); + return ip_set_eexist(ret, flags) ? 0 : ret; + } + + port = ntohs(data.port); + port_to = ip_set_get_h16(tb[IPSET_ATTR_PORT_TO]); + if (port > port_to) + swap(port, port_to); + + for (; port <= port_to; port++) { + data.port = htons(port); + ret = adtfn(set, &data, timeout); + + if (ret && !ip_set_eexist(ret, flags)) + return ret; + else + ret = 0; + } + return ret; +} + +static bool +hash_netport_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_netport6_elem { + union nf_inet_addr ip; + u16 port; + u8 proto; + u8 cidr; +}; + +struct hash_netport6_telem { + union nf_inet_addr ip; + u16 port; + u8 proto; + u8 cidr; + unsigned long timeout; +}; + +static inline bool +hash_netport6_data_equal(const struct hash_netport6_elem *ip1, + const struct hash_netport6_elem *ip2) +{ + return ipv6_addr_cmp(&ip1->ip.in6, &ip2->ip.in6) == 0 + && ip1->port == ip2->port + && ip1->proto == ip2->proto + && ip1->cidr == ip2->cidr; +} + +static inline bool +hash_netport6_data_isnull(const struct hash_netport6_elem *elem) +{ + return elem->proto == 0; +} + +static inline void +hash_netport6_data_copy(struct hash_netport6_elem *dst, + const struct hash_netport6_elem *src) +{ + memcpy(dst, src, sizeof(*dst)); +} + +static inline void +hash_netport6_data_swap(struct hash_netport6_elem *dst, + struct hash_netport6_elem *src) +{ + struct hash_netport6_elem tmp; + + memcpy(&tmp, dst, sizeof(tmp)); + memcpy(dst, src, sizeof(tmp)); + memcpy(src, &tmp, sizeof(tmp)); +} + +static inline void +hash_netport6_data_zero_out(struct hash_netport6_elem *elem) +{ + elem->proto = 0; +} + +static inline void +ip6_netmask(union nf_inet_addr *ip, u8 prefix) +{ + ip->ip6[0] &= NETMASK6(prefix)[0]; + ip->ip6[1] &= NETMASK6(prefix)[1]; + ip->ip6[2] &= NETMASK6(prefix)[2]; + ip->ip6[3] &= NETMASK6(prefix)[3]; +} + +static inline void +hash_netport6_data_netmask(struct hash_netport6_elem *elem, u8 cidr) +{ + ip6_netmask(&elem->ip, cidr); + elem->cidr = cidr; +} + +static inline bool +hash_netport6_data_list(struct sk_buff *skb, + const struct hash_netport6_elem *data) +{ + NLA_PUT_IPADDR6(skb, IPSET_ATTR_IP, &data->ip); + NLA_PUT_NET16(skb, IPSET_ATTR_PORT, data->port); + NLA_PUT_U8(skb, IPSET_ATTR_CIDR, data->cidr); + NLA_PUT_U8(skb, IPSET_ATTR_PROTO, data->proto); + return 0; + +nla_put_failure: + return 1; +} + +static inline bool +hash_netport6_data_tlist(struct sk_buff *skb, + const struct hash_netport6_elem *data) +{ + const struct hash_netport6_telem *e = + (const struct hash_netport6_telem *)data; + + NLA_PUT_IPADDR6(skb, IPSET_ATTR_IP, &e->ip); + NLA_PUT_NET16(skb, IPSET_ATTR_PORT, data->port); + NLA_PUT_U8(skb, IPSET_ATTR_CIDR, data->cidr); + NLA_PUT_U8(skb, IPSET_ATTR_PROTO, data->proto); + 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 "ip_set_ahash.h" + +static int +hash_netport6_kadt(struct ip_set *set, const struct sk_buff *skb, + enum ipset_adt adt, u8 pf, u8 dim, u8 flags) +{ + const struct ip_set_hash *h = set->data; + ipset_adtfn adtfn = set->variant->adt[adt]; + struct hash_netport6_elem data = { + .cidr = h->nets[0].cidr || HOST_MASK }; + + if (data.cidr == 0) + return -EINVAL; + if (adt == IPSET_TEST) + data.cidr = HOST_MASK; + + if (!get_ip6_port(skb, flags & IPSET_DIM_TWO_SRC, + &data.port, &data.proto)) + return -EINVAL; + + ip6addrptr(skb, flags & IPSET_DIM_ONE_SRC, &data.ip.in6); + ip6_netmask(&data.ip, data.cidr); + + return adtfn(set, &data, h->timeout); +} + +static int +hash_netport6_uadt(struct ip_set *set, struct nlattr *head, int len, + enum ipset_adt adt, u32 *lineno, u32 flags) +{ + const struct ip_set_hash *h = set->data; + struct nlattr *tb[IPSET_ATTR_ADT_MAX+1]; + ipset_adtfn adtfn = set->variant->adt[adt]; + struct hash_netport6_elem data = { .cidr = HOST_MASK }; + u32 port, port_to; + u32 timeout = h->timeout; + int ret; + + if (nla_parse(tb, IPSET_ATTR_ADT_MAX, head, len, + hash_netport_adt_policy)) + return -IPSET_ERR_PROTOCOL; + + 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_PORT]) + data.port = ip_set_get_n16(tb[IPSET_ATTR_PORT]); + else + return -IPSET_ERR_PROTOCOL; + + if (tb[IPSET_ATTR_PROTO]) { + data.proto = nla_get_u8(tb[IPSET_ATTR_PROTO]); + + if (data.proto == 0) + return -IPSET_ERR_INVALID_PROTO; + } else + return -IPSET_ERR_MISSING_PROTO; + + switch (data.proto) { + case IPPROTO_UDP: + case IPPROTO_TCP: + case IPPROTO_ICMPV6: + break; + default: + data.port = 0; + break; + } + + if (tb[IPSET_ATTR_TIMEOUT]) { + if (!with_timeout(h->timeout)) + return -IPSET_ERR_TIMEOUT; + timeout = ip_set_timeout_uget(tb[IPSET_ATTR_TIMEOUT]); + } + + if (adt == IPSET_TEST + || !(data.proto == IPPROTO_TCP || data.proto == IPPROTO_UDP) + || !tb[IPSET_ATTR_PORT_TO]) { + ret = adtfn(set, &data, timeout); + return ip_set_eexist(ret, flags) ? 0 : ret; + } + + port = ntohs(data.port); + port_to = ip_set_get_h16(tb[IPSET_ATTR_PORT_TO]); + if (port > port_to) + swap(port, port_to); + + for (; port <= port_to; port++) { + data.port = htons(port); + ret = adtfn(set, &data, timeout); + + if (ret && !ip_set_eexist(ret, flags)) + return ret; + else + ret = 0; + } + return ret; +} + +/* Create hash:ip type of sets */ + +static const struct nla_policy +hash_netport_create_policy[IPSET_ATTR_CREATE_MAX+1] = { + [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 }, +}; + +static int +hash_netport_create(struct ip_set *set, struct nlattr *head, int len, u32 flags) +{ + struct nlattr *tb[IPSET_ATTR_CREATE_MAX+1]; + 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 (nla_parse(tb, IPSET_ATTR_CREATE_MAX, head, len, + hash_netport_create_policy)) + 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), + GFP_KERNEL); + if (!h->table) { + kfree(h); + return -ENOMEM; + } + h->table->htable_bits = hbits; + + 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_netport4_tvariant : &hash_netport6_tvariant; + + if (set->family == AF_INET) + hash_netport4_gc_init(set); + else + hash_netport6_gc_init(set); + } else { + set->variant = set->family == AF_INET + ? &hash_netport4_variant : &hash_netport6_variant; + } + + pr_debug("create %s hashsize %u (%u) maxelem %u: %p(%p)", + 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_netport_type __read_mostly = { + .name = "hash:net,port", + .protocol = IPSET_PROTOCOL, + .features = IPSET_TYPE_IP | IPSET_TYPE_PORT, + .dimension = IPSET_DIM_TWO, + .family = AF_UNSPEC, + .revision = 0, + .create = hash_netport_create, + .me = THIS_MODULE, +}; + +static int __init +hash_netport_init(void) +{ + return ip_set_type_register(&hash_netport_type); +} + +static void __exit +hash_netport_fini(void) +{ + ip_set_type_unregister(&hash_netport_type); +} + +module_init(hash_netport_init); +module_exit(hash_netport_fini); diff --git a/extensions/ipset-5/ip_set_kernel.h b/extensions/ipset-5/ip_set_kernel.h new file mode 100644 index 0000000..d770589 --- /dev/null +++ b/extensions/ipset-5/ip_set_kernel.h @@ -0,0 +1,15 @@ +#ifndef _IP_SET_KERNEL_H +#define _IP_SET_KERNEL_H + +#ifdef __KERNEL__ + +#ifdef CONFIG_DEBUG_KERNEL +/* Complete debug messages */ +#define pr_fmt(fmt) "%s %s[%i]: " fmt "\n", __FILE__, __func__, __LINE__ +#endif + +#include + +#endif /* __KERNEL__ */ + +#endif /*_IP_SET_H */ diff --git a/extensions/ipset-5/ip_set_list.h b/extensions/ipset-5/ip_set_list.h new file mode 100644 index 0000000..40a63f3 --- /dev/null +++ b/extensions/ipset-5/ip_set_list.h @@ -0,0 +1,27 @@ +#ifndef __IP_SET_LIST_H +#define __IP_SET_LIST_H + +/* List type specific error codes */ +enum { + /* Set name to be added/deleted/tested does not exist. */ + IPSET_ERR_NAME = IPSET_ERR_TYPE_SPECIFIC, + /* list:set type is not permitted to add */ + IPSET_ERR_LOOP, + /* Missing reference set */ + IPSET_ERR_BEFORE, + /* Reference set does not exist */ + IPSET_ERR_NAMEREF, + /* Set is full */ + IPSET_ERR_LIST_FULL, + /* Reference set is not added to the set */ + IPSET_ERR_REF_EXIST, +}; + +#ifdef __KERNEL__ + +#define IP_SET_LIST_DEFAULT_SIZE 8 +#define IP_SET_LIST_MIN_SIZE 4 + +#endif /* __KERNEL__ */ + +#endif /* __IP_SET_LIST_H */ diff --git a/extensions/ipset-5/ip_set_list_set.c b/extensions/ipset-5/ip_set_list_set.c new file mode 100644 index 0000000..b53729c --- /dev/null +++ b/extensions/ipset-5/ip_set_list_set.c @@ -0,0 +1,589 @@ +/* Copyright (C) 2008-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 set type: the list:set type */ + +#include "ip_set_kernel.h" +#include +#include +#include +#include + +#include "ip_set.h" +#include "ip_set_timeout.h" +#include "ip_set_list.h" + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Jozsef Kadlecsik "); +MODULE_DESCRIPTION("list:set type of IP sets"); +MODULE_ALIAS("ip_set_list:set"); + +/* Member elements without and with timeout */ +struct set_elem { + ip_set_id_t id; +}; + +struct set_telem { + ip_set_id_t id; + unsigned long timeout; +}; + +/* Type structure */ +struct list_set { + size_t dsize; /* element size */ + u32 size; /* size of set list array */ + u32 timeout; /* timeout value */ + struct timer_list gc; /* garbage collection */ + struct set_elem members[0]; /* the set members */ +}; + +static inline struct set_elem * +list_set_elem(const struct list_set *map, u32 id) +{ + return (struct set_elem *)((char *)map->members + id * map->dsize); +} + +static inline bool +list_set_timeout(const struct list_set *map, u32 id) +{ + const struct set_telem *elem = + (const struct set_telem *) list_set_elem(map, id); + + return ip_set_timeout_test(elem->timeout); +} + +static inline bool +list_set_expired(const struct list_set *map, u32 id) +{ + const struct set_telem *elem = + (const struct set_telem *) list_set_elem(map, id); + + return ip_set_timeout_expired(elem->timeout); +} + +static inline int +list_set_exist(const struct set_telem *elem) +{ + return elem->id != IPSET_INVALID_ID + && !ip_set_timeout_expired(elem->timeout); +} + +/* Set list without and with timeout */ + +static int +list_set_kadt(struct ip_set *set, const struct sk_buff *skb, + enum ipset_adt adt, u8 pf, u8 dim, u8 flags) +{ + struct list_set *map = set->data; + struct set_elem *elem; + u32 i; + int ret; + + for (i = 0; i < map->size; i++) { + elem = list_set_elem(map, i); + if (elem->id == IPSET_INVALID_ID) + return 0; + if (with_timeout(map->timeout) && list_set_expired(map, i)) + continue; + switch (adt) { + case IPSET_TEST: + ret = ip_set_test(elem->id, skb, pf, dim, flags); + if (ret > 0) + return ret; + break; + case IPSET_ADD: + ret = ip_set_add(elem->id, skb, pf, dim, flags); + if (ret == 0) + return ret; + break; + case IPSET_DEL: + ret = ip_set_del(elem->id, skb, pf, dim, flags); + if (ret == 0) + return ret; + break; + default: + break; + } + } + return -EINVAL; +} + +static const struct nla_policy list_set_adt_policy[IPSET_ATTR_ADT_MAX+1] = { + [IPSET_ATTR_NAME] = { .type = NLA_STRING, + .len = IPSET_MAXNAMELEN }, + [IPSET_ATTR_NAMEREF] = { .type = NLA_STRING, + .len = IPSET_MAXNAMELEN }, + [IPSET_ATTR_TIMEOUT] = { .type = NLA_U32 }, + [IPSET_ATTR_LINENO] = { .type = NLA_U32 }, + [IPSET_ATTR_CADT_FLAGS] = { .type = NLA_U32 }, +}; + +static inline bool +next_id_eq(const struct list_set *map, u32 i, ip_set_id_t id) +{ + const struct set_elem *elem; + + if (i + 1 < map->size) { + elem = list_set_elem(map, i + 1); + return !!(elem->id == id + && !(with_timeout(map->timeout) + && list_set_expired(map, i + 1))); + } + + return 0; +} + +static inline void +list_elem_add(struct list_set *map, u32 i, ip_set_id_t id) +{ + struct set_elem *e; + + for (; i < map->size; i++) { + e = list_set_elem(map, i); + swap(e->id, id); + if (e->id == IPSET_INVALID_ID) + break; + } +} + +static inline void +list_elem_tadd(struct list_set *map, u32 i, ip_set_id_t id, + unsigned long timeout) +{ + struct set_telem *e; + + for (; i < map->size; i++) { + e = (struct set_telem *)list_set_elem(map, i); + swap(e->id, id); + if (e->id == IPSET_INVALID_ID) + break; + swap(e->timeout, timeout); + } +} + +static int +list_set_add(struct list_set *map, u32 i, ip_set_id_t id, + unsigned long timeout) +{ + const struct set_elem *e = list_set_elem(map, i); + + if (i == map->size - 1 && e->id != IPSET_INVALID_ID) + /* Last element replaced: e.g. add new,before,last */ + ip_set_put_byindex(e->id); + if (with_timeout(map->timeout)) + list_elem_tadd(map, i, id, timeout); + else + list_elem_add(map, i, id); + + return 0; +} + +static int +list_set_del(struct list_set *map, ip_set_id_t id, u32 i) +{ + struct set_elem *a = list_set_elem(map, i), *b; + + ip_set_put_byindex(id); + + for (; i < map->size - 1; i++) { + b = list_set_elem(map, i + 1); + a->id = b->id; + if (with_timeout(map->timeout)) + ((struct set_telem *)a)->timeout = + ((struct set_telem *)b)->timeout; + a = b; + if (a->id == IPSET_INVALID_ID) + break; + } + /* Last element */ + a->id = IPSET_INVALID_ID; + return 0; +} + +static int +list_set_uadt(struct ip_set *set, struct nlattr *head, int len, + enum ipset_adt adt, u32 *lineno, u32 flags) +{ + struct list_set *map = set->data; + struct nlattr *tb[IPSET_ATTR_ADT_MAX+1]; + bool with_timeout = with_timeout(map->timeout); + int before = 0; + u32 timeout = map->timeout; + ip_set_id_t id, refid = IPSET_INVALID_ID; + const struct set_elem *elem; + struct ip_set *s; + u32 i; + int ret = 0; + + if (nla_parse(tb, IPSET_ATTR_ADT_MAX, head, len, + list_set_adt_policy)) + return -IPSET_ERR_PROTOCOL; + + if (tb[IPSET_ATTR_LINENO]) + *lineno = nla_get_u32(tb[IPSET_ATTR_LINENO]); + + if (tb[IPSET_ATTR_NAME]) { + id = ip_set_get_byname(nla_data(tb[IPSET_ATTR_NAME]), &s); + if (id == IPSET_INVALID_ID) + return -IPSET_ERR_NAME; + /* "Loop detection" */ + if (s->type->features & IPSET_TYPE_NAME) { + ret = -IPSET_ERR_LOOP; + goto finish; + } + } else + return -IPSET_ERR_PROTOCOL; + + if (tb[IPSET_ATTR_CADT_FLAGS]) { + u32 f = ip_set_get_h32(tb[IPSET_ATTR_CADT_FLAGS]); + before = f & IPSET_FLAG_BEFORE; + } + + if (before && !tb[IPSET_ATTR_NAMEREF]) { + ret = -IPSET_ERR_BEFORE; + goto finish; + } + + if (tb[IPSET_ATTR_NAMEREF]) { + refid = ip_set_get_byname(nla_data(tb[IPSET_ATTR_NAMEREF]), + &s); + if (refid == IPSET_INVALID_ID) { + ret = -IPSET_ERR_NAMEREF; + goto finish; + } + if (!before) + before = -1; + } + if (tb[IPSET_ATTR_TIMEOUT]) { + if (!with_timeout) { + ret = -IPSET_ERR_TIMEOUT; + goto finish; + } + timeout = ip_set_timeout_uget(tb[IPSET_ATTR_TIMEOUT]); + } + + switch (adt) { + case IPSET_TEST: + for (i = 0; i < map->size && !ret; i++) { + elem = list_set_elem(map, i); + if (elem->id == IPSET_INVALID_ID + || (before != 0 && i + 1 >= map->size)) + break; + else if (with_timeout && list_set_expired(map, i)) + continue; + else if (before > 0 && elem->id == id) + ret = next_id_eq(map, i, refid); + else if (before < 0 && elem->id == refid) + ret = next_id_eq(map, i, id); + else if (before == 0 && elem->id == id) + ret = 1; + } + break; + case IPSET_ADD: + for (i = 0; i < map->size && !ret; i++) { + elem = list_set_elem(map, i); + if (elem->id == id + && !(with_timeout && list_set_expired(map, i))) + ret = -IPSET_ERR_EXIST; + } + if (ret == -IPSET_ERR_EXIST) + break; + ret = -IPSET_ERR_LIST_FULL; + for (i = 0; i < map->size && ret == -IPSET_ERR_LIST_FULL; i++) { + elem = list_set_elem(map, i); + if (elem->id == IPSET_INVALID_ID) + ret = before != 0 ? -IPSET_ERR_REF_EXIST + : list_set_add(map, i, id, timeout); + else if (elem->id != refid) + continue; + else if (with_timeout && list_set_expired(map, i)) + ret = -IPSET_ERR_REF_EXIST; + else if (before) + ret = list_set_add(map, i, id, timeout); + else if (i + 1 < map->size) + ret = list_set_add(map, i + 1, id, timeout); + } + break; + case IPSET_DEL: + ret = -IPSET_ERR_EXIST; + for (i = 0; i < map->size && ret == -IPSET_ERR_EXIST; i++) { + elem = list_set_elem(map, i); + if (elem->id == IPSET_INVALID_ID) { + ret = before != 0 ? -IPSET_ERR_REF_EXIST + : -IPSET_ERR_EXIST; + break; + } else if (with_timeout && list_set_expired(map, i)) + continue; + else if (elem->id == id + && (before == 0 + || (before > 0 + && next_id_eq(map, i, refid)))) + ret = list_set_del(map, id, i); + else if (before < 0 + && elem->id == refid + && next_id_eq(map, i, id)) + ret = list_set_del(map, id, i + 1); + } + break; + default: + break; + } + +finish: + if (refid != IPSET_INVALID_ID) + ip_set_put_byindex(refid); + if (adt != IPSET_ADD || ret) + ip_set_put_byindex(id); + + return ip_set_eexist(ret, flags) ? 0 : ret; +} + +static void +list_set_flush(struct ip_set *set) +{ + struct list_set *map = set->data; + struct set_elem *elem; + u32 i; + + for (i = 0; i < map->size; i++) { + elem = list_set_elem(map, i); + if (elem->id != IPSET_INVALID_ID) { + ip_set_put_byindex(elem->id); + elem->id = IPSET_INVALID_ID; + } + } +} + +static void +list_set_destroy(struct ip_set *set) +{ + struct list_set *map = set->data; + + if (with_timeout(map->timeout)) + del_timer_sync(&map->gc); + list_set_flush(set); + kfree(map); + + set->data = NULL; +} + +static int +list_set_head(struct ip_set *set, struct sk_buff *skb) +{ + const struct list_set *map = set->data; + struct nlattr *nested; + + nested = ipset_nest_start(skb, IPSET_ATTR_DATA); + if (!nested) + goto nla_put_failure; + NLA_PUT_NET32(skb, IPSET_ATTR_SIZE, htonl(map->size)); + if (with_timeout(map->timeout)) + NLA_PUT_NET32(skb, IPSET_ATTR_TIMEOUT, htonl(map->timeout)); + NLA_PUT_NET32(skb, IPSET_ATTR_REFERENCES, + htonl(atomic_read(&set->ref) - 1)); + NLA_PUT_NET32(skb, IPSET_ATTR_MEMSIZE, + htonl(sizeof(*map) + map->size * map->dsize)); + ipset_nest_end(skb, nested); + + return 0; +nla_put_failure: + return -EFAULT; +} + +static int +list_set_list(const struct ip_set *set, + struct sk_buff *skb, struct netlink_callback *cb) +{ + const struct list_set *map = set->data; + struct nlattr *atd, *nested; + u32 i, first = cb->args[2]; + const struct set_elem *e; + + atd = ipset_nest_start(skb, IPSET_ATTR_ADT); + if (!atd) + return -EFAULT; + for (; cb->args[2] < map->size; cb->args[2]++) { + i = cb->args[2]; + e = list_set_elem(map, i); + if (e->id == IPSET_INVALID_ID) + goto finish; + if (with_timeout(map->timeout) && list_set_expired(map, i)) + continue; + nested = ipset_nest_start(skb, IPSET_ATTR_DATA); + if (!nested) { + if (i == first) { + nla_nest_cancel(skb, atd); + return -EFAULT; + } else + goto nla_put_failure; + } + NLA_PUT_STRING(skb, IPSET_ATTR_NAME, + ip_set_name_byindex(e->id)); + if (with_timeout(map->timeout)) { + const struct set_telem *te = + (const struct set_telem *) e; + NLA_PUT_NET32(skb, IPSET_ATTR_TIMEOUT, + htonl(ip_set_timeout_get(te->timeout))); + } + ipset_nest_end(skb, nested); + } +finish: + ipset_nest_end(skb, atd); + /* Set listing finished */ + cb->args[2] = 0; + return 0; + +nla_put_failure: + nla_nest_cancel(skb, nested); + ipset_nest_end(skb, atd); + return 0; +} + +static bool +list_set_same_set(const struct ip_set *a, const struct ip_set *b) +{ + const struct list_set *x = a->data; + const struct list_set *y = b->data; + + return x->size == y->size + && x->timeout == y->timeout; +} + +static const struct ip_set_type_variant list_set = { + .kadt = list_set_kadt, + .uadt = list_set_uadt, + .destroy = list_set_destroy, + .flush = list_set_flush, + .head = list_set_head, + .list = list_set_list, + .same_set = list_set_same_set, +}; + +static void +list_set_gc(unsigned long ul_set) +{ + struct ip_set *set = (struct ip_set *) ul_set; + struct list_set *map = set->data; + struct set_telem *e; + u32 i; + + /* We run parallel with other readers (test element) + * but adding/deleting new entries is locked out */ + read_lock_bh(&set->lock); + for (i = map->size - 1; i >= 0; i--) { + e = (struct set_telem *) list_set_elem(map, i); + if (e->id != IPSET_INVALID_ID + && list_set_expired(map, i)) + list_set_del(map, e->id, i); + } + read_unlock_bh(&set->lock); + + map->gc.expires = jiffies + IPSET_GC_PERIOD(map->timeout) * HZ; + add_timer(&map->gc); +} + +static inline void +list_set_gc_init(struct ip_set *set) +{ + struct list_set *map = set->data; + + init_timer(&map->gc); + map->gc.data = (unsigned long) set; + map->gc.function = list_set_gc; + map->gc.expires = jiffies + IPSET_GC_PERIOD(map->timeout) * HZ; + add_timer(&map->gc); +} + +/* Create list:set type of sets */ + +static const struct nla_policy +list_set_create_policy[IPSET_ATTR_CREATE_MAX+1] = { + [IPSET_ATTR_SIZE] = { .type = NLA_U32 }, + [IPSET_ATTR_TIMEOUT] = { .type = NLA_U32 }, +}; + +static inline bool +init_list_set(struct ip_set *set, u32 size, size_t dsize, + unsigned long timeout) +{ + struct list_set *map; + struct set_elem *e; + u32 i; + + map = kzalloc(sizeof(*map) + size * dsize, GFP_KERNEL); + if (!map) + return false; + + map->size = size; + map->dsize = dsize; + map->timeout = timeout; + set->data = map; + + for (i = 0; i < size; i++) { + e = list_set_elem(map, i); + e->id = IPSET_INVALID_ID; + } + + return true; +} + +static int +list_set_create(struct ip_set *set, struct nlattr *head, int len, + u32 flags) +{ + struct nlattr *tb[IPSET_ATTR_CREATE_MAX+1]; + u32 size = IP_SET_LIST_DEFAULT_SIZE; + + if (nla_parse(tb, IPSET_ATTR_CREATE_MAX, head, len, + list_set_create_policy)) + return -IPSET_ERR_PROTOCOL; + + if (tb[IPSET_ATTR_SIZE]) + size = ip_set_get_h32(tb[IPSET_ATTR_SIZE]); + if (size < IP_SET_LIST_MIN_SIZE) + size = IP_SET_LIST_MIN_SIZE; + + if (tb[IPSET_ATTR_TIMEOUT]) { + if (!init_list_set(set, size, sizeof(struct set_telem), + ip_set_timeout_uget(tb[IPSET_ATTR_TIMEOUT]))) + return -ENOMEM; + + list_set_gc_init(set); + } else { + if (!init_list_set(set, size, sizeof(struct set_elem), + IPSET_NO_TIMEOUT)) + return -ENOMEM; + } + set->variant = &list_set; + return 0; +} + +static struct ip_set_type list_set_type __read_mostly = { + .name = "list:set", + .protocol = IPSET_PROTOCOL, + .features = IPSET_TYPE_NAME | IPSET_DUMP_LAST, + .dimension = IPSET_DIM_ONE, + .family = AF_UNSPEC, + .revision = 0, + .create = list_set_create, + .me = THIS_MODULE, +}; + +static int __init +list_set_init(void) +{ + return ip_set_type_register(&list_set_type); +} + +static void __exit +list_set_fini(void) +{ + ip_set_type_unregister(&list_set_type); +} + +module_init(list_set_init); +module_exit(list_set_fini); diff --git a/extensions/ipset-5/ip_set_timeout.h b/extensions/ipset-5/ip_set_timeout.h new file mode 100644 index 0000000..519d84f --- /dev/null +++ b/extensions/ipset-5/ip_set_timeout.h @@ -0,0 +1,127 @@ +#ifndef _IP_SET_TIMEOUT_H +#define _IP_SET_TIMEOUT_H + +/* Copyright (C) 2003-2010 Jozsef Kadlecsik + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifdef __KERNEL__ + +/* How often should the gc be run by default */ +#define IPSET_GC_TIME (3 * 60) + +/* Timeout period depending on the timeout value of the given set */ +#define IPSET_GC_PERIOD(timeout) \ + ((timeout/3) ? min_t(u32, (timeout)/3, IPSET_GC_TIME) : 1) + +/* Set is defined without timeout support: timeout value may be 0 */ +#define IPSET_NO_TIMEOUT UINT_MAX + +#define with_timeout(timeout) ((timeout) != IPSET_NO_TIMEOUT) + +static inline unsigned int +ip_set_timeout_uget(struct nlattr *tb) +{ + unsigned int timeout = ip_set_get_h32(tb); + + /* Userspace supplied TIMEOUT parameter: adjust crazy size */ + return timeout == IPSET_NO_TIMEOUT ? IPSET_NO_TIMEOUT - 1 : timeout; +} + +#ifdef IP_SET_BITMAP_TIMEOUT + +/* Bitmap specific timeout constants and macros for the entries */ + +/* Bitmap entry is unset */ +#define IPSET_ELEM_UNSET 0 +/* Bitmap entry is set with no timeout value */ +#define IPSET_ELEM_PERMANENT (UINT_MAX/2) + +static inline bool +ip_set_timeout_test(unsigned long timeout) +{ + return timeout != IPSET_ELEM_UNSET + && (timeout == IPSET_ELEM_PERMANENT + || time_after(timeout, jiffies)); +} + +static inline bool +ip_set_timeout_expired(unsigned long timeout) +{ + return timeout != IPSET_ELEM_UNSET + && timeout != IPSET_ELEM_PERMANENT + && time_before(timeout, jiffies); +} + +static inline unsigned long +ip_set_timeout_set(u32 timeout) +{ + unsigned long t; + + if (!timeout) + return IPSET_ELEM_PERMANENT; + + t = timeout * HZ + jiffies; + if (t == IPSET_ELEM_UNSET || t == IPSET_ELEM_PERMANENT) + /* Bingo! */ + t++; + + return t; +} + +static inline u32 +ip_set_timeout_get(unsigned long timeout) +{ + return timeout == IPSET_ELEM_PERMANENT ? 0 : (timeout - jiffies)/HZ; +} + +#else + +/* Hash specific timeout constants and macros for the entries */ + +/* Hash entry is set with no timeout value */ +#define IPSET_ELEM_PERMANENT 0 + +static inline bool +ip_set_timeout_test(unsigned long timeout) +{ + return timeout == IPSET_ELEM_PERMANENT + || time_after(timeout, jiffies); +} + +static inline bool +ip_set_timeout_expired(unsigned long timeout) +{ + return timeout != IPSET_ELEM_PERMANENT + && time_before(timeout, jiffies); +} + +static inline unsigned long +ip_set_timeout_set(u32 timeout) +{ + unsigned long t; + + if (!timeout) + return IPSET_ELEM_PERMANENT; + + t = timeout * HZ + jiffies; + if (t == IPSET_ELEM_PERMANENT) + /* Bingo! :-) */ + t++; + + return t; +} + +static inline u32 +ip_set_timeout_get(unsigned long timeout) +{ + return timeout == IPSET_ELEM_PERMANENT ? 0 : (timeout - jiffies)/HZ; +} +#endif /* ! IP_SET_BITMAP_TIMEOUT */ + +#endif /* __KERNEL__ */ + +#endif /* _IP_SET_TIMEOUT_H */ diff --git a/extensions/ipset-5/ipset.8 b/extensions/ipset-5/ipset.8 new file mode 100644 index 0000000..b9ca8a5 --- /dev/null +++ b/extensions/ipset-5/ipset.8 @@ -0,0 +1,748 @@ +.\" Man page written by Jozsef Kadlecsik +.\" +.\" This program is free software; you can redistribute it and/or modify +.\" it under the terms of the GNU General Public License as published by +.\" the Free Software Foundation; either version 2 of the License, or +.\" (at your option) any later version. +.\" +.\" This program is distributed in the hope that it will be useful, +.\" but WITHOUT ANY WARRANTY; without even the implied warranty of +.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +.\" GNU General Public License for more details. +.\" +.\" You should have received a copy of the GNU General Public License +.\" along with this program; if not, write to the Free Software +.\" Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +.TH "IPSET" "8" "Oct 15, 2010" "Jozsef Kadlecsik" "" +.SH "NAME" +ipset \(em administration tool for IP sets +.SH "SYNOPSIS" +\fBipset\fR [ \fIOPTIONS\fR ] \fICOMMAND\fR [ \fICOMMAND\-OPTIONS\fR ] +.PP +COMMANDS := { \fBcreate\fR | \fBadd\fR | \fBdel\fR | \fBtest\fR | \fBdestroy\fR | \fBlist\fR | \fBsave\fR | \fBrestore\fR | \fBflush\fR | \fBrename\fR | \fBswap\fR | \fBhelp\fR | \fBversion\fR | \fB\-\fR } +.PP +\fIOPTIONS\fR := { \fB\-exist\fR | \fB\-output\fR { \fBplain\fR | \fBsave\fR | \fBxml\fR } | \fB\-quiet\fR | \fB\-resolve\fR | \fB\-sorted\fR } +.PP +\fBipset\fR \fBcreate\fR \fISETNAME\fR \fITYPENAME\fR [ \fICREATE\-OPTIONS\fR ] +.PP +\fBipset\fR \fBadd\fR \fISETNAME\fR \fIADD\-ENTRY\fR [ \fIADD\-OPTIONS\fR ] +.PP +\fBipset\fR \fBdel\fR \fISETNAME\fR \fIDEL\-ENTRY\fR [ \fIDEL\-OPTIONS\fR ] +.PP +\fBipset\fR \fBtest\fR \fISETNAME\fR \fITEST\-ENTRY\fR [ \fITEST\-OPTIONS\fR ] +.PP +\fBipset\fR \fBdestroy\fR [ \fISETNAME\fR ] +.PP +\fBipset\fR \fBlist\fR [ \fISETNAME\fR ] +.PP +\fBipset\fR \fBsave\fR [ \fISETNAME\fR ] +.PP +\fBipset\fR \fBrestore\fR +.PP +\fBipset\fR \fBflush\fR [ \fISETNAME\fR ] +.PP +\fBipset\fR \fBrename\fR \fISETNAME\-FROM\fR \fISETNAME\-TO\fR +.PP +\fBipset\fR \fBswap\fR \fISETNAME\-FROM\fR \fISETNAME\-TO\fR +.PP +\fBipset\fR \fBhelp\fR [ \fITYPENAME\fR ] +.PP +\fBipset\fR \fBversion\fR +.PP +\fBipset\fR \fB\-\fR +.SH "DESCRIPTION" +\fBipset\fR +is used to set up, maintain and inspect so called IP sets in the Linux +kernel. Depending on the type of the set, an IP set may store IP(v4/v6) +addresses, (TCP/UDP) port numbers, IP and MAC address pairs, IP address +and port number pairs, etc. See the set type definitions below. +.PP +\fBIptables\fR +matches and targets referring to sets create references, which +protect the given sets in the kernel. A set cannot be destroyed +while there is a single reference pointing to it. +.SH "OPTIONS" +The options that are recognized by +\fBipset\fR +can be divided into several different groups. +.SS COMMANDS +These options specify the desired action to perform. Only one of them +can be specified on the command line unless otherwise specified below. +For all the long versions of the command names, you need to use only enough +letters to ensure that +\fBipset\fR +can differentiate it from all other commands. The +\fBipset\fR +parser follows the order here when looking for the shortest match +in the long command names. +.TP +\fBn\fP, \fBcreate\fP \fISETNAME\fP \fITYPENAME\fP [ \fICREATE\-OPTIONS\fP ] +Create a set identified with setname and specified type. The type may require +type specific options. If the +\fB\-exist\fR +option is specified, +\fBipset\fR +ignores the error otherwise raised when the same set (setname and create parameters +are identical) already exists. +.TP +\fBadd\fP \fISETNAME\fP \fIADD\-ENTRY\fP [ \fIADD\-OPTIONS\fP ] +Add a given entry to the set. If the +\fB\-exist\fR +option is specified, +\fBipset\fR +ignores if the entry already added to the set. +.TP +\fBdel\fP \fISETNAME\fP \fIDEL\-ENTRY\fP [ \fIDEL\-OPTIONS\fP ] +Delete an entry from a set. If the +\fB\-exist\fR +option is specified, +\fBipset\fR +ignores if the entry does not added to (already expired from) the set. +.TP +\fBtest\fP \fISETNAME\fP \fITEST\-ENTRY\fP [ \fITEST\-OPTIONS\fP ] +Test wether an entry is in a set or not. Exit status number is zero +if the tested entry is in the set and nonzero if it is missing from +the set. +.TP +\fBx\fP, \fBdestroy\fP [ \fISETNAME\fP ] +Destroy the specified set or all the sets if none is given. + +If the set has got reference(s), nothing is done and no set destroyed. +.TP +\fBlist\fP [ \fISETNAME\fP ] +List the header data and the entries for the specified set, or for +all sets if none is given. The +\fB\-resolve\fP +option can be used to force name lookups (which may be slow). When the +\fB\-sorted\fP +option is given, the entries are listed sorted (if the given set +type supports the operation). The option +\fB\-output\fR +can be used to control the format of the listing: +\fBplain\fR, \fBsave\fR or \fBxml\fR. +The default is +\fBplain\fR. +.TP +\fBsave\fP [ \fISETNAME\fP ] +Save the given set, or all sets if none is given +to stdout in a format that +\fBrestore\fP +can read. +.TP +\fBrestore\fP +Restore a saved session generated by +\fBsave\fP. +The saved session can be fed from stdin. +.TP +\fBflush\fP [ \fISETNAME\fP ] +Flush all entries from the specified set or flush +all sets if none is given. +.TP +\fBe\fP, \fBrename\fP \fISETNAME\-FROM\fP \fISETNAME\-TO\fP +Rename a set. Set identified by +\fISETNAME\-TO\fR +must not exist. +.TP +\fBw\fP, \fBswap\fP \fISETNAME\-FROM\fP \fISETNAME\-TO\fP +Swap the content of two sets, or in another words, +exchange the name of two sets. The referred sets must exist and +identical type of sets can be swapped only. +.TP +\fBhelp\fP [ \fITYPENAME\fP ] +Print help and set type specific help if +\fITYPENAME\fR +is specified. +.TP +\fBversion\fP +Print program version. +.TP +\fB\-\fP +If a dash is specified as command, then +\fBipset\fR +enters a simple interactive mode and the commands are read from the standard input. +The interactive mode can be finished by entering the pseudo\-command +\fBquit\fR. +.P +.SS "OTHER OPTIONS" +The following additional options can be specified. The long option names +cannot be abbreviated. +.TP +\fB\-!\fP, \fB\-exist\fP +Ignore errors when the exactly the same set is to be created or already +added entry is added or missing entry is deleted. +.TP +\fB\-o\fP, \fB\-output\fP { \fBplain\fR | \fBsave\fR | \fBxml\fR } +Select the output format to the +\fBlist\fR +command. +.TP +\fB\-q\fP, \fB\-quiet\fP +Suppress any output to stdout and stderr. +\fBipset\fR +will still exit with error if it cannot continue. +.TP +\fB\-r\fP, \fB\-resolve\fP +When listing sets, enforce name lookup. The +program will try to display the IP entries resolved to +host names which requires +\fBslow\fR +DNS lookups. +.TP +\fB\-s\fP, \fB\-sorted\fP +Sorted output. When listing sets entries are listed sorted. Not supported yet. +.SH "SET TYPES" +A set type comprises of the storage method by which the data is stored and +the data type(s) which are stored in the set. Therefore the +\fITYPENAME\fR +parameter of the +\fBcreate\fR +command follows the syntax + +\fITYPENAME\fR := \fImethod\fR\fB:\fR\fIdatatype\fR[\fB,\fR\fIdatatype\fR[\fB,\fR\fIdatatype\fR]] + +where the current list of the methods are +\fBbitmap\fR, \fBhash\fR, and \fBlist\fR and the possible data types +are \fBip\fR, \fBmac\fR and \fBport\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 + +ipset add foo ipaddr,portnum,ipaddr + +The \fBbitmap\fR and \fBlist\fR types use a fixed sized storage. The \fBhash\fR +types use a hash to store the elements. In order to avoid clashes in the hash, +a limited number of chaining, and if that is exhausted, the doubling of the hash size +is performed when adding entries by the +\fBipset\fR +command. When entries added by the +\fBSET\fR +target of +\fBiptables/ip6tables\fR, +then the hash size is fixed and the set won't be duplicated, even if the new +entry cannot be added to the set. + +All set types support the optional + +\fBtimeout\fR \fIvalue\fR + +parameter when creating a set and adding entries. The value of the \fBtimeout\fR +parameter for the \fBcreate\fR command means the default timeout value (in seconds) +for new entries. If a set is created with timeout support, then the same +\fBtimeout\fR option can be used to specify non\-default timeout values +when adding entries. Zero timeout value means the entry is added permanent to the set. +.SS bitmap:ip +The \fBbitmap:ip\fR set type uses a memory range to store either IPv4 host +(default) or IPv4 network addresses. A \fBbitmap:ip\fR type of set can store up +to 65536 entries. +.PP +\fICREATE\-OPTIONS\fR := \fBrange\fP \fIfromip\fP\-\fItoip\fR|\fIip\fR/\fIcidr\fR [ \fBnetmask\fP \fIcidr\fP ] [ \fBtimeout\fR \fIvalue\fR ] +.PP +\fIADD\-ENTRY\fR := { \fIip\fR | \fIfromip\fR\-\fItoip\fR | \fIip\fR/\fIcidr\fR } +.PP +\fIADD\-OPTIONS\fR := [ \fBtimeout\fR \fIvalue\fR ] +.PP +\fIDEL\-ENTRY\fR := { \fIip\fR | \fIfromip\fR\-\fItoip\fR | \fIip\fR/\fIcidr\fR } +.PP +\fITEST\-ENTRY\fR := \fIip\fR +.PP +Mandatory \fBcreate\fR options: +.TP +\fBrange\fP \fIfromip\fP\-\fItoip\fR|\fIip\fR/\fIcidr\fR +Create the set from the specified inclusive address range expressed in an +IPv4 address range or network. The size of the range (in entries) cannot exceed +the limit of maximum 65536 elements. +.PP +Optional \fBcreate\fR options: +.TP +\fBnetmask\fP \fIcidr\fP +When the optional \fBnetmask\fP parameter specified, network addresses will be +stored in the set instead of IP host addresses. The \fIcidr\fR prefix value must be +between 1\-32. +An IP address will be in the set if the network address, which is resulted by +masking the address with the specified netmask calculated from the prefix, +can be found in the set. +.PP +The \fBbitmap:ip\fR type supports adding or deleting multiple entries in one +command. +.PP +Examples: +.IP +ipset create foo bitmap:ip range 192.168.0.0/16 +.IP +ipset add foo 192.168.1/24 +.IP +ipset test foo 192.168.1.1 +.SS bitmap:ip,mac +The \fBbitmap:ip,mac\fR set type uses a memory range to store IPv4 and a MAC address pairs. A \fBbitmap:ip,mac\fR type of set can store up to 65536 entries. +.PP +\fICREATE\-OPTIONS\fR := \fBrange\fP \fIfromip\fP\-\fItoip\fR|\fIip\fR/\fIcidr\fR [ \fBtimeout\fR \fIvalue\fR ] +.PP +\fIADD\-ENTRY\fR := \fIip\fR[,\fImacaddr\fR] +.PP +\fIADD\-OPTIONS\fR := [ \fBtimeout\fR \fIvalue\fR ] +.PP +\fIDEL\-ENTRY\fR := \fIip\fR[,\fImacaddr\fR] +.PP +\fITEST\-ENTRY\fR := \fIip\fR[,\fImacaddr\fR] +.PP +Mandatory options to use when creating a \fBbitmap:ip,mac\fR type of set: +.TP +\fBrange\fP \fIfromip\fP\-\fItoip\fR|\fIip\fR/\fIcidr\fR +Create the set from the specified inclusive address range expressed in an +IPv4 address range or network. The size of the range cannot exceed the limit +of maximum 65536 entries. +.PP +The \fBbitmap:ip,mac\fR type is exceptional in the sense that the MAC part can +be left out when adding/deleting/testing entries in the set. If we add an entry +without the MAC address specified, then when the first time the entry is +matched by the kernel, it will automatically fill out the missing MAC address with the +source MAC address from the packet. If the entry was specified with a timeout value, +the timer starts off when the IP and MAC address pair is complete. +.PP +Please note, the \fBset\fR match and \fBSET\fR target netfilter kernel modules +\fBalways\fR use the source MAC address from the packet to match, add or delete +entries from a \fBbitmap:ip,mac\fR type of set. +.PP +Examples: +.IP +ipset create foo bitmap:ip,mac range 192.168.0.0/16 +.IP +ipset add foo 192.168.1.1,12:34:56:78:9A:BC +.IP +ipset test foo 192.168.1.1 +.SS bitmap:port +The \fBbitmap:port\fR set type uses a memory range to store port numbers +and such a set can store up to 65536 ports. +.PP +\fICREATE\-OPTIONS\fR := \fBrange\fP \fIfromport\fP\-\fItoport [ \fBtimeout\fR \fIvalue\fR ] +.PP +\fIADD\-ENTRY\fR := { \fIport\fR | \fIfromport\fR\-\fItoport\fR } +.PP +\fIADD\-OPTIONS\fR := [ \fBtimeout\fR \fIvalue\fR ] +.PP +\fIDEL\-ENTRY\fR := { \fIport\fR | \fIfromport\fR\-\fItoport\fR } +.PP +\fITEST\-ENTRY\fR := \fIport\fR +.PP +Mandatory options to use when creating a \fBbitmap:port\fR type of set: +.TP +\fBrange\fP \fIfromport\fP\-\fItoport\fR +Create the set from the specified inclusive port range. +.PP +Examples: +.IP +ipset create foo bitmap:port range 0\-1024 +.IP +ipset add foo 80 +.IP +ipset test foo 80 +.SS hash:ip +The \fBhash:ip\fR set type uses a hash to store IP host addresses (default) or +network addresses. Zero valued IP address cannot be stored in a \fBhash:ip\fR +type of set. +.PP +\fICREATE\-OPTIONS\fR := [ \fBfamily\fR { \fBinet\fR | \fBinet6\fR } ] | [ \fBhashsize\fR \fIvalue\fR ] [ \fBmaxelem\fR \fIvalue\fR ] [ \fBnetmask\fP \fIcidr\fP ] [ \fBtimeout\fR \fIvalue\fR ] +.PP +\fIADD\-ENTRY\fR := \fIipaddr\fR +.PP +\fIADD\-OPTIONS\fR := [ \fBtimeout\fR \fIvalue\fR ] +.PP +\fIDEL\-ENTRY\fR := \fIipaddr\fR +.PP +\fITEST\-ENTRY\fR := \fIipaddr\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. +.TP +\fBnetmask\fP \fIcidr\fP +When the optional \fBnetmask\fP parameter specified, network addresses will be +stored in the set instead of IP host addresses. The \fIcidr\fP prefix value must be +between 1\-32 for IPv4 and between 1\-128 for IPv6. An IP address will be in the set +if the network address, which is resulted by masking the address with the netmask +calculated from the prefix, can be found in the set. +.PP +For the \fBinet\fR family one can add or delete multiple entries by specifying +a range or a network: +.PP +\fIipaddr\fR := { \fIip\fR | \fIfromaddr\fR\-\fItoaddr\fR | \fIip\fR/\fIcidr\fR } +.PP +Examples: +.IP +ipset create foo hash:ip netmask 24 +.IP +ipset add foo 192.168.1.1\-192.168.1.2 +.IP +ipset test foo 192.168.1.2 +.SS hash:net +The \fBhash:net\fR set type uses a hash to store different sized IP network addresses. +Network address with zero prefix size cannot be stored in this type of sets. +.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 := \fIip\fR[/\fIcidr\fR] +.PP +\fIADD\-OPTIONS\fR := [ \fBtimeout\fR \fIvalue\fR ] +.PP +\fIDEL\-ENTRY\fR := \fIip\fR[/\fIcidr\fR] +.PP +\fITEST\-ENTRY\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 +When adding/deleting/testing entries, if the cidr prefix parameter is not specified, +then the host prefix value is assumed. When adding/deleting entries, overlapping +elements are not checked. +.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 lookup time grows linearly with the number of the different prefix +values added to the set. +.PP +Examples: +.IP +ipset create foo hash:net +.IP +ipset add foo 192.168.0/24 +.IP +ipset add foo 10.1.0.0/16 +.IP +ipset test foo 192.168.0/24 +.SS hash:ip,port +The \fBhash:ip,port\fR set type uses a hash to store IP address and port number pairs. +The port number is interpreted together with a protocol (default TCP) and zero +protocol number cannot be used. +.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 := \fIipaddr\fR,[\fIproto\fR:]\fIport\fR +.PP +\fIADD\-OPTIONS\fR := [ \fBtimeout\fR \fIvalue\fR ] +.PP +\fIDEL\-ENTRY\fR := \fIipaddr\fR,[\fIproto\fR:]\fIport\fR +.PP +\fITEST\-ENTRY\fR := \fIipaddr\fR,[\fIproto\fR:]\fIport\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 \fBinet\fR family one can add or delete multiple entries by specifying +a range or a network of IPv4 addresses in the IP address part of the entry: +.PP +\fIipaddr\fR := { \fIip\fR | \fIfromaddr\fR\-\fItoaddr\fR | \fIip\fR/\fIcidr\fR } +.PP +The +[\fIproto\fR:]\fIport\fR +part of the elements may be expressed in the following forms, where the range +variations are valid when adding or deleting entries: +.TP +\fIportname[\-portname]\fR +TCP port or range of ports expressed in TCP portname identifiers from /etc/services +.TP +\fIportnumber[\-portnumber]\fR +TCP port or range of ports expressed in TCP port numbers +.TP +\fBtcp\fR|\fBudp\fR:\fIportname\fR|\fIportnumber\fR[\-\fIportname\fR|\fIportnumber\fR] +TCP or UDP port or port range expressed in port name(s) or port number(s) +.TP +\fBicmp\fR:\fIcodename\fR|\fItype\fR/\fIcode\fR +ICMP codename or type/code. The supported ICMP codename identifiers can always +be listed by the help command. +.TP +\fBicmpv6\fR:\fIcodename\fR|\fItype\fR/\fIcode\fR +ICMPv6 codename or type/code. The supported ICMPv6 codename identifiers can always +be listed by the help command. +.TP +\fIproto\fR:0 +All other protocols, as an identifier from /etc/protocols or number. The pseudo +port number must be zero. +.PP +The \fBhash:ip,port\fR type of sets require +two \fBsrc\fR/\fBdst\fR parameters of the \fBset\fR match and \fBSET\fR +target kernel modules. +.PP +Examples: +.IP +ipset create foo hash:ip,port +.IP +ipset add foo 192.168.1.0/24,80\-82 +.IP +ipset add foo 192.168.1.1,udp:53 +.IP +ipset add foo 192.168.1.1,ospf:0 +.IP +ipset test foo 192.168.1.1,80 +.SS hash:net,port +The \fBhash:net,port\fR set type uses a hash to store different sized IP network +address and port pairs. The port number is interpreted together with a protocol +(default TCP) and zero protocol number cannot be used. Network +address with zero prefix size is not accepted either. +.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 := \fIipaddr\fR[/\fIcidr\fR],[\fIproto\fR:]\fIport\fR +.PP +\fIADD\-OPTIONS\fR := [ \fBtimeout\fR \fIvalue\fR ] +.PP +\fIDEL\-ENTRY\fR := \fIipaddr\fR[/\fIcidr\fR],[\fIproto\fR:]\fIport\fR +.PP +\fITEST\-ENTRY\fR := \fIipaddr\fR[/\fIcidr\fR],[\fIproto\fR:]\fIport\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 +[\fIproto\fR:]\fIport\fR +part of the elements see the description at the +\fBhash:ip,port\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, overlapping +elements are not checked. +.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 lookup time grows linearly with the number of the different prefix +values added to the set. +.PP +Examples: +.IP +ipset create foo hash:net,port +.IP +ipset add foo 192.168.0/24,25 +.IP +ipset add foo 10.1.0.0/16,80 +.IP +ipset test foo 192.168.0/24,25 +.SS hash:ip,port,ip +The \fBhash:ip,port,ip\fR set type uses a hash to store IP address, port number +and a second IP address triples. The port number is interpreted together with a +protocol (default TCP) and zero protocol number cannot be used. +.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 := \fIipaddr\fR,[\fIproto\fR:]\fIport\fR,\fIip\fR +.PP +\fIADD\-OPTIONS\fR := [ \fBtimeout\fR \fIvalue\fR ] +.PP +\fIDEL\-ENTRY\fR := \fIipaddr\fR,[\fIproto\fR:]\fIport\fR,\fIip\fR +.PP +\fITEST\-ENTRY\fR := \fIipaddr\fR,[\fIproto\fR:]\fIport\fR,\fIip\fR +.PP +For the first \fIipaddr\fR and +[\fIproto\fR:]\fIport\fR +parts of the elements see the descriptions at the +\fBhash:ip,port\fR set type. +.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 +The \fBhash:ip,port,ip\fR type of sets require +three \fBsrc\fR/\fBdst\fR parameters of the \fBset\fR match and \fBSET\fR +target kernel modules. +.PP +Examples: +.IP +ipset create foo hash:ip,port,ip +.IP +ipset add foo 192.168.1.1,80,10.0.0.1 +.IP +ipset test foo 192.168.1.1,udp:53,10.0.0.1 +.SS hash:ip,port,net +The \fBhash:ip,port,net\fR set type uses a hash to store IP address, port number +and IP network address triples. The port number is interpreted together with a +protocol (default TCP) and zero protocol number cannot be used. Network +address with zero prefix size cannot be stored either. +.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 := \fIipaddr\fR,[\fIproto\fR:]\fIport\fR,\fIip\fR[/\fIcidr\fR] +.PP +\fIADD\-OPTIONS\fR := [ \fBtimeout\fR \fIvalue\fR ] +.PP +\fIDEL\-ENTRY\fR := \fIipaddr\fR,[\fIproto\fR:]\fIport\fR,\fIip\fR[/\fIcidr\fR] +.PP +\fITEST\-ENTRY\fR := \fIipaddr\fR,[\fIproto\fR:]\fIport\fR,\fIip\fR[/\fIcidr\fR] +.PP +For the first \fIipaddr\fR and +[\fIproto\fR:]\fIport\fR +parts of the elements see the descriptions at the +\fBhash:ip,port\fR set type. +.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 +From the \fBset\fR netfilter match point of view the searching for a match +always starts from the smallest size of netblock (most specific +cidr) to the largest one (least specific cidr) added to the set. +When adding/deleting triples +to the set by the \fBSET\fR netfilter target, it will be +added/deleted by the most specific cidr which can be found in the +set, or by the host cidr value if the set is empty. +.PP +The lookup time grows linearly with the number of the different \fIcidr\fR +values added to the set. +.PP +The \fBhash:ip,port,net\fR type of sets require three \fBsrc\fR/\fBdst\fR parameters of +the \fBset\fR match and \fBSET\fR target kernel modules. +.PP +Examples: +.IP +ipset create foo hash:ip,port,net +.IP +ipset add foo 192.168.1,80,10.0.0/24 +.IP +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 list:set +The \fBlist:set\fR type uses a simple list in which you can store +set names. +.PP +\fICREATE\-OPTIONS\fR := [ \fBsize\fR \fIvalue\fR ] [ \fBtimeout\fR \fIvalue\fR ] +.PP +\fIADD\-ENTRY\fR := \fIsetname\fR [ { \fBbefore\fR | \fBafter\fR } \fIsetname\fR ] +.PP +\fIADD\-OPTIONS\fR := [ \fBtimeout\fR \fIvalue\fR ] +.PP +\fIDEL\-ENTRY\fR := \fIsetname\fR [ { \fBbefore\fR | \fBafter\fR } \fIsetname\fR ] +.PP +\fITEST\-ENTRY\fR := \fIsetname\fR [ { \fBbefore\fR | \fBafter\fR } \fIsetname\fR ] +.PP +Optional \fBcreate\fR options: +.TP +\fBsize\fR \fIvalue\fR +The size of the list, the default is 8. +.PP +By the \fBipset\fR commad you can add, delete and test set names in a +\fBlist:set\fR type of set. +.PP +By the \fBset\fR match or \fBSET\fR target of netfilter +you can test, add or delete entries in the sets added to the \fBlist:set\fR +type of set. The match will try to find a matching entry in the sets and +the target will try to add an entry to the first set to which it can be added. +The number of direction options of the match and target are important: sets which +require more parameters than specified are skipped, while sets with equal +or less parameters are checked, elements added/deleted. For example if \fIa\fR and +\fIb\fR are \fBlist:set\fR type of sets then in the command +.IP +iptables \-m set \-\-match\-set a src,dst \-j SET \-\-add\-set b src,dst +.PP +the match and target will skip any set in \fIa\fR and \fIb\fR +which stores data triples, but will match all sets with single or double +data storage in \fIa\fR set and stop matching at the first successful set, +and add src to the first single or src,dst to the first double data storage set +in \fIb\fR to which the entry can be added. You can imagine a \fBlist:set\fR +type of set as an ordered union of the set elements. +.PP +Please note: by the \fBipset\fR commad you can add, delete and \fBtest\fR +the setnames in a \fBlist:set\fR type of set, and \fBnot\fR the presence of +a set's member (such as an IP address). +.SH "GENERAL RESTRICTIONS" +Zero valued set entries cannot be used with hash methods. Zero protocol value with ports +cannot be used. +.SH "COMMENTS" +If you want to store same size subnets from a given network +(say /24 blocks from a /8 network), use the \fBbitmap:ip\fR set type. +If you want to store random same size networks (say random /24 blocks), +use the \fBhash:ip\fR set type. If you have got random size of netblocks, +use \fBhash:net\fR. +.PP +Backward compatibility is maintained and old \fBipset\fR syntax is still supported. +.PP +The \fBiptree\fR and \fBiptreemap\fR set types are removed: if you refer to them, +they are automatically replaced by \fBhash:ip\fR type of sets. +.SH "DIAGNOSTICS" +Various error messages are printed to standard error. The exit code +is 0 for correct functioning. +.SH "BUGS" +Bugs? No, just funny features. :\-) +OK, just kidding... +.SH "SEE ALSO" +\fBiptables\fR(8), +\fBip6tables\fR(8) +.SH "AUTHORS" +Jozsef Kadlecsik wrote ipset, which is based on ippool by +Joakim Axelsson, Patrick Schaaf and Martin Josefsson. +.br +Sven Wegener wrote the iptreemap type. +.SH "LAST REMARK" +\fBI stand on the shoulders of giants.\fR diff --git a/extensions/ipset-5/jhash.h b/extensions/ipset-5/jhash.h new file mode 100644 index 0000000..72ca901 --- /dev/null +++ b/extensions/ipset-5/jhash.h @@ -0,0 +1,169 @@ +#ifndef _LINUX_JHASH_H +#define _LINUX_JHASH_H + +/* jhash.h: Jenkins hash support. + * + * Copyright (C) 2006. Bob Jenkins (bob_jenkins@burtleburtle.net) + * + * http://burtleburtle.net/bob/hash/ + * + * These are the credits from Bob's sources: + * + * lookup3.c, by Bob Jenkins, May 2006, Public Domain. + * + * These are functions for producing 32-bit hashes for hash table lookup. + * hashword(), hashlittle(), hashlittle2(), hashbig(), mix(), and final() + * are externally useful functions. Routines to test the hash are included + * if SELF_TEST is defined. You can use this free for any purpose. It's in + * the public domain. It has no warranty. + * + * Copyright (C) 2009-2010 Jozsef Kadlecsik (kadlec@blackhole.kfki.hu) + * + * I've modified Bob's hash to be useful in the Linux kernel, and + * any bugs present are my fault. + * Jozsef + */ +#include +#include + +/* Best hash sizes are of power of two */ +#define jhash_size(n) ((u32)1<<(n)) +/* Mask the hash value, i.e (value & jhash_mask(n)) instead of (value % n) */ +#define jhash_mask(n) (jhash_size(n)-1) + +/* __jhash_mix -- mix 3 32-bit values reversibly. */ +#define __jhash_mix(a, b, c) \ +{ \ + a -= c; a ^= rol32(c, 4); c += b; \ + b -= a; b ^= rol32(a, 6); a += c; \ + c -= b; c ^= rol32(b, 8); b += a; \ + a -= c; a ^= rol32(c, 16); c += b; \ + b -= a; b ^= rol32(a, 19); a += c; \ + c -= b; c ^= rol32(b, 4); b += a; \ +} + +/* __jhash_final - final mixing of 3 32-bit values (a,b,c) into c */ +#define __jhash_final(a, b, c) \ +{ \ + c ^= b; c -= rol32(b, 14); \ + a ^= c; a -= rol32(c, 11); \ + b ^= a; b -= rol32(a, 25); \ + c ^= b; c -= rol32(b, 16); \ + a ^= c; a -= rol32(c, 4); \ + b ^= a; b -= rol32(a, 14); \ + c ^= b; c -= rol32(b, 24); \ +} + +/* An arbitrary initial parameter */ +#define JHASH_INITVAL 0xdeadbeef + +/* jhash - hash an arbitrary key + * @k: sequence of bytes as key + * @length: the length of the key + * @initval: the previous hash, or an arbitray value + * + * The generic version, hashes an arbitrary sequence of bytes. + * No alignment or length assumptions are made about the input key. + * + * Returns the hash value of the key. The result depends on endianness. + */ +static inline u32 jhash(const void *key, u32 length, u32 initval) +{ + u32 a, b, c; + const u8 *k = key; + + /* Set up the internal state */ + a = b = c = JHASH_INITVAL + length + initval; + + /* All but the last block: affect some 32 bits of (a,b,c) */ + while (length > 12) { + a += __get_unaligned_cpu32(k); + b += __get_unaligned_cpu32(k + 4); + c += __get_unaligned_cpu32(k + 8); + __jhash_mix(a, b, c); + length -= 12; + k += 12; + } + /* Last block: affect all 32 bits of (c) */ + /* All the case statements fall through */ + switch (length) { + case 12: c += (u32)k[11]<<24; + case 11: c += (u32)k[10]<<16; + case 10: c += (u32)k[9]<<8; + case 9: c += k[8]; + case 8: b += (u32)k[7]<<24; + case 7: b += (u32)k[6]<<16; + case 6: b += (u32)k[5]<<8; + case 5: b += k[4]; + case 4: a += (u32)k[3]<<24; + case 3: a += (u32)k[2]<<16; + case 2: a += (u32)k[1]<<8; + case 1: a += k[0]; + __jhash_final(a, b, c); + case 0: /* Nothing left to add */ + break; + } + + return c; +} + +/* jhash2 - hash an array of u32's + * @k: the key which must be an array of u32's + * @length: the number of u32's in the key + * @initval: the previous hash, or an arbitray value + * + * Returns the hash value of the key. + */ +static inline u32 jhash2(const u32 *k, u32 length, u32 initval) +{ + u32 a, b, c; + + /* Set up the internal state */ + a = b = c = JHASH_INITVAL + (length<<2) + initval; + + /* Handle most of the key */ + while (length > 3) { + a += k[0]; + b += k[1]; + c += k[2]; + __jhash_mix(a, b, c); + length -= 3; + k += 3; + } + + /* Handle the last 3 u32's: all the case statements fall through */ + switch (length) { + case 3: c += k[2]; + case 2: b += k[1]; + case 1: a += k[0]; + __jhash_final(a, b, c); + case 0: /* Nothing left to add */ + break; + } + + return c; +} + +/* jhash_3words - hash exactly 3, 2 or 1 word(s) */ +static inline u32 jhash_3words(u32 a, u32 b, u32 c, u32 initval) +{ + a += JHASH_INITVAL; + b += JHASH_INITVAL; + c += initval; + + __jhash_final(a, b, c); + + return c; +} + +static inline u32 jhash_2words(u32 a, u32 b, u32 initval) +{ + return jhash_3words(a, b, 0, initval); +} + +static inline u32 jhash_1word(u32 a, u32 initval) +{ + return jhash_3words(a, 0, 0, initval); +} + +#endif /* _LINUX_JHASH_H */ diff --git a/extensions/ipset-5/libipset/data.c b/extensions/ipset-5/libipset/data.c new file mode 100644 index 0000000..022e4b4 --- /dev/null +++ b/extensions/ipset-5/libipset/data.c @@ -0,0 +1,568 @@ +/* Copyright 2007-2010 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 /* assert */ +#include /* ntoh* */ +#include /* ETH_ALEN */ +#include /* AF_ */ +#include /* malloc, free */ +#include /* memset */ + +#include /* IPSET_MAXNAMELEN */ +#include /* D() */ +#include /* struct ipset_type */ +#include /* inXcpy */ +#include /* prototypes */ + +/* Internal data structure to hold + * a) input data entered by the user or + * b) data received from kernel + * + * We always store the data in host order, *except* IP addresses. + */ + +struct ipset_data { + /* Option bits: which fields are set */ + uint64_t bits; + /* Option bits: which options are ignored */ + uint64_t ignored; + /* Setname */ + char setname[IPSET_MAXNAMELEN]; + /* Set type */ + const struct ipset_type *type; + /* Common CADT options */ + uint8_t cidr; + uint8_t family; + uint32_t flags; /* command level flags */ + uint32_t cadt_flags; /* data level flags */ + uint32_t timeout; + union nf_inet_addr ip; + union nf_inet_addr ip_to; + uint16_t port; + uint16_t port_to; + union { + /* RENAME/SWAP */ + char setname2[IPSET_MAXNAMELEN]; + /* CREATE/LIST/SAVE */ + struct { + uint8_t probes; + uint8_t resize; + uint8_t netmask; + uint32_t hashsize; + uint32_t maxelem; + uint32_t gc; + uint32_t size; + /* Filled out by kernel */ + uint32_t references; + uint32_t elements; + uint32_t memsize; + char typename[IPSET_MAXNAMELEN]; + uint8_t revision_min; + uint8_t revision; + } create; + /* ADT/LIST/SAVE */ + struct { + union nf_inet_addr ip2; + uint8_t cidr2; + uint8_t proto; + char ether[ETH_ALEN]; + char name[IPSET_MAXNAMELEN]; + char nameref[IPSET_MAXNAMELEN]; + } adt; + }; +}; + +static void +copy_addr(uint8_t family, union nf_inet_addr *ip, const void *value) +{ + if (family == AF_INET) + in4cpy(&ip->in, value); + else + in6cpy(&ip->in6, value); +} + +/** + * ipset_strlcpy - copy the string from src to dst + * @dst: the target string buffer + * @src: the source string buffer + * @len: the length of bytes to copy, including the terminating null byte. + * + * Copy the string from src to destination, but at most len bytes are + * copied. The target is unconditionally terminated by the null byte. + */ +void +ipset_strlcpy(char *dst, const char *src, size_t len) +{ + assert(dst); + assert(src); + + strncpy(dst, src, len); + dst[len - 1] = '\0'; +} + +/** + * ipset_data_flags_test - test option bits in the data blob + * @data: data blob + * @flags: the option flags to test + * + * Returns true if the options are already set in the data blob. + */ +bool +ipset_data_flags_test(const struct ipset_data *data, uint64_t flags) +{ + assert(data); + return !!(data->bits & flags); +} + +/** + * ipset_data_flags_set - set option bits in the data blob + * @data: data blob + * @flags: the option flags to set + * + * The function sets the flags in the data blob so that + * the corresponding fields are regarded as if filled with proper data. + */ +void +ipset_data_flags_set(struct ipset_data *data, uint64_t flags) +{ + assert(data); + data->bits |= flags; +} + +/** + * ipset_data_flags_unset - unset option bits in the data blob + * @data: data blob + * @flags: the option flags to unset + * + * The function unsets the flags in the data blob. + * This is the quick way to clear specific fields. + */ +void +ipset_data_flags_unset(struct ipset_data *data, uint64_t flags) +{ + assert(data); + data->bits &= ~flags; +} + +#define flag_type_attr(data, opt, flag) \ +do { \ + data->flags |= flag; \ + opt = IPSET_OPT_FLAGS; \ +} while (0) + +#define cadt_flag_type_attr(data, opt, flag) \ +do { \ + data->cadt_flags |= flag; \ + opt = IPSET_OPT_CADT_FLAGS; \ +} while (0) + +/** + * ipset_data_ignored - test and set ignored bits in the data blob + * @data: data blob + * @flags: the option flag to be ignored + * + * Returns true if the option was not already ignored. + */ +bool +ipset_data_ignored(struct ipset_data *data, enum ipset_opt opt) +{ + bool ignored; + assert(data); + + ignored = data->ignored & IPSET_FLAG(opt); + data->ignored |= IPSET_FLAG(opt); + + return ignored; +} + +/** + * ipset_data_set - put data into the data blob + * @data: data blob + * @opt: the option kind of the data + * @value: the value of the data + * + * Put a given kind of data into the data blob and mark the + * option kind as already set in the blob. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_data_set(struct ipset_data *data, enum ipset_opt opt, const void *value) +{ + assert(data); + assert(opt != IPSET_OPT_NONE); + assert(value); + + switch (opt) { + /* Common ones */ + case IPSET_SETNAME: + ipset_strlcpy(data->setname, value, IPSET_MAXNAMELEN); + break; + case IPSET_OPT_TYPE: + data->type = value; + break; + case IPSET_OPT_FAMILY: + data->family = *(const uint8_t *) value; + D("family set to %u", data->family); + break; + /* CADT options */ + case IPSET_OPT_IP: + if (!(data->family == AF_INET || data->family == AF_INET6)) + return -1; + copy_addr(data->family, &data->ip, value); + break; + case IPSET_OPT_IP_TO: + if (!(data->family == AF_INET || data->family == AF_INET6)) + return -1; + copy_addr(data->family, &data->ip_to, value); + break; + case IPSET_OPT_CIDR: + data->cidr = *(const uint8_t *) value; + break; + case IPSET_OPT_PORT: + data->port = *(const uint16_t *) value; + break; + case IPSET_OPT_PORT_TO: + data->port_to = *(const uint16_t *) value; + break; + case IPSET_OPT_TIMEOUT: + data->timeout = *(const uint32_t *) value; + break; + /* Create-specific options */ + case IPSET_OPT_GC: + data->create.gc = *(const uint32_t *) value; + break; + case IPSET_OPT_HASHSIZE: + data->create.hashsize = *(const uint32_t *) value; + break; + case IPSET_OPT_MAXELEM: + data->create.maxelem = *(const uint32_t *) value; + break; + case IPSET_OPT_NETMASK: + data->create.netmask = *(const uint8_t *) value; + break; + case IPSET_OPT_PROBES: + data->create.probes = *(const uint8_t *) value; + break; + case IPSET_OPT_RESIZE: + data->create.resize = *(const uint8_t *) value; + break; + case IPSET_OPT_SIZE: + data->create.size = *(const uint32_t *) value; + break; + /* Create-specific options, filled out by the kernel */ + case IPSET_OPT_ELEMENTS: + data->create.elements = *(const uint32_t *) value; + break; + case IPSET_OPT_REFERENCES: + data->create.references = *(const uint32_t *) value; + break; + case IPSET_OPT_MEMSIZE: + data->create.memsize = *(const uint32_t *) value; + break; + /* Create-specific options, type */ + case IPSET_OPT_TYPENAME: + ipset_strlcpy(data->create.typename, value, + IPSET_MAXNAMELEN); + break; + case IPSET_OPT_REVISION: + data->create.revision = *(const uint8_t *) value; + break; + case IPSET_OPT_REVISION_MIN: + data->create.revision_min = *(const uint8_t *) value; + break; + /* ADT-specific options */ + case IPSET_OPT_ETHER: + memcpy(data->adt.ether, value, ETH_ALEN); + break; + case IPSET_OPT_NAME: + ipset_strlcpy(data->adt.name, value, IPSET_MAXNAMELEN); + break; + case IPSET_OPT_NAMEREF: + ipset_strlcpy(data->adt.nameref, value, IPSET_MAXNAMELEN); + break; + case IPSET_OPT_IP2: + if (!(data->family == AF_INET || data->family == AF_INET6)) + return -1; + copy_addr(data->family, &data->adt.ip2, value); + break; + case IPSET_OPT_CIDR2: + data->adt.cidr2 = *(const uint8_t *) value; + break; + case IPSET_OPT_PROTO: + data->adt.proto = *(const uint8_t *) value; + break; + /* Swap/rename */ + case IPSET_OPT_SETNAME2: + ipset_strlcpy(data->setname2, value, IPSET_MAXNAMELEN); + break; + /* flags */ + case IPSET_OPT_EXIST: + flag_type_attr(data, opt, IPSET_FLAG_EXIST); + break; + case IPSET_OPT_BEFORE: + cadt_flag_type_attr(data, opt, IPSET_FLAG_BEFORE); + break; + case IPSET_OPT_FLAGS: + data->flags = *(const uint32_t *)value; + break; + case IPSET_OPT_CADT_FLAGS: + data->cadt_flags = *(const uint32_t *)value; + break; + default: + return -1; + }; + + ipset_data_flags_set(data, IPSET_FLAG(opt)); + return 0; +} + +/** + * ipset_data_get - get data from the data blob + * @data: data blob + * @opt: option kind of the requested data + * + * Returns the pointer to the requested kind of data from the data blob + * if it is set. If the option kind is not set or is an unkown type, + * NULL is returned. + */ +const void * +ipset_data_get(const struct ipset_data *data, enum ipset_opt opt) +{ + assert(data); + assert(opt != IPSET_OPT_NONE); + + if (!(opt == IPSET_OPT_TYPENAME || ipset_data_test(data, opt))) + return NULL; + + switch (opt) { + /* Common ones */ + case IPSET_SETNAME: + return data->setname; + case IPSET_OPT_TYPE: + return data->type; + case IPSET_OPT_TYPENAME: + if (ipset_data_test(data, IPSET_OPT_TYPE)) + return data->type->name; + else if (ipset_data_test(data, IPSET_OPT_TYPENAME)) + return data->create.typename; + return NULL; + case IPSET_OPT_FAMILY: + return &data->family; + /* CADT options */ + case IPSET_OPT_IP: + return &data->ip; + case IPSET_OPT_IP_TO: + return &data->ip_to; + case IPSET_OPT_CIDR: + return &data->cidr; + case IPSET_OPT_PORT: + return &data->port; + case IPSET_OPT_PORT_TO: + return &data->port_to; + case IPSET_OPT_TIMEOUT: + return &data->timeout; + /* Create-specific options */ + case IPSET_OPT_GC: + return &data->create.gc; + case IPSET_OPT_HASHSIZE: + return &data->create.hashsize; + case IPSET_OPT_MAXELEM: + return &data->create.maxelem; + case IPSET_OPT_NETMASK: + return &data->create.netmask; + case IPSET_OPT_PROBES: + return &data->create.probes; + case IPSET_OPT_RESIZE: + return &data->create.resize; + case IPSET_OPT_SIZE: + return &data->create.size; + /* Create-specific options, filled out by the kernel */ + case IPSET_OPT_ELEMENTS: + return &data->create.elements; + case IPSET_OPT_REFERENCES: + return &data->create.references; + case IPSET_OPT_MEMSIZE: + return &data->create.memsize; + /* Create-specific options, TYPE */ + case IPSET_OPT_REVISION: + return &data->create.revision; + case IPSET_OPT_REVISION_MIN: + return &data->create.revision_min; + /* ADT-specific options */ + case IPSET_OPT_ETHER: + return data->adt.ether; + case IPSET_OPT_NAME: + return data->adt.name; + case IPSET_OPT_NAMEREF: + return data->adt.nameref; + case IPSET_OPT_IP2: + return &data->adt.ip2; + case IPSET_OPT_CIDR2: + return &data->adt.cidr2; + case IPSET_OPT_PROTO: + return &data->adt.proto; + /* Swap/rename */ + case IPSET_OPT_SETNAME2: + return data->setname2; + /* flags */ + case IPSET_OPT_FLAGS: + case IPSET_OPT_EXIST: + return &data->flags; + case IPSET_OPT_CADT_FLAGS: + case IPSET_OPT_BEFORE: + return &data->cadt_flags; + default: + return NULL; + } +} + +/** + * ipset_data_sizeof - calculates the size of the data type + * @opt: option kind of the data + * @family: INET family + * + * Returns the size required to store the given data type. + */ +size_t +ipset_data_sizeof(enum ipset_opt opt, uint8_t family) +{ + assert(opt != IPSET_OPT_NONE); + + switch (opt) { + case IPSET_OPT_IP: + case IPSET_OPT_IP_TO: + case IPSET_OPT_IP2: + return family == AF_INET ? sizeof(uint32_t) + : sizeof(struct in6_addr); + case IPSET_OPT_PORT: + case IPSET_OPT_PORT_TO: + return sizeof(uint16_t); + case IPSET_SETNAME: + case IPSET_OPT_NAME: + case IPSET_OPT_NAMEREF: + return IPSET_MAXNAMELEN; + case IPSET_OPT_TIMEOUT: + case IPSET_OPT_GC: + case IPSET_OPT_HASHSIZE: + case IPSET_OPT_MAXELEM: + case IPSET_OPT_SIZE: + case IPSET_OPT_ELEMENTS: + case IPSET_OPT_REFERENCES: + case IPSET_OPT_MEMSIZE: + return sizeof(uint32_t); + case IPSET_OPT_CIDR: + case IPSET_OPT_CIDR2: + case IPSET_OPT_NETMASK: + case IPSET_OPT_PROBES: + case IPSET_OPT_RESIZE: + case IPSET_OPT_PROTO: + return sizeof(uint8_t); + case IPSET_OPT_ETHER: + return ETH_ALEN; + /* Flags counted once */ + case IPSET_OPT_BEFORE: + return sizeof(uint32_t); + default: + return 0; + }; +} + +/** + * ipset_setname - return the name of the set from the data blob + * @data: data blob + * + * Return the name of the set from the data blob or NULL if the + * name not set yet. + */ +const char * +ipset_data_setname(const struct ipset_data *data) +{ + assert(data); + return ipset_data_test(data, IPSET_SETNAME) ? data->setname : NULL; +} + +/** + * ipset_family - return the INET family of the set from the data blob + * @data: data blob + * + * Return the INET family supported by the set from the data blob. + * If the family is not set yet, AF_UNSPEC is returned. + */ +uint8_t +ipset_data_family(const struct ipset_data *data) +{ + assert(data); + return ipset_data_test(data, IPSET_OPT_FAMILY) + ? data->family : AF_UNSPEC; +} + +/** + * ipset_data_cidr - return the value of IPSET_OPT_CIDR + * @data: data blob + * + * Return the value of IPSET_OPT_CIDR stored in the data blob. + * If it is not set, then the returned value corresponds to + * the default one according to the family type or zero. + */ +uint8_t +ipset_data_cidr(const struct ipset_data *data) +{ + assert(data); + return ipset_data_test(data, IPSET_OPT_CIDR) ? data->cidr : + data->family == AF_INET ? 32 : + data->family == AF_INET6 ? 128 : 0; +} + +/** + * ipset_flags - return which fields are set in the data blob + * @data: data blob + * + * Returns the value of the bit field which elements are set. + */ +uint64_t +ipset_data_flags(const struct ipset_data *data) +{ + assert(data); + return data->bits; +} + +/** + * ipset_data_reset - reset the data blob to unset + * @data: data blob + * + * Resets the data blob to the unset state for every field. + */ +void +ipset_data_reset(struct ipset_data *data) +{ + assert(data); + memset(data, 0, sizeof(*data)); +} + +/** + * ipset_data_init - create a new data blob + * + * Return the new data blob initialized to empty. In case of + * an error, NULL is retured. + */ +struct ipset_data * +ipset_data_init(void) +{ + return calloc(1, sizeof(struct ipset_data)); +} + +/** + * ipset_data_fini - release a data blob created by ipset_data_init + * + * Release the data blob created by ipset_data_init previously. + */ +void +ipset_data_fini(struct ipset_data *data) +{ + assert(data); + free(data); +} diff --git a/extensions/ipset-5/libipset/icmp.c b/extensions/ipset-5/libipset/icmp.c new file mode 100644 index 0000000..c749272 --- /dev/null +++ b/extensions/ipset-5/libipset/icmp.c @@ -0,0 +1,79 @@ +/* Copyright 2007-2010 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 /* STRNEQ */ +#include /* prototypes */ + +struct icmp_names { + const char *name; + uint8_t type, code; +}; + +static const struct icmp_names icmp_typecodes[] = { + { "echo-reply", 0, 0 }, + { "pong", 0, 0 }, + { "network-unreachable", 3, 0 }, + { "host-unreachable", 3, 1 }, + { "protocol-unreachable", 3, 2 }, + { "port-unreachable", 3, 3 }, + { "fragmentation-needed", 3, 4 }, + { "source-route-failed", 3, 5 }, + { "network-unknown", 3, 6 }, + { "host-unknown", 3, 7 }, + { "network-prohibited", 3, 9 }, + { "host-prohibited", 3, 10 }, + { "TOS-network-unreachable", 3, 11 }, + { "TOS-host-unreachable", 3, 12 }, + { "communication-prohibited", 3, 13 }, + { "host-precedence-violation", 3, 14 }, + { "precedence-cutoff", 3, 15 }, + { "source-quench", 4, 0 }, + { "network-redirect", 5, 0 }, + { "host-redirect", 5, 1 }, + { "TOS-network-redirect", 5, 2 }, + { "TOS-host-redirect", 5, 3 }, + { "echo-request", 8, 0 }, + { "ping", 8, 0 }, + { "router-advertisement", 9, 0 }, + { "router-solicitation", 10, 0 }, + { "ttl-zero-during-transit", 11, 0 }, + { "ttl-zero-during-reassembly", 11, 1 }, + { "ip-header-bad", 12, 0 }, + { "required-option-missing", 12, 1 }, + { "timestamp-request", 13, 0 }, + { "timestamp-reply", 14, 0 }, + { "address-mask-request", 17, 0 }, + { "address-mask-reply", 18, 0 }, +}; + +const char * id_to_icmp(uint8_t id) +{ + return id < ARRAY_SIZE(icmp_typecodes) ? icmp_typecodes[id].name : NULL; +} + +const char * icmp_to_name(uint8_t type, uint8_t code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(icmp_typecodes); i++) + if (icmp_typecodes[i].type == type && icmp_typecodes[i].code == code) + return icmp_typecodes[i].name; + + return NULL; +} + +int name_to_icmp(const char *str, uint16_t *typecode) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(icmp_typecodes); i++) + if (STRNCASEQ(icmp_typecodes[i].name, str, strlen(str))) { + *typecode = (icmp_typecodes[i].type << 8) | icmp_typecodes[i].code; + return 0; + } + + return -1; +} diff --git a/extensions/ipset-5/libipset/icmpv6.c b/extensions/ipset-5/libipset/icmpv6.c new file mode 100644 index 0000000..5ba93ca --- /dev/null +++ b/extensions/ipset-5/libipset/icmpv6.c @@ -0,0 +1,66 @@ +/* Copyright 2007-2010 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 /* STRNEQ */ +#include /* prototypes */ + +struct icmpv6_names { + const char *name; + uint8_t type, code; +}; + +static const struct icmpv6_names icmpv6_typecodes[] = { + { "no-route", 1, 0 }, + { "communication-prohibited", 1, 1 }, + { "address-unreachable", 1, 3 }, + { "port-unreachable", 1, 4 }, + { "packet-too-big", 2, 0 }, + { "ttl-zero-during-transit", 3, 0 }, + { "ttl-zero-during-reassembly", 3, 1 }, + { "bad-header", 4, 0 }, + { "unknown-header-type", 4, 1 }, + { "unknown-option", 4, 2 }, + { "echo-request", 128, 0 }, + { "ping", 128, 0 }, + { "echo-reply", 129, 0 }, + { "pong", 129, 0 }, + { "router-solicitation", 133, 0 }, + { "router-advertisement", 134, 0 }, + { "neighbour-solicitation", 135, 0 }, + { "neigbour-solicitation", 135, 0 }, + { "neighbour-advertisement", 136, 0 }, + { "neigbour-advertisement", 136, 0 }, + { "redirect", 137, 0 }, +}; + +const char * id_to_icmpv6(uint8_t id) +{ + return id < ARRAY_SIZE(icmpv6_typecodes) ? icmpv6_typecodes[id].name : NULL; +} + +const char * icmpv6_to_name(uint8_t type, uint8_t code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(icmpv6_typecodes); i++) + if (icmpv6_typecodes[i].type == type && icmpv6_typecodes[i].code == code) + return icmpv6_typecodes[i].name; + + return NULL; +} + +int name_to_icmpv6(const char *str, uint16_t *typecode) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(icmpv6_typecodes); i++) + if (STRNCASEQ(icmpv6_typecodes[i].name, str, strlen(str))) { + *typecode = (icmpv6_typecodes[i].type << 8) | icmpv6_typecodes[i].code; + return 0; + } + + return -1; +} diff --git a/extensions/ipset-5/libipset/mnl.c b/extensions/ipset-5/libipset/mnl.c new file mode 100644 index 0000000..9d1b630 --- /dev/null +++ b/extensions/ipset-5/libipset/mnl.c @@ -0,0 +1,258 @@ +/* Copyright 2007-2010 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 /* assert */ +#include /* errno */ +#include +#include /* calloc, free */ +#include /* time */ +#include /* hto* */ + +#include /* enum ipset_cmd */ +#include /* D() */ +#include /* ipset_session_handle */ +#include /* IPSET_ENV_EXIST */ +#include /* UNUSED */ +#include /* prototypes */ + +#include +#include +#include + +#ifndef NFNL_SUBSYS_IPSET +#define NFNL_SUBSYS_IPSET 6 +#endif + +/* Internal data structure for the kernel-userspace communication parameters */ +struct ipset_handle { + struct mnl_socket *h; /* the mnl socket */ + unsigned int seq; /* netlink message sequence number */ + unsigned int portid; /* the socket port identifier */ + mnl_cb_t *cb_ctl; /* control block callbacks */ + void *data; /* data pointer */ + unsigned int genl_id; /* genetlink ID of ip_set */ +}; + +/* Netlink flags of the commands */ +static const uint16_t cmdflags[] = { + [IPSET_CMD_CREATE-1] = NLM_F_REQUEST|NLM_F_ACK|NLM_F_CREATE|NLM_F_EXCL, + [IPSET_CMD_DESTROY-1] = NLM_F_REQUEST|NLM_F_ACK, + [IPSET_CMD_FLUSH-1] = NLM_F_REQUEST|NLM_F_ACK, + [IPSET_CMD_RENAME-1] = NLM_F_REQUEST|NLM_F_ACK, + [IPSET_CMD_SWAP-1] = NLM_F_REQUEST|NLM_F_ACK, + [IPSET_CMD_LIST-1] = NLM_F_REQUEST, + [IPSET_CMD_SAVE-1] = NLM_F_REQUEST, + [IPSET_CMD_ADD-1] = NLM_F_REQUEST|NLM_F_ACK|NLM_F_EXCL, + [IPSET_CMD_DEL-1] = NLM_F_REQUEST|NLM_F_ACK|NLM_F_EXCL, + [IPSET_CMD_TEST-1] = NLM_F_REQUEST|NLM_F_ACK, + [IPSET_CMD_HEADER-1] = NLM_F_REQUEST, + [IPSET_CMD_TYPE-1] = NLM_F_REQUEST, + [IPSET_CMD_PROTOCOL-1] = NLM_F_REQUEST, +}; + +/** + * ipset_get_nlmsg_type - get ipset netlink message type + * @nlh: pointer to the netlink message header + * + * Returns the ipset netlink message type, i.e. the ipset command. + */ +int +ipset_get_nlmsg_type(const struct nlmsghdr *nlh) +{ + const struct genlmsghdr *ghdr = mnl_nlmsg_get_payload(nlh); + + return ghdr->cmd; +} + +static void +ipset_mnl_fill_hdr(struct ipset_handle *handle, enum ipset_cmd cmd, + void *buffer, size_t len UNUSED, uint8_t envflags) +{ + struct nlmsghdr *nlh; + struct genlmsghdr *ghdr; + + assert(handle); + assert(buffer); + assert(cmd > IPSET_CMD_NONE && cmd < IPSET_MSG_MAX); + + nlh = mnl_nlmsg_put_header(buffer); + nlh->nlmsg_type = handle->genl_id; + nlh->nlmsg_flags = NLM_F_REQUEST; + if (cmdflags[cmd-1] & NLM_F_ACK) + nlh->nlmsg_flags |= NLM_F_ACK; + nlh->nlmsg_seq = handle->seq = time(NULL); + + ghdr = mnl_nlmsg_put_extra_header(nlh, sizeof(struct genlmsghdr)); + ghdr->cmd = cmd; + /* (ge)netlink is dumb, NLM_F_CREATE and NLM_F_DUMP overlap, get misinterpreted. */ + ghdr->reserved = cmdflags[cmd-1]; + if (envflags & IPSET_ENV_EXIST) + ghdr->reserved &= ~NLM_F_EXCL; +} + +static int +ipset_mnl_query(struct ipset_handle *handle, void *buffer, size_t len) +{ + struct nlmsghdr *nlh = buffer; + int ret; + + assert(handle); + assert(buffer); + + if (mnl_socket_sendto(handle->h, nlh, nlh->nlmsg_len) < 0) + return -ECOMM; + + D("message sent"); + ret = mnl_socket_recvfrom(handle->h, buffer, len); + D("message received, ret: %d", ret); + while (ret > 0) { + ret = mnl_cb_run2(buffer, ret, + handle->seq, handle->portid, + handle->cb_ctl[NLMSG_MIN_TYPE], + handle->data, + handle->cb_ctl, NLMSG_MIN_TYPE); + D("nfln_cb_run2, ret: %d", ret); + if (ret <= 0) + break; + ret = mnl_socket_recvfrom(handle->h, buffer, len); + D("message received, ret: %d", ret); + } + return ret > 0 ? 0 : ret; +} + +static int ipset_mnl_getid_acb(const struct nlattr *attr, void *datap) +{ + const struct nlattr **tb = datap; + uint16_t type = mnl_attr_get_type(attr); + + if (mnl_attr_type_valid(attr, CTRL_ATTR_MAX) < 0) + return MNL_CB_OK; + tb[type] = attr; + return MNL_CB_OK; +} + +static int ipset_mnl_getid_cb(const struct nlmsghdr *nlhdr, void *datap) +{ + struct ipset_handle *h = datap; + const struct nlattr *tb[CTRL_ATTR_MAX+1] = {0}; + const struct genlmsghdr *ghdr = mnl_nlmsg_get_payload(nlhdr); + int ret; + + ret = mnl_attr_parse(nlhdr, sizeof(*ghdr), ipset_mnl_getid_acb, tb); + if (ret != MNL_CB_OK) + return ret; + if (tb[CTRL_ATTR_FAMILY_ID] != NULL) + h->genl_id = mnl_attr_get_u16(tb[CTRL_ATTR_FAMILY_ID]); + return MNL_CB_OK; +} + +/** + * Look up the GENL identifier for the ip_set subsystem, and store it in + * @h->genl_id. On success, 0 is returned, otherwise error encoded as + * negative number. + */ +static int ipset_mnl_getid(struct ipset_handle *h, bool modprobe) +{ + size_t buf_size = 8192; //MNL_SOCKET_BUFFER_SIZE; + struct nlmsghdr *nlhdr; + struct genlmsghdr *ghdr; + char *buf; + int ret = -ENOENT; + + h->genl_id = 0; + + if (modprobe) { + /* genetlink has no autoloading like nfnetlink... */ + system("/sbin/modprobe -q ip_set"); + } + + buf = malloc(buf_size); + if (buf == NULL) + return -errno; + + nlhdr = mnl_nlmsg_put_header(buf); + nlhdr->nlmsg_type = GENL_ID_CTRL; + nlhdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + ghdr = mnl_nlmsg_put_extra_header(nlhdr, sizeof(struct genlmsghdr)); + ghdr->cmd = CTRL_CMD_GETFAMILY; + ghdr->version = 2; + if (!mnl_attr_put_strz_check(nlhdr, buf_size, + CTRL_ATTR_FAMILY_NAME, "ip_set")) + goto out; + + ret = mnl_socket_sendto(h->h, buf, nlhdr->nlmsg_len); + if (ret < 0) + goto out; + ret = mnl_socket_recvfrom(h->h, buf, buf_size); + while (ret > 0) { + ret = mnl_cb_run(buf, ret, 0, 0, ipset_mnl_getid_cb, h); + if (ret <= 0) + break; + ret = mnl_socket_recvfrom(h->h, buf, buf_size); + } + if (h->genl_id == 0 && !modprobe) + /* Redo, with modprobe this time. */ + ret = ipset_mnl_getid(h, true); + if (h->genl_id > 0) + ret = 0; + out: + free(buf); + return ret; +} + +static struct ipset_handle * +ipset_mnl_init(mnl_cb_t *cb_ctl, void *data) +{ + struct ipset_handle *handle; + + assert(cb_ctl); + assert(data); + + handle = calloc(1, sizeof(*handle)); + if (!handle) + return NULL; + + handle->h = mnl_socket_open(NETLINK_GENERIC); + if (!handle->h) + goto free_handle; + + if (mnl_socket_bind(handle->h, 0, MNL_SOCKET_AUTOPID) < 0) + goto close_nl; + + handle->portid = mnl_socket_get_portid(handle->h); + handle->cb_ctl = cb_ctl; + handle->data = data; + + if (ipset_mnl_getid(handle, false) < 0) + goto close_nl; + return handle; + +close_nl: + mnl_socket_close(handle->h); +free_handle: + free(handle); + + return NULL; +} + +static int +ipset_mnl_fini(struct ipset_handle *handle) +{ + assert(handle); + + if (handle->h) + mnl_socket_close(handle->h); + + free(handle); + return 0; +} + +const struct ipset_transport ipset_mnl_transport = { + .init = ipset_mnl_init, + .fini = ipset_mnl_fini, + .fill_hdr = ipset_mnl_fill_hdr, + .query = ipset_mnl_query, +}; diff --git a/extensions/ipset-5/libipset/parse.c b/extensions/ipset-5/libipset/parse.c new file mode 100644 index 0000000..c4d9c75 --- /dev/null +++ b/extensions/ipset-5/libipset/parse.c @@ -0,0 +1,1526 @@ +/* Copyright 2007-2010 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 /* assert */ +#include /* errno */ +#include /* ULLONG_MAX */ +#include /* getservbyname, getaddrinfo */ +#include /* strtoull, etc. */ +#include /* getaddrinfo */ +#include /* getaddrinfo, AF_ */ +#include /* ETH_ALEN */ +#include /* IPPROTO_ */ + +#include /* D() */ +#include /* IPSET_OPT_* */ +#include /* name_to_icmp */ +#include /* name_to_icmpv6 */ +#include /* prefixlen_netmask_map */ +#include /* ipset_err */ +#include /* ipset_type_get */ +#include /* string utilities */ +#include /* prototypes */ + +#ifndef ULLONG_MAX +#define ULLONG_MAX 18446744073709551615ULL +#endif + +/* Parse input data */ + +#define cidr_separator(str) ipset_strchr(str, IPSET_CIDR_SEPARATOR) +#define range_separator(str) ipset_strchr(str, IPSET_RANGE_SEPARATOR) +#define elem_separator(str) ipset_strchr(str, IPSET_ELEM_SEPARATOR) +#define name_separator(str) ipset_strchr(str, IPSET_NAME_SEPARATOR) +#define proto_separator(str) ipset_strchr(str, IPSET_PROTO_SEPARATOR) + +#define syntax_err(fmt, args...) \ + ipset_err(session, "Syntax error: " fmt , ## args) + +static char * +ipset_strchr(const char *str, const char *sep) +{ + char *match; + + assert(str); + assert(sep); + + for (; *sep != '\0'; sep++) + if ((match = strchr(str, sep[0])) != NULL + && str[0] != sep[0] + && str[strlen(str)-1] != sep[0]) + return match; + + return NULL; +} + +/* + * Parser functions, shamelessly taken from iptables.c, ip6tables.c + * and parser.c from libnetfilter_conntrack. + */ + +/* + * Parse numbers + */ +static int +string_to_number_ll(struct ipset_session *session, + const char *str, + unsigned long long min, + unsigned long long max, + unsigned long long *ret) +{ + unsigned long long number = 0; + char *end; + + /* Handle hex, octal, etc. */ + errno = 0; + number = strtoull(str, &end, 0); + if (*end == '\0' && end != str && errno != ERANGE) { + /* we parsed a number, let's see if we want this */ + if (min <= number && (!max || number <= max)) { + *ret = number; + return 0; + } else + errno = ERANGE; + } + if (errno == ERANGE && max) + return syntax_err("'%s' is out of range %llu-%llu", + str, min, max); + else if (errno == ERANGE) + return syntax_err("'%s' is out of range %llu-%llu", + str, min, ULLONG_MAX); + else + return syntax_err("'%s' is invalid as number", str); +} + +static int +string_to_u8(struct ipset_session *session, + const char *str, uint8_t *ret) +{ + int err; + unsigned long long num = 0; + + err = string_to_number_ll(session, str, 0, 255, &num); + *ret = num; + + return err; +} + +static int +string_to_cidr(struct ipset_session *session, + const char *str, uint8_t min, uint8_t max, uint8_t *ret) +{ + int err = string_to_u8(session, str, ret); + + if (!err && (*ret < min || *ret > max)) + return syntax_err("'%s' is out of range %u-%u", + str, min, max); + + return err; +} + +static int +string_to_u16(struct ipset_session *session, + const char *str, uint16_t *ret) +{ + int err; + unsigned long long num = 0; + + err = string_to_number_ll(session, str, 0, USHRT_MAX, &num); + *ret = num; + + return err; +} + +static int +string_to_u32(struct ipset_session *session, + const char *str, uint32_t *ret) +{ + int err; + unsigned long long num = 0; + + err = string_to_number_ll(session, str, 0, UINT_MAX, &num); + *ret = num; + + return err; +} + +/** + * ipset_parse_ether - parse ethernet address + * @session: session structure + * @opt: option kind of the data + * @str: string to parse + * + * Parse string as an ethernet address. The parsed ethernet + * address is stored in the data blob of the session. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_parse_ether(struct ipset_session *session, + enum ipset_opt opt, const char *str) +{ + unsigned int i = 0; + unsigned char ether[ETH_ALEN]; + + assert(session); + assert(opt == IPSET_OPT_ETHER); + assert(str); + + if (strlen(str) != ETH_ALEN * 3 - 1) + goto error; + + for (i = 0; i < ETH_ALEN; i++) { + long number; + char *end; + + number = strtol(str + i * 3, &end, 16); + + if (end == str + i * 3 + 2 + && (*end == ':' || *end == '\0') + && number >= 0 && number <= 255) + ether[i] = number; + else + goto error; + } + return ipset_session_data_set(session, opt, ether); + +error: + return syntax_err("cannot parse '%s' as ethernet address", str); +} + +/* + * Parse TCP service names or port numbers + */ +static int +parse_portname(struct ipset_session *session, const char *str, + uint16_t *port, const char *proto) +{ + struct servent *service; + + if ((service = getservbyname(str, proto)) != NULL) { + *port = ntohs((uint16_t) service->s_port); + return 0; + } + + return syntax_err("cannot parse '%s' as a %s port", str, proto); +} + +/** + * ipset_parse_single_port - parse a single port number or name + * @session: session structure + * @opt: option kind of the data + * @str: string to parse + * @proto: protocol + * + * Parse string as a single port number or name. The parsed port + * number is stored in the data blob of the session. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_parse_port(struct ipset_session *session, + enum ipset_opt opt, const char *str, + const char *proto) +{ + uint16_t port; + int err; + + assert(session); + assert(opt == IPSET_OPT_PORT || opt == IPSET_OPT_PORT_TO); + assert(str); + + if ((err = string_to_u16(session, str, &port)) == 0 + || (err = parse_portname(session, str, &port, proto)) == 0) + err = ipset_session_data_set(session, opt, &port); + + if (!err) + /* No error, so reset false error messages! */ + ipset_session_report_reset(session); + + return err; +} + +/** + * ipset_parse_tcpudp_port - parse TCP/UDP port name, number, or range of them + * @session: session structure + * @opt: option kind of the data + * @str: string to parse + * @proto: TCP|UDP + * + * Parse string as a TCP/UDP port name or number or range of them + * separated by a dash. The parsed port numbers are stored + * in the data blob of the session. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_parse_tcpudp_port(struct ipset_session *session, + enum ipset_opt opt, const char *str, const char *proto) +{ + char *a, *saved, *tmp; + int err = 0; + + assert(session); + assert(opt == IPSET_OPT_PORT); + assert(str); + + saved = tmp = strdup(str); + if (tmp == NULL) + return ipset_err(session, + "Cannot allocate memory to duplicate %s.", + str); + + a = range_separator(tmp); + if (a != NULL) { + /* port-port */ + *a++ = '\0'; + err = ipset_parse_port(session, IPSET_OPT_PORT_TO, a, proto); + if (err) + goto error; + } + err = ipset_parse_port(session, opt, tmp, proto); + +error: + free(saved); + return err; +} + +/** + * ipset_parse_tcp_port - parse TCP port name, number, or range of them + * @session: session structure + * @opt: option kind of the data + * @str: string to parse + * + * Parse string as a TCP port name or number or range of them + * separated by a dash. The parsed port numbers are stored + * in the data blob of the session. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_parse_tcp_port(struct ipset_session *session, + enum ipset_opt opt, const char *str) +{ + return ipset_parse_tcpudp_port(session, opt, str, "TCP"); +} + +/** + * ipset_parse_single_tcp_port - parse TCP port name or number + * @session: session structure + * @opt: option kind of the data + * @str: string to parse + * + * Parse string as a single TCP port name or number. + * The parsed port number is stored + * in the data blob of the session. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_parse_single_tcp_port(struct ipset_session *session, + enum ipset_opt opt, const char *str) +{ + assert(session); + assert(opt == IPSET_OPT_PORT || opt == IPSET_OPT_PORT_TO); + assert(str); + + return ipset_parse_port(session, opt, str, "TCP"); +} + +/** + * ipset_parse_proto - parse protocol name + * @session: session structure + * @opt: option kind of the data + * @str: string to parse + * + * Parse string as a protocol name. + * The parsed protocol is stored in the data blob of the session. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_parse_proto(struct ipset_session *session, + enum ipset_opt opt, const char *str) +{ + const struct protoent *protoent; + uint8_t proto = 0; + + assert(session); + assert(opt == IPSET_OPT_PROTO); + assert(str); + + protoent = getprotobyname(strcasecmp(str, "icmpv6") == 0 + ? "ipv6-icmp" : str); + if (protoent == NULL) + return syntax_err("cannot parse '%s' " + "as a protocol name", str); + proto = protoent->p_proto; + if (!proto) + return syntax_err("Unsupported protocol '%s'", str); + + return ipset_session_data_set(session, opt, &proto); +} + +/* Parse ICMP and ICMPv6 type/code */ +static int +parse_icmp_typecode(struct ipset_session *session, + enum ipset_opt opt, const char *str, + const char *family) +{ + uint16_t typecode; + uint8_t type, code; + char *a, *saved, *tmp; + int err; + + saved = tmp = strdup(str); + if (tmp == NULL) + return ipset_err(session, + "Cannot allocate memory to duplicate %s.", + str); + a = cidr_separator(tmp); + if (a == NULL) { + free(saved); + return ipset_err(session, + "Cannot parse %s as an %s type/code.", str, family); + } + *a++ = '\0'; + if ((err = string_to_u8(session, a, &type)) != 0 + || (err = string_to_u8(session, tmp, &code)) != 0) + goto error; + + typecode = (type << 8) | code; + err = ipset_session_data_set(session, opt, &typecode); + +error: + free(saved); + return err; +} + +/** + * ipset_parse_icmp - parse an ICMP name or type/code + * @session: session structure + * @opt: option kind of the data + * @str: string to parse + * + * Parse string as an ICMP name or type/code numbers. + * The parsed ICMP type/code is stored in the data blob of the session. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_parse_icmp(struct ipset_session *session, + enum ipset_opt opt, const char *str) +{ + uint16_t typecode; + + assert(session); + assert(opt == IPSET_OPT_PORT); + assert(str); + + if (name_to_icmp(str, &typecode) < 0) + return parse_icmp_typecode(session, opt, str, "ICMP"); + + return ipset_session_data_set(session, opt, &typecode); +} + +/** + * ipset_parse_icmpv6 - parse an ICMPv6 name or type/code + * @session: session structure + * @opt: option kind of the data + * @str: string to parse + * + * Parse string as an ICMPv6 name or type/code numbers. + * The parsed ICMPv6 type/code is stored in the data blob of the session. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_parse_icmpv6(struct ipset_session *session, + enum ipset_opt opt, const char *str) +{ + uint16_t typecode; + + assert(session); + assert(opt == IPSET_OPT_PORT); + assert(str); + + if (name_to_icmpv6(str, &typecode) < 0) + return parse_icmp_typecode(session, opt, str, "ICMPv6"); + + return ipset_session_data_set(session, opt, &typecode); +} + +/** + * ipset_parse_proto_port - parse (optional) protocol and a single port + * @session: session structure + * @opt: option kind of the data + * @str: string to parse + * + * Parse string as a protocol and port, separated by a colon. + * The protocol part is optional. + * The parsed protocol and port numbers are stored in the data + * blob of the session. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_parse_proto_port(struct ipset_session *session, + enum ipset_opt opt, const char *str) +{ + struct ipset_data *data; + char *a, *saved, *tmp; + const char *proto; + uint8_t p = IPPROTO_TCP; + int err = 0; + + assert(session); + assert(opt == IPSET_OPT_PORT); + assert(str); + + data = ipset_session_data(session); + saved = tmp = strdup(str); + if (tmp == NULL) + return ipset_err(session, + "Cannot allocate memory to duplicate %s.", + str); + + a = proto_separator(tmp); + if (a != NULL) { + uint8_t family = ipset_data_family(data); + + /* proto:port */ + *a++ = '\0'; + err = ipset_parse_proto(session, IPSET_OPT_PROTO, tmp); + if (err) + goto error; + + p = *(const uint8_t *) ipset_data_get(data, IPSET_OPT_PROTO); + switch (p) { + case IPPROTO_TCP: + proto = tmp; + tmp = a; + goto parse_port; + case IPPROTO_UDP: + proto = tmp; + tmp = a; + goto parse_port; + case IPPROTO_ICMP: + if (family != AF_INET) { + syntax_err("Protocol ICMP can be used with family INET only"); + goto error; + } + err = ipset_parse_icmp(session, opt, a); + break; + case IPPROTO_ICMPV6: + if (family != AF_INET6) { + syntax_err("Protocol ICMPv6 can be used with family INET6 only"); + goto error; + } + err = ipset_parse_icmpv6(session, opt, a); + break; + default: + if (!STREQ(a, "0")) { + syntax_err("Protocol %s can be used with pseudo port value 0 only."); + goto error; + } + ipset_data_flags_set(data, IPSET_FLAG(opt)); + } + goto error; + } else { + proto = "TCP"; + err = ipset_data_set(data, IPSET_OPT_PROTO, &p); + if (err) + goto error; + } +parse_port: + err = ipset_parse_tcpudp_port(session, opt, tmp, proto); + +error: + free(saved); + return err; +} + +/** + * ipset_parse_family - parse INET|INET6 family names + * @session: session structure + * @opt: option kind of the data + * @str: string to parse + * + * Parse string as an INET|INET6 family name. + * The value is stored in the data blob of the session. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_parse_family(struct ipset_session *session, + enum ipset_opt opt, const char *str) +{ + struct ipset_data *data; + uint8_t family; + + assert(session); + assert(opt == IPSET_OPT_FAMILY); + assert(str); + + data = ipset_session_data(session); + if (ipset_data_flags_test(data, IPSET_FLAG(IPSET_OPT_FAMILY))) + syntax_err("protocol family may not be specified " + "multiple times"); + + if (STREQ(str, "inet") || STREQ(str, "ipv4") || STREQ(str, "-4")) + family = AF_INET; + else if (STREQ(str, "inet6") || STREQ(str, "ipv6") || STREQ(str, "-6")) + family = AF_INET6; + else if (STREQ(str, "any") || STREQ(str, "unspec")) + family = AF_UNSPEC; + else + return syntax_err("unknown INET family %s", str); + + return ipset_data_set(data, opt, &family); +} + +/* + * Parse IPv4/IPv6 addresses, networks and ranges. + * We resolve hostnames but just the first IP address is used. + */ + +static struct addrinfo * +call_getaddrinfo(struct ipset_session *session, const char *str, + uint8_t family) +{ + struct addrinfo hints; + struct addrinfo *res; + int err; + + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_CANONNAME; + hints.ai_family = family; + hints.ai_socktype = SOCK_RAW; + hints.ai_protocol = 0; + hints.ai_next = NULL; + + if ((err = getaddrinfo(str, NULL, &hints, &res)) != 0) { + syntax_err("cannot resolve '%s' to an %s address: %s", + str, family == AF_INET6 ? "IPv6" : "IPv4", + gai_strerror(err)); + return NULL; + } else + return res; +} + +static int +get_addrinfo(struct ipset_session *session, + enum ipset_opt opt, + const char *str, + struct addrinfo **info, + uint8_t family) +{ + struct addrinfo *i; + size_t addrlen = family == AF_INET ? sizeof(struct sockaddr_in) + : sizeof(struct sockaddr_in6); + int found, err = 0; + + if ((*info = call_getaddrinfo(session, str, family)) == NULL) { + syntax_err("cannot parse %s: resolving to %s address failed", + str, family == AF_INET ? "IPv4" : "IPv6"); + return EINVAL; + } + + for (i = *info, found = 0; i != NULL; i = i->ai_next) { + if (i->ai_family != family || i->ai_addrlen != addrlen) + continue; + if (found == 0) { + if (family == AF_INET) { + /* Workaround: direct cast increases required alignment on Sparc */ + const struct sockaddr_in *saddr = (void *)i->ai_addr; + err = ipset_session_data_set(session, opt, &saddr->sin_addr); + } else { + /* Workaround: direct cast increases required alignment on Sparc */ + const struct sockaddr_in6 *saddr = (void *)i->ai_addr; + err = ipset_session_data_set(session, opt, &saddr->sin6_addr); + } + } else if (found == 1) { + ipset_warn(session, + "%s resolves to multiple addresses: " + "using only the first one returned " + "by the resolver", + str); + } + found++; + } + if (found == 0) + return syntax_err("cannot parse %s: " + "%s address could not be resolved", + str, family == AF_INET ? "IPv4" : "IPv6"); + return err; +} + +static int +parse_ipaddr(struct ipset_session *session, + enum ipset_opt opt, const char *str, + uint8_t family) +{ + uint8_t m = family == AF_INET ? 32 : 128; + int aerr = EINVAL, err = 0, range = 0; + char *saved = strdup(str); + char *a, *tmp = saved; + struct addrinfo *info; + enum ipset_opt copt = opt == IPSET_OPT_IP ? IPSET_OPT_CIDR + : IPSET_OPT_CIDR2; + + if (tmp == NULL) + return ipset_err(session, + "Cannot allocate memory to duplicate %s.", + str); + if ((a = cidr_separator(tmp)) != NULL) { + /* IP/mask */ + *a++ = '\0'; + + if ((err = string_to_cidr(session, a, 0, m, &m)) != 0 + || (err = ipset_session_data_set(session, copt, &m)) != 0) + goto out; + } else if ((a = range_separator(tmp)) != NULL) { + /* IP-IP */ + *a++ = '\0'; + D("range %s", a); + range++; + } + if ((aerr = get_addrinfo(session, opt, tmp, &info, family)) != 0 + || !range) + goto out; + freeaddrinfo(info); + aerr = get_addrinfo(session, IPSET_OPT_IP_TO, a, &info, family); + +out: + if (aerr != EINVAL) + /* getaddrinfo not failed */ + freeaddrinfo(info); + else if (aerr) + err = -1; + free(saved); + return err; +} + +enum ipaddr_type { + IPADDR_ANY, + IPADDR_PLAIN, + IPADDR_NET, + IPADDR_RANGE, +}; + +static int +parse_ip(struct ipset_session *session, + enum ipset_opt opt, const char *str, enum ipaddr_type addrtype) +{ + struct ipset_data *data = ipset_session_data(session); + uint8_t family = ipset_data_family(data); + + if (family == AF_UNSPEC) { + family = AF_INET; + ipset_data_set(data, IPSET_OPT_FAMILY, &family); + } + + switch (addrtype) { + case IPADDR_PLAIN: + if (range_separator(str) || cidr_separator(str)) + return syntax_err("plain IP address must be supplied: %s", + str); + break; + case IPADDR_NET: + if (!cidr_separator(str) || range_separator(str)) + return syntax_err("IP/netblock must be supplied: %s", + str); + break; + case IPADDR_RANGE: + if (!range_separator(str) || cidr_separator(str)) + return syntax_err("IP-IP range must supplied: %s", + str); + break; + case IPADDR_ANY: + default: + break; + } + + return parse_ipaddr(session, opt, str, family); +} + +/** + * ipset_parse_ip - parse IPv4|IPv6 address, range or netblock + * @session: session structure + * @opt: option kind of the data + * @str: string to parse + * + * Parse string as an IPv4|IPv6 address or address range + * or netblock. Hostnames are resolved. If family is not set + * yet in the data blob, INET is assumed. + * The values are stored in the data blob of the session. + * + * FIXME: if the hostname resolves to multiple addresses, + * the first one is used only. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_parse_ip(struct ipset_session *session, + enum ipset_opt opt, const char *str) +{ + assert(session); + assert(opt == IPSET_OPT_IP || opt == IPSET_OPT_IP2); + assert(str); + + return parse_ip(session, opt, str, IPADDR_ANY); +} + +/** + * ipset_parse_single_ip - parse a single IPv4|IPv6 address + * @session: session structure + * @opt: option kind of the data + * @str: string to parse + * + * Parse string as an IPv4|IPv6 address or hostname. If family + * is not set yet in the data blob, INET is assumed. + * The value is stored in the data blob of the session. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_parse_single_ip(struct ipset_session *session, + enum ipset_opt opt, const char *str) +{ + assert(session); + assert(opt == IPSET_OPT_IP + || opt == IPSET_OPT_IP_TO + || opt == IPSET_OPT_IP2); + assert(str); + + return parse_ip(session, opt, str, IPADDR_PLAIN); +} + +/** + * ipset_parse_net - parse IPv4|IPv6 address/cidr + * @session: session structure + * @opt: option kind of the data + * @str: string to parse + * + * Parse string as an IPv4|IPv6 address/cidr pattern. If family + * is not set yet in the data blob, INET is assumed. + * The value is stored in the data blob of the session. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_parse_net(struct ipset_session *session, + enum ipset_opt opt, const char *str) +{ + assert(session); + assert(opt == IPSET_OPT_IP || opt == IPSET_OPT_IP2); + assert(str); + + return parse_ip(session, opt, str, IPADDR_NET); +} + +/** + * ipset_parse_range - parse IPv4|IPv6 ranges + * @session: session structure + * @opt: option kind of the data + * @str: string to parse + * + * Parse string as an IPv4|IPv6 range separated by a dash. If family + * is not set yet in the data blob, INET is assumed. + * The values are stored in the data blob of the session. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_parse_range(struct ipset_session *session, + enum ipset_opt opt, const char *str) +{ + assert(session); + assert(opt == IPSET_OPT_IP || opt == IPSET_OPT_IP2); + assert(str); + + return parse_ip(session, IPSET_OPT_IP, str, IPADDR_RANGE); +} + +/** + * ipset_parse_netrange - parse IPv4|IPv6 address/cidr or range + * @session: session structure + * @opt: option kind of the data + * @str: string to parse + * + * Parse string as an IPv4|IPv6 address/cidr pattern or a range + * of addresses separated by a dash. If family is not set yet in + * the data blob, INET is assumed. + * The value is stored in the data blob of the session. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_parse_netrange(struct ipset_session *session, + enum ipset_opt opt, const char *str) +{ + assert(session); + assert(opt == IPSET_OPT_IP || opt == IPSET_OPT_IP2); + assert(str); + + if (!(range_separator(str) || cidr_separator(str))) + return syntax_err("IP/cidr or IP-IP range must be specified: %s", + str); + return parse_ip(session, opt, str, IPADDR_ANY); +} + +/** + * ipset_parse_iprange - parse IPv4|IPv6 address or range + * @session: session structure + * @opt: option kind of the data + * @str: string to parse + * + * Parse string as an IPv4|IPv6 address pattern or a range + * of addresses separated by a dash. If family is not set yet in + * the data blob, INET is assumed. + * The value is stored in the data blob of the session. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_parse_iprange(struct ipset_session *session, + enum ipset_opt opt, const char *str) +{ + assert(session); + assert(opt == IPSET_OPT_IP || opt == IPSET_OPT_IP2); + assert(str); + + if (cidr_separator(str)) + return syntax_err("IP address or IP-IP range must be specified: %s", + str); + return parse_ip(session, opt, str, IPADDR_ANY); +} + +/** + * ipset_parse_ipnet - parse IPv4|IPv6 address or address/cidr pattern + * @session: session structure + * @opt: option kind of the data + * @str: string to parse + * + * Parse string as an IPv4|IPv6 address or address/cidr pattern. + * If family is not set yet in the data blob, INET is assumed. + * The value is stored in the data blob of the session. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_parse_ipnet(struct ipset_session *session, + enum ipset_opt opt, const char *str) +{ + assert(session); + assert(opt == IPSET_OPT_IP || opt == IPSET_OPT_IP2); + assert(str); + + if (range_separator(str)) + return syntax_err("IP address or IP/cidr must be specified: %s", + str); + return parse_ip(session, opt, str, IPADDR_ANY); +} + +/** + * ipset_parse_ip4_single6 - parse IPv4 address, range or netblock or IPv6 address + * @session: session structure + * @opt: option kind of the data + * @str: string to parse + * + * Parse string as an IPv4 address or address range + * or netblock or and IPv6 address. Hostnames are resolved. If family + * is not set yet in the data blob, INET is assumed. + * The values are stored in the data blob of the session. + * + * FIXME: if the hostname resolves to multiple addresses, + * the first one is used only. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_parse_ip4_single6(struct ipset_session *session, + enum ipset_opt opt, const char *str) +{ + struct ipset_data *data; + uint8_t family; + + assert(session); + assert(opt == IPSET_OPT_IP || opt == IPSET_OPT_IP2); + assert(str); + + data = ipset_session_data(session); + family = ipset_data_family(data); + + if (family == AF_UNSPEC) { + family = AF_INET; + ipset_data_set(data, IPSET_OPT_FAMILY, &family); + } + + return family == AF_INET ? ipset_parse_ip(session, opt, str) + : ipset_parse_single_ip(session, opt, str); + +} + +/** + * ipset_parse_iptimeout - parse IPv4|IPv6 address and timeout + * @session: session structure + * @opt: option kind of the data + * @str: string to parse + * + * Parse string as an IPv4|IPv6 address and timeout parameter. + * If family is not set yet in the data blob, INET is assumed. + * The value is stored in the data blob of the session. + * + * Compatibility parser. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_parse_iptimeout(struct ipset_session *session, + enum ipset_opt opt, const char *str) +{ + char *tmp, *saved, *a; + int err; + + assert(session); + assert(opt == IPSET_OPT_IP); + assert(str); + + /* IP,timeout */ + if (ipset_data_flags_test(ipset_session_data(session), + IPSET_FLAG(IPSET_OPT_TIMEOUT))) + return syntax_err("mixed syntax, timeout already specified"); + + tmp = saved = strdup(str); + if (saved == NULL) + return ipset_err(session, + "Cannot allocate memory to duplicate %s.", + str); + + a = elem_separator(tmp); + if (a == NULL) { + free(saved); + return syntax_err("Missing separator from %s", str); + } + *a++ = '\0'; + err = parse_ip(session, opt, tmp, IPADDR_ANY); + if (!err) + err = ipset_parse_uint32(session, IPSET_OPT_TIMEOUT, a); + + free(saved); + return err; +} + +#define check_setname(str, saved) \ +do { \ + if (strlen(str) > IPSET_MAXNAMELEN - 1) { \ + if (saved != NULL) \ + free(saved); \ + return syntax_err("setname '%s' is longer than %u characters", \ + str, IPSET_MAXNAMELEN - 1); \ + } \ +} while (0) + + +/** + * ipset_parse_name_compat - parse setname as element + * @session: session structure + * @opt: option kind of the data + * @str: string to parse + * + * Parse string as a setname or a setname element to add to a set. + * The pattern "setname,before|after,setname" is recognized and + * parsed. + * The value is stored in the data blob of the session. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_parse_name_compat(struct ipset_session *session, + enum ipset_opt opt, const char *str) +{ + char *saved; + char *a = NULL, *b = NULL, *tmp; + int err, before = 0; + const char *sep = IPSET_ELEM_SEPARATOR; + struct ipset_data *data; + + assert(session); + assert(opt == IPSET_OPT_NAME); + assert(str); + + data = ipset_session_data(session); + if (ipset_data_flags_test(data, IPSET_FLAG(IPSET_OPT_NAMEREF))) + syntax_err("mixed syntax, before|after option already used"); + + tmp = saved = strdup(str); + if (saved == NULL) + return ipset_err(session, + "Cannot allocate memory to duplicate %s.", + str); + if ((a = elem_separator(tmp)) != NULL) { + /* setname,[before|after,setname */ + *a++ = '\0'; + if ((b = elem_separator(a)) != NULL) + *b++ = '\0'; + if (b == NULL + || !(STREQ(a, "before") || STREQ(a, "after"))) { + err = ipset_err(session, "you must specify elements " + "as setname%s[before|after]%ssetname", + sep, sep); + goto out; + } + before = STREQ(a, "before"); + } + check_setname(tmp, saved); + if ((err = ipset_data_set(data, opt, tmp)) != 0 || b == NULL) + goto out; + + check_setname(b, saved); + if ((err = ipset_data_set(data, + IPSET_OPT_NAMEREF, b)) != 0) + goto out; + + if (before) + err = ipset_data_set(data, IPSET_OPT_BEFORE, &before); + +out: + free(saved); + return err; +} + +/** + * ipset_parse_setname - parse string as a setname + * @session: session structure + * @opt: option kind of the data + * @str: string to parse + * + * Parse string as a setname. + * The value is stored in the data blob of the session. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_parse_setname(struct ipset_session *session, + enum ipset_opt opt, const char *str) +{ + assert(session); + assert(opt == IPSET_SETNAME + || opt == IPSET_OPT_NAME + || opt == IPSET_OPT_SETNAME2); + assert(str); + + check_setname(str, NULL); + + return ipset_session_data_set(session, opt, str); +} + +/** + * ipset_parse_before - parse string as "before" reference setname + * @session: session structure + * @opt: option kind of the data + * @str: string to parse + * + * Parse string as a "before" reference setname for list:set + * type of sets. The value is stored in the data blob of the session. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_parse_before(struct ipset_session *session, + enum ipset_opt opt, const char *str) +{ + struct ipset_data *data; + + assert(session); + assert(opt == IPSET_OPT_NAMEREF); + assert(str); + + data = ipset_session_data(session); + if (ipset_data_flags_test(data, IPSET_FLAG(IPSET_OPT_NAMEREF))) + syntax_err("mixed syntax, before|after option already used"); + + check_setname(str, NULL); + ipset_data_set(data, IPSET_OPT_BEFORE, str); + + return ipset_data_set(data, opt, str); +} + +/** + * ipset_parse_after - parse string as "after" reference setname + * @session: session structure + * @opt: option kind of the data + * @str: string to parse + * + * Parse string as a "after" reference setname for list:set + * type of sets. The value is stored in the data blob of the session. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_parse_after(struct ipset_session *session, + enum ipset_opt opt, const char *str) +{ + struct ipset_data *data; + + assert(session); + assert(opt == IPSET_OPT_NAMEREF); + assert(str); + + data = ipset_session_data(session); + if (ipset_data_flags_test(data, IPSET_FLAG(IPSET_OPT_NAMEREF))) + syntax_err("mixed syntax, before|after option already used"); + + check_setname(str, NULL); + + return ipset_data_set(data, opt, str); +} + +/** + * ipset_parse_uint32 - parse string as an unsigned integer + * @session: session structure + * @opt: option kind of the data + * @str: string to parse + * + * Parse string as an unsigned integer number. + * The value is stored in the data blob of the session. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_parse_uint32(struct ipset_session *session, + enum ipset_opt opt, const char *str) +{ + uint32_t value; + int err; + + assert(session); + assert(str); + + if ((err = string_to_u32(session, str, &value)) == 0) + return ipset_session_data_set(session, opt, &value); + + return err; +} + +/** + * ipset_parse_uint8 - parse string as an unsigned short integer + * @session: session structure + * @opt: option kind of the data + * @str: string to parse + * + * Parse string as an unsigned short integer number. + * The value is stored in the data blob of the session. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_parse_uint8(struct ipset_session *session, + enum ipset_opt opt, const char *str) +{ + uint8_t value; + int err; + + assert(session); + assert(str); + + if ((err = string_to_u8(session, str, &value)) == 0) + return ipset_session_data_set(session, opt, &value); + + return err; +} + +/** + * ipset_parse_netmask - parse string as a CIDR netmask value + * @session: session structure + * @opt: option kind of the data + * @str: string to parse + * + * Parse string as a CIDR netmask value, depending on family type. + * If family is not set yet, INET is assumed. + * The value is stored in the data blob of the session. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_parse_netmask(struct ipset_session *session, + enum ipset_opt opt, const char *str) +{ + uint8_t family, cidr; + struct ipset_data *data; + int err = 0; + + assert(session); + assert(opt == IPSET_OPT_NETMASK); + assert(str); + + data = ipset_session_data(session); + family = ipset_data_family(data); + if (family == AF_UNSPEC) { + family = AF_INET; + ipset_data_set(data, IPSET_OPT_FAMILY, &family); + } + + err = string_to_cidr(session, str, + family == AF_INET ? 1 : 4, + family == AF_INET ? 31 : 124, + &cidr); + + if (err) + return syntax_err("netmask is out of the inclusive range " + "of %u-%u", + family == AF_INET ? 1 : 4, + family == AF_INET ? 31 : 124); + + return ipset_data_set(data, opt, &cidr); +} + +/** + * ipset_parse_flag - "parse" option flags + * @session: session structure + * @opt: option kind of the data + * @str: string to parse + * + * Parse option flags :-) + * The value is stored in the data blob of the session. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_parse_flag(struct ipset_session *session, + enum ipset_opt opt, const char *str UNUSED) +{ + assert(session); + + return ipset_session_data_set(session, opt, NULL); +} + +/** + * ipset_parse_type - parse ipset type name + * @session: session structure + * @opt: option kind of the data + * @str: string to parse + * + * Parse ipset module type: supports both old and new formats. + * The type name is looked up and the type found is stored + * in the data blob of the session. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_parse_typename(struct ipset_session *session, + enum ipset_opt opt, const char *str) +{ + const struct ipset_type *type; + const char *typename; + + assert(session); + assert(opt == IPSET_OPT_TYPENAME); + assert(str); + + if (strlen(str) > IPSET_MAXNAMELEN - 1) + return syntax_err("typename '%s' is longer than %u characters", + str, IPSET_MAXNAMELEN - 1); + + /* Find the corresponding type */ + typename = ipset_typename_resolve(str); + if (typename == NULL) + return syntax_err("typename '%s' is unkown", str); + ipset_session_data_set(session, IPSET_OPT_TYPENAME, typename); + type = ipset_type_get(session, IPSET_CMD_CREATE); + + if (type == NULL) + return -1; + + return ipset_session_data_set(session, IPSET_OPT_TYPE, type); +} + +/** + * ipset_parse_output - parse output format name + * @session: session structure + * @opt: option kind of the data + * @str: string to parse + * + * Parse output format names and set session mode. + * The value is stored in the session. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_parse_output(struct ipset_session *session, + int opt UNUSED, const char *str) +{ + assert(session); + assert(str); + + if (STREQ(str, "plain")) + return ipset_session_output(session, IPSET_LIST_PLAIN); + else if (STREQ(str, "xml")) + return ipset_session_output(session, IPSET_LIST_XML); + else if (STREQ(str, "save")) + return ipset_session_output(session, IPSET_LIST_SAVE); + + return syntax_err("unkown output mode '%s'", str); +} + +/** + * ipset_parse_ignored - "parse" ignored option + * @session: session structure + * @opt: option kind of the data + * @str: string to parse + * + * Ignore deprecated options. A single warning is generated + * for every ignored opton. + * + * Returns 0. + */ +int +ipset_parse_ignored(struct ipset_session *session, + enum ipset_opt opt, const char *str) +{ + assert(session); + assert(str); + + if (!ipset_data_ignored(ipset_session_data(session), opt)) + ipset_warn(session, + "Option %s is ignored. Please upgrade your syntax.", str); + + return 0; +} + +/** + * ipset_call_parser - call a parser function + * @session: session structure + * @parsefn: parser function + * @optstr: option name + * @opt: option kind of the data + * @str: string to parse + * + * Wrapper to call the parser functions so that ignored options + * are handled properly. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_call_parser(struct ipset_session *session, + ipset_parsefn parse, const char *optstr, + enum ipset_opt opt, const char *str) +{ + if (ipset_data_flags_test(ipset_session_data(session), + IPSET_FLAG(opt))) + syntax_err("%s already specified", optstr); + + return parse(session, opt, parse == ipset_parse_ignored + ? optstr : str); +} + +#define parse_elem(s, t, d, str) \ +do { \ + if (!(t)->elem[d].parse) \ + goto internal; \ + ret = (t)->elem[d].parse(s, (t)->elem[d].opt, str); \ + if (ret) \ + goto out; \ +} while (0) + +#define elem_syntax_err(fmt, args...) \ +do { \ + free(saved); \ + return syntax_err(fmt , ## args);\ +} while (0) + +/** + * ipset_parse_elem - parse ADT elem, depending on settype + * @session: session structure + * @opt: option kind of the data + * @str: string to parse + * + * Parse string as a (multipart) element according to the settype. + * The value is stored in the data blob of the session. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_parse_elem(struct ipset_session *session, + enum ipset_opt optional, const char *str) +{ + const struct ipset_type *type; + char *a = NULL, *b = NULL, *tmp, *saved; + int ret; + + assert(session); + assert(str); + + type = ipset_session_data_get(session, IPSET_OPT_TYPE); + if (!type) + return ipset_err(session, + "Internal error: set type is unknown!"); + + saved = tmp = strdup(str); + if (tmp == NULL) + return ipset_err(session, + "Cannot allocate memory to duplicate %s.", + str); + + a = elem_separator(tmp); + if (type->dimension > IPSET_DIM_ONE) { + if (a != NULL) { + /* elem,elem */ + *a++ = '\0'; + } else if (!optional) + elem_syntax_err("Second element is missing from %s.", + str); + } else if (a != NULL) { + if (type->compat_parse_elem) { + ret = type->compat_parse_elem(session, + type->elem[IPSET_DIM_ONE].opt, + saved); + goto out; + } + elem_syntax_err("Elem separator in %s, " + "but settype %s supports none.", + str, type->name); + } + + if (a) + b = elem_separator(a); + if (type->dimension > IPSET_DIM_TWO) { + if (b != NULL) { + /* elem,elem,elem */ + *b++ = '\0'; + } else if (!optional) + elem_syntax_err("Third element is missing from %s.", + str); + } else if (b != NULL) + elem_syntax_err("Two elem separators in %s, " + "but settype %s supports one.", + str, type->name); + if (b != NULL && elem_separator(b)) + elem_syntax_err("Three elem separators in %s, " + "but settype %s supports two.", + str, type->name); + + D("parse elem part one: %s", tmp); + parse_elem(session, type, IPSET_DIM_ONE, tmp); + + if (type->dimension > IPSET_DIM_ONE && a != NULL) { + D("parse elem part two: %s", a); + parse_elem(session, type, IPSET_DIM_TWO, a); + } + if (type->dimension > IPSET_DIM_TWO && b != NULL) + parse_elem(session, type, IPSET_DIM_THREE, b); + + goto out; + +internal: + ret = ipset_err(session, + "Internal error: missing parser function for %s", + type->name); +out: + free(saved); + return ret; +} diff --git a/extensions/ipset-5/libipset/print.c b/extensions/ipset-5/libipset/print.c new file mode 100644 index 0000000..b6819e5 --- /dev/null +++ b/extensions/ipset-5/libipset/print.c @@ -0,0 +1,743 @@ +/* Copyright 2007-2010 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 /* assert */ +#include /* errno */ +#include /* snprintf */ +#include /* getservbyport */ +#include /* inet_ntop */ +#include /* inet_ntop */ +#include /* inet_ntop */ +#include /* ETH_ALEN */ + +#include /* D() */ +#include /* ipset_data_* */ +#include /* icmp_to_name */ +#include /* icmpv6_to_name */ +#include /* IPSET_*_SEPARATOR */ +#include /* ipset set types */ +#include /* IPSET_FLAG_ */ +#include /* UNUSED */ +#include /* IPSET_ENV_* */ +#include /* prototypes */ + +/* Print data (to output buffer). All function must follow snprintf. */ + +#define SNPRINTF_FAILURE(size, len, offset) \ +do { \ + if (size < 0 || (unsigned int) size >= len) \ + return size; \ + offset += size; \ + len -= size; \ +} while (0) + +/** + * ipset_print_ether - print ethernet address to string + * @buf: printing buffer + * @len: length of available buffer space + * @data: data blob + * @opt: the option kind + * @env: environment flags + * + * Print Ethernet address to output buffer. + * + * Return lenght of printed string or error size. + */ +int +ipset_print_ether(char *buf, unsigned int len, + const struct ipset_data *data, enum ipset_opt opt, + uint8_t env UNUSED) +{ + const unsigned char *ether; + int i, size, offset = 0; + + assert(buf); + assert(len > 0); + assert(data); + assert(opt == IPSET_OPT_ETHER); + + if (len < ETH_ALEN*3) + return -1; + + ether = ipset_data_get(data, opt); + assert(ether); + + size = snprintf(buf, len, "%02X", ether[0]); + SNPRINTF_FAILURE(size, len, offset); + for (i = 1; i < ETH_ALEN; i++) { + size = snprintf(buf + offset, len, ":%02X", ether[i]); + SNPRINTF_FAILURE(size, len, offset); + } + + return offset; +} + +/** + * ipset_print_family - print INET family + * @buf: printing buffer + * @len: length of available buffer space + * @data: data blob + * @opt: the option kind + * @env: environment flags + * + * Print INET family string to output buffer. + * + * Return lenght of printed string or error size. + */ +int +ipset_print_family(char *buf, unsigned int len, + const struct ipset_data *data, enum ipset_opt opt, + uint8_t env UNUSED) +{ + uint8_t family; + + assert(buf); + assert(len > 0); + assert(data); + assert(opt == IPSET_OPT_FAMILY); + + if (len < strlen("inet6") + 1) + return -1; + + family = ipset_data_family(data); + + return snprintf(buf, len, "%s", + family == AF_INET ? "inet" : + family == AF_INET6 ? "inet6" : "any"); +} + +/** + * ipset_print_type - print ipset type string + * @buf: printing buffer + * @len: length of available buffer space + * @data: data blob + * @opt: the option kind + * @env: environment flags + * + * Print ipset module string identifier to output buffer. + * + * Return lenght of printed string or error size. + */ +int +ipset_print_type(char *buf, unsigned int len, + const struct ipset_data *data, enum ipset_opt opt, + uint8_t env UNUSED) +{ + const struct ipset_type *type; + + assert(buf); + assert(len > 0); + assert(data); + assert(opt == IPSET_OPT_TYPE); + + type = ipset_data_get(data, opt); + assert(type); + if (len < strlen(type->name) + 1) + return -1; + + return snprintf(buf, len, "%s", type->name); +} + +#define GETNAMEINFO(family, f, n) \ +static inline int \ +__getnameinfo##f(char *buf, unsigned int len, \ + int flags, const union nf_inet_addr *addr) \ +{ \ + struct sockaddr_in##n saddr; \ + int err; \ + \ + memset(&saddr, 0, sizeof(saddr)); \ + in##f##cpy(&saddr.sin##n##_addr, &addr->in##n); \ + saddr.sin##n##_family = family; \ + \ + err = getnameinfo((const struct sockaddr *)&saddr, \ + sizeof(saddr), \ + buf, len, NULL, 0, flags); \ + \ + if (err == EAI_AGAIN && !(flags & NI_NUMERICHOST)) \ + err = getnameinfo((const struct sockaddr *)&saddr, \ + sizeof(saddr), \ + buf, len, NULL, 0, \ + flags | NI_NUMERICHOST); \ + D("getnameinfo err: %i, errno %i", err, errno); \ + return (err == 0 ? (int)strlen(buf) : \ + (err == EAI_OVERFLOW || err == EAI_SYSTEM) ? (int)len : -1);\ +} + +#define SNPRINTF_IP(mask, f) \ +static int \ +snprintf_ipv##f(char *buf, unsigned int len, int flags, \ + const union nf_inet_addr *ip, uint8_t cidr) \ +{ \ + int size, offset = 0; \ + \ + size = __getnameinfo##f(buf, len, flags, ip); \ + SNPRINTF_FAILURE(size, len, offset); \ + \ + D("cidr %u mask %u", cidr, mask); \ + if (cidr == mask) \ + return offset; \ + D("print cidr"); \ + size = snprintf(buf + offset, len, \ + "%s%u", IPSET_CIDR_SEPARATOR, cidr); \ + SNPRINTF_FAILURE(size, len, offset); \ + return offset; \ +} + +GETNAMEINFO(AF_INET, 4, ) +SNPRINTF_IP(32, 4) + +GETNAMEINFO(AF_INET6, 6, 6) +SNPRINTF_IP(128, 6) + +/** + * ipset_print_ip - print IPv4|IPv6 address to string + * @buf: printing buffer + * @len: length of available buffer space + * @data: data blob + * @opt: the option kind + * @env: environment flags + * + * Print IPv4|IPv6 address, address/cidr or address range to output buffer. + * + * Return lenght of printed string or error size. + */ +int +ipset_print_ip(char *buf, unsigned int len, + const struct ipset_data *data, enum ipset_opt opt, + uint8_t env) +{ + const union nf_inet_addr *ip; + uint8_t family, cidr; + int flags, size, offset = 0; + enum ipset_opt cidropt; + + assert(buf); + assert(len > 0); + assert(data); + assert(opt == IPSET_OPT_IP || opt == IPSET_OPT_IP2); + + D("len: %u", len); + family = ipset_data_family(data); + cidropt = opt == IPSET_OPT_IP ? IPSET_OPT_CIDR : IPSET_OPT_CIDR2; + if (ipset_data_test(data, cidropt)) { + cidr = *(const uint8_t *) ipset_data_get(data, cidropt); + D("CIDR: %u", cidr); + } else + cidr = family == AF_INET6 ? 128 : 32; + flags = env & (1 << IPSET_ENV_RESOLVE) ? 0 : NI_NUMERICHOST; + + ip = ipset_data_get(data, opt); + assert(ip); + if (family == AF_INET) + size = snprintf_ipv4(buf, len, flags, ip, cidr); + else if (family == AF_INET6) + size = snprintf_ipv6(buf, len, flags, ip, cidr); + else + return -1; + D("size %i, len %u", size, len); + SNPRINTF_FAILURE(size, len, offset); + + D("len: %u, offset %u", len, offset); + if (!ipset_data_test(data, IPSET_OPT_IP_TO)) + return offset; + + size = snprintf(buf + offset, len, "%s", IPSET_RANGE_SEPARATOR); + SNPRINTF_FAILURE(size, len, offset); + + ip = ipset_data_get(data, IPSET_OPT_IP_TO); + if (family == AF_INET) + size = snprintf_ipv4(buf + offset, len, flags, ip, cidr); + else if (family == AF_INET6) + size = snprintf_ipv6(buf + offset, len, flags, ip, cidr); + else + return -1; + + SNPRINTF_FAILURE(size, len, offset); + return offset; +} + +/** + * ipset_print_ipaddr - print IPv4|IPv6 address to string + * @buf: printing buffer + * @len: length of available buffer space + * @data: data blob + * @opt: the option kind + * @env: environment flags + * + * Print IPv4|IPv6 address or address/cidr to output buffer. + * + * Return lenght of printed string or error size. + */ +int +ipset_print_ipaddr(char *buf, unsigned int len, + const struct ipset_data *data, enum ipset_opt opt, + uint8_t env) +{ + const union nf_inet_addr *ip; + uint8_t family, cidr; + enum ipset_opt cidropt; + int flags; + + assert(buf); + assert(len > 0); + assert(data); + assert(opt == IPSET_OPT_IP + || opt == IPSET_OPT_IP_TO + || opt == IPSET_OPT_IP2); + + family = ipset_data_family(data); + cidropt = opt == IPSET_OPT_IP ? IPSET_OPT_CIDR : IPSET_OPT_CIDR2; + if (ipset_data_test(data, cidropt)) + cidr = *(const uint8_t *) ipset_data_get(data, cidropt); + else + cidr = family == AF_INET6 ? 128 : 32; + flags = env & (1 << IPSET_ENV_RESOLVE) ? 0 : NI_NUMERICHOST; + + ip = ipset_data_get(data, opt); + assert(ip); + if (family == AF_INET) + return snprintf_ipv4(buf, len, flags, ip, cidr); + else if (family == AF_INET6) + return snprintf_ipv6(buf, len, flags, ip, cidr); + + return -1; +} + +/** + * ipset_print_number - print number to string + * @buf: printing buffer + * @len: length of available buffer space + * @data: data blob + * @opt: the option kind + * @env: environment flags + * + * Print number to output buffer. + * + * Return lenght of printed string or error size. + */ +int +ipset_print_number(char *buf, unsigned int len, + const struct ipset_data *data, enum ipset_opt opt, + uint8_t env UNUSED) +{ + size_t maxsize; + const void *number; + + assert(buf); + assert(len > 0); + assert(data); + + number = ipset_data_get(data, opt); + maxsize = ipset_data_sizeof(opt, AF_INET); + D("opt: %u, maxsize %zu", opt, maxsize); + if (maxsize == sizeof(uint8_t)) + return snprintf(buf, len, "%u", *(const uint8_t *) number); + else if (maxsize == sizeof(uint16_t)) + return snprintf(buf, len, "%u", *(const uint16_t *) number); + else if (maxsize == sizeof(uint32_t)) + return snprintf(buf, len, "%lu", + (long unsigned) *(const uint32_t *) number); + else + assert(0); + return 0; +} + +/** + * ipset_print_name - print setname element string + * @buf: printing buffer + * @len: length of available buffer space + * @data: data blob + * @opt: the option kind + * @env: environment flags + * + * Print setname element string to output buffer. + * + * Return lenght of printed string or error size. + */ +int +ipset_print_name(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_NAME); + + if (len < 2*IPSET_MAXNAMELEN + 2 + strlen("before")) + return -1; + + name = ipset_data_get(data, opt); + assert(name); + size = snprintf(buf, len, "%s", name); + SNPRINTF_FAILURE(size, len, offset); + + if (ipset_data_test(data, IPSET_OPT_NAMEREF)) { + bool before = false; + if (ipset_data_flags_test(data, IPSET_FLAG(IPSET_OPT_FLAGS))) { + const uint32_t *flags = + ipset_data_get(data, IPSET_OPT_FLAGS); + before = (*flags) & IPSET_FLAG_BEFORE; + } + size = snprintf(buf + offset, len, + " %s %s", before ? "before" : "after", + (const char *) ipset_data_get(data, + IPSET_OPT_NAMEREF)); + SNPRINTF_FAILURE(size, len, offset); + } + + return offset; +} + +/** + * ipset_print_port - print port or port range + * @buf: printing buffer + * @len: length of available buffer space + * @data: data blob + * @opt: the option kind + * @env: environment flags + * + * Print port or port range to output buffer. + * + * Return lenght of printed string or error size. + */ +int +ipset_print_port(char *buf, unsigned int len, + const struct ipset_data *data, enum ipset_opt opt, + uint8_t env UNUSED) +{ + const uint16_t *port; + int size, offset = 0; + + assert(buf); + assert(len > 0); + assert(data); + assert(opt == IPSET_OPT_PORT); + + if (len < 2*strlen("65535") + 2) + return -1; + + port = ipset_data_get(data, IPSET_OPT_PORT); + assert(port); + size = snprintf(buf, len, "%u", *port); + SNPRINTF_FAILURE(size, len, offset); + + if (ipset_data_test(data, IPSET_OPT_PORT_TO)) { + port = ipset_data_get(data, IPSET_OPT_PORT_TO); + size = snprintf(buf + offset, len, + "%s%u", + IPSET_RANGE_SEPARATOR, *port); + SNPRINTF_FAILURE(size, len, offset); + } + + return offset; +} + +/** + * ipset_print_proto - print protocol name + * @buf: printing buffer + * @len: length of available buffer space + * @data: data blob + * @opt: the option kind + * @env: environment flags + * + * Print protocol name to output buffer. + * + * Return lenght of printed string or error size. + */ +int +ipset_print_proto(char *buf, unsigned int len, + const struct ipset_data *data, enum ipset_opt opt, + uint8_t env UNUSED) +{ + const struct protoent *protoent; + uint8_t proto; + + assert(buf); + assert(len > 0); + assert(data); + assert(opt == IPSET_OPT_PROTO); + + proto = *(const uint8_t *) ipset_data_get(data, IPSET_OPT_PROTO); + assert(proto); + + protoent = getprotobynumber(proto); + if (protoent) + return snprintf(buf, len, "%s", protoent->p_name); + + /* Should not happen */ + return snprintf(buf, len, "%u", proto); +} + +/** + * ipset_print_icmp - print ICMP code name or type/code + * @buf: printing buffer + * @len: length of available buffer space + * @data: data blob + * @opt: the option kind + * @env: environment flags + * + * Print ICMP code name or type/code name to output buffer. + * + * Return lenght of printed string or error size. + */ +int +ipset_print_icmp(char *buf, unsigned int len, + const struct ipset_data *data, enum ipset_opt opt, + uint8_t env UNUSED) +{ + const char *name; + uint16_t typecode; + + assert(buf); + assert(len > 0); + assert(data); + assert(opt == IPSET_OPT_PORT); + + typecode = *(const uint16_t *) ipset_data_get(data, IPSET_OPT_PORT); + name = icmp_to_name(typecode >> 8, typecode & 0xFF); + if (name != NULL) + return snprintf(buf, len, "%s", name); + else + return snprintf(buf, len, "%u/%u", typecode >> 8, typecode & 0xFF); +} + +/** + * ipset_print_icmpv6 - print ICMPv6 code name or type/code + * @buf: printing buffer + * @len: length of available buffer space + * @data: data blob + * @opt: the option kind + * @env: environment flags + * + * Print ICMPv6 code name or type/code name to output buffer. + * + * Return lenght of printed string or error size. + */ +int +ipset_print_icmpv6(char *buf, unsigned int len, + const struct ipset_data *data, enum ipset_opt opt, + uint8_t env UNUSED) +{ + const char *name; + uint16_t typecode; + + assert(buf); + assert(len > 0); + assert(data); + assert(opt == IPSET_OPT_PORT); + + typecode = *(const uint16_t *) ipset_data_get(data, IPSET_OPT_PORT); + name = icmpv6_to_name(typecode >> 8, typecode & 0xFF); + if (name != NULL) + return snprintf(buf, len, "%s", name); + else + return snprintf(buf, len, "%u/%u", typecode >> 8, typecode & 0xFF); +} + +/** + * ipset_print_proto_port - print proto:port + * @buf: printing buffer + * @len: length of available buffer space + * @data: data blob + * @opt: the option kind + * @env: environment flags + * + * Print protocol and port to output buffer. + * + * Return lenght of printed string or error size. + */ +int +ipset_print_proto_port(char *buf, unsigned int len, + const struct ipset_data *data, enum ipset_opt opt, + uint8_t env UNUSED) +{ + int size, offset = 0; + + assert(buf); + assert(len > 0); + assert(data); + assert(opt == IPSET_OPT_PORT); + + if (ipset_data_flags_test(data, IPSET_FLAG(IPSET_OPT_PROTO))) { + uint8_t proto = *(const uint8_t *) ipset_data_get(data, + IPSET_OPT_PROTO); + size = ipset_print_proto(buf, len, data, IPSET_OPT_PROTO, env); + SNPRINTF_FAILURE(size, len, offset); + if (len < 2) + return -ENOSPC; + size = snprintf(buf + offset, len, IPSET_PROTO_SEPARATOR); + SNPRINTF_FAILURE(size, len, offset); + + switch (proto) { + case IPPROTO_TCP: + case IPPROTO_UDP: + break; + case IPPROTO_ICMP: + return ipset_print_icmp(buf + offset, len, data, + IPSET_OPT_PORT, env); + case IPPROTO_ICMPV6: + return ipset_print_icmpv6(buf + offset, len, data, + IPSET_OPT_PORT, env); + default: + break; + } + } + size = ipset_print_port(buf + offset, len, data, IPSET_OPT_PORT, env); + SNPRINTF_FAILURE(size, len, offset); + + return offset; +} + +#define print_second(data) \ +ipset_data_flags_test(data, \ + IPSET_FLAG(IPSET_OPT_PORT)|IPSET_FLAG(IPSET_OPT_ETHER)) + +#define print_third(data) \ +ipset_data_flags_test(data, IPSET_FLAG(IPSET_OPT_IP2)) + +/** + * ipset_print_elem - print ADT elem according to settype + * @buf: printing buffer + * @len: length of available buffer space + * @data: data blob + * @opt: the option kind + * @env: environment flags + * + * Print (multipart) element according to settype + * + * Return lenght of printed string or error size. + */ +int +ipset_print_elem(char *buf, unsigned int len, + const struct ipset_data *data, enum ipset_opt opt UNUSED, + uint8_t env) +{ + const struct ipset_type *type; + int size, offset = 0; + + assert(buf); + assert(len > 0); + assert(data); + + type = ipset_data_get(data, IPSET_OPT_TYPE); + if (!type) + return -1; + + size = type->elem[IPSET_DIM_ONE].print(buf, len, data, + type->elem[IPSET_DIM_ONE].opt, env); + SNPRINTF_FAILURE(size, len, offset); + IF_D(ipset_data_test(data, type->elem[IPSET_DIM_TWO].opt), + "print second elem"); + if (type->dimension == IPSET_DIM_ONE + || (type->last_elem_optional + && !ipset_data_test(data, type->elem[IPSET_DIM_TWO].opt))) + return offset; + + size = snprintf(buf + offset, len, IPSET_ELEM_SEPARATOR); + SNPRINTF_FAILURE(size, len, offset); + size = type->elem[IPSET_DIM_TWO].print(buf + offset, len, data, + type->elem[IPSET_DIM_TWO].opt, env); + SNPRINTF_FAILURE(size, len, offset); + if (type->dimension == IPSET_DIM_TWO + || (type->last_elem_optional + && !ipset_data_test(data, type->elem[IPSET_DIM_THREE].opt))) + return offset; + + size = snprintf(buf + offset, len, IPSET_ELEM_SEPARATOR); + SNPRINTF_FAILURE(size, len, offset); + size = type->elem[IPSET_DIM_THREE].print(buf + offset, len, data, + type->elem[IPSET_DIM_THREE].opt, env); + SNPRINTF_FAILURE(size, len, offset); + + return offset; +} + +/** + * ipset_print_flag - print a flag + * @buf: printing buffer + * @len: length of available buffer space + * @data: data blob + * @opt: the option kind + * @env: environment flags + * + * Print a flag, i.e. option without value + * + * Return lenght of printed string or error size. + */ +int +ipset_print_flag(char *buf UNUSED, unsigned int len UNUSED, + const struct ipset_data *data UNUSED, + enum ipset_opt opt UNUSED, uint8_t env UNUSED) +{ + return 0; +} + +/** + * ipset_print_data - print data, generic fuction + * @buf: printing buffer + * @len: length of available buffer space + * @data: data blob + * @opt: the option kind + * @env: environment flags + * + * Generic wrapper of the printing functions. + * + * Return lenght of printed string or error size. + */ +int +ipset_print_data(char *buf, unsigned int len, + const struct ipset_data *data, enum ipset_opt opt, + uint8_t env) +{ + int size = 0, offset = 0; + + assert(buf); + assert(len > 0); + assert(data); + + switch (opt) { + case IPSET_OPT_FAMILY: + size = ipset_print_family(buf, len, data, opt, env); + break; + case IPSET_OPT_TYPE: + size = ipset_print_type(buf, len, data, opt, env); + break; + case IPSET_SETNAME: + size = snprintf(buf, len, "%s", ipset_data_setname(data)); + break; + case IPSET_OPT_ELEM: + size = ipset_print_elem(buf, len, data, opt, env); + break; + case IPSET_OPT_IP: + size = ipset_print_ip(buf, len, data, opt, env); + break; + case IPSET_OPT_PORT: + size = ipset_print_port(buf, len, data, opt, env); + break; + case IPSET_OPT_GC: + case IPSET_OPT_HASHSIZE: + case IPSET_OPT_MAXELEM: + case IPSET_OPT_NETMASK: + case IPSET_OPT_PROBES: + case IPSET_OPT_RESIZE: + case IPSET_OPT_TIMEOUT: + case IPSET_OPT_REFERENCES: + case IPSET_OPT_ELEMENTS: + case IPSET_OPT_SIZE: + size = ipset_print_number(buf, len, data, opt, env); + break; + default: + return -1; + } + SNPRINTF_FAILURE(size, len, offset); + + return offset; +} diff --git a/extensions/ipset-5/libipset/session.c b/extensions/ipset-5/libipset/session.c new file mode 100644 index 0000000..82cceea --- /dev/null +++ b/extensions/ipset-5/libipset/session.c @@ -0,0 +1,1915 @@ +/* Copyright 2007-2010 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 /* assert */ +#include /* errno */ +#include /* setjmp, longjmp */ +#include /* snprintf */ +#include /* va_* */ +#include /* free */ +#include /* str* */ +#include /* getpagesize */ +#include /* ETH_ALEN */ + +#include /* D() */ +#include /* IPSET_OPT_* */ +#include /* ipset_errcode */ +#include /* ipset_print_* */ +#include /* struct ipset_type */ +#include /* transport */ +#include /* default backend */ +#include /* STREQ */ +#include /* IPSET_ENV_* */ +#include /* prototypes */ + +#include + +#define IPSET_NEST_MAX 4 + +/* The session structure */ +struct ipset_session { + const struct ipset_transport *transport;/* Transport protocol */ + struct ipset_handle *handle; /* Transport handler */ + struct ipset_data *data; /* Input/output data */ + /* Command state */ + enum ipset_cmd cmd; /* Current command */ + uint32_t lineno; /* Current lineno in restore mode */ + char saved_setname[IPSET_MAXNAMELEN]; /* Saved setname */ + const struct ipset_type *saved_type; /* Saved type */ + struct nlattr *nested[IPSET_NEST_MAX]; /* Pointer to nest levels */ + uint8_t nestid; /* Current nest level */ + bool version_checked; /* Version checked */ + /* Output buffer */ + char outbuf[IPSET_OUTBUFLEN]; /* Output buffer */ + enum ipset_output_mode mode; /* Output mode */ + ipset_outfn outfn; /* Output function */ + /* Error/warning reporting */ + char report[IPSET_ERRORBUFLEN]; /* Error/report buffer */ + char *errmsg; + char *warnmsg; + uint8_t envopts; /* Session env opts */ + /* Kernel message buffer */ + size_t bufsize; + void *buffer; +}; + +/* + * Glue functions + */ + +/** + * ipset_session_data - return pointer to the data + * @session: session structure + * + * Returns the pointer to the data structure of the session. + */ +struct ipset_data * +ipset_session_data(const struct ipset_session *session) +{ + assert(session); + return session->data; +} + +/** + * ipset_session_handle - return pointer to the handle + * @session: session structure + * + * Returns the pointer to the transport handle structure of the session. + */ +struct ipset_handle * +ipset_session_handle(const struct ipset_session *session) +{ + assert(session); + return session->handle; +} + +/** + * ipset_saved_type - return pointer to the saved type + * @session: session structure + * + * Returns the pointer to the saved type from the last ipset_cmd + * It is required to decode type-specific error codes in restore mode. + */ +const struct ipset_type * +ipset_saved_type(const struct ipset_session *session) +{ + assert(session); + return session->saved_type; +} + +/* + * Environment options + */ + +/** + * ipset_envopt_parse - parse/set environment option + * @session: session structure + * @opt: environment option + * @arg: option argument (unused) + * + * Parse and set an environment option. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_envopt_parse(struct ipset_session *session, int opt, + const char *arg UNUSED) +{ + assert(session); + + switch (opt) { + case IPSET_ENV_SORTED: + case IPSET_ENV_QUIET: + case IPSET_ENV_RESOLVE: + case IPSET_ENV_EXIST: + session->envopts |= opt; + return 0; + default: + break; + } + return -1; +} + +/** + * ipset_envopt_test - test environment option + * @session: session structure + * @opt: environment option + * + * Test whether the environment option is set in the session. + * + * Returns true or false. + */ +bool +ipset_envopt_test(struct ipset_session *session, enum ipset_envopt opt) +{ + assert(session); + return session->envopts & opt; +} + +/** + * ipset_session_output - set the session output mode + * @session: session structure + * @mode: output mode + * + * Set the output mode for the session. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_session_output(struct ipset_session *session, + enum ipset_output_mode mode) +{ + assert(session); + session->mode = mode; + return 0; +} + +/* + * Error and warning reporting + */ + +/** + * ipset_session_report - fill the report buffer + * @session: session structure + * @type: report type + * @fmt: message format + * + * Fill the report buffer with an error or warning message. + * Depending on the report type, set the error or warning + * message pointer. + * + * Returns -1. + */ +int __attribute__((format(printf,3,4))) +ipset_session_report(struct ipset_session *session, + enum ipset_err_type type, + const char *fmt, ...) +{ + int len, offset = 0; + va_list args; + + assert(session); + assert(fmt); + + if (session->lineno != 0 && type == IPSET_ERROR) { + sprintf(session->report, "Error in line %u: ", + session->lineno); + } + offset = strlen(session->report); + + va_start(args, fmt); + len = vsnprintf(session->report + offset, + IPSET_ERRORBUFLEN - 1 - offset, + fmt, args); + va_end(args); + + if (len >= IPSET_ERRORBUFLEN - 1 - offset) + session->report[IPSET_ERRORBUFLEN - 1] = '\0'; + if (strlen(session->report) < IPSET_ERRORBUFLEN - 1) + strcat(session->report, "\n"); + + if (type == IPSET_ERROR) { + session->errmsg = session->report; + session->warnmsg = NULL; + } else { + session->errmsg = NULL; + session->warnmsg = session->report; + } + return -1; +} + +/** + * ipset_session_reset - reset the report buffer + * @session: session structure + * + * Reset the report buffer, the error and warning pointers. + */ +void +ipset_session_report_reset(struct ipset_session *session) +{ + assert(session); + session->report[0] = '\0'; + session->errmsg = session->warnmsg = NULL; +} + +/** + * ipset_session_error - return the report buffer as error + * @session: session structure + * + * Return the pointer to the report buffer as an error report. + * If there is no error message in the buffer, NULL returned. + */ +const char * +ipset_session_error(const struct ipset_session *session) +{ + assert(session); + + return session->errmsg; +} + +/** + * ipset_session_warning - return the report buffer as warning + * @session: session structure + * + * Return the pointer to the report buffer as a warning report. + * If there is no warning message in the buffer, NULL returned. + */ +const char * +ipset_session_warning(const struct ipset_session *session) +{ + assert(session); + + return session->warnmsg; +} + +/* + * Receive data from the kernel + */ + +struct ipset_attr_policy { + uint16_t type; + uint16_t len; + enum ipset_opt opt; +}; + +/* Attribute policies and mapping to options */ +static const struct ipset_attr_policy cmd_attrs[] = { + [IPSET_ATTR_PROTOCOL] = { + .type = MNL_TYPE_U8, + }, + [IPSET_ATTR_SETNAME] = { + .type = MNL_TYPE_NUL_STRING, + .opt = IPSET_SETNAME, + .len = IPSET_MAXNAMELEN, + }, + [IPSET_ATTR_TYPENAME] = { + .type = MNL_TYPE_NUL_STRING, + .opt = IPSET_OPT_TYPENAME, + .len = IPSET_MAXNAMELEN, + }, + /* IPSET_ATTR_SETNAME2 is an alias for IPSET_ATTR_TYPENAME */ + [IPSET_ATTR_REVISION] = { + .type = MNL_TYPE_U8, + .opt = IPSET_OPT_REVISION, + }, + [IPSET_ATTR_FAMILY] = { + .type = MNL_TYPE_U8, + .opt = IPSET_OPT_FAMILY, + }, + [IPSET_ATTR_FLAGS] = { + .type = MNL_TYPE_U32, + .opt = IPSET_OPT_FLAGS, + }, + [IPSET_ATTR_DATA] = { + .type = MNL_TYPE_NESTED, + }, + [IPSET_ATTR_ADT] = { + .type = MNL_TYPE_NESTED, + }, + [IPSET_ATTR_REVISION_MIN] = { + .type = MNL_TYPE_U8, + .opt = IPSET_OPT_REVISION_MIN, + }, + /* IPSET_ATTR_PROTOCOL_MIN is an alias for IPSET_ATTR_REVISION_MIN */ + [IPSET_ATTR_LINENO] = { + .type = MNL_TYPE_U32, + .opt = IPSET_OPT_LINENO, + }, +}; + +static const struct ipset_attr_policy create_attrs[] = { + [IPSET_ATTR_IP] = { + .type = MNL_TYPE_NESTED, + .opt = IPSET_OPT_IP, + }, + [IPSET_ATTR_IP_TO] = { + .type = MNL_TYPE_NESTED, + .opt = IPSET_OPT_IP_TO, + }, + [IPSET_ATTR_CIDR] = { + .type = MNL_TYPE_U8, + .opt = IPSET_OPT_CIDR, + }, + [IPSET_ATTR_PORT] = { + .type = MNL_TYPE_U16, + .opt = IPSET_OPT_PORT, + }, + [IPSET_ATTR_PORT_TO] = { + .type = MNL_TYPE_U16, + .opt = IPSET_OPT_PORT_TO, + }, + [IPSET_ATTR_TIMEOUT] = { + .type = MNL_TYPE_U32, + .opt = IPSET_OPT_TIMEOUT, + }, + [IPSET_ATTR_PROTO] = { + .type = MNL_TYPE_U8, + .opt = IPSET_OPT_PROTO, + }, + [IPSET_ATTR_CADT_FLAGS] = { + .type = MNL_TYPE_U32, + .opt = IPSET_OPT_CADT_FLAGS, + }, + [IPSET_ATTR_GC] = { + .type = MNL_TYPE_U32, + .opt = IPSET_OPT_GC, + }, + [IPSET_ATTR_HASHSIZE] = { + .type = MNL_TYPE_U32, + .opt = IPSET_OPT_HASHSIZE, + }, + [IPSET_ATTR_MAXELEM] = { + .type = MNL_TYPE_U32, + .opt = IPSET_OPT_MAXELEM, + }, + [IPSET_ATTR_NETMASK] = { + .type = MNL_TYPE_U8, + .opt = IPSET_OPT_NETMASK, + }, + [IPSET_ATTR_PROBES] = { + .type = MNL_TYPE_U8, + .opt = IPSET_OPT_PROBES, + }, + [IPSET_ATTR_RESIZE] = { + .type = MNL_TYPE_U8, + .opt = IPSET_OPT_RESIZE, + }, + [IPSET_ATTR_SIZE] = { + .type = MNL_TYPE_U32, + .opt = IPSET_OPT_SIZE, + }, + [IPSET_ATTR_ELEMENTS] = { + .type = MNL_TYPE_U32, + .opt = IPSET_OPT_ELEMENTS, + }, + [IPSET_ATTR_REFERENCES] = { + .type = MNL_TYPE_U32, + .opt = IPSET_OPT_REFERENCES, + }, + [IPSET_ATTR_MEMSIZE] = { + .type = MNL_TYPE_U32, + .opt = IPSET_OPT_MEMSIZE, + }, +}; + +static const struct ipset_attr_policy adt_attrs[] = { + [IPSET_ATTR_IP] = { + .type = MNL_TYPE_NESTED, + .opt = IPSET_OPT_IP, + }, + [IPSET_ATTR_IP_TO] = { + .type = MNL_TYPE_NESTED, + .opt = IPSET_OPT_IP_TO, + }, + [IPSET_ATTR_CIDR] = { + .type = MNL_TYPE_U8, + .opt = IPSET_OPT_CIDR, + }, + [IPSET_ATTR_PORT] = { + .type = MNL_TYPE_U16, + .opt = IPSET_OPT_PORT, + }, + [IPSET_ATTR_PORT_TO] = { + .type = MNL_TYPE_U16, + .opt = IPSET_OPT_PORT_TO, + }, + [IPSET_ATTR_PROTO] = { + .type = MNL_TYPE_U8, + .opt = IPSET_OPT_PROTO, + }, + [IPSET_ATTR_TIMEOUT] = { + .type = MNL_TYPE_U32, + .opt = IPSET_OPT_TIMEOUT, + }, + [IPSET_ATTR_CADT_FLAGS] = { + .type = MNL_TYPE_U32, + .opt = IPSET_OPT_CADT_FLAGS, + }, + [IPSET_ATTR_LINENO] = { + .type = MNL_TYPE_U32, + .opt = IPSET_OPT_LINENO, + }, + [IPSET_ATTR_ETHER] = { + .type = MNL_TYPE_BINARY, + .opt = IPSET_OPT_ETHER, + .len = ETH_ALEN, + }, + [IPSET_ATTR_NAME] = { + .type = MNL_TYPE_NUL_STRING, + .opt = IPSET_OPT_NAME, + .len = IPSET_MAXNAMELEN, + }, + [IPSET_ATTR_NAMEREF] = { + .type = MNL_TYPE_NUL_STRING, + .opt = IPSET_OPT_NAMEREF, + .len = IPSET_MAXNAMELEN, + }, + [IPSET_ATTR_IP2] = { + .type = MNL_TYPE_NESTED, + .opt = IPSET_OPT_IP2, + }, + [IPSET_ATTR_CIDR2] = { + .type = MNL_TYPE_U8, + .opt = IPSET_OPT_CIDR2, + }, +}; + +static const struct ipset_attr_policy ipaddr_attrs[] = { + [IPSET_ATTR_IPADDR_IPV4] = { + .type = MNL_TYPE_U32, + }, + [IPSET_ATTR_IPADDR_IPV6] = { + .type = MNL_TYPE_BINARY, + .len = sizeof(union nf_inet_addr), + }, +}; + +static int +generic_data_attr_cb(const struct nlattr *attr, void *data, + int attr_max, const struct ipset_attr_policy *policy) +{ + const struct nlattr **tb = data; + int type = mnl_attr_get_type(attr); + + D("attr type: %u, len %u", type, attr->nla_len); + if (mnl_attr_type_valid(attr, attr_max) < 0) { + D("attr type: %u INVALID", type); + return MNL_CB_ERROR; + } + if (mnl_attr_validate(attr, policy[type].type) < 0) { + D("attr type: %u POLICY, attrlen %u", type, + mnl_attr_get_payload_len(attr)); + return MNL_CB_ERROR; + } + if (policy[type].type == MNL_TYPE_NUL_STRING + && mnl_attr_get_payload_len(attr) > IPSET_MAXNAMELEN) + return MNL_CB_ERROR; + tb[type] = attr; + return MNL_CB_OK; +} + +static int +create_attr_cb(const struct nlattr *attr, void *data) +{ + return generic_data_attr_cb(attr, data, + IPSET_ATTR_CREATE_MAX, create_attrs); +} + +static int +adt_attr_cb(const struct nlattr *attr, void *data) +{ + return generic_data_attr_cb(attr, data, + IPSET_ATTR_ADT_MAX, adt_attrs); +} + +static int +ipaddr_attr_cb(const struct nlattr *attr, void *data) +{ + return generic_data_attr_cb(attr, data, + IPSET_ATTR_IPADDR_MAX, ipaddr_attrs); +} + +#define FAILURE(format, args...) \ + { ipset_err(session, format , ## args); return MNL_CB_ERROR; } + +static int +attr2data(struct ipset_session *session, struct nlattr *nla[], + int type, const struct ipset_attr_policy attrs[]) +{ + struct ipset_data *data = session->data; + const struct ipset_attr_policy *attr; + const void *d; + int ret; + + attr = &attrs[type]; + d = mnl_attr_get_payload(nla[type]); + + if (attr->type == MNL_TYPE_NESTED && attr->opt) { + /* IP addresses */ + struct nlattr *ipattr[IPSET_ATTR_IPADDR_MAX+1] = {}; + uint8_t family = ipset_data_family(data); + int atype; + D("attr type %u", type); + if (mnl_attr_parse_nested(nla[type], + ipaddr_attr_cb, ipattr) < 0) + FAILURE("Broken kernel message, cannot validate " + "IP address attribute!"); + + /* Validate by hand */ + switch (family) { + case AF_INET: + atype = IPSET_ATTR_IPADDR_IPV4; + if (!ipattr[atype]) + FAILURE("Broken kernel message: IPv4 address " + "expected but not received!"); + if (ipattr[atype]->nla_len < sizeof(uint32_t)) + FAILURE("Broken kernel message: " + "cannot validate IPv4 " + "address attribute!"); + break; + case AF_INET6: + atype = IPSET_ATTR_IPADDR_IPV6; + if (!ipattr[atype]) + FAILURE("Broken kernel message: IPv6 address " + "expected but not received!"); + if (ipattr[atype]->nla_len < sizeof(struct in6_addr)) + FAILURE("Broken kernel message: " + "cannot validate IPv6 " + "address attribute!"); + break; + default: + FAILURE("Broken kernel message: " + "IP address attribute but " + "family is unspecified!"); + } + d = mnl_attr_get_payload(ipattr[atype]); + } else if (nla[type]->nla_type & NLA_F_NET_BYTEORDER) { + switch (attr->type) { + case MNL_TYPE_U32: { + uint32_t value; + + value = ntohl(*(const uint32_t *)d); + + d = &value; + break; + } + case MNL_TYPE_U16: { + uint16_t value; + + value = ntohs(*(const uint16_t *)d); + + d = &value; + break; + } + default: + break; + } + } +#ifdef IPSET_DEBUG + if (type == IPSET_ATTR_TYPENAME) + D("nla typename %s", (const char *) d); +#endif + ret = ipset_data_set(data, attr->opt, d); +#ifdef IPSET_DEBUG + if (type == IPSET_ATTR_TYPENAME) + D("nla typename %s", + (const char *) ipset_data_get(data, IPSET_OPT_TYPENAME)); +#endif + return ret; +} + +#define ATTR2DATA(session, nla, type, attrs) \ + if (attr2data(session, nla, type, attrs) < 0) \ + return MNL_CB_ERROR + +static const char cmd2name[][9] = { + [IPSET_CMD_NONE] = "NONE", + [IPSET_CMD_CREATE] = "CREATE", + [IPSET_CMD_DESTROY] = "DESTROY", + [IPSET_CMD_FLUSH] = "FLUSH", + [IPSET_CMD_RENAME] = "RENAME", + [IPSET_CMD_SWAP] = "SWAP", + [IPSET_CMD_LIST] = "LIST", + [IPSET_CMD_SAVE] = "SAVE", + [IPSET_CMD_ADD] = "ADD", + [IPSET_CMD_DEL] = "DEL", + [IPSET_CMD_TEST] = "TEST", + [IPSET_CMD_HEADER] = "HEADER", + [IPSET_CMD_TYPE] = "TYPE", + [IPSET_CMD_PROTOCOL] = "PROTOCOL", +}; + +static inline int +call_outfn(struct ipset_session *session) +{ + int ret = session->outfn("%s", session->outbuf); + + session->outbuf[0] = '\0'; + + return ret < 0 ? ret : 0; +} + +/* Handle printing failures */ +static jmp_buf printf_failure; + +static int __attribute__((format(printf,2,3))) +safe_snprintf(struct ipset_session *session, const char *fmt, ...) +{ + va_list args; + int len, ret, loop = 0; + +retry: + len = strlen(session->outbuf); + D("len: %u, retry %u", len, loop); + va_start(args, fmt); + ret = vsnprintf(session->outbuf + len, IPSET_OUTBUFLEN - len, + fmt, args); + va_end(args); + + if (ret < 0) { + ipset_err(session, + "Internal error at printing to output buffer"); + longjmp(printf_failure, 1); + } + + if (ret >= IPSET_OUTBUFLEN - len) { + /* Buffer was too small, push it out and retry */ + D("print buffer and try again: %u", len); + if (loop++) { + ipset_err(session, + "Internal error at printing, loop detected!"); + longjmp(printf_failure, 1); + } + + session->outbuf[len] = '\0'; + if (!call_outfn(session)) + goto retry; + } + return ret; +} + +static int +safe_dprintf(struct ipset_session *session, ipset_printfn fn, + enum ipset_opt opt) +{ + int len, ret, loop = 0; + +retry: + len = strlen(session->outbuf); + D("len: %u, retry %u", len, loop); + ret = fn(session->outbuf + len, IPSET_OUTBUFLEN - len, + session->data, opt, session->envopts); + + if (ret < 0) { + ipset_err(session, + "Internal error at printing to output buffer"); + longjmp(printf_failure, 1); + } + + if (ret >= IPSET_OUTBUFLEN - len) { + /* Buffer was too small, push it out and retry */ + D("print buffer and try again: %u", len); + if (loop++) { + ipset_err(session, + "Internal error at printing, loop detected!"); + longjmp(printf_failure, 1); + } + + session->outbuf[len] = '\0'; + if (!call_outfn(session)) + goto retry; + } + return ret; +} + +static int +list_adt(struct ipset_session *session, struct nlattr *nla[]) +{ + const struct ipset_data *data = session->data; + const struct ipset_type *type; + const struct ipset_arg *arg; + uint8_t family; + int i, found = 0; + + D("enter"); + /* Check and load type, family */ + if (!ipset_data_test(data, IPSET_OPT_TYPE)) + type = ipset_type_get(session, IPSET_CMD_ADD); + else + type = ipset_data_get(data, IPSET_OPT_TYPE); + + if (type == NULL) + return MNL_CB_ERROR; + family = ipset_data_family(data); + + for (i = IPSET_ATTR_UNSPEC + 1; i <= IPSET_ATTR_ADT_MAX; i++) + if (nla[i]) { + found++; + ATTR2DATA(session, nla, i, adt_attrs); + } + D("attr found %u", found); + if (!found) + return MNL_CB_OK; + + switch (session->mode) { + case IPSET_LIST_SAVE: + safe_snprintf(session, "add %s ", ipset_data_setname(data)); + break; + case IPSET_LIST_XML: + safe_snprintf(session, " "); + break; + case IPSET_LIST_PLAIN: + default: + break; + } + + safe_dprintf(session, ipset_print_elem, IPSET_OPT_ELEM); + + for (arg = type->args[IPSET_ADD]; arg != NULL && arg->print; arg++) { + if (!ipset_data_test(data, arg->opt)) + continue; + switch (session->mode) { + case IPSET_LIST_SAVE: + case IPSET_LIST_PLAIN: + safe_snprintf(session, " %s ", arg->name[0]); + if (arg->has_arg == IPSET_NO_ARG) + break; + safe_dprintf(session, arg->print, arg->opt); + break; + case IPSET_LIST_XML: + if (arg->has_arg == IPSET_NO_ARG) { + safe_snprintf(session, + " <%s/>\n", + arg->name[0]); + break; + } + safe_snprintf(session, " <%s>", + arg->name[0]); + safe_dprintf(session, arg->print, arg->opt); + safe_snprintf(session, "\n", + arg->name[0]); + break; + default: + break; + } + } + + if (session->mode == IPSET_LIST_XML) + safe_snprintf(session, "\n"); + else + safe_snprintf(session, "\n"); + + return MNL_CB_OK; +} + +#define FAMILY_TO_STR(f) \ + ((f) == AF_INET ? "inet" : \ + (f) == AF_INET6 ? "inet6" : "any") + +static int +list_create(struct ipset_session *session, struct nlattr *nla[]) +{ + const struct ipset_data *data = session->data; + const struct ipset_type *type; + const struct ipset_arg *arg; + uint8_t family; + int i; + + for (i = IPSET_ATTR_UNSPEC + 1; i <= IPSET_ATTR_CREATE_MAX; i++) + if (nla[i]) { + D("add attr %u, opt %u", i, create_attrs[i].opt); + ATTR2DATA(session, nla, i, create_attrs); + } + + type = ipset_type_check(session); + if (type == NULL) + return MNL_CB_ERROR; + family = ipset_data_family(data); + + switch (session->mode) { + case IPSET_LIST_SAVE: + safe_snprintf(session, "create %s %s ", + ipset_data_setname(data), + type->name); + break; + case IPSET_LIST_PLAIN: + safe_snprintf(session, "Name: %s\n" + "Type: %s\nHeader: ", + ipset_data_setname(data), + type->name); + break; + case IPSET_LIST_XML: + safe_snprintf(session, + "\n" + " %s\n" + "
\n", + ipset_data_setname(data), + type->name); + break; + default: + break; + } + + for (arg = type->args[IPSET_CREATE]; arg != NULL && arg->opt; arg++) { + if (!arg->print + || !ipset_data_test(data, arg->opt) + || (arg->opt == IPSET_OPT_FAMILY + && family == type->family)) + continue; + switch (session->mode) { + case IPSET_LIST_SAVE: + case IPSET_LIST_PLAIN: + safe_snprintf(session, "%s ", arg->name[0]); + if (arg->has_arg == IPSET_NO_ARG) + break; + safe_dprintf(session, arg->print, arg->opt); + safe_snprintf(session, " "); + break; + case IPSET_LIST_XML: + if (arg->has_arg == IPSET_NO_ARG) { + safe_snprintf(session, + " <%s/>\n", + arg->name[0]); + break; + } + safe_snprintf(session, " <%s>", + arg->name[0]); + safe_dprintf(session, arg->print, arg->opt); + safe_snprintf(session, "\n", + arg->name[0]); + break; + default: + break; + } + } + switch (session->mode) { + case IPSET_LIST_SAVE: + safe_snprintf(session, "\n"); + break; + case IPSET_LIST_PLAIN: + safe_snprintf(session, "\nSize in memory: "); + safe_dprintf(session, ipset_print_number, IPSET_OPT_MEMSIZE); + safe_snprintf(session, "\nReferences: "); + safe_dprintf(session, ipset_print_number, IPSET_OPT_REFERENCES); + safe_snprintf(session, "\nMembers:\n"); + break; + case IPSET_LIST_XML: + safe_snprintf(session, "\n "); + safe_dprintf(session, ipset_print_number, IPSET_OPT_MEMSIZE); + safe_snprintf(session, "\n "); + safe_dprintf(session, ipset_print_number, IPSET_OPT_REFERENCES); + safe_snprintf(session, "\n
\n \n"); + break; + default: + break; + } + + return MNL_CB_OK; +} + +static int +print_set_done(struct ipset_session *session) +{ + D("called for %s", session->saved_setname[0] == '\0' + ? "NONE" : session->saved_setname); + switch (session->mode) { + case IPSET_LIST_XML: + if (session->saved_setname[0] == '\0') + safe_snprintf(session, "\n"); + else + safe_snprintf(session, " \n
\n"); + break; + case IPSET_LIST_SAVE: + /* No empty lines between the sets */ + break; + default: + safe_snprintf(session, "\n"); + break; + } + return call_outfn(session) ? MNL_CB_ERROR : MNL_CB_STOP; +} + +static int +callback_list(struct ipset_session *session, struct nlattr *nla[], + enum ipset_cmd cmd) +{ + struct ipset_data *data = session->data; + + if (setjmp(printf_failure)) + return MNL_CB_ERROR; + + if (!nla[IPSET_ATTR_SETNAME]) + FAILURE("Broken %s kernel message: missing setname!", + cmd2name[cmd]); + + ATTR2DATA(session, nla, IPSET_ATTR_SETNAME, cmd_attrs); + D("setname %s", ipset_data_setname(data)); + if (STREQ(ipset_data_setname(data), session->saved_setname)) { + /* Header part already seen */ + if (ipset_data_test(data, IPSET_OPT_TYPE) + && nla[IPSET_ATTR_DATA] != NULL) + FAILURE("Broken %s kernel message: " + "extra DATA received!", cmd2name[cmd]); + } else { + if (nla[IPSET_ATTR_DATA] == NULL) + FAILURE("Broken %s kernel message: " + "missing DATA part!", cmd2name[cmd]); + + /* Close previous set printing */ + if (session->saved_setname[0] != '\0') + print_set_done(session); + } + + if (nla[IPSET_ATTR_DATA] != NULL) { + struct nlattr *cattr[IPSET_ATTR_CREATE_MAX+1] = {}; + + if (!(nla[IPSET_ATTR_TYPENAME] + && nla[IPSET_ATTR_FAMILY] + && nla[IPSET_ATTR_REVISION])) + FAILURE("Broken %s kernel message: missing %s!", + cmd2name[cmd], + !nla[IPSET_ATTR_TYPENAME] ? "typename" : + !nla[IPSET_ATTR_FAMILY] ? "family" : "revision"); + + /* Reset CREATE specific flags */ + ipset_data_flags_unset(data, IPSET_CREATE_FLAGS); + D("nla typename %s", + (char *) mnl_attr_get_payload(nla[IPSET_ATTR_TYPENAME])); + D("nla typename %s", + (char *) mnl_attr_get_payload(nla[IPSET_ATTR_TYPENAME])); + ATTR2DATA(session, nla, IPSET_ATTR_FAMILY, cmd_attrs); + ATTR2DATA(session, nla, IPSET_ATTR_TYPENAME, cmd_attrs); + ATTR2DATA(session, nla, IPSET_ATTR_REVISION, cmd_attrs); + D("head: family %u, typename %s", + ipset_data_family(data), + (const char *) ipset_data_get(data, IPSET_OPT_TYPENAME)); + if (mnl_attr_parse_nested(nla[IPSET_ATTR_DATA], + create_attr_cb, cattr) < 0) + FAILURE("Broken %s kernel message: " + "cannot validate DATA attributes!", + cmd2name[cmd]); + if (list_create(session, cattr) != MNL_CB_OK) + return MNL_CB_ERROR; + strcpy(session->saved_setname, ipset_data_setname(data)); + } + + if (nla[IPSET_ATTR_ADT] != NULL) { + struct nlattr *tb, *adt[IPSET_ATTR_ADT_MAX+1]; + + mnl_attr_for_each_nested(tb, nla[IPSET_ATTR_ADT]) { + D("ADT attributes for %s", ipset_data_setname(data)); + memset(adt, 0, sizeof(adt)); + /* Reset ADT specific flags */ + ipset_data_flags_unset(data, IPSET_ADT_FLAGS); + if (mnl_attr_parse_nested(tb, adt_attr_cb, adt) < 0) + FAILURE("Broken %s kernel message: " + "cannot validate ADT attributes!", + cmd2name[cmd]); + if (list_adt(session, adt) != MNL_CB_OK) + return MNL_CB_ERROR; + } + } + return call_outfn(session) ? MNL_CB_ERROR : MNL_CB_OK; +} + +#ifndef IPSET_PROTOCOL_MIN +#define IPSET_PROTOCOL_MIN IPSET_PROTOCOL +#endif + +#ifndef IPSET_PROTOCOL_MAX +#define IPSET_PROTOCOL_MAX IPSET_PROTOCOL +#endif + +static int +callback_version(struct ipset_session *session, struct nlattr *nla[]) +{ + uint8_t min, max; + + min = max = mnl_attr_get_u8(nla[IPSET_ATTR_PROTOCOL]); + + if (nla[IPSET_ATTR_PROTOCOL_MIN]) { + min = mnl_attr_get_u8(nla[IPSET_ATTR_PROTOCOL_MIN]); + D("min: %u", min); + } + + if (min > IPSET_PROTOCOL_MAX || max < IPSET_PROTOCOL_MIN) + FAILURE("Cannot communicate with kernel: " + "Kernel support protocol versions %u-%u " + "while userspace supports protocol versions %u-%u", + min, max, IPSET_PROTOCOL_MIN, IPSET_PROTOCOL_MAX); + + if (!(session->envopts & IPSET_ENV_QUIET) + && max != IPSET_PROTOCOL_MAX) + ipset_warn(session, + "Kernel support protocol versions %u-%u " + "while userspace supports protocol versions %u-%u", + min, max, IPSET_PROTOCOL_MIN, IPSET_PROTOCOL_MAX); + + session->version_checked = true; + + return MNL_CB_STOP; +} + +static int +callback_header(struct ipset_session *session, struct nlattr *nla[]) +{ + const char *setname; + const struct ipset_data *data = session->data; + + if (!nla[IPSET_ATTR_SETNAME]) + FAILURE("Broken HEADER kernel message: missing setname!"); + + setname = mnl_attr_get_str(nla[IPSET_ATTR_SETNAME]); + if (!STREQ(setname, ipset_data_setname(data))) + FAILURE("Broken HEADER kernel message: sent setname `%s' " + "does not match with received one `%s'!", + ipset_data_setname(data), setname); + + if (!(nla[IPSET_ATTR_TYPENAME] + && nla[IPSET_ATTR_REVISION] + && nla[IPSET_ATTR_FAMILY])) + FAILURE("Broken HEADER kernel message: " + "missing attribute '%s'!", + !nla[IPSET_ATTR_TYPENAME] ? "typename" : + !nla[IPSET_ATTR_REVISION] ? "revision" : + "family"); + + ATTR2DATA(session, nla, IPSET_ATTR_TYPENAME, cmd_attrs); + ATTR2DATA(session, nla, IPSET_ATTR_REVISION, cmd_attrs); + ATTR2DATA(session, nla, IPSET_ATTR_FAMILY, cmd_attrs); + D("got family: %u", ipset_data_family(session->data)); + + return MNL_CB_STOP; +} + +static int +callback_type(struct ipset_session *session, struct nlattr *nla[]) +{ + const struct ipset_data *data = session->data; + const char *typename, *orig; + + if (!(nla[IPSET_ATTR_TYPENAME] + && nla[IPSET_ATTR_REVISION] + && nla[IPSET_ATTR_FAMILY])) + FAILURE("Broken TYPE kernel message: " + "missing attribute '%s'!", + !nla[IPSET_ATTR_TYPENAME] ? "typename" : + !nla[IPSET_ATTR_REVISION] ? "revision" : + "family"); + + typename = mnl_attr_get_str(nla[IPSET_ATTR_TYPENAME]); + orig = ipset_data_get(data, IPSET_OPT_TYPENAME); + if (!STREQ(typename, orig)) + FAILURE("Broken TYPE kernel message: sent typename `%s' " + "does not match with received one `%s'!", + orig, typename); + + ATTR2DATA(session, nla, IPSET_ATTR_TYPENAME, cmd_attrs); + ATTR2DATA(session, nla, IPSET_ATTR_REVISION, cmd_attrs); + ATTR2DATA(session, nla, IPSET_ATTR_FAMILY, cmd_attrs); + if (nla[IPSET_ATTR_REVISION_MIN]) + ATTR2DATA(session, nla, IPSET_ATTR_REVISION_MIN, cmd_attrs); + + return MNL_CB_STOP; +} + +static int +cmd_attr_cb(const struct nlattr *attr, void *data) +{ + return generic_data_attr_cb(attr, data, IPSET_ATTR_CMD_MAX, cmd_attrs); +} + +#if 0 +static int +mnl_attr_parse_dbg(const struct nlmsghdr *nlh, int offset, + mnl_attr_cb_t cb, void *data) +{ + int ret = MNL_CB_OK; + struct nlattr *attr = mnl_nlmsg_get_payload_offset(nlh, offset); + int len = nlh->nlmsg_len - MNL_NLMSG_HDRLEN - MNL_ALIGN(offset); + + while (mnl_attr_ok(attr, len)) { + D("attr: type %u, attrlen %u, len %u", + mnl_attr_get_type(attr), attr->nla_len, len); + if (cb && (ret = cb(attr, data)) <= MNL_CB_STOP) + return ret; + attr = mnl_attr_next(attr, &len); + } + return ret; +} +#endif + +static int +callback_data(const struct nlmsghdr *nlh, void *data) +{ + struct ipset_session *session = data; + struct nlattr *nla[IPSET_ATTR_CMD_MAX+1] = {}; + uint8_t proto, cmd; + int ret = MNL_CB_OK, nfmsglen = MNL_ALIGN(sizeof(struct genlmsghdr)); + + D("called, nlmsg_len %u", nlh->nlmsg_len); + cmd = ipset_get_nlmsg_type(nlh); + if (cmd == IPSET_CMD_LIST && session->cmd == IPSET_CMD_SAVE) + /* Kernel always send IPSET_CMD_LIST */ + cmd = IPSET_CMD_SAVE; + + if (cmd != session->cmd) + FAILURE("Protocol error, we sent command %s " + "and received %s[%u]", + cmd2name[session->cmd], + cmd < IPSET_MSG_MAX ? cmd2name[cmd] : "unknown", cmd); + + if (mnl_attr_parse(nlh, nfmsglen, cmd_attr_cb, nla) < MNL_CB_STOP) + FAILURE("Broken %s kernel message: " + "cannot validate and parse attributes", + cmd2name[cmd]); + + if (!nla[IPSET_ATTR_PROTOCOL]) + FAILURE("Sad, sad day: kernel message %s " + "does not carry the protocol version.", + cmd2name[cmd]); + + proto = mnl_attr_get_u8(nla[IPSET_ATTR_PROTOCOL]); + + /* Check protocol */ + if (cmd != IPSET_CMD_PROTOCOL && proto != IPSET_PROTOCOL) + FAILURE("Giving up: kernel protocol version %u " + "does not match our protocol version %u", + proto, IPSET_PROTOCOL); + + D("Message: %s", cmd2name[cmd]); + switch (cmd) { + case IPSET_CMD_LIST: + case IPSET_CMD_SAVE: + ret = callback_list(session, nla, cmd); + D("flag multi: %u", nlh->nlmsg_flags & NLM_F_MULTI); + if (ret >= MNL_CB_STOP && !(nlh->nlmsg_flags & NLM_F_MULTI)) + ret = print_set_done(session); + break; + case IPSET_CMD_PROTOCOL: + if (!session->version_checked) + ret = callback_version(session, nla); + break; + case IPSET_CMD_HEADER: + ret = callback_header(session, nla); + break; + case IPSET_CMD_TYPE: + ret = callback_type(session, nla); + break; + default: + FAILURE("Data message received when not expected at %s", + cmd2name[session->cmd]); + } + D("return code: %s", ret == MNL_CB_STOP ? "stop" : + ret == MNL_CB_OK ? "ok" : "error"); + return ret; +} + +static int +callback_done(const struct nlmsghdr *nlh UNUSED, void *data) +{ + struct ipset_session *session = data; + + D(" called"); + if (session->cmd == IPSET_CMD_LIST || session->cmd == IPSET_CMD_SAVE) + return print_set_done(session); + + FAILURE("Invalid message received in non LIST or SAVE state."); +} + +static int +decode_errmsg(struct ipset_session *session, const struct nlmsghdr *nlh) +{ + const struct nlmsgerr *err = mnl_nlmsg_get_payload(nlh); + const struct nlmsghdr *msg = &err->msg; + struct nlattr *nla[IPSET_ATTR_CMD_MAX+1] = {}; + enum ipset_cmd cmd; + int nfmsglen = MNL_ALIGN(sizeof(struct genlmsghdr)); + + if (nlh->nlmsg_len < (uint32_t) MNL_ALIGN(sizeof(struct nlmsgerr)) + || nlh->nlmsg_len < MNL_ALIGN(sizeof(struct nlmsgerr)) + + msg->nlmsg_len) + FAILURE("Broken error report message received."); + + cmd = ipset_get_nlmsg_type(msg); + D("nlsmg_len: %u", msg->nlmsg_len); + if (cmd != session->cmd) + FAILURE("Protocol error, we sent command %s " + "and received error report for %s[%u]", + cmd2name[session->cmd], + cmd < IPSET_MSG_MAX ? cmd2name[cmd] : "unknown", cmd); + + if (mnl_attr_parse(msg, nfmsglen, cmd_attr_cb, nla) < MNL_CB_STOP) + FAILURE("Broken %s error report message: " + "cannot validate attributes", + cmd2name[cmd]); + + if (!nla[IPSET_ATTR_PROTOCOL]) + FAILURE("Broken %s error report message: " + "missing protocol attribute", + cmd2name[cmd]); + + if (nla[IPSET_ATTR_LINENO]) { + session->lineno = mnl_attr_get_u32(nla[IPSET_ATTR_LINENO]); + if (nla[IPSET_ATTR_LINENO]->nla_type & NLA_F_NET_BYTEORDER) + session->lineno = ntohl(session->lineno); + } + + return ipset_errcode(session, cmd, -err->error); +} + +static int +callback_error(const struct nlmsghdr *nlh, void *cbdata) +{ + struct ipset_session *session = cbdata; + struct ipset_data *data = session->data; + const struct nlmsgerr *err = mnl_nlmsg_get_payload(nlh); + int ret = MNL_CB_ERROR; + + D(" called, cmd %s", cmd2name[session->cmd]); + if (nlh->nlmsg_len < mnl_nlmsg_size(sizeof(struct nlmsgerr))) + FAILURE("Broken error message received."); + + if (err->error == 0) { + /* ACK */ + ret = MNL_CB_STOP; + + switch (session->cmd) { + case IPSET_CMD_CREATE: + /* Add successfully created set to the cache */ + ipset_cache_add(ipset_data_setname(data), + ipset_data_get(data, IPSET_OPT_TYPE), + ipset_data_family(data)); + break; + case IPSET_CMD_DESTROY: + /* Delete destroyed sets from the cache */ + ipset_cache_del(ipset_data_setname(data)); + /* Fall through */ + case IPSET_CMD_FLUSH: + break; + case IPSET_CMD_RENAME: + ipset_cache_rename(ipset_data_setname(data), + ipset_data_get(data, IPSET_OPT_SETNAME2)); + break; + case IPSET_CMD_SWAP: + ipset_cache_swap(ipset_data_setname(data), + ipset_data_get(data, IPSET_OPT_SETNAME2)); + break; + case IPSET_CMD_TEST: + if (!(session->envopts & IPSET_ENV_QUIET)) { + ipset_print_elem(session->report, IPSET_ERRORBUFLEN, + session->data, IPSET_OPT_NONE, 0); + ipset_warn(session, " is in set %s.", + ipset_data_setname(data)); + } + /* Fall through */ + case IPSET_CMD_ADD: + case IPSET_CMD_DEL: + break; + case IPSET_CMD_LIST: + case IPSET_CMD_SAVE: + /* No set in kernel */ + print_set_done(session); + break; + default: + FAILURE("ACK message received to command %s[%u], which is not expected", + session->cmd < IPSET_MSG_MAX + ? cmd2name[session->cmd] : "unknown", + session->cmd); + } + return ret; + } + D("nlmsgerr error: %u", -err->error); + + /* Error messages */ + + /* Special case for IPSET_CMD_TEST */ + if (session->cmd == IPSET_CMD_TEST + && err->error == -IPSET_ERR_EXIST) { + if (!(session->envopts & IPSET_ENV_QUIET)) { + ipset_print_elem(session->report, IPSET_ERRORBUFLEN, + session->data, IPSET_OPT_NONE, 0); + ipset_warn(session, " is NOT in set %s.", + ipset_data_setname(data)); + } + return ret; + } + + decode_errmsg(session, nlh); + + return ret; +} + +static int +callback_noop(const struct nlmsghdr *nlh UNUSED, void *data UNUSED) +{ + return MNL_CB_OK; +} +/* + * Build and send messages + */ + +static inline int +open_nested(struct ipset_session *session, struct nlmsghdr *nlh, int attr) +{ + if (nlh->nlmsg_len + MNL_ATTR_HDRLEN > session->bufsize) + return 1; + session->nested[session->nestid++] = mnl_attr_nest_start(nlh, attr); + return 0; +} + +static inline void +close_nested(struct ipset_session *session, struct nlmsghdr *nlh) +{ + mnl_attr_nest_end(nlh, session->nested[session->nestid-1]); + session->nested[--session->nestid] = NULL; +} + +static size_t +attr_len(const struct ipset_attr_policy *attr, uint8_t family, uint16_t *flags) +{ + switch (attr->type) { + case MNL_TYPE_NESTED: + if (attr->len) + return attr->len; + + *flags = NLA_F_NET_BYTEORDER; + return family == AF_INET ? sizeof(uint32_t) + : sizeof(struct in6_addr); + case MNL_TYPE_U32: + *flags = NLA_F_NET_BYTEORDER; + return sizeof(uint32_t); + case MNL_TYPE_U16: + *flags = NLA_F_NET_BYTEORDER; + return sizeof(uint16_t); + case MNL_TYPE_U8: + return sizeof(uint8_t); + default: + return attr->len; + } +} + +#define BUFFER_FULL(bufsize, nlmsg_len, nestlen, attrlen) \ +(nlmsg_len + nestlen + MNL_ATTR_HDRLEN + MNL_ALIGN(alen) + MNL_ALIGN(sizeof(struct nlmsgerr)) > bufsize) + +static int +rawdata2attr(struct ipset_session *session, struct nlmsghdr *nlh, + const void *d, int type, uint8_t family, + const struct ipset_attr_policy attrs[]) +{ + const struct ipset_attr_policy *attr; + int alen; + uint16_t flags = 0; + + + attr = &attrs[type]; + if (attr->type == MNL_TYPE_NESTED) { + /* IP addresses */ + struct nlattr *nested; + int atype = family == AF_INET ? IPSET_ATTR_IPADDR_IPV4 + : IPSET_ATTR_IPADDR_IPV6; + + alen = attr_len(attr, family, &flags); + if (BUFFER_FULL(session->bufsize, nlh->nlmsg_len, MNL_ATTR_HDRLEN, alen)) + return 1; + nested = mnl_attr_nest_start(nlh, type); + D("family: %s", family == AF_INET ? "INET" : + family == AF_INET6 ? "INET6" : "UNSPEC"); + mnl_attr_put(nlh, atype | flags, alen, d); + mnl_attr_nest_end(nlh, nested); + + return 0; + } + + alen = attr_len(attr, family, &flags); + if (BUFFER_FULL(session->bufsize, nlh->nlmsg_len, 0, alen)) + return 1; + + switch (attr->type) { + case MNL_TYPE_U32: { + uint32_t value = htonl(*(const uint32_t *)d); + + d = &value; + break; + } + case MNL_TYPE_U16: { + uint16_t value = htons(*(const uint16_t *)d); + + d = &value; + break; + } + default: + break; + } + + mnl_attr_put(nlh, type | flags, alen, d); + + return 0; +} + +static int +data2attr(struct ipset_session *session, struct nlmsghdr *nlh, + struct ipset_data *data, int type, uint8_t family, + const struct ipset_attr_policy attrs[]) +{ + const struct ipset_attr_policy *attr = &attrs[type]; + + return rawdata2attr(session, nlh, ipset_data_get(data, attr->opt), + type, family, attrs); +} + +#define ADDATTR_PROTOCOL(nlh) \ + mnl_attr_put_u8(nlh, IPSET_ATTR_PROTOCOL, IPSET_PROTOCOL) + +#define ADDATTR(session, nlh, data, type, family, attrs) \ + data2attr(session, nlh, data, type, family, attrs) + +#define ADDATTR_SETNAME(session, nlh, data) \ + data2attr(session, nlh, data, IPSET_ATTR_SETNAME, AF_INET, cmd_attrs) + +#define ADDATTR_IF(session, nlh, data, type, family, attrs) \ + ipset_data_test(data, attrs[type].opt) ? \ + data2attr(session, nlh, data, type, family, attrs) : 0 + +#define ADDATTR_RAW(session, nlh, data, type, attrs) \ + rawdata2attr(session, nlh, data, type, AF_INET, attrs) + +static void +addattr_create(struct ipset_session *session, + struct nlmsghdr *nlh, struct ipset_data *data, uint8_t family) +{ + int i; + + for (i = IPSET_ATTR_UNSPEC + 1; i <= IPSET_ATTR_CREATE_MAX; i++) + ADDATTR_IF(session, nlh, data, i, family, create_attrs); +} + +static int +addattr_adt(struct ipset_session *session, + struct nlmsghdr *nlh, struct ipset_data *data, uint8_t family) +{ + int i; + + for (i = IPSET_ATTR_UNSPEC + 1; i <= IPSET_ATTR_ADT_MAX; i++) + if (ADDATTR_IF(session, nlh, data, i, family, adt_attrs)) + return 1; + return 0; +} + +#define PRIVATE_MSG_BUFLEN 256 + +static int +build_send_private_msg(struct ipset_session *session, enum ipset_cmd cmd) +{ + char buffer[PRIVATE_MSG_BUFLEN] __attribute__ ((aligned)); + struct nlmsghdr *nlh = (void *)buffer; + struct ipset_data *data = session->data; + int len = PRIVATE_MSG_BUFLEN, ret; + enum ipset_cmd saved = session->cmd; + + /* Initialize header */ + session->transport->fill_hdr(session->handle, cmd, buffer, len, 0); + + ADDATTR_PROTOCOL(nlh); + + switch (cmd) { + case IPSET_CMD_PROTOCOL: + break; + case IPSET_CMD_HEADER: + if (!ipset_data_test(data, IPSET_SETNAME)) + return ipset_err(session, + "Invalid internal HEADER command: " + "missing setname"); + ADDATTR_SETNAME(session, nlh, data); + break; + case IPSET_CMD_TYPE: + if (!ipset_data_test(data, IPSET_OPT_TYPENAME)) + return ipset_err(session, + "Invalid internal TYPE command: " + "missing settype"); + ADDATTR(session, nlh, data, IPSET_ATTR_TYPENAME, AF_INET, cmd_attrs); + if (ipset_data_test(data, IPSET_OPT_FAMILY)) + ADDATTR(session, nlh, data, IPSET_ATTR_FAMILY, AF_INET, cmd_attrs); + else + /* bitmap:port and list:set types */ + mnl_attr_put_u8(nlh, IPSET_ATTR_FAMILY, AF_UNSPEC); + break; + default: + return ipset_err(session, "Internal error: " + "unknown private command %u", cmd); + } + + /* Backup, then restore real command */ + session->cmd = cmd; + ret = session->transport->query(session->handle, buffer, len); + session->cmd = saved; + + return ret; +} + +static inline bool +may_aggregate_ad(struct ipset_session *session, enum ipset_cmd cmd) +{ + return session->lineno != 0 + && (cmd == IPSET_CMD_ADD || cmd == IPSET_CMD_DEL) + && cmd == session->cmd + && STREQ(ipset_data_setname(session->data), session->saved_setname); +} + +static int +build_msg(struct ipset_session *session, bool aggregate) +{ + struct nlmsghdr *nlh = session->buffer; + struct ipset_data *data = session->data; + + /* Public commands */ + D("cmd %s, nlmsg_len: %u", cmd2name[session->cmd], nlh->nlmsg_len); + if (nlh->nlmsg_len == 0) { + /* Initialize header */ + aggregate = false; + session->transport->fill_hdr(session->handle, + session->cmd, + session->buffer, + session->bufsize, + session->envopts); + ADDATTR_PROTOCOL(nlh); + } + D("Protocol added, aggregate %s", aggregate ? "yes" : "no"); + switch (session->cmd) { + case IPSET_CMD_CREATE: { + const struct ipset_type *type; + + /* Sanity checkings */ + if (!ipset_data_test(data, IPSET_SETNAME)) + return ipset_err(session, + "Invalid create command: missing setname"); + if (!ipset_data_test(data, IPSET_OPT_TYPE)) + return ipset_err(session, + "Invalid create command: missing settype"); + + type = ipset_data_get(data, IPSET_OPT_TYPE); + /* Core attributes: + * setname, typename, revision, family, flags (optional) */ + ADDATTR_SETNAME(session, nlh, data); + ADDATTR(session, nlh, data, IPSET_ATTR_TYPENAME, AF_INET, cmd_attrs); + ADDATTR_RAW(session, nlh, &type->revision, + IPSET_ATTR_REVISION, cmd_attrs); + D("family: %u, type family %u", + ipset_data_family(data), type->family); + ADDATTR(session, nlh, data, IPSET_ATTR_FAMILY, AF_INET, cmd_attrs); + + /* Type-specific create attributes */ + D("call open_nested"); + open_nested(session, nlh, IPSET_ATTR_DATA); + addattr_create(session, nlh, data, type->family); + D("call close_nested"); + close_nested(session, nlh); + break; + } + case IPSET_CMD_DESTROY: + case IPSET_CMD_FLUSH: + case IPSET_CMD_LIST: + case IPSET_CMD_SAVE: + if (ipset_data_test(data, IPSET_SETNAME)) + ADDATTR_SETNAME(session, nlh, data); + break; + case IPSET_CMD_RENAME: + case IPSET_CMD_SWAP: + if (!ipset_data_test(data, IPSET_SETNAME)) + return ipset_err(session, + "Invalid %s command: missing from-setname", + session->cmd == IPSET_CMD_SWAP ? "swap" : "rename"); + if (!ipset_data_test(data, IPSET_OPT_SETNAME2)) + return ipset_err(session, + "Invalid %s command: missing to-setname", + session->cmd == IPSET_CMD_SWAP ? "swap" : "rename"); + ADDATTR_SETNAME(session, nlh, data); + ADDATTR_RAW(session, nlh, ipset_data_get(data, IPSET_OPT_SETNAME2), + IPSET_ATTR_SETNAME2, cmd_attrs); + break; + case IPSET_CMD_ADD: + case IPSET_CMD_DEL: { + const struct ipset_type *type; + + if (!aggregate) { + /* Setname, type not checked/added yet */ + if (!ipset_data_test(data, IPSET_SETNAME)) + return ipset_err(session, + "Invalid %s command: missing setname", + session->cmd == IPSET_CMD_ADD ? "add" : "del"); + + if (!ipset_data_test(data, IPSET_OPT_TYPE)) + return ipset_err(session, + "Invalid %s command: missing settype", + session->cmd == IPSET_CMD_ADD ? "add" : "del"); + + /* Core options: setname */ + ADDATTR_SETNAME(session, nlh, data); + if (session->lineno != 0) { + /* Restore mode */ + ADDATTR_RAW(session, nlh, &session->lineno, + IPSET_ATTR_LINENO, cmd_attrs); + open_nested(session, nlh, IPSET_ATTR_ADT); + } + } + type = ipset_data_get(data, IPSET_OPT_TYPE); + D("family: %u, type family %u", + ipset_data_family(data), type->family); + if (open_nested(session, nlh, IPSET_ATTR_DATA)) { + D("open_nested failed"); + return 1; + } + if (addattr_adt(session, nlh, data, ipset_data_family(data)) + || ADDATTR_RAW(session, nlh, &session->lineno, + IPSET_ATTR_LINENO, cmd_attrs)) { + /* Cancel last, unfinished nested attribute */ + mnl_attr_nest_cancel(nlh, session->nested[session->nestid-1]); + session->nested[--session->nestid] = NULL; + return 1; + } + close_nested(session, nlh); + break; + } + case IPSET_CMD_TEST: { + const struct ipset_type *type; + /* Return codes are not aggregated, so tests cannot be either */ + + /* Setname, type not checked/added yet */ + + if (!ipset_data_test(data, IPSET_SETNAME)) + return ipset_err(session, + "Invalid test command: missing setname"); + + if (!ipset_data_test(data, IPSET_OPT_TYPE)) + return ipset_err(session, + "Invalid test command: missing settype"); + + type = ipset_data_get(data, IPSET_OPT_TYPE); + D("family: %u, type family %u", + ipset_data_family(data), type->family); + ADDATTR_SETNAME(session, nlh, data); + open_nested(session, nlh, IPSET_ATTR_DATA); + addattr_adt(session, nlh, data, ipset_data_family(data)); + close_nested(session, nlh); + break; + } + default: + return ipset_err(session, "Internal error: unknown command %u", + session->cmd); + } + return 0; +} + +/** + * ipset_commit - commit buffered commands + * @session: session structure + * + * Commit buffered commands, if there are any. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_commit(struct ipset_session *session) +{ + struct nlmsghdr *nlh; + int ret = 0, i; + + assert(session); + + nlh = session->buffer; + D("send buffer: len %u, cmd %s", nlh->nlmsg_len, cmd2name[session->cmd]); + if (nlh->nlmsg_len == 0) + /* Nothing to do */ + return 0; + + /* Close nested data blocks */ + for (i = session->nestid - 1; i >= 0; i--) + close_nested(session, nlh); + + /* Send buffer */ + ret = session->transport->query(session->handle, + session->buffer, + session->bufsize); + + /* Reset saved data and nested state */ + session->saved_setname[0] = '\0'; + for (i = session->nestid - 1; i >= 0; i--) + session->nested[i] = NULL; + session->nestid = 0; + nlh->nlmsg_len = 0; + + D("ret: %d", ret); + + if (ret < 0) { + if (session->report[0] != '\0') + return -1; + else + return ipset_err(session, + "Internal protocol error"); + } + return 0; +} + +static mnl_cb_t cb_ctl[] = { + [NLMSG_NOOP] = callback_noop, + [NLMSG_ERROR] = callback_error, + [NLMSG_DONE] = callback_done, + [NLMSG_OVERRUN] = callback_noop, + [NLMSG_MIN_TYPE] = callback_data, +}; + +static inline struct ipset_handle * +init_transport(struct ipset_session *session) +{ + session->handle = session->transport->init(cb_ctl, session); + + return session->handle; +} + +/** + * ipset_cmd - execute a command + * @session: session structure + * @cmd: command to execute + * @lineno: command line number in restore mode + * + * Execute - or prepare/buffer in restore mode - a command. + * It is the caller responsibility that the data field be filled out + * with all required parameters for a successful execution. + * The data field is cleared after this function call for the public + * commands. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_cmd(struct ipset_session *session, enum ipset_cmd cmd, uint32_t lineno) +{ + struct ipset_data *data; + bool aggregate = false; + int ret = -1; + + assert(session); + + if (cmd <= IPSET_CMD_NONE || cmd >= IPSET_MSG_MAX) + return 0; + + /* Initialize transport method if not done yet */ + if (session->handle == NULL && init_transport(session) == NULL) + return ipset_err(session, + "Cannot open session to kernel."); + + data = session->data; + + /* Check protocol version once */ + if (!session->version_checked) { + if (build_send_private_msg(session, IPSET_CMD_PROTOCOL) < 0) + return -1; + } + + /* Private commands */ + if (cmd == IPSET_CMD_TYPE || cmd == IPSET_CMD_HEADER) + return build_send_private_msg(session, cmd); + + /* Check aggregatable commands */ + aggregate = may_aggregate_ad(session, cmd); + if (!aggregate) { + /* Flush possible aggregated commands */ + ret = ipset_commit(session); + if (ret < 0) + return ret; + } + + /* Real command: update lineno too */ + session->cmd = cmd; + session->lineno = lineno; + + /* Set default output mode */ + if (cmd == IPSET_CMD_LIST) { + if (session->mode == IPSET_LIST_NONE) + session->mode = IPSET_LIST_PLAIN; + } else if (cmd == IPSET_CMD_SAVE) { + if (session->mode == IPSET_LIST_NONE) + session->mode = IPSET_LIST_SAVE; + } + + D("next: build_msg"); + /* Build new message or append buffered commands */ + ret = build_msg(session, aggregate); + D("build_msg returned %u", ret); + if (ret > 0) { + /* Buffer is full, send buffered commands */ + ret = ipset_commit(session); + if (ret < 0) + goto cleanup; + ret = build_msg(session, false); + D("build_msg 2 returned %u", ret); + } + if (ret < 0) + goto cleanup; + D("past: build_msg"); + + /* We have to save the type for error handling */ + session->saved_type = ipset_data_get(data, IPSET_OPT_TYPE); + if (session->lineno != 0 + && (cmd == IPSET_CMD_ADD || cmd == IPSET_CMD_DEL)) { + /* Save setname for the next possible aggregated restore line */ + strcpy(session->saved_setname, ipset_data_setname(data)); + ipset_data_reset(data); + /* Don't commit: we may aggregate next command */ + ret = 0; + goto cleanup; + } + + D("call commit"); + ret = ipset_commit(session); + +cleanup: + D("reset data"); + ipset_data_reset(data); + return ret; +} + +/** + * ipset_session_init - initialize an ipset session + * + * Initialize an ipset session by allocating a session structure + * and filling out with the initialization data. + * + * Returns the created session sctructure on success or NULL. + */ +struct ipset_session * +ipset_session_init(ipset_outfn outfn) +{ + struct ipset_session *session; + size_t bufsize = getpagesize(); + + /* Create session object */ + session = calloc(1, sizeof(struct ipset_session) + bufsize); + if (session == NULL) + return NULL; + session->bufsize = bufsize; + session->buffer = session + 1; + + /* The single transport method yet */ + session->transport = &ipset_mnl_transport; + + /* Output function */ + session->outfn = outfn; + + /* Initialize data structures */ + session->data = ipset_data_init(); + if (session->data == NULL) + goto free_session; + + ipset_cache_init(); + return session; + +free_session: + free(session); + return NULL; +} + +/** + * ipset_session_fini - destroy an ipset session + * @session: session structure + * + * Destroy an ipset session: release the created structures. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_session_fini(struct ipset_session *session) +{ + assert(session); + + if (session->handle) + session->transport->fini(session->handle); + if (session->data) + ipset_data_fini(session->data); + + ipset_cache_fini(); + free(session); + return 0; +} diff --git a/extensions/ipset-5/libipset/types.c b/extensions/ipset-5/libipset/types.c new file mode 100644 index 0000000..69dac6a --- /dev/null +++ b/extensions/ipset-5/libipset/types.c @@ -0,0 +1,538 @@ +/* Copyright 2007-2010 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 /* assert */ +#include /* errno */ +#include /* ETH_ALEN */ +#include /* struct in6_addr */ +#include /* AF_ */ +#include /* malloc, free */ +#include /* FIXME: debug */ +#include /* MNL_ALIGN */ + +#include /* D() */ +#include /* ipset_data_* */ +#include /* ipset_cmd */ +#include /* STREQ */ +#include /* prototypes */ + +/* Userspace cache of sets which exists in the kernel */ + +struct ipset { + char name[IPSET_MAXNAMELEN]; /* set name */ + const struct ipset_type *type; /* set type */ + uint8_t family; /* family */ + struct ipset *next; +}; + +static struct ipset_type *typelist = NULL; /* registered set types */ +static struct ipset *setlist = NULL; /* cached sets */ + +/** + * ipset_cache_add - add a set to the cache + * @name: set name + * @type: set type structure + * + * Add the named set to the internal cache with the specified + * set type. The set name must be unique. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_cache_add(const char *name, const struct ipset_type *type, + uint8_t family) +{ + struct ipset *s, *n; + + assert(name); + assert(type); + + n = malloc(sizeof(*n)); + if (n == NULL) + return -ENOMEM; + + ipset_strlcpy(n->name, name, IPSET_MAXNAMELEN); + n->type = type; + n->family = family; + n->next = NULL; + + if (setlist == NULL) { + setlist = n; + return 0; + } + for (s = setlist; s->next != NULL; s = s->next) { + if (STREQ(name, s->name)) { + free(n); + return -EEXIST; + } + } + s->next = n; + + return 0; +} + +/** + * ipset_cache_del - delete set from the cache + * @name: set name + * + * Delete the named set from the internal cache. If NULL is + * specified as setname, the whole cache is emptied. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_cache_del(const char *name) +{ + struct ipset *s, *match = NULL, *prev = NULL; + + if (!name) { + for (s = setlist; s != NULL; ) { + prev = s; + s = s->next; + free(prev); + } + setlist = NULL; + return 0; + } + for (s = setlist; s != NULL && match == NULL; s = s->next) { + if (STREQ(s->name, name)) { + match = s; + if (prev == NULL) + setlist = match->next; + else + prev->next = match->next; + } + prev = s; + } + if (match == NULL) + return -EEXIST; + + free(match); + return 0; +} + +/** + * ipset_cache_rename - rename a set in the cache + * @from: the set to rename + * @to: the new name of the set + * + * Rename the given set in the cache. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_cache_rename(const char *from, const char *to) +{ + struct ipset *s; + + assert(from); + assert(to); + + for (s = setlist; s != NULL; s = s->next) { + if (STREQ(s->name, from)) { + ipset_strlcpy(s->name, to, IPSET_MAXNAMELEN); + return 0; + } + } + return -EEXIST; +} + +/** + * ipset_cache_swap - swap two sets in the cache + * @from: the first set + * @to: the second set + * + * Swap two existing sets in the cache. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_cache_swap(const char *from, const char *to) +{ + struct ipset *s, *a = NULL, *b = NULL; + + assert(from); + assert(to); + + for (s = setlist; s != NULL && (a == NULL || b == NULL); s = s->next) { + if (a == NULL && STREQ(s->name, from)) + a = s; + if (b == NULL && STREQ(s->name, to)) + b = s; + } + if (a != NULL && b != NULL) { + ipset_strlcpy(a->name, to, IPSET_MAXNAMELEN); + ipset_strlcpy(b->name, from, IPSET_MAXNAMELEN); + return 0; + } + + return -EEXIST; +} + +#define MATCH_FAMILY(type, f) \ + (f == AF_UNSPEC || type->family == f || type->family == AF_INET46) + +bool +ipset_match_typename(const char *name, const struct ipset_type *type) +{ + const char * const * alias = type->alias; + + if (STREQ(name, type->name)) + return true; + + while (alias[0]) { + if (STREQ(name, alias[0])) + return true; + alias++; + } + return false; +} + +static inline const struct ipset_type * +create_type_get(struct ipset_session *session) +{ + struct ipset_type *t, *match = NULL; + struct ipset_data *data; + const char *typename; + uint8_t family, tmin = 0, tmax = 0; + const uint8_t *kmin, *kmax; + int ret; + + data = ipset_session_data(session); + assert(data); + typename = ipset_data_get(data, IPSET_OPT_TYPENAME); + assert(typename); + family = ipset_data_family(data); + + /* Check registered types in userspace */ + for (t = typelist; t != NULL; t = t->next) { + /* Skip revisions which are unsupported by the kernel */ + if (t->kernel_check == IPSET_KERNEL_MISMATCH) + continue; + if (ipset_match_typename(typename, t) + && MATCH_FAMILY(t, family)) { + if (match == NULL) { + match = t; + tmax = t->revision; + } else if (t->family == match->family) + tmin = t->revision; + } + } + if (!match) + return ipset_errptr(session, + "Syntax error: unknown settype %s", + typename); + + /* Family is unspecified yet: set from matching set type */ + if (family == AF_UNSPEC && match->family != AF_UNSPEC) { + family = match->family == AF_INET46 ? AF_INET : match->family; + ipset_data_set(data, IPSET_OPT_FAMILY, &family); + } + + if (match->kernel_check == IPSET_KERNEL_OK) + goto found; + + /* Check kernel */ + ret = ipset_cmd(session, IPSET_CMD_TYPE, 0); + if (ret != 0) + return NULL; + + kmax = ipset_data_get(data, IPSET_OPT_REVISION); + if (ipset_data_test(data, IPSET_OPT_REVISION_MIN)) + kmin = ipset_data_get(data, IPSET_OPT_REVISION_MIN); + else + kmin = kmax; + if (MAX(tmin, *kmin) > MIN(tmax, *kmax)) { + if (*kmin > tmax) + return ipset_errptr(session, + "Kernel supports %s type with family %s " + "in minimal revision %u while ipset library " + "in maximal revision %u. " + "You need to upgrade your ipset library.", + typename, + family == AF_INET ? "INET" : + family == AF_INET6 ? "INET6" : "UNSPEC", + *kmin, tmax); + else + return ipset_errptr(session, + "Kernel supports %s type with family %s " + "in maximal revision %u while ipset library " + "in minimal revision %u. " + "You need to upgrade your kernel.", + typename, + family == AF_INET ? "INET" : + family == AF_INET6 ? "INET6" : "UNSPEC", + *kmax, tmin); + } + + match->kernel_check = IPSET_KERNEL_OK; +found: + ipset_data_set(data, IPSET_OPT_TYPE, match); + + return match; +} + +#define set_family_and_type(data, match, family) do { \ + if (family == AF_UNSPEC && match->family != AF_UNSPEC) \ + family = match->family == AF_INET46 ? AF_INET : match->family;\ + ipset_data_set(data, IPSET_OPT_FAMILY, &family); \ + ipset_data_set(data, IPSET_OPT_TYPE, match); \ +} while (0) + + +static inline const struct ipset_type * +adt_type_get(struct ipset_session *session) +{ + struct ipset_data *data; + struct ipset *s; + struct ipset_type *t; + const struct ipset_type *match; + const char *setname, *typename; + const uint8_t *revision; + uint8_t family = AF_UNSPEC; + int ret; + + data = ipset_session_data(session); + assert(data); + setname = ipset_data_setname(data); + assert(setname); + + /* Check existing sets in cache */ + for (s = setlist; s != NULL; s = s->next) { + if (STREQ(setname, s->name)) { + ipset_data_set(data, IPSET_OPT_FAMILY, &s->family); + ipset_data_set(data, IPSET_OPT_TYPE, s->type); + return s->type; + } + } + + /* Check kernel */ + ret = ipset_cmd(session, IPSET_CMD_HEADER, 0); + if (ret != 0) + return NULL; + + typename = ipset_data_get(data, IPSET_OPT_TYPENAME); + revision = ipset_data_get(data, IPSET_OPT_REVISION); + family = ipset_data_family(data); + + /* Check registered types */ + for (t = typelist, match = NULL; + t != NULL && match == NULL; t = t->next) { + if (t->kernel_check == IPSET_KERNEL_MISMATCH) + continue; + if (STREQ(typename, t->name) + && MATCH_FAMILY(t, family) + && *revision == t->revision) { + t->kernel_check = IPSET_KERNEL_OK; + match = t; + } + } + if (!match) + return ipset_errptr(session, + "Kernel-library incompatibility: " + "set %s in kernel has got settype %s " + "with family %s and revision %u while " + "ipset library does not support the " + "settype with that family and revision.", + setname, typename, + family == AF_INET ? "inet" : + family == AF_INET6 ? "inet6" : "unspec", + *revision); + + set_family_and_type(data, match, family); + + return match; +} + +/** + * ipset_type_get - get a set type from the kernel + * @session: session structure + * @cmd: the command which needs the set type + * + * Build up and send a private message to the kernel in order to + * get the set type. When creating the set, we send the typename + * and family and get the supported revisions of the given set type. + * When adding/deleting/testing an entry, we send the setname and + * receive the typename, family and revision. + * + * Returns the set type for success and NULL for failure. + */ +const struct ipset_type * +ipset_type_get(struct ipset_session *session, enum ipset_cmd cmd) +{ + assert(session); + + switch (cmd) { + case IPSET_CMD_CREATE: + return create_type_get(session); + case IPSET_CMD_ADD: + case IPSET_CMD_DEL: + case IPSET_CMD_TEST: + return adt_type_get(session); + default: + break; + } + + assert(cmd == IPSET_CMD_NONE); + return NULL; +} + +/** + * ipset_type_check - check the set type received from kernel + * @session: session structure + * + * Check the set type received from the kernel (typename, revision, + * family) against the userspace types looking for a matching type. + * + * Returns the set type for success and NULL for failure. + */ +const struct ipset_type * +ipset_type_check(struct ipset_session *session) +{ + const struct ipset_type *t, *match = NULL; + struct ipset_data *data; + const char *typename; + uint8_t family = AF_UNSPEC, revision; + + assert(session); + data = ipset_session_data(session); + assert(data); + + typename = ipset_data_get(data, IPSET_OPT_TYPENAME); + family = ipset_data_family(data); + revision = *(const uint8_t *) ipset_data_get(data, IPSET_OPT_REVISION); + + /* Check registered types */ + for (t = typelist; t != NULL && match == NULL; t = t->next) { + if (t->kernel_check == IPSET_KERNEL_MISMATCH) + continue; + if (ipset_match_typename(typename, t) + && MATCH_FAMILY(t, family) + && t->revision == revision) + match = t; + } + if (!match) + return ipset_errptr(session, + "Kernel and userspace incompatible: " + "settype %s with revision %u not supported ", + "by userspace.", typename, revision); + + set_family_and_type(data, match, family); + + return match; +} + +/** + * ipset_type_add - add (register) a userspace set type + * @type: pointer to the set type structure + * + * Add the given set type to the type list. The types + * are added sorted, in descending revision number. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_type_add(struct ipset_type *type) +{ + struct ipset_type *t, *prev; + + assert(type); + + /* Add to the list: higher revision numbers first */ + for (t = typelist, prev = NULL; t != NULL; t = t->next) { + if (STREQ(t->name, type->name)) { + if (t->revision == type->revision) { + errno = EEXIST; + return -1; + } else if (t->revision < type->revision) { + type->next = t; + if (prev) + prev->next = type; + else + typelist = type; + return 0; + } + } + if (t->next != NULL && STREQ(t->next->name, type->name)) { + if (t->next->revision == type->revision) { + errno = EEXIST; + return -1; + } else if (t->next->revision < type->revision) { + type->next = t->next; + t->next = type; + return 0; + } + } + prev = t; + } + type->next = typelist; + typelist = type; + return 0; +} + +/** + * ipset_typename_resolve - resolve typename alias + * @str: typename or alias + * + * Check the typenames (and aliases) and return the + * preferred name of the set type. + * + * Returns the name of the matching set type or NULL. + */ +const char * +ipset_typename_resolve(const char *str) +{ + const struct ipset_type *t; + + for (t = typelist; t != NULL; t = t->next) + if (ipset_match_typename(str, t)) + return t->name; + return NULL; +} + +/** + * ipset_types - return the list of the set types + * + * The types can be unchecked with respect of the running kernel. + * Only useful for type specific help. + * + * Returns the list of the set types. + */ +const struct ipset_type * +ipset_types(void) +{ + return typelist; +} + +/** + * ipset_cache_init - initialize set cache + * + * Initialize the set cache in userspace. + * + * Returns 0 on success or a negative error code. + */ +int +ipset_cache_init(void) +{ + return 0; +} + +/** + * ipset_cache_fini - release the set cache + * + * Release the set cache. + */ +void +ipset_cache_fini(void) +{ + struct ipset *set; + + while (setlist) { + set = setlist; + setlist = setlist->next; + free(set); + } +} diff --git a/extensions/ipset-5/slist.h b/extensions/ipset-5/slist.h new file mode 100644 index 0000000..e472e43 --- /dev/null +++ b/extensions/ipset-5/slist.h @@ -0,0 +1,89 @@ +#ifndef _IP_SET_SLIST_H +#define _IP_SET_SLIST_H + +#include +#include +#include + +/* + * Single linked lists with a single pointer. + * Mostly useful for hash tables where the two pointer list head + * and list node is too wasteful. + */ + +struct slist { + struct slist *next; +}; + +#define SLIST(name) struct slist name = { .next = NULL } +#define INIT_SLIST(ptr) ((ptr)->next = NULL) + +#define slist_entry(ptr, type, member) container_of(ptr, type, member) + +#define slist_for_each(pos, head) \ + for (pos = (head)->next; pos && ({ prefetch(pos->next); 1; }); \ + pos = pos->next) + +#define slist_for_each_prev(prev, pos, head) \ + for (prev = head, pos = (head)->next; \ + pos && ({ prefetch(pos->next); 1; }); \ + prev = pos, pos = pos->next) + +#define slist_for_each_safe(pos, n, head) \ + for (pos = (head)->next; pos && ({ n = pos->next; 1; }); \ + pos = n) + +/** + * slist_for_each_entry - iterate over list of given type + * @tpos: the type * to use as a loop cursor. + * @pos: the &struct slist to use as a loop cursor. + * @head: the head for your list. + * @member: the name of the slist within the struct. + */ +#define slist_for_each_entry(tpos, pos, head, member) \ + for (pos = (head)->next; \ + pos && ({ prefetch(pos->next); 1; }) && \ + ({ tpos = slist_entry(pos, typeof(*tpos), member); 1; });\ + pos = pos->next) + +/** + * slist_for_each_entry_continue - iterate over a hlist continuing + * after current point + * @tpos: the type * to use as a loop cursor. + * @pos: the &struct slist to use as a loop cursor. + * @member: the name of the slist within the struct. + */ +#define slist_for_each_entry_continue(tpos, pos, member) \ + for (pos = (pos)->next; \ + pos && ({ prefetch(pos->next); 1; }) && \ + ({ tpos = slist_entry(pos, typeof(*tpos), member); 1; });\ + pos = pos->next) + +/** + * slist_for_each_entry_from - iterate over a hlist continuing + * from current point + * @tpos: the type * to use as a loop cursor. + * @pos: the &struct slist to use as a loop cursor. + * @member: the name of the slist within the struct. + */ +#define slist_for_each_entry_from(tpos, pos, member) \ + for (; pos && ({ prefetch(pos->next); 1; }) && \ + ({ tpos = slist_entry(pos, typeof(*tpos), member); 1; });\ + pos = pos->next) + +/** + * slist_for_each_entry_safe - iterate over list of given type safe against + * removal of list entry + * @tpos: the type * to use as a loop cursor. + * @pos: the &struct slist to use as a loop cursor. + * @n: another &struct slist to use as temporary storage + * @head: the head for your list. + * @member: the name of the slist within the struct. + */ +#define slist_for_each_entry_safe(tpos, pos, n, head, member) \ + for (pos = (head)->next; \ + pos && ({ n = pos->next; 1; }) && \ + ({ tpos = slist_entry(pos, typeof(*tpos), member); 1; });\ + pos = n) + +#endif /* _IP_SET_SLIST_H */ diff --git a/extensions/ipset-5/src/errcode.c b/extensions/ipset-5/src/errcode.c new file mode 100644 index 0000000..313c500 --- /dev/null +++ b/extensions/ipset-5/src/errcode.c @@ -0,0 +1,194 @@ +/* Copyright 2007-2010 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 /* assert */ +#include /* errno */ +#include /* strerror */ + +#include /* D() */ +#include /* ipset_data_get */ +#include /* ipset_err */ +#include /* struct ipset_type */ +#include /* STRNEQ */ +#include /* prototypes */ +#include /* bitmap specific errcodes */ +#include /* hash specific errcodes */ +#include /* list specific errcodes */ + +/* Core kernel error codes */ +static const struct ipset_errcode_table core_errcode_table[] = { + /* Generic error codes */ + { EEXIST, 0, + "The set with the given name does not exist" }, + { IPSET_ERR_PROTOCOL, 0, + "Kernel error received: ipset protocol error" }, + + /* CREATE specific error codes */ + { EEXIST, IPSET_CMD_CREATE, + "Set cannot be created: set with the same name already exists" }, + { IPSET_ERR_FIND_TYPE, 0, + "Kernel error received: set type does not supported" }, + { IPSET_ERR_MAX_SETS, 0, + "Kernel error received: maximal number of sets reached, " + "cannot create more." }, + { IPSET_ERR_INVALID_NETMASK, 0, + "The value of the netmask parameter is invalid" }, + { IPSET_ERR_INVALID_FAMILY, 0, + "The protocol family not supported by the set type" }, + + /* DESTROY specific error codes */ + { IPSET_ERR_BUSY, IPSET_CMD_DESTROY, + "Set cannot be destroyed: it is in use by a kernel component" }, + + /* FLUSH specific error codes */ + + /* RENAME specific error codes */ + { IPSET_ERR_EXIST_SETNAME2, IPSET_CMD_RENAME, + "Set cannot be renamed: a set with the new name already exists" }, + { IPSET_ERR_REFERENCED, IPSET_CMD_RENAME, + "Set cannot be renamed: it is in use by another system" }, + + /* SWAP specific error codes */ + { IPSET_ERR_EXIST_SETNAME2, IPSET_CMD_SWAP, + "Sets cannot be swapped: the second set does not exist" }, + { IPSET_ERR_TYPE_MISMATCH, IPSET_CMD_SWAP, + "The sets cannot be swapped: they type does not match" }, + + /* LIST/SAVE specific error codes */ + + /* Generic (CADT) error codes */ + { IPSET_ERR_INVALID_CIDR, 0, + "The value of the CIDR parameter of the IP address is invalid" }, + { IPSET_ERR_TIMEOUT, 0, + "Timeout cannot be used: set was created without timeout support" }, + { IPSET_ERR_IPADDR_IPV4, 0, + "An IPv4 address is expected, but not received" }, + { IPSET_ERR_IPADDR_IPV6, 0, + "An IPv6 address is expected, but not received" }, + + /* ADD specific error codes */ + { IPSET_ERR_EXIST, IPSET_CMD_ADD, + "Element cannot be added to the set: it's already added" }, + + /* DEL specific error codes */ + { IPSET_ERR_EXIST, IPSET_CMD_DEL, + "Element cannot be deleted from the set: it's not added" }, + + /* TEST specific error codes */ + + /* HEADER specific error codes */ + + /* TYPE specific error codes */ + { EEXIST, IPSET_CMD_TYPE, + "Kernel error received: set type does not supported" }, + + /* PROTOCOL specific error codes */ + + { }, +}; + +/* Bitmap type-specific error codes */ +static const struct ipset_errcode_table bitmap_errcode_table[] = { + /* Generic (CADT) error codes */ + { IPSET_ERR_BITMAP_RANGE, 0, + "Element is out of the range of the set" }, + { IPSET_ERR_BITMAP_RANGE_SIZE, IPSET_CMD_CREATE, + "The range you specified exceeds the size limit of the set type" }, + { }, +}; + +/* Hash type-specific error codes */ +static const struct ipset_errcode_table hash_errcode_table[] = { + /* Generic (CADT) error codes */ + { IPSET_ERR_HASH_FULL, 0, + "Hash is full, cannot add more elements" }, + { IPSET_ERR_HASH_ELEM, 0, + "Null-valued element, cannot be stored in a hash type of set" }, + { IPSET_ERR_INVALID_PROTO, 0, + "Invalid protocol specified" }, + { IPSET_ERR_MISSING_PROTO, 0, + "Protocol missing, but must be specified" }, + { }, +}; + +/* List type-specific error codes */ +static const struct ipset_errcode_table list_errcode_table[] = { + /* Generic (CADT) error codes */ + { IPSET_ERR_NAME, 0, + "Set to be added/deleted/tested as element does not exist." }, + { IPSET_ERR_LOOP, 0, + "Sets with list:set type cannot be added to the set." }, + { IPSET_ERR_BEFORE, 0, + "No reference set specified." }, + { IPSET_ERR_NAMEREF, 0, + "The set to which you referred with 'before' or 'after' " + "does not exist." }, + { IPSET_ERR_LIST_FULL, 0, + "The set is full, more elements cannot be added." }, + { IPSET_ERR_REF_EXIST, 0, + "The set to which you referred with 'before' or 'after' " + "is not added to the set." }, + { }, +}; + +#define MATCH_TYPENAME(a, b) STRNEQ(a, b, strlen(b)) + +/** + * ipset_errcode - interpret a kernel error code + * @session: session structure + * @errcode: errcode + * + * Find the error code and print the appropriate + * error message into the error buffer. + * + * Returns -1. + */ +int +ipset_errcode(struct ipset_session *session, enum ipset_cmd cmd, int errcode) +{ + const struct ipset_errcode_table *table = core_errcode_table; + int i, generic; + + if (errcode >= IPSET_ERR_TYPE_SPECIFIC) { + const struct ipset_type *type; + + type = ipset_saved_type(session); + if (type) { + if (MATCH_TYPENAME(type->name, "bitmap:")) + table = bitmap_errcode_table; + else if (MATCH_TYPENAME(type->name, "hash:")) + table = hash_errcode_table; + else if (MATCH_TYPENAME(type->name, "list:")) + table = list_errcode_table; + } + } + +retry: + for (i = 0, generic = -1; table[i].errcode; i++) { + if (table[i].errcode == errcode + && (table[i].cmd == cmd || table[i].cmd == 0)) { + if (table[i].cmd == 0) { + generic = i; + continue; + } + return ipset_err(session, table[i].message); + } + } + if (generic != -1) + return ipset_err(session, table[generic].message); + /* Fall back to the core table */ + if (table != core_errcode_table) { + table = core_errcode_table; + goto retry; + } + if (errcode < IPSET_ERR_PRIVATE) + return ipset_err(session, "Kernel error received: %s", + strerror(errcode)); + else + return ipset_err(session, + "Undecoded error %u received from kernel", + errcode); +} diff --git a/extensions/ipset-5/src/ipset.c b/extensions/ipset-5/src/ipset.c new file mode 100644 index 0000000..89dbe8f --- /dev/null +++ b/extensions/ipset-5/src/ipset.c @@ -0,0 +1,747 @@ +/* Copyright 2000-2002 Joakim Axelsson (gozem@linux.nu) + * Patrick Schaaf (bof@bof.de) + * Copyright 2003-2010 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 /* isspace */ +#include /* va_* */ +#include /* bool */ +#include /* fprintf, fgets */ +#include /* exit */ +#include /* str* */ + +#include + +#include /* D() */ +#include /* enum ipset_data */ +#include /* ipset_parse_* */ +#include /* ipset_session_* */ +#include /* struct ipset_type */ +#include /* core options, commands */ +#include /* STREQ */ + +static char program_name[] = PACKAGE; +static char program_version[] = PACKAGE_VERSION; + +static struct ipset_session *session = NULL; +static uint32_t restore_line = 0; +static bool interactive = false; +static char cmdline[1024]; +static char *newargv[255]; +static int newargc = 0; + +/* The known set types: (typename, revision, family) is unique */ +extern struct ipset_type ipset_bitmap_ip0; +extern struct ipset_type ipset_bitmap_ipmac0; +extern struct ipset_type ipset_bitmap_port0; +extern struct ipset_type ipset_hash_ip0; +extern struct ipset_type ipset_hash_net0; +extern struct ipset_type ipset_hash_netport0; +extern struct ipset_type ipset_hash_ipport0; +extern struct ipset_type ipset_hash_ipportip0; +extern struct ipset_type ipset_hash_ipportnet0; +extern struct ipset_type ipset_list_set0; + +enum exittype { + NO_PROBLEM = 0, + OTHER_PROBLEM, + PARAMETER_PROBLEM, + VERSION_PROBLEM, + SESSION_PROBLEM, +}; + +static int __attribute__((format(printf,2,3))) +exit_error(int status, const char *msg, ...) +{ + bool quiet = !interactive + && session + && ipset_envopt_test(session, IPSET_ENV_QUIET); + + if (status && msg && !quiet) { + va_list args; + + fprintf(stderr, "%s v%s: ", program_name, program_version); + va_start(args, msg); + vfprintf(stderr, msg, args); + va_end(args); + if (status != SESSION_PROBLEM) + fprintf(stderr, "\n"); + + if (status == PARAMETER_PROBLEM) + fprintf(stderr, + "Try `%s help' for more information.\n", + program_name); + } + /* Ignore errors in interactive mode */ + if (status && interactive) { + if (session) + ipset_session_report_reset(session); + return -1; + } + + if (session) + ipset_session_fini(session); + + D("status: %u", status); + exit(status > VERSION_PROBLEM ? OTHER_PROBLEM : status); + /* Unreached */ + return -1; +} + +static int +handle_error(void) +{ + if (ipset_session_warning(session) + && !ipset_envopt_test(session, IPSET_ENV_QUIET)) + fprintf(stderr, "Warning: %s\n", + ipset_session_warning(session)); + if (ipset_session_error(session)) + return exit_error(SESSION_PROBLEM, "%s", + ipset_session_error(session)); + + if (!interactive) { + ipset_session_fini(session); + exit(OTHER_PROBLEM); + } + + ipset_session_report_reset(session); + return -1; +} + +static void +help(void) +{ + const struct ipset_commands *c; + const struct ipset_envopts *opt = ipset_envopts; + + printf("%s v%s\n\n" + "Usage: %s [options] COMMAND\n\nCommands:\n", + program_name, program_version, program_name); + + for (c = ipset_commands; c->cmd; c++) { + printf("%s %s\n", c->name[0], c->help); + } + printf("\nOptions:\n"); + + while (opt->flag) { + if (opt->help) + printf("%s %s\n", opt->name[0], opt->help); + opt++; + } +} + +/* Build faked argv from parsed line */ +static void +build_argv(char *buffer) +{ + char *ptr; + int i; + + /* Reset */ + for (i = 1; i < newargc; i++) + newargv[i] = NULL; + newargc = 1; + + ptr = strtok(buffer, " \t\n"); + newargv[newargc++] = ptr; + while ((ptr = strtok(NULL, " \t\n")) != NULL) { + if ((newargc + 1) < (int)(sizeof(newargv)/sizeof(char *))) + newargv[newargc++] = ptr; + else { + exit_error(PARAMETER_PROBLEM, + "Line is too long to parse."); + return; + } + } +} + +/* Main parser function, workhorse */ +int parse_commandline(int argc, char *argv[]); + +/* + * Performs a restore from stdin + */ +static int +restore(char *argv0) +{ + int ret = 0; + char *c; + + /* Initialize newargv/newargc */ + newargc = 0; + newargv[newargc++] = argv0; + + while (fgets(cmdline, sizeof(cmdline), stdin)) { + restore_line++; + c = cmdline; + while (isspace(c[0])) + c++; + if (c[0] == '\0' || c[0] == '#') + continue; + else if (strcmp(c, "COMMIT\n") == 0) { + ret = ipset_commit(session); + if (ret < 0) + handle_error(); + continue; + } + /* Build faked argv, argc */ + build_argv(c); + + /* Execute line */ + ret = parse_commandline(newargc, newargv); + if (ret < 0) + handle_error(); + } + /* implicit "COMMIT" at EOF */ + ret = ipset_commit(session); + if (ret < 0) + handle_error(); + + return ret; +} + +static int +call_parser(int *argc, char *argv[], const struct ipset_arg *args) +{ + int i = 1, ret = 0; + const struct ipset_arg *arg; + const char *optstr; + + /* Currently CREATE and ADT may have got additional arguments */ + if (!args) + goto done; + for (arg = args; arg->opt; arg++) { + for (i = 1; i < *argc; ) { + D("argc: %u, i: %u: %s vs %s", + *argc, i, argv[i], arg->name[0]); + if (!(ipset_match_option(argv[i], arg->name))) { + i++; + continue; + } + optstr = argv[i]; + /* Shift off matched option */ + D("match %s", arg->name[0]); + ipset_shift_argv(argc, argv, i); + D("argc: %u, i: %u", *argc, i); + switch (arg->has_arg) { + case IPSET_MANDATORY_ARG: + if (i + 1 > *argc) + return exit_error(PARAMETER_PROBLEM, + "Missing mandatory argument " + "of option `%s'", + arg->name[0]); + /* Fall through */ + case IPSET_OPTIONAL_ARG: + if (i + 1 <= *argc) { + ret = ipset_call_parser(session, + arg->parse, + optstr, arg->opt, + argv[i]); + if (ret < 0) + return ret; + ipset_shift_argv(argc, argv, i); + break; + } + /* Fall through */ + default: + ret = ipset_call_parser(session, + arg->parse, + optstr, arg->opt, + optstr); + if (ret < 0) + return ret; + } + } + } +done: + if (i < *argc) + return exit_error(PARAMETER_PROBLEM, + "Unknown argument: `%s'", + argv[i]); + return ret; +} + +static enum ipset_adt +cmd2cmd(int cmd) +{ + switch(cmd) { + case IPSET_CMD_ADD: + return IPSET_ADD; + case IPSET_CMD_DEL: + return IPSET_DEL; + case IPSET_CMD_TEST: + return IPSET_TEST; + case IPSET_CMD_CREATE: + return IPSET_CREATE; + default: + return 0; + } +} + +static void +check_mandatory(const struct ipset_type *type, enum ipset_cmd command) +{ + enum ipset_adt cmd = cmd2cmd(command); + uint64_t flags = ipset_data_flags(ipset_session_data(session)); + uint64_t mandatory = type->mandatory[cmd]; + const struct ipset_arg *arg = type->args[cmd]; + + /* Range can be expressed by ip/cidr */ + if (flags & IPSET_FLAG(IPSET_OPT_CIDR)) + flags |= IPSET_FLAG(IPSET_OPT_IP_TO); + + mandatory &= ~flags; + if (!mandatory) + return; + if (!arg) { + exit_error(OTHER_PROBLEM, + "There are missing mandatory flags " + "but can't check them. " + "It's a bug, please report the problem."); + return; + } + + for (; arg->opt; arg++) + if (mandatory & IPSET_FLAG(arg->opt)) { + exit_error(PARAMETER_PROBLEM, + "Mandatory option `%s' is missing", + arg->name[0]); + return; + } +} + +static const char * +cmd2name(enum ipset_cmd cmd) +{ + const struct ipset_commands *c; + + for (c = ipset_commands; c->cmd; c++) + if (cmd == c->cmd) + return c->name[0]; + return "unknown command"; +} + +static const char * +session_family(void) +{ + switch (ipset_data_family(ipset_session_data(session))) { + case AF_INET: + return "inet"; + case AF_INET6: + return "inet6"; + default: + return "unspec"; + } +} + +static void +check_allowed(const struct ipset_type *type, enum ipset_cmd command) +{ + uint64_t flags = ipset_data_flags(ipset_session_data(session)); + enum ipset_adt cmd = cmd2cmd(command); + uint64_t allowed = type->full[cmd]; + uint64_t cmdflags = command == IPSET_CMD_CREATE + ? IPSET_CREATE_FLAGS : IPSET_ADT_FLAGS; + const struct ipset_arg *arg = type->args[cmd]; + enum ipset_opt i; + + /* Range can be expressed by ip/cidr or from-to */ + if (allowed & IPSET_FLAG(IPSET_OPT_IP_TO)) + allowed |= IPSET_FLAG(IPSET_OPT_CIDR); + + for (i = IPSET_OPT_IP; i < IPSET_OPT_FLAGS; i++) { + if (!(cmdflags & IPSET_FLAG(i)) + || (allowed & IPSET_FLAG(i)) + || !(flags & IPSET_FLAG(i))) + continue; + /* Not allowed element-expressions */ + switch (i) { + case IPSET_OPT_CIDR: + exit_error(OTHER_PROBLEM, + "IP/CIDR range is not allowed in command %s " + "with set type %s and family %s", + cmd2name(command), type->name, session_family()); + return; + case IPSET_OPT_IP_TO: + exit_error(OTHER_PROBLEM, + "FROM-TO IP range is not allowed in command %s " + "with set type %s and family %s", + cmd2name(command), type->name, session_family()); + return; + case IPSET_OPT_PORT_TO: + exit_error(OTHER_PROBLEM, + "FROM-TO port range is not allowed in command %s " + "with set type %s and family %s", + cmd2name(command), type->name, session_family()); + return; + default: + break; + } + /* Other options */ + if (!arg) { + exit_error(OTHER_PROBLEM, + "There are not allowed options (%u) " + "but option list is NULL. " + "It's a bug, please report the problem.", i); + return; + } + for (; arg->opt; arg++) { + if (arg->opt != i) + continue; + exit_error(OTHER_PROBLEM, + "%s parameter is not allowed in command %s " + "with set type %s and family %s", + arg->name[0], + cmd2name(command), type->name, session_family()); + return; + } + exit_error(OTHER_PROBLEM, + "There are not allowed options (%u) " + "but can't resolve them. " + "It's a bug, please report the problem.", i); + return; + } +} + +static const struct ipset_type * +type_find(const char *name) +{ + const struct ipset_type *t = ipset_types(); + + while (t) { + if (ipset_match_typename(name, t)) + return t; + t = t->next; + } + return NULL; +} + +/* Workhorse */ +int +parse_commandline(int argc, char *argv[]) +{ + int ret = 0; + enum ipset_cmd cmd = IPSET_CMD_NONE; + int i; + char *arg0 = NULL, *arg1 = NULL, *c; + const struct ipset_envopts *opt; + const struct ipset_commands *command; + const struct ipset_type *type; + + /* Initialize session */ + if (session == NULL) { + session = ipset_session_init(printf); + if (session == NULL) + return exit_error(OTHER_PROBLEM, + "Cannot initialize ipset session, aborting."); + } + + /* Commandline parsing, somewhat similar to that of 'ip' */ + + /* First: parse core options */ + for (opt = ipset_envopts; opt->flag; opt++) { + for (i = 1; i < argc; ) { + if (!ipset_match_envopt(argv[i], opt->name)) { + i++; + continue; + } + /* Shift off matched option */ + ipset_shift_argv(&argc, argv, i); + switch (opt->has_arg) { + case IPSET_MANDATORY_ARG: + if (i + 1 > argc) + return exit_error(PARAMETER_PROBLEM, + "Missing mandatory argument " + "to option %s", + opt->name[0]); + /* Fall through */ + case IPSET_OPTIONAL_ARG: + if (i + 1 <= argc) { + ret = opt->parse(session, opt->flag, + argv[i]); + if (ret < 0) + return handle_error(); + ipset_shift_argv(&argc, argv, i); + } + break; + case IPSET_NO_ARG: + ret = opt->parse(session, opt->flag, + opt->name[0]); + if (ret < 0) + return handle_error(); + break; + default: + break; + } + } + } + + /* Second: parse command */ + for (command = ipset_commands; + command->cmd && cmd == IPSET_CMD_NONE; + command++) { + for (i = 1; i < argc; ) { + if (!ipset_match_cmd(argv[1], command->name)) { + i++; + continue; + } + if (restore_line != 0 + && (command->cmd == IPSET_CMD_RESTORE + || command->cmd == IPSET_CMD_VERSION + || command->cmd == IPSET_CMD_HELP)) + return exit_error(PARAMETER_PROBLEM, + "Command `%s' is invalid " + "in restore mode.", + command->name[0]); + if (interactive + && command->cmd == IPSET_CMD_RESTORE) { + printf("Restore command ignored " + "in interactive mode\n"); + return 0; + } + + /* Shift off matched command arg */ + ipset_shift_argv(&argc, argv, i); + cmd = command->cmd; + switch (command->has_arg) { + case IPSET_MANDATORY_ARG: + case IPSET_MANDATORY_ARG2: + if (i + 1 > argc) + return exit_error(PARAMETER_PROBLEM, + "Missing mandatory argument " + "to command %s", + command->name[0]); + /* Fall through */ + case IPSET_OPTIONAL_ARG: + arg0 = argv[i]; + if (i + 1 <= argc) + /* Shift off first arg */ + ipset_shift_argv(&argc, argv, i); + break; + default: + break; + } + if (command->has_arg == IPSET_MANDATORY_ARG2) { + if (i + 1 > argc) + return exit_error(PARAMETER_PROBLEM, + "Missing second mandatory " + "argument to command %s", + command->name[0]); + arg1 = argv[i]; + /* Shift off second arg */ + ipset_shift_argv(&argc, argv, i); + } + break; + } + } + + /* Third: catch interactive mode, handle help, version */ + switch (cmd) { + case IPSET_CMD_NONE: + if (interactive) { + printf("No command specified\n"); + return 0; + } + if (argc > 1 && STREQ(argv[1], "-")) { + interactive = true; + printf("%s> ", program_name); + /* Initialize newargv/newargc */ + newargv[newargc++] = program_name; + while (fgets(cmdline, sizeof(cmdline), stdin)) { + c = cmdline; + while (isspace(c[0])) + c++; + if (c[0] == '\0' || c[0] == '#') + continue; + /* Build fake argv, argc */ + build_argv(c); + /* Execute line: ignore errors */ + parse_commandline(newargc, newargv); + printf("%s> ", program_name); + } + return exit_error(NO_PROBLEM, NULL); + } + if (argc > 1) + return exit_error(PARAMETER_PROBLEM, + "No command specified: unknown argument %s", + argv[1]); + return exit_error(PARAMETER_PROBLEM, "No command specified."); + case IPSET_CMD_VERSION: + printf("%s v%s.\n", program_name, program_version); + if (interactive) + return 0; + return exit_error(NO_PROBLEM, NULL); + case IPSET_CMD_HELP: + help(); + + if (interactive + || !ipset_envopt_test(session, IPSET_ENV_QUIET)) { + if (arg0) { + /* Type-specific help, without kernel checking */ + type = type_find(arg0); + if (!type) + return exit_error(PARAMETER_PROBLEM, + "Unknown settype: `%s'", arg0); + printf("\n%s type specific options:\n\n%s", + type->name, type->usage); + if (type->usagefn) + type->usagefn(); + if (type->family == AF_UNSPEC) + printf("\nType %s is family neutral.\n", + type->name); + else if (type->family == AF_INET46) + printf("\nType %s supports INET " + "and INET6.\n", + type->name); + else + printf("\nType %s supports family " + "%s only.\n", + type->name, + type->family == AF_INET + ? "INET" : "INET6"); + } else { + printf("\nSupported set types:\n"); + type = ipset_types(); + while (type) { + printf(" %s\n", type->name); + type = type->next; + } + } + } + if (interactive) + return 0; + return exit_error(NO_PROBLEM, NULL); + case IPSET_CMD_QUIT: + return exit_error(NO_PROBLEM, NULL); + default: + break; + } + + /* Forth: parse command args and issue the command */ + switch (cmd) { + case IPSET_CMD_CREATE: + /* Args: setname typename [type specific options] */ + ret = ipset_parse_setname(session, IPSET_SETNAME, arg0); + if (ret < 0) + return handle_error(); + + ret = ipset_parse_typename(session, IPSET_OPT_TYPENAME, arg1); + if (ret < 0) + return handle_error(); + + type = ipset_type_get(session, cmd); + if (type == NULL) + return handle_error(); + + /* Parse create options */ + ret = call_parser(&argc, argv, type->args[IPSET_CREATE]); + if (ret < 0) + return handle_error(); + else if (ret) + return ret; + + /* Check mandatory, then allowed options */ + check_mandatory(type, cmd); + check_allowed(type, cmd); + + break; + case IPSET_CMD_DESTROY: + case IPSET_CMD_FLUSH: + case IPSET_CMD_LIST: + case IPSET_CMD_SAVE: + /* Args: [setname] */ + if (arg0) { + ret = ipset_parse_setname(session, + IPSET_SETNAME, arg0); + if (ret < 0) + return handle_error(); + } + break; + + case IPSET_CMD_RENAME: + case IPSET_CMD_SWAP: + /* Args: from-setname to-setname */ + ret = ipset_parse_setname(session, IPSET_SETNAME, arg0); + if (ret < 0) + return handle_error(); + ret = ipset_parse_setname(session, IPSET_OPT_SETNAME2, arg1); + if (ret < 0) + return handle_error(); + break; + + case IPSET_CMD_RESTORE: + /* Restore mode */ + if (argc > 1) + return exit_error(PARAMETER_PROBLEM, + "Unknown argument %s", argv[1]); + return restore(argv[0]); + case IPSET_CMD_ADD: + case IPSET_CMD_DEL: + case IPSET_CMD_TEST: + D("ADT: setname %s", arg0); + /* Args: setname ip [options] */ + ret = ipset_parse_setname(session, IPSET_SETNAME, arg0); + if (ret < 0) + return handle_error(); + + type = ipset_type_get(session, cmd); + if (type == NULL) + return handle_error(); + + ret = ipset_parse_elem(session, type->last_elem_optional, arg1); + if (ret < 0) + return handle_error(); + + /* Parse additional ADT options */ + ret = call_parser(&argc, argv, type->args[cmd2cmd(cmd)]); + if (ret < 0) + return handle_error(); + else if (ret) + return ret; + + /* Check mandatory, then allowed options */ + check_mandatory(type, cmd); + check_allowed(type, cmd); + + break; + default: + break; + } + + if (argc > 1) + return exit_error(PARAMETER_PROBLEM, + "Unknown argument %s", argv[1]); + ret = ipset_cmd(session, cmd, restore_line); + D("ret %d", ret); + /* Special case for TEST and non-quiet mode */ + if (cmd == IPSET_CMD_TEST && ipset_session_warning(session)) { + if (!ipset_envopt_test(session, IPSET_ENV_QUIET)) + fprintf(stderr, "%s", ipset_session_warning(session)); + ipset_session_report_reset(session); + } + if (ret < 0) + handle_error(); + + return ret; +} + +int +main(int argc, char *argv[]) +{ + /* Register types */ + ipset_type_add(&ipset_bitmap_ip0); + ipset_type_add(&ipset_bitmap_ipmac0); + ipset_type_add(&ipset_bitmap_port0); + ipset_type_add(&ipset_hash_ip0); + ipset_type_add(&ipset_hash_net0); + ipset_type_add(&ipset_hash_netport0); + ipset_type_add(&ipset_hash_ipport0); + ipset_type_add(&ipset_hash_ipportip0); + ipset_type_add(&ipset_hash_ipportnet0); + ipset_type_add(&ipset_list_set0); + + return parse_commandline(argc, argv); +} diff --git a/extensions/ipset-5/src/ipset_bitmap_ip.c b/extensions/ipset-5/src/ipset_bitmap_ip.c new file mode 100644 index 0000000..4194875 --- /dev/null +++ b/extensions/ipset-5/src/ipset_bitmap_ip.c @@ -0,0 +1,97 @@ +/* Copyright 2007-2010 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 /* prototypes */ + +/* Parse commandline arguments */ +static const struct ipset_arg bitmap_ip_create_args[] = { + { .name = { "range", NULL }, + .has_arg = IPSET_MANDATORY_ARG, .opt = IPSET_OPT_IP, + .parse = ipset_parse_netrange, .print = ipset_print_ip, + }, + { .name = { "netmask", NULL }, + .has_arg = IPSET_MANDATORY_ARG, .opt = IPSET_OPT_NETMASK, + .parse = ipset_parse_netmask, .print = ipset_print_number, + }, + { .name = { "timeout", NULL }, + .has_arg = IPSET_MANDATORY_ARG, .opt = IPSET_OPT_TIMEOUT, + .parse = ipset_parse_uint32, .print = ipset_print_number, + }, + /* Backward compatibility */ + { .name = { "from", NULL }, + .has_arg = IPSET_MANDATORY_ARG, .opt = IPSET_OPT_IP, + .parse = ipset_parse_single_ip, + }, + { .name = { "to", NULL }, + .has_arg = IPSET_MANDATORY_ARG, .opt = IPSET_OPT_IP_TO, + .parse = ipset_parse_single_ip, + }, + { .name = { "network", NULL }, + .has_arg = IPSET_MANDATORY_ARG, .opt = IPSET_OPT_IP, + .parse = ipset_parse_net, + }, + { }, +}; + +static const struct ipset_arg bitmap_ip_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 bitmap_ip_usage[] = +"create SETNAME bitmap:ip range IP/CIDR|FROM-TO\n" +" [netmask CIDR] [timeout VALUE]\n" +"add SETNAME IP|IP/CIDR|FROM-TO [timeout VALUE]\n" +"del SETNAME IP|IP/CIDR|FROM-TO\n" +"test SETNAME IP\n\n" +"where IP, FROM and TO are IPv4 addresses (or hostnames),\n" +" CIDR is a valid IPv4 CIDR prefix.\n"; + +struct ipset_type ipset_bitmap_ip0 = { + .name = "bitmap:ip", + .alias = { "ipmap", NULL }, + .revision = 0, + .family = AF_INET, + .dimension = IPSET_DIM_ONE, + .elem = { + [IPSET_DIM_ONE] = { + .parse = ipset_parse_ip, + .print = ipset_print_ip, + .opt = IPSET_OPT_IP + }, + }, + .args = { + [IPSET_CREATE] = bitmap_ip_create_args, + [IPSET_ADD] = bitmap_ip_add_args, + }, + .mandatory = { + [IPSET_CREATE] = IPSET_FLAG(IPSET_OPT_IP) + | IPSET_FLAG(IPSET_OPT_IP_TO), + [IPSET_ADD] = IPSET_FLAG(IPSET_OPT_IP), + [IPSET_DEL] = IPSET_FLAG(IPSET_OPT_IP), + [IPSET_TEST] = IPSET_FLAG(IPSET_OPT_IP), + }, + .full = { + [IPSET_CREATE] = IPSET_FLAG(IPSET_OPT_IP) + | IPSET_FLAG(IPSET_OPT_IP_TO) + | IPSET_FLAG(IPSET_OPT_NETMASK) + | IPSET_FLAG(IPSET_OPT_TIMEOUT), + [IPSET_ADD] = IPSET_FLAG(IPSET_OPT_IP) + | IPSET_FLAG(IPSET_OPT_IP_TO) + | IPSET_FLAG(IPSET_OPT_TIMEOUT), + [IPSET_DEL] = IPSET_FLAG(IPSET_OPT_IP) + | IPSET_FLAG(IPSET_OPT_IP_TO), + [IPSET_TEST] = IPSET_FLAG(IPSET_OPT_IP), + }, + + .usage = bitmap_ip_usage, +}; diff --git a/extensions/ipset-5/src/ipset_bitmap_ipmac.c b/extensions/ipset-5/src/ipset_bitmap_ipmac.c new file mode 100644 index 0000000..8f2cb72 --- /dev/null +++ b/extensions/ipset-5/src/ipset_bitmap_ipmac.c @@ -0,0 +1,100 @@ +/* Copyright 2007-2010 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 /* prototypes */ + +/* Parse commandline arguments */ +static const struct ipset_arg bitmap_ipmac_create_args[] = { + { .name = { "range", NULL }, + .has_arg = IPSET_MANDATORY_ARG, .opt = IPSET_OPT_IP, + .parse = ipset_parse_netrange, .print = ipset_print_ip, + }, + { .name = { "timeout", NULL }, + .has_arg = IPSET_MANDATORY_ARG, .opt = IPSET_OPT_TIMEOUT, + .parse = ipset_parse_uint32, .print = ipset_print_number, + }, + /* Backward compatibility */ + { .name = { "from", NULL }, + .has_arg = IPSET_MANDATORY_ARG, .opt = IPSET_OPT_IP, + .parse = ipset_parse_single_ip, + }, + { .name = { "to", NULL }, + .has_arg = IPSET_MANDATORY_ARG, .opt = IPSET_OPT_IP_TO, + .parse = ipset_parse_single_ip, + }, + { .name = { "network", NULL }, + .has_arg = IPSET_MANDATORY_ARG, .opt = IPSET_OPT_IP, + .parse = ipset_parse_net, + }, + { }, +}; + +static const struct ipset_arg bitmap_ipmac_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 bitmap_ipmac_usage[] = +"create SETNAME bitmap:ip,mac range IP/CIDR|FROM-TO\n" +" [matchunset] [timeout VALUE]\n" +"add SETNAME IP[,MAC] [timeout VALUE]\n" +"del SETNAME IP[,MAC]\n" +"test SETNAME IP[,MAC]\n\n" +"where IP, FROM and TO are IPv4 addresses (or hostnames),\n" +" CIDR is a valid IPv4 CIDR prefix,\n" +" MAC is a valid MAC address.\n"; + +struct ipset_type ipset_bitmap_ipmac0 = { + .name = "bitmap:ip,mac", + .alias = { "macipmap", NULL }, + .revision = 0, + .family = AF_INET, + .dimension = IPSET_DIM_TWO, + .last_elem_optional = true, + .elem = { + [IPSET_DIM_ONE] = { + .parse = ipset_parse_single_ip, + .print = ipset_print_ip, + .opt = IPSET_OPT_IP + }, + [IPSET_DIM_TWO] = { + .parse = ipset_parse_ether, + .print = ipset_print_ether, + .opt = IPSET_OPT_ETHER + }, + }, + .args = { + [IPSET_CREATE] = bitmap_ipmac_create_args, + [IPSET_ADD] = bitmap_ipmac_add_args, + }, + .mandatory = { + [IPSET_CREATE] = IPSET_FLAG(IPSET_OPT_IP) + | IPSET_FLAG(IPSET_OPT_IP_TO), + [IPSET_ADD] = IPSET_FLAG(IPSET_OPT_IP), + [IPSET_DEL] = IPSET_FLAG(IPSET_OPT_IP), + [IPSET_TEST] = IPSET_FLAG(IPSET_OPT_IP), + }, + .full = { + [IPSET_CREATE] = IPSET_FLAG(IPSET_OPT_IP) + | IPSET_FLAG(IPSET_OPT_IP_TO) + | IPSET_FLAG(IPSET_OPT_TIMEOUT), + [IPSET_ADD] = IPSET_FLAG(IPSET_OPT_IP) + | IPSET_FLAG(IPSET_OPT_ETHER) + | IPSET_FLAG(IPSET_OPT_TIMEOUT), + [IPSET_DEL] = IPSET_FLAG(IPSET_OPT_IP) + | IPSET_FLAG(IPSET_OPT_ETHER), + [IPSET_TEST] = IPSET_FLAG(IPSET_OPT_IP) + | IPSET_FLAG(IPSET_OPT_ETHER), + }, + + .usage = bitmap_ipmac_usage, +}; diff --git a/extensions/ipset-5/src/ipset_bitmap_port.c b/extensions/ipset-5/src/ipset_bitmap_port.c new file mode 100644 index 0000000..82b98a4 --- /dev/null +++ b/extensions/ipset-5/src/ipset_bitmap_port.c @@ -0,0 +1,87 @@ +/* Copyright 2007-2010 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 /* prototypes */ + +/* Parse commandline arguments */ +static const struct ipset_arg bitmap_port_create_args[] = { + { .name = { "range", NULL }, + .has_arg = IPSET_MANDATORY_ARG, .opt = IPSET_OPT_PORT, + .parse = ipset_parse_tcp_port, .print = ipset_print_port, + }, + { .name = { "timeout", NULL }, + .has_arg = IPSET_MANDATORY_ARG, .opt = IPSET_OPT_TIMEOUT, + .parse = ipset_parse_uint32, .print = ipset_print_number, + }, + /* Backward compatibility */ + { .name = { "from", NULL }, + .has_arg = IPSET_MANDATORY_ARG, .opt = IPSET_OPT_PORT, + .parse = ipset_parse_single_tcp_port, + }, + { .name = { "to", NULL }, + .has_arg = IPSET_MANDATORY_ARG, .opt = IPSET_OPT_PORT_TO, + .parse = ipset_parse_single_tcp_port, + }, + { }, +}; + +static const struct ipset_arg bitmap_port_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 bitmap_port_usage[] = +"create SETNAME bitmap:port range FROM-TO\n" +" [timeout VALUE]\n" +"add SETNAME PORT|FROM-TO [timeout VALUE]\n" +"del SETNAME PORT|FROM-TO\n" +"test SETNAME PORT\n\n" +"where PORT, FROM and TO are port numbers or port names from /etc/services.\n"; + +struct ipset_type ipset_bitmap_port0 = { + .name = "bitmap:port", + .alias = { "portmap", NULL }, + .revision = 0, + .family = AF_UNSPEC, + .dimension = IPSET_DIM_ONE, + .elem = { + [IPSET_DIM_ONE] = { + .parse = ipset_parse_tcp_port, + .print = ipset_print_port, + .opt = IPSET_OPT_PORT + }, + }, + .args = { + [IPSET_CREATE] = bitmap_port_create_args, + [IPSET_ADD] = bitmap_port_add_args, + }, + .mandatory = { + [IPSET_CREATE] = IPSET_FLAG(IPSET_OPT_PORT) + | IPSET_FLAG(IPSET_OPT_PORT_TO), + [IPSET_ADD] = IPSET_FLAG(IPSET_OPT_PORT), + [IPSET_DEL] = IPSET_FLAG(IPSET_OPT_PORT), + [IPSET_TEST] = IPSET_FLAG(IPSET_OPT_PORT), + }, + .full = { + [IPSET_CREATE] = IPSET_FLAG(IPSET_OPT_PORT) + | IPSET_FLAG(IPSET_OPT_PORT_TO) + | IPSET_FLAG(IPSET_OPT_TIMEOUT), + [IPSET_ADD] = IPSET_FLAG(IPSET_OPT_PORT) + | IPSET_FLAG(IPSET_OPT_PORT_TO) + | IPSET_FLAG(IPSET_OPT_TIMEOUT), + [IPSET_DEL] = IPSET_FLAG(IPSET_OPT_PORT) + | IPSET_FLAG(IPSET_OPT_PORT_TO), + [IPSET_TEST] = IPSET_FLAG(IPSET_OPT_PORT), + }, + + .usage = bitmap_port_usage, +}; diff --git a/extensions/ipset-5/src/ipset_hash_ip.c b/extensions/ipset-5/src/ipset_hash_ip.c new file mode 100644 index 0000000..0af65b7 --- /dev/null +++ b/extensions/ipset-5/src/ipset_hash_ip.c @@ -0,0 +1,120 @@ +/* Copyright 2007-2010 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 /* prototypes */ + +/* Parse commandline arguments */ +static const struct ipset_arg hash_ip_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 = { "netmask", NULL }, + .has_arg = IPSET_MANDATORY_ARG, .opt = IPSET_OPT_NETMASK, + .parse = ipset_parse_netmask, .print = ipset_print_number, + }, + { .name = { "timeout", NULL }, + .has_arg = IPSET_MANDATORY_ARG, .opt = IPSET_OPT_TIMEOUT, + .parse = ipset_parse_uint32, .print = ipset_print_number, + }, + /* Ignored options: backward compatibilty */ + { .name = { "probes", NULL }, + .has_arg = IPSET_MANDATORY_ARG, .opt = IPSET_OPT_PROBES, + .parse = ipset_parse_ignored, .print = ipset_print_number, + }, + { .name = { "resize", NULL }, + .has_arg = IPSET_MANDATORY_ARG, .opt = IPSET_OPT_RESIZE, + .parse = ipset_parse_ignored, .print = ipset_print_number, + }, + { .name = { "gc", NULL }, + .has_arg = IPSET_MANDATORY_ARG, .opt = IPSET_OPT_GC, + .parse = ipset_parse_ignored, .print = ipset_print_number, + }, + { }, +}; + +static const struct ipset_arg hash_ip_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_ip_usage[] = +"create SETNAME hash:ip\n" +" [family inet|inet6]\n" +" [hashsize VALUE] [maxelem VALUE]\n" +" [netmask CIDR] [timeout VALUE]\n" +"add SETNAME IP [timeout VALUE]\n" +"del SETNAME IP\n" +"test SETNAME IP\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 in IP/CIDR or FROM-TO form\n" +" is supported for IPv4.\n"; + +struct ipset_type ipset_hash_ip0 = { + .name = "hash:ip", + .alias = { "iphash", "iptree", "iptreemap", NULL }, + .revision = 0, + .family = AF_INET46, + .dimension = IPSET_DIM_ONE, + .elem = { + [IPSET_DIM_ONE] = { + .parse = ipset_parse_ip4_single6, + .print = ipset_print_ip, + .opt = IPSET_OPT_IP + }, + }, + .compat_parse_elem = ipset_parse_iptimeout, + .args = { + [IPSET_CREATE] = hash_ip_create_args, + [IPSET_ADD] = hash_ip_add_args, + }, + .mandatory = { + [IPSET_CREATE] = 0, + [IPSET_ADD] = IPSET_FLAG(IPSET_OPT_IP), + [IPSET_DEL] = IPSET_FLAG(IPSET_OPT_IP), + [IPSET_TEST] = IPSET_FLAG(IPSET_OPT_IP), + }, + .full = { + [IPSET_CREATE] = IPSET_FLAG(IPSET_OPT_HASHSIZE) + | IPSET_FLAG(IPSET_OPT_MAXELEM) + | IPSET_FLAG(IPSET_OPT_NETMASK) + | IPSET_FLAG(IPSET_OPT_TIMEOUT), + [IPSET_ADD] = IPSET_FLAG(IPSET_OPT_IP) + | IPSET_FLAG(IPSET_OPT_IP_TO) + | IPSET_FLAG(IPSET_OPT_TIMEOUT), + [IPSET_DEL] = IPSET_FLAG(IPSET_OPT_IP) + | IPSET_FLAG(IPSET_OPT_IP_TO), + [IPSET_TEST] = IPSET_FLAG(IPSET_OPT_IP), + }, + + .usage = hash_ip_usage, +}; diff --git a/extensions/ipset-5/src/ipset_hash_ipport.c b/extensions/ipset-5/src/ipset_hash_ipport.c new file mode 100644 index 0000000..94bda07 --- /dev/null +++ b/extensions/ipset-5/src/ipset_hash_ipport.c @@ -0,0 +1,144 @@ +/* Copyright 2007-2010 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_ipport_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, + }, + /* Backward compatibility */ + { .name = { "probes", NULL }, + .has_arg = IPSET_MANDATORY_ARG, .opt = IPSET_OPT_PROBES, + .parse = ipset_parse_ignored, .print = ipset_print_number, + }, + { .name = { "resize", NULL }, + .has_arg = IPSET_MANDATORY_ARG, .opt = IPSET_OPT_RESIZE, + .parse = ipset_parse_ignored, .print = ipset_print_number, + }, + { .name = { "from", NULL }, + .has_arg = IPSET_MANDATORY_ARG, .opt = IPSET_OPT_IP, + .parse = ipset_parse_ignored, + }, + { .name = { "to", NULL }, + .has_arg = IPSET_MANDATORY_ARG, .opt = IPSET_OPT_IP_TO, + .parse = ipset_parse_ignored, + }, + { .name = { "network", NULL }, + .has_arg = IPSET_MANDATORY_ARG, .opt = IPSET_OPT_IP, + .parse = ipset_parse_ignored, + }, + { }, +}; + +static const struct ipset_arg hash_ipport_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_ipport_usage[] = +"create SETNAME hash:ip,port\n" +" [family inet|inet6]\n" +" [hashsize VALUE] [maxelem VALUE]\n" +" [timeout VALUE]\n" +"add SETNAME IP,PROTO:PORT [timeout VALUE]\n" +"del SETNAME IP,PROTO:PORT\n" +"test SETNAME IP,PROTO:PORT\n\n" +"where depending on the INET family\n" +" IP is a valid IPv4 or IPv6 address (or hostname).\n" +" Adding/deleting multiple elements in IP/CIDR or FROM-TO form\n" +" is supported for IPv4.\n" +" Adding/deleting multiple elements with TCP/UDP port range\n" +" is supported both for IPv4 and IPv6.\n"; + +struct ipset_type ipset_hash_ipport0 = { + .name = "hash:ip,port", + .alias = { "ipporthash", NULL }, + .revision = 0, + .family = AF_INET46, + .dimension = IPSET_DIM_TWO, + .elem = { + [IPSET_DIM_ONE] = { + .parse = ipset_parse_ip4_single6, + .print = ipset_print_ip, + .opt = IPSET_OPT_IP + }, + [IPSET_DIM_TWO] = { + .parse = ipset_parse_proto_port, + .print = ipset_print_proto_port, + .opt = IPSET_OPT_PORT + }, + }, + .args = { + [IPSET_CREATE] = hash_ipport_create_args, + [IPSET_ADD] = hash_ipport_add_args, + }, + .mandatory = { + [IPSET_CREATE] = 0, + [IPSET_ADD] = IPSET_FLAG(IPSET_OPT_IP) + | IPSET_FLAG(IPSET_OPT_PROTO) + | IPSET_FLAG(IPSET_OPT_PORT), + [IPSET_DEL] = IPSET_FLAG(IPSET_OPT_IP) + | IPSET_FLAG(IPSET_OPT_PROTO) + | IPSET_FLAG(IPSET_OPT_PORT), + [IPSET_TEST] = IPSET_FLAG(IPSET_OPT_IP) + | IPSET_FLAG(IPSET_OPT_PROTO) + | IPSET_FLAG(IPSET_OPT_PORT), + }, + .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_IP_TO) + | IPSET_FLAG(IPSET_OPT_PORT) + | IPSET_FLAG(IPSET_OPT_PORT_TO) + | IPSET_FLAG(IPSET_OPT_PROTO) + | IPSET_FLAG(IPSET_OPT_TIMEOUT), + [IPSET_DEL] = IPSET_FLAG(IPSET_OPT_IP) + | IPSET_FLAG(IPSET_OPT_IP_TO) + | IPSET_FLAG(IPSET_OPT_PORT) + | IPSET_FLAG(IPSET_OPT_PORT_TO) + | IPSET_FLAG(IPSET_OPT_PROTO), + [IPSET_TEST] = IPSET_FLAG(IPSET_OPT_IP) + | IPSET_FLAG(IPSET_OPT_PORT) + | IPSET_FLAG(IPSET_OPT_PROTO), + }, + + .usage = hash_ipport_usage, + .usagefn = ipset_port_usage, +}; diff --git a/extensions/ipset-5/src/ipset_hash_ipportip.c b/extensions/ipset-5/src/ipset_hash_ipportip.c new file mode 100644 index 0000000..cb90152 --- /dev/null +++ b/extensions/ipset-5/src/ipset_hash_ipportip.c @@ -0,0 +1,155 @@ +/* Copyright 2007-2010 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_ipportip_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, + }, + /* Backward compatibility */ + { .name = { "probes", NULL }, + .has_arg = IPSET_MANDATORY_ARG, .opt = IPSET_OPT_PROBES, + .parse = ipset_parse_ignored, .print = ipset_print_number, + }, + { .name = { "resize", NULL }, + .has_arg = IPSET_MANDATORY_ARG, .opt = IPSET_OPT_RESIZE, + .parse = ipset_parse_ignored, .print = ipset_print_number, + }, + { .name = { "from", NULL }, + .has_arg = IPSET_MANDATORY_ARG, .opt = IPSET_OPT_IP, + .parse = ipset_parse_ignored, + }, + { .name = { "to", NULL }, + .has_arg = IPSET_MANDATORY_ARG, .opt = IPSET_OPT_IP_TO, + .parse = ipset_parse_ignored, + }, + { .name = { "network", NULL }, + .has_arg = IPSET_MANDATORY_ARG, .opt = IPSET_OPT_IP, + .parse = ipset_parse_ignored, + }, + { }, +}; + +static const struct ipset_arg hash_ipportip_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_ipportip_usage[] = +"create SETNAME hash:ip,port,ip\n" +" [family inet|inet6]\n" +" [hashsize VALUE] [maxelem VALUE]\n" +" [timeout VALUE]\n" +"add SETNAME IP,PROTO:PORT,IP [timeout VALUE]\n" +"del SETNAME IP,PROTO:PORT,IP\n" +"test SETNAME IP,PROTO:PORT,IP\n\n" +"where depending on the INET family\n" +" IP is a valid IPv4 or IPv6 address (or hostname).\n" +" Adding/deleting multiple elements in IP/CIDR or FROM-TO form\n" +" in the first IP component is supported for IPv4.\n" +" Adding/deleting multiple elements with TCP/UDP port range\n" +" is supported both for IPv4 and IPv6.\n"; + +struct ipset_type ipset_hash_ipportip0 = { + .name = "hash:ip,port,ip", + .alias = { "ipportiphash", NULL }, + .revision = 0, + .family = AF_INET46, + .dimension = IPSET_DIM_THREE, + .elem = { + [IPSET_DIM_ONE] = { + .parse = ipset_parse_ip4_single6, + .print = ipset_print_ip, + .opt = IPSET_OPT_IP + }, + [IPSET_DIM_TWO] = { + .parse = ipset_parse_proto_port, + .print = ipset_print_proto_port, + .opt = IPSET_OPT_PORT + }, + [IPSET_DIM_THREE] = { + .parse = ipset_parse_single_ip, + .print = ipset_print_ip, + .opt = IPSET_OPT_IP2 + }, + }, + .args = { + [IPSET_CREATE] = hash_ipportip_create_args, + [IPSET_ADD] = hash_ipportip_add_args, + }, + .mandatory = { + [IPSET_CREATE] = 0, + [IPSET_ADD] = IPSET_FLAG(IPSET_OPT_IP) + | IPSET_FLAG(IPSET_OPT_PORT) + | IPSET_FLAG(IPSET_OPT_PROTO) + | IPSET_FLAG(IPSET_OPT_IP2), + [IPSET_DEL] = IPSET_FLAG(IPSET_OPT_IP) + | IPSET_FLAG(IPSET_OPT_PORT) + | IPSET_FLAG(IPSET_OPT_PROTO) + | IPSET_FLAG(IPSET_OPT_IP2), + [IPSET_TEST] = IPSET_FLAG(IPSET_OPT_IP) + | IPSET_FLAG(IPSET_OPT_PORT) + | IPSET_FLAG(IPSET_OPT_PROTO) + | IPSET_FLAG(IPSET_OPT_IP2), + }, + .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_IP_TO) + | IPSET_FLAG(IPSET_OPT_PORT) + | IPSET_FLAG(IPSET_OPT_PORT_TO) + | IPSET_FLAG(IPSET_OPT_PROTO) + | IPSET_FLAG(IPSET_OPT_IP2) + | IPSET_FLAG(IPSET_OPT_TIMEOUT), + [IPSET_DEL] = IPSET_FLAG(IPSET_OPT_IP) + | IPSET_FLAG(IPSET_OPT_IP_TO) + | IPSET_FLAG(IPSET_OPT_PORT) + | IPSET_FLAG(IPSET_OPT_PORT_TO) + | IPSET_FLAG(IPSET_OPT_PROTO) + | IPSET_FLAG(IPSET_OPT_IP2), + [IPSET_TEST] = IPSET_FLAG(IPSET_OPT_IP) + | IPSET_FLAG(IPSET_OPT_PORT) + | IPSET_FLAG(IPSET_OPT_PROTO) + | IPSET_FLAG(IPSET_OPT_IP2), + }, + + .usage = hash_ipportip_usage, + .usagefn = ipset_port_usage, +}; diff --git a/extensions/ipset-5/src/ipset_hash_ipportnet.c b/extensions/ipset-5/src/ipset_hash_ipportnet.c new file mode 100644 index 0000000..ff3a8ec --- /dev/null +++ b/extensions/ipset-5/src/ipset_hash_ipportnet.c @@ -0,0 +1,159 @@ +/* Copyright 2007-2010 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_ipportnet_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, + }, + /* Backward compatibility */ + { .name = { "probes", NULL }, + .has_arg = IPSET_MANDATORY_ARG, .opt = IPSET_OPT_PROBES, + .parse = ipset_parse_ignored, .print = ipset_print_number, + }, + { .name = { "resize", NULL }, + .has_arg = IPSET_MANDATORY_ARG, .opt = IPSET_OPT_RESIZE, + .parse = ipset_parse_ignored, .print = ipset_print_number, + }, + { .name = { "from", NULL }, + .has_arg = IPSET_MANDATORY_ARG, .opt = IPSET_OPT_IP, + .parse = ipset_parse_ignored, + }, + { .name = { "to", NULL }, + .has_arg = IPSET_MANDATORY_ARG, .opt = IPSET_OPT_IP_TO, + .parse = ipset_parse_ignored, + }, + { .name = { "network", NULL }, + .has_arg = IPSET_MANDATORY_ARG, .opt = IPSET_OPT_IP, + .parse = ipset_parse_ignored, + }, + { }, +}; + +static const struct ipset_arg hash_ipportnet_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_ipportnet_usage[] = +"create SETNAME hash:ip,port,net\n" +" [family inet|inet6]\n" +" [hashsize VALUE] [maxelem VALUE]\n" +" [timeout VALUE]\n" +"add SETNAME IP,PROTO:PORT,IP[/CIDR] [timeout VALUE]\n" +"del SETNAME IP,PROTO:PORT,IP[/CIDR]\n" +"test SETNAME IP,PROTO:PORT,IP[/CIDR]\n\n" +"where depending on the INET family\n" +" IP are valid IPv4 or IPv6 addresses (or hostnames),\n" +" CIDR is a valid IPv4 or IPv6 CIDR prefix.\n" +" Adding/deleting multiple elements in IP/CIDR or FROM-TO form\n" +" in the first IP component is supported for IPv4.\n" +" Adding/deleting multiple elements with TCP/UDP port range\n" +" is supported both for IPv4 and IPv6.\n"; + +struct ipset_type ipset_hash_ipportnet0 = { + .name = "hash:ip,port,net", + .alias = { "ipportnethash", NULL }, + .revision = 0, + .family = AF_INET46, + .dimension = IPSET_DIM_THREE, + .elem = { + [IPSET_DIM_ONE] = { + .parse = ipset_parse_ip4_single6, + .print = ipset_print_ip, + .opt = IPSET_OPT_IP + }, + [IPSET_DIM_TWO] = { + .parse = ipset_parse_proto_port, + .print = ipset_print_proto_port, + .opt = IPSET_OPT_PORT + }, + [IPSET_DIM_THREE] = { + .parse = ipset_parse_ipnet, + .print = ipset_print_ip, + .opt = IPSET_OPT_IP2 + }, + }, + .args = { + [IPSET_CREATE] = hash_ipportnet_create_args, + [IPSET_ADD] = hash_ipportnet_add_args, + }, + .mandatory = { + [IPSET_CREATE] = 0, + [IPSET_ADD] = IPSET_FLAG(IPSET_OPT_IP) + | IPSET_FLAG(IPSET_OPT_PORT) + | IPSET_FLAG(IPSET_OPT_PROTO) + | IPSET_FLAG(IPSET_OPT_IP2), + [IPSET_DEL] = IPSET_FLAG(IPSET_OPT_IP) + | IPSET_FLAG(IPSET_OPT_PORT) + | IPSET_FLAG(IPSET_OPT_PROTO) + | IPSET_FLAG(IPSET_OPT_IP2), + [IPSET_TEST] = IPSET_FLAG(IPSET_OPT_IP) + | IPSET_FLAG(IPSET_OPT_PORT) + | IPSET_FLAG(IPSET_OPT_PROTO) + | IPSET_FLAG(IPSET_OPT_IP2), + }, + .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_IP_TO) + | IPSET_FLAG(IPSET_OPT_PORT) + | IPSET_FLAG(IPSET_OPT_PORT_TO) + | IPSET_FLAG(IPSET_OPT_PROTO) + | IPSET_FLAG(IPSET_OPT_IP2) + | IPSET_FLAG(IPSET_OPT_CIDR2) + | IPSET_FLAG(IPSET_OPT_TIMEOUT), + [IPSET_DEL] = IPSET_FLAG(IPSET_OPT_IP) + | IPSET_FLAG(IPSET_OPT_IP_TO) + | IPSET_FLAG(IPSET_OPT_PORT) + | IPSET_FLAG(IPSET_OPT_PORT_TO) + | IPSET_FLAG(IPSET_OPT_PROTO) + | IPSET_FLAG(IPSET_OPT_IP2) + | IPSET_FLAG(IPSET_OPT_CIDR2), + [IPSET_TEST] = IPSET_FLAG(IPSET_OPT_IP) + | IPSET_FLAG(IPSET_OPT_PORT) + | IPSET_FLAG(IPSET_OPT_PROTO) + | IPSET_FLAG(IPSET_OPT_IP2) + | IPSET_FLAG(IPSET_OPT_CIDR2), + }, + + .usage = hash_ipportnet_usage, + .usagefn = ipset_port_usage, +}; diff --git a/extensions/ipset-5/src/ipset_hash_net.c b/extensions/ipset-5/src/ipset_hash_net.c new file mode 100644 index 0000000..e8891c1 --- /dev/null +++ b/extensions/ipset-5/src/ipset_hash_net.c @@ -0,0 +1,109 @@ +/* Copyright 2007-2010 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 /* prototypes */ + +/* Parse commandline arguments */ +static const struct ipset_arg hash_net_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, + }, + /* Ignored options: backward compatibilty */ + { .name = { "probes", NULL }, + .has_arg = IPSET_MANDATORY_ARG, .opt = IPSET_OPT_PROBES, + .parse = ipset_parse_ignored, .print = ipset_print_number, + }, + { .name = { "resize", NULL }, + .has_arg = IPSET_MANDATORY_ARG, .opt = IPSET_OPT_RESIZE, + .parse = ipset_parse_ignored, .print = ipset_print_number, + }, + { }, +}; + +static const struct ipset_arg hash_net_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_net_usage[] = +"create SETNAME hash:net\n" +" [family inet|inet6]\n" +" [hashsize VALUE] [maxelem VALUE]\n" +" [timeout VALUE]\n" +"add SETNAME IP[/CIDR] [timeout VALUE]\n" +"del SETNAME IP[/CIDR]\n" +"test SETNAME IP[/CIDR]\n\n" +"where depending on the INET family\n" +" IP is an IPv4 or IPv6 address (or hostname),\n" +" CIDR is a valid IPv4 or IPv6 CIDR prefix.\n"; + +struct ipset_type ipset_hash_net0 = { + .name = "hash:net", + .alias = { "nethash", NULL }, + .revision = 0, + .family = AF_INET46, + .dimension = IPSET_DIM_ONE, + .elem = { + [IPSET_DIM_ONE] = { + .parse = ipset_parse_ipnet, + .print = ipset_print_ip, + .opt = IPSET_OPT_IP + }, + }, + .args = { + [IPSET_CREATE] = hash_net_create_args, + [IPSET_ADD] = hash_net_add_args, + }, + .mandatory = { + [IPSET_CREATE] = 0, + [IPSET_ADD] = IPSET_FLAG(IPSET_OPT_IP), + [IPSET_DEL] = IPSET_FLAG(IPSET_OPT_IP), + [IPSET_TEST] = IPSET_FLAG(IPSET_OPT_IP), + }, + .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_TIMEOUT), + [IPSET_DEL] = IPSET_FLAG(IPSET_OPT_IP) + | IPSET_FLAG(IPSET_OPT_CIDR), + [IPSET_TEST] = IPSET_FLAG(IPSET_OPT_IP) + | IPSET_FLAG(IPSET_OPT_CIDR), + }, + + .usage = hash_net_usage, +}; diff --git a/extensions/ipset-5/src/ipset_hash_netport.c b/extensions/ipset-5/src/ipset_hash_netport.c new file mode 100644 index 0000000..843ef31 --- /dev/null +++ b/extensions/ipset-5/src/ipset_hash_netport.c @@ -0,0 +1,122 @@ +/* Copyright 2007-2010 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_netport_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_netport_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_netport_usage[] = +"create SETNAME hash:net,port\n" +" [family inet|inet6]\n" +" [hashsize VALUE] [maxelem VALUE]\n" +" [timeout VALUE]\n" +"add SETNAME IP[/CIDR],PROTO:PORT [timeout VALUE]\n" +"del SETNAME IP[/CIDR],PROTO:PORT\n" +"test SETNAME IP[/CIDR],PROTO:PORT\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 TCP/UDP port range supported.\n"; + +struct ipset_type ipset_hash_netport0 = { + .name = "hash:net,port", + .alias = { "netporthash", NULL }, + .revision = 0, + .family = AF_INET46, + .dimension = IPSET_DIM_TWO, + .elem = { + [IPSET_DIM_ONE] = { + .parse = ipset_parse_ipnet, + .print = ipset_print_ip, + .opt = IPSET_OPT_IP + }, + [IPSET_DIM_TWO] = { + .parse = ipset_parse_proto_port, + .print = ipset_print_proto_port, + .opt = IPSET_OPT_PORT + }, + }, + .args = { + [IPSET_CREATE] = hash_netport_create_args, + [IPSET_ADD] = hash_netport_add_args, + }, + .mandatory = { + [IPSET_CREATE] = 0, + [IPSET_ADD] = IPSET_FLAG(IPSET_OPT_IP) + | IPSET_FLAG(IPSET_OPT_PROTO) + | IPSET_FLAG(IPSET_OPT_PORT), + [IPSET_DEL] = IPSET_FLAG(IPSET_OPT_IP) + | IPSET_FLAG(IPSET_OPT_PROTO) + | IPSET_FLAG(IPSET_OPT_PORT), + [IPSET_TEST] = IPSET_FLAG(IPSET_OPT_IP) + | IPSET_FLAG(IPSET_OPT_PROTO) + | IPSET_FLAG(IPSET_OPT_PORT), + }, + .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_PORT) + | IPSET_FLAG(IPSET_OPT_PORT_TO) + | IPSET_FLAG(IPSET_OPT_PROTO) + | IPSET_FLAG(IPSET_OPT_TIMEOUT) + | IPSET_FLAG(IPSET_OPT_CIDR), + [IPSET_DEL] = IPSET_FLAG(IPSET_OPT_IP) + | IPSET_FLAG(IPSET_OPT_PORT) + | IPSET_FLAG(IPSET_OPT_PORT_TO) + | IPSET_FLAG(IPSET_OPT_PROTO) + | IPSET_FLAG(IPSET_OPT_CIDR), + [IPSET_TEST] = IPSET_FLAG(IPSET_OPT_IP) + | IPSET_FLAG(IPSET_OPT_PORT) + | IPSET_FLAG(IPSET_OPT_PROTO) + | IPSET_FLAG(IPSET_OPT_CIDR), + }, + + .usage = hash_netport_usage, + .usagefn = ipset_port_usage, +}; diff --git a/extensions/ipset-5/src/ipset_list_set.c b/extensions/ipset-5/src/ipset_list_set.c new file mode 100644 index 0000000..7e7ddff --- /dev/null +++ b/extensions/ipset-5/src/ipset_list_set.c @@ -0,0 +1,91 @@ +/* Copyright 2007-2010 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 /* prototypes */ + +/* Parse commandline arguments */ +static const struct ipset_arg list_set_create_args[] = { + { .name = { "size", NULL }, + .has_arg = IPSET_MANDATORY_ARG, .opt = IPSET_OPT_SIZE, + .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 list_set_adt_args[] = { + { .name = { "timeout", NULL }, + .has_arg = IPSET_MANDATORY_ARG, .opt = IPSET_OPT_TIMEOUT, + .parse = ipset_parse_uint32, .print = ipset_print_number, + }, + { .name = { "before", NULL }, + .has_arg = IPSET_MANDATORY_ARG, .opt = IPSET_OPT_NAMEREF, + .parse = ipset_parse_before, + }, + { .name = { "after", NULL }, + .has_arg = IPSET_MANDATORY_ARG, .opt = IPSET_OPT_NAMEREF, + .parse = ipset_parse_after, + }, + { }, +}; + +static const char list_set_usage[] = +"create SETNAME list:set\n" +" [size VALUE] [timeout VALUE]\n" +"add SETNAME NAME [before|after NAME] [timeout VALUE]\n" +"del SETNAME NAME [before|after NAME]\n" +"test SETNAME NAME [before|after NAME]\n\n" +"where NAME are existing set names.\n"; + +struct ipset_type ipset_list_set0 = { + .name = "list:set", + .alias = { "setlist", NULL }, + .revision = 0, + .family = AF_UNSPEC, + .dimension = IPSET_DIM_ONE, + .elem = { + [IPSET_DIM_ONE] = { + .parse = ipset_parse_setname, + .print = ipset_print_name, + .opt = IPSET_OPT_NAME + }, + }, + .compat_parse_elem = ipset_parse_name_compat, + .args = { + [IPSET_CREATE] = list_set_create_args, + [IPSET_ADD] = list_set_adt_args, + [IPSET_DEL] = list_set_adt_args, + [IPSET_TEST] = list_set_adt_args, + }, + .mandatory = { + [IPSET_CREATE] = 0, + [IPSET_ADD] = IPSET_FLAG(IPSET_OPT_NAME), + [IPSET_DEL] = IPSET_FLAG(IPSET_OPT_NAME), + [IPSET_TEST] = IPSET_FLAG(IPSET_OPT_NAME), + }, + .full = { + [IPSET_CREATE] = IPSET_FLAG(IPSET_OPT_SIZE) + | IPSET_FLAG(IPSET_OPT_TIMEOUT), + [IPSET_ADD] = IPSET_FLAG(IPSET_OPT_NAME) + | IPSET_FLAG(IPSET_OPT_BEFORE) + | IPSET_FLAG(IPSET_OPT_NAMEREF) + | IPSET_FLAG(IPSET_OPT_TIMEOUT), + [IPSET_DEL] = IPSET_FLAG(IPSET_OPT_NAME) + | IPSET_FLAG(IPSET_OPT_BEFORE) + | IPSET_FLAG(IPSET_OPT_NAMEREF), + [IPSET_TEST] = IPSET_FLAG(IPSET_OPT_NAME) + | IPSET_FLAG(IPSET_OPT_BEFORE) + | IPSET_FLAG(IPSET_OPT_NAMEREF), + }, + + .usage = list_set_usage, +}; diff --git a/extensions/ipset-5/src/ui.c b/extensions/ipset-5/src/ui.c new file mode 100644 index 0000000..176e1b2 --- /dev/null +++ b/extensions/ipset-5/src/ui.c @@ -0,0 +1,276 @@ +/* Copyright 2007-2010 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 /* assert */ +#include /* tolower */ +#include /* memcmp, str* */ + +#include /* IPSET_CMD_* */ +#include /* id_to_icmp */ +#include /* id_to_icmpv6 */ +#include /* IPSET_*_ARG */ +#include /* ipset_envopt_parse */ +#include /* ipset_parse_family */ +#include /* ipset_print_family */ +#include /* STREQ */ +#include /* prototypes */ + +/* Commands and environment options */ + +const struct ipset_commands ipset_commands[] = { + /* Order is important */ + + { /* c[reate], --create, n, -N */ + .cmd = IPSET_CMD_CREATE, + .name = { "create", "n" }, + .has_arg = IPSET_MANDATORY_ARG2, + .help = "SETNAME TYPENAME [type-specific-options]\n" + " Create a new set", + }, + { /* a[dd], --add, -A */ + .cmd = IPSET_CMD_ADD, + .name = { "add", NULL }, + .has_arg = IPSET_MANDATORY_ARG2, + .help = "SETNAME ENTRY\n" + " Add entry to the named set", + }, + { /* d[el], --del, -D */ + .cmd = IPSET_CMD_DEL, + .name = { "del", NULL }, + .has_arg = IPSET_MANDATORY_ARG2, + .help = "SETNAME ENTRY\n" + " Delete entry from the named set", + }, + { /* t[est], --test, -T */ + .cmd = IPSET_CMD_TEST, + .name = { "test", NULL }, + .has_arg = IPSET_MANDATORY_ARG2, + .help = "SETNAME ENTRY\n" + " Test entry in the named set", + }, + { /* des[troy], --destroy, x, -X */ + .cmd = IPSET_CMD_DESTROY, + .name = { "destroy", "x" }, + .has_arg = IPSET_OPTIONAL_ARG, + .help = "[SETNAME]\n" + " Destroy a named set or all sets", + }, + { /* l[ist], --list, -L */ + .cmd = IPSET_CMD_LIST, + .name = { "list", NULL }, + .has_arg = IPSET_OPTIONAL_ARG, + .help = "[SETNAME]\n" + " List the entries of a named set or all sets", + }, + { /* s[save], --save, -S */ + .cmd = IPSET_CMD_SAVE, + .name = { "save", NULL }, + .has_arg = IPSET_OPTIONAL_ARG, + .help = "[SETNAME]\n" + " Save the named set or all sets to stdout", + }, + { /* r[estore], --restore, -R */ + .cmd = IPSET_CMD_RESTORE, + .name = { "restore", NULL }, + .has_arg = IPSET_NO_ARG, + .help = "\n" + " Restore a saved state", + }, + { /* f[lush], --flush, -F */ + .cmd = IPSET_CMD_FLUSH, + .name = { "flush", NULL }, + .has_arg = IPSET_OPTIONAL_ARG, + .help = "[SETNAME]\n" + " Flush a named set or all sets", + }, + { /* ren[ame], --rename, e, -E */ + .cmd = IPSET_CMD_RENAME, + .name = { "rename", "e" }, + .has_arg = IPSET_MANDATORY_ARG2, + .help = "FROM-SETNAME TO-SETNAME\n" + " Rename two sets", + }, + { /* sw[ap], --swap, w, -W */ + .cmd = IPSET_CMD_SWAP, + .name = { "swap", "w" }, + .has_arg = IPSET_MANDATORY_ARG2, + .help = "FROM-SETNAME TO-SETNAME\n" + " Swap the contect of two existing sets", + }, + { /* h[elp, --help, -H */ + .cmd = IPSET_CMD_HELP, + .name = { "help", NULL }, + .has_arg = IPSET_OPTIONAL_ARG, + .help = "[TYPENAME]\n" + " Print help, and settype specific help", + }, + { /* v[ersion], --version, -V */ + .cmd = IPSET_CMD_VERSION, + .name = { "version", NULL }, + .has_arg = IPSET_NO_ARG, + .help = "\n" + " Print version information", + }, + { /* q[uit] */ + .cmd = IPSET_CMD_QUIT, + .name = { "quit", NULL }, + .has_arg = IPSET_NO_ARG, + .help = "\n" + " Quit interactive mode", + }, + { }, +}; + +/* Match a command: try to match as a prefix or letter-command */ +bool +ipset_match_cmd(const char *arg, const char * const name[]) +{ + size_t len; + + assert(arg); + assert(name && name[0]); + + /* Ignore (two) leading dashes */ + if (arg[0] == '-') + arg++; + if (arg[0] == '-') + arg++; + + len = strlen(arg); + + if (len > strlen(name[0]) || !len) + return false; + else if (strncmp(arg, name[0], len) == 0) + return true; + else if (len != 1) + return false; + else if (name[1] == NULL) + return tolower(arg[0]) == name[0][0]; + else + return tolower(arg[0]) == name[1][0]; +} + +const struct ipset_envopts ipset_envopts[] = { + { .name = { "-o", "-output" }, + .has_arg = IPSET_MANDATORY_ARG, .flag = IPSET_OPT_MAX, + .parse = ipset_parse_output, + .help = "plain|save|xml\n" + " Specify output mode for listing sets.\n" + " Default value for \"list\" command is mode \"plain\"\n" + " and for \"save\" command is mode \"save\".", + }, + { .name = { "-s", "-sorted" }, + .parse = ipset_envopt_parse, + .has_arg = IPSET_NO_ARG, .flag = IPSET_ENV_SORTED, + .help = "\n" + " Print elements sorted (if supported by the set type).", + }, + { .name = { "-q", "-quiet" }, + .parse = ipset_envopt_parse, + .has_arg = IPSET_NO_ARG, .flag = IPSET_ENV_QUIET, + .help = "\n" + " Suppress any notice or warning message.", + }, + { .name = { "-r", "-resolve" }, + .parse = ipset_envopt_parse, + .has_arg = IPSET_NO_ARG, .flag = IPSET_ENV_RESOLVE, + .help = "\n" + " Try to resolve IP addresses in the output (slow!)", + }, + { .name = { "-!", "-exist" }, + .parse = ipset_envopt_parse, + .has_arg = IPSET_NO_ARG, .flag = IPSET_ENV_EXIST, + .help = "\n" + " Ignore errors when creating already created sets,\n" + " when adding already existing elements\n" + " or when deleting non-existing elements.", + }, + { }, +}; + +/* Strict option matching */ +bool +ipset_match_option(const char *arg, const char * const name[]) +{ + assert(arg); + assert(name && name[0]); + + /* Skip two leading dashes */ + if (arg[0] == '-' && arg[1] == '-') + arg++, arg++; + + return STREQ(arg, name[0]) + || (name[1] != NULL && STREQ(arg, name[1])); +} + +/* Strict envopt matching */ +bool +ipset_match_envopt(const char *arg, const char * const name[]) +{ + assert(arg); + assert(name && name[0]); + + /* Skip one leading dash */ + if (arg[0] == '-' && arg[1] == '-') + arg++; + + return STREQ(arg, name[0]) + || (name[1] != NULL && STREQ(arg, name[1])); +} + +/** + * ipset_shift_argv - shift off an argument + * @arc: argument count + * @argv: array of argument strings + * @from: from where shift off an argument + * + * Shift off the argument at "from" from the array of + * arguments argv of size argc. + */ +void +ipset_shift_argv(int *argc, char *argv[], int from) +{ + int i; + + assert(*argc >= from + 1); + + for (i = from + 1; i <= *argc; i++) { + argv[i-1] = argv[i]; + } + (*argc)--; + return; +} + +/** + * ipset_port_usage - prints the usage for the port parameter + * + * Print the usage for the port parameter to stdout. + */ +void +ipset_port_usage(void) +{ + int i; + const char *name; + + printf(" [PROTO:]PORT is a valid pattern of the following:\n" + " PORTNAME port name from /etc/services\n" + " PORTNUMBER port number identifier\n" + " tcp|udp:PORTNAME|PORTNUMBER\n" + " icmp:CODENAME supported ICMP codename\n" + " icmp:TYPE/CODE ICMP type/code value\n" + " icmpv6:CODENAME supported ICMPv6 codename\n" + " icmpv6:TYPE/CODE ICMPv6 type/code value\n" + " PROTO:0 all other protocols\n\n"); + + printf(" Supported ICMP codenames:\n"); + i = 0; + while ((name = id_to_icmp(i++)) != NULL) + printf(" %s\n", name); + printf(" Supported ICMPv6 codenames:\n"); + i = 0; + while ((name = id_to_icmpv6(i++)) != NULL) + printf(" %s\n", name); +} diff --git a/extensions/ipset-5/xt_set.c b/extensions/ipset-5/xt_set.c new file mode 100644 index 0000000..86e24e3 --- /dev/null +++ b/extensions/ipset-5/xt_set.c @@ -0,0 +1,419 @@ +/* Copyright (C) 2000-2002 Joakim Axelsson + * Patrick Schaaf + * Martin Josefsson + * 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 which implements the set match and SET target + * for netfilter/iptables. */ + +#include +#include +#include + +#include +#include "xt_set.h" + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Jozsef Kadlecsik "); +MODULE_DESCRIPTION("Xtables: IP set match and target module"); +MODULE_ALIAS("xt_SET"); +MODULE_ALIAS("ipt_set"); +MODULE_ALIAS("ip6t_set"); +MODULE_ALIAS("ipt_SET"); +MODULE_ALIAS("ip6t_SET"); + +static inline int +match_set(ip_set_id_t index, const struct sk_buff *skb, + u8 pf, u8 dim, u8 flags, int inv) +{ + if (ip_set_test(index, skb, pf, dim, flags)) + inv = !inv; + return inv; +} + +/* Revision 0 interface: backward compatible with netfilter/iptables */ + +/* Backward compatibility constrains (incomplete): + * 2.6.24: [NETLINK]: Introduce nested and byteorder flag to netlink attribute + * 2.6.25: is_vmalloc_addr(): Check if an address is within the vmalloc + * boundaries + * 2.6.27: rcu: split list.h and move rcu-protected lists into rculist.h + * 2.6.28: netfilter: ctnetlink: remove bogus module dependency between + * ctnetlink and nf_nat (nfnl_lock/nfnl_unlock) + * 2.6.29: generic swap(): introduce global macro swap(a, b) + * 2.6.31: netfilter: passive OS fingerprint xtables match + * 2.6.34: rcu: Add lockdep-enabled variants of rcu_dereference() + */ + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 34) +#error "Linux kernel version too old: must be >= 2.6.34" +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 35) +#define CHECK_OK 1 +#define CHECK_FAIL 0 +#else /* LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35) */ +#define CHECK_OK 0 +#define CHECK_FAIL (-EINVAL) +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 35) +static bool +set_match_v0(const struct sk_buff *skb, const struct xt_match_param *par) +#else /* LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35) */ +static bool +set_match_v0(const struct sk_buff *skb, struct xt_action_param *par) +#endif +{ + const struct xt_set_info_match_v0 *info = par->matchinfo; + + return match_set(info->match_set.index, skb, par->family, + info->match_set.u.compat.dim, + info->match_set.u.compat.flags, + info->match_set.u.compat.flags & IPSET_INV_MATCH); +} + +static void +compat_flags(struct xt_set_info_v0 *info) +{ + u_int8_t i; + + /* Fill out compatibility data according to enum ip_set_kopt */ + info->u.compat.dim = IPSET_DIM_ZERO; + if (info->u.flags[0] & IPSET_MATCH_INV) + info->u.compat.flags |= IPSET_INV_MATCH; + for (i = 0; i < IPSET_DIM_MAX-1 && info->u.flags[i]; i++) { + info->u.compat.dim++; + if (info->u.flags[i] & IPSET_SRC) + info->u.compat.flags |= (1<u.compat.dim); + } +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 35) +static bool +set_match_v0_checkentry(const struct xt_mtchk_param *par) +#else /* LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35) */ +static int +set_match_v0_checkentry(const struct xt_mtchk_param *par) +#endif +{ + struct xt_set_info_match_v0 *info = par->matchinfo; + ip_set_id_t index; + + index = ip_set_nfnl_get_byindex(info->match_set.index); + + if (index == IPSET_INVALID_ID) { + pr_warning("Cannot find set indentified by id %u to match", + info->match_set.index); + return CHECK_FAIL; /* error */ + } + if (info->match_set.u.flags[IPSET_DIM_MAX-1] != 0) { + pr_warning("That's nasty!"); + return CHECK_FAIL; /* error */ + } + + /* Fill out compatibility data */ + compat_flags(&info->match_set); + + return CHECK_OK; +} + +static void +set_match_v0_destroy(const struct xt_mtdtor_param *par) +{ + struct xt_set_info_match_v0 *info = par->matchinfo; + + ip_set_nfnl_put(info->match_set.index); +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 35) +static unsigned int +set_target_v0(struct sk_buff *skb, const struct xt_target_param *par) +#else /* LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35) */ +static unsigned int +set_target_v0(struct sk_buff *skb, const struct xt_action_param *par) +#endif +{ + const struct xt_set_info_target_v0 *info = par->targinfo; + + if (info->add_set.index != IPSET_INVALID_ID) + ip_set_add(info->add_set.index, skb, par->family, + info->add_set.u.compat.dim, + info->add_set.u.compat.flags); + if (info->del_set.index != IPSET_INVALID_ID) + ip_set_del(info->del_set.index, skb, par->family, + info->del_set.u.compat.dim, + info->del_set.u.compat.flags); + + return XT_CONTINUE; +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 35) +static bool +set_target_v0_checkentry(const struct xt_tgchk_param *par) +#else /* LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35) */ +static int +set_target_v0_checkentry(const struct xt_tgchk_param *par) +#endif +{ + struct xt_set_info_target_v0 *info = par->targinfo; + ip_set_id_t index; + + if (info->add_set.index != IPSET_INVALID_ID) { + index = ip_set_nfnl_get_byindex(info->add_set.index); + if (index == IPSET_INVALID_ID) { + pr_warning("cannot find add_set index %u as target", + info->add_set.index); + return CHECK_FAIL; /* error */ + } + } + + if (info->del_set.index != IPSET_INVALID_ID) { + index = ip_set_nfnl_get_byindex(info->del_set.index); + if (index == IPSET_INVALID_ID) { + pr_warning("cannot find del_set index %u as target", + info->del_set.index); + return CHECK_FAIL; /* error */ + } + } + if (info->add_set.u.flags[IPSET_DIM_MAX-1] != 0 + || info->del_set.u.flags[IPSET_DIM_MAX-1] != 0) { + pr_warning("That's nasty!"); + return CHECK_FAIL; /* error */ + } + + /* Fill out compatibility data */ + compat_flags(&info->add_set); + compat_flags(&info->del_set); + + return CHECK_OK; +} + +static void +set_target_v0_destroy(const struct xt_tgdtor_param *par) +{ + const struct xt_set_info_target_v0 *info = par->targinfo; + + if (info->add_set.index != IPSET_INVALID_ID) + ip_set_nfnl_put(info->add_set.index); + if (info->del_set.index != IPSET_INVALID_ID) + ip_set_nfnl_put(info->del_set.index); +} + +/* Revision 1: current interface to netfilter/iptables */ + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 35) +static bool +set_match(const struct sk_buff *skb, const struct xt_match_param *par) +#else /* LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35) */ +static bool +set_match(const struct sk_buff *skb, struct xt_action_param *par) +#endif +{ + const struct xt_set_info_match *info = par->matchinfo; + + return match_set(info->match_set.index, skb, par->family, + info->match_set.dim, + info->match_set.flags, + info->match_set.flags & IPSET_INV_MATCH); +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 35) +static bool +set_match_checkentry(const struct xt_mtchk_param *par) +#else /* LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35) */ +static int +set_match_checkentry(const struct xt_mtchk_param *par) +#endif +{ + struct xt_set_info_match *info = par->matchinfo; + ip_set_id_t index; + + index = ip_set_nfnl_get_byindex(info->match_set.index); + + if (index == IPSET_INVALID_ID) { + pr_warning("Cannot find set indentified by id %u to match", + info->match_set.index); + return CHECK_FAIL; /* error */ + } + if (info->match_set.dim > IPSET_DIM_MAX) { + pr_warning("That's nasty!"); + return CHECK_FAIL; /* error */ + } + + return CHECK_OK; +} + +static void +set_match_destroy(const struct xt_mtdtor_param *par) +{ + struct xt_set_info_match *info = par->matchinfo; + + ip_set_nfnl_put(info->match_set.index); +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 35) +static unsigned int +set_target(struct sk_buff *skb, const struct xt_target_param *par) +#else /* LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35) */ +static unsigned int +set_target(struct sk_buff *skb, const struct xt_action_param *par) +#endif +{ + const struct xt_set_info_target *info = par->targinfo; + + if (info->add_set.index != IPSET_INVALID_ID) + ip_set_add(info->add_set.index, + skb, par->family, + info->add_set.dim, + info->add_set.flags); + if (info->del_set.index != IPSET_INVALID_ID) + ip_set_del(info->del_set.index, + skb, par->family, + info->add_set.dim, + info->del_set.flags); + + return XT_CONTINUE; +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 35) +static bool +set_target_checkentry(const struct xt_tgchk_param *par) +#else /* LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35) */ +static int +set_target_checkentry(const struct xt_tgchk_param *par) +#endif +{ + const struct xt_set_info_target *info = par->targinfo; + ip_set_id_t index; + + if (info->add_set.index != IPSET_INVALID_ID) { + index = ip_set_nfnl_get_byindex(info->add_set.index); + if (index == IPSET_INVALID_ID) { + pr_warning("cannot find add_set index %u as target", + info->add_set.index); + return CHECK_FAIL; /* error */ + } + } + + if (info->del_set.index != IPSET_INVALID_ID) { + index = ip_set_nfnl_get_byindex(info->del_set.index); + if (index == IPSET_INVALID_ID) { + pr_warning("cannot find del_set index %u as target", + info->del_set.index); + return CHECK_FAIL; /* error */ + } + } + if (info->add_set.dim > IPSET_DIM_MAX + || info->del_set.flags > IPSET_DIM_MAX) { + pr_warning("That's nasty!"); + return CHECK_FAIL; /* error */ + } + + return CHECK_OK; +} + +static void +set_target_destroy(const struct xt_tgdtor_param *par) +{ + const struct xt_set_info_target *info = par->targinfo; + + if (info->add_set.index != IPSET_INVALID_ID) + ip_set_nfnl_put(info->add_set.index); + if (info->del_set.index != IPSET_INVALID_ID) + ip_set_nfnl_put(info->del_set.index); +} + +static struct xt_match set_matches[] __read_mostly = { + { + .name = "set", + .family = NFPROTO_IPV4, + .revision = 0, + .match = set_match_v0, + .matchsize = sizeof(struct xt_set_info_match_v0), + .checkentry = set_match_v0_checkentry, + .destroy = set_match_v0_destroy, + .me = THIS_MODULE + }, + { + .name = "set", + .family = NFPROTO_IPV4, + .revision = 1, + .match = set_match, + .matchsize = sizeof(struct xt_set_info_match), + .checkentry = set_match_checkentry, + .destroy = set_match_destroy, + .me = THIS_MODULE + }, + { + .name = "set", + .family = NFPROTO_IPV6, + .revision = 1, + .match = set_match, + .matchsize = sizeof(struct xt_set_info_match), + .checkentry = set_match_checkentry, + .destroy = set_match_destroy, + .me = THIS_MODULE + }, +}; + +static struct xt_target set_targets[] __read_mostly = { + { + .name = "SET", + .revision = 0, + .family = NFPROTO_IPV4, + .target = set_target_v0, + .targetsize = sizeof(struct xt_set_info_target_v0), + .checkentry = set_target_v0_checkentry, + .destroy = set_target_v0_destroy, + .me = THIS_MODULE + }, + { + .name = "SET", + .revision = 1, + .family = NFPROTO_IPV4, + .target = set_target, + .targetsize = sizeof(struct xt_set_info_target), + .checkentry = set_target_checkentry, + .destroy = set_target_destroy, + .me = THIS_MODULE + }, + { + .name = "SET", + .revision = 1, + .family = NFPROTO_IPV6, + .target = set_target, + .targetsize = sizeof(struct xt_set_info_target), + .checkentry = set_target_checkentry, + .destroy = set_target_destroy, + .me = THIS_MODULE + }, +}; + +static int __init xt_set_init(void) +{ + int ret = xt_register_matches(set_matches, ARRAY_SIZE(set_matches)); + + if (!ret) { + ret = xt_register_targets(set_targets, + ARRAY_SIZE(set_targets)); + if (ret) + xt_unregister_matches(set_matches, + ARRAY_SIZE(set_matches)); + } + return ret; +} + +static void __exit xt_set_fini(void) +{ + xt_unregister_matches(set_matches, ARRAY_SIZE(set_matches)); + xt_unregister_targets(set_targets, ARRAY_SIZE(set_targets)); +} + +module_init(xt_set_init); +module_exit(xt_set_fini); diff --git a/extensions/ipset-5/xt_set.h b/extensions/ipset-5/xt_set.h new file mode 100644 index 0000000..b5450a9 --- /dev/null +++ b/extensions/ipset-5/xt_set.h @@ -0,0 +1,55 @@ +#ifndef _XT_SET_H +#define _XT_SET_H + +#include "ip_set.h" + +/* Revision 0 interface: backward compatible with netfilter/iptables */ + +/* + * Option flags for kernel operations (xt_set_info_v0) + */ +#define IPSET_SRC 0x01 /* Source match/add */ +#define IPSET_DST 0x02 /* Destination match/add */ +#define IPSET_MATCH_INV 0x04 /* Inverse matching */ + +struct xt_set_info_v0 { + ip_set_id_t index; + union { + __u32 flags[IPSET_DIM_MAX + 1]; + struct { + __u32 __flags[IPSET_DIM_MAX]; + __u8 dim; + __u8 flags; + } compat; + } u; +}; + +/* match and target infos */ +struct xt_set_info_match_v0 { + struct xt_set_info_v0 match_set; +}; + +struct xt_set_info_target_v0 { + struct xt_set_info_v0 add_set; + struct xt_set_info_v0 del_set; +}; + +/* Revision 1: current interface to netfilter/iptables */ + +struct xt_set_info { + ip_set_id_t index; + __u8 dim; + __u8 flags; +}; + +/* match and target infos */ +struct xt_set_info_match { + struct xt_set_info match_set; +}; + +struct xt_set_info_target { + struct xt_set_info add_set; + struct xt_set_info del_set; +}; + +#endif /*_XT_SET_H*/ diff --git a/mconfig b/mconfig index 717603b..e096018 100644 --- a/mconfig +++ b/mconfig @@ -19,7 +19,8 @@ build_geoip=m build_gradm=m build_iface=m build_ipp2p=m -build_ipset=m +build_ipset4=m +build_ipset5= build_ipv4options=m build_length2=m build_lscan=m