]>
Commit | Line | Data |
---|---|---|
48b1de4c PM |
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 | ||
20 | static struct iphdr * | |
29421198 LZ |
21 | synproxy_build_ip(struct net *net, struct sk_buff *skb, __be32 saddr, |
22 | __be32 daddr) | |
48b1de4c PM |
23 | { |
24 | struct iphdr *iph; | |
25 | ||
26 | skb_reset_network_header(skb); | |
27 | iph = (struct iphdr *)skb_put(skb, sizeof(*iph)); | |
28 | iph->version = 4; | |
29 | iph->ihl = sizeof(*iph) / 4; | |
30 | iph->tos = 0; | |
31 | iph->id = 0; | |
32 | iph->frag_off = htons(IP_DF); | |
fa50d974 | 33 | iph->ttl = net->ipv4.sysctl_ip_default_ttl; |
48b1de4c PM |
34 | iph->protocol = IPPROTO_TCP; |
35 | iph->check = 0; | |
36 | iph->saddr = saddr; | |
37 | iph->daddr = daddr; | |
38 | ||
39 | return iph; | |
40 | } | |
41 | ||
42 | static void | |
29421198 | 43 | synproxy_send_tcp(struct net *net, |
6a1d689d | 44 | const struct sk_buff *skb, struct sk_buff *nskb, |
48b1de4c PM |
45 | struct nf_conntrack *nfct, enum ip_conntrack_info ctinfo, |
46 | struct iphdr *niph, struct tcphdr *nth, | |
47 | unsigned int tcp_hdr_size) | |
48 | { | |
49 | nth->check = ~tcp_v4_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 | skb_dst_set_noref(nskb, skb_dst(skb)); | |
55 | nskb->protocol = htons(ETH_P_IP); | |
e45f5066 | 56 | if (ip_route_me_harder(net, nskb, RTN_UNSPEC)) |
48b1de4c PM |
57 | goto free_nskb; |
58 | ||
59 | if (nfct) { | |
60 | nskb->nfct = nfct; | |
61 | nskb->nfctinfo = ctinfo; | |
62 | nf_conntrack_get(nfct); | |
63 | } | |
64 | ||
33224b16 | 65 | ip_local_out(net, nskb->sk, nskb); |
48b1de4c PM |
66 | return; |
67 | ||
68 | free_nskb: | |
69 | kfree_skb(nskb); | |
70 | } | |
71 | ||
72 | static void | |
29421198 | 73 | synproxy_send_client_synack(struct net *net, |
6a1d689d | 74 | const struct sk_buff *skb, const struct tcphdr *th, |
48b1de4c PM |
75 | const struct synproxy_options *opts) |
76 | { | |
77 | struct sk_buff *nskb; | |
78 | struct iphdr *iph, *niph; | |
79 | struct tcphdr *nth; | |
80 | unsigned int tcp_hdr_size; | |
81 | u16 mss = opts->mss; | |
82 | ||
83 | iph = ip_hdr(skb); | |
84 | ||
85 | tcp_hdr_size = sizeof(*nth) + synproxy_options_size(opts); | |
86 | nskb = alloc_skb(sizeof(*niph) + tcp_hdr_size + MAX_TCP_HEADER, | |
87 | GFP_ATOMIC); | |
88 | if (nskb == NULL) | |
89 | return; | |
90 | skb_reserve(nskb, MAX_TCP_HEADER); | |
91 | ||
29421198 | 92 | niph = synproxy_build_ip(net, nskb, iph->daddr, iph->saddr); |
48b1de4c PM |
93 | |
94 | skb_reset_transport_header(nskb); | |
95 | nth = (struct tcphdr *)skb_put(nskb, tcp_hdr_size); | |
96 | nth->source = th->dest; | |
97 | nth->dest = th->source; | |
98 | nth->seq = htonl(__cookie_v4_init_sequence(iph, th, &mss)); | |
99 | nth->ack_seq = htonl(ntohl(th->seq) + 1); | |
100 | tcp_flag_word(nth) = TCP_FLAG_SYN | TCP_FLAG_ACK; | |
101 | if (opts->options & XT_SYNPROXY_OPT_ECN) | |
102 | tcp_flag_word(nth) |= TCP_FLAG_ECE; | |
103 | nth->doff = tcp_hdr_size / 4; | |
104 | nth->window = 0; | |
105 | nth->check = 0; | |
106 | nth->urg_ptr = 0; | |
107 | ||
108 | synproxy_build_options(nth, opts); | |
109 | ||
cb9c6836 FW |
110 | synproxy_send_tcp(net, skb, nskb, skb_nfct(skb), |
111 | IP_CT_ESTABLISHED_REPLY, niph, nth, tcp_hdr_size); | |
48b1de4c PM |
112 | } |
113 | ||
114 | static void | |
29421198 | 115 | synproxy_send_server_syn(struct net *net, |
48b1de4c PM |
116 | const struct sk_buff *skb, const struct tcphdr *th, |
117 | const struct synproxy_options *opts, u32 recv_seq) | |
118 | { | |
29421198 | 119 | struct synproxy_net *snet = synproxy_pernet(net); |
48b1de4c PM |
120 | struct sk_buff *nskb; |
121 | struct iphdr *iph, *niph; | |
122 | struct tcphdr *nth; | |
123 | unsigned int tcp_hdr_size; | |
124 | ||
125 | iph = ip_hdr(skb); | |
126 | ||
127 | tcp_hdr_size = sizeof(*nth) + synproxy_options_size(opts); | |
128 | nskb = alloc_skb(sizeof(*niph) + tcp_hdr_size + MAX_TCP_HEADER, | |
129 | GFP_ATOMIC); | |
130 | if (nskb == NULL) | |
131 | return; | |
132 | skb_reserve(nskb, MAX_TCP_HEADER); | |
133 | ||
29421198 | 134 | niph = synproxy_build_ip(net, nskb, iph->saddr, iph->daddr); |
48b1de4c PM |
135 | |
136 | skb_reset_transport_header(nskb); | |
137 | nth = (struct tcphdr *)skb_put(nskb, tcp_hdr_size); | |
138 | nth->source = th->source; | |
139 | nth->dest = th->dest; | |
140 | nth->seq = htonl(recv_seq - 1); | |
141 | /* ack_seq is used to relay our ISN to the synproxy hook to initialize | |
142 | * sequence number translation once a connection tracking entry exists. | |
143 | */ | |
144 | nth->ack_seq = htonl(ntohl(th->ack_seq) - 1); | |
145 | tcp_flag_word(nth) = TCP_FLAG_SYN; | |
146 | if (opts->options & XT_SYNPROXY_OPT_ECN) | |
147 | tcp_flag_word(nth) |= TCP_FLAG_ECE | TCP_FLAG_CWR; | |
148 | nth->doff = tcp_hdr_size / 4; | |
149 | nth->window = th->window; | |
150 | nth->check = 0; | |
151 | nth->urg_ptr = 0; | |
152 | ||
153 | synproxy_build_options(nth, opts); | |
154 | ||
29421198 | 155 | synproxy_send_tcp(net, skb, nskb, &snet->tmpl->ct_general, IP_CT_NEW, |
48b1de4c PM |
156 | niph, nth, tcp_hdr_size); |
157 | } | |
158 | ||
159 | static void | |
29421198 | 160 | synproxy_send_server_ack(struct net *net, |
48b1de4c PM |
161 | const struct ip_ct_tcp *state, |
162 | const struct sk_buff *skb, const struct tcphdr *th, | |
163 | const struct synproxy_options *opts) | |
164 | { | |
165 | struct sk_buff *nskb; | |
166 | struct iphdr *iph, *niph; | |
167 | struct tcphdr *nth; | |
168 | unsigned int tcp_hdr_size; | |
169 | ||
170 | iph = ip_hdr(skb); | |
171 | ||
172 | tcp_hdr_size = sizeof(*nth) + synproxy_options_size(opts); | |
173 | nskb = alloc_skb(sizeof(*niph) + tcp_hdr_size + MAX_TCP_HEADER, | |
174 | GFP_ATOMIC); | |
175 | if (nskb == NULL) | |
176 | return; | |
177 | skb_reserve(nskb, MAX_TCP_HEADER); | |
178 | ||
29421198 | 179 | niph = synproxy_build_ip(net, nskb, iph->daddr, iph->saddr); |
48b1de4c PM |
180 | |
181 | skb_reset_transport_header(nskb); | |
182 | nth = (struct tcphdr *)skb_put(nskb, tcp_hdr_size); | |
183 | nth->source = th->dest; | |
184 | nth->dest = th->source; | |
185 | nth->seq = htonl(ntohl(th->ack_seq)); | |
186 | nth->ack_seq = htonl(ntohl(th->seq) + 1); | |
187 | tcp_flag_word(nth) = TCP_FLAG_ACK; | |
188 | nth->doff = tcp_hdr_size / 4; | |
189 | nth->window = htons(state->seen[IP_CT_DIR_ORIGINAL].td_maxwin); | |
190 | nth->check = 0; | |
191 | nth->urg_ptr = 0; | |
192 | ||
193 | synproxy_build_options(nth, opts); | |
194 | ||
29421198 | 195 | synproxy_send_tcp(net, skb, nskb, NULL, 0, niph, nth, tcp_hdr_size); |
48b1de4c PM |
196 | } |
197 | ||
198 | static void | |
29421198 | 199 | synproxy_send_client_ack(struct net *net, |
48b1de4c PM |
200 | const struct sk_buff *skb, const struct tcphdr *th, |
201 | const struct synproxy_options *opts) | |
202 | { | |
203 | struct sk_buff *nskb; | |
204 | struct iphdr *iph, *niph; | |
205 | struct tcphdr *nth; | |
206 | unsigned int tcp_hdr_size; | |
207 | ||
208 | iph = ip_hdr(skb); | |
209 | ||
210 | tcp_hdr_size = sizeof(*nth) + synproxy_options_size(opts); | |
211 | nskb = alloc_skb(sizeof(*niph) + tcp_hdr_size + MAX_TCP_HEADER, | |
212 | GFP_ATOMIC); | |
213 | if (nskb == NULL) | |
214 | return; | |
215 | skb_reserve(nskb, MAX_TCP_HEADER); | |
216 | ||
29421198 | 217 | niph = synproxy_build_ip(net, nskb, iph->saddr, iph->daddr); |
48b1de4c PM |
218 | |
219 | skb_reset_transport_header(nskb); | |
220 | nth = (struct tcphdr *)skb_put(nskb, tcp_hdr_size); | |
221 | nth->source = th->source; | |
222 | nth->dest = th->dest; | |
223 | nth->seq = htonl(ntohl(th->seq) + 1); | |
224 | nth->ack_seq = th->ack_seq; | |
225 | tcp_flag_word(nth) = TCP_FLAG_ACK; | |
226 | nth->doff = tcp_hdr_size / 4; | |
ba6d0564 | 227 | nth->window = htons(ntohs(th->window) >> opts->wscale); |
48b1de4c PM |
228 | nth->check = 0; |
229 | nth->urg_ptr = 0; | |
230 | ||
231 | synproxy_build_options(nth, opts); | |
232 | ||
cb9c6836 FW |
233 | synproxy_send_tcp(net, skb, nskb, skb_nfct(skb), |
234 | IP_CT_ESTABLISHED_REPLY, niph, nth, tcp_hdr_size); | |
48b1de4c PM |
235 | } |
236 | ||
237 | static bool | |
29421198 | 238 | synproxy_recv_client_ack(struct net *net, |
48b1de4c PM |
239 | const struct sk_buff *skb, const struct tcphdr *th, |
240 | struct synproxy_options *opts, u32 recv_seq) | |
241 | { | |
29421198 | 242 | struct synproxy_net *snet = synproxy_pernet(net); |
48b1de4c PM |
243 | int mss; |
244 | ||
245 | mss = __cookie_v4_check(ip_hdr(skb), th, ntohl(th->ack_seq) - 1); | |
246 | if (mss == 0) { | |
247 | this_cpu_inc(snet->stats->cookie_invalid); | |
248 | return false; | |
249 | } | |
250 | ||
251 | this_cpu_inc(snet->stats->cookie_valid); | |
252 | opts->mss = mss; | |
a6441b7a | 253 | opts->options |= XT_SYNPROXY_OPT_MSS; |
48b1de4c PM |
254 | |
255 | if (opts->options & XT_SYNPROXY_OPT_TIMESTAMP) | |
256 | synproxy_check_timestamp_cookie(opts); | |
257 | ||
29421198 | 258 | synproxy_send_server_syn(net, skb, th, opts, recv_seq); |
48b1de4c PM |
259 | return true; |
260 | } | |
261 | ||
262 | static unsigned int | |
263 | synproxy_tg4(struct sk_buff *skb, const struct xt_action_param *par) | |
264 | { | |
265 | const struct xt_synproxy_info *info = par->targinfo; | |
613dbd95 | 266 | struct net *net = xt_net(par); |
29421198 | 267 | struct synproxy_net *snet = synproxy_pernet(net); |
48b1de4c PM |
268 | struct synproxy_options opts = {}; |
269 | struct tcphdr *th, _th; | |
270 | ||
613dbd95 | 271 | if (nf_ip_checksum(skb, xt_hooknum(par), par->thoff, IPPROTO_TCP)) |
48b1de4c PM |
272 | return NF_DROP; |
273 | ||
274 | th = skb_header_pointer(skb, par->thoff, sizeof(_th), &_th); | |
275 | if (th == NULL) | |
276 | return NF_DROP; | |
277 | ||
f4a87e7b PM |
278 | if (!synproxy_parse_options(skb, par->thoff, th, &opts)) |
279 | return NF_DROP; | |
48b1de4c | 280 | |
775ada6d | 281 | if (th->syn && !(th->ack || th->fin || th->rst)) { |
48b1de4c PM |
282 | /* Initial SYN from client */ |
283 | this_cpu_inc(snet->stats->syn_received); | |
284 | ||
285 | if (th->ece && th->cwr) | |
286 | opts.options |= XT_SYNPROXY_OPT_ECN; | |
287 | ||
288 | opts.options &= info->options; | |
289 | if (opts.options & XT_SYNPROXY_OPT_TIMESTAMP) | |
290 | synproxy_init_timestamp_cookie(info, &opts); | |
291 | else | |
292 | opts.options &= ~(XT_SYNPROXY_OPT_WSCALE | | |
293 | XT_SYNPROXY_OPT_SACK_PERM | | |
294 | XT_SYNPROXY_OPT_ECN); | |
295 | ||
29421198 | 296 | synproxy_send_client_synack(net, skb, th, &opts); |
7cc9eb6e JDB |
297 | return NF_DROP; |
298 | ||
299 | } else if (th->ack && !(th->fin || th->rst || th->syn)) { | |
48b1de4c | 300 | /* ACK from client */ |
29421198 | 301 | synproxy_recv_client_ack(net, skb, th, &opts, ntohl(th->seq)); |
7cc9eb6e JDB |
302 | return NF_DROP; |
303 | } | |
48b1de4c | 304 | |
7cc9eb6e | 305 | return XT_CONTINUE; |
48b1de4c PM |
306 | } |
307 | ||
06198b34 | 308 | static unsigned int ipv4_synproxy_hook(void *priv, |
48b1de4c | 309 | struct sk_buff *skb, |
238e54c9 | 310 | const struct nf_hook_state *nhs) |
48b1de4c | 311 | { |
29421198 LZ |
312 | struct net *net = nhs->net; |
313 | struct synproxy_net *snet = synproxy_pernet(net); | |
48b1de4c PM |
314 | enum ip_conntrack_info ctinfo; |
315 | struct nf_conn *ct; | |
316 | struct nf_conn_synproxy *synproxy; | |
317 | struct synproxy_options opts = {}; | |
318 | const struct ip_ct_tcp *state; | |
319 | struct tcphdr *th, _th; | |
320 | unsigned int thoff; | |
321 | ||
322 | ct = nf_ct_get(skb, &ctinfo); | |
323 | if (ct == NULL) | |
324 | return NF_ACCEPT; | |
325 | ||
326 | synproxy = nfct_synproxy(ct); | |
327 | if (synproxy == NULL) | |
328 | return NF_ACCEPT; | |
329 | ||
330 | if (nf_is_loopback_packet(skb)) | |
331 | return NF_ACCEPT; | |
332 | ||
333 | thoff = ip_hdrlen(skb); | |
334 | th = skb_header_pointer(skb, thoff, sizeof(_th), &_th); | |
335 | if (th == NULL) | |
336 | return NF_DROP; | |
337 | ||
338 | state = &ct->proto.tcp; | |
339 | switch (state->state) { | |
340 | case TCP_CONNTRACK_CLOSE: | |
341 | if (th->rst && !test_bit(IPS_SEEN_REPLY_BIT, &ct->status)) { | |
342 | nf_ct_seqadj_init(ct, ctinfo, synproxy->isn - | |
343 | ntohl(th->seq) + 1); | |
344 | break; | |
345 | } | |
346 | ||
347 | if (!th->syn || th->ack || | |
348 | CTINFO2DIR(ctinfo) != IP_CT_DIR_ORIGINAL) | |
349 | break; | |
350 | ||
351 | /* Reopened connection - reset the sequence number and timestamp | |
352 | * adjustments, they will get initialized once the connection is | |
353 | * reestablished. | |
354 | */ | |
355 | nf_ct_seqadj_init(ct, ctinfo, 0); | |
356 | synproxy->tsoff = 0; | |
357 | this_cpu_inc(snet->stats->conn_reopened); | |
358 | ||
359 | /* fall through */ | |
360 | case TCP_CONNTRACK_SYN_SENT: | |
f4a87e7b PM |
361 | if (!synproxy_parse_options(skb, thoff, th, &opts)) |
362 | return NF_DROP; | |
48b1de4c PM |
363 | |
364 | if (!th->syn && th->ack && | |
365 | CTINFO2DIR(ctinfo) == IP_CT_DIR_ORIGINAL) { | |
366 | /* Keep-Alives are sent with SEG.SEQ = SND.NXT-1, | |
367 | * therefore we need to add 1 to make the SYN sequence | |
368 | * number match the one of first SYN. | |
369 | */ | |
29421198 | 370 | if (synproxy_recv_client_ack(net, skb, th, &opts, |
48b1de4c PM |
371 | ntohl(th->seq) + 1)) |
372 | this_cpu_inc(snet->stats->cookie_retrans); | |
373 | ||
374 | return NF_DROP; | |
375 | } | |
376 | ||
377 | synproxy->isn = ntohl(th->ack_seq); | |
378 | if (opts.options & XT_SYNPROXY_OPT_TIMESTAMP) | |
379 | synproxy->its = opts.tsecr; | |
380 | break; | |
381 | case TCP_CONNTRACK_SYN_RECV: | |
382 | if (!th->syn || !th->ack) | |
383 | break; | |
384 | ||
f4a87e7b PM |
385 | if (!synproxy_parse_options(skb, thoff, th, &opts)) |
386 | return NF_DROP; | |
387 | ||
48b1de4c PM |
388 | if (opts.options & XT_SYNPROXY_OPT_TIMESTAMP) |
389 | synproxy->tsoff = opts.tsval - synproxy->its; | |
390 | ||
391 | opts.options &= ~(XT_SYNPROXY_OPT_MSS | | |
392 | XT_SYNPROXY_OPT_WSCALE | | |
393 | XT_SYNPROXY_OPT_SACK_PERM); | |
394 | ||
395 | swap(opts.tsval, opts.tsecr); | |
29421198 | 396 | synproxy_send_server_ack(net, state, skb, th, &opts); |
48b1de4c PM |
397 | |
398 | nf_ct_seqadj_init(ct, ctinfo, synproxy->isn - ntohl(th->seq)); | |
399 | ||
400 | swap(opts.tsval, opts.tsecr); | |
29421198 | 401 | synproxy_send_client_ack(net, skb, th, &opts); |
48b1de4c PM |
402 | |
403 | consume_skb(skb); | |
404 | return NF_STOLEN; | |
405 | default: | |
406 | break; | |
407 | } | |
408 | ||
409 | synproxy_tstamp_adjust(skb, thoff, th, ct, ctinfo, synproxy); | |
410 | return NF_ACCEPT; | |
411 | } | |
412 | ||
413 | static int synproxy_tg4_check(const struct xt_tgchk_param *par) | |
414 | { | |
415 | const struct ipt_entry *e = par->entryinfo; | |
416 | ||
417 | if (e->ip.proto != IPPROTO_TCP || | |
418 | e->ip.invflags & XT_INV_PROTO) | |
419 | return -EINVAL; | |
420 | ||
ecb2421b | 421 | return nf_ct_netns_get(par->net, par->family); |
48b1de4c PM |
422 | } |
423 | ||
424 | static void synproxy_tg4_destroy(const struct xt_tgdtor_param *par) | |
425 | { | |
ecb2421b | 426 | nf_ct_netns_put(par->net, par->family); |
48b1de4c PM |
427 | } |
428 | ||
429 | static struct xt_target synproxy_tg4_reg __read_mostly = { | |
430 | .name = "SYNPROXY", | |
431 | .family = NFPROTO_IPV4, | |
f01b3926 | 432 | .hooks = (1 << NF_INET_LOCAL_IN) | (1 << NF_INET_FORWARD), |
48b1de4c PM |
433 | .target = synproxy_tg4, |
434 | .targetsize = sizeof(struct xt_synproxy_info), | |
435 | .checkentry = synproxy_tg4_check, | |
436 | .destroy = synproxy_tg4_destroy, | |
437 | .me = THIS_MODULE, | |
438 | }; | |
439 | ||
440 | static struct nf_hook_ops ipv4_synproxy_ops[] __read_mostly = { | |
441 | { | |
442 | .hook = ipv4_synproxy_hook, | |
48b1de4c PM |
443 | .pf = NFPROTO_IPV4, |
444 | .hooknum = NF_INET_LOCAL_IN, | |
445 | .priority = NF_IP_PRI_CONNTRACK_CONFIRM - 1, | |
446 | }, | |
447 | { | |
448 | .hook = ipv4_synproxy_hook, | |
48b1de4c PM |
449 | .pf = NFPROTO_IPV4, |
450 | .hooknum = NF_INET_POST_ROUTING, | |
451 | .priority = NF_IP_PRI_CONNTRACK_CONFIRM - 1, | |
452 | }, | |
453 | }; | |
454 | ||
455 | static int __init synproxy_tg4_init(void) | |
456 | { | |
457 | int err; | |
458 | ||
459 | err = nf_register_hooks(ipv4_synproxy_ops, | |
460 | ARRAY_SIZE(ipv4_synproxy_ops)); | |
461 | if (err < 0) | |
462 | goto err1; | |
463 | ||
464 | err = xt_register_target(&synproxy_tg4_reg); | |
465 | if (err < 0) | |
466 | goto err2; | |
467 | ||
468 | return 0; | |
469 | ||
470 | err2: | |
471 | nf_unregister_hooks(ipv4_synproxy_ops, ARRAY_SIZE(ipv4_synproxy_ops)); | |
472 | err1: | |
473 | return err; | |
474 | } | |
475 | ||
476 | static void __exit synproxy_tg4_exit(void) | |
477 | { | |
478 | xt_unregister_target(&synproxy_tg4_reg); | |
479 | nf_unregister_hooks(ipv4_synproxy_ops, ARRAY_SIZE(ipv4_synproxy_ops)); | |
480 | } | |
481 | ||
482 | module_init(synproxy_tg4_init); | |
483 | module_exit(synproxy_tg4_exit); | |
484 | ||
485 | MODULE_LICENSE("GPL"); | |
486 | MODULE_AUTHOR("Patrick McHardy <kaber@trash.net>"); |