[dpdk-dev] [PATCH] [RFC] Elastic Flow Distributor

Pablo de Lara pablo.de.lara.guarch at intel.com
Thu Oct 20 10:15:02 CEST 2016


The following RFC shows the functionality and usage overview of
the new Elastic Flow Distributor (EFD) library.
Library code is included in the RFC (implemention in progress, API complete),
with a sample application to demonstrate the usage of the library,
based on the existing server/client sample app.

A PDF document has been uploaded with the following and more information
about the library, including color images (not ascii) to improve
readability, in the following link:

https://github.com/pablodelara/perfect_hash_flow_distributor/blob/master/EFD_description.pdf

Introduction
============

In Data Centers today clustering and scheduling of distributed workloads is a
very common task. Many workload requires a deterministic partitioning of a flat
key space among a cluster of machines. When a packet enters the cluster, the
ingress node will direct the packet to its handling node.  For example,
datacenters with disaggregated storage uses storage metadata table to forward
I/O requests to correct backend storage cluster, stateful packet inspection will
use match incoming flows to signatures in flow tables to send incoming packets
to their intended deep packet inspection (DPI) devices, and so on.

EFD is a distributor library that uses perfect hashing to determine a
target/value for a given incoming flow key. It has the following advantages:
first, because it uses perfect hashing it does not store the key itself and
hence lookup performance is not dependent on the key size. Second, the
target/value can be any arbitrary value hence the system designer and/or
operator can better optimize service rates and inter-cluster network traffic
locating.  Third, since the storage requirement is much smaller than a
hash-based flow table (i.e. better fit for CPU cache), EFD can scale to millions
of flow keys. Finally, with current optimized library implementation performance
is fully scalable with number of CPU cores.

Overview
========

The basic idea of EFD is when a given key is to be inserted, a family of hash
functions is searched until the correct hash function that maps the input key to
the correct value is found. However, rather than explicitly storing all keys and
their associated values, EFD stores only indices of hash functions that map keys
to values, and thereby consumes much less space than conventional  flow-based
tables. The lookup operation is very simple, similar to computational-based
scheme, given an input key the lookup operation is reduced to hashing that key
with the correct hash function.

Intuitively, finding a hash function that maps each of a large number (millions)
of input keys to the correct output value is effectively impossible, as a result
EFD, breaks the problem into smaller pieces (divide and conquer). EFD divides
the entire input key set into many small groups. Each group consists of
approximately 20-28 keys (a configurable parameter for the library), then, for
each small group, a brute force search to find a hash function that produces the
correct outputs for each key in the group.
It should be mentioned that since in the online lookup table for EFD doesn’t
store the key itself, the size of the EFD table is independent of the key size
and hence EFD lookup performance which is almost constant irrespective of the
length of the key which is a highly desirable feature especially for longer
keys.

API
===

The EFD library API is created with a very similar semantics of a hash-index or
a flow table, the application creates an EFD table for a given maximum number of
flows, a function is called to insert a flow key with a specific target value,
and another function is used to retrieve target values for a given individual
flow key or a bulk of keys.

EFD Table Create
----------------

The function rte_efd_create("flow table", num_flows, online_socket_bitmask,
offline_socket_bitmask) is used to create and return a pointer to an EFD table
that is sized to hold up to num_flows key. The online version of the EFD table
(the one that does not store the keys and is used for lookups) will be allocated
and created in the last level cache (LLC) of the socket defined by the
online_socket_bitmask, while the offline EFD table (the one that stores the keys
and is used for key inserts and for computing the perfect hashing) is allocated
and created in the LLC of the socket defined by offline_socket_bitmask. It
should be noted, that For highest performance the socket id should match that
where the thread is running, i.e. the online EFD lookup table should be created
on the same socket as where the lookup thread is running.

EFD Insert and Update
---------------------

The EFD function to insert a key or update a key to a new value is
rte_efd_update(struct rte_efd_table *table, unsigned socket_id, const efd_key_t
*key, efd_value_t value). This function will update an existing key to a new
value (target) if the key has already been inserted before, or will insert the
<key,value> pair if this key has not been inserted before. It will return 0 upon
success. It will return EFD_UPDATE_WARN_GROUP_FULL (1) if the operation is
insert, and the last available space in the key's group was just used. It will
return EFD_UPDATE_FAILED (2) when the insertion or update has filed (either it
failed to find a suitable perfect hash or the group was full). The function will
return EFD_UPDATE_NO_CHANGE (3) if there is no change to the EFD table (i.e,
same value already exists).

EFD Lookup
----------

To lookup a certain key in an EFD table, the function rte_efd_lookup(const
struct rte_efd_table *table, unsigned socket_id, const efd_key_t *key) which is
used to return the value associated with single key. As previously mentioned, if
the key has been inserted the correct value inserted is returned, if the key has
not been inserted before a ‘random’ value (based on hashing of the key) is
returned. For better performance and to decrease the overhead of function calls
per key, it is always recommended to use a bulk lookup function (simultaneous
lookup of multiple keys) instead of a single key lookup function.

rte_efd_lookup_bulk(const struct rte_efd_table *table, unsigned socket_id, int
num_keys, const efd_key_t *const *key_list, efd_value_t *value_list) is the bulk
lookup function, that looks up num_keys simultaneously stored in the key_list
and the corresponding return values will be returned in the value_list.

EFD Delete
----------

To delete a certain key in an EFD table, the function rte_efd_delete(struct
rte_efd_table *table, unsigned socket_id, const efd_key_t *key,  efd_value_t
*prev_value) can be used. The function returns zero upon success when the key
has been found and deleted. Socket_id is the one to use to lookup the existing
value and ideally it is the caller's socket id. The previous value associated
with this key will be returned in the prev_value argument.

Example of EFD Library usage
============================

EFD can be used along the data path of many network functions and middle boxes.
As previously mentioned, It can used as an index table for <key,value> pair,
meta-data for objects, flow-level load balancer, etc. The following figure shows
an example of using EFD as a flow-level load balancer, where flows are received
at a front end server before being forwarded to the target backend server for
processing. The system designer would co-locate flows together (needs to
deterministically control the target backend server for each flow) in order to
minimize cross server together (for example, flows requesting certain webpage
objects are co-located together, to minimize forwarding of common objects across
servers).

                                                        Local Table for specific flows serviced at Node 1

                                                         +---------------------------------------------+
                                                         |  Key 1 | Action 1 | ... |  Key x | Action x |
                                                         |        |          |     |        |          |
                                                         +---------------------------------------------+
                                     +-----------+   +--->        |          |     |        |          |
                                     |  Backend  |   |   |        |          |     |        |          |
                                     |   Node 1  +---+   +---------------------------------------------+
                             +------->           |       |  Key y | Action y | ... |  Key N | Action N |
                             |       +-----------+       |        |          |     |        |          |
        +-----------------+  |                           +---------------------------------------------+
        |                 |  |       +-----------+
        | Frontend Server |  |       |  Backend  |
   +----+         or      +---------->   Node 2  |
   |    |   Load Balancer |  |       |           |
   |    |                 |  |       +-----------+       Local Table for specific flows serviced at Node X
   |    +-----------------+  |
   |                         |       +-----------+       +---------------------------------------------+
   |                         |       |  Backend  |       |  Key 1 | Action 1 | ... |  Key x | Action x |
   |                         +-------+   Node X  +----+  |        |          |     |        |          |
   |                                 |           |    |  +---------------------------------------------+
   |                                 +-----------+    +-->        |          |     |        |          |
   |   +-----------------------+                         |        |          |     |        |          |
   |   |          |            |                         +---------------------------------------------+
   |   | Group id |  Hash index|                         |  Key y | Action y | ... | Key N  | Action N |
   +--->          |            |                         |        |          |     |        |          |
       +-----------------------+                         +---------------------------------------------+
       |          |            |
       +-----------------------+
       |          |            |
       +-----------------------+
       |          |            |
       +-----------------------+
       |          |            |
       +-----------------------+
             EFD Table
        (Supports X*N flows)

As shown the figure, the front end server will have an EFD table that stores for
each group what is the perfect hash index that satisfies the correct output.
Because the table size is small and fits in cache (since keys are not stored) it
sustains a large number of flows (N*X, where N is the maximum number of flows
served by each back end server of the X possible targets).
With an input flow key, the group id is computed (for example, using last few
bits of CRC hash) and then the EFD table is indexed with the group id to
retrieve the corresponding hash index to use. Once the index is retrieved the
key is hashed using this hash function and the result will be the intended
correct target where this flow is supposed to be processed.
It should be noted that since EFD is not matching the exact key but rather
distributing the flows to a target backend node based on the perfect hash index,
as a result, a key that has not been inserted before will be distributed to a
valid target. Hence, a local table which stores the flows served at each node is
used, as shown in the figure, and is exact matched with the input key to rule
out new never seen before flows.
---
 config/common_base                             |   5 +
 examples/Makefile                              |   1 +
 examples/flow_distributor/Makefile             |  44 ++
 examples/flow_distributor/distributor/Makefile |  61 ++
 examples/flow_distributor/distributor/args.c   | 199 +++++
 examples/flow_distributor/distributor/args.h   |  39 +
 examples/flow_distributor/distributor/init.c   | 360 +++++++++
 examples/flow_distributor/distributor/init.h   |  76 ++
 examples/flow_distributor/distributor/main.c   | 355 +++++++++
 examples/flow_distributor/node/Makefile        |  48 ++
 examples/flow_distributor/node/node.c          | 404 +++++++++++
 examples/flow_distributor/shared/common.h      |  97 +++
 lib/Makefile                                   |   1 +
 lib/librte_eal/common/include/rte_log.h        |   1 +
 lib/librte_efd/Makefile                        |  56 ++
 lib/librte_efd/rte_efd.c                       | 969 +++++++++++++++++++++++++
 lib/librte_efd/rte_efd.h                       | 423 +++++++++++
 lib/librte_efd/rte_efd_version.map             |  12 +
 mk/rte.app.mk                                  |   1 +
 19 files changed, 3152 insertions(+)
 create mode 100644 examples/flow_distributor/Makefile
 create mode 100644 examples/flow_distributor/distributor/Makefile
 create mode 100644 examples/flow_distributor/distributor/args.c
 create mode 100644 examples/flow_distributor/distributor/args.h
 create mode 100644 examples/flow_distributor/distributor/init.c
 create mode 100644 examples/flow_distributor/distributor/init.h
 create mode 100644 examples/flow_distributor/distributor/main.c
 create mode 100644 examples/flow_distributor/node/Makefile
 create mode 100644 examples/flow_distributor/node/node.c
 create mode 100644 examples/flow_distributor/shared/common.h
 create mode 100644 lib/librte_efd/Makefile
 create mode 100644 lib/librte_efd/rte_efd.c
 create mode 100644 lib/librte_efd/rte_efd.h
 create mode 100644 lib/librte_efd/rte_efd_version.map

diff --git a/config/common_base b/config/common_base
index c7fd3db..86a7e53 100644
--- a/config/common_base
+++ b/config/common_base
@@ -458,6 +458,11 @@ CONFIG_RTE_LIBRTE_HASH=y
 CONFIG_RTE_LIBRTE_HASH_DEBUG=n
 
 #
+# Compile librte_efd
+#
+CONFIG_RTE_LIBRTE_EFD=y
+
+#
 # Compile librte_jobstats
 #
 CONFIG_RTE_LIBRTE_JOBSTATS=y
diff --git a/examples/Makefile b/examples/Makefile
index d49c7f2..b404982 100644
--- a/examples/Makefile
+++ b/examples/Makefile
@@ -45,6 +45,7 @@ DIRS-y += dpdk_qat
 endif
 DIRS-y += ethtool
 DIRS-y += exception_path
+DIRS-$(CONFIG_RTE_LIBRTE_EFD) += flow_distributor
 DIRS-y += helloworld
 DIRS-$(CONFIG_RTE_LIBRTE_PIPELINE) += ip_pipeline
 ifeq ($(CONFIG_RTE_LIBRTE_LPM),y)
diff --git a/examples/flow_distributor/Makefile b/examples/flow_distributor/Makefile
new file mode 100644
index 0000000..402c588
--- /dev/null
+++ b/examples/flow_distributor/Makefile
@@ -0,0 +1,44 @@
+#   BSD LICENSE
+#
+#   Copyright(c) 2010-2016 Intel Corporation. All rights reserved.
+#   All rights reserved.
+#
+#   Redistribution and use in source and binary forms, with or without
+#   modification, are permitted provided that the following conditions
+#   are met:
+#
+#     * Redistributions of source code must retain the above copyright
+#       notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above copyright
+#       notice, this list of conditions and the following disclaimer in
+#       the documentation and/or other materials provided with the
+#       distribution.
+#     * Neither the name of Intel Corporation nor the names of its
+#       contributors may be used to endorse or promote products derived
+#       from this software without specific prior written permission.
+#
+#   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+#   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+#   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+#   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+#   OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+#   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+#   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+#   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+#   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+#   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+#   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ifeq ($(RTE_SDK),)
+$(error "Please define RTE_SDK environment variable")
+endif
+
+# Default target, can be overriden by command line or environment
+RTE_TARGET ?= x86_64-native-linuxapp-gcc
+
+include $(RTE_SDK)/mk/rte.vars.mk
+
+DIRS-$(CONFIG_RTE_EXEC_ENV_LINUXAPP) += distributor
+DIRS-$(CONFIG_RTE_EXEC_ENV_LINUXAPP) += node
+
+include $(RTE_SDK)/mk/rte.extsubdir.mk
diff --git a/examples/flow_distributor/distributor/Makefile b/examples/flow_distributor/distributor/Makefile
new file mode 100644
index 0000000..61dfb2f
--- /dev/null
+++ b/examples/flow_distributor/distributor/Makefile
@@ -0,0 +1,61 @@
+#   BSD LICENSE
+#
+#   Copyright(c) 2010-2016 Intel Corporation. All rights reserved.
+#   All rights reserved.
+#
+#   Redistribution and use in source and binary forms, with or without
+#   modification, are permitted provided that the following conditions
+#   are met:
+#
+#     * Redistributions of source code must retain the above copyright
+#       notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above copyright
+#       notice, this list of conditions and the following disclaimer in
+#       the documentation and/or other materials provided with the
+#       distribution.
+#     * Neither the name of Intel Corporation nor the names of its
+#       contributors may be used to endorse or promote products derived
+#       from this software without specific prior written permission.
+#
+#   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+#   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+#   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+#   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+#   OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+#   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+#   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+#   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+#   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+#   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+#   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ifeq ($(RTE_SDK),)
+$(error "Please define RTE_SDK environment variable")
+endif
+
+# Default target, can be overriden by command line or environment
+RTE_TARGET ?= x86_64-native-linuxapp-gcc
+
+include $(RTE_SDK)/mk/rte.vars.mk
+
+ifneq ($(CONFIG_RTE_EXEC_ENV),"linuxapp")
+$(error This application can only operate in a linuxapp environment, \
+please change the definition of the RTE_TARGET environment variable)
+endif
+
+# binary name
+APP = distributor
+
+# all source are stored in SRCS-y
+SRCS-y := main.c init.c args.c
+
+INC := $(wildcard *.h)
+
+CFLAGS += $(WERROR_FLAGS) -O3
+CFLAGS += -I$(SRCDIR)/../shared
+
+# for newer gcc, e.g. 4.4, no-strict-aliasing may not be necessary
+# and so the next line can be removed in those cases.
+EXTRA_CFLAGS += -fno-strict-aliasing
+
+include $(RTE_SDK)/mk/rte.extapp.mk
diff --git a/examples/flow_distributor/distributor/args.c b/examples/flow_distributor/distributor/args.c
new file mode 100644
index 0000000..6007711
--- /dev/null
+++ b/examples/flow_distributor/distributor/args.c
@@ -0,0 +1,199 @@
+/*-
+ *   BSD LICENSE
+ *
+ *   Copyright(c) 2010-2016 Intel Corporation. All rights reserved.
+ *   All rights reserved.
+ *
+ *   Redistribution and use in source and binary forms, with or without
+ *   modification, are permitted provided that the following conditions
+ *   are met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in
+ *       the documentation and/or other materials provided with the
+ *       distribution.
+ *     * Neither the name of Intel Corporation nor the names of its
+ *       contributors may be used to endorse or promote products derived
+ *       from this software without specific prior written permission.
+ *
+ *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ *   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ *   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ *   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ *   OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ *   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ *   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ *   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <getopt.h>
+#include <stdarg.h>
+#include <errno.h>
+
+#include <rte_memory.h>
+#include <rte_string_fns.h>
+
+#include "common.h"
+#include "args.h"
+#include "init.h"
+
+/* 1M flows by default */
+#define DEFAULT_NUM_FLOWS    0x100000
+
+/* global var for number of nodes - extern in header */
+uint8_t num_nodes;
+/* global var for number of flows - extern in header */
+uint32_t num_flows = DEFAULT_NUM_FLOWS;
+
+static const char *progname;
+
+/**
+ * Prints out usage information to stdout
+ */
+static void
+usage(void)
+{
+	printf(
+	    "%s [EAL options] -- -p PORTMASK -n NUM_NODES -f NUM_FLOWS\n"
+	    " -p PORTMASK: hexadecimal bitmask of ports to use\n"
+	    " -n NUM_NODES: number of node processes to use\n"
+            " -f NUM_FLOWS: number of flows to be added in the EFD table\n"
+	    , progname);
+}
+
+/**
+ * The ports to be used by the application are passed in
+ * the form of a bitmask. This function parses the bitmask
+ * and places the port numbers to be used into the port[]
+ * array variable
+ */
+static int
+parse_portmask(uint8_t max_ports, const char *portmask)
+{
+	char *end = NULL;
+	unsigned long pm;
+	uint8_t count = 0;
+
+	if (portmask == NULL || *portmask == '\0')
+		return -1;
+
+	/* convert parameter to a number and verify */
+	pm = strtoul(portmask, &end, 16);
+	if (end == NULL || *end != '\0' || pm == 0)
+		return -1;
+
+	/* loop through bits of the mask and mark ports */
+	while (pm != 0){
+		if (pm & 0x01){ /* bit is set in mask, use port */
+			if (count >= max_ports)
+				printf("WARNING: requested port %u not present"
+				" - ignoring\n", (unsigned)count);
+			else
+			    info->id[info->num_ports++] = count;
+		}
+		pm = (pm >> 1);
+		count++;
+	}
+
+	return 0;
+}
+
+/**
+ * Take the number of nodes parameter passed to the app
+ * and convert to a number to store in the num_nodes variable
+ */
+static int
+parse_num_nodes(const char *nodes)
+{
+	char *end = NULL;
+	unsigned long temp;
+
+	if (nodes == NULL || *nodes == '\0')
+		return -1;
+
+	temp = strtoul(nodes, &end, 10);
+	if (end == NULL || *end != '\0' || temp == 0)
+		return -1;
+
+	num_nodes = (uint8_t)temp;
+	return 0;
+}
+
+static int
+parse_num_flows(const char *flows)
+{
+        char *end = NULL;
+        /* parse hexadecimal string */
+        num_flows = strtoul(flows, &end, 16);
+        if ((flows[0] == '\0') || (end == NULL) || (*end != '\0'))
+                return -1;
+
+        if (num_flows == 0)
+                return -1;
+
+        return 0;
+}
+
+/**
+ * The application specific arguments follow the DPDK-specific
+ * arguments which are stripped by the DPDK init. This function
+ * processes these application arguments, printing usage info
+ * on error.
+ */
+int
+parse_app_args(uint8_t max_ports, int argc, char *argv[])
+{
+	int option_index, opt;
+	char **argvopt = argv;
+	static struct option lgopts[] = { /* no long options */
+		{NULL, 0, 0, 0 }
+	};
+	progname = argv[0];
+
+	while ((opt = getopt_long(argc, argvopt, "n:f:p:", lgopts,
+		&option_index)) != EOF){
+		switch (opt){
+			case 'p':
+				if (parse_portmask(max_ports, optarg) != 0) {
+					usage();
+					return -1;
+				}
+				break;
+			case 'n':
+				if (parse_num_nodes(optarg) != 0) {
+					usage();
+					return -1;
+				}
+				break;
+			case 'f':
+				if (parse_num_flows(optarg) != 0) {
+					usage();
+					return -1;
+				}
+				break;
+			default:
+				printf("ERROR: Unknown option '%c'\n", opt);
+				usage();
+				return -1;
+		}
+	}
+
+	if (info->num_ports == 0 || num_nodes == 0){
+		usage();
+		return -1;
+	}
+
+	if (info->num_ports % 2 != 0){
+		printf("ERROR: application requires an even number of ports to use\n");
+		return -1;
+	}
+	return 0;
+}
diff --git a/examples/flow_distributor/distributor/args.h b/examples/flow_distributor/distributor/args.h
new file mode 100644
index 0000000..8b36148
--- /dev/null
+++ b/examples/flow_distributor/distributor/args.h
@@ -0,0 +1,39 @@
+/*-
+ *   BSD LICENSE
+ *
+ *   Copyright(c) 2010-2016 Intel Corporation. All rights reserved.
+ *   All rights reserved.
+ *
+ *   Redistribution and use in source and binary forms, with or without
+ *   modification, are permitted provided that the following conditions
+ *   are met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in
+ *       the documentation and/or other materials provided with the
+ *       distribution.
+ *     * Neither the name of Intel Corporation nor the names of its
+ *       contributors may be used to endorse or promote products derived
+ *       from this software without specific prior written permission.
+ *
+ *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ *   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ *   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ *   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ *   OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ *   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ *   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ *   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _ARGS_H_
+#define _ARGS_H_
+
+int parse_app_args(uint8_t max_ports, int argc, char *argv[]);
+
+#endif /* ifndef _ARGS_H_ */
diff --git a/examples/flow_distributor/distributor/init.c b/examples/flow_distributor/distributor/init.c
new file mode 100644
index 0000000..24664e5
--- /dev/null
+++ b/examples/flow_distributor/distributor/init.c
@@ -0,0 +1,360 @@
+/*-
+ *   BSD LICENSE
+ *
+ *   Copyright(c) 2010-2016 Intel Corporation. All rights reserved.
+ *   All rights reserved.
+ *
+ *   Redistribution and use in source and binary forms, with or without
+ *   modification, are permitted provided that the following conditions
+ *   are met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in
+ *       the documentation and/or other materials provided with the
+ *       distribution.
+ *     * Neither the name of Intel Corporation nor the names of its
+ *       contributors may be used to endorse or promote products derived
+ *       from this software without specific prior written permission.
+ *
+ *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ *   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ *   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ *   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ *   OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ *   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ *   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ *   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/queue.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <inttypes.h>
+
+#include <rte_common.h>
+#include <rte_memory.h>
+#include <rte_memzone.h>
+#include <rte_eal.h>
+#include <rte_byteorder.h>
+#include <rte_atomic.h>
+#include <rte_launch.h>
+#include <rte_per_lcore.h>
+#include <rte_lcore.h>
+#include <rte_branch_prediction.h>
+#include <rte_debug.h>
+#include <rte_ring.h>
+#include <rte_log.h>
+#include <rte_mempool.h>
+#include <rte_memcpy.h>
+#include <rte_mbuf.h>
+#include <rte_interrupts.h>
+#include <rte_pci.h>
+#include <rte_ether.h>
+#include <rte_ethdev.h>
+#include <rte_malloc.h>
+#include <rte_string_fns.h>
+#include <rte_cycles.h>
+#include <rte_efd.h>
+#include <rte_hash.h>
+
+#include "common.h"
+#include "args.h"
+#include "init.h"
+
+#define MBUFS_PER_NODE 1536
+#define MBUFS_PER_PORT 1536
+#define MBUF_CACHE_SIZE 512
+
+#define RTE_MP_RX_DESC_DEFAULT 512
+#define RTE_MP_TX_DESC_DEFAULT 512
+#define NODE_QUEUE_RINGSIZE 128
+
+#define NO_FLAGS 0
+
+/* The mbuf pool for packet rx */
+struct rte_mempool *pktmbuf_pool;
+
+/* array of info/queues for nodes */
+struct node *nodes = NULL;
+
+/* Flow distributor table */
+struct rte_efd_table *efd_table;
+
+/* Shared info between distributor and nodes */
+struct shared_info *info;
+
+/**
+ * Initialise the mbuf pool for packet reception for the NIC, and any other
+ * buffer pools needed by the app - currently none.
+ */
+static int
+init_mbuf_pools(void)
+{
+	const unsigned num_mbufs = (num_nodes * MBUFS_PER_NODE) \
+			+ (info->num_ports * MBUFS_PER_PORT);
+
+	/* don't pass single-producer/single-consumer flags to mbuf create as it
+	 * seems faster to use a cache instead */
+	printf("Creating mbuf pool '%s' [%u mbufs] ...\n",
+			PKTMBUF_POOL_NAME, num_mbufs);
+	pktmbuf_pool = rte_pktmbuf_pool_create(PKTMBUF_POOL_NAME, num_mbufs,
+		MBUF_CACHE_SIZE, 0, RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id());
+
+	return pktmbuf_pool == NULL; /* 0  on success */
+}
+
+/**
+ * Initialise an individual port:
+ * - configure number of rx and tx rings
+ * - set up each rx ring, to pull from the main mbuf pool
+ * - set up each tx ring
+ * - start the port and report its status to stdout
+ */
+static int
+init_port(uint8_t port_num)
+{
+	/* for port configuration all features are off by default */
+	const struct rte_eth_conf port_conf = {
+		.rxmode = {
+			.mq_mode = ETH_MQ_RX_RSS
+		}
+	};
+	const uint16_t rx_rings = 1, tx_rings = num_nodes;
+	const uint16_t rx_ring_size = RTE_MP_RX_DESC_DEFAULT;
+	const uint16_t tx_ring_size = RTE_MP_TX_DESC_DEFAULT;
+
+	uint16_t q;
+	int retval;
+
+	printf("Port %u init ... ", (unsigned)port_num);
+	fflush(stdout);
+
+	/* Standard DPDK port initialisation - config port, then set up
+	 * rx and tx rings */
+	if ((retval = rte_eth_dev_configure(port_num, rx_rings, tx_rings,
+		&port_conf)) != 0)
+		return retval;
+
+	for (q = 0; q < rx_rings; q++) {
+		retval = rte_eth_rx_queue_setup(port_num, q, rx_ring_size,
+				rte_eth_dev_socket_id(port_num),
+				NULL, pktmbuf_pool);
+		if (retval < 0) return retval;
+	}
+
+	for ( q = 0; q < tx_rings; q ++ ) {
+		retval = rte_eth_tx_queue_setup(port_num, q, tx_ring_size,
+				rte_eth_dev_socket_id(port_num),
+				NULL);
+		if (retval < 0) return retval;
+	}
+
+	rte_eth_promiscuous_enable(port_num);
+
+	retval  = rte_eth_dev_start(port_num);
+	if (retval < 0) return retval;
+
+	printf( "done: \n");
+
+	return 0;
+}
+
+/**
+ * Set up the DPDK rings which will be used to pass packets, via
+ * pointers, between the multi-process distributor and node processes.
+ * Each node needs one RX queue.
+ */
+static int
+init_shm_rings(void)
+{
+	unsigned i;
+	unsigned socket_id;
+	const char * q_name;
+	const unsigned ringsize = NODE_QUEUE_RINGSIZE;
+
+	nodes = rte_malloc("node details",
+		sizeof(*nodes) * num_nodes, 0);
+	if (nodes == NULL)
+		rte_exit(EXIT_FAILURE, "Cannot allocate memory for node program details\n");
+
+	for (i = 0; i < num_nodes; i++) {
+		/* Create an RX queue for each node */
+		socket_id = rte_socket_id();
+		q_name = get_rx_queue_name(i);
+		nodes[i].rx_q = rte_ring_create(q_name,
+				ringsize, socket_id,
+				RING_F_SP_ENQ | RING_F_SC_DEQ ); /* single prod, single cons */
+		if (nodes[i].rx_q == NULL)
+			rte_exit(EXIT_FAILURE, "Cannot create rx ring queue for node %u\n", i);
+	}
+	return 0;
+}
+
+/*
+ * Create flow distributor table which will contain all the flows
+ * that will be distributed among the nodes
+ */
+static void
+create_flow_distributor_table(void)
+{
+	uint8_t socket_id = rte_socket_id();
+
+	/* create table */
+	efd_table = rte_efd_create("flow table", num_flows * 2, 1 << socket_id, socket_id);
+
+	if (efd_table == NULL)
+		rte_exit(EXIT_FAILURE, "Problem creating the flow table\n");
+}
+
+static void
+populate_flow_distributor_table(void)
+{
+	unsigned int i;
+	int32_t ret;
+	uint32_t ip_dst;
+	uint8_t socket_id = rte_socket_id();
+	uint64_t node_id;
+
+	/* Add flows in tables */
+	for (i = 0; i < num_flows; i++) {
+		node_id = i % num_nodes;
+
+		ip_dst = rte_cpu_to_be_32(i);
+		ret = rte_efd_update(efd_table, socket_id,
+				(efd_key_t *)&ip_dst, (efd_value_t)node_id);
+		if (ret < 0)
+			rte_exit(EXIT_FAILURE,
+					"Unable to add entry %u in flow distributor table\n", i);
+	}
+
+	printf("EFD table: Adding 0x%x keys\n", num_flows);
+}
+
+/* Check the link status of all ports in up to 9s, and print them finally */
+static void
+check_all_ports_link_status(uint8_t port_num, uint32_t port_mask)
+{
+#define CHECK_INTERVAL 100 /* 100ms */
+#define MAX_CHECK_TIME 90 /* 9s (90 * 100ms) in total */
+	uint8_t portid, count, all_ports_up, print_flag = 0;
+	struct rte_eth_link link;
+
+	printf("\nChecking link status");
+	fflush(stdout);
+	for (count = 0; count <= MAX_CHECK_TIME; count++) {
+		all_ports_up = 1;
+		for (portid = 0; portid < port_num; portid++) {
+			if ((port_mask & (1 << info->id[portid])) == 0)
+				continue;
+			memset(&link, 0, sizeof(link));
+			rte_eth_link_get_nowait(info->id[portid], &link);
+			/* print link status if flag set */
+			if (print_flag == 1) {
+				if (link.link_status)
+					printf("Port %d Link Up - speed %u "
+						"Mbps - %s\n", info->id[portid],
+						(unsigned)link.link_speed,
+				(link.link_duplex == ETH_LINK_FULL_DUPLEX) ?
+					("full-duplex") : ("half-duplex\n"));
+				else
+					printf("Port %d Link Down\n",
+						(uint8_t)info->id[portid]);
+				continue;
+			}
+			/* clear all_ports_up flag if any link down */
+			if (link.link_status == ETH_LINK_DOWN) {
+				all_ports_up = 0;
+				break;
+			}
+		}
+		/* after finally printing all link status, get out */
+		if (print_flag == 1)
+			break;
+
+		if (all_ports_up == 0) {
+			printf(".");
+			fflush(stdout);
+			rte_delay_ms(CHECK_INTERVAL);
+		}
+
+		/* set the print_flag if all ports up or timeout */
+		if (all_ports_up == 1 || count == (MAX_CHECK_TIME - 1)) {
+			print_flag = 1;
+			printf("done\n");
+		}
+	}
+}
+
+/**
+ * Main init function for the multi-process distributor app,
+ * calls subfunctions to do each stage of the initialisation.
+ */
+int
+init(int argc, char *argv[])
+{
+	int retval;
+	const struct rte_memzone *mz;
+	uint8_t i, total_ports;
+
+	/* init EAL, parsing EAL args */
+	retval = rte_eal_init(argc, argv);
+	if (retval < 0)
+		return -1;
+	argc -= retval;
+	argv += retval;
+
+	/* get total number of ports */
+	total_ports = rte_eth_dev_count();
+
+	/* set up array for port data */
+	mz = rte_memzone_reserve(MZ_SHARED_INFO, sizeof(*info),
+				rte_socket_id(), NO_FLAGS);
+	if (mz == NULL)
+		rte_exit(EXIT_FAILURE, "Cannot reserve memory zone for port information\n");
+	memset(mz->addr, 0, sizeof(*info));
+	info = mz->addr;
+
+	/* parse additional, application arguments */
+	retval = parse_app_args(total_ports, argc, argv);
+	if (retval != 0)
+		return -1;
+
+	/* initialise mbuf pools */
+	retval = init_mbuf_pools();
+	if (retval != 0)
+		rte_exit(EXIT_FAILURE, "Cannot create needed mbuf pools\n");
+
+	/* now initialise the ports we will use */
+	for (i = 0; i < info->num_ports; i++) {
+		retval = init_port(info->id[i]);
+		if (retval != 0)
+			rte_exit(EXIT_FAILURE, "Cannot initialise port %u\n",
+					(unsigned)i);
+	}
+
+	check_all_ports_link_status(info->num_ports, (~0x0));
+
+	/* initialise the node queues/rings for inter-eu comms */
+	init_shm_rings();
+
+	/* Create the flow distributor table */
+	create_flow_distributor_table();
+
+	/* Populate the flow distributor table */
+	populate_flow_distributor_table();
+
+	/* Share the total number of nodes */
+	info->num_nodes = num_nodes;
+
+	/* Share the total number of flows */
+	info->num_flows = num_flows;
+	return 0;
+}
diff --git a/examples/flow_distributor/distributor/init.h b/examples/flow_distributor/distributor/init.h
new file mode 100644
index 0000000..fd8dc06
--- /dev/null
+++ b/examples/flow_distributor/distributor/init.h
@@ -0,0 +1,76 @@
+/*-
+ *   BSD LICENSE
+ *
+ *   Copyright(c) 2010-2016 Intel Corporation. All rights reserved.
+ *   All rights reserved.
+ *
+ *   Redistribution and use in source and binary forms, with or without
+ *   modification, are permitted provided that the following conditions
+ *   are met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in
+ *       the documentation and/or other materials provided with the
+ *       distribution.
+ *     * Neither the name of Intel Corporation nor the names of its
+ *       contributors may be used to endorse or promote products derived
+ *       from this software without specific prior written permission.
+ *
+ *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ *   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ *   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ *   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ *   OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ *   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ *   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ *   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _INIT_H_
+#define _INIT_H_
+
+/*
+ * #include <rte_ring.h>
+ * #include "args.h"
+ */
+
+/*
+ * Define a node structure with all needed info, including
+ * stats from the nodes.
+ */
+struct node {
+	struct rte_ring *rx_q;
+	unsigned node_id;
+	/* these stats hold how many packets the node will actually receive,
+	 * and how many packets were dropped because the node's queue was full.
+	 * The port-info stats, in contrast, record how many packets were received
+	 * or transmitted on an actual NIC port.
+	 */
+	struct {
+		volatile uint64_t rx;
+		volatile uint64_t rx_drop;
+	} stats;
+};
+
+extern struct rte_efd_table *efd_table;
+extern struct node *nodes;
+
+/*
+ * shared information between distributor and nodes: number of clients,
+ * port numbers, rx and tx stats etc.
+ */
+extern struct shared_info *info;
+
+extern struct rte_mempool *pktmbuf_pool;
+extern uint8_t num_nodes;
+extern unsigned int num_sockets;
+extern uint32_t num_flows;
+
+int init(int argc, char *argv[]);
+
+#endif /* ifndef _INIT_H_ */
diff --git a/examples/flow_distributor/distributor/main.c b/examples/flow_distributor/distributor/main.c
new file mode 100644
index 0000000..49fe1b9
--- /dev/null
+++ b/examples/flow_distributor/distributor/main.c
@@ -0,0 +1,355 @@
+/*-
+ *   BSD LICENSE
+ *
+ *   Copyright(c) 2010-2016 Intel Corporation. All rights reserved.
+ *   All rights reserved.
+ *
+ *   Redistribution and use in source and binary forms, with or without
+ *   modification, are permitted provided that the following conditions
+ *   are met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in
+ *       the documentation and/or other materials provided with the
+ *       distribution.
+ *     * Neither the name of Intel Corporation nor the names of its
+ *       contributors may be used to endorse or promote products derived
+ *       from this software without specific prior written permission.
+ *
+ *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ *   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ *   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ *   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ *   OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ *   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ *   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ *   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <stdarg.h>
+#include <inttypes.h>
+#include <inttypes.h>
+#include <sys/queue.h>
+#include <errno.h>
+#include <netinet/ip.h>
+
+#include <rte_common.h>
+#include <rte_memory.h>
+#include <rte_memzone.h>
+#include <rte_eal.h>
+#include <rte_byteorder.h>
+#include <rte_launch.h>
+#include <rte_per_lcore.h>
+#include <rte_lcore.h>
+#include <rte_branch_prediction.h>
+#include <rte_atomic.h>
+#include <rte_ring.h>
+#include <rte_log.h>
+#include <rte_debug.h>
+#include <rte_mempool.h>
+#include <rte_memcpy.h>
+#include <rte_mbuf.h>
+#include <rte_ether.h>
+#include <rte_interrupts.h>
+#include <rte_pci.h>
+#include <rte_ethdev.h>
+#include <rte_byteorder.h>
+#include <rte_malloc.h>
+#include <rte_string_fns.h>
+#include <rte_efd.h>
+#include <rte_ip.h>
+
+#include "common.h"
+#include "args.h"
+#include "init.h"
+
+/*
+ * When doing reads from the NIC or the node queues,
+ * use this batch size
+ */
+#define PACKET_READ_SIZE 32
+
+/*
+ * Local buffers to put packets in, used to send packets in bursts to the
+ * nodes
+ */
+struct node_rx_buf {
+	struct rte_mbuf *buffer[PACKET_READ_SIZE];
+	uint16_t count;
+};
+
+struct flow_distributor_stats {
+	volatile uint64_t distributed;
+	volatile uint64_t drop;
+} flow_dist_stats;
+
+/* One buffer per node rx queue - dynamically allocate array */
+static struct node_rx_buf *cl_rx_buf;
+
+static const char *
+get_printable_mac_addr(uint8_t port)
+{
+	static const char err_address[] = "00:00:00:00:00:00";
+	static char addresses[RTE_MAX_ETHPORTS][sizeof(err_address)];
+
+	if (unlikely(port >= RTE_MAX_ETHPORTS))
+		return err_address;
+	if (unlikely(addresses[port][0]=='\0')){
+		struct ether_addr mac;
+		rte_eth_macaddr_get(port, &mac);
+		snprintf(addresses[port], sizeof(addresses[port]),
+				"%02x:%02x:%02x:%02x:%02x:%02x\n",
+				mac.addr_bytes[0], mac.addr_bytes[1], mac.addr_bytes[2],
+				mac.addr_bytes[3], mac.addr_bytes[4], mac.addr_bytes[5]);
+	}
+	return addresses[port];
+}
+
+/*
+ * This function displays the recorded statistics for each port
+ * and for each node. It uses ANSI terminal codes to clear
+ * screen when called. It is called from a single non-master
+ * thread in the distributor process, when the process is run with more
+ * than one lcore enabled.
+ */
+static void
+do_stats_display(void)
+{
+	unsigned i, j;
+	const char clr[] = { 27, '[', '2', 'J', '\0' };
+	const char topLeft[] = { 27, '[', '1', ';', '1', 'H','\0' };
+	uint64_t port_tx[RTE_MAX_ETHPORTS], port_tx_drop[RTE_MAX_ETHPORTS];
+	uint64_t node_tx[MAX_NODES], node_tx_drop[MAX_NODES];
+
+	/* to get TX stats, we need to do some summing calculations */
+	memset(port_tx, 0, sizeof(port_tx));
+	memset(port_tx_drop, 0, sizeof(port_tx_drop));
+	memset(node_tx, 0, sizeof(node_tx));
+	memset(node_tx_drop, 0, sizeof(node_tx_drop));
+
+	for (i = 0; i < num_nodes; i++){
+		const volatile struct tx_stats *tx = &info->tx_stats[i];
+		for (j = 0; j < info->num_ports; j++){
+			/* assign to local variables here, save re-reading volatile vars */
+			const uint64_t tx_val = tx->tx[info->id[j]];
+			const uint64_t drop_val = tx->tx_drop[info->id[j]];
+			port_tx[j] += tx_val;
+			port_tx_drop[j] += drop_val;
+			node_tx[i] += tx_val;
+			node_tx_drop[i] += drop_val;
+		}
+	}
+
+	/* Clear screen and move to top left */
+	printf("%s%s", clr, topLeft);
+
+	printf("PORTS\n");
+	printf("-----\n");
+	for (i = 0; i < info->num_ports; i++)
+		printf("Port %u: '%s'\t", (unsigned)info->id[i],
+				get_printable_mac_addr(info->id[i]));
+	printf("\n\n");
+	for (i = 0; i < info->num_ports; i++){
+		printf("Port %u - rx: %9"PRIu64"\t"
+				"tx: %9"PRIu64"\n",
+				(unsigned)info->id[i], info->rx_stats.rx[i],
+				port_tx[i]);
+	}
+
+	printf("\nFLOW DISTRIBUTOR\n");
+	printf("-----\n");
+	printf("distributed: %9"PRIu64", drop: %9"PRIu64"\n",
+			flow_dist_stats.distributed, flow_dist_stats.drop);
+
+	printf("\nNODES\n");
+	printf("-------\n");
+	for (i = 0; i < num_nodes; i++){
+		const unsigned long long rx = nodes[i].stats.rx;
+		const unsigned long long rx_drop = nodes[i].stats.rx_drop;
+		const volatile struct filter_stats *filter = &info->filter_stats[i];
+		printf("Node %2u - rx: %9llu, rx_drop: %9llu\n"
+				"            tx: %9"PRIu64", tx_drop: %9"PRIu64"\n"
+				"            filter_passed: %9"PRIu64", filter_drop: %9"PRIu64"\n",
+				i, rx, rx_drop, node_tx[i], node_tx_drop[i],
+				filter->passed, filter->drop);
+	}
+
+	printf("\n");
+}
+
+/*
+ * The function called from each non-master lcore used by the process.
+ * The test_and_set function is used to randomly pick a single lcore on which
+ * the code to display the statistics will run. Otherwise, the code just
+ * repeatedly sleeps.
+ */
+static int
+sleep_lcore(__attribute__((unused)) void *dummy)
+{
+	/* Used to pick a display thread - static, so zero-initialised */
+	static rte_atomic32_t display_stats;
+
+	/* Only one core should display stats */
+	if (rte_atomic32_test_and_set(&display_stats)) {
+		const unsigned sleeptime = 1;
+		printf("Core %u displaying statistics\n", rte_lcore_id());
+
+		/* Longer initial pause so above printf is seen */
+		sleep(sleeptime * 3);
+
+		/* Loop forever: sleep always returns 0 or <= param */
+		while (sleep(sleeptime) <= sleeptime)
+			do_stats_display();
+	}
+	return 0;
+}
+
+/*
+ * Function to set all the node statistic values to zero.
+ * Called at program startup.
+ */
+static void
+clear_stats(void)
+{
+	unsigned i;
+
+	for (i = 0; i < num_nodes; i++)
+		nodes[i].stats.rx = nodes[i].stats.rx_drop = 0;
+}
+
+/*
+ * send a burst of traffic to a node, assuming there are packets
+ * available to be sent to this node
+ */
+static void
+flush_rx_queue(uint16_t node)
+{
+	uint16_t j;
+	struct node *cl;
+
+	if (cl_rx_buf[node].count == 0)
+		return;
+
+	cl = &nodes[node];
+	if (rte_ring_enqueue_bulk(cl->rx_q, (void **)cl_rx_buf[node].buffer,
+			cl_rx_buf[node].count) != 0){
+		for (j = 0; j < cl_rx_buf[node].count; j++)
+			rte_pktmbuf_free(cl_rx_buf[node].buffer[j]);
+		cl->stats.rx_drop += cl_rx_buf[node].count;
+	}
+	else
+		cl->stats.rx += cl_rx_buf[node].count;
+
+	cl_rx_buf[node].count = 0;
+}
+
+/*
+ * marks a packet down to be sent to a particular node process
+ */
+static inline void
+enqueue_rx_packet(uint8_t node, struct rte_mbuf *buf)
+{
+	cl_rx_buf[node].buffer[cl_rx_buf[node].count++] = buf;
+}
+
+/*
+ * This function takes a group of packets and routes them
+ * individually to the node process. Very simply round-robins the packets
+ * without checking any of the packet contents.
+ */
+static void
+process_packets(uint32_t port_num __rte_unused,
+		struct rte_mbuf *pkts[], uint16_t rx_count, unsigned int socket_id)
+{
+	uint16_t i;
+	uint8_t node;
+	efd_value_t data[EFD_BURST_MAX];
+	const efd_key_t *key_ptrs[EFD_BURST_MAX];
+
+	struct ipv4_hdr *ipv4_hdr;
+	uint32_t ipv4_dst_ip[EFD_BURST_MAX];
+
+	for (i = 0; i < rx_count; i++) {
+		/* Handle IPv4 header.*/
+		ipv4_hdr = rte_pktmbuf_mtod_offset(pkts[i], struct ipv4_hdr *,
+				sizeof(struct ether_hdr));
+		ipv4_dst_ip[i] = ipv4_hdr->dst_addr;
+		key_ptrs[i] = (efd_key_t*)&ipv4_dst_ip[i];
+	}
+
+	rte_efd_lookup_bulk(efd_table, socket_id, rx_count,
+				(const efd_key_t **) key_ptrs, data);
+	for (i = 0; i < rx_count; i++) {
+		node = (uint8_t) ((uintptr_t)data[i]);
+
+		if (node >= num_nodes) {
+			/* Node is out of range, which means that flow has not been inserted */
+			flow_dist_stats.drop++;
+			rte_pktmbuf_free(pkts[i]);
+		} else {
+			flow_dist_stats.distributed++;
+			enqueue_rx_packet(node, pkts[i]);
+		}
+	}
+
+	for (i = 0; i < num_nodes; i++)
+		flush_rx_queue(i);
+}
+
+/*
+ * Function called by the master lcore of the DPDK process.
+ */
+static void
+do_packet_forwarding(void)
+{
+	unsigned port_num = 0; /* indexes the port[] array */
+	unsigned int socket_id = rte_socket_id();
+
+	for (;;) {
+		struct rte_mbuf *buf[PACKET_READ_SIZE];
+		uint16_t rx_count;
+
+		/* read a port */
+		rx_count = rte_eth_rx_burst(info->id[port_num], 0,
+				buf, PACKET_READ_SIZE);
+		info->rx_stats.rx[port_num] += rx_count;
+
+		/* Now process the NIC packets read */
+		if (likely(rx_count > 0))
+			process_packets(port_num, buf, rx_count, socket_id);
+
+		/* move to next port */
+		if (++port_num == info->num_ports)
+			port_num = 0;
+	}
+}
+
+int
+main(int argc, char *argv[])
+{
+	/* initialise the system */
+	if (init(argc, argv) < 0 )
+		return -1;
+	RTE_LOG(INFO, APP, "Finished Process Init.\n");
+
+	cl_rx_buf = calloc(num_nodes, sizeof(cl_rx_buf[0]));
+
+	/* clear statistics */
+	clear_stats();
+
+	/* put all other cores to sleep bar master */
+	rte_eal_mp_remote_launch(sleep_lcore, NULL, SKIP_MASTER);
+
+	do_packet_forwarding();
+	return 0;
+}
diff --git a/examples/flow_distributor/node/Makefile b/examples/flow_distributor/node/Makefile
new file mode 100644
index 0000000..88c669e
--- /dev/null
+++ b/examples/flow_distributor/node/Makefile
@@ -0,0 +1,48 @@
+#   BSD LICENSE
+#
+#   Copyright(c) 2010-2016 Intel Corporation. All rights reserved.
+#   All rights reserved.
+#
+#   Redistribution and use in source and binary forms, with or without
+#   modification, are permitted provided that the following conditions
+#   are met:
+#
+#     * Redistributions of source code must retain the above copyright
+#       notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above copyright
+#       notice, this list of conditions and the following disclaimer in
+#       the documentation and/or other materials provided with the
+#       distribution.
+#     * Neither the name of Intel Corporation nor the names of its
+#       contributors may be used to endorse or promote products derived
+#       from this software without specific prior written permission.
+#
+#   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+#   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+#   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+#   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+#   OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+#   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+#   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+#   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+#   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+#   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+#   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+ifeq ($(RTE_SDK),)
+$(error "Please define RTE_SDK environment variable")
+endif
+
+# Default target, can be overriden by command line or environment
+include $(RTE_SDK)/mk/rte.vars.mk
+
+# binary name
+APP = node
+
+# all source are stored in SRCS-y
+SRCS-y := node.c
+
+CFLAGS += $(WERROR_FLAGS) -O3
+CFLAGS += -I$(SRCDIR)/../shared
+
+include $(RTE_SDK)/mk/rte.extapp.mk
diff --git a/examples/flow_distributor/node/node.c b/examples/flow_distributor/node/node.c
new file mode 100644
index 0000000..3a52207
--- /dev/null
+++ b/examples/flow_distributor/node/node.c
@@ -0,0 +1,404 @@
+/*-
+ *   BSD LICENSE
+ *
+ *   Copyright(c) 2010-2016 Intel Corporation. All rights reserved.
+ *   All rights reserved.
+ *
+ *   Redistribution and use in source and binary forms, with or without
+ *   modification, are permitted provided that the following conditions
+ *   are met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in
+ *       the documentation and/or other materials provided with the
+ *       distribution.
+ *     * Neither the name of Intel Corporation nor the names of its
+ *       contributors may be used to endorse or promote products derived
+ *       from this software without specific prior written permission.
+ *
+ *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ *   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ *   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ *   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ *   OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ *   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ *   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ *   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <inttypes.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <sys/queue.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <string.h>
+
+#include <rte_common.h>
+#include <rte_malloc.h>
+#include <rte_memory.h>
+#include <rte_memzone.h>
+#include <rte_eal.h>
+#include <rte_atomic.h>
+#include <rte_branch_prediction.h>
+#include <rte_log.h>
+#include <rte_per_lcore.h>
+#include <rte_launch.h>
+#include <rte_lcore.h>
+#include <rte_ring.h>
+#include <rte_launch.h>
+#include <rte_lcore.h>
+#include <rte_debug.h>
+#include <rte_mempool.h>
+#include <rte_mbuf.h>
+#include <rte_interrupts.h>
+#include <rte_pci.h>
+#include <rte_ether.h>
+#include <rte_ethdev.h>
+#include <rte_string_fns.h>
+#include <rte_ip.h>
+
+#include "common.h"
+
+/* Number of packets to attempt to read from queue */
+#define PKT_READ_SIZE  ((uint16_t)32)
+
+/* our node id number - tells us which rx queue to read, and NIC TX
+ * queue to write to. */
+static uint8_t node_id = 0;
+
+#define MBQ_CAPACITY 32
+
+/* maps input ports to output ports for packets */
+static uint8_t output_ports[RTE_MAX_ETHPORTS];
+
+/* buffers up a set of packet that are ready to send */
+struct rte_eth_dev_tx_buffer *tx_buffer[RTE_MAX_ETHPORTS];
+
+/* shared data from distributor. We update statistics here */
+static volatile struct tx_stats *tx_stats;
+
+static volatile struct filter_stats *filter_stats;
+
+/*
+ * print a usage message
+ */
+static void
+usage(const char *progname)
+{
+	printf("Usage: %s [EAL args] -- -n <node_id>\n\n", progname);
+}
+
+/*
+ * Convert the node id number from a string to an int.
+ */
+static int
+parse_node_num(const char *node)
+{
+	char *end = NULL;
+	unsigned long temp;
+
+	if (node == NULL || *node == '\0')
+		return -1;
+
+	temp = strtoul(node, &end, 10);
+	if (end == NULL || *end != '\0')
+		return -1;
+
+	node_id = (uint8_t)temp;
+	return 0;
+}
+
+/*
+ * Parse the application arguments to the node app.
+ */
+static int
+parse_app_args(int argc, char *argv[])
+{
+	int option_index, opt;
+	char **argvopt = argv;
+	const char *progname = NULL;
+	static struct option lgopts[] = { /* no long options */
+		{NULL, 0, 0, 0 }
+	};
+	progname = argv[0];
+
+	while ((opt = getopt_long(argc, argvopt, "n:", lgopts,
+		&option_index)) != EOF){
+		switch (opt){
+			case 'n':
+				if (parse_node_num(optarg) != 0){
+					usage(progname);
+					return -1;
+				}
+				break;
+			default:
+				usage(progname);
+				return -1;
+		}
+	}
+	return 0;
+}
+
+/*
+ * Tx buffer error callback
+ */
+static void
+flush_tx_error_callback(struct rte_mbuf **unsent, uint16_t count,
+		void *userdata) {
+	int i;
+	uint8_t port_id = (uintptr_t)userdata;
+
+	tx_stats->tx_drop[port_id] += count;
+
+	/* free the mbufs which failed from transmit */
+	for (i = 0; i < count; i++)
+		rte_pktmbuf_free(unsent[i]);
+
+}
+
+static void
+configure_tx_buffer(uint8_t port_id, uint16_t size)
+{
+	int ret;
+
+	/* Initialize TX buffers */
+	tx_buffer[port_id] = rte_zmalloc_socket("tx_buffer",
+			RTE_ETH_TX_BUFFER_SIZE(size), 0,
+			rte_eth_dev_socket_id(port_id));
+	if (tx_buffer[port_id] == NULL)
+		rte_exit(EXIT_FAILURE, "Cannot allocate buffer for tx on port %u\n",
+				(unsigned) port_id);
+
+	rte_eth_tx_buffer_init(tx_buffer[port_id], size);
+
+	ret = rte_eth_tx_buffer_set_err_callback(tx_buffer[port_id],
+			flush_tx_error_callback, (void *)(intptr_t)port_id);
+	if (ret < 0)
+			rte_exit(EXIT_FAILURE, "Cannot set error callback for "
+					"tx buffer on port %u\n", (unsigned) port_id);
+}
+
+/*
+ * set up output ports so that all traffic on port gets sent out
+ * its paired port. Index using actual port numbers since that is
+ * what comes in the mbuf structure.
+ */
+static void
+configure_output_ports(const struct shared_info *info)
+{
+	int i;
+	if (info->num_ports > RTE_MAX_ETHPORTS)
+		rte_exit(EXIT_FAILURE, "Too many ethernet ports. RTE_MAX_ETHPORTS = %u\n",
+				(unsigned)RTE_MAX_ETHPORTS);
+	for (i = 0; i < info->num_ports - 1; i+=2){
+		uint8_t p1 = info->id[i];
+		uint8_t p2 = info->id[i+1];
+		output_ports[p1] = p2;
+		output_ports[p2] = p1;
+
+		configure_tx_buffer(p1, MBQ_CAPACITY);
+		configure_tx_buffer(p2, MBQ_CAPACITY);
+
+	}
+}
+
+/*
+ * Create the hash table that will contain the flows that
+ * the node will handle, which will be used to decide if packet
+ * is transmitted or dropped.
+ */
+static struct rte_hash *
+create_hash_table(const struct shared_info *info)
+{
+	uint32_t num_flows_node = info->num_flows / info->num_nodes;
+	char name[RTE_HASH_NAMESIZE];
+	struct rte_hash *h;
+
+	/* create table */
+	struct rte_hash_parameters hash_params = {
+		.entries = num_flows_node * 2, /* table load = 50% */
+		.key_len = sizeof(uint32_t), /* Store IPv4 dest IP address */
+		.socket_id = rte_socket_id(),
+		.hash_func_init_val = 0,
+	};
+
+	snprintf(name, sizeof(name), "hash_table_%d", node_id);
+	hash_params.name = name;
+	h = rte_hash_create(&hash_params);
+
+	if (h == NULL)
+		rte_exit(EXIT_FAILURE,
+				"Problem creating the hash table for node %d\n",
+				node_id);
+	return h;
+}
+
+static void
+populate_hash_table(const struct rte_hash *h, const struct shared_info *info)
+{
+	unsigned int i;
+	int32_t ret;
+	uint32_t ip_dst;
+	uint32_t num_flows_node = 0;
+	uint64_t target_node;
+
+	/* Add flows in table */
+	for (i = 0; i < info->num_flows; i++) {
+		target_node = i % info->num_nodes;
+		if (target_node != node_id)
+			continue;
+
+		ip_dst = rte_cpu_to_be_32(i);
+
+		ret = rte_hash_add_key(h, (void *) &ip_dst);
+		if (ret < 0)
+			rte_exit(EXIT_FAILURE, "Unable to add entry %u in hash table\n", i);
+		else
+			num_flows_node++;
+
+	}
+
+	printf("Hash table: Adding 0x%x keys\n", num_flows_node);
+}
+
+/*
+ * This function performs routing of packets
+ * Just sends each input packet out an output port based solely on the input
+ * port it arrived on.
+ */
+static inline void
+transmit_packet(struct rte_mbuf *buf)
+{
+	int sent;
+	const uint8_t in_port = buf->port;
+	const uint8_t out_port = output_ports[in_port];
+	struct rte_eth_dev_tx_buffer *buffer = tx_buffer[out_port];
+
+	sent = rte_eth_tx_buffer(out_port, node_id, buffer, buf);
+	if (sent)
+		tx_stats->tx[out_port] += sent;
+
+}
+
+static inline int
+handle_packets(struct rte_hash *h, struct rte_mbuf **bufs, uint16_t num_packets)
+{
+	struct ipv4_hdr *ipv4_hdr;
+	uint32_t ipv4_dst_ip[PKT_READ_SIZE];
+	const void *key_ptrs[PKT_READ_SIZE];
+	unsigned int i;
+	int32_t positions[PKT_READ_SIZE] = {0};
+
+	for (i = 0; i < num_packets; i++) {
+		/* Handle IPv4 header.*/
+		ipv4_hdr = rte_pktmbuf_mtod_offset(bufs[i], struct ipv4_hdr *,
+				sizeof(struct ether_hdr));
+		ipv4_dst_ip[i] = ipv4_hdr->dst_addr;
+		key_ptrs[i] = &ipv4_dst_ip[i];
+	}
+	/* Check if packets belongs to any flows handled by this node */
+	rte_hash_lookup_bulk(h, key_ptrs, num_packets, positions);
+
+	for (i = 0; i < num_packets; i++) {
+		if (likely(positions[i] >= 0)) {
+			filter_stats->passed++;
+			transmit_packet(bufs[i]);
+		} else {
+			filter_stats->drop++;
+			/* Drop packet, as flow is not handled by this node */
+			rte_pktmbuf_free(bufs[i]);
+		}
+	}
+
+	return 1;
+}
+
+/*
+ * Application main function - loops through
+ * receiving and processing packets. Never returns
+ */
+int
+main(int argc, char *argv[])
+{
+	const struct rte_memzone *mz;
+	struct rte_ring *rx_ring;
+	struct rte_hash *h;
+	struct rte_mempool *mp;
+	struct shared_info *info;
+	int need_flush = 0; /* indicates whether we have unsent packets */
+	int retval;
+	void *pkts[PKT_READ_SIZE];
+	uint16_t sent;
+
+	if ((retval = rte_eal_init(argc, argv)) < 0)
+		return -1;
+	argc -= retval;
+	argv += retval;
+
+	if (parse_app_args(argc, argv) < 0)
+		rte_exit(EXIT_FAILURE, "Invalid command-line arguments\n");
+
+	if (rte_eth_dev_count() == 0)
+		rte_exit(EXIT_FAILURE, "No Ethernet ports - bye\n");
+
+	rx_ring = rte_ring_lookup(get_rx_queue_name(node_id));
+	if (rx_ring == NULL)
+		rte_exit(EXIT_FAILURE, "Cannot get RX ring - is distributor process running?\n");
+
+	mp = rte_mempool_lookup(PKTMBUF_POOL_NAME);
+	if (mp == NULL)
+		rte_exit(EXIT_FAILURE, "Cannot get mempool for mbufs\n");
+
+	mz = rte_memzone_lookup(MZ_SHARED_INFO);
+	if (mz == NULL)
+		rte_exit(EXIT_FAILURE, "Cannot get port info structure\n");
+	info = mz->addr;
+	tx_stats = &(info->tx_stats[node_id]);
+	filter_stats = &(info->filter_stats[node_id]);
+
+	configure_output_ports(info);
+
+	h = create_hash_table(info);
+
+	populate_hash_table(h, info);
+
+	RTE_LOG(INFO, APP, "Finished Process Init.\n");
+
+	printf("\nNode process %d handling packets\n", node_id);
+	printf("[Press Ctrl-C to quit ...]\n");
+
+	for (;;) {
+		uint16_t  rx_pkts = PKT_READ_SIZE;
+		uint8_t port;
+
+		/* try dequeuing max possible packets first, if that fails, get the
+		 * most we can. Loop body should only execute once, maximum */
+		while (rx_pkts > 0 &&
+				unlikely(rte_ring_dequeue_bulk(rx_ring, pkts, rx_pkts) != 0))
+			rx_pkts = (uint16_t)RTE_MIN(rte_ring_count(rx_ring), PKT_READ_SIZE);
+
+		if (unlikely(rx_pkts == 0)){
+			if (need_flush)
+				for (port = 0; port < info->num_ports; port++) {
+					sent = rte_eth_tx_buffer_flush(info->id[port], node_id,
+							tx_buffer[port]);
+					if (unlikely(sent))
+						tx_stats->tx[port] += sent;
+				}
+			need_flush = 0;
+			continue;
+		}
+
+		handle_packets(h, (struct rte_mbuf **)pkts, rx_pkts);
+
+		need_flush = 1;
+	}
+}
diff --git a/examples/flow_distributor/shared/common.h b/examples/flow_distributor/shared/common.h
new file mode 100644
index 0000000..b635b71
--- /dev/null
+++ b/examples/flow_distributor/shared/common.h
@@ -0,0 +1,97 @@
+/*-
+ *   BSD LICENSE
+ *
+ *   Copyright(c) 2010-2016 Intel Corporation. All rights reserved.
+ *   All rights reserved.
+ *
+ *   Redistribution and use in source and binary forms, with or without
+ *   modification, are permitted provided that the following conditions
+ *   are met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in
+ *       the documentation and/or other materials provided with the
+ *       distribution.
+ *     * Neither the name of Intel Corporation nor the names of its
+ *       contributors may be used to endorse or promote products derived
+ *       from this software without specific prior written permission.
+ *
+ *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ *   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ *   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ *   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ *   OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ *   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ *   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ *   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _COMMON_H_
+#define _COMMON_H_
+
+#include <rte_hash_crc.h>
+#include <rte_hash.h>
+
+#define MAX_NODES             16
+/*
+ * Shared port info, including statistics information for display by distributor.
+ * Structure will be put in a memzone.
+ * - All port id values share one cache line as this data will be read-only
+ * during operation.
+ * - All rx statistic values share cache lines, as this data is written only
+ * by the distributor process. (rare reads by stats display)
+ * - The tx statistics have values for all ports per cache line, but the stats
+ * themselves are written by the nodes, so we have a distinct set, on different
+ * cache lines for each node to use.
+ */
+struct rx_stats {
+	uint64_t rx[RTE_MAX_ETHPORTS];
+} __rte_cache_aligned;
+
+struct tx_stats {
+	uint64_t tx[RTE_MAX_ETHPORTS];
+	uint64_t tx_drop[RTE_MAX_ETHPORTS];
+} __rte_cache_aligned;
+
+struct filter_stats {
+	uint64_t drop;
+	uint64_t passed;
+} __rte_cache_aligned;
+
+struct shared_info {
+	uint8_t num_nodes;
+	uint8_t num_ports;
+	uint32_t num_flows;
+	uint8_t id[RTE_MAX_ETHPORTS];
+	volatile struct rx_stats rx_stats;
+	volatile struct tx_stats tx_stats[MAX_NODES];
+	volatile struct filter_stats filter_stats[MAX_NODES];
+};
+
+/* define common names for structures shared between distributor and node */
+#define MP_NODE_RXQ_NAME "MProc_Node_%u_RX"
+#define PKTMBUF_POOL_NAME "MProc_pktmbuf_pool"
+#define MZ_SHARED_INFO "MProc_shared_info"
+
+/*
+ * Given the rx queue name template above, get the queue name
+ */
+static inline const char *
+get_rx_queue_name(unsigned id)
+{
+	/* buffer for return value. Size calculated by %u being replaced
+	 * by maximum 3 digits (plus an extra byte for safety) */
+	static char buffer[sizeof(MP_NODE_RXQ_NAME) + 2];
+
+	snprintf(buffer, sizeof(buffer) - 1, MP_NODE_RXQ_NAME, id);
+	return buffer;
+}
+
+#define RTE_LOGTYPE_APP RTE_LOGTYPE_USER1
+
+#endif
diff --git a/lib/Makefile b/lib/Makefile
index 990f23a..9a41188 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -43,6 +43,7 @@ DIRS-$(CONFIG_RTE_LIBRTE_ETHER) += librte_ether
 DIRS-$(CONFIG_RTE_LIBRTE_CRYPTODEV) += librte_cryptodev
 DIRS-$(CONFIG_RTE_LIBRTE_VHOST) += librte_vhost
 DIRS-$(CONFIG_RTE_LIBRTE_HASH) += librte_hash
+DIRS-$(CONFIG_RTE_LIBRTE_EFD) += librte_efd
 DIRS-$(CONFIG_RTE_LIBRTE_LPM) += librte_lpm
 DIRS-$(CONFIG_RTE_LIBRTE_ACL) += librte_acl
 DIRS-$(CONFIG_RTE_LIBRTE_NET) += librte_net
diff --git a/lib/librte_eal/common/include/rte_log.h b/lib/librte_eal/common/include/rte_log.h
index 29f7d19..70e150d 100644
--- a/lib/librte_eal/common/include/rte_log.h
+++ b/lib/librte_eal/common/include/rte_log.h
@@ -79,6 +79,7 @@ extern struct rte_logs rte_logs;
 #define RTE_LOGTYPE_PIPELINE 0x00008000 /**< Log related to pipeline. */
 #define RTE_LOGTYPE_MBUF    0x00010000 /**< Log related to mbuf. */
 #define RTE_LOGTYPE_CRYPTODEV 0x00020000 /**< Log related to cryptodev. */
+#define RTE_LOGTYPE_EFD     0x00040000 /**< Log related to cryptodev. */
 
 /* these log types can be used in an application */
 #define RTE_LOGTYPE_USER1   0x01000000 /**< User-defined log type 1. */
diff --git a/lib/librte_efd/Makefile b/lib/librte_efd/Makefile
new file mode 100644
index 0000000..ae21bc7
--- /dev/null
+++ b/lib/librte_efd/Makefile
@@ -0,0 +1,56 @@
+#   BSD LICENSE
+#
+#   Copyright(c) 2016 Intel Corporation. All rights reserved.
+#   All rights reserved.
+#
+#   Redistribution and use in source and binary forms, with or without
+#   modification, are permitted provided that the following conditions
+#   are met:
+#
+#     * Redistributions of source code must retain the above copyright
+#       notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above copyright
+#       notice, this list of conditions and the following disclaimer in
+#       the documentation and/or other materials provided with the
+#       distribution.
+#     * Neither the name of Intel Corporation nor the names of its
+#       contributors may be used to endorse or promote products derived
+#       from this software without specific prior written permission.
+#
+#   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+#   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+#   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+#   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+#   OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+#   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+#   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+#   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+#   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+#   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+#   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+include $(RTE_SDK)/mk/rte.vars.mk
+
+# library name
+LIB = librte_efd.a
+
+CFLAGS += $(WERROR_FLAGS) -I$(SRCDIR) -O3
+CFLAGS += -D_GNU_SOURCE
+
+EXPORT_MAP := rte_efd_version.map
+
+LIBABIVER := 1
+
+# all source are stored in SRCS-y
+SRCS-$(CONFIG_RTE_LIBRTE_EFD) := rte_efd.c
+
+# install this header file
+SYMLINK-$(CONFIG_RTE_LIBRTE_EFD)-include := rte_efd.h
+
+# this lib depends upon:
+DEPDIRS-$(CONFIG_RTE_LIBRTE_EFD) += lib/librte_mbuf
+DEPDIRS-$(CONFIG_RTE_LIBRTE_EFD) += lib/librte_mempool
+DEPDIRS-$(CONFIG_RTE_LIBRTE_EFD) += lib/librte_eal
+DEPDIRS-$(CONFIG_RTE_LIBRTE_EFD) += lib/librte_ether
+
+include $(RTE_SDK)/mk/rte.lib.mk
diff --git a/lib/librte_efd/rte_efd.c b/lib/librte_efd/rte_efd.c
new file mode 100644
index 0000000..4c9606e
--- /dev/null
+++ b/lib/librte_efd/rte_efd.c
@@ -0,0 +1,969 @@
+/*-
+ *   BSD LICENSE
+ *
+ *   Copyright(c) 2016 Intel Corporation. All rights reserved.
+ *   All rights reserved.
+ *
+ *   Redistribution and use in source and binary forms, with or without
+ *   modification, are permitted provided that the following conditions
+ *   are met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in
+ *       the documentation and/or other materials provided with the
+ *       distribution.
+ *     * Neither the name of Intel Corporation nor the names of its
+ *       contributors may be used to endorse or promote products derived
+ *       from this software without specific prior written permission.
+ *
+ *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ *   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ *   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ *   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ *   OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ *   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ *   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ *   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <immintrin.h>
+#include <math.h>
+#include <sys/queue.h>
+
+#include <rte_log.h>
+#include <rte_eal_memconfig.h>
+#include <rte_errno.h>
+#include <rte_malloc.h>
+#include <rte_memzone.h>
+#include <rte_prefetch.h>
+#include <rte_branch_prediction.h>
+
+#include "rte_efd.h"
+
+/** Hash function used to determine chunk_id and bin_id for a group */
+#define EFD_HASH(key) (efd_hash_internal((key), 0xbc9f1d34))
+/** Hash function used as constant component of perfect hash search */
+#define EFD_HASHFUNCA(key) (efd_hash_internal((key), 0xbc9f1d35))
+/** Hash function used as multiplicative component of perfect hash search */
+#define EFD_HASHFUNCB(key) (efd_hash_internal((key), 0xbc9f1d36))
+
+TAILQ_HEAD(rte_efd_list, rte_tailq_entry);
+
+static struct rte_tailq_elem rte_efd_tailq = {
+	.name = "RTE_EFD",
+};
+EAL_REGISTER_TAILQ(rte_efd_tailq);
+
+/** Internal permutation array used to shuffle bins into pseudorandom groups */
+const uint32_t efd_bin_to_group[EFD_CHUNK_NUM_BIN_TO_GROUP_SETS][EFD_CHUNK_NUM_BINS] = {
+	{
+		0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3,
+		4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7,
+		8, 8, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 11, 11, 11, 11,
+		12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15,
+		16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, 19, 19, 19, 19,
+		20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23,
+		24, 24, 24, 24, 25, 25, 25, 25, 26, 26, 26, 26, 27, 27, 27, 27,
+		28, 28, 28, 28, 29, 29, 29, 29, 30, 30, 30, 30, 31, 31, 31, 31,
+		32, 32, 32, 32, 33, 33, 33, 33, 34, 34, 34, 34, 35, 35, 35, 35,
+		36, 36, 36, 36, 37, 37, 37, 37, 38, 38, 38, 38, 39, 39, 39, 39,
+		40, 40, 40, 40, 41, 41, 41, 41, 42, 42, 42, 42, 43, 43, 43, 43,
+		44, 44, 44, 44, 45, 45, 45, 45, 46, 46, 46, 46, 47, 47, 47, 47,
+		48, 48, 48, 48, 49, 49, 49, 49, 50, 50, 50, 50, 51, 51, 51, 51,
+		52, 52, 52, 52, 53, 53, 53, 53, 54, 54, 54, 54, 55, 55, 55, 55,
+		56, 56, 56, 56, 57, 57, 57, 57, 58, 58, 58, 58, 59, 59, 59, 59,
+		60, 60, 60, 60, 61, 61, 61, 61, 62, 62, 62, 62, 63, 63, 63, 63
+	},
+	{
+		34, 33, 48, 59, 0, 21, 36, 18, 9, 49, 54, 38, 51, 23, 31, 5,
+		44, 23, 37, 52, 11, 4, 58, 20, 38, 40, 38, 22, 26, 28, 42, 6,
+		46, 16, 31, 28, 46, 14, 60, 0, 35, 53, 16, 58, 16, 29, 39, 7,
+		1, 54, 15, 11, 48, 3, 62, 9, 58, 5, 30, 43, 17, 7, 36, 34,
+		6, 36, 2, 14, 10, 1, 47, 47, 20, 45, 62, 56, 34, 25, 39, 18,
+		51, 41, 61, 25, 56, 40, 41, 37, 52, 35, 30, 57, 11, 42, 37, 27,
+		54, 19, 26, 13, 48, 31, 46, 15, 12, 10, 16, 20, 43, 17, 12, 55,
+		45, 18, 8, 41, 7, 31, 42, 63, 12, 14, 21, 57, 24, 40, 5, 41,
+		13, 44, 23, 59, 25, 57, 52, 50, 62, 1, 2, 49, 32, 57, 26, 43,
+		56, 60, 55, 5, 49, 6, 3, 50, 46, 39, 27, 33, 17, 4, 53, 13,
+		2, 19, 36, 51, 63, 0, 22, 33, 59, 28, 29, 23, 45, 33, 53, 27,
+		22, 21, 40, 56, 4, 18, 44, 47, 28, 17, 4, 50, 21, 62, 8, 39,
+		0, 8, 15, 24, 29, 24, 9, 11, 48, 61, 35, 55, 43, 1, 54, 42,
+		53, 60, 22, 3, 32, 52, 25, 8, 15, 60, 7, 55, 27, 63, 19, 10,
+		63, 24, 61, 19, 12, 38, 6, 29, 13, 37, 10, 3, 45, 32, 32, 30,
+		49, 61, 44, 14, 20, 58, 35, 30, 2, 26, 34, 51, 9, 59, 47, 50
+	},
+	{
+		32, 35, 32, 34, 55, 5, 6, 23, 49, 11, 6, 23, 52, 37, 29, 54,
+		55, 40, 63, 50, 29, 52, 61, 25, 12, 56, 39, 38, 29, 11, 46, 1,
+		40, 11, 19, 56, 7, 28, 51, 16, 15, 48, 21, 51, 60, 31, 14, 22,
+		41, 47, 59, 56, 53, 28, 58, 26, 43, 27, 41, 33, 24, 52, 44, 38,
+		13, 59, 48, 51, 60, 15, 3, 30, 15, 0, 10, 62, 44, 14, 28, 51,
+		38, 2, 41, 26, 25, 49, 10, 12, 55, 57, 27, 35, 19, 33, 0, 30,
+		5, 36, 47, 53, 5, 53, 20, 43, 34, 37, 52, 41, 21, 63, 59, 9,
+		24, 1, 45, 24, 39, 44, 45, 16, 9, 17, 7, 50, 57, 22, 18, 28,
+		25, 45, 2, 40, 58, 15, 17, 3, 1, 27, 61, 39, 19, 0, 19, 21,
+		57, 62, 54, 60, 54, 40, 48, 33, 36, 37, 4, 42, 1, 43, 58, 8,
+		13, 42, 10, 56, 35, 22, 48, 61, 63, 10, 49, 9, 24, 9, 25, 57,
+		33, 18, 13, 31, 42, 36, 36, 55, 30, 37, 53, 34, 59, 4, 4, 23,
+		8, 16, 58, 14, 30, 11, 12, 63, 49, 62, 2, 39, 47, 22, 2, 60,
+		18, 8, 46, 31, 6, 20, 32, 29, 46, 42, 20, 31, 32, 61, 34, 4,
+		47, 26, 20, 43, 26, 21, 7, 3, 16, 35, 18, 44, 27, 62, 13, 23,
+		6, 50, 12, 8, 45, 17, 3, 46, 50, 7, 14, 5, 17, 54, 38, 0
+	},
+	{
+		29, 56, 5, 7, 54, 48, 23, 37, 35, 44, 52, 40, 33, 49, 60, 0,
+		59, 51, 28, 12, 41, 26, 2, 23, 34, 5, 59, 40, 3, 19, 6, 26,
+		35, 53, 45, 49, 29, 57, 28, 62, 58, 59, 19, 53, 59, 62, 6, 54,
+		13, 15, 48, 50, 45, 21, 41, 12, 34, 40, 24, 56, 19, 21, 35, 18,
+		55, 45, 9, 61, 47, 61, 19, 15, 16, 39, 17, 31, 3, 51, 21, 50,
+		17, 25, 25, 11, 44, 16, 18, 28, 14, 2, 37, 61, 58, 27, 62, 4,
+		14, 17, 1, 9, 46, 28, 37, 0, 53, 43, 57, 7, 57, 46, 21, 41,
+		39, 14, 52, 60, 44, 53, 49, 60, 49, 63, 13, 11, 29, 1, 55, 47,
+		55, 12, 60, 43, 54, 37, 13, 6, 42, 10, 36, 13, 9, 8, 34, 51,
+		31, 32, 12, 7, 57, 2, 26, 14, 3, 30, 63, 3, 32, 1, 5, 11,
+		27, 24, 26, 44, 31, 23, 56, 38, 62, 0, 40, 30, 6, 23, 38, 2,
+		47, 5, 15, 27, 16, 10, 31, 25, 22, 63, 30, 25, 20, 33, 32, 50,
+		29, 43, 55, 10, 50, 45, 56, 20, 4, 7, 27, 46, 11, 16, 22, 52,
+		35, 20, 41, 54, 46, 33, 42, 18, 63, 8, 22, 58, 36, 4, 51, 42,
+		38, 32, 38, 22, 17, 0, 47, 8, 48, 8, 48, 1, 61, 36, 33, 20,
+		24, 39, 39, 18, 30, 36, 9, 43, 42, 24, 10, 58, 4, 15, 34, 52
+	},
+};
+
+
+/**
+ * Internal function used to compute the hash of a key given a particular seed
+ *
+ * @param[in] key   key to hash
+ * @param[in] seed  seed for the hash function
+ *
+ * @return 32-bit hash
+ */
+static inline uint32_t
+efd_hash_internal(const efd_key_t *const key, const uint32_t seed)
+{
+	return (uint32_t)(rte_hash_crc(key->bytes, EFD_KEY_LEN, seed));
+}
+
+
+/**
+ * Computes the chunk ID for a given key hash
+ *
+ * @param[in]  table           EFD table to reference
+ * @param[in]  hashed_key      32-bit key hash returned by EFD_HASH
+ *
+ * @return chunk ID containing this key hash
+ */
+static inline uint32_t
+efd_get_chunk_id(const struct rte_efd_table *const table,
+				const uint32_t hashed_key)
+{
+	return hashed_key & (table->num_chunks - 1);
+}
+
+/**
+ * Computes the bin ID for a given key hash
+ *
+ * @param[in]  table           EFD table to reference
+ * @param[in]  hashed_key      32-bit key hash returned by EFD_HASH
+ *
+ * @return bin ID containing this key hash
+ */
+static inline uint32_t
+efd_get_bin_id(const struct rte_efd_table *const table,
+			const uint32_t hashed_key)
+{
+	return (hashed_key >> table->num_chunks_shift) & (EFD_CHUNK_NUM_BINS - 1);
+}
+
+/**
+ * Looks up the current permutation choice for a particular bin in the online table
+ *
+ * @param[in]  table           EFD table to reference
+ * @param[in]  socket_id       Socket ID to use to look up existing values
+ *                             (ideally caller's socket id)
+ * @param[in]  chunk_id        Chunk ID of bin to look up
+ * @param[in]  bin_id          Bin ID to look up
+ *
+ * @return
+ *   Currently active permutation choice in the online table
+ */
+static inline uint8_t
+efd_get_choice(const struct rte_efd_table *const table,
+				const unsigned int socket_id,
+				const uint32_t chunk_id,
+				const uint32_t bin_id)
+{
+	struct efd_online_chunk *chunk = &table->chunks[socket_id][chunk_id];
+
+	/* Grab the chunk (byte) that contains the choices for four neighboring bins */
+	uint8_t choice_chunk =
+		chunk->bin_choice_list[bin_id / EFD_CHUNK_NUM_BIN_TO_GROUP_SETS];
+
+	/* Compute the offset into the chunk that contains the group_id lookup position */
+	int offset = (bin_id & 0x3) * 2;
+
+	/* Extract from the byte just the desired lookup position */
+	return (uint8_t)((choice_chunk >> offset) & 0x3);
+}
+
+/**
+ * Compute the chunk_id and bin_id for a given key
+ *
+ * @param[in]  table           EFD table to reference
+ * @param[in]  key             Key to hash and find location of
+ * @param[out] chunk_id        Computed chunk ID
+ * @param[out] bin_id          Computed bin ID
+ *
+ */
+static inline void
+efd_compute_ids(const struct rte_efd_table *const table, const efd_key_t *const key,
+                                   uint32_t *const chunk_id, uint32_t *const bin_id)
+{
+	/* Compute the position of the entry in the hash table */
+	uint32_t h = EFD_HASH(key);
+
+	/* Compute the chunk_id where that entry can be found */
+	*chunk_id = efd_get_chunk_id(table, h);
+
+	/* Compute the bin within that chunk where the entry can be found (0 - 255) */
+	*bin_id = efd_get_bin_id(table, h);
+}
+
+/**
+ * Search for a hash function for a group that satisfies all group results
+ */
+static inline int
+efd_search_hash(const struct efd_offline_group_rules *const off_group,
+                    struct efd_online_group_entry *const on_group)
+{
+	efd_hashfunc_t hash_idx;
+	efd_hashfunc_t start_hash_idx[EFD_VALUE_NUM_BITS];
+	efd_lookuptbl_t start_lookup_table[EFD_VALUE_NUM_BITS];
+
+	uint32_t i, j, rule_id;
+	uint32_t hash_val_a[EFD_MAX_GROUP_NUM_RULES];
+	uint32_t hash_val_b[EFD_MAX_GROUP_NUM_RULES];
+	uint32_t hash_val[EFD_MAX_GROUP_NUM_RULES];
+
+
+	rte_prefetch0(off_group->value);
+
+	/*
+	 * Prepopulate the hash_val tables by running the two hash functions
+	 * for each provided rule
+	 */
+	for (i = 0; i < off_group->num_rules; i++) {
+		hash_val_b[i] = EFD_HASHFUNCB(&off_group->key[i]);
+		hash_val_a[i] = EFD_HASHFUNCA(&off_group->key[i]);
+	}
+
+	for (i = 0; i < EFD_VALUE_NUM_BITS; i++) {
+		hash_idx = on_group->hash_idx[i];
+		start_hash_idx[i] = hash_idx;
+
+		do {
+			efd_lookuptbl_t lookup_table = 0;
+			efd_lookuptbl_t lookup_table_complement = 0;
+
+			for (rule_id = 0; rule_id < off_group->num_rules; rule_id++)
+				hash_val[rule_id] = hash_val_a[rule_id] + hash_idx *
+									hash_val_b[rule_id];
+
+			/*
+			 * The goal here is to find a hash function for this particular
+			 * bit entry that meets the following criteria:
+			 * - The most significant bits of the hash result define a shift
+			 *   into the lookup table where the bit will be stored
+			 */
+
+			/* Iterate over each provided rule */
+			for (rule_id = 0; rule_id < off_group->num_rules; rule_id++) {
+				/*
+				 * Use the few most significant bits (number based on
+				 * EFD_LOOKUPTBL_SIZE) to see what position the
+				 * expected bit should be set in the lookup_table
+				 */
+				uint32_t bucket_idx = hash_val[rule_id] >>
+						EFD_LOOKUPTBL_SHIFT;
+
+				/*
+				 * Get the current bit of interest. This only finds
+				 * an appropriate hash function for one bit
+				 * at a time of the rule
+				 */
+				efd_lookuptbl_t expected =
+					(off_group->value[rule_id] >> i) & 0x1;
+
+				/*
+				 * Add the expected bit (if set) to a map (lookup_table).
+				 * Also set its complement in lookup_table_complement
+				 */
+				lookup_table |= expected << bucket_idx;
+				lookup_table_complement |= (1 - expected) << bucket_idx;
+
+				/*
+				 * If ever the hash function of two different
+				 * elements result in different values at the
+				 * same location in the lookup_table,
+				 * the current hash_idx is not valid.
+				 */
+				if (lookup_table & lookup_table_complement)
+					break;
+			}
+
+			/* Check if the previous loop completed without breaking early */
+			if (rule_id == off_group->num_rules) {
+				/* Current hash function worked, store it
+				 * for the current group */
+				on_group->hash_idx[i] = hash_idx;
+				on_group->lookup_table[i] = lookup_table;
+
+				/*
+				 * Make sure that the hash function has changed
+				 * from the starting value
+				 */
+				hash_idx = start_hash_idx[i] + 1;
+				break;
+			}
+			hash_idx++;
+
+		} while (hash_idx != start_hash_idx[i]);
+
+		/* Failed to find perfect hash for this group */
+		if (hash_idx == start_hash_idx[i]) {
+			/* Restore previous hash_idx and lookup_table for all value bits */
+			for (j = 0; j < i; j++) {
+				on_group->hash_idx[j] = start_hash_idx[j];
+				on_group->lookup_table[j] = start_lookup_table[j];
+			}
+			return 1;
+		}
+	}
+
+	return 0;
+}
+
+struct rte_efd_table *
+rte_efd_create(const char *name, uint32_t max_num_rules,
+			uint8_t online_cpu_socket_bitmask,
+			uint8_t offline_cpu_socket)
+{
+	struct rte_efd_table *table = NULL;
+	uint32_t num_chunks, num_chunks_shift;
+	uint8_t socket_id;
+	struct rte_efd_list* efd_list = NULL;
+	struct rte_tailq_entry *te;
+
+	efd_list = RTE_TAILQ_CAST(rte_efd_tailq.head, rte_efd_list);
+
+	if (online_cpu_socket_bitmask == 0) {
+		RTE_LOG(ERR, EFD, "At least one CPU socket must be enabled in the bitmask\n");
+		return NULL;
+	}
+
+	if (max_num_rules == 0) {
+		RTE_LOG(ERR, EFD, "Max num rules must be higher than 0\n");
+		return NULL;
+	}
+
+	/*
+	 * Compute the minimum number of chunks (smallest power of 2)
+	 * that can hold all of the rules
+	 */
+	if (max_num_rules % EFD_TARGET_CHUNK_NUM_RULES == 0)
+		num_chunks = rte_align32pow2(max_num_rules/EFD_TARGET_CHUNK_NUM_RULES);
+	else
+		num_chunks = rte_align32pow2((max_num_rules/EFD_TARGET_CHUNK_NUM_RULES) + 1);
+
+	num_chunks_shift = log2(num_chunks);
+
+	rte_rwlock_write_lock(RTE_EAL_TAILQ_RWLOCK);
+
+	/* guarantee there's no existing: this is normally already checked
+	 * by ring creation above */
+	TAILQ_FOREACH(te, efd_list, next) {
+		table = (struct rte_efd_table *) te->data;
+		if (strncmp(name, table->name, RTE_EFD_NAMESIZE) == 0)
+			break;
+	}
+
+	table = NULL;
+	if (te != NULL) {
+		rte_errno = EEXIST;
+		te = NULL;
+		goto error_unlock_exit;
+	}
+
+	te = rte_zmalloc("EFD_TAILQ_ENTRY", sizeof(*te), 0);
+	if (te == NULL) {
+		RTE_LOG(ERR, EFD, "tailq entry allocation failed\n");
+		goto error_unlock_exit;
+	}
+
+	/* Create a new EFD table management structure */
+	table = (struct rte_efd_table *) rte_zmalloc_socket(NULL,
+				sizeof(struct rte_efd_table),
+				RTE_CACHE_LINE_SIZE,
+				offline_cpu_socket);
+	if (table == NULL) {
+		RTE_LOG(ERR, EFD, "Allocating EFD table management structure"
+				" on socket %u failed\n",
+				offline_cpu_socket);
+		goto error_unlock_exit;
+	}
+
+
+	RTE_LOG(DEBUG, EFD, "Allocated EFD table management structure on socket %u\n",
+			offline_cpu_socket);
+
+	table->max_num_rules = num_chunks * EFD_TARGET_CHUNK_NUM_RULES;
+	table->num_rules = 0;
+	table->num_chunks = num_chunks;
+	table->num_chunks_shift = num_chunks_shift;
+	snprintf(table->name, sizeof(table->name), "%s", name);
+
+	RTE_LOG(DEBUG, EFD, "Creating an EFD table with %u chunks,"
+			" which potentially supports %u entries\n",
+			num_chunks, table->max_num_rules);
+
+	/* Make sure all the allocatable table pointers are NULL initially */
+	for (socket_id = 0; socket_id < RTE_MAX_NUMA_NODES; socket_id++) {
+		table->chunks[socket_id] = NULL;
+	}
+	table->offline_chunks = NULL;
+
+	/* Allocate one online table per socket specified in the user-supplied bitmask */
+	uint64_t online_table_size = num_chunks * sizeof(struct efd_online_chunk) +
+	                             EFD_NUM_CHUNK_PADDING_BYTES;
+
+	for (socket_id = 0; socket_id < RTE_MAX_NUMA_NODES; socket_id++) {
+		if ((online_cpu_socket_bitmask >> socket_id) & 0x01) {
+			/*
+			 * Allocate all of the EFD table chunks (the online portion)
+			 * as a continuous block
+			 */
+			table->chunks[socket_id] =
+				(struct efd_online_chunk *) rte_zmalloc_socket(NULL,
+					online_table_size,
+					RTE_CACHE_LINE_SIZE,
+					socket_id);
+			if (table->chunks[socket_id] == NULL) {
+				RTE_LOG(ERR, EFD,
+						"Allocating EFD online table on "
+						"socket %u failed\n",
+						socket_id);
+				goto error_unlock_exit;
+			}
+			RTE_LOG(DEBUG, EFD,
+					"Allocated EFD online table of size %lu bytes (%.2f MB) "
+					"on socket %u\n", online_table_size,
+			       (float)online_table_size / (1024.0F * 1024.0F), socket_id);
+		}
+	}
+
+	/*
+	 * Allocate the EFD table offline portion (with the actual rules mapping keys
+	 * to values) as a continuous block. This could be several gigabytes of memory.
+	 */
+	uint64_t offline_table_size = num_chunks * sizeof(struct efd_offline_chunk_rules);
+	table->offline_chunks =
+			(struct efd_offline_chunk_rules *) rte_zmalloc_socket(NULL,
+							offline_table_size,
+							RTE_CACHE_LINE_SIZE,
+							offline_cpu_socket);
+	if (table->offline_chunks == NULL) {
+		RTE_LOG(ERR, EFD, "Allocating EFD offline table on socket %u failed\n",
+				offline_cpu_socket);
+		goto error_unlock_exit;
+	}
+
+	RTE_LOG(DEBUG, EFD,
+			"Allocated EFD offline table of size %lu bytes (%.2f MB) on socket %u\n",
+			offline_table_size,
+	       (float)offline_table_size / (1024.0F * 1024.0F),
+		   offline_cpu_socket);
+
+	te->data = (void *) table;
+	TAILQ_INSERT_TAIL(efd_list, te, next);
+	rte_rwlock_write_unlock(RTE_EAL_TAILQ_RWLOCK);
+
+	return table;
+
+error_unlock_exit:
+	rte_rwlock_write_unlock(RTE_EAL_TAILQ_RWLOCK);
+	rte_efd_free(table);
+
+	return NULL;
+}
+
+struct rte_efd_table *
+rte_efd_find_existing(const char *name)
+{
+	struct rte_efd_table *table = NULL;
+	struct rte_tailq_entry *te;
+	struct rte_efd_list *efd_list;
+
+	efd_list = RTE_TAILQ_CAST(rte_efd_tailq.head, rte_efd_list);
+
+	rte_rwlock_read_lock(RTE_EAL_TAILQ_RWLOCK);
+	TAILQ_FOREACH(te, efd_list, next) {
+		table = (struct rte_efd_table *) te->data;
+		if (strncmp(name, table->name, RTE_EFD_NAMESIZE) == 0)
+			break;
+	}
+	rte_rwlock_read_unlock(RTE_EAL_TAILQ_RWLOCK);
+
+	if (te == NULL) {
+		rte_errno = ENOENT;
+		return NULL;
+	}
+	return table;
+}
+
+void
+rte_efd_free(struct rte_efd_table *table)
+{
+	if (table != NULL) {
+		uint8_t socket_id;
+		for (socket_id = 0; socket_id < RTE_MAX_NUMA_NODES; socket_id++)
+			if (table->chunks[socket_id] != NULL)
+				rte_free(table->chunks[socket_id]);
+
+		if (table->offline_chunks != NULL) rte_free(table->offline_chunks);
+		rte_free(table);
+	}
+}
+
+/**
+ * Applies a previously computed table entry to the specified table for all
+ * socket-local copies of the online table.
+ * Intended to apply an update for only a single change to a key/value pair at a time
+ *
+ * @param[in]  table           EFD table to reference
+ * @param[in]  socket_id       Socket ID to use to lookup existing values (ideally caller's socket id)
+ * @param[in]  chunk_id        Chunk index to update
+ * @param[in]  group_id        Group index to update
+ * @param[in]  bin_id          Bin within the group that this update affects
+ * @param[in]  new_bin_choice  Newly chosen permutation which this bin should use - only lower 2 bits
+ * @param[in]  new_group_entry Previously computed updated chunk/group entry
+ */
+static inline void
+efd_apply_update(struct rte_efd_table *const table, const unsigned int socket_id,
+                      const uint32_t chunk_id, const uint32_t group_id,
+                      const uint32_t bin_id, const uint8_t new_bin_choice,
+                      const struct efd_online_group_entry *const new_group_entry)
+{
+	int i;
+	struct efd_online_chunk *chunk = &table->chunks[socket_id][chunk_id];
+
+	/* Grab the current byte that contains the choices for four neighboring bins */
+	uint8_t choice_chunk = chunk->bin_choice_list[bin_id / EFD_CHUNK_NUM_BIN_TO_GROUP_SETS];
+
+
+	/* Compute the offset into the chunk that needs to be updated */
+	int offset = (bin_id & 0x3) * 2;
+
+	/* Zero the two bits of interest and set them to new_bin_choice */
+	choice_chunk = (choice_chunk & (~(0x03 << offset)))
+				   | ((new_bin_choice & 0x03) << offset);
+
+	/* Update the online table with the new data across all sockets */
+	for (i = 0; i < RTE_MAX_NUMA_NODES; i++) {
+		if (table->chunks[i] != NULL) {
+			memcpy(&(table->chunks[i][chunk_id].groups[group_id]), new_group_entry,
+				   sizeof(struct efd_online_group_entry));
+			table->chunks[i][chunk_id].bin_choice_list[bin_id / EFD_CHUNK_NUM_BIN_TO_GROUP_SETS] =
+					choice_chunk;
+		}
+	}
+}
+
+
+/**
+ * Computes an updated table entry where the supplied key points to a new host.
+ * If no entry exists, one is inserted.
+ *
+ * This function does NOT modify the online table(s)
+ * This function DOES modify the offline table
+ *
+ * @param[in]  table           EFD table to reference
+ * @param[in]  socket_id       Socket ID to use to lookup existing values (ideally caller's socket id)
+ * @param[in]  key             Key to insert
+ * @param[in]  value           Value to associate with key
+ * @param[out] chunk_id        Chunk ID of the chunk that was modified
+ * @param[out] group_id        Group ID of the group that was modified
+ * @param[out] bin_id          Bin ID that was modified
+ * @param[out] new_bin_choice  Newly chosen permutation which this bin will use
+ * @param[out] entry           Newly computed online entry to apply later with efd_apply_update
+ *
+ * @return
+ *  EFD_UPDATE_WARN_GROUP_FULL
+ *     Operation is insert, and the last available space in the key's group was just used
+ *     Future inserts may fail as groups fill up
+ *     This operation was still successful, and entry contains a valid update
+ *  EFD_UPDATE_FAILED
+ *     Either the EFD failed to find a suitable perfect hash or the group was full
+ *     This is a fatal error, and the table is now in an indeterminite state
+ *  EFD_UPDATE_NO_CHANGE
+ *     Operation resulted in no change to the table (same value already exists)
+ *  0
+ *     Insert or update was succesful, and the new efd_online_group_entry is stored in *entry
+ *
+ * @warning
+ *   Note that entry will be UNCHANGED if the update has no effect, and thus any
+ *   subsequent use of the entry content will likely be invalid
+ */
+static inline int
+efd_compute_update(struct rte_efd_table *const table, const unsigned int socket_id,
+                       const efd_key_t *const key, const efd_value_t value,
+                       uint32_t *const chunk_id, uint32_t *const group_id,
+                       uint32_t *const bin_id, uint8_t *const new_bin_choice,
+                       struct efd_online_group_entry *const entry)
+{
+	unsigned int i;
+	int status = EXIT_SUCCESS;
+	int found = 0;
+
+	efd_compute_ids(table, key, chunk_id, bin_id);
+
+	struct efd_offline_chunk_rules *const chunk = &table->offline_chunks[*chunk_id];
+	struct efd_offline_group_rules *new_group;
+	struct efd_offline_group_rules current_group_copy;
+	struct efd_offline_group_rules new_group_copy;
+
+	uint8_t current_choice = efd_get_choice(table, socket_id, *chunk_id, *bin_id);
+	uint32_t current_group_id = efd_bin_to_group[current_choice][*bin_id];
+	struct efd_offline_group_rules *const current_group = &chunk->group_rules[current_group_id];
+	uint8_t bin_size = 0;
+
+	/* Scan the current group and see if the key is already present */
+	for (i = 0; i < current_group->num_rules; i++) {
+		if (current_group->bin_id[i] == *bin_id) {
+			bin_size++;
+		}
+
+		if (found == 0 && unlikely(memcmp(&current_group->key[i], key, EFD_KEY_LEN) == 0)) {
+			/* Key is already present */
+
+			/* If previous value is same as new value, no additional work is required */
+			if (current_group->value[i] == value) {
+				return EFD_UPDATE_NO_CHANGE;
+			}
+
+			/* Save the original group state, if update fails */
+			memcpy(&current_group_copy, current_group,
+					sizeof(struct efd_offline_group_rules));
+			current_group->value[i] = value;
+			found = 1;
+		}
+	}
+
+	if (found == 0) {
+		/* Key does not exist. Insert the rule into the bin/group */
+		if (unlikely(current_group->num_rules >= EFD_MAX_GROUP_NUM_RULES)) {
+			RTE_LOG(ERR, EFD,
+					"Fatal: No room remaining for insert into "
+					"chunk %u group %u bin %u\n",
+					*chunk_id,
+					current_group_id, *bin_id);
+			return EFD_UPDATE_FAILED;
+		} else {
+			/* Save the original group state, if update fails */
+			memcpy(&current_group_copy, current_group,
+					sizeof(struct efd_offline_group_rules));
+
+			if (unlikely(current_group->num_rules == (EFD_MAX_GROUP_NUM_RULES - 1))) {
+				RTE_LOG(INFO, EFD, "Warn: Insert into last available slot in "
+							"chunk %u group %u bin %u\n", *chunk_id,
+							current_group_id, *bin_id);
+				status = EFD_UPDATE_WARN_GROUP_FULL;
+			}
+			memcpy(&current_group->key[current_group->num_rules], key, EFD_KEY_LEN);
+			current_group->value[current_group->num_rules] = value;
+			current_group->bin_id[current_group->num_rules] = *bin_id;
+			table->num_rules++;
+			current_group->num_rules++;
+			bin_size++;
+		}
+	}
+
+	/* Group need to be rebalanced when it starts to get loaded */
+	if (current_group->num_rules > EFD_MIN_BALANCED_NUM_RULES) {
+
+		/* Subtract the number of entries in the bin from the original group */
+		current_group->num_rules -= bin_size;
+
+		/*
+		 * Figure out which of the available groups that this bin can map to is the smallest
+		 * (using the current group as baseline)
+		 */
+		uint8_t smallest_choice = current_choice;
+		uint8_t smallest_size = current_group->num_rules;
+		uint32_t smallest_group_id = current_group_id;
+		unsigned char choice;
+		for (choice = 0; choice < EFD_CHUNK_NUM_BIN_TO_GROUP_SETS; choice ++) {
+			uint32_t test_group_id = efd_bin_to_group[choice][*bin_id];
+			uint32_t num_rules = chunk->group_rules[test_group_id].num_rules;
+			if (num_rules < smallest_size) {
+				smallest_choice = choice;
+				smallest_size = num_rules;
+				smallest_group_id = test_group_id;
+			}
+		}
+
+		*new_bin_choice = smallest_choice;
+		*group_id = smallest_group_id;
+		new_group = &chunk->group_rules[smallest_group_id];
+
+		if (smallest_group_id == current_group_id) {
+			new_group->num_rules += bin_size;
+			RTE_LOG(DEBUG, EFD, "chunk %u: Left bin %u (%u entries) "
+					"in group %u (%u entries)\n", *chunk_id, *bin_id,
+					bin_size, *group_id, new_group->num_rules);
+		} else {
+			/*
+			 * Remove the bin from the group it was previously assigned to
+			 * and add it to the new group
+			 */
+
+			RTE_LOG(DEBUG, EFD, "chunk %u: Moving bin %u (%u entries) "
+					"from group %u (%u entries) to %u (%u entries)\n",
+					*chunk_id, *bin_id, bin_size, current_group_id,
+					current_group->num_rules + bin_size,
+					smallest_group_id, new_group->num_rules + bin_size);
+
+			/* Save the original group state, if update fails */
+			memcpy(&new_group_copy, new_group,
+					sizeof(struct efd_offline_group_rules));
+
+			uint8_t empty_idx = 0;
+			for (i = 0; i < current_group->num_rules + bin_size; i++) {
+				/* Move keys that belong to the same bin to the new group */
+				if (current_group->bin_id[i] == *bin_id) {
+					new_group->key[new_group->num_rules] = current_group->key[i];
+					new_group->value[new_group->num_rules] = current_group->value[i];
+					new_group->bin_id[new_group->num_rules] = current_group->bin_id[i];
+					new_group->num_rules++;
+				} else {
+					if (i != empty_idx) {
+						/* Need to move this key towards the top of the array */
+						current_group->key[empty_idx] = current_group->key[i];
+						current_group->value[empty_idx] = current_group->value[i];
+						current_group->bin_id[empty_idx] = current_group->bin_id[i];
+					}
+					empty_idx++;
+				}
+
+			}
+		}
+	} else {
+		*new_bin_choice = current_choice;
+		*group_id = current_group_id;
+		new_group = current_group;
+	}
+
+	/*
+	 * Recompute the hash function for the modified group,
+	 * and return it to the caller
+	 */
+	int ret = efd_search_hash(new_group, entry);
+	if (ret != 0) {
+		RTE_LOG(ERR, EFD,
+				"Failed to find perfect hash for group containing %u entries\n",
+				new_group->num_rules);
+		/* Restore table to the previous state */
+		if (new_group != current_group)
+			memcpy(new_group, &new_group_copy,
+					sizeof(struct efd_offline_group_rules));
+		memcpy(current_group, &current_group_copy,
+				sizeof(struct efd_offline_group_rules));
+
+		return EFD_UPDATE_FAILED;
+	}
+
+	return status;
+}
+
+int
+rte_efd_update(struct rte_efd_table *const table, const unsigned socket_id,
+					const efd_key_t *const key,
+					const efd_value_t value)
+{
+	uint32_t chunk_id, group_id, bin_id;
+	uint8_t new_bin_choice;
+	struct efd_online_group_entry entry;
+
+	int status = efd_compute_update(table, socket_id, key, value,
+					&chunk_id, &group_id, &bin_id, &new_bin_choice,
+					&entry);
+	if (status == EFD_UPDATE_NO_CHANGE)
+		return EXIT_SUCCESS;
+	else if (status != EXIT_SUCCESS)
+		return status;
+	else
+		efd_apply_update(table, socket_id, chunk_id, group_id, bin_id,
+	                 new_bin_choice, &entry);
+	return status;
+}
+
+int
+rte_efd_delete(struct rte_efd_table *const table, const unsigned socket_id,
+				const efd_key_t *const key, efd_value_t *const prev_value)
+{
+	unsigned int i;
+	uint32_t chunk_id, bin_id;
+	uint8_t not_found = 1;
+
+	efd_compute_ids(table, key, &chunk_id, &bin_id);
+
+	struct efd_offline_chunk_rules *const chunk = &table->offline_chunks[chunk_id];
+
+	uint8_t current_choice = efd_get_choice(table, socket_id, chunk_id, bin_id);
+	uint32_t current_group_id = efd_bin_to_group[current_choice][bin_id];
+	struct efd_offline_group_rules *const current_group =
+					&chunk->group_rules[current_group_id];
+
+	/*
+	 * Search the current group for the specified key.
+	 * If it exists, remove it and re-pack the other values
+	 */
+	for (i = 0; i < current_group->num_rules; i++) {
+		if (not_found) {
+			/* Found key that needs to be removed */
+			if (memcmp(&current_group->key[i], key, EFD_KEY_LEN) == 0) {
+				/* Store previous value if requested by caller */
+				if (prev_value != NULL) {
+					*prev_value = current_group->value[i];
+				}
+
+				not_found = 0;
+			}
+		} else {
+			/* If the desired key has been found, need to shift other values up one */
+
+			/* Need to shift this entry back up one index */
+			current_group->key[i - 1] = current_group->key[i];
+			current_group->value[i - 1] = current_group->value[i];
+			current_group->bin_id[i - 1] = current_group->bin_id[i];
+		}
+	}
+
+	if (not_found == 0) {
+		table->num_rules--;
+		current_group->num_rules--;
+	}
+
+	return not_found;
+}
+
+
+#if (EFD_VALUE_NUM_BITS == 8 || EFD_VALUE_NUM_BITS == 16 || EFD_VALUE_NUM_BITS == 24 || EFD_VALUE_NUM_BITS == 32)
+#define EFD_LOAD_SI128(val) _mm_load_si128(val)
+#else
+#define EFD_LOAD_SI128(val) _mm_lddqu_si128(val)
+#endif
+
+static inline efd_value_t efd_lookup_internal(const struct efd_online_group_entry *const group,
+                                              const uint32_t hash_val_a, const uint32_t hash_val_b)
+{
+	efd_value_t value = 0;
+
+#if EFD_VALUE_NUM_BITS > 3 && defined(RTE_MACHINE_CPUFLAG_AVX2)
+	uint32_t byte_idx;
+
+	__m256i vhash_val_a = _mm256_set1_epi32(hash_val_a);
+	__m256i vhash_val_b = _mm256_set1_epi32(hash_val_b);
+
+	for (byte_idx = 0; byte_idx < EFD_VALUE_NUM_BITS; byte_idx += 8) {
+#if (EFD_HASHFUNC_SIZE == 8)
+		__m256i vhash_idx = _mm256_cvtepu8_epi32(EFD_LOAD_SI128((__m128i const *)
+                                                        &group->hash_idx[byte_idx]));
+#elif (EFD_HASHFUNC_SIZE == 16)
+		__m256i vhash_idx = _mm256_cvtepu16_epi32(EFD_LOAD_SI128((__m128i const *)
+		                                                         &group->hash_idx[byte_idx]));
+#endif
+#if (EFD_LOOKUPTBL_SIZE == 8)
+		__m256i vlookup_table = _mm256_cvtepu8_epi32(EFD_LOAD_SI128((__m128i const *)
+		                                             &group->lookup_table[byte_idx]));
+#elif (EFD_LOOKUPTBL_SIZE == 16)
+		__m256i vlookup_table = _mm256_cvtepu16_epi32(EFD_LOAD_SI128((__m128i const *)
+		                                              &group->lookup_table[byte_idx]));
+#endif
+		__m256i vhash = _mm256_add_epi32(vhash_val_a, _mm256_mullo_epi32(vhash_idx, vhash_val_b));
+		__m256i vbucket_idx = _mm256_srli_epi32(vhash, EFD_LOOKUPTBL_SHIFT);
+		__m256i vresult = _mm256_srlv_epi32(vlookup_table, vbucket_idx);
+		value |= (_mm256_movemask_ps((__m256)_mm256_slli_epi32(vresult, 31))
+		          & ((1 << (EFD_VALUE_NUM_BITS - byte_idx)) - 1)) << byte_idx;
+	}
+#else
+	uint32_t bit;
+
+	for (bit = 0; bit < EFD_VALUE_NUM_BITS; bit++) {
+		value <<= 1;
+
+		uint32_t h = hash_val_a + hash_val_b * group->hash_idx[EFD_VALUE_NUM_BITS - bit - 1];
+		uint16_t bucket_idx = h >> EFD_LOOKUPTBL_SHIFT;
+
+		value |= (group->lookup_table[EFD_VALUE_NUM_BITS - bit - 1] >> bucket_idx) & 0x1;
+	}
+#endif
+
+	return value;
+}
+
+efd_value_t
+rte_efd_lookup(const struct rte_efd_table *const table, const unsigned socket_id,
+                       const efd_key_t *const key)
+{
+	uint32_t chunk_id, group_id, bin_id;
+	uint8_t bin_choice;
+	const struct efd_online_chunk *const chunks = table->chunks[socket_id];
+
+	/* Determine the chunk and group location for the given key */
+	efd_compute_ids(table, key, &chunk_id, &bin_id);
+	bin_choice = efd_get_choice(table, socket_id, chunk_id, bin_id);
+	group_id = efd_bin_to_group[bin_choice][bin_id];
+
+	return efd_lookup_internal(&chunks[chunk_id].groups[group_id], EFD_HASHFUNCA(key),
+	                           EFD_HASHFUNCB(key));
+}
+
+void rte_efd_lookup_bulk(const struct rte_efd_table *const table, const unsigned socket_id,
+                     const int num_keys, const efd_key_t *const *const key_list,
+                     efd_value_t *const value_list)
+{
+	int i;
+	uint32_t chunk_id_list[EFD_BURST_MAX];
+	uint32_t bin_id_list[EFD_BURST_MAX];
+	uint8_t bin_choice_list[EFD_BURST_MAX];
+	uint32_t group_id_list[EFD_BURST_MAX];
+
+	struct efd_online_chunk *chunks = table->chunks[socket_id];
+
+	for (i = 0; i < num_keys; i++) {
+		efd_compute_ids(table, key_list[i], &chunk_id_list[i], &bin_id_list[i]);
+		rte_prefetch0(&chunks[chunk_id_list[i]].bin_choice_list);
+	}
+
+	for (i = 0; i < num_keys; i++) {
+		bin_choice_list[i] = efd_get_choice(table, socket_id, chunk_id_list[i], bin_id_list[i]);
+		group_id_list[i] = efd_bin_to_group[bin_choice_list[i]][bin_id_list[i]];
+		rte_prefetch0(&chunks[chunk_id_list[i]].groups[group_id_list[i]]);
+	}
+
+	for (i = 0; i < num_keys; i++) {
+		value_list[i] = efd_lookup_internal(&chunks[chunk_id_list[i]].groups[group_id_list[i]],
+		                                    EFD_HASHFUNCA(key_list[i]), EFD_HASHFUNCB(key_list[i]));
+	}
+}
+
diff --git a/lib/librte_efd/rte_efd.h b/lib/librte_efd/rte_efd.h
new file mode 100644
index 0000000..aacb62a
--- /dev/null
+++ b/lib/librte_efd/rte_efd.h
@@ -0,0 +1,423 @@
+/*-
+ *   BSD LICENSE
+ *
+ *   Copyright(c) 2016 Intel Corporation. All rights reserved.
+ *   All rights reserved.
+ *
+ *   Redistribution and use in source and binary forms, with or without
+ *   modification, are permitted provided that the following conditions
+ *   are met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in
+ *       the documentation and/or other materials provided with the
+ *       distribution.
+ *     * Neither the name of Intel Corporation nor the names of its
+ *       contributors may be used to endorse or promote products derived
+ *       from this software without specific prior written permission.
+ *
+ *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ *   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ *   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ *   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ *   OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ *   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ *   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ *   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __LIBEFD_H__
+#define __LIBEFD_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <rte_hash_crc.h>
+#include "rte_lcore.h"
+
+/**************************************************************************************************
+ * User selectable constants
+ *************************************************************************************************/
+
+/*
+ * If possible, best lookup performance will be achieved by ensuring that
+ * the entire table fits in the L3 cache.
+ *
+ * Some formulas for calculating various sizes are listed below:
+ *
+ * # of chunks =
+ *   2 ^ (ceiling(log2((requested # of rules) /
+ *   		(EFD_CHUNK_NUM_GROUPS * EFD_TARGET_GROUP_NUM_RULES))))
+ *
+ * Target # of rules = (# of chunks) * EFD_CHUNK_NUM_GROUPS *
+ * 			EFD_TARGET_GROUP_NUM_RULES
+ *
+ * Group Size (in bytes) = (EFD_HASHFUNC_SIZE + EFD_LOOKUPTBL_SIZE + 8) / 8
+ *
+ * Table size (in bytes) = EFD_VALUE_NUM_BITS * (# of chunks) *
+ * 			EFD_CHUNK_NUM_GROUPS * (group size)
+ */
+
+/**
+ * !!! This parameter should be adjusted for your application !!!
+ *
+ * This parameter adjusts the number of bits of value that can be stored in the table.
+ * For example, setting the number of bits to 3 will allow storing 8 values
+ * in the table (between 0 and 7).
+ *
+ * This number directly affects the performance of both lookups and insertion.
+ * In general, performance decreases as more bits are stored in the table.
+ *
+ * This number is directly proportional to the size of the online region
+ * used for lookups.
+ *
+ * Note that due to the way the CPU operates on memory, best lookup performance
+ * will be achieved when EFD_VALUE_NUM_BITS is a multiple of 8.
+ * These values align the hash indexes on 16-byte boundaries.
+ * The greatest performance drop is moving from 8->9 bits, 16->17 bits, etc.
+ *
+ * This value must be between 1 and 32
+ */
+#ifndef EFD_VALUE_NUM_BITS
+#define EFD_VALUE_NUM_BITS (8)
+#endif
+
+/**
+ * !!! This parameter should be adjusted for your application !!!
+ *
+ * This parameter adjusts the size of the keys stored in the table (in bytes).
+ * There are no restrictions around valid key sizes, and larger keys
+ * do not change the size of the online table used for lookups.
+ */
+#ifndef EFD_KEY_LEN
+#define EFD_KEY_LEN (4)
+#endif
+
+/*
+ * EFD_TARGET_GROUP_NUM_RULES:
+ *   Adjusts how many groups/chunks are allocated at table creation time
+ *   to support the requested number of rules. Higher values pack entries
+ *   more tightly in memory, resulting in a smaller memory footprint
+ *   for the online table.
+ *   This comes at the cost of lower insert/update performance.
+ *
+ * EFD_MAX_GROUP_NUM_RULES:
+ *   This adjusts the amount of offline memory allocated to store key/value
+ *   pairs for the table. The recommended numbers are upper-bounds for this parameter
+ *   - any higher and it becomes very unlikely that a perfect hash function
+ *   can be found for that group size. This value should be at
+ *   least 40% larger than EFD_TARGET_GROUP_NUM_RULES
+ *
+ * Recommended values for various lookuptable and hashfunc sizes are:
+ *
+ *   HASH_FUNC_SIZE = 16, LOOKUPTBL_SIZE = 16:
+ *     EFD_TARGET_GROUP_NUM_RULES = 22
+ *     EFD_MAX_GROUP_NUM_RULES = 28
+ */
+#define EFD_TARGET_GROUP_NUM_RULES (22)
+#define EFD_MAX_GROUP_NUM_RULES (28)
+
+#define EFD_MIN_BALANCED_NUM_RULES      5
+
+/**
+ * Adjusts the number of bits used to store the hash function index
+ */
+#define EFD_HASHFUNC_SIZE (16)
+
+/**
+ * Adjusts the number of bits used to store the lookup table result map
+ */
+#define EFD_LOOKUPTBL_SIZE (16)
+
+/**
+ * Maximum number of keys that can be looked up in one call to efd_lookup_bulk
+ */
+#ifndef EFD_BURST_MAX
+#define EFD_BURST_MAX (32)
+#endif
+
+/*****************************************************************************
+ * Fixed constants
+ *****************************************************************************/
+
+/** Maximum number of characters in efd name.*/
+#define RTE_EFD_NAMESIZE			32
+
+/* These parameters are fixed by the efd_bin_to_group balancing table */
+#define EFD_CHUNK_NUM_GROUPS (64)
+#define EFD_CHUNK_NUM_BINS   (256)
+#define EFD_CHUNK_NUM_BIN_TO_GROUP_SETS (EFD_CHUNK_NUM_BINS / EFD_CHUNK_NUM_GROUPS)
+
+/*
+ * Target number of rules that each chunk is created to handle.
+ * Used when initially allocating the table
+ */
+#define EFD_TARGET_CHUNK_NUM_RULES  (EFD_CHUNK_NUM_GROUPS * EFD_TARGET_GROUP_NUM_RULES)
+
+/** This is fixed based on the bin_to_group permutation array */
+#define EFD_MAX_GROUP_NUM_BINS (16)
+
+/**
+ * The end of the chunks array needs some extra padding to ensure
+ * that vectorization over-reads on the last online chunk stay within allocated memory
+ */
+#define EFD_NUM_CHUNK_PADDING_BYTES (256)
+
+typedef uint16_t efd_lookuptbl_t;
+#define EFD_LOOKUPTBL_SHIFT (32 - 4)
+
+typedef uint16_t efd_hashfunc_t;
+
+#if (EFD_VALUE_NUM_BITS > 0 && EFD_VALUE_NUM_BITS <= 8)
+typedef uint8_t efd_value_t;
+#elif (EFD_VALUE_NUM_BITS > 8 && EFD_VALUE_NUM_BITS <= 16)
+typedef uint16_t efd_value_t;
+#elif (EFD_VALUE_NUM_BITS > 16 && EFD_VALUE_NUM_BITS <= 32)
+typedef uint32_t efd_value_t;
+#else
+#error("EFD_VALUE_NUM_BITS must be in the range [1:32]")
+#endif
+
+#if (EFD_KEY_LEN <= 0)
+#error("EFD_KEY_LEN must be an integer greater than 0")
+#endif
+
+/* Variable size keys */
+typedef struct {
+	uint8_t bytes[EFD_KEY_LEN];
+} efd_key_t;
+
+/*****************************************************************************
+ * Offline region structures
+ *****************************************************************************/
+
+struct efd_offline_group_rules {
+	/** Sum of the number of rules in all bins assigned to this group */
+	uint32_t num_rules;
+
+	/* Indexes >= num_rules are unused/undefined */
+	efd_key_t key[EFD_MAX_GROUP_NUM_RULES];
+	efd_value_t value[EFD_MAX_GROUP_NUM_RULES];
+
+	/* Stores the bin for each correspending key to avoid having to recompute it */
+	uint8_t bin_id[EFD_MAX_GROUP_NUM_RULES];
+};
+
+struct efd_offline_chunk_rules {
+	/** Number of rules in the entire chunk; used to detect unbalanced groups */
+	uint16_t num_rules;
+
+	struct efd_offline_group_rules group_rules[EFD_CHUNK_NUM_GROUPS];
+};
+
+/*****************************************************************************
+ * Online region structures
+ *****************************************************************************/
+
+struct efd_online_group_entry {
+	efd_hashfunc_t hash_idx[EFD_VALUE_NUM_BITS];
+	efd_lookuptbl_t lookup_table[EFD_VALUE_NUM_BITS];
+} __attribute__((__packed__));
+
+/**
+ * A single chunk record, containing EFD_TARGET_CHUNK_NUM_RULES rules.
+ * Those rules are split into EFD_CHUNK_NUM_GROUPS groups per chunk.
+ */
+struct efd_online_chunk {
+	/**
+	 * This is a packed indirection index into the 'groups' array.
+	 * Each byte contains four two-bit values which index into
+	 * the efd_bin_to_group array.
+	 * The efd_bin_to_group array returns the index into the groups array
+	 */
+	uint8_t bin_choice_list[(EFD_CHUNK_NUM_BINS * 2 + 7) / 8];
+
+	struct efd_online_group_entry groups[EFD_CHUNK_NUM_GROUPS];
+} __attribute__((__packed__));
+
+struct rte_efd_table {
+	char name[RTE_EFD_NAMESIZE];    /**< Name of the efd table. */
+	/**
+	 * Static maximum number of entries the table was constructed to hold
+	 */
+	uint32_t max_num_rules;
+
+	/**
+	 * Number of entries currently in the table
+	 *
+	 * Note that this is only correct if all group modifications/insertions
+	 * were performed using this table structure.
+	 * If groups are updated by applying chunks computed elsewhere,
+	 * there is no way to know how many rules are in the table
+	 */
+	uint32_t num_rules;
+
+	/**
+	 * Number of chunks in the table needed to support num_rules.
+	 * Each chunk contains EFD_TARGET_CHUNK_NUM_RULES entries.
+	 * When the table is constructed, num_chunks is set such that
+	 *   num_chunks * EFD_TARGET_CHUNK_NUM_RULES > num_rules
+	 * and num_chunks is always a power of two.
+	 */
+	uint32_t num_chunks;
+
+	/**
+	 * For faster math operations, rather than divide by num_chunks,
+	 * shift by num_chunks_shift
+	 */
+	uint32_t num_chunks_shift;
+
+	/**
+	 * Dynamic array of size num_chunks of chunk records
+	 */
+	struct efd_online_chunk *chunks[RTE_MAX_NUMA_NODES];
+
+	/**
+	 * Dynamic array of size num_chunks of key-value pairs
+	 */
+	struct efd_offline_chunk_rules *offline_chunks;
+};
+
+
+/*****************************************************************************
+ * Common user functions
+ *****************************************************************************/
+
+/**
+ * Creates an EFD table with a single offline region and multiple per-socket
+ * internally-managed copies of the online table used for lookups
+ *
+ * @param[in] max_num_rules
+ *   Minimum number of rules the table should be sized to hold. Will be rounded up to the next
+ *   smallest valid table size
+ * @param[in] online_cpu_socket_bitmask
+ *   Bitmask specifying which sockets should get a copy of the online table. LSB = socket 0, etc.
+ * @param[in] offline_cpu_socket
+ *   Identifies the socket where the offline table will be allocated (and most efficiently accessed
+ *   in the case of updates/insertions)
+ *
+ * @return
+ *   EFD table, or NULL if table allocation failed or the bitmask is invalid
+ */
+struct rte_efd_table *
+rte_efd_create(const char *name, uint32_t max_num_rules, uint8_t online_cpu_socket_bitmask,
+                             uint8_t offline_cpu_socket);
+
+/**
+ * Releases the resources from an EFD table
+ *
+ * @param table
+ *   Table to free
+ */
+void
+rte_efd_free(struct rte_efd_table *table);
+
+/**
+ * Find an existing EFD table object and return a pointer to it.
+ *
+ * @param name
+ *   Name of the EFD table as passed to rte_hash_create()
+ * @return
+ *   Pointer to EFD table or NULL if object not found
+ *   with rte_errno set appropriately. Possible rte_errno values include:
+ *    - ENOENT - value not available for return
+ */
+struct rte_efd_table*
+rte_efd_find_existing(const char *name);
+
+#define EFD_UPDATE_WARN_GROUP_FULL   (1)
+#define EFD_UPDATE_NO_CHANGE         (2)
+#define EFD_UPDATE_FAILED            (3)
+
+/**
+ * Computes an updated table entry for the supplied key/value pair.
+ * The update is then immediately applied to the provided table and all socket-local
+ * copies of the chunks are updated.
+ *
+ * @param[in]  table     EFD table to reference
+ * @param[in]  socket_id Socket ID to use to lookup existing value (ideally caller's socket id)
+ * @param[in]  key       EFD table key to modify
+ * @param[in]  value     Value to associate with the key
+ *
+ * @return
+ *  EFD_UPDATE_WARN_GROUP_FULL
+ *     Operation is insert, and the last available space in the key's group was just used
+ *     Future inserts may fail as groups fill up
+ *     This operation was still successful, and entry contains a valid update
+ *  EFD_UPDATE_FAILED
+ *     Either the EFD failed to find a suitable perfect hash or the group was full
+ *     This is a fatal error, and the table is now in an indeterminite state
+ *  EFD_UPDATE_NO_CHANGE
+ *     Operation resulted in no change to the table (same value already exists)
+ *  0 - success
+ */
+int
+rte_efd_update(struct rte_efd_table *table, unsigned socket_id, const efd_key_t *key,
+               efd_value_t value);
+
+/**
+ * Removes any value currently associated with the specified key from the table
+ *
+ * @param[in]  table      EFD table to reference
+ * @param[in]  socket_id  Socket ID to use to lookup existing value (ideally caller's socket id)
+ * @param[in]  key        EFD table key to delete
+ * @param[out] prev_value If not NULL, will store the previous value here before deleting it
+ *
+ * @return
+ *   0 - successfully found and deleted the key
+ *   nonzero otherwise
+ */
+int
+rte_efd_delete(struct rte_efd_table *table, unsigned socket_id, const efd_key_t *key,
+               efd_value_t *prev_value);
+
+/**
+ * Looks up the value associated with a key
+ *
+ * NOTE: Lookups will *always* succeed - this is a property of using a perfect hash table.
+ * If the specified key was never inserted, a pseudorandom answer will be returned. There is no
+ * way to know based on the lookup if the key was ever inserted originally - this must be tracked
+ * elsewhere.
+ *
+ * @param[in]  table      EFD table to reference
+ * @param[in]  socket_id  Socket ID to use to lookup existing value (ideally caller's socket id)
+ * @param[in]  key        EFD table key to look up
+ *
+ * @return
+ *  Value associated with the key, or random junk if they key was never inserted
+ */
+efd_value_t
+rte_efd_lookup(const struct rte_efd_table *table, unsigned socket_id,
+                       const efd_key_t *key);
+
+/**
+ * Looks up the value associated with several keys.
+ *
+ * NOTE: Lookups will *always* succeed - this is a property of using a perfect hash table.
+ * If the specified key was never inserted, a pseudorandom answer will be returned. There is no
+ * way to know based on the lookup if the key was ever inserted originally - this must be tracked
+ * elsewhere.
+ *
+ * @param[in]  table      EFD table to reference
+ * @param[in]  socket_id  Socket ID to use to lookup existing value (ideally caller's socket id)
+ * @param[in]  num_keys   Number of keys in the key_list array, must be less than EFD_BURST_MAX
+ * @param[in]  key_list   Array of num_keys pointers which point to keys to look up
+ * @param[out] value_list Array of size num_keys where lookup values will be stored
+ */
+void
+rte_efd_lookup_bulk(const struct rte_efd_table *table, unsigned socket_id,
+                     int num_keys, const efd_key_t *const *key_list,
+                     efd_value_t *value_list);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __LIBEFD_H__ */
+
diff --git a/lib/librte_efd/rte_efd_version.map b/lib/librte_efd/rte_efd_version.map
new file mode 100644
index 0000000..91810b3
--- /dev/null
+++ b/lib/librte_efd/rte_efd_version.map
@@ -0,0 +1,12 @@
+DPDK_17.02 {
+	global:
+
+	rte_efd_create;
+	rte_efd_delete;
+	rte_efd_free;
+	rte_efd_lookup;
+	rte_efd_lookup_bulk;
+	rte_efd_update;
+
+	local: *;
+};
diff --git a/mk/rte.app.mk b/mk/rte.app.mk
index 72c2fe7..60535c0 100644
--- a/mk/rte.app.mk
+++ b/mk/rte.app.mk
@@ -86,6 +86,7 @@ _LDLIBS-y += --whole-archive
 
 _LDLIBS-$(CONFIG_RTE_LIBRTE_TIMER)          += -lrte_timer
 _LDLIBS-$(CONFIG_RTE_LIBRTE_HASH)           += -lrte_hash
+_LDLIBS-$(CONFIG_RTE_LIBRTE_EFD)            += -lrte_efd
 _LDLIBS-$(CONFIG_RTE_LIBRTE_VHOST)          += -lrte_vhost
 
 _LDLIBS-$(CONFIG_RTE_LIBRTE_KVARGS)         += -lrte_kvargs
-- 
2.7.4



More information about the dev mailing list