Blob Blame History Raw
/*
 * Copyright (c) 2007-2012 Patrick McHardy <kaber@trash.net>
 *
 * 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.
 *
 * Development of this code funded by Astaro AG (http://www.astaro.com/)
 */

%{

#include <stddef.h>
#include <stdio.h>
#include <inttypes.h>
#include <syslog.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <netinet/if_ether.h>
#include <linux/netfilter.h>
#include <linux/netfilter/nf_tables.h>
#include <linux/netfilter/nf_conntrack_tuple_common.h>
#include <linux/netfilter/nf_nat.h>
#include <linux/netfilter/nf_log.h>
#include <linux/netfilter/nfnetlink_osf.h>
#include <linux/netfilter/nf_synproxy.h>
#include <linux/xfrm.h>
#include <netinet/ip_icmp.h>
#include <netinet/icmp6.h>
#include <libnftnl/common.h>
#include <libnftnl/set.h>
#include <libnftnl/udata.h>

#include <rule.h>
#include <statement.h>
#include <expression.h>
#include <headers.h>
#include <utils.h>
#include <parser.h>
#include <erec.h>

#include "parser_bison.h"

void parser_init(struct nft_ctx *nft, struct parser_state *state,
		 struct list_head *msgs, struct list_head *cmds,
		 struct scope *top_scope)
{
	memset(state, 0, sizeof(*state));
	state->msgs = msgs;
	state->cmds = cmds;
	state->scopes[0] = scope_init(top_scope, NULL);
	init_list_head(&state->indesc_list);
}

static void yyerror(struct location *loc, struct nft_ctx *nft, void *scanner,
		    struct parser_state *state, const char *s)
{
	erec_queue(error(loc, "%s", s), state->msgs);
}

static struct scope *current_scope(const struct parser_state *state)
{
	return state->scopes[state->scope];
}

static void open_scope(struct parser_state *state, struct scope *scope)
{
	assert(state->scope < array_size(state->scopes) - 1);
	scope_init(scope, current_scope(state));
	state->scopes[++state->scope] = scope;
}

static void close_scope(struct parser_state *state)
{
	assert(state->scope > 0);
	state->scope--;
}

static void location_init(void *scanner, struct parser_state *state,
			  struct location *loc)
{
	memset(loc, 0, sizeof(*loc));
	loc->indesc = state->indesc;
}

static void location_update(struct location *loc, struct location *rhs, int n)
{
	if (n) {
		loc->indesc       = rhs[n].indesc;
		loc->token_offset = rhs[1].token_offset;
		loc->line_offset  = rhs[1].line_offset;
		loc->first_line   = rhs[1].first_line;
		loc->first_column = rhs[1].first_column;
		loc->last_line    = rhs[n].last_line;
		loc->last_column  = rhs[n].last_column;
	} else {
		loc->indesc       = rhs[0].indesc;
		loc->token_offset = rhs[0].token_offset;
		loc->line_offset  = rhs[0].line_offset;
		loc->first_line   = loc->last_line   = rhs[0].last_line;
		loc->first_column = loc->last_column = rhs[0].last_column;
	}
}

static struct expr *handle_concat_expr(const struct location *loc,
					 struct expr *expr,
					 struct expr *expr_l, struct expr *expr_r,
					 struct location loc_rhs[3])
{
	if (expr->etype != EXPR_CONCAT) {
		expr = concat_expr_alloc(loc);
		compound_expr_add(expr, expr_l);
	} else {
		location_update(&expr_r->location, loc_rhs, 2);

		expr = expr_l;
		expr->location = *loc;
	}

	compound_expr_add(expr, expr_r);
	return expr;
}

#define YYLLOC_DEFAULT(Current, Rhs, N)	location_update(&Current, Rhs, N)

#define symbol_value(loc, str) \
	symbol_expr_alloc(loc, SYMBOL_VALUE, current_scope(state), str)

/* Declare those here to avoid compiler warnings */
void nft_set_debug(int, void *);
int nft_lex(void *, void *, void *);
%}

/* Declaration section */

%name-prefix "nft_"
%debug
%define api.pure
%parse-param		{ struct nft_ctx *nft }
%parse-param		{ void *scanner }
%parse-param		{ struct parser_state *state }
%lex-param		{ scanner }
%define parse.error verbose
%locations

%initial-action {
	location_init(scanner, state, &yylloc);
	if (nft->debug_mask & NFT_DEBUG_SCANNER)
		nft_set_debug(1, scanner);
	if (nft->debug_mask & NFT_DEBUG_PARSER)
		yydebug = 1;
}

%union {
	uint64_t		val;
	uint32_t		val32;
	uint8_t			val8;
	const char *		string;

	struct list_head	*list;
	struct cmd		*cmd;
	struct handle		handle;
	struct table		*table;
	struct chain		*chain;
	struct rule		*rule;
	struct stmt		*stmt;
	struct expr		*expr;
	struct set		*set;
	struct obj		*obj;
	struct flowtable	*flowtable;
	struct ct		*ct;
	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 TOKEN_EOF 0		"end of file"
%token JUNK			"junk"

%token NEWLINE			"newline"
%token COLON			"colon"
%token SEMICOLON		"semicolon"
%token COMMA			"comma"
%token DOT			"."

%token EQ			"=="
%token NEQ			"!="
%token LT			"<"
%token GT			">"
%token GTE			">="
%token LTE			"<="
%token LSHIFT			"<<"
%token RSHIFT			">>"
%token AMPERSAND		"&"
%token CARET			"^"
%token NOT			"!"
%token SLASH			"/"
%token ASTERISK			"*"
%token DASH			"-"
%token AT			"@"
%token VMAP			"vmap"

%token PLUS			"+"

%token INCLUDE			"include"
%token DEFINE			"define"
%token REDEFINE			"redefine"
%token UNDEFINE			"undefine"

%token FIB			"fib"

%token SOCKET			"socket"
%token TRANSPARENT		"transparent"

%token TPROXY			"tproxy"

%token OSF			"osf"

%token SYNPROXY			"synproxy"
%token MSS			"mss"
%token WSCALE			"wscale"
%token SACKPERM			"sack-perm"

%token HOOK			"hook"
%token DEVICE			"device"
%token DEVICES			"devices"
%token TABLE			"table"
%token TABLES			"tables"
%token CHAIN			"chain"
%token CHAINS			"chains"
%token RULE			"rule"
%token RULES			"rules"
%token SETS			"sets"
%token SET			"set"
%token ELEMENT			"element"
%token MAP			"map"
%token MAPS			"maps"
%token FLOWTABLE		"flowtable"
%token HANDLE			"handle"
%token RULESET			"ruleset"
%token TRACE			"trace"

%token INET			"inet"
%token NETDEV			"netdev"

%token ADD			"add"
%token UPDATE			"update"
%token REPLACE			"replace"
%token CREATE			"create"
%token INSERT			"insert"
%token DELETE			"delete"
%token GET			"get"
%token LIST			"list"
%token RESET			"reset"
%token FLUSH			"flush"
%token RENAME			"rename"
%token DESCRIBE			"describe"
%token IMPORT			"import"
%token EXPORT			"export"
%token MONITOR			"monitor"

%token ALL			"all"

%token ACCEPT			"accept"
%token DROP			"drop"
%token CONTINUE			"continue"
%token JUMP			"jump"
%token GOTO			"goto"
%token RETURN			"return"
%token TO			"to"

%token CONSTANT			"constant"
%token INTERVAL			"interval"
%token DYNAMIC			"dynamic"
%token AUTOMERGE		"auto-merge"
%token TIMEOUT			"timeout"
%token GC_INTERVAL		"gc-interval"
%token ELEMENTS			"elements"
%token EXPIRES			"expires"

%token POLICY			"policy"
%token MEMORY			"memory"
%token PERFORMANCE		"performance"
%token SIZE			"size"

%token FLOW			"flow"
%token OFFLOAD			"offload"
%token METER			"meter"
%token METERS			"meters"

%token FLOWTABLES		"flowtables"

%token <val> NUM		"number"
%token <string> STRING		"string"
%token <string> QUOTED_STRING	"quoted string"
%token <string> ASTERISK_STRING	"string with a trailing asterisk"
%destructor { xfree($$); }	STRING QUOTED_STRING ASTERISK_STRING

%token LL_HDR			"ll"
%token NETWORK_HDR		"nh"
%token TRANSPORT_HDR		"th"

%token BRIDGE			"bridge"

%token ETHER			"ether"
%token SADDR			"saddr"
%token DADDR			"daddr"
%token TYPE			"type"

%token VLAN			"vlan"
%token ID			"id"
%token CFI			"cfi"
%token PCP			"pcp"

%token ARP			"arp"
%token HTYPE			"htype"
%token PTYPE			"ptype"
%token HLEN			"hlen"
%token PLEN			"plen"
%token OPERATION		"operation"

%token IP			"ip"
%token HDRVERSION		"version"
%token HDRLENGTH		"hdrlength"
%token DSCP			"dscp"
%token ECN			"ecn"
%token LENGTH			"length"
%token FRAG_OFF			"frag-off"
%token TTL			"ttl"
%token PROTOCOL			"protocol"
%token CHECKSUM			"checksum"

%token PTR			"ptr"
%token VALUE			"value"

%token LSRR			"lsrr"
%token RR			"rr"
%token SSRR			"ssrr"
%token RA			"ra"

%token ICMP			"icmp"
%token CODE			"code"
%token SEQUENCE			"seq"
%token GATEWAY			"gateway"
%token MTU			"mtu"

%token IGMP			"igmp"
%token MRT			"mrt"

%token OPTIONS			"options"

%token IP6			"ip6"
%token PRIORITY			"priority"
%token FLOWLABEL		"flowlabel"
%token NEXTHDR			"nexthdr"
%token HOPLIMIT			"hoplimit"

%token ICMP6			"icmpv6"
%token PPTR			"param-problem"
%token MAXDELAY			"max-delay"

%token AH			"ah"
%token RESERVED			"reserved"
%token SPI			"spi"

%token ESP			"esp"

%token COMP			"comp"
%token FLAGS			"flags"
%token CPI			"cpi"

%token UDP			"udp"
%token SPORT			"sport"
%token DPORT			"dport"
%token UDPLITE			"udplite"
%token CSUMCOV			"csumcov"

%token TCP			"tcp"
%token ACKSEQ			"ackseq"
%token DOFF			"doff"
%token WINDOW			"window"
%token URGPTR			"urgptr"
%token OPTION			"option"
%token ECHO			"echo"
%token EOL			"eol"
%token MAXSEG			"maxseg"
%token NOOP			"noop"
%token SACK			"sack"
%token SACK0			"sack0"
%token SACK1			"sack1"
%token SACK2			"sack2"
%token SACK3			"sack3"
%token SACK_PERMITTED		"sack-permitted"
%token TIMESTAMP		"timestamp"
%token KIND			"kind"
%token COUNT			"count"
%token LEFT			"left"
%token RIGHT			"right"
%token TSVAL			"tsval"
%token TSECR			"tsecr"

%token DCCP			"dccp"

%token SCTP			"sctp"
%token VTAG			"vtag"

%token RT			"rt"
%token RT0			"rt0"
%token RT2			"rt2"
%token RT4			"srh"
%token SEG_LEFT			"seg-left"
%token ADDR			"addr"
%token LAST_ENT			"last-entry"
%token TAG			"tag"
%token SID			"sid"

%token HBH			"hbh"

%token FRAG			"frag"
%token RESERVED2		"reserved2"
%token MORE_FRAGMENTS		"more-fragments"

%token DST			"dst"

%token MH			"mh"

%token META			"meta"
%token MARK			"mark"
%token IIF			"iif"
%token IIFNAME			"iifname"
%token IIFTYPE			"iiftype"
%token OIF			"oif"
%token OIFNAME			"oifname"
%token OIFTYPE			"oiftype"
%token SKUID			"skuid"
%token SKGID			"skgid"
%token NFTRACE			"nftrace"
%token RTCLASSID		"rtclassid"
%token IBRIPORT			"ibriport"
%token OBRIPORT			"obriport"
%token IBRIDGENAME		"ibrname"
%token OBRIDGENAME		"obrname"
%token PKTTYPE			"pkttype"
%token CPU			"cpu"
%token IIFGROUP			"iifgroup"
%token OIFGROUP			"oifgroup"
%token CGROUP			"cgroup"
%token TIME			"time"

%token CLASSID			"classid"
%token NEXTHOP			"nexthop"

%token CT			"ct"
%token L3PROTOCOL		"l3proto"
%token PROTO_SRC		"proto-src"
%token PROTO_DST		"proto-dst"
%token ZONE			"zone"
%token DIRECTION		"direction"
%token EVENT			"event"
%token EXPECTATION		"expectation"
%token EXPIRATION		"expiration"
%token HELPER			"helper"
%token LABEL			"label"
%token STATE			"state"
%token STATUS			"status"
%token ORIGINAL			"original"
%token REPLY			"reply"

%token COUNTER			"counter"
%token NAME			"name"
%token PACKETS			"packets"
%token BYTES			"bytes"
%token AVGPKT			"avgpkt"

%token COUNTERS			"counters"
%token QUOTAS			"quotas"
%token LIMITS			"limits"
%token SYNPROXYS		"synproxys"
%token HELPERS			"helpers"

%token LOG			"log"
%token PREFIX			"prefix"
%token GROUP			"group"
%token SNAPLEN			"snaplen"
%token QUEUE_THRESHOLD		"queue-threshold"
%token LEVEL			"level"

%token LIMIT			"limit"
%token RATE			"rate"
%token BURST			"burst"
%token OVER			"over"
%token UNTIL			"until"

%token QUOTA			"quota"
%token USED			"used"

%token SECMARK			"secmark"
%token SECMARKS			"secmarks"

%token NANOSECOND		"nanosecond"
%token MICROSECOND		"microsecond"
%token MILLISECOND		"millisecond"
%token SECOND			"second"
%token MINUTE			"minute"
%token HOUR			"hour"
%token DAY			"day"
%token WEEK			"week"

%token _REJECT			"reject"
%token WITH			"with"
%token ICMPX			"icmpx"

%token SNAT			"snat"
%token DNAT			"dnat"
%token MASQUERADE		"masquerade"
%token REDIRECT			"redirect"
%token RANDOM			"random"
%token FULLY_RANDOM		"fully-random"
%token PERSISTENT		"persistent"

%token QUEUE			"queue"
%token QUEUENUM			"num"
%token BYPASS			"bypass"
%token FANOUT			"fanout"

%token DUP			"dup"
%token FWD			"fwd"

%token NUMGEN			"numgen"
%token INC			"inc"
%token MOD			"mod"
%token OFFSET			"offset"

%token JHASH			"jhash"
%token SYMHASH			"symhash"
%token SEED			"seed"

%token POSITION			"position"
%token INDEX			"index"
%token COMMENT			"comment"

%token XML			"xml"
%token JSON			"json"
%token VM			"vm"

%token NOTRACK			"notrack"

%token EXISTS			"exists"
%token MISSING			"missing"

%token EXTHDR			"exthdr"

%token IPSEC		"ipsec"
%token MODE			"mode"
%token REQID		"reqid"
%token SPNUM		"spnum"
%token TRANSPORT	"transport"
%token TUNNEL		"tunnel"

%token IN			"in"
%token OUT			"out"

%type <string>			identifier type_identifier string comment_spec
%destructor { xfree($$); }	identifier type_identifier string comment_spec

%type <val>			time_spec quota_used

%type <expr>			data_type_expr data_type_atom_expr
%destructor { expr_free($$); }  data_type_expr data_type_atom_expr

%type <cmd>			line
%destructor { cmd_free($$); }	line

%type <cmd>			base_cmd add_cmd replace_cmd create_cmd insert_cmd delete_cmd get_cmd list_cmd reset_cmd flush_cmd rename_cmd export_cmd monitor_cmd describe_cmd import_cmd
%destructor { cmd_free($$); }	base_cmd add_cmd replace_cmd create_cmd insert_cmd delete_cmd get_cmd list_cmd reset_cmd flush_cmd rename_cmd export_cmd monitor_cmd describe_cmd import_cmd

%type <handle>			table_spec tableid_spec chain_spec chainid_spec flowtable_spec chain_identifier ruleid_spec handle_spec position_spec rule_position ruleset_spec index_spec
%destructor { handle_free(&$$); } table_spec tableid_spec chain_spec chainid_spec flowtable_spec chain_identifier ruleid_spec handle_spec position_spec rule_position ruleset_spec index_spec
%type <handle>			set_spec setid_spec set_identifier flowtableid_spec flowtable_identifier obj_spec objid_spec obj_identifier
%destructor { handle_free(&$$); } set_spec setid_spec set_identifier flowtableid_spec obj_spec objid_spec obj_identifier
%type <val>			family_spec family_spec_explicit
%type <val32>			int_num	chain_policy
%type <prio_spec>		extended_prio_spec prio_spec
%type <string>			extended_prio_name quota_unit
%destructor { xfree($$); }	extended_prio_name quota_unit

%type <expr>			dev_spec
%destructor { xfree($$); }	dev_spec

%type <table>			table_block_alloc table_block
%destructor { close_scope(state); table_free($$); }	table_block_alloc
%type <chain>			chain_block_alloc chain_block
%destructor { close_scope(state); chain_free($$); }	chain_block_alloc
%type <rule>			rule rule_alloc
%destructor { rule_free($$); }	rule

%type <val>			set_flag_list	set_flag

%type <val>			set_policy_spec

%type <set>			set_block_alloc set_block
%destructor { set_free($$); }	set_block_alloc

%type <set>			map_block_alloc map_block
%destructor { set_free($$); }	map_block_alloc

%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
%destructor { obj_free($$); }	obj_block_alloc

%type <list>			stmt_list
%destructor { stmt_list_free($$); xfree($$); } stmt_list
%type <stmt>			stmt match_stmt verdict_stmt
%destructor { stmt_free($$); }	stmt match_stmt verdict_stmt
%type <stmt>			counter_stmt counter_stmt_alloc stateful_stmt
%destructor { stmt_free($$); }	counter_stmt counter_stmt_alloc stateful_stmt
%type <stmt>			payload_stmt
%destructor { stmt_free($$); }	payload_stmt
%type <stmt>			ct_stmt
%destructor { stmt_free($$); }	ct_stmt
%type <stmt>			meta_stmt
%destructor { stmt_free($$); }	meta_stmt
%type <stmt>			log_stmt log_stmt_alloc
%destructor { stmt_free($$); }	log_stmt log_stmt_alloc
%type <val>			level_type log_flags log_flags_tcp log_flag_tcp
%type <stmt>			limit_stmt quota_stmt connlimit_stmt
%destructor { stmt_free($$); }	limit_stmt quota_stmt connlimit_stmt
%type <val>			limit_burst_pkts limit_burst_bytes limit_mode time_unit quota_mode
%type <stmt>			reject_stmt reject_stmt_alloc
%destructor { stmt_free($$); }	reject_stmt reject_stmt_alloc
%type <stmt>			nat_stmt nat_stmt_alloc masq_stmt masq_stmt_alloc redir_stmt redir_stmt_alloc
%destructor { stmt_free($$); }	nat_stmt nat_stmt_alloc masq_stmt masq_stmt_alloc redir_stmt redir_stmt_alloc
%type <val>			nf_nat_flags nf_nat_flag offset_opt
%type <stmt>			tproxy_stmt
%destructor { stmt_free($$); }	tproxy_stmt
%type <stmt>			synproxy_stmt synproxy_stmt_alloc
%destructor { stmt_free($$); }	synproxy_stmt synproxy_stmt_alloc


%type <stmt>			queue_stmt queue_stmt_alloc
%destructor { stmt_free($$); }	queue_stmt queue_stmt_alloc
%type <val>			queue_stmt_flags queue_stmt_flag
%type <stmt>			dup_stmt
%destructor { stmt_free($$); }	dup_stmt
%type <stmt>			fwd_stmt
%destructor { stmt_free($$); }	fwd_stmt
%type <stmt>			set_stmt
%destructor { stmt_free($$); }	set_stmt
%type <val>			set_stmt_op
%type <stmt>			map_stmt
%destructor { stmt_free($$); }	map_stmt
%type <stmt>			meter_stmt meter_stmt_alloc flow_stmt_legacy_alloc
%destructor { stmt_free($$); }	meter_stmt meter_stmt_alloc flow_stmt_legacy_alloc

%type <expr>			symbol_expr verdict_expr integer_expr variable_expr chain_expr policy_expr
%destructor { expr_free($$); }	symbol_expr verdict_expr integer_expr variable_expr chain_expr policy_expr
%type <expr>			primary_expr shift_expr and_expr
%destructor { expr_free($$); }	primary_expr shift_expr and_expr
%type <expr>			exclusive_or_expr inclusive_or_expr
%destructor { expr_free($$); }	exclusive_or_expr inclusive_or_expr
%type <expr>			basic_expr
%destructor { expr_free($$); }	basic_expr
%type <expr>			set_ref_expr set_ref_symbol_expr
%destructor { expr_free($$); }	set_ref_expr set_ref_symbol_expr

%type <expr>			multiton_rhs_expr
%destructor { expr_free($$); }	multiton_rhs_expr
%type <expr>			prefix_rhs_expr range_rhs_expr
%destructor { expr_free($$); }	prefix_rhs_expr range_rhs_expr

%type <expr>			stmt_expr concat_stmt_expr map_stmt_expr map_stmt_expr_set
%destructor { expr_free($$); }	stmt_expr concat_stmt_expr map_stmt_expr map_stmt_expr_set

%type <expr>			multiton_stmt_expr
%destructor { expr_free($$); }	multiton_stmt_expr
%type <expr>			prefix_stmt_expr range_stmt_expr wildcard_expr
%destructor { expr_free($$); }	prefix_stmt_expr range_stmt_expr wildcard_expr

%type <expr>			primary_stmt_expr basic_stmt_expr
%destructor { expr_free($$); }	primary_stmt_expr basic_stmt_expr
%type <expr>			list_stmt_expr shift_stmt_expr
%destructor { expr_free($$); }	list_stmt_expr shift_stmt_expr
%type <expr>			and_stmt_expr exclusive_or_stmt_expr inclusive_or_stmt_expr
%destructor { expr_free($$); }	and_stmt_expr exclusive_or_stmt_expr inclusive_or_stmt_expr

%type <expr>			concat_expr
%destructor { expr_free($$); }	concat_expr

%type <expr>			map_expr
%destructor { expr_free($$); }	map_expr

%type <expr>			verdict_map_stmt
%destructor { expr_free($$); }	verdict_map_stmt

%type <expr>			verdict_map_expr verdict_map_list_expr verdict_map_list_member_expr
%destructor { expr_free($$); }	verdict_map_expr verdict_map_list_expr verdict_map_list_member_expr

%type <expr>			set_expr set_block_expr set_list_expr set_list_member_expr flowtable_expr flowtable_list_expr flowtable_expr_member
%destructor { expr_free($$); }	set_expr set_block_expr set_list_expr set_list_member_expr flowtable_expr flowtable_list_expr flowtable_expr_member
%type <expr>			set_elem_expr set_elem_expr_alloc set_lhs_expr set_rhs_expr
%destructor { expr_free($$); }	set_elem_expr set_elem_expr_alloc set_lhs_expr set_rhs_expr
%type <expr>			set_elem_expr_stmt set_elem_expr_stmt_alloc
%destructor { expr_free($$); }	set_elem_expr_stmt set_elem_expr_stmt_alloc

%type <expr>			meter_key_expr meter_key_expr_alloc
%destructor { expr_free($$); }	meter_key_expr meter_key_expr_alloc

%type <expr>			expr initializer_expr keyword_expr
%destructor { expr_free($$); }	expr initializer_expr keyword_expr

%type <expr>			rhs_expr concat_rhs_expr basic_rhs_expr
%destructor { expr_free($$); }	rhs_expr concat_rhs_expr basic_rhs_expr
%type <expr>			primary_rhs_expr list_rhs_expr shift_rhs_expr symbol_stmt_expr
%destructor { expr_free($$); }	primary_rhs_expr list_rhs_expr shift_rhs_expr symbol_stmt_expr
%type <expr>			and_rhs_expr exclusive_or_rhs_expr inclusive_or_rhs_expr
%destructor { expr_free($$); }	and_rhs_expr exclusive_or_rhs_expr inclusive_or_rhs_expr

%type <obj>			counter_obj quota_obj ct_obj_alloc limit_obj secmark_obj synproxy_obj
%destructor { obj_free($$); }	counter_obj quota_obj ct_obj_alloc limit_obj secmark_obj synproxy_obj

%type <expr>			relational_expr
%destructor { expr_free($$); }	relational_expr
%type <val>			relational_op

%type <expr>			payload_expr payload_raw_expr
%destructor { expr_free($$); }	payload_expr payload_raw_expr
%type <val>			payload_base_spec
%type <expr>			eth_hdr_expr	vlan_hdr_expr
%destructor { expr_free($$); }	eth_hdr_expr	vlan_hdr_expr
%type <val>			eth_hdr_field	vlan_hdr_field
%type <expr>			arp_hdr_expr
%destructor { expr_free($$); }	arp_hdr_expr
%type <val>			arp_hdr_field
%type <expr>			ip_hdr_expr	icmp_hdr_expr		igmp_hdr_expr numgen_expr	hash_expr
%destructor { expr_free($$); }	ip_hdr_expr	icmp_hdr_expr		igmp_hdr_expr numgen_expr	hash_expr
%type <val>			ip_hdr_field	icmp_hdr_field		igmp_hdr_field
%type <val>			ip_option_type	ip_option_field
%type <expr>			ip6_hdr_expr    icmp6_hdr_expr
%destructor { expr_free($$); }	ip6_hdr_expr	icmp6_hdr_expr
%type <val>			ip6_hdr_field   icmp6_hdr_field
%type <expr>			auth_hdr_expr	esp_hdr_expr		comp_hdr_expr
%destructor { expr_free($$); }	auth_hdr_expr	esp_hdr_expr		comp_hdr_expr
%type <val>			auth_hdr_field	esp_hdr_field		comp_hdr_field
%type <expr>			udp_hdr_expr	udplite_hdr_expr
%destructor { expr_free($$); }	udp_hdr_expr	udplite_hdr_expr
%type <val>			udp_hdr_field	udplite_hdr_field
%type <expr>			dccp_hdr_expr	sctp_hdr_expr
%destructor { expr_free($$); }	dccp_hdr_expr	sctp_hdr_expr
%type <val>			dccp_hdr_field	sctp_hdr_field
%type <expr>			th_hdr_expr
%destructor { expr_free($$); }	th_hdr_expr
%type <val>			th_hdr_field

%type <expr>			exthdr_expr
%destructor { expr_free($$); }	exthdr_expr
%type <expr>			hbh_hdr_expr	frag_hdr_expr		dst_hdr_expr
%destructor { expr_free($$); }	hbh_hdr_expr	frag_hdr_expr		dst_hdr_expr
%type <val>			hbh_hdr_field	frag_hdr_field		dst_hdr_field
%type <expr>			rt_hdr_expr	rt0_hdr_expr		rt2_hdr_expr	rt4_hdr_expr
%destructor { expr_free($$); }	rt_hdr_expr	rt0_hdr_expr		rt2_hdr_expr	rt4_hdr_expr
%type <val>			rt_hdr_field	rt0_hdr_field		rt2_hdr_field	rt4_hdr_field
%type <expr>			mh_hdr_expr
%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>			socket_expr
%destructor { expr_free($$); } socket_expr
%type<val>			socket_key

%type <val>			nf_key_proto

%type <expr>			rt_expr
%destructor { expr_free($$); }	rt_expr
%type <val>			rt_key

%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

%type <expr>			fib_expr
%destructor { expr_free($$); }	fib_expr
%type <val>			fib_tuple	fib_result	fib_flag

%type <expr>			osf_expr
%type <val>			osf_ttl
%destructor { expr_free($$); }	osf_expr

%type <val>			markup_format
%type <string>			monitor_event
%destructor { xfree($$); }	monitor_event
%type <val>			monitor_object	monitor_format

%type <val>			synproxy_ts	synproxy_sack

%type <expr>			tcp_hdr_expr
%destructor { expr_free($$); }	tcp_hdr_expr
%type <val>			tcp_hdr_field
%type <val>			tcp_hdr_option_type tcp_hdr_option_field

%type <expr>			boolean_expr
%destructor { expr_free($$); }	boolean_expr
%type <val8>			boolean_keys

%type <expr>			exthdr_exists_expr
%destructor { expr_free($$); }	exthdr_exists_expr
%type <val>			exthdr_key

%type <val>			ct_l4protoname ct_obj_type

%type <list>			timeout_states timeout_state
%destructor { xfree($$); }	timeout_states timeout_state

%type <val>			xfrm_state_key	xfrm_state_proto_key xfrm_dir	xfrm_spnum
%type <expr>			xfrm_expr
%destructor { expr_free($$); }	xfrm_expr

%%

input			:	/* empty */
			|	input		line
			{
				if ($2 != NULL) {
					$2->location = @2;
					list_add_tail(&$2->list, state->cmds);
				}
			}
			;

stmt_separator		:	NEWLINE
			|	SEMICOLON
			;

opt_newline		:	NEWLINE
		 	|	/* empty */
			;

common_block		:	INCLUDE		QUOTED_STRING	stmt_separator
			{
				if (scanner_include_file(nft, scanner, $2, &@$) < 0) {
					xfree($2);
					YYERROR;
				}
				xfree($2);
			}
			|	DEFINE		identifier	'='	initializer_expr	stmt_separator
			{
				struct scope *scope = current_scope(state);

				if (symbol_lookup(scope, $2) != NULL) {
					erec_queue(error(&@2, "redefinition of symbol '%s'", $2),
						   state->msgs);
					xfree($2);
					YYERROR;
				}

				symbol_bind(scope, $2, $4);
				xfree($2);
			}
			|	REDEFINE	identifier	'='	initializer_expr	stmt_separator
			{
				struct scope *scope = current_scope(state);

				symbol_bind(scope, $2, $4);
				xfree($2);
			}
			|	UNDEFINE	identifier	stmt_separator
			{
				struct scope *scope = current_scope(state);

				if (symbol_unbind(scope, $2) < 0) {
					erec_queue(error(&@2, "undefined symbol '%s'", $2),
						   state->msgs);
					YYERROR;
				}
				xfree($2);
			}
			|	error		stmt_separator
			{
				if (++state->nerrs == nft->parser_max_errors)
					YYABORT;
				yyerrok;
			}
			;

line			:	common_block			{ $$ = NULL; }
			|	stmt_separator			{ $$ = NULL; }
			|	base_cmd	stmt_separator	{ $$ = $1; }
			|	base_cmd	TOKEN_EOF
			{
				/*
				 * Very hackish workaround for bison >= 2.4: previous versions
				 * terminated parsing after EOF, 2.4+ tries to get further input
				 * in 'input' and calls the scanner again, causing a crash when
				 * the final input buffer has been popped. Terminate manually to
				 * avoid this. The correct fix should be to adjust the grammar
				 * to accept EOF in input, but for unknown reasons it does not
				 * work.
				 */
				if ($1 != NULL) {
					$1->location = @1;
					list_add_tail(&$1->list, state->cmds);
				}
				$$ = NULL;
				YYACCEPT;
			}
			;

base_cmd		:	/* empty */	add_cmd		{ $$ = $1; }
	  		|	ADD		add_cmd		{ $$ = $2; }
			|	REPLACE		replace_cmd	{ $$ = $2; }
			|	CREATE		create_cmd	{ $$ = $2; }
			|	INSERT		insert_cmd	{ $$ = $2; }
			|	DELETE		delete_cmd	{ $$ = $2; }
			|	GET		get_cmd		{ $$ = $2; }
			|	LIST		list_cmd	{ $$ = $2; }
			|	RESET		reset_cmd	{ $$ = $2; }
			|	FLUSH		flush_cmd	{ $$ = $2; }
			|	RENAME		rename_cmd	{ $$ = $2; }
			|       IMPORT          import_cmd      { $$ = $2; }
			|	EXPORT		export_cmd	{ $$ = $2; }
			|	MONITOR		monitor_cmd	{ $$ = $2; }
			|	DESCRIBE	describe_cmd	{ $$ = $2; }
			;

add_cmd			:	TABLE		table_spec
			{
				$$ = cmd_alloc(CMD_ADD, CMD_OBJ_TABLE, &$2, &@$, NULL);
			}
			|	TABLE		table_spec	table_block_alloc
						'{'	table_block	'}'
			{
				handle_merge(&$3->handle, &$2);
				close_scope(state);
				$$ = cmd_alloc(CMD_ADD, CMD_OBJ_TABLE, &$2, &@$, $5);
			}
			|	CHAIN		chain_spec
			{
				$$ = cmd_alloc(CMD_ADD, CMD_OBJ_CHAIN, &$2, &@$, NULL);
			}
			|	CHAIN		chain_spec	chain_block_alloc
						'{'	chain_block	'}'
			{
				$5->location = @5;
				handle_merge(&$3->handle, &$2);
				close_scope(state);
				$$ = cmd_alloc(CMD_ADD, CMD_OBJ_CHAIN, &$2, &@$, $5);
			}
			|	RULE		rule_position	rule
			{
				$$ = cmd_alloc(CMD_ADD, CMD_OBJ_RULE, &$2, &@$, $3);
			}
			|	/* empty */	rule_position	rule
			{
				$$ = cmd_alloc(CMD_ADD, CMD_OBJ_RULE, &$1, &@$, $2);
			}
			|	SET		set_spec	set_block_alloc
						'{'	set_block	'}'
			{
				$5->location = @5;
				handle_merge(&$3->handle, &$2);
				$$ = cmd_alloc(CMD_ADD, CMD_OBJ_SET, &$2, &@$, $5);
			}
			|	MAP		set_spec	map_block_alloc
						'{'	map_block	'}'
			{
				$5->location = @5;
				handle_merge(&$3->handle, &$2);
				$$ = cmd_alloc(CMD_ADD, CMD_OBJ_SET, &$2, &@$, $5);
			}
			|	ELEMENT		set_spec	set_block_expr
			{
				$$ = cmd_alloc(CMD_ADD, CMD_OBJ_SETELEM, &$2, &@$, $3);
			}
			|	FLOWTABLE	flowtable_spec	flowtable_block_alloc
						'{'	flowtable_block	'}'
			{
				$5->location = @5;
				handle_merge(&$3->handle, &$2);
				$$ = cmd_alloc(CMD_ADD, CMD_OBJ_FLOWTABLE, &$2, &@$, $5);
			}
			|	COUNTER		obj_spec
			{
				struct obj *obj;

				obj = obj_alloc(&@$);
				obj->type = NFT_OBJECT_COUNTER;
				handle_merge(&obj->handle, &$2);
				$$ = cmd_alloc(CMD_ADD, CMD_OBJ_COUNTER, &$2, &@$, obj);
			}
			|	COUNTER		obj_spec	counter_obj	counter_config
			{
				$$ = cmd_alloc(CMD_ADD, CMD_OBJ_COUNTER, &$2, &@$, $3);
			}
			|	QUOTA		obj_spec	quota_obj	quota_config
			{
				$$ = cmd_alloc(CMD_ADD, CMD_OBJ_QUOTA, &$2, &@$, $3);
			}
			|	CT	HELPER	obj_spec	ct_obj_alloc	'{' ct_helper_block '}'
			{
				$$ = cmd_alloc_obj_ct(CMD_ADD, NFT_OBJECT_CT_HELPER, &$3, &@$, $4);
			}
			|	CT	TIMEOUT obj_spec	ct_obj_alloc	'{' ct_timeout_block '}'
			{
				$$ = cmd_alloc_obj_ct(CMD_ADD, NFT_OBJECT_CT_TIMEOUT, &$3, &@$, $4);
			}
			|	CT	EXPECTATION	obj_spec	ct_obj_alloc	'{' ct_expect_block '}'
			{
				$$ = cmd_alloc_obj_ct(CMD_ADD, NFT_OBJECT_CT_EXPECT, &$3, &@$, $4);
			}
			|	LIMIT		obj_spec	limit_obj	limit_config
			{
				$$ = cmd_alloc(CMD_ADD, CMD_OBJ_LIMIT, &$2, &@$, $3);
			}
			|	SECMARK		obj_spec	secmark_obj	secmark_config
			{
				$$ = cmd_alloc(CMD_ADD, CMD_OBJ_SECMARK, &$2, &@$, $3);
			}
			|	SYNPROXY	obj_spec	synproxy_obj	synproxy_config
			{
				$$ = cmd_alloc(CMD_ADD, CMD_OBJ_SYNPROXY, &$2, &@$, $3);
			}
			;

replace_cmd		:	RULE		ruleid_spec	rule
			{
				$$ = cmd_alloc(CMD_REPLACE, CMD_OBJ_RULE, &$2, &@$, $3);
			}
			;

create_cmd		:	TABLE		table_spec
			{
				$$ = cmd_alloc(CMD_CREATE, CMD_OBJ_TABLE, &$2, &@$, NULL);
			}
			|	TABLE		table_spec	table_block_alloc
						'{'	table_block	'}'
			{
				handle_merge(&$3->handle, &$2);
				close_scope(state);
				$$ = cmd_alloc(CMD_CREATE, CMD_OBJ_TABLE, &$2, &@$, $5);
			}
			|	CHAIN		chain_spec
			{
				$$ = cmd_alloc(CMD_CREATE, CMD_OBJ_CHAIN, &$2, &@$, NULL);
			}
			|	CHAIN		chain_spec	chain_block_alloc
						'{'	chain_block	'}'
			{
				$5->location = @5;
				handle_merge(&$3->handle, &$2);
				close_scope(state);
				$$ = cmd_alloc(CMD_CREATE, CMD_OBJ_CHAIN, &$2, &@$, $5);
			}
			|	SET		set_spec	set_block_alloc
						'{'	set_block	'}'
			{
				$5->location = @5;
				handle_merge(&$3->handle, &$2);
				$$ = cmd_alloc(CMD_CREATE, CMD_OBJ_SET, &$2, &@$, $5);
			}
			|	MAP		set_spec	map_block_alloc
						'{'	map_block	'}'
			{
				$5->location = @5;
				handle_merge(&$3->handle, &$2);
				$$ = cmd_alloc(CMD_CREATE, CMD_OBJ_SET, &$2, &@$, $5);
			}
			|	ELEMENT		set_spec	set_block_expr
			{
				$$ = cmd_alloc(CMD_CREATE, CMD_OBJ_SETELEM, &$2, &@$, $3);
			}
			|	FLOWTABLE	flowtable_spec	flowtable_block_alloc
						'{'	flowtable_block	'}'
			{
				$5->location = @5;
				handle_merge(&$3->handle, &$2);
				$$ = cmd_alloc(CMD_CREATE, CMD_OBJ_FLOWTABLE, &$2, &@$, $5);
			}
			|	COUNTER		obj_spec
			{
				struct obj *obj;

				obj = obj_alloc(&@$);
				obj->type = NFT_OBJECT_COUNTER;
				handle_merge(&obj->handle, &$2);
				$$ = cmd_alloc(CMD_CREATE, CMD_OBJ_COUNTER, &$2, &@$, obj);
			}
			|	COUNTER		obj_spec	counter_obj	counter_config
			{
				$$ = cmd_alloc(CMD_CREATE, CMD_OBJ_COUNTER, &$2, &@$, $3);
			}
			|	QUOTA		obj_spec	quota_obj	quota_config
			{
				$$ = cmd_alloc(CMD_CREATE, CMD_OBJ_QUOTA, &$2, &@$, $3);
			}
			|	CT	HELPER	obj_spec	ct_obj_alloc	'{' ct_helper_block '}'
			{
				$$ = cmd_alloc_obj_ct(CMD_CREATE, NFT_OBJECT_CT_HELPER, &$3, &@$, $4);
			}
			|	CT	TIMEOUT obj_spec	ct_obj_alloc	'{' ct_timeout_block '}'
			{
				$$ = cmd_alloc_obj_ct(CMD_CREATE, NFT_OBJECT_CT_TIMEOUT, &$3, &@$, $4);
			}
			|	CT	EXPECTATION obj_spec	ct_obj_alloc	'{' ct_expect_block '}'
			{
				$$ = cmd_alloc_obj_ct(CMD_CREATE, NFT_OBJECT_CT_EXPECT, &$3, &@$, $4);
			}
			|	LIMIT		obj_spec	limit_obj	limit_config
			{
				$$ = cmd_alloc(CMD_CREATE, CMD_OBJ_LIMIT, &$2, &@$, $3);
			}
			|	SECMARK		obj_spec	secmark_obj	secmark_config
			{
				$$ = cmd_alloc(CMD_CREATE, CMD_OBJ_SECMARK, &$2, &@$, $3);
			}
			|	SYNPROXY	obj_spec	synproxy_obj	synproxy_config
			{
				$$ = cmd_alloc(CMD_CREATE, CMD_OBJ_SYNPROXY, &$2, &@$, $3);
			}
			;

insert_cmd		:	RULE		rule_position	rule
			{
				$$ = cmd_alloc(CMD_INSERT, CMD_OBJ_RULE, &$2, &@$, $3);
			}
			;

delete_cmd		:	TABLE		table_spec
			{
				$$ = cmd_alloc(CMD_DELETE, CMD_OBJ_TABLE, &$2, &@$, NULL);
			}
			|	TABLE 		tableid_spec
			{
				$$ = cmd_alloc(CMD_DELETE, CMD_OBJ_TABLE, &$2, &@$, NULL);
			}
			|	CHAIN		chain_spec
			{
				$$ = cmd_alloc(CMD_DELETE, CMD_OBJ_CHAIN, &$2, &@$, NULL);
			}
			| 	CHAIN 		chainid_spec
			{
				$$ = cmd_alloc(CMD_DELETE, CMD_OBJ_CHAIN, &$2, &@$, NULL);
			}
			|	RULE		ruleid_spec
			{
				$$ = cmd_alloc(CMD_DELETE, CMD_OBJ_RULE, &$2, &@$, NULL);
			}
			|	SET		set_spec
			{
				$$ = cmd_alloc(CMD_DELETE, CMD_OBJ_SET, &$2, &@$, NULL);
			}
			| 	SET 		setid_spec
			{
				$$ = cmd_alloc(CMD_DELETE, CMD_OBJ_SET, &$2, &@$, NULL);
			}
			|	MAP		set_spec
			{
				$$ = cmd_alloc(CMD_DELETE, CMD_OBJ_SET, &$2, &@$, NULL);
			}
			|	ELEMENT		set_spec	set_block_expr
			{
				$$ = cmd_alloc(CMD_DELETE, CMD_OBJ_SETELEM, &$2, &@$, $3);
			}
			|	FLOWTABLE	flowtable_spec
			{
				$$ = cmd_alloc(CMD_DELETE, CMD_OBJ_FLOWTABLE, &$2, &@$, NULL);
			}
			|	FLOWTABLE	flowtableid_spec
			{
				$$ = cmd_alloc(CMD_DELETE, CMD_OBJ_FLOWTABLE, &$2, &@$, NULL);
			}
			|	COUNTER		obj_spec
			{
				$$ = cmd_alloc(CMD_DELETE, CMD_OBJ_COUNTER, &$2, &@$, NULL);
			}
			|  	COUNTER 	objid_spec
			{
				$$ = cmd_alloc(CMD_DELETE, CMD_OBJ_COUNTER, &$2, &@$, NULL);
			}
			|	QUOTA		obj_spec
			{
				$$ = cmd_alloc(CMD_DELETE, CMD_OBJ_QUOTA, &$2, &@$, NULL);
			}
			| 	QUOTA 		objid_spec
			{
				$$ = cmd_alloc(CMD_DELETE, CMD_OBJ_QUOTA, &$2, &@$, NULL);
			}
			|	CT	ct_obj_type	obj_spec	ct_obj_alloc
			{
				$$ = cmd_alloc_obj_ct(CMD_DELETE, $2, &$3, &@$, $4);
			}
			|	LIMIT		obj_spec
			{
				$$ = cmd_alloc(CMD_DELETE, CMD_OBJ_LIMIT, &$2, &@$, NULL);
			}
			| 	LIMIT 		objid_spec
			{
				$$ = cmd_alloc(CMD_DELETE, CMD_OBJ_LIMIT, &$2, &@$, NULL);
			}
			|	SECMARK		obj_spec
			{
				$$ = cmd_alloc(CMD_DELETE, CMD_OBJ_SECMARK, &$2, &@$, NULL);
			}
			| 	SECMARK		objid_spec
			{
				$$ = cmd_alloc(CMD_DELETE, CMD_OBJ_SECMARK, &$2, &@$, NULL);
			}
			|	SYNPROXY	obj_spec
			{
				$$ = cmd_alloc(CMD_DELETE, CMD_OBJ_SYNPROXY, &$2, &@$, NULL);
			}
			|	SYNPROXY	objid_spec
			{
				$$ = cmd_alloc(CMD_DELETE, CMD_OBJ_SYNPROXY, &$2, &@$, NULL);
			}
			;

get_cmd			:	ELEMENT		set_spec	set_block_expr
			{
				$$ = cmd_alloc(CMD_GET, CMD_OBJ_SETELEM, &$2, &@$, $3);
			}
			;

list_cmd		:	TABLE		table_spec
			{
				$$ = cmd_alloc(CMD_LIST, CMD_OBJ_TABLE, &$2, &@$, NULL);
			}
			|	TABLES		ruleset_spec
			{
				$$ = cmd_alloc(CMD_LIST, CMD_OBJ_TABLE, &$2, &@$, NULL);
			}
			|	CHAIN		chain_spec
			{
				$$ = cmd_alloc(CMD_LIST, CMD_OBJ_CHAIN, &$2, &@$, NULL);
			}
			|	CHAINS		ruleset_spec
			{
				$$ = cmd_alloc(CMD_LIST, CMD_OBJ_CHAINS, &$2, &@$, NULL);
			}
			|	SETS		ruleset_spec
			{
				$$ = cmd_alloc(CMD_LIST, CMD_OBJ_SETS, &$2, &@$, NULL);
			}
			|	SETS		TABLE	table_spec
			{
				$$ = cmd_alloc(CMD_LIST, CMD_OBJ_SETS, &$3, &@$, NULL);
			}
			|	SET		set_spec
			{
				$$ = cmd_alloc(CMD_LIST, CMD_OBJ_SET, &$2, &@$, NULL);
			}
			|	COUNTERS	ruleset_spec
			{
				$$ = cmd_alloc(CMD_LIST, CMD_OBJ_COUNTERS, &$2, &@$, NULL);
			}
			|	COUNTERS	TABLE	table_spec
			{
				$$ = cmd_alloc(CMD_LIST, CMD_OBJ_COUNTERS, &$3, &@$, NULL);
			}
			|	COUNTER		obj_spec
			{
				$$ = cmd_alloc(CMD_LIST, CMD_OBJ_COUNTER, &$2, &@$, NULL);
			}
			|	QUOTAS		ruleset_spec
			{
				$$ = cmd_alloc(CMD_LIST, CMD_OBJ_QUOTAS, &$2, &@$, NULL);
			}
			|	QUOTAS		TABLE	table_spec
			{
				$$ = cmd_alloc(CMD_LIST, CMD_OBJ_QUOTAS, &$3, &@$, NULL);
			}
			|	QUOTA		obj_spec
			{
				$$ = cmd_alloc(CMD_LIST, CMD_OBJ_QUOTA, &$2, &@$, NULL);
			}
			|	LIMITS		ruleset_spec
			{
				$$ = cmd_alloc(CMD_LIST, CMD_OBJ_LIMITS, &$2, &@$, NULL);
			}
			|	LIMITS		TABLE	table_spec
			{
				$$ = cmd_alloc(CMD_LIST, CMD_OBJ_LIMITS, &$3, &@$, NULL);
			}
			|	LIMIT		obj_spec
			{
				$$ = cmd_alloc(CMD_LIST, CMD_OBJ_LIMIT, &$2, &@$, NULL);
			}
			|	SECMARKS	ruleset_spec
			{
				$$ = cmd_alloc(CMD_LIST, CMD_OBJ_SECMARKS, &$2, &@$, NULL);
			}
			|	SECMARKS	TABLE	table_spec
			{
				$$ = cmd_alloc(CMD_LIST, CMD_OBJ_SECMARKS, &$3, &@$, NULL);
			}
			|	SECMARK		obj_spec
			{
				$$ = cmd_alloc(CMD_LIST, CMD_OBJ_SECMARK, &$2, &@$, NULL);
			}
			|	SYNPROXYS	ruleset_spec
			{
				$$ = cmd_alloc(CMD_LIST, CMD_OBJ_SYNPROXYS, &$2, &@$, NULL);
			}
			|	SYNPROXYS	TABLE	table_spec
			{
				$$ = cmd_alloc(CMD_LIST, CMD_OBJ_SYNPROXYS, &$3, &@$, NULL);
			}
			|	SYNPROXY	obj_spec
			{
				$$ = cmd_alloc(CMD_LIST, CMD_OBJ_SYNPROXY, &$2, &@$, NULL);
			}
			|	RULESET		ruleset_spec
			{
				$$ = cmd_alloc(CMD_LIST, CMD_OBJ_RULESET, &$2, &@$, NULL);
			}
			|	FLOW TABLES	ruleset_spec
			{
				$$ = cmd_alloc(CMD_LIST, CMD_OBJ_METERS, &$3, &@$, NULL);
			}
			|	FLOW TABLE	set_spec
			{
				$$ = cmd_alloc(CMD_LIST, CMD_OBJ_METER, &$3, &@$, NULL);
			}
			|	METERS		ruleset_spec
			{
				$$ = cmd_alloc(CMD_LIST, CMD_OBJ_METERS, &$2, &@$, NULL);
			}
			|	METER		set_spec
			{
				$$ = cmd_alloc(CMD_LIST, CMD_OBJ_METER, &$2, &@$, NULL);
			}
			|       FLOWTABLES      ruleset_spec
			{
				$$ = cmd_alloc(CMD_LIST, CMD_OBJ_FLOWTABLES, &$2, &@$, NULL);
			}
			|	FLOWTABLE	flowtable_spec
			{
				$$ = cmd_alloc(CMD_LIST, CMD_OBJ_FLOWTABLE, &$2, &@$, NULL);
			}
			|	MAPS		ruleset_spec
			{
				$$ = cmd_alloc(CMD_LIST, CMD_OBJ_MAPS, &$2, &@$, NULL);
			}
			|	MAP		set_spec
			{
				$$ = cmd_alloc(CMD_LIST, CMD_OBJ_MAP, &$2, &@$, NULL);
			}
			|	CT		ct_obj_type	obj_spec
			{
				$$ = cmd_alloc_obj_ct(CMD_LIST, $2, &$3, &@$, NULL);
			}
			|       CT		HELPERS		TABLE   table_spec
			{
				$$ = cmd_alloc(CMD_LIST, CMD_OBJ_CT_HELPERS, &$4, &@$, NULL);
			}
			|	CT		TIMEOUT		TABLE		table_spec
			{
				$$ = cmd_alloc(CMD_LIST, CMD_OBJ_CT_TIMEOUT, &$4, &@$, NULL);
			}
			|	CT		EXPECTATION		TABLE		table_spec
			{
				$$ = cmd_alloc(CMD_LIST, CMD_OBJ_CT_EXPECT, &$4, &@$, NULL);
			}
			;

reset_cmd		:	COUNTERS	ruleset_spec
			{
				$$ = cmd_alloc(CMD_RESET, CMD_OBJ_COUNTERS, &$2, &@$, NULL);
			}
			|	COUNTERS	TABLE	table_spec
			{
				$$ = cmd_alloc(CMD_RESET, CMD_OBJ_COUNTERS, &$3, &@$, NULL);
			}
			|       COUNTER         obj_spec
			{
				$$ = cmd_alloc(CMD_RESET, CMD_OBJ_COUNTER, &$2,&@$, NULL);
			}
			|	QUOTAS		ruleset_spec
			{
				$$ = cmd_alloc(CMD_RESET, CMD_OBJ_QUOTAS, &$2, &@$, NULL);
			}
			|	QUOTAS		TABLE	table_spec
			{
				$$ = cmd_alloc(CMD_RESET, CMD_OBJ_QUOTAS, &$3, &@$, NULL);
			}
			|       QUOTA           obj_spec
			{
				$$ = cmd_alloc(CMD_RESET, CMD_OBJ_QUOTA, &$2, &@$, NULL);
			}
			;

flush_cmd		:	TABLE		table_spec
			{
				$$ = cmd_alloc(CMD_FLUSH, CMD_OBJ_TABLE, &$2, &@$, NULL);
			}
			|	CHAIN		chain_spec
			{
				$$ = cmd_alloc(CMD_FLUSH, CMD_OBJ_CHAIN, &$2, &@$, NULL);
			}
			|	SET		set_spec
			{
				$$ = cmd_alloc(CMD_FLUSH, CMD_OBJ_SET, &$2, &@$, NULL);
			}
			|	MAP		set_spec
			{
				$$ = cmd_alloc(CMD_FLUSH, CMD_OBJ_MAP, &$2, &@$, NULL);
			}
			|	FLOW TABLE	set_spec
			{
				$$ = cmd_alloc(CMD_FLUSH, CMD_OBJ_METER, &$3, &@$, NULL);
			}
			|	METER		set_spec
			{
				$$ = cmd_alloc(CMD_FLUSH, CMD_OBJ_METER, &$2, &@$, NULL);
			}
			|	RULESET		ruleset_spec
			{
				$$ = cmd_alloc(CMD_FLUSH, CMD_OBJ_RULESET, &$2, &@$, NULL);
			}
			;

rename_cmd		:	CHAIN		chain_spec	identifier
			{
				$$ = cmd_alloc(CMD_RENAME, CMD_OBJ_CHAIN, &$2, &@$, NULL);
				$$->arg = $3;
			}
			;

import_cmd			:       RULESET         markup_format
			{
				struct handle h = { .family = NFPROTO_UNSPEC };
				struct markup *markup = markup_alloc($2);
				$$ = cmd_alloc(CMD_IMPORT, CMD_OBJ_MARKUP, &h, &@$, markup);
			}
			|	markup_format
			{
				struct handle h = { .family = NFPROTO_UNSPEC };
				struct markup *markup = markup_alloc($1);
				$$ = cmd_alloc(CMD_IMPORT, CMD_OBJ_MARKUP, &h, &@$, markup);
			}
			;

export_cmd		:	RULESET		markup_format
			{
				struct handle h = { .family = NFPROTO_UNSPEC };
				struct markup *markup = markup_alloc($2);
				$$ = cmd_alloc(CMD_EXPORT, CMD_OBJ_MARKUP, &h, &@$, markup);
			}
			|	markup_format
			{
				struct handle h = { .family = NFPROTO_UNSPEC };
				struct markup *markup = markup_alloc($1);
				$$ = cmd_alloc(CMD_EXPORT, CMD_OBJ_MARKUP, &h, &@$, markup);
			}
			;

monitor_cmd		:	monitor_event	monitor_object	monitor_format
			{
				struct handle h = { .family = NFPROTO_UNSPEC };
				struct monitor *m = monitor_alloc($3, $2, $1);
				m->location = @1;
				$$ = cmd_alloc(CMD_MONITOR, CMD_OBJ_MONITOR, &h, &@$, m);
			}
			;

monitor_event		:	/* empty */	{ $$ = NULL; }
			|       STRING		{ $$ = $1; }
			;

monitor_object		:	/* empty */	{ $$ = CMD_MONITOR_OBJ_ANY; }
			| 	TABLES		{ $$ = CMD_MONITOR_OBJ_TABLES; }
			| 	CHAINS		{ $$ = CMD_MONITOR_OBJ_CHAINS; }
			| 	SETS		{ $$ = CMD_MONITOR_OBJ_SETS; }
			|	RULES		{ $$ = CMD_MONITOR_OBJ_RULES; }
			|	ELEMENTS	{ $$ = CMD_MONITOR_OBJ_ELEMS; }
			|	RULESET		{ $$ = CMD_MONITOR_OBJ_RULESET; }
			|	TRACE		{ $$ = CMD_MONITOR_OBJ_TRACE; }
			;

monitor_format		:	/* empty */	{ $$ = NFTNL_OUTPUT_DEFAULT; }
			|	markup_format
			;

markup_format		: 	XML 		{ $$ = __NFT_OUTPUT_NOTSUPP; }
			|	JSON		{ $$ = NFTNL_OUTPUT_JSON; }
			|	VM JSON		{ $$ = NFTNL_OUTPUT_JSON; }
			;

describe_cmd		:	primary_expr
			{
				struct handle h = { .family = NFPROTO_UNSPEC };
				$$ = cmd_alloc(CMD_DESCRIBE, CMD_OBJ_EXPR, &h, &@$, NULL);
				$$->expr = $1;
			}
			;

table_block_alloc	:	/* empty */
			{
				$$ = table_alloc();
				open_scope(state, &$$->scope);
			}
			;

table_options		:	FLAGS		STRING
			{
				if (strcmp($2, "dormant") == 0) {
					$<table>0->flags = TABLE_F_DORMANT;
					xfree($2);
				} else {
					erec_queue(error(&@2, "unknown table option %s", $2),
						   state->msgs);
					xfree($2);
					YYERROR;
				}
			}
			;

table_block		:	/* empty */	{ $$ = $<table>-1; }
			|	table_block	common_block
			|	table_block	stmt_separator
			|	table_block	table_options	stmt_separator
			|	table_block	CHAIN		chain_identifier
					chain_block_alloc	'{' 	chain_block	'}'
					stmt_separator
			{
				$4->location = @3;
				handle_merge(&$4->handle, &$3);
				handle_free(&$3);
				close_scope(state);
				list_add_tail(&$4->list, &$1->chains);
				$$ = $1;
			}
			|	table_block	SET		set_identifier
					set_block_alloc		'{'	set_block	'}'
					stmt_separator
			{
				$4->location = @3;
				handle_merge(&$4->handle, &$3);
				handle_free(&$3);
				list_add_tail(&$4->list, &$1->sets);
				$$ = $1;
			}
			|	table_block	MAP		set_identifier
					map_block_alloc		'{'	map_block	'}'
					stmt_separator
			{
				$4->location = @3;
				handle_merge(&$4->handle, &$3);
				handle_free(&$3);
				list_add_tail(&$4->list, &$1->sets);
				$$ = $1;
			}

			|	table_block	FLOWTABLE	flowtable_identifier
					flowtable_block_alloc	'{'	flowtable_block	'}'
					stmt_separator
			{
				$4->location = @3;
				handle_merge(&$4->handle, &$3);
				handle_free(&$3);
				list_add_tail(&$4->list, &$1->flowtables);
				$$ = $1;
			}
			|	table_block	COUNTER		obj_identifier
					obj_block_alloc	'{'	counter_block	'}'
					stmt_separator
			{
				$4->location = @3;
				$4->type = NFT_OBJECT_COUNTER;
				handle_merge(&$4->handle, &$3);
				handle_free(&$3);
				list_add_tail(&$4->list, &$1->objs);
				$$ = $1;
			}
			|	table_block	QUOTA		obj_identifier
					obj_block_alloc	'{'	quota_block	'}'
					stmt_separator
			{
				$4->location = @3;
				$4->type = NFT_OBJECT_QUOTA;
				handle_merge(&$4->handle, &$3);
				handle_free(&$3);
				list_add_tail(&$4->list, &$1->objs);
				$$ = $1;
			}
			|	table_block	CT	HELPER	obj_identifier  obj_block_alloc '{'     ct_helper_block     '}' stmt_separator
			{
				$5->location = @4;
				$5->type = NFT_OBJECT_CT_HELPER;
				handle_merge(&$5->handle, &$4);
				handle_free(&$4);
				list_add_tail(&$5->list, &$1->objs);
				$$ = $1;
			}
			|	table_block	CT	TIMEOUT obj_identifier obj_block_alloc '{'	ct_timeout_block	'}' stmt_separator
			{
				$5->location = @4;
				$5->type = NFT_OBJECT_CT_TIMEOUT;
				handle_merge(&$5->handle, &$4);
				handle_free(&$4);
				list_add_tail(&$5->list, &$1->objs);
				$$ = $1;
			}
			|	table_block	CT	EXPECTATION obj_identifier obj_block_alloc '{'	ct_expect_block	'}' stmt_separator
			{
				$5->location = @4;
				$5->type = NFT_OBJECT_CT_EXPECT;
				handle_merge(&$5->handle, &$4);
				handle_free(&$4);
				list_add_tail(&$5->list, &$1->objs);
				$$ = $1;
			}
			|	table_block	LIMIT		obj_identifier
					obj_block_alloc	'{'	limit_block	'}'
					stmt_separator
			{
				$4->location = @3;
				$4->type = NFT_OBJECT_LIMIT;
				handle_merge(&$4->handle, &$3);
				handle_free(&$3);
				list_add_tail(&$4->list, &$1->objs);
				$$ = $1;
			}
			|	table_block	SECMARK		obj_identifier
					obj_block_alloc	'{'	secmark_block	'}'
					stmt_separator
			{
				$4->location = @3;
				$4->type = NFT_OBJECT_SECMARK;
				handle_merge(&$4->handle, &$3);
				handle_free(&$3);
				list_add_tail(&$4->list, &$1->objs);
				$$ = $1;
			}
			|	table_block	SYNPROXY	obj_identifier
					obj_block_alloc '{'	synproxy_block	'}'
					stmt_separator
			{
				$4->location = @3;
				$4->type = NFT_OBJECT_SYNPROXY;
				handle_merge(&$4->handle, &$3);
				handle_free(&$3);
				list_add_tail(&$4->list, &$1->objs);
				$$ = $1;
			}
			;

chain_block_alloc	:	/* empty */
			{
				$$ = chain_alloc(NULL);
				open_scope(state, &$$->scope);
			}
			;

chain_block		:	/* empty */	{ $$ = $<chain>-1; }
			|	chain_block	common_block
			|	chain_block	stmt_separator
			|	chain_block	hook_spec	stmt_separator
			|	chain_block	policy_spec	stmt_separator
			|	chain_block	rule		stmt_separator
			{
				list_add_tail(&$2->list, &$1->rules);
				$$ = $1;
			}
			;

set_block_alloc		:	/* empty */
			{
				$$ = set_alloc(NULL);
			}
			;

set_block		:	/* empty */	{ $$ = $<set>-1; }
			|	set_block	common_block
			|	set_block	stmt_separator
			|	set_block	TYPE		data_type_expr	stmt_separator
			{
				$1->key = $3;
				$$ = $1;
			}
			|	set_block	FLAGS		set_flag_list	stmt_separator
			{
				$1->flags = $3;
				$$ = $1;
			}
			|	set_block	TIMEOUT		time_spec	stmt_separator
			{
				$1->timeout = $3;
				$$ = $1;
			}
			|	set_block	GC_INTERVAL	time_spec	stmt_separator
			{
				$1->gc_int = $3;
				$$ = $1;
			}
			|	set_block	ELEMENTS	'='		set_block_expr
			{
				$1->init = $4;
				$$ = $1;
			}
			|	set_block	AUTOMERGE
			{
				$1->automerge = true;
				$$ = $1;
			}
			|	set_block	set_mechanism	stmt_separator
			;

set_block_expr		:	set_expr
			|	variable_expr
			;

set_flag_list		:	set_flag_list	COMMA		set_flag
			{
				$$ = $1 | $3;
			}
			|	set_flag
			;

set_flag		:	CONSTANT	{ $$ = NFT_SET_CONSTANT; }
			|	INTERVAL	{ $$ = NFT_SET_INTERVAL; }
			|	TIMEOUT		{ $$ = NFT_SET_TIMEOUT; }
			|	DYNAMIC		{ $$ = NFT_SET_EVAL; }
			;

map_block_alloc		:	/* empty */
			{
				$$ = set_alloc(NULL);
			}
			;

map_block		:	/* empty */	{ $$ = $<set>-1; }
			|	map_block	common_block
			|	map_block	stmt_separator
			|	map_block	TIMEOUT		time_spec	stmt_separator
			{
				$1->timeout = $3;
				$$ = $1;
			}
			|	map_block	TYPE
						data_type_expr	COLON	data_type_expr
						stmt_separator
			{
				$1->key = $3;
				$1->datatype = $5->dtype;

				expr_free($5);
				$1->flags |= NFT_SET_MAP;
				$$ = $1;
			}
			|	map_block	TYPE
						data_type_expr	COLON	COUNTER
						stmt_separator
			{
				$1->key = $3;
				$1->objtype = NFT_OBJECT_COUNTER;
				$1->flags  |= NFT_SET_OBJECT;
				$$ = $1;
			}
			|	map_block	TYPE
						data_type_expr	COLON	QUOTA
						stmt_separator
			{
				$1->key = $3;
				$1->objtype = NFT_OBJECT_QUOTA;
				$1->flags  |= NFT_SET_OBJECT;
				$$ = $1;
			}
			|	map_block	TYPE
						data_type_expr	COLON	LIMIT
						stmt_separator
			{
				$1->key = $3;
				$1->objtype = NFT_OBJECT_LIMIT;
				$1->flags  |= NFT_SET_OBJECT;
				$$ = $1;
			}
			|	map_block	TYPE
						data_type_expr	COLON	SECMARK
						stmt_separator
			{
				$1->key = $3;
				$1->objtype = NFT_OBJECT_SECMARK;
				$1->flags  |= NFT_SET_OBJECT;
				$$ = $1;
			}
			|	map_block	FLAGS		set_flag_list	stmt_separator
			{
				$1->flags |= $3;
				$$ = $1;
			}
			|	map_block	ELEMENTS	'='		set_block_expr
			{
				$1->init = $4;
				$$ = $1;
			}
			|	map_block	set_mechanism	stmt_separator
			;

set_mechanism		:	POLICY		set_policy_spec
			{
				$<set>0->policy = $2;
			}
			|	SIZE		NUM
			{
				$<set>0->desc.size = $2;
			}
			;

set_policy_spec		:	PERFORMANCE	{ $$ = NFT_SET_POL_PERFORMANCE; }
			|	MEMORY		{ $$ = NFT_SET_POL_MEMORY; }
			;

flowtable_block_alloc	:	/* empty */
			{
				$$ = flowtable_alloc(NULL);
			}
			;

flowtable_block		:	/* empty */	{ $$ = $<flowtable>-1; }
			|	flowtable_block	common_block
			|	flowtable_block	stmt_separator
			|	flowtable_block	HOOK		STRING	prio_spec	stmt_separator
			{
				$$->hookstr	= chain_hookname_lookup($3);
				if ($$->hookstr == NULL) {
					erec_queue(error(&@3, "unknown chain hook %s", $3),
						   state->msgs);
					xfree($3);
					YYERROR;
				}
				xfree($3);

				$$->priority = $4;
			}
			|	flowtable_block	DEVICES		'='	flowtable_expr	stmt_separator
			{
				$$->dev_expr = $4;
			}
			;

flowtable_expr		:	'{'	flowtable_list_expr	'}'
			{
				$2->location = @$;
				$$ = $2;
			}
			;

flowtable_list_expr	:	flowtable_expr_member
			{
				$$ = compound_expr_alloc(&@$, EXPR_LIST);
				compound_expr_add($$, $1);
			}
			|	flowtable_list_expr	COMMA	flowtable_expr_member
			{
				compound_expr_add($1, $3);
				$$ = $1;
			}
			|	flowtable_list_expr	COMMA	opt_newline
			;

flowtable_expr_member	:	STRING
			{
				$$ = symbol_expr_alloc(&@$, SYMBOL_VALUE,
						       current_scope(state),
						       $1);
				xfree($1);
			}
			;

data_type_atom_expr	:	type_identifier
			{
				const struct datatype *dtype = datatype_lookup_byname($1);
				if (dtype == NULL) {
					erec_queue(error(&@1, "unknown datatype %s", $1),
						   state->msgs);
					YYERROR;
				}
				$$ = constant_expr_alloc(&@1, dtype, dtype->byteorder,
							 dtype->size, NULL);
				xfree($1);
			}
			|	TIME
			{
				$$ = constant_expr_alloc(&@1, &time_type, time_type.byteorder,
							 time_type.size, NULL);
			}
			;

data_type_expr		:	data_type_atom_expr
			|	data_type_expr	DOT	data_type_atom_expr
			{
				struct location rhs[] = {
					[1]	= @2,
					[2]	= @3,
				};

				$$ = handle_concat_expr(&@$, $$, $1, $3, rhs);
			}
			;

obj_block_alloc		:       /* empty */
			{
				$$ = obj_alloc(NULL);
			}
			;

counter_block		:	/* empty */	{ $$ = $<obj>-1; }
			|       counter_block     common_block
			|       counter_block     stmt_separator
			|       counter_block     counter_config
			{
				$$ = $1;
			}
			;

quota_block		:	/* empty */	{ $$ = $<obj>-1; }
			|       quota_block     common_block
			|       quota_block     stmt_separator
			|       quota_block     quota_config
			{
				$$ = $1;
			}
			;

ct_helper_block		:	/* empty */	{ $$ = $<obj>-1; }
			|       ct_helper_block     common_block
			|       ct_helper_block     stmt_separator
			|       ct_helper_block     ct_helper_config
			{
				$$ = $1;
			}
			;

ct_timeout_block	:	/*empty */	{ $$ = $<obj>-1; }
			|	ct_timeout_block     common_block
			|	ct_timeout_block     stmt_separator
			|	ct_timeout_block     ct_timeout_config
			{
				$$ = $1;
			}
			;

ct_expect_block		:	/*empty */	{ $$ = $<obj>-1; }
			|	ct_expect_block     common_block
			|	ct_expect_block     stmt_separator
			|	ct_expect_block     ct_expect_config
			{
				$$ = $1;
			}
			;

limit_block		:	/* empty */	{ $$ = $<obj>-1; }
			|       limit_block     common_block
			|       limit_block     stmt_separator
			|       limit_block     limit_config
			{
				$$ = $1;
			}
			;

secmark_block		:	/* empty */	{ $$ = $<obj>-1; }
			|       secmark_block     common_block
			|       secmark_block     stmt_separator
			|       secmark_block     secmark_config
			{
				$$ = $1;
			}
			;

synproxy_block		:	/* empty */	{ $$ = $<obj>-1; }
			|	synproxy_block	common_block
			|	synproxy_block	stmt_separator
			|	synproxy_block	synproxy_config
			{
				$$ = $1;
			}
			;

type_identifier		:	STRING	{ $$ = $1; }
			|	MARK	{ $$ = xstrdup("mark"); }
			|	DSCP	{ $$ = xstrdup("dscp"); }
			|	ECN	{ $$ = xstrdup("ecn"); }
			|	CLASSID { $$ = xstrdup("classid"); }
			;

hook_spec		:	TYPE		STRING		HOOK		STRING		dev_spec	prio_spec
			{
				const char *chain_type = chain_type_name_lookup($2);

				if (chain_type == NULL) {
					erec_queue(error(&@2, "unknown chain type %s", $2),
						   state->msgs);
					xfree($2);
					YYERROR;
				}
				$<chain>0->type		= xstrdup(chain_type);
				xfree($2);

				$<chain>0->hookstr	= chain_hookname_lookup($4);
				if ($<chain>0->hookstr == NULL) {
					erec_queue(error(&@4, "unknown chain hook %s", $4),
						   state->msgs);
					xfree($4);
					YYERROR;
				}
				xfree($4);

				$<chain>0->dev_expr	= $5;
				$<chain>0->priority	= $6;
				$<chain>0->flags	|= CHAIN_F_BASECHAIN;
			}
			;

prio_spec		:	PRIORITY extended_prio_spec
			{
				$$ = $2;
				$$.loc = @$;
			}
			;

extended_prio_name	:	OUT
			{
				$$ = strdup("out");
			}
			|	STRING
			;

extended_prio_spec	:	int_num
			{
				struct prio_spec spec = {0};

				spec.expr = constant_expr_alloc(&@$, &integer_type,
								BYTEORDER_HOST_ENDIAN,
								sizeof(int) *
								BITS_PER_BYTE, &$1);
				$$ = spec;
			}
			|	variable_expr
			{
				struct prio_spec spec = {0};

				datatype_set($1->sym->expr, &priority_type);
				spec.expr = $1;
				$$ = spec;
			}
			|	extended_prio_name
			{
				struct prio_spec spec = {0};

				spec.expr = constant_expr_alloc(&@$, &string_type,
								BYTEORDER_HOST_ENDIAN,
								strlen($1) * BITS_PER_BYTE,
								$1);
				xfree($1);
				$$ = spec;
			}
			|	extended_prio_name PLUS NUM
			{
				struct prio_spec spec = {0};

				char str[NFT_NAME_MAXLEN];
				snprintf(str, sizeof(str), "%s + %" PRIu64, $1, $3);
				spec.expr = constant_expr_alloc(&@$, &string_type,
								BYTEORDER_HOST_ENDIAN,
								strlen(str) * BITS_PER_BYTE,
								str);
				xfree($1);
				$$ = spec;
			}
			|	extended_prio_name DASH NUM
			{
				struct prio_spec spec = {0};
				char str[NFT_NAME_MAXLEN];

				snprintf(str, sizeof(str), "%s - %" PRIu64, $1, $3);
				spec.expr = constant_expr_alloc(&@$, &string_type,
								BYTEORDER_HOST_ENDIAN,
								strlen(str) * BITS_PER_BYTE,
								str);
				$$ = spec;
			}
			;

int_num			:	NUM			{ $$ = $1; }
			|	DASH	NUM		{ $$ = -$2; }
			;

dev_spec		:	DEVICE	string
			{
				struct expr *expr;

				expr = constant_expr_alloc(&@$, &string_type,
							   BYTEORDER_HOST_ENDIAN,
							   strlen($2) * BITS_PER_BYTE, $2);
				$$ = compound_expr_alloc(&@$, EXPR_LIST);
				compound_expr_add($$, expr);

			}
			|	DEVICES		'='	flowtable_expr
			{
				$$ = $3;
			}
			|	/* empty */		{ $$ = NULL; }
			;

policy_spec		:	POLICY		policy_expr
			{
				if ($<chain>0->policy) {
					erec_queue(error(&@$, "you cannot set chain policy twice"),
						   state->msgs);
					expr_free($2);
					YYERROR;
				}
				$<chain>0->policy	= $2;
			}
			;

policy_expr		:	variable_expr
			{
				datatype_set($1->sym->expr, &policy_type);
				$$ = $1;
			}
			|	chain_policy
			{
				$$ = constant_expr_alloc(&@$, &integer_type,
							 BYTEORDER_HOST_ENDIAN,
							 sizeof(int) *
							 BITS_PER_BYTE, &$1);
			}
			;

chain_policy		:	ACCEPT		{ $$ = NF_ACCEPT; }
			|	DROP		{ $$ = NF_DROP;   }
			;

identifier		:	STRING
			;

string			:	STRING
			|	QUOTED_STRING
			|	ASTERISK_STRING
			;

time_spec		:	STRING
			{
				struct error_record *erec;
				uint64_t res;

				erec = time_parse(&@1, $1, &res);
				xfree($1);
				if (erec != NULL) {
					erec_queue(erec, state->msgs);
					YYERROR;
				}
				$$ = res;
			}
			;

family_spec		:	/* empty */		{ $$ = NFPROTO_IPV4; }
			|	family_spec_explicit
			;

family_spec_explicit	:	IP		{ $$ = NFPROTO_IPV4; }
			|	IP6		{ $$ = NFPROTO_IPV6; }
			|	INET		{ $$ = NFPROTO_INET; }
			|	ARP		{ $$ = NFPROTO_ARP; }
			|	BRIDGE		{ $$ = NFPROTO_BRIDGE; }
			|	NETDEV		{ $$ = NFPROTO_NETDEV; }
			;

table_spec		:	family_spec	identifier
			{
				memset(&$$, 0, sizeof($$));
				$$.family	= $1;
				$$.table.location = @2;
				$$.table.name	= $2;
			}
			;

tableid_spec 		: 	family_spec 	HANDLE NUM
			{
				memset(&$$, 0, sizeof($$));
				$$.family 		= $1;
				$$.handle.id 		= $3;
				$$.handle.location	= @$;
			}
			;

chain_spec		:	table_spec	identifier
			{
				$$		= $1;
				$$.chain.name	= $2;
				$$.chain.location = @2;
			}
			;

chainid_spec 		: 	table_spec 	HANDLE NUM
			{
				$$ 			= $1;
				$$.handle.location 	= @$;
				$$.handle.id 		= $3;
			}
			;

chain_identifier	:	identifier
			{
				memset(&$$, 0, sizeof($$));
				$$.chain.name		= $1;
				$$.chain.location	= @1;
			}
			;

set_spec		:	table_spec	identifier
			{
				$$		= $1;
				$$.set.name	= $2;
				$$.set.location	= @2;
			}
			;

setid_spec 		: 	table_spec 	HANDLE NUM
			{
				$$ 			= $1;
				$$.handle.location 	= @$;
				$$.handle.id 		= $3;
			}
			;

set_identifier		:	identifier
			{
				memset(&$$, 0, sizeof($$));
				$$.set.name	= $1;
				$$.set.location	= @1;
			}
			;

flowtable_spec		:	table_spec	identifier
			{
				$$			= $1;
				$$.flowtable.name	= $2;
				$$.flowtable.location	= @2;
			}
			;

flowtableid_spec	:	table_spec	HANDLE NUM
			{
				$$			= $1;
				$$.handle.location	= @$;
				$$.handle.id		= $3;
			}
			;

flowtable_identifier	:	identifier
			{
				memset(&$$, 0, sizeof($$));
				$$.flowtable.name	= $1;
				$$.flowtable.location	= @1;
			}
			;

obj_spec		:	table_spec	identifier
			{
				$$		= $1;
				$$.obj.name	= $2;
				$$.obj.location	= @2;
			}
			;

objid_spec		:	table_spec	HANDLE NUM
			{
				$$ 			= $1;
				$$.handle.location	= @$;
				$$.handle.id		= $3;
			}
			;

obj_identifier		:	identifier
			{
				memset(&$$, 0, sizeof($$));
				$$.obj.name		= $1;
				$$.obj.location		= @1;
			}
			;

handle_spec		:	HANDLE		NUM
			{
				memset(&$$, 0, sizeof($$));
				$$.handle.location	= @$;
				$$.handle.id		= $2;
			}
			;

position_spec		:	POSITION	NUM
			{
				memset(&$$, 0, sizeof($$));
				$$.position.location	= @$;
				$$.position.id		= $2;
			}
			;

index_spec		:	INDEX		NUM
			{
				memset(&$$, 0, sizeof($$));
				$$.index.location	= @$;
				$$.index.id		= $2 + 1;
			}
			;

rule_position		:	chain_spec
			{
				$$ = $1;
			}
			|	chain_spec	position_spec
			{
				handle_merge(&$1, &$2);
				$$ = $1;
			}
			|	chain_spec	handle_spec
			{
				$2.position.location = $2.handle.location;
				$2.position.id = $2.handle.id;
				$2.handle.id = 0;
				handle_merge(&$1, &$2);
				$$ = $1;
			}
			|	chain_spec	index_spec
			{
				handle_merge(&$1, &$2);
				$$ = $1;
			}
			;

ruleid_spec		:	chain_spec	handle_spec
			{
				handle_merge(&$1, &$2);
				$$ = $1;
			}
			;

comment_spec		:	COMMENT		string
			{
				if (strlen($2) > NFTNL_UDATA_COMMENT_MAXLEN) {
					erec_queue(error(&@2, "comment too long, %d characters maximum allowed",
							 NFTNL_UDATA_COMMENT_MAXLEN),
						   state->msgs);
					YYERROR;
				}
				$$ = $2;
			}
			;

ruleset_spec		:	/* empty */
			{
				memset(&$$, 0, sizeof($$));
				$$.family	= NFPROTO_UNSPEC;
			}
			|	family_spec_explicit
			{
				memset(&$$, 0, sizeof($$));
				$$.family	= $1;
			}
			;

rule			:	rule_alloc
			{
				$$->comment = NULL;
			}
			|	rule_alloc	comment_spec
			{
				$$->comment = $2;
			}
			;

rule_alloc		:	stmt_list
			{
				struct stmt *i;

				$$ = rule_alloc(&@$, NULL);
				list_for_each_entry(i, $1, list)
					$$->num_stmts++;
				list_splice_tail($1, &$$->stmts);
				xfree($1);
			}
			;

stmt_list		:	stmt
			{
				$$ = xmalloc(sizeof(*$$));
				init_list_head($$);
				list_add_tail(&$1->list, $$);
			}
			|	stmt_list		stmt
			{
				$$ = $1;
				list_add_tail(&$2->list, $1);
			}
			;

stateful_stmt		:	counter_stmt
			|	limit_stmt
			|	quota_stmt
			|	connlimit_stmt
			;

stmt			:	verdict_stmt
			|	match_stmt
			|	meter_stmt
			|	payload_stmt
			|	stateful_stmt
			|	meta_stmt
			|	log_stmt
			|	reject_stmt
			|	nat_stmt
			|	tproxy_stmt
			|	queue_stmt
			|	ct_stmt
			|	masq_stmt
			|	redir_stmt
			|	dup_stmt
			|	fwd_stmt
			|	set_stmt
			|	map_stmt
			|	synproxy_stmt
			;

verdict_stmt		:	verdict_expr
			{
				$$ = verdict_stmt_alloc(&@$, $1);
			}
			|	verdict_map_stmt
			{
				$$ = verdict_stmt_alloc(&@$, $1);
			}
			;

verdict_map_stmt	:	concat_expr	VMAP	verdict_map_expr
			{
				$$ = map_expr_alloc(&@$, $1, $3);
			}
			;

verdict_map_expr	:	'{'	verdict_map_list_expr	'}'
			{
				$2->location = @$;
				$$ = $2;
			}
			|	set_ref_expr
			;

verdict_map_list_expr	:	verdict_map_list_member_expr
			{
				$$ = set_expr_alloc(&@$, NULL);
				compound_expr_add($$, $1);
			}
			|	verdict_map_list_expr	COMMA	verdict_map_list_member_expr
			{
				compound_expr_add($1, $3);
				$$ = $1;
			}
			|	verdict_map_list_expr	COMMA	opt_newline
			;

verdict_map_list_member_expr:	opt_newline	set_elem_expr	COLON	verdict_expr	opt_newline
			{
				$$ = mapping_expr_alloc(&@$, $2, $4);
			}
			;

connlimit_stmt		:	CT	COUNT	NUM
			{
				$$ = connlimit_stmt_alloc(&@$);
				$$->connlimit.count	= $3;
			}
			|	CT	COUNT	OVER	NUM
			{
				$$ = connlimit_stmt_alloc(&@$);
				$$->connlimit.count = $4;
				$$->connlimit.flags = NFT_CONNLIMIT_F_INV;
			}
			;

counter_stmt		:	counter_stmt_alloc
			|	counter_stmt_alloc	counter_args

counter_stmt_alloc	:	COUNTER
			{
				$$ = counter_stmt_alloc(&@$);
			}
			|	COUNTER		NAME	stmt_expr
			{
				$$ = objref_stmt_alloc(&@$);
				$$->objref.type = NFT_OBJECT_COUNTER;
				$$->objref.expr = $3;
			}
			;

counter_args		:	counter_arg
			{
				$<stmt>$	= $<stmt>0;
			}
			|	counter_args	counter_arg
			;

counter_arg		:	PACKETS			NUM
			{
				$<stmt>0->counter.packets = $2;
			}
			|	BYTES			NUM
			{
				$<stmt>0->counter.bytes	 = $2;
			}
			;

log_stmt		:	log_stmt_alloc
			|	log_stmt_alloc		log_args
			;

log_stmt_alloc		:	LOG
			{
				$$ = log_stmt_alloc(&@$);
			}
			;

log_args		:	log_arg
			{
				$<stmt>$	= $<stmt>0;
			}
			|	log_args	log_arg
			;

log_arg			:	PREFIX			string
			{
				$<stmt>0->log.prefix	 = $2;
				$<stmt>0->log.flags 	|= STMT_LOG_PREFIX;
			}
			|	GROUP			NUM
			{
				$<stmt>0->log.group	 = $2;
				$<stmt>0->log.flags 	|= STMT_LOG_GROUP;
			}
			|	SNAPLEN			NUM
			{
				$<stmt>0->log.snaplen	 = $2;
				$<stmt>0->log.flags 	|= STMT_LOG_SNAPLEN;
			}
			|	QUEUE_THRESHOLD		NUM
			{
				$<stmt>0->log.qthreshold = $2;
				$<stmt>0->log.flags 	|= STMT_LOG_QTHRESHOLD;
			}
			|	LEVEL			level_type
			{
				$<stmt>0->log.level	= $2;
				$<stmt>0->log.flags 	|= STMT_LOG_LEVEL;
			}
			|	FLAGS			log_flags
			{
				$<stmt>0->log.logflags	|= $2;
			}
			;

level_type		:	string
			{
				if (!strcmp("emerg", $1))
					$$ = NFT_LOGLEVEL_EMERG;
				else if (!strcmp("alert", $1))
					$$ = NFT_LOGLEVEL_ALERT;
				else if (!strcmp("crit", $1))
					$$ = NFT_LOGLEVEL_CRIT;
				else if (!strcmp("err", $1))
					$$ = NFT_LOGLEVEL_ERR;
				else if (!strcmp("warn", $1))
					$$ = NFT_LOGLEVEL_WARNING;
				else if (!strcmp("notice", $1))
					$$ = NFT_LOGLEVEL_NOTICE;
				else if (!strcmp("info", $1))
					$$ = NFT_LOGLEVEL_INFO;
				else if (!strcmp("debug", $1))
					$$ = NFT_LOGLEVEL_DEBUG;
				else if (!strcmp("audit", $1))
					$$ = NFT_LOGLEVEL_AUDIT;
				else {
					erec_queue(error(&@1, "invalid log level"),
						   state->msgs);
					xfree($1);
					YYERROR;
				}
				xfree($1);
			}
			;

log_flags		:	TCP	log_flags_tcp
			{
				$$ = $2;
			}
			|	IP	OPTIONS
			{
				$$ = NF_LOG_IPOPT;
			}
			|	SKUID
			{
				$$ = NF_LOG_UID;
			}
			|	ETHER
			{
				$$ = NF_LOG_MACDECODE;
			}
			|	ALL
			{
				$$ = NF_LOG_MASK;
			}
			;

log_flags_tcp		:	log_flags_tcp	COMMA	log_flag_tcp
			{
				$$ = $1 | $3;
			}
			|	log_flag_tcp
			;

log_flag_tcp		:	SEQUENCE
			{
				$$ = NF_LOG_TCPSEQ;
			}
			|	OPTIONS
			{
				$$ = NF_LOG_TCPOPT;
			}
			;

limit_stmt		:	LIMIT	RATE	limit_mode	NUM	SLASH	time_unit	limit_burst_pkts
	    		{
				$$ = limit_stmt_alloc(&@$);
				$$->limit.rate	= $4;
				$$->limit.unit	= $6;
				$$->limit.burst	= $7;
				$$->limit.type	= NFT_LIMIT_PKTS;
				$$->limit.flags = $3;
			}
			|	LIMIT	RATE	limit_mode	NUM	STRING	limit_burst_bytes
			{
				struct error_record *erec;
				uint64_t rate, unit;

				erec = rate_parse(&@$, $5, &rate, &unit);
				xfree($5);
				if (erec != NULL) {
					erec_queue(erec, state->msgs);
					YYERROR;
				}

				$$ = limit_stmt_alloc(&@$);
				$$->limit.rate	= rate * $4;
				$$->limit.unit	= unit;
				$$->limit.burst	= $6;
				$$->limit.type	= NFT_LIMIT_PKT_BYTES;
				$$->limit.flags = $3;
			}
			|	LIMIT	NAME	stmt_expr
			{
				$$ = objref_stmt_alloc(&@$);
				$$->objref.type = NFT_OBJECT_LIMIT;
				$$->objref.expr = $3;
			}
			;

quota_mode		:	OVER		{ $$ = NFT_QUOTA_F_INV; }
			|	UNTIL		{ $$ = 0; }
			|	/* empty */	{ $$ = 0; }
			;

quota_unit		:	BYTES		{ $$ = xstrdup("bytes"); }
			|	STRING		{ $$ = $1; }
			;

quota_used		:	/* empty */	{ $$ = 0; }
			|	USED NUM quota_unit
			{
				struct error_record *erec;
				uint64_t rate;

				erec = data_unit_parse(&@$, $3, &rate);
				xfree($3);
				if (erec != NULL) {
					erec_queue(erec, state->msgs);
					YYERROR;
				}
				$$ = $2 * rate;
			}
			;

quota_stmt		:	QUOTA	quota_mode NUM quota_unit quota_used
			{
				struct error_record *erec;
				uint64_t rate;

				erec = data_unit_parse(&@$, $4, &rate);
				xfree($4);
				if (erec != NULL) {
					erec_queue(erec, state->msgs);
					YYERROR;
				}
				$$ = quota_stmt_alloc(&@$);
				$$->quota.bytes	= $3 * rate;
				$$->quota.used = $5;
				$$->quota.flags	= $2;
			}
			|	QUOTA	NAME	stmt_expr
			{
				$$ = objref_stmt_alloc(&@$);
				$$->objref.type = NFT_OBJECT_QUOTA;
				$$->objref.expr = $3;
			}
			;

limit_mode		:	OVER				{ $$ = NFT_LIMIT_F_INV; }
			|	UNTIL				{ $$ = 0; }
			|	/* empty */			{ $$ = 0; }
			;

limit_burst_pkts	:	/* empty */			{ $$ = 0; }
			|	BURST	NUM	PACKETS		{ $$ = $2; }
			;

limit_burst_bytes	:	/* empty */			{ $$ = 0; }
			|	BURST	NUM	BYTES		{ $$ = $2; }
			|	BURST	NUM	STRING
			{
				struct error_record *erec;
				uint64_t rate;

				erec = data_unit_parse(&@$, $3, &rate);
				xfree($3);
				if (erec != NULL) {
					erec_queue(erec, state->msgs);
					YYERROR;
				}
				$$ = $2 * rate;
			}
			;

time_unit		:	SECOND		{ $$ = 1ULL; }
			|	MINUTE		{ $$ = 1ULL * 60; }
			|	HOUR		{ $$ = 1ULL * 60 * 60; }
			|	DAY		{ $$ = 1ULL * 60 * 60 * 24; }
			|	WEEK		{ $$ = 1ULL * 60 * 60 * 24 * 7; }
			;

reject_stmt		:	reject_stmt_alloc	reject_opts
			;

reject_stmt_alloc	:	_REJECT
			{
				$$ = reject_stmt_alloc(&@$);
			}
			;

reject_opts		:       /* empty */
			{
				$<stmt>0->reject.type = -1;
				$<stmt>0->reject.icmp_code = -1;
			}
			|	WITH	ICMP	TYPE	STRING
			{
				$<stmt>0->reject.family = NFPROTO_IPV4;
				$<stmt>0->reject.type = NFT_REJECT_ICMP_UNREACH;
				$<stmt>0->reject.expr =
					symbol_expr_alloc(&@$, SYMBOL_VALUE,
							  current_scope(state),
							  $4);
				datatype_set($<stmt>0->reject.expr, &icmp_code_type);
				xfree($4);
			}
			|	WITH	ICMP6	TYPE	STRING
			{
				$<stmt>0->reject.family = NFPROTO_IPV6;
				$<stmt>0->reject.type = NFT_REJECT_ICMP_UNREACH;
				$<stmt>0->reject.expr =
					symbol_expr_alloc(&@$, SYMBOL_VALUE,
							  current_scope(state),
							  $4);
				datatype_set($<stmt>0->reject.expr, &icmpv6_code_type);
				xfree($4);
			}
			|	WITH	ICMPX	TYPE	STRING
			{
				$<stmt>0->reject.type = NFT_REJECT_ICMPX_UNREACH;
				$<stmt>0->reject.expr =
					symbol_expr_alloc(&@$, SYMBOL_VALUE,
							  current_scope(state),
							  $4);
				datatype_set($<stmt>0->reject.expr, &icmpx_code_type);
				xfree($4);
			}
			|	WITH	TCP	RESET
			{
				$<stmt>0->reject.type = NFT_REJECT_TCP_RST;
			}
			;

nat_stmt		:	nat_stmt_alloc	nat_stmt_args
			;

nat_stmt_alloc		:	SNAT	{ $$ = nat_stmt_alloc(&@$, NFT_NAT_SNAT); }
			|	DNAT	{ $$ = nat_stmt_alloc(&@$, NFT_NAT_DNAT); }
			;

tproxy_stmt		:	TPROXY TO stmt_expr
			{
				$$ = tproxy_stmt_alloc(&@$);
				$$->tproxy.family = NFPROTO_UNSPEC;
				$$->tproxy.addr = $3;
			}
			|	TPROXY nf_key_proto TO stmt_expr
			{
				$$ = tproxy_stmt_alloc(&@$);
				$$->tproxy.family = $2;
				$$->tproxy.addr = $4;
			}
			|	TPROXY TO COLON stmt_expr
			{
				$$ = tproxy_stmt_alloc(&@$);
				$$->tproxy.family = NFPROTO_UNSPEC;
				$$->tproxy.port = $4;
			}
			|	TPROXY TO stmt_expr COLON stmt_expr
			{
				$$ = tproxy_stmt_alloc(&@$);
				$$->tproxy.family = NFPROTO_UNSPEC;
				$$->tproxy.addr = $3;
				$$->tproxy.port = $5;
			}
			|	TPROXY nf_key_proto	TO stmt_expr COLON stmt_expr
			{
				$$ = tproxy_stmt_alloc(&@$);
				$$->tproxy.family = $2;
				$$->tproxy.addr = $4;
				$$->tproxy.port = $6;
			}
			|	TPROXY nf_key_proto	TO COLON stmt_expr
			{
				$$ = tproxy_stmt_alloc(&@$);
				$$->tproxy.family = $2;
				$$->tproxy.port = $5;
			}
			;

synproxy_stmt		:	synproxy_stmt_alloc
			|	synproxy_stmt_alloc	synproxy_args
			;

synproxy_stmt_alloc	:	SYNPROXY
			{
				$$ = synproxy_stmt_alloc(&@$);
			}
			|	SYNPROXY	NAME	stmt_expr
			{
				$$ = objref_stmt_alloc(&@$);
				$$->objref.type = NFT_OBJECT_SYNPROXY;
				$$->objref.expr = $3;
			}
			;

synproxy_args		:	synproxy_arg
			{
				$<stmt>$	= $<stmt>0;
			}
			|	synproxy_args	synproxy_arg
			;

synproxy_arg		:	MSS	NUM
			{
				$<stmt>0->synproxy.mss = $2;
				$<stmt>0->synproxy.flags |= NF_SYNPROXY_OPT_MSS;
			}
			|	WSCALE	NUM
			{
				$<stmt>0->synproxy.wscale = $2;
				$<stmt>0->synproxy.flags |= NF_SYNPROXY_OPT_WSCALE;
			}
			|	TIMESTAMP
			{
				$<stmt>0->synproxy.flags |= NF_SYNPROXY_OPT_TIMESTAMP;
			}
			|	SACKPERM
			{
				$<stmt>0->synproxy.flags |= NF_SYNPROXY_OPT_SACK_PERM;
			}
			;

synproxy_config		:	MSS	NUM	WSCALE	NUM	synproxy_ts	synproxy_sack
			{
				struct synproxy *synproxy;
				uint32_t flags = 0;

				synproxy = &$<obj>0->synproxy;
				synproxy->mss = $2;
				flags |= NF_SYNPROXY_OPT_MSS;
				synproxy->wscale = $4;
				flags |= NF_SYNPROXY_OPT_WSCALE;
				if ($5)
					flags |= $5;
				if ($6)
					flags |= $6;
				synproxy->flags = flags;
			}
			|	MSS	NUM	stmt_separator	WSCALE	NUM	stmt_separator	synproxy_ts	synproxy_sack
			{
				struct synproxy *synproxy;
				uint32_t flags = 0;

				synproxy = &$<obj>0->synproxy;
				synproxy->mss = $2;
				flags |= NF_SYNPROXY_OPT_MSS;
				synproxy->wscale = $5;
				flags |= NF_SYNPROXY_OPT_WSCALE;
				if ($7)
					flags |= $7;
				if ($8)
					flags |= $8;
				synproxy->flags = flags;
			}
			;

synproxy_obj		:	/* empty */
			{
				$$ = obj_alloc(&@$);
				$$->type = NFT_OBJECT_SYNPROXY;
			}
			;

synproxy_ts		:	/* empty */	{ $$ = 0; }
			|	TIMESTAMP
			{
				$$ = NF_SYNPROXY_OPT_TIMESTAMP;
			}
			;

synproxy_sack		:	/* empty */	{ $$ = 0; }
			|	SACKPERM
			{
				$$ = NF_SYNPROXY_OPT_SACK_PERM;
			}
			;

primary_stmt_expr	:	symbol_expr		{ $$ = $1; }
			|	integer_expr		{ $$ = $1; }
			|	boolean_expr		{ $$ = $1; }
			|	meta_expr		{ $$ = $1; }
			|	rt_expr			{ $$ = $1; }
			|	ct_expr			{ $$ = $1; }
			|	numgen_expr             { $$ = $1; }
			|	hash_expr               { $$ = $1; }
			|	payload_expr		{ $$ = $1; }
			|	keyword_expr		{ $$ = $1; }
			|	socket_expr		{ $$ = $1; }
			|	osf_expr		{ $$ = $1; }
			;

shift_stmt_expr		:	primary_stmt_expr
			|	shift_stmt_expr		LSHIFT		primary_stmt_expr
			{
				$$ = binop_expr_alloc(&@$, OP_LSHIFT, $1, $3);
			}
			|	shift_stmt_expr		RSHIFT		primary_rhs_expr
			{
				$$ = binop_expr_alloc(&@$, OP_RSHIFT, $1, $3);
			}
			;

and_stmt_expr		:	shift_stmt_expr
			|	and_stmt_expr		AMPERSAND	shift_stmt_expr
			{
				$$ = binop_expr_alloc(&@$, OP_AND, $1, $3);
			}
			;

exclusive_or_stmt_expr	:	and_stmt_expr
			|	exclusive_or_stmt_expr	CARET		and_stmt_expr
			{
				$$ = binop_expr_alloc(&@$, OP_XOR, $1, $3);
			}
			;

inclusive_or_stmt_expr	:	exclusive_or_stmt_expr
			|	inclusive_or_stmt_expr	'|'		exclusive_or_stmt_expr
			{
				$$ = binop_expr_alloc(&@$, OP_OR, $1, $3);
			}
			;

basic_stmt_expr		:	inclusive_or_stmt_expr
			;

concat_stmt_expr	:	basic_stmt_expr
			|	concat_stmt_expr	DOT	primary_stmt_expr
			{
				struct location rhs[] = {
					[1]	= @2,
					[2]	= @3,
				};

				$$ = handle_concat_expr(&@$, $$, $1, $3, rhs);
			}
			;

map_stmt_expr_set	:	set_expr
			|	set_ref_expr
			;

map_stmt_expr		:	concat_stmt_expr	MAP	map_stmt_expr_set
			{
				$$ = map_expr_alloc(&@$, $1, $3);
			}
			|	concat_stmt_expr	{ $$ = $1; }
			;

prefix_stmt_expr	:	basic_stmt_expr	SLASH	NUM
			{
				$$ = prefix_expr_alloc(&@$, $1, $3);
			}
			;

range_stmt_expr		:	basic_stmt_expr	DASH	basic_stmt_expr
			{
				$$ = range_expr_alloc(&@$, $1, $3);
			}
			;

wildcard_expr		:	ASTERISK
			{
				struct expr *expr;

				expr = constant_expr_alloc(&@$, &integer_type,
							   BYTEORDER_HOST_ENDIAN,
							   0, NULL);
				$$ = prefix_expr_alloc(&@$, expr, 0);
			}
			;

multiton_stmt_expr	:	prefix_stmt_expr
			|	range_stmt_expr
			|	wildcard_expr
			;

stmt_expr		:	map_stmt_expr
			|	multiton_stmt_expr
			|	list_stmt_expr
			;

nat_stmt_args		:	stmt_expr
			{
				$<stmt>0->nat.addr = $1;
			}
			|	TO	stmt_expr
			{
				$<stmt>0->nat.addr = $2;
			}
			|	nf_key_proto	TO	stmt_expr
			{
				$<stmt>0->nat.family = $1;
				$<stmt>0->nat.addr = $3;
			}
			|	stmt_expr	COLON	stmt_expr
			{
				$<stmt>0->nat.addr = $1;
				$<stmt>0->nat.proto = $3;
			}
			|	TO	stmt_expr	COLON	stmt_expr
			{
				$<stmt>0->nat.addr = $2;
				$<stmt>0->nat.proto = $4;
			}
			|	nf_key_proto	TO	 stmt_expr	COLON	stmt_expr
			{
				$<stmt>0->nat.family = $1;
				$<stmt>0->nat.addr = $3;
				$<stmt>0->nat.proto = $5;
			}
			|	COLON		stmt_expr
			{
				$<stmt>0->nat.proto = $2;
			}
			|	TO	COLON		stmt_expr
			{
				$<stmt>0->nat.proto = $3;
			}
			|       nat_stmt_args   nf_nat_flags
			{
				$<stmt>0->nat.flags = $2;
			}
			;

masq_stmt		:	masq_stmt_alloc		masq_stmt_args
			|	masq_stmt_alloc
			;

masq_stmt_alloc		:	MASQUERADE	{ $$ = nat_stmt_alloc(&@$, NFT_NAT_MASQ); }
			;

masq_stmt_args		:	TO 	COLON	stmt_expr
			{
				$<stmt>0->nat.proto = $3;
			}
			|	TO 	COLON	stmt_expr	nf_nat_flags
			{
				$<stmt>0->nat.proto = $3;
				$<stmt>0->nat.flags = $4;
			}
			|	nf_nat_flags
			{
				$<stmt>0->nat.flags = $1;
			}
			;

redir_stmt		:	redir_stmt_alloc	redir_stmt_arg
			|	redir_stmt_alloc
			;

redir_stmt_alloc	:	REDIRECT	{ $$ = nat_stmt_alloc(&@$, NFT_NAT_REDIR); }
			;

redir_stmt_arg		:	TO	stmt_expr
			{
				$<stmt>0->nat.proto = $2;
			}
			|	TO	COLON	stmt_expr
			{
				$<stmt>0->nat.proto = $3;
			}
			|	nf_nat_flags
			{
				$<stmt>0->nat.flags = $1;
			}
			|	TO	stmt_expr	nf_nat_flags
			{
				$<stmt>0->nat.proto = $2;
				$<stmt>0->nat.flags = $3;
			}
			|	TO	COLON	stmt_expr	nf_nat_flags
			{
				$<stmt>0->nat.proto = $3;
				$<stmt>0->nat.flags = $4;
			}
			;

dup_stmt		:	DUP	TO	stmt_expr
			{
				$$ = dup_stmt_alloc(&@$);
				$$->dup.to = $3;
			}
			|	DUP	TO	stmt_expr 	DEVICE	stmt_expr
			{
				$$ = dup_stmt_alloc(&@$);
				$$->dup.to = $3;
				$$->dup.dev = $5;
			}
			;

fwd_stmt		:	FWD	TO	stmt_expr
			{
				$$ = fwd_stmt_alloc(&@$);
				$$->fwd.dev = $3;
			}
			|	FWD	nf_key_proto	TO	stmt_expr	DEVICE	stmt_expr
			{
				$$ = fwd_stmt_alloc(&@$);
				$$->fwd.family = $2;
				$$->fwd.addr = $4;
				$$->fwd.dev = $6;
			}
			;

nf_nat_flags		:	nf_nat_flag
			|	nf_nat_flags	COMMA	nf_nat_flag
			{
				$$ = $1 | $3;
			}
			;

nf_nat_flag		:	RANDOM		{ $$ = NF_NAT_RANGE_PROTO_RANDOM; }
			|	FULLY_RANDOM	{ $$ = NF_NAT_RANGE_PROTO_RANDOM_FULLY; }
			|	PERSISTENT 	{ $$ = NF_NAT_RANGE_PERSISTENT; }
			;

queue_stmt		:	queue_stmt_alloc
			|	queue_stmt_alloc	queue_stmt_args
			;

queue_stmt_alloc	:	QUEUE
			{
				$$ = queue_stmt_alloc(&@$);
			}
			;

queue_stmt_args		:	queue_stmt_arg
			{
				$<stmt>$	= $<stmt>0;
			}
			|	queue_stmt_args	queue_stmt_arg
			;

queue_stmt_arg		:	QUEUENUM	stmt_expr
			{
				$<stmt>0->queue.queue = $2;
				$<stmt>0->queue.queue->location = @$;
			}
			|	queue_stmt_flags
			{
				$<stmt>0->queue.flags |= $1;
			}
			;

queue_stmt_flags	:	queue_stmt_flag
			|	queue_stmt_flags	COMMA	queue_stmt_flag
			{
				$$ = $1 | $3;
			}
			;

queue_stmt_flag		:	BYPASS	{ $$ = NFT_QUEUE_FLAG_BYPASS; }
			|	FANOUT	{ $$ = NFT_QUEUE_FLAG_CPU_FANOUT; }
			;

set_elem_expr_stmt	:	set_elem_expr_stmt_alloc
			|	set_elem_expr_stmt_alloc	set_elem_options
			;

set_elem_expr_stmt_alloc:	concat_expr
			{
				$$ = set_elem_expr_alloc(&@1, $1);
			}
			;

set_stmt		:	SET	set_stmt_op	set_elem_expr_stmt	set_ref_expr
			{
				$$ = set_stmt_alloc(&@$);
				$$->set.op  = $2;
				$$->set.key = $3;
				$$->set.set = $4;
			}
			|	set_stmt_op	set_ref_expr	'{' set_elem_expr_stmt	'}'
			{
				$$ = set_stmt_alloc(&@$);
				$$->set.op  = $1;
				$$->set.key = $4;
				$$->set.set = $2;
			}
			|	set_stmt_op	set_ref_expr '{' set_elem_expr_stmt	stateful_stmt	'}'
			{
				$$ = set_stmt_alloc(&@$);
				$$->set.op  = $1;
				$$->set.key = $4;
				$$->set.set = $2;
				$$->set.stmt = $5;
			}
			;

set_stmt_op		:	ADD	{ $$ = NFT_DYNSET_OP_ADD; }
			|	UPDATE	{ $$ = NFT_DYNSET_OP_UPDATE; }
			|	DELETE  { $$ = NFT_DYNSET_OP_DELETE; }
			;

map_stmt		:	set_stmt_op	set_ref_expr '{' set_elem_expr_stmt	COLON	set_elem_expr_stmt	'}'
			{
				$$ = map_stmt_alloc(&@$);
				$$->map.op  = $1;
				$$->map.key = $4;
				$$->map.data = $6;
				$$->map.set = $2;
			}
			|	set_stmt_op	set_ref_expr '{' set_elem_expr_stmt	stateful_stmt COLON	set_elem_expr_stmt	'}'
			{
				$$ = map_stmt_alloc(&@$);
				$$->map.op  = $1;
				$$->map.key = $4;
				$$->map.data = $7;
				$$->map.stmt = $5;
				$$->map.set = $2;
			}
			;

meter_stmt		:	flow_stmt_legacy_alloc		flow_stmt_opts	'{' meter_key_expr stmt '}'
			{
				$1->meter.key  = $4;
				$1->meter.stmt = $5;
				$$->location  = @$;
				$$ = $1;
			}
			|	meter_stmt_alloc		{ $$ = $1; }
			;

flow_stmt_legacy_alloc	:	FLOW
			{
				$$ = meter_stmt_alloc(&@$);
			}
			;

flow_stmt_opts		:	flow_stmt_opt
			{
				$<stmt>$	= $<stmt>0;
			}
			|	flow_stmt_opts		flow_stmt_opt
			;

flow_stmt_opt		:	TABLE			identifier
			{
				$<stmt>0->meter.name = $2;
			}
			;

meter_stmt_alloc	:	METER	identifier		'{' meter_key_expr stmt '}'
			{
				$$ = meter_stmt_alloc(&@$);
				$$->meter.name = $2;
				$$->meter.size = 0;
				$$->meter.key  = $4;
				$$->meter.stmt = $5;
				$$->location  = @$;
			}
			|	METER	identifier	SIZE	NUM	'{' meter_key_expr stmt '}'
			{
				$$ = meter_stmt_alloc(&@$);
				$$->meter.name = $2;
				$$->meter.size = $4;
				$$->meter.key  = $6;
				$$->meter.stmt = $7;
				$$->location  = @$;
			}
			;

match_stmt		:	relational_expr
			{
				$$ = expr_stmt_alloc(&@$, $1);
			}
			;

variable_expr		:	'$'	identifier
			{
				struct scope *scope = current_scope(state);
				struct symbol *sym;

				sym = symbol_get(scope, $2);
				if (!sym) {
					sym = symbol_lookup_fuzzy(scope, $2);
					if (sym) {
						erec_queue(error(&@2, "unknown identifier '%s'; "
								      "did you mean identifier ā€˜%sā€™?",
								      $2, sym->identifier),
							   state->msgs);
					} else {
						erec_queue(error(&@2, "unknown identifier '%s'", $2),
							   state->msgs);
					}
					xfree($2);
					YYERROR;
				}

				$$ = variable_expr_alloc(&@$, scope, sym);
				xfree($2);
			}
			;

symbol_expr		:	variable_expr
			|	string
			{
				$$ = symbol_expr_alloc(&@$, SYMBOL_VALUE,
						       current_scope(state),
						       $1);
				xfree($1);
			}
			;

set_ref_expr		:	set_ref_symbol_expr
			|	variable_expr
			;

set_ref_symbol_expr	:	AT	identifier
			{
				$$ = symbol_expr_alloc(&@$, SYMBOL_SET,
						       current_scope(state),
						       $2);
				xfree($2);
			}
			;

integer_expr		:	NUM
			{
				char str[64];

				snprintf(str, sizeof(str), "%" PRIu64, $1);
				$$ = symbol_expr_alloc(&@$, SYMBOL_VALUE,
						       current_scope(state),
						       str);
			}
			;

primary_expr		:	symbol_expr			{ $$ = $1; }
			|	integer_expr			{ $$ = $1; }
			|	payload_expr			{ $$ = $1; }
			|	exthdr_expr			{ $$ = $1; }
			|	exthdr_exists_expr		{ $$ = $1; }
			|	meta_expr			{ $$ = $1; }
			|	socket_expr			{ $$ = $1; }
			|	rt_expr				{ $$ = $1; }
			|	ct_expr				{ $$ = $1; }
			|	numgen_expr			{ $$ = $1; }
			|	hash_expr			{ $$ = $1; }
			|	fib_expr			{ $$ = $1; }
			|	osf_expr			{ $$ = $1; }
			|	xfrm_expr			{ $$ = $1; }
			|	'('	basic_expr	')'	{ $$ = $2; }
			;

fib_expr		:	FIB	fib_tuple	fib_result
			{
				if (($2 & (NFTA_FIB_F_SADDR|NFTA_FIB_F_DADDR)) == 0) {
					erec_queue(error(&@2, "fib: need either saddr or daddr"), state->msgs);
					YYERROR;
				}

				if (($2 & (NFTA_FIB_F_SADDR|NFTA_FIB_F_DADDR)) ==
					  (NFTA_FIB_F_SADDR|NFTA_FIB_F_DADDR)) {
					erec_queue(error(&@2, "fib: saddr and daddr are mutually exclusive"), state->msgs);
					YYERROR;
				}

				if (($2 & (NFTA_FIB_F_IIF|NFTA_FIB_F_OIF)) ==
					  (NFTA_FIB_F_IIF|NFTA_FIB_F_OIF)) {
					erec_queue(error(&@2, "fib: iif and oif are mutually exclusive"), state->msgs);
					YYERROR;
				}

				$$ = fib_expr_alloc(&@$, $2, $3);
			}
			;

fib_result		:	OIF	{ $$ =NFT_FIB_RESULT_OIF; }
			|	OIFNAME { $$ =NFT_FIB_RESULT_OIFNAME; }
			|	TYPE	{ $$ =NFT_FIB_RESULT_ADDRTYPE; }
			;

fib_flag		:       SADDR	{ $$ = NFTA_FIB_F_SADDR; }
			|	DADDR	{ $$ = NFTA_FIB_F_DADDR; }
			|	MARK	{ $$ = NFTA_FIB_F_MARK; }
			|	IIF	{ $$ = NFTA_FIB_F_IIF; }
			|	OIF	{ $$ = NFTA_FIB_F_OIF; }
			;

fib_tuple		:  	fib_flag	DOT	fib_tuple
			{
				$$ = $1 | $3;
			}
			|	fib_flag
			;

osf_expr		:	OSF	osf_ttl		HDRVERSION
			{
				$$ = osf_expr_alloc(&@$, $2, NFT_OSF_F_VERSION);
			}
			|	OSF	osf_ttl		NAME
			{
				$$ = osf_expr_alloc(&@$, $2, 0);
			}
			;

osf_ttl			:	/* empty */
			{
				$$ = NF_OSF_TTL_TRUE;
			}
			|	TTL	STRING
			{
				if (!strcmp($2, "loose"))
					$$ = NF_OSF_TTL_LESS;
				else if (!strcmp($2, "skip"))
					$$ = NF_OSF_TTL_NOCHECK;
				else {
					erec_queue(error(&@2, "invalid ttl option"),
						   state->msgs);
					YYERROR;
				}
			}
			;

shift_expr		:	primary_expr
			|	shift_expr		LSHIFT		primary_rhs_expr
			{
				$$ = binop_expr_alloc(&@$, OP_LSHIFT, $1, $3);
			}
			|	shift_expr		RSHIFT		primary_rhs_expr
			{
				$$ = binop_expr_alloc(&@$, OP_RSHIFT, $1, $3);
			}
			;

and_expr		:	shift_expr
			|	and_expr		AMPERSAND	shift_rhs_expr
			{
				$$ = binop_expr_alloc(&@$, OP_AND, $1, $3);
			}
			;

exclusive_or_expr	:	and_expr
			|	exclusive_or_expr	CARET		and_rhs_expr
			{
				$$ = binop_expr_alloc(&@$, OP_XOR, $1, $3);
			}
			;

inclusive_or_expr	:	exclusive_or_expr
			|	inclusive_or_expr	'|'		exclusive_or_rhs_expr
			{
				$$ = binop_expr_alloc(&@$, OP_OR, $1, $3);
			}
			;

basic_expr		:	inclusive_or_expr
			;

concat_expr		:	basic_expr
			|	concat_expr		DOT		basic_expr
			{
				struct location rhs[] = {
					[1]	= @2,
					[2]	= @3,
				};

				$$ = handle_concat_expr(&@$, $$, $1, $3, rhs);
			}
			;

prefix_rhs_expr		:	basic_rhs_expr	SLASH	NUM
			{
				$$ = prefix_expr_alloc(&@$, $1, $3);
			}
			;

range_rhs_expr		:	basic_rhs_expr	DASH	basic_rhs_expr
			{
				$$ = range_expr_alloc(&@$, $1, $3);
			}
			;

multiton_rhs_expr	:	prefix_rhs_expr
			|	range_rhs_expr
			|	wildcard_expr
			;

map_expr		:	concat_expr	MAP	rhs_expr
			{
				$$ = map_expr_alloc(&@$, $1, $3);
			}
			;

expr			:	concat_expr
			|	set_expr
			|       map_expr
			;

set_expr		:	'{'	set_list_expr		'}'
			{
				$2->location = @$;
				$$ = $2;
			}
			;

set_list_expr		:	set_list_member_expr
			{
				$$ = set_expr_alloc(&@$, NULL);
				compound_expr_add($$, $1);
			}
			|	set_list_expr		COMMA	set_list_member_expr
			{
				compound_expr_add($1, $3);
				$$ = $1;
			}
			|	set_list_expr		COMMA	opt_newline
			;

set_list_member_expr	:	opt_newline	set_expr	opt_newline
			{
				$$ = $2;
			}
			|	opt_newline	set_elem_expr	opt_newline
			{
				$$ = $2;
			}
			|	opt_newline	set_elem_expr	COLON	set_rhs_expr	opt_newline
			{
				$$ = mapping_expr_alloc(&@$, $2, $4);
			}
			;

meter_key_expr		:	meter_key_expr_alloc
			|	meter_key_expr_alloc		set_elem_options
			{
				$$->location = @$;
				$$ = $1;
			}
			;

meter_key_expr_alloc	:	concat_expr
			{
				$$ = set_elem_expr_alloc(&@1, $1);
			}
			;

set_elem_expr		:	set_elem_expr_alloc
			|	set_elem_expr_alloc		set_elem_options
			;

set_elem_expr_alloc	:	set_lhs_expr
			{
				$$ = set_elem_expr_alloc(&@1, $1);
			}
			;

set_elem_options	:	set_elem_option
			{
				$<expr>$	= $<expr>0;
			}
			|	set_elem_options	set_elem_option
			;

set_elem_option		:	TIMEOUT			time_spec
			{
				$<expr>0->timeout = $2;
			}
			|	EXPIRES		time_spec
			{
				$<expr>0->expiration = $2;
			}
			|	comment_spec
			{
				$<expr>0->comment = $1;
			}
			;

set_lhs_expr		:	concat_rhs_expr
			|	multiton_rhs_expr
			;

set_rhs_expr		:	concat_rhs_expr
			|	verdict_expr
			;

initializer_expr	:	rhs_expr
			|	list_rhs_expr
			;

counter_config		:	PACKETS		NUM	BYTES	NUM
			{
				struct counter *counter;

				counter = &$<obj>0->counter;
				counter->packets = $2;
				counter->bytes = $4;
			}
			;

counter_obj		:	/* empty */
			{
				$$ = obj_alloc(&@$);
				$$->type = NFT_OBJECT_COUNTER;
			}
			;

quota_config		:	quota_mode NUM quota_unit quota_used
			{
				struct error_record *erec;
				struct quota *quota;
				uint64_t rate;

				erec = data_unit_parse(&@$, $3, &rate);
				xfree($3);
				if (erec != NULL) {
					erec_queue(erec, state->msgs);
					YYERROR;
				}

				quota = &$<obj>0->quota;
				quota->bytes	= $2 * rate;
				quota->used	= $4;
				quota->flags	= $1;
			}
			;

quota_obj		:	/* empty */
			{
				$$ = obj_alloc(&@$);
				$$->type = NFT_OBJECT_QUOTA;
			}
			;

secmark_config		:	string
			{
				int ret;
				struct secmark *secmark;

				secmark = &$<obj>0->secmark;
				ret = snprintf(secmark->ctx, sizeof(secmark->ctx), "%s", $1);
				if (ret <= 0 || ret >= (int)sizeof(secmark->ctx)) {
					erec_queue(error(&@1, "invalid context '%s', max length is %u\n", $1, (int)sizeof(secmark->ctx)), state->msgs);
					xfree($1);
					YYERROR;
				}
				xfree($1);
			}
			;

secmark_obj		:	/* empty */
			{
				$$ = obj_alloc(&@$);
				$$->type = NFT_OBJECT_SECMARK;
			}
			;

ct_obj_type		:	HELPER		{ $$ = NFT_OBJECT_CT_HELPER; }
			|	TIMEOUT		{ $$ = NFT_OBJECT_CT_TIMEOUT; }
			|	EXPECTATION	{ $$ = NFT_OBJECT_CT_EXPECT; }
			;

ct_l4protoname		:	TCP	{ $$ = IPPROTO_TCP; }
			|	UDP	{ $$ = IPPROTO_UDP; }
			;

ct_helper_config		:	TYPE	QUOTED_STRING	PROTOCOL	ct_l4protoname	stmt_separator
			{
				struct ct_helper *ct;
				int ret;

				ct = &$<obj>0->ct_helper;

				ret = snprintf(ct->name, sizeof(ct->name), "%s", $2);
				if (ret <= 0 || ret >= (int)sizeof(ct->name)) {
					erec_queue(error(&@2, "invalid name '%s', max length is %u\n", $2, (int)sizeof(ct->name)), state->msgs);
					YYERROR;
				}

				ct->l4proto = $4;
			}
			|	L3PROTOCOL	family_spec_explicit	stmt_separator
			{
				$<obj>0->ct_helper.l3proto = $2;
			}
			;

timeout_states		:	timeout_state
			{
				$$ = xmalloc(sizeof(*$$));
				init_list_head($$);
				list_add_tail($1, $$);
			}
			|	timeout_states	COMMA	timeout_state
			{
				list_add_tail($3, $1);
				$$ = $1;
			}
			;

timeout_state		:	STRING	COLON	NUM

			{
				struct timeout_state *ts;

				ts = xzalloc(sizeof(*ts));
				ts->timeout_str = $1;
				ts->timeout_value = $3;
				ts->location = @1;
				init_list_head(&ts->head);
				$$ = &ts->head;
			}
			;

ct_timeout_config	:	PROTOCOL	ct_l4protoname	stmt_separator
			{
				struct ct_timeout *ct;
				int l4proto = $2;

				ct = &$<obj>0->ct_timeout;
				ct->l4proto = l4proto;
			}
			|	POLICY 	'=' 	'{' 	timeout_states 	'}'	 stmt_separator
			{
				struct ct_timeout *ct;

				ct = &$<obj>0->ct_timeout;
				init_list_head(&ct->timeout_list);
				list_splice_tail($4, &ct->timeout_list);
			}
			|	L3PROTOCOL	family_spec_explicit	stmt_separator
			{
				$<obj>0->ct_timeout.l3proto = $2;
			}
			;

ct_expect_config	:	PROTOCOL	ct_l4protoname	stmt_separator
			{
				$<obj>0->ct_expect.l4proto = $2;
			}
			|	DPORT	NUM	stmt_separator
			{
				$<obj>0->ct_expect.dport = $2;
			}
			|	TIMEOUT	time_spec	stmt_separator
			{
				$<obj>0->ct_expect.timeout = $2;
			}
			|	SIZE	NUM	stmt_separator
			{
				$<obj>0->ct_expect.size = $2;
			}
			|	L3PROTOCOL	family_spec_explicit	stmt_separator
			{
				$<obj>0->ct_expect.l3proto = $2;
			}
			;

ct_obj_alloc		:	/* empty */
			{
				$$ = obj_alloc(&@$);
			}
			;

limit_config		:	RATE	limit_mode	NUM	SLASH	time_unit	limit_burst_pkts
			{
				struct limit *limit;

				limit = &$<obj>0->limit;
				limit->rate	= $3;
				limit->unit	= $5;
				limit->burst	= $6;
				limit->type	= NFT_LIMIT_PKTS;
				limit->flags	= $2;
			}
			|	RATE	limit_mode	NUM	STRING	limit_burst_bytes
			{
				struct limit *limit;
				struct error_record *erec;
				uint64_t rate, unit;

				erec = rate_parse(&@$, $4, &rate, &unit);
				if (erec != NULL) {
					erec_queue(erec, state->msgs);
					YYERROR;
				}

				limit = &$<obj>0->limit;
				limit->rate	= rate * $3;
				limit->unit	= unit;
				limit->burst	= $5;
				limit->type	= NFT_LIMIT_PKT_BYTES;
				limit->flags	= $2;
			}
			;

limit_obj		:	/* empty */
			{
				$$ = obj_alloc(&@$);
				$$->type = NFT_OBJECT_LIMIT;
			}
			;

relational_expr		:	expr	/* implicit */	rhs_expr
			{
				$$ = relational_expr_alloc(&@$, OP_IMPLICIT, $1, $2);
			}
			|	expr	/* implicit */	list_rhs_expr
			{
				$$ = relational_expr_alloc(&@$, OP_IMPLICIT, $1, $2);
			}
			|	expr	relational_op	rhs_expr
			{
				$$ = relational_expr_alloc(&@2, $2, $1, $3);
			}
			;

list_rhs_expr		:	basic_rhs_expr		COMMA		basic_rhs_expr
			{
				$$ = list_expr_alloc(&@$);
				compound_expr_add($$, $1);
				compound_expr_add($$, $3);
			}
			|	list_rhs_expr		COMMA		basic_rhs_expr
			{
				$1->location = @$;
				compound_expr_add($1, $3);
				$$ = $1;
			}
			;

rhs_expr		:	concat_rhs_expr		{ $$ = $1; }
			|	multiton_rhs_expr	{ $$ = $1; }
			|	set_expr		{ $$ = $1; }
			|	set_ref_symbol_expr	{ $$ = $1; }
			;

shift_rhs_expr		:	primary_rhs_expr
			|	shift_rhs_expr		LSHIFT		primary_rhs_expr
			{
				$$ = binop_expr_alloc(&@$, OP_LSHIFT, $1, $3);
			}
			|	shift_rhs_expr		RSHIFT		primary_rhs_expr
			{
				$$ = binop_expr_alloc(&@$, OP_RSHIFT, $1, $3);
			}
			;

and_rhs_expr		:	shift_rhs_expr
			|	and_rhs_expr		AMPERSAND	shift_rhs_expr
			{
				$$ = binop_expr_alloc(&@$, OP_AND, $1, $3);
			}
			;

exclusive_or_rhs_expr	:	and_rhs_expr
			|	exclusive_or_rhs_expr	CARET		and_rhs_expr
			{
				$$ = binop_expr_alloc(&@$, OP_XOR, $1, $3);
			}
			;

inclusive_or_rhs_expr	:	exclusive_or_rhs_expr
			|	inclusive_or_rhs_expr	'|'		exclusive_or_rhs_expr
			{
				$$ = binop_expr_alloc(&@$, OP_OR, $1, $3);
			}
			;

basic_rhs_expr		:	inclusive_or_rhs_expr
			;

concat_rhs_expr		:	basic_rhs_expr
			|	concat_rhs_expr	DOT	basic_rhs_expr
			{
				struct location rhs[] = {
					[1]	= @2,
					[2]	= @3,
				};

				$$ = handle_concat_expr(&@$, $$, $1, $3, rhs);
			}
			;

boolean_keys		:	EXISTS		{ $$ = true; }
			|	MISSING		{ $$ = false; }
			;

boolean_expr		:	boolean_keys
			{
				$$ = constant_expr_alloc(&@$, &boolean_type,
							 BYTEORDER_HOST_ENDIAN,
							 sizeof($1) * BITS_PER_BYTE, &$1);
			}
			;

keyword_expr		:	ETHER                   { $$ = symbol_value(&@$, "ether"); }
			|	IP			{ $$ = symbol_value(&@$, "ip"); }
			|	IP6			{ $$ = symbol_value(&@$, "ip6"); }
			|	VLAN			{ $$ = symbol_value(&@$, "vlan"); }
			|	ARP			{ $$ = symbol_value(&@$, "arp"); }
			|	DNAT			{ $$ = symbol_value(&@$, "dnat"); }
			|	SNAT			{ $$ = symbol_value(&@$, "snat"); }
			|	ECN			{ $$ = symbol_value(&@$, "ecn"); }
			|	RESET			{ $$ = symbol_value(&@$, "reset"); }
			|	ORIGINAL		{ $$ = symbol_value(&@$, "original"); }
			|	REPLY			{ $$ = symbol_value(&@$, "reply"); }
			|	LABEL			{ $$ = symbol_value(&@$, "label"); }
			;

primary_rhs_expr	:	symbol_expr		{ $$ = $1; }
			|	integer_expr		{ $$ = $1; }
			|	boolean_expr		{ $$ = $1; }
			|	keyword_expr		{ $$ = $1; }
			|	TCP
			{
				uint8_t data = IPPROTO_TCP;
				$$ = constant_expr_alloc(&@$, &inet_protocol_type,
							 BYTEORDER_HOST_ENDIAN,
							 sizeof(data) * BITS_PER_BYTE, &data);
			}
			|	UDP
			{
				uint8_t data = IPPROTO_UDP;
				$$ = constant_expr_alloc(&@$, &inet_protocol_type,
							 BYTEORDER_HOST_ENDIAN,
							 sizeof(data) * BITS_PER_BYTE, &data);
			}
			|	UDPLITE
			{
				uint8_t data = IPPROTO_UDPLITE;
				$$ = constant_expr_alloc(&@$, &inet_protocol_type,
							 BYTEORDER_HOST_ENDIAN,
							 sizeof(data) * BITS_PER_BYTE, &data);
			}
			|	ESP
			{
				uint8_t data = IPPROTO_ESP;
				$$ = constant_expr_alloc(&@$, &inet_protocol_type,
							 BYTEORDER_HOST_ENDIAN,
							 sizeof(data) * BITS_PER_BYTE, &data);
			}
			|	AH
			{
				uint8_t data = IPPROTO_AH;
				$$ = constant_expr_alloc(&@$, &inet_protocol_type,
							 BYTEORDER_HOST_ENDIAN,
							 sizeof(data) * BITS_PER_BYTE, &data);
			}
			|	ICMP
			{
				uint8_t data = IPPROTO_ICMP;
				$$ = constant_expr_alloc(&@$, &inet_protocol_type,
							 BYTEORDER_HOST_ENDIAN,
							 sizeof(data) * BITS_PER_BYTE, &data);
			}
			|	IGMP
			{
				uint8_t data = IPPROTO_IGMP;
				$$ = constant_expr_alloc(&@$, &inet_protocol_type,
							 BYTEORDER_HOST_ENDIAN,
							 sizeof(data) * BITS_PER_BYTE, &data);
			}
			|	ICMP6
			{
				uint8_t data = IPPROTO_ICMPV6;
				$$ = constant_expr_alloc(&@$, &inet_protocol_type,
							 BYTEORDER_HOST_ENDIAN,
							 sizeof(data) * BITS_PER_BYTE, &data);
			}
			|	COMP
			{
				uint8_t data = IPPROTO_COMP;
				$$ = constant_expr_alloc(&@$, &inet_protocol_type,
							 BYTEORDER_HOST_ENDIAN,
							 sizeof(data) * BITS_PER_BYTE, &data);
			}
			|	DCCP
			{
				uint8_t data = IPPROTO_DCCP;
				$$ = constant_expr_alloc(&@$, &inet_protocol_type,
							 BYTEORDER_HOST_ENDIAN,
							 sizeof(data) * BITS_PER_BYTE, &data);
			}
			|	SCTP
			{
				uint8_t data = IPPROTO_SCTP;
				$$ = constant_expr_alloc(&@$, &inet_protocol_type,
							 BYTEORDER_HOST_ENDIAN,
							 sizeof(data) * BITS_PER_BYTE, &data);
			}
			|	REDIRECT
			{
				uint8_t data = ICMP_REDIRECT;
				$$ = constant_expr_alloc(&@$, &icmp_type_type,
							 BYTEORDER_HOST_ENDIAN,
							 sizeof(data) * BITS_PER_BYTE, &data);
			}
			|	'('	basic_rhs_expr	')'	{ $$ = $2; }
			;

relational_op		:	EQ		{ $$ = OP_EQ; }
			|	NEQ		{ $$ = OP_NEQ; }
			|	LT		{ $$ = OP_LT; }
			|	GT		{ $$ = OP_GT; }
			|	GTE		{ $$ = OP_GTE; }
			|	LTE		{ $$ = OP_LTE; }
			;

verdict_expr		:	ACCEPT
			{
				$$ = verdict_expr_alloc(&@$, NF_ACCEPT, NULL);
			}
			|	DROP
			{
				$$ = verdict_expr_alloc(&@$, NF_DROP, NULL);
			}
			|	CONTINUE
			{
				$$ = verdict_expr_alloc(&@$, NFT_CONTINUE, NULL);
			}
			|	JUMP			chain_expr
			{
				$$ = verdict_expr_alloc(&@$, NFT_JUMP, $2);
			}
			|	GOTO			chain_expr
			{
				$$ = verdict_expr_alloc(&@$, NFT_GOTO, $2);
			}
			|	RETURN
			{
				$$ = verdict_expr_alloc(&@$, NFT_RETURN, NULL);
			}
			;

chain_expr		:	variable_expr
			|	identifier
			{
				$$ = constant_expr_alloc(&@$, &string_type,
							 BYTEORDER_HOST_ENDIAN,
							 strlen($1) * BITS_PER_BYTE,
							 $1);
				xfree($1);
			}
			;

meta_expr		:	META	meta_key
			{
				$$ = meta_expr_alloc(&@$, $2);
			}
			|	meta_key_unqualified
			{
				$$ = meta_expr_alloc(&@$, $1);
			}
			|	META	STRING
			{
				struct error_record *erec;
				unsigned int key;

				erec = meta_key_parse(&@$, $2, &key);
				xfree($2);
				if (erec != NULL) {
					erec_queue(erec, state->msgs);
					YYERROR;
				}

				$$ = meta_expr_alloc(&@$, key);
			}
			;

meta_key		:	meta_key_qualified
			|	meta_key_unqualified
			;

meta_key_qualified	:	LENGTH		{ $$ = NFT_META_LEN; }
			|	PROTOCOL	{ $$ = NFT_META_PROTOCOL; }
			|	PRIORITY	{ $$ = NFT_META_PRIORITY; }
			|	RANDOM		{ $$ = NFT_META_PRANDOM; }
			|	SECMARK		{ $$ = NFT_META_SECMARK; }
			;

meta_key_unqualified	:	MARK		{ $$ = NFT_META_MARK; }
			|	IIF		{ $$ = NFT_META_IIF; }
			|	IIFNAME		{ $$ = NFT_META_IIFNAME; }
			|	IIFTYPE		{ $$ = NFT_META_IIFTYPE; }
			|	OIF		{ $$ = NFT_META_OIF; }
			|	OIFNAME		{ $$ = NFT_META_OIFNAME; }
			|	OIFTYPE		{ $$ = NFT_META_OIFTYPE; }
			|	SKUID		{ $$ = NFT_META_SKUID; }
			|	SKGID		{ $$ = NFT_META_SKGID; }
			|	NFTRACE		{ $$ = NFT_META_NFTRACE; }
			|	RTCLASSID	{ $$ = NFT_META_RTCLASSID; }
			|	IBRIPORT	{ $$ = NFT_META_BRI_IIFNAME; }
			|       OBRIPORT	{ $$ = NFT_META_BRI_OIFNAME; }
			|	IBRIDGENAME	{ $$ = NFT_META_BRI_IIFNAME; }
			|       OBRIDGENAME	{ $$ = NFT_META_BRI_OIFNAME; }
			|       PKTTYPE		{ $$ = NFT_META_PKTTYPE; }
			|       CPU		{ $$ = NFT_META_CPU; }
			|       IIFGROUP	{ $$ = NFT_META_IIFGROUP; }
			|       OIFGROUP	{ $$ = NFT_META_OIFGROUP; }
			|       CGROUP		{ $$ = NFT_META_CGROUP; }
			|       IPSEC		{ $$ = NFT_META_SECPATH; }
			|       TIME		{ $$ = NFT_META_TIME_NS; }
			|       DAY		{ $$ = NFT_META_TIME_DAY; }
			|       HOUR		{ $$ = NFT_META_TIME_HOUR; }
			;

meta_stmt		:	META	meta_key	SET	stmt_expr
			{
				switch ($2) {
				case NFT_META_SECMARK:
					switch ($4->etype) {
					case EXPR_CT:
						$$ = meta_stmt_alloc(&@$, $2, $4);
						break;
					default:
						$$ = objref_stmt_alloc(&@$);
						$$->objref.type = NFT_OBJECT_SECMARK;
						$$->objref.expr = $4;
						break;
					}
					break;
				default:
					$$ = meta_stmt_alloc(&@$, $2, $4);
					break;
				}
			}
			|	meta_key_unqualified	SET	stmt_expr
			{
				$$ = meta_stmt_alloc(&@$, $1, $3);
			}
			|	META	STRING	SET	stmt_expr
			{
				struct error_record *erec;
				unsigned int key;

				erec = meta_key_parse(&@$, $2, &key);
				xfree($2);
				if (erec != NULL) {
					erec_queue(erec, state->msgs);
					YYERROR;
				}

				$$ = meta_stmt_alloc(&@$, key, $4);
			}
			|	NOTRACK
			{
				$$ = notrack_stmt_alloc(&@$);
			}
			|	FLOW	OFFLOAD	AT string
			{
				$$ = flow_offload_stmt_alloc(&@$, $4);
			}
			|	FLOW	ADD	AT string
			{
				$$ = flow_offload_stmt_alloc(&@$, $4);
			}
			;

socket_expr		:	SOCKET	socket_key
			{
				$$ = socket_expr_alloc(&@$, $2);
			}
			;

socket_key 		: 	TRANSPARENT	{ $$ = NFT_SOCKET_TRANSPARENT; }
			|	MARK		{ $$ = NFT_SOCKET_MARK; }
			;

offset_opt		:	/* empty */	{ $$ = 0; }
			|	OFFSET	NUM	{ $$ = $2; }
			;

numgen_type		:	INC		{ $$ = NFT_NG_INCREMENTAL; }
			|	RANDOM		{ $$ = NFT_NG_RANDOM; }
			;

numgen_expr		:	NUMGEN	numgen_type	MOD	NUM	offset_opt
			{
				$$ = numgen_expr_alloc(&@$, $2, $4, $5);
			}
			;

xfrm_spnum		:	SPNUM	NUM { $$ = $2; }
			|		    { $$ = 0; }
			;

xfrm_dir		:	IN	{ $$ = XFRM_POLICY_IN; }
			|	OUT	{ $$ = XFRM_POLICY_OUT; }
			;

xfrm_state_key		:	SPI { $$ = NFT_XFRM_KEY_SPI; }
			|	REQID { $$ = NFT_XFRM_KEY_REQID; }
			;

xfrm_state_proto_key	:	DADDR		{ $$ = NFT_XFRM_KEY_DADDR_IP4; }
			|	SADDR		{ $$ = NFT_XFRM_KEY_SADDR_IP4; }
			;

xfrm_expr		:	IPSEC	xfrm_dir	xfrm_spnum	xfrm_state_key
			{
				if ($3 > 255) {
					erec_queue(error(&@3, "value too large"), state->msgs);
					YYERROR;
				}
				$$ = xfrm_expr_alloc(&@$, $2, $3, $4);
			}
			|	IPSEC	xfrm_dir	xfrm_spnum	nf_key_proto	xfrm_state_proto_key
			{
				enum nft_xfrm_keys xfrmk = $5;

				switch ($4) {
				case NFPROTO_IPV4:
					break;
				case NFPROTO_IPV6:
					if ($5 == NFT_XFRM_KEY_SADDR_IP4)
						xfrmk = NFT_XFRM_KEY_SADDR_IP6;
					else if ($5 == NFT_XFRM_KEY_DADDR_IP4)
						xfrmk = NFT_XFRM_KEY_DADDR_IP6;
					break;
				default:
					YYERROR;
					break;
				}

				if ($3 > 255) {
					erec_queue(error(&@3, "value too large"), state->msgs);
					YYERROR;
				}

				$$ = xfrm_expr_alloc(&@$, $2, $3, xfrmk);
			}
			;

hash_expr		:	JHASH		expr	MOD	NUM	SEED	NUM	offset_opt
			{
				$$ = hash_expr_alloc(&@$, $4, true, $6, $7, NFT_HASH_JENKINS);
				$$->hash.expr = $2;
			}
			|	JHASH		expr	MOD	NUM	offset_opt
			{
				$$ = hash_expr_alloc(&@$, $4, false, 0, $5, NFT_HASH_JENKINS);
				$$->hash.expr = $2;
			}
			|	SYMHASH		MOD	NUM	offset_opt
			{
				$$ = hash_expr_alloc(&@$, $3, false, 0, $4, NFT_HASH_SYM);
			}
			;

nf_key_proto		:	IP		{ $$ = NFPROTO_IPV4; }
			|	IP6		{ $$ = NFPROTO_IPV6; }
			;

rt_expr			:	RT	rt_key
			{
				$$ = rt_expr_alloc(&@$, $2, true);
			}
			|	RT	nf_key_proto	rt_key
			{
				enum nft_rt_keys rtk = $3;

				switch ($2) {
				case NFPROTO_IPV4:
					break;
				case NFPROTO_IPV6:
					if ($3 == NFT_RT_NEXTHOP4)
						rtk = NFT_RT_NEXTHOP6;
					break;
				default:
					YYERROR;
					break;
				}

				$$ = rt_expr_alloc(&@$, rtk, false);
			}
			;

rt_key			:	CLASSID		{ $$ = NFT_RT_CLASSID; }
			|	NEXTHOP		{ $$ = NFT_RT_NEXTHOP4; }
			|	MTU		{ $$ = NFT_RT_TCPMSS; }
			|	IPSEC		{ $$ = NFT_RT_XFRM; }
			;

ct_expr			: 	CT	ct_key
			{
				$$ = ct_expr_alloc(&@$, $2, -1);
			}
			|	CT	ct_dir	ct_key_dir
			{
				$$ = ct_expr_alloc(&@$, $3, $2);
			}
			|	CT	ct_dir	ct_key_proto_field
			{
				$$ = ct_expr_alloc(&@$, $3, $2);
			}
			;

ct_dir			:	ORIGINAL	{ $$ = IP_CT_DIR_ORIGINAL; }
			|	REPLY		{ $$ = IP_CT_DIR_REPLY; }
			;

ct_key			:	L3PROTOCOL	{ $$ = NFT_CT_L3PROTOCOL; }
			|	PROTOCOL	{ $$ = NFT_CT_PROTOCOL; }
			|	MARK		{ $$ = NFT_CT_MARK; }
			|	STATE		{ $$ = NFT_CT_STATE; }
			|	DIRECTION	{ $$ = NFT_CT_DIRECTION; }
			|	STATUS		{ $$ = NFT_CT_STATUS; }
			|	EXPIRATION	{ $$ = NFT_CT_EXPIRATION; }
			|	HELPER		{ $$ = NFT_CT_HELPER; }
			|	SADDR		{ $$ = NFT_CT_SRC; }
			|	DADDR		{ $$ = NFT_CT_DST; }
			|	PROTO_SRC	{ $$ = NFT_CT_PROTO_SRC; }
			|	PROTO_DST	{ $$ = NFT_CT_PROTO_DST; }
			|	LABEL		{ $$ = NFT_CT_LABELS; }
			|	EVENT		{ $$ = NFT_CT_EVENTMASK; }
			|	SECMARK		{ $$ = NFT_CT_SECMARK; }
			|	ct_key_dir_optional
			;

ct_key_dir		:	SADDR		{ $$ = NFT_CT_SRC; }
			|	DADDR		{ $$ = NFT_CT_DST; }
			|	L3PROTOCOL	{ $$ = NFT_CT_L3PROTOCOL; }
			|	PROTOCOL	{ $$ = NFT_CT_PROTOCOL; }
			|	PROTO_SRC	{ $$ = NFT_CT_PROTO_SRC; }
			|	PROTO_DST	{ $$ = NFT_CT_PROTO_DST; }
			|	ct_key_dir_optional
			;

ct_key_proto_field	:	IP	SADDR	{ $$ = NFT_CT_SRC_IP; }
			|	IP	DADDR	{ $$ = NFT_CT_DST_IP; }
			|	IP6	SADDR	{ $$ = NFT_CT_SRC_IP6; }
			|	IP6	DADDR	{ $$ = NFT_CT_DST_IP6; }
			;

ct_key_dir_optional	:	BYTES		{ $$ = NFT_CT_BYTES; }
			|	PACKETS		{ $$ = NFT_CT_PKTS; }
			|	AVGPKT		{ $$ = NFT_CT_AVGPKT; }
			|	ZONE		{ $$ = NFT_CT_ZONE; }
			;

symbol_stmt_expr		:	symbol_expr
			|	keyword_expr
			;

list_stmt_expr		:	symbol_stmt_expr	COMMA	symbol_stmt_expr
			{
				$$ = list_expr_alloc(&@$);
				compound_expr_add($$, $1);
				compound_expr_add($$, $3);
			}
			|	list_stmt_expr	COMMA	symbol_stmt_expr
			{
				$1->location = @$;
				compound_expr_add($1, $3);
				$$ = $1;
			}
			;

ct_stmt			:	CT	ct_key		SET	stmt_expr
			{
				switch ($2) {
				case NFT_CT_HELPER:
					$$ = objref_stmt_alloc(&@$);
					$$->objref.type = NFT_OBJECT_CT_HELPER;
					$$->objref.expr = $4;
					break;
				default:
					$$ = ct_stmt_alloc(&@$, $2, -1, $4);
					break;
				}
			}
			|	CT	TIMEOUT		SET	stmt_expr
			{
				$$ = objref_stmt_alloc(&@$);
				$$->objref.type = NFT_OBJECT_CT_TIMEOUT;
				$$->objref.expr = $4;

			}
			|	CT	EXPECTATION	SET	stmt_expr
			{
				$$ = objref_stmt_alloc(&@$);
				$$->objref.type = NFT_OBJECT_CT_EXPECT;
				$$->objref.expr = $4;
			}
			|	CT	ct_dir	ct_key_dir_optional SET	stmt_expr
			{
				$$ = ct_stmt_alloc(&@$, $3, $2, $5);
			}
			;

payload_stmt		:	payload_expr		SET	stmt_expr
			{
				if ($1->etype == EXPR_EXTHDR)
					$$ = exthdr_stmt_alloc(&@$, $1, $3);
				else
					$$ = payload_stmt_alloc(&@$, $1, $3);
			}
			;

payload_expr		:	payload_raw_expr
			|	eth_hdr_expr
			|	vlan_hdr_expr
			|	arp_hdr_expr
			|	ip_hdr_expr
			|	icmp_hdr_expr
			|	igmp_hdr_expr
			|	ip6_hdr_expr
			|	icmp6_hdr_expr
			|	auth_hdr_expr
			|	esp_hdr_expr
			|	comp_hdr_expr
			|	udp_hdr_expr
			|	udplite_hdr_expr
			|	tcp_hdr_expr
			|	dccp_hdr_expr
			|	sctp_hdr_expr
			|	th_hdr_expr
			;

payload_raw_expr	:	AT	payload_base_spec	COMMA	NUM	COMMA	NUM
			{
				$$ = payload_expr_alloc(&@$, NULL, 0);
				payload_init_raw($$, $2, $4, $6);
				$$->byteorder		= BYTEORDER_BIG_ENDIAN;
				$$->payload.is_raw	= true;
			}
			;

payload_base_spec	:	LL_HDR		{ $$ = PROTO_BASE_LL_HDR; }
			|	NETWORK_HDR	{ $$ = PROTO_BASE_NETWORK_HDR; }
			|	TRANSPORT_HDR	{ $$ = PROTO_BASE_TRANSPORT_HDR; }
			;

eth_hdr_expr		:	ETHER	eth_hdr_field
			{
				$$ = payload_expr_alloc(&@$, &proto_eth, $2);
			}
			;

eth_hdr_field		:	SADDR		{ $$ = ETHHDR_SADDR; }
			|	DADDR		{ $$ = ETHHDR_DADDR; }
			|	TYPE		{ $$ = ETHHDR_TYPE; }
			;

vlan_hdr_expr		:	VLAN	vlan_hdr_field
			{
				$$ = payload_expr_alloc(&@$, &proto_vlan, $2);
			}
			;

vlan_hdr_field		:	ID		{ $$ = VLANHDR_VID; }
			|	CFI		{ $$ = VLANHDR_CFI; }
			|	PCP		{ $$ = VLANHDR_PCP; }
			|	TYPE		{ $$ = VLANHDR_TYPE; }
			;

arp_hdr_expr		:	ARP	arp_hdr_field
			{
				$$ = payload_expr_alloc(&@$, &proto_arp, $2);
			}
			;

arp_hdr_field		:	HTYPE		{ $$ = ARPHDR_HRD; }
			|	PTYPE		{ $$ = ARPHDR_PRO; }
			|	HLEN		{ $$ = ARPHDR_HLN; }
			|	PLEN		{ $$ = ARPHDR_PLN; }
			|	OPERATION	{ $$ = ARPHDR_OP; }
			|	SADDR ETHER	{ $$ = ARPHDR_SADDR_ETHER; }
			|	DADDR ETHER	{ $$ = ARPHDR_DADDR_ETHER; }
			|	SADDR IP	{ $$ = ARPHDR_SADDR_IP; }
			|	DADDR IP	{ $$ = ARPHDR_DADDR_IP; }
			;

ip_hdr_expr		:	IP	ip_hdr_field
			{
				$$ = payload_expr_alloc(&@$, &proto_ip, $2);
			}
			|	IP	OPTION	ip_option_type ip_option_field
			{
				$$ = ipopt_expr_alloc(&@$, $3, $4, 0);
			}
			|	IP	OPTION	ip_option_type
			{
				$$ = ipopt_expr_alloc(&@$, $3, IPOPT_FIELD_TYPE, 0);
				$$->exthdr.flags = NFT_EXTHDR_F_PRESENT;
			}
			;

ip_hdr_field		:	HDRVERSION	{ $$ = IPHDR_VERSION; }
			|	HDRLENGTH	{ $$ = IPHDR_HDRLENGTH; }
			|	DSCP		{ $$ = IPHDR_DSCP; }
			|	ECN		{ $$ = IPHDR_ECN; }
			|	LENGTH		{ $$ = IPHDR_LENGTH; }
			|	ID		{ $$ = IPHDR_ID; }
			|	FRAG_OFF	{ $$ = IPHDR_FRAG_OFF; }
			|	TTL		{ $$ = IPHDR_TTL; }
			|	PROTOCOL	{ $$ = IPHDR_PROTOCOL; }
			|	CHECKSUM	{ $$ = IPHDR_CHECKSUM; }
			|	SADDR		{ $$ = IPHDR_SADDR; }
			|	DADDR		{ $$ = IPHDR_DADDR; }
			;

ip_option_type		:	LSRR		{ $$ = IPOPT_LSRR; }
			|	RR		{ $$ = IPOPT_RR; }
			|	SSRR		{ $$ = IPOPT_SSRR; }
			|	RA		{ $$ = IPOPT_RA; }
			;

ip_option_field		:	TYPE		{ $$ = IPOPT_FIELD_TYPE; }
			|	LENGTH		{ $$ = IPOPT_FIELD_LENGTH; }
			|	VALUE		{ $$ = IPOPT_FIELD_VALUE; }
			|	PTR		{ $$ = IPOPT_FIELD_PTR; }
			|	ADDR		{ $$ = IPOPT_FIELD_ADDR_0; }
			;

icmp_hdr_expr		:	ICMP	icmp_hdr_field
			{
				$$ = payload_expr_alloc(&@$, &proto_icmp, $2);
			}
			;

icmp_hdr_field		:	TYPE		{ $$ = ICMPHDR_TYPE; }
			|	CODE		{ $$ = ICMPHDR_CODE; }
			|	CHECKSUM	{ $$ = ICMPHDR_CHECKSUM; }
			|	ID		{ $$ = ICMPHDR_ID; }
			|	SEQUENCE	{ $$ = ICMPHDR_SEQ; }
			|	GATEWAY		{ $$ = ICMPHDR_GATEWAY; }
			|	MTU		{ $$ = ICMPHDR_MTU; }
			;

igmp_hdr_expr		:	IGMP	igmp_hdr_field
			{
				$$ = payload_expr_alloc(&@$, &proto_igmp, $2);
			}
			;

igmp_hdr_field		:	TYPE		{ $$ = IGMPHDR_TYPE; }
			|	CHECKSUM	{ $$ = IGMPHDR_CHECKSUM; }
			|	MRT		{ $$ = IGMPHDR_MRT; }
			|	GROUP		{ $$ = IGMPHDR_GROUP; }
			;

ip6_hdr_expr		:	IP6	ip6_hdr_field
			{
				$$ = payload_expr_alloc(&@$, &proto_ip6, $2);
			}
			;

ip6_hdr_field		:	HDRVERSION	{ $$ = IP6HDR_VERSION; }
			|	DSCP		{ $$ = IP6HDR_DSCP; }
			|	ECN		{ $$ = IP6HDR_ECN; }
			|	FLOWLABEL	{ $$ = IP6HDR_FLOWLABEL; }
			|	LENGTH		{ $$ = IP6HDR_LENGTH; }
			|	NEXTHDR		{ $$ = IP6HDR_NEXTHDR; }
			|	HOPLIMIT	{ $$ = IP6HDR_HOPLIMIT; }
			|	SADDR		{ $$ = IP6HDR_SADDR; }
			|	DADDR		{ $$ = IP6HDR_DADDR; }
			;
icmp6_hdr_expr		:	ICMP6	icmp6_hdr_field
			{
				$$ = payload_expr_alloc(&@$, &proto_icmp6, $2);
			}
			;

icmp6_hdr_field		:	TYPE		{ $$ = ICMP6HDR_TYPE; }
			|	CODE		{ $$ = ICMP6HDR_CODE; }
			|	CHECKSUM	{ $$ = ICMP6HDR_CHECKSUM; }
			|	PPTR		{ $$ = ICMP6HDR_PPTR; }
			|	MTU		{ $$ = ICMP6HDR_MTU; }
			|	ID		{ $$ = ICMP6HDR_ID; }
			|	SEQUENCE	{ $$ = ICMP6HDR_SEQ; }
			|	MAXDELAY	{ $$ = ICMP6HDR_MAXDELAY; }
			;

auth_hdr_expr		:	AH	auth_hdr_field
			{
				$$ = payload_expr_alloc(&@$, &proto_ah, $2);
			}
			;

auth_hdr_field		:	NEXTHDR		{ $$ = AHHDR_NEXTHDR; }
			|	HDRLENGTH	{ $$ = AHHDR_HDRLENGTH; }
			|	RESERVED	{ $$ = AHHDR_RESERVED; }
			|	SPI		{ $$ = AHHDR_SPI; }
			|	SEQUENCE	{ $$ = AHHDR_SEQUENCE; }
			;

esp_hdr_expr		:	ESP	esp_hdr_field
			{
				$$ = payload_expr_alloc(&@$, &proto_esp, $2);
			}
			;

esp_hdr_field		:	SPI		{ $$ = ESPHDR_SPI; }
			|	SEQUENCE	{ $$ = ESPHDR_SEQUENCE; }
			;

comp_hdr_expr		:	COMP	comp_hdr_field
			{
				$$ = payload_expr_alloc(&@$, &proto_comp, $2);
			}
			;

comp_hdr_field		:	NEXTHDR		{ $$ = COMPHDR_NEXTHDR; }
			|	FLAGS		{ $$ = COMPHDR_FLAGS; }
			|	CPI		{ $$ = COMPHDR_CPI; }
			;

udp_hdr_expr		:	UDP	udp_hdr_field
			{
				$$ = payload_expr_alloc(&@$, &proto_udp, $2);
			}
			;

udp_hdr_field		:	SPORT		{ $$ = UDPHDR_SPORT; }
			|	DPORT		{ $$ = UDPHDR_DPORT; }
			|	LENGTH		{ $$ = UDPHDR_LENGTH; }
			|	CHECKSUM	{ $$ = UDPHDR_CHECKSUM; }
			;

udplite_hdr_expr	:	UDPLITE	udplite_hdr_field
			{
				$$ = payload_expr_alloc(&@$, &proto_udplite, $2);
			}
			;

udplite_hdr_field	:	SPORT		{ $$ = UDPHDR_SPORT; }
			|	DPORT		{ $$ = UDPHDR_DPORT; }
			|	CSUMCOV		{ $$ = UDPHDR_LENGTH; }
			|	CHECKSUM	{ $$ = UDPHDR_CHECKSUM; }
			;

tcp_hdr_expr		:	TCP	tcp_hdr_field
			{
				$$ = payload_expr_alloc(&@$, &proto_tcp, $2);
			}
			|	TCP	OPTION	tcp_hdr_option_type tcp_hdr_option_field
			{
				$$ = tcpopt_expr_alloc(&@$, $3, $4);
			}
			|	TCP	OPTION	tcp_hdr_option_type
			{
				$$ = tcpopt_expr_alloc(&@$, $3, TCPOPTHDR_FIELD_KIND);
				$$->exthdr.flags = NFT_EXTHDR_F_PRESENT;
			}
			;

tcp_hdr_field		:	SPORT		{ $$ = TCPHDR_SPORT; }
			|	DPORT		{ $$ = TCPHDR_DPORT; }
			|	SEQUENCE	{ $$ = TCPHDR_SEQ; }
			|	ACKSEQ		{ $$ = TCPHDR_ACKSEQ; }
			|	DOFF		{ $$ = TCPHDR_DOFF; }
			|	RESERVED	{ $$ = TCPHDR_RESERVED; }
			|	FLAGS		{ $$ = TCPHDR_FLAGS; }
			|	WINDOW		{ $$ = TCPHDR_WINDOW; }
			|	CHECKSUM	{ $$ = TCPHDR_CHECKSUM; }
			|	URGPTR		{ $$ = TCPHDR_URGPTR; }
			;

tcp_hdr_option_type	:	EOL		{ $$ = TCPOPTHDR_EOL; }
			|	NOOP		{ $$ = TCPOPTHDR_NOOP; }
			|	MAXSEG		{ $$ = TCPOPTHDR_MAXSEG; }
			|	WINDOW		{ $$ = TCPOPTHDR_WINDOW; }
			|	SACK_PERMITTED	{ $$ = TCPOPTHDR_SACK_PERMITTED; }
			|	SACK		{ $$ = TCPOPTHDR_SACK0; }
			|	SACK0		{ $$ = TCPOPTHDR_SACK0; }
			|	SACK1		{ $$ = TCPOPTHDR_SACK1; }
			|	SACK2		{ $$ = TCPOPTHDR_SACK2; }
			|	SACK3		{ $$ = TCPOPTHDR_SACK3; }
			|	ECHO		{ $$ = TCPOPTHDR_ECHO; }
			|	TIMESTAMP	{ $$ = TCPOPTHDR_TIMESTAMP; }
			;

tcp_hdr_option_field	:	KIND		{ $$ = TCPOPTHDR_FIELD_KIND; }
			|	LENGTH		{ $$ = TCPOPTHDR_FIELD_LENGTH; }
			|	SIZE		{ $$ = TCPOPTHDR_FIELD_SIZE; }
			|	COUNT		{ $$ = TCPOPTHDR_FIELD_COUNT; }
			|	LEFT		{ $$ = TCPOPTHDR_FIELD_LEFT; }
			|	RIGHT		{ $$ = TCPOPTHDR_FIELD_RIGHT; }
			|	TSVAL		{ $$ = TCPOPTHDR_FIELD_TSVAL; }
			|	TSECR		{ $$ = TCPOPTHDR_FIELD_TSECR; }
			;

dccp_hdr_expr		:	DCCP	dccp_hdr_field
			{
				$$ = payload_expr_alloc(&@$, &proto_dccp, $2);
			}
			;

dccp_hdr_field		:	SPORT		{ $$ = DCCPHDR_SPORT; }
			|	DPORT		{ $$ = DCCPHDR_DPORT; }
			|	TYPE		{ $$ = DCCPHDR_TYPE; }
			;

sctp_hdr_expr		:	SCTP	sctp_hdr_field
			{
				$$ = payload_expr_alloc(&@$, &proto_sctp, $2);
			}
			;

sctp_hdr_field		:	SPORT		{ $$ = SCTPHDR_SPORT; }
			|	DPORT		{ $$ = SCTPHDR_DPORT; }
			|	VTAG		{ $$ = SCTPHDR_VTAG; }
			|	CHECKSUM	{ $$ = SCTPHDR_CHECKSUM; }
			;

th_hdr_expr		:	TRANSPORT_HDR 	th_hdr_field
			{
				$$ = payload_expr_alloc(&@$, &proto_th, $2);
				if ($$)
					$$->payload.is_raw = true;
			}
			;

th_hdr_field		:	SPORT		{ $$ = THDR_SPORT; }
			|	DPORT		{ $$ = THDR_DPORT; }
			;

exthdr_expr		:	hbh_hdr_expr
			|	rt_hdr_expr
			|	rt0_hdr_expr
			|	rt2_hdr_expr
			|	rt4_hdr_expr
			|	frag_hdr_expr
			|	dst_hdr_expr
			|	mh_hdr_expr
			;

hbh_hdr_expr		:	HBH	hbh_hdr_field
			{
				$$ = exthdr_expr_alloc(&@$, &exthdr_hbh, $2);
			}
			;

hbh_hdr_field		:	NEXTHDR		{ $$ = HBHHDR_NEXTHDR; }
			|	HDRLENGTH	{ $$ = HBHHDR_HDRLENGTH; }
			;

rt_hdr_expr		:	RT	rt_hdr_field
			{
				$$ = exthdr_expr_alloc(&@$, &exthdr_rt, $2);
			}
			;

rt_hdr_field		:	NEXTHDR		{ $$ = RTHDR_NEXTHDR; }
			|	HDRLENGTH	{ $$ = RTHDR_HDRLENGTH; }
			|	TYPE		{ $$ = RTHDR_TYPE; }
			|	SEG_LEFT	{ $$ = RTHDR_SEG_LEFT; }
			;

rt0_hdr_expr		:	RT0	rt0_hdr_field
			{
				$$ = exthdr_expr_alloc(&@$, &exthdr_rt0, $2);
			}
			;

rt0_hdr_field		:	ADDR	'['	NUM	']'
			{
				$$ = RT0HDR_ADDR_1 + $3 - 1;
			}
			;

rt2_hdr_expr		:	RT2	rt2_hdr_field
			{
				$$ = exthdr_expr_alloc(&@$, &exthdr_rt2, $2);
			}
			;

rt2_hdr_field		:	ADDR		{ $$ = RT2HDR_ADDR; }
			;

rt4_hdr_expr		:	RT4	rt4_hdr_field
			{
				$$ = exthdr_expr_alloc(&@$, &exthdr_rt4, $2);
			}
			;

rt4_hdr_field		:	LAST_ENT	{ $$ = RT4HDR_LASTENT; }
			|	FLAGS		{ $$ = RT4HDR_FLAGS; }
			|	TAG		{ $$ = RT4HDR_TAG; }
			|	SID		'['	NUM	']'
			{
				$$ = RT4HDR_SID_1 + $3 - 1;
			}
			;

frag_hdr_expr		:	FRAG	frag_hdr_field
			{
				$$ = exthdr_expr_alloc(&@$, &exthdr_frag, $2);
			}
			;

frag_hdr_field		:	NEXTHDR		{ $$ = FRAGHDR_NEXTHDR; }
			|	RESERVED	{ $$ = FRAGHDR_RESERVED; }
			|	FRAG_OFF	{ $$ = FRAGHDR_FRAG_OFF; }
			|	RESERVED2	{ $$ = FRAGHDR_RESERVED2; }
			|	MORE_FRAGMENTS	{ $$ = FRAGHDR_MFRAGS; }
			|	ID		{ $$ = FRAGHDR_ID; }
			;

dst_hdr_expr		:	DST	dst_hdr_field
			{
				$$ = exthdr_expr_alloc(&@$, &exthdr_dst, $2);
			}
			;

dst_hdr_field		:	NEXTHDR		{ $$ = DSTHDR_NEXTHDR; }
			|	HDRLENGTH	{ $$ = DSTHDR_HDRLENGTH; }
			;

mh_hdr_expr		:	MH	mh_hdr_field
			{
				$$ = exthdr_expr_alloc(&@$, &exthdr_mh, $2);
			}
			;

mh_hdr_field		:	NEXTHDR		{ $$ = MHHDR_NEXTHDR; }
			|	HDRLENGTH	{ $$ = MHHDR_HDRLENGTH; }
			|	TYPE		{ $$ = MHHDR_TYPE; }
			|	RESERVED	{ $$ = MHHDR_RESERVED; }
			|	CHECKSUM	{ $$ = MHHDR_CHECKSUM; }
			;

exthdr_exists_expr	:	EXTHDR	exthdr_key
			{
				const struct exthdr_desc *desc;

				desc = exthdr_find_proto($2);

				/* Assume that NEXTHDR template is always
				 * the fist one in list of templates.
				 */
				$$ = exthdr_expr_alloc(&@$, desc, 1);
				$$->exthdr.flags = NFT_EXTHDR_F_PRESENT;
			}
			;

exthdr_key		:	HBH	{ $$ = IPPROTO_HOPOPTS; }
			|	RT	{ $$ = IPPROTO_ROUTING; }
			|	FRAG	{ $$ = IPPROTO_FRAGMENT; }
			|	DST	{ $$ = IPPROTO_DSTOPTS; }
			|	MH	{ $$ = IPPROTO_MH; }
			;

%%