diff --git a/MAINTAINERS b/MAINTAINERS
index a646ca3e1..f836f4c5d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -325,6 +325,9 @@ F: drivers/raw/skeleton_rawdev/
 F: test/test/test_rawdev.c
 F: doc/guides/prog_guide/rawdev.rst
 
+Netlink Library
+M: Pascal Mazon <pascal.mazon@6wind.com>
+F: lib/librte_netlink/
 
 Bus Drivers
 -----------
diff --git a/config/common_base b/config/common_base
index ad03cf433..6ee715b21 100644
--- a/config/common_base
+++ b/config/common_base
@@ -823,3 +823,9 @@ CONFIG_RTE_APP_CRYPTO_PERF=y
 # Compile the eventdev application
 #
 CONFIG_RTE_APP_EVENTDEV=y
+
+#
+# Compile netlink library
+# It is enabled by default for Linux only.
+#
+CONFIG_RTE_LIBRTE_NETLINK=n
diff --git a/config/common_linuxapp b/config/common_linuxapp
index ff98f2355..97e9d6dfe 100644
--- a/config/common_linuxapp
+++ b/config/common_linuxapp
@@ -23,3 +23,4 @@ CONFIG_RTE_LIBRTE_NFP_PMD=y
 CONFIG_RTE_LIBRTE_POWER=y
 CONFIG_RTE_VIRTIO_USER=y
 CONFIG_RTE_PROC_INFO=y
+CONFIG_RTE_LIBRTE_NETLINK=y
diff --git a/drivers/net/tap/Makefile b/drivers/net/tap/Makefile
index ccc5c5fc0..05fd392ef 100644
--- a/drivers/net/tap/Makefile
+++ b/drivers/net/tap/Makefile
@@ -24,7 +24,7 @@ CFLAGS += -I.
 CFLAGS += $(WERROR_FLAGS)
 LDLIBS += -lrte_eal -lrte_mbuf -lrte_mempool -lrte_ring
 LDLIBS += -lrte_ethdev -lrte_net -lrte_kvargs -lrte_hash
-LDLIBS += -lrte_bus_vdev
+LDLIBS += -lrte_bus_vdev -lrte_netlink
 
 CFLAGS += -DTAP_MAX_QUEUES=$(TAP_MAX_QUEUES)
 
@@ -33,7 +33,6 @@ CFLAGS += -DTAP_MAX_QUEUES=$(TAP_MAX_QUEUES)
 #
 SRCS-$(CONFIG_RTE_LIBRTE_PMD_TAP) += rte_eth_tap.c
 SRCS-$(CONFIG_RTE_LIBRTE_PMD_TAP) += tap_flow.c
-SRCS-$(CONFIG_RTE_LIBRTE_PMD_TAP) += tap_netlink.c
 SRCS-$(CONFIG_RTE_LIBRTE_PMD_TAP) += tap_tcmsgs.c
 SRCS-$(CONFIG_RTE_LIBRTE_PMD_TAP) += tap_bpf_api.c
 SRCS-$(CONFIG_RTE_LIBRTE_PMD_TAP) += tap_intr.c
diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
index f09db0ea9..05a6beabb 100644
--- a/drivers/net/tap/rte_eth_tap.c
+++ b/drivers/net/tap/rte_eth_tap.c
@@ -15,6 +15,7 @@
 #include <rte_net.h>
 #include <rte_debug.h>
 #include <rte_ip.h>
+#include <rte_netlink.h>
 
 #include <sys/types.h>
 #include <sys/stat.h>
@@ -36,7 +37,6 @@
 
 #include <rte_eth_tap.h>
 #include <tap_flow.h>
-#include <tap_netlink.h>
 #include <tap_tcmsgs.h>
 
 /* Linux based path to the TUN device */
@@ -1197,7 +1197,7 @@ tap_set_mc_addr_list(struct rte_eth_dev *dev __rte_unused,
 }
 
 static int
-tap_nl_msg_handler(struct nlmsghdr *nh, void *arg)
+rte_nl_msg_handler(struct nlmsghdr *nh, void *arg)
 {
 	struct rte_eth_dev *dev = arg;
 	struct pmd_internals *pmd = dev->data->dev_private;
@@ -1216,7 +1216,7 @@ tap_dev_intr_handler(void *cb_arg)
 	struct rte_eth_dev *dev = cb_arg;
 	struct pmd_internals *pmd = dev->data->dev_private;
 
-	tap_nl_recv(pmd->intr_handle.fd, tap_nl_msg_handler, dev);
+	rte_nl_recv(pmd->intr_handle.fd, rte_nl_msg_handler, dev);
 }
 
 static int
@@ -1227,20 +1227,20 @@ tap_lsc_intr_handle_set(struct rte_eth_dev *dev, int set)
 	/* In any case, disable interrupt if the conf is no longer there. */
 	if (!dev->data->dev_conf.intr_conf.lsc) {
 		if (pmd->intr_handle.fd != -1) {
-			tap_nl_final(pmd->intr_handle.fd);
+			rte_nl_final(pmd->intr_handle.fd);
 			rte_intr_callback_unregister(&pmd->intr_handle,
 				tap_dev_intr_handler, dev);
 		}
 		return 0;
 	}
 	if (set) {
-		pmd->intr_handle.fd = tap_nl_init(RTMGRP_LINK);
+		pmd->intr_handle.fd = rte_nl_init(RTMGRP_LINK);
 		if (unlikely(pmd->intr_handle.fd == -1))
 			return -EBADF;
 		return rte_intr_callback_register(
 			&pmd->intr_handle, tap_dev_intr_handler, dev);
 	}
-	tap_nl_final(pmd->intr_handle.fd);
+	rte_nl_final(pmd->intr_handle.fd);
 	return rte_intr_callback_unregister(&pmd->intr_handle,
 					    tap_dev_intr_handler, dev);
 }
@@ -1436,7 +1436,7 @@ eth_dev_tap_create(struct rte_vdev_device *vdev, char *tap_name,
 	 * - rte_flow actual/implicit lists
 	 * - implicit rules
 	 */
-	pmd->nlsk_fd = tap_nl_init(0);
+	pmd->nlsk_fd = rte_nl_init(0);
 	if (pmd->nlsk_fd == -1) {
 		RTE_LOG(WARNING, PMD, "%s: failed to create netlink socket.\n",
 			pmd->name);
@@ -1673,7 +1673,7 @@ rte_pmd_tap_remove(struct rte_vdev_device *dev)
 	if (internals->nlsk_fd) {
 		tap_flow_flush(eth_dev, NULL);
 		tap_flow_implicit_flush(internals, NULL);
-		tap_nl_final(internals->nlsk_fd);
+		rte_nl_final(internals->nlsk_fd);
 	}
 	for (i = 0; i < RTE_PMD_TAP_MAX_QUEUES; i++) {
 		if (internals->rxq[i].fd != -1) {
diff --git a/drivers/net/tap/tap_flow.c b/drivers/net/tap/tap_flow.c
index 551b2d83d..90a105d47 100644
--- a/drivers/net/tap/tap_flow.c
+++ b/drivers/net/tap/tap_flow.c
@@ -538,16 +538,16 @@ tap_flow_create_eth(const struct rte_flow_item *item, void *data)
 		return 0;
 	msg = &flow->msg;
 	if (!is_zero_ether_addr(&spec->dst)) {
-		tap_nlattr_add(&msg->nh, TCA_FLOWER_KEY_ETH_DST, ETHER_ADDR_LEN,
+		rte_nlattr_add(&msg->nh, TCA_FLOWER_KEY_ETH_DST, ETHER_ADDR_LEN,
 			   &spec->dst.addr_bytes);
-		tap_nlattr_add(&msg->nh,
+		rte_nlattr_add(&msg->nh,
 			   TCA_FLOWER_KEY_ETH_DST_MASK, ETHER_ADDR_LEN,
 			   &mask->dst.addr_bytes);
 	}
 	if (!is_zero_ether_addr(&mask->src)) {
-		tap_nlattr_add(&msg->nh, TCA_FLOWER_KEY_ETH_SRC, ETHER_ADDR_LEN,
+		rte_nlattr_add(&msg->nh, TCA_FLOWER_KEY_ETH_SRC, ETHER_ADDR_LEN,
 			   &spec->src.addr_bytes);
-		tap_nlattr_add(&msg->nh,
+		rte_nlattr_add(&msg->nh,
 			   TCA_FLOWER_KEY_ETH_SRC_MASK, ETHER_ADDR_LEN,
 			   &mask->src.addr_bytes);
 	}
@@ -599,10 +599,10 @@ tap_flow_create_vlan(const struct rte_flow_item *item, void *data)
 		uint8_t vid = VLAN_ID(tci);
 
 		if (prio)
-			tap_nlattr_add8(&msg->nh,
+			rte_nlattr_add8(&msg->nh,
 					TCA_FLOWER_KEY_VLAN_PRIO, prio);
 		if (vid)
-			tap_nlattr_add16(&msg->nh,
+			rte_nlattr_add16(&msg->nh,
 					 TCA_FLOWER_KEY_VLAN_ID, vid);
 	}
 	return 0;
@@ -646,19 +646,19 @@ tap_flow_create_ipv4(const struct rte_flow_item *item, void *data)
 	if (!spec)
 		return 0;
 	if (spec->hdr.dst_addr) {
-		tap_nlattr_add32(&msg->nh, TCA_FLOWER_KEY_IPV4_DST,
+		rte_nlattr_add32(&msg->nh, TCA_FLOWER_KEY_IPV4_DST,
 			     spec->hdr.dst_addr);
-		tap_nlattr_add32(&msg->nh, TCA_FLOWER_KEY_IPV4_DST_MASK,
+		rte_nlattr_add32(&msg->nh, TCA_FLOWER_KEY_IPV4_DST_MASK,
 			     mask->hdr.dst_addr);
 	}
 	if (spec->hdr.src_addr) {
-		tap_nlattr_add32(&msg->nh, TCA_FLOWER_KEY_IPV4_SRC,
+		rte_nlattr_add32(&msg->nh, TCA_FLOWER_KEY_IPV4_SRC,
 			     spec->hdr.src_addr);
-		tap_nlattr_add32(&msg->nh, TCA_FLOWER_KEY_IPV4_SRC_MASK,
+		rte_nlattr_add32(&msg->nh, TCA_FLOWER_KEY_IPV4_SRC_MASK,
 			     mask->hdr.src_addr);
 	}
 	if (spec->hdr.next_proto_id)
-		tap_nlattr_add8(&msg->nh, TCA_FLOWER_KEY_IP_PROTO,
+		rte_nlattr_add8(&msg->nh, TCA_FLOWER_KEY_IP_PROTO,
 			    spec->hdr.next_proto_id);
 	return 0;
 }
@@ -702,19 +702,19 @@ tap_flow_create_ipv6(const struct rte_flow_item *item, void *data)
 	if (!spec)
 		return 0;
 	if (memcmp(spec->hdr.dst_addr, empty_addr, 16)) {
-		tap_nlattr_add(&msg->nh, TCA_FLOWER_KEY_IPV6_DST,
+		rte_nlattr_add(&msg->nh, TCA_FLOWER_KEY_IPV6_DST,
 			   sizeof(spec->hdr.dst_addr), &spec->hdr.dst_addr);
-		tap_nlattr_add(&msg->nh, TCA_FLOWER_KEY_IPV6_DST_MASK,
+		rte_nlattr_add(&msg->nh, TCA_FLOWER_KEY_IPV6_DST_MASK,
 			   sizeof(mask->hdr.dst_addr), &mask->hdr.dst_addr);
 	}
 	if (memcmp(spec->hdr.src_addr, empty_addr, 16)) {
-		tap_nlattr_add(&msg->nh, TCA_FLOWER_KEY_IPV6_SRC,
+		rte_nlattr_add(&msg->nh, TCA_FLOWER_KEY_IPV6_SRC,
 			   sizeof(spec->hdr.src_addr), &spec->hdr.src_addr);
-		tap_nlattr_add(&msg->nh, TCA_FLOWER_KEY_IPV6_SRC_MASK,
+		rte_nlattr_add(&msg->nh, TCA_FLOWER_KEY_IPV6_SRC_MASK,
 			   sizeof(mask->hdr.src_addr), &mask->hdr.src_addr);
 	}
 	if (spec->hdr.proto)
-		tap_nlattr_add8(&msg->nh,
+		rte_nlattr_add8(&msg->nh,
 				TCA_FLOWER_KEY_IP_PROTO, spec->hdr.proto);
 	return 0;
 }
@@ -753,14 +753,14 @@ tap_flow_create_udp(const struct rte_flow_item *item, void *data)
 	if (!flow)
 		return 0;
 	msg = &flow->msg;
-	tap_nlattr_add8(&msg->nh, TCA_FLOWER_KEY_IP_PROTO, IPPROTO_UDP);
+	rte_nlattr_add8(&msg->nh, TCA_FLOWER_KEY_IP_PROTO, IPPROTO_UDP);
 	if (!spec)
 		return 0;
 	if (spec->hdr.dst_port & mask->hdr.dst_port)
-		tap_nlattr_add16(&msg->nh, TCA_FLOWER_KEY_UDP_DST,
+		rte_nlattr_add16(&msg->nh, TCA_FLOWER_KEY_UDP_DST,
 			     spec->hdr.dst_port);
 	if (spec->hdr.src_port & mask->hdr.src_port)
-		tap_nlattr_add16(&msg->nh, TCA_FLOWER_KEY_UDP_SRC,
+		rte_nlattr_add16(&msg->nh, TCA_FLOWER_KEY_UDP_SRC,
 			     spec->hdr.src_port);
 	return 0;
 }
@@ -799,14 +799,14 @@ tap_flow_create_tcp(const struct rte_flow_item *item, void *data)
 	if (!flow)
 		return 0;
 	msg = &flow->msg;
-	tap_nlattr_add8(&msg->nh, TCA_FLOWER_KEY_IP_PROTO, IPPROTO_TCP);
+	rte_nlattr_add8(&msg->nh, TCA_FLOWER_KEY_IP_PROTO, IPPROTO_TCP);
 	if (!spec)
 		return 0;
 	if (spec->hdr.dst_port & mask->hdr.dst_port)
-		tap_nlattr_add16(&msg->nh, TCA_FLOWER_KEY_TCP_DST,
+		rte_nlattr_add16(&msg->nh, TCA_FLOWER_KEY_TCP_DST,
 			     spec->hdr.dst_port);
 	if (spec->hdr.src_port & mask->hdr.src_port)
-		tap_nlattr_add16(&msg->nh, TCA_FLOWER_KEY_TCP_SRC,
+		rte_nlattr_add16(&msg->nh, TCA_FLOWER_KEY_TCP_SRC,
 			     spec->hdr.src_port);
 	return 0;
 }
@@ -912,43 +912,43 @@ add_action(struct rte_flow *flow, size_t *act_index, struct action_data *adata)
 {
 	struct nlmsg *msg = &flow->msg;
 
-	if (tap_nlattr_nested_start(msg, (*act_index)++) < 0)
+	if (rte_nlattr_nested_start(msg, (*act_index)++) < 0)
 		return -1;
 
-	tap_nlattr_add(&msg->nh, TCA_ACT_KIND,
+	rte_nlattr_add(&msg->nh, TCA_ACT_KIND,
 				strlen(adata->id) + 1, adata->id);
-	if (tap_nlattr_nested_start(msg, TCA_ACT_OPTIONS) < 0)
+	if (rte_nlattr_nested_start(msg, TCA_ACT_OPTIONS) < 0)
 		return -1;
 	if (strcmp("gact", adata->id) == 0) {
-		tap_nlattr_add(&msg->nh, TCA_GACT_PARMS, sizeof(adata->gact),
+		rte_nlattr_add(&msg->nh, TCA_GACT_PARMS, sizeof(adata->gact),
 			   &adata->gact);
 	} else if (strcmp("mirred", adata->id) == 0) {
 		if (adata->mirred.eaction == TCA_EGRESS_MIRROR)
 			adata->mirred.action = TC_ACT_PIPE;
 		else /* REDIRECT */
 			adata->mirred.action = TC_ACT_STOLEN;
-		tap_nlattr_add(&msg->nh, TCA_MIRRED_PARMS,
+		rte_nlattr_add(&msg->nh, TCA_MIRRED_PARMS,
 			   sizeof(adata->mirred),
 			   &adata->mirred);
 	} else if (strcmp("skbedit", adata->id) == 0) {
-		tap_nlattr_add(&msg->nh, TCA_SKBEDIT_PARMS,
+		rte_nlattr_add(&msg->nh, TCA_SKBEDIT_PARMS,
 			   sizeof(adata->skbedit.skbedit),
 			   &adata->skbedit.skbedit);
-		tap_nlattr_add16(&msg->nh, TCA_SKBEDIT_QUEUE_MAPPING,
+		rte_nlattr_add16(&msg->nh, TCA_SKBEDIT_QUEUE_MAPPING,
 			     adata->skbedit.queue);
 	} else if (strcmp("bpf", adata->id) == 0) {
-		tap_nlattr_add32(&msg->nh, TCA_ACT_BPF_FD, adata->bpf.bpf_fd);
-		tap_nlattr_add(&msg->nh, TCA_ACT_BPF_NAME,
+		rte_nlattr_add32(&msg->nh, TCA_ACT_BPF_FD, adata->bpf.bpf_fd);
+		rte_nlattr_add(&msg->nh, TCA_ACT_BPF_NAME,
 			   strlen(adata->bpf.annotation) + 1,
 			   adata->bpf.annotation);
-		tap_nlattr_add(&msg->nh, TCA_ACT_BPF_PARMS,
+		rte_nlattr_add(&msg->nh, TCA_ACT_BPF_PARMS,
 			   sizeof(adata->bpf.bpf),
 			   &adata->bpf.bpf);
 	} else {
 		return -1;
 	}
-	tap_nlattr_nested_finish(msg); /* nested TCA_ACT_OPTIONS */
-	tap_nlattr_nested_finish(msg); /* nested act_index */
+	rte_nlattr_nested_finish(msg); /* nested TCA_ACT_OPTIONS */
+	rte_nlattr_nested_finish(msg); /* nested act_index */
 	return 0;
 }
 
@@ -978,12 +978,12 @@ add_actions(struct rte_flow *flow, int nb_actions, struct action_data *data,
 	size_t act_index = 1;
 	int i;
 
-	if (tap_nlattr_nested_start(msg, classifier_action) < 0)
+	if (rte_nlattr_nested_start(msg, classifier_action) < 0)
 		return -1;
 	for (i = 0; i < nb_actions; i++)
 		if (add_action(flow, &act_index, data + i) < 0)
 			return -1;
-	tap_nlattr_nested_finish(msg); /* nested TCA_FLOWER_ACT */
+	rte_nlattr_nested_finish(msg); /* nested TCA_FLOWER_ACT */
 	return 0;
 }
 
@@ -1067,8 +1067,8 @@ priv_flow_process(struct pmd_internals *pmd,
 				TC_H_MAKE(MULTIQ_MAJOR_HANDLE, 0);
 		}
 		/* use flower filter type */
-		tap_nlattr_add(&flow->msg.nh, TCA_KIND, sizeof("flower"), "flower");
-		if (tap_nlattr_nested_start(&flow->msg, TCA_OPTIONS) < 0)
+		rte_nlattr_add(&flow->msg.nh, TCA_KIND, sizeof("flower"), "flower");
+		if (rte_nlattr_nested_start(&flow->msg, TCA_OPTIONS) < 0)
 			goto exit_item_not_supported;
 	}
 	for (; items->type != RTE_FLOW_ITEM_TYPE_END; ++items) {
@@ -1104,14 +1104,14 @@ priv_flow_process(struct pmd_internals *pmd,
 	}
 	if (flow) {
 		if (data.vlan) {
-			tap_nlattr_add16(&flow->msg.nh, TCA_FLOWER_KEY_ETH_TYPE,
+			rte_nlattr_add16(&flow->msg.nh, TCA_FLOWER_KEY_ETH_TYPE,
 				     htons(ETH_P_8021Q));
-			tap_nlattr_add16(&flow->msg.nh,
+			rte_nlattr_add16(&flow->msg.nh,
 				     TCA_FLOWER_KEY_VLAN_ETH_TYPE,
 				     data.eth_type ?
 				     data.eth_type : htons(ETH_P_ALL));
 		} else if (data.eth_type) {
-			tap_nlattr_add16(&flow->msg.nh, TCA_FLOWER_KEY_ETH_TYPE,
+			rte_nlattr_add16(&flow->msg.nh, TCA_FLOWER_KEY_ETH_TYPE,
 				     data.eth_type);
 		}
 	}
@@ -1224,7 +1224,7 @@ priv_flow_process(struct pmd_internals *pmd,
 	}
 end:
 	if (flow)
-		tap_nlattr_nested_finish(&flow->msg); /* nested TCA_OPTIONS */
+		rte_nlattr_nested_finish(&flow->msg); /* nested TCA_OPTIONS */
 	return 0;
 exit_item_not_supported:
 	rte_flow_error_set(error, ENOTSUP, RTE_FLOW_ERROR_TYPE_ITEM,
@@ -1368,13 +1368,13 @@ tap_flow_create(struct rte_eth_dev *dev,
 	tap_flow_set_handle(flow);
 	if (priv_flow_process(pmd, attr, items, actions, error, flow, 0))
 		goto fail;
-	err = tap_nl_send(pmd->nlsk_fd, &msg->nh);
+	err = rte_nl_send(pmd->nlsk_fd, &msg->nh);
 	if (err < 0) {
 		rte_flow_error_set(error, ENOTSUP, RTE_FLOW_ERROR_TYPE_HANDLE,
 				   NULL, "couldn't send request to kernel");
 		goto fail;
 	}
-	err = tap_nl_recv_ack(pmd->nlsk_fd);
+	err = rte_nl_recv_ack(pmd->nlsk_fd);
 	if (err < 0) {
 		RTE_LOG(ERR, PMD,
 			"Kernel refused TC filter rule creation (%d): %s\n",
@@ -1412,14 +1412,14 @@ tap_flow_create(struct rte_eth_dev *dev,
 				NULL, "rte flow rule validation failed");
 			goto fail;
 		}
-		err = tap_nl_send(pmd->nlsk_fd, &msg->nh);
+		err = rte_nl_send(pmd->nlsk_fd, &msg->nh);
 		if (err < 0) {
 			rte_flow_error_set(
 				error, ENOMEM, RTE_FLOW_ERROR_TYPE_HANDLE,
 				NULL, "Failure sending nl request");
 			goto fail;
 		}
-		err = tap_nl_recv_ack(pmd->nlsk_fd);
+		err = rte_nl_recv_ack(pmd->nlsk_fd);
 		if (err < 0) {
 			RTE_LOG(ERR, PMD,
 				"Kernel refused TC filter rule creation (%d): %s\n",
@@ -1465,13 +1465,13 @@ tap_flow_destroy_pmd(struct pmd_internals *pmd,
 	flow->msg.nh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
 	flow->msg.nh.nlmsg_type = RTM_DELTFILTER;
 
-	ret = tap_nl_send(pmd->nlsk_fd, &flow->msg.nh);
+	ret = rte_nl_send(pmd->nlsk_fd, &flow->msg.nh);
 	if (ret < 0) {
 		rte_flow_error_set(error, ENOTSUP, RTE_FLOW_ERROR_TYPE_HANDLE,
 				   NULL, "couldn't send request to kernel");
 		goto end;
 	}
-	ret = tap_nl_recv_ack(pmd->nlsk_fd);
+	ret = rte_nl_recv_ack(pmd->nlsk_fd);
 	/* If errno is ENOENT, the rule is already no longer in the kernel. */
 	if (ret < 0 && errno == ENOENT)
 		ret = 0;
@@ -1489,14 +1489,14 @@ tap_flow_destroy_pmd(struct pmd_internals *pmd,
 		remote_flow->msg.nh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
 		remote_flow->msg.nh.nlmsg_type = RTM_DELTFILTER;
 
-		ret = tap_nl_send(pmd->nlsk_fd, &remote_flow->msg.nh);
+		ret = rte_nl_send(pmd->nlsk_fd, &remote_flow->msg.nh);
 		if (ret < 0) {
 			rte_flow_error_set(
 				error, ENOMEM, RTE_FLOW_ERROR_TYPE_HANDLE,
 				NULL, "Failure sending nl request");
 			goto end;
 		}
-		ret = tap_nl_recv_ack(pmd->nlsk_fd);
+		ret = rte_nl_recv_ack(pmd->nlsk_fd);
 		if (ret < 0 && errno == ENOENT)
 			ret = 0;
 		if (ret < 0) {
@@ -1709,12 +1709,12 @@ int tap_flow_implicit_create(struct pmd_internals *pmd,
 		RTE_LOG(ERR, PMD, "rte flow rule validation failed\n");
 		goto fail;
 	}
-	err = tap_nl_send(pmd->nlsk_fd, &msg->nh);
+	err = rte_nl_send(pmd->nlsk_fd, &msg->nh);
 	if (err < 0) {
 		RTE_LOG(ERR, PMD, "Failure sending nl request\n");
 		goto fail;
 	}
-	err = tap_nl_recv_ack(pmd->nlsk_fd);
+	err = rte_nl_recv_ack(pmd->nlsk_fd);
 	if (err < 0) {
 		/* Silently ignore re-entering remote promiscuous rule */
 		if (errno == EEXIST && idx == TAP_REMOTE_PROMISC)
@@ -1882,13 +1882,13 @@ static int rss_enable(struct pmd_internals *pmd,
 		msg->t.tcm_info = TC_H_MAKE(prio << 16, msg->t.tcm_info);
 		msg->t.tcm_parent = TC_H_MAKE(MULTIQ_MAJOR_HANDLE, 0);
 
-		tap_nlattr_add(&msg->nh, TCA_KIND, sizeof("bpf"), "bpf");
-		if (tap_nlattr_nested_start(msg, TCA_OPTIONS) < 0)
+		rte_nlattr_add(&msg->nh, TCA_KIND, sizeof("bpf"), "bpf");
+		if (rte_nlattr_nested_start(msg, TCA_OPTIONS) < 0)
 			return -1;
-		tap_nlattr_add32(&msg->nh, TCA_BPF_FD, pmd->bpf_fd[i]);
+		rte_nlattr_add32(&msg->nh, TCA_BPF_FD, pmd->bpf_fd[i]);
 		snprintf(annotation, sizeof(annotation), "[%s%d]",
 			SEC_NAME_CLS_Q, i);
-		tap_nlattr_add(&msg->nh, TCA_BPF_NAME, strlen(annotation) + 1,
+		rte_nlattr_add(&msg->nh, TCA_BPF_NAME, strlen(annotation) + 1,
 			   annotation);
 		/* Actions */
 		{
@@ -1904,12 +1904,12 @@ static int rss_enable(struct pmd_internals *pmd,
 			if (add_actions(rss_flow, 1, &adata, TCA_BPF_ACT) < 0)
 				return -1;
 		}
-		tap_nlattr_nested_finish(msg); /* nested TCA_OPTIONS */
+		rte_nlattr_nested_finish(msg); /* nested TCA_OPTIONS */
 
 		/* Netlink message is now ready to be sent */
-		if (tap_nl_send(pmd->nlsk_fd, &msg->nh) < 0)
+		if (rte_nl_send(pmd->nlsk_fd, &msg->nh) < 0)
 			return -1;
-		err = tap_nl_recv_ack(pmd->nlsk_fd);
+		err = rte_nl_recv_ack(pmd->nlsk_fd);
 		if (err < 0) {
 			RTE_LOG(ERR, PMD,
 				"Kernel refused TC filter rule creation (%d): %s\n",
diff --git a/drivers/net/tap/tap_netlink.h b/drivers/net/tap/tap_netlink.h
deleted file mode 100644
index fafef8401..000000000
--- a/drivers/net/tap/tap_netlink.h
+++ /dev/null
@@ -1,42 +0,0 @@
-/* SPDX-License-Identifier: BSD-3-Clause
- * Copyright 2017 6WIND S.A.
- * Copyright 2017 Mellanox.
- */
-
-#ifndef _TAP_NETLINK_H_
-#define _TAP_NETLINK_H_
-
-#include <ctype.h>
-#include <inttypes.h>
-#include <linux/rtnetlink.h>
-#include <linux/netlink.h>
-#include <stdio.h>
-
-#include <rte_log.h>
-
-#define NLMSG_BUF 512
-
-struct nlmsg {
-	struct nlmsghdr nh;
-	struct tcmsg t;
-	char buf[NLMSG_BUF];
-	struct nested_tail *nested_tails;
-};
-
-#define NLMSG_TAIL(nlh) (void *)((char *)(nlh) + NLMSG_ALIGN((nlh)->nlmsg_len))
-
-int tap_nl_init(uint32_t nl_groups);
-int tap_nl_final(int nlsk_fd);
-int tap_nl_send(int nlsk_fd, struct nlmsghdr *nh);
-int tap_nl_recv(int nlsk_fd, int (*callback)(struct nlmsghdr *, void *),
-		void *arg);
-int tap_nl_recv_ack(int nlsk_fd);
-void tap_nlattr_add(struct nlmsghdr *nh, unsigned short type,
-		    unsigned int data_len, const void *data);
-void tap_nlattr_add8(struct nlmsghdr *nh, unsigned short type, uint8_t data);
-void tap_nlattr_add16(struct nlmsghdr *nh, unsigned short type, uint16_t data);
-void tap_nlattr_add32(struct nlmsghdr *nh, unsigned short type, uint32_t data);
-int tap_nlattr_nested_start(struct nlmsg *msg, uint16_t type);
-void tap_nlattr_nested_finish(struct nlmsg *msg);
-
-#endif /* _TAP_NETLINK_H_ */
diff --git a/drivers/net/tap/tap_tcmsgs.c b/drivers/net/tap/tap_tcmsgs.c
index 954f13ebb..69638dfc7 100644
--- a/drivers/net/tap/tap_tcmsgs.c
+++ b/drivers/net/tap/tap_tcmsgs.c
@@ -79,7 +79,7 @@ qdisc_del(int nlsk_fd, uint16_t ifindex, struct qdisc *qinfo)
 	msg.t.tcm_parent = qinfo->parent;
 	/* if no netlink socket is provided, create one */
 	if (!nlsk_fd) {
-		fd = tap_nl_init(0);
+		fd = rte_nl_init(0);
 		if (fd < 0) {
 			RTE_LOG(ERR, PMD,
 				"Could not delete QDISC: null netlink socket\n");
@@ -88,16 +88,16 @@ qdisc_del(int nlsk_fd, uint16_t ifindex, struct qdisc *qinfo)
 	} else {
 		fd = nlsk_fd;
 	}
-	if (tap_nl_send(fd, &msg.nh) < 0)
+	if (rte_nl_send(fd, &msg.nh) < 0)
 		goto error;
-	if (tap_nl_recv_ack(fd) < 0)
+	if (rte_nl_recv_ack(fd) < 0)
 		goto error;
 	if (!nlsk_fd)
-		return tap_nl_final(fd);
+		return rte_nl_final(fd);
 	return 0;
 error:
 	if (!nlsk_fd)
-		tap_nl_final(fd);
+		rte_nl_final(fd);
 	return -1;
 }
 
@@ -122,11 +122,11 @@ qdisc_add_multiq(int nlsk_fd, uint16_t ifindex)
 		    NLM_F_REQUEST | NLM_F_ACK | NLM_F_EXCL | NLM_F_CREATE);
 	msg.t.tcm_handle = TC_H_MAKE(MULTIQ_MAJOR_HANDLE, 0);
 	msg.t.tcm_parent = TC_H_ROOT;
-	tap_nlattr_add(&msg.nh, TCA_KIND, sizeof("multiq"), "multiq");
-	tap_nlattr_add(&msg.nh, TCA_OPTIONS, sizeof(opt), &opt);
-	if (tap_nl_send(nlsk_fd, &msg.nh) < 0)
+	rte_nlattr_add(&msg.nh, TCA_KIND, sizeof("multiq"), "multiq");
+	rte_nlattr_add(&msg.nh, TCA_OPTIONS, sizeof(opt), &opt);
+	if (rte_nl_send(nlsk_fd, &msg.nh) < 0)
 		return -1;
-	if (tap_nl_recv_ack(nlsk_fd) < 0)
+	if (rte_nl_recv_ack(nlsk_fd) < 0)
 		return -1;
 	return 0;
 }
@@ -151,10 +151,10 @@ qdisc_add_ingress(int nlsk_fd, uint16_t ifindex)
 		    NLM_F_REQUEST | NLM_F_ACK | NLM_F_EXCL | NLM_F_CREATE);
 	msg.t.tcm_handle = TC_H_MAKE(TC_H_INGRESS, 0);
 	msg.t.tcm_parent = TC_H_INGRESS;
-	tap_nlattr_add(&msg.nh, TCA_KIND, sizeof("ingress"), "ingress");
-	if (tap_nl_send(nlsk_fd, &msg.nh) < 0)
+	rte_nlattr_add(&msg.nh, TCA_KIND, sizeof("ingress"), "ingress");
+	if (rte_nl_send(nlsk_fd, &msg.nh) < 0)
 		return -1;
-	if (tap_nl_recv_ack(nlsk_fd) < 0)
+	if (rte_nl_recv_ack(nlsk_fd) < 0)
 		return -1;
 	return 0;
 }
@@ -218,9 +218,9 @@ qdisc_iterate(int nlsk_fd, uint16_t ifindex,
 	};
 
 	tc_init_msg(&msg, ifindex, RTM_GETQDISC, NLM_F_REQUEST | NLM_F_DUMP);
-	if (tap_nl_send(nlsk_fd, &msg.nh) < 0)
+	if (rte_nl_send(nlsk_fd, &msg.nh) < 0)
 		return -1;
-	if (tap_nl_recv(nlsk_fd, callback, &args) < 0)
+	if (rte_nl_recv(nlsk_fd, callback, &args) < 0)
 		return -1;
 	return 0;
 }
diff --git a/drivers/net/tap/tap_tcmsgs.h b/drivers/net/tap/tap_tcmsgs.h
index f72f8c5c2..73f74502a 100644
--- a/drivers/net/tap/tap_tcmsgs.h
+++ b/drivers/net/tap/tap_tcmsgs.h
@@ -20,7 +20,7 @@
 #include <inttypes.h>
 
 #include <rte_ether.h>
-#include <tap_netlink.h>
+#include <rte_netlink.h>
 
 #define MULTIQ_MAJOR_HANDLE (1 << 16)
 
diff --git a/lib/Makefile b/lib/Makefile
index ec965a606..699f962d9 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -103,5 +103,7 @@ DIRS-$(CONFIG_RTE_LIBRTE_KNI) += librte_kni
 endif
 DEPDIRS-librte_kni := librte_eal librte_mempool librte_mbuf librte_ether
 DEPDIRS-librte_kni += librte_pci
+DIRS-$(CONFIG_RTE_LIBRTE_NETLINK) += librte_netlink
+DEPDIRS-librte_netlink := librte_eal
 
 include $(RTE_SDK)/mk/rte.subdir.mk
diff --git a/lib/librte_netlink/Makefile b/lib/librte_netlink/Makefile
new file mode 100644
index 000000000..89300284f
--- /dev/null
+++ b/lib/librte_netlink/Makefile
@@ -0,0 +1,26 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2010-2017 Intel Corporation
+
+include $(RTE_SDK)/mk/rte.vars.mk
+
+#
+# library name
+#
+LIB = librte_netlink.a
+
+CFLAGS += -O3
+CFLAGS += $(WERROR_FLAGS)
+LDLIBS += -lrte_eal
+
+EXPORT_MAP := rte_netlink_version.map
+
+LIBABIVER := 1
+
+SRCS-y += rte_netlink.c
+
+#
+# Export include files
+#
+SYMLINK-y-include += rte_netlink.h
+
+include $(RTE_SDK)/mk/rte.lib.mk
diff --git a/lib/librte_netlink/meson.build b/lib/librte_netlink/meson.build
new file mode 100644
index 000000000..77592c7d4
--- /dev/null
+++ b/lib/librte_netlink/meson.build
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright 2018 6WIND S.A.
+# Copyright 2018 Mellanox Technologies, Ltd.
+
+
+name = 'netlink'
+version = 1
+allow_experimental_apis = true
+sources = files('rte_netlink.c',)
+
+headers = files('rte_netlink.h')
+
+deps += ['net']
diff --git a/drivers/net/tap/tap_netlink.c b/lib/librte_netlink/rte_netlink.c
similarity index 88%
rename from drivers/net/tap/tap_netlink.c
rename to lib/librte_netlink/rte_netlink.c
index 82c8dc0e6..a4ed07d30 100644
--- a/drivers/net/tap/tap_netlink.c
+++ b/lib/librte_netlink/rte_netlink.c
@@ -11,7 +11,7 @@
 #include <unistd.h>
 
 #include <rte_malloc.h>
-#include <tap_netlink.h>
+#include <rte_netlink.h>
 #include <rte_random.h>
 
 /* Must be quite large to support dumping a huge list of QDISC or filters. */
@@ -35,7 +35,7 @@ struct nested_tail {
  *   netlink socket file descriptor on success, -1 otherwise.
  */
 int
-tap_nl_init(uint32_t nl_groups)
+rte_nl_init(uint32_t nl_groups)
 {
 	int fd, sndbuf_size = SNDBUF_SIZE, rcvbuf_size = RCVBUF_SIZE;
 	struct sockaddr_nl local = {
@@ -73,7 +73,7 @@ tap_nl_init(uint32_t nl_groups)
  *   0 on success, -1 otherwise.
  */
 int
-tap_nl_final(int nlsk_fd)
+rte_nl_final(int nlsk_fd)
 {
 	if (close(nlsk_fd)) {
 		RTE_LOG(ERR, PMD, "Failed to close netlink socket: %s (%d)\n",
@@ -95,7 +95,7 @@ tap_nl_final(int nlsk_fd)
  *   the number of sent bytes on success, -1 otherwise.
  */
 int
-tap_nl_send(int nlsk_fd, struct nlmsghdr *nh)
+rte_nl_send(int nlsk_fd, struct nlmsghdr *nh)
 {
 	/* man 7 netlink EXAMPLE */
 	struct sockaddr_nl sa = {
@@ -126,7 +126,7 @@ tap_nl_send(int nlsk_fd, struct nlmsghdr *nh)
 
 /**
  * Check that the kernel sends an appropriate ACK in response
- * to an tap_nl_send().
+ * to an rte_nl_send().
  *
  * @param[in] nlsk_fd
  *   The netlink socket file descriptor used for communication.
@@ -135,14 +135,14 @@ tap_nl_send(int nlsk_fd, struct nlmsghdr *nh)
  *   0 on success, -1 otherwise with errno set.
  */
 int
-tap_nl_recv_ack(int nlsk_fd)
+rte_nl_recv_ack(int nlsk_fd)
 {
-	return tap_nl_recv(nlsk_fd, NULL, NULL);
+	return rte_nl_recv(nlsk_fd, NULL, NULL);
 }
 
 /**
  * Receive a message from the kernel on the netlink socket, following an
- * tap_nl_send().
+ * rte_nl_send().
  *
  * @param[in] nlsk_fd
  *   The netlink socket file descriptor used for communication.
@@ -155,7 +155,7 @@ tap_nl_recv_ack(int nlsk_fd)
  *   0 on success, -1 otherwise with errno set.
  */
 int
-tap_nl_recv(int nlsk_fd, int (*cb)(struct nlmsghdr *, void *arg), void *arg)
+rte_nl_recv(int nlsk_fd, int (*cb)(struct nlmsghdr *, void *arg), void *arg)
 {
 	/* man 7 netlink EXAMPLE */
 	struct sockaddr_nl sa;
@@ -220,7 +220,7 @@ tap_nl_recv(int nlsk_fd, int (*cb)(struct nlmsghdr *, void *arg), void *arg)
  *   The data to append.
  */
 void
-tap_nlattr_add(struct nlmsghdr *nh, unsigned short type,
+rte_nlattr_add(struct nlmsghdr *nh, unsigned short type,
 	   unsigned int data_len, const void *data)
 {
 	/* see man 3 rtnetlink */
@@ -244,9 +244,9 @@ tap_nlattr_add(struct nlmsghdr *nh, unsigned short type,
  *   The data to append.
  */
 void
-tap_nlattr_add8(struct nlmsghdr *nh, unsigned short type, uint8_t data)
+rte_nlattr_add8(struct nlmsghdr *nh, unsigned short type, uint8_t data)
 {
-	tap_nlattr_add(nh, type, sizeof(uint8_t), &data);
+	rte_nlattr_add(nh, type, sizeof(uint8_t), &data);
 }
 
 /**
@@ -260,9 +260,9 @@ tap_nlattr_add8(struct nlmsghdr *nh, unsigned short type, uint8_t data)
  *   The data to append.
  */
 void
-tap_nlattr_add16(struct nlmsghdr *nh, unsigned short type, uint16_t data)
+rte_nlattr_add16(struct nlmsghdr *nh, unsigned short type, uint16_t data)
 {
-	tap_nlattr_add(nh, type, sizeof(uint16_t), &data);
+	rte_nlattr_add(nh, type, sizeof(uint16_t), &data);
 }
 
 /**
@@ -276,14 +276,14 @@ tap_nlattr_add16(struct nlmsghdr *nh, unsigned short type, uint16_t data)
  *   The data to append.
  */
 void
-tap_nlattr_add32(struct nlmsghdr *nh, unsigned short type, uint32_t data)
+rte_nlattr_add32(struct nlmsghdr *nh, unsigned short type, uint32_t data)
 {
-	tap_nlattr_add(nh, type, sizeof(uint32_t), &data);
+	rte_nlattr_add(nh, type, sizeof(uint32_t), &data);
 }
 
 /**
  * Start a nested netlink attribute.
- * It must be followed later by a call to tap_nlattr_nested_finish().
+ * It must be followed later by a call to rte_nlattr_nested_finish().
  *
  * @param[in, out] msg
  *   The netlink message where to edit the nested_tails metadata.
@@ -294,7 +294,7 @@ tap_nlattr_add32(struct nlmsghdr *nh, unsigned short type, uint32_t data)
  *   -1 if adding a nested netlink attribute failed, 0 otherwise.
  */
 int
-tap_nlattr_nested_start(struct nlmsg *msg, uint16_t type)
+rte_nlattr_nested_start(struct nlmsg *msg, uint16_t type)
 {
 	struct nested_tail *tail;
 
@@ -308,7 +308,7 @@ tap_nlattr_nested_start(struct nlmsg *msg, uint16_t type)
 
 	tail->tail = (struct rtattr *)NLMSG_TAIL(&msg->nh);
 
-	tap_nlattr_add(&msg->nh, type, 0, NULL);
+	rte_nlattr_add(&msg->nh, type, 0, NULL);
 
 	tail->prev = msg->nested_tails;
 
@@ -319,7 +319,7 @@ tap_nlattr_nested_start(struct nlmsg *msg, uint16_t type)
 
 /**
  * End a nested netlink attribute.
- * It follows a call to tap_nlattr_nested_start().
+ * It follows a call to rte_nlattr_nested_start().
  * In effect, it will modify the nested attribute length to include every bytes
  * from the nested attribute start, up to here.
  *
@@ -327,7 +327,7 @@ tap_nlattr_nested_start(struct nlmsg *msg, uint16_t type)
  *   The netlink message where to edit the nested_tails metadata.
  */
 void
-tap_nlattr_nested_finish(struct nlmsg *msg)
+rte_nlattr_nested_finish(struct nlmsg *msg)
 {
 	struct nested_tail *tail = msg->nested_tails;
 
diff --git a/lib/librte_netlink/rte_netlink.h b/lib/librte_netlink/rte_netlink.h
new file mode 100644
index 000000000..29f7d64c5
--- /dev/null
+++ b/lib/librte_netlink/rte_netlink.h
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright 2017 6WIND S.A.
+ * Copyright 2017 Mellanox.
+ */
+
+#ifndef _RTE_NETLINK_H_
+#define _RTE_NETLINK_H_
+
+#include <ctype.h>
+#include <inttypes.h>
+#include <linux/rtnetlink.h>
+#include <linux/netlink.h>
+#include <stdio.h>
+
+#include <rte_log.h>
+
+#define NLMSG_BUF 512
+
+struct nlmsg {
+	struct nlmsghdr nh;
+	struct tcmsg t;
+	char buf[NLMSG_BUF];
+	struct nested_tail *nested_tails;
+};
+
+#define NLMSG_TAIL(nlh) (void *)((char *)(nlh) + NLMSG_ALIGN((nlh)->nlmsg_len))
+
+int rte_nl_init(uint32_t nl_groups);
+int rte_nl_final(int nlsk_fd);
+int rte_nl_send(int nlsk_fd, struct nlmsghdr *nh);
+int rte_nl_recv(int nlsk_fd, int (*callback)(struct nlmsghdr *, void *),
+		void *arg);
+int rte_nl_recv_ack(int nlsk_fd);
+void rte_nlattr_add(struct nlmsghdr *nh, unsigned short type,
+		    unsigned int data_len, const void *data);
+void rte_nlattr_add8(struct nlmsghdr *nh, unsigned short type, uint8_t data);
+void rte_nlattr_add16(struct nlmsghdr *nh, unsigned short type, uint16_t data);
+void rte_nlattr_add32(struct nlmsghdr *nh, unsigned short type, uint32_t data);
+int rte_nlattr_nested_start(struct nlmsg *msg, uint16_t type);
+void rte_nlattr_nested_finish(struct nlmsg *msg);
+
+#endif /* _RTE_NETLINK_H_ */
diff --git a/lib/librte_netlink/rte_netlink_version.map b/lib/librte_netlink/rte_netlink_version.map
new file mode 100644
index 000000000..5aa9b6228
--- /dev/null
+++ b/lib/librte_netlink/rte_netlink_version.map
@@ -0,0 +1,17 @@
+DPDK_18.05 {
+	global:
+
+	rte_nl_init;
+	rte_nl_final;
+	rte_nl_send;
+	rte_nl_recv;
+	rte_nl_recv_ack;
+	rte_nlattr_add;
+	rte_nlattr_add8;
+	rte_nlattr_add16;
+	rte_nlattr_add32;
+	rte_nlattr_nested_start;
+	rte_nlattr_nested_finish;
+
+	local: *;
+};
diff --git a/lib/meson.build b/lib/meson.build
index ef6159170..873a39785 100644
--- a/lib/meson.build
+++ b/lib/meson.build
@@ -23,7 +23,7 @@ libraries = [ 'compat', # just a header, used for versioning
 	# add pkt framework libs which use other libs from above
 	'port', 'table', 'pipeline',
 	# flow_classify lib depends on pkt framework table lib
-	'flow_classify']
+	'flow_classify', 'netlink']
 
 foreach l:libraries
 	build = true
diff --git a/mk/rte.app.mk b/mk/rte.app.mk
index 3eb41d176..5cc8ed622 100644
--- a/mk/rte.app.mk
+++ b/mk/rte.app.mk
@@ -106,6 +106,7 @@ _LDLIBS-$(CONFIG_RTE_LIBRTE_EAL)            += -lrte_eal
 _LDLIBS-$(CONFIG_RTE_LIBRTE_CMDLINE)        += -lrte_cmdline
 _LDLIBS-$(CONFIG_RTE_LIBRTE_REORDER)        += -lrte_reorder
 _LDLIBS-$(CONFIG_RTE_LIBRTE_SCHED)          += -lrte_sched
+_LDLIBS-$(CONFIG_RTE_LIBRTE_NETLINK)        += -lrte_netlink
 
 ifeq ($(CONFIG_RTE_EXEC_ENV_LINUXAPP),y)
 _LDLIBS-$(CONFIG_RTE_LIBRTE_KNI)            += -lrte_kni
