| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* |
| * "TEE" target extension for Xtables |
| * Copyright © Sebastian Claßen, 2007 |
| * Jan Engelhardt, 2007-2010 |
| * |
| * based on ipt_ROUTE.c from Cédric de Launois |
| * <delaunois@info.ucl.be> |
| */ |
| #include <linux/module.h> |
| #include <linux/skbuff.h> |
| #include <linux/route.h> |
| #include <linux/netfilter/x_tables.h> |
| #include <net/net_namespace.h> |
| #include <net/netns/generic.h> |
| #include <net/route.h> |
| #include <net/netfilter/ipv4/nf_dup_ipv4.h> |
| #include <net/netfilter/ipv6/nf_dup_ipv6.h> |
| #include <linux/netfilter/xt_TEE.h> |
| |
| struct xt_tee_priv { |
| struct list_head list; |
| struct xt_tee_tginfo *tginfo; |
| int oif; |
| }; |
| |
| static unsigned int tee_net_id __read_mostly; |
| static const union nf_inet_addr tee_zero_address; |
| |
| struct tee_net { |
| struct list_head priv_list; |
| /* lock protects the priv_list */ |
| struct mutex lock; |
| }; |
| |
| static unsigned int |
| tee_tg4(struct sk_buff *skb, const struct xt_action_param *par) |
| { |
| const struct xt_tee_tginfo *info = par->targinfo; |
| int oif = info->priv ? info->priv->oif : 0; |
| |
| nf_dup_ipv4(xt_net(par), skb, xt_hooknum(par), &info->gw.in, oif); |
| |
| return XT_CONTINUE; |
| } |
| |
| #if IS_ENABLED(CONFIG_IP6_NF_IPTABLES) |
| static unsigned int |
| tee_tg6(struct sk_buff *skb, const struct xt_action_param *par) |
| { |
| const struct xt_tee_tginfo *info = par->targinfo; |
| int oif = info->priv ? info->priv->oif : 0; |
| |
| nf_dup_ipv6(xt_net(par), skb, xt_hooknum(par), &info->gw.in6, oif); |
| |
| return XT_CONTINUE; |
| } |
| #endif |
| |
| static int tee_netdev_event(struct notifier_block *this, unsigned long event, |
| void *ptr) |
| { |
| struct net_device *dev = netdev_notifier_info_to_dev(ptr); |
| struct net *net = dev_net(dev); |
| struct tee_net *tn = net_generic(net, tee_net_id); |
| struct xt_tee_priv *priv; |
| |
| mutex_lock(&tn->lock); |
| list_for_each_entry(priv, &tn->priv_list, list) { |
| switch (event) { |
| case NETDEV_REGISTER: |
| if (!strcmp(dev->name, priv->tginfo->oif)) |
| priv->oif = dev->ifindex; |
| break; |
| case NETDEV_UNREGISTER: |
| if (dev->ifindex == priv->oif) |
| priv->oif = -1; |
| break; |
| case NETDEV_CHANGENAME: |
| if (!strcmp(dev->name, priv->tginfo->oif)) |
| priv->oif = dev->ifindex; |
| else if (dev->ifindex == priv->oif) |
| priv->oif = -1; |
| break; |
| } |
| } |
| mutex_unlock(&tn->lock); |
| |
| return NOTIFY_DONE; |
| } |
| |
| static int tee_tg_check(const struct xt_tgchk_param *par) |
| { |
| struct tee_net *tn = net_generic(par->net, tee_net_id); |
| struct xt_tee_tginfo *info = par->targinfo; |
| struct xt_tee_priv *priv; |
| |
| /* 0.0.0.0 and :: not allowed */ |
| if (memcmp(&info->gw, &tee_zero_address, |
| sizeof(tee_zero_address)) == 0) |
| return -EINVAL; |
| |
| if (info->oif[0]) { |
| struct net_device *dev; |
| |
| if (info->oif[sizeof(info->oif)-1] != '\0') |
| return -EINVAL; |
| |
| priv = kzalloc(sizeof(*priv), GFP_KERNEL); |
| if (priv == NULL) |
| return -ENOMEM; |
| |
| priv->tginfo = info; |
| priv->oif = -1; |
| info->priv = priv; |
| |
| dev = dev_get_by_name(par->net, info->oif); |
| if (dev) { |
| priv->oif = dev->ifindex; |
| dev_put(dev); |
| } |
| mutex_lock(&tn->lock); |
| list_add(&priv->list, &tn->priv_list); |
| mutex_unlock(&tn->lock); |
| } else |
| info->priv = NULL; |
| |
| static_key_slow_inc(&xt_tee_enabled); |
| return 0; |
| } |
| |
| static void tee_tg_destroy(const struct xt_tgdtor_param *par) |
| { |
| struct tee_net *tn = net_generic(par->net, tee_net_id); |
| struct xt_tee_tginfo *info = par->targinfo; |
| |
| if (info->priv) { |
| mutex_lock(&tn->lock); |
| list_del(&info->priv->list); |
| mutex_unlock(&tn->lock); |
| kfree(info->priv); |
| } |
| static_key_slow_dec(&xt_tee_enabled); |
| } |
| |
| static struct xt_target tee_tg_reg[] __read_mostly = { |
| { |
| .name = "TEE", |
| .revision = 1, |
| .family = NFPROTO_IPV4, |
| .target = tee_tg4, |
| .targetsize = sizeof(struct xt_tee_tginfo), |
| .usersize = offsetof(struct xt_tee_tginfo, priv), |
| .checkentry = tee_tg_check, |
| .destroy = tee_tg_destroy, |
| .me = THIS_MODULE, |
| }, |
| #if IS_ENABLED(CONFIG_IP6_NF_IPTABLES) |
| { |
| .name = "TEE", |
| .revision = 1, |
| .family = NFPROTO_IPV6, |
| .target = tee_tg6, |
| .targetsize = sizeof(struct xt_tee_tginfo), |
| .usersize = offsetof(struct xt_tee_tginfo, priv), |
| .checkentry = tee_tg_check, |
| .destroy = tee_tg_destroy, |
| .me = THIS_MODULE, |
| }, |
| #endif |
| }; |
| |
| static int __net_init tee_net_init(struct net *net) |
| { |
| struct tee_net *tn = net_generic(net, tee_net_id); |
| |
| INIT_LIST_HEAD(&tn->priv_list); |
| mutex_init(&tn->lock); |
| return 0; |
| } |
| |
| static struct pernet_operations tee_net_ops = { |
| .init = tee_net_init, |
| .id = &tee_net_id, |
| .size = sizeof(struct tee_net), |
| }; |
| |
| static struct notifier_block tee_netdev_notifier = { |
| .notifier_call = tee_netdev_event, |
| }; |
| |
| static int __init tee_tg_init(void) |
| { |
| int ret; |
| |
| ret = register_pernet_subsys(&tee_net_ops); |
| if (ret < 0) |
| return ret; |
| |
| ret = xt_register_targets(tee_tg_reg, ARRAY_SIZE(tee_tg_reg)); |
| if (ret < 0) |
| goto cleanup_subsys; |
| |
| ret = register_netdevice_notifier(&tee_netdev_notifier); |
| if (ret < 0) |
| goto unregister_targets; |
| |
| return 0; |
| |
| unregister_targets: |
| xt_unregister_targets(tee_tg_reg, ARRAY_SIZE(tee_tg_reg)); |
| cleanup_subsys: |
| unregister_pernet_subsys(&tee_net_ops); |
| return ret; |
| } |
| |
| static void __exit tee_tg_exit(void) |
| { |
| unregister_netdevice_notifier(&tee_netdev_notifier); |
| xt_unregister_targets(tee_tg_reg, ARRAY_SIZE(tee_tg_reg)); |
| unregister_pernet_subsys(&tee_net_ops); |
| } |
| |
| module_init(tee_tg_init); |
| module_exit(tee_tg_exit); |
| MODULE_AUTHOR("Sebastian Claßen <sebastian.classen@freenet.ag>"); |
| MODULE_AUTHOR("Jan Engelhardt <jengelh@medozas.de>"); |
| MODULE_DESCRIPTION("Xtables: Reroute packet copy"); |
| MODULE_LICENSE("GPL"); |
| MODULE_ALIAS("ipt_TEE"); |
| MODULE_ALIAS("ip6t_TEE"); |