GNU Linux-libre 6.1.90-gnu
[releases.git] / net / ipv6 / netfilter / ip6t_NPT.c
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, &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
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(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
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(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
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, &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
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, &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
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(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(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");