diff --git a/test/test/Makefile b/test/test/Makefile
index a88cc38..e5d8200 100644
--- a/test/test/Makefile
+++ b/test/test/Makefile
@@ -193,6 +193,10 @@ endif
 
 SRCS-$(CONFIG_RTE_LIBRTE_KVARGS) += test_kvargs.c
 
+ifeq ($(CONFIG_RTE_LIBRTE_PMD_TAP),y)
+SRCS-y += test_tun.c
+endif
+
 CFLAGS += -DALLOW_EXPERIMENTAL_API
 
 CFLAGS += -O3
diff --git a/test/test/autotest_data.py b/test/test/autotest_data.py
index aacfe0a..35f3aab 100644
--- a/test/test/autotest_data.py
+++ b/test/test/autotest_data.py
@@ -357,6 +357,19 @@ def per_sockets(num):
         ]
     },
     {
+        "Prefix":    "tun",
+        "Memory":    "512",
+        "Tests":
+        [
+            {
+                "Name":    "TUN autotest",
+                "Command": "tun_autotest",
+                "Func":    default_autotest,
+                "Report":  None,
+            },
+        ]
+    },
+    {
         "Prefix":    "mempool_perf",
         "Memory":    per_sockets(256),
         "Tests":
diff --git a/test/test/meson.build b/test/test/meson.build
index eb3d87a..fbb4cf7 100644
--- a/test/test/meson.build
+++ b/test/test/meson.build
@@ -93,6 +93,7 @@ test_sources = files('commands.c',
 	'test_timer.c',
 	'test_timer_perf.c',
 	'test_timer_racecond.c',
+	'test_tun.c',
 	'test_version.c',
 	'virtual_pmd.c'
 )
@@ -227,6 +228,9 @@ endif
 if dpdk_conf.has('RTE_LIBRTE_RING_PMD')
 	test_deps += 'pmd_ring'
 endif
+if dpdk_conf.has('RTE_LIBRTE_TAP_PMD')
+	test_deps += 'pmd_tap'
+endif
 if dpdk_conf.has('RTE_LIBRTE_POWER')
 	test_deps += 'power'
 endif
diff --git a/test/test/test_tun.c b/test/test/test_tun.c
new file mode 100644
index 0000000..c165a94
--- /dev/null
+++ b/test/test/test_tun.c
@@ -0,0 +1,333 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2010-2014 Intel Corporation
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/wait.h>
+#include <net/if.h>
+
+#include "test.h"
+
+#include <rte_string_fns.h>
+#include <rte_mempool.h>
+#include <rte_ethdev.h>
+#include <rte_bus_pci.h>
+#include <rte_cycles.h>
+#include <rte_bus_vdev.h>
+#include <rte_ip.h>
+#include <rte_icmp.h>
+#include <rte_alarm.h>
+
+#define NB_MBUF          8192
+#define MAX_PACKET_SZ    2048
+#define MBUF_DATA_SZ     (MAX_PACKET_SZ + RTE_PKTMBUF_HEADROOM)
+#define PKT_BURST_SZ     32
+#define MEMPOOL_CACHE_SZ PKT_BURST_SZ
+#define SOCKET           0
+#define NB_RXD           1024
+#define NB_TXD           1024
+#define MAX_PKT_BURST    32
+#define IFCONFIG         "/sbin/ifconfig "
+#define PING             "/bin/ping -qr -c 10 -i 0.2 15.0.0.1 -I "
+
+static int tun_id;
+static int socket_id;
+static uint16_t nb_ports, port_id;
+
+static struct rte_eth_dev_info info;
+static struct rte_eth_stats stats;
+static struct rte_mempool *mp;
+static struct rte_mbuf *mbuf;
+
+static char tun_drv_name[20] = "net_tun";
+static char tun_intf_name[20] = "atest";
+static char tun_intf_cmd[20] = "\0";
+static char portname[25] = "\0";
+static char cmd_exec[70] = "\0";
+
+static const struct rte_eth_rxconf rx_conf = {
+	.rx_thresh = {
+		.pthresh = 8,
+		.hthresh = 8,
+		.wthresh = 4,
+	},
+	.rx_free_thresh = 0,
+};
+
+static const struct rte_eth_txconf tx_conf = {
+	.tx_thresh = {
+		.pthresh = 36,
+		.hthresh = 0,
+		.wthresh = 0,
+	},
+	.tx_free_thresh = 0,
+	.tx_rs_thresh = 0,
+};
+
+static const struct rte_eth_conf port_conf = {
+	.rxmode = {
+		.header_split = 0,
+		.hw_ip_checksum = 0,
+		.hw_vlan_filter = 0,
+		.jumbo_frame = 0,
+		.hw_strip_crc = 0,
+	},
+	.txmode = {
+		.mq_mode = ETH_MQ_TX_NONE | ETH_DCB_NONE,
+	},
+};
+
+static void
+send_ping(void *param)
+{
+	char *tun_intf_name = (char *) param;
+
+	sprintf(cmd_exec, "%s %s &", PING, tun_intf_name);
+	if (system(cmd_exec) != 0)
+		printf("fail to execute (%s)!\n", cmd_exec);
+
+	fflush(stdout);
+}
+
+static int
+tun_port_setup(uint16_t port_id)
+{
+	int ret = rte_eth_dev_configure(port_id, 1, 1, &port_conf);
+	if (ret < 0) {
+		printf("fail to configure port %d\n", port_id);
+		return -1;
+	}
+
+	ret = rte_eth_rx_queue_setup(port_id, 0, NB_RXD, socket_id,
+			&rx_conf, mp);
+	if (ret < 0) {
+		printf("fail to setup rx queue for port %d\n", port_id);
+		return -1;
+	}
+
+	ret = rte_eth_tx_queue_setup(port_id, 0, NB_TXD, socket_id,
+			&tx_conf);
+	if (ret < 0) {
+		printf("fail to setup tx queue for port %d\n", port_id);
+		return -1;
+	}
+
+	ret = rte_eth_dev_start(port_id);
+	if (ret < 0) {
+		printf("fail to start port %d\n", port_id);
+		return -2;
+	}
+
+	rte_eth_promiscuous_enable(port_id);
+	return 0;
+}
+
+static int
+setup_interface(char *tun_intf_name)
+{
+	sprintf(cmd_exec, "%s %s 15.0.0.254/24 up", IFCONFIG, tun_intf_name);
+	int ret = system(cmd_exec);
+	if (ret != 0) {
+		printf("fail to execute (%s)!\n", cmd_exec);
+		return -1;
+	}
+
+	printf("Stats after interface config\n");
+	sprintf(cmd_exec, "%s %s | grep packets", IFCONFIG, tun_intf_name);
+	ret = system(cmd_exec);
+	if (ret != 0) {
+		printf("fail to execute (%s)!\n", cmd_exec);
+		return -1;
+	}
+
+	return 0;
+}
+
+static int
+test_tun(void)
+{
+	int ret = -1, i;
+	int8_t count = 10, recv_pkts = 10;
+	uint64_t rx_pkts, tx_pkts, rx_err, tx_err;
+	socket_id = rte_lcore_to_socket_id(rte_lcore_id());
+
+	mp = rte_mempool_lookup("tun_mempool");
+	if (!mp)
+		mp = rte_pktmbuf_pool_create("tun_mempool",
+				NB_MBUF,
+				MEMPOOL_CACHE_SZ, 0, MBUF_DATA_SZ,
+				socket_id);
+
+	nb_ports = rte_eth_dev_count();
+	printf("Found (%d) ports\n", nb_ports);
+
+	sprintf(tun_drv_name, "%s%d", tun_drv_name, tun_id);
+	sprintf(tun_intf_name, "%s%d", tun_intf_name, tun_id++);
+	sprintf(tun_intf_cmd, "iface=%s", tun_intf_name);
+
+	if (rte_vdev_init(tun_drv_name, tun_intf_cmd) != 0) {
+		printf("failed to add tun device!\n");
+		goto fail;
+	}
+
+	nb_ports = rte_eth_dev_count();
+	printf("Found port (%d) with PMD TUN\n", nb_ports);
+
+	for (i = 0; i < nb_ports; i++) {
+		memset(&info, 0, sizeof(info));
+		rte_eth_dev_info_get(i, &info);
+
+		if_indextoname(info.if_index, portname);
+
+		ret = rte_eth_dev_get_name_by_port(i, portname);
+		if (ret != 0) {
+			printf("Fail to get port name for (%d)", i);
+			goto fail;
+		}
+
+		printf("port (%d) driver (%s) interface name (%s)\n",
+				i, info.driver_name, portname);
+	}
+	port_id = i - 1;
+
+	/* configuring new port for the test is enough */
+	ret = tun_port_setup(port_id);
+	if (ret != 0)
+		goto fail;
+
+	ret = rte_eth_stats_get(port_id, &stats);
+	if (ret != 0)
+		goto port_fail;
+
+	rx_pkts = stats.ipackets;
+	tx_pkts = stats.opackets;
+	rx_err = stats.ierrors;
+	tx_err = stats.oerrors;
+
+	ret = setup_interface(tun_intf_name);
+	if (ret != 0)
+		goto port_fail;
+
+	printf("send 1 packet through tun interface\n");
+	mbuf = rte_pktmbuf_alloc(mp);
+	if (mbuf == NULL) {
+		printf("fail to allocate mbuf!\n");
+		goto port_fail;
+	}
+
+	mbuf->nb_segs = 1;
+	mbuf->next = NULL;
+	mbuf->pkt_len = 64;
+	mbuf->data_len = 64;
+
+	struct ipv4_hdr *v4 = rte_pktmbuf_mtod(mbuf, struct ipv4_hdr *);
+	v4->src_addr = 0x0a0a0a0a;
+	v4->dst_addr = 0x0b0b0b0b;
+	v4->time_to_live = 0x2;
+	v4->fragment_offset = 0x00;
+	v4->version_ihl = 0x45;
+
+	/* sent one pkt out */
+	ret = rte_eth_tx_burst(port_id, 0, &mbuf, 1);
+	if (ret != 1) {
+		printf("fail to send nbuf via tun port!\n");
+		goto port_fail;
+	}
+
+	ret = rte_eth_stats_get(port_id, &stats);
+	if (ret != 0)
+		goto port_fail;
+
+	if (((stats.opackets - tx_pkts) != 1) ||
+			(stats.ipackets - rx_pkts) ||
+			(stats.ierrors - rx_err) ||
+			(stats.oerrors - tx_err)) {
+		printf("Fail to match the expected stats\n");
+		goto port_fail;
+}
+
+	rx_pkts = stats.ipackets;
+	tx_pkts = stats.opackets;
+	rx_err = stats.ierrors;
+	tx_err = stats.oerrors;
+
+	rte_eal_alarm_set(1, send_ping, (void *) tun_intf_name);
+	rte_delay_ms(1000);
+
+	printf("Stats after 1 pkt TX");
+	sprintf(cmd_exec, "%s %s | grep packets", IFCONFIG, tun_intf_name);
+	ret = system(cmd_exec);
+	if (ret != 0) {
+		printf("fail to execute (%s)!\n", cmd_exec);
+		goto port_fail;
+	}
+
+	while ((count > 0) && (recv_pkts > 0)) {
+		struct rte_mbuf *rx_pkts_burst[MAX_PKT_BURST];
+		uint16_t nb_pkts = rte_eth_rx_burst(port_id, 0, rx_pkts_burst,
+				10);
+
+		if (!nb_pkts) {
+			rte_delay_ms(500);
+			count = count - 1;
+		}
+
+		recv_pkts -= nb_pkts;
+
+		for (i = 0; i < nb_pkts; i++) {
+			struct ipv4_hdr *v4 = rte_pktmbuf_mtod(rx_pkts_burst[i],
+			struct ipv4_hdr *);
+			v4->src_addr = v4->src_addr ^ v4->dst_addr;
+			v4->dst_addr = v4->src_addr ^ v4->dst_addr;
+			v4->src_addr = v4->src_addr ^ v4->dst_addr;
+
+			struct icmp_hdr *icmp = (struct icmp_hdr *)(v4 + 1);
+			icmp->icmp_type = 0x00;
+
+			/* sent one pkt out */
+			ret = rte_eth_tx_burst(port_id, 0, &mbuf, 1);
+			if (ret != 1) {
+				printf("fail to send nbuf via tun port!\n");
+				goto port_fail;
+			}
+		}
+
+		count = count - 1;
+	}
+
+	printf("Stats after ping replies\n");
+	sprintf(cmd_exec, "%s %s | grep packets", IFCONFIG, tun_intf_name);
+	ret = system(cmd_exec);
+	if (ret != 0) {
+		printf("fail to execute (%s)!\n", cmd_exec);
+		goto port_fail;
+	}
+
+	ret = rte_eth_stats_get(port_id, &stats);
+	if (ret != 0)
+		goto port_fail;
+
+	if (((stats.opackets - tx_pkts) != 10) ||
+			((stats.ipackets - rx_pkts) != 10) ||
+			(stats.ierrors - rx_err) ||
+			(stats.oerrors - tx_err)) {
+		printf("Fail to match the expected stats\n");
+		goto port_fail;
+}
+
+port_fail:
+	rte_eth_dev_stop(port_id);
+
+fail:
+	rte_vdev_uninit(tun_drv_name);
+
+	fflush(stdout);
+
+	rte_delay_ms(2000);
+	return ret;
+}
+
+REGISTER_TEST_COMMAND(tun_autotest, test_tun);
