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
9int 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}
27EXPORT_SYMBOL_GPL(nf_ct_seqadj_init);
28
29int 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}
57EXPORT_SYMBOL_GPL(nf_ct_seqadj_set);
58
59void 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}
71EXPORT_SYMBOL_GPL(nf_ct_tcp_seqadj_set);
72
73/* Adjust one found SACK option including checksum correction */
74static 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 */
116static unsigned int nf_ct_sack_adjust(struct sk_buff *skb,
117				      unsigned int protoff,
118				      struct tcphdr *tcph,
119				      struct nf_conn *ct,
120				      enum ip_conntrack_info ctinfo)
121{
122	unsigned int dir, optoff, optend;
123	struct nf_conn_seqadj *seqadj = nfct_seqadj(ct);
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	dir = CTINFO2DIR(ctinfo);
132
133	while (optoff < optend) {
134		/* Usually: option, length. */
135		unsigned char *op = skb->data + optoff;
136
137		switch (op[0]) {
138		case TCPOPT_EOL:
139			return 1;
140		case TCPOPT_NOP:
141			optoff++;
142			continue;
143		default:
144			/* no partial options */
145			if (optoff + 1 == optend ||
146			    optoff + op[1] > optend ||
147			    op[1] < 2)
148				return 0;
149			if (op[0] == TCPOPT_SACK &&
150			    op[1] >= 2+TCPOLEN_SACK_PERBLOCK &&
151			    ((op[1] - 2) % TCPOLEN_SACK_PERBLOCK) == 0)
152				nf_ct_sack_block_adjust(skb, tcph, optoff + 2,
153							optoff+op[1],
154							&seqadj->seq[!dir]);
155			optoff += op[1];
156		}
157	}
158	return 1;
159}
160
161/* TCP sequence number adjustment.  Returns 1 on success, 0 on failure */
162int nf_ct_seq_adjust(struct sk_buff *skb,
163		     struct nf_conn *ct, enum ip_conntrack_info ctinfo,
164		     unsigned int protoff)
165{
166	enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
167	struct tcphdr *tcph;
168	__be32 newseq, newack;
169	s32 seqoff, ackoff;
170	struct nf_conn_seqadj *seqadj = nfct_seqadj(ct);
171	struct nf_ct_seqadj *this_way, *other_way;
172	int res;
173
174	this_way  = &seqadj->seq[dir];
175	other_way = &seqadj->seq[!dir];
176
177	if (!skb_make_writable(skb, protoff + sizeof(*tcph)))
178		return 0;
179
180	tcph = (void *)skb->data + protoff;
181	spin_lock_bh(&ct->lock);
182	if (after(ntohl(tcph->seq), this_way->correction_pos))
183		seqoff = this_way->offset_after;
184	else
185		seqoff = this_way->offset_before;
186
187	if (after(ntohl(tcph->ack_seq) - other_way->offset_before,
188		  other_way->correction_pos))
189		ackoff = other_way->offset_after;
190	else
191		ackoff = other_way->offset_before;
192
193	newseq = htonl(ntohl(tcph->seq) + seqoff);
194	newack = htonl(ntohl(tcph->ack_seq) - ackoff);
195
196	inet_proto_csum_replace4(&tcph->check, skb, tcph->seq, newseq, false);
197	inet_proto_csum_replace4(&tcph->check, skb, tcph->ack_seq, newack,
198				 false);
199
200	pr_debug("Adjusting sequence number from %u->%u, ack from %u->%u\n",
201		 ntohl(tcph->seq), ntohl(newseq), ntohl(tcph->ack_seq),
202		 ntohl(newack));
203
204	tcph->seq = newseq;
205	tcph->ack_seq = newack;
206
207	res = nf_ct_sack_adjust(skb, protoff, tcph, ct, ctinfo);
208	spin_unlock_bh(&ct->lock);
209
210	return res;
211}
212EXPORT_SYMBOL_GPL(nf_ct_seq_adjust);
213
214s32 nf_ct_seq_offset(const struct nf_conn *ct,
215		     enum ip_conntrack_dir dir,
216		     u32 seq)
217{
218	struct nf_conn_seqadj *seqadj = nfct_seqadj(ct);
219	struct nf_ct_seqadj *this_way;
220
221	if (!seqadj)
222		return 0;
223
224	this_way = &seqadj->seq[dir];
225	return after(seq, this_way->correction_pos) ?
226		 this_way->offset_after : this_way->offset_before;
227}
228EXPORT_SYMBOL_GPL(nf_ct_seq_offset);
229
230static struct nf_ct_ext_type nf_ct_seqadj_extend __read_mostly = {
231	.len	= sizeof(struct nf_conn_seqadj),
232	.align	= __alignof__(struct nf_conn_seqadj),
233	.id	= NF_CT_EXT_SEQADJ,
234};
235
236int nf_conntrack_seqadj_init(void)
237{
238	return nf_ct_extend_register(&nf_ct_seqadj_extend);
239}
240
241void nf_conntrack_seqadj_fini(void)
242{
243	nf_ct_extend_unregister(&nf_ct_seqadj_extend);
244}
245