summaryrefslogtreecommitdiff
path: root/lib/librte_ipsec/ipsec_sqn.h
blob: 0c2f76a7ac567d1ea08241bca75d9beb88deaf09 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
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
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
/* SPDX-License-Identifier: BSD-3-Clause
 * Copyright(c) 2018 Intel Corporation
 */

#ifndef _IPSEC_SQN_H_
#define _IPSEC_SQN_H_

#define WINDOW_BUCKET_BITS		6 /* uint64_t */
#define WINDOW_BUCKET_SIZE		(1 << WINDOW_BUCKET_BITS)
#define WINDOW_BIT_LOC_MASK		(WINDOW_BUCKET_SIZE - 1)

/* minimum number of bucket, power of 2*/
#define WINDOW_BUCKET_MIN		2
#define WINDOW_BUCKET_MAX		(INT16_MAX + 1)

#define IS_ESN(sa)	((sa)->sqn_mask == UINT64_MAX)

#define	SQN_ATOMIC(sa)	((sa)->type & RTE_IPSEC_SATP_SQN_ATOM)

/*
 * gets SQN.hi32 bits, SQN supposed to be in network byte order.
 */
static inline rte_be32_t
sqn_hi32(rte_be64_t sqn)
{
#if RTE_BYTE_ORDER == RTE_BIG_ENDIAN
	return (sqn >> 32);
#else
	return sqn;
#endif
}

/*
 * gets SQN.low32 bits, SQN supposed to be in network byte order.
 */
static inline rte_be32_t
sqn_low32(rte_be64_t sqn)
{
#if RTE_BYTE_ORDER == RTE_BIG_ENDIAN
	return sqn;
#else
	return (sqn >> 32);
#endif
}

/*
 * gets SQN.low16 bits, SQN supposed to be in network byte order.
 */
static inline rte_be16_t
sqn_low16(rte_be64_t sqn)
{
#if RTE_BYTE_ORDER == RTE_BIG_ENDIAN
	return sqn;
#else
	return (sqn >> 48);
#endif
}

/*
 * According to RFC4303 A2.1, determine the high-order bit of sequence number.
 * use 32bit arithmetic inside, return uint64_t.
 */
static inline uint64_t
reconstruct_esn(uint64_t t, uint32_t sqn, uint32_t w)
{
	uint32_t th, tl, bl;

	tl = t;
	th = t >> 32;
	bl = tl - w + 1;

	/* case A: window is within one sequence number subspace */
	if (tl >= (w - 1))
		th += (sqn < bl);
	/* case B: window spans two sequence number subspaces */
	else if (th != 0)
		th -= (sqn >= bl);

	/* return constructed sequence with proper high-order bits */
	return (uint64_t)th << 32 | sqn;
}

/**
 * Perform the replay checking.
 *
 * struct rte_ipsec_sa contains the window and window related parameters,
 * such as the window size, bitmask, and the last acknowledged sequence number.
 *
 * Based on RFC 6479.
 * Blocks are 64 bits unsigned integers
 */
static inline int32_t
esn_inb_check_sqn(const struct replay_sqn *rsn, const struct rte_ipsec_sa *sa,
	uint64_t sqn)
{
	uint32_t bit, bucket;

	/* replay not enabled */
	if (sa->replay.win_sz == 0)
		return 0;

	/* seq is larger than lastseq */
	if (sqn > rsn->sqn)
		return 0;

	/* seq is outside window */
	if (sqn == 0 || sqn + sa->replay.win_sz < rsn->sqn)
		return -EINVAL;

	/* seq is inside the window */
	bit = sqn & WINDOW_BIT_LOC_MASK;
	bucket = (sqn >> WINDOW_BUCKET_BITS) & sa->replay.bucket_index_mask;

	/* already seen packet */
	if (rsn->window[bucket] & ((uint64_t)1 << bit))
		return -EINVAL;

	return 0;
}

/**
 * For outbound SA perform the sequence number update.
 */
static inline uint64_t
esn_outb_update_sqn(struct rte_ipsec_sa *sa, uint32_t *num)
{
	uint64_t n, s, sqn;

	n = *num;
	if (SQN_ATOMIC(sa))
		sqn = (uint64_t)rte_atomic64_add_return(&sa->sqn.outb.atom, n);
	else {
		sqn = sa->sqn.outb.raw + n;
		sa->sqn.outb.raw = sqn;
	}

	/* overflow */
	if (sqn > sa->sqn_mask) {
		s = sqn - sa->sqn_mask;
		*num = (s < n) ?  n - s : 0;
	}

	return sqn - n;
}

/**
 * For inbound SA perform the sequence number and replay window update.
 */
static inline int32_t
esn_inb_update_sqn(struct replay_sqn *rsn, const struct rte_ipsec_sa *sa,
	uint64_t sqn)
{
	uint32_t bit, bucket, last_bucket, new_bucket, diff, i;

	/* handle ESN */
	if (IS_ESN(sa))
		sqn = reconstruct_esn(rsn->sqn, sqn, sa->replay.win_sz);

	/* seq is outside window*/
	if (sqn == 0 || sqn + sa->replay.win_sz < rsn->sqn)
		return -EINVAL;

	/* update the bit */
	bucket = (sqn >> WINDOW_BUCKET_BITS);

	/* check if the seq is within the range */
	if (sqn > rsn->sqn) {
		last_bucket = rsn->sqn >> WINDOW_BUCKET_BITS;
		diff = bucket - last_bucket;
		/* seq is way after the range of WINDOW_SIZE */
		if (diff > sa->replay.nb_bucket)
			diff = sa->replay.nb_bucket;

		for (i = 0; i != diff; i++) {
			new_bucket = (i + last_bucket + 1) &
				sa->replay.bucket_index_mask;
			rsn->window[new_bucket] = 0;
		}
		rsn->sqn = sqn;
	}

	bucket &= sa->replay.bucket_index_mask;
	bit = (uint64_t)1 << (sqn & WINDOW_BIT_LOC_MASK);

	/* already seen packet */
	if (rsn->window[bucket] & bit)
		return -EINVAL;

	rsn->window[bucket] |= bit;
	return 0;
}

/**
 * To achieve ability to do multiple readers single writer for
 * SA replay window information and sequence number (RSN)
 * basic RCU schema is used:
 * SA have 2 copies of RSN (one for readers, another for writers).
 * Each RSN contains a rwlock that has to be grabbed (for read/write)
 * to avoid races between readers and writer.
 * Writer is responsible to make a copy or reader RSN, update it
 * and mark newly updated RSN as readers one.
 * That approach is intended to minimize contention and cache sharing
 * between writer and readers.
 */

/**
 * Copy replay window and SQN.
 */
static inline void
rsn_copy(const struct rte_ipsec_sa *sa, uint32_t dst, uint32_t src)
{
	uint32_t i, n;
	struct replay_sqn *d;
	const struct replay_sqn *s;

	d = sa->sqn.inb.rsn[dst];
	s = sa->sqn.inb.rsn[src];

	n = sa->replay.nb_bucket;

	d->sqn = s->sqn;
	for (i = 0; i != n; i++)
		d->window[i] = s->window[i];
}

/**
 * Get RSN for read-only access.
 */
static inline struct replay_sqn *
rsn_acquire(struct rte_ipsec_sa *sa)
{
	uint32_t n;
	struct replay_sqn *rsn;

	n = sa->sqn.inb.rdidx;
	rsn = sa->sqn.inb.rsn[n];

	if (!SQN_ATOMIC(sa))
		return rsn;

	/* check there are no writers */
	while (rte_rwlock_read_trylock(&rsn->rwl) < 0) {
		rte_pause();
		n = sa->sqn.inb.rdidx;
		rsn = sa->sqn.inb.rsn[n];
		rte_compiler_barrier();
	}

	return rsn;
}

/**
 * Release read-only access for RSN.
 */
static inline void
rsn_release(struct rte_ipsec_sa *sa, struct replay_sqn *rsn)
{
	if (SQN_ATOMIC(sa))
		rte_rwlock_read_unlock(&rsn->rwl);
}

/**
 * Start RSN update.
 */
static inline struct replay_sqn *
rsn_update_start(struct rte_ipsec_sa *sa)
{
	uint32_t k, n;
	struct replay_sqn *rsn;

	n = sa->sqn.inb.wridx;

	/* no active writers */
	RTE_ASSERT(n == sa->sqn.inb.rdidx);

	if (!SQN_ATOMIC(sa))
		return sa->sqn.inb.rsn[n];

	k = REPLAY_SQN_NEXT(n);
	sa->sqn.inb.wridx = k;

	rsn = sa->sqn.inb.rsn[k];
	rte_rwlock_write_lock(&rsn->rwl);
	rsn_copy(sa, k, n);

	return rsn;
}

/**
 * Finish RSN update.
 */
static inline void
rsn_update_finish(struct rte_ipsec_sa *sa, struct replay_sqn *rsn)
{
	uint32_t n;

	if (!SQN_ATOMIC(sa))
		return;

	n = sa->sqn.inb.wridx;
	RTE_ASSERT(n != sa->sqn.inb.rdidx);
	RTE_ASSERT(rsn == sa->sqn.inb.rsn[n]);

	rte_rwlock_write_unlock(&rsn->rwl);
	sa->sqn.inb.rdidx = n;
}


#endif /* _IPSEC_SQN_H_ */