141d73ec0SPatrick McHardy #include <linux/types.h> 241d73ec0SPatrick McHardy #include <linux/netfilter.h> 341d73ec0SPatrick McHardy #include <net/tcp.h> 441d73ec0SPatrick McHardy 541d73ec0SPatrick McHardy #include <net/netfilter/nf_conntrack.h> 641d73ec0SPatrick McHardy #include <net/netfilter/nf_conntrack_extend.h> 741d73ec0SPatrick McHardy #include <net/netfilter/nf_conntrack_seqadj.h> 841d73ec0SPatrick McHardy 941d73ec0SPatrick McHardy int nf_ct_seqadj_set(struct nf_conn *ct, enum ip_conntrack_info ctinfo, 1041d73ec0SPatrick McHardy __be32 seq, s32 off) 1141d73ec0SPatrick McHardy { 1241d73ec0SPatrick McHardy struct nf_conn_seqadj *seqadj = nfct_seqadj(ct); 1341d73ec0SPatrick McHardy enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo); 1441d73ec0SPatrick McHardy struct nf_ct_seqadj *this_way; 1541d73ec0SPatrick McHardy 1641d73ec0SPatrick McHardy if (off == 0) 1741d73ec0SPatrick McHardy return 0; 1841d73ec0SPatrick McHardy 1941d73ec0SPatrick McHardy set_bit(IPS_SEQ_ADJUST_BIT, &ct->status); 2041d73ec0SPatrick McHardy 2141d73ec0SPatrick McHardy spin_lock_bh(&ct->lock); 2241d73ec0SPatrick McHardy this_way = &seqadj->seq[dir]; 2341d73ec0SPatrick McHardy if (this_way->offset_before == this_way->offset_after || 2441d73ec0SPatrick McHardy before(this_way->correction_pos, seq)) { 2541d73ec0SPatrick McHardy this_way->correction_pos = seq; 2641d73ec0SPatrick McHardy this_way->offset_before = this_way->offset_after; 2741d73ec0SPatrick McHardy this_way->offset_after += off; 2841d73ec0SPatrick McHardy } 2941d73ec0SPatrick McHardy spin_unlock_bh(&ct->lock); 3041d73ec0SPatrick McHardy return 0; 3141d73ec0SPatrick McHardy } 3241d73ec0SPatrick McHardy EXPORT_SYMBOL_GPL(nf_ct_seqadj_set); 3341d73ec0SPatrick McHardy 3441d73ec0SPatrick McHardy void nf_ct_tcp_seqadj_set(struct sk_buff *skb, 3541d73ec0SPatrick McHardy struct nf_conn *ct, enum ip_conntrack_info ctinfo, 3641d73ec0SPatrick McHardy s32 off) 3741d73ec0SPatrick McHardy { 3841d73ec0SPatrick McHardy const struct tcphdr *th; 3941d73ec0SPatrick McHardy 4041d73ec0SPatrick McHardy if (nf_ct_protonum(ct) != IPPROTO_TCP) 4141d73ec0SPatrick McHardy return; 4241d73ec0SPatrick McHardy 4341d73ec0SPatrick McHardy th = (struct tcphdr *)(skb_network_header(skb) + ip_hdrlen(skb)); 4441d73ec0SPatrick McHardy nf_ct_seqadj_set(ct, ctinfo, th->seq, off); 4541d73ec0SPatrick McHardy } 4641d73ec0SPatrick McHardy EXPORT_SYMBOL_GPL(nf_ct_tcp_seqadj_set); 4741d73ec0SPatrick McHardy 4841d73ec0SPatrick McHardy /* Adjust one found SACK option including checksum correction */ 4941d73ec0SPatrick McHardy static void nf_ct_sack_block_adjust(struct sk_buff *skb, 5041d73ec0SPatrick McHardy struct tcphdr *tcph, 5141d73ec0SPatrick McHardy unsigned int sackoff, 5241d73ec0SPatrick McHardy unsigned int sackend, 5341d73ec0SPatrick McHardy struct nf_ct_seqadj *seq) 5441d73ec0SPatrick McHardy { 5541d73ec0SPatrick McHardy while (sackoff < sackend) { 5641d73ec0SPatrick McHardy struct tcp_sack_block_wire *sack; 5741d73ec0SPatrick McHardy __be32 new_start_seq, new_end_seq; 5841d73ec0SPatrick McHardy 5941d73ec0SPatrick McHardy sack = (void *)skb->data + sackoff; 6041d73ec0SPatrick McHardy if (after(ntohl(sack->start_seq) - seq->offset_before, 6141d73ec0SPatrick McHardy seq->correction_pos)) 6241d73ec0SPatrick McHardy new_start_seq = htonl(ntohl(sack->start_seq) - 6341d73ec0SPatrick McHardy seq->offset_after); 6441d73ec0SPatrick McHardy else 6541d73ec0SPatrick McHardy new_start_seq = htonl(ntohl(sack->start_seq) - 6641d73ec0SPatrick McHardy seq->offset_before); 6741d73ec0SPatrick McHardy 6841d73ec0SPatrick McHardy if (after(ntohl(sack->end_seq) - seq->offset_before, 6941d73ec0SPatrick McHardy seq->correction_pos)) 7041d73ec0SPatrick McHardy new_end_seq = htonl(ntohl(sack->end_seq) - 7141d73ec0SPatrick McHardy seq->offset_after); 7241d73ec0SPatrick McHardy else 7341d73ec0SPatrick McHardy new_end_seq = htonl(ntohl(sack->end_seq) - 7441d73ec0SPatrick McHardy seq->offset_before); 7541d73ec0SPatrick McHardy 7641d73ec0SPatrick McHardy pr_debug("sack_adjust: start_seq: %d->%d, end_seq: %d->%d\n", 7741d73ec0SPatrick McHardy ntohl(sack->start_seq), new_start_seq, 7841d73ec0SPatrick McHardy ntohl(sack->end_seq), new_end_seq); 7941d73ec0SPatrick McHardy 8041d73ec0SPatrick McHardy inet_proto_csum_replace4(&tcph->check, skb, 8141d73ec0SPatrick McHardy sack->start_seq, new_start_seq, 0); 8241d73ec0SPatrick McHardy inet_proto_csum_replace4(&tcph->check, skb, 8341d73ec0SPatrick McHardy sack->end_seq, new_end_seq, 0); 8441d73ec0SPatrick McHardy sack->start_seq = new_start_seq; 8541d73ec0SPatrick McHardy sack->end_seq = new_end_seq; 8641d73ec0SPatrick McHardy sackoff += sizeof(*sack); 8741d73ec0SPatrick McHardy } 8841d73ec0SPatrick McHardy } 8941d73ec0SPatrick McHardy 9041d73ec0SPatrick McHardy /* TCP SACK sequence number adjustment */ 9141d73ec0SPatrick McHardy static unsigned int nf_ct_sack_adjust(struct sk_buff *skb, 9241d73ec0SPatrick McHardy unsigned int protoff, 9341d73ec0SPatrick McHardy struct tcphdr *tcph, 9441d73ec0SPatrick McHardy struct nf_conn *ct, 9541d73ec0SPatrick McHardy enum ip_conntrack_info ctinfo) 9641d73ec0SPatrick McHardy { 9741d73ec0SPatrick McHardy unsigned int dir, optoff, optend; 9841d73ec0SPatrick McHardy struct nf_conn_seqadj *seqadj = nfct_seqadj(ct); 9941d73ec0SPatrick McHardy 10041d73ec0SPatrick McHardy optoff = protoff + sizeof(struct tcphdr); 10141d73ec0SPatrick McHardy optend = protoff + tcph->doff * 4; 10241d73ec0SPatrick McHardy 10341d73ec0SPatrick McHardy if (!skb_make_writable(skb, optend)) 10441d73ec0SPatrick McHardy return 0; 10541d73ec0SPatrick McHardy 10641d73ec0SPatrick McHardy dir = CTINFO2DIR(ctinfo); 10741d73ec0SPatrick McHardy 10841d73ec0SPatrick McHardy while (optoff < optend) { 10941d73ec0SPatrick McHardy /* Usually: option, length. */ 11041d73ec0SPatrick McHardy unsigned char *op = skb->data + optoff; 11141d73ec0SPatrick McHardy 11241d73ec0SPatrick McHardy switch (op[0]) { 11341d73ec0SPatrick McHardy case TCPOPT_EOL: 11441d73ec0SPatrick McHardy return 1; 11541d73ec0SPatrick McHardy case TCPOPT_NOP: 11641d73ec0SPatrick McHardy optoff++; 11741d73ec0SPatrick McHardy continue; 11841d73ec0SPatrick McHardy default: 11941d73ec0SPatrick McHardy /* no partial options */ 12041d73ec0SPatrick McHardy if (optoff + 1 == optend || 12141d73ec0SPatrick McHardy optoff + op[1] > optend || 12241d73ec0SPatrick McHardy op[1] < 2) 12341d73ec0SPatrick McHardy return 0; 12441d73ec0SPatrick McHardy if (op[0] == TCPOPT_SACK && 12541d73ec0SPatrick McHardy op[1] >= 2+TCPOLEN_SACK_PERBLOCK && 12641d73ec0SPatrick McHardy ((op[1] - 2) % TCPOLEN_SACK_PERBLOCK) == 0) 12741d73ec0SPatrick McHardy nf_ct_sack_block_adjust(skb, tcph, optoff + 2, 12841d73ec0SPatrick McHardy optoff+op[1], 12941d73ec0SPatrick McHardy &seqadj->seq[!dir]); 13041d73ec0SPatrick McHardy optoff += op[1]; 13141d73ec0SPatrick McHardy } 13241d73ec0SPatrick McHardy } 13341d73ec0SPatrick McHardy return 1; 13441d73ec0SPatrick McHardy } 13541d73ec0SPatrick McHardy 13641d73ec0SPatrick McHardy /* TCP sequence number adjustment. Returns 1 on success, 0 on failure */ 13741d73ec0SPatrick McHardy int nf_ct_seq_adjust(struct sk_buff *skb, 13841d73ec0SPatrick McHardy struct nf_conn *ct, enum ip_conntrack_info ctinfo, 13941d73ec0SPatrick McHardy unsigned int protoff) 14041d73ec0SPatrick McHardy { 14141d73ec0SPatrick McHardy enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo); 14241d73ec0SPatrick McHardy struct tcphdr *tcph; 14341d73ec0SPatrick McHardy __be32 newseq, newack; 14441d73ec0SPatrick McHardy s32 seqoff, ackoff; 14541d73ec0SPatrick McHardy struct nf_conn_seqadj *seqadj = nfct_seqadj(ct); 14641d73ec0SPatrick McHardy struct nf_ct_seqadj *this_way, *other_way; 14741d73ec0SPatrick McHardy int res; 14841d73ec0SPatrick McHardy 14941d73ec0SPatrick McHardy this_way = &seqadj->seq[dir]; 15041d73ec0SPatrick McHardy other_way = &seqadj->seq[!dir]; 15141d73ec0SPatrick McHardy 15241d73ec0SPatrick McHardy if (!skb_make_writable(skb, protoff + sizeof(*tcph))) 15341d73ec0SPatrick McHardy return 0; 15441d73ec0SPatrick McHardy 15541d73ec0SPatrick McHardy tcph = (void *)skb->data + protoff; 15641d73ec0SPatrick McHardy spin_lock_bh(&ct->lock); 15741d73ec0SPatrick McHardy if (after(ntohl(tcph->seq), this_way->correction_pos)) 15841d73ec0SPatrick McHardy seqoff = this_way->offset_after; 15941d73ec0SPatrick McHardy else 16041d73ec0SPatrick McHardy seqoff = this_way->offset_before; 16141d73ec0SPatrick McHardy 16241d73ec0SPatrick McHardy if (after(ntohl(tcph->ack_seq) - other_way->offset_before, 16341d73ec0SPatrick McHardy other_way->correction_pos)) 16441d73ec0SPatrick McHardy ackoff = other_way->offset_after; 16541d73ec0SPatrick McHardy else 16641d73ec0SPatrick McHardy ackoff = other_way->offset_before; 16741d73ec0SPatrick McHardy 16841d73ec0SPatrick McHardy newseq = htonl(ntohl(tcph->seq) + seqoff); 16941d73ec0SPatrick McHardy newack = htonl(ntohl(tcph->ack_seq) - ackoff); 17041d73ec0SPatrick McHardy 17141d73ec0SPatrick McHardy inet_proto_csum_replace4(&tcph->check, skb, tcph->seq, newseq, 0); 17241d73ec0SPatrick McHardy inet_proto_csum_replace4(&tcph->check, skb, tcph->ack_seq, newack, 0); 17341d73ec0SPatrick McHardy 17441d73ec0SPatrick McHardy pr_debug("Adjusting sequence number from %u->%u, ack from %u->%u\n", 17541d73ec0SPatrick McHardy ntohl(tcph->seq), ntohl(newseq), ntohl(tcph->ack_seq), 17641d73ec0SPatrick McHardy ntohl(newack)); 17741d73ec0SPatrick McHardy 17841d73ec0SPatrick McHardy tcph->seq = newseq; 17941d73ec0SPatrick McHardy tcph->ack_seq = newack; 18041d73ec0SPatrick McHardy 18141d73ec0SPatrick McHardy res = nf_ct_sack_adjust(skb, protoff, tcph, ct, ctinfo); 18241d73ec0SPatrick McHardy spin_unlock_bh(&ct->lock); 18341d73ec0SPatrick McHardy 18441d73ec0SPatrick McHardy return res; 18541d73ec0SPatrick McHardy } 18641d73ec0SPatrick McHardy EXPORT_SYMBOL_GPL(nf_ct_seq_adjust); 18741d73ec0SPatrick McHardy 18841d73ec0SPatrick McHardy s32 nf_ct_seq_offset(const struct nf_conn *ct, 18941d73ec0SPatrick McHardy enum ip_conntrack_dir dir, 19041d73ec0SPatrick McHardy u32 seq) 19141d73ec0SPatrick McHardy { 19241d73ec0SPatrick McHardy struct nf_conn_seqadj *seqadj = nfct_seqadj(ct); 19341d73ec0SPatrick McHardy struct nf_ct_seqadj *this_way; 19441d73ec0SPatrick McHardy 19541d73ec0SPatrick McHardy if (!seqadj) 19641d73ec0SPatrick McHardy return 0; 19741d73ec0SPatrick McHardy 19841d73ec0SPatrick McHardy this_way = &seqadj->seq[dir]; 19941d73ec0SPatrick McHardy return after(seq, this_way->correction_pos) ? 20041d73ec0SPatrick McHardy this_way->offset_after : this_way->offset_before; 20141d73ec0SPatrick McHardy } 20241d73ec0SPatrick McHardy EXPORT_SYMBOL_GPL(nf_ct_seq_offset); 20341d73ec0SPatrick McHardy 20441d73ec0SPatrick McHardy static struct nf_ct_ext_type nf_ct_seqadj_extend __read_mostly = { 20541d73ec0SPatrick McHardy .len = sizeof(struct nf_conn_seqadj), 20641d73ec0SPatrick McHardy .align = __alignof__(struct nf_conn_seqadj), 20741d73ec0SPatrick McHardy .id = NF_CT_EXT_SEQADJ, 20841d73ec0SPatrick McHardy }; 20941d73ec0SPatrick McHardy 21041d73ec0SPatrick McHardy int nf_conntrack_seqadj_init(void) 21141d73ec0SPatrick McHardy { 21241d73ec0SPatrick McHardy return nf_ct_extend_register(&nf_ct_seqadj_extend); 21341d73ec0SPatrick McHardy } 21441d73ec0SPatrick McHardy 21541d73ec0SPatrick McHardy void nf_conntrack_seqadj_fini(void) 21641d73ec0SPatrick McHardy { 21741d73ec0SPatrick McHardy nf_ct_extend_unregister(&nf_ct_seqadj_extend); 21841d73ec0SPatrick McHardy } 219