DPDK patches and discussions
 help / color / mirror / Atom feed
* [dpdk-dev] [PATCH v1 0/3] Predictable RSS feature
@ 2021-03-16 18:24 Vladimir Medvedkin
  2021-03-16 18:24 ` [dpdk-dev] [PATCH v1 1/3] hash: add predictable RSS API Vladimir Medvedkin
                   ` (6 more replies)
  0 siblings, 7 replies; 45+ messages in thread
From: Vladimir Medvedkin @ 2021-03-16 18:24 UTC (permalink / raw)
  To: dev
  Cc: konstantin.ananyev, andrey.chilikin, ray.kinsella, yipeng1.wang,
	sameh.gobriel, bruce.richardson

This patch series introduces predictable RSS feature.
It is based on the idea of searching for partial hash collisions
within Toeplitz hash.

The Toeplitz hash function is a homomorphism between (G, ^) and (H, ^),
where (G, ^) - is a group of tuples and (H, ^) is a group of hashes
with respect to XOR operation. So tuples and hashes could be treated as
n-dimension and 32-dimension vector spaces over GF(2).
So, f(x ^ y) == f(x) ^ f(y)
where f - is the toeplitz hash function and x, y are tuples.

The ability to predict partial collisions allows user to compute
input hash value with desired LSB values.
Usually number of LSB's are defined by the size of RSS Redirection Table.

There could be number of use cases, for example:
1) NAT. Using this library it is possible to select a new port number
on a translation in the way that rss hash for original tuple will have
the same LSB's as rss hash for reverse tuple.
2) IPSec/MPLS/Vxlan. It is possible to choose tunnel id to be pinned to
a desired queue.
3) TCP stack. It is possible to choose a source port number for outgoing
connections in the way that received replies will be assigned to
desired queue.
4) RSS hash key generation. Hash key initialization with random values
does not guarantee an uniform distribution amongst queues. This library
uses mathematically proved algorithm to complete the rss hash key to
provide the best distribution.

Vladimir Medvedkin (3):
  hash: add predictable RSS API
  hash: add predictable RSS implementation
  test/hash: add additional thash tests

 app/test/test_thash.c       | 383 +++++++++++++++++++++++++++-
 lib/librte_hash/meson.build |   3 +-
 lib/librte_hash/rte_thash.c | 596 ++++++++++++++++++++++++++++++++++++++++++++
 lib/librte_hash/rte_thash.h | 138 ++++++++++
 lib/librte_hash/version.map |   7 +
 5 files changed, 1120 insertions(+), 7 deletions(-)
 create mode 100644 lib/librte_hash/rte_thash.c

-- 
2.7.4


^ permalink raw reply	[flat|nested] 45+ messages in thread

* [dpdk-dev] [PATCH v1 1/3] hash: add predictable RSS API
  2021-03-16 18:24 [dpdk-dev] [PATCH v1 0/3] Predictable RSS feature Vladimir Medvedkin
@ 2021-03-16 18:24 ` Vladimir Medvedkin
  2021-03-16 18:24 ` [dpdk-dev] [PATCH v1 2/3] hash: add predictable RSS implementation Vladimir Medvedkin
                   ` (5 subsequent siblings)
  6 siblings, 0 replies; 45+ messages in thread
From: Vladimir Medvedkin @ 2021-03-16 18:24 UTC (permalink / raw)
  To: dev
  Cc: konstantin.ananyev, andrey.chilikin, ray.kinsella, yipeng1.wang,
	sameh.gobriel, bruce.richardson

This patch adds predictable RSS API.
It is based on the idea of searching partial Toeplitz hash collisions.

Signed-off-by: Vladimir Medvedkin <vladimir.medvedkin@intel.com>
---
 lib/librte_hash/meson.build |   3 +-
 lib/librte_hash/rte_thash.c |  96 ++++++++++++++++++++++++++++++
 lib/librte_hash/rte_thash.h | 138 ++++++++++++++++++++++++++++++++++++++++++++
 lib/librte_hash/version.map |   7 +++
 4 files changed, 243 insertions(+), 1 deletion(-)
 create mode 100644 lib/librte_hash/rte_thash.c

diff --git a/lib/librte_hash/meson.build b/lib/librte_hash/meson.build
index 242859f..3546014 100644
--- a/lib/librte_hash/meson.build
+++ b/lib/librte_hash/meson.build
@@ -8,6 +8,7 @@ headers = files('rte_fbk_hash.h',
 	'rte_thash.h')
 indirect_headers += files('rte_crc_arm64.h')
 
-sources = files('rte_cuckoo_hash.c', 'rte_fbk_hash.c')
+sources = files('rte_cuckoo_hash.c', 'rte_fbk_hash.c', 'rte_thash.c')
+deps += ['net']
 deps += ['ring']
 deps += ['rcu']
diff --git a/lib/librte_hash/rte_thash.c b/lib/librte_hash/rte_thash.c
new file mode 100644
index 0000000..79e8724
--- /dev/null
+++ b/lib/librte_hash/rte_thash.c
@@ -0,0 +1,96 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2021 Intel Corporation
+ */
+
+#include <rte_thash.h>
+#include <rte_tailq.h>
+#include <rte_random.h>
+#include <rte_memcpy.h>
+#include <rte_errno.h>
+#include <rte_eal.h>
+#include <rte_eal_memconfig.h>
+#include <rte_malloc.h>
+
+#define THASH_NAME_LEN		64
+
+struct thash_lfsr {
+	uint32_t	ref_cnt;
+	uint32_t	poly;
+	/**< polynomial associated with the lfsr */
+	uint32_t	rev_poly;
+	/**< polynomial to generate the sequence in reverse direction */
+	uint32_t	state;
+	/**< current state of the lfsr */
+	uint32_t	rev_state;
+	/**< current state of the lfsr for reverse direction */
+	uint32_t	deg;	/**< polynomial degree*/
+	uint32_t	bits_cnt;  /**< number of bits generated by lfsr*/
+};
+
+struct rte_thash_subtuple_helper {
+	char	name[THASH_NAME_LEN];	/** < Name of subtuple configuration */
+	LIST_ENTRY(rte_thash_subtuple_helper)	next;
+	struct thash_lfsr	*lfsr;
+	uint32_t	offset;		/** < Offset in bits of the subtuple */
+	uint32_t	len;		/** < Length in bits of the subtuple */
+	uint32_t	lsb_msk;	/** < (1 << reta_sz_log) - 1 */
+	__extension__ uint32_t	compl_table[0] __rte_cache_aligned;
+	/** < Complimentary table */
+};
+
+struct rte_thash_ctx {
+	char		name[THASH_NAME_LEN];
+	LIST_HEAD(, rte_thash_subtuple_helper) head;
+	uint32_t	key_len;	/** < Length of the NIC RSS hash key */
+	uint32_t	reta_sz_log;	/** < size of the RSS ReTa in bits */
+	uint32_t	subtuples_nb;	/** < number of subtuples */
+	uint32_t	flags;
+	uint8_t		hash_key[0];
+};
+
+struct rte_thash_ctx *
+rte_thash_init_ctx(const char *name __rte_unused,
+	uint32_t key_len __rte_unused, uint32_t reta_sz __rte_unused,
+	uint8_t *key __rte_unused, uint32_t flags __rte_unused)
+{
+	return NULL;
+}
+
+struct rte_thash_ctx *
+rte_thash_find_existing(const char *name __rte_unused)
+{
+	return NULL;
+}
+
+void
+rte_thash_free_ctx(struct rte_thash_ctx *ctx __rte_unused)
+{
+}
+
+int
+rte_thash_add_helper(struct rte_thash_ctx *ctx __rte_unused,
+	const char *name __rte_unused, uint32_t len __rte_unused,
+	uint32_t offset __rte_unused)
+{
+	return 0;
+}
+
+struct rte_thash_subtuple_helper *
+rte_thash_get_helper(struct rte_thash_ctx *ctx __rte_unused,
+	const char *name __rte_unused)
+{
+	return NULL;
+}
+
+uint32_t
+rte_thash_get_compliment(struct rte_thash_subtuple_helper *h __rte_unused,
+	uint32_t hash __rte_unused, uint32_t desired_hash __rte_unused)
+{
+	return 0;
+}
+
+const uint8_t *
+rte_thash_get_key(struct rte_thash_ctx *ctx __rte_unused)
+{
+	return NULL;
+}
diff --git a/lib/librte_hash/rte_thash.h b/lib/librte_hash/rte_thash.h
index 061efa2..b30a85c 100644
--- a/lib/librte_hash/rte_thash.h
+++ b/lib/librte_hash/rte_thash.h
@@ -1,5 +1,6 @@
 /* SPDX-License-Identifier: BSD-3-Clause
  * Copyright(c) 2015-2019 Vladimir Medvedkin <medvedkinv@gmail.com>
+ * Copyright(c) 2021 Intel Corporation
  */
 
 #ifndef _RTE_THASH_H
@@ -222,6 +223,143 @@ rte_softrss_be(uint32_t *input_tuple, uint32_t input_len,
 	return ret;
 }
 
+/**
+ * LFSR will ignore if generated m-seqence has more than 2^n -1 bits
+ */
+#define RTE_THASH_IGNORE_PERIOD_OVERFLOW	0x1
+/**
+ * Generate minimal required bit (equal to ReTa LSB) sequence into
+ * the hash_key
+ */
+#define RTE_THASH_MINIMAL_SEQ			0x2
+
+/** @internal thash context structure. */
+struct rte_thash_ctx;
+/** @internal thash helper structure. */
+struct rte_thash_subtuple_helper;
+
+/**
+ * Create a new thash context.
+ *
+ * @param name
+ *  context name
+ * @param key_len
+ *  length of the toeplitz hash key
+ * @param reta_sz
+ *  logarithm of the NIC's Redirection Table (ReTa) size,
+ *  i.e. number of the LSBs if the hash used to determine
+ *  the reta entry.
+ * @param key
+ *  pointer to the key used to init an internal key state.
+ *  Could be NULL, in this case internal key will be inited with random.
+ * @param flags
+ *  supported flags are:
+ *   RTE_THASH_IGNORE_PERIOD_OVERFLOW
+ *   RTE_THASH_MINIMAL_SEQ
+ * @return
+ *  A pointer to the created context on success
+ *  NULL otherwise
+ */
+__rte_experimental
+struct rte_thash_ctx *
+rte_thash_init_ctx(const char *name, uint32_t key_len, uint32_t reta_sz,
+	uint8_t *key, uint32_t flags);
+
+/**
+ * Find an existing thash context and return a pointer to it.
+ *
+ * @param name
+ *  Name of the thash context
+ * @return
+ *  Pointer to the thash context or NULL if it was not found with rte_errno
+ *  set appropriately. Possible rte_errno values include:
+ *   - ENOENT - required entry not available to return.
+ */
+__rte_experimental
+struct rte_thash_ctx *
+rte_thash_find_existing(const char *name);
+
+/**
+ * Free a thash context object
+ *
+ * @param ctx
+ *  thash context
+ * @return
+ *  None
+ */
+__rte_experimental
+void
+rte_thash_free_ctx(struct rte_thash_ctx *ctx);
+
+/**
+ * Add a special properties to the toeplitz hash key inside a thash context.
+ * Creates an internal helper struct which has a complimentary table
+ * to calculate toeplitz hash collisions.
+ *
+ * @param ctx
+ *  thash context
+ * @param name
+ *  name of the helper
+ * @param len
+ *  length in bits of the target subtuple
+ * @param offset
+ *  offset in bits of the subtuple
+ * @return
+ *  0 on success
+ *  negative on error
+ */
+__rte_experimental
+int
+rte_thash_add_helper(struct rte_thash_ctx *ctx, const char *name, uint32_t len,
+	uint32_t offset);
+
+/**
+ * Find a helper in the context by the given name
+ *
+ * @param ctx
+ *  thash context
+ * @param name
+ *  name of the helper
+ * @return
+ *  Pointer to the thash helper or NULL if it was not found.
+ */
+__rte_experimental
+struct rte_thash_subtuple_helper *
+rte_thash_get_helper(struct rte_thash_ctx *ctx, const char *name);
+
+/**
+ * Get a complimentary value for the subtuple to produce a
+ * partial toeplitz hash collision. It muxt be XOR'ed with the
+ * subtuple to produce the hash value with the desired hash LSB's
+ *
+ * @param h
+ *  Pointer to the helper struct
+ * @param hash
+ *  toeplitz hash value calculated for the given tuple
+ * @param desired_hash
+ *  desired hash value to find a collision for
+ * @return
+ *  A complimentary value which must be xored with the corresponding subtuple
+ */
+__rte_experimental
+uint32_t
+rte_thash_get_compliment(struct rte_thash_subtuple_helper *h,
+	uint32_t hash, uint32_t desired_hash);
+
+/**
+ * Get a pointer to the toeplitz hash contained in the context.
+ * It changes after each addition of a helper. It should be installed to
+ * the NIC.
+ *
+ * @param ctx
+ *  thash context
+ * @return
+ *  A pointer to the toeplitz hash key
+ */
+__rte_experimental
+const uint8_t *
+rte_thash_get_key(struct rte_thash_ctx *ctx);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/lib/librte_hash/version.map b/lib/librte_hash/version.map
index c6d7308..93cb230 100644
--- a/lib/librte_hash/version.map
+++ b/lib/librte_hash/version.map
@@ -37,4 +37,11 @@ EXPERIMENTAL {
 	rte_hash_lookup_with_hash_bulk_data;
 	rte_hash_max_key_id;
 	rte_hash_rcu_qsbr_add;
+	rte_thash_add_helper;
+	rte_thash_find_existing;
+	rte_thash_free_ctx;
+	rte_thash_get_compliment;
+	rte_thash_get_helper;
+	rte_thash_get_key;
+	rte_thash_init_ctx;
 };
-- 
2.7.4


^ permalink raw reply	[flat|nested] 45+ messages in thread

* [dpdk-dev] [PATCH v1 2/3] hash: add predictable RSS implementation
  2021-03-16 18:24 [dpdk-dev] [PATCH v1 0/3] Predictable RSS feature Vladimir Medvedkin
  2021-03-16 18:24 ` [dpdk-dev] [PATCH v1 1/3] hash: add predictable RSS API Vladimir Medvedkin
@ 2021-03-16 18:24 ` Vladimir Medvedkin
  2021-03-16 18:24 ` [dpdk-dev] [PATCH v1 3/3] test/hash: add additional thash tests Vladimir Medvedkin
                   ` (4 subsequent siblings)
  6 siblings, 0 replies; 45+ messages in thread
From: Vladimir Medvedkin @ 2021-03-16 18:24 UTC (permalink / raw)
  To: dev
  Cc: konstantin.ananyev, andrey.chilikin, ray.kinsella, yipeng1.wang,
	sameh.gobriel, bruce.richardson

This patch implements predictable RSS functionality.

Signed-off-by: Vladimir Medvedkin <vladimir.medvedkin@intel.com>
---
 lib/librte_hash/rte_thash.c | 532 ++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 516 insertions(+), 16 deletions(-)

diff --git a/lib/librte_hash/rte_thash.c b/lib/librte_hash/rte_thash.c
index 79e8724..d267a7a 100644
--- a/lib/librte_hash/rte_thash.c
+++ b/lib/librte_hash/rte_thash.c
@@ -12,6 +12,45 @@
 #include <rte_malloc.h>
 
 #define THASH_NAME_LEN		64
+#define TOEPLITZ_HASH_LEN	32
+
+#define	RETA_SZ_MIN	2U
+#define	RETA_SZ_MAX	16U
+#define RETA_SZ_IN_RANGE(reta_sz)	((reta_sz >= RETA_SZ_MIN) && \
+					(reta_sz <= RETA_SZ_MAX))
+
+TAILQ_HEAD(rte_thash_list, rte_tailq_entry);
+static struct rte_tailq_elem rte_thash_tailq = {
+	.name = "RTE_THASH",
+};
+EAL_REGISTER_TAILQ(rte_thash_tailq)
+
+/**
+ * Table of some irreducible polinomials over GF(2).
+ * For lfsr they are reperesented in BE bit order, and
+ * x^0 is masked out.
+ * For example, poly x^5 + x^2 + 1 will be represented
+ * as (101001b & 11111b) = 01001b = 0x9
+ */
+static const uint32_t irreducible_poly_table[][4] = {
+	{0, 0, 0, 0},	/** < degree 0 */
+	{1, 1, 1, 1},	/** < degree 1 */
+	{0x3, 0x3, 0x3, 0x3},	/** < degree 2 and so on... */
+	{0x5, 0x3, 0x5, 0x3},
+	{0x9, 0x3, 0x9, 0x3},
+	{0x9, 0x1b, 0xf, 0x5},
+	{0x21, 0x33, 0x1b, 0x2d},
+	{0x41, 0x11, 0x71, 0x9},
+	{0x71, 0xa9, 0xf5, 0x8d},
+	{0x21, 0xd1, 0x69, 0x1d9},
+	{0x81, 0x2c1, 0x3b1, 0x185},
+	{0x201, 0x541, 0x341, 0x461},
+	{0x941, 0x609, 0xe19, 0x45d},
+	{0x1601, 0x1f51, 0x1171, 0x359},
+	{0x2141, 0x2111, 0x2db1, 0x2109},
+	{0x4001, 0x801, 0x101, 0x7301},
+	{0x7781, 0xa011, 0x4211, 0x86d9},
+};
 
 struct thash_lfsr {
 	uint32_t	ref_cnt;
@@ -48,49 +87,510 @@ struct rte_thash_ctx {
 	uint8_t		hash_key[0];
 };
 
+static inline uint32_t
+get_bit_lfsr(struct thash_lfsr *lfsr)
+{
+	uint32_t bit, ret;
+
+	/*
+	 * masking the TAP bits defined by the polynomial and
+	 * calculating parity
+	 */
+	bit = __builtin_popcount(lfsr->state & lfsr->poly) & 0x1;
+	ret = lfsr->state & 0x1;
+	lfsr->state = ((lfsr->state >> 1) | (bit << (lfsr->deg - 1))) &
+		((1 << lfsr->deg) - 1);
+
+	lfsr->bits_cnt++;
+	return ret;
+}
+
+static inline uint32_t
+get_rev_bit_lfsr(struct thash_lfsr *lfsr)
+{
+	uint32_t bit, ret;
+
+	bit = __builtin_popcount(lfsr->rev_state & lfsr->rev_poly) & 0x1;
+	ret = lfsr->rev_state & (1 << (lfsr->deg - 1));
+	lfsr->rev_state = ((lfsr->rev_state << 1) | bit) &
+		((1 << lfsr->deg) - 1);
+
+	lfsr->bits_cnt++;
+	return ret;
+}
+
+static inline uint32_t
+thash_get_rand_poly(uint32_t poly_degree)
+{
+	return irreducible_poly_table[poly_degree][rte_rand() %
+		RTE_DIM(irreducible_poly_table[poly_degree])];
+}
+
+static struct thash_lfsr *
+alloc_lfsr(struct rte_thash_ctx *ctx)
+{
+	struct thash_lfsr *lfsr;
+	uint32_t i;
+
+	if (ctx == NULL)
+		return NULL;
+
+	lfsr = rte_zmalloc(NULL, sizeof(struct thash_lfsr), 0);
+	if (lfsr == NULL)
+		return NULL;
+
+	lfsr->deg = ctx->reta_sz_log;
+	lfsr->poly = thash_get_rand_poly(lfsr->deg);
+	do {
+		lfsr->state = rte_rand() & ((1 << lfsr->deg) - 1);
+	} while (lfsr->state == 0);
+	/* init reverse order polynomial */
+	lfsr->rev_poly = (lfsr->poly >> 1) | (1 << (lfsr->deg - 1));
+	/* init proper rev_state*/
+	lfsr->rev_state = lfsr->state;
+	for (i = 0; i <= lfsr->deg; i++)
+		get_rev_bit_lfsr(lfsr);
+
+	/* clear bits_cnt after rev_state was inited */
+	lfsr->bits_cnt = 0;
+	lfsr->ref_cnt = 1;
+
+	return lfsr;
+}
+
+static void
+attach_lfsr(struct rte_thash_subtuple_helper *h, struct thash_lfsr *lfsr)
+{
+	lfsr->ref_cnt++;
+	h->lfsr = lfsr;
+}
+
+static void
+free_lfsr(struct thash_lfsr *lfsr)
+{
+	lfsr->ref_cnt--;
+	if (lfsr->ref_cnt == 0)
+		rte_free(lfsr);
+}
+
 struct rte_thash_ctx *
-rte_thash_init_ctx(const char *name __rte_unused,
-	uint32_t key_len __rte_unused, uint32_t reta_sz __rte_unused,
-	uint8_t *key __rte_unused, uint32_t flags __rte_unused)
+rte_thash_init_ctx(const char *name, uint32_t key_len, uint32_t reta_sz,
+	uint8_t *key, uint32_t flags)
 {
+	struct rte_thash_ctx *ctx;
+	struct rte_tailq_entry *te;
+	struct rte_thash_list *thash_list;
+	uint32_t i;
+	if ((name == NULL) || (key_len == 0) || !RETA_SZ_IN_RANGE(reta_sz)) {
+		rte_errno = EINVAL;
+		return NULL;
+	}
+
+	thash_list = RTE_TAILQ_CAST(rte_thash_tailq.head, rte_thash_list);
+
+	rte_mcfg_tailq_write_lock();
+
+	/* guarantee there's no existing */
+	TAILQ_FOREACH(te, thash_list, next) {
+		ctx = (struct rte_thash_ctx *)te->data;
+		if (strncmp(name, ctx->name, sizeof(ctx->name)) == 0)
+			break;
+	}
+	ctx = NULL;
+	if (te != NULL) {
+		rte_errno = EEXIST;
+		goto exit;
+	}
+
+	/* allocate tailq entry */
+	te = rte_zmalloc("THASH_TAILQ_ENTRY", sizeof(*te), 0);
+	if (te == NULL) {
+		RTE_LOG(ERR, HASH,
+			"Can not allocate tailq entry for thash context %s\n",
+			name);
+		rte_errno = ENOMEM;
+		goto exit;
+	}
+
+	ctx = rte_zmalloc(NULL, sizeof(struct rte_thash_ctx) + key_len, 0);
+	if (ctx == NULL) {
+		RTE_LOG(ERR, HASH, "thash ctx %s memory allocation failed\n",
+			name);
+		rte_errno = ENOMEM;
+		goto free_te;
+	}
+
+	rte_strlcpy(ctx->name, name, sizeof(ctx->name));
+	ctx->key_len = key_len;
+	ctx->reta_sz_log = reta_sz;
+	LIST_INIT(&ctx->head);
+	ctx->flags = flags;
+
+	if (key)
+		rte_memcpy(ctx->hash_key, key, key_len);
+	else {
+		for (i = 0; i < key_len; i++)
+			ctx->hash_key[i] = rte_rand();
+	}
+
+	te->data = (void *)ctx;
+	TAILQ_INSERT_TAIL(thash_list, te, next);
+
+	rte_mcfg_tailq_write_unlock();
+
+	return ctx;
+free_te:
+	rte_free(te);
+exit:
+	rte_mcfg_tailq_write_unlock();
 	return NULL;
 }
 
 struct rte_thash_ctx *
-rte_thash_find_existing(const char *name __rte_unused)
+rte_thash_find_existing(const char *name)
 {
-	return NULL;
+	struct rte_thash_ctx *ctx;
+	struct rte_tailq_entry *te;
+	struct rte_thash_list *thash_list;
+
+	thash_list = RTE_TAILQ_CAST(rte_thash_tailq.head, rte_thash_list);
+
+	rte_mcfg_tailq_read_lock();
+	TAILQ_FOREACH(te, thash_list, next) {
+		ctx = (struct rte_thash_ctx *)te->data;
+		if (strncmp(name, ctx->name, sizeof(ctx->name)) == 0)
+			break;
+	}
+
+	rte_mcfg_tailq_read_unlock();
+
+	if (te == NULL) {
+		rte_errno = ENOENT;
+		return NULL;
+	}
+
+	return ctx;
 }
 
 void
-rte_thash_free_ctx(struct rte_thash_ctx *ctx __rte_unused)
+rte_thash_free_ctx(struct rte_thash_ctx *ctx)
 {
+	struct rte_tailq_entry *te;
+	struct rte_thash_list *thash_list;
+	struct rte_thash_subtuple_helper *ent, *tmp;
+
+	if (ctx == NULL)
+		return;
+
+	thash_list = RTE_TAILQ_CAST(rte_thash_tailq.head, rte_thash_list);
+	rte_mcfg_tailq_write_lock();
+	TAILQ_FOREACH(te, thash_list, next) {
+		if (te->data == (void *)ctx)
+			break;
+	}
+
+	if (te != NULL)
+		TAILQ_REMOVE(thash_list, te, next);
+
+	rte_mcfg_tailq_write_unlock();
+	ent = LIST_FIRST(&(ctx->head));
+	while (ent) {
+		free_lfsr(ent->lfsr);
+		tmp = ent;
+		ent = LIST_NEXT(ent, next);
+		LIST_REMOVE(tmp, next);
+		rte_free(tmp);
+	}
+
+	rte_free(ctx);
+	rte_free(te);
+}
+
+static inline void
+set_bit(uint8_t *ptr, uint32_t bit, uint32_t pos)
+{
+	uint32_t byte_idx = pos >> 3;
+	uint32_t bit_idx = (CHAR_BIT - 1) - (pos & (CHAR_BIT - 1));
+	uint8_t tmp;
+
+	tmp = ptr[byte_idx];
+	tmp &= ~(1 << bit_idx);
+	tmp |= bit << bit_idx;
+	ptr[byte_idx] = tmp;
+}
+
+/**
+ * writes m-sequence to the hash_key for range [start, end]
+ * (i.e. including start and end positions)
+ */
+static int
+generate_subkey(struct rte_thash_ctx *ctx, struct thash_lfsr *lfsr,
+	uint32_t start, uint32_t end)
+{
+	uint32_t i;
+	uint32_t req_bits = (start < end) ? (end - start) : (start - end);
+	req_bits++; /* due to incuding end */
+
+	/* check if lfsr overflow period of the m-sequence */
+	if (((lfsr->bits_cnt + req_bits) > (1ULL << lfsr->deg) - 1) &&
+			((ctx->flags & RTE_THASH_IGNORE_PERIOD_OVERFLOW) !=
+			RTE_THASH_IGNORE_PERIOD_OVERFLOW))
+		return -ENOSPC;
+
+	if (start < end) {
+		/* original direction (from left to right)*/
+		for (i = start; i <= end; i++)
+			set_bit(ctx->hash_key, get_bit_lfsr(lfsr), i);
+
+	} else {
+		/* reverse direction (from right to left) */
+		for (i = end; i >= start; i--)
+			set_bit(ctx->hash_key, get_rev_bit_lfsr(lfsr), i);
+	}
+
+	return 0;
+}
+
+static inline uint32_t
+get_subvalue(struct rte_thash_ctx *ctx, uint32_t offset)
+{
+	uint32_t *tmp, val;
+
+	tmp = (uint32_t *)(&ctx->hash_key[offset >> 3]);
+	val = rte_be_to_cpu_32(*tmp);
+	val >>= (TOEPLITZ_HASH_LEN - ((offset & (CHAR_BIT - 1)) +
+		ctx->reta_sz_log));
+
+	return val & ((1 << ctx->reta_sz_log) - 1);
+}
+
+static inline void
+generate_compliment_table(struct rte_thash_ctx *ctx,
+	struct rte_thash_subtuple_helper *h)
+{
+	int i, j, k;
+	uint32_t val;
+	uint32_t start;
+
+	start = h->offset + h->len - (2 * ctx->reta_sz_log - 1);
+
+	for (i = 1; i < (1 << ctx->reta_sz_log); i++) {
+		val = 0;
+		for (j = i; j; j &= (j - 1)) {
+			k = rte_bsf32(j);
+			val ^= get_subvalue(ctx, start - k +
+				ctx->reta_sz_log - 1);
+		}
+		h->compl_table[val] = i;
+	}
+}
+
+static inline int
+insert_before(struct rte_thash_ctx *ctx,
+	struct rte_thash_subtuple_helper *ent,
+	struct rte_thash_subtuple_helper *cur_ent,
+	struct rte_thash_subtuple_helper *next_ent,
+	uint32_t start, uint32_t end, uint32_t range_end)
+{
+	int ret;
+
+	if (end < cur_ent->offset) {
+		ent->lfsr = alloc_lfsr(ctx);
+		if (ent->lfsr == NULL) {
+			rte_free(ent);
+			return -ENOMEM;
+		}
+		/* generate nonoverlapping range [start, end) */
+		ret = generate_subkey(ctx, ent->lfsr, start, end - 1);
+		if (ret != 0) {
+			free_lfsr(ent->lfsr);
+			rte_free(ent);
+			return ret;
+		}
+	} else if ((next_ent != NULL) && (end > next_ent->offset)) {
+		rte_free(ent);
+		return -ENOSPC;
+	}
+	attach_lfsr(ent, cur_ent->lfsr);
+
+	/**
+	 * generate partially overlapping range
+	 * [start, cur_ent->start) in reverse order
+	 */
+	ret = generate_subkey(ctx, ent->lfsr, cur_ent->offset - 1, start);
+	if (ret != 0) {
+		free_lfsr(ent->lfsr);
+		rte_free(ent);
+		return ret;
+	}
+
+	if (end > range_end) {
+		/**
+		 * generate partially overlapping range
+		 * (range_end, end)
+		 */
+		ret = generate_subkey(ctx, ent->lfsr, range_end, end - 1);
+		if (ret != 0) {
+			free_lfsr(ent->lfsr);
+			rte_free(ent);
+			return ret;
+		}
+	}
+
+	LIST_INSERT_BEFORE(cur_ent, ent, next);
+	generate_compliment_table(ctx, ent);
+	ctx->subtuples_nb++;
+	return 0;
+}
+
+static inline int
+insert_after(struct rte_thash_ctx *ctx,
+	struct rte_thash_subtuple_helper *ent,
+	struct rte_thash_subtuple_helper *cur_ent,
+	struct rte_thash_subtuple_helper *next_ent,
+	struct rte_thash_subtuple_helper *prev_ent,
+	uint32_t end, uint32_t range_end)
+{
+	int ret;
+
+	if ((next_ent != NULL) && (end > next_ent->offset)) {
+		rte_free(ent);
+		return -EEXIST;
+	}
+
+	attach_lfsr(ent, cur_ent->lfsr);
+	if (end > range_end) {
+		/**
+		 * generate partially overlapping range
+		 * (range_end, end)
+		 */
+		ret = generate_subkey(ctx, ent->lfsr, range_end, end - 1);
+		if (ret != 0) {
+			free_lfsr(ent->lfsr);
+			rte_free(ent);
+			return ret;
+		}
+	}
+
+	LIST_INSERT_AFTER(prev_ent, ent, next);
+	generate_compliment_table(ctx, ent);
+	ctx->subtuples_nb++;
+
+	return 0;
 }
 
 int
-rte_thash_add_helper(struct rte_thash_ctx *ctx __rte_unused,
-	const char *name __rte_unused, uint32_t len __rte_unused,
-	uint32_t offset __rte_unused)
+rte_thash_add_helper(struct rte_thash_ctx *ctx, const char *name, uint32_t len,
+	uint32_t offset)
 {
+	struct rte_thash_subtuple_helper *ent, *cur_ent, *prev_ent, *next_ent;
+	uint32_t start, end;
+	int ret;
+
+	if ((ctx == NULL) || (name == NULL) || (len < ctx->reta_sz_log) ||
+			((offset + len + TOEPLITZ_HASH_LEN - 1) >
+			ctx->key_len * CHAR_BIT))
+		return -EINVAL;
+
+	/* Check for existing name*/
+	LIST_FOREACH(cur_ent, &ctx->head, next) {
+		if (strncmp(name, cur_ent->name, sizeof(cur_ent->name)) == 0)
+			return -EEXIST;
+	}
+
+	end = offset + len + TOEPLITZ_HASH_LEN - 1;
+	start = ((ctx->flags & RTE_THASH_MINIMAL_SEQ) ==
+		RTE_THASH_MINIMAL_SEQ) ? (end - (2 * ctx->reta_sz_log - 1)) :
+		offset;
+
+	ent = rte_zmalloc(NULL, sizeof(struct rte_thash_subtuple_helper) +
+		sizeof(uint32_t) * (1 << ctx->reta_sz_log), 0);
+	if (ent == NULL)
+		return -ENOMEM;
+
+	rte_strlcpy(ent->name, name, sizeof(ent->name));
+	ent->offset = start;
+	ent->len = end - start;
+	ent->lsb_msk = (1 << ctx->reta_sz_log) - 1;
+
+	cur_ent = LIST_FIRST(&ctx->head);
+	while (cur_ent) {
+		uint32_t range_end = cur_ent->offset + cur_ent->len;
+		next_ent = LIST_NEXT(cur_ent, next);
+		prev_ent = cur_ent;
+		/* Iterate through overlapping ranges */
+		while ((next_ent != NULL) && (next_ent->offset < range_end)) {
+			range_end = RTE_MAX(next_ent->offset + next_ent->len,
+				range_end);
+			if (start > next_ent->offset)
+				prev_ent = next_ent;
+
+			next_ent = LIST_NEXT(next_ent, next);
+		}
+
+		if (start < cur_ent->offset)
+			return insert_before(ctx, ent, cur_ent, next_ent,
+				start, end, range_end);
+		else if (start < range_end)
+			return insert_after(ctx, ent, cur_ent, next_ent,
+				prev_ent, end, range_end);
+
+		cur_ent = next_ent;
+		continue;
+	}
+
+	ent->lfsr = alloc_lfsr(ctx);
+	if (ent->lfsr == NULL) {
+		rte_free(ent);
+		return -ENOMEM;
+	}
+
+	/* generate nonoverlapping range [start, end) */
+	ret = generate_subkey(ctx, ent->lfsr, start, end - 1);
+	if (ret != 0) {
+		free_lfsr(ent->lfsr);
+		rte_free(ent);
+		return ret;
+	}
+	if (LIST_EMPTY(&ctx->head)) {
+		LIST_INSERT_HEAD(&ctx->head, ent, next);
+	} else {
+		LIST_FOREACH(next_ent, &ctx->head, next)
+			prev_ent = next_ent;
+
+		LIST_INSERT_AFTER(prev_ent, ent, next);
+	}
+	generate_compliment_table(ctx, ent);
+	ctx->subtuples_nb++;
+
 	return 0;
 }
 
 struct rte_thash_subtuple_helper *
-rte_thash_get_helper(struct rte_thash_ctx *ctx __rte_unused,
-	const char *name __rte_unused)
+rte_thash_get_helper(struct rte_thash_ctx *ctx, const char *name)
 {
+	struct rte_thash_subtuple_helper *ent;
+
+	if ((ctx == NULL) || (name == NULL))
+		return NULL;
+
+	LIST_FOREACH(ent, &ctx->head, next) {
+		if (strncmp(name, ent->name, sizeof(ent->name)) == 0)
+			return ent;
+	}
+
 	return NULL;
 }
 
 uint32_t
-rte_thash_get_compliment(struct rte_thash_subtuple_helper *h __rte_unused,
-	uint32_t hash __rte_unused, uint32_t desired_hash __rte_unused)
+rte_thash_get_compliment(struct rte_thash_subtuple_helper *h,
+	uint32_t hash, uint32_t desired_hash)
 {
-	return 0;
+	return h->compl_table[(hash ^ desired_hash) & h->lsb_msk];
 }
 
 const uint8_t *
-rte_thash_get_key(struct rte_thash_ctx *ctx __rte_unused)
+rte_thash_get_key(struct rte_thash_ctx *ctx)
 {
-	return NULL;
+	return ctx->hash_key;
 }
-- 
2.7.4


^ permalink raw reply	[flat|nested] 45+ messages in thread

* [dpdk-dev] [PATCH v1 3/3] test/hash: add additional thash tests
  2021-03-16 18:24 [dpdk-dev] [PATCH v1 0/3] Predictable RSS feature Vladimir Medvedkin
  2021-03-16 18:24 ` [dpdk-dev] [PATCH v1 1/3] hash: add predictable RSS API Vladimir Medvedkin
  2021-03-16 18:24 ` [dpdk-dev] [PATCH v1 2/3] hash: add predictable RSS implementation Vladimir Medvedkin
@ 2021-03-16 18:24 ` Vladimir Medvedkin
  2021-04-06 19:50 ` [dpdk-dev] [PATCH v2 0/3] Predictable RSS feature Vladimir Medvedkin
                   ` (3 subsequent siblings)
  6 siblings, 0 replies; 45+ messages in thread
From: Vladimir Medvedkin @ 2021-03-16 18:24 UTC (permalink / raw)
  To: dev
  Cc: konstantin.ananyev, andrey.chilikin, ray.kinsella, yipeng1.wang,
	sameh.gobriel, bruce.richardson

This patch adds tests for predictable RSS feature

Signed-off-by: Vladimir Medvedkin <vladimir.medvedkin@intel.com>
---
 app/test/test_thash.c | 383 +++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 377 insertions(+), 6 deletions(-)

diff --git a/app/test/test_thash.c b/app/test/test_thash.c
index a6aadd1..e55c0f7 100644
--- a/app/test/test_thash.c
+++ b/app/test/test_thash.c
@@ -5,11 +5,14 @@
 #include <rte_common.h>
 #include <rte_eal.h>
 #include <rte_ip.h>
+#include <rte_random.h>
 
 #include "test.h"
 
 #include <rte_thash.h>
 
+#define HASH_MSK(reta_sz)	((1 << reta_sz) - 1)
+
 struct test_thash_v4 {
 	uint32_t	dst_ip;
 	uint32_t	src_ip;
@@ -75,7 +78,7 @@ uint8_t default_rss_key[] = {
 };
 
 static int
-test_thash(void)
+test_toeplitz_hash_calc(void)
 {
 	uint32_t i, j;
 	union rte_thash_tuple tuple;
@@ -100,7 +103,7 @@ test_thash(void)
 				RTE_THASH_V4_L4_LEN, default_rss_key);
 		if ((rss_l3 != v4_tbl[i].hash_l3) ||
 				(rss_l3l4 != v4_tbl[i].hash_l3l4))
-			return -1;
+			return -TEST_FAILED;
 		/*Calculate hash with converted key*/
 		rss_l3 = rte_softrss_be((uint32_t *)&tuple,
 				RTE_THASH_V4_L3_LEN, rss_key_be);
@@ -108,7 +111,7 @@ test_thash(void)
 				RTE_THASH_V4_L4_LEN, rss_key_be);
 		if ((rss_l3 != v4_tbl[i].hash_l3) ||
 				(rss_l3l4 != v4_tbl[i].hash_l3l4))
-			return -1;
+			return -TEST_FAILED;
 	}
 	for (i = 0; i < RTE_DIM(v6_tbl); i++) {
 		/*Fill ipv6 hdr*/
@@ -127,7 +130,7 @@ test_thash(void)
 				RTE_THASH_V6_L4_LEN, default_rss_key);
 		if ((rss_l3 != v6_tbl[i].hash_l3) ||
 				(rss_l3l4 != v6_tbl[i].hash_l3l4))
-			return -1;
+			return -TEST_FAILED;
 		/*Calculate hash with converted key*/
 		rss_l3 = rte_softrss_be((uint32_t *)&tuple,
 				RTE_THASH_V6_L3_LEN, rss_key_be);
@@ -135,9 +138,377 @@ test_thash(void)
 				RTE_THASH_V6_L4_LEN, rss_key_be);
 		if ((rss_l3 != v6_tbl[i].hash_l3) ||
 				(rss_l3l4 != v6_tbl[i].hash_l3l4))
-			return -1;
+			return -TEST_FAILED;
 	}
-	return 0;
+	return TEST_SUCCESS;
+}
+
+static int
+test_create_invalid(void)
+{
+	struct rte_thash_ctx *ctx;
+	int key_len = 40;
+	int reta_sz = 7;
+
+	ctx = rte_thash_init_ctx(NULL, key_len, reta_sz, NULL, 0);
+	RTE_TEST_ASSERT(ctx == NULL,
+		"Call succeeded with invalid parameters\n");
+
+	ctx = rte_thash_init_ctx("test", 0, reta_sz, NULL, 0);
+	RTE_TEST_ASSERT(ctx == NULL,
+		"Call succeeded with invalid parameters\n");
+
+	ctx = rte_thash_init_ctx(NULL, key_len, 1, NULL, 0);
+	RTE_TEST_ASSERT(ctx == NULL,
+		"Call succeeded with invalid parameters\n");
+
+	ctx = rte_thash_init_ctx(NULL, key_len, 17, NULL, 0);
+	RTE_TEST_ASSERT(ctx == NULL,
+		"Call succeeded with invalid parameters\n");
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_multiple_create(void)
+{
+	struct rte_thash_ctx *ctx;
+	int key_len = 40;
+	int reta_sz = 7;
+	int i;
+
+	for (i = 0; i < 100; i++) {
+		ctx = rte_thash_init_ctx("test", key_len, reta_sz, NULL, 0);
+		RTE_TEST_ASSERT(ctx != NULL, "Can not create CTX\n");
+
+		rte_thash_free_ctx(ctx);
+	}
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_free_null(void)
+{
+	struct rte_thash_ctx *ctx;
+
+	ctx = rte_thash_init_ctx("test", 40, 7, NULL, 0);
+	RTE_TEST_ASSERT(ctx != NULL, "Can not create CTX\n");
+
+	rte_thash_free_ctx(ctx);
+	rte_thash_free_ctx(NULL);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_add_invalid_helper(void)
+{
+	struct rte_thash_ctx *ctx;
+	const int key_len = 40;
+	int reta_sz = 7;
+	int ret;
+
+	ctx = rte_thash_init_ctx("test", key_len, reta_sz, NULL, 0);
+	RTE_TEST_ASSERT(ctx != NULL, "can not create thash ctx\n");
+
+	ret = rte_thash_add_helper(NULL, "test", reta_sz, 0);
+	RTE_TEST_ASSERT(ret == -EINVAL,
+		"Call succeeded with invalid parameters\n");
+
+	ret = rte_thash_add_helper(ctx, NULL, reta_sz, 0);
+	RTE_TEST_ASSERT(ret == -EINVAL,
+		"Call succeeded with invalid parameters\n");
+
+	ret = rte_thash_add_helper(ctx, "test", reta_sz - 1, 0);
+	RTE_TEST_ASSERT(ret == -EINVAL,
+		"Call succeeded with invalid parameters\n");
+
+	ret = rte_thash_add_helper(ctx, "test", reta_sz, key_len * 8);
+	RTE_TEST_ASSERT(ret == -EINVAL,
+		"Call succeeded with invalid parameters\n");
+
+	ret = rte_thash_add_helper(ctx, "first_range", reta_sz, 0);
+	RTE_TEST_ASSERT(ret == 0, "Can not create helper\n");
+
+	ret = rte_thash_add_helper(ctx, "first_range", reta_sz, 0);
+	RTE_TEST_ASSERT(ret == -EEXIST,
+		"Call succeeded with duplicated name\n");
+
+	/*
+	 * Create second helper with offset 3 * reta_sz.
+	 * Note firts_range helper created range in key:
+	 * [0, 32 + length{= reta_sz} - 1), i.e [0, 37).
+	 * second range is [44, 81)
+	 */
+	ret = rte_thash_add_helper(ctx, "second_range", reta_sz,
+		32 +  2 * reta_sz);
+	RTE_TEST_ASSERT(ret == 0, "Can not create helper\n");
+
+	/*
+	 * Try to create overlapping with first_ and second_ ranges,
+	 * i.e. [6, 49)
+	 */
+	ret = rte_thash_add_helper(ctx, "third_range", 2 * reta_sz, reta_sz);
+	RTE_TEST_ASSERT(ret == -EEXIST,
+		"Call succeeded with overlapping ranges\n");
+
+	rte_thash_free_ctx(ctx);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_find_existing(void)
+{
+	struct rte_thash_ctx *ctx, *ret_ctx;
+
+	ctx = rte_thash_init_ctx("test", 40, 7, NULL, 0);
+	RTE_TEST_ASSERT(ctx != NULL, "can not create thash ctx\n");
+
+	ret_ctx = rte_thash_find_existing("test");
+	RTE_TEST_ASSERT(ret_ctx != NULL, "can not find existing ctx\n");
+
+	rte_thash_free_ctx(ctx);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_get_helper(void)
+{
+	struct rte_thash_ctx *ctx;
+	struct rte_thash_subtuple_helper *h;
+	int ret;
+
+	ctx = rte_thash_init_ctx("test", 40, 7, NULL, 0);
+	RTE_TEST_ASSERT(ctx != NULL, "Can not create thash ctx\n");
+
+	h = rte_thash_get_helper(NULL, "first_range");
+	RTE_TEST_ASSERT(h == NULL, "Call succeeded with invalid parameters\n");
+
+	h = rte_thash_get_helper(ctx, NULL);
+	RTE_TEST_ASSERT(h == NULL, "Call succeeded with invalid parameters\n");
+
+	ret = rte_thash_add_helper(ctx, "first_range", 8, 0);
+	RTE_TEST_ASSERT(ret == 0, "Can not create helper\n");
+
+	h = rte_thash_get_helper(ctx, "first_range");
+	RTE_TEST_ASSERT(h != NULL, "Can not find helper\n");
+
+	rte_thash_free_ctx(ctx);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_period_overflow(void)
+{
+	struct rte_thash_ctx *ctx;
+	int reta_sz = 7; /* reflects polynomial degree */
+	int ret;
+
+	/* first create without RTE_THASH_IGNORE_PERIOD_OVERFLOW flag */
+	ctx = rte_thash_init_ctx("test", 40, reta_sz, NULL, 0);
+	RTE_TEST_ASSERT(ctx != NULL, "Can not create thash ctx\n");
+
+	/* requested range > (2^reta_sz) - 1 */
+	ret = rte_thash_add_helper(ctx, "test", (1 << reta_sz), 0);
+	RTE_TEST_ASSERT(ret == -ENOSPC,
+		"Call succeeded with invalid parameters\n");
+
+	/* requested range == len + 32 - 1, smaller than (2^reta_sz) - 1 */
+	ret = rte_thash_add_helper(ctx, "test", (1 << reta_sz) - 32, 0);
+	RTE_TEST_ASSERT(ret == 0, "Can not create helper\n");
+
+	rte_thash_free_ctx(ctx);
+
+	/* create with RTE_THASH_IGNORE_PERIOD_OVERFLOW flag */
+	ctx = rte_thash_init_ctx("test", 40, reta_sz, NULL,
+		RTE_THASH_IGNORE_PERIOD_OVERFLOW);
+	RTE_TEST_ASSERT(ctx != NULL, "Can not create thash ctx\n");
+
+	/* requested range > (2^reta_sz - 1) */
+	ret = rte_thash_add_helper(ctx, "test", (1 << reta_sz) + 10, 0);
+	RTE_TEST_ASSERT(ret == 0, "Can not create helper\n");
+
+	rte_thash_free_ctx(ctx);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_predictable_rss_min_seq(void)
+{
+	struct rte_thash_ctx *ctx;
+	struct rte_thash_subtuple_helper *h;
+	const int key_len = 40;
+	int reta_sz = 6;
+	uint8_t initial_key[key_len];
+	const uint8_t *new_key;
+	int ret;
+	union rte_thash_tuple tuple;
+	uint32_t orig_hash, adj_hash, adj;
+	unsigned int desired_value = 27 & HASH_MSK(reta_sz);
+	uint16_t port_value = 22;
+
+	memset(initial_key, 0, key_len);
+
+	ctx = rte_thash_init_ctx("test", key_len, reta_sz, initial_key,
+		RTE_THASH_MINIMAL_SEQ);
+	RTE_TEST_ASSERT(ctx != NULL, "can not create thash ctx\n");
+
+	ret = rte_thash_add_helper(ctx, "snat", sizeof(uint16_t) * 8,
+		offsetof(union rte_thash_tuple, v4.sport) * 8);
+	RTE_TEST_ASSERT(ret == 0, "can not add helper, ret %d\n", ret);
+
+	h = rte_thash_get_helper(ctx, "snat");
+	RTE_TEST_ASSERT(h != NULL, "can not find helper\n");
+
+	new_key = rte_thash_get_key(ctx);
+	tuple.v4.src_addr = RTE_IPV4(0, 0, 0, 0);
+	tuple.v4.dst_addr = RTE_IPV4(0, 0, 0, 0);
+	tuple.v4.sport = 0;
+	tuple.v4.sport = rte_cpu_to_be_16(port_value);
+	tuple.v4.dport = 0;
+	tuple.v4.sctp_tag = rte_be_to_cpu_32(tuple.v4.sctp_tag);
+
+	orig_hash = rte_softrss((uint32_t *)&tuple,
+		RTE_THASH_V4_L4_LEN, new_key);
+	adj = rte_thash_get_compliment(h, orig_hash, desired_value);
+
+	tuple.v4.sctp_tag = rte_cpu_to_be_32(tuple.v4.sctp_tag);
+	tuple.v4.sport ^= rte_cpu_to_be_16(adj);
+	tuple.v4.sctp_tag = rte_be_to_cpu_32(tuple.v4.sctp_tag);
+
+	adj_hash = rte_softrss((uint32_t *)&tuple,
+		RTE_THASH_V4_L4_LEN, new_key);
+	RTE_TEST_ASSERT((adj_hash & HASH_MSK(reta_sz)) ==
+		desired_value, "bad desired value\n");
+
+	rte_thash_free_ctx(ctx);
+
+	return TEST_SUCCESS;
+}
+
+/*
+ * This test creates 7 subranges in the folowing order:
+ * range_one	= [56, 95),	len = 8, offset = 56
+ * range_two	= [64, 103),	len = 8, offset = 64
+ * range_three	= [120, 159),	len = 8, offset = 120
+ * range_four	= [48, 87),	len = 8, offset = 48
+ * range_five	= [57, 95),	len = 7, offset = 57
+ * range_six	= [40, 111),	len = 40, offset = 40
+ * range_seven	= [0, 39),	len = 8, offset = 0
+ */
+struct range {
+	const char *name;
+	int len;
+	int offset;
+	int byte_idx;
+};
+
+struct range rng_arr[] = {
+	{"one",   8,  56,  7},
+	{"two",   8,  64,  8},
+	{"three", 8,  120, 15},
+	{"four",  8,  48,  6},
+	{"six",   40, 40,  9},
+	{"five",  7,  57,  7},
+	{"seven", 8,  0,   0}
+};
+
+static int
+test_predictable_rss_multirange(void)
+{
+	struct rte_thash_ctx *ctx;
+	struct rte_thash_subtuple_helper *h[RTE_DIM(rng_arr)];
+	const uint8_t *new_key;
+	const int key_len = 40;
+	int reta_sz = 7;
+	unsigned int i, j, k;
+	int ret;
+	uint32_t desired_value = rte_rand() & HASH_MSK(reta_sz);
+	uint8_t tuples[RTE_DIM(rng_arr)][16] = { {0} };
+	uint32_t *ptr;
+	uint32_t hashes[RTE_DIM(rng_arr)];
+	uint32_t adj_hashes[RTE_DIM(rng_arr)];
+	uint32_t adj;
+
+	ctx = rte_thash_init_ctx("test", key_len, reta_sz, NULL, 0);
+	RTE_TEST_ASSERT(ctx != NULL, "can not create thash ctx\n");
+
+	for (i = 0; i < RTE_DIM(rng_arr); i++) {
+		ret = rte_thash_add_helper(ctx, rng_arr[i].name,
+			rng_arr[i].len, rng_arr[i].offset);
+		RTE_TEST_ASSERT(ret == 0, "can not add helper\n");
+
+		h[i] = rte_thash_get_helper(ctx, rng_arr[i].name);
+		RTE_TEST_ASSERT(h[i] != NULL, "can not find helper\n");
+	}
+	new_key = rte_thash_get_key(ctx);
+
+	/*
+	 * calculate hashes, compliments, then adjust keys with
+	 * compliments and recalsulate hashes
+	 */
+	for (i = 0; i < RTE_DIM(rng_arr); i++) {
+		for (k = 0; k < 100; k++) {
+			/* init with random keys */
+			ptr = (uint32_t *)&tuples[i][0];
+			for (j = 0; j < 4; j++)
+				ptr[j] = rte_rand();
+			/* convert keys from BE to CPU byte order */
+			for (j = 0; j < 4; j++)
+				ptr[j] = rte_be_to_cpu_32(ptr[j]);
+
+			hashes[i] = rte_softrss(ptr, 4, new_key);
+			adj = rte_thash_get_compliment(h[i], hashes[i],
+				desired_value);
+			/* convert back to BE to adjust the value */
+			for (j = 0; j < 4; j++)
+				ptr[j] = rte_cpu_to_be_32(ptr[j]);
+
+			tuples[i][rng_arr[i].byte_idx] ^= adj;
+
+			for (j = 0; j < 4; j++)
+				ptr[j] = rte_be_to_cpu_32(ptr[j]);
+
+			adj_hashes[i] = rte_softrss(ptr, 4, new_key);
+			RTE_TEST_ASSERT((adj_hashes[i] & HASH_MSK(reta_sz)) ==
+				desired_value,
+				"bad desired value for %d tuple\n", i);
+		}
+	}
+
+	rte_thash_free_ctx(ctx);
+
+	return TEST_SUCCESS;
+}
+
+static struct unit_test_suite thash_tests = {
+	.suite_name = "thash autotest",
+	.setup = NULL,
+	.teardown = NULL,
+	.unit_test_cases = {
+	TEST_CASE(test_toeplitz_hash_calc),
+	TEST_CASE(test_create_invalid),
+	TEST_CASE(test_multiple_create),
+	TEST_CASE(test_free_null),
+	TEST_CASE(test_add_invalid_helper),
+	TEST_CASE(test_find_existing),
+	TEST_CASE(test_get_helper),
+	TEST_CASE(test_period_overflow),
+	TEST_CASE(test_predictable_rss_min_seq),
+	TEST_CASE(test_predictable_rss_multirange),
+	TEST_CASES_END()
+	}
+};
+
+static int
+test_thash(void)
+{
+	return unit_test_suite_runner(&thash_tests);
 }
 
 REGISTER_TEST_COMMAND(thash_autotest, test_thash);
-- 
2.7.4


^ permalink raw reply	[flat|nested] 45+ messages in thread

* [dpdk-dev] [PATCH v2 0/3] Predictable RSS feature
  2021-03-16 18:24 [dpdk-dev] [PATCH v1 0/3] Predictable RSS feature Vladimir Medvedkin
                   ` (2 preceding siblings ...)
  2021-03-16 18:24 ` [dpdk-dev] [PATCH v1 3/3] test/hash: add additional thash tests Vladimir Medvedkin
@ 2021-04-06 19:50 ` Vladimir Medvedkin
  2021-04-08 15:56   ` Stephen Hemminger
                     ` (5 more replies)
  2021-04-06 19:50 ` [dpdk-dev] [PATCH v2 1/3] hash: add predictable RSS API Vladimir Medvedkin
                   ` (2 subsequent siblings)
  6 siblings, 6 replies; 45+ messages in thread
From: Vladimir Medvedkin @ 2021-04-06 19:50 UTC (permalink / raw)
  To: dev
  Cc: konstantin.ananyev, andrey.chilikin, ray.kinsella, yipeng1.wang,
	sameh.gobriel, bruce.richardson

This patch series introduces predictable RSS feature.
It is based on the idea of searching for partial hash collisions
within Toeplitz hash.

The Toeplitz hash function is a homomorphism between (G, ^) and (H, ^),
where (G, ^) - is a group of tuples and (H, ^) is a group of hashes
with respect to XOR operation. So tuples and hashes could be treated as
n-dimension and 32-dimension vector spaces over GF(2).
So, f(x ^ y) == f(x) ^ f(y)
where f - is the toeplitz hash function and x, y are tuples.

The ability to predict partial collisions allows user to compute
input hash value with desired LSB values.
Usually number of LSB's are defined by the size of RSS Redirection Table.

There could be number of use cases, for example:
1) NAT. Using this library it is possible to select a new port number
on a translation in the way that rss hash for original tuple will have
the same LSB's as rss hash for reverse tuple.
2) IPSec/MPLS/Vxlan. It is possible to choose tunnel id to be pinned to
a desired queue.
3) TCP stack. It is possible to choose a source port number for outgoing
connections in the way that received replies will be assigned to
desired queue.
4) RSS hash key generation. Hash key initialization with random values
does not guarantee an uniform distribution amongst queues. This library
uses mathematically proved algorithm to complete the rss hash key to
provide the best distribution.

v2:
- added extra API rte_thash_adjust_tuple()
- added extra tests for rte_thash_adjust_tuple()
- added extra fields to rte_thash_subtuple_helper struct
- fixed typos 

Vladimir Medvedkin (3):
  hash: add predictable RSS API
  hash: add predictable RSS implementation
  test/hash: add additional thash tests

 app/test/test_thash.c       | 468 +++++++++++++++++++++++++++++++-
 lib/librte_hash/meson.build |   3 +-
 lib/librte_hash/rte_thash.c | 637 ++++++++++++++++++++++++++++++++++++++++++++
 lib/librte_hash/rte_thash.h | 180 +++++++++++++
 lib/librte_hash/version.map |   8 +
 5 files changed, 1289 insertions(+), 7 deletions(-)
 create mode 100644 lib/librte_hash/rte_thash.c

-- 
2.7.4


^ permalink raw reply	[flat|nested] 45+ messages in thread

* [dpdk-dev] [PATCH v2 1/3] hash: add predictable RSS API
  2021-03-16 18:24 [dpdk-dev] [PATCH v1 0/3] Predictable RSS feature Vladimir Medvedkin
                   ` (3 preceding siblings ...)
  2021-04-06 19:50 ` [dpdk-dev] [PATCH v2 0/3] Predictable RSS feature Vladimir Medvedkin
@ 2021-04-06 19:50 ` Vladimir Medvedkin
  2021-04-10  0:05   ` Wang, Yipeng1
  2021-04-06 19:50 ` [dpdk-dev] [PATCH v2 2/3] hash: add predictable RSS implementation Vladimir Medvedkin
  2021-04-06 19:50 ` [dpdk-dev] [PATCH v2 3/3] test/hash: add additional thash tests Vladimir Medvedkin
  6 siblings, 1 reply; 45+ messages in thread
From: Vladimir Medvedkin @ 2021-04-06 19:50 UTC (permalink / raw)
  To: dev
  Cc: konstantin.ananyev, andrey.chilikin, ray.kinsella, yipeng1.wang,
	sameh.gobriel, bruce.richardson

This patch adds predictable RSS API.
It is based on the idea of searching partial Toeplitz hash collisions.

Signed-off-by: Vladimir Medvedkin <vladimir.medvedkin@intel.com>
---
 lib/librte_hash/meson.build |   3 +-
 lib/librte_hash/rte_thash.c |  96 ++++++++++++++++++++++++++++++
 lib/librte_hash/rte_thash.h | 138 ++++++++++++++++++++++++++++++++++++++++++++
 lib/librte_hash/version.map |   7 +++
 4 files changed, 243 insertions(+), 1 deletion(-)
 create mode 100644 lib/librte_hash/rte_thash.c

diff --git a/lib/librte_hash/meson.build b/lib/librte_hash/meson.build
index 242859f..3546014 100644
--- a/lib/librte_hash/meson.build
+++ b/lib/librte_hash/meson.build
@@ -8,6 +8,7 @@ headers = files('rte_fbk_hash.h',
 	'rte_thash.h')
 indirect_headers += files('rte_crc_arm64.h')
 
-sources = files('rte_cuckoo_hash.c', 'rte_fbk_hash.c')
+sources = files('rte_cuckoo_hash.c', 'rte_fbk_hash.c', 'rte_thash.c')
+deps += ['net']
 deps += ['ring']
 deps += ['rcu']
diff --git a/lib/librte_hash/rte_thash.c b/lib/librte_hash/rte_thash.c
new file mode 100644
index 0000000..79e8724
--- /dev/null
+++ b/lib/librte_hash/rte_thash.c
@@ -0,0 +1,96 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2021 Intel Corporation
+ */
+
+#include <rte_thash.h>
+#include <rte_tailq.h>
+#include <rte_random.h>
+#include <rte_memcpy.h>
+#include <rte_errno.h>
+#include <rte_eal.h>
+#include <rte_eal_memconfig.h>
+#include <rte_malloc.h>
+
+#define THASH_NAME_LEN		64
+
+struct thash_lfsr {
+	uint32_t	ref_cnt;
+	uint32_t	poly;
+	/**< polynomial associated with the lfsr */
+	uint32_t	rev_poly;
+	/**< polynomial to generate the sequence in reverse direction */
+	uint32_t	state;
+	/**< current state of the lfsr */
+	uint32_t	rev_state;
+	/**< current state of the lfsr for reverse direction */
+	uint32_t	deg;	/**< polynomial degree*/
+	uint32_t	bits_cnt;  /**< number of bits generated by lfsr*/
+};
+
+struct rte_thash_subtuple_helper {
+	char	name[THASH_NAME_LEN];	/** < Name of subtuple configuration */
+	LIST_ENTRY(rte_thash_subtuple_helper)	next;
+	struct thash_lfsr	*lfsr;
+	uint32_t	offset;		/** < Offset in bits of the subtuple */
+	uint32_t	len;		/** < Length in bits of the subtuple */
+	uint32_t	lsb_msk;	/** < (1 << reta_sz_log) - 1 */
+	__extension__ uint32_t	compl_table[0] __rte_cache_aligned;
+	/** < Complimentary table */
+};
+
+struct rte_thash_ctx {
+	char		name[THASH_NAME_LEN];
+	LIST_HEAD(, rte_thash_subtuple_helper) head;
+	uint32_t	key_len;	/** < Length of the NIC RSS hash key */
+	uint32_t	reta_sz_log;	/** < size of the RSS ReTa in bits */
+	uint32_t	subtuples_nb;	/** < number of subtuples */
+	uint32_t	flags;
+	uint8_t		hash_key[0];
+};
+
+struct rte_thash_ctx *
+rte_thash_init_ctx(const char *name __rte_unused,
+	uint32_t key_len __rte_unused, uint32_t reta_sz __rte_unused,
+	uint8_t *key __rte_unused, uint32_t flags __rte_unused)
+{
+	return NULL;
+}
+
+struct rte_thash_ctx *
+rte_thash_find_existing(const char *name __rte_unused)
+{
+	return NULL;
+}
+
+void
+rte_thash_free_ctx(struct rte_thash_ctx *ctx __rte_unused)
+{
+}
+
+int
+rte_thash_add_helper(struct rte_thash_ctx *ctx __rte_unused,
+	const char *name __rte_unused, uint32_t len __rte_unused,
+	uint32_t offset __rte_unused)
+{
+	return 0;
+}
+
+struct rte_thash_subtuple_helper *
+rte_thash_get_helper(struct rte_thash_ctx *ctx __rte_unused,
+	const char *name __rte_unused)
+{
+	return NULL;
+}
+
+uint32_t
+rte_thash_get_compliment(struct rte_thash_subtuple_helper *h __rte_unused,
+	uint32_t hash __rte_unused, uint32_t desired_hash __rte_unused)
+{
+	return 0;
+}
+
+const uint8_t *
+rte_thash_get_key(struct rte_thash_ctx *ctx __rte_unused)
+{
+	return NULL;
+}
diff --git a/lib/librte_hash/rte_thash.h b/lib/librte_hash/rte_thash.h
index 061efa2..38a641b 100644
--- a/lib/librte_hash/rte_thash.h
+++ b/lib/librte_hash/rte_thash.h
@@ -1,5 +1,6 @@
 /* SPDX-License-Identifier: BSD-3-Clause
  * Copyright(c) 2015-2019 Vladimir Medvedkin <medvedkinv@gmail.com>
+ * Copyright(c) 2021 Intel Corporation
  */
 
 #ifndef _RTE_THASH_H
@@ -222,6 +223,143 @@ rte_softrss_be(uint32_t *input_tuple, uint32_t input_len,
 	return ret;
 }
 
+/**
+ * LFSR will ignore if generated m-sequence has more than 2^n -1 bits
+ */
+#define RTE_THASH_IGNORE_PERIOD_OVERFLOW	0x1
+/**
+ * Generate minimal required bit (equal to ReTa LSB) sequence into
+ * the hash_key
+ */
+#define RTE_THASH_MINIMAL_SEQ			0x2
+
+/** @internal thash context structure. */
+struct rte_thash_ctx;
+/** @internal thash helper structure. */
+struct rte_thash_subtuple_helper;
+
+/**
+ * Create a new thash context.
+ *
+ * @param name
+ *  context name
+ * @param key_len
+ *  length of the toeplitz hash key
+ * @param reta_sz
+ *  logarithm of the NIC's Redirection Table (ReTa) size,
+ *  i.e. number of the LSBs if the hash used to determine
+ *  the reta entry.
+ * @param key
+ *  pointer to the key used to init an internal key state.
+ *  Could be NULL, in this case internal key will be inited with random.
+ * @param flags
+ *  supported flags are:
+ *   RTE_THASH_IGNORE_PERIOD_OVERFLOW
+ *   RTE_THASH_MINIMAL_SEQ
+ * @return
+ *  A pointer to the created context on success
+ *  NULL otherwise
+ */
+__rte_experimental
+struct rte_thash_ctx *
+rte_thash_init_ctx(const char *name, uint32_t key_len, uint32_t reta_sz,
+	uint8_t *key, uint32_t flags);
+
+/**
+ * Find an existing thash context and return a pointer to it.
+ *
+ * @param name
+ *  Name of the thash context
+ * @return
+ *  Pointer to the thash context or NULL if it was not found with rte_errno
+ *  set appropriately. Possible rte_errno values include:
+ *   - ENOENT - required entry not available to return.
+ */
+__rte_experimental
+struct rte_thash_ctx *
+rte_thash_find_existing(const char *name);
+
+/**
+ * Free a thash context object
+ *
+ * @param ctx
+ *  thash context
+ * @return
+ *  None
+ */
+__rte_experimental
+void
+rte_thash_free_ctx(struct rte_thash_ctx *ctx);
+
+/**
+ * Add a special properties to the toeplitz hash key inside a thash context.
+ * Creates an internal helper struct which has a complimentary table
+ * to calculate toeplitz hash collisions.
+ *
+ * @param ctx
+ *  thash context
+ * @param name
+ *  name of the helper
+ * @param len
+ *  length in bits of the target subtuple
+ * @param offset
+ *  offset in bits of the subtuple
+ * @return
+ *  0 on success
+ *  negative on error
+ */
+__rte_experimental
+int
+rte_thash_add_helper(struct rte_thash_ctx *ctx, const char *name, uint32_t len,
+	uint32_t offset);
+
+/**
+ * Find a helper in the context by the given name
+ *
+ * @param ctx
+ *  thash context
+ * @param name
+ *  name of the helper
+ * @return
+ *  Pointer to the thash helper or NULL if it was not found.
+ */
+__rte_experimental
+struct rte_thash_subtuple_helper *
+rte_thash_get_helper(struct rte_thash_ctx *ctx, const char *name);
+
+/**
+ * Get a complimentary value for the subtuple to produce a
+ * partial toeplitz hash collision. It muxt be XOR'ed with the
+ * subtuple to produce the hash value with the desired hash LSB's
+ *
+ * @param h
+ *  Pointer to the helper struct
+ * @param hash
+ *  toeplitz hash value calculated for the given tuple
+ * @param desired_hash
+ *  desired hash value to find a collision for
+ * @return
+ *  A complimentary value which must be xored with the corresponding subtuple
+ */
+__rte_experimental
+uint32_t
+rte_thash_get_compliment(struct rte_thash_subtuple_helper *h,
+	uint32_t hash, uint32_t desired_hash);
+
+/**
+ * Get a pointer to the toeplitz hash contained in the context.
+ * It changes after each addition of a helper. It should be installed to
+ * the NIC.
+ *
+ * @param ctx
+ *  thash context
+ * @return
+ *  A pointer to the toeplitz hash key
+ */
+__rte_experimental
+const uint8_t *
+rte_thash_get_key(struct rte_thash_ctx *ctx);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/lib/librte_hash/version.map b/lib/librte_hash/version.map
index c6d7308..93cb230 100644
--- a/lib/librte_hash/version.map
+++ b/lib/librte_hash/version.map
@@ -37,4 +37,11 @@ EXPERIMENTAL {
 	rte_hash_lookup_with_hash_bulk_data;
 	rte_hash_max_key_id;
 	rte_hash_rcu_qsbr_add;
+	rte_thash_add_helper;
+	rte_thash_find_existing;
+	rte_thash_free_ctx;
+	rte_thash_get_compliment;
+	rte_thash_get_helper;
+	rte_thash_get_key;
+	rte_thash_init_ctx;
 };
-- 
2.7.4


^ permalink raw reply	[flat|nested] 45+ messages in thread

* [dpdk-dev] [PATCH v2 2/3] hash: add predictable RSS implementation
  2021-03-16 18:24 [dpdk-dev] [PATCH v1 0/3] Predictable RSS feature Vladimir Medvedkin
                   ` (4 preceding siblings ...)
  2021-04-06 19:50 ` [dpdk-dev] [PATCH v2 1/3] hash: add predictable RSS API Vladimir Medvedkin
@ 2021-04-06 19:50 ` Vladimir Medvedkin
  2021-04-07 12:53   ` Ananyev, Konstantin
  2021-04-10  0:10   ` Wang, Yipeng1
  2021-04-06 19:50 ` [dpdk-dev] [PATCH v2 3/3] test/hash: add additional thash tests Vladimir Medvedkin
  6 siblings, 2 replies; 45+ messages in thread
From: Vladimir Medvedkin @ 2021-04-06 19:50 UTC (permalink / raw)
  To: dev
  Cc: konstantin.ananyev, andrey.chilikin, ray.kinsella, yipeng1.wang,
	sameh.gobriel, bruce.richardson

This patch implements predictable RSS functionality.

Signed-off-by: Vladimir Medvedkin <vladimir.medvedkin@intel.com>
---
 lib/librte_hash/rte_thash.c | 577 ++++++++++++++++++++++++++++++++++++++++++--
 lib/librte_hash/rte_thash.h |  42 ++++
 lib/librte_hash/version.map |   1 +
 3 files changed, 602 insertions(+), 18 deletions(-)

diff --git a/lib/librte_hash/rte_thash.c b/lib/librte_hash/rte_thash.c
index 79e8724..cc60ada 100644
--- a/lib/librte_hash/rte_thash.c
+++ b/lib/librte_hash/rte_thash.c
@@ -12,6 +12,45 @@
 #include <rte_malloc.h>
 
 #define THASH_NAME_LEN		64
+#define TOEPLITZ_HASH_LEN	32
+
+#define	RETA_SZ_MIN	2U
+#define	RETA_SZ_MAX	16U
+#define RETA_SZ_IN_RANGE(reta_sz)	((reta_sz >= RETA_SZ_MIN) && \
+					(reta_sz <= RETA_SZ_MAX))
+
+TAILQ_HEAD(rte_thash_list, rte_tailq_entry);
+static struct rte_tailq_elem rte_thash_tailq = {
+	.name = "RTE_THASH",
+};
+EAL_REGISTER_TAILQ(rte_thash_tailq)
+
+/**
+ * Table of some irreducible polinomials over GF(2).
+ * For lfsr they are reperesented in BE bit order, and
+ * x^0 is masked out.
+ * For example, poly x^5 + x^2 + 1 will be represented
+ * as (101001b & 11111b) = 01001b = 0x9
+ */
+static const uint32_t irreducible_poly_table[][4] = {
+	{0, 0, 0, 0},	/** < degree 0 */
+	{1, 1, 1, 1},	/** < degree 1 */
+	{0x3, 0x3, 0x3, 0x3},	/** < degree 2 and so on... */
+	{0x5, 0x3, 0x5, 0x3},
+	{0x9, 0x3, 0x9, 0x3},
+	{0x9, 0x1b, 0xf, 0x5},
+	{0x21, 0x33, 0x1b, 0x2d},
+	{0x41, 0x11, 0x71, 0x9},
+	{0x71, 0xa9, 0xf5, 0x8d},
+	{0x21, 0xd1, 0x69, 0x1d9},
+	{0x81, 0x2c1, 0x3b1, 0x185},
+	{0x201, 0x541, 0x341, 0x461},
+	{0x941, 0x609, 0xe19, 0x45d},
+	{0x1601, 0x1f51, 0x1171, 0x359},
+	{0x2141, 0x2111, 0x2db1, 0x2109},
+	{0x4001, 0x801, 0x101, 0x7301},
+	{0x7781, 0xa011, 0x4211, 0x86d9},
+};
 
 struct thash_lfsr {
 	uint32_t	ref_cnt;
@@ -31,8 +70,10 @@ struct rte_thash_subtuple_helper {
 	char	name[THASH_NAME_LEN];	/** < Name of subtuple configuration */
 	LIST_ENTRY(rte_thash_subtuple_helper)	next;
 	struct thash_lfsr	*lfsr;
-	uint32_t	offset;		/** < Offset in bits of the subtuple */
-	uint32_t	len;		/** < Length in bits of the subtuple */
+	uint32_t	offset;		/** < Offset of the m-sequence */
+	uint32_t	len;		/** < Length of the m-sequence */
+	uint32_t	tuple_offset;	/** < Offset in bits of the subtuple */
+	uint32_t	tuple_len;	/** < Length in bits of the subtuple */
 	uint32_t	lsb_msk;	/** < (1 << reta_sz_log) - 1 */
 	__extension__ uint32_t	compl_table[0] __rte_cache_aligned;
 	/** < Complimentary table */
@@ -48,49 +89,549 @@ struct rte_thash_ctx {
 	uint8_t		hash_key[0];
 };
 
+static inline uint32_t
+get_bit_lfsr(struct thash_lfsr *lfsr)
+{
+	uint32_t bit, ret;
+
+	/*
+	 * masking the TAP bits defined by the polynomial and
+	 * calculating parity
+	 */
+	bit = __builtin_popcount(lfsr->state & lfsr->poly) & 0x1;
+	ret = lfsr->state & 0x1;
+	lfsr->state = ((lfsr->state >> 1) | (bit << (lfsr->deg - 1))) &
+		((1 << lfsr->deg) - 1);
+
+	lfsr->bits_cnt++;
+	return ret;
+}
+
+static inline uint32_t
+get_rev_bit_lfsr(struct thash_lfsr *lfsr)
+{
+	uint32_t bit, ret;
+
+	bit = __builtin_popcount(lfsr->rev_state & lfsr->rev_poly) & 0x1;
+	ret = lfsr->rev_state & (1 << (lfsr->deg - 1));
+	lfsr->rev_state = ((lfsr->rev_state << 1) | bit) &
+		((1 << lfsr->deg) - 1);
+
+	lfsr->bits_cnt++;
+	return ret;
+}
+
+static inline uint32_t
+thash_get_rand_poly(uint32_t poly_degree)
+{
+	return irreducible_poly_table[poly_degree][rte_rand() %
+		RTE_DIM(irreducible_poly_table[poly_degree])];
+}
+
+static struct thash_lfsr *
+alloc_lfsr(struct rte_thash_ctx *ctx)
+{
+	struct thash_lfsr *lfsr;
+	uint32_t i;
+
+	if (ctx == NULL)
+		return NULL;
+
+	lfsr = rte_zmalloc(NULL, sizeof(struct thash_lfsr), 0);
+	if (lfsr == NULL)
+		return NULL;
+
+	lfsr->deg = ctx->reta_sz_log;
+	lfsr->poly = thash_get_rand_poly(lfsr->deg);
+	do {
+		lfsr->state = rte_rand() & ((1 << lfsr->deg) - 1);
+	} while (lfsr->state == 0);
+	/* init reverse order polynomial */
+	lfsr->rev_poly = (lfsr->poly >> 1) | (1 << (lfsr->deg - 1));
+	/* init proper rev_state*/
+	lfsr->rev_state = lfsr->state;
+	for (i = 0; i <= lfsr->deg; i++)
+		get_rev_bit_lfsr(lfsr);
+
+	/* clear bits_cnt after rev_state was inited */
+	lfsr->bits_cnt = 0;
+	lfsr->ref_cnt = 1;
+
+	return lfsr;
+}
+
+static void
+attach_lfsr(struct rte_thash_subtuple_helper *h, struct thash_lfsr *lfsr)
+{
+	lfsr->ref_cnt++;
+	h->lfsr = lfsr;
+}
+
+static void
+free_lfsr(struct thash_lfsr *lfsr)
+{
+	lfsr->ref_cnt--;
+	if (lfsr->ref_cnt == 0)
+		rte_free(lfsr);
+}
+
 struct rte_thash_ctx *
-rte_thash_init_ctx(const char *name __rte_unused,
-	uint32_t key_len __rte_unused, uint32_t reta_sz __rte_unused,
-	uint8_t *key __rte_unused, uint32_t flags __rte_unused)
+rte_thash_init_ctx(const char *name, uint32_t key_len, uint32_t reta_sz,
+	uint8_t *key, uint32_t flags)
 {
+	struct rte_thash_ctx *ctx;
+	struct rte_tailq_entry *te;
+	struct rte_thash_list *thash_list;
+	uint32_t i;
+	if ((name == NULL) || (key_len == 0) || !RETA_SZ_IN_RANGE(reta_sz)) {
+		rte_errno = EINVAL;
+		return NULL;
+	}
+
+	thash_list = RTE_TAILQ_CAST(rte_thash_tailq.head, rte_thash_list);
+
+	rte_mcfg_tailq_write_lock();
+
+	/* guarantee there's no existing */
+	TAILQ_FOREACH(te, thash_list, next) {
+		ctx = (struct rte_thash_ctx *)te->data;
+		if (strncmp(name, ctx->name, sizeof(ctx->name)) == 0)
+			break;
+	}
+	ctx = NULL;
+	if (te != NULL) {
+		rte_errno = EEXIST;
+		goto exit;
+	}
+
+	/* allocate tailq entry */
+	te = rte_zmalloc("THASH_TAILQ_ENTRY", sizeof(*te), 0);
+	if (te == NULL) {
+		RTE_LOG(ERR, HASH,
+			"Can not allocate tailq entry for thash context %s\n",
+			name);
+		rte_errno = ENOMEM;
+		goto exit;
+	}
+
+	ctx = rte_zmalloc(NULL, sizeof(struct rte_thash_ctx) + key_len, 0);
+	if (ctx == NULL) {
+		RTE_LOG(ERR, HASH, "thash ctx %s memory allocation failed\n",
+			name);
+		rte_errno = ENOMEM;
+		goto free_te;
+	}
+
+	rte_strlcpy(ctx->name, name, sizeof(ctx->name));
+	ctx->key_len = key_len;
+	ctx->reta_sz_log = reta_sz;
+	LIST_INIT(&ctx->head);
+	ctx->flags = flags;
+
+	if (key)
+		rte_memcpy(ctx->hash_key, key, key_len);
+	else {
+		for (i = 0; i < key_len; i++)
+			ctx->hash_key[i] = rte_rand();
+	}
+
+	te->data = (void *)ctx;
+	TAILQ_INSERT_TAIL(thash_list, te, next);
+
+	rte_mcfg_tailq_write_unlock();
+
+	return ctx;
+free_te:
+	rte_free(te);
+exit:
+	rte_mcfg_tailq_write_unlock();
 	return NULL;
 }
 
 struct rte_thash_ctx *
-rte_thash_find_existing(const char *name __rte_unused)
+rte_thash_find_existing(const char *name)
 {
-	return NULL;
+	struct rte_thash_ctx *ctx;
+	struct rte_tailq_entry *te;
+	struct rte_thash_list *thash_list;
+
+	thash_list = RTE_TAILQ_CAST(rte_thash_tailq.head, rte_thash_list);
+
+	rte_mcfg_tailq_read_lock();
+	TAILQ_FOREACH(te, thash_list, next) {
+		ctx = (struct rte_thash_ctx *)te->data;
+		if (strncmp(name, ctx->name, sizeof(ctx->name)) == 0)
+			break;
+	}
+
+	rte_mcfg_tailq_read_unlock();
+
+	if (te == NULL) {
+		rte_errno = ENOENT;
+		return NULL;
+	}
+
+	return ctx;
 }
 
 void
-rte_thash_free_ctx(struct rte_thash_ctx *ctx __rte_unused)
+rte_thash_free_ctx(struct rte_thash_ctx *ctx)
 {
+	struct rte_tailq_entry *te;
+	struct rte_thash_list *thash_list;
+	struct rte_thash_subtuple_helper *ent, *tmp;
+
+	if (ctx == NULL)
+		return;
+
+	thash_list = RTE_TAILQ_CAST(rte_thash_tailq.head, rte_thash_list);
+	rte_mcfg_tailq_write_lock();
+	TAILQ_FOREACH(te, thash_list, next) {
+		if (te->data == (void *)ctx)
+			break;
+	}
+
+	if (te != NULL)
+		TAILQ_REMOVE(thash_list, te, next);
+
+	rte_mcfg_tailq_write_unlock();
+	ent = LIST_FIRST(&(ctx->head));
+	while (ent) {
+		free_lfsr(ent->lfsr);
+		tmp = ent;
+		ent = LIST_NEXT(ent, next);
+		LIST_REMOVE(tmp, next);
+		rte_free(tmp);
+	}
+
+	rte_free(ctx);
+	rte_free(te);
+}
+
+static inline void
+set_bit(uint8_t *ptr, uint32_t bit, uint32_t pos)
+{
+	uint32_t byte_idx = pos >> 3;
+	uint32_t bit_idx = (CHAR_BIT - 1) - (pos & (CHAR_BIT - 1));
+	uint8_t tmp;
+
+	tmp = ptr[byte_idx];
+	tmp &= ~(1 << bit_idx);
+	tmp |= bit << bit_idx;
+	ptr[byte_idx] = tmp;
+}
+
+/**
+ * writes m-sequence to the hash_key for range [start, end]
+ * (i.e. including start and end positions)
+ */
+static int
+generate_subkey(struct rte_thash_ctx *ctx, struct thash_lfsr *lfsr,
+	uint32_t start, uint32_t end)
+{
+	uint32_t i;
+	uint32_t req_bits = (start < end) ? (end - start) : (start - end);
+	req_bits++; /* due to incuding end */
+
+	/* check if lfsr overflow period of the m-sequence */
+	if (((lfsr->bits_cnt + req_bits) > (1ULL << lfsr->deg) - 1) &&
+			((ctx->flags & RTE_THASH_IGNORE_PERIOD_OVERFLOW) !=
+			RTE_THASH_IGNORE_PERIOD_OVERFLOW))
+		return -ENOSPC;
+
+	if (start < end) {
+		/* original direction (from left to right)*/
+		for (i = start; i <= end; i++)
+			set_bit(ctx->hash_key, get_bit_lfsr(lfsr), i);
+
+	} else {
+		/* reverse direction (from right to left) */
+		for (i = end; i >= start; i--)
+			set_bit(ctx->hash_key, get_rev_bit_lfsr(lfsr), i);
+	}
+
+	return 0;
+}
+
+static inline uint32_t
+get_subvalue(struct rte_thash_ctx *ctx, uint32_t offset)
+{
+	uint32_t *tmp, val;
+
+	tmp = (uint32_t *)(&ctx->hash_key[offset >> 3]);
+	val = rte_be_to_cpu_32(*tmp);
+	val >>= (TOEPLITZ_HASH_LEN - ((offset & (CHAR_BIT - 1)) +
+		ctx->reta_sz_log));
+
+	return val & ((1 << ctx->reta_sz_log) - 1);
+}
+
+static inline void
+generate_compliment_table(struct rte_thash_ctx *ctx,
+	struct rte_thash_subtuple_helper *h)
+{
+	int i, j, k;
+	uint32_t val;
+	uint32_t start;
+
+	start = h->offset + h->len - (2 * ctx->reta_sz_log - 1);
+
+	for (i = 1; i < (1 << ctx->reta_sz_log); i++) {
+		val = 0;
+		for (j = i; j; j &= (j - 1)) {
+			k = rte_bsf32(j);
+			val ^= get_subvalue(ctx, start - k +
+				ctx->reta_sz_log - 1);
+		}
+		h->compl_table[val] = i;
+	}
+}
+
+static inline int
+insert_before(struct rte_thash_ctx *ctx,
+	struct rte_thash_subtuple_helper *ent,
+	struct rte_thash_subtuple_helper *cur_ent,
+	struct rte_thash_subtuple_helper *next_ent,
+	uint32_t start, uint32_t end, uint32_t range_end)
+{
+	int ret;
+
+	if (end < cur_ent->offset) {
+		ent->lfsr = alloc_lfsr(ctx);
+		if (ent->lfsr == NULL) {
+			rte_free(ent);
+			return -ENOMEM;
+		}
+		/* generate nonoverlapping range [start, end) */
+		ret = generate_subkey(ctx, ent->lfsr, start, end - 1);
+		if (ret != 0) {
+			free_lfsr(ent->lfsr);
+			rte_free(ent);
+			return ret;
+		}
+	} else if ((next_ent != NULL) && (end > next_ent->offset)) {
+		rte_free(ent);
+		return -ENOSPC;
+	}
+	attach_lfsr(ent, cur_ent->lfsr);
+
+	/**
+	 * generate partially overlapping range
+	 * [start, cur_ent->start) in reverse order
+	 */
+	ret = generate_subkey(ctx, ent->lfsr, cur_ent->offset - 1, start);
+	if (ret != 0) {
+		free_lfsr(ent->lfsr);
+		rte_free(ent);
+		return ret;
+	}
+
+	if (end > range_end) {
+		/**
+		 * generate partially overlapping range
+		 * (range_end, end)
+		 */
+		ret = generate_subkey(ctx, ent->lfsr, range_end, end - 1);
+		if (ret != 0) {
+			free_lfsr(ent->lfsr);
+			rte_free(ent);
+			return ret;
+		}
+	}
+
+	LIST_INSERT_BEFORE(cur_ent, ent, next);
+	generate_compliment_table(ctx, ent);
+	ctx->subtuples_nb++;
+	return 0;
+}
+
+static inline int
+insert_after(struct rte_thash_ctx *ctx,
+	struct rte_thash_subtuple_helper *ent,
+	struct rte_thash_subtuple_helper *cur_ent,
+	struct rte_thash_subtuple_helper *next_ent,
+	struct rte_thash_subtuple_helper *prev_ent,
+	uint32_t end, uint32_t range_end)
+{
+	int ret;
+
+	if ((next_ent != NULL) && (end > next_ent->offset)) {
+		rte_free(ent);
+		return -EEXIST;
+	}
+
+	attach_lfsr(ent, cur_ent->lfsr);
+	if (end > range_end) {
+		/**
+		 * generate partially overlapping range
+		 * (range_end, end)
+		 */
+		ret = generate_subkey(ctx, ent->lfsr, range_end, end - 1);
+		if (ret != 0) {
+			free_lfsr(ent->lfsr);
+			rte_free(ent);
+			return ret;
+		}
+	}
+
+	LIST_INSERT_AFTER(prev_ent, ent, next);
+	generate_compliment_table(ctx, ent);
+	ctx->subtuples_nb++;
+
+	return 0;
 }
 
 int
-rte_thash_add_helper(struct rte_thash_ctx *ctx __rte_unused,
-	const char *name __rte_unused, uint32_t len __rte_unused,
-	uint32_t offset __rte_unused)
+rte_thash_add_helper(struct rte_thash_ctx *ctx, const char *name, uint32_t len,
+	uint32_t offset)
 {
+	struct rte_thash_subtuple_helper *ent, *cur_ent, *prev_ent, *next_ent;
+	uint32_t start, end;
+	int ret;
+
+	if ((ctx == NULL) || (name == NULL) || (len < ctx->reta_sz_log) ||
+			((offset + len + TOEPLITZ_HASH_LEN - 1) >
+			ctx->key_len * CHAR_BIT))
+		return -EINVAL;
+
+	/* Check for existing name*/
+	LIST_FOREACH(cur_ent, &ctx->head, next) {
+		if (strncmp(name, cur_ent->name, sizeof(cur_ent->name)) == 0)
+			return -EEXIST;
+	}
+
+	end = offset + len + TOEPLITZ_HASH_LEN - 1;
+	start = ((ctx->flags & RTE_THASH_MINIMAL_SEQ) ==
+		RTE_THASH_MINIMAL_SEQ) ? (end - (2 * ctx->reta_sz_log - 1)) :
+		offset;
+
+	ent = rte_zmalloc(NULL, sizeof(struct rte_thash_subtuple_helper) +
+		sizeof(uint32_t) * (1 << ctx->reta_sz_log), 0);
+	if (ent == NULL)
+		return -ENOMEM;
+
+	rte_strlcpy(ent->name, name, sizeof(ent->name));
+	ent->offset = start;
+	ent->len = end - start;
+	ent->tuple_offset = offset;
+	ent->tuple_len = len;
+	ent->lsb_msk = (1 << ctx->reta_sz_log) - 1;
+
+	cur_ent = LIST_FIRST(&ctx->head);
+	while (cur_ent) {
+		uint32_t range_end = cur_ent->offset + cur_ent->len;
+		next_ent = LIST_NEXT(cur_ent, next);
+		prev_ent = cur_ent;
+		/* Iterate through overlapping ranges */
+		while ((next_ent != NULL) && (next_ent->offset < range_end)) {
+			range_end = RTE_MAX(next_ent->offset + next_ent->len,
+				range_end);
+			if (start > next_ent->offset)
+				prev_ent = next_ent;
+
+			next_ent = LIST_NEXT(next_ent, next);
+		}
+
+		if (start < cur_ent->offset)
+			return insert_before(ctx, ent, cur_ent, next_ent,
+				start, end, range_end);
+		else if (start < range_end)
+			return insert_after(ctx, ent, cur_ent, next_ent,
+				prev_ent, end, range_end);
+
+		cur_ent = next_ent;
+		continue;
+	}
+
+	ent->lfsr = alloc_lfsr(ctx);
+	if (ent->lfsr == NULL) {
+		rte_free(ent);
+		return -ENOMEM;
+	}
+
+	/* generate nonoverlapping range [start, end) */
+	ret = generate_subkey(ctx, ent->lfsr, start, end - 1);
+	if (ret != 0) {
+		free_lfsr(ent->lfsr);
+		rte_free(ent);
+		return ret;
+	}
+	if (LIST_EMPTY(&ctx->head)) {
+		LIST_INSERT_HEAD(&ctx->head, ent, next);
+	} else {
+		LIST_FOREACH(next_ent, &ctx->head, next)
+			prev_ent = next_ent;
+
+		LIST_INSERT_AFTER(prev_ent, ent, next);
+	}
+	generate_compliment_table(ctx, ent);
+	ctx->subtuples_nb++;
+
 	return 0;
 }
 
 struct rte_thash_subtuple_helper *
-rte_thash_get_helper(struct rte_thash_ctx *ctx __rte_unused,
-	const char *name __rte_unused)
+rte_thash_get_helper(struct rte_thash_ctx *ctx, const char *name)
 {
+	struct rte_thash_subtuple_helper *ent;
+
+	if ((ctx == NULL) || (name == NULL))
+		return NULL;
+
+	LIST_FOREACH(ent, &ctx->head, next) {
+		if (strncmp(name, ent->name, sizeof(ent->name)) == 0)
+			return ent;
+	}
+
 	return NULL;
 }
 
 uint32_t
-rte_thash_get_compliment(struct rte_thash_subtuple_helper *h __rte_unused,
-	uint32_t hash __rte_unused, uint32_t desired_hash __rte_unused)
+rte_thash_get_compliment(struct rte_thash_subtuple_helper *h,
+	uint32_t hash, uint32_t desired_hash)
 {
-	return 0;
+	return h->compl_table[(hash ^ desired_hash) & h->lsb_msk];
 }
 
 const uint8_t *
-rte_thash_get_key(struct rte_thash_ctx *ctx __rte_unused)
+rte_thash_get_key(struct rte_thash_ctx *ctx)
 {
-	return NULL;
+	return ctx->hash_key;
+}
+
+static inline void
+xor_bit(uint8_t *ptr, uint32_t bit, uint32_t pos)
+{
+	uint32_t byte_idx = pos >> 3;
+	uint32_t bit_idx = (CHAR_BIT - 1) - (pos & (CHAR_BIT - 1));
+	uint8_t tmp;
+
+	tmp = ptr[byte_idx];
+	tmp ^= bit << bit_idx;
+	ptr[byte_idx] = tmp;
+}
+
+int
+rte_thash_adjust_tuple(struct rte_thash_subtuple_helper *h,
+	uint8_t *orig_tuple, uint32_t adj_bits,
+	rte_thash_check_tuple_t fn, void *userdata)
+{
+	unsigned i;
+
+	if ((h == NULL) || (orig_tuple == NULL))
+		return -EINVAL;
+
+	adj_bits &= h->lsb_msk;
+	/* Hint: LSB of adj_bits corresponds to offset + len bit of tuple */
+	for (i = 0; i < sizeof(uint32_t) * CHAR_BIT; i++) {
+		uint8_t bit = (adj_bits >> i) & 0x1;
+		if (bit)
+			xor_bit(orig_tuple, bit,
+				h->tuple_offset + h->tuple_len - 1 - i);
+	}
+
+	if (fn != NULL)
+		return (fn(userdata, orig_tuple)) ? 0 : -EEXIST;
+
+	return 0;
 }
diff --git a/lib/librte_hash/rte_thash.h b/lib/librte_hash/rte_thash.h
index 38a641b..fd67931 100644
--- a/lib/librte_hash/rte_thash.h
+++ b/lib/librte_hash/rte_thash.h
@@ -360,6 +360,48 @@ __rte_experimental
 const uint8_t *
 rte_thash_get_key(struct rte_thash_ctx *ctx);
 
+/**
+ * Function prototype for the rte_thash_adjust_tuple
+ * to check if adjusted tuple could be used.
+ * Generally it is some kind of lookup function to check
+ * if adjusted tuple is already in use.
+ *
+ * @param userdata
+ *  Pointer to the userdata. It could be a pointer to the
+ *  table with used tuples to search.
+ * @param tuple
+ *  Pointer to the tuple to check
+ *
+ * @return
+ *  1 on success
+ *  0 otherwise
+ */
+typedef int (*rte_thash_check_tuple_t)(void *userdata, uint8_t *tuple);
+
+/**
+ * Adjust tuple with complimentary bits.
+ *
+ * @param h
+ *  Pointer to the helper struct
+ * @param orig_tuple
+ *  Pointer to the tuple to be adjusted
+ * @param adj_bits
+ *  Valure returned by rte_thash_get_compliment
+ * @param fn
+ *  Callback function to check adjusted tuple. Could be NULL
+ * @param userdata
+ *  Pointer to the userdata to be passed to fn(). Could be NULL
+ *
+ * @return
+ *  0 on success
+ *  negative otherwise
+ */
+__rte_experimental
+int
+rte_thash_adjust_tuple(struct rte_thash_subtuple_helper *h,
+	uint8_t *orig_tuple, uint32_t adj_bits,
+	rte_thash_check_tuple_t fn, void *userdata);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/lib/librte_hash/version.map b/lib/librte_hash/version.map
index 93cb230..a992a1e 100644
--- a/lib/librte_hash/version.map
+++ b/lib/librte_hash/version.map
@@ -32,6 +32,7 @@ DPDK_21 {
 EXPERIMENTAL {
 	global:
 
+	rte_thash_adjust_tuple;
 	rte_hash_free_key_with_position;
 	rte_hash_lookup_with_hash_bulk;
 	rte_hash_lookup_with_hash_bulk_data;
-- 
2.7.4


^ permalink raw reply	[flat|nested] 45+ messages in thread

* [dpdk-dev] [PATCH v2 3/3] test/hash: add additional thash tests
  2021-03-16 18:24 [dpdk-dev] [PATCH v1 0/3] Predictable RSS feature Vladimir Medvedkin
                   ` (5 preceding siblings ...)
  2021-04-06 19:50 ` [dpdk-dev] [PATCH v2 2/3] hash: add predictable RSS implementation Vladimir Medvedkin
@ 2021-04-06 19:50 ` Vladimir Medvedkin
  6 siblings, 0 replies; 45+ messages in thread
From: Vladimir Medvedkin @ 2021-04-06 19:50 UTC (permalink / raw)
  To: dev
  Cc: konstantin.ananyev, andrey.chilikin, ray.kinsella, yipeng1.wang,
	sameh.gobriel, bruce.richardson

This patch adds tests for predictable RSS feature

Signed-off-by: Vladimir Medvedkin <vladimir.medvedkin@intel.com>
---
 app/test/test_thash.c | 468 +++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 462 insertions(+), 6 deletions(-)

diff --git a/app/test/test_thash.c b/app/test/test_thash.c
index a6aadd1..28ae767 100644
--- a/app/test/test_thash.c
+++ b/app/test/test_thash.c
@@ -5,11 +5,15 @@
 #include <rte_common.h>
 #include <rte_eal.h>
 #include <rte_ip.h>
+#include <rte_random.h>
 
 #include "test.h"
 
 #include <rte_thash.h>
 
+#define HASH_MSK(reta_sz)	((1 << reta_sz) - 1)
+#define TUPLE_SZ	(RTE_THASH_V4_L4_LEN * 4)
+
 struct test_thash_v4 {
 	uint32_t	dst_ip;
 	uint32_t	src_ip;
@@ -75,7 +79,7 @@ uint8_t default_rss_key[] = {
 };
 
 static int
-test_thash(void)
+test_toeplitz_hash_calc(void)
 {
 	uint32_t i, j;
 	union rte_thash_tuple tuple;
@@ -100,7 +104,7 @@ test_thash(void)
 				RTE_THASH_V4_L4_LEN, default_rss_key);
 		if ((rss_l3 != v4_tbl[i].hash_l3) ||
 				(rss_l3l4 != v4_tbl[i].hash_l3l4))
-			return -1;
+			return -TEST_FAILED;
 		/*Calculate hash with converted key*/
 		rss_l3 = rte_softrss_be((uint32_t *)&tuple,
 				RTE_THASH_V4_L3_LEN, rss_key_be);
@@ -108,7 +112,7 @@ test_thash(void)
 				RTE_THASH_V4_L4_LEN, rss_key_be);
 		if ((rss_l3 != v4_tbl[i].hash_l3) ||
 				(rss_l3l4 != v4_tbl[i].hash_l3l4))
-			return -1;
+			return -TEST_FAILED;
 	}
 	for (i = 0; i < RTE_DIM(v6_tbl); i++) {
 		/*Fill ipv6 hdr*/
@@ -127,7 +131,7 @@ test_thash(void)
 				RTE_THASH_V6_L4_LEN, default_rss_key);
 		if ((rss_l3 != v6_tbl[i].hash_l3) ||
 				(rss_l3l4 != v6_tbl[i].hash_l3l4))
-			return -1;
+			return -TEST_FAILED;
 		/*Calculate hash with converted key*/
 		rss_l3 = rte_softrss_be((uint32_t *)&tuple,
 				RTE_THASH_V6_L3_LEN, rss_key_be);
@@ -135,9 +139,461 @@ test_thash(void)
 				RTE_THASH_V6_L4_LEN, rss_key_be);
 		if ((rss_l3 != v6_tbl[i].hash_l3) ||
 				(rss_l3l4 != v6_tbl[i].hash_l3l4))
-			return -1;
+			return -TEST_FAILED;
 	}
-	return 0;
+	return TEST_SUCCESS;
+}
+
+static int
+test_create_invalid(void)
+{
+	struct rte_thash_ctx *ctx;
+	int key_len = 40;
+	int reta_sz = 7;
+
+	ctx = rte_thash_init_ctx(NULL, key_len, reta_sz, NULL, 0);
+	RTE_TEST_ASSERT(ctx == NULL,
+		"Call succeeded with invalid parameters\n");
+
+	ctx = rte_thash_init_ctx("test", 0, reta_sz, NULL, 0);
+	RTE_TEST_ASSERT(ctx == NULL,
+		"Call succeeded with invalid parameters\n");
+
+	ctx = rte_thash_init_ctx(NULL, key_len, 1, NULL, 0);
+	RTE_TEST_ASSERT(ctx == NULL,
+		"Call succeeded with invalid parameters\n");
+
+	ctx = rte_thash_init_ctx(NULL, key_len, 17, NULL, 0);
+	RTE_TEST_ASSERT(ctx == NULL,
+		"Call succeeded with invalid parameters\n");
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_multiple_create(void)
+{
+	struct rte_thash_ctx *ctx;
+	int key_len = 40;
+	int reta_sz = 7;
+	int i;
+
+	for (i = 0; i < 100; i++) {
+		ctx = rte_thash_init_ctx("test", key_len, reta_sz, NULL, 0);
+		RTE_TEST_ASSERT(ctx != NULL, "Can not create CTX\n");
+
+		rte_thash_free_ctx(ctx);
+	}
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_free_null(void)
+{
+	struct rte_thash_ctx *ctx;
+
+	ctx = rte_thash_init_ctx("test", 40, 7, NULL, 0);
+	RTE_TEST_ASSERT(ctx != NULL, "Can not create CTX\n");
+
+	rte_thash_free_ctx(ctx);
+	rte_thash_free_ctx(NULL);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_add_invalid_helper(void)
+{
+	struct rte_thash_ctx *ctx;
+	const int key_len = 40;
+	int reta_sz = 7;
+	int ret;
+
+	ctx = rte_thash_init_ctx("test", key_len, reta_sz, NULL, 0);
+	RTE_TEST_ASSERT(ctx != NULL, "can not create thash ctx\n");
+
+	ret = rte_thash_add_helper(NULL, "test", reta_sz, 0);
+	RTE_TEST_ASSERT(ret == -EINVAL,
+		"Call succeeded with invalid parameters\n");
+
+	ret = rte_thash_add_helper(ctx, NULL, reta_sz, 0);
+	RTE_TEST_ASSERT(ret == -EINVAL,
+		"Call succeeded with invalid parameters\n");
+
+	ret = rte_thash_add_helper(ctx, "test", reta_sz - 1, 0);
+	RTE_TEST_ASSERT(ret == -EINVAL,
+		"Call succeeded with invalid parameters\n");
+
+	ret = rte_thash_add_helper(ctx, "test", reta_sz, key_len * 8);
+	RTE_TEST_ASSERT(ret == -EINVAL,
+		"Call succeeded with invalid parameters\n");
+
+	ret = rte_thash_add_helper(ctx, "first_range", reta_sz, 0);
+	RTE_TEST_ASSERT(ret == 0, "Can not create helper\n");
+
+	ret = rte_thash_add_helper(ctx, "first_range", reta_sz, 0);
+	RTE_TEST_ASSERT(ret == -EEXIST,
+		"Call succeeded with duplicated name\n");
+
+	/*
+	 * Create second helper with offset 3 * reta_sz.
+	 * Note firts_range helper created range in key:
+	 * [0, 32 + length{= reta_sz} - 1), i.e [0, 37).
+	 * second range is [44, 81)
+	 */
+	ret = rte_thash_add_helper(ctx, "second_range", reta_sz,
+		32 +  2 * reta_sz);
+	RTE_TEST_ASSERT(ret == 0, "Can not create helper\n");
+
+	/*
+	 * Try to create overlapping with first_ and second_ ranges,
+	 * i.e. [6, 49)
+	 */
+	ret = rte_thash_add_helper(ctx, "third_range", 2 * reta_sz, reta_sz);
+	RTE_TEST_ASSERT(ret == -EEXIST,
+		"Call succeeded with overlapping ranges\n");
+
+	rte_thash_free_ctx(ctx);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_find_existing(void)
+{
+	struct rte_thash_ctx *ctx, *ret_ctx;
+
+	ctx = rte_thash_init_ctx("test", 40, 7, NULL, 0);
+	RTE_TEST_ASSERT(ctx != NULL, "can not create thash ctx\n");
+
+	ret_ctx = rte_thash_find_existing("test");
+	RTE_TEST_ASSERT(ret_ctx != NULL, "can not find existing ctx\n");
+
+	rte_thash_free_ctx(ctx);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_get_helper(void)
+{
+	struct rte_thash_ctx *ctx;
+	struct rte_thash_subtuple_helper *h;
+	int ret;
+
+	ctx = rte_thash_init_ctx("test", 40, 7, NULL, 0);
+	RTE_TEST_ASSERT(ctx != NULL, "Can not create thash ctx\n");
+
+	h = rte_thash_get_helper(NULL, "first_range");
+	RTE_TEST_ASSERT(h == NULL, "Call succeeded with invalid parameters\n");
+
+	h = rte_thash_get_helper(ctx, NULL);
+	RTE_TEST_ASSERT(h == NULL, "Call succeeded with invalid parameters\n");
+
+	ret = rte_thash_add_helper(ctx, "first_range", 8, 0);
+	RTE_TEST_ASSERT(ret == 0, "Can not create helper\n");
+
+	h = rte_thash_get_helper(ctx, "first_range");
+	RTE_TEST_ASSERT(h != NULL, "Can not find helper\n");
+
+	rte_thash_free_ctx(ctx);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_period_overflow(void)
+{
+	struct rte_thash_ctx *ctx;
+	int reta_sz = 7; /* reflects polynomial degree */
+	int ret;
+
+	/* first create without RTE_THASH_IGNORE_PERIOD_OVERFLOW flag */
+	ctx = rte_thash_init_ctx("test", 40, reta_sz, NULL, 0);
+	RTE_TEST_ASSERT(ctx != NULL, "Can not create thash ctx\n");
+
+	/* requested range > (2^reta_sz) - 1 */
+	ret = rte_thash_add_helper(ctx, "test", (1 << reta_sz), 0);
+	RTE_TEST_ASSERT(ret == -ENOSPC,
+		"Call succeeded with invalid parameters\n");
+
+	/* requested range == len + 32 - 1, smaller than (2^reta_sz) - 1 */
+	ret = rte_thash_add_helper(ctx, "test", (1 << reta_sz) - 32, 0);
+	RTE_TEST_ASSERT(ret == 0, "Can not create helper\n");
+
+	rte_thash_free_ctx(ctx);
+
+	/* create with RTE_THASH_IGNORE_PERIOD_OVERFLOW flag */
+	ctx = rte_thash_init_ctx("test", 40, reta_sz, NULL,
+		RTE_THASH_IGNORE_PERIOD_OVERFLOW);
+	RTE_TEST_ASSERT(ctx != NULL, "Can not create thash ctx\n");
+
+	/* requested range > (2^reta_sz - 1) */
+	ret = rte_thash_add_helper(ctx, "test", (1 << reta_sz) + 10, 0);
+	RTE_TEST_ASSERT(ret == 0, "Can not create helper\n");
+
+	rte_thash_free_ctx(ctx);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_predictable_rss_min_seq(void)
+{
+	struct rte_thash_ctx *ctx;
+	struct rte_thash_subtuple_helper *h;
+	const int key_len = 40;
+	int reta_sz = 6;
+	uint8_t initial_key[key_len];
+	const uint8_t *new_key;
+	int ret;
+	union rte_thash_tuple tuple;
+	uint32_t orig_hash, adj_hash, adj;
+	unsigned int desired_value = 27 & HASH_MSK(reta_sz);
+	uint16_t port_value = 22;
+
+	memset(initial_key, 0, key_len);
+
+	ctx = rte_thash_init_ctx("test", key_len, reta_sz, initial_key,
+		RTE_THASH_MINIMAL_SEQ);
+	RTE_TEST_ASSERT(ctx != NULL, "can not create thash ctx\n");
+
+	ret = rte_thash_add_helper(ctx, "snat", sizeof(uint16_t) * 8,
+		offsetof(union rte_thash_tuple, v4.sport) * 8);
+	RTE_TEST_ASSERT(ret == 0, "can not add helper, ret %d\n", ret);
+
+	h = rte_thash_get_helper(ctx, "snat");
+	RTE_TEST_ASSERT(h != NULL, "can not find helper\n");
+
+	new_key = rte_thash_get_key(ctx);
+	tuple.v4.src_addr = RTE_IPV4(0, 0, 0, 0);
+	tuple.v4.dst_addr = RTE_IPV4(0, 0, 0, 0);
+	tuple.v4.sport = 0;
+	tuple.v4.sport = rte_cpu_to_be_16(port_value);
+	tuple.v4.dport = 0;
+	tuple.v4.sctp_tag = rte_be_to_cpu_32(tuple.v4.sctp_tag);
+
+	orig_hash = rte_softrss((uint32_t *)&tuple,
+		RTE_THASH_V4_L4_LEN, new_key);
+	adj = rte_thash_get_compliment(h, orig_hash, desired_value);
+
+	tuple.v4.sctp_tag = rte_cpu_to_be_32(tuple.v4.sctp_tag);
+	tuple.v4.sport ^= rte_cpu_to_be_16(adj);
+	tuple.v4.sctp_tag = rte_be_to_cpu_32(tuple.v4.sctp_tag);
+
+	adj_hash = rte_softrss((uint32_t *)&tuple,
+		RTE_THASH_V4_L4_LEN, new_key);
+	RTE_TEST_ASSERT((adj_hash & HASH_MSK(reta_sz)) ==
+		desired_value, "bad desired value\n");
+
+	rte_thash_free_ctx(ctx);
+
+	return TEST_SUCCESS;
+}
+
+/*
+ * This test creates 7 subranges in the folowing order:
+ * range_one	= [56, 95),	len = 8, offset = 56
+ * range_two	= [64, 103),	len = 8, offset = 64
+ * range_three	= [120, 159),	len = 8, offset = 120
+ * range_four	= [48, 87),	len = 8, offset = 48
+ * range_five	= [57, 95),	len = 7, offset = 57
+ * range_six	= [40, 111),	len = 40, offset = 40
+ * range_seven	= [0, 39),	len = 8, offset = 0
+ */
+struct range {
+	const char *name;
+	int len;
+	int offset;
+	int byte_idx;
+};
+
+struct range rng_arr[] = {
+	{"one",   8,  56,  7},
+	{"two",   8,  64,  8},
+	{"three", 8,  120, 15},
+	{"four",  8,  48,  6},
+	{"six",   40, 40,  9},
+	{"five",  7,  57,  7},
+	{"seven", 8,  0,   0}
+};
+
+static int
+test_predictable_rss_multirange(void)
+{
+	struct rte_thash_ctx *ctx;
+	struct rte_thash_subtuple_helper *h[RTE_DIM(rng_arr)];
+	const uint8_t *new_key;
+	const int key_len = 40;
+	int reta_sz = 7;
+	unsigned int i, j, k;
+	int ret;
+	uint32_t desired_value = rte_rand() & HASH_MSK(reta_sz);
+	uint8_t tuples[RTE_DIM(rng_arr)][16] = { {0} };
+	uint32_t *ptr;
+	uint32_t hashes[RTE_DIM(rng_arr)];
+	uint32_t adj_hashes[RTE_DIM(rng_arr)];
+	uint32_t adj;
+
+	ctx = rte_thash_init_ctx("test", key_len, reta_sz, NULL, 0);
+	RTE_TEST_ASSERT(ctx != NULL, "can not create thash ctx\n");
+
+	for (i = 0; i < RTE_DIM(rng_arr); i++) {
+		ret = rte_thash_add_helper(ctx, rng_arr[i].name,
+			rng_arr[i].len, rng_arr[i].offset);
+		RTE_TEST_ASSERT(ret == 0, "can not add helper\n");
+
+		h[i] = rte_thash_get_helper(ctx, rng_arr[i].name);
+		RTE_TEST_ASSERT(h[i] != NULL, "can not find helper\n");
+	}
+	new_key = rte_thash_get_key(ctx);
+
+	/*
+	 * calculate hashes, compliments, then adjust keys with
+	 * compliments and recalsulate hashes
+	 */
+	for (i = 0; i < RTE_DIM(rng_arr); i++) {
+		for (k = 0; k < 100; k++) {
+			/* init with random keys */
+			ptr = (uint32_t *)&tuples[i][0];
+			for (j = 0; j < 4; j++)
+				ptr[j] = rte_rand();
+			/* convert keys from BE to CPU byte order */
+			for (j = 0; j < 4; j++)
+				ptr[j] = rte_be_to_cpu_32(ptr[j]);
+
+			hashes[i] = rte_softrss(ptr, 4, new_key);
+			adj = rte_thash_get_compliment(h[i], hashes[i],
+				desired_value);
+			/* convert back to BE to adjust the value */
+			for (j = 0; j < 4; j++)
+				ptr[j] = rte_cpu_to_be_32(ptr[j]);
+
+			tuples[i][rng_arr[i].byte_idx] ^= adj;
+
+			for (j = 0; j < 4; j++)
+				ptr[j] = rte_be_to_cpu_32(ptr[j]);
+
+			adj_hashes[i] = rte_softrss(ptr, 4, new_key);
+			RTE_TEST_ASSERT((adj_hashes[i] & HASH_MSK(reta_sz)) ==
+				desired_value,
+				"bad desired value for %d tuple\n", i);
+		}
+	}
+
+	rte_thash_free_ctx(ctx);
+
+	return TEST_SUCCESS;
+}
+
+static int
+cmp_tuple_eq(void *userdata, uint8_t *tuple)
+{
+	return memcmp(userdata, tuple, TUPLE_SZ);
+}
+
+static int
+test_adjust_tuple(void)
+{
+	struct rte_thash_ctx *ctx;
+	struct rte_thash_subtuple_helper *h;
+	const int key_len = 40;
+	int reta_sz = CHAR_BIT;
+	const uint8_t *new_key;
+	int ret;
+	uint8_t be_tuple[TUPLE_SZ];
+	uint8_t be_tuple_tmp[TUPLE_SZ];
+	uint8_t le_tuple[TUPLE_SZ];
+	uint32_t orig_hash, adj_hash, adj;
+	unsigned int i;
+	unsigned int desired_value = 27 & HASH_MSK(reta_sz);
+
+	memset(be_tuple, 0xab, TUPLE_SZ);
+	memset(le_tuple, 0xab, TUPLE_SZ);
+
+	ctx = rte_thash_init_ctx("test", key_len, reta_sz, NULL, 0);
+	RTE_TEST_ASSERT(ctx != NULL, "can not create thash ctx\n");
+
+	/* set offset to be in the middle of a byte */
+	ret = rte_thash_add_helper(ctx, "test", CHAR_BIT, (5 * CHAR_BIT) + 4);
+	RTE_TEST_ASSERT(ret == 0, "can not add helper, ret %d\n", ret);
+
+	h = rte_thash_get_helper(ctx, "test");
+	RTE_TEST_ASSERT(h != NULL, "can not find helper\n");
+
+	new_key = rte_thash_get_key(ctx);
+
+	/* at the moment be_ and le_ tuples are the same */
+	orig_hash = rte_softrss((uint32_t *)le_tuple,
+		RTE_THASH_V4_L4_LEN, new_key);
+
+	adj = rte_thash_get_compliment(h, orig_hash, desired_value);
+
+	ret = rte_thash_adjust_tuple(h, be_tuple, adj, NULL, NULL);
+	RTE_TEST_ASSERT(ret == 0, "can not adjust tuple, ret %d\n", ret);
+
+	for (i = 0; i < (TUPLE_SZ / 4); i++)
+		*(uint32_t *)&le_tuple[i * 4] =
+			rte_be_to_cpu_32(*(uint32_t *)&be_tuple[i * 4]);
+
+	adj_hash = rte_softrss((uint32_t *)le_tuple,
+		RTE_THASH_V4_L4_LEN, new_key);
+	RTE_TEST_ASSERT((adj_hash & HASH_MSK(reta_sz)) ==
+		desired_value, "bad desired value\n");
+
+	/* Pass previously calculated tuple to callback function */
+	memcpy(be_tuple_tmp, be_tuple, TUPLE_SZ);
+
+	memset(be_tuple, 0xab, TUPLE_SZ);
+	ret = rte_thash_adjust_tuple(h, be_tuple, adj, cmp_tuple_eq,
+		&be_tuple_tmp);
+	RTE_TEST_ASSERT(ret == -EEXIST, "adjust tuple didn't indicate collision\n");
+
+	/* Pass another tuple to check with */
+	memset(be_tuple, 0xab, TUPLE_SZ);
+	ret = rte_thash_adjust_tuple(h, be_tuple, adj, cmp_tuple_eq,
+		&le_tuple);
+	RTE_TEST_ASSERT(ret == 0, "can not adjust tuple, ret %d\n", ret);
+
+	for (i = 0; i < (TUPLE_SZ / 4); i++)
+		*(uint32_t *)&le_tuple[i * 4] =
+			rte_be_to_cpu_32(*(uint32_t *)&be_tuple[i * 4]);
+
+	adj_hash = rte_softrss((uint32_t *)le_tuple,
+		RTE_THASH_V4_L4_LEN, new_key);
+	RTE_TEST_ASSERT((adj_hash & HASH_MSK(reta_sz)) ==
+		desired_value, "bad desired value\n");
+
+	rte_thash_free_ctx(ctx);
+
+	return TEST_SUCCESS;
+}
+
+static struct unit_test_suite thash_tests = {
+	.suite_name = "thash autotest",
+	.setup = NULL,
+	.teardown = NULL,
+	.unit_test_cases = {
+	TEST_CASE(test_toeplitz_hash_calc),
+	TEST_CASE(test_create_invalid),
+	TEST_CASE(test_multiple_create),
+	TEST_CASE(test_free_null),
+	TEST_CASE(test_add_invalid_helper),
+	TEST_CASE(test_find_existing),
+	TEST_CASE(test_get_helper),
+	TEST_CASE(test_period_overflow),
+	TEST_CASE(test_predictable_rss_min_seq),
+	TEST_CASE(test_predictable_rss_multirange),
+	TEST_CASE(test_adjust_tuple),
+	TEST_CASES_END()
+	}
+};
+
+static int
+test_thash(void)
+{
+	return unit_test_suite_runner(&thash_tests);
 }
 
 REGISTER_TEST_COMMAND(thash_autotest, test_thash);
-- 
2.7.4


^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [dpdk-dev] [PATCH v2 2/3] hash: add predictable RSS implementation
  2021-04-06 19:50 ` [dpdk-dev] [PATCH v2 2/3] hash: add predictable RSS implementation Vladimir Medvedkin
@ 2021-04-07 12:53   ` Ananyev, Konstantin
  2021-04-11 18:51     ` Medvedkin, Vladimir
  2021-04-10  0:10   ` Wang, Yipeng1
  1 sibling, 1 reply; 45+ messages in thread
From: Ananyev, Konstantin @ 2021-04-07 12:53 UTC (permalink / raw)
  To: Medvedkin, Vladimir, dev
  Cc: Chilikin, Andrey, Kinsella, Ray, Wang, Yipeng1, Gobriel, Sameh,
	Richardson, Bruce

Hi Vladimir,

Few comments below, mostly minor.
One generic one - doc seems missing.
With that in place:
Acked-by: Konstantin Ananyev <konstantin.ananyev@intel.com>

> 
> This patch implements predictable RSS functionality.
> 
> Signed-off-by: Vladimir Medvedkin <vladimir.medvedkin@intel.com>
> ---
>  lib/librte_hash/rte_thash.c | 577 ++++++++++++++++++++++++++++++++++++++++++--
>  lib/librte_hash/rte_thash.h |  42 ++++
>  lib/librte_hash/version.map |   1 +
>  3 files changed, 602 insertions(+), 18 deletions(-)
> 
> diff --git a/lib/librte_hash/rte_thash.c b/lib/librte_hash/rte_thash.c
> index 79e8724..cc60ada 100644
> --- a/lib/librte_hash/rte_thash.c
> +++ b/lib/librte_hash/rte_thash.c
> @@ -12,6 +12,45 @@
>  #include <rte_malloc.h>
> 
>  #define THASH_NAME_LEN		64
> +#define TOEPLITZ_HASH_LEN	32
> +
> +#define	RETA_SZ_MIN	2U
> +#define	RETA_SZ_MAX	16U

Should these RETA_SZ defines be in public header?
So user can know what are allowed values?

> +#define RETA_SZ_IN_RANGE(reta_sz)	((reta_sz >= RETA_SZ_MIN) && \
> +					(reta_sz <= RETA_SZ_MAX))
> +
> +TAILQ_HEAD(rte_thash_list, rte_tailq_entry);
> +static struct rte_tailq_elem rte_thash_tailq = {
> +	.name = "RTE_THASH",
> +};
> +EAL_REGISTER_TAILQ(rte_thash_tailq)
> +
> +/**
> + * Table of some irreducible polinomials over GF(2).
> + * For lfsr they are reperesented in BE bit order, and
> + * x^0 is masked out.
> + * For example, poly x^5 + x^2 + 1 will be represented
> + * as (101001b & 11111b) = 01001b = 0x9
> + */
> +static const uint32_t irreducible_poly_table[][4] = {
> +	{0, 0, 0, 0},	/** < degree 0 */
> +	{1, 1, 1, 1},	/** < degree 1 */
> +	{0x3, 0x3, 0x3, 0x3},	/** < degree 2 and so on... */
> +	{0x5, 0x3, 0x5, 0x3},
> +	{0x9, 0x3, 0x9, 0x3},
> +	{0x9, 0x1b, 0xf, 0x5},
> +	{0x21, 0x33, 0x1b, 0x2d},
> +	{0x41, 0x11, 0x71, 0x9},
> +	{0x71, 0xa9, 0xf5, 0x8d},
> +	{0x21, 0xd1, 0x69, 0x1d9},
> +	{0x81, 0x2c1, 0x3b1, 0x185},
> +	{0x201, 0x541, 0x341, 0x461},
> +	{0x941, 0x609, 0xe19, 0x45d},
> +	{0x1601, 0x1f51, 0x1171, 0x359},
> +	{0x2141, 0x2111, 0x2db1, 0x2109},
> +	{0x4001, 0x801, 0x101, 0x7301},
> +	{0x7781, 0xa011, 0x4211, 0x86d9},
> +};
> 
>  struct thash_lfsr {
>  	uint32_t	ref_cnt;
> @@ -31,8 +70,10 @@ struct rte_thash_subtuple_helper {
>  	char	name[THASH_NAME_LEN];	/** < Name of subtuple configuration */
>  	LIST_ENTRY(rte_thash_subtuple_helper)	next;
>  	struct thash_lfsr	*lfsr;
> -	uint32_t	offset;		/** < Offset in bits of the subtuple */
> -	uint32_t	len;		/** < Length in bits of the subtuple */
> +	uint32_t	offset;		/** < Offset of the m-sequence */
> +	uint32_t	len;		/** < Length of the m-sequence */
> +	uint32_t	tuple_offset;	/** < Offset in bits of the subtuple */
> +	uint32_t	tuple_len;	/** < Length in bits of the subtuple */
>  	uint32_t	lsb_msk;	/** < (1 << reta_sz_log) - 1 */
>  	__extension__ uint32_t	compl_table[0] __rte_cache_aligned;
>  	/** < Complimentary table */
> @@ -48,49 +89,549 @@ struct rte_thash_ctx {
>  	uint8_t		hash_key[0];
>  };
> 
> +static inline uint32_t
> +get_bit_lfsr(struct thash_lfsr *lfsr)
> +{
> +	uint32_t bit, ret;
> +
> +	/*
> +	 * masking the TAP bits defined by the polynomial and
> +	 * calculating parity
> +	 */
> +	bit = __builtin_popcount(lfsr->state & lfsr->poly) & 0x1;
> +	ret = lfsr->state & 0x1;
> +	lfsr->state = ((lfsr->state >> 1) | (bit << (lfsr->deg - 1))) &
> +		((1 << lfsr->deg) - 1);
> +
> +	lfsr->bits_cnt++;
> +	return ret;
> +}
> +
> +static inline uint32_t
> +get_rev_bit_lfsr(struct thash_lfsr *lfsr)
> +{
> +	uint32_t bit, ret;
> +
> +	bit = __builtin_popcount(lfsr->rev_state & lfsr->rev_poly) & 0x1;
> +	ret = lfsr->rev_state & (1 << (lfsr->deg - 1));
> +	lfsr->rev_state = ((lfsr->rev_state << 1) | bit) &
> +		((1 << lfsr->deg) - 1);
> +
> +	lfsr->bits_cnt++;
> +	return ret;
> +}
> +
> +static inline uint32_t
> +thash_get_rand_poly(uint32_t poly_degree)
> +{
> +	return irreducible_poly_table[poly_degree][rte_rand() %
> +		RTE_DIM(irreducible_poly_table[poly_degree])];
> +}
> +
> +static struct thash_lfsr *
> +alloc_lfsr(struct rte_thash_ctx *ctx)
> +{
> +	struct thash_lfsr *lfsr;
> +	uint32_t i;
> +
> +	if (ctx == NULL)
> +		return NULL;
> +
> +	lfsr = rte_zmalloc(NULL, sizeof(struct thash_lfsr), 0);
> +	if (lfsr == NULL)
> +		return NULL;
> +
> +	lfsr->deg = ctx->reta_sz_log;
> +	lfsr->poly = thash_get_rand_poly(lfsr->deg);
> +	do {
> +		lfsr->state = rte_rand() & ((1 << lfsr->deg) - 1);
> +	} while (lfsr->state == 0);
> +	/* init reverse order polynomial */
> +	lfsr->rev_poly = (lfsr->poly >> 1) | (1 << (lfsr->deg - 1));
> +	/* init proper rev_state*/
> +	lfsr->rev_state = lfsr->state;
> +	for (i = 0; i <= lfsr->deg; i++)
> +		get_rev_bit_lfsr(lfsr);
> +
> +	/* clear bits_cnt after rev_state was inited */
> +	lfsr->bits_cnt = 0;
> +	lfsr->ref_cnt = 1;
> +
> +	return lfsr;
> +}
> +
> +static void
> +attach_lfsr(struct rte_thash_subtuple_helper *h, struct thash_lfsr *lfsr)
> +{
> +	lfsr->ref_cnt++;
> +	h->lfsr = lfsr;
> +}
> +
> +static void
> +free_lfsr(struct thash_lfsr *lfsr)
> +{
> +	lfsr->ref_cnt--;
> +	if (lfsr->ref_cnt == 0)
> +		rte_free(lfsr);
> +}
> +
>  struct rte_thash_ctx *
> -rte_thash_init_ctx(const char *name __rte_unused,
> -	uint32_t key_len __rte_unused, uint32_t reta_sz __rte_unused,
> -	uint8_t *key __rte_unused, uint32_t flags __rte_unused)
> +rte_thash_init_ctx(const char *name, uint32_t key_len, uint32_t reta_sz,
> +	uint8_t *key, uint32_t flags)
>  {
> +	struct rte_thash_ctx *ctx;
> +	struct rte_tailq_entry *te;
> +	struct rte_thash_list *thash_list;
> +	uint32_t i;

Empty line is  missing.

> +	if ((name == NULL) || (key_len == 0) || !RETA_SZ_IN_RANGE(reta_sz)) {
> +		rte_errno = EINVAL;
> +		return NULL;
> +	}
> +
> +	thash_list = RTE_TAILQ_CAST(rte_thash_tailq.head, rte_thash_list);
> +
> +	rte_mcfg_tailq_write_lock();
> +
> +	/* guarantee there's no existing */
> +	TAILQ_FOREACH(te, thash_list, next) {
> +		ctx = (struct rte_thash_ctx *)te->data;
> +		if (strncmp(name, ctx->name, sizeof(ctx->name)) == 0)
> +			break;
> +	}
> +	ctx = NULL;
> +	if (te != NULL) {
> +		rte_errno = EEXIST;
> +		goto exit;
> +	}
> +
> +	/* allocate tailq entry */
> +	te = rte_zmalloc("THASH_TAILQ_ENTRY", sizeof(*te), 0);
> +	if (te == NULL) {
> +		RTE_LOG(ERR, HASH,
> +			"Can not allocate tailq entry for thash context %s\n",
> +			name);
> +		rte_errno = ENOMEM;
> +		goto exit;
> +	}
> +
> +	ctx = rte_zmalloc(NULL, sizeof(struct rte_thash_ctx) + key_len, 0);
> +	if (ctx == NULL) {
> +		RTE_LOG(ERR, HASH, "thash ctx %s memory allocation failed\n",
> +			name);
> +		rte_errno = ENOMEM;
> +		goto free_te;
> +	}
> +
> +	rte_strlcpy(ctx->name, name, sizeof(ctx->name));
> +	ctx->key_len = key_len;
> +	ctx->reta_sz_log = reta_sz;
> +	LIST_INIT(&ctx->head);
> +	ctx->flags = flags;
> +
> +	if (key)
> +		rte_memcpy(ctx->hash_key, key, key_len);
> +	else {
> +		for (i = 0; i < key_len; i++)
> +			ctx->hash_key[i] = rte_rand();
> +	}
> +
> +	te->data = (void *)ctx;
> +	TAILQ_INSERT_TAIL(thash_list, te, next);
> +
> +	rte_mcfg_tailq_write_unlock();
> +
> +	return ctx;
> +free_te:
> +	rte_free(te);
> +exit:
> +	rte_mcfg_tailq_write_unlock();
>  	return NULL;
>  }
> 
>  struct rte_thash_ctx *
> -rte_thash_find_existing(const char *name __rte_unused)
> +rte_thash_find_existing(const char *name)
>  {
> -	return NULL;
> +	struct rte_thash_ctx *ctx;
> +	struct rte_tailq_entry *te;
> +	struct rte_thash_list *thash_list;
> +
> +	thash_list = RTE_TAILQ_CAST(rte_thash_tailq.head, rte_thash_list);
> +
> +	rte_mcfg_tailq_read_lock();
> +	TAILQ_FOREACH(te, thash_list, next) {
> +		ctx = (struct rte_thash_ctx *)te->data;
> +		if (strncmp(name, ctx->name, sizeof(ctx->name)) == 0)
> +			break;
> +	}
> +
> +	rte_mcfg_tailq_read_unlock();
> +
> +	if (te == NULL) {
> +		rte_errno = ENOENT;
> +		return NULL;
> +	}
> +
> +	return ctx;
>  }
> 
>  void
> -rte_thash_free_ctx(struct rte_thash_ctx *ctx __rte_unused)
> +rte_thash_free_ctx(struct rte_thash_ctx *ctx)
>  {
> +	struct rte_tailq_entry *te;
> +	struct rte_thash_list *thash_list;
> +	struct rte_thash_subtuple_helper *ent, *tmp;
> +
> +	if (ctx == NULL)
> +		return;
> +
> +	thash_list = RTE_TAILQ_CAST(rte_thash_tailq.head, rte_thash_list);
> +	rte_mcfg_tailq_write_lock();
> +	TAILQ_FOREACH(te, thash_list, next) {
> +		if (te->data == (void *)ctx)
> +			break;
> +	}
> +
> +	if (te != NULL)
> +		TAILQ_REMOVE(thash_list, te, next);
> +
> +	rte_mcfg_tailq_write_unlock();
> +	ent = LIST_FIRST(&(ctx->head));
> +	while (ent) {
> +		free_lfsr(ent->lfsr);
> +		tmp = ent;
> +		ent = LIST_NEXT(ent, next);
> +		LIST_REMOVE(tmp, next);
> +		rte_free(tmp);
> +	}
> +
> +	rte_free(ctx);
> +	rte_free(te);
> +}
> +
> +static inline void
> +set_bit(uint8_t *ptr, uint32_t bit, uint32_t pos)
> +{
> +	uint32_t byte_idx = pos >> 3;

Just as a nit to be consistent with the line below:
pos / CHAR_BIT; 

> +	uint32_t bit_idx = (CHAR_BIT - 1) - (pos & (CHAR_BIT - 1));
> +	uint8_t tmp;
> +
> +	tmp = ptr[byte_idx];
> +	tmp &= ~(1 << bit_idx);
> +	tmp |= bit << bit_idx;
> +	ptr[byte_idx] = tmp;
> +}
> +
> +/**
> + * writes m-sequence to the hash_key for range [start, end]
> + * (i.e. including start and end positions)
> + */
> +static int
> +generate_subkey(struct rte_thash_ctx *ctx, struct thash_lfsr *lfsr,
> +	uint32_t start, uint32_t end)
> +{
> +	uint32_t i;
> +	uint32_t req_bits = (start < end) ? (end - start) : (start - end);
> +	req_bits++; /* due to incuding end */
> +
> +	/* check if lfsr overflow period of the m-sequence */
> +	if (((lfsr->bits_cnt + req_bits) > (1ULL << lfsr->deg) - 1) &&
> +			((ctx->flags & RTE_THASH_IGNORE_PERIOD_OVERFLOW) !=
> +			RTE_THASH_IGNORE_PERIOD_OVERFLOW))
> +		return -ENOSPC;
> +
> +	if (start < end) {
> +		/* original direction (from left to right)*/
> +		for (i = start; i <= end; i++)
> +			set_bit(ctx->hash_key, get_bit_lfsr(lfsr), i);
> +
> +	} else {
> +		/* reverse direction (from right to left) */
> +		for (i = end; i >= start; i--)
> +			set_bit(ctx->hash_key, get_rev_bit_lfsr(lfsr), i);
> +	}
> +
> +	return 0;
> +}
> +
> +static inline uint32_t
> +get_subvalue(struct rte_thash_ctx *ctx, uint32_t offset)
> +{
> +	uint32_t *tmp, val;
> +
> +	tmp = (uint32_t *)(&ctx->hash_key[offset >> 3]);
> +	val = rte_be_to_cpu_32(*tmp);
> +	val >>= (TOEPLITZ_HASH_LEN - ((offset & (CHAR_BIT - 1)) +
> +		ctx->reta_sz_log));
> +
> +	return val & ((1 << ctx->reta_sz_log) - 1);
> +}
> +
> +static inline void
> +generate_compliment_table(struct rte_thash_ctx *ctx,
> +	struct rte_thash_subtuple_helper *h)
> +{
> +	int i, j, k;
> +	uint32_t val;
> +	uint32_t start;
> +
> +	start = h->offset + h->len - (2 * ctx->reta_sz_log - 1);
> +
> +	for (i = 1; i < (1 << ctx->reta_sz_log); i++) {
> +		val = 0;
> +		for (j = i; j; j &= (j - 1)) {
> +			k = rte_bsf32(j);
> +			val ^= get_subvalue(ctx, start - k +
> +				ctx->reta_sz_log - 1);
> +		}
> +		h->compl_table[val] = i;
> +	}
> +}
> +
> +static inline int
> +insert_before(struct rte_thash_ctx *ctx,
> +	struct rte_thash_subtuple_helper *ent,
> +	struct rte_thash_subtuple_helper *cur_ent,
> +	struct rte_thash_subtuple_helper *next_ent,
> +	uint32_t start, uint32_t end, uint32_t range_end)
> +{
> +	int ret;
> +
> +	if (end < cur_ent->offset) {
> +		ent->lfsr = alloc_lfsr(ctx);
> +		if (ent->lfsr == NULL) {
> +			rte_free(ent);
> +			return -ENOMEM;
> +		}
> +		/* generate nonoverlapping range [start, end) */
> +		ret = generate_subkey(ctx, ent->lfsr, start, end - 1);
> +		if (ret != 0) {
> +			free_lfsr(ent->lfsr);
> +			rte_free(ent);
> +			return ret;
> +		}
> +	} else if ((next_ent != NULL) && (end > next_ent->offset)) {
> +		rte_free(ent);
> +		return -ENOSPC;
> +	}
> +	attach_lfsr(ent, cur_ent->lfsr);
> +
> +	/**
> +	 * generate partially overlapping range
> +	 * [start, cur_ent->start) in reverse order
> +	 */
> +	ret = generate_subkey(ctx, ent->lfsr, cur_ent->offset - 1, start);
> +	if (ret != 0) {
> +		free_lfsr(ent->lfsr);
> +		rte_free(ent);
> +		return ret;
> +	}
> +
> +	if (end > range_end) {
> +		/**
> +		 * generate partially overlapping range
> +		 * (range_end, end)
> +		 */
> +		ret = generate_subkey(ctx, ent->lfsr, range_end, end - 1);
> +		if (ret != 0) {
> +			free_lfsr(ent->lfsr);
> +			rte_free(ent);
> +			return ret;
> +		}
> +	}
> +
> +	LIST_INSERT_BEFORE(cur_ent, ent, next);
> +	generate_compliment_table(ctx, ent);
> +	ctx->subtuples_nb++;
> +	return 0;
> +}
> +
> +static inline int
> +insert_after(struct rte_thash_ctx *ctx,
> +	struct rte_thash_subtuple_helper *ent,
> +	struct rte_thash_subtuple_helper *cur_ent,
> +	struct rte_thash_subtuple_helper *next_ent,
> +	struct rte_thash_subtuple_helper *prev_ent,
> +	uint32_t end, uint32_t range_end)
> +{
> +	int ret;
> +
> +	if ((next_ent != NULL) && (end > next_ent->offset)) {
> +		rte_free(ent);
> +		return -EEXIST;
> +	}
> +
> +	attach_lfsr(ent, cur_ent->lfsr);
> +	if (end > range_end) {
> +		/**
> +		 * generate partially overlapping range
> +		 * (range_end, end)
> +		 */
> +		ret = generate_subkey(ctx, ent->lfsr, range_end, end - 1);
> +		if (ret != 0) {
> +			free_lfsr(ent->lfsr);
> +			rte_free(ent);
> +			return ret;
> +		}
> +	}
> +
> +	LIST_INSERT_AFTER(prev_ent, ent, next);
> +	generate_compliment_table(ctx, ent);
> +	ctx->subtuples_nb++;
> +
> +	return 0;
>  }
> 
>  int
> -rte_thash_add_helper(struct rte_thash_ctx *ctx __rte_unused,
> -	const char *name __rte_unused, uint32_t len __rte_unused,
> -	uint32_t offset __rte_unused)
> +rte_thash_add_helper(struct rte_thash_ctx *ctx, const char *name, uint32_t len,
> +	uint32_t offset)
>  {
> +	struct rte_thash_subtuple_helper *ent, *cur_ent, *prev_ent, *next_ent;
> +	uint32_t start, end;
> +	int ret;
> +
> +	if ((ctx == NULL) || (name == NULL) || (len < ctx->reta_sz_log) ||
> +			((offset + len + TOEPLITZ_HASH_LEN - 1) >
> +			ctx->key_len * CHAR_BIT))
> +		return -EINVAL;
> +
> +	/* Check for existing name*/
> +	LIST_FOREACH(cur_ent, &ctx->head, next) {
> +		if (strncmp(name, cur_ent->name, sizeof(cur_ent->name)) == 0)
> +			return -EEXIST;
> +	}
> +
> +	end = offset + len + TOEPLITZ_HASH_LEN - 1;
> +	start = ((ctx->flags & RTE_THASH_MINIMAL_SEQ) ==
> +		RTE_THASH_MINIMAL_SEQ) ? (end - (2 * ctx->reta_sz_log - 1)) :
> +		offset;
> +
> +	ent = rte_zmalloc(NULL, sizeof(struct rte_thash_subtuple_helper) +
> +		sizeof(uint32_t) * (1 << ctx->reta_sz_log), 0);

Helper can be used by data-path code (via rte_thash_get_compliment()) right?
Then might be better to align it at cache-line. 

> +	if (ent == NULL)
> +		return -ENOMEM;
> +
> +	rte_strlcpy(ent->name, name, sizeof(ent->name));
> +	ent->offset = start;
> +	ent->len = end - start;
> +	ent->tuple_offset = offset;
> +	ent->tuple_len = len;
> +	ent->lsb_msk = (1 << ctx->reta_sz_log) - 1;
> +
> +	cur_ent = LIST_FIRST(&ctx->head);
> +	while (cur_ent) {
> +		uint32_t range_end = cur_ent->offset + cur_ent->len;
> +		next_ent = LIST_NEXT(cur_ent, next);
> +		prev_ent = cur_ent;
> +		/* Iterate through overlapping ranges */
> +		while ((next_ent != NULL) && (next_ent->offset < range_end)) {
> +			range_end = RTE_MAX(next_ent->offset + next_ent->len,
> +				range_end);
> +			if (start > next_ent->offset)
> +				prev_ent = next_ent;
> +
> +			next_ent = LIST_NEXT(next_ent, next);
> +		}
> +
> +		if (start < cur_ent->offset)
> +			return insert_before(ctx, ent, cur_ent, next_ent,
> +				start, end, range_end);
> +		else if (start < range_end)
> +			return insert_after(ctx, ent, cur_ent, next_ent,
> +				prev_ent, end, range_end);
> +
> +		cur_ent = next_ent;
> +		continue;
> +	}
> +
> +	ent->lfsr = alloc_lfsr(ctx);
> +	if (ent->lfsr == NULL) {
> +		rte_free(ent);
> +		return -ENOMEM;
> +	}
> +
> +	/* generate nonoverlapping range [start, end) */
> +	ret = generate_subkey(ctx, ent->lfsr, start, end - 1);
> +	if (ret != 0) {
> +		free_lfsr(ent->lfsr);
> +		rte_free(ent);
> +		return ret;
> +	}
> +	if (LIST_EMPTY(&ctx->head)) {
> +		LIST_INSERT_HEAD(&ctx->head, ent, next);
> +	} else {
> +		LIST_FOREACH(next_ent, &ctx->head, next)
> +			prev_ent = next_ent;
> +
> +		LIST_INSERT_AFTER(prev_ent, ent, next);
> +	}
> +	generate_compliment_table(ctx, ent);
> +	ctx->subtuples_nb++;
> +
>  	return 0;
>  }
> 
>  struct rte_thash_subtuple_helper *
> -rte_thash_get_helper(struct rte_thash_ctx *ctx __rte_unused,
> -	const char *name __rte_unused)
> +rte_thash_get_helper(struct rte_thash_ctx *ctx, const char *name)
>  {
> +	struct rte_thash_subtuple_helper *ent;
> +
> +	if ((ctx == NULL) || (name == NULL))
> +		return NULL;
> +
> +	LIST_FOREACH(ent, &ctx->head, next) {
> +		if (strncmp(name, ent->name, sizeof(ent->name)) == 0)
> +			return ent;
> +	}
> +
>  	return NULL;
>  }
> 
>  uint32_t
> -rte_thash_get_compliment(struct rte_thash_subtuple_helper *h __rte_unused,
> -	uint32_t hash __rte_unused, uint32_t desired_hash __rte_unused)
> +rte_thash_get_compliment(struct rte_thash_subtuple_helper *h,
> +	uint32_t hash, uint32_t desired_hash)
>  {
> -	return 0;
> +	return h->compl_table[(hash ^ desired_hash) & h->lsb_msk];
>  }

Would it make sense to add another-one for multi values:
rte_thash_get_compliment(uint32_t hash, const uint32_t desired_hashes[], uint32_t adj_hash[], uint32_t num);
So user can get adjustment values for multiple queues at once? 

> 
>  const uint8_t *
> -rte_thash_get_key(struct rte_thash_ctx *ctx __rte_unused)
> +rte_thash_get_key(struct rte_thash_ctx *ctx)
>  {
> -	return NULL;
> +	return ctx->hash_key;
> +}
> +
> +static inline void
> +xor_bit(uint8_t *ptr, uint32_t bit, uint32_t pos)
> +{
> +	uint32_t byte_idx = pos >> 3;
> +	uint32_t bit_idx = (CHAR_BIT - 1) - (pos & (CHAR_BIT - 1));
> +	uint8_t tmp;
> +
> +	tmp = ptr[byte_idx];
> +	tmp ^= bit << bit_idx;
> +	ptr[byte_idx] = tmp;
> +}
> +
> +int
> +rte_thash_adjust_tuple(struct rte_thash_subtuple_helper *h,
> +	uint8_t *orig_tuple, uint32_t adj_bits,
> +	rte_thash_check_tuple_t fn, void *userdata)
> +{
> +	unsigned i;
> +
> +	if ((h == NULL) || (orig_tuple == NULL))
> +		return -EINVAL;
> +
> +	adj_bits &= h->lsb_msk;
> +	/* Hint: LSB of adj_bits corresponds to offset + len bit of tuple */
> +	for (i = 0; i < sizeof(uint32_t) * CHAR_BIT; i++) {
> +		uint8_t bit = (adj_bits >> i) & 0x1;
> +		if (bit)
> +			xor_bit(orig_tuple, bit,
> +				h->tuple_offset + h->tuple_len - 1 - i);
> +	}
> +
> +	if (fn != NULL)
> +		return (fn(userdata, orig_tuple)) ? 0 : -EEXIST;
> +
> +	return 0;
>  }

Not sure is there much point to have a callback that is called only once.
Might be better to rework the function in a way that user to provide 2 callbacks -
one to generate new value, second to check.
Something like that:

int
rte_thash_gen_tuple(struct rte_thash_subtuple_helper *h,
	uint8_t *tuple, uint32_t desired_hash,
	int (*cb_gen_tuple)(uint8_t *, void *),
	int (*cb_check_tuple)(const uint8_t *, void *),
	void *userdata) 
{
	do {
		rc = cb_gen_tuple(tuple, userdata);
		if (rc != 0)
			return rc;
		hash = rte_softrss(tuple, ...);
		adj = rte_thash_get_compliment(h, hash, desired_hash);
		update_tuple(tuple, adj, ...);
		rc = cb_check_tuple(tuple, userdata); 
	} while(rc != 0);

             return rc;
}

> diff --git a/lib/librte_hash/rte_thash.h b/lib/librte_hash/rte_thash.h
> index 38a641b..fd67931 100644
> --- a/lib/librte_hash/rte_thash.h
> +++ b/lib/librte_hash/rte_thash.h
> @@ -360,6 +360,48 @@ __rte_experimental
>  const uint8_t *
>  rte_thash_get_key(struct rte_thash_ctx *ctx);
> 
> +/**
> + * Function prototype for the rte_thash_adjust_tuple
> + * to check if adjusted tuple could be used.
> + * Generally it is some kind of lookup function to check
> + * if adjusted tuple is already in use.
> + *
> + * @param userdata
> + *  Pointer to the userdata. It could be a pointer to the
> + *  table with used tuples to search.
> + * @param tuple
> + *  Pointer to the tuple to check
> + *
> + * @return
> + *  1 on success
> + *  0 otherwise
> + */
> +typedef int (*rte_thash_check_tuple_t)(void *userdata, uint8_t *tuple);
> +
> +/**
> + * Adjust tuple with complimentary bits.
> + *
> + * @param h
> + *  Pointer to the helper struct
> + * @param orig_tuple
> + *  Pointer to the tuple to be adjusted
> + * @param adj_bits
> + *  Valure returned by rte_thash_get_compliment
> + * @param fn
> + *  Callback function to check adjusted tuple. Could be NULL
> + * @param userdata
> + *  Pointer to the userdata to be passed to fn(). Could be NULL
> + *
> + * @return
> + *  0 on success
> + *  negative otherwise
> + */
> +__rte_experimental
> +int
> +rte_thash_adjust_tuple(struct rte_thash_subtuple_helper *h,
> +	uint8_t *orig_tuple, uint32_t adj_bits,
> +	rte_thash_check_tuple_t fn, void *userdata);
> +
>  #ifdef __cplusplus
>  }
>  #endif
> diff --git a/lib/librte_hash/version.map b/lib/librte_hash/version.map
> index 93cb230..a992a1e 100644
> --- a/lib/librte_hash/version.map
> +++ b/lib/librte_hash/version.map
> @@ -32,6 +32,7 @@ DPDK_21 {
>  EXPERIMENTAL {
>  	global:
> 
> +	rte_thash_adjust_tuple;
>  	rte_hash_free_key_with_position;
>  	rte_hash_lookup_with_hash_bulk;
>  	rte_hash_lookup_with_hash_bulk_data;
> --
> 2.7.4


^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [dpdk-dev] [PATCH v2 0/3] Predictable RSS feature
  2021-04-06 19:50 ` [dpdk-dev] [PATCH v2 0/3] Predictable RSS feature Vladimir Medvedkin
@ 2021-04-08 15:56   ` Stephen Hemminger
  2021-04-11 18:51     ` Medvedkin, Vladimir
  2021-04-10  0:32   ` Wang, Yipeng1
                     ` (4 subsequent siblings)
  5 siblings, 1 reply; 45+ messages in thread
From: Stephen Hemminger @ 2021-04-08 15:56 UTC (permalink / raw)
  To: Vladimir Medvedkin
  Cc: dev, konstantin.ananyev, andrey.chilikin, ray.kinsella,
	yipeng1.wang, sameh.gobriel, bruce.richardson

On Tue,  6 Apr 2021 20:50:40 +0100
Vladimir Medvedkin <vladimir.medvedkin@intel.com> wrote:

> This patch series introduces predictable RSS feature.
> It is based on the idea of searching for partial hash collisions
> within Toeplitz hash.
> 
> The Toeplitz hash function is a homomorphism between (G, ^) and (H, ^),
> where (G, ^) - is a group of tuples and (H, ^) is a group of hashes
> with respect to XOR operation. So tuples and hashes could be treated as
> n-dimension and 32-dimension vector spaces over GF(2).
> So, f(x ^ y) == f(x) ^ f(y)
> where f - is the toeplitz hash function and x, y are tuples.
> 
> The ability to predict partial collisions allows user to compute
> input hash value with desired LSB values.
> Usually number of LSB's are defined by the size of RSS Redirection Table.
> 
> There could be number of use cases, for example:
> 1) NAT. Using this library it is possible to select a new port number
> on a translation in the way that rss hash for original tuple will have
> the same LSB's as rss hash for reverse tuple.
> 2) IPSec/MPLS/Vxlan. It is possible to choose tunnel id to be pinned to
> a desired queue.
> 3) TCP stack. It is possible to choose a source port number for outgoing
> connections in the way that received replies will be assigned to
> desired queue.
> 4) RSS hash key generation. Hash key initialization with random values
> does not guarantee an uniform distribution amongst queues. This library
> uses mathematically proved algorithm to complete the rss hash key to
> provide the best distribution.
> 
> v2:
> - added extra API rte_thash_adjust_tuple()
> - added extra tests for rte_thash_adjust_tuple()
> - added extra fields to rte_thash_subtuple_helper struct
> - fixed typos 
> 
> Vladimir Medvedkin (3):
>   hash: add predictable RSS API
>   hash: add predictable RSS implementation
>   test/hash: add additional thash tests
> 
>  app/test/test_thash.c       | 468 +++++++++++++++++++++++++++++++-
>  lib/librte_hash/meson.build |   3 +-
>  lib/librte_hash/rte_thash.c | 637 ++++++++++++++++++++++++++++++++++++++++++++
>  lib/librte_hash/rte_thash.h | 180 +++++++++++++
>  lib/librte_hash/version.map |   8 +
>  5 files changed, 1289 insertions(+), 7 deletions(-)
>  create mode 100644 lib/librte_hash/rte_thash.c
> 

It would be good to show how this could be used in an application.
Maybe yet another variant/flag to l3fwd example.

^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [dpdk-dev] [PATCH v2 1/3] hash: add predictable RSS API
  2021-04-06 19:50 ` [dpdk-dev] [PATCH v2 1/3] hash: add predictable RSS API Vladimir Medvedkin
@ 2021-04-10  0:05   ` Wang, Yipeng1
  2021-04-11 18:52     ` Medvedkin, Vladimir
  0 siblings, 1 reply; 45+ messages in thread
From: Wang, Yipeng1 @ 2021-04-10  0:05 UTC (permalink / raw)
  To: Medvedkin, Vladimir, dev
  Cc: Ananyev, Konstantin, Chilikin, Andrey, Kinsella, Ray, Gobriel,
	Sameh, Richardson, Bruce, Stephen Hemminger

> -----Original Message-----
> From: Medvedkin, Vladimir <vladimir.medvedkin@intel.com>
> Sent: Tuesday, April 6, 2021 12:51 PM
> To: dev@dpdk.org
> Cc: Ananyev, Konstantin <konstantin.ananyev@intel.com>; Chilikin, Andrey
> <andrey.chilikin@intel.com>; Kinsella, Ray <ray.kinsella@intel.com>; Wang,
> Yipeng1 <yipeng1.wang@intel.com>; Gobriel, Sameh
> <sameh.gobriel@intel.com>; Richardson, Bruce
> <bruce.richardson@intel.com>
> Subject: [PATCH v2 1/3] hash: add predictable RSS API
> 
> This patch adds predictable RSS API.
> It is based on the idea of searching partial Toeplitz hash collisions.
> 
> Signed-off-by: Vladimir Medvedkin <vladimir.medvedkin@intel.com>
> ---
>  lib/librte_hash/meson.build |   3 +-
>  lib/librte_hash/rte_thash.c |  96 ++++++++++++++++++++++++++++++
> lib/librte_hash/rte_thash.h | 138
> ++++++++++++++++++++++++++++++++++++++++++++
>  lib/librte_hash/version.map |   7 +++
>  4 files changed, 243 insertions(+), 1 deletion(-)  create mode 100644
> lib/librte_hash/rte_thash.c
> 
> diff --git a/lib/librte_hash/meson.build b/lib/librte_hash/meson.build index
> 242859f..3546014 100644
> --- a/lib/librte_hash/meson.build
> +++ b/lib/librte_hash/meson.build
> @@ -8,6 +8,7 @@ headers = files('rte_fbk_hash.h',
>  	'rte_thash.h')
>  indirect_headers += files('rte_crc_arm64.h')
> 
> -sources = files('rte_cuckoo_hash.c', 'rte_fbk_hash.c')
> +sources = files('rte_cuckoo_hash.c', 'rte_fbk_hash.c', 'rte_thash.c')
> +deps += ['net']
>  deps += ['ring']
>  deps += ['rcu']
> diff --git a/lib/librte_hash/rte_thash.c b/lib/librte_hash/rte_thash.c new file
> mode 100644 index 0000000..79e8724
> --- /dev/null
> +++ b/lib/librte_hash/rte_thash.c
> @@ -0,0 +1,96 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2021 Intel Corporation
> + */
> +
> +#include <rte_thash.h>
> +#include <rte_tailq.h>
> +#include <rte_random.h>
> +#include <rte_memcpy.h>
> +#include <rte_errno.h>
> +#include <rte_eal.h>
> +#include <rte_eal_memconfig.h>
> +#include <rte_malloc.h>
> +
> +#define THASH_NAME_LEN		64
> +
> +struct thash_lfsr {
> +	uint32_t	ref_cnt;
> +	uint32_t	poly;
> +	/**< polynomial associated with the lfsr */
> +	uint32_t	rev_poly;
> +	/**< polynomial to generate the sequence in reverse direction */
> +	uint32_t	state;
> +	/**< current state of the lfsr */
> +	uint32_t	rev_state;
> +	/**< current state of the lfsr for reverse direction */
> +	uint32_t	deg;	/**< polynomial degree*/
> +	uint32_t	bits_cnt;  /**< number of bits generated by lfsr*/
> +};
> +
> +struct rte_thash_subtuple_helper {
> +	char	name[THASH_NAME_LEN];	/** < Name of subtuple
> configuration */
> +	LIST_ENTRY(rte_thash_subtuple_helper)	next;
> +	struct thash_lfsr	*lfsr;
> +	uint32_t	offset;		/** < Offset in bits of the subtuple */
> +	uint32_t	len;		/** < Length in bits of the subtuple
> */
> +	uint32_t	lsb_msk;	/** < (1 << reta_sz_log) - 1 */
> +	__extension__ uint32_t	compl_table[0] __rte_cache_aligned;
> +	/** < Complimentary table */
> +};
> +
> +struct rte_thash_ctx {
> +	char		name[THASH_NAME_LEN];
> +	LIST_HEAD(, rte_thash_subtuple_helper) head;
> +	uint32_t	key_len;	/** < Length of the NIC RSS hash key
> */
> +	uint32_t	reta_sz_log;	/** < size of the RSS ReTa in bits */
> +	uint32_t	subtuples_nb;	/** < number of subtuples */
> +	uint32_t	flags;
> +	uint8_t		hash_key[0];
> +};
> +
> +struct rte_thash_ctx *
> +rte_thash_init_ctx(const char *name __rte_unused,
> +	uint32_t key_len __rte_unused, uint32_t reta_sz __rte_unused,
> +	uint8_t *key __rte_unused, uint32_t flags __rte_unused) {
> +	return NULL;
> +}
> +
> +struct rte_thash_ctx *
> +rte_thash_find_existing(const char *name __rte_unused) {
> +	return NULL;
> +}
> +
> +void
> +rte_thash_free_ctx(struct rte_thash_ctx *ctx __rte_unused) { }
> +
> +int
> +rte_thash_add_helper(struct rte_thash_ctx *ctx __rte_unused,
> +	const char *name __rte_unused, uint32_t len __rte_unused,
> +	uint32_t offset __rte_unused)
> +{
> +	return 0;
> +}
> +
> +struct rte_thash_subtuple_helper *
> +rte_thash_get_helper(struct rte_thash_ctx *ctx __rte_unused,
> +	const char *name __rte_unused)
> +{
> +	return NULL;
> +}
> +
> +uint32_t
> +rte_thash_get_compliment(struct rte_thash_subtuple_helper *h
> __rte_unused,
> +	uint32_t hash __rte_unused, uint32_t desired_hash __rte_unused) {
> +	return 0;
> +}
> +
> +const uint8_t *
> +rte_thash_get_key(struct rte_thash_ctx *ctx __rte_unused) {
> +	return NULL;
> +}
> diff --git a/lib/librte_hash/rte_thash.h b/lib/librte_hash/rte_thash.h index
> 061efa2..38a641b 100644
> --- a/lib/librte_hash/rte_thash.h
> +++ b/lib/librte_hash/rte_thash.h
> @@ -1,5 +1,6 @@
>  /* SPDX-License-Identifier: BSD-3-Clause
>   * Copyright(c) 2015-2019 Vladimir Medvedkin <medvedkinv@gmail.com>
> + * Copyright(c) 2021 Intel Corporation
>   */
> 
>  #ifndef _RTE_THASH_H
> @@ -222,6 +223,143 @@ rte_softrss_be(uint32_t *input_tuple, uint32_t
> input_len,
>  	return ret;
>  }
> 
> +/**
> + * LFSR will ignore if generated m-sequence has more than 2^n -1 bits
> +*/
[Wang, Yipeng] 
I haven't fully got the significance/reasons behind the two flags.
For the comment above, 2^n is the reta_size right?
If so, it is better than commenting 2^n.

For the first flag:
What would be the issue for overflow? I understand that multiple helpers may overlap
on the m-sequence, but since they are for different tuples, what would be the issue?

For the second flag: is it always good to keep it minimum for each helper?

The goal is to have the best default values for user who do not understand the algorithm details.
Less flags is usually better.

> +#define RTE_THASH_IGNORE_PERIOD_OVERFLOW	0x1
> +/**
> + * Generate minimal required bit (equal to ReTa LSB) sequence into
> + * the hash_key
> + */
> +#define RTE_THASH_MINIMAL_SEQ			0x2
> +
> +/** @internal thash context structure. */ struct rte_thash_ctx;
> +/** @internal thash helper structure. */ struct
> +rte_thash_subtuple_helper;
> +
> +/**
> + * Create a new thash context.
> + *
> + * @param name
> + *  context name
> + * @param key_len
> + *  length of the toeplitz hash key
> + * @param reta_sz
> + *  logarithm of the NIC's Redirection Table (ReTa) size,
> + *  i.e. number of the LSBs if the hash used to determine
> + *  the reta entry.
> + * @param key
[Wang, Yipeng] Key will be modified by helper anyway. What is the reason of having
the users to specify the key here?

> + *  pointer to the key used to init an internal key state.
> + *  Could be NULL, in this case internal key will be inited with random.
> + * @param flags
> + *  supported flags are:
> + *   RTE_THASH_IGNORE_PERIOD_OVERFLOW
> + *   RTE_THASH_MINIMAL_SEQ
> + * @return
> + *  A pointer to the created context on success
> + *  NULL otherwise
> + */
> +__rte_experimental
> +struct rte_thash_ctx *
> +rte_thash_init_ctx(const char *name, uint32_t key_len, uint32_t reta_sz,
> +	uint8_t *key, uint32_t flags);
> +
> +/**
> + * Find an existing thash context and return a pointer to it.
> + *
> + * @param name
> + *  Name of the thash context
> + * @return
> + *  Pointer to the thash context or NULL if it was not found with
> +rte_errno
> + *  set appropriately. Possible rte_errno values include:
> + *   - ENOENT - required entry not available to return.
> + */
> +__rte_experimental
> +struct rte_thash_ctx *
> +rte_thash_find_existing(const char *name);
> +
> +/**
> + * Free a thash context object
> + *
> + * @param ctx
> + *  thash context
> + * @return
> + *  None
> + */
> +__rte_experimental
> +void
> +rte_thash_free_ctx(struct rte_thash_ctx *ctx);
> +
> +/**
> + * Add a special properties to the toeplitz hash key inside a thash context.
> + * Creates an internal helper struct which has a complimentary table
> + * to calculate toeplitz hash collisions.
> + *
> + * @param ctx
> + *  thash context
> + * @param name
> + *  name of the helper
> + * @param len
[Wang, Yipeng] 
Add requirement here so user know the expectation.
e.g. Len should be no shorter than log(reta_size).

> + *  length in bits of the target subtuple
> + * @param offset
> + *  offset in bits of the subtuple
> + * @return
> + *  0 on success
> + *  negative on error
> + */
[Wang, Yipeng] thread-safety for the APIs?
Better to add thread-safety info in the comments.

> +__rte_experimental
> +int
> +rte_thash_add_helper(struct rte_thash_ctx *ctx, const char *name,
> uint32_t len,
> +	uint32_t offset);
> +
> +/**
> + * Find a helper in the context by the given name
> + *
> + * @param ctx
> + *  thash context
> + * @param name
> + *  name of the helper
> + * @return
> + *  Pointer to the thash helper or NULL if it was not found.
> + */
> +__rte_experimental
> +struct rte_thash_subtuple_helper *
> +rte_thash_get_helper(struct rte_thash_ctx *ctx, const char *name);
> +
> +/**
> + * Get a complimentary value for the subtuple to produce a
[Wang, Yipeng] 
Should it be complimentary->complementary?  compliment -> complement?

> + * partial toeplitz hash collision. It muxt be XOR'ed with the
[Wang, Yipeng] typo *must be
> + * subtuple to produce the hash value with the desired hash LSB's
> + *
> + * @param h
> + *  Pointer to the helper struct
> + * @param hash
> + *  toeplitz hash value calculated for the given tuple
> + * @param desired_hash
> + *  desired hash value to find a collision for
> + * @return
> + *  A complimentary value which must be xored with the corresponding
> +subtuple  */ __rte_experimental uint32_t
> +rte_thash_get_compliment(struct rte_thash_subtuple_helper *h,
> +	uint32_t hash, uint32_t desired_hash);
> +
> +/**
> + * Get a pointer to the toeplitz hash contained in the context.
> + * It changes after each addition of a helper. It should be installed
> +to
> + * the NIC.
> + *
> + * @param ctx
> + *  thash context
> + * @return
> + *  A pointer to the toeplitz hash key
> + */
> +__rte_experimental
> +const uint8_t *
> +rte_thash_get_key(struct rte_thash_ctx *ctx);
> +
>  #ifdef __cplusplus
>  }
>  #endif
> diff --git a/lib/librte_hash/version.map b/lib/librte_hash/version.map index
> c6d7308..93cb230 100644
> --- a/lib/librte_hash/version.map
> +++ b/lib/librte_hash/version.map
> @@ -37,4 +37,11 @@ EXPERIMENTAL {
>  	rte_hash_lookup_with_hash_bulk_data;
>  	rte_hash_max_key_id;
>  	rte_hash_rcu_qsbr_add;
> +	rte_thash_add_helper;
> +	rte_thash_find_existing;
> +	rte_thash_free_ctx;
> +	rte_thash_get_compliment;
> +	rte_thash_get_helper;
> +	rte_thash_get_key;
> +	rte_thash_init_ctx;
>  };
> --
> 2.7.4


^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [dpdk-dev] [PATCH v2 2/3] hash: add predictable RSS implementation
  2021-04-06 19:50 ` [dpdk-dev] [PATCH v2 2/3] hash: add predictable RSS implementation Vladimir Medvedkin
  2021-04-07 12:53   ` Ananyev, Konstantin
@ 2021-04-10  0:10   ` Wang, Yipeng1
  2021-04-11 18:52     ` Medvedkin, Vladimir
  1 sibling, 1 reply; 45+ messages in thread
From: Wang, Yipeng1 @ 2021-04-10  0:10 UTC (permalink / raw)
  To: Medvedkin, Vladimir, dev
  Cc: Ananyev, Konstantin, Chilikin, Andrey, Kinsella, Ray, Gobriel,
	Sameh, Richardson, Bruce, Stephen Hemminger

> -----Original Message-----
> From: Medvedkin, Vladimir <vladimir.medvedkin@intel.com>
> Sent: Tuesday, April 6, 2021 12:51 PM
> To: dev@dpdk.org
> Cc: Ananyev, Konstantin <konstantin.ananyev@intel.com>; Chilikin, Andrey
> <andrey.chilikin@intel.com>; Kinsella, Ray <ray.kinsella@intel.com>; Wang,
> Yipeng1 <yipeng1.wang@intel.com>; Gobriel, Sameh
> <sameh.gobriel@intel.com>; Richardson, Bruce
> <bruce.richardson@intel.com>
> Subject: [PATCH v2 2/3] hash: add predictable RSS implementation
> 
> This patch implements predictable RSS functionality.
> 
> Signed-off-by: Vladimir Medvedkin <vladimir.medvedkin@intel.com>
> ---
>  lib/librte_hash/rte_thash.c | 577
> ++++++++++++++++++++++++++++++++++++++++++--
>  lib/librte_hash/rte_thash.h |  42 ++++
>  lib/librte_hash/version.map |   1 +
>  3 files changed, 602 insertions(+), 18 deletions(-)
> 
> diff --git a/lib/librte_hash/rte_thash.c b/lib/librte_hash/rte_thash.c index
> 79e8724..cc60ada 100644
> --- a/lib/librte_hash/rte_thash.c
> +++ b/lib/librte_hash/rte_thash.c
> @@ -12,6 +12,45 @@
>  #include <rte_malloc.h>
> 
>  #define THASH_NAME_LEN		64
> +#define TOEPLITZ_HASH_LEN	32
> +
> +#define	RETA_SZ_MIN	2U
> +#define	RETA_SZ_MAX	16U
> +#define RETA_SZ_IN_RANGE(reta_sz)	((reta_sz >= RETA_SZ_MIN)
> && \
> +					(reta_sz <= RETA_SZ_MAX))
> +
> +TAILQ_HEAD(rte_thash_list, rte_tailq_entry); static struct
> +rte_tailq_elem rte_thash_tailq = {
> +	.name = "RTE_THASH",
> +};
> +EAL_REGISTER_TAILQ(rte_thash_tailq)
> +
> +/**
> + * Table of some irreducible polinomials over GF(2).
> + * For lfsr they are reperesented in BE bit order, and
> + * x^0 is masked out.
> + * For example, poly x^5 + x^2 + 1 will be represented
> + * as (101001b & 11111b) = 01001b = 0x9  */ static const uint32_t
> +irreducible_poly_table[][4] = {
> +	{0, 0, 0, 0},	/** < degree 0 */
> +	{1, 1, 1, 1},	/** < degree 1 */
> +	{0x3, 0x3, 0x3, 0x3},	/** < degree 2 and so on... */
> +	{0x5, 0x3, 0x5, 0x3},
> +	{0x9, 0x3, 0x9, 0x3},
> +	{0x9, 0x1b, 0xf, 0x5},
> +	{0x21, 0x33, 0x1b, 0x2d},
> +	{0x41, 0x11, 0x71, 0x9},
> +	{0x71, 0xa9, 0xf5, 0x8d},
> +	{0x21, 0xd1, 0x69, 0x1d9},
> +	{0x81, 0x2c1, 0x3b1, 0x185},
> +	{0x201, 0x541, 0x341, 0x461},
> +	{0x941, 0x609, 0xe19, 0x45d},
> +	{0x1601, 0x1f51, 0x1171, 0x359},
> +	{0x2141, 0x2111, 0x2db1, 0x2109},
> +	{0x4001, 0x801, 0x101, 0x7301},
> +	{0x7781, 0xa011, 0x4211, 0x86d9},
> +};
> 
>  struct thash_lfsr {
>  	uint32_t	ref_cnt;
> @@ -31,8 +70,10 @@ struct rte_thash_subtuple_helper {
>  	char	name[THASH_NAME_LEN];	/** < Name of subtuple
> configuration */
>  	LIST_ENTRY(rte_thash_subtuple_helper)	next;
>  	struct thash_lfsr	*lfsr;
> -	uint32_t	offset;		/** < Offset in bits of the subtuple */
> -	uint32_t	len;		/** < Length in bits of the subtuple
> */
> +	uint32_t	offset;		/** < Offset of the m-sequence */
> +	uint32_t	len;		/** < Length of the m-sequence */
> +	uint32_t	tuple_offset;	/** < Offset in bits of the subtuple */
> +	uint32_t	tuple_len;	/** < Length in bits of the subtuple
> */
>  	uint32_t	lsb_msk;	/** < (1 << reta_sz_log) - 1 */
>  	__extension__ uint32_t	compl_table[0] __rte_cache_aligned;
>  	/** < Complimentary table */
> @@ -48,49 +89,549 @@ struct rte_thash_ctx {
>  	uint8_t		hash_key[0];
>  };
> 
> +static inline uint32_t
> +get_bit_lfsr(struct thash_lfsr *lfsr)
> +{
> +	uint32_t bit, ret;
> +
> +	/*
> +	 * masking the TAP bits defined by the polynomial and
> +	 * calculating parity
> +	 */
> +	bit = __builtin_popcount(lfsr->state & lfsr->poly) & 0x1;
> +	ret = lfsr->state & 0x1;
> +	lfsr->state = ((lfsr->state >> 1) | (bit << (lfsr->deg - 1))) &
> +		((1 << lfsr->deg) - 1);
> +
> +	lfsr->bits_cnt++;
> +	return ret;
> +}
> +
> +static inline uint32_t
> +get_rev_bit_lfsr(struct thash_lfsr *lfsr) {
> +	uint32_t bit, ret;
> +
> +	bit = __builtin_popcount(lfsr->rev_state & lfsr->rev_poly) & 0x1;
> +	ret = lfsr->rev_state & (1 << (lfsr->deg - 1));
> +	lfsr->rev_state = ((lfsr->rev_state << 1) | bit) &
> +		((1 << lfsr->deg) - 1);
> +
> +	lfsr->bits_cnt++;
> +	return ret;
> +}
> +
> +static inline uint32_t
> +thash_get_rand_poly(uint32_t poly_degree) {
> +	return irreducible_poly_table[poly_degree][rte_rand() %
> +		RTE_DIM(irreducible_poly_table[poly_degree])];
> +}
> +
> +static struct thash_lfsr *
> +alloc_lfsr(struct rte_thash_ctx *ctx)
> +{
> +	struct thash_lfsr *lfsr;
> +	uint32_t i;
> +
> +	if (ctx == NULL)
> +		return NULL;
> +
> +	lfsr = rte_zmalloc(NULL, sizeof(struct thash_lfsr), 0);
> +	if (lfsr == NULL)
> +		return NULL;
> +
> +	lfsr->deg = ctx->reta_sz_log;
> +	lfsr->poly = thash_get_rand_poly(lfsr->deg);
> +	do {
> +		lfsr->state = rte_rand() & ((1 << lfsr->deg) - 1);
> +	} while (lfsr->state == 0);
> +	/* init reverse order polynomial */
> +	lfsr->rev_poly = (lfsr->poly >> 1) | (1 << (lfsr->deg - 1));
> +	/* init proper rev_state*/
> +	lfsr->rev_state = lfsr->state;
> +	for (i = 0; i <= lfsr->deg; i++)
> +		get_rev_bit_lfsr(lfsr);
> +
> +	/* clear bits_cnt after rev_state was inited */
> +	lfsr->bits_cnt = 0;
> +	lfsr->ref_cnt = 1;
> +
> +	return lfsr;
> +}
> +
> +static void
> +attach_lfsr(struct rte_thash_subtuple_helper *h, struct thash_lfsr
> +*lfsr) {
> +	lfsr->ref_cnt++;
> +	h->lfsr = lfsr;
> +}
> +
> +static void
> +free_lfsr(struct thash_lfsr *lfsr)
> +{
> +	lfsr->ref_cnt--;
> +	if (lfsr->ref_cnt == 0)
> +		rte_free(lfsr);
> +}
> +
>  struct rte_thash_ctx *
> -rte_thash_init_ctx(const char *name __rte_unused,
> -	uint32_t key_len __rte_unused, uint32_t reta_sz __rte_unused,
> -	uint8_t *key __rte_unused, uint32_t flags __rte_unused)
> +rte_thash_init_ctx(const char *name, uint32_t key_len, uint32_t reta_sz,
> +	uint8_t *key, uint32_t flags)
>  {
> +	struct rte_thash_ctx *ctx;
> +	struct rte_tailq_entry *te;
> +	struct rte_thash_list *thash_list;
> +	uint32_t i;
> +	if ((name == NULL) || (key_len == 0)
> || !RETA_SZ_IN_RANGE(reta_sz)) {
> +		rte_errno = EINVAL;
> +		return NULL;
> +	}
> +
> +	thash_list = RTE_TAILQ_CAST(rte_thash_tailq.head, rte_thash_list);
> +
> +	rte_mcfg_tailq_write_lock();
> +
> +	/* guarantee there's no existing */
> +	TAILQ_FOREACH(te, thash_list, next) {
> +		ctx = (struct rte_thash_ctx *)te->data;
> +		if (strncmp(name, ctx->name, sizeof(ctx->name)) == 0)
> +			break;
> +	}
> +	ctx = NULL;
> +	if (te != NULL) {
> +		rte_errno = EEXIST;
> +		goto exit;
> +	}
> +
> +	/* allocate tailq entry */
> +	te = rte_zmalloc("THASH_TAILQ_ENTRY", sizeof(*te), 0);
> +	if (te == NULL) {
> +		RTE_LOG(ERR, HASH,
> +			"Can not allocate tailq entry for thash context %s\n",
> +			name);
> +		rte_errno = ENOMEM;
> +		goto exit;
> +	}
> +
> +	ctx = rte_zmalloc(NULL, sizeof(struct rte_thash_ctx) + key_len, 0);
> +	if (ctx == NULL) {
> +		RTE_LOG(ERR, HASH, "thash ctx %s memory allocation
> failed\n",
> +			name);
> +		rte_errno = ENOMEM;
> +		goto free_te;
> +	}
> +
> +	rte_strlcpy(ctx->name, name, sizeof(ctx->name));
> +	ctx->key_len = key_len;
> +	ctx->reta_sz_log = reta_sz;
> +	LIST_INIT(&ctx->head);
> +	ctx->flags = flags;
> +
> +	if (key)
> +		rte_memcpy(ctx->hash_key, key, key_len);
> +	else {
> +		for (i = 0; i < key_len; i++)
> +			ctx->hash_key[i] = rte_rand();
> +	}
> +
> +	te->data = (void *)ctx;
> +	TAILQ_INSERT_TAIL(thash_list, te, next);
> +
> +	rte_mcfg_tailq_write_unlock();
> +
> +	return ctx;
> +free_te:
> +	rte_free(te);
> +exit:
> +	rte_mcfg_tailq_write_unlock();
>  	return NULL;
>  }
> 
>  struct rte_thash_ctx *
> -rte_thash_find_existing(const char *name __rte_unused)
> +rte_thash_find_existing(const char *name)
>  {
> -	return NULL;
> +	struct rte_thash_ctx *ctx;
> +	struct rte_tailq_entry *te;
> +	struct rte_thash_list *thash_list;
> +
> +	thash_list = RTE_TAILQ_CAST(rte_thash_tailq.head, rte_thash_list);
> +
> +	rte_mcfg_tailq_read_lock();
> +	TAILQ_FOREACH(te, thash_list, next) {
> +		ctx = (struct rte_thash_ctx *)te->data;
> +		if (strncmp(name, ctx->name, sizeof(ctx->name)) == 0)
> +			break;
> +	}
> +
> +	rte_mcfg_tailq_read_unlock();
> +
> +	if (te == NULL) {
> +		rte_errno = ENOENT;
> +		return NULL;
> +	}
> +
> +	return ctx;
>  }
> 
>  void
> -rte_thash_free_ctx(struct rte_thash_ctx *ctx __rte_unused)
> +rte_thash_free_ctx(struct rte_thash_ctx *ctx)
>  {
> +	struct rte_tailq_entry *te;
> +	struct rte_thash_list *thash_list;
> +	struct rte_thash_subtuple_helper *ent, *tmp;
> +
> +	if (ctx == NULL)
> +		return;
> +
> +	thash_list = RTE_TAILQ_CAST(rte_thash_tailq.head, rte_thash_list);
> +	rte_mcfg_tailq_write_lock();
> +	TAILQ_FOREACH(te, thash_list, next) {
> +		if (te->data == (void *)ctx)
> +			break;
> +	}
> +
> +	if (te != NULL)
> +		TAILQ_REMOVE(thash_list, te, next);
> +
> +	rte_mcfg_tailq_write_unlock();
> +	ent = LIST_FIRST(&(ctx->head));
> +	while (ent) {
> +		free_lfsr(ent->lfsr);
> +		tmp = ent;
> +		ent = LIST_NEXT(ent, next);
> +		LIST_REMOVE(tmp, next);
> +		rte_free(tmp);
> +	}
> +
> +	rte_free(ctx);
> +	rte_free(te);
> +}
> +
> +static inline void
> +set_bit(uint8_t *ptr, uint32_t bit, uint32_t pos) {
> +	uint32_t byte_idx = pos >> 3;
> +	uint32_t bit_idx = (CHAR_BIT - 1) - (pos & (CHAR_BIT - 1));
> +	uint8_t tmp;
> +
> +	tmp = ptr[byte_idx];
> +	tmp &= ~(1 << bit_idx);
> +	tmp |= bit << bit_idx;
> +	ptr[byte_idx] = tmp;
> +}
> +
> +/**
> + * writes m-sequence to the hash_key for range [start, end]
> + * (i.e. including start and end positions)  */ static int
> +generate_subkey(struct rte_thash_ctx *ctx, struct thash_lfsr *lfsr,
> +	uint32_t start, uint32_t end)
> +{
> +	uint32_t i;
> +	uint32_t req_bits = (start < end) ? (end - start) : (start - end);
> +	req_bits++; /* due to incuding end */
> +
> +	/* check if lfsr overflow period of the m-sequence */
> +	if (((lfsr->bits_cnt + req_bits) > (1ULL << lfsr->deg) - 1) &&
> +			((ctx->flags &
> RTE_THASH_IGNORE_PERIOD_OVERFLOW) !=
> +			RTE_THASH_IGNORE_PERIOD_OVERFLOW))
> +		return -ENOSPC;
[Wang, Yipeng] 
If nospace, should one increase lfsr->deg? Or if it is already the highest deg you predefined then what to do?
Maybe a log msg could help user with more information on the solutions.
> +
> +	if (start < end) {
> +		/* original direction (from left to right)*/
> +		for (i = start; i <= end; i++)
> +			set_bit(ctx->hash_key, get_bit_lfsr(lfsr), i);
> +
> +	} else {
> +		/* reverse direction (from right to left) */
> +		for (i = end; i >= start; i--)
> +			set_bit(ctx->hash_key, get_rev_bit_lfsr(lfsr), i);
> +	}
> +
> +	return 0;
> +}
> +
> +static inline uint32_t
> +get_subvalue(struct rte_thash_ctx *ctx, uint32_t offset) {
> +	uint32_t *tmp, val;
> +
> +	tmp = (uint32_t *)(&ctx->hash_key[offset >> 3]);
> +	val = rte_be_to_cpu_32(*tmp);
> +	val >>= (TOEPLITZ_HASH_LEN - ((offset & (CHAR_BIT - 1)) +
> +		ctx->reta_sz_log));
> +
> +	return val & ((1 << ctx->reta_sz_log) - 1); }
> +
> +static inline void
> +generate_compliment_table(struct rte_thash_ctx *ctx,
> +	struct rte_thash_subtuple_helper *h)
> +{
> +	int i, j, k;
> +	uint32_t val;
> +	uint32_t start;
> +
> +	start = h->offset + h->len - (2 * ctx->reta_sz_log - 1);
> +
> +	for (i = 1; i < (1 << ctx->reta_sz_log); i++) {
> +		val = 0;
> +		for (j = i; j; j &= (j - 1)) {
> +			k = rte_bsf32(j);
> +			val ^= get_subvalue(ctx, start - k +
> +				ctx->reta_sz_log - 1);
> +		}
> +		h->compl_table[val] = i;
> +	}
> +}
> +
> +static inline int
> +insert_before(struct rte_thash_ctx *ctx,
> +	struct rte_thash_subtuple_helper *ent,
> +	struct rte_thash_subtuple_helper *cur_ent,
> +	struct rte_thash_subtuple_helper *next_ent,
> +	uint32_t start, uint32_t end, uint32_t range_end) {
> +	int ret;
> +
> +	if (end < cur_ent->offset) {
> +		ent->lfsr = alloc_lfsr(ctx);
> +		if (ent->lfsr == NULL) {
> +			rte_free(ent);
> +			return -ENOMEM;
> +		}
> +		/* generate nonoverlapping range [start, end) */
> +		ret = generate_subkey(ctx, ent->lfsr, start, end - 1);
> +		if (ret != 0) {
> +			free_lfsr(ent->lfsr);
> +			rte_free(ent);
> +			return ret;
> +		}
> +	} else if ((next_ent != NULL) && (end > next_ent->offset)) {
> +		rte_free(ent);
> +		return -ENOSPC;
> +	}
> +	attach_lfsr(ent, cur_ent->lfsr);
> +
> +	/**
> +	 * generate partially overlapping range
> +	 * [start, cur_ent->start) in reverse order
> +	 */
> +	ret = generate_subkey(ctx, ent->lfsr, cur_ent->offset - 1, start);
> +	if (ret != 0) {
> +		free_lfsr(ent->lfsr);
> +		rte_free(ent);
> +		return ret;
> +	}
> +
> +	if (end > range_end) {
> +		/**
> +		 * generate partially overlapping range
> +		 * (range_end, end)
> +		 */
> +		ret = generate_subkey(ctx, ent->lfsr, range_end, end - 1);
> +		if (ret != 0) {
> +			free_lfsr(ent->lfsr);
> +			rte_free(ent);
> +			return ret;
> +		}
> +	}
> +
> +	LIST_INSERT_BEFORE(cur_ent, ent, next);
> +	generate_compliment_table(ctx, ent);
> +	ctx->subtuples_nb++;
> +	return 0;
> +}
> +
> +static inline int
> +insert_after(struct rte_thash_ctx *ctx,
> +	struct rte_thash_subtuple_helper *ent,
> +	struct rte_thash_subtuple_helper *cur_ent,
> +	struct rte_thash_subtuple_helper *next_ent,
> +	struct rte_thash_subtuple_helper *prev_ent,
> +	uint32_t end, uint32_t range_end)
> +{
> +	int ret;
> +
> +	if ((next_ent != NULL) && (end > next_ent->offset)) {
> +		rte_free(ent);
> +		return -EEXIST;
> +	}
> +
> +	attach_lfsr(ent, cur_ent->lfsr);
> +	if (end > range_end) {
> +		/**
> +		 * generate partially overlapping range
> +		 * (range_end, end)
> +		 */
> +		ret = generate_subkey(ctx, ent->lfsr, range_end, end - 1);
> +		if (ret != 0) {
> +			free_lfsr(ent->lfsr);
> +			rte_free(ent);
> +			return ret;
> +		}
> +	}
> +
> +	LIST_INSERT_AFTER(prev_ent, ent, next);
> +	generate_compliment_table(ctx, ent);
> +	ctx->subtuples_nb++;
> +
> +	return 0;
>  }
> 
>  int
> -rte_thash_add_helper(struct rte_thash_ctx *ctx __rte_unused,
> -	const char *name __rte_unused, uint32_t len __rte_unused,
> -	uint32_t offset __rte_unused)
> +rte_thash_add_helper(struct rte_thash_ctx *ctx, const char *name,
> uint32_t len,
> +	uint32_t offset)
>  {
> +	struct rte_thash_subtuple_helper *ent, *cur_ent, *prev_ent,
> *next_ent;
> +	uint32_t start, end;
> +	int ret;
> +
> +	if ((ctx == NULL) || (name == NULL) || (len < ctx->reta_sz_log) ||
> +			((offset + len + TOEPLITZ_HASH_LEN - 1) >
> +			ctx->key_len * CHAR_BIT))
> +		return -EINVAL;
> +
> +	/* Check for existing name*/
> +	LIST_FOREACH(cur_ent, &ctx->head, next) {
> +		if (strncmp(name, cur_ent->name, sizeof(cur_ent->name))
> == 0)
> +			return -EEXIST;
> +	}
> +
> +	end = offset + len + TOEPLITZ_HASH_LEN - 1;
> +	start = ((ctx->flags & RTE_THASH_MINIMAL_SEQ) ==
> +		RTE_THASH_MINIMAL_SEQ) ? (end - (2 * ctx->reta_sz_log -
> 1)) :
> +		offset;
> +
> +	ent = rte_zmalloc(NULL, sizeof(struct rte_thash_subtuple_helper) +
> +		sizeof(uint32_t) * (1 << ctx->reta_sz_log), 0);
> +	if (ent == NULL)
> +		return -ENOMEM;
> +
> +	rte_strlcpy(ent->name, name, sizeof(ent->name));
> +	ent->offset = start;
> +	ent->len = end - start;
> +	ent->tuple_offset = offset;
> +	ent->tuple_len = len;
> +	ent->lsb_msk = (1 << ctx->reta_sz_log) - 1;
> +
> +	cur_ent = LIST_FIRST(&ctx->head);
> +	while (cur_ent) {
> +		uint32_t range_end = cur_ent->offset + cur_ent->len;
> +		next_ent = LIST_NEXT(cur_ent, next);
> +		prev_ent = cur_ent;
> +		/* Iterate through overlapping ranges */
> +		while ((next_ent != NULL) && (next_ent->offset <
> range_end)) {
> +			range_end = RTE_MAX(next_ent->offset +
> next_ent->len,
> +				range_end);
> +			if (start > next_ent->offset)
> +				prev_ent = next_ent;
> +
> +			next_ent = LIST_NEXT(next_ent, next);
> +		}
> +
> +		if (start < cur_ent->offset)
> +			return insert_before(ctx, ent, cur_ent, next_ent,
> +				start, end, range_end);
> +		else if (start < range_end)
> +			return insert_after(ctx, ent, cur_ent, next_ent,
> +				prev_ent, end, range_end);
> +
> +		cur_ent = next_ent;
> +		continue;
> +	}
> +
> +	ent->lfsr = alloc_lfsr(ctx);
> +	if (ent->lfsr == NULL) {
> +		rte_free(ent);
> +		return -ENOMEM;
> +	}
> +
> +	/* generate nonoverlapping range [start, end) */
> +	ret = generate_subkey(ctx, ent->lfsr, start, end - 1);
> +	if (ret != 0) {
> +		free_lfsr(ent->lfsr);
> +		rte_free(ent);
> +		return ret;
> +	}
> +	if (LIST_EMPTY(&ctx->head)) {
> +		LIST_INSERT_HEAD(&ctx->head, ent, next);
> +	} else {
> +		LIST_FOREACH(next_ent, &ctx->head, next)
> +			prev_ent = next_ent;
> +
> +		LIST_INSERT_AFTER(prev_ent, ent, next);
> +	}
> +	generate_compliment_table(ctx, ent);
> +	ctx->subtuples_nb++;
> +
>  	return 0;
>  }
> 
>  struct rte_thash_subtuple_helper *
> -rte_thash_get_helper(struct rte_thash_ctx *ctx __rte_unused,
> -	const char *name __rte_unused)
> +rte_thash_get_helper(struct rte_thash_ctx *ctx, const char *name)
>  {
> +	struct rte_thash_subtuple_helper *ent;
> +
> +	if ((ctx == NULL) || (name == NULL))
> +		return NULL;
> +
> +	LIST_FOREACH(ent, &ctx->head, next) {
> +		if (strncmp(name, ent->name, sizeof(ent->name)) == 0)
> +			return ent;
> +	}
> +
>  	return NULL;
>  }
> 
>  uint32_t
> -rte_thash_get_compliment(struct rte_thash_subtuple_helper *h
> __rte_unused,
> -	uint32_t hash __rte_unused, uint32_t desired_hash __rte_unused)
> +rte_thash_get_compliment(struct rte_thash_subtuple_helper *h,
> +	uint32_t hash, uint32_t desired_hash)
>  {
> -	return 0;
> +	return h->compl_table[(hash ^ desired_hash) & h->lsb_msk];
>  }
> 
>  const uint8_t *
> -rte_thash_get_key(struct rte_thash_ctx *ctx __rte_unused)
> +rte_thash_get_key(struct rte_thash_ctx *ctx)
>  {
> -	return NULL;
> +	return ctx->hash_key;
> +}
> +
> +static inline void
> +xor_bit(uint8_t *ptr, uint32_t bit, uint32_t pos) {
> +	uint32_t byte_idx = pos >> 3;
> +	uint32_t bit_idx = (CHAR_BIT - 1) - (pos & (CHAR_BIT - 1));
> +	uint8_t tmp;
> +
> +	tmp = ptr[byte_idx];
> +	tmp ^= bit << bit_idx;
> +	ptr[byte_idx] = tmp;
> +}
> +
> +int
> +rte_thash_adjust_tuple(struct rte_thash_subtuple_helper *h,
> +	uint8_t *orig_tuple, uint32_t adj_bits,
> +	rte_thash_check_tuple_t fn, void *userdata) {
> +	unsigned i;
> +
> +	if ((h == NULL) || (orig_tuple == NULL))
> +		return -EINVAL;
> +
> +	adj_bits &= h->lsb_msk;
> +	/* Hint: LSB of adj_bits corresponds to offset + len bit of tuple */
> +	for (i = 0; i < sizeof(uint32_t) * CHAR_BIT; i++) {
> +		uint8_t bit = (adj_bits >> i) & 0x1;
> +		if (bit)
> +			xor_bit(orig_tuple, bit,
> +				h->tuple_offset + h->tuple_len - 1 - i);
> +	}
> +
> +	if (fn != NULL)
> +		return (fn(userdata, orig_tuple)) ? 0 : -EEXIST;
> +
> +	return 0;
>  }
> diff --git a/lib/librte_hash/rte_thash.h b/lib/librte_hash/rte_thash.h index
> 38a641b..fd67931 100644
> --- a/lib/librte_hash/rte_thash.h
> +++ b/lib/librte_hash/rte_thash.h
> @@ -360,6 +360,48 @@ __rte_experimental
>  const uint8_t *
>  rte_thash_get_key(struct rte_thash_ctx *ctx);
> 
> +/**
> + * Function prototype for the rte_thash_adjust_tuple
> + * to check if adjusted tuple could be used.
> + * Generally it is some kind of lookup function to check
> + * if adjusted tuple is already in use.
> + *
> + * @param userdata
> + *  Pointer to the userdata. It could be a pointer to the
> + *  table with used tuples to search.
> + * @param tuple
> + *  Pointer to the tuple to check
> + *
> + * @return
> + *  1 on success
> + *  0 otherwise
> + */
> +typedef int (*rte_thash_check_tuple_t)(void *userdata, uint8_t *tuple);
> +
> +/**
> + * Adjust tuple with complimentary bits.
> + *
[Wang, Yipeng] 
More explanation for this API is needed.
My understanding is that user should call this function in a loop, until
the above callback function returns success thus this function succeeds.
BTW, why not put this API in the first API commit?

> + * @param h
> + *  Pointer to the helper struct
> + * @param orig_tuple
> + *  Pointer to the tuple to be adjusted
> + * @param adj_bits
> + *  Valure returned by rte_thash_get_compliment
[Wang, Yipeng] typo. *value
> + * @param fn
> + *  Callback function to check adjusted tuple. Could be NULL
> + * @param userdata
> + *  Pointer to the userdata to be passed to fn(). Could be NULL
> + *
> + * @return
> + *  0 on success
> + *  negative otherwise
> + */
> +__rte_experimental
> +int
> +rte_thash_adjust_tuple(struct rte_thash_subtuple_helper *h,
> +	uint8_t *orig_tuple, uint32_t adj_bits,
> +	rte_thash_check_tuple_t fn, void *userdata);
> +
>  #ifdef __cplusplus
>  }
>  #endif
> diff --git a/lib/librte_hash/version.map b/lib/librte_hash/version.map index
> 93cb230..a992a1e 100644
> --- a/lib/librte_hash/version.map
> +++ b/lib/librte_hash/version.map
> @@ -32,6 +32,7 @@ DPDK_21 {
>  EXPERIMENTAL {
>  	global:
> 
> +	rte_thash_adjust_tuple;
>  	rte_hash_free_key_with_position;
>  	rte_hash_lookup_with_hash_bulk;
>  	rte_hash_lookup_with_hash_bulk_data;
> --
> 2.7.4


^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [dpdk-dev] [PATCH v2 0/3] Predictable RSS feature
  2021-04-06 19:50 ` [dpdk-dev] [PATCH v2 0/3] Predictable RSS feature Vladimir Medvedkin
  2021-04-08 15:56   ` Stephen Hemminger
@ 2021-04-10  0:32   ` Wang, Yipeng1
  2021-04-11 18:51     ` Medvedkin, Vladimir
  2021-04-11 19:11   ` [dpdk-dev] [PATCH v3 " Vladimir Medvedkin
                     ` (3 subsequent siblings)
  5 siblings, 1 reply; 45+ messages in thread
From: Wang, Yipeng1 @ 2021-04-10  0:32 UTC (permalink / raw)
  To: Medvedkin, Vladimir, dev
  Cc: Ananyev, Konstantin, Chilikin, Andrey, Kinsella, Ray, Gobriel,
	Sameh, Richardson, Bruce, Stephen Hemminger

> -----Original Message-----
> From: Medvedkin, Vladimir <vladimir.medvedkin@intel.com>
> Sent: Tuesday, April 6, 2021 12:51 PM
> To: dev@dpdk.org
> Cc: Ananyev, Konstantin <konstantin.ananyev@intel.com>; Chilikin, Andrey
> <andrey.chilikin@intel.com>; Kinsella, Ray <ray.kinsella@intel.com>; Wang,
> Yipeng1 <yipeng1.wang@intel.com>; Gobriel, Sameh
> <sameh.gobriel@intel.com>; Richardson, Bruce
> <bruce.richardson@intel.com>
> Subject: [PATCH v2 0/3] Predictable RSS feature
> 
> This patch series introduces predictable RSS feature.
> It is based on the idea of searching for partial hash collisions within Toeplitz
> hash.
> 
> The Toeplitz hash function is a homomorphism between (G, ^) and (H, ^),
> where (G, ^) - is a group of tuples and (H, ^) is a group of hashes with respect
> to XOR operation. So tuples and hashes could be treated as n-dimension and
> 32-dimension vector spaces over GF(2).
> So, f(x ^ y) == f(x) ^ f(y)
> where f - is the toeplitz hash function and x, y are tuples.
> 
> The ability to predict partial collisions allows user to compute input hash value
> with desired LSB values.
> Usually number of LSB's are defined by the size of RSS Redirection Table.
> 
> There could be number of use cases, for example:
> 1) NAT. Using this library it is possible to select a new port number on a
> translation in the way that rss hash for original tuple will have the same LSB's
> as rss hash for reverse tuple.
> 2) IPSec/MPLS/Vxlan. It is possible to choose tunnel id to be pinned to a
> desired queue.
> 3) TCP stack. It is possible to choose a source port number for outgoing
> connections in the way that received replies will be assigned to desired
> queue.
> 4) RSS hash key generation. Hash key initialization with random values does
> not guarantee an uniform distribution amongst queues. This library uses
> mathematically proved algorithm to complete the rss hash key to provide the
> best distribution.
> 
> v2:
> - added extra API rte_thash_adjust_tuple()
> - added extra tests for rte_thash_adjust_tuple()
> - added extra fields to rte_thash_subtuple_helper struct
> - fixed typos
> 
> Vladimir Medvedkin (3):
>   hash: add predictable RSS API
>   hash: add predictable RSS implementation
>   test/hash: add additional thash tests
> 
>  app/test/test_thash.c       | 468 +++++++++++++++++++++++++++++++-
>  lib/librte_hash/meson.build |   3 +-
>  lib/librte_hash/rte_thash.c | 637
> ++++++++++++++++++++++++++++++++++++++++++++
>  lib/librte_hash/rte_thash.h | 180 +++++++++++++
>  lib/librte_hash/version.map |   8 +
>  5 files changed, 1289 insertions(+), 7 deletions(-)  create mode 100644
> lib/librte_hash/rte_thash.c
> 
> --
> 2.7.4

[Wang, Yipeng] 
Hi, Vladimir, thanks for the patch!
I haven't fully understood every bit of the algorithm yet, 
but I did see issues that this patch could potentially solve.
My understanding is that there are some restrictions for the current implementation,
for example, it only supports port(16-bit) manipulation, but not multiple fields or IP. 
Still, I think it should be good for the use cases you listed. I would love to hear
more feedbacks from people who are more familiar with doing NAT in production systems.

For me, besides the comments I sent earlier,
good documentation and references are needed with clear usage examples, as others pointed out already.

Also, the current API design seems a bit cumbersome.
To use the library, one needs:
Init_ctx
Add_helper.
Get_helper
Get_complement
Then in a loop:
Adjust_tuples
Then XOR with the current tuple

I wonder if an alternative all-in-one API could be designed for simpler use cases.

Thanks!




^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [dpdk-dev] [PATCH v2 2/3] hash: add predictable RSS implementation
  2021-04-07 12:53   ` Ananyev, Konstantin
@ 2021-04-11 18:51     ` Medvedkin, Vladimir
  2021-04-12  9:47       ` Ananyev, Konstantin
  0 siblings, 1 reply; 45+ messages in thread
From: Medvedkin, Vladimir @ 2021-04-11 18:51 UTC (permalink / raw)
  To: Ananyev, Konstantin, dev
  Cc: Chilikin, Andrey, Kinsella, Ray, Wang, Yipeng1, Gobriel, Sameh,
	Richardson, Bruce

Hi Konstantin,

Thanks for the review,

On 07/04/2021 15:53, Ananyev, Konstantin wrote:
> Hi Vladimir,
> 
> Few comments below, mostly minor.
> One generic one - doc seems missing.
> With that in place:
> Acked-by: Konstantin Ananyev <konstantin.ananyev@intel.com>
> 
>>
>> This patch implements predictable RSS functionality.
>>
>> Signed-off-by: Vladimir Medvedkin <vladimir.medvedkin@intel.com>

<snip>

>> +#defineRETA_SZ_MIN2U
>> +#defineRETA_SZ_MAX16U
> 
> Should these RETA_SZ defines be in public header?
> So user can know what are allowed values?
> 

I don't think this is necessary, because the user chooses it not 
arbitrary, but depending on the NIC.

>> +#define RETA_SZ_IN_RANGE(reta_sz)((reta_sz >= RETA_SZ_MIN) && \

<snip>

>> +uint32_t i;
> 
> Empty line is  missing.
> 

Thanks

>> +if ((name == NULL) || (key_len == 0) || !RETA_SZ_IN_RANGE(reta_sz)) {
>> +rte_errno = EINVAL;
>> +return NULL;
>> +}

<snip>

>> +static inline void
>> +set_bit(uint8_t *ptr, uint32_t bit, uint32_t pos)
>> +{
>> +uint32_t byte_idx = pos >> 3;
> 
> Just as a nit to be consistent with the line below:
> pos / CHAR_BIT;
> 

Fixed

>> +uint32_t bit_idx = (CHAR_BIT - 1) - (pos & (CHAR_BIT - 1));
>> +uint8_t tmp;

<snip>

>> +ent = rte_zmalloc(NULL, sizeof(struct rte_thash_subtuple_helper) +
>> +sizeof(uint32_t) * (1 << ctx->reta_sz_log), 0);
> 
> Helper can be used by data-path code (via rte_thash_get_compliment()) right?
> Then might be better to align it at cache-line.
> 

Agree, I'll fix it

>> +if (ent == NULL)
>> +return -ENOMEM;

<snip>

>>   uint32_t
>> -rte_thash_get_compliment(struct rte_thash_subtuple_helper *h __rte_unused,
>> -uint32_t hash __rte_unused, uint32_t desired_hash __rte_unused)
>> +rte_thash_get_compliment(struct rte_thash_subtuple_helper *h,
>> +uint32_t hash, uint32_t desired_hash)
>>   {
>> -return 0;
>> +return h->compl_table[(hash ^ desired_hash) & h->lsb_msk];
>>   }
> 
> Would it make sense to add another-one for multi values:
> rte_thash_get_compliment(uint32_t hash, const uint32_t desired_hashes[], uint32_t adj_hash[], uint32_t num);
> So user can get adjustment values for multiple queues at once?
> 

At the moment I can't find scenarios why do we need to have a bulk 
version for this function

>>
>>   const uint8_t *
>> -rte_thash_get_key(struct rte_thash_ctx *ctx __rte_unused)
>> +rte_thash_get_key(struct rte_thash_ctx *ctx)
>>   {
>> -return NULL;
>> +return ctx->hash_key;
>> +}
>> +
>> +static inline void
>> +xor_bit(uint8_t *ptr, uint32_t bit, uint32_t pos)
>> +{
>> +uint32_t byte_idx = pos >> 3;
>> +uint32_t bit_idx = (CHAR_BIT - 1) - (pos & (CHAR_BIT - 1));
>> +uint8_t tmp;
>> +
>> +tmp = ptr[byte_idx];
>> +tmp ^= bit << bit_idx;
>> +ptr[byte_idx] = tmp;
>> +}
>> +
>> +int
>> +rte_thash_adjust_tuple(struct rte_thash_subtuple_helper *h,
>> +uint8_t *orig_tuple, uint32_t adj_bits,
>> +rte_thash_check_tuple_t fn, void *userdata)
>> +{
>> +unsigned i;
>> +
>> +if ((h == NULL) || (orig_tuple == NULL))
>> +return -EINVAL;
>> +
>> +adj_bits &= h->lsb_msk;
>> +/* Hint: LSB of adj_bits corresponds to offset + len bit of tuple */
>> +for (i = 0; i < sizeof(uint32_t) * CHAR_BIT; i++) {
>> +uint8_t bit = (adj_bits >> i) & 0x1;
>> +if (bit)
>> +xor_bit(orig_tuple, bit,
>> +h->tuple_offset + h->tuple_len - 1 - i);
>> +}
>> +
>> +if (fn != NULL)
>> +return (fn(userdata, orig_tuple)) ? 0 : -EEXIST;
>> +
>> +return 0;
>>   }
> 
> Not sure is there much point to have a callback that is called only once.
> Might be better to rework the function in a way that user to provide 2 callbacks -
> one to generate new value, second to check.
> Something like that:
> 
> int
> rte_thash_gen_tuple(struct rte_thash_subtuple_helper *h,
> uint8_t *tuple, uint32_t desired_hash,
> int (*cb_gen_tuple)(uint8_t *, void *),
> int (*cb_check_tuple)(const uint8_t *, void *),
> void *userdata)
> {
> do {
> rc = cb_gen_tuple(tuple, userdata);
> if (rc != 0)
> return rc;
> hash = rte_softrss(tuple, ...);
> adj = rte_thash_get_compliment(h, hash, desired_hash);
> update_tuple(tuple, adj, ...);
> rc = cb_check_tuple(tuple, userdata);
> } while(rc != 0);
> 
>               return rc;
> }

Agree, there is no point to call the callback for a single function 
call. I'll rewrite rte_thash_adjust_tuple() and send a new version an 
v3. As for gen_tuple, I think we don't need to have a separate callback,
new rte_thash_adjust_tuple implementation randomly changes corresponding 
bits (based on configured offset and length in the helper) in the tuple.

> 
>> diff --git a/lib/librte_hash/rte_thash.h b/lib/librte_hash/rte_thash.h
>> index 38a641b..fd67931 100644
>> --- a/lib/librte_hash/rte_thash.h
>> +++ b/lib/librte_hash/rte_thash.h
>> @@ -360,6 +360,48 @@ __rte_experimental
>>   const uint8_t *
>>   rte_thash_get_key(struct rte_thash_ctx *ctx);
>>
>> +/**
>> + * Function prototype for the rte_thash_adjust_tuple
>> + * to check if adjusted tuple could be used.
>> + * Generally it is some kind of lookup function to check
>> + * if adjusted tuple is already in use.
>> + *
>> + * @param userdata
>> + *  Pointer to the userdata. It could be a pointer to the
>> + *  table with used tuples to search.
>> + * @param tuple
>> + *  Pointer to the tuple to check
>> + *
>> + * @return
>> + *  1 on success
>> + *  0 otherwise
>> + */
>> +typedef int (*rte_thash_check_tuple_t)(void *userdata, uint8_t *tuple);
>> +
>> +/**
>> + * Adjust tuple with complimentary bits.
>> + *
>> + * @param h
>> + *  Pointer to the helper struct
>> + * @param orig_tuple
>> + *  Pointer to the tuple to be adjusted
>> + * @param adj_bits
>> + *  Valure returned by rte_thash_get_compliment
>> + * @param fn
>> + *  Callback function to check adjusted tuple. Could be NULL
>> + * @param userdata
>> + *  Pointer to the userdata to be passed to fn(). Could be NULL
>> + *
>> + * @return
>> + *  0 on success
>> + *  negative otherwise
>> + */
>> +__rte_experimental
>> +int
>> +rte_thash_adjust_tuple(struct rte_thash_subtuple_helper *h,
>> +uint8_t *orig_tuple, uint32_t adj_bits,
>> +rte_thash_check_tuple_t fn, void *userdata);
>> +
>>   #ifdef __cplusplus
>>   }
>>   #endif
>> diff --git a/lib/librte_hash/version.map b/lib/librte_hash/version.map
>> index 93cb230..a992a1e 100644
>> --- a/lib/librte_hash/version.map
>> +++ b/lib/librte_hash/version.map
>> @@ -32,6 +32,7 @@ DPDK_21 {
>>   EXPERIMENTAL {
>>   global:
>>
>> +rte_thash_adjust_tuple;
>>   rte_hash_free_key_with_position;
>>   rte_hash_lookup_with_hash_bulk;
>>   rte_hash_lookup_with_hash_bulk_data;
>> --
>> 2.7.4
> 

-- 
Regards,
Vladimir

^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [dpdk-dev] [PATCH v2 0/3] Predictable RSS feature
  2021-04-08 15:56   ` Stephen Hemminger
@ 2021-04-11 18:51     ` Medvedkin, Vladimir
  0 siblings, 0 replies; 45+ messages in thread
From: Medvedkin, Vladimir @ 2021-04-11 18:51 UTC (permalink / raw)
  To: Stephen Hemminger
  Cc: dev, konstantin.ananyev, andrey.chilikin, ray.kinsella,
	yipeng1.wang, sameh.gobriel, bruce.richardson

Hi Stephen,

Thanks for the feedback,

On 08/04/2021 18:56, Stephen Hemminger wrote:
> On Tue,  6 Apr 2021 20:50:40 +0100
> Vladimir Medvedkin <vladimir.medvedkin@intel.com> wrote:
> 
>> This patch series introduces predictable RSS feature.
>> It is based on the idea of searching for partial hash collisions
>> within Toeplitz hash.
>>
>> The Toeplitz hash function is a homomorphism between (G, ^) and (H, ^),
>> where (G, ^) - is a group of tuples and (H, ^) is a group of hashes
>> with respect to XOR operation. So tuples and hashes could be treated as
>> n-dimension and 32-dimension vector spaces over GF(2).
>> So, f(x ^ y) == f(x) ^ f(y)
>> where f - is the toeplitz hash function and x, y are tuples.
>>
>> The ability to predict partial collisions allows user to compute
>> input hash value with desired LSB values.
>> Usually number of LSB's are defined by the size of RSS Redirection Table.
>>
>> There could be number of use cases, for example:
>> 1) NAT. Using this library it is possible to select a new port number
>> on a translation in the way that rss hash for original tuple will have
>> the same LSB's as rss hash for reverse tuple.
>> 2) IPSec/MPLS/Vxlan. It is possible to choose tunnel id to be pinned to
>> a desired queue.
>> 3) TCP stack. It is possible to choose a source port number for outgoing
>> connections in the way that received replies will be assigned to
>> desired queue.
>> 4) RSS hash key generation. Hash key initialization with random values
>> does not guarantee an uniform distribution amongst queues. This library
>> uses mathematically proved algorithm to complete the rss hash key to
>> provide the best distribution.
>>
>> v2:
>> - added extra API rte_thash_adjust_tuple()
>> - added extra tests for rte_thash_adjust_tuple()
>> - added extra fields to rte_thash_subtuple_helper struct
>> - fixed typos
>>
>> Vladimir Medvedkin (3):
>>    hash: add predictable RSS API
>>    hash: add predictable RSS implementation
>>    test/hash: add additional thash tests
>>
>>   app/test/test_thash.c       | 468 +++++++++++++++++++++++++++++++-
>>   lib/librte_hash/meson.build |   3 +-
>>   lib/librte_hash/rte_thash.c | 637 ++++++++++++++++++++++++++++++++++++++++++++
>>   lib/librte_hash/rte_thash.h | 180 +++++++++++++
>>   lib/librte_hash/version.map |   8 +
>>   5 files changed, 1289 insertions(+), 7 deletions(-)
>>   create mode 100644 lib/librte_hash/rte_thash.c
>>
> 
> It would be good to show how this could be used in an application.
> Maybe yet another variant/flag to l3fwd example.

Agree, I think it would be great to have a simple NAT implementation in 
examples. We've discussed this and will probably add in next releases.

> 

-- 
Regards,
Vladimir

^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [dpdk-dev] [PATCH v2 0/3] Predictable RSS feature
  2021-04-10  0:32   ` Wang, Yipeng1
@ 2021-04-11 18:51     ` Medvedkin, Vladimir
  0 siblings, 0 replies; 45+ messages in thread
From: Medvedkin, Vladimir @ 2021-04-11 18:51 UTC (permalink / raw)
  To: Wang, Yipeng1, dev
  Cc: Ananyev, Konstantin, Chilikin, Andrey, Kinsella, Ray, Gobriel,
	Sameh, Richardson, Bruce, Stephen Hemminger

Hi Yipeng,

Thanks for the review,

On 10/04/2021 03:32, Wang, Yipeng1 wrote:
>> -----Original Message-----
>> From: Medvedkin, Vladimir <vladimir.medvedkin@intel.com>
>> Sent: Tuesday, April 6, 2021 12:51 PM
>> To: dev@dpdk.org
>> Cc: Ananyev, Konstantin <konstantin.ananyev@intel.com>; Chilikin, Andrey
>> <andrey.chilikin@intel.com>; Kinsella, Ray <ray.kinsella@intel.com>; Wang,
>> Yipeng1 <yipeng1.wang@intel.com>; Gobriel, Sameh
>> <sameh.gobriel@intel.com>; Richardson, Bruce
>> <bruce.richardson@intel.com>
>> Subject: [PATCH v2 0/3] Predictable RSS feature
>>
>> This patch series introduces predictable RSS feature.
>> It is based on the idea of searching for partial hash collisions within Toeplitz
>> hash.
>>
>> The Toeplitz hash function is a homomorphism between (G, ^) and (H, ^),
>> where (G, ^) - is a group of tuples and (H, ^) is a group of hashes with respect
>> to XOR operation. So tuples and hashes could be treated as n-dimension and
>> 32-dimension vector spaces over GF(2).
>> So, f(x ^ y) == f(x) ^ f(y)
>> where f - is the toeplitz hash function and x, y are tuples.
>>
>> The ability to predict partial collisions allows user to compute input hash value
>> with desired LSB values.
>> Usually number of LSB's are defined by the size of RSS Redirection Table.
>>
>> There could be number of use cases, for example:
>> 1) NAT. Using this library it is possible to select a new port number on a
>> translation in the way that rss hash for original tuple will have the same LSB's
>> as rss hash for reverse tuple.
>> 2) IPSec/MPLS/Vxlan. It is possible to choose tunnel id to be pinned to a
>> desired queue.
>> 3) TCP stack. It is possible to choose a source port number for outgoing
>> connections in the way that received replies will be assigned to desired
>> queue.
>> 4) RSS hash key generation. Hash key initialization with random values does
>> not guarantee an uniform distribution amongst queues. This library uses
>> mathematically proved algorithm to complete the rss hash key to provide the
>> best distribution.
>>
>> v2:
>> - added extra API rte_thash_adjust_tuple()
>> - added extra tests for rte_thash_adjust_tuple()
>> - added extra fields to rte_thash_subtuple_helper struct
>> - fixed typos
>>
>> Vladimir Medvedkin (3):
>>    hash: add predictable RSS API
>>    hash: add predictable RSS implementation
>>    test/hash: add additional thash tests
>>
>>   app/test/test_thash.c       | 468 +++++++++++++++++++++++++++++++-
>>   lib/librte_hash/meson.build |   3 +-
>>   lib/librte_hash/rte_thash.c | 637
>> ++++++++++++++++++++++++++++++++++++++++++++
>>   lib/librte_hash/rte_thash.h | 180 +++++++++++++
>>   lib/librte_hash/version.map |   8 +
>>   5 files changed, 1289 insertions(+), 7 deletions(-)  create mode 100644
>> lib/librte_hash/rte_thash.c
>>
>> --
>> 2.7.4
> 
> [Wang, Yipeng]
> Hi, Vladimir, thanks for the patch!
> I haven't fully understood every bit of the algorithm yet,
> but I did see issues that this patch could potentially solve.
> My understanding is that there are some restrictions for the current implementation,
> for example, it only supports port(16-bit) manipulation, but not multiple fields or IP.

It supports any bit-range ( with size >= reta_sz, configured for the 
CTX) manipulations within a tuple as well as multiple number of them.

> Still, I think it should be good for the use cases you listed. I would love to hear
> more feedbacks from people who are more familiar with doing NAT in production systems.
> 
> For me, besides the comments I sent earlier,
> good documentation and references are needed with clear usage examples, as others pointed out already.
> 
> Also, the current API design seems a bit cumbersome.
> To use the library, one needs:
> Init_ctx
> Add_helper.
> Get_helper
> Get_complement
> Then in a loop:
> Adjust_tuples
> Then XOR with the current tuple
> 
> I wonder if an alternative all-in-one API could be designed for simpler use cases.

Agree, v3 will have a new rte_thash_adjust_tuple() implementation that 
will make everything much easier.

> 
> Thanks!
> >
> 

-- 
Regards,
Vladimir

^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [dpdk-dev] [PATCH v2 1/3] hash: add predictable RSS API
  2021-04-10  0:05   ` Wang, Yipeng1
@ 2021-04-11 18:52     ` Medvedkin, Vladimir
  0 siblings, 0 replies; 45+ messages in thread
From: Medvedkin, Vladimir @ 2021-04-11 18:52 UTC (permalink / raw)
  To: Wang, Yipeng1, dev
  Cc: Ananyev, Konstantin, Chilikin, Andrey, Kinsella, Ray, Gobriel,
	Sameh, Richardson, Bruce, Stephen Hemminger



On 10/04/2021 03:05, Wang, Yipeng1 wrote:
>> -----Original Message-----
>> From: Medvedkin, Vladimir <vladimir.medvedkin@intel.com>
>> Sent: Tuesday, April 6, 2021 12:51 PM
>> To: dev@dpdk.org
>> Cc: Ananyev, Konstantin <konstantin.ananyev@intel.com>; Chilikin, Andrey
>> <andrey.chilikin@intel.com>; Kinsella, Ray <ray.kinsella@intel.com>; Wang,
>> Yipeng1 <yipeng1.wang@intel.com>; Gobriel, Sameh
>> <sameh.gobriel@intel.com>; Richardson, Bruce
>> <bruce.richardson@intel.com>
>> Subject: [PATCH v2 1/3] hash: add predictable RSS API
>>
>> This patch adds predictable RSS API.
>> It is based on the idea of searching partial Toeplitz hash collisions.
>>
>> Signed-off-by: Vladimir Medvedkin <vladimir.medvedkin@intel.com>
>> ---
>>   lib/librte_hash/meson.build |   3 +-
>>   lib/librte_hash/rte_thash.c |  96 ++++++++++++++++++++++++++++++
>> lib/librte_hash/rte_thash.h | 138
>> ++++++++++++++++++++++++++++++++++++++++++++
>>   lib/librte_hash/version.map |   7 +++
>>   4 files changed, 243 insertions(+), 1 deletion(-)  create mode 100644
>> lib/librte_hash/rte_thash.c
>>

<snip>

>> + * LFSR will ignore if generated m-sequence has more than 2^n -1 bits
>> +*/
> [Wang, Yipeng]
> I haven't fully got the significance/reasons behind the two flags.
> For the comment above, 2^n is the reta_size right?

Here "2^n - 1" is a length of m-sequence - a pseudorandom bit sequence 
which has a number of mathematical properties we need.

> If so, it is better than commenting 2^n.
> 
> For the first flag:
> What would be the issue for overflow? I understand that multiple helpers may overlap
> on the m-sequence, but since they are for different tuples, what would be the issue?
> 

M-sequence has a period and after (2^n - 1) bits it the sequence it 
repeats. Eventually it is written to the rss hash key. In some 
circumstances an attack with spoofed packets can be made to overflow 
particular NIC queue.
So generally this flag should be used for tests, for example to spread 
evenly traffic from the packet generator among the queues.

> For the second flag: is it always good to keep it minimum for each helper?
> 

Not always, without this flag an m-sequence till be generated for all 
variable bits, for example for 16 bits of port. And if we know that all 
values of this 16-bit port are equally probable then the distribution of 
the hash LSB's will be even.

On the other hand, if the user have a number of helpers which shares a 
single m-sequence, then there could be an overflow. And having this flag 
could break a single m-sequence with two independent.

> The goal is to have the best default values for user who do not understand the algorithm details.
> Less flags is usually better.
> 

I think the default value of 0 for flags is the best general use case.

>> +#define RTE_THASH_IGNORE_PERIOD_OVERFLOW0x1
>> +/**
>> + * Generate minimal required bit (equal to ReTa LSB) sequence into
>> + * the hash_key
>> + */
>> +#define RTE_THASH_MINIMAL_SEQ0x2
>> +
>> +/** @internal thash context structure. */ struct rte_thash_ctx;
>> +/** @internal thash helper structure. */ struct
>> +rte_thash_subtuple_helper;
>> +
>> +/**
>> + * Create a new thash context.
>> + *
>> + * @param name
>> + *  context name
>> + * @param key_len
>> + *  length of the toeplitz hash key
>> + * @param reta_sz
>> + *  logarithm of the NIC's Redirection Table (ReTa) size,
>> + *  i.e. number of the LSBs if the hash used to determine
>> + *  the reta entry.
>> + * @param key
> [Wang, Yipeng] Key will be modified by helper anyway. What is the reason of having
> the users to specify the key here?
> 

In some cases user will want to specify particular key. For example, if 
user wants to symmetrically load balance ipv4/tcp and do the NAT inside 
the tunnel without decapsulation, user will submit key with repeated 
2-byte values.

>> + *  pointer to the key used to init an internal key state.
>> + *  Could be NULL, in this case internal key will be inited with random.
>> + * @param flags
>> + *  supported flags are:
>> + *   RTE_THASH_IGNORE_PERIOD_OVERFLOW
>> + *   RTE_THASH_MINIMAL_SEQ
>> + * @return
>> + *  A pointer to the created context on success
>> + *  NULL otherwise
>> + */
>> +__rte_experimental
>> +struct rte_thash_ctx *
>> +rte_thash_init_ctx(const char *name, uint32_t key_len, uint32_t reta_sz,
>> +uint8_t *key, uint32_t flags);
>> +

<snip>

>> +/**
>> + * Add a special properties to the toeplitz hash key inside a thash context.
>> + * Creates an internal helper struct which has a complimentary table
>> + * to calculate toeplitz hash collisions.
>> + *
>> + * @param ctx
>> + *  thash context
>> + * @param name
>> + *  name of the helper
>> + * @param len
> [Wang, Yipeng]
> Add requirement here so user know the expectation.
> e.g. Len should be no shorter than log(reta_size).
> 

Agree, I'll add

>> + *  length in bits of the target subtuple
>> + * @param offset
>> + *  offset in bits of the subtuple
>> + * @return
>> + *  0 on success
>> + *  negative on error
>> + */
> [Wang, Yipeng] thread-safety for the APIs?
> Better to add thread-safety info in the comments.
> 

Agree, I'll add

>> +__rte_experimental
>> +int
>> +rte_thash_add_helper(struct rte_thash_ctx *ctx, const char *name,
>> uint32_t len,
>> +uint32_t offset);
>> +
>> +/**
>> + * Find a helper in the context by the given name
>> + *
>> + * @param ctx
>> + *  thash context
>> + * @param name
>> + *  name of the helper
>> + * @return
>> + *  Pointer to the thash helper or NULL if it was not found.
>> + */
>> +__rte_experimental
>> +struct rte_thash_subtuple_helper *
>> +rte_thash_get_helper(struct rte_thash_ctx *ctx, const char *name);
>> +
>> +/**
>> + * Get a complimentary value for the subtuple to produce a
> [Wang, Yipeng]
> Should it be complimentary->complementary?  compliment -> complement?
> 

Agree, will fix

>> + * partial toeplitz hash collision. It muxt be XOR'ed with the
> [Wang, Yipeng] typo *must be

will fix

>> + * subtuple to produce the hash value with the desired hash LSB's
>> + *
>> + * @param h
>> + *  Pointer to the helper struct
>> + * @param hash
>> + *  toeplitz hash value calculated for the given tuple
>> + * @param desired_hash
>> + *  desired hash value to find a collision for
>> + * @return
>> + *  A complimentary value which must be xored with the corresponding
>> +subtuple  */ __rte_experimental uint32_t
>> +rte_thash_get_compliment(struct rte_thash_subtuple_helper *h,
>> +uint32_t hash, uint32_t desired_hash);
>> +
>> +/**
>> + * Get a pointer to the toeplitz hash contained in the context.
>> + * It changes after each addition of a helper. It should be installed
>> +to
>> + * the NIC.
>> + *
>> + * @param ctx
>> + *  thash context
>> + * @return
>> + *  A pointer to the toeplitz hash key
>> + */
>> +__rte_experimental
>> +const uint8_t *
>> +rte_thash_get_key(struct rte_thash_ctx *ctx);
>> +
>>   #ifdef __cplusplus
>>   }
>>   #endif
>> diff --git a/lib/librte_hash/version.map b/lib/librte_hash/version.map index
>> c6d7308..93cb230 100644
>> --- a/lib/librte_hash/version.map
>> +++ b/lib/librte_hash/version.map
>> @@ -37,4 +37,11 @@ EXPERIMENTAL {
>>   rte_hash_lookup_with_hash_bulk_data;
>>   rte_hash_max_key_id;
>>   rte_hash_rcu_qsbr_add;
>> +rte_thash_add_helper;
>> +rte_thash_find_existing;
>> +rte_thash_free_ctx;
>> +rte_thash_get_compliment;
>> +rte_thash_get_helper;
>> +rte_thash_get_key;
>> +rte_thash_init_ctx;
>>   };
>> --
>> 2.7.4
> 

-- 
Regards,
Vladimir

^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [dpdk-dev] [PATCH v2 2/3] hash: add predictable RSS implementation
  2021-04-10  0:10   ` Wang, Yipeng1
@ 2021-04-11 18:52     ` Medvedkin, Vladimir
  0 siblings, 0 replies; 45+ messages in thread
From: Medvedkin, Vladimir @ 2021-04-11 18:52 UTC (permalink / raw)
  To: Wang, Yipeng1, dev
  Cc: Ananyev, Konstantin, Chilikin, Andrey, Kinsella, Ray, Gobriel,
	Sameh, Richardson, Bruce, Stephen Hemminger



On 10/04/2021 03:10, Wang, Yipeng1 wrote:
>> -----Original Message-----
>> From: Medvedkin, Vladimir <vladimir.medvedkin@intel.com>
>> Sent: Tuesday, April 6, 2021 12:51 PM
>> To: dev@dpdk.org
>> Cc: Ananyev, Konstantin <konstantin.ananyev@intel.com>; Chilikin, Andrey
>> <andrey.chilikin@intel.com>; Kinsella, Ray <ray.kinsella@intel.com>; Wang,
>> Yipeng1 <yipeng1.wang@intel.com>; Gobriel, Sameh
>> <sameh.gobriel@intel.com>; Richardson, Bruce
>> <bruce.richardson@intel.com>
>> Subject: [PATCH v2 2/3] hash: add predictable RSS implementation
>>
>> This patch implements predictable RSS functionality.
>>
>> Signed-off-by: Vladimir Medvedkin <vladimir.medvedkin@intel.com>
>> ---
>>   lib/librte_hash/rte_thash.c | 577
>> ++++++++++++++++++++++++++++++++++++++++++--
>>   lib/librte_hash/rte_thash.h |  42 ++++
>>   lib/librte_hash/version.map |   1 +
>>   3 files changed, 602 insertions(+), 18 deletions(-)
>>

<snip>

>> +/**
>> + * writes m-sequence to the hash_key for range [start, end]
>> + * (i.e. including start and end positions)  */ static int
>> +generate_subkey(struct rte_thash_ctx *ctx, struct thash_lfsr *lfsr,
>> +uint32_t start, uint32_t end)
>> +{
>> +uint32_t i;
>> +uint32_t req_bits = (start < end) ? (end - start) : (start - end);
>> +req_bits++; /* due to incuding end */
>> +
>> +/* check if lfsr overflow period of the m-sequence */
>> +if (((lfsr->bits_cnt + req_bits) > (1ULL << lfsr->deg) - 1) &&
>> +((ctx->flags &
>> RTE_THASH_IGNORE_PERIOD_OVERFLOW) !=
>> +RTE_THASH_IGNORE_PERIOD_OVERFLOW))
>> +return -ENOSPC;
> [Wang, Yipeng]
> If nospace, should one increase lfsr->deg? Or if it is already the highest deg you predefined then what to do?
> Maybe a log msg could help user with more information on the solutions.

It is not possible to increase the degree of lfsr due to mathematical 
restrictions. It must be exactly equal to the number of bits for which 
we want to have a collision.

>> +
>> +if (start < end) {
>> +/* original direction (from left to right)*/
>> +for (i = start; i <= end; i++)
>> +set_bit(ctx->hash_key, get_bit_lfsr(lfsr), i);

<snip>

>> +/**
>> + * Adjust tuple with complimentary bits.
>> + *
> [Wang, Yipeng]
> More explanation for this API is needed.
> My understanding is that user should call this function in a loop, until
> the above callback function returns success thus this function succeeds.

I'm going to rewrite this function in v3. The loop will be internal.

> BTW, why not put this API in the first API commit?
> 

My fault, will fix this

>> + * @param h
>> + *  Pointer to the helper struct
>> + * @param orig_tuple
>> + *  Pointer to the tuple to be adjusted
>> + * @param adj_bits
>> + *  Valure returned by rte_thash_get_compliment
> [Wang, Yipeng] typo. *value
>> + * @param fn
>> + *  Callback function to check adjusted tuple. Could be NULL
>> + * @param userdata
>> + *  Pointer to the userdata to be passed to fn(). Could be NULL
>> + *
>> + * @return
>> + *  0 on success
>> + *  negative otherwise
>> + */
>> +__rte_experimental
>> +int
>> +rte_thash_adjust_tuple(struct rte_thash_subtuple_helper *h,
>> +uint8_t *orig_tuple, uint32_t adj_bits,
>> +rte_thash_check_tuple_t fn, void *userdata);
>> +
>>   #ifdef __cplusplus
>>   }
>>   #endif
>> diff --git a/lib/librte_hash/version.map b/lib/librte_hash/version.map index
>> 93cb230..a992a1e 100644
>> --- a/lib/librte_hash/version.map
>> +++ b/lib/librte_hash/version.map
>> @@ -32,6 +32,7 @@ DPDK_21 {
>>   EXPERIMENTAL {
>>   global:
>>
>> +rte_thash_adjust_tuple;
>>   rte_hash_free_key_with_position;
>>   rte_hash_lookup_with_hash_bulk;
>>   rte_hash_lookup_with_hash_bulk_data;
>> --
>> 2.7.4
> 

-- 
Regards,
Vladimir

^ permalink raw reply	[flat|nested] 45+ messages in thread

* [dpdk-dev] [PATCH v3 0/3] Predictable RSS feature
  2021-04-06 19:50 ` [dpdk-dev] [PATCH v2 0/3] Predictable RSS feature Vladimir Medvedkin
  2021-04-08 15:56   ` Stephen Hemminger
  2021-04-10  0:32   ` Wang, Yipeng1
@ 2021-04-11 19:11   ` Vladimir Medvedkin
  2021-04-13 13:19     ` [dpdk-dev] [PATCH v4 " Vladimir Medvedkin
                       ` (3 more replies)
  2021-04-11 19:11   ` [dpdk-dev] [PATCH v3 1/3] hash: add predictable RSS API Vladimir Medvedkin
                     ` (2 subsequent siblings)
  5 siblings, 4 replies; 45+ messages in thread
From: Vladimir Medvedkin @ 2021-04-11 19:11 UTC (permalink / raw)
  To: dev
  Cc: konstantin.ananyev, andrey.chilikin, ray.kinsella, yipeng1.wang,
	sameh.gobriel, bruce.richardson

This patch series introduces predictable RSS feature.
It is based on the idea of searching for partial hash collisions
within Toeplitz hash.

The Toeplitz hash function is a homomorphism between (G, ^) and (H, ^),
where (G, ^) - is a group of tuples and (H, ^) is a group of hashes
with respect to XOR operation. So tuples and hashes could be treated as
n-dimension and 32-dimension vector spaces over GF(2).
So, f(x ^ y) == f(x) ^ f(y)
where f - is the toeplitz hash function and x, y are tuples.

The ability to predict partial collisions allows user to compute
input hash value with desired LSB values.
Usually number of LSB's are defined by the size of RSS Redirection Table.

There could be number of use cases, for example:
1) NAT. Using this library it is possible to select a new port number
on a translation in the way that rss hash for original tuple will have
the same LSB's as rss hash for reverse tuple.
2) IPSec/MPLS/Vxlan. It is possible to choose tunnel id to be pinned to
a desired queue.
3) TCP stack. It is possible to choose a source port number for outgoing
connections in the way that received replies will be assigned to
desired queue.
4) RSS hash key generation. Hash key initialization with random values
does not guarantee an uniform distribution amongst queues. This library
uses mathematically proved algorithm to complete the rss hash key to
provide the best distribution.

v3:
- reworked rte_thash_adjust_tuple()
- added extra comments
- fixed typos
- rte_thash_adjust_tuple() API was putted into the first commit

v2:
- added extra API rte_thash_adjust_tuple()
- added extra tests for rte_thash_adjust_tuple()
- added extra fields to rte_thash_subtuple_helper struct
- fixed typos

Vladimir Medvedkin (3):
  hash: add predictable RSS API
  hash: add predictable RSS implementation
  test/hash: add additional thash tests

 app/test/test_thash.c       | 469 +++++++++++++++++++++++++++++-
 lib/librte_hash/meson.build |   3 +-
 lib/librte_hash/rte_thash.c | 673 ++++++++++++++++++++++++++++++++++++++++++++
 lib/librte_hash/rte_thash.h | 193 +++++++++++++
 lib/librte_hash/version.map |   8 +
 5 files changed, 1339 insertions(+), 7 deletions(-)
 create mode 100644 lib/librte_hash/rte_thash.c

-- 
2.7.4


^ permalink raw reply	[flat|nested] 45+ messages in thread

* [dpdk-dev] [PATCH v3 1/3] hash: add predictable RSS API
  2021-04-06 19:50 ` [dpdk-dev] [PATCH v2 0/3] Predictable RSS feature Vladimir Medvedkin
                     ` (2 preceding siblings ...)
  2021-04-11 19:11   ` [dpdk-dev] [PATCH v3 " Vladimir Medvedkin
@ 2021-04-11 19:11   ` Vladimir Medvedkin
  2021-04-11 19:11   ` [dpdk-dev] [PATCH v3 2/3] hash: add predictable RSS implementation Vladimir Medvedkin
  2021-04-11 19:11   ` [dpdk-dev] [PATCH v3 3/3] test/hash: add additional thash tests Vladimir Medvedkin
  5 siblings, 0 replies; 45+ messages in thread
From: Vladimir Medvedkin @ 2021-04-11 19:11 UTC (permalink / raw)
  To: dev
  Cc: konstantin.ananyev, andrey.chilikin, ray.kinsella, yipeng1.wang,
	sameh.gobriel, bruce.richardson

This patch adds predictable RSS API.
It is based on the idea of searching partial Toeplitz hash collisions.

Signed-off-by: Vladimir Medvedkin <vladimir.medvedkin@intel.com>
---
 lib/librte_hash/meson.build |   3 +-
 lib/librte_hash/rte_thash.c | 109 +++++++++++++++++++++++++
 lib/librte_hash/rte_thash.h | 193 ++++++++++++++++++++++++++++++++++++++++++++
 lib/librte_hash/version.map |   8 ++
 4 files changed, 312 insertions(+), 1 deletion(-)
 create mode 100644 lib/librte_hash/rte_thash.c

diff --git a/lib/librte_hash/meson.build b/lib/librte_hash/meson.build
index 242859f..3546014 100644
--- a/lib/librte_hash/meson.build
+++ b/lib/librte_hash/meson.build
@@ -8,6 +8,7 @@ headers = files('rte_fbk_hash.h',
 	'rte_thash.h')
 indirect_headers += files('rte_crc_arm64.h')
 
-sources = files('rte_cuckoo_hash.c', 'rte_fbk_hash.c')
+sources = files('rte_cuckoo_hash.c', 'rte_fbk_hash.c', 'rte_thash.c')
+deps += ['net']
 deps += ['ring']
 deps += ['rcu']
diff --git a/lib/librte_hash/rte_thash.c b/lib/librte_hash/rte_thash.c
new file mode 100644
index 0000000..1325678
--- /dev/null
+++ b/lib/librte_hash/rte_thash.c
@@ -0,0 +1,109 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2021 Intel Corporation
+ */
+
+#include <rte_thash.h>
+#include <rte_tailq.h>
+#include <rte_random.h>
+#include <rte_memcpy.h>
+#include <rte_errno.h>
+#include <rte_eal.h>
+#include <rte_eal_memconfig.h>
+#include <rte_malloc.h>
+
+#define THASH_NAME_LEN		64
+
+struct thash_lfsr {
+	uint32_t	ref_cnt;
+	uint32_t	poly;
+	/**< polynomial associated with the lfsr */
+	uint32_t	rev_poly;
+	/**< polynomial to generate the sequence in reverse direction */
+	uint32_t	state;
+	/**< current state of the lfsr */
+	uint32_t	rev_state;
+	/**< current state of the lfsr for reverse direction */
+	uint32_t	deg;	/**< polynomial degree*/
+	uint32_t	bits_cnt;  /**< number of bits generated by lfsr*/
+};
+
+struct rte_thash_subtuple_helper {
+	char	name[THASH_NAME_LEN];	/** < Name of subtuple configuration */
+	LIST_ENTRY(rte_thash_subtuple_helper)	next;
+	struct thash_lfsr	*lfsr;
+	uint32_t	offset;		/** < Offset of the m-sequence */
+	uint32_t	len;		/** < Length of the m-sequence */
+	uint32_t	tuple_offset;	/** < Offset in bits of the subtuple */
+	uint32_t	tuple_len;	/** < Length in bits of the subtuple */
+	uint32_t	lsb_msk;	/** < (1 << reta_sz_log) - 1 */
+	__extension__ uint32_t	compl_table[0] __rte_cache_aligned;
+	/** < Complementary table */
+};
+
+struct rte_thash_ctx {
+	char		name[THASH_NAME_LEN];
+	LIST_HEAD(, rte_thash_subtuple_helper) head;
+	uint32_t	key_len;	/** < Length of the NIC RSS hash key */
+	uint32_t	reta_sz_log;	/** < size of the RSS ReTa in bits */
+	uint32_t	subtuples_nb;	/** < number of subtuples */
+	uint32_t	flags;
+	uint8_t		hash_key[0];
+};
+
+struct rte_thash_ctx *
+rte_thash_init_ctx(const char *name __rte_unused,
+	uint32_t key_len __rte_unused, uint32_t reta_sz __rte_unused,
+	uint8_t *key __rte_unused, uint32_t flags __rte_unused)
+{
+	return NULL;
+}
+
+struct rte_thash_ctx *
+rte_thash_find_existing(const char *name __rte_unused)
+{
+	return NULL;
+}
+
+void
+rte_thash_free_ctx(struct rte_thash_ctx *ctx __rte_unused)
+{
+}
+
+int
+rte_thash_add_helper(struct rte_thash_ctx *ctx __rte_unused,
+	const char *name __rte_unused, uint32_t len __rte_unused,
+	uint32_t offset __rte_unused)
+{
+	return 0;
+}
+
+struct rte_thash_subtuple_helper *
+rte_thash_get_helper(struct rte_thash_ctx *ctx __rte_unused,
+	const char *name __rte_unused)
+{
+	return NULL;
+}
+
+uint32_t
+rte_thash_get_complement(struct rte_thash_subtuple_helper *h __rte_unused,
+	uint32_t hash __rte_unused, uint32_t desired_hash __rte_unused)
+{
+	return 0;
+}
+
+const uint8_t *
+rte_thash_get_key(struct rte_thash_ctx *ctx __rte_unused)
+{
+	return NULL;
+}
+
+int
+rte_thash_adjust_tuple(struct rte_thash_ctx *ctx __rte_unused,
+	struct rte_thash_subtuple_helper *h __rte_unused,
+	uint8_t *tuple __rte_unused, unsigned int tuple_len __rte_unused,
+	uint32_t desired_value __rte_unused,
+	unsigned int attempts __rte_unused,
+	rte_thash_check_tuple_t fn __rte_unused, void *userdata __rte_unused)
+{
+	return 0;
+}
diff --git a/lib/librte_hash/rte_thash.h b/lib/librte_hash/rte_thash.h
index 061efa2..f5602ba 100644
--- a/lib/librte_hash/rte_thash.h
+++ b/lib/librte_hash/rte_thash.h
@@ -1,5 +1,6 @@
 /* SPDX-License-Identifier: BSD-3-Clause
  * Copyright(c) 2015-2019 Vladimir Medvedkin <medvedkinv@gmail.com>
+ * Copyright(c) 2021 Intel Corporation
  */
 
 #ifndef _RTE_THASH_H
@@ -222,6 +223,198 @@ rte_softrss_be(uint32_t *input_tuple, uint32_t input_len,
 	return ret;
 }
 
+/**
+ * LFSR will ignore if generated m-sequence has more than 2^n -1 bits
+ */
+#define RTE_THASH_IGNORE_PERIOD_OVERFLOW	0x1
+/**
+ * Generate minimal required bit (equal to ReTa LSB) sequence into
+ * the hash_key
+ */
+#define RTE_THASH_MINIMAL_SEQ			0x2
+
+/** @internal thash context structure. */
+struct rte_thash_ctx;
+/** @internal thash helper structure. */
+struct rte_thash_subtuple_helper;
+
+/**
+ * Create a new thash context.
+ *
+ * @param name
+ *  context name
+ * @param key_len
+ *  length of the toeplitz hash key
+ * @param reta_sz
+ *  logarithm of the NIC's Redirection Table (ReTa) size,
+ *  i.e. number of the LSBs if the hash used to determine
+ *  the reta entry.
+ * @param key
+ *  pointer to the key used to init an internal key state.
+ *  Could be NULL, in this case internal key will be inited with random.
+ * @param flags
+ *  supported flags are:
+ *   RTE_THASH_IGNORE_PERIOD_OVERFLOW
+ *   RTE_THASH_MINIMAL_SEQ
+ * @return
+ *  A pointer to the created context on success
+ *  NULL otherwise
+ */
+__rte_experimental
+struct rte_thash_ctx *
+rte_thash_init_ctx(const char *name, uint32_t key_len, uint32_t reta_sz,
+	uint8_t *key, uint32_t flags);
+
+/**
+ * Find an existing thash context and return a pointer to it.
+ *
+ * @param name
+ *  Name of the thash context
+ * @return
+ *  Pointer to the thash context or NULL if it was not found with rte_errno
+ *  set appropriately. Possible rte_errno values include:
+ *   - ENOENT - required entry not available to return.
+ */
+__rte_experimental
+struct rte_thash_ctx *
+rte_thash_find_existing(const char *name);
+
+/**
+ * Free a thash context object
+ *
+ * @param ctx
+ *  thash context
+ * @return
+ *  None
+ */
+__rte_experimental
+void
+rte_thash_free_ctx(struct rte_thash_ctx *ctx);
+
+/**
+ * Add a special properties to the toeplitz hash key inside a thash context.
+ * Creates an internal helper struct which has a complementary table
+ * to calculate toeplitz hash collisions.
+ * This function is not multi-thread safe.
+ *
+ * @param ctx
+ *  thash context
+ * @param name
+ *  name of the helper
+ * @param len
+ *  length in bits of the target subtuple
+ *  Must be no shorter than reta_sz passed on rte_thash_init_ctx().
+ * @param offset
+ *  offset in bits of the subtuple
+ * @return
+ *  0 on success
+ *  negative on error
+ */
+__rte_experimental
+int
+rte_thash_add_helper(struct rte_thash_ctx *ctx, const char *name, uint32_t len,
+	uint32_t offset);
+
+/**
+ * Find a helper in the context by the given name
+ *
+ * @param ctx
+ *  thash context
+ * @param name
+ *  name of the helper
+ * @return
+ *  Pointer to the thash helper or NULL if it was not found.
+ */
+__rte_experimental
+struct rte_thash_subtuple_helper *
+rte_thash_get_helper(struct rte_thash_ctx *ctx, const char *name);
+
+/**
+ * Get a complementary value for the subtuple to produce a
+ * partial toeplitz hash collision. It must be XOR'ed with the
+ * subtuple to produce the hash value with the desired hash LSB's
+ * This function is multi-thread safe.
+ *
+ * @param h
+ *  Pointer to the helper struct
+ * @param hash
+ *  toeplitz hash value calculated for the given tuple
+ * @param desired_hash
+ *  desired hash value to find a collision for
+ * @return
+ *  A complementary value which must be xored with the corresponding subtuple
+ */
+__rte_experimental
+uint32_t
+rte_thash_get_complement(struct rte_thash_subtuple_helper *h,
+	uint32_t hash, uint32_t desired_hash);
+
+/**
+ * Get a pointer to the toeplitz hash contained in the context.
+ * It changes after each addition of a helper. It should be installed to
+ * the NIC.
+ *
+ * @param ctx
+ *  thash context
+ * @return
+ *  A pointer to the toeplitz hash key
+ */
+__rte_experimental
+const uint8_t *
+rte_thash_get_key(struct rte_thash_ctx *ctx);
+
+/**
+ * Function prototype for the rte_thash_adjust_tuple
+ * to check if adjusted tuple could be used.
+ * Generally it is some kind of lookup function to check
+ * if adjusted tuple is already in use.
+ *
+ * @param userdata
+ *  Pointer to the userdata. It could be a pointer to the
+ *  table with used tuples to search.
+ * @param tuple
+ *  Pointer to the tuple to check
+ *
+ * @return
+ *  1 on success
+ *  0 otherwise
+ */
+typedef int (*rte_thash_check_tuple_t)(void *userdata, uint8_t *tuple);
+
+/**
+ * Adjusts tuple in the way to make Toeplitz hash has
+ * desired least significant bits.
+ * This function is multi-thread safe.
+ *
+ * @param ctx
+ *  thash context
+ * @param h
+ *  Pointer to the helper struct
+ * @param tuple
+ *  Pointer to the tuple to be adjusted
+ * @param tuple_len
+ *  Length of the tuple. Must be multiple of 4.
+ * @param desired_value
+ *  Desired value of least significant bits of the hash
+ * @param attempts
+ *   Number of attempts to adjust tuple with fn() calling
+ * @param fn
+ *  Callback function to check adjusted tuple. Could be NULL
+ * @param userdata
+ *  Pointer to the userdata to be passed to fn(). Could be NULL
+ *
+ * @return
+ *  0 on success
+ *  negative otherwise
+ */
+__rte_experimental
+int
+rte_thash_adjust_tuple(struct rte_thash_ctx *ctx,
+	struct rte_thash_subtuple_helper *h,
+	uint8_t *tuple, unsigned int tuple_len,
+	uint32_t desired_value, unsigned int attempts,
+	rte_thash_check_tuple_t fn, void *userdata);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/lib/librte_hash/version.map b/lib/librte_hash/version.map
index c6d7308..17cb8aa 100644
--- a/lib/librte_hash/version.map
+++ b/lib/librte_hash/version.map
@@ -32,9 +32,17 @@ DPDK_21 {
 EXPERIMENTAL {
 	global:
 
+	rte_thash_adjust_tuple;
 	rte_hash_free_key_with_position;
 	rte_hash_lookup_with_hash_bulk;
 	rte_hash_lookup_with_hash_bulk_data;
 	rte_hash_max_key_id;
 	rte_hash_rcu_qsbr_add;
+	rte_thash_add_helper;
+	rte_thash_find_existing;
+	rte_thash_free_ctx;
+	rte_thash_get_complement;
+	rte_thash_get_helper;
+	rte_thash_get_key;
+	rte_thash_init_ctx;
 };
-- 
2.7.4


^ permalink raw reply	[flat|nested] 45+ messages in thread

* [dpdk-dev] [PATCH v3 2/3] hash: add predictable RSS implementation
  2021-04-06 19:50 ` [dpdk-dev] [PATCH v2 0/3] Predictable RSS feature Vladimir Medvedkin
                     ` (3 preceding siblings ...)
  2021-04-11 19:11   ` [dpdk-dev] [PATCH v3 1/3] hash: add predictable RSS API Vladimir Medvedkin
@ 2021-04-11 19:11   ` Vladimir Medvedkin
  2021-04-11 19:11   ` [dpdk-dev] [PATCH v3 3/3] test/hash: add additional thash tests Vladimir Medvedkin
  5 siblings, 0 replies; 45+ messages in thread
From: Vladimir Medvedkin @ 2021-04-11 19:11 UTC (permalink / raw)
  To: dev
  Cc: konstantin.ananyev, andrey.chilikin, ray.kinsella, yipeng1.wang,
	sameh.gobriel, bruce.richardson

This patch implements predictable RSS functionality.

Signed-off-by: Vladimir Medvedkin <vladimir.medvedkin@intel.com>
---
 lib/librte_hash/rte_thash.c | 610 ++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 587 insertions(+), 23 deletions(-)

diff --git a/lib/librte_hash/rte_thash.c b/lib/librte_hash/rte_thash.c
index 1325678..c76ceee 100644
--- a/lib/librte_hash/rte_thash.c
+++ b/lib/librte_hash/rte_thash.c
@@ -12,6 +12,45 @@
 #include <rte_malloc.h>
 
 #define THASH_NAME_LEN		64
+#define TOEPLITZ_HASH_LEN	32
+
+#define	RETA_SZ_MIN	2U
+#define	RETA_SZ_MAX	16U
+#define RETA_SZ_IN_RANGE(reta_sz)	((reta_sz >= RETA_SZ_MIN) && \
+					(reta_sz <= RETA_SZ_MAX))
+
+TAILQ_HEAD(rte_thash_list, rte_tailq_entry);
+static struct rte_tailq_elem rte_thash_tailq = {
+	.name = "RTE_THASH",
+};
+EAL_REGISTER_TAILQ(rte_thash_tailq)
+
+/**
+ * Table of some irreducible polinomials over GF(2).
+ * For lfsr they are reperesented in BE bit order, and
+ * x^0 is masked out.
+ * For example, poly x^5 + x^2 + 1 will be represented
+ * as (101001b & 11111b) = 01001b = 0x9
+ */
+static const uint32_t irreducible_poly_table[][4] = {
+	{0, 0, 0, 0},	/** < degree 0 */
+	{1, 1, 1, 1},	/** < degree 1 */
+	{0x3, 0x3, 0x3, 0x3},	/** < degree 2 and so on... */
+	{0x5, 0x3, 0x5, 0x3},
+	{0x9, 0x3, 0x9, 0x3},
+	{0x9, 0x1b, 0xf, 0x5},
+	{0x21, 0x33, 0x1b, 0x2d},
+	{0x41, 0x11, 0x71, 0x9},
+	{0x71, 0xa9, 0xf5, 0x8d},
+	{0x21, 0xd1, 0x69, 0x1d9},
+	{0x81, 0x2c1, 0x3b1, 0x185},
+	{0x201, 0x541, 0x341, 0x461},
+	{0x941, 0x609, 0xe19, 0x45d},
+	{0x1601, 0x1f51, 0x1171, 0x359},
+	{0x2141, 0x2111, 0x2db1, 0x2109},
+	{0x4001, 0x801, 0x101, 0x7301},
+	{0x7781, 0xa011, 0x4211, 0x86d9},
+};
 
 struct thash_lfsr {
 	uint32_t	ref_cnt;
@@ -50,60 +89,585 @@ struct rte_thash_ctx {
 	uint8_t		hash_key[0];
 };
 
+static inline uint32_t
+get_bit_lfsr(struct thash_lfsr *lfsr)
+{
+	uint32_t bit, ret;
+
+	/*
+	 * masking the TAP bits defined by the polynomial and
+	 * calculating parity
+	 */
+	bit = __builtin_popcount(lfsr->state & lfsr->poly) & 0x1;
+	ret = lfsr->state & 0x1;
+	lfsr->state = ((lfsr->state >> 1) | (bit << (lfsr->deg - 1))) &
+		((1 << lfsr->deg) - 1);
+
+	lfsr->bits_cnt++;
+	return ret;
+}
+
+static inline uint32_t
+get_rev_bit_lfsr(struct thash_lfsr *lfsr)
+{
+	uint32_t bit, ret;
+
+	bit = __builtin_popcount(lfsr->rev_state & lfsr->rev_poly) & 0x1;
+	ret = lfsr->rev_state & (1 << (lfsr->deg - 1));
+	lfsr->rev_state = ((lfsr->rev_state << 1) | bit) &
+		((1 << lfsr->deg) - 1);
+
+	lfsr->bits_cnt++;
+	return ret;
+}
+
+static inline uint32_t
+thash_get_rand_poly(uint32_t poly_degree)
+{
+	return irreducible_poly_table[poly_degree][rte_rand() %
+		RTE_DIM(irreducible_poly_table[poly_degree])];
+}
+
+static struct thash_lfsr *
+alloc_lfsr(struct rte_thash_ctx *ctx)
+{
+	struct thash_lfsr *lfsr;
+	uint32_t i;
+
+	if (ctx == NULL)
+		return NULL;
+
+	lfsr = rte_zmalloc(NULL, sizeof(struct thash_lfsr), 0);
+	if (lfsr == NULL)
+		return NULL;
+
+	lfsr->deg = ctx->reta_sz_log;
+	lfsr->poly = thash_get_rand_poly(lfsr->deg);
+	do {
+		lfsr->state = rte_rand() & ((1 << lfsr->deg) - 1);
+	} while (lfsr->state == 0);
+	/* init reverse order polynomial */
+	lfsr->rev_poly = (lfsr->poly >> 1) | (1 << (lfsr->deg - 1));
+	/* init proper rev_state*/
+	lfsr->rev_state = lfsr->state;
+	for (i = 0; i <= lfsr->deg; i++)
+		get_rev_bit_lfsr(lfsr);
+
+	/* clear bits_cnt after rev_state was inited */
+	lfsr->bits_cnt = 0;
+	lfsr->ref_cnt = 1;
+
+	return lfsr;
+}
+
+static void
+attach_lfsr(struct rte_thash_subtuple_helper *h, struct thash_lfsr *lfsr)
+{
+	lfsr->ref_cnt++;
+	h->lfsr = lfsr;
+}
+
+static void
+free_lfsr(struct thash_lfsr *lfsr)
+{
+	lfsr->ref_cnt--;
+	if (lfsr->ref_cnt == 0)
+		rte_free(lfsr);
+}
+
 struct rte_thash_ctx *
-rte_thash_init_ctx(const char *name __rte_unused,
-	uint32_t key_len __rte_unused, uint32_t reta_sz __rte_unused,
-	uint8_t *key __rte_unused, uint32_t flags __rte_unused)
+rte_thash_init_ctx(const char *name, uint32_t key_len, uint32_t reta_sz,
+	uint8_t *key, uint32_t flags)
 {
+	struct rte_thash_ctx *ctx;
+	struct rte_tailq_entry *te;
+	struct rte_thash_list *thash_list;
+	uint32_t i;
+
+	if ((name == NULL) || (key_len == 0) || !RETA_SZ_IN_RANGE(reta_sz)) {
+		rte_errno = EINVAL;
+		return NULL;
+	}
+
+	thash_list = RTE_TAILQ_CAST(rte_thash_tailq.head, rte_thash_list);
+
+	rte_mcfg_tailq_write_lock();
+
+	/* guarantee there's no existing */
+	TAILQ_FOREACH(te, thash_list, next) {
+		ctx = (struct rte_thash_ctx *)te->data;
+		if (strncmp(name, ctx->name, sizeof(ctx->name)) == 0)
+			break;
+	}
+	ctx = NULL;
+	if (te != NULL) {
+		rte_errno = EEXIST;
+		goto exit;
+	}
+
+	/* allocate tailq entry */
+	te = rte_zmalloc("THASH_TAILQ_ENTRY", sizeof(*te), 0);
+	if (te == NULL) {
+		RTE_LOG(ERR, HASH,
+			"Can not allocate tailq entry for thash context %s\n",
+			name);
+		rte_errno = ENOMEM;
+		goto exit;
+	}
+
+	ctx = rte_zmalloc(NULL, sizeof(struct rte_thash_ctx) + key_len, 0);
+	if (ctx == NULL) {
+		RTE_LOG(ERR, HASH, "thash ctx %s memory allocation failed\n",
+			name);
+		rte_errno = ENOMEM;
+		goto free_te;
+	}
+
+	rte_strlcpy(ctx->name, name, sizeof(ctx->name));
+	ctx->key_len = key_len;
+	ctx->reta_sz_log = reta_sz;
+	LIST_INIT(&ctx->head);
+	ctx->flags = flags;
+
+	if (key)
+		rte_memcpy(ctx->hash_key, key, key_len);
+	else {
+		for (i = 0; i < key_len; i++)
+			ctx->hash_key[i] = rte_rand();
+	}
+
+	te->data = (void *)ctx;
+	TAILQ_INSERT_TAIL(thash_list, te, next);
+
+	rte_mcfg_tailq_write_unlock();
+
+	return ctx;
+free_te:
+	rte_free(te);
+exit:
+	rte_mcfg_tailq_write_unlock();
 	return NULL;
 }
 
 struct rte_thash_ctx *
-rte_thash_find_existing(const char *name __rte_unused)
+rte_thash_find_existing(const char *name)
 {
-	return NULL;
+	struct rte_thash_ctx *ctx;
+	struct rte_tailq_entry *te;
+	struct rte_thash_list *thash_list;
+
+	thash_list = RTE_TAILQ_CAST(rte_thash_tailq.head, rte_thash_list);
+
+	rte_mcfg_tailq_read_lock();
+	TAILQ_FOREACH(te, thash_list, next) {
+		ctx = (struct rte_thash_ctx *)te->data;
+		if (strncmp(name, ctx->name, sizeof(ctx->name)) == 0)
+			break;
+	}
+
+	rte_mcfg_tailq_read_unlock();
+
+	if (te == NULL) {
+		rte_errno = ENOENT;
+		return NULL;
+	}
+
+	return ctx;
 }
 
 void
-rte_thash_free_ctx(struct rte_thash_ctx *ctx __rte_unused)
+rte_thash_free_ctx(struct rte_thash_ctx *ctx)
 {
+	struct rte_tailq_entry *te;
+	struct rte_thash_list *thash_list;
+	struct rte_thash_subtuple_helper *ent, *tmp;
+
+	if (ctx == NULL)
+		return;
+
+	thash_list = RTE_TAILQ_CAST(rte_thash_tailq.head, rte_thash_list);
+	rte_mcfg_tailq_write_lock();
+	TAILQ_FOREACH(te, thash_list, next) {
+		if (te->data == (void *)ctx)
+			break;
+	}
+
+	if (te != NULL)
+		TAILQ_REMOVE(thash_list, te, next);
+
+	rte_mcfg_tailq_write_unlock();
+	ent = LIST_FIRST(&(ctx->head));
+	while (ent) {
+		free_lfsr(ent->lfsr);
+		tmp = ent;
+		ent = LIST_NEXT(ent, next);
+		LIST_REMOVE(tmp, next);
+		rte_free(tmp);
+	}
+
+	rte_free(ctx);
+	rte_free(te);
+}
+
+static inline void
+set_bit(uint8_t *ptr, uint32_t bit, uint32_t pos)
+{
+	uint32_t byte_idx = pos / CHAR_BIT;
+	uint32_t bit_idx = (CHAR_BIT - 1) - (pos & (CHAR_BIT - 1));
+	uint8_t tmp;
+
+	tmp = ptr[byte_idx];
+	tmp &= ~(1 << bit_idx);
+	tmp |= bit << bit_idx;
+	ptr[byte_idx] = tmp;
+}
+
+/**
+ * writes m-sequence to the hash_key for range [start, end]
+ * (i.e. including start and end positions)
+ */
+static int
+generate_subkey(struct rte_thash_ctx *ctx, struct thash_lfsr *lfsr,
+	uint32_t start, uint32_t end)
+{
+	uint32_t i;
+	uint32_t req_bits = (start < end) ? (end - start) : (start - end);
+	req_bits++; /* due to incuding end */
+
+	/* check if lfsr overflow period of the m-sequence */
+	if (((lfsr->bits_cnt + req_bits) > (1ULL << lfsr->deg) - 1) &&
+			((ctx->flags & RTE_THASH_IGNORE_PERIOD_OVERFLOW) !=
+			RTE_THASH_IGNORE_PERIOD_OVERFLOW))
+		return -ENOSPC;
+
+	if (start < end) {
+		/* original direction (from left to right)*/
+		for (i = start; i <= end; i++)
+			set_bit(ctx->hash_key, get_bit_lfsr(lfsr), i);
+
+	} else {
+		/* reverse direction (from right to left) */
+		for (i = end; i >= start; i--)
+			set_bit(ctx->hash_key, get_rev_bit_lfsr(lfsr), i);
+	}
+
+	return 0;
+}
+
+static inline uint32_t
+get_subvalue(struct rte_thash_ctx *ctx, uint32_t offset)
+{
+	uint32_t *tmp, val;
+
+	tmp = (uint32_t *)(&ctx->hash_key[offset >> 3]);
+	val = rte_be_to_cpu_32(*tmp);
+	val >>= (TOEPLITZ_HASH_LEN - ((offset & (CHAR_BIT - 1)) +
+		ctx->reta_sz_log));
+
+	return val & ((1 << ctx->reta_sz_log) - 1);
+}
+
+static inline void
+generate_complement_table(struct rte_thash_ctx *ctx,
+	struct rte_thash_subtuple_helper *h)
+{
+	int i, j, k;
+	uint32_t val;
+	uint32_t start;
+
+	start = h->offset + h->len - (2 * ctx->reta_sz_log - 1);
+
+	for (i = 1; i < (1 << ctx->reta_sz_log); i++) {
+		val = 0;
+		for (j = i; j; j &= (j - 1)) {
+			k = rte_bsf32(j);
+			val ^= get_subvalue(ctx, start - k +
+				ctx->reta_sz_log - 1);
+		}
+		h->compl_table[val] = i;
+	}
+}
+
+static inline int
+insert_before(struct rte_thash_ctx *ctx,
+	struct rte_thash_subtuple_helper *ent,
+	struct rte_thash_subtuple_helper *cur_ent,
+	struct rte_thash_subtuple_helper *next_ent,
+	uint32_t start, uint32_t end, uint32_t range_end)
+{
+	int ret;
+
+	if (end < cur_ent->offset) {
+		ent->lfsr = alloc_lfsr(ctx);
+		if (ent->lfsr == NULL) {
+			rte_free(ent);
+			return -ENOMEM;
+		}
+		/* generate nonoverlapping range [start, end) */
+		ret = generate_subkey(ctx, ent->lfsr, start, end - 1);
+		if (ret != 0) {
+			free_lfsr(ent->lfsr);
+			rte_free(ent);
+			return ret;
+		}
+	} else if ((next_ent != NULL) && (end > next_ent->offset)) {
+		rte_free(ent);
+		return -ENOSPC;
+	}
+	attach_lfsr(ent, cur_ent->lfsr);
+
+	/**
+	 * generate partially overlapping range
+	 * [start, cur_ent->start) in reverse order
+	 */
+	ret = generate_subkey(ctx, ent->lfsr, cur_ent->offset - 1, start);
+	if (ret != 0) {
+		free_lfsr(ent->lfsr);
+		rte_free(ent);
+		return ret;
+	}
+
+	if (end > range_end) {
+		/**
+		 * generate partially overlapping range
+		 * (range_end, end)
+		 */
+		ret = generate_subkey(ctx, ent->lfsr, range_end, end - 1);
+		if (ret != 0) {
+			free_lfsr(ent->lfsr);
+			rte_free(ent);
+			return ret;
+		}
+	}
+
+	LIST_INSERT_BEFORE(cur_ent, ent, next);
+	generate_complement_table(ctx, ent);
+	ctx->subtuples_nb++;
+	return 0;
+}
+
+static inline int
+insert_after(struct rte_thash_ctx *ctx,
+	struct rte_thash_subtuple_helper *ent,
+	struct rte_thash_subtuple_helper *cur_ent,
+	struct rte_thash_subtuple_helper *next_ent,
+	struct rte_thash_subtuple_helper *prev_ent,
+	uint32_t end, uint32_t range_end)
+{
+	int ret;
+
+	if ((next_ent != NULL) && (end > next_ent->offset)) {
+		rte_free(ent);
+		return -EEXIST;
+	}
+
+	attach_lfsr(ent, cur_ent->lfsr);
+	if (end > range_end) {
+		/**
+		 * generate partially overlapping range
+		 * (range_end, end)
+		 */
+		ret = generate_subkey(ctx, ent->lfsr, range_end, end - 1);
+		if (ret != 0) {
+			free_lfsr(ent->lfsr);
+			rte_free(ent);
+			return ret;
+		}
+	}
+
+	LIST_INSERT_AFTER(prev_ent, ent, next);
+	generate_complement_table(ctx, ent);
+	ctx->subtuples_nb++;
+
+	return 0;
 }
 
 int
-rte_thash_add_helper(struct rte_thash_ctx *ctx __rte_unused,
-	const char *name __rte_unused, uint32_t len __rte_unused,
-	uint32_t offset __rte_unused)
+rte_thash_add_helper(struct rte_thash_ctx *ctx, const char *name, uint32_t len,
+	uint32_t offset)
 {
+	struct rte_thash_subtuple_helper *ent, *cur_ent, *prev_ent, *next_ent;
+	uint32_t start, end;
+	int ret;
+
+	if ((ctx == NULL) || (name == NULL) || (len < ctx->reta_sz_log) ||
+			((offset + len + TOEPLITZ_HASH_LEN - 1) >
+			ctx->key_len * CHAR_BIT))
+		return -EINVAL;
+
+	/* Check for existing name*/
+	LIST_FOREACH(cur_ent, &ctx->head, next) {
+		if (strncmp(name, cur_ent->name, sizeof(cur_ent->name)) == 0)
+			return -EEXIST;
+	}
+
+	end = offset + len + TOEPLITZ_HASH_LEN - 1;
+	start = ((ctx->flags & RTE_THASH_MINIMAL_SEQ) ==
+		RTE_THASH_MINIMAL_SEQ) ? (end - (2 * ctx->reta_sz_log - 1)) :
+		offset;
+
+	ent = rte_zmalloc(NULL, sizeof(struct rte_thash_subtuple_helper) +
+		sizeof(uint32_t) * (1 << ctx->reta_sz_log),
+		RTE_CACHE_LINE_SIZE);
+	if (ent == NULL)
+		return -ENOMEM;
+
+	rte_strlcpy(ent->name, name, sizeof(ent->name));
+	ent->offset = start;
+	ent->len = end - start;
+	ent->tuple_offset = offset;
+	ent->tuple_len = len;
+	ent->lsb_msk = (1 << ctx->reta_sz_log) - 1;
+
+	cur_ent = LIST_FIRST(&ctx->head);
+	while (cur_ent) {
+		uint32_t range_end = cur_ent->offset + cur_ent->len;
+		next_ent = LIST_NEXT(cur_ent, next);
+		prev_ent = cur_ent;
+		/* Iterate through overlapping ranges */
+		while ((next_ent != NULL) && (next_ent->offset < range_end)) {
+			range_end = RTE_MAX(next_ent->offset + next_ent->len,
+				range_end);
+			if (start > next_ent->offset)
+				prev_ent = next_ent;
+
+			next_ent = LIST_NEXT(next_ent, next);
+		}
+
+		if (start < cur_ent->offset)
+			return insert_before(ctx, ent, cur_ent, next_ent,
+				start, end, range_end);
+		else if (start < range_end)
+			return insert_after(ctx, ent, cur_ent, next_ent,
+				prev_ent, end, range_end);
+
+		cur_ent = next_ent;
+		continue;
+	}
+
+	ent->lfsr = alloc_lfsr(ctx);
+	if (ent->lfsr == NULL) {
+		rte_free(ent);
+		return -ENOMEM;
+	}
+
+	/* generate nonoverlapping range [start, end) */
+	ret = generate_subkey(ctx, ent->lfsr, start, end - 1);
+	if (ret != 0) {
+		free_lfsr(ent->lfsr);
+		rte_free(ent);
+		return ret;
+	}
+	if (LIST_EMPTY(&ctx->head)) {
+		LIST_INSERT_HEAD(&ctx->head, ent, next);
+	} else {
+		LIST_FOREACH(next_ent, &ctx->head, next)
+			prev_ent = next_ent;
+
+		LIST_INSERT_AFTER(prev_ent, ent, next);
+	}
+	generate_complement_table(ctx, ent);
+	ctx->subtuples_nb++;
+
 	return 0;
 }
 
 struct rte_thash_subtuple_helper *
-rte_thash_get_helper(struct rte_thash_ctx *ctx __rte_unused,
-	const char *name __rte_unused)
+rte_thash_get_helper(struct rte_thash_ctx *ctx, const char *name)
 {
+	struct rte_thash_subtuple_helper *ent;
+
+	if ((ctx == NULL) || (name == NULL))
+		return NULL;
+
+	LIST_FOREACH(ent, &ctx->head, next) {
+		if (strncmp(name, ent->name, sizeof(ent->name)) == 0)
+			return ent;
+	}
+
 	return NULL;
 }
 
 uint32_t
-rte_thash_get_complement(struct rte_thash_subtuple_helper *h __rte_unused,
-	uint32_t hash __rte_unused, uint32_t desired_hash __rte_unused)
+rte_thash_get_complement(struct rte_thash_subtuple_helper *h,
+	uint32_t hash, uint32_t desired_hash)
 {
-	return 0;
+	return h->compl_table[(hash ^ desired_hash) & h->lsb_msk];
 }
 
 const uint8_t *
-rte_thash_get_key(struct rte_thash_ctx *ctx __rte_unused)
+rte_thash_get_key(struct rte_thash_ctx *ctx)
 {
-	return NULL;
+	return ctx->hash_key;
+}
+
+static inline void
+xor_bit(uint8_t *ptr, uint32_t bit, uint32_t pos)
+{
+	uint32_t byte_idx = pos >> 3;
+	uint32_t bit_idx = (CHAR_BIT - 1) - (pos & (CHAR_BIT - 1));
+	uint8_t tmp;
+
+	tmp = ptr[byte_idx];
+	tmp ^= bit << bit_idx;
+	ptr[byte_idx] = tmp;
 }
 
 int
-rte_thash_adjust_tuple(struct rte_thash_ctx *ctx __rte_unused,
-	struct rte_thash_subtuple_helper *h __rte_unused,
-	uint8_t *tuple __rte_unused, unsigned int tuple_len __rte_unused,
-	uint32_t desired_value __rte_unused,
-	unsigned int attempts __rte_unused,
-	rte_thash_check_tuple_t fn __rte_unused, void *userdata __rte_unused)
+rte_thash_adjust_tuple(struct rte_thash_ctx *ctx,
+	struct rte_thash_subtuple_helper *h,
+	uint8_t *tuple, unsigned int tuple_len,
+	uint32_t desired_value,	unsigned int attempts,
+	rte_thash_check_tuple_t fn, void *userdata)
 {
-	return 0;
+	uint32_t tmp_tuple[tuple_len / sizeof(uint32_t)];
+	unsigned int i, j, ret = 0;
+	uint32_t hash, adj_bits;
+	uint8_t bit;
+	const uint8_t *hash_key;
+
+	if ((ctx == NULL) || (h == NULL) || (tuple == NULL) ||
+			(tuple_len % sizeof(uint32_t) != 0) || (attempts <= 0))
+		return -EINVAL;
+
+	hash_key = rte_thash_get_key(ctx);
+
+	for (i = 0; i < attempts; i++) {
+		for (j = 0; j < (tuple_len / 4); j++)
+			tmp_tuple[j] =
+				rte_be_to_cpu_32(*(uint32_t *)&tuple[j * 4]);
+
+		hash = rte_softrss(tmp_tuple, tuple_len / 4, hash_key);
+		adj_bits = rte_thash_get_complement(h, hash, desired_value);
+
+		/*
+		 * Hint: LSB of adj_bits corresponds to
+		 * offset + len bit of tuple
+		 */
+		for (j = 0; j < sizeof(uint32_t) * CHAR_BIT; j++) {
+			bit = (adj_bits >> j) & 0x1;
+			if (bit)
+				xor_bit(tuple, bit, h->tuple_offset +
+					h->tuple_len - 1 - j);
+		}
+
+		if (fn != NULL) {
+			ret = (fn(userdata, tuple)) ? 0 : -EEXIST;
+			if (ret == 0)
+				return 0;
+			else if (i < (attempts - 1)) {
+				/* Update tuple with random bits */
+				for (j = 0; j < h->tuple_len; j++) {
+					bit = rte_rand() & 0x1;
+					if (bit)
+						xor_bit(tuple, bit,
+							h->tuple_offset +
+							h->tuple_len - 1 - j);
+				}
+			}
+		} else
+			return 0;
+	}
+
+	return ret;
 }
-- 
2.7.4


^ permalink raw reply	[flat|nested] 45+ messages in thread

* [dpdk-dev] [PATCH v3 3/3] test/hash: add additional thash tests
  2021-04-06 19:50 ` [dpdk-dev] [PATCH v2 0/3] Predictable RSS feature Vladimir Medvedkin
                     ` (4 preceding siblings ...)
  2021-04-11 19:11   ` [dpdk-dev] [PATCH v3 2/3] hash: add predictable RSS implementation Vladimir Medvedkin
@ 2021-04-11 19:11   ` Vladimir Medvedkin
  5 siblings, 0 replies; 45+ messages in thread
From: Vladimir Medvedkin @ 2021-04-11 19:11 UTC (permalink / raw)
  To: dev
  Cc: konstantin.ananyev, andrey.chilikin, ray.kinsella, yipeng1.wang,
	sameh.gobriel, bruce.richardson

This patch adds tests for predictable RSS feature.

Signed-off-by: Vladimir Medvedkin <vladimir.medvedkin@intel.com>
---
 app/test/test_thash.c | 469 +++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 463 insertions(+), 6 deletions(-)

diff --git a/app/test/test_thash.c b/app/test/test_thash.c
index a6aadd1..d8981fb 100644
--- a/app/test/test_thash.c
+++ b/app/test/test_thash.c
@@ -5,11 +5,15 @@
 #include <rte_common.h>
 #include <rte_eal.h>
 #include <rte_ip.h>
+#include <rte_random.h>
 
 #include "test.h"
 
 #include <rte_thash.h>
 
+#define HASH_MSK(reta_sz)	((1 << reta_sz) - 1)
+#define TUPLE_SZ	(RTE_THASH_V4_L4_LEN * 4)
+
 struct test_thash_v4 {
 	uint32_t	dst_ip;
 	uint32_t	src_ip;
@@ -75,7 +79,7 @@ uint8_t default_rss_key[] = {
 };
 
 static int
-test_thash(void)
+test_toeplitz_hash_calc(void)
 {
 	uint32_t i, j;
 	union rte_thash_tuple tuple;
@@ -100,7 +104,7 @@ test_thash(void)
 				RTE_THASH_V4_L4_LEN, default_rss_key);
 		if ((rss_l3 != v4_tbl[i].hash_l3) ||
 				(rss_l3l4 != v4_tbl[i].hash_l3l4))
-			return -1;
+			return -TEST_FAILED;
 		/*Calculate hash with converted key*/
 		rss_l3 = rte_softrss_be((uint32_t *)&tuple,
 				RTE_THASH_V4_L3_LEN, rss_key_be);
@@ -108,7 +112,7 @@ test_thash(void)
 				RTE_THASH_V4_L4_LEN, rss_key_be);
 		if ((rss_l3 != v4_tbl[i].hash_l3) ||
 				(rss_l3l4 != v4_tbl[i].hash_l3l4))
-			return -1;
+			return -TEST_FAILED;
 	}
 	for (i = 0; i < RTE_DIM(v6_tbl); i++) {
 		/*Fill ipv6 hdr*/
@@ -127,7 +131,7 @@ test_thash(void)
 				RTE_THASH_V6_L4_LEN, default_rss_key);
 		if ((rss_l3 != v6_tbl[i].hash_l3) ||
 				(rss_l3l4 != v6_tbl[i].hash_l3l4))
-			return -1;
+			return -TEST_FAILED;
 		/*Calculate hash with converted key*/
 		rss_l3 = rte_softrss_be((uint32_t *)&tuple,
 				RTE_THASH_V6_L3_LEN, rss_key_be);
@@ -135,9 +139,462 @@ test_thash(void)
 				RTE_THASH_V6_L4_LEN, rss_key_be);
 		if ((rss_l3 != v6_tbl[i].hash_l3) ||
 				(rss_l3l4 != v6_tbl[i].hash_l3l4))
-			return -1;
+			return -TEST_FAILED;
 	}
-	return 0;
+	return TEST_SUCCESS;
+}
+
+static int
+test_create_invalid(void)
+{
+	struct rte_thash_ctx *ctx;
+	int key_len = 40;
+	int reta_sz = 7;
+
+	ctx = rte_thash_init_ctx(NULL, key_len, reta_sz, NULL, 0);
+	RTE_TEST_ASSERT(ctx == NULL,
+		"Call succeeded with invalid parameters\n");
+
+	ctx = rte_thash_init_ctx("test", 0, reta_sz, NULL, 0);
+	RTE_TEST_ASSERT(ctx == NULL,
+		"Call succeeded with invalid parameters\n");
+
+	ctx = rte_thash_init_ctx(NULL, key_len, 1, NULL, 0);
+	RTE_TEST_ASSERT(ctx == NULL,
+		"Call succeeded with invalid parameters\n");
+
+	ctx = rte_thash_init_ctx(NULL, key_len, 17, NULL, 0);
+	RTE_TEST_ASSERT(ctx == NULL,
+		"Call succeeded with invalid parameters\n");
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_multiple_create(void)
+{
+	struct rte_thash_ctx *ctx;
+	int key_len = 40;
+	int reta_sz = 7;
+	int i;
+
+	for (i = 0; i < 100; i++) {
+		ctx = rte_thash_init_ctx("test", key_len, reta_sz, NULL, 0);
+		RTE_TEST_ASSERT(ctx != NULL, "Can not create CTX\n");
+
+		rte_thash_free_ctx(ctx);
+	}
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_free_null(void)
+{
+	struct rte_thash_ctx *ctx;
+
+	ctx = rte_thash_init_ctx("test", 40, 7, NULL, 0);
+	RTE_TEST_ASSERT(ctx != NULL, "Can not create CTX\n");
+
+	rte_thash_free_ctx(ctx);
+	rte_thash_free_ctx(NULL);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_add_invalid_helper(void)
+{
+	struct rte_thash_ctx *ctx;
+	const int key_len = 40;
+	int reta_sz = 7;
+	int ret;
+
+	ctx = rte_thash_init_ctx("test", key_len, reta_sz, NULL, 0);
+	RTE_TEST_ASSERT(ctx != NULL, "can not create thash ctx\n");
+
+	ret = rte_thash_add_helper(NULL, "test", reta_sz, 0);
+	RTE_TEST_ASSERT(ret == -EINVAL,
+		"Call succeeded with invalid parameters\n");
+
+	ret = rte_thash_add_helper(ctx, NULL, reta_sz, 0);
+	RTE_TEST_ASSERT(ret == -EINVAL,
+		"Call succeeded with invalid parameters\n");
+
+	ret = rte_thash_add_helper(ctx, "test", reta_sz - 1, 0);
+	RTE_TEST_ASSERT(ret == -EINVAL,
+		"Call succeeded with invalid parameters\n");
+
+	ret = rte_thash_add_helper(ctx, "test", reta_sz, key_len * 8);
+	RTE_TEST_ASSERT(ret == -EINVAL,
+		"Call succeeded with invalid parameters\n");
+
+	ret = rte_thash_add_helper(ctx, "first_range", reta_sz, 0);
+	RTE_TEST_ASSERT(ret == 0, "Can not create helper\n");
+
+	ret = rte_thash_add_helper(ctx, "first_range", reta_sz, 0);
+	RTE_TEST_ASSERT(ret == -EEXIST,
+		"Call succeeded with duplicated name\n");
+
+	/*
+	 * Create second helper with offset 3 * reta_sz.
+	 * Note first_range helper created range in key:
+	 * [0, 32 + length{= reta_sz} - 1), i.e [0, 37).
+	 * second range is [44, 81)
+	 */
+	ret = rte_thash_add_helper(ctx, "second_range", reta_sz,
+		32 +  2 * reta_sz);
+	RTE_TEST_ASSERT(ret == 0, "Can not create helper\n");
+
+	/*
+	 * Try to create overlapping with first_ and second_ ranges,
+	 * i.e. [6, 49)
+	 */
+	ret = rte_thash_add_helper(ctx, "third_range", 2 * reta_sz, reta_sz);
+	RTE_TEST_ASSERT(ret == -EEXIST,
+		"Call succeeded with overlapping ranges\n");
+
+	rte_thash_free_ctx(ctx);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_find_existing(void)
+{
+	struct rte_thash_ctx *ctx, *ret_ctx;
+
+	ctx = rte_thash_init_ctx("test", 40, 7, NULL, 0);
+	RTE_TEST_ASSERT(ctx != NULL, "can not create thash ctx\n");
+
+	ret_ctx = rte_thash_find_existing("test");
+	RTE_TEST_ASSERT(ret_ctx != NULL, "can not find existing ctx\n");
+
+	rte_thash_free_ctx(ctx);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_get_helper(void)
+{
+	struct rte_thash_ctx *ctx;
+	struct rte_thash_subtuple_helper *h;
+	int ret;
+
+	ctx = rte_thash_init_ctx("test", 40, 7, NULL, 0);
+	RTE_TEST_ASSERT(ctx != NULL, "Can not create thash ctx\n");
+
+	h = rte_thash_get_helper(NULL, "first_range");
+	RTE_TEST_ASSERT(h == NULL, "Call succeeded with invalid parameters\n");
+
+	h = rte_thash_get_helper(ctx, NULL);
+	RTE_TEST_ASSERT(h == NULL, "Call succeeded with invalid parameters\n");
+
+	ret = rte_thash_add_helper(ctx, "first_range", 8, 0);
+	RTE_TEST_ASSERT(ret == 0, "Can not create helper\n");
+
+	h = rte_thash_get_helper(ctx, "first_range");
+	RTE_TEST_ASSERT(h != NULL, "Can not find helper\n");
+
+	rte_thash_free_ctx(ctx);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_period_overflow(void)
+{
+	struct rte_thash_ctx *ctx;
+	int reta_sz = 7; /* reflects polynomial degree */
+	int ret;
+
+	/* first create without RTE_THASH_IGNORE_PERIOD_OVERFLOW flag */
+	ctx = rte_thash_init_ctx("test", 40, reta_sz, NULL, 0);
+	RTE_TEST_ASSERT(ctx != NULL, "Can not create thash ctx\n");
+
+	/* requested range > (2^reta_sz) - 1 */
+	ret = rte_thash_add_helper(ctx, "test", (1 << reta_sz), 0);
+	RTE_TEST_ASSERT(ret == -ENOSPC,
+		"Call succeeded with invalid parameters\n");
+
+	/* requested range == len + 32 - 1, smaller than (2^reta_sz) - 1 */
+	ret = rte_thash_add_helper(ctx, "test", (1 << reta_sz) - 32, 0);
+	RTE_TEST_ASSERT(ret == 0, "Can not create helper\n");
+
+	rte_thash_free_ctx(ctx);
+
+	/* create with RTE_THASH_IGNORE_PERIOD_OVERFLOW flag */
+	ctx = rte_thash_init_ctx("test", 40, reta_sz, NULL,
+		RTE_THASH_IGNORE_PERIOD_OVERFLOW);
+	RTE_TEST_ASSERT(ctx != NULL, "Can not create thash ctx\n");
+
+	/* requested range > (2^reta_sz - 1) */
+	ret = rte_thash_add_helper(ctx, "test", (1 << reta_sz) + 10, 0);
+	RTE_TEST_ASSERT(ret == 0, "Can not create helper\n");
+
+	rte_thash_free_ctx(ctx);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_predictable_rss_min_seq(void)
+{
+	struct rte_thash_ctx *ctx;
+	struct rte_thash_subtuple_helper *h;
+	const int key_len = 40;
+	int reta_sz = 6;
+	uint8_t initial_key[key_len];
+	const uint8_t *new_key;
+	int ret;
+	union rte_thash_tuple tuple;
+	uint32_t orig_hash, adj_hash, adj;
+	unsigned int desired_value = 27 & HASH_MSK(reta_sz);
+	uint16_t port_value = 22;
+
+	memset(initial_key, 0, key_len);
+
+	ctx = rte_thash_init_ctx("test", key_len, reta_sz, initial_key,
+		RTE_THASH_MINIMAL_SEQ);
+	RTE_TEST_ASSERT(ctx != NULL, "can not create thash ctx\n");
+
+	ret = rte_thash_add_helper(ctx, "snat", sizeof(uint16_t) * 8,
+		offsetof(union rte_thash_tuple, v4.sport) * 8);
+	RTE_TEST_ASSERT(ret == 0, "can not add helper, ret %d\n", ret);
+
+	h = rte_thash_get_helper(ctx, "snat");
+	RTE_TEST_ASSERT(h != NULL, "can not find helper\n");
+
+	new_key = rte_thash_get_key(ctx);
+	tuple.v4.src_addr = RTE_IPV4(0, 0, 0, 0);
+	tuple.v4.dst_addr = RTE_IPV4(0, 0, 0, 0);
+	tuple.v4.sport = 0;
+	tuple.v4.sport = rte_cpu_to_be_16(port_value);
+	tuple.v4.dport = 0;
+	tuple.v4.sctp_tag = rte_be_to_cpu_32(tuple.v4.sctp_tag);
+
+	orig_hash = rte_softrss((uint32_t *)&tuple,
+		RTE_THASH_V4_L4_LEN, new_key);
+	adj = rte_thash_get_complement(h, orig_hash, desired_value);
+
+	tuple.v4.sctp_tag = rte_cpu_to_be_32(tuple.v4.sctp_tag);
+	tuple.v4.sport ^= rte_cpu_to_be_16(adj);
+	tuple.v4.sctp_tag = rte_be_to_cpu_32(tuple.v4.sctp_tag);
+
+	adj_hash = rte_softrss((uint32_t *)&tuple,
+		RTE_THASH_V4_L4_LEN, new_key);
+	RTE_TEST_ASSERT((adj_hash & HASH_MSK(reta_sz)) ==
+		desired_value, "bad desired value\n");
+
+	rte_thash_free_ctx(ctx);
+
+	return TEST_SUCCESS;
+}
+
+/*
+ * This test creates 7 subranges in the following order:
+ * range_one	= [56, 95),	len = 8, offset = 56
+ * range_two	= [64, 103),	len = 8, offset = 64
+ * range_three	= [120, 159),	len = 8, offset = 120
+ * range_four	= [48, 87),	len = 8, offset = 48
+ * range_five	= [57, 95),	len = 7, offset = 57
+ * range_six	= [40, 111),	len = 40, offset = 40
+ * range_seven	= [0, 39),	len = 8, offset = 0
+ */
+struct range {
+	const char *name;
+	int len;
+	int offset;
+	int byte_idx;
+};
+
+struct range rng_arr[] = {
+	{"one",   8,  56,  7},
+	{"two",   8,  64,  8},
+	{"three", 8,  120, 15},
+	{"four",  8,  48,  6},
+	{"six",   40, 40,  9},
+	{"five",  7,  57,  7},
+	{"seven", 8,  0,   0}
+};
+
+static int
+test_predictable_rss_multirange(void)
+{
+	struct rte_thash_ctx *ctx;
+	struct rte_thash_subtuple_helper *h[RTE_DIM(rng_arr)];
+	const uint8_t *new_key;
+	const int key_len = 40;
+	int reta_sz = 7;
+	unsigned int i, j, k;
+	int ret;
+	uint32_t desired_value = rte_rand() & HASH_MSK(reta_sz);
+	uint8_t tuples[RTE_DIM(rng_arr)][16] = { {0} };
+	uint32_t *ptr;
+	uint32_t hashes[RTE_DIM(rng_arr)];
+	uint32_t adj_hashes[RTE_DIM(rng_arr)];
+	uint32_t adj;
+
+	ctx = rte_thash_init_ctx("test", key_len, reta_sz, NULL, 0);
+	RTE_TEST_ASSERT(ctx != NULL, "can not create thash ctx\n");
+
+	for (i = 0; i < RTE_DIM(rng_arr); i++) {
+		ret = rte_thash_add_helper(ctx, rng_arr[i].name,
+			rng_arr[i].len, rng_arr[i].offset);
+		RTE_TEST_ASSERT(ret == 0, "can not add helper\n");
+
+		h[i] = rte_thash_get_helper(ctx, rng_arr[i].name);
+		RTE_TEST_ASSERT(h[i] != NULL, "can not find helper\n");
+	}
+	new_key = rte_thash_get_key(ctx);
+
+	/*
+	 * calculate hashes, complements, then adjust keys with
+	 * complements and recalsulate hashes
+	 */
+	for (i = 0; i < RTE_DIM(rng_arr); i++) {
+		for (k = 0; k < 100; k++) {
+			/* init with random keys */
+			ptr = (uint32_t *)&tuples[i][0];
+			for (j = 0; j < 4; j++)
+				ptr[j] = rte_rand();
+			/* convert keys from BE to CPU byte order */
+			for (j = 0; j < 4; j++)
+				ptr[j] = rte_be_to_cpu_32(ptr[j]);
+
+			hashes[i] = rte_softrss(ptr, 4, new_key);
+			adj = rte_thash_get_complement(h[i], hashes[i],
+				desired_value);
+			/* convert back to BE to adjust the value */
+			for (j = 0; j < 4; j++)
+				ptr[j] = rte_cpu_to_be_32(ptr[j]);
+
+			tuples[i][rng_arr[i].byte_idx] ^= adj;
+
+			for (j = 0; j < 4; j++)
+				ptr[j] = rte_be_to_cpu_32(ptr[j]);
+
+			adj_hashes[i] = rte_softrss(ptr, 4, new_key);
+			RTE_TEST_ASSERT((adj_hashes[i] & HASH_MSK(reta_sz)) ==
+				desired_value,
+				"bad desired value for %d tuple\n", i);
+		}
+	}
+
+	rte_thash_free_ctx(ctx);
+
+	return TEST_SUCCESS;
+}
+
+static int
+cmp_tuple_eq(void *userdata, uint8_t *tuple)
+{
+	return memcmp(userdata, tuple, TUPLE_SZ);
+}
+
+static int
+test_adjust_tuple(void)
+{
+	struct rte_thash_ctx *ctx;
+	struct rte_thash_subtuple_helper *h;
+	const int key_len = 40;
+	const uint8_t *new_key;
+	uint8_t tuple[TUPLE_SZ];
+	uint32_t tmp_tuple[TUPLE_SZ / sizeof(uint32_t)];
+	uint32_t tuple_copy[TUPLE_SZ / sizeof(uint32_t)];
+	uint32_t hash;
+	int reta_sz = CHAR_BIT;
+	int ret;
+	unsigned int i, desired_value = rte_rand() & HASH_MSK(reta_sz);
+
+	memset(tuple, 0xab, TUPLE_SZ);
+
+	ctx = rte_thash_init_ctx("test", key_len, reta_sz, NULL, 0);
+	RTE_TEST_ASSERT(ctx != NULL, "can not create thash ctx\n");
+
+	/*
+	 * set offset to be in the middle of a byte
+	 * set size of the subtuple to be 2 * rets_sz
+	 * to have the room for random bits
+	 */
+	ret = rte_thash_add_helper(ctx, "test", reta_sz * 2,
+		(5 * CHAR_BIT) + 4);
+	RTE_TEST_ASSERT(ret == 0, "can not add helper, ret %d\n", ret);
+
+	new_key = rte_thash_get_key(ctx);
+
+	h = rte_thash_get_helper(ctx, "test");
+	RTE_TEST_ASSERT(h != NULL, "can not find helper\n");
+
+	ret = rte_thash_adjust_tuple(ctx, h, tuple, TUPLE_SZ, desired_value,
+		1, NULL, NULL);
+	RTE_TEST_ASSERT(ret == 0, "can not adjust tuple, ret %d\n", ret);
+
+	for (i = 0; i < (TUPLE_SZ / 4); i++)
+		tmp_tuple[i] =
+			rte_be_to_cpu_32(*(uint32_t *)&tuple[i * 4]);
+
+	hash = rte_softrss(tmp_tuple, TUPLE_SZ / 4, new_key);
+	RTE_TEST_ASSERT((hash & HASH_MSK(reta_sz)) ==
+		desired_value, "bad desired value\n");
+
+
+	/* Pass previously calculated tuple to callback function */
+	memcpy(tuple_copy, tuple, TUPLE_SZ);
+
+	memset(tuple, 0xab, TUPLE_SZ);
+	ret = rte_thash_adjust_tuple(ctx, h, tuple, TUPLE_SZ, desired_value,
+		1, cmp_tuple_eq, tuple_copy);
+	RTE_TEST_ASSERT(ret == -EEXIST,
+		"adjust tuple didn't indicate collision\n");
+
+	/*
+	 * Make the function to generate random bits into subtuple
+	 * after first adjustment attempt.
+	 */
+	memset(tuple, 0xab, TUPLE_SZ);
+	ret = rte_thash_adjust_tuple(ctx, h, tuple, TUPLE_SZ, desired_value,
+		2, cmp_tuple_eq, tuple_copy);
+	RTE_TEST_ASSERT(ret == 0, "can not adjust tuple, ret %d\n", ret);
+
+	for (i = 0; i < (TUPLE_SZ / 4); i++)
+		tmp_tuple[i] =
+			rte_be_to_cpu_32(*(uint32_t *)&tuple[i * 4]);
+
+	hash = rte_softrss(tmp_tuple, TUPLE_SZ / 4, new_key);
+	RTE_TEST_ASSERT((hash & HASH_MSK(reta_sz)) ==
+		desired_value, "bad desired value\n");
+
+	rte_thash_free_ctx(ctx);
+
+	return TEST_SUCCESS;
+}
+
+static struct unit_test_suite thash_tests = {
+	.suite_name = "thash autotest",
+	.setup = NULL,
+	.teardown = NULL,
+	.unit_test_cases = {
+	TEST_CASE(test_toeplitz_hash_calc),
+	TEST_CASE(test_create_invalid),
+	TEST_CASE(test_multiple_create),
+	TEST_CASE(test_free_null),
+	TEST_CASE(test_add_invalid_helper),
+	TEST_CASE(test_find_existing),
+	TEST_CASE(test_get_helper),
+	TEST_CASE(test_period_overflow),
+	TEST_CASE(test_predictable_rss_min_seq),
+	TEST_CASE(test_predictable_rss_multirange),
+	TEST_CASE(test_adjust_tuple),
+	TEST_CASES_END()
+	}
+};
+
+static int
+test_thash(void)
+{
+	return unit_test_suite_runner(&thash_tests);
 }
 
 REGISTER_TEST_COMMAND(thash_autotest, test_thash);
-- 
2.7.4


^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [dpdk-dev] [PATCH v2 2/3] hash: add predictable RSS implementation
  2021-04-11 18:51     ` Medvedkin, Vladimir
@ 2021-04-12  9:47       ` Ananyev, Konstantin
  2021-04-13 12:28         ` Medvedkin, Vladimir
  0 siblings, 1 reply; 45+ messages in thread
From: Ananyev, Konstantin @ 2021-04-12  9:47 UTC (permalink / raw)
  To: Medvedkin, Vladimir, dev
  Cc: Chilikin, Andrey, Kinsella, Ray, Wang, Yipeng1, Gobriel, Sameh,
	Richardson, Bruce

> 
> <snip>
> 
> >> +#defineRETA_SZ_MIN2U
> >> +#defineRETA_SZ_MAX16U
> >
> > Should these RETA_SZ defines be in public header?
> > So user can know what are allowed values?
> >
> 
> I don't think this is necessary, because the user chooses it not
> arbitrary, but depending on the NIC.

Sure thing, but it would be goo for the user to know what are the SW
Limitations on it without digging through .c.

> 
> >> +#define RETA_SZ_IN_RANGE(reta_sz)((reta_sz >= RETA_SZ_MIN) && \
> 
> <snip>
> 
> >> +uint32_t i;
> >
> > Empty line is  missing.
> >
> 
> Thanks
> 
> >> +if ((name == NULL) || (key_len == 0) || !RETA_SZ_IN_RANGE(reta_sz)) {
> >> +rte_errno = EINVAL;
> >> +return NULL;
> >> +}
> 
> <snip>
> 
> >> +static inline void
> >> +set_bit(uint8_t *ptr, uint32_t bit, uint32_t pos)
> >> +{
> >> +uint32_t byte_idx = pos >> 3;
> >
> > Just as a nit to be consistent with the line below:
> > pos / CHAR_BIT;
> >
> 
> Fixed
> 
> >> +uint32_t bit_idx = (CHAR_BIT - 1) - (pos & (CHAR_BIT - 1));
> >> +uint8_t tmp;
> 
> <snip>
> 
> >> +ent = rte_zmalloc(NULL, sizeof(struct rte_thash_subtuple_helper) +
> >> +sizeof(uint32_t) * (1 << ctx->reta_sz_log), 0);
> >
> > Helper can be used by data-path code (via rte_thash_get_compliment()) right?
> > Then might be better to align it at cache-line.
> >
> 
> Agree, I'll fix it
> 
> >> +if (ent == NULL)
> >> +return -ENOMEM;
> 
> <snip>
> 
> >>   uint32_t
> >> -rte_thash_get_compliment(struct rte_thash_subtuple_helper *h __rte_unused,
> >> -uint32_t hash __rte_unused, uint32_t desired_hash __rte_unused)
> >> +rte_thash_get_compliment(struct rte_thash_subtuple_helper *h,
> >> +uint32_t hash, uint32_t desired_hash)
> >>   {
> >> -return 0;
> >> +return h->compl_table[(hash ^ desired_hash) & h->lsb_msk];
> >>   }
> >
> > Would it make sense to add another-one for multi values:
> > rte_thash_get_compliment(uint32_t hash, const uint32_t desired_hashes[], uint32_t adj_hash[], uint32_t num);
> > So user can get adjustment values for multiple queues at once?
> >
> 
> At the moment I can't find scenarios why do we need to have a bulk
> version for this function

My thought was about case when number of configured
HW queues is less than reta_size.
Let say reta_size==4, but user configured only 3 queues and reta={0,1,2,0}.
In that case for queue 0, both 0 and 3 values would suit. 


^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [dpdk-dev] [PATCH v2 2/3] hash: add predictable RSS implementation
  2021-04-12  9:47       ` Ananyev, Konstantin
@ 2021-04-13 12:28         ` Medvedkin, Vladimir
  0 siblings, 0 replies; 45+ messages in thread
From: Medvedkin, Vladimir @ 2021-04-13 12:28 UTC (permalink / raw)
  To: Ananyev, Konstantin, dev
  Cc: Chilikin, Andrey, Kinsella, Ray, Wang, Yipeng1, Gobriel, Sameh,
	Richardson, Bruce

Hi Konstantin,

On 12/04/2021 12:47, Ananyev, Konstantin wrote:
>>
>> <snip>
>>
>>>> +#defineRETA_SZ_MIN2U
>>>> +#defineRETA_SZ_MAX16U
>>>
>>> Should these RETA_SZ defines be in public header?
>>> So user can know what are allowed values?
>>>
>>
>> I don't think this is necessary, because the user chooses it not
>> arbitrary, but depending on the NIC.
> 
> Sure thing, but it would be goo for the user to know what are the SW
> Limitations on it without digging through .c.
> 

I see, I'll move it to .h

>>
>>>> +#define RETA_SZ_IN_RANGE(reta_sz)((reta_sz >= RETA_SZ_MIN) && \
>>
>> <snip>
>>
>>>> +uint32_t i;
>>>
>>> Empty line is  missing.
>>>
>>
>> Thanks
>>
>>>> +if ((name == NULL) || (key_len == 0) || !RETA_SZ_IN_RANGE(reta_sz)) {
>>>> +rte_errno = EINVAL;
>>>> +return NULL;
>>>> +}
>>
>> <snip>
>>
>>>> +static inline void
>>>> +set_bit(uint8_t *ptr, uint32_t bit, uint32_t pos)
>>>> +{
>>>> +uint32_t byte_idx = pos >> 3;
>>>
>>> Just as a nit to be consistent with the line below:
>>> pos / CHAR_BIT;
>>>
>>
>> Fixed
>>
>>>> +uint32_t bit_idx = (CHAR_BIT - 1) - (pos & (CHAR_BIT - 1));
>>>> +uint8_t tmp;
>>
>> <snip>
>>
>>>> +ent = rte_zmalloc(NULL, sizeof(struct rte_thash_subtuple_helper) +
>>>> +sizeof(uint32_t) * (1 << ctx->reta_sz_log), 0);
>>>
>>> Helper can be used by data-path code (via rte_thash_get_compliment()) right?
>>> Then might be better to align it at cache-line.
>>>
>>
>> Agree, I'll fix it
>>
>>>> +if (ent == NULL)
>>>> +return -ENOMEM;
>>
>> <snip>
>>
>>>>    uint32_t
>>>> -rte_thash_get_compliment(struct rte_thash_subtuple_helper *h __rte_unused,
>>>> -uint32_t hash __rte_unused, uint32_t desired_hash __rte_unused)
>>>> +rte_thash_get_compliment(struct rte_thash_subtuple_helper *h,
>>>> +uint32_t hash, uint32_t desired_hash)
>>>>    {
>>>> -return 0;
>>>> +return h->compl_table[(hash ^ desired_hash) & h->lsb_msk];
>>>>    }
>>>
>>> Would it make sense to add another-one for multi values:
>>> rte_thash_get_compliment(uint32_t hash, const uint32_t desired_hashes[], uint32_t adj_hash[], uint32_t num);
>>> So user can get adjustment values for multiple queues at once?
>>>
>>
>> At the moment I can't find scenarios why do we need to have a bulk
>> version for this function
> 
> My thought was about case when number of configured
> HW queues is less than reta_size.
> Let say reta_size==4, but user configured only 3 queues and reta={0,1,2,0}.
> In that case for queue 0, both 0 and 3 values would suit.
> 

Ah, yes, I remember out discussion about this. I'll think about this and 
add next releases.
I should be kind of

int
rte_thash_get_compliment_reta(struct rte_thash_subtuple_helper *helper, 
uint32_t hash, const uint16_t queue_id, struct rte_eth_rss_reta_entry64 
*reta, int reta_sz);

where reta is completed by rte_eth_dev_rss_reta_query()


-- 
Regards,
Vladimir

^ permalink raw reply	[flat|nested] 45+ messages in thread

* [dpdk-dev] [PATCH v4 0/3] Predictable RSS feature
  2021-04-11 19:11   ` [dpdk-dev] [PATCH v3 " Vladimir Medvedkin
@ 2021-04-13 13:19     ` Vladimir Medvedkin
  2021-04-14 18:04       ` Wang, Yipeng1
                         ` (6 more replies)
  2021-04-13 13:19     ` [dpdk-dev] [PATCH v4 1/3] hash: add predictable RSS API Vladimir Medvedkin
                       ` (2 subsequent siblings)
  3 siblings, 7 replies; 45+ messages in thread
From: Vladimir Medvedkin @ 2021-04-13 13:19 UTC (permalink / raw)
  To: dev
  Cc: konstantin.ananyev, andrey.chilikin, ray.kinsella, yipeng1.wang,
	sameh.gobriel, bruce.richardson

This patch series introduces predictable RSS feature.
It is based on the idea of searching for partial hash collisions
within Toeplitz hash.

The Toeplitz hash function is a homomorphism between (G, ^) and (H, ^),
where (G, ^) - is a group of tuples and (H, ^) is a group of hashes
with respect to XOR operation. So tuples and hashes could be treated as
n-dimension and 32-dimension vector spaces over GF(2).
So, f(x ^ y) == f(x) ^ f(y)
where f - is the toeplitz hash function and x, y are tuples.

The ability to predict partial collisions allows user to compute
input hash value with desired LSB values.
Usually number of LSB's are defined by the size of RSS Redirection Table.

There could be number of use cases, for example:
1) NAT. Using this library it is possible to select a new port number
on a translation in the way that rss hash for original tuple will have
the same LSB's as rss hash for reverse tuple.
2) IPSec/MPLS/Vxlan. It is possible to choose tunnel id to be pinned to
a desired queue.
3) TCP stack. It is possible to choose a source port number for outgoing
connections in the way that received replies will be assigned to
desired queue.
4) RSS hash key generation. Hash key initialization with random values
does not guarantee an uniform distribution amongst queues. This library
uses mathematically proved algorithm to complete the rss hash key to
provide the best distribution.

v4:
- RETA_SZ macros were moved to .h
- fixed typos 

v3:
- reworked rte_thash_adjust_tuple()
- added extra comments
- fixed typos
- rte_thash_adjust_tuple() API was putted into the first commit

v2:
- added extra API rte_thash_adjust_tuple()
- added extra tests for rte_thash_adjust_tuple()
- added extra fields to rte_thash_subtuple_helper struct
- fixed typos

Vladimir Medvedkin (3):
  hash: add predictable RSS API
  hash: add predictable RSS implementation
  test/hash: add additional thash tests

 app/test/test_thash.c       | 469 ++++++++++++++++++++++++++++++-
 lib/librte_hash/meson.build |   3 +-
 lib/librte_hash/rte_thash.c | 671 ++++++++++++++++++++++++++++++++++++++++++++
 lib/librte_hash/rte_thash.h | 198 +++++++++++++
 lib/librte_hash/version.map |   8 +
 5 files changed, 1342 insertions(+), 7 deletions(-)
 create mode 100644 lib/librte_hash/rte_thash.c

-- 
2.7.4


^ permalink raw reply	[flat|nested] 45+ messages in thread

* [dpdk-dev] [PATCH v4 1/3] hash: add predictable RSS API
  2021-04-11 19:11   ` [dpdk-dev] [PATCH v3 " Vladimir Medvedkin
  2021-04-13 13:19     ` [dpdk-dev] [PATCH v4 " Vladimir Medvedkin
@ 2021-04-13 13:19     ` Vladimir Medvedkin
  2021-04-14 17:06       ` Wang, Yipeng1
  2021-04-13 13:19     ` [dpdk-dev] [PATCH v4 2/3] hash: add predictable RSS implementation Vladimir Medvedkin
  2021-04-13 13:19     ` [dpdk-dev] [PATCH v4 3/3] test/hash: add additional thash tests Vladimir Medvedkin
  3 siblings, 1 reply; 45+ messages in thread
From: Vladimir Medvedkin @ 2021-04-13 13:19 UTC (permalink / raw)
  To: dev
  Cc: konstantin.ananyev, andrey.chilikin, ray.kinsella, yipeng1.wang,
	sameh.gobriel, bruce.richardson

This patch adds predictable RSS API.
It is based on the idea of searching partial Toeplitz hash collisions.

Signed-off-by: Vladimir Medvedkin <vladimir.medvedkin@intel.com>
---
 lib/librte_hash/meson.build |   3 +-
 lib/librte_hash/rte_thash.c | 109 ++++++++++++++++++++++++
 lib/librte_hash/rte_thash.h | 198 ++++++++++++++++++++++++++++++++++++++++++++
 lib/librte_hash/version.map |   8 ++
 4 files changed, 317 insertions(+), 1 deletion(-)
 create mode 100644 lib/librte_hash/rte_thash.c

diff --git a/lib/librte_hash/meson.build b/lib/librte_hash/meson.build
index 242859f..3546014 100644
--- a/lib/librte_hash/meson.build
+++ b/lib/librte_hash/meson.build
@@ -8,6 +8,7 @@ headers = files('rte_fbk_hash.h',
 	'rte_thash.h')
 indirect_headers += files('rte_crc_arm64.h')
 
-sources = files('rte_cuckoo_hash.c', 'rte_fbk_hash.c')
+sources = files('rte_cuckoo_hash.c', 'rte_fbk_hash.c', 'rte_thash.c')
+deps += ['net']
 deps += ['ring']
 deps += ['rcu']
diff --git a/lib/librte_hash/rte_thash.c b/lib/librte_hash/rte_thash.c
new file mode 100644
index 0000000..1325678
--- /dev/null
+++ b/lib/librte_hash/rte_thash.c
@@ -0,0 +1,109 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2021 Intel Corporation
+ */
+
+#include <rte_thash.h>
+#include <rte_tailq.h>
+#include <rte_random.h>
+#include <rte_memcpy.h>
+#include <rte_errno.h>
+#include <rte_eal.h>
+#include <rte_eal_memconfig.h>
+#include <rte_malloc.h>
+
+#define THASH_NAME_LEN		64
+
+struct thash_lfsr {
+	uint32_t	ref_cnt;
+	uint32_t	poly;
+	/**< polynomial associated with the lfsr */
+	uint32_t	rev_poly;
+	/**< polynomial to generate the sequence in reverse direction */
+	uint32_t	state;
+	/**< current state of the lfsr */
+	uint32_t	rev_state;
+	/**< current state of the lfsr for reverse direction */
+	uint32_t	deg;	/**< polynomial degree*/
+	uint32_t	bits_cnt;  /**< number of bits generated by lfsr*/
+};
+
+struct rte_thash_subtuple_helper {
+	char	name[THASH_NAME_LEN];	/** < Name of subtuple configuration */
+	LIST_ENTRY(rte_thash_subtuple_helper)	next;
+	struct thash_lfsr	*lfsr;
+	uint32_t	offset;		/** < Offset of the m-sequence */
+	uint32_t	len;		/** < Length of the m-sequence */
+	uint32_t	tuple_offset;	/** < Offset in bits of the subtuple */
+	uint32_t	tuple_len;	/** < Length in bits of the subtuple */
+	uint32_t	lsb_msk;	/** < (1 << reta_sz_log) - 1 */
+	__extension__ uint32_t	compl_table[0] __rte_cache_aligned;
+	/** < Complementary table */
+};
+
+struct rte_thash_ctx {
+	char		name[THASH_NAME_LEN];
+	LIST_HEAD(, rte_thash_subtuple_helper) head;
+	uint32_t	key_len;	/** < Length of the NIC RSS hash key */
+	uint32_t	reta_sz_log;	/** < size of the RSS ReTa in bits */
+	uint32_t	subtuples_nb;	/** < number of subtuples */
+	uint32_t	flags;
+	uint8_t		hash_key[0];
+};
+
+struct rte_thash_ctx *
+rte_thash_init_ctx(const char *name __rte_unused,
+	uint32_t key_len __rte_unused, uint32_t reta_sz __rte_unused,
+	uint8_t *key __rte_unused, uint32_t flags __rte_unused)
+{
+	return NULL;
+}
+
+struct rte_thash_ctx *
+rte_thash_find_existing(const char *name __rte_unused)
+{
+	return NULL;
+}
+
+void
+rte_thash_free_ctx(struct rte_thash_ctx *ctx __rte_unused)
+{
+}
+
+int
+rte_thash_add_helper(struct rte_thash_ctx *ctx __rte_unused,
+	const char *name __rte_unused, uint32_t len __rte_unused,
+	uint32_t offset __rte_unused)
+{
+	return 0;
+}
+
+struct rte_thash_subtuple_helper *
+rte_thash_get_helper(struct rte_thash_ctx *ctx __rte_unused,
+	const char *name __rte_unused)
+{
+	return NULL;
+}
+
+uint32_t
+rte_thash_get_complement(struct rte_thash_subtuple_helper *h __rte_unused,
+	uint32_t hash __rte_unused, uint32_t desired_hash __rte_unused)
+{
+	return 0;
+}
+
+const uint8_t *
+rte_thash_get_key(struct rte_thash_ctx *ctx __rte_unused)
+{
+	return NULL;
+}
+
+int
+rte_thash_adjust_tuple(struct rte_thash_ctx *ctx __rte_unused,
+	struct rte_thash_subtuple_helper *h __rte_unused,
+	uint8_t *tuple __rte_unused, unsigned int tuple_len __rte_unused,
+	uint32_t desired_value __rte_unused,
+	unsigned int attempts __rte_unused,
+	rte_thash_check_tuple_t fn __rte_unused, void *userdata __rte_unused)
+{
+	return 0;
+}
diff --git a/lib/librte_hash/rte_thash.h b/lib/librte_hash/rte_thash.h
index 061efa2..f3e05fc 100644
--- a/lib/librte_hash/rte_thash.h
+++ b/lib/librte_hash/rte_thash.h
@@ -1,5 +1,6 @@
 /* SPDX-License-Identifier: BSD-3-Clause
  * Copyright(c) 2015-2019 Vladimir Medvedkin <medvedkinv@gmail.com>
+ * Copyright(c) 2021 Intel Corporation
  */
 
 #ifndef _RTE_THASH_H
@@ -222,6 +223,203 @@ rte_softrss_be(uint32_t *input_tuple, uint32_t input_len,
 	return ret;
 }
 
+/** @internal Minimum size of the RSS ReTa */
+#define	RTE_THASH_RETA_SZ_MIN	2U
+/** @internal Maximum size of the RSS ReTa */
+#define	RTE_THASH_RETA_SZ_MAX	16U
+
+/**
+ * LFSR will ignore if generated m-sequence has more than 2^n -1 bits
+ */
+#define RTE_THASH_IGNORE_PERIOD_OVERFLOW	0x1
+/**
+ * Generate minimal required bit (equal to ReTa LSB) sequence into
+ * the hash_key
+ */
+#define RTE_THASH_MINIMAL_SEQ			0x2
+
+/** @internal thash context structure. */
+struct rte_thash_ctx;
+/** @internal thash helper structure. */
+struct rte_thash_subtuple_helper;
+
+/**
+ * Create a new thash context.
+ *
+ * @param name
+ *  Context name
+ * @param key_len
+ *  Length of the toeplitz hash key
+ * @param reta_sz
+ *  Logarithm of the NIC's Redirection Table (ReTa) size,
+ *  i.e. number of the LSBs if the hash used to determine
+ *  the reta entry.
+ * @param key
+ *  Pointer to the key used to init an internal key state.
+ *  Could be NULL, in this case internal key will be inited with random.
+ * @param flags
+ *  Supported flags are:
+ *   RTE_THASH_IGNORE_PERIOD_OVERFLOW
+ *   RTE_THASH_MINIMAL_SEQ
+ * @return
+ *  A pointer to the created context on success
+ *  NULL otherwise
+ */
+__rte_experimental
+struct rte_thash_ctx *
+rte_thash_init_ctx(const char *name, uint32_t key_len, uint32_t reta_sz,
+	uint8_t *key, uint32_t flags);
+
+/**
+ * Find an existing thash context and return a pointer to it.
+ *
+ * @param name
+ *  Name of the thash context
+ * @return
+ *  Pointer to the thash context or NULL if it was not found with rte_errno
+ *  set appropriately. Possible rte_errno values include:
+ *   - ENOENT - required entry not available to return.
+ */
+__rte_experimental
+struct rte_thash_ctx *
+rte_thash_find_existing(const char *name);
+
+/**
+ * Free a thash context object
+ *
+ * @param ctx
+ *  Thash context
+ * @return
+ *  None
+ */
+__rte_experimental
+void
+rte_thash_free_ctx(struct rte_thash_ctx *ctx);
+
+/**
+ * Add a special properties to the toeplitz hash key inside a thash context.
+ * Creates an internal helper struct which has a complementary table
+ * to calculate toeplitz hash collisions.
+ * This function is not multi-thread safe.
+ *
+ * @param ctx
+ *  Thash context
+ * @param name
+ *  Name of the helper
+ * @param len
+ *  Length in bits of the target subtuple
+ *  Must be no shorter than reta_sz passed on rte_thash_init_ctx().
+ * @param offset
+ *  Offset in bits of the subtuple
+ * @return
+ *  0 on success
+ *  negative on error
+ */
+__rte_experimental
+int
+rte_thash_add_helper(struct rte_thash_ctx *ctx, const char *name, uint32_t len,
+	uint32_t offset);
+
+/**
+ * Find a helper in the context by the given name
+ *
+ * @param ctx
+ *  Thash context
+ * @param name
+ *  Name of the helper
+ * @return
+ *  Pointer to the thash helper or NULL if it was not found.
+ */
+__rte_experimental
+struct rte_thash_subtuple_helper *
+rte_thash_get_helper(struct rte_thash_ctx *ctx, const char *name);
+
+/**
+ * Get a complementary value for the subtuple to produce a
+ * partial toeplitz hash collision. It must be XOR'ed with the
+ * subtuple to produce the hash value with the desired hash LSB's
+ * This function is multi-thread safe.
+ *
+ * @param h
+ *  Pointer to the helper struct
+ * @param hash
+ *  Toeplitz hash value calculated for the given tuple
+ * @param desired_hash
+ *  Desired hash value to find a collision for
+ * @return
+ *  A complementary value which must be xored with the corresponding subtuple
+ */
+__rte_experimental
+uint32_t
+rte_thash_get_complement(struct rte_thash_subtuple_helper *h,
+	uint32_t hash, uint32_t desired_hash);
+
+/**
+ * Get a pointer to the toeplitz hash contained in the context.
+ * It changes after each addition of a helper. It should be installed to
+ * the NIC.
+ *
+ * @param ctx
+ *  Thash context
+ * @return
+ *  A pointer to the toeplitz hash key
+ */
+__rte_experimental
+const uint8_t *
+rte_thash_get_key(struct rte_thash_ctx *ctx);
+
+/**
+ * Function prototype for the rte_thash_adjust_tuple
+ * to check if adjusted tuple could be used.
+ * Generally it is some kind of lookup function to check
+ * if adjusted tuple is already in use.
+ *
+ * @param userdata
+ *  Pointer to the userdata. It could be a pointer to the
+ *  table with used tuples to search.
+ * @param tuple
+ *  Pointer to the tuple to check
+ *
+ * @return
+ *  1 on success
+ *  0 otherwise
+ */
+typedef int (*rte_thash_check_tuple_t)(void *userdata, uint8_t *tuple);
+
+/**
+ * Adjusts tuple in the way to make Toeplitz hash has
+ * desired least significant bits.
+ * This function is multi-thread safe.
+ *
+ * @param ctx
+ *  Thash context
+ * @param h
+ *  Pointer to the helper struct
+ * @param tuple
+ *  Pointer to the tuple to be adjusted
+ * @param tuple_len
+ *  Length of the tuple. Must be multiple of 4.
+ * @param desired_value
+ *  Desired value of least significant bits of the hash
+ * @param attempts
+ *  Number of attempts to adjust tuple with fn() calling
+ * @param fn
+ *  Callback function to check adjusted tuple. Could be NULL
+ * @param userdata
+ *  Pointer to the userdata to be passed to fn(). Could be NULL
+ *
+ * @return
+ *  0 on success
+ *  negative otherwise
+ */
+__rte_experimental
+int
+rte_thash_adjust_tuple(struct rte_thash_ctx *ctx,
+	struct rte_thash_subtuple_helper *h,
+	uint8_t *tuple, unsigned int tuple_len,
+	uint32_t desired_value, unsigned int attempts,
+	rte_thash_check_tuple_t fn, void *userdata);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/lib/librte_hash/version.map b/lib/librte_hash/version.map
index c6d7308..17cb8aa 100644
--- a/lib/librte_hash/version.map
+++ b/lib/librte_hash/version.map
@@ -32,9 +32,17 @@ DPDK_21 {
 EXPERIMENTAL {
 	global:
 
+	rte_thash_adjust_tuple;
 	rte_hash_free_key_with_position;
 	rte_hash_lookup_with_hash_bulk;
 	rte_hash_lookup_with_hash_bulk_data;
 	rte_hash_max_key_id;
 	rte_hash_rcu_qsbr_add;
+	rte_thash_add_helper;
+	rte_thash_find_existing;
+	rte_thash_free_ctx;
+	rte_thash_get_complement;
+	rte_thash_get_helper;
+	rte_thash_get_key;
+	rte_thash_init_ctx;
 };
-- 
2.7.4


^ permalink raw reply	[flat|nested] 45+ messages in thread

* [dpdk-dev] [PATCH v4 2/3] hash: add predictable RSS implementation
  2021-04-11 19:11   ` [dpdk-dev] [PATCH v3 " Vladimir Medvedkin
  2021-04-13 13:19     ` [dpdk-dev] [PATCH v4 " Vladimir Medvedkin
  2021-04-13 13:19     ` [dpdk-dev] [PATCH v4 1/3] hash: add predictable RSS API Vladimir Medvedkin
@ 2021-04-13 13:19     ` Vladimir Medvedkin
  2021-04-14 17:51       ` Wang, Yipeng1
  2021-04-13 13:19     ` [dpdk-dev] [PATCH v4 3/3] test/hash: add additional thash tests Vladimir Medvedkin
  3 siblings, 1 reply; 45+ messages in thread
From: Vladimir Medvedkin @ 2021-04-13 13:19 UTC (permalink / raw)
  To: dev
  Cc: konstantin.ananyev, andrey.chilikin, ray.kinsella, yipeng1.wang,
	sameh.gobriel, bruce.richardson

This patch implements predictable RSS functionality.

Signed-off-by: Vladimir Medvedkin <vladimir.medvedkin@intel.com>
Acked-by: Konstantin Ananyev <konstantin.ananyev@intel.com>
---
 lib/librte_hash/rte_thash.c | 608 ++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 585 insertions(+), 23 deletions(-)

diff --git a/lib/librte_hash/rte_thash.c b/lib/librte_hash/rte_thash.c
index 1325678..d871cee 100644
--- a/lib/librte_hash/rte_thash.c
+++ b/lib/librte_hash/rte_thash.c
@@ -12,6 +12,43 @@
 #include <rte_malloc.h>
 
 #define THASH_NAME_LEN		64
+#define TOEPLITZ_HASH_LEN	32
+
+#define RETA_SZ_IN_RANGE(reta_sz)	((reta_sz >= RTE_THASH_RETA_SZ_MIN) &&\
+					(reta_sz <= RTE_THASH_RETA_SZ_MAX))
+
+TAILQ_HEAD(rte_thash_list, rte_tailq_entry);
+static struct rte_tailq_elem rte_thash_tailq = {
+	.name = "RTE_THASH",
+};
+EAL_REGISTER_TAILQ(rte_thash_tailq)
+
+/**
+ * Table of some irreducible polinomials over GF(2).
+ * For lfsr they are reperesented in BE bit order, and
+ * x^0 is masked out.
+ * For example, poly x^5 + x^2 + 1 will be represented
+ * as (101001b & 11111b) = 01001b = 0x9
+ */
+static const uint32_t irreducible_poly_table[][4] = {
+	{0, 0, 0, 0},	/** < degree 0 */
+	{1, 1, 1, 1},	/** < degree 1 */
+	{0x3, 0x3, 0x3, 0x3},	/** < degree 2 and so on... */
+	{0x5, 0x3, 0x5, 0x3},
+	{0x9, 0x3, 0x9, 0x3},
+	{0x9, 0x1b, 0xf, 0x5},
+	{0x21, 0x33, 0x1b, 0x2d},
+	{0x41, 0x11, 0x71, 0x9},
+	{0x71, 0xa9, 0xf5, 0x8d},
+	{0x21, 0xd1, 0x69, 0x1d9},
+	{0x81, 0x2c1, 0x3b1, 0x185},
+	{0x201, 0x541, 0x341, 0x461},
+	{0x941, 0x609, 0xe19, 0x45d},
+	{0x1601, 0x1f51, 0x1171, 0x359},
+	{0x2141, 0x2111, 0x2db1, 0x2109},
+	{0x4001, 0x801, 0x101, 0x7301},
+	{0x7781, 0xa011, 0x4211, 0x86d9},
+};
 
 struct thash_lfsr {
 	uint32_t	ref_cnt;
@@ -50,60 +87,585 @@ struct rte_thash_ctx {
 	uint8_t		hash_key[0];
 };
 
+static inline uint32_t
+get_bit_lfsr(struct thash_lfsr *lfsr)
+{
+	uint32_t bit, ret;
+
+	/*
+	 * masking the TAP bits defined by the polynomial and
+	 * calculating parity
+	 */
+	bit = __builtin_popcount(lfsr->state & lfsr->poly) & 0x1;
+	ret = lfsr->state & 0x1;
+	lfsr->state = ((lfsr->state >> 1) | (bit << (lfsr->deg - 1))) &
+		((1 << lfsr->deg) - 1);
+
+	lfsr->bits_cnt++;
+	return ret;
+}
+
+static inline uint32_t
+get_rev_bit_lfsr(struct thash_lfsr *lfsr)
+{
+	uint32_t bit, ret;
+
+	bit = __builtin_popcount(lfsr->rev_state & lfsr->rev_poly) & 0x1;
+	ret = lfsr->rev_state & (1 << (lfsr->deg - 1));
+	lfsr->rev_state = ((lfsr->rev_state << 1) | bit) &
+		((1 << lfsr->deg) - 1);
+
+	lfsr->bits_cnt++;
+	return ret;
+}
+
+static inline uint32_t
+thash_get_rand_poly(uint32_t poly_degree)
+{
+	return irreducible_poly_table[poly_degree][rte_rand() %
+		RTE_DIM(irreducible_poly_table[poly_degree])];
+}
+
+static struct thash_lfsr *
+alloc_lfsr(struct rte_thash_ctx *ctx)
+{
+	struct thash_lfsr *lfsr;
+	uint32_t i;
+
+	if (ctx == NULL)
+		return NULL;
+
+	lfsr = rte_zmalloc(NULL, sizeof(struct thash_lfsr), 0);
+	if (lfsr == NULL)
+		return NULL;
+
+	lfsr->deg = ctx->reta_sz_log;
+	lfsr->poly = thash_get_rand_poly(lfsr->deg);
+	do {
+		lfsr->state = rte_rand() & ((1 << lfsr->deg) - 1);
+	} while (lfsr->state == 0);
+	/* init reverse order polynomial */
+	lfsr->rev_poly = (lfsr->poly >> 1) | (1 << (lfsr->deg - 1));
+	/* init proper rev_state*/
+	lfsr->rev_state = lfsr->state;
+	for (i = 0; i <= lfsr->deg; i++)
+		get_rev_bit_lfsr(lfsr);
+
+	/* clear bits_cnt after rev_state was inited */
+	lfsr->bits_cnt = 0;
+	lfsr->ref_cnt = 1;
+
+	return lfsr;
+}
+
+static void
+attach_lfsr(struct rte_thash_subtuple_helper *h, struct thash_lfsr *lfsr)
+{
+	lfsr->ref_cnt++;
+	h->lfsr = lfsr;
+}
+
+static void
+free_lfsr(struct thash_lfsr *lfsr)
+{
+	lfsr->ref_cnt--;
+	if (lfsr->ref_cnt == 0)
+		rte_free(lfsr);
+}
+
 struct rte_thash_ctx *
-rte_thash_init_ctx(const char *name __rte_unused,
-	uint32_t key_len __rte_unused, uint32_t reta_sz __rte_unused,
-	uint8_t *key __rte_unused, uint32_t flags __rte_unused)
+rte_thash_init_ctx(const char *name, uint32_t key_len, uint32_t reta_sz,
+	uint8_t *key, uint32_t flags)
 {
+	struct rte_thash_ctx *ctx;
+	struct rte_tailq_entry *te;
+	struct rte_thash_list *thash_list;
+	uint32_t i;
+
+	if ((name == NULL) || (key_len == 0) || !RETA_SZ_IN_RANGE(reta_sz)) {
+		rte_errno = EINVAL;
+		return NULL;
+	}
+
+	thash_list = RTE_TAILQ_CAST(rte_thash_tailq.head, rte_thash_list);
+
+	rte_mcfg_tailq_write_lock();
+
+	/* guarantee there's no existing */
+	TAILQ_FOREACH(te, thash_list, next) {
+		ctx = (struct rte_thash_ctx *)te->data;
+		if (strncmp(name, ctx->name, sizeof(ctx->name)) == 0)
+			break;
+	}
+	ctx = NULL;
+	if (te != NULL) {
+		rte_errno = EEXIST;
+		goto exit;
+	}
+
+	/* allocate tailq entry */
+	te = rte_zmalloc("THASH_TAILQ_ENTRY", sizeof(*te), 0);
+	if (te == NULL) {
+		RTE_LOG(ERR, HASH,
+			"Can not allocate tailq entry for thash context %s\n",
+			name);
+		rte_errno = ENOMEM;
+		goto exit;
+	}
+
+	ctx = rte_zmalloc(NULL, sizeof(struct rte_thash_ctx) + key_len, 0);
+	if (ctx == NULL) {
+		RTE_LOG(ERR, HASH, "thash ctx %s memory allocation failed\n",
+			name);
+		rte_errno = ENOMEM;
+		goto free_te;
+	}
+
+	rte_strlcpy(ctx->name, name, sizeof(ctx->name));
+	ctx->key_len = key_len;
+	ctx->reta_sz_log = reta_sz;
+	LIST_INIT(&ctx->head);
+	ctx->flags = flags;
+
+	if (key)
+		rte_memcpy(ctx->hash_key, key, key_len);
+	else {
+		for (i = 0; i < key_len; i++)
+			ctx->hash_key[i] = rte_rand();
+	}
+
+	te->data = (void *)ctx;
+	TAILQ_INSERT_TAIL(thash_list, te, next);
+
+	rte_mcfg_tailq_write_unlock();
+
+	return ctx;
+free_te:
+	rte_free(te);
+exit:
+	rte_mcfg_tailq_write_unlock();
 	return NULL;
 }
 
 struct rte_thash_ctx *
-rte_thash_find_existing(const char *name __rte_unused)
+rte_thash_find_existing(const char *name)
 {
-	return NULL;
+	struct rte_thash_ctx *ctx;
+	struct rte_tailq_entry *te;
+	struct rte_thash_list *thash_list;
+
+	thash_list = RTE_TAILQ_CAST(rte_thash_tailq.head, rte_thash_list);
+
+	rte_mcfg_tailq_read_lock();
+	TAILQ_FOREACH(te, thash_list, next) {
+		ctx = (struct rte_thash_ctx *)te->data;
+		if (strncmp(name, ctx->name, sizeof(ctx->name)) == 0)
+			break;
+	}
+
+	rte_mcfg_tailq_read_unlock();
+
+	if (te == NULL) {
+		rte_errno = ENOENT;
+		return NULL;
+	}
+
+	return ctx;
 }
 
 void
-rte_thash_free_ctx(struct rte_thash_ctx *ctx __rte_unused)
+rte_thash_free_ctx(struct rte_thash_ctx *ctx)
 {
+	struct rte_tailq_entry *te;
+	struct rte_thash_list *thash_list;
+	struct rte_thash_subtuple_helper *ent, *tmp;
+
+	if (ctx == NULL)
+		return;
+
+	thash_list = RTE_TAILQ_CAST(rte_thash_tailq.head, rte_thash_list);
+	rte_mcfg_tailq_write_lock();
+	TAILQ_FOREACH(te, thash_list, next) {
+		if (te->data == (void *)ctx)
+			break;
+	}
+
+	if (te != NULL)
+		TAILQ_REMOVE(thash_list, te, next);
+
+	rte_mcfg_tailq_write_unlock();
+	ent = LIST_FIRST(&(ctx->head));
+	while (ent) {
+		free_lfsr(ent->lfsr);
+		tmp = ent;
+		ent = LIST_NEXT(ent, next);
+		LIST_REMOVE(tmp, next);
+		rte_free(tmp);
+	}
+
+	rte_free(ctx);
+	rte_free(te);
+}
+
+static inline void
+set_bit(uint8_t *ptr, uint32_t bit, uint32_t pos)
+{
+	uint32_t byte_idx = pos / CHAR_BIT;
+	uint32_t bit_idx = (CHAR_BIT - 1) - (pos & (CHAR_BIT - 1));
+	uint8_t tmp;
+
+	tmp = ptr[byte_idx];
+	tmp &= ~(1 << bit_idx);
+	tmp |= bit << bit_idx;
+	ptr[byte_idx] = tmp;
+}
+
+/**
+ * writes m-sequence to the hash_key for range [start, end]
+ * (i.e. including start and end positions)
+ */
+static int
+generate_subkey(struct rte_thash_ctx *ctx, struct thash_lfsr *lfsr,
+	uint32_t start, uint32_t end)
+{
+	uint32_t i;
+	uint32_t req_bits = (start < end) ? (end - start) : (start - end);
+	req_bits++; /* due to including end */
+
+	/* check if lfsr overflow period of the m-sequence */
+	if (((lfsr->bits_cnt + req_bits) > (1ULL << lfsr->deg) - 1) &&
+			((ctx->flags & RTE_THASH_IGNORE_PERIOD_OVERFLOW) !=
+			RTE_THASH_IGNORE_PERIOD_OVERFLOW))
+		return -ENOSPC;
+
+	if (start < end) {
+		/* original direction (from left to right)*/
+		for (i = start; i <= end; i++)
+			set_bit(ctx->hash_key, get_bit_lfsr(lfsr), i);
+
+	} else {
+		/* reverse direction (from right to left) */
+		for (i = end; i >= start; i--)
+			set_bit(ctx->hash_key, get_rev_bit_lfsr(lfsr), i);
+	}
+
+	return 0;
+}
+
+static inline uint32_t
+get_subvalue(struct rte_thash_ctx *ctx, uint32_t offset)
+{
+	uint32_t *tmp, val;
+
+	tmp = (uint32_t *)(&ctx->hash_key[offset >> 3]);
+	val = rte_be_to_cpu_32(*tmp);
+	val >>= (TOEPLITZ_HASH_LEN - ((offset & (CHAR_BIT - 1)) +
+		ctx->reta_sz_log));
+
+	return val & ((1 << ctx->reta_sz_log) - 1);
+}
+
+static inline void
+generate_complement_table(struct rte_thash_ctx *ctx,
+	struct rte_thash_subtuple_helper *h)
+{
+	int i, j, k;
+	uint32_t val;
+	uint32_t start;
+
+	start = h->offset + h->len - (2 * ctx->reta_sz_log - 1);
+
+	for (i = 1; i < (1 << ctx->reta_sz_log); i++) {
+		val = 0;
+		for (j = i; j; j &= (j - 1)) {
+			k = rte_bsf32(j);
+			val ^= get_subvalue(ctx, start - k +
+				ctx->reta_sz_log - 1);
+		}
+		h->compl_table[val] = i;
+	}
+}
+
+static inline int
+insert_before(struct rte_thash_ctx *ctx,
+	struct rte_thash_subtuple_helper *ent,
+	struct rte_thash_subtuple_helper *cur_ent,
+	struct rte_thash_subtuple_helper *next_ent,
+	uint32_t start, uint32_t end, uint32_t range_end)
+{
+	int ret;
+
+	if (end < cur_ent->offset) {
+		ent->lfsr = alloc_lfsr(ctx);
+		if (ent->lfsr == NULL) {
+			rte_free(ent);
+			return -ENOMEM;
+		}
+		/* generate nonoverlapping range [start, end) */
+		ret = generate_subkey(ctx, ent->lfsr, start, end - 1);
+		if (ret != 0) {
+			free_lfsr(ent->lfsr);
+			rte_free(ent);
+			return ret;
+		}
+	} else if ((next_ent != NULL) && (end > next_ent->offset)) {
+		rte_free(ent);
+		return -ENOSPC;
+	}
+	attach_lfsr(ent, cur_ent->lfsr);
+
+	/**
+	 * generate partially overlapping range
+	 * [start, cur_ent->start) in reverse order
+	 */
+	ret = generate_subkey(ctx, ent->lfsr, cur_ent->offset - 1, start);
+	if (ret != 0) {
+		free_lfsr(ent->lfsr);
+		rte_free(ent);
+		return ret;
+	}
+
+	if (end > range_end) {
+		/**
+		 * generate partially overlapping range
+		 * (range_end, end)
+		 */
+		ret = generate_subkey(ctx, ent->lfsr, range_end, end - 1);
+		if (ret != 0) {
+			free_lfsr(ent->lfsr);
+			rte_free(ent);
+			return ret;
+		}
+	}
+
+	LIST_INSERT_BEFORE(cur_ent, ent, next);
+	generate_complement_table(ctx, ent);
+	ctx->subtuples_nb++;
+	return 0;
+}
+
+static inline int
+insert_after(struct rte_thash_ctx *ctx,
+	struct rte_thash_subtuple_helper *ent,
+	struct rte_thash_subtuple_helper *cur_ent,
+	struct rte_thash_subtuple_helper *next_ent,
+	struct rte_thash_subtuple_helper *prev_ent,
+	uint32_t end, uint32_t range_end)
+{
+	int ret;
+
+	if ((next_ent != NULL) && (end > next_ent->offset)) {
+		rte_free(ent);
+		return -EEXIST;
+	}
+
+	attach_lfsr(ent, cur_ent->lfsr);
+	if (end > range_end) {
+		/**
+		 * generate partially overlapping range
+		 * (range_end, end)
+		 */
+		ret = generate_subkey(ctx, ent->lfsr, range_end, end - 1);
+		if (ret != 0) {
+			free_lfsr(ent->lfsr);
+			rte_free(ent);
+			return ret;
+		}
+	}
+
+	LIST_INSERT_AFTER(prev_ent, ent, next);
+	generate_complement_table(ctx, ent);
+	ctx->subtuples_nb++;
+
+	return 0;
 }
 
 int
-rte_thash_add_helper(struct rte_thash_ctx *ctx __rte_unused,
-	const char *name __rte_unused, uint32_t len __rte_unused,
-	uint32_t offset __rte_unused)
+rte_thash_add_helper(struct rte_thash_ctx *ctx, const char *name, uint32_t len,
+	uint32_t offset)
 {
+	struct rte_thash_subtuple_helper *ent, *cur_ent, *prev_ent, *next_ent;
+	uint32_t start, end;
+	int ret;
+
+	if ((ctx == NULL) || (name == NULL) || (len < ctx->reta_sz_log) ||
+			((offset + len + TOEPLITZ_HASH_LEN - 1) >
+			ctx->key_len * CHAR_BIT))
+		return -EINVAL;
+
+	/* Check for existing name*/
+	LIST_FOREACH(cur_ent, &ctx->head, next) {
+		if (strncmp(name, cur_ent->name, sizeof(cur_ent->name)) == 0)
+			return -EEXIST;
+	}
+
+	end = offset + len + TOEPLITZ_HASH_LEN - 1;
+	start = ((ctx->flags & RTE_THASH_MINIMAL_SEQ) ==
+		RTE_THASH_MINIMAL_SEQ) ? (end - (2 * ctx->reta_sz_log - 1)) :
+		offset;
+
+	ent = rte_zmalloc(NULL, sizeof(struct rte_thash_subtuple_helper) +
+		sizeof(uint32_t) * (1 << ctx->reta_sz_log),
+		RTE_CACHE_LINE_SIZE);
+	if (ent == NULL)
+		return -ENOMEM;
+
+	rte_strlcpy(ent->name, name, sizeof(ent->name));
+	ent->offset = start;
+	ent->len = end - start;
+	ent->tuple_offset = offset;
+	ent->tuple_len = len;
+	ent->lsb_msk = (1 << ctx->reta_sz_log) - 1;
+
+	cur_ent = LIST_FIRST(&ctx->head);
+	while (cur_ent) {
+		uint32_t range_end = cur_ent->offset + cur_ent->len;
+		next_ent = LIST_NEXT(cur_ent, next);
+		prev_ent = cur_ent;
+		/* Iterate through overlapping ranges */
+		while ((next_ent != NULL) && (next_ent->offset < range_end)) {
+			range_end = RTE_MAX(next_ent->offset + next_ent->len,
+				range_end);
+			if (start > next_ent->offset)
+				prev_ent = next_ent;
+
+			next_ent = LIST_NEXT(next_ent, next);
+		}
+
+		if (start < cur_ent->offset)
+			return insert_before(ctx, ent, cur_ent, next_ent,
+				start, end, range_end);
+		else if (start < range_end)
+			return insert_after(ctx, ent, cur_ent, next_ent,
+				prev_ent, end, range_end);
+
+		cur_ent = next_ent;
+		continue;
+	}
+
+	ent->lfsr = alloc_lfsr(ctx);
+	if (ent->lfsr == NULL) {
+		rte_free(ent);
+		return -ENOMEM;
+	}
+
+	/* generate nonoverlapping range [start, end) */
+	ret = generate_subkey(ctx, ent->lfsr, start, end - 1);
+	if (ret != 0) {
+		free_lfsr(ent->lfsr);
+		rte_free(ent);
+		return ret;
+	}
+	if (LIST_EMPTY(&ctx->head)) {
+		LIST_INSERT_HEAD(&ctx->head, ent, next);
+	} else {
+		LIST_FOREACH(next_ent, &ctx->head, next)
+			prev_ent = next_ent;
+
+		LIST_INSERT_AFTER(prev_ent, ent, next);
+	}
+	generate_complement_table(ctx, ent);
+	ctx->subtuples_nb++;
+
 	return 0;
 }
 
 struct rte_thash_subtuple_helper *
-rte_thash_get_helper(struct rte_thash_ctx *ctx __rte_unused,
-	const char *name __rte_unused)
+rte_thash_get_helper(struct rte_thash_ctx *ctx, const char *name)
 {
+	struct rte_thash_subtuple_helper *ent;
+
+	if ((ctx == NULL) || (name == NULL))
+		return NULL;
+
+	LIST_FOREACH(ent, &ctx->head, next) {
+		if (strncmp(name, ent->name, sizeof(ent->name)) == 0)
+			return ent;
+	}
+
 	return NULL;
 }
 
 uint32_t
-rte_thash_get_complement(struct rte_thash_subtuple_helper *h __rte_unused,
-	uint32_t hash __rte_unused, uint32_t desired_hash __rte_unused)
+rte_thash_get_complement(struct rte_thash_subtuple_helper *h,
+	uint32_t hash, uint32_t desired_hash)
 {
-	return 0;
+	return h->compl_table[(hash ^ desired_hash) & h->lsb_msk];
 }
 
 const uint8_t *
-rte_thash_get_key(struct rte_thash_ctx *ctx __rte_unused)
+rte_thash_get_key(struct rte_thash_ctx *ctx)
 {
-	return NULL;
+	return ctx->hash_key;
+}
+
+static inline void
+xor_bit(uint8_t *ptr, uint32_t bit, uint32_t pos)
+{
+	uint32_t byte_idx = pos >> 3;
+	uint32_t bit_idx = (CHAR_BIT - 1) - (pos & (CHAR_BIT - 1));
+	uint8_t tmp;
+
+	tmp = ptr[byte_idx];
+	tmp ^= bit << bit_idx;
+	ptr[byte_idx] = tmp;
 }
 
 int
-rte_thash_adjust_tuple(struct rte_thash_ctx *ctx __rte_unused,
-	struct rte_thash_subtuple_helper *h __rte_unused,
-	uint8_t *tuple __rte_unused, unsigned int tuple_len __rte_unused,
-	uint32_t desired_value __rte_unused,
-	unsigned int attempts __rte_unused,
-	rte_thash_check_tuple_t fn __rte_unused, void *userdata __rte_unused)
+rte_thash_adjust_tuple(struct rte_thash_ctx *ctx,
+	struct rte_thash_subtuple_helper *h,
+	uint8_t *tuple, unsigned int tuple_len,
+	uint32_t desired_value,	unsigned int attempts,
+	rte_thash_check_tuple_t fn, void *userdata)
 {
-	return 0;
+	uint32_t tmp_tuple[tuple_len / sizeof(uint32_t)];
+	unsigned int i, j, ret = 0;
+	uint32_t hash, adj_bits;
+	uint8_t bit;
+	const uint8_t *hash_key;
+
+	if ((ctx == NULL) || (h == NULL) || (tuple == NULL) ||
+			(tuple_len % sizeof(uint32_t) != 0) || (attempts <= 0))
+		return -EINVAL;
+
+	hash_key = rte_thash_get_key(ctx);
+
+	for (i = 0; i < attempts; i++) {
+		for (j = 0; j < (tuple_len / 4); j++)
+			tmp_tuple[j] =
+				rte_be_to_cpu_32(*(uint32_t *)&tuple[j * 4]);
+
+		hash = rte_softrss(tmp_tuple, tuple_len / 4, hash_key);
+		adj_bits = rte_thash_get_complement(h, hash, desired_value);
+
+		/*
+		 * Hint: LSB of adj_bits corresponds to
+		 * offset + len bit of tuple
+		 */
+		for (j = 0; j < sizeof(uint32_t) * CHAR_BIT; j++) {
+			bit = (adj_bits >> j) & 0x1;
+			if (bit)
+				xor_bit(tuple, bit, h->tuple_offset +
+					h->tuple_len - 1 - j);
+		}
+
+		if (fn != NULL) {
+			ret = (fn(userdata, tuple)) ? 0 : -EEXIST;
+			if (ret == 0)
+				return 0;
+			else if (i < (attempts - 1)) {
+				/* Update tuple with random bits */
+				for (j = 0; j < h->tuple_len; j++) {
+					bit = rte_rand() & 0x1;
+					if (bit)
+						xor_bit(tuple, bit,
+							h->tuple_offset +
+							h->tuple_len - 1 - j);
+				}
+			}
+		} else
+			return 0;
+	}
+
+	return ret;
 }
-- 
2.7.4


^ permalink raw reply	[flat|nested] 45+ messages in thread

* [dpdk-dev] [PATCH v4 3/3] test/hash: add additional thash tests
  2021-04-11 19:11   ` [dpdk-dev] [PATCH v3 " Vladimir Medvedkin
                       ` (2 preceding siblings ...)
  2021-04-13 13:19     ` [dpdk-dev] [PATCH v4 2/3] hash: add predictable RSS implementation Vladimir Medvedkin
@ 2021-04-13 13:19     ` Vladimir Medvedkin
  2021-04-14 17:56       ` Wang, Yipeng1
  3 siblings, 1 reply; 45+ messages in thread
From: Vladimir Medvedkin @ 2021-04-13 13:19 UTC (permalink / raw)
  To: dev
  Cc: konstantin.ananyev, andrey.chilikin, ray.kinsella, yipeng1.wang,
	sameh.gobriel, bruce.richardson

This patch adds tests for predictable RSS feature.

Signed-off-by: Vladimir Medvedkin <vladimir.medvedkin@intel.com>
---
 app/test/test_thash.c | 469 +++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 463 insertions(+), 6 deletions(-)

diff --git a/app/test/test_thash.c b/app/test/test_thash.c
index a6aadd1..d8981fb 100644
--- a/app/test/test_thash.c
+++ b/app/test/test_thash.c
@@ -5,11 +5,15 @@
 #include <rte_common.h>
 #include <rte_eal.h>
 #include <rte_ip.h>
+#include <rte_random.h>
 
 #include "test.h"
 
 #include <rte_thash.h>
 
+#define HASH_MSK(reta_sz)	((1 << reta_sz) - 1)
+#define TUPLE_SZ	(RTE_THASH_V4_L4_LEN * 4)
+
 struct test_thash_v4 {
 	uint32_t	dst_ip;
 	uint32_t	src_ip;
@@ -75,7 +79,7 @@ uint8_t default_rss_key[] = {
 };
 
 static int
-test_thash(void)
+test_toeplitz_hash_calc(void)
 {
 	uint32_t i, j;
 	union rte_thash_tuple tuple;
@@ -100,7 +104,7 @@ test_thash(void)
 				RTE_THASH_V4_L4_LEN, default_rss_key);
 		if ((rss_l3 != v4_tbl[i].hash_l3) ||
 				(rss_l3l4 != v4_tbl[i].hash_l3l4))
-			return -1;
+			return -TEST_FAILED;
 		/*Calculate hash with converted key*/
 		rss_l3 = rte_softrss_be((uint32_t *)&tuple,
 				RTE_THASH_V4_L3_LEN, rss_key_be);
@@ -108,7 +112,7 @@ test_thash(void)
 				RTE_THASH_V4_L4_LEN, rss_key_be);
 		if ((rss_l3 != v4_tbl[i].hash_l3) ||
 				(rss_l3l4 != v4_tbl[i].hash_l3l4))
-			return -1;
+			return -TEST_FAILED;
 	}
 	for (i = 0; i < RTE_DIM(v6_tbl); i++) {
 		/*Fill ipv6 hdr*/
@@ -127,7 +131,7 @@ test_thash(void)
 				RTE_THASH_V6_L4_LEN, default_rss_key);
 		if ((rss_l3 != v6_tbl[i].hash_l3) ||
 				(rss_l3l4 != v6_tbl[i].hash_l3l4))
-			return -1;
+			return -TEST_FAILED;
 		/*Calculate hash with converted key*/
 		rss_l3 = rte_softrss_be((uint32_t *)&tuple,
 				RTE_THASH_V6_L3_LEN, rss_key_be);
@@ -135,9 +139,462 @@ test_thash(void)
 				RTE_THASH_V6_L4_LEN, rss_key_be);
 		if ((rss_l3 != v6_tbl[i].hash_l3) ||
 				(rss_l3l4 != v6_tbl[i].hash_l3l4))
-			return -1;
+			return -TEST_FAILED;
 	}
-	return 0;
+	return TEST_SUCCESS;
+}
+
+static int
+test_create_invalid(void)
+{
+	struct rte_thash_ctx *ctx;
+	int key_len = 40;
+	int reta_sz = 7;
+
+	ctx = rte_thash_init_ctx(NULL, key_len, reta_sz, NULL, 0);
+	RTE_TEST_ASSERT(ctx == NULL,
+		"Call succeeded with invalid parameters\n");
+
+	ctx = rte_thash_init_ctx("test", 0, reta_sz, NULL, 0);
+	RTE_TEST_ASSERT(ctx == NULL,
+		"Call succeeded with invalid parameters\n");
+
+	ctx = rte_thash_init_ctx(NULL, key_len, 1, NULL, 0);
+	RTE_TEST_ASSERT(ctx == NULL,
+		"Call succeeded with invalid parameters\n");
+
+	ctx = rte_thash_init_ctx(NULL, key_len, 17, NULL, 0);
+	RTE_TEST_ASSERT(ctx == NULL,
+		"Call succeeded with invalid parameters\n");
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_multiple_create(void)
+{
+	struct rte_thash_ctx *ctx;
+	int key_len = 40;
+	int reta_sz = 7;
+	int i;
+
+	for (i = 0; i < 100; i++) {
+		ctx = rte_thash_init_ctx("test", key_len, reta_sz, NULL, 0);
+		RTE_TEST_ASSERT(ctx != NULL, "Can not create CTX\n");
+
+		rte_thash_free_ctx(ctx);
+	}
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_free_null(void)
+{
+	struct rte_thash_ctx *ctx;
+
+	ctx = rte_thash_init_ctx("test", 40, 7, NULL, 0);
+	RTE_TEST_ASSERT(ctx != NULL, "Can not create CTX\n");
+
+	rte_thash_free_ctx(ctx);
+	rte_thash_free_ctx(NULL);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_add_invalid_helper(void)
+{
+	struct rte_thash_ctx *ctx;
+	const int key_len = 40;
+	int reta_sz = 7;
+	int ret;
+
+	ctx = rte_thash_init_ctx("test", key_len, reta_sz, NULL, 0);
+	RTE_TEST_ASSERT(ctx != NULL, "can not create thash ctx\n");
+
+	ret = rte_thash_add_helper(NULL, "test", reta_sz, 0);
+	RTE_TEST_ASSERT(ret == -EINVAL,
+		"Call succeeded with invalid parameters\n");
+
+	ret = rte_thash_add_helper(ctx, NULL, reta_sz, 0);
+	RTE_TEST_ASSERT(ret == -EINVAL,
+		"Call succeeded with invalid parameters\n");
+
+	ret = rte_thash_add_helper(ctx, "test", reta_sz - 1, 0);
+	RTE_TEST_ASSERT(ret == -EINVAL,
+		"Call succeeded with invalid parameters\n");
+
+	ret = rte_thash_add_helper(ctx, "test", reta_sz, key_len * 8);
+	RTE_TEST_ASSERT(ret == -EINVAL,
+		"Call succeeded with invalid parameters\n");
+
+	ret = rte_thash_add_helper(ctx, "first_range", reta_sz, 0);
+	RTE_TEST_ASSERT(ret == 0, "Can not create helper\n");
+
+	ret = rte_thash_add_helper(ctx, "first_range", reta_sz, 0);
+	RTE_TEST_ASSERT(ret == -EEXIST,
+		"Call succeeded with duplicated name\n");
+
+	/*
+	 * Create second helper with offset 3 * reta_sz.
+	 * Note first_range helper created range in key:
+	 * [0, 32 + length{= reta_sz} - 1), i.e [0, 37).
+	 * second range is [44, 81)
+	 */
+	ret = rte_thash_add_helper(ctx, "second_range", reta_sz,
+		32 +  2 * reta_sz);
+	RTE_TEST_ASSERT(ret == 0, "Can not create helper\n");
+
+	/*
+	 * Try to create overlapping with first_ and second_ ranges,
+	 * i.e. [6, 49)
+	 */
+	ret = rte_thash_add_helper(ctx, "third_range", 2 * reta_sz, reta_sz);
+	RTE_TEST_ASSERT(ret == -EEXIST,
+		"Call succeeded with overlapping ranges\n");
+
+	rte_thash_free_ctx(ctx);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_find_existing(void)
+{
+	struct rte_thash_ctx *ctx, *ret_ctx;
+
+	ctx = rte_thash_init_ctx("test", 40, 7, NULL, 0);
+	RTE_TEST_ASSERT(ctx != NULL, "can not create thash ctx\n");
+
+	ret_ctx = rte_thash_find_existing("test");
+	RTE_TEST_ASSERT(ret_ctx != NULL, "can not find existing ctx\n");
+
+	rte_thash_free_ctx(ctx);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_get_helper(void)
+{
+	struct rte_thash_ctx *ctx;
+	struct rte_thash_subtuple_helper *h;
+	int ret;
+
+	ctx = rte_thash_init_ctx("test", 40, 7, NULL, 0);
+	RTE_TEST_ASSERT(ctx != NULL, "Can not create thash ctx\n");
+
+	h = rte_thash_get_helper(NULL, "first_range");
+	RTE_TEST_ASSERT(h == NULL, "Call succeeded with invalid parameters\n");
+
+	h = rte_thash_get_helper(ctx, NULL);
+	RTE_TEST_ASSERT(h == NULL, "Call succeeded with invalid parameters\n");
+
+	ret = rte_thash_add_helper(ctx, "first_range", 8, 0);
+	RTE_TEST_ASSERT(ret == 0, "Can not create helper\n");
+
+	h = rte_thash_get_helper(ctx, "first_range");
+	RTE_TEST_ASSERT(h != NULL, "Can not find helper\n");
+
+	rte_thash_free_ctx(ctx);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_period_overflow(void)
+{
+	struct rte_thash_ctx *ctx;
+	int reta_sz = 7; /* reflects polynomial degree */
+	int ret;
+
+	/* first create without RTE_THASH_IGNORE_PERIOD_OVERFLOW flag */
+	ctx = rte_thash_init_ctx("test", 40, reta_sz, NULL, 0);
+	RTE_TEST_ASSERT(ctx != NULL, "Can not create thash ctx\n");
+
+	/* requested range > (2^reta_sz) - 1 */
+	ret = rte_thash_add_helper(ctx, "test", (1 << reta_sz), 0);
+	RTE_TEST_ASSERT(ret == -ENOSPC,
+		"Call succeeded with invalid parameters\n");
+
+	/* requested range == len + 32 - 1, smaller than (2^reta_sz) - 1 */
+	ret = rte_thash_add_helper(ctx, "test", (1 << reta_sz) - 32, 0);
+	RTE_TEST_ASSERT(ret == 0, "Can not create helper\n");
+
+	rte_thash_free_ctx(ctx);
+
+	/* create with RTE_THASH_IGNORE_PERIOD_OVERFLOW flag */
+	ctx = rte_thash_init_ctx("test", 40, reta_sz, NULL,
+		RTE_THASH_IGNORE_PERIOD_OVERFLOW);
+	RTE_TEST_ASSERT(ctx != NULL, "Can not create thash ctx\n");
+
+	/* requested range > (2^reta_sz - 1) */
+	ret = rte_thash_add_helper(ctx, "test", (1 << reta_sz) + 10, 0);
+	RTE_TEST_ASSERT(ret == 0, "Can not create helper\n");
+
+	rte_thash_free_ctx(ctx);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_predictable_rss_min_seq(void)
+{
+	struct rte_thash_ctx *ctx;
+	struct rte_thash_subtuple_helper *h;
+	const int key_len = 40;
+	int reta_sz = 6;
+	uint8_t initial_key[key_len];
+	const uint8_t *new_key;
+	int ret;
+	union rte_thash_tuple tuple;
+	uint32_t orig_hash, adj_hash, adj;
+	unsigned int desired_value = 27 & HASH_MSK(reta_sz);
+	uint16_t port_value = 22;
+
+	memset(initial_key, 0, key_len);
+
+	ctx = rte_thash_init_ctx("test", key_len, reta_sz, initial_key,
+		RTE_THASH_MINIMAL_SEQ);
+	RTE_TEST_ASSERT(ctx != NULL, "can not create thash ctx\n");
+
+	ret = rte_thash_add_helper(ctx, "snat", sizeof(uint16_t) * 8,
+		offsetof(union rte_thash_tuple, v4.sport) * 8);
+	RTE_TEST_ASSERT(ret == 0, "can not add helper, ret %d\n", ret);
+
+	h = rte_thash_get_helper(ctx, "snat");
+	RTE_TEST_ASSERT(h != NULL, "can not find helper\n");
+
+	new_key = rte_thash_get_key(ctx);
+	tuple.v4.src_addr = RTE_IPV4(0, 0, 0, 0);
+	tuple.v4.dst_addr = RTE_IPV4(0, 0, 0, 0);
+	tuple.v4.sport = 0;
+	tuple.v4.sport = rte_cpu_to_be_16(port_value);
+	tuple.v4.dport = 0;
+	tuple.v4.sctp_tag = rte_be_to_cpu_32(tuple.v4.sctp_tag);
+
+	orig_hash = rte_softrss((uint32_t *)&tuple,
+		RTE_THASH_V4_L4_LEN, new_key);
+	adj = rte_thash_get_complement(h, orig_hash, desired_value);
+
+	tuple.v4.sctp_tag = rte_cpu_to_be_32(tuple.v4.sctp_tag);
+	tuple.v4.sport ^= rte_cpu_to_be_16(adj);
+	tuple.v4.sctp_tag = rte_be_to_cpu_32(tuple.v4.sctp_tag);
+
+	adj_hash = rte_softrss((uint32_t *)&tuple,
+		RTE_THASH_V4_L4_LEN, new_key);
+	RTE_TEST_ASSERT((adj_hash & HASH_MSK(reta_sz)) ==
+		desired_value, "bad desired value\n");
+
+	rte_thash_free_ctx(ctx);
+
+	return TEST_SUCCESS;
+}
+
+/*
+ * This test creates 7 subranges in the following order:
+ * range_one	= [56, 95),	len = 8, offset = 56
+ * range_two	= [64, 103),	len = 8, offset = 64
+ * range_three	= [120, 159),	len = 8, offset = 120
+ * range_four	= [48, 87),	len = 8, offset = 48
+ * range_five	= [57, 95),	len = 7, offset = 57
+ * range_six	= [40, 111),	len = 40, offset = 40
+ * range_seven	= [0, 39),	len = 8, offset = 0
+ */
+struct range {
+	const char *name;
+	int len;
+	int offset;
+	int byte_idx;
+};
+
+struct range rng_arr[] = {
+	{"one",   8,  56,  7},
+	{"two",   8,  64,  8},
+	{"three", 8,  120, 15},
+	{"four",  8,  48,  6},
+	{"six",   40, 40,  9},
+	{"five",  7,  57,  7},
+	{"seven", 8,  0,   0}
+};
+
+static int
+test_predictable_rss_multirange(void)
+{
+	struct rte_thash_ctx *ctx;
+	struct rte_thash_subtuple_helper *h[RTE_DIM(rng_arr)];
+	const uint8_t *new_key;
+	const int key_len = 40;
+	int reta_sz = 7;
+	unsigned int i, j, k;
+	int ret;
+	uint32_t desired_value = rte_rand() & HASH_MSK(reta_sz);
+	uint8_t tuples[RTE_DIM(rng_arr)][16] = { {0} };
+	uint32_t *ptr;
+	uint32_t hashes[RTE_DIM(rng_arr)];
+	uint32_t adj_hashes[RTE_DIM(rng_arr)];
+	uint32_t adj;
+
+	ctx = rte_thash_init_ctx("test", key_len, reta_sz, NULL, 0);
+	RTE_TEST_ASSERT(ctx != NULL, "can not create thash ctx\n");
+
+	for (i = 0; i < RTE_DIM(rng_arr); i++) {
+		ret = rte_thash_add_helper(ctx, rng_arr[i].name,
+			rng_arr[i].len, rng_arr[i].offset);
+		RTE_TEST_ASSERT(ret == 0, "can not add helper\n");
+
+		h[i] = rte_thash_get_helper(ctx, rng_arr[i].name);
+		RTE_TEST_ASSERT(h[i] != NULL, "can not find helper\n");
+	}
+	new_key = rte_thash_get_key(ctx);
+
+	/*
+	 * calculate hashes, complements, then adjust keys with
+	 * complements and recalsulate hashes
+	 */
+	for (i = 0; i < RTE_DIM(rng_arr); i++) {
+		for (k = 0; k < 100; k++) {
+			/* init with random keys */
+			ptr = (uint32_t *)&tuples[i][0];
+			for (j = 0; j < 4; j++)
+				ptr[j] = rte_rand();
+			/* convert keys from BE to CPU byte order */
+			for (j = 0; j < 4; j++)
+				ptr[j] = rte_be_to_cpu_32(ptr[j]);
+
+			hashes[i] = rte_softrss(ptr, 4, new_key);
+			adj = rte_thash_get_complement(h[i], hashes[i],
+				desired_value);
+			/* convert back to BE to adjust the value */
+			for (j = 0; j < 4; j++)
+				ptr[j] = rte_cpu_to_be_32(ptr[j]);
+
+			tuples[i][rng_arr[i].byte_idx] ^= adj;
+
+			for (j = 0; j < 4; j++)
+				ptr[j] = rte_be_to_cpu_32(ptr[j]);
+
+			adj_hashes[i] = rte_softrss(ptr, 4, new_key);
+			RTE_TEST_ASSERT((adj_hashes[i] & HASH_MSK(reta_sz)) ==
+				desired_value,
+				"bad desired value for %d tuple\n", i);
+		}
+	}
+
+	rte_thash_free_ctx(ctx);
+
+	return TEST_SUCCESS;
+}
+
+static int
+cmp_tuple_eq(void *userdata, uint8_t *tuple)
+{
+	return memcmp(userdata, tuple, TUPLE_SZ);
+}
+
+static int
+test_adjust_tuple(void)
+{
+	struct rte_thash_ctx *ctx;
+	struct rte_thash_subtuple_helper *h;
+	const int key_len = 40;
+	const uint8_t *new_key;
+	uint8_t tuple[TUPLE_SZ];
+	uint32_t tmp_tuple[TUPLE_SZ / sizeof(uint32_t)];
+	uint32_t tuple_copy[TUPLE_SZ / sizeof(uint32_t)];
+	uint32_t hash;
+	int reta_sz = CHAR_BIT;
+	int ret;
+	unsigned int i, desired_value = rte_rand() & HASH_MSK(reta_sz);
+
+	memset(tuple, 0xab, TUPLE_SZ);
+
+	ctx = rte_thash_init_ctx("test", key_len, reta_sz, NULL, 0);
+	RTE_TEST_ASSERT(ctx != NULL, "can not create thash ctx\n");
+
+	/*
+	 * set offset to be in the middle of a byte
+	 * set size of the subtuple to be 2 * rets_sz
+	 * to have the room for random bits
+	 */
+	ret = rte_thash_add_helper(ctx, "test", reta_sz * 2,
+		(5 * CHAR_BIT) + 4);
+	RTE_TEST_ASSERT(ret == 0, "can not add helper, ret %d\n", ret);
+
+	new_key = rte_thash_get_key(ctx);
+
+	h = rte_thash_get_helper(ctx, "test");
+	RTE_TEST_ASSERT(h != NULL, "can not find helper\n");
+
+	ret = rte_thash_adjust_tuple(ctx, h, tuple, TUPLE_SZ, desired_value,
+		1, NULL, NULL);
+	RTE_TEST_ASSERT(ret == 0, "can not adjust tuple, ret %d\n", ret);
+
+	for (i = 0; i < (TUPLE_SZ / 4); i++)
+		tmp_tuple[i] =
+			rte_be_to_cpu_32(*(uint32_t *)&tuple[i * 4]);
+
+	hash = rte_softrss(tmp_tuple, TUPLE_SZ / 4, new_key);
+	RTE_TEST_ASSERT((hash & HASH_MSK(reta_sz)) ==
+		desired_value, "bad desired value\n");
+
+
+	/* Pass previously calculated tuple to callback function */
+	memcpy(tuple_copy, tuple, TUPLE_SZ);
+
+	memset(tuple, 0xab, TUPLE_SZ);
+	ret = rte_thash_adjust_tuple(ctx, h, tuple, TUPLE_SZ, desired_value,
+		1, cmp_tuple_eq, tuple_copy);
+	RTE_TEST_ASSERT(ret == -EEXIST,
+		"adjust tuple didn't indicate collision\n");
+
+	/*
+	 * Make the function to generate random bits into subtuple
+	 * after first adjustment attempt.
+	 */
+	memset(tuple, 0xab, TUPLE_SZ);
+	ret = rte_thash_adjust_tuple(ctx, h, tuple, TUPLE_SZ, desired_value,
+		2, cmp_tuple_eq, tuple_copy);
+	RTE_TEST_ASSERT(ret == 0, "can not adjust tuple, ret %d\n", ret);
+
+	for (i = 0; i < (TUPLE_SZ / 4); i++)
+		tmp_tuple[i] =
+			rte_be_to_cpu_32(*(uint32_t *)&tuple[i * 4]);
+
+	hash = rte_softrss(tmp_tuple, TUPLE_SZ / 4, new_key);
+	RTE_TEST_ASSERT((hash & HASH_MSK(reta_sz)) ==
+		desired_value, "bad desired value\n");
+
+	rte_thash_free_ctx(ctx);
+
+	return TEST_SUCCESS;
+}
+
+static struct unit_test_suite thash_tests = {
+	.suite_name = "thash autotest",
+	.setup = NULL,
+	.teardown = NULL,
+	.unit_test_cases = {
+	TEST_CASE(test_toeplitz_hash_calc),
+	TEST_CASE(test_create_invalid),
+	TEST_CASE(test_multiple_create),
+	TEST_CASE(test_free_null),
+	TEST_CASE(test_add_invalid_helper),
+	TEST_CASE(test_find_existing),
+	TEST_CASE(test_get_helper),
+	TEST_CASE(test_period_overflow),
+	TEST_CASE(test_predictable_rss_min_seq),
+	TEST_CASE(test_predictable_rss_multirange),
+	TEST_CASE(test_adjust_tuple),
+	TEST_CASES_END()
+	}
+};
+
+static int
+test_thash(void)
+{
+	return unit_test_suite_runner(&thash_tests);
 }
 
 REGISTER_TEST_COMMAND(thash_autotest, test_thash);
-- 
2.7.4


^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [dpdk-dev] [PATCH v4 1/3] hash: add predictable RSS API
  2021-04-13 13:19     ` [dpdk-dev] [PATCH v4 1/3] hash: add predictable RSS API Vladimir Medvedkin
@ 2021-04-14 17:06       ` Wang, Yipeng1
  0 siblings, 0 replies; 45+ messages in thread
From: Wang, Yipeng1 @ 2021-04-14 17:06 UTC (permalink / raw)
  To: Medvedkin, Vladimir, dev
  Cc: Ananyev, Konstantin, Chilikin, Andrey, Kinsella, Ray, Gobriel,
	Sameh, Richardson, Bruce

> -----Original Message-----
> From: Medvedkin, Vladimir <vladimir.medvedkin@intel.com>
> Sent: Tuesday, April 13, 2021 6:20 AM
> To: dev@dpdk.org
> Cc: Ananyev, Konstantin <konstantin.ananyev@intel.com>; Chilikin, Andrey
> <andrey.chilikin@intel.com>; Kinsella, Ray <ray.kinsella@intel.com>; Wang,
> Yipeng1 <yipeng1.wang@intel.com>; Gobriel, Sameh
> <sameh.gobriel@intel.com>; Richardson, Bruce
> <bruce.richardson@intel.com>
> Subject: [PATCH v4 1/3] hash: add predictable RSS API
> 
> This patch adds predictable RSS API.
> It is based on the idea of searching partial Toeplitz hash collisions.
> 
> Signed-off-by: Vladimir Medvedkin <vladimir.medvedkin@intel.com>
> ---
>  lib/librte_hash/meson.build |   3 +-
>  lib/librte_hash/rte_thash.c | 109 ++++++++++++++++++++++++
> lib/librte_hash/rte_thash.h | 198
> ++++++++++++++++++++++++++++++++++++++++++++
>  lib/librte_hash/version.map |   8 ++
>  4 files changed, 317 insertions(+), 1 deletion(-)  create mode 100644
> lib/librte_hash/rte_thash.c
> 
> diff --git a/lib/librte_hash/meson.build b/lib/librte_hash/meson.build index
> 242859f..3546014 100644
> --- a/lib/librte_hash/meson.build
> +++ b/lib/librte_hash/meson.build
> @@ -8,6 +8,7 @@ headers = files('rte_fbk_hash.h',
>  	'rte_thash.h')
>  indirect_headers += files('rte_crc_arm64.h')
> 
> -sources = files('rte_cuckoo_hash.c', 'rte_fbk_hash.c')
> +sources = files('rte_cuckoo_hash.c', 'rte_fbk_hash.c', 'rte_thash.c')
> +deps += ['net']
>  deps += ['ring']
>  deps += ['rcu']
> diff --git a/lib/librte_hash/rte_thash.c b/lib/librte_hash/rte_thash.c new file
> mode 100644 index 0000000..1325678
> --- /dev/null
> +++ b/lib/librte_hash/rte_thash.c
> @@ -0,0 +1,109 @@
> +/* SPDX-License-Identifier: BSD-3-Clause
> + * Copyright(c) 2021 Intel Corporation
> + */
> +
> +#include <rte_thash.h>
> +#include <rte_tailq.h>
> +#include <rte_random.h>
> +#include <rte_memcpy.h>
> +#include <rte_errno.h>
> +#include <rte_eal.h>
> +#include <rte_eal_memconfig.h>
> +#include <rte_malloc.h>
> +
> +#define THASH_NAME_LEN		64
> +
> +struct thash_lfsr {
> +	uint32_t	ref_cnt;
> +	uint32_t	poly;
> +	/**< polynomial associated with the lfsr */
> +	uint32_t	rev_poly;
> +	/**< polynomial to generate the sequence in reverse direction */
> +	uint32_t	state;
> +	/**< current state of the lfsr */
> +	uint32_t	rev_state;
> +	/**< current state of the lfsr for reverse direction */
> +	uint32_t	deg;	/**< polynomial degree*/
> +	uint32_t	bits_cnt;  /**< number of bits generated by lfsr*/
> +};
> +
> +struct rte_thash_subtuple_helper {
> +	char	name[THASH_NAME_LEN];	/** < Name of subtuple
> configuration */
> +	LIST_ENTRY(rte_thash_subtuple_helper)	next;
> +	struct thash_lfsr	*lfsr;
> +	uint32_t	offset;		/** < Offset of the m-sequence */
> +	uint32_t	len;		/** < Length of the m-sequence */
> +	uint32_t	tuple_offset;	/** < Offset in bits of the subtuple */
> +	uint32_t	tuple_len;	/** < Length in bits of the subtuple
> */
> +	uint32_t	lsb_msk;	/** < (1 << reta_sz_log) - 1 */
> +	__extension__ uint32_t	compl_table[0] __rte_cache_aligned;
> +	/** < Complementary table */
> +};
> +
> +struct rte_thash_ctx {
> +	char		name[THASH_NAME_LEN];
> +	LIST_HEAD(, rte_thash_subtuple_helper) head;
> +	uint32_t	key_len;	/** < Length of the NIC RSS hash key
> */
> +	uint32_t	reta_sz_log;	/** < size of the RSS ReTa in bits */
> +	uint32_t	subtuples_nb;	/** < number of subtuples */
> +	uint32_t	flags;
> +	uint8_t		hash_key[0];
> +};
> +
> +struct rte_thash_ctx *
> +rte_thash_init_ctx(const char *name __rte_unused,
> +	uint32_t key_len __rte_unused, uint32_t reta_sz __rte_unused,
> +	uint8_t *key __rte_unused, uint32_t flags __rte_unused) {
> +	return NULL;
> +}
> +
> +struct rte_thash_ctx *
> +rte_thash_find_existing(const char *name __rte_unused) {
> +	return NULL;
> +}
> +
> +void
> +rte_thash_free_ctx(struct rte_thash_ctx *ctx __rte_unused) { }
> +
> +int
> +rte_thash_add_helper(struct rte_thash_ctx *ctx __rte_unused,
> +	const char *name __rte_unused, uint32_t len __rte_unused,
> +	uint32_t offset __rte_unused)
> +{
> +	return 0;
> +}
> +
> +struct rte_thash_subtuple_helper *
> +rte_thash_get_helper(struct rte_thash_ctx *ctx __rte_unused,
> +	const char *name __rte_unused)
> +{
> +	return NULL;
> +}
> +
> +uint32_t
> +rte_thash_get_complement(struct rte_thash_subtuple_helper *h
> __rte_unused,
> +	uint32_t hash __rte_unused, uint32_t desired_hash __rte_unused) {
> +	return 0;
> +}
> +
> +const uint8_t *
> +rte_thash_get_key(struct rte_thash_ctx *ctx __rte_unused) {
> +	return NULL;
> +}
> +
> +int
> +rte_thash_adjust_tuple(struct rte_thash_ctx *ctx __rte_unused,
> +	struct rte_thash_subtuple_helper *h __rte_unused,
> +	uint8_t *tuple __rte_unused, unsigned int tuple_len __rte_unused,
> +	uint32_t desired_value __rte_unused,
> +	unsigned int attempts __rte_unused,
> +	rte_thash_check_tuple_t fn __rte_unused, void *userdata
> __rte_unused)
> +{
> +	return 0;
> +}
> diff --git a/lib/librte_hash/rte_thash.h b/lib/librte_hash/rte_thash.h index
> 061efa2..f3e05fc 100644
> --- a/lib/librte_hash/rte_thash.h
> +++ b/lib/librte_hash/rte_thash.h
> @@ -1,5 +1,6 @@
>  /* SPDX-License-Identifier: BSD-3-Clause
>   * Copyright(c) 2015-2019 Vladimir Medvedkin <medvedkinv@gmail.com>
> + * Copyright(c) 2021 Intel Corporation
>   */
> 
>  #ifndef _RTE_THASH_H
> @@ -222,6 +223,203 @@ rte_softrss_be(uint32_t *input_tuple, uint32_t
> input_len,
>  	return ret;
>  }
> 
> +/** @internal Minimum size of the RSS ReTa */
[Wang, Yipeng] Logarithm of minimum size.
> +#define	RTE_THASH_RETA_SZ_MIN	2U
> +/** @internal Maximum size of the RSS ReTa */
> +#define	RTE_THASH_RETA_SZ_MAX	16U
> +
> +/**
> + * LFSR will ignore if generated m-sequence has more than 2^n -1 bits
[Wang, Yipeng] Have you mentioned what is n here in the comment?
> +*/
<snip>
>  };
> --
> 2.7.4

[Wang, Yipeng] 
Acked-by: Yipeng Wang <yipeng1.wang@intel.com>

^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [dpdk-dev] [PATCH v4 2/3] hash: add predictable RSS implementation
  2021-04-13 13:19     ` [dpdk-dev] [PATCH v4 2/3] hash: add predictable RSS implementation Vladimir Medvedkin
@ 2021-04-14 17:51       ` Wang, Yipeng1
  0 siblings, 0 replies; 45+ messages in thread
From: Wang, Yipeng1 @ 2021-04-14 17:51 UTC (permalink / raw)
  To: Medvedkin, Vladimir, dev
  Cc: Ananyev, Konstantin, Chilikin, Andrey, Kinsella, Ray, Gobriel,
	Sameh, Richardson, Bruce

> -----Original Message-----
> From: Medvedkin, Vladimir <vladimir.medvedkin@intel.com>
> Sent: Tuesday, April 13, 2021 6:20 AM
> To: dev@dpdk.org
> Cc: Ananyev, Konstantin <konstantin.ananyev@intel.com>; Chilikin, Andrey
> <andrey.chilikin@intel.com>; Kinsella, Ray <ray.kinsella@intel.com>; Wang,
> Yipeng1 <yipeng1.wang@intel.com>; Gobriel, Sameh
> <sameh.gobriel@intel.com>; Richardson, Bruce
> <bruce.richardson@intel.com>
> Subject: [PATCH v4 2/3] hash: add predictable RSS implementation
> 
> This patch implements predictable RSS functionality.
> 
> Signed-off-by: Vladimir Medvedkin <vladimir.medvedkin@intel.com>
> Acked-by: Konstantin Ananyev <konstantin.ananyev@intel.com>
> ---
[Wang, Yipeng] 
Suggestion would be some log messages to help user understand the meaning of some errors (e.g. some of the nospace errors in my last comment).
It is up to you though.

Acked-by: Yipeng Wang <yipeng1.wang@intel.com>

^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [dpdk-dev] [PATCH v4 3/3] test/hash: add additional thash tests
  2021-04-13 13:19     ` [dpdk-dev] [PATCH v4 3/3] test/hash: add additional thash tests Vladimir Medvedkin
@ 2021-04-14 17:56       ` Wang, Yipeng1
  0 siblings, 0 replies; 45+ messages in thread
From: Wang, Yipeng1 @ 2021-04-14 17:56 UTC (permalink / raw)
  To: Medvedkin, Vladimir, dev
  Cc: Ananyev, Konstantin, Chilikin, Andrey, Kinsella, Ray, Gobriel,
	Sameh, Richardson, Bruce

> -----Original Message-----
> From: Medvedkin, Vladimir <vladimir.medvedkin@intel.com>
> Sent: Tuesday, April 13, 2021 6:20 AM
> To: dev@dpdk.org
> Cc: Ananyev, Konstantin <konstantin.ananyev@intel.com>; Chilikin, Andrey
> <andrey.chilikin@intel.com>; Kinsella, Ray <ray.kinsella@intel.com>; Wang,
> Yipeng1 <yipeng1.wang@intel.com>; Gobriel, Sameh
> <sameh.gobriel@intel.com>; Richardson, Bruce
> <bruce.richardson@intel.com>
> Subject: [PATCH v4 3/3] test/hash: add additional thash tests
> 
> This patch adds tests for predictable RSS feature.
> 
> Signed-off-by: Vladimir Medvedkin <vladimir.medvedkin@intel.com>
> ---
Acked-by: Yipeng Wang <yipeng1.wang@intel.com>

^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [dpdk-dev] [PATCH v4 0/3] Predictable RSS feature
  2021-04-13 13:19     ` [dpdk-dev] [PATCH v4 " Vladimir Medvedkin
@ 2021-04-14 18:04       ` Wang, Yipeng1
  2021-04-15  8:29         ` David Marchand
  2021-04-19 15:59       ` [dpdk-dev] [PATCH v5 0/5] " Vladimir Medvedkin
                         ` (5 subsequent siblings)
  6 siblings, 1 reply; 45+ messages in thread
From: Wang, Yipeng1 @ 2021-04-14 18:04 UTC (permalink / raw)
  To: Medvedkin, Vladimir, dev
  Cc: Ananyev, Konstantin, Chilikin, Andrey, Kinsella, Ray, Gobriel,
	Sameh, Richardson, Bruce

> -----Original Message-----
> From: Medvedkin, Vladimir <vladimir.medvedkin@intel.com>
> Sent: Tuesday, April 13, 2021 6:20 AM
> To: dev@dpdk.org
> Cc: Ananyev, Konstantin <konstantin.ananyev@intel.com>; Chilikin, Andrey
> <andrey.chilikin@intel.com>; Kinsella, Ray <ray.kinsella@intel.com>; Wang,
> Yipeng1 <yipeng1.wang@intel.com>; Gobriel, Sameh
> <sameh.gobriel@intel.com>; Richardson, Bruce
> <bruce.richardson@intel.com>
> Subject: [PATCH v4 0/3] Predictable RSS feature
> 
> This patch series introduces predictable RSS feature.
> It is based on the idea of searching for partial hash collisions within Toeplitz
> hash.
> 
> The Toeplitz hash function is a homomorphism between (G, ^) and (H, ^),
> where (G, ^) - is a group of tuples and (H, ^) is a group of hashes with respect
> to XOR operation. So tuples and hashes could be treated as n-dimension and
> 32-dimension vector spaces over GF(2).
> So, f(x ^ y) == f(x) ^ f(y)
> where f - is the toeplitz hash function and x, y are tuples.
> 
> The ability to predict partial collisions allows user to compute input hash value
> with desired LSB values.
> Usually number of LSB's are defined by the size of RSS Redirection Table.
> 
> There could be number of use cases, for example:
> 1) NAT. Using this library it is possible to select a new port number on a
> translation in the way that rss hash for original tuple will have the same LSB's
> as rss hash for reverse tuple.
> 2) IPSec/MPLS/Vxlan. It is possible to choose tunnel id to be pinned to a
> desired queue.
> 3) TCP stack. It is possible to choose a source port number for outgoing
> connections in the way that received replies will be assigned to desired
> queue.
> 4) RSS hash key generation. Hash key initialization with random values does
> not guarantee an uniform distribution amongst queues. This library uses
> mathematically proved algorithm to complete the rss hash key to provide the
> best distribution.
> 
> v4:
> - RETA_SZ macros were moved to .h
> - fixed typos
> 
> v3:
> - reworked rte_thash_adjust_tuple()
> - added extra comments
> - fixed typos
> - rte_thash_adjust_tuple() API was putted into the first commit
> 
> v2:
> - added extra API rte_thash_adjust_tuple()
> - added extra tests for rte_thash_adjust_tuple()
> - added extra fields to rte_thash_subtuple_helper struct
> - fixed typos
> 
> Vladimir Medvedkin (3):
>   hash: add predictable RSS API
>   hash: add predictable RSS implementation
>   test/hash: add additional thash tests
> 
>  app/test/test_thash.c       | 469 ++++++++++++++++++++++++++++++-
>  lib/librte_hash/meson.build |   3 +-
>  lib/librte_hash/rte_thash.c | 671
> ++++++++++++++++++++++++++++++++++++++++++++
>  lib/librte_hash/rte_thash.h | 198 +++++++++++++
>  lib/librte_hash/version.map |   8 +
>  5 files changed, 1342 insertions(+), 7 deletions(-)  create mode 100644
> lib/librte_hash/rte_thash.c
> 
> --
> 2.7.4
[Wang, Yipeng] 
Hi, Vladimir, thanks for the new version.
Most parts look good to me, so I acked to not block the integration.
But documentation and a good use example is still needed.
I think it can be added later after RC1.

Thanks!
Yipeng


^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [dpdk-dev] [PATCH v4 0/3] Predictable RSS feature
  2021-04-14 18:04       ` Wang, Yipeng1
@ 2021-04-15  8:29         ` David Marchand
  0 siblings, 0 replies; 45+ messages in thread
From: David Marchand @ 2021-04-15  8:29 UTC (permalink / raw)
  To: Medvedkin, Vladimir
  Cc: dev, Ananyev, Konstantin, Chilikin, Andrey, Kinsella, Ray,
	Gobriel, Sameh, Richardson, Bruce, Stephen Hemminger, Wang,
	Yipeng1

On Wed, Apr 14, 2021 at 8:05 PM Wang, Yipeng1 <yipeng1.wang@intel.com> wrote:
>
> > -----Original Message-----
> > From: Medvedkin, Vladimir <vladimir.medvedkin@intel.com>
> > Sent: Tuesday, April 13, 2021 6:20 AM
> > To: dev@dpdk.org
> > Cc: Ananyev, Konstantin <konstantin.ananyev@intel.com>; Chilikin, Andrey
> > <andrey.chilikin@intel.com>; Kinsella, Ray <ray.kinsella@intel.com>; Wang,
> > Yipeng1 <yipeng1.wang@intel.com>; Gobriel, Sameh
> > <sameh.gobriel@intel.com>; Richardson, Bruce
> > <bruce.richardson@intel.com>
> > Subject: [PATCH v4 0/3] Predictable RSS feature
> >
> > This patch series introduces predictable RSS feature.
> > It is based on the idea of searching for partial hash collisions within Toeplitz
> > hash.
> >
> > The Toeplitz hash function is a homomorphism between (G, ^) and (H, ^),
> > where (G, ^) - is a group of tuples and (H, ^) is a group of hashes with respect
> > to XOR operation. So tuples and hashes could be treated as n-dimension and
> > 32-dimension vector spaces over GF(2).
> > So, f(x ^ y) == f(x) ^ f(y)
> > where f - is the toeplitz hash function and x, y are tuples.
> >
> > The ability to predict partial collisions allows user to compute input hash value
> > with desired LSB values.
> > Usually number of LSB's are defined by the size of RSS Redirection Table.
> >
> > There could be number of use cases, for example:
> > 1) NAT. Using this library it is possible to select a new port number on a
> > translation in the way that rss hash for original tuple will have the same LSB's
> > as rss hash for reverse tuple.
> > 2) IPSec/MPLS/Vxlan. It is possible to choose tunnel id to be pinned to a
> > desired queue.
> > 3) TCP stack. It is possible to choose a source port number for outgoing
> > connections in the way that received replies will be assigned to desired
> > queue.
> > 4) RSS hash key generation. Hash key initialization with random values does
> > not guarantee an uniform distribution amongst queues. This library uses
> > mathematically proved algorithm to complete the rss hash key to provide the
> > best distribution.
> >
> > v4:
> > - RETA_SZ macros were moved to .h
> > - fixed typos
> >
> > v3:
> > - reworked rte_thash_adjust_tuple()
> > - added extra comments
> > - fixed typos
> > - rte_thash_adjust_tuple() API was putted into the first commit
> >
> > v2:
> > - added extra API rte_thash_adjust_tuple()
> > - added extra tests for rte_thash_adjust_tuple()
> > - added extra fields to rte_thash_subtuple_helper struct
> > - fixed typos
> >
> > Vladimir Medvedkin (3):
> >   hash: add predictable RSS API
> >   hash: add predictable RSS implementation
> >   test/hash: add additional thash tests
> >
> >  app/test/test_thash.c       | 469 ++++++++++++++++++++++++++++++-
> >  lib/librte_hash/meson.build |   3 +-
> >  lib/librte_hash/rte_thash.c | 671
> > ++++++++++++++++++++++++++++++++++++++++++++
> >  lib/librte_hash/rte_thash.h | 198 +++++++++++++
> >  lib/librte_hash/version.map |   8 +
> >  5 files changed, 1342 insertions(+), 7 deletions(-)  create mode 100644
> > lib/librte_hash/rte_thash.c
> >
> > --
> > 2.7.4
> [Wang, Yipeng]
> Hi, Vladimir, thanks for the new version.
> Most parts look good to me, so I acked to not block the integration.
> But documentation and a good use example is still needed.
> I think it can be added later after RC1.

This is not something new.
Documentation, release note update comes with the API.

The example had been asked by Stephen too and this is justified.

I can see some comments in patches.

I will wait for a v5 from Vladimir.
If it can be done for rc1, cool, else it will wait for the next release.


-- 
David Marchand


^ permalink raw reply	[flat|nested] 45+ messages in thread

* [dpdk-dev] [PATCH v5 0/5] Predictable RSS feature
  2021-04-13 13:19     ` [dpdk-dev] [PATCH v4 " Vladimir Medvedkin
  2021-04-14 18:04       ` Wang, Yipeng1
@ 2021-04-19 15:59       ` Vladimir Medvedkin
  2021-04-20 21:31         ` Thomas Monjalon
  2021-04-19 15:59       ` [dpdk-dev] [PATCH v5 1/5] hash: add predictable RSS API Vladimir Medvedkin
                         ` (4 subsequent siblings)
  6 siblings, 1 reply; 45+ messages in thread
From: Vladimir Medvedkin @ 2021-04-19 15:59 UTC (permalink / raw)
  To: dev
  Cc: konstantin.ananyev, andrey.chilikin, ray.kinsella, yipeng1.wang,
	sameh.gobriel, bruce.richardson

This patch series introduces predictable RSS feature.
It is based on the idea of searching for partial hash collisions
within Toeplitz hash.

The Toeplitz hash function is a homomorphism between (G, ^) and (H, ^),
where (G, ^) - is a group of tuples and (H, ^) is a group of hashes
with respect to XOR operation. So tuples and hashes could be treated as
n-dimension and 32-dimension vector spaces over GF(2).
So, f(x ^ y) == f(x) ^ f(y)
where f - is the toeplitz hash function and x, y are tuples.

The ability to predict partial collisions allows user to compute
input hash value with desired LSB values.
Usually number of LSB's are defined by the size of RSS Redirection Table.

There could be number of use cases, for example:
1) NAT. Using this library it is possible to select a new port number
on a translation in the way that rss hash for original tuple will have
the same LSB's as rss hash for reverse tuple.
2) IPSec/MPLS/Vxlan. It is possible to choose tunnel id to be pinned to
a desired queue.
3) TCP stack. It is possible to choose a source port number for outgoing
connections in the way that received replies will be assigned to
desired queue.
4) RSS hash key generation. Hash key initialization with random values
does not guarantee an uniform distribution amongst queues. This library
uses mathematically proved algorithm to complete the rss hash key to
provide the best distribution.

v5:
- added documentation
- added release notes
- added logging

v4:
- RETA_SZ macros were moved to .h
- fixed typos

v3:
- reworked rte_thash_adjust_tuple()
- added extra comments
- fixed typos
- rte_thash_adjust_tuple() API was putted into the first commit

v2:
- added extra API rte_thash_adjust_tuple()
- added extra tests for rte_thash_adjust_tuple()
- added extra fields to rte_thash_subtuple_helper struct
- fixed typos

Vladimir Medvedkin (5):
  hash: add predictable RSS API
  hash: add predictable RSS implementation
  test/hash: add additional thash tests
  doc: add thash documentation
  maintainers: claim maintainership of the hash library

 MAINTAINERS                                      |    2 +
 app/test/test_thash.c                            |  469 ++++++-
 doc/guides/prog_guide/img/predictable_snat_1.svg | 1444 +++++++++++++++++++++
 doc/guides/prog_guide/img/predictable_snat_2.svg | 1444 +++++++++++++++++++++
 doc/guides/prog_guide/img/rss_queue_assign.svg   | 1454 ++++++++++++++++++++++
 doc/guides/prog_guide/index.rst                  |    1 +
 doc/guides/prog_guide/toeplitz_hash_lib.rst      |  289 +++++
 doc/guides/rel_notes/release_21_05.rst           |    6 +
 lib/librte_hash/meson.build                      |    3 +-
 lib/librte_hash/rte_thash.c                      |  682 ++++++++++
 lib/librte_hash/rte_thash.h                      |  199 +++
 lib/librte_hash/version.map                      |    8 +
 12 files changed, 5994 insertions(+), 7 deletions(-)
 create mode 100644 doc/guides/prog_guide/img/predictable_snat_1.svg
 create mode 100644 doc/guides/prog_guide/img/predictable_snat_2.svg
 create mode 100644 doc/guides/prog_guide/img/rss_queue_assign.svg
 create mode 100644 doc/guides/prog_guide/toeplitz_hash_lib.rst
 create mode 100644 lib/librte_hash/rte_thash.c

-- 
2.7.4


^ permalink raw reply	[flat|nested] 45+ messages in thread

* [dpdk-dev] [PATCH v5 1/5] hash: add predictable RSS API
  2021-04-13 13:19     ` [dpdk-dev] [PATCH v4 " Vladimir Medvedkin
  2021-04-14 18:04       ` Wang, Yipeng1
  2021-04-19 15:59       ` [dpdk-dev] [PATCH v5 0/5] " Vladimir Medvedkin
@ 2021-04-19 15:59       ` Vladimir Medvedkin
  2021-04-19 15:59       ` [dpdk-dev] [PATCH v5 2/5] hash: add predictable RSS implementation Vladimir Medvedkin
                         ` (3 subsequent siblings)
  6 siblings, 0 replies; 45+ messages in thread
From: Vladimir Medvedkin @ 2021-04-19 15:59 UTC (permalink / raw)
  To: dev
  Cc: konstantin.ananyev, andrey.chilikin, ray.kinsella, yipeng1.wang,
	sameh.gobriel, bruce.richardson

This patch adds predictable RSS API.
It is based on the idea of searching partial Toeplitz hash collisions.

Signed-off-by: Vladimir Medvedkin <vladimir.medvedkin@intel.com>
Acked-by: Yipeng Wang <yipeng1.wang@intel.com>
---
 lib/librte_hash/meson.build |   3 +-
 lib/librte_hash/rte_thash.c | 109 ++++++++++++++++++++++++
 lib/librte_hash/rte_thash.h | 199 ++++++++++++++++++++++++++++++++++++++++++++
 lib/librte_hash/version.map |   8 ++
 4 files changed, 318 insertions(+), 1 deletion(-)
 create mode 100644 lib/librte_hash/rte_thash.c

diff --git a/lib/librte_hash/meson.build b/lib/librte_hash/meson.build
index 242859f..3546014 100644
--- a/lib/librte_hash/meson.build
+++ b/lib/librte_hash/meson.build
@@ -8,6 +8,7 @@ headers = files('rte_fbk_hash.h',
 	'rte_thash.h')
 indirect_headers += files('rte_crc_arm64.h')
 
-sources = files('rte_cuckoo_hash.c', 'rte_fbk_hash.c')
+sources = files('rte_cuckoo_hash.c', 'rte_fbk_hash.c', 'rte_thash.c')
+deps += ['net']
 deps += ['ring']
 deps += ['rcu']
diff --git a/lib/librte_hash/rte_thash.c b/lib/librte_hash/rte_thash.c
new file mode 100644
index 0000000..1325678
--- /dev/null
+++ b/lib/librte_hash/rte_thash.c
@@ -0,0 +1,109 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2021 Intel Corporation
+ */
+
+#include <rte_thash.h>
+#include <rte_tailq.h>
+#include <rte_random.h>
+#include <rte_memcpy.h>
+#include <rte_errno.h>
+#include <rte_eal.h>
+#include <rte_eal_memconfig.h>
+#include <rte_malloc.h>
+
+#define THASH_NAME_LEN		64
+
+struct thash_lfsr {
+	uint32_t	ref_cnt;
+	uint32_t	poly;
+	/**< polynomial associated with the lfsr */
+	uint32_t	rev_poly;
+	/**< polynomial to generate the sequence in reverse direction */
+	uint32_t	state;
+	/**< current state of the lfsr */
+	uint32_t	rev_state;
+	/**< current state of the lfsr for reverse direction */
+	uint32_t	deg;	/**< polynomial degree*/
+	uint32_t	bits_cnt;  /**< number of bits generated by lfsr*/
+};
+
+struct rte_thash_subtuple_helper {
+	char	name[THASH_NAME_LEN];	/** < Name of subtuple configuration */
+	LIST_ENTRY(rte_thash_subtuple_helper)	next;
+	struct thash_lfsr	*lfsr;
+	uint32_t	offset;		/** < Offset of the m-sequence */
+	uint32_t	len;		/** < Length of the m-sequence */
+	uint32_t	tuple_offset;	/** < Offset in bits of the subtuple */
+	uint32_t	tuple_len;	/** < Length in bits of the subtuple */
+	uint32_t	lsb_msk;	/** < (1 << reta_sz_log) - 1 */
+	__extension__ uint32_t	compl_table[0] __rte_cache_aligned;
+	/** < Complementary table */
+};
+
+struct rte_thash_ctx {
+	char		name[THASH_NAME_LEN];
+	LIST_HEAD(, rte_thash_subtuple_helper) head;
+	uint32_t	key_len;	/** < Length of the NIC RSS hash key */
+	uint32_t	reta_sz_log;	/** < size of the RSS ReTa in bits */
+	uint32_t	subtuples_nb;	/** < number of subtuples */
+	uint32_t	flags;
+	uint8_t		hash_key[0];
+};
+
+struct rte_thash_ctx *
+rte_thash_init_ctx(const char *name __rte_unused,
+	uint32_t key_len __rte_unused, uint32_t reta_sz __rte_unused,
+	uint8_t *key __rte_unused, uint32_t flags __rte_unused)
+{
+	return NULL;
+}
+
+struct rte_thash_ctx *
+rte_thash_find_existing(const char *name __rte_unused)
+{
+	return NULL;
+}
+
+void
+rte_thash_free_ctx(struct rte_thash_ctx *ctx __rte_unused)
+{
+}
+
+int
+rte_thash_add_helper(struct rte_thash_ctx *ctx __rte_unused,
+	const char *name __rte_unused, uint32_t len __rte_unused,
+	uint32_t offset __rte_unused)
+{
+	return 0;
+}
+
+struct rte_thash_subtuple_helper *
+rte_thash_get_helper(struct rte_thash_ctx *ctx __rte_unused,
+	const char *name __rte_unused)
+{
+	return NULL;
+}
+
+uint32_t
+rte_thash_get_complement(struct rte_thash_subtuple_helper *h __rte_unused,
+	uint32_t hash __rte_unused, uint32_t desired_hash __rte_unused)
+{
+	return 0;
+}
+
+const uint8_t *
+rte_thash_get_key(struct rte_thash_ctx *ctx __rte_unused)
+{
+	return NULL;
+}
+
+int
+rte_thash_adjust_tuple(struct rte_thash_ctx *ctx __rte_unused,
+	struct rte_thash_subtuple_helper *h __rte_unused,
+	uint8_t *tuple __rte_unused, unsigned int tuple_len __rte_unused,
+	uint32_t desired_value __rte_unused,
+	unsigned int attempts __rte_unused,
+	rte_thash_check_tuple_t fn __rte_unused, void *userdata __rte_unused)
+{
+	return 0;
+}
diff --git a/lib/librte_hash/rte_thash.h b/lib/librte_hash/rte_thash.h
index 061efa2..659a387 100644
--- a/lib/librte_hash/rte_thash.h
+++ b/lib/librte_hash/rte_thash.h
@@ -1,5 +1,6 @@
 /* SPDX-License-Identifier: BSD-3-Clause
  * Copyright(c) 2015-2019 Vladimir Medvedkin <medvedkinv@gmail.com>
+ * Copyright(c) 2021 Intel Corporation
  */
 
 #ifndef _RTE_THASH_H
@@ -222,6 +223,204 @@ rte_softrss_be(uint32_t *input_tuple, uint32_t input_len,
 	return ret;
 }
 
+/** @internal Logarithm of minimum size of the RSS ReTa */
+#define	RTE_THASH_RETA_SZ_MIN	2U
+/** @internal Logarithm of maximum size of the RSS ReTa */
+#define	RTE_THASH_RETA_SZ_MAX	16U
+
+/**
+ * LFSR will ignore if generated m-sequence has more than 2^n -1 bits,
+ * where n is the logarithm of the RSS ReTa size.
+ */
+#define RTE_THASH_IGNORE_PERIOD_OVERFLOW	0x1
+/**
+ * Generate minimal required bit (equal to ReTa LSB) sequence into
+ * the hash_key
+ */
+#define RTE_THASH_MINIMAL_SEQ			0x2
+
+/** @internal thash context structure. */
+struct rte_thash_ctx;
+/** @internal thash helper structure. */
+struct rte_thash_subtuple_helper;
+
+/**
+ * Create a new thash context.
+ *
+ * @param name
+ *  Context name
+ * @param key_len
+ *  Length of the toeplitz hash key
+ * @param reta_sz
+ *  Logarithm of the NIC's Redirection Table (ReTa) size,
+ *  i.e. number of the LSBs if the hash used to determine
+ *  the reta entry.
+ * @param key
+ *  Pointer to the key used to init an internal key state.
+ *  Could be NULL, in this case internal key will be inited with random.
+ * @param flags
+ *  Supported flags are:
+ *   RTE_THASH_IGNORE_PERIOD_OVERFLOW
+ *   RTE_THASH_MINIMAL_SEQ
+ * @return
+ *  A pointer to the created context on success
+ *  NULL otherwise
+ */
+__rte_experimental
+struct rte_thash_ctx *
+rte_thash_init_ctx(const char *name, uint32_t key_len, uint32_t reta_sz,
+	uint8_t *key, uint32_t flags);
+
+/**
+ * Find an existing thash context and return a pointer to it.
+ *
+ * @param name
+ *  Name of the thash context
+ * @return
+ *  Pointer to the thash context or NULL if it was not found with rte_errno
+ *  set appropriately. Possible rte_errno values include:
+ *   - ENOENT - required entry not available to return.
+ */
+__rte_experimental
+struct rte_thash_ctx *
+rte_thash_find_existing(const char *name);
+
+/**
+ * Free a thash context object
+ *
+ * @param ctx
+ *  Thash context
+ * @return
+ *  None
+ */
+__rte_experimental
+void
+rte_thash_free_ctx(struct rte_thash_ctx *ctx);
+
+/**
+ * Add a special properties to the toeplitz hash key inside a thash context.
+ * Creates an internal helper struct which has a complementary table
+ * to calculate toeplitz hash collisions.
+ * This function is not multi-thread safe.
+ *
+ * @param ctx
+ *  Thash context
+ * @param name
+ *  Name of the helper
+ * @param len
+ *  Length in bits of the target subtuple
+ *  Must be no shorter than reta_sz passed on rte_thash_init_ctx().
+ * @param offset
+ *  Offset in bits of the subtuple
+ * @return
+ *  0 on success
+ *  negative on error
+ */
+__rte_experimental
+int
+rte_thash_add_helper(struct rte_thash_ctx *ctx, const char *name, uint32_t len,
+	uint32_t offset);
+
+/**
+ * Find a helper in the context by the given name
+ *
+ * @param ctx
+ *  Thash context
+ * @param name
+ *  Name of the helper
+ * @return
+ *  Pointer to the thash helper or NULL if it was not found.
+ */
+__rte_experimental
+struct rte_thash_subtuple_helper *
+rte_thash_get_helper(struct rte_thash_ctx *ctx, const char *name);
+
+/**
+ * Get a complementary value for the subtuple to produce a
+ * partial toeplitz hash collision. It must be XOR'ed with the
+ * subtuple to produce the hash value with the desired hash LSB's
+ * This function is multi-thread safe.
+ *
+ * @param h
+ *  Pointer to the helper struct
+ * @param hash
+ *  Toeplitz hash value calculated for the given tuple
+ * @param desired_hash
+ *  Desired hash value to find a collision for
+ * @return
+ *  A complementary value which must be xored with the corresponding subtuple
+ */
+__rte_experimental
+uint32_t
+rte_thash_get_complement(struct rte_thash_subtuple_helper *h,
+	uint32_t hash, uint32_t desired_hash);
+
+/**
+ * Get a pointer to the toeplitz hash contained in the context.
+ * It changes after each addition of a helper. It should be installed to
+ * the NIC.
+ *
+ * @param ctx
+ *  Thash context
+ * @return
+ *  A pointer to the toeplitz hash key
+ */
+__rte_experimental
+const uint8_t *
+rte_thash_get_key(struct rte_thash_ctx *ctx);
+
+/**
+ * Function prototype for the rte_thash_adjust_tuple
+ * to check if adjusted tuple could be used.
+ * Generally it is some kind of lookup function to check
+ * if adjusted tuple is already in use.
+ *
+ * @param userdata
+ *  Pointer to the userdata. It could be a pointer to the
+ *  table with used tuples to search.
+ * @param tuple
+ *  Pointer to the tuple to check
+ *
+ * @return
+ *  1 on success
+ *  0 otherwise
+ */
+typedef int (*rte_thash_check_tuple_t)(void *userdata, uint8_t *tuple);
+
+/**
+ * Adjusts tuple in the way to make Toeplitz hash has
+ * desired least significant bits.
+ * This function is multi-thread safe.
+ *
+ * @param ctx
+ *  Thash context
+ * @param h
+ *  Pointer to the helper struct
+ * @param tuple
+ *  Pointer to the tuple to be adjusted
+ * @param tuple_len
+ *  Length of the tuple. Must be multiple of 4.
+ * @param desired_value
+ *  Desired value of least significant bits of the hash
+ * @param attempts
+ *  Number of attempts to adjust tuple with fn() calling
+ * @param fn
+ *  Callback function to check adjusted tuple. Could be NULL
+ * @param userdata
+ *  Pointer to the userdata to be passed to fn(). Could be NULL
+ *
+ * @return
+ *  0 on success
+ *  negative otherwise
+ */
+__rte_experimental
+int
+rte_thash_adjust_tuple(struct rte_thash_ctx *ctx,
+	struct rte_thash_subtuple_helper *h,
+	uint8_t *tuple, unsigned int tuple_len,
+	uint32_t desired_value, unsigned int attempts,
+	rte_thash_check_tuple_t fn, void *userdata);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/lib/librte_hash/version.map b/lib/librte_hash/version.map
index c6d7308..17cb8aa 100644
--- a/lib/librte_hash/version.map
+++ b/lib/librte_hash/version.map
@@ -32,9 +32,17 @@ DPDK_21 {
 EXPERIMENTAL {
 	global:
 
+	rte_thash_adjust_tuple;
 	rte_hash_free_key_with_position;
 	rte_hash_lookup_with_hash_bulk;
 	rte_hash_lookup_with_hash_bulk_data;
 	rte_hash_max_key_id;
 	rte_hash_rcu_qsbr_add;
+	rte_thash_add_helper;
+	rte_thash_find_existing;
+	rte_thash_free_ctx;
+	rte_thash_get_complement;
+	rte_thash_get_helper;
+	rte_thash_get_key;
+	rte_thash_init_ctx;
 };
-- 
2.7.4


^ permalink raw reply	[flat|nested] 45+ messages in thread

* [dpdk-dev] [PATCH v5 2/5] hash: add predictable RSS implementation
  2021-04-13 13:19     ` [dpdk-dev] [PATCH v4 " Vladimir Medvedkin
                         ` (2 preceding siblings ...)
  2021-04-19 15:59       ` [dpdk-dev] [PATCH v5 1/5] hash: add predictable RSS API Vladimir Medvedkin
@ 2021-04-19 15:59       ` Vladimir Medvedkin
  2021-04-19 15:59       ` [dpdk-dev] [PATCH v5 3/5] test/hash: add additional thash tests Vladimir Medvedkin
                         ` (2 subsequent siblings)
  6 siblings, 0 replies; 45+ messages in thread
From: Vladimir Medvedkin @ 2021-04-19 15:59 UTC (permalink / raw)
  To: dev
  Cc: konstantin.ananyev, andrey.chilikin, ray.kinsella, yipeng1.wang,
	sameh.gobriel, bruce.richardson

This patch implements predictable RSS functionality.

Signed-off-by: Vladimir Medvedkin <vladimir.medvedkin@intel.com>
Acked-by: Konstantin Ananyev <konstantin.ananyev@intel.com>
Acked-by: Yipeng Wang <yipeng1.wang@intel.com>
---
 lib/librte_hash/rte_thash.c | 619 ++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 596 insertions(+), 23 deletions(-)

diff --git a/lib/librte_hash/rte_thash.c b/lib/librte_hash/rte_thash.c
index 1325678..135a26d 100644
--- a/lib/librte_hash/rte_thash.c
+++ b/lib/librte_hash/rte_thash.c
@@ -9,9 +9,47 @@
 #include <rte_errno.h>
 #include <rte_eal.h>
 #include <rte_eal_memconfig.h>
+#include <rte_log.h>
 #include <rte_malloc.h>
 
 #define THASH_NAME_LEN		64
+#define TOEPLITZ_HASH_LEN	32
+
+#define RETA_SZ_IN_RANGE(reta_sz)	((reta_sz >= RTE_THASH_RETA_SZ_MIN) &&\
+					(reta_sz <= RTE_THASH_RETA_SZ_MAX))
+
+TAILQ_HEAD(rte_thash_list, rte_tailq_entry);
+static struct rte_tailq_elem rte_thash_tailq = {
+	.name = "RTE_THASH",
+};
+EAL_REGISTER_TAILQ(rte_thash_tailq)
+
+/**
+ * Table of some irreducible polinomials over GF(2).
+ * For lfsr they are reperesented in BE bit order, and
+ * x^0 is masked out.
+ * For example, poly x^5 + x^2 + 1 will be represented
+ * as (101001b & 11111b) = 01001b = 0x9
+ */
+static const uint32_t irreducible_poly_table[][4] = {
+	{0, 0, 0, 0},	/** < degree 0 */
+	{1, 1, 1, 1},	/** < degree 1 */
+	{0x3, 0x3, 0x3, 0x3},	/** < degree 2 and so on... */
+	{0x5, 0x3, 0x5, 0x3},
+	{0x9, 0x3, 0x9, 0x3},
+	{0x9, 0x1b, 0xf, 0x5},
+	{0x21, 0x33, 0x1b, 0x2d},
+	{0x41, 0x11, 0x71, 0x9},
+	{0x71, 0xa9, 0xf5, 0x8d},
+	{0x21, 0xd1, 0x69, 0x1d9},
+	{0x81, 0x2c1, 0x3b1, 0x185},
+	{0x201, 0x541, 0x341, 0x461},
+	{0x941, 0x609, 0xe19, 0x45d},
+	{0x1601, 0x1f51, 0x1171, 0x359},
+	{0x2141, 0x2111, 0x2db1, 0x2109},
+	{0x4001, 0x801, 0x101, 0x7301},
+	{0x7781, 0xa011, 0x4211, 0x86d9},
+};
 
 struct thash_lfsr {
 	uint32_t	ref_cnt;
@@ -50,60 +88,595 @@ struct rte_thash_ctx {
 	uint8_t		hash_key[0];
 };
 
+static inline uint32_t
+get_bit_lfsr(struct thash_lfsr *lfsr)
+{
+	uint32_t bit, ret;
+
+	/*
+	 * masking the TAP bits defined by the polynomial and
+	 * calculating parity
+	 */
+	bit = __builtin_popcount(lfsr->state & lfsr->poly) & 0x1;
+	ret = lfsr->state & 0x1;
+	lfsr->state = ((lfsr->state >> 1) | (bit << (lfsr->deg - 1))) &
+		((1 << lfsr->deg) - 1);
+
+	lfsr->bits_cnt++;
+	return ret;
+}
+
+static inline uint32_t
+get_rev_bit_lfsr(struct thash_lfsr *lfsr)
+{
+	uint32_t bit, ret;
+
+	bit = __builtin_popcount(lfsr->rev_state & lfsr->rev_poly) & 0x1;
+	ret = lfsr->rev_state & (1 << (lfsr->deg - 1));
+	lfsr->rev_state = ((lfsr->rev_state << 1) | bit) &
+		((1 << lfsr->deg) - 1);
+
+	lfsr->bits_cnt++;
+	return ret;
+}
+
+static inline uint32_t
+thash_get_rand_poly(uint32_t poly_degree)
+{
+	return irreducible_poly_table[poly_degree][rte_rand() %
+		RTE_DIM(irreducible_poly_table[poly_degree])];
+}
+
+static struct thash_lfsr *
+alloc_lfsr(struct rte_thash_ctx *ctx)
+{
+	struct thash_lfsr *lfsr;
+	uint32_t i;
+
+	if (ctx == NULL)
+		return NULL;
+
+	lfsr = rte_zmalloc(NULL, sizeof(struct thash_lfsr), 0);
+	if (lfsr == NULL)
+		return NULL;
+
+	lfsr->deg = ctx->reta_sz_log;
+	lfsr->poly = thash_get_rand_poly(lfsr->deg);
+	do {
+		lfsr->state = rte_rand() & ((1 << lfsr->deg) - 1);
+	} while (lfsr->state == 0);
+	/* init reverse order polynomial */
+	lfsr->rev_poly = (lfsr->poly >> 1) | (1 << (lfsr->deg - 1));
+	/* init proper rev_state*/
+	lfsr->rev_state = lfsr->state;
+	for (i = 0; i <= lfsr->deg; i++)
+		get_rev_bit_lfsr(lfsr);
+
+	/* clear bits_cnt after rev_state was inited */
+	lfsr->bits_cnt = 0;
+	lfsr->ref_cnt = 1;
+
+	return lfsr;
+}
+
+static void
+attach_lfsr(struct rte_thash_subtuple_helper *h, struct thash_lfsr *lfsr)
+{
+	lfsr->ref_cnt++;
+	h->lfsr = lfsr;
+}
+
+static void
+free_lfsr(struct thash_lfsr *lfsr)
+{
+	lfsr->ref_cnt--;
+	if (lfsr->ref_cnt == 0)
+		rte_free(lfsr);
+}
+
 struct rte_thash_ctx *
-rte_thash_init_ctx(const char *name __rte_unused,
-	uint32_t key_len __rte_unused, uint32_t reta_sz __rte_unused,
-	uint8_t *key __rte_unused, uint32_t flags __rte_unused)
+rte_thash_init_ctx(const char *name, uint32_t key_len, uint32_t reta_sz,
+	uint8_t *key, uint32_t flags)
 {
+	struct rte_thash_ctx *ctx;
+	struct rte_tailq_entry *te;
+	struct rte_thash_list *thash_list;
+	uint32_t i;
+
+	if ((name == NULL) || (key_len == 0) || !RETA_SZ_IN_RANGE(reta_sz)) {
+		rte_errno = EINVAL;
+		return NULL;
+	}
+
+	thash_list = RTE_TAILQ_CAST(rte_thash_tailq.head, rte_thash_list);
+
+	rte_mcfg_tailq_write_lock();
+
+	/* guarantee there's no existing */
+	TAILQ_FOREACH(te, thash_list, next) {
+		ctx = (struct rte_thash_ctx *)te->data;
+		if (strncmp(name, ctx->name, sizeof(ctx->name)) == 0)
+			break;
+	}
+	ctx = NULL;
+	if (te != NULL) {
+		rte_errno = EEXIST;
+		goto exit;
+	}
+
+	/* allocate tailq entry */
+	te = rte_zmalloc("THASH_TAILQ_ENTRY", sizeof(*te), 0);
+	if (te == NULL) {
+		RTE_LOG(ERR, HASH,
+			"Can not allocate tailq entry for thash context %s\n",
+			name);
+		rte_errno = ENOMEM;
+		goto exit;
+	}
+
+	ctx = rte_zmalloc(NULL, sizeof(struct rte_thash_ctx) + key_len, 0);
+	if (ctx == NULL) {
+		RTE_LOG(ERR, HASH, "thash ctx %s memory allocation failed\n",
+			name);
+		rte_errno = ENOMEM;
+		goto free_te;
+	}
+
+	rte_strlcpy(ctx->name, name, sizeof(ctx->name));
+	ctx->key_len = key_len;
+	ctx->reta_sz_log = reta_sz;
+	LIST_INIT(&ctx->head);
+	ctx->flags = flags;
+
+	if (key)
+		rte_memcpy(ctx->hash_key, key, key_len);
+	else {
+		for (i = 0; i < key_len; i++)
+			ctx->hash_key[i] = rte_rand();
+	}
+
+	te->data = (void *)ctx;
+	TAILQ_INSERT_TAIL(thash_list, te, next);
+
+	rte_mcfg_tailq_write_unlock();
+
+	return ctx;
+free_te:
+	rte_free(te);
+exit:
+	rte_mcfg_tailq_write_unlock();
 	return NULL;
 }
 
 struct rte_thash_ctx *
-rte_thash_find_existing(const char *name __rte_unused)
+rte_thash_find_existing(const char *name)
 {
-	return NULL;
+	struct rte_thash_ctx *ctx;
+	struct rte_tailq_entry *te;
+	struct rte_thash_list *thash_list;
+
+	thash_list = RTE_TAILQ_CAST(rte_thash_tailq.head, rte_thash_list);
+
+	rte_mcfg_tailq_read_lock();
+	TAILQ_FOREACH(te, thash_list, next) {
+		ctx = (struct rte_thash_ctx *)te->data;
+		if (strncmp(name, ctx->name, sizeof(ctx->name)) == 0)
+			break;
+	}
+
+	rte_mcfg_tailq_read_unlock();
+
+	if (te == NULL) {
+		rte_errno = ENOENT;
+		return NULL;
+	}
+
+	return ctx;
 }
 
 void
-rte_thash_free_ctx(struct rte_thash_ctx *ctx __rte_unused)
+rte_thash_free_ctx(struct rte_thash_ctx *ctx)
 {
+	struct rte_tailq_entry *te;
+	struct rte_thash_list *thash_list;
+	struct rte_thash_subtuple_helper *ent, *tmp;
+
+	if (ctx == NULL)
+		return;
+
+	thash_list = RTE_TAILQ_CAST(rte_thash_tailq.head, rte_thash_list);
+	rte_mcfg_tailq_write_lock();
+	TAILQ_FOREACH(te, thash_list, next) {
+		if (te->data == (void *)ctx)
+			break;
+	}
+
+	if (te != NULL)
+		TAILQ_REMOVE(thash_list, te, next);
+
+	rte_mcfg_tailq_write_unlock();
+	ent = LIST_FIRST(&(ctx->head));
+	while (ent) {
+		free_lfsr(ent->lfsr);
+		tmp = ent;
+		ent = LIST_NEXT(ent, next);
+		LIST_REMOVE(tmp, next);
+		rte_free(tmp);
+	}
+
+	rte_free(ctx);
+	rte_free(te);
+}
+
+static inline void
+set_bit(uint8_t *ptr, uint32_t bit, uint32_t pos)
+{
+	uint32_t byte_idx = pos / CHAR_BIT;
+	/* index of the bit int byte, indexing starts from MSB */
+	uint32_t bit_idx = (CHAR_BIT - 1) - (pos & (CHAR_BIT - 1));
+	uint8_t tmp;
+
+	tmp = ptr[byte_idx];
+	tmp &= ~(1 << bit_idx);
+	tmp |= bit << bit_idx;
+	ptr[byte_idx] = tmp;
+}
+
+/**
+ * writes m-sequence to the hash_key for range [start, end]
+ * (i.e. including start and end positions)
+ */
+static int
+generate_subkey(struct rte_thash_ctx *ctx, struct thash_lfsr *lfsr,
+	uint32_t start, uint32_t end)
+{
+	uint32_t i;
+	uint32_t req_bits = (start < end) ? (end - start) : (start - end);
+	req_bits++; /* due to including end */
+
+	/* check if lfsr overflow period of the m-sequence */
+	if (((lfsr->bits_cnt + req_bits) > (1ULL << lfsr->deg) - 1) &&
+			((ctx->flags & RTE_THASH_IGNORE_PERIOD_OVERFLOW) !=
+			RTE_THASH_IGNORE_PERIOD_OVERFLOW)) {
+		RTE_LOG(ERR, HASH,
+			"Can't generate m-sequence due to period overflow\n");
+		return -ENOSPC;
+	}
+
+	if (start < end) {
+		/* original direction (from left to right)*/
+		for (i = start; i <= end; i++)
+			set_bit(ctx->hash_key, get_bit_lfsr(lfsr), i);
+
+	} else {
+		/* reverse direction (from right to left) */
+		for (i = end; i >= start; i--)
+			set_bit(ctx->hash_key, get_rev_bit_lfsr(lfsr), i);
+	}
+
+	return 0;
+}
+
+static inline uint32_t
+get_subvalue(struct rte_thash_ctx *ctx, uint32_t offset)
+{
+	uint32_t *tmp, val;
+
+	tmp = (uint32_t *)(&ctx->hash_key[offset >> 3]);
+	val = rte_be_to_cpu_32(*tmp);
+	val >>= (TOEPLITZ_HASH_LEN - ((offset & (CHAR_BIT - 1)) +
+		ctx->reta_sz_log));
+
+	return val & ((1 << ctx->reta_sz_log) - 1);
+}
+
+static inline void
+generate_complement_table(struct rte_thash_ctx *ctx,
+	struct rte_thash_subtuple_helper *h)
+{
+	int i, j, k;
+	uint32_t val;
+	uint32_t start;
+
+	start = h->offset + h->len - (2 * ctx->reta_sz_log - 1);
+
+	for (i = 1; i < (1 << ctx->reta_sz_log); i++) {
+		val = 0;
+		for (j = i; j; j &= (j - 1)) {
+			k = rte_bsf32(j);
+			val ^= get_subvalue(ctx, start - k +
+				ctx->reta_sz_log - 1);
+		}
+		h->compl_table[val] = i;
+	}
+}
+
+static inline int
+insert_before(struct rte_thash_ctx *ctx,
+	struct rte_thash_subtuple_helper *ent,
+	struct rte_thash_subtuple_helper *cur_ent,
+	struct rte_thash_subtuple_helper *next_ent,
+	uint32_t start, uint32_t end, uint32_t range_end)
+{
+	int ret;
+
+	if (end < cur_ent->offset) {
+		ent->lfsr = alloc_lfsr(ctx);
+		if (ent->lfsr == NULL) {
+			rte_free(ent);
+			return -ENOMEM;
+		}
+		/* generate nonoverlapping range [start, end) */
+		ret = generate_subkey(ctx, ent->lfsr, start, end - 1);
+		if (ret != 0) {
+			free_lfsr(ent->lfsr);
+			rte_free(ent);
+			return ret;
+		}
+	} else if ((next_ent != NULL) && (end > next_ent->offset)) {
+		rte_free(ent);
+		RTE_LOG(ERR, HASH,
+			"Can't add helper %s due to conflict with existing"
+			" helper %s\n", ent->name, next_ent->name);
+		return -ENOSPC;
+	}
+	attach_lfsr(ent, cur_ent->lfsr);
+
+	/**
+	 * generate partially overlapping range
+	 * [start, cur_ent->start) in reverse order
+	 */
+	ret = generate_subkey(ctx, ent->lfsr, cur_ent->offset - 1, start);
+	if (ret != 0) {
+		free_lfsr(ent->lfsr);
+		rte_free(ent);
+		return ret;
+	}
+
+	if (end > range_end) {
+		/**
+		 * generate partially overlapping range
+		 * (range_end, end)
+		 */
+		ret = generate_subkey(ctx, ent->lfsr, range_end, end - 1);
+		if (ret != 0) {
+			free_lfsr(ent->lfsr);
+			rte_free(ent);
+			return ret;
+		}
+	}
+
+	LIST_INSERT_BEFORE(cur_ent, ent, next);
+	generate_complement_table(ctx, ent);
+	ctx->subtuples_nb++;
+	return 0;
+}
+
+static inline int
+insert_after(struct rte_thash_ctx *ctx,
+	struct rte_thash_subtuple_helper *ent,
+	struct rte_thash_subtuple_helper *cur_ent,
+	struct rte_thash_subtuple_helper *next_ent,
+	struct rte_thash_subtuple_helper *prev_ent,
+	uint32_t end, uint32_t range_end)
+{
+	int ret;
+
+	if ((next_ent != NULL) && (end > next_ent->offset)) {
+		rte_free(ent);
+		RTE_LOG(ERR, HASH,
+			"Can't add helper %s due to conflict with existing"
+			" helper %s\n", ent->name, next_ent->name);
+		return -EEXIST;
+	}
+
+	attach_lfsr(ent, cur_ent->lfsr);
+	if (end > range_end) {
+		/**
+		 * generate partially overlapping range
+		 * (range_end, end)
+		 */
+		ret = generate_subkey(ctx, ent->lfsr, range_end, end - 1);
+		if (ret != 0) {
+			free_lfsr(ent->lfsr);
+			rte_free(ent);
+			return ret;
+		}
+	}
+
+	LIST_INSERT_AFTER(prev_ent, ent, next);
+	generate_complement_table(ctx, ent);
+	ctx->subtuples_nb++;
+
+	return 0;
 }
 
 int
-rte_thash_add_helper(struct rte_thash_ctx *ctx __rte_unused,
-	const char *name __rte_unused, uint32_t len __rte_unused,
-	uint32_t offset __rte_unused)
+rte_thash_add_helper(struct rte_thash_ctx *ctx, const char *name, uint32_t len,
+	uint32_t offset)
 {
+	struct rte_thash_subtuple_helper *ent, *cur_ent, *prev_ent, *next_ent;
+	uint32_t start, end;
+	int ret;
+
+	if ((ctx == NULL) || (name == NULL) || (len < ctx->reta_sz_log) ||
+			((offset + len + TOEPLITZ_HASH_LEN - 1) >
+			ctx->key_len * CHAR_BIT))
+		return -EINVAL;
+
+	/* Check for existing name*/
+	LIST_FOREACH(cur_ent, &ctx->head, next) {
+		if (strncmp(name, cur_ent->name, sizeof(cur_ent->name)) == 0)
+			return -EEXIST;
+	}
+
+	end = offset + len + TOEPLITZ_HASH_LEN - 1;
+	start = ((ctx->flags & RTE_THASH_MINIMAL_SEQ) ==
+		RTE_THASH_MINIMAL_SEQ) ? (end - (2 * ctx->reta_sz_log - 1)) :
+		offset;
+
+	ent = rte_zmalloc(NULL, sizeof(struct rte_thash_subtuple_helper) +
+		sizeof(uint32_t) * (1 << ctx->reta_sz_log),
+		RTE_CACHE_LINE_SIZE);
+	if (ent == NULL)
+		return -ENOMEM;
+
+	rte_strlcpy(ent->name, name, sizeof(ent->name));
+	ent->offset = start;
+	ent->len = end - start;
+	ent->tuple_offset = offset;
+	ent->tuple_len = len;
+	ent->lsb_msk = (1 << ctx->reta_sz_log) - 1;
+
+	cur_ent = LIST_FIRST(&ctx->head);
+	while (cur_ent) {
+		uint32_t range_end = cur_ent->offset + cur_ent->len;
+		next_ent = LIST_NEXT(cur_ent, next);
+		prev_ent = cur_ent;
+		/* Iterate through overlapping ranges */
+		while ((next_ent != NULL) && (next_ent->offset < range_end)) {
+			range_end = RTE_MAX(next_ent->offset + next_ent->len,
+				range_end);
+			if (start > next_ent->offset)
+				prev_ent = next_ent;
+
+			next_ent = LIST_NEXT(next_ent, next);
+		}
+
+		if (start < cur_ent->offset)
+			return insert_before(ctx, ent, cur_ent, next_ent,
+				start, end, range_end);
+		else if (start < range_end)
+			return insert_after(ctx, ent, cur_ent, next_ent,
+				prev_ent, end, range_end);
+
+		cur_ent = next_ent;
+		continue;
+	}
+
+	ent->lfsr = alloc_lfsr(ctx);
+	if (ent->lfsr == NULL) {
+		rte_free(ent);
+		return -ENOMEM;
+	}
+
+	/* generate nonoverlapping range [start, end) */
+	ret = generate_subkey(ctx, ent->lfsr, start, end - 1);
+	if (ret != 0) {
+		free_lfsr(ent->lfsr);
+		rte_free(ent);
+		return ret;
+	}
+	if (LIST_EMPTY(&ctx->head)) {
+		LIST_INSERT_HEAD(&ctx->head, ent, next);
+	} else {
+		LIST_FOREACH(next_ent, &ctx->head, next)
+			prev_ent = next_ent;
+
+		LIST_INSERT_AFTER(prev_ent, ent, next);
+	}
+	generate_complement_table(ctx, ent);
+	ctx->subtuples_nb++;
+
 	return 0;
 }
 
 struct rte_thash_subtuple_helper *
-rte_thash_get_helper(struct rte_thash_ctx *ctx __rte_unused,
-	const char *name __rte_unused)
+rte_thash_get_helper(struct rte_thash_ctx *ctx, const char *name)
 {
+	struct rte_thash_subtuple_helper *ent;
+
+	if ((ctx == NULL) || (name == NULL))
+		return NULL;
+
+	LIST_FOREACH(ent, &ctx->head, next) {
+		if (strncmp(name, ent->name, sizeof(ent->name)) == 0)
+			return ent;
+	}
+
 	return NULL;
 }
 
 uint32_t
-rte_thash_get_complement(struct rte_thash_subtuple_helper *h __rte_unused,
-	uint32_t hash __rte_unused, uint32_t desired_hash __rte_unused)
+rte_thash_get_complement(struct rte_thash_subtuple_helper *h,
+	uint32_t hash, uint32_t desired_hash)
 {
-	return 0;
+	return h->compl_table[(hash ^ desired_hash) & h->lsb_msk];
 }
 
 const uint8_t *
-rte_thash_get_key(struct rte_thash_ctx *ctx __rte_unused)
+rte_thash_get_key(struct rte_thash_ctx *ctx)
 {
-	return NULL;
+	return ctx->hash_key;
+}
+
+static inline void
+xor_bit(uint8_t *ptr, uint32_t bit, uint32_t pos)
+{
+	uint32_t byte_idx = pos >> 3;
+	uint32_t bit_idx = (CHAR_BIT - 1) - (pos & (CHAR_BIT - 1));
+	uint8_t tmp;
+
+	tmp = ptr[byte_idx];
+	tmp ^= bit << bit_idx;
+	ptr[byte_idx] = tmp;
 }
 
 int
-rte_thash_adjust_tuple(struct rte_thash_ctx *ctx __rte_unused,
-	struct rte_thash_subtuple_helper *h __rte_unused,
-	uint8_t *tuple __rte_unused, unsigned int tuple_len __rte_unused,
-	uint32_t desired_value __rte_unused,
-	unsigned int attempts __rte_unused,
-	rte_thash_check_tuple_t fn __rte_unused, void *userdata __rte_unused)
+rte_thash_adjust_tuple(struct rte_thash_ctx *ctx,
+	struct rte_thash_subtuple_helper *h,
+	uint8_t *tuple, unsigned int tuple_len,
+	uint32_t desired_value,	unsigned int attempts,
+	rte_thash_check_tuple_t fn, void *userdata)
 {
-	return 0;
+	uint32_t tmp_tuple[tuple_len / sizeof(uint32_t)];
+	unsigned int i, j, ret = 0;
+	uint32_t hash, adj_bits;
+	uint8_t bit;
+	const uint8_t *hash_key;
+
+	if ((ctx == NULL) || (h == NULL) || (tuple == NULL) ||
+			(tuple_len % sizeof(uint32_t) != 0) || (attempts <= 0))
+		return -EINVAL;
+
+	hash_key = rte_thash_get_key(ctx);
+
+	for (i = 0; i < attempts; i++) {
+		for (j = 0; j < (tuple_len / 4); j++)
+			tmp_tuple[j] =
+				rte_be_to_cpu_32(*(uint32_t *)&tuple[j * 4]);
+
+		hash = rte_softrss(tmp_tuple, tuple_len / 4, hash_key);
+		adj_bits = rte_thash_get_complement(h, hash, desired_value);
+
+		/*
+		 * Hint: LSB of adj_bits corresponds to
+		 * offset + len bit of tuple
+		 */
+		for (j = 0; j < sizeof(uint32_t) * CHAR_BIT; j++) {
+			bit = (adj_bits >> j) & 0x1;
+			if (bit)
+				xor_bit(tuple, bit, h->tuple_offset +
+					h->tuple_len - 1 - j);
+		}
+
+		if (fn != NULL) {
+			ret = (fn(userdata, tuple)) ? 0 : -EEXIST;
+			if (ret == 0)
+				return 0;
+			else if (i < (attempts - 1)) {
+				/* Update tuple with random bits */
+				for (j = 0; j < h->tuple_len; j++) {
+					bit = rte_rand() & 0x1;
+					if (bit)
+						xor_bit(tuple, bit,
+							h->tuple_offset +
+							h->tuple_len - 1 - j);
+				}
+			}
+		} else
+			return 0;
+	}
+
+	return ret;
 }
-- 
2.7.4


^ permalink raw reply	[flat|nested] 45+ messages in thread

* [dpdk-dev] [PATCH v5 3/5] test/hash: add additional thash tests
  2021-04-13 13:19     ` [dpdk-dev] [PATCH v4 " Vladimir Medvedkin
                         ` (3 preceding siblings ...)
  2021-04-19 15:59       ` [dpdk-dev] [PATCH v5 2/5] hash: add predictable RSS implementation Vladimir Medvedkin
@ 2021-04-19 15:59       ` Vladimir Medvedkin
  2021-04-29  9:13         ` David Marchand
  2021-04-19 15:59       ` [dpdk-dev] [PATCH v5 4/5] doc: add thash documentation Vladimir Medvedkin
  2021-04-19 15:59       ` [dpdk-dev] [PATCH v5 5/5] maintainers: claim maintainership of the hash library Vladimir Medvedkin
  6 siblings, 1 reply; 45+ messages in thread
From: Vladimir Medvedkin @ 2021-04-19 15:59 UTC (permalink / raw)
  To: dev
  Cc: konstantin.ananyev, andrey.chilikin, ray.kinsella, yipeng1.wang,
	sameh.gobriel, bruce.richardson

This patch adds tests for predictable RSS feature.

Signed-off-by: Vladimir Medvedkin <vladimir.medvedkin@intel.com>
Acked-by: Yipeng Wang <yipeng1.wang@intel.com>
---
 app/test/test_thash.c | 469 +++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 463 insertions(+), 6 deletions(-)

diff --git a/app/test/test_thash.c b/app/test/test_thash.c
index a6aadd1..d8981fb 100644
--- a/app/test/test_thash.c
+++ b/app/test/test_thash.c
@@ -5,11 +5,15 @@
 #include <rte_common.h>
 #include <rte_eal.h>
 #include <rte_ip.h>
+#include <rte_random.h>
 
 #include "test.h"
 
 #include <rte_thash.h>
 
+#define HASH_MSK(reta_sz)	((1 << reta_sz) - 1)
+#define TUPLE_SZ	(RTE_THASH_V4_L4_LEN * 4)
+
 struct test_thash_v4 {
 	uint32_t	dst_ip;
 	uint32_t	src_ip;
@@ -75,7 +79,7 @@ uint8_t default_rss_key[] = {
 };
 
 static int
-test_thash(void)
+test_toeplitz_hash_calc(void)
 {
 	uint32_t i, j;
 	union rte_thash_tuple tuple;
@@ -100,7 +104,7 @@ test_thash(void)
 				RTE_THASH_V4_L4_LEN, default_rss_key);
 		if ((rss_l3 != v4_tbl[i].hash_l3) ||
 				(rss_l3l4 != v4_tbl[i].hash_l3l4))
-			return -1;
+			return -TEST_FAILED;
 		/*Calculate hash with converted key*/
 		rss_l3 = rte_softrss_be((uint32_t *)&tuple,
 				RTE_THASH_V4_L3_LEN, rss_key_be);
@@ -108,7 +112,7 @@ test_thash(void)
 				RTE_THASH_V4_L4_LEN, rss_key_be);
 		if ((rss_l3 != v4_tbl[i].hash_l3) ||
 				(rss_l3l4 != v4_tbl[i].hash_l3l4))
-			return -1;
+			return -TEST_FAILED;
 	}
 	for (i = 0; i < RTE_DIM(v6_tbl); i++) {
 		/*Fill ipv6 hdr*/
@@ -127,7 +131,7 @@ test_thash(void)
 				RTE_THASH_V6_L4_LEN, default_rss_key);
 		if ((rss_l3 != v6_tbl[i].hash_l3) ||
 				(rss_l3l4 != v6_tbl[i].hash_l3l4))
-			return -1;
+			return -TEST_FAILED;
 		/*Calculate hash with converted key*/
 		rss_l3 = rte_softrss_be((uint32_t *)&tuple,
 				RTE_THASH_V6_L3_LEN, rss_key_be);
@@ -135,9 +139,462 @@ test_thash(void)
 				RTE_THASH_V6_L4_LEN, rss_key_be);
 		if ((rss_l3 != v6_tbl[i].hash_l3) ||
 				(rss_l3l4 != v6_tbl[i].hash_l3l4))
-			return -1;
+			return -TEST_FAILED;
 	}
-	return 0;
+	return TEST_SUCCESS;
+}
+
+static int
+test_create_invalid(void)
+{
+	struct rte_thash_ctx *ctx;
+	int key_len = 40;
+	int reta_sz = 7;
+
+	ctx = rte_thash_init_ctx(NULL, key_len, reta_sz, NULL, 0);
+	RTE_TEST_ASSERT(ctx == NULL,
+		"Call succeeded with invalid parameters\n");
+
+	ctx = rte_thash_init_ctx("test", 0, reta_sz, NULL, 0);
+	RTE_TEST_ASSERT(ctx == NULL,
+		"Call succeeded with invalid parameters\n");
+
+	ctx = rte_thash_init_ctx(NULL, key_len, 1, NULL, 0);
+	RTE_TEST_ASSERT(ctx == NULL,
+		"Call succeeded with invalid parameters\n");
+
+	ctx = rte_thash_init_ctx(NULL, key_len, 17, NULL, 0);
+	RTE_TEST_ASSERT(ctx == NULL,
+		"Call succeeded with invalid parameters\n");
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_multiple_create(void)
+{
+	struct rte_thash_ctx *ctx;
+	int key_len = 40;
+	int reta_sz = 7;
+	int i;
+
+	for (i = 0; i < 100; i++) {
+		ctx = rte_thash_init_ctx("test", key_len, reta_sz, NULL, 0);
+		RTE_TEST_ASSERT(ctx != NULL, "Can not create CTX\n");
+
+		rte_thash_free_ctx(ctx);
+	}
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_free_null(void)
+{
+	struct rte_thash_ctx *ctx;
+
+	ctx = rte_thash_init_ctx("test", 40, 7, NULL, 0);
+	RTE_TEST_ASSERT(ctx != NULL, "Can not create CTX\n");
+
+	rte_thash_free_ctx(ctx);
+	rte_thash_free_ctx(NULL);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_add_invalid_helper(void)
+{
+	struct rte_thash_ctx *ctx;
+	const int key_len = 40;
+	int reta_sz = 7;
+	int ret;
+
+	ctx = rte_thash_init_ctx("test", key_len, reta_sz, NULL, 0);
+	RTE_TEST_ASSERT(ctx != NULL, "can not create thash ctx\n");
+
+	ret = rte_thash_add_helper(NULL, "test", reta_sz, 0);
+	RTE_TEST_ASSERT(ret == -EINVAL,
+		"Call succeeded with invalid parameters\n");
+
+	ret = rte_thash_add_helper(ctx, NULL, reta_sz, 0);
+	RTE_TEST_ASSERT(ret == -EINVAL,
+		"Call succeeded with invalid parameters\n");
+
+	ret = rte_thash_add_helper(ctx, "test", reta_sz - 1, 0);
+	RTE_TEST_ASSERT(ret == -EINVAL,
+		"Call succeeded with invalid parameters\n");
+
+	ret = rte_thash_add_helper(ctx, "test", reta_sz, key_len * 8);
+	RTE_TEST_ASSERT(ret == -EINVAL,
+		"Call succeeded with invalid parameters\n");
+
+	ret = rte_thash_add_helper(ctx, "first_range", reta_sz, 0);
+	RTE_TEST_ASSERT(ret == 0, "Can not create helper\n");
+
+	ret = rte_thash_add_helper(ctx, "first_range", reta_sz, 0);
+	RTE_TEST_ASSERT(ret == -EEXIST,
+		"Call succeeded with duplicated name\n");
+
+	/*
+	 * Create second helper with offset 3 * reta_sz.
+	 * Note first_range helper created range in key:
+	 * [0, 32 + length{= reta_sz} - 1), i.e [0, 37).
+	 * second range is [44, 81)
+	 */
+	ret = rte_thash_add_helper(ctx, "second_range", reta_sz,
+		32 +  2 * reta_sz);
+	RTE_TEST_ASSERT(ret == 0, "Can not create helper\n");
+
+	/*
+	 * Try to create overlapping with first_ and second_ ranges,
+	 * i.e. [6, 49)
+	 */
+	ret = rte_thash_add_helper(ctx, "third_range", 2 * reta_sz, reta_sz);
+	RTE_TEST_ASSERT(ret == -EEXIST,
+		"Call succeeded with overlapping ranges\n");
+
+	rte_thash_free_ctx(ctx);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_find_existing(void)
+{
+	struct rte_thash_ctx *ctx, *ret_ctx;
+
+	ctx = rte_thash_init_ctx("test", 40, 7, NULL, 0);
+	RTE_TEST_ASSERT(ctx != NULL, "can not create thash ctx\n");
+
+	ret_ctx = rte_thash_find_existing("test");
+	RTE_TEST_ASSERT(ret_ctx != NULL, "can not find existing ctx\n");
+
+	rte_thash_free_ctx(ctx);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_get_helper(void)
+{
+	struct rte_thash_ctx *ctx;
+	struct rte_thash_subtuple_helper *h;
+	int ret;
+
+	ctx = rte_thash_init_ctx("test", 40, 7, NULL, 0);
+	RTE_TEST_ASSERT(ctx != NULL, "Can not create thash ctx\n");
+
+	h = rte_thash_get_helper(NULL, "first_range");
+	RTE_TEST_ASSERT(h == NULL, "Call succeeded with invalid parameters\n");
+
+	h = rte_thash_get_helper(ctx, NULL);
+	RTE_TEST_ASSERT(h == NULL, "Call succeeded with invalid parameters\n");
+
+	ret = rte_thash_add_helper(ctx, "first_range", 8, 0);
+	RTE_TEST_ASSERT(ret == 0, "Can not create helper\n");
+
+	h = rte_thash_get_helper(ctx, "first_range");
+	RTE_TEST_ASSERT(h != NULL, "Can not find helper\n");
+
+	rte_thash_free_ctx(ctx);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_period_overflow(void)
+{
+	struct rte_thash_ctx *ctx;
+	int reta_sz = 7; /* reflects polynomial degree */
+	int ret;
+
+	/* first create without RTE_THASH_IGNORE_PERIOD_OVERFLOW flag */
+	ctx = rte_thash_init_ctx("test", 40, reta_sz, NULL, 0);
+	RTE_TEST_ASSERT(ctx != NULL, "Can not create thash ctx\n");
+
+	/* requested range > (2^reta_sz) - 1 */
+	ret = rte_thash_add_helper(ctx, "test", (1 << reta_sz), 0);
+	RTE_TEST_ASSERT(ret == -ENOSPC,
+		"Call succeeded with invalid parameters\n");
+
+	/* requested range == len + 32 - 1, smaller than (2^reta_sz) - 1 */
+	ret = rte_thash_add_helper(ctx, "test", (1 << reta_sz) - 32, 0);
+	RTE_TEST_ASSERT(ret == 0, "Can not create helper\n");
+
+	rte_thash_free_ctx(ctx);
+
+	/* create with RTE_THASH_IGNORE_PERIOD_OVERFLOW flag */
+	ctx = rte_thash_init_ctx("test", 40, reta_sz, NULL,
+		RTE_THASH_IGNORE_PERIOD_OVERFLOW);
+	RTE_TEST_ASSERT(ctx != NULL, "Can not create thash ctx\n");
+
+	/* requested range > (2^reta_sz - 1) */
+	ret = rte_thash_add_helper(ctx, "test", (1 << reta_sz) + 10, 0);
+	RTE_TEST_ASSERT(ret == 0, "Can not create helper\n");
+
+	rte_thash_free_ctx(ctx);
+
+	return TEST_SUCCESS;
+}
+
+static int
+test_predictable_rss_min_seq(void)
+{
+	struct rte_thash_ctx *ctx;
+	struct rte_thash_subtuple_helper *h;
+	const int key_len = 40;
+	int reta_sz = 6;
+	uint8_t initial_key[key_len];
+	const uint8_t *new_key;
+	int ret;
+	union rte_thash_tuple tuple;
+	uint32_t orig_hash, adj_hash, adj;
+	unsigned int desired_value = 27 & HASH_MSK(reta_sz);
+	uint16_t port_value = 22;
+
+	memset(initial_key, 0, key_len);
+
+	ctx = rte_thash_init_ctx("test", key_len, reta_sz, initial_key,
+		RTE_THASH_MINIMAL_SEQ);
+	RTE_TEST_ASSERT(ctx != NULL, "can not create thash ctx\n");
+
+	ret = rte_thash_add_helper(ctx, "snat", sizeof(uint16_t) * 8,
+		offsetof(union rte_thash_tuple, v4.sport) * 8);
+	RTE_TEST_ASSERT(ret == 0, "can not add helper, ret %d\n", ret);
+
+	h = rte_thash_get_helper(ctx, "snat");
+	RTE_TEST_ASSERT(h != NULL, "can not find helper\n");
+
+	new_key = rte_thash_get_key(ctx);
+	tuple.v4.src_addr = RTE_IPV4(0, 0, 0, 0);
+	tuple.v4.dst_addr = RTE_IPV4(0, 0, 0, 0);
+	tuple.v4.sport = 0;
+	tuple.v4.sport = rte_cpu_to_be_16(port_value);
+	tuple.v4.dport = 0;
+	tuple.v4.sctp_tag = rte_be_to_cpu_32(tuple.v4.sctp_tag);
+
+	orig_hash = rte_softrss((uint32_t *)&tuple,
+		RTE_THASH_V4_L4_LEN, new_key);
+	adj = rte_thash_get_complement(h, orig_hash, desired_value);
+
+	tuple.v4.sctp_tag = rte_cpu_to_be_32(tuple.v4.sctp_tag);
+	tuple.v4.sport ^= rte_cpu_to_be_16(adj);
+	tuple.v4.sctp_tag = rte_be_to_cpu_32(tuple.v4.sctp_tag);
+
+	adj_hash = rte_softrss((uint32_t *)&tuple,
+		RTE_THASH_V4_L4_LEN, new_key);
+	RTE_TEST_ASSERT((adj_hash & HASH_MSK(reta_sz)) ==
+		desired_value, "bad desired value\n");
+
+	rte_thash_free_ctx(ctx);
+
+	return TEST_SUCCESS;
+}
+
+/*
+ * This test creates 7 subranges in the following order:
+ * range_one	= [56, 95),	len = 8, offset = 56
+ * range_two	= [64, 103),	len = 8, offset = 64
+ * range_three	= [120, 159),	len = 8, offset = 120
+ * range_four	= [48, 87),	len = 8, offset = 48
+ * range_five	= [57, 95),	len = 7, offset = 57
+ * range_six	= [40, 111),	len = 40, offset = 40
+ * range_seven	= [0, 39),	len = 8, offset = 0
+ */
+struct range {
+	const char *name;
+	int len;
+	int offset;
+	int byte_idx;
+};
+
+struct range rng_arr[] = {
+	{"one",   8,  56,  7},
+	{"two",   8,  64,  8},
+	{"three", 8,  120, 15},
+	{"four",  8,  48,  6},
+	{"six",   40, 40,  9},
+	{"five",  7,  57,  7},
+	{"seven", 8,  0,   0}
+};
+
+static int
+test_predictable_rss_multirange(void)
+{
+	struct rte_thash_ctx *ctx;
+	struct rte_thash_subtuple_helper *h[RTE_DIM(rng_arr)];
+	const uint8_t *new_key;
+	const int key_len = 40;
+	int reta_sz = 7;
+	unsigned int i, j, k;
+	int ret;
+	uint32_t desired_value = rte_rand() & HASH_MSK(reta_sz);
+	uint8_t tuples[RTE_DIM(rng_arr)][16] = { {0} };
+	uint32_t *ptr;
+	uint32_t hashes[RTE_DIM(rng_arr)];
+	uint32_t adj_hashes[RTE_DIM(rng_arr)];
+	uint32_t adj;
+
+	ctx = rte_thash_init_ctx("test", key_len, reta_sz, NULL, 0);
+	RTE_TEST_ASSERT(ctx != NULL, "can not create thash ctx\n");
+
+	for (i = 0; i < RTE_DIM(rng_arr); i++) {
+		ret = rte_thash_add_helper(ctx, rng_arr[i].name,
+			rng_arr[i].len, rng_arr[i].offset);
+		RTE_TEST_ASSERT(ret == 0, "can not add helper\n");
+
+		h[i] = rte_thash_get_helper(ctx, rng_arr[i].name);
+		RTE_TEST_ASSERT(h[i] != NULL, "can not find helper\n");
+	}
+	new_key = rte_thash_get_key(ctx);
+
+	/*
+	 * calculate hashes, complements, then adjust keys with
+	 * complements and recalsulate hashes
+	 */
+	for (i = 0; i < RTE_DIM(rng_arr); i++) {
+		for (k = 0; k < 100; k++) {
+			/* init with random keys */
+			ptr = (uint32_t *)&tuples[i][0];
+			for (j = 0; j < 4; j++)
+				ptr[j] = rte_rand();
+			/* convert keys from BE to CPU byte order */
+			for (j = 0; j < 4; j++)
+				ptr[j] = rte_be_to_cpu_32(ptr[j]);
+
+			hashes[i] = rte_softrss(ptr, 4, new_key);
+			adj = rte_thash_get_complement(h[i], hashes[i],
+				desired_value);
+			/* convert back to BE to adjust the value */
+			for (j = 0; j < 4; j++)
+				ptr[j] = rte_cpu_to_be_32(ptr[j]);
+
+			tuples[i][rng_arr[i].byte_idx] ^= adj;
+
+			for (j = 0; j < 4; j++)
+				ptr[j] = rte_be_to_cpu_32(ptr[j]);
+
+			adj_hashes[i] = rte_softrss(ptr, 4, new_key);
+			RTE_TEST_ASSERT((adj_hashes[i] & HASH_MSK(reta_sz)) ==
+				desired_value,
+				"bad desired value for %d tuple\n", i);
+		}
+	}
+
+	rte_thash_free_ctx(ctx);
+
+	return TEST_SUCCESS;
+}
+
+static int
+cmp_tuple_eq(void *userdata, uint8_t *tuple)
+{
+	return memcmp(userdata, tuple, TUPLE_SZ);
+}
+
+static int
+test_adjust_tuple(void)
+{
+	struct rte_thash_ctx *ctx;
+	struct rte_thash_subtuple_helper *h;
+	const int key_len = 40;
+	const uint8_t *new_key;
+	uint8_t tuple[TUPLE_SZ];
+	uint32_t tmp_tuple[TUPLE_SZ / sizeof(uint32_t)];
+	uint32_t tuple_copy[TUPLE_SZ / sizeof(uint32_t)];
+	uint32_t hash;
+	int reta_sz = CHAR_BIT;
+	int ret;
+	unsigned int i, desired_value = rte_rand() & HASH_MSK(reta_sz);
+
+	memset(tuple, 0xab, TUPLE_SZ);
+
+	ctx = rte_thash_init_ctx("test", key_len, reta_sz, NULL, 0);
+	RTE_TEST_ASSERT(ctx != NULL, "can not create thash ctx\n");
+
+	/*
+	 * set offset to be in the middle of a byte
+	 * set size of the subtuple to be 2 * rets_sz
+	 * to have the room for random bits
+	 */
+	ret = rte_thash_add_helper(ctx, "test", reta_sz * 2,
+		(5 * CHAR_BIT) + 4);
+	RTE_TEST_ASSERT(ret == 0, "can not add helper, ret %d\n", ret);
+
+	new_key = rte_thash_get_key(ctx);
+
+	h = rte_thash_get_helper(ctx, "test");
+	RTE_TEST_ASSERT(h != NULL, "can not find helper\n");
+
+	ret = rte_thash_adjust_tuple(ctx, h, tuple, TUPLE_SZ, desired_value,
+		1, NULL, NULL);
+	RTE_TEST_ASSERT(ret == 0, "can not adjust tuple, ret %d\n", ret);
+
+	for (i = 0; i < (TUPLE_SZ / 4); i++)
+		tmp_tuple[i] =
+			rte_be_to_cpu_32(*(uint32_t *)&tuple[i * 4]);
+
+	hash = rte_softrss(tmp_tuple, TUPLE_SZ / 4, new_key);
+	RTE_TEST_ASSERT((hash & HASH_MSK(reta_sz)) ==
+		desired_value, "bad desired value\n");
+
+
+	/* Pass previously calculated tuple to callback function */
+	memcpy(tuple_copy, tuple, TUPLE_SZ);
+
+	memset(tuple, 0xab, TUPLE_SZ);
+	ret = rte_thash_adjust_tuple(ctx, h, tuple, TUPLE_SZ, desired_value,
+		1, cmp_tuple_eq, tuple_copy);
+	RTE_TEST_ASSERT(ret == -EEXIST,
+		"adjust tuple didn't indicate collision\n");
+
+	/*
+	 * Make the function to generate random bits into subtuple
+	 * after first adjustment attempt.
+	 */
+	memset(tuple, 0xab, TUPLE_SZ);
+	ret = rte_thash_adjust_tuple(ctx, h, tuple, TUPLE_SZ, desired_value,
+		2, cmp_tuple_eq, tuple_copy);
+	RTE_TEST_ASSERT(ret == 0, "can not adjust tuple, ret %d\n", ret);
+
+	for (i = 0; i < (TUPLE_SZ / 4); i++)
+		tmp_tuple[i] =
+			rte_be_to_cpu_32(*(uint32_t *)&tuple[i * 4]);
+
+	hash = rte_softrss(tmp_tuple, TUPLE_SZ / 4, new_key);
+	RTE_TEST_ASSERT((hash & HASH_MSK(reta_sz)) ==
+		desired_value, "bad desired value\n");
+
+	rte_thash_free_ctx(ctx);
+
+	return TEST_SUCCESS;
+}
+
+static struct unit_test_suite thash_tests = {
+	.suite_name = "thash autotest",
+	.setup = NULL,
+	.teardown = NULL,
+	.unit_test_cases = {
+	TEST_CASE(test_toeplitz_hash_calc),
+	TEST_CASE(test_create_invalid),
+	TEST_CASE(test_multiple_create),
+	TEST_CASE(test_free_null),
+	TEST_CASE(test_add_invalid_helper),
+	TEST_CASE(test_find_existing),
+	TEST_CASE(test_get_helper),
+	TEST_CASE(test_period_overflow),
+	TEST_CASE(test_predictable_rss_min_seq),
+	TEST_CASE(test_predictable_rss_multirange),
+	TEST_CASE(test_adjust_tuple),
+	TEST_CASES_END()
+	}
+};
+
+static int
+test_thash(void)
+{
+	return unit_test_suite_runner(&thash_tests);
 }
 
 REGISTER_TEST_COMMAND(thash_autotest, test_thash);
-- 
2.7.4


^ permalink raw reply	[flat|nested] 45+ messages in thread

* [dpdk-dev] [PATCH v5 4/5] doc: add thash documentation
  2021-04-13 13:19     ` [dpdk-dev] [PATCH v4 " Vladimir Medvedkin
                         ` (4 preceding siblings ...)
  2021-04-19 15:59       ` [dpdk-dev] [PATCH v5 3/5] test/hash: add additional thash tests Vladimir Medvedkin
@ 2021-04-19 15:59       ` Vladimir Medvedkin
  2021-04-20 11:25         ` Mcnamara, John
  2021-04-19 15:59       ` [dpdk-dev] [PATCH v5 5/5] maintainers: claim maintainership of the hash library Vladimir Medvedkin
  6 siblings, 1 reply; 45+ messages in thread
From: Vladimir Medvedkin @ 2021-04-19 15:59 UTC (permalink / raw)
  To: dev
  Cc: konstantin.ananyev, andrey.chilikin, ray.kinsella, yipeng1.wang,
	sameh.gobriel, bruce.richardson, john.mcnamara

Adds documentation for the Toeplitz hash library

Signed-off-by: Vladimir Medvedkin <vladimir.medvedkin@intel.com>
Reviewed-by: Konstantin Ananyev <konstantin.ananyev@intel.com>
Reviewed-by: John McNamara <john.mcnamara@intel.com>
---
 doc/guides/prog_guide/img/predictable_snat_1.svg | 1444 +++++++++++++++++++++
 doc/guides/prog_guide/img/predictable_snat_2.svg | 1444 +++++++++++++++++++++
 doc/guides/prog_guide/img/rss_queue_assign.svg   | 1454 ++++++++++++++++++++++
 doc/guides/prog_guide/index.rst                  |    1 +
 doc/guides/prog_guide/toeplitz_hash_lib.rst      |  289 +++++
 doc/guides/rel_notes/release_21_05.rst           |    6 +
 6 files changed, 4638 insertions(+)
 create mode 100644 doc/guides/prog_guide/img/predictable_snat_1.svg
 create mode 100644 doc/guides/prog_guide/img/predictable_snat_2.svg
 create mode 100644 doc/guides/prog_guide/img/rss_queue_assign.svg
 create mode 100644 doc/guides/prog_guide/toeplitz_hash_lib.rst

diff --git a/doc/guides/prog_guide/img/predictable_snat_1.svg b/doc/guides/prog_guide/img/predictable_snat_1.svg
new file mode 100644
index 0000000..5f97ccb
--- /dev/null
+++ b/doc/guides/prog_guide/img/predictable_snat_1.svg
@@ -0,0 +1,1444 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generated by Microsoft Visio, SVG Export predictable_snat_1.svg Page-4 -->
+
+<svg
+   xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/"
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="7.8211055in"
+   height="1.8973334in"
+   viewBox="0 0 563.12017 136.6082"
+   xml:space="preserve"
+   class="st14"
+   version="1.1"
+   id="svg1495"
+   sodipodi:docname="predictable_snat_1.svg"
+   style="font-size:12px;overflow:visible;color-interpolation-filters:sRGB;fill:none;fill-rule:evenodd;stroke-linecap:square;stroke-miterlimit:3"
+   inkscape:version="0.92.3 (2405546, 2018-03-11)"><metadata
+   id="metadata1499"><rdf:RDF><cc:Work
+       rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+         rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><sodipodi:namedview
+   pagecolor="#ffffff"
+   bordercolor="#666666"
+   borderopacity="1"
+   objecttolerance="10"
+   gridtolerance="10"
+   guidetolerance="10"
+   inkscape:pageopacity="0"
+   inkscape:pageshadow="2"
+   inkscape:window-width="1640"
+   inkscape:window-height="878"
+   id="namedview1497"
+   showgrid="false"
+   inkscape:zoom="0.75544421"
+   inkscape:cx="638.25085"
+   inkscape:cy="70.081592"
+   inkscape:window-x="52"
+   inkscape:window-y="52"
+   inkscape:window-maximized="0"
+   inkscape:current-layer="svg1495" />
+	<v:documentProperties
+   v:langID="6153"
+   v:metric="true"
+   v:viewMarkup="false">
+		<v:userDefs>
+			<v:ud
+   v:nameU="msvNoAutoConnect"
+   v:val="VT0(1):26" />
+		</v:userDefs>
+	</v:documentProperties>
+
+	<style
+   type="text/css"
+   id="style1158">
+	<![CDATA[
+		.st1 {fill:#ff00ff;fill-opacity:0;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0;stroke-width:0.72}
+		.st2 {fill:url(#grad0-7);stroke:#ffffff;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.72}
+		.st3 {fill:#ff0000;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.24}
+		.st4 {fill:url(#grad0-19);stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.72}
+		.st5 {stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+		.st6 {fill:#ffffff;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.24;visibility:hidden}
+		.st7 {fill:#ffffff;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.24}
+		.st8 {fill:#000000;font-family:Calibri;font-size:0.666664em}
+		.st9 {marker-end:url(#mrkr4-70);stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.239976}
+		.st10 {fill:#000000;fill-opacity:1;stroke:#000000;stroke-opacity:1;stroke-width:0.086948582161584}
+		.st11 {fill:none;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+		.st12 {fill:#000000;font-family:Calibri;font-size:1.00001em}
+		.st13 {font-size:1em}
+		.st14 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+	]]>
+	</style>
+
+	<defs
+   id="Patterns_And_Gradients">
+		<linearGradient
+   id="grad0-7"
+   x1="-0.2804561"
+   y1="1048.661"
+   x2="32.190596"
+   y2="1048.661"
+   gradientTransform="scale(1.2836234,0.7790447)"
+   gradientUnits="userSpaceOnUse">
+			<stop
+   offset="0"
+   stop-color="#ffffff"
+   stop-opacity="1"
+   id="stop1160" />
+			<stop
+   offset="1"
+   stop-color="#dfdfdf"
+   stop-opacity="1"
+   id="stop1162" />
+		</linearGradient>
+		<linearGradient
+   id="grad0-19"
+   x1="-0.30286968"
+   y1="967.01808"
+   x2="34.733349"
+   y2="967.01808"
+   gradientTransform="scale(1.1896436,0.84058789)"
+   gradientUnits="userSpaceOnUse">
+			<stop
+   offset="0.01"
+   stop-color="#c0c0c0"
+   stop-opacity="1"
+   id="stop1165" />
+			<stop
+   offset="0.5"
+   stop-color="#ffffff"
+   stop-opacity="1"
+   id="stop1167" />
+			<stop
+   offset="1"
+   stop-color="#c0c0c0"
+   stop-opacity="1"
+   id="stop1169" />
+		</linearGradient>
+	</defs>
+	<defs
+   id="Markers">
+		<g
+   id="lend4">
+			<path
+   d="M 2,1 0,0 2,-1 v 2"
+   style="stroke:none"
+   id="path1173"
+   inkscape:connector-curvature="0" />
+		</g>
+		<marker
+   id="mrkr4-70"
+   class="st10"
+   v:arrowType="4"
+   v:arrowSize="2"
+   v:setback="23.0021"
+   refX="-23.0021"
+   orient="auto"
+   markerUnits="strokeWidth"
+   overflow="visible"
+   style="overflow:visible;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.08694858;stroke-opacity:1">
+			<use
+   xlink:href="#lend4"
+   transform="scale(-11.50105)"
+   id="use1176"
+   x="0"
+   y="0"
+   width="100%"
+   height="100%" />
+		</marker>
+	</defs>
+	<g
+   v:mID="8"
+   v:index="4"
+   v:groupContext="foregroundPage"
+   id="g1493"
+   transform="translate(-98.5864,-336.89801)">
+		<title
+   id="title1180">Page-4</title>
+		<v:pageProperties
+   v:drawingScale="0.0393701"
+   v:pageScale="0.0393701"
+   v:drawingUnits="24"
+   v:shadowOffsetX="8.50394"
+   v:shadowOffsetY="-8.50394" />
+		<v:layer
+   v:name="Connector"
+   v:index="0" />
+		<g
+   id="group1001-1"
+   transform="translate(275.811,-396.85)"
+   v:mID="1001"
+   v:groupContext="group">
+			<v:custProps>
+				<v:cp
+   v:nameU="ShapeClass"
+   v:lbl="ShapeClass"
+   v:type="0"
+   v:invis="true"
+   v:ask="false"
+   v:langID="1033"
+   v:val="VT4(Equipment)" />
+				<v:cp
+   v:nameU="ShapeType"
+   v:lbl="ShapeType"
+   v:type="0"
+   v:invis="true"
+   v:ask="false"
+   v:langID="1033"
+   v:val="VT4(Device)" />
+				<v:cp
+   v:nameU="SubShapeType"
+   v:lbl="SubShapeType"
+   v:type="0"
+   v:invis="true"
+   v:ask="false"
+   v:langID="1033"
+   v:val="VT4(Switch)" />
+				<v:cp
+   v:nameU="Manufacturer"
+   v:lbl="Manufacturer"
+   v:type="0"
+   v:sortKey="Equipment"
+   v:invis="false"
+   v:ask="false"
+   v:langID="1033" />
+				<v:cp
+   v:nameU="ProductNumber"
+   v:lbl="Product Number"
+   v:type="0"
+   v:sortKey="Equipment"
+   v:invis="false"
+   v:ask="false"
+   v:langID="1033" />
+				<v:cp
+   v:nameU="PartNumber"
+   v:lbl="Part Number"
+   v:type="0"
+   v:sortKey="Equipment"
+   v:invis="false"
+   v:ask="false"
+   v:langID="1033" />
+				<v:cp
+   v:nameU="ProductDescription"
+   v:lbl="Product Description"
+   v:type="0"
+   v:sortKey="Equipment"
+   v:invis="false"
+   v:ask="false"
+   v:langID="1033" />
+				<v:cp
+   v:nameU="AssetNumber"
+   v:lbl="Asset Number"
+   v:type="0"
+   v:sortKey="Asset"
+   v:invis="false"
+   v:ask="false"
+   v:langID="1033" />
+				<v:cp
+   v:nameU="SerialNumber"
+   v:lbl="Serial Number"
+   v:type="0"
+   v:sortKey="Asset"
+   v:invis="false"
+   v:ask="false"
+   v:langID="1033" />
+				<v:cp
+   v:nameU="Location"
+   v:lbl="Location"
+   v:type="0"
+   v:sortKey="Asset"
+   v:invis="false"
+   v:ask="false"
+   v:langID="1033" />
+				<v:cp
+   v:nameU="Building"
+   v:lbl="Building"
+   v:type="0"
+   v:sortKey="Asset"
+   v:invis="false"
+   v:ask="false"
+   v:langID="1033" />
+				<v:cp
+   v:nameU="Room"
+   v:lbl="Room"
+   v:type="0"
+   v:sortKey="Asset"
+   v:invis="false"
+   v:ask="false"
+   v:langID="1033" />
+				<v:cp
+   v:nameU="NetworkName"
+   v:lbl="Network Name"
+   v:type="0"
+   v:sortKey="Network"
+   v:invis="false"
+   v:ask="false"
+   v:langID="1033" />
+				<v:cp
+   v:nameU="IPAddress"
+   v:lbl="IP Address"
+   v:type="0"
+   v:sortKey="Network"
+   v:invis="false"
+   v:ask="false"
+   v:langID="1033" />
+				<v:cp
+   v:nameU="SubnetMask"
+   v:lbl="Subnet Mask"
+   v:type="0"
+   v:sortKey="Network"
+   v:invis="false"
+   v:ask="false"
+   v:langID="1033" />
+				<v:cp
+   v:nameU="AdminInterface"
+   v:lbl="Administrative Interface"
+   v:type="0"
+   v:sortKey="Network"
+   v:invis="false"
+   v:ask="false"
+   v:langID="1033" />
+				<v:cp
+   v:nameU="NumberOfPorts"
+   v:lbl="Number of Ports"
+   v:type="0"
+   v:sortKey="Network"
+   v:invis="false"
+   v:ask="false"
+   v:langID="1033" />
+				<v:cp
+   v:nameU="MACAddress"
+   v:lbl="MAC Address"
+   v:type="0"
+   v:sortKey="Network"
+   v:invis="false"
+   v:ask="false"
+   v:langID="1033" />
+				<v:cp
+   v:nameU="CommunityString"
+   v:lbl="Community String"
+   v:type="0"
+   v:sortKey="Network"
+   v:invis="false"
+   v:ask="false"
+   v:langID="1033" />
+				<v:cp
+   v:nameU="NetworkDescription"
+   v:lbl="Network Description"
+   v:type="0"
+   v:sortKey="Network"
+   v:invis="false"
+   v:ask="false"
+   v:langID="1033" />
+			</v:custProps>
+			<v:userDefs>
+				<v:ud
+   v:nameU="HasText"
+   v:val="VT0(0):5" />
+				<v:ud
+   v:nameU="ShapeClass"
+   v:val="VT0(5):26" />
+				<v:ud
+   v:nameU="ShapeType"
+   v:val="VT0(8):26" />
+				<v:ud
+   v:nameU="SubShapeType"
+   v:val="VT0(66):26" />
+				<v:ud
+   v:nameU="visLegendShape"
+   v:val="VT0(2):26" />
+				<v:ud
+   v:nameU="SolSH"
+   v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41" />
+				<v:ud
+   v:nameU="visVersion"
+   v:prompt=""
+   v:val="VT0(15):26" />
+			</v:userDefs>
+			<title
+   id="title1182">Router.1001</title>
+			<g
+   id="shape1002-2"
+   v:mID="1002"
+   v:groupContext="shape">
+				<title
+   id="title1184">Sheet.1002</title>
+				<path
+   d="m 40.96,813.22 a 20.4803,12.2882 -180 1 0 -40.96,0 v 16.38 a 20.4803,12.2882 -180 1 0 40.96,0 z"
+   class="st1"
+   id="path1186"
+   inkscape:connector-curvature="0"
+   style="fill:#ff00ff;fill-opacity:0;stroke:#000000;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0" />
+			</g>
+			<g
+   id="shape1003-4"
+   v:mID="1003"
+   v:groupContext="shape"
+   transform="translate(0,-16.3843)">
+				<title
+   id="title1189">Sheet.1003</title>
+				<ellipse
+   cx="20.480301"
+   cy="829.60199"
+   rx="20.480301"
+   ry="12.2882"
+   class="st2"
+   id="ellipse1191"
+   style="fill:url(#grad0-7);stroke:#ffffff;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1004-8"
+   v:mID="1004"
+   v:groupContext="shape"
+   transform="matrix(-1,0,0,1,34.4921,-28.6724)">
+				<title
+   id="title1194">Sheet.1004</title>
+				<path
+   d="m 0,839.53 v -5.73 h 9.93 l -4.08,2.36 8.16,4.71 -1.77,1.02 -8.16,-4.71 z"
+   class="st3"
+   id="path1196"
+   inkscape:connector-curvature="0"
+   style="fill:#ff0000;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1005-10"
+   v:mID="1005"
+   v:groupContext="shape"
+   transform="translate(5.58168,-28.6724)">
+				<title
+   id="title1199">Sheet.1005</title>
+				<path
+   d="m 14.9,836.16 -0.89,5.22 -9.04,0.51 4.08,-2.36 -9.05,-5.22 1.77,-1.02 9.05,5.22 z"
+   class="st3"
+   id="path1201"
+   inkscape:connector-curvature="0"
+   style="fill:#ff0000;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1006-12"
+   v:mID="1006"
+   v:groupContext="shape"
+   transform="translate(20.4803,-20.0707)">
+				<title
+   id="title1204">Sheet.1006</title>
+				<path
+   d="m 0,839.02 0.89,-5.22 9.04,-0.51 -4.08,2.35 9.05,5.23 -1.78,1.02 -9.04,-5.22 z"
+   class="st3"
+   id="path1206"
+   inkscape:connector-curvature="0"
+   style="fill:#ff0000;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1007-14"
+   v:mID="1007"
+   v:groupContext="shape"
+   transform="matrix(1,0,0,-1,6.4685,1655.11)">
+				<title
+   id="title1209">Sheet.1007</title>
+				<path
+   d="m 0,839.53 v -5.73 h 9.93 l -4.08,2.36 8.16,4.71 -1.77,1.02 -8.16,-4.71 z"
+   class="st3"
+   id="path1211"
+   inkscape:connector-curvature="0"
+   style="fill:#ff0000;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1008-16"
+   v:mID="1008"
+   v:groupContext="shape">
+				<title
+   id="title1214">Sheet.1008</title>
+				<path
+   d="m 0,813.22 v 16.38 a 20.4803,12.2882 -180 1 0 40.96,0 v -16.38 a 20.4803,12.2882 0 1 1 -40.96,0 z"
+   class="st4"
+   id="path1216"
+   inkscape:connector-curvature="0"
+   style="fill:url(#grad0-19);stroke:#000000;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1009-20"
+   v:mID="1009"
+   v:groupContext="shape">
+				<title
+   id="title1219">Sheet.1009</title>
+				<path
+   d="m 40.96,813.22 a 20.4803,12.2882 -180 1 0 -40.96,0 v 16.38 a 20.4803,12.2882 -180 1 0 40.96,0 v -16.38"
+   class="st5"
+   id="path1221"
+   inkscape:connector-curvature="0"
+   style="stroke:#000000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+		</g>
+		<g
+   id="group1011-23"
+   transform="translate(101.657,-458.787)"
+   v:mID="1011"
+   v:groupContext="group">
+			<v:userDefs>
+				<v:ud
+   v:nameU="visVersion"
+   v:val="VT0(15):26" />
+			</v:userDefs>
+			<title
+   id="title1225">Array.1011</title>
+			<g
+   id="shape1012-24"
+   v:mID="1012"
+   v:groupContext="shape"
+   transform="translate(297.638)">
+				<title
+   id="title1227">Sheet.1012</title>
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st6"
+   id="rect1229"
+   style="visibility:hidden;fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1013-26"
+   v:mID="1013"
+   v:groupContext="shape"
+   transform="translate(255.118)">
+				<title
+   id="title1232">Sheet.1013</title>
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st6"
+   id="rect1234"
+   style="visibility:hidden;fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1014-28"
+   v:mID="1014"
+   v:groupContext="shape"
+   transform="translate(212.598)">
+				<title
+   id="title1237">Sheet.1014</title>
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st6"
+   id="rect1239"
+   style="visibility:hidden;fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1015-30"
+   v:mID="1015"
+   v:groupContext="shape"
+   transform="translate(170.079)">
+				<title
+   id="title1242">Sheet.1015</title>
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st6"
+   id="rect1244"
+   style="visibility:hidden;fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1016-32"
+   v:mID="1016"
+   v:groupContext="shape"
+   transform="translate(127.559)">
+				<title
+   id="title1247">Sheet.1016</title>
+				<desc
+   id="desc1249">443</desc>
+				<v:textBlock
+   v:margins="rect(1,1,1,1)"
+   v:tabSpace="42.5197" />
+				<v:textRect
+   cx="21.2598"
+   cy="834.803"
+   width="42.52"
+   height="14.1732" />
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st7"
+   id="rect1251"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+				<text
+   x="15.18"
+   y="837.20001"
+   class="st8"
+   v:langID="6153"
+   id="text1253"
+   style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />443</text>
+			</g>
+			<g
+   id="shape1017-35"
+   v:mID="1017"
+   v:groupContext="shape"
+   transform="translate(85.0394)">
+				<title
+   id="title1256">Sheet.1017</title>
+				<desc
+   id="desc1258">10000</desc>
+				<v:textBlock
+   v:margins="rect(1,1,1,1)"
+   v:tabSpace="42.5197" />
+				<v:textRect
+   cx="21.2598"
+   cy="834.803"
+   width="42.52"
+   height="14.1732" />
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st7"
+   id="rect1260"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+				<text
+   x="11.12"
+   y="837.20001"
+   class="st8"
+   v:langID="6153"
+   id="text1262"
+   style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />10000</text>
+			</g>
+			<g
+   id="shape1018-38"
+   v:mID="1018"
+   v:groupContext="shape"
+   transform="translate(42.5197)">
+				<title
+   id="title1265">Sheet.1018</title>
+				<desc
+   id="desc1267">192.0.2.100</desc>
+				<v:textBlock
+   v:margins="rect(1,1,1,1)"
+   v:tabSpace="42.5197" />
+				<v:textRect
+   cx="21.2598"
+   cy="834.803"
+   width="42.52"
+   height="14.1732" />
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st7"
+   id="rect1269"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+				<text
+   x="2.01"
+   y="837.20001"
+   class="st8"
+   v:langID="6153"
+   id="text1271"
+   style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />192.0.2.100</text>
+			</g>
+			<g
+   id="shape1019-41"
+   v:mID="1019"
+   v:groupContext="shape">
+				<title
+   id="title1274">Sheet.1019</title>
+				<desc
+   id="desc1276">10.10.10.10</desc>
+				<v:textBlock
+   v:margins="rect(1,1,1,1)"
+   v:tabSpace="42.5197" />
+				<v:textRect
+   cx="21.2598"
+   cy="834.803"
+   width="42.52"
+   height="14.1732" />
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st7"
+   id="rect1278"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+				<text
+   x="2.01"
+   y="837.20001"
+   class="st8"
+   v:langID="6153"
+   id="text1280"
+   style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />10.10.10.10</text>
+			</g>
+		</g>
+		<g
+   id="group1020-44"
+   transform="translate(321.429,-458.787)"
+   v:mID="1020"
+   v:groupContext="group">
+			<v:userDefs>
+				<v:ud
+   v:nameU="visVersion"
+   v:val="VT0(15):26" />
+			</v:userDefs>
+			<title
+   id="title1284">Array.1020</title>
+			<g
+   id="shape1021-45"
+   v:mID="1021"
+   v:groupContext="shape"
+   transform="translate(297.638)">
+				<title
+   id="title1286">Sheet.1021</title>
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st6"
+   id="rect1288"
+   style="visibility:hidden;fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1022-47"
+   v:mID="1022"
+   v:groupContext="shape"
+   transform="translate(255.118)">
+				<title
+   id="title1291">Sheet.1022</title>
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st6"
+   id="rect1293"
+   style="visibility:hidden;fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1023-49"
+   v:mID="1023"
+   v:groupContext="shape"
+   transform="translate(212.598)">
+				<title
+   id="title1296">Sheet.1023</title>
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st6"
+   id="rect1298"
+   style="visibility:hidden;fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1024-51"
+   v:mID="1024"
+   v:groupContext="shape"
+   transform="translate(170.079)">
+				<title
+   id="title1301">Sheet.1024</title>
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st6"
+   id="rect1303"
+   style="visibility:hidden;fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1025-53"
+   v:mID="1025"
+   v:groupContext="shape"
+   transform="translate(127.559)">
+				<title
+   id="title1306">Sheet.1025</title>
+				<desc
+   id="desc1308">443</desc>
+				<v:textBlock
+   v:margins="rect(1,1,1,1)"
+   v:tabSpace="42.5197" />
+				<v:textRect
+   cx="21.2598"
+   cy="834.803"
+   width="42.52"
+   height="14.1732" />
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st7"
+   id="rect1310"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+				<text
+   x="15.18"
+   y="837.20001"
+   class="st8"
+   v:langID="6153"
+   id="text1312"
+   style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />443</text>
+			</g>
+			<g
+   id="shape1026-56"
+   v:mID="1026"
+   v:groupContext="shape"
+   transform="translate(85.0394)">
+				<title
+   id="title1315">Sheet.1026</title>
+				<desc
+   id="desc1317">12345</desc>
+				<v:textBlock
+   v:margins="rect(1,1,1,1)"
+   v:tabSpace="42.5197" />
+				<v:textRect
+   cx="21.2598"
+   cy="834.803"
+   width="42.52"
+   height="14.1732" />
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st7"
+   id="rect1319"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+				<text
+   x="11.12"
+   y="837.20001"
+   class="st8"
+   v:langID="6153"
+   id="text1321"
+   style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />12345</text>
+			</g>
+			<g
+   id="shape1027-59"
+   v:mID="1027"
+   v:groupContext="shape"
+   transform="translate(42.5197)">
+				<title
+   id="title1324">Sheet.1027</title>
+				<desc
+   id="desc1326">192.0.2.100</desc>
+				<v:textBlock
+   v:margins="rect(1,1,1,1)"
+   v:tabSpace="42.5197" />
+				<v:textRect
+   cx="21.2598"
+   cy="834.803"
+   width="42.52"
+   height="14.1732" />
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st7"
+   id="rect1328"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+				<text
+   x="2.01"
+   y="837.20001"
+   class="st8"
+   v:langID="6153"
+   id="text1330"
+   style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />192.0.2.100</text>
+			</g>
+			<g
+   id="shape1028-62"
+   v:mID="1028"
+   v:groupContext="shape">
+				<title
+   id="title1333">Sheet.1028</title>
+				<desc
+   id="desc1335">172.16.0.20</desc>
+				<v:textBlock
+   v:margins="rect(1,1,1,1)"
+   v:tabSpace="42.5197" />
+				<v:textRect
+   cx="21.2598"
+   cy="834.803"
+   width="42.52"
+   height="14.1732" />
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st7"
+   id="rect1337"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+				<text
+   x="2.01"
+   y="837.20001"
+   class="st8"
+   v:langID="6153"
+   id="text1339"
+   style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />172.16.0.20</text>
+			</g>
+		</g>
+		<g
+   id="shape1029-65"
+   v:mID="1029"
+   v:groupContext="shape"
+   v:layerMember="0"
+   transform="translate(271.736,-458.787)">
+			<title
+   id="title1343">Dynamic connector.1029</title>
+			<v:userDefs>
+				<v:ud
+   v:nameU="visVersion"
+   v:val="VT0(15):26" />
+			</v:userDefs>
+			<path
+   d="M 0,834.8 H 44.17"
+   class="st9"
+   id="path1345"
+   inkscape:connector-curvature="0"
+   style="stroke:#000000;stroke-width:0.239976;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr4-70)" />
+		</g>
+		<g
+   id="group1030-71"
+   transform="translate(321.403,-368.504)"
+   v:mID="1030"
+   v:groupContext="group">
+			<v:userDefs>
+				<v:ud
+   v:nameU="visVersion"
+   v:val="VT0(15):26" />
+			</v:userDefs>
+			<title
+   id="title1348">Array.1030</title>
+			<g
+   id="shape1031-72"
+   v:mID="1031"
+   v:groupContext="shape"
+   transform="translate(297.638)">
+				<title
+   id="title1350">Sheet.1031</title>
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st6"
+   id="rect1352"
+   style="visibility:hidden;fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1032-74"
+   v:mID="1032"
+   v:groupContext="shape"
+   transform="translate(255.118)">
+				<title
+   id="title1355">Sheet.1032</title>
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st6"
+   id="rect1357"
+   style="visibility:hidden;fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1033-76"
+   v:mID="1033"
+   v:groupContext="shape"
+   transform="translate(212.598)">
+				<title
+   id="title1360">Sheet.1033</title>
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st6"
+   id="rect1362"
+   style="visibility:hidden;fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1034-78"
+   v:mID="1034"
+   v:groupContext="shape"
+   transform="translate(170.079)">
+				<title
+   id="title1365">Sheet.1034</title>
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st6"
+   id="rect1367"
+   style="visibility:hidden;fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1035-80"
+   v:mID="1035"
+   v:groupContext="shape"
+   transform="translate(127.559)">
+				<title
+   id="title1370">Sheet.1035</title>
+				<desc
+   id="desc1372">12345</desc>
+				<v:textBlock
+   v:margins="rect(1,1,1,1)"
+   v:tabSpace="42.5197" />
+				<v:textRect
+   cx="21.2598"
+   cy="834.803"
+   width="42.52"
+   height="14.1732" />
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st7"
+   id="rect1374"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+				<text
+   x="11.12"
+   y="837.20001"
+   class="st8"
+   v:langID="6153"
+   id="text1376"
+   style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />12345</text>
+			</g>
+			<g
+   id="shape1036-83"
+   v:mID="1036"
+   v:groupContext="shape"
+   transform="translate(85.0394)">
+				<title
+   id="title1379">Sheet.1036</title>
+				<desc
+   id="desc1381">443</desc>
+				<v:textBlock
+   v:margins="rect(1,1,1,1)"
+   v:tabSpace="42.5197" />
+				<v:textRect
+   cx="21.2598"
+   cy="834.803"
+   width="42.52"
+   height="14.1732" />
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st7"
+   id="rect1383"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+				<text
+   x="15.18"
+   y="837.20001"
+   class="st8"
+   v:langID="6153"
+   id="text1385"
+   style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />443</text>
+			</g>
+			<g
+   id="shape1037-86"
+   v:mID="1037"
+   v:groupContext="shape"
+   transform="translate(42.5197)">
+				<title
+   id="title1388">Sheet.1037</title>
+				<desc
+   id="desc1390">172.16.0.20</desc>
+				<v:textBlock
+   v:margins="rect(1,1,1,1)"
+   v:tabSpace="42.5197" />
+				<v:textRect
+   cx="21.2598"
+   cy="834.803"
+   width="42.52"
+   height="14.1732" />
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st7"
+   id="rect1392"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+				<text
+   x="2.01"
+   y="837.20001"
+   class="st8"
+   v:langID="6153"
+   id="text1394"
+   style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />172.16.0.20</text>
+			</g>
+			<g
+   id="shape1038-89"
+   v:mID="1038"
+   v:groupContext="shape">
+				<title
+   id="title1397">Sheet.1038</title>
+				<desc
+   id="desc1399">192.0.2.100</desc>
+				<v:textBlock
+   v:margins="rect(1,1,1,1)"
+   v:tabSpace="42.5197" />
+				<v:textRect
+   cx="21.2598"
+   cy="834.803"
+   width="42.52"
+   height="14.1732" />
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st7"
+   id="rect1401"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+				<text
+   x="2.01"
+   y="837.20001"
+   class="st8"
+   v:langID="6153"
+   id="text1403"
+   style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />192.0.2.100</text>
+			</g>
+		</g>
+		<g
+   id="group1039-92"
+   transform="translate(98.7064,-368.504)"
+   v:mID="1039"
+   v:groupContext="group">
+			<v:userDefs>
+				<v:ud
+   v:nameU="visVersion"
+   v:val="VT0(15):26" />
+			</v:userDefs>
+			<title
+   id="title1407">Array.1039</title>
+			<g
+   id="shape1040-93"
+   v:mID="1040"
+   v:groupContext="shape"
+   transform="translate(297.638)">
+				<title
+   id="title1409">Sheet.1040</title>
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st6"
+   id="rect1411"
+   style="visibility:hidden;fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1041-95"
+   v:mID="1041"
+   v:groupContext="shape"
+   transform="translate(255.118)">
+				<title
+   id="title1414">Sheet.1041</title>
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st6"
+   id="rect1416"
+   style="visibility:hidden;fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1042-97"
+   v:mID="1042"
+   v:groupContext="shape"
+   transform="translate(212.598)">
+				<title
+   id="title1419">Sheet.1042</title>
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st6"
+   id="rect1421"
+   style="visibility:hidden;fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1043-99"
+   v:mID="1043"
+   v:groupContext="shape"
+   transform="translate(170.079)">
+				<title
+   id="title1424">Sheet.1043</title>
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st6"
+   id="rect1426"
+   style="visibility:hidden;fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1044-101"
+   v:mID="1044"
+   v:groupContext="shape"
+   transform="translate(127.559)">
+				<title
+   id="title1429">Sheet.1044</title>
+				<desc
+   id="desc1431">10000</desc>
+				<v:textBlock
+   v:margins="rect(1,1,1,1)"
+   v:tabSpace="42.5197" />
+				<v:textRect
+   cx="21.2598"
+   cy="834.803"
+   width="42.52"
+   height="14.1732" />
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st7"
+   id="rect1433"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+				<text
+   x="11.12"
+   y="837.20001"
+   class="st8"
+   v:langID="6153"
+   id="text1435"
+   style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />10000</text>
+			</g>
+			<g
+   id="shape1045-104"
+   v:mID="1045"
+   v:groupContext="shape"
+   transform="translate(85.0394)">
+				<title
+   id="title1438">Sheet.1045</title>
+				<desc
+   id="desc1440">443</desc>
+				<v:textBlock
+   v:margins="rect(1,1,1,1)"
+   v:tabSpace="42.5197" />
+				<v:textRect
+   cx="21.2598"
+   cy="834.803"
+   width="42.52"
+   height="14.1732" />
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st7"
+   id="rect1442"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+				<text
+   x="15.18"
+   y="837.20001"
+   class="st8"
+   v:langID="6153"
+   id="text1444"
+   style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />443</text>
+			</g>
+			<g
+   id="shape1046-107"
+   v:mID="1046"
+   v:groupContext="shape"
+   transform="translate(42.5197)">
+				<title
+   id="title1447">Sheet.1046</title>
+				<desc
+   id="desc1449">10.10.10.10</desc>
+				<v:textBlock
+   v:margins="rect(1,1,1,1)"
+   v:tabSpace="42.5197" />
+				<v:textRect
+   cx="21.2598"
+   cy="834.803"
+   width="42.52"
+   height="14.1732" />
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st7"
+   id="rect1451"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+				<text
+   x="2.01"
+   y="837.20001"
+   class="st8"
+   v:langID="6153"
+   id="text1453"
+   style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />10.10.10.10</text>
+			</g>
+			<g
+   id="shape1047-110"
+   v:mID="1047"
+   v:groupContext="shape">
+				<title
+   id="title1456">Sheet.1047</title>
+				<desc
+   id="desc1458">192.0.2.100</desc>
+				<v:textBlock
+   v:margins="rect(1,1,1,1)"
+   v:tabSpace="42.5197" />
+				<v:textRect
+   cx="21.2598"
+   cy="834.803"
+   width="42.52"
+   height="14.1732" />
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st7"
+   id="rect1460"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+				<text
+   x="2.01"
+   y="837.20001"
+   class="st8"
+   v:langID="6153"
+   id="text1462"
+   style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />192.0.2.100</text>
+			</g>
+		</g>
+		<g
+   id="shape1048-113"
+   v:mID="1048"
+   v:groupContext="shape"
+   v:layerMember="0"
+   transform="translate(321.403,-368.504)">
+			<title
+   id="title1466">Dynamic connector.1048</title>
+			<v:userDefs>
+				<v:ud
+   v:nameU="visVersion"
+   v:val="VT0(15):26" />
+			</v:userDefs>
+			<path
+   d="M 0,834.8 H -47.1"
+   class="st9"
+   id="path1468"
+   inkscape:connector-curvature="0"
+   style="stroke:#000000;stroke-width:0.239976;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr4-70)" />
+		</g>
+		<g
+   id="shape1049-118"
+   v:mID="1049"
+   v:groupContext="shape"
+   transform="translate(101.657,-480.047)">
+			<title
+   id="title1471">Sheet.1049</title>
+			<desc
+   id="desc1473">RSS hash value 0xdeadbeef Packet assigned to queue 15</desc>
+			<v:textBlock
+   v:margins="rect(4,4,4,4)"
+   v:tabSpace="42.5197" />
+			<v:textRect
+   cx="89.1142"
+   cy="829.417"
+   width="178.23"
+   height="24.9449" />
+			<rect
+   x="0"
+   y="816.94501"
+   width="178.228"
+   height="24.944901"
+   class="st11"
+   id="rect1475"
+   style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" />
+			<text
+   x="22.889999"
+   y="825.82001"
+   class="st12"
+   v:langID="6153"
+   id="text1479"
+   style="font-size:12.00012016px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />RSS hash value 0xdeadbeef<v:newlineChar /><tspan
+   x="19.709999"
+   dy="1.2em"
+   class="st13"
+   id="tspan1477"
+   style="font-size:12.00012016px">Packet assigned to queue 15</tspan></text>
+		</g>
+		<g
+   id="shape1051-122"
+   v:mID="1051"
+   v:groupContext="shape"
+   transform="translate(318.331,-386.079)">
+			<title
+   id="title1482">Sheet.1051</title>
+			<desc
+   id="desc1484">RSS hash value 0xbadcab1e Packet assigned to queue 14</desc>
+			<v:textBlock
+   v:margins="rect(4,4,4,4)"
+   v:tabSpace="42.5197" />
+			<v:textRect
+   cx="89.1142"
+   cy="829.417"
+   width="178.23"
+   height="24.9449" />
+			<rect
+   x="0"
+   y="816.94501"
+   width="178.228"
+   height="24.944901"
+   class="st11"
+   id="rect1486"
+   style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" />
+			<text
+   x="22.24"
+   y="825.82001"
+   class="st12"
+   v:langID="6153"
+   id="text1490"
+   style="font-size:12.00012016px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />RSS hash value 0xbadcab1e<v:newlineChar /><tspan
+   x="19.709999"
+   dy="1.2em"
+   class="st13"
+   id="tspan1488"
+   style="font-size:12.00012016px">Packet assigned to queue 14</tspan></text>
+		</g>
+	</g>
+</svg>
\ No newline at end of file
diff --git a/doc/guides/prog_guide/img/predictable_snat_2.svg b/doc/guides/prog_guide/img/predictable_snat_2.svg
new file mode 100644
index 0000000..8525459
--- /dev/null
+++ b/doc/guides/prog_guide/img/predictable_snat_2.svg
@@ -0,0 +1,1444 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generated by Microsoft Visio, SVG Export predictable_snat_2.svg Page-5 -->
+
+<svg
+   xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/"
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="7.8211055in"
+   height="1.8973334in"
+   viewBox="0 0 563.12017 136.6082"
+   xml:space="preserve"
+   class="st14"
+   version="1.1"
+   id="svg7179"
+   sodipodi:docname="predictable_snat_2.svg"
+   style="font-size:12px;overflow:visible;color-interpolation-filters:sRGB;fill:none;fill-rule:evenodd;stroke-linecap:square;stroke-miterlimit:3"
+   inkscape:version="0.92.3 (2405546, 2018-03-11)"><metadata
+   id="metadata7183"><rdf:RDF><cc:Work
+       rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+         rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><sodipodi:namedview
+   pagecolor="#ffffff"
+   bordercolor="#666666"
+   borderopacity="1"
+   objecttolerance="10"
+   gridtolerance="10"
+   guidetolerance="10"
+   inkscape:pageopacity="0"
+   inkscape:pageshadow="2"
+   inkscape:window-width="1296"
+   inkscape:window-height="757"
+   id="namedview7181"
+   showgrid="false"
+   inkscape:zoom="0.3689753"
+   inkscape:cx="598.10007"
+   inkscape:cy="151.8361"
+   inkscape:window-x="356"
+   inkscape:window-y="113"
+   inkscape:window-maximized="0"
+   inkscape:current-layer="svg7179" />
+	<v:documentProperties
+   v:langID="6153"
+   v:metric="true"
+   v:viewMarkup="false">
+		<v:userDefs>
+			<v:ud
+   v:nameU="msvNoAutoConnect"
+   v:val="VT0(1):26" />
+		</v:userDefs>
+	</v:documentProperties>
+
+	<style
+   type="text/css"
+   id="style6842">
+	<![CDATA[
+		.st1 {fill:#ff00ff;fill-opacity:0;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0;stroke-width:0.72}
+		.st2 {fill:url(#grad0-7);stroke:#ffffff;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.72}
+		.st3 {fill:#ff0000;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.24}
+		.st4 {fill:url(#grad0-19);stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.72}
+		.st5 {stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.5}
+		.st6 {fill:#ffffff;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.24;visibility:hidden}
+		.st7 {fill:#ffffff;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.24}
+		.st8 {fill:#000000;font-family:Calibri;font-size:0.666664em}
+		.st9 {marker-end:url(#mrkr4-70);stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.239976}
+		.st10 {fill:#000000;fill-opacity:1;stroke:#000000;stroke-opacity:1;stroke-width:0.086948582161584}
+		.st11 {fill:none;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+		.st12 {fill:#000000;font-family:Calibri;font-size:1.00001em}
+		.st13 {font-size:1em}
+		.st14 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+	]]>
+	</style>
+
+	<defs
+   id="Patterns_And_Gradients">
+		<linearGradient
+   id="grad0-7"
+   x1="-0.2804561"
+   y1="1048.661"
+   x2="32.190596"
+   y2="1048.661"
+   gradientTransform="scale(1.2836234,0.7790447)"
+   gradientUnits="userSpaceOnUse">
+			<stop
+   offset="0"
+   stop-color="#ffffff"
+   stop-opacity="1"
+   id="stop6844" />
+			<stop
+   offset="1"
+   stop-color="#dfdfdf"
+   stop-opacity="1"
+   id="stop6846" />
+		</linearGradient>
+		<linearGradient
+   id="grad0-19"
+   x1="-0.30286968"
+   y1="967.01808"
+   x2="34.733349"
+   y2="967.01808"
+   gradientTransform="scale(1.1896436,0.84058789)"
+   gradientUnits="userSpaceOnUse">
+			<stop
+   offset="0.01"
+   stop-color="#c0c0c0"
+   stop-opacity="1"
+   id="stop6849" />
+			<stop
+   offset="0.5"
+   stop-color="#ffffff"
+   stop-opacity="1"
+   id="stop6851" />
+			<stop
+   offset="1"
+   stop-color="#c0c0c0"
+   stop-opacity="1"
+   id="stop6853" />
+		</linearGradient>
+	</defs>
+	<defs
+   id="Markers">
+		<g
+   id="lend4">
+			<path
+   d="M 2,1 0,0 2,-1 v 2"
+   style="stroke:none"
+   id="path6857"
+   inkscape:connector-curvature="0" />
+		</g>
+		<marker
+   id="mrkr4-70"
+   class="st10"
+   v:arrowType="4"
+   v:arrowSize="2"
+   v:setback="23.0021"
+   refX="-23.0021"
+   orient="auto"
+   markerUnits="strokeWidth"
+   overflow="visible"
+   style="overflow:visible;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.08694858;stroke-opacity:1">
+			<use
+   xlink:href="#lend4"
+   transform="scale(-11.50105)"
+   id="use6860"
+   x="0"
+   y="0"
+   width="100%"
+   height="100%" />
+		</marker>
+	</defs>
+	<g
+   v:mID="9"
+   v:index="5"
+   v:groupContext="foregroundPage"
+   id="g7177"
+   transform="translate(-98.5864,-336.89801)">
+		<title
+   id="title6864">Page-5</title>
+		<v:pageProperties
+   v:drawingScale="0.0393701"
+   v:pageScale="0.0393701"
+   v:drawingUnits="24"
+   v:shadowOffsetX="8.50394"
+   v:shadowOffsetY="-8.50394" />
+		<v:layer
+   v:name="Connector"
+   v:index="0" />
+		<g
+   id="group1001-1"
+   transform="translate(275.811,-396.85)"
+   v:mID="1001"
+   v:groupContext="group">
+			<v:custProps>
+				<v:cp
+   v:nameU="ShapeClass"
+   v:lbl="ShapeClass"
+   v:type="0"
+   v:invis="true"
+   v:ask="false"
+   v:langID="1033"
+   v:val="VT4(Equipment)" />
+				<v:cp
+   v:nameU="ShapeType"
+   v:lbl="ShapeType"
+   v:type="0"
+   v:invis="true"
+   v:ask="false"
+   v:langID="1033"
+   v:val="VT4(Device)" />
+				<v:cp
+   v:nameU="SubShapeType"
+   v:lbl="SubShapeType"
+   v:type="0"
+   v:invis="true"
+   v:ask="false"
+   v:langID="1033"
+   v:val="VT4(Switch)" />
+				<v:cp
+   v:nameU="Manufacturer"
+   v:lbl="Manufacturer"
+   v:type="0"
+   v:sortKey="Equipment"
+   v:invis="false"
+   v:ask="false"
+   v:langID="1033" />
+				<v:cp
+   v:nameU="ProductNumber"
+   v:lbl="Product Number"
+   v:type="0"
+   v:sortKey="Equipment"
+   v:invis="false"
+   v:ask="false"
+   v:langID="1033" />
+				<v:cp
+   v:nameU="PartNumber"
+   v:lbl="Part Number"
+   v:type="0"
+   v:sortKey="Equipment"
+   v:invis="false"
+   v:ask="false"
+   v:langID="1033" />
+				<v:cp
+   v:nameU="ProductDescription"
+   v:lbl="Product Description"
+   v:type="0"
+   v:sortKey="Equipment"
+   v:invis="false"
+   v:ask="false"
+   v:langID="1033" />
+				<v:cp
+   v:nameU="AssetNumber"
+   v:lbl="Asset Number"
+   v:type="0"
+   v:sortKey="Asset"
+   v:invis="false"
+   v:ask="false"
+   v:langID="1033" />
+				<v:cp
+   v:nameU="SerialNumber"
+   v:lbl="Serial Number"
+   v:type="0"
+   v:sortKey="Asset"
+   v:invis="false"
+   v:ask="false"
+   v:langID="1033" />
+				<v:cp
+   v:nameU="Location"
+   v:lbl="Location"
+   v:type="0"
+   v:sortKey="Asset"
+   v:invis="false"
+   v:ask="false"
+   v:langID="1033" />
+				<v:cp
+   v:nameU="Building"
+   v:lbl="Building"
+   v:type="0"
+   v:sortKey="Asset"
+   v:invis="false"
+   v:ask="false"
+   v:langID="1033" />
+				<v:cp
+   v:nameU="Room"
+   v:lbl="Room"
+   v:type="0"
+   v:sortKey="Asset"
+   v:invis="false"
+   v:ask="false"
+   v:langID="1033" />
+				<v:cp
+   v:nameU="NetworkName"
+   v:lbl="Network Name"
+   v:type="0"
+   v:sortKey="Network"
+   v:invis="false"
+   v:ask="false"
+   v:langID="1033" />
+				<v:cp
+   v:nameU="IPAddress"
+   v:lbl="IP Address"
+   v:type="0"
+   v:sortKey="Network"
+   v:invis="false"
+   v:ask="false"
+   v:langID="1033" />
+				<v:cp
+   v:nameU="SubnetMask"
+   v:lbl="Subnet Mask"
+   v:type="0"
+   v:sortKey="Network"
+   v:invis="false"
+   v:ask="false"
+   v:langID="1033" />
+				<v:cp
+   v:nameU="AdminInterface"
+   v:lbl="Administrative Interface"
+   v:type="0"
+   v:sortKey="Network"
+   v:invis="false"
+   v:ask="false"
+   v:langID="1033" />
+				<v:cp
+   v:nameU="NumberOfPorts"
+   v:lbl="Number of Ports"
+   v:type="0"
+   v:sortKey="Network"
+   v:invis="false"
+   v:ask="false"
+   v:langID="1033" />
+				<v:cp
+   v:nameU="MACAddress"
+   v:lbl="MAC Address"
+   v:type="0"
+   v:sortKey="Network"
+   v:invis="false"
+   v:ask="false"
+   v:langID="1033" />
+				<v:cp
+   v:nameU="CommunityString"
+   v:lbl="Community String"
+   v:type="0"
+   v:sortKey="Network"
+   v:invis="false"
+   v:ask="false"
+   v:langID="1033" />
+				<v:cp
+   v:nameU="NetworkDescription"
+   v:lbl="Network Description"
+   v:type="0"
+   v:sortKey="Network"
+   v:invis="false"
+   v:ask="false"
+   v:langID="1033" />
+			</v:custProps>
+			<v:userDefs>
+				<v:ud
+   v:nameU="HasText"
+   v:val="VT0(0):5" />
+				<v:ud
+   v:nameU="ShapeClass"
+   v:val="VT0(5):26" />
+				<v:ud
+   v:nameU="ShapeType"
+   v:val="VT0(8):26" />
+				<v:ud
+   v:nameU="SubShapeType"
+   v:val="VT0(66):26" />
+				<v:ud
+   v:nameU="visLegendShape"
+   v:val="VT0(2):26" />
+				<v:ud
+   v:nameU="SolSH"
+   v:val="VT15({BF0433D9-CD73-4EB5-8390-8653BE590246}):41" />
+				<v:ud
+   v:nameU="visVersion"
+   v:prompt=""
+   v:val="VT0(15):26" />
+			</v:userDefs>
+			<title
+   id="title6866">Router.1001</title>
+			<g
+   id="shape1002-2"
+   v:mID="1002"
+   v:groupContext="shape">
+				<title
+   id="title6868">Sheet.1002</title>
+				<path
+   d="m 40.96,813.22 a 20.4803,12.2882 -180 1 0 -40.96,0 v 16.38 a 20.4803,12.2882 -180 1 0 40.96,0 z"
+   class="st1"
+   id="path6870"
+   inkscape:connector-curvature="0"
+   style="fill:#ff00ff;fill-opacity:0;stroke:#000000;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0" />
+			</g>
+			<g
+   id="shape1003-4"
+   v:mID="1003"
+   v:groupContext="shape"
+   transform="translate(0,-16.3843)">
+				<title
+   id="title6873">Sheet.1003</title>
+				<ellipse
+   cx="20.480301"
+   cy="829.60199"
+   rx="20.480301"
+   ry="12.2882"
+   class="st2"
+   id="ellipse6875"
+   style="fill:url(#grad0-7);stroke:#ffffff;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1004-8"
+   v:mID="1004"
+   v:groupContext="shape"
+   transform="matrix(-1,0,0,1,34.4921,-28.6724)">
+				<title
+   id="title6878">Sheet.1004</title>
+				<path
+   d="m 0,839.53 v -5.73 h 9.93 l -4.08,2.36 8.16,4.71 -1.77,1.02 -8.16,-4.71 z"
+   class="st3"
+   id="path6880"
+   inkscape:connector-curvature="0"
+   style="fill:#ff0000;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1005-10"
+   v:mID="1005"
+   v:groupContext="shape"
+   transform="translate(5.58168,-28.6724)">
+				<title
+   id="title6883">Sheet.1005</title>
+				<path
+   d="m 14.9,836.16 -0.89,5.22 -9.04,0.51 4.08,-2.36 -9.05,-5.22 1.77,-1.02 9.05,5.22 z"
+   class="st3"
+   id="path6885"
+   inkscape:connector-curvature="0"
+   style="fill:#ff0000;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1006-12"
+   v:mID="1006"
+   v:groupContext="shape"
+   transform="translate(20.4803,-20.0707)">
+				<title
+   id="title6888">Sheet.1006</title>
+				<path
+   d="m 0,839.02 0.89,-5.22 9.04,-0.51 -4.08,2.35 9.05,5.23 -1.78,1.02 -9.04,-5.22 z"
+   class="st3"
+   id="path6890"
+   inkscape:connector-curvature="0"
+   style="fill:#ff0000;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1007-14"
+   v:mID="1007"
+   v:groupContext="shape"
+   transform="matrix(1,0,0,-1,6.4685,1655.11)">
+				<title
+   id="title6893">Sheet.1007</title>
+				<path
+   d="m 0,839.53 v -5.73 h 9.93 l -4.08,2.36 8.16,4.71 -1.77,1.02 -8.16,-4.71 z"
+   class="st3"
+   id="path6895"
+   inkscape:connector-curvature="0"
+   style="fill:#ff0000;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1008-16"
+   v:mID="1008"
+   v:groupContext="shape">
+				<title
+   id="title6898">Sheet.1008</title>
+				<path
+   d="m 0,813.22 v 16.38 a 20.4803,12.2882 -180 1 0 40.96,0 v -16.38 a 20.4803,12.2882 0 1 1 -40.96,0 z"
+   class="st4"
+   id="path6900"
+   inkscape:connector-curvature="0"
+   style="fill:url(#grad0-19);stroke:#000000;stroke-width:0.72000003;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1009-20"
+   v:mID="1009"
+   v:groupContext="shape">
+				<title
+   id="title6903">Sheet.1009</title>
+				<path
+   d="m 40.96,813.22 a 20.4803,12.2882 -180 1 0 -40.96,0 v 16.38 a 20.4803,12.2882 -180 1 0 40.96,0 v -16.38"
+   class="st5"
+   id="path6905"
+   inkscape:connector-curvature="0"
+   style="stroke:#000000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+		</g>
+		<g
+   id="group1011-23"
+   transform="translate(101.657,-458.787)"
+   v:mID="1011"
+   v:groupContext="group">
+			<v:userDefs>
+				<v:ud
+   v:nameU="visVersion"
+   v:val="VT0(15):26" />
+			</v:userDefs>
+			<title
+   id="title6909">Array.1011</title>
+			<g
+   id="shape1012-24"
+   v:mID="1012"
+   v:groupContext="shape"
+   transform="translate(297.638)">
+				<title
+   id="title6911">Sheet.1012</title>
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st6"
+   id="rect6913"
+   style="visibility:hidden;fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1013-26"
+   v:mID="1013"
+   v:groupContext="shape"
+   transform="translate(255.118)">
+				<title
+   id="title6916">Sheet.1013</title>
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st6"
+   id="rect6918"
+   style="visibility:hidden;fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1014-28"
+   v:mID="1014"
+   v:groupContext="shape"
+   transform="translate(212.598)">
+				<title
+   id="title6921">Sheet.1014</title>
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st6"
+   id="rect6923"
+   style="visibility:hidden;fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1015-30"
+   v:mID="1015"
+   v:groupContext="shape"
+   transform="translate(170.079)">
+				<title
+   id="title6926">Sheet.1015</title>
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st6"
+   id="rect6928"
+   style="visibility:hidden;fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1016-32"
+   v:mID="1016"
+   v:groupContext="shape"
+   transform="translate(127.559)">
+				<title
+   id="title6931">Sheet.1016</title>
+				<desc
+   id="desc6933">443</desc>
+				<v:textBlock
+   v:margins="rect(1,1,1,1)"
+   v:tabSpace="42.5197" />
+				<v:textRect
+   cx="21.2598"
+   cy="834.803"
+   width="42.52"
+   height="14.1732" />
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st7"
+   id="rect6935"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+				<text
+   x="15.18"
+   y="837.20001"
+   class="st8"
+   v:langID="6153"
+   id="text6937"
+   style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />443</text>
+			</g>
+			<g
+   id="shape1017-35"
+   v:mID="1017"
+   v:groupContext="shape"
+   transform="translate(85.0394)">
+				<title
+   id="title6940">Sheet.1017</title>
+				<desc
+   id="desc6942">10000</desc>
+				<v:textBlock
+   v:margins="rect(1,1,1,1)"
+   v:tabSpace="42.5197" />
+				<v:textRect
+   cx="21.2598"
+   cy="834.803"
+   width="42.52"
+   height="14.1732" />
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st7"
+   id="rect6944"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+				<text
+   x="11.12"
+   y="837.20001"
+   class="st8"
+   v:langID="6153"
+   id="text6946"
+   style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />10000</text>
+			</g>
+			<g
+   id="shape1018-38"
+   v:mID="1018"
+   v:groupContext="shape"
+   transform="translate(42.5197)">
+				<title
+   id="title6949">Sheet.1018</title>
+				<desc
+   id="desc6951">192.0.2.100</desc>
+				<v:textBlock
+   v:margins="rect(1,1,1,1)"
+   v:tabSpace="42.5197" />
+				<v:textRect
+   cx="21.2598"
+   cy="834.803"
+   width="42.52"
+   height="14.1732" />
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st7"
+   id="rect6953"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+				<text
+   x="2.01"
+   y="837.20001"
+   class="st8"
+   v:langID="6153"
+   id="text6955"
+   style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />192.0.2.100</text>
+			</g>
+			<g
+   id="shape1019-41"
+   v:mID="1019"
+   v:groupContext="shape">
+				<title
+   id="title6958">Sheet.1019</title>
+				<desc
+   id="desc6960">10.10.10.10</desc>
+				<v:textBlock
+   v:margins="rect(1,1,1,1)"
+   v:tabSpace="42.5197" />
+				<v:textRect
+   cx="21.2598"
+   cy="834.803"
+   width="42.52"
+   height="14.1732" />
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st7"
+   id="rect6962"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+				<text
+   x="2.01"
+   y="837.20001"
+   class="st8"
+   v:langID="6153"
+   id="text6964"
+   style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />10.10.10.10</text>
+			</g>
+		</g>
+		<g
+   id="group1020-44"
+   transform="translate(321.429,-458.787)"
+   v:mID="1020"
+   v:groupContext="group">
+			<v:userDefs>
+				<v:ud
+   v:nameU="visVersion"
+   v:val="VT0(15):26" />
+			</v:userDefs>
+			<title
+   id="title6968">Array.1020</title>
+			<g
+   id="shape1021-45"
+   v:mID="1021"
+   v:groupContext="shape"
+   transform="translate(297.638)">
+				<title
+   id="title6970">Sheet.1021</title>
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st6"
+   id="rect6972"
+   style="visibility:hidden;fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1022-47"
+   v:mID="1022"
+   v:groupContext="shape"
+   transform="translate(255.118)">
+				<title
+   id="title6975">Sheet.1022</title>
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st6"
+   id="rect6977"
+   style="visibility:hidden;fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1023-49"
+   v:mID="1023"
+   v:groupContext="shape"
+   transform="translate(212.598)">
+				<title
+   id="title6980">Sheet.1023</title>
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st6"
+   id="rect6982"
+   style="visibility:hidden;fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1024-51"
+   v:mID="1024"
+   v:groupContext="shape"
+   transform="translate(170.079)">
+				<title
+   id="title6985">Sheet.1024</title>
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st6"
+   id="rect6987"
+   style="visibility:hidden;fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1025-53"
+   v:mID="1025"
+   v:groupContext="shape"
+   transform="translate(127.559)">
+				<title
+   id="title6990">Sheet.1025</title>
+				<desc
+   id="desc6992">443</desc>
+				<v:textBlock
+   v:margins="rect(1,1,1,1)"
+   v:tabSpace="42.5197" />
+				<v:textRect
+   cx="21.2598"
+   cy="834.803"
+   width="42.52"
+   height="14.1732" />
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st7"
+   id="rect6994"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+				<text
+   x="15.18"
+   y="837.20001"
+   class="st8"
+   v:langID="6153"
+   id="text6996"
+   style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />443</text>
+			</g>
+			<g
+   id="shape1026-56"
+   v:mID="1026"
+   v:groupContext="shape"
+   transform="translate(85.0394)">
+				<title
+   id="title6999">Sheet.1026</title>
+				<desc
+   id="desc7001">23456</desc>
+				<v:textBlock
+   v:margins="rect(1,1,1,1)"
+   v:tabSpace="42.5197" />
+				<v:textRect
+   cx="21.2598"
+   cy="834.803"
+   width="42.52"
+   height="14.1732" />
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st7"
+   id="rect7003"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+				<text
+   x="11.12"
+   y="837.20001"
+   class="st8"
+   v:langID="6153"
+   id="text7005"
+   style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />23456</text>
+			</g>
+			<g
+   id="shape1027-59"
+   v:mID="1027"
+   v:groupContext="shape"
+   transform="translate(42.5197)">
+				<title
+   id="title7008">Sheet.1027</title>
+				<desc
+   id="desc7010">192.0.2.100</desc>
+				<v:textBlock
+   v:margins="rect(1,1,1,1)"
+   v:tabSpace="42.5197" />
+				<v:textRect
+   cx="21.2598"
+   cy="834.803"
+   width="42.52"
+   height="14.1732" />
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st7"
+   id="rect7012"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+				<text
+   x="2.01"
+   y="837.20001"
+   class="st8"
+   v:langID="6153"
+   id="text7014"
+   style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />192.0.2.100</text>
+			</g>
+			<g
+   id="shape1028-62"
+   v:mID="1028"
+   v:groupContext="shape">
+				<title
+   id="title7017">Sheet.1028</title>
+				<desc
+   id="desc7019">172.16.0.20</desc>
+				<v:textBlock
+   v:margins="rect(1,1,1,1)"
+   v:tabSpace="42.5197" />
+				<v:textRect
+   cx="21.2598"
+   cy="834.803"
+   width="42.52"
+   height="14.1732" />
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st7"
+   id="rect7021"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+				<text
+   x="2.01"
+   y="837.20001"
+   class="st8"
+   v:langID="6153"
+   id="text7023"
+   style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />172.16.0.20</text>
+			</g>
+		</g>
+		<g
+   id="shape1029-65"
+   v:mID="1029"
+   v:groupContext="shape"
+   v:layerMember="0"
+   transform="translate(271.736,-458.787)">
+			<title
+   id="title7027">Dynamic connector.1029</title>
+			<v:userDefs>
+				<v:ud
+   v:nameU="visVersion"
+   v:val="VT0(15):26" />
+			</v:userDefs>
+			<path
+   d="M 0,834.8 H 44.17"
+   class="st9"
+   id="path7029"
+   inkscape:connector-curvature="0"
+   style="stroke:#000000;stroke-width:0.239976;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr4-70)" />
+		</g>
+		<g
+   id="group1030-71"
+   transform="translate(321.403,-368.504)"
+   v:mID="1030"
+   v:groupContext="group">
+			<v:userDefs>
+				<v:ud
+   v:nameU="visVersion"
+   v:val="VT0(15):26" />
+			</v:userDefs>
+			<title
+   id="title7032">Array.1030</title>
+			<g
+   id="shape1031-72"
+   v:mID="1031"
+   v:groupContext="shape"
+   transform="translate(297.638)">
+				<title
+   id="title7034">Sheet.1031</title>
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st6"
+   id="rect7036"
+   style="visibility:hidden;fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1032-74"
+   v:mID="1032"
+   v:groupContext="shape"
+   transform="translate(255.118)">
+				<title
+   id="title7039">Sheet.1032</title>
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st6"
+   id="rect7041"
+   style="visibility:hidden;fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1033-76"
+   v:mID="1033"
+   v:groupContext="shape"
+   transform="translate(212.598)">
+				<title
+   id="title7044">Sheet.1033</title>
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st6"
+   id="rect7046"
+   style="visibility:hidden;fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1034-78"
+   v:mID="1034"
+   v:groupContext="shape"
+   transform="translate(170.079)">
+				<title
+   id="title7049">Sheet.1034</title>
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st6"
+   id="rect7051"
+   style="visibility:hidden;fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1035-80"
+   v:mID="1035"
+   v:groupContext="shape"
+   transform="translate(127.559)">
+				<title
+   id="title7054">Sheet.1035</title>
+				<desc
+   id="desc7056">23456</desc>
+				<v:textBlock
+   v:margins="rect(1,1,1,1)"
+   v:tabSpace="42.5197" />
+				<v:textRect
+   cx="21.2598"
+   cy="834.803"
+   width="42.52"
+   height="14.1732" />
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st7"
+   id="rect7058"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+				<text
+   x="11.12"
+   y="837.20001"
+   class="st8"
+   v:langID="6153"
+   id="text7060"
+   style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />23456</text>
+			</g>
+			<g
+   id="shape1036-83"
+   v:mID="1036"
+   v:groupContext="shape"
+   transform="translate(85.0394)">
+				<title
+   id="title7063">Sheet.1036</title>
+				<desc
+   id="desc7065">443</desc>
+				<v:textBlock
+   v:margins="rect(1,1,1,1)"
+   v:tabSpace="42.5197" />
+				<v:textRect
+   cx="21.2598"
+   cy="834.803"
+   width="42.52"
+   height="14.1732" />
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st7"
+   id="rect7067"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+				<text
+   x="15.18"
+   y="837.20001"
+   class="st8"
+   v:langID="6153"
+   id="text7069"
+   style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />443</text>
+			</g>
+			<g
+   id="shape1037-86"
+   v:mID="1037"
+   v:groupContext="shape"
+   transform="translate(42.5197)">
+				<title
+   id="title7072">Sheet.1037</title>
+				<desc
+   id="desc7074">172.16.0.20</desc>
+				<v:textBlock
+   v:margins="rect(1,1,1,1)"
+   v:tabSpace="42.5197" />
+				<v:textRect
+   cx="21.2598"
+   cy="834.803"
+   width="42.52"
+   height="14.1732" />
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st7"
+   id="rect7076"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+				<text
+   x="2.01"
+   y="837.20001"
+   class="st8"
+   v:langID="6153"
+   id="text7078"
+   style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />172.16.0.20</text>
+			</g>
+			<g
+   id="shape1038-89"
+   v:mID="1038"
+   v:groupContext="shape">
+				<title
+   id="title7081">Sheet.1038</title>
+				<desc
+   id="desc7083">192.0.2.100</desc>
+				<v:textBlock
+   v:margins="rect(1,1,1,1)"
+   v:tabSpace="42.5197" />
+				<v:textRect
+   cx="21.2598"
+   cy="834.803"
+   width="42.52"
+   height="14.1732" />
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st7"
+   id="rect7085"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+				<text
+   x="2.01"
+   y="837.20001"
+   class="st8"
+   v:langID="6153"
+   id="text7087"
+   style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />192.0.2.100</text>
+			</g>
+		</g>
+		<g
+   id="group1039-92"
+   transform="translate(98.7064,-368.504)"
+   v:mID="1039"
+   v:groupContext="group">
+			<v:userDefs>
+				<v:ud
+   v:nameU="visVersion"
+   v:val="VT0(15):26" />
+			</v:userDefs>
+			<title
+   id="title7091">Array.1039</title>
+			<g
+   id="shape1040-93"
+   v:mID="1040"
+   v:groupContext="shape"
+   transform="translate(297.638)">
+				<title
+   id="title7093">Sheet.1040</title>
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st6"
+   id="rect7095"
+   style="visibility:hidden;fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1041-95"
+   v:mID="1041"
+   v:groupContext="shape"
+   transform="translate(255.118)">
+				<title
+   id="title7098">Sheet.1041</title>
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st6"
+   id="rect7100"
+   style="visibility:hidden;fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1042-97"
+   v:mID="1042"
+   v:groupContext="shape"
+   transform="translate(212.598)">
+				<title
+   id="title7103">Sheet.1042</title>
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st6"
+   id="rect7105"
+   style="visibility:hidden;fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1043-99"
+   v:mID="1043"
+   v:groupContext="shape"
+   transform="translate(170.079)">
+				<title
+   id="title7108">Sheet.1043</title>
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st6"
+   id="rect7110"
+   style="visibility:hidden;fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1044-101"
+   v:mID="1044"
+   v:groupContext="shape"
+   transform="translate(127.559)">
+				<title
+   id="title7113">Sheet.1044</title>
+				<desc
+   id="desc7115">10000</desc>
+				<v:textBlock
+   v:margins="rect(1,1,1,1)"
+   v:tabSpace="42.5197" />
+				<v:textRect
+   cx="21.2598"
+   cy="834.803"
+   width="42.52"
+   height="14.1732" />
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st7"
+   id="rect7117"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+				<text
+   x="11.12"
+   y="837.20001"
+   class="st8"
+   v:langID="6153"
+   id="text7119"
+   style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />10000</text>
+			</g>
+			<g
+   id="shape1045-104"
+   v:mID="1045"
+   v:groupContext="shape"
+   transform="translate(85.0394)">
+				<title
+   id="title7122">Sheet.1045</title>
+				<desc
+   id="desc7124">443</desc>
+				<v:textBlock
+   v:margins="rect(1,1,1,1)"
+   v:tabSpace="42.5197" />
+				<v:textRect
+   cx="21.2598"
+   cy="834.803"
+   width="42.52"
+   height="14.1732" />
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st7"
+   id="rect7126"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+				<text
+   x="15.18"
+   y="837.20001"
+   class="st8"
+   v:langID="6153"
+   id="text7128"
+   style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />443</text>
+			</g>
+			<g
+   id="shape1046-107"
+   v:mID="1046"
+   v:groupContext="shape"
+   transform="translate(42.5197)">
+				<title
+   id="title7131">Sheet.1046</title>
+				<desc
+   id="desc7133">10.10.10.10</desc>
+				<v:textBlock
+   v:margins="rect(1,1,1,1)"
+   v:tabSpace="42.5197" />
+				<v:textRect
+   cx="21.2598"
+   cy="834.803"
+   width="42.52"
+   height="14.1732" />
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st7"
+   id="rect7135"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+				<text
+   x="2.01"
+   y="837.20001"
+   class="st8"
+   v:langID="6153"
+   id="text7137"
+   style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />10.10.10.10</text>
+			</g>
+			<g
+   id="shape1047-110"
+   v:mID="1047"
+   v:groupContext="shape">
+				<title
+   id="title7140">Sheet.1047</title>
+				<desc
+   id="desc7142">192.0.2.100</desc>
+				<v:textBlock
+   v:margins="rect(1,1,1,1)"
+   v:tabSpace="42.5197" />
+				<v:textRect
+   cx="21.2598"
+   cy="834.803"
+   width="42.52"
+   height="14.1732" />
+				<rect
+   x="0"
+   y="827.71698"
+   width="42.519699"
+   height="14.1732"
+   class="st7"
+   id="rect7144"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+				<text
+   x="2.01"
+   y="837.20001"
+   class="st8"
+   v:langID="6153"
+   id="text7146"
+   style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />192.0.2.100</text>
+			</g>
+		</g>
+		<g
+   id="shape1048-113"
+   v:mID="1048"
+   v:groupContext="shape"
+   v:layerMember="0"
+   transform="translate(321.403,-368.504)">
+			<title
+   id="title7150">Dynamic connector.1048</title>
+			<v:userDefs>
+				<v:ud
+   v:nameU="visVersion"
+   v:val="VT0(15):26" />
+			</v:userDefs>
+			<path
+   d="M 0,834.8 H -47.1"
+   class="st9"
+   id="path7152"
+   inkscape:connector-curvature="0"
+   style="stroke:#000000;stroke-width:0.239976;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr4-70)" />
+		</g>
+		<g
+   id="shape1049-118"
+   v:mID="1049"
+   v:groupContext="shape"
+   transform="translate(101.657,-480.047)">
+			<title
+   id="title7155">Sheet.1049</title>
+			<desc
+   id="desc7157">RSS hash value 0xdeadbeef Packet assigned to queue 15</desc>
+			<v:textBlock
+   v:margins="rect(4,4,4,4)"
+   v:tabSpace="42.5197" />
+			<v:textRect
+   cx="89.1142"
+   cy="829.417"
+   width="178.23"
+   height="24.9449" />
+			<rect
+   x="0"
+   y="816.94501"
+   width="178.228"
+   height="24.944901"
+   class="st11"
+   id="rect7159"
+   style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" />
+			<text
+   x="22.889999"
+   y="825.82001"
+   class="st12"
+   v:langID="6153"
+   id="text7163"
+   style="font-size:12.00012016px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />RSS hash value 0xdeadbeef<v:newlineChar /><tspan
+   x="19.709999"
+   dy="1.2em"
+   class="st13"
+   id="tspan7161"
+   style="font-size:12.00012016px">Packet assigned to queue 15</tspan></text>
+		</g>
+		<g
+   id="shape1051-122"
+   v:mID="1051"
+   v:groupContext="shape"
+   transform="translate(318.331,-386.079)">
+			<title
+   id="title7166">Sheet.1051</title>
+			<desc
+   id="desc7168">RSS hash value 0xf00d1eaf Packet assigned to queue 15</desc>
+			<v:textBlock
+   v:margins="rect(4,4,4,4)"
+   v:tabSpace="42.5197" />
+			<v:textRect
+   cx="89.1142"
+   cy="829.417"
+   width="178.23"
+   height="24.9449" />
+			<rect
+   x="0"
+   y="816.94501"
+   width="178.228"
+   height="24.944901"
+   class="st11"
+   id="rect7170"
+   style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" />
+			<text
+   x="24.209999"
+   y="825.82001"
+   class="st12"
+   v:langID="6153"
+   id="text7174"
+   style="font-size:12.00012016px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />RSS hash value 0xf00d1eaf<v:newlineChar /><tspan
+   x="19.709999"
+   dy="1.2em"
+   class="st13"
+   id="tspan7172"
+   style="font-size:12.00012016px">Packet assigned to queue 15</tspan></text>
+		</g>
+	</g>
+</svg>
\ No newline at end of file
diff --git a/doc/guides/prog_guide/img/rss_queue_assign.svg b/doc/guides/prog_guide/img/rss_queue_assign.svg
new file mode 100644
index 0000000..d0eef8c
--- /dev/null
+++ b/doc/guides/prog_guide/img/rss_queue_assign.svg
@@ -0,0 +1,1454 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generated by Microsoft Visio, SVG Export rss_queue_assign.svg Page-6 -->
+
+<svg
+   xmlns:v="http://schemas.microsoft.com/visio/2003/SVGExtensions/"
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="5.377346in"
+   height="5.1358395in"
+   viewBox="0 0 387.16901 369.78098"
+   xml:space="preserve"
+   class="st11"
+   version="1.1"
+   id="svg5938"
+   sodipodi:docname="rss_queue_assign.svg"
+   style="font-size:12px;overflow:visible;color-interpolation-filters:sRGB;fill:none;fill-rule:evenodd;stroke-linecap:square;stroke-miterlimit:3"
+   inkscape:version="0.92.3 (2405546, 2018-03-11)"><metadata
+   id="metadata5942"><rdf:RDF><cc:Work
+       rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+         rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><sodipodi:namedview
+   pagecolor="#ffffff"
+   bordercolor="#666666"
+   borderopacity="1"
+   objecttolerance="10"
+   gridtolerance="10"
+   guidetolerance="10"
+   inkscape:pageopacity="0"
+   inkscape:pageshadow="2"
+   inkscape:window-width="1920"
+   inkscape:window-height="1017"
+   id="namedview5940"
+   showgrid="false"
+   inkscape:zoom="0.4204831"
+   inkscape:cx="684.49886"
+   inkscape:cy="109.29466"
+   inkscape:window-x="-8"
+   inkscape:window-y="-8"
+   inkscape:window-maximized="1"
+   inkscape:current-layer="svg5938" />
+	<v:documentProperties
+   v:langID="6153"
+   v:metric="true"
+   v:viewMarkup="false">
+		<v:userDefs>
+			<v:ud
+   v:nameU="msvNoAutoConnect"
+   v:val="VT0(1):26" />
+		</v:userDefs>
+	</v:documentProperties>
+
+	<style
+   type="text/css"
+   id="style5574">
+	<![CDATA[
+		.st1 {fill:#ffffff;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.24;visibility:hidden}
+		.st2 {fill:#ffffff;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.24}
+		.st3 {fill:#000000;font-family:Calibri;font-size:0.666664em}
+		.st4 {fill:#c0c0c0;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.24}
+		.st5 {fill:none;stroke:none;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.75}
+		.st6 {font-size:1em}
+		.st7 {fill:#000000;font-family:Calibri;font-size:0.833336em}
+		.st8 {fill:#bfbfbf;stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.24}
+		.st9 {marker-end:url(#mrkr4-123);stroke:#000000;stroke-linecap:round;stroke-linejoin:round;stroke-width:0.239976}
+		.st10 {fill:#000000;fill-opacity:1;stroke:#000000;stroke-opacity:1;stroke-width:0.086948582161584}
+		.st11 {fill:none;fill-rule:evenodd;font-size:12px;overflow:visible;stroke-linecap:square;stroke-miterlimit:3}
+	]]>
+	</style>
+
+	<defs
+   id="Markers">
+		<g
+   id="lend4">
+			<path
+   d="M 2,1 0,0 2,-1 v 2"
+   style="stroke:none"
+   id="path5576"
+   inkscape:connector-curvature="0" />
+		</g>
+		<marker
+   id="mrkr4-123"
+   class="st10"
+   v:arrowType="4"
+   v:arrowSize="2"
+   v:setback="23.0021"
+   refX="-23.0021"
+   orient="auto"
+   markerUnits="strokeWidth"
+   overflow="visible"
+   style="overflow:visible;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.08694858;stroke-opacity:1">
+			<use
+   xlink:href="#lend4"
+   transform="scale(-11.50105)"
+   id="use5579"
+   x="0"
+   y="0"
+   width="100%"
+   height="100%" />
+		</marker>
+	</defs>
+	<g
+   v:mID="10"
+   v:index="6"
+   v:groupContext="foregroundPage"
+   id="g5936"
+   transform="translate(-58.840987,-97.651017)">
+		<title
+   id="title5583">Page-6</title>
+		<v:pageProperties
+   v:drawingScale="0.0393701"
+   v:pageScale="0.0393701"
+   v:drawingUnits="24"
+   v:shadowOffsetX="8.50394"
+   v:shadowOffsetY="-8.50394" />
+		<v:layer
+   v:name="Connector"
+   v:index="0" />
+		<g
+   id="group1000-1"
+   transform="translate(58.9606,-616.535)"
+   v:mID="1000"
+   v:groupContext="group">
+			<v:userDefs>
+				<v:ud
+   v:nameU="visVersion"
+   v:val="VT0(15):26" />
+			</v:userDefs>
+			<title
+   id="title5585">Array.1000</title>
+			<g
+   id="shape1001-2"
+   v:mID="1001"
+   v:groupContext="shape"
+   transform="translate(326.161)">
+				<title
+   id="title5587">Sheet.1001</title>
+				<rect
+   x="0"
+   y="827.71698"
+   width="46.594501"
+   height="14.1732"
+   class="st1"
+   id="rect5589"
+   style="visibility:hidden;fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1002-4"
+   v:mID="1002"
+   v:groupContext="shape"
+   transform="translate(279.567)">
+				<title
+   id="title5592">Sheet.1002</title>
+				<rect
+   x="0"
+   y="827.71698"
+   width="46.594501"
+   height="14.1732"
+   class="st1"
+   id="rect5594"
+   style="visibility:hidden;fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1003-6"
+   v:mID="1003"
+   v:groupContext="shape"
+   transform="translate(232.972)">
+				<title
+   id="title5597">Sheet.1003</title>
+				<rect
+   x="0"
+   y="827.71698"
+   width="46.594501"
+   height="14.1732"
+   class="st1"
+   id="rect5599"
+   style="visibility:hidden;fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1004-8"
+   v:mID="1004"
+   v:groupContext="shape"
+   transform="translate(186.378)">
+				<title
+   id="title5602">Sheet.1004</title>
+				<rect
+   x="0"
+   y="827.71698"
+   width="46.594501"
+   height="14.1732"
+   class="st1"
+   id="rect5604"
+   style="visibility:hidden;fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1005-10"
+   v:mID="1005"
+   v:groupContext="shape"
+   transform="translate(139.783)">
+				<title
+   id="title5607">Sheet.1005</title>
+				<rect
+   x="0"
+   y="827.71698"
+   width="46.594501"
+   height="14.1732"
+   class="st1"
+   id="rect5609"
+   style="visibility:hidden;fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1006-12"
+   v:mID="1006"
+   v:groupContext="shape"
+   transform="translate(93.189)">
+				<title
+   id="title5612">Sheet.1006</title>
+				<desc
+   id="desc5614">Src/Dst ports</desc>
+				<v:textBlock
+   v:margins="rect(1,1,1,1)"
+   v:tabSpace="42.5197" />
+				<v:textRect
+   cx="23.2972"
+   cy="834.803"
+   width="46.6"
+   height="14.1732" />
+				<rect
+   x="0"
+   y="827.71698"
+   width="46.594501"
+   height="14.1732"
+   class="st2"
+   id="rect5616"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+				<text
+   x="2.05"
+   y="837.20001"
+   class="st3"
+   v:langID="6153"
+   id="text5618"
+   style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />Src/Dst ports</text>
+			</g>
+			<g
+   id="shape1007-15"
+   v:mID="1007"
+   v:groupContext="shape"
+   transform="translate(46.5945)">
+				<title
+   id="title5621">Sheet.1007</title>
+				<desc
+   id="desc5623">Dst_ip</desc>
+				<v:textBlock
+   v:margins="rect(1,1,1,1)"
+   v:tabSpace="42.5197" />
+				<v:textRect
+   cx="23.2972"
+   cy="834.803"
+   width="46.6"
+   height="14.1732" />
+				<rect
+   x="0"
+   y="827.71698"
+   width="46.594501"
+   height="14.1732"
+   class="st2"
+   id="rect5625"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+				<text
+   x="12.92"
+   y="837.20001"
+   class="st3"
+   v:langID="6153"
+   id="text5627"
+   style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />Dst_ip</text>
+			</g>
+			<g
+   id="shape1008-18"
+   v:mID="1008"
+   v:groupContext="shape">
+				<title
+   id="title5630">Sheet.1008</title>
+				<desc
+   id="desc5632">Src_ip</desc>
+				<v:textBlock
+   v:margins="rect(1,1,1,1)"
+   v:tabSpace="42.5197" />
+				<v:textRect
+   cx="23.2972"
+   cy="834.803"
+   width="46.6"
+   height="14.1732" />
+				<rect
+   x="0"
+   y="827.71698"
+   width="46.594501"
+   height="14.1732"
+   class="st2"
+   id="rect5634"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+				<text
+   x="13.36"
+   y="837.20001"
+   class="st3"
+   v:langID="6153"
+   id="text5636"
+   style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />Src_ip</text>
+			</g>
+		</g>
+		<g
+   id="group1009-21"
+   transform="translate(58.9606,-704.359)"
+   v:mID="1009"
+   v:groupContext="group">
+			<v:userDefs>
+				<v:ud
+   v:nameU="visVersion"
+   v:val="VT0(15):26" />
+			</v:userDefs>
+			<title
+   id="title5640">Data block.1009</title>
+			<desc
+   id="desc5642">Received Packet Data</desc>
+			<g
+   id="shape1010-22"
+   v:mID="1010"
+   v:groupContext="shape"
+   transform="translate(263.622)">
+				<title
+   id="title5644">Sheet.1010</title>
+				<path
+   d="M 0,841.89 11.41,830.48 V 802.13 L 0,813.54 Z"
+   class="st4"
+   id="path5646"
+   inkscape:connector-curvature="0"
+   style="fill:#c0c0c0;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1011-24"
+   v:mID="1011"
+   v:groupContext="shape"
+   transform="translate(0,-28.3465)">
+				<title
+   id="title5649">Sheet.1011</title>
+				<path
+   d="m 0,841.89 h 263.62 l 11.41,-11.41 H 11.41 Z"
+   class="st4"
+   id="path5651"
+   inkscape:connector-curvature="0"
+   style="fill:#c0c0c0;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1012-26"
+   v:mID="1012"
+   v:groupContext="shape">
+				<title
+   id="title5654">Sheet.1012</title>
+				<rect
+   x="0"
+   y="813.54303"
+   width="263.62201"
+   height="28.3465"
+   class="st2"
+   id="rect5656"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1009-28"
+   v:mID="1009"
+   v:groupContext="groupContent">
+				<v:textBlock
+   v:margins="rect(0,0,0,0)"
+   v:tabSpace="42.5197" />
+				<v:textRect
+   cx="131.811"
+   cy="827.717"
+   width="263.63"
+   height="28.3465" />
+				<text
+   x="96.889999"
+   y="830.12"
+   class="st3"
+   v:langID="6153"
+   id="text5659"
+   style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />Received Packet Data</text>
+			</g>
+		</g>
+		<g
+   id="shape1013-30"
+   v:mID="1013"
+   v:groupContext="shape"
+   transform="rotate(-89.8898,-254.59128,465.04725)">
+			<title
+   id="title5663">Simple Arrow.1013</title>
+			<v:userDefs>
+				<v:ud
+   v:nameU="visVersion"
+   v:val="VT0(15):26" />
+				<v:ud
+   v:nameU="ArrowType"
+   v:prompt=""
+   v:val="VT0(2):26" />
+			</v:userDefs>
+			<path
+   d="m 0,841.89 12.6,-4.25 v 2.12 h 61.1 v 2.13 2.12 H 12.6 v 2.13 z"
+   class="st2"
+   id="path5665"
+   inkscape:connector-curvature="0"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+		</g>
+		<g
+   id="shape1014-32"
+   v:mID="1014"
+   v:groupContext="shape"
+   transform="translate(67.829,-663.024)">
+			<title
+   id="title5668">Sheet.1014</title>
+			<desc
+   id="desc5670">Parser extracts required fields</desc>
+			<v:textBlock
+   v:margins="rect(4,4,4,4)"
+   v:tabSpace="42.5197" />
+			<v:textRect
+   cx="28.3465"
+   cy="838.772"
+   width="56.7"
+   height="6.23622" />
+			<rect
+   x="0"
+   y="835.65399"
+   width="56.692902"
+   height="6.2362199"
+   class="st5"
+   id="rect5672"
+   style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" />
+			<text
+   x="4.1500001"
+   y="836.37"
+   class="st3"
+   v:langID="6153"
+   id="text5676"
+   style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />Parser extracts <tspan
+   x="4.7399998"
+   dy="1.2em"
+   class="st6"
+   id="tspan5674"
+   style="font-size:7.99996805px">required fields</tspan></text>
+		</g>
+		<g
+   id="shape1015-36"
+   v:mID="1015"
+   v:groupContext="shape"
+   transform="translate(58.9606,-600.945)">
+			<title
+   id="title5679">Sheet.1015</title>
+			<desc
+   id="desc5681">tuple</desc>
+			<v:textBlock
+   v:margins="rect(4,4,4,4)"
+   v:tabSpace="42.5197" />
+			<v:textRect
+   cx="20.2835"
+   cy="834.803"
+   width="40.57"
+   height="14.1732" />
+			<rect
+   x="0"
+   y="827.71698"
+   width="40.566898"
+   height="14.1732"
+   class="st5"
+   id="rect5683"
+   style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" />
+			<text
+   x="11.83"
+   y="837.20001"
+   class="st3"
+   v:langID="6153"
+   id="text5685"
+   style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />tuple</text>
+		</g>
+		<g
+   id="shape1016-39"
+   v:mID="1016"
+   v:groupContext="shape"
+   transform="rotate(-89.8898,-210.64069,509.05539)">
+			<title
+   id="title5688">Simple Arrow.1016</title>
+			<v:userDefs>
+				<v:ud
+   v:nameU="visVersion"
+   v:val="VT0(15):26" />
+				<v:ud
+   v:nameU="ArrowType"
+   v:prompt=""
+   v:val="VT0(2):26" />
+			</v:userDefs>
+			<path
+   d="m 0,841.89 12.6,-4.25 v 2.12 h 61.1 v 2.13 2.12 H 12.6 v 2.13 z"
+   class="st2"
+   id="path5690"
+   inkscape:connector-curvature="0"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+		</g>
+		<g
+   id="shape1017-41"
+   v:mID="1017"
+   v:groupContext="shape"
+   transform="translate(77.3858,-518.74)">
+			<title
+   id="title5693">Rectangle.1017</title>
+			<desc
+   id="desc5695">Toeplitz hash function</desc>
+			<v:userDefs>
+				<v:ud
+   v:nameU="visVersion"
+   v:val="VT0(15):26" />
+			</v:userDefs>
+			<v:textBlock
+   v:margins="rect(4,4,4,4)"
+   v:tabSpace="42.5197" />
+			<v:textRect
+   cx="45.3543"
+   cy="830.551"
+   width="90.71"
+   height="22.6772" />
+			<rect
+   x="0"
+   y="819.21301"
+   width="90.708702"
+   height="22.6772"
+   class="st2"
+   id="rect5697"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			<text
+   x="18.49"
+   y="827.54999"
+   class="st7"
+   v:langID="6153"
+   id="text5701"
+   style="font-size:10.00003242px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />Toeplitz hash <tspan
+   x="28.370001"
+   dy="1.2em"
+   class="st6"
+   id="tspan5699"
+   style="font-size:10.00003242px">function</tspan></text>
+		</g>
+		<g
+   id="shape1018-45"
+   v:mID="1018"
+   v:groupContext="shape"
+   transform="rotate(-89.8898,-161.64905,557.95289)">
+			<title
+   id="title5704">Simple Arrow.1018</title>
+			<v:userDefs>
+				<v:ud
+   v:nameU="visVersion"
+   v:val="VT0(15):26" />
+				<v:ud
+   v:nameU="ArrowType"
+   v:prompt=""
+   v:val="VT0(2):26" />
+			</v:userDefs>
+			<path
+   d="m 0,841.89 12.6,-4.25 v 2.12 h 61.1 v 2.13 2.12 H 12.6 v 2.13 z"
+   class="st2"
+   id="path5706"
+   inkscape:connector-curvature="0"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+		</g>
+		<g
+   id="shape1019-47"
+   v:mID="1019"
+   v:groupContext="shape"
+   transform="translate(83.0551,-430.866)">
+			<title
+   id="title5709">Byte or variable.1019</title>
+			<v:userDefs>
+				<v:ud
+   v:nameU="visVersion"
+   v:val="VT0(15):26" />
+			</v:userDefs>
+			<rect
+   x="0"
+   y="827.71698"
+   width="90.708702"
+   height="14.1732"
+   class="st2"
+   id="rect5711"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+		</g>
+		<g
+   id="shape1020-49"
+   v:mID="1020"
+   v:groupContext="shape"
+   transform="translate(145.417,-430.866)">
+			<title
+   id="title5714">Byte or variable.1020</title>
+			<desc
+   id="desc5716">LSB</desc>
+			<v:userDefs>
+				<v:ud
+   v:nameU="visVersion"
+   v:val="VT0(15):26" />
+			</v:userDefs>
+			<v:textBlock
+   v:margins="rect(1,1,1,1)"
+   v:tabSpace="42.5197" />
+			<v:textRect
+   cx="14.1732"
+   cy="834.803"
+   width="28.35"
+   height="14.1732" />
+			<rect
+   x="0"
+   y="827.71698"
+   width="28.3465"
+   height="14.1732"
+   class="st8"
+   id="rect5718"
+   style="fill:#bfbfbf;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			<text
+   x="8.4799995"
+   y="837.20001"
+   class="st3"
+   v:langID="6153"
+   id="text5720"
+   style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />LSB</text>
+		</g>
+		<g
+   id="shape1021-52"
+   v:mID="1021"
+   v:groupContext="shape"
+   transform="translate(92.6929,-419.528)">
+			<title
+   id="title5723">Sheet.1021</title>
+			<desc
+   id="desc5725">32-bit hash value</desc>
+			<v:textBlock
+   v:margins="rect(4,4,4,4)"
+   v:tabSpace="42.5197" />
+			<v:textRect
+   cx="35.7165"
+   cy="837.638"
+   width="71.44"
+   height="8.50394" />
+			<rect
+   x="0"
+   y="833.38599"
+   width="71.433098"
+   height="8.5039396"
+   class="st5"
+   id="rect5727"
+   style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" />
+			<text
+   x="7.8499999"
+   y="840.03998"
+   class="st3"
+   v:langID="6153"
+   id="text5729"
+   style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />32-bit hash value</text>
+		</g>
+		<g
+   id="shape1022-55"
+   v:mID="1022"
+   v:groupContext="shape"
+   transform="rotate(179.653,118.72832,623.08024)">
+			<title
+   id="title5732">Simple Arrow.1022</title>
+			<v:userDefs>
+				<v:ud
+   v:nameU="visVersion"
+   v:val="VT0(15):26" />
+				<v:ud
+   v:nameU="ArrowType"
+   v:prompt=""
+   v:val="VT0(2):26" />
+			</v:userDefs>
+			<path
+   d="m 0,841.89 12.6,-4.25 v 2.12 h 49.76 v 2.13 2.12 H 12.6 v 2.13 z"
+   class="st2"
+   id="path5734"
+   inkscape:connector-curvature="0"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+		</g>
+		<g
+   id="group1023-57"
+   transform="translate(240.378,-374.578)"
+   v:mID="1023"
+   v:groupContext="group">
+			<v:userDefs>
+				<v:ud
+   v:nameU="visVersion"
+   v:val="VT0(15):26" />
+			</v:userDefs>
+			<title
+   id="title5737">Stack or heap.1023</title>
+			<g
+   id="shape1024-58"
+   v:mID="1024"
+   v:groupContext="shape"
+   transform="translate(0,-270)">
+				<title
+   id="title5739">Sheet.1024</title>
+				<rect
+   x="0"
+   y="823.89001"
+   width="70.866096"
+   height="18"
+   class="st1"
+   id="rect5741"
+   style="visibility:hidden;fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1025-60"
+   v:mID="1025"
+   v:groupContext="shape"
+   transform="translate(0,-252)">
+				<title
+   id="title5744">Sheet.1025</title>
+				<rect
+   x="0"
+   y="823.89001"
+   width="70.866096"
+   height="18"
+   class="st1"
+   id="rect5746"
+   style="visibility:hidden;fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1026-62"
+   v:mID="1026"
+   v:groupContext="shape"
+   transform="translate(0,-234)">
+				<title
+   id="title5749">Sheet.1026</title>
+				<rect
+   x="0"
+   y="823.89001"
+   width="70.866096"
+   height="18"
+   class="st1"
+   id="rect5751"
+   style="visibility:hidden;fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1027-64"
+   v:mID="1027"
+   v:groupContext="shape"
+   transform="translate(0,-216)">
+				<title
+   id="title5754">Sheet.1027</title>
+				<rect
+   x="0"
+   y="823.89001"
+   width="70.866096"
+   height="18"
+   class="st1"
+   id="rect5756"
+   style="visibility:hidden;fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1028-66"
+   v:mID="1028"
+   v:groupContext="shape"
+   transform="translate(0,-198)">
+				<title
+   id="title5759">Sheet.1028</title>
+				<rect
+   x="0"
+   y="823.89001"
+   width="70.866096"
+   height="18"
+   class="st1"
+   id="rect5761"
+   style="visibility:hidden;fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1029-68"
+   v:mID="1029"
+   v:groupContext="shape"
+   transform="translate(0,-180)">
+				<title
+   id="title5764">Sheet.1029</title>
+				<rect
+   x="0"
+   y="823.89001"
+   width="70.866096"
+   height="18"
+   class="st1"
+   id="rect5766"
+   style="visibility:hidden;fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1030-70"
+   v:mID="1030"
+   v:groupContext="shape"
+   transform="translate(0,-162)">
+				<title
+   id="title5769">Sheet.1030</title>
+				<rect
+   x="0"
+   y="823.89001"
+   width="70.866096"
+   height="18"
+   class="st1"
+   id="rect5771"
+   style="visibility:hidden;fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1031-72"
+   v:mID="1031"
+   v:groupContext="shape"
+   transform="translate(0,-144)">
+				<title
+   id="title5774">Sheet.1031</title>
+				<rect
+   x="0"
+   y="823.89001"
+   width="70.866096"
+   height="18"
+   class="st1"
+   id="rect5776"
+   style="visibility:hidden;fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1032-74"
+   v:mID="1032"
+   v:groupContext="shape"
+   transform="translate(0,-126)">
+				<title
+   id="title5779">Sheet.1032</title>
+				<rect
+   x="0"
+   y="823.89001"
+   width="70.866096"
+   height="18"
+   class="st1"
+   id="rect5781"
+   style="visibility:hidden;fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			</g>
+			<g
+   id="shape1033-76"
+   v:mID="1033"
+   v:groupContext="shape"
+   transform="translate(0,-108)">
+				<title
+   id="title5784">Sheet.1033</title>
+				<desc
+   id="desc5786">Q_idx_0</desc>
+				<v:textBlock
+   v:margins="rect(1,1,1,1)"
+   v:tabSpace="42.5197" />
+				<v:textRect
+   cx="35.4331"
+   cy="832.89"
+   width="70.87"
+   height="18" />
+				<rect
+   x="0"
+   y="823.89001"
+   width="70.866096"
+   height="18"
+   class="st2"
+   id="rect5788"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+				<text
+   x="21.98"
+   y="835.28998"
+   class="st3"
+   v:langID="6153"
+   id="text5790"
+   style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />Q_idx_0</text>
+			</g>
+			<g
+   id="shape1034-79"
+   v:mID="1034"
+   v:groupContext="shape"
+   transform="translate(0,-90)">
+				<title
+   id="title5793">Sheet.1034</title>
+				<desc
+   id="desc5795">Q_idx_1</desc>
+				<v:textBlock
+   v:margins="rect(1,1,1,1)"
+   v:tabSpace="42.5197" />
+				<v:textRect
+   cx="35.4331"
+   cy="832.89"
+   width="70.87"
+   height="18" />
+				<rect
+   x="0"
+   y="823.89001"
+   width="70.866096"
+   height="18"
+   class="st2"
+   id="rect5797"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+				<text
+   x="21.98"
+   y="835.28998"
+   class="st3"
+   v:langID="6153"
+   id="text5799"
+   style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />Q_idx_1</text>
+			</g>
+			<g
+   id="shape1035-82"
+   v:mID="1035"
+   v:groupContext="shape"
+   transform="translate(0,-72)">
+				<title
+   id="title5802">Sheet.1035</title>
+				<desc
+   id="desc5804">Q_idx_2</desc>
+				<v:textBlock
+   v:margins="rect(1,1,1,1)"
+   v:tabSpace="42.5197" />
+				<v:textRect
+   cx="35.4331"
+   cy="832.89"
+   width="70.87"
+   height="18" />
+				<rect
+   x="0"
+   y="823.89001"
+   width="70.866096"
+   height="18"
+   class="st2"
+   id="rect5806"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+				<text
+   x="21.98"
+   y="835.28998"
+   class="st3"
+   v:langID="6153"
+   id="text5808"
+   style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />Q_idx_2</text>
+			</g>
+			<g
+   id="shape1036-85"
+   v:mID="1036"
+   v:groupContext="shape"
+   transform="translate(0,-54)">
+				<title
+   id="title5811">Sheet.1036</title>
+				<desc
+   id="desc5813">Q_idx_3</desc>
+				<v:textBlock
+   v:margins="rect(1,1,1,1)"
+   v:tabSpace="42.5197" />
+				<v:textRect
+   cx="35.4331"
+   cy="832.89"
+   width="70.87"
+   height="18" />
+				<rect
+   x="0"
+   y="823.89001"
+   width="70.866096"
+   height="18"
+   class="st2"
+   id="rect5815"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+				<text
+   x="21.98"
+   y="835.28998"
+   class="st3"
+   v:langID="6153"
+   id="text5817"
+   style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />Q_idx_3</text>
+			</g>
+			<g
+   id="shape1037-88"
+   v:mID="1037"
+   v:groupContext="shape"
+   transform="translate(0,-36)">
+				<title
+   id="title5820">Sheet.1037</title>
+				<desc
+   id="desc5822">...</desc>
+				<v:textBlock
+   v:margins="rect(1,1,1,1)"
+   v:tabSpace="42.5197" />
+				<v:textRect
+   cx="35.4331"
+   cy="832.89"
+   width="70.87"
+   height="18" />
+				<rect
+   x="0"
+   y="823.89001"
+   width="70.866096"
+   height="18"
+   class="st2"
+   id="rect5824"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+				<text
+   x="32.400002"
+   y="835.28998"
+   class="st3"
+   v:langID="6153"
+   id="text5826"
+   style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />...</text>
+			</g>
+			<g
+   id="shape1038-91"
+   v:mID="1038"
+   v:groupContext="shape"
+   transform="translate(0,-18)">
+				<title
+   id="title5829">Sheet.1038</title>
+				<desc
+   id="desc5831">...</desc>
+				<v:textBlock
+   v:margins="rect(1,1,1,1)"
+   v:tabSpace="42.5197" />
+				<v:textRect
+   cx="35.4331"
+   cy="832.89"
+   width="70.87"
+   height="18" />
+				<rect
+   x="0"
+   y="823.89001"
+   width="70.866096"
+   height="18"
+   class="st2"
+   id="rect5833"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+				<text
+   x="32.400002"
+   y="835.28998"
+   class="st3"
+   v:langID="6153"
+   id="text5835"
+   style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />...</text>
+			</g>
+			<g
+   id="shape1039-94"
+   v:mID="1039"
+   v:groupContext="shape">
+				<title
+   id="title5838">Sheet.1039</title>
+				<desc
+   id="desc5840">Q_idx_n</desc>
+				<v:textBlock
+   v:margins="rect(1,1,1,1)"
+   v:tabSpace="42.5197" />
+				<v:textRect
+   cx="35.4331"
+   cy="832.89"
+   width="70.87"
+   height="18" />
+				<rect
+   x="0"
+   y="823.89001"
+   width="70.866096"
+   height="18"
+   class="st2"
+   id="rect5842"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+				<text
+   x="21.9"
+   y="835.28998"
+   class="st3"
+   v:langID="6153"
+   id="text5844"
+   style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />Q_idx_n</text>
+			</g>
+		</g>
+		<g
+   id="shape1040-97"
+   v:mID="1040"
+   v:groupContext="shape"
+   transform="translate(240.378,-504.668)">
+			<title
+   id="title5848">Sheet.1040</title>
+			<desc
+   id="desc5850">RSS Redirection Table</desc>
+			<v:textBlock
+   v:margins="rect(4,4,4,4)"
+   v:tabSpace="42.5197" />
+			<v:textRect
+   cx="35.4331"
+   cy="830.551"
+   width="70.87"
+   height="22.6772" />
+			<rect
+   x="0"
+   y="819.21301"
+   width="70.866096"
+   height="22.6772"
+   class="st5"
+   id="rect5852"
+   style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" />
+			<text
+   x="9.9499998"
+   y="828.15002"
+   class="st3"
+   v:langID="6153"
+   id="text5856"
+   style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />RSS Redirection <tspan
+   x="26.559999"
+   dy="1.2em"
+   class="st6"
+   id="tspan5854"
+   style="font-size:7.99996805px">Table</tspan></text>
+		</g>
+		<g
+   id="shape1041-101"
+   v:mID="1041"
+   v:groupContext="shape"
+   transform="translate(173.98,-445.189)">
+			<title
+   id="title5859">Sheet.1041</title>
+			<desc
+   id="desc5861">Hash LSB’s are used as an index in table</desc>
+			<v:textBlock
+   v:margins="rect(4,4,4,4)"
+   v:tabSpace="42.5197" />
+			<v:textRect
+   cx="28.2716"
+   cy="833.536"
+   width="56.55"
+   height="16.7079" />
+			<rect
+   x="0"
+   y="825.18201"
+   width="56.543201"
+   height="16.707899"
+   class="st5"
+   id="rect5863"
+   style="fill:none;stroke:none;stroke-width:0.75;stroke-linecap:round;stroke-linejoin:round" />
+			<text
+   x="4.8299999"
+   y="826.34003"
+   class="st3"
+   v:langID="6153"
+   id="text5869"
+   style="font-size:7.99996805px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />Hash LSB’s are <tspan
+   x="11.21"
+   dy="1.2em"
+   class="st6"
+   id="tspan5865"
+   style="font-size:7.99996805px">used as an </tspan><tspan
+   x="6.3299999"
+   dy="1.2em"
+   class="st6"
+   id="tspan5867"
+   style="font-size:7.99996805px">index in table</tspan></text>
+		</g>
+		<g
+   id="shape1043-106"
+   v:mID="1043"
+   v:groupContext="shape"
+   transform="translate(375.024,-707.826)">
+			<title
+   id="title5872">Rectangle.1043</title>
+			<desc
+   id="desc5874">CPU 0</desc>
+			<v:userDefs>
+				<v:ud
+   v:nameU="visVersion"
+   v:val="VT0(15):26" />
+			</v:userDefs>
+			<v:textBlock
+   v:margins="rect(4,4,4,4)"
+   v:tabSpace="42.5197" />
+			<v:textRect
+   cx="35.4331"
+   cy="824.965"
+   width="70.87"
+   height="33.8487" />
+			<rect
+   x="0"
+   y="808.04102"
+   width="70.866096"
+   height="33.848701"
+   class="st2"
+   id="rect5876"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			<text
+   x="23.309999"
+   y="827.96997"
+   class="st7"
+   v:langID="6153"
+   id="text5878"
+   style="font-size:10.00003242px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />CPU 0</text>
+		</g>
+		<g
+   id="shape1044-109"
+   v:mID="1044"
+   v:groupContext="shape"
+   transform="translate(375.024,-659.804)">
+			<title
+   id="title5881">Rectangle.1044</title>
+			<desc
+   id="desc5883">CPU 1</desc>
+			<v:userDefs>
+				<v:ud
+   v:nameU="visVersion"
+   v:val="VT0(15):26" />
+			</v:userDefs>
+			<v:textBlock
+   v:margins="rect(4,4,4,4)"
+   v:tabSpace="42.5197" />
+			<v:textRect
+   cx="35.4331"
+   cy="824.965"
+   width="70.87"
+   height="33.8487" />
+			<rect
+   x="0"
+   y="808.04102"
+   width="70.866096"
+   height="33.848701"
+   class="st2"
+   id="rect5885"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			<text
+   x="23.309999"
+   y="827.96997"
+   class="st7"
+   v:langID="6153"
+   id="text5887"
+   style="font-size:10.00003242px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />CPU 1</text>
+		</g>
+		<g
+   id="shape1045-112"
+   v:mID="1045"
+   v:groupContext="shape"
+   transform="translate(375.024,-604.696)">
+			<title
+   id="title5890">Rectangle.1045</title>
+			<desc
+   id="desc5892">CPU 2</desc>
+			<v:userDefs>
+				<v:ud
+   v:nameU="visVersion"
+   v:val="VT0(15):26" />
+			</v:userDefs>
+			<v:textBlock
+   v:margins="rect(4,4,4,4)"
+   v:tabSpace="42.5197" />
+			<v:textRect
+   cx="35.4331"
+   cy="824.965"
+   width="70.87"
+   height="33.8487" />
+			<rect
+   x="0"
+   y="808.04102"
+   width="70.866096"
+   height="33.848701"
+   class="st2"
+   id="rect5894"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			<text
+   x="23.309999"
+   y="827.96997"
+   class="st7"
+   v:langID="6153"
+   id="text5896"
+   style="font-size:10.00003242px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />CPU 2</text>
+		</g>
+		<g
+   id="shape1046-115"
+   v:mID="1046"
+   v:groupContext="shape"
+   transform="translate(375.024,-549.587)">
+			<title
+   id="title5899">Rectangle.1046</title>
+			<desc
+   id="desc5901">CPU 3</desc>
+			<v:userDefs>
+				<v:ud
+   v:nameU="visVersion"
+   v:val="VT0(15):26" />
+			</v:userDefs>
+			<v:textBlock
+   v:margins="rect(4,4,4,4)"
+   v:tabSpace="42.5197" />
+			<v:textRect
+   cx="35.4331"
+   cy="824.965"
+   width="70.87"
+   height="33.8487" />
+			<rect
+   x="0"
+   y="808.04102"
+   width="70.866096"
+   height="33.848701"
+   class="st2"
+   id="rect5903"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			<text
+   x="23.309999"
+   y="827.96997"
+   class="st7"
+   v:langID="6153"
+   id="text5905"
+   style="font-size:10.00003242px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />CPU 3</text>
+		</g>
+		<g
+   id="shape1047-118"
+   v:mID="1047"
+   v:groupContext="shape"
+   v:layerMember="0"
+   transform="translate(311.244,-437.578)">
+			<title
+   id="title5908">Dynamic connector.1047</title>
+			<v:userDefs>
+				<v:ud
+   v:nameU="visVersion"
+   v:val="VT0(15):26" />
+			</v:userDefs>
+			<path
+   d="M 0,841.89 H 41.06 V 712.96 h 17.2"
+   class="st9"
+   id="path5910"
+   inkscape:connector-curvature="0"
+   style="stroke:#000000;stroke-width:0.239976;stroke-linecap:round;stroke-linejoin:round;marker-end:url(#mrkr4-123)" />
+		</g>
+		<g
+   id="shape1048-124"
+   v:mID="1048"
+   v:groupContext="shape"
+   transform="translate(375.024,-494.479)">
+			<title
+   id="title5913">Rectangle.1048</title>
+			<desc
+   id="desc5915">CPU 4</desc>
+			<v:userDefs>
+				<v:ud
+   v:nameU="visVersion"
+   v:val="VT0(15):26" />
+			</v:userDefs>
+			<v:textBlock
+   v:margins="rect(4,4,4,4)"
+   v:tabSpace="42.5197" />
+			<v:textRect
+   cx="35.4331"
+   cy="824.965"
+   width="70.87"
+   height="33.8487" />
+			<rect
+   x="0"
+   y="808.04102"
+   width="70.866096"
+   height="33.848701"
+   class="st2"
+   id="rect5917"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			<text
+   x="23.309999"
+   y="827.96997"
+   class="st7"
+   v:langID="6153"
+   id="text5919"
+   style="font-size:10.00003242px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />CPU 4</text>
+		</g>
+		<g
+   id="shape1049-127"
+   v:mID="1049"
+   v:groupContext="shape"
+   transform="translate(375.024,-439.37)">
+			<title
+   id="title5922">Rectangle.1049</title>
+			<desc
+   id="desc5924">CPU 5</desc>
+			<v:userDefs>
+				<v:ud
+   v:nameU="visVersion"
+   v:val="VT0(15):26" />
+			</v:userDefs>
+			<v:textBlock
+   v:margins="rect(4,4,4,4)"
+   v:tabSpace="42.5197" />
+			<v:textRect
+   cx="35.4331"
+   cy="824.965"
+   width="70.87"
+   height="33.8487" />
+			<rect
+   x="0"
+   y="808.04102"
+   width="70.866096"
+   height="33.848701"
+   class="st2"
+   id="rect5926"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+			<text
+   x="23.309999"
+   y="827.96997"
+   class="st7"
+   v:langID="6153"
+   id="text5928"
+   style="font-size:10.00003242px;font-family:Calibri;fill:#000000"><v:paragraph
+   v:horizAlign="1" /><v:tabList />CPU 5</text>
+		</g>
+		<g
+   id="shape1050-130"
+   v:mID="1050"
+   v:groupContext="shape"
+   transform="rotate(-130.647,57.370743,472.48224)">
+			<title
+   id="title5931">Simple Arrow.1050</title>
+			<v:userDefs>
+				<v:ud
+   v:nameU="visVersion"
+   v:val="VT0(15):26" />
+				<v:ud
+   v:nameU="ArrowType"
+   v:prompt=""
+   v:val="VT0(2):26" />
+			</v:userDefs>
+			<path
+   d="m 0,841.89 12.6,-4.25 v 2.12 h 169.08 v 2.13 2.12 H 12.6 v 2.13 z"
+   class="st2"
+   id="path5933"
+   inkscape:connector-curvature="0"
+   style="fill:#ffffff;stroke:#000000;stroke-width:0.23999999;stroke-linecap:round;stroke-linejoin:round" />
+		</g>
+	</g>
+</svg>
\ No newline at end of file
diff --git a/doc/guides/prog_guide/index.rst b/doc/guides/prog_guide/index.rst
index 45c7dec..2dce507 100644
--- a/doc/guides/prog_guide/index.rst
+++ b/doc/guides/prog_guide/index.rst
@@ -32,6 +32,7 @@ Programmer's Guide
     link_bonding_poll_mode_drv_lib
     timer_lib
     hash_lib
+    toeplitz_hash_lib
     efd_lib
     member_lib
     lpm_lib
diff --git a/doc/guides/prog_guide/toeplitz_hash_lib.rst b/doc/guides/prog_guide/toeplitz_hash_lib.rst
new file mode 100644
index 0000000..fcaab6b
--- /dev/null
+++ b/doc/guides/prog_guide/toeplitz_hash_lib.rst
@@ -0,0 +1,289 @@
+..  SPDX-License-Identifier: BSD-3-Clause
+    Copyright(c) 2021 Intel Corporation.
+
+.. _Thash_Library:
+
+Toeplitz Hash Library
+=====================
+
+DPDK provides a Toeplitz Hash Library to calculate the Toeplitz hash function
+and to use its properties. The Toeplitz hash function is commonly used in a
+wide range of NICs to calculate the RSS hash sum to spread the traffic among
+the queues.
+
+.. _figure_rss_queue_assign:
+
+.. figure:: img/rss_queue_assign.*
+
+   RSS queue assignment example
+
+
+Toeplitz hash function API
+--------------------------
+
+There are two functions that provide calculation of the Toeplitz hash sum:
+
+* rte_softrss()
+
+* rte_softrss_be()
+
+Both of these functions take the parameters:
+
+* A pointer to the tuple, containing fields extracted from the packet.
+
+* A length of this tuple counted in double words.
+
+* A pointer to the RSS hash key corresponding to the one installed on the NIC.
+
+Both functions expect the tuple to be in "host" byte order and a multiple of 4
+bytes in length. The ``rte_softrss()`` function expects the ``rss_key`` to be
+exactly the same as the one installed on the NIC. The ``rte_softrss_be``
+function is a faster implementation, but it expects ``rss_key`` to be
+converted to the host byte order.
+
+Predictable RSS
+---------------
+
+In some usecases it is useful to have a way to find partial collisions of the
+Toeplitz hash function. In figure :numref:`figure_rss_queue_assign` only a few
+of the least significant bits (LSB) of the hash value are used to indicate an
+entry in the RSS Redirection Table (ReTa) and thus the index of the queue. So,
+in this case it would be useful to find another tuple whose hash has the same
+LSB's as the hash from the original tuple.
+
+For example:
+
+- In the case of SNAT (Source Network Address Translation) it is possible to
+  find a special source port number on translation so that the hash of
+  returning packets, of the given connection, will have desired LSB's.
+- In the case of MPLS (Multiprotocol Label Switching), if the MPLS tag is used
+  in the hash calculation, the Label Switching router can allocate a special
+  MPLS tag to bind an LSP (Label Switching Path) to a given queue. This method
+  can be used with the allocation of IPSec SPI, VXLan VNI, etc., to bind the
+  tunnel to the desired queue.
+- In the case of a TCP stack, a special source port could be chosen for
+  outgoing connections, such that the response packets will be assigned to the
+  desired queue.
+
+This functionality is provided by the API shown below. The API consists of 3
+parts:
+
+* Create the thash context.
+
+* Create the thash helper, associated with a context.
+
+* Use the helper run time to calculate the adjustable bits of the tuple to
+  ensure a collision.
+
+Thash context
+~~~~~~~~~~~~~
+
+The function ``rte_thash_init_ctx()`` initializes the context struct
+associated with a particular NIC or a set of NICs
+
+It expects:
+
+* The log2 value of the size of the RSS redirection table for the
+  corresponding NIC. It reflects the number of least significant bits of the
+  hash value to produce a collision for.
+
+* A predefined RSS hash key. This is optional, if ``NULL`` then a random key
+  will be initialized.
+
+* The length of the RSS hash key. This value is usually hardware/driver
+  specific and can be found in the NIC datasheet.
+
+* Optional flags, as shown below.
+
+Supported flags:
+
+* ``RTE_THASH_IGNORE_PERIOD_OVERFLOW`` - By default, and for security reasons,
+  the library prohibits generating a repeatable sequence in the hash key. This
+  flag disables such checking. The flag is mainly used for testing in the lab
+  to generate an RSS hash key with a uniform hash distribution, if the input
+  traffic also has a uniform distribution.
+
+* ``RTE_THASH_MINIMAL_SEQ`` - By default, the library generates a special bit
+  sequence in the hash key for all the bits of the subtuple. However, the
+  collision generation task requires only the ``log2(RETA_SZ)`` bits in the
+  subtuple. This flag forces the minimum bit sequence in the hash key to be
+  generated for the required ``log2(RETA_SZ)`` least significant bits of the
+  subtuple. The flag can be used in the case of a relatively large number of
+  helpers that may overlap with their corresponding bit sequences of RSS hash
+  keys.
+
+
+Thash helper
+~~~~~~~~~~~~
+
+The function ``rte_thash_add_helper()`` initializes the helper struct
+associated with a given context and a part of a target tuple of interest which
+could be altered to produce a hash collision. On success it writes a specially
+calculated bit sequence into the RSS hash key which is stored in the context
+and calculates a table with values to be XORed with a subtuple.
+
+It expects:
+
+* A pointer to the Thash context to be associated with.
+
+* A length of the subtuple to be modified. The length is counted in bits.
+
+* An offset of the subtuple to be modified from the beginning of the tuple. It
+  is also counted in bits.
+
+.. note::
+
+   Adding a helper changes the key stored in the corresponding context. So the
+   updated RSS hash key must be uploaded into the NIC after creating all the
+   required helpers.
+
+
+Calculation of the complementary bits to adjust the subtuple
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The ``rte_thash_get_complement()`` function returns a special bit sequence
+with length ``N = log2(rss_reta_sz)`` (for the ``rss_reta_sz`` provided at
+context initialization) to be xored with N least significant bits of the
+subtuple.
+
+It expects:
+
+* A corresponding helper created for a given subtuple of the tuple.
+
+* A hash value of the tuple we want to alter.
+
+* The desired LSB's of the hash value the user expects to have.
+
+After the returned bit sequence has been XORed with the subtuple, the resulted
+LSB's of the new hash value, calculated from the altered tuple, will be the
+same as in ``desired_hash``.
+
+
+Adjust tuple API
+~~~~~~~~~~~~~~~~~
+
+The ``rte_thash_get_complement()`` function is a user-friendly wrapper around
+a number of other functions. It alters a passed tuple to meet the above
+mentioned requirements around the desired hash LSB's.
+
+It expects:
+
+* A Thash context and helper.
+
+* A pointer to the tuple to be changed.
+
+* The length of the tuple.
+
+* A callback function and its userdata to check the tuple after it has been
+  changed.
+
+* The number of attempts to change the tuple. Basically, it makes sense if
+  there is a callback and a limit on the number of attempts to change the
+  tuple, if the callback function returns an error.
+
+
+Usecase example
+---------------
+
+There could be a number of different usecases, such as NAT, TCP stack, MPLS
+tag allocation, etc. In the following we will consider a SNAT application.
+
+Packets of a single bidirectional flow belonging to different directions can
+end up being assigned to different queues and thus processed by different
+lcores, as shown in :numref:`figure_predictable_snat_1`:
+
+.. _figure_predictable_snat_1:
+
+.. figure:: img/predictable_snat_1.*
+
+   Bidirectional flow packets distribution in general
+
+That leads to a situation where the same packet flow can be shared between two
+cores. Such a situation is not ideal from a performance perspective and
+requires extra synchronization efforts that might lead to various performance
+penalties, for example:
+
+* The connections table is global so locking/RCU on the flow insertion/removal
+  is required.
+
+* Connection metadata must be protected to avoid race conditions.
+
+* More cache pressure if a single connection metadata is kept in different
+  L1/L2 caches of a different CPU core.
+
+* Cache pressure/less cache locality on packet handover to the different cores.
+
+We can avoid all these penalties if it can be guaranteed that packets
+belonging to one bidirectional flow will be assigned to the same queue, as
+shown in :numref:`figure_predictable_snat_2`:
+
+.. _figure_predictable_snat_2:
+
+.. figure:: img/predictable_snat_2.*
+
+   Bidirectional flow packets distribution with predictable RSS
+
+
+To achieve this in a SNAT scenario it is possible to choose a source port not
+randomly, but using the predictable RSS library to produce a partial hash
+collision. This is shown in the code below.
+
+.. code-block:: c
+
+   int key_len = 40; /* The default Niantic RSS key length. */
+
+   /** The default Niantic RSS reta size = 2^7 entries, LSBs of hash value are
+    *  used as an indexes in RSS ReTa. */
+   int reta_sz = 7;
+   int ret;
+   struct rte_thash_ctx *ctx;
+
+   uint8_t initial_key[key_len] = {0}; /* Default empty key. */
+
+   /* Create and initialize a new thash context. */
+   ctx = rte_thash_init_ctx("SNAT", key_len, reta_sz, initial_key, 0);
+
+   /** Add a helper and specify the variable tuple part and its length. In the
+    *  SNAT case we want to choose a new source port on SNAT translation in a
+    *  way that the reverse tuple will have the same LSBs as the original
+    *  direction tuple so that the selected source port will be the
+    *  destination port on reply.
+    */
+   ret = rte_thash_add_helper(ctx, "snat", sizeof(uint16_t) * 8,
+                              offsetof(union rte_thash_tuple, v4.dport) * 8);
+
+   if (ret != 0)
+       return ret;
+
+   /* Get handler of the required helper. */
+   struct rte_thash_subtuple_helper *h = rte_thash_get_helper(ctx, "snat");
+
+   /** After calling rte_thash_add_helper() the initial_key passed on ctx
+    *  creation has been changed so we get the new one.
+    */
+   uint8_t *new_key = rte_thash_get_key(ctx);
+
+   union rte_thash_tuple tuple, rev_tuple;
+
+   /* A complete tuple from the packet. */
+   complete_tuple(mbuf, &tuple);
+
+   /* Calculate the RSS hash or get it from mbuf->hash.rss. */
+   uint32_t orig_hash = rte_softrss((uint32_t *)&tuple, RTE_THASH_V4_L4_LEN, new_key);
+
+   /** Complete the reverse tuple by translating the SRC address and swapping
+    *  src and dst addresses and ports.
+    */
+   get_rev_tuple(&rev_tuple, &tuple, new_ip);
+
+   /* Calculate the expected rss hash for the reverse tuple. */
+   uint32_t rev_hash = rte_softrss((uint32_t *)&rev_tuple, RTE_THASH_V4_L4_LEN, new_key);
+
+   /* Get the adjustment bits for the src port to get a new port. */
+   uint32_t adj = rte_thash_get_compliment(h, rev_hash, orig_hash);
+
+   /* Adjust the source port bits. */
+   uint16_t new_sport = tuple.v4.sport ^ adj;
+
+   /* Make an actual packet translation. */
+   do_snat(mbuf, new_ip, new_sport);
diff --git a/doc/guides/rel_notes/release_21_05.rst b/doc/guides/rel_notes/release_21_05.rst
index 1c2e093..3b14822 100644
--- a/doc/guides/rel_notes/release_21_05.rst
+++ b/doc/guides/rel_notes/release_21_05.rst
@@ -203,6 +203,12 @@ New Features
     the events across multiple stages.
   * This also reduced the scheduling overhead on a event device.
 
+* **Added Predictable RSS functionality to the Toeplitz hash library.**
+
+  This feature provides functionality for finding collisions of the Toeplitz
+  hash function - the hash function used in NIC's to spread the traffic
+  among the queues. It can be used to get predictable mapping of the flows.
+
 * **Updated testpmd.**
 
   * Added a command line option to configure forced speed for Ethernet port.
-- 
2.7.4


^ permalink raw reply	[flat|nested] 45+ messages in thread

* [dpdk-dev] [PATCH v5 5/5] maintainers: claim maintainership of the hash library
  2021-04-13 13:19     ` [dpdk-dev] [PATCH v4 " Vladimir Medvedkin
                         ` (5 preceding siblings ...)
  2021-04-19 15:59       ` [dpdk-dev] [PATCH v5 4/5] doc: add thash documentation Vladimir Medvedkin
@ 2021-04-19 15:59       ` Vladimir Medvedkin
  6 siblings, 0 replies; 45+ messages in thread
From: Vladimir Medvedkin @ 2021-04-19 15:59 UTC (permalink / raw)
  To: dev
  Cc: konstantin.ananyev, andrey.chilikin, ray.kinsella, yipeng1.wang,
	sameh.gobriel, bruce.richardson

Claim maintainership of the hash library.

Signed-off-by: Vladimir Medvedkin <vladimir.medvedkin@intel.com>
---
 MAINTAINERS | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 2550d95..35a87e8 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1430,8 +1430,10 @@ Hashes
 M: Yipeng Wang <yipeng1.wang@intel.com>
 M: Sameh Gobriel <sameh.gobriel@intel.com>
 M: Bruce Richardson <bruce.richardson@intel.com>
+M: Vladimir Medvedkin <vladimir.medvedkin@intel.com>
 F: lib/librte_hash/
 F: doc/guides/prog_guide/hash_lib.rst
+F: doc/guides/prog_guide/toeplitz_hash_lib.rst
 F: app/test/test_*hash*
 F: app/test/test_func_reentrancy.c
 
-- 
2.7.4


^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [dpdk-dev] [PATCH v5 4/5] doc: add thash documentation
  2021-04-19 15:59       ` [dpdk-dev] [PATCH v5 4/5] doc: add thash documentation Vladimir Medvedkin
@ 2021-04-20 11:25         ` Mcnamara, John
  0 siblings, 0 replies; 45+ messages in thread
From: Mcnamara, John @ 2021-04-20 11:25 UTC (permalink / raw)
  To: Medvedkin, Vladimir, dev
  Cc: Ananyev, Konstantin, Chilikin, Andrey, Kinsella, Ray, Wang,
	Yipeng1, Gobriel, Sameh, Richardson, Bruce



> -----Original Message-----
> From: Medvedkin, Vladimir <vladimir.medvedkin@intel.com>
> Sent: Monday, April 19, 2021 5:00 PM
> To: dev@dpdk.org
> Cc: Ananyev, Konstantin <konstantin.ananyev@intel.com>; Chilikin, Andrey
> <andrey.chilikin@intel.com>; Kinsella, Ray <ray.kinsella@intel.com>; Wang,
> Yipeng1 <yipeng1.wang@intel.com>; Gobriel, Sameh
> <sameh.gobriel@intel.com>; Richardson, Bruce <bruce.richardson@intel.com>;
> Mcnamara, John <john.mcnamara@intel.com>
> Subject: [PATCH v5 4/5] doc: add thash documentation
> 
> Adds documentation for the Toeplitz hash library
> 
> Signed-off-by: Vladimir Medvedkin <vladimir.medvedkin@intel.com>
> Reviewed-by: Konstantin Ananyev <konstantin.ananyev@intel.com>
> Reviewed-by: John McNamara <john.mcnamara@intel.com>



> +Thash context
> +~~~~~~~~~~~~~
> +
> +The function ``rte_thash_init_ctx()`` initializes the context struct
> +associated with a particular NIC or a set of NICs
> +
> +It expects:

Missing full spot at the end of the line. Could also be merged with the
following line:

> +The function ``rte_thash_init_ctx()`` initializes the context struct
> +associated with a particular NIC or a set of NICs. It expects:

Apart from that, it looks good, so:

Acked-by: John McNamara <john.mcnamara@intel.com>





^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [dpdk-dev] [PATCH v5 0/5] Predictable RSS feature
  2021-04-19 15:59       ` [dpdk-dev] [PATCH v5 0/5] " Vladimir Medvedkin
@ 2021-04-20 21:31         ` Thomas Monjalon
  0 siblings, 0 replies; 45+ messages in thread
From: Thomas Monjalon @ 2021-04-20 21:31 UTC (permalink / raw)
  To: Vladimir Medvedkin
  Cc: dev, konstantin.ananyev, andrey.chilikin, ray.kinsella,
	yipeng1.wang, sameh.gobriel, bruce.richardson, john.mcnamara,
	david.marchand

19/04/2021 17:59, Vladimir Medvedkin:
> Vladimir Medvedkin (5):
>   hash: add predictable RSS API
>   hash: add predictable RSS implementation
>   test/hash: add additional thash tests
>   doc: add thash documentation
>   maintainers: claim maintainership of the hash library

I kept the doc of the old feature in a separate patch.
The rest is squashed.
I've fixed more details like sorting in .map, experimental banner, etc.

Applied



^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [dpdk-dev] [PATCH v5 3/5] test/hash: add additional thash tests
  2021-04-19 15:59       ` [dpdk-dev] [PATCH v5 3/5] test/hash: add additional thash tests Vladimir Medvedkin
@ 2021-04-29  9:13         ` David Marchand
  2021-04-29  9:17           ` Medvedkin, Vladimir
  0 siblings, 1 reply; 45+ messages in thread
From: David Marchand @ 2021-04-29  9:13 UTC (permalink / raw)
  To: Vladimir Medvedkin
  Cc: dev, Ananyev, Konstantin, Chilikin, Andrey, Kinsella, Ray, Wang,
	Yipeng1, Gobriel, Sameh, Bruce Richardson

Hello Vladimir,

On Mon, Apr 19, 2021 at 6:00 PM Vladimir Medvedkin
<vladimir.medvedkin@intel.com> wrote:
> +static int
> +test_adjust_tuple(void)
> +{
> +       struct rte_thash_ctx *ctx;
> +       struct rte_thash_subtuple_helper *h;
> +       const int key_len = 40;
> +       const uint8_t *new_key;
> +       uint8_t tuple[TUPLE_SZ];
> +       uint32_t tmp_tuple[TUPLE_SZ / sizeof(uint32_t)];
> +       uint32_t tuple_copy[TUPLE_SZ / sizeof(uint32_t)];
> +       uint32_t hash;
> +       int reta_sz = CHAR_BIT;
> +       int ret;
> +       unsigned int i, desired_value = rte_rand() & HASH_MSK(reta_sz);
> +
> +       memset(tuple, 0xab, TUPLE_SZ);
> +
> +       ctx = rte_thash_init_ctx("test", key_len, reta_sz, NULL, 0);
> +       RTE_TEST_ASSERT(ctx != NULL, "can not create thash ctx\n");
> +
> +       /*
> +        * set offset to be in the middle of a byte
> +        * set size of the subtuple to be 2 * rets_sz
> +        * to have the room for random bits
> +        */
> +       ret = rte_thash_add_helper(ctx, "test", reta_sz * 2,
> +               (5 * CHAR_BIT) + 4);
> +       RTE_TEST_ASSERT(ret == 0, "can not add helper, ret %d\n", ret);
> +
> +       new_key = rte_thash_get_key(ctx);
> +
> +       h = rte_thash_get_helper(ctx, "test");
> +       RTE_TEST_ASSERT(h != NULL, "can not find helper\n");
> +
> +       ret = rte_thash_adjust_tuple(ctx, h, tuple, TUPLE_SZ, desired_value,
> +               1, NULL, NULL);
> +       RTE_TEST_ASSERT(ret == 0, "can not adjust tuple, ret %d\n", ret);
> +
> +       for (i = 0; i < (TUPLE_SZ / 4); i++)
> +               tmp_tuple[i] =
> +                       rte_be_to_cpu_32(*(uint32_t *)&tuple[i * 4]);
> +
> +       hash = rte_softrss(tmp_tuple, TUPLE_SZ / 4, new_key);
> +       RTE_TEST_ASSERT((hash & HASH_MSK(reta_sz)) ==
> +               desired_value, "bad desired value\n");
> +
> +
> +       /* Pass previously calculated tuple to callback function */
> +       memcpy(tuple_copy, tuple, TUPLE_SZ);
> +
> +       memset(tuple, 0xab, TUPLE_SZ);
> +       ret = rte_thash_adjust_tuple(ctx, h, tuple, TUPLE_SZ, desired_value,
> +               1, cmp_tuple_eq, tuple_copy);
> +       RTE_TEST_ASSERT(ret == -EEXIST,
> +               "adjust tuple didn't indicate collision\n");
> +
> +       /*
> +        * Make the function to generate random bits into subtuple
> +        * after first adjustment attempt.
> +        */
> +       memset(tuple, 0xab, TUPLE_SZ);
> +       ret = rte_thash_adjust_tuple(ctx, h, tuple, TUPLE_SZ, desired_value,
> +               2, cmp_tuple_eq, tuple_copy);
> +       RTE_TEST_ASSERT(ret == 0, "can not adjust tuple, ret %d\n", ret);


This check failed at least once in the CI, for no obvious (for me) reason.
https://github.com/ovsrobot/dpdk/actions/runs/794713806

87/96 DPDK:fast-tests / thash_autotest        FAIL     0.17 s (exit
status 255 or signal 127 SIGinvalid)

--- command ---
DPDK_TEST='thash_autotest'
/home/runner/work/dpdk/dpdk/build/app/test/dpdk-test -l 0-1
--file-prefix=thash_autotest
--- stdout ---
RTE>>thash_autotest
 + ------------------------------------------------------- +
 + Test Suite : thash autotest
 + ------------------------------------------------------- +
 + TestCase [ 0] : test_toeplitz_hash_calc succeeded
 + TestCase [ 1] : test_create_invalid succeeded
 + TestCase [ 2] : test_multiple_create succeeded
 + TestCase [ 3] : test_free_null succeeded
 + TestCase [ 4] : test_add_invalid_helper succeeded
 + TestCase [ 5] : test_find_existing succeeded
 + TestCase [ 6] : test_get_helper succeeded
 + TestCase [ 7] : test_period_overflow succeeded
 + TestCase [ 8] : test_predictable_rss_min_seq succeeded
 + TestCase [ 9] : test_predictable_rss_multirange succeeded
 + TestCase [10] : test_adjust_tuple failed
 + ------------------------------------------------------- +
 + Test Suite Summary
 + Tests Total :       11
 + Tests Skipped :      0
 + Tests Executed :    11
 + Tests Unsupported:   0
 + Tests Passed :      10
 + Tests Failed :       1
 + ------------------------------------------------------- +
Test Failed
RTE>>
--- stderr ---
EAL: Detected 2 lcore(s)
EAL: Detected 1 NUMA nodes
EAL: Detected shared linkage of DPDK
EAL: Multi-process socket /var/run/dpdk/thash_autotest/mp_socket
EAL: Selected IOVA mode 'PA'
EAL: No available 1048576 kB hugepages reported
EAL: VFIO support initialized
APP: HPET is not enabled, using TSC as default timer
HASH: Can't add helper  due to conflict with existing helper second_range
HASH: Can't generate m-sequence due to period overflow
EAL: Test assert test_adjust_tuple line 559 failed: can not adjust
tuple, ret -17

-------



> +
> +       for (i = 0; i < (TUPLE_SZ / 4); i++)
> +               tmp_tuple[i] =
> +                       rte_be_to_cpu_32(*(uint32_t *)&tuple[i * 4]);
> +
> +       hash = rte_softrss(tmp_tuple, TUPLE_SZ / 4, new_key);
> +       RTE_TEST_ASSERT((hash & HASH_MSK(reta_sz)) ==
> +               desired_value, "bad desired value\n");
> +
> +       rte_thash_free_ctx(ctx);
> +
> +       return TEST_SUCCESS;
> +}


-- 
David Marchand


^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [dpdk-dev] [PATCH v5 3/5] test/hash: add additional thash tests
  2021-04-29  9:13         ` David Marchand
@ 2021-04-29  9:17           ` Medvedkin, Vladimir
  2021-04-29 18:45             ` Stanislaw Kardach
  0 siblings, 1 reply; 45+ messages in thread
From: Medvedkin, Vladimir @ 2021-04-29  9:17 UTC (permalink / raw)
  To: David Marchand
  Cc: dev, Ananyev, Konstantin, Chilikin, Andrey, Kinsella, Ray, Wang,
	Yipeng1, Gobriel, Sameh, Bruce Richardson

Hello David,

Oh, interesting, thanks for the report, I'll take a look!

On 29/04/2021 12:13, David Marchand wrote:
> Hello Vladimir,
> 
> On Mon, Apr 19, 2021 at 6:00 PM Vladimir Medvedkin
> <vladimir.medvedkin@intel.com> wrote:
>> +static int
>> +test_adjust_tuple(void)
>> +{
>> +       struct rte_thash_ctx *ctx;
>> +       struct rte_thash_subtuple_helper *h;
>> +       const int key_len = 40;
>> +       const uint8_t *new_key;
>> +       uint8_t tuple[TUPLE_SZ];
>> +       uint32_t tmp_tuple[TUPLE_SZ / sizeof(uint32_t)];
>> +       uint32_t tuple_copy[TUPLE_SZ / sizeof(uint32_t)];
>> +       uint32_t hash;
>> +       int reta_sz = CHAR_BIT;
>> +       int ret;
>> +       unsigned int i, desired_value = rte_rand() & HASH_MSK(reta_sz);
>> +
>> +       memset(tuple, 0xab, TUPLE_SZ);
>> +
>> +       ctx = rte_thash_init_ctx("test", key_len, reta_sz, NULL, 0);
>> +       RTE_TEST_ASSERT(ctx != NULL, "can not create thash ctx\n");
>> +
>> +       /*
>> +        * set offset to be in the middle of a byte
>> +        * set size of the subtuple to be 2 * rets_sz
>> +        * to have the room for random bits
>> +        */
>> +       ret = rte_thash_add_helper(ctx, "test", reta_sz * 2,
>> +               (5 * CHAR_BIT) + 4);
>> +       RTE_TEST_ASSERT(ret == 0, "can not add helper, ret %d\n", ret);
>> +
>> +       new_key = rte_thash_get_key(ctx);
>> +
>> +       h = rte_thash_get_helper(ctx, "test");
>> +       RTE_TEST_ASSERT(h != NULL, "can not find helper\n");
>> +
>> +       ret = rte_thash_adjust_tuple(ctx, h, tuple, TUPLE_SZ, desired_value,
>> +               1, NULL, NULL);
>> +       RTE_TEST_ASSERT(ret == 0, "can not adjust tuple, ret %d\n", ret);
>> +
>> +       for (i = 0; i < (TUPLE_SZ / 4); i++)
>> +               tmp_tuple[i] =
>> +                       rte_be_to_cpu_32(*(uint32_t *)&tuple[i * 4]);
>> +
>> +       hash = rte_softrss(tmp_tuple, TUPLE_SZ / 4, new_key);
>> +       RTE_TEST_ASSERT((hash & HASH_MSK(reta_sz)) ==
>> +               desired_value, "bad desired value\n");
>> +
>> +
>> +       /* Pass previously calculated tuple to callback function */
>> +       memcpy(tuple_copy, tuple, TUPLE_SZ);
>> +
>> +       memset(tuple, 0xab, TUPLE_SZ);
>> +       ret = rte_thash_adjust_tuple(ctx, h, tuple, TUPLE_SZ, desired_value,
>> +               1, cmp_tuple_eq, tuple_copy);
>> +       RTE_TEST_ASSERT(ret == -EEXIST,
>> +               "adjust tuple didn't indicate collision\n");
>> +
>> +       /*
>> +        * Make the function to generate random bits into subtuple
>> +        * after first adjustment attempt.
>> +        */
>> +       memset(tuple, 0xab, TUPLE_SZ);
>> +       ret = rte_thash_adjust_tuple(ctx, h, tuple, TUPLE_SZ, desired_value,
>> +               2, cmp_tuple_eq, tuple_copy);
>> +       RTE_TEST_ASSERT(ret == 0, "can not adjust tuple, ret %d\n", ret);
> 
> 
> This check failed at least once in the CI, for no obvious (for me) reason.
> https://github.com/ovsrobot/dpdk/actions/runs/794713806
> 
> 87/96 DPDK:fast-tests / thash_autotest        FAIL     0.17 s (exit
> status 255 or signal 127 SIGinvalid)
> 
> --- command ---
> DPDK_TEST='thash_autotest'
> /home/runner/work/dpdk/dpdk/build/app/test/dpdk-test -l 0-1
> --file-prefix=thash_autotest
> --- stdout ---
> RTE>>thash_autotest
>   + ------------------------------------------------------- +
>   + Test Suite : thash autotest
>   + ------------------------------------------------------- +
>   + TestCase [ 0] : test_toeplitz_hash_calc succeeded
>   + TestCase [ 1] : test_create_invalid succeeded
>   + TestCase [ 2] : test_multiple_create succeeded
>   + TestCase [ 3] : test_free_null succeeded
>   + TestCase [ 4] : test_add_invalid_helper succeeded
>   + TestCase [ 5] : test_find_existing succeeded
>   + TestCase [ 6] : test_get_helper succeeded
>   + TestCase [ 7] : test_period_overflow succeeded
>   + TestCase [ 8] : test_predictable_rss_min_seq succeeded
>   + TestCase [ 9] : test_predictable_rss_multirange succeeded
>   + TestCase [10] : test_adjust_tuple failed
>   + ------------------------------------------------------- +
>   + Test Suite Summary
>   + Tests Total :       11
>   + Tests Skipped :      0
>   + Tests Executed :    11
>   + Tests Unsupported:   0
>   + Tests Passed :      10
>   + Tests Failed :       1
>   + ------------------------------------------------------- +
> Test Failed
> RTE>>
> --- stderr ---
> EAL: Detected 2 lcore(s)
> EAL: Detected 1 NUMA nodes
> EAL: Detected shared linkage of DPDK
> EAL: Multi-process socket /var/run/dpdk/thash_autotest/mp_socket
> EAL: Selected IOVA mode 'PA'
> EAL: No available 1048576 kB hugepages reported
> EAL: VFIO support initialized
> APP: HPET is not enabled, using TSC as default timer
> HASH: Can't add helper  due to conflict with existing helper second_range
> HASH: Can't generate m-sequence due to period overflow
> EAL: Test assert test_adjust_tuple line 559 failed: can not adjust
> tuple, ret -17
> 
> -------
> 
> 
> 
>> +
>> +       for (i = 0; i < (TUPLE_SZ / 4); i++)
>> +               tmp_tuple[i] =
>> +                       rte_be_to_cpu_32(*(uint32_t *)&tuple[i * 4]);
>> +
>> +       hash = rte_softrss(tmp_tuple, TUPLE_SZ / 4, new_key);
>> +       RTE_TEST_ASSERT((hash & HASH_MSK(reta_sz)) ==
>> +               desired_value, "bad desired value\n");
>> +
>> +       rte_thash_free_ctx(ctx);
>> +
>> +       return TEST_SUCCESS;
>> +}
> 
> 

-- 
Regards,
Vladimir

^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [dpdk-dev] [PATCH v5 3/5] test/hash: add additional thash tests
  2021-04-29  9:17           ` Medvedkin, Vladimir
@ 2021-04-29 18:45             ` Stanislaw Kardach
  2021-05-04 14:06               ` Medvedkin, Vladimir
  0 siblings, 1 reply; 45+ messages in thread
From: Stanislaw Kardach @ 2021-04-29 18:45 UTC (permalink / raw)
  To: Medvedkin, Vladimir
  Cc: David Marchand, dev, Ananyev, Konstantin, Chilikin, Andrey,
	Kinsella, Ray, Wang, Yipeng1, Gobriel, Sameh, Bruce Richardson

On Thu, Apr 29, 2021 at 12:17:08PM +0300, Medvedkin, Vladimir wrote:
<snip>
> > Test Failed
> > RTE>>
> > --- stderr ---
> > EAL: Detected 2 lcore(s)
> > EAL: Detected 1 NUMA nodes
> > EAL: Detected shared linkage of DPDK
> > EAL: Multi-process socket /var/run/dpdk/thash_autotest/mp_socket
> > EAL: Selected IOVA mode 'PA'
> > EAL: No available 1048576 kB hugepages reported
> > EAL: VFIO support initialized
> > APP: HPET is not enabled, using TSC as default timer
> > HASH: Can't add helper  due to conflict with existing helper second_range
> > HASH: Can't generate m-sequence due to period overflow
> > EAL: Test assert test_adjust_tuple line 559 failed: can not adjust
> > tuple, ret -17
> > 

I can see the same issue on my side. Happening randomly, more often on a
RISC-V target than on my laptop (i5-10210U). Though the reproduction
seems to be a lot of patience and the following:

  meson test --repeat 100000 DPDK:fast-tests / thash_autotest

I wonder if it can be related to the desired_value in test_adjust_tuple
being a randomized value without setting the seed prior to the test?
I haven't analyzed the code in-depth but it seems that the
rte_thash_add_helper() also uses a random lfsr which is then used in the
subkey generation. Could this contribute to the randomness of the issue?

-- 
Best Regards,
Stanislaw Kardach

^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [dpdk-dev] [PATCH v5 3/5] test/hash: add additional thash tests
  2021-04-29 18:45             ` Stanislaw Kardach
@ 2021-05-04 14:06               ` Medvedkin, Vladimir
  0 siblings, 0 replies; 45+ messages in thread
From: Medvedkin, Vladimir @ 2021-05-04 14:06 UTC (permalink / raw)
  To: Stanislaw Kardach
  Cc: David Marchand, dev, Ananyev, Konstantin, Chilikin, Andrey,
	Kinsella, Ray, Wang, Yipeng1, Gobriel, Sameh, Bruce Richardson

Hi Stanislaw,

On 29/04/2021 21H:45, Stanislaw Kardach wrote:
> On Thu, Apr 29, 2021 at 12:17:08PM +0300, Medvedkin, Vladimir wrote:
> <snip>
>>> Test Failed
>>> RTE>>
>>> --- stderr ---
>>> EAL: Detected 2 lcore(s)
>>> EAL: Detected 1 NUMA nodes
>>> EAL: Detected shared linkage of DPDK
>>> EAL: Multi-process socket /var/run/dpdk/thash_autotest/mp_socket
>>> EAL: Selected IOVA mode 'PA'
>>> EAL: No available 1048576 kB hugepages reported
>>> EAL: VFIO support initialized
>>> APP: HPET is not enabled, using TSC as default timer
>>> HASH: Can't add helper  due to conflict with existing helper second_range
>>> HASH: Can't generate m-sequence due to period overflow
>>> EAL: Test assert test_adjust_tuple line 559 failed: can not adjust
>>> tuple, ret -17
>>>
> 
> I can see the same issue on my side. Happening randomly, more often on a
> RISC-V target than on my laptop (i5-10210U). Though the reproduction
> seems to be a lot of patience and the following:
> 
>    meson test --repeat 100000 DPDK:fast-tests / thash_autotest
> 
> I wonder if it can be related to the desired_value in test_adjust_tuple
> being a randomized value without setting the seed prior to the test?
> I haven't analyzed the code in-depth but it seems that the
> rte_thash_add_helper() also uses a random lfsr which is then used in the
> subkey generation. Could this contribute to the randomness of the issue?
> 

The problem here is that the rte_thash_adjust_tuple() function does not 
guarantee that it will find a tuple in a given number of attempts in the 
case when the fn() callback is passed. It depends on random, the logic 
of the fn() callback, and the content of the userdata.

So, in the test we have:
- 96-bit("sizeof(tuple) * CHAR_BITS") tuple
- 16-bit("reta_sz * 2") changeable part of the tuple (i.e. subtuple)
- we want to have a collision in the hash value for 8("reta_sz") least 
significant bits

In other words there are 16 changeable bits inside the subtuple and 8 
least significant bits of them are changed by the 
rte_thash_get_complement() in order to produce collision. The rest 8 
bits are our entropy, i.e. 2^8 different subtuples can produce the 
required collision.

in the problematic call
ret = rte_thash_adjust_tuple(ctx, h, tuple, TUPLE_SZ, desired_value,
                 2, cmp_tuple_eq, tuple_copy);

the original tuple is passed to be changed and the tuple_copy we got 
from the previous invocation. Content of the tuple_copy previously was 
derived from the original tuple applying 8 bit complement on the 
subtuple part.

On the first attempt the function gets the complement and applies it to 
the original tuple, tuple becomes equal to the tuple_copy. After it 
calls the callback and finds that tuple is equal to the tuple_copy. Then 
on the second attempt random bits are xored with 16 bit subtuple value. 
If the first 8 MSBs of random are zeroes, then after applying a new 
complement to the tuple it will be equal to the tuple_copy. While we 
allow only 2 attempts function returns -EEXIST.

I reworked rte_thash_adjust_tuple() removing the randomness. Instead it 
increments the subtuple part which is free of complementary bits.

-- 
Regards,
Vladimir

^ permalink raw reply	[flat|nested] 45+ messages in thread

end of thread, other threads:[~2021-05-04 14:06 UTC | newest]

Thread overview: 45+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-03-16 18:24 [dpdk-dev] [PATCH v1 0/3] Predictable RSS feature Vladimir Medvedkin
2021-03-16 18:24 ` [dpdk-dev] [PATCH v1 1/3] hash: add predictable RSS API Vladimir Medvedkin
2021-03-16 18:24 ` [dpdk-dev] [PATCH v1 2/3] hash: add predictable RSS implementation Vladimir Medvedkin
2021-03-16 18:24 ` [dpdk-dev] [PATCH v1 3/3] test/hash: add additional thash tests Vladimir Medvedkin
2021-04-06 19:50 ` [dpdk-dev] [PATCH v2 0/3] Predictable RSS feature Vladimir Medvedkin
2021-04-08 15:56   ` Stephen Hemminger
2021-04-11 18:51     ` Medvedkin, Vladimir
2021-04-10  0:32   ` Wang, Yipeng1
2021-04-11 18:51     ` Medvedkin, Vladimir
2021-04-11 19:11   ` [dpdk-dev] [PATCH v3 " Vladimir Medvedkin
2021-04-13 13:19     ` [dpdk-dev] [PATCH v4 " Vladimir Medvedkin
2021-04-14 18:04       ` Wang, Yipeng1
2021-04-15  8:29         ` David Marchand
2021-04-19 15:59       ` [dpdk-dev] [PATCH v5 0/5] " Vladimir Medvedkin
2021-04-20 21:31         ` Thomas Monjalon
2021-04-19 15:59       ` [dpdk-dev] [PATCH v5 1/5] hash: add predictable RSS API Vladimir Medvedkin
2021-04-19 15:59       ` [dpdk-dev] [PATCH v5 2/5] hash: add predictable RSS implementation Vladimir Medvedkin
2021-04-19 15:59       ` [dpdk-dev] [PATCH v5 3/5] test/hash: add additional thash tests Vladimir Medvedkin
2021-04-29  9:13         ` David Marchand
2021-04-29  9:17           ` Medvedkin, Vladimir
2021-04-29 18:45             ` Stanislaw Kardach
2021-05-04 14:06               ` Medvedkin, Vladimir
2021-04-19 15:59       ` [dpdk-dev] [PATCH v5 4/5] doc: add thash documentation Vladimir Medvedkin
2021-04-20 11:25         ` Mcnamara, John
2021-04-19 15:59       ` [dpdk-dev] [PATCH v5 5/5] maintainers: claim maintainership of the hash library Vladimir Medvedkin
2021-04-13 13:19     ` [dpdk-dev] [PATCH v4 1/3] hash: add predictable RSS API Vladimir Medvedkin
2021-04-14 17:06       ` Wang, Yipeng1
2021-04-13 13:19     ` [dpdk-dev] [PATCH v4 2/3] hash: add predictable RSS implementation Vladimir Medvedkin
2021-04-14 17:51       ` Wang, Yipeng1
2021-04-13 13:19     ` [dpdk-dev] [PATCH v4 3/3] test/hash: add additional thash tests Vladimir Medvedkin
2021-04-14 17:56       ` Wang, Yipeng1
2021-04-11 19:11   ` [dpdk-dev] [PATCH v3 1/3] hash: add predictable RSS API Vladimir Medvedkin
2021-04-11 19:11   ` [dpdk-dev] [PATCH v3 2/3] hash: add predictable RSS implementation Vladimir Medvedkin
2021-04-11 19:11   ` [dpdk-dev] [PATCH v3 3/3] test/hash: add additional thash tests Vladimir Medvedkin
2021-04-06 19:50 ` [dpdk-dev] [PATCH v2 1/3] hash: add predictable RSS API Vladimir Medvedkin
2021-04-10  0:05   ` Wang, Yipeng1
2021-04-11 18:52     ` Medvedkin, Vladimir
2021-04-06 19:50 ` [dpdk-dev] [PATCH v2 2/3] hash: add predictable RSS implementation Vladimir Medvedkin
2021-04-07 12:53   ` Ananyev, Konstantin
2021-04-11 18:51     ` Medvedkin, Vladimir
2021-04-12  9:47       ` Ananyev, Konstantin
2021-04-13 12:28         ` Medvedkin, Vladimir
2021-04-10  0:10   ` Wang, Yipeng1
2021-04-11 18:52     ` Medvedkin, Vladimir
2021-04-06 19:50 ` [dpdk-dev] [PATCH v2 3/3] test/hash: add additional thash tests Vladimir Medvedkin

DPDK patches and discussions

This inbox may be cloned and mirrored by anyone:

	git clone --mirror https://inbox.dpdk.org/dev/0 dev/git/0.git

	# If you have public-inbox 1.1+ installed, you may
	# initialize and index your mirror using the following commands:
	public-inbox-init -V2 dev dev/ https://inbox.dpdk.org/dev \
		dev@dpdk.org
	public-inbox-index dev

Example config snippet for mirrors.
Newsgroup available over NNTP:
	nntp://inbox.dpdk.org/inbox.dpdk.dev


AGPL code for this site: git clone https://public-inbox.org/public-inbox.git