diff --git a/drivers/net/ntnic/meson.build b/drivers/net/ntnic/meson.build
index f7454ffb79..ee8cf982ef 100644
--- a/drivers/net/ntnic/meson.build
+++ b/drivers/net/ntnic/meson.build
@@ -7,6 +7,22 @@ if not is_linux or not dpdk_conf.has('RTE_ARCH_X86_64')
     subdir_done()
 endif
 
+# config object
+ntnic_conf = configuration_data()
+
+# transfer options into config object
+ntnic_conf.set('NT_TOOLS', true)
+
+# 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('.'),
@@ -20,6 +36,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'),
@@ -41,6 +58,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..affe905027
--- /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_ACTIVE,
+	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..22ef7828c7
--- /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 <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..294b95846b
--- /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_ACTIVE;
+			}
+		} 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..3d81242524
--- /dev/null
+++ b/drivers/net/ntnic/ntconnect_modules/ntconn_flow.c
@@ -0,0 +1,1312 @@
+/* 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 IN_PORT_TOK "in_port="
+#define VPATH_TOK "vpath="
+
+#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) {
+		size_t length = strlen(tok);
+		if (length > strlen(IN_PORT_TOK) && memcmp(tok, IN_PORT_TOK,
+							   strlen(IN_PORT_TOK)) == 0)
+			in_port = atoi(tok + strlen(IN_PORT_TOK));
+	}
+#ifdef DEBUG_FLOW
+	NT_LOG(DBG, NTCONNECT, "in_port:          %u\n", in_port);
+#endif
+
+	tok = strtok_r(NULL, ",", &saveptr);
+	if (tok) {
+		size_t length = strlen(tok);
+		if (length > strlen(VPATH_TOK) && memcmp(tok, VPATH_TOK, strlen(VPATH_TOK)) == 0)
+			strlcpy(vpath, tok + strlen(VPATH_TOK), 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..5c8b8db39e
--- /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 *queue =
+		(struct queue_type_fields_s *)qdata->data;
+	int q;
+
+	for (q = 0; q < nbq; q++) {
+		queue->flush_pkts = hwstat->mp_stat_structs_hb[q].flush_packets;
+		queue->drop_pkts = hwstat->mp_stat_structs_hb[q].drop_packets;
+		queue->fwd_pkts = hwstat->mp_stat_structs_hb[q].fwd_packets;
+		queue->dbs_drop_pkts = hwstat->mp_stat_structs_hb[q].dbs_drop_packets;
+		queue->flush_octets = hwstat->mp_stat_structs_hb[q].flush_bytes;
+		queue->drop_octets = hwstat->mp_stat_structs_hb[q].drop_bytes;
+		queue->fwd_octets = hwstat->mp_stat_structs_hb[q].fwd_bytes;
+		queue->dbs_drop_octets = hwstat->mp_stat_structs_hb[q].dbs_drop_bytes;
+		queue++;
+	}
+	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);
+}
