Blame view

net/sched/act_nat.c 7.22 KB
b42199523   Herbert Xu   [PKT_SCHED]: Add ...
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
  /*
   * Stateless NAT actions
   *
   * Copyright (c) 2007 Herbert Xu <herbert@gondor.apana.org.au>
   *
   * This program is free software; you can redistribute it and/or modify it
   * under the terms of the GNU General Public License as published by the Free
   * Software Foundation; either version 2 of the License, or (at your option)
   * any later version.
   */
  
  #include <linux/errno.h>
  #include <linux/init.h>
  #include <linux/kernel.h>
  #include <linux/module.h>
  #include <linux/netfilter.h>
  #include <linux/rtnetlink.h>
  #include <linux/skbuff.h>
  #include <linux/slab.h>
  #include <linux/spinlock.h>
  #include <linux/string.h>
  #include <linux/tc_act/tc_nat.h>
  #include <net/act_api.h>
  #include <net/icmp.h>
  #include <net/ip.h>
  #include <net/netlink.h>
  #include <net/tc_act/tc_nat.h>
  #include <net/tcp.h>
  #include <net/udp.h>
  
  
  #define NAT_TAB_MASK	15
  static struct tcf_common *tcf_nat_ht[NAT_TAB_MASK + 1];
  static u32 nat_idx_gen;
  static DEFINE_RWLOCK(nat_lock);
  
  static struct tcf_hashinfo nat_hash_info = {
  	.htab	=	tcf_nat_ht,
  	.hmask	=	NAT_TAB_MASK,
  	.lock	=	&nat_lock,
  };
53b2bf3f8   Patrick McHardy   [NET_SCHED]: Use ...
42
43
44
  static const struct nla_policy nat_policy[TCA_NAT_MAX + 1] = {
  	[TCA_NAT_PARMS]	= { .len = sizeof(struct tc_nat) },
  };
7ba699c60   Patrick McHardy   [NET_SCHED]: Conv...
45
  static int tcf_nat_init(struct nlattr *nla, struct nlattr *est,
b42199523   Herbert Xu   [PKT_SCHED]: Add ...
46
47
  			struct tc_action *a, int ovr, int bind)
  {
7ba699c60   Patrick McHardy   [NET_SCHED]: Conv...
48
  	struct nlattr *tb[TCA_NAT_MAX + 1];
b42199523   Herbert Xu   [PKT_SCHED]: Add ...
49
  	struct tc_nat *parm;
cee63723b   Patrick McHardy   [NET_SCHED]: Prop...
50
  	int ret = 0, err;
b42199523   Herbert Xu   [PKT_SCHED]: Add ...
51
52
  	struct tcf_nat *p;
  	struct tcf_common *pc;
cee63723b   Patrick McHardy   [NET_SCHED]: Prop...
53
  	if (nla == NULL)
b42199523   Herbert Xu   [PKT_SCHED]: Add ...
54
  		return -EINVAL;
53b2bf3f8   Patrick McHardy   [NET_SCHED]: Use ...
55
  	err = nla_parse_nested(tb, TCA_NAT_MAX, nla, nat_policy);
cee63723b   Patrick McHardy   [NET_SCHED]: Prop...
56
57
  	if (err < 0)
  		return err;
53b2bf3f8   Patrick McHardy   [NET_SCHED]: Use ...
58
  	if (tb[TCA_NAT_PARMS] == NULL)
b42199523   Herbert Xu   [PKT_SCHED]: Add ...
59
  		return -EINVAL;
7ba699c60   Patrick McHardy   [NET_SCHED]: Conv...
60
  	parm = nla_data(tb[TCA_NAT_PARMS]);
b42199523   Herbert Xu   [PKT_SCHED]: Add ...
61
62
63
64
65
  
  	pc = tcf_hash_check(parm->index, a, bind, &nat_hash_info);
  	if (!pc) {
  		pc = tcf_hash_create(parm->index, est, a, sizeof(*p), bind,
  				     &nat_idx_gen, &nat_hash_info);
0e991ec6a   Stephen Hemminger   tc: propogate err...
66
  		if (IS_ERR(pc))
cc7ec456f   Eric Dumazet   net_sched: cleanups
67
  			return PTR_ERR(pc);
b42199523   Herbert Xu   [PKT_SCHED]: Add ...
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
  		p = to_tcf_nat(pc);
  		ret = ACT_P_CREATED;
  	} else {
  		p = to_tcf_nat(pc);
  		if (!ovr) {
  			tcf_hash_release(pc, bind, &nat_hash_info);
  			return -EEXIST;
  		}
  	}
  
  	spin_lock_bh(&p->tcf_lock);
  	p->old_addr = parm->old_addr;
  	p->new_addr = parm->new_addr;
  	p->mask = parm->mask;
  	p->flags = parm->flags;
  
  	p->tcf_action = parm->action;
  	spin_unlock_bh(&p->tcf_lock);
  
  	if (ret == ACT_P_CREATED)
  		tcf_hash_insert(pc, &nat_hash_info);
  
  	return ret;
  }
  
  static int tcf_nat_cleanup(struct tc_action *a, int bind)
  {
  	struct tcf_nat *p = a->priv;
  
  	return tcf_hash_release(&p->common, bind, &nat_hash_info);
  }
dc7f9f6e8   Eric Dumazet   net: sched: const...
99
  static int tcf_nat(struct sk_buff *skb, const struct tc_action *a,
b42199523   Herbert Xu   [PKT_SCHED]: Add ...
100
101
102
103
104
105
106
107
108
109
110
  		   struct tcf_result *res)
  {
  	struct tcf_nat *p = a->priv;
  	struct iphdr *iph;
  	__be32 old_addr;
  	__be32 new_addr;
  	__be32 mask;
  	__be32 addr;
  	int egress;
  	int action;
  	int ihl;
36d12690a   Changli Gao   act_nat: fix on t...
111
  	int noff;
b42199523   Herbert Xu   [PKT_SCHED]: Add ...
112
113
114
115
116
117
118
119
120
  
  	spin_lock(&p->tcf_lock);
  
  	p->tcf_tm.lastuse = jiffies;
  	old_addr = p->old_addr;
  	new_addr = p->new_addr;
  	mask = p->mask;
  	egress = p->flags & TCA_NAT_FLAG_EGRESS;
  	action = p->tcf_action;
bfe0d0298   Eric Dumazet   net_sched: factor...
121
  	bstats_update(&p->tcf_bstats, skb);
b42199523   Herbert Xu   [PKT_SCHED]: Add ...
122
123
124
125
126
  
  	spin_unlock(&p->tcf_lock);
  
  	if (unlikely(action == TC_ACT_SHOT))
  		goto drop;
36d12690a   Changli Gao   act_nat: fix on t...
127
128
  	noff = skb_network_offset(skb);
  	if (!pskb_may_pull(skb, sizeof(*iph) + noff))
b42199523   Herbert Xu   [PKT_SCHED]: Add ...
129
130
131
132
133
134
135
136
137
138
139
  		goto drop;
  
  	iph = ip_hdr(skb);
  
  	if (egress)
  		addr = iph->saddr;
  	else
  		addr = iph->daddr;
  
  	if (!((old_addr ^ addr) & mask)) {
  		if (skb_cloned(skb) &&
36d12690a   Changli Gao   act_nat: fix on t...
140
  		    !skb_clone_writable(skb, sizeof(*iph) + noff) &&
b42199523   Herbert Xu   [PKT_SCHED]: Add ...
141
142
143
144
145
146
147
148
149
150
151
152
  		    pskb_expand_head(skb, 0, 0, GFP_ATOMIC))
  			goto drop;
  
  		new_addr &= mask;
  		new_addr |= addr & ~mask;
  
  		/* Rewrite IP header */
  		iph = ip_hdr(skb);
  		if (egress)
  			iph->saddr = new_addr;
  		else
  			iph->daddr = new_addr;
be0ea7d5d   Patrick McHardy   [NETFILTER]: Conv...
153
  		csum_replace4(&iph->check, addr, new_addr);
33c29dde7   Changli Gao   act_nat: fix the ...
154
155
156
  	} else if ((iph->frag_off & htons(IP_OFFSET)) ||
  		   iph->protocol != IPPROTO_ICMP) {
  		goto out;
b42199523   Herbert Xu   [PKT_SCHED]: Add ...
157
158
159
160
161
162
163
164
165
  	}
  
  	ihl = iph->ihl * 4;
  
  	/* It would be nice to share code with stateful NAT. */
  	switch (iph->frag_off & htons(IP_OFFSET) ? 0 : iph->protocol) {
  	case IPPROTO_TCP:
  	{
  		struct tcphdr *tcph;
36d12690a   Changli Gao   act_nat: fix on t...
166
  		if (!pskb_may_pull(skb, ihl + sizeof(*tcph) + noff) ||
b42199523   Herbert Xu   [PKT_SCHED]: Add ...
167
  		    (skb_cloned(skb) &&
36d12690a   Changli Gao   act_nat: fix on t...
168
  		     !skb_clone_writable(skb, ihl + sizeof(*tcph) + noff) &&
b42199523   Herbert Xu   [PKT_SCHED]: Add ...
169
170
171
172
  		     pskb_expand_head(skb, 0, 0, GFP_ATOMIC)))
  			goto drop;
  
  		tcph = (void *)(skb_network_header(skb) + ihl);
be0ea7d5d   Patrick McHardy   [NETFILTER]: Conv...
173
  		inet_proto_csum_replace4(&tcph->check, skb, addr, new_addr, 1);
b42199523   Herbert Xu   [PKT_SCHED]: Add ...
174
175
176
177
178
  		break;
  	}
  	case IPPROTO_UDP:
  	{
  		struct udphdr *udph;
36d12690a   Changli Gao   act_nat: fix on t...
179
  		if (!pskb_may_pull(skb, ihl + sizeof(*udph) + noff) ||
b42199523   Herbert Xu   [PKT_SCHED]: Add ...
180
  		    (skb_cloned(skb) &&
36d12690a   Changli Gao   act_nat: fix on t...
181
  		     !skb_clone_writable(skb, ihl + sizeof(*udph) + noff) &&
b42199523   Herbert Xu   [PKT_SCHED]: Add ...
182
183
184
185
186
  		     pskb_expand_head(skb, 0, 0, GFP_ATOMIC)))
  			goto drop;
  
  		udph = (void *)(skb_network_header(skb) + ihl);
  		if (udph->check || skb->ip_summed == CHECKSUM_PARTIAL) {
be0ea7d5d   Patrick McHardy   [NETFILTER]: Conv...
187
188
  			inet_proto_csum_replace4(&udph->check, skb, addr,
  						 new_addr, 1);
b42199523   Herbert Xu   [PKT_SCHED]: Add ...
189
190
191
192
193
194
195
196
  			if (!udph->check)
  				udph->check = CSUM_MANGLED_0;
  		}
  		break;
  	}
  	case IPPROTO_ICMP:
  	{
  		struct icmphdr *icmph;
36d12690a   Changli Gao   act_nat: fix on t...
197
  		if (!pskb_may_pull(skb, ihl + sizeof(*icmph) + noff))
b42199523   Herbert Xu   [PKT_SCHED]: Add ...
198
199
200
201
202
203
204
205
  			goto drop;
  
  		icmph = (void *)(skb_network_header(skb) + ihl);
  
  		if ((icmph->type != ICMP_DEST_UNREACH) &&
  		    (icmph->type != ICMP_TIME_EXCEEDED) &&
  		    (icmph->type != ICMP_PARAMETERPROB))
  			break;
36d12690a   Changli Gao   act_nat: fix on t...
206
207
  		if (!pskb_may_pull(skb, ihl + sizeof(*icmph) + sizeof(*iph) +
  					noff))
70c2efa5a   Changli Gao   act_nat: not all ...
208
  			goto drop;
072d79a31   Changli Gao   act_nat: fix wild...
209
  		icmph = (void *)(skb_network_header(skb) + ihl);
b42199523   Herbert Xu   [PKT_SCHED]: Add ...
210
211
212
213
214
215
216
217
218
219
  		iph = (void *)(icmph + 1);
  		if (egress)
  			addr = iph->daddr;
  		else
  			addr = iph->saddr;
  
  		if ((old_addr ^ addr) & mask)
  			break;
  
  		if (skb_cloned(skb) &&
36d12690a   Changli Gao   act_nat: fix on t...
220
221
  		    !skb_clone_writable(skb, ihl + sizeof(*icmph) +
  					     sizeof(*iph) + noff) &&
b42199523   Herbert Xu   [PKT_SCHED]: Add ...
222
223
224
225
226
227
228
229
230
231
232
233
234
235
  		    pskb_expand_head(skb, 0, 0, GFP_ATOMIC))
  			goto drop;
  
  		icmph = (void *)(skb_network_header(skb) + ihl);
  		iph = (void *)(icmph + 1);
  
  		new_addr &= mask;
  		new_addr |= addr & ~mask;
  
  		/* XXX Fix up the inner checksums. */
  		if (egress)
  			iph->daddr = new_addr;
  		else
  			iph->saddr = new_addr;
be0ea7d5d   Patrick McHardy   [NETFILTER]: Conv...
236
  		inet_proto_csum_replace4(&icmph->checksum, skb, addr, new_addr,
3a3dfb062   Changli Gao   act_nat: the chec...
237
  					 0);
b42199523   Herbert Xu   [PKT_SCHED]: Add ...
238
239
240
241
242
  		break;
  	}
  	default:
  		break;
  	}
33c29dde7   Changli Gao   act_nat: fix the ...
243
  out:
b42199523   Herbert Xu   [PKT_SCHED]: Add ...
244
245
246
247
248
249
250
251
252
253
254
255
256
257
  	return action;
  
  drop:
  	spin_lock(&p->tcf_lock);
  	p->tcf_qstats.drops++;
  	spin_unlock(&p->tcf_lock);
  	return TC_ACT_SHOT;
  }
  
  static int tcf_nat_dump(struct sk_buff *skb, struct tc_action *a,
  			int bind, int ref)
  {
  	unsigned char *b = skb_tail_pointer(skb);
  	struct tcf_nat *p = a->priv;
1c40be12f   Eric Dumazet   net sched: fix so...
258
259
260
261
262
263
264
265
266
267
268
  	struct tc_nat opt = {
  		.old_addr = p->old_addr,
  		.new_addr = p->new_addr,
  		.mask     = p->mask,
  		.flags    = p->flags,
  
  		.index    = p->tcf_index,
  		.action   = p->tcf_action,
  		.refcnt   = p->tcf_refcnt - ref,
  		.bindcnt  = p->tcf_bindcnt - bind,
  	};
b42199523   Herbert Xu   [PKT_SCHED]: Add ...
269
  	struct tcf_t t;
b42199523   Herbert Xu   [PKT_SCHED]: Add ...
270

504f85c9d   Changli Gao   act_nat: use stac...
271
  	NLA_PUT(skb, TCA_NAT_PARMS, sizeof(opt), &opt);
b42199523   Herbert Xu   [PKT_SCHED]: Add ...
272
273
274
  	t.install = jiffies_to_clock_t(jiffies - p->tcf_tm.install);
  	t.lastuse = jiffies_to_clock_t(jiffies - p->tcf_tm.lastuse);
  	t.expires = jiffies_to_clock_t(p->tcf_tm.expires);
7ba699c60   Patrick McHardy   [NET_SCHED]: Conv...
275
  	NLA_PUT(skb, TCA_NAT_TM, sizeof(t), &t);
b42199523   Herbert Xu   [PKT_SCHED]: Add ...
276

b42199523   Herbert Xu   [PKT_SCHED]: Add ...
277
  	return skb->len;
7ba699c60   Patrick McHardy   [NET_SCHED]: Conv...
278
  nla_put_failure:
b42199523   Herbert Xu   [PKT_SCHED]: Add ...
279
  	nlmsg_trim(skb, b);
b42199523   Herbert Xu   [PKT_SCHED]: Add ...
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
  	return -1;
  }
  
  static struct tc_action_ops act_nat_ops = {
  	.kind		=	"nat",
  	.hinfo		=	&nat_hash_info,
  	.type		=	TCA_ACT_NAT,
  	.capab		=	TCA_CAP_NONE,
  	.owner		=	THIS_MODULE,
  	.act		=	tcf_nat,
  	.dump		=	tcf_nat_dump,
  	.cleanup	=	tcf_nat_cleanup,
  	.lookup		=	tcf_hash_search,
  	.init		=	tcf_nat_init,
  	.walk		=	tcf_generic_walker
  };
  
  MODULE_DESCRIPTION("Stateless NAT actions");
  MODULE_LICENSE("GPL");
  
  static int __init nat_init_module(void)
  {
  	return tcf_register_action(&act_nat_ops);
  }
  
  static void __exit nat_cleanup_module(void)
  {
  	tcf_unregister_action(&act_nat_ops);
  }
  
  module_init(nat_init_module);
  module_exit(nat_cleanup_module);