Allow to forward packets through to explicit destination and interface.
nft add rule netdev x y fwd ip to 192.168.2.200 device eth0
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
* enum nft_fwd_attributes - nf_tables fwd expression netlink attributes
*
* @NFTA_FWD_SREG_DEV: source register of output interface (NLA_U32: nft_register)
+ * @NFTA_FWD_SREG_ADDR: source register of destination address (NLA_U32: nft_register)
+ * @NFTA_FWD_NFPROTO: layer 3 family of source register address (NLA_U32: enum nfproto)
*/
enum nft_fwd_attributes {
NFTA_FWD_UNSPEC,
NFTA_FWD_SREG_DEV,
+ NFTA_FWD_SREG_ADDR,
+ NFTA_FWD_NFPROTO,
__NFTA_FWD_MAX
};
#define NFTA_FWD_MAX (__NFTA_FWD_MAX - 1)
uint32_t dup_stmt_type(const char *type);
struct fwd_stmt {
- struct expr *to;
+ uint8_t family;
+ struct expr *addr;
+ struct expr *dev;
};
struct stmt *fwd_stmt_alloc(const struct location *loc);
static int stmt_evaluate_fwd(struct eval_ctx *ctx, struct stmt *stmt)
{
- int err;
+ const struct datatype *dtype;
+ int err, len;
switch (ctx->pctx.family) {
case NFPROTO_NETDEV:
- if (stmt->fwd.to == NULL)
+ if (stmt->fwd.dev == NULL)
return stmt_error(ctx, stmt,
"missing destination interface");
err = stmt_evaluate_arg(ctx, stmt, &ifindex_type,
sizeof(uint32_t) * BITS_PER_BYTE,
- BYTEORDER_HOST_ENDIAN, &stmt->fwd.to);
+ BYTEORDER_HOST_ENDIAN, &stmt->fwd.dev);
if (err < 0)
return err;
+
+ if (stmt->fwd.addr != NULL) {
+ switch (stmt->fwd.family) {
+ case NFPROTO_IPV4:
+ dtype = &ipaddr_type;
+ len = 4 * BITS_PER_BYTE;
+ break;
+ case NFPROTO_IPV6:
+ dtype = &ip6addr_type;
+ len = 16 * BITS_PER_BYTE;
+ break;
+ default:
+ return stmt_error(ctx, stmt, "missing family");
+ }
+ err = stmt_evaluate_arg(ctx, stmt, dtype, len,
+ BYTEORDER_BIG_ENDIAN,
+ &stmt->fwd.addr);
+ if (err < 0)
+ return err;
+ }
break;
default:
return stmt_error(ctx, stmt, "unsupported family");
{
json_t *root;
- root = expr_print_json(stmt->fwd.to, octx);
+ root = expr_print_json(stmt->fwd.dev, octx);
return json_pack("{s:o}", "fwd", root);
}
const struct location *loc,
const struct nftnl_expr *nle)
{
- enum nft_registers reg1;
- struct expr *dev;
+ enum nft_registers reg1, reg2;
+ struct expr *dev, *addr;
struct stmt *stmt;
stmt = fwd_stmt_alloc(loc);
}
expr_set_type(dev, &ifindex_type, BYTEORDER_HOST_ENDIAN);
- stmt->fwd.to = dev;
+ stmt->fwd.dev = dev;
+ }
+
+ if (nftnl_expr_is_set(nle, NFTNL_EXPR_FWD_NFPROTO)) {
+ stmt->fwd.family =
+ nftnl_expr_get_u32(nle, NFTNL_EXPR_FWD_NFPROTO);
+ }
+
+ if (nftnl_expr_is_set(nle, NFTNL_EXPR_FWD_SREG_ADDR)) {
+ reg2 = netlink_parse_register(nle, NFTNL_EXPR_FWD_SREG_ADDR);
+ if (reg2) {
+ addr = netlink_get_register(ctx, loc, reg2);
+ if (addr == NULL)
+ return netlink_error(ctx, loc,
+ "fwd statement has no output expression");
+
+ switch (stmt->fwd.family) {
+ case AF_INET:
+ expr_set_type(addr, &ipaddr_type,
+ BYTEORDER_BIG_ENDIAN);
+ break;
+ case AF_INET6:
+ expr_set_type(addr, &ip6addr_type,
+ BYTEORDER_BIG_ENDIAN);
+ break;
+ default:
+ return netlink_error(ctx, loc,
+ "fwd statement has no family");
+ }
+ stmt->fwd.addr = addr;
+ }
}
ctx->stmt = stmt;
expr_postprocess(&rctx, &stmt->dup.dev);
break;
case STMT_FWD:
- if (stmt->fwd.to != NULL)
- expr_postprocess(&rctx, &stmt->fwd.to);
+ expr_postprocess(&rctx, &stmt->fwd.dev);
+ if (stmt->fwd.addr != NULL)
+ expr_postprocess(&rctx, &stmt->fwd.addr);
break;
case STMT_XT:
stmt_xt_postprocess(&rctx, stmt, rule);
static void netlink_gen_fwd_stmt(struct netlink_linearize_ctx *ctx,
const struct stmt *stmt)
{
- enum nft_registers sreg1;
+ enum nft_registers sreg1, sreg2;
struct nftnl_expr *nle;
nle = alloc_nft_expr("fwd");
- sreg1 = get_register(ctx, stmt->fwd.to);
- netlink_gen_expr(ctx, stmt->fwd.to, sreg1);
+ sreg1 = get_register(ctx, stmt->fwd.dev);
+ netlink_gen_expr(ctx, stmt->fwd.dev, sreg1);
netlink_put_register(nle, NFTNL_EXPR_FWD_SREG_DEV, sreg1);
- release_register(ctx, stmt->fwd.to);
+
+ if (stmt->fwd.addr != NULL) {
+ sreg2 = get_register(ctx, stmt->fwd.addr);
+ netlink_gen_expr(ctx, stmt->fwd.addr, sreg2);
+ netlink_put_register(nle, NFTNL_EXPR_FWD_SREG_ADDR, sreg2);
+ release_register(ctx, stmt->fwd.addr);
+ }
+ release_register(ctx, stmt->fwd.dev);
+
+ if (stmt->fwd.family)
+ nftnl_expr_set_u32(nle, NFTNL_EXPR_FWD_NFPROTO,
+ stmt->fwd.family);
nftnl_rule_add_expr(ctx->nlr, nle);
}
%destructor { expr_free($$); } rt_expr
%type <val> rt_key
+%type <val> fwd_key_proto
+
%type <expr> ct_expr
%destructor { expr_free($$); } ct_expr
%type <val> ct_key ct_dir ct_key_dir_optional ct_key_dir ct_key_proto_field
}
;
-fwd_stmt : FWD TO expr
+fwd_key_proto : IP { $$ = NFPROTO_IPV4; }
+ | IP6 { $$ = NFPROTO_IPV6; }
+ ;
+
+fwd_stmt : FWD TO stmt_expr
+ {
+ $$ = fwd_stmt_alloc(&@$);
+ $$->fwd.dev = $3;
+ }
+ | FWD fwd_key_proto TO stmt_expr DEVICE stmt_expr
{
$$ = fwd_stmt_alloc(&@$);
- $$->fwd.to = $3;
+ $$->fwd.family = $2;
+ $$->fwd.addr = $4;
+ $$->fwd.dev = $6;
}
;
{
struct stmt *stmt = fwd_stmt_alloc(int_loc);
- stmt->fwd.to = json_parse_expr(ctx, value);
+ stmt->fwd.dev = json_parse_expr(ctx, value);
return stmt;
}
return stmt_alloc(loc, &dup_stmt_ops);
}
+static const char * const nfproto_family_name_array[NFPROTO_NUMPROTO] = {
+ [NFPROTO_IPV4] = "ip",
+ [NFPROTO_IPV6] = "ip6",
+};
+
+static const char *nfproto_family_name(uint8_t nfproto)
+{
+ if (nfproto >= NFPROTO_NUMPROTO || !nfproto_family_name_array[nfproto])
+ return "unknown";
+
+ return nfproto_family_name_array[nfproto];
+}
+
static void fwd_stmt_print(const struct stmt *stmt, struct output_ctx *octx)
{
- nft_print(octx, "fwd to ");
- expr_print(stmt->fwd.to, octx);
+ if (stmt->fwd.addr) {
+ nft_print(octx, "fwd %s to ",
+ nfproto_family_name(stmt->fwd.family));
+ expr_print(stmt->fwd.addr, octx);
+ nft_print(octx, " device ");
+ expr_print(stmt->fwd.dev, octx);
+ } else {
+ nft_print(octx, "fwd to ");
+ expr_print(stmt->fwd.dev, octx);
+ }
}
static void fwd_stmt_destroy(struct stmt *stmt)
{
- expr_free(stmt->fwd.to);
+ expr_free(stmt->fwd.addr);
+ expr_free(stmt->fwd.dev);
}
static const struct stmt_ops fwd_stmt_ops = {