| 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 |  | 
|---|
| 10 | int 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(nr: IPS_SEQ_ADJUST_BIT, addr: &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 | } | 
|---|
| 28 | EXPORT_SYMBOL_GPL(nf_ct_seqadj_init); | 
|---|
| 29 |  | 
|---|
| 30 | int 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(nr: IPS_SEQ_ADJUST_BIT, addr: &ct->status); | 
|---|
| 46 |  | 
|---|
| 47 | spin_lock_bh(lock: &ct->lock); | 
|---|
| 48 | this_way = &seqadj->seq[dir]; | 
|---|
| 49 | if (this_way->offset_before == this_way->offset_after || | 
|---|
| 50 | before(seq1: 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(lock: &ct->lock); | 
|---|
| 56 | return 0; | 
|---|
| 57 | } | 
|---|
| 58 | EXPORT_SYMBOL_GPL(nf_ct_seqadj_set); | 
|---|
| 59 |  | 
|---|
| 60 | void 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 | } | 
|---|
| 72 | EXPORT_SYMBOL_GPL(nf_ct_tcp_seqadj_set); | 
|---|
| 73 |  | 
|---|
| 74 | /* Adjust one found SACK option including checksum correction */ | 
|---|
| 75 | static 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(sum: &tcph->check, skb, | 
|---|
| 107 | from: sack->start_seq, to: new_start_seq, pseudohdr: false); | 
|---|
| 108 | inet_proto_csum_replace4(sum: &tcph->check, skb, | 
|---|
| 109 | from: sack->end_seq, to: new_end_seq, pseudohdr: 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 */ | 
|---|
| 117 | static 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, write_len: 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, sackoff: optoff + 2, | 
|---|
| 155 | sackend: optoff+op[1], | 
|---|
| 156 | seq: &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 */ | 
|---|
| 164 | int 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, write_len: protoff + sizeof(*tcph))) | 
|---|
| 180 | return 0; | 
|---|
| 181 |  | 
|---|
| 182 | tcph = (void *)skb->data + protoff; | 
|---|
| 183 | spin_lock_bh(lock: &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(sum: &tcph->check, skb, from: tcph->seq, to: newseq, pseudohdr: 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(sum: &tcph->check, skb, from: tcph->ack_seq, to: newack, | 
|---|
| 206 | pseudohdr: 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); | 
|---|
| 213 | out: | 
|---|
| 214 | spin_unlock_bh(lock: &ct->lock); | 
|---|
| 215 |  | 
|---|
| 216 | return res; | 
|---|
| 217 | } | 
|---|
| 218 | EXPORT_SYMBOL_GPL(nf_ct_seq_adjust); | 
|---|
| 219 |  | 
|---|
| 220 | s32 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 | } | 
|---|
| 234 | EXPORT_SYMBOL_GPL(nf_ct_seq_offset); | 
|---|
| 235 |  | 
|---|