2 * Copyright (c) 2013 Patrick McHardy <kaber@trash.net>
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.
9 #include <linux/module.h>
10 #include <linux/skbuff.h>
11 #include <net/ip6_checksum.h>
12 #include <net/ip6_route.h>
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>
22 static struct ipv6hdr
*
23 synproxy_build_ip(struct sk_buff
*skb
, const struct in6_addr
*saddr
,
24 const struct in6_addr
*daddr
)
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
;
40 synproxy_send_tcp(const struct synproxy_net
*snet
,
41 const struct sk_buff
*skb
, struct sk_buff
*nskb
,
42 struct nf_conntrack
*nfct
, enum ip_conntrack_info ctinfo
,
43 struct ipv6hdr
*niph
, struct tcphdr
*nth
,
44 unsigned int tcp_hdr_size
)
46 struct net
*net
= nf_ct_net(snet
->tmpl
);
47 struct dst_entry
*dst
;
50 nth
->check
= ~tcp_v6_check(tcp_hdr_size
, &niph
->saddr
, &niph
->daddr
, 0);
51 nskb
->ip_summed
= CHECKSUM_PARTIAL
;
52 nskb
->csum_start
= (unsigned char *)nth
- nskb
->head
;
53 nskb
->csum_offset
= offsetof(struct tcphdr
, check
);
55 memset(&fl6
, 0, sizeof(fl6
));
56 fl6
.flowi6_proto
= IPPROTO_TCP
;
57 fl6
.saddr
= niph
->saddr
;
58 fl6
.daddr
= niph
->daddr
;
59 fl6
.fl6_sport
= nth
->source
;
60 fl6
.fl6_dport
= nth
->dest
;
61 security_skb_classify_flow((struct sk_buff
*)skb
, flowi6_to_flowi(&fl6
));
62 dst
= ip6_route_output(net
, NULL
, &fl6
);
63 if (dst
== NULL
|| dst
->error
) {
67 dst
= xfrm_lookup(net
, dst
, flowi6_to_flowi(&fl6
), NULL
, 0);
71 skb_dst_set(nskb
, dst
);
75 nskb
->nfctinfo
= ctinfo
;
76 nf_conntrack_get(nfct
);
79 ip6_local_out(net
, nskb
->sk
, nskb
);
87 synproxy_send_client_synack(const struct synproxy_net
*snet
,
88 const struct sk_buff
*skb
, const struct tcphdr
*th
,
89 const struct synproxy_options
*opts
)
92 struct ipv6hdr
*iph
, *niph
;
94 unsigned int tcp_hdr_size
;
99 tcp_hdr_size
= sizeof(*nth
) + synproxy_options_size(opts
);
100 nskb
= alloc_skb(sizeof(*niph
) + tcp_hdr_size
+ MAX_TCP_HEADER
,
104 skb_reserve(nskb
, MAX_TCP_HEADER
);
106 niph
= synproxy_build_ip(nskb
, &iph
->daddr
, &iph
->saddr
);
108 skb_reset_transport_header(nskb
);
109 nth
= (struct tcphdr
*)skb_put(nskb
, tcp_hdr_size
);
110 nth
->source
= th
->dest
;
111 nth
->dest
= th
->source
;
112 nth
->seq
= htonl(__cookie_v6_init_sequence(iph
, th
, &mss
));
113 nth
->ack_seq
= htonl(ntohl(th
->seq
) + 1);
114 tcp_flag_word(nth
) = TCP_FLAG_SYN
| TCP_FLAG_ACK
;
115 if (opts
->options
& XT_SYNPROXY_OPT_ECN
)
116 tcp_flag_word(nth
) |= TCP_FLAG_ECE
;
117 nth
->doff
= tcp_hdr_size
/ 4;
122 synproxy_build_options(nth
, opts
);
124 synproxy_send_tcp(snet
, skb
, nskb
, skb
->nfct
, IP_CT_ESTABLISHED_REPLY
,
125 niph
, nth
, tcp_hdr_size
);
129 synproxy_send_server_syn(const struct synproxy_net
*snet
,
130 const struct sk_buff
*skb
, const struct tcphdr
*th
,
131 const struct synproxy_options
*opts
, u32 recv_seq
)
133 struct sk_buff
*nskb
;
134 struct ipv6hdr
*iph
, *niph
;
136 unsigned int tcp_hdr_size
;
140 tcp_hdr_size
= sizeof(*nth
) + synproxy_options_size(opts
);
141 nskb
= alloc_skb(sizeof(*niph
) + tcp_hdr_size
+ MAX_TCP_HEADER
,
145 skb_reserve(nskb
, MAX_TCP_HEADER
);
147 niph
= synproxy_build_ip(nskb
, &iph
->saddr
, &iph
->daddr
);
149 skb_reset_transport_header(nskb
);
150 nth
= (struct tcphdr
*)skb_put(nskb
, tcp_hdr_size
);
151 nth
->source
= th
->source
;
152 nth
->dest
= th
->dest
;
153 nth
->seq
= htonl(recv_seq
- 1);
154 /* ack_seq is used to relay our ISN to the synproxy hook to initialize
155 * sequence number translation once a connection tracking entry exists.
157 nth
->ack_seq
= htonl(ntohl(th
->ack_seq
) - 1);
158 tcp_flag_word(nth
) = TCP_FLAG_SYN
;
159 if (opts
->options
& XT_SYNPROXY_OPT_ECN
)
160 tcp_flag_word(nth
) |= TCP_FLAG_ECE
| TCP_FLAG_CWR
;
161 nth
->doff
= tcp_hdr_size
/ 4;
162 nth
->window
= th
->window
;
166 synproxy_build_options(nth
, opts
);
168 synproxy_send_tcp(snet
, skb
, nskb
, &snet
->tmpl
->ct_general
, IP_CT_NEW
,
169 niph
, nth
, tcp_hdr_size
);
173 synproxy_send_server_ack(const struct synproxy_net
*snet
,
174 const struct ip_ct_tcp
*state
,
175 const struct sk_buff
*skb
, const struct tcphdr
*th
,
176 const struct synproxy_options
*opts
)
178 struct sk_buff
*nskb
;
179 struct ipv6hdr
*iph
, *niph
;
181 unsigned int tcp_hdr_size
;
185 tcp_hdr_size
= sizeof(*nth
) + synproxy_options_size(opts
);
186 nskb
= alloc_skb(sizeof(*niph
) + tcp_hdr_size
+ MAX_TCP_HEADER
,
190 skb_reserve(nskb
, MAX_TCP_HEADER
);
192 niph
= synproxy_build_ip(nskb
, &iph
->daddr
, &iph
->saddr
);
194 skb_reset_transport_header(nskb
);
195 nth
= (struct tcphdr
*)skb_put(nskb
, tcp_hdr_size
);
196 nth
->source
= th
->dest
;
197 nth
->dest
= th
->source
;
198 nth
->seq
= htonl(ntohl(th
->ack_seq
));
199 nth
->ack_seq
= htonl(ntohl(th
->seq
) + 1);
200 tcp_flag_word(nth
) = TCP_FLAG_ACK
;
201 nth
->doff
= tcp_hdr_size
/ 4;
202 nth
->window
= htons(state
->seen
[IP_CT_DIR_ORIGINAL
].td_maxwin
);
206 synproxy_build_options(nth
, opts
);
208 synproxy_send_tcp(snet
, skb
, nskb
, NULL
, 0, niph
, nth
, tcp_hdr_size
);
212 synproxy_send_client_ack(const struct synproxy_net
*snet
,
213 const struct sk_buff
*skb
, const struct tcphdr
*th
,
214 const struct synproxy_options
*opts
)
216 struct sk_buff
*nskb
;
217 struct ipv6hdr
*iph
, *niph
;
219 unsigned int tcp_hdr_size
;
223 tcp_hdr_size
= sizeof(*nth
) + synproxy_options_size(opts
);
224 nskb
= alloc_skb(sizeof(*niph
) + tcp_hdr_size
+ MAX_TCP_HEADER
,
228 skb_reserve(nskb
, MAX_TCP_HEADER
);
230 niph
= synproxy_build_ip(nskb
, &iph
->saddr
, &iph
->daddr
);
232 skb_reset_transport_header(nskb
);
233 nth
= (struct tcphdr
*)skb_put(nskb
, tcp_hdr_size
);
234 nth
->source
= th
->source
;
235 nth
->dest
= th
->dest
;
236 nth
->seq
= htonl(ntohl(th
->seq
) + 1);
237 nth
->ack_seq
= th
->ack_seq
;
238 tcp_flag_word(nth
) = TCP_FLAG_ACK
;
239 nth
->doff
= tcp_hdr_size
/ 4;
240 nth
->window
= htons(ntohs(th
->window
) >> opts
->wscale
);
244 synproxy_build_options(nth
, opts
);
246 synproxy_send_tcp(snet
, skb
, nskb
, skb
->nfct
, IP_CT_ESTABLISHED_REPLY
,
247 niph
, nth
, tcp_hdr_size
);
251 synproxy_recv_client_ack(const struct synproxy_net
*snet
,
252 const struct sk_buff
*skb
, const struct tcphdr
*th
,
253 struct synproxy_options
*opts
, u32 recv_seq
)
257 mss
= __cookie_v6_check(ipv6_hdr(skb
), th
, ntohl(th
->ack_seq
) - 1);
259 this_cpu_inc(snet
->stats
->cookie_invalid
);
263 this_cpu_inc(snet
->stats
->cookie_valid
);
265 opts
->options
|= XT_SYNPROXY_OPT_MSS
;
267 if (opts
->options
& XT_SYNPROXY_OPT_TIMESTAMP
)
268 synproxy_check_timestamp_cookie(opts
);
270 synproxy_send_server_syn(snet
, skb
, th
, opts
, recv_seq
);
275 synproxy_tg6(struct sk_buff
*skb
, const struct xt_action_param
*par
)
277 const struct xt_synproxy_info
*info
= par
->targinfo
;
278 struct synproxy_net
*snet
= synproxy_pernet(par
->net
);
279 struct synproxy_options opts
= {};
280 struct tcphdr
*th
, _th
;
282 if (nf_ip6_checksum(skb
, par
->hooknum
, par
->thoff
, IPPROTO_TCP
))
285 th
= skb_header_pointer(skb
, par
->thoff
, sizeof(_th
), &_th
);
289 if (!synproxy_parse_options(skb
, par
->thoff
, th
, &opts
))
292 if (th
->syn
&& !(th
->ack
|| th
->fin
|| th
->rst
)) {
293 /* Initial SYN from client */
294 this_cpu_inc(snet
->stats
->syn_received
);
296 if (th
->ece
&& th
->cwr
)
297 opts
.options
|= XT_SYNPROXY_OPT_ECN
;
299 opts
.options
&= info
->options
;
300 if (opts
.options
& XT_SYNPROXY_OPT_TIMESTAMP
)
301 synproxy_init_timestamp_cookie(info
, &opts
);
303 opts
.options
&= ~(XT_SYNPROXY_OPT_WSCALE
|
304 XT_SYNPROXY_OPT_SACK_PERM
|
305 XT_SYNPROXY_OPT_ECN
);
307 synproxy_send_client_synack(snet
, skb
, th
, &opts
);
310 } else if (th
->ack
&& !(th
->fin
|| th
->rst
|| th
->syn
)) {
311 /* ACK from client */
312 synproxy_recv_client_ack(snet
, skb
, th
, &opts
, ntohl(th
->seq
));
319 static unsigned int ipv6_synproxy_hook(void *priv
,
321 const struct nf_hook_state
*nhs
)
323 struct synproxy_net
*snet
= synproxy_pernet(nhs
->net
);
324 enum ip_conntrack_info ctinfo
;
326 struct nf_conn_synproxy
*synproxy
;
327 struct synproxy_options opts
= {};
328 const struct ip_ct_tcp
*state
;
329 struct tcphdr
*th
, _th
;
334 ct
= nf_ct_get(skb
, &ctinfo
);
338 synproxy
= nfct_synproxy(ct
);
339 if (synproxy
== NULL
)
342 if (nf_is_loopback_packet(skb
))
345 nexthdr
= ipv6_hdr(skb
)->nexthdr
;
346 thoff
= ipv6_skip_exthdr(skb
, sizeof(struct ipv6hdr
), &nexthdr
,
351 th
= skb_header_pointer(skb
, thoff
, sizeof(_th
), &_th
);
355 state
= &ct
->proto
.tcp
;
356 switch (state
->state
) {
357 case TCP_CONNTRACK_CLOSE
:
358 if (th
->rst
&& !test_bit(IPS_SEEN_REPLY_BIT
, &ct
->status
)) {
359 nf_ct_seqadj_init(ct
, ctinfo
, synproxy
->isn
-
364 if (!th
->syn
|| th
->ack
||
365 CTINFO2DIR(ctinfo
) != IP_CT_DIR_ORIGINAL
)
368 /* Reopened connection - reset the sequence number and timestamp
369 * adjustments, they will get initialized once the connection is
372 nf_ct_seqadj_init(ct
, ctinfo
, 0);
374 this_cpu_inc(snet
->stats
->conn_reopened
);
377 case TCP_CONNTRACK_SYN_SENT
:
378 if (!synproxy_parse_options(skb
, thoff
, th
, &opts
))
381 if (!th
->syn
&& th
->ack
&&
382 CTINFO2DIR(ctinfo
) == IP_CT_DIR_ORIGINAL
) {
383 /* Keep-Alives are sent with SEG.SEQ = SND.NXT-1,
384 * therefore we need to add 1 to make the SYN sequence
385 * number match the one of first SYN.
387 if (synproxy_recv_client_ack(snet
, skb
, th
, &opts
,
389 this_cpu_inc(snet
->stats
->cookie_retrans
);
394 synproxy
->isn
= ntohl(th
->ack_seq
);
395 if (opts
.options
& XT_SYNPROXY_OPT_TIMESTAMP
)
396 synproxy
->its
= opts
.tsecr
;
398 case TCP_CONNTRACK_SYN_RECV
:
399 if (!th
->syn
|| !th
->ack
)
402 if (!synproxy_parse_options(skb
, thoff
, th
, &opts
))
405 if (opts
.options
& XT_SYNPROXY_OPT_TIMESTAMP
)
406 synproxy
->tsoff
= opts
.tsval
- synproxy
->its
;
408 opts
.options
&= ~(XT_SYNPROXY_OPT_MSS
|
409 XT_SYNPROXY_OPT_WSCALE
|
410 XT_SYNPROXY_OPT_SACK_PERM
);
412 swap(opts
.tsval
, opts
.tsecr
);
413 synproxy_send_server_ack(snet
, state
, skb
, th
, &opts
);
415 nf_ct_seqadj_init(ct
, ctinfo
, synproxy
->isn
- ntohl(th
->seq
));
417 swap(opts
.tsval
, opts
.tsecr
);
418 synproxy_send_client_ack(snet
, skb
, th
, &opts
);
426 synproxy_tstamp_adjust(skb
, thoff
, th
, ct
, ctinfo
, synproxy
);
430 static int synproxy_tg6_check(const struct xt_tgchk_param
*par
)
432 const struct ip6t_entry
*e
= par
->entryinfo
;
434 if (!(e
->ipv6
.flags
& IP6T_F_PROTO
) ||
435 e
->ipv6
.proto
!= IPPROTO_TCP
||
436 e
->ipv6
.invflags
& XT_INV_PROTO
)
439 return nf_ct_l3proto_try_module_get(par
->family
);
442 static void synproxy_tg6_destroy(const struct xt_tgdtor_param
*par
)
444 nf_ct_l3proto_module_put(par
->family
);
447 static struct xt_target synproxy_tg6_reg __read_mostly
= {
449 .family
= NFPROTO_IPV6
,
450 .hooks
= (1 << NF_INET_LOCAL_IN
) | (1 << NF_INET_FORWARD
),
451 .target
= synproxy_tg6
,
452 .targetsize
= sizeof(struct xt_synproxy_info
),
453 .checkentry
= synproxy_tg6_check
,
454 .destroy
= synproxy_tg6_destroy
,
458 static struct nf_hook_ops ipv6_synproxy_ops
[] __read_mostly
= {
460 .hook
= ipv6_synproxy_hook
,
462 .hooknum
= NF_INET_LOCAL_IN
,
463 .priority
= NF_IP_PRI_CONNTRACK_CONFIRM
- 1,
466 .hook
= ipv6_synproxy_hook
,
468 .hooknum
= NF_INET_POST_ROUTING
,
469 .priority
= NF_IP_PRI_CONNTRACK_CONFIRM
- 1,
473 static int __init
synproxy_tg6_init(void)
477 err
= nf_register_hooks(ipv6_synproxy_ops
,
478 ARRAY_SIZE(ipv6_synproxy_ops
));
482 err
= xt_register_target(&synproxy_tg6_reg
);
489 nf_unregister_hooks(ipv6_synproxy_ops
, ARRAY_SIZE(ipv6_synproxy_ops
));
494 static void __exit
synproxy_tg6_exit(void)
496 xt_unregister_target(&synproxy_tg6_reg
);
497 nf_unregister_hooks(ipv6_synproxy_ops
, ARRAY_SIZE(ipv6_synproxy_ops
));
500 module_init(synproxy_tg6_init
);
501 module_exit(synproxy_tg6_exit
);
503 MODULE_LICENSE("GPL");
504 MODULE_AUTHOR("Patrick McHardy <kaber@trash.net>");