Merge tag 'landlock_v34' of git://git.kernel.org/pub/scm/linux/kernel/git/jmorris...
[linux-2.6-microblaze.git] / net / netfilter / nf_nat_redirect.c
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/ip.h>
14 #include <linux/kernel.h>
15 #include <linux/netdevice.h>
16 #include <linux/netfilter.h>
17 #include <linux/types.h>
18 #include <linux/netfilter_ipv4.h>
19 #include <linux/netfilter_ipv6.h>
20 #include <linux/netfilter/x_tables.h>
21 #include <net/addrconf.h>
22 #include <net/checksum.h>
23 #include <net/protocol.h>
24 #include <net/netfilter/nf_nat.h>
25 #include <net/netfilter/nf_nat_redirect.h>
26
27 unsigned int
28 nf_nat_redirect_ipv4(struct sk_buff *skb,
29                      const struct nf_nat_ipv4_multi_range_compat *mr,
30                      unsigned int hooknum)
31 {
32         struct nf_conn *ct;
33         enum ip_conntrack_info ctinfo;
34         __be32 newdst;
35         struct nf_nat_range2 newrange;
36
37         WARN_ON(hooknum != NF_INET_PRE_ROUTING &&
38                 hooknum != NF_INET_LOCAL_OUT);
39
40         ct = nf_ct_get(skb, &ctinfo);
41         WARN_ON(!(ct && (ctinfo == IP_CT_NEW || ctinfo == IP_CT_RELATED)));
42
43         /* Local packets: make them go to loopback */
44         if (hooknum == NF_INET_LOCAL_OUT) {
45                 newdst = htonl(0x7F000001);
46         } else {
47                 const struct in_device *indev;
48
49                 newdst = 0;
50
51                 indev = __in_dev_get_rcu(skb->dev);
52                 if (indev) {
53                         const struct in_ifaddr *ifa;
54
55                         ifa = rcu_dereference(indev->ifa_list);
56                         if (ifa)
57                                 newdst = ifa->ifa_local;
58                 }
59
60                 if (!newdst)
61                         return NF_DROP;
62         }
63
64         /* Transfer from original range. */
65         memset(&newrange.min_addr, 0, sizeof(newrange.min_addr));
66         memset(&newrange.max_addr, 0, sizeof(newrange.max_addr));
67         newrange.flags       = mr->range[0].flags | NF_NAT_RANGE_MAP_IPS;
68         newrange.min_addr.ip = newdst;
69         newrange.max_addr.ip = newdst;
70         newrange.min_proto   = mr->range[0].min;
71         newrange.max_proto   = mr->range[0].max;
72
73         /* Hand modified range to generic setup. */
74         return nf_nat_setup_info(ct, &newrange, NF_NAT_MANIP_DST);
75 }
76 EXPORT_SYMBOL_GPL(nf_nat_redirect_ipv4);
77
78 static const struct in6_addr loopback_addr = IN6ADDR_LOOPBACK_INIT;
79
80 unsigned int
81 nf_nat_redirect_ipv6(struct sk_buff *skb, const struct nf_nat_range2 *range,
82                      unsigned int hooknum)
83 {
84         struct nf_nat_range2 newrange;
85         struct in6_addr newdst;
86         enum ip_conntrack_info ctinfo;
87         struct nf_conn *ct;
88
89         ct = nf_ct_get(skb, &ctinfo);
90         if (hooknum == NF_INET_LOCAL_OUT) {
91                 newdst = loopback_addr;
92         } else {
93                 struct inet6_dev *idev;
94                 struct inet6_ifaddr *ifa;
95                 bool addr = false;
96
97                 idev = __in6_dev_get(skb->dev);
98                 if (idev != NULL) {
99                         read_lock_bh(&idev->lock);
100                         list_for_each_entry(ifa, &idev->addr_list, if_list) {
101                                 newdst = ifa->addr;
102                                 addr = true;
103                                 break;
104                         }
105                         read_unlock_bh(&idev->lock);
106                 }
107
108                 if (!addr)
109                         return NF_DROP;
110         }
111
112         newrange.flags          = range->flags | NF_NAT_RANGE_MAP_IPS;
113         newrange.min_addr.in6   = newdst;
114         newrange.max_addr.in6   = newdst;
115         newrange.min_proto      = range->min_proto;
116         newrange.max_proto      = range->max_proto;
117
118         return nf_nat_setup_info(ct, &newrange, NF_NAT_MANIP_DST);
119 }
120 EXPORT_SYMBOL_GPL(nf_nat_redirect_ipv6);