nf_conntrack_seqadj.c (6310B)
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);