*.loT
*.o
.deps
+.dirstamp
.libs
Makefile
Makefile.in
[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],
AC_SUBST([xtlibdir])
AC_CONFIG_FILES([Makefile Makefile.iptrules Makefile.mans geoip/Makefile
extensions/Makefile extensions/ACCOUNT/Makefile
- extensions/ipset-4/Makefile extensions/pknock/Makefile])
+ extensions/ipset-4/Makefile extensions/ipset-5/Makefile
+ extensions/pknock/Makefile])
AC_OUTPUT
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)
obj-${build_iface} += xt_iface.o
obj-${build_ipp2p} += xt_ipp2p.o
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
obj-${build_iface} += libxt_iface.so
obj-${build_ipp2p} += libxt_ipp2p.so
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
--- /dev/null
+# -*- 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
--- /dev/null
+# -*- 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
--- /dev/null
+# -*- Makefile -*-
+
--- /dev/null
+/* 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 <stdbool.h> /* bool */
+#include <libipset/nf_inet_addr.h> /* 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 */
--- /dev/null
+/* 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 <stdio.h>
+#include <sys/socket.h>
+#include <linux/netlink.h>
+#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 */
--- /dev/null
+/* 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 <libipset/linux_ip_set.h> /* 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 */
--- /dev/null
+/* 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 <stdint.h> /* 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 */
--- /dev/null
+/* 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 <stdint.h> /* 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 */
--- /dev/null
+#ifndef _IP_SET_H
+#define _IP_SET_H
+
+/* Copyright (C) 2000-2002 Joakim Axelsson <gozem@linux.nu>
+ * Patrick Schaaf <bof@bof.de>
+ * Martin Josefsson <gandalf@wlug.westbo.se>
+ * Copyright (C) 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.
+ */
+
+/* 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 */
--- /dev/null
+#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 */
--- /dev/null
+#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 */
--- /dev/null
+#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 */
--- /dev/null
+/* 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 <stdint.h> /* uintxx_t */
+#include <libmnl/libmnl.h> /* libmnl backend */
+
+#include <libipset/transport.h> /* 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 */
--- /dev/null
+/* 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 <stdint.h> /* uint32_t */
+#include <netinet/in.h> /* 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 */
--- /dev/null
+/* 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 <libipset/data.h> /* 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 */
--- /dev/null
+#ifndef _NET_PFXLEN_H
+#define _NET_PFXLEN_H 1
+
+#include <asm/byteorder.h>
+#ifdef HAVE_PFXLEN_H
+#include <linux/netfilter/pfxlen.h>
+#else
+
+#include <libipset/nf_inet_addr.h> /* 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
--- /dev/null
+/* 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 <libipset/data.h> /* 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 */
--- /dev/null
+/* 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 <stdbool.h> /* bool */
+#include <stdint.h> /* uintxx_t */
+#include <stdio.h> /* printf */
+
+#include <libipset/linux_ip_set.h> /* 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 */
--- /dev/null
+/* 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 <stdint.h> /* uintxx_t */
+#include <linux/netlink.h> /* struct nlmsghdr */
+
+#include <libmnl/libmnl.h> /* mnl_cb_t */
+
+#include <libipset/linux_ip_set.h> /* 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 */
--- /dev/null
+/* 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 <stddef.h> /* NULL */
+#include <stdint.h> /* uintxx_t */
+
+#include <libipset/data.h> /* enum ipset_opt */
+#include <libipset/parse.h> /* ipset_parsefn */
+#include <libipset/print.h> /* ipset_printfn */
+#include <libipset/linux_ip_set.h> /* 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 */
--- /dev/null
+/* 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 <libipset/linux_ip_set.h> /* 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 */
--- /dev/null
+/* 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 <string.h> /* strcmp */
+#include <netinet/in.h> /* 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 */
--- /dev/null
+/* Copyright (C) 2000-2002 Joakim Axelsson <gozem@linux.nu>
+ * Patrick Schaaf <bof@bof.de>
+ * Copyright (C) 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.
+ */
+
+/* Kernel module for IP set management */
+
+#include "ip_set_kernel.h"
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/ip.h>
+#include <linux/skbuff.h>
+#include <linux/spinlock.h>
+#include <linux/netlink.h>
+#include <linux/rculist.h>
+#include <net/netlink.h>
+
+#include <linux/netfilter.h>
+#include <linux/netfilter/nfnetlink.h>
+#include "ip_set.h"
+#include <net/genetlink.h>
+#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 <kadlec@blackhole.kfki.hu>");
+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);
--- /dev/null
+#ifndef _IP_SET_H
+#define _IP_SET_H
+
+/* Copyright (C) 2000-2002 Joakim Axelsson <gozem@linux.nu>
+ * Patrick Schaaf <bof@bof.de>
+ * Martin Josefsson <gandalf@wlug.westbo.se>
+ * Copyright (C) 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.
+ */
+
+/* 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 <linux/ip.h>
+#include <linux/ipv6.h>
+#include <linux/netlink.h>
+#include <linux/netfilter.h>
+#include <linux/vmalloc.h>
+#include <net/netlink.h>
+
+/* 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 */
--- /dev/null
+#ifndef _IP_SET_AHASH_H
+#define _IP_SET_AHASH_H
+
+#include <linux/rcupdate.h>
+#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
--- /dev/null
+#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 */
--- /dev/null
+/* Copyright (C) 2000-2002 Joakim Axelsson <gozem@linux.nu>
+ * Patrick Schaaf <bof@bof.de>
+ * Copyright (C) 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.
+ */
+
+/* Kernel module implementing an IP set type: the bitmap:ip type */
+
+#include "ip_set_kernel.h"
+#include <linux/module.h>
+#include <linux/ip.h>
+#include <linux/skbuff.h>
+#include <linux/errno.h>
+#include <linux/uaccess.h>
+#include <linux/bitops.h>
+#include <linux/spinlock.h>
+#include <linux/netlink.h>
+#include <linux/jiffies.h>
+#include <linux/timer.h>
+#include <net/netlink.h>
+#include <net/tcp.h>
+
+#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 <kadlec@blackhole.kfki.hu>");
+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);
--- /dev/null
+/* Copyright (C) 2000-2002 Joakim Axelsson <gozem@linux.nu>
+ * Patrick Schaaf <bof@bof.de>
+ * Martin Josefsson <gandalf@wlug.westbo.se>
+ * Copyright (C) 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.
+ */
+
+/* Kernel module implementing an IP set type: the bitmap:ip,mac type */
+
+#include "ip_set_kernel.h"
+#include <linux/module.h>
+#include <linux/ip.h>
+#include <linux/etherdevice.h>
+#include <linux/skbuff.h>
+#include <linux/errno.h>
+#include <linux/uaccess.h>
+#include <linux/bitops.h>
+#include <linux/spinlock.h>
+#include <linux/if_ether.h>
+#include <linux/netlink.h>
+#include <linux/jiffies.h>
+#include <linux/timer.h>
+#include <net/netlink.h>
+
+#include "ip_set.h"
+#include "ip_set_timeout.h"
+#include "ip_set_bitmap.h"
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>");
+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);
--- /dev/null
+/* Copyright (C) 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.
+ */
+
+/* Kernel module implementing an IP set type: the bitmap:port type */
+
+#include "ip_set_kernel.h"
+#include <linux/module.h>
+#include <linux/ip.h>
+#include <linux/tcp.h>
+#include <linux/udp.h>
+#include <linux/skbuff.h>
+#include <linux/errno.h>
+#include <linux/uaccess.h>
+#include <linux/bitops.h>
+#include <linux/spinlock.h>
+#include <linux/netlink.h>
+#include <linux/jiffies.h>
+#include <linux/timer.h>
+#include <net/netlink.h>
+
+#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 <kadlec@blackhole.kfki.hu>");
+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);
--- /dev/null
+#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
--- /dev/null
+#ifndef _IP_SET_GETPORT_H
+#define _IP_SET_GETPORT_H
+
+#ifdef __KERNEL__
+#include <linux/icmp.h>
+#include <linux/icmpv6.h>
+#include <linux/netfilter_ipv6/ip6_tables.h>
+#include <net/ip.h>
+
+#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*/
--- /dev/null
+#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 */
--- /dev/null
+/* Copyright (C) 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.
+ */
+
+/* Kernel module implementing an IP set type: the hash:ip type */
+
+#include "ip_set_kernel.h"
+#include "jhash.h"
+#include <linux/module.h>
+#include <linux/ip.h>
+#include <linux/skbuff.h>
+#include <linux/errno.h>
+#include <linux/uaccess.h>
+#include <linux/bitops.h>
+#include <linux/spinlock.h>
+#include <linux/random.h>
+#include <net/ip.h>
+#include <net/ipv6.h>
+#include <net/netlink.h>
+#include <net/tcp.h>
+
+#include <linux/netfilter.h>
+#include "ip_set.h"
+#include "ip_set_timeout.h"
+#include "ip_set_hash.h"
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>");
+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);
--- /dev/null
+/* Copyright (C) 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.
+ */
+
+/* Kernel module implementing an IP set type: the hash:ip,port type */
+
+#include "ip_set_kernel.h"
+#include "jhash.h"
+#include <linux/module.h>
+#include <linux/ip.h>
+#include <linux/skbuff.h>
+#include <linux/errno.h>
+#include <linux/uaccess.h>
+#include <linux/bitops.h>
+#include <linux/spinlock.h>
+#include <linux/random.h>
+#include <net/ip.h>
+#include <net/ipv6.h>
+#include <net/netlink.h>
+#include <net/tcp.h>
+
+#include <linux/netfilter.h>
+#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 <kadlec@blackhole.kfki.hu>");
+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);
--- /dev/null
+/* Copyright (C) 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.
+ */
+
+/* Kernel module implementing an IP set type: the hash:ip,port,ip type */
+
+#include "ip_set_kernel.h"
+#include "jhash.h"
+#include <linux/module.h>
+#include <linux/ip.h>
+#include <linux/skbuff.h>
+#include <linux/errno.h>
+#include <linux/uaccess.h>
+#include <linux/bitops.h>
+#include <linux/spinlock.h>
+#include <linux/random.h>
+#include <net/ip.h>
+#include <net/ipv6.h>
+#include <net/netlink.h>
+#include <net/tcp.h>
+
+#include <linux/netfilter.h>
+#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 <kadlec@blackhole.kfki.hu>");
+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);
--- /dev/null
+/* Copyright (C) 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.
+ */
+
+/* Kernel module implementing an IP set type: the hash:ip,port,net type */
+
+#include "ip_set_kernel.h"
+#include "jhash.h"
+#include <linux/module.h>
+#include <linux/ip.h>
+#include <linux/skbuff.h>
+#include <linux/errno.h>
+#include <asm/uaccess.h>
+#include <asm/bitops.h>
+#include <linux/spinlock.h>
+#include <linux/random.h>
+#include <net/ip.h>
+#include <net/ipv6.h>
+#include <net/netlink.h>
+#include <net/tcp.h>
+
+#include <linux/netfilter.h>
+#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 <kadlec@blackhole.kfki.hu>");
+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);
--- /dev/null
+/* Copyright (C) 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.
+ */
+
+/* Kernel module implementing an IP set type: the hash:net type */
+
+#include "ip_set_kernel.h"
+#include "jhash.h"
+#include <linux/module.h>
+#include <linux/ip.h>
+#include <linux/skbuff.h>
+#include <linux/errno.h>
+#include <linux/uaccess.h>
+#include <linux/bitops.h>
+#include <linux/spinlock.h>
+#include <linux/random.h>
+#include <net/ip.h>
+#include <net/ipv6.h>
+#include <net/netlink.h>
+
+#include <linux/netfilter.h>
+#include "ip_set.h"
+#include "ip_set_timeout.h"
+#include "ip_set_hash.h"
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>");
+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);
--- /dev/null
+/* Copyright (C) 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.
+ */
+
+/* Kernel module implementing an IP set type: the hash:net,port type */
+
+#include "ip_set_kernel.h"
+#include "jhash.h"
+#include <linux/module.h>
+#include <linux/ip.h>
+#include <linux/skbuff.h>
+#include <linux/errno.h>
+#include <linux/uaccess.h>
+#include <linux/bitops.h>
+#include <linux/spinlock.h>
+#include <linux/random.h>
+#include <net/ip.h>
+#include <net/ipv6.h>
+#include <net/netlink.h>
+
+#include <linux/netfilter.h>
+#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 <kadlec@blackhole.kfki.hu>");
+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);
--- /dev/null
+#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 <linux/kernel.h>
+
+#endif /* __KERNEL__ */
+
+#endif /*_IP_SET_H */
--- /dev/null
+#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 */
--- /dev/null
+/* Copyright (C) 2008-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.
+ */
+
+/* Kernel module implementing an IP set type: the list:set type */
+
+#include "ip_set_kernel.h"
+#include <linux/module.h>
+#include <linux/ip.h>
+#include <linux/skbuff.h>
+#include <linux/errno.h>
+
+#include "ip_set.h"
+#include "ip_set_timeout.h"
+#include "ip_set_list.h"
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>");
+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);
--- /dev/null
+#ifndef _IP_SET_TIMEOUT_H
+#define _IP_SET_TIMEOUT_H
+
+/* Copyright (C) 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.
+ */
+
+#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 */
--- /dev/null
+.\" Man page written by 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 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
--- /dev/null
+#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 <linux/bitops.h>
+#include <linux/unaligned/packed_struct.h>
+
+/* 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 */
--- /dev/null
+/* 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.h> /* assert */
+#include <arpa/inet.h> /* ntoh* */
+#include <net/ethernet.h> /* ETH_ALEN */
+#include <sys/socket.h> /* AF_ */
+#include <stdlib.h> /* malloc, free */
+#include <string.h> /* memset */
+
+#include <libipset/linux_ip_set.h> /* IPSET_MAXNAMELEN */
+#include <libipset/debug.h> /* D() */
+#include <libipset/types.h> /* struct ipset_type */
+#include <libipset/utils.h> /* inXcpy */
+#include <libipset/data.h> /* 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);
+}
--- /dev/null
+/* 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 <libipset/utils.h> /* STRNEQ */
+#include <libipset/icmp.h> /* 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;
+}
--- /dev/null
+/* 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 <libipset/utils.h> /* STRNEQ */
+#include <libipset/icmpv6.h> /* 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;
+}
--- /dev/null
+/* 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.h> /* assert */
+#include <errno.h> /* errno */
+#include <stdint.h>
+#include <stdlib.h> /* calloc, free */
+#include <time.h> /* time */
+#include <arpa/inet.h> /* hto* */
+
+#include <libipset/linux_ip_set.h> /* enum ipset_cmd */
+#include <libipset/debug.h> /* D() */
+#include <libipset/session.h> /* ipset_session_handle */
+#include <libipset/ui.h> /* IPSET_ENV_EXIST */
+#include <libipset/utils.h> /* UNUSED */
+#include <libipset/mnl.h> /* prototypes */
+
+#include <libmnl/libmnl.h>
+#include <linux/genetlink.h>
+#include <linux/netlink.h>
+
+#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,
+};
--- /dev/null
+/* 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.h> /* assert */
+#include <errno.h> /* errno */
+#include <limits.h> /* ULLONG_MAX */
+#include <netdb.h> /* getservbyname, getaddrinfo */
+#include <stdlib.h> /* strtoull, etc. */
+#include <sys/types.h> /* getaddrinfo */
+#include <sys/socket.h> /* getaddrinfo, AF_ */
+#include <net/ethernet.h> /* ETH_ALEN */
+#include <netinet/in.h> /* IPPROTO_ */
+
+#include <libipset/debug.h> /* D() */
+#include <libipset/data.h> /* IPSET_OPT_* */
+#include <libipset/icmp.h> /* name_to_icmp */
+#include <libipset/icmpv6.h> /* name_to_icmpv6 */
+#include <libipset/pfxlen.h> /* prefixlen_netmask_map */
+#include <libipset/session.h> /* ipset_err */
+#include <libipset/types.h> /* ipset_type_get */
+#include <libipset/utils.h> /* string utilities */
+#include <libipset/parse.h> /* 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;
+}
--- /dev/null
+/* 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.h> /* assert */
+#include <errno.h> /* errno */
+#include <stdio.h> /* snprintf */
+#include <netdb.h> /* getservbyport */
+#include <sys/types.h> /* inet_ntop */
+#include <sys/socket.h> /* inet_ntop */
+#include <arpa/inet.h> /* inet_ntop */
+#include <net/ethernet.h> /* ETH_ALEN */
+
+#include <libipset/debug.h> /* D() */
+#include <libipset/data.h> /* ipset_data_* */
+#include <libipset/icmp.h> /* icmp_to_name */
+#include <libipset/icmpv6.h> /* icmpv6_to_name */
+#include <libipset/parse.h> /* IPSET_*_SEPARATOR */
+#include <libipset/types.h> /* ipset set types */
+#include <libipset/session.h> /* IPSET_FLAG_ */
+#include <libipset/utils.h> /* UNUSED */
+#include <libipset/ui.h> /* IPSET_ENV_* */
+#include <libipset/print.h> /* 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;
+}
--- /dev/null
+/* 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.h> /* assert */
+#include <errno.h> /* errno */
+#include <setjmp.h> /* setjmp, longjmp */
+#include <stdio.h> /* snprintf */
+#include <stdarg.h> /* va_* */
+#include <stdlib.h> /* free */
+#include <string.h> /* str* */
+#include <unistd.h> /* getpagesize */
+#include <net/ethernet.h> /* ETH_ALEN */
+
+#include <libipset/debug.h> /* D() */
+#include <libipset/data.h> /* IPSET_OPT_* */
+#include <libipset/errcode.h> /* ipset_errcode */
+#include <libipset/print.h> /* ipset_print_* */
+#include <libipset/types.h> /* struct ipset_type */
+#include <libipset/transport.h> /* transport */
+#include <libipset/mnl.h> /* default backend */
+#include <libipset/utils.h> /* STREQ */
+#include <libipset/ui.h> /* IPSET_ENV_* */
+#include <libipset/session.h> /* prototypes */
+
+#include <linux/genetlink.h>
+
+#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, " <member>");
+ 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, "</%s>\n",
+ arg->name[0]);
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (session->mode == IPSET_LIST_XML)
+ safe_snprintf(session, "</member>\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,
+ "<ipset name=\"%s\">\n"
+ " <type>%s</type>\n"
+ " <header>\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, "</%s>\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, "</elements>\n <memsize>");
+ safe_dprintf(session, ipset_print_number, IPSET_OPT_MEMSIZE);
+ safe_snprintf(session, "</memsize>\n <references>");
+ safe_dprintf(session, ipset_print_number, IPSET_OPT_REFERENCES);
+ safe_snprintf(session, "</references>\n </header>\n <members>\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, " </members>\n</ipset>\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;
+}
--- /dev/null
+/* 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.h> /* assert */
+#include <errno.h> /* errno */
+#include <net/ethernet.h> /* ETH_ALEN */
+#include <netinet/in.h> /* struct in6_addr */
+#include <sys/socket.h> /* AF_ */
+#include <stdlib.h> /* malloc, free */
+#include <stdio.h> /* FIXME: debug */
+#include <libmnl/libmnl.h> /* MNL_ALIGN */
+
+#include <libipset/debug.h> /* D() */
+#include <libipset/data.h> /* ipset_data_* */
+#include <libipset/session.h> /* ipset_cmd */
+#include <libipset/utils.h> /* STREQ */
+#include <libipset/types.h> /* 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);
+ }
+}
--- /dev/null
+#ifndef _IP_SET_SLIST_H
+#define _IP_SET_SLIST_H
+
+#include <linux/stddef.h>
+#include <linux/prefetch.h>
+#include <asm/system.h>
+
+/*
+ * 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 */
--- /dev/null
+/* 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.h> /* assert */
+#include <errno.h> /* errno */
+#include <string.h> /* strerror */
+
+#include <libipset/debug.h> /* D() */
+#include <libipset/data.h> /* ipset_data_get */
+#include <libipset/session.h> /* ipset_err */
+#include <libipset/types.h> /* struct ipset_type */
+#include <libipset/utils.h> /* STRNEQ */
+#include <libipset/errcode.h> /* prototypes */
+#include <libipset/linux_ip_set_bitmap.h> /* bitmap specific errcodes */
+#include <libipset/linux_ip_set_hash.h> /* hash specific errcodes */
+#include <libipset/linux_ip_set_list.h> /* 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);
+}
--- /dev/null
+/* 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 <ctype.h> /* isspace */
+#include <stdarg.h> /* va_* */
+#include <stdbool.h> /* bool */
+#include <stdio.h> /* fprintf, fgets */
+#include <stdlib.h> /* exit */
+#include <string.h> /* str* */
+
+#include <config.h>
+
+#include <libipset/debug.h> /* D() */
+#include <libipset/data.h> /* enum ipset_data */
+#include <libipset/parse.h> /* ipset_parse_* */
+#include <libipset/session.h> /* ipset_session_* */
+#include <libipset/types.h> /* struct ipset_type */
+#include <libipset/ui.h> /* core options, commands */
+#include <libipset/utils.h> /* 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);
+}
--- /dev/null
+/* 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 <libipset/data.h> /* IPSET_OPT_* */
+#include <libipset/parse.h> /* parser functions */
+#include <libipset/print.h> /* printing functions */
+#include <libipset/types.h> /* 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,
+};
--- /dev/null
+/* 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 <libipset/data.h> /* IPSET_OPT_* */
+#include <libipset/parse.h> /* parser functions */
+#include <libipset/print.h> /* printing functions */
+#include <libipset/types.h> /* 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,
+};
--- /dev/null
+/* 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 <libipset/data.h> /* IPSET_OPT_* */
+#include <libipset/parse.h> /* parser functions */
+#include <libipset/print.h> /* printing functions */
+#include <libipset/types.h> /* 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,
+};
--- /dev/null
+/* 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 <libipset/data.h> /* IPSET_OPT_* */
+#include <libipset/parse.h> /* parser functions */
+#include <libipset/print.h> /* printing functions */
+#include <libipset/types.h> /* 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,
+};
--- /dev/null
+/* 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 <libipset/data.h> /* IPSET_OPT_* */
+#include <libipset/parse.h> /* parser functions */
+#include <libipset/print.h> /* printing functions */
+#include <libipset/ui.h> /* ipset_port_usage */
+#include <libipset/types.h> /* prototypes */
+
+/* Parse commandline arguments */
+static const struct ipset_arg hash_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,
+};
--- /dev/null
+/* 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 <libipset/data.h> /* IPSET_OPT_* */
+#include <libipset/parse.h> /* parser functions */
+#include <libipset/print.h> /* printing functions */
+#include <libipset/ui.h> /* ipset_port_usage */
+#include <libipset/types.h> /* prototypes */
+
+/* Parse commandline arguments */
+static const struct ipset_arg hash_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,
+};
--- /dev/null
+/* 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 <libipset/data.h> /* IPSET_OPT_* */
+#include <libipset/parse.h> /* parser functions */
+#include <libipset/print.h> /* printing functions */
+#include <libipset/ui.h> /* ipset_port_usage */
+#include <libipset/types.h> /* prototypes */
+
+/* Parse commandline arguments */
+static const struct ipset_arg hash_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,
+};
--- /dev/null
+/* 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 <libipset/data.h> /* IPSET_OPT_* */
+#include <libipset/parse.h> /* parser functions */
+#include <libipset/print.h> /* printing functions */
+#include <libipset/types.h> /* 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,
+};
--- /dev/null
+/* 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 <libipset/data.h> /* IPSET_OPT_* */
+#include <libipset/parse.h> /* parser functions */
+#include <libipset/print.h> /* printing functions */
+#include <libipset/ui.h> /* ipset_port_usage */
+#include <libipset/types.h> /* prototypes */
+
+/* Parse commandline arguments */
+static const struct ipset_arg hash_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,
+};
--- /dev/null
+/* 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 <libipset/data.h> /* IPSET_OPT_* */
+#include <libipset/parse.h> /* parser functions */
+#include <libipset/print.h> /* printing functions */
+#include <libipset/types.h> /* 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,
+};
--- /dev/null
+/* 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.h> /* assert */
+#include <ctype.h> /* tolower */
+#include <string.h> /* memcmp, str* */
+
+#include <libipset/linux_ip_set.h> /* IPSET_CMD_* */
+#include <libipset/icmp.h> /* id_to_icmp */
+#include <libipset/icmpv6.h> /* id_to_icmpv6 */
+#include <libipset/types.h> /* IPSET_*_ARG */
+#include <libipset/session.h> /* ipset_envopt_parse */
+#include <libipset/parse.h> /* ipset_parse_family */
+#include <libipset/print.h> /* ipset_print_family */
+#include <libipset/utils.h> /* STREQ */
+#include <libipset/ui.h> /* 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);
+}
--- /dev/null
+/* Copyright (C) 2000-2002 Joakim Axelsson <gozem@linux.nu>
+ * Patrick Schaaf <bof@bof.de>
+ * Martin Josefsson <gandalf@wlug.westbo.se>
+ * Copyright (C) 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.
+ */
+
+/* Kernel module which implements the set match and SET target
+ * for netfilter/iptables. */
+
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <linux/version.h>
+
+#include <linux/netfilter/x_tables.h>
+#include "xt_set.h"
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Jozsef Kadlecsik <kadlec@blackhole.kfki.hu>");
+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<<info->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);
--- /dev/null
+#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*/
build_iface=m
build_ipp2p=m
build_ipset4=m
+build_ipset5=
build_ipv4options=m
build_length2=m
build_lscan=m