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