xref: /openbmc/linux/net/netfilter/xt_REDIRECT.c (revision 2cbc78a2)
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/module.h>
19 #include <linux/netdevice.h>
20 #include <linux/netfilter.h>
21 #include <linux/types.h>
22 #include <linux/netfilter_ipv4.h>
23 #include <linux/netfilter_ipv6.h>
24 #include <linux/netfilter/x_tables.h>
25 #include <net/addrconf.h>
26 #include <net/checksum.h>
27 #include <net/protocol.h>
28 #include <net/netfilter/nf_nat.h>
29 
30 static const struct in6_addr loopback_addr = IN6ADDR_LOOPBACK_INIT;
31 
32 static unsigned int
33 redirect_tg6(struct sk_buff *skb, const struct xt_action_param *par)
34 {
35 	const struct nf_nat_range *range = par->targinfo;
36 	struct nf_nat_range newrange;
37 	struct in6_addr newdst;
38 	enum ip_conntrack_info ctinfo;
39 	struct nf_conn *ct;
40 
41 	ct = nf_ct_get(skb, &ctinfo);
42 	if (par->hooknum == NF_INET_LOCAL_OUT)
43 		newdst = loopback_addr;
44 	else {
45 		struct inet6_dev *idev;
46 		struct inet6_ifaddr *ifa;
47 		bool addr = false;
48 
49 		rcu_read_lock();
50 		idev = __in6_dev_get(skb->dev);
51 		if (idev != NULL) {
52 			list_for_each_entry(ifa, &idev->addr_list, if_list) {
53 				newdst = ifa->addr;
54 				addr = true;
55 				break;
56 			}
57 		}
58 		rcu_read_unlock();
59 
60 		if (!addr)
61 			return NF_DROP;
62 	}
63 
64 	newrange.flags		= range->flags | NF_NAT_RANGE_MAP_IPS;
65 	newrange.min_addr.in6	= newdst;
66 	newrange.max_addr.in6	= newdst;
67 	newrange.min_proto	= range->min_proto;
68 	newrange.max_proto	= range->max_proto;
69 
70 	return nf_nat_setup_info(ct, &newrange, NF_NAT_MANIP_DST);
71 }
72 
73 static int redirect_tg6_checkentry(const struct xt_tgchk_param *par)
74 {
75 	const struct nf_nat_range *range = par->targinfo;
76 
77 	if (range->flags & NF_NAT_RANGE_MAP_IPS)
78 		return -EINVAL;
79 	return 0;
80 }
81 
82 /* FIXME: Take multiple ranges --RR */
83 static int redirect_tg4_check(const struct xt_tgchk_param *par)
84 {
85 	const struct nf_nat_ipv4_multi_range_compat *mr = par->targinfo;
86 
87 	if (mr->range[0].flags & NF_NAT_RANGE_MAP_IPS) {
88 		pr_debug("bad MAP_IPS.\n");
89 		return -EINVAL;
90 	}
91 	if (mr->rangesize != 1) {
92 		pr_debug("bad rangesize %u.\n", mr->rangesize);
93 		return -EINVAL;
94 	}
95 	return 0;
96 }
97 
98 static unsigned int
99 redirect_tg4(struct sk_buff *skb, const struct xt_action_param *par)
100 {
101 	struct nf_conn *ct;
102 	enum ip_conntrack_info ctinfo;
103 	__be32 newdst;
104 	const struct nf_nat_ipv4_multi_range_compat *mr = par->targinfo;
105 	struct nf_nat_range newrange;
106 
107 	NF_CT_ASSERT(par->hooknum == NF_INET_PRE_ROUTING ||
108 		     par->hooknum == NF_INET_LOCAL_OUT);
109 
110 	ct = nf_ct_get(skb, &ctinfo);
111 	NF_CT_ASSERT(ct && (ctinfo == IP_CT_NEW || ctinfo == IP_CT_RELATED));
112 
113 	/* Local packets: make them go to loopback */
114 	if (par->hooknum == NF_INET_LOCAL_OUT)
115 		newdst = htonl(0x7F000001);
116 	else {
117 		struct in_device *indev;
118 		struct in_ifaddr *ifa;
119 
120 		newdst = 0;
121 
122 		rcu_read_lock();
123 		indev = __in_dev_get_rcu(skb->dev);
124 		if (indev && (ifa = indev->ifa_list))
125 			newdst = ifa->ifa_local;
126 		rcu_read_unlock();
127 
128 		if (!newdst)
129 			return NF_DROP;
130 	}
131 
132 	/* Transfer from original range. */
133 	memset(&newrange.min_addr, 0, sizeof(newrange.min_addr));
134 	memset(&newrange.max_addr, 0, sizeof(newrange.max_addr));
135 	newrange.flags	     = mr->range[0].flags | NF_NAT_RANGE_MAP_IPS;
136 	newrange.min_addr.ip = newdst;
137 	newrange.max_addr.ip = newdst;
138 	newrange.min_proto   = mr->range[0].min;
139 	newrange.max_proto   = mr->range[0].max;
140 
141 	/* Hand modified range to generic setup. */
142 	return nf_nat_setup_info(ct, &newrange, NF_NAT_MANIP_DST);
143 }
144 
145 static struct xt_target redirect_tg_reg[] __read_mostly = {
146 	{
147 		.name       = "REDIRECT",
148 		.family     = NFPROTO_IPV6,
149 		.revision   = 0,
150 		.table      = "nat",
151 		.checkentry = redirect_tg6_checkentry,
152 		.target     = redirect_tg6,
153 		.targetsize = sizeof(struct nf_nat_range),
154 		.hooks      = (1 << NF_INET_PRE_ROUTING) |
155 		              (1 << NF_INET_LOCAL_OUT),
156 		.me         = THIS_MODULE,
157 	},
158 	{
159 		.name       = "REDIRECT",
160 		.family     = NFPROTO_IPV4,
161 		.revision   = 0,
162 		.table      = "nat",
163 		.target     = redirect_tg4,
164 		.checkentry = redirect_tg4_check,
165 		.targetsize = sizeof(struct nf_nat_ipv4_multi_range_compat),
166 		.hooks      = (1 << NF_INET_PRE_ROUTING) |
167 		              (1 << NF_INET_LOCAL_OUT),
168 		.me         = THIS_MODULE,
169 	},
170 };
171 
172 static int __init redirect_tg_init(void)
173 {
174 	return xt_register_targets(redirect_tg_reg,
175 				   ARRAY_SIZE(redirect_tg_reg));
176 }
177 
178 static void __exit redirect_tg_exit(void)
179 {
180 	xt_unregister_targets(redirect_tg_reg, ARRAY_SIZE(redirect_tg_reg));
181 }
182 
183 module_init(redirect_tg_init);
184 module_exit(redirect_tg_exit);
185 
186 MODULE_LICENSE("GPL");
187 MODULE_AUTHOR("Patrick McHardy <kaber@trash.net>");
188 MODULE_DESCRIPTION("Xtables: Connection redirection to localhost");
189 MODULE_ALIAS("ip6t_REDIRECT");
190 MODULE_ALIAS("ipt_REDIRECT");
191