diff --git a/drivers/net/tap/bpf/meson.build b/drivers/net/tap/bpf/meson.build
index f2c03a19fd..45e8c3b498 100644
--- a/drivers/net/tap/bpf/meson.build
+++ b/drivers/net/tap/bpf/meson.build
@@ -1,17 +1,26 @@
 # SPDX-License-Identifier: BSD-3-Clause
 # Copyright 2024 Stephen Hemminger <stephen@networkplumber.org>
 
-enable_tap_rss = false
-
-libbpf = dependency('libbpf', required: false, method: 'pkg-config')
+# Loading BPF requires libbpf
+# and the bpf_map__XXX API's were introduced in 0.8.0
+libbpf = dependency('libbpf', version: '>= 1.0',
+                    required: false, method: 'pkg-config')
 if not libbpf.found()
     message('net/tap: no RSS support missing libbpf')
     subdir_done()
 endif
 
+# Making skeleton needs bpftool
 # Debian install this in /usr/sbin which is not in $PATH
-bpftool = find_program('bpftool', '/usr/sbin/bpftool', required: false, version: '>= 5.6.0')
-if not bpftool.found()
+bpftool_supports_skel = false
+bpftool = find_program('bpftool', '/usr/sbin/bpftool', required: false)
+if bpftool.found()
+    # Some Ubuntu versions have non-functional bpftool
+    bpftool_supports_skel = run_command(bpftool, 'gen', 'help',
+                                        check:false).returncode() == 0
+endif
+
+if not bpftool_supports_skel
     message('net/tap: no RSS support missing bpftool')
     subdir_done()
 endif
@@ -42,6 +51,7 @@ clang_flags = [
     '-O2',
     '-Wall',
     '-Wextra',
+    max_queues,
     '-target',
     'bpf',
     '-g',
@@ -67,6 +77,22 @@ skel_h_cmd = [
     '@INPUT@'
 ]
 
+vmlinux_h_cmd = [
+    bpftool,
+    'btf',
+    'dump',
+    'file',
+    '/sys/kernel/btf/vmlinux',
+    'format',
+    'c'
+]
+
+vmlinux_h = custom_target(
+    'vmlinux.h',
+    output: 'vmlinux.h',
+    command: vmlinux_h_cmd,
+    capture: true)
+
 tap_rss_o = custom_target(
     'tap_rss.bpf.o',
     input: 'tap_rss.c',
diff --git a/drivers/net/tap/meson.build b/drivers/net/tap/meson.build
index 66647a1c62..26074059fa 100644
--- a/drivers/net/tap/meson.build
+++ b/drivers/net/tap/meson.build
@@ -5,36 +5,33 @@ if not is_linux
     build = false
     reason = 'only supported on Linux'
 endif
+
 sources = files(
         'rte_eth_tap.c',
         'tap_intr.c',
         'tap_netlink.c',
 )
 
+deps = ['bus_vdev', 'gso', 'hash']
+
+max_queues = '-DTAP_MAX_QUEUES=16'
+cflags += max_queues
+
+require_iova_in_mbuf = false
+
 if cc.has_header_symbol('linux/pkt_cls.h', 'TCA_FLOWER_ACT')
     cflags += '-DHAVE_TCA_FLOWER'
     sources += files(
-        'tap_bpf_api.c',
         'tap_flow.c',
         'tap_tcmsgs.c',
     )
-endif
-
-deps = ['bus_vdev', 'gso', 'hash']
 
-cflags += '-DTAP_MAX_QUEUES=16'
-
-# input array for meson symbol search:
-# [ "MACRO to define if found", "header for the search",
-#   "enum/define", "symbol to search" ]
-#
-args = [
-        [ 'HAVE_TC_ACT_BPF', 'linux/tc_act/tc_bpf.h', 'TCA_ACT_BPF_UNSPEC' ],
-]
-config = configuration_data()
-foreach arg:args
-    config.set(arg[0], cc.has_header_symbol(arg[1], arg[2]))
-endforeach
-configure_file(output : 'tap_autoconf.h', configuration : config)
+    enable_tap_rss = false
 
-require_iova_in_mbuf = false
+    subdir('bpf')
+    if enable_tap_rss
+        cflags += '-DHAVE_BPF_RSS'
+        ext_deps += libbpf
+        sources += tap_rss_skel_h
+    endif
+endif
diff --git a/drivers/net/tap/rte_eth_tap.c b/drivers/net/tap/rte_eth_tap.c
index 9058a47295..d847565073 100644
--- a/drivers/net/tap/rte_eth_tap.c
+++ b/drivers/net/tap/rte_eth_tap.c
@@ -1140,6 +1140,7 @@ tap_dev_close(struct rte_eth_dev *dev)
 		tap_flow_implicit_flush(internals, NULL);
 		tap_nl_final(internals->nlsk_fd);
 		internals->nlsk_fd = -1;
+		tap_flow_bpf_destroy(internals);
 	}
 #endif
 
@@ -1949,6 +1950,7 @@ eth_dev_tap_create(struct rte_vdev_device *vdev, const char *tap_name,
 	strlcpy(pmd->name, tap_name, sizeof(pmd->name));
 	pmd->type = type;
 	pmd->ka_fd = -1;
+
 #ifdef HAVE_TCA_FLOWER
 	pmd->nlsk_fd = -1;
 #endif
@@ -2031,13 +2033,6 @@ eth_dev_tap_create(struct rte_vdev_device *vdev, const char *tap_name,
 	/* Make network device persist after application exit */
 	pmd->persist = persist;
 
-	pmd->if_index = if_nametoindex(pmd->name);
-	if (!pmd->if_index) {
-		TAP_LOG(ERR, "%s: failed to get if_index.", pmd->name);
-		goto disable_rte_flow;
-	}
-
-
 #ifdef HAVE_TCA_FLOWER
 	/*
 	 * Set up everything related to rte_flow:
@@ -2053,6 +2048,11 @@ eth_dev_tap_create(struct rte_vdev_device *vdev, const char *tap_name,
 			pmd->name);
 		goto disable_rte_flow;
 	}
+	pmd->if_index = if_nametoindex(pmd->name);
+	if (!pmd->if_index) {
+		TAP_LOG(ERR, "%s: failed to get if_index.", pmd->name);
+		goto disable_rte_flow;
+	}
 	if (qdisc_create_multiq(pmd->nlsk_fd, pmd->if_index) < 0) {
 		TAP_LOG(ERR, "%s: failed to create multiq qdisc.",
 			pmd->name);
diff --git a/drivers/net/tap/rte_eth_tap.h b/drivers/net/tap/rte_eth_tap.h
index af18b29090..ce4322ad04 100644
--- a/drivers/net/tap/rte_eth_tap.h
+++ b/drivers/net/tap/rte_eth_tap.h
@@ -81,10 +81,8 @@ struct pmd_internals {
 #ifdef HAVE_TCA_FLOWER
 	int nlsk_fd;                      /* Netlink socket fd */
 	int flow_isolate;                 /* 1 if flow isolation is enabled */
-	int rss_enabled;                  /* 1 if RSS is enabled, else 0 */
-	/* implicit rules set when RSS is enabled */
-	int map_fd;                       /* BPF RSS map fd */
-	int bpf_fd[RTE_PMD_TAP_MAX_QUEUES];/* List of bpf fds per queue */
+
+	struct tap_rss *rss;		  /* BPF program */
 
 	LIST_HEAD(tap_flows, rte_flow) flows;        /* rte_flow rules */
 	/* implicit rte_flow rules set when a remote device is active */
diff --git a/drivers/net/tap/tap_flow.c b/drivers/net/tap/tap_flow.c
index 5a5a4ea048..679cffe528 100644
--- a/drivers/net/tap/tap_flow.c
+++ b/drivers/net/tap/tap_flow.c
@@ -15,25 +15,19 @@
 #include <rte_random.h>
 #include <rte_malloc.h>
 #include <rte_eth_tap.h>
+#include <rte_uuid.h>
 
 #include <tap_flow.h>
-#include <tap_autoconf.h>
 #include <tap_tcmsgs.h>
 #include <tap_rss.h>
 
-/* RSS key management */
-enum bpf_rss_key_e {
-	KEY_CMD_GET = 1,
-	KEY_CMD_RELEASE,
-	KEY_CMD_INIT,
-	KEY_CMD_DEINIT,
-};
-
-enum key_status_e {
-	KEY_STAT_UNSPEC,
-	KEY_STAT_USED,
-	KEY_STAT_AVAILABLE,
-};
+#ifdef HAVE_BPF_RSS
+/* Workaround for warning in bpftool generated skeleton code */
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wcast-qual"
+#include "tap_rss.skel.h"
+#pragma GCC diagnostic pop
+#endif
 
 #define ISOLATE_HANDLE 1
 #define REMOTE_PROMISCUOUS_HANDLE 2
@@ -41,8 +35,6 @@ enum key_status_e {
 struct rte_flow {
 	LIST_ENTRY(rte_flow) next; /* Pointer to the next rte_flow structure */
 	struct rte_flow *remote_flow; /* associated remote flow */
-	int bpf_fd[SEC_MAX]; /* list of bfs fds per ELF section */
-	uint32_t key_idx; /* RSS rule key index into BPF map */
 	struct nlmsg msg;
 };
 
@@ -69,12 +61,16 @@ struct action_data {
 		struct skbedit {
 			struct tc_skbedit skbedit;
 			uint16_t queue;
+			uint32_t mark;
 		} skbedit;
+#ifdef HAVE_BPF_RSS
 		struct bpf {
 			struct tc_act_bpf bpf;
+			uint32_t map_key;
 			int bpf_fd;
 			const char *annotation;
 		} bpf;
+#endif
 	};
 };
 
@@ -112,13 +108,12 @@ tap_flow_isolate(struct rte_eth_dev *dev,
 		 int set,
 		 struct rte_flow_error *error);
 
-static int bpf_rss_key(enum bpf_rss_key_e cmd, __u32 *key_idx);
-static int rss_enable(struct pmd_internals *pmd,
-			const struct rte_flow_attr *attr,
-			struct rte_flow_error *error);
+#ifdef HAVE_BPF_RSS
+static int rss_enable(struct pmd_internals *pmd, struct rte_flow_error *error);
 static int rss_add_actions(struct rte_flow *flow, struct pmd_internals *pmd,
 			const struct rte_flow_action_rss *rss,
 			struct rte_flow_error *error);
+#endif
 
 static const struct rte_flow_ops tap_flow_ops = {
 	.validate = tap_flow_validate,
@@ -853,11 +848,13 @@ add_action(struct rte_flow *flow, size_t *act_index, struct action_data *adata)
 			   &adata->mirred);
 	} else if (strcmp("skbedit", adata->id) == 0) {
 		tap_nlattr_add(&msg->nh, TCA_SKBEDIT_PARMS,
-			   sizeof(adata->skbedit.skbedit),
-			   &adata->skbedit.skbedit);
-		tap_nlattr_add16(&msg->nh, TCA_SKBEDIT_QUEUE_MAPPING,
-			     adata->skbedit.queue);
+			   sizeof(adata->skbedit.skbedit), &adata->skbedit.skbedit);
+		if (adata->skbedit.mark)
+			tap_nlattr_add32(&msg->nh, TCA_SKBEDIT_MARK, adata->skbedit.mark);
+		else
+			tap_nlattr_add16(&msg->nh, TCA_SKBEDIT_QUEUE_MAPPING, adata->skbedit.queue);
 	} else if (strcmp("bpf", adata->id) == 0) {
+#ifdef HAVE_BPF_RSS
 		tap_nlattr_add32(&msg->nh, TCA_ACT_BPF_FD, adata->bpf.bpf_fd);
 		tap_nlattr_add(&msg->nh, TCA_ACT_BPF_NAME,
 			   strlen(adata->bpf.annotation) + 1,
@@ -865,7 +862,12 @@ add_action(struct rte_flow *flow, size_t *act_index, struct action_data *adata)
 		tap_nlattr_add(&msg->nh, TCA_ACT_BPF_PARMS,
 			   sizeof(adata->bpf.bpf),
 			   &adata->bpf.bpf);
+#else
+		TAP_LOG(ERR, "Internal error: bpf requested but not supported");
+		return -1;
+#endif
 	} else {
+		TAP_LOG(ERR, "Internal error: unknown action: %s", adata->id);
 		return -1;
 	}
 	tap_nlattr_nested_finish(msg); /* nested TCA_ACT_OPTIONS */
@@ -1104,8 +1106,7 @@ priv_flow_process(struct pmd_internals *pmd,
 					},
 				};
 
-				err = add_actions(flow, 1, &adata,
-						  TCA_FLOWER_ACT);
+				err = add_actions(flow, 1, &adata, TCA_FLOWER_ACT);
 			}
 		} else if (actions->type == RTE_FLOW_ACTION_TYPE_QUEUE) {
 			const struct rte_flow_action_queue *queue =
@@ -1135,6 +1136,7 @@ priv_flow_process(struct pmd_internals *pmd,
 				err = add_actions(flow, 1, &adata,
 					TCA_FLOWER_ACT);
 			}
+#ifdef HAVE_BPF_RSS
 		} else if (actions->type == RTE_FLOW_ACTION_TYPE_RSS) {
 			const struct rte_flow_action_rss *rss =
 				(const struct rte_flow_action_rss *)
@@ -1143,13 +1145,14 @@ priv_flow_process(struct pmd_internals *pmd,
 			if (action++)
 				goto exit_action_not_supported;
 
-			if (!pmd->rss_enabled) {
-				err = rss_enable(pmd, attr, error);
+			if (pmd->rss == NULL) {
+				err = rss_enable(pmd, error);
 				if (err)
 					goto exit_return_error;
 			}
 			if (flow)
 				err = rss_add_actions(flow, pmd, rss, error);
+#endif
 		} else {
 			goto exit_action_not_supported;
 		}
@@ -1246,26 +1249,17 @@ tap_flow_set_handle(struct rte_flow *flow)
  *
  */
 static void
-tap_flow_free(struct pmd_internals *pmd, struct rte_flow *flow)
+tap_flow_free(struct pmd_internals *pmd __rte_unused, struct rte_flow *flow)
 {
-	int i;
-
 	if (!flow)
 		return;
 
-	if (pmd->rss_enabled) {
-		/* Close flow BPF file descriptors */
-		for (i = 0; i < SEC_MAX; i++)
-			if (flow->bpf_fd[i] != 0) {
-				close(flow->bpf_fd[i]);
-				flow->bpf_fd[i] = 0;
-			}
-
-		/* Release the map key for this RSS rule */
-		bpf_rss_key(KEY_CMD_RELEASE, &flow->key_idx);
-		flow->key_idx = 0;
-	}
-
+#ifdef HAVE_BPF_RSS
+	struct tap_rss *rss = pmd->rss;
+	if (rss)
+		bpf_map__delete_elem(rss->maps.rss_map,
+				     &flow->msg.t.tcm_handle, sizeof(uint32_t), 0);
+#endif
 	/* Free flow allocated memory */
 	rte_free(flow);
 }
@@ -1733,14 +1727,18 @@ tap_flow_implicit_flush(struct pmd_internals *pmd, struct rte_flow_error *error)
 	return 0;
 }
 
-#define MAX_RSS_KEYS 256
-#define KEY_IDX_OFFSET (3 * MAX_RSS_KEYS)
-#define SEC_NAME_CLS_Q "cls_q"
-
-static const char *sec_name[SEC_MAX] = {
-	[SEC_L3_L4] = "l3_l4",
-};
+/**
+ * Cleanup when device is closed
+ */
+void tap_flow_bpf_destroy(struct pmd_internals *pmd __rte_unused)
+{
+#ifdef HAVE_BPF_RSS
+	tap_rss__destroy(pmd->rss);
+	pmd->rss = NULL;
+#endif
+}
 
+#ifdef HAVE_BPF_RSS
 /**
  * Enable RSS on tap: create TC rules for queuing.
  *
@@ -1755,225 +1753,32 @@ static const char *sec_name[SEC_MAX] = {
  *
  * @return 0 on success, negative value on failure.
  */
-static int rss_enable(struct pmd_internals *pmd,
-			const struct rte_flow_attr *attr,
-			struct rte_flow_error *error)
+static int rss_enable(struct pmd_internals *pmd, struct rte_flow_error *error)
 {
-	struct rte_flow *rss_flow = NULL;
-	struct nlmsg *msg = NULL;
-	/* 4096 is the maximum number of instructions for a BPF program */
-	char annotation[64];
-	int i;
-	int err = 0;
-
-	/* unlimit locked memory */
-	struct rlimit memlock_limit = {
-		.rlim_cur = RLIM_INFINITY,
-		.rlim_max = RLIM_INFINITY,
-	};
-	setrlimit(RLIMIT_MEMLOCK, &memlock_limit);
-
-	 /* Get a new map key for a new RSS rule */
-	err = bpf_rss_key(KEY_CMD_INIT, NULL);
-	if (err < 0) {
-		rte_flow_error_set(
-			error, EINVAL, RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
-			"Failed to initialize BPF RSS keys");
-
-		return -1;
-	}
-
-	/*
-	 *  Create BPF RSS MAP
-	 */
-	pmd->map_fd = tap_flow_bpf_rss_map_create(sizeof(__u32), /* key size */
-				sizeof(struct rss_key),
-				MAX_RSS_KEYS);
-	if (pmd->map_fd < 0) {
-		TAP_LOG(ERR,
-			"Failed to create BPF map (%d): %s",
-				errno, strerror(errno));
-		rte_flow_error_set(
-			error, ENOTSUP, RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
-			"Kernel too old or not configured "
-			"to support BPF maps");
-
-		return -ENOTSUP;
-	}
-
-	/*
-	 * Add a rule per queue to match reclassified packets and direct them to
-	 * the correct queue.
-	 */
-	for (i = 0; i < pmd->dev->data->nb_rx_queues; i++) {
-		pmd->bpf_fd[i] = tap_flow_bpf_cls_q(i);
-		if (pmd->bpf_fd[i] < 0) {
-			TAP_LOG(ERR,
-				"Failed to load BPF section %s for queue %d",
-				SEC_NAME_CLS_Q, i);
-			rte_flow_error_set(
-				error, ENOTSUP, RTE_FLOW_ERROR_TYPE_HANDLE,
-				NULL,
-				"Kernel too old or not configured "
-				"to support BPF programs loading");
-
-			return -ENOTSUP;
-		}
-
-		rss_flow = rte_zmalloc(__func__, sizeof(struct rte_flow), 0);
-		if (!rss_flow) {
-			TAP_LOG(ERR,
-				"Cannot allocate memory for rte_flow");
-			return -1;
-		}
-		msg = &rss_flow->msg;
-		tc_init_msg(msg, pmd->if_index, RTM_NEWTFILTER, NLM_F_REQUEST |
-			    NLM_F_ACK | NLM_F_EXCL | NLM_F_CREATE);
-		msg->t.tcm_info = TC_H_MAKE(0, htons(ETH_P_ALL));
-		tap_flow_set_handle(rss_flow);
-		uint16_t group = attr->group << GROUP_SHIFT;
-		uint16_t prio = group | (i + PRIORITY_OFFSET);
-		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)
-			return -1;
-		tap_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,
-			   annotation);
-		/* Actions */
-		{
-			struct action_data adata = {
-				.id = "skbedit",
-				.skbedit = {
-					.skbedit = {
-						.action = TC_ACT_PIPE,
-					},
-					.queue = i,
-				},
-			};
-			if (add_actions(rss_flow, 1, &adata, TCA_BPF_ACT) < 0)
-				return -1;
-		}
-		tap_nlattr_nested_finish(msg); /* nested TCA_OPTIONS */
+	int err;
 
-		/* Netlink message is now ready to be sent */
-		if (tap_nl_send(pmd->nlsk_fd, &msg->nh) < 0)
-			return -1;
-		err = tap_nl_recv_ack(pmd->nlsk_fd);
-		if (err < 0) {
-			TAP_LOG(ERR,
-				"Kernel refused TC filter rule creation (%d): %s",
-				errno, strerror(errno));
-			return err;
-		}
+	/* Load the BPF program (defined in tap_bpf.h from skeleton) */
+	pmd->rss = tap_rss__open_and_load();
+	if (pmd->rss == NULL) {
+		TAP_LOG(ERR, "Failed to load BPF object: %s", strerror(errno));
+		rte_flow_error_set(error, errno, RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
+			"BPF object could not be loaded");
+		return -errno;
 	}
 
-	pmd->rss_enabled = 1;
-	return err;
-}
-
-/**
- * Manage bpf RSS keys repository with operations: init, get, release
- *
- * @param[in] cmd
- *   Command on RSS keys: init, get, release
- *
- * @param[in, out] key_idx
- *   Pointer to RSS Key index (out for get command, in for release command)
- *
- * @return -1 if couldn't get, release or init the RSS keys, 0 otherwise.
- */
-static int bpf_rss_key(enum bpf_rss_key_e cmd, __u32 *key_idx)
-{
-	__u32 i;
-	int err = 0;
-	static __u32 num_used_keys;
-	static __u32 rss_keys[MAX_RSS_KEYS] = {KEY_STAT_UNSPEC};
-	static __u32 rss_keys_initialized;
-	__u32 key;
-
-	switch (cmd) {
-	case KEY_CMD_GET:
-		if (!rss_keys_initialized) {
-			err = -1;
-			break;
-		}
-
-		if (num_used_keys == RTE_DIM(rss_keys)) {
-			err = -1;
-			break;
-		}
-
-		*key_idx = num_used_keys % RTE_DIM(rss_keys);
-		while (rss_keys[*key_idx] == KEY_STAT_USED)
-			*key_idx = (*key_idx + 1) % RTE_DIM(rss_keys);
-
-		rss_keys[*key_idx] = KEY_STAT_USED;
-
-		/*
-		 * Add an offset to key_idx in order to handle a case of
-		 * RSS and non RSS flows mixture.
-		 * If a non RSS flow is destroyed it has an eBPF map
-		 * index 0 (initialized on flow creation) and might
-		 * unintentionally remove RSS entry 0 from eBPF map.
-		 * To avoid this issue, add an offset to the real index
-		 * during a KEY_CMD_GET operation and subtract this offset
-		 * during a KEY_CMD_RELEASE operation in order to restore
-		 * the real index.
-		 */
-		*key_idx += KEY_IDX_OFFSET;
-		num_used_keys++;
-	break;
-
-	case KEY_CMD_RELEASE:
-		if (!rss_keys_initialized)
-			break;
-
-		/*
-		 * Subtract offset to restore real key index
-		 * If a non RSS flow is falsely trying to release map
-		 * entry 0 - the offset subtraction will calculate the real
-		 * map index as an out-of-range value and the release operation
-		 * will be silently ignored.
-		 */
-		key = *key_idx - KEY_IDX_OFFSET;
-		if (key >= RTE_DIM(rss_keys))
-			break;
-
-		if (rss_keys[key] == KEY_STAT_USED) {
-			rss_keys[key] = KEY_STAT_AVAILABLE;
-			num_used_keys--;
-		}
-	break;
-
-	case KEY_CMD_INIT:
-		for (i = 0; i < RTE_DIM(rss_keys); i++)
-			rss_keys[i] = KEY_STAT_AVAILABLE;
-
-		rss_keys_initialized = 1;
-		num_used_keys = 0;
-	break;
-
-	case KEY_CMD_DEINIT:
-		for (i = 0; i < RTE_DIM(rss_keys); i++)
-			rss_keys[i] = KEY_STAT_UNSPEC;
-
-		rss_keys_initialized = 0;
-		num_used_keys = 0;
-	break;
-
-	default:
-		break;
+	/* Attach the maps defined in BPF program */
+	err = tap_rss__attach(pmd->rss);
+	if (err < 0) {
+		TAP_LOG(ERR, "Failed to attach BPF object: %d", err);
+		rte_flow_error_set(error, -err, RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
+			"BPF object could not be attached");
+		tap_flow_bpf_destroy(pmd);
+		return err;
 	}
 
-	return err;
+	return 0;
 }
 
-
 /* Default RSS hash key also used by mlx devices */
 static const uint8_t rss_hash_default_key[] = {
 	0x2c, 0xc6, 0x81, 0xd1,
@@ -2006,9 +1811,11 @@ static int rss_add_actions(struct rte_flow *flow, struct pmd_internals *pmd,
 			   const struct rte_flow_action_rss *rss,
 			   struct rte_flow_error *error)
 {
+	const struct bpf_program *rss_prog = pmd->rss->progs.rss_flow_action;
 	struct rss_key rss_entry = { };
 	const uint8_t *key_in;
 	uint32_t hash_type = 0;
+	uint32_t handle = flow->msg.t.tcm_handle;
 	unsigned int i;
 	int err;
 
@@ -2057,34 +1864,24 @@ static int rss_add_actions(struct rte_flow *flow, struct pmd_internals *pmd,
 	else if (rss->types & (RTE_ETH_RSS_IPV6 | RTE_ETH_RSS_FRAG_IPV6 | RTE_ETH_RSS_IPV6_EX))
 		hash_type |= RTE_BIT32(HASH_FIELD_IPV6_L3);
 
-	/* Get a new map key for a new RSS rule */
-	err = bpf_rss_key(KEY_CMD_GET, &flow->key_idx);
-	if (err < 0) {
-		rte_flow_error_set(
-			error, EINVAL, RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
-			"Failed to get BPF RSS key");
-
-		return -1;
-	}
+	rss_entry.hash_fields = hash_type;
+	rte_convert_rss_key((const uint32_t *)key_in, (uint32_t *)rss_entry.key,
+			    TAP_RSS_HASH_KEY_SIZE);
 
 	/* Update RSS map entry with queues */
 	rss_entry.nb_queues = rss->queue_num;
 	for (i = 0; i < rss->queue_num; i++)
 		rss_entry.queues[i] = rss->queue[i];
 
-	rss_entry.hash_fields = hash_type;
-	rte_convert_rss_key((const uint32_t *)key_in, (uint32_t *)rss_entry.key,
-			    TAP_RSS_HASH_KEY_SIZE);
-
-
-	/* Add this RSS entry to map */
-	err = tap_flow_bpf_update_rss_elem(pmd->map_fd,
-				&flow->key_idx, &rss_entry);
 
+	/* Add this way for BPF to find  entry in map */
+	err = bpf_map__update_elem(pmd->rss->maps.rss_map,
+				   &handle, sizeof(handle),
+				   &rss_entry, sizeof(rss_entry), 0);
 	if (err) {
 		TAP_LOG(ERR,
-			"Failed to update BPF map entry #%u (%d): %s",
-			flow->key_idx, errno, strerror(errno));
+			"Failed to update BPF map entry %#x (%d): %s",
+			handle,  errno, strerror(errno));
 		rte_flow_error_set(
 			error, ENOTSUP, RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
 			"Kernel too old or not configured "
@@ -2093,47 +1890,28 @@ static int rss_add_actions(struct rte_flow *flow, struct pmd_internals *pmd,
 		return -ENOTSUP;
 	}
 
-
-	/*
-	 * Load bpf rules to calculate hash for this key_idx
-	 */
-
-	flow->bpf_fd[SEC_L3_L4] =
-		tap_flow_bpf_calc_l3_l4_hash(flow->key_idx, pmd->map_fd);
-	if (flow->bpf_fd[SEC_L3_L4] < 0) {
-		TAP_LOG(ERR,
-			"Failed to load BPF section %s (%d): %s",
-				sec_name[SEC_L3_L4], errno, strerror(errno));
-		rte_flow_error_set(
-			error, ENOTSUP, RTE_FLOW_ERROR_TYPE_HANDLE, NULL,
-			"Kernel too old or not configured "
-			"to support BPF program loading");
-
-		return -ENOTSUP;
-	}
-
-	/* Actions */
-	{
-		struct action_data adata[] = {
-			{
-				.id = "bpf",
-				.bpf = {
-					.bpf_fd = flow->bpf_fd[SEC_L3_L4],
-					.annotation = sec_name[SEC_L3_L4],
-					.bpf = {
-						.action = TC_ACT_PIPE,
-					},
-				},
+	/* Add actions to mark packet then run the RSS BPF program */
+	struct action_data adata[] = {
+		{
+			.id = "skbedit",
+			.skbedit = {
+				.skbedit.action = TC_ACT_PIPE,
+				.mark = handle,
 			},
-		};
-
-		if (add_actions(flow, RTE_DIM(adata), adata,
-			TCA_FLOWER_ACT) < 0)
-			return -1;
-	}
+		},
+		{
+			.id = "bpf",
+			.bpf = {
+				.bpf.action = TC_ACT_PIPE,
+				.annotation = "tap_rss",
+				.bpf_fd = bpf_program__fd(rss_prog),
+			},
+		},
+	};
 
-	return 0;
+	return add_actions(flow, RTE_DIM(adata), adata, TCA_FLOWER_ACT);
 }
+#endif
 
 /**
  * Get rte_flow operations.
diff --git a/drivers/net/tap/tap_flow.h b/drivers/net/tap/tap_flow.h
index 240fbc3dfa..8b19347a93 100644
--- a/drivers/net/tap/tap_flow.h
+++ b/drivers/net/tap/tap_flow.h
@@ -9,7 +9,11 @@
 #include <rte_flow.h>
 #include <rte_flow_driver.h>
 #include <rte_eth_tap.h>
-#include <tap_autoconf.h>
+
+/**
+ * Mask of unsupported RSS types
+ */
+#define TAP_RSS_HF_MASK (~(RTE_ETH_RSS_IP | RTE_ETH_RSS_UDP | RTE_ETH_RSS_TCP))
 
 /**
  * In TC, priority 0 means we require the kernel to allocate one for us.
@@ -41,11 +45,6 @@ enum implicit_rule_index {
 	TAP_REMOTE_MAX_IDX,
 };
 
-enum bpf_fd_idx {
-	SEC_L3_L4,
-	SEC_MAX,
-};
-
 int tap_dev_flow_ops_get(struct rte_eth_dev *dev,
 			 const struct rte_flow_ops **ops);
 int tap_flow_flush(struct rte_eth_dev *dev, struct rte_flow_error *error);
@@ -57,10 +56,6 @@ int tap_flow_implicit_destroy(struct pmd_internals *pmd,
 int tap_flow_implicit_flush(struct pmd_internals *pmd,
 			    struct rte_flow_error *error);
 
-int tap_flow_bpf_cls_q(__u32 queue_idx);
-int tap_flow_bpf_calc_l3_l4_hash(__u32 key_idx, int map_fd);
-int tap_flow_bpf_rss_map_create(unsigned int key_size, unsigned int value_size,
-			unsigned int max_entries);
-int tap_flow_bpf_update_rss_elem(int fd, void *key, void *value);
+void tap_flow_bpf_destroy(struct pmd_internals *pmd);
 
 #endif /* _TAP_FLOW_H_ */
diff --git a/drivers/net/tap/tap_rss.h b/drivers/net/tap/tap_rss.h
index 6009be7031..65bd8991b1 100644
--- a/drivers/net/tap/tap_rss.h
+++ b/drivers/net/tap/tap_rss.h
@@ -5,16 +5,14 @@
 #ifndef _TAP_RSS_H_
 #define _TAP_RSS_H_
 
-#ifndef TAP_MAX_QUEUES
-#define TAP_MAX_QUEUES 16
+/* Size of the map from BPF classid to queue table */
+#ifndef TAP_RSS_MAX
+#define TAP_RSS_MAX	32
 #endif
 
-/* Fixed RSS hash key size in bytes. */
+/* Standard Toeplitz hash key size */
 #define TAP_RSS_HASH_KEY_SIZE 40
 
-/* Supported RSS */
-#define TAP_RSS_HF_MASK (~(RTE_ETH_RSS_IP | RTE_ETH_RSS_UDP | RTE_ETH_RSS_TCP))
-
 /* hashed fields for RSS */
 enum hash_field {
 	HASH_FIELD_IPV4_L3,	/* IPv4 src/dst addr */
diff --git a/drivers/net/tap/tap_tcmsgs.h b/drivers/net/tap/tap_tcmsgs.h
index a64cb29d6f..9411626661 100644
--- a/drivers/net/tap/tap_tcmsgs.h
+++ b/drivers/net/tap/tap_tcmsgs.h
@@ -6,7 +6,6 @@
 #ifndef _TAP_TCMSGS_H_
 #define _TAP_TCMSGS_H_
 
-#include <tap_autoconf.h>
 #include <linux/if_ether.h>
 #include <linux/rtnetlink.h>
 #include <linux/pkt_sched.h>
@@ -14,9 +13,10 @@
 #include <linux/tc_act/tc_mirred.h>
 #include <linux/tc_act/tc_gact.h>
 #include <linux/tc_act/tc_skbedit.h>
-#ifdef HAVE_TC_ACT_BPF
+#ifdef HAVE_BPF_RSS
 #include <linux/tc_act/tc_bpf.h>
 #endif
+
 #include <inttypes.h>
 
 #include <rte_ether.h>
