1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * (C) 1999-2001 Paul `Rusty' Russell
4  * (C) 2002-2006 Netfilter Core Team <coreteam@netfilter.org>
5  * Copyright (c) 2011 Patrick McHardy <kaber@trash.net>
6  *
7  * Based on Rusty Russell's IPv4 REDIRECT target. Development of IPv6
8  * NAT funded by Astaro.
9  */
10 
11 #include <linux/if.h>
12 #include <linux/inetdevice.h>
13 #include <linux/in.h>
14 #include <linux/ip.h>
15 #include <linux/kernel.h>
16 #include <linux/netdevice.h>
17 #include <linux/netfilter.h>
18 #include <linux/types.h>
19 #include <linux/netfilter_ipv4.h>
20 #include <linux/netfilter_ipv6.h>
21 #include <linux/netfilter/x_tables.h>
22 #include <net/addrconf.h>
23 #include <net/checksum.h>
24 #include <net/protocol.h>
25 #include <net/netfilter/nf_nat.h>
26 #include <net/netfilter/nf_nat_redirect.h>
27 
28 static unsigned int
nf_nat_redirect(struct sk_buff * skb,const struct nf_nat_range2 * range,const union nf_inet_addr * newdst)29 nf_nat_redirect(struct sk_buff *skb, const struct nf_nat_range2 *range,
30 		const union nf_inet_addr *newdst)
31 {
32 	struct nf_nat_range2 newrange;
33 	enum ip_conntrack_info ctinfo;
34 	struct nf_conn *ct;
35 
36 	ct = nf_ct_get(skb, &ctinfo);
37 
38 	memset(&newrange, 0, sizeof(newrange));
39 
40 	newrange.flags		= range->flags | NF_NAT_RANGE_MAP_IPS;
41 	newrange.min_addr	= *newdst;
42 	newrange.max_addr	= *newdst;
43 	newrange.min_proto	= range->min_proto;
44 	newrange.max_proto	= range->max_proto;
45 
46 	return nf_nat_setup_info(ct, &newrange, NF_NAT_MANIP_DST);
47 }
48 
49 unsigned int
nf_nat_redirect_ipv4(struct sk_buff * skb,const struct nf_nat_range2 * range,unsigned int hooknum)50 nf_nat_redirect_ipv4(struct sk_buff *skb, const struct nf_nat_range2 *range,
51 		     unsigned int hooknum)
52 {
53 	union nf_inet_addr newdst = {};
54 
55 	WARN_ON(hooknum != NF_INET_PRE_ROUTING &&
56 		hooknum != NF_INET_LOCAL_OUT);
57 
58 	/* Local packets: make them go to loopback */
59 	if (hooknum == NF_INET_LOCAL_OUT) {
60 		newdst.ip = htonl(INADDR_LOOPBACK);
61 	} else {
62 		const struct in_device *indev;
63 
64 		indev = __in_dev_get_rcu(skb->dev);
65 		if (indev) {
66 			const struct in_ifaddr *ifa;
67 
68 			ifa = rcu_dereference(indev->ifa_list);
69 			if (ifa)
70 				newdst.ip = ifa->ifa_local;
71 		}
72 
73 		if (!newdst.ip)
74 			return NF_DROP;
75 	}
76 
77 	return nf_nat_redirect(skb, range, &newdst);
78 }
79 EXPORT_SYMBOL_GPL(nf_nat_redirect_ipv4);
80 
81 static const struct in6_addr loopback_addr = IN6ADDR_LOOPBACK_INIT;
82 
nf_nat_redirect_ipv6_usable(const struct inet6_ifaddr * ifa,unsigned int scope)83 static bool nf_nat_redirect_ipv6_usable(const struct inet6_ifaddr *ifa, unsigned int scope)
84 {
85 	unsigned int ifa_addr_type = ipv6_addr_type(&ifa->addr);
86 
87 	if (ifa_addr_type & IPV6_ADDR_MAPPED)
88 		return false;
89 
90 	if ((ifa->flags & IFA_F_TENTATIVE) && (!(ifa->flags & IFA_F_OPTIMISTIC)))
91 		return false;
92 
93 	if (scope) {
94 		unsigned int ifa_scope = ifa_addr_type & IPV6_ADDR_SCOPE_MASK;
95 
96 		if (!(scope & ifa_scope))
97 			return false;
98 	}
99 
100 	return true;
101 }
102 
103 unsigned int
nf_nat_redirect_ipv6(struct sk_buff * skb,const struct nf_nat_range2 * range,unsigned int hooknum)104 nf_nat_redirect_ipv6(struct sk_buff *skb, const struct nf_nat_range2 *range,
105 		     unsigned int hooknum)
106 {
107 	union nf_inet_addr newdst = {};
108 
109 	if (hooknum == NF_INET_LOCAL_OUT) {
110 		newdst.in6 = loopback_addr;
111 	} else {
112 		unsigned int scope = ipv6_addr_scope(&ipv6_hdr(skb)->daddr);
113 		struct inet6_dev *idev;
114 		bool addr = false;
115 
116 		idev = __in6_dev_get(skb->dev);
117 		if (idev != NULL) {
118 			const struct inet6_ifaddr *ifa;
119 
120 			read_lock_bh(&idev->lock);
121 			list_for_each_entry(ifa, &idev->addr_list, if_list) {
122 				if (!nf_nat_redirect_ipv6_usable(ifa, scope))
123 					continue;
124 
125 				newdst.in6 = ifa->addr;
126 				addr = true;
127 				break;
128 			}
129 			read_unlock_bh(&idev->lock);
130 		}
131 
132 		if (!addr)
133 			return NF_DROP;
134 	}
135 
136 	return nf_nat_redirect(skb, range, &newdst);
137 }
138 EXPORT_SYMBOL_GPL(nf_nat_redirect_ipv6);
139