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