linux/net/netfilter/nf_conntrack_seqadj.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2#include <linux/types.h>
   3#include <linux/netfilter.h>
   4#include <net/tcp.h>
   5
   6#include <net/netfilter/nf_conntrack.h>
   7#include <net/netfilter/nf_conntrack_extend.h>
   8#include <net/netfilter/nf_conntrack_seqadj.h>
   9
  10int nf_ct_seqadj_init(struct nf_conn *ct, enum ip_conntrack_info ctinfo,
  11                      s32 off)
  12{
  13        enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
  14        struct nf_conn_seqadj *seqadj;
  15        struct nf_ct_seqadj *this_way;
  16
  17        if (off == 0)
  18                return 0;
  19
  20        set_bit(IPS_SEQ_ADJUST_BIT, &ct->status);
  21
  22        seqadj = nfct_seqadj(ct);
  23        this_way = &seqadj->seq[dir];
  24        this_way->offset_before  = off;
  25        this_way->offset_after   = off;
  26        return 0;
  27}
  28EXPORT_SYMBOL_GPL(nf_ct_seqadj_init);
  29
  30int nf_ct_seqadj_set(struct nf_conn *ct, enum ip_conntrack_info ctinfo,
  31                     __be32 seq, s32 off)
  32{
  33        struct nf_conn_seqadj *seqadj = nfct_seqadj(ct);
  34        enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
  35        struct nf_ct_seqadj *this_way;
  36
  37        if (off == 0)
  38                return 0;
  39
  40        if (unlikely(!seqadj)) {
  41                WARN_ONCE(1, "Missing nfct_seqadj_ext_add() setup call\n");
  42                return 0;
  43        }
  44
  45        set_bit(IPS_SEQ_ADJUST_BIT, &ct->status);
  46
  47        spin_lock_bh(&ct->lock);
  48        this_way = &seqadj->seq[dir];
  49        if (this_way->offset_before == this_way->offset_after ||
  50            before(this_way->correction_pos, ntohl(seq))) {
  51                this_way->correction_pos = ntohl(seq);
  52                this_way->offset_before  = this_way->offset_after;
  53                this_way->offset_after  += off;
  54        }
  55        spin_unlock_bh(&ct->lock);
  56        return 0;
  57}
  58EXPORT_SYMBOL_GPL(nf_ct_seqadj_set);
  59
  60void nf_ct_tcp_seqadj_set(struct sk_buff *skb,
  61                          struct nf_conn *ct, enum ip_conntrack_info ctinfo,
  62                          s32 off)
  63{
  64        const struct tcphdr *th;
  65
  66        if (nf_ct_protonum(ct) != IPPROTO_TCP)
  67                return;
  68
  69        th = (struct tcphdr *)(skb_network_header(skb) + ip_hdrlen(skb));
  70        nf_ct_seqadj_set(ct, ctinfo, th->seq, off);
  71}
  72EXPORT_SYMBOL_GPL(nf_ct_tcp_seqadj_set);
  73
  74/* Adjust one found SACK option including checksum correction */
  75static void nf_ct_sack_block_adjust(struct sk_buff *skb,
  76                                    struct tcphdr *tcph,
  77                                    unsigned int sackoff,
  78                                    unsigned int sackend,
  79                                    struct nf_ct_seqadj *seq)
  80{
  81        while (sackoff < sackend) {
  82                struct tcp_sack_block_wire *sack;
  83                __be32 new_start_seq, new_end_seq;
  84
  85                sack = (void *)skb->data + sackoff;
  86                if (after(ntohl(sack->start_seq) - seq->offset_before,
  87                          seq->correction_pos))
  88                        new_start_seq = htonl(ntohl(sack->start_seq) -
  89                                        seq->offset_after);
  90                else
  91                        new_start_seq = htonl(ntohl(sack->start_seq) -
  92                                        seq->offset_before);
  93
  94                if (after(ntohl(sack->end_seq) - seq->offset_before,
  95                          seq->correction_pos))
  96                        new_end_seq = htonl(ntohl(sack->end_seq) -
  97                                      seq->offset_after);
  98                else
  99                        new_end_seq = htonl(ntohl(sack->end_seq) -
 100                                      seq->offset_before);
 101
 102                pr_debug("sack_adjust: start_seq: %u->%u, end_seq: %u->%u\n",
 103                         ntohl(sack->start_seq), ntohl(new_start_seq),
 104                         ntohl(sack->end_seq), ntohl(new_end_seq));
 105
 106                inet_proto_csum_replace4(&tcph->check, skb,
 107                                         sack->start_seq, new_start_seq, false);
 108                inet_proto_csum_replace4(&tcph->check, skb,
 109                                         sack->end_seq, new_end_seq, false);
 110                sack->start_seq = new_start_seq;
 111                sack->end_seq = new_end_seq;
 112                sackoff += sizeof(*sack);
 113        }
 114}
 115
 116/* TCP SACK sequence number adjustment */
 117static unsigned int nf_ct_sack_adjust(struct sk_buff *skb,
 118                                      unsigned int protoff,
 119                                      struct nf_conn *ct,
 120                                      enum ip_conntrack_info ctinfo)
 121{
 122        struct tcphdr *tcph = (void *)skb->data + protoff;
 123        struct nf_conn_seqadj *seqadj = nfct_seqadj(ct);
 124        unsigned int dir, optoff, optend;
 125
 126        optoff = protoff + sizeof(struct tcphdr);
 127        optend = protoff + tcph->doff * 4;
 128
 129        if (skb_ensure_writable(skb, optend))
 130                return 0;
 131
 132        tcph = (void *)skb->data + protoff;
 133        dir = CTINFO2DIR(ctinfo);
 134
 135        while (optoff < optend) {
 136                /* Usually: option, length. */
 137                unsigned char *op = skb->data + optoff;
 138
 139                switch (op[0]) {
 140                case TCPOPT_EOL:
 141                        return 1;
 142                case TCPOPT_NOP:
 143                        optoff++;
 144                        continue;
 145                default:
 146                        /* no partial options */
 147                        if (optoff + 1 == optend ||
 148                            optoff + op[1] > optend ||
 149                            op[1] < 2)
 150                                return 0;
 151                        if (op[0] == TCPOPT_SACK &&
 152                            op[1] >= 2+TCPOLEN_SACK_PERBLOCK &&
 153                            ((op[1] - 2) % TCPOLEN_SACK_PERBLOCK) == 0)
 154                                nf_ct_sack_block_adjust(skb, tcph, optoff + 2,
 155                                                        optoff+op[1],
 156                                                        &seqadj->seq[!dir]);
 157                        optoff += op[1];
 158                }
 159        }
 160        return 1;
 161}
 162
 163/* TCP sequence number adjustment.  Returns 1 on success, 0 on failure */
 164int nf_ct_seq_adjust(struct sk_buff *skb,
 165                     struct nf_conn *ct, enum ip_conntrack_info ctinfo,
 166                     unsigned int protoff)
 167{
 168        enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
 169        struct tcphdr *tcph;
 170        __be32 newseq, newack;
 171        s32 seqoff, ackoff;
 172        struct nf_conn_seqadj *seqadj = nfct_seqadj(ct);
 173        struct nf_ct_seqadj *this_way, *other_way;
 174        int res = 1;
 175
 176        this_way  = &seqadj->seq[dir];
 177        other_way = &seqadj->seq[!dir];
 178
 179        if (skb_ensure_writable(skb, protoff + sizeof(*tcph)))
 180                return 0;
 181
 182        tcph = (void *)skb->data + protoff;
 183        spin_lock_bh(&ct->lock);
 184        if (after(ntohl(tcph->seq), this_way->correction_pos))
 185                seqoff = this_way->offset_after;
 186        else
 187                seqoff = this_way->offset_before;
 188
 189        newseq = htonl(ntohl(tcph->seq) + seqoff);
 190        inet_proto_csum_replace4(&tcph->check, skb, tcph->seq, newseq, false);
 191        pr_debug("Adjusting sequence number from %u->%u\n",
 192                 ntohl(tcph->seq), ntohl(newseq));
 193        tcph->seq = newseq;
 194
 195        if (!tcph->ack)
 196                goto out;
 197
 198        if (after(ntohl(tcph->ack_seq) - other_way->offset_before,
 199                  other_way->correction_pos))
 200                ackoff = other_way->offset_after;
 201        else
 202                ackoff = other_way->offset_before;
 203
 204        newack = htonl(ntohl(tcph->ack_seq) - ackoff);
 205        inet_proto_csum_replace4(&tcph->check, skb, tcph->ack_seq, newack,
 206                                 false);
 207        pr_debug("Adjusting ack number from %u->%u, ack from %u->%u\n",
 208                 ntohl(tcph->seq), ntohl(newseq), ntohl(tcph->ack_seq),
 209                 ntohl(newack));
 210        tcph->ack_seq = newack;
 211
 212        res = nf_ct_sack_adjust(skb, protoff, ct, ctinfo);
 213out:
 214        spin_unlock_bh(&ct->lock);
 215
 216        return res;
 217}
 218EXPORT_SYMBOL_GPL(nf_ct_seq_adjust);
 219
 220s32 nf_ct_seq_offset(const struct nf_conn *ct,
 221                     enum ip_conntrack_dir dir,
 222                     u32 seq)
 223{
 224        struct nf_conn_seqadj *seqadj = nfct_seqadj(ct);
 225        struct nf_ct_seqadj *this_way;
 226
 227        if (!seqadj)
 228                return 0;
 229
 230        this_way = &seqadj->seq[dir];
 231        return after(seq, this_way->correction_pos) ?
 232                 this_way->offset_after : this_way->offset_before;
 233}
 234EXPORT_SYMBOL_GPL(nf_ct_seq_offset);
 235
 236static const struct nf_ct_ext_type nf_ct_seqadj_extend = {
 237        .len    = sizeof(struct nf_conn_seqadj),
 238        .align  = __alignof__(struct nf_conn_seqadj),
 239        .id     = NF_CT_EXT_SEQADJ,
 240};
 241
 242int nf_conntrack_seqadj_init(void)
 243{
 244        return nf_ct_extend_register(&nf_ct_seqadj_extend);
 245}
 246
 247void nf_conntrack_seqadj_fini(void)
 248{
 249        nf_ct_extend_unregister(&nf_ct_seqadj_extend);
 250}
 251