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 net
*net
, struct sk_buff
*skb
,
24 const struct in6_addr
*saddr
,
25 const struct in6_addr
*daddr
)
29 skb_reset_network_header(skb
);
30 iph
= (struct ipv6hdr
*)skb_put(skb
, sizeof(*iph
));
31 ip6_flow_hdr(iph
, 0, 0);
32 iph
->hop_limit
= net
->ipv6
.devconf_all
->hop_limit
;
33 iph
->nexthdr
= IPPROTO_TCP
;
41 synproxy_send_tcp(struct net
*net
,
42 const struct sk_buff
*skb
, struct sk_buff
*nskb
,
43 struct nf_conntrack
*nfct
, enum ip_conntrack_info ctinfo
,
44 struct ipv6hdr
*niph
, struct tcphdr
*nth
,
45 unsigned int tcp_hdr_size
)
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
);
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(struct net
*net
,
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(net
, 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(net
, skb
, nskb
, skb
->nfct
, IP_CT_ESTABLISHED_REPLY
,
125 niph
, nth
, tcp_hdr_size
);
129 synproxy_send_server_syn(struct net
*net
,
130 const struct sk_buff
*skb
, const struct tcphdr
*th
,
131 const struct synproxy_options
*opts
, u32 recv_seq
)
133 struct synproxy_net
*snet
= synproxy_pernet(net
);
134 struct sk_buff
*nskb
;
135 struct ipv6hdr
*iph
, *niph
;
137 unsigned int tcp_hdr_size
;
141 tcp_hdr_size
= sizeof(*nth
) + synproxy_options_size(opts
);
142 nskb
= alloc_skb(sizeof(*niph
) + tcp_hdr_size
+ MAX_TCP_HEADER
,
146 skb_reserve(nskb
, MAX_TCP_HEADER
);
148 niph
= synproxy_build_ip(net
, nskb
, &iph
->saddr
, &iph
->daddr
);
150 skb_reset_transport_header(nskb
);
151 nth
= (struct tcphdr
*)skb_put(nskb
, tcp_hdr_size
);
152 nth
->source
= th
->source
;
153 nth
->dest
= th
->dest
;
154 nth
->seq
= htonl(recv_seq
- 1);
155 /* ack_seq is used to relay our ISN to the synproxy hook to initialize
156 * sequence number translation once a connection tracking entry exists.
158 nth
->ack_seq
= htonl(ntohl(th
->ack_seq
) - 1);
159 tcp_flag_word(nth
) = TCP_FLAG_SYN
;
160 if (opts
->options
& XT_SYNPROXY_OPT_ECN
)
161 tcp_flag_word(nth
) |= TCP_FLAG_ECE
| TCP_FLAG_CWR
;
162 nth
->doff
= tcp_hdr_size
/ 4;
163 nth
->window
= th
->window
;
167 synproxy_build_options(nth
, opts
);
169 synproxy_send_tcp(net
, skb
, nskb
, &snet
->tmpl
->ct_general
, IP_CT_NEW
,
170 niph
, nth
, tcp_hdr_size
);
174 synproxy_send_server_ack(struct net
*net
,
175 const struct ip_ct_tcp
*state
,
176 const struct sk_buff
*skb
, const struct tcphdr
*th
,
177 const struct synproxy_options
*opts
)
179 struct sk_buff
*nskb
;
180 struct ipv6hdr
*iph
, *niph
;
182 unsigned int tcp_hdr_size
;
186 tcp_hdr_size
= sizeof(*nth
) + synproxy_options_size(opts
);
187 nskb
= alloc_skb(sizeof(*niph
) + tcp_hdr_size
+ MAX_TCP_HEADER
,
191 skb_reserve(nskb
, MAX_TCP_HEADER
);
193 niph
= synproxy_build_ip(net
, nskb
, &iph
->daddr
, &iph
->saddr
);
195 skb_reset_transport_header(nskb
);
196 nth
= (struct tcphdr
*)skb_put(nskb
, tcp_hdr_size
);
197 nth
->source
= th
->dest
;
198 nth
->dest
= th
->source
;
199 nth
->seq
= htonl(ntohl(th
->ack_seq
));
200 nth
->ack_seq
= htonl(ntohl(th
->seq
) + 1);
201 tcp_flag_word(nth
) = TCP_FLAG_ACK
;
202 nth
->doff
= tcp_hdr_size
/ 4;
203 nth
->window
= htons(state
->seen
[IP_CT_DIR_ORIGINAL
].td_maxwin
);
207 synproxy_build_options(nth
, opts
);
209 synproxy_send_tcp(net
, skb
, nskb
, NULL
, 0, niph
, nth
, tcp_hdr_size
);
213 synproxy_send_client_ack(struct net
*net
,
214 const struct sk_buff
*skb
, const struct tcphdr
*th
,
215 const struct synproxy_options
*opts
)
217 struct sk_buff
*nskb
;
218 struct ipv6hdr
*iph
, *niph
;
220 unsigned int tcp_hdr_size
;
224 tcp_hdr_size
= sizeof(*nth
) + synproxy_options_size(opts
);
225 nskb
= alloc_skb(sizeof(*niph
) + tcp_hdr_size
+ MAX_TCP_HEADER
,
229 skb_reserve(nskb
, MAX_TCP_HEADER
);
231 niph
= synproxy_build_ip(net
, nskb
, &iph
->saddr
, &iph
->daddr
);
233 skb_reset_transport_header(nskb
);
234 nth
= (struct tcphdr
*)skb_put(nskb
, tcp_hdr_size
);
235 nth
->source
= th
->source
;
236 nth
->dest
= th
->dest
;
237 nth
->seq
= htonl(ntohl(th
->seq
) + 1);
238 nth
->ack_seq
= th
->ack_seq
;
239 tcp_flag_word(nth
) = TCP_FLAG_ACK
;
240 nth
->doff
= tcp_hdr_size
/ 4;
241 nth
->window
= htons(ntohs(th
->window
) >> opts
->wscale
);
245 synproxy_build_options(nth
, opts
);
247 synproxy_send_tcp(net
, skb
, nskb
, skb
->nfct
, IP_CT_ESTABLISHED_REPLY
,
248 niph
, nth
, tcp_hdr_size
);
252 synproxy_recv_client_ack(struct net
*net
,
253 const struct sk_buff
*skb
, const struct tcphdr
*th
,
254 struct synproxy_options
*opts
, u32 recv_seq
)
256 struct synproxy_net
*snet
= synproxy_pernet(net
);
259 mss
= __cookie_v6_check(ipv6_hdr(skb
), th
, ntohl(th
->ack_seq
) - 1);
261 this_cpu_inc(snet
->stats
->cookie_invalid
);
265 this_cpu_inc(snet
->stats
->cookie_valid
);
267 opts
->options
|= XT_SYNPROXY_OPT_MSS
;
269 if (opts
->options
& XT_SYNPROXY_OPT_TIMESTAMP
)
270 synproxy_check_timestamp_cookie(opts
);
272 synproxy_send_server_syn(net
, skb
, th
, opts
, recv_seq
);
277 synproxy_tg6(struct sk_buff
*skb
, const struct xt_action_param
*par
)
279 const struct xt_synproxy_info
*info
= par
->targinfo
;
280 struct net
*net
= xt_net(par
);
281 struct synproxy_net
*snet
= synproxy_pernet(net
);
282 struct synproxy_options opts
= {};
283 struct tcphdr
*th
, _th
;
285 if (nf_ip6_checksum(skb
, xt_hooknum(par
), par
->thoff
, IPPROTO_TCP
))
288 th
= skb_header_pointer(skb
, par
->thoff
, sizeof(_th
), &_th
);
292 if (!synproxy_parse_options(skb
, par
->thoff
, th
, &opts
))
295 if (th
->syn
&& !(th
->ack
|| th
->fin
|| th
->rst
)) {
296 /* Initial SYN from client */
297 this_cpu_inc(snet
->stats
->syn_received
);
299 if (th
->ece
&& th
->cwr
)
300 opts
.options
|= XT_SYNPROXY_OPT_ECN
;
302 opts
.options
&= info
->options
;
303 if (opts
.options
& XT_SYNPROXY_OPT_TIMESTAMP
)
304 synproxy_init_timestamp_cookie(info
, &opts
);
306 opts
.options
&= ~(XT_SYNPROXY_OPT_WSCALE
|
307 XT_SYNPROXY_OPT_SACK_PERM
|
308 XT_SYNPROXY_OPT_ECN
);
310 synproxy_send_client_synack(net
, skb
, th
, &opts
);
313 } else if (th
->ack
&& !(th
->fin
|| th
->rst
|| th
->syn
)) {
314 /* ACK from client */
315 synproxy_recv_client_ack(net
, skb
, th
, &opts
, ntohl(th
->seq
));
322 static unsigned int ipv6_synproxy_hook(void *priv
,
324 const struct nf_hook_state
*nhs
)
326 struct net
*net
= nhs
->net
;
327 struct synproxy_net
*snet
= synproxy_pernet(net
);
328 enum ip_conntrack_info ctinfo
;
330 struct nf_conn_synproxy
*synproxy
;
331 struct synproxy_options opts
= {};
332 const struct ip_ct_tcp
*state
;
333 struct tcphdr
*th
, _th
;
338 ct
= nf_ct_get(skb
, &ctinfo
);
342 synproxy
= nfct_synproxy(ct
);
343 if (synproxy
== NULL
)
346 if (nf_is_loopback_packet(skb
))
349 nexthdr
= ipv6_hdr(skb
)->nexthdr
;
350 thoff
= ipv6_skip_exthdr(skb
, sizeof(struct ipv6hdr
), &nexthdr
,
355 th
= skb_header_pointer(skb
, thoff
, sizeof(_th
), &_th
);
359 state
= &ct
->proto
.tcp
;
360 switch (state
->state
) {
361 case TCP_CONNTRACK_CLOSE
:
362 if (th
->rst
&& !test_bit(IPS_SEEN_REPLY_BIT
, &ct
->status
)) {
363 nf_ct_seqadj_init(ct
, ctinfo
, synproxy
->isn
-
368 if (!th
->syn
|| th
->ack
||
369 CTINFO2DIR(ctinfo
) != IP_CT_DIR_ORIGINAL
)
372 /* Reopened connection - reset the sequence number and timestamp
373 * adjustments, they will get initialized once the connection is
376 nf_ct_seqadj_init(ct
, ctinfo
, 0);
378 this_cpu_inc(snet
->stats
->conn_reopened
);
381 case TCP_CONNTRACK_SYN_SENT
:
382 if (!synproxy_parse_options(skb
, thoff
, th
, &opts
))
385 if (!th
->syn
&& th
->ack
&&
386 CTINFO2DIR(ctinfo
) == IP_CT_DIR_ORIGINAL
) {
387 /* Keep-Alives are sent with SEG.SEQ = SND.NXT-1,
388 * therefore we need to add 1 to make the SYN sequence
389 * number match the one of first SYN.
391 if (synproxy_recv_client_ack(net
, skb
, th
, &opts
,
393 this_cpu_inc(snet
->stats
->cookie_retrans
);
398 synproxy
->isn
= ntohl(th
->ack_seq
);
399 if (opts
.options
& XT_SYNPROXY_OPT_TIMESTAMP
)
400 synproxy
->its
= opts
.tsecr
;
402 case TCP_CONNTRACK_SYN_RECV
:
403 if (!th
->syn
|| !th
->ack
)
406 if (!synproxy_parse_options(skb
, thoff
, th
, &opts
))
409 if (opts
.options
& XT_SYNPROXY_OPT_TIMESTAMP
)
410 synproxy
->tsoff
= opts
.tsval
- synproxy
->its
;
412 opts
.options
&= ~(XT_SYNPROXY_OPT_MSS
|
413 XT_SYNPROXY_OPT_WSCALE
|
414 XT_SYNPROXY_OPT_SACK_PERM
);
416 swap(opts
.tsval
, opts
.tsecr
);
417 synproxy_send_server_ack(net
, state
, skb
, th
, &opts
);
419 nf_ct_seqadj_init(ct
, ctinfo
, synproxy
->isn
- ntohl(th
->seq
));
421 swap(opts
.tsval
, opts
.tsecr
);
422 synproxy_send_client_ack(net
, skb
, th
, &opts
);
430 synproxy_tstamp_adjust(skb
, thoff
, th
, ct
, ctinfo
, synproxy
);
434 static int synproxy_tg6_check(const struct xt_tgchk_param
*par
)
436 const struct ip6t_entry
*e
= par
->entryinfo
;
438 if (!(e
->ipv6
.flags
& IP6T_F_PROTO
) ||
439 e
->ipv6
.proto
!= IPPROTO_TCP
||
440 e
->ipv6
.invflags
& XT_INV_PROTO
)
443 return nf_ct_netns_get(par
->net
, par
->family
);
446 static void synproxy_tg6_destroy(const struct xt_tgdtor_param
*par
)
448 nf_ct_netns_put(par
->net
, par
->family
);
451 static struct xt_target synproxy_tg6_reg __read_mostly
= {
453 .family
= NFPROTO_IPV6
,
454 .hooks
= (1 << NF_INET_LOCAL_IN
) | (1 << NF_INET_FORWARD
),
455 .target
= synproxy_tg6
,
456 .targetsize
= sizeof(struct xt_synproxy_info
),
457 .checkentry
= synproxy_tg6_check
,
458 .destroy
= synproxy_tg6_destroy
,
462 static struct nf_hook_ops ipv6_synproxy_ops
[] __read_mostly
= {
464 .hook
= ipv6_synproxy_hook
,
466 .hooknum
= NF_INET_LOCAL_IN
,
467 .priority
= NF_IP_PRI_CONNTRACK_CONFIRM
- 1,
470 .hook
= ipv6_synproxy_hook
,
472 .hooknum
= NF_INET_POST_ROUTING
,
473 .priority
= NF_IP_PRI_CONNTRACK_CONFIRM
- 1,
477 static int __init
synproxy_tg6_init(void)
481 err
= nf_register_hooks(ipv6_synproxy_ops
,
482 ARRAY_SIZE(ipv6_synproxy_ops
));
486 err
= xt_register_target(&synproxy_tg6_reg
);
493 nf_unregister_hooks(ipv6_synproxy_ops
, ARRAY_SIZE(ipv6_synproxy_ops
));
498 static void __exit
synproxy_tg6_exit(void)
500 xt_unregister_target(&synproxy_tg6_reg
);
501 nf_unregister_hooks(ipv6_synproxy_ops
, ARRAY_SIZE(ipv6_synproxy_ops
));
504 module_init(synproxy_tg6_init
);
505 module_exit(synproxy_tg6_exit
);
507 MODULE_LICENSE("GPL");
508 MODULE_AUTHOR("Patrick McHardy <kaber@trash.net>");