ip6t_NPT.c (5236B)
1// SPDX-License-Identifier: GPL-2.0-only 2/* 3 * Copyright (c) 2011, 2012 Patrick McHardy <kaber@trash.net> 4 */ 5 6#include <linux/module.h> 7#include <linux/skbuff.h> 8#include <linux/ipv6.h> 9#include <net/ipv6.h> 10#include <linux/netfilter.h> 11#include <linux/netfilter_ipv6.h> 12#include <linux/netfilter_ipv6/ip6t_NPT.h> 13#include <linux/netfilter/x_tables.h> 14 15static int ip6t_npt_checkentry(const struct xt_tgchk_param *par) 16{ 17 struct ip6t_npt_tginfo *npt = par->targinfo; 18 struct in6_addr pfx; 19 __wsum src_sum, dst_sum; 20 21 if (npt->src_pfx_len > 64 || npt->dst_pfx_len > 64) 22 return -EINVAL; 23 24 /* Ensure that LSB of prefix is zero */ 25 ipv6_addr_prefix(&pfx, &npt->src_pfx.in6, npt->src_pfx_len); 26 if (!ipv6_addr_equal(&pfx, &npt->src_pfx.in6)) 27 return -EINVAL; 28 ipv6_addr_prefix(&pfx, &npt->dst_pfx.in6, npt->dst_pfx_len); 29 if (!ipv6_addr_equal(&pfx, &npt->dst_pfx.in6)) 30 return -EINVAL; 31 32 src_sum = csum_partial(&npt->src_pfx.in6, sizeof(npt->src_pfx.in6), 0); 33 dst_sum = csum_partial(&npt->dst_pfx.in6, sizeof(npt->dst_pfx.in6), 0); 34 35 npt->adjustment = ~csum_fold(csum_sub(src_sum, dst_sum)); 36 return 0; 37} 38 39static bool ip6t_npt_map_pfx(const struct ip6t_npt_tginfo *npt, 40 struct in6_addr *addr) 41{ 42 unsigned int pfx_len; 43 unsigned int i, idx; 44 __be32 mask; 45 __sum16 sum; 46 47 pfx_len = max(npt->src_pfx_len, npt->dst_pfx_len); 48 for (i = 0; i < pfx_len; i += 32) { 49 if (pfx_len - i >= 32) 50 mask = 0; 51 else 52 mask = htonl((1 << (i - pfx_len + 32)) - 1); 53 54 idx = i / 32; 55 addr->s6_addr32[idx] &= mask; 56 addr->s6_addr32[idx] |= ~mask & npt->dst_pfx.in6.s6_addr32[idx]; 57 } 58 59 if (pfx_len <= 48) 60 idx = 3; 61 else { 62 for (idx = 4; idx < ARRAY_SIZE(addr->s6_addr16); idx++) { 63 if ((__force __sum16)addr->s6_addr16[idx] != 64 CSUM_MANGLED_0) 65 break; 66 } 67 if (idx == ARRAY_SIZE(addr->s6_addr16)) 68 return false; 69 } 70 71 sum = ~csum_fold(csum_add(csum_unfold((__force __sum16)addr->s6_addr16[idx]), 72 csum_unfold(npt->adjustment))); 73 if (sum == CSUM_MANGLED_0) 74 sum = 0; 75 *(__force __sum16 *)&addr->s6_addr16[idx] = sum; 76 77 return true; 78} 79 80static struct ipv6hdr *icmpv6_bounced_ipv6hdr(struct sk_buff *skb, 81 struct ipv6hdr *_bounced_hdr) 82{ 83 if (ipv6_hdr(skb)->nexthdr != IPPROTO_ICMPV6) 84 return NULL; 85 86 if (!icmpv6_is_err(icmp6_hdr(skb)->icmp6_type)) 87 return NULL; 88 89 return skb_header_pointer(skb, 90 skb_transport_offset(skb) + sizeof(struct icmp6hdr), 91 sizeof(struct ipv6hdr), 92 _bounced_hdr); 93} 94 95static unsigned int 96ip6t_snpt_tg(struct sk_buff *skb, const struct xt_action_param *par) 97{ 98 const struct ip6t_npt_tginfo *npt = par->targinfo; 99 struct ipv6hdr _bounced_hdr; 100 struct ipv6hdr *bounced_hdr; 101 struct in6_addr bounced_pfx; 102 103 if (!ip6t_npt_map_pfx(npt, &ipv6_hdr(skb)->saddr)) { 104 icmpv6_send(skb, ICMPV6_PARAMPROB, ICMPV6_HDR_FIELD, 105 offsetof(struct ipv6hdr, saddr)); 106 return NF_DROP; 107 } 108 109 /* rewrite dst addr of bounced packet which was sent to dst range */ 110 bounced_hdr = icmpv6_bounced_ipv6hdr(skb, &_bounced_hdr); 111 if (bounced_hdr) { 112 ipv6_addr_prefix(&bounced_pfx, &bounced_hdr->daddr, npt->src_pfx_len); 113 if (ipv6_addr_cmp(&bounced_pfx, &npt->src_pfx.in6) == 0) 114 ip6t_npt_map_pfx(npt, &bounced_hdr->daddr); 115 } 116 117 return XT_CONTINUE; 118} 119 120static unsigned int 121ip6t_dnpt_tg(struct sk_buff *skb, const struct xt_action_param *par) 122{ 123 const struct ip6t_npt_tginfo *npt = par->targinfo; 124 struct ipv6hdr _bounced_hdr; 125 struct ipv6hdr *bounced_hdr; 126 struct in6_addr bounced_pfx; 127 128 if (!ip6t_npt_map_pfx(npt, &ipv6_hdr(skb)->daddr)) { 129 icmpv6_send(skb, ICMPV6_PARAMPROB, ICMPV6_HDR_FIELD, 130 offsetof(struct ipv6hdr, daddr)); 131 return NF_DROP; 132 } 133 134 /* rewrite src addr of bounced packet which was sent from dst range */ 135 bounced_hdr = icmpv6_bounced_ipv6hdr(skb, &_bounced_hdr); 136 if (bounced_hdr) { 137 ipv6_addr_prefix(&bounced_pfx, &bounced_hdr->saddr, npt->src_pfx_len); 138 if (ipv6_addr_cmp(&bounced_pfx, &npt->src_pfx.in6) == 0) 139 ip6t_npt_map_pfx(npt, &bounced_hdr->saddr); 140 } 141 142 return XT_CONTINUE; 143} 144 145static struct xt_target ip6t_npt_target_reg[] __read_mostly = { 146 { 147 .name = "SNPT", 148 .table = "mangle", 149 .target = ip6t_snpt_tg, 150 .targetsize = sizeof(struct ip6t_npt_tginfo), 151 .usersize = offsetof(struct ip6t_npt_tginfo, adjustment), 152 .checkentry = ip6t_npt_checkentry, 153 .family = NFPROTO_IPV6, 154 .hooks = (1 << NF_INET_LOCAL_IN) | 155 (1 << NF_INET_POST_ROUTING), 156 .me = THIS_MODULE, 157 }, 158 { 159 .name = "DNPT", 160 .table = "mangle", 161 .target = ip6t_dnpt_tg, 162 .targetsize = sizeof(struct ip6t_npt_tginfo), 163 .usersize = offsetof(struct ip6t_npt_tginfo, adjustment), 164 .checkentry = ip6t_npt_checkentry, 165 .family = NFPROTO_IPV6, 166 .hooks = (1 << NF_INET_PRE_ROUTING) | 167 (1 << NF_INET_LOCAL_OUT), 168 .me = THIS_MODULE, 169 }, 170}; 171 172static int __init ip6t_npt_init(void) 173{ 174 return xt_register_targets(ip6t_npt_target_reg, 175 ARRAY_SIZE(ip6t_npt_target_reg)); 176} 177 178static void __exit ip6t_npt_exit(void) 179{ 180 xt_unregister_targets(ip6t_npt_target_reg, 181 ARRAY_SIZE(ip6t_npt_target_reg)); 182} 183 184module_init(ip6t_npt_init); 185module_exit(ip6t_npt_exit); 186 187MODULE_LICENSE("GPL"); 188MODULE_DESCRIPTION("IPv6-to-IPv6 Network Prefix Translation (RFC 6296)"); 189MODULE_AUTHOR("Patrick McHardy <kaber@trash.net>"); 190MODULE_ALIAS("ip6t_SNPT"); 191MODULE_ALIAS("ip6t_DNPT");