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