ip_nat_standalone.c 9.7 KB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed
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
/* This file contains all the functions required for the standalone
   ip_nat module.

   These are not required by the compatibility layer.
*/

/* (C) 1999-2001 Paul `Rusty' Russell
 * (C) 2002-2004 Netfilter Core Team <coreteam@netfilter.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

/*
 * 23 Apr 2001: Harald Welte <laforge@gnumonks.org>
 * 	- new API and handling of conntrack/nat helpers
 * 	- now capable of multiple expectations for one master
 * */

#include <linux/types.h>
#include <linux/icmp.h>
#include <linux/ip.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/proc_fs.h>
#include <net/ip.h>
#include <net/checksum.h>
#include <linux/spinlock.h>

#include <linux/netfilter_ipv4/ip_nat.h>
#include <linux/netfilter_ipv4/ip_nat_rule.h>
#include <linux/netfilter_ipv4/ip_nat_protocol.h>
#include <linux/netfilter_ipv4/ip_nat_core.h>
#include <linux/netfilter_ipv4/ip_nat_helper.h>
#include <linux/netfilter_ipv4/ip_tables.h>
#include <linux/netfilter_ipv4/ip_conntrack_core.h>

#if 0
#define DEBUGP printk
#else
#define DEBUGP(format, args...)
#endif

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
#ifdef CONFIG_XFRM
static void nat_decode_session(struct sk_buff *skb, struct flowi *fl)
{
	struct ip_conntrack *ct;
	struct ip_conntrack_tuple *t;
	enum ip_conntrack_info ctinfo;
	enum ip_conntrack_dir dir;
	unsigned long statusbit;

	ct = ip_conntrack_get(skb, &ctinfo);
	if (ct == NULL)
		return;
	dir = CTINFO2DIR(ctinfo);
	t = &ct->tuplehash[dir].tuple;

	if (dir == IP_CT_DIR_ORIGINAL)
		statusbit = IPS_DST_NAT;
	else
		statusbit = IPS_SRC_NAT;

	if (ct->status & statusbit) {
		fl->fl4_dst = t->dst.ip;
		if (t->dst.protonum == IPPROTO_TCP ||
		    t->dst.protonum == IPPROTO_UDP)
			fl->fl_ip_dport = t->dst.u.tcp.port;
	}

	statusbit ^= IPS_NAT_MASK;

	if (ct->status & statusbit) {
		fl->fl4_src = t->src.ip;
		if (t->dst.protonum == IPPROTO_TCP ||
		    t->dst.protonum == IPPROTO_UDP)
			fl->fl_ip_sport = t->src.u.tcp.port;
	}
}
#endif
		
Linus Torvalds's avatar
Linus Torvalds committed
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
static unsigned int
ip_nat_fn(unsigned int hooknum,
	  struct sk_buff **pskb,
	  const struct net_device *in,
	  const struct net_device *out,
	  int (*okfn)(struct sk_buff *))
{
	struct ip_conntrack *ct;
	enum ip_conntrack_info ctinfo;
	struct ip_nat_info *info;
	/* maniptype == SRC for postrouting. */
	enum ip_nat_manip_type maniptype = HOOK2MANIP(hooknum);

	/* We never see fragments: conntrack defrags on pre-routing
	   and local-out, and ip_nat_out protects post-routing. */
	IP_NF_ASSERT(!((*pskb)->nh.iph->frag_off
		       & htons(IP_MF|IP_OFFSET)));

	ct = ip_conntrack_get(*pskb, &ctinfo);
	/* Can't track?  It's not due to stress, or conntrack would
	   have dropped it.  Hence it's the user's responsibilty to
	   packet filter it out, or implement conntrack/NAT for that
	   protocol. 8) --RR */
	if (!ct) {
		/* Exception: ICMP redirect to new connection (not in
                   hash table yet).  We must not let this through, in
                   case we're doing NAT to the same network. */
		if ((*pskb)->nh.iph->protocol == IPPROTO_ICMP) {
			struct icmphdr _hdr, *hp;

			hp = skb_header_pointer(*pskb,
						(*pskb)->nh.iph->ihl*4,
						sizeof(_hdr), &_hdr);
			if (hp != NULL &&
			    hp->type == ICMP_REDIRECT)
				return NF_DROP;
		}
		return NF_ACCEPT;
	}

125
126
127
128
	/* Don't try to NAT if this packet is not conntracked */
	if (ct == &ip_conntrack_untracked)
		return NF_ACCEPT;

Linus Torvalds's avatar
Linus Torvalds committed
129
130
131
132
	switch (ctinfo) {
	case IP_CT_RELATED:
	case IP_CT_RELATED+IP_CT_IS_REPLY:
		if ((*pskb)->nh.iph->protocol == IPPROTO_ICMP) {
133
134
			if (!ip_nat_icmp_reply_translation(ct, ctinfo,
							   hooknum, pskb))
Linus Torvalds's avatar
Linus Torvalds committed
135
136
137
138
139
140
141
142
143
144
145
146
147
				return NF_DROP;
			else
				return NF_ACCEPT;
		}
		/* Fall thru... (Only ICMPs can be IP_CT_IS_REPLY) */
	case IP_CT_NEW:
		info = &ct->nat.info;

		/* Seen it before?  This can happen for loopback, retrans,
		   or local packets.. */
		if (!ip_nat_initialized(ct, maniptype)) {
			unsigned int ret;

148
149
150
151
152
153
			if (unlikely(is_confirmed(ct)))
				/* NAT module was loaded late */
				ret = alloc_null_binding_confirmed(ct, info,
				                                   hooknum);
			else if (hooknum == NF_IP_LOCAL_IN)
				/* LOCAL_IN hook doesn't have a chain!  */
Linus Torvalds's avatar
Linus Torvalds committed
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
				ret = alloc_null_binding(ct, info, hooknum);
			else
				ret = ip_nat_rule_find(pskb, hooknum,
						       in, out, ct,
						       info);

			if (ret != NF_ACCEPT) {
				return ret;
			}
		} else
			DEBUGP("Already setup manip %s for ct %p\n",
			       maniptype == IP_NAT_MANIP_SRC ? "SRC" : "DST",
			       ct);
		break;

	default:
		/* ESTABLISHED */
		IP_NF_ASSERT(ctinfo == IP_CT_ESTABLISHED
			     || ctinfo == (IP_CT_ESTABLISHED+IP_CT_IS_REPLY));
		info = &ct->nat.info;
	}

	IP_NF_ASSERT(info);
177
	return ip_nat_packet(ct, ctinfo, hooknum, pskb);
Linus Torvalds's avatar
Linus Torvalds committed
178
179
180
181
182
183
184
185
186
187
}

static unsigned int
ip_nat_in(unsigned int hooknum,
          struct sk_buff **pskb,
          const struct net_device *in,
          const struct net_device *out,
          int (*okfn)(struct sk_buff *))
{
	unsigned int ret;
Al Viro's avatar
Al Viro committed
188
	__be32 daddr = (*pskb)->nh.iph->daddr;
Linus Torvalds's avatar
Linus Torvalds committed
189
190
191

	ret = ip_nat_fn(hooknum, pskb, in, out, okfn);
	if (ret != NF_DROP && ret != NF_STOLEN
192
193
194
	    && daddr != (*pskb)->nh.iph->daddr) {
		dst_release((*pskb)->dst);
		(*pskb)->dst = NULL;
Linus Torvalds's avatar
Linus Torvalds committed
195
196
197
198
199
200
201
202
203
204
205
	}
	return ret;
}

static unsigned int
ip_nat_out(unsigned int hooknum,
	   struct sk_buff **pskb,
	   const struct net_device *in,
	   const struct net_device *out,
	   int (*okfn)(struct sk_buff *))
{
206
#ifdef CONFIG_XFRM
207
208
	struct ip_conntrack *ct;
	enum ip_conntrack_info ctinfo;
209
#endif
210
211
	unsigned int ret;

Linus Torvalds's avatar
Linus Torvalds committed
212
213
214
215
216
	/* root is playing with raw sockets. */
	if ((*pskb)->len < sizeof(struct iphdr)
	    || (*pskb)->nh.iph->ihl * 4 < sizeof(struct iphdr))
		return NF_ACCEPT;

217
	ret = ip_nat_fn(hooknum, pskb, in, out, okfn);
218
#ifdef CONFIG_XFRM
219
220
221
222
223
224
225
226
227
	if (ret != NF_DROP && ret != NF_STOLEN
	    && (ct = ip_conntrack_get(*pskb, &ctinfo)) != NULL) {
		enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);

		if (ct->tuplehash[dir].tuple.src.ip !=
		    ct->tuplehash[!dir].tuple.dst.ip
		    || ct->tuplehash[dir].tuple.src.u.all !=
		       ct->tuplehash[!dir].tuple.dst.u.all
		    )
228
			return ip_xfrm_me_harder(pskb) == 0 ? ret : NF_DROP;
229
	}
230
#endif
231
	return ret;
Linus Torvalds's avatar
Linus Torvalds committed
232
233
234
235
236
237
238
239
240
}

static unsigned int
ip_nat_local_fn(unsigned int hooknum,
		struct sk_buff **pskb,
		const struct net_device *in,
		const struct net_device *out,
		int (*okfn)(struct sk_buff *))
{
241
242
	struct ip_conntrack *ct;
	enum ip_conntrack_info ctinfo;
Linus Torvalds's avatar
Linus Torvalds committed
243
244
245
246
247
248
249
250
251
	unsigned int ret;

	/* root is playing with raw sockets. */
	if ((*pskb)->len < sizeof(struct iphdr)
	    || (*pskb)->nh.iph->ihl * 4 < sizeof(struct iphdr))
		return NF_ACCEPT;

	ret = ip_nat_fn(hooknum, pskb, in, out, okfn);
	if (ret != NF_DROP && ret != NF_STOLEN
252
253
254
255
	    && (ct = ip_conntrack_get(*pskb, &ctinfo)) != NULL) {
		enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);

		if (ct->tuplehash[dir].tuple.dst.ip !=
256
257
258
		    ct->tuplehash[!dir].tuple.src.ip
#ifdef CONFIG_XFRM
		    || ct->tuplehash[dir].tuple.dst.u.all !=
259
		       ct->tuplehash[!dir].tuple.src.u.all
260
261
#endif
		    )
262
263
			if (ip_route_me_harder(pskb, RTN_UNSPEC))
				ret = NF_DROP;
264
	}
Linus Torvalds's avatar
Linus Torvalds committed
265
266
267
	return ret;
}

268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
static unsigned int
ip_nat_adjust(unsigned int hooknum,
	      struct sk_buff **pskb,
	      const struct net_device *in,
	      const struct net_device *out,
	      int (*okfn)(struct sk_buff *))
{
	struct ip_conntrack *ct;
	enum ip_conntrack_info ctinfo;

	ct = ip_conntrack_get(*pskb, &ctinfo);
	if (ct && test_bit(IPS_SEQ_ADJUST_BIT, &ct->status)) {
	        DEBUGP("ip_nat_standalone: adjusting sequence number\n");
	        if (!ip_nat_seq_adjust(pskb, ct, ctinfo))
	                return NF_DROP;
	}
	return NF_ACCEPT;
}

Linus Torvalds's avatar
Linus Torvalds committed
287
288
/* We must be after connection tracking and before packet filtering. */

289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
static struct nf_hook_ops ip_nat_ops[] = {
	/* Before packet filtering, change destination */
	{
		.hook		= ip_nat_in,
		.owner		= THIS_MODULE,
		.pf		= PF_INET,
		.hooknum	= NF_IP_PRE_ROUTING,
		.priority	= NF_IP_PRI_NAT_DST,
	},
	/* After packet filtering, change source */
	{
		.hook		= ip_nat_out,
		.owner		= THIS_MODULE,
		.pf		= PF_INET,
		.hooknum	= NF_IP_POST_ROUTING,
		.priority	= NF_IP_PRI_NAT_SRC,
	},
	/* After conntrack, adjust sequence number */
	{
		.hook		= ip_nat_adjust,
		.owner		= THIS_MODULE,
		.pf		= PF_INET,
		.hooknum	= NF_IP_POST_ROUTING,
		.priority	= NF_IP_PRI_NAT_SEQ_ADJUST,
	},
	/* Before packet filtering, change destination */
	{
		.hook		= ip_nat_local_fn,
		.owner		= THIS_MODULE,
		.pf		= PF_INET,
		.hooknum	= NF_IP_LOCAL_OUT,
		.priority	= NF_IP_PRI_NAT_DST,
	},
	/* After packet filtering, change source */
	{
		.hook		= ip_nat_fn,
		.owner		= THIS_MODULE,
		.pf		= PF_INET,
		.hooknum	= NF_IP_LOCAL_IN,
		.priority	= NF_IP_PRI_NAT_SRC,
	},
	/* After conntrack, adjust sequence number */
	{
		.hook		= ip_nat_adjust,
		.owner		= THIS_MODULE,
		.pf		= PF_INET,
		.hooknum	= NF_IP_LOCAL_IN,
		.priority	= NF_IP_PRI_NAT_SEQ_ADJUST,
	},
Linus Torvalds's avatar
Linus Torvalds committed
338
339
};

340
static int __init ip_nat_standalone_init(void)
Linus Torvalds's avatar
Linus Torvalds committed
341
342
343
{
	int ret = 0;

344
	need_conntrack();
Linus Torvalds's avatar
Linus Torvalds committed
345

346
347
348
349
#ifdef CONFIG_XFRM
	BUG_ON(ip_nat_decode_session != NULL);
	ip_nat_decode_session = nat_decode_session;
#endif
Linus Torvalds's avatar
Linus Torvalds committed
350
351
352
	ret = ip_nat_rule_init();
	if (ret < 0) {
		printk("ip_nat_init: can't setup rules.\n");
353
		goto cleanup_decode_session;
Linus Torvalds's avatar
Linus Torvalds committed
354
	}
355
	ret = nf_register_hooks(ip_nat_ops, ARRAY_SIZE(ip_nat_ops));
Linus Torvalds's avatar
Linus Torvalds committed
356
	if (ret < 0) {
357
		printk("ip_nat_init: can't register hooks.\n");
358
		goto cleanup_rule_init;
Linus Torvalds's avatar
Linus Torvalds committed
359
360
361
362
363
	}
	return ret;

 cleanup_rule_init:
	ip_nat_rule_cleanup();
364
365
366
367
368
 cleanup_decode_session:
#ifdef CONFIG_XFRM
	ip_nat_decode_session = NULL;
	synchronize_net();
#endif
Linus Torvalds's avatar
Linus Torvalds committed
369
370
371
	return ret;
}

372
static void __exit ip_nat_standalone_fini(void)
Linus Torvalds's avatar
Linus Torvalds committed
373
{
374
375
376
377
378
379
	nf_unregister_hooks(ip_nat_ops, ARRAY_SIZE(ip_nat_ops));
	ip_nat_rule_cleanup();
#ifdef CONFIG_XFRM
	ip_nat_decode_session = NULL;
	synchronize_net();
#endif
Linus Torvalds's avatar
Linus Torvalds committed
380
381
}

382
383
module_init(ip_nat_standalone_init);
module_exit(ip_nat_standalone_fini);
Linus Torvalds's avatar
Linus Torvalds committed
384
385

MODULE_LICENSE("GPL");