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");
192