GNU Linux-libre 4.19.245-gnu1
[releases.git] / net / netfilter / nf_synproxy_core.c
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 #include <linux/proc_fs.h>
15
16 #include <linux/netfilter_ipv4/ip_tables.h>
17 #include <linux/netfilter/x_tables.h>
18 #include <linux/netfilter/xt_tcpudp.h>
19 #include <linux/netfilter/xt_SYNPROXY.h>
20
21 #include <net/netfilter/nf_conntrack.h>
22 #include <net/netfilter/nf_conntrack_extend.h>
23 #include <net/netfilter/nf_conntrack_seqadj.h>
24 #include <net/netfilter/nf_conntrack_synproxy.h>
25 #include <net/netfilter/nf_conntrack_zones.h>
26
27 unsigned int synproxy_net_id;
28 EXPORT_SYMBOL_GPL(synproxy_net_id);
29
30 bool
31 synproxy_parse_options(const struct sk_buff *skb, unsigned int doff,
32                        const struct tcphdr *th, struct synproxy_options *opts)
33 {
34         int length = (th->doff * 4) - sizeof(*th);
35         u8 buf[40], *ptr;
36
37         if (unlikely(length < 0))
38                 return false;
39
40         ptr = skb_header_pointer(skb, doff + sizeof(*th), length, buf);
41         if (ptr == NULL)
42                 return false;
43
44         opts->options = 0;
45         while (length > 0) {
46                 int opcode = *ptr++;
47                 int opsize;
48
49                 switch (opcode) {
50                 case TCPOPT_EOL:
51                         return true;
52                 case TCPOPT_NOP:
53                         length--;
54                         continue;
55                 default:
56                         if (length < 2)
57                                 return true;
58                         opsize = *ptr++;
59                         if (opsize < 2)
60                                 return true;
61                         if (opsize > length)
62                                 return true;
63
64                         switch (opcode) {
65                         case TCPOPT_MSS:
66                                 if (opsize == TCPOLEN_MSS) {
67                                         opts->mss = get_unaligned_be16(ptr);
68                                         opts->options |= XT_SYNPROXY_OPT_MSS;
69                                 }
70                                 break;
71                         case TCPOPT_WINDOW:
72                                 if (opsize == TCPOLEN_WINDOW) {
73                                         opts->wscale = *ptr;
74                                         if (opts->wscale > TCP_MAX_WSCALE)
75                                                 opts->wscale = TCP_MAX_WSCALE;
76                                         opts->options |= XT_SYNPROXY_OPT_WSCALE;
77                                 }
78                                 break;
79                         case TCPOPT_TIMESTAMP:
80                                 if (opsize == TCPOLEN_TIMESTAMP) {
81                                         opts->tsval = get_unaligned_be32(ptr);
82                                         opts->tsecr = get_unaligned_be32(ptr + 4);
83                                         opts->options |= XT_SYNPROXY_OPT_TIMESTAMP;
84                                 }
85                                 break;
86                         case TCPOPT_SACK_PERM:
87                                 if (opsize == TCPOLEN_SACK_PERM)
88                                         opts->options |= XT_SYNPROXY_OPT_SACK_PERM;
89                                 break;
90                         }
91
92                         ptr += opsize - 2;
93                         length -= opsize;
94                 }
95         }
96         return true;
97 }
98 EXPORT_SYMBOL_GPL(synproxy_parse_options);
99
100 unsigned int synproxy_options_size(const struct synproxy_options *opts)
101 {
102         unsigned int size = 0;
103
104         if (opts->options & XT_SYNPROXY_OPT_MSS)
105                 size += TCPOLEN_MSS_ALIGNED;
106         if (opts->options & XT_SYNPROXY_OPT_TIMESTAMP)
107                 size += TCPOLEN_TSTAMP_ALIGNED;
108         else if (opts->options & XT_SYNPROXY_OPT_SACK_PERM)
109                 size += TCPOLEN_SACKPERM_ALIGNED;
110         if (opts->options & XT_SYNPROXY_OPT_WSCALE)
111                 size += TCPOLEN_WSCALE_ALIGNED;
112
113         return size;
114 }
115 EXPORT_SYMBOL_GPL(synproxy_options_size);
116
117 void
118 synproxy_build_options(struct tcphdr *th, const struct synproxy_options *opts)
119 {
120         __be32 *ptr = (__be32 *)(th + 1);
121         u8 options = opts->options;
122
123         if (options & XT_SYNPROXY_OPT_MSS)
124                 *ptr++ = htonl((TCPOPT_MSS << 24) |
125                                (TCPOLEN_MSS << 16) |
126                                opts->mss);
127
128         if (options & XT_SYNPROXY_OPT_TIMESTAMP) {
129                 if (options & XT_SYNPROXY_OPT_SACK_PERM)
130                         *ptr++ = htonl((TCPOPT_SACK_PERM << 24) |
131                                        (TCPOLEN_SACK_PERM << 16) |
132                                        (TCPOPT_TIMESTAMP << 8) |
133                                        TCPOLEN_TIMESTAMP);
134                 else
135                         *ptr++ = htonl((TCPOPT_NOP << 24) |
136                                        (TCPOPT_NOP << 16) |
137                                        (TCPOPT_TIMESTAMP << 8) |
138                                        TCPOLEN_TIMESTAMP);
139
140                 *ptr++ = htonl(opts->tsval);
141                 *ptr++ = htonl(opts->tsecr);
142         } else if (options & XT_SYNPROXY_OPT_SACK_PERM)
143                 *ptr++ = htonl((TCPOPT_NOP << 24) |
144                                (TCPOPT_NOP << 16) |
145                                (TCPOPT_SACK_PERM << 8) |
146                                TCPOLEN_SACK_PERM);
147
148         if (options & XT_SYNPROXY_OPT_WSCALE)
149                 *ptr++ = htonl((TCPOPT_NOP << 24) |
150                                (TCPOPT_WINDOW << 16) |
151                                (TCPOLEN_WINDOW << 8) |
152                                opts->wscale);
153 }
154 EXPORT_SYMBOL_GPL(synproxy_build_options);
155
156 void synproxy_init_timestamp_cookie(const struct xt_synproxy_info *info,
157                                     struct synproxy_options *opts)
158 {
159         opts->tsecr = opts->tsval;
160         opts->tsval = tcp_time_stamp_raw() & ~0x3f;
161
162         if (opts->options & XT_SYNPROXY_OPT_WSCALE) {
163                 opts->tsval |= opts->wscale;
164                 opts->wscale = info->wscale;
165         } else
166                 opts->tsval |= 0xf;
167
168         if (opts->options & XT_SYNPROXY_OPT_SACK_PERM)
169                 opts->tsval |= 1 << 4;
170
171         if (opts->options & XT_SYNPROXY_OPT_ECN)
172                 opts->tsval |= 1 << 5;
173 }
174 EXPORT_SYMBOL_GPL(synproxy_init_timestamp_cookie);
175
176 void synproxy_check_timestamp_cookie(struct synproxy_options *opts)
177 {
178         opts->wscale = opts->tsecr & 0xf;
179         if (opts->wscale != 0xf)
180                 opts->options |= XT_SYNPROXY_OPT_WSCALE;
181
182         opts->options |= opts->tsecr & (1 << 4) ? XT_SYNPROXY_OPT_SACK_PERM : 0;
183
184         opts->options |= opts->tsecr & (1 << 5) ? XT_SYNPROXY_OPT_ECN : 0;
185 }
186 EXPORT_SYMBOL_GPL(synproxy_check_timestamp_cookie);
187
188 unsigned int synproxy_tstamp_adjust(struct sk_buff *skb,
189                                     unsigned int protoff,
190                                     struct tcphdr *th,
191                                     struct nf_conn *ct,
192                                     enum ip_conntrack_info ctinfo,
193                                     const struct nf_conn_synproxy *synproxy)
194 {
195         unsigned int optoff, optend;
196         __be32 *ptr, old;
197
198         if (synproxy->tsoff == 0)
199                 return 1;
200
201         optoff = protoff + sizeof(struct tcphdr);
202         optend = protoff + th->doff * 4;
203
204         if (!skb_make_writable(skb, optend))
205                 return 0;
206
207         while (optoff < optend) {
208                 unsigned char *op = skb->data + optoff;
209
210                 switch (op[0]) {
211                 case TCPOPT_EOL:
212                         return 1;
213                 case TCPOPT_NOP:
214                         optoff++;
215                         continue;
216                 default:
217                         if (optoff + 1 == optend ||
218                             optoff + op[1] > optend ||
219                             op[1] < 2)
220                                 return 0;
221                         if (op[0] == TCPOPT_TIMESTAMP &&
222                             op[1] == TCPOLEN_TIMESTAMP) {
223                                 if (CTINFO2DIR(ctinfo) == IP_CT_DIR_REPLY) {
224                                         ptr = (__be32 *)&op[2];
225                                         old = *ptr;
226                                         *ptr = htonl(ntohl(*ptr) -
227                                                      synproxy->tsoff);
228                                 } else {
229                                         ptr = (__be32 *)&op[6];
230                                         old = *ptr;
231                                         *ptr = htonl(ntohl(*ptr) +
232                                                      synproxy->tsoff);
233                                 }
234                                 inet_proto_csum_replace4(&th->check, skb,
235                                                          old, *ptr, false);
236                                 return 1;
237                         }
238                         optoff += op[1];
239                 }
240         }
241         return 1;
242 }
243 EXPORT_SYMBOL_GPL(synproxy_tstamp_adjust);
244
245 static struct nf_ct_ext_type nf_ct_synproxy_extend __read_mostly = {
246         .len            = sizeof(struct nf_conn_synproxy),
247         .align          = __alignof__(struct nf_conn_synproxy),
248         .id             = NF_CT_EXT_SYNPROXY,
249 };
250
251 #ifdef CONFIG_PROC_FS
252 static void *synproxy_cpu_seq_start(struct seq_file *seq, loff_t *pos)
253 {
254         struct synproxy_net *snet = synproxy_pernet(seq_file_net(seq));
255         int cpu;
256
257         if (*pos == 0)
258                 return SEQ_START_TOKEN;
259
260         for (cpu = *pos - 1; cpu < nr_cpu_ids; cpu++) {
261                 if (!cpu_possible(cpu))
262                         continue;
263                 *pos = cpu + 1;
264                 return per_cpu_ptr(snet->stats, cpu);
265         }
266
267         return NULL;
268 }
269
270 static void *synproxy_cpu_seq_next(struct seq_file *seq, void *v, loff_t *pos)
271 {
272         struct synproxy_net *snet = synproxy_pernet(seq_file_net(seq));
273         int cpu;
274
275         for (cpu = *pos; cpu < nr_cpu_ids; cpu++) {
276                 if (!cpu_possible(cpu))
277                         continue;
278                 *pos = cpu + 1;
279                 return per_cpu_ptr(snet->stats, cpu);
280         }
281         (*pos)++;
282         return NULL;
283 }
284
285 static void synproxy_cpu_seq_stop(struct seq_file *seq, void *v)
286 {
287         return;
288 }
289
290 static int synproxy_cpu_seq_show(struct seq_file *seq, void *v)
291 {
292         struct synproxy_stats *stats = v;
293
294         if (v == SEQ_START_TOKEN) {
295                 seq_puts(seq, "entries\t\tsyn_received\t"
296                               "cookie_invalid\tcookie_valid\t"
297                               "cookie_retrans\tconn_reopened\n");
298                 return 0;
299         }
300
301         seq_printf(seq, "%08x\t%08x\t%08x\t%08x\t%08x\t%08x\n", 0,
302                    stats->syn_received,
303                    stats->cookie_invalid,
304                    stats->cookie_valid,
305                    stats->cookie_retrans,
306                    stats->conn_reopened);
307
308         return 0;
309 }
310
311 static const struct seq_operations synproxy_cpu_seq_ops = {
312         .start          = synproxy_cpu_seq_start,
313         .next           = synproxy_cpu_seq_next,
314         .stop           = synproxy_cpu_seq_stop,
315         .show           = synproxy_cpu_seq_show,
316 };
317
318 static int __net_init synproxy_proc_init(struct net *net)
319 {
320         if (!proc_create_net("synproxy", 0444, net->proc_net_stat,
321                         &synproxy_cpu_seq_ops, sizeof(struct seq_net_private)))
322                 return -ENOMEM;
323         return 0;
324 }
325
326 static void __net_exit synproxy_proc_exit(struct net *net)
327 {
328         remove_proc_entry("synproxy", net->proc_net_stat);
329 }
330 #else
331 static int __net_init synproxy_proc_init(struct net *net)
332 {
333         return 0;
334 }
335
336 static void __net_exit synproxy_proc_exit(struct net *net)
337 {
338         return;
339 }
340 #endif /* CONFIG_PROC_FS */
341
342 static int __net_init synproxy_net_init(struct net *net)
343 {
344         struct synproxy_net *snet = synproxy_pernet(net);
345         struct nf_conn *ct;
346         int err = -ENOMEM;
347
348         ct = nf_ct_tmpl_alloc(net, &nf_ct_zone_dflt, GFP_KERNEL);
349         if (!ct)
350                 goto err1;
351
352         if (!nfct_seqadj_ext_add(ct))
353                 goto err2;
354         if (!nfct_synproxy_ext_add(ct))
355                 goto err2;
356
357         __set_bit(IPS_CONFIRMED_BIT, &ct->status);
358         nf_conntrack_get(&ct->ct_general);
359         snet->tmpl = ct;
360
361         snet->stats = alloc_percpu(struct synproxy_stats);
362         if (snet->stats == NULL)
363                 goto err2;
364
365         err = synproxy_proc_init(net);
366         if (err < 0)
367                 goto err3;
368
369         return 0;
370
371 err3:
372         free_percpu(snet->stats);
373 err2:
374         nf_ct_tmpl_free(ct);
375 err1:
376         return err;
377 }
378
379 static void __net_exit synproxy_net_exit(struct net *net)
380 {
381         struct synproxy_net *snet = synproxy_pernet(net);
382
383         nf_ct_put(snet->tmpl);
384         synproxy_proc_exit(net);
385         free_percpu(snet->stats);
386 }
387
388 static struct pernet_operations synproxy_net_ops = {
389         .init           = synproxy_net_init,
390         .exit           = synproxy_net_exit,
391         .id             = &synproxy_net_id,
392         .size           = sizeof(struct synproxy_net),
393 };
394
395 static int __init synproxy_core_init(void)
396 {
397         int err;
398
399         err = nf_ct_extend_register(&nf_ct_synproxy_extend);
400         if (err < 0)
401                 goto err1;
402
403         err = register_pernet_subsys(&synproxy_net_ops);
404         if (err < 0)
405                 goto err2;
406
407         return 0;
408
409 err2:
410         nf_ct_extend_unregister(&nf_ct_synproxy_extend);
411 err1:
412         return err;
413 }
414
415 static void __exit synproxy_core_exit(void)
416 {
417         unregister_pernet_subsys(&synproxy_net_ops);
418         nf_ct_extend_unregister(&nf_ct_synproxy_extend);
419 }
420
421 module_init(synproxy_core_init);
422 module_exit(synproxy_core_exit);
423
424 MODULE_LICENSE("GPL");
425 MODULE_AUTHOR("Patrick McHardy <kaber@trash.net>");