#include <libnftnl/chain.h>
#include <libnftnl/expr.h>
#include <libnftnl/set.h>
+#include <libnftnl/common.h>
+#include <linux/netfilter/nfnetlink.h>
#include <linux/netfilter/nf_tables.h>
#include <linux/netfilter.h>
#include <erec.h>
static struct mnl_socket *nf_sock;
+static struct mnl_socket *nf_mon_sock;
const struct input_descriptor indesc_netlink = {
.name = "netlink",
.indesc = &indesc_netlink,
};
-static void __init netlink_open_sock(void)
+static struct mnl_socket *nfsock_open(void)
{
- nf_sock = mnl_socket_open(NETLINK_NETFILTER);
- if (nf_sock == NULL)
+ struct mnl_socket *s = mnl_socket_open(NETLINK_NETFILTER);
+ if (s == NULL)
netlink_open_error();
+ return s;
+}
+
+static void __init netlink_open_sock(void)
+{
+ nf_sock = nfsock_open();
fcntl(mnl_socket_get_fd(nf_sock), F_SETFL, O_NONBLOCK);
mnl_batch_init();
}
static void __exit netlink_close_sock(void)
{
mnl_socket_close(nf_sock);
+ if (nf_mon_sock)
+ mnl_socket_close(nf_mon_sock);
+}
+
+static void netlink_open_mon_sock(void)
+{
+ nf_mon_sock = nfsock_open();
+}
+
+void __noreturn netlink_abi_error(void)
+{
+ fprintf(stderr, "E: Contact urgently your Linux kernel vendor. "
+ "Netlink ABI is broken: %s\n", strerror(errno));
+ exit(NFT_EXIT_FAILURE);
}
int netlink_io_error(struct netlink_ctx *ctx, const struct location *loc,
return rs;
}
+
+static struct nft_table *netlink_table_alloc(const struct nlmsghdr *nlh)
+{
+ struct nft_table *nlt = nft_table_alloc();
+ if (nlt == NULL)
+ memory_allocation_error();
+
+ if (nft_table_nlmsg_parse(nlh, nlt) < 0)
+ netlink_abi_error();
+
+ return nlt;
+}
+
+static struct nft_chain *netlink_chain_alloc(const struct nlmsghdr *nlh)
+{
+ struct nft_chain *nlc = nft_chain_alloc();
+ if (nlc == NULL)
+ memory_allocation_error();
+
+ if (nft_chain_nlmsg_parse(nlh, nlc) < 0)
+ netlink_abi_error();
+
+ return nlc;
+}
+
+static struct nft_set *netlink_set_alloc(const struct nlmsghdr *nlh)
+{
+ struct nft_set *nls = nft_set_alloc();
+ if (nls == NULL)
+ memory_allocation_error();
+
+ if (nft_set_nlmsg_parse(nlh, nls) < 0)
+ netlink_abi_error();
+
+ return nls;
+}
+
+static struct nft_set *netlink_setelem_alloc(const struct nlmsghdr *nlh)
+{
+ struct nft_set *nls = nft_set_alloc();
+ if (nls == NULL)
+ memory_allocation_error();
+
+ if (nft_set_elems_nlmsg_parse(nlh, nls) < 0)
+ netlink_abi_error();
+
+ return nls;
+}
+
+static struct nft_rule *netlink_rule_alloc(const struct nlmsghdr *nlh)
+{
+ struct nft_rule *nlr = nft_rule_alloc();
+ if (nlr == NULL)
+ memory_allocation_error();
+
+ if (nft_rule_nlmsg_parse(nlh, nlr) < 0)
+ netlink_abi_error();
+
+ return nlr;
+}
+
+static uint32_t netlink_msg2nftnl_of(uint32_t msg)
+{
+ switch (msg) {
+ case NFT_MSG_NEWTABLE:
+ case NFT_MSG_NEWCHAIN:
+ case NFT_MSG_NEWSET:
+ case NFT_MSG_NEWSETELEM:
+ case NFT_MSG_NEWRULE:
+ return NFT_OF_EVENT_NEW;
+ case NFT_MSG_DELTABLE:
+ case NFT_MSG_DELCHAIN:
+ case NFT_MSG_DELSET:
+ case NFT_MSG_DELSETELEM:
+ case NFT_MSG_DELRULE:
+ return NFT_OF_EVENT_DEL;
+ }
+
+ return 0;
+}
+
+static int netlink_events_table_cb(const struct nlmsghdr *nlh, int type,
+ struct netlink_mon_handler *monh)
+{
+ uint32_t family;
+ struct nft_table *nlt = netlink_table_alloc(nlh);
+
+ if (monh->format == NFT_OUTPUT_DEFAULT) {
+ if (type == NFT_MSG_NEWTABLE) {
+ if (nlh->nlmsg_flags & NLM_F_EXCL)
+ printf("update table ");
+ else
+ printf("add table ");
+ } else {
+ printf("delete table ");
+ }
+
+ family = nft_table_attr_get_u32(nlt, NFT_TABLE_ATTR_FAMILY);
+
+ printf("%s %s\n", family2str(family),
+ nft_table_attr_get_str(nlt, NFT_TABLE_ATTR_NAME));
+ } else {
+ nft_table_fprintf(stdout, nlt, monh->format,
+ netlink_msg2nftnl_of(type));
+ fprintf(stdout, "\n");
+ }
+
+ nft_table_free(nlt);
+ return MNL_CB_OK;
+}
+
+static int netlink_events_chain_cb(const struct nlmsghdr *nlh, int type,
+ struct netlink_mon_handler *monh)
+{
+ struct chain *c;
+ uint32_t family;
+ struct nft_chain *nlc = netlink_chain_alloc(nlh);
+
+ if (monh->format == NFT_OUTPUT_DEFAULT) {
+ if (type == NFT_MSG_NEWCHAIN) {
+ if (nlh->nlmsg_flags & NLM_F_EXCL)
+ printf("update ");
+ else
+ printf("add ");
+
+ c = netlink_delinearize_chain(monh->ctx, nlc);
+ chain_print_plain(c);
+ chain_free(c);
+ } else {
+ family = nft_chain_attr_get_u32(nlc,
+ NFT_CHAIN_ATTR_FAMILY);
+ printf("delete chain %s %s %s\n", family2str(family),
+ nft_chain_attr_get_str(nlc,
+ NFT_CHAIN_ATTR_TABLE),
+ nft_chain_attr_get_str(nlc,
+ NFT_CHAIN_ATTR_NAME));
+ }
+ } else {
+ nft_chain_fprintf(stdout, nlc, monh->format,
+ netlink_msg2nftnl_of(type));
+ fprintf(stdout, "\n");
+ }
+
+ nft_chain_free(nlc);
+ return MNL_CB_OK;
+}
+
+static int netlink_events_set_cb(const struct nlmsghdr *nlh, int type,
+ struct netlink_mon_handler *monh)
+{
+ struct set *set;
+ uint32_t family, flags;
+ struct nft_set *nls = netlink_set_alloc(nlh);
+
+ flags = nft_set_attr_get_u32(nls, NFT_SET_ATTR_FLAGS);
+ if (flags & SET_F_ANONYMOUS)
+ goto out;
+
+ if (monh->format == NFT_OUTPUT_DEFAULT) {
+ if (type == NFT_MSG_NEWSET) {
+ printf("add ");
+ set = netlink_delinearize_set(monh->ctx, nls);
+ set_print_plain(set);
+ set_free(set);
+ } else {
+ family = nft_set_attr_get_u32(nls,
+ NFT_SET_ATTR_FAMILY);
+ printf("delete set %s %s %s",
+ family2str(family),
+ nft_set_attr_get_str(nls, NFT_SET_ATTR_TABLE),
+ nft_set_attr_get_str(nls, NFT_SET_ATTR_NAME));
+ }
+
+ printf("\n");
+
+ } else {
+ nft_set_fprintf(stdout, nls, monh->format,
+ netlink_msg2nftnl_of(type));
+ fprintf(stdout, "\n");
+ }
+
+out:
+ nft_set_free(nls);
+ return MNL_CB_OK;
+}
+
+static int netlink_events_setelem_cb(const struct nlmsghdr *nlh, int type,
+ struct netlink_mon_handler *monh)
+{
+ struct nft_set_elem *nlse;
+ struct nft_set_elems_iter *nlsei;
+ struct set *dummyset;
+ struct set *set;
+ const char *setname, *table;
+ uint32_t family;
+ struct nft_set *nls = netlink_setelem_alloc(nlh);
+
+ table = nft_set_attr_get_str(nls, NFT_SET_ATTR_TABLE);
+ setname = nft_set_attr_get_str(nls, NFT_SET_ATTR_NAME);
+ family = nft_set_attr_get_u32(nls, NFT_SET_ATTR_FAMILY);
+
+ set = set_lookup_global(family, table, setname);
+ if (set == NULL) {
+ fprintf(stderr, "W: Received event for an unknown set.");
+ goto out;
+ }
+
+ if (monh->format == NFT_OUTPUT_DEFAULT) {
+ if (set->flags & SET_F_ANONYMOUS)
+ goto out;
+
+ /* we want to 'delinearize' the set_elem, but don't
+ * modify the original cached set. This path is only
+ * used by named sets, so use a dummy set.
+ */
+ dummyset = set_alloc(monh->loc);
+ dummyset->keytype = set->keytype;
+ dummyset->datatype = set->datatype;
+ dummyset->init = set_expr_alloc(monh->loc);
+
+ nlsei = nft_set_elems_iter_create(nls);
+ if (nlsei == NULL)
+ memory_allocation_error();
+
+ nlse = nft_set_elems_iter_next(nlsei);
+ while (nlse != NULL) {
+ if (netlink_delinearize_setelem(nlse, dummyset) < 0) {
+ set_free(dummyset);
+ nft_set_elems_iter_destroy(nlsei);
+ goto out;
+ }
+ nlse = nft_set_elems_iter_next(nlsei);
+ }
+ nft_set_elems_iter_destroy(nlsei);
+
+ if (type == NFT_MSG_NEWSETELEM)
+ printf("add ");
+ else
+ printf("delete ");
+
+ printf("element %s %s %s ", family2str(family), table, setname);
+ expr_print(dummyset->init);
+ printf("\n");
+
+ set_free(dummyset);
+ } else {
+ nft_set_fprintf(stdout, nls, monh->format,
+ netlink_msg2nftnl_of(type));
+ fprintf(stdout, "\n");
+ }
+
+out:
+ nft_set_free(nls);
+ return MNL_CB_OK;
+}
+
+static int netlink_events_rule_cb(const struct nlmsghdr *nlh, int type,
+ struct netlink_mon_handler *monh)
+{
+ struct rule *r;
+ uint32_t fam;
+ const char *family;
+ const char *table;
+ const char *chain;
+ uint64_t handle;
+ struct nft_rule *nlr = netlink_rule_alloc(nlh);
+
+ if (monh->format == NFT_OUTPUT_DEFAULT) {
+ fam = nft_rule_attr_get_u32(nlr, NFT_RULE_ATTR_FAMILY);
+ family = family2str(fam);
+ table = nft_rule_attr_get_str(nlr, NFT_RULE_ATTR_TABLE);
+ chain = nft_rule_attr_get_str(nlr, NFT_RULE_ATTR_CHAIN);
+ handle = nft_rule_attr_get_u64(nlr, NFT_RULE_ATTR_HANDLE);
+
+ if (type == NFT_MSG_NEWRULE) {
+ r = netlink_delinearize_rule(monh->ctx, nlr);
+
+ printf("add rule %s %s %s", family, table, chain);
+ rule_print(r);
+ printf("\n");
+
+ rule_free(r);
+ goto out;
+ }
+
+ printf("delete rule %s %s %s handle %u\n",
+ family, table, chain, (unsigned int)handle);
+ } else {
+ nft_rule_fprintf(stdout, nlr, monh->format,
+ netlink_msg2nftnl_of(type));
+ fprintf(stdout, "\n");
+ }
+
+out:
+ nft_rule_free(nlr);
+ return MNL_CB_OK;
+}
+
+static void netlink_events_cache_addtable(struct netlink_mon_handler *monh,
+ const struct nlmsghdr *nlh)
+{
+ struct table *t;
+ struct nft_table *nlt = netlink_table_alloc(nlh);
+
+ t = netlink_delinearize_table(monh->ctx, nlt);
+ table_add_hash(t);
+
+ nft_table_free(nlt);
+}
+
+static void netlink_events_cache_deltable(struct netlink_mon_handler *monh,
+ const struct nlmsghdr *nlh)
+{
+ struct table *t;
+ struct handle h;
+ struct nft_table *nlt = netlink_table_alloc(nlh);
+
+ h.family = nft_table_attr_get_u32(nlt, NFT_TABLE_ATTR_FAMILY);
+ h.table = nft_table_attr_get_str(nlt, NFT_TABLE_ATTR_NAME);
+
+ t = table_lookup(&h);
+ if (t == NULL)
+ goto out;
+
+ list_del(&t->list);
+ table_free(t);
+
+out:
+ nft_table_free(nlt);
+}
+
+static void netlink_events_cache_addset(struct netlink_mon_handler *monh,
+ const struct nlmsghdr *nlh)
+{
+ struct set *s;
+ LIST_HEAD(msgs);
+ struct table *t;
+ struct netlink_ctx set_tmpctx;
+ struct nft_set *nls = netlink_set_alloc(nlh);
+
+ memset(&set_tmpctx, 0, sizeof(set_tmpctx));
+ init_list_head(&set_tmpctx.list);
+ init_list_head(&msgs);
+ set_tmpctx.msgs = &msgs;
+
+ s = netlink_delinearize_set(&set_tmpctx, nls);
+ s->init = set_expr_alloc(monh->loc);
+
+ t = table_lookup(&s->handle);
+ if (t == NULL) {
+ fprintf(stderr, "W: Unable to cache set: table not found.\n");
+ goto out;
+ }
+
+ set_add_hash(s, t);
+out:
+ nft_set_free(nls);
+}
+
+static void netlink_events_cache_addsetelem(struct netlink_mon_handler *monh,
+ const struct nlmsghdr *nlh)
+{
+ struct set *set;
+ struct nft_set_elem *nlse;
+ struct nft_set_elems_iter *nlsei;
+ const char *table, *setname;
+ struct nft_set *nls = netlink_setelem_alloc(nlh);
+
+ table = nft_set_attr_get_str(nls, NFT_SET_ATTR_TABLE);
+ setname = nft_set_attr_get_str(nls, NFT_SET_ATTR_NAME);
+
+ set = set_lookup_global(nft_set_attr_get_u32(nls, NFT_SET_ATTR_FAMILY),
+ table, setname);
+ if (set == NULL) {
+ fprintf(stderr,
+ "W: Unable to cache set_elem. Set not found.\n");
+ goto out;
+ }
+
+ nlsei = nft_set_elems_iter_create(nls);
+ if (nlsei == NULL)
+ memory_allocation_error();
+
+ nlse = nft_set_elems_iter_next(nlsei);
+ while (nlse != NULL) {
+ if (netlink_delinearize_setelem(nlse, set) < 0) {
+ fprintf(stderr,
+ "W: Unable to cache set_elem. "
+ "Delinearize failed.\n");
+ nft_set_elems_iter_destroy(nlsei);
+ goto out;
+ }
+ nlse = nft_set_elems_iter_next(nlsei);
+ }
+ nft_set_elems_iter_destroy(nlsei);
+
+out:
+ nft_set_free(nls);
+}
+
+static void netlink_events_cache_delsets(struct netlink_mon_handler *monh,
+ const struct nlmsghdr *nlh)
+{
+ struct set *s;
+ uint32_t family;
+ struct nft_rule_expr *nlre;
+ struct nft_rule_expr_iter *nlrei;
+ const char *expr_name, *set_name, *table;
+ struct nft_rule *nlr = netlink_rule_alloc(nlh);
+
+ nlrei = nft_rule_expr_iter_create(nlr);
+ if (nlrei == NULL)
+ memory_allocation_error();
+
+ family = nft_rule_attr_get_u32(nlr, NFT_RULE_ATTR_FAMILY);
+ table = nft_rule_attr_get_str(nlr, NFT_RULE_ATTR_TABLE);
+
+ nlre = nft_rule_expr_iter_next(nlrei);
+ while (nlre != NULL) {
+ expr_name = nft_rule_expr_get_str(nlre,
+ NFT_RULE_EXPR_ATTR_NAME);
+ if (strcmp(expr_name, "lookup") != 0)
+ goto next;
+
+ set_name = nft_rule_expr_get_str(nlre, NFT_EXPR_LOOKUP_SET);
+ s = set_lookup_global(family, table, set_name);
+ if (s == NULL)
+ goto next;
+
+ list_del(&s->list);
+ set_free(s);
+next:
+ nlre = nft_rule_expr_iter_next(nlrei);
+ }
+ nft_rule_expr_iter_destroy(nlrei);
+
+ nft_rule_free(nlr);
+}
+
+static void netlink_events_cache_update(struct netlink_mon_handler *monh,
+ const struct nlmsghdr *nlh, int type)
+{
+ if (!monh->cache_needed)
+ return;
+
+ switch (type) {
+ case NFT_MSG_NEWTABLE:
+ netlink_events_cache_addtable(monh, nlh);
+ break;
+ case NFT_MSG_DELTABLE:
+ netlink_events_cache_deltable(monh, nlh);
+ break;
+ case NFT_MSG_NEWSET:
+ netlink_events_cache_addset(monh, nlh);
+ break;
+ case NFT_MSG_NEWSETELEM:
+ netlink_events_cache_addsetelem(monh, nlh);
+ break;
+ case NFT_MSG_DELRULE:
+ /* there are no notification for anon-set deletion */
+ netlink_events_cache_delsets(monh, nlh);
+ break;
+ }
+}
+
+static int netlink_events_cb(const struct nlmsghdr *nlh, void *data)
+{
+ int ret = MNL_CB_OK;
+ int type = nlh->nlmsg_type & 0xFF;
+ struct netlink_mon_handler *monh = (struct netlink_mon_handler *)data;
+
+ netlink_events_cache_update(monh, nlh, type);
+
+ if (!(monh->monitor_flags & (1 << type)))
+ return ret;
+
+ switch (type) {
+ case NFT_MSG_NEWTABLE:
+ case NFT_MSG_DELTABLE:
+ ret = netlink_events_table_cb(nlh, type, monh);
+ break;
+ case NFT_MSG_NEWCHAIN:
+ case NFT_MSG_DELCHAIN:
+ ret = netlink_events_chain_cb(nlh, type, monh);
+ break;
+ case NFT_MSG_NEWSET:
+ case NFT_MSG_DELSET: /* nft {add|delete} set */
+ ret = netlink_events_set_cb(nlh, type, monh);
+ break;
+ case NFT_MSG_NEWSETELEM:
+ case NFT_MSG_DELSETELEM: /* nft {add|delete} element */
+ ret = netlink_events_setelem_cb(nlh, type, monh);
+ break;
+ case NFT_MSG_NEWRULE:
+ case NFT_MSG_DELRULE:
+ ret = netlink_events_rule_cb(nlh, type, monh);
+ break;
+ default:
+ BUG("Unknow event received from netlink.\n");
+ break;
+ }
+
+ return ret;
+}
+
+int netlink_monitor(struct netlink_mon_handler *monhandler)
+{
+ netlink_open_mon_sock();
+
+ if (mnl_socket_bind(nf_mon_sock, (1 << (NFNLGRP_NFTABLES-1)),
+ MNL_SOCKET_AUTOPID) < 0)
+ return netlink_io_error(monhandler->ctx, monhandler->loc,
+ "Could not bind to netlink socket %s",
+ strerror(errno));
+
+ return mnl_nft_event_listener(nf_mon_sock, netlink_events_cb,
+ monhandler);
+}
%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 HANDLE "handle"
+%token NEW "new"
+%token DESTROY "destroy"
%token INET "inet"
%token RENAME "rename"
%token DESCRIBE "describe"
%token EXPORT "export"
+%token MONITOR "monitor"
%token ACCEPT "accept"
%token DROP "drop"
%type <cmd> line
%destructor { cmd_free($$); } line
-%type <cmd> base_cmd add_cmd create_cmd insert_cmd delete_cmd list_cmd flush_cmd rename_cmd export_cmd
-%destructor { cmd_free($$); } base_cmd add_cmd create_cmd insert_cmd delete_cmd list_cmd flush_cmd rename_cmd export_cmd
+%type <cmd> base_cmd add_cmd create_cmd insert_cmd delete_cmd list_cmd flush_cmd rename_cmd export_cmd monitor_cmd
+%destructor { cmd_free($$); } base_cmd add_cmd create_cmd insert_cmd delete_cmd list_cmd flush_cmd rename_cmd export_cmd monitor_cmd
%type <handle> table_spec tables_spec chain_spec chain_identifier ruleid_spec
%destructor { handle_free(&$$); } table_spec tables_spec chain_spec chain_identifier ruleid_spec
%destructor { expr_free($$); } ct_expr
%type <val> ct_key
-%type <val> export_format
+%type <val> export_format output_format monitor_flags
%%
| FLUSH flush_cmd { $$ = $2; }
| RENAME rename_cmd { $$ = $2; }
| EXPORT export_cmd { $$ = $2; }
+ | MONITOR monitor_cmd { $$ = $2; }
| DESCRIBE primary_expr
{
expr_describe($2);
}
;
+monitor_cmd : monitor_flags output_format
+ {
+ struct handle h = { .family = NFPROTO_UNSPEC };
+ $$ = cmd_alloc(CMD_MONITOR, CMD_OBJ_RULESET, &h, &@$, NULL);
+ $$->monitor_flags = $1;
+ $$->format = $2;
+ }
+ ;
+
+monitor_flags : /* empty */
+ {
+ $$ |= (1 << NFT_MSG_NEWRULE);
+ $$ |= (1 << NFT_MSG_DELRULE);
+ $$ |= (1 << NFT_MSG_NEWSET);
+ $$ |= (1 << NFT_MSG_DELSET);
+ $$ |= (1 << NFT_MSG_NEWSETELEM);
+ $$ |= (1 << NFT_MSG_DELSETELEM);
+ $$ |= (1 << NFT_MSG_NEWCHAIN);
+ $$ |= (1 << NFT_MSG_DELCHAIN);
+ $$ |= (1 << NFT_MSG_NEWTABLE);
+ $$ |= (1 << NFT_MSG_DELTABLE);
+ }
+ | NEW
+ {
+ $$ |= (1 << NFT_MSG_NEWRULE);
+ $$ |= (1 << NFT_MSG_NEWSET);
+ $$ |= (1 << NFT_MSG_NEWSETELEM);
+ $$ |= (1 << NFT_MSG_NEWCHAIN);
+ $$ |= (1 << NFT_MSG_NEWTABLE);
+ }
+ | DESTROY
+ {
+ $$ |= (1 << NFT_MSG_DELRULE);
+ $$ |= (1 << NFT_MSG_DELSET);
+ $$ |= (1 << NFT_MSG_DELSETELEM);
+ $$ |= (1 << NFT_MSG_DELCHAIN);
+ $$ |= (1 << NFT_MSG_DELTABLE);
+ }
+ | TABLES
+ {
+ $$ |= (1 << NFT_MSG_NEWTABLE); $$ |= (1 << NFT_MSG_DELTABLE);
+ }
+ | NEW TABLES { $$ |= (1 << NFT_MSG_NEWTABLE); }
+ | DESTROY TABLES { $$ |= (1 << NFT_MSG_DELTABLE); }
+ | CHAIN
+ {
+ $$ |= (1 << NFT_MSG_NEWCHAIN); $$ |= (1 << NFT_MSG_DELCHAIN);
+ }
+ | NEW CHAINS { $$ |= (1 << NFT_MSG_NEWCHAIN); }
+ | DESTROY CHAINS { $$ |= (1 << NFT_MSG_DELCHAIN); }
+ | SETS
+ {
+ $$ |= (1 << NFT_MSG_NEWSET); $$ |= (1 << NFT_MSG_DELSET);
+ }
+ | NEW SETS { $$ |= (1 << NFT_MSG_NEWSET); }
+ | DESTROY SETS { $$ |= (1 << NFT_MSG_DELSET); }
+ | RULE
+ {
+ $$ |= (1 << NFT_MSG_NEWRULE); $$ |= (1 << NFT_MSG_DELRULE);
+ }
+ | NEW RULES { $$ |= (1 << NFT_MSG_NEWRULE); }
+ | DESTROY RULES { $$ |= (1 << NFT_MSG_DELRULE); }
+ | ELEMENTS
+ {
+ $$ |= (1 << NFT_MSG_NEWSETELEM);
+ $$ |= (1 << NFT_MSG_DELSETELEM);
+ }
+ | NEW ELEMENTS { $$ |= (1 << NFT_MSG_NEWSETELEM); }
+ | DESTROY ELEMENTS { $$ |= (1 << NFT_MSG_DELSETELEM); }
+ ;
+
+output_format : /* empty */
+ {
+ $$ = NFT_OUTPUT_DEFAULT;
+ }
+ | export_format
+ ;
+
table_block_alloc : /* empty */
{
$$ = table_alloc();