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