diff --git a/drivers/net/mlx5/mlx5_flow_tcf.c b/drivers/net/mlx5/mlx5_flow_tcf.c
index d6840d5..efa9c3b 100644
--- a/drivers/net/mlx5/mlx5_flow_tcf.c
+++ b/drivers/net/mlx5/mlx5_flow_tcf.c
@@ -3443,6 +3443,432 @@ struct pedit_parser {
 	return -err;
 }
 
+/* VTEP device list is shared between PMD port instances. */
+static LIST_HEAD(, mlx5_flow_tcf_vtep)
+			vtep_list_vxlan = LIST_HEAD_INITIALIZER();
+static pthread_mutex_t vtep_list_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+/**
+ * Deletes VTEP network device.
+ *
+ * @param[in] tcf
+ *   Context object initialized by mlx5_flow_tcf_context_create().
+ * @param[in] vtep
+ *   Object represinting the network device to delete. Memory
+ *   allocated for this object is freed by routine.
+ */
+static void
+flow_tcf_delete_iface(struct mlx5_flow_tcf_context *tcf,
+		      struct mlx5_flow_tcf_vtep *vtep)
+{
+	struct nlmsghdr *nlh;
+	struct ifinfomsg *ifm;
+	alignas(struct nlmsghdr)
+	uint8_t buf[mnl_nlmsg_size(MNL_ALIGN(sizeof(*ifm))) + 8];
+	int ret;
+
+	assert(!vtep->refcnt);
+	if (vtep->created && vtep->ifindex) {
+		DRV_LOG(INFO, "VTEP delete (%d)", vtep->ifindex);
+		nlh = mnl_nlmsg_put_header(buf);
+		nlh->nlmsg_type = RTM_DELLINK;
+		nlh->nlmsg_flags = NLM_F_REQUEST;
+		ifm = mnl_nlmsg_put_extra_header(nlh, sizeof(*ifm));
+		ifm->ifi_family = AF_UNSPEC;
+		ifm->ifi_index = vtep->ifindex;
+		ret = flow_tcf_nl_ack(tcf, nlh, 0, NULL, NULL);
+		if (ret)
+			DRV_LOG(WARNING, "netlink: error deleting VXLAN "
+					 "encap/decap ifindex %u",
+					 ifm->ifi_index);
+	}
+	rte_free(vtep);
+}
+
+/**
+ * Creates VTEP network device.
+ *
+ * @param[in] tcf
+ *   Context object initialized by mlx5_flow_tcf_context_create().
+ * @param[in] ifouter
+ *   Outer interface to attach new-created VXLAN device
+ *   If zero the VXLAN device will not be attached to any device.
+ * @param[in] port
+ *   UDP port of created VTEP device.
+ * @param[out] error
+ *   Perform verbose error reporting if not NULL.
+ *
+ * @return
+ * Pointer to created device structure on success, NULL otherwise
+ * and rte_errno is set.
+ */
+#ifndef HAVE_IFLA_VXLAN_COLLECT_METADATA
+static struct mlx5_flow_tcf_vtep*
+flow_tcf_create_iface(struct mlx5_flow_tcf_context *tcf __rte_unused,
+		      unsigned int ifouter __rte_unused,
+		      uint16_t port __rte_unused,
+		      struct rte_flow_error *error)
+{
+	rte_flow_error_set(error, ENOTSUP,
+			 RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+			 "netlink: failed to create VTEP, "
+			 "VXLAN metadat is not supported by kernel");
+	return NULL;
+}
+#else
+static struct mlx5_flow_tcf_vtep*
+flow_tcf_create_iface(struct mlx5_flow_tcf_context *tcf,
+		      unsigned int ifouter,
+		      uint16_t port, struct rte_flow_error *error)
+{
+	struct mlx5_flow_tcf_vtep *vtep;
+	struct nlmsghdr *nlh;
+	struct ifinfomsg *ifm;
+	char name[sizeof(MLX5_VXLAN_DEVICE_PFX) + 24];
+	alignas(struct nlmsghdr)
+	uint8_t buf[mnl_nlmsg_size(sizeof(*ifm)) + 128 +
+		       SZ_NLATTR_DATA_OF(sizeof(name)) +
+		       SZ_NLATTR_NEST * 2 +
+		       SZ_NLATTR_STRZ_OF("vxlan") +
+		       SZ_NLATTR_DATA_OF(sizeof(uint32_t)) +
+		       SZ_NLATTR_DATA_OF(sizeof(uint32_t)) +
+		       SZ_NLATTR_DATA_OF(sizeof(uint16_t)) +
+		       SZ_NLATTR_DATA_OF(sizeof(uint8_t))];
+	struct nlattr *na_info;
+	struct nlattr *na_vxlan;
+	rte_be16_t vxlan_port = RTE_BE16(port);
+	int ret;
+
+	vtep = rte_zmalloc(__func__, sizeof(*vtep),
+			alignof(struct mlx5_flow_tcf_vtep));
+	if (!vtep) {
+		rte_flow_error_set
+			(error, ENOMEM, RTE_FLOW_ERROR_TYPE_UNSPECIFIED,
+			 NULL, "unadble to allocate memory for VTEP desc");
+		return NULL;
+	}
+	*vtep = (struct mlx5_flow_tcf_vtep){
+			.refcnt = 0,
+			.port = port,
+			.created = 0,
+			.ifouter = 0,
+			.ifindex = 0,
+			.local = LIST_HEAD_INITIALIZER(),
+			.neigh = LIST_HEAD_INITIALIZER(),
+	};
+	memset(buf, 0, sizeof(buf));
+	nlh = mnl_nlmsg_put_header(buf);
+	nlh->nlmsg_type = RTM_NEWLINK;
+	nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE  | NLM_F_EXCL;
+	ifm = mnl_nlmsg_put_extra_header(nlh, sizeof(*ifm));
+	ifm->ifi_family = AF_UNSPEC;
+	ifm->ifi_type = 0;
+	ifm->ifi_index = 0;
+	ifm->ifi_flags = IFF_UP;
+	ifm->ifi_change = 0xffffffff;
+	snprintf(name, sizeof(name), "%s%u", MLX5_VXLAN_DEVICE_PFX, port);
+	mnl_attr_put_strz(nlh, IFLA_IFNAME, name);
+	na_info = mnl_attr_nest_start(nlh, IFLA_LINKINFO);
+	assert(na_info);
+	mnl_attr_put_strz(nlh, IFLA_INFO_KIND, "vxlan");
+	na_vxlan = mnl_attr_nest_start(nlh, IFLA_INFO_DATA);
+	if (ifouter)
+		mnl_attr_put_u32(nlh, IFLA_VXLAN_LINK, ifouter);
+	assert(na_vxlan);
+	mnl_attr_put_u8(nlh, IFLA_VXLAN_COLLECT_METADATA, 1);
+	mnl_attr_put_u8(nlh, IFLA_VXLAN_UDP_ZERO_CSUM6_RX, 1);
+	mnl_attr_put_u8(nlh, IFLA_VXLAN_LEARNING, 0);
+	mnl_attr_put_u16(nlh, IFLA_VXLAN_PORT, vxlan_port);
+	mnl_attr_nest_end(nlh, na_vxlan);
+	mnl_attr_nest_end(nlh, na_info);
+	assert(sizeof(buf) >= nlh->nlmsg_len);
+	ret = flow_tcf_nl_ack(tcf, nlh, 0, NULL, NULL);
+	if (ret)
+		DRV_LOG(WARNING,
+			"netlink: VTEP %s create failure (%d)",
+			name, rte_errno);
+	else
+		vtep->created = 1;
+	if (ret && ifouter)
+		ret = 0;
+	else
+		ret = if_nametoindex(name);
+	if (ret) {
+		vtep->ifindex = ret;
+		vtep->ifouter = ifouter;
+		memset(buf, 0, sizeof(buf));
+		nlh = mnl_nlmsg_put_header(buf);
+		nlh->nlmsg_type = RTM_NEWLINK;
+		nlh->nlmsg_flags = NLM_F_REQUEST;
+		ifm = mnl_nlmsg_put_extra_header(nlh, sizeof(*ifm));
+		ifm->ifi_family = AF_UNSPEC;
+		ifm->ifi_type = 0;
+		ifm->ifi_index = vtep->ifindex;
+		ifm->ifi_flags = IFF_UP;
+		ifm->ifi_change = IFF_UP;
+		ret = flow_tcf_nl_ack(tcf, nlh, 0, NULL, NULL);
+		if (ret) {
+			DRV_LOG(WARNING,
+				"netlink: VTEP %s set link up failure (%d)",
+				name, rte_errno);
+			rte_free(vtep);
+			rte_flow_error_set
+				(error, -errno,
+				 RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+				 "netlink: failed to set VTEP link up");
+			vtep = NULL;
+		} else {
+			ret = mlx5_flow_tcf_init(tcf, vtep->ifindex, error);
+			if (ret)
+				DRV_LOG(WARNING,
+				"VTEP %s init failure (%d)", name, rte_errno);
+		}
+	} else {
+		DRV_LOG(WARNING,
+			"VTEP %s failed to get index (%d)", name, errno);
+		rte_flow_error_set
+			(error, -errno,
+			 RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+			 !vtep->created ? "netlink: failed to create VTEP" :
+			 "netlink: failed to retrieve VTEP ifindex");
+			 ret = 1;
+	}
+	if (ret) {
+		flow_tcf_delete_iface(tcf, vtep);
+		vtep = NULL;
+	}
+	DRV_LOG(INFO, "VTEP create (%d, %s)", vtep->port, vtep ? "OK" : "error");
+	return vtep;
+}
+#endif /* HAVE_IFLA_VXLAN_COLLECT_METADATA */
+
+/**
+ * Create target interface index for VXLAN tunneling decapsulation.
+ * In order to share the UDP port within the other interfaces the
+ * VXLAN device created as not attached to any interface (if created).
+ *
+ * @param[in] tcf
+ *   Context object initialized by mlx5_flow_tcf_context_create().
+ * @param[in] dev_flow
+ *   Flow tcf object with tunnel structure pointer set.
+ * @param[out] error
+ *   Perform verbose error reporting if not NULL.
+ * @return
+ *   Interface index on success, zero otherwise and rte_errno is set.
+ */
+static unsigned int
+flow_tcf_decap_vtep_create(struct mlx5_flow_tcf_context *tcf,
+			   struct mlx5_flow *dev_flow,
+			   struct rte_flow_error *error)
+{
+	struct mlx5_flow_tcf_vtep *vtep, *vlst;
+	uint16_t port = dev_flow->tcf.vxlan_decap->udp_port;
+
+	vtep = NULL;
+	LIST_FOREACH(vlst, &vtep_list_vxlan, next) {
+		if (vlst->port == port) {
+			vtep = vlst;
+			break;
+		}
+	}
+	if (!vtep) {
+		vtep = flow_tcf_create_iface(tcf, 0, port, error);
+		if (vtep)
+			LIST_INSERT_HEAD(&vtep_list_vxlan, vtep, next);
+	} else {
+		if (vtep->ifouter) {
+			rte_flow_error_set(error, -errno,
+				RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+				"Failed to create decap VTEP, attached "
+				"device with the same UDP port exists");
+				vtep = NULL;
+		}
+	}
+	if (vtep) {
+		vtep->refcnt++;
+		assert(vtep->ifindex);
+		return vtep->ifindex;
+	} else {
+		return 0;
+	}
+}
+
+/**
+ * Creates target interface index for VXLAN tunneling encapsulation.
+ *
+ * @param[in] tcf
+ *   Context object initialized by mlx5_flow_tcf_context_create().
+ * @param[in] ifouter
+ *   Network interface index to attach VXLAN encap device to.
+ * @param[in] dev_flow
+ *   Flow tcf object with tunnel structure pointer set.
+ * @param[out] error
+ *   Perform verbose error reporting if not NULL.
+ * @return
+ *   Interface index on success, zero otherwise and rte_errno is set.
+ */
+static unsigned int
+flow_tcf_encap_vtep_create(struct mlx5_flow_tcf_context *tcf,
+			    unsigned int ifouter,
+			    struct mlx5_flow *dev_flow __rte_unused,
+			    struct rte_flow_error *error)
+{
+	static uint16_t encap_port = MLX5_VXLAN_PORT_RANGE_MIN - 1;
+	struct mlx5_flow_tcf_vtep *vtep, *vlst;
+
+	assert(ifouter);
+	/* Look whether the attached VTEP for encap is created. */
+	vtep = NULL;
+	LIST_FOREACH(vlst, &vtep_list_vxlan, next) {
+		if (vlst->ifouter == ifouter) {
+			vtep = vlst;
+			break;
+		}
+	}
+	if (!vtep) {
+		uint16_t pcnt;
+
+		/* Not found, we should create the new attached VTEP. */
+/*
+ * TODO: not implemented yet
+ * flow_tcf_encap_iface_cleanup(tcf, ifouter);
+ * flow_tcf_encap_local_cleanup(tcf, ifouter);
+ * flow_tcf_encap_neigh_cleanup(tcf, ifouter);
+ */
+		for (pcnt = 0; pcnt <= (MLX5_VXLAN_PORT_RANGE_MAX
+				     - MLX5_VXLAN_PORT_RANGE_MIN); pcnt++) {
+			encap_port++;
+			/* Wraparound the UDP port index. */
+			if (encap_port < MLX5_VXLAN_PORT_RANGE_MIN ||
+			    encap_port > MLX5_VXLAN_PORT_RANGE_MAX)
+				encap_port = MLX5_VXLAN_PORT_RANGE_MIN;
+			/* Check whether UDP port is in already in use. */
+			vtep = NULL;
+			LIST_FOREACH(vlst, &vtep_list_vxlan, next) {
+				if (vlst->port == encap_port) {
+					vtep = vlst;
+					break;
+				}
+			}
+			if (vtep) {
+				vtep = NULL;
+				continue;
+			}
+			vtep = flow_tcf_create_iface(tcf, ifouter,
+						     encap_port, error);
+			if (vtep) {
+				LIST_INSERT_HEAD(&vtep_list_vxlan, vtep, next);
+				break;
+			}
+			if (rte_errno != EEXIST)
+				break;
+		}
+	}
+	if (!vtep)
+		return 0;
+	vtep->refcnt++;
+	assert(vtep->ifindex);
+	return vtep->ifindex;
+}
+
+/**
+ * Creates target interface index for tunneling of any type.
+ *
+ * @param[in] tcf
+ *   Context object initialized by mlx5_flow_tcf_context_create().
+ * @param[in] ifouter
+ *   Network interface index to attach VXLAN encap device to.
+ * @param[in] dev_flow
+ *   Flow tcf object with tunnel structure pointer set.
+ * @param[out] error
+ *   Perform verbose error reporting if not NULL.
+ * @return
+ *   Interface index on success, zero otherwise and rte_errno is set.
+ */
+static unsigned int
+flow_tcf_tunnel_vtep_create(struct mlx5_flow_tcf_context *tcf,
+			    unsigned int ifouter,
+			    struct mlx5_flow *dev_flow,
+			    struct rte_flow_error *error)
+{
+	unsigned int ret;
+
+	assert(dev_flow->tcf.tunnel);
+	pthread_mutex_lock(&vtep_list_mutex);
+	switch (dev_flow->tcf.tunnel->type) {
+	case MLX5_FLOW_TCF_TUNACT_VXLAN_ENCAP:
+		ret = flow_tcf_encap_vtep_create(tcf, ifouter,
+						 dev_flow, error);
+		break;
+	case MLX5_FLOW_TCF_TUNACT_VXLAN_DECAP:
+		ret = flow_tcf_decap_vtep_create(tcf, dev_flow, error);
+		break;
+	default:
+		rte_flow_error_set(error, ENOTSUP,
+				RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+				"unsupported tunnel type");
+		ret = 0;
+		break;
+	}
+	pthread_mutex_unlock(&vtep_list_mutex);
+	return ret;
+}
+
+/**
+ * Deletes tunneling interface by UDP port.
+ *
+ * @param[in] tcf
+ *   Context object initialized by mlx5_flow_tcf_context_create().
+ * @param[in] ifindex
+ *   Network interface index of VXLAN device.
+ * @param[in] dev_flow
+ *   Flow tcf object with tunnel structure pointer set.
+ */
+static void
+flow_tcf_tunnel_vtep_delete(struct mlx5_flow_tcf_context *tcf,
+			    unsigned int ifindex,
+			    struct mlx5_flow *dev_flow)
+{
+	struct mlx5_flow_tcf_vtep *vtep, *vlst;
+
+	assert(dev_flow->tcf.tunnel);
+	pthread_mutex_lock(&vtep_list_mutex);
+	vtep = NULL;
+	LIST_FOREACH(vlst, &vtep_list_vxlan, next) {
+		if (vlst->ifindex == ifindex) {
+			vtep = vlst;
+			break;
+		}
+	}
+	if (!vtep) {
+		DRV_LOG(WARNING, "No VTEP device found in the list");
+		goto exit;
+	}
+	switch (dev_flow->tcf.tunnel->type) {
+	case MLX5_FLOW_TCF_TUNACT_VXLAN_DECAP:
+		break;
+	case MLX5_FLOW_TCF_TUNACT_VXLAN_ENCAP:
+/*
+ * TODO: Remove the encap ancillary rules first.
+ * flow_tcf_encap_neigh(tcf, vtep, dev_flow, false, NULL);
+ * flow_tcf_encap_local(tcf, vtep, dev_flow, false, NULL);
+ */
+		break;
+	default:
+		assert(false);
+		DRV_LOG(WARNING, "Unsupported tunnel type");
+		break;
+	}
+	assert(dev_flow->tcf.tunnel->ifindex_tun == vtep->ifindex);
+	assert(vtep->refcnt);
+	if (!vtep->refcnt || !--vtep->refcnt) {
+		LIST_REMOVE(vtep, next);
+		flow_tcf_delete_iface(tcf, vtep);
+	}
+exit:
+	pthread_mutex_unlock(&vtep_list_mutex);
+}
+
 /**
  * Apply flow to E-Switch by sending Netlink message.
  *
@@ -3461,18 +3887,61 @@ struct pedit_parser {
 	       struct rte_flow_error *error)
 {
 	struct priv *priv = dev->data->dev_private;
-	struct mlx5_flow_tcf_context *nl = priv->tcf_context;
+	struct mlx5_flow_tcf_context *tcf = priv->tcf_context;
 	struct mlx5_flow *dev_flow;
 	struct nlmsghdr *nlh;
+	int ret;
 
 	dev_flow = LIST_FIRST(&flow->dev_flows);
 	/* E-Switch flow can't be expanded. */
 	assert(!LIST_NEXT(dev_flow, next));
+	if (dev_flow->tcf.applied)
+		return 0;
 	nlh = dev_flow->tcf.nlh;
 	nlh->nlmsg_type = RTM_NEWTFILTER;
 	nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL;
-	if (!flow_tcf_nl_ack(nl, nlh, 0, NULL, NULL))
+	if (dev_flow->tcf.tunnel) {
+		/*
+		 * Replace the interface index, target for
+		 * encapsulation, source for decapsulation.
+		 */
+		assert(!dev_flow->tcf.tunnel->ifindex_tun);
+		assert(dev_flow->tcf.tunnel->ifindex_ptr);
+		/* Create actual VTEP device when rule is being applied. */
+		dev_flow->tcf.tunnel->ifindex_tun
+			= flow_tcf_tunnel_vtep_create(tcf,
+					*dev_flow->tcf.tunnel->ifindex_ptr,
+					dev_flow, error);
+			DRV_LOG(INFO, "Replace ifindex: %d->%d",
+				dev_flow->tcf.tunnel->ifindex_tun,
+				*dev_flow->tcf.tunnel->ifindex_ptr);
+		if (!dev_flow->tcf.tunnel->ifindex_tun)
+			return -rte_errno;
+		dev_flow->tcf.tunnel->ifindex_org
+			= *dev_flow->tcf.tunnel->ifindex_ptr;
+		*dev_flow->tcf.tunnel->ifindex_ptr
+			= dev_flow->tcf.tunnel->ifindex_tun;
+	}
+	ret = flow_tcf_nl_ack(tcf, nlh, 0, NULL, NULL);
+	if (dev_flow->tcf.tunnel) {
+		DRV_LOG(INFO, "Restore ifindex: %d->%d",
+				dev_flow->tcf.tunnel->ifindex_org,
+				*dev_flow->tcf.tunnel->ifindex_ptr);
+		*dev_flow->tcf.tunnel->ifindex_ptr
+			= dev_flow->tcf.tunnel->ifindex_org;
+		dev_flow->tcf.tunnel->ifindex_org = 0;
+	}
+	if (!ret) {
+		dev_flow->tcf.applied = 1;
 		return 0;
+	}
+	DRV_LOG(WARNING, "netlink: failed to create TC rule (%d)", rte_errno);
+	if (dev_flow->tcf.tunnel->ifindex_tun) {
+		flow_tcf_tunnel_vtep_delete(tcf,
+					    dev_flow->tcf.tunnel->ifindex_tun,
+					    dev_flow);
+		dev_flow->tcf.tunnel->ifindex_tun = 0;
+	}
 	return rte_flow_error_set(error, rte_errno,
 				  RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
 				  "netlink: failed to create TC flow rule");
@@ -3490,7 +3959,7 @@ struct pedit_parser {
 flow_tcf_remove(struct rte_eth_dev *dev, struct rte_flow *flow)
 {
 	struct priv *priv = dev->data->dev_private;
-	struct mlx5_flow_tcf_context *nl = priv->tcf_context;
+	struct mlx5_flow_tcf_context *tcf = priv->tcf_context;
 	struct mlx5_flow *dev_flow;
 	struct nlmsghdr *nlh;
 
@@ -3501,10 +3970,36 @@ struct pedit_parser {
 		return;
 	/* E-Switch flow can't be expanded. */
 	assert(!LIST_NEXT(dev_flow, next));
+	if (!dev_flow->tcf.applied)
+		return;
+	if (dev_flow->tcf.tunnel) {
+		/*
+		 * Replace the interface index, target for
+		 * encapsulation, source for decapsulation.
+		 */
+		assert(dev_flow->tcf.tunnel->ifindex_tun);
+		assert(dev_flow->tcf.tunnel->ifindex_ptr);
+		dev_flow->tcf.tunnel->ifindex_org
+			= *dev_flow->tcf.tunnel->ifindex_ptr;
+		*dev_flow->tcf.tunnel->ifindex_ptr
+			= dev_flow->tcf.tunnel->ifindex_tun;
+	}
 	nlh = dev_flow->tcf.nlh;
 	nlh->nlmsg_type = RTM_DELTFILTER;
 	nlh->nlmsg_flags = NLM_F_REQUEST;
-	flow_tcf_nl_ack(nl, nlh, 0, NULL, NULL);
+	flow_tcf_nl_ack(tcf, nlh, 0, NULL, NULL);
+	if (dev_flow->tcf.tunnel) {
+		*dev_flow->tcf.tunnel->ifindex_ptr
+			= dev_flow->tcf.tunnel->ifindex_org;
+		dev_flow->tcf.tunnel->ifindex_org = 0;
+		if (dev_flow->tcf.tunnel->ifindex_tun) {
+			flow_tcf_tunnel_vtep_delete(tcf,
+					dev_flow->tcf.tunnel->ifindex_tun,
+					dev_flow);
+			dev_flow->tcf.tunnel->ifindex_tun = 0;
+		}
+	}
+	dev_flow->tcf.applied = 0;
 }
 
 /**
