aboutsummaryrefslogtreecommitdiffstats
path: root/net
diff options
context:
space:
mode:
authorPatrick McHardy <kaber@trash.net>2013-08-27 02:50:14 -0400
committerPablo Neira Ayuso <pablo@netfilter.org>2013-08-27 18:27:54 -0400
commit48b1de4c110a7afa4b85862f6c75af817db26fad (patch)
tree9d5af0462fdfda02e8eba53018f3e9e577c657f2 /net
parent0198230b7705eb2386e53778d944e307eef0cc71 (diff)
netfilter: add SYNPROXY core/target
Add a SYNPROXY for netfilter. The code is split into two parts, the synproxy core with common functions and an address family specific target. The SYNPROXY receives the connection request from the client, responds with a SYN/ACK containing a SYN cookie and announcing a zero window and checks whether the final ACK from the client contains a valid cookie. It then establishes a connection to the original destination and, if successful, sends a window update to the client with the window size announced by the server. Support for timestamps, SACK, window scaling and MSS options can be statically configured as target parameters if the features of the server are known. If timestamps are used, the timestamp value sent back to the client in the SYN/ACK will be different from the real timestamp of the server. In order to now break PAWS, the timestamps are translated in the direction server->client. Signed-off-by: Patrick McHardy <kaber@trash.net> Tested-by: Martin Topholm <mph@one.com> Signed-off-by: Jesper Dangaard Brouer <brouer@redhat.com> Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
Diffstat (limited to 'net')
-rw-r--r--net/ipv4/netfilter/Kconfig13
-rw-r--r--net/ipv4/netfilter/Makefile1
-rw-r--r--net/ipv4/netfilter/ipt_SYNPROXY.c472
-rw-r--r--net/netfilter/Kconfig3
-rw-r--r--net/netfilter/Makefile3
-rw-r--r--net/netfilter/nf_conntrack_core.c6
-rw-r--r--net/netfilter/nf_conntrack_proto_tcp.c16
-rw-r--r--net/netfilter/nf_conntrack_seqadj.c20
-rw-r--r--net/netfilter/nf_synproxy_core.c432
9 files changed, 966 insertions, 0 deletions
diff --git a/net/ipv4/netfilter/Kconfig b/net/ipv4/netfilter/Kconfig
index 4e9028017428..1657e39b291f 100644
--- a/net/ipv4/netfilter/Kconfig
+++ b/net/ipv4/netfilter/Kconfig
@@ -110,6 +110,19 @@ config IP_NF_TARGET_REJECT
110 110
111 To compile it as a module, choose M here. If unsure, say N. 111 To compile it as a module, choose M here. If unsure, say N.
112 112
113config IP_NF_TARGET_SYNPROXY
114 tristate "SYNPROXY target support"
115 depends on NF_CONNTRACK && NETFILTER_ADVANCED
116 select NETFILTER_SYNPROXY
117 select SYN_COOKIES
118 help
119 The SYNPROXY target allows you to intercept TCP connections and
120 establish them using syncookies before they are passed on to the
121 server. This allows to avoid conntrack and server resource usage
122 during SYN-flood attacks.
123
124 To compile it as a module, choose M here. If unsure, say N.
125
113config IP_NF_TARGET_ULOG 126config IP_NF_TARGET_ULOG
114 tristate "ULOG target support (obsolete)" 127 tristate "ULOG target support (obsolete)"
115 default m if NETFILTER_ADVANCED=n 128 default m if NETFILTER_ADVANCED=n
diff --git a/net/ipv4/netfilter/Makefile b/net/ipv4/netfilter/Makefile
index 007b128eecc9..3622b248b6dd 100644
--- a/net/ipv4/netfilter/Makefile
+++ b/net/ipv4/netfilter/Makefile
@@ -46,6 +46,7 @@ obj-$(CONFIG_IP_NF_TARGET_CLUSTERIP) += ipt_CLUSTERIP.o
46obj-$(CONFIG_IP_NF_TARGET_ECN) += ipt_ECN.o 46obj-$(CONFIG_IP_NF_TARGET_ECN) += ipt_ECN.o
47obj-$(CONFIG_IP_NF_TARGET_MASQUERADE) += ipt_MASQUERADE.o 47obj-$(CONFIG_IP_NF_TARGET_MASQUERADE) += ipt_MASQUERADE.o
48obj-$(CONFIG_IP_NF_TARGET_REJECT) += ipt_REJECT.o 48obj-$(CONFIG_IP_NF_TARGET_REJECT) += ipt_REJECT.o
49obj-$(CONFIG_IP_NF_TARGET_SYNPROXY) += ipt_SYNPROXY.o
49obj-$(CONFIG_IP_NF_TARGET_ULOG) += ipt_ULOG.o 50obj-$(CONFIG_IP_NF_TARGET_ULOG) += ipt_ULOG.o
50 51
51# generic ARP tables 52# generic ARP tables
diff --git a/net/ipv4/netfilter/ipt_SYNPROXY.c b/net/ipv4/netfilter/ipt_SYNPROXY.c
new file mode 100644
index 000000000000..94371db6aecc
--- /dev/null
+++ b/net/ipv4/netfilter/ipt_SYNPROXY.c
@@ -0,0 +1,472 @@
1/*
2 * Copyright (c) 2013 Patrick McHardy <kaber@trash.net>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 as
6 * published by the Free Software Foundation.
7 */
8
9#include <linux/module.h>
10#include <linux/skbuff.h>
11#include <net/tcp.h>
12
13#include <linux/netfilter_ipv4/ip_tables.h>
14#include <linux/netfilter/x_tables.h>
15#include <linux/netfilter/xt_SYNPROXY.h>
16#include <net/netfilter/nf_conntrack.h>
17#include <net/netfilter/nf_conntrack_seqadj.h>
18#include <net/netfilter/nf_conntrack_synproxy.h>
19
20static struct iphdr *
21synproxy_build_ip(struct sk_buff *skb, u32 saddr, u32 daddr)
22{
23 struct iphdr *iph;
24
25 skb_reset_network_header(skb);
26 iph = (struct iphdr *)skb_put(skb, sizeof(*iph));
27 iph->version = 4;
28 iph->ihl = sizeof(*iph) / 4;
29 iph->tos = 0;
30 iph->id = 0;
31 iph->frag_off = htons(IP_DF);
32 iph->ttl = sysctl_ip_default_ttl;
33 iph->protocol = IPPROTO_TCP;
34 iph->check = 0;
35 iph->saddr = saddr;
36 iph->daddr = daddr;
37
38 return iph;
39}
40
41static void
42synproxy_send_tcp(const struct sk_buff *skb, struct sk_buff *nskb,
43 struct nf_conntrack *nfct, enum ip_conntrack_info ctinfo,
44 struct iphdr *niph, struct tcphdr *nth,
45 unsigned int tcp_hdr_size)
46{
47 nth->check = ~tcp_v4_check(tcp_hdr_size, niph->saddr, niph->daddr, 0);
48 nskb->ip_summed = CHECKSUM_PARTIAL;
49 nskb->csum_start = (unsigned char *)nth - nskb->head;
50 nskb->csum_offset = offsetof(struct tcphdr, check);
51
52 skb_dst_set_noref(nskb, skb_dst(skb));
53 nskb->protocol = htons(ETH_P_IP);
54 if (ip_route_me_harder(nskb, RTN_UNSPEC))
55 goto free_nskb;
56
57 if (nfct) {
58 nskb->nfct = nfct;
59 nskb->nfctinfo = ctinfo;
60 nf_conntrack_get(nfct);
61 }
62
63 ip_local_out(nskb);
64 return;
65
66free_nskb:
67 kfree_skb(nskb);
68}
69
70static void
71synproxy_send_client_synack(const struct sk_buff *skb, const struct tcphdr *th,
72 const struct synproxy_options *opts)
73{
74 struct sk_buff *nskb;
75 struct iphdr *iph, *niph;
76 struct tcphdr *nth;
77 unsigned int tcp_hdr_size;
78 u16 mss = opts->mss;
79
80 iph = ip_hdr(skb);
81
82 tcp_hdr_size = sizeof(*nth) + synproxy_options_size(opts);
83 nskb = alloc_skb(sizeof(*niph) + tcp_hdr_size + MAX_TCP_HEADER,
84 GFP_ATOMIC);
85 if (nskb == NULL)
86 return;
87 skb_reserve(nskb, MAX_TCP_HEADER);
88
89 niph = synproxy_build_ip(nskb, iph->daddr, iph->saddr);
90
91 skb_reset_transport_header(nskb);
92 nth = (struct tcphdr *)skb_put(nskb, tcp_hdr_size);
93 nth->source = th->dest;
94 nth->dest = th->source;
95 nth->seq = htonl(__cookie_v4_init_sequence(iph, th, &mss));
96 nth->ack_seq = htonl(ntohl(th->seq) + 1);
97 tcp_flag_word(nth) = TCP_FLAG_SYN | TCP_FLAG_ACK;
98 if (opts->options & XT_SYNPROXY_OPT_ECN)
99 tcp_flag_word(nth) |= TCP_FLAG_ECE;
100 nth->doff = tcp_hdr_size / 4;
101 nth->window = 0;
102 nth->check = 0;
103 nth->urg_ptr = 0;
104
105 synproxy_build_options(nth, opts);
106
107 synproxy_send_tcp(skb, nskb, skb->nfct, IP_CT_ESTABLISHED_REPLY,
108 niph, nth, tcp_hdr_size);
109}
110
111static void
112synproxy_send_server_syn(const struct synproxy_net *snet,
113 const struct sk_buff *skb, const struct tcphdr *th,
114 const struct synproxy_options *opts, u32 recv_seq)
115{
116 struct sk_buff *nskb;
117 struct iphdr *iph, *niph;
118 struct tcphdr *nth;
119 unsigned int tcp_hdr_size;
120
121 iph = ip_hdr(skb);
122
123 tcp_hdr_size = sizeof(*nth) + synproxy_options_size(opts);
124 nskb = alloc_skb(sizeof(*niph) + tcp_hdr_size + MAX_TCP_HEADER,
125 GFP_ATOMIC);
126 if (nskb == NULL)
127 return;
128 skb_reserve(nskb, MAX_TCP_HEADER);
129
130 niph = synproxy_build_ip(nskb, iph->saddr, iph->daddr);
131
132 skb_reset_transport_header(nskb);
133 nth = (struct tcphdr *)skb_put(nskb, tcp_hdr_size);
134 nth->source = th->source;
135 nth->dest = th->dest;
136 nth->seq = htonl(recv_seq - 1);
137 /* ack_seq is used to relay our ISN to the synproxy hook to initialize
138 * sequence number translation once a connection tracking entry exists.
139 */
140 nth->ack_seq = htonl(ntohl(th->ack_seq) - 1);
141 tcp_flag_word(nth) = TCP_FLAG_SYN;
142 if (opts->options & XT_SYNPROXY_OPT_ECN)
143 tcp_flag_word(nth) |= TCP_FLAG_ECE | TCP_FLAG_CWR;
144 nth->doff = tcp_hdr_size / 4;
145 nth->window = th->window;
146 nth->check = 0;
147 nth->urg_ptr = 0;
148
149 synproxy_build_options(nth, opts);
150
151 synproxy_send_tcp(skb, nskb, &snet->tmpl->ct_general, IP_CT_NEW,
152 niph, nth, tcp_hdr_size);
153}
154
155static void
156synproxy_send_server_ack(const struct synproxy_net *snet,
157 const struct ip_ct_tcp *state,
158 const struct sk_buff *skb, const struct tcphdr *th,
159 const struct synproxy_options *opts)
160{
161 struct sk_buff *nskb;
162 struct iphdr *iph, *niph;
163 struct tcphdr *nth;
164 unsigned int tcp_hdr_size;
165
166 iph = ip_hdr(skb);
167
168 tcp_hdr_size = sizeof(*nth) + synproxy_options_size(opts);
169 nskb = alloc_skb(sizeof(*niph) + tcp_hdr_size + MAX_TCP_HEADER,
170 GFP_ATOMIC);
171 if (nskb == NULL)
172 return;
173 skb_reserve(nskb, MAX_TCP_HEADER);
174
175 niph = synproxy_build_ip(nskb, iph->daddr, iph->saddr);
176
177 skb_reset_transport_header(nskb);
178 nth = (struct tcphdr *)skb_put(nskb, tcp_hdr_size);
179 nth->source = th->dest;
180 nth->dest = th->source;
181 nth->seq = htonl(ntohl(th->ack_seq));
182 nth->ack_seq = htonl(ntohl(th->seq) + 1);
183 tcp_flag_word(nth) = TCP_FLAG_ACK;
184 nth->doff = tcp_hdr_size / 4;
185 nth->window = htons(state->seen[IP_CT_DIR_ORIGINAL].td_maxwin);
186 nth->check = 0;
187 nth->urg_ptr = 0;
188
189 synproxy_build_options(nth, opts);
190
191 synproxy_send_tcp(skb, nskb, NULL, 0, niph, nth, tcp_hdr_size);
192}
193
194static void
195synproxy_send_client_ack(const struct synproxy_net *snet,
196 const struct sk_buff *skb, const struct tcphdr *th,
197 const struct synproxy_options *opts)
198{
199 struct sk_buff *nskb;
200 struct iphdr *iph, *niph;
201 struct tcphdr *nth;
202 unsigned int tcp_hdr_size;
203
204 iph = ip_hdr(skb);
205
206 tcp_hdr_size = sizeof(*nth) + synproxy_options_size(opts);
207 nskb = alloc_skb(sizeof(*niph) + tcp_hdr_size + MAX_TCP_HEADER,
208 GFP_ATOMIC);
209 if (nskb == NULL)
210 return;
211 skb_reserve(nskb, MAX_TCP_HEADER);
212
213 niph = synproxy_build_ip(nskb, iph->saddr, iph->daddr);
214
215 skb_reset_transport_header(nskb);
216 nth = (struct tcphdr *)skb_put(nskb, tcp_hdr_size);
217 nth->source = th->source;
218 nth->dest = th->dest;
219 nth->seq = htonl(ntohl(th->seq) + 1);
220 nth->ack_seq = th->ack_seq;
221 tcp_flag_word(nth) = TCP_FLAG_ACK;
222 nth->doff = tcp_hdr_size / 4;
223 nth->window = ntohs(htons(th->window) >> opts->wscale);
224 nth->check = 0;
225 nth->urg_ptr = 0;
226
227 synproxy_build_options(nth, opts);
228
229 synproxy_send_tcp(skb, nskb, NULL, 0, niph, nth, tcp_hdr_size);
230}
231
232static bool
233synproxy_recv_client_ack(const struct synproxy_net *snet,
234 const struct sk_buff *skb, const struct tcphdr *th,
235 struct synproxy_options *opts, u32 recv_seq)
236{
237 int mss;
238
239 mss = __cookie_v4_check(ip_hdr(skb), th, ntohl(th->ack_seq) - 1);
240 if (mss == 0) {
241 this_cpu_inc(snet->stats->cookie_invalid);
242 return false;
243 }
244
245 this_cpu_inc(snet->stats->cookie_valid);
246 opts->mss = mss;
247
248 if (opts->options & XT_SYNPROXY_OPT_TIMESTAMP)
249 synproxy_check_timestamp_cookie(opts);
250
251 synproxy_send_server_syn(snet, skb, th, opts, recv_seq);
252 return true;
253}
254
255static unsigned int
256synproxy_tg4(struct sk_buff *skb, const struct xt_action_param *par)
257{
258 const struct xt_synproxy_info *info = par->targinfo;
259 struct synproxy_net *snet = synproxy_pernet(dev_net(par->in));
260 struct synproxy_options opts = {};
261 struct tcphdr *th, _th;
262
263 if (nf_ip_checksum(skb, par->hooknum, par->thoff, IPPROTO_TCP))
264 return NF_DROP;
265
266 th = skb_header_pointer(skb, par->thoff, sizeof(_th), &_th);
267 if (th == NULL)
268 return NF_DROP;
269
270 synproxy_parse_options(skb, par->thoff, th, &opts);
271
272 if (th->syn && !th->ack) {
273 /* Initial SYN from client */
274 this_cpu_inc(snet->stats->syn_received);
275
276 if (th->ece && th->cwr)
277 opts.options |= XT_SYNPROXY_OPT_ECN;
278
279 opts.options &= info->options;
280 if (opts.options & XT_SYNPROXY_OPT_TIMESTAMP)
281 synproxy_init_timestamp_cookie(info, &opts);
282 else
283 opts.options &= ~(XT_SYNPROXY_OPT_WSCALE |
284 XT_SYNPROXY_OPT_SACK_PERM |
285 XT_SYNPROXY_OPT_ECN);
286
287 synproxy_send_client_synack(skb, th, &opts);
288 } else if (th->ack && !(th->fin || th->rst))
289 /* ACK from client */
290 synproxy_recv_client_ack(snet, skb, th, &opts, ntohl(th->seq));
291
292 return NF_DROP;
293}
294
295static unsigned int ipv4_synproxy_hook(unsigned int hooknum,
296 struct sk_buff *skb,
297 const struct net_device *in,
298 const struct net_device *out,
299 int (*okfn)(struct sk_buff *))
300{
301 struct synproxy_net *snet = synproxy_pernet(dev_net(in ? : out));
302 enum ip_conntrack_info ctinfo;
303 struct nf_conn *ct;
304 struct nf_conn_synproxy *synproxy;
305 struct synproxy_options opts = {};
306 const struct ip_ct_tcp *state;
307 struct tcphdr *th, _th;
308 unsigned int thoff;
309
310 ct = nf_ct_get(skb, &ctinfo);
311 if (ct == NULL)
312 return NF_ACCEPT;
313
314 synproxy = nfct_synproxy(ct);
315 if (synproxy == NULL)
316 return NF_ACCEPT;
317
318 if (nf_is_loopback_packet(skb))
319 return NF_ACCEPT;
320
321 thoff = ip_hdrlen(skb);
322 th = skb_header_pointer(skb, thoff, sizeof(_th), &_th);
323 if (th == NULL)
324 return NF_DROP;
325
326 state = &ct->proto.tcp;
327 switch (state->state) {
328 case TCP_CONNTRACK_CLOSE:
329 if (th->rst && !test_bit(IPS_SEEN_REPLY_BIT, &ct->status)) {
330 nf_ct_seqadj_init(ct, ctinfo, synproxy->isn -
331 ntohl(th->seq) + 1);
332 break;
333 }
334
335 if (!th->syn || th->ack ||
336 CTINFO2DIR(ctinfo) != IP_CT_DIR_ORIGINAL)
337 break;
338
339 /* Reopened connection - reset the sequence number and timestamp
340 * adjustments, they will get initialized once the connection is
341 * reestablished.
342 */
343 nf_ct_seqadj_init(ct, ctinfo, 0);
344 synproxy->tsoff = 0;
345 this_cpu_inc(snet->stats->conn_reopened);
346
347 /* fall through */
348 case TCP_CONNTRACK_SYN_SENT:
349 synproxy_parse_options(skb, thoff, th, &opts);
350
351 if (!th->syn && th->ack &&
352 CTINFO2DIR(ctinfo) == IP_CT_DIR_ORIGINAL) {
353 /* Keep-Alives are sent with SEG.SEQ = SND.NXT-1,
354 * therefore we need to add 1 to make the SYN sequence
355 * number match the one of first SYN.
356 */
357 if (synproxy_recv_client_ack(snet, skb, th, &opts,
358 ntohl(th->seq) + 1))
359 this_cpu_inc(snet->stats->cookie_retrans);
360
361 return NF_DROP;
362 }
363
364 synproxy->isn = ntohl(th->ack_seq);
365 if (opts.options & XT_SYNPROXY_OPT_TIMESTAMP)
366 synproxy->its = opts.tsecr;
367 break;
368 case TCP_CONNTRACK_SYN_RECV:
369 if (!th->syn || !th->ack)
370 break;
371
372 synproxy_parse_options(skb, thoff, th, &opts);
373 if (opts.options & XT_SYNPROXY_OPT_TIMESTAMP)
374 synproxy->tsoff = opts.tsval - synproxy->its;
375
376 opts.options &= ~(XT_SYNPROXY_OPT_MSS |
377 XT_SYNPROXY_OPT_WSCALE |
378 XT_SYNPROXY_OPT_SACK_PERM);
379
380 swap(opts.tsval, opts.tsecr);
381 synproxy_send_server_ack(snet, state, skb, th, &opts);
382
383 nf_ct_seqadj_init(ct, ctinfo, synproxy->isn - ntohl(th->seq));
384
385 swap(opts.tsval, opts.tsecr);
386 synproxy_send_client_ack(snet, skb, th, &opts);
387
388 consume_skb(skb);
389 return NF_STOLEN;
390 default:
391 break;
392 }
393
394 synproxy_tstamp_adjust(skb, thoff, th, ct, ctinfo, synproxy);
395 return NF_ACCEPT;
396}
397
398static int synproxy_tg4_check(const struct xt_tgchk_param *par)
399{
400 const struct ipt_entry *e = par->entryinfo;
401
402 if (e->ip.proto != IPPROTO_TCP ||
403 e->ip.invflags & XT_INV_PROTO)
404 return -EINVAL;
405
406 return nf_ct_l3proto_try_module_get(par->family);
407}
408
409static void synproxy_tg4_destroy(const struct xt_tgdtor_param *par)
410{
411 nf_ct_l3proto_module_put(par->family);
412}
413
414static struct xt_target synproxy_tg4_reg __read_mostly = {
415 .name = "SYNPROXY",
416 .family = NFPROTO_IPV4,
417 .target = synproxy_tg4,
418 .targetsize = sizeof(struct xt_synproxy_info),
419 .checkentry = synproxy_tg4_check,
420 .destroy = synproxy_tg4_destroy,
421 .me = THIS_MODULE,
422};
423
424static struct nf_hook_ops ipv4_synproxy_ops[] __read_mostly = {
425 {
426 .hook = ipv4_synproxy_hook,
427 .owner = THIS_MODULE,
428 .pf = NFPROTO_IPV4,
429 .hooknum = NF_INET_LOCAL_IN,
430 .priority = NF_IP_PRI_CONNTRACK_CONFIRM - 1,
431 },
432 {
433 .hook = ipv4_synproxy_hook,
434 .owner = THIS_MODULE,
435 .pf = NFPROTO_IPV4,
436 .hooknum = NF_INET_POST_ROUTING,
437 .priority = NF_IP_PRI_CONNTRACK_CONFIRM - 1,
438 },
439};
440
441static int __init synproxy_tg4_init(void)
442{
443 int err;
444
445 err = nf_register_hooks(ipv4_synproxy_ops,
446 ARRAY_SIZE(ipv4_synproxy_ops));
447 if (err < 0)
448 goto err1;
449
450 err = xt_register_target(&synproxy_tg4_reg);
451 if (err < 0)
452 goto err2;
453
454 return 0;
455
456err2:
457 nf_unregister_hooks(ipv4_synproxy_ops, ARRAY_SIZE(ipv4_synproxy_ops));
458err1:
459 return err;
460}
461
462static void __exit synproxy_tg4_exit(void)
463{
464 xt_unregister_target(&synproxy_tg4_reg);
465 nf_unregister_hooks(ipv4_synproxy_ops, ARRAY_SIZE(ipv4_synproxy_ops));
466}
467
468module_init(synproxy_tg4_init);
469module_exit(synproxy_tg4_exit);
470
471MODULE_LICENSE("GPL");
472MODULE_AUTHOR("Patrick McHardy <kaber@trash.net>");
diff --git a/net/netfilter/Kconfig b/net/netfilter/Kconfig
index c45fc1a60e0d..62a171ab204f 100644
--- a/net/netfilter/Kconfig
+++ b/net/netfilter/Kconfig
@@ -408,6 +408,9 @@ config NF_NAT_TFTP
408 depends on NF_CONNTRACK && NF_NAT 408 depends on NF_CONNTRACK && NF_NAT
409 default NF_NAT && NF_CONNTRACK_TFTP 409 default NF_NAT && NF_CONNTRACK_TFTP
410 410
411config NETFILTER_SYNPROXY
412 tristate
413
411endif # NF_CONNTRACK 414endif # NF_CONNTRACK
412 415
413config NETFILTER_XTABLES 416config NETFILTER_XTABLES
diff --git a/net/netfilter/Makefile b/net/netfilter/Makefile
index 89a9c1658f5e..c3a0a12907f6 100644
--- a/net/netfilter/Makefile
+++ b/net/netfilter/Makefile
@@ -61,6 +61,9 @@ obj-$(CONFIG_NF_NAT_IRC) += nf_nat_irc.o
61obj-$(CONFIG_NF_NAT_SIP) += nf_nat_sip.o 61obj-$(CONFIG_NF_NAT_SIP) += nf_nat_sip.o
62obj-$(CONFIG_NF_NAT_TFTP) += nf_nat_tftp.o 62obj-$(CONFIG_NF_NAT_TFTP) += nf_nat_tftp.o
63 63
64# SYNPROXY
65obj-$(CONFIG_NETFILTER_SYNPROXY) += nf_synproxy_core.o
66
64# generic X tables 67# generic X tables
65obj-$(CONFIG_NETFILTER_XTABLES) += x_tables.o xt_tcpudp.o 68obj-$(CONFIG_NETFILTER_XTABLES) += x_tables.o xt_tcpudp.o
66 69
diff --git a/net/netfilter/nf_conntrack_core.c b/net/netfilter/nf_conntrack_core.c
index 00a7a94d4132..5d892febd64c 100644
--- a/net/netfilter/nf_conntrack_core.c
+++ b/net/netfilter/nf_conntrack_core.c
@@ -48,6 +48,7 @@
48#include <net/netfilter/nf_conntrack_timestamp.h> 48#include <net/netfilter/nf_conntrack_timestamp.h>
49#include <net/netfilter/nf_conntrack_timeout.h> 49#include <net/netfilter/nf_conntrack_timeout.h>
50#include <net/netfilter/nf_conntrack_labels.h> 50#include <net/netfilter/nf_conntrack_labels.h>
51#include <net/netfilter/nf_conntrack_synproxy.h>
51#include <net/netfilter/nf_nat.h> 52#include <net/netfilter/nf_nat.h>
52#include <net/netfilter/nf_nat_core.h> 53#include <net/netfilter/nf_nat_core.h>
53#include <net/netfilter/nf_nat_helper.h> 54#include <net/netfilter/nf_nat_helper.h>
@@ -799,6 +800,11 @@ init_conntrack(struct net *net, struct nf_conn *tmpl,
799 if (IS_ERR(ct)) 800 if (IS_ERR(ct))
800 return (struct nf_conntrack_tuple_hash *)ct; 801 return (struct nf_conntrack_tuple_hash *)ct;
801 802
803 if (tmpl && nfct_synproxy(tmpl)) {
804 nfct_seqadj_ext_add(ct);
805 nfct_synproxy_ext_add(ct);
806 }
807
802 timeout_ext = tmpl ? nf_ct_timeout_find(tmpl) : NULL; 808 timeout_ext = tmpl ? nf_ct_timeout_find(tmpl) : NULL;
803 if (timeout_ext) 809 if (timeout_ext)
804 timeouts = NF_CT_TIMEOUT_EXT_DATA(timeout_ext); 810 timeouts = NF_CT_TIMEOUT_EXT_DATA(timeout_ext);
diff --git a/net/netfilter/nf_conntrack_proto_tcp.c b/net/netfilter/nf_conntrack_proto_tcp.c
index 984a8d1a3359..44d1ea32570a 100644
--- a/net/netfilter/nf_conntrack_proto_tcp.c
+++ b/net/netfilter/nf_conntrack_proto_tcp.c
@@ -28,6 +28,7 @@
28#include <net/netfilter/nf_conntrack_l4proto.h> 28#include <net/netfilter/nf_conntrack_l4proto.h>
29#include <net/netfilter/nf_conntrack_ecache.h> 29#include <net/netfilter/nf_conntrack_ecache.h>
30#include <net/netfilter/nf_conntrack_seqadj.h> 30#include <net/netfilter/nf_conntrack_seqadj.h>
31#include <net/netfilter/nf_conntrack_synproxy.h>
31#include <net/netfilter/nf_log.h> 32#include <net/netfilter/nf_log.h>
32#include <net/netfilter/ipv4/nf_conntrack_ipv4.h> 33#include <net/netfilter/ipv4/nf_conntrack_ipv4.h>
33#include <net/netfilter/ipv6/nf_conntrack_ipv6.h> 34#include <net/netfilter/ipv6/nf_conntrack_ipv6.h>
@@ -946,6 +947,21 @@ static int tcp_packet(struct nf_conn *ct,
946 "state %s ", tcp_conntrack_names[old_state]); 947 "state %s ", tcp_conntrack_names[old_state]);
947 return NF_ACCEPT; 948 return NF_ACCEPT;
948 case TCP_CONNTRACK_MAX: 949 case TCP_CONNTRACK_MAX:
950 /* Special case for SYN proxy: when the SYN to the server or
951 * the SYN/ACK from the server is lost, the client may transmit
952 * a keep-alive packet while in SYN_SENT state. This needs to
953 * be associated with the original conntrack entry in order to
954 * generate a new SYN with the correct sequence number.
955 */
956 if (nfct_synproxy(ct) && old_state == TCP_CONNTRACK_SYN_SENT &&
957 index == TCP_ACK_SET && dir == IP_CT_DIR_ORIGINAL &&
958 ct->proto.tcp.last_dir == IP_CT_DIR_ORIGINAL &&
959 ct->proto.tcp.seen[dir].td_end - 1 == ntohl(th->seq)) {
960 pr_debug("nf_ct_tcp: SYN proxy client keep alive\n");
961 spin_unlock_bh(&ct->lock);
962 return NF_ACCEPT;
963 }
964
949 /* Invalid packet */ 965 /* Invalid packet */
950 pr_debug("nf_ct_tcp: Invalid dir=%i index=%u ostate=%u\n", 966 pr_debug("nf_ct_tcp: Invalid dir=%i index=%u ostate=%u\n",
951 dir, get_conntrack_index(th), old_state); 967 dir, get_conntrack_index(th), old_state);
diff --git a/net/netfilter/nf_conntrack_seqadj.c b/net/netfilter/nf_conntrack_seqadj.c
index 483eb9ce3216..5f9bfd060dea 100644
--- a/net/netfilter/nf_conntrack_seqadj.c
+++ b/net/netfilter/nf_conntrack_seqadj.c
@@ -6,6 +6,26 @@
6#include <net/netfilter/nf_conntrack_extend.h> 6#include <net/netfilter/nf_conntrack_extend.h>
7#include <net/netfilter/nf_conntrack_seqadj.h> 7#include <net/netfilter/nf_conntrack_seqadj.h>
8 8
9int nf_ct_seqadj_init(struct nf_conn *ct, enum ip_conntrack_info ctinfo,
10 s32 off)
11{
12 enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
13 struct nf_conn_seqadj *seqadj;
14 struct nf_ct_seqadj *this_way;
15
16 if (off == 0)
17 return 0;
18
19 set_bit(IPS_SEQ_ADJUST_BIT, &ct->status);
20
21 seqadj = nfct_seqadj(ct);
22 this_way = &seqadj->seq[dir];
23 this_way->offset_before = off;
24 this_way->offset_after = off;
25 return 0;
26}
27EXPORT_SYMBOL_GPL(nf_ct_seqadj_init);
28
9int nf_ct_seqadj_set(struct nf_conn *ct, enum ip_conntrack_info ctinfo, 29int nf_ct_seqadj_set(struct nf_conn *ct, enum ip_conntrack_info ctinfo,
10 __be32 seq, s32 off) 30 __be32 seq, s32 off)
11{ 31{
diff --git a/net/netfilter/nf_synproxy_core.c b/net/netfilter/nf_synproxy_core.c
new file mode 100644
index 000000000000..d23dc791aca7
--- /dev/null
+++ b/net/netfilter/nf_synproxy_core.c
@@ -0,0 +1,432 @@
1/*
2 * Copyright (c) 2013 Patrick McHardy <kaber@trash.net>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 as
6 * published by the Free Software Foundation.
7 */
8
9#include <linux/module.h>
10#include <linux/skbuff.h>
11#include <asm/unaligned.h>
12#include <net/tcp.h>
13#include <net/netns/generic.h>
14
15#include <linux/netfilter_ipv4/ip_tables.h>
16#include <linux/netfilter/x_tables.h>
17#include <linux/netfilter/xt_tcpudp.h>
18#include <linux/netfilter/xt_SYNPROXY.h>
19#include <net/netfilter/nf_conntrack.h>
20#include <net/netfilter/nf_conntrack_extend.h>
21#include <net/netfilter/nf_conntrack_seqadj.h>
22#include <net/netfilter/nf_conntrack_synproxy.h>
23
24int synproxy_net_id;
25EXPORT_SYMBOL_GPL(synproxy_net_id);
26
27void
28synproxy_parse_options(const struct sk_buff *skb, unsigned int doff,
29 const struct tcphdr *th, struct synproxy_options *opts)
30{
31 int length = (th->doff * 4) - sizeof(*th);
32 u8 buf[40], *ptr;
33
34 ptr = skb_header_pointer(skb, doff + sizeof(*th), length, buf);
35 BUG_ON(ptr == NULL);
36
37 opts->options = 0;
38 while (length > 0) {
39 int opcode = *ptr++;
40 int opsize;
41
42 switch (opcode) {
43 case TCPOPT_EOL:
44 return;
45 case TCPOPT_NOP:
46 length--;
47 continue;
48 default:
49 opsize = *ptr++;
50 if (opsize < 2)
51 return;
52 if (opsize > length)
53 return;
54
55 switch (opcode) {
56 case TCPOPT_MSS:
57 if (opsize == TCPOLEN_MSS) {
58 opts->mss = get_unaligned_be16(ptr);
59 opts->options |= XT_SYNPROXY_OPT_MSS;
60 }
61 break;
62 case TCPOPT_WINDOW:
63 if (opsize == TCPOLEN_WINDOW) {
64 opts->wscale = *ptr;
65 if (opts->wscale > 14)
66 opts->wscale = 14;
67 opts->options |= XT_SYNPROXY_OPT_WSCALE;
68 }
69 break;
70 case TCPOPT_TIMESTAMP:
71 if (opsize == TCPOLEN_TIMESTAMP) {
72 opts->tsval = get_unaligned_be32(ptr);
73 opts->tsecr = get_unaligned_be32(ptr + 4);
74 opts->options |= XT_SYNPROXY_OPT_TIMESTAMP;
75 }
76 break;
77 case TCPOPT_SACK_PERM:
78 if (opsize == TCPOLEN_SACK_PERM)
79 opts->options |= XT_SYNPROXY_OPT_SACK_PERM;
80 break;
81 }
82
83 ptr += opsize - 2;
84 length -= opsize;
85 }
86 }
87}
88EXPORT_SYMBOL_GPL(synproxy_parse_options);
89
90unsigned int synproxy_options_size(const struct synproxy_options *opts)
91{
92 unsigned int size = 0;
93
94 if (opts->options & XT_SYNPROXY_OPT_MSS)
95 size += TCPOLEN_MSS_ALIGNED;
96 if (opts->options & XT_SYNPROXY_OPT_TIMESTAMP)
97 size += TCPOLEN_TSTAMP_ALIGNED;
98 else if (opts->options & XT_SYNPROXY_OPT_SACK_PERM)
99 size += TCPOLEN_SACKPERM_ALIGNED;
100 if (opts->options & XT_SYNPROXY_OPT_WSCALE)
101 size += TCPOLEN_WSCALE_ALIGNED;
102
103 return size;
104}
105EXPORT_SYMBOL_GPL(synproxy_options_size);
106
107void
108synproxy_build_options(struct tcphdr *th, const struct synproxy_options *opts)
109{
110 __be32 *ptr = (__be32 *)(th + 1);
111 u8 options = opts->options;
112
113 if (options & XT_SYNPROXY_OPT_MSS)
114 *ptr++ = htonl((TCPOPT_MSS << 24) |
115 (TCPOLEN_MSS << 16) |
116 opts->mss);
117
118 if (options & XT_SYNPROXY_OPT_TIMESTAMP) {
119 if (options & XT_SYNPROXY_OPT_SACK_PERM)
120 *ptr++ = htonl((TCPOPT_SACK_PERM << 24) |
121 (TCPOLEN_SACK_PERM << 16) |
122 (TCPOPT_TIMESTAMP << 8) |
123 TCPOLEN_TIMESTAMP);
124 else
125 *ptr++ = htonl((TCPOPT_NOP << 24) |
126 (TCPOPT_NOP << 16) |
127 (TCPOPT_TIMESTAMP << 8) |
128 TCPOLEN_TIMESTAMP);
129
130 *ptr++ = htonl(opts->tsval);
131 *ptr++ = htonl(opts->tsecr);
132 } else if (options & XT_SYNPROXY_OPT_SACK_PERM)
133 *ptr++ = htonl((TCPOPT_NOP << 24) |
134 (TCPOPT_NOP << 16) |
135 (TCPOPT_SACK_PERM << 8) |
136 TCPOLEN_SACK_PERM);
137
138 if (options & XT_SYNPROXY_OPT_WSCALE)
139 *ptr++ = htonl((TCPOPT_NOP << 24) |
140 (TCPOPT_WINDOW << 16) |
141 (TCPOLEN_WINDOW << 8) |
142 opts->wscale);
143}
144EXPORT_SYMBOL_GPL(synproxy_build_options);
145
146void synproxy_init_timestamp_cookie(const struct xt_synproxy_info *info,
147 struct synproxy_options *opts)
148{
149 opts->tsecr = opts->tsval;
150 opts->tsval = tcp_time_stamp & ~0x3f;
151
152 if (opts->options & XT_SYNPROXY_OPT_WSCALE)
153 opts->tsval |= info->wscale;
154 else
155 opts->tsval |= 0xf;
156
157 if (opts->options & XT_SYNPROXY_OPT_SACK_PERM)
158 opts->tsval |= 1 << 4;
159
160 if (opts->options & XT_SYNPROXY_OPT_ECN)
161 opts->tsval |= 1 << 5;
162}
163EXPORT_SYMBOL_GPL(synproxy_init_timestamp_cookie);
164
165void synproxy_check_timestamp_cookie(struct synproxy_options *opts)
166{
167 opts->wscale = opts->tsecr & 0xf;
168 if (opts->wscale != 0xf)
169 opts->options |= XT_SYNPROXY_OPT_WSCALE;
170
171 opts->options |= opts->tsecr & (1 << 4) ? XT_SYNPROXY_OPT_SACK_PERM : 0;
172
173 opts->options |= opts->tsecr & (1 << 5) ? XT_SYNPROXY_OPT_ECN : 0;
174}
175EXPORT_SYMBOL_GPL(synproxy_check_timestamp_cookie);
176
177unsigned int synproxy_tstamp_adjust(struct sk_buff *skb,
178 unsigned int protoff,
179 struct tcphdr *th,
180 struct nf_conn *ct,
181 enum ip_conntrack_info ctinfo,
182 const struct nf_conn_synproxy *synproxy)
183{
184 unsigned int optoff, optend;
185 u32 *ptr, old;
186
187 if (synproxy->tsoff == 0)
188 return 1;
189
190 optoff = protoff + sizeof(struct tcphdr);
191 optend = protoff + th->doff * 4;
192
193 if (!skb_make_writable(skb, optend))
194 return 0;
195
196 while (optoff < optend) {
197 unsigned char *op = skb->data + optoff;
198
199 switch (op[0]) {
200 case TCPOPT_EOL:
201 return 1;
202 case TCPOPT_NOP:
203 optoff++;
204 continue;
205 default:
206 if (optoff + 1 == optend ||
207 optoff + op[1] > optend ||
208 op[1] < 2)
209 return 0;
210 if (op[0] == TCPOPT_TIMESTAMP &&
211 op[1] == TCPOLEN_TIMESTAMP) {
212 if (CTINFO2DIR(ctinfo) == IP_CT_DIR_REPLY) {
213 ptr = (u32 *)&op[2];
214 old = *ptr;
215 *ptr = htonl(ntohl(*ptr) -
216 synproxy->tsoff);
217 } else {
218 ptr = (u32 *)&op[6];
219 old = *ptr;
220 *ptr = htonl(ntohl(*ptr) +
221 synproxy->tsoff);
222 }
223 inet_proto_csum_replace4(&th->check, skb,
224 old, *ptr, 0);
225 return 1;
226 }
227 optoff += op[1];
228 }
229 }
230 return 1;
231}
232EXPORT_SYMBOL_GPL(synproxy_tstamp_adjust);
233
234static struct nf_ct_ext_type nf_ct_synproxy_extend __read_mostly = {
235 .len = sizeof(struct nf_conn_synproxy),
236 .align = __alignof__(struct nf_conn_synproxy),
237 .id = NF_CT_EXT_SYNPROXY,
238};
239
240#ifdef CONFIG_PROC_FS
241static void *synproxy_cpu_seq_start(struct seq_file *seq, loff_t *pos)
242{
243 struct synproxy_net *snet = synproxy_pernet(seq_file_net(seq));
244 int cpu;
245
246 if (*pos == 0)
247 return SEQ_START_TOKEN;
248
249 for (cpu = *pos - 1; cpu < nr_cpu_ids; cpu++) {
250 if (!cpu_possible(cpu))
251 continue;
252 *pos = cpu + 1;
253 return per_cpu_ptr(snet->stats, cpu);
254 }
255
256 return NULL;
257}
258
259static void *synproxy_cpu_seq_next(struct seq_file *seq, void *v, loff_t *pos)
260{
261 struct synproxy_net *snet = synproxy_pernet(seq_file_net(seq));
262 int cpu;
263
264 for (cpu = *pos; cpu < nr_cpu_ids; cpu++) {
265 if (!cpu_possible(cpu))
266 continue;
267 *pos = cpu + 1;
268 return per_cpu_ptr(snet->stats, cpu);
269 }
270
271 return NULL;
272}
273
274static void synproxy_cpu_seq_stop(struct seq_file *seq, void *v)
275{
276 return;
277}
278
279static int synproxy_cpu_seq_show(struct seq_file *seq, void *v)
280{
281 struct synproxy_stats *stats = v;
282
283 if (v == SEQ_START_TOKEN) {
284 seq_printf(seq, "entries\t\tsyn_received\t"
285 "cookie_invalid\tcookie_valid\t"
286 "cookie_retrans\tconn_reopened\n");
287 return 0;
288 }
289
290 seq_printf(seq, "%08x\t%08x\t%08x\t%08x\t%08x\t%08x\n", 0,
291 stats->syn_received,
292 stats->cookie_invalid,
293 stats->cookie_valid,
294 stats->cookie_retrans,
295 stats->conn_reopened);
296
297 return 0;
298}
299
300static const struct seq_operations synproxy_cpu_seq_ops = {
301 .start = synproxy_cpu_seq_start,
302 .next = synproxy_cpu_seq_next,
303 .stop = synproxy_cpu_seq_stop,
304 .show = synproxy_cpu_seq_show,
305};
306
307static int synproxy_cpu_seq_open(struct inode *inode, struct file *file)
308{
309 return seq_open_net(inode, file, &synproxy_cpu_seq_ops,
310 sizeof(struct seq_net_private));
311}
312
313static const struct file_operations synproxy_cpu_seq_fops = {
314 .owner = THIS_MODULE,
315 .open = synproxy_cpu_seq_open,
316 .read = seq_read,
317 .llseek = seq_lseek,
318 .release = seq_release_net,
319};
320
321static int __net_init synproxy_proc_init(struct net *net)
322{
323 if (!proc_create("synproxy", S_IRUGO, net->proc_net_stat,
324 &synproxy_cpu_seq_fops))
325 return -ENOMEM;
326 return 0;
327}
328
329static void __net_exit synproxy_proc_exit(struct net *net)
330{
331 remove_proc_entry("synproxy", net->proc_net_stat);
332}
333#else
334static int __net_init synproxy_proc_init(struct net *net)
335{
336 return 0;
337}
338
339static void __net_exit synproxy_proc_exit(struct net *net)
340{
341 return;
342}
343#endif /* CONFIG_PROC_FS */
344
345static int __net_init synproxy_net_init(struct net *net)
346{
347 struct synproxy_net *snet = synproxy_pernet(net);
348 struct nf_conntrack_tuple t;
349 struct nf_conn *ct;
350 int err = -ENOMEM;
351
352 memset(&t, 0, sizeof(t));
353 ct = nf_conntrack_alloc(net, 0, &t, &t, GFP_KERNEL);
354 if (IS_ERR(ct)) {
355 err = PTR_ERR(ct);
356 goto err1;
357 }
358
359 __set_bit(IPS_TEMPLATE_BIT, &ct->status);
360 __set_bit(IPS_CONFIRMED_BIT, &ct->status);
361 if (!nfct_seqadj_ext_add(ct))
362 goto err2;
363 if (!nfct_synproxy_ext_add(ct))
364 goto err2;
365
366 snet->tmpl = ct;
367
368 snet->stats = alloc_percpu(struct synproxy_stats);
369 if (snet->stats == NULL)
370 goto err2;
371
372 err = synproxy_proc_init(net);
373 if (err < 0)
374 goto err3;
375
376 return 0;
377
378err3:
379 free_percpu(snet->stats);
380err2:
381 nf_conntrack_free(ct);
382err1:
383 return err;
384}
385
386static void __net_exit synproxy_net_exit(struct net *net)
387{
388 struct synproxy_net *snet = synproxy_pernet(net);
389
390 nf_conntrack_free(snet->tmpl);
391 synproxy_proc_exit(net);
392 free_percpu(snet->stats);
393}
394
395static struct pernet_operations synproxy_net_ops = {
396 .init = synproxy_net_init,
397 .exit = synproxy_net_exit,
398 .id = &synproxy_net_id,
399 .size = sizeof(struct synproxy_net),
400};
401
402static int __init synproxy_core_init(void)
403{
404 int err;
405
406 err = nf_ct_extend_register(&nf_ct_synproxy_extend);
407 if (err < 0)
408 goto err1;
409
410 err = register_pernet_subsys(&synproxy_net_ops);
411 if (err < 0)
412 goto err2;
413
414 return 0;
415
416err2:
417 nf_ct_extend_unregister(&nf_ct_synproxy_extend);
418err1:
419 return err;
420}
421
422static void __exit synproxy_core_exit(void)
423{
424 unregister_pernet_subsys(&synproxy_net_ops);
425 nf_ct_extend_unregister(&nf_ct_synproxy_extend);
426}
427
428module_init(synproxy_core_init);
429module_exit(synproxy_core_exit);
430
431MODULE_LICENSE("GPL");
432MODULE_AUTHOR("Patrick McHardy <kaber@trash.net>");