[v5,nft] Set/print standard chain prios with textual names

Message ID 20180803085531.24207-1-ecklm94@gmail.com
State Accepted
Delegated to: Pablo Neira
Headers show
Series
  • [v5,nft] Set/print standard chain prios with textual names
Related show

Commit Message

Máté Eckl Aug. 3, 2018, 8:55 a.m.
This patch adds the possibility to use textual names to set the chain priority
to standard values so that numeric values do not need to be learnt any more for
basic usage.

Basic arithmetic can also be done with them to ease the addition of
relatively higher/lower priority chains.
Addition and substraction is possible.

Values are also printed with their friendly name within the range of
<basicprio> +- 10.

Also numeric printing is supported in case of -nnn option
(numeric == NFT_NUMERIC_ALL)

The supported name-value pairs and where they are valid is based on how
x_tables use these values when registering their base chains. (See
iptables/nft.c in the iptables repository).

Also see the compatibility matrices extracted from the man page:

       Standard priority names, family and hook compatibility matrix
       ┌─────────┬───────┬────────────────┬─────────────┐
       │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, │ all         │
       │         │       │ arp, netdev    │             │
       ├─────────┼───────┼────────────────┼─────────────┤
       │         │       │                │             │
       │security │ 50    │ ip, ip6, inet  │ all         │
       ├─────────┼───────┼────────────────┼─────────────┤
       │         │       │                │             │
       │srcnat   │ 100   │ ip, ip6, inet  │ postrouting │
       └─────────┴───────┴────────────────┴─────────────┘

       Standard priority names and hook compatibility for the bridge family
       ┌───────┬───────┬─────────────┐
       │       │       │             │
       │Name   │ Value │ Hooks       │
       ├───────┼───────┼─────────────┤
       │       │       │             │
       │dstnat │ -300  │ prerouting  │
       ├───────┼───────┼─────────────┤
       │       │       │             │
       │filter │ -200  │ all         │
       ├───────┼───────┼─────────────┤
       │       │       │             │
       │out    │ 100   │ output      │
       ├───────┼───────┼─────────────┤
       │       │       │             │
       │srcnat │ 300   │ postrouting │
       └───────┴───────┴─────────────┘

This can be also applied for flowtables wher it works as a netdev family
chain.

Example:
nft> add table ip x
nft> add chain ip x y { type filter hook prerouting priority raw; }
nft> add chain ip x z { type filter hook prerouting priority mangle + 1; }
nft> add chain ip x w { type filter hook prerouting priority dstnat - 5; }
nft> add chain ip x r { type filter hook prerouting priority filter + 10; }
nft> add chain ip x t { type filter hook prerouting priority security; }
nft> add chain ip x q { type filter hook postrouting priority srcnat + 11; }
nft> add chain ip x h { type filter hook prerouting priority 15; }
nft>
nft> add flowtable ip x y { hook ingress priority filter + 5 ; devices = {enp0s31f6}; }
nft>
nft> add table arp x
nft> add chain arp x y { type filter hook input priority filter + 5; }
nft>
nft> add table bridge x
nft> add chain bridge x y { type filter hook input priority filter + 9; }
nft> add chain bridge x z { type filter hook prerouting priority dstnat; }
nft> add chain bridge x q { type filter hook postrouting priority srcnat; }
nft> add chain bridge x k { type filter hook output priority out; }
nft>
nft> list ruleset
table ip x {
	flowtable y {
		hook ingress priority filter + 5
		devices = { enp0s31f6 }
	}

	chain y {
		type filter hook prerouting priority raw; policy accept;
	}

	chain z {
		type filter hook prerouting priority mangle + 1; policy accept;
	}

	chain w {
		type filter hook prerouting priority dstnat - 5; policy accept;
	}

	chain r {
		type filter hook prerouting priority filter + 10; policy accept;
	}

	chain t {
		type filter hook prerouting priority security; policy accept;
	}

	chain q {
		type filter hook postrouting priority 111; policy accept;
	}

	chain h {
		type filter hook prerouting priority 15; policy accept;
	}
}
table arp x {
	chain y {
		type filter hook input priority filter + 5; policy accept;
	}
}
table bridge x {
	chain y {
		type filter hook input priority filter + 9; policy accept;
	}

	chain z {
		type filter hook prerouting priority dstnat; policy accept;
	}

	chain q {
		type filter hook postrouting priority srcnat; policy accept;
	}

	chain k {
		type filter hook output priority out; policy accept;
	}
}
nft> # Everything should fail after this
nft> add chain ip x h { type filter hook prerouting priority first; }
Error: 'first' is invalid priority in this context.
add chain ip x h { type filter hook prerouting priority first; }
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
nft> add chain ip x q { type filter hook prerouting priority srcnat + 11; }
Error: 'srcnat' is invalid priority in this context.
add chain ip x q { type filter hook prerouting priority srcnat + 11; }
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
nft> add chain arp x y { type filter hook input priority raw; }
Error: 'raw' is invalid priority in this context.
add chain arp x y { type filter hook input priority raw; }
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
nft> add flowtable ip x y { hook ingress priority magle; devices = {enp0s31f6}; }
Error: 'magle' is invalid priority.
add flowtable ip x y { hook ingress priority magle; devices = {enp0s31f6}; }
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
nft> add chain bridge x r { type filter hook postrouting priority dstnat; }
Error: 'dstnat' is invalid priority in this context.
add chain bridge x r { type filter hook postrouting priority dstnat; }
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
nft> add chain bridge x t { type filter hook prerouting priority srcnat; }
Error: 'srcnat' is invalid priority in this context.
add chain bridge x t { type filter hook prerouting priority srcnat; }
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Signed-off-by: Máté Eckl <ecklm94@gmail.com>
---
v2:
 - more comprehensive names
 - expose basic priorities used by iptables
 - use arithmetics with new names (+-)
 - print friendly names with arithmetics with an epsilon of 10

v3:
 - no tokens are used for priority names, lookup is used instead
 - names and values are moved out to a structure
 - the helper function became unnecessary, thus I removed it

v4:
 - fix snat and dnat conflict with existing tokens
 - remove static char array from chain_prio2str
 - make numerical priority printing available via -nnn nft flag
 - add docs about priority names
 - check compatibility of standard prio names and table family
 - handle flowtables

v5:
 - Add hook compatibility check
 - make priority necessary only in base chains (in evaluate)
 - doc updated
 - Add asciidoc man part
 - Make bridge priorities work
 - Refactor compatibility function
 - Store string + offset instead of instead of building and resplitting
   in eval

 doc/nft.txt                      |  56 ++++++++--
 include/linux/netfilter_bridge.h |  13 +++
 include/rule.h                   |  19 +++-
 src/evaluate.c                   |  27 +++++
 src/json.c                       |   4 +-
 src/netlink.c                    |   8 +-
 src/parser_bison.y               |  36 ++++++-
 src/parser_json.c                |   2 +-
 src/rule.c                       | 180 +++++++++++++++++++++++++++++--
 src/scanner.l                    |   2 +
 10 files changed, 323 insertions(+), 24 deletions(-)

Comments

Pablo Neira Ayuso Aug. 14, 2018, 1:26 p.m. | #1
On Fri, Aug 03, 2018 at 10:55:33AM +0200, Máté Eckl wrote:
> This patch adds the possibility to use textual names to set the chain priority
> to standard values so that numeric values do not need to be learnt any more for
> basic usage.
> 
[...]
> Example:
> nft> add table ip x
> nft> add chain ip x y { type filter hook prerouting priority raw; }
> nft> add chain ip x z { type filter hook prerouting priority mangle + 1; }
> nft> add chain ip x w { type filter hook prerouting priority dstnat - 5; }
> nft> add chain ip x r { type filter hook prerouting priority filter + 10; }
> nft> add chain ip x t { type filter hook prerouting priority security; }
> nft> add chain ip x q { type filter hook postrouting priority srcnat + 11; }
> nft> add chain ip x h { type filter hook prerouting priority 15; }

Applied, thanks Máté. BTW, can we have automated tests for this?

[...]
> nft> add chain ip x h { type filter hook prerouting priority first; }
> Error: 'first' is invalid priority in this context.
> add chain ip x h { type filter hook prerouting priority first; }
>                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

BTW, now that you have added a prio_spec object, you can place the
struct location there and store the position from the parser, so we
can perform better error reporting, eg.

nft> add chain ip x h { type filter hook prerouting priority first; }
Error: 'first' is invalid priority in this context.
add chain ip x h { type filter hook prerouting priority first; }
                                               ^^^^^^^^^^^^^^

Thanks.
Máté Eckl Aug. 14, 2018, 4:03 p.m. | #2
On Tue, Aug 14, 2018 at 03:26:16PM +0200, Pablo Neira Ayuso wrote:
> On Fri, Aug 03, 2018 at 10:55:33AM +0200, Máté Eckl wrote:
> > This patch adds the possibility to use textual names to set the chain priority
> > to standard values so that numeric values do not need to be learnt any more for
> > basic usage.
> > 
> [...]
> > Example:
> > nft> add table ip x
> > nft> add chain ip x y { type filter hook prerouting priority raw; }
> > nft> add chain ip x z { type filter hook prerouting priority mangle + 1; }
> > nft> add chain ip x w { type filter hook prerouting priority dstnat - 5; }
> > nft> add chain ip x r { type filter hook prerouting priority filter + 10; }
> > nft> add chain ip x t { type filter hook prerouting priority security; }
> > nft> add chain ip x q { type filter hook postrouting priority srcnat + 11; }
> > nft> add chain ip x h { type filter hook prerouting priority 15; }
> 
> Applied, thanks Máté. BTW, can we have automated tests for this?

I will make them.

> 
> [...]
> > nft> add chain ip x h { type filter hook prerouting priority first; }
> > Error: 'first' is invalid priority in this context.
> > add chain ip x h { type filter hook prerouting priority first; }
> >                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 
> BTW, now that you have added a prio_spec object, you can place the
> struct location there and store the position from the parser, so we
> can perform better error reporting, eg.
> 
> nft> add chain ip x h { type filter hook prerouting priority first; }
> Error: 'first' is invalid priority in this context.
> add chain ip x h { type filter hook prerouting priority first; }
>                                                ^^^^^^^^^^^^^^

Thanks for the idea, I'll try it out next week.

> 
> Thanks.

Patch

diff --git a/doc/nft.txt b/doc/nft.txt
index d33d01c..4f42c39 100644
--- a/doc/nft.txt
+++ b/doc/nft.txt
@@ -37,7 +37,7 @@  For a full summary of options, run *nft --help*.
 	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*::
@@ -345,13 +345,51 @@  further quirks worth noticing:
 * 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
 -----
@@ -543,6 +581,10 @@  family and their name. The address family must be one of ip, ip6, inet. The inet
 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.
diff --git a/include/linux/netfilter_bridge.h b/include/linux/netfilter_bridge.h
index 5094ecc..6187a55 100644
--- a/include/linux/netfilter_bridge.h
+++ b/include/linux/netfilter_bridge.h
@@ -24,4 +24,17 @@ 
 #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
diff --git a/include/rule.h b/include/rule.h
index 909ff36..d564cb0 100644
--- a/include/rule.h
+++ b/include/rule.h
@@ -161,6 +161,19 @@  enum chain_flags {
 	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
  *
@@ -185,7 +198,7 @@  struct chain {
 	uint32_t		flags;
 	const char		*hookstr;
 	unsigned int		hooknum;
-	int			priority;
+	struct prio_spec	priority;
 	int			policy;
 	const char		*type;
 	const char		*dev;
@@ -193,6 +206,8 @@  struct chain {
 	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);
@@ -357,7 +372,7 @@  struct flowtable {
 	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;
diff --git a/src/evaluate.c b/src/evaluate.c
index 61cdff0..217ded5 100644
--- a/src/evaluate.c
+++ b/src/evaluate.c
@@ -17,6 +17,7 @@ 
 #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>
@@ -2873,6 +2874,21 @@  static int set_evaluate(struct eval_ctx *ctx, struct set *set)
 	return 0;
 }
 
+static bool parse_evaluate_priority(struct prio_spec *prio, int family, int hook)
+{
+	int priority;
+
+	if (prio->str == NULL)
+		/* only a numper is used to specify priority */
+		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)
@@ -2889,6 +2905,10 @@  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 (!parse_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)");
 
@@ -3041,6 +3061,13 @@  static int chain_evaluate(struct eval_ctx *ctx, struct chain *chain)
 		if (chain->hooknum == NF_INET_NUMHOOKS)
 			return chain_error(ctx, chain, "invalid hook %s",
 					   chain->hookstr);
+
+		if (!parse_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) {
diff --git a/src/json.c b/src/json.c
index c1cd0fb..8a3e15e 100644
--- a/src/json.c
+++ b/src/json.c
@@ -214,7 +214,7 @@  static json_t *chain_print_json(const struct output_ctx *octx,
 				"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));
@@ -315,7 +315,7 @@  static json_t *flowtable_print_json(const struct flowtable *ftable)
 			"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];
diff --git a/src/netlink.c b/src/netlink.c
index 394af2f..bd47279 100644
--- a/src/netlink.c
+++ b/src/netlink.c
@@ -597,7 +597,7 @@  int netlink_add_chain_batch(struct netlink_ctx *ctx, const struct cmd *cmd,
 			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);
 		}
@@ -666,7 +666,7 @@  struct chain *netlink_delinearize_chain(struct netlink_ctx *ctx,
 			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));
@@ -1495,7 +1495,7 @@  int netlink_add_flowtable(struct netlink_ctx *ctx, const struct cmd *cmd,
 
 	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;
@@ -1598,7 +1598,7 @@  netlink_delinearize_flowtable(struct netlink_ctx *ctx,
 
 	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);
diff --git a/src/parser_bison.y b/src/parser_bison.y
index 98bfeba..b6b8d29 100644
--- a/src/parser_bison.y
+++ b/src/parser_bison.y
@@ -153,6 +153,7 @@  int nft_lex(void *, void *, void *);
 	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;
 }
 
@@ -182,6 +183,8 @@  int nft_lex(void *, void *, void *);
 %token AT			"@"
 %token VMAP			"vmap"
 
+%token PLUS			"+"
+
 %token INCLUDE			"include"
 %token DEFINE			"define"
 %token REDEFINE			"redefine"
@@ -522,6 +525,7 @@  int nft_lex(void *, void *, void *);
 %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
@@ -1633,7 +1637,7 @@  flowtable_block_alloc	:	/* empty */
 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) {
@@ -1766,7 +1770,7 @@  type_identifier		:	STRING	{ $$ = $1; }
 			|	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);
 
@@ -1794,6 +1798,34 @@  hook_spec		:	TYPE		STRING		HOOK		STRING		dev_spec	PRIORITY	prio_spec
 			}
 			;
 
+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; }
 			;
diff --git a/src/parser_json.c b/src/parser_json.c
index 8f29aaf..630b75a 100644
--- a/src/parser_json.c
+++ b/src/parser_json.c
@@ -2593,7 +2593,7 @@  static struct cmd *json_parse_cmd_add_flowtable(struct json_ctx *ctx,
 
 	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) {
diff --git a/src/rule.c b/src/rule.c
index 7a7ac73..1b2890d 100644
--- a/src/rule.c
+++ b/src/rule.c
@@ -28,6 +28,8 @@ 
 #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)
 {
@@ -675,6 +677,7 @@  void chain_free(struct chain *chain)
 	xfree(chain->type);
 	if (chain->dev != NULL)
 		xfree(chain->dev);
+	xfree(chain->priority.str);
 	xfree(chain);
 }
 
@@ -773,9 +776,161 @@  const char *chain_policy2str(uint32_t policy)
 	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);
@@ -785,8 +940,11 @@  static void chain_print_declaration(const struct chain *chain,
 			  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));
 	}
 }
 
@@ -806,13 +964,18 @@  static void chain_print(const struct chain *chain, struct output_ctx *octx)
 
 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);
@@ -1638,6 +1801,7 @@  void flowtable_free(struct flowtable *flowtable)
 	if (--flowtable->refcnt > 0)
 		return;
 	handle_free(&flowtable->handle);
+	xfree(flowtable->priority.str);
 	xfree(flowtable);
 }
 
@@ -1650,6 +1814,7 @@  static void flowtable_print_declaration(const struct flowtable *flowtable,
 					struct print_fmt_options *opts,
 					struct output_ctx *octx)
 {
+	char priobuf[STD_PRIO_BUFSIZE];
 	int i;
 
 	nft_print(octx, "%sflowtable", opts->tab);
@@ -1662,10 +1827,13 @@  static void flowtable_print_declaration(const struct flowtable *flowtable,
 
 	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++) {
diff --git a/src/scanner.l b/src/scanner.l
index ed01b5e..4fb3a39 100644
--- a/src/scanner.l
+++ b/src/scanner.l
@@ -232,6 +232,8 @@  addrstring	({macaddr}|{ip4addr}|{ip6addr})
 "="			{ return '='; }
 "vmap"			{ return VMAP; }
 
+"+" 		{ return PLUS; }
+
 "include"		{ return INCLUDE; }
 "define"		{ return DEFINE; }
 "redefine"		{ return REDEFINE; }