Show data numerically. When used once (the default behaviour), skip
lookup of addresses to symbolic names. Use twice to also show Internet
services (port numbers) numerically. Use three times to also show
- protocols and UIDs/GIDs numerically.
+ protocols, UIDs/GIDs and priorities numerically.
*-s*::
*--stateless*::
* arp family supports only *input* and *output* hooks, both in chains of type
*filter*.
-The *priority* parameter accepts a signed integer value which specifies the
-order in which chains with same *hook* value are traversed. The ordering is
-ascending, i.e. lower priority values have precedence over higher ones.
+The *priority* parameter accepts a signed integer value or a standard priority
+name which specifies the order in which chains with same *hook* value are
+traversed. The ordering is ascending, i.e. lower priority values have precedence
+over higher ones.
-Base chains also allow to set the chain's *policy*, i.e. what happens to packets
-not explicitly accepted or refused in contained rules. Supported policy values
-are *accept* (which is the default) or *drop*.
+Standard priority values can be replaced with easily memorizable names. Not all
+names make sense in every family with every hook (see the compatibility matrices
+below) but their numerical value can still be used for prioritizing chains.
+
+These names and values are defined and made available based on what priorities
+are used by xtables when registering their default chains.
+
+Most of the families use the same values, but bridge uses different ones from
+the others. See the following tables that describe the values and compatibility.
+
+.Standard priority names, family and hook compatibility matrix
+[options="header"]
+|==================
+| Name | Value | Families | Hooks
+| raw | -300 | ip, ip6, inet | all
+| mangle | -150 | ip, ip6, inet | all
+| dstnat | -100 | ip, ip6, inet | prerouting
+| filter | 0 | ip, ip6, inet, arp, netdev | all
+| security | 50 | ip, ip6, inet | all
+| srcnat | 100 | ip, ip6, inet | postrouting
+|===================
+
+.Standard priority names and hook compatibility for the bridge family
+[option="header"]
+|==================
+| Name | Value | Hooks
+| dstnat | -300 | prerouting
+| filter | -200 | all
+| out | 100 | output
+| srcnat | 300 | postrouting
+|==================
+
+Basic arithmetic expressions (addition and substraction) can also be achieved
+with these standard names to ease relative prioritizing, eg. *mangle - 5* stands
+for *-155*. Values will also be printed like this untill the value is not
+further than 10 form the standard value.
+
+Base chains also allow to set the chain's *policy*, i.e. what happens to
+packets not explicitly accepted or refused in contained rules. Supported policy
+values are *accept* (which is the default) or *drop*.
RULES
-----
address family is a dummy family which is used to create hybrid IPv4/IPv6
tables. When no address family is specified, ip is used by default.
+The *priority* can be a signed integer or *filter* which stands for 0. Addition
+and substraction can be used to set relative priority eg. filter + 5 equals to
+5.
+
[horizontal]
*add*:: Add a new flowtable for the given family with the given name.
*delete*:: Delete the specified flowtable.
#define NF_BR_BROUTING 5
#define NF_BR_NUMHOOKS 6
+#include <limits.h> /* for INT_MIN, INT_MAX */
+
+enum nf_br_hook_priorities {
+ NF_BR_PRI_FIRST = INT_MIN,
+ NF_BR_PRI_NAT_DST_BRIDGED = -300,
+ NF_BR_PRI_FILTER_BRIDGED = -200,
+ NF_BR_PRI_BRNF = 0,
+ NF_BR_PRI_NAT_DST_OTHER = 100,
+ NF_BR_PRI_FILTER_OTHER = 200,
+ NF_BR_PRI_NAT_SRC = 300,
+ NF_BR_PRI_LAST = INT_MAX,
+};
+
#endif
CHAIN_F_BASECHAIN = 0x1,
};
+/**
+ * struct prio_spec - extendend priority specification for mixed
+ * textual/numerical parsing.
+ *
+ * @str: name of the standard priority value
+ * @num: Numerical value. This MUST contain the parsed value of str after
+ * evaluation.
+ */
+struct prio_spec {
+ const char *str;
+ int num;
+};
+
/**
* struct chain - nftables chain
*
uint32_t flags;
const char *hookstr;
unsigned int hooknum;
- int priority;
+ struct prio_spec priority;
int policy;
const char *type;
const char *dev;
struct list_head rules;
};
+#define STD_PRIO_BUFSIZE 100
+extern int std_prio_lookup(const char *std_prio_name, int family, int hook);
extern const char *chain_type_name_lookup(const char *name);
extern const char *chain_hookname_lookup(const char *name);
extern struct chain *chain_alloc(const char *name);
struct location location;
const char * hookstr;
unsigned int hooknum;
- int priority;
+ struct prio_spec priority;
const char **dev_array;
struct expr *dev_expr;
int dev_array_len;
#include <linux/netfilter.h>
#include <linux/netfilter_arp.h>
#include <linux/netfilter/nf_tables.h>
+#include <linux/netfilter_ipv4.h>
#include <netinet/ip_icmp.h>
#include <netinet/icmp6.h>
#include <net/ethernet.h>
return 0;
}
+static bool evaluate_priority(struct prio_spec *prio, int family, int hook)
+{
+ int priority;
+
+ /* A numeric value has been used to specify priority. */
+ if (prio->str == NULL)
+ return true;
+
+ priority = std_prio_lookup(prio->str, family, hook);
+ if (priority == NF_IP_PRI_LAST)
+ return false;
+ prio->num += priority;
+
+ return true;
+}
+
static uint32_t str2hooknum(uint32_t family, const char *hook);
static int flowtable_evaluate(struct eval_ctx *ctx, struct flowtable *ft)
if (ft->hooknum == NF_INET_NUMHOOKS)
return chain_error(ctx, ft, "invalid hook %s", ft->hookstr);
+ if (!evaluate_priority(&ft->priority, NFPROTO_NETDEV, ft->hooknum))
+ return chain_error(ctx, ft, "'%s' is invalid priority.",
+ ft->priority.str);
+
if (!ft->dev_expr)
return chain_error(ctx, ft, "Unbound flowtable not allowed (must specify devices)");
if (chain->hooknum == NF_INET_NUMHOOKS)
return chain_error(ctx, chain, "invalid hook %s",
chain->hookstr);
+
+ if (!evaluate_priority(&chain->priority, chain->handle.family,
+ chain->hooknum))
+ return chain_error(ctx, chain,
+ "'%s' is invalid priority in this context.",
+ chain->priority.str);
}
list_for_each_entry(rule, &chain->rules, list) {
"type", chain->type,
"hook", hooknum2str(chain->handle.family,
chain->hooknum),
- "prio", chain->priority,
+ "prio", chain->priority.num,
"policy", chain_policy2str(chain->policy));
if (chain->dev)
json_object_set_new(tmp, "dev", json_string(chain->dev));
"name", ftable->handle.flowtable,
"table", ftable->handle.table.name,
"hook", hooknum2str(NFPROTO_NETDEV, ftable->hooknum),
- "prio", ftable->priority);
+ "prio", ftable->priority.num);
for (i = 0; i < ftable->dev_array_len; i++) {
const char *dev = ftable->dev_array[i];
nftnl_chain_set_u32(nlc, NFTNL_CHAIN_HOOKNUM,
chain->hooknum);
nftnl_chain_set_s32(nlc, NFTNL_CHAIN_PRIO,
- chain->priority);
+ chain->priority.num);
nftnl_chain_set_str(nlc, NFTNL_CHAIN_TYPE,
chain->type);
}
nftnl_chain_get_u32(nlc, NFTNL_CHAIN_HOOKNUM);
chain->hookstr =
hooknum2str(chain->handle.family, chain->hooknum);
- chain->priority =
+ chain->priority.num =
nftnl_chain_get_s32(nlc, NFTNL_CHAIN_PRIO);
chain->type =
xstrdup(nftnl_chain_get_str(nlc, NFTNL_CHAIN_TYPE));
flo = alloc_nftnl_flowtable(&cmd->handle, ft);
nftnl_flowtable_set_u32(flo, NFTNL_FLOWTABLE_HOOKNUM, ft->hooknum);
- nftnl_flowtable_set_u32(flo, NFTNL_FLOWTABLE_PRIO, ft->priority);
+ nftnl_flowtable_set_u32(flo, NFTNL_FLOWTABLE_PRIO, ft->priority.num);
list_for_each_entry(expr, &ft->dev_expr->expressions, list)
dev_array[i++] = expr->identifier;
flowtable->dev_array_len = len;
- flowtable->priority =
+ flowtable->priority.num =
nftnl_flowtable_get_u32(nlo, NFTNL_FLOWTABLE_PRIO);
flowtable->hooknum =
nftnl_flowtable_get_u32(nlo, NFTNL_FLOWTABLE_HOOKNUM);
const struct datatype *datatype;
struct handle_spec handle_spec;
struct position_spec position_spec;
+ struct prio_spec prio_spec;
const struct exthdr_desc *exthdr_desc;
}
%token AT "@"
%token VMAP "vmap"
+%token PLUS "+"
+
%token INCLUDE "include"
%token DEFINE "define"
%token REDEFINE "redefine"
%type <handle> set_spec setid_spec set_identifier flowtable_identifier obj_spec objid_spec obj_identifier
%destructor { handle_free(&$$); } set_spec setid_spec set_identifier obj_spec objid_spec obj_identifier
%type <val> family_spec family_spec_explicit chain_policy prio_spec
+%type <prio_spec> extended_prio_spec
%type <string> dev_spec quota_unit
%destructor { xfree($$); } dev_spec quota_unit
flowtable_block : /* empty */ { $$ = $<flowtable>-1; }
| flowtable_block common_block
| flowtable_block stmt_separator
- | flowtable_block HOOK STRING PRIORITY prio_spec stmt_separator
+ | flowtable_block HOOK STRING PRIORITY extended_prio_spec stmt_separator
{
$$->hookstr = chain_hookname_lookup($3);
if ($$->hookstr == NULL) {
| CLASSID { $$ = xstrdup("classid"); }
;
-hook_spec : TYPE STRING HOOK STRING dev_spec PRIORITY prio_spec
+hook_spec : TYPE STRING HOOK STRING dev_spec PRIORITY extended_prio_spec
{
const char *chain_type = chain_type_name_lookup($2);
}
;
+extended_prio_spec : prio_spec
+ {
+ struct prio_spec spec = {0};
+ spec.num = $1;
+ $$ = spec;
+ }
+ | STRING
+ {
+ struct prio_spec spec = {0};
+ spec.str = $1;
+ $$ = spec;
+ }
+ | STRING PLUS NUM
+ {
+ struct prio_spec spec = {0};
+ spec.num = $3;
+ spec.str = $1;
+ $$ = spec;
+ }
+ | STRING DASH NUM
+ {
+ struct prio_spec spec = {0};
+ spec.num = -$3;
+ spec.str = $1;
+ $$ = spec;
+ }
+ ;
+
prio_spec : NUM { $$ = $1; }
| DASH NUM { $$ = -$2; }
;
flowtable = flowtable_alloc(int_loc);
flowtable->hookstr = hookstr;
- flowtable->priority = prio;
+ flowtable->priority.num = prio;
flowtable->dev_expr = json_parse_flowtable_devs(ctx, devs);
if (!flowtable->dev_expr) {
#include <netinet/ip.h>
#include <linux/netfilter.h>
#include <linux/netfilter_arp.h>
+#include <linux/netfilter_ipv4.h>
+#include <linux/netfilter_bridge.h>
void handle_free(struct handle *h)
{
xfree(chain->type);
if (chain->dev != NULL)
xfree(chain->dev);
+ xfree(chain->priority.str);
xfree(chain);
}
return "unknown";
}
+struct prio_tag {
+ int val;
+ const char *str;
+};
+
+const static struct prio_tag std_prios[] = {
+ { NF_IP_PRI_RAW, "raw" },
+ { NF_IP_PRI_MANGLE, "mangle" },
+ { NF_IP_PRI_NAT_DST, "dstnat" },
+ { NF_IP_PRI_FILTER, "filter" },
+ { NF_IP_PRI_SECURITY, "security" },
+ { NF_IP_PRI_NAT_SRC, "srcnat" },
+};
+
+const static struct prio_tag bridge_std_prios[] = {
+ { NF_BR_PRI_NAT_DST_BRIDGED, "dstnat" },
+ { NF_BR_PRI_FILTER_BRIDGED, "filter" },
+ { NF_BR_PRI_NAT_DST_OTHER, "out" },
+ { NF_BR_PRI_NAT_SRC, "srcnat" },
+};
+
+static bool std_prio_family_hook_compat(int prio, int family, int hook)
+{
+ /* bridge family has different values */
+ if (family == NFPROTO_BRIDGE) {
+ switch (prio) {
+ case NF_BR_PRI_NAT_DST_BRIDGED:
+ if (hook == NF_BR_PRE_ROUTING)
+ return true;
+ break;
+ case NF_BR_PRI_FILTER_BRIDGED:
+ return true;
+ case NF_BR_PRI_NAT_DST_OTHER:
+ if (hook == NF_BR_LOCAL_OUT)
+ return true;
+ break;
+ case NF_BR_PRI_NAT_SRC:
+ if (hook == NF_BR_POST_ROUTING)
+ return true;
+ }
+ return false;
+ }
+ switch(prio) {
+ case NF_IP_PRI_FILTER:
+ switch (family) {
+ case NFPROTO_INET:
+ case NFPROTO_IPV4:
+ case NFPROTO_IPV6:
+ case NFPROTO_ARP:
+ case NFPROTO_NETDEV:
+ return true;
+ }
+ break;
+ case NF_IP_PRI_RAW:
+ case NF_IP_PRI_MANGLE:
+ case NF_IP_PRI_SECURITY:
+ switch (family) {
+ case NFPROTO_INET:
+ case NFPROTO_IPV4:
+ case NFPROTO_IPV6:
+ return true;
+ }
+ break;
+ case NF_IP_PRI_NAT_DST:
+ switch(family) {
+ case NFPROTO_INET:
+ case NFPROTO_IPV4:
+ case NFPROTO_IPV6:
+ if (hook == NF_INET_PRE_ROUTING)
+ return true;
+ }
+ break;
+ case NF_IP_PRI_NAT_SRC:
+ switch(family) {
+ case NFPROTO_INET:
+ case NFPROTO_IPV4:
+ case NFPROTO_IPV6:
+ if (hook == NF_INET_POST_ROUTING)
+ return true;
+ }
+ }
+ return false;
+}
+
+int std_prio_lookup(const char *std_prio_name, int family, int hook)
+{
+ const struct prio_tag *prio_arr;
+ size_t i, arr_size;
+
+ if (family == NFPROTO_BRIDGE) {
+ prio_arr = bridge_std_prios;
+ arr_size = array_size(bridge_std_prios);
+ } else {
+ prio_arr = std_prios;
+ arr_size = array_size(std_prios);
+ }
+
+ for (i = 0; i < arr_size; ++i) {
+ if (strcmp(prio_arr[i].str, std_prio_name) == 0 &&
+ std_prio_family_hook_compat(prio_arr[i].val, family, hook))
+ return prio_arr[i].val;
+ }
+ return NF_IP_PRI_LAST;
+}
+
+static const char *prio2str(char *buf, size_t bufsize, int family, int hook,
+ int prio, int numeric)
+{
+ const struct prio_tag *prio_arr;
+ const char *std_prio_str;
+ const int reach = 10;
+ int std_prio, offset;
+ size_t i, arr_size;
+
+ if (family == NFPROTO_BRIDGE) {
+ prio_arr = bridge_std_prios;
+ arr_size = array_size(bridge_std_prios);
+ } else {
+ prio_arr = std_prios;
+ arr_size = array_size(std_prios);
+ }
+
+ if (numeric != NFT_NUMERIC_ALL) {
+ for (i = 0; i < arr_size; ++i) {
+ std_prio = prio_arr[i].val;
+ std_prio_str = prio_arr[i].str;
+ if (abs(prio - std_prio) <= reach) {
+ if (!std_prio_family_hook_compat(std_prio,
+ family, hook))
+ break;
+ offset = prio - std_prio;
+ strncpy(buf, std_prio_str, bufsize);
+ if (offset > 0)
+ snprintf(buf + strlen(buf),
+ bufsize - strlen(buf), " + %d",
+ offset);
+ else if (offset < 0)
+ snprintf(buf + strlen(buf),
+ bufsize - strlen(buf), " - %d",
+ -offset);
+ return buf;
+ }
+ }
+ }
+ snprintf(buf, bufsize, "%d", prio);
+ return buf;
+}
+
static void chain_print_declaration(const struct chain *chain,
struct output_ctx *octx)
{
+ char priobuf[STD_PRIO_BUFSIZE];
+
nft_print(octx, "\tchain %s {", chain->handle.chain.name);
if (octx->handle > 0)
nft_print(octx, " # handle %" PRIu64, chain->handle.handle.id);
hooknum2str(chain->handle.family, chain->hooknum));
if (chain->dev != NULL)
nft_print(octx, " device %s", chain->dev);
- nft_print(octx, " priority %d; policy %s;\n",
- chain->priority, chain_policy2str(chain->policy));
+ nft_print(octx, " priority %s; policy %s;\n",
+ prio2str(priobuf, sizeof(priobuf),
+ chain->handle.family, chain->hooknum,
+ chain->priority.num, octx->numeric),
+ chain_policy2str(chain->policy));
}
}
void chain_print_plain(const struct chain *chain, struct output_ctx *octx)
{
+ char priobuf[STD_PRIO_BUFSIZE];
+
nft_print(octx, "chain %s %s %s", family2str(chain->handle.family),
chain->handle.table.name, chain->handle.chain.name);
if (chain->flags & CHAIN_F_BASECHAIN) {
- nft_print(octx, " { type %s hook %s priority %d; policy %s; }",
+ nft_print(octx, " { type %s hook %s priority %s; policy %s; }",
chain->type, chain->hookstr,
- chain->priority, chain_policy2str(chain->policy));
+ prio2str(priobuf, sizeof(priobuf),
+ chain->handle.family, chain->hooknum,
+ chain->priority.num, octx->numeric),
+ chain_policy2str(chain->policy));
}
if (octx->handle > 0)
nft_print(octx, " # handle %" PRIu64, chain->handle.handle.id);
if (--flowtable->refcnt > 0)
return;
handle_free(&flowtable->handle);
+ xfree(flowtable->priority.str);
xfree(flowtable);
}
struct print_fmt_options *opts,
struct output_ctx *octx)
{
+ char priobuf[STD_PRIO_BUFSIZE];
int i;
nft_print(octx, "%sflowtable", opts->tab);
nft_print(octx, " %s {%s", flowtable->handle.flowtable, opts->nl);
- nft_print(octx, "%s%shook %s priority %d%s",
+ nft_print(octx, "%s%shook %s priority %s%s",
opts->tab, opts->tab,
hooknum2str(NFPROTO_NETDEV, flowtable->hooknum),
- flowtable->priority, opts->stmt_separator);
+ prio2str(priobuf, sizeof(priobuf), NFPROTO_NETDEV,
+ flowtable->hooknum, flowtable->priority.num,
+ octx->numeric),
+ opts->stmt_separator);
nft_print(octx, "%s%sdevices = { ", opts->tab, opts->tab);
for (i = 0; i < flowtable->dev_array_len; i++) {
"=" { return '='; }
"vmap" { return VMAP; }
+"+" { return PLUS; }
+
"include" { return INCLUDE; }
"define" { return DEFINE; }
"redefine" { return REDEFINE; }
table ip t {
chain c1 {
- type nat hook postrouting priority 0; policy accept;
+ type nat hook postrouting priority filter; policy accept;
masquerade
}
}
table inet t {
flowtable f {
- hook ingress priority 10
+ hook ingress priority filter + 10
devices = { lo }
}
table inet filter {
chain ssh {
- type filter hook input priority 0; policy accept;
+ type filter hook input priority filter; policy accept;
tcp dport 22 accept
}
chain input {
- type filter hook input priority 1; policy accept;
+ type filter hook input priority filter + 1; policy accept;
}
}
}
chain y {
- type filter hook input priority 0; policy accept;
+ type filter hook input priority filter; policy accept;
counter name ip saddr map { 1.1.1.1 : "user123", 2.2.2.2 : "user123", 192.168.2.2 : "user123" }
quota name ip saddr map @test drop
}
table ip t {
chain c {
- type filter hook output priority 0; policy accept;
+ type filter hook output priority filter; policy accept;
ip daddr { 192.168.0.1, 192.168.0.2, 192.168.0.3 }
tcp dport { 22, 23 } counter packets 0 bytes 0
}
}
chain input {
- type filter hook input priority 0; policy accept;
+ type filter hook input priority filter; policy accept;
limit name tcp dport map { 80 : "http-traffic", 443 : "http-traffic" }
}
}
}
chain FORWARD {
- type filter hook forward priority 0; policy accept;
+ type filter hook forward priority filter; policy accept;
goto client_to_any
}
table ip x {
chain y {
- type filter hook input priority 0; policy drop;
+ type filter hook input priority filter; policy drop;
}
}
table ip w {
chain y {
- type filter hook output priority 0; policy accept;
+ type filter hook output priority filter; policy accept;
}
}
table ip w {
chain y {
- type filter hook output priority 0; policy accept;
+ type filter hook output priority filter; policy accept;
}
}
}
chain FORWARD {
- type filter hook forward priority 0; policy accept;
+ type filter hook forward priority filter; policy accept;
goto client_to_any
}