aboutsummaryrefslogtreecommitdiffstats
path: root/net/netfilter/nf_nat_redirect.c
blob: 5b37487d9d11fa30e064ea05aae2f080ec7aa483 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
// SPDX-License-Identifier: GPL-2.0-only
/*
 * (C) 1999-2001 Paul `Rusty' Russell
 * (C) 2002-2006 Netfilter Core Team <coreteam@netfilter.org>
 * Copyright (c) 2011 Patrick McHardy <kaber@trash.net>
 *
 * Based on Rusty Russell's IPv4 REDIRECT target. Development of IPv6
 * NAT funded by Astaro.
 */

#include <linux/if.h>
#include <linux/inetdevice.h>
#include <linux/in.h>
#include <linux/ip.h>
#include <linux/kernel.h>
#include <linux/netdevice.h>
#include <linux/netfilter.h>
#include <linux/types.h>
#include <linux/netfilter_ipv4.h>
#include <linux/netfilter_ipv6.h>
#include <linux/netfilter/x_tables.h>
#include <net/addrconf.h>
#include <net/checksum.h>
#include <net/protocol.h>
#include <net/netfilter/nf_nat.h>
#include <net/netfilter/nf_nat_redirect.h>

static unsigned int
nf_nat_redirect(struct sk_buff *skb, const struct nf_nat_range2 *range,
		const union nf_inet_addr *newdst)
{
	struct nf_nat_range2 newrange;
	enum ip_conntrack_info ctinfo;
	struct nf_conn *ct;

	ct = nf_ct_get(skb, &ctinfo);

	memset(&newrange, 0, sizeof(newrange));

	newrange.flags		= range->flags | NF_NAT_RANGE_MAP_IPS;
	newrange.min_addr	= *newdst;
	newrange.max_addr	= *newdst;
	newrange.min_proto	= range->min_proto;
	newrange.max_proto	= range->max_proto;

	return nf_nat_setup_info(ct, &newrange, NF_NAT_MANIP_DST);
}

unsigned int
nf_nat_redirect_ipv4(struct sk_buff *skb, const struct nf_nat_range2 *range,
		     unsigned int hooknum)
{
	union nf_inet_addr newdst = {};

	WARN_ON(hooknum != NF_INET_PRE_ROUTING &&
		hooknum != NF_INET_LOCAL_OUT);

	/* Local packets: make them go to loopback */
	if (hooknum == NF_INET_LOCAL_OUT) {
		newdst.ip = htonl(INADDR_LOOPBACK);
	} else {
		const struct in_device *indev;

		indev = __in_dev_get_rcu(skb->dev);
		if (indev) {
			const struct in_ifaddr *ifa;

			ifa = rcu_dereference(indev->ifa_list);
			if (ifa)
				newdst.ip = ifa->ifa_local;
		}

		if (!newdst.ip)
			return NF_DROP;
	}

	return nf_nat_redirect(skb, range, &newdst);
}
EXPORT_SYMBOL_GPL(nf_nat_redirect_ipv4);

static const struct in6_addr loopback_addr = IN6ADDR_LOOPBACK_INIT;

static bool nf_nat_redirect_ipv6_usable(const struct inet6_ifaddr *ifa, unsigned int scope)
{
	unsigned int ifa_addr_type = ipv6_addr_type(&ifa->addr);

	if (ifa_addr_type & IPV6_ADDR_MAPPED)
		return false;

	if ((ifa->flags & IFA_F_TENTATIVE) && (!(ifa->flags & IFA_F_OPTIMISTIC)))
		return false;

	if (scope) {
		unsigned int ifa_scope = ifa_addr_type & IPV6_ADDR_SCOPE_MASK;

		if (!(scope & ifa_scope))
			return false;
	}

	return true;
}

unsigned int
nf_nat_redirect_ipv6(struct sk_buff *skb, const struct nf_nat_range2 *range,
		     unsigned int hooknum)
{
	union nf_inet_addr newdst = {};

	if (hooknum == NF_INET_LOCAL_OUT) {
		newdst.in6 = loopback_addr;
	} else {
		unsigned int scope = ipv6_addr_scope(&ipv6_hdr(skb)->daddr);
		struct inet6_dev *idev;
		bool addr = false;

		idev = __in6_dev_get(skb->dev);
		if (idev != NULL) {
			const struct inet6_ifaddr *ifa;

			read_lock_bh(&idev->lock);
			list_for_each_entry(ifa, &idev->addr_list, if_list) {
				if (!nf_nat_redirect_ipv6_usable(ifa, scope))
					continue;

				newdst.in6 = ifa->addr;
				addr = true;
				break;
			}
			read_unlock_bh(&idev->lock);
		}

		if (!addr)
			return NF_DROP;
	}

	return nf_nat_redirect(skb, range, &newdst);
}
EXPORT_SYMBOL_GPL(nf_nat_redirect_ipv6);