1 /*
2  * (C) 1999-2001 Paul `Rusty' Russell
3  * (C) 2002-2006 Netfilter Core Team <coreteam@netfilter.org>
4  * Copyright (c) 2011 Patrick McHardy <kaber@trash.net>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License version 2 as
8  * published by the Free Software Foundation.
9  *
10  * Based on Rusty Russell's IPv4 REDIRECT target. Development of IPv6
11  * NAT funded by Astaro.
12  */
13 
14 #include <linux/if.h>
15 #include <linux/inetdevice.h>
16 #include <linux/ip.h>
17 #include <linux/kernel.h>
18 #include <linux/netdevice.h>
19 #include <linux/netfilter.h>
20 #include <linux/types.h>
21 #include <linux/netfilter_ipv4.h>
22 #include <linux/netfilter_ipv6.h>
23 #include <linux/netfilter/x_tables.h>
24 #include <net/addrconf.h>
25 #include <net/checksum.h>
26 #include <net/protocol.h>
27 #include <net/netfilter/nf_nat.h>
28 #include <net/netfilter/nf_nat_redirect.h>
29 
30 unsigned int
31 nf_nat_redirect_ipv4(struct sk_buff *skb,
32 		     const struct nf_nat_ipv4_multi_range_compat *mr,
33 		     unsigned int hooknum)
34 {
35 	struct nf_conn *ct;
36 	enum ip_conntrack_info ctinfo;
37 	__be32 newdst;
38 	struct nf_nat_range2 newrange;
39 
40 	WARN_ON(hooknum != NF_INET_PRE_ROUTING &&
41 		hooknum != NF_INET_LOCAL_OUT);
42 
43 	ct = nf_ct_get(skb, &ctinfo);
44 	WARN_ON(!(ct && (ctinfo == IP_CT_NEW || ctinfo == IP_CT_RELATED)));
45 
46 	/* Local packets: make them go to loopback */
47 	if (hooknum == NF_INET_LOCAL_OUT) {
48 		newdst = htonl(0x7F000001);
49 	} else {
50 		struct in_device *indev;
51 		struct in_ifaddr *ifa;
52 
53 		newdst = 0;
54 
55 		indev = __in_dev_get_rcu(skb->dev);
56 		if (indev && indev->ifa_list) {
57 			ifa = indev->ifa_list;
58 			newdst = ifa->ifa_local;
59 		}
60 
61 		if (!newdst)
62 			return NF_DROP;
63 	}
64 
65 	/* Transfer from original range. */
66 	memset(&newrange.min_addr, 0, sizeof(newrange.min_addr));
67 	memset(&newrange.max_addr, 0, sizeof(newrange.max_addr));
68 	newrange.flags	     = mr->range[0].flags | NF_NAT_RANGE_MAP_IPS;
69 	newrange.min_addr.ip = newdst;
70 	newrange.max_addr.ip = newdst;
71 	newrange.min_proto   = mr->range[0].min;
72 	newrange.max_proto   = mr->range[0].max;
73 
74 	/* Hand modified range to generic setup. */
75 	return nf_nat_setup_info(ct, &newrange, NF_NAT_MANIP_DST);
76 }
77 EXPORT_SYMBOL_GPL(nf_nat_redirect_ipv4);
78 
79 static const struct in6_addr loopback_addr = IN6ADDR_LOOPBACK_INIT;
80 
81 unsigned int
82 nf_nat_redirect_ipv6(struct sk_buff *skb, const struct nf_nat_range2 *range,
83 		     unsigned int hooknum)
84 {
85 	struct nf_nat_range2 newrange;
86 	struct in6_addr newdst;
87 	enum ip_conntrack_info ctinfo;
88 	struct nf_conn *ct;
89 
90 	ct = nf_ct_get(skb, &ctinfo);
91 	if (hooknum == NF_INET_LOCAL_OUT) {
92 		newdst = loopback_addr;
93 	} else {
94 		struct inet6_dev *idev;
95 		struct inet6_ifaddr *ifa;
96 		bool addr = false;
97 
98 		idev = __in6_dev_get(skb->dev);
99 		if (idev != NULL) {
100 			read_lock_bh(&idev->lock);
101 			list_for_each_entry(ifa, &idev->addr_list, if_list) {
102 				newdst = ifa->addr;
103 				addr = true;
104 				break;
105 			}
106 			read_unlock_bh(&idev->lock);
107 		}
108 
109 		if (!addr)
110 			return NF_DROP;
111 	}
112 
113 	newrange.flags		= range->flags | NF_NAT_RANGE_MAP_IPS;
114 	newrange.min_addr.in6	= newdst;
115 	newrange.max_addr.in6	= newdst;
116 	newrange.min_proto	= range->min_proto;
117 	newrange.max_proto	= range->max_proto;
118 
119 	return nf_nat_setup_info(ct, &newrange, NF_NAT_MANIP_DST);
120 }
121 EXPORT_SYMBOL_GPL(nf_nat_redirect_ipv6);
122