diff options
Diffstat (limited to 'ip6t_MAP66.c')
-rw-r--r-- | ip6t_MAP66.c | 307 |
1 files changed, 307 insertions, 0 deletions
diff --git a/ip6t_MAP66.c b/ip6t_MAP66.c new file mode 100644 index 0000000..fb0a243 --- /dev/null +++ b/ip6t_MAP66.c @@ -0,0 +1,307 @@ +/* + * MAP66: Network Address Translation IPv6-to-IPv6 as + * proposed in the IETF's second NAT66 draft document. + * (c) 2010 sven-ola()gmx.de + */ + +#include <linux/module.h> +#include <linux/version.h> +#include <linux/netfilter_ipv6/ip6_tables.h> +#include <net/ipv6.h> + +#include "ip6t_MAP66.h" + +MODULE_AUTHOR("Sven-Ola <sven-ola()gmx.de>"); +MODULE_DESCRIPTION("Xtables: MAP66 - IPv6 to IPv6 SNAT"); +MODULE_LICENSE("GPL"); + +#undef MAP66_DEBUG +#ifdef MAP66_DEBUG +/* Use lock to serialize, so printks don't overlap */ +static DEFINE_SPINLOCK(MAP66_lock); +#endif + +/* One's complement add */ +static inline u_int16_t add16( + u_int16_t a, + u_int16_t b) +{ + a += b; + return a + (a < b); +} + +/* Calc one's complement csum */ +static inline u_int16_t csum16(const u_int16_t *buf, int len) +{ + u_int16_t csum = 0; + while(len--) csum = add16(csum, *buf++); + return csum; +} + +/* Perform checksum neutral mapping */ +static void map16( + struct in6_addr* addr, + unsigned char mask, + const struct in6_addr* to, + u_int16_t csum) +{ + csum = add16( + *((u_int16_t*)addr + mask), + add16(csum16((const u_int16_t *)addr, mask), csum) + ); + if (csum == 0xffff) csum = 0x0000; + *((u_int16_t *)addr + mask) = csum; + memcpy(addr, to, sizeof(u_int16_t) * mask); +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) +# define rcu_dereference(p) p +# define ipv6_addr_equal(a, b) 0 == ipv6_addr_cmp(a, b) +# define ipv6_hdr(sbk) skb->nh.ipv6h +# ifndef bool +# define bool int +# define false 0 +# define true 0 +# endif +# define xt_target ip6t_target +# define __read_mostly +# define xt_register_target ip6t_register_target +# define xt_unregister_target ip6t_unregister_target +#endif + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,24) +# define NF_INET_PRE_ROUTING NF_IP6_PRE_ROUTING +# define NF_INET_POST_ROUTING NF_IP6_POST_ROUTING +#endif + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,26) + +struct xt_target_param { + const struct net_device *in, *out; + const void *targinfo; + unsigned int hooknum; +}; + +struct xt_tgchk_param { + void *targinfo; +}; + +#endif + +static bool is_my_ipv6_addr( + const struct net_device *dev, + const struct in6_addr *addr) +{ +#ifdef MAP66_DEBUG +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,28) + printk("MAP66: is_my_ipv6_addr(%s, " NIP6_FMT ")\n", NULL != dev ? dev->name : "", NIP6(*addr)); +#else + printk("MAP66: is_my_ipv6_addr(%s, %pI6)\n", NULL != dev ? dev->name : "", addr); +#endif +#endif + if (NULL != dev) { + const struct inet6_ifaddr *ifa; + const struct inet6_dev *idev = rcu_dereference(dev->ip6_ptr); + for (ifa = idev->addr_list; NULL != ifa; ifa = ifa->if_next) { +#ifdef MAP66_DEBUG +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,28) + printk("MAP66: cmp " NIP6_FMT "\n", NIP6(ifa->addr)); +#else + printk("MAP66: cmp %pI6\n", &ifa->addr); +#endif +#endif + if (ipv6_addr_equal(&ifa->addr, addr)) return true; + } + } + return false; +} + +static unsigned int MAP66_tg6( + struct sk_buff *skb, + const struct xt_target_param *par) +{ + struct ipv6hdr *hdr = ipv6_hdr(skb); + const struct ip6t_MAP66_info *info = (const struct ip6t_MAP66_info *)par->targinfo; + +#ifdef MAP66_DEBUG + spin_lock_bh(&MAP66_lock); +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,28) + printk("MAP66i: in=%s, out=%s, saddr=" NIP6_FMT ", daddr=" NIP6_FMT "\n", + NULL != par->in ? par->in->name : "", + NULL != par->out ? par->out->name : "", + NIP6(hdr->saddr), NIP6(hdr->daddr)); +#else + printk("MAP66i: in=%s, out=%s, saddr=%pI6, daddr=%pI6\n", + NULL != par->in ? par->in->name : "", + NULL != par->out ? par->out->name : "", + &hdr->saddr, &hdr->daddr); +#endif +#endif + + switch(par->hooknum) { + case NF_INET_PRE_ROUTING: +#ifdef MAP66_DEBUG + printk("MAP66 PRE, spoof=%d\n", 0 != (IP6T_MAP66_OPT_NOSPOOF & info->mapflags)); +#endif + if (0 != (IP6T_MAP66_OPT_NOSPOOF & info->mapflags) || + !is_my_ipv6_addr(par->in, &hdr->daddr)) + { + map16(&hdr->daddr, info->prefixlength, &info->prefix, info->prefixcsum); + } + break; + case NF_INET_POST_ROUTING: +#ifdef MAP66_DEBUG + printk("MAP66 POST, spoof=%d\n", 0 != (IP6T_MAP66_OPT_NOSPOOF & info->mapflags)); +#endif + map16(&hdr->saddr, info->prefixlength, &info->prefix, info->prefixcsum); + if (0 == (IP6T_MAP66_OPT_NOSPOOF & info->mapflags) && + is_my_ipv6_addr(par->out, &hdr->saddr)) + { +#ifdef MAP66_DEBUG + spin_unlock_bh(&MAP66_lock); +#endif + return NF_DROP; + } + break; + default: +#ifdef MAP66_DEBUG + printk("MAP66: unsupported hook: %d\n", par->hooknum); +#endif + break; + } + +#ifdef MAP66_DEBUG +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,28) + printk("MAP66i: in=%s, out=%s, saddr=" NIP6_FMT ", daddr=" NIP6_FMT "\n", + NULL != par->in ? par->in->name : "", + NULL != par->out ? par->out->name : "", + NIP6(hdr->saddr), NIP6(hdr->daddr)); +#else + printk("MAP66i: in=%s, out=%s, saddr=%pI6, daddr=%pI6\n", + NULL != par->in ? par->in->name : "", + NULL != par->out ? par->out->name : "", + &hdr->saddr, &hdr->daddr); +#endif + spin_unlock_bh(&MAP66_lock); +#endif + + return IP6T_CONTINUE; +} + +static bool MAP66_tg6_check( + const struct xt_tgchk_param *par) +{ + const struct ip6t_MAP66_info *info = (const struct ip6t_MAP66_info *)par->targinfo; + + if (0 >= info->prefixlength || 8 <= info->prefixlength) { + printk("MAP66: Unsupported prefix length /%d\n", 16 * info->prefixlength); + return false; + } + return true; +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) + +static unsigned int MAP66_tg6_24( + struct sk_buff **pskb, + unsigned int hooknum, + const struct net_device *in, + const struct net_device *out, + const void *targinfo, + void *userinfo) +{ + const struct xt_target_param par = { + .in = in, + .out = out, + .hooknum = hooknum, + .targinfo = targinfo, + }; + return MAP66_tg6(*pskb, &par); +} + +static int MAP66_tg6_check_24( + const char *table, + const struct ip6t_entry *entry, + void *targinfo, + unsigned int targinfosize, + unsigned int hook_mask) +{ + const struct xt_tgchk_param par = { + .targinfo = targinfo, + }; + if (0 != (hook_mask & ~((1 << NF_IP6_PRE_ROUTING) | (1 << NF_IP6_POST_ROUTING)))) { + printk("MAP66: Only valid for PRE_ROUTING or POST_ROUTING.\n"); + return 0; + } + return MAP66_tg6_check(&par); +} + +#elif LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28) + +static unsigned int MAP66_tg6_26( + struct sk_buff *skb, + const struct net_device *in, + const struct net_device *out, + unsigned int hooknum, + const struct xt_target *target, + const void *targinfo) +{ + const struct xt_target_param par = { + .in = in, + .out = out, + .hooknum = hooknum, + .targinfo = targinfo, + }; + return MAP66_tg6(skb, &par); +} + +static bool MAP66_tg6_check_26( + const char *table, + const void *entryinfo, + const struct xt_target *target, + void *targinfo, + unsigned int hook_mask) +{ + const struct xt_tgchk_param par = { + .targinfo = targinfo, + }; + return MAP66_tg6_check(&par); +} + +#endif + +static struct xt_target MAP66_tg6_reg __read_mostly = { + .name = "MAP66", +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) + .target = MAP66_tg6_24, + .checkentry = MAP66_tg6_check_24, +#else +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28) + .family = AF_INET6, + .target = MAP66_tg6_26, + .checkentry = MAP66_tg6_check_26, + .destroy = NULL, +#else + .family = NFPROTO_IPV6, + .target = MAP66_tg6, + .checkentry = MAP66_tg6_check, +#endif + .targetsize = sizeof(struct ip6t_MAP66_info), + .table = "mangle", + .hooks = (1 << NF_INET_POST_ROUTING) | (1 << NF_INET_PRE_ROUTING), +#endif + .me = THIS_MODULE, +}; + +static int __init MAP66_tg6_init(void) +{ + return xt_register_target(&MAP66_tg6_reg); +} + +static void __exit MAP66_tg6_exit(void) +{ + xt_unregister_target(&MAP66_tg6_reg); +} + +module_init(MAP66_tg6_init); +module_exit(MAP66_tg6_exit); |