1a805dee01
For SA outbound packets, rte_atomic64_add_return is used to generate SQN atomically. Use C11 atomics with RELAXED ordering for outbound SQN update instead of rte_atomic ops which enforce unnecessary barriers on aarch64. Signed-off-by: Phil Yang <phil.yang@arm.com> Reviewed-by: Ruifeng Wang <ruifeng.wang@arm.com> Reviewed-by: Gavin Hu <gavin.hu@arm.com> Acked-by: Konstantin Ananyev <konstantin.ananyev@intel.com>
310 lines
6.4 KiB
C
310 lines
6.4 KiB
C
/* 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 = __atomic_add_fetch(&sa->sqn.outb, n, __ATOMIC_RELAXED);
|
|
else {
|
|
sqn = sa->sqn.outb + n;
|
|
sa->sqn.outb = 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_ */
|