aboutsummaryrefslogtreecommitdiffstats
path: root/net/ipv6
diff options
context:
space:
mode:
authorPatrick McHardy <kaber@trash.net>2013-08-27 02:50:16 -0400
committerPablo Neira Ayuso <pablo@netfilter.org>2013-08-27 18:28:13 -0400
commit4ad362282cb45bbc831a182e45637da8c5bd7aa1 (patch)
tree66d91d0ac5947985d41ff8c12cbed39fa2548e9d /net/ipv6
parent81eb6a1487718a89621a0e0be7fafd0cd7c429a4 (diff)
netfilter: add IPv6 SYNPROXY target
Add an IPv6 version of the SYNPROXY target. The main differences to the IPv4 version is routing and IP header construction. 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/ipv6')
-rw-r--r--net/ipv6/netfilter/Kconfig13
-rw-r--r--net/ipv6/netfilter/Makefile1
-rw-r--r--net/ipv6/netfilter/ip6t_SYNPROXY.c495
3 files changed, 509 insertions, 0 deletions
diff --git a/net/ipv6/netfilter/Kconfig b/net/ipv6/netfilter/Kconfig
index 4433ab40e7de..a7f842b29b67 100644
--- a/net/ipv6/netfilter/Kconfig
+++ b/net/ipv6/netfilter/Kconfig
@@ -153,6 +153,19 @@ config IP6_NF_TARGET_REJECT
153 153
154 To compile it as a module, choose M here. If unsure, say N. 154 To compile it as a module, choose M here. If unsure, say N.
155 155
156config IP6_NF_TARGET_SYNPROXY
157 tristate "SYNPROXY target support"
158 depends on NF_CONNTRACK && NETFILTER_ADVANCED
159 select NETFILTER_SYNPROXY
160 select SYN_COOKIES
161 help
162 The SYNPROXY target allows you to intercept TCP connections and
163 establish them using syncookies before they are passed on to the
164 server. This allows to avoid conntrack and server resource usage
165 during SYN-flood attacks.
166
167 To compile it as a module, choose M here. If unsure, say N.
168
156config IP6_NF_MANGLE 169config IP6_NF_MANGLE
157 tristate "Packet mangling" 170 tristate "Packet mangling"
158 default m if NETFILTER_ADVANCED=n 171 default m if NETFILTER_ADVANCED=n
diff --git a/net/ipv6/netfilter/Makefile b/net/ipv6/netfilter/Makefile
index ad13bf0d25f0..2b53738f798c 100644
--- a/net/ipv6/netfilter/Makefile
+++ b/net/ipv6/netfilter/Makefile
@@ -37,3 +37,4 @@ obj-$(CONFIG_IP6_NF_MATCH_RT) += ip6t_rt.o
37obj-$(CONFIG_IP6_NF_TARGET_MASQUERADE) += ip6t_MASQUERADE.o 37obj-$(CONFIG_IP6_NF_TARGET_MASQUERADE) += ip6t_MASQUERADE.o
38obj-$(CONFIG_IP6_NF_TARGET_NPT) += ip6t_NPT.o 38obj-$(CONFIG_IP6_NF_TARGET_NPT) += ip6t_NPT.o
39obj-$(CONFIG_IP6_NF_TARGET_REJECT) += ip6t_REJECT.o 39obj-$(CONFIG_IP6_NF_TARGET_REJECT) += ip6t_REJECT.o
40obj-$(CONFIG_IP6_NF_TARGET_SYNPROXY) += ip6t_SYNPROXY.o
diff --git a/net/ipv6/netfilter/ip6t_SYNPROXY.c b/net/ipv6/netfilter/ip6t_SYNPROXY.c
new file mode 100644
index 000000000000..4270a9b145e5
--- /dev/null
+++ b/net/ipv6/netfilter/ip6t_SYNPROXY.c
@@ -0,0 +1,495 @@
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/ip6_checksum.h>
12#include <net/ip6_route.h>
13#include <net/tcp.h>
14
15#include <linux/netfilter_ipv6/ip6_tables.h>
16#include <linux/netfilter/x_tables.h>
17#include <linux/netfilter/xt_SYNPROXY.h>
18#include <net/netfilter/nf_conntrack.h>
19#include <net/netfilter/nf_conntrack_seqadj.h>
20#include <net/netfilter/nf_conntrack_synproxy.h>
21
22static struct ipv6hdr *
23synproxy_build_ip(struct sk_buff *skb, const struct in6_addr *saddr,
24 const struct in6_addr *daddr)
25{
26 struct ipv6hdr *iph;
27
28 skb_reset_network_header(skb);
29 iph = (struct ipv6hdr *)skb_put(skb, sizeof(*iph));
30 ip6_flow_hdr(iph, 0, 0);
31 iph->hop_limit = 64; //XXX
32 iph->nexthdr = IPPROTO_TCP;
33 iph->saddr = *saddr;
34 iph->daddr = *daddr;
35
36 return iph;
37}
38
39static void
40synproxy_send_tcp(const struct sk_buff *skb, struct sk_buff *nskb,
41 struct nf_conntrack *nfct, enum ip_conntrack_info ctinfo,
42 struct ipv6hdr *niph, struct tcphdr *nth,
43 unsigned int tcp_hdr_size)
44{
45 struct net *net = nf_ct_net((struct nf_conn *)nfct);
46 struct dst_entry *dst;
47 struct flowi6 fl6;
48
49 nth->check = ~tcp_v6_check(tcp_hdr_size, &niph->saddr, &niph->daddr, 0);
50 nskb->ip_summed = CHECKSUM_PARTIAL;
51 nskb->csum_start = (unsigned char *)nth - nskb->head;
52 nskb->csum_offset = offsetof(struct tcphdr, check);
53
54 memset(&fl6, 0, sizeof(fl6));
55 fl6.flowi6_proto = IPPROTO_TCP;
56 fl6.saddr = niph->saddr;
57 fl6.daddr = niph->daddr;
58 fl6.fl6_sport = nth->source;
59 fl6.fl6_dport = nth->dest;
60 security_skb_classify_flow((struct sk_buff *)skb, flowi6_to_flowi(&fl6));
61 dst = ip6_route_output(net, NULL, &fl6);
62 if (dst == NULL || dst->error) {
63 dst_release(dst);
64 goto free_nskb;
65 }
66 dst = xfrm_lookup(net, dst, flowi6_to_flowi(&fl6), NULL, 0);
67 if (IS_ERR(dst))
68 goto free_nskb;
69
70 skb_dst_set(nskb, dst);
71
72 if (nfct) {
73 nskb->nfct = nfct;
74 nskb->nfctinfo = ctinfo;
75 nf_conntrack_get(nfct);
76 }
77
78 ip6_local_out(nskb);
79 return;
80
81free_nskb:
82 kfree_skb(nskb);
83}
84
85static void
86synproxy_send_client_synack(const struct sk_buff *skb, const struct tcphdr *th,
87 const struct synproxy_options *opts)
88{
89 struct sk_buff *nskb;
90 struct ipv6hdr *iph, *niph;
91 struct tcphdr *nth;
92 unsigned int tcp_hdr_size;
93 u16 mss = opts->mss;
94
95 iph = ipv6_hdr(skb);
96
97 tcp_hdr_size = sizeof(*nth) + synproxy_options_size(opts);
98 nskb = alloc_skb(sizeof(*niph) + tcp_hdr_size + MAX_TCP_HEADER,
99 GFP_ATOMIC);
100 if (nskb == NULL)
101 return;
102 skb_reserve(nskb, MAX_TCP_HEADER);
103
104 niph = synproxy_build_ip(nskb, &iph->daddr, &iph->saddr);
105
106 skb_reset_transport_header(nskb);
107 nth = (struct tcphdr *)skb_put(nskb, tcp_hdr_size);
108 nth->source = th->dest;
109 nth->dest = th->source;
110 nth->seq = htonl(__cookie_v6_init_sequence(iph, th, &mss));
111 nth->ack_seq = htonl(ntohl(th->seq) + 1);
112 tcp_flag_word(nth) = TCP_FLAG_SYN | TCP_FLAG_ACK;
113 if (opts->options & XT_SYNPROXY_OPT_ECN)
114 tcp_flag_word(nth) |= TCP_FLAG_ECE;
115 nth->doff = tcp_hdr_size / 4;
116 nth->window = 0;
117 nth->check = 0;
118 nth->urg_ptr = 0;
119
120 synproxy_build_options(nth, opts);
121
122 synproxy_send_tcp(skb, nskb, skb->nfct, IP_CT_ESTABLISHED_REPLY,
123 niph, nth, tcp_hdr_size);
124}
125
126static void
127synproxy_send_server_syn(const struct synproxy_net *snet,
128 const struct sk_buff *skb, const struct tcphdr *th,
129 const struct synproxy_options *opts, u32 recv_seq)
130{
131 struct sk_buff *nskb;
132 struct ipv6hdr *iph, *niph;
133 struct tcphdr *nth;
134 unsigned int tcp_hdr_size;
135
136 iph = ipv6_hdr(skb);
137
138 tcp_hdr_size = sizeof(*nth) + synproxy_options_size(opts);
139 nskb = alloc_skb(sizeof(*niph) + tcp_hdr_size + MAX_TCP_HEADER,
140 GFP_ATOMIC);
141 if (nskb == NULL)
142 return;
143 skb_reserve(nskb, MAX_TCP_HEADER);
144
145 niph = synproxy_build_ip(nskb, &iph->saddr, &iph->daddr);
146
147 skb_reset_transport_header(nskb);
148 nth = (struct tcphdr *)skb_put(nskb, tcp_hdr_size);
149 nth->source = th->source;
150 nth->dest = th->dest;
151 nth->seq = htonl(recv_seq - 1);
152 /* ack_seq is used to relay our ISN to the synproxy hook to initialize
153 * sequence number translation once a connection tracking entry exists.
154 */
155 nth->ack_seq = htonl(ntohl(th->ack_seq) - 1);
156 tcp_flag_word(nth) = TCP_FLAG_SYN;
157 if (opts->options & XT_SYNPROXY_OPT_ECN)
158 tcp_flag_word(nth) |= TCP_FLAG_ECE | TCP_FLAG_CWR;
159 nth->doff = tcp_hdr_size / 4;
160 nth->window = th->window;
161 nth->check = 0;
162 nth->urg_ptr = 0;
163
164 synproxy_build_options(nth, opts);
165
166 synproxy_send_tcp(skb, nskb, &snet->tmpl->ct_general, IP_CT_NEW,
167 niph, nth, tcp_hdr_size);
168}
169
170static void
171synproxy_send_server_ack(const struct synproxy_net *snet,
172 const struct ip_ct_tcp *state,
173 const struct sk_buff *skb, const struct tcphdr *th,
174 const struct synproxy_options *opts)
175{
176 struct sk_buff *nskb;
177 struct ipv6hdr *iph, *niph;
178 struct tcphdr *nth;
179 unsigned int tcp_hdr_size;
180
181 iph = ipv6_hdr(skb);
182
183 tcp_hdr_size = sizeof(*nth) + synproxy_options_size(opts);
184 nskb = alloc_skb(sizeof(*niph) + tcp_hdr_size + MAX_TCP_HEADER,
185 GFP_ATOMIC);
186 if (nskb == NULL)
187 return;
188 skb_reserve(nskb, MAX_TCP_HEADER);
189
190 niph = synproxy_build_ip(nskb, &iph->daddr, &iph->saddr);
191
192 skb_reset_transport_header(nskb);
193 nth = (struct tcphdr *)skb_put(nskb, tcp_hdr_size);
194 nth->source = th->dest;
195 nth->dest = th->source;
196 nth->seq = htonl(ntohl(th->ack_seq));
197 nth->ack_seq = htonl(ntohl(th->seq) + 1);
198 tcp_flag_word(nth) = TCP_FLAG_ACK;
199 nth->doff = tcp_hdr_size / 4;
200 nth->window = htons(state->seen[IP_CT_DIR_ORIGINAL].td_maxwin);
201 nth->check = 0;
202 nth->urg_ptr = 0;
203
204 synproxy_build_options(nth, opts);
205
206 synproxy_send_tcp(skb, nskb, NULL, 0, niph, nth, tcp_hdr_size);
207}
208
209static void
210synproxy_send_client_ack(const struct synproxy_net *snet,
211 const struct sk_buff *skb, const struct tcphdr *th,
212 const struct synproxy_options *opts)
213{
214 struct sk_buff *nskb;
215 struct ipv6hdr *iph, *niph;
216 struct tcphdr *nth;
217 unsigned int tcp_hdr_size;
218
219 iph = ipv6_hdr(skb);
220
221 tcp_hdr_size = sizeof(*nth) + synproxy_options_size(opts);
222 nskb = alloc_skb(sizeof(*niph) + tcp_hdr_size + MAX_TCP_HEADER,
223 GFP_ATOMIC);
224 if (nskb == NULL)
225 return;
226 skb_reserve(nskb, MAX_TCP_HEADER);
227
228 niph = synproxy_build_ip(nskb, &iph->saddr, &iph->daddr);
229
230 skb_reset_transport_header(nskb);
231 nth = (struct tcphdr *)skb_put(nskb, tcp_hdr_size);
232 nth->source = th->source;
233 nth->dest = th->dest;
234 nth->seq = htonl(ntohl(th->seq) + 1);
235 nth->ack_seq = th->ack_seq;
236 tcp_flag_word(nth) = TCP_FLAG_ACK;
237 nth->doff = tcp_hdr_size / 4;
238 nth->window = ntohs(htons(th->window) >> opts->wscale);
239 nth->check = 0;
240 nth->urg_ptr = 0;
241
242 synproxy_build_options(nth, opts);
243
244 synproxy_send_tcp(skb, nskb, NULL, 0, niph, nth, tcp_hdr_size);
245}
246
247static bool
248synproxy_recv_client_ack(const struct synproxy_net *snet,
249 const struct sk_buff *skb, const struct tcphdr *th,
250 struct synproxy_options *opts, u32 recv_seq)
251{
252 int mss;
253
254 mss = __cookie_v6_check(ipv6_hdr(skb), th, ntohl(th->ack_seq) - 1);
255 if (mss == 0) {
256 this_cpu_inc(snet->stats->cookie_invalid);
257 return false;
258 }
259
260 this_cpu_inc(snet->stats->cookie_valid);
261 opts->mss = mss;
262
263 if (opts->options & XT_SYNPROXY_OPT_TIMESTAMP)
264 synproxy_check_timestamp_cookie(opts);
265
266 synproxy_send_server_syn(snet, skb, th, opts, recv_seq);
267 return true;
268}
269
270static unsigned int
271synproxy_tg6(struct sk_buff *skb, const struct xt_action_param *par)
272{
273 const struct xt_synproxy_info *info = par->targinfo;
274 struct synproxy_net *snet = synproxy_pernet(dev_net(par->in));
275 struct synproxy_options opts = {};
276 struct tcphdr *th, _th;
277
278 if (nf_ip6_checksum(skb, par->hooknum, par->thoff, IPPROTO_TCP))
279 return NF_DROP;
280
281 th = skb_header_pointer(skb, par->thoff, sizeof(_th), &_th);
282 if (th == NULL)
283 return NF_DROP;
284
285 synproxy_parse_options(skb, par->thoff, th, &opts);
286
287 if (th->syn) {
288 /* Initial SYN from client */
289 this_cpu_inc(snet->stats->syn_received);
290
291 if (th->ece && th->cwr)
292 opts.options |= XT_SYNPROXY_OPT_ECN;
293
294 opts.options &= info->options;
295 if (opts.options & XT_SYNPROXY_OPT_TIMESTAMP)
296 synproxy_init_timestamp_cookie(info, &opts);
297 else
298 opts.options &= ~(XT_SYNPROXY_OPT_WSCALE |
299 XT_SYNPROXY_OPT_SACK_PERM |
300 XT_SYNPROXY_OPT_ECN);
301
302 synproxy_send_client_synack(skb, th, &opts);
303 } else if (th->ack && !(th->fin || th->rst))
304 /* ACK from client */
305 synproxy_recv_client_ack(snet, skb, th, &opts, ntohl(th->seq));
306
307 return NF_DROP;
308}
309
310static unsigned int ipv6_synproxy_hook(unsigned int hooknum,
311 struct sk_buff *skb,
312 const struct net_device *in,
313 const struct net_device *out,
314 int (*okfn)(struct sk_buff *))
315{
316 struct synproxy_net *snet = synproxy_pernet(dev_net(in ? : out));
317 enum ip_conntrack_info ctinfo;
318 struct nf_conn *ct;
319 struct nf_conn_synproxy *synproxy;
320 struct synproxy_options opts = {};
321 const struct ip_ct_tcp *state;
322 struct tcphdr *th, _th;
323 __be16 frag_off;
324 u8 nexthdr;
325 int thoff;
326
327 ct = nf_ct_get(skb, &ctinfo);
328 if (ct == NULL)
329 return NF_ACCEPT;
330
331 synproxy = nfct_synproxy(ct);
332 if (synproxy == NULL)
333 return NF_ACCEPT;
334
335 if (nf_is_loopback_packet(skb))
336 return NF_ACCEPT;
337
338 nexthdr = ipv6_hdr(skb)->nexthdr;
339 thoff = ipv6_skip_exthdr(skb, sizeof(struct ipv6hdr), &nexthdr,
340 &frag_off);
341 if (thoff < 0)
342 return NF_ACCEPT;
343
344 th = skb_header_pointer(skb, thoff, sizeof(_th), &_th);
345 if (th == NULL)
346 return NF_DROP;
347
348 state = &ct->proto.tcp;
349 switch (state->state) {
350 case TCP_CONNTRACK_CLOSE:
351 if (th->rst && !test_bit(IPS_SEEN_REPLY_BIT, &ct->status)) {
352 nf_ct_seqadj_init(ct, ctinfo, synproxy->isn -
353 ntohl(th->seq) + 1);
354 break;
355 }
356
357 if (!th->syn || th->ack ||
358 CTINFO2DIR(ctinfo) != IP_CT_DIR_ORIGINAL)
359 break;
360
361 /* Reopened connection - reset the sequence number and timestamp
362 * adjustments, they will get initialized once the connection is
363 * reestablished.
364 */
365 nf_ct_seqadj_init(ct, ctinfo, 0);
366 synproxy->tsoff = 0;
367 this_cpu_inc(snet->stats->conn_reopened);
368
369 /* fall through */
370 case TCP_CONNTRACK_SYN_SENT:
371 synproxy_parse_options(skb, thoff, th, &opts);
372
373 if (!th->syn && th->ack &&
374 CTINFO2DIR(ctinfo) == IP_CT_DIR_ORIGINAL) {
375 /* Keep-Alives are sent with SEG.SEQ = SND.NXT-1,
376 * therefore we need to add 1 to make the SYN sequence
377 * number match the one of first SYN.
378 */
379 if (synproxy_recv_client_ack(snet, skb, th, &opts,
380 ntohl(th->seq) + 1))
381 this_cpu_inc(snet->stats->cookie_retrans);
382
383 return NF_DROP;
384 }
385
386 synproxy->isn = ntohl(th->ack_seq);
387 if (opts.options & XT_SYNPROXY_OPT_TIMESTAMP)
388 synproxy->its = opts.tsecr;
389 break;
390 case TCP_CONNTRACK_SYN_RECV:
391 if (!th->syn || !th->ack)
392 break;
393
394 synproxy_parse_options(skb, thoff, th, &opts);
395 if (opts.options & XT_SYNPROXY_OPT_TIMESTAMP)
396 synproxy->tsoff = opts.tsval - synproxy->its;
397
398 opts.options &= ~(XT_SYNPROXY_OPT_MSS |
399 XT_SYNPROXY_OPT_WSCALE |
400 XT_SYNPROXY_OPT_SACK_PERM);
401
402 swap(opts.tsval, opts.tsecr);
403 synproxy_send_server_ack(snet, state, skb, th, &opts);
404
405 nf_ct_seqadj_init(ct, ctinfo, synproxy->isn - ntohl(th->seq));
406
407 swap(opts.tsval, opts.tsecr);
408 synproxy_send_client_ack(snet, skb, th, &opts);
409
410 consume_skb(skb);
411 return NF_STOLEN;
412 default:
413 break;
414 }
415
416 synproxy_tstamp_adjust(skb, thoff, th, ct, ctinfo, synproxy);
417 return NF_ACCEPT;
418}
419
420static int synproxy_tg6_check(const struct xt_tgchk_param *par)
421{
422 const struct ip6t_entry *e = par->entryinfo;
423
424 if (!(e->ipv6.flags & IP6T_F_PROTO) ||
425 e->ipv6.proto != IPPROTO_TCP ||
426 e->ipv6.invflags & XT_INV_PROTO)
427 return -EINVAL;
428
429 return nf_ct_l3proto_try_module_get(par->family);
430}
431
432static void synproxy_tg6_destroy(const struct xt_tgdtor_param *par)
433{
434 nf_ct_l3proto_module_put(par->family);
435}
436
437static struct xt_target synproxy_tg6_reg __read_mostly = {
438 .name = "SYNPROXY",
439 .family = NFPROTO_IPV6,
440 .target = synproxy_tg6,
441 .targetsize = sizeof(struct xt_synproxy_info),
442 .checkentry = synproxy_tg6_check,
443 .destroy = synproxy_tg6_destroy,
444 .me = THIS_MODULE,
445};
446
447static struct nf_hook_ops ipv6_synproxy_ops[] __read_mostly = {
448 {
449 .hook = ipv6_synproxy_hook,
450 .owner = THIS_MODULE,
451 .pf = NFPROTO_IPV6,
452 .hooknum = NF_INET_LOCAL_IN,
453 .priority = NF_IP_PRI_CONNTRACK_CONFIRM - 1,
454 },
455 {
456 .hook = ipv6_synproxy_hook,
457 .owner = THIS_MODULE,
458 .pf = NFPROTO_IPV6,
459 .hooknum = NF_INET_POST_ROUTING,
460 .priority = NF_IP_PRI_CONNTRACK_CONFIRM - 1,
461 },
462};
463
464static int __init synproxy_tg6_init(void)
465{
466 int err;
467
468 err = nf_register_hooks(ipv6_synproxy_ops,
469 ARRAY_SIZE(ipv6_synproxy_ops));
470 if (err < 0)
471 goto err1;
472
473 err = xt_register_target(&synproxy_tg6_reg);
474 if (err < 0)
475 goto err2;
476
477 return 0;
478
479err2:
480 nf_unregister_hooks(ipv6_synproxy_ops, ARRAY_SIZE(ipv6_synproxy_ops));
481err1:
482 return err;
483}
484
485static void __exit synproxy_tg6_exit(void)
486{
487 xt_unregister_target(&synproxy_tg6_reg);
488 nf_unregister_hooks(ipv6_synproxy_ops, ARRAY_SIZE(ipv6_synproxy_ops));
489}
490
491module_init(synproxy_tg6_init);
492module_exit(synproxy_tg6_exit);
493
494MODULE_LICENSE("GPL");
495MODULE_AUTHOR("Patrick McHardy <kaber@trash.net>");