[8/8] net/ntnic: adds socket connection to PMD

Message ID 20230816132552.2483752-8-mko-plv@napatech.com (mailing list archive)
State Superseded, archived
Delegated to: Ferruh Yigit
Headers
Series [1/8] net/ntnic: initial commit which adds register defines |

Checks

Context Check Description
ci/loongarch-compilation success Compilation OK
ci/checkpatch warning coding style issues
ci/loongarch-unit-testing success Unit Testing PASS
ci/github-robot: build fail github build: failed
ci/iol-intel-Functional success Functional Testing PASS
ci/iol-compile-amd64-testing fail Testing issues
ci/iol-broadcom-Performance success Performance Testing PASS
ci/iol-sample-apps-testing success Testing PASS
ci/iol-compile-arm64-testing fail Testing issues
ci/iol-unit-amd64-testing fail Testing issues
ci/iol-intel-Performance success Performance Testing PASS
ci/iol-broadcom-Functional success Functional Testing PASS
ci/iol-unit-arm64-testing fail Testing issues
ci/Intel-compilation fail Compilation issues

Commit Message

Mykola Kostenok Aug. 16, 2023, 1:25 p.m. UTC
  From: Christian Koue Muf <ckm@napatech.com>

The socket connection is used by Napatech's tools for monitoring
and rte_flow programming from other processes.

Signed-off-by: Christian Koue Muf <ckm@napatech.com>
Reviewed-by: Mykola Kostenok <mko-plv@napatech.com>
---
 drivers/net/ntnic/meson.build                 |   24 +
 .../ntconnect/include/ntconn_mod_helper.h     |   97 ++
 .../net/ntnic/ntconnect/include/ntconnect.h   |   96 ++
 .../ntnic/ntconnect/include/ntconnect_api.h   |   87 ++
 .../ntconnect/include/ntconnect_api_adapter.h |  221 +++
 .../ntconnect/include/ntconnect_api_flow.h    |  168 +++
 .../ntconnect/include/ntconnect_api_meter.h   |   89 ++
 .../include/ntconnect_api_statistic.h         |  173 +++
 .../ntconnect/include/ntconnect_api_test.h    |   18 +
 drivers/net/ntnic/ntconnect/ntconn_server.c   |   97 ++
 drivers/net/ntnic/ntconnect/ntconnect.c       |  641 ++++++++
 .../ntnic/ntconnect_modules/ntconn_adapter.c  |  775 ++++++++++
 .../net/ntnic/ntconnect_modules/ntconn_flow.c | 1310 +++++++++++++++++
 .../ntnic/ntconnect_modules/ntconn_meter.c    |  517 +++++++
 .../ntnic/ntconnect_modules/ntconn_modules.h  |   19 +
 .../net/ntnic/ntconnect_modules/ntconn_stat.c |  876 +++++++++++
 .../net/ntnic/ntconnect_modules/ntconn_test.c |  146 ++
 17 files changed, 5354 insertions(+)
 create mode 100644 drivers/net/ntnic/ntconnect/include/ntconn_mod_helper.h
 create mode 100644 drivers/net/ntnic/ntconnect/include/ntconnect.h
 create mode 100644 drivers/net/ntnic/ntconnect/include/ntconnect_api.h
 create mode 100644 drivers/net/ntnic/ntconnect/include/ntconnect_api_adapter.h
 create mode 100644 drivers/net/ntnic/ntconnect/include/ntconnect_api_flow.h
 create mode 100644 drivers/net/ntnic/ntconnect/include/ntconnect_api_meter.h
 create mode 100644 drivers/net/ntnic/ntconnect/include/ntconnect_api_statistic.h
 create mode 100644 drivers/net/ntnic/ntconnect/include/ntconnect_api_test.h
 create mode 100644 drivers/net/ntnic/ntconnect/ntconn_server.c
 create mode 100644 drivers/net/ntnic/ntconnect/ntconnect.c
 create mode 100644 drivers/net/ntnic/ntconnect_modules/ntconn_adapter.c
 create mode 100644 drivers/net/ntnic/ntconnect_modules/ntconn_flow.c
 create mode 100644 drivers/net/ntnic/ntconnect_modules/ntconn_meter.c
 create mode 100644 drivers/net/ntnic/ntconnect_modules/ntconn_modules.h
 create mode 100644 drivers/net/ntnic/ntconnect_modules/ntconn_stat.c
 create mode 100644 drivers/net/ntnic/ntconnect_modules/ntconn_test.c
  

Comments

Stephen Hemminger Aug. 16, 2023, 2:46 p.m. UTC | #1
On Wed, 16 Aug 2023 15:25:52 +0200
Mykola Kostenok <mko-plv@napatech.com> wrote:

> From: Christian Koue Muf <ckm@napatech.com>
> 
> The socket connection is used by Napatech's tools for monitoring
> and rte_flow programming from other processes.
> 
> Signed-off-by: Christian Koue Muf <ckm@napatech.com>
> Reviewed-by: Mykola Kostenok <mko-plv@napatech.com>

I would prefer that this be general and work with other PMD's.
Why is existing telemetry model not good enough?
  
Stephen Hemminger Aug. 16, 2023, 2:47 p.m. UTC | #2
On Wed, 16 Aug 2023 15:25:52 +0200
Mykola Kostenok <mko-plv@napatech.com> wrote:

> From: Christian Koue Muf <ckm@napatech.com>
> 
> The socket connection is used by Napatech's tools for monitoring
> and rte_flow programming from other processes.
> 
> Signed-off-by: Christian Koue Muf <ckm@napatech.com>
> Reviewed-by: Mykola Kostenok <mko-plv@napatech.com>

Also, a proprietary socket API introduces a whole new security attack surface.
  
Christian Koue Muf Aug. 25, 2023, 1:52 p.m. UTC | #3
On Wednesday, August 16, 2023 4:46 PM
Stephen Hemminger <stephen@networkplumber.org> wrote:

> On Wed, 16 Aug 2023 15:25:52 +0200
> Mykola Kostenok <mko-plv@napatech.com> wrote:
>
> > From: Christian Koue Muf <ckm@napatech.com>
> > 
> > The socket connection is used by Napatech's tools for monitoring and 
> > rte_flow programming from other processes.
> > 
> > Signed-off-by: Christian Koue Muf <ckm@napatech.com>
> > Reviewed-by: Mykola Kostenok <mko-plv@napatech.com>
>
> I would prefer that this be general and work with other PMD's.
> Why is existing telemetry model not good enough?

The existing telemetry is good enough in many cases. The problems arise in multi-container environments. The design of Napatech's adapters is that they only have 1 PF, which is owned by a single process in a single container. Other containers will only have access to VFs, which do not provide any metrics. The ntconnect socket will allow remote applications to access data from the application that owns the PF.

I understand your concerns for security. My suggestion would be to disable the code using meson.build config by default.
  

Patch

diff --git a/drivers/net/ntnic/meson.build b/drivers/net/ntnic/meson.build
index faaba95af3..262ce436b9 100644
--- a/drivers/net/ntnic/meson.build
+++ b/drivers/net/ntnic/meson.build
@@ -1,11 +1,27 @@ 
 # SPDX-License-Identifier: BSD-3-Clause
 # Copyright(c) 2020-2023 Napatech A/S
 
+# config object
+ntnic_conf = configuration_data()
+
+# transfer options into config object
+ntnic_conf.set('NT_TOOLS', true)
+
 # cflags
 cflags += [
     '-std=c11',
 ]
 
+# check option 'debug' (boolean flag derived from meson buildtype)
+if get_option('debug')
+    cflags += '-DDEBUG'
+endif
+
+# check nt_tools build option
+if ntnic_conf.get('NT_TOOLS')
+    cflags += '-DNT_TOOLS'
+endif
+
 # includes
 includes = [
     include_directories('.'),
@@ -19,6 +35,7 @@  includes = [
     include_directories('nthw/supported'),
     include_directories('nthw/flow_api'),
     include_directories('nthw/flow_filter'),
+    include_directories('ntconnect/include'),
     include_directories('sensors'),
     include_directories('sensors/avr_sensors'),
     include_directories('sensors/board_sensors'),
@@ -40,6 +57,13 @@  sources = files(
     'nim/nt_link_speed.c',
     'nim/qsfp_sensors.c',
     'nim/sfp_sensors.c',
+    'ntconnect/ntconn_server.c',
+    'ntconnect/ntconnect.c',
+    'ntconnect_modules/ntconn_adapter.c',
+    'ntconnect_modules/ntconn_flow.c',
+    'ntconnect_modules/ntconn_meter.c',
+    'ntconnect_modules/ntconn_stat.c',
+    'ntconnect_modules/ntconn_test.c',
     'nthw/core/nthw_clock_profiles.c',
     'nthw/core/nthw_fpga.c',
     'nthw/core/nthw_fpga_nt200a0x.c',
diff --git a/drivers/net/ntnic/ntconnect/include/ntconn_mod_helper.h b/drivers/net/ntnic/ntconnect/include/ntconn_mod_helper.h
new file mode 100644
index 0000000000..f55c4141cc
--- /dev/null
+++ b/drivers/net/ntnic/ntconnect/include/ntconn_mod_helper.h
@@ -0,0 +1,97 @@ 
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef _NTCONN_MOD_HELPER_H_
+#define _NTCONN_MOD_HELPER_H_
+
+#include "ntconnect.h"
+
+/*
+ * Module parameter function call tree structures
+ */
+struct func_s {
+	const char *param;
+	struct func_s *sub_funcs;
+	int (*func)(void *hdl, int client_fd, struct ntconn_header_s *hdr,
+		    char **data, int *len);
+};
+
+static inline int ntconn_error(char **data, int *len, const char *module,
+			       enum ntconn_err_e err_code)
+{
+	*len = 0;
+	if (data) {
+		const ntconn_err_t *ntcerr = get_ntconn_error(err_code);
+		*data = malloc(4 + strlen(module) + 1 +
+			       strlen(ntcerr->err_text) + 1);
+		if (*data) {
+			sprintf(*data, "----%s:%s", module, ntcerr->err_text);
+			*len = strlen(*data) + 1; /* add 0 - terminator */
+			*(uint32_t *)*data = (uint32_t)ntcerr->err_code;
+		}
+	}
+	return REQUEST_ERR;
+}
+
+static inline int ntconn_reply_status(char **data, int *len,
+				      enum ntconn_reply_code_e code)
+{
+	*len = 0;
+	if (data) {
+		*data = malloc(sizeof(uint32_t));
+		if (*data) {
+			*len = sizeof(uint32_t);
+			*(uint32_t *)*data = (uint32_t)code;
+		}
+	}
+	return REQUEST_OK;
+}
+
+static inline int execute_function(const char *module, void *hdl, int client_id,
+				   struct ntconn_header_s *hdr, char *function,
+				   struct func_s *func_list, char **data,
+				   int *len, int recur_depth)
+{
+	char *tok = strtok(function, ",");
+
+	if (!tok) {
+		if (recur_depth == 0)
+			return ntconn_error(data, len, module,
+					    NTCONN_ERR_CODE_MISSING_INVALID_PARAM);
+		else
+			return ntconn_error(data, len, module,
+					    NTCONN_ERR_CODE_FUNCTION_PARAM_INCOMPLETE);
+	}
+
+	hdr->len -= strlen(tok) + 1;
+	char *sub_funcs = function + strlen(tok) + 1;
+	int idx = 0;
+
+	while (func_list[idx].param) {
+		if (strcmp(func_list[idx].param, tok) == 0) {
+			/* hit */
+			if (func_list[idx].sub_funcs) {
+				return execute_function(module, hdl, client_id,
+							hdr, sub_funcs,
+							func_list[idx].sub_funcs,
+							data, len,
+							++recur_depth);
+			} else if (func_list[idx].func) {
+				/* commands/parameters for function in text, zero-terminated */
+				*data = sub_funcs;
+				return func_list[idx].func(hdl, client_id, hdr,
+							   data, len);
+			} else {
+				return ntconn_error(data, len, module,
+						    NTCONN_ERR_CODE_INTERNAL_FUNC_ERROR);
+			}
+		}
+		idx++;
+	}
+	/* no hits */
+	return ntconn_error(data, len, module,
+			    NTCONN_ERR_CODE_FUNC_PARAM_NOT_RECOGNIZED);
+}
+
+#endif /* _NTCONN_MOD_HELPER_H_ */
diff --git a/drivers/net/ntnic/ntconnect/include/ntconnect.h b/drivers/net/ntnic/ntconnect/include/ntconnect.h
new file mode 100644
index 0000000000..9dcf2ec0a1
--- /dev/null
+++ b/drivers/net/ntnic/ntconnect/include/ntconnect.h
@@ -0,0 +1,96 @@ 
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef _NTCONNECT_H_
+#define _NTCONNECT_H_
+
+#include <rte_pci.h>
+#include <sched.h>
+#include <stdint.h>
+
+#include "ntconnect_api.h"
+
+#define REQUEST_OK 0
+#define REQUEST_ERR -1
+
+typedef struct ntconn_api_s {
+	/*
+	 * Name specifying this module. This name is used in the request string
+	 */
+	const char *module;
+	/*
+	 * The version of this module integration
+	 */
+	uint32_t version_major;
+	uint32_t version_minor;
+	/*
+	 * The request function:
+	 * hdl       : pointer to the context of this instance.
+	 * client_id : identifying the client. To be able to manage client specific data/state.
+	 * function  : pointer to the remainder of the request command (Layer 3). May be modified.
+	 *             an example: <pci_id>;adapter;get,interface,port0,link_speed function will
+	 *             then be 'get,interface,port0,link_speed'.
+	 * hdr       : header for length of command string and length of binary blop.
+	 *             The command string will start at "*data" and will have the length hdr->len.
+	 *             The binary blob will start at "&(*data)[hdr->len]" and will have the length
+	 *             hdr->blob_len.
+	 * data      : pointer to the resulting data. Typically this will be allocated.
+	 * len       : length of the data in the reply.
+	 *
+	 * return    : REQUEST_OK on success, REQUEST_ERR on failure. On failure, the data and len
+	 *             can contain an describing error text
+	 */
+	int (*request)(void *hdl, int client_id, struct ntconn_header_s *hdr,
+		       char *function, char **data, int *len);
+	/*
+	 * After each request call, and when 'len' returns > 0, this function is called
+	 * after sending reply to client.
+	 * hdl       : pointer to the context of this instance.
+	 * data      : the data pointer returned in the request function
+	 */
+	void (*free_data)(void *hdl, char *data);
+	/*
+	 * Clean up of client specific data allocations. Called when client disconnects from server
+	 * hdl       : pointer to the context of this instance.
+	 * client_id : identifying the client.
+	 */
+	void (*client_cleanup)(void *hdl, int client_id);
+} ntconnapi_t;
+
+/*
+ * ntconn error
+ */
+typedef struct ntconn_err_s {
+	uint32_t err_code;
+	const char *err_text;
+} ntconn_err_t;
+
+const ntconn_err_t *get_ntconn_error(enum ntconn_err_e err_code);
+
+typedef struct ntconn_mod_s {
+	void *hdl;
+	struct pci_id_s addr;
+	const ntconnapi_t *op;
+
+	pthread_mutex_t mutex;
+	struct ntconn_mod_s *next;
+} ntconn_mod_t;
+
+struct ntconn_server_s {
+	int serv_fd;
+	int running;
+	/*
+	 * list of different pci_ids registered aka SmartNICs
+	 */
+	struct pci_id_s pci_id_list[MAX_PCI_IDS]; /* 0 - terminates */
+	cpu_set_t cpuset;
+};
+
+int ntconn_server_register(void *server);
+
+int register_ntconn_mod(const struct rte_pci_addr *addr, void *hdl,
+			const ntconnapi_t *op);
+int ntconnect_init(const char *sockname, cpu_set_t cpuset);
+
+#endif /* _NTCONNECT_H_ */
diff --git a/drivers/net/ntnic/ntconnect/include/ntconnect_api.h b/drivers/net/ntnic/ntconnect/include/ntconnect_api.h
new file mode 100644
index 0000000000..14668bf2ee
--- /dev/null
+++ b/drivers/net/ntnic/ntconnect/include/ntconnect_api.h
@@ -0,0 +1,87 @@ 
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef _NTCONNECT_API_H_
+#define _NTCONNECT_API_H_
+
+#include "stdint.h"
+/*
+ * NtConnect API
+ */
+
+#define NTCONNECT_SOCKET "/var/run/ntconnect/ntconnect.sock"
+
+enum ntconn_err_e {
+	NTCONN_ERR_CODE_NONE = 0U,
+	NTCONN_ERR_CODE_INTERNAL_ERROR,
+	NTCONN_ERR_CODE_INVALID_REQUEST,
+	NTCONN_ERR_CODE_INTERNAL_REPLY_ERROR,
+	NTCONN_ERR_CODE_NO_DATA,
+	NTCONN_ERR_CODE_NOT_YET_IMPLEMENTED,
+	NTCONN_ERR_CODE_MISSING_INVALID_PARAM,
+	NTCONN_ERR_CODE_FUNCTION_PARAM_INCOMPLETE,
+	NTCONN_ERR_CODE_INTERNAL_FUNC_ERROR,
+	NTCONN_ERR_CODE_FUNC_PARAM_NOT_RECOGNIZED,
+};
+
+enum ntconn_reply_code_e {
+	NTCONN_ADAPTER_ERR_PORT_STATE_FAIL = 0U,
+	NTCONN_ADAPTER_ERR_WRONG_LINK_STATE,
+	NTCONN_ADAPTER_ERR_TX_POWER_FAIL,
+};
+
+enum {
+	NTCONN_TAG_NONE,
+	NTCONN_TAG_REQUEST,
+	NTCONN_TAG_REPLY,
+	NTCONN_TAG_ERROR
+};
+
+#define MESSAGE_BUFFER 256
+#define MAX_ERR_MESSAGE_LENGTH 256
+
+struct reply_err_s {
+	enum ntconn_err_e err_code;
+	char msg[MAX_ERR_MESSAGE_LENGTH];
+};
+
+#define NTCMOD_HDR_LEN sizeof(struct ntconn_header_s)
+struct ntconn_header_s {
+	uint16_t tag;
+	uint16_t len;
+	uint32_t blob_len;
+};
+
+struct pci_id_s {
+	union {
+		uint64_t pci_id;
+		struct {
+			uint32_t domain;
+			uint8_t bus;
+			uint8_t devid;
+			uint8_t function;
+			uint8_t pad;
+		};
+	};
+};
+
+#define VERSION_HI(version) ((unsigned int)((version) >> 32))
+#define VERSION_LO(version) ((unsigned int)((version) & 0xffffffff))
+
+/*
+ * Binary interface description for ntconnect module replies
+ */
+
+/*
+ * server get,nic_pci_ids
+ */
+#define MAX_PCI_IDS 16
+#define NICS_PCI_ID_LEN 12
+
+struct ntc_nic_pci_ids_s {
+	char nic_pci_id[MAX_PCI_IDS][NICS_PCI_ID_LEN + 1];
+	int num_nics;
+};
+
+#endif /* _NTCONNECT_API_H_ */
diff --git a/drivers/net/ntnic/ntconnect/include/ntconnect_api_adapter.h b/drivers/net/ntnic/ntconnect/include/ntconnect_api_adapter.h
new file mode 100644
index 0000000000..2362d440b4
--- /dev/null
+++ b/drivers/net/ntnic/ntconnect/include/ntconnect_api_adapter.h
@@ -0,0 +1,221 @@ 
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef _NTCONNECT_API_ADAPTER_H_
+#define _NTCONNECT_API_ADAPTER_H_
+
+/*
+ * adapter get,interfaces
+ */
+enum port_speed {
+	PORT_LINK_SPEED_UNKNOWN,
+	PORT_LINK_SPEED_NONE_REPORTED,
+	PORT_LINK_SPEED_10M,
+	PORT_LINK_SPEED_100M,
+	PORT_LINK_SPEED_1G,
+	PORT_LINK_SPEED_10G,
+	PORT_LINK_SPEED_25G,
+	PORT_LINK_SPEED_40G,
+	PORT_LINK_SPEED_50G,
+	PORT_LINK_SPEED_100G,
+};
+
+enum port_states {
+	PORT_STATE_DISABLED,
+	PORT_STATE_NIM_PRESENT,
+	PORT_STATE_NIM_ABSENT,
+	PORT_STATE_VIRTUAL_UNATTACHED,
+	PORT_STATE_VIRTUAL_SPLIT,
+	PORT_STATE_VIRTUAL_PACKED,
+	PORT_STATE_VIRTUAL_RELAY,
+};
+
+enum port_link { PORT_LINK_UNKNOWN, PORT_LINK_UP, PORT_LINK_DOWN };
+
+enum port_type {
+	PORT_TYPE_PHY_NORMAL, /* Normal phy port (no LAG) */
+	/* Link aggregated phy port in active/active LAG configuration */
+	PORT_TYPE_PHY_LAG_ACTIVE_AVTIVE,
+	PORT_TYPE_PHY_LAG_PRIMARY, /* Primary phy port in active/backup LAG configuration */
+	PORT_TYPE_PHY_LAG_BACKUP, /* Backup phy port in active/backup LAG configuration */
+	PORT_TYPE_VIRT,
+	PORT_TYPE_LAST
+};
+
+enum nim_identifier_e {
+	NIM_UNKNOWN = 0x00, /* Nim type is unknown */
+	NIM_GBIC = 0x01, /* Nim type = GBIC */
+	NIM_FIXED = 0x02, /* Nim type = FIXED */
+	NIM_SFP_SFP_PLUS = 0x03, /* Nim type = SFP/SFP+ */
+	NIM_300_PIN_XBI = 0x04, /* Nim type = 300 pin XBI */
+	NIM_XEN_PAK = 0x05, /* Nim type = XEN-PAK */
+	NIM_XFP = 0x06, /* Nim type = XFP */
+	NIM_XFF = 0x07, /* Nim type = XFF */
+	NIM_XFP_E = 0x08, /* Nim type = XFP-E */
+	NIM_XPAK = 0x09, /* Nim type = XPAK */
+	NIM_X2 = 0x0A, /* Nim type = X2 */
+	NIM_DWDM = 0x0B, /* Nim type = DWDM */
+	NIM_QSFP = 0x0C, /* Nim type = QSFP */
+	NIM_QSFP_PLUS = 0x0D, /* Nim type = QSFP+ */
+	NIM_QSFP28 = 0x11, /* Nim type = QSFP28 */
+	NIM_CFP4 = 0x12, /* Nim type = CFP4 */
+};
+
+/*
+ * Port types
+ */
+enum port_type_e {
+	PORT_TYPE_NOT_AVAILABLE =
+		0, /* The NIM/port type is not available (unknown) */
+	PORT_TYPE_NOT_RECOGNISED, /* The NIM/port type not recognized */
+	PORT_TYPE_RJ45, /* RJ45 type */
+	PORT_TYPE_SFP_NOT_PRESENT, /* SFP type but slot is empty */
+	PORT_TYPE_SFP_SX, /* SFP SX */
+	PORT_TYPE_SFP_SX_DD, /* SFP SX digital diagnostic */
+	PORT_TYPE_SFP_LX, /* SFP LX */
+	PORT_TYPE_SFP_LX_DD, /* SFP LX digital diagnostic */
+	PORT_TYPE_SFP_ZX, /* SFP ZX */
+	PORT_TYPE_SFP_ZX_DD, /* SFP ZX digital diagnostic */
+	PORT_TYPE_SFP_CU, /* SFP copper */
+	PORT_TYPE_SFP_CU_DD, /* SFP copper digital diagnostic */
+	PORT_TYPE_SFP_NOT_RECOGNISED, /* SFP unknown */
+	PORT_TYPE_XFP, /* XFP */
+	PORT_TYPE_XPAK, /* XPAK */
+	PORT_TYPE_SFP_CU_TRI_SPEED, /* SFP copper tri-speed */
+	PORT_TYPE_SFP_CU_TRI_SPEED_DD, /* SFP copper tri-speed digital diagnostic */
+	PORT_TYPE_SFP_PLUS, /* SFP+ type */
+	PORT_TYPE_SFP_PLUS_NOT_PRESENT, /* SFP+ type but slot is empty */
+	PORT_TYPE_XFP_NOT_PRESENT, /* XFP type but slot is empty */
+	PORT_TYPE_QSFP_PLUS_NOT_PRESENT, /* QSFP type but slot is empty */
+	PORT_TYPE_QSFP_PLUS, /* QSFP type */
+	PORT_TYPE_SFP_PLUS_PASSIVE_DAC, /* SFP+ Passive DAC */
+	PORT_TYPE_SFP_PLUS_ACTIVE_DAC, /* SFP+ Active DAC */
+	PORT_TYPE_CFP4, /* CFP4 type */
+	PORT_TYPE_CFP4_LR4 = PORT_TYPE_CFP4, /* CFP4 100G, LR4 type */
+	PORT_TYPE_CFP4_NOT_PRESENT, /* CFP4 type but slot is empty */
+	PORT_TYPE_INITIALIZE, /* The port type is not fully established yet */
+	PORT_TYPE_NIM_NOT_PRESENT, /* Generic "Not present" */
+	PORT_TYPE_HCB, /* Test mode: Host Compliance Board */
+	PORT_TYPE_NOT_SUPPORTED, /* The NIM type is not supported in this context */
+	PORT_TYPE_SFP_PLUS_DUAL_RATE, /* SFP+ supports 1G/10G */
+	PORT_TYPE_CFP4_SR4, /* CFP4 100G, SR4 type */
+	PORT_TYPE_QSFP28_NOT_PRESENT, /* QSFP28 type but slot is empty */
+	PORT_TYPE_QSFP28, /* QSFP28 type */
+	PORT_TYPE_QSFP28_SR4, /* QSFP28-SR4 type */
+	PORT_TYPE_QSFP28_LR4, /* QSFP28-LR4 type */
+	/* Deprecated. The port type should not mention speed eg 4x10 or 1x40 */
+	PORT_TYPE_QSFP_PLUS_4X10,
+	/* Deprecated. The port type should not mention speed eg 4x10 or 1x40 */
+	PORT_TYPE_QSFP_PASSIVE_DAC_4X10,
+	PORT_TYPE_QSFP_PASSIVE_DAC =
+		PORT_TYPE_QSFP_PASSIVE_DAC_4X10, /* QSFP passive DAC type */
+	/* Deprecated. The port type should not mention speed eg 4x10 or 1x40 */
+	PORT_TYPE_QSFP_ACTIVE_DAC_4X10,
+	PORT_TYPE_QSFP_ACTIVE_DAC =
+		PORT_TYPE_QSFP_ACTIVE_DAC_4X10, /* QSFP active DAC type */
+	PORT_TYPE_SFP_28, /* SFP28 type */
+	PORT_TYPE_SFP_28_SR, /* SFP28-SR type */
+	PORT_TYPE_SFP_28_LR, /* SFP28-LR type */
+	PORT_TYPE_SFP_28_CR_CA_L, /* SFP28-CR-CA-L type */
+	PORT_TYPE_SFP_28_CR_CA_S, /* SFP28-CR-CA-S type */
+	PORT_TYPE_SFP_28_CR_CA_N, /* SFP28-CR-CA-N type */
+	PORT_TYPE_QSFP28_CR_CA_L, /* QSFP28-CR-CA-L type */
+	PORT_TYPE_QSFP28_CR_CA_S, /* QSFP28-CR-CA-S type */
+	PORT_TYPE_QSFP28_CR_CA_N, /* QSFP28-CR-CA-N type */
+	PORT_TYPE_SFP_28_SR_DR, /* SFP28-SR-DR type */
+	PORT_TYPE_SFP_28_LR_DR, /* SFP28-LR-DR type */
+	PORT_TYPE_SFP_FX, /* SFP FX */
+	PORT_TYPE_SFP_PLUS_CU, /* SFP+ CU type */
+	PORT_TYPE_QSFP28_FR, /* QSFP28-FR type. Uses PAM4 modulation on one lane only */
+	PORT_TYPE_QSFP28_DR, /* QSFP28-DR type. Uses PAM4 modulation on one lane only */
+	PORT_TYPE_QSFP28_LR, /* QSFP28-LR type. Uses PAM4 modulation on one lane only */
+};
+
+struct mac_addr_s {
+	uint8_t addr_b[6];
+};
+
+struct nim_link_length_s {
+	/* NIM link length (in meters) supported SM (9um). A value of 0xFFFF indicates that the
+	 * length is >= 65535 m
+	 */
+	uint16_t sm;
+	uint16_t ebw; /* NIM link length (in meters) supported EBW (50um) */
+	uint16_t mm50; /* NIM link length (in meters) supported MM (50um) */
+	uint16_t mm62; /* NIM link length (in meters) supported MM (62.5um) */
+	uint16_t copper; /* NIM link length (in meters) supported copper */
+};
+
+struct nim_data_s {
+	uint8_t nim_id;
+	uint8_t port_type;
+	char vendor_name[17];
+	char prod_no[17];
+	char serial_no[17];
+	char date[9];
+	char rev[5];
+	uint8_t pwr_level_req;
+	uint8_t pwr_level_cur;
+	struct nim_link_length_s link_length;
+};
+
+struct sensor {
+	uint8_t sign;
+	uint8_t type;
+	uint32_t current_value;
+	uint32_t min_value;
+	uint32_t max_value;
+	char name[50];
+};
+
+struct ntc_sensors_s {
+	uint16_t adapter_sensors_cnt;
+	uint16_t ports_cnt;
+	uint16_t nim_sensors_cnt[8];
+	char adapter_name[24];
+};
+
+#define MAX_RSS_QUEUES 128
+
+enum queue_dir_e { QUEUE_INPUT, QUEUE_OUTPUT };
+
+struct queue_s {
+	enum queue_dir_e dir;
+	int idx;
+};
+
+struct ntc_interface_s {
+	uint8_t port_id;
+	enum port_type type;
+	enum port_link link;
+	enum port_states port_state;
+	enum port_speed port_speed;
+	struct pci_id_s pci_id;
+	struct mac_addr_s mac;
+	struct nim_data_s nim_data;
+	uint16_t mtu;
+	/* attached queues */
+	struct {
+		struct queue_s queue[MAX_RSS_QUEUES];
+		int num_queues;
+	};
+};
+
+/*
+ * adapter get,interfaces
+ */
+struct ntc_interfaces_s {
+	int final_list;
+	uint8_t nb_ports;
+	struct ntc_interface_s intf[];
+};
+
+/*
+ * adapter get,info
+ */
+struct ntc_adap_get_info_s {
+	char *fw_version[32];
+};
+
+#endif /* _NTCONNECT_API_ADAPTER_H_ */
diff --git a/drivers/net/ntnic/ntconnect/include/ntconnect_api_flow.h b/drivers/net/ntnic/ntconnect/include/ntconnect_api_flow.h
new file mode 100644
index 0000000000..4091d61d7d
--- /dev/null
+++ b/drivers/net/ntnic/ntconnect/include/ntconnect_api_flow.h
@@ -0,0 +1,168 @@ 
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef _NTCONNECT_API_FILTER_H_
+#define _NTCONNECT_API_FILTER_H_
+
+#include "stream_binary_flow_api.h"
+
+/*
+ * Create structures allocating the space to carry through ntconnect interface
+ */
+#define MAX_FLOW_STREAM_ELEM 16
+#define MAX_FLOW_STREAM_QUERY_DATA 1024
+#define MAX_FLOW_STREAM_ERROR_MSG 128
+#define MAX_FLOW_STREAM_VXLAN_TUN_ELEM 8
+#define MAX_FLOW_STREAM_COUNT_ACTIONS 4
+
+#define MAX_PATH_LEN 128
+
+enum ntconn_flow_err_e {
+	NTCONN_FLOW_ERR_NONE = 0,
+	NTCONN_FLOW_ERR_INTERNAL_ERROR = 0x100,
+	NTCONN_FLOW_ERR_PORT_IS_NOT_INITIALIZED,
+	NTCONN_FLOW_ERR_INVALID_PORT,
+	NTCONN_FLOW_ERR_UNEXPECTED_VIRTIO_PATH,
+	NTCONN_FLOW_ERR_UNSUPPORTED_ADAPTER,
+	NTCONN_FLOW_ERR_TO_MANY_FLOWS,
+	NTCONN_FLOW_ERR_NOT_YET_IMPLEMENTED,
+	NTCONN_FLOW_ERR_NO_VF_QUEUES,
+};
+
+struct flow_elem_types_s {
+	int valid;
+	union {
+		int start_addr;
+		struct flow_elem_eth eth;
+		struct flow_elem_vlan vlan[2];
+		struct flow_elem_ipv4 ipv4;
+		struct flow_elem_ipv6 ipv6;
+		struct flow_elem_sctp sctp;
+		struct flow_elem_tcp tcp;
+		struct flow_elem_udp udp;
+		struct flow_elem_icmp icmp;
+		struct flow_elem_vxlan vxlan;
+		struct flow_elem_port_id port_id;
+		struct flow_elem_tag tag;
+	} u;
+};
+
+struct flow_elem_cpy {
+	enum flow_elem_type type; /* element type */
+	struct flow_elem_types_s spec_cpy;
+	struct flow_elem_types_s mask_cpy;
+};
+
+struct flow_action_vxlan_encap_cpy {
+	/* Encapsulating vxlan tunnel definition */
+	struct flow_elem_cpy vxlan_tunnel[MAX_FLOW_STREAM_VXLAN_TUN_ELEM];
+};
+
+struct flow_action_rss_cpy {
+	struct flow_action_rss rss;
+	uint16_t cpy_queue[FLOW_MAX_QUEUES];
+};
+
+#define MAX_ACTION_ENCAP_DATA 512
+struct flow_action_decap_cpy {
+	uint8_t data[MAX_ACTION_ENCAP_DATA];
+	size_t size;
+	struct flow_elem_cpy item_cpy
+		[RAW_ENCAP_DECAP_ELEMS_MAX]; /* Need room for end command */
+	int item_count;
+};
+
+struct flow_action_encap_cpy {
+	uint8_t data[MAX_ACTION_ENCAP_DATA];
+	size_t size;
+	struct flow_elem_cpy item_cpy
+		[RAW_ENCAP_DECAP_ELEMS_MAX]; /* Need room for end command */
+	int item_count;
+};
+
+struct flow_action_types_s {
+	int valid;
+	union {
+		int start_addr;
+		struct flow_action_rss_cpy rss;
+		struct flow_action_push_vlan vlan;
+		struct flow_action_set_vlan_vid vlan_vid;
+		struct flow_action_vxlan_encap_cpy vxlan;
+		struct flow_action_count count;
+		struct flow_action_mark mark;
+		struct flow_action_port_id port_id;
+		struct flow_action_tag tag;
+		struct flow_action_queue queue;
+		struct flow_action_decap_cpy decap;
+		struct flow_action_encap_cpy encap;
+		struct flow_action_jump jump;
+		struct flow_action_meter meter;
+	} u;
+};
+
+struct flow_action_cpy {
+	enum flow_action_type type;
+	struct flow_action_types_s conf_cpy;
+};
+
+struct query_flow_ntconnect {
+	uint8_t port;
+	struct flow_action_cpy action;
+	uint64_t flow;
+};
+
+struct create_flow_ntconnect {
+	uint8_t port;
+	uint8_t vport;
+	struct flow_attr attr;
+	struct flow_elem_cpy elem[MAX_FLOW_STREAM_ELEM];
+	struct flow_action_cpy action[MAX_FLOW_STREAM_ELEM];
+};
+
+struct destroy_flow_ntconnect {
+	uint8_t port;
+	uint64_t flow;
+};
+
+#define ERR_MSG_LEN 128LLU
+
+struct flow_setport_return {
+	struct flow_queue_id_s queues[FLOW_MAX_QUEUES];
+	uint8_t num_queues;
+};
+
+struct flow_error_return_s {
+	enum flow_error_e type;
+	char err_msg[ERR_MSG_LEN];
+	int status;
+};
+
+struct create_flow_return_s {
+	uint64_t flow;
+};
+
+struct validate_flow_return_s {
+	int status;
+};
+
+struct query_flow_return_s {
+	enum flow_error_e type;
+	char err_msg[ERR_MSG_LEN];
+	int status;
+	uint32_t data_length;
+	uint8_t data[];
+};
+
+struct flow_return_s {
+	enum flow_error_e type;
+	char err_msg[ERR_MSG_LEN];
+	int status;
+};
+
+struct flow_error_ntconn {
+	enum flow_error_e type;
+	char message[ERR_MSG_LEN];
+};
+
+#endif /* _NTCONNECT_API_FILTER_H_ */
diff --git a/drivers/net/ntnic/ntconnect/include/ntconnect_api_meter.h b/drivers/net/ntnic/ntconnect/include/ntconnect_api_meter.h
new file mode 100644
index 0000000000..901b0ccba1
--- /dev/null
+++ b/drivers/net/ntnic/ntconnect/include/ntconnect_api_meter.h
@@ -0,0 +1,89 @@ 
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef _NTCONNECT_METER_FILTER_H_
+#define _NTCONNECT_METER_FILTER_H_
+
+#define FLOW_COOKIE 0x12344321
+
+/*
+ * Create structures allocating the space to carry through ntconnect interface
+ */
+
+#define MAX_PATH_LEN 128
+
+enum ntconn_meter_err_e {
+	NTCONN_METER_ERR_NONE = 0,
+	NTCONN_METER_ERR_INTERNAL_ERROR = 0x100,
+	NTCONN_METER_ERR_INVALID_PORT,
+	NTCONN_METER_ERR_UNEXPECTED_VIRTIO_PATH,
+	NTCONN_METER_ERR_PROFILE_ID,
+	NTCONN_METER_ERR_POLICY_ID,
+	NTCONN_METER_ERR_METER_ID,
+};
+
+enum ntconn_meter_command_e {
+	UNKNOWN_CMD,
+	ADD_PROFILE,
+	DEL_PROFILE,
+	ADD_POLICY,
+	DEL_POLICY,
+	CREATE_MTR,
+	DEL_MTR
+};
+
+#define ERR_MSG_LEN 128LLU
+
+struct meter_error_return_s {
+	enum rte_mtr_error_type type;
+	int status;
+	char err_msg[ERR_MSG_LEN];
+};
+
+struct meter_setup_s {
+	uint8_t vport;
+	uint32_t id;
+	int shared;
+	union {
+		struct rte_mtr_meter_profile profile;
+		struct {
+			struct rte_mtr_meter_policy_params policy;
+			struct rte_flow_action actions_green[2];
+			struct rte_flow_action actions_yellow[2];
+			struct rte_flow_action actions_red[2];
+		} p;
+		struct rte_mtr_params mtr_params;
+	};
+};
+
+struct meter_get_stat_s {
+	uint8_t vport;
+	uint32_t mtr_id;
+	int clear;
+};
+
+struct meter_return_stat_s {
+	struct rte_mtr_stats stats;
+	uint64_t stats_mask;
+};
+
+struct meter_setup_ptr_s {
+	uint32_t id;
+	int shared;
+	union {
+		struct rte_mtr_meter_profile *profile;
+		struct rte_mtr_meter_policy_params *policy;
+		struct rte_mtr_params *mtr_params;
+	};
+};
+
+struct meter_return_s {
+	int status;
+};
+
+struct meter_capabilities_return_s {
+	struct rte_mtr_capabilities cap;
+};
+
+#endif /* _NTCONNECT_METER_FILTER_H_ */
diff --git a/drivers/net/ntnic/ntconnect/include/ntconnect_api_statistic.h b/drivers/net/ntnic/ntconnect/include/ntconnect_api_statistic.h
new file mode 100644
index 0000000000..1022bc2056
--- /dev/null
+++ b/drivers/net/ntnic/ntconnect/include/ntconnect_api_statistic.h
@@ -0,0 +1,173 @@ 
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef _NTCONNECT_API_STATISTIC_H_
+#define _NTCONNECT_API_STATISTIC_H_
+
+/*
+ * Supported defined statistic records for Stat layout version 6 - defined in nthw_stat module
+ */
+#define NUM_STAT_RECORD_TYPE_COLOR \
+	(sizeof(struct color_type_fields_s) / sizeof(uint64_t))
+struct color_type_fields_s {
+	uint64_t pkts;
+	uint64_t octets;
+	uint64_t tcp_flgs;
+};
+
+#define NUM_STAT_RECORD_TYPE_FLOWMATCHER \
+	(sizeof(struct flowmatcher_type_fields_s) / sizeof(uint64_t))
+struct flowmatcher_type_fields_s {
+	/* FLM 0.17 */
+	uint64_t current;
+	uint64_t learn_done;
+	uint64_t learn_ignore;
+	uint64_t learn_fail;
+	uint64_t unlearn_done;
+	uint64_t unlearn_ignore;
+	uint64_t auto_unlearn_done;
+	uint64_t auto_unlearn_ignore;
+	uint64_t auto_unlearn_fail;
+	uint64_t timeout_unlearn_done;
+	uint64_t rel_done;
+	uint64_t rel_ignore;
+	uint64_t prb_done;
+	uint64_t prb_ignore;
+	/* FLM 0.20 */
+	uint64_t sta_done;
+	uint64_t inf_done;
+	uint64_t inf_skip;
+	uint64_t pck_hit;
+	uint64_t pck_miss;
+	uint64_t pck_unh;
+	uint64_t pck_dis;
+	uint64_t csh_hit;
+	uint64_t csh_miss;
+	uint64_t csh_unh;
+	uint64_t cuc_start;
+	uint64_t cuc_move;
+};
+
+#define NUM_STAT_RECORD_TYPE_QUEUE \
+	(sizeof(struct queue_type_fields_s) / sizeof(uint64_t))
+struct queue_type_fields_s {
+	uint64_t flush_pkts;
+	uint64_t drop_pkts;
+	uint64_t fwd_pkts;
+	uint64_t dbs_drop_pkts;
+	uint64_t flush_octets;
+	uint64_t drop_octets;
+	uint64_t fwd_octets;
+	uint64_t dbs_drop_octets;
+};
+
+/*
+ * Port stat counters for virtualization NICS with virtual ports support
+ */
+#define NUM_STAT_RECORD_TYPE_RX_PORT_VIRT \
+	(sizeof(struct rtx_type_fields_virt_s) / sizeof(uint64_t))
+/* same for Rx and Tx counters on Virt */
+#define NUM_STAT_RECORD_TYPE_TX_PORT_VIRT NUM_STAT_RECORD_TYPE_RX_PORT_VIRT
+struct rtx_type_fields_virt_s {
+	uint64_t octets;
+	uint64_t pkts;
+	uint64_t drop_events;
+	uint64_t qos_drop_octets;
+	uint64_t qos_drop_pkts;
+};
+
+/*
+ * Port RMON counters for Cap devices
+ */
+struct stat_rmon_s {
+	/* Sums that are calculated by software */
+	uint64_t drop_events;
+	uint64_t pkts;
+	/* Read from FPGA */
+	uint64_t octets;
+	uint64_t broadcast_pkts;
+	uint64_t multicast_pkts;
+	uint64_t unicast_pkts;
+	uint64_t pkts_alignment;
+	uint64_t pkts_code_violation;
+	uint64_t pkts_crc;
+	uint64_t undersize_pkts;
+	uint64_t oversize_pkts;
+	uint64_t fragments;
+	uint64_t jabbers_not_truncated;
+	uint64_t jabbers_truncated;
+	uint64_t pkts_64_octets;
+	uint64_t pkts_65_to_127_octets;
+	uint64_t pkts_128_to_255_octets;
+	uint64_t pkts_256_to_511_octets;
+	uint64_t pkts_512_to_1023_octets;
+	uint64_t pkts_1024_to_1518_octets;
+	uint64_t pkts_1519_to_2047_octets;
+	uint64_t pkts_2048_to_4095_octets;
+	uint64_t pkts_4096_to_8191_octets;
+	uint64_t pkts_8192_to_max_octets;
+};
+
+#define NUM_STAT_RECORD_TYPE_RX_PORT_CAP \
+	(sizeof(struct rx_type_fields_cap_s) / sizeof(uint64_t))
+struct rx_type_fields_cap_s {
+	struct stat_rmon_s rmon;
+	uint64_t mac_drop_events;
+	uint64_t pkts_lr;
+	/* Rx only port counters */
+	uint64_t duplicate;
+	uint64_t pkts_ip_chksum_error;
+	uint64_t pkts_udp_chksum_error;
+	uint64_t pkts_tcp_chksum_error;
+	uint64_t pkts_giant_undersize;
+	uint64_t pkts_baby_giant;
+	uint64_t pkts_not_isl_vlan_mpls;
+	uint64_t pkts_isl;
+	uint64_t pkts_vlan;
+	uint64_t pkts_isl_vlan;
+	uint64_t pkts_mpls;
+	uint64_t pkts_isl_mpls;
+	uint64_t pkts_vlan_mpls;
+	uint64_t pkts_isl_vlan_mpls;
+	uint64_t pkts_no_filter;
+	uint64_t pkts_dedup_drop;
+	uint64_t pkts_filter_drop;
+	uint64_t pkts_overflow;
+	uint64_t pkts_dbs_drop;
+	uint64_t octets_no_filter;
+	uint64_t octets_dedup_drop;
+	uint64_t octets_filter_drop;
+	uint64_t octets_overflow;
+	uint64_t octets_dbs_drop;
+	uint64_t ipft_first_hit;
+	uint64_t ipft_first_not_hit;
+	uint64_t ipft_mid_hit;
+	uint64_t ipft_mid_not_hit;
+	uint64_t ipft_last_hit;
+	uint64_t ipft_last_not_hit;
+};
+
+#define NUM_STAT_RECORD_TYPE_TX_PORT_CAP \
+	(sizeof(struct tx_type_fields_cap_s) / sizeof(uint64_t))
+struct tx_type_fields_cap_s {
+	struct stat_rmon_s rmon;
+};
+
+/*
+ * stat get,colors
+ * stat get,queues
+ * stat get,rx_counters
+ * stat get,tx_counters
+ */
+#define STAT_INFO_ELEMENTS \
+	(sizeof(struct ntc_stat_get_data_s) / sizeof(uint64_t))
+
+struct ntc_stat_get_data_s {
+	uint64_t nb_counters;
+	uint64_t timestamp;
+	uint64_t is_virt;
+	uint64_t data[];
+};
+
+#endif /* _NTCONNECT_API_STATISTIC_H_ */
diff --git a/drivers/net/ntnic/ntconnect/include/ntconnect_api_test.h b/drivers/net/ntnic/ntconnect/include/ntconnect_api_test.h
new file mode 100644
index 0000000000..44cacbd931
--- /dev/null
+++ b/drivers/net/ntnic/ntconnect/include/ntconnect_api_test.h
@@ -0,0 +1,18 @@ 
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef _NTCONNECT_TEST_FILTER_H_
+#define _NTCONNECT_TEST_FILTER_H_
+
+/*
+ * Create structures allocating the space to carry through ntconnect interface
+ */
+
+struct test_s {
+	int number;
+	int status;
+	uint64_t test[];
+};
+
+#endif /* _NTCONNECT_TEST_FILTER_H_ */
diff --git a/drivers/net/ntnic/ntconnect/ntconn_server.c b/drivers/net/ntnic/ntconnect/ntconn_server.c
new file mode 100644
index 0000000000..34a3c19955
--- /dev/null
+++ b/drivers/net/ntnic/ntconnect/ntconn_server.c
@@ -0,0 +1,97 @@ 
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "ntconnect.h"
+#include "ntconn_mod_helper.h"
+#include "nt_util.h"
+#include "ntlog.h"
+
+/*
+ * Server module always registered on 0000:00:00.0
+ */
+#define this_module_name "server"
+
+#define NTCONNECT_SERVER_VERSION_MAJOR 0U
+#define NTCONNECT_SERVER_VERSION_MINOR 1U
+
+static int func_get_nic_pci(void *hdl, int client_fd,
+			    struct ntconn_header_s *hdr, char **data, int *len);
+static struct func_s funcs_get_level1[] = {
+	{ "nic_pci_ids", NULL, func_get_nic_pci },
+	{ NULL, NULL, NULL },
+};
+
+/*
+ * Entry level
+ */
+static struct func_s server_entry_funcs[] = {
+	{ "get", funcs_get_level1, NULL },
+	{ NULL, NULL, NULL },
+};
+
+static int func_get_nic_pci(void *hdl, int client_fd _unused,
+			    struct ntconn_header_s *hdr _unused, char **data,
+			    int *len)
+{
+	struct ntconn_server_s *serv = (struct ntconn_server_s *)hdl;
+	struct ntc_nic_pci_ids_s *npci =
+		calloc(1, sizeof(struct ntc_nic_pci_ids_s));
+	if (!npci) {
+		*len = 0;
+		NT_LOG(ERR, NTCONNECT, "memory allocation failed");
+		return REQUEST_ERR;
+	}
+	int i = 0;
+
+	while (i < MAX_PCI_IDS && serv->pci_id_list[i].pci_id) {
+		sprintf(npci->nic_pci_id[i], "%04x:%02x:%02x.%x",
+			serv->pci_id_list[i].domain & 0xffff,
+			serv->pci_id_list[i].bus, serv->pci_id_list[i].devid,
+			serv->pci_id_list[i].function);
+		i++;
+	}
+	npci->num_nics = i;
+	*data = (char *)npci;
+	*len = sizeof(struct ntc_nic_pci_ids_s);
+
+	return REQUEST_OK;
+}
+
+static int ntconn_server_request(void *hdl, int client_id,
+				 struct ntconn_header_s *hdr, char *function,
+				 char **data, int *len)
+{
+	return execute_function(this_module_name, hdl, client_id, hdr, function,
+				server_entry_funcs, data, len, 0);
+}
+
+static void ntconn_server_free_data(void *hdl _unused, char *data)
+{
+	if (data) {
+#ifdef DEBUG
+		NT_LOG(DBG, NTCONNECT, "server free data\n");
+#endif
+		free(data);
+	}
+}
+
+static const ntconnapi_t ntconn_server_op = { this_module_name,
+					      NTCONNECT_SERVER_VERSION_MAJOR,
+					      NTCONNECT_SERVER_VERSION_MINOR,
+					      ntconn_server_request,
+					      ntconn_server_free_data,
+					      NULL
+					    };
+
+int ntconn_server_register(void *server)
+{
+	const struct rte_pci_addr addr = {
+		.domain = 0, .bus = 0, .devid = 0, .function = 0
+	};
+
+	return register_ntconn_mod(&addr, server, &ntconn_server_op);
+}
diff --git a/drivers/net/ntnic/ntconnect/ntconnect.c b/drivers/net/ntnic/ntconnect/ntconnect.c
new file mode 100644
index 0000000000..51f0577194
--- /dev/null
+++ b/drivers/net/ntnic/ntconnect/ntconnect.c
@@ -0,0 +1,641 @@ 
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <libgen.h>
+#include <pthread.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/poll.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+#include "nt_util.h"
+#include "ntconnect.h"
+#include "ntconnect_api.h"
+#include "ntlog.h"
+
+/* clang-format off */
+ntconn_err_t ntconn_err[] = {
+	{NTCONN_ERR_CODE_NONE, "Success"},
+	{NTCONN_ERR_CODE_INTERNAL_ERROR, "Internal error"},
+	{NTCONN_ERR_CODE_INTERNAL_REPLY_ERROR, "Internal error in reply from module"},
+	{NTCONN_ERR_CODE_NO_DATA, "No data found"},
+	{NTCONN_ERR_CODE_INVALID_REQUEST, "Invalid request"},
+	{NTCONN_ERR_CODE_NOT_YET_IMPLEMENTED, "Function not yet implemented"},
+	{NTCONN_ERR_CODE_INTERNAL_FUNC_ERROR, "Internal error in function call list"},
+	{NTCONN_ERR_CODE_MISSING_INVALID_PARAM, "Missing or invalid parameter"},
+	{NTCONN_ERR_CODE_FUNCTION_PARAM_INCOMPLETE, "Function parameter is incomplete"},
+	{NTCONN_ERR_CODE_FUNC_PARAM_NOT_RECOGNIZED,
+		"Function or parameter not recognized/supported"},
+	{-1, NULL}
+};
+
+/* clang-format on */
+
+static ntconn_mod_t *ntcmod_base;
+static pthread_t tid;
+static pthread_t ctid;
+static struct ntconn_server_s ntconn_serv;
+
+const ntconn_err_t *get_ntconn_error(enum ntconn_err_e err_code)
+{
+	int idx = 0;
+
+	while (ntconn_err[idx].err_code != (uint32_t)-1 &&
+			ntconn_err[idx].err_code != err_code)
+		idx++;
+	if (ntconn_err[idx].err_code == (uint32_t)-1)
+		idx = 1;
+
+	return &ntconn_err[idx];
+}
+
+int register_ntconn_mod(const struct rte_pci_addr *addr, void *hdl,
+			const ntconnapi_t *op)
+{
+	/* Verify and check module name is unique */
+#ifdef DEBUG
+	NT_LOG(DBG, NTCONNECT,
+	       "Registering pci: %04x:%02x:%02x.%x, module %s\n", addr->domain,
+	       addr->bus, addr->devid, addr->function, op->module);
+#endif
+
+	ntconn_mod_t *ntcmod = (ntconn_mod_t *)malloc(sizeof(ntconn_mod_t));
+
+	if (!ntcmod) {
+		NT_LOG(ERR, NTCONNECT, "memory allocation failed");
+		return -1;
+	}
+	ntcmod->addr.domain = addr->domain;
+	ntcmod->addr.bus = addr->bus;
+	ntcmod->addr.devid = addr->devid;
+	ntcmod->addr.function = addr->function;
+	ntcmod->addr.pad = 0;
+
+	ntcmod->hdl = hdl;
+	ntcmod->op = op;
+	pthread_mutex_init(&ntcmod->mutex, NULL);
+
+	ntcmod->next = ntcmod_base;
+	ntcmod_base = ntcmod;
+
+	if (ntcmod->addr.pci_id) { /* Avoid server fake pci_id */
+		int i;
+
+		for (i = 0; i < MAX_PCI_IDS; i++) {
+			if (ntconn_serv.pci_id_list[i].pci_id == 0) {
+				NT_LOG(DBG, NTCONNECT,
+				       "insert at index %i PCI ID %" PRIX64 "\n", i,
+				       ntcmod->addr.pci_id);
+				ntconn_serv.pci_id_list[i].pci_id =
+					ntcmod->addr.pci_id;
+				break;
+			} else if (ntconn_serv.pci_id_list[i].pci_id ==
+					ntcmod->addr.pci_id)
+				break;
+		}
+	}
+
+	return 0;
+}
+
+static int unix_build_address(const char *path, struct sockaddr_un *addr)
+{
+	if (addr == NULL || path == NULL)
+		return -1;
+	memset(addr, 0, sizeof(struct sockaddr_un));
+	addr->sun_family = AF_UNIX;
+	if (strlen(path) < sizeof(addr->sun_path)) {
+		rte_strscpy(addr->sun_path, path, sizeof(addr->sun_path) - 1);
+		return 0;
+	}
+	return -1;
+}
+
+#define STATUS_OK 0
+#define STATUS_INTERNAL_ERROR -1
+#define STATUS_TRYAGAIN -2
+#define STATUS_INVALID_PARAMETER -3
+#define STATUS_CONNECTION_CLOSED -4
+#define STATUS_CONNECTION_INVALID -5
+#define STATUS_TIMEOUT -6
+
+static int read_data(int fd, size_t len, uint8_t *data, size_t *recv_len,
+		     int timeout)
+{
+	struct pollfd pfd;
+	ssize_t ret;
+
+	pfd.fd = fd;
+	pfd.events = POLLIN;
+	pfd.revents = 0;
+
+	ret = poll(&pfd, 1, timeout);
+	if (ret < 0) {
+		if (errno == EINTR)
+			return STATUS_TRYAGAIN; /* Caught signal before timeout */
+		if (errno == EINVAL)
+			return STATUS_INVALID_PARAMETER; /* Timeout is negative */
+		if (errno == EFAULT)
+			return STATUS_INVALID_PARAMETER; /* Fds argument is illegal */
+		/* else */
+		assert(0);
+		return STATUS_INTERNAL_ERROR;
+	}
+
+	if (ret == 0)
+		return STATUS_TIMEOUT;
+
+	if (pfd.revents == 0) {
+		assert(ret == 1);
+		assert(0); /* Revents cannot be zero when NtSocket_Poll returns 1 */
+		return STATUS_TRYAGAIN;
+	}
+
+	if ((pfd.revents & POLLIN) &&
+			((pfd.revents & (POLLERR | POLLNVAL)) == 0)) {
+		ret = recv(pfd.fd, data, len, 0);
+		if (ret < 0) {
+			int lerrno = errno;
+
+			if (lerrno == EWOULDBLOCK || lerrno == EAGAIN) {
+				/*
+				 * We have data but if the very first read turns out to return
+				 * EWOULDBLOCK or EAGAIN it means that the remote  end has dropped
+				 * the connection
+				 */
+				NT_LOG(DBG, NTCONNECT,
+				       "The socket with fd %d has been closed by remote end. %d [%s]\n",
+				       pfd.fd, lerrno, strerror(lerrno));
+				return STATUS_CONNECTION_CLOSED;
+			}
+			if (lerrno != EINTR) {
+				NT_LOG(ERR, NTCONNECT,
+				       "recv() from fd %d received errno %d [%s]\n",
+				       pfd.fd, lerrno, strerror(lerrno));
+				return STATUS_CONNECTION_INVALID;
+			}
+			/* EINTR */
+			return STATUS_TRYAGAIN;
+		}
+		if (ret == 0) {
+			if (pfd.revents & POLLHUP) {
+				/* This means that we have read all data and the remote end has
+				 * HUP
+				 */
+#ifdef DEBUG
+				NT_LOG(DBG, NTCONNECT,
+				       "The remote end has terminated the session\n");
+#endif
+				return STATUS_CONNECTION_CLOSED;
+			}
+			return STATUS_TRYAGAIN;
+		}
+
+		/* Ret can only be positive at this point */
+		 *recv_len = (size_t)ret;
+		return STATUS_OK;
+	}
+
+	if ((pfd.revents & POLLHUP) == POLLHUP) {
+		/* this means that the remote end has HUP */
+		NT_LOG(DBG, NTCONNECT,
+		       "The remote end has terminated the session\n");
+		return STATUS_CONNECTION_CLOSED;
+	}
+
+	NT_LOG(ERR, NTCONNECT,
+	       "poll() returned 0x%x. Invalidating the connection\n",
+	       pfd.revents);
+	return STATUS_CONNECTION_INVALID;
+}
+
+static int read_all(int clfd, uint8_t *data, size_t length)
+{
+	size_t recv_len = 0;
+	size_t left = length;
+	size_t pos = 0;
+
+	while (left > 0) {
+		int ret = read_data(clfd, left, &data[pos], &recv_len, -1);
+
+		if (ret == STATUS_OK) {
+			pos += recv_len;
+			left -= recv_len;
+		} else {
+			if (ret == STATUS_CONNECTION_CLOSED || ret == STATUS_TIMEOUT) {
+				/* Silently return status */
+				return ret;
+			}
+			if (ret != STATUS_TRYAGAIN) {
+				NT_LOG(ERR, NTCONNECT,
+				       "Failed getting packet. Error code: 0x%X\n",
+				       ret);
+				return ret;
+			}
+		}
+		/* Try again */
+	}
+	return STATUS_OK;
+}
+
+static int write_all(int fd, const uint8_t *data, size_t size)
+{
+	size_t len = 0;
+
+	while (len < size) {
+		ssize_t res = write(fd, (const void *)&data[len], size - len);
+
+		if (res < 0) {
+			NT_LOG(ERR, NTCONNECT, "write to socket failed!");
+			return STATUS_INTERNAL_ERROR;
+		}
+		len += res;
+	}
+	return 0;
+}
+
+static int read_request(int clfd, char **rdata)
+{
+	uint8_t *data = malloc(MESSAGE_BUFFER * sizeof(uint8_t));
+
+	if (!data) {
+		NT_LOG(ERR, NTCONNECT, "memory allocation failed");
+		return STATUS_INTERNAL_ERROR;
+	}
+
+	size_t recv_len = 0;
+	int ret = read_data(clfd, MESSAGE_BUFFER, data, &recv_len, -1);
+
+	if (ret) {
+		free(data);
+		return ret;
+	}
+
+	struct ntconn_header_s hdr;
+
+	memcpy(&hdr, data, NTCMOD_HDR_LEN);
+	size_t length = (hdr.len + hdr.blob_len) * sizeof(uint8_t);
+
+	if (length > MESSAGE_BUFFER) {
+		uint8_t *new_data = realloc(data, length);
+
+		if (!new_data) {
+			NT_LOG(ERR, NTCONNECT, "memory reallocation failed");
+			free(data);
+			return STATUS_INTERNAL_ERROR;
+		}
+		data = new_data;
+		ret = read_all(clfd, &data[recv_len], length - recv_len);
+		if (ret) {
+			free(data);
+			return ret;
+		}
+	}
+
+	*rdata = (char *)data;
+	return STATUS_OK;
+}
+
+static ntconn_mod_t *ntconnect_interpret_request(int clfd,
+		struct ntconn_header_s *hdr,
+		char **get_req _unused,
+		char **module_cmd, int *status)
+{
+	char pci_id[32];
+	char module[64];
+	ntconn_mod_t *result_ntcmod = NULL;
+	char *request = NULL;
+
+	int ret = read_request(clfd, &request);
+	*status = ret;
+	*get_req = request;
+
+	if (ret == STATUS_OK && request) {
+		*hdr = *(struct ntconn_header_s *)request;
+
+		if (!hdr) {
+			NT_LOG(ERR, NTCONNECT, "hdr returned NULL\n");
+			*status = STATUS_INTERNAL_ERROR;
+			return NULL;
+		}
+
+		switch (hdr->tag) {
+		case NTCONN_TAG_REQUEST: {
+			unsigned long idx = NTCMOD_HDR_LEN;
+			char *saveptr;
+			char *req = &request[idx];
+
+			uint32_t domain = 0xffffffff;
+			uint8_t bus = 0xff;
+			uint8_t devid = 0xff;
+			uint8_t function = 0xff;
+
+			char *tok = strtok_r(req, ";", &saveptr);
+
+			idx += strlen(tok) + 1;
+			if (!tok)
+				goto err_out;
+			rte_strscpy(pci_id, tok, 31);
+
+			tok = strtok_r(NULL, ";", &saveptr);
+			idx += strlen(tok) + 1;
+			if (!tok)
+				goto err_out;
+			rte_strscpy(module, tok, 63);
+
+			tok = strtok_r(NULL, "", &saveptr);
+			hdr->len -= idx;
+			if (tok)
+				*module_cmd = &request[idx];
+
+			tok = strtok_r(pci_id, ":.", &saveptr);
+			if (!tok)
+				goto err_out;
+			domain = (uint32_t)strtol(tok, NULL, 16);
+			tok = strtok_r(NULL, ":.", &saveptr);
+			if (!tok)
+				goto err_out;
+			bus = (uint8_t)strtol(tok, NULL, 16);
+
+			tok = strtok_r(NULL, ":.", &saveptr);
+			if (!tok)
+				goto err_out;
+			devid = (uint8_t)strtol(tok, NULL, 16);
+			tok = strtok_r(NULL, "", &saveptr);
+			if (!tok)
+				goto err_out;
+			function = (uint8_t)strtol(tok, NULL, 16);
+
+			/* Search for module registered as <pci_id:module> */
+			ntconn_mod_t *ntcmod = ntcmod_base;
+
+			while (ntcmod) {
+				if (domain == ntcmod->addr.domain &&
+						bus == ntcmod->addr.bus &&
+						devid == ntcmod->addr.devid &&
+						function == ntcmod->addr.function &&
+						strcmp(ntcmod->op->module, module) == 0) {
+					result_ntcmod = ntcmod;
+					break;
+				}
+				ntcmod = ntcmod->next;
+			}
+		}
+		break;
+
+		default:
+			break;
+		}
+	}
+
+err_out:
+
+	return result_ntcmod;
+}
+
+static int send_reply(int clfd, uint16_t reply_tag, const void *data,
+		      uint32_t size)
+{
+	struct ntconn_header_s hdr;
+
+	hdr.tag = reply_tag;
+	hdr.len = NTCMOD_HDR_LEN + size;
+	hdr.blob_len = 0;
+	uint8_t *message = malloc(hdr.len * sizeof(uint8_t));
+
+	if (!message) {
+		NT_LOG(ERR, NTCONNECT, "memory allocation failed");
+		return STATUS_INTERNAL_ERROR;
+	}
+	memcpy(message, (void *)&hdr, NTCMOD_HDR_LEN);
+	memcpy(&message[NTCMOD_HDR_LEN], data, size);
+	int res = write_all(clfd, message, hdr.len);
+
+	free(message);
+	if (res)
+		return res;
+
+	return 0;
+}
+
+static int send_reply_free_data(int clfd, ntconn_mod_t *cmod,
+				uint16_t reply_tag, void *data, uint32_t size)
+{
+	int res = send_reply(clfd, reply_tag, data, size);
+
+	if (size) {
+		pthread_mutex_lock(&cmod->mutex);
+		cmod->op->free_data(cmod->hdl, data);
+		pthread_mutex_unlock(&cmod->mutex);
+	}
+
+	return res;
+}
+
+static int ntconnect_send_error(int clfd, enum ntconn_err_e err_code)
+{
+	char err_buf[MAX_ERR_MESSAGE_LENGTH];
+	const ntconn_err_t *ntcerr = get_ntconn_error(err_code);
+
+	sprintf(err_buf, "----connect:%s", ntcerr->err_text);
+	unsigned int len = strlen(err_buf);
+	*(uint32_t *)err_buf = (uint32_t)ntcerr->err_code;
+
+	return send_reply(clfd, NTCONN_TAG_ERROR, err_buf, len);
+}
+
+static void *ntconnect_worker(void *arg)
+{
+	int status;
+	int clfd = (int)(uint64_t)arg;
+	char *module_cmd = NULL;
+	char *request = NULL;
+	struct ntconn_header_s hdr;
+
+	do {
+		ntconn_mod_t *cmod = ntconnect_interpret_request(clfd, &hdr,
+								 &request,
+								 &module_cmd,
+								 &status);
+
+		if (cmod && module_cmd && status == 0) {
+			int len;
+			char *data;
+
+			/*
+			 * Handle general module commands
+			 */
+			if (strcmp(module_cmd, "version") == 0) {
+				uint64_t version =
+					((uint64_t)cmod->op->version_major
+					 << 32) +
+					(cmod->op->version_minor);
+
+				if (send_reply(clfd, NTCONN_TAG_REPLY,
+						(void *)&version,
+						sizeof(uint64_t)))
+					break;
+
+			} else {
+				/*
+				 * Call module for execution of command
+				 */
+				data = NULL;
+				pthread_mutex_lock(&cmod->mutex);
+				int repl = cmod->op->request(cmod->hdl, clfd,
+							     &hdr, module_cmd,
+							     &data, &len);
+				pthread_mutex_unlock(&cmod->mutex);
+
+				if (repl == REQUEST_OK && len >= 0) {
+					if (send_reply_free_data(clfd, cmod,
+								 NTCONN_TAG_REPLY,
+								 (void *)data,
+								 (uint32_t)len))
+						break;
+
+				} else if (repl == REQUEST_ERR && len >= 0) {
+					if (send_reply_free_data(clfd, cmod,
+								 NTCONN_TAG_ERROR,
+								 (void *)data,
+								 (uint32_t)len))
+						break;
+				} else {
+					NT_LOG(ERR, NTCONNECT,
+					       "Invalid result from module request function: module %s, result %i\n",
+					       cmod->op->module, repl);
+					if (ntconnect_send_error(clfd,
+						NTCONN_ERR_CODE_INTERNAL_REPLY_ERROR))
+						break;
+				}
+			}
+
+		} else if (status == STATUS_TIMEOUT) {
+			/* Other end is dead */
+			NT_LOG(WRN, NTCONNECT,
+			       "Client must be dead - timeout\n");
+			break;
+		} else if (status == STATUS_CONNECTION_CLOSED) {
+			break; /* silently break out */
+		}
+		/* Error - send error back */
+		if (ntconnect_send_error(clfd, NTCONN_ERR_CODE_INVALID_REQUEST))
+			break;
+		if (request)
+			free(request);
+	} while (1); /* while still connected */
+
+	close(clfd);
+
+	/* call module cleanup callback function for client_id */
+	ntconn_mod_t *ntcmod = ntcmod_base;
+
+	while (ntcmod) {
+		if (ntcmod->op->client_cleanup) {
+			pthread_mutex_lock(&ntcmod->mutex);
+			ntcmod->op->client_cleanup(ntcmod->hdl, clfd);
+			pthread_mutex_unlock(&ntcmod->mutex);
+		}
+
+		ntcmod = ntcmod->next;
+	}
+	pthread_exit(NULL);
+	return NULL;
+}
+
+static void *ntconnect_server(void *arg)
+{
+	struct ntconn_server_s *ntcserv = (struct ntconn_server_s *)arg;
+
+	ntcserv->running = 1;
+
+#ifdef DEBUG
+	NT_LOG(DBG, NTCONNECT, "Running NT Connection Server fd %i\n",
+	       ntcserv->serv_fd);
+#endif
+
+	if (listen(ntcserv->serv_fd, 5) < 0) {
+		NT_LOG(ERR, NTCONNECT,
+		       "Server failed on listen(), stopping thread. err: %s\n",
+		       strerror(errno));
+		pthread_exit(NULL);
+		return NULL;
+	}
+
+	while (ntcserv->running) {
+		int clfd = accept(ntcserv->serv_fd, NULL, NULL);
+
+		if (clfd < 0) {
+			NT_LOG(ERR, NTCONNECT,
+			       "ERROR from accept(), stopping thread. err: %s\n",
+			       strerror(errno));
+			break;
+		}
+		pthread_create(&ctid, NULL, ntconnect_worker,
+			       (void *)(uint64_t)clfd);
+		pthread_setaffinity_np(ctid, sizeof(cpu_set_t),
+				       &ntcserv->cpuset);
+		/* Detach immediately. We will never join this thread */
+		pthread_detach(ctid);
+	}
+
+	pthread_exit(NULL);
+	return NULL;
+}
+
+int ntconnect_init(const char *sockname, cpu_set_t cpuset)
+{
+	if (ntcmod_base) {
+		/* Make sure the socket directory exists */
+		char *sockname_copy = strdup(sockname);
+		char *sockname_dir = dirname(sockname_copy);
+
+		if (mkdir(sockname_dir, 0755) < 0 && errno != EEXIST) {
+			NT_LOG(ERR, NTCONNECT,
+			       "Can't create socket directory: %s",
+			       sockname_dir);
+			free(sockname_copy);
+			return -1;
+		}
+		free(sockname_copy);
+
+		/* Add server to module list - cannot work without */
+		ntconn_server_register(&ntconn_serv);
+
+		/* Start named socket server */
+		struct sockaddr_un addr;
+
+		unix_build_address(sockname, &addr);
+
+		ntconn_serv.serv_fd = socket(AF_UNIX, SOCK_STREAM, 0);
+		ntconn_serv.cpuset = cpuset;
+		if (ntconn_serv.serv_fd == -1)
+			return -1;
+
+		/* Make sure the node in filesystem is deleted otherwise bind will fail */
+		unlink(sockname);
+
+		if (bind(ntconn_serv.serv_fd, (struct sockaddr *)&addr,
+				sizeof(struct sockaddr_un)) == -1) {
+			close(ntconn_serv.serv_fd);
+			return -1;
+		}
+
+		/* Run ntconnect service */
+		pthread_create(&tid, NULL, ntconnect_server, &ntconn_serv);
+		pthread_setaffinity_np(tid, sizeof(cpu_set_t),
+				       &ntconn_serv.cpuset);
+	}
+
+	return 0;
+}
diff --git a/drivers/net/ntnic/ntconnect_modules/ntconn_adapter.c b/drivers/net/ntnic/ntconnect_modules/ntconn_adapter.c
new file mode 100644
index 0000000000..60753d6ef5
--- /dev/null
+++ b/drivers/net/ntnic/ntconnect_modules/ntconn_adapter.c
@@ -0,0 +1,775 @@ 
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#include "ntnic_ethdev.h"
+#include "ntconnect.h"
+#include "ntconnect_api_adapter.h"
+#include "ntos_system.h"
+#include "ntconn_modules.h"
+#include "ntconn_mod_helper.h"
+#include "nt_util.h"
+#include "ntlog.h"
+
+#define NTCONN_ADAP_VERSION_MAJOR 0U
+#define NTCONN_ADAP_VERSION_MINOR 1U
+
+#define this_module_name "adapter"
+
+#define MAX_ADAPTERS 2
+
+static struct adap_hdl_s {
+	struct drv_s *drv;
+} adap_hdl[MAX_ADAPTERS];
+
+static int func_adapter_get_interfaces(void *hdl, int client_id,
+				       struct ntconn_header_s *hdr, char **data,
+				       int *len);
+static int func_adapter_get_info(void *hdl, int client_id,
+				 struct ntconn_header_s *hdr, char **data,
+				 int *len);
+static int func_adapter_get_sensors(void *hdl, int client_id _unused,
+				    struct ntconn_header_s *hdr _unused,
+				    char **data, int *len);
+static struct func_s funcs_get_level1[] = {
+	{ "interfaces", NULL, func_adapter_get_interfaces },
+	{ "info", NULL, func_adapter_get_info },
+	{ "sensors", NULL, func_adapter_get_sensors },
+	{ NULL, NULL, NULL },
+};
+
+static int func_adapter_set_interface(void *hdl, int client_id,
+				      struct ntconn_header_s *hdr, char **data,
+				      int *len);
+static int func_adapter_set_adapter(void *hdl, int client_id,
+				    struct ntconn_header_s *hdr, char **data,
+				    int *len);
+static struct func_s funcs_set_level1[] = {
+	{ "interface", NULL, func_adapter_set_interface },
+	{ "adapter", NULL, func_adapter_set_adapter },
+	{ NULL, NULL, NULL },
+};
+
+/*
+ * Entry level
+ */
+static struct func_s adapter_entry_funcs[] = {
+	{ "get", funcs_get_level1, NULL },
+	{ "set", funcs_set_level1, NULL },
+	{ NULL, NULL, NULL },
+};
+
+static int read_link_speed(enum nt_link_speed_e link_speed)
+{
+	switch (link_speed) {
+	case NT_LINK_SPEED_10M:
+		return PORT_LINK_SPEED_10M;
+	case NT_LINK_SPEED_100M:
+		return PORT_LINK_SPEED_100M;
+	case NT_LINK_SPEED_1G:
+		return PORT_LINK_SPEED_1G;
+	case NT_LINK_SPEED_10G:
+		return PORT_LINK_SPEED_10G;
+	case NT_LINK_SPEED_25G:
+		return PORT_LINK_SPEED_25G;
+	case NT_LINK_SPEED_40G:
+		return PORT_LINK_SPEED_40G;
+	case NT_LINK_SPEED_50G:
+		return PORT_LINK_SPEED_50G;
+	case NT_LINK_SPEED_100G:
+		return PORT_LINK_SPEED_100G;
+	default:
+		break;
+	}
+	return PORT_LINK_SPEED_UNKNOWN;
+}
+
+static nt_link_speed_t convert_link_speed(char *speed_str)
+{
+	if (strcmp(speed_str, "10M") == 0)
+		return NT_LINK_SPEED_10M;
+	else if (strcmp(speed_str, "100M") == 0)
+		return NT_LINK_SPEED_100M;
+	else if (strcmp(speed_str, "1G") == 0)
+		return NT_LINK_SPEED_1G;
+	else if (strcmp(speed_str, "10G") == 0)
+		return NT_LINK_SPEED_10G;
+	else if (strcmp(speed_str, "25G") == 0)
+		return NT_LINK_SPEED_25G;
+	else if (strcmp(speed_str, "40G") == 0)
+		return NT_LINK_SPEED_40G;
+	else if (strcmp(speed_str, "50G") == 0)
+		return NT_LINK_SPEED_50G;
+	else if (strcmp(speed_str, "100G") == 0)
+		return NT_LINK_SPEED_100G;
+	else
+		return NT_LINK_SPEED_UNKNOWN;
+}
+
+static int func_adapter_get_interfaces(void *hdl, int client_id _unused,
+				       struct ntconn_header_s *hdr _unused,
+				       char **data, int *len)
+{
+	struct ntc_interfaces_s *ifs;
+	struct adap_hdl_s *adap = (struct adap_hdl_s *)hdl;
+	fpga_info_t *fpga_info = &adap->drv->ntdrv.adapter_info.fpga_info;
+	int lag_active;
+	int final_list = adap->drv->probe_finished;
+	/* keep final_list set before nb_ports are called */
+	rte_compiler_barrier();
+	int nb_ports = rte_eth_dev_count_avail();
+
+	/* Get the "internals" structure of phy port 0 to find out if we're running LAG */
+	char phy0_name[128];
+
+	rte_eth_dev_get_name_by_port(0, phy0_name);
+	struct rte_eth_dev *phy0_eth_dev = rte_eth_dev_get_by_name(phy0_name);
+
+	if (phy0_eth_dev == NULL || phy0_eth_dev->data == NULL ||
+			phy0_eth_dev->data->dev_private == NULL) {
+		return ntconn_error(data, len, this_module_name,
+				    NTCONN_ERR_CODE_INTERNAL_ERROR);
+	}
+	struct pmd_internals *phy0_internals =
+		(struct pmd_internals *)phy0_eth_dev->data->dev_private;
+	lag_active = (phy0_internals->lag_config == NULL) ? 0 : 1;
+	if (lag_active) {
+		/*
+		 * Phy ports are link aggregated. I.e. number of ports is actually
+		 * one bigger than what rte_eth_dev_count_avail() returned
+		 */
+		nb_ports++;
+
+		/*
+		 * Sanity check:
+		 * For now we know about LAG with 2 ports only.
+		 * If in the future we get HW with more ports, make assert to alert
+		 * the developers that something needs to be looked at...
+		 */
+		assert(fpga_info->n_phy_ports == 2);
+	}
+
+	*len = sizeof(struct ntc_interfaces_s) +
+	       sizeof(struct ntc_interface_s) * nb_ports;
+	ifs = malloc(*len);
+	if (!ifs) {
+		*len = 0;
+		NT_LOG(ERR, NTCONNECT, "memory allocation failed");
+		return REQUEST_ERR;
+	}
+	*data = (char *)ifs;
+
+	ifs->nb_ports = nb_ports;
+	ifs->final_list = final_list;
+
+	int i;
+
+	/* First set the "port type" of the physical ports */
+	if (lag_active) {
+		if (phy0_internals->lag_config->mode == BONDING_MODE_8023AD) {
+			/* Active/active LAG */
+			for (i = 0; i < fpga_info->n_phy_ports; i++) {
+				ifs->intf[i].type =
+					PORT_TYPE_PHY_LAG_ACTIVE_AVTIVE;
+			}
+		} else if (phy0_internals->lag_config->mode ==
+				BONDING_MODE_ACTIVE_BACKUP) {
+			/* Active/backup LAG */
+			ifs->intf[phy0_internals->lag_config->primary_port]
+			.type = PORT_TYPE_PHY_LAG_PRIMARY;
+			ifs->intf[phy0_internals->lag_config->backup_port].type =
+				PORT_TYPE_PHY_LAG_BACKUP;
+		} else {
+			/* Unknown LAG mode */
+			assert(0);
+		}
+	} else {
+		/* Normal phy ports (not link aggregated) */
+		for (i = 0; i < fpga_info->n_phy_ports; i++)
+			ifs->intf[i].type = PORT_TYPE_PHY_NORMAL;
+	}
+
+	/* Then set the remaining port values for the physical ports. */
+	for (i = 0; i < fpga_info->n_phy_ports; i++) {
+		char name[128];
+
+		if (i > 0 && lag_active) {
+			/*
+			 * Secondary link aggregated port. Just display the "internals" values
+			 * from port 0
+			 */
+			rte_eth_dev_get_name_by_port(0, name);
+		} else {
+			rte_eth_dev_get_name_by_port(i, name);
+		}
+		struct rte_eth_dev *eth_dev = rte_eth_dev_get_by_name(name);
+
+		struct pmd_internals *internals =
+			(struct pmd_internals *)eth_dev->data->dev_private;
+		struct adapter_info_s *p_adapter_info =
+				&adap->drv->ntdrv.adapter_info;
+
+		ifs->intf[i].port_id = i;
+		ifs->intf[i].pci_id.domain = internals->pci_dev->addr.domain;
+		ifs->intf[i].pci_id.bus = internals->pci_dev->addr.bus;
+		ifs->intf[i].pci_id.devid = internals->pci_dev->addr.devid;
+		ifs->intf[i].pci_id.function =
+			internals->pci_dev->addr.function;
+		ifs->intf[i].pci_id.pad = 0;
+
+		const bool port_link_status =
+			nt4ga_port_get_link_status(p_adapter_info, i);
+		ifs->intf[i].link = port_link_status ? PORT_LINK_UP :
+				    PORT_LINK_DOWN;
+
+		const nt_link_speed_t port_link_speed =
+			nt4ga_port_get_link_speed(p_adapter_info, i);
+		ifs->intf[i].port_speed = read_link_speed(port_link_speed);
+
+		const bool port_adm_state =
+			nt4ga_port_get_adm_state(p_adapter_info, i);
+		if (!port_adm_state) {
+			ifs->intf[i].port_state = PORT_STATE_DISABLED;
+		} else {
+			const bool port_nim_present =
+				nt4ga_port_get_nim_present(p_adapter_info, i);
+			if (port_nim_present) {
+				ifs->intf[i].port_state =
+					PORT_STATE_NIM_PRESENT;
+			} else {
+				ifs->intf[i].port_state = PORT_STATE_NIM_ABSENT;
+			}
+		}
+
+		/* MTU */
+		if (i > 0 && lag_active) {
+			/* Secondary link aggregated port. Display same MTU value as port 0 */
+			rte_eth_dev_get_mtu(0, &ifs->intf[i].mtu);
+		} else {
+			rte_eth_dev_get_mtu(i, &ifs->intf[i].mtu);
+		}
+
+		/* MAC */
+		const uint64_t mac =
+			fpga_info->nthw_hw_info.vpd_info.mn_mac_addr_value + i;
+		ifs->intf[i].mac.addr_b[0] = (mac >> 40) & 0xFFu;
+		ifs->intf[i].mac.addr_b[1] = (mac >> 32) & 0xFFu;
+		ifs->intf[i].mac.addr_b[2] = (mac >> 24) & 0xFFu;
+		ifs->intf[i].mac.addr_b[3] = (mac >> 16) & 0xFFu;
+		ifs->intf[i].mac.addr_b[4] = (mac >> 8) & 0xFFu;
+		ifs->intf[i].mac.addr_b[5] = (mac >> 0) & 0xFFu;
+
+		if (i > 0 && lag_active) {
+			/* Secondary link aggregated port. Queues not applicable */
+			ifs->intf[i].num_queues = 0;
+		} else {
+			/* attached hw queues to this interface */
+			unsigned int input_num = internals->nb_rx_queues;
+			/*
+			 * These are the "input" queues, meaning these go to host and is attached
+			 * to receiving from a port
+			 */
+			for (unsigned int ii = 0; ii < input_num; ii++) {
+				ifs->intf[i].queue[ii].idx =
+					internals->rxq_scg[ii].queue.hw_id;
+				ifs->intf[i].queue[ii].dir = QUEUE_INPUT;
+			}
+
+			/*
+			 * These are the "output" queues, meaning these go to a virtual port queue
+			 * which typically is used by vDPA
+			 */
+			for (unsigned int ii = 0; ii < internals->vpq_nb_vq;
+					ii++) {
+				ifs->intf[i].queue[ii + input_num].idx =
+					internals->vpq[ii].hw_id;
+				ifs->intf[i].queue[ii + input_num].dir =
+					QUEUE_OUTPUT;
+			}
+
+			ifs->intf[i].num_queues =
+				input_num + internals->vpq_nb_vq;
+		}
+
+		/* NIM information */
+		nim_i2c_ctx_t nim_ctx =
+			nt4ga_port_get_nim_capabilities(p_adapter_info, i);
+
+		strlcpy((char *)&ifs->intf[i].nim_data.vendor_name,
+			nim_ctx.vendor_name,
+			sizeof(ifs->intf[i].nim_data.vendor_name));
+		strlcpy((char *)&ifs->intf[i].nim_data.prod_no, nim_ctx.prod_no,
+			sizeof(ifs->intf[i].nim_data.prod_no));
+		strlcpy((char *)&ifs->intf[i].nim_data.serial_no,
+			nim_ctx.serial_no,
+			sizeof(ifs->intf[i].nim_data.serial_no));
+		strlcpy((char *)&ifs->intf[i].nim_data.date, nim_ctx.date,
+			sizeof(ifs->intf[i].nim_data.date));
+		strlcpy((char *)&ifs->intf[i].nim_data.rev, nim_ctx.rev,
+			sizeof(ifs->intf[i].nim_data.rev));
+
+		if (nim_ctx.len_info[0] >= 0xFFFF)
+			ifs->intf[i].nim_data.link_length.sm = 0xFFFF;
+		else
+			ifs->intf[i].nim_data.link_length.sm =
+				nim_ctx.len_info[0];
+
+		ifs->intf[i].nim_data.link_length.ebw = nim_ctx.len_info[1];
+		ifs->intf[i].nim_data.link_length.mm50 = nim_ctx.len_info[2];
+		ifs->intf[i].nim_data.link_length.mm62 = nim_ctx.len_info[3];
+		ifs->intf[i].nim_data.link_length.copper = nim_ctx.len_info[4];
+
+		ifs->intf[i].nim_data.pwr_level_req = nim_ctx.pwr_level_req;
+		ifs->intf[i].nim_data.pwr_level_cur = nim_ctx.pwr_level_cur;
+		ifs->intf[i].nim_data.nim_id = nim_ctx.nim_id;
+		ifs->intf[i].nim_data.port_type = nim_ctx.port_type;
+	}
+
+	/* And finally handle the virtual ports. */
+	int rte_eth_dev_virt_port_offset = lag_active ? 1 :
+					   fpga_info->n_phy_ports;
+	for (; i < nb_ports; i++, rte_eth_dev_virt_port_offset++) {
+		/* Continue counting from the "i" value reached in the previous for loop */
+		char name[128];
+
+		rte_eth_dev_get_name_by_port(rte_eth_dev_virt_port_offset,
+					     name);
+		struct rte_eth_dev *eth_dev = rte_eth_dev_get_by_name(name);
+
+		struct pmd_internals *internals =
+			(struct pmd_internals *)eth_dev->data->dev_private;
+
+		ifs->intf[i].port_id = i;
+		ifs->intf[i].type = PORT_TYPE_VIRT;
+		ifs->intf[i].pci_id.domain = internals->pci_dev->addr.domain;
+		ifs->intf[i].pci_id.bus = internals->pci_dev->addr.bus;
+		ifs->intf[i].pci_id.devid = internals->pci_dev->addr.devid;
+		ifs->intf[i].pci_id.function =
+			internals->pci_dev->addr.function;
+		ifs->intf[i].pci_id.pad = 0;
+
+		ifs->intf[i].port_speed = PORT_LINK_SPEED_NONE_REPORTED;
+		switch (internals->vport_comm) {
+		case VIRT_PORT_NEGOTIATED_NONE:
+			ifs->intf[i].port_state = PORT_STATE_VIRTUAL_UNATTACHED;
+			ifs->intf[i].link = PORT_LINK_DOWN;
+			break;
+		case VIRT_PORT_NEGOTIATED_SPLIT:
+			ifs->intf[i].port_state = PORT_STATE_VIRTUAL_SPLIT;
+			ifs->intf[i].link = PORT_LINK_UP;
+			break;
+		case VIRT_PORT_NEGOTIATED_PACKED:
+			ifs->intf[i].port_state = PORT_STATE_VIRTUAL_PACKED;
+			ifs->intf[i].link = PORT_LINK_UP;
+			break;
+		case VIRT_PORT_USE_RELAY:
+			ifs->intf[i].port_state = PORT_STATE_VIRTUAL_RELAY;
+			ifs->intf[i].link = PORT_LINK_UP;
+			break;
+		}
+
+		/* MTU */
+		rte_eth_dev_get_mtu(rte_eth_dev_virt_port_offset,
+				    &ifs->intf[i].mtu);
+
+		/* MAC */
+		for (int ii = 0; ii < 6; ii++) {
+			ifs->intf[i].mac.addr_b[ii] =
+				internals->eth_addrs[0].addr_bytes[ii];
+		}
+
+		/* attached hw queues to this interface */
+		unsigned int input_num = internals->nb_rx_queues;
+
+		/*
+		 * These are the "input" queues, meaning these go to host and is attached to
+		 * receiving from a port
+		 */
+		for (unsigned int ii = 0; ii < input_num; ii++) {
+			ifs->intf[i].queue[ii].idx =
+				internals->rxq_scg[ii].queue.hw_id;
+			ifs->intf[i].queue[ii].dir = QUEUE_INPUT;
+		}
+
+		/*
+		 * These are the "output" queues, meaning these go to a virtual port queue
+		 * which typically is used by vDPA
+		 */
+		unsigned int numq =
+			((internals->vpq_nb_vq + input_num) > MAX_RSS_QUEUES) ?
+			MAX_RSS_QUEUES - input_num :
+			internals->vpq_nb_vq;
+		for (unsigned int ii = 0; ii < numq; ii++) {
+			ifs->intf[i].queue[ii + input_num].idx =
+				internals->vpq[ii].hw_id;
+			ifs->intf[i].queue[ii + input_num].dir = QUEUE_OUTPUT;
+		}
+		ifs->intf[i].num_queues = input_num + numq;
+	}
+	return REQUEST_OK;
+}
+
+static int func_adapter_get_info(void *hdl, int client_id _unused,
+				 struct ntconn_header_s *hdr _unused,
+				 char **data, int *len)
+{
+	struct adap_hdl_s *adap = (struct adap_hdl_s *)hdl;
+	fpga_info_t *fpga_info = &adap->drv->ntdrv.adapter_info.fpga_info;
+
+	*len = sizeof(struct ntc_adap_get_info_s);
+	*data = malloc(*len);
+	if (!*data) {
+		*len = 0;
+		NT_LOG(ERR, NTCONNECT, "memory allocation failed");
+		return REQUEST_ERR;
+	}
+
+	snprintf(*data, 31, "%03d-%04d-%02d-%02d", fpga_info->n_fpga_type_id,
+		 fpga_info->n_fpga_prod_id, fpga_info->n_fpga_ver_id,
+		 fpga_info->n_fpga_rev_id);
+
+	return REQUEST_OK;
+}
+
+static int func_adapter_get_sensors(void *hdl, int client_id _unused,
+				    struct ntconn_header_s *hdr _unused,
+				    char **data, int *len)
+{
+	struct adapter_info_s *adapter =
+		&(((struct adap_hdl_s *)hdl)->drv->ntdrv.adapter_info);
+	struct sensor *sensor_ptr = NULL;
+	uint16_t sensors_num = 0;
+	uint8_t *sensors = NULL;
+	struct ntc_sensors_s sensors_info = {
+		.adapter_sensors_cnt = adapter->adapter_sensors_cnt,
+		.ports_cnt = adapter->fpga_info.n_phy_ports
+	};
+	memcpy(sensors_info.adapter_name, adapter->p_dev_name, 24);
+
+	/* Set a sum of sensor`s counters */
+	sensors_num = adapter->adapter_sensors_cnt;
+	for (int i = 0; i < adapter->fpga_info.n_phy_ports; i++) {
+		sensors_num += adapter->nim_sensors_cnt[i];
+		sensors_info.nim_sensors_cnt[i] = adapter->nim_sensors_cnt[i];
+	}
+
+	*len = sizeof(struct ntc_sensors_s) +
+	       sensors_num * sizeof(struct sensor);
+
+	/* Allocate memory for sensors array */
+	sensors = malloc(*len);
+	if (!sensors) {
+		NT_LOG(ERR, NTCONNECT, "memory allocation failed");
+		*len = 0;
+		return REQUEST_ERR;
+	}
+	memcpy(sensors, &sensors_info, sizeof(struct ntc_sensors_s));
+	sensor_ptr = (struct sensor *)(sensors + sizeof(struct ntc_sensors_s));
+
+	/* Fetch adapter sensors */
+	for (struct nt_sensor_group *ptr = adapter->adapter_sensors;
+			ptr != NULL; ptr = ptr->next) {
+		sensor_ptr->current_value = ptr->sensor->info.value;
+		sensor_ptr->min_value = ptr->sensor->info.value_lowest;
+		sensor_ptr->max_value = ptr->sensor->info.value_highest;
+		sensor_ptr->sign = ptr->sensor->si;
+		sensor_ptr->type = ptr->sensor->info.type;
+		memcpy(sensor_ptr->name, ptr->sensor->info.name, 50);
+		sensor_ptr++;
+	}
+
+	/* Fetch NIM sensors */
+	for (int i = 0; i < adapter->fpga_info.n_phy_ports; i++) {
+		for (struct nim_sensor_group *ptr = adapter->nim_sensors[i];
+				ptr != NULL; ptr = ptr->next) {
+			sensor_ptr->current_value = ptr->sensor->info.value;
+			sensor_ptr->min_value = ptr->sensor->info.value_lowest;
+			sensor_ptr->max_value = ptr->sensor->info.value_highest;
+			sensor_ptr->sign = ptr->sensor->si;
+			sensor_ptr->type = ptr->sensor->info.type;
+
+			memcpy(sensor_ptr->name, ptr->sensor->info.name,
+			       (strlen(ptr->sensor->info.name) >= 50) ?
+			       50 :
+			       strlen(ptr->sensor->info.name));
+			sensor_ptr++;
+		}
+	}
+
+	/* Send response */
+	 *data = (char *)sensors;
+
+	return REQUEST_OK;
+}
+
+static int set_port_enable(struct adap_hdl_s *adap, int port_nr)
+{
+	adapter_info_t *p_adapter_info = &adap->drv->ntdrv.adapter_info;
+
+	nt4ga_port_set_adm_state(p_adapter_info, port_nr, true);
+
+	return REQUEST_OK;
+}
+
+static int set_port_disable(struct adap_hdl_s *adap, int port_nr)
+{
+	adapter_info_t *p_adapter_info = &adap->drv->ntdrv.adapter_info;
+
+	nt4ga_port_set_adm_state(p_adapter_info, port_nr, false);
+
+	return REQUEST_OK;
+}
+
+static int set_link_up(struct adap_hdl_s *adap, int portid)
+{
+	struct adapter_info_s *p_adapter_info = &adap->drv->ntdrv.adapter_info;
+
+	const bool link_status =
+		nt4ga_port_get_link_status(p_adapter_info, portid);
+
+	if (!link_status) {
+		nt4ga_port_set_link_status(p_adapter_info, portid, true);
+		NT_LOG(DBG, NTCONNECT, "Port %i: Link set to be up\n", portid);
+	} else {
+		NT_LOG(DBG, NTCONNECT,
+		       "Port %i: Link is already set to be up\n", portid);
+	}
+
+	return REQUEST_OK;
+}
+
+static int set_link_down(struct adap_hdl_s *adap, int portid)
+{
+	struct adapter_info_s *p_adapter_info = &adap->drv->ntdrv.adapter_info;
+
+	const bool link_status =
+		nt4ga_port_get_link_status(p_adapter_info, portid);
+
+	if (!link_status) {
+		NT_LOG(DBG, NTCONNECT,
+		       "Port %i: Link is already set to be down\n", portid);
+	} else {
+		nt4ga_port_set_link_status(p_adapter_info, portid, false);
+		NT_LOG(DBG, NTCONNECT, "Port %i: Link set to be down\n",
+		       portid);
+	}
+
+	return REQUEST_OK;
+}
+
+static int set_link_speed(struct adap_hdl_s *adap, int portid, char *speed_str,
+			  char **data, int *len)
+{
+	struct adapter_info_s *p_adapter_info = &adap->drv->ntdrv.adapter_info;
+
+	const bool port_adm_state =
+		nt4ga_port_get_adm_state(p_adapter_info, portid);
+	if (!port_adm_state) {
+		const nt_link_speed_t speed = convert_link_speed(speed_str);
+
+		if (speed != NT_LINK_SPEED_UNKNOWN) {
+			nt4ga_port_set_link_speed(p_adapter_info, portid, speed);
+			NT_LOG(DBG, NTCONNECT, "Port %i: set link speed - %s\n",
+			       portid, speed_str);
+		} else {
+			return ntconn_error(data, len, this_module_name,
+					    NTCONN_ERR_CODE_MISSING_INVALID_PARAM);
+		}
+	} else {
+		NT_LOG(DBG, NTCONNECT,
+		       "Port %i: fail to set link speed, port is enabled\n",
+		       portid);
+		return ntconn_reply_status(data, len,
+					   NTCONN_ADAPTER_ERR_WRONG_LINK_STATE);
+	}
+
+	return REQUEST_OK;
+}
+
+static int set_loopback_mode(struct adap_hdl_s *adap, int portid, int mode)
+{
+	struct adapter_info_s *p_adapter_info = &adap->drv->ntdrv.adapter_info;
+
+	NT_LOG(DBG, NTCONNECT, "Port %i: set loopback mode %i\n", portid, mode);
+	nt4ga_port_set_loopback_mode(p_adapter_info, portid, mode);
+	return REQUEST_OK;
+}
+
+static int set_tx_power(struct adap_hdl_s *adap, int portid, bool disable,
+			char **data, int *len)
+{
+	struct adapter_info_s *p_adapter_info = &adap->drv->ntdrv.adapter_info;
+
+	NT_LOG(DBG, NTCONNECT, "Port %i: set tx_power %i\n", portid, disable);
+	if (nt4ga_port_tx_power(p_adapter_info, portid, disable)) {
+		NT_LOG(DBG, NTCONNECT,
+		       "Port %i: ERROR while changing tx_power\n", portid);
+		return ntconn_reply_status(data, len,
+					   NTCONN_ADAPTER_ERR_TX_POWER_FAIL);
+	}
+	return REQUEST_OK;
+}
+
+static int func_adapter_set_interface(void *hdl, int client_id _unused,
+				      struct ntconn_header_s *hdr _unused,
+				      char **data, int *len)
+{
+	struct adap_hdl_s *adap = (struct adap_hdl_s *)hdl;
+	char *saveptr;
+	int port_nr;
+	int length;
+	char *tok;
+
+	*len = 0;
+
+	/*
+	 * This will receive the request strings starting with "adapter;set,interface,...."
+	 * so in the situation of a request like: "adapter,set,interface,port0,link_speed=10G"
+	 * the remainder of the command "port0,link_speed=10G" will be pointed to by *data,
+	 * zero-terminated on entry
+	 */
+
+	if (!(data && *data))
+		return ntconn_error(data, len, this_module_name,
+				    NTCONN_ERR_CODE_INVALID_REQUEST);
+
+	/* OK to modify *data */
+	tok = strtok_r(*data, ",", &saveptr);
+	if (!tok)
+		return ntconn_error(data, len, this_module_name,
+				    NTCONN_ERR_CODE_MISSING_INVALID_PARAM);
+
+	length = strlen(tok);
+
+	if (!(length > 4 && memcmp(tok, "port", 4) == 0))
+		return ntconn_error(data, len, this_module_name,
+				    NTCONN_ERR_CODE_MISSING_INVALID_PARAM);
+
+	port_nr = atoi(tok + 4);
+
+	/* Only set on phy ports */
+	if (port_nr < adap->drv->ntdrv.adapter_info.fpga_info.n_phy_ports)
+		return ntconn_error(data, len, this_module_name,
+				    NTCONN_ERR_CODE_MISSING_INVALID_PARAM);
+
+	tok = strtok_r(NULL, "=,", &saveptr);
+	if (!tok)
+		return ntconn_error(data, len, this_module_name,
+			NTCONN_ERR_CODE_MISSING_INVALID_PARAM);
+	if (strcmp(tok, "link_speed") == 0) {
+		tok = strtok_r(NULL, ",", &saveptr);
+		if (!tok)
+			return ntconn_error(data, len, this_module_name,
+				NTCONN_ERR_CODE_MISSING_INVALID_PARAM);
+		return set_link_speed(adap, port_nr, tok, data, len);
+	} else if (strcmp(tok, "enable") == 0) {
+		return set_port_enable(adap, port_nr);
+	} else if (strcmp(tok, "disable") == 0) {
+		return set_port_disable(adap, port_nr);
+	} else if (strcmp(tok, "link_state") == 0) {
+		tok = strtok_r(NULL, ",", &saveptr);
+		if (!tok)
+			return ntconn_error(data, len, this_module_name,
+				NTCONN_ERR_CODE_MISSING_INVALID_PARAM);
+		if (strcmp(tok, "up") == 0)
+			return set_link_up(adap, port_nr);
+		else if (strcmp(tok, "down") == 0)
+			return set_link_down(adap, port_nr);
+	} else if (strcmp(tok, "host_loopback") == 0) {
+		tok = strtok_r(NULL, ",", &saveptr);
+		if (!tok)
+			return ntconn_error(data, len, this_module_name,
+				NTCONN_ERR_CODE_MISSING_INVALID_PARAM);
+		if (strcmp(tok, "on") == 0)
+			return set_loopback_mode(adap, port_nr,
+				NT_LINK_LOOPBACK_HOST);
+		else if (strcmp(tok, "off") == 0)
+			return set_loopback_mode(adap, port_nr,
+				NT_LINK_LOOPBACK_OFF);
+	} else if (strcmp(tok, "line_loopback") == 0) {
+		tok = strtok_r(NULL, ",", &saveptr);
+		if (!tok)
+			return ntconn_error(data, len, this_module_name,
+				NTCONN_ERR_CODE_MISSING_INVALID_PARAM);
+		if (strcmp(tok, "on") == 0)
+			return set_loopback_mode(adap, port_nr,
+				NT_LINK_LOOPBACK_LINE);
+		else if (strcmp(tok, "off") == 0)
+			return set_loopback_mode(adap, port_nr,
+				NT_LINK_LOOPBACK_OFF);
+	} else if (strcmp(tok, "tx_power") == 0) {
+		tok = strtok_r(NULL, ",", &saveptr);
+		if (!tok)
+			return ntconn_error(data, len, this_module_name,
+				NTCONN_ERR_CODE_MISSING_INVALID_PARAM);
+		if (strcmp(tok, "on") == 0)
+			return set_tx_power(adap, port_nr, false, data, len);
+		else if (strcmp(tok, "off") == 0)
+			return set_tx_power(adap, port_nr, true, data, len);
+	}
+
+	/* Should return 0 on success */
+	return ntconn_error(data, len, this_module_name,
+			    NTCONN_ERR_CODE_MISSING_INVALID_PARAM);
+}
+
+static int func_adapter_set_adapter(void *hdl _unused, int client_id _unused,
+				    struct ntconn_header_s *hdr _unused,
+				    char **data, int *len)
+{
+	if (data && *data) {
+		NT_LOG(DBG, NTCONNECT,
+		       "Set adapter: Command: %s\n", *data);
+	}
+
+	*len = 0;
+
+	/* Should return 0 on success */
+	return ntconn_error(data, len, this_module_name,
+			    NTCONN_ERR_CODE_NOT_YET_IMPLEMENTED);
+}
+
+static int adap_request(void *hdl, int client_id _unused,
+			struct ntconn_header_s *hdr, char *function,
+			char **data, int *len)
+{
+	return execute_function(this_module_name, hdl, client_id, hdr, function,
+				adapter_entry_funcs, data, len, 0);
+}
+
+static void adap_free_data(void *hdl _unused, char *data)
+{
+	free(data);
+}
+
+static void adap_client_cleanup(void *hdl _unused, int client_id _unused)
+{
+	/* Nothing to do */
+}
+
+static const ntconnapi_t ntconn_adap_op = { this_module_name,
+					    NTCONN_ADAP_VERSION_MAJOR,
+					    NTCONN_ADAP_VERSION_MINOR,
+					    adap_request,
+					    adap_free_data,
+					    adap_client_cleanup
+					  };
+
+int ntconn_adap_register(struct drv_s *drv)
+{
+	int i;
+
+	for (i = 0; i < MAX_ADAPTERS; i++) {
+		if (adap_hdl[i].drv == NULL)
+			break;
+	}
+	if (i == MAX_ADAPTERS) {
+		NT_LOG(ERR, NTCONNECT,
+		       "Cannot register more adapters into NtConnect framework");
+		return -1;
+	}
+
+	adap_hdl[i].drv = drv;
+	return register_ntconn_mod(&drv->p_dev->addr, (void *)&adap_hdl[i],
+				   &ntconn_adap_op);
+}
diff --git a/drivers/net/ntnic/ntconnect_modules/ntconn_flow.c b/drivers/net/ntnic/ntconnect_modules/ntconn_flow.c
new file mode 100644
index 0000000000..31d5dc3edc
--- /dev/null
+++ b/drivers/net/ntnic/ntconnect_modules/ntconn_flow.c
@@ -0,0 +1,1310 @@ 
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#include <errno.h>
+#include "ntnic_ethdev.h"
+#include "ntconnect.h"
+#include "ntos_system.h"
+#include "ntconn_modules.h"
+#include "ntconn_mod_helper.h"
+#include "nt_util.h"
+#include "ntlog.h"
+#include "ntnic_vf_vdpa.h"
+
+#include "ntconnect_api_flow.h"
+#include "ntconnect_api_meter.h"
+#include "stream_binary_flow_api.h"
+
+#include <rte_errno.h>
+#include "flow_api.h"
+
+#define DEBUG_FLOW 1
+
+#define NTCONN_FLOW_VERSION_MAJOR 0U
+#define NTCONN_FLOW_VERSION_MINOR 1U
+
+#define this_module_name "filter"
+
+#define MAX_CLIENTS 32
+
+#define UNUSED __rte_unused
+
+static struct flow_hdl_s {
+	struct drv_s *drv;
+} flow_hdl[MAX_CLIENTS];
+
+#define MAX_PORTS 64
+static struct port_to_eth_s {
+	struct flow_eth_dev *flw_dev;
+	uint32_t forced_vlan_vid;
+	uint32_t caller_id;
+} port_eth[MAX_PORTS];
+
+static ntconn_err_t ntconn_err[] = {
+	{ NTCONN_FLOW_ERR_NONE, "Success" },
+	{ NTCONN_FLOW_ERR_INTERNAL_ERROR, "Internal error" },
+	{ NTCONN_FLOW_ERR_PORT_IS_NOT_INITIALIZED, "Port is not initialized" },
+	{ NTCONN_FLOW_ERR_UNEXPECTED_VIRTIO_PATH, "Unexpected virtio path" },
+	{ NTCONN_FLOW_ERR_TO_MANY_FLOWS, "To many flows" },
+	{ NTCONN_FLOW_ERR_INVALID_PORT, "Invalid port" },
+	{ NTCONN_FLOW_ERR_NOT_YET_IMPLEMENTED, "Function not yet implemented" },
+	{ NTCONN_FLOW_ERR_UNSUPPORTED_ADAPTER, "Adapter is not supported" },
+	{ NTCONN_FLOW_ERR_NO_VF_QUEUES, "No queues for the VF is found" },
+	{ -1, NULL }
+};
+
+static const char *get_error_msg(enum ntconn_flow_err_e err_code)
+{
+	int idx = 0;
+
+	while (ntconn_err[idx].err_code != (uint32_t)-1 &&
+			ntconn_err[idx].err_code != err_code)
+		idx++;
+	if (ntconn_err[idx].err_code == (uint32_t)-1)
+		idx = 1;
+
+	return ntconn_err[idx].err_text;
+}
+
+static inline int ntconn_flow_err_reply_status(char **data, int *len,
+		enum ntconn_flow_err_e code,
+		int err)
+{
+	*data = malloc(sizeof(struct flow_return_s));
+	if (*data) {
+		struct flow_return_s *return_value =
+			(struct flow_return_s *)*data;
+		*len = sizeof(struct flow_return_s);
+		return_value->status = err;
+		return_value->type = FLOW_ERROR_GENERAL;
+		const char *err_msg = get_error_msg(code);
+
+		memcpy(return_value->err_msg, err_msg,
+		       RTE_MIN(strlen(err_msg), ERR_MSG_LEN));
+		return REQUEST_OK;
+	}
+	*len = 0;
+	NT_LOG(ERR, NTCONNECT, "Not able to allocate memory");
+	return REQUEST_ERR;
+}
+
+static inline int ntconn_flow_err_status(char **data, int *len, int err)
+{
+	*data = malloc(sizeof(struct flow_return_s));
+	if (*data) {
+		struct flow_return_s *return_value =
+			(struct flow_return_s *)*data;
+		*len = sizeof(struct flow_return_s);
+		return_value->status = err;
+		return_value->type = FLOW_ERROR_GENERAL;
+		const char *err_msg =
+			get_error_msg(NTCONN_FLOW_ERR_INTERNAL_ERROR);
+		strlcpy(return_value->err_msg, err_msg, ERR_MSG_LEN);
+		return REQUEST_OK;
+	}
+	*len = 0;
+	NT_LOG(ERR, NTCONNECT, "Not able to allocate memory");
+	return REQUEST_ERR;
+}
+
+/*
+ * Filter functions
+ */
+static int func_flow_create(void *hdl, int client_id,
+			    struct ntconn_header_s *hdr, char **data, int *len);
+static int func_flow_validate(void *hdl, int client_id,
+			      struct ntconn_header_s *hdr, char **data,
+			      int *len);
+static int func_flow_destroy(void *hdl, int client_id,
+			     struct ntconn_header_s *hdr, char **data,
+			     int *len);
+static int func_flow_flush(void *hdl, int client_id,
+			   struct ntconn_header_s *hdr, char **data, int *len);
+static int func_flow_query(void *hdl, int client_id,
+			   struct ntconn_header_s *hdr, char **data, int *len);
+static int func_flow_setport(void *hdl, int client_id,
+			     struct ntconn_header_s *hdr, char **data,
+			     int *len);
+static struct func_s adapter_entry_funcs[] = {
+	{ "setport", NULL, func_flow_setport },
+	{ "create", NULL, func_flow_create },
+	{ "validate", NULL, func_flow_validate },
+	{ "destroy", NULL, func_flow_destroy },
+	{ "flush", NULL, func_flow_flush },
+	{ "query", NULL, func_flow_query },
+	{ NULL, NULL, NULL },
+};
+
+static int copy_return_status(char **data, int *len, int status,
+			      struct flow_error *error)
+{
+	*data = malloc(sizeof(struct flow_return_s));
+	if (*data) {
+		struct flow_return_s *return_value =
+			(struct flow_return_s *)*data;
+		*len = sizeof(struct flow_return_s);
+
+		return_value->status = status;
+		return_value->type = error->type;
+		strlcpy(return_value->err_msg, error->message, ERR_MSG_LEN);
+		return REQUEST_OK;
+	}
+	*len = 0;
+	NT_LOG(ERR, NTCONNECT, "Not able to allocate memory %s",
+	       __func__);
+	return REQUEST_ERR;
+}
+
+static void set_error(struct flow_error *error)
+{
+	error->type = FLOW_ERROR_SUCCESS;
+	error->message = "Operation successfully completed";
+}
+
+static int func_flow_setport(void *hdl _unused, int client_id _unused,
+			     struct ntconn_header_s *hdr _unused, char **data,
+			     int *len)
+{
+	uint32_t i;
+	struct flow_error error;
+	uint32_t nb_port;
+	uint8_t in_port = MAX_PORTS;
+	char vpath[MAX_PATH_LEN];
+	char *saveptr;
+
+	set_error(&error);
+
+	nb_port = rte_eth_dev_count_avail();
+
+#ifdef DEBUG_FLOW
+	NT_LOG(DBG, NTCONNECT, "%s: \"%s\"\n", __func__, *data);
+	NT_LOG(DBG, NTCONNECT, "Number of ports: %u\n", nb_port);
+#endif
+
+	char *tok = strtok_r(*data, ",", &saveptr);
+
+	if (tok) {
+		int length = strlen(tok);
+
+		if (length > 7 && memcmp(tok, "in_port=", 5) == 0)
+			in_port = atoi(tok + 7);
+	}
+#ifdef DEBUG_FLOW
+	NT_LOG(DBG, NTCONNECT, "in_port:          %u\n", in_port);
+#endif
+
+	tok = strtok_r(NULL, ",", &saveptr);
+	if (tok) {
+		int length = strlen(tok);
+
+		if (length > 6 && memcmp(tok, "vpath=", 6) == 0)
+			strlcpy(vpath, tok + 6, MAX_PATH_LEN);
+	}
+#ifdef DEBUG_FLOW
+	NT_LOG(DBG, NTCONNECT, "vpath:           %s\n", vpath);
+#endif
+
+	/* Check that the wanted ports are valid ports */
+	if (in_port >= nb_port) {
+		NT_LOG(ERR, NTCONNECT, "port out of range");
+		return ntconn_flow_err_status(data, len, ENODEV);
+	}
+
+	struct pmd_internals *vp_internals = vp_path_instance_ready(vpath);
+
+	if (!vp_internals) {
+		NT_LOG(ERR, NTCONNECT, "Failed to get VF device");
+		return ntconn_flow_err_status(data, len, ENODEV);
+	}
+
+	/* Get flow device */
+	port_eth[in_port].flw_dev = vp_internals->flw_dev;
+
+	if (port_eth[in_port].flw_dev == NULL) {
+		NT_LOG(ERR, NTCONNECT, "Failed to get eth device");
+		return ntconn_flow_err_status(data, len, ENODEV);
+	}
+
+	/* Only INLINE is supported */
+	if (vp_internals->flw_dev->ndev->flow_profile !=
+			FLOW_ETH_DEV_PROFILE_INLINE) {
+		/* Only inline profile is supported */
+		NT_LOG(ERR, NTCONNECT, "Adapter is not supported");
+		return ntconn_flow_err_status(data, len, ENODEV);
+	}
+
+	if (vp_internals->vpq_nb_vq == 0) {
+		NT_LOG(ERR, NTCONNECT, "No queues for the VF is found");
+		return ntconn_flow_err_status(data, len, ENODEV);
+	}
+
+	/* Server and client must agree of the virtual port number */
+	if (vp_internals->port != (in_port + 4U)) {
+		NT_LOG(ERR, NTCONNECT,
+		       "Internal error: Virtual port out of sync");
+		return ntconn_flow_err_status(data, len, ENODEV);
+	}
+
+#ifdef DEBUG_FLOW
+	NT_LOG(DBG, NTCONNECT, "vport:           %u\n", vp_internals->port);
+	NT_LOG(DBG, NTCONNECT, "vlan (forced):   %u\n", vp_internals->vlan);
+#endif
+
+	port_eth[in_port].caller_id = vp_internals->port;
+	port_eth[in_port].forced_vlan_vid = vp_internals->vlan;
+
+	*data = malloc(sizeof(struct flow_setport_return));
+	if (*data) {
+		struct flow_setport_return *return_value =
+			(struct flow_setport_return *)*data;
+		*len = sizeof(struct flow_setport_return);
+		return_value->num_queues = vp_internals->vpq_nb_vq;
+
+#ifdef DEBUG_FLOW
+		NT_LOG(DBG, NTCONNECT, "Number of queues: %u\n",
+		       vp_internals->vpq_nb_vq);
+#endif
+		for (i = 0; i < vp_internals->vpq_nb_vq && i < MAX_QUEUES;
+				i++) {
+#ifdef DEBUG_FLOW
+			NT_LOG(DBG, NTCONNECT, "Queue:            %u\n",
+			       vp_internals->vpq[i].id);
+			NT_LOG(DBG, NTCONNECT, "HW ID:            %u\n",
+			       vp_internals->vpq[i].hw_id);
+#endif
+			return_value->queues[i].id = vp_internals->vpq[i].id;
+			return_value->queues[i].hw_id =
+				vp_internals->vpq[i].hw_id;
+#ifdef DEBUG_FLOW
+			NT_LOG(DBG, NTCONNECT,
+			       "Setup output port: %u, %04x:%02x:%02x.%x\n",
+			       in_port, vp_internals->pci_dev->addr.domain,
+			       vp_internals->pci_dev->addr.bus,
+			       vp_internals->pci_dev->addr.devid,
+			       vp_internals->pci_dev->addr.function);
+#endif
+		}
+		return REQUEST_OK;
+	}
+	*len = 0;
+	return REQUEST_ERR;
+}
+
+static int func_flow_flush(void *hdl _unused, int client_id _unused,
+			   struct ntconn_header_s *hdr _unused, char **data,
+			   int *len)
+{
+	struct flow_error error;
+	int port = MAX_PORTS;
+	int status = -1;
+	char *saveptr;
+
+	set_error(&error);
+
+#ifdef DEBUG_FLOW
+	NT_LOG(DBG, NTCONNECT, "%s: [%s:%u] enter\n", __func__, __FILE__, __LINE__);
+#endif
+
+	char *tok = strtok_r(*data, ",", &saveptr);
+
+	if (tok) {
+		int length = strlen(tok);
+
+		if (length > 5 && memcmp(tok, "port=", 5) == 0)
+			port = atoi(tok + 5);
+	}
+#ifdef DEBUG_FLOW
+	NT_LOG(DBG, NTCONNECT, "Port id=%u\n", port);
+#endif
+
+	if (port >= MAX_PORTS) {
+		*len = 0;
+		NT_LOG(ERR, NTCONNECT, "port id out of range");
+		return ntconn_flow_err_reply_status(data, len,
+						    NTCONN_FLOW_ERR_INVALID_PORT,
+						    ENODEV);
+	}
+
+	/* Call filter with data */
+	status = flow_flush(port_eth[port].flw_dev, &error);
+	return copy_return_status(data, len, status, &error);
+}
+
+static int func_flow_destroy(void *hdl _unused, int client_id _unused,
+			     struct ntconn_header_s *hdr, char **data, int *len)
+{
+	struct flow_error error;
+	int port = MAX_PORTS;
+	uint64_t flow = 0;
+	int status = -1;
+
+	struct destroy_flow_ntconnect *flow_cpy =
+		(struct destroy_flow_ntconnect *)&(*data)[hdr->len];
+
+	if (hdr->blob_len != sizeof(struct destroy_flow_ntconnect)) {
+		*len = 0;
+		NT_LOG(ERR, NTCONNECT, "Error in filter data");
+		return ntconn_error(data, len, this_module_name,
+				    NTCONN_ERR_CODE_INVALID_REQUEST);
+	}
+
+#ifdef DEBUG_FLOW1
+	NT_LOG(DBG, NTCONNECT, "%s: [%s:%u] enter\n", __func__, __FILE__, __LINE__);
+#endif
+
+	port = flow_cpy->port;
+
+#ifdef DEBUG_FLOW1
+	NT_LOG(DBG, NTCONNECT, "Port id=%u\n", port);
+#endif
+
+	if (port >= MAX_PORTS) {
+		*len = 0;
+		NT_LOG(ERR, NTCONNECT, "port id out of range");
+		return ntconn_flow_err_reply_status(data, len,
+						    NTCONN_FLOW_ERR_INVALID_PORT,
+						    ENODEV);
+	}
+
+	flow = flow_cpy->flow;
+
+#ifdef DEBUG_FLOW1
+	NT_LOG(DBG, NTCONNECT, "flow=0x%016llX\n",
+	       (unsigned long long)flow);
+#endif
+
+	/* Call filter with data */
+	status = flow_destroy(port_eth[port].flw_dev,
+			      (struct flow_handle *)flow, &error);
+
+	*data = malloc(sizeof(struct flow_return_s));
+	if (*data) {
+		struct flow_return_s *return_value =
+			(struct flow_return_s *)*data;
+		*len = sizeof(struct flow_return_s);
+
+		return_value->status = status;
+		return_value->type = error.type;
+		strlcpy(return_value->err_msg, error.message, ERR_MSG_LEN);
+		return REQUEST_OK;
+	}
+	*len = 0;
+	NT_LOG(ERR, NTCONNECT, "Not able to allocate memory %s",
+	       __func__);
+	return REQUEST_ERR;
+}
+
+enum {
+	FLOW_API_FUNC_CREATE,
+	FLOW_API_FUNC_VALIDATE,
+};
+
+static uint64_t make_flow_create(int func, int port,
+				 struct create_flow_ntconnect *flow_cpy,
+				 int *status, struct flow_error *error)
+{
+	struct flow_elem elem[MAX_FLOW_STREAM_ELEM];
+	struct flow_action action[MAX_FLOW_STREAM_ELEM];
+	struct flow_action_vxlan_encap vxlan_tun;
+	struct flow_action_raw_encap encap;
+	struct flow_action_raw_decap decap;
+	struct flow_elem elem_tun[MAX_FLOW_STREAM_VXLAN_TUN_ELEM];
+	int idx = -1;
+
+	struct flow_attr *attr = &flow_cpy->attr;
+	struct flow_elem_cpy *elem_cpy = flow_cpy->elem;
+	struct flow_action_cpy *action_cpy = flow_cpy->action;
+
+	error->type = FLOW_ERROR_GENERAL;
+	error->message = "To many flows";
+	*status = NTCONN_FLOW_ERR_TO_MANY_FLOWS;
+
+	attr->caller_id = port_eth[port].caller_id;
+	attr->forced_vlan_vid = port_eth[port].forced_vlan_vid;
+
+	do {
+		idx++;
+		if (idx > MAX_FLOW_STREAM_ELEM)
+			goto error;
+		elem[idx].type = elem_cpy[idx].type;
+		if (!elem_cpy[idx].spec_cpy.valid) {
+			elem[idx].spec = NULL;
+		} else {
+			elem[idx].spec =
+				(void *)&elem_cpy[idx].spec_cpy.u.start_addr;
+		}
+		if (!elem_cpy[idx].mask_cpy.valid) {
+			elem[idx].mask = NULL;
+		} else {
+			elem[idx].mask =
+				(void *)&elem_cpy[idx].mask_cpy.u.start_addr;
+		}
+	} while (elem_cpy[idx].type != FLOW_ELEM_TYPE_END);
+
+	idx = -1;
+	do {
+		idx++;
+		if (idx > MAX_FLOW_STREAM_ELEM)
+			goto error;
+		action[idx].type = action_cpy[idx].type;
+		if (!action_cpy[idx].conf_cpy.valid) {
+			action[idx].conf = NULL;
+		} else {
+			switch (action_cpy[idx].type) {
+			case FLOW_ACTION_TYPE_VXLAN_ENCAP: {
+				/*
+				 * Special VXLAN ENCAP treatment create inner tunnel
+				 * elements in action
+				 */
+				struct flow_elem_cpy *tun_elem_cpy =
+					(struct flow_elem_cpy *)action_cpy[idx]
+					.conf_cpy.u.vxlan.vxlan_tunnel;
+				vxlan_tun.vxlan_tunnel = elem_tun;
+				int tun_idx = -1;
+
+				do {
+					tun_idx++;
+					if (tun_idx >
+							MAX_FLOW_STREAM_VXLAN_TUN_ELEM) {
+						error->message =
+							"To many VXLAN tunnels";
+						goto error;
+					}
+					elem_tun[tun_idx].type =
+						tun_elem_cpy[tun_idx].type;
+					if (!tun_elem_cpy[tun_idx]
+							.spec_cpy.valid) {
+						elem_tun[tun_idx].spec = NULL;
+					} else {
+						elem_tun[tun_idx].spec =
+							(void *)&tun_elem_cpy[tun_idx]
+							.spec_cpy.u
+							.start_addr;
+					}
+					if (!tun_elem_cpy[tun_idx]
+							.mask_cpy.valid) {
+						elem_tun[tun_idx].mask = NULL;
+					} else {
+						elem_tun[tun_idx].mask =
+							(void *)&tun_elem_cpy[tun_idx]
+							.mask_cpy.u
+							.start_addr;
+					}
+				} while (tun_elem_cpy[tun_idx].type !=
+						FLOW_ELEM_TYPE_END);
+				/* VXLAN ENCAP tunnel finished */
+				action[idx].conf = &vxlan_tun;
+			}
+			break;
+			case FLOW_ACTION_TYPE_RSS: {
+				/* Need to set queue pointer */
+				action_cpy[idx].conf_cpy.u.rss.rss.queue =
+					(const uint16_t *)&action_cpy[idx]
+					.conf_cpy.u.rss.cpy_queue;
+				action[idx].conf = (void *)&action_cpy[idx]
+						   .conf_cpy.u.rss.rss;
+			}
+			break;
+			case FLOW_ACTION_TYPE_METER: {
+				/* Need to convert meter ID to uniq ID for the VF */
+				action_cpy[idx].conf_cpy.u.meter.mtr_id =
+					((flow_mtr_meters_supported() /
+					  (RTE_MAX_ETHPORTS - 2)) *
+					 (flow_cpy->vport - 4)) +
+					action_cpy[idx].conf_cpy.u.meter.mtr_id;
+				action[idx].conf = (void *)&action_cpy[idx]
+						   .conf_cpy.u.meter;
+			}
+			break;
+			case FLOW_ACTION_TYPE_RAW_ENCAP: {
+				encap.preserve = NULL;
+				encap.data =
+					action_cpy[idx].conf_cpy.u.encap.data;
+				encap.item_count =
+					action_cpy[idx]
+					.conf_cpy.u.encap.item_count;
+				encap.size =
+					action_cpy[idx].conf_cpy.u.encap.size;
+
+				for (int eidx = 0;
+						eidx <
+						action_cpy[idx].conf_cpy.u.encap.item_count;
+						eidx++) {
+					if (eidx > RAW_ENCAP_DECAP_ELEMS_MAX) {
+						error->message =
+							"To many encap items";
+						goto error;
+					}
+					encap.items[eidx].type =
+						action_cpy[idx]
+						.conf_cpy.u.encap
+						.item_cpy[eidx]
+						.type;
+					if (action_cpy[idx]
+							.conf_cpy.u.encap
+							.item_cpy[eidx]
+							.spec_cpy.valid) {
+						encap.items[eidx].spec =
+							(void *)&action_cpy[idx]
+							.conf_cpy.u
+							.encap
+							.item_cpy[eidx]
+							.spec_cpy.u
+							.start_addr;
+					} else {
+						encap.items[eidx].spec = NULL;
+					}
+					if (action_cpy[idx]
+							.conf_cpy.u.encap
+							.item_cpy[eidx]
+							.mask_cpy.valid) {
+						encap.items[eidx].mask =
+							(void *)&action_cpy[idx]
+							.conf_cpy.u
+							.encap
+							.item_cpy[eidx]
+							.mask_cpy.u
+							.start_addr;
+					} else {
+						encap.items[eidx].mask = NULL;
+					}
+				}
+				action[idx].conf = &encap;
+			}
+			break;
+			case FLOW_ACTION_TYPE_RAW_DECAP: {
+				decap.data =
+					action_cpy[idx].conf_cpy.u.decap.data;
+				decap.item_count =
+					action_cpy[idx]
+					.conf_cpy.u.decap.item_count;
+				decap.size =
+					action_cpy[idx].conf_cpy.u.decap.size;
+
+				for (int eidx = 0;
+						eidx <
+						action_cpy[idx].conf_cpy.u.decap.item_count;
+						eidx++) {
+					if (eidx > RAW_ENCAP_DECAP_ELEMS_MAX) {
+						error->message =
+							"To many decap items";
+						goto error;
+					}
+					decap.items[eidx].type =
+						action_cpy[idx]
+						.conf_cpy.u.decap
+						.item_cpy[eidx]
+						.type;
+					if (action_cpy[idx]
+							.conf_cpy.u.decap
+							.item_cpy[eidx]
+							.spec_cpy.valid) {
+						decap.items[eidx].spec =
+							(void *)&action_cpy[idx]
+							.conf_cpy.u
+							.decap
+							.item_cpy[eidx]
+							.spec_cpy.u
+							.start_addr;
+					} else {
+						decap.items[eidx].spec = NULL;
+					}
+					if (action_cpy[idx]
+							.conf_cpy.u.decap
+							.item_cpy[eidx]
+							.mask_cpy.valid) {
+						decap.items[eidx].mask =
+							(void *)&action_cpy[idx]
+							.conf_cpy.u
+							.decap
+							.item_cpy[eidx]
+							.mask_cpy.u
+							.start_addr;
+					} else {
+						decap.items[eidx].mask = NULL;
+					}
+				}
+				action[idx].conf = &decap;
+			}
+			break;
+			default: {
+				/* Move conf pointer into conf_cpy data field */
+				action[idx].conf =
+					(void *)&action_cpy[idx]
+					.conf_cpy.u.start_addr;
+			}
+			break;
+			}
+		}
+	} while (action_cpy[idx].type != FLOW_ACTION_TYPE_END);
+
+	*status = NTCONN_FLOW_ERR_NONE;
+	if (func == FLOW_API_FUNC_VALIDATE) {
+		*status = flow_validate(port_eth[port].flw_dev, elem, action,
+					error);
+		return 0ULL;
+	} else {
+		return (uint64_t)flow_create(port_eth[port].flw_dev, attr, elem,
+					     action, error);
+	}
+
+error:
+	return 0;
+}
+
+static int func_flow_create(void *hdl _unused, int client_id _unused,
+			    struct ntconn_header_s *hdr, char **data, int *len)
+{
+	int status;
+	struct flow_error error;
+	uint64_t flow = 0UL;
+	int port = MAX_PORTS;
+
+	struct create_flow_ntconnect *flow_cpy =
+		(struct create_flow_ntconnect *)&(*data)[hdr->len];
+
+	if (hdr->blob_len != sizeof(struct create_flow_ntconnect)) {
+		*len = 0;
+		NT_LOG(ERR, NTCONNECT, "Error in filter data");
+		return ntconn_error(data, len, this_module_name,
+				    NTCONN_ERR_CODE_INVALID_REQUEST);
+	}
+
+	port = flow_cpy->port;
+
+	if (port >= MAX_PORTS) {
+		NT_LOG(ERR, NTCONNECT, "port id out of range");
+		return ntconn_flow_err_reply_status(data, len,
+						    NTCONN_FLOW_ERR_INVALID_PORT,
+						    ENODEV);
+	}
+
+#ifdef DEBUG_PARSING
+	int i;
+
+	for (i = 0; i < MAX_FLOW_STREAM_ELEM; i++) {
+		if (flow_cpy[i].elem[i].type == FLOW_ELEM_TYPE_END) {
+			NT_LOG(DBG, NTCONNECT, "FLOW_ELEM_TYPE_END\n");
+			break;
+		}
+		switch (flow_cpy->elem[i].type) {
+		case FLOW_ELEM_TYPE_IPV4:
+			NT_LOG(DBG, NTCONNECT, "FLOW_ELEM_TYPE_IPV4 %i\n", i);
+			NT_LOG(DBG, NTCONNECT, "     src_ip:   %u.%u.%u.%u\n",
+				((const char *)&flow_cpy->elem[i]
+					.spec_cpy.u.ipv4.hdr.src_ip)[0] & 0xFF,
+				((const char *)&flow_cpy->elem[i]
+					.spec_cpy.u.ipv4.hdr.src_ip)[1] & 0xFF,
+				((const char *)&flow_cpy->elem[i]
+					.spec_cpy.u.ipv4.hdr.src_ip)[2] & 0xFF,
+				((const char *)&flow_cpy->elem[i]
+					.spec_cpy.u.ipv4.hdr.src_ip)[3] & 0xFF);
+			NT_LOG(DBG, NTCONNECT, "     dst_ip:   %u.%u.%u.%u\n",
+				((const char *)&flow_cpy->elem[i]
+					.spec_cpy.u.ipv4.hdr.dst_ip)[0] & 0xFF,
+				((const char *)&flow_cpy->elem[i]
+					.spec_cpy.u.ipv4.hdr.dst_ip)[1] & 0xFF,
+				((const char *)&flow_cpy->elem[i]
+					.spec_cpy.u.ipv4.hdr.dst_ip)[2] & 0xFF,
+				((const char *)&flow_cpy->elem[i]
+					.spec_cpy.u.ipv4.hdr.dst_ip)[3] & 0xFF);
+			NT_LOG(DBG, NTCONNECT, "     src_mask: %u.%u.%u.%u\n",
+				((const char *)&flow_cpy->elem[i]
+					.mask_cpy.u.ipv4.hdr.src_ip)[0] & 0xFF,
+				((const char *)&flow_cpy->elem[i]
+					.mask_cpy.u.ipv4.hdr.src_ip)[1] & 0xFF,
+				((const char *)&flow_cpy->elem[i]
+					.mask_cpy.u.ipv4.hdr.src_ip)[2] & 0xFF,
+				((const char *)&flow_cpy->elem[i]
+					.mask_cpy.u.ipv4.hdr.src_ip)[3] & 0xFF);
+			NT_LOG(DBG, NTCONNECT, "     dst_mask: %u.%u.%u.%u\n",
+				((const char *)&flow_cpy->elem[i]
+					.mask_cpy.u.ipv4.hdr.dst_ip)[0] & 0xFF,
+				((const char *)&flow_cpy->elem[i]
+					.mask_cpy.u.ipv4.hdr.dst_ip)[1] & 0xFF,
+				((const char *)&flow_cpy->elem[i]
+					.mask_cpy.u.ipv4.hdr.dst_ip)[2] & 0xFF,
+				((const char *)&flow_cpy->elem[i]
+					.mask_cpy.u.ipv4.hdr.dst_ip)[3] & 0xFF);
+			break;
+		case FLOW_ELEM_TYPE_ETH:
+			NT_LOG(DBG, NTCONNECT, "FLOW_ELEM_TYPE_ETH %i\n", i);
+			NT_LOG(DBG, NTCONNECT,
+			       "     src mac:  %02X:%02X:%02X:%02X:%02X:%02X\n",
+			       flow_cpy->elem[i].spec_cpy.u.eth.s_addr.addr_b[0] & 0xFF,
+			       flow_cpy->elem[i].spec_cpy.u.eth.s_addr.addr_b[1] & 0xFF,
+			       flow_cpy->elem[i].spec_cpy.u.eth.s_addr.addr_b[2] & 0xFF,
+			       flow_cpy->elem[i].spec_cpy.u.eth.s_addr.addr_b[3] & 0xFF,
+			       flow_cpy->elem[i].spec_cpy.u.eth.s_addr.addr_b[4] & 0xFF,
+			       flow_cpy->elem[i].spec_cpy.u.eth.s_addr.addr_b[5] & 0xFF);
+			NT_LOG(DBG, NTCONNECT,
+			       "     dst mac:  %02X:%02X:%02X:%02X:%02X:%02X\n",
+			       flow_cpy->elem[i].spec_cpy.u.eth.d_addr.addr_b[0] & 0xFF,
+			       flow_cpy->elem[i].spec_cpy.u.eth.d_addr.addr_b[1] & 0xFF,
+			       flow_cpy->elem[i].spec_cpy.u.eth.d_addr.addr_b[2] & 0xFF,
+			       flow_cpy->elem[i].spec_cpy.u.eth.d_addr.addr_b[3] & 0xFF,
+			       flow_cpy->elem[i].spec_cpy.u.eth.d_addr.addr_b[4] & 0xFF,
+			       flow_cpy->elem[i].spec_cpy.u.eth.d_addr.addr_b[5] & 0xFF);
+			NT_LOG(DBG, NTCONNECT,
+			       "     src mask  %02X:%02X:%02X:%02X:%02X:%02X\n",
+			       flow_cpy->elem[i].mask_cpy.u.eth.s_addr.addr_b[0] & 0xFF,
+			       flow_cpy->elem[i].mask_cpy.u.eth.s_addr.addr_b[1] & 0xFF,
+			       flow_cpy->elem[i].mask_cpy.u.eth.s_addr.addr_b[2] & 0xFF,
+			       flow_cpy->elem[i].mask_cpy.u.eth.s_addr.addr_b[3] & 0xFF,
+			       flow_cpy->elem[i].mask_cpy.u.eth.s_addr.addr_b[4] & 0xFF,
+			       flow_cpy->elem[i].mask_cpy.u.eth.s_addr.addr_b[5] & 0xFF);
+			NT_LOG(DBG, NTCONNECT,
+			       "     dst mask  %02X:%02X:%02X:%02X:%02X:%02X\n",
+			       flow_cpy->elem[i].mask_cpy.u.eth.d_addr.addr_b[0] & 0xFF,
+			       flow_cpy->elem[i].mask_cpy.u.eth.d_addr.addr_b[1] & 0xFF,
+			       flow_cpy->elem[i].mask_cpy.u.eth.d_addr.addr_b[2] & 0xFF,
+			       flow_cpy->elem[i].mask_cpy.u.eth.d_addr.addr_b[3] & 0xFF,
+			       flow_cpy->elem[i].mask_cpy.u.eth.d_addr.addr_b[4] & 0xFF,
+			       flow_cpy->elem[i].mask_cpy.u.eth.d_addr.addr_b[5] & 0xFF);
+			break;
+		case FLOW_ELEM_TYPE_VLAN:
+			NT_LOG(DBG, NTCONNECT, "FLOW_ELEM_TYPE_VLAN %i\n", i);
+			break;
+		case FLOW_ELEM_TYPE_IPV6:
+			NT_LOG(DBG, NTCONNECT, "FLOW_ELEM_TYPE_IPV6 %i\n", i);
+			break;
+		case FLOW_ELEM_TYPE_SCTP:
+			NT_LOG(DBG, NTCONNECT, "FLOW_ELEM_TYPE_SCTP %i\n", i);
+			break;
+		case FLOW_ELEM_TYPE_TCP:
+			NT_LOG(DBG, NTCONNECT, "FLOW_ELEM_TYPE_TCP %i\n", i);
+			break;
+		case FLOW_ELEM_TYPE_UDP:
+			NT_LOG(DBG, NTCONNECT, "FLOW_ELEM_TYPE_UDP %i\n", i);
+			break;
+		case FLOW_ELEM_TYPE_ICMP:
+			NT_LOG(DBG, NTCONNECT, "FLOW_ELEM_TYPE_ICMP %i\n", i);
+			break;
+		case FLOW_ELEM_TYPE_VXLAN:
+			NT_LOG(DBG, NTCONNECT, "FLOW_ELEM_TYPE_VXLAN %i\n", i);
+			break;
+		case FLOW_ELEM_TYPE_PORT_ID:
+			NT_LOG(DBG, NTCONNECT, "FLOW_ELEM_TYPE_PORT_ID %i\n",
+			       i);
+			break;
+		default:
+			NT_LOG(DBG, NTCONNECT, "Unknown item %u\n",
+			       flow_cpy->elem[i].type);
+			break;
+		}
+	}
+
+	for (i = 0; i < MAX_FLOW_STREAM_ELEM; i++) {
+		uint32_t j;
+
+		if (flow_cpy->action[i].type == FLOW_ACTION_TYPE_END) {
+			NT_LOG(DBG, NTCONNECT, "FLOW_ACTION_TYPE_END\n");
+			break;
+		}
+		switch (flow_cpy->action[i].type) {
+		case FLOW_ACTION_TYPE_RSS:
+			NT_LOG(DBG, NTCONNECT, "FLOW_ACTION_TYPE_RSS %i\n", i);
+			NT_LOG(DBG, NTCONNECT, "     queue nb: %u\n",
+			       flow_cpy->action[i].conf_cpy.u.rss.rss.queue_num);
+			NT_LOG(DBG, NTCONNECT, "     queue:    ");
+			for (j = 0;
+					j < flow_cpy->action[i]
+					.conf_cpy.u.rss.rss.queue_num &&
+					j < FLOW_MAX_QUEUES;
+					j++) {
+				NT_LOG(DBG, NTCONNECT, "%u ",
+				       flow_cpy->action[i]
+				       .conf_cpy.u.rss.cpy_queue[j]);
+			}
+			NT_LOG(DBG, NTCONNECT, "\n");
+			break;
+
+		case FLOW_ACTION_TYPE_POP_VLAN:
+			NT_LOG(DBG, NTCONNECT, "FLOW_ACTION_TYPE_POP_VLAN %i\n",
+			       i);
+			break;
+		case FLOW_ACTION_TYPE_PUSH_VLAN:
+			NT_LOG(DBG, NTCONNECT,
+			       "FLOW_ACTION_TYPE_PUSH_VLAN %i\n", i);
+			break;
+		case FLOW_ACTION_TYPE_SET_VLAN_VID:
+			NT_LOG(DBG, NTCONNECT,
+			       "FLOW_ACTION_TYPE_SET_VLAN_VID %i\n", i);
+			break;
+		case FLOW_ACTION_TYPE_SET_VLAN_PCP:
+			NT_LOG(DBG, NTCONNECT,
+			       "FLOW_ACTION_TYPE_SET_VLAN_PCP %i\n", i);
+			break;
+		case FLOW_ACTION_TYPE_VXLAN_DECAP:
+			NT_LOG(DBG, NTCONNECT,
+			       "FLOW_ACTION_TYPE_VXLAN_DECAP %i\n", i);
+			break;
+		case FLOW_ACTION_TYPE_VXLAN_ENCAP:
+			NT_LOG(DBG, NTCONNECT,
+			       "FLOW_ACTION_TYPE_VXLAN_ENCAP %i\n", i);
+			break;
+		case FLOW_ACTION_TYPE_DROP:
+			NT_LOG(DBG, NTCONNECT, "FLOW_ACTION_TYPE_DROP %i\n", i);
+			break;
+		case FLOW_ACTION_TYPE_COUNT:
+			NT_LOG(DBG, NTCONNECT, "FLOW_ACTION_TYPE_COUNT %i\n",
+			       i);
+			break;
+		case FLOW_ACTION_TYPE_MARK:
+			NT_LOG(DBG, NTCONNECT, "FLOW_ACTION_TYPE_MARK %i\n", i);
+			break;
+		case FLOW_ACTION_TYPE_PORT_ID:
+			NT_LOG(DBG, NTCONNECT,
+			       "FLOW_ACTION_TYPE_PORT_ID %i: ID=%u\n", i,
+			       flow_cpy->action[i].conf_cpy.u.port_id.id);
+			break;
+		case FLOW_ACTION_TYPE_QUEUE:
+			NT_LOG(DBG, NTCONNECT,
+			       "FLOW_ACTION_TYPE_QUEUE %i: queue=%u\n", i,
+			       flow_cpy->action[i].conf_cpy.u.queue.index);
+			break;
+		case FLOW_ACTION_TYPE_SET_TAG:
+			NT_LOG(DBG, NTCONNECT,
+			       "FLOW_ACTION_TYPE_SET_TAG %i: idx=%u, data=%u, mask=%X\n",
+			       i, flow_cpy->action[i].conf_cpy.u.tag.index,
+			       flow_cpy->action[i].conf_cpy.u.tag.data,
+			       flow_cpy->action[i].conf_cpy.u.tag.mask);
+			break;
+		default:
+			NT_LOG(DBG, NTCONNECT, "Unknown action %u\n",
+			       flow_cpy->action[i].type);
+			break;
+		}
+	}
+#endif
+
+	/* Call filter with data */
+	flow = make_flow_create(FLOW_API_FUNC_CREATE, port, flow_cpy, &status,
+				&error);
+	if (flow) {
+		*data = malloc(sizeof(struct create_flow_return_s));
+		if (!*data)
+			goto create_flow_error_malloc;
+		struct create_flow_return_s *return_value =
+			(struct create_flow_return_s *)*data;
+		*len = sizeof(struct create_flow_return_s);
+		return_value->flow = flow;
+		return REQUEST_OK;
+	}
+
+	*data = malloc(sizeof(struct flow_error_return_s));
+	if (!*data)
+		goto create_flow_error_malloc;
+	struct flow_error_return_s *return_value =
+		(struct flow_error_return_s *)*data;
+	*len = sizeof(struct flow_error_return_s);
+	return_value->type = error.type;
+	strlcpy(return_value->err_msg, error.message, ERR_MSG_LEN);
+	return REQUEST_OK;
+
+create_flow_error_malloc:
+
+	*len = 0;
+	NT_LOG(ERR, NTCONNECT, "Not able to allocate memory %s", __func__);
+	return REQUEST_ERR;
+}
+
+static int func_flow_validate(void *hdl _unused, int client_id _unused,
+			      struct ntconn_header_s *hdr, char **data,
+			      int *len)
+{
+	int status;
+	struct flow_error error;
+	int port = MAX_PORTS;
+
+	struct create_flow_ntconnect *flow_cpy =
+		(struct create_flow_ntconnect *)&(*data)[hdr->len];
+
+	if (hdr->blob_len != sizeof(struct create_flow_ntconnect)) {
+		*len = 0;
+		NT_LOG(ERR, NTCONNECT, "Error in filter data");
+		return ntconn_error(data, len, this_module_name,
+				    NTCONN_ERR_CODE_INVALID_REQUEST);
+	}
+
+	set_error(&error);
+
+#ifdef DEBUG_FLOW
+	NT_LOG(DBG, NTCONNECT, "func_flow_create\n");
+#endif
+
+	port = flow_cpy->port;
+
+#ifdef DEBUG_FLOW
+	NT_LOG(DBG, NTCONNECT, "Port id=%u\n", port);
+#endif
+
+	if (port >= MAX_PORTS) {
+		NT_LOG(ERR, NTCONNECT, "port id out of range");
+		return ntconn_flow_err_reply_status(data, len,
+			NTCONN_FLOW_ERR_INVALID_PORT, ENODEV);
+	}
+
+#ifdef DEBUG_PARSING
+	int i;
+
+	for (i = 0; i < MAX_FLOW_STREAM_ELEM; i++) {
+		if (flow_cpy[i].elem[i].type == FLOW_ELEM_TYPE_END) {
+			NT_LOG(DBG, NTCONNECT, "FLOW_ELEM_TYPE_END\n");
+			break;
+		}
+		switch (flow_cpy->elem[i].type) {
+		case FLOW_ELEM_TYPE_IPV4:
+			NT_LOG(DBG, NTCONNECT, "FLOW_ELEM_TYPE_IPV4 %i\n", i);
+			NT_LOG(DBG, NTCONNECT, "     src_ip:   %u.%u.%u.%u\n",
+				((const char *)&flow_cpy->elem[i]
+					.spec_cpy.u.ipv4.hdr.src_ip)[0] & 0xFF,
+				((const char *)&flow_cpy->elem[i]
+					.spec_cpy.u.ipv4.hdr.src_ip)[0] & 0xFF,
+				((const char *)&flow_cpy->elem[i]
+					.spec_cpy.u.ipv4.hdr.src_ip)[0] & 0xFF,
+				((const char *)&flow_cpy->elem[i]
+					.spec_cpy.u.ipv4.hdr.src_ip)[0] & 0xFF);
+			NT_LOG(DBG, NTCONNECT, "     dst_ip:   %u.%u.%u.%u\n",
+				((const char *)&flow_cpy->elem[i]
+					.spec_cpy.u.ipv4.hdr.dst_ip)[0] & 0xFF,
+				((const char *)&flow_cpy->elem[i]
+					.spec_cpy.u.ipv4.hdr.dst_ip)[0] & 0xFF,
+				((const char *)&flow_cpy->elem[i]
+					.spec_cpy.u.ipv4.hdr.dst_ip)[0] & 0xFF,
+				((const char *)&flow_cpy->elem[i]
+					.spec_cpy.u.ipv4.hdr.dst_ip)[0] & 0xFF);
+			NT_LOG(DBG, NTCONNECT, "     src_mask: %u.%u.%u.%u\n",
+				((const char *)&flow_cpy->elem[i]
+					.mask_cpy.u.ipv4.hdr.src_ip)[0] & 0xFF,
+				((const char *)&flow_cpy->elem[i]
+					.mask_cpy.u.ipv4.hdr.src_ip)[0] & 0xFF,
+				((const char *)&flow_cpy->elem[i]
+					.mask_cpy.u.ipv4.hdr.src_ip)[0] & 0xFF,
+				((const char *)&flow_cpy->elem[i]
+					.mask_cpy.u.ipv4.hdr.src_ip)[0] & 0xFF);
+			NT_LOG(DBG, NTCONNECT, "     dst_mask: %u.%u.%u.%u\n",
+				((const char *)&flow_cpy->elem[i]
+					.mask_cpy.u.ipv4.hdr.dst_ip)[0] & 0xFF,
+				((const char *)&flow_cpy->elem[i]
+					.mask_cpy.u.ipv4.hdr.dst_ip)[0] & 0xFF,
+				((const char *)&flow_cpy->elem[i]
+					.mask_cpy.u.ipv4.hdr.dst_ip)[0] & 0xFF,
+				((const char *)&flow_cpy->elem[i]
+					.mask_cpy.u.ipv4.hdr.dst_ip)[0] & 0xFF);
+			break;
+		case FLOW_ELEM_TYPE_ETH:
+			NT_LOG(DBG, NTCONNECT, "FLOW_ELEM_TYPE_ETH %i\n", i);
+			NT_LOG(DBG, NTCONNECT,
+			       "     src mac:  %02X:%02X:%02X:%02X:%02X:%02X\n",
+			       flow_cpy->elem[i].spec_cpy.u.eth.s_addr.addr_b[0] & 0xFF,
+			       flow_cpy->elem[i].spec_cpy.u.eth.s_addr.addr_b[1] & 0xFF,
+			       flow_cpy->elem[i].spec_cpy.u.eth.s_addr.addr_b[2] & 0xFF,
+			       flow_cpy->elem[i].spec_cpy.u.eth.s_addr.addr_b[3] & 0xFF,
+			       flow_cpy->elem[i].spec_cpy.u.eth.s_addr.addr_b[4] & 0xFF,
+			       flow_cpy->elem[i].spec_cpy.u.eth.s_addr.addr_b[5] & 0xFF);
+			NT_LOG(DBG, NTCONNECT,
+			       "     dst mac:  %02X:%02X:%02X:%02X:%02X:%02X\n",
+			       flow_cpy->elem[i].spec_cpy.u.eth.d_addr.addr_b[0] & 0xFF,
+			       flow_cpy->elem[i].spec_cpy.u.eth.d_addr.addr_b[1] & 0xFF,
+			       flow_cpy->elem[i].spec_cpy.u.eth.d_addr.addr_b[2] & 0xFF,
+			       flow_cpy->elem[i].spec_cpy.u.eth.d_addr.addr_b[3] & 0xFF,
+			       flow_cpy->elem[i].spec_cpy.u.eth.d_addr.addr_b[4] & 0xFF,
+			       flow_cpy->elem[i].spec_cpy.u.eth.d_addr.addr_b[5] & 0xFF);
+			NT_LOG(DBG, NTCONNECT,
+			       "     src mask  %02X:%02X:%02X:%02X:%02X:%02X\n",
+			       flow_cpy->elem[i].mask_cpy.u.eth.s_addr.addr_b[0] & 0xFF,
+			       flow_cpy->elem[i].mask_cpy.u.eth.s_addr.addr_b[1] & 0xFF,
+			       flow_cpy->elem[i].mask_cpy.u.eth.s_addr.addr_b[2] & 0xFF,
+			       flow_cpy->elem[i].mask_cpy.u.eth.s_addr.addr_b[3] & 0xFF,
+			       flow_cpy->elem[i].mask_cpy.u.eth.s_addr.addr_b[4] & 0xFF,
+			       flow_cpy->elem[i].mask_cpy.u.eth.s_addr.addr_b[5] & 0xFF);
+			NT_LOG(DBG, NTCONNECT,
+			       "     dst mask  %02X:%02X:%02X:%02X:%02X:%02X\n",
+			       flow_cpy->elem[i].mask_cpy.u.eth.d_addr.addr_b[0] & 0xFF,
+			       flow_cpy->elem[i].mask_cpy.u.eth.d_addr.addr_b[1] & 0xFF,
+			       flow_cpy->elem[i].mask_cpy.u.eth.d_addr.addr_b[2] & 0xFF,
+			       flow_cpy->elem[i].mask_cpy.u.eth.d_addr.addr_b[3] & 0xFF,
+			       flow_cpy->elem[i].mask_cpy.u.eth.d_addr.addr_b[4] & 0xFF,
+			       flow_cpy->elem[i].mask_cpy.u.eth.d_addr.addr_b[5] & 0xFF);
+			break;
+		case FLOW_ELEM_TYPE_VLAN:
+			NT_LOG(DBG, NTCONNECT, "FLOW_ELEM_TYPE_VLAN %i\n", i);
+			break;
+		case FLOW_ELEM_TYPE_IPV6:
+			NT_LOG(DBG, NTCONNECT, "FLOW_ELEM_TYPE_IPV6 %i\n", i);
+			break;
+		case FLOW_ELEM_TYPE_SCTP:
+			NT_LOG(DBG, NTCONNECT, "FLOW_ELEM_TYPE_SCTP %i\n", i);
+			break;
+		case FLOW_ELEM_TYPE_TCP:
+			NT_LOG(DBG, NTCONNECT, "FLOW_ELEM_TYPE_TCP %i\n", i);
+			break;
+		case FLOW_ELEM_TYPE_UDP:
+			NT_LOG(DBG, NTCONNECT, "FLOW_ELEM_TYPE_UDP %i\n", i);
+			break;
+		case FLOW_ELEM_TYPE_ICMP:
+			NT_LOG(DBG, NTCONNECT, "FLOW_ELEM_TYPE_ICMP %i\n", i);
+			break;
+		case FLOW_ELEM_TYPE_VXLAN:
+			NT_LOG(DBG, NTCONNECT, "FLOW_ELEM_TYPE_VXLAN %i\n", i);
+			break;
+		case FLOW_ELEM_TYPE_PORT_ID:
+			NT_LOG(DBG, NTCONNECT, "FLOW_ELEM_TYPE_PORT_ID %i\n",
+			       i);
+			break;
+		default:
+			NT_LOG(DBG, NTCONNECT, "Unknown item %u\n",
+			       flow_cpy->elem[i].type);
+			break;
+		}
+	}
+
+	for (i = 0; i < MAX_FLOW_STREAM_ELEM; i++) {
+		uint32_t j;
+
+		if (flow_cpy->action[i].type == FLOW_ACTION_TYPE_END) {
+			NT_LOG(DBG, NTCONNECT, "FLOW_ACTION_TYPE_END\n");
+			break;
+		}
+		switch (flow_cpy->action[i].type) {
+		case FLOW_ACTION_TYPE_RSS:
+			NT_LOG(DBG, NTCONNECT, "FLOW_ACTION_TYPE_RSS %i\n", i);
+			NT_LOG(DBG, NTCONNECT, "     queue nb: %u\n",
+			       flow_cpy->action[i].conf_cpy.u.rss.rss.queue_num);
+			NT_LOG(DBG, NTCONNECT, "     queue:    ");
+			for (j = 0;
+					j < flow_cpy->action[i]
+					.conf_cpy.u.rss.rss.queue_num &&
+					j < FLOW_MAX_QUEUES;
+					j++) {
+				NT_LOG(DBG, NTCONNECT, "%u ",
+				       flow_cpy->action[i]
+				       .conf_cpy.u.rss.cpy_queue[j]);
+			}
+			NT_LOG(DBG, NTCONNECT, "\n");
+			break;
+
+		case FLOW_ACTION_TYPE_POP_VLAN:
+			NT_LOG(DBG, NTCONNECT, "FLOW_ACTION_TYPE_POP_VLAN %i\n",
+			       i);
+			break;
+		case FLOW_ACTION_TYPE_PUSH_VLAN:
+			NT_LOG(DBG, NTCONNECT,
+			       "FLOW_ACTION_TYPE_PUSH_VLAN %i\n", i);
+			break;
+		case FLOW_ACTION_TYPE_SET_VLAN_VID:
+			NT_LOG(DBG, NTCONNECT,
+			       "FLOW_ACTION_TYPE_SET_VLAN_VID %i\n", i);
+			break;
+		case FLOW_ACTION_TYPE_SET_VLAN_PCP:
+			NT_LOG(DBG, NTCONNECT,
+			       "FLOW_ACTION_TYPE_SET_VLAN_PCP %i\n", i);
+			break;
+		case FLOW_ACTION_TYPE_VXLAN_DECAP:
+			NT_LOG(DBG, NTCONNECT,
+			       "FLOW_ACTION_TYPE_VXLAN_DECAP %i\n", i);
+			break;
+		case FLOW_ACTION_TYPE_VXLAN_ENCAP:
+			NT_LOG(DBG, NTCONNECT,
+			       "FLOW_ACTION_TYPE_VXLAN_ENCAP %i\n", i);
+			break;
+		case FLOW_ACTION_TYPE_DROP:
+			NT_LOG(DBG, NTCONNECT, "FLOW_ACTION_TYPE_DROP %i\n", i);
+			break;
+		case FLOW_ACTION_TYPE_COUNT:
+			NT_LOG(DBG, NTCONNECT, "FLOW_ACTION_TYPE_COUNT %i\n",
+			       i);
+			break;
+		case FLOW_ACTION_TYPE_MARK:
+			NT_LOG(DBG, NTCONNECT, "FLOW_ACTION_TYPE_MARK %i\n", i);
+			break;
+		case FLOW_ACTION_TYPE_PORT_ID:
+			NT_LOG(DBG, NTCONNECT,
+			       "FLOW_ACTION_TYPE_PORT_ID %i: ID=%u\n", i,
+			       flow_cpy->action[i].conf_cpy.u.port_id.id);
+			break;
+		case FLOW_ACTION_TYPE_QUEUE:
+			NT_LOG(DBG, NTCONNECT,
+			       "FLOW_ACTION_TYPE_QUEUE %i: queue=%u\n", i,
+			       flow_cpy->action[i].conf_cpy.u.queue.index);
+			break;
+		case FLOW_ACTION_TYPE_SET_TAG:
+			NT_LOG(DBG, NTCONNECT,
+			       "FLOW_ACTION_TYPE_SET_TAG %i: idx=%u, data=%u, mask=%X\n",
+			       i, flow_cpy->action[i].conf_cpy.u.tag.index,
+			       flow_cpy->action[i].conf_cpy.u.tag.data,
+			       flow_cpy->action[i].conf_cpy.u.tag.mask);
+			break;
+		default:
+			NT_LOG(DBG, NTCONNECT, "Unknown action %u\n",
+			       flow_cpy->action[i].type);
+			break;
+		}
+	}
+#endif
+
+	/* Call filter with data */
+	make_flow_create(FLOW_API_FUNC_VALIDATE, port, flow_cpy, &status,
+			 &error);
+	return copy_return_status(data, len, status, &error);
+
+	/* Call filter with data */
+	make_flow_create(FLOW_API_FUNC_VALIDATE, port, flow_cpy, &status,
+			 &error);
+	if (!status) {
+		*data = malloc(sizeof(struct validate_flow_return_s));
+		if (!*data)
+			goto validate_flow_error_malloc;
+		struct validate_flow_return_s *return_value =
+			(struct validate_flow_return_s *)*data;
+		*len = sizeof(struct validate_flow_return_s);
+		return_value->status = 0;
+		return REQUEST_OK;
+	}
+
+	*data = malloc(sizeof(struct flow_error_return_s));
+	if (!*data)
+		goto validate_flow_error_malloc;
+	struct flow_error_return_s *return_value =
+		(struct flow_error_return_s *)*data;
+	*len = sizeof(struct flow_error_return_s);
+	return_value->type = error.type;
+	strlcpy(return_value->err_msg, error.message, ERR_MSG_LEN);
+	return_value->status = status;
+	return REQUEST_OK;
+
+validate_flow_error_malloc:
+
+	*len = 0;
+	NT_LOG(ERR, NTCONNECT, "Not able to allocate memory %s", __func__);
+	return REQUEST_ERR;
+}
+
+static int func_flow_query(void *hdl _unused, int client_id _unused,
+			   struct ntconn_header_s *hdr, char **data, int *len)
+{
+	int status;
+	struct flow_error error;
+	int port = MAX_PORTS;
+	struct flow_handle *flow;
+
+	struct query_flow_ntconnect *flow_cpy =
+		(struct query_flow_ntconnect *)&(*data)[hdr->len];
+
+	if (hdr->blob_len != sizeof(struct query_flow_ntconnect)) {
+		*len = 0;
+		NT_LOG(ERR, NTCONNECT, "Error in filter data");
+		return ntconn_error(data, len, this_module_name,
+				    NTCONN_ERR_CODE_INVALID_REQUEST);
+	}
+
+	set_error(&error);
+
+#ifdef DEBUG_FLOW
+	NT_LOG(DBG, NTCONNECT, "%s: [%s:%u] enter\n", __func__, __FILE__, __LINE__);
+#endif
+
+	port = flow_cpy->port;
+
+#ifdef DEBUG_FLOW
+	NT_LOG(DBG, NTCONNECT, "Port id=%u\n", port);
+#endif
+
+	if (port >= MAX_PORTS) {
+		*len = 0;
+		NT_LOG(ERR, NTCONNECT, "port id out of range");
+		return ntconn_flow_err_reply_status(data, len,
+			NTCONN_FLOW_ERR_INVALID_PORT, ENODEV);
+	}
+
+#ifdef DEBUG_FLOW
+	NT_LOG(DBG, NTCONNECT, "flow=0x%016llX\n",
+	       (unsigned long long)flow_cpy->flow);
+#endif
+
+	flow = (struct flow_handle *)flow_cpy->flow;
+
+	const struct flow_action action = {
+		flow_cpy->action.type,
+		(const void *)&flow_cpy->action.conf_cpy.u.count
+	};
+
+	/* Call filter with data */
+	void *data_out = NULL;
+	uint32_t length = 0;
+
+	status = flow_query(port_eth[port].flw_dev, flow, &action, &data_out,
+			    &length, &error);
+
+	*data = malloc(sizeof(struct query_flow_return_s) + length);
+	if (*data) {
+		struct query_flow_return_s *return_value =
+			(struct query_flow_return_s *)*data;
+		*len = sizeof(struct query_flow_return_s) + length;
+
+		return_value->status = status;
+		return_value->type = error.type;
+		strlcpy(return_value->err_msg, error.message, ERR_MSG_LEN);
+
+		if (data_out) {
+			memcpy(return_value->data, data_out, length);
+			return_value->data_length = length;
+			free(data_out);
+		} else {
+			return_value->data_length = 0;
+		}
+		return REQUEST_OK;
+	}
+	*len = 0;
+	NT_LOG(ERR, NTCONNECT, "Not able to allocate memory %s",
+	       __func__);
+	return REQUEST_ERR;
+}
+
+static int flow_request(void *hdl, int client_id _unused,
+			struct ntconn_header_s *hdr, char *function,
+			char **data, int *len)
+{
+	return execute_function(this_module_name, hdl, client_id, hdr, function,
+				adapter_entry_funcs, data, len, 0);
+}
+
+static void flow_free_data(void *hdl _unused, char *data)
+{
+	if (data)
+		free(data);
+}
+
+static void flow_client_cleanup(void *hdl _unused, int client_id _unused)
+{
+	/* Nothing to do */
+}
+
+static const ntconnapi_t ntconn_flow_op = { this_module_name,
+					    NTCONN_FLOW_VERSION_MAJOR,
+					    NTCONN_FLOW_VERSION_MINOR,
+					    flow_request,
+					    flow_free_data,
+					    flow_client_cleanup
+					  };
+
+int ntconn_flow_register(struct drv_s *drv)
+{
+	int i;
+
+	for (i = 0; i < MAX_CLIENTS; i++) {
+		if (flow_hdl[i].drv == NULL)
+			break;
+	}
+	if (i == MAX_CLIENTS) {
+		NT_LOG(ERR, NTCONNECT,
+		       "Cannot register more adapters into NtConnect framework");
+		return -1;
+	}
+
+	flow_hdl[i].drv = drv;
+	return register_ntconn_mod(&drv->p_dev->addr, (void *)&flow_hdl[i],
+				   &ntconn_flow_op);
+}
diff --git a/drivers/net/ntnic/ntconnect_modules/ntconn_meter.c b/drivers/net/ntnic/ntconnect_modules/ntconn_meter.c
new file mode 100644
index 0000000000..7c21690f8b
--- /dev/null
+++ b/drivers/net/ntnic/ntconnect_modules/ntconn_meter.c
@@ -0,0 +1,517 @@ 
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#include <errno.h>
+#include "ntnic_ethdev.h"
+#include "ntconnect.h"
+#include "ntos_system.h"
+#include "ntconn_modules.h"
+#include "ntconn_mod_helper.h"
+#include "nt_util.h"
+#include "ntlog.h"
+#include "ntnic_vf_vdpa.h"
+
+#include "ntconnect_api_meter.h"
+#include "flow_api_profile_inline.h"
+
+#include <rte_errno.h>
+#include <rte_mtr.h>
+#include <rte_mtr_driver.h>
+
+#define NTCONN_METER_VERSION_MAJOR 0U
+#define NTCONN_METER_VERSION_MINOR 1U
+
+#define this_module_name "meter"
+
+#define MAX_CLIENTS 32
+
+#define UNUSED __rte_unused
+
+static struct meter_hdl_s {
+	struct drv_s *drv;
+} meter_hdl[MAX_CLIENTS];
+
+static ntconn_err_t ntconn_err[] = {
+	{ NTCONN_METER_ERR_NONE, "Success" },
+	{ NTCONN_METER_ERR_INTERNAL_ERROR, "Internal error" },
+	{ NTCONN_METER_ERR_INVALID_PORT, "Invalid virtual port" },
+	{ NTCONN_METER_ERR_PROFILE_ID, "Profile ID out of range" },
+	{ NTCONN_METER_ERR_POLICY_ID, "Policy ID out of range" },
+	{ NTCONN_METER_ERR_METER_ID, "Meter ID out of range" },
+	{ -1, NULL }
+};
+
+/********************************************************************/
+/* Get error message corresponding to the error code                */
+/********************************************************************/
+static const char *get_error_msg(uint32_t err_code)
+{
+	int idx = 0;
+
+	if (err_code < NTCONN_METER_ERR_INTERNAL_ERROR) {
+		const ntconn_err_t *err_msg = get_ntconn_error(err_code);
+
+		return err_msg->err_text;
+	}
+	while (ntconn_err[idx].err_code != (uint32_t)-1 &&
+			ntconn_err[idx].err_code != err_code)
+		idx++;
+	if (ntconn_err[idx].err_code == (uint32_t)-1)
+		idx = 1;
+	return ntconn_err[idx].err_text;
+}
+
+/*
+ * Filter functions
+ */
+static int func_meter_get_capabilities(void *hdl, int client_id,
+				       struct ntconn_header_s *hdr, char **data,
+				       int *len);
+static int func_meter_setup(void *hdl, int client_id,
+			    struct ntconn_header_s *hdr, char **data, int *len);
+static int func_meter_read(void *hdl, int client_id,
+			   struct ntconn_header_s *hdr, char **data, int *len);
+static struct func_s adapter_entry_funcs[] = {
+	{ "capabilities", NULL, func_meter_get_capabilities },
+	{ "setup", NULL, func_meter_setup },
+	{ "read", NULL, func_meter_read },
+	{ NULL, NULL, NULL },
+};
+
+/**********************************************************************/
+/* copy error message corresponding to the error code to error struct */
+/**********************************************************************/
+static void copy_mtr_error(struct rte_mtr_error *error, uint32_t err)
+{
+	error->type = RTE_MTR_ERROR_TYPE_UNSPECIFIED;
+	error->message = get_error_msg(err);
+	error->cause = NULL;
+}
+
+static int func_meter_get_capabilities(void *hdl _unused, int client_id _unused,
+				       struct ntconn_header_s *hdr _unused,
+				       char **data, int *len)
+{
+	char *saveptr;
+	uint8_t vport = 0;
+	uint8_t port = 0;
+	int status;
+	struct rte_mtr_capabilities cap;
+	struct rte_mtr_error error;
+
+#ifdef DEBUG_METER
+	NT_LOG(DBG, NTCONNECT, "%s: \"%s\"\n", __func__, *data);
+#endif
+
+	char *tok = strtok_r(*data, ",", &saveptr);
+
+	if (tok) {
+		int length = strlen(tok);
+
+		if (length > 6 && memcmp(tok, "vport=", 6) == 0)
+			vport = atoi(tok + 6);
+	}
+#ifdef DEBUG_METER
+	NT_LOG(DBG, NTCONNECT, "vport=%u\n", vport);
+#endif
+
+	if (vport == 0 || vport > 64) {
+		*len = 0;
+		NT_LOG(ERR, NTCONNECT, "Virtual port is invalid");
+		copy_mtr_error(&error, NTCONN_METER_ERR_INVALID_PORT);
+		status = -ENODEV;
+		goto error_get_capa;
+	}
+
+	port = vport & 1;
+	status = rte_mtr_capabilities_get(port, &cap, &error);
+	if (status == 0) {
+		/* Handle success by copying the return values to the return struct */
+		*data = malloc(sizeof(struct meter_capabilities_return_s));
+		if (!*data)
+			goto error_get_capa_malloc;
+		struct meter_capabilities_return_s *return_value =
+			(struct meter_capabilities_return_s *)*data;
+		*len = sizeof(struct meter_capabilities_return_s);
+		memcpy(&return_value->cap, &cap,
+		       sizeof(struct rte_mtr_capabilities));
+		return REQUEST_OK;
+	}
+
+error_get_capa:
+
+	/* Handle errors by copy errors to the error struct */
+	NT_LOG(ERR, NTCONNECT, "Failed to get capabilities for port %u (%u)",
+	       port, vport);
+	*data = malloc(sizeof(struct meter_error_return_s));
+	if (!*data)
+		goto error_get_capa_malloc;
+	struct meter_error_return_s *return_value =
+		(struct meter_error_return_s *)*data;
+	*len = sizeof(struct meter_error_return_s);
+	return_value->status = status;
+	return_value->type = error.type;
+	strlcpy(return_value->err_msg, error.message, ERR_MSG_LEN);
+	return REQUEST_OK;
+
+error_get_capa_malloc:
+
+	*len = 0;
+	return REQUEST_ERR;
+}
+
+static int func_meter_setup(void *hdl _unused, int client_id _unused,
+			    struct ntconn_header_s *hdr, char **data, int *len)
+{
+	char *saveptr;
+	uint8_t port;
+	uint32_t max_id;
+	int status;
+	struct rte_mtr_error error;
+	int command = UNKNOWN_CMD;
+
+#ifdef DEBUG_METER
+	NT_LOG(DBG, NTCONNECT, "%s: \"%s\"\n", __func__, *data);
+#endif
+
+	if (hdr->blob_len != sizeof(struct meter_setup_s)) {
+		*len = 0;
+		NT_LOG(ERR, NTCONNECT, "Error: Profile data size is illegal");
+		copy_mtr_error(&error, NTCONN_ERR_CODE_INVALID_REQUEST);
+		status = -EINTR;
+		goto error_meter_setup;
+	}
+
+	/* Get the data blob containing the data for the meter function */
+	struct meter_setup_s *cpy_data =
+		(struct meter_setup_s *)&(*data)[hdr->len];
+
+	if (cpy_data->vport < 4 || cpy_data->vport > 128) {
+		*len = 0;
+		NT_LOG(ERR, NTCONNECT, "Virtual port is invalid");
+		copy_mtr_error(&error, NTCONN_METER_ERR_INVALID_PORT);
+		status = -ENODEV;
+		goto error_meter_setup;
+	}
+
+	char *tok = strtok_r(*data, ",", &saveptr);
+
+	if (tok) {
+		int length = strlen(tok);
+
+		if (length == 6) {
+			if (memcmp(tok, "addpro", 6) == 0)
+				command = ADD_PROFILE;
+
+			else if (memcmp(tok, "delpro", 6) == 0)
+				command = DEL_PROFILE;
+
+			else if (memcmp(tok, "addpol", 6) == 0)
+				command = ADD_POLICY;
+
+			else if (memcmp(tok, "delpol", 6) == 0)
+				command = DEL_POLICY;
+
+			else if (memcmp(tok, "crtmtr", 6) == 0)
+				command = CREATE_MTR;
+
+			else if (memcmp(tok, "delmtr", 6) == 0)
+				command = DEL_MTR;
+		}
+	}
+
+	if (command == UNKNOWN_CMD) {
+		NT_LOG(ERR, NTCONNECT, "Error: Invalid command");
+		copy_mtr_error(&error, NTCONN_ERR_CODE_INVALID_REQUEST);
+		status = -EINVAL;
+		goto error_meter_setup;
+	}
+
+	/* Port will be either 0 or 1 depending on the VF. */
+	port = cpy_data->vport & 1;
+
+	switch (command) {
+	case ADD_PROFILE:
+		max_id = flow_mtr_meter_policy_n_max() / (RTE_MAX_ETHPORTS - 2);
+		if (cpy_data->id > max_id) {
+			NT_LOG(ERR, NTCONNECT,
+			       "Error: Profile ID %u out of range. Max value is %u",
+			       cpy_data->id, max_id);
+			copy_mtr_error(&error, NTCONN_METER_ERR_PROFILE_ID);
+			status = -EINVAL;
+			goto error_meter_setup;
+		}
+		cpy_data->id = ((cpy_data->vport - 4) * max_id) + cpy_data->id;
+		status = rte_mtr_meter_profile_add(port, cpy_data->id,
+						   &cpy_data->profile, &error);
+		if (status != 0) {
+			NT_LOG(ERR, NTCONNECT,
+			       "Failed to add profile for port %u (%u)", port,
+			       cpy_data->vport);
+		}
+		break;
+	case DEL_PROFILE:
+		max_id = flow_mtr_meter_policy_n_max() / (RTE_MAX_ETHPORTS - 2);
+		if (cpy_data->id > max_id) {
+			NT_LOG(ERR, NTCONNECT,
+			       "Error: Profile ID %u out of range. Max value is %u",
+			       cpy_data->id, max_id);
+			copy_mtr_error(&error, NTCONN_METER_ERR_PROFILE_ID);
+			status = -EINVAL;
+			goto error_meter_setup;
+		}
+		cpy_data->id = ((cpy_data->vport - 4) * max_id) + cpy_data->id;
+		status = rte_mtr_meter_profile_delete(port, cpy_data->id,
+						      &error);
+		if (status != 0) {
+			NT_LOG(ERR, NTCONNECT,
+			       "Failed to delete profile for port %u (%u)",
+			       port, cpy_data->vport);
+		}
+		break;
+	case ADD_POLICY:
+		max_id = flow_mtr_meter_policy_n_max() / (RTE_MAX_ETHPORTS - 2);
+		if (cpy_data->id > max_id) {
+			NT_LOG(ERR, NTCONNECT,
+			       "Error: Policy ID %u out of range. Max value is %u",
+			       cpy_data->id, max_id);
+			copy_mtr_error(&error, NTCONN_METER_ERR_POLICY_ID);
+			status = -EINVAL;
+			goto error_meter_setup;
+		}
+		cpy_data->id = ((cpy_data->vport - 4) * max_id) + cpy_data->id;
+		cpy_data->p.policy.actions[RTE_COLOR_GREEN] =
+			cpy_data->p.actions_green;
+		cpy_data->p.policy.actions[RTE_COLOR_YELLOW] =
+			cpy_data->p.actions_yellow;
+		cpy_data->p.policy.actions[RTE_COLOR_RED] =
+			cpy_data->p.actions_red;
+		status = rte_mtr_meter_policy_add(port, cpy_data->id,
+						  &cpy_data->p.policy, &error);
+		if (status != 0) {
+			NT_LOG(ERR, NTCONNECT,
+			       "Failed to add policy for port %u (%u)", port,
+			       cpy_data->vport);
+		}
+		break;
+	case DEL_POLICY:
+		max_id = flow_mtr_meter_policy_n_max() / (RTE_MAX_ETHPORTS - 2);
+		if (cpy_data->id > max_id) {
+			NT_LOG(ERR, NTCONNECT,
+			       "Error: Policy ID %u out of range. Max value is %u",
+			       cpy_data->id, max_id);
+			copy_mtr_error(&error, NTCONN_METER_ERR_POLICY_ID);
+			status = -EINVAL;
+			goto error_meter_setup;
+		}
+		cpy_data->id = ((cpy_data->vport - 4) * max_id) + cpy_data->id;
+		status =
+			rte_mtr_meter_policy_delete(port, cpy_data->id, &error);
+		if (status != 0) {
+			NT_LOG(ERR, NTCONNECT,
+			       "Failed to delete policy for port %u (%u)", port,
+			       cpy_data->vport);
+		}
+		break;
+	case CREATE_MTR:
+		max_id = flow_mtr_meters_supported() / (RTE_MAX_ETHPORTS - 2);
+		if (cpy_data->id > max_id) {
+			NT_LOG(ERR, NTCONNECT,
+			       "Error: Meter ID %u out of range. Max value is %u",
+			       cpy_data->id, max_id);
+			copy_mtr_error(&error, NTCONN_METER_ERR_METER_ID);
+			status = -EINVAL;
+			goto error_meter_setup;
+		}
+		cpy_data->id = ((cpy_data->vport - 4) * max_id) + cpy_data->id;
+		cpy_data->mtr_params.meter_profile_id =
+			((cpy_data->vport - 4) *
+			 (flow_mtr_meter_policy_n_max() /
+			  (RTE_MAX_ETHPORTS - 2))) +
+			cpy_data->mtr_params.meter_profile_id;
+		cpy_data->mtr_params.meter_policy_id =
+			((cpy_data->vport - 4) *
+			 (flow_mtr_meter_policy_n_max() /
+			  (RTE_MAX_ETHPORTS - 2))) +
+			cpy_data->mtr_params.meter_policy_id;
+		status = rte_mtr_create(port, cpy_data->id,
+					&cpy_data->mtr_params, cpy_data->shared,
+					&error);
+		if (status != 0) {
+			NT_LOG(ERR, NTCONNECT,
+			       "Failed to create meter for port %u (%u)", port,
+			       cpy_data->vport);
+		}
+		break;
+	case DEL_MTR:
+		max_id = flow_mtr_meters_supported() / (RTE_MAX_ETHPORTS - 2);
+		if (cpy_data->id > max_id) {
+			NT_LOG(ERR, NTCONNECT,
+			       "Error: Meter ID %u out of range. Max value is %u",
+			       cpy_data->id, max_id);
+			copy_mtr_error(&error, NTCONN_METER_ERR_METER_ID);
+			status = -EINVAL;
+			goto error_meter_setup;
+		}
+		cpy_data->id = ((cpy_data->vport - 4) * max_id) + cpy_data->id;
+		status = rte_mtr_destroy(port, cpy_data->id, &error);
+		if (status != 0) {
+			NT_LOG(ERR, NTCONNECT,
+			       "Failed to destroy meter for port %u (%u)", port,
+			       cpy_data->vport);
+		}
+		break;
+	}
+
+	if (status == 0) {
+		/* Handle success by copying the return values to the return struct */
+		*data = malloc(sizeof(struct meter_return_s));
+		if (!*data)
+			goto error_meter_setup_malloc;
+		struct meter_return_s *return_value =
+			(struct meter_return_s *)*data;
+		*len = sizeof(struct meter_return_s);
+		return_value->status = 0;
+		return REQUEST_OK;
+	}
+
+error_meter_setup:
+
+	/* Handle errors by copy errors to the error struct */
+	 *data = malloc(sizeof(struct meter_error_return_s));
+	if (!*data)
+		goto error_meter_setup_malloc;
+	struct meter_error_return_s *return_value =
+		(struct meter_error_return_s *)*data;
+	*len = sizeof(struct meter_error_return_s);
+	return_value->status = status;
+	return_value->type = error.type;
+	strlcpy(return_value->err_msg, error.message, ERR_MSG_LEN);
+	return REQUEST_OK;
+
+error_meter_setup_malloc:
+
+	*len = 0;
+	return REQUEST_ERR;
+}
+
+static int func_meter_read(void *hdl _unused, int client_id _unused,
+			   struct ntconn_header_s *hdr, char **data, int *len)
+{
+	uint8_t port = 0;
+	int status;
+	struct rte_mtr_error error;
+	struct rte_mtr_stats stats;
+	uint64_t stats_mask;
+
+#ifdef DEBUG_METER
+	NT_LOG(DBG, NTCONNECT, "%s: [%s:%u] enter\n", __func__, __FILE__, __LINE__);
+#endif
+
+	if (hdr->blob_len != sizeof(struct meter_get_stat_s)) {
+		*len = 0;
+		NT_LOG(ERR, NTCONNECT,
+		       "Error: Read meter stats data size is illegal");
+		copy_mtr_error(&error, NTCONN_ERR_CODE_INVALID_REQUEST);
+		status = -EINTR;
+		goto error_meter_read;
+	}
+
+	/* Get the data blob containing the data for the meter function */
+	struct meter_get_stat_s *cpy_data =
+		(struct meter_get_stat_s *)&(*data)[hdr->len];
+
+	if (cpy_data->vport < 4 || cpy_data->vport > 128) {
+		*len = 0;
+		NT_LOG(ERR, NTCONNECT, "Virtual port is invalid");
+		copy_mtr_error(&error, NTCONN_METER_ERR_INVALID_PORT);
+		status = -ENODEV;
+		goto error_meter_read;
+	}
+
+	port = cpy_data->vport & 1;
+	cpy_data->mtr_id =
+		((cpy_data->vport - 4) *
+		 (flow_mtr_meters_supported() / (RTE_MAX_ETHPORTS - 2))) +
+		cpy_data->mtr_id;
+	status = rte_mtr_stats_read(port, cpy_data->mtr_id, &stats, &stats_mask,
+				    cpy_data->clear, &error);
+	if (status == 0) {
+		/* Handle success by copying the return values to the return struct */
+		*data = malloc(sizeof(struct meter_return_stat_s));
+		if (!*data)
+			goto error_meter_read_malloc;
+		struct meter_return_stat_s *return_value =
+			(struct meter_return_stat_s *)*data;
+		*len = sizeof(struct meter_return_stat_s);
+		return_value->stats_mask = stats_mask;
+		memcpy(&return_value->stats, &stats,
+		       sizeof(struct rte_mtr_stats));
+		return REQUEST_OK;
+	}
+
+error_meter_read:
+	/* Handle errors by copy errors to the error struct */
+	NT_LOG(ERR, NTCONNECT, "Failed to read meter stats");
+	*data = malloc(sizeof(struct meter_error_return_s));
+	if (!*data)
+		goto error_meter_read_malloc;
+	struct meter_error_return_s *return_value =
+		(struct meter_error_return_s *)*data;
+	*len = sizeof(struct meter_error_return_s);
+	strlcpy(return_value->err_msg, error.message, ERR_MSG_LEN);
+	return_value->status = status;
+	return_value->type = error.type;
+	return REQUEST_OK;
+
+error_meter_read_malloc:
+	*len = 0;
+	return REQUEST_ERR;
+}
+
+static int meter_request(void *hdl, int client_id _unused,
+			 struct ntconn_header_s *hdr, char *function,
+			 char **data, int *len)
+{
+	return execute_function(this_module_name, hdl, client_id, hdr, function,
+				adapter_entry_funcs, data, len, 0);
+}
+
+static void meter_free_data(void *hdl _unused, char *data)
+{
+	if (data)
+		free(data);
+}
+
+static void meter_client_cleanup(void *hdl _unused, int client_id _unused)
+{
+	/* Nothing to do */
+}
+
+static const ntconnapi_t ntconn_meter_op = { this_module_name,
+					     NTCONN_METER_VERSION_MAJOR,
+					     NTCONN_METER_VERSION_MINOR,
+					     meter_request,
+					     meter_free_data,
+					     meter_client_cleanup
+					   };
+
+int ntconn_meter_register(struct drv_s *drv)
+{
+	int i;
+
+	for (i = 0; i < MAX_CLIENTS; i++) {
+		if (meter_hdl[i].drv == NULL)
+			break;
+	}
+	if (i == MAX_CLIENTS) {
+		NT_LOG(ERR, NTCONNECT,
+		       "Cannot register more adapters into NtConnect framework");
+		return -1;
+	}
+
+	meter_hdl[i].drv = drv;
+	return register_ntconn_mod(&drv->p_dev->addr, (void *)&meter_hdl[i],
+				   &ntconn_meter_op);
+}
diff --git a/drivers/net/ntnic/ntconnect_modules/ntconn_modules.h b/drivers/net/ntnic/ntconnect_modules/ntconn_modules.h
new file mode 100644
index 0000000000..ea379015fe
--- /dev/null
+++ b/drivers/net/ntnic/ntconnect_modules/ntconn_modules.h
@@ -0,0 +1,19 @@ 
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#ifndef _NTCONN_MODULES_H_
+#define _NTCONN_MODULES_H_
+
+#include "ntos_system.h"
+
+/*
+ * All defined NT connection modules
+ */
+int ntconn_adap_register(struct drv_s *drv);
+int ntconn_stat_register(struct drv_s *drv);
+int ntconn_flow_register(struct drv_s *drv);
+int ntconn_meter_register(struct drv_s *drv);
+int ntconn_test_register(struct drv_s *drv);
+
+#endif /* _NTCONN_MODULES_H_ */
diff --git a/drivers/net/ntnic/ntconnect_modules/ntconn_stat.c b/drivers/net/ntnic/ntconnect_modules/ntconn_stat.c
new file mode 100644
index 0000000000..1b3e59fcc1
--- /dev/null
+++ b/drivers/net/ntnic/ntconnect_modules/ntconn_stat.c
@@ -0,0 +1,876 @@ 
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#include <rte_dev.h>
+#include <rte_bus_pci.h>
+#include <ethdev_pci.h>
+#include <rte_ethdev.h>
+
+#include "ntconnect.h"
+#include "ntconnect_api_statistic.h"
+#include "ntos_system.h"
+#include "ntconn_modules.h"
+#include "ntconn_mod_helper.h"
+#include "nt_util.h"
+#include "ntlog.h"
+#include "ntnic_xstats.h"
+
+#define STAT_VERSION_MAJOR 0U
+#define STAT_VERSION_MINOR 2U
+
+#define this_module_name "stat"
+
+/*
+ * Supported Stat Layout Versions
+ */
+#define NUM_LAYOUT_VERSIONS_SUPPORTED (RTE_DIM(layout_versions_supported))
+static int layout_versions_supported[] = {
+	6,
+	/*
+	 * Add here other layout versions to support
+	 * When more versions are added, add new version dependent binary reply structures
+	 * in ntconnect_api.h file for client to select on reading layout_version
+	 */
+};
+
+enum snap_addr_select_e {
+	SNAP_COLORS,
+	SNAP_QUEUES,
+	SNAP_RX_PORT,
+	SNAP_TX_PORT,
+	SNAP_ADDR_COUNT
+};
+
+struct snap_addr_s {
+	const uint64_t *ptr;
+	unsigned int size;
+};
+
+struct snaps_s {
+	int client_id;
+	/* Pointers into buffer */
+	struct snap_addr_s snap_addr[SNAP_ADDR_COUNT];
+	uint64_t *buffer;
+	struct snaps_s *next;
+};
+
+static struct stat_hdl {
+	struct drv_s *drv;
+	nt4ga_stat_t *p_nt4ga_stat;
+	struct snaps_s *snaps_base;
+} stat_hdl;
+
+enum stat_type_e {
+	STAT_TYPE_COLOR,
+	STAT_TYPE_QUEUE,
+	STAT_TYPE_RX,
+	STAT_TYPE_TX,
+	STAT_TYPE_FLOWMATCHER
+};
+
+static int func_get_snap_colors(void *hdl, int client_id,
+				struct ntconn_header_s *hdr, char **data,
+				int *len);
+static int func_get_snap_queues(void *hdl, int client_id,
+				struct ntconn_header_s *hdr, char **data,
+				int *len);
+static int func_get_snap_rx_port(void *hdl, int client_id,
+				 struct ntconn_header_s *hdr, char **data,
+				 int *len);
+static int func_get_snap_tx_port(void *hdl, int client_id,
+				 struct ntconn_header_s *hdr, char **data,
+				 int *len);
+static struct func_s func_snap_level2[] = {
+	{ "colors", NULL, func_get_snap_colors },
+	{ "queues", NULL, func_get_snap_queues },
+	{ "rx_counters", NULL, func_get_snap_rx_port },
+	{ "tx_counters", NULL, func_get_snap_tx_port },
+	{ NULL, NULL, NULL },
+};
+
+static int func_get_layout_version(void *hdl, int client_id,
+				   struct ntconn_header_s *hdr, char **data,
+				   int *len);
+static int func_get_flm(void *hdl, int client_id, struct ntconn_header_s *hdr,
+			char **data, int *len);
+static int func_get_color(void *hdl, int client_id, struct ntconn_header_s *hdr,
+			  char **data, int *len);
+static int func_get_queue(void *hdl, int client_id, struct ntconn_header_s *hdr,
+			  char **data, int *len);
+static int func_get_rx_counters(void *hdl, int client_id,
+				struct ntconn_header_s *hdr, char **data,
+				int *len);
+static int func_get_tx_counters(void *hdl, int client_id,
+				struct ntconn_header_s *hdr, char **data,
+				int *len);
+static int func_get_flm_layout_version(void *hdl, int client_id,
+				       struct ntconn_header_s *hdr, char **data,
+				       int *len);
+
+static struct func_s funcs_get_level1[] = {
+	{ "snapshot", func_snap_level2, NULL },
+	{ "layout_version", NULL, func_get_layout_version },
+	{ "flm", NULL, func_get_flm },
+	{ "colors", NULL, func_get_color },
+	{ "queues", NULL, func_get_queue },
+	{ "rx_counters", NULL, func_get_rx_counters },
+	{ "tx_counters", NULL, func_get_tx_counters },
+	{ "flm_layout_version", NULL, func_get_flm_layout_version },
+	{ NULL, NULL, NULL },
+};
+
+/*
+ * Entry level
+ */
+static int func_snapshot(void *hdl, int client_id, struct ntconn_header_s *hdr,
+			 char **data, int *len);
+static struct func_s stat_entry_funcs[] = {
+	{ "get", funcs_get_level1, NULL },
+	{ "snapshot", NULL, func_snapshot },
+	{ NULL, NULL, NULL },
+};
+
+static int read_flm(nt4ga_stat_t *hwstat, uint64_t *val, int nbc)
+{
+	struct ntc_stat_get_data_s *cdata = (struct ntc_stat_get_data_s *)val;
+
+	cdata->nb_counters = (uint64_t)nbc;
+	cdata->timestamp = hwstat->last_timestamp;
+	cdata->is_virt = hwstat->mp_nthw_stat->mb_is_vswitch;
+
+	struct rte_eth_xstat stats[100];
+	struct rte_eth_xstat_name names[100];
+	int cnt_names = nthw_xstats_get_names(hwstat, names, 100,
+					      hwstat->mp_nthw_stat->mb_is_vswitch);
+	int cnt_values = nthw_xstats_get(hwstat, stats, 100,
+					 hwstat->mp_nthw_stat->mb_is_vswitch, 0);
+	assert(cnt_names == cnt_values);
+
+	/* virt/cap same */
+	struct flowmatcher_type_fields_s *flm =
+		(struct flowmatcher_type_fields_s *)cdata->data;
+	if (hwstat->mp_stat_structs_flm) {
+		int c;
+
+		for (c = 0; c < nbc; c++) {
+			flm->current = hwstat->mp_stat_structs_flm->current;
+			flm->learn_done = hwstat->mp_stat_structs_flm->learn_done;
+			flm->learn_ignore =
+				hwstat->mp_stat_structs_flm->learn_ignore;
+			flm->learn_fail = hwstat->mp_stat_structs_flm->learn_fail;
+			flm->unlearn_done =
+				hwstat->mp_stat_structs_flm->unlearn_done;
+			flm->unlearn_ignore =
+				hwstat->mp_stat_structs_flm->unlearn_ignore;
+			flm->auto_unlearn_done =
+				hwstat->mp_stat_structs_flm->auto_unlearn_done;
+			flm->auto_unlearn_ignore =
+				hwstat->mp_stat_structs_flm->auto_unlearn_ignore;
+			flm->auto_unlearn_fail =
+				hwstat->mp_stat_structs_flm->auto_unlearn_fail;
+			flm->timeout_unlearn_done =
+				hwstat->mp_stat_structs_flm->timeout_unlearn_done;
+			flm->rel_done = hwstat->mp_stat_structs_flm->rel_done;
+			flm->rel_ignore = hwstat->mp_stat_structs_flm->rel_ignore;
+			flm->prb_done = hwstat->mp_stat_structs_flm->prb_done;
+			flm->prb_ignore = hwstat->mp_stat_structs_flm->prb_ignore;
+
+			flm->sta_done = hwstat->mp_stat_structs_flm->sta_done;
+			flm->inf_done = hwstat->mp_stat_structs_flm->inf_done;
+			flm->inf_skip = hwstat->mp_stat_structs_flm->inf_skip;
+			flm->pck_hit = hwstat->mp_stat_structs_flm->pck_hit;
+			flm->pck_miss = hwstat->mp_stat_structs_flm->pck_miss;
+			flm->pck_unh = hwstat->mp_stat_structs_flm->pck_unh;
+			flm->pck_dis = hwstat->mp_stat_structs_flm->pck_dis;
+			flm->csh_hit = hwstat->mp_stat_structs_flm->csh_hit;
+			flm->csh_miss = hwstat->mp_stat_structs_flm->csh_miss;
+			flm->csh_unh = hwstat->mp_stat_structs_flm->csh_unh;
+			flm->cuc_start = hwstat->mp_stat_structs_flm->cuc_start;
+			flm->cuc_move = hwstat->mp_stat_structs_flm->cuc_move;
+		}
+	} else {
+		memset(flm, 0, sizeof(*hwstat->mp_stat_structs_flm));
+	}
+	return nbc * NUM_STAT_RECORD_TYPE_FLOWMATCHER + STAT_INFO_ELEMENTS;
+}
+
+static int read_colors(nt4ga_stat_t *hwstat, uint64_t *val, int nbc)
+{
+	struct ntc_stat_get_data_s *cdata = (struct ntc_stat_get_data_s *)val;
+
+	cdata->nb_counters = (uint64_t)nbc;
+	cdata->timestamp = hwstat->last_timestamp;
+	cdata->is_virt = hwstat->mp_nthw_stat->mb_is_vswitch;
+
+	/* virt/cap same */
+	struct color_type_fields_s *clr =
+		(struct color_type_fields_s *)cdata->data;
+	int c;
+
+	for (c = 0; c < nbc; c++) {
+		clr->pkts = hwstat->mp_stat_structs_color[c].color_packets;
+		clr->octets = hwstat->mp_stat_structs_color[c].color_bytes;
+		clr->tcp_flgs =
+			(uint64_t)hwstat->mp_stat_structs_color[c].tcp_flags;
+		clr++;
+	}
+	return nbc * NUM_STAT_RECORD_TYPE_COLOR + STAT_INFO_ELEMENTS;
+}
+
+static int read_queues(nt4ga_stat_t *hwstat, uint64_t *val, int nbq)
+{
+	struct ntc_stat_get_data_s *qdata = (struct ntc_stat_get_data_s *)val;
+
+	qdata->nb_counters = (uint64_t)nbq;
+	qdata->timestamp = hwstat->last_timestamp;
+	qdata->is_virt = hwstat->mp_nthw_stat->mb_is_vswitch;
+
+	/* virt/cap same */
+	struct queue_type_fields_s *que =
+		(struct queue_type_fields_s *)qdata->data;
+	int q;
+
+	for (q = 0; q < nbq; q++) {
+		que->flush_pkts = hwstat->mp_stat_structs_hb[q].flush_packets;
+		que->drop_pkts = hwstat->mp_stat_structs_hb[q].drop_packets;
+		que->fwd_pkts = hwstat->mp_stat_structs_hb[q].fwd_packets;
+		que->dbs_drop_pkts = hwstat->mp_stat_structs_hb[q].dbs_drop_packets;
+		que->flush_octets = hwstat->mp_stat_structs_hb[q].flush_bytes;
+		que->drop_octets = hwstat->mp_stat_structs_hb[q].drop_bytes;
+		que->fwd_octets = hwstat->mp_stat_structs_hb[q].fwd_bytes;
+		que->dbs_drop_octets = hwstat->mp_stat_structs_hb[q].dbs_drop_bytes;
+		que++;
+	}
+	return nbq * NUM_STAT_RECORD_TYPE_QUEUE + STAT_INFO_ELEMENTS;
+}
+
+static void copy_rmon_stat(struct port_counters_v2 *cptr,
+			    struct stat_rmon_s *rmon)
+{
+	rmon->drop_events = cptr->drop_events;
+	rmon->pkts = cptr->pkts;
+	rmon->octets = cptr->octets;
+	rmon->broadcast_pkts = cptr->broadcast_pkts;
+	rmon->multicast_pkts = cptr->multicast_pkts;
+	rmon->unicast_pkts = cptr->unicast_pkts;
+	rmon->pkts_alignment = cptr->pkts_alignment;
+	rmon->pkts_code_violation = cptr->pkts_code_violation;
+	rmon->pkts_crc = cptr->pkts_crc;
+	rmon->undersize_pkts = cptr->undersize_pkts;
+	rmon->oversize_pkts = cptr->oversize_pkts;
+	rmon->fragments = cptr->fragments;
+	rmon->jabbers_not_truncated = cptr->jabbers_not_truncated;
+	rmon->jabbers_truncated = cptr->jabbers_truncated;
+	rmon->pkts_64_octets = cptr->pkts_64_octets;
+	rmon->pkts_65_to_127_octets = cptr->pkts_65_to_127_octets;
+	rmon->pkts_128_to_255_octets = cptr->pkts_128_to_255_octets;
+	rmon->pkts_256_to_511_octets = cptr->pkts_256_to_511_octets;
+	rmon->pkts_512_to_1023_octets = cptr->pkts_512_to_1023_octets;
+	rmon->pkts_1024_to_1518_octets = cptr->pkts_1024_to_1518_octets;
+	rmon->pkts_1519_to_2047_octets = cptr->pkts_1519_to_2047_octets;
+	rmon->pkts_2048_to_4095_octets = cptr->pkts_2048_to_4095_octets;
+	rmon->pkts_4096_to_8191_octets = cptr->pkts_4096_to_8191_octets;
+	rmon->pkts_8192_to_max_octets = cptr->pkts_8192_to_max_octets;
+}
+
+static int read_rx_counters(nt4ga_stat_t *hwstat, uint64_t *val, int nbp)
+{
+	struct ntc_stat_get_data_s *rxdata = (struct ntc_stat_get_data_s *)val;
+
+	rxdata->nb_counters = (uint64_t)nbp;
+	rxdata->timestamp = hwstat->last_timestamp;
+	rxdata->is_virt = hwstat->mp_nthw_stat->mb_is_vswitch;
+
+	if (rxdata->is_virt) {
+		struct rtx_type_fields_virt_s *rxc =
+			(struct rtx_type_fields_virt_s *)rxdata->data;
+		int p;
+
+		for (p = 0; p < nbp; p++) {
+			rxc->octets =
+				hwstat->virt.mp_stat_structs_port_rx[p].octets;
+			rxc->pkts = hwstat->virt.mp_stat_structs_port_rx[p].pkts;
+			rxc->drop_events =
+				hwstat->virt.mp_stat_structs_port_rx[p].drop_events;
+			rxc->qos_drop_octets =
+				hwstat->virt.mp_stat_structs_port_rx[p]
+				.qos_drop_octets;
+			rxc->qos_drop_pkts = hwstat->virt.mp_stat_structs_port_rx[p]
+					     .qos_drop_pkts;
+			rxc++;
+		}
+		return nbp * NUM_STAT_RECORD_TYPE_RX_PORT_VIRT +
+		       STAT_INFO_ELEMENTS;
+	} else {
+		struct rx_type_fields_cap_s *rxc =
+			(struct rx_type_fields_cap_s *)rxdata->data;
+		int p;
+
+		for (p = 0; p < nbp; p++) {
+			copy_rmon_stat(&hwstat->cap.mp_stat_structs_port_rx[p],
+					&rxc->rmon);
+
+			/* Rx only port counters */
+			rxc->mac_drop_events =
+				hwstat->cap.mp_stat_structs_port_rx[p]
+				.mac_drop_events;
+			rxc->pkts_lr =
+				hwstat->cap.mp_stat_structs_port_rx[p].pkts_lr;
+			rxc->duplicate =
+				hwstat->cap.mp_stat_structs_port_rx[p].duplicate;
+			rxc->pkts_ip_chksum_error =
+				hwstat->cap.mp_stat_structs_port_rx[p]
+				.pkts_ip_chksum_error;
+			rxc->pkts_udp_chksum_error =
+				hwstat->cap.mp_stat_structs_port_rx[p]
+				.pkts_udp_chksum_error;
+			rxc->pkts_tcp_chksum_error =
+				hwstat->cap.mp_stat_structs_port_rx[p]
+				.pkts_tcp_chksum_error;
+			rxc->pkts_giant_undersize =
+				hwstat->cap.mp_stat_structs_port_rx[p]
+				.pkts_giant_undersize;
+			rxc->pkts_baby_giant =
+				hwstat->cap.mp_stat_structs_port_rx[p]
+				.pkts_baby_giant;
+			rxc->pkts_not_isl_vlan_mpls =
+				hwstat->cap.mp_stat_structs_port_rx[p]
+				.pkts_not_isl_vlan_mpls;
+			rxc->pkts_isl =
+				hwstat->cap.mp_stat_structs_port_rx[p].pkts_isl;
+			rxc->pkts_vlan =
+				hwstat->cap.mp_stat_structs_port_rx[p].pkts_vlan;
+			rxc->pkts_isl_vlan =
+				hwstat->cap.mp_stat_structs_port_rx[p].pkts_isl_vlan;
+			rxc->pkts_mpls =
+				hwstat->cap.mp_stat_structs_port_rx[p].pkts_mpls;
+			rxc->pkts_isl_mpls =
+				hwstat->cap.mp_stat_structs_port_rx[p].pkts_isl_mpls;
+			rxc->pkts_vlan_mpls = hwstat->cap.mp_stat_structs_port_rx[p]
+					      .pkts_vlan_mpls;
+			rxc->pkts_isl_vlan_mpls =
+				hwstat->cap.mp_stat_structs_port_rx[p]
+				.pkts_isl_vlan_mpls;
+			rxc->pkts_no_filter = hwstat->cap.mp_stat_structs_port_rx[p]
+					      .pkts_no_filter;
+			rxc->pkts_dedup_drop =
+				hwstat->cap.mp_stat_structs_port_rx[p]
+				.pkts_dedup_drop;
+			rxc->pkts_filter_drop =
+				hwstat->cap.mp_stat_structs_port_rx[p]
+				.pkts_filter_drop;
+			rxc->pkts_overflow =
+				hwstat->cap.mp_stat_structs_port_rx[p].pkts_overflow;
+			rxc->pkts_dbs_drop =
+				hwstat->cap.mp_stat_structs_port_rx[p].pkts_dbs_drop;
+			rxc->octets_no_filter =
+				hwstat->cap.mp_stat_structs_port_rx[p]
+				.octets_no_filter;
+			rxc->octets_dedup_drop =
+				hwstat->cap.mp_stat_structs_port_rx[p]
+				.octets_dedup_drop;
+			rxc->octets_filter_drop =
+				hwstat->cap.mp_stat_structs_port_rx[p]
+				.octets_filter_drop;
+			rxc->octets_overflow =
+				hwstat->cap.mp_stat_structs_port_rx[p]
+				.octets_overflow;
+			rxc->octets_dbs_drop =
+				hwstat->cap.mp_stat_structs_port_rx[p]
+				.octets_dbs_drop;
+			rxc->ipft_first_hit = hwstat->cap.mp_stat_structs_port_rx[p]
+					      .ipft_first_hit;
+			rxc->ipft_first_not_hit =
+				hwstat->cap.mp_stat_structs_port_rx[p]
+				.ipft_first_not_hit;
+			rxc->ipft_mid_hit =
+				hwstat->cap.mp_stat_structs_port_rx[p].ipft_mid_hit;
+			rxc->ipft_mid_not_hit =
+				hwstat->cap.mp_stat_structs_port_rx[p]
+				.ipft_mid_not_hit;
+			rxc->ipft_last_hit =
+				hwstat->cap.mp_stat_structs_port_rx[p].ipft_last_hit;
+			rxc->ipft_last_not_hit =
+				hwstat->cap.mp_stat_structs_port_rx[p]
+				.ipft_last_not_hit;
+			rxc++;
+		}
+		return nbp * NUM_STAT_RECORD_TYPE_RX_PORT_CAP +
+		       STAT_INFO_ELEMENTS;
+	}
+}
+
+static int read_tx_counters(nt4ga_stat_t *hwstat, uint64_t *val, int nbp)
+{
+	struct ntc_stat_get_data_s *txdata = (struct ntc_stat_get_data_s *)val;
+
+	txdata->nb_counters = (uint64_t)nbp;
+	txdata->timestamp = hwstat->last_timestamp;
+	txdata->is_virt = hwstat->mp_nthw_stat->mb_is_vswitch;
+
+	if (txdata->is_virt) {
+		struct rtx_type_fields_virt_s *txc =
+			(struct rtx_type_fields_virt_s *)txdata->data;
+		int p;
+
+		for (p = 0; p < nbp; p++) {
+			txc->octets =
+				hwstat->virt.mp_stat_structs_port_tx[p].octets;
+			txc->pkts = hwstat->virt.mp_stat_structs_port_tx[p].pkts;
+			txc->drop_events =
+				hwstat->virt.mp_stat_structs_port_tx[p].drop_events;
+			txc->qos_drop_octets =
+				hwstat->virt.mp_stat_structs_port_tx[p]
+				.qos_drop_octets;
+			txc->qos_drop_pkts = hwstat->virt.mp_stat_structs_port_tx[p]
+					     .qos_drop_pkts;
+			txc++;
+		}
+		return nbp * NUM_STAT_RECORD_TYPE_TX_PORT_VIRT +
+		       STAT_INFO_ELEMENTS;
+	} else {
+		struct tx_type_fields_cap_s *txc =
+			(struct tx_type_fields_cap_s *)txdata->data;
+		int p;
+
+		for (p = 0; p < nbp; p++) {
+			copy_rmon_stat(&hwstat->cap.mp_stat_structs_port_tx[p],
+					&txc->rmon);
+			txc->rmon.pkts = hwstat->a_port_tx_packets_total[p];
+			txc++;
+		}
+		return nbp * NUM_STAT_RECORD_TYPE_TX_PORT_CAP +
+		       STAT_INFO_ELEMENTS;
+	}
+}
+
+static int func_get_layout_version(void *hdl, int client_id _unused,
+				   struct ntconn_header_s *hdr _unused,
+				   char **data, int *len)
+{
+	struct stat_hdl *stat = (struct stat_hdl *)hdl;
+
+	if (!stat->p_nt4ga_stat || !stat->p_nt4ga_stat->mp_nthw_stat) {
+		*data = NULL;
+		*len = 0;
+		return REQUEST_ERR;
+	}
+	*data = malloc(sizeof(int));
+	if (!*data) {
+		*len = 0;
+		NT_LOG(ERR, NTCONNECT, "memory allocation failed");
+		return REQUEST_ERR;
+	}
+
+	*(int *)*data = stat->p_nt4ga_stat->mp_nthw_stat->mn_stat_layout_version;
+	*len = sizeof(int);
+	return REQUEST_OK;
+}
+
+static int func_get_flm_layout_version(void *hdl, int client_id _unused,
+				       struct ntconn_header_s *hdr _unused,
+				       char **data, int *len)
+{
+	struct stat_hdl *stat = (struct stat_hdl *)hdl;
+
+	if (!stat->p_nt4ga_stat || !stat->p_nt4ga_stat->mp_nthw_stat) {
+		*data = NULL;
+		*len = 0;
+		return REQUEST_ERR;
+	}
+	*data = malloc(sizeof(int));
+	if (!*data) {
+		*len = 0;
+		NT_LOG(ERR, NTCONNECT, "memory allocation failed");
+		return REQUEST_ERR;
+	}
+
+	*(int *)*data = (stat->p_nt4ga_stat->flm_stat_ver < 18) ? 1 : 2;
+	*len = sizeof(int);
+	return REQUEST_OK;
+}
+
+/*
+ * Return total number of 64bit counters occupied by this stat type
+ * additionally, returns total number of records for this type (ie number of queues, ports, etc)
+ */
+static int get_size(struct stat_hdl *stat, enum stat_type_e type,
+		     int *num_records)
+{
+	int nrec, size;
+
+	switch (type) {
+	case STAT_TYPE_COLOR:
+		nrec = stat->p_nt4ga_stat->mp_nthw_stat->m_nb_color_counters / 2;
+		size = nrec * NUM_STAT_RECORD_TYPE_COLOR;
+		break;
+	case STAT_TYPE_QUEUE:
+		nrec = stat->p_nt4ga_stat->mp_nthw_stat->m_nb_rx_host_buffers;
+		size = nrec * NUM_STAT_RECORD_TYPE_QUEUE;
+		break;
+	case STAT_TYPE_RX:
+		nrec = stat->p_nt4ga_stat->mn_rx_ports;
+		size = nrec * ((stat->p_nt4ga_stat->mp_nthw_stat->mb_is_vswitch) ?
+			       NUM_STAT_RECORD_TYPE_RX_PORT_VIRT :
+			       NUM_STAT_RECORD_TYPE_RX_PORT_CAP);
+		break;
+	case STAT_TYPE_TX:
+		nrec = stat->p_nt4ga_stat->mn_tx_ports;
+		size = nrec * ((stat->p_nt4ga_stat->mp_nthw_stat->mb_is_vswitch) ?
+			       NUM_STAT_RECORD_TYPE_TX_PORT_VIRT :
+			       NUM_STAT_RECORD_TYPE_TX_PORT_CAP);
+		break;
+	case STAT_TYPE_FLOWMATCHER:
+		nrec = 1;
+		size = nrec * NUM_STAT_RECORD_TYPE_FLOWMATCHER;
+		break;
+	}
+
+	*num_records = nrec;
+	return size + STAT_INFO_ELEMENTS;
+}
+
+static int do_get_stats(struct stat_hdl *stat, char **data, int *len,
+			enum stat_type_e stype,
+			int (*read_counters)(nt4ga_stat_t *, uint64_t *, int))
+{
+	int nbg;
+	int size = get_size(stat, stype, &nbg);
+
+	size *= sizeof(uint64_t);
+	uint64_t *val = (uint64_t *)malloc(size);
+
+	if (!val) {
+		*len = 0;
+		NT_LOG(ERR, NTCONNECT, "memory allocation failed");
+		return REQUEST_ERR;
+	}
+
+	pthread_mutex_lock(&stat->drv->ntdrv.stat_lck);
+	read_counters(stat->p_nt4ga_stat, val, nbg);
+	pthread_mutex_unlock(&stat->drv->ntdrv.stat_lck);
+
+	*data = (char *)val;
+	*len = size;
+	return REQUEST_OK;
+}
+
+/*
+ * Stat Request functions
+ */
+static int func_get_flm(void *hdl, int client_id _unused,
+			struct ntconn_header_s *hdr _unused, char **data,
+			int *len)
+{
+	struct stat_hdl *stat = (struct stat_hdl *)hdl;
+
+	if (!stat->p_nt4ga_stat || !stat->p_nt4ga_stat->mp_nthw_stat) {
+		*data = NULL;
+		*len = 0;
+		return REQUEST_ERR;
+	}
+	return do_get_stats(stat, data, len, STAT_TYPE_FLOWMATCHER, read_flm);
+}
+
+static int func_get_color(void *hdl, int client_id _unused,
+			  struct ntconn_header_s *hdr _unused, char **data,
+			  int *len)
+{
+	struct stat_hdl *stat = (struct stat_hdl *)hdl;
+
+	if (!stat->p_nt4ga_stat || !stat->p_nt4ga_stat->mp_nthw_stat) {
+		*data = NULL;
+		*len = 0;
+		return REQUEST_ERR;
+	}
+	return do_get_stats(stat, data, len, STAT_TYPE_COLOR, read_colors);
+}
+
+static int func_get_queue(void *hdl, int client_id _unused,
+			  struct ntconn_header_s *hdr _unused, char **data,
+			  int *len)
+{
+	struct stat_hdl *stat = (struct stat_hdl *)hdl;
+
+	if (!stat->p_nt4ga_stat || !stat->p_nt4ga_stat->mp_nthw_stat) {
+		*data = NULL;
+		*len = 0;
+		return REQUEST_ERR;
+	}
+	return do_get_stats(stat, data, len, STAT_TYPE_QUEUE, read_queues);
+}
+
+static int func_get_rx_counters(void *hdl, int client_id _unused,
+				struct ntconn_header_s *hdr _unused,
+				char **data, int *len)
+{
+	struct stat_hdl *stat = (struct stat_hdl *)hdl;
+
+	if (!stat->p_nt4ga_stat || !stat->p_nt4ga_stat->mp_nthw_stat) {
+		*data = NULL;
+		*len = 0;
+		return REQUEST_ERR;
+	}
+	return do_get_stats(stat, data, len, STAT_TYPE_RX, read_rx_counters);
+}
+
+static int func_get_tx_counters(void *hdl, int client_id _unused,
+				struct ntconn_header_s *hdr _unused,
+				char **data, int *len)
+{
+	struct stat_hdl *stat = (struct stat_hdl *)hdl;
+
+	if (!stat->p_nt4ga_stat || !stat->p_nt4ga_stat->mp_nthw_stat) {
+		*data = NULL;
+		*len = 0;
+		return REQUEST_ERR;
+	}
+	return do_get_stats(stat, data, len, STAT_TYPE_TX, read_tx_counters);
+}
+
+/*
+ * Snapshot handling. This is to ensure atomic reading of all statistics in one collection
+ */
+
+static struct snaps_s *find_client_snap_data(struct stat_hdl *stat,
+		int client_id,
+		struct snaps_s **parent)
+{
+	struct snaps_s *snaps = stat->snaps_base;
+
+	if (parent)
+		*parent = NULL;
+	while (snaps && snaps->client_id != client_id) {
+		if (parent)
+			*parent = snaps;
+		snaps = snaps->next;
+	}
+
+	return snaps;
+}
+
+static struct snaps_s *get_client_snap_data(struct stat_hdl *stat,
+		int client_id)
+{
+	struct snaps_s *snaps = find_client_snap_data(stat, client_id, NULL);
+
+	if (!snaps) {
+		snaps = malloc(sizeof(struct snaps_s)); /* return NULL on malloc failure */
+		if (snaps) {
+			snaps->client_id = client_id;
+			snaps->next = stat->snaps_base;
+			stat->snaps_base = snaps;
+			snaps->buffer = NULL;
+		}
+	}
+	return snaps;
+}
+
+static int func_snapshot(void *hdl, int client_id,
+			 struct ntconn_header_s *hdr _unused, char **data,
+			 int *len)
+{
+	struct stat_hdl *stat = (struct stat_hdl *)hdl;
+	int nbc, nbq, nbpr, nbpt;
+	struct snaps_s *snaps;
+
+	if (!stat->p_nt4ga_stat || !stat->p_nt4ga_stat->mp_nthw_stat) {
+		*data = NULL;
+		*len = 0;
+		return REQUEST_ERR;
+	}
+	snaps = get_client_snap_data(stat, client_id);
+	if (!snaps)
+		goto err_out;
+
+	if (snaps->buffer)
+		free(snaps->buffer);
+
+	snaps->snap_addr[SNAP_COLORS].size =
+		(unsigned int)get_size(stat, STAT_TYPE_COLOR, &nbc);
+	snaps->snap_addr[SNAP_QUEUES].size =
+		(unsigned int)get_size(stat, STAT_TYPE_QUEUE, &nbq);
+	snaps->snap_addr[SNAP_RX_PORT].size =
+		(unsigned int)get_size(stat, STAT_TYPE_RX, &nbpr);
+	snaps->snap_addr[SNAP_TX_PORT].size =
+		(unsigned int)get_size(stat, STAT_TYPE_TX, &nbpt);
+
+	unsigned int tot_size = snaps->snap_addr[SNAP_COLORS].size +
+				snaps->snap_addr[SNAP_QUEUES].size +
+				snaps->snap_addr[SNAP_RX_PORT].size +
+				snaps->snap_addr[SNAP_TX_PORT].size;
+
+	snaps->buffer = malloc(tot_size * sizeof(uint64_t));
+	if (!snaps->buffer) {
+		*len = 0;
+		NT_LOG(ERR, NTCONNECT, "memory allocation failed");
+		return REQUEST_ERR;
+	}
+	uint64_t *val = snaps->buffer;
+
+	snaps->snap_addr[SNAP_COLORS].ptr = val;
+	pthread_mutex_lock(&stat->drv->ntdrv.stat_lck);
+	unsigned int size = read_colors(stat->p_nt4ga_stat, val, nbc);
+
+	if (size != snaps->snap_addr[SNAP_COLORS].size) {
+		NT_LOG(ERR, NTCONNECT, "stat.snapshot: color size mismatch");
+		goto err_out;
+	}
+
+	val += size;
+	snaps->snap_addr[SNAP_QUEUES].ptr = val;
+	size = read_queues(stat->p_nt4ga_stat, val, nbq);
+	if (size != snaps->snap_addr[SNAP_QUEUES].size) {
+		NT_LOG(ERR, NTCONNECT,
+		       "stat.snapshot: queue statistic size mismatch");
+		goto err_out;
+	}
+
+	val += size;
+	snaps->snap_addr[SNAP_RX_PORT].ptr = val;
+	size = read_rx_counters(stat->p_nt4ga_stat, val, nbpr);
+	if (size != snaps->snap_addr[SNAP_RX_PORT].size) {
+		NT_LOG(ERR, NTCONNECT,
+		       "stat.snapshot: Rx port statistic size mismatch %i, %i",
+		       size, snaps->snap_addr[SNAP_RX_PORT].size);
+		goto err_out;
+	}
+
+	val += size;
+	snaps->snap_addr[SNAP_TX_PORT].ptr = val;
+	size = read_tx_counters(stat->p_nt4ga_stat, val, nbpt);
+	if (size != snaps->snap_addr[SNAP_TX_PORT].size) {
+		NT_LOG(ERR, NTCONNECT,
+		       "stat.snapshot: Tx port statistic size mismatch");
+		goto err_out;
+	}
+
+	pthread_mutex_unlock(&stat->drv->ntdrv.stat_lck);
+
+	*data = NULL;
+	*len = 0;
+	return REQUEST_OK;
+
+err_out:
+	pthread_mutex_unlock(&stat->drv->ntdrv.stat_lck);
+	return ntconn_error(data, len, "stat",
+			    NTCONN_ERR_CODE_INTERNAL_REPLY_ERROR);
+}
+
+static int get_snap_data(void *hdl, int client_id, char **data, int *len,
+			  enum snap_addr_select_e snap_addr_idx)
+{
+	struct stat_hdl *stat = (struct stat_hdl *)hdl;
+	struct snaps_s *snaps = find_client_snap_data(stat, client_id, NULL);
+
+	if (!snaps || !snaps->buffer)
+		return ntconn_error(data, len, "stat", NTCONN_ERR_CODE_NO_DATA);
+
+	int ln = snaps->snap_addr[snap_addr_idx].size * sizeof(uint64_t);
+
+	*data = malloc(ln);
+	if (!data) {
+		*len = 0;
+		NT_LOG(ERR, NTCONNECT, "memory allocation failed");
+		return REQUEST_ERR;
+	}
+	memcpy(*data, snaps->snap_addr[snap_addr_idx].ptr, ln);
+	*len = ln;
+
+	return REQUEST_OK;
+}
+
+static int func_get_snap_colors(void *hdl, int client_id,
+				struct ntconn_header_s *hdr _unused,
+				char **data, int *len)
+{
+	return get_snap_data(hdl, client_id, data, len, SNAP_COLORS);
+}
+
+static int func_get_snap_queues(void *hdl, int client_id,
+				struct ntconn_header_s *hdr _unused,
+				char **data, int *len)
+{
+	return get_snap_data(hdl, client_id, data, len, SNAP_QUEUES);
+}
+
+static int func_get_snap_rx_port(void *hdl, int client_id,
+				 struct ntconn_header_s *hdr _unused,
+				 char **data, int *len)
+{
+	return get_snap_data(hdl, client_id, data, len, SNAP_RX_PORT);
+}
+
+static int func_get_snap_tx_port(void *hdl, int client_id,
+				 struct ntconn_header_s *hdr _unused,
+				 char **data, int *len)
+{
+	return get_snap_data(hdl, client_id, data, len, SNAP_TX_PORT);
+}
+
+/*
+ * Stat main request function
+ */
+static int stat_request(void *hdl, int client_id _unused,
+			struct ntconn_header_s *hdr, char *function,
+			char **data, int *len)
+{
+	return execute_function(this_module_name, hdl, client_id, hdr, function,
+				stat_entry_funcs, data, len, 0);
+}
+
+static void stat_free_data(void *hdl _unused, char *data)
+{
+	free(data);
+}
+
+static void stat_client_cleanup(void *hdl, int client_id)
+{
+	struct stat_hdl *stat = (struct stat_hdl *)hdl;
+	struct snaps_s *snaps_parent;
+	struct snaps_s *snaps =
+		find_client_snap_data(stat, client_id, &snaps_parent);
+
+	if (!snaps)
+		return;
+
+	if (snaps_parent)
+		snaps_parent->next = snaps->next;
+	else
+		stat->snaps_base = snaps->next;
+
+	if (snaps->buffer)
+		free(snaps->buffer);
+	free(snaps);
+}
+
+static const ntconnapi_t ntconn_stat_op = {
+	this_module_name, STAT_VERSION_MAJOR, STAT_VERSION_MINOR,
+	stat_request,	  stat_free_data,     stat_client_cleanup
+};
+
+int ntconn_stat_register(struct drv_s *drv)
+{
+	stat_hdl.drv = drv;
+	stat_hdl.p_nt4ga_stat = &drv->ntdrv.adapter_info.nt4ga_stat;
+
+	/* Check supported Layout_versions by this module */
+	size_t i;
+
+	for (i = 0; i < NUM_LAYOUT_VERSIONS_SUPPORTED; i++) {
+		if (stat_hdl.p_nt4ga_stat->mp_nthw_stat->mn_stat_layout_version ==
+				layout_versions_supported[i])
+			break;
+	}
+
+	if (i == NUM_LAYOUT_VERSIONS_SUPPORTED) {
+		NT_LOG(ERR, NTCONNECT,
+		       "stat: layout version %i is not supported. Module will not be activated",
+		       stat_hdl.p_nt4ga_stat->mp_nthw_stat->mn_stat_layout_version);
+		return -1;
+	}
+
+	return register_ntconn_mod(&drv->p_dev->addr, (void *)&stat_hdl,
+				   &ntconn_stat_op);
+}
diff --git a/drivers/net/ntnic/ntconnect_modules/ntconn_test.c b/drivers/net/ntnic/ntconnect_modules/ntconn_test.c
new file mode 100644
index 0000000000..907ea4ff5f
--- /dev/null
+++ b/drivers/net/ntnic/ntconnect_modules/ntconn_test.c
@@ -0,0 +1,146 @@ 
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2023 Napatech A/S
+ */
+
+#include <errno.h>
+#include "ntnic_ethdev.h"
+#include "ntconnect.h"
+#include "ntos_system.h"
+#include "ntconn_modules.h"
+#include "ntconn_mod_helper.h"
+#include "nt_util.h"
+#include "ntlog.h"
+#include "ntnic_vf_vdpa.h"
+
+#include "ntconnect_api_test.h"
+
+#define NTCONN_TEST_VERSION_MAJOR 0U
+#define NTCONN_TEST_VERSION_MINOR 1U
+
+#define this_module_name "ntconnect_test"
+
+#define MAX_CLIENTS 32
+
+#define UNUSED __rte_unused
+
+static struct test_hdl_s {
+	struct drv_s *drv;
+} test_hdl[MAX_CLIENTS];
+
+/*
+ * Test functions
+ */
+static int func_test(void *hdl, int client_id, struct ntconn_header_s *hdr,
+		     char **data, int *len);
+static struct func_s adapter_entry_funcs[] = {
+	{ "test", NULL, func_test },
+	{ NULL, NULL, NULL },
+};
+
+static int func_test(void *hdl _unused, int client_id _unused,
+		     struct ntconn_header_s *hdr, char **data, int *len)
+{
+	int status = 0;
+	int number = 0;
+	uint32_t size;
+	struct test_s *test_cpy = (struct test_s *)&(*data)[hdr->len];
+
+	if (hdr->blob_len < sizeof(struct test_s)) {
+		NT_LOG(ERR, NTCONNECT, "Error in test data: to small");
+		status = -1;
+		goto TEST_ERROR;
+	}
+
+	number = test_cpy->number;
+	size = sizeof(struct test_s) + sizeof(uint64_t) * number;
+
+	if (hdr->blob_len != size) {
+		NT_LOG(ERR, NTCONNECT, "Error in test data: wrong size");
+		status = -1;
+		goto TEST_ERROR;
+	}
+
+	{
+		*data = malloc(sizeof(struct test_s) +
+			       number * sizeof(uint64_t));
+		if (!*data)
+			goto TEST_ERROR_MALLOC;
+		struct test_s *return_value = (struct test_s *)*data;
+		*len = sizeof(struct test_s) + number * sizeof(uint64_t);
+		for (int i = 0; i < number; i++)
+			return_value->test[i] = test_cpy->test[i];
+		return_value->status = 0;
+		return_value->number = number;
+		return REQUEST_OK;
+	}
+
+TEST_ERROR:
+
+	{
+		*data = malloc(sizeof(struct test_s));
+		if (!*data)
+			goto TEST_ERROR_MALLOC;
+		struct test_s *return_value = (struct test_s *)*data;
+		*len = sizeof(struct test_s);
+		return_value->status = status;
+		return_value->number = 0;
+		return REQUEST_OK;
+	}
+
+TEST_ERROR_MALLOC:
+
+	*len = 0;
+	NT_LOG(ERR, NTCONNECT, "Not able to allocate memory %s", __func__);
+	return REQUEST_ERR;
+}
+
+enum {
+	FLOW_API_FUNC_CREATE,
+	FLOW_API_FUNC_VALIDATE,
+};
+
+static int test_request(void *hdl, int client_id _unused,
+			struct ntconn_header_s *hdr, char *function,
+			char **data, int *len)
+{
+	return execute_function(this_module_name, hdl, client_id, hdr, function,
+				adapter_entry_funcs, data, len, 0);
+}
+
+static void test_free_data(void *hdl _unused, char *data)
+{
+	if (data)
+		free(data);
+}
+
+static void test_client_cleanup(void *hdl _unused, int client_id _unused)
+{
+	/* Nothing to do */
+}
+
+static const ntconnapi_t ntconn_test_op = { this_module_name,
+					    NTCONN_TEST_VERSION_MAJOR,
+					    NTCONN_TEST_VERSION_MINOR,
+					    test_request,
+					    test_free_data,
+					    test_client_cleanup
+					  };
+
+int ntconn_test_register(struct drv_s *drv)
+{
+	int i;
+
+	for (i = 0; i < MAX_CLIENTS; i++) {
+		if (test_hdl[i].drv == NULL)
+			break;
+	}
+	if (i == MAX_CLIENTS) {
+		NT_LOG(ERR, NTCONNECT,
+		       "Cannot register more adapters into NtConnect framework");
+		return -1;
+	}
+
+	test_hdl[i].drv = drv;
+	return register_ntconn_mod(&drv->p_dev->addr, (void *)&test_hdl[i],
+				   &ntconn_test_op);
+}