|  | /* | 
|  | *	"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> | 
|  | * | 
|  | *	This program is free software; you can redistribute it and/or | 
|  | *	modify it under the terms of the GNU General Public License | 
|  | *	version 2 or later, as published by the Free Software Foundation. | 
|  | */ | 
|  | #include <linux/ip.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/percpu.h> | 
|  | #include <linux/route.h> | 
|  | #include <linux/skbuff.h> | 
|  | #include <linux/notifier.h> | 
|  | #include <net/checksum.h> | 
|  | #include <net/icmp.h> | 
|  | #include <net/ip.h> | 
|  | #include <net/ipv6.h> | 
|  | #include <net/ip6_route.h> | 
|  | #include <net/route.h> | 
|  | #include <linux/netfilter/x_tables.h> | 
|  | #include <linux/netfilter/xt_TEE.h> | 
|  |  | 
|  | #if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE) | 
|  | #	define WITH_CONNTRACK 1 | 
|  | #	include <net/netfilter/nf_conntrack.h> | 
|  | #endif | 
|  | #if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE) | 
|  | #	define WITH_IPV6 1 | 
|  | #endif | 
|  |  | 
|  | struct xt_tee_priv { | 
|  | struct notifier_block	notifier; | 
|  | struct xt_tee_tginfo	*tginfo; | 
|  | int			oif; | 
|  | }; | 
|  |  | 
|  | static const union nf_inet_addr tee_zero_address; | 
|  | static DEFINE_PER_CPU(bool, tee_active); | 
|  |  | 
|  | static struct net *pick_net(struct sk_buff *skb) | 
|  | { | 
|  | #ifdef CONFIG_NET_NS | 
|  | const struct dst_entry *dst; | 
|  |  | 
|  | if (skb->dev != NULL) | 
|  | return dev_net(skb->dev); | 
|  | dst = skb_dst(skb); | 
|  | if (dst != NULL && dst->dev != NULL) | 
|  | return dev_net(dst->dev); | 
|  | #endif | 
|  | return &init_net; | 
|  | } | 
|  |  | 
|  | static bool | 
|  | tee_tg_route4(struct sk_buff *skb, const struct xt_tee_tginfo *info) | 
|  | { | 
|  | const struct iphdr *iph = ip_hdr(skb); | 
|  | struct net *net = pick_net(skb); | 
|  | struct rtable *rt; | 
|  | struct flowi4 fl4; | 
|  |  | 
|  | memset(&fl4, 0, sizeof(fl4)); | 
|  | if (info->priv) { | 
|  | if (info->priv->oif == -1) | 
|  | return false; | 
|  | fl4.flowi4_oif = info->priv->oif; | 
|  | } | 
|  | fl4.daddr = info->gw.ip; | 
|  | fl4.flowi4_tos = RT_TOS(iph->tos); | 
|  | fl4.flowi4_scope = RT_SCOPE_UNIVERSE; | 
|  | rt = ip_route_output_key(net, &fl4); | 
|  | if (IS_ERR(rt)) | 
|  | return false; | 
|  |  | 
|  | skb_dst_drop(skb); | 
|  | skb_dst_set(skb, &rt->dst); | 
|  | skb->dev      = rt->dst.dev; | 
|  | skb->protocol = htons(ETH_P_IP); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static unsigned int | 
|  | tee_tg4(struct sk_buff *skb, const struct xt_action_param *par) | 
|  | { | 
|  | const struct xt_tee_tginfo *info = par->targinfo; | 
|  | struct iphdr *iph; | 
|  |  | 
|  | if (percpu_read(tee_active)) | 
|  | return XT_CONTINUE; | 
|  | /* | 
|  | * Copy the skb, and route the copy. Will later return %XT_CONTINUE for | 
|  | * the original skb, which should continue on its way as if nothing has | 
|  | * happened. The copy should be independently delivered to the TEE | 
|  | * --gateway. | 
|  | */ | 
|  | skb = pskb_copy(skb, GFP_ATOMIC); | 
|  | if (skb == NULL) | 
|  | return XT_CONTINUE; | 
|  |  | 
|  | #ifdef WITH_CONNTRACK | 
|  | /* Avoid counting cloned packets towards the original connection. */ | 
|  | nf_conntrack_put(skb->nfct); | 
|  | skb->nfct     = &nf_ct_untracked_get()->ct_general; | 
|  | skb->nfctinfo = IP_CT_NEW; | 
|  | nf_conntrack_get(skb->nfct); | 
|  | #endif | 
|  | /* | 
|  | * If we are in PREROUTING/INPUT, the checksum must be recalculated | 
|  | * since the length could have changed as a result of defragmentation. | 
|  | * | 
|  | * We also decrease the TTL to mitigate potential TEE loops | 
|  | * between two hosts. | 
|  | * | 
|  | * Set %IP_DF so that the original source is notified of a potentially | 
|  | * decreased MTU on the clone route. IPv6 does this too. | 
|  | */ | 
|  | iph = ip_hdr(skb); | 
|  | iph->frag_off |= htons(IP_DF); | 
|  | if (par->hooknum == NF_INET_PRE_ROUTING || | 
|  | par->hooknum == NF_INET_LOCAL_IN) | 
|  | --iph->ttl; | 
|  | ip_send_check(iph); | 
|  |  | 
|  | if (tee_tg_route4(skb, info)) { | 
|  | percpu_write(tee_active, true); | 
|  | ip_local_out(skb); | 
|  | percpu_write(tee_active, false); | 
|  | } else { | 
|  | kfree_skb(skb); | 
|  | } | 
|  | return XT_CONTINUE; | 
|  | } | 
|  |  | 
|  | #ifdef WITH_IPV6 | 
|  | static bool | 
|  | tee_tg_route6(struct sk_buff *skb, const struct xt_tee_tginfo *info) | 
|  | { | 
|  | const struct ipv6hdr *iph = ipv6_hdr(skb); | 
|  | struct net *net = pick_net(skb); | 
|  | struct dst_entry *dst; | 
|  | struct flowi6 fl6; | 
|  |  | 
|  | memset(&fl6, 0, sizeof(fl6)); | 
|  | if (info->priv) { | 
|  | if (info->priv->oif == -1) | 
|  | return false; | 
|  | fl6.flowi6_oif = info->priv->oif; | 
|  | } | 
|  | fl6.daddr = info->gw.in6; | 
|  | fl6.flowlabel = ((iph->flow_lbl[0] & 0xF) << 16) | | 
|  | (iph->flow_lbl[1] << 8) | iph->flow_lbl[2]; | 
|  | dst = ip6_route_output(net, NULL, &fl6); | 
|  | if (dst == NULL) | 
|  | return false; | 
|  |  | 
|  | skb_dst_drop(skb); | 
|  | skb_dst_set(skb, dst); | 
|  | skb->dev      = dst->dev; | 
|  | skb->protocol = htons(ETH_P_IPV6); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static unsigned int | 
|  | tee_tg6(struct sk_buff *skb, const struct xt_action_param *par) | 
|  | { | 
|  | const struct xt_tee_tginfo *info = par->targinfo; | 
|  |  | 
|  | if (percpu_read(tee_active)) | 
|  | return XT_CONTINUE; | 
|  | skb = pskb_copy(skb, GFP_ATOMIC); | 
|  | if (skb == NULL) | 
|  | return XT_CONTINUE; | 
|  |  | 
|  | #ifdef WITH_CONNTRACK | 
|  | nf_conntrack_put(skb->nfct); | 
|  | skb->nfct     = &nf_ct_untracked_get()->ct_general; | 
|  | skb->nfctinfo = IP_CT_NEW; | 
|  | nf_conntrack_get(skb->nfct); | 
|  | #endif | 
|  | if (par->hooknum == NF_INET_PRE_ROUTING || | 
|  | par->hooknum == NF_INET_LOCAL_IN) { | 
|  | struct ipv6hdr *iph = ipv6_hdr(skb); | 
|  | --iph->hop_limit; | 
|  | } | 
|  | if (tee_tg_route6(skb, info)) { | 
|  | percpu_write(tee_active, true); | 
|  | ip6_local_out(skb); | 
|  | percpu_write(tee_active, false); | 
|  | } else { | 
|  | kfree_skb(skb); | 
|  | } | 
|  | return XT_CONTINUE; | 
|  | } | 
|  | #endif /* WITH_IPV6 */ | 
|  |  | 
|  | static int tee_netdev_event(struct notifier_block *this, unsigned long event, | 
|  | void *ptr) | 
|  | { | 
|  | struct net_device *dev = ptr; | 
|  | struct xt_tee_priv *priv; | 
|  |  | 
|  | priv = container_of(this, struct xt_tee_priv, notifier); | 
|  | 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; | 
|  | } | 
|  |  | 
|  | return NOTIFY_DONE; | 
|  | } | 
|  |  | 
|  | static int tee_tg_check(const struct xt_tgchk_param *par) | 
|  | { | 
|  | 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]) { | 
|  | 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; | 
|  | priv->notifier.notifier_call = tee_netdev_event; | 
|  | info->priv    = priv; | 
|  |  | 
|  | register_netdevice_notifier(&priv->notifier); | 
|  | } else | 
|  | info->priv = NULL; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void tee_tg_destroy(const struct xt_tgdtor_param *par) | 
|  | { | 
|  | struct xt_tee_tginfo *info = par->targinfo; | 
|  |  | 
|  | if (info->priv) { | 
|  | unregister_netdevice_notifier(&info->priv->notifier); | 
|  | kfree(info->priv); | 
|  | } | 
|  | } | 
|  |  | 
|  | 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), | 
|  | .checkentry = tee_tg_check, | 
|  | .destroy    = tee_tg_destroy, | 
|  | .me         = THIS_MODULE, | 
|  | }, | 
|  | #ifdef WITH_IPV6 | 
|  | { | 
|  | .name       = "TEE", | 
|  | .revision   = 1, | 
|  | .family     = NFPROTO_IPV6, | 
|  | .target     = tee_tg6, | 
|  | .targetsize = sizeof(struct xt_tee_tginfo), | 
|  | .checkentry = tee_tg_check, | 
|  | .destroy    = tee_tg_destroy, | 
|  | .me         = THIS_MODULE, | 
|  | }, | 
|  | #endif | 
|  | }; | 
|  |  | 
|  | static int __init tee_tg_init(void) | 
|  | { | 
|  | return xt_register_targets(tee_tg_reg, ARRAY_SIZE(tee_tg_reg)); | 
|  | } | 
|  |  | 
|  | static void __exit tee_tg_exit(void) | 
|  | { | 
|  | xt_unregister_targets(tee_tg_reg, ARRAY_SIZE(tee_tg_reg)); | 
|  | } | 
|  |  | 
|  | 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"); |