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 | |
15 | static 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: &pfx, addr: &npt->src_pfx.in6, plen: npt->src_pfx_len); |
26 | if (!ipv6_addr_equal(a1: &pfx, a2: &npt->src_pfx.in6)) |
27 | return -EINVAL; |
28 | ipv6_addr_prefix(pfx: &pfx, addr: &npt->dst_pfx.in6, plen: npt->dst_pfx_len); |
29 | if (!ipv6_addr_equal(a1: &pfx, a2: &npt->dst_pfx.in6)) |
30 | return -EINVAL; |
31 | |
32 | src_sum = csum_partial(buff: &npt->src_pfx.in6, len: sizeof(npt->src_pfx.in6), sum: 0); |
33 | dst_sum = csum_partial(buff: &npt->dst_pfx.in6, len: sizeof(npt->dst_pfx.in6), sum: 0); |
34 | |
35 | npt->adjustment = ~csum_fold(sum: csum_sub(csum: src_sum, addend: dst_sum)); |
36 | return 0; |
37 | } |
38 | |
39 | static 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(sum: csum_add(csum: csum_unfold(n: (__force __sum16)addr->s6_addr16[idx]), |
72 | addend: csum_unfold(n: 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 | |
80 | static 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(type: icmp6_hdr(skb)->icmp6_type)) |
87 | return NULL; |
88 | |
89 | return skb_header_pointer(skb, |
90 | offset: skb_transport_offset(skb) + sizeof(struct icmp6hdr), |
91 | len: sizeof(struct ipv6hdr), |
92 | buffer: _bounced_hdr); |
93 | } |
94 | |
95 | static unsigned int |
96 | ip6t_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, addr: &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: &_bounced_hdr); |
111 | if (bounced_hdr) { |
112 | ipv6_addr_prefix(pfx: &bounced_pfx, addr: &bounced_hdr->daddr, plen: npt->src_pfx_len); |
113 | if (ipv6_addr_cmp(a1: &bounced_pfx, a2: &npt->src_pfx.in6) == 0) |
114 | ip6t_npt_map_pfx(npt, addr: &bounced_hdr->daddr); |
115 | } |
116 | |
117 | return XT_CONTINUE; |
118 | } |
119 | |
120 | static unsigned int |
121 | ip6t_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, addr: &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: &_bounced_hdr); |
136 | if (bounced_hdr) { |
137 | ipv6_addr_prefix(pfx: &bounced_pfx, addr: &bounced_hdr->saddr, plen: npt->src_pfx_len); |
138 | if (ipv6_addr_cmp(a1: &bounced_pfx, a2: &npt->src_pfx.in6) == 0) |
139 | ip6t_npt_map_pfx(npt, addr: &bounced_hdr->saddr); |
140 | } |
141 | |
142 | return XT_CONTINUE; |
143 | } |
144 | |
145 | static 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 | |
172 | static int __init ip6t_npt_init(void) |
173 | { |
174 | return xt_register_targets(target: ip6t_npt_target_reg, |
175 | ARRAY_SIZE(ip6t_npt_target_reg)); |
176 | } |
177 | |
178 | static void __exit ip6t_npt_exit(void) |
179 | { |
180 | xt_unregister_targets(target: ip6t_npt_target_reg, |
181 | ARRAY_SIZE(ip6t_npt_target_reg)); |
182 | } |
183 | |
184 | module_init(ip6t_npt_init); |
185 | module_exit(ip6t_npt_exit); |
186 | |
187 | MODULE_LICENSE("GPL" ); |
188 | MODULE_DESCRIPTION("IPv6-to-IPv6 Network Prefix Translation (RFC 6296)" ); |
189 | MODULE_AUTHOR("Patrick McHardy <kaber@trash.net>" ); |
190 | MODULE_ALIAS("ip6t_SNPT" ); |
191 | MODULE_ALIAS("ip6t_DNPT" ); |
192 | |