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