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