diff --git a/drivers/net/mlx5/mlx5.c b/drivers/net/mlx5/mlx5.c
index 9a504a31c..c10ca4ae5 100644
--- a/drivers/net/mlx5/mlx5.c
+++ b/drivers/net/mlx5/mlx5.c
@@ -282,8 +282,8 @@ mlx5_dev_close(struct rte_eth_dev *dev)
 		close(priv->nl_socket_route);
 	if (priv->nl_socket_rdma >= 0)
 		close(priv->nl_socket_rdma);
-	if (priv->mnl_socket)
-		mlx5_nl_flow_socket_destroy(priv->mnl_socket);
+	if (priv->nl_flow_ctx)
+		mlx5_nl_flow_ctx_destroy(priv->nl_flow_ctx);
 	ret = mlx5_hrxq_ibv_verify(dev);
 	if (ret)
 		DRV_LOG(WARNING, "port %u some hash Rx queue still remain",
@@ -1136,13 +1136,13 @@ mlx5_dev_spawn(struct rte_device *dpdk_dev,
 	claim_zero(mlx5_mac_addr_add(eth_dev, &mac, 0, 0));
 	if (vf && config.vf_nl_en)
 		mlx5_nl_mac_addr_sync(eth_dev);
-	priv->mnl_socket = mlx5_nl_flow_socket_create();
-	if (!priv->mnl_socket ||
+	priv->nl_flow_ctx = mlx5_nl_flow_ctx_create(eth_dev->device->numa_node);
+	if (!priv->nl_flow_ctx ||
 	    !priv->ifindex ||
-	    mlx5_nl_flow_ifindex_init(priv->mnl_socket, priv->ifindex,
+	    mlx5_nl_flow_ifindex_init(priv->nl_flow_ctx, priv->ifindex,
 				      &flow_error)) {
-		if (!priv->mnl_socket) {
-			flow_error.message = "cannot open libmnl socket";
+		if (!priv->nl_flow_ctx) {
+			flow_error.message = "cannot create NL flow context";
 		} else if (!priv->ifindex) {
 			rte_errno = ENXIO;
 			flow_error.message = "unknown network interface index";
@@ -1204,8 +1204,8 @@ mlx5_dev_spawn(struct rte_device *dpdk_dev,
 			close(priv->nl_socket_route);
 		if (priv->nl_socket_rdma >= 0)
 			close(priv->nl_socket_rdma);
-		if (priv->mnl_socket)
-			mlx5_nl_flow_socket_destroy(priv->mnl_socket);
+		if (priv->nl_flow_ctx)
+			mlx5_nl_flow_ctx_destroy(priv->nl_flow_ctx);
 		if (own_domain_id)
 			claim_zero(rte_eth_switch_domain_free(priv->domain_id));
 		rte_free(priv);
diff --git a/drivers/net/mlx5/mlx5.h b/drivers/net/mlx5/mlx5.h
index 287cfc643..210f4ea11 100644
--- a/drivers/net/mlx5/mlx5.h
+++ b/drivers/net/mlx5/mlx5.h
@@ -162,7 +162,8 @@ struct mlx5_nl_flow_ptoi {
 	unsigned int ifindex; /**< Network interface index. */
 };
 
-struct mnl_socket;
+struct mlx5_nl_flow;
+struct mlx5_nl_flow_ctx;
 
 struct priv {
 	LIST_ENTRY(priv) mem_event_cb; /* Called by memory event callback. */
@@ -229,7 +230,7 @@ struct priv {
 	rte_spinlock_t uar_lock[MLX5_UAR_PAGE_NUM_MAX];
 	/* UAR same-page access control required in 32bit implementations. */
 #endif
-	struct mnl_socket *mnl_socket; /* Libmnl socket. */
+	struct mlx5_nl_flow_ctx *nl_flow_ctx; /* Context for NL flow rules. */
 };
 
 #define PORT_ID(priv) ((priv)->dev_data->port_id)
@@ -396,21 +397,24 @@ int mlx5_nl_switch_info(int nl, unsigned int ifindex,
 
 /* mlx5_nl_flow.c */
 
-int mlx5_nl_flow_transpose(void *buf,
+int mlx5_nl_flow_transpose(struct mlx5_nl_flow *nl_flow,
 			   size_t size,
 			   const struct mlx5_nl_flow_ptoi *ptoi,
 			   const struct rte_flow_attr *attr,
 			   const struct rte_flow_item *pattern,
 			   const struct rte_flow_action *actions,
 			   struct rte_flow_error *error);
-void mlx5_nl_flow_brand(void *buf, uint32_t handle);
-int mlx5_nl_flow_create(struct mnl_socket *nl, void *buf,
+void mlx5_nl_flow_brand(struct mlx5_nl_flow *nl_flow, uint32_t handle);
+int mlx5_nl_flow_create(struct mlx5_nl_flow_ctx *ctx,
+			struct mlx5_nl_flow *nl_flow,
 			struct rte_flow_error *error);
-int mlx5_nl_flow_destroy(struct mnl_socket *nl, void *buf,
+int mlx5_nl_flow_destroy(struct mlx5_nl_flow_ctx *ctx,
+			 struct mlx5_nl_flow *nl_flow,
 			 struct rte_flow_error *error);
-int mlx5_nl_flow_ifindex_init(struct mnl_socket *nl, unsigned int ifindex,
+int mlx5_nl_flow_ifindex_init(struct mlx5_nl_flow_ctx *ctx,
+			      unsigned int ifindex,
 			      struct rte_flow_error *error);
-struct mnl_socket *mlx5_nl_flow_socket_create(void);
-void mlx5_nl_flow_socket_destroy(struct mnl_socket *nl);
+struct mlx5_nl_flow_ctx *mlx5_nl_flow_ctx_create(int socket);
+void mlx5_nl_flow_ctx_destroy(struct mlx5_nl_flow_ctx *ctx);
 
 #endif /* RTE_PMD_MLX5_H_ */
diff --git a/drivers/net/mlx5/mlx5_flow.c b/drivers/net/mlx5/mlx5_flow.c
index f093a5ed0..77e510dc3 100644
--- a/drivers/net/mlx5/mlx5_flow.c
+++ b/drivers/net/mlx5/mlx5_flow.c
@@ -2485,7 +2485,7 @@ mlx5_flow_merge_switch(struct rte_eth_dev *dev,
 	ptoi[n].ifindex = 0;
 	if (flow_size < off)
 		flow_size = 0;
-	ret = mlx5_nl_flow_transpose((uint8_t *)flow + off,
+	ret = mlx5_nl_flow_transpose((void *)((uint8_t *)flow + off),
 				     flow_size ? flow_size - off : 0,
 				     ptoi, attr, pattern, actions, error);
 	if (ret < 0)
@@ -2885,8 +2885,8 @@ mlx5_flow_remove(struct rte_eth_dev *dev, struct rte_flow *flow)
 	struct priv *priv = dev->data->dev_private;
 	struct mlx5_flow_verbs *verbs;
 
-	if (flow->nl_flow && priv->mnl_socket)
-		mlx5_nl_flow_destroy(priv->mnl_socket, flow->nl_flow, NULL);
+	if (flow->nl_flow && priv->nl_flow_ctx)
+		mlx5_nl_flow_destroy(priv->nl_flow_ctx, flow->nl_flow, NULL);
 	LIST_FOREACH(verbs, &flow->verbs, next) {
 		if (verbs->flow) {
 			claim_zero(mlx5_glue->destroy_flow(verbs->flow));
@@ -2975,8 +2975,8 @@ mlx5_flow_apply(struct rte_eth_dev *dev, struct rte_flow *flow,
 		}
 	}
 	if (flow->nl_flow &&
-	    priv->mnl_socket &&
-	    mlx5_nl_flow_create(priv->mnl_socket, flow->nl_flow, error))
+	    priv->nl_flow_ctx &&
+	    mlx5_nl_flow_create(priv->nl_flow_ctx, flow->nl_flow, error))
 		goto error;
 	return 0;
 error:
diff --git a/drivers/net/mlx5/mlx5_nl_flow.c b/drivers/net/mlx5/mlx5_nl_flow.c
index e720728b7..d20416026 100644
--- a/drivers/net/mlx5/mlx5_nl_flow.c
+++ b/drivers/net/mlx5/mlx5_nl_flow.c
@@ -22,10 +22,10 @@
 #include <sys/socket.h>
 
 #include <rte_byteorder.h>
-#include <rte_cycles.h>
 #include <rte_errno.h>
 #include <rte_ether.h>
 #include <rte_flow.h>
+#include <rte_malloc.h>
 
 #include "mlx5.h"
 #include "mlx5_autoconf.h"
@@ -148,6 +148,23 @@ struct tc_vlan {
 #define TCA_FLOWER_KEY_VLAN_ETH_TYPE 25
 #endif
 
+/** Context object required by most functions. */
+struct mlx5_nl_flow_ctx {
+	int socket; /**< NUMA socket for memory allocations. */
+	uint32_t seq; /**< Message sequence number for @p nl. */
+	struct mnl_socket *nl; /**< @p NETLINK_ROUTE libmnl socket. */
+};
+
+/** Flow rule descriptor. */
+struct mlx5_nl_flow {
+	uint32_t size; /**< Size of this object. */
+	uint32_t applied:1; /**< Whether rule is currently applied. */
+	unsigned int *ifindex_src; /**< Source interface. */
+	unsigned int *ifindex_dst; /**< Destination interface. */
+	alignas(struct nlmsghdr)
+	uint8_t msg[]; /**< Netlink message data. */
+};
+
 /** Parser state definitions for mlx5_nl_flow_trans[]. */
 enum mlx5_nl_flow_trans {
 	INVALID,
@@ -350,10 +367,10 @@ mlx5_nl_flow_item_mask(const struct rte_flow_item *item,
  * Subsequent entries enable this function to resolve other DPDK port IDs
  * found in the flow rule.
  *
- * @param[out] buf
- *   Output message buffer. May be NULL when @p size is 0.
+ * @param[out] nl_flow
+ *   Output buffer. May be NULL when @p size is 0.
  * @param size
- *   Size of @p buf. Message may be truncated if not large enough.
+ *   Size of @p nl_flow. May be truncated if not large enough.
  * @param[in] ptoi
  *   DPDK port ID to network interface index translation table. This table
  *   is terminated by an entry with a zero ifindex value.
@@ -372,7 +389,7 @@ mlx5_nl_flow_item_mask(const struct rte_flow_item *item,
  *   otherwise and rte_errno is set.
  */
 int
-mlx5_nl_flow_transpose(void *buf,
+mlx5_nl_flow_transpose(struct mlx5_nl_flow *nl_flow,
 		       size_t size,
 		       const struct mlx5_nl_flow_ptoi *ptoi,
 		       const struct rte_flow_attr *attr,
@@ -380,8 +397,9 @@ mlx5_nl_flow_transpose(void *buf,
 		       const struct rte_flow_action *actions,
 		       struct rte_flow_error *error)
 {
-	alignas(struct nlmsghdr)
-	uint8_t buf_tmp[mnl_nlmsg_size(sizeof(struct tcmsg) + 1024)];
+	alignas(struct mlx5_nl_flow)
+	uint8_t buf_tmp[1024];
+	void *buf;
 	const struct rte_flow_item *item;
 	const struct rte_flow_action *action;
 	unsigned int n;
@@ -398,9 +416,15 @@ mlx5_nl_flow_transpose(void *buf,
 	const enum mlx5_nl_flow_trans *trans;
 	const enum mlx5_nl_flow_trans *back;
 
-	if (!size)
-		goto error_nobufs;
 init:
+	buf = NULL;
+	if (size < offsetof(struct mlx5_nl_flow, msg))
+		goto error_nobufs;
+	nl_flow->size = offsetof(struct mlx5_nl_flow, msg);
+	nl_flow->applied = 0;
+	nl_flow->ifindex_src = NULL;
+	nl_flow->ifindex_dst = NULL;
+	size -= nl_flow->size;
 	item = pattern;
 	action = actions;
 	n = 0;
@@ -483,15 +507,21 @@ mlx5_nl_flow_transpose(void *buf,
 				(error, ENOTSUP,
 				 RTE_FLOW_ERROR_TYPE_ATTR_INGRESS,
 				 attr, "egress is not supported");
-		if (size < mnl_nlmsg_size(sizeof(*tcm)))
+		i = RTE_ALIGN_CEIL(nl_flow->size, alignof(struct nlmsghdr));
+		i -= nl_flow->size;
+		if (size < i + mnl_nlmsg_size(sizeof(*tcm)))
 			goto error_nobufs;
+		nl_flow->size += i;
+		buf = (void *)((uintptr_t)nl_flow + nl_flow->size);
+		size -= i;
 		nlh = mnl_nlmsg_put_header(buf);
-		nlh->nlmsg_type = 0;
+		nlh->nlmsg_type = RTM_NEWTFILTER;
 		nlh->nlmsg_flags = 0;
 		nlh->nlmsg_seq = 0;
 		tcm = mnl_nlmsg_put_extra_header(nlh, sizeof(*tcm));
 		tcm->tcm_family = AF_UNSPEC;
 		tcm->tcm_ifindex = ptoi[0].ifindex;
+		nl_flow->ifindex_src = (unsigned int *)&tcm->tcm_ifindex;
 		/*
 		 * Let kernel pick a handle by default. A predictable handle
 		 * can be set by the caller on the resulting buffer through
@@ -893,6 +923,10 @@ mlx5_nl_flow_transpose(void *buf,
 		act = mnl_attr_nest_start_check(buf, size, TCA_ACT_OPTIONS);
 		if (!act)
 			goto error_nobufs;
+		nl_flow->ifindex_dst =
+			&((struct tc_mirred *)
+			  mnl_attr_get_payload
+			  (mnl_nlmsg_get_payload_tail(buf)))->ifindex;
 		if (!mnl_attr_put_check(buf, size, TCA_MIRRED_PARMS,
 					sizeof(struct tc_mirred),
 					&(struct tc_mirred){
@@ -1014,15 +1048,18 @@ mlx5_nl_flow_transpose(void *buf,
 		if (na_flower)
 			mnl_attr_nest_end(buf, na_flower);
 		nlh = buf;
-		return nlh->nlmsg_len;
+		buf = NULL;
+		size -= nlh->nlmsg_len;
+		nl_flow->size += nlh->nlmsg_len;
+		return nl_flow->size;
 	}
 	back = trans;
 	trans = mlx5_nl_flow_trans[trans[n - 1]];
 	n = 0;
 	goto trans;
 error_nobufs:
-	if (buf != buf_tmp) {
-		buf = buf_tmp;
+	if (nl_flow != (void *)buf_tmp) {
+		nl_flow = (void *)buf_tmp;
 		size = sizeof(buf_tmp);
 		goto init;
 	}
@@ -1037,14 +1074,15 @@ mlx5_nl_flow_transpose(void *buf,
  * This handle should be unique for a given network interface to avoid
  * collisions.
  *
- * @param buf
+ * @param nl_flow
  *   Flow rule buffer previously initialized by mlx5_nl_flow_transpose().
  * @param handle
  *   Unique 32-bit handle to use.
  */
 void
-mlx5_nl_flow_brand(void *buf, uint32_t handle)
+mlx5_nl_flow_brand(struct mlx5_nl_flow *nl_flow, uint32_t handle)
 {
+	void *buf = nl_flow->msg;
 	struct tcmsg *tcm = mnl_nlmsg_get_payload(buf);
 
 	tcm->tcm_handle = handle;
@@ -1053,8 +1091,8 @@ mlx5_nl_flow_brand(void *buf, uint32_t handle)
 /**
  * Send Netlink message with acknowledgment and process reply.
  *
- * @param nl
- *   Libmnl socket to use.
+ * @param ctx
+ *   Context object initialized by mlx5_nl_flow_ctx_create().
  * @param nlh
  *   Message to send. This function always raises the NLM_F_ACK flag and
  *   sets its sequence number before sending.
@@ -1067,26 +1105,26 @@ mlx5_nl_flow_brand(void *buf, uint32_t handle)
  *   0 on success, a negative errno value otherwise and rte_errno is set.
  */
 static int
-mlx5_nl_flow_chat(struct mnl_socket *nl, struct nlmsghdr *nlh,
+mlx5_nl_flow_chat(struct mlx5_nl_flow_ctx *ctx, struct nlmsghdr *nlh,
 		  mnl_cb_t cb, void *arg)
 {
 	alignas(struct nlmsghdr)
 	uint8_t ans[MNL_SOCKET_BUFFER_SIZE];
-	unsigned int portid = mnl_socket_get_portid(nl);
-	uint32_t seq = rte_get_tsc_cycles();
+	unsigned int portid = mnl_socket_get_portid(ctx->nl);
+	uint32_t seq = ++ctx->seq ? ctx->seq : ++ctx->seq;
 	int err = 0;
 	int ret;
 
 	nlh->nlmsg_flags |= NLM_F_ACK;
 	nlh->nlmsg_seq = seq;
-	ret = mnl_socket_sendto(nl, nlh, nlh->nlmsg_len);
+	ret = mnl_socket_sendto(ctx->nl, nlh, nlh->nlmsg_len);
 	nlh = (void *)ans;
 	/*
 	 * The following loop postpones non-fatal errors until multipart
 	 * messages are complete.
 	 */
 	while (ret > 0) {
-		ret = mnl_socket_recvfrom(nl, ans, sizeof(ans));
+		ret = mnl_socket_recvfrom(ctx->nl, ans, sizeof(ans));
 		if (ret == -1) {
 			err = errno;
 			if (err != ENOSPC)
@@ -1113,9 +1151,9 @@ mlx5_nl_flow_chat(struct mnl_socket *nl, struct nlmsghdr *nlh,
 /**
  * Create a Netlink flow rule.
  *
- * @param nl
- *   Libmnl socket to use.
- * @param buf
+ * @param ctx
+ *   Context object initialized by mlx5_nl_flow_ctx_create().
+ * @param nl_flow
  *   Flow rule buffer previously initialized by mlx5_nl_flow_transpose().
  * @param[out] error
  *   Perform verbose error reporting if not NULL.
@@ -1124,15 +1162,19 @@ mlx5_nl_flow_chat(struct mnl_socket *nl, struct nlmsghdr *nlh,
  *   0 on success, a negative errno value otherwise and rte_errno is set.
  */
 int
-mlx5_nl_flow_create(struct mnl_socket *nl, void *buf,
+mlx5_nl_flow_create(struct mlx5_nl_flow_ctx *ctx, struct mlx5_nl_flow *nl_flow,
 		    struct rte_flow_error *error)
 {
-	struct nlmsghdr *nlh = buf;
+	struct nlmsghdr *nlh = (void *)nl_flow->msg;
 
+	if (nl_flow->applied)
+		return 0;
 	nlh->nlmsg_type = RTM_NEWTFILTER;
 	nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL;
-	if (!mlx5_nl_flow_chat(nl, nlh, NULL, NULL))
+	if (!mlx5_nl_flow_chat(ctx, nlh, NULL, NULL)) {
+		nl_flow->applied = 1;
 		return 0;
+	}
 	return rte_flow_error_set
 		(error, rte_errno, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
 		 "netlink: failed to create TC flow rule");
@@ -1141,9 +1183,12 @@ mlx5_nl_flow_create(struct mnl_socket *nl, void *buf,
 /**
  * Destroy a Netlink flow rule.
  *
- * @param nl
- *   Libmnl socket to use.
- * @param buf
+ * In case of error, no recovery is possible; caller must suppose flow rule
+ * was destroyed.
+ *
+ * @param ctx
+ *   Context object initialized by mlx5_nl_flow_ctx_create().
+ * @param nl_flow
  *   Flow rule buffer previously initialized by mlx5_nl_flow_transpose().
  * @param[out] error
  *   Perform verbose error reporting if not NULL.
@@ -1152,26 +1197,31 @@ mlx5_nl_flow_create(struct mnl_socket *nl, void *buf,
  *   0 on success, a negative errno value otherwise and rte_errno is set.
  */
 int
-mlx5_nl_flow_destroy(struct mnl_socket *nl, void *buf,
+mlx5_nl_flow_destroy(struct mlx5_nl_flow_ctx *ctx, struct mlx5_nl_flow *nl_flow,
 		     struct rte_flow_error *error)
 {
-	struct nlmsghdr *nlh = buf;
+	struct nlmsghdr *nlh = (void *)nl_flow->msg;
+	int ret;
 
+	if (!nl_flow->applied)
+		return 0;
 	nlh->nlmsg_type = RTM_DELTFILTER;
 	nlh->nlmsg_flags = NLM_F_REQUEST;
-	if (!mlx5_nl_flow_chat(nl, nlh, NULL, NULL))
+	ret = mlx5_nl_flow_chat(ctx, nlh, NULL, NULL);
+	nl_flow->applied = 0;
+	if (!ret)
 		return 0;
 	return rte_flow_error_set
-		(error, errno, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
+		(error, rte_errno, RTE_FLOW_ERROR_TYPE_UNSPECIFIED, NULL,
 		 "netlink: failed to destroy TC flow rule");
 }
 
 /**
- * Initialize ingress qdisc of a given network interface.
+ * Initialize ingress qdisc of network interfaces.
  *
- * @param nl
- *   Libmnl socket of the @p NETLINK_ROUTE kind.
- * @param ifindex
+ * @param ctx
+ *   Context object initialized by mlx5_nl_flow_ctx_create().
+ * @param[in] ifindex
  *   Index of network interface to initialize.
  * @param[out] error
  *   Perform verbose error reporting if not NULL.
@@ -1180,7 +1230,8 @@ mlx5_nl_flow_destroy(struct mnl_socket *nl, void *buf,
  *   0 on success, a negative errno value otherwise and rte_errno is set.
  */
 int
-mlx5_nl_flow_ifindex_init(struct mnl_socket *nl, unsigned int ifindex,
+mlx5_nl_flow_ifindex_init(struct mlx5_nl_flow_ctx *ctx,
+			  const unsigned int ifindex,
 			  struct rte_flow_error *error)
 {
 	struct nlmsghdr *nlh;
@@ -1202,15 +1253,15 @@ mlx5_nl_flow_ifindex_init(struct mnl_socket *nl, unsigned int ifindex,
 			(error, ENOBUFS, RTE_FLOW_ERROR_TYPE_UNSPECIFIED,
 			 NULL, "netlink: not enough space for message");
 	/* Ignore errors when qdisc is already absent. */
-	if (mlx5_nl_flow_chat(nl, nlh, NULL, NULL) &&
-	    rte_errno != EINVAL && rte_errno != ENOENT)
+	if (mlx5_nl_flow_chat(ctx, nlh, NULL, NULL) &&
+	    rte_errno != EINVAL && rte_errno != ENOENT && rte_errno != EPERM)
 		return rte_flow_error_set
 			(error, rte_errno, RTE_FLOW_ERROR_TYPE_UNSPECIFIED,
 			 NULL, "netlink: failed to remove ingress qdisc");
 	/* Create fresh ingress qdisc. */
 	nlh->nlmsg_type = RTM_NEWQDISC;
 	nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL;
-	if (mlx5_nl_flow_chat(nl, nlh, NULL, NULL))
+	if (mlx5_nl_flow_chat(ctx, nlh, NULL, NULL))
 		return rte_flow_error_set
 			(error, rte_errno, RTE_FLOW_ERROR_TYPE_UNSPECIFIED,
 			 NULL, "netlink: failed to create ingress qdisc");
@@ -1218,34 +1269,50 @@ mlx5_nl_flow_ifindex_init(struct mnl_socket *nl, unsigned int ifindex,
 }
 
 /**
- * Create and configure a libmnl socket for Netlink flow rules.
+ * Create NL flow rule context object.
  *
+ * @param socket
+ *   NUMA socket for memory allocations.
  * @return
- *   A valid libmnl socket object pointer on success, NULL otherwise and
- *   rte_errno is set.
+ *   A valid object on success, NULL otherwise and rte_errno is set.
  */
-struct mnl_socket *
-mlx5_nl_flow_socket_create(void)
+struct mlx5_nl_flow_ctx *
+mlx5_nl_flow_ctx_create(int socket)
 {
-	struct mnl_socket *nl = mnl_socket_open(NETLINK_ROUTE);
+	struct mlx5_nl_flow_ctx *ctx =
+		rte_zmalloc_socket(__func__, sizeof(*ctx), 0, socket);
 
-	if (nl) {
-		mnl_socket_setsockopt(nl, NETLINK_CAP_ACK, &(int){ 1 },
-				      sizeof(int));
-		if (!mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID))
-			return nl;
-	}
+	if (!ctx)
+		goto error;
+	ctx->socket = socket;
+	ctx->seq = 0;
+	ctx->nl = mnl_socket_open(NETLINK_ROUTE);
+	if (!ctx->nl)
+		goto error;
+	mnl_socket_setsockopt(ctx->nl, NETLINK_CAP_ACK, &(int){ 1 },
+			      sizeof(int));
+	if (mnl_socket_bind(ctx->nl, 0, MNL_SOCKET_AUTOPID))
+		goto error;
+	return ctx;
+error:
 	rte_errno = errno;
-	if (nl)
-		mnl_socket_close(nl);
+	if (ctx) {
+		if (ctx->nl)
+			mnl_socket_close(ctx->nl);
+		rte_free(ctx);
+	}
 	return NULL;
 }
 
 /**
- * Destroy a libmnl socket.
+ * Destroy NL flow rule context object.
+ *
+ * @param ctx
+ *   Context object initialized by mlx5_nl_flow_ctx_create().
  */
 void
-mlx5_nl_flow_socket_destroy(struct mnl_socket *nl)
+mlx5_nl_flow_ctx_destroy(struct mlx5_nl_flow_ctx *ctx)
 {
-	mnl_socket_close(nl);
+	mnl_socket_close(ctx->nl);
+	rte_free(ctx);
 }
