[v8,04/12] bpf: add function to convert classic BPF to DPDK BPF

Message ID 20210913181510.46058-5-stephen@networkplumber.org (mailing list archive)
State Superseded, archived
Delegated to: Thomas Monjalon
Headers
Series Packet capture framework enhancements |

Checks

Context Check Description
ci/checkpatch warning coding style issues

Commit Message

Stephen Hemminger Sept. 13, 2021, 6:15 p.m. UTC
  The pcap library emits classic BPF (32 bit) and is useful for
creating filter programs.  The DPDK BPF library only implements
extended BPF (eBPF).  Add an function to convert from old to
new.

The rte_bpf_convert function uses rte_malloc to put the resulting
program in hugepage shared memory so it can be passed from a
secondary process to a primary process.

The code to convert was originally done as part of the Linux
kernel implementation then converted to a userspace program.
Both authors have agreed that it is allowable to license this
as BSD licensed in DPDK.

Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
 lib/bpf/bpf_convert.c | 570 ++++++++++++++++++++++++++++++++++++++++++
 lib/bpf/meson.build   |   5 +
 lib/bpf/rte_bpf.h     |  25 ++
 lib/bpf/version.map   |   6 +
 4 files changed, 606 insertions(+)
 create mode 100644 lib/bpf/bpf_convert.c
  

Comments

Ananyev, Konstantin Sept. 15, 2021, 11:02 a.m. UTC | #1
> 
> The pcap library emits classic BPF (32 bit) and is useful for
> creating filter programs.  The DPDK BPF library only implements
> extended BPF (eBPF).  Add an function to convert from old to
> new.
> 
> The rte_bpf_convert function uses rte_malloc to put the resulting
> program in hugepage shared memory so it can be passed from a
> secondary process to a primary process.
> 
> The code to convert was originally done as part of the Linux
> kernel implementation then converted to a userspace program.
> Both authors have agreed that it is allowable to license this
> as BSD licensed in DPDK.
> 
> Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
> ---
>  lib/bpf/bpf_convert.c | 570 ++++++++++++++++++++++++++++++++++++++++++
>  lib/bpf/meson.build   |   5 +
>  lib/bpf/rte_bpf.h     |  25 ++
>  lib/bpf/version.map   |   6 +
>  4 files changed, 606 insertions(+)
>  create mode 100644 lib/bpf/bpf_convert.c
> 
> diff --git a/lib/bpf/meson.build b/lib/bpf/meson.build
> index 63cbd60185e0..54f7610ae990 100644
> --- a/lib/bpf/meson.build
> +++ b/lib/bpf/meson.build
> @@ -25,3 +25,8 @@ if dep.found()
>      sources += files('bpf_load_elf.c')
>      ext_deps += dep
>  endif
> +
> +if dpdk_conf.has('RTE_PORT_PCAP')

Do we really need that 'if' above?
Why not to always have it enabled?

> +    sources += files('bpf_convert.c')
> +    ext_deps += pcap_dep
> +endif
> diff --git a/lib/bpf/rte_bpf.h b/lib/bpf/rte_bpf.h
> index 69116f36ba8b..2f23e272a376 100644
> --- a/lib/bpf/rte_bpf.h
> +++ b/lib/bpf/rte_bpf.h
> @@ -198,6 +198,31 @@ rte_bpf_exec_burst(const struct rte_bpf *bpf, void *ctx[], uint64_t rc[],
>  int
>  rte_bpf_get_jit(const struct rte_bpf *bpf, struct rte_bpf_jit *jit);
> 
> +#ifdef RTE_PORT_PCAP
> +
> +struct bpf_program;
> +
> +/**
> + * Convert a Classic BPF program from libpcap into a DPDK BPF code.
> + *
> + * @param prog
> + *  Classic BPF program from pcap_compile().
> + * @param prm
> + *  Result Extended BPF program.
> + * @return
> + *   Pointer to BPF program (allocated with *rte_malloc*)
> + *   that is used in future BPF operations,
> + *   or NULL on error, with error code set in rte_errno.
> + *   Possible rte_errno errors include:
> + *   - EINVAL - invalid parameter passed to function
> + *   - ENOMEM - can't reserve enough memory
> + */
> +__rte_experimental
> +struct rte_bpf_prm *
> +rte_bpf_convert(const struct bpf_program *prog);
> +
> +#endif
> +
>  #ifdef __cplusplus
>  }
>  #endif
> diff --git a/lib/bpf/version.map b/lib/bpf/version.map
> index 0bf35f487666..47082d5003ef 100644
> --- a/lib/bpf/version.map
> +++ b/lib/bpf/version.map
> @@ -14,3 +14,9 @@ DPDK_22 {
> 
>  	local: *;
>  };
> +
> +EXPERIMENTAL {
> +	global:
> +
> +	rte_bpf_convert;
> +};
> --

Cool feature, thanks for contributing.
Acked-by: Konstantin Ananyev <konstantin.ananyev@intel.com>

> 2.30.2
  
Stephen Hemminger Sept. 15, 2021, 4:25 p.m. UTC | #2
On Wed, 15 Sep 2021 11:02:20 +0000
"Ananyev, Konstantin" <konstantin.ananyev@intel.com> wrote:

> > +if dpdk_conf.has('RTE_PORT_PCAP')  
> 
> Do we really need that 'if' above?
> Why not to always have it enabled?

The converter code needs libpcap for the pcap header files that define
the encoding of classic BPF (struct bpf_insn).
  

Patch

diff --git a/lib/bpf/bpf_convert.c b/lib/bpf/bpf_convert.c
new file mode 100644
index 000000000000..a46ffeb067dd
--- /dev/null
+++ b/lib/bpf/bpf_convert.c
@@ -0,0 +1,570 @@ 
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2021 Microsoft Corporation
+ *
+ * Based on bpf_convert_filter() in the Linux kernel sources
+ * and filter2xdp.
+ *
+ * Licensed as BSD with permission original authors.
+ * Copyright (C) 2017 Tobias Klauser
+ * Copyright (c) 2011 - 2014 PLUMgrid, http://plumgrid.com
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <rte_common.h>
+#include <rte_bpf.h>
+#include <rte_log.h>
+#include <rte_malloc.h>
+#include <rte_errno.h>
+
+/* Workaround name conflicts with libpcap */
+#define bpf_validate(f, len) bpf_validate_libpcap(f, len)
+#include <pcap/pcap.h>
+#include <pcap/bpf.h>
+#undef bpf_validate
+
+#include "bpf_impl.h"
+#include "bpf_def.h"
+
+#ifndef BPF_MAXINSNS
+#define BPF_MAXINSNS 4096
+#endif
+
+/*
+ * Linux socket filter uses negative absolute offsets to
+ * reference ancillary data.
+ */
+#define SKF_AD_OFF    (-0x1000)
+#define SKF_AD_PROTOCOL 0
+#define SKF_AD_PKTTYPE	4
+#define SKF_AD_IFINDEX	8
+#define SKF_AD_NLATTR	12
+#define SKF_AD_NLATTR_NEST	16
+#define SKF_AD_MARK	20
+#define SKF_AD_QUEUE	24
+#define SKF_AD_HATYPE	28
+#define SKF_AD_RXHASH	32
+#define SKF_AD_CPU	36
+#define SKF_AD_ALU_XOR_X	40
+#define SKF_AD_VLAN_TAG	44
+#define SKF_AD_VLAN_TAG_PRESENT 48
+#define SKF_AD_PAY_OFFSET	52
+#define SKF_AD_RANDOM	56
+#define SKF_AD_VLAN_TPID	60
+#define SKF_AD_MAX	64
+
+/* ArgX, context and stack frame pointer register positions. Note,
+ * Arg1, Arg2, Arg3, etc are used as argument mappings of function
+ * calls in BPF_CALL instruction.
+ */
+#define BPF_REG_ARG1	EBPF_REG_1
+#define BPF_REG_ARG2	EBPF_REG_2
+#define BPF_REG_ARG3	EBPF_REG_3
+#define BPF_REG_ARG4	EBPF_REG_4
+#define BPF_REG_ARG5	EBPF_REG_5
+#define BPF_REG_CTX	EBPF_REG_6
+#define BPF_REG_FP	EBPF_REG_10
+
+/* Additional register mappings for converted user programs. */
+#define BPF_REG_A	EBPF_REG_0
+#define BPF_REG_X	EBPF_REG_7
+#define BPF_REG_TMP	EBPF_REG_8
+
+/* Helper macros for filter block array initializers. */
+
+/* ALU ops on registers, bpf_add|sub|...: dst_reg += src_reg */
+
+#define EBPF_ALU64_REG(OP, DST, SRC)				\
+	((struct ebpf_insn) {					\
+		.code  = EBPF_ALU64 | BPF_OP(OP) | BPF_X,	\
+		.dst_reg = DST,					\
+		.src_reg = SRC,					\
+		.off   = 0,					\
+		.imm   = 0 })
+
+#define BPF_ALU32_REG(OP, DST, SRC)				\
+	((struct ebpf_insn) {					\
+		.code  = BPF_ALU | BPF_OP(OP) | BPF_X,		\
+		.dst_reg = DST,					\
+		.src_reg = SRC,					\
+		.off   = 0,					\
+		.imm   = 0 })
+
+/* ALU ops on immediates, bpf_add|sub|...: dst_reg += imm32 */
+
+#define BPF_ALU32_IMM(OP, DST, IMM)				\
+	((struct ebpf_insn) {					\
+		.code  = BPF_ALU | BPF_OP(OP) | BPF_K,		\
+		.dst_reg = DST,					\
+		.src_reg = 0,					\
+		.off   = 0,					\
+		.imm   = IMM })
+
+/* Short form of mov, dst_reg = src_reg */
+
+#define BPF_MOV64_REG(DST, SRC)					\
+	((struct ebpf_insn) {					\
+		.code  = EBPF_ALU64 | EBPF_MOV | BPF_X,		\
+		.dst_reg = DST,					\
+		.src_reg = SRC,					\
+		.off   = 0,					\
+		.imm   = 0 })
+
+#define BPF_MOV32_REG(DST, SRC)					\
+	((struct ebpf_insn) {					\
+		.code  = BPF_ALU | EBPF_MOV | BPF_X,		\
+		.dst_reg = DST,					\
+		.src_reg = SRC,					\
+		.off   = 0,					\
+		.imm   = 0 })
+
+/* Short form of mov, dst_reg = imm32 */
+
+#define BPF_MOV32_IMM(DST, IMM)					\
+	((struct ebpf_insn) {					\
+		.code  = BPF_ALU | EBPF_MOV | BPF_K,		\
+		.dst_reg = DST,					\
+		.src_reg = 0,					\
+		.off   = 0,					\
+		.imm   = IMM })
+
+/* Short form of mov based on type, BPF_X: dst_reg = src_reg, BPF_K: dst_reg = imm32 */
+
+#define BPF_MOV32_RAW(TYPE, DST, SRC, IMM)			\
+	((struct ebpf_insn) {					\
+		.code  = BPF_ALU | EBPF_MOV | BPF_SRC(TYPE),	\
+		.dst_reg = DST,					\
+		.src_reg = SRC,					\
+		.off   = 0,					\
+		.imm   = IMM })
+
+/* Direct packet access, R0 = *(uint *) (skb->data + imm32) */
+
+#define BPF_LD_ABS(SIZE, IMM)					\
+	((struct ebpf_insn) {					\
+		.code  = BPF_LD | BPF_SIZE(SIZE) | BPF_ABS,	\
+		.dst_reg = 0,					\
+		.src_reg = 0,					\
+		.off   = 0,					\
+		.imm   = IMM })
+
+/* Memory load, dst_reg = *(uint *) (src_reg + off16) */
+
+#define BPF_LDX_MEM(SIZE, DST, SRC, OFF)			\
+	((struct ebpf_insn) {					\
+		.code  = BPF_LDX | BPF_SIZE(SIZE) | BPF_MEM,	\
+		.dst_reg = DST,					\
+		.src_reg = SRC,					\
+		.off   = OFF,					\
+		.imm   = 0 })
+
+/* Memory store, *(uint *) (dst_reg + off16) = src_reg */
+
+#define BPF_STX_MEM(SIZE, DST, SRC, OFF)			\
+	((struct ebpf_insn) {					\
+		.code  = BPF_STX | BPF_SIZE(SIZE) | BPF_MEM,	\
+		.dst_reg = DST,					\
+		.src_reg = SRC,					\
+		.off   = OFF,					\
+		.imm   = 0 })
+
+/* Conditional jumps against immediates, if (dst_reg 'op' imm32) goto pc + off16 */
+
+#define BPF_JMP_IMM(OP, DST, IMM, OFF)				\
+	((struct ebpf_insn) {					\
+		.code  = BPF_JMP | BPF_OP(OP) | BPF_K,		\
+		.dst_reg = DST,					\
+		.src_reg = 0,					\
+		.off   = OFF,					\
+		.imm   = IMM })
+
+/* Raw code statement block */
+
+#define BPF_RAW_INSN(CODE, DST, SRC, OFF, IMM)			\
+	((struct ebpf_insn) {					\
+		.code  = CODE,					\
+		.dst_reg = DST,					\
+		.src_reg = SRC,					\
+		.off   = OFF,					\
+		.imm   = IMM })
+
+/* Program exit */
+
+#define BPF_EXIT_INSN()						\
+	((struct ebpf_insn) {					\
+		.code  = BPF_JMP | EBPF_EXIT,			\
+		.dst_reg = 0,					\
+		.src_reg = 0,					\
+		.off   = 0,					\
+		.imm   = 0 })
+
+/*
+ * Placeholder to convert BPF extensions like length and VLAN tag
+ * If and when DPDK BPF supports them.
+ */
+static bool convert_bpf_load(const struct bpf_insn *fp,
+			     struct ebpf_insn **new_insnp __rte_unused)
+{
+	switch (fp->k) {
+	case SKF_AD_OFF + SKF_AD_PROTOCOL:
+	case SKF_AD_OFF + SKF_AD_PKTTYPE:
+	case SKF_AD_OFF + SKF_AD_IFINDEX:
+	case SKF_AD_OFF + SKF_AD_HATYPE:
+	case SKF_AD_OFF + SKF_AD_MARK:
+	case SKF_AD_OFF + SKF_AD_RXHASH:
+	case SKF_AD_OFF + SKF_AD_QUEUE:
+	case SKF_AD_OFF + SKF_AD_VLAN_TAG:
+	case SKF_AD_OFF + SKF_AD_VLAN_TAG_PRESENT:
+	case SKF_AD_OFF + SKF_AD_VLAN_TPID:
+	case SKF_AD_OFF + SKF_AD_PAY_OFFSET:
+	case SKF_AD_OFF + SKF_AD_NLATTR:
+	case SKF_AD_OFF + SKF_AD_NLATTR_NEST:
+	case SKF_AD_OFF + SKF_AD_CPU:
+	case SKF_AD_OFF + SKF_AD_RANDOM:
+	case SKF_AD_OFF + SKF_AD_ALU_XOR_X:
+		/* Linux has special negative offsets to access meta-data. */
+		RTE_BPF_LOG(ERR,
+			    "rte_bpf_convert: socket offset %d not supported\n",
+			    fp->k - SKF_AD_OFF);
+		return true;
+	default:
+		return false;
+	}
+}
+
+static int bpf_convert_filter(const struct bpf_insn *prog, size_t len,
+			      struct ebpf_insn *new_prog, uint32_t *new_len)
+{
+	unsigned int pass = 0;
+	size_t new_flen = 0, target, i;
+	struct ebpf_insn *new_insn;
+	const struct bpf_insn *fp;
+	int *addrs = NULL;
+	uint8_t bpf_src;
+
+	if (len > BPF_MAXINSNS) {
+		RTE_BPF_LOG(ERR, "%s: cBPF program too long (%zu insns)\n",
+			    __func__, len);
+		return -EINVAL;
+	}
+
+	/* On second pass, allocate the new program */
+	if (new_prog) {
+		addrs = calloc(len, sizeof(*addrs));
+		if (addrs == NULL)
+			return -ENOMEM;
+	}
+
+do_pass:
+	new_insn = new_prog;
+	fp = prog;
+
+	/* Classic BPF related prologue emission. */
+	if (new_insn) {
+		/* Classic BPF expects A and X to be reset first. These need
+		 * to be guaranteed to be the first two instructions.
+		 */
+		*new_insn++ = EBPF_ALU64_REG(BPF_XOR, BPF_REG_A, BPF_REG_A);
+		*new_insn++ = EBPF_ALU64_REG(BPF_XOR, BPF_REG_X, BPF_REG_X);
+
+		/* All programs must keep CTX in callee saved BPF_REG_CTX.
+		 * In eBPF case it's done by the compiler, here we need to
+		 * do this ourself. Initial CTX is present in BPF_REG_ARG1.
+		 */
+		*new_insn++ = BPF_MOV64_REG(BPF_REG_CTX, BPF_REG_ARG1);
+	} else {
+		new_insn += 3;
+	}
+
+	for (i = 0; i < len; fp++, i++) {
+		struct ebpf_insn tmp_insns[6] = { };
+		struct ebpf_insn *insn = tmp_insns;
+
+		if (addrs)
+			addrs[i] = new_insn - new_prog;
+
+		switch (fp->code) {
+			/* Absolute loads are how classic BPF accesses skb */
+		case BPF_LD | BPF_ABS | BPF_W:
+		case BPF_LD | BPF_ABS | BPF_H:
+		case BPF_LD | BPF_ABS | BPF_B:
+			if (convert_bpf_load(fp, &insn))
+				goto err;
+
+			*insn = BPF_RAW_INSN(fp->code, 0, 0, 0, fp->k);
+			break;
+
+		case BPF_ALU | BPF_DIV | BPF_X:
+		case BPF_ALU | BPF_MOD | BPF_X:
+			/* For cBPF, don't cause floating point exception */
+			*insn++ = BPF_MOV32_REG(BPF_REG_X, BPF_REG_X);
+			*insn++ = BPF_JMP_IMM(EBPF_JNE, BPF_REG_X, 0, 2);
+			*insn++ = BPF_ALU32_REG(BPF_XOR, BPF_REG_A, BPF_REG_A);
+			*insn++ = BPF_EXIT_INSN();
+			/* fallthrough */
+		case BPF_ALU | BPF_ADD | BPF_X:
+		case BPF_ALU | BPF_ADD | BPF_K:
+		case BPF_ALU | BPF_SUB | BPF_X:
+		case BPF_ALU | BPF_SUB | BPF_K:
+		case BPF_ALU | BPF_AND | BPF_X:
+		case BPF_ALU | BPF_AND | BPF_K:
+		case BPF_ALU | BPF_OR | BPF_X:
+		case BPF_ALU | BPF_OR | BPF_K:
+		case BPF_ALU | BPF_LSH | BPF_X:
+		case BPF_ALU | BPF_LSH | BPF_K:
+		case BPF_ALU | BPF_RSH | BPF_X:
+		case BPF_ALU | BPF_RSH | BPF_K:
+		case BPF_ALU | BPF_XOR | BPF_X:
+		case BPF_ALU | BPF_XOR | BPF_K:
+		case BPF_ALU | BPF_MUL | BPF_X:
+		case BPF_ALU | BPF_MUL | BPF_K:
+		case BPF_ALU | BPF_DIV | BPF_K:
+		case BPF_ALU | BPF_MOD | BPF_K:
+		case BPF_ALU | BPF_NEG:
+		case BPF_LD | BPF_IND | BPF_W:
+		case BPF_LD | BPF_IND | BPF_H:
+		case BPF_LD | BPF_IND | BPF_B:
+			/* All arithmetic insns map as-is. */
+			*insn = BPF_RAW_INSN(fp->code, BPF_REG_A, BPF_REG_X, 0, fp->k);
+			break;
+
+			/* Jump transformation cannot use BPF block macros
+			 * everywhere as offset calculation and target updates
+			 * require a bit more work than the rest, i.e. jump
+			 * opcodes map as-is, but offsets need adjustment.
+			 */
+
+#define BPF_EMIT_JMP							\
+			do {						\
+				if (target >= len)			\
+					goto err;			\
+				insn->off = addrs ? addrs[target] - addrs[i] - 1 : 0; \
+				/* Adjust pc relative offset for 2nd or 3rd insn. */ \
+				insn->off -= insn - tmp_insns;		\
+			} while (0)
+
+		case BPF_JMP | BPF_JA:
+			target = i + fp->k + 1;
+			insn->code = fp->code;
+			BPF_EMIT_JMP;
+			break;
+
+		case BPF_JMP | BPF_JEQ | BPF_K:
+		case BPF_JMP | BPF_JEQ | BPF_X:
+		case BPF_JMP | BPF_JSET | BPF_K:
+		case BPF_JMP | BPF_JSET | BPF_X:
+		case BPF_JMP | BPF_JGT | BPF_K:
+		case BPF_JMP | BPF_JGT | BPF_X:
+		case BPF_JMP | BPF_JGE | BPF_K:
+		case BPF_JMP | BPF_JGE | BPF_X:
+			if (BPF_SRC(fp->code) == BPF_K && (int) fp->k < 0) {
+				/* BPF immediates are signed, zero extend
+				 * immediate into tmp register and use it
+				 * in compare insn.
+				 */
+				*insn++ = BPF_MOV32_IMM(BPF_REG_TMP, fp->k);
+
+				insn->dst_reg = BPF_REG_A;
+				insn->src_reg = BPF_REG_TMP;
+				bpf_src = BPF_X;
+			} else {
+				insn->dst_reg = BPF_REG_A;
+				insn->imm = fp->k;
+				bpf_src = BPF_SRC(fp->code);
+				insn->src_reg = bpf_src == BPF_X ? BPF_REG_X : 0;
+			}
+
+			/* Common case where 'jump_false' is next insn. */
+			if (fp->jf == 0) {
+				insn->code = BPF_JMP | BPF_OP(fp->code) | bpf_src;
+				target = i + fp->jt + 1;
+				BPF_EMIT_JMP;
+				break;
+			}
+
+			/* Convert JEQ into JNE when 'jump_true' is next insn. */
+			if (fp->jt == 0 && BPF_OP(fp->code) == BPF_JEQ) {
+				insn->code = BPF_JMP | EBPF_JNE | bpf_src;
+				target = i + fp->jf + 1;
+				BPF_EMIT_JMP;
+				break;
+			}
+
+			/* Other jumps are mapped into two insns: Jxx and JA. */
+			target = i + fp->jt + 1;
+			insn->code = BPF_JMP | BPF_OP(fp->code) | bpf_src;
+			BPF_EMIT_JMP;
+			insn++;
+
+			insn->code = BPF_JMP | BPF_JA;
+			target = i + fp->jf + 1;
+			BPF_EMIT_JMP;
+			break;
+
+			/* ldxb 4 * ([14] & 0xf) is remaped into 6 insns. */
+		case BPF_LDX | BPF_MSH | BPF_B:
+			/* tmp = A */
+			*insn++ = BPF_MOV64_REG(BPF_REG_TMP, BPF_REG_A);
+			/* A = BPF_R0 = *(u8 *) (skb->data + K) */
+			*insn++ = BPF_LD_ABS(BPF_B, fp->k);
+			/* A &= 0xf */
+			*insn++ = BPF_ALU32_IMM(BPF_AND, BPF_REG_A, 0xf);
+			/* A <<= 2 */
+			*insn++ = BPF_ALU32_IMM(BPF_LSH, BPF_REG_A, 2);
+			/* X = A */
+			*insn++ = BPF_MOV64_REG(BPF_REG_X, BPF_REG_A);
+			/* A = tmp */
+			*insn = BPF_MOV64_REG(BPF_REG_A, BPF_REG_TMP);
+			break;
+
+			/* RET_K is remaped into 2 insns. RET_A case doesn't need an
+			 * extra mov as EBPF_REG_0 is already mapped into BPF_REG_A.
+			 */
+		case BPF_RET | BPF_A:
+		case BPF_RET | BPF_K:
+			if (BPF_RVAL(fp->code) == BPF_K) {
+				*insn++ = BPF_MOV32_RAW(BPF_K, EBPF_REG_0,
+							0, fp->k);
+			}
+			*insn = BPF_EXIT_INSN();
+			break;
+
+			/* Store to stack. */
+		case BPF_ST:
+		case BPF_STX:
+			*insn = BPF_STX_MEM(BPF_W, BPF_REG_FP, BPF_CLASS(fp->code) ==
+					    BPF_ST ? BPF_REG_A : BPF_REG_X,
+					    -(BPF_MEMWORDS - fp->k) * 4);
+			break;
+
+			/* Load from stack. */
+		case BPF_LD | BPF_MEM:
+		case BPF_LDX | BPF_MEM:
+			*insn = BPF_LDX_MEM(BPF_W, BPF_CLASS(fp->code) == BPF_LD  ?
+					    BPF_REG_A : BPF_REG_X, BPF_REG_FP,
+					    -(BPF_MEMWORDS - fp->k) * 4);
+			break;
+
+			/* A = K or X = K */
+		case BPF_LD | BPF_IMM:
+		case BPF_LDX | BPF_IMM:
+			*insn = BPF_MOV32_IMM(BPF_CLASS(fp->code) == BPF_LD ?
+					      BPF_REG_A : BPF_REG_X, fp->k);
+			break;
+
+			/* X = A */
+		case BPF_MISC | BPF_TAX:
+			*insn = BPF_MOV64_REG(BPF_REG_X, BPF_REG_A);
+			break;
+
+			/* A = X */
+		case BPF_MISC | BPF_TXA:
+			*insn = BPF_MOV64_REG(BPF_REG_A, BPF_REG_X);
+			break;
+
+			/* A = mbuf->len or X = mbuf->len */
+		case BPF_LD | BPF_W | BPF_LEN:
+		case BPF_LDX | BPF_W | BPF_LEN:
+			/* BPF_ABS/BPF_IND implicitly expect mbuf ptr in R6 */
+
+			*insn = BPF_LDX_MEM(BPF_W, BPF_CLASS(fp->code) == BPF_LD ?
+					    BPF_REG_A : BPF_REG_X, BPF_REG_CTX,
+					    offsetof(struct rte_mbuf, pkt_len));
+			break;
+
+			/* Unknown instruction. */
+		default:
+			RTE_BPF_LOG(ERR, "%s: Unknown instruction!: %#x\n",
+				    __func__, fp->code);
+			goto err;
+		}
+
+		insn++;
+		if (new_prog)
+			memcpy(new_insn, tmp_insns,
+			       sizeof(*insn) * (insn - tmp_insns));
+		new_insn += insn - tmp_insns;
+	}
+
+	if (!new_prog) {
+		/* Only calculating new length. */
+		*new_len = new_insn - new_prog;
+		return 0;
+	}
+
+	pass++;
+	if ((ptrdiff_t)new_flen != new_insn - new_prog) {
+		new_flen = new_insn - new_prog;
+		if (pass > 2)
+			goto err;
+		goto do_pass;
+	}
+
+	free(addrs);
+	assert(*new_len == new_flen);
+
+	return 0;
+err:
+	free(addrs);
+	return -1;
+}
+
+struct rte_bpf_prm *
+rte_bpf_convert(const struct bpf_program *prog)
+{
+	struct rte_bpf_prm *prm = NULL;
+	struct ebpf_insn *ebpf = NULL;
+	uint32_t ebpf_len = 0;
+	int ret;
+
+	if (prog == NULL) {
+		RTE_BPF_LOG(ERR, "%s: NULL program\n", __func__);
+		rte_errno = EINVAL;
+		return NULL;
+	}
+
+	/* 1st pass: calculate the eBPF program length */
+	ret = bpf_convert_filter(prog->bf_insns, prog->bf_len, NULL, &ebpf_len);
+	if (ret < 0) {
+		RTE_BPF_LOG(ERR, "%s: cannot get eBPF length\n", __func__);
+		rte_errno = -ret;
+		return NULL;
+	}
+
+	RTE_BPF_LOG(DEBUG, "%s: prog len cBPF=%u -> eBPF=%u\n",
+		    __func__, prog->bf_len, ebpf_len);
+
+	prm = rte_zmalloc("bpf_filter",
+			  sizeof(*prm) + ebpf_len * sizeof(*ebpf), 0);
+	if (prm == NULL) {
+		rte_errno = ENOMEM;
+		return NULL;
+	}
+
+	/* The EPBF instructions in this case are right after the header */
+	ebpf = (void *)(prm + 1);
+
+	/* 2nd pass: remap cBPF to eBPF instructions  */
+	ret = bpf_convert_filter(prog->bf_insns, prog->bf_len, ebpf, &ebpf_len);
+	if (ret < 0) {
+		RTE_BPF_LOG(ERR, "%s: cannot convert cBPF to eBPF\n", __func__);
+		free(prm);
+		rte_errno = -ret;
+		return NULL;
+	}
+
+	prm->ins = ebpf;
+	prm->nb_ins = ebpf_len;
+
+	/* Classic BPF programs use mbufs */
+	prm->prog_arg.type = RTE_BPF_ARG_PTR_MBUF;
+	prm->prog_arg.size = sizeof(struct rte_mbuf);
+
+	return prm;
+}
diff --git a/lib/bpf/meson.build b/lib/bpf/meson.build
index 63cbd60185e0..54f7610ae990 100644
--- a/lib/bpf/meson.build
+++ b/lib/bpf/meson.build
@@ -25,3 +25,8 @@  if dep.found()
     sources += files('bpf_load_elf.c')
     ext_deps += dep
 endif
+
+if dpdk_conf.has('RTE_PORT_PCAP')
+    sources += files('bpf_convert.c')
+    ext_deps += pcap_dep
+endif
diff --git a/lib/bpf/rte_bpf.h b/lib/bpf/rte_bpf.h
index 69116f36ba8b..2f23e272a376 100644
--- a/lib/bpf/rte_bpf.h
+++ b/lib/bpf/rte_bpf.h
@@ -198,6 +198,31 @@  rte_bpf_exec_burst(const struct rte_bpf *bpf, void *ctx[], uint64_t rc[],
 int
 rte_bpf_get_jit(const struct rte_bpf *bpf, struct rte_bpf_jit *jit);
 
+#ifdef RTE_PORT_PCAP
+
+struct bpf_program;
+
+/**
+ * Convert a Classic BPF program from libpcap into a DPDK BPF code.
+ *
+ * @param prog
+ *  Classic BPF program from pcap_compile().
+ * @param prm
+ *  Result Extended BPF program.
+ * @return
+ *   Pointer to BPF program (allocated with *rte_malloc*)
+ *   that is used in future BPF operations,
+ *   or NULL on error, with error code set in rte_errno.
+ *   Possible rte_errno errors include:
+ *   - EINVAL - invalid parameter passed to function
+ *   - ENOMEM - can't reserve enough memory
+ */
+__rte_experimental
+struct rte_bpf_prm *
+rte_bpf_convert(const struct bpf_program *prog);
+
+#endif
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/lib/bpf/version.map b/lib/bpf/version.map
index 0bf35f487666..47082d5003ef 100644
--- a/lib/bpf/version.map
+++ b/lib/bpf/version.map
@@ -14,3 +14,9 @@  DPDK_22 {
 
 	local: *;
 };
+
+EXPERIMENTAL {
+	global:
+
+	rte_bpf_convert;
+};