/* * MAP66: Network Address Translation IPv6-to-IPv6 as * proposed in the IETF's second NAT66 draft document. * (c) 2010 sven-ola()gmx.de */ #include #include #include #include #include "ip6t_MAP66.h" MODULE_AUTHOR("Sven-Ola "); MODULE_DESCRIPTION("Xtables: MAP66 - IPv6 to IPv6 SNAT"); MODULE_LICENSE("GPL"); /* 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, const struct in6_addr* to, int len, u_int16_t csum) { csum = add16( *((u_int16_t*)addr + len), add16(csum16((const u_int16_t *)addr, len), csum) ); if (csum == 0xffff) csum = 0x0000; *((u_int16_t *)addr + len) = csum; memcpy(addr, to, sizeof(u_int16_t) * len); } #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 1 # endif # define xt_target ip6t_target # define __read_mostly # define xt_register_target ip6t_register_target # define xt_unregister_target ip6t_unregister_target # define pr_devel(fmt, ...) ({ if (0); }) #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) { #if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,28) pr_devel("MAP66: is_my_ipv6_addr(%s, " NIP6_FMT ")\n", NULL != dev ? dev->name : "", NIP6(*addr)); #else pr_devel("MAP66: is_my_ipv6_addr(%s, %pI6)\n", NULL != dev ? dev->name : "", addr); #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) { #if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,28) pr_devel("MAP66: cmp " NIP6_FMT "\n", NIP6(ifa->addr)); #else pr_devel("MAP66: cmp %pI6\n", &ifa->addr); #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 = par->targinfo; #if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,28) pr_devel("MAP66: enter 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 pr_devel("MAP66: enter 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 #if 0 #if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0) if (skb_cloned(skb) && !skb->sk) { struct sk_buff *nskb = skb_copy(skb, GFP_ATOMIC); if (!nskb) { pr_devel("MAP66: cannot copy, dropped\n"); return NF_DROP; } kfree_skb(skb); skb = nskb; } #else if (!skb_make_writable(skb, sizeof(struct ipv6hdr))) { pr_devel("MAP66: unwriteable, dropped\n"); return NF_DROP; } #endif hdr = ipv6_hdr(skb); #endif if (0 != (IP6T_MAP66_OPT_DST_TO & info->mapflags)) { pr_devel("MAP66 DST, nocheck=%d, ip_summed=%d\n", 0 != (IP6T_MAP66_OPT_NOCHECK & info->mapflags), skb->ip_summed); if (0 != (IP6T_MAP66_OPT_NOCHECK & info->mapflags) || !is_my_ipv6_addr(par->in, &hdr->daddr)) { if (0 != (IP6T_MAP66_OPT_UNBALANCED & info->mapflags)) { memcpy(&hdr->daddr, &info->pfix_dst_to, sizeof(u_int16_t) * info->pfix_dst_len); } else { map16(&hdr->daddr, &info->pfix_dst_to, info->pfix_dst_len, info->pfix_dst_csum); } } } if (0 != (IP6T_MAP66_OPT_SRC_TO & info->mapflags)) { pr_devel("MAP66 SRC, nocheck=%d, ip_summed=%d\n", 0 != (IP6T_MAP66_OPT_NOCHECK & info->mapflags), skb->ip_summed); if (0 != (IP6T_MAP66_OPT_UNBALANCED & info->mapflags)) { memcpy(&hdr->saddr, &info->pfix_src_to, sizeof(u_int16_t) * info->pfix_src_len); } else { map16(&hdr->saddr, &info->pfix_src_to, info->pfix_src_len, info->pfix_src_csum); } if (0 == (IP6T_MAP66_OPT_NOCHECK & info->mapflags) && is_my_ipv6_addr(par->out, &hdr->saddr)) { return NF_DROP; } } #if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,28) pr_devel("MAP66: exit 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 pr_devel("MAP66: exit 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 return IP6T_CONTINUE; } static bool MAP66_tg6_check( const struct xt_tgchk_param *par) { const struct ip6t_MAP66_info *info = par->targinfo; if (0 == ((IP6T_MAP66_OPT_DST_TO | IP6T_MAP66_OPT_SRC_TO) & info->mapflags)) { printk("MAP66: No --" IP6T_MAP66_DST_TO " nor --" IP6T_MAP66_SRC_TO "\n"); return false; } if (0 != (IP6T_MAP66_OPT_DST_TO & info->mapflags) && (0 >= info->pfix_dst_len || (0 != (IP6T_MAP66_OPT_UNBALANCED & info->mapflags) ? 8 : 7) < info->pfix_dst_len)) { if (8 == info->pfix_dst_len) { printk("MAP66: --" IP6T_MAP66_DST_TO " prefix length /%d only possible with --unbalanced\n", 16 * info->pfix_dst_len); } else { printk("MAP66: Unsupported --" IP6T_MAP66_DST_TO " prefix length /%d\n", 16 * info->pfix_dst_len); } return false; } if (0 != (IP6T_MAP66_OPT_SRC_TO & info->mapflags) && (0 >= info->pfix_src_len || (0 != (IP6T_MAP66_OPT_UNBALANCED & info->mapflags) ? 8 : 7) < info->pfix_src_len)) { if (8 == info->pfix_src_len) { printk("MAP66: --" IP6T_MAP66_SRC_TO " prefix length /%d only possible with --unbalanced\n", 16 * info->pfix_src_len); } else { printk("MAP66: Unsupported --" IP6T_MAP66_SRC_TO " prefix length /%d\n", 16 * info->pfix_src_len); } 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);