* [PATCH 2/7 nft v3] tunnel: add erspan support
2025-08-21 9:12 [PATCH 1/7 nft v3] src: add tunnel template support Fernando Fernandez Mancera
@ 2025-08-21 9:12 ` Fernando Fernandez Mancera
2025-08-21 9:12 ` [PATCH 3/7 nft v3] src: add tunnel statement and expression support Fernando Fernandez Mancera
` (5 subsequent siblings)
6 siblings, 0 replies; 13+ messages in thread
From: Fernando Fernandez Mancera @ 2025-08-21 9:12 UTC (permalink / raw)
To: netfilter-devel; +Cc: coreteam, pablo, fw, Fernando Fernandez Mancera
From: Pablo Neira Ayuso <pablo@netfilter.org>
This patch extends the tunnel metadata object to define erspan tunnel
specific configurations:
table netdev x {
tunnel y {
id 10
ip saddr 192.168.2.10
ip daddr 192.168.2.11
sport 10
dport 20
ttl 10
erspan {
version 1
index 2
}
}
}
Joint work with Fernando.
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
Signed-off-by: Fernando Fernandez Mancera <fmancera@suse.de>
---
v3: rebased
---
include/rule.h | 18 ++++++++++++++++++
src/mnl.c | 43 +++++++++++++++++++++++++++++++++++++++++++
src/netlink.c | 38 ++++++++++++++++++++++++++++++++++++++
src/parser_bison.y | 46 +++++++++++++++++++++++++++++++++++++++++++++-
src/rule.c | 26 ++++++++++++++++++++++++++
src/scanner.l | 5 +++++
6 files changed, 175 insertions(+), 1 deletion(-)
diff --git a/include/rule.h b/include/rule.h
index 0fa87b52..71e9a07e 100644
--- a/include/rule.h
+++ b/include/rule.h
@@ -492,6 +492,11 @@ struct secmark {
char ctx[NFT_SECMARK_CTX_MAXLEN];
};
+enum tunnel_type {
+ TUNNEL_UNSPEC = 0,
+ TUNNEL_ERSPAN,
+};
+
struct tunnel {
uint32_t id;
struct expr *src;
@@ -500,6 +505,19 @@ struct tunnel {
uint16_t dport;
uint8_t tos;
uint8_t ttl;
+ enum tunnel_type type;
+ union {
+ struct {
+ uint32_t version;
+ struct {
+ uint32_t index;
+ } v1;
+ struct {
+ uint8_t direction;
+ uint8_t hwid;
+ } v2;
+ } erspan;
+ };
};
/**
diff --git a/src/mnl.c b/src/mnl.c
index e17f2c2f..3762b6f9 100644
--- a/src/mnl.c
+++ b/src/mnl.c
@@ -1474,6 +1474,48 @@ err:
return NULL;
}
+static void obj_tunnel_add_opts(struct nftnl_obj *nlo, struct tunnel *tunnel)
+{
+ struct nftnl_tunnel_opts *opts;
+ struct nftnl_tunnel_opt *opt;
+
+ switch (tunnel->type) {
+ case TUNNEL_ERSPAN:
+ opts = nftnl_tunnel_opts_alloc(NFTNL_TUNNEL_TYPE_ERSPAN);
+ if (!opts)
+ memory_allocation_error();
+
+ opt = nftnl_tunnel_opt_alloc(NFTNL_TUNNEL_TYPE_ERSPAN);
+ if (!opt)
+ memory_allocation_error();
+
+ nftnl_tunnel_opt_set(opt, NFTNL_TUNNEL_ERSPAN_VERSION,
+ &tunnel->erspan.version,
+ sizeof(tunnel->erspan.version));
+ switch (tunnel->erspan.version) {
+ case 1:
+ nftnl_tunnel_opt_set(opt, NFTNL_TUNNEL_ERSPAN_V1_INDEX,
+ &tunnel->erspan.v1.index,
+ sizeof(tunnel->erspan.v1.index));
+ break;
+ case 2:
+ nftnl_tunnel_opt_set(opt, NFTNL_TUNNEL_ERSPAN_V2_DIR,
+ &tunnel->erspan.v2.direction,
+ sizeof(tunnel->erspan.v2.direction));
+ nftnl_tunnel_opt_set(opt, NFTNL_TUNNEL_ERSPAN_V2_HWID,
+ &tunnel->erspan.v2.hwid,
+ sizeof(tunnel->erspan.v2.hwid));
+ break;
+ }
+
+ nftnl_tunnel_opts_add(opts, opt);
+ nftnl_obj_set_data(nlo, NFTNL_OBJ_TUNNEL_OPTS, &opts, sizeof(struct nftnl_tunnel_opts *));
+ break;
+ case TUNNEL_UNSPEC:
+ break;
+ }
+}
+
int mnl_nft_obj_add(struct netlink_ctx *ctx, struct cmd *cmd,
unsigned int flags)
{
@@ -1605,6 +1647,7 @@ int mnl_nft_obj_add(struct netlink_ctx *ctx, struct cmd *cmd,
nld.value, nld.len);
}
}
+ obj_tunnel_add_opts(nlo, &obj->tunnel);
break;
default:
BUG("Unknown type %d\n", obj->type);
diff --git a/src/netlink.c b/src/netlink.c
index f0a9c02b..4ef88402 100644
--- a/src/netlink.c
+++ b/src/netlink.c
@@ -1808,6 +1808,41 @@ static int obj_parse_udata_cb(const struct nftnl_udata *attr, void *data)
return 0;
}
+static int tunnel_parse_opt_cb(struct nftnl_tunnel_opt *opt, void *data) {
+
+ struct obj *obj = data;
+
+ switch (nftnl_tunnel_opt_get_type(opt)) {
+ case NFTNL_TUNNEL_TYPE_ERSPAN:
+ obj->tunnel.type = TUNNEL_ERSPAN;
+ if (nftnl_tunnel_opt_get_flags(opt) & (1 << NFTNL_TUNNEL_ERSPAN_VERSION)) {
+ obj->tunnel.erspan.version =
+ nftnl_tunnel_opt_get_u32(opt,
+ NFTNL_TUNNEL_ERSPAN_VERSION);
+ }
+ if (nftnl_tunnel_opt_get_flags(opt) & (1 << NFTNL_TUNNEL_ERSPAN_V1_INDEX)) {
+ obj->tunnel.erspan.v1.index =
+ nftnl_tunnel_opt_get_u32(opt,
+ NFTNL_TUNNEL_ERSPAN_V1_INDEX);
+ }
+ if (nftnl_tunnel_opt_get_flags(opt) & (1 << NFTNL_TUNNEL_ERSPAN_V2_HWID)) {
+ obj->tunnel.erspan.v2.hwid =
+ nftnl_tunnel_opt_get_u8(opt,
+ NFTNL_TUNNEL_ERSPAN_V2_HWID);
+ }
+ if (nftnl_tunnel_opt_get_flags(opt) & (1 << NFTNL_TUNNEL_ERSPAN_V2_DIR)) {
+ obj->tunnel.erspan.v2.direction =
+ nftnl_tunnel_opt_get_u8(opt,
+ NFTNL_TUNNEL_ERSPAN_V2_DIR);
+ }
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
struct obj *netlink_delinearize_obj(struct netlink_ctx *ctx,
struct nftnl_obj *nlo)
{
@@ -1938,6 +1973,9 @@ struct obj *netlink_delinearize_obj(struct netlink_ctx *ctx,
obj->tunnel.dst =
netlink_obj_tunnel_parse_addr(nlo, NFTNL_OBJ_TUNNEL_IPV6_DST);
}
+ if (nftnl_obj_is_set(nlo, NFTNL_OBJ_TUNNEL_OPTS)) {
+ nftnl_obj_tunnel_opts_foreach(nlo, tunnel_parse_opt_cb, obj);
+ }
break;
default:
netlink_io_error(ctx, NULL, "Unknown object type %u", type);
diff --git a/src/parser_bison.y b/src/parser_bison.y
index 367c6b3b..557977e2 100644
--- a/src/parser_bison.y
+++ b/src/parser_bison.y
@@ -607,6 +607,9 @@ int nft_lex(void *, void *, void *);
%token NEVER "never"
%token TUNNEL "tunnel"
+%token ERSPAN "erspan"
+%token EGRESS "egress"
+%token INGRESS "ingress"
%token COUNTERS "counters"
%token QUOTAS "quotas"
@@ -765,7 +768,7 @@ int nft_lex(void *, void *, void *);
%type <flowtable> flowtable_block_alloc flowtable_block
%destructor { flowtable_free($$); } flowtable_block_alloc
-%type <obj> obj_block_alloc counter_block quota_block ct_helper_block ct_timeout_block ct_expect_block limit_block secmark_block synproxy_block tunnel_block
+%type <obj> obj_block_alloc counter_block quota_block ct_helper_block ct_timeout_block ct_expect_block limit_block secmark_block synproxy_block tunnel_block erspan_block erspan_block_alloc
%destructor { obj_free($$); } obj_block_alloc
%type <list> stmt_list stateful_stmt_list set_elem_stmt_list
@@ -4958,6 +4961,43 @@ limit_obj : /* empty */
}
;
+erspan_block : /* empty */ { $$ = $<obj>-1; }
+ | erspan_block common_block
+ | erspan_block stmt_separator
+ | erspan_block erspan_config stmt_separator
+ {
+ $$ = $1;
+ }
+ ;
+
+erspan_block_alloc : /* empty */
+ {
+ $$ = $<obj>-1;
+ }
+ ;
+
+erspan_config : HDRVERSION NUM
+ {
+ $<obj>0->tunnel.erspan.version = $2;
+ }
+ | INDEX NUM
+ {
+ $<obj>0->tunnel.erspan.v1.index = $2;
+ }
+ | DIRECTION INGRESS
+ {
+ $<obj>0->tunnel.erspan.v2.direction = 0;
+ }
+ | DIRECTION EGRESS
+ {
+ $<obj>0->tunnel.erspan.v2.direction = 1;
+ }
+ | ID NUM
+ {
+ $<obj>0->tunnel.erspan.v2.hwid = $2;
+ }
+ ;
+
tunnel_config : ID NUM
{
$<obj>0->tunnel.id = $2;
@@ -4998,6 +5038,10 @@ tunnel_config : ID NUM
{
$<obj>0->tunnel.tos = $2;
}
+ | ERSPAN erspan_block_alloc '{' erspan_block '}'
+ {
+ $<obj>0->tunnel.type = TUNNEL_ERSPAN;
+ }
;
tunnel_block : /* empty */ { $$ = $<obj>-1; }
diff --git a/src/rule.c b/src/rule.c
index 5b79facb..2557f4cc 100644
--- a/src/rule.c
+++ b/src/rule.c
@@ -2021,6 +2021,32 @@ static void obj_print_data(const struct obj *obj,
opts->nl, opts->tab, opts->tab,
obj->tunnel.ttl);
}
+ switch (obj->tunnel.type) {
+ case TUNNEL_ERSPAN:
+ nft_print(octx, "%s%s%serspan {",
+ opts->nl, opts->tab, opts->tab);
+ nft_print(octx, "%s%s%s%sversion %u",
+ opts->nl, opts->tab, opts->tab, opts->tab,
+ obj->tunnel.erspan.version);
+ if (obj->tunnel.erspan.version == 1) {
+ nft_print(octx, "%s%s%s%sindex %u",
+ opts->nl, opts->tab, opts->tab, opts->tab,
+ obj->tunnel.erspan.v1.index);
+ } else {
+ nft_print(octx, "%s%s%s%sdirection %s",
+ opts->nl, opts->tab, opts->tab, opts->tab,
+ obj->tunnel.erspan.v2.direction ? "egress"
+ : "ingress");
+ nft_print(octx, "%s%s%s%sid %u",
+ opts->nl, opts->tab, opts->tab, opts->tab,
+ obj->tunnel.erspan.v2.hwid);
+ }
+ nft_print(octx, "%s%s%s}",
+ opts->nl, opts->tab, opts->tab);
+ default:
+ break;
+ }
+
nft_print(octx, "%s", opts->stmt_separator);
break;
default:
diff --git a/src/scanner.l b/src/scanner.l
index 5e848890..def0ac0e 100644
--- a/src/scanner.l
+++ b/src/scanner.l
@@ -821,6 +821,11 @@ addrstring ({macaddr}|{ip4addr}|{ip6addr})
"dport" { return DPORT; }
"ttl" { return TTL; }
"tos" { return TOS; }
+ "version" { return HDRVERSION; }
+ "direction" { return DIRECTION; }
+ "erspan" { return ERSPAN; }
+ "egress" { return EGRESS; }
+ "ingress" { return INGRESS; }
}
"notrack" { return NOTRACK; }
--
2.50.1
^ permalink raw reply related [flat|nested] 13+ messages in thread* [PATCH 3/7 nft v3] src: add tunnel statement and expression support
2025-08-21 9:12 [PATCH 1/7 nft v3] src: add tunnel template support Fernando Fernandez Mancera
2025-08-21 9:12 ` [PATCH 2/7 nft v3] tunnel: add erspan support Fernando Fernandez Mancera
@ 2025-08-21 9:12 ` Fernando Fernandez Mancera
2025-12-29 13:51 ` Yi Chen
2025-08-21 9:12 ` [PATCH 4/7 nft v3] tunnel: add vxlan support Fernando Fernandez Mancera
` (4 subsequent siblings)
6 siblings, 1 reply; 13+ messages in thread
From: Fernando Fernandez Mancera @ 2025-08-21 9:12 UTC (permalink / raw)
To: netfilter-devel; +Cc: coreteam, pablo, fw, Fernando Fernandez Mancera
From: Pablo Neira Ayuso <pablo@netfilter.org>
This patch allows you to attach tunnel metadata through the tunnel
statement.
The following example shows how to redirect traffic to the erspan0
tunnel device which will take the tunnel configuration that is
specified by the ruleset.
table netdev x {
tunnel y {
id 10
ip saddr 192.168.2.10
ip daddr 192.168.2.11
sport 10
dport 20
ttl 10
erspan {
version 1
index 2
}
}
chain x {
type filter hook ingress device veth0 priority 0;
ip daddr 10.141.10.123 tunnel name y fwd to erspan0
}
}
This patch also allows to match on tunnel metadata via tunnel expression.
Joint work with Fernando.
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
Signed-off-by: Fernando Fernandez Mancera <fmancera@suse.de>
---
v3: rebased
---
Makefile.am | 2 +
include/expression.h | 6 +++
include/tunnel.h | 33 ++++++++++++++++
src/evaluate.c | 8 ++++
src/expression.c | 1 +
src/netlink_delinearize.c | 17 ++++++++
src/netlink_linearize.c | 14 +++++++
src/parser_bison.y | 33 +++++++++++++---
src/scanner.l | 3 +-
src/statement.c | 1 +
src/tunnel.c | 81 +++++++++++++++++++++++++++++++++++++++
11 files changed, 193 insertions(+), 6 deletions(-)
create mode 100644 include/tunnel.h
create mode 100644 src/tunnel.c
diff --git a/Makefile.am b/Makefile.am
index 4909abfe..152a80d6 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -100,6 +100,7 @@ noinst_HEADERS = \
include/statement.h \
include/tcpopt.h \
include/trace.h \
+ include/tunnel.h \
include/utils.h \
include/xfrm.h \
include/xt.h \
@@ -243,6 +244,7 @@ src_libnftables_la_SOURCES = \
src/socket.c \
src/statement.c \
src/tcpopt.c \
+ src/tunnel.c \
src/utils.c \
src/xfrm.c \
$(NULL)
diff --git a/include/expression.h b/include/expression.h
index e483b7e7..7185ee66 100644
--- a/include/expression.h
+++ b/include/expression.h
@@ -77,6 +77,7 @@ enum expr_types {
EXPR_NUMGEN,
EXPR_HASH,
EXPR_RT,
+ EXPR_TUNNEL,
EXPR_FIB,
EXPR_XFRM,
EXPR_SET_ELEM_CATCHALL,
@@ -229,6 +230,7 @@ enum expr_flags {
#include <hash.h>
#include <ct.h>
#include <socket.h>
+#include <tunnel.h>
#include <osf.h>
#include <xfrm.h>
@@ -368,6 +370,10 @@ struct expr {
enum nft_socket_keys key;
uint32_t level;
} socket;
+ struct {
+ /* EXPR_TUNNEL */
+ enum nft_tunnel_keys key;
+ } tunnel;
struct {
/* EXPR_RT */
enum nft_rt_keys key;
diff --git a/include/tunnel.h b/include/tunnel.h
new file mode 100644
index 00000000..9e6bd97a
--- /dev/null
+++ b/include/tunnel.h
@@ -0,0 +1,33 @@
+#ifndef NFTABLES_TUNNEL_H
+#define NFTABLES_TUNNEL_H
+
+/**
+ * struct tunnel_template - template for tunnel expressions
+ *
+ * @token: parser token for the expression
+ * @dtype: data type of the expression
+ * @len: length of the expression
+ * @byteorder: byteorder
+ */
+struct tunnel_template {
+ const char *token;
+ const struct datatype *dtype;
+ enum byteorder byteorder;
+ unsigned int len;
+};
+
+extern const struct tunnel_template tunnel_templates[];
+
+#define TUNNEL_TEMPLATE(__token, __dtype, __len, __byteorder) { \
+ .token = (__token), \
+ .dtype = (__dtype), \
+ .len = (__len), \
+ .byteorder = (__byteorder), \
+}
+
+extern struct expr *tunnel_expr_alloc(const struct location *loc,
+ enum nft_tunnel_keys key);
+
+extern const struct expr_ops tunnel_expr_ops;
+
+#endif /* NFTABLES_TUNNEL_H */
diff --git a/src/evaluate.c b/src/evaluate.c
index da8794dd..6bf14b0c 100644
--- a/src/evaluate.c
+++ b/src/evaluate.c
@@ -1737,6 +1737,7 @@ static int expr_evaluate_concat(struct eval_ctx *ctx, struct expr **expr)
case EXPR_SOCKET:
case EXPR_OSF:
case EXPR_XFRM:
+ case EXPR_TUNNEL:
break;
case EXPR_RANGE:
case EXPR_PREFIX:
@@ -3053,6 +3054,11 @@ static int expr_evaluate_osf(struct eval_ctx *ctx, struct expr **expr)
return expr_evaluate_primary(ctx, expr);
}
+static int expr_evaluate_tunnel(struct eval_ctx *ctx, struct expr **exprp)
+{
+ return expr_evaluate_primary(ctx, exprp);
+}
+
static int expr_evaluate_variable(struct eval_ctx *ctx, struct expr **exprp)
{
struct symbol *sym = (*exprp)->sym;
@@ -3170,6 +3176,8 @@ static int expr_evaluate(struct eval_ctx *ctx, struct expr **expr)
return expr_evaluate_meta(ctx, expr);
case EXPR_SOCKET:
return expr_evaluate_socket(ctx, expr);
+ case EXPR_TUNNEL:
+ return expr_evaluate_tunnel(ctx, expr);
case EXPR_OSF:
return expr_evaluate_osf(ctx, expr);
case EXPR_FIB:
diff --git a/src/expression.c b/src/expression.c
index 8cb63979..e3c27a13 100644
--- a/src/expression.c
+++ b/src/expression.c
@@ -1762,6 +1762,7 @@ static const struct expr_ops *__expr_ops_by_type(enum expr_types etype)
case EXPR_NUMGEN: return &numgen_expr_ops;
case EXPR_HASH: return &hash_expr_ops;
case EXPR_RT: return &rt_expr_ops;
+ case EXPR_TUNNEL: return &tunnel_expr_ops;
case EXPR_FIB: return &fib_expr_ops;
case EXPR_XFRM: return &xfrm_expr_ops;
case EXPR_SET_ELEM_CATCHALL: return &set_elem_catchall_expr_ops;
diff --git a/src/netlink_delinearize.c b/src/netlink_delinearize.c
index b97962a3..5627826d 100644
--- a/src/netlink_delinearize.c
+++ b/src/netlink_delinearize.c
@@ -940,6 +940,21 @@ static void netlink_parse_osf(struct netlink_parse_ctx *ctx,
netlink_set_register(ctx, dreg, expr);
}
+static void netlink_parse_tunnel(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nftnl_expr *nle)
+{
+ enum nft_registers dreg;
+ struct expr * expr;
+ uint32_t key;
+
+ key = nftnl_expr_get_u32(nle, NFTNL_EXPR_TUNNEL_KEY);
+ expr = tunnel_expr_alloc(loc, key);
+
+ dreg = netlink_parse_register(nle, NFTNL_EXPR_TUNNEL_DREG);
+ netlink_set_register(ctx, dreg, expr);
+}
+
static void netlink_parse_meta_stmt(struct netlink_parse_ctx *ctx,
const struct location *loc,
const struct nftnl_expr *nle)
@@ -1922,6 +1937,7 @@ static const struct expr_handler netlink_parsers[] = {
{ .name = "exthdr", .parse = netlink_parse_exthdr },
{ .name = "meta", .parse = netlink_parse_meta },
{ .name = "socket", .parse = netlink_parse_socket },
+ { .name = "tunnel", .parse = netlink_parse_tunnel },
{ .name = "osf", .parse = netlink_parse_osf },
{ .name = "rt", .parse = netlink_parse_rt },
{ .name = "ct", .parse = netlink_parse_ct },
@@ -3023,6 +3039,7 @@ static void expr_postprocess(struct rule_pp_ctx *ctx, struct expr **exprp)
case EXPR_NUMGEN:
case EXPR_FIB:
case EXPR_SOCKET:
+ case EXPR_TUNNEL:
case EXPR_OSF:
case EXPR_XFRM:
break;
diff --git a/src/netlink_linearize.c b/src/netlink_linearize.c
index 8ac33d34..d01cadf8 100644
--- a/src/netlink_linearize.c
+++ b/src/netlink_linearize.c
@@ -334,6 +334,18 @@ static void netlink_gen_osf(struct netlink_linearize_ctx *ctx,
nft_rule_add_expr(ctx, nle, &expr->location);
}
+static void netlink_gen_tunnel(struct netlink_linearize_ctx *ctx,
+ const struct expr *expr,
+ enum nft_registers dreg)
+{
+ struct nftnl_expr *nle;
+
+ nle = alloc_nft_expr("tunnel");
+ netlink_put_register(nle, NFTNL_EXPR_TUNNEL_DREG, dreg);
+ nftnl_expr_set_u32(nle, NFTNL_EXPR_TUNNEL_KEY, expr->tunnel.key);
+ nftnl_rule_add_expr(ctx->nlr, nle);
+}
+
static void netlink_gen_numgen(struct netlink_linearize_ctx *ctx,
const struct expr *expr,
enum nft_registers dreg)
@@ -932,6 +944,8 @@ static void netlink_gen_expr(struct netlink_linearize_ctx *ctx,
return netlink_gen_fib(ctx, expr, dreg);
case EXPR_SOCKET:
return netlink_gen_socket(ctx, expr, dreg);
+ case EXPR_TUNNEL:
+ return netlink_gen_tunnel(ctx, expr, dreg);
case EXPR_OSF:
return netlink_gen_osf(ctx, expr, dreg);
case EXPR_XFRM:
diff --git a/src/parser_bison.y b/src/parser_bison.y
index 557977e2..08d75dbb 100644
--- a/src/parser_bison.y
+++ b/src/parser_bison.y
@@ -321,6 +321,8 @@ int nft_lex(void *, void *, void *);
%token RULESET "ruleset"
%token TRACE "trace"
+%token PATH "path"
+
%token INET "inet"
%token NETDEV "netdev"
@@ -779,8 +781,8 @@ int nft_lex(void *, void *, void *);
%destructor { stmt_free($$); } counter_stmt counter_stmt_alloc stateful_stmt last_stmt
%type <stmt> limit_stmt_alloc quota_stmt_alloc last_stmt_alloc ct_limit_stmt_alloc
%destructor { stmt_free($$); } limit_stmt_alloc quota_stmt_alloc last_stmt_alloc ct_limit_stmt_alloc
-%type <stmt> objref_stmt objref_stmt_counter objref_stmt_limit objref_stmt_quota objref_stmt_ct objref_stmt_synproxy
-%destructor { stmt_free($$); } objref_stmt objref_stmt_counter objref_stmt_limit objref_stmt_quota objref_stmt_ct objref_stmt_synproxy
+%type <stmt> objref_stmt objref_stmt_counter objref_stmt_limit objref_stmt_quota objref_stmt_ct objref_stmt_synproxy objref_stmt_tunnel
+%destructor { stmt_free($$); } objref_stmt objref_stmt_counter objref_stmt_limit objref_stmt_quota objref_stmt_ct objref_stmt_synproxy objref_stmt_tunnel
%type <stmt> payload_stmt
%destructor { stmt_free($$); } payload_stmt
@@ -940,9 +942,9 @@ int nft_lex(void *, void *, void *);
%destructor { expr_free($$); } mh_hdr_expr
%type <val> mh_hdr_field
-%type <expr> meta_expr
-%destructor { expr_free($$); } meta_expr
-%type <val> meta_key meta_key_qualified meta_key_unqualified numgen_type
+%type <expr> meta_expr tunnel_expr
+%destructor { expr_free($$); } meta_expr tunnel_expr
+%type <val> meta_key meta_key_qualified meta_key_unqualified numgen_type tunnel_key
%type <expr> socket_expr
%destructor { expr_free($$); } socket_expr
@@ -3206,6 +3208,14 @@ objref_stmt_synproxy : SYNPROXY NAME stmt_expr close_scope_synproxy
}
;
+objref_stmt_tunnel : TUNNEL NAME stmt_expr close_scope_tunnel
+ {
+ $$ = objref_stmt_alloc(&@$);
+ $$->objref.type = NFT_OBJECT_TUNNEL;
+ $$->objref.expr = $3;
+ }
+ ;
+
objref_stmt_ct : CT TIMEOUT SET stmt_expr close_scope_ct
{
$$ = objref_stmt_alloc(&@$);
@@ -3226,6 +3236,7 @@ objref_stmt : objref_stmt_counter
| objref_stmt_quota
| objref_stmt_synproxy
| objref_stmt_ct
+ | objref_stmt_tunnel
;
stateful_stmt : counter_stmt close_scope_counter
@@ -3904,6 +3915,7 @@ primary_stmt_expr : symbol_expr { $$ = $1; }
| boolean_expr { $$ = $1; }
| meta_expr { $$ = $1; }
| rt_expr { $$ = $1; }
+ | tunnel_expr { $$ = $1; }
| ct_expr { $$ = $1; }
| numgen_expr { $$ = $1; }
| hash_expr { $$ = $1; }
@@ -4381,6 +4393,7 @@ selector_expr : payload_expr { $$ = $1; }
| exthdr_expr { $$ = $1; }
| exthdr_exists_expr { $$ = $1; }
| meta_expr { $$ = $1; }
+ | tunnel_expr { $$ = $1; }
| socket_expr { $$ = $1; }
| rt_expr { $$ = $1; }
| ct_expr { $$ = $1; }
@@ -5493,6 +5506,16 @@ socket_key : TRANSPARENT { $$ = NFT_SOCKET_TRANSPARENT; }
| WILDCARD { $$ = NFT_SOCKET_WILDCARD; }
;
+tunnel_key : PATH { $$ = NFT_TUNNEL_PATH; }
+ | ID { $$ = NFT_TUNNEL_ID; }
+ ;
+
+tunnel_expr : TUNNEL tunnel_key
+ {
+ $$ = tunnel_expr_alloc(&@$, $2);
+ }
+ ;
+
offset_opt : /* empty */ { $$ = 0; }
| OFFSET NUM { $$ = $2; }
;
diff --git a/src/scanner.l b/src/scanner.l
index def0ac0e..9695d710 100644
--- a/src/scanner.l
+++ b/src/scanner.l
@@ -410,7 +410,7 @@ addrstring ({macaddr}|{ip4addr}|{ip6addr})
}
"counter" { scanner_push_start_cond(yyscanner, SCANSTATE_COUNTER); return COUNTER; }
-<SCANSTATE_COUNTER,SCANSTATE_LIMIT,SCANSTATE_QUOTA,SCANSTATE_STMT_SYNPROXY,SCANSTATE_EXPR_OSF>"name" { return NAME; }
+<SCANSTATE_COUNTER,SCANSTATE_LIMIT,SCANSTATE_QUOTA,SCANSTATE_STMT_SYNPROXY,SCANSTATE_EXPR_OSF,SCANSTATE_TUNNEL>"name" { return NAME; }
<SCANSTATE_COUNTER,SCANSTATE_CT,SCANSTATE_LIMIT>"packets" { return PACKETS; }
<SCANSTATE_COUNTER,SCANSTATE_CT,SCANSTATE_LIMIT,SCANSTATE_QUOTA>"bytes" { return BYTES; }
@@ -826,6 +826,7 @@ addrstring ({macaddr}|{ip4addr}|{ip6addr})
"erspan" { return ERSPAN; }
"egress" { return EGRESS; }
"ingress" { return INGRESS; }
+ "path" { return PATH; }
}
"notrack" { return NOTRACK; }
diff --git a/src/statement.c b/src/statement.c
index 2bfed4ac..20241f68 100644
--- a/src/statement.c
+++ b/src/statement.c
@@ -290,6 +290,7 @@ static const char *objref_type[NFT_OBJECT_MAX + 1] = {
[NFT_OBJECT_QUOTA] = "quota",
[NFT_OBJECT_CT_HELPER] = "ct helper",
[NFT_OBJECT_LIMIT] = "limit",
+ [NFT_OBJECT_TUNNEL] = "tunnel",
[NFT_OBJECT_CT_TIMEOUT] = "ct timeout",
[NFT_OBJECT_SECMARK] = "secmark",
[NFT_OBJECT_SYNPROXY] = "synproxy",
diff --git a/src/tunnel.c b/src/tunnel.c
new file mode 100644
index 00000000..cd92d039
--- /dev/null
+++ b/src/tunnel.c
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2018 Pablo Neira Ayuso <pablo@netfilter.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <errno.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+#include <net/if.h>
+#include <net/if_arp.h>
+#include <pwd.h>
+#include <grp.h>
+#include <arpa/inet.h>
+#include <linux/netfilter.h>
+#include <linux/pkt_sched.h>
+#include <linux/if_packet.h>
+
+#include <nftables.h>
+#include <expression.h>
+#include <datatype.h>
+#include <tunnel.h>
+#include <gmputil.h>
+#include <utils.h>
+#include <erec.h>
+
+const struct tunnel_template tunnel_templates[] = {
+ [NFT_TUNNEL_PATH] = META_TEMPLATE("path", &boolean_type,
+ BITS_PER_BYTE, BYTEORDER_HOST_ENDIAN),
+ [NFT_TUNNEL_ID] = META_TEMPLATE("id", &integer_type,
+ 4 * 8, BYTEORDER_HOST_ENDIAN),
+};
+
+static void tunnel_expr_print(const struct expr *expr, struct output_ctx *octx)
+{
+ uint32_t key = expr->tunnel.key;
+ const char *token = "unknown";
+
+ if (key < array_size(tunnel_templates))
+ token = tunnel_templates[key].token;
+
+ nft_print(octx, "tunnel %s", token);
+}
+
+static bool tunnel_expr_cmp(const struct expr *e1, const struct expr *e2)
+{
+ return e1->tunnel.key == e2->tunnel.key;
+}
+
+static void tunnel_expr_clone(struct expr *new, const struct expr *expr)
+{
+ new->tunnel.key = expr->tunnel.key;
+}
+
+const struct expr_ops tunnel_expr_ops = {
+ .type = EXPR_TUNNEL,
+ .name = "tunnel",
+ .print = tunnel_expr_print,
+ .cmp = tunnel_expr_cmp,
+ .clone = tunnel_expr_clone,
+};
+
+struct expr *tunnel_expr_alloc(const struct location *loc,
+ enum nft_tunnel_keys key)
+{
+ const struct tunnel_template *tmpl = &tunnel_templates[key];
+ struct expr *expr;
+
+ expr = expr_alloc(loc, EXPR_TUNNEL, tmpl->dtype, tmpl->byteorder,
+ tmpl->len);
+ expr->tunnel.key = key;
+
+ return expr;
+}
--
2.50.1
^ permalink raw reply related [flat|nested] 13+ messages in thread* Re: [PATCH 3/7 nft v3] src: add tunnel statement and expression support
2025-08-21 9:12 ` [PATCH 3/7 nft v3] src: add tunnel statement and expression support Fernando Fernandez Mancera
@ 2025-12-29 13:51 ` Yi Chen
2025-12-30 11:11 ` Fernando Fernandez Mancera
2026-01-07 14:31 ` Fernando Fernandez Mancera
0 siblings, 2 replies; 13+ messages in thread
From: Yi Chen @ 2025-12-29 13:51 UTC (permalink / raw)
To: Fernando Fernandez Mancera, pablo
Cc: netfilter-devel, fw, Phil Sutter, Eric Garver
[-- Attachment #1: Type: text/plain, Size: 20622 bytes --]
Hello Pablo and Fernando,
I have started working on a test script (attached) to exercise this
feature, using a geneve tunnel with an egress hook.
Please let me know if egress is the correct hook to use in this context.
However, the behavior is not what I expected: the tunnel template does
not appear to be attached, and even ARP packets are not being
encapsulated.
I would appreciate any guidance on what I might be missing, or
suggestions on how this test could be improved.
Thank you for your time and help.
On Thu, Aug 21, 2025 at 5:18 PM Fernando Fernandez Mancera
<fmancera@suse.de> wrote:
>
> From: Pablo Neira Ayuso <pablo@netfilter.org>
>
> This patch allows you to attach tunnel metadata through the tunnel
> statement.
>
> The following example shows how to redirect traffic to the erspan0
> tunnel device which will take the tunnel configuration that is
> specified by the ruleset.
>
> table netdev x {
> tunnel y {
> id 10
> ip saddr 192.168.2.10
> ip daddr 192.168.2.11
> sport 10
> dport 20
> ttl 10
> erspan {
> version 1
> index 2
> }
> }
>
> chain x {
> type filter hook ingress device veth0 priority 0;
>
> ip daddr 10.141.10.123 tunnel name y fwd to erspan0
> }
> }
>
> This patch also allows to match on tunnel metadata via tunnel expression.
>
> Joint work with Fernando.
>
> Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
> Signed-off-by: Fernando Fernandez Mancera <fmancera@suse.de>
> ---
> v3: rebased
> ---
> Makefile.am | 2 +
> include/expression.h | 6 +++
> include/tunnel.h | 33 ++++++++++++++++
> src/evaluate.c | 8 ++++
> src/expression.c | 1 +
> src/netlink_delinearize.c | 17 ++++++++
> src/netlink_linearize.c | 14 +++++++
> src/parser_bison.y | 33 +++++++++++++---
> src/scanner.l | 3 +-
> src/statement.c | 1 +
> src/tunnel.c | 81 +++++++++++++++++++++++++++++++++++++++
> 11 files changed, 193 insertions(+), 6 deletions(-)
> create mode 100644 include/tunnel.h
> create mode 100644 src/tunnel.c
>
> diff --git a/Makefile.am b/Makefile.am
> index 4909abfe..152a80d6 100644
> --- a/Makefile.am
> +++ b/Makefile.am
> @@ -100,6 +100,7 @@ noinst_HEADERS = \
> include/statement.h \
> include/tcpopt.h \
> include/trace.h \
> + include/tunnel.h \
> include/utils.h \
> include/xfrm.h \
> include/xt.h \
> @@ -243,6 +244,7 @@ src_libnftables_la_SOURCES = \
> src/socket.c \
> src/statement.c \
> src/tcpopt.c \
> + src/tunnel.c \
> src/utils.c \
> src/xfrm.c \
> $(NULL)
> diff --git a/include/expression.h b/include/expression.h
> index e483b7e7..7185ee66 100644
> --- a/include/expression.h
> +++ b/include/expression.h
> @@ -77,6 +77,7 @@ enum expr_types {
> EXPR_NUMGEN,
> EXPR_HASH,
> EXPR_RT,
> + EXPR_TUNNEL,
> EXPR_FIB,
> EXPR_XFRM,
> EXPR_SET_ELEM_CATCHALL,
> @@ -229,6 +230,7 @@ enum expr_flags {
> #include <hash.h>
> #include <ct.h>
> #include <socket.h>
> +#include <tunnel.h>
> #include <osf.h>
> #include <xfrm.h>
>
> @@ -368,6 +370,10 @@ struct expr {
> enum nft_socket_keys key;
> uint32_t level;
> } socket;
> + struct {
> + /* EXPR_TUNNEL */
> + enum nft_tunnel_keys key;
> + } tunnel;
> struct {
> /* EXPR_RT */
> enum nft_rt_keys key;
> diff --git a/include/tunnel.h b/include/tunnel.h
> new file mode 100644
> index 00000000..9e6bd97a
> --- /dev/null
> +++ b/include/tunnel.h
> @@ -0,0 +1,33 @@
> +#ifndef NFTABLES_TUNNEL_H
> +#define NFTABLES_TUNNEL_H
> +
> +/**
> + * struct tunnel_template - template for tunnel expressions
> + *
> + * @token: parser token for the expression
> + * @dtype: data type of the expression
> + * @len: length of the expression
> + * @byteorder: byteorder
> + */
> +struct tunnel_template {
> + const char *token;
> + const struct datatype *dtype;
> + enum byteorder byteorder;
> + unsigned int len;
> +};
> +
> +extern const struct tunnel_template tunnel_templates[];
> +
> +#define TUNNEL_TEMPLATE(__token, __dtype, __len, __byteorder) { \
> + .token = (__token), \
> + .dtype = (__dtype), \
> + .len = (__len), \
> + .byteorder = (__byteorder), \
> +}
> +
> +extern struct expr *tunnel_expr_alloc(const struct location *loc,
> + enum nft_tunnel_keys key);
> +
> +extern const struct expr_ops tunnel_expr_ops;
> +
> +#endif /* NFTABLES_TUNNEL_H */
> diff --git a/src/evaluate.c b/src/evaluate.c
> index da8794dd..6bf14b0c 100644
> --- a/src/evaluate.c
> +++ b/src/evaluate.c
> @@ -1737,6 +1737,7 @@ static int expr_evaluate_concat(struct eval_ctx *ctx, struct expr **expr)
> case EXPR_SOCKET:
> case EXPR_OSF:
> case EXPR_XFRM:
> + case EXPR_TUNNEL:
> break;
> case EXPR_RANGE:
> case EXPR_PREFIX:
> @@ -3053,6 +3054,11 @@ static int expr_evaluate_osf(struct eval_ctx *ctx, struct expr **expr)
> return expr_evaluate_primary(ctx, expr);
> }
>
> +static int expr_evaluate_tunnel(struct eval_ctx *ctx, struct expr **exprp)
> +{
> + return expr_evaluate_primary(ctx, exprp);
> +}
> +
> static int expr_evaluate_variable(struct eval_ctx *ctx, struct expr **exprp)
> {
> struct symbol *sym = (*exprp)->sym;
> @@ -3170,6 +3176,8 @@ static int expr_evaluate(struct eval_ctx *ctx, struct expr **expr)
> return expr_evaluate_meta(ctx, expr);
> case EXPR_SOCKET:
> return expr_evaluate_socket(ctx, expr);
> + case EXPR_TUNNEL:
> + return expr_evaluate_tunnel(ctx, expr);
> case EXPR_OSF:
> return expr_evaluate_osf(ctx, expr);
> case EXPR_FIB:
> diff --git a/src/expression.c b/src/expression.c
> index 8cb63979..e3c27a13 100644
> --- a/src/expression.c
> +++ b/src/expression.c
> @@ -1762,6 +1762,7 @@ static const struct expr_ops *__expr_ops_by_type(enum expr_types etype)
> case EXPR_NUMGEN: return &numgen_expr_ops;
> case EXPR_HASH: return &hash_expr_ops;
> case EXPR_RT: return &rt_expr_ops;
> + case EXPR_TUNNEL: return &tunnel_expr_ops;
> case EXPR_FIB: return &fib_expr_ops;
> case EXPR_XFRM: return &xfrm_expr_ops;
> case EXPR_SET_ELEM_CATCHALL: return &set_elem_catchall_expr_ops;
> diff --git a/src/netlink_delinearize.c b/src/netlink_delinearize.c
> index b97962a3..5627826d 100644
> --- a/src/netlink_delinearize.c
> +++ b/src/netlink_delinearize.c
> @@ -940,6 +940,21 @@ static void netlink_parse_osf(struct netlink_parse_ctx *ctx,
> netlink_set_register(ctx, dreg, expr);
> }
>
> +static void netlink_parse_tunnel(struct netlink_parse_ctx *ctx,
> + const struct location *loc,
> + const struct nftnl_expr *nle)
> +{
> + enum nft_registers dreg;
> + struct expr * expr;
> + uint32_t key;
> +
> + key = nftnl_expr_get_u32(nle, NFTNL_EXPR_TUNNEL_KEY);
> + expr = tunnel_expr_alloc(loc, key);
> +
> + dreg = netlink_parse_register(nle, NFTNL_EXPR_TUNNEL_DREG);
> + netlink_set_register(ctx, dreg, expr);
> +}
> +
> static void netlink_parse_meta_stmt(struct netlink_parse_ctx *ctx,
> const struct location *loc,
> const struct nftnl_expr *nle)
> @@ -1922,6 +1937,7 @@ static const struct expr_handler netlink_parsers[] = {
> { .name = "exthdr", .parse = netlink_parse_exthdr },
> { .name = "meta", .parse = netlink_parse_meta },
> { .name = "socket", .parse = netlink_parse_socket },
> + { .name = "tunnel", .parse = netlink_parse_tunnel },
> { .name = "osf", .parse = netlink_parse_osf },
> { .name = "rt", .parse = netlink_parse_rt },
> { .name = "ct", .parse = netlink_parse_ct },
> @@ -3023,6 +3039,7 @@ static void expr_postprocess(struct rule_pp_ctx *ctx, struct expr **exprp)
> case EXPR_NUMGEN:
> case EXPR_FIB:
> case EXPR_SOCKET:
> + case EXPR_TUNNEL:
> case EXPR_OSF:
> case EXPR_XFRM:
> break;
> diff --git a/src/netlink_linearize.c b/src/netlink_linearize.c
> index 8ac33d34..d01cadf8 100644
> --- a/src/netlink_linearize.c
> +++ b/src/netlink_linearize.c
> @@ -334,6 +334,18 @@ static void netlink_gen_osf(struct netlink_linearize_ctx *ctx,
> nft_rule_add_expr(ctx, nle, &expr->location);
> }
>
> +static void netlink_gen_tunnel(struct netlink_linearize_ctx *ctx,
> + const struct expr *expr,
> + enum nft_registers dreg)
> +{
> + struct nftnl_expr *nle;
> +
> + nle = alloc_nft_expr("tunnel");
> + netlink_put_register(nle, NFTNL_EXPR_TUNNEL_DREG, dreg);
> + nftnl_expr_set_u32(nle, NFTNL_EXPR_TUNNEL_KEY, expr->tunnel.key);
> + nftnl_rule_add_expr(ctx->nlr, nle);
> +}
> +
> static void netlink_gen_numgen(struct netlink_linearize_ctx *ctx,
> const struct expr *expr,
> enum nft_registers dreg)
> @@ -932,6 +944,8 @@ static void netlink_gen_expr(struct netlink_linearize_ctx *ctx,
> return netlink_gen_fib(ctx, expr, dreg);
> case EXPR_SOCKET:
> return netlink_gen_socket(ctx, expr, dreg);
> + case EXPR_TUNNEL:
> + return netlink_gen_tunnel(ctx, expr, dreg);
> case EXPR_OSF:
> return netlink_gen_osf(ctx, expr, dreg);
> case EXPR_XFRM:
> diff --git a/src/parser_bison.y b/src/parser_bison.y
> index 557977e2..08d75dbb 100644
> --- a/src/parser_bison.y
> +++ b/src/parser_bison.y
> @@ -321,6 +321,8 @@ int nft_lex(void *, void *, void *);
> %token RULESET "ruleset"
> %token TRACE "trace"
>
> +%token PATH "path"
> +
> %token INET "inet"
> %token NETDEV "netdev"
>
> @@ -779,8 +781,8 @@ int nft_lex(void *, void *, void *);
> %destructor { stmt_free($$); } counter_stmt counter_stmt_alloc stateful_stmt last_stmt
> %type <stmt> limit_stmt_alloc quota_stmt_alloc last_stmt_alloc ct_limit_stmt_alloc
> %destructor { stmt_free($$); } limit_stmt_alloc quota_stmt_alloc last_stmt_alloc ct_limit_stmt_alloc
> -%type <stmt> objref_stmt objref_stmt_counter objref_stmt_limit objref_stmt_quota objref_stmt_ct objref_stmt_synproxy
> -%destructor { stmt_free($$); } objref_stmt objref_stmt_counter objref_stmt_limit objref_stmt_quota objref_stmt_ct objref_stmt_synproxy
> +%type <stmt> objref_stmt objref_stmt_counter objref_stmt_limit objref_stmt_quota objref_stmt_ct objref_stmt_synproxy objref_stmt_tunnel
> +%destructor { stmt_free($$); } objref_stmt objref_stmt_counter objref_stmt_limit objref_stmt_quota objref_stmt_ct objref_stmt_synproxy objref_stmt_tunnel
>
> %type <stmt> payload_stmt
> %destructor { stmt_free($$); } payload_stmt
> @@ -940,9 +942,9 @@ int nft_lex(void *, void *, void *);
> %destructor { expr_free($$); } mh_hdr_expr
> %type <val> mh_hdr_field
>
> -%type <expr> meta_expr
> -%destructor { expr_free($$); } meta_expr
> -%type <val> meta_key meta_key_qualified meta_key_unqualified numgen_type
> +%type <expr> meta_expr tunnel_expr
> +%destructor { expr_free($$); } meta_expr tunnel_expr
> +%type <val> meta_key meta_key_qualified meta_key_unqualified numgen_type tunnel_key
>
> %type <expr> socket_expr
> %destructor { expr_free($$); } socket_expr
> @@ -3206,6 +3208,14 @@ objref_stmt_synproxy : SYNPROXY NAME stmt_expr close_scope_synproxy
> }
> ;
>
> +objref_stmt_tunnel : TUNNEL NAME stmt_expr close_scope_tunnel
> + {
> + $$ = objref_stmt_alloc(&@$);
> + $$->objref.type = NFT_OBJECT_TUNNEL;
> + $$->objref.expr = $3;
> + }
> + ;
> +
> objref_stmt_ct : CT TIMEOUT SET stmt_expr close_scope_ct
> {
> $$ = objref_stmt_alloc(&@$);
> @@ -3226,6 +3236,7 @@ objref_stmt : objref_stmt_counter
> | objref_stmt_quota
> | objref_stmt_synproxy
> | objref_stmt_ct
> + | objref_stmt_tunnel
> ;
>
> stateful_stmt : counter_stmt close_scope_counter
> @@ -3904,6 +3915,7 @@ primary_stmt_expr : symbol_expr { $$ = $1; }
> | boolean_expr { $$ = $1; }
> | meta_expr { $$ = $1; }
> | rt_expr { $$ = $1; }
> + | tunnel_expr { $$ = $1; }
> | ct_expr { $$ = $1; }
> | numgen_expr { $$ = $1; }
> | hash_expr { $$ = $1; }
> @@ -4381,6 +4393,7 @@ selector_expr : payload_expr { $$ = $1; }
> | exthdr_expr { $$ = $1; }
> | exthdr_exists_expr { $$ = $1; }
> | meta_expr { $$ = $1; }
> + | tunnel_expr { $$ = $1; }
> | socket_expr { $$ = $1; }
> | rt_expr { $$ = $1; }
> | ct_expr { $$ = $1; }
> @@ -5493,6 +5506,16 @@ socket_key : TRANSPARENT { $$ = NFT_SOCKET_TRANSPARENT; }
> | WILDCARD { $$ = NFT_SOCKET_WILDCARD; }
> ;
>
> +tunnel_key : PATH { $$ = NFT_TUNNEL_PATH; }
> + | ID { $$ = NFT_TUNNEL_ID; }
> + ;
> +
> +tunnel_expr : TUNNEL tunnel_key
> + {
> + $$ = tunnel_expr_alloc(&@$, $2);
> + }
> + ;
> +
> offset_opt : /* empty */ { $$ = 0; }
> | OFFSET NUM { $$ = $2; }
> ;
> diff --git a/src/scanner.l b/src/scanner.l
> index def0ac0e..9695d710 100644
> --- a/src/scanner.l
> +++ b/src/scanner.l
> @@ -410,7 +410,7 @@ addrstring ({macaddr}|{ip4addr}|{ip6addr})
> }
>
> "counter" { scanner_push_start_cond(yyscanner, SCANSTATE_COUNTER); return COUNTER; }
> -<SCANSTATE_COUNTER,SCANSTATE_LIMIT,SCANSTATE_QUOTA,SCANSTATE_STMT_SYNPROXY,SCANSTATE_EXPR_OSF>"name" { return NAME; }
> +<SCANSTATE_COUNTER,SCANSTATE_LIMIT,SCANSTATE_QUOTA,SCANSTATE_STMT_SYNPROXY,SCANSTATE_EXPR_OSF,SCANSTATE_TUNNEL>"name" { return NAME; }
> <SCANSTATE_COUNTER,SCANSTATE_CT,SCANSTATE_LIMIT>"packets" { return PACKETS; }
> <SCANSTATE_COUNTER,SCANSTATE_CT,SCANSTATE_LIMIT,SCANSTATE_QUOTA>"bytes" { return BYTES; }
>
> @@ -826,6 +826,7 @@ addrstring ({macaddr}|{ip4addr}|{ip6addr})
> "erspan" { return ERSPAN; }
> "egress" { return EGRESS; }
> "ingress" { return INGRESS; }
> + "path" { return PATH; }
> }
>
> "notrack" { return NOTRACK; }
> diff --git a/src/statement.c b/src/statement.c
> index 2bfed4ac..20241f68 100644
> --- a/src/statement.c
> +++ b/src/statement.c
> @@ -290,6 +290,7 @@ static const char *objref_type[NFT_OBJECT_MAX + 1] = {
> [NFT_OBJECT_QUOTA] = "quota",
> [NFT_OBJECT_CT_HELPER] = "ct helper",
> [NFT_OBJECT_LIMIT] = "limit",
> + [NFT_OBJECT_TUNNEL] = "tunnel",
> [NFT_OBJECT_CT_TIMEOUT] = "ct timeout",
> [NFT_OBJECT_SECMARK] = "secmark",
> [NFT_OBJECT_SYNPROXY] = "synproxy",
> diff --git a/src/tunnel.c b/src/tunnel.c
> new file mode 100644
> index 00000000..cd92d039
> --- /dev/null
> +++ b/src/tunnel.c
> @@ -0,0 +1,81 @@
> +/*
> + * Copyright (c) 2018 Pablo Neira Ayuso <pablo@netfilter.org>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +
> +#include <errno.h>
> +#include <limits.h>
> +#include <stddef.h>
> +#include <stdlib.h>
> +#include <stdio.h>
> +#include <stdbool.h>
> +#include <stdint.h>
> +#include <string.h>
> +#include <net/if.h>
> +#include <net/if_arp.h>
> +#include <pwd.h>
> +#include <grp.h>
> +#include <arpa/inet.h>
> +#include <linux/netfilter.h>
> +#include <linux/pkt_sched.h>
> +#include <linux/if_packet.h>
> +
> +#include <nftables.h>
> +#include <expression.h>
> +#include <datatype.h>
> +#include <tunnel.h>
> +#include <gmputil.h>
> +#include <utils.h>
> +#include <erec.h>
> +
> +const struct tunnel_template tunnel_templates[] = {
> + [NFT_TUNNEL_PATH] = META_TEMPLATE("path", &boolean_type,
> + BITS_PER_BYTE, BYTEORDER_HOST_ENDIAN),
> + [NFT_TUNNEL_ID] = META_TEMPLATE("id", &integer_type,
> + 4 * 8, BYTEORDER_HOST_ENDIAN),
> +};
> +
> +static void tunnel_expr_print(const struct expr *expr, struct output_ctx *octx)
> +{
> + uint32_t key = expr->tunnel.key;
> + const char *token = "unknown";
> +
> + if (key < array_size(tunnel_templates))
> + token = tunnel_templates[key].token;
> +
> + nft_print(octx, "tunnel %s", token);
> +}
> +
> +static bool tunnel_expr_cmp(const struct expr *e1, const struct expr *e2)
> +{
> + return e1->tunnel.key == e2->tunnel.key;
> +}
> +
> +static void tunnel_expr_clone(struct expr *new, const struct expr *expr)
> +{
> + new->tunnel.key = expr->tunnel.key;
> +}
> +
> +const struct expr_ops tunnel_expr_ops = {
> + .type = EXPR_TUNNEL,
> + .name = "tunnel",
> + .print = tunnel_expr_print,
> + .cmp = tunnel_expr_cmp,
> + .clone = tunnel_expr_clone,
> +};
> +
> +struct expr *tunnel_expr_alloc(const struct location *loc,
> + enum nft_tunnel_keys key)
> +{
> + const struct tunnel_template *tmpl = &tunnel_templates[key];
> + struct expr *expr;
> +
> + expr = expr_alloc(loc, EXPR_TUNNEL, tmpl->dtype, tmpl->byteorder,
> + tmpl->len);
> + expr->tunnel.key = key;
> +
> + return expr;
> +}
> --
> 2.50.1
>
>
[-- Attachment #2: tunnel_object --]
[-- Type: application/octet-stream, Size: 3628 bytes --]
#!/bin/bash
# NFT_TEST_REQUIRES(NFT_TEST_HAVE_tcpdump)
#. $NFT_TEST_LIBRARY_FILE
. ../../helpers/lib.sh
cleanup()
{
for i in $C $S $R $B;do
kill $(ip netns pid $i) 2>/dev/null
ip netns del $i
done
rm -rf $WORKDIR
sysctl -w net.netfilter.nf_log_all_netns=0
}
trap cleanup EXIT
rnd=$(mktemp -u XXXXXXXX)
C="client-$rnd"
S="server-$rnd"
R="route-$rnd"
B="bridge-$rnd"
WORKDIR="/tmp/route-type-chain-$rnd"
umask 022
mkdir -p "$WORKDIR"
assert_pass "mkdir $WORKDIR"
sysctl -w net.netfilter.nf_log_all_netns=1
# client --- bridge --- route --- bridge --- server
# 10.167.68.1/24 10.167.69.254/24 10.167.69.1/24
ip netns add $C
ip netns add $S
ip netns add $R
ip netns add $B
ip -n $S link set lo up
ip -n $C link set lo up
ip -n $R link set lo up
ip -n $B link set lo up
ip link add brc netns $B up type bridge
ip link add brs netns $B up type bridge
ip link add s_r netns $S up type veth peer name brs_s netns $B
ip link add c_r netns $C up type veth peer name brc_c netns $B
ip link add r_s netns $R up type veth peer name brs_r netns $B
ip link add r_c netns $R up type veth peer name brc_r netns $B
ip -n $B link set brs_r master brs up
ip -n $B link set brc_r master brc up
ip -n $B link set brs_s master brs up
ip -n $B link set brc_c master brc up
ip_s=10.167.69.1
ip_c=10.167.68.1
ip_rs=10.167.69.254
ip_rc=10.167.68.254
ip netns exec $S ip addr add ${ip_s}/24 dev s_r
ip netns exec $C ip -4 addr add ${ip_c}/24 dev c_r
ip netns exec $R ip -4 addr add ${ip_rs}/24 dev r_s
ip netns exec $R ip -4 addr add ${ip_rc}/24 dev r_c
ip netns exec $R sysctl -w net.ipv4.ip_forward=1
ip netns exec $C ip route add ${ip_rs%254}0/24 via ${ip_rc} dev c_r
ip netns exec $S ip route add ${ip_rc%254}0/24 via ${ip_rs} dev s_r
ip netns exec $C ping 10.167.69.1 -c1
#ip -n $S link add geneve1 type geneve id 10 remote 10.167.68.1 dstport 6081
#ip -n $C link add geneve1 type geneve id 10 remote 10.167.69.1 dstport 6081
#ip -n $S link set geneve1 up
#ip -n $S addr add 1.1.1.1/24 dev geneve1
#ip -n $C link set geneve1 up
#ip -n $C addr add 1.1.1.2/24 dev geneve1
#ip netns exec $C ping 1.1.1.1 -c1 || exit 1
ip -n $S link add geneve1 type geneve external
ip -n $C link add geneve1 type geneve external
ip -n $S link set geneve1 up
ip -n $S addr add 1.1.1.1/24 dev geneve1
ip -n $C link set geneve1 up
ip -n $C addr add 1.1.1.2/24 dev geneve1
ServerRuleset="
table netdev tunnel_test {
tunnel geneve-t1 {
id 10
ip saddr ${ip_s}
ip daddr ${ip_c}
dport 6081
ttl 88
tos 0
geneve {
class 0x1 opt-type 0x1 data \"0x12345678\"
class 0x1010 opt-type 0x2 data \"0x87654321\"
class 0x2020 opt-type 0x3 data \"0x87654321abcdeffe\"
}
}
chain egress {
type filter hook egress device s_r priority 0; policy accept;
counter tunnel name geneve-t1 fwd to geneve1
}
}
"
ClientRuleset="
table netdev tunnel_test {
tunnel geneve-t1 {
id 10
ip saddr ${ip_c}
ip daddr ${ip_s}
dport 6081
ttl 88
tos 0
geneve {
class 0x1 opt-type 0x1 data \"0x12345678\"
class 0x1010 opt-type 0x2 data \"0x87654321\"
class 0x2020 opt-type 0x3 data \"0x87654321abcdeffe\"
}
}
chain egress {
type filter hook egress device c_r priority 0; policy accept;
counter tunnel name geneve-t1 fwd to geneve1
}
}
"
ip netns exec $S nft -f - <<<"${ServerRuleset}"
ip netns exec $C nft -f - <<<"${ClientRuleset}"
ip netns exec $C tcpdump --immediate-mode -nni c_r -w geneve.pcap & pid=$!
#ip netns exec $C arping 1.1.1.1
ip netns exec $C ping 1.1.1.1
kill $pid; sleep 0.2
tcpdump -nnr geneve.pcap geneve
ip netns exec $C ip -d link show geneve1
ip netns exec $C nft list ruleset
^ permalink raw reply [flat|nested] 13+ messages in thread* Re: [PATCH 3/7 nft v3] src: add tunnel statement and expression support
2025-12-29 13:51 ` Yi Chen
@ 2025-12-30 11:11 ` Fernando Fernandez Mancera
2026-01-07 14:31 ` Fernando Fernandez Mancera
1 sibling, 0 replies; 13+ messages in thread
From: Fernando Fernandez Mancera @ 2025-12-30 11:11 UTC (permalink / raw)
To: Yi Chen, pablo; +Cc: netfilter-devel, fw, Phil Sutter, Eric Garver
On 12/29/25 2:51 PM, Yi Chen wrote:
> Hello Pablo and Fernando,
> I have started working on a test script (attached) to exercise this
> feature, using a geneve tunnel with an egress hook.
> Please let me know if egress is the correct hook to use in this context.
>
> However, the behavior is not what I expected: the tunnel template does
> not appear to be attached, and even ARP packets are not being
> encapsulated.
> I would appreciate any guidance on what I might be missing, or
> suggestions on how this test could be improved.
> Thank you for your time and help.
>
Hi Yi Chen,
I am working on a patch adding proper documentation to nft manpage that
will clarify all these questions. I plan to send it out this week. I
will CC you in the patch.
Thanks,
Fernando.
>
> On Thu, Aug 21, 2025 at 5:18 PM Fernando Fernandez Mancera
> <fmancera@suse.de> wrote:
>>
>> From: Pablo Neira Ayuso <pablo@netfilter.org>
>>
>> This patch allows you to attach tunnel metadata through the tunnel
>> statement.
>>
>> The following example shows how to redirect traffic to the erspan0
>> tunnel device which will take the tunnel configuration that is
>> specified by the ruleset.
>>
>> table netdev x {
>> tunnel y {
>> id 10
>> ip saddr 192.168.2.10
>> ip daddr 192.168.2.11
>> sport 10
>> dport 20
>> ttl 10
>> erspan {
>> version 1
>> index 2
>> }
>> }
>>
>> chain x {
>> type filter hook ingress device veth0 priority 0;
>>
>> ip daddr 10.141.10.123 tunnel name y fwd to erspan0
>> }
>> }
>>
>> This patch also allows to match on tunnel metadata via tunnel expression.
>>
>> Joint work with Fernando.
>>
>> Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
>> Signed-off-by: Fernando Fernandez Mancera <fmancera@suse.de>
>> ---
>> v3: rebased
>> ---
>> Makefile.am | 2 +
>> include/expression.h | 6 +++
>> include/tunnel.h | 33 ++++++++++++++++
>> src/evaluate.c | 8 ++++
>> src/expression.c | 1 +
>> src/netlink_delinearize.c | 17 ++++++++
>> src/netlink_linearize.c | 14 +++++++
>> src/parser_bison.y | 33 +++++++++++++---
>> src/scanner.l | 3 +-
>> src/statement.c | 1 +
>> src/tunnel.c | 81 +++++++++++++++++++++++++++++++++++++++
>> 11 files changed, 193 insertions(+), 6 deletions(-)
>> create mode 100644 include/tunnel.h
>> create mode 100644 src/tunnel.c
>>
>> diff --git a/Makefile.am b/Makefile.am
>> index 4909abfe..152a80d6 100644
>> --- a/Makefile.am
>> +++ b/Makefile.am
>> @@ -100,6 +100,7 @@ noinst_HEADERS = \
>> include/statement.h \
>> include/tcpopt.h \
>> include/trace.h \
>> + include/tunnel.h \
>> include/utils.h \
>> include/xfrm.h \
>> include/xt.h \
>> @@ -243,6 +244,7 @@ src_libnftables_la_SOURCES = \
>> src/socket.c \
>> src/statement.c \
>> src/tcpopt.c \
>> + src/tunnel.c \
>> src/utils.c \
>> src/xfrm.c \
>> $(NULL)
>> diff --git a/include/expression.h b/include/expression.h
>> index e483b7e7..7185ee66 100644
>> --- a/include/expression.h
>> +++ b/include/expression.h
>> @@ -77,6 +77,7 @@ enum expr_types {
>> EXPR_NUMGEN,
>> EXPR_HASH,
>> EXPR_RT,
>> + EXPR_TUNNEL,
>> EXPR_FIB,
>> EXPR_XFRM,
>> EXPR_SET_ELEM_CATCHALL,
>> @@ -229,6 +230,7 @@ enum expr_flags {
>> #include <hash.h>
>> #include <ct.h>
>> #include <socket.h>
>> +#include <tunnel.h>
>> #include <osf.h>
>> #include <xfrm.h>
>>
>> @@ -368,6 +370,10 @@ struct expr {
>> enum nft_socket_keys key;
>> uint32_t level;
>> } socket;
>> + struct {
>> + /* EXPR_TUNNEL */
>> + enum nft_tunnel_keys key;
>> + } tunnel;
>> struct {
>> /* EXPR_RT */
>> enum nft_rt_keys key;
>> diff --git a/include/tunnel.h b/include/tunnel.h
>> new file mode 100644
>> index 00000000..9e6bd97a
>> --- /dev/null
>> +++ b/include/tunnel.h
>> @@ -0,0 +1,33 @@
>> +#ifndef NFTABLES_TUNNEL_H
>> +#define NFTABLES_TUNNEL_H
>> +
>> +/**
>> + * struct tunnel_template - template for tunnel expressions
>> + *
>> + * @token: parser token for the expression
>> + * @dtype: data type of the expression
>> + * @len: length of the expression
>> + * @byteorder: byteorder
>> + */
>> +struct tunnel_template {
>> + const char *token;
>> + const struct datatype *dtype;
>> + enum byteorder byteorder;
>> + unsigned int len;
>> +};
>> +
>> +extern const struct tunnel_template tunnel_templates[];
>> +
>> +#define TUNNEL_TEMPLATE(__token, __dtype, __len, __byteorder) { \
>> + .token = (__token), \
>> + .dtype = (__dtype), \
>> + .len = (__len), \
>> + .byteorder = (__byteorder), \
>> +}
>> +
>> +extern struct expr *tunnel_expr_alloc(const struct location *loc,
>> + enum nft_tunnel_keys key);
>> +
>> +extern const struct expr_ops tunnel_expr_ops;
>> +
>> +#endif /* NFTABLES_TUNNEL_H */
>> diff --git a/src/evaluate.c b/src/evaluate.c
>> index da8794dd..6bf14b0c 100644
>> --- a/src/evaluate.c
>> +++ b/src/evaluate.c
>> @@ -1737,6 +1737,7 @@ static int expr_evaluate_concat(struct eval_ctx *ctx, struct expr **expr)
>> case EXPR_SOCKET:
>> case EXPR_OSF:
>> case EXPR_XFRM:
>> + case EXPR_TUNNEL:
>> break;
>> case EXPR_RANGE:
>> case EXPR_PREFIX:
>> @@ -3053,6 +3054,11 @@ static int expr_evaluate_osf(struct eval_ctx *ctx, struct expr **expr)
>> return expr_evaluate_primary(ctx, expr);
>> }
>>
>> +static int expr_evaluate_tunnel(struct eval_ctx *ctx, struct expr **exprp)
>> +{
>> + return expr_evaluate_primary(ctx, exprp);
>> +}
>> +
>> static int expr_evaluate_variable(struct eval_ctx *ctx, struct expr **exprp)
>> {
>> struct symbol *sym = (*exprp)->sym;
>> @@ -3170,6 +3176,8 @@ static int expr_evaluate(struct eval_ctx *ctx, struct expr **expr)
>> return expr_evaluate_meta(ctx, expr);
>> case EXPR_SOCKET:
>> return expr_evaluate_socket(ctx, expr);
>> + case EXPR_TUNNEL:
>> + return expr_evaluate_tunnel(ctx, expr);
>> case EXPR_OSF:
>> return expr_evaluate_osf(ctx, expr);
>> case EXPR_FIB:
>> diff --git a/src/expression.c b/src/expression.c
>> index 8cb63979..e3c27a13 100644
>> --- a/src/expression.c
>> +++ b/src/expression.c
>> @@ -1762,6 +1762,7 @@ static const struct expr_ops *__expr_ops_by_type(enum expr_types etype)
>> case EXPR_NUMGEN: return &numgen_expr_ops;
>> case EXPR_HASH: return &hash_expr_ops;
>> case EXPR_RT: return &rt_expr_ops;
>> + case EXPR_TUNNEL: return &tunnel_expr_ops;
>> case EXPR_FIB: return &fib_expr_ops;
>> case EXPR_XFRM: return &xfrm_expr_ops;
>> case EXPR_SET_ELEM_CATCHALL: return &set_elem_catchall_expr_ops;
>> diff --git a/src/netlink_delinearize.c b/src/netlink_delinearize.c
>> index b97962a3..5627826d 100644
>> --- a/src/netlink_delinearize.c
>> +++ b/src/netlink_delinearize.c
>> @@ -940,6 +940,21 @@ static void netlink_parse_osf(struct netlink_parse_ctx *ctx,
>> netlink_set_register(ctx, dreg, expr);
>> }
>>
>> +static void netlink_parse_tunnel(struct netlink_parse_ctx *ctx,
>> + const struct location *loc,
>> + const struct nftnl_expr *nle)
>> +{
>> + enum nft_registers dreg;
>> + struct expr * expr;
>> + uint32_t key;
>> +
>> + key = nftnl_expr_get_u32(nle, NFTNL_EXPR_TUNNEL_KEY);
>> + expr = tunnel_expr_alloc(loc, key);
>> +
>> + dreg = netlink_parse_register(nle, NFTNL_EXPR_TUNNEL_DREG);
>> + netlink_set_register(ctx, dreg, expr);
>> +}
>> +
>> static void netlink_parse_meta_stmt(struct netlink_parse_ctx *ctx,
>> const struct location *loc,
>> const struct nftnl_expr *nle)
>> @@ -1922,6 +1937,7 @@ static const struct expr_handler netlink_parsers[] = {
>> { .name = "exthdr", .parse = netlink_parse_exthdr },
>> { .name = "meta", .parse = netlink_parse_meta },
>> { .name = "socket", .parse = netlink_parse_socket },
>> + { .name = "tunnel", .parse = netlink_parse_tunnel },
>> { .name = "osf", .parse = netlink_parse_osf },
>> { .name = "rt", .parse = netlink_parse_rt },
>> { .name = "ct", .parse = netlink_parse_ct },
>> @@ -3023,6 +3039,7 @@ static void expr_postprocess(struct rule_pp_ctx *ctx, struct expr **exprp)
>> case EXPR_NUMGEN:
>> case EXPR_FIB:
>> case EXPR_SOCKET:
>> + case EXPR_TUNNEL:
>> case EXPR_OSF:
>> case EXPR_XFRM:
>> break;
>> diff --git a/src/netlink_linearize.c b/src/netlink_linearize.c
>> index 8ac33d34..d01cadf8 100644
>> --- a/src/netlink_linearize.c
>> +++ b/src/netlink_linearize.c
>> @@ -334,6 +334,18 @@ static void netlink_gen_osf(struct netlink_linearize_ctx *ctx,
>> nft_rule_add_expr(ctx, nle, &expr->location);
>> }
>>
>> +static void netlink_gen_tunnel(struct netlink_linearize_ctx *ctx,
>> + const struct expr *expr,
>> + enum nft_registers dreg)
>> +{
>> + struct nftnl_expr *nle;
>> +
>> + nle = alloc_nft_expr("tunnel");
>> + netlink_put_register(nle, NFTNL_EXPR_TUNNEL_DREG, dreg);
>> + nftnl_expr_set_u32(nle, NFTNL_EXPR_TUNNEL_KEY, expr->tunnel.key);
>> + nftnl_rule_add_expr(ctx->nlr, nle);
>> +}
>> +
>> static void netlink_gen_numgen(struct netlink_linearize_ctx *ctx,
>> const struct expr *expr,
>> enum nft_registers dreg)
>> @@ -932,6 +944,8 @@ static void netlink_gen_expr(struct netlink_linearize_ctx *ctx,
>> return netlink_gen_fib(ctx, expr, dreg);
>> case EXPR_SOCKET:
>> return netlink_gen_socket(ctx, expr, dreg);
>> + case EXPR_TUNNEL:
>> + return netlink_gen_tunnel(ctx, expr, dreg);
>> case EXPR_OSF:
>> return netlink_gen_osf(ctx, expr, dreg);
>> case EXPR_XFRM:
>> diff --git a/src/parser_bison.y b/src/parser_bison.y
>> index 557977e2..08d75dbb 100644
>> --- a/src/parser_bison.y
>> +++ b/src/parser_bison.y
>> @@ -321,6 +321,8 @@ int nft_lex(void *, void *, void *);
>> %token RULESET "ruleset"
>> %token TRACE "trace"
>>
>> +%token PATH "path"
>> +
>> %token INET "inet"
>> %token NETDEV "netdev"
>>
>> @@ -779,8 +781,8 @@ int nft_lex(void *, void *, void *);
>> %destructor { stmt_free($$); } counter_stmt counter_stmt_alloc stateful_stmt last_stmt
>> %type <stmt> limit_stmt_alloc quota_stmt_alloc last_stmt_alloc ct_limit_stmt_alloc
>> %destructor { stmt_free($$); } limit_stmt_alloc quota_stmt_alloc last_stmt_alloc ct_limit_stmt_alloc
>> -%type <stmt> objref_stmt objref_stmt_counter objref_stmt_limit objref_stmt_quota objref_stmt_ct objref_stmt_synproxy
>> -%destructor { stmt_free($$); } objref_stmt objref_stmt_counter objref_stmt_limit objref_stmt_quota objref_stmt_ct objref_stmt_synproxy
>> +%type <stmt> objref_stmt objref_stmt_counter objref_stmt_limit objref_stmt_quota objref_stmt_ct objref_stmt_synproxy objref_stmt_tunnel
>> +%destructor { stmt_free($$); } objref_stmt objref_stmt_counter objref_stmt_limit objref_stmt_quota objref_stmt_ct objref_stmt_synproxy objref_stmt_tunnel
>>
>> %type <stmt> payload_stmt
>> %destructor { stmt_free($$); } payload_stmt
>> @@ -940,9 +942,9 @@ int nft_lex(void *, void *, void *);
>> %destructor { expr_free($$); } mh_hdr_expr
>> %type <val> mh_hdr_field
>>
>> -%type <expr> meta_expr
>> -%destructor { expr_free($$); } meta_expr
>> -%type <val> meta_key meta_key_qualified meta_key_unqualified numgen_type
>> +%type <expr> meta_expr tunnel_expr
>> +%destructor { expr_free($$); } meta_expr tunnel_expr
>> +%type <val> meta_key meta_key_qualified meta_key_unqualified numgen_type tunnel_key
>>
>> %type <expr> socket_expr
>> %destructor { expr_free($$); } socket_expr
>> @@ -3206,6 +3208,14 @@ objref_stmt_synproxy : SYNPROXY NAME stmt_expr close_scope_synproxy
>> }
>> ;
>>
>> +objref_stmt_tunnel : TUNNEL NAME stmt_expr close_scope_tunnel
>> + {
>> + $$ = objref_stmt_alloc(&@$);
>> + $$->objref.type = NFT_OBJECT_TUNNEL;
>> + $$->objref.expr = $3;
>> + }
>> + ;
>> +
>> objref_stmt_ct : CT TIMEOUT SET stmt_expr close_scope_ct
>> {
>> $$ = objref_stmt_alloc(&@$);
>> @@ -3226,6 +3236,7 @@ objref_stmt : objref_stmt_counter
>> | objref_stmt_quota
>> | objref_stmt_synproxy
>> | objref_stmt_ct
>> + | objref_stmt_tunnel
>> ;
>>
>> stateful_stmt : counter_stmt close_scope_counter
>> @@ -3904,6 +3915,7 @@ primary_stmt_expr : symbol_expr { $$ = $1; }
>> | boolean_expr { $$ = $1; }
>> | meta_expr { $$ = $1; }
>> | rt_expr { $$ = $1; }
>> + | tunnel_expr { $$ = $1; }
>> | ct_expr { $$ = $1; }
>> | numgen_expr { $$ = $1; }
>> | hash_expr { $$ = $1; }
>> @@ -4381,6 +4393,7 @@ selector_expr : payload_expr { $$ = $1; }
>> | exthdr_expr { $$ = $1; }
>> | exthdr_exists_expr { $$ = $1; }
>> | meta_expr { $$ = $1; }
>> + | tunnel_expr { $$ = $1; }
>> | socket_expr { $$ = $1; }
>> | rt_expr { $$ = $1; }
>> | ct_expr { $$ = $1; }
>> @@ -5493,6 +5506,16 @@ socket_key : TRANSPARENT { $$ = NFT_SOCKET_TRANSPARENT; }
>> | WILDCARD { $$ = NFT_SOCKET_WILDCARD; }
>> ;
>>
>> +tunnel_key : PATH { $$ = NFT_TUNNEL_PATH; }
>> + | ID { $$ = NFT_TUNNEL_ID; }
>> + ;
>> +
>> +tunnel_expr : TUNNEL tunnel_key
>> + {
>> + $$ = tunnel_expr_alloc(&@$, $2);
>> + }
>> + ;
>> +
>> offset_opt : /* empty */ { $$ = 0; }
>> | OFFSET NUM { $$ = $2; }
>> ;
>> diff --git a/src/scanner.l b/src/scanner.l
>> index def0ac0e..9695d710 100644
>> --- a/src/scanner.l
>> +++ b/src/scanner.l
>> @@ -410,7 +410,7 @@ addrstring ({macaddr}|{ip4addr}|{ip6addr})
>> }
>>
>> "counter" { scanner_push_start_cond(yyscanner, SCANSTATE_COUNTER); return COUNTER; }
>> -<SCANSTATE_COUNTER,SCANSTATE_LIMIT,SCANSTATE_QUOTA,SCANSTATE_STMT_SYNPROXY,SCANSTATE_EXPR_OSF>"name" { return NAME; }
>> +<SCANSTATE_COUNTER,SCANSTATE_LIMIT,SCANSTATE_QUOTA,SCANSTATE_STMT_SYNPROXY,SCANSTATE_EXPR_OSF,SCANSTATE_TUNNEL>"name" { return NAME; }
>> <SCANSTATE_COUNTER,SCANSTATE_CT,SCANSTATE_LIMIT>"packets" { return PACKETS; }
>> <SCANSTATE_COUNTER,SCANSTATE_CT,SCANSTATE_LIMIT,SCANSTATE_QUOTA>"bytes" { return BYTES; }
>>
>> @@ -826,6 +826,7 @@ addrstring ({macaddr}|{ip4addr}|{ip6addr})
>> "erspan" { return ERSPAN; }
>> "egress" { return EGRESS; }
>> "ingress" { return INGRESS; }
>> + "path" { return PATH; }
>> }
>>
>> "notrack" { return NOTRACK; }
>> diff --git a/src/statement.c b/src/statement.c
>> index 2bfed4ac..20241f68 100644
>> --- a/src/statement.c
>> +++ b/src/statement.c
>> @@ -290,6 +290,7 @@ static const char *objref_type[NFT_OBJECT_MAX + 1] = {
>> [NFT_OBJECT_QUOTA] = "quota",
>> [NFT_OBJECT_CT_HELPER] = "ct helper",
>> [NFT_OBJECT_LIMIT] = "limit",
>> + [NFT_OBJECT_TUNNEL] = "tunnel",
>> [NFT_OBJECT_CT_TIMEOUT] = "ct timeout",
>> [NFT_OBJECT_SECMARK] = "secmark",
>> [NFT_OBJECT_SYNPROXY] = "synproxy",
>> diff --git a/src/tunnel.c b/src/tunnel.c
>> new file mode 100644
>> index 00000000..cd92d039
>> --- /dev/null
>> +++ b/src/tunnel.c
>> @@ -0,0 +1,81 @@
>> +/*
>> + * Copyright (c) 2018 Pablo Neira Ayuso <pablo@netfilter.org>
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License version 2 as
>> + * published by the Free Software Foundation.
>> + */
>> +
>> +#include <errno.h>
>> +#include <limits.h>
>> +#include <stddef.h>
>> +#include <stdlib.h>
>> +#include <stdio.h>
>> +#include <stdbool.h>
>> +#include <stdint.h>
>> +#include <string.h>
>> +#include <net/if.h>
>> +#include <net/if_arp.h>
>> +#include <pwd.h>
>> +#include <grp.h>
>> +#include <arpa/inet.h>
>> +#include <linux/netfilter.h>
>> +#include <linux/pkt_sched.h>
>> +#include <linux/if_packet.h>
>> +
>> +#include <nftables.h>
>> +#include <expression.h>
>> +#include <datatype.h>
>> +#include <tunnel.h>
>> +#include <gmputil.h>
>> +#include <utils.h>
>> +#include <erec.h>
>> +
>> +const struct tunnel_template tunnel_templates[] = {
>> + [NFT_TUNNEL_PATH] = META_TEMPLATE("path", &boolean_type,
>> + BITS_PER_BYTE, BYTEORDER_HOST_ENDIAN),
>> + [NFT_TUNNEL_ID] = META_TEMPLATE("id", &integer_type,
>> + 4 * 8, BYTEORDER_HOST_ENDIAN),
>> +};
>> +
>> +static void tunnel_expr_print(const struct expr *expr, struct output_ctx *octx)
>> +{
>> + uint32_t key = expr->tunnel.key;
>> + const char *token = "unknown";
>> +
>> + if (key < array_size(tunnel_templates))
>> + token = tunnel_templates[key].token;
>> +
>> + nft_print(octx, "tunnel %s", token);
>> +}
>> +
>> +static bool tunnel_expr_cmp(const struct expr *e1, const struct expr *e2)
>> +{
>> + return e1->tunnel.key == e2->tunnel.key;
>> +}
>> +
>> +static void tunnel_expr_clone(struct expr *new, const struct expr *expr)
>> +{
>> + new->tunnel.key = expr->tunnel.key;
>> +}
>> +
>> +const struct expr_ops tunnel_expr_ops = {
>> + .type = EXPR_TUNNEL,
>> + .name = "tunnel",
>> + .print = tunnel_expr_print,
>> + .cmp = tunnel_expr_cmp,
>> + .clone = tunnel_expr_clone,
>> +};
>> +
>> +struct expr *tunnel_expr_alloc(const struct location *loc,
>> + enum nft_tunnel_keys key)
>> +{
>> + const struct tunnel_template *tmpl = &tunnel_templates[key];
>> + struct expr *expr;
>> +
>> + expr = expr_alloc(loc, EXPR_TUNNEL, tmpl->dtype, tmpl->byteorder,
>> + tmpl->len);
>> + expr->tunnel.key = key;
>> +
>> + return expr;
>> +}
>> --
>> 2.50.1
>>
>>
^ permalink raw reply [flat|nested] 13+ messages in thread* Re: [PATCH 3/7 nft v3] src: add tunnel statement and expression support
2025-12-29 13:51 ` Yi Chen
2025-12-30 11:11 ` Fernando Fernandez Mancera
@ 2026-01-07 14:31 ` Fernando Fernandez Mancera
[not found] ` <CAJsUoE24NEe65atDs58dgwgxir8vLtEbrRkKp0nXpUVHFD6E_g@mail.gmail.com>
2026-06-23 22:37 ` Florian Westphal
1 sibling, 2 replies; 13+ messages in thread
From: Fernando Fernandez Mancera @ 2026-01-07 14:31 UTC (permalink / raw)
To: Yi Chen, pablo; +Cc: netfilter-devel, fw, Phil Sutter, Eric Garver
On 12/29/25 2:51 PM, Yi Chen wrote:
> Hello Pablo and Fernando,
> I have started working on a test script (attached) to exercise this
> feature, using a geneve tunnel with an egress hook.
> Please let me know if egress is the correct hook to use in this context.
>
> However, the behavior is not what I expected: the tunnel template does
> not appear to be attached, and even ARP packets are not being
> encapsulated.
> I would appreciate any guidance on what I might be missing, or
> suggestions on how this test could be improved.
> Thank you for your time and help.
>
Hi Yi Chen,
As my patch is taking longer than expected because I am polishing all
the details related to the tunnel object let me explain it here briefly
to unblock you.
The tunnel expression/object is used to attach tunnel metadata into a
packet so in essence support Lightweight Tunneling (LWT) using Nftables.
The LWT support is useful on virtualization environments where the users
need to created a lot of tunnels to interconnect containers that are
inside different VMs. Instead of creating one interface per container,
the idea is that the user can create a single one and then attach the
metadata as needed. Imagine the topology described below.
+------------------------+ +------------------------+
|--------+ VM A | | VM B +--------|
|Box | +------+ +---+|(192.168.124.49) +----+ +------+ |Box |
|10.0.0.1|-|vxlan0|-|eth0|-------------------|eth0|-|vxlan0|-|10.0.0.2|
|--------+ +------+ +---+| (192.168.124.134)+----+ +------+ +--------|
| | | |
| | | |
+------------------------+ +------------------------+
We want to reach 10.0.0.2 from 10.0.0.1, the nftables ruleset on VM A
will look like this:
```
table netdev filter_tunnel {
tunnel vxlan_tmpl {
id 100
ip saddr 192.168.124.49
ip daddr 192.168.124.134
dport 8472
ttl 255
vxlan {
gbp 100
}
}
chain redirect_to_tunnel {
type filter hook ingress device "veth_host" priority filter; policy
accept;
ip daddr 10.0.0.2 tunnel name "vxlan_tmpl" fwd to "vxlan0"
}
chain redirect_from_tunnel {
type filter hook ingress device "vxlan0" priority filter; policy accept;
ip daddr 10.0.0.1 fwd to "veth_host"
}
}
```
On VM B the ruleset will be exactly the same but swapping saddr/daddr
everywhere, both the external and internal one.
The idea behind this feature is to scale up the number of
containers/namespaces without creating more VXLAN interfaces. Also, keep
on mind that you need to mark the VXLAN as external when creating it, i.e
ip link add dev vxlan0 type vxlan external
After that, you should be able to ping between them:
PING 10.0.0.2 (10.0.0.2) 56(84) bytes of data.
64 bytes from 10.0.0.2: icmp_seq=1 ttl=64 time=0.198 ms
64 bytes from 10.0.0.2: icmp_seq=2 ttl=64 time=0.364 ms
64 bytes from 10.0.0.2: icmp_seq=3 ttl=64 time=0.369 ms
Please, let me know if something is missing.
Thanks,
Fernando.
>
> On Thu, Aug 21, 2025 at 5:18 PM Fernando Fernandez Mancera
> <fmancera@suse.de> wrote:
>>
>> From: Pablo Neira Ayuso <pablo@netfilter.org>
>>
>> This patch allows you to attach tunnel metadata through the tunnel
>> statement.
>>
>> The following example shows how to redirect traffic to the erspan0
>> tunnel device which will take the tunnel configuration that is
>> specified by the ruleset.
>>
>> table netdev x {
>> tunnel y {
>> id 10
>> ip saddr 192.168.2.10
>> ip daddr 192.168.2.11
>> sport 10
>> dport 20
>> ttl 10
>> erspan {
>> version 1
>> index 2
>> }
>> }
>>
>> chain x {
>> type filter hook ingress device veth0 priority 0;
>>
>> ip daddr 10.141.10.123 tunnel name y fwd to erspan0
>> }
>> }
>>
>> This patch also allows to match on tunnel metadata via tunnel expression.
>>
>> Joint work with Fernando.
>>
>> Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
>> Signed-off-by: Fernando Fernandez Mancera <fmancera@suse.de>
>> ---
>> v3: rebased
>> ---
>> Makefile.am | 2 +
>> include/expression.h | 6 +++
>> include/tunnel.h | 33 ++++++++++++++++
>> src/evaluate.c | 8 ++++
>> src/expression.c | 1 +
>> src/netlink_delinearize.c | 17 ++++++++
>> src/netlink_linearize.c | 14 +++++++
>> src/parser_bison.y | 33 +++++++++++++---
>> src/scanner.l | 3 +-
>> src/statement.c | 1 +
>> src/tunnel.c | 81 +++++++++++++++++++++++++++++++++++++++
>> 11 files changed, 193 insertions(+), 6 deletions(-)
>> create mode 100644 include/tunnel.h
>> create mode 100644 src/tunnel.c
>>
>> diff --git a/Makefile.am b/Makefile.am
>> index 4909abfe..152a80d6 100644
>> --- a/Makefile.am
>> +++ b/Makefile.am
>> @@ -100,6 +100,7 @@ noinst_HEADERS = \
>> include/statement.h \
>> include/tcpopt.h \
>> include/trace.h \
>> + include/tunnel.h \
>> include/utils.h \
>> include/xfrm.h \
>> include/xt.h \
>> @@ -243,6 +244,7 @@ src_libnftables_la_SOURCES = \
>> src/socket.c \
>> src/statement.c \
>> src/tcpopt.c \
>> + src/tunnel.c \
>> src/utils.c \
>> src/xfrm.c \
>> $(NULL)
>> diff --git a/include/expression.h b/include/expression.h
>> index e483b7e7..7185ee66 100644
>> --- a/include/expression.h
>> +++ b/include/expression.h
>> @@ -77,6 +77,7 @@ enum expr_types {
>> EXPR_NUMGEN,
>> EXPR_HASH,
>> EXPR_RT,
>> + EXPR_TUNNEL,
>> EXPR_FIB,
>> EXPR_XFRM,
>> EXPR_SET_ELEM_CATCHALL,
>> @@ -229,6 +230,7 @@ enum expr_flags {
>> #include <hash.h>
>> #include <ct.h>
>> #include <socket.h>
>> +#include <tunnel.h>
>> #include <osf.h>
>> #include <xfrm.h>
>>
>> @@ -368,6 +370,10 @@ struct expr {
>> enum nft_socket_keys key;
>> uint32_t level;
>> } socket;
>> + struct {
>> + /* EXPR_TUNNEL */
>> + enum nft_tunnel_keys key;
>> + } tunnel;
>> struct {
>> /* EXPR_RT */
>> enum nft_rt_keys key;
>> diff --git a/include/tunnel.h b/include/tunnel.h
>> new file mode 100644
>> index 00000000..9e6bd97a
>> --- /dev/null
>> +++ b/include/tunnel.h
>> @@ -0,0 +1,33 @@
>> +#ifndef NFTABLES_TUNNEL_H
>> +#define NFTABLES_TUNNEL_H
>> +
>> +/**
>> + * struct tunnel_template - template for tunnel expressions
>> + *
>> + * @token: parser token for the expression
>> + * @dtype: data type of the expression
>> + * @len: length of the expression
>> + * @byteorder: byteorder
>> + */
>> +struct tunnel_template {
>> + const char *token;
>> + const struct datatype *dtype;
>> + enum byteorder byteorder;
>> + unsigned int len;
>> +};
>> +
>> +extern const struct tunnel_template tunnel_templates[];
>> +
>> +#define TUNNEL_TEMPLATE(__token, __dtype, __len, __byteorder) { \
>> + .token = (__token), \
>> + .dtype = (__dtype), \
>> + .len = (__len), \
>> + .byteorder = (__byteorder), \
>> +}
>> +
>> +extern struct expr *tunnel_expr_alloc(const struct location *loc,
>> + enum nft_tunnel_keys key);
>> +
>> +extern const struct expr_ops tunnel_expr_ops;
>> +
>> +#endif /* NFTABLES_TUNNEL_H */
>> diff --git a/src/evaluate.c b/src/evaluate.c
>> index da8794dd..6bf14b0c 100644
>> --- a/src/evaluate.c
>> +++ b/src/evaluate.c
>> @@ -1737,6 +1737,7 @@ static int expr_evaluate_concat(struct eval_ctx *ctx, struct expr **expr)
>> case EXPR_SOCKET:
>> case EXPR_OSF:
>> case EXPR_XFRM:
>> + case EXPR_TUNNEL:
>> break;
>> case EXPR_RANGE:
>> case EXPR_PREFIX:
>> @@ -3053,6 +3054,11 @@ static int expr_evaluate_osf(struct eval_ctx *ctx, struct expr **expr)
>> return expr_evaluate_primary(ctx, expr);
>> }
>>
>> +static int expr_evaluate_tunnel(struct eval_ctx *ctx, struct expr **exprp)
>> +{
>> + return expr_evaluate_primary(ctx, exprp);
>> +}
>> +
>> static int expr_evaluate_variable(struct eval_ctx *ctx, struct expr **exprp)
>> {
>> struct symbol *sym = (*exprp)->sym;
>> @@ -3170,6 +3176,8 @@ static int expr_evaluate(struct eval_ctx *ctx, struct expr **expr)
>> return expr_evaluate_meta(ctx, expr);
>> case EXPR_SOCKET:
>> return expr_evaluate_socket(ctx, expr);
>> + case EXPR_TUNNEL:
>> + return expr_evaluate_tunnel(ctx, expr);
>> case EXPR_OSF:
>> return expr_evaluate_osf(ctx, expr);
>> case EXPR_FIB:
>> diff --git a/src/expression.c b/src/expression.c
>> index 8cb63979..e3c27a13 100644
>> --- a/src/expression.c
>> +++ b/src/expression.c
>> @@ -1762,6 +1762,7 @@ static const struct expr_ops *__expr_ops_by_type(enum expr_types etype)
>> case EXPR_NUMGEN: return &numgen_expr_ops;
>> case EXPR_HASH: return &hash_expr_ops;
>> case EXPR_RT: return &rt_expr_ops;
>> + case EXPR_TUNNEL: return &tunnel_expr_ops;
>> case EXPR_FIB: return &fib_expr_ops;
>> case EXPR_XFRM: return &xfrm_expr_ops;
>> case EXPR_SET_ELEM_CATCHALL: return &set_elem_catchall_expr_ops;
>> diff --git a/src/netlink_delinearize.c b/src/netlink_delinearize.c
>> index b97962a3..5627826d 100644
>> --- a/src/netlink_delinearize.c
>> +++ b/src/netlink_delinearize.c
>> @@ -940,6 +940,21 @@ static void netlink_parse_osf(struct netlink_parse_ctx *ctx,
>> netlink_set_register(ctx, dreg, expr);
>> }
>>
>> +static void netlink_parse_tunnel(struct netlink_parse_ctx *ctx,
>> + const struct location *loc,
>> + const struct nftnl_expr *nle)
>> +{
>> + enum nft_registers dreg;
>> + struct expr * expr;
>> + uint32_t key;
>> +
>> + key = nftnl_expr_get_u32(nle, NFTNL_EXPR_TUNNEL_KEY);
>> + expr = tunnel_expr_alloc(loc, key);
>> +
>> + dreg = netlink_parse_register(nle, NFTNL_EXPR_TUNNEL_DREG);
>> + netlink_set_register(ctx, dreg, expr);
>> +}
>> +
>> static void netlink_parse_meta_stmt(struct netlink_parse_ctx *ctx,
>> const struct location *loc,
>> const struct nftnl_expr *nle)
>> @@ -1922,6 +1937,7 @@ static const struct expr_handler netlink_parsers[] = {
>> { .name = "exthdr", .parse = netlink_parse_exthdr },
>> { .name = "meta", .parse = netlink_parse_meta },
>> { .name = "socket", .parse = netlink_parse_socket },
>> + { .name = "tunnel", .parse = netlink_parse_tunnel },
>> { .name = "osf", .parse = netlink_parse_osf },
>> { .name = "rt", .parse = netlink_parse_rt },
>> { .name = "ct", .parse = netlink_parse_ct },
>> @@ -3023,6 +3039,7 @@ static void expr_postprocess(struct rule_pp_ctx *ctx, struct expr **exprp)
>> case EXPR_NUMGEN:
>> case EXPR_FIB:
>> case EXPR_SOCKET:
>> + case EXPR_TUNNEL:
>> case EXPR_OSF:
>> case EXPR_XFRM:
>> break;
>> diff --git a/src/netlink_linearize.c b/src/netlink_linearize.c
>> index 8ac33d34..d01cadf8 100644
>> --- a/src/netlink_linearize.c
>> +++ b/src/netlink_linearize.c
>> @@ -334,6 +334,18 @@ static void netlink_gen_osf(struct netlink_linearize_ctx *ctx,
>> nft_rule_add_expr(ctx, nle, &expr->location);
>> }
>>
>> +static void netlink_gen_tunnel(struct netlink_linearize_ctx *ctx,
>> + const struct expr *expr,
>> + enum nft_registers dreg)
>> +{
>> + struct nftnl_expr *nle;
>> +
>> + nle = alloc_nft_expr("tunnel");
>> + netlink_put_register(nle, NFTNL_EXPR_TUNNEL_DREG, dreg);
>> + nftnl_expr_set_u32(nle, NFTNL_EXPR_TUNNEL_KEY, expr->tunnel.key);
>> + nftnl_rule_add_expr(ctx->nlr, nle);
>> +}
>> +
>> static void netlink_gen_numgen(struct netlink_linearize_ctx *ctx,
>> const struct expr *expr,
>> enum nft_registers dreg)
>> @@ -932,6 +944,8 @@ static void netlink_gen_expr(struct netlink_linearize_ctx *ctx,
>> return netlink_gen_fib(ctx, expr, dreg);
>> case EXPR_SOCKET:
>> return netlink_gen_socket(ctx, expr, dreg);
>> + case EXPR_TUNNEL:
>> + return netlink_gen_tunnel(ctx, expr, dreg);
>> case EXPR_OSF:
>> return netlink_gen_osf(ctx, expr, dreg);
>> case EXPR_XFRM:
>> diff --git a/src/parser_bison.y b/src/parser_bison.y
>> index 557977e2..08d75dbb 100644
>> --- a/src/parser_bison.y
>> +++ b/src/parser_bison.y
>> @@ -321,6 +321,8 @@ int nft_lex(void *, void *, void *);
>> %token RULESET "ruleset"
>> %token TRACE "trace"
>>
>> +%token PATH "path"
>> +
>> %token INET "inet"
>> %token NETDEV "netdev"
>>
>> @@ -779,8 +781,8 @@ int nft_lex(void *, void *, void *);
>> %destructor { stmt_free($$); } counter_stmt counter_stmt_alloc stateful_stmt last_stmt
>> %type <stmt> limit_stmt_alloc quota_stmt_alloc last_stmt_alloc ct_limit_stmt_alloc
>> %destructor { stmt_free($$); } limit_stmt_alloc quota_stmt_alloc last_stmt_alloc ct_limit_stmt_alloc
>> -%type <stmt> objref_stmt objref_stmt_counter objref_stmt_limit objref_stmt_quota objref_stmt_ct objref_stmt_synproxy
>> -%destructor { stmt_free($$); } objref_stmt objref_stmt_counter objref_stmt_limit objref_stmt_quota objref_stmt_ct objref_stmt_synproxy
>> +%type <stmt> objref_stmt objref_stmt_counter objref_stmt_limit objref_stmt_quota objref_stmt_ct objref_stmt_synproxy objref_stmt_tunnel
>> +%destructor { stmt_free($$); } objref_stmt objref_stmt_counter objref_stmt_limit objref_stmt_quota objref_stmt_ct objref_stmt_synproxy objref_stmt_tunnel
>>
>> %type <stmt> payload_stmt
>> %destructor { stmt_free($$); } payload_stmt
>> @@ -940,9 +942,9 @@ int nft_lex(void *, void *, void *);
>> %destructor { expr_free($$); } mh_hdr_expr
>> %type <val> mh_hdr_field
>>
>> -%type <expr> meta_expr
>> -%destructor { expr_free($$); } meta_expr
>> -%type <val> meta_key meta_key_qualified meta_key_unqualified numgen_type
>> +%type <expr> meta_expr tunnel_expr
>> +%destructor { expr_free($$); } meta_expr tunnel_expr
>> +%type <val> meta_key meta_key_qualified meta_key_unqualified numgen_type tunnel_key
>>
>> %type <expr> socket_expr
>> %destructor { expr_free($$); } socket_expr
>> @@ -3206,6 +3208,14 @@ objref_stmt_synproxy : SYNPROXY NAME stmt_expr close_scope_synproxy
>> }
>> ;
>>
>> +objref_stmt_tunnel : TUNNEL NAME stmt_expr close_scope_tunnel
>> + {
>> + $$ = objref_stmt_alloc(&@$);
>> + $$->objref.type = NFT_OBJECT_TUNNEL;
>> + $$->objref.expr = $3;
>> + }
>> + ;
>> +
>> objref_stmt_ct : CT TIMEOUT SET stmt_expr close_scope_ct
>> {
>> $$ = objref_stmt_alloc(&@$);
>> @@ -3226,6 +3236,7 @@ objref_stmt : objref_stmt_counter
>> | objref_stmt_quota
>> | objref_stmt_synproxy
>> | objref_stmt_ct
>> + | objref_stmt_tunnel
>> ;
>>
>> stateful_stmt : counter_stmt close_scope_counter
>> @@ -3904,6 +3915,7 @@ primary_stmt_expr : symbol_expr { $$ = $1; }
>> | boolean_expr { $$ = $1; }
>> | meta_expr { $$ = $1; }
>> | rt_expr { $$ = $1; }
>> + | tunnel_expr { $$ = $1; }
>> | ct_expr { $$ = $1; }
>> | numgen_expr { $$ = $1; }
>> | hash_expr { $$ = $1; }
>> @@ -4381,6 +4393,7 @@ selector_expr : payload_expr { $$ = $1; }
>> | exthdr_expr { $$ = $1; }
>> | exthdr_exists_expr { $$ = $1; }
>> | meta_expr { $$ = $1; }
>> + | tunnel_expr { $$ = $1; }
>> | socket_expr { $$ = $1; }
>> | rt_expr { $$ = $1; }
>> | ct_expr { $$ = $1; }
>> @@ -5493,6 +5506,16 @@ socket_key : TRANSPARENT { $$ = NFT_SOCKET_TRANSPARENT; }
>> | WILDCARD { $$ = NFT_SOCKET_WILDCARD; }
>> ;
>>
>> +tunnel_key : PATH { $$ = NFT_TUNNEL_PATH; }
>> + | ID { $$ = NFT_TUNNEL_ID; }
>> + ;
>> +
>> +tunnel_expr : TUNNEL tunnel_key
>> + {
>> + $$ = tunnel_expr_alloc(&@$, $2);
>> + }
>> + ;
>> +
>> offset_opt : /* empty */ { $$ = 0; }
>> | OFFSET NUM { $$ = $2; }
>> ;
>> diff --git a/src/scanner.l b/src/scanner.l
>> index def0ac0e..9695d710 100644
>> --- a/src/scanner.l
>> +++ b/src/scanner.l
>> @@ -410,7 +410,7 @@ addrstring ({macaddr}|{ip4addr}|{ip6addr})
>> }
>>
>> "counter" { scanner_push_start_cond(yyscanner, SCANSTATE_COUNTER); return COUNTER; }
>> -<SCANSTATE_COUNTER,SCANSTATE_LIMIT,SCANSTATE_QUOTA,SCANSTATE_STMT_SYNPROXY,SCANSTATE_EXPR_OSF>"name" { return NAME; }
>> +<SCANSTATE_COUNTER,SCANSTATE_LIMIT,SCANSTATE_QUOTA,SCANSTATE_STMT_SYNPROXY,SCANSTATE_EXPR_OSF,SCANSTATE_TUNNEL>"name" { return NAME; }
>> <SCANSTATE_COUNTER,SCANSTATE_CT,SCANSTATE_LIMIT>"packets" { return PACKETS; }
>> <SCANSTATE_COUNTER,SCANSTATE_CT,SCANSTATE_LIMIT,SCANSTATE_QUOTA>"bytes" { return BYTES; }
>>
>> @@ -826,6 +826,7 @@ addrstring ({macaddr}|{ip4addr}|{ip6addr})
>> "erspan" { return ERSPAN; }
>> "egress" { return EGRESS; }
>> "ingress" { return INGRESS; }
>> + "path" { return PATH; }
>> }
>>
>> "notrack" { return NOTRACK; }
>> diff --git a/src/statement.c b/src/statement.c
>> index 2bfed4ac..20241f68 100644
>> --- a/src/statement.c
>> +++ b/src/statement.c
>> @@ -290,6 +290,7 @@ static const char *objref_type[NFT_OBJECT_MAX + 1] = {
>> [NFT_OBJECT_QUOTA] = "quota",
>> [NFT_OBJECT_CT_HELPER] = "ct helper",
>> [NFT_OBJECT_LIMIT] = "limit",
>> + [NFT_OBJECT_TUNNEL] = "tunnel",
>> [NFT_OBJECT_CT_TIMEOUT] = "ct timeout",
>> [NFT_OBJECT_SECMARK] = "secmark",
>> [NFT_OBJECT_SYNPROXY] = "synproxy",
>> diff --git a/src/tunnel.c b/src/tunnel.c
>> new file mode 100644
>> index 00000000..cd92d039
>> --- /dev/null
>> +++ b/src/tunnel.c
>> @@ -0,0 +1,81 @@
>> +/*
>> + * Copyright (c) 2018 Pablo Neira Ayuso <pablo@netfilter.org>
>> + *
>> + * This program is free software; you can redistribute it and/or modify
>> + * it under the terms of the GNU General Public License version 2 as
>> + * published by the Free Software Foundation.
>> + */
>> +
>> +#include <errno.h>
>> +#include <limits.h>
>> +#include <stddef.h>
>> +#include <stdlib.h>
>> +#include <stdio.h>
>> +#include <stdbool.h>
>> +#include <stdint.h>
>> +#include <string.h>
>> +#include <net/if.h>
>> +#include <net/if_arp.h>
>> +#include <pwd.h>
>> +#include <grp.h>
>> +#include <arpa/inet.h>
>> +#include <linux/netfilter.h>
>> +#include <linux/pkt_sched.h>
>> +#include <linux/if_packet.h>
>> +
>> +#include <nftables.h>
>> +#include <expression.h>
>> +#include <datatype.h>
>> +#include <tunnel.h>
>> +#include <gmputil.h>
>> +#include <utils.h>
>> +#include <erec.h>
>> +
>> +const struct tunnel_template tunnel_templates[] = {
>> + [NFT_TUNNEL_PATH] = META_TEMPLATE("path", &boolean_type,
>> + BITS_PER_BYTE, BYTEORDER_HOST_ENDIAN),
>> + [NFT_TUNNEL_ID] = META_TEMPLATE("id", &integer_type,
>> + 4 * 8, BYTEORDER_HOST_ENDIAN),
>> +};
>> +
>> +static void tunnel_expr_print(const struct expr *expr, struct output_ctx *octx)
>> +{
>> + uint32_t key = expr->tunnel.key;
>> + const char *token = "unknown";
>> +
>> + if (key < array_size(tunnel_templates))
>> + token = tunnel_templates[key].token;
>> +
>> + nft_print(octx, "tunnel %s", token);
>> +}
>> +
>> +static bool tunnel_expr_cmp(const struct expr *e1, const struct expr *e2)
>> +{
>> + return e1->tunnel.key == e2->tunnel.key;
>> +}
>> +
>> +static void tunnel_expr_clone(struct expr *new, const struct expr *expr)
>> +{
>> + new->tunnel.key = expr->tunnel.key;
>> +}
>> +
>> +const struct expr_ops tunnel_expr_ops = {
>> + .type = EXPR_TUNNEL,
>> + .name = "tunnel",
>> + .print = tunnel_expr_print,
>> + .cmp = tunnel_expr_cmp,
>> + .clone = tunnel_expr_clone,
>> +};
>> +
>> +struct expr *tunnel_expr_alloc(const struct location *loc,
>> + enum nft_tunnel_keys key)
>> +{
>> + const struct tunnel_template *tmpl = &tunnel_templates[key];
>> + struct expr *expr;
>> +
>> + expr = expr_alloc(loc, EXPR_TUNNEL, tmpl->dtype, tmpl->byteorder,
>> + tmpl->len);
>> + expr->tunnel.key = key;
>> +
>> + return expr;
>> +}
>> --
>> 2.50.1
>>
>>
^ permalink raw reply [flat|nested] 13+ messages in thread[parent not found: <CAJsUoE24NEe65atDs58dgwgxir8vLtEbrRkKp0nXpUVHFD6E_g@mail.gmail.com>]
* Re: [PATCH 3/7 nft v3] src: add tunnel statement and expression support
[not found] ` <CAJsUoE24NEe65atDs58dgwgxir8vLtEbrRkKp0nXpUVHFD6E_g@mail.gmail.com>
@ 2026-01-26 1:02 ` Yi Chen
0 siblings, 0 replies; 13+ messages in thread
From: Yi Chen @ 2026-01-26 1:02 UTC (permalink / raw)
To: Fernando Fernandez Mancera
Cc: pablo, netfilter-devel, fw, Phil Sutter, Eric Garver
Hi Fernando,
Thanks for the detailed explanation, that helps a lot to understand
the intended design and the use case.
While going through the example ruleset, I didn't see how the initial
traffic initiation from VM A is supposed to work in practice,
and I would like to check whether I am missing something.
As far as I can see, all the rules are added to netdev ingress hooks
(both on veth_host and vxlan0),
but it is unclear to me how locally generated traffic is handled.
For example, when VM A actively sends traffic (e.g. ping 10.0.0.2),
the packets are outgoing and won't match in an ingress hook chain.
so they would not match the redirect_to_tunnel chain.
Related to this, I am also unsure how ARP is expected to work. Before
any ICMP packet can be sent, ARP resolution for 10.0.0.2 must happen,
but ARP packets would not match the ip daddr 10.0.0.2 rule either. and
won't be encapsulated.
Could you clarify how egress traffic and ARP/neighbor resolution are
expected to be handled in this way?
Thanks again, and sorry if I am missing something obvious.
Best regards,
Yi
On Fri, Jan 23, 2026 at 11:27 PM Yi Chen <yiche@redhat.com> wrote:
>
> Hi Fernando,
>
> Thanks for the detailed explanation, that helps a lot to understand the intended design and the use case.
> While going through the example ruleset, I am still struggling to see how the initial traffic initiation from VM A is supposed to work in practice,
> and I would like to check whether I am missing something.
>
> As far as I can see, all the rules are added to netdev ingress hooks (both on veth_host and vxlan0),
> but it is unclear to me how locally generated traffic is handled.
> For example, when VM A actively sends traffic (e.g. ping 10.0.0.2), the packets are outgoing and won't match in a ingress hook chain.
> so they would not match the redirect_to_tunnel chain.
>
> Related to this, I am also unsure how ARP is expected to work. Before any ICMP packet can be sent, ARP resolution for 10.0.0.2 must happen,
> but ARP packets would not match the ip daddr 10.0.0.2 rule either. and won't be encapsulated.
>
> Could you clarify how egress traffic and ARP/neighbor resolution are expected to be handled in this way?
> Thanks again, and sorry if I am missing something obvious.
>
> Best regards,
> Yi
>
> On Wed, Jan 7, 2026 at 10:31 PM Fernando Fernandez Mancera <fmancera@suse.de> wrote:
>>
>> On 12/29/25 2:51 PM, Yi Chen wrote:
>> > Hello Pablo and Fernando,
>> > I have started working on a test script (attached) to exercise this
>> > feature, using a geneve tunnel with an egress hook.
>> > Please let me know if egress is the correct hook to use in this context.
>> >
>> > However, the behavior is not what I expected: the tunnel template does
>> > not appear to be attached, and even ARP packets are not being
>> > encapsulated.
>> > I would appreciate any guidance on what I might be missing, or
>> > suggestions on how this test could be improved.
>> > Thank you for your time and help.
>> >
>>
>> Hi Yi Chen,
>>
>> As my patch is taking longer than expected because I am polishing all
>> the details related to the tunnel object let me explain it here briefly
>> to unblock you.
>>
>> The tunnel expression/object is used to attach tunnel metadata into a
>> packet so in essence support Lightweight Tunneling (LWT) using Nftables.
>> The LWT support is useful on virtualization environments where the users
>> need to created a lot of tunnels to interconnect containers that are
>> inside different VMs. Instead of creating one interface per container,
>> the idea is that the user can create a single one and then attach the
>> metadata as needed. Imagine the topology described below.
>>
>> +------------------------+ +------------------------+
>> |--------+ VM A | | VM B +--------|
>> |Box | +------+ +---+|(192.168.124.49) +----+ +------+ |Box |
>> |10.0.0.1|-|vxlan0|-|eth0|-------------------|eth0|-|vxlan0|-|10.0.0.2|
>> |--------+ +------+ +---+| (192.168.124.134)+----+ +------+ +--------|
>> | | | |
>> | | | |
>> +------------------------+ +------------------------+
>>
>> We want to reach 10.0.0.2 from 10.0.0.1, the nftables ruleset on VM A
>> will look like this:
>>
>> ```
>> table netdev filter_tunnel {
>> tunnel vxlan_tmpl {
>> id 100
>> ip saddr 192.168.124.49
>> ip daddr 192.168.124.134
>> dport 8472
>> ttl 255
>> vxlan {
>> gbp 100
>> }
>> }
>>
>> chain redirect_to_tunnel {
>> type filter hook ingress device "veth_host" priority filter; policy
>> accept;
>> ip daddr 10.0.0.2 tunnel name "vxlan_tmpl" fwd to "vxlan0"
>> }
>>
>> chain redirect_from_tunnel {
>> type filter hook ingress device "vxlan0" priority filter; policy accept;
>> ip daddr 10.0.0.1 fwd to "veth_host"
>> }
>> }
>> ```
>>
>> On VM B the ruleset will be exactly the same but swapping saddr/daddr
>> everywhere, both the external and internal one.
>>
>> The idea behind this feature is to scale up the number of
>> containers/namespaces without creating more VXLAN interfaces. Also, keep
>> on mind that you need to mark the VXLAN as external when creating it, i.e
>>
>> ip link add dev vxlan0 type vxlan external
>>
>> After that, you should be able to ping between them:
>>
>> PING 10.0.0.2 (10.0.0.2) 56(84) bytes of data.
>> 64 bytes from 10.0.0.2: icmp_seq=1 ttl=64 time=0.198 ms
>> 64 bytes from 10.0.0.2: icmp_seq=2 ttl=64 time=0.364 ms
>> 64 bytes from 10.0.0.2: icmp_seq=3 ttl=64 time=0.369 ms
>>
>> Please, let me know if something is missing.
>>
>> Thanks,
>> Fernando.
>>
>> >
>> > On Thu, Aug 21, 2025 at 5:18 PM Fernando Fernandez Mancera
>> > <fmancera@suse.de> wrote:
>> >>
>> >> From: Pablo Neira Ayuso <pablo@netfilter.org>
>> >>
>> >> This patch allows you to attach tunnel metadata through the tunnel
>> >> statement.
>> >>
>> >> The following example shows how to redirect traffic to the erspan0
>> >> tunnel device which will take the tunnel configuration that is
>> >> specified by the ruleset.
>> >>
>> >> table netdev x {
>> >> tunnel y {
>> >> id 10
>> >> ip saddr 192.168.2.10
>> >> ip daddr 192.168.2.11
>> >> sport 10
>> >> dport 20
>> >> ttl 10
>> >> erspan {
>> >> version 1
>> >> index 2
>> >> }
>> >> }
>> >>
>> >> chain x {
>> >> type filter hook ingress device veth0 priority 0;
>> >>
>> >> ip daddr 10.141.10.123 tunnel name y fwd to erspan0
>> >> }
>> >> }
>> >>
>> >> This patch also allows to match on tunnel metadata via tunnel expression.
>> >>
>> >> Joint work with Fernando.
>> >>
>> >> Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
>> >> Signed-off-by: Fernando Fernandez Mancera <fmancera@suse.de>
>> >> ---
>> >> v3: rebased
>> >> ---
>> >> Makefile.am | 2 +
>> >> include/expression.h | 6 +++
>> >> include/tunnel.h | 33 ++++++++++++++++
>> >> src/evaluate.c | 8 ++++
>> >> src/expression.c | 1 +
>> >> src/netlink_delinearize.c | 17 ++++++++
>> >> src/netlink_linearize.c | 14 +++++++
>> >> src/parser_bison.y | 33 +++++++++++++---
>> >> src/scanner.l | 3 +-
>> >> src/statement.c | 1 +
>> >> src/tunnel.c | 81 +++++++++++++++++++++++++++++++++++++++
>> >> 11 files changed, 193 insertions(+), 6 deletions(-)
>> >> create mode 100644 include/tunnel.h
>> >> create mode 100644 src/tunnel.c
>> >>
>> >> diff --git a/Makefile.am b/Makefile.am
>> >> index 4909abfe..152a80d6 100644
>> >> --- a/Makefile.am
>> >> +++ b/Makefile.am
>> >> @@ -100,6 +100,7 @@ noinst_HEADERS = \
>> >> include/statement.h \
>> >> include/tcpopt.h \
>> >> include/trace.h \
>> >> + include/tunnel.h \
>> >> include/utils.h \
>> >> include/xfrm.h \
>> >> include/xt.h \
>> >> @@ -243,6 +244,7 @@ src_libnftables_la_SOURCES = \
>> >> src/socket.c \
>> >> src/statement.c \
>> >> src/tcpopt.c \
>> >> + src/tunnel.c \
>> >> src/utils.c \
>> >> src/xfrm.c \
>> >> $(NULL)
>> >> diff --git a/include/expression.h b/include/expression.h
>> >> index e483b7e7..7185ee66 100644
>> >> --- a/include/expression.h
>> >> +++ b/include/expression.h
>> >> @@ -77,6 +77,7 @@ enum expr_types {
>> >> EXPR_NUMGEN,
>> >> EXPR_HASH,
>> >> EXPR_RT,
>> >> + EXPR_TUNNEL,
>> >> EXPR_FIB,
>> >> EXPR_XFRM,
>> >> EXPR_SET_ELEM_CATCHALL,
>> >> @@ -229,6 +230,7 @@ enum expr_flags {
>> >> #include <hash.h>
>> >> #include <ct.h>
>> >> #include <socket.h>
>> >> +#include <tunnel.h>
>> >> #include <osf.h>
>> >> #include <xfrm.h>
>> >>
>> >> @@ -368,6 +370,10 @@ struct expr {
>> >> enum nft_socket_keys key;
>> >> uint32_t level;
>> >> } socket;
>> >> + struct {
>> >> + /* EXPR_TUNNEL */
>> >> + enum nft_tunnel_keys key;
>> >> + } tunnel;
>> >> struct {
>> >> /* EXPR_RT */
>> >> enum nft_rt_keys key;
>> >> diff --git a/include/tunnel.h b/include/tunnel.h
>> >> new file mode 100644
>> >> index 00000000..9e6bd97a
>> >> --- /dev/null
>> >> +++ b/include/tunnel.h
>> >> @@ -0,0 +1,33 @@
>> >> +#ifndef NFTABLES_TUNNEL_H
>> >> +#define NFTABLES_TUNNEL_H
>> >> +
>> >> +/**
>> >> + * struct tunnel_template - template for tunnel expressions
>> >> + *
>> >> + * @token: parser token for the expression
>> >> + * @dtype: data type of the expression
>> >> + * @len: length of the expression
>> >> + * @byteorder: byteorder
>> >> + */
>> >> +struct tunnel_template {
>> >> + const char *token;
>> >> + const struct datatype *dtype;
>> >> + enum byteorder byteorder;
>> >> + unsigned int len;
>> >> +};
>> >> +
>> >> +extern const struct tunnel_template tunnel_templates[];
>> >> +
>> >> +#define TUNNEL_TEMPLATE(__token, __dtype, __len, __byteorder) { \
>> >> + .token = (__token), \
>> >> + .dtype = (__dtype), \
>> >> + .len = (__len), \
>> >> + .byteorder = (__byteorder), \
>> >> +}
>> >> +
>> >> +extern struct expr *tunnel_expr_alloc(const struct location *loc,
>> >> + enum nft_tunnel_keys key);
>> >> +
>> >> +extern const struct expr_ops tunnel_expr_ops;
>> >> +
>> >> +#endif /* NFTABLES_TUNNEL_H */
>> >> diff --git a/src/evaluate.c b/src/evaluate.c
>> >> index da8794dd..6bf14b0c 100644
>> >> --- a/src/evaluate.c
>> >> +++ b/src/evaluate.c
>> >> @@ -1737,6 +1737,7 @@ static int expr_evaluate_concat(struct eval_ctx *ctx, struct expr **expr)
>> >> case EXPR_SOCKET:
>> >> case EXPR_OSF:
>> >> case EXPR_XFRM:
>> >> + case EXPR_TUNNEL:
>> >> break;
>> >> case EXPR_RANGE:
>> >> case EXPR_PREFIX:
>> >> @@ -3053,6 +3054,11 @@ static int expr_evaluate_osf(struct eval_ctx *ctx, struct expr **expr)
>> >> return expr_evaluate_primary(ctx, expr);
>> >> }
>> >>
>> >> +static int expr_evaluate_tunnel(struct eval_ctx *ctx, struct expr **exprp)
>> >> +{
>> >> + return expr_evaluate_primary(ctx, exprp);
>> >> +}
>> >> +
>> >> static int expr_evaluate_variable(struct eval_ctx *ctx, struct expr **exprp)
>> >> {
>> >> struct symbol *sym = (*exprp)->sym;
>> >> @@ -3170,6 +3176,8 @@ static int expr_evaluate(struct eval_ctx *ctx, struct expr **expr)
>> >> return expr_evaluate_meta(ctx, expr);
>> >> case EXPR_SOCKET:
>> >> return expr_evaluate_socket(ctx, expr);
>> >> + case EXPR_TUNNEL:
>> >> + return expr_evaluate_tunnel(ctx, expr);
>> >> case EXPR_OSF:
>> >> return expr_evaluate_osf(ctx, expr);
>> >> case EXPR_FIB:
>> >> diff --git a/src/expression.c b/src/expression.c
>> >> index 8cb63979..e3c27a13 100644
>> >> --- a/src/expression.c
>> >> +++ b/src/expression.c
>> >> @@ -1762,6 +1762,7 @@ static const struct expr_ops *__expr_ops_by_type(enum expr_types etype)
>> >> case EXPR_NUMGEN: return &numgen_expr_ops;
>> >> case EXPR_HASH: return &hash_expr_ops;
>> >> case EXPR_RT: return &rt_expr_ops;
>> >> + case EXPR_TUNNEL: return &tunnel_expr_ops;
>> >> case EXPR_FIB: return &fib_expr_ops;
>> >> case EXPR_XFRM: return &xfrm_expr_ops;
>> >> case EXPR_SET_ELEM_CATCHALL: return &set_elem_catchall_expr_ops;
>> >> diff --git a/src/netlink_delinearize.c b/src/netlink_delinearize.c
>> >> index b97962a3..5627826d 100644
>> >> --- a/src/netlink_delinearize.c
>> >> +++ b/src/netlink_delinearize.c
>> >> @@ -940,6 +940,21 @@ static void netlink_parse_osf(struct netlink_parse_ctx *ctx,
>> >> netlink_set_register(ctx, dreg, expr);
>> >> }
>> >>
>> >> +static void netlink_parse_tunnel(struct netlink_parse_ctx *ctx,
>> >> + const struct location *loc,
>> >> + const struct nftnl_expr *nle)
>> >> +{
>> >> + enum nft_registers dreg;
>> >> + struct expr * expr;
>> >> + uint32_t key;
>> >> +
>> >> + key = nftnl_expr_get_u32(nle, NFTNL_EXPR_TUNNEL_KEY);
>> >> + expr = tunnel_expr_alloc(loc, key);
>> >> +
>> >> + dreg = netlink_parse_register(nle, NFTNL_EXPR_TUNNEL_DREG);
>> >> + netlink_set_register(ctx, dreg, expr);
>> >> +}
>> >> +
>> >> static void netlink_parse_meta_stmt(struct netlink_parse_ctx *ctx,
>> >> const struct location *loc,
>> >> const struct nftnl_expr *nle)
>> >> @@ -1922,6 +1937,7 @@ static const struct expr_handler netlink_parsers[] = {
>> >> { .name = "exthdr", .parse = netlink_parse_exthdr },
>> >> { .name = "meta", .parse = netlink_parse_meta },
>> >> { .name = "socket", .parse = netlink_parse_socket },
>> >> + { .name = "tunnel", .parse = netlink_parse_tunnel },
>> >> { .name = "osf", .parse = netlink_parse_osf },
>> >> { .name = "rt", .parse = netlink_parse_rt },
>> >> { .name = "ct", .parse = netlink_parse_ct },
>> >> @@ -3023,6 +3039,7 @@ static void expr_postprocess(struct rule_pp_ctx *ctx, struct expr **exprp)
>> >> case EXPR_NUMGEN:
>> >> case EXPR_FIB:
>> >> case EXPR_SOCKET:
>> >> + case EXPR_TUNNEL:
>> >> case EXPR_OSF:
>> >> case EXPR_XFRM:
>> >> break;
>> >> diff --git a/src/netlink_linearize.c b/src/netlink_linearize.c
>> >> index 8ac33d34..d01cadf8 100644
>> >> --- a/src/netlink_linearize.c
>> >> +++ b/src/netlink_linearize.c
>> >> @@ -334,6 +334,18 @@ static void netlink_gen_osf(struct netlink_linearize_ctx *ctx,
>> >> nft_rule_add_expr(ctx, nle, &expr->location);
>> >> }
>> >>
>> >> +static void netlink_gen_tunnel(struct netlink_linearize_ctx *ctx,
>> >> + const struct expr *expr,
>> >> + enum nft_registers dreg)
>> >> +{
>> >> + struct nftnl_expr *nle;
>> >> +
>> >> + nle = alloc_nft_expr("tunnel");
>> >> + netlink_put_register(nle, NFTNL_EXPR_TUNNEL_DREG, dreg);
>> >> + nftnl_expr_set_u32(nle, NFTNL_EXPR_TUNNEL_KEY, expr->tunnel.key);
>> >> + nftnl_rule_add_expr(ctx->nlr, nle);
>> >> +}
>> >> +
>> >> static void netlink_gen_numgen(struct netlink_linearize_ctx *ctx,
>> >> const struct expr *expr,
>> >> enum nft_registers dreg)
>> >> @@ -932,6 +944,8 @@ static void netlink_gen_expr(struct netlink_linearize_ctx *ctx,
>> >> return netlink_gen_fib(ctx, expr, dreg);
>> >> case EXPR_SOCKET:
>> >> return netlink_gen_socket(ctx, expr, dreg);
>> >> + case EXPR_TUNNEL:
>> >> + return netlink_gen_tunnel(ctx, expr, dreg);
>> >> case EXPR_OSF:
>> >> return netlink_gen_osf(ctx, expr, dreg);
>> >> case EXPR_XFRM:
>> >> diff --git a/src/parser_bison.y b/src/parser_bison.y
>> >> index 557977e2..08d75dbb 100644
>> >> --- a/src/parser_bison.y
>> >> +++ b/src/parser_bison.y
>> >> @@ -321,6 +321,8 @@ int nft_lex(void *, void *, void *);
>> >> %token RULESET "ruleset"
>> >> %token TRACE "trace"
>> >>
>> >> +%token PATH "path"
>> >> +
>> >> %token INET "inet"
>> >> %token NETDEV "netdev"
>> >>
>> >> @@ -779,8 +781,8 @@ int nft_lex(void *, void *, void *);
>> >> %destructor { stmt_free($$); } counter_stmt counter_stmt_alloc stateful_stmt last_stmt
>> >> %type <stmt> limit_stmt_alloc quota_stmt_alloc last_stmt_alloc ct_limit_stmt_alloc
>> >> %destructor { stmt_free($$); } limit_stmt_alloc quota_stmt_alloc last_stmt_alloc ct_limit_stmt_alloc
>> >> -%type <stmt> objref_stmt objref_stmt_counter objref_stmt_limit objref_stmt_quota objref_stmt_ct objref_stmt_synproxy
>> >> -%destructor { stmt_free($$); } objref_stmt objref_stmt_counter objref_stmt_limit objref_stmt_quota objref_stmt_ct objref_stmt_synproxy
>> >> +%type <stmt> objref_stmt objref_stmt_counter objref_stmt_limit objref_stmt_quota objref_stmt_ct objref_stmt_synproxy objref_stmt_tunnel
>> >> +%destructor { stmt_free($$); } objref_stmt objref_stmt_counter objref_stmt_limit objref_stmt_quota objref_stmt_ct objref_stmt_synproxy objref_stmt_tunnel
>> >>
>> >> %type <stmt> payload_stmt
>> >> %destructor { stmt_free($$); } payload_stmt
>> >> @@ -940,9 +942,9 @@ int nft_lex(void *, void *, void *);
>> >> %destructor { expr_free($$); } mh_hdr_expr
>> >> %type <val> mh_hdr_field
>> >>
>> >> -%type <expr> meta_expr
>> >> -%destructor { expr_free($$); } meta_expr
>> >> -%type <val> meta_key meta_key_qualified meta_key_unqualified numgen_type
>> >> +%type <expr> meta_expr tunnel_expr
>> >> +%destructor { expr_free($$); } meta_expr tunnel_expr
>> >> +%type <val> meta_key meta_key_qualified meta_key_unqualified numgen_type tunnel_key
>> >>
>> >> %type <expr> socket_expr
>> >> %destructor { expr_free($$); } socket_expr
>> >> @@ -3206,6 +3208,14 @@ objref_stmt_synproxy : SYNPROXY NAME stmt_expr close_scope_synproxy
>> >> }
>> >> ;
>> >>
>> >> +objref_stmt_tunnel : TUNNEL NAME stmt_expr close_scope_tunnel
>> >> + {
>> >> + $$ = objref_stmt_alloc(&@$);
>> >> + $$->objref.type = NFT_OBJECT_TUNNEL;
>> >> + $$->objref.expr = $3;
>> >> + }
>> >> + ;
>> >> +
>> >> objref_stmt_ct : CT TIMEOUT SET stmt_expr close_scope_ct
>> >> {
>> >> $$ = objref_stmt_alloc(&@$);
>> >> @@ -3226,6 +3236,7 @@ objref_stmt : objref_stmt_counter
>> >> | objref_stmt_quota
>> >> | objref_stmt_synproxy
>> >> | objref_stmt_ct
>> >> + | objref_stmt_tunnel
>> >> ;
>> >>
>> >> stateful_stmt : counter_stmt close_scope_counter
>> >> @@ -3904,6 +3915,7 @@ primary_stmt_expr : symbol_expr { $$ = $1; }
>> >> | boolean_expr { $$ = $1; }
>> >> | meta_expr { $$ = $1; }
>> >> | rt_expr { $$ = $1; }
>> >> + | tunnel_expr { $$ = $1; }
>> >> | ct_expr { $$ = $1; }
>> >> | numgen_expr { $$ = $1; }
>> >> | hash_expr { $$ = $1; }
>> >> @@ -4381,6 +4393,7 @@ selector_expr : payload_expr { $$ = $1; }
>> >> | exthdr_expr { $$ = $1; }
>> >> | exthdr_exists_expr { $$ = $1; }
>> >> | meta_expr { $$ = $1; }
>> >> + | tunnel_expr { $$ = $1; }
>> >> | socket_expr { $$ = $1; }
>> >> | rt_expr { $$ = $1; }
>> >> | ct_expr { $$ = $1; }
>> >> @@ -5493,6 +5506,16 @@ socket_key : TRANSPARENT { $$ = NFT_SOCKET_TRANSPARENT; }
>> >> | WILDCARD { $$ = NFT_SOCKET_WILDCARD; }
>> >> ;
>> >>
>> >> +tunnel_key : PATH { $$ = NFT_TUNNEL_PATH; }
>> >> + | ID { $$ = NFT_TUNNEL_ID; }
>> >> + ;
>> >> +
>> >> +tunnel_expr : TUNNEL tunnel_key
>> >> + {
>> >> + $$ = tunnel_expr_alloc(&@$, $2);
>> >> + }
>> >> + ;
>> >> +
>> >> offset_opt : /* empty */ { $$ = 0; }
>> >> | OFFSET NUM { $$ = $2; }
>> >> ;
>> >> diff --git a/src/scanner.l b/src/scanner.l
>> >> index def0ac0e..9695d710 100644
>> >> --- a/src/scanner.l
>> >> +++ b/src/scanner.l
>> >> @@ -410,7 +410,7 @@ addrstring ({macaddr}|{ip4addr}|{ip6addr})
>> >> }
>> >>
>> >> "counter" { scanner_push_start_cond(yyscanner, SCANSTATE_COUNTER); return COUNTER; }
>> >> -<SCANSTATE_COUNTER,SCANSTATE_LIMIT,SCANSTATE_QUOTA,SCANSTATE_STMT_SYNPROXY,SCANSTATE_EXPR_OSF>"name" { return NAME; }
>> >> +<SCANSTATE_COUNTER,SCANSTATE_LIMIT,SCANSTATE_QUOTA,SCANSTATE_STMT_SYNPROXY,SCANSTATE_EXPR_OSF,SCANSTATE_TUNNEL>"name" { return NAME; }
>> >> <SCANSTATE_COUNTER,SCANSTATE_CT,SCANSTATE_LIMIT>"packets" { return PACKETS; }
>> >> <SCANSTATE_COUNTER,SCANSTATE_CT,SCANSTATE_LIMIT,SCANSTATE_QUOTA>"bytes" { return BYTES; }
>> >>
>> >> @@ -826,6 +826,7 @@ addrstring ({macaddr}|{ip4addr}|{ip6addr})
>> >> "erspan" { return ERSPAN; }
>> >> "egress" { return EGRESS; }
>> >> "ingress" { return INGRESS; }
>> >> + "path" { return PATH; }
>> >> }
>> >>
>> >> "notrack" { return NOTRACK; }
>> >> diff --git a/src/statement.c b/src/statement.c
>> >> index 2bfed4ac..20241f68 100644
>> >> --- a/src/statement.c
>> >> +++ b/src/statement.c
>> >> @@ -290,6 +290,7 @@ static const char *objref_type[NFT_OBJECT_MAX + 1] = {
>> >> [NFT_OBJECT_QUOTA] = "quota",
>> >> [NFT_OBJECT_CT_HELPER] = "ct helper",
>> >> [NFT_OBJECT_LIMIT] = "limit",
>> >> + [NFT_OBJECT_TUNNEL] = "tunnel",
>> >> [NFT_OBJECT_CT_TIMEOUT] = "ct timeout",
>> >> [NFT_OBJECT_SECMARK] = "secmark",
>> >> [NFT_OBJECT_SYNPROXY] = "synproxy",
>> >> diff --git a/src/tunnel.c b/src/tunnel.c
>> >> new file mode 100644
>> >> index 00000000..cd92d039
>> >> --- /dev/null
>> >> +++ b/src/tunnel.c
>> >> @@ -0,0 +1,81 @@
>> >> +/*
>> >> + * Copyright (c) 2018 Pablo Neira Ayuso <pablo@netfilter.org>
>> >> + *
>> >> + * This program is free software; you can redistribute it and/or modify
>> >> + * it under the terms of the GNU General Public License version 2 as
>> >> + * published by the Free Software Foundation.
>> >> + */
>> >> +
>> >> +#include <errno.h>
>> >> +#include <limits.h>
>> >> +#include <stddef.h>
>> >> +#include <stdlib.h>
>> >> +#include <stdio.h>
>> >> +#include <stdbool.h>
>> >> +#include <stdint.h>
>> >> +#include <string.h>
>> >> +#include <net/if.h>
>> >> +#include <net/if_arp.h>
>> >> +#include <pwd.h>
>> >> +#include <grp.h>
>> >> +#include <arpa/inet.h>
>> >> +#include <linux/netfilter.h>
>> >> +#include <linux/pkt_sched.h>
>> >> +#include <linux/if_packet.h>
>> >> +
>> >> +#include <nftables.h>
>> >> +#include <expression.h>
>> >> +#include <datatype.h>
>> >> +#include <tunnel.h>
>> >> +#include <gmputil.h>
>> >> +#include <utils.h>
>> >> +#include <erec.h>
>> >> +
>> >> +const struct tunnel_template tunnel_templates[] = {
>> >> + [NFT_TUNNEL_PATH] = META_TEMPLATE("path", &boolean_type,
>> >> + BITS_PER_BYTE, BYTEORDER_HOST_ENDIAN),
>> >> + [NFT_TUNNEL_ID] = META_TEMPLATE("id", &integer_type,
>> >> + 4 * 8, BYTEORDER_HOST_ENDIAN),
>> >> +};
>> >> +
>> >> +static void tunnel_expr_print(const struct expr *expr, struct output_ctx *octx)
>> >> +{
>> >> + uint32_t key = expr->tunnel.key;
>> >> + const char *token = "unknown";
>> >> +
>> >> + if (key < array_size(tunnel_templates))
>> >> + token = tunnel_templates[key].token;
>> >> +
>> >> + nft_print(octx, "tunnel %s", token);
>> >> +}
>> >> +
>> >> +static bool tunnel_expr_cmp(const struct expr *e1, const struct expr *e2)
>> >> +{
>> >> + return e1->tunnel.key == e2->tunnel.key;
>> >> +}
>> >> +
>> >> +static void tunnel_expr_clone(struct expr *new, const struct expr *expr)
>> >> +{
>> >> + new->tunnel.key = expr->tunnel.key;
>> >> +}
>> >> +
>> >> +const struct expr_ops tunnel_expr_ops = {
>> >> + .type = EXPR_TUNNEL,
>> >> + .name = "tunnel",
>> >> + .print = tunnel_expr_print,
>> >> + .cmp = tunnel_expr_cmp,
>> >> + .clone = tunnel_expr_clone,
>> >> +};
>> >> +
>> >> +struct expr *tunnel_expr_alloc(const struct location *loc,
>> >> + enum nft_tunnel_keys key)
>> >> +{
>> >> + const struct tunnel_template *tmpl = &tunnel_templates[key];
>> >> + struct expr *expr;
>> >> +
>> >> + expr = expr_alloc(loc, EXPR_TUNNEL, tmpl->dtype, tmpl->byteorder,
>> >> + tmpl->len);
>> >> + expr->tunnel.key = key;
>> >> +
>> >> + return expr;
>> >> +}
>> >> --
>> >> 2.50.1
>> >>
>> >>
>>
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH 3/7 nft v3] src: add tunnel statement and expression support
2026-01-07 14:31 ` Fernando Fernandez Mancera
[not found] ` <CAJsUoE24NEe65atDs58dgwgxir8vLtEbrRkKp0nXpUVHFD6E_g@mail.gmail.com>
@ 2026-06-23 22:37 ` Florian Westphal
1 sibling, 0 replies; 13+ messages in thread
From: Florian Westphal @ 2026-06-23 22:37 UTC (permalink / raw)
To: Fernando Fernandez Mancera
Cc: pablo, netfilter-devel, Phil Sutter, Eric Garver
Hi Fernando
Fernando Fernandez Mancera <fmancera@suse.de> wrote:
> On 12/29/25 2:51 PM, Yi Chen wrote:
> > Hello Pablo and Fernando,
> > I have started working on a test script (attached) to exercise this
> > feature, using a geneve tunnel with an egress hook.
> > Please let me know if egress is the correct hook to use in this context.
> >
> > However, the behavior is not what I expected: the tunnel template does
> > not appear to be attached, and even ARP packets are not being
> > encapsulated.
> > I would appreciate any guidance on what I might be missing, or
> > suggestions on how this test could be improved.
> > Thank you for your time and help.
> >
>
> As my patch is taking longer than expected because I am polishing all the
> details related to the tunnel object let me explain it here briefly to
> unblock you.
>
> The tunnel expression/object is used to attach tunnel metadata into a packet
> so in essence support Lightweight Tunneling (LWT) using Nftables. The LWT
> support is useful on virtualization environments where the users need to
> created a lot of tunnels to interconnect containers that are inside
> different VMs. Instead of creating one interface per container, the idea is
> that the user can create a single one and then attach the metadata as
> needed. Imagine the topology described below.
I'm trying to get Yi's test script to work but I am failing as well.
AFAICS the entire feature doesn't work *by design*.
> +------------------------+ +------------------------+
> |--------+ VM A | | VM B +--------|
> |Box | +------+ +---+|(192.168.124.49) +----+ +------+ |Box |
> |10.0.0.1|-|vxlan0|-|eth0|-------------------|eth0|-|vxlan0|-|10.0.0.2|
> |--------+ +------+ +---+| (192.168.124.134)+----+ +------+ +--------|
> | | | |
> | | | |
> +------------------------+ +------------------------+
How do I read this diagram?
Are these 4 computers or 2?
What is "Box" ? Is that a container inside of VM A / B ?
And if so, how does it connect to VM A? veth? The diagram reads like
its a container connected to VM A via a vxlan tunnel...
Which makes no sense to me.
> We want to reach 10.0.0.2 from 10.0.0.1, the nftables ruleset on VM A will
> look like this:
>
> ```
> table netdev filter_tunnel {
> tunnel vxlan_tmpl {
> id 100
> ip saddr 192.168.124.49
> ip daddr 192.168.124.134
> dport 8472
> ttl 255
> vxlan {
> gbp 100
> }
> }
>
> chain redirect_to_tunnel {
> type filter hook ingress device "veth_host" priority filter; policy
> accept;
> ip daddr 10.0.0.2 tunnel name "vxlan_tmpl" fwd to "vxlan0"
> }
>
> chain redirect_from_tunnel {
> type filter hook ingress device "vxlan0" priority filter; policy accept;
> ip daddr 10.0.0.1 fwd to "veth_host"
How can this work? I tried to get this to run but *ingress* sees no
packets. Which is not surprising to me, as packets are *egressing* from
VM A, not coming in.
The only way that I can get it to work is via normal tunnel device +
routes, no nftables rules needed.
Can you make a test script for packetpath?
Or add documentation that explains how to use this feature?
Thanks.
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH 4/7 nft v3] tunnel: add vxlan support
2025-08-21 9:12 [PATCH 1/7 nft v3] src: add tunnel template support Fernando Fernandez Mancera
2025-08-21 9:12 ` [PATCH 2/7 nft v3] tunnel: add erspan support Fernando Fernandez Mancera
2025-08-21 9:12 ` [PATCH 3/7 nft v3] src: add tunnel statement and expression support Fernando Fernandez Mancera
@ 2025-08-21 9:12 ` Fernando Fernandez Mancera
2025-08-21 9:13 ` [PATCH 5/7 nft v3] tunnel: add geneve support Fernando Fernandez Mancera
` (3 subsequent siblings)
6 siblings, 0 replies; 13+ messages in thread
From: Fernando Fernandez Mancera @ 2025-08-21 9:12 UTC (permalink / raw)
To: netfilter-devel; +Cc: coreteam, pablo, fw, Fernando Fernandez Mancera
This patch extends the tunnel metadata object to define vxlan tunnel
specific configurations:
table netdev x {
tunnel y {
id 10
ip saddr 192.168.2.10
ip daddr 192.168.2.11
sport 10
dport 20
ttl 10
vxlan {
gbp 200
}
}
}
Signed-off-by: Fernando Fernandez Mancera <fmancera@suse.de>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
v3: rebased
---
include/rule.h | 4 ++++
src/mnl.c | 16 ++++++++++++++++
src/netlink.c | 7 +++++++
src/parser_bison.y | 28 +++++++++++++++++++++++++++-
src/rule.c | 10 ++++++++++
src/scanner.l | 1 +
6 files changed, 65 insertions(+), 1 deletion(-)
diff --git a/include/rule.h b/include/rule.h
index 71e9a07e..c52af2c4 100644
--- a/include/rule.h
+++ b/include/rule.h
@@ -495,6 +495,7 @@ struct secmark {
enum tunnel_type {
TUNNEL_UNSPEC = 0,
TUNNEL_ERSPAN,
+ TUNNEL_VXLAN,
};
struct tunnel {
@@ -517,6 +518,9 @@ struct tunnel {
uint8_t hwid;
} v2;
} erspan;
+ struct {
+ uint32_t gbp;
+ } vxlan;
};
};
diff --git a/src/mnl.c b/src/mnl.c
index 3762b6f9..c0aadf59 100644
--- a/src/mnl.c
+++ b/src/mnl.c
@@ -1508,6 +1508,22 @@ static void obj_tunnel_add_opts(struct nftnl_obj *nlo, struct tunnel *tunnel)
break;
}
+ nftnl_tunnel_opts_add(opts, opt);
+ nftnl_obj_set_data(nlo, NFTNL_OBJ_TUNNEL_OPTS, &opts, sizeof(struct nftnl_tunnel_opts *));
+ break;
+ case TUNNEL_VXLAN:
+ opts = nftnl_tunnel_opts_alloc(NFTNL_TUNNEL_TYPE_VXLAN);
+ if (!opts)
+ memory_allocation_error();
+
+ opt = nftnl_tunnel_opt_alloc(NFTNL_TUNNEL_TYPE_VXLAN);
+ if (!opt)
+ memory_allocation_error();
+
+ nftnl_tunnel_opt_set(opt, NFTNL_TUNNEL_VXLAN_GBP,
+ &tunnel->vxlan.gbp,
+ sizeof(tunnel->vxlan.gbp));
+
nftnl_tunnel_opts_add(opts, opt);
nftnl_obj_set_data(nlo, NFTNL_OBJ_TUNNEL_OPTS, &opts, sizeof(struct nftnl_tunnel_opts *));
break;
diff --git a/src/netlink.c b/src/netlink.c
index 4ef88402..e132362b 100644
--- a/src/netlink.c
+++ b/src/netlink.c
@@ -1836,6 +1836,13 @@ static int tunnel_parse_opt_cb(struct nftnl_tunnel_opt *opt, void *data) {
NFTNL_TUNNEL_ERSPAN_V2_DIR);
}
break;
+ case NFTNL_TUNNEL_TYPE_VXLAN:
+ obj->tunnel.type = TUNNEL_VXLAN;
+ if (nftnl_tunnel_opt_get_flags(opt) & (1 << NFTNL_TUNNEL_VXLAN_GBP)) {
+ obj->tunnel.type = TUNNEL_VXLAN;
+ obj->tunnel.vxlan.gbp = nftnl_tunnel_opt_get_u32(opt, NFTNL_TUNNEL_VXLAN_GBP);
+ }
+ break;
default:
break;
}
diff --git a/src/parser_bison.y b/src/parser_bison.y
index 08d75dbb..ca93a658 100644
--- a/src/parser_bison.y
+++ b/src/parser_bison.y
@@ -612,6 +612,7 @@ int nft_lex(void *, void *, void *);
%token ERSPAN "erspan"
%token EGRESS "egress"
%token INGRESS "ingress"
+%token GBP "gbp"
%token COUNTERS "counters"
%token QUOTAS "quotas"
@@ -770,7 +771,7 @@ int nft_lex(void *, void *, void *);
%type <flowtable> flowtable_block_alloc flowtable_block
%destructor { flowtable_free($$); } flowtable_block_alloc
-%type <obj> obj_block_alloc counter_block quota_block ct_helper_block ct_timeout_block ct_expect_block limit_block secmark_block synproxy_block tunnel_block erspan_block erspan_block_alloc
+%type <obj> obj_block_alloc counter_block quota_block ct_helper_block ct_timeout_block ct_expect_block limit_block secmark_block synproxy_block tunnel_block erspan_block erspan_block_alloc vxlan_block vxlan_block_alloc
%destructor { obj_free($$); } obj_block_alloc
%type <list> stmt_list stateful_stmt_list set_elem_stmt_list
@@ -5011,6 +5012,27 @@ erspan_config : HDRVERSION NUM
}
;
+vxlan_block : /* empty */ { $$ = $<obj>-1; }
+ | vxlan_block common_block
+ | vxlan_block stmt_separator
+ | vxlan_block vxlan_config stmt_separator
+ {
+ $$ = $1;
+ }
+ ;
+
+vxlan_block_alloc : /* empty */
+ {
+ $$ = $<obj>-1;
+ }
+ ;
+
+vxlan_config : GBP NUM
+ {
+ $<obj>0->tunnel.vxlan.gbp = $2;
+ }
+ ;
+
tunnel_config : ID NUM
{
$<obj>0->tunnel.id = $2;
@@ -5055,6 +5077,10 @@ tunnel_config : ID NUM
{
$<obj>0->tunnel.type = TUNNEL_ERSPAN;
}
+ | VXLAN vxlan_block_alloc '{' vxlan_block '}'
+ {
+ $<obj>0->tunnel.type = TUNNEL_VXLAN;
+ }
;
tunnel_block : /* empty */ { $$ = $<obj>-1; }
diff --git a/src/rule.c b/src/rule.c
index 2557f4cc..0450851c 100644
--- a/src/rule.c
+++ b/src/rule.c
@@ -2043,6 +2043,16 @@ static void obj_print_data(const struct obj *obj,
}
nft_print(octx, "%s%s%s}",
opts->nl, opts->tab, opts->tab);
+ break;
+ case TUNNEL_VXLAN:
+ nft_print(octx, "%s%s%svxlan {",
+ opts->nl, opts->tab, opts->tab);
+ nft_print(octx, "%s%s%s%sgbp %u",
+ opts->nl, opts->tab, opts->tab, opts->tab,
+ obj->tunnel.vxlan.gbp);
+ nft_print(octx, "%s%s%s}",
+ opts->nl, opts->tab, opts->tab);
+ break;
default:
break;
}
diff --git a/src/scanner.l b/src/scanner.l
index 9695d710..74ebca3b 100644
--- a/src/scanner.l
+++ b/src/scanner.l
@@ -827,6 +827,7 @@ addrstring ({macaddr}|{ip4addr}|{ip6addr})
"egress" { return EGRESS; }
"ingress" { return INGRESS; }
"path" { return PATH; }
+ "gbp" { return GBP; }
}
"notrack" { return NOTRACK; }
--
2.50.1
^ permalink raw reply related [flat|nested] 13+ messages in thread* [PATCH 5/7 nft v3] tunnel: add geneve support
2025-08-21 9:12 [PATCH 1/7 nft v3] src: add tunnel template support Fernando Fernandez Mancera
` (2 preceding siblings ...)
2025-08-21 9:12 ` [PATCH 4/7 nft v3] tunnel: add vxlan support Fernando Fernandez Mancera
@ 2025-08-21 9:13 ` Fernando Fernandez Mancera
2025-08-21 9:13 ` [PATCH 6/7 nft v3] tunnel: add tunnel object and statement json support Fernando Fernandez Mancera
` (2 subsequent siblings)
6 siblings, 0 replies; 13+ messages in thread
From: Fernando Fernandez Mancera @ 2025-08-21 9:13 UTC (permalink / raw)
To: netfilter-devel; +Cc: coreteam, pablo, fw, Fernando Fernandez Mancera
From: Pablo Neira Ayuso <pablo@netfilter.org>
This patch extends the tunnel metadata object to define geneve tunnel
specific configurations:
table netdev x {
tunnel y {
id 10
ip saddr 192.168.2.10
ip daddr 192.168.2.11
sport 10
dport 20
ttl 10
geneve {
class 0x1010 opt-type 0x1 data "0x12345678"
class 0x1020 opt-type 0x2 data "0x87654321"
class 0x2020 opt-type 0x3 data "0x87654321abcdeffe"
}
}
}
Signed-off-by: Fernando Fernandez Mancera <fmancera@suse.de>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
v3: rebased
---
include/rule.h | 14 ++++++++++
src/mnl.c | 25 +++++++++++++++++
src/netlink.c | 29 ++++++++++++++++++++
src/parser_bison.y | 43 ++++++++++++++++++++++++++++-
src/rule.c | 67 ++++++++++++++++++++++++++++++++++++++++++++++
src/scanner.l | 3 +++
6 files changed, 180 insertions(+), 1 deletion(-)
diff --git a/include/rule.h b/include/rule.h
index c52af2c4..498a88bf 100644
--- a/include/rule.h
+++ b/include/rule.h
@@ -496,6 +496,15 @@ enum tunnel_type {
TUNNEL_UNSPEC = 0,
TUNNEL_ERSPAN,
TUNNEL_VXLAN,
+ TUNNEL_GENEVE,
+};
+
+struct tunnel_geneve {
+ struct list_head list;
+ uint16_t geneve_class;
+ uint8_t type;
+ uint8_t data[NFTNL_TUNNEL_GENEVE_DATA_MAXLEN];
+ uint32_t data_len;
};
struct tunnel {
@@ -521,9 +530,14 @@ struct tunnel {
struct {
uint32_t gbp;
} vxlan;
+ struct list_head geneve_opts;
};
};
+int tunnel_geneve_data_str2array(const char *hexstr,
+ uint8_t *out_data,
+ uint32_t *out_len);
+
/**
* struct obj - nftables stateful object statement
*
diff --git a/src/mnl.c b/src/mnl.c
index c0aadf59..108d6e55 100644
--- a/src/mnl.c
+++ b/src/mnl.c
@@ -1527,6 +1527,31 @@ static void obj_tunnel_add_opts(struct nftnl_obj *nlo, struct tunnel *tunnel)
nftnl_tunnel_opts_add(opts, opt);
nftnl_obj_set_data(nlo, NFTNL_OBJ_TUNNEL_OPTS, &opts, sizeof(struct nftnl_tunnel_opts *));
break;
+ case TUNNEL_GENEVE:
+ struct tunnel_geneve *geneve;
+
+ opts = nftnl_tunnel_opts_alloc(NFTNL_TUNNEL_TYPE_GENEVE);
+ if (!opts)
+ memory_allocation_error();
+
+ list_for_each_entry(geneve, &tunnel->geneve_opts, list) {
+ opt = nftnl_tunnel_opt_alloc(NFTNL_TUNNEL_TYPE_GENEVE);
+ if (!opt)
+ memory_allocation_error();
+
+ nftnl_tunnel_opt_set(opt,
+ NFTNL_TUNNEL_GENEVE_TYPE,
+ &geneve->type, sizeof(geneve->type));
+ nftnl_tunnel_opt_set(opt,
+ NFTNL_TUNNEL_GENEVE_CLASS,
+ &geneve->geneve_class, sizeof(geneve->geneve_class));
+ nftnl_tunnel_opt_set(opt,
+ NFTNL_TUNNEL_GENEVE_DATA,
+ &geneve->data, geneve->data_len);
+ nftnl_tunnel_opts_add(opts, opt);
+ }
+ nftnl_obj_set_data(nlo, NFTNL_OBJ_TUNNEL_OPTS, &opts, sizeof(struct nftnl_tunnel_opts *));
+ break;
case TUNNEL_UNSPEC:
break;
}
diff --git a/src/netlink.c b/src/netlink.c
index e132362b..5bae3b82 100644
--- a/src/netlink.c
+++ b/src/netlink.c
@@ -1843,6 +1843,35 @@ static int tunnel_parse_opt_cb(struct nftnl_tunnel_opt *opt, void *data) {
obj->tunnel.vxlan.gbp = nftnl_tunnel_opt_get_u32(opt, NFTNL_TUNNEL_VXLAN_GBP);
}
break;
+ case NFTNL_TUNNEL_TYPE_GENEVE:
+ struct tunnel_geneve *geneve;
+ const void *data;
+
+ if (!obj->tunnel.type) {
+ init_list_head(&obj->tunnel.geneve_opts);
+ obj->tunnel.type = TUNNEL_GENEVE;
+ }
+
+ geneve = xmalloc(sizeof(struct tunnel_geneve));
+ if (!geneve)
+ memory_allocation_error();
+
+ if (nftnl_tunnel_opt_get_flags(opt) & (1 << NFTNL_TUNNEL_GENEVE_TYPE))
+ geneve->type = nftnl_tunnel_opt_get_u8(opt, NFTNL_TUNNEL_GENEVE_TYPE);
+
+ if (nftnl_tunnel_opt_get_flags(opt) & (1 << NFTNL_TUNNEL_GENEVE_CLASS))
+ geneve->geneve_class = nftnl_tunnel_opt_get_u16(opt, NFTNL_TUNNEL_GENEVE_CLASS);
+
+ if (nftnl_tunnel_opt_get_flags(opt) & (1 << NFTNL_TUNNEL_GENEVE_DATA)) {
+ data = nftnl_tunnel_opt_get_data(opt, NFTNL_TUNNEL_GENEVE_DATA,
+ &geneve->data_len);
+ if (!data)
+ return -1;
+ memcpy(&geneve->data, data, geneve->data_len);
+ }
+
+ list_add_tail(&geneve->list, &obj->tunnel.geneve_opts);
+ break;
default:
break;
}
diff --git a/src/parser_bison.y b/src/parser_bison.y
index ca93a658..13eb6027 100644
--- a/src/parser_bison.y
+++ b/src/parser_bison.y
@@ -613,6 +613,8 @@ int nft_lex(void *, void *, void *);
%token EGRESS "egress"
%token INGRESS "ingress"
%token GBP "gbp"
+%token CLASS "class"
+%token OPTTYPE "opt-type"
%token COUNTERS "counters"
%token QUOTAS "quotas"
@@ -771,7 +773,7 @@ int nft_lex(void *, void *, void *);
%type <flowtable> flowtable_block_alloc flowtable_block
%destructor { flowtable_free($$); } flowtable_block_alloc
-%type <obj> obj_block_alloc counter_block quota_block ct_helper_block ct_timeout_block ct_expect_block limit_block secmark_block synproxy_block tunnel_block erspan_block erspan_block_alloc vxlan_block vxlan_block_alloc
+%type <obj> obj_block_alloc counter_block quota_block ct_helper_block ct_timeout_block ct_expect_block limit_block secmark_block synproxy_block tunnel_block erspan_block erspan_block_alloc vxlan_block vxlan_block_alloc geneve_block geneve_block_alloc
%destructor { obj_free($$); } obj_block_alloc
%type <list> stmt_list stateful_stmt_list set_elem_stmt_list
@@ -5012,6 +5014,44 @@ erspan_config : HDRVERSION NUM
}
;
+geneve_block : /* empty */ { $$ = $<obj>-1; }
+ | geneve_block common_block
+ | geneve_block stmt_separator
+ | geneve_block geneve_config stmt_separator
+ {
+ $$ = $1;
+ }
+ ;
+
+geneve_block_alloc : /* empty */
+ {
+ $$ = $<obj>-1;
+ }
+ ;
+
+geneve_config : CLASS NUM OPTTYPE NUM DATA string
+ {
+ struct tunnel_geneve *geneve;
+
+ geneve = xmalloc(sizeof(struct tunnel_geneve));
+ geneve->geneve_class = $2;
+ geneve->type = $4;
+ if (tunnel_geneve_data_str2array($6, geneve->data, &geneve->data_len)) {
+ erec_queue(error(&@6, "Invalid data array %s\n", $6), state->msgs);
+ free_const($6);
+ free(geneve);
+ YYERROR;
+ }
+
+ if (!$<obj>0->tunnel.type) {
+ $<obj>0->tunnel.type = TUNNEL_GENEVE;
+ init_list_head(&$<obj>0->tunnel.geneve_opts);
+ }
+ list_add_tail(&geneve->list, &$<obj>0->tunnel.geneve_opts);
+ free_const($6);
+ }
+ ;
+
vxlan_block : /* empty */ { $$ = $<obj>-1; }
| vxlan_block common_block
| vxlan_block stmt_separator
@@ -5081,6 +5121,7 @@ tunnel_config : ID NUM
{
$<obj>0->tunnel.type = TUNNEL_VXLAN;
}
+ | GENEVE geneve_block_alloc '{' geneve_block '}'
;
tunnel_block : /* empty */ { $$ = $<obj>-1; }
diff --git a/src/rule.c b/src/rule.c
index 0450851c..e6216bca 100644
--- a/src/rule.c
+++ b/src/rule.c
@@ -1707,6 +1707,14 @@ void obj_free(struct obj *obj)
case NFT_OBJECT_TUNNEL:
expr_free(obj->tunnel.src);
expr_free(obj->tunnel.dst);
+ if (obj->tunnel.type == TUNNEL_GENEVE) {
+ struct tunnel_geneve *geneve, *next;
+
+ list_for_each_entry_safe(geneve, next, &obj->tunnel.geneve_opts, list) {
+ list_del(&geneve->list);
+ free(geneve);
+ }
+ }
break;
default:
break;
@@ -1787,6 +1795,44 @@ static const char *synproxy_timestamp_to_str(const uint32_t flags)
return "";
}
+int tunnel_geneve_data_str2array(const char *hexstr,
+ uint8_t *out_data,
+ uint32_t *out_len)
+{
+ char bytestr[3] = {0};
+ size_t len;
+
+ if (hexstr[0] == '0' && (hexstr[1] == 'x' || hexstr[1] == 'X'))
+ hexstr += 2;
+ else
+ return -1;
+
+ len = strlen(hexstr);
+ if (len % 4 != 0)
+ return -1;
+
+ len = len / 2;
+ if (len > NFTNL_TUNNEL_GENEVE_DATA_MAXLEN)
+ return -1;
+
+ for (size_t i = 0; i < len; i++) {
+ uint32_t value;
+ char *endptr;
+
+ bytestr[0] = hexstr[i * 2];
+ bytestr[1] = hexstr[i * 2 + 1];
+
+ value = strtoul(bytestr, &endptr, 16);
+ if (*endptr != '\0')
+ return -1;
+
+ out_data[i] = (uint8_t) value;
+ }
+ *out_len = (uint8_t) len;
+
+ return 0;
+}
+
static void obj_print_comment(const struct obj *obj,
struct print_fmt_options *opts,
struct output_ctx *octx)
@@ -2053,6 +2099,27 @@ static void obj_print_data(const struct obj *obj,
nft_print(octx, "%s%s%s}",
opts->nl, opts->tab, opts->tab);
break;
+ case TUNNEL_GENEVE:
+ struct tunnel_geneve *geneve;
+
+ nft_print(octx, "%s%s%sgeneve {", opts->nl, opts->tab, opts->tab);
+ list_for_each_entry(geneve, &obj->tunnel.geneve_opts, list) {
+ char data_str[256];
+ int offset = 0;
+
+ for (uint32_t i = 0; i < geneve->data_len; i++) {
+ offset += snprintf(data_str + offset,
+ geneve->data_len,
+ "%x",
+ geneve->data[i]);
+ }
+ nft_print(octx, "%s%s%s%sclass 0x%x opt-type 0x%x data \"0x%s\"",
+ opts->nl, opts->tab, opts->tab, opts->tab,
+ geneve->geneve_class, geneve->type, data_str);
+
+ }
+ nft_print(octx, "%s%s%s}", opts->nl, opts->tab, opts->tab);
+ break;
default:
break;
}
diff --git a/src/scanner.l b/src/scanner.l
index 74ebca3b..8085c93b 100644
--- a/src/scanner.l
+++ b/src/scanner.l
@@ -828,6 +828,9 @@ addrstring ({macaddr}|{ip4addr}|{ip6addr})
"ingress" { return INGRESS; }
"path" { return PATH; }
"gbp" { return GBP; }
+ "class" { return CLASS; }
+ "opt-type" { return OPTTYPE; }
+ "data" { return DATA; }
}
"notrack" { return NOTRACK; }
--
2.50.1
^ permalink raw reply related [flat|nested] 13+ messages in thread* [PATCH 6/7 nft v3] tunnel: add tunnel object and statement json support
2025-08-21 9:12 [PATCH 1/7 nft v3] src: add tunnel template support Fernando Fernandez Mancera
` (3 preceding siblings ...)
2025-08-21 9:13 ` [PATCH 5/7 nft v3] tunnel: add geneve support Fernando Fernandez Mancera
@ 2025-08-21 9:13 ` Fernando Fernandez Mancera
2025-08-21 9:13 ` [PATCH 7/7 nft v3] tests: add tunnel shell and python tests Fernando Fernandez Mancera
2025-08-27 22:24 ` [PATCH 1/7 nft v3] src: add tunnel template support Pablo Neira Ayuso
6 siblings, 0 replies; 13+ messages in thread
From: Fernando Fernandez Mancera @ 2025-08-21 9:13 UTC (permalink / raw)
To: netfilter-devel; +Cc: coreteam, pablo, fw, Fernando Fernandez Mancera
Signed-off-by: Fernando Fernandez Mancera <fmancera@suse.de>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
v3: added ipv6 support
---
include/json.h | 2 +
include/tunnel.h | 4 +
src/json.c | 96 ++++++++++++++++++-
src/parser_json.c | 236 +++++++++++++++++++++++++++++++++++++++++++++-
src/tunnel.c | 21 +++++
5 files changed, 351 insertions(+), 8 deletions(-)
diff --git a/include/json.h b/include/json.h
index b61eeafe..42e1c861 100644
--- a/include/json.h
+++ b/include/json.h
@@ -31,6 +31,7 @@ json_t *binop_expr_json(const struct expr *expr, struct output_ctx *octx);
json_t *relational_expr_json(const struct expr *expr, struct output_ctx *octx);
json_t *range_expr_json(const struct expr *expr, struct output_ctx *octx);
json_t *meta_expr_json(const struct expr *expr, struct output_ctx *octx);
+json_t *tunnel_expr_json(const struct expr *expr, struct output_ctx *octx);
json_t *payload_expr_json(const struct expr *expr, struct output_ctx *octx);
json_t *ct_expr_json(const struct expr *expr, struct output_ctx *octx);
json_t *concat_expr_json(const struct expr *expr, struct output_ctx *octx);
@@ -160,6 +161,7 @@ EXPR_PRINT_STUB(fib_expr)
EXPR_PRINT_STUB(constant_expr)
EXPR_PRINT_STUB(socket_expr)
EXPR_PRINT_STUB(osf_expr)
+EXPR_PRINT_STUB(tunnel_expr)
EXPR_PRINT_STUB(xfrm_expr)
EXPR_PRINT_STUB(integer_type)
diff --git a/include/tunnel.h b/include/tunnel.h
index 9e6bd97a..e2d87d2c 100644
--- a/include/tunnel.h
+++ b/include/tunnel.h
@@ -25,6 +25,10 @@ extern const struct tunnel_template tunnel_templates[];
.byteorder = (__byteorder), \
}
+struct error_record *tunnel_key_parse(const struct location *loc,
+ const char *str,
+ unsigned int *value);
+
extern struct expr *tunnel_expr_alloc(const struct location *loc,
enum nft_tunnel_keys key);
diff --git a/src/json.c b/src/json.c
index bb14e1bd..d06fd040 100644
--- a/src/json.c
+++ b/src/json.c
@@ -373,7 +373,31 @@ static json_t *timeout_policy_json(uint8_t l4, const uint32_t *timeout)
return root ? : json_null();
}
-static json_t *obj_print_json(const struct obj *obj)
+static json_t *tunnel_erspan_print_json(const struct obj *obj)
+{
+ json_t *tunnel;
+
+ switch (obj->tunnel.erspan.version) {
+ case 1:
+ tunnel = json_pack("{s:i, s:i}",
+ "version", obj->tunnel.erspan.version,
+ "index", obj->tunnel.erspan.v1.index);
+ break;
+ case 2:
+ tunnel = json_pack("{s:i, s:s, s:i}",
+ "version", obj->tunnel.erspan.version,
+ "dir", obj->tunnel.erspan.v2.direction ?
+ "egress" : "ingress",
+ "hwid", obj->tunnel.erspan.v2.hwid);
+ break;
+ default:
+ BUG("Unknown tunnel erspan version %d", obj->tunnel.erspan.version);
+ }
+
+ return tunnel;
+}
+
+static json_t *obj_print_json(struct output_ctx *octx, const struct obj *obj)
{
const char *rate_unit = NULL, *burst_unit = NULL;
const char *type = obj_type_name(obj->type);
@@ -488,7 +512,61 @@ static json_t *obj_print_json(const struct obj *obj)
json_decref(tmp);
break;
case NFT_OBJECT_TUNNEL:
- /* TODO */
+ tmp = json_pack("{s:i, s:o, s:o, s:i, s:i, s:i, s:i}",
+ "id", obj->tunnel.id,
+ obj->tunnel.src->dtype->type == TYPE_IPADDR ? "src-ipv4" : "src-ipv6",
+ expr_print_json(obj->tunnel.src, octx),
+ obj->tunnel.dst->dtype->type == TYPE_IPADDR ? "dst-ipv4" : "dst-ipv6",
+ expr_print_json(obj->tunnel.dst, octx),
+ "sport", obj->tunnel.sport,
+ "dport", obj->tunnel.dport,
+ "tos", obj->tunnel.tos,
+ "ttl", obj->tunnel.ttl);
+
+ switch (obj->tunnel.type) {
+ case TUNNEL_UNSPEC:
+ break;
+ case TUNNEL_ERSPAN:
+ json_object_set_new(tmp, "type", json_string("erspan"));
+ json_object_set_new(tmp, "tunnel",
+ tunnel_erspan_print_json(obj));
+ break;
+ case TUNNEL_VXLAN:
+ json_object_set_new(tmp, "type", json_string("vxlan"));
+ json_object_set_new(tmp, "tunnel",
+ json_pack("{s:i}",
+ "gbp",
+ obj->tunnel.vxlan.gbp));
+ break;
+ case TUNNEL_GENEVE:
+ struct tunnel_geneve *geneve;
+ json_t *opts = json_array();
+
+ list_for_each_entry(geneve, &obj->tunnel.geneve_opts, list) {
+ char data_str[256];
+ json_t *opt;
+ int offset;
+
+ data_str[0] = '0';
+ data_str[1] = 'x';
+ offset = 2;
+ for (uint32_t i = 0; i < geneve->data_len; i++)
+ offset += snprintf(data_str + offset,
+ 3, "%x", geneve->data[i]);
+
+ opt = json_pack("{s:i, s:i, s:s}",
+ "class", geneve->geneve_class,
+ "opt-type", geneve->type,
+ "data", data_str);
+ json_array_append_new(opts, opt);
+ }
+
+ json_object_set_new(tmp, "type", json_string("geneve"));
+ json_object_set_new(tmp, "tunnel", opts);
+ break;
+ }
+ json_object_update(root, tmp);
+ json_decref(tmp);
break;
}
@@ -1115,6 +1193,12 @@ json_t *xfrm_expr_json(const struct expr *expr, struct output_ctx *octx)
return nft_json_pack("{s:o}", "ipsec", root);
}
+json_t *tunnel_expr_json(const struct expr *expr, struct output_ctx *octx)
+{
+ return json_pack("{s:{s:s}}", "tunnel",
+ "key", tunnel_templates[expr->tunnel.key].token);
+}
+
json_t *integer_type_json(const struct expr *expr, struct output_ctx *octx)
{
char buf[1024] = "0x";
@@ -1731,7 +1815,7 @@ static json_t *table_print_json_full(struct netlink_ctx *ctx,
json_array_append_new(root, tmp);
}
list_for_each_entry(obj, &table->obj_cache.list, cache.list) {
- tmp = obj_print_json(obj);
+ tmp = obj_print_json(&ctx->nft->output, obj);
json_array_append_new(root, tmp);
}
list_for_each_entry(set, &table->set_cache.list, cache.list) {
@@ -1906,7 +1990,7 @@ static json_t *do_list_obj_json(struct netlink_ctx *ctx,
strcmp(cmd->handle.obj.name, obj->handle.obj.name)))
continue;
- json_array_append_new(root, obj_print_json(obj));
+ json_array_append_new(root, obj_print_json(&ctx->nft->output, obj));
}
}
@@ -2125,7 +2209,9 @@ void monitor_print_element_json(struct netlink_mon_handler *monh,
void monitor_print_obj_json(struct netlink_mon_handler *monh,
const char *cmd, struct obj *o)
{
- monitor_print_json(monh, cmd, obj_print_json(o));
+ struct output_ctx *octx = &monh->ctx->nft->output;
+
+ monitor_print_json(monh, cmd, obj_print_json(octx, o));
}
void monitor_print_flowtable_json(struct netlink_mon_handler *monh,
diff --git a/src/parser_json.c b/src/parser_json.c
index ebb96d79..8bca6a59 100644
--- a/src/parser_json.c
+++ b/src/parser_json.c
@@ -439,6 +439,23 @@ static struct expr *json_parse_meta_expr(struct json_ctx *ctx,
return meta_expr_alloc(int_loc, key);
}
+static struct expr *json_parse_tunnel_expr(struct json_ctx *ctx,
+ const char *type, json_t *root)
+{
+ struct error_record *erec;
+ unsigned int key;
+ const char *name;
+
+ if (json_unpack_err(ctx, root, "{s:s}", "key", &name))
+ return NULL;
+ erec = tunnel_key_parse(int_loc, name, &key);
+ if (erec) {
+ erec_queue(erec, ctx->msgs);
+ return NULL;
+ }
+ return tunnel_expr_alloc(int_loc, key);
+}
+
static struct expr *json_parse_osf_expr(struct json_ctx *ctx,
const char *type, json_t *root)
{
@@ -1642,6 +1659,7 @@ static struct expr *json_parse_expr(struct json_ctx *ctx, json_t *root)
{ "rt", json_parse_rt_expr, CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_SES | CTX_F_MAP | CTX_F_CONCAT },
{ "ct", json_parse_ct_expr, CTX_F_RHS | CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_MANGLE | CTX_F_SES | CTX_F_MAP | CTX_F_CONCAT },
{ "numgen", json_parse_numgen_expr, CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_SES | CTX_F_MAP | CTX_F_CONCAT },
+ { "tunnel", json_parse_tunnel_expr, CTX_F_RHS | CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SES | CTX_F_MAP },
/* below two are hash expr */
{ "jhash", json_parse_hash_expr, CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_SES | CTX_F_MAP | CTX_F_CONCAT },
{ "symhash", json_parse_hash_expr, CTX_F_STMT | CTX_F_PRIMARY | CTX_F_SET_RHS | CTX_F_SES | CTX_F_MAP | CTX_F_CONCAT },
@@ -2202,6 +2220,23 @@ static struct stmt *json_parse_secmark_stmt(struct json_ctx *ctx,
return stmt;
}
+static struct stmt *json_parse_tunnel_stmt(struct json_ctx *ctx,
+ const char *key, json_t *value)
+{
+ struct stmt *stmt;
+
+ stmt = objref_stmt_alloc(int_loc);
+ stmt->objref.type = NFT_OBJECT_TUNNEL;
+ stmt->objref.expr = json_parse_stmt_expr(ctx, value);
+ if (!stmt->objref.expr) {
+ json_error(ctx, "Invalid tunnel reference.");
+ stmt_free(stmt);
+ return NULL;
+ }
+
+ return stmt;
+}
+
static unsigned int json_parse_nat_flag(const char *flag)
{
const struct {
@@ -2870,6 +2905,7 @@ static struct stmt *json_parse_stmt(struct json_ctx *ctx, json_t *root)
{ "synproxy", json_parse_synproxy_stmt },
{ "reset", json_parse_optstrip_stmt },
{ "secmark", json_parse_secmark_stmt },
+ { "tunnel", json_parse_tunnel_stmt },
};
const char *type;
unsigned int i;
@@ -3518,14 +3554,139 @@ static int json_parse_ct_timeout_policy(struct json_ctx *ctx,
return 0;
}
+static int json_parse_tunnel_erspan(struct json_ctx *ctx,
+ json_t *root, struct obj *obj)
+{
+ const char *dir;
+ json_t *tmp;
+ int i;
+
+ if (json_unpack_err(ctx, root, "{s:o}", "tunnel", &tmp))
+ return 1;
+
+ if (json_unpack_err(ctx, tmp, "{s:i}", "version", &obj->tunnel.erspan.version))
+ return 1;
+
+ switch (obj->tunnel.erspan.version) {
+ case 1:
+ if (json_unpack_err(ctx, tmp, "{s:i}",
+ "index", &obj->tunnel.erspan.v1.index))
+ return 1;
+ break;
+ case 2:
+ if (json_unpack_err(ctx, tmp, "{s:s, s:i}",
+ "dir", &dir,
+ "hwid", &i))
+ return 1;
+ obj->tunnel.erspan.v2.hwid = i;
+
+ if (!strcmp(dir, "ingress")) {
+ obj->tunnel.erspan.v2.direction = 0;
+ } else if (!strcmp(dir, "egress")) {
+ obj->tunnel.erspan.v2.direction = 1;
+ } else {
+ json_error(ctx, "Invalid direction '%s'.", dir);
+ return 1;
+ }
+ break;
+ default:
+ json_error(ctx, "Invalid erspan version %u" , obj->tunnel.erspan.version);
+ return 1;
+ }
+
+ return 0;
+}
+
+static enum tunnel_type json_parse_tunnel_type(struct json_ctx *ctx,
+ const char *type)
+{
+ const struct {
+ const char *type;
+ int val;
+ } type_tbl[] = {
+ { "erspan", TUNNEL_ERSPAN },
+ { "vxlan", TUNNEL_VXLAN },
+ { "geneve", TUNNEL_GENEVE },
+ };
+ unsigned int i;
+
+ if (!type)
+ return TUNNEL_UNSPEC;
+
+ for (i = 0; i < array_size(type_tbl); i++) {
+ if (!strcmp(type, type_tbl[i].type))
+ return type_tbl[i].val;
+ }
+
+ return TUNNEL_UNSPEC;
+}
+
+static int json_parse_tunnel_src_and_dst(struct json_ctx *ctx,
+ json_t *root,
+ struct obj *obj)
+{
+ bool is_ipv4 = false, src_set = false, dst_set = false;
+ struct expr *expr;
+ json_t *tmp;
+
+ if (!json_unpack(root, "{s:o}", "src-ipv4", &tmp)) {
+ is_ipv4 = true;
+ src_set = true;
+ expr = json_parse_expr(ctx, tmp);
+ if (!expr)
+ return -1;
+ datatype_set(expr, &ipaddr_type);
+ obj->tunnel.src = expr;
+ }
+
+ if (!json_unpack(root, "{s:o}", "src-ipv6", &tmp)) {
+ if (is_ipv4 || src_set)
+ return -1;
+ src_set = true;
+ expr = json_parse_expr(ctx, tmp);
+ if (!expr)
+ return -1;
+ datatype_set(expr, &ip6addr_type);
+ obj->tunnel.src = expr;
+ }
+
+ if (!json_unpack(root, "{s:o}", "dst-ipv4", &tmp)) {
+ dst_set = true;
+ if (!is_ipv4)
+ return -1;
+ expr = json_parse_expr(ctx, tmp);
+ if (!expr)
+ return -1;
+ datatype_set(expr, &ipaddr_type);
+ obj->tunnel.dst = expr;
+ }
+
+ if (!json_unpack(root, "{s:o}", "dst-ipv6", &tmp)) {
+ if (is_ipv4 || dst_set)
+ return -1;
+ dst_set = true;
+ expr = json_parse_expr(ctx, tmp);
+ if (!expr)
+ return -1;
+ datatype_set(expr, &ip6addr_type);
+ obj->tunnel.dst = expr;
+ }
+
+ if (!dst_set || !src_set)
+ return -1;
+
+ return 0;
+}
+
static struct cmd *json_parse_cmd_add_object(struct json_ctx *ctx,
json_t *root, enum cmd_ops op,
enum cmd_obj cmd_obj)
{
- const char *family, *tmp, *rate_unit = "packets", *burst_unit = "bytes";
+ const char *family, *tmp = NULL, *rate_unit = "packets", *burst_unit = "bytes";
uint32_t l3proto = NFPROTO_UNSPEC;
int inv = 0, flags = 0, i, j;
struct handle h = { 0 };
+ json_t *tmp_json;
struct obj *obj;
if (json_unpack_err(ctx, root, "{s:s, s:s}",
@@ -3713,8 +3874,77 @@ static struct cmd *json_parse_cmd_add_object(struct json_ctx *ctx,
obj->synproxy.flags |= flags;
break;
- case CMD_OBJ_TUNNEL:
- /* TODO */
+ case NFT_OBJECT_TUNNEL:
+ cmd_obj = CMD_OBJ_TUNNEL;
+ obj->type = NFT_OBJECT_TUNNEL;
+
+ if (json_parse_tunnel_src_and_dst(ctx, root, obj))
+ goto err_free_obj;
+
+ json_unpack(root, "{s:i}", "id", &obj->tunnel.id);
+ json_unpack(root, "{s:i}", "sport", &i);
+ obj->tunnel.sport = i;
+ json_unpack(root, "{s:i}", "dport", &i);
+ obj->tunnel.sport = i;
+ json_unpack(root, "{s:i}", "ttl", &i);
+ obj->tunnel.ttl = i;
+ json_unpack(root, "{s:i}", "tos", &i);
+ obj->tunnel.tos = i;
+ json_unpack(root, "{s:s}", "type", &tmp);
+
+ obj->tunnel.type = json_parse_tunnel_type(ctx, tmp);
+ switch (obj->tunnel.type) {
+ case TUNNEL_UNSPEC:
+ break;
+ case TUNNEL_ERSPAN:
+ if (json_parse_tunnel_erspan(ctx, root, obj))
+ goto err_free_obj;
+ break;
+ case TUNNEL_VXLAN:
+ if (json_unpack_err(ctx, root,
+ "{s:o}", "tunnel", &tmp_json))
+ goto err_free_obj;
+
+ json_unpack(tmp_json, "{s:i}",
+ "gbp", &obj->tunnel.vxlan.gbp);
+ break;
+ case TUNNEL_GENEVE:
+ json_t *value;
+ size_t index;
+
+ if (json_unpack_err(ctx, root,
+ "{s:o}", "tunnel", &tmp_json))
+ goto err_free_obj;
+
+ json_array_foreach(tmp_json, index, value) {
+ struct tunnel_geneve *geneve = xmalloc(sizeof(struct tunnel_geneve));
+ if (!geneve)
+ memory_allocation_error();
+
+ if (json_unpack_err(ctx, value, "{s:i, s:i, s:s}",
+ "class", &i,
+ "opt-type", &j,
+ "data", &tmp)) {
+ free(geneve);
+ goto err_free_obj;
+ }
+ geneve->geneve_class = i;
+ geneve->type = j;
+
+ if (tunnel_geneve_data_str2array(tmp,
+ geneve->data,
+ &geneve->data_len)) {
+ free(geneve);
+ goto err_free_obj;
+ }
+
+ if (index == 0)
+ init_list_head(&obj->tunnel.geneve_opts);
+
+ list_add_tail(&geneve->list, &obj->tunnel.geneve_opts);
+ }
+ break;
+ }
break;
default:
BUG("Invalid CMD '%d'", cmd_obj);
diff --git a/src/tunnel.c b/src/tunnel.c
index cd92d039..a311246b 100644
--- a/src/tunnel.c
+++ b/src/tunnel.c
@@ -6,6 +6,8 @@
* published by the Free Software Foundation.
*/
+#include <nft.h>
+
#include <errno.h>
#include <limits.h>
#include <stddef.h>
@@ -38,6 +40,24 @@ const struct tunnel_template tunnel_templates[] = {
4 * 8, BYTEORDER_HOST_ENDIAN),
};
+struct error_record *tunnel_key_parse(const struct location *loc,
+ const char *str,
+ unsigned int *value)
+{
+ unsigned int i;
+
+ for (i = 0; i < array_size(tunnel_templates); i++) {
+ if (!tunnel_templates[i].token ||
+ strcmp(tunnel_templates[i].token, str))
+ continue;
+
+ *value = i;
+ return NULL;
+ }
+
+ return error(loc, "syntax error, unexpected %s", str);
+}
+
static void tunnel_expr_print(const struct expr *expr, struct output_ctx *octx)
{
uint32_t key = expr->tunnel.key;
@@ -63,6 +83,7 @@ const struct expr_ops tunnel_expr_ops = {
.type = EXPR_TUNNEL,
.name = "tunnel",
.print = tunnel_expr_print,
+ .json = tunnel_expr_json,
.cmp = tunnel_expr_cmp,
.clone = tunnel_expr_clone,
};
--
2.50.1
^ permalink raw reply related [flat|nested] 13+ messages in thread* [PATCH 7/7 nft v3] tests: add tunnel shell and python tests
2025-08-21 9:12 [PATCH 1/7 nft v3] src: add tunnel template support Fernando Fernandez Mancera
` (4 preceding siblings ...)
2025-08-21 9:13 ` [PATCH 6/7 nft v3] tunnel: add tunnel object and statement json support Fernando Fernandez Mancera
@ 2025-08-21 9:13 ` Fernando Fernandez Mancera
2025-08-27 22:24 ` [PATCH 1/7 nft v3] src: add tunnel template support Pablo Neira Ayuso
6 siblings, 0 replies; 13+ messages in thread
From: Fernando Fernandez Mancera @ 2025-08-21 9:13 UTC (permalink / raw)
To: netfilter-devel; +Cc: coreteam, pablo, fw, Fernando Fernandez Mancera
Add tests for tunnel statement and object support. Shell and python
tests both cover standard nft output and json.
Signed-off-by: Fernando Fernandez Mancera <fmancera@suse.de>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
v3: rebased and adapted tests for new json src/dst keys
---
tests/py/netdev/tunnel.t | 7 +
tests/py/netdev/tunnel.t.json | 45 +++++
tests/py/netdev/tunnel.t.payload | 15 ++
tests/shell/features/tunnel.nft | 17 ++
tests/shell/testcases/sets/0075tunnel_0 | 75 ++++++++
.../sets/dumps/0075tunnel_0.json-nft | 171 ++++++++++++++++++
.../testcases/sets/dumps/0075tunnel_0.nft | 63 +++++++
7 files changed, 393 insertions(+)
create mode 100644 tests/py/netdev/tunnel.t
create mode 100644 tests/py/netdev/tunnel.t.json
create mode 100644 tests/py/netdev/tunnel.t.payload
create mode 100644 tests/shell/features/tunnel.nft
create mode 100755 tests/shell/testcases/sets/0075tunnel_0
create mode 100644 tests/shell/testcases/sets/dumps/0075tunnel_0.json-nft
create mode 100644 tests/shell/testcases/sets/dumps/0075tunnel_0.nft
diff --git a/tests/py/netdev/tunnel.t b/tests/py/netdev/tunnel.t
new file mode 100644
index 00000000..920d21ff
--- /dev/null
+++ b/tests/py/netdev/tunnel.t
@@ -0,0 +1,7 @@
+:tunnelchain;type filter hook ingress device lo priority 0
+
+*netdev;test-netdev;tunnelchain
+
+tunnel path exists;ok
+tunnel path missing;ok
+tunnel id 10;ok
diff --git a/tests/py/netdev/tunnel.t.json b/tests/py/netdev/tunnel.t.json
new file mode 100644
index 00000000..3ca877d9
--- /dev/null
+++ b/tests/py/netdev/tunnel.t.json
@@ -0,0 +1,45 @@
+# tunnel path exists
+[
+ {
+ "match": {
+ "left": {
+ "tunnel": {
+ "key": "path"
+ }
+ },
+ "op": "==",
+ "right": true
+ }
+ }
+]
+
+# tunnel path missing
+[
+ {
+ "match": {
+ "left": {
+ "tunnel": {
+ "key": "path"
+ }
+ },
+ "op": "==",
+ "right": false
+ }
+ }
+]
+
+# tunnel id 10
+[
+ {
+ "match": {
+ "left": {
+ "tunnel": {
+ "key": "id"
+ }
+ },
+ "op": "==",
+ "right": 10
+ }
+ }
+]
+
diff --git a/tests/py/netdev/tunnel.t.payload b/tests/py/netdev/tunnel.t.payload
new file mode 100644
index 00000000..9148d0e5
--- /dev/null
+++ b/tests/py/netdev/tunnel.t.payload
@@ -0,0 +1,15 @@
+# tunnel path exists
+netdev test-netdev tunnelchain
+ [ tunnel load path => reg 1 ]
+ [ cmp eq reg 1 0x00000001 ]
+
+# tunnel path missing
+netdev test-netdev tunnelchain
+ [ tunnel load path => reg 1 ]
+ [ cmp eq reg 1 0x00000000 ]
+
+# tunnel id 10
+netdev test-netdev tunnelchain
+ [ tunnel load id => reg 1 ]
+ [ cmp eq reg 1 0x0000000a ]
+
diff --git a/tests/shell/features/tunnel.nft b/tests/shell/features/tunnel.nft
new file mode 100644
index 00000000..64b2f70b
--- /dev/null
+++ b/tests/shell/features/tunnel.nft
@@ -0,0 +1,17 @@
+# v5.7-rc1~146^2~137^2~26
+# 925d844696d9 ("netfilter: nft_tunnel: add support for geneve opts")
+table netdev x {
+ tunnel y {
+ id 10
+ ip saddr 192.168.2.10
+ ip daddr 192.168.2.11
+ sport 10
+ dport 20
+ ttl 10
+ geneve {
+ class 0x1010 opt-type 0x1 data "0x12345678"
+ class 0x2010 opt-type 0x2 data "0x87654321"
+ class 0x2020 opt-type 0x3 data "0x87654321abcdeffe"
+ }
+ }
+}
diff --git a/tests/shell/testcases/sets/0075tunnel_0 b/tests/shell/testcases/sets/0075tunnel_0
new file mode 100755
index 00000000..f8a8cf00
--- /dev/null
+++ b/tests/shell/testcases/sets/0075tunnel_0
@@ -0,0 +1,75 @@
+#!/bin/bash
+
+# NFT_TEST_REQUIRES(NFT_TEST_HAVE_tunnel)
+
+# * creating valid named objects
+# * referencing them from a valid rule
+
+RULESET="
+table netdev x {
+ tunnel geneve-t {
+ id 10
+ ip saddr 192.168.2.10
+ ip daddr 192.168.2.11
+ sport 10
+ dport 10
+ ttl 10
+ tos 10
+ geneve {
+ class 0x1 opt-type 0x1 data \"0x12345678\"
+ class 0x1010 opt-type 0x2 data \"0x87654321\"
+ class 0x2020 opt-type 0x3 data \"0x87654321abcdeffe\"
+ }
+ }
+
+ tunnel vxlan-t {
+ id 20
+ ip saddr 192.168.2.20
+ ip daddr 192.168.2.21
+ sport 20
+ dport 20
+ ttl 10
+ tos 10
+ vxlan {
+ gbp 200
+ }
+ }
+
+ tunnel erspan-tv1 {
+ id 30
+ ip saddr 192.168.2.30
+ ip daddr 192.168.2.31
+ sport 30
+ dport 30
+ ttl 10
+ tos 10
+ erspan {
+ version 1
+ index 5
+ }
+ }
+
+ tunnel erspan-tv2 {
+ id 40
+ ip saddr 192.168.2.40
+ ip daddr 192.168.2.41
+ sport 40
+ dport 40
+ ttl 10
+ tos 10
+ erspan {
+ version 2
+ direction ingress
+ id 10
+ }
+ }
+
+ chain x {
+ type filter hook ingress priority 0; policy accept;
+ tunnel name ip saddr map { 10.141.10.123 : "geneve-t", 10.141.10.124 : "vxlan-t", 10.141.10.125 : "erspan-tv1", 10.141.10.126 : "erspan-tv2" } counter
+ }
+}
+"
+
+set -e
+$NFT -f - <<< "$RULESET"
diff --git a/tests/shell/testcases/sets/dumps/0075tunnel_0.json-nft b/tests/shell/testcases/sets/dumps/0075tunnel_0.json-nft
new file mode 100644
index 00000000..7cd58268
--- /dev/null
+++ b/tests/shell/testcases/sets/dumps/0075tunnel_0.json-nft
@@ -0,0 +1,171 @@
+{
+ "nftables": [
+ {
+ "metainfo": {
+ "version": "VERSION",
+ "release_name": "RELEASE_NAME",
+ "json_schema_version": 1
+ }
+ },
+ {
+ "table": {
+ "family": "netdev",
+ "name": "x",
+ "handle": 0
+ }
+ },
+ {
+ "chain": {
+ "family": "netdev",
+ "table": "x",
+ "name": "x",
+ "handle": 0,
+ "type": "filter",
+ "hook": "ingress",
+ "prio": 0,
+ "policy": "accept"
+ }
+ },
+ {
+ "tunnel": {
+ "family": "netdev",
+ "name": "geneve-t",
+ "table": "x",
+ "handle": 0,
+ "id": 10,
+ "src-ipv4": "192.168.2.10",
+ "dst-ipv4": "192.168.2.11",
+ "sport": 10,
+ "dport": 10,
+ "tos": 10,
+ "ttl": 10,
+ "type": "geneve",
+ "tunnel": [
+ {
+ "class": 1,
+ "opt-type": 1,
+ "data": "0x12345678"
+ },
+ {
+ "class": 4112,
+ "opt-type": 2,
+ "data": "0x87654321"
+ },
+ {
+ "class": 8224,
+ "opt-type": 3,
+ "data": "0x87654321abcdeffe"
+ }
+ ]
+ }
+ },
+ {
+ "tunnel": {
+ "family": "netdev",
+ "name": "vxlan-t",
+ "table": "x",
+ "handle": 0,
+ "id": 20,
+ "src-ipv4": "192.168.2.20",
+ "dst-ipv4": "192.168.2.21",
+ "sport": 20,
+ "dport": 20,
+ "tos": 10,
+ "ttl": 10,
+ "type": "vxlan",
+ "tunnel": {
+ "gbp": 200
+ }
+ }
+ },
+ {
+ "tunnel": {
+ "family": "netdev",
+ "name": "erspan-tv1",
+ "table": "x",
+ "handle": 0,
+ "id": 30,
+ "src-ipv4": "192.168.2.30",
+ "dst-ipv4": "192.168.2.31",
+ "sport": 30,
+ "dport": 30,
+ "tos": 10,
+ "ttl": 10,
+ "type": "erspan",
+ "tunnel": {
+ "version": 1,
+ "index": 5
+ }
+ }
+ },
+ {
+ "tunnel": {
+ "family": "netdev",
+ "name": "erspan-tv2",
+ "table": "x",
+ "handle": 0,
+ "id": 40,
+ "src-ipv4": "192.168.2.40",
+ "dst-ipv4": "192.168.2.41",
+ "sport": 40,
+ "dport": 40,
+ "tos": 10,
+ "ttl": 10,
+ "type": "erspan",
+ "tunnel": {
+ "version": 2,
+ "dir": "ingress",
+ "hwid": 10
+ }
+ }
+ },
+ {
+ "rule": {
+ "family": "netdev",
+ "table": "x",
+ "chain": "x",
+ "handle": 0,
+ "expr": [
+ {
+ "tunnel": {
+ "map": {
+ "key": {
+ "payload": {
+ "protocol": "ip",
+ "field": "saddr"
+ }
+ },
+ "data": {
+ "set": [
+ [
+ "10.141.10.123",
+ "geneve-t"
+ ],
+ [
+ "10.141.10.124",
+ "vxlan-t"
+ ],
+ [
+ "10.141.10.125",
+ "erspan-tv1"
+ ],
+ [
+ "10.141.10.126",
+ "erspan-tv2"
+ ]
+ ]
+ }
+ }
+ }
+ },
+ {
+ "counter": {
+ "packets": 0,
+ "bytes": 0
+ }
+ }
+ ]
+ }
+ }
+ ]
+}
diff --git a/tests/shell/testcases/sets/dumps/0075tunnel_0.nft b/tests/shell/testcases/sets/dumps/0075tunnel_0.nft
new file mode 100644
index 00000000..9969124d
--- /dev/null
+++ b/tests/shell/testcases/sets/dumps/0075tunnel_0.nft
@@ -0,0 +1,63 @@
+table netdev x {
+ tunnel geneve-t {
+ id 10
+ ip saddr 192.168.2.10
+ ip daddr 192.168.2.11
+ sport 10
+ dport 10
+ tos 10
+ ttl 10
+ geneve {
+ class 0x1 opt-type 0x1 data "0x12345678"
+ class 0x1010 opt-type 0x2 data "0x87654321"
+ class 0x2020 opt-type 0x3 data "0x87654321abcdeffe"
+ }
+ }
+
+ tunnel vxlan-t {
+ id 20
+ ip saddr 192.168.2.20
+ ip daddr 192.168.2.21
+ sport 20
+ dport 20
+ tos 10
+ ttl 10
+ vxlan {
+ gbp 200
+ }
+ }
+
+ tunnel erspan-tv1 {
+ id 30
+ ip saddr 192.168.2.30
+ ip daddr 192.168.2.31
+ sport 30
+ dport 30
+ tos 10
+ ttl 10
+ erspan {
+ version 1
+ index 5
+ }
+ }
+
+ tunnel erspan-tv2 {
+ id 40
+ ip saddr 192.168.2.40
+ ip daddr 192.168.2.41
+ sport 40
+ dport 40
+ tos 10
+ ttl 10
+ erspan {
+ version 2
+ direction ingress
+ id 10
+ }
+ }
+
+ chain x {
+ type filter hook ingress priority filter; policy accept;
+ tunnel name ip saddr map { 10.141.10.123 : "geneve-t", 10.141.10.124 : "vxlan-t", 10.141.10.125 : "erspan-tv1", 10.141.10.126 : "erspan-tv2" } counter packets 0 bytes 0
+ }
+}
--
2.50.1
^ permalink raw reply related [flat|nested] 13+ messages in thread* Re: [PATCH 1/7 nft v3] src: add tunnel template support
2025-08-21 9:12 [PATCH 1/7 nft v3] src: add tunnel template support Fernando Fernandez Mancera
` (5 preceding siblings ...)
2025-08-21 9:13 ` [PATCH 7/7 nft v3] tests: add tunnel shell and python tests Fernando Fernandez Mancera
@ 2025-08-27 22:24 ` Pablo Neira Ayuso
6 siblings, 0 replies; 13+ messages in thread
From: Pablo Neira Ayuso @ 2025-08-27 22:24 UTC (permalink / raw)
To: Fernando Fernandez Mancera; +Cc: netfilter-devel, coreteam, fw, phil
Hi,
On Thu, Aug 21, 2025 at 11:12:56AM +0200, Fernando Fernandez Mancera wrote:
> From: Pablo Neira Ayuso <pablo@netfilter.org>
>
> This patch adds tunnel template support, this allows to attach a
> metadata template that provides the configuration for the tunnel driver.
I have applied this series.
Thanks.
^ permalink raw reply [flat|nested] 13+ messages in thread