| // SPDX-License-Identifier: GPL-2.0-only |
| /* Copyright (C) 2000-2002 Joakim Axelsson <gozem@linux.nu> |
| * Patrick Schaaf <bof@bof.de> |
| * Martin Josefsson <gandalf@wlug.westbo.se> |
| * Copyright (C) 2003-2013 Jozsef Kadlecsik <kadlec@netfilter.org> |
| */ |
| |
| /* Kernel module which implements the set match and SET target |
| * for netfilter/iptables. |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/skbuff.h> |
| |
| #include <linux/netfilter/x_tables.h> |
| #include <linux/netfilter/ipset/ip_set.h> |
| #include <uapi/linux/netfilter/xt_set.h> |
| |
| MODULE_LICENSE("GPL"); |
| MODULE_AUTHOR("Jozsef Kadlecsik <kadlec@netfilter.org>"); |
| 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, |
| const struct xt_action_param *par, |
| struct ip_set_adt_opt *opt, int inv) |
| { |
| if (ip_set_test(index, skb, par, opt)) |
| inv = !inv; |
| return inv; |
| } |
| |
| #define ADT_OPT(n, f, d, fs, cfs, t, p, b, po, bo) \ |
| struct ip_set_adt_opt n = { \ |
| .family = f, \ |
| .dim = d, \ |
| .flags = fs, \ |
| .cmdflags = cfs, \ |
| .ext.timeout = t, \ |
| .ext.packets = p, \ |
| .ext.bytes = b, \ |
| .ext.packets_op = po, \ |
| .ext.bytes_op = bo, \ |
| } |
| |
| /* Revision 0 interface: backward compatible with netfilter/iptables */ |
| |
| static bool |
| set_match_v0(const struct sk_buff *skb, struct xt_action_param *par) |
| { |
| const struct xt_set_info_match_v0 *info = par->matchinfo; |
| |
| ADT_OPT(opt, xt_family(par), info->match_set.u.compat.dim, |
| info->match_set.u.compat.flags, 0, UINT_MAX, |
| 0, 0, 0, 0); |
| |
| return match_set(info->match_set.index, skb, par, &opt, |
| 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); |
| } |
| } |
| |
| static int |
| set_match_v0_checkentry(const struct xt_mtchk_param *par) |
| { |
| struct xt_set_info_match_v0 *info = par->matchinfo; |
| ip_set_id_t index; |
| |
| index = ip_set_nfnl_get_byindex(par->net, info->match_set.index); |
| |
| if (index == IPSET_INVALID_ID) { |
| pr_info_ratelimited("Cannot find set identified by id %u to match\n", |
| info->match_set.index); |
| return -ENOENT; |
| } |
| if (info->match_set.u.flags[IPSET_DIM_MAX - 1] != 0) { |
| pr_info_ratelimited("set match dimension is over the limit!\n"); |
| ip_set_nfnl_put(par->net, info->match_set.index); |
| return -ERANGE; |
| } |
| |
| /* Fill out compatibility data */ |
| compat_flags(&info->match_set); |
| |
| return 0; |
| } |
| |
| 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(par->net, info->match_set.index); |
| } |
| |
| /* Revision 1 match */ |
| |
| static bool |
| set_match_v1(const struct sk_buff *skb, struct xt_action_param *par) |
| { |
| const struct xt_set_info_match_v1 *info = par->matchinfo; |
| |
| ADT_OPT(opt, xt_family(par), info->match_set.dim, |
| info->match_set.flags, 0, UINT_MAX, |
| 0, 0, 0, 0); |
| |
| if (opt.flags & IPSET_RETURN_NOMATCH) |
| opt.cmdflags |= IPSET_FLAG_RETURN_NOMATCH; |
| |
| return match_set(info->match_set.index, skb, par, &opt, |
| info->match_set.flags & IPSET_INV_MATCH); |
| } |
| |
| static int |
| set_match_v1_checkentry(const struct xt_mtchk_param *par) |
| { |
| struct xt_set_info_match_v1 *info = par->matchinfo; |
| ip_set_id_t index; |
| |
| index = ip_set_nfnl_get_byindex(par->net, info->match_set.index); |
| |
| if (index == IPSET_INVALID_ID) { |
| pr_info_ratelimited("Cannot find set identified by id %u to match\n", |
| info->match_set.index); |
| return -ENOENT; |
| } |
| if (info->match_set.dim > IPSET_DIM_MAX) { |
| pr_info_ratelimited("set match dimension is over the limit!\n"); |
| ip_set_nfnl_put(par->net, info->match_set.index); |
| return -ERANGE; |
| } |
| |
| return 0; |
| } |
| |
| static void |
| set_match_v1_destroy(const struct xt_mtdtor_param *par) |
| { |
| struct xt_set_info_match_v1 *info = par->matchinfo; |
| |
| ip_set_nfnl_put(par->net, info->match_set.index); |
| } |
| |
| /* Revision 3 match */ |
| |
| static bool |
| set_match_v3(const struct sk_buff *skb, struct xt_action_param *par) |
| { |
| const struct xt_set_info_match_v3 *info = par->matchinfo; |
| |
| ADT_OPT(opt, xt_family(par), info->match_set.dim, |
| info->match_set.flags, info->flags, UINT_MAX, |
| info->packets.value, info->bytes.value, |
| info->packets.op, info->bytes.op); |
| |
| if (info->packets.op != IPSET_COUNTER_NONE || |
| info->bytes.op != IPSET_COUNTER_NONE) |
| opt.cmdflags |= IPSET_FLAG_MATCH_COUNTERS; |
| |
| return match_set(info->match_set.index, skb, par, &opt, |
| info->match_set.flags & IPSET_INV_MATCH); |
| } |
| |
| #define set_match_v3_checkentry set_match_v1_checkentry |
| #define set_match_v3_destroy set_match_v1_destroy |
| |
| /* Revision 4 match */ |
| |
| static bool |
| set_match_v4(const struct sk_buff *skb, struct xt_action_param *par) |
| { |
| const struct xt_set_info_match_v4 *info = par->matchinfo; |
| |
| ADT_OPT(opt, xt_family(par), info->match_set.dim, |
| info->match_set.flags, info->flags, UINT_MAX, |
| info->packets.value, info->bytes.value, |
| info->packets.op, info->bytes.op); |
| |
| if (info->packets.op != IPSET_COUNTER_NONE || |
| info->bytes.op != IPSET_COUNTER_NONE) |
| opt.cmdflags |= IPSET_FLAG_MATCH_COUNTERS; |
| |
| return match_set(info->match_set.index, skb, par, &opt, |
| info->match_set.flags & IPSET_INV_MATCH); |
| } |
| |
| #define set_match_v4_checkentry set_match_v1_checkentry |
| #define set_match_v4_destroy set_match_v1_destroy |
| |
| /* Revision 0 interface: backward compatible with netfilter/iptables */ |
| |
| static unsigned int |
| set_target_v0(struct sk_buff *skb, const struct xt_action_param *par) |
| { |
| const struct xt_set_info_target_v0 *info = par->targinfo; |
| |
| ADT_OPT(add_opt, xt_family(par), info->add_set.u.compat.dim, |
| info->add_set.u.compat.flags, 0, UINT_MAX, |
| 0, 0, 0, 0); |
| ADT_OPT(del_opt, xt_family(par), info->del_set.u.compat.dim, |
| info->del_set.u.compat.flags, 0, UINT_MAX, |
| 0, 0, 0, 0); |
| |
| if (info->add_set.index != IPSET_INVALID_ID) |
| ip_set_add(info->add_set.index, skb, par, &add_opt); |
| if (info->del_set.index != IPSET_INVALID_ID) |
| ip_set_del(info->del_set.index, skb, par, &del_opt); |
| |
| return XT_CONTINUE; |
| } |
| |
| static int |
| set_target_v0_checkentry(const struct xt_tgchk_param *par) |
| { |
| 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(par->net, info->add_set.index); |
| if (index == IPSET_INVALID_ID) { |
| pr_info_ratelimited("Cannot find add_set index %u as target\n", |
| info->add_set.index); |
| return -ENOENT; |
| } |
| } |
| |
| if (info->del_set.index != IPSET_INVALID_ID) { |
| index = ip_set_nfnl_get_byindex(par->net, info->del_set.index); |
| if (index == IPSET_INVALID_ID) { |
| pr_info_ratelimited("Cannot find del_set index %u as target\n", |
| info->del_set.index); |
| if (info->add_set.index != IPSET_INVALID_ID) |
| ip_set_nfnl_put(par->net, info->add_set.index); |
| return -ENOENT; |
| } |
| } |
| if (info->add_set.u.flags[IPSET_DIM_MAX - 1] != 0 || |
| info->del_set.u.flags[IPSET_DIM_MAX - 1] != 0) { |
| pr_info_ratelimited("SET target dimension over the limit!\n"); |
| if (info->add_set.index != IPSET_INVALID_ID) |
| ip_set_nfnl_put(par->net, info->add_set.index); |
| if (info->del_set.index != IPSET_INVALID_ID) |
| ip_set_nfnl_put(par->net, info->del_set.index); |
| return -ERANGE; |
| } |
| |
| /* Fill out compatibility data */ |
| compat_flags(&info->add_set); |
| compat_flags(&info->del_set); |
| |
| return 0; |
| } |
| |
| 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(par->net, info->add_set.index); |
| if (info->del_set.index != IPSET_INVALID_ID) |
| ip_set_nfnl_put(par->net, info->del_set.index); |
| } |
| |
| /* Revision 1 target */ |
| |
| static unsigned int |
| set_target_v1(struct sk_buff *skb, const struct xt_action_param *par) |
| { |
| const struct xt_set_info_target_v1 *info = par->targinfo; |
| |
| ADT_OPT(add_opt, xt_family(par), info->add_set.dim, |
| info->add_set.flags, 0, UINT_MAX, |
| 0, 0, 0, 0); |
| ADT_OPT(del_opt, xt_family(par), info->del_set.dim, |
| info->del_set.flags, 0, UINT_MAX, |
| 0, 0, 0, 0); |
| |
| if (info->add_set.index != IPSET_INVALID_ID) |
| ip_set_add(info->add_set.index, skb, par, &add_opt); |
| if (info->del_set.index != IPSET_INVALID_ID) |
| ip_set_del(info->del_set.index, skb, par, &del_opt); |
| |
| return XT_CONTINUE; |
| } |
| |
| static int |
| set_target_v1_checkentry(const struct xt_tgchk_param *par) |
| { |
| const struct xt_set_info_target_v1 *info = par->targinfo; |
| ip_set_id_t index; |
| |
| if (info->add_set.index != IPSET_INVALID_ID) { |
| index = ip_set_nfnl_get_byindex(par->net, info->add_set.index); |
| if (index == IPSET_INVALID_ID) { |
| pr_info_ratelimited("Cannot find add_set index %u as target\n", |
| info->add_set.index); |
| return -ENOENT; |
| } |
| } |
| |
| if (info->del_set.index != IPSET_INVALID_ID) { |
| index = ip_set_nfnl_get_byindex(par->net, info->del_set.index); |
| if (index == IPSET_INVALID_ID) { |
| pr_info_ratelimited("Cannot find del_set index %u as target\n", |
| info->del_set.index); |
| if (info->add_set.index != IPSET_INVALID_ID) |
| ip_set_nfnl_put(par->net, info->add_set.index); |
| return -ENOENT; |
| } |
| } |
| if (info->add_set.dim > IPSET_DIM_MAX || |
| info->del_set.dim > IPSET_DIM_MAX) { |
| pr_info_ratelimited("SET target dimension over the limit!\n"); |
| if (info->add_set.index != IPSET_INVALID_ID) |
| ip_set_nfnl_put(par->net, info->add_set.index); |
| if (info->del_set.index != IPSET_INVALID_ID) |
| ip_set_nfnl_put(par->net, info->del_set.index); |
| return -ERANGE; |
| } |
| |
| return 0; |
| } |
| |
| static void |
| set_target_v1_destroy(const struct xt_tgdtor_param *par) |
| { |
| const struct xt_set_info_target_v1 *info = par->targinfo; |
| |
| if (info->add_set.index != IPSET_INVALID_ID) |
| ip_set_nfnl_put(par->net, info->add_set.index); |
| if (info->del_set.index != IPSET_INVALID_ID) |
| ip_set_nfnl_put(par->net, info->del_set.index); |
| } |
| |
| /* Revision 2 target */ |
| |
| static unsigned int |
| set_target_v2(struct sk_buff *skb, const struct xt_action_param *par) |
| { |
| const struct xt_set_info_target_v2 *info = par->targinfo; |
| |
| ADT_OPT(add_opt, xt_family(par), info->add_set.dim, |
| info->add_set.flags, info->flags, info->timeout, |
| 0, 0, 0, 0); |
| ADT_OPT(del_opt, xt_family(par), info->del_set.dim, |
| info->del_set.flags, 0, UINT_MAX, |
| 0, 0, 0, 0); |
| |
| /* Normalize to fit into jiffies */ |
| if (add_opt.ext.timeout != IPSET_NO_TIMEOUT && |
| add_opt.ext.timeout > IPSET_MAX_TIMEOUT) |
| add_opt.ext.timeout = IPSET_MAX_TIMEOUT; |
| if (info->add_set.index != IPSET_INVALID_ID) |
| ip_set_add(info->add_set.index, skb, par, &add_opt); |
| if (info->del_set.index != IPSET_INVALID_ID) |
| ip_set_del(info->del_set.index, skb, par, &del_opt); |
| |
| return XT_CONTINUE; |
| } |
| |
| #define set_target_v2_checkentry set_target_v1_checkentry |
| #define set_target_v2_destroy set_target_v1_destroy |
| |
| /* Revision 3 target */ |
| |
| #define MOPT(opt, member) ((opt).ext.skbinfo.member) |
| |
| static unsigned int |
| set_target_v3(struct sk_buff *skb, const struct xt_action_param *par) |
| { |
| const struct xt_set_info_target_v3 *info = par->targinfo; |
| int ret; |
| |
| ADT_OPT(add_opt, xt_family(par), info->add_set.dim, |
| info->add_set.flags, info->flags, info->timeout, |
| 0, 0, 0, 0); |
| ADT_OPT(del_opt, xt_family(par), info->del_set.dim, |
| info->del_set.flags, 0, UINT_MAX, |
| 0, 0, 0, 0); |
| ADT_OPT(map_opt, xt_family(par), info->map_set.dim, |
| info->map_set.flags, 0, UINT_MAX, |
| 0, 0, 0, 0); |
| |
| /* Normalize to fit into jiffies */ |
| if (add_opt.ext.timeout != IPSET_NO_TIMEOUT && |
| add_opt.ext.timeout > IPSET_MAX_TIMEOUT) |
| add_opt.ext.timeout = IPSET_MAX_TIMEOUT; |
| if (info->add_set.index != IPSET_INVALID_ID) |
| ip_set_add(info->add_set.index, skb, par, &add_opt); |
| if (info->del_set.index != IPSET_INVALID_ID) |
| ip_set_del(info->del_set.index, skb, par, &del_opt); |
| if (info->map_set.index != IPSET_INVALID_ID) { |
| map_opt.cmdflags |= info->flags & (IPSET_FLAG_MAP_SKBMARK | |
| IPSET_FLAG_MAP_SKBPRIO | |
| IPSET_FLAG_MAP_SKBQUEUE); |
| ret = match_set(info->map_set.index, skb, par, &map_opt, |
| info->map_set.flags & IPSET_INV_MATCH); |
| if (!ret) |
| return XT_CONTINUE; |
| if (map_opt.cmdflags & IPSET_FLAG_MAP_SKBMARK) |
| skb->mark = (skb->mark & ~MOPT(map_opt,skbmarkmask)) |
| ^ MOPT(map_opt, skbmark); |
| if (map_opt.cmdflags & IPSET_FLAG_MAP_SKBPRIO) |
| skb->priority = MOPT(map_opt, skbprio); |
| if ((map_opt.cmdflags & IPSET_FLAG_MAP_SKBQUEUE) && |
| skb->dev && |
| skb->dev->real_num_tx_queues > MOPT(map_opt, skbqueue)) |
| skb_set_queue_mapping(skb, MOPT(map_opt, skbqueue)); |
| } |
| return XT_CONTINUE; |
| } |
| |
| static int |
| set_target_v3_checkentry(const struct xt_tgchk_param *par) |
| { |
| const struct xt_set_info_target_v3 *info = par->targinfo; |
| ip_set_id_t index; |
| int ret = 0; |
| |
| if (info->add_set.index != IPSET_INVALID_ID) { |
| index = ip_set_nfnl_get_byindex(par->net, |
| info->add_set.index); |
| if (index == IPSET_INVALID_ID) { |
| pr_info_ratelimited("Cannot find add_set index %u as target\n", |
| info->add_set.index); |
| return -ENOENT; |
| } |
| } |
| |
| if (info->del_set.index != IPSET_INVALID_ID) { |
| index = ip_set_nfnl_get_byindex(par->net, |
| info->del_set.index); |
| if (index == IPSET_INVALID_ID) { |
| pr_info_ratelimited("Cannot find del_set index %u as target\n", |
| info->del_set.index); |
| ret = -ENOENT; |
| goto cleanup_add; |
| } |
| } |
| |
| if (info->map_set.index != IPSET_INVALID_ID) { |
| if (strncmp(par->table, "mangle", 7)) { |
| pr_info_ratelimited("--map-set only usable from mangle table\n"); |
| ret = -EINVAL; |
| goto cleanup_del; |
| } |
| if (((info->flags & IPSET_FLAG_MAP_SKBPRIO) | |
| (info->flags & IPSET_FLAG_MAP_SKBQUEUE)) && |
| (par->hook_mask & ~(1 << NF_INET_FORWARD | |
| 1 << NF_INET_LOCAL_OUT | |
| 1 << NF_INET_POST_ROUTING))) { |
| pr_info_ratelimited("mapping of prio or/and queue is allowed only from OUTPUT/FORWARD/POSTROUTING chains\n"); |
| ret = -EINVAL; |
| goto cleanup_del; |
| } |
| index = ip_set_nfnl_get_byindex(par->net, |
| info->map_set.index); |
| if (index == IPSET_INVALID_ID) { |
| pr_info_ratelimited("Cannot find map_set index %u as target\n", |
| info->map_set.index); |
| ret = -ENOENT; |
| goto cleanup_del; |
| } |
| } |
| |
| if (info->add_set.dim > IPSET_DIM_MAX || |
| info->del_set.dim > IPSET_DIM_MAX || |
| info->map_set.dim > IPSET_DIM_MAX) { |
| pr_info_ratelimited("SET target dimension over the limit!\n"); |
| ret = -ERANGE; |
| goto cleanup_mark; |
| } |
| |
| return 0; |
| cleanup_mark: |
| if (info->map_set.index != IPSET_INVALID_ID) |
| ip_set_nfnl_put(par->net, info->map_set.index); |
| cleanup_del: |
| if (info->del_set.index != IPSET_INVALID_ID) |
| ip_set_nfnl_put(par->net, info->del_set.index); |
| cleanup_add: |
| if (info->add_set.index != IPSET_INVALID_ID) |
| ip_set_nfnl_put(par->net, info->add_set.index); |
| return ret; |
| } |
| |
| static void |
| set_target_v3_destroy(const struct xt_tgdtor_param *par) |
| { |
| const struct xt_set_info_target_v3 *info = par->targinfo; |
| |
| if (info->add_set.index != IPSET_INVALID_ID) |
| ip_set_nfnl_put(par->net, info->add_set.index); |
| if (info->del_set.index != IPSET_INVALID_ID) |
| ip_set_nfnl_put(par->net, info->del_set.index); |
| if (info->map_set.index != IPSET_INVALID_ID) |
| ip_set_nfnl_put(par->net, info->map_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_v1, |
| .matchsize = sizeof(struct xt_set_info_match_v1), |
| .checkentry = set_match_v1_checkentry, |
| .destroy = set_match_v1_destroy, |
| .me = THIS_MODULE |
| }, |
| { |
| .name = "set", |
| .family = NFPROTO_IPV6, |
| .revision = 1, |
| .match = set_match_v1, |
| .matchsize = sizeof(struct xt_set_info_match_v1), |
| .checkentry = set_match_v1_checkentry, |
| .destroy = set_match_v1_destroy, |
| .me = THIS_MODULE |
| }, |
| /* --return-nomatch flag support */ |
| { |
| .name = "set", |
| .family = NFPROTO_IPV4, |
| .revision = 2, |
| .match = set_match_v1, |
| .matchsize = sizeof(struct xt_set_info_match_v1), |
| .checkentry = set_match_v1_checkentry, |
| .destroy = set_match_v1_destroy, |
| .me = THIS_MODULE |
| }, |
| { |
| .name = "set", |
| .family = NFPROTO_IPV6, |
| .revision = 2, |
| .match = set_match_v1, |
| .matchsize = sizeof(struct xt_set_info_match_v1), |
| .checkentry = set_match_v1_checkentry, |
| .destroy = set_match_v1_destroy, |
| .me = THIS_MODULE |
| }, |
| /* counters support: update, match */ |
| { |
| .name = "set", |
| .family = NFPROTO_IPV4, |
| .revision = 3, |
| .match = set_match_v3, |
| .matchsize = sizeof(struct xt_set_info_match_v3), |
| .checkentry = set_match_v3_checkentry, |
| .destroy = set_match_v3_destroy, |
| .me = THIS_MODULE |
| }, |
| { |
| .name = "set", |
| .family = NFPROTO_IPV6, |
| .revision = 3, |
| .match = set_match_v3, |
| .matchsize = sizeof(struct xt_set_info_match_v3), |
| .checkentry = set_match_v3_checkentry, |
| .destroy = set_match_v3_destroy, |
| .me = THIS_MODULE |
| }, |
| /* new revision for counters support: update, match */ |
| { |
| .name = "set", |
| .family = NFPROTO_IPV4, |
| .revision = 4, |
| .match = set_match_v4, |
| .matchsize = sizeof(struct xt_set_info_match_v4), |
| .checkentry = set_match_v4_checkentry, |
| .destroy = set_match_v4_destroy, |
| .me = THIS_MODULE |
| }, |
| { |
| .name = "set", |
| .family = NFPROTO_IPV6, |
| .revision = 4, |
| .match = set_match_v4, |
| .matchsize = sizeof(struct xt_set_info_match_v4), |
| .checkentry = set_match_v4_checkentry, |
| .destroy = set_match_v4_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_v1, |
| .targetsize = sizeof(struct xt_set_info_target_v1), |
| .checkentry = set_target_v1_checkentry, |
| .destroy = set_target_v1_destroy, |
| .me = THIS_MODULE |
| }, |
| { |
| .name = "SET", |
| .revision = 1, |
| .family = NFPROTO_IPV6, |
| .target = set_target_v1, |
| .targetsize = sizeof(struct xt_set_info_target_v1), |
| .checkentry = set_target_v1_checkentry, |
| .destroy = set_target_v1_destroy, |
| .me = THIS_MODULE |
| }, |
| /* --timeout and --exist flags support */ |
| { |
| .name = "SET", |
| .revision = 2, |
| .family = NFPROTO_IPV4, |
| .target = set_target_v2, |
| .targetsize = sizeof(struct xt_set_info_target_v2), |
| .checkentry = set_target_v2_checkentry, |
| .destroy = set_target_v2_destroy, |
| .me = THIS_MODULE |
| }, |
| { |
| .name = "SET", |
| .revision = 2, |
| .family = NFPROTO_IPV6, |
| .target = set_target_v2, |
| .targetsize = sizeof(struct xt_set_info_target_v2), |
| .checkentry = set_target_v2_checkentry, |
| .destroy = set_target_v2_destroy, |
| .me = THIS_MODULE |
| }, |
| /* --map-set support */ |
| { |
| .name = "SET", |
| .revision = 3, |
| .family = NFPROTO_IPV4, |
| .target = set_target_v3, |
| .targetsize = sizeof(struct xt_set_info_target_v3), |
| .checkentry = set_target_v3_checkentry, |
| .destroy = set_target_v3_destroy, |
| .me = THIS_MODULE |
| }, |
| { |
| .name = "SET", |
| .revision = 3, |
| .family = NFPROTO_IPV6, |
| .target = set_target_v3, |
| .targetsize = sizeof(struct xt_set_info_target_v3), |
| .checkentry = set_target_v3_checkentry, |
| .destroy = set_target_v3_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); |