Blame view

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