1457c8996SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
241d73ec0SPatrick McHardy #include <linux/types.h>
341d73ec0SPatrick McHardy #include <linux/netfilter.h>
441d73ec0SPatrick McHardy #include <net/tcp.h>
541d73ec0SPatrick McHardy 
641d73ec0SPatrick McHardy #include <net/netfilter/nf_conntrack.h>
741d73ec0SPatrick McHardy #include <net/netfilter/nf_conntrack_extend.h>
841d73ec0SPatrick McHardy #include <net/netfilter/nf_conntrack_seqadj.h>
941d73ec0SPatrick McHardy 
nf_ct_seqadj_init(struct nf_conn * ct,enum ip_conntrack_info ctinfo,s32 off)1048b1de4cSPatrick McHardy int nf_ct_seqadj_init(struct nf_conn *ct, enum ip_conntrack_info ctinfo,
1148b1de4cSPatrick McHardy 		      s32 off)
1248b1de4cSPatrick McHardy {
1348b1de4cSPatrick McHardy 	enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
1448b1de4cSPatrick McHardy 	struct nf_conn_seqadj *seqadj;
1548b1de4cSPatrick McHardy 	struct nf_ct_seqadj *this_way;
1648b1de4cSPatrick McHardy 
1748b1de4cSPatrick McHardy 	if (off == 0)
1848b1de4cSPatrick McHardy 		return 0;
1948b1de4cSPatrick McHardy 
2048b1de4cSPatrick McHardy 	set_bit(IPS_SEQ_ADJUST_BIT, &ct->status);
2148b1de4cSPatrick McHardy 
2248b1de4cSPatrick McHardy 	seqadj = nfct_seqadj(ct);
2348b1de4cSPatrick McHardy 	this_way = &seqadj->seq[dir];
2448b1de4cSPatrick McHardy 	this_way->offset_before	 = off;
2548b1de4cSPatrick McHardy 	this_way->offset_after	 = off;
2648b1de4cSPatrick McHardy 	return 0;
2748b1de4cSPatrick McHardy }
2848b1de4cSPatrick McHardy EXPORT_SYMBOL_GPL(nf_ct_seqadj_init);
2948b1de4cSPatrick McHardy 
nf_ct_seqadj_set(struct nf_conn * ct,enum ip_conntrack_info ctinfo,__be32 seq,s32 off)3041d73ec0SPatrick McHardy int nf_ct_seqadj_set(struct nf_conn *ct, enum ip_conntrack_info ctinfo,
3141d73ec0SPatrick McHardy 		     __be32 seq, s32 off)
3241d73ec0SPatrick McHardy {
3341d73ec0SPatrick McHardy 	struct nf_conn_seqadj *seqadj = nfct_seqadj(ct);
3441d73ec0SPatrick McHardy 	enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
3541d73ec0SPatrick McHardy 	struct nf_ct_seqadj *this_way;
3641d73ec0SPatrick McHardy 
3741d73ec0SPatrick McHardy 	if (off == 0)
3841d73ec0SPatrick McHardy 		return 0;
3941d73ec0SPatrick McHardy 
40db12cf27SJesper Dangaard Brouer 	if (unlikely(!seqadj)) {
41f2661adcSJesper Dangaard Brouer 		WARN_ONCE(1, "Missing nfct_seqadj_ext_add() setup call\n");
42db12cf27SJesper Dangaard Brouer 		return 0;
43db12cf27SJesper Dangaard Brouer 	}
44db12cf27SJesper Dangaard Brouer 
4541d73ec0SPatrick McHardy 	set_bit(IPS_SEQ_ADJUST_BIT, &ct->status);
4641d73ec0SPatrick McHardy 
4741d73ec0SPatrick McHardy 	spin_lock_bh(&ct->lock);
4841d73ec0SPatrick McHardy 	this_way = &seqadj->seq[dir];
4941d73ec0SPatrick McHardy 	if (this_way->offset_before == this_way->offset_after ||
5023dfe136SPhil Oester 	    before(this_way->correction_pos, ntohl(seq))) {
5123dfe136SPhil Oester 		this_way->correction_pos = ntohl(seq);
5241d73ec0SPatrick McHardy 		this_way->offset_before	 = this_way->offset_after;
5341d73ec0SPatrick McHardy 		this_way->offset_after	+= off;
5441d73ec0SPatrick McHardy 	}
5541d73ec0SPatrick McHardy 	spin_unlock_bh(&ct->lock);
5641d73ec0SPatrick McHardy 	return 0;
5741d73ec0SPatrick McHardy }
5841d73ec0SPatrick McHardy EXPORT_SYMBOL_GPL(nf_ct_seqadj_set);
5941d73ec0SPatrick McHardy 
nf_ct_tcp_seqadj_set(struct sk_buff * skb,struct nf_conn * ct,enum ip_conntrack_info ctinfo,s32 off)6041d73ec0SPatrick McHardy void nf_ct_tcp_seqadj_set(struct sk_buff *skb,
6141d73ec0SPatrick McHardy 			  struct nf_conn *ct, enum ip_conntrack_info ctinfo,
6241d73ec0SPatrick McHardy 			  s32 off)
6341d73ec0SPatrick McHardy {
6441d73ec0SPatrick McHardy 	const struct tcphdr *th;
6541d73ec0SPatrick McHardy 
6641d73ec0SPatrick McHardy 	if (nf_ct_protonum(ct) != IPPROTO_TCP)
6741d73ec0SPatrick McHardy 		return;
6841d73ec0SPatrick McHardy 
6941d73ec0SPatrick McHardy 	th = (struct tcphdr *)(skb_network_header(skb) + ip_hdrlen(skb));
7041d73ec0SPatrick McHardy 	nf_ct_seqadj_set(ct, ctinfo, th->seq, off);
7141d73ec0SPatrick McHardy }
7241d73ec0SPatrick McHardy EXPORT_SYMBOL_GPL(nf_ct_tcp_seqadj_set);
7341d73ec0SPatrick McHardy 
7441d73ec0SPatrick McHardy /* Adjust one found SACK option including checksum correction */
nf_ct_sack_block_adjust(struct sk_buff * skb,struct tcphdr * tcph,unsigned int sackoff,unsigned int sackend,struct nf_ct_seqadj * seq)7541d73ec0SPatrick McHardy static void nf_ct_sack_block_adjust(struct sk_buff *skb,
7641d73ec0SPatrick McHardy 				    struct tcphdr *tcph,
7741d73ec0SPatrick McHardy 				    unsigned int sackoff,
7841d73ec0SPatrick McHardy 				    unsigned int sackend,
7941d73ec0SPatrick McHardy 				    struct nf_ct_seqadj *seq)
8041d73ec0SPatrick McHardy {
8141d73ec0SPatrick McHardy 	while (sackoff < sackend) {
8241d73ec0SPatrick McHardy 		struct tcp_sack_block_wire *sack;
8341d73ec0SPatrick McHardy 		__be32 new_start_seq, new_end_seq;
8441d73ec0SPatrick McHardy 
8541d73ec0SPatrick McHardy 		sack = (void *)skb->data + sackoff;
8641d73ec0SPatrick McHardy 		if (after(ntohl(sack->start_seq) - seq->offset_before,
8741d73ec0SPatrick McHardy 			  seq->correction_pos))
8841d73ec0SPatrick McHardy 			new_start_seq = htonl(ntohl(sack->start_seq) -
8941d73ec0SPatrick McHardy 					seq->offset_after);
9041d73ec0SPatrick McHardy 		else
9141d73ec0SPatrick McHardy 			new_start_seq = htonl(ntohl(sack->start_seq) -
9241d73ec0SPatrick McHardy 					seq->offset_before);
9341d73ec0SPatrick McHardy 
9441d73ec0SPatrick McHardy 		if (after(ntohl(sack->end_seq) - seq->offset_before,
9541d73ec0SPatrick McHardy 			  seq->correction_pos))
9641d73ec0SPatrick McHardy 			new_end_seq = htonl(ntohl(sack->end_seq) -
9741d73ec0SPatrick McHardy 				      seq->offset_after);
9841d73ec0SPatrick McHardy 		else
9941d73ec0SPatrick McHardy 			new_end_seq = htonl(ntohl(sack->end_seq) -
10041d73ec0SPatrick McHardy 				      seq->offset_before);
10141d73ec0SPatrick McHardy 
102b44b565cSGao feng 		pr_debug("sack_adjust: start_seq: %u->%u, end_seq: %u->%u\n",
103b44b565cSGao feng 			 ntohl(sack->start_seq), ntohl(new_start_seq),
104b44b565cSGao feng 			 ntohl(sack->end_seq), ntohl(new_end_seq));
10541d73ec0SPatrick McHardy 
10641d73ec0SPatrick McHardy 		inet_proto_csum_replace4(&tcph->check, skb,
1074b048d6dSTom Herbert 					 sack->start_seq, new_start_seq, false);
10841d73ec0SPatrick McHardy 		inet_proto_csum_replace4(&tcph->check, skb,
1094b048d6dSTom Herbert 					 sack->end_seq, new_end_seq, false);
11041d73ec0SPatrick McHardy 		sack->start_seq = new_start_seq;
11141d73ec0SPatrick McHardy 		sack->end_seq = new_end_seq;
11241d73ec0SPatrick McHardy 		sackoff += sizeof(*sack);
11341d73ec0SPatrick McHardy 	}
11441d73ec0SPatrick McHardy }
11541d73ec0SPatrick McHardy 
11641d73ec0SPatrick McHardy /* TCP SACK sequence number adjustment */
nf_ct_sack_adjust(struct sk_buff * skb,unsigned int protoff,struct nf_conn * ct,enum ip_conntrack_info ctinfo)11741d73ec0SPatrick McHardy static unsigned int nf_ct_sack_adjust(struct sk_buff *skb,
11841d73ec0SPatrick McHardy 				      unsigned int protoff,
11941d73ec0SPatrick McHardy 				      struct nf_conn *ct,
12041d73ec0SPatrick McHardy 				      enum ip_conntrack_info ctinfo)
12141d73ec0SPatrick McHardy {
122530aad77SFlorian Westphal 	struct tcphdr *tcph = (void *)skb->data + protoff;
12341d73ec0SPatrick McHardy 	struct nf_conn_seqadj *seqadj = nfct_seqadj(ct);
124530aad77SFlorian Westphal 	unsigned int dir, optoff, optend;
12541d73ec0SPatrick McHardy 
12641d73ec0SPatrick McHardy 	optoff = protoff + sizeof(struct tcphdr);
12741d73ec0SPatrick McHardy 	optend = protoff + tcph->doff * 4;
12841d73ec0SPatrick McHardy 
12986f04538SFlorian Westphal 	if (skb_ensure_writable(skb, optend))
13041d73ec0SPatrick McHardy 		return 0;
13141d73ec0SPatrick McHardy 
132530aad77SFlorian Westphal 	tcph = (void *)skb->data + protoff;
13341d73ec0SPatrick McHardy 	dir = CTINFO2DIR(ctinfo);
13441d73ec0SPatrick McHardy 
13541d73ec0SPatrick McHardy 	while (optoff < optend) {
13641d73ec0SPatrick McHardy 		/* Usually: option, length. */
13741d73ec0SPatrick McHardy 		unsigned char *op = skb->data + optoff;
13841d73ec0SPatrick McHardy 
13941d73ec0SPatrick McHardy 		switch (op[0]) {
14041d73ec0SPatrick McHardy 		case TCPOPT_EOL:
14141d73ec0SPatrick McHardy 			return 1;
14241d73ec0SPatrick McHardy 		case TCPOPT_NOP:
14341d73ec0SPatrick McHardy 			optoff++;
14441d73ec0SPatrick McHardy 			continue;
14541d73ec0SPatrick McHardy 		default:
14641d73ec0SPatrick McHardy 			/* no partial options */
14741d73ec0SPatrick McHardy 			if (optoff + 1 == optend ||
14841d73ec0SPatrick McHardy 			    optoff + op[1] > optend ||
14941d73ec0SPatrick McHardy 			    op[1] < 2)
15041d73ec0SPatrick McHardy 				return 0;
15141d73ec0SPatrick McHardy 			if (op[0] == TCPOPT_SACK &&
15241d73ec0SPatrick McHardy 			    op[1] >= 2+TCPOLEN_SACK_PERBLOCK &&
15341d73ec0SPatrick McHardy 			    ((op[1] - 2) % TCPOLEN_SACK_PERBLOCK) == 0)
15441d73ec0SPatrick McHardy 				nf_ct_sack_block_adjust(skb, tcph, optoff + 2,
15541d73ec0SPatrick McHardy 							optoff+op[1],
15641d73ec0SPatrick McHardy 							&seqadj->seq[!dir]);
15741d73ec0SPatrick McHardy 			optoff += op[1];
15841d73ec0SPatrick McHardy 		}
15941d73ec0SPatrick McHardy 	}
16041d73ec0SPatrick McHardy 	return 1;
16141d73ec0SPatrick McHardy }
16241d73ec0SPatrick McHardy 
16341d73ec0SPatrick McHardy /* TCP sequence number adjustment.  Returns 1 on success, 0 on failure */
nf_ct_seq_adjust(struct sk_buff * skb,struct nf_conn * ct,enum ip_conntrack_info ctinfo,unsigned int protoff)16441d73ec0SPatrick McHardy int nf_ct_seq_adjust(struct sk_buff *skb,
16541d73ec0SPatrick McHardy 		     struct nf_conn *ct, enum ip_conntrack_info ctinfo,
16641d73ec0SPatrick McHardy 		     unsigned int protoff)
16741d73ec0SPatrick McHardy {
16841d73ec0SPatrick McHardy 	enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
16941d73ec0SPatrick McHardy 	struct tcphdr *tcph;
17041d73ec0SPatrick McHardy 	__be32 newseq, newack;
17141d73ec0SPatrick McHardy 	s32 seqoff, ackoff;
17241d73ec0SPatrick McHardy 	struct nf_conn_seqadj *seqadj = nfct_seqadj(ct);
17341d73ec0SPatrick McHardy 	struct nf_ct_seqadj *this_way, *other_way;
1748d11350fSGao Feng 	int res = 1;
17541d73ec0SPatrick McHardy 
17641d73ec0SPatrick McHardy 	this_way  = &seqadj->seq[dir];
17741d73ec0SPatrick McHardy 	other_way = &seqadj->seq[!dir];
17841d73ec0SPatrick McHardy 
17986f04538SFlorian Westphal 	if (skb_ensure_writable(skb, protoff + sizeof(*tcph)))
18041d73ec0SPatrick McHardy 		return 0;
18141d73ec0SPatrick McHardy 
18241d73ec0SPatrick McHardy 	tcph = (void *)skb->data + protoff;
18341d73ec0SPatrick McHardy 	spin_lock_bh(&ct->lock);
18441d73ec0SPatrick McHardy 	if (after(ntohl(tcph->seq), this_way->correction_pos))
18541d73ec0SPatrick McHardy 		seqoff = this_way->offset_after;
18641d73ec0SPatrick McHardy 	else
18741d73ec0SPatrick McHardy 		seqoff = this_way->offset_before;
18841d73ec0SPatrick McHardy 
1898d11350fSGao Feng 	newseq = htonl(ntohl(tcph->seq) + seqoff);
1908d11350fSGao Feng 	inet_proto_csum_replace4(&tcph->check, skb, tcph->seq, newseq, false);
1918d11350fSGao Feng 	pr_debug("Adjusting sequence number from %u->%u\n",
1928d11350fSGao Feng 		 ntohl(tcph->seq), ntohl(newseq));
1938d11350fSGao Feng 	tcph->seq = newseq;
1948d11350fSGao Feng 
1958d11350fSGao Feng 	if (!tcph->ack)
1968d11350fSGao Feng 		goto out;
1978d11350fSGao Feng 
19841d73ec0SPatrick McHardy 	if (after(ntohl(tcph->ack_seq) - other_way->offset_before,
19941d73ec0SPatrick McHardy 		  other_way->correction_pos))
20041d73ec0SPatrick McHardy 		ackoff = other_way->offset_after;
20141d73ec0SPatrick McHardy 	else
20241d73ec0SPatrick McHardy 		ackoff = other_way->offset_before;
20341d73ec0SPatrick McHardy 
20441d73ec0SPatrick McHardy 	newack = htonl(ntohl(tcph->ack_seq) - ackoff);
2054b048d6dSTom Herbert 	inet_proto_csum_replace4(&tcph->check, skb, tcph->ack_seq, newack,
2064b048d6dSTom Herbert 				 false);
2078d11350fSGao Feng 	pr_debug("Adjusting ack number from %u->%u, ack from %u->%u\n",
20841d73ec0SPatrick McHardy 		 ntohl(tcph->seq), ntohl(newseq), ntohl(tcph->ack_seq),
20941d73ec0SPatrick McHardy 		 ntohl(newack));
21041d73ec0SPatrick McHardy 	tcph->ack_seq = newack;
21141d73ec0SPatrick McHardy 
212530aad77SFlorian Westphal 	res = nf_ct_sack_adjust(skb, protoff, ct, ctinfo);
2138d11350fSGao Feng out:
21441d73ec0SPatrick McHardy 	spin_unlock_bh(&ct->lock);
21541d73ec0SPatrick McHardy 
21641d73ec0SPatrick McHardy 	return res;
21741d73ec0SPatrick McHardy }
21841d73ec0SPatrick McHardy EXPORT_SYMBOL_GPL(nf_ct_seq_adjust);
21941d73ec0SPatrick McHardy 
nf_ct_seq_offset(const struct nf_conn * ct,enum ip_conntrack_dir dir,u32 seq)22041d73ec0SPatrick McHardy s32 nf_ct_seq_offset(const struct nf_conn *ct,
22141d73ec0SPatrick McHardy 		     enum ip_conntrack_dir dir,
22241d73ec0SPatrick McHardy 		     u32 seq)
22341d73ec0SPatrick McHardy {
22441d73ec0SPatrick McHardy 	struct nf_conn_seqadj *seqadj = nfct_seqadj(ct);
22541d73ec0SPatrick McHardy 	struct nf_ct_seqadj *this_way;
22641d73ec0SPatrick McHardy 
22741d73ec0SPatrick McHardy 	if (!seqadj)
22841d73ec0SPatrick McHardy 		return 0;
22941d73ec0SPatrick McHardy 
23041d73ec0SPatrick McHardy 	this_way = &seqadj->seq[dir];
23141d73ec0SPatrick McHardy 	return after(seq, this_way->correction_pos) ?
23241d73ec0SPatrick McHardy 		 this_way->offset_after : this_way->offset_before;
23341d73ec0SPatrick McHardy }
23441d73ec0SPatrick McHardy EXPORT_SYMBOL_GPL(nf_ct_seq_offset);
235