463 lines
13 KiB
C
463 lines
13 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Nftables NAT extension: fullcone expression support
|
|
*
|
|
* Copyright (c) 2018 Chion Tang <tech@chionlab.moe>
|
|
* Original xt_FULLCONENAT and related iptables extension author
|
|
* Copyright (c) 2019-2022 GitHub/llccd Twitter/@gNodeB
|
|
* Added IPv6 support for xt_FULLCONENAT and ip6tables extension
|
|
* Ported to recent kernel versions
|
|
* Copyright (c) 2022 Syrone Wong <wong.syrone@gmail.com>
|
|
* Massively rewrite the whole module, split the original code into library and nftables 'fullcone' expression module
|
|
*/
|
|
#define pr_fmt(fmt) "fullcone " KBUILD_MODNAME ": " fmt
|
|
#define NF_FULLCONE_WORKQUEUE_NAME "fullcone " KBUILD_MODNAME ": wq"
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/init.h>
|
|
#include <linux/module.h>
|
|
#include <linux/netlink.h>
|
|
#include <linux/netfilter.h>
|
|
#include <linux/netfilter/nf_tables.h>
|
|
#include <net/netfilter/nf_tables.h>
|
|
#include <net/netfilter/nf_nat.h>
|
|
|
|
#include <linux/workqueue.h>
|
|
|
|
#include "nf_nat_fullcone.h"
|
|
|
|
static void nft_fullcone_set_regs(const struct nft_expr *expr, const struct nft_regs *regs,
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 18, 0)
|
|
struct nf_nat_range2 *range);
|
|
#else
|
|
struct nf_nat_range *range);
|
|
#endif
|
|
|
|
#ifdef CONFIG_NF_CONNTRACK_CHAIN_EVENTS
|
|
struct notifier_block ct_event_notifier;
|
|
#else
|
|
struct nf_ct_event_notifier ct_event_notifier;
|
|
#endif
|
|
static DEFINE_MUTEX(nf_ct_net_event_lock);
|
|
int ct_event_notifier_registered = 0;
|
|
|
|
int module_refer_count = 0;
|
|
|
|
static void gc_worker(struct work_struct *work);
|
|
static struct workqueue_struct *wq __read_mostly = NULL;
|
|
static DECLARE_DELAYED_WORK(gc_worker_wk, gc_worker);
|
|
|
|
static void gc_worker(struct work_struct *work)
|
|
{
|
|
nf_nat_fullcone_handle_dying_tuples();
|
|
}
|
|
|
|
struct nft_fullcone {
|
|
u32 flags;
|
|
u8 sreg_proto_min;
|
|
u8 sreg_proto_max;
|
|
};
|
|
|
|
static const struct nla_policy nft_fullcone_policy[NFTA_FULLCONE_MAX + 1] = {
|
|
[NFTA_FULLCONE_FLAGS] = {.type = NLA_U32 },
|
|
[NFTA_FULLCONE_REG_PROTO_MIN] = {.type = NLA_U32 },
|
|
[NFTA_FULLCONE_REG_PROTO_MAX] = {.type = NLA_U32 },
|
|
};
|
|
|
|
/* conntrack destroy event callback function */
|
|
#ifdef CONFIG_NF_CONNTRACK_CHAIN_EVENTS
|
|
static int ct_event_cb(struct notifier_block *this, unsigned long events, void *ptr)
|
|
{
|
|
struct nf_ct_event *item = ptr;
|
|
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0)
|
|
static int ct_event_cb(unsigned int events, const struct nf_ct_event *item)
|
|
{
|
|
#else
|
|
static int ct_event_cb(unsigned int events, struct nf_ct_event *item)
|
|
{
|
|
#endif
|
|
struct nf_conn *ct;
|
|
struct nf_conntrack_tuple *ct_tuple_reply, *ct_tuple_original;
|
|
uint8_t protonum;
|
|
struct tuple_list *dying_tuple_item;
|
|
|
|
ct = item->ct;
|
|
/* we handle only conntrack destroy events */
|
|
if (ct == NULL || !(events & (1 << IPCT_DESTROY))) {
|
|
return 0;
|
|
}
|
|
|
|
ct_tuple_original = &(ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple);
|
|
|
|
ct_tuple_reply = &(ct->tuplehash[IP_CT_DIR_REPLY].tuple);
|
|
|
|
protonum = (ct_tuple_original->dst).protonum;
|
|
if (protonum != IPPROTO_UDP) {
|
|
return 0;
|
|
}
|
|
|
|
dying_tuple_item = kmalloc(sizeof(struct tuple_list), GFP_ATOMIC);
|
|
|
|
if (dying_tuple_item == NULL) {
|
|
pr_debug("warning: ct_event_cb(): kmalloc failed.\n");
|
|
return 0;
|
|
}
|
|
|
|
memcpy(&(dying_tuple_item->tuple_original), ct_tuple_original, sizeof(struct nf_conntrack_tuple));
|
|
memcpy(&(dying_tuple_item->tuple_reply), ct_tuple_reply, sizeof(struct nf_conntrack_tuple));
|
|
|
|
nf_nat_fullcone_dying_tuple_list_add(&(dying_tuple_item->list));
|
|
|
|
if (wq != NULL)
|
|
queue_delayed_work(wq, &gc_worker_wk, msecs_to_jiffies(100));
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0) && !defined(CONFIG_NF_CONNTRACK_CHAIN_EVENTS)
|
|
static int exp_event_cb(unsigned int events, const struct nf_exp_event *item)
|
|
{
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static int nft_fullcone_validate(const struct nft_ctx *ctx, const struct nft_expr *expr, const struct nft_data **data)
|
|
{
|
|
int err;
|
|
|
|
err = nft_chain_validate_dependency(ctx->chain, NFT_CHAIN_T_NAT);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
// TODO: check hooks
|
|
return nft_chain_validate_hooks(ctx->chain, (1 << NF_INET_PRE_ROUTING) | (1 << NF_INET_POST_ROUTING));
|
|
}
|
|
|
|
static int nft_fullcone_init(const struct nft_ctx *ctx, const struct nft_expr *expr, const struct nlattr *const tb[])
|
|
{
|
|
int err;
|
|
int register_ct_notifier_ret = 0;
|
|
|
|
err = nf_ct_netns_get(ctx->net, ctx->family);
|
|
|
|
mutex_lock(&nf_ct_net_event_lock);
|
|
|
|
module_refer_count++;
|
|
|
|
pr_debug("nft_fullcone_init(): module_refer_count is now %d\n", module_refer_count);
|
|
|
|
if (module_refer_count == 1) {
|
|
#ifdef CONFIG_NF_CONNTRACK_CHAIN_EVENTS
|
|
ct_event_notifier.notifier_call = ct_event_cb;
|
|
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0)
|
|
ct_event_notifier.ct_event = ct_event_cb;
|
|
ct_event_notifier.exp_event = exp_event_cb;
|
|
#else
|
|
ct_event_notifier.fcn = ct_event_cb;
|
|
#endif
|
|
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0) && !defined(CONFIG_NF_CONNTRACK_CHAIN_EVENTS)
|
|
if (!READ_ONCE(ctx->net->ct.nf_conntrack_event_cb)) {
|
|
nf_conntrack_register_notifier(ctx->net, &ct_event_notifier);
|
|
}
|
|
#else
|
|
register_ct_notifier_ret = nf_conntrack_register_notifier(ctx->net, &ct_event_notifier);
|
|
#endif
|
|
|
|
if (register_ct_notifier_ret) {
|
|
/* non-zero means failure */
|
|
pr_warn("failed to register a conntrack notifier. Disable active GC for mappings.\n");
|
|
} else {
|
|
ct_event_notifier_registered = 1;
|
|
pr_debug("nft_fullcone_init(): ct_event_notifier registered\n");
|
|
}
|
|
|
|
}
|
|
|
|
mutex_unlock(&nf_ct_net_event_lock);
|
|
|
|
return err;
|
|
}
|
|
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 2, 0)
|
|
static int nft_fullcone_dump(struct sk_buff *skb, const struct nft_expr *expr, bool reset)
|
|
#else
|
|
static int nft_fullcone_dump(struct sk_buff *skb, const struct nft_expr *expr)
|
|
#endif
|
|
{
|
|
const struct nft_fullcone *priv = nft_expr_priv(expr);
|
|
|
|
if (priv->flags != 0 && nla_put_be32(skb, NFTA_FULLCONE_FLAGS, htonl(priv->flags)))
|
|
goto nla_put_failure;
|
|
|
|
if (priv->sreg_proto_min) {
|
|
if (nft_dump_register(skb, NFTA_FULLCONE_REG_PROTO_MIN,
|
|
priv->sreg_proto_min) ||
|
|
nft_dump_register(skb, NFTA_FULLCONE_REG_PROTO_MAX, priv->sreg_proto_max))
|
|
goto nla_put_failure;
|
|
}
|
|
|
|
return 0;
|
|
|
|
nla_put_failure:
|
|
return -1;
|
|
}
|
|
|
|
/* nft_fullcone_set_regs sets nft_regs from nft_expr fullcone specific private data */
|
|
static void nft_fullcone_set_regs(const struct nft_expr *expr, const struct nft_regs *regs,
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 18, 0)
|
|
struct nf_nat_range2 *range
|
|
#else
|
|
struct nf_nat_range *range
|
|
#endif
|
|
)
|
|
{
|
|
// private data connected via nft_expr_type.ops <==> nft_expr_ops.type
|
|
// private data type from nft_expr_type.{policy,maxattr,ops}
|
|
// private data size from nft_expr_ops.size
|
|
struct nft_fullcone *priv = nft_expr_priv(expr);
|
|
range->flags = priv->flags;
|
|
if (priv->sreg_proto_min) {
|
|
range->min_proto.all = (__force __be16)
|
|
nft_reg_load16(®s->data[priv->sreg_proto_min]);
|
|
range->max_proto.all = (__force __be16)
|
|
nft_reg_load16(®s->data[priv->sreg_proto_max]);
|
|
}
|
|
}
|
|
|
|
static void nft_fullcone_ipv4_eval(const struct nft_expr *expr, struct nft_regs *regs, const struct nft_pktinfo *pkt)
|
|
{
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 18, 0)
|
|
struct nf_nat_range2 range;
|
|
#else
|
|
struct nf_nat_range range;
|
|
#endif
|
|
|
|
memset(&range, 0, sizeof(range));
|
|
nft_fullcone_set_regs(expr, regs, &range);
|
|
regs->verdict.code = nf_nat_fullcone_ipv4(pkt->skb, nft_hook(pkt), &range, nft_out(pkt));
|
|
}
|
|
|
|
static void nft_fullcone_common_destory(const struct nft_ctx *ctx)
|
|
{
|
|
mutex_lock(&nf_ct_net_event_lock);
|
|
|
|
module_refer_count--;
|
|
|
|
pr_debug("nft_fullcone_common_destory(): module_refer_count is now %d\n", module_refer_count);
|
|
|
|
if (module_refer_count == 0) {
|
|
if (ct_event_notifier_registered) {
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0) && !defined(CONFIG_NF_CONNTRACK_CHAIN_EVENTS)
|
|
nf_conntrack_unregister_notifier(ctx->net);
|
|
#else
|
|
nf_conntrack_unregister_notifier(ctx->net, &ct_event_notifier);
|
|
#endif
|
|
ct_event_notifier_registered = 0;
|
|
|
|
pr_debug("nft_fullcone_common_destory(): ct_event_notifier unregistered\n");
|
|
|
|
}
|
|
}
|
|
|
|
mutex_unlock(&nf_ct_net_event_lock);
|
|
}
|
|
|
|
static void nft_fullcone_ipv4_destroy(const struct nft_ctx *ctx, const struct nft_expr *expr)
|
|
{
|
|
nft_fullcone_common_destory(ctx);
|
|
nf_ct_netns_put(ctx->net, NFPROTO_IPV4);
|
|
}
|
|
|
|
static struct nft_expr_type nft_fullcone_ipv4_type;
|
|
static const struct nft_expr_ops nft_fullcone_ipv4_ops = {
|
|
.type = &nft_fullcone_ipv4_type,
|
|
.size = NFT_EXPR_SIZE(sizeof(struct nft_fullcone)),
|
|
.eval = nft_fullcone_ipv4_eval,
|
|
.init = nft_fullcone_init,
|
|
.destroy = nft_fullcone_ipv4_destroy,
|
|
.dump = nft_fullcone_dump,
|
|
.validate = nft_fullcone_validate,
|
|
};
|
|
|
|
static struct nft_expr_type nft_fullcone_ipv4_type __read_mostly = {
|
|
.family = NFPROTO_IPV4,
|
|
.name = "fullcone",
|
|
.ops = &nft_fullcone_ipv4_ops,
|
|
.policy = nft_fullcone_policy,
|
|
.maxattr = NFTA_FULLCONE_MAX,
|
|
.owner = THIS_MODULE,
|
|
};
|
|
|
|
#ifdef CONFIG_NF_TABLES_IPV6
|
|
static void nft_fullcone_ipv6_eval(const struct nft_expr *expr, struct nft_regs *regs, const struct nft_pktinfo *pkt)
|
|
{
|
|
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 18, 0)
|
|
struct nf_nat_range2 range;
|
|
#else
|
|
struct nf_nat_range range;
|
|
#endif
|
|
|
|
memset(&range, 0, sizeof(range));
|
|
nft_fullcone_set_regs(expr, regs, &range);
|
|
regs->verdict.code = nf_nat_fullcone_ipv6(pkt->skb, nft_hook(pkt), &range, nft_out(pkt));
|
|
}
|
|
|
|
static void nft_fullcone_ipv6_destroy(const struct nft_ctx *ctx, const struct nft_expr *expr)
|
|
{
|
|
nft_fullcone_common_destory(ctx);
|
|
nf_ct_netns_put(ctx->net, NFPROTO_IPV6);
|
|
}
|
|
|
|
static struct nft_expr_type nft_fullcone_ipv6_type;
|
|
static const struct nft_expr_ops nft_fullcone_ipv6_ops = {
|
|
.type = &nft_fullcone_ipv6_type,
|
|
.size = NFT_EXPR_SIZE(sizeof(struct nft_fullcone)),
|
|
.eval = nft_fullcone_ipv6_eval,
|
|
.init = nft_fullcone_init,
|
|
.destroy = nft_fullcone_ipv6_destroy,
|
|
.dump = nft_fullcone_dump,
|
|
.validate = nft_fullcone_validate,
|
|
};
|
|
|
|
static struct nft_expr_type nft_fullcone_ipv6_type __read_mostly = {
|
|
.family = NFPROTO_IPV6,
|
|
.name = "fullcone",
|
|
.ops = &nft_fullcone_ipv6_ops,
|
|
.policy = nft_fullcone_policy,
|
|
.maxattr = NFTA_FULLCONE_MAX,
|
|
.owner = THIS_MODULE,
|
|
};
|
|
|
|
static int __init nft_fullcone_module_init_ipv6(void)
|
|
{
|
|
return nft_register_expr(&nft_fullcone_ipv6_type);
|
|
}
|
|
|
|
static void nft_fullcone_module_exit_ipv6(void)
|
|
{
|
|
nft_unregister_expr(&nft_fullcone_ipv6_type);
|
|
}
|
|
#else
|
|
static inline int nft_fullcone_module_init_ipv6(void)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static inline void nft_fullcone_module_exit_ipv6(void)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_NF_TABLES_INET
|
|
static void nft_fullcone_inet_eval(const struct nft_expr *expr, struct nft_regs *regs, const struct nft_pktinfo *pkt)
|
|
{
|
|
switch (nft_pf(pkt)) {
|
|
case NFPROTO_IPV4:
|
|
return nft_fullcone_ipv4_eval(expr, regs, pkt);
|
|
case NFPROTO_IPV6:
|
|
return nft_fullcone_ipv6_eval(expr, regs, pkt);
|
|
}
|
|
|
|
WARN_ON_ONCE(1);
|
|
}
|
|
|
|
static void nft_fullcone_inet_destroy(const struct nft_ctx *ctx, const struct nft_expr *expr)
|
|
{
|
|
nft_fullcone_common_destory(ctx);
|
|
nf_ct_netns_put(ctx->net, NFPROTO_INET);
|
|
}
|
|
|
|
static struct nft_expr_type nft_fullcone_inet_type;
|
|
static const struct nft_expr_ops nft_fullcone_inet_ops = {
|
|
.type = &nft_fullcone_inet_type,
|
|
.size = NFT_EXPR_SIZE(sizeof(struct nft_fullcone)),
|
|
.eval = nft_fullcone_inet_eval,
|
|
.init = nft_fullcone_init,
|
|
.destroy = nft_fullcone_inet_destroy,
|
|
.dump = nft_fullcone_dump,
|
|
.validate = nft_fullcone_validate,
|
|
};
|
|
|
|
static struct nft_expr_type nft_fullcone_inet_type __read_mostly = {
|
|
.family = NFPROTO_INET,
|
|
.name = "fullcone",
|
|
.ops = &nft_fullcone_inet_ops,
|
|
.policy = nft_fullcone_policy,
|
|
.maxattr = NFTA_FULLCONE_MAX,
|
|
.owner = THIS_MODULE,
|
|
};
|
|
|
|
static int __init nft_fullcone_module_init_inet(void)
|
|
{
|
|
return nft_register_expr(&nft_fullcone_inet_type);
|
|
}
|
|
|
|
static void nft_fullcone_module_exit_inet(void)
|
|
{
|
|
nft_unregister_expr(&nft_fullcone_inet_type);
|
|
}
|
|
#else
|
|
static inline int nft_fullcone_module_init_inet(void)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static inline void nft_fullcone_module_exit_inet(void)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
static int __init nft_fullcone_module_init(void)
|
|
{
|
|
int ret;
|
|
|
|
ret = nft_fullcone_module_init_ipv6();
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
ret = nft_fullcone_module_init_inet();
|
|
if (ret < 0) {
|
|
nft_fullcone_module_exit_ipv6();
|
|
return ret;
|
|
}
|
|
|
|
ret = nft_register_expr(&nft_fullcone_ipv4_type);
|
|
if (ret < 0) {
|
|
nft_fullcone_module_exit_inet();
|
|
nft_fullcone_module_exit_ipv6();
|
|
return ret;
|
|
}
|
|
|
|
wq = create_singlethread_workqueue(NF_FULLCONE_WORKQUEUE_NAME);
|
|
if (wq == NULL) {
|
|
pr_err("failed to create workqueue %s\n", NF_FULLCONE_WORKQUEUE_NAME);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void __exit nft_fullcone_module_exit(void)
|
|
{
|
|
nft_fullcone_module_exit_ipv6();
|
|
nft_fullcone_module_exit_inet();
|
|
nft_unregister_expr(&nft_fullcone_ipv4_type);
|
|
|
|
if (wq) {
|
|
cancel_delayed_work_sync(&gc_worker_wk);
|
|
flush_workqueue(wq);
|
|
destroy_workqueue(wq);
|
|
}
|
|
|
|
nf_nat_fullcone_handle_dying_tuples();
|
|
nf_nat_fullcone_destroy_mappings();
|
|
}
|
|
|
|
module_init(nft_fullcone_module_init);
|
|
module_exit(nft_fullcone_module_exit);
|
|
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_AUTHOR("Syrone Wong <wong.syrone@gmail.com>");
|
|
MODULE_ALIAS_NFT_EXPR("fullcone");
|
|
MODULE_DESCRIPTION("Netfilter nftables fullcone expression support of RFC3489 full cone NAT");
|