summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorPatrick McHardy <kaber@trash.net>2009-03-18 04:55:00 +0100
committerPatrick McHardy <kaber@trash.net>2009-03-18 04:55:00 +0100
commitfac10ea799fe9b6158d74f66d6ad46536d38a545 (patch)
tree8c093bcbb2144aab54c70103e6ed438456ae0d48 /src
Initial commitv0.01-alpha1
Diffstat (limited to 'src')
-rw-r--r--src/.gitignore5
-rw-r--r--src/Makefile.in30
-rw-r--r--src/cli.c175
-rw-r--r--src/ct.c149
-rw-r--r--src/datatype.c568
-rw-r--r--src/erec.c159
-rw-r--r--src/evaluate.c1031
-rw-r--r--src/expression.c622
-rw-r--r--src/exthdr.c238
-rw-r--r--src/gmputil.c156
-rw-r--r--src/main.c202
-rw-r--r--src/meta.c347
-rw-r--r--src/netlink.c476
-rw-r--r--src/netlink_delinearize.c781
-rw-r--r--src/netlink_linearize.c720
-rw-r--r--src/parser-skeleton.c1529
-rw-r--r--src/parser.y1386
-rw-r--r--src/payload.c908
-rw-r--r--src/rbtree.c388
-rw-r--r--src/rule.c441
-rw-r--r--src/scanner.l581
-rw-r--r--src/segtree.c541
-rw-r--r--src/statement.c210
-rw-r--r--src/utils.c69
24 files changed, 11712 insertions, 0 deletions
diff --git a/src/.gitignore b/src/.gitignore
new file mode 100644
index 00000000..23e6ae03
--- /dev/null
+++ b/src/.gitignore
@@ -0,0 +1,5 @@
+parser.c
+parser.h
+scanner.c
+scanner.h
+nft
diff --git a/src/Makefile.in b/src/Makefile.in
new file mode 100644
index 00000000..3c933070
--- /dev/null
+++ b/src/Makefile.in
@@ -0,0 +1,30 @@
+PROGRAMS += nft
+YACCFLAGS += --skeleton=../../..$(shell pwd)/src/parser-skeleton.c
+
+nft-destdir := @sbindir@
+
+nft-obj += main.o
+nft-obj += cli.o
+nft-obj += rule.o
+nft-obj += statement.o
+nft-obj += datatype.o
+nft-obj += expression.o
+nft-obj += evaluate.o
+nft-obj += payload.o
+nft-obj += exthdr.o
+nft-obj += meta.o
+nft-obj += ct.o
+nft-obj += netlink.o
+nft-obj += netlink_linearize.o
+nft-obj += netlink_delinearize.o
+nft-obj += segtree.o
+nft-obj += rbtree.o
+nft-obj += gmputil.o
+nft-obj += utils.o
+nft-obj += erec.o
+
+nft-obj += parser.o
+nft-extra-clean-files += parser.c parser.h
+
+nft-obj += scanner.o
+nft-extra-clean-files += scanner.c scanner.h
diff --git a/src/cli.c b/src/cli.c
new file mode 100644
index 00000000..e302dfa8
--- /dev/null
+++ b/src/cli.c
@@ -0,0 +1,175 @@
+/*
+ * Asynchronous readline-based interactive interface
+ *
+ * Actually not asynchronous so far, but intended to be.
+ *
+ * Copyright (c) 2008 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 <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <ctype.h>
+#include <limits.h>
+#include <readline/readline.h>
+#include <readline/history.h>
+
+#include <nftables.h>
+#include <parser.h>
+#include <erec.h>
+#include <utils.h>
+
+#define CMDLINE_HISTFILE ".nft.history"
+
+static const struct input_descriptor indesc_cli = {
+ .type = INDESC_CLI,
+};
+
+static struct parser_state *state;
+static void *scanner;
+
+static char histfile[PATH_MAX];
+static char *multiline;
+static bool eof;
+
+static char *cli_append_multiline(char *line)
+{
+ bool complete = false;
+ size_t len;
+ char *s;
+
+ if (line == NULL && multiline == NULL) {
+ eof = true;
+ return NULL;
+ }
+
+ len = strlen(line);
+ if (line[len - 1] == '\\') {
+ line[len - 1] = '\0';
+ len--;
+ } else if (multiline == NULL)
+ return line;
+ else
+ complete = 1;
+
+ if (multiline == NULL) {
+ multiline = line;
+ rl_save_prompt();
+ rl_clear_message();
+ } else {
+ len += strlen(multiline);
+ s = xmalloc(len + 1);
+ snprintf(s, len + 1, "%s%s", multiline, line);
+ xfree(multiline);
+ multiline = s;
+ }
+ line = NULL;
+
+ if (complete) {
+ line = multiline;
+ multiline = NULL;
+ rl_restore_prompt();
+ }
+ return line;
+}
+
+static void cli_complete(char *line)
+{
+ const HIST_ENTRY *hist;
+ const char *c;
+
+ line = cli_append_multiline(line);
+ if (line == NULL)
+ return;
+
+ for (c = line; *c != '\0'; c++)
+ if (!isspace(*c))
+ break;
+ if (*c == '\0')
+ return;
+
+ /* avoid duplicate history entries */
+ hist = history_get(history_length);
+ if (hist == NULL || strcmp(hist->line, line))
+ add_history(line);
+
+ scanner_push_buffer(scanner, &indesc_cli, line);
+ nft_parse(scanner, state);
+
+ erec_print_list(stdout, state->msgs);
+ xfree(line);
+}
+
+static char **cli_completion(const char *text, int start, int end)
+{
+ return NULL;
+}
+
+void __fmtstring(1, 0) cli_display(const char *fmt, va_list ap)
+{
+ int point, end;
+ char *buf;
+
+ point = rl_point;
+ end = rl_end;
+ rl_point = rl_end = 0;
+
+ rl_save_prompt();
+ rl_clear_message();
+
+ if (vasprintf(&buf, fmt, ap) < 0)
+ fprintf(rl_outstream, "cli_display: out of memory\n");
+ else {
+ fprintf(rl_outstream, "%s\n", buf);
+ xfree(buf);
+ }
+
+ rl_restore_prompt();
+
+ rl_point = point;
+ rl_end = end;
+ rl_forced_update_display();
+}
+
+int cli_init(void *_scanner, struct parser_state *_state)
+{
+ const char *home;
+
+ rl_readline_name = "nft";
+ rl_instream = stdin;
+ rl_outstream = stdout;
+
+ rl_callback_handler_install("nft> ", cli_complete);
+ rl_attempted_completion_function = cli_completion;
+
+ home = getenv("HOME");
+ if (home == NULL)
+ home = "";
+ snprintf(histfile, sizeof(histfile), "%s/%s", home, CMDLINE_HISTFILE);
+
+ read_history(histfile);
+ history_set_pos(history_length);
+
+ scanner = _scanner;
+ state = _state;
+
+ while (!eof)
+ rl_callback_read_char();
+ return 0;
+}
+
+void cli_exit(void)
+{
+ rl_callback_handler_remove();
+ rl_deprep_terminal();
+ write_history(histfile);
+}
diff --git a/src/ct.c b/src/ct.c
new file mode 100644
index 00000000..00895394
--- /dev/null
+++ b/src/ct.c
@@ -0,0 +1,149 @@
+/*
+ * Conntrack expression related definitions and types.
+ *
+ * Copyright (c) 2008 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 <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+
+#include <linux/netfilter/nf_tables.h>
+#include <linux/netfilter/nf_conntrack_common.h>
+#include <linux/netfilter/nf_conntrack_tuple_common.h>
+
+#include <expression.h>
+#include <datatype.h>
+#include <ct.h>
+#include <utils.h>
+
+static const struct symbol_table ct_state_tbl = {
+ .byteorder = BYTEORDER_HOST_ENDIAN,
+ .size = 4 * BITS_PER_BYTE,
+ .symbols = {
+ SYMBOL("invalid", NF_CT_STATE_INVALID_BIT),
+ SYMBOL("new", NF_CT_STATE_BIT(IP_CT_NEW)),
+ SYMBOL("established", NF_CT_STATE_BIT(IP_CT_ESTABLISHED)),
+ SYMBOL("related", NF_CT_STATE_BIT(IP_CT_RELATED)),
+ SYMBOL("untracked", NF_CT_STATE_UNTRACKED_BIT),
+ SYMBOL_LIST_END
+ }
+};
+
+static const struct datatype ct_state_type = {
+ .type = TYPE_CT_STATE,
+ .name = "conntrack state",
+ .basetype = &bitmask_type,
+ .sym_tbl = &ct_state_tbl,
+};
+
+static const struct symbol_table ct_dir_tbl = {
+ .byteorder = BYTEORDER_INVALID,
+ .size = BITS_PER_BYTE,
+ .symbols = {
+ SYMBOL("original", IP_CT_DIR_ORIGINAL),
+ SYMBOL("reply", IP_CT_DIR_REPLY),
+ SYMBOL_LIST_END
+ }
+};
+
+static const struct datatype ct_dir_type = {
+ .type = TYPE_CT_DIR,
+ .name = "conntrack direction",
+ .basetype = &bitmask_type,
+ .sym_tbl = &ct_dir_tbl,
+};
+
+static const struct symbol_table ct_status_tbl = {
+ .byteorder = BYTEORDER_HOST_ENDIAN,
+ .size = 4 * BITS_PER_BYTE,
+ /*
+ * There are more, but most of them don't make sense for filtering.
+ */
+ .symbols = {
+ SYMBOL("expected", IPS_EXPECTED),
+ SYMBOL("seen-reply", IPS_SEEN_REPLY),
+ SYMBOL("assured", IPS_ASSURED),
+ SYMBOL("confirmed", IPS_CONFIRMED),
+ SYMBOL("snat", IPS_SRC_NAT),
+ SYMBOL("dnat", IPS_DST_NAT),
+ SYMBOL("dying", IPS_DYING),
+ SYMBOL_LIST_END
+ },
+};
+
+static const struct datatype ct_status_type = {
+ .type = TYPE_CT_STATUS,
+ .name = "conntrack status",
+ .basetype = &bitmask_type,
+ .sym_tbl = &ct_status_tbl,
+};
+
+static const struct ct_template ct_templates[] = {
+ [NFT_CT_STATE] = CT_TEMPLATE("state", &ct_state_type,
+ BYTEORDER_HOST_ENDIAN,
+ 4 * BITS_PER_BYTE),
+ [NFT_CT_DIRECTION] = CT_TEMPLATE("direction", &ct_dir_type,
+ BYTEORDER_HOST_ENDIAN,
+ BITS_PER_BYTE),
+ [NFT_CT_STATUS] = CT_TEMPLATE("status", &ct_status_type,
+ BYTEORDER_HOST_ENDIAN,
+ 4 * BITS_PER_BYTE),
+ [NFT_CT_MARK] = CT_TEMPLATE("mark", &mark_type,
+ BYTEORDER_HOST_ENDIAN,
+ 4 * BITS_PER_BYTE),
+ [NFT_CT_SECMARK] = CT_TEMPLATE("secmark", &integer_type,
+ BYTEORDER_HOST_ENDIAN,
+ 4 * BITS_PER_BYTE),
+ [NFT_CT_EXPIRATION] = CT_TEMPLATE("expiration", &time_type,
+ BYTEORDER_HOST_ENDIAN,
+ 4 * BITS_PER_BYTE),
+ [NFT_CT_HELPER] = CT_TEMPLATE("helper", &string_type,
+ BYTEORDER_INVALID, 0),
+ [NFT_CT_L3PROTO] = CT_TEMPLATE("l3proto", &invalid_type,
+ BYTEORDER_INVALID,
+ BITS_PER_BYTE),
+ [NFT_CT_SADDR] = CT_TEMPLATE("saddr", &invalid_type,
+ BYTEORDER_BIG_ENDIAN, 0),
+ [NFT_CT_DADDR] = CT_TEMPLATE("daddr", &invalid_type,
+ BYTEORDER_BIG_ENDIAN, 0),
+ [NFT_CT_PROTOCOL] = CT_TEMPLATE("protocol", &inet_protocol_type,
+ BYTEORDER_BIG_ENDIAN,
+ BITS_PER_BYTE),
+ [NFT_CT_PROTO_SRC] = CT_TEMPLATE("proto-src", &invalid_type,
+ BYTEORDER_BIG_ENDIAN,
+ 2 * BITS_PER_BYTE),
+ [NFT_CT_PROTO_DST] = CT_TEMPLATE("proto-dst", &invalid_type,
+ BYTEORDER_BIG_ENDIAN,
+ 2 * BITS_PER_BYTE),
+};
+
+static void ct_expr_print(const struct expr *expr)
+{
+ printf("ct %s", ct_templates[expr->ct.key].token);
+}
+
+static const struct expr_ops ct_expr_ops = {
+ .type = EXPR_CT,
+ .name = "ct",
+ .print = ct_expr_print,
+};
+
+struct expr *ct_expr_alloc(const struct location *loc, enum nft_ct_keys key)
+{
+ const struct ct_template *tmpl = &ct_templates[key];
+ struct expr *expr;
+
+ expr = expr_alloc(loc, &ct_expr_ops, tmpl->dtype,
+ tmpl->byteorder, tmpl->len);
+ expr->ct.key = key;
+ return expr;
+}
diff --git a/src/datatype.c b/src/datatype.c
new file mode 100644
index 00000000..8e17c218
--- /dev/null
+++ b/src/datatype.c
@@ -0,0 +1,568 @@
+/*
+ * Copyright (c) 2008 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 <stdlib.h>
+#include <string.h>
+#include <inttypes.h>
+#include <netdb.h>
+#include <arpa/inet.h>
+#include <linux/types.h>
+#include <linux/netfilter.h>
+
+#include <nftables.h>
+#include <datatype.h>
+#include <expression.h>
+#include <gmputil.h>
+#include <erec.h>
+
+void datatype_print(const struct expr *expr)
+{
+ const struct datatype *dtype = expr->dtype;
+
+ if (dtype->print != NULL)
+ return dtype->print(expr);
+ if (dtype->sym_tbl != NULL)
+ return symbolic_constant_print(dtype->sym_tbl, expr);
+ BUG();
+}
+
+struct error_record *symbol_parse(const struct expr *sym,
+ struct expr **res)
+{
+ const struct datatype *dtype = sym->sym_type;
+
+ assert(sym->ops->type == EXPR_SYMBOL);
+
+ if (dtype == NULL)
+ return error(&sym->location, "No symbol type information");
+ if (dtype->parse != NULL)
+ return dtype->parse(sym, res);
+ if (dtype->sym_tbl != NULL)
+ return symbolic_constant_parse(sym, dtype->sym_tbl, res);
+
+ return error(&sym->location,
+ "Can't parse symbolic %s expressions",
+ sym->sym_type->name);
+}
+
+struct error_record *symbolic_constant_parse(const struct expr *sym,
+ const struct symbol_table *tbl,
+ struct expr **res)
+{
+ const struct symbolic_constant *s;
+
+ for (s = tbl->symbols; s->identifier != NULL; s++) {
+ if (!strcmp(sym->identifier, s->identifier))
+ break;
+ }
+
+ if (s->identifier == NULL)
+ return error(&sym->location, "Could not parse %s",
+ sym->sym_type->name);
+
+ *res = constant_expr_alloc(&sym->location, sym->sym_type,
+ tbl->byteorder, tbl->size, &s->value);
+ return NULL;
+}
+
+void symbolic_constant_print(const struct symbol_table *tbl,
+ const struct expr *expr)
+{
+ const struct symbolic_constant *s;
+
+ for (s = tbl->symbols; s->identifier != NULL; s++) {
+ if (!mpz_cmp_ui(expr->value, s->value))
+ break;
+ }
+
+ if (s->identifier == NULL)
+ return expr_basetype(expr)->print(expr);
+
+ printf("%s", s->identifier);
+}
+
+void symbol_table_print(const struct symbol_table *tbl)
+{
+ const struct symbolic_constant *s;
+ unsigned int size = 2 * tbl->size / BITS_PER_BYTE;
+
+ for (s = tbl->symbols; s->identifier != NULL; s++)
+ printf("\t%-30s\t0x%.*" PRIx64 "\n",
+ s->identifier, size, s->value);
+}
+
+static void invalid_type_print(const struct expr *expr)
+{
+ gmp_printf("0x%Zx [invalid type]", expr->value);
+}
+
+const struct datatype invalid_type = {
+ .type = TYPE_INVALID,
+ .name = "invalid",
+ .print = invalid_type_print,
+};
+
+static void verdict_type_print(const struct expr *expr)
+{
+ switch (expr->verdict) {
+ case NF_ACCEPT:
+ printf("accept");
+ break;
+ case NF_DROP:
+ printf("drop");
+ break;
+ case NF_QUEUE:
+ printf("queue");
+ break;
+ case NFT_CONTINUE:
+ printf("continue");
+ break;
+ case NFT_BREAK:
+ printf("break");
+ break;
+ case NFT_JUMP:
+ printf("jump %s", expr->chain);
+ break;
+ case NFT_GOTO:
+ printf("goto %s", expr->chain);
+ break;
+ case NFT_RETURN:
+ printf("return");
+ break;
+ default:
+ BUG();
+ }
+}
+
+const struct datatype verdict_type = {
+ .type = TYPE_VERDICT,
+ .name = "verdict",
+ .print = verdict_type_print,
+};
+
+const struct datatype bitmask_type = {
+ .type = TYPE_BITMASK,
+ .name = "bitmask",
+ .basetype = &integer_type,
+};
+
+static void integer_type_print(const struct expr *expr)
+{
+ const char *fmt = "%Zu";
+
+ if (expr->dtype->basefmt != NULL)
+ fmt = expr->dtype->basefmt;
+ gmp_printf(fmt, expr->value);
+}
+
+static struct error_record *integer_type_parse(const struct expr *sym,
+ struct expr **res)
+{
+ mpz_t v;
+
+ mpz_init(v);
+ if (gmp_sscanf(sym->identifier, "%Zu", v) != 1) {
+ mpz_clear(v);
+ if (sym->sym_type != &integer_type)
+ return NULL;
+ return error(&sym->location, "Could not parse %s",
+ sym->sym_type->name);
+ }
+
+ *res = constant_expr_alloc(&sym->location, sym->sym_type,
+ BYTEORDER_HOST_ENDIAN, 1, NULL);
+ mpz_set((*res)->value, v);
+ mpz_clear(v);
+ return NULL;
+}
+
+const struct datatype integer_type = {
+ .type = TYPE_INTEGER,
+ .name = "integer",
+ .print = integer_type_print,
+ .parse = integer_type_parse,
+};
+
+static void string_type_print(const struct expr *expr)
+{
+ unsigned int len = div_round_up(expr->len, BITS_PER_BYTE);
+ char data[len];
+
+ mpz_export_data(data, expr->value, BYTEORDER_BIG_ENDIAN, len);
+ printf("\"%s\"", data);
+}
+
+static struct error_record *string_type_parse(const struct expr *sym,
+ struct expr **res)
+{
+ *res = constant_expr_alloc(&sym->location, &string_type,
+ BYTEORDER_INVALID,
+ (strlen(sym->identifier) + 1) * BITS_PER_BYTE,
+ sym->identifier);
+ return NULL;
+}
+
+const struct datatype string_type = {
+ .type = TYPE_STRING,
+ .name = "string",
+ .print = string_type_print,
+ .parse = string_type_parse,
+};
+
+static void lladdr_type_print(const struct expr *expr)
+{
+ unsigned int len = div_round_up(expr->len, BITS_PER_BYTE);
+ const char *delim = "";
+ uint8_t data[len];
+ unsigned int i;
+
+ mpz_export_data(data, expr->value, BYTEORDER_HOST_ENDIAN, len);
+ for (i = 0; i < len; i++) {
+ printf("%s%.2x", delim, data[i]);
+ delim = ":";
+ }
+}
+
+static struct error_record *lladdr_type_parse(const struct expr *sym,
+ struct expr **res)
+{
+ char buf[strlen(sym->identifier) + 1], *p;
+ const char *s = sym->identifier;
+ unsigned int len, n;
+
+ for (len = 0;;) {
+ n = strtoul(s, &p, 16);
+ if (s == p || n > 0xff)
+ return erec_create(EREC_ERROR, &sym->location,
+ "Invalid LL address");
+ buf[len++] = n;
+ if (*p == '\0')
+ break;
+ s = ++p;
+ }
+
+ *res = constant_expr_alloc(&sym->location, &lladdr_type,
+ BYTEORDER_HOST_ENDIAN, len * BITS_PER_BYTE,
+ buf);
+ return NULL;
+}
+
+const struct datatype lladdr_type = {
+ .type = TYPE_LLADDR,
+ .name = "LL address",
+ .basetype = &integer_type,
+ .print = lladdr_type_print,
+ .parse = lladdr_type_parse,
+};
+
+static void ipaddr_type_print(const struct expr *expr)
+{
+ struct sockaddr_in sin = { .sin_family = AF_INET, };
+ char buf[NI_MAXHOST];
+
+ sin.sin_addr.s_addr = mpz_get_be32(expr->value);
+ getnameinfo((struct sockaddr *)&sin, sizeof(sin), buf, sizeof(buf),
+ NULL, 0, numeric_output ? NI_NUMERICHOST : 0);
+ printf("%s", buf);
+}
+
+static struct error_record *ipaddr_type_parse(const struct expr *sym,
+ struct expr **res)
+{
+ struct addrinfo *ai, hints = { .ai_family = AF_INET,
+ .ai_socktype = SOCK_DGRAM};
+ struct in_addr *addr;
+ int err;
+
+ err = getaddrinfo(sym->identifier, NULL, &hints, &ai);
+ if (err != 0)
+ return error(&sym->location, "Could not resolve hostname: %s",
+ gai_strerror(err));
+
+ if (ai->ai_next != NULL) {
+ freeaddrinfo(ai);
+ return error(&sym->location,
+ "Hostname resolves to multiple addresses");
+ }
+
+ addr = &((struct sockaddr_in *)ai->ai_addr)->sin_addr;
+ *res = constant_expr_alloc(&sym->location, &ipaddr_type,
+ BYTEORDER_BIG_ENDIAN,
+ sizeof(*addr) * BITS_PER_BYTE, addr);
+ freeaddrinfo(ai);
+ return NULL;
+}
+
+const struct datatype ipaddr_type = {
+ .type = TYPE_IPADDR,
+ .name = "IPv4 address",
+ .basetype = &integer_type,
+ .print = ipaddr_type_print,
+ .parse = ipaddr_type_parse,
+};
+
+static void ip6addr_type_print(const struct expr *expr)
+{
+ struct sockaddr_in6 sin6 = { .sin6_family = AF_INET6 };
+ char buf[NI_MAXHOST];
+
+ mpz_export_data(&sin6.sin6_addr, expr->value, BYTEORDER_BIG_ENDIAN,
+ sizeof(sin6.sin6_addr));
+
+ getnameinfo((struct sockaddr *)&sin6, sizeof(sin6), buf, sizeof(buf),
+ NULL, 0, numeric_output ? NI_NUMERICHOST : 0);
+ printf("%s", buf);
+}
+
+static struct error_record *ip6addr_type_parse(const struct expr *sym,
+ struct expr **res)
+{
+ struct addrinfo *ai, hints = { .ai_family = AF_INET6,
+ .ai_socktype = SOCK_DGRAM};
+ struct in6_addr *addr;
+ int err;
+
+ err = getaddrinfo(sym->identifier, NULL, &hints, &ai);
+ if (err != 0)
+ return error(&sym->location, "Could not resolve hostname: %s",
+ gai_strerror(err));
+
+ if (ai->ai_next != NULL) {
+ freeaddrinfo(ai);
+ return error(&sym->location,
+ "Hostname resolves to multiple addresses");
+ }
+
+ addr = &((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr;
+ *res = constant_expr_alloc(&sym->location, &ip6addr_type,
+ BYTEORDER_BIG_ENDIAN,
+ sizeof(*addr) * BITS_PER_BYTE, addr);
+ freeaddrinfo(ai);
+ return NULL;
+}
+
+const struct datatype ip6addr_type = {
+ .type = TYPE_IP6ADDR,
+ .name = "IPv6 address",
+ .basetype = &integer_type,
+ .print = ip6addr_type_print,
+ .parse = ip6addr_type_parse,
+};
+
+static void inet_protocol_type_print(const struct expr *expr)
+{
+ struct protoent *p;
+
+ if (numeric_output < NUMERIC_ALL) {
+ p = getprotobynumber(mpz_get_uint8(expr->value));
+ if (p != NULL) {
+ printf("%s", p->p_name);
+ return;
+ }
+ }
+ integer_type_print(expr);
+}
+
+static struct error_record *inet_protocol_type_parse(const struct expr *sym,
+ struct expr **res)
+{
+ struct protoent *p;
+
+ p = getprotobyname(sym->identifier);
+ if (p == NULL)
+ return error(&sym->location, "Could not resolve protocol name");
+
+ *res = constant_expr_alloc(&sym->location, &inet_protocol_type,
+ BYTEORDER_HOST_ENDIAN, BITS_PER_BYTE,
+ &p->p_proto);
+ return NULL;
+}
+
+const struct datatype inet_protocol_type = {
+ .type = TYPE_INET_PROTOCOL,
+ .name = "Internet protocol",
+ .basetype = &integer_type,
+ .print = inet_protocol_type_print,
+ .parse = inet_protocol_type_parse,
+};
+
+static void inet_service_type_print(const struct expr *expr)
+{
+ struct sockaddr_in sin = { .sin_family = AF_INET };
+ char buf[NI_MAXSERV];
+
+ sin.sin_port = mpz_get_be16(expr->value);
+ getnameinfo((struct sockaddr *)&sin, sizeof(sin), NULL, 0,
+ buf, sizeof(buf),
+ numeric_output < NUMERIC_ALL ? 0 : NI_NUMERICSERV);
+ printf("%s", buf);
+}
+
+static struct error_record *inet_service_type_parse(const struct expr *sym,
+ struct expr **res)
+{
+ struct addrinfo *ai;
+ uint16_t port;
+ int err;
+
+ err = getaddrinfo(NULL, sym->identifier, NULL, &ai);
+ if (err != 0)
+ return error(&sym->location, "Could not resolve service: %s",
+ gai_strerror(err));
+
+ port = ((struct sockaddr_in *)ai->ai_addr)->sin_port;
+ *res = constant_expr_alloc(&sym->location, &inet_service_type,
+ BYTEORDER_BIG_ENDIAN,
+ sizeof(port) * BITS_PER_BYTE, &port);
+ freeaddrinfo(ai);
+ return NULL;
+}
+
+const struct datatype inet_service_type = {
+ .type = TYPE_INET_SERVICE,
+ .name = "internet network service",
+ .basetype = &integer_type,
+ .print = inet_service_type_print,
+ .parse = inet_service_type_parse,
+};
+
+#define RT_SYM_TAB_INITIAL_SIZE 16
+
+struct symbol_table *rt_symbol_table_init(const char *filename)
+{
+ struct symbolic_constant s;
+ struct symbol_table *tbl;
+ unsigned int size, nelems, val;
+ char buf[512], namebuf[512], *p;
+ FILE *f;
+
+ size = RT_SYM_TAB_INITIAL_SIZE;
+ tbl = xmalloc(sizeof(*tbl) + size * sizeof(s));
+ nelems = 0;
+
+ tbl->size = 4 * BITS_PER_BYTE;
+ tbl->byteorder = BYTEORDER_HOST_ENDIAN;
+
+ f = fopen(filename, "r");
+ if (f == NULL)
+ goto out;
+
+ while (fgets(buf, sizeof(buf), f)) {
+ p = buf;
+ while (*p == ' ' || *p == '\t')
+ p++;
+ if (*p == '#' || *p == '\n' || *p == '\0')
+ continue;
+ if (sscanf(p, "0x%x %511s\n", &val, namebuf) != 2 &&
+ sscanf(p, "0x%x %511s #", &val, namebuf) != 2 &&
+ sscanf(p, "%u %511s\n", &val, namebuf) != 2 &&
+ sscanf(p, "%u %511s #", &val, namebuf) != 2) {
+ fprintf(stderr, "iproute database '%s' corrupted\n",
+ filename);
+ goto out;
+ }
+
+ /* One element is reserved for list terminator */
+ if (nelems == size - 2) {
+ size *= 2;
+ tbl = xrealloc(tbl, sizeof(*tbl) + size * sizeof(s));
+ }
+
+ tbl->symbols[nelems].identifier = xstrdup(namebuf);
+ tbl->symbols[nelems].value = val;
+ nelems++;
+ }
+
+ fclose(f);
+out:
+ tbl->symbols[nelems] = SYMBOL_LIST_END;
+ return tbl;
+}
+
+void rt_symbol_table_free(struct symbol_table *tbl)
+{
+ const struct symbolic_constant *s;
+
+ for (s = tbl->symbols; s->identifier != NULL; s++)
+ xfree(s->identifier);
+ xfree(tbl);
+}
+
+static struct symbol_table *mark_tbl;
+static void __init mark_table_init(void)
+{
+ mark_tbl = rt_symbol_table_init("/etc/iproute2/rt_marks");
+}
+
+static void __exit mark_table_exit(void)
+{
+ rt_symbol_table_free(mark_tbl);
+}
+
+static void mark_type_print(const struct expr *expr)
+{
+ return symbolic_constant_print(mark_tbl, expr);
+}
+
+static struct error_record *mark_type_parse(const struct expr *sym,
+ struct expr **res)
+{
+ return symbolic_constant_parse(sym, mark_tbl, res);
+}
+
+const struct datatype mark_type = {
+ .type = TYPE_MARK,
+ .name = "packet mark",
+ .basetype = &integer_type,
+ .basefmt = "0x%.8Zx",
+ .print = mark_type_print,
+ .parse = mark_type_parse,
+};
+
+static void time_type_print(const struct expr *expr)
+{
+ uint64_t days, hours, minutes, seconds;
+ const char *delim = "";
+
+ seconds = mpz_get_uint64(expr->value);
+
+ days = seconds / 86400;
+ seconds %= 86400;
+
+ hours = seconds / 3600;
+ seconds %= 3600;
+
+ minutes = seconds / 60;
+ seconds %= 60;
+
+ if (days > 0) {
+ printf("%s%" PRIu64 " d", delim, days);
+ delim = " ";
+ }
+ if (hours > 0) {
+ printf("%s%" PRIu64 " h", delim, hours);
+ delim = " ";
+ }
+ if (minutes > 0) {
+ printf("%s%" PRIu64 " min", delim, minutes);
+ delim = " ";
+ }
+ if (seconds > 0) {
+ printf("%s%" PRIu64 " s", delim, seconds);
+ delim = " ";
+ }
+}
+
+const struct datatype time_type = {
+ .type = TYPE_TIME,
+ .name = "relative time",
+ .basetype = &integer_type,
+ .print = time_type_print,
+};
diff --git a/src/erec.c b/src/erec.c
new file mode 100644
index 00000000..501bf4b6
--- /dev/null
+++ b/src/erec.c
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 2008 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 <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include <netlink.h>
+#include <gmputil.h>
+#include <erec.h>
+
+static const struct input_descriptor internal_indesc = {
+ .type = INDESC_INTERNAL,
+ .name = "internal",
+};
+
+const struct location internal_location = {
+ .indesc = &internal_indesc,
+};
+
+static const char *error_record_names[] = {
+ [EREC_INFORMATIONAL] = NULL,
+ [EREC_WARNING] = "Warning",
+ [EREC_ERROR] = "Error"
+};
+
+void erec_add_location(struct error_record *erec, const struct location *loc)
+{
+ assert(erec->num_locations < EREC_LOCATIONS_MAX);
+ erec->locations[erec->num_locations++] = *loc;
+}
+
+static void erec_destroy(struct error_record *erec)
+{
+ xfree(erec->msg);
+ xfree(erec);
+}
+
+struct error_record *erec_vcreate(enum error_record_types type,
+ const struct location *loc,
+ const char *fmt, va_list ap)
+{
+ struct error_record *erec;
+
+ erec = xmalloc(sizeof(*erec));
+ erec->type = type;
+ erec->num_locations = 0;
+ erec_add_location(erec, loc);
+
+ gmp_vasprintf(&erec->msg, fmt, ap);
+ return erec;
+}
+
+struct error_record *erec_create(enum error_record_types type,
+ const struct location *loc,
+ const char *fmt, ...)
+{
+ struct error_record *erec;
+ va_list ap;
+
+ va_start(ap, fmt);
+ erec = erec_vcreate(type, loc, fmt, ap);
+ va_end(ap);
+ return erec;
+}
+
+void erec_print(FILE *f, const struct error_record *erec)
+{
+ const struct location *loc = erec->locations, *iloc;
+ const struct input_descriptor *indesc = loc->indesc, *tmp;
+ const char *line = NULL; /* silence gcc */
+ char buf[1024];
+ unsigned int i, end;
+ int l;
+
+ switch (indesc->type) {
+ case INDESC_BUFFER:
+ case INDESC_CLI:
+ line = indesc->data;
+ break;
+ case INDESC_FILE:
+ memset(buf, 0, sizeof(buf));
+ lseek(indesc->fd, loc->line_offset, SEEK_SET);
+ read(indesc->fd, buf, sizeof(buf) - 1);
+ *strchrnul(buf, '\n') = '\0';
+ line = buf;
+ break;
+ case INDESC_INTERNAL:
+ case INDESC_NETLINK:
+ break;
+ default:
+ BUG();
+ }
+
+ if (indesc->type == INDESC_NETLINK) {
+ fprintf(f, "%s: ", indesc->name);
+ if (error_record_names[erec->type])
+ fprintf(f, "%s: ", error_record_names[erec->type]);
+ fprintf(f, "%s\n", erec->msg);
+ for (l = 0; l < (int)erec->num_locations; l++) {
+ loc = &erec->locations[l];
+ netlink_dump_object(loc->nl_obj);
+ }
+ fprintf(f, "\n");
+ } else {
+ if (indesc->location.indesc != NULL) {
+ const char *prefix = "In file included from";
+ iloc = &indesc->location;
+ for (tmp = iloc->indesc; tmp != NULL; tmp = iloc->indesc) {
+ fprintf(f, "%s %s:%u:%u-%u:\n", prefix,
+ tmp->name,
+ iloc->first_line, iloc->first_column,
+ iloc->last_column);
+ prefix = " from";
+ iloc = &tmp->location;
+ }
+ }
+ if (indesc->name != NULL)
+ fprintf(f, "%s:%u:%u-%u: ", indesc->name,
+ loc->first_line, loc->first_column,
+ loc->last_column);
+ if (error_record_names[erec->type])
+ fprintf(f, "%s: ", error_record_names[erec->type]);
+ fprintf(f, "%s\n", erec->msg);
+
+ if (indesc->type != INDESC_INTERNAL)
+ fprintf(f, "%s\n", line);
+
+ memset(buf, ' ', sizeof(buf));
+ end = 0;
+ for (l = erec->num_locations - 1; l >= 0; l--) {
+ loc = &erec->locations[l];
+ for (i = loc->first_column - 1; i < loc->last_column; i++)
+ buf[i] = l ? '~' : '^';
+ end = max(end, loc->last_column);
+ }
+ buf[end] = '\0';
+ fprintf(f, "%s", buf);
+ }
+ fprintf(f, "\n");
+}
+
+void erec_print_list(FILE *f, struct list_head *list)
+{
+ struct error_record *erec, *next;
+
+ list_for_each_entry_safe(erec, next, list, list) {
+ list_del(&erec->list);
+ erec_print(f, erec);
+ erec_destroy(erec);
+ }
+}
diff --git a/src/evaluate.c b/src/evaluate.c
new file mode 100644
index 00000000..0deff9ad
--- /dev/null
+++ b/src/evaluate.c
@@ -0,0 +1,1031 @@
+/*
+ * Copyright (c) 2008 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 <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <arpa/inet.h>
+#include <linux/netfilter/nf_tables.h>
+
+#include <expression.h>
+#include <statement.h>
+#include <rule.h>
+#include <erec.h>
+#include <gmputil.h>
+#include <utils.h>
+
+#define TRACE 0
+
+static int expr_evaluate(struct eval_ctx *ctx, struct expr **expr);
+static int stmt_evaluate(struct eval_ctx *ctx, struct stmt *stmt);
+
+static const char *byteorder_names[] = {
+ [BYTEORDER_INVALID] = "invalid",
+ [BYTEORDER_HOST_ENDIAN] = "host endian",
+ [BYTEORDER_BIG_ENDIAN] = "big endian",
+};
+
+
+static int __fmtstring(4, 5) stmt_binary_error(struct eval_ctx *ctx,
+ const struct stmt *s1,
+ const struct stmt *s2,
+ const char *fmt, ...)
+{
+ struct error_record *erec;
+ va_list ap;
+
+ va_start(ap, fmt);
+ erec = erec_vcreate(EREC_ERROR, &s1->location, fmt, ap);
+ if (s2 != NULL)
+ erec_add_location(erec, &s2->location);
+ va_end(ap);
+ erec_queue(erec, ctx->msgs);
+ return -1;
+}
+
+static enum ops byteorder_conversion_op(struct expr *expr,
+ enum byteorder byteorder)
+{
+ switch (expr->byteorder) {
+ case BYTEORDER_HOST_ENDIAN:
+ if (byteorder == BYTEORDER_BIG_ENDIAN)
+ return OP_HTON;
+ break;
+ case BYTEORDER_BIG_ENDIAN:
+ if (byteorder == BYTEORDER_HOST_ENDIAN)
+ return OP_NTOH;
+ break;
+ default:
+ break;
+ }
+ BUG();
+}
+
+static int byteorder_conversion(struct eval_ctx *ctx, struct expr **expr,
+ enum byteorder byteorder)
+{
+ enum ops op;
+
+ assert(!expr_is_constant(*expr) || expr_is_singleton(*expr));
+
+ if ((*expr)->byteorder == byteorder)
+ return 0;
+ if (expr_basetype(*expr)->type != TYPE_INTEGER)
+ return expr_error(ctx, *expr,
+ "Byteorder mismatch: expected %s, got %s",
+ byteorder_names[byteorder],
+ byteorder_names[(*expr)->byteorder]);
+
+ if (expr_is_constant(*expr))
+ (*expr)->byteorder = byteorder;
+ else {
+ op = byteorder_conversion_op(*expr, byteorder);
+ *expr = unary_expr_alloc(&(*expr)->location, op, *expr);
+ if (expr_evaluate(ctx, expr) < 0)
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * Symbol expression: parse symbol and evaluate resulting expression.
+ */
+static int expr_evaluate_symbol(struct eval_ctx *ctx, struct expr **expr)
+{
+ struct error_record *erec;
+ struct expr *new;
+
+ (*expr)->sym_type = ctx->ectx.dtype;
+ erec = symbol_parse(*expr, &new);
+ if (erec != NULL) {
+ erec_queue(erec, ctx->msgs);
+ return -1;
+ }
+
+ expr_free(*expr);
+ *expr = new;
+
+ return expr_evaluate(ctx, expr);
+}
+
+static int expr_evaluate_value(struct eval_ctx *ctx, struct expr **expr)
+{
+ mpz_t mask;
+
+ switch (expr_basetype(*expr)->type) {
+ case TYPE_INTEGER:
+ mpz_init_bitmask(mask, ctx->ectx.len);
+ if (mpz_cmp((*expr)->value, mask) > 0) {
+ expr_error(ctx, *expr,
+ "Value %Zu exceeds valid range 0-%Zu",
+ (*expr)->value, mask);
+ mpz_clear(mask);
+ return -1;
+ }
+ (*expr)->len = ctx->ectx.len;
+ mpz_clear(mask);
+ break;
+ case TYPE_STRING:
+ if (ctx->ectx.len > 0) {
+ if ((*expr)->len > ctx->ectx.len)
+ return expr_error(ctx, *expr,
+ "String exceeds maximum length of %u",
+ ctx->ectx.len / BITS_PER_BYTE);
+ (*expr)->len = ctx->ectx.len;
+ }
+ break;
+ default:
+ BUG();
+ }
+ return 0;
+}
+
+/*
+ * Primary expressions determine the datatype context.
+ */
+static int expr_evaluate_primary(struct eval_ctx *ctx, struct expr **expr)
+{
+ ctx->ectx.dtype = (*expr)->dtype;
+ ctx->ectx.len = (*expr)->len;
+ (*expr)->flags |= EXPR_F_PRIMARY;
+ return 0;
+}
+
+/*
+ * Payload expression: check whether dependencies are fulfilled, otherwise
+ * generate the necessary relational expression and prepend it to the current
+ * statement.
+ */
+static int expr_evaluate_payload(struct eval_ctx *ctx, struct expr **expr)
+{
+ struct expr *payload = *expr;
+ enum payload_bases base = payload->payload.base;
+ struct stmt *nstmt;
+ struct expr *nexpr;
+
+ if (ctx->pctx.protocol[base].desc == NULL) {
+ if (payload_gen_dependency(ctx, payload, &nexpr) < 0)
+ return -1;
+ nstmt = expr_stmt_alloc(&nexpr->location, nexpr);
+ if (stmt_evaluate(ctx, nstmt) < 0)
+ return -1;
+ list_add_tail(&nstmt->list, &ctx->stmt->list);
+ } else if (ctx->pctx.protocol[base].desc != payload->payload.desc)
+ return expr_error(ctx, payload,
+ "conflicting protocols specified: %s vs. %s",
+ ctx->pctx.protocol[base].desc->name,
+ payload->payload.desc->name);
+
+ return expr_evaluate_primary(ctx, expr);
+}
+
+/*
+ * Prefix expression: the argument must be a constant value of integer base
+ * type; the prefix length must be less than or equal to the type width.
+ */
+static int expr_evaluate_prefix(struct eval_ctx *ctx, struct expr **expr)
+{
+ struct expr *prefix = *expr, *base, *and, *mask;
+
+ if (expr_evaluate(ctx, &prefix->expr) < 0)
+ return -1;
+ base = prefix->expr;
+
+ if (!expr_is_constant(base))
+ return expr_error(ctx, prefix,
+ "Prefix expression is undefined for "
+ "non-constant expressions");
+
+ if (expr_basetype(base)->type != TYPE_INTEGER)
+ return expr_error(ctx, prefix,
+ "Prefix expression is undefined for "
+ "%s types", base->dtype->name);
+
+ if (prefix->prefix_len > base->len)
+ return expr_error(ctx, prefix,
+ "Prefix length %u is invalid for type "
+ "of %u bits width",
+ prefix->prefix_len, base->len);
+
+ /* Clear the uncovered bits of the base value */
+ mask = constant_expr_alloc(&prefix->location, &integer_type,
+ BYTEORDER_HOST_ENDIAN, base->len, NULL);
+ mpz_prefixmask(mask->value, base->len, prefix->prefix_len);
+ and = binop_expr_alloc(&prefix->location, OP_AND, base, mask);
+
+ prefix->expr = and;
+ if (expr_evaluate(ctx, &prefix->expr) < 0)
+ return -1;
+ base = prefix->expr;
+ assert(expr_is_constant(base));
+
+ prefix->dtype = base->dtype;
+ prefix->byteorder = base->byteorder;
+ prefix->len = base->len;
+ prefix->flags |= EXPR_F_CONSTANT;
+ return 0;
+}
+
+/*
+ * Range expression: both sides must be constants of integer base type.
+ */
+static int expr_evaluate_range_expr(struct eval_ctx *ctx,
+ const struct expr *range,
+ struct expr **expr)
+{
+ if (expr_evaluate(ctx, expr) < 0)
+ return -1;
+
+ if (expr_basetype(*expr)->type != TYPE_INTEGER)
+ return expr_binary_error(ctx, *expr, range,
+ "Range expression is undefined for "
+ "%s types", (*expr)->dtype->name);
+ if (!expr_is_constant(*expr))
+ return expr_binary_error(ctx, *expr, range,
+ "Range is not constant");
+ return 0;
+}
+
+static int expr_evaluate_range(struct eval_ctx *ctx, struct expr **expr)
+{
+ struct expr *range = *expr, *left, *right;
+
+ if (expr_evaluate_range_expr(ctx, range, &range->left) < 0)
+ return -1;
+ left = range->left;
+
+ if (expr_evaluate_range_expr(ctx, range, &range->right) < 0)
+ return -1;
+ right = range->right;
+
+ if (mpz_cmp(left->value, right->value) >= 0)
+ return expr_error(ctx, range, "Range has zero or negative size");
+
+ range->dtype = left->dtype;
+ range->flags |= EXPR_F_CONSTANT;
+ return 0;
+}
+
+/*
+ * Unary expressions: unary expressions are only generated internally for
+ * byteorder conversion of non-constant numerical expressions.
+ */
+static int expr_evaluate_unary(struct eval_ctx *ctx, struct expr **expr)
+{
+ struct expr *unary = *expr, *arg;
+ enum byteorder byteorder;
+
+ if (expr_evaluate(ctx, &unary->arg) < 0)
+ return -1;
+ arg = unary->arg;
+
+ assert(!expr_is_constant(arg));
+ assert(expr_basetype(arg)->type == TYPE_INTEGER);
+ assert(arg->ops->type != EXPR_UNARY);
+
+ switch (unary->op) {
+ case OP_HTON:
+ assert(arg->byteorder == BYTEORDER_HOST_ENDIAN);
+ byteorder = BYTEORDER_BIG_ENDIAN;
+ break;
+ case OP_NTOH:
+ assert(arg->byteorder == BYTEORDER_BIG_ENDIAN);
+ byteorder = BYTEORDER_HOST_ENDIAN;
+ break;
+ default:
+ BUG();
+ }
+
+ unary->dtype = arg->dtype;
+ unary->byteorder = byteorder;
+ unary->len = arg->len;
+ return 0;
+}
+
+/*
+ * Binops
+ */
+static int constant_binop_simplify(struct eval_ctx *ctx, struct expr **expr)
+{
+ struct expr *op = *expr, *left = (*expr)->left, *right = (*expr)->right;
+ struct expr *new;
+ mpz_t val, mask;
+
+ assert(left->ops->type == EXPR_VALUE);
+ assert(right->ops->type == EXPR_VALUE);
+ assert(left->byteorder == right->byteorder);
+
+ mpz_init2(val, op->len);
+ mpz_init_bitmask(mask, op->len);
+
+ switch (op->op) {
+ case OP_AND:
+ mpz_and(val, left->value, right->value);
+ mpz_and(val, val, mask);
+ break;
+ case OP_XOR:
+ mpz_xor(val, left->value, right->value);
+ mpz_and(val, val, mask);
+ break;
+ case OP_OR:
+ mpz_ior(val, left->value, right->value);
+ mpz_and(val, val, mask);
+ break;
+ case OP_LSHIFT:
+ assert(left->byteorder == BYTEORDER_HOST_ENDIAN);
+ mpz_lshift_ui(val, mpz_get_uint32(right->value));
+ mpz_and(val, val, mask);
+ break;
+ case OP_RSHIFT:
+ assert(left->byteorder == BYTEORDER_HOST_ENDIAN);
+ mpz_set(val, left->value);
+ mpz_and(val, val, mask);
+ mpz_rshift_ui(val, mpz_get_uint32(right->value));
+ break;
+ default:
+ BUG();
+ }
+
+ new = constant_expr_alloc(&op->location, op->dtype, op->byteorder,
+ op->len, NULL);
+ mpz_set(new->value, val);
+
+ expr_free(*expr);
+ *expr = new;
+
+ mpz_clear(mask);
+ mpz_clear(val);
+
+ return expr_evaluate(ctx, expr);
+}
+
+static int expr_evaluate_shift(struct eval_ctx *ctx, struct expr **expr)
+{
+ struct expr *op = *expr, *left = op->left, *right = op->right;
+
+ if (mpz_get_uint32(right->value) >= left->len)
+ return expr_binary_error(ctx, right, left,
+ "%s shift of %u bits is undefined "
+ "for type of %u bits width",
+ op->op == OP_LSHIFT ? "Left" : "Right",
+ mpz_get_uint32(right->value),
+ left->len);
+
+ /* Both sides need to be in host byte order */
+ if (byteorder_conversion(ctx, &op->left, BYTEORDER_HOST_ENDIAN) < 0)
+ return -1;
+ left = op->left;
+ if (byteorder_conversion(ctx, &op->right, BYTEORDER_HOST_ENDIAN) < 0)
+ return -1;
+
+ op->dtype = &integer_type;
+ op->byteorder = BYTEORDER_HOST_ENDIAN;
+ op->len = left->len;
+
+ if (expr_is_constant(left))
+ return constant_binop_simplify(ctx, expr);
+ return 0;
+}
+
+static int expr_evaluate_bitwise(struct eval_ctx *ctx, struct expr **expr)
+{
+ struct expr *op = *expr, *left = op->left, *right = op->right;
+
+ if (byteorder_conversion(ctx, &op->right, left->byteorder) < 0)
+ return -1;
+ right = op->right;
+
+ op->dtype = left->dtype;
+ op->byteorder = left->byteorder;
+ op->len = left->len;
+
+ if (expr_is_constant(left))
+ return constant_binop_simplify(ctx, expr);
+ return 0;
+}
+
+/*
+ * Binop expression: both sides must be of integer base type. The left
+ * hand side may be either constant or non-constant; in case its constant
+ * it must be a singleton. The ride hand side must always be a constant
+ * singleton.
+ */
+static int expr_evaluate_binop(struct eval_ctx *ctx, struct expr **expr)
+{
+ struct expr *op = *expr, *left, *right;
+ const char *sym = expr_op_symbols[op->op];
+
+ if (expr_evaluate(ctx, &op->left) < 0)
+ return -1;
+ left = op->left;
+
+ if (op->op == OP_LSHIFT || op->op == OP_RSHIFT)
+ expr_set_context(&ctx->ectx, &integer_type, ctx->ectx.len);
+ if (expr_evaluate(ctx, &op->right) < 0)
+ return -1;
+ right = op->right;
+
+ if (expr_basetype(left)->type != TYPE_INTEGER)
+ return expr_binary_error(ctx, left, op,
+ "Binary operation (%s) is undefined "
+ "for %s types",
+ sym, left->dtype->name);
+
+ if (expr_is_constant(left) && !expr_is_singleton(left))
+ return expr_binary_error(ctx, left, op,
+ "Binary operation (%s) is undefined "
+ "for %s expressions",
+ sym, left->ops->name);
+
+ if (!expr_is_constant(right))
+ return expr_binary_error(ctx, right, op,
+ "Right hand side of binary operation "
+ "(%s) must be constant", sym);
+
+ if (!expr_is_singleton(right))
+ return expr_binary_error(ctx, left, op,
+ "Binary operation (%s) is undefined "
+ "for %s expressions",
+ sym, right->ops->name);
+
+ /* The grammar guarantees this */
+ assert(expr_basetype(left) == expr_basetype(right));
+
+ switch (op->op) {
+ case OP_LSHIFT:
+ case OP_RSHIFT:
+ return expr_evaluate_shift(ctx, expr);
+ case OP_AND:
+ case OP_XOR:
+ case OP_OR:
+ return expr_evaluate_bitwise(ctx, expr);
+ default:
+ BUG();
+ }
+}
+
+static int list_member_evaluate(struct eval_ctx *ctx, struct expr **expr)
+{
+ struct expr *next = list_entry((*expr)->list.next, struct expr, list);
+ int err;
+
+ assert(*expr != next);
+ list_del(&(*expr)->list);
+ err = expr_evaluate(ctx, expr);
+ list_add_tail(&(*expr)->list, &next->list);
+ return err;
+}
+
+static int expr_evaluate_concat(struct eval_ctx *ctx, struct expr **expr)
+{
+ unsigned int flags = EXPR_F_CONSTANT;
+ struct expr *i, *next;
+
+ list_for_each_entry_safe(i, next, &(*expr)->expressions, list) {
+ if (list_member_evaluate(ctx, &i) < 0)
+ return -1;
+ flags &= i->flags;
+ }
+
+ (*expr)->flags |= flags;
+ return 0;
+}
+
+static int expr_evaluate_list(struct eval_ctx *ctx, struct expr **expr)
+{
+ struct expr *list = *expr, *new, *i, *next;
+ mpz_t val;
+
+ mpz_init_set_ui(val, 0);
+ list_for_each_entry_safe(i, next, &list->expressions, list) {
+ if (list_member_evaluate(ctx, &i) < 0)
+ return -1;
+ if (i->ops->type != EXPR_VALUE)
+ return expr_error(ctx, i,
+ "List member must be a constant "
+ "value");
+ if (i->dtype->basetype->type != TYPE_BITMASK)
+ return expr_error(ctx, i,
+ "Basetype of type %s is not bitmask",
+ i->dtype->name);
+ mpz_ior(val, val, i->value);
+ }
+
+ new = constant_expr_alloc(&list->location, ctx->ectx.dtype,
+ BYTEORDER_HOST_ENDIAN, ctx->ectx.len, NULL);
+ mpz_set(new->value, val);
+ mpz_clear(val);
+
+ expr_free(*expr);
+ *expr = new;
+ return 0;
+}
+
+static int expr_evaluate_set(struct eval_ctx *ctx, struct expr **expr)
+{
+ struct expr *set = *expr, *i, *next;
+
+ list_for_each_entry_safe(i, next, &set->expressions, list) {
+ if (list_member_evaluate(ctx, &i) < 0)
+ return -1;
+ if (!expr_is_constant(i))
+ return expr_error(ctx, i, "Set member is not constant");
+ if (!expr_is_singleton(i))
+ set->flags |= SET_F_INTERVAL;
+ }
+
+ set->dtype = ctx->ectx.dtype;
+ set->len = ctx->ectx.len;
+ set->flags |= EXPR_F_CONSTANT;
+ return 0;
+}
+
+static int expr_evaluate_map(struct eval_ctx *ctx, struct expr **expr)
+{
+ struct expr_ctx ectx = ctx->ectx;
+ struct expr *map = *expr, *i;
+
+ if (expr_evaluate(ctx, &map->expr) < 0)
+ return -1;
+ if (expr_is_constant(map->expr))
+ return expr_error(ctx, map->expr,
+ "Map expression can not be constant");
+
+ /* FIXME: segtree needs to know the dimension of the *key*.
+ * The len should actually be the value of the mapping. */
+ map->mappings->dtype = ctx->ectx.dtype;
+ map->mappings->len = ctx->ectx.len;
+
+ list_for_each_entry(i, &map->mappings->expressions, list) {
+ expr_set_context(&ctx->ectx, map->expr->dtype, map->expr->len);
+ if (expr_evaluate(ctx, &i->left) < 0)
+ return -1;
+ if (!expr_is_constant(i->left))
+ return expr_error(ctx, i->left,
+ "Key must be a constant");
+ if (!expr_is_singleton(i->left))
+ map->mappings->flags |= SET_F_INTERVAL;
+
+ expr_set_context(&ctx->ectx, ectx.dtype, ectx.len);
+ if (expr_evaluate(ctx, &i->right) < 0)
+ return -1;
+ if (!expr_is_constant(i->right))
+ return expr_error(ctx, i->right,
+ "Mapping must be a constant");
+ if (!expr_is_singleton(i->right))
+ return expr_error(ctx, i->right,
+ "Mapping must be a singleton");
+ }
+
+ map->dtype = ctx->ectx.dtype;
+ map->flags |= EXPR_F_CONSTANT;
+
+ /* Data for range lookups needs to be in big endian order */
+ if (map->mappings->flags & SET_F_INTERVAL &&
+ byteorder_conversion(ctx, &map->expr, BYTEORDER_BIG_ENDIAN) < 0)
+ return -1;
+ return 0;
+}
+
+/*
+ * Transfer the invertible binops to the constant side of an equality
+ * expression. A left shift is only invertible if the low n bits are
+ * zero.
+ */
+static int binop_can_transfer(struct eval_ctx *ctx,
+ struct expr *left, struct expr *right)
+{
+ switch (left->op) {
+ case OP_LSHIFT:
+ if (mpz_scan1(right->value, 0) < mpz_get_uint32(left->right->value))
+ return expr_binary_error(ctx, right, left,
+ "Comparison is always false");
+ return 1;
+ case OP_XOR:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static int binop_transfer_one(struct eval_ctx *ctx,
+ const struct expr *left, struct expr **right)
+{
+ expr_get(*right);
+
+ switch (left->op) {
+ case OP_LSHIFT:
+ (*right) = binop_expr_alloc(&(*right)->location, OP_RSHIFT,
+ *right, expr_get(left->right));
+ break;
+ case OP_XOR:
+ (*right) = binop_expr_alloc(&(*right)->location, OP_XOR,
+ *right, expr_get(left->right));
+ break;
+ default:
+ BUG();
+ }
+
+ return expr_evaluate(ctx, right);
+}
+
+static int binop_transfer(struct eval_ctx *ctx, struct expr **expr)
+{
+ struct expr *left = (*expr)->left, *i, *next;
+ int err;
+
+ if (left->ops->type != EXPR_BINOP)
+ return 0;
+
+ switch ((*expr)->right->ops->type) {
+ case EXPR_VALUE:
+ err = binop_can_transfer(ctx, left, (*expr)->right);
+ if (err <= 0)
+ return err;
+ if (binop_transfer_one(ctx, left, &(*expr)->right) < 0)
+ return -1;
+ break;
+ case EXPR_SET:
+ list_for_each_entry(i, &(*expr)->right->expressions, list) {
+ err = binop_can_transfer(ctx, left, i);
+ if (err <= 0)
+ return err;
+ }
+ list_for_each_entry_safe(i, next, &(*expr)->right->expressions,
+ list) {
+ list_del(&i->list);
+ if (binop_transfer_one(ctx, left, &i) < 0)
+ return -1;
+ list_add_tail(&i->list, &next->list);
+ }
+ break;
+ default:
+ return 0;
+ }
+
+ left = expr_get((*expr)->left->left);
+ left->dtype = (*expr)->left->dtype;
+ expr_free((*expr)->left);
+ (*expr)->left = left;
+ return 0;
+}
+
+static int expr_evaluate_relational(struct eval_ctx *ctx, struct expr **expr)
+{
+ struct expr *rel = *expr, *left, *right;
+
+ if (expr_evaluate(ctx, &rel->left) < 0)
+ return -1;
+ left = rel->left;
+
+ if (expr_evaluate(ctx, &rel->right) < 0)
+ return -1;
+ right = rel->right;
+
+ if (!expr_is_constant(right))
+ return expr_binary_error(ctx, right, rel,
+ "Right hand side of relational "
+ "expression (%s) must be constant",
+ expr_op_symbols[rel->op]);
+ if (expr_is_constant(left))
+ return expr_binary_error(ctx, left, right,
+ "Relational expression (%s) has "
+ "constant value",
+ expr_op_symbols[rel->op]);
+
+ switch (rel->op) {
+ case OP_LOOKUP:
+ /* Data for range lookups needs to be in big endian order */
+ if (right->flags & SET_F_INTERVAL &&
+ byteorder_conversion(ctx, &rel->left,
+ BYTEORDER_BIG_ENDIAN) < 0)
+ return -1;
+ left = rel->left;
+ break;
+ case OP_EQ:
+ /*
+ * Update payload context for payload and meta iiftype equality
+ * expressions.
+ */
+ switch (left->ops->type) {
+ case EXPR_PAYLOAD:
+ payload_ctx_update(&ctx->pctx, rel);
+ break;
+ case EXPR_META:
+ payload_ctx_update_meta(&ctx->pctx, rel);
+ break;
+ default:
+ break;
+ }
+ case OP_NEQ:
+ case OP_FLAGCMP:
+ switch (right->ops->type) {
+ case EXPR_RANGE:
+ goto range;
+ case EXPR_PREFIX:
+ if (byteorder_conversion(ctx, &right->expr, left->byteorder) < 0)
+ return -1;
+ break;
+ case EXPR_VALUE:
+ if (byteorder_conversion(ctx, &rel->right, left->byteorder) < 0)
+ return -1;
+ break;
+ default:
+ BUG();
+ }
+ break;
+ case OP_LT:
+ case OP_GT:
+ case OP_LTE:
+ case OP_GTE:
+ switch (left->ops->type) {
+ case EXPR_CONCAT:
+ return expr_binary_error(ctx, left, rel,
+ "Relational expression (%s) is undefined "
+ "for %s expressions",
+ expr_op_symbols[rel->op],
+ left->ops->name);
+ default:
+ break;
+ }
+
+ if (!expr_is_singleton(right))
+ return expr_binary_error(ctx, right, rel,
+ "Relational expression (%s) is undefined "
+ "for %s expressions",
+ expr_op_symbols[rel->op],
+ right->ops->name);
+
+ if (byteorder_conversion(ctx, &rel->left, BYTEORDER_BIG_ENDIAN) < 0)
+ return -1;
+ if (byteorder_conversion(ctx, &rel->right, BYTEORDER_BIG_ENDIAN) < 0)
+ return -1;
+ break;
+ case OP_RANGE:
+range:
+ switch (left->ops->type) {
+ case EXPR_CONCAT:
+ return expr_binary_error(ctx, left, rel,
+ "Relational expression (%s) is undefined"
+ "for %s expressions",
+ expr_op_symbols[rel->op],
+ left->ops->name);
+ default:
+ break;
+ }
+
+ if (byteorder_conversion(ctx, &rel->left, BYTEORDER_BIG_ENDIAN) < 0)
+ return -1;
+ if (byteorder_conversion(ctx, &right->left, BYTEORDER_BIG_ENDIAN) < 0)
+ return -1;
+ if (byteorder_conversion(ctx, &right->right, BYTEORDER_BIG_ENDIAN) < 0)
+ return -1;
+ break;
+ default:
+ BUG();
+ }
+
+ if (binop_transfer(ctx, expr) < 0)
+ return -1;
+
+ return 0;
+}
+
+static int expr_evaluate(struct eval_ctx *ctx, struct expr **expr)
+{
+#if TRACE
+ struct error_record *erec;
+ erec = erec_create(EREC_INFORMATIONAL, &(*expr)->location, "Evaluate");
+ erec_print(stdout, erec); expr_print(*expr); printf("\n\n");
+#endif
+
+ switch ((*expr)->ops->type) {
+ case EXPR_SYMBOL:
+ return expr_evaluate_symbol(ctx, expr);
+ case EXPR_VALUE:
+ return expr_evaluate_value(ctx, expr);
+ case EXPR_VERDICT:
+ case EXPR_EXTHDR:
+ case EXPR_META:
+ case EXPR_CT:
+ return expr_evaluate_primary(ctx, expr);
+ case EXPR_PAYLOAD:
+ return expr_evaluate_payload(ctx, expr);
+ case EXPR_PREFIX:
+ return expr_evaluate_prefix(ctx, expr);
+ case EXPR_RANGE:
+ return expr_evaluate_range(ctx, expr);
+ case EXPR_UNARY:
+ return expr_evaluate_unary(ctx, expr);
+ case EXPR_BINOP:
+ return expr_evaluate_binop(ctx, expr);
+ case EXPR_CONCAT:
+ return expr_evaluate_concat(ctx, expr);
+ case EXPR_LIST:
+ return expr_evaluate_list(ctx, expr);
+ case EXPR_SET:
+ return expr_evaluate_set(ctx, expr);
+ case EXPR_MAP:
+ return expr_evaluate_map(ctx, expr);
+ case EXPR_RELATIONAL:
+ return expr_evaluate_relational(ctx, expr);
+ default:
+ BUG();
+ }
+}
+
+static int stmt_evaluate_expr(struct eval_ctx *ctx, struct stmt *stmt)
+{
+ memset(&ctx->ectx, 0, sizeof(ctx->ectx));
+ return expr_evaluate(ctx, &stmt->expr);
+}
+
+static int stmt_evaluate_verdict(struct eval_ctx *ctx, struct stmt *stmt)
+{
+ if (expr_evaluate(ctx, &stmt->expr) < 0)
+ return -1;
+
+ switch (stmt->expr->ops->type) {
+ case EXPR_VERDICT:
+ if (stmt->expr->verdict != NFT_CONTINUE)
+ stmt->flags |= STMT_F_TERMINAL;
+ break;
+ case EXPR_MAP:
+ break;
+ default:
+ BUG();
+ }
+ return 0;
+}
+
+static int stmt_evaluate_meta(struct eval_ctx *ctx, struct stmt *stmt)
+{
+ expr_set_context(&ctx->ectx, stmt->meta.tmpl->dtype,
+ stmt->meta.tmpl->len);
+ if (expr_evaluate(ctx, &stmt->meta.expr) < 0)
+ return -1;
+ return 0;
+}
+
+static int stmt_evaluate_reject(struct eval_ctx *ctx, struct stmt *stmt)
+{
+ stmt->flags |= STMT_F_TERMINAL;
+ return 0;
+}
+
+static int stmt_evaluate_nat(struct eval_ctx *ctx, struct stmt *stmt)
+{
+ int err;
+
+ if (stmt->nat.addr != NULL) {
+ expr_set_context(&ctx->ectx, &ipaddr_type,
+ 4 * BITS_PER_BYTE);
+ err = expr_evaluate(ctx, &stmt->nat.addr);
+ if (err < 0)
+ return err;
+ }
+
+ if (stmt->nat.proto != NULL) {
+ expr_set_context(&ctx->ectx, &inet_service_type,
+ 2 * BITS_PER_BYTE);
+ err = expr_evaluate(ctx, &stmt->nat.proto);
+ if (err < 0)
+ return err;
+ }
+
+ stmt->flags |= STMT_F_TERMINAL;
+ return 0;
+}
+
+static int stmt_evaluate(struct eval_ctx *ctx, struct stmt *stmt)
+{
+#if TRACE
+ struct error_record *erec;
+ erec = erec_create(EREC_INFORMATIONAL, &stmt->location, "Evaluate");
+ erec_print(stdout, erec); stmt_print(stmt); printf("\n\n");
+#endif
+
+ switch (stmt->ops->type) {
+ case STMT_COUNTER:
+ case STMT_LIMIT:
+ case STMT_LOG:
+ return 0;
+ case STMT_EXPRESSION:
+ return stmt_evaluate_expr(ctx, stmt);
+ case STMT_VERDICT:
+ return stmt_evaluate_verdict(ctx, stmt);
+ case STMT_META:
+ return stmt_evaluate_meta(ctx, stmt);
+ case STMT_REJECT:
+ return stmt_evaluate_reject(ctx, stmt);
+ case STMT_NAT:
+ return stmt_evaluate_nat(ctx, stmt);
+ default:
+ BUG();
+ }
+}
+
+static int rule_evaluate(struct eval_ctx *ctx, struct rule *rule)
+{
+ struct stmt *stmt, *tstmt = NULL;
+
+ payload_ctx_init(&ctx->pctx, rule->handle.family);
+ memset(&ctx->ectx, 0, sizeof(ctx->ectx));
+
+ list_for_each_entry(stmt, &rule->stmts, list) {
+ if (tstmt != NULL)
+ return stmt_binary_error(ctx, stmt, tstmt,
+ "Statement after terminal "
+ "statement has no effect");
+
+ ctx->stmt = stmt;
+ if (stmt_evaluate(ctx, stmt) < 0)
+ return -1;
+ if (stmt->flags & STMT_F_TERMINAL)
+ tstmt = stmt;
+ }
+ return 0;
+}
+
+static int chain_evaluate(struct eval_ctx *ctx, struct chain *chain)
+{
+ struct rule *rule;
+
+ list_for_each_entry(rule, &chain->rules, list) {
+ handle_merge(&rule->handle, &chain->handle);
+ if (rule_evaluate(ctx, rule) < 0)
+ return -1;
+ }
+ return 0;
+}
+
+static int table_evaluate(struct eval_ctx *ctx, struct table *table)
+{
+ struct chain *chain;
+
+ list_for_each_entry(chain, &table->chains, list) {
+ handle_merge(&chain->handle, &table->handle);
+ if (chain_evaluate(ctx, chain) < 0)
+ return -1;
+ }
+ return 0;
+}
+
+static int cmd_evaluate_add(struct eval_ctx *ctx, struct cmd *cmd)
+{
+ switch (cmd->obj) {
+ case CMD_OBJ_RULE:
+ handle_merge(&cmd->rule->handle, &cmd->handle);
+ return rule_evaluate(ctx, cmd->rule);
+ case CMD_OBJ_CHAIN:
+ if (cmd->data == NULL)
+ return 0;
+ return chain_evaluate(ctx, cmd->chain);
+ case CMD_OBJ_TABLE:
+ if (cmd->data == NULL)
+ return 0;
+ return table_evaluate(ctx, cmd->table);
+ default:
+ BUG();
+ }
+}
+
+static int cmd_evaluate(struct eval_ctx *ctx, struct cmd *cmd)
+{
+#if TRACE
+ struct error_record *erec;
+ erec = erec_create(EREC_INFORMATIONAL, &cmd->location, "Evaluate");
+ erec_print(stdout, erec); printf("\n\n");
+#endif
+
+ switch (cmd->op) {
+ case CMD_ADD:
+ return cmd_evaluate_add(ctx, cmd);
+ case CMD_DELETE:
+ case CMD_LIST:
+ case CMD_FLUSH:
+ return 0;
+ default:
+ BUG();
+ };
+}
+
+int evaluate(struct eval_ctx *ctx, struct list_head *commands)
+{
+ struct cmd *cmd;
+
+ list_for_each_entry(cmd, commands, list) {
+ if (cmd_evaluate(ctx, cmd) < 0)
+ return -1;
+ }
+ return 0;
+}
diff --git a/src/expression.c b/src/expression.c
new file mode 100644
index 00000000..66a8793f
--- /dev/null
+++ b/src/expression.c
@@ -0,0 +1,622 @@
+/*
+ * Copyright (c) 2008 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 <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+
+#include <expression.h>
+#include <datatype.h>
+#include <rule.h>
+#include <gmputil.h>
+#include <utils.h>
+#include <list.h>
+#include <erec.h>
+
+struct expr *expr_alloc(const struct location *loc, const struct expr_ops *ops,
+ const struct datatype *dtype, enum byteorder byteorder,
+ unsigned int len)
+{
+ struct expr *expr;
+
+ expr = xzalloc(sizeof(*expr));
+ expr->location = *loc;
+ expr->ops = ops;
+ expr->dtype = dtype;
+ expr->byteorder = byteorder;
+ expr->len = len;
+ expr->refcnt = 1;
+ init_list_head(&expr->list);
+ return expr;
+}
+
+struct expr *expr_get(struct expr *expr)
+{
+ expr->refcnt++;
+ return expr;
+}
+
+void expr_free(struct expr *expr)
+{
+ if (expr == NULL)
+ return;
+ if (--expr->refcnt > 0)
+ return;
+ if (expr->ops->destroy)
+ expr->ops->destroy(expr);
+ xfree(expr);
+}
+
+void expr_print(const struct expr *expr)
+{
+ expr->ops->print(expr);
+}
+
+void expr_describe(const struct expr *expr)
+{
+ const struct datatype *dtype = expr->dtype;
+ const char *delim = "";
+
+ printf("%s expression, datatype %s", expr->ops->name, dtype->name);
+ if (dtype->basetype != NULL) {
+ printf(" (basetype ");
+ for (dtype = dtype->basetype; dtype != NULL;
+ dtype = dtype->basetype) {
+ printf("%s%s", delim, dtype->name);
+ delim = ", ";
+ }
+ printf(")");
+ }
+
+ if (expr_basetype(expr)->type == TYPE_STRING) {
+ if (expr->len)
+ printf(", %u characters", expr->len / BITS_PER_BYTE);
+ else
+ printf(", dynamic length");
+ } else
+ printf(", %u bits", expr->len);
+
+ printf("\n");
+
+ if (expr->dtype->sym_tbl != NULL) {
+ printf("\npre-defined symbolic constants:\n");
+ symbol_table_print(expr->dtype->sym_tbl);
+ }
+}
+
+void expr_set_type(struct expr *expr, const struct datatype *dtype,
+ enum byteorder byteorder)
+{
+ if (expr->ops->set_type)
+ expr->ops->set_type(expr, dtype, byteorder);
+ else {
+ expr->dtype = dtype;
+ expr->byteorder = byteorder;
+ }
+}
+
+const struct datatype *expr_basetype(const struct expr *expr)
+{
+ const struct datatype *type = expr->dtype;
+
+ while (type->basetype != NULL)
+ type = type->basetype;
+ return type;
+}
+
+int __fmtstring(4, 5) expr_binary_error(struct eval_ctx *ctx,
+ const struct expr *e1, const struct expr *e2,
+ const char *fmt, ...)
+{
+ struct error_record *erec;
+ va_list ap;
+
+ va_start(ap, fmt);
+ erec = erec_vcreate(EREC_ERROR, &e1->location, fmt, ap);
+ if (e2 != NULL)
+ erec_add_location(erec, &e2->location);
+ va_end(ap);
+ erec_queue(erec, ctx->msgs);
+ return -1;
+}
+
+static void verdict_expr_print(const struct expr *expr)
+{
+ datatype_print(expr);
+}
+
+static void verdict_expr_destroy(struct expr *expr)
+{
+ xfree(expr->chain);
+}
+
+static const struct expr_ops verdict_expr_ops = {
+ .type = EXPR_VERDICT,
+ .name = "verdict",
+ .print = verdict_expr_print,
+ .destroy = verdict_expr_destroy,
+};
+
+struct expr *verdict_expr_alloc(const struct location *loc,
+ int verdict, const char *chain)
+{
+ struct expr *expr;
+
+ expr = expr_alloc(loc, &verdict_expr_ops, &verdict_type,
+ BYTEORDER_INVALID, 0);
+ expr->verdict = verdict;
+ if (chain != NULL)
+ expr->chain = chain;
+ expr->flags = EXPR_F_CONSTANT | EXPR_F_SINGLETON;
+ return expr;
+}
+
+static void symbol_expr_print(const struct expr *expr)
+{
+ printf("%s", expr->identifier);
+}
+
+static void symbol_expr_destroy(struct expr *expr)
+{
+ xfree(expr->identifier);
+}
+
+static const struct expr_ops symbol_expr_ops = {
+ .type = EXPR_SYMBOL,
+ .name = "symbol",
+ .print = symbol_expr_print,
+ .destroy = symbol_expr_destroy,
+};
+
+struct expr *symbol_expr_alloc(const struct location *loc,
+ const char *identifier)
+{
+ struct expr *expr;
+
+ expr = expr_alloc(loc, &symbol_expr_ops, &invalid_type,
+ BYTEORDER_INVALID, 0);
+ expr->identifier = xstrdup(identifier);
+ return expr;
+}
+
+static void constant_expr_print(const struct expr *expr)
+{
+ datatype_print(expr);
+}
+
+static void constant_expr_destroy(struct expr *expr)
+{
+ mpz_clear(expr->value);
+}
+
+static const struct expr_ops constant_expr_ops = {
+ .type = EXPR_VALUE,
+ .name = "value",
+ .print = constant_expr_print,
+ .destroy = constant_expr_destroy,
+};
+
+struct expr *constant_expr_alloc(const struct location *loc,
+ const struct datatype *dtype,
+ enum byteorder byteorder,
+ unsigned int len, const void *data)
+{
+ struct expr *expr;
+
+ expr = expr_alloc(loc, &constant_expr_ops, dtype, byteorder, len);
+ expr->flags = EXPR_F_CONSTANT | EXPR_F_SINGLETON;
+
+ mpz_init2(expr->value, len);
+ if (data != NULL)
+ mpz_import_data(expr->value, data, byteorder,
+ div_round_up(len, BITS_PER_BYTE));
+
+ return expr;
+}
+
+struct expr *constant_expr_join(const struct expr *e1, const struct expr *e2)
+{
+ unsigned int len = (e1->len + e2->len) / BITS_PER_BYTE, tmp;
+ unsigned char data[len];
+
+ assert(e1->ops->type == EXPR_VALUE);
+ assert(e2->ops->type == EXPR_VALUE);
+
+ tmp = e1->len / BITS_PER_BYTE;
+ mpz_export_data(data, e1->value, e1->byteorder, tmp);
+ mpz_export_data(data + tmp, e2->value, e2->byteorder,
+ e2->len / BITS_PER_BYTE);
+
+ return constant_expr_alloc(&e1->location, &invalid_type,
+ BYTEORDER_INVALID, len * BITS_PER_BYTE,
+ data);
+}
+
+struct expr *constant_expr_splice(struct expr *expr, unsigned int len)
+{
+ struct expr *slice;
+ mpz_t mask;
+
+ assert(expr->ops->type == EXPR_VALUE);
+ assert(len <= expr->len);
+
+ slice = constant_expr_alloc(&expr->location, &invalid_type,
+ BYTEORDER_INVALID, len, NULL);
+ mpz_init2(mask, len);
+ mpz_bitmask(mask, len);
+ mpz_set(slice->value, expr->value);
+ mpz_and(slice->value, slice->value, mask);
+ mpz_clear(mask);
+
+ mpz_rshift_ui(expr->value, len);
+ expr->len -= len;
+ return slice;
+}
+
+static void prefix_expr_print(const struct expr *expr)
+{
+ expr_print(expr->expr);
+ printf("/%u", expr->prefix_len);
+}
+
+static void prefix_expr_set_type(const struct expr *expr,
+ const struct datatype *type,
+ enum byteorder byteorder)
+{
+ expr_set_type(expr->expr, type, byteorder);
+}
+
+static const struct expr_ops prefix_expr_ops = {
+ .type = EXPR_PREFIX,
+ .name = "prefix",
+ .print = prefix_expr_print,
+ .set_type = prefix_expr_set_type,
+};
+
+struct expr *prefix_expr_alloc(const struct location *loc,
+ struct expr *expr, unsigned int prefix_len)
+{
+ struct expr *prefix;
+
+ prefix = expr_alloc(loc, &prefix_expr_ops, &invalid_type,
+ BYTEORDER_INVALID, 0);
+ prefix->expr = expr;
+ prefix->prefix_len = prefix_len;
+ return prefix;
+}
+
+const char *expr_op_symbols[] = {
+ [OP_INVALID] = "invalid",
+ [OP_HTON] = "hton",
+ [OP_NTOH] = "ntoh",
+ [OP_AND] = "&",
+ [OP_OR] = "|",
+ [OP_XOR] = "^",
+ [OP_LSHIFT] = "<<",
+ [OP_RSHIFT] = ">>",
+ [OP_EQ] = NULL,
+ [OP_NEQ] = "!=",
+ [OP_LT] = "<",
+ [OP_GT] = ">",
+ [OP_LTE] = "<=",
+ [OP_GTE] = ">=",
+ [OP_RANGE] = "within range",
+ [OP_LOOKUP] = NULL,
+};
+
+static void unary_expr_print(const struct expr *expr)
+{
+ if (expr_op_symbols[expr->op] != NULL)
+ printf("%s(", expr_op_symbols[expr->op]);
+ expr_print(expr->arg);
+ printf(")");
+}
+
+static void unary_expr_destroy(struct expr *expr)
+{
+ expr_free(expr->arg);
+}
+
+static const struct expr_ops unary_expr_ops = {
+ .type = EXPR_UNARY,
+ .name = "unary",
+ .print = unary_expr_print,
+ .destroy = unary_expr_destroy,
+};
+
+struct expr *unary_expr_alloc(const struct location *loc,
+ enum ops op, struct expr *arg)
+{
+ struct expr *expr;
+
+ expr = expr_alloc(loc, &unary_expr_ops, &invalid_type,
+ BYTEORDER_INVALID, 0);
+ expr->op = op;
+ expr->arg = arg;
+ return expr;
+}
+
+static void binop_expr_print(const struct expr *expr)
+{
+ expr_print(expr->left);
+ if (expr_op_symbols[expr->op] != NULL)
+ printf(" %s ", expr_op_symbols[expr->op]);
+ else
+ printf(" ");
+ expr_print(expr->right);
+}
+
+static void binop_expr_destroy(struct expr *expr)
+{
+ expr_free(expr->left);
+ expr_free(expr->right);
+}
+
+static const struct expr_ops binop_expr_ops = {
+ .type = EXPR_BINOP,
+ .name = "binop",
+ .print = binop_expr_print,
+ .destroy = binop_expr_destroy,
+};
+
+struct expr *binop_expr_alloc(const struct location *loc, enum ops op,
+ struct expr *left, struct expr *right)
+{
+ struct expr *expr;
+
+ expr = expr_alloc(loc, &binop_expr_ops, &invalid_type,
+ BYTEORDER_INVALID, 0);
+ expr->left = left;
+ expr->op = op;
+ expr->right = right;
+ return expr;
+}
+
+static const struct expr_ops relational_expr_ops = {
+ .type = EXPR_RELATIONAL,
+ .name = "relational",
+ .print = binop_expr_print,
+ .destroy = binop_expr_destroy,
+};
+
+struct expr *relational_expr_alloc(const struct location *loc, enum ops op,
+ struct expr *left, struct expr *right)
+{
+ struct expr *expr;
+
+ expr = expr_alloc(loc, &relational_expr_ops, &verdict_type,
+ BYTEORDER_INVALID, 0);
+ expr->left = left;
+ expr->op = op;
+ expr->right = right;
+ return expr;
+}
+
+static void range_expr_print(const struct expr *expr)
+{
+ expr_print(expr->left);
+ printf("-");
+ expr_print(expr->right);
+}
+
+static void range_expr_destroy(struct expr *expr)
+{
+ expr_free(expr->left);
+ expr_free(expr->right);
+}
+
+static void range_expr_set_type(const struct expr *expr,
+ const struct datatype *type,
+ enum byteorder byteorder)
+{
+ expr_set_type(expr->left, type, byteorder);
+ expr_set_type(expr->right, type, byteorder);
+}
+
+static const struct expr_ops range_expr_ops = {
+ .type = EXPR_RANGE,
+ .name = "range",
+ .print = range_expr_print,
+ .destroy = range_expr_destroy,
+ .set_type = range_expr_set_type,
+};
+
+struct expr *range_expr_alloc(const struct location *loc,
+ struct expr *left, struct expr *right)
+{
+ struct expr *expr;
+
+ expr = expr_alloc(loc, &range_expr_ops, &invalid_type,
+ BYTEORDER_INVALID, 0);
+ expr->left = left;
+ expr->right = right;
+ return expr;
+}
+
+static struct expr *compound_expr_alloc(const struct location *loc,
+ const struct expr_ops *ops)
+{
+ struct expr *expr;
+
+ expr = expr_alloc(loc, ops, &invalid_type, BYTEORDER_INVALID, 0);
+ init_list_head(&expr->expressions);
+ return expr;
+}
+
+static void compound_expr_destroy(struct expr *expr)
+{
+ struct expr *i, *next;
+
+ list_for_each_entry_safe(i, next, &expr->expressions, list)
+ expr_free(i);
+}
+
+static void compound_expr_print(const struct expr *expr, const char *delim)
+{
+ const struct expr *i;
+ const char *d = "";
+
+ list_for_each_entry(i, &expr->expressions, list) {
+ printf("%s", d);
+ expr_print(i);
+ d = delim;
+ }
+}
+
+void compound_expr_add(struct expr *compound, struct expr *expr)
+{
+ list_add_tail(&expr->list, &compound->expressions);
+ compound->size++;
+}
+
+void compound_expr_remove(struct expr *compound, struct expr *expr)
+{
+ compound->size--;
+ list_del(&expr->list);
+}
+
+static void concat_expr_print(const struct expr *expr)
+{
+ compound_expr_print(expr, " . ");
+}
+
+static const struct expr_ops concat_expr_ops = {
+ .type = EXPR_CONCAT,
+ .name = "concat",
+ .print = concat_expr_print,
+ .destroy = compound_expr_destroy,
+};
+
+struct expr *concat_expr_alloc(const struct location *loc)
+{
+ return compound_expr_alloc(loc, &concat_expr_ops);
+}
+
+static void list_expr_print(const struct expr *expr)
+{
+ compound_expr_print(expr, ",");
+}
+
+static const struct expr_ops list_expr_ops = {
+ .type = EXPR_LIST,
+ .name = "list",
+ .print = list_expr_print,
+ .destroy = compound_expr_destroy,
+};
+
+struct expr *list_expr_alloc(const struct location *loc)
+{
+ return compound_expr_alloc(loc, &list_expr_ops);
+}
+
+static void set_expr_print(const struct expr *expr)
+{
+ printf("{ ");
+ compound_expr_print(expr, ", ");
+ printf("}");
+}
+
+static void set_expr_set_type(const struct expr *expr,
+ const struct datatype *dtype,
+ enum byteorder byteorder)
+{
+ struct expr *i;
+
+ list_for_each_entry(i, &expr->expressions, list)
+ expr_set_type(i, dtype, byteorder);
+}
+
+static const struct expr_ops set_expr_ops = {
+ .type = EXPR_SET,
+ .name = "set",
+ .print = set_expr_print,
+ .set_type = set_expr_set_type,
+ .destroy = compound_expr_destroy,
+};
+
+struct expr *set_expr_alloc(const struct location *loc)
+{
+ return compound_expr_alloc(loc, &set_expr_ops);
+}
+
+static void mapping_expr_print(const struct expr *expr)
+{
+ expr_print(expr->left);
+ printf(" => ");
+ expr_print(expr->right);
+}
+
+static void mapping_expr_set_type(const struct expr *expr,
+ const struct datatype *dtype,
+ enum byteorder byteorder)
+{
+ expr_set_type(expr->left, dtype, byteorder);
+}
+
+static void mapping_expr_destroy(struct expr *expr)
+{
+ expr_free(expr->left);
+ expr_free(expr->right);
+}
+
+static const struct expr_ops mapping_expr_ops = {
+ .type = EXPR_MAPPING,
+ .name = "mapping",
+ .print = mapping_expr_print,
+ .set_type = mapping_expr_set_type,
+ .destroy = mapping_expr_destroy,
+};
+
+struct expr *mapping_expr_alloc(const struct location *loc,
+ struct expr *from, struct expr *to)
+{
+ struct expr *expr;
+
+ expr = expr_alloc(loc, &mapping_expr_ops, from->dtype,
+ from->byteorder, 0);
+ expr->left = from;
+ expr->right = to;
+ return expr;
+}
+
+static void map_expr_print(const struct expr *expr)
+{
+ expr_print(expr->expr);
+ printf(" map ");
+ expr_print(expr->mappings);
+}
+
+static void map_expr_destroy(struct expr *expr)
+{
+ expr_free(expr->expr);
+ expr_free(expr->mappings);
+}
+
+static const struct expr_ops map_expr_ops = {
+ .type = EXPR_MAP,
+ .name = "map",
+ .print = map_expr_print,
+ .destroy = map_expr_destroy,
+};
+
+struct expr *map_expr_alloc(const struct location *loc, struct expr *arg,
+ struct expr *list)
+{
+ struct expr *expr;
+
+ assert(list->ops->type == EXPR_SET);
+ expr = expr_alloc(loc, &map_expr_ops, list->dtype,
+ list->byteorder, list->len);
+ expr->expr = arg;
+ expr->mappings = list;
+ return expr;
+}
diff --git a/src/exthdr.c b/src/exthdr.c
new file mode 100644
index 00000000..2defc7cb
--- /dev/null
+++ b/src/exthdr.c
@@ -0,0 +1,238 @@
+/*
+ * Exthdr expression protocol and type definitions and related functions.
+ *
+ * Copyright (c) 2008 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 <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <netinet/in.h>
+#include <netinet/ip6.h>
+
+#include <utils.h>
+#include <headers.h>
+#include <expression.h>
+
+static void exthdr_expr_print(const struct expr *expr)
+{
+ printf("%s %s", expr->exthdr.desc->name, expr->exthdr.tmpl->token);
+}
+
+static const struct expr_ops exthdr_expr_ops = {
+ .type = EXPR_EXTHDR,
+ .name = "exthdr",
+ .print = exthdr_expr_print,
+};
+
+static const struct payload_template exthdr_unknown_template =
+ PAYLOAD_TEMPLATE("unknown", &invalid_type, 0, 0);
+
+struct expr *exthdr_expr_alloc(const struct location *loc,
+ const struct exthdr_desc *desc,
+ uint8_t type)
+{
+ const struct payload_template *tmpl;
+ struct expr *expr;
+
+ if (desc != NULL)
+ tmpl = &desc->templates[type];
+ else
+ tmpl = &exthdr_unknown_template;
+
+ expr = expr_alloc(loc, &exthdr_expr_ops, tmpl->dtype,
+ BYTEORDER_BIG_ENDIAN, tmpl->len);
+ expr->exthdr.desc = desc;
+ expr->exthdr.tmpl = tmpl;
+ return expr;
+}
+
+static const struct exthdr_desc *exthdr_protocols[IPPROTO_MAX] = {
+ [IPPROTO_HOPOPTS] = &exthdr_hbh,
+ [IPPROTO_ROUTING] = &exthdr_rt,
+ [IPPROTO_FRAGMENT] = &exthdr_frag,
+ [IPPROTO_DSTOPTS] = &exthdr_dst,
+ [IPPROTO_MH] = &exthdr_mh,
+};
+
+void exthdr_init_raw(struct expr *expr, uint8_t type,
+ unsigned int offset, unsigned int len)
+{
+ const struct payload_template *tmpl;
+ unsigned int i;
+
+ assert(expr->ops->type == EXPR_EXTHDR);
+
+ expr->len = len;
+ expr->exthdr.desc = exthdr_protocols[type];
+ assert(expr->exthdr.desc != NULL);
+
+ for (i = 0; i < array_size(expr->exthdr.desc->templates); i++) {
+ tmpl = &expr->exthdr.desc->templates[i];
+ if (tmpl->offset != offset ||
+ tmpl->len != len)
+ continue;
+ expr->dtype = tmpl->dtype;
+ expr->exthdr.tmpl = tmpl;
+ return;
+ }
+}
+
+#define HDR_TEMPLATE(__name, __dtype, __type, __member) \
+ PAYLOAD_TEMPLATE(__name, __dtype, \
+ offsetof(__type, __member) * 8, \
+ field_sizeof(__type, __member) * 8)
+
+/*
+ * Hop-by-hop options
+ */
+
+#define HBH_FIELD(__name, __member, __dtype) \
+ HDR_TEMPLATE(__name, __dtype, struct ip6_hbh, __member)
+
+const struct exthdr_desc exthdr_hbh = {
+ .name = "hbh",
+ .type = IPPROTO_HOPOPTS,
+ .templates = {
+ [HBHHDR_NEXTHDR] = HBH_FIELD("nexthdr", ip6h_nxt, &inet_protocol_type),
+ [HBHHDR_HDRLENGTH] = HBH_FIELD("hdrlength", ip6h_len, &integer_type),
+ },
+};
+
+/*
+ * Routing header
+ */
+
+const struct exthdr_desc exthdr_rt2 = {
+ .templates = {
+ [RT2HDR_RESERVED] = {},
+ [RT2HDR_ADDR] = {},
+ },
+};
+
+#define RT0_FIELD(__name, __member, __dtype) \
+ HDR_TEMPLATE(__name, __dtype, struct ip6_rthdr0, __member)
+
+const struct exthdr_desc exthdr_rt0 = {
+ .templates = {
+ [RT0HDR_RESERVED] = RT0_FIELD("reserved", ip6r0_reserved, &integer_type),
+ [RT0HDR_ADDR_1] = RT0_FIELD("addr[1]", ip6r0_addr[0], &ip6addr_type),
+ [RT0HDR_ADDR_1 + 1] = RT0_FIELD("addr[2]", ip6r0_addr[0], &ip6addr_type),
+ // ...
+ },
+};
+
+#define RT_FIELD(__name, __member, __dtype) \
+ HDR_TEMPLATE(__name, __dtype, struct ip6_rthdr, __member)
+
+const struct exthdr_desc exthdr_rt = {
+ .name = "rt",
+ .type = IPPROTO_ROUTING,
+#if 0
+ .protocol_key = RTHDR_TYPE,
+ .protocols = {
+ [0] = &exthdr_rt0,
+ [2] = &exthdr_rt2,
+ },
+#endif
+ .templates = {
+ [RTHDR_NEXTHDR] = RT_FIELD("nexthdr", ip6r_nxt, &inet_protocol_type),
+ [RTHDR_HDRLENGTH] = RT_FIELD("hdrlength", ip6r_len, &integer_type),
+ [RTHDR_TYPE] = RT_FIELD("type", ip6r_type, &integer_type),
+ [RTHDR_SEG_LEFT] = RT_FIELD("seg-left", ip6r_segleft, &integer_type),
+ },
+};
+
+/*
+ * Fragment header
+ */
+
+#define FRAG_FIELD(__name, __member, __dtype) \
+ HDR_TEMPLATE(__name, __dtype, struct ip6_frag, __member)
+
+const struct exthdr_desc exthdr_frag = {
+ .name = "frag",
+ .type = IPPROTO_FRAGMENT,
+ .templates = {
+ [FRAGHDR_NEXTHDR] = FRAG_FIELD("nexthdr", ip6f_nxt, &inet_protocol_type),
+ [FRAGHDR_RESERVED] = FRAG_FIELD("reserved", ip6f_reserved, &integer_type),
+ [FRAGHDR_FRAG_OFF] = PAYLOAD_TEMPLATE("frag-off", &integer_type,
+ 16, 13),
+ [FRAGHDR_RESERVED2] = PAYLOAD_TEMPLATE("reserved2", &integer_type,
+ 29, 2),
+ [FRAGHDR_MFRAGS] = PAYLOAD_TEMPLATE("more-fragments", &integer_type,
+ 31, 1),
+ [FRAGHDR_ID] = FRAG_FIELD("id", ip6f_ident, &integer_type),
+ },
+};
+
+/*
+ * DST options
+ */
+
+#define DST_FIELD(__name, __member, __dtype) \
+ HDR_TEMPLATE(__name, __dtype, struct ip6_dest, __member)
+
+const struct exthdr_desc exthdr_dst = {
+ .name = "dst",
+ .type = IPPROTO_DSTOPTS,
+ .templates = {
+ [DSTHDR_NEXTHDR] = DST_FIELD("nexthdr", ip6d_nxt, &inet_protocol_type),
+ [DSTHDR_HDRLENGTH] = DST_FIELD("hdrlength", ip6d_len, &integer_type),
+ },
+};
+
+/*
+ * Mobility header
+ */
+
+#define MH_FIELD(__name, __member, __dtype) \
+ HDR_TEMPLATE(__name, __dtype, struct ip6_mh, __member)
+
+static const struct symbol_table mh_type_tbl = {
+ .byteorder = BYTEORDER_BIG_ENDIAN,
+ .size = BITS_PER_BYTE,
+ .symbols = {
+ SYMBOL("binding-refresh-request", IP6_MH_TYPE_BRR),
+ SYMBOL("home-test-init", IP6_MH_TYPE_HOTI),
+ SYMBOL("careof-test-init", IP6_MH_TYPE_COTI),
+ SYMBOL("home-test", IP6_MH_TYPE_HOT),
+ SYMBOL("careof-test", IP6_MH_TYPE_COT),
+ SYMBOL("binding-update", IP6_MH_TYPE_BU),
+ SYMBOL("binding-acknowledgement", IP6_MH_TYPE_BACK),
+ SYMBOL("binding-error", IP6_MH_TYPE_BERROR),
+ SYMBOL("fast-binding-update", IP6_MH_TYPE_FBU),
+ SYMBOL("fast-binding-acknowledgement", IP6_MH_TYPE_FBACK),
+ SYMBOL("fast-binding-advertisement", IP6_MH_TYPE_FNA),
+ SYMBOL("experimental-mobility-header", IP6_MH_TYPE_EMH),
+ SYMBOL("home-agent-switch-message", IP6_MH_TYPE_HASM),
+ SYMBOL_LIST_END
+ },
+};
+
+static const struct datatype mh_type_type = {
+ .type = TYPE_MH_TYPE,
+ .name = "Mobility Header Type",
+ .basetype = &integer_type,
+ .sym_tbl = &mh_type_tbl,
+};
+
+const struct exthdr_desc exthdr_mh = {
+ .name = "mh",
+ .type = IPPROTO_MH,
+ .templates = {
+ [MHHDR_NEXTHDR] = MH_FIELD("nexthdr", ip6mh_proto, &inet_protocol_type),
+ [MHHDR_HDRLENGTH] = MH_FIELD("hdrlength", ip6mh_hdrlen, &integer_type),
+ [MHHDR_TYPE] = MH_FIELD("type", ip6mh_type, &mh_type_type),
+ [MHHDR_RESERVED] = MH_FIELD("reserved", ip6mh_reserved, &integer_type),
+ [MHHDR_CHECKSUM] = MH_FIELD("checksum", ip6mh_cksum, &integer_type),
+ },
+};
diff --git a/src/gmputil.c b/src/gmputil.c
new file mode 100644
index 00000000..f34c077e
--- /dev/null
+++ b/src/gmputil.c
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2008 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 <stdlib.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <gmp.h>
+
+#include <nftables.h>
+#include <datatype.h>
+#include <gmputil.h>
+#include <utils.h>
+
+void mpz_bitmask(mpz_t rop, unsigned int width)
+{
+ mpz_set_ui(rop, 0);
+ mpz_setbit(rop, width);
+ mpz_sub_ui(rop, rop, 1);
+}
+
+void mpz_init_bitmask(mpz_t rop, unsigned int width)
+{
+ mpz_init2(rop, width);
+ mpz_bitmask(rop, width);
+}
+
+void mpz_prefixmask(mpz_t rop, unsigned int width, unsigned int prefix_len)
+{
+ mpz_bitmask(rop, prefix_len);
+ mpz_lshift_ui(rop, width - prefix_len);
+}
+
+void mpz_lshift_ui(mpz_t rop, unsigned int n)
+{
+ mpz_mul_2exp(rop, rop, n);
+}
+
+void mpz_rshift_ui(mpz_t rop, unsigned int n)
+{
+ mpz_tdiv_q_2exp(rop, rop, n);
+}
+
+#define mpz_get_type(type, endian, op) \
+({ \
+ type ret = 0; \
+ size_t cnt; \
+ mpz_export(&ret, &cnt, MPZ_LSWF, sizeof(ret), endian, 0, op); \
+ assert(cnt <= 1); \
+ ret; \
+ })
+
+uint64_t mpz_get_uint64(const mpz_t op)
+{
+ return mpz_get_type(uint64_t, MPZ_HOST_ENDIAN, op);
+}
+
+uint32_t mpz_get_uint32(const mpz_t op)
+{
+ return mpz_get_type(uint32_t, MPZ_HOST_ENDIAN, op);
+}
+
+uint16_t mpz_get_uint16(const mpz_t op)
+{
+ return mpz_get_type(uint16_t, MPZ_HOST_ENDIAN, op);
+}
+
+uint8_t mpz_get_uint8(const mpz_t op)
+{
+ return mpz_get_type(uint8_t, MPZ_HOST_ENDIAN, op);
+}
+
+uint64_t mpz_get_be64(const mpz_t op)
+{
+ return mpz_get_type(uint64_t, MPZ_BIG_ENDIAN, op);
+}
+
+uint32_t mpz_get_be32(const mpz_t op)
+{
+ return mpz_get_type(uint32_t, MPZ_BIG_ENDIAN, op);
+}
+
+uint16_t mpz_get_be16(const mpz_t op)
+{
+ return mpz_get_type(uint16_t, MPZ_BIG_ENDIAN, op);
+}
+
+void *mpz_export_data(void *data, const mpz_t op,
+ enum byteorder byteorder,
+ unsigned int len)
+{
+ enum mpz_byte_order endian;
+
+ switch (byteorder) {
+ case BYTEORDER_BIG_ENDIAN:
+ default:
+ endian = MPZ_BIG_ENDIAN;
+ break;
+ case BYTEORDER_HOST_ENDIAN:
+ endian = MPZ_HOST_ENDIAN;
+ break;
+ }
+
+ memset(data, 0, len);
+ mpz_export(data, NULL, MPZ_MSWF, len, endian, 0, op);
+ return data;
+}
+
+void mpz_import_data(mpz_t rop, const void *data,
+ enum byteorder byteorder,
+ unsigned int len)
+{
+ enum mpz_word_order order;
+ enum mpz_byte_order endian;
+
+ switch (byteorder) {
+ case BYTEORDER_BIG_ENDIAN:
+ default:
+ order = MPZ_MSWF;
+ endian = MPZ_BIG_ENDIAN;
+ break;
+ case BYTEORDER_HOST_ENDIAN:
+ order = MPZ_LSWF;
+ endian = MPZ_HOST_ENDIAN;
+ break;
+ }
+
+ mpz_import(rop, len, order, 1, endian, 0, data);
+}
+
+void mpz_switch_byteorder(mpz_t rop, unsigned int len)
+{
+ char data[len];
+
+ mpz_export_data(data, rop, BYTEORDER_BIG_ENDIAN, len);
+ mpz_import_data(rop, data, BYTEORDER_HOST_ENDIAN, len);
+}
+
+static void *gmp_xrealloc(void *ptr, size_t old_size, size_t new_size)
+{
+ return xrealloc(ptr, new_size);
+}
+
+static void __init gmp_init(void)
+{
+ mp_set_memory_functions(xmalloc, gmp_xrealloc, NULL);
+}
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 00000000..f7686ae3
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,202 @@
+/*
+ * Copyright (c) 2008 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 <stdlib.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <getopt.h>
+#include <fcntl.h>
+#include <sys/types.h>
+
+#include <nftables.h>
+#include <utils.h>
+#include <parser.h>
+#include <rule.h>
+#include <netlink.h>
+#include <erec.h>
+
+unsigned int numeric_output;
+
+const char *include_paths[INCLUDE_PATHS_MAX] = { DEFAULT_INCLUDE_PATH };
+static unsigned int num_include_paths = 1;
+
+enum opt_vals {
+ OPT_HELP = 'h',
+ OPT_VERSION = 'v',
+ OPT_FILE = 'f',
+ OPT_INTERACTIVE = 'i',
+ OPT_INCLUDEPATH = 'I',
+ OPT_NUMERIC = 'n',
+ OPT_INVALID = '?',
+};
+
+#define OPTSTRING "hvf:iI:vn"
+
+static const struct option options[] = {
+ {
+ .name = "help",
+ .val = OPT_HELP,
+ },
+ {
+ .name = "version",
+ .val = OPT_VERSION,
+ },
+ {
+ .name = "file",
+ .val = OPT_FILE,
+ .has_arg = 1,
+ },
+ {
+ .name = "interactive",
+ .val = OPT_INTERACTIVE,
+ },
+ {
+ .name = "numeric",
+ .val = OPT_NUMERIC,
+ },
+ {
+ .name = "includepath",
+ .val = OPT_INCLUDEPATH,
+ .has_arg = 1,
+ },
+ {
+ .name = NULL
+ }
+};
+
+static void show_help(const char *name)
+{
+ printf(
+"Usage: %s [ options ] [ cmds... ]\n"
+"\n"
+"Options:\n"
+" -h/--help Show this help\n"
+" -v/--version Show version information\n"
+"\n"
+" -f/--file <filename> Read input from <filename>\n"
+" -i/--interactive Read input from interactive CLI\n"
+"\n"
+" -n/--numeric When specified once, show network addresses numerically.\n"
+" When specified twice, also show Internet protocols,\n"
+" Internet services, user IDs and group IDs numerically.\n"
+" -i/--includepath <directory> Add <directory> to the paths searched for include files.\n"
+"\n",
+ name);
+}
+
+static const struct input_descriptor indesc_cmdline = {
+ .type = INDESC_BUFFER,
+ .name = "<cmdline>",
+};
+
+int main(int argc, char * const *argv)
+{
+ struct parser_state state;
+ struct eval_ctx ctx;
+ void *scanner;
+ LIST_HEAD(msgs);
+ char *buf = NULL, *filename = NULL;
+ unsigned int len;
+ bool interactive = false;
+ int i, val;
+ int ret;
+
+ while (1) {
+ val = getopt_long(argc, argv, OPTSTRING, options, NULL);
+ if (val == -1)
+ break;
+
+ switch (val) {
+ case OPT_HELP:
+ show_help(argv[0]);
+ exit(NFT_EXIT_SUCCESS);
+ case OPT_VERSION:
+ printf("%s v%s (%s)\n",
+ PACKAGE_NAME, PACKAGE_VERSION, RELEASE_NAME);
+ exit(NFT_EXIT_SUCCESS);
+ case OPT_FILE:
+ filename = optarg;
+ break;
+ case OPT_INTERACTIVE:
+ interactive = true;
+ break;
+ case OPT_INCLUDEPATH:
+ if (num_include_paths >= INCLUDE_PATHS_MAX) {
+ fprintf(stderr, "Too many include paths "
+ "specified, max. %u\n",
+ INCLUDE_PATHS_MAX - 1);
+ exit(NFT_EXIT_FAILURE);
+ }
+ include_paths[num_include_paths++] = optarg;
+ break;
+ case OPT_NUMERIC:
+ numeric_output++;
+ break;
+ case OPT_INVALID:
+ exit(NFT_EXIT_FAILURE);
+ }
+ }
+
+ parser_init(&state, &msgs);
+ scanner = scanner_init(&state);
+
+ if (optind != argc) {
+ for (len = 0, i = optind; i < argc; i++)
+ len += strlen(argv[i]) + strlen(" ");
+
+ buf = xzalloc(len + 1);
+ for (i = optind; i < argc; i++) {
+ strcat(buf, argv[i]);
+ if (i + 1 < argc)
+ strcat(buf, " ");
+ }
+
+ scanner_push_buffer(scanner, &indesc_cmdline, buf);
+ } else if (filename != NULL) {
+ if (scanner_read_file(scanner, filename, &internal_location) < 0)
+ goto out;
+ } else if (interactive) {
+ cli_init(scanner, &state);
+ } else {
+ fprintf(stderr, "%s: no command specified\n", argv[0]);
+ exit(NFT_EXIT_FAILURE);
+ }
+
+ ret = nft_parse(scanner, &state);
+ if (ret < 0)
+ goto out;
+
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.msgs = &msgs;
+ if (evaluate(&ctx, &state.cmds) < 0)
+ goto out;
+
+ {
+ struct netlink_ctx ctx;
+ struct cmd *cmd;
+
+ list_for_each_entry(cmd, &state.cmds, list) {
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.msgs = &msgs;
+ init_list_head(&ctx.list);
+ if (do_command(&ctx, cmd) < 0)
+ goto out;
+ }
+ }
+out:
+ scanner_destroy(scanner);
+ erec_print_list(stdout, &msgs);
+
+ xfree(buf);
+ return 0;
+}
diff --git a/src/meta.c b/src/meta.c
new file mode 100644
index 00000000..cb9e495c
--- /dev/null
+++ b/src/meta.c
@@ -0,0 +1,347 @@
+/*
+ * Meta expression/statement related definition and types.
+ *
+ * Copyright (c) 2008 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 <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <net/if_arp.h>
+#include <pwd.h>
+#include <grp.h>
+#include <netlink/route/link.h>
+#include <netlink/route/tc.h>
+
+#include <nftables.h>
+#include <expression.h>
+#include <statement.h>
+#include <datatype.h>
+#include <meta.h>
+#include <gmputil.h>
+#include <utils.h>
+#include <erec.h>
+
+static struct symbol_table *realm_tbl;
+static void __init realm_table_init(void)
+{
+ realm_tbl = rt_symbol_table_init("/etc/iproute2/rt_realms");
+}
+
+static void __exit realm_table_exit(void)
+{
+ rt_symbol_table_free(realm_tbl);
+}
+
+static void realm_type_print(const struct expr *expr)
+{
+ return symbolic_constant_print(realm_tbl, expr);
+}
+
+static struct error_record *realm_type_parse(const struct expr *sym,
+ struct expr **res)
+{
+ return symbolic_constant_parse(sym, realm_tbl, res);
+}
+
+static const struct datatype realm_type = {
+ .type = TYPE_REALM,
+ .name = "routing realm",
+ .basetype = &integer_type,
+ .print = realm_type_print,
+ .parse = realm_type_parse,
+};
+
+static void tchandle_type_print(const struct expr *expr)
+{
+ char buf[sizeof("ffff:ffff")];
+
+ printf("%s", rtnl_tc_handle2str(mpz_get_uint32(expr->value),
+ buf, sizeof(buf)));
+}
+
+static struct error_record *tchandle_type_parse(const struct expr *sym,
+ struct expr **res)
+{
+ uint32_t handle;
+
+ if (rtnl_tc_str2handle(sym->identifier, &handle) < 0)
+ return error(&sym->location, "Could not parse %s",
+ sym->sym_type->name);
+
+ *res = constant_expr_alloc(&sym->location, sym->sym_type,
+ BYTEORDER_HOST_ENDIAN,
+ sizeof(handle) * BITS_PER_BYTE, &handle);
+ return NULL;
+}
+
+static const struct datatype tchandle_type = {
+ .type = TYPE_TC_HANDLE,
+ .name = "TC handle",
+ .basetype = &integer_type,
+ .print = tchandle_type_print,
+ .parse = tchandle_type_parse,
+};
+
+static struct nl_cache *link_cache;
+
+static int link_cache_init(void)
+{
+ struct nl_sock *rt_sock;
+ int err;
+
+ rt_sock = nl_socket_alloc();
+ if (rt_sock == NULL)
+ memory_allocation_error();
+
+ err = nl_connect(rt_sock, NETLINK_ROUTE);
+ if (err < 0)
+ goto err;
+ err = rtnl_link_alloc_cache(rt_sock, &link_cache);
+ if (err < 0)
+ goto err;
+ nl_cache_mngt_provide(link_cache);
+ nl_socket_free(rt_sock);
+ return 0;
+
+err:
+ nl_socket_free(rt_sock);
+ return err;
+}
+
+static void ifindex_type_print(const struct expr *expr)
+{
+ char name[IFNAMSIZ];
+ int ifindex;
+
+ if (link_cache == NULL)
+ link_cache_init();
+
+ ifindex = mpz_get_uint32(expr->value);
+ if (link_cache != NULL &&
+ rtnl_link_i2name(link_cache, ifindex, name, sizeof(name)))
+ printf("%s", name);
+ else
+ printf("%d", ifindex);
+}
+
+static struct error_record *ifindex_type_parse(const struct expr *sym,
+ struct expr **res)
+{
+ int ifindex, err;
+
+ if (link_cache == NULL &&
+ (err = link_cache_init()) < 0)
+ return error(&sym->location,
+ "Could not initialize link cache: %s",
+ nl_geterror(err));
+
+ ifindex = rtnl_link_name2i(link_cache, sym->identifier);
+ if (ifindex == 0)
+ return error(&sym->location, "Interface does not exist");
+
+ *res = constant_expr_alloc(&sym->location, sym->sym_type,
+ BYTEORDER_HOST_ENDIAN,
+ sizeof(ifindex) * BITS_PER_BYTE, &ifindex);
+ return NULL;
+}
+
+static void __exit ifindex_table_free(void)
+{
+ nl_cache_free(link_cache);
+}
+
+static const struct datatype ifindex_type = {
+ .type = TYPE_IFINDEX,
+ .name = "interface index",
+ .basetype = &integer_type,
+ .print = ifindex_type_print,
+ .parse = ifindex_type_parse,
+};
+
+static const struct symbol_table arphrd_tbl = {
+ .byteorder = BYTEORDER_HOST_ENDIAN,
+ .size = 2 * BITS_PER_BYTE,
+ .symbols = {
+ SYMBOL("ether", ARPHRD_ETHER),
+ SYMBOL("ppp", ARPHRD_PPP),
+ /* dummy types */
+ SYMBOL("ipip", ARPHRD_TUNNEL),
+ SYMBOL("ipip6", ARPHRD_TUNNEL6),
+ SYMBOL("loopback", ARPHRD_LOOPBACK),
+ SYMBOL("sit", ARPHRD_SIT),
+ SYMBOL("ipgre", ARPHRD_IPGRE),
+ SYMBOL_LIST_END,
+ },
+};
+
+const struct datatype arphrd_type = {
+ .type = TYPE_ARPHRD,
+ .name = "hardware type",
+ .basetype = &integer_type,
+ .sym_tbl = &arphrd_tbl,
+};
+
+static void uid_type_print(const struct expr *expr)
+{
+ struct passwd *pw;
+
+ if (numeric_output < NUMERIC_ALL) {
+ pw = getpwuid(mpz_get_uint32(expr->value));
+ if (pw != NULL) {
+ printf("%s", pw->pw_name);
+ return;
+ }
+ }
+ expr_basetype(expr)->print(expr);
+}
+
+static struct error_record *uid_type_parse(const struct expr *sym,
+ struct expr **res)
+{
+ struct passwd *pw;
+
+ pw = getpwnam(sym->identifier);
+ if (pw == NULL)
+ return error(&sym->location, "User does not exist");
+
+ *res = constant_expr_alloc(&sym->location, sym->sym_type,
+ BYTEORDER_HOST_ENDIAN,
+ sizeof(pw->pw_uid) * BITS_PER_BYTE,
+ &pw->pw_uid);
+ return NULL;
+}
+
+static const struct datatype uid_type = {
+ .type = TYPE_UID,
+ .name = "user ID",
+ .basetype = &integer_type,
+ .print = uid_type_print,
+ .parse = uid_type_parse,
+};
+
+static void gid_type_print(const struct expr *expr)
+{
+ struct group *gr;
+
+ if (numeric_output < NUMERIC_ALL) {
+ gr = getgrgid(mpz_get_uint32(expr->value));
+ if (gr != NULL) {
+ printf("%s", gr->gr_name);
+ return;
+ }
+ }
+ expr_basetype(expr)->print(expr);
+}
+
+static struct error_record *gid_type_parse(const struct expr *sym,
+ struct expr **res)
+{
+ struct group *gr;
+
+ gr = getgrnam(sym->identifier);
+ if (gr == NULL)
+ return error(&sym->location, "Group does not exist");
+
+ *res = constant_expr_alloc(&sym->location, sym->sym_type,
+ BYTEORDER_HOST_ENDIAN,
+ sizeof(gr->gr_gid) * BITS_PER_BYTE,
+ &gr->gr_gid);
+ return NULL;
+}
+
+static const struct datatype gid_type = {
+ .type = TYPE_GID,
+ .name = "group ID",
+ .basetype = &integer_type,
+ .print = gid_type_print,
+ .parse = gid_type_parse,
+};
+
+static const struct meta_template meta_templates[] = {
+ [NFT_META_LEN] = META_TEMPLATE("len", &integer_type,
+ 4 * 8, BYTEORDER_HOST_ENDIAN),
+ [NFT_META_PROTOCOL] = META_TEMPLATE("protocol", &ethertype_type,
+ 2 * 8, BYTEORDER_BIG_ENDIAN),
+ [NFT_META_PRIORITY] = META_TEMPLATE("priority", &tchandle_type,
+ 4 * 8, BYTEORDER_HOST_ENDIAN),
+ [NFT_META_MARK] = META_TEMPLATE("mark", &mark_type,
+ 4 * 8, BYTEORDER_HOST_ENDIAN),
+ [NFT_META_IIF] = META_TEMPLATE("iif", &ifindex_type,
+ 4 * 8, BYTEORDER_HOST_ENDIAN),
+ [NFT_META_IIFNAME] = META_TEMPLATE("iifname", &string_type,
+ IFNAMSIZ * BITS_PER_BYTE,
+ BYTEORDER_INVALID),
+ [NFT_META_IIFTYPE] = META_TEMPLATE("iiftype", &arphrd_type,
+ 2 * 8, BYTEORDER_HOST_ENDIAN),
+ [NFT_META_OIF] = META_TEMPLATE("oif", &ifindex_type,
+ 4 * 8, BYTEORDER_HOST_ENDIAN),
+ [NFT_META_OIFNAME] = META_TEMPLATE("oifname", &string_type,
+ IFNAMSIZ * BITS_PER_BYTE,
+ BYTEORDER_INVALID),
+ [NFT_META_OIFTYPE] = META_TEMPLATE("oiftype", &arphrd_type,
+ 2 * 8, BYTEORDER_HOST_ENDIAN),
+ [NFT_META_SKUID] = META_TEMPLATE("skuid", &uid_type,
+ 4 * 8, BYTEORDER_HOST_ENDIAN),
+ [NFT_META_SKGID] = META_TEMPLATE("skgid", &gid_type,
+ 4 * 8, BYTEORDER_HOST_ENDIAN),
+ [NFT_META_NFTRACE] = META_TEMPLATE("nftrace", &integer_type,
+ 1 , BYTEORDER_HOST_ENDIAN),
+ [NFT_META_RTCLASSID] = META_TEMPLATE("rtclassid", &realm_type,
+ 4 * 8, BYTEORDER_HOST_ENDIAN),
+ [NFT_META_SECMARK] = META_TEMPLATE("secmark", &integer_type,
+ 4 * 8, BYTEORDER_HOST_ENDIAN),
+};
+
+static void meta_expr_print(const struct expr *expr)
+{
+ printf("meta %s", meta_templates[expr->meta.key].token);
+}
+
+static const struct expr_ops meta_expr_ops = {
+ .type = EXPR_META,
+ .name = "meta",
+ .print = meta_expr_print,
+};
+
+struct expr *meta_expr_alloc(const struct location *loc, enum nft_meta_keys key)
+{
+ const struct meta_template *tmpl = &meta_templates[key];
+ struct expr *expr;
+
+ expr = expr_alloc(loc, &meta_expr_ops, tmpl->dtype,
+ tmpl->byteorder, tmpl->len);
+ expr->meta.key = key;
+ return expr;
+}
+
+static void meta_stmt_print(const struct stmt *stmt)
+{
+ printf("meta %s set ", meta_templates[stmt->meta.key].token);
+ expr_print(stmt->meta.expr);
+}
+
+static const struct stmt_ops meta_stmt_ops = {
+ .type = STMT_META,
+ .name = "meta",
+ .print = meta_stmt_print,
+};
+
+struct stmt *meta_stmt_alloc(const struct location *loc, enum nft_meta_keys key,
+ struct expr *expr)
+{
+ struct stmt *stmt;
+
+ stmt = stmt_alloc(loc, &meta_stmt_ops);
+ stmt->meta.key = key;
+ stmt->meta.tmpl = &meta_templates[key];
+ stmt->meta.expr = expr;
+ return stmt;
+}
diff --git a/src/netlink.c b/src/netlink.c
new file mode 100644
index 00000000..4700cd7e
--- /dev/null
+++ b/src/netlink.c
@@ -0,0 +1,476 @@
+/*
+ * Copyright (c) 2008 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 <netlink/netfilter/nfnl.h>
+#include <netlink/netfilter/netfilter.h>
+#include <netlink/netfilter/nft_table.h>
+#include <netlink/netfilter/nft_chain.h>
+#include <netlink/netfilter/nft_rule.h>
+#include <netlink/netfilter/nft_expr.h>
+#include <netlink/netfilter/nft_data.h>
+#include <linux/netfilter/nf_tables.h>
+
+#include <nftables.h>
+#include <netlink.h>
+#include <expression.h>
+#include <utils.h>
+#include <erec.h>
+
+#define TRACE 0
+
+static struct nl_sock *nf_sock;
+
+static void __init netlink_open_sock(void)
+{
+ // FIXME: should be done dynamically by nft_set and based on set members
+ nlmsg_set_default_size(65536);
+ nf_sock = nl_socket_alloc();
+ nfnl_connect(nf_sock);
+}
+
+static void __exit netlink_close_sock(void)
+{
+ nl_socket_free(nf_sock);
+}
+
+void netlink_dump_object(struct nl_object *obj)
+{
+ struct nl_dump_params params = {
+ .dp_fd = stdout,
+ .dp_type = NL_DUMP_DETAILS,
+ };
+ nl_object_dump(obj, &params);
+}
+
+static int netlink_io_error(struct netlink_ctx *ctx, const struct location *loc,
+ const char *fmt, ...)
+{
+ struct error_record *erec;
+ va_list ap;
+
+ if (loc == NULL)
+ loc = &internal_location;
+
+ va_start(ap, fmt);
+ erec = erec_vcreate(EREC_ERROR, loc, fmt, ap);
+ va_end(ap);
+ erec_queue(erec, ctx->msgs);
+ return -1;
+}
+
+struct nfnl_nft_table *alloc_nft_table(const struct handle *h)
+{
+ struct nfnl_nft_table *nlt;
+
+ nlt = nfnl_nft_table_alloc();
+ if (nlt == NULL)
+ memory_allocation_error();
+ nfnl_nft_table_set_family(nlt, h->family);
+ nfnl_nft_table_set_name(nlt, h->table, strlen(h->table) + 1);
+ return nlt;
+}
+
+struct nfnl_nft_chain *alloc_nft_chain(const struct handle *h)
+{
+ struct nfnl_nft_chain *nlc;
+
+ nlc = nfnl_nft_chain_alloc();
+ if (nlc == NULL)
+ memory_allocation_error();
+ nfnl_nft_chain_set_family(nlc, h->family);
+ nfnl_nft_chain_set_table(nlc, h->table, strlen(h->table) + 1);
+ if (h->chain != NULL)
+ nfnl_nft_chain_set_name(nlc, h->chain, strlen(h->chain) + 1);
+ return nlc;
+}
+
+struct nfnl_nft_rule *alloc_nft_rule(const struct handle *h)
+{
+ struct nfnl_nft_rule *nlr;
+
+ nlr = nfnl_nft_rule_alloc();
+ if (nlr == NULL)
+ memory_allocation_error();
+ nfnl_nft_rule_set_family(nlr, h->family);
+ nfnl_nft_rule_set_table(nlr, h->table, strlen(h->table) + 1);
+ if (h->chain != NULL)
+ nfnl_nft_rule_set_chain(nlr, h->chain, strlen(h->chain) + 1);
+ if (h->handle)
+ nfnl_nft_rule_set_handle(nlr, h->handle);
+ return nlr;
+}
+
+struct nfnl_nft_expr *alloc_nft_expr(int (*init)(struct nfnl_nft_expr *))
+{
+ struct nfnl_nft_expr *nle;
+
+ nle = nfnl_nft_expr_alloc();
+ if (nle == NULL || init(nle) != 0)
+ memory_allocation_error();
+ return nle;
+}
+
+struct nfnl_nft_data *alloc_nft_data(const void *data, unsigned int len)
+{
+ struct nfnl_nft_data *nfd;
+
+ assert(len > 0);
+ nfd = nfnl_nft_data_alloc(data, len);
+ if (nfd == NULL)
+ memory_allocation_error();
+ return nfd;
+}
+
+int netlink_add_rule(struct netlink_ctx *ctx, const struct handle *h,
+ const struct rule *rule)
+{
+ struct nfnl_nft_rule *nlr;
+ int err;
+
+ nlr = alloc_nft_rule(&rule->handle);
+ err = netlink_linearize_rule(ctx, nlr, rule);
+ if (err == 0) {
+ err = nfnl_nft_rule_add(nf_sock, nlr, NLM_F_EXCL | NLM_F_APPEND);
+ if (err < 0)
+ netlink_io_error(ctx, &rule->location,
+ "Could not add rule: %s", nl_geterror(err));
+ }
+ nfnl_nft_rule_put(nlr);
+ return err;
+}
+
+int netlink_delete_rule(struct netlink_ctx *ctx, const struct handle *h)
+{
+ struct nfnl_nft_rule *nlr;
+ int err;
+
+ nlr = alloc_nft_rule(h);
+ err = nfnl_nft_rule_delete(nf_sock, nlr, 0);
+ nfnl_nft_rule_put(nlr);
+
+ if (err < 0)
+ netlink_io_error(ctx, NULL, "Could not delete rule: %s",
+ nl_geterror(err));
+ return err;
+}
+
+static void list_rule_cb(struct nl_object *obj, void *arg)
+{
+ const struct nfnl_nft_rule *nlr = (struct nfnl_nft_rule *)obj;
+ struct netlink_ctx *ctx = arg;
+ struct rule *rule;
+#if TRACE
+ printf("\n"); netlink_dump_object(obj); printf("\n");
+#endif
+ if (!nfnl_nft_rule_test_family(nlr) ||
+ !nfnl_nft_rule_test_table(nlr) ||
+ !nfnl_nft_rule_test_chain(nlr) ||
+ !nfnl_nft_rule_test_handle(nlr)) {
+ netlink_io_error(ctx, NULL, "Incomplete rule received");
+ return;
+ }
+
+ rule = netlink_delinearize_rule(ctx, obj);
+ list_add_tail(&rule->list, &ctx->list);
+}
+
+static int netlink_list_rules(struct netlink_ctx *ctx, const struct handle *h)
+{
+ struct nl_cache *rule_cache;
+ struct nfnl_nft_rule *nlr;
+ int err;
+
+ err = nfnl_nft_rule_alloc_cache(nf_sock, &rule_cache);
+ if (err < 0)
+ return netlink_io_error(ctx, NULL,
+ "Could not receive rules from kernel: %s",
+ nl_geterror(err));
+
+ nlr = alloc_nft_rule(h);
+ nl_cache_foreach_filter(rule_cache, (struct nl_object *)nlr,
+ list_rule_cb, ctx);
+ nfnl_nft_rule_put(nlr);
+ nl_cache_free(rule_cache);
+ return 0;
+}
+
+static int netlink_get_rule_cb(struct nl_msg *msg, void *arg)
+{
+ return nl_msg_parse(msg, list_rule_cb, arg);
+}
+
+int netlink_get_rule(struct netlink_ctx *ctx, const struct handle *h)
+{
+ struct nfnl_nft_rule *nlr;
+ int err;
+
+ nlr = alloc_nft_rule(h);
+ nfnl_nft_rule_query(nf_sock, nlr, 0);
+ nl_socket_modify_cb(nf_sock, NL_CB_VALID, NL_CB_CUSTOM,
+ netlink_get_rule_cb, ctx);
+ err = nl_recvmsgs_default(nf_sock);
+ nfnl_nft_rule_put(nlr);
+
+ if (err < 0)
+ return netlink_io_error(ctx, NULL,
+ "Could not receive rule from kernel: %s",
+ nl_geterror(err));
+ return err;
+}
+
+static void flush_rule_cb(struct nl_object *obj, void *arg)
+{
+ struct netlink_ctx *ctx = arg;
+ int err;
+
+ netlink_dump_object(obj);
+ err = nfnl_nft_rule_delete(nf_sock, (struct nfnl_nft_rule *)obj, 0);
+ if (err < 0)
+ netlink_io_error(ctx, NULL, "Could not delete rule: %s",
+ nl_geterror(err));
+}
+
+static int netlink_flush_rules(struct netlink_ctx *ctx, const struct handle *h)
+{
+ struct nl_cache *rule_cache;
+ struct nfnl_nft_rule *nlr;
+ int err;
+
+ err = nfnl_nft_rule_alloc_cache(nf_sock, &rule_cache);
+ if (err < 0)
+ return netlink_io_error(ctx, NULL,
+ "Could not receive rules from kernel: %s",
+ nl_geterror(err));
+
+ nlr = alloc_nft_rule(h);
+ nl_cache_foreach_filter(rule_cache, (struct nl_object *)nlr,
+ flush_rule_cb, ctx);
+ nfnl_nft_rule_put(nlr);
+ nl_cache_free(rule_cache);
+ return 0;
+}
+
+int netlink_add_chain(struct netlink_ctx *ctx, const struct handle *h,
+ const struct chain *chain)
+{
+ struct nfnl_nft_chain *nlc;
+ int err;
+
+ nlc = alloc_nft_chain(h);
+ if (chain != NULL && (chain->hooknum || chain->priority)) {
+ nfnl_nft_chain_set_hooknum(nlc, chain->hooknum);
+ nfnl_nft_chain_set_priority(nlc, chain->priority);
+ }
+ err = nfnl_nft_chain_add(nf_sock, nlc, NLM_F_EXCL);
+ nfnl_nft_chain_put(nlc);
+
+ if (err < 0)
+ netlink_io_error(ctx, NULL, "Could not add chain: %s",
+ nl_geterror(err));
+ return err;
+}
+
+int netlink_delete_chain(struct netlink_ctx *ctx, const struct handle *h)
+{
+ struct nfnl_nft_chain *nlc;
+ int err;
+
+ nlc = alloc_nft_chain(h);
+ err = nfnl_nft_chain_delete(nf_sock, nlc, 0);
+ nfnl_nft_chain_put(nlc);
+
+ if (err < 0)
+ netlink_io_error(ctx, NULL, "Could not delete chain: %s",
+ nl_geterror(err));
+ return err;
+}
+
+static void list_chain_cb(struct nl_object *obj, void *arg)
+{
+ struct nfnl_nft_chain *nlc = (struct nfnl_nft_chain *)obj;
+ struct netlink_ctx *ctx = arg;
+ struct chain *chain;
+#if TRACE
+ netlink_dump_object(obj);;
+#endif
+ if (!nfnl_nft_chain_test_family(nlc) ||
+ !nfnl_nft_chain_test_table(nlc) ||
+ !nfnl_nft_chain_test_name(nlc)) {
+ netlink_io_error(ctx, NULL, "Incomplete chain received");
+ return;
+ }
+
+ chain = chain_alloc(nfnl_nft_chain_get_name(nlc));
+ chain->handle.family = nfnl_nft_chain_get_family(nlc);
+ chain->handle.table = xstrdup(nfnl_nft_chain_get_table(nlc));
+ chain->hooknum = nfnl_nft_chain_get_hooknum(nlc);
+ chain->priority = nfnl_nft_chain_get_priority(nlc);
+ list_add_tail(&chain->list, &ctx->list);
+}
+
+int netlink_list_chains(struct netlink_ctx *ctx, const struct handle *h)
+{
+ struct nl_cache *chain_cache;
+ struct nfnl_nft_chain *nlc;
+ int err;
+
+ err = nfnl_nft_chain_alloc_cache(nf_sock, &chain_cache);
+ if (err < 0)
+ return netlink_io_error(ctx, NULL,
+ "Could not receive chains from kernel: %s",
+ nl_geterror(err));
+
+ nlc = alloc_nft_chain(h);
+ nl_cache_foreach_filter(chain_cache, (struct nl_object *)nlc,
+ list_chain_cb, ctx);
+ nfnl_nft_chain_put(nlc);
+ nl_cache_free(chain_cache);
+ return 0;
+}
+
+static int netlink_get_chain_cb(struct nl_msg *msg, void *arg)
+{
+ return nl_msg_parse(msg, list_chain_cb, arg);
+}
+
+int netlink_get_chain(struct netlink_ctx *ctx, const struct handle *h)
+{
+ struct nfnl_nft_chain *nlc;
+ int err;
+
+ nlc = alloc_nft_chain(h);
+ nfnl_nft_chain_query(nf_sock, nlc, 0);
+ nl_socket_modify_cb(nf_sock, NL_CB_VALID, NL_CB_CUSTOM,
+ netlink_get_chain_cb, ctx);
+ err = nl_recvmsgs_default(nf_sock);
+ nfnl_nft_chain_put(nlc);
+
+ if (err < 0)
+ return netlink_io_error(ctx, NULL,
+ "Could not receive chain from kernel: %s",
+ nl_geterror(err));
+ return err;
+}
+
+int netlink_list_chain(struct netlink_ctx *ctx, const struct handle *h)
+{
+ return netlink_list_rules(ctx, h);
+}
+
+int netlink_flush_chain(struct netlink_ctx *ctx, const struct handle *h)
+{
+ return netlink_flush_rules(ctx, h);
+}
+
+int netlink_add_table(struct netlink_ctx *ctx, const struct handle *h,
+ const struct table *table)
+{
+ struct nfnl_nft_table *nlt;
+ int err;
+
+ nlt = alloc_nft_table(h);
+ err = nfnl_nft_table_add(nf_sock, nlt, NLM_F_EXCL);
+ nfnl_nft_table_put(nlt);
+
+ if (err < 0)
+ netlink_io_error(ctx, NULL, "Could not add table: %s",
+ nl_geterror(err));
+ return err;
+}
+
+int netlink_delete_table(struct netlink_ctx *ctx, const struct handle *h)
+{
+ struct nfnl_nft_table *nlt;
+ int err;
+
+ nlt = alloc_nft_table(h);
+ err = nfnl_nft_table_delete(nf_sock, nlt, 0);
+ nfnl_nft_table_put(nlt);
+
+ if (err < 0)
+ netlink_io_error(ctx, NULL, "Could not delete table: %s",
+ nl_geterror(err));
+ return err;
+}
+
+static void list_table_cb(struct nl_object *obj, void *arg)
+{
+ struct nfnl_nft_table *nlt = (struct nfnl_nft_table *)obj;
+ struct netlink_ctx *ctx = arg;
+ struct table *table;
+#if TRACE
+ netlink_dump_object(obj);
+#endif
+ if (!nfnl_nft_table_test_family(nlt) ||
+ !nfnl_nft_table_test_name(nlt)) {
+ netlink_io_error(ctx, NULL, "Incomplete table received");
+ return;
+ }
+
+ table = table_alloc();
+ table->handle.family = nfnl_nft_table_get_family(nlt);
+ table->handle.table = xstrdup(nfnl_nft_table_get_name(nlt));
+ list_add_tail(&table->list, &ctx->list);
+}
+
+int netlink_list_tables(struct netlink_ctx *ctx, const struct handle *h)
+{
+ struct nl_cache *table_cache;
+ struct nfnl_nft_table *nlt;
+ int err;
+
+ err = nfnl_nft_table_alloc_cache(nf_sock, &table_cache);
+ if (err < 0)
+ return netlink_io_error(ctx, NULL,
+ "Could not receive tables from kernel: %s",
+ nl_geterror(err));
+
+ nlt = alloc_nft_table(h);
+ nl_cache_foreach_filter(table_cache, (struct nl_object *)nlt,
+ list_table_cb, ctx);
+ nfnl_nft_table_put(nlt);
+ nl_cache_free(table_cache);
+ return 0;
+}
+
+static int netlink_get_table_cb(struct nl_msg *msg, void *arg)
+{
+ return nl_msg_parse(msg, list_table_cb, arg);
+}
+
+int netlink_get_table(struct netlink_ctx *ctx, const struct handle *h)
+{
+ struct nfnl_nft_table *nlt;
+ int err;
+
+ nlt = alloc_nft_table(h);
+ nfnl_nft_table_query(nf_sock, nlt, 0);
+ nl_socket_modify_cb(nf_sock, NL_CB_VALID, NL_CB_CUSTOM,
+ netlink_get_table_cb, ctx);
+ err = nl_recvmsgs_default(nf_sock);
+ nfnl_nft_table_put(nlt);
+
+ if (err < 0)
+ return netlink_io_error(ctx, NULL,
+ "Could not receive table from kernel: %s",
+ nl_geterror(err));
+ return err;
+}
+
+
+int netlink_list_table(struct netlink_ctx *ctx, const struct handle *h)
+{
+ return netlink_list_rules(ctx, h);
+}
+
+int netlink_flush_table(struct netlink_ctx *ctx, const struct handle *h)
+{
+ return netlink_flush_rules(ctx, h);
+}
diff --git a/src/netlink_delinearize.c b/src/netlink_delinearize.c
new file mode 100644
index 00000000..be2271ce
--- /dev/null
+++ b/src/netlink_delinearize.c
@@ -0,0 +1,781 @@
+/*
+ * Copyright (c) 2008 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 <linux/netfilter/nf_tables.h>
+#include <netlink.h>
+#include <rule.h>
+#include <statement.h>
+#include <expression.h>
+#include <gmputil.h>
+#include <utils.h>
+#include <erec.h>
+
+struct netlink_parse_ctx {
+ struct list_head *msgs;
+ struct rule *rule;
+ struct expr *registers[NFT_REG_MAX + 1];
+};
+
+static void __fmtstring(3, 4) netlink_error(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const char *fmt, ...)
+{
+ struct error_record *erec;
+ va_list ap;
+
+ va_start(ap, fmt);
+ erec = erec_vcreate(EREC_ERROR, loc, fmt, ap);
+ va_end(ap);
+ erec_queue(erec, ctx->msgs);
+}
+
+static void netlink_set_register(struct netlink_parse_ctx *ctx,
+ enum nft_registers reg,
+ struct expr *expr)
+{
+ if (reg > NFT_REG_MAX) {
+ netlink_error(ctx, &expr->location,
+ "Invalid destination register %u", reg);
+ expr_free(expr);
+ return;
+ }
+
+ ctx->registers[reg] = expr;
+}
+
+static struct expr *netlink_get_register(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ enum nft_registers reg)
+{
+ struct expr *expr;
+
+ if (reg == NFT_REG_VERDICT || reg > NFT_REG_MAX) {
+ netlink_error(ctx, loc, "Invalid source register %u", reg);
+ return NULL;
+ }
+
+ expr = ctx->registers[reg];
+ ctx->registers[reg] = NULL;
+ return expr;
+}
+
+static struct expr *netlink_alloc_value(const struct location *loc,
+ const struct nfnl_nft_data *nld)
+{
+ return constant_expr_alloc(loc, &invalid_type, BYTEORDER_INVALID,
+ nfnl_nft_data_get_size(nld) * BITS_PER_BYTE,
+ nfnl_nft_data_get(nld));
+}
+
+static struct expr *netlink_alloc_verdict(const struct location *loc,
+ const struct nfnl_nft_data *nld)
+{
+ unsigned int code;
+ char *chain;
+
+ code = nfnl_nft_verdict_get_verdict(nld);
+ switch (code) {
+ case NFT_JUMP:
+ case NFT_GOTO:
+ chain = xstrdup(nfnl_nft_verdict_get_chain(nld));
+ break;
+ default:
+ chain = NULL;
+ break;
+ }
+
+ return verdict_expr_alloc(loc, code, chain);
+}
+
+static struct expr *netlink_alloc_data(const struct location *loc,
+ const struct nfnl_nft_data *nld,
+ enum nft_registers dreg)
+{
+ switch (dreg) {
+ case NFT_REG_VERDICT:
+ return netlink_alloc_verdict(loc, nld);
+ default:
+ return netlink_alloc_value(loc, nld);
+ }
+}
+
+static void netlink_parse_immediate(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nfnl_nft_expr *nle)
+{
+ const struct nfnl_nft_data *data = nfnl_nft_immediate_get_data(nle);
+ enum nft_registers dreg = nfnl_nft_immediate_get_dreg(nle);
+ struct stmt *stmt;
+ struct expr *expr;
+
+ expr = netlink_alloc_data(loc, data, dreg);
+ if (dreg == NFT_REG_VERDICT) {
+ stmt = verdict_stmt_alloc(loc, expr);
+ list_add_tail(&stmt->list, &ctx->rule->stmts);
+ } else
+ netlink_set_register(ctx, dreg, expr);
+}
+
+static enum ops netlink_parse_cmp_op(const struct nfnl_nft_expr *nle)
+{
+ switch (nfnl_nft_cmp_get_op(nle)) {
+ case NFT_CMP_EQ:
+ return OP_EQ;
+ case NFT_CMP_NEQ:
+ return OP_NEQ;
+ case NFT_CMP_LT:
+ return OP_LT;
+ case NFT_CMP_LTE:
+ return OP_LTE;
+ case NFT_CMP_GT:
+ return OP_GT;
+ case NFT_CMP_GTE:
+ return OP_GTE;
+ default:
+ return OP_INVALID;
+ }
+}
+
+static void netlink_parse_cmp(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nfnl_nft_expr *nle)
+{
+ const struct nfnl_nft_data *data = nfnl_nft_cmp_get_data(nle);
+ struct expr *expr, *left, *right;
+ struct stmt *stmt;
+ enum ops op;
+
+ left = netlink_get_register(ctx, loc, nfnl_nft_cmp_get_sreg(nle));
+ if (left == NULL)
+ return netlink_error(ctx, loc,
+ "Relational expression has no left "
+ "hand side");
+
+ op = netlink_parse_cmp_op(nle);
+ right = netlink_alloc_value(loc, data);
+
+ // FIXME
+ if (left->len && left->dtype && left->dtype->type != TYPE_STRING &&
+ left->len != right->len)
+ return netlink_error(ctx, loc,
+ "Relational expression size mismatch");
+
+ expr = relational_expr_alloc(loc, op, left, right);
+ stmt = expr_stmt_alloc(loc, expr);
+ list_add_tail(&stmt->list, &ctx->rule->stmts);
+}
+
+struct netlink_data_ctx {
+ const struct location *location;
+ struct expr *expr;
+ enum nft_registers dreg;
+};
+
+static void netlink_data_ctx_init(struct netlink_data_ctx *dctx,
+ const struct location *loc,
+ struct expr *expr, enum nft_registers dreg)
+{
+ dctx->location = loc;
+ dctx->expr = expr;
+ dctx->dreg = dreg;
+}
+
+static void netlink_set_parse_data(struct nfnl_nft_data *data,
+ enum nft_set_elem_flags flags,
+ void *arg)
+{
+ struct netlink_data_ctx *dctx = arg;
+ struct expr *expr;
+
+ assert(dctx->dreg != NFT_REG_VERDICT);
+ expr = netlink_alloc_value(dctx->location, data);
+ if (flags & NFT_SE_INTERVAL_END)
+ expr->flags |= EXPR_F_INTERVAL_END;
+ compound_expr_add(dctx->expr, expr);
+}
+
+static void netlink_set_parse_mapping(struct nfnl_nft_data *data,
+ struct nfnl_nft_data *mapping,
+ enum nft_set_elem_flags flags,
+ void *arg)
+{
+ struct netlink_data_ctx *dctx = arg;
+ struct expr *expr, *left, *right;
+
+ left = netlink_alloc_value(dctx->location, data);
+ if (mapping != NULL) {
+ right = netlink_alloc_data(dctx->location, mapping, dctx->dreg);
+ expr = mapping_expr_alloc(dctx->location, left, right);
+ } else
+ expr = left;
+
+ if (flags & NFT_SE_INTERVAL_END)
+ expr->flags |= EXPR_F_INTERVAL_END;
+ compound_expr_add(dctx->expr, expr);
+}
+
+extern void interval_map_decompose(struct expr *set);
+
+static void netlink_parse_set(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nfnl_nft_expr *nle)
+{
+ struct stmt *stmt;
+ struct expr *expr, *left, *right;
+ struct netlink_data_ctx dctx;
+ enum nft_registers dreg;
+ enum nft_set_flags flags;
+
+ left = netlink_get_register(ctx, loc, nfnl_nft_set_get_sreg(nle));
+ if (left == NULL)
+ return netlink_error(ctx, loc,
+ "Set expression has no left hand side");
+
+ right = set_expr_alloc(loc);
+
+ flags = nfnl_nft_set_get_flags(nle);
+ if (flags & NFT_SET_MAP) {
+ dreg = nfnl_nft_set_get_dreg(nle);
+ netlink_data_ctx_init(&dctx, loc, right, dreg);
+ nfnl_nft_set_foreach_mapping(nle, netlink_set_parse_mapping,
+ &dctx);
+
+ expr = map_expr_alloc(loc, left, right);
+ if (dreg != NFT_REG_VERDICT)
+ return netlink_set_register(ctx, dreg, expr);
+ } else {
+ netlink_data_ctx_init(&dctx, loc, right, EXPR_VALUE);
+ nfnl_nft_set_foreach_elem(nle, netlink_set_parse_data, &dctx);
+ if (flags & NFT_SET_INTERVAL) {
+ interval_map_decompose(right);
+ right->flags |= NFT_SET_INTERVAL;
+ }
+ expr = relational_expr_alloc(loc, OP_LOOKUP, left, right);
+ }
+
+ stmt = expr_stmt_alloc(loc, expr);
+ list_add_tail(&stmt->list, &ctx->rule->stmts);
+}
+
+static void netlink_parse_bitwise(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nfnl_nft_expr *nle)
+{
+ struct expr *expr, *left, *mask, *xor, *or;
+ mpz_t m, x, o;
+
+ left = netlink_get_register(ctx, loc, nfnl_nft_bitwise_get_sreg(nle));
+ if (left == NULL)
+ return netlink_error(ctx, loc,
+ "Bitwise expression has no left "
+ "hand side");
+
+ expr = left;
+
+ mask = netlink_alloc_value(loc, nfnl_nft_bitwise_get_mask(nle));
+ mpz_init_set(m, mask->value);
+
+ xor = netlink_alloc_value(loc, nfnl_nft_bitwise_get_xor(nle));
+ mpz_init_set(x, xor->value);
+
+ mpz_init_set_ui(o, 0);
+ if (mpz_scan0(m, 0) != mask->len || mpz_cmp_ui(x, 0)) {
+ /* o = (m & x) ^ x */
+ mpz_and(o, m, x);
+ mpz_xor(o, o, x);
+ /* x &= m */
+ mpz_and(x, x, m);
+ /* m |= o */
+ mpz_ior(m, m, o);
+ }
+
+ if (mpz_scan0(m, 0) != left->len) {
+ mpz_set(mask->value, m);
+ expr = binop_expr_alloc(loc, OP_AND, expr, mask);
+ expr->len = left->len;
+ } else
+ expr_free(mask);
+
+ if (mpz_cmp_ui(x, 0)) {
+ mpz_set(xor->value, x);
+ expr = binop_expr_alloc(loc, OP_XOR, expr, xor);
+ expr->len = left->len;
+ } else
+ expr_free(xor);
+
+ if (mpz_cmp_ui(o, 0)) {
+ or = netlink_alloc_value(loc, nfnl_nft_bitwise_get_xor(nle));
+ mpz_set(or->value, o);
+ expr = binop_expr_alloc(loc, OP_OR, expr, or);
+ expr->len = left->len;
+ }
+
+ mpz_clear(m);
+ mpz_clear(x);
+ mpz_clear(o);
+
+ netlink_set_register(ctx, nfnl_nft_bitwise_get_dreg(nle), expr);
+}
+
+static void netlink_parse_byteorder(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nfnl_nft_expr *nle)
+{
+ struct expr *expr, *arg;
+ enum ops op;
+
+ arg = netlink_get_register(ctx, loc, nfnl_nft_byteorder_get_sreg(nle));
+ if (arg == NULL)
+ return netlink_error(ctx, loc,
+ "Byteorder expression has no left "
+ "hand side");
+
+ switch (nfnl_nft_byteorder_get_op(nle)) {
+ case NFT_BYTEORDER_NTOH:
+ op = OP_NTOH;
+ break;
+ case NFT_BYTEORDER_HTON:
+ op = OP_HTON;
+ break;
+ default:
+ BUG();
+ }
+
+ expr = unary_expr_alloc(loc, op, arg);
+ expr->len = arg->len;
+ netlink_set_register(ctx, nfnl_nft_byteorder_get_dreg(nle), expr);
+}
+
+static void netlink_parse_payload(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nfnl_nft_expr *nle)
+{
+ struct expr *expr;
+
+ expr = payload_expr_alloc(loc, NULL, 0);
+ payload_init_raw(expr, nfnl_nft_payload_get_base(nle) + 1,
+ nfnl_nft_payload_get_offset(nle) * BITS_PER_BYTE,
+ nfnl_nft_payload_get_len(nle) * BITS_PER_BYTE);
+
+ netlink_set_register(ctx, nfnl_nft_payload_get_dreg(nle), expr);
+}
+
+static void netlink_parse_exthdr(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nfnl_nft_expr *nle)
+{
+ struct expr *expr;
+
+ expr = exthdr_expr_alloc(loc, NULL, 0);
+ exthdr_init_raw(expr, nfnl_nft_exthdr_get_type(nle),
+ nfnl_nft_exthdr_get_offset(nle) * BITS_PER_BYTE,
+ nfnl_nft_exthdr_get_len(nle) * BITS_PER_BYTE);
+
+ netlink_set_register(ctx, nfnl_nft_exthdr_get_dreg(nle), expr);
+}
+
+static void netlink_parse_meta(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nfnl_nft_expr *nle)
+{
+ struct expr *expr;
+
+ expr = meta_expr_alloc(loc, nfnl_nft_meta_get_key(nle));
+ netlink_set_register(ctx, nfnl_nft_meta_get_dreg(nle), expr);
+}
+
+static void netlink_parse_ct(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nfnl_nft_expr *nle)
+{
+ struct expr *expr;
+
+ expr = ct_expr_alloc(loc, nfnl_nft_ct_get_key(nle));
+ netlink_set_register(ctx, nfnl_nft_ct_get_dreg(nle), expr);
+}
+
+static void netlink_parse_counter(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nfnl_nft_expr *nle)
+{
+ struct stmt *stmt;
+
+ stmt = counter_stmt_alloc(loc);
+ stmt->counter.packets = nfnl_nft_counter_get_packets(nle);
+ stmt->counter.bytes = nfnl_nft_counter_get_bytes(nle);
+ list_add_tail(&stmt->list, &ctx->rule->stmts);
+}
+
+static void netlink_parse_log(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nfnl_nft_expr *nle)
+{
+ struct stmt *stmt;
+ const char *prefix;
+
+ stmt = log_stmt_alloc(loc);
+ prefix = nfnl_nft_log_get_prefix(nle);
+ if (prefix != NULL)
+ stmt->log.prefix = xstrdup(prefix);
+ stmt->log.group = nfnl_nft_log_get_group(nle);
+ stmt->log.snaplen = nfnl_nft_log_get_snaplen(nle);
+ stmt->log.qthreshold = nfnl_nft_log_get_qthreshold(nle);
+ list_add_tail(&stmt->list, &ctx->rule->stmts);
+}
+
+static void netlink_parse_limit(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nfnl_nft_expr *nle)
+{
+ struct stmt *stmt;
+
+ stmt = limit_stmt_alloc(loc);
+ stmt->limit.rate = nfnl_nft_limit_get_rate(nle);
+ stmt->limit.depth = nfnl_nft_limit_get_depth(nle);
+ list_add_tail(&stmt->list, &ctx->rule->stmts);
+}
+
+static void netlink_parse_reject(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nfnl_nft_expr *expr)
+{
+ struct stmt *stmt;
+
+ stmt = reject_stmt_alloc(loc);
+ list_add_tail(&stmt->list, &ctx->rule->stmts);
+}
+
+static void netlink_parse_nat(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nfnl_nft_expr *nle)
+{
+ struct stmt *stmt;
+ struct expr *addr, *proto;
+ enum nft_registers reg1, reg2;
+
+ stmt = nat_stmt_alloc(loc);
+ stmt->nat.type = nfnl_nft_nat_get_type(nle);
+
+ reg1 = nfnl_nft_nat_get_sreg_addr_min(nle);
+ if (reg1) {
+ addr = netlink_get_register(ctx, loc, reg1);
+ if (addr == NULL)
+ return netlink_error(ctx, loc,
+ "NAT statement has no address "
+ "expression");
+
+ expr_set_type(addr, &ipaddr_type, BYTEORDER_BIG_ENDIAN);
+ stmt->nat.addr = addr;
+ }
+
+ reg2 = nfnl_nft_nat_get_sreg_addr_max(nle);
+ if (reg2 && reg2 != reg1) {
+ addr = netlink_get_register(ctx, loc, reg2);
+ if (addr == NULL)
+ return netlink_error(ctx, loc,
+ "NAT statement has no address "
+ "expression");
+
+ expr_set_type(addr, &ipaddr_type, BYTEORDER_BIG_ENDIAN);
+ if (stmt->nat.addr != NULL)
+ addr = range_expr_alloc(loc, stmt->nat.addr, addr);
+ stmt->nat.addr = addr;
+ }
+
+ reg1 = nfnl_nft_nat_get_sreg_proto_min(nle);
+ if (reg1) {
+ proto = netlink_get_register(ctx, loc, reg1);
+ if (proto == NULL)
+ return netlink_error(ctx, loc,
+ "NAT statement has no proto "
+ "expression");
+
+ expr_set_type(proto, &inet_service_type, BYTEORDER_BIG_ENDIAN);
+ stmt->nat.proto = proto;
+ }
+
+ reg2 = nfnl_nft_nat_get_sreg_proto_max(nle);
+ if (reg2 && reg2 != reg1) {
+ proto = netlink_get_register(ctx, loc, reg1);
+ if (proto == NULL)
+ return netlink_error(ctx, loc,
+ "NAT statement has no proto "
+ "expression");
+
+ expr_set_type(proto, &inet_service_type, BYTEORDER_BIG_ENDIAN);
+ stmt->nat.proto = proto;
+ if (stmt->nat.proto != NULL)
+ proto = range_expr_alloc(loc, stmt->nat.proto, proto);
+ stmt->nat.proto = proto;
+ }
+
+ list_add_tail(&stmt->list, &ctx->rule->stmts);
+}
+
+static const struct {
+ const char *name;
+ void (*parse)(struct netlink_parse_ctx *ctx,
+ const struct location *loc,
+ const struct nfnl_nft_expr *nle);
+} netlink_parsers[] = {
+ { .name = "immediate", .parse = netlink_parse_immediate },
+ { .name = "cmp", .parse = netlink_parse_cmp },
+ { .name = "set", .parse = netlink_parse_set },
+ { .name = "bitwise", .parse = netlink_parse_bitwise },
+ { .name = "byteorder", .parse = netlink_parse_byteorder },
+ { .name = "payload", .parse = netlink_parse_payload },
+ { .name = "exthdr", .parse = netlink_parse_exthdr },
+ { .name = "meta", .parse = netlink_parse_meta },
+ { .name = "ct", .parse = netlink_parse_ct },
+ { .name = "counter", .parse = netlink_parse_counter },
+ { .name = "log", .parse = netlink_parse_log },
+ { .name = "limit", .parse = netlink_parse_limit },
+ { .name = "reject", .parse = netlink_parse_reject },
+ { .name = "nat", .parse = netlink_parse_nat },
+};
+
+static const struct input_descriptor indesc_netlink = {
+ .name = "netlink",
+ .type = INDESC_NETLINK,
+};
+
+static void netlink_parse_expr(struct nl_object *obj, void *arg)
+{
+ const struct nfnl_nft_expr *nle = (struct nfnl_nft_expr *)obj;
+ const char *type = nfnl_nft_expr_get_type(nle);
+ struct netlink_parse_ctx *ctx = arg;
+ struct location loc;
+ unsigned int i;
+
+ memset(&loc, 0, sizeof(loc));
+ loc.indesc = &indesc_netlink;
+ loc.nl_obj = obj;
+
+ for (i = 0; i < array_size(netlink_parsers); i++) {
+ if (strcmp(type, netlink_parsers[i].name))
+ continue;
+ return netlink_parsers[i].parse(ctx, &loc, nle);
+ }
+
+ netlink_error(ctx, &loc, "unknown expression type '%s'", type);
+}
+
+struct rule_pp_ctx {
+ struct payload_ctx pctx;
+};
+
+static void payload_match_postprocess(struct payload_ctx *ctx,
+ struct stmt *stmt, struct expr *expr)
+{
+ struct expr *left = expr->left, *right = expr->right, *tmp;
+ struct list_head list = LIST_HEAD_INIT(list);
+ struct stmt *nstmt;
+ struct expr *nexpr;
+
+ switch (expr->op) {
+ case OP_EQ:
+ case OP_NEQ:
+ payload_expr_expand(&list, left, ctx);
+ list_for_each_entry(left, &list, list) {
+ tmp = constant_expr_splice(right, left->len);
+ expr_set_type(tmp, left->dtype, left->byteorder);
+ nexpr = relational_expr_alloc(&expr->location, expr->op,
+ left, tmp);
+ payload_ctx_update(ctx, nexpr);
+
+ nstmt = expr_stmt_alloc(&stmt->location, nexpr);
+ list_add_tail(&nstmt->list, &stmt->list);
+ }
+ list_del(&stmt->list);
+ stmt_free(stmt);
+ break;
+ default:
+ payload_expr_complete(left, ctx);
+ expr_set_type(expr->right, expr->left->dtype,
+ expr->left->byteorder);
+ break;
+ }
+}
+
+static void meta_match_postprocess(struct payload_ctx *ctx,
+ const struct expr *expr)
+{
+ switch (expr->op) {
+ case OP_EQ:
+ payload_ctx_update_meta(ctx, expr);
+ break;
+ default:
+ break;
+ }
+}
+
+static void expr_postprocess(struct rule_pp_ctx *ctx,
+ struct stmt *stmt, struct expr **exprp)
+{
+ struct expr *expr = *exprp, *i;
+
+ //pr_debug("%s len %u\n", expr->ops->name, expr->len);
+
+ switch (expr->ops->type) {
+ case EXPR_MAP:
+ expr_postprocess(ctx, stmt, &expr->expr);
+ list_for_each_entry(i, &expr->mappings->expressions, list) {
+ if (i->flags & EXPR_F_INTERVAL_END)
+ continue;
+ expr_set_type(i->left, expr->expr->dtype,
+ expr->expr->byteorder);
+ }
+ expr_postprocess(ctx, stmt, &expr->mappings);
+ break;
+ case EXPR_MAPPING:
+ expr_postprocess(ctx, stmt, &expr->left);
+ expr_postprocess(ctx, stmt, &expr->right);
+ break;
+ case EXPR_SET:
+ list_for_each_entry(i, &expr->expressions, list)
+ expr_postprocess(ctx, stmt, &i);
+ break;
+ case EXPR_UNARY:
+ expr_postprocess(ctx, stmt, &expr->arg);
+ expr_set_type(expr->arg, expr->arg->dtype, !expr->arg->byteorder);
+
+ *exprp = expr_get(expr->arg);
+ expr_free(expr);
+ break;
+ case EXPR_BINOP:
+ expr_postprocess(ctx, stmt, &expr->left);
+ expr_postprocess(ctx, stmt, &expr->right);
+ expr_set_type(expr->right, expr->left->dtype,
+ expr->left->byteorder);
+ expr_set_type(expr, expr->left->dtype,
+ expr->left->byteorder);
+ break;
+ case EXPR_RELATIONAL:
+ switch (expr->left->ops->type) {
+ case EXPR_PAYLOAD:
+ payload_match_postprocess(&ctx->pctx, stmt, expr);
+ return;
+ case EXPR_META:
+ meta_match_postprocess(&ctx->pctx, expr);
+ break;
+ default:
+ expr_postprocess(ctx, stmt, &expr->left);
+ break;
+ }
+
+ expr_set_type(expr->right, expr->left->dtype, expr->left->byteorder);
+ expr_postprocess(ctx, stmt, &expr->right);
+
+ if (expr->left->ops->type == EXPR_BINOP &&
+ expr->left->op == OP_AND &&
+ expr->op == OP_NEQ &&
+ expr->right->dtype->basetype->type == TYPE_BITMASK) {
+ unsigned int n;
+
+ expr_free(expr->right);
+ expr->right = list_expr_alloc(&expr->left->left->location);
+ n = 0;
+ while ((n = mpz_scan1(expr->left->right->value, n + 1))) {
+ if (n > expr->left->right->len)
+ break;
+ i = constant_expr_alloc(&expr->left->right->location,
+ expr->left->left->dtype,
+ expr->left->right->byteorder,
+ expr->left->right->len, NULL);
+ mpz_set_ui(i->value, 1);
+ mpz_lshift_ui(i->value, n);
+ compound_expr_add(expr->right, i);
+ }
+ expr->left = expr->left->left;
+ expr->op = OP_FLAGCMP;
+ }
+ break;
+ case EXPR_PAYLOAD:
+ payload_expr_complete(expr, &ctx->pctx);
+ break;
+ case EXPR_VALUE:
+ // FIXME
+ if (expr->byteorder == BYTEORDER_HOST_ENDIAN)
+ mpz_switch_byteorder(expr->value, expr->len / BITS_PER_BYTE);
+
+ // Quite a hack :)
+ if (expr->dtype->type == TYPE_STRING) {
+ unsigned int len = expr->len;
+ mpz_t tmp;
+ mpz_init(tmp);
+ while (len >= BITS_PER_BYTE) {
+ mpz_bitmask(tmp, BITS_PER_BYTE);
+ mpz_lshift_ui(tmp, len - BITS_PER_BYTE);
+ mpz_and(tmp, tmp, expr->value);
+ if (mpz_cmp_ui(tmp, 0))
+ break;
+ len -= BITS_PER_BYTE;
+ }
+ mpz_clear(tmp);
+ expr->len = len;
+ }
+ break;
+ case EXPR_EXTHDR:
+ case EXPR_META:
+ case EXPR_CT:
+ case EXPR_VERDICT:
+ break;
+ default:
+ printf("%s\n", expr->ops->name);
+ BUG();
+ }
+}
+
+static void rule_parse_postprocess(struct netlink_parse_ctx *ctx, struct rule *rule)
+{
+ struct rule_pp_ctx rctx;
+ struct stmt *stmt, *next;
+
+ payload_ctx_init(&rctx.pctx, rule->handle.family);
+
+ list_for_each_entry_safe(stmt, next, &rule->stmts, list) {
+ switch (stmt->ops->type) {
+ case STMT_EXPRESSION:
+ expr_postprocess(&rctx, stmt, &stmt->expr);
+ break;
+ case STMT_NAT:
+ if (stmt->nat.addr != NULL)
+ expr_postprocess(&rctx, stmt, &stmt->nat.addr);
+ if (stmt->nat.proto != NULL)
+ expr_postprocess(&rctx, stmt, &stmt->nat.proto);
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+struct rule *netlink_delinearize_rule(struct netlink_ctx *ctx,
+ const struct nl_object *obj)
+{
+ const struct nfnl_nft_rule *nlr = (const struct nfnl_nft_rule *)obj;
+ struct netlink_parse_ctx _ctx, *pctx = &_ctx;
+ struct handle h;
+
+ memset(&_ctx, 0, sizeof(_ctx));
+ _ctx.msgs = ctx->msgs;
+
+ memset(&h, 0, sizeof(h));
+ h.family = nfnl_nft_rule_get_family(nlr);
+ h.table = xstrdup(nfnl_nft_rule_get_table(nlr));
+ h.chain = xstrdup(nfnl_nft_rule_get_chain(nlr));
+ h.handle = nfnl_nft_rule_get_handle(nlr);
+
+ pctx->rule = rule_alloc(&internal_location, &h);
+ nfnl_nft_rule_foreach_expr(nlr, netlink_parse_expr, pctx);
+
+ rule_parse_postprocess(pctx, pctx->rule);
+ return pctx->rule;
+}
diff --git a/src/netlink_linearize.c b/src/netlink_linearize.c
new file mode 100644
index 00000000..fcb111c6
--- /dev/null
+++ b/src/netlink_linearize.c
@@ -0,0 +1,720 @@
+/*
+ * Copyright (c) 2008 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 <linux/netfilter/nf_tables.h>
+
+#include <rule.h>
+#include <statement.h>
+#include <expression.h>
+#include <netlink.h>
+#include <gmputil.h>
+#include <utils.h>
+
+struct netlink_linearize_ctx {
+ struct nfnl_nft_rule *nlr;
+ unsigned int reg_low;
+};
+
+static enum nft_registers get_register(struct netlink_linearize_ctx *ctx)
+{
+ if (ctx->reg_low > NFT_REG_MAX)
+ BUG();
+ return ctx->reg_low++;
+}
+
+static void release_register(struct netlink_linearize_ctx *ctx)
+{
+ ctx->reg_low--;
+}
+
+static struct nfnl_nft_data *netlink_gen_mpz_data(const mpz_t value,
+ enum byteorder byteorder,
+ unsigned int len)
+{
+ unsigned char data[len];
+
+ mpz_export_data(data, value, byteorder, len);
+ return alloc_nft_data(data, len);
+}
+
+static struct nfnl_nft_data *netlink_gen_constant_data(const struct expr *expr)
+{
+ assert(expr->ops->type == EXPR_VALUE);
+ return netlink_gen_mpz_data(expr->value, expr->byteorder,
+ div_round_up(expr->len, BITS_PER_BYTE));
+}
+
+static struct nfnl_nft_data *netlink_gen_concat_data(const struct expr *expr)
+{
+ struct nfnl_nft_data *data;
+ const struct expr *i;
+ void *buf;
+ unsigned int len, offset;
+
+ len = 0;
+ list_for_each_entry(i, &expr->expressions, list)
+ len += i->len;
+
+ buf = xmalloc(len / BITS_PER_BYTE);
+
+ offset = 0;
+ list_for_each_entry(i, &expr->expressions, list) {
+ assert(i->ops->type == EXPR_VALUE);
+ mpz_export_data(buf + offset, i->value, i->byteorder,
+ i->len / BITS_PER_BYTE);
+ offset += i->len / BITS_PER_BYTE;
+ }
+
+ data = alloc_nft_data(buf, len / BITS_PER_BYTE);
+ xfree(buf);
+ return data;
+}
+
+static struct nfnl_nft_data *netlink_gen_verdict(const struct expr *expr)
+{
+ struct nfnl_nft_data *verdict;
+
+ verdict = nfnl_nft_verdict_alloc();
+ nfnl_nft_verdict_set_verdict(verdict, expr->verdict);
+
+ switch (expr->verdict) {
+ case NFT_JUMP:
+ case NFT_GOTO:
+ nfnl_nft_verdict_set_chain(verdict, expr->chain);
+ }
+
+ return verdict;
+}
+
+static struct nfnl_nft_data *netlink_gen_data(const struct expr *expr)
+{
+ switch (expr->ops->type) {
+ case EXPR_VALUE:
+ return netlink_gen_constant_data(expr);
+ case EXPR_CONCAT:
+ return netlink_gen_concat_data(expr);
+ case EXPR_VERDICT:
+ return netlink_gen_verdict(expr);
+ default:
+ BUG();
+ }
+}
+
+static void netlink_gen_expr(struct netlink_linearize_ctx *ctx,
+ const struct expr *expr,
+ enum nft_registers dreg);
+
+static void netlink_gen_concat(struct netlink_linearize_ctx *ctx,
+ const struct expr *expr,
+ enum nft_registers dreg)
+{
+ const struct expr *i;
+
+ list_for_each_entry(i, &expr->expressions, list)
+ netlink_gen_expr(ctx, i, dreg);
+}
+
+static void netlink_gen_payload(struct netlink_linearize_ctx *ctx,
+ const struct expr *expr,
+ enum nft_registers dreg)
+{
+ struct nfnl_nft_expr *nle;
+
+ nle = alloc_nft_expr(nfnl_nft_payload_init);
+ nfnl_nft_payload_set_dreg(nle, dreg);
+ nfnl_nft_payload_set_base(nle, expr->payload.base - 1);
+ nfnl_nft_payload_set_offset(nle, expr->payload.offset / BITS_PER_BYTE);
+ nfnl_nft_payload_set_len(nle, expr->len / BITS_PER_BYTE);
+ nfnl_nft_rule_add_expr(ctx->nlr, nle);
+}
+
+static void netlink_gen_exthdr(struct netlink_linearize_ctx *ctx,
+ const struct expr *expr,
+ enum nft_registers dreg)
+{
+ struct nfnl_nft_expr *nle;
+
+ nle = alloc_nft_expr(nfnl_nft_exthdr_init);
+ nfnl_nft_exthdr_set_dreg(nle, dreg);
+ nfnl_nft_exthdr_set_type(nle, expr->exthdr.desc->type);
+ nfnl_nft_exthdr_set_offset(nle, expr->exthdr.tmpl->offset / BITS_PER_BYTE);
+ nfnl_nft_exthdr_set_len(nle, expr->len / BITS_PER_BYTE);
+ nfnl_nft_rule_add_expr(ctx->nlr, nle);
+}
+
+static void netlink_gen_meta(struct netlink_linearize_ctx *ctx,
+ const struct expr *expr,
+ enum nft_registers dreg)
+{
+ struct nfnl_nft_expr *nle;
+
+ nle = alloc_nft_expr(nfnl_nft_meta_init);
+ nfnl_nft_meta_set_dreg(nle, dreg);
+ nfnl_nft_meta_set_key(nle, expr->meta.key);
+ nfnl_nft_rule_add_expr(ctx->nlr, nle);
+}
+
+static void netlink_gen_ct(struct netlink_linearize_ctx *ctx,
+ const struct expr *expr,
+ enum nft_registers dreg)
+{
+ struct nfnl_nft_expr *nle;
+
+ nle = alloc_nft_expr(nfnl_nft_ct_init);
+ nfnl_nft_ct_set_dreg(nle, dreg);
+ nfnl_nft_ct_set_key(nle, expr->ct.key);
+ nfnl_nft_rule_add_expr(ctx->nlr, nle);
+}
+
+static void netlink_gen_map(struct netlink_linearize_ctx *ctx,
+ const struct expr *expr,
+ enum nft_registers dreg)
+{
+ struct nfnl_nft_expr *nle;
+ struct nfnl_nft_data *data;
+ struct nfnl_nft_data *mapping;
+ const struct expr *i;
+ enum nft_set_elem_flags flags;
+ enum nft_registers sreg;
+ unsigned int klen, dlen;
+
+ assert(expr->mappings->ops->type == EXPR_SET);
+
+ klen = expr->expr->len / BITS_PER_BYTE;
+ dlen = expr->mappings->len / BITS_PER_BYTE;
+ if (dreg == NFT_REG_VERDICT)
+ sreg = get_register(ctx);
+ else
+ sreg = dreg;
+
+ netlink_gen_expr(ctx, expr->expr, sreg);
+
+ nle = alloc_nft_expr(nfnl_nft_set_init);
+ nfnl_nft_set_set_flags(nle, NFT_SET_MAP);
+ nfnl_nft_set_set_sreg(nle, sreg);
+ nfnl_nft_set_set_klen(nle, klen);
+ nfnl_nft_set_set_dreg(nle, dreg);
+ nfnl_nft_set_set_dlen(nle, dlen);
+
+ if (expr->mappings->flags & SET_F_INTERVAL) {
+ set_to_intervals(expr->mappings);
+ nfnl_nft_set_set_flags(nle, NFT_SET_INTERVAL);
+ }
+
+ list_for_each_entry(i, &expr->mappings->expressions, list) {
+ flags = 0;
+
+ switch (i->ops->type) {
+ case EXPR_MAPPING:
+ data = netlink_gen_data(i->left);
+ mapping = netlink_gen_data(i->right);
+ break;
+ case EXPR_VALUE:
+ assert(i->flags & EXPR_F_INTERVAL_END);
+ data = netlink_gen_data(i);
+ mapping = NULL;
+ flags = NFT_SE_INTERVAL_END;
+ break;
+ default:
+ BUG();
+ }
+
+ nfnl_nft_set_add_mapping(nle, data, mapping, flags);
+ }
+
+ if (dreg == NFT_REG_VERDICT)
+ release_register(ctx);
+
+ nfnl_nft_rule_add_expr(ctx->nlr, nle);
+}
+
+static void netlink_gen_lookup(struct netlink_linearize_ctx *ctx,
+ const struct expr *expr,
+ enum nft_registers dreg)
+{
+ struct nfnl_nft_expr *nle;
+ const struct expr *i;
+ enum nft_set_elem_flags flags;
+ enum nft_registers sreg;
+
+ assert(expr->right->ops->type == EXPR_SET);
+ assert(dreg == NFT_REG_VERDICT);
+
+ sreg = get_register(ctx);
+ netlink_gen_expr(ctx, expr->left, sreg);
+
+ nle = alloc_nft_expr(nfnl_nft_set_init);
+ nfnl_nft_set_set_sreg(nle, sreg);
+ nfnl_nft_set_set_klen(nle, expr->left->len / BITS_PER_BYTE);
+
+ if (expr->right->flags & SET_F_INTERVAL) {
+ set_to_intervals(expr->right);
+ nfnl_nft_set_set_flags(nle, NFT_SET_INTERVAL);
+ }
+
+ list_for_each_entry(i, &expr->right->expressions, list) {
+ assert(i->ops->type == EXPR_VALUE);
+
+ flags = 0;
+ if (i->flags & EXPR_F_INTERVAL_END)
+ flags = NFT_SE_INTERVAL_END;
+
+ nfnl_nft_set_add_elem(nle, netlink_gen_data(i), flags);
+ }
+
+ release_register(ctx);
+ nfnl_nft_rule_add_expr(ctx->nlr, nle);
+}
+
+static enum nft_cmp_ops netlink_gen_cmp_op(enum ops op)
+{
+ switch (op) {
+ case OP_EQ:
+ return NFT_CMP_EQ;
+ case OP_NEQ:
+ return NFT_CMP_NEQ;
+ case OP_LT:
+ return NFT_CMP_LT;
+ case OP_GT:
+ return NFT_CMP_GT;
+ case OP_LTE:
+ return NFT_CMP_LTE;
+ case OP_GTE:
+ return NFT_CMP_GTE;
+ default:
+ BUG();
+ }
+}
+
+static void netlink_gen_cmp(struct netlink_linearize_ctx *ctx,
+ const struct expr *expr,
+ enum nft_registers dreg)
+{
+ struct nfnl_nft_expr *nle;
+ enum nft_registers sreg;
+
+ assert(dreg == NFT_REG_VERDICT);
+
+ sreg = get_register(ctx);
+ netlink_gen_expr(ctx, expr->left, sreg);
+
+ nle = alloc_nft_expr(nfnl_nft_cmp_init);
+ nfnl_nft_cmp_set_sreg(nle, sreg);
+ nfnl_nft_cmp_set_op(nle, netlink_gen_cmp_op(expr->op));
+ nfnl_nft_cmp_set_data(nle, netlink_gen_data(expr->right));
+ release_register(ctx);
+
+ nfnl_nft_rule_add_expr(ctx->nlr, nle);
+}
+
+static void netlink_gen_range(struct netlink_linearize_ctx *ctx,
+ const struct expr *expr,
+ enum nft_registers dreg)
+{
+ struct expr *range = expr->right;
+ struct nfnl_nft_expr *nle;
+ enum nft_registers sreg;
+
+ assert(dreg == NFT_REG_VERDICT);
+
+ sreg = get_register(ctx);
+ netlink_gen_expr(ctx, expr->left, sreg);
+
+ nle = alloc_nft_expr(nfnl_nft_cmp_init);
+ nfnl_nft_cmp_set_sreg(nle, sreg);
+ nfnl_nft_cmp_set_op(nle, netlink_gen_cmp_op(OP_GTE));
+ nfnl_nft_cmp_set_data(nle, netlink_gen_data(range->left));
+ nfnl_nft_rule_add_expr(ctx->nlr, nle);
+
+ nle = alloc_nft_expr(nfnl_nft_cmp_init);
+ nfnl_nft_cmp_set_sreg(nle, sreg);
+ nfnl_nft_cmp_set_op(nle, netlink_gen_cmp_op(OP_LTE));
+ nfnl_nft_cmp_set_data(nle, netlink_gen_data(range->right));
+ nfnl_nft_rule_add_expr(ctx->nlr, nle);
+
+ release_register(ctx);
+}
+
+static void netlink_gen_flagcmp(struct netlink_linearize_ctx *ctx,
+ const struct expr *expr,
+ enum nft_registers dreg)
+{
+ struct nfnl_nft_expr *nle;
+ struct nfnl_nft_data *nld;
+ enum nft_registers sreg;
+ unsigned int len;
+ mpz_t zero;
+
+ assert(dreg == NFT_REG_VERDICT);
+
+ sreg = get_register(ctx);
+ netlink_gen_expr(ctx, expr->left, sreg);
+ len = div_round_up(expr->left->len, BITS_PER_BYTE);
+
+ mpz_init_set_ui(zero, 0);
+
+ nle = alloc_nft_expr(nfnl_nft_bitwise_init);
+ nld = netlink_gen_mpz_data(zero, expr->right->byteorder, len);
+ nfnl_nft_bitwise_set_sreg(nle, sreg);
+ nfnl_nft_bitwise_set_dreg(nle, sreg);
+ nfnl_nft_bitwise_set_len(nle, len);
+ nfnl_nft_bitwise_set_mask(nle, netlink_gen_data(expr->right));
+ nfnl_nft_bitwise_set_xor(nle, nld);
+ nfnl_nft_rule_add_expr(ctx->nlr, nle);
+
+ nle = alloc_nft_expr(nfnl_nft_cmp_init);
+ nld = netlink_gen_mpz_data(zero, expr->right->byteorder, len);
+ nfnl_nft_cmp_set_sreg(nle, sreg);
+ nfnl_nft_cmp_set_op(nle, NFT_CMP_NEQ);
+ nfnl_nft_cmp_set_data(nle, nld);
+ nfnl_nft_rule_add_expr(ctx->nlr, nle);
+
+ mpz_clear(zero);
+ release_register(ctx);
+}
+
+static void netlink_gen_relational(struct netlink_linearize_ctx *ctx,
+ const struct expr *expr,
+ enum nft_registers dreg)
+{
+ switch (expr->op) {
+ case OP_EQ:
+ case OP_NEQ:
+ case OP_LT:
+ case OP_GT:
+ case OP_LTE:
+ case OP_GTE:
+ return netlink_gen_cmp(ctx, expr, dreg);
+ case OP_RANGE:
+ return netlink_gen_range(ctx, expr, dreg);
+ case OP_FLAGCMP:
+ return netlink_gen_flagcmp(ctx, expr, dreg);
+ case OP_LOOKUP:
+ return netlink_gen_lookup(ctx, expr, dreg);
+ default:
+ BUG();
+ }
+}
+
+static void combine_binop(mpz_t mask, mpz_t xor, const mpz_t m, const mpz_t x)
+{
+ /* xor = x ^ (xor & m) */
+ mpz_and(xor, xor, m);
+ mpz_xor(xor, x, xor);
+ /* mask &= m */
+ mpz_and(mask, mask, m);
+}
+
+static void netlink_gen_binop(struct netlink_linearize_ctx *ctx,
+ const struct expr *expr,
+ enum nft_registers dreg)
+{
+ struct nfnl_nft_expr *nle;
+ struct nfnl_nft_data *nld;
+ struct expr *left, *i;
+ struct expr *binops[16];
+ mpz_t mask, xor, val, tmp;
+ unsigned int len;
+ int n = 0;
+
+ mpz_init(mask);
+ mpz_init(xor);
+ mpz_init(val);
+ mpz_init(tmp);
+
+ binops[n++] = left = (void *)expr;
+ while (left->ops->type == EXPR_BINOP && left->left != NULL)
+ binops[n++] = left = left->left;
+ n--;
+
+ netlink_gen_expr(ctx, binops[n--], dreg);
+
+ mpz_bitmask(mask, expr->len);
+ mpz_set_ui(xor, 0);
+ for (; n >= 0; n--) {
+ i = binops[n];
+ mpz_set(val, i->right->value);
+
+ switch (i->op) {
+ case OP_AND:
+ mpz_set_ui(tmp, 0);
+ combine_binop(mask, xor, val, tmp);
+ break;
+ case OP_OR:
+ mpz_com(tmp, val);
+ combine_binop(mask, xor, tmp, val);
+ break;
+ case OP_XOR:
+ mpz_bitmask(tmp, expr->len);
+ combine_binop(mask, xor, tmp, val);
+ break;
+ default:
+ BUG();
+ }
+ }
+
+ len = div_round_up(expr->len, BITS_PER_BYTE);
+
+ nle = alloc_nft_expr(nfnl_nft_bitwise_init);
+ nfnl_nft_bitwise_set_sreg(nle, dreg);
+ nfnl_nft_bitwise_set_dreg(nle, dreg);
+ nfnl_nft_bitwise_set_len(nle, len);
+
+ nld = netlink_gen_mpz_data(mask, expr->byteorder, len);
+ nfnl_nft_bitwise_set_mask(nle, nld);
+
+ nld = netlink_gen_mpz_data(xor, expr->byteorder, len);
+ nfnl_nft_bitwise_set_xor(nle, nld);
+
+ mpz_clear(tmp);
+ mpz_clear(val);
+ mpz_clear(xor);
+ mpz_clear(mask);
+
+ nfnl_nft_rule_add_expr(ctx->nlr, nle);
+}
+
+static enum nft_byteorder_ops netlink_gen_unary_op(enum ops op)
+{
+ switch (op) {
+ case OP_HTON:
+ return NFT_BYTEORDER_HTON;
+ case OP_NTOH:
+ return NFT_BYTEORDER_HTON;
+ default:
+ BUG();
+ }
+}
+
+static void netlink_gen_unary(struct netlink_linearize_ctx *ctx,
+ const struct expr *expr,
+ enum nft_registers dreg)
+{
+ struct nfnl_nft_expr *nle;
+
+ netlink_gen_expr(ctx, expr->arg, dreg);
+
+ nle = alloc_nft_expr(nfnl_nft_byteorder_init);
+ nfnl_nft_byteorder_set_sreg(nle, dreg);
+ nfnl_nft_byteorder_set_dreg(nle, dreg);
+ printf("%u\n", expr->len);
+ nfnl_nft_byteorder_set_len(nle, expr->len / BITS_PER_BYTE);
+ printf("%u\n", expr->arg->len);
+ nfnl_nft_byteorder_set_size(nle, expr->arg->len % 32 ? 2 : 4);
+ nfnl_nft_byteorder_set_op(nle, netlink_gen_unary_op(expr->op));
+ nfnl_nft_rule_add_expr(ctx->nlr, nle);
+}
+
+static void netlink_gen_immediate(struct netlink_linearize_ctx *ctx,
+ const struct expr *expr,
+ enum nft_registers dreg)
+{
+ struct nfnl_nft_expr *nle;
+
+ nle = alloc_nft_expr(nfnl_nft_immediate_init);
+ nfnl_nft_immediate_set_dreg(nle, dreg);
+ nfnl_nft_immediate_set_data(nle, netlink_gen_data(expr));
+ nfnl_nft_rule_add_expr(ctx->nlr, nle);
+}
+
+static void netlink_gen_expr(struct netlink_linearize_ctx *ctx,
+ const struct expr *expr,
+ enum nft_registers dreg)
+{
+ switch (expr->ops->type) {
+ case EXPR_VERDICT:
+ case EXPR_VALUE:
+ return netlink_gen_immediate(ctx, expr, dreg);
+ case EXPR_UNARY:
+ return netlink_gen_unary(ctx, expr, dreg);
+ case EXPR_BINOP:
+ return netlink_gen_binop(ctx, expr, dreg);
+ case EXPR_RELATIONAL:
+ return netlink_gen_relational(ctx, expr, dreg);
+ case EXPR_CONCAT:
+ return netlink_gen_concat(ctx, expr, dreg);
+ case EXPR_MAP:
+ return netlink_gen_map(ctx, expr, dreg);
+ case EXPR_PAYLOAD:
+ return netlink_gen_payload(ctx, expr, dreg);
+ case EXPR_EXTHDR:
+ return netlink_gen_exthdr(ctx, expr, dreg);
+ case EXPR_META:
+ return netlink_gen_meta(ctx, expr, dreg);
+ case EXPR_CT:
+ return netlink_gen_ct(ctx, expr, dreg);
+ default:
+ BUG();
+ }
+}
+
+static void netlink_gen_verdict_stmt(struct netlink_linearize_ctx *ctx,
+ const struct stmt *stmt)
+{
+ return netlink_gen_expr(ctx, stmt->expr, NFT_REG_VERDICT);
+}
+
+static void netlink_gen_counter_stmt(struct netlink_linearize_ctx *ctx,
+ const struct stmt *stmt)
+{
+ struct nfnl_nft_expr *nle;
+
+ nle = alloc_nft_expr(nfnl_nft_counter_init);
+ nfnl_nft_rule_add_expr(ctx->nlr, nle);
+}
+
+static void netlink_gen_meta_stmt(struct netlink_linearize_ctx *ctx,
+ const struct stmt *stmt)
+{
+ struct nfnl_nft_expr *nle;
+ enum nft_registers sreg;
+
+ sreg = get_register(ctx);
+ netlink_gen_expr(ctx, stmt->meta.expr, sreg);
+ release_register(ctx);
+
+ nle = alloc_nft_expr(nfnl_nft_meta_init);
+ nfnl_nft_rule_add_expr(ctx->nlr, nle);
+}
+
+static void netlink_gen_log_stmt(struct netlink_linearize_ctx *ctx,
+ const struct stmt *stmt)
+{
+ struct nfnl_nft_expr *nle;
+
+ nle = alloc_nft_expr(nfnl_nft_log_init);
+ if (stmt->log.prefix != NULL)
+ nfnl_nft_log_set_prefix(nle, stmt->log.prefix);
+ if (stmt->log.group)
+ nfnl_nft_log_set_group(nle, stmt->log.group);
+ if (stmt->log.snaplen)
+ nfnl_nft_log_set_snaplen(nle, stmt->log.snaplen);
+ if (stmt->log.qthreshold)
+ nfnl_nft_log_set_qthreshold(nle, stmt->log.qthreshold);
+ nfnl_nft_rule_add_expr(ctx->nlr, nle);
+}
+
+static void netlink_gen_limit_stmt(struct netlink_linearize_ctx *ctx,
+ const struct stmt *stmt)
+{
+ struct nfnl_nft_expr *nle;
+
+ nle = alloc_nft_expr(nfnl_nft_limit_init);
+ nfnl_nft_limit_set_rate(nle, stmt->limit.rate);
+ nfnl_nft_limit_set_depth(nle, stmt->limit.depth);
+ nfnl_nft_rule_add_expr(ctx->nlr, nle);
+}
+
+static void netlink_gen_reject_stmt(struct netlink_linearize_ctx *ctx,
+ const struct stmt *stmt)
+{
+ struct nfnl_nft_expr *nle;
+
+ nle = alloc_nft_expr(NULL);
+ nfnl_nft_rule_add_expr(ctx->nlr, nle);
+}
+
+static void netlink_gen_nat_stmt(struct netlink_linearize_ctx *ctx,
+ const struct stmt *stmt)
+{
+ struct nfnl_nft_expr *nle;
+ enum nft_registers amin_reg, amax_reg;
+ enum nft_registers pmin_reg, pmax_reg;
+
+ nle = alloc_nft_expr(nfnl_nft_nat_init);
+ nfnl_nft_nat_set_type(nle, stmt->nat.type);
+
+ if (stmt->nat.addr) {
+ switch (stmt->nat.addr->ops->type) {
+ default:
+ amin_reg = amax_reg = get_register(ctx);
+ netlink_gen_expr(ctx, stmt->nat.addr, amin_reg);
+ nfnl_nft_nat_set_sreg_addr_min(nle, amin_reg);
+ release_register(ctx);
+ break;
+ case EXPR_RANGE:
+ amin_reg = get_register(ctx);
+ amax_reg = get_register(ctx);
+ netlink_gen_expr(ctx, stmt->nat.addr->left, amin_reg);
+ netlink_gen_expr(ctx, stmt->nat.addr->right, amax_reg);
+ nfnl_nft_nat_set_sreg_addr_min(nle, amin_reg);
+ nfnl_nft_nat_set_sreg_addr_max(nle, amax_reg);
+ release_register(ctx);
+ release_register(ctx);
+ break;
+ }
+
+ }
+
+ if (stmt->nat.proto) {
+ switch (stmt->nat.proto->ops->type) {
+ default:
+ pmin_reg = pmax_reg = get_register(ctx);
+ netlink_gen_expr(ctx, stmt->nat.proto, pmin_reg);
+ nfnl_nft_nat_set_sreg_proto_min(nle, pmin_reg);
+ release_register(ctx);
+ break;
+ case EXPR_RANGE:
+ pmin_reg = get_register(ctx);
+ pmax_reg = get_register(ctx);
+ netlink_gen_expr(ctx, stmt->nat.proto->left, pmin_reg);
+ netlink_gen_expr(ctx, stmt->nat.proto->right, pmax_reg);
+ nfnl_nft_nat_set_sreg_proto_min(nle, pmin_reg);
+ nfnl_nft_nat_set_sreg_proto_max(nle, pmax_reg);
+ release_register(ctx);
+ release_register(ctx);
+ break;
+ }
+ }
+
+ nfnl_nft_rule_add_expr(ctx->nlr, nle);
+}
+
+static void netlink_gen_stmt(struct netlink_linearize_ctx *ctx,
+ const struct stmt *stmt)
+{
+ switch (stmt->ops->type) {
+ case STMT_EXPRESSION:
+ return netlink_gen_expr(ctx, stmt->expr, NFT_REG_VERDICT);
+ case STMT_VERDICT:
+ return netlink_gen_verdict_stmt(ctx, stmt);
+ case STMT_COUNTER:
+ return netlink_gen_counter_stmt(ctx, stmt);
+ case STMT_META:
+ return netlink_gen_meta_stmt(ctx, stmt);
+ case STMT_LOG:
+ return netlink_gen_log_stmt(ctx, stmt);
+ case STMT_LIMIT:
+ return netlink_gen_limit_stmt(ctx, stmt);
+ case STMT_REJECT:
+ return netlink_gen_reject_stmt(ctx, stmt);
+ case STMT_NAT:
+ return netlink_gen_nat_stmt(ctx, stmt);
+ default:
+ BUG();
+ }
+}
+
+int netlink_linearize_rule(struct netlink_ctx *ctx, struct nfnl_nft_rule *nlr,
+ const struct rule *rule)
+{
+ struct netlink_linearize_ctx lctx;
+ const struct stmt *stmt;
+
+ memset(&lctx, 0, sizeof(lctx));
+ lctx.reg_low = NFT_REG_1;
+ lctx.nlr = nlr;
+
+ list_for_each_entry(stmt, &rule->stmts, list)
+ netlink_gen_stmt(&lctx, stmt);
+
+ netlink_dump_object((struct nl_object *)nlr);
+ return 0;
+}
diff --git a/src/parser-skeleton.c b/src/parser-skeleton.c
new file mode 100644
index 00000000..978f7c48
--- /dev/null
+++ b/src/parser-skeleton.c
@@ -0,0 +1,1529 @@
+m4_divert(-1) -*- C -*-
+
+# Yacc compatible skeleton for Bison
+
+# Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004, 2005, 2006
+# Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 USA
+
+m4_include(b4_pkgdatadir/[c.m4])
+
+## ---------------- ##
+## Default values. ##
+## ---------------- ##
+
+# Stack parameters.
+m4_define_default([b4_stack_depth_max], [10000])
+m4_define_default([b4_stack_depth_init], [200])
+
+
+## ------------------------ ##
+## Pure/impure interfaces. ##
+## ------------------------ ##
+
+
+# b4_yacc_pure_if(IF-TRUE, IF-FALSE)
+# ----------------------------------
+# Expand IF-TRUE, if %pure-parser and %parse-param, IF-FALSE otherwise.
+m4_define([b4_yacc_pure_if],
+[b4_pure_if([m4_ifset([b4_parse_param],
+ [$1], [$2])],
+ [$2])])
+
+
+# b4_yyerror_args
+# ---------------
+# Arguments passed to yyerror: user args plus yylloc.
+m4_define([b4_yyerror_args],
+[b4_yacc_pure_if([b4_locations_if([&yylloc, ])])dnl
+m4_ifset([b4_parse_param], [b4_c_args(b4_parse_param), ])])
+
+
+# b4_lex_param
+# ------------
+# Accumulate in b4_lex_param all the yylex arguments.
+# b4_lex_param arrives quoted twice, but we want to keep only one level.
+m4_define([b4_lex_param],
+m4_dquote(b4_pure_if([[[[YYSTYPE *]], [[&yylval]]][]dnl
+b4_locations_if([, [[YYLTYPE *], [&yylloc]]])m4_ifdef([b4_lex_param], [, ])])dnl
+m4_ifdef([b4_lex_param], b4_lex_param)))
+
+
+
+## ------------ ##
+## Data Types. ##
+## ------------ ##
+
+# b4_int_type(MIN, MAX)
+# ---------------------
+# Return the smallest int type able to handle numbers ranging from
+# MIN to MAX (included). Overwrite the version from c.m4, which
+# uses only C89 types, so that the user can override the shorter
+# types, and so that pre-C89 compilers are handled correctly.
+m4_define([b4_int_type],
+[m4_if(b4_ints_in($@, [0], [255]), [1], [yytype_uint8],
+ b4_ints_in($@, [-128], [127]), [1], [yytype_int8],
+
+ b4_ints_in($@, [0], [65535]), [1], [yytype_uint16],
+ b4_ints_in($@, [-32768], [32767]), [1], [yytype_int16],
+
+ m4_eval([0 <= $1]), [1], [unsigned int],
+
+ [int])])
+
+
+## ----------------- ##
+## Semantic Values. ##
+## ----------------- ##
+
+
+# b4_lhs_value([TYPE])
+# --------------------
+# Expansion of $<TYPE>$.
+m4_define([b4_lhs_value],
+[(yyval[]m4_ifval([$1], [.$1]))])
+
+
+# b4_rhs_value(RULE-LENGTH, NUM, [TYPE])
+# --------------------------------------
+# Expansion of $<TYPE>NUM, where the current rule has RULE-LENGTH
+# symbols on RHS.
+m4_define([b4_rhs_value],
+[(yyvsp@{($2) - ($1)@}m4_ifval([$3], [.$3]))])
+
+
+
+## ----------- ##
+## Locations. ##
+## ----------- ##
+
+# b4_lhs_location()
+# -----------------
+# Expansion of @$.
+m4_define([b4_lhs_location],
+[(yyloc)])
+
+
+# b4_rhs_location(RULE-LENGTH, NUM)
+# ---------------------------------
+# Expansion of @NUM, where the current rule has RULE-LENGTH symbols
+# on RHS.
+m4_define([b4_rhs_location],
+[(yylsp@{($2) - ($1)@})])
+
+
+
+## --------------------------------------------------------- ##
+## Defining symbol actions, e.g., printers and destructors. ##
+## --------------------------------------------------------- ##
+
+# We do want M4 expansion after # for CPP macros.
+m4_changecom()
+m4_divert(0)dnl
+@output @output_parser_name@
+b4_copyright([Skeleton implementation for Bison's Yacc-like parsers in C],dnl '
+ [1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004, 2005, 2006])[
+
+/* C LALR(1) parser skeleton written by Richard Stallman, by
+ simplifying the original so-called "semantic" parser. */
+
+/* All symbols defined below should begin with yy or YY, to avoid
+ infringing on user name space. This should be done even for local
+ variables, as they might otherwise be expanded by user macros.
+ There are some unavoidable exceptions within include files to
+ define necessary library symbols; they are noted "INFRINGES ON
+ USER NAME SPACE" below. */
+
+]b4_identification
+m4_if(b4_prefix, [yy], [],
+[/* Substitute the variable and function names. */
+#define yyparse b4_prefix[]parse
+#define yylex b4_prefix[]lex
+#define yyerror b4_prefix[]error
+#define yylval b4_prefix[]lval
+#define yychar b4_prefix[]char
+#define yydebug b4_prefix[]debug
+#define yynerrs b4_prefix[]nerrs
+b4_locations_if([#define yylloc b4_prefix[]lloc])])[
+
+]b4_token_enums_defines(b4_tokens)[
+
+/* Copy the first part of user declarations. */
+]b4_pre_prologue[
+
+/* Enabling traces. */
+#ifndef YYDEBUG
+# define YYDEBUG ]b4_debug_flag[
+#endif
+
+/* Enabling verbose error messages. */
+#ifdef YYERROR_VERBOSE
+# undef YYERROR_VERBOSE
+# define YYERROR_VERBOSE 1
+#else
+# define YYERROR_VERBOSE ]b4_error_verbose_flag[
+#endif
+
+/* Enabling the token table. */
+#ifndef YYTOKEN_TABLE
+# define YYTOKEN_TABLE ]b4_token_table[
+#endif
+
+#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED
+]m4_ifdef([b4_stype],
+[typedef union[]m4_bregexp(b4_stype, [^{], [ YYSTYPE])
+b4_stype
+/* Line __line__ of yacc.c. */
+b4_syncline([@oline@], [@ofile@])
+ YYSTYPE;],
+[typedef int YYSTYPE;])[
+# define yystype YYSTYPE /* obsolescent; will be withdrawn */
+# define YYSTYPE_IS_DECLARED 1
+# define YYSTYPE_IS_TRIVIAL 1
+#endif
+
+]b4_locations_if([#if ! defined YYLTYPE && ! defined YYLTYPE_IS_DECLARED
+typedef struct YYLTYPE
+{
+ int first_line;
+ int first_column;
+ int last_line;
+ int last_column;
+} YYLTYPE;
+# define yyltype YYLTYPE /* obsolescent; will be withdrawn */
+# define YYLTYPE_IS_DECLARED 1
+# define YYLTYPE_IS_TRIVIAL 1
+#endif
+])[
+
+/* Copy the second part of user declarations. */
+]b4_post_prologue
+
+/* Line __line__ of yacc.c. */
+b4_syncline([@oline@], [@ofile@])[
+
+#ifdef short
+# undef short
+#endif
+
+#ifdef YYTYPE_UINT8
+typedef YYTYPE_UINT8 yytype_uint8;
+#else
+typedef unsigned char yytype_uint8;
+#endif
+
+#ifdef YYTYPE_INT8
+typedef YYTYPE_INT8 yytype_int8;
+#elif ]b4_c_modern[
+typedef signed char yytype_int8;
+#else
+typedef short int yytype_int8;
+#endif
+
+#ifdef YYTYPE_UINT16
+typedef YYTYPE_UINT16 yytype_uint16;
+#else
+typedef unsigned short int yytype_uint16;
+#endif
+
+#ifdef YYTYPE_INT16
+typedef YYTYPE_INT16 yytype_int16;
+#else
+typedef short int yytype_int16;
+#endif
+
+#ifndef YYSIZE_T
+# ifdef __SIZE_TYPE__
+# define YYSIZE_T __SIZE_TYPE__
+# elif defined size_t
+# define YYSIZE_T size_t
+# elif ! defined YYSIZE_T && ]b4_c_modern[
+# include <stddef.h> /* INFRINGES ON USER NAME SPACE */
+# define YYSIZE_T size_t
+# else
+# define YYSIZE_T unsigned int
+# endif
+#endif
+
+#define YYSIZE_MAXIMUM ((YYSIZE_T) -1)
+
+#ifndef YY_
+# if YYENABLE_NLS
+# if ENABLE_NLS
+# include <libintl.h> /* INFRINGES ON USER NAME SPACE */
+# define YY_(msgid) dgettext ("bison-runtime", msgid)
+# endif
+# endif
+# ifndef YY_
+# define YY_(msgid) msgid
+# endif
+#endif
+
+/* Suppress unused-variable warnings by "using" E. */
+#if ! defined lint || defined __GNUC__
+# define YYUSE(e) ((void) (e))
+#else
+# define YYUSE(e) /* empty */
+#endif
+
+/* Identity function, used to suppress warnings about constant conditions. */
+#ifndef lint
+# define YYID(n) (n)
+#else
+]b4_c_function_def([YYID], [static int], [[int i], [i]])[
+{
+ return i;
+}
+#endif
+
+#if ! defined yyoverflow || YYERROR_VERBOSE
+
+/* The parser invokes alloca or malloc; define the necessary symbols. */
+
+# ifdef YYSTACK_USE_ALLOCA
+# if YYSTACK_USE_ALLOCA
+# ifdef __GNUC__
+# define YYSTACK_ALLOC __builtin_alloca
+# elif defined __BUILTIN_VA_ARG_INCR
+# include <alloca.h> /* INFRINGES ON USER NAME SPACE */
+# elif defined _AIX
+# define YYSTACK_ALLOC __alloca
+# elif defined _MSC_VER
+# include <malloc.h> /* INFRINGES ON USER NAME SPACE */
+# define alloca _alloca
+# else
+# define YYSTACK_ALLOC alloca
+# if ! defined _ALLOCA_H && ! defined _STDLIB_H && ]b4_c_modern[
+# include <stdlib.h> /* INFRINGES ON USER NAME SPACE */
+# ifndef _STDLIB_H
+# define _STDLIB_H 1
+# endif
+# endif
+# endif
+# endif
+# endif
+
+# ifdef YYSTACK_ALLOC
+ /* Pacify GCC's `empty if-body' warning. */
+# define YYSTACK_FREE(Ptr) do { /* empty */; } while (YYID (0))
+# ifndef YYSTACK_ALLOC_MAXIMUM
+ /* The OS might guarantee only one guard page at the bottom of the stack,
+ and a page size can be as small as 4096 bytes. So we cannot safely
+ invoke alloca (N) if N exceeds 4096. Use a slightly smaller number
+ to allow for a few compiler-allocated temporary stack slots. */
+# define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */
+# endif
+# else
+# define YYSTACK_ALLOC YYMALLOC
+# define YYSTACK_FREE YYFREE
+# ifndef YYSTACK_ALLOC_MAXIMUM
+# define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM
+# endif
+# if (defined __cplusplus && ! defined _STDLIB_H \
+ && ! ((defined YYMALLOC || defined malloc) \
+ && (defined YYFREE || defined free)))
+# include <stdlib.h> /* INFRINGES ON USER NAME SPACE */
+# ifndef _STDLIB_H
+# define _STDLIB_H 1
+# endif
+# endif
+# ifndef YYMALLOC
+# define YYMALLOC malloc
+# if ! defined malloc && ! defined _STDLIB_H && ]b4_c_modern[
+void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */
+# endif
+# endif
+# ifndef YYFREE
+# define YYFREE free
+# if ! defined free && ! defined _STDLIB_H && ]b4_c_modern[
+void free (void *); /* INFRINGES ON USER NAME SPACE */
+# endif
+# endif
+# endif
+#endif /* ! defined yyoverflow || YYERROR_VERBOSE */
+
+
+#if (! defined yyoverflow \
+ && (! defined __cplusplus \
+ || (]b4_locations_if([[defined YYLTYPE_IS_TRIVIAL && YYLTYPE_IS_TRIVIAL \
+ && ]])[defined YYSTYPE_IS_TRIVIAL && YYSTYPE_IS_TRIVIAL)))
+
+/* A type that is properly aligned for any stack member. */
+union yyalloc
+{
+ yytype_int16 yyss;
+ YYSTYPE yyvs;
+ ]b4_locations_if([ YYLTYPE yyls;
+])dnl
+[};
+
+/* The size of the maximum gap between one aligned stack and the next. */
+# define YYSTACK_GAP_MAXIMUM (sizeof (union yyalloc) - 1)
+
+/* The size of an array large to enough to hold all stacks, each with
+ N elements. */
+]b4_locations_if(
+[# define YYSTACK_BYTES(N) \
+ ((N) * (sizeof (yytype_int16) + sizeof (YYSTYPE) + sizeof (YYLTYPE)) \
+ + 2 * YYSTACK_GAP_MAXIMUM)],
+[# define YYSTACK_BYTES(N) \
+ ((N) * (sizeof (yytype_int16) + sizeof (YYSTYPE)) \
+ + YYSTACK_GAP_MAXIMUM)])[
+
+/* Copy COUNT objects from FROM to TO. The source and destination do
+ not overlap. */
+# ifndef YYCOPY
+# if defined __GNUC__ && 1 < __GNUC__
+# define YYCOPY(To, From, Count) \
+ __builtin_memcpy (To, From, (Count) * sizeof (*(From)))
+# else
+# define YYCOPY(To, From, Count) \
+ do \
+ { \
+ YYSIZE_T yyi; \
+ for (yyi = 0; yyi < (Count); yyi++) \
+ (To)[yyi] = (From)[yyi]; \
+ } \
+ while (YYID (0))
+# endif
+# endif
+
+/* Relocate STACK from its old location to the new one. The
+ local variables YYSIZE and YYSTACKSIZE give the old and new number of
+ elements in the stack, and YYPTR gives the new location of the
+ stack. Advance YYPTR to a properly aligned location for the next
+ stack. */
+# define YYSTACK_RELOCATE(Stack) \
+ do \
+ { \
+ YYSIZE_T yynewbytes; \
+ YYCOPY (&yyptr->Stack, Stack, yysize); \
+ Stack = &yyptr->Stack; \
+ yynewbytes = yystacksize * sizeof (*Stack) + YYSTACK_GAP_MAXIMUM; \
+ yyptr += yynewbytes / sizeof (*yyptr); \
+ } \
+ while (YYID (0))
+
+#endif
+
+/* YYFINAL -- State number of the termination state. */
+#define YYFINAL ]b4_final_state_number[
+/* YYLAST -- Last index in YYTABLE. */
+#define YYLAST ]b4_last[
+
+/* YYNTOKENS -- Number of terminals. */
+#define YYNTOKENS ]b4_tokens_number[
+/* YYNNTS -- Number of nonterminals. */
+#define YYNNTS ]b4_nterms_number[
+/* YYNRULES -- Number of rules. */
+#define YYNRULES ]b4_rules_number[
+/* YYNRULES -- Number of states. */
+#define YYNSTATES ]b4_states_number[
+
+/* YYTRANSLATE(YYLEX) -- Bison symbol number corresponding to YYLEX. */
+#define YYUNDEFTOK ]b4_undef_token_number[
+#define YYMAXUTOK ]b4_user_token_number_max[
+
+#define YYTRANSLATE(YYX) \
+ ((unsigned int) (YYX) <= YYMAXUTOK ? yytranslate[YYX] : YYUNDEFTOK)
+
+/* YYTRANSLATE[YYLEX] -- Bison symbol number corresponding to YYLEX. */
+static const ]b4_int_type_for([b4_translate])[ yytranslate[] =
+{
+ ]b4_translate[
+};
+
+#if YYDEBUG
+/* YYPRHS[YYN] -- Index of the first RHS symbol of rule number YYN in
+ YYRHS. */
+static const ]b4_int_type_for([b4_prhs])[ yyprhs[] =
+{
+ ]b4_prhs[
+};
+
+/* YYRHS -- A `-1'-separated list of the rules' RHS. */
+static const ]b4_int_type_for([b4_rhs])[ yyrhs[] =
+{
+ ]b4_rhs[
+};
+
+/* YYRLINE[YYN] -- source line where rule number YYN was defined. */
+static const ]b4_int_type_for([b4_rline])[ yyrline[] =
+{
+ ]b4_rline[
+};
+#endif
+
+#if YYDEBUG || YYERROR_VERBOSE || YYTOKEN_TABLE
+/* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM.
+ First, the terminals, then, starting at YYNTOKENS, nonterminals. */
+static const char *const yytname[] =
+{
+ ]b4_tname[
+};
+#endif
+
+# ifdef YYPRINT
+/* YYTOKNUM[YYLEX-NUM] -- Internal token number corresponding to
+ token YYLEX-NUM. */
+static const ]b4_int_type_for([b4_toknum])[ yytoknum[] =
+{
+ ]b4_toknum[
+};
+# endif
+
+/* YYR1[YYN] -- Symbol number of symbol that rule YYN derives. */
+static const ]b4_int_type_for([b4_r1])[ yyr1[] =
+{
+ ]b4_r1[
+};
+
+/* YYR2[YYN] -- Number of symbols composing right hand side of rule YYN. */
+static const ]b4_int_type_for([b4_r2])[ yyr2[] =
+{
+ ]b4_r2[
+};
+
+/* YYDEFACT[STATE-NAME] -- Default rule to reduce with in state
+ STATE-NUM when YYTABLE doesn't specify something else to do. Zero
+ means the default is an error. */
+static const ]b4_int_type_for([b4_defact])[ yydefact[] =
+{
+ ]b4_defact[
+};
+
+/* YYDEFGOTO[NTERM-NUM]. */
+static const ]b4_int_type_for([b4_defgoto])[ yydefgoto[] =
+{
+ ]b4_defgoto[
+};
+
+/* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing
+ STATE-NUM. */
+#define YYPACT_NINF ]b4_pact_ninf[
+static const ]b4_int_type_for([b4_pact])[ yypact[] =
+{
+ ]b4_pact[
+};
+
+/* YYPGOTO[NTERM-NUM]. */
+static const ]b4_int_type_for([b4_pgoto])[ yypgoto[] =
+{
+ ]b4_pgoto[
+};
+
+/* YYTABLE[YYPACT[STATE-NUM]]. What to do in state STATE-NUM. If
+ positive, shift that token. If negative, reduce the rule which
+ number is the opposite. If zero, do what YYDEFACT says.
+ If YYTABLE_NINF, syntax error. */
+#define YYTABLE_NINF ]b4_table_ninf[
+static const ]b4_int_type_for([b4_table])[ yytable[] =
+{
+ ]b4_table[
+};
+
+static const ]b4_int_type_for([b4_check])[ yycheck[] =
+{
+ ]b4_check[
+};
+
+/* YYSTOS[STATE-NUM] -- The (internal number of the) accessing
+ symbol of state STATE-NUM. */
+static const ]b4_int_type_for([b4_stos])[ yystos[] =
+{
+ ]b4_stos[
+};
+
+#define yyerrok (yyerrstatus = 0)
+#define yyclearin (yychar = YYEMPTY)
+#define YYEMPTY (-2)
+#define YYEOF 0
+
+#define YYACCEPT goto yyacceptlab
+#define YYABORT goto yyabortlab
+#define YYERROR goto yyerrorlab
+
+
+/* Like YYERROR except do call yyerror. This remains here temporarily
+ to ease the transition to the new meaning of YYERROR, for GCC.
+ Once GCC version 2 has supplanted version 1, this can go. */
+
+#define YYFAIL goto yyerrlab
+
+#define YYRECOVERING() (!!yyerrstatus)
+
+#define YYBACKUP(Token, Value) \
+do \
+ if (yychar == YYEMPTY && yylen == 1) \
+ { \
+ yychar = (Token); \
+ yylval = (Value); \
+ yytoken = YYTRANSLATE (yychar); \
+ YYPOPSTACK (1); \
+ goto yybackup; \
+ } \
+ else \
+ { \
+ yyerror (]b4_yyerror_args[YY_("syntax error: cannot back up")); \
+ YYERROR; \
+ } \
+while (YYID (0))
+
+
+#define YYTERROR 1
+#define YYERRCODE 256
+
+
+/* YYLLOC_DEFAULT -- Set CURRENT to span from RHS[1] to RHS[N].
+ If N is 0, then set CURRENT to the empty location which ends
+ the previous symbol: RHS[0] (always defined). */
+
+#define YYRHSLOC(Rhs, K) ((Rhs)[K])
+#ifndef YYLLOC_DEFAULT
+# define YYLLOC_DEFAULT(Current, Rhs, N) \
+ do \
+ if (YYID (N)) \
+ { \
+ (Current).first_line = YYRHSLOC (Rhs, 1).first_line; \
+ (Current).first_column = YYRHSLOC (Rhs, 1).first_column; \
+ (Current).last_line = YYRHSLOC (Rhs, N).last_line; \
+ (Current).last_column = YYRHSLOC (Rhs, N).last_column; \
+ } \
+ else \
+ { \
+ (Current).first_line = (Current).last_line = \
+ YYRHSLOC (Rhs, 0).last_line; \
+ (Current).first_column = (Current).last_column = \
+ YYRHSLOC (Rhs, 0).last_column; \
+ } \
+ while (YYID (0))
+#endif
+
+
+/* YY_LOCATION_PRINT -- Print the location on the stream.
+ This macro was not mandated originally: define only if we know
+ we won't break user code: when these are the locations we know. */
+
+#ifndef YY_LOCATION_PRINT
+# if YYLTYPE_IS_TRIVIAL
+# define YY_LOCATION_PRINT(File, Loc) \
+ fprintf (File, "%d.%d-%d.%d", \
+ (Loc).first_line, (Loc).first_column, \
+ (Loc).last_line, (Loc).last_column)
+# else
+# define YY_LOCATION_PRINT(File, Loc) ((void) 0)
+# endif
+#endif
+
+
+/* YYLEX -- calling `yylex' with the right arguments. */
+
+#ifdef YYLEX_PARAM
+# define YYLEX yylex (]b4_pure_if([&yylval[]b4_locations_if([, &yylloc]), ])[YYLEX_PARAM)
+#else
+# define YYLEX ]b4_c_function_call([yylex], [int], b4_lex_param)[
+#endif
+
+/* Enable debugging if requested. */
+#if YYDEBUG
+
+# ifndef YYFPRINTF
+# include <stdio.h> /* INFRINGES ON USER NAME SPACE */
+# define YYFPRINTF fprintf
+# endif
+
+# define YYDPRINTF(Args) \
+do { \
+ if (yydebug) \
+ YYFPRINTF Args; \
+} while (YYID (0))
+
+# define YY_SYMBOL_PRINT(Title, Type, Value, Location) \
+do { \
+ if (yydebug) \
+ { \
+ YYFPRINTF (stderr, "%s ", Title); \
+ yy_symbol_print (stderr, \
+ Type, Value]b4_locations_if([, Location])[]b4_user_args[); \
+ YYFPRINTF (stderr, "\n"); \
+ } \
+} while (YYID (0))
+
+]b4_yy_symbol_print_generate([b4_c_function_def])[
+
+/*------------------------------------------------------------------.
+| yy_stack_print -- Print the state stack from its BOTTOM up to its |
+| TOP (included). |
+`------------------------------------------------------------------*/
+
+]b4_c_function_def([yy_stack_print], [static void],
+ [[yytype_int16 *bottom], [bottom]],
+ [[yytype_int16 *top], [top]])[
+{
+ YYFPRINTF (stderr, "Stack now");
+ for (; bottom <= top; ++bottom)
+ YYFPRINTF (stderr, " %d", *bottom);
+ YYFPRINTF (stderr, "\n");
+}
+
+# define YY_STACK_PRINT(Bottom, Top) \
+do { \
+ if (yydebug) \
+ yy_stack_print ((Bottom), (Top)); \
+} while (YYID (0))
+
+
+/*------------------------------------------------.
+| Report that the YYRULE is going to be reduced. |
+`------------------------------------------------*/
+
+]b4_c_function_def([yy_reduce_print], [static void],
+ [[YYSTYPE *yyvsp], [yyvsp]],
+ b4_locations_if([[[YYLTYPE *yylsp], [yylsp]],
+ ])[[int yyrule], [yyrule]]m4_ifset([b4_parse_param], [,
+ ])b4_parse_param)[
+{
+ int yynrhs = yyr2[yyrule];
+ int yyi;
+ unsigned long int yylno = yyrline[yyrule];
+ YYFPRINTF (stderr, "Reducing stack by rule %d (line %lu):\n",
+ yyrule - 1, yylno);
+ /* The symbols being reduced. */
+ for (yyi = 0; yyi < yynrhs; yyi++)
+ {
+ fprintf (stderr, " $%d = ", yyi + 1);
+ yy_symbol_print (stderr, yyrhs[yyprhs[yyrule] + yyi],
+ &]b4_rhs_value(yynrhs, yyi + 1)[
+ ]b4_locations_if([, &]b4_rhs_location(yynrhs, yyi + 1))[]dnl
+ b4_user_args[);
+ fprintf (stderr, "\n");
+ }
+}
+
+# define YY_REDUCE_PRINT(Rule) \
+do { \
+ if (yydebug) \
+ yy_reduce_print (yyvsp, ]b4_locations_if([yylsp, ])[Rule]b4_user_args[); \
+} while (YYID (0))
+
+/* Nonzero means print parse trace. It is left uninitialized so that
+ multiple parsers can coexist. */
+int yydebug;
+#else /* !YYDEBUG */
+# define YYDPRINTF(Args)
+# define YY_SYMBOL_PRINT(Title, Type, Value, Location)
+# define YY_STACK_PRINT(Bottom, Top)
+# define YY_REDUCE_PRINT(Rule)
+#endif /* !YYDEBUG */
+
+
+/* YYINITDEPTH -- initial size of the parser's stacks. */
+#ifndef YYINITDEPTH
+# define YYINITDEPTH ]b4_stack_depth_init[
+#endif
+
+/* YYMAXDEPTH -- maximum size the stacks can grow to (effective only
+ if the built-in stack extension method is used).
+
+ Do not make this value too large; the results are undefined if
+ YYSTACK_ALLOC_MAXIMUM < YYSTACK_BYTES (YYMAXDEPTH)
+ evaluated with infinite-precision integer arithmetic. */
+
+#ifndef YYMAXDEPTH
+# define YYMAXDEPTH ]b4_stack_depth_max[
+#endif
+
+
+
+#if YYERROR_VERBOSE
+
+# ifndef yystrlen
+# if defined __GLIBC__ && defined _STRING_H
+# define yystrlen strlen
+# else
+/* Return the length of YYSTR. */
+]b4_c_function_def([yystrlen], [static YYSIZE_T],
+ [[const char *yystr], [yystr]])[
+{
+ YYSIZE_T yylen;
+ for (yylen = 0; yystr[yylen]; yylen++)
+ continue;
+ return yylen;
+}
+# endif
+# endif
+
+# ifndef yystpcpy
+# if defined __GLIBC__ && defined _STRING_H && defined _GNU_SOURCE
+# define yystpcpy stpcpy
+# else
+/* Copy YYSRC to YYDEST, returning the address of the terminating '\0' in
+ YYDEST. */
+]b4_c_function_def([yystpcpy], [static char *],
+ [[char *yydest], [yydest]], [[const char *yysrc], [yysrc]])[
+{
+ char *yyd = yydest;
+ const char *yys = yysrc;
+
+ while ((*yyd++ = *yys++) != '\0')
+ continue;
+
+ return yyd - 1;
+}
+# endif
+# endif
+
+# ifndef yytnamerr
+/* Copy to YYRES the contents of YYSTR after stripping away unnecessary
+ quotes and backslashes, so that it's suitable for yyerror. The
+ heuristic is that double-quoting is unnecessary unless the string
+ contains an apostrophe, a comma, or backslash (other than
+ backslash-backslash). YYSTR is taken from yytname. If YYRES is
+ null, do not copy; instead, return the length of what the result
+ would have been. */
+static YYSIZE_T
+yytnamerr (char *yyres, const char *yystr)
+{
+ if (*yystr == '"')
+ {
+ YYSIZE_T yyn = 0;
+ char const *yyp = yystr;
+
+ for (;;)
+ switch (*++yyp)
+ {
+ case '\'':
+ case ',':
+ goto do_not_strip_quotes;
+
+ case '\\':
+ if (*++yyp != '\\')
+ goto do_not_strip_quotes;
+ /* Fall through. */
+ default:
+ if (yyres)
+ yyres[yyn] = *yyp;
+ yyn++;
+ break;
+
+ case '"':
+ if (yyres)
+ yyres[yyn] = '\0';
+ return yyn;
+ }
+ do_not_strip_quotes: ;
+ }
+
+ if (! yyres)
+ return yystrlen (yystr);
+
+ return yystpcpy (yyres, yystr) - yyres;
+}
+# endif
+
+/* Copy into YYRESULT an error message about the unexpected token
+ YYCHAR while in state YYSTATE. Return the number of bytes copied,
+ including the terminating null byte. If YYRESULT is null, do not
+ copy anything; just return the number of bytes that would be
+ copied. As a special case, return 0 if an ordinary "syntax error"
+ message will do. Return YYSIZE_MAXIMUM if overflow occurs during
+ size calculation. */
+static YYSIZE_T
+yysyntax_error (char *yyresult, int yystate, int yychar)
+{
+ int yyn = yypact[yystate];
+
+ if (! (YYPACT_NINF < yyn && yyn <= YYLAST))
+ return 0;
+ else
+ {
+ int yytype = YYTRANSLATE (yychar);
+ YYSIZE_T yysize0 = yytnamerr (0, yytname[yytype]);
+ YYSIZE_T yysize = yysize0;
+ YYSIZE_T yysize1;
+ int yysize_overflow = 0;
+ enum { YYERROR_VERBOSE_ARGS_MAXIMUM = 100 };
+ char const *yyarg[YYERROR_VERBOSE_ARGS_MAXIMUM];
+ int yyx;
+
+# if 0
+ /* This is so xgettext sees the translatable formats that are
+ constructed on the fly. */
+ YY_("syntax error, unexpected %s");
+ YY_("syntax error, unexpected %s, expecting %s");
+ YY_("syntax error, unexpected %s, expecting %s or %s");
+ YY_("syntax error, unexpected %s, expecting %s or %s or %s");
+ YY_("syntax error, unexpected %s, expecting %s or %s or %s or %s");
+# endif
+ char *yyfmt;
+ char const *yyf;
+ static char const yyunexpected[] = "syntax error, unexpected %s";
+ static char const yyexpecting[] = ", expecting %s";
+ static char const yyor[] = " or %s";
+ char yyformat[sizeof yyunexpected
+ + sizeof yyexpecting - 1
+ + ((YYERROR_VERBOSE_ARGS_MAXIMUM - 2)
+ * (sizeof yyor - 1))];
+ char const *yyprefix = yyexpecting;
+
+ /* Start YYX at -YYN if negative to avoid negative indexes in
+ YYCHECK. */
+ int yyxbegin = yyn < 0 ? -yyn : 0;
+
+ /* Stay within bounds of both yycheck and yytname. */
+ int yychecklim = YYLAST - yyn + 1;
+ int yyxend = yychecklim < YYNTOKENS ? yychecklim : YYNTOKENS;
+ int yycount = 1;
+
+ yyarg[0] = yytname[yytype];
+ yyfmt = yystpcpy (yyformat, yyunexpected);
+
+ for (yyx = yyxbegin; yyx < yyxend; ++yyx)
+ if (yycheck[yyx + yyn] == yyx && yyx != YYTERROR)
+ {
+ if (yycount == YYERROR_VERBOSE_ARGS_MAXIMUM)
+ {
+ yycount = 1;
+ yysize = yysize0;
+ yyformat[sizeof yyunexpected - 1] = '\0';
+ break;
+ }
+ yyarg[yycount++] = yytname[yyx];
+ yysize1 = yysize + yytnamerr (0, yytname[yyx]);
+ yysize_overflow |= (yysize1 < yysize);
+ yysize = yysize1;
+ yyfmt = yystpcpy (yyfmt, yyprefix);
+ yyprefix = yyor;
+ }
+
+ yyf = YY_(yyformat);
+ yysize1 = yysize + yystrlen (yyf);
+ yysize_overflow |= (yysize1 < yysize);
+ yysize = yysize1;
+
+ if (yysize_overflow)
+ return YYSIZE_MAXIMUM;
+
+ if (yyresult)
+ {
+ /* Avoid sprintf, as that infringes on the user's name space.
+ Don't have undefined behavior even if the translation
+ produced a string with the wrong number of "%s"s. */
+ char *yyp = yyresult;
+ int yyi = 0;
+ while ((*yyp = *yyf) != '\0')
+ {
+ if (*yyp == '%' && yyf[1] == 's' && yyi < yycount)
+ {
+ yyp += yytnamerr (yyp, yyarg[yyi++]);
+ yyf += 2;
+ }
+ else
+ {
+ yyp++;
+ yyf++;
+ }
+ }
+ }
+ return yysize;
+ }
+}
+#endif /* YYERROR_VERBOSE */
+
+
+]b4_yydestruct_generate([b4_c_function_def])[
+
+
+/* Prevent warnings from -Wmissing-prototypes. */
+
+#ifdef YYPARSE_PARAM
+]b4_c_function_decl([yyparse], [int],
+ [[void *YYPARSE_PARAM], [YYPARSE_PARAM]])[
+#else /* ! YYPARSE_PARAM */
+]b4_c_function_decl([yyparse], [int], b4_parse_param)[
+#endif /* ! YYPARSE_PARAM */
+
+
+]m4_divert_push([KILL])# ======================== M4 code.
+# b4_declare_parser_variables
+# ---------------------------
+# Declare the variables that are global, or local to YYPARSE if
+# pure-parser.
+m4_define([b4_declare_parser_variables],
+[/* The look-ahead symbol. */
+int yychar;
+
+/* The semantic value of the look-ahead symbol. */
+YYSTYPE yylval;
+
+/* Number of syntax errors so far. */
+int yynerrs;b4_locations_if([
+/* Location data for the look-ahead symbol. */
+YYLTYPE yylloc;])
+])
+m4_divert_pop([KILL])dnl# ====================== End of M4 code.
+
+b4_pure_if([],
+ [b4_declare_parser_variables])
+
+
+/*----------.
+| yyparse. |
+`----------*/
+
+#ifdef YYPARSE_PARAM
+b4_c_function_def([yyparse], [int], [[void *YYPARSE_PARAM], [YYPARSE_PARAM]])
+#else /* ! YYPARSE_PARAM */
+b4_c_function_def([yyparse], [int], b4_parse_param)
+#endif
+{[
+ ]b4_pure_if([b4_declare_parser_variables])[
+ int yystate;
+ int yyn;
+ int yyresult;
+ /* Number of tokens to shift before error messages enabled. */
+ int yyerrstatus;
+ /* Look-ahead token as an internal (translated) token number. */
+ int yytoken = 0;
+#if YYERROR_VERBOSE
+ /* Buffer for error messages, and its allocated size. */
+ char yymsgbuf[128];
+ char *yymsg = yymsgbuf;
+ YYSIZE_T yymsg_alloc = sizeof yymsgbuf;
+#endif
+
+ /* Three stacks and their tools:
+ `yyss': related to states,
+ `yyvs': related to semantic values,
+ `yyls': related to locations.
+
+ Refer to the stacks thru separate pointers, to allow yyoverflow
+ to reallocate them elsewhere. */
+
+ /* The state stack. */
+ yytype_int16 yyssa[YYINITDEPTH];
+ yytype_int16 *yyss = yyssa;
+ yytype_int16 *yyssp;
+
+ /* The semantic value stack. */
+ YYSTYPE yyvsa[YYINITDEPTH];
+ YYSTYPE *yyvs = yyvsa;
+ YYSTYPE *yyvsp;
+
+]b4_locations_if(
+[[ /* The location stack. */
+ YYLTYPE yylsa[YYINITDEPTH];
+ YYLTYPE *yyls = yylsa;
+ YYLTYPE *yylsp;
+ /* The locations where the error started and ended. */
+ YYLTYPE yyerror_range[2];]])[
+
+#define YYPOPSTACK(N) (yyvsp -= (N), yyssp -= (N)]b4_locations_if([, yylsp -= (N)])[)
+
+ YYSIZE_T yystacksize = YYINITDEPTH;
+
+ /* The variables used to return semantic value and location from the
+ action routines. */
+ YYSTYPE yyval;
+]b4_locations_if([ YYLTYPE yyloc;])[
+
+ /* The number of symbols on the RHS of the reduced rule.
+ Keep to zero when no symbol should be popped. */
+ int yylen = 0;
+
+ YYDPRINTF ((stderr, "Starting parse\n"));
+
+ yystate = 0;
+ yyerrstatus = 0;
+ yynerrs = 0;
+ yychar = YYEMPTY; /* Cause a token to be read. */
+
+ /* Initialize stack pointers.
+ Waste one element of value and location stack
+ so that they stay on the same level as the state stack.
+ The wasted elements are never initialized. */
+
+ yyssp = yyss;
+ yyvsp = yyvs;
+]b4_locations_if([[ yylsp = yyls;
+#if YYLTYPE_IS_TRIVIAL
+ /* Initialize the default location before parsing starts. */
+ yylloc.first_line = yylloc.last_line = 1;
+ yylloc.first_column = yylloc.last_column = 0;
+#endif
+]])
+m4_ifdef([b4_initial_action], [
+m4_pushdef([b4_at_dollar], [m4_define([b4_at_dollar_used])yylloc])dnl
+m4_pushdef([b4_dollar_dollar], [m4_define([b4_dollar_dollar_used])yylval])dnl
+ /* User initialization code. */
+b4_initial_action
+m4_popdef([b4_dollar_dollar])dnl
+m4_popdef([b4_at_dollar])dnl
+/* Line __line__ of yacc.c. */
+b4_syncline([@oline@], [@ofile@])
+])dnl
+m4_ifdef([b4_dollar_dollar_used],[[ yyvsp[0] = yylval;
+]])dnl
+m4_ifdef([b4_at_dollar_used], [[ yylsp[0] = yylloc;
+]])dnl
+[ goto yysetstate;
+
+/*------------------------------------------------------------.
+| yynewstate -- Push a new state, which is found in yystate. |
+`------------------------------------------------------------*/
+ yynewstate:
+ /* In all cases, when you get here, the value and location stacks
+ have just been pushed. So pushing a state here evens the stacks. */
+ yyssp++;
+
+ yysetstate:
+ *yyssp = yystate;
+
+ if (yyss + yystacksize - 1 <= yyssp)
+ {
+ /* Get the current used size of the three stacks, in elements. */
+ YYSIZE_T yysize = yyssp - yyss + 1;
+
+#ifdef yyoverflow
+ {
+ /* Give user a chance to reallocate the stack. Use copies of
+ these so that the &'s don't force the real ones into
+ memory. */
+ YYSTYPE *yyvs1 = yyvs;
+ yytype_int16 *yyss1 = yyss;
+]b4_locations_if([ YYLTYPE *yyls1 = yyls;])[
+
+ /* Each stack pointer address is followed by the size of the
+ data in use in that stack, in bytes. This used to be a
+ conditional around just the two extra args, but that might
+ be undefined if yyoverflow is a macro. */
+ yyoverflow (YY_("memory exhausted"),
+ &yyss1, yysize * sizeof (*yyssp),
+ &yyvs1, yysize * sizeof (*yyvsp),
+]b4_locations_if([ &yyls1, yysize * sizeof (*yylsp),])[
+ &yystacksize);
+]b4_locations_if([ yyls = yyls1;])[
+ yyss = yyss1;
+ yyvs = yyvs1;
+ }
+#else /* no yyoverflow */
+# ifndef YYSTACK_RELOCATE
+ goto yyexhaustedlab;
+# else
+ /* Extend the stack our own way. */
+ if (YYMAXDEPTH <= yystacksize)
+ goto yyexhaustedlab;
+ yystacksize *= 2;
+ if (YYMAXDEPTH < yystacksize)
+ yystacksize = YYMAXDEPTH;
+
+ {
+ yytype_int16 *yyss1 = yyss;
+ union yyalloc *yyptr =
+ (union yyalloc *) YYSTACK_ALLOC (YYSTACK_BYTES (yystacksize));
+ if (! yyptr)
+ goto yyexhaustedlab;
+ YYSTACK_RELOCATE (yyss);
+ YYSTACK_RELOCATE (yyvs);
+]b4_locations_if([ YYSTACK_RELOCATE (yyls);])[
+# undef YYSTACK_RELOCATE
+ if (yyss1 != yyssa)
+ YYSTACK_FREE (yyss1);
+ }
+# endif
+#endif /* no yyoverflow */
+
+ yyssp = yyss + yysize - 1;
+ yyvsp = yyvs + yysize - 1;
+]b4_locations_if([ yylsp = yyls + yysize - 1;])[
+
+ YYDPRINTF ((stderr, "Stack size increased to %lu\n",
+ (unsigned long int) yystacksize));
+
+ if (yyss + yystacksize - 1 <= yyssp)
+ YYABORT;
+ }
+
+ YYDPRINTF ((stderr, "Entering state %d\n", yystate));
+
+ goto yybackup;
+
+/*-----------.
+| yybackup. |
+`-----------*/
+yybackup:
+
+ /* Do appropriate processing given the current state. Read a
+ look-ahead token if we need one and don't already have one. */
+
+ /* First try to decide what to do without reference to look-ahead token. */
+ yyn = yypact[yystate];
+ if (yyn == YYPACT_NINF)
+ goto yydefault;
+
+ /* Not known => get a look-ahead token if don't already have one. */
+
+ /* YYCHAR is either YYEMPTY or YYEOF or a valid look-ahead symbol. */
+ if (yychar == YYEMPTY)
+ {
+ YYDPRINTF ((stderr, "Reading a token: "));
+ yychar = YYLEX;
+ }
+
+ if (yychar <= YYEOF)
+ {
+ yychar = yytoken = YYEOF;
+ YYDPRINTF ((stderr, "Now at end of input.\n"));
+ }
+ else
+ {
+ yytoken = YYTRANSLATE (yychar);
+ YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc);
+ }
+
+ /* If the proper action on seeing token YYTOKEN is to reduce or to
+ detect an error, take that action. */
+ yyn += yytoken;
+ if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken)
+ goto yydefault;
+ yyn = yytable[yyn];
+ if (yyn <= 0)
+ {
+ if (yyn == 0 || yyn == YYTABLE_NINF)
+ goto yyerrlab;
+ yyn = -yyn;
+ goto yyreduce;
+ }
+
+ if (yyn == YYFINAL)
+ YYACCEPT;
+
+ /* Count tokens shifted since error; after three, turn off error
+ status. */
+ if (yyerrstatus)
+ yyerrstatus--;
+
+ /* Shift the look-ahead token. */
+ YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc);
+
+ /* Discard the shifted token unless it is eof. */
+ if (yychar != YYEOF)
+ yychar = YYEMPTY;
+
+ yystate = yyn;
+ *++yyvsp = yylval;
+]b4_locations_if([ *++yylsp = yylloc;])[
+ goto yynewstate;
+
+
+/*-----------------------------------------------------------.
+| yydefault -- do the default action for the current state. |
+`-----------------------------------------------------------*/
+yydefault:
+ yyn = yydefact[yystate];
+ if (yyn == 0)
+ goto yyerrlab;
+ goto yyreduce;
+
+
+/*-----------------------------.
+| yyreduce -- Do a reduction. |
+`-----------------------------*/
+yyreduce:
+ /* yyn is the number of a rule to reduce with. */
+ yylen = yyr2[yyn];
+
+ /* If YYLEN is nonzero, implement the default value of the action:
+ `$$ = $1'.
+
+ Otherwise, the following line sets YYVAL to garbage.
+ This behavior is undocumented and Bison
+ users should not rely upon it. Assigning to YYVAL
+ unconditionally makes the parser a bit smaller, and it avoids a
+ GCC warning that YYVAL may be used uninitialized. */
+ yyval = yyvsp[1-yylen];
+
+]b4_locations_if(
+[[ /* Default location. */
+ YYLLOC_DEFAULT (yyloc, (yylsp - yylen), yylen);]])[
+ YY_REDUCE_PRINT (yyn);
+ switch (yyn)
+ {
+ ]b4_actions
+/* Line __line__ of yacc.c. */
+b4_syncline([@oline@], [@ofile@])[
+ default: break;
+ }
+ YY_SYMBOL_PRINT ("-> $$ =", yyr1[yyn], &yyval, &yyloc);
+
+ YYPOPSTACK (yylen);
+ yylen = 0;
+ YY_STACK_PRINT (yyss, yyssp);
+
+ *++yyvsp = yyval;
+]b4_locations_if([ *++yylsp = yyloc;])[
+
+ /* Now `shift' the result of the reduction. Determine what state
+ that goes to, based on the state we popped back to and the rule
+ number reduced by. */
+
+ yyn = yyr1[yyn];
+
+ yystate = yypgoto[yyn - YYNTOKENS] + *yyssp;
+ if (0 <= yystate && yystate <= YYLAST && yycheck[yystate] == *yyssp)
+ yystate = yytable[yystate];
+ else
+ yystate = yydefgoto[yyn - YYNTOKENS];
+
+ goto yynewstate;
+
+
+/*------------------------------------.
+| yyerrlab -- here on detecting error |
+`------------------------------------*/
+yyerrlab:
+ /* If not already recovering from an error, report this error. */
+ if (!yyerrstatus)
+ {
+ ++yynerrs;
+#if ! YYERROR_VERBOSE
+ yyerror (]b4_yyerror_args[YY_("syntax error"));
+#else
+ {
+ YYSIZE_T yysize = yysyntax_error (0, yystate, yychar);
+ if (yymsg_alloc < yysize && yymsg_alloc < YYSTACK_ALLOC_MAXIMUM)
+ {
+ YYSIZE_T yyalloc = 2 * yysize;
+ if (! (yysize <= yyalloc && yyalloc <= YYSTACK_ALLOC_MAXIMUM))
+ yyalloc = YYSTACK_ALLOC_MAXIMUM;
+ if (yymsg != yymsgbuf)
+ YYSTACK_FREE (yymsg);
+ yymsg = (char *) YYSTACK_ALLOC (yyalloc);
+ if (yymsg)
+ yymsg_alloc = yyalloc;
+ else
+ {
+ yymsg = yymsgbuf;
+ yymsg_alloc = sizeof yymsgbuf;
+ }
+ }
+
+ if (0 < yysize && yysize <= yymsg_alloc)
+ {
+ (void) yysyntax_error (yymsg, yystate, yychar);
+ yyerror (]b4_yyerror_args[yymsg);
+ }
+ else
+ {
+ yyerror (]b4_yyerror_args[YY_("syntax error"));
+ if (yysize != 0)
+ goto yyexhaustedlab;
+ }
+ }
+#endif
+ }
+
+]b4_locations_if([[ yyerror_range[0] = yylloc;]])[
+
+ if (yyerrstatus == 3)
+ {
+ /* If just tried and failed to reuse look-ahead token after an
+ error, discard it. */
+
+ if (yychar <= YYEOF)
+ {
+ /* Return failure if at end of input. */
+ if (yychar == YYEOF)
+ YYABORT;
+ }
+ else
+ {
+ yydestruct ("Error: discarding",
+ yytoken, &yylval]b4_locations_if([, &yylloc])[]b4_user_args[);
+ yychar = YYEMPTY;
+ }
+ }
+
+ /* Else will try to reuse look-ahead token after shifting the error
+ token. */
+ goto yyerrlab1;
+
+
+/*---------------------------------------------------.
+| yyerrorlab -- error raised explicitly by YYERROR. |
+`---------------------------------------------------*/
+yyerrorlab:
+
+ /* Pacify compilers like GCC when the user code never invokes
+ YYERROR and the label yyerrorlab therefore never appears in user
+ code. */
+ if (/*CONSTCOND*/ 0)
+ goto yyerrorlab;
+
+]b4_locations_if([[ yyerror_range[0] = yylsp[1-yylen];
+]])[ /* Do not reclaim the symbols of the rule which action triggered
+ this YYERROR. */
+ YYPOPSTACK (yylen);
+ yylen = 0;
+ YY_STACK_PRINT (yyss, yyssp);
+ yystate = *yyssp;
+ goto yyerrlab1;
+
+
+/*-------------------------------------------------------------.
+| yyerrlab1 -- common code for both syntax error and YYERROR. |
+`-------------------------------------------------------------*/
+yyerrlab1:
+ yyerrstatus = 3; /* Each real token shifted decrements this. */
+
+ for (;;)
+ {
+ yyn = yypact[yystate];
+ if (yyn != YYPACT_NINF)
+ {
+ yyn += YYTERROR;
+ if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYTERROR)
+ {
+ yyn = yytable[yyn];
+ if (0 < yyn)
+ break;
+ }
+ }
+
+ /* Pop the current state because it cannot handle the error token. */
+ if (yyssp == yyss)
+ YYABORT;
+
+]b4_locations_if([[ yyerror_range[0] = *yylsp;]])[
+ yydestruct ("Error: popping",
+ yystos[yystate], yyvsp]b4_locations_if([, yylsp])[]b4_user_args[);
+ YYPOPSTACK (1);
+ yystate = *yyssp;
+ YY_STACK_PRINT (yyss, yyssp);
+ }
+
+ if (yyn == YYFINAL)
+ YYACCEPT;
+
+ *++yyvsp = yylval;
+]b4_locations_if([[
+ yyerror_range[1] = yylloc;
+ /* Using YYLLOC is tempting, but would change the location of
+ the look-ahead. YYLOC is available though. */
+ YYLLOC_DEFAULT (yyloc, (yyerror_range - 1), 2);
+ *++yylsp = yyloc;]])[
+
+ /* Shift the error token. */
+ YY_SYMBOL_PRINT ("Shifting", yystos[yyn], yyvsp, yylsp);
+
+ yystate = yyn;
+ goto yynewstate;
+
+
+/*-------------------------------------.
+| yyacceptlab -- YYACCEPT comes here. |
+`-------------------------------------*/
+yyacceptlab:
+ yyresult = 0;
+ goto yyreturn;
+
+/*-----------------------------------.
+| yyabortlab -- YYABORT comes here. |
+`-----------------------------------*/
+yyabortlab:
+ yyresult = 1;
+ goto yyreturn;
+
+#ifndef yyoverflow
+/*-------------------------------------------------.
+| yyexhaustedlab -- memory exhaustion comes here. |
+`-------------------------------------------------*/
+yyexhaustedlab:
+ yyerror (]b4_yyerror_args[YY_("memory exhausted"));
+ yyresult = 2;
+ /* Fall through. */
+#endif
+
+yyreturn:
+ if (yychar != YYEOF && yychar != YYEMPTY)
+ yydestruct ("Cleanup: discarding lookahead",
+ yytoken, &yylval]b4_locations_if([, &yylloc])[]b4_user_args[);
+ /* Do not reclaim the symbols of the rule which action triggered
+ this YYABORT or YYACCEPT. */
+ YYPOPSTACK (yylen);
+ YY_STACK_PRINT (yyss, yyssp);
+ while (yyssp != yyss)
+ {
+ yydestruct ("Cleanup: popping",
+ yystos[*yyssp], yyvsp]b4_locations_if([, yylsp])[]b4_user_args[);
+ YYPOPSTACK (1);
+ }
+#ifndef yyoverflow
+ if (yyss != yyssa)
+ YYSTACK_FREE (yyss);
+#endif
+#if YYERROR_VERBOSE
+ if (yymsg != yymsgbuf)
+ YYSTACK_FREE (yymsg);
+#endif
+ /* Make sure YYID is used. */
+ return YYID (yyresult);
+]}
+
+
+b4_epilogue
+b4_defines_if(
+[@output @output_header_name@
+b4_copyright([Skeleton interface for Bison's Yacc-like parsers in C],dnl '
+ [1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004, 2005, 2006])
+
+b4_token_enums_defines(b4_tokens)
+
+#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED
+m4_ifdef([b4_stype],
+[typedef union[]m4_bregexp(b4_stype, [^{], [ YYSTYPE])
+b4_stype
+/* Line __line__ of yacc.c. */
+b4_syncline([@oline@], [@ofile@])
+ YYSTYPE;],
+[typedef int YYSTYPE;])
+# define yystype YYSTYPE /* obsolescent; will be withdrawn */
+# define YYSTYPE_IS_DECLARED 1
+# define YYSTYPE_IS_TRIVIAL 1
+#endif
+
+b4_pure_if([],
+[extern YYSTYPE b4_prefix[]lval;])
+
+b4_locations_if(
+[#if ! defined YYLTYPE && ! defined YYLTYPE_IS_DECLARED
+typedef struct YYLTYPE
+{
+ int first_line;
+ int first_column;
+ int last_line;
+ int last_column;
+} YYLTYPE;
+# define yyltype YYLTYPE /* obsolescent; will be withdrawn */
+# define YYLTYPE_IS_DECLARED 1
+# define YYLTYPE_IS_TRIVIAL 1
+#endif
+
+b4_pure_if([],
+ [extern YYLTYPE b4_prefix[]lloc;])
+])dnl b4_locations_if
+])dnl b4_defines_if
diff --git a/src/parser.y b/src/parser.y
new file mode 100644
index 00000000..c3ade912
--- /dev/null
+++ b/src/parser.y
@@ -0,0 +1,1386 @@
+/*
+ * Copyright (c) 2007-2008 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 <netinet/ip.h>
+#include <linux/netfilter.h>
+#include <linux/netfilter/nf_tables.h>
+#include <linux/netfilter/nf_conntrack_tuple_common.h>
+
+#include <rule.h>
+#include <statement.h>
+#include <expression.h>
+#include <utils.h>
+#include <parser.h>
+#include <erec.h>
+
+#include "parser.h"
+#include "scanner.h"
+
+void parser_init(struct parser_state *state, struct list_head *msgs)
+{
+ memset(state, 0, sizeof(*state));
+ init_list_head(&state->cmds);
+ state->msgs = msgs;
+}
+
+static void yyerror(struct location *loc, void *scanner,
+ struct parser_state *state, const char *s)
+{
+ erec_queue(error(loc, "%s", s), state->msgs);
+}
+
+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;
+ }
+}
+
+#define YYLLOC_DEFAULT(Current, Rhs, N) location_update(&Current, Rhs, N)
+
+%}
+
+/* Declaration section */
+
+%name-prefix="nft_"
+%debug
+%pure-parser
+%parse-param { void *scanner }
+%parse-param { struct parser_state *state }
+%lex-param { scanner }
+%error-verbose
+%locations
+
+%initial-action {
+ location_init(scanner, state, &yylloc);
+#if 0
+ nft_set_debug(1, scanner);
+ yydebug = 1;
+#endif
+}
+
+%union {
+ uint64_t val;
+ 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;
+}
+
+%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 ARROW "=>"
+%token MAP "map"
+%token VMAP "vmap"
+%token SET "set"
+
+%token INCLUDE "include"
+
+%token HOOK "hook"
+%token <val> HOOKNUM "hooknum"
+%token TABLE "table"
+%token CHAIN "chain"
+%token RULE "rule"
+%token HANDLE "handle"
+
+%token ADD "add"
+%token DELETE "delete"
+%token LIST "list"
+%token FLUSH "flush"
+%token DESCRIBE "describe"
+
+%token ACCEPT "accept"
+%token DROP "drop"
+%token CONTINUE "continue"
+%token JUMP "jump"
+%token GOTO "goto"
+%token RETURN "return"
+%token QUEUE "queue"
+
+%token <val> NUM "number"
+%token <string> STRING "string"
+%token <string> QUOTED_STRING
+%destructor { xfree($$); } STRING QUOTED_STRING
+
+%token LL_HDR "ll"
+%token NETWORK_HDR "nh"
+%token TRANSPORT_HDR "th"
+
+%token BRIDGE "bridge"
+
+%token ETH "eth"
+%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 VERSION "version"
+%token HDRLENGTH "hdrlength"
+%token TOS "tos"
+%token LENGTH "length"
+%token FRAG_OFF "frag-off"
+%token TTL "ttl"
+%token PROTOCOL "protocol"
+%token CHECKSUM "checksum"
+
+%token ICMP "icmp"
+%token CODE "code"
+%token SEQUENCE "seq"
+%token GATEWAY "gateway"
+%token MTU "mtu"
+
+%token IP6 "ip6"
+%token PRIORITY "priority"
+%token FLOWLABEL "flowlabel"
+%token NEXTHDR "nexthdr"
+%token HOPLIMIT "hoplimit"
+
+%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 DCCP "dccp"
+
+%token SCTP "sctp"
+%token VTAG "vtag"
+
+%token RT "rt"
+%token RT0 "rt0"
+%token RT2 "rt2"
+%token SEG_LEFT "seg-left"
+%token ADDR "addr"
+
+%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 SECMARK "secmark"
+
+%token CT "ct"
+%token DIRECTION "direction"
+%token STATE "state"
+%token STATUS "status"
+%token EXPIRATION "expiration"
+%token HELPER "helper"
+%token PROTO_SRC "proto-src"
+%token PROTO_DST "proto-dst"
+
+%token COUNTER "counter"
+
+%token LOG "log"
+%token PREFIX "prefix"
+%token GROUP "group"
+%token SNAPLEN "snaplen"
+%token QUEUE_THRESHOLD "queue-threshold"
+
+%token LIMIT "limit"
+%token RATE "rate"
+
+%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 SNAT "snat"
+%token DNAT "dnat"
+
+%type <string> identifier string
+%destructor { xfree($$); } identifier string
+
+%type <cmd> line
+%destructor { cmd_free($$); } line
+
+%type <cmd> base_cmd add_cmd delete_cmd list_cmd flush_cmd
+%destructor { cmd_free($$); } base_cmd add_cmd delete_cmd list_cmd flush_cmd
+
+%type <handle> table_spec chain_spec chain_identifier ruleid_spec
+%destructor { handle_free(&$$); } table_spec chain_spec chain_identifier ruleid_spec
+%type <val> handle_spec family_spec
+
+%type <table> table_block_alloc table_block
+%destructor { table_free($$); } table_block_alloc
+%type <chain> table_line chain_block_alloc chain_block
+%destructor { chain_free($$); } table_line chain_block_alloc
+%type <rule> rule
+%destructor { rule_free($$); } rule
+
+%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
+%destructor { stmt_free($$); } counter_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 <stmt> limit_stmt
+%destructor { stmt_free($$); } limit_stmt
+%type <val> time_unit
+%type <stmt> reject_stmt
+%destructor { stmt_free($$); } reject_stmt
+%type <stmt> nat_stmt nat_stmt_alloc
+%destructor { stmt_free($$); } nat_stmt nat_stmt_alloc
+
+%type <expr> symbol_expr verdict_expr integer_expr
+%destructor { expr_free($$); } symbol_expr verdict_expr integer_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> multiton_expr
+%destructor { expr_free($$); } multiton_expr
+%type <expr> prefix_expr range_expr wildcard_expr
+%destructor { expr_free($$); } prefix_expr range_expr wildcard_expr
+%type <expr> list_expr
+%destructor { expr_free($$); } list_expr
+%type <expr> concat_expr map_lhs_expr
+%destructor { expr_free($$); } concat_expr map_lhs_expr
+
+%type <expr> map_expr map_list map_list_expr
+%destructor { expr_free($$); } map_expr map_list map_list_expr
+
+%type <expr> verdict_map_expr verdict_map_list verdict_map_list_expr
+%destructor { expr_free($$); } verdict_map_expr verdict_map_list verdict_map_list_expr
+
+%type <expr> set_expr
+%destructor { expr_free($$); } set_expr
+
+%type <expr> expr
+%destructor { expr_free($$); } expr
+
+%type <expr> match_expr
+%destructor { expr_free($$); } match_expr
+%type <expr> relational_expr membership_expr
+%destructor { expr_free($$); } relational_expr membership_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
+%destructor { expr_free($$); } ip_hdr_expr icmp_hdr_expr
+%type <val> ip_hdr_field icmp_hdr_field
+%type <expr> ip6_hdr_expr
+%destructor { expr_free($$); } ip6_hdr_expr
+%type <val> ip6_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 tcp_hdr_expr
+%destructor { expr_free($$); } udp_hdr_expr udplite_hdr_expr tcp_hdr_expr
+%type <val> udp_hdr_field udplite_hdr_field tcp_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> 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
+%destructor { expr_free($$); } rt_hdr_expr rt0_hdr_expr rt2_hdr_expr
+%type <val> rt_hdr_field rt0_hdr_field rt2_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
+
+%type <expr> ct_expr
+%destructor { expr_free($$); } ct_expr
+%type <val> ct_key
+
+%%
+
+input : /* empty */
+ | input line
+ {
+ if ($2 != NULL) {
+ $2->location = @2;
+ list_add_tail(&$2->list, &state->cmds);
+ }
+ }
+ ;
+
+stmt_seperator : NEWLINE
+ | SEMICOLON
+ ;
+
+common_block : INCLUDE QUOTED_STRING stmt_seperator
+ {
+ if (scanner_include_file(scanner, $2, &@$) < 0) {
+ xfree($2);
+ YYERROR;
+ }
+ xfree($2);
+ }
+ ;
+
+line : common_block { $$ = NULL; }
+ | stmt_seperator { $$ = NULL; }
+ | base_cmd stmt_seperator { $$ = $1; }
+ | base_cmd TOKEN_EOF { $$ = $1; }
+ | base_cmd error { $$ = $1; }
+ ;
+
+base_cmd : /* empty */ add_cmd { $$ = $1; }
+ | ADD add_cmd { $$ = $2; }
+ | DELETE delete_cmd { $$ = $2; }
+ | LIST list_cmd { $$ = $2; }
+ | FLUSH flush_cmd { $$ = $2; }
+ | DESCRIBE primary_expr
+ {
+ expr_describe($2);
+ expr_free($2);
+ $$ = NULL;
+ }
+ ;
+
+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);
+ $$ = 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 '}'
+ {
+ handle_merge(&$3->handle, &$2);
+ $$ = cmd_alloc(CMD_ADD, CMD_OBJ_CHAIN, &$2, $5);
+ }
+ | RULE ruleid_spec rule
+ {
+ $$ = cmd_alloc(CMD_ADD, CMD_OBJ_RULE, &$2, $3);
+ }
+ | /* empty */ ruleid_spec rule
+ {
+ $$ = cmd_alloc(CMD_ADD, CMD_OBJ_RULE, &$1, $2);
+ }
+ ;
+
+delete_cmd : TABLE table_spec
+ {
+ $$ = cmd_alloc(CMD_DELETE, CMD_OBJ_TABLE, &$2, NULL);
+ }
+ | CHAIN chain_spec
+ {
+ $$ = cmd_alloc(CMD_DELETE, CMD_OBJ_CHAIN, &$2, NULL);
+ }
+ | RULE ruleid_spec
+ {
+ $$ = cmd_alloc(CMD_DELETE, CMD_OBJ_RULE, &$2, NULL);
+ }
+ ;
+
+list_cmd : TABLE table_spec
+ {
+ $$ = cmd_alloc(CMD_LIST, CMD_OBJ_TABLE, &$2, NULL);
+ }
+ | CHAIN chain_spec
+ {
+ $$ = cmd_alloc(CMD_LIST, CMD_OBJ_CHAIN, &$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);
+ }
+ ;
+
+table_block_alloc : /* empty */ { $$ = table_alloc(); }
+ ;
+
+table_block : /* empty */ { $$ = $<table>-1; }
+ | common_block { $$ = $<table>-1; }
+ | table_block stmt_seperator
+ | table_block table_line stmt_seperator
+ {
+ list_add_tail(&$2->list, &$1->chains);
+ $$ = $1;
+ }
+ ;
+
+table_line : CHAIN chain_identifier chain_block_alloc
+ '{' chain_block '}'
+ {
+ handle_merge(&$3->handle, &$2);
+ $$ = $3;
+ }
+ ;
+
+chain_block_alloc : /* empty */ { $$ = chain_alloc(NULL); }
+ ;
+
+chain_block : /* empty */ { $$ = $<chain>-1; }
+ | common_block { $$ = $<chain>-1; }
+ | chain_block stmt_seperator
+ | chain_block hook_spec stmt_seperator
+ | chain_block rule stmt_seperator
+ {
+ list_add_tail(&$2->list, &$1->rules);
+ $$ = $1;
+ }
+ ;
+
+hook_spec : HOOK HOOKNUM NUM
+ {
+ $<chain>0->hooknum = $2;
+ $<chain>0->priority = $3;
+ }
+ | HOOK HOOKNUM DASH NUM
+ {
+ $<chain>0->hooknum = $2;
+ $<chain>0->priority = -$4;
+ }
+ ;
+
+identifier : STRING
+ ;
+
+string : STRING
+ | QUOTED_STRING
+ ;
+
+family_spec : /* empty */ { $$ = NFPROTO_IPV4; }
+ | IP { $$ = NFPROTO_IPV4; }
+ | IP6 { $$ = NFPROTO_IPV6; }
+ | ARP { $$ = NFPROTO_ARP; }
+ | BRIDGE { $$ = NFPROTO_BRIDGE; }
+ ;
+
+table_spec : family_spec identifier
+ {
+ memset(&$$, 0, sizeof($$));
+ $$.family = $1;
+ $$.table = $2;
+ }
+ ;
+
+chain_spec : table_spec identifier
+ {
+ $$ = $1;
+ $$.chain = $2;
+ }
+ ;
+
+chain_identifier : identifier
+ {
+ memset(&$$, 0, sizeof($$));
+ $$.chain = $1;
+ }
+ ;
+
+handle_spec : /* empty */
+ {
+ $$ = 0;
+ }
+ | HANDLE NUM
+ {
+ $$ = $2;
+ }
+ ;
+
+ruleid_spec : chain_spec handle_spec
+ {
+ $$ = $1;
+ $$.handle = $2;
+ }
+ ;
+
+rule : 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);
+ }
+ ;
+
+stmt : verdict_stmt
+ | match_stmt
+ | counter_stmt
+ | meta_stmt
+ | log_stmt
+ | limit_stmt
+ | reject_stmt
+ | nat_stmt
+ ;
+
+verdict_stmt : verdict_expr
+ {
+ $$ = verdict_stmt_alloc(&@1, $1);
+ }
+ | verdict_map_expr
+ {
+ $$ = verdict_stmt_alloc(&@1, $1);
+ }
+ ;
+
+counter_stmt : COUNTER
+ {
+ $$ = counter_stmt_alloc(&@1);
+ }
+ ;
+
+log_stmt : log_stmt_alloc
+ | log_stmt_alloc log_args
+ ;
+
+log_stmt_alloc : LOG
+ {
+ $$ = log_stmt_alloc(&@1);
+ }
+ ;
+
+log_args : log_arg
+ {
+ $<stmt>$ = $<stmt>0;
+ }
+ | log_args log_arg
+ ;
+
+log_arg : PREFIX string
+ {
+ $<stmt>0->log.prefix = $2;
+ }
+ | GROUP NUM
+ {
+ $<stmt>0->log.group = $2;
+ }
+ | SNAPLEN NUM
+ {
+ $<stmt>0->log.snaplen = $2;
+ }
+ | QUEUE_THRESHOLD NUM
+ {
+ $<stmt>0->log.qthreshold = $2;
+ }
+ ;
+
+limit_stmt : LIMIT RATE NUM SLASH time_unit
+ {
+ $$ = limit_stmt_alloc(&@$);
+ $$->limit.rate = $3;
+ $$->limit.unit = $5;
+ }
+ ;
+
+time_unit : NANOSECOND { $$ = 1ULL; }
+ | MICROSECOND { $$ = 1ULL * 1000; }
+ | MILLISECOND { $$ = 1ULL * 1000 * 1000; }
+ | SECOND { $$ = 1ULL * 1000 * 1000 * 1000; }
+ | MINUTE { $$ = 1ULL * 1000 * 1000 * 1000 * 60; }
+ | HOUR { $$ = 1ULL * 1000 * 1000 * 1000 * 60 * 60; }
+ | DAY { $$ = 1ULL * 1000 * 1000 * 1000 * 60 * 60 * 24; }
+ | WEEK { $$ = 1ULL * 1000 * 1000 * 1000 * 60 * 60 * 24 * 7; }
+ ;
+
+reject_stmt : _REJECT
+ {
+ $$ = reject_stmt_alloc(&@$);
+ }
+ ;
+
+nat_stmt : nat_stmt_alloc nat_stmt_args
+ ;
+
+nat_stmt_alloc : SNAT
+ {
+ $$ = nat_stmt_alloc(&@$);
+ $$->nat.type = NFT_NAT_SNAT;
+ }
+ | DNAT
+ {
+ $$ = nat_stmt_alloc(&@$);
+ $$->nat.type = NFT_NAT_DNAT;
+ }
+ ;
+
+nat_stmt_args : expr
+ {
+ $<stmt>0->nat.addr = $1;
+ }
+ | expr COLON expr
+ {
+ $<stmt>0->nat.addr = $1;
+ $<stmt>0->nat.proto = $3;
+ }
+ | COLON expr
+ {
+ $<stmt>0->nat.proto = $2;
+ }
+ ;
+
+match_stmt : match_expr
+ {
+ $$ = expr_stmt_alloc(&@$, $1);
+ }
+ ;
+
+symbol_expr : string
+ {
+ $$ = symbol_expr_alloc(&@1, $1);
+ xfree($1);
+ }
+ ;
+
+integer_expr : NUM
+ {
+ char str[64];
+
+ snprintf(str, sizeof(str), "%" PRIu64, $1);
+ $$ = symbol_expr_alloc(&@1, str);
+ }
+ ;
+
+primary_expr : symbol_expr { $$ = $1; }
+ | integer_expr { $$ = $1; }
+ | payload_expr { $$ = $1; }
+ | exthdr_expr { $$ = $1; }
+ | meta_expr { $$ = $1; }
+ | ct_expr { $$ = $1; }
+ | '(' basic_expr ')' { $$ = $2; }
+ ;
+
+shift_expr : primary_expr
+ | shift_expr LSHIFT primary_expr
+ {
+ $$ = binop_expr_alloc(&@$, OP_LSHIFT, $1, $3);
+ }
+ | shift_expr RSHIFT primary_expr
+ {
+ $$ = binop_expr_alloc(&@$, OP_RSHIFT, $1, $3);
+ }
+ ;
+
+and_expr : shift_expr
+ | and_expr AMPERSAND shift_expr
+ {
+ $$ = binop_expr_alloc(&@$, OP_AND, $1, $3);
+ }
+ ;
+
+exclusive_or_expr : and_expr
+ | exclusive_or_expr CARET and_expr
+ {
+ $$ = binop_expr_alloc(&@$, OP_XOR, $1, $3);
+ }
+ ;
+
+inclusive_or_expr : exclusive_or_expr
+ | inclusive_or_expr '|' exclusive_or_expr
+ {
+ $$ = binop_expr_alloc(&@$, OP_OR, $1, $3);
+ }
+ ;
+
+basic_expr : inclusive_or_expr
+ ;
+
+concat_expr : basic_expr
+ | concat_expr DOT basic_expr
+ {
+ if ($$->ops->type != EXPR_CONCAT) {
+ $$ = concat_expr_alloc(&@$);
+ compound_expr_add($$, $1);
+ } else {
+ $$ = $1;
+ $$->location = @$;
+ }
+ compound_expr_add($$, $3);
+ }
+ ;
+
+list_expr : basic_expr COMMA basic_expr
+ {
+ $$ = list_expr_alloc(&@$);
+ compound_expr_add($$, $1);
+ compound_expr_add($$, $3);
+ }
+ | list_expr COMMA basic_expr
+ {
+ $1->location = @$;
+ compound_expr_add($1, $3);
+ $$ = $1;
+ }
+ ;
+
+prefix_expr : basic_expr SLASH NUM
+ {
+ $$ = prefix_expr_alloc(&@$, $1, $3);
+ }
+ ;
+
+range_expr : basic_expr DASH basic_expr
+ {
+ $$ = range_expr_alloc(&@$, $1, $3);
+ }
+ ;
+
+wildcard_expr : ASTERISK
+ {
+ struct expr *expr;
+
+ expr = constant_expr_alloc(&@1, &integer_type,
+ BYTEORDER_HOST_ENDIAN,
+ 0, NULL);
+ $$ = prefix_expr_alloc(&@$, expr, 0);
+ }
+ ;
+
+multiton_expr : prefix_expr
+ | range_expr
+ | wildcard_expr
+ ;
+
+map_lhs_expr : multiton_expr
+ | concat_expr
+ ;
+
+map_expr : concat_expr MAP '{' map_list '}'
+ {
+ $$ = map_expr_alloc(&@$, $1, $4);
+ }
+ ;
+
+map_list : map_list_expr
+ {
+ $$ = set_expr_alloc(&@$);
+ compound_expr_add($$, $1);
+ }
+ | map_list COMMA map_list_expr
+ {
+ compound_expr_add($1, $3);
+ $1->location = @$;
+ $$ = $1;
+ }
+ | map_list COMMA
+ ;
+
+map_list_expr : map_lhs_expr ARROW concat_expr
+ {
+ $$ = mapping_expr_alloc(&@$, $1, $3);
+ }
+ ;
+
+verdict_map_expr : concat_expr VMAP '{' verdict_map_list '}'
+ {
+ $$ = map_expr_alloc(&@$, $1, $4);
+ }
+ ;
+
+verdict_map_list : verdict_map_list_expr
+ {
+ $$ = set_expr_alloc(&@$);
+ compound_expr_add($$, $1);
+ }
+ | verdict_map_list COMMA verdict_map_list_expr
+ {
+ compound_expr_add($1, $3);
+ $1->location = @$;
+ $$ = $1;
+ }
+ | verdict_map_list COMMA
+ ;
+
+verdict_map_list_expr : map_lhs_expr ARROW verdict_expr
+ {
+ $$ = mapping_expr_alloc(&@$, $1, $3);
+ }
+ ;
+
+expr : concat_expr
+ | map_expr
+ | multiton_expr
+ ;
+
+match_expr : relational_expr
+ | membership_expr
+ ;
+
+relational_expr : expr /* implicit */ expr
+ {
+ enum ops op;
+
+ /* RHS determines operation */
+ op = ($2->ops->type == EXPR_RANGE) ? OP_RANGE : OP_EQ;
+ $$ = relational_expr_alloc(&@$, op, $1, $2);
+ }
+ | expr /* implicit */ list_expr
+ {
+ $$ = relational_expr_alloc(&@$, OP_FLAGCMP, $1, $2);
+ }
+ | expr relational_op expr
+ {
+ $$ = relational_expr_alloc(&@2, $2, $1, $3);
+ }
+ ;
+
+relational_op : EQ { $$ = OP_EQ; }
+ | NEQ { $$ = OP_NEQ; }
+ | LT { $$ = OP_LT; }
+ | GT { $$ = OP_GT; }
+ | GTE { $$ = OP_GTE; }
+ | LTE { $$ = OP_LTE; }
+ ;
+
+membership_expr : expr '{' set_expr '}'
+ {
+ $3->location = @$;
+ $$ = relational_expr_alloc(&@$, OP_LOOKUP, $1, $3);
+ }
+ ;
+
+set_expr : expr
+ {
+ $$ = set_expr_alloc(&@1);
+ compound_expr_add($$, $1);
+ }
+ | set_expr COMMA expr
+ {
+ compound_expr_add($1, $3);
+ $$ = $1;
+ }
+ | set_expr COMMA
+ ;
+
+verdict_expr : ACCEPT
+ {
+ $$ = verdict_expr_alloc(&@1, NF_ACCEPT, NULL);
+ }
+ | DROP
+ {
+ $$ = verdict_expr_alloc(&@1, NF_DROP, NULL);
+ }
+ | QUEUE
+ {
+ $$ = verdict_expr_alloc(&@1, NF_QUEUE, NULL);
+ }
+ | CONTINUE
+ {
+ $$ = verdict_expr_alloc(&@1, NFT_CONTINUE, NULL);
+ }
+ | JUMP identifier
+ {
+ $$ = verdict_expr_alloc(&@1, NFT_JUMP, $2);
+ }
+ | GOTO identifier
+ {
+ $$ = verdict_expr_alloc(&@1, NFT_GOTO, $2);
+ }
+ | RETURN
+ {
+ $$ = verdict_expr_alloc(&@1, NFT_RETURN, NULL);
+ }
+ ;
+
+meta_expr : META meta_key
+ {
+ $$ = meta_expr_alloc(&@$, $2);
+ }
+ ;
+
+meta_key : LENGTH { $$ = NFT_META_LEN; }
+ | PROTOCOL { $$ = NFT_META_PROTOCOL; }
+ | PRIORITY { $$ = NFT_META_PRIORITY; }
+ | 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; }
+ | SECMARK { $$ = NFT_META_SECMARK; }
+ ;
+
+meta_stmt : META meta_key SET expr
+ {
+ $$ = meta_stmt_alloc(&@$, $2, $4);
+ }
+ ;
+
+ct_expr : CT ct_key
+ {
+ $$ = ct_expr_alloc(&@$, $2);
+ }
+ ;
+
+ct_key : STATE { $$ = NFT_CT_STATE; }
+ | DIRECTION { $$ = NFT_CT_DIRECTION; }
+ | STATUS { $$ = NFT_CT_STATUS; }
+ | MARK { $$ = NFT_CT_MARK; }
+ | SECMARK { $$ = NFT_CT_SECMARK; }
+ | EXPIRATION { $$ = NFT_CT_EXPIRATION; }
+ | HELPER { $$ = NFT_CT_HELPER; }
+ | PROTOCOL { $$ = NFT_CT_PROTOCOL; }
+ | SADDR { $$ = NFT_CT_SADDR; }
+ | DADDR { $$ = NFT_CT_DADDR; }
+ | PROTO_SRC { $$ = NFT_CT_PROTO_SRC; }
+ | PROTO_DST { $$ = NFT_CT_PROTO_DST; }
+ ;
+
+payload_expr : payload_raw_expr
+ | eth_hdr_expr
+ | vlan_hdr_expr
+ | arp_hdr_expr
+ | ip_hdr_expr
+ | icmp_hdr_expr
+ | ip6_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
+ ;
+
+payload_raw_expr : AT payload_base_spec COMMA NUM COMMA NUM
+ {
+ $$ = payload_expr_alloc(&@$, NULL, 0);
+ $$->payload.base = $2;
+ $$->payload.offset = $4;
+ $$->len = $6;
+ $$->dtype = &integer_type;
+ }
+ ;
+
+payload_base_spec : LL_HDR { $$ = PAYLOAD_BASE_LL_HDR; }
+ | NETWORK_HDR { $$ = PAYLOAD_BASE_NETWORK_HDR; }
+ | TRANSPORT_HDR { $$ = PAYLOAD_BASE_TRANSPORT_HDR; }
+ ;
+
+eth_hdr_expr : ETH eth_hdr_field
+ {
+ $$ = payload_expr_alloc(&@$, &payload_eth, $2);
+ }
+ ;
+
+eth_hdr_field : SADDR { $$ = ETHHDR_SADDR; }
+ | DADDR { $$ = ETHHDR_DADDR; }
+ | TYPE { $$ = ETHHDR_TYPE; }
+ ;
+
+vlan_hdr_expr : VLAN vlan_hdr_field
+ {
+ $$ = payload_expr_alloc(&@$, &payload_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(&@$, &payload_arp, $2);
+ }
+ ;
+
+arp_hdr_field : HTYPE { $$ = ARPHDR_HRD; }
+ | PTYPE { $$ = ARPHDR_PRO; }
+ | HLEN { $$ = ARPHDR_HLN; }
+ | PLEN { $$ = ARPHDR_PLN; }
+ | OPERATION { $$ = ARPHDR_OP; }
+ ;
+
+ip_hdr_expr : IP ip_hdr_field
+ {
+ $$ = payload_expr_alloc(&@$, &payload_ip, $2);
+ }
+ ;
+
+ip_hdr_field : VERSION { $$ = IPHDR_VERSION; }
+ | HDRLENGTH { $$ = IPHDR_HDRLENGTH; }
+ | TOS { $$ = IPHDR_TOS; }
+ | 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; }
+ ;
+
+icmp_hdr_expr : ICMP icmp_hdr_field
+ {
+ $$ = payload_expr_alloc(&@$, &payload_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; }
+ ;
+
+ip6_hdr_expr : IP6 ip6_hdr_field
+ {
+ $$ = payload_expr_alloc(&@$, &payload_ip6, $2);
+ }
+ ;
+
+ip6_hdr_field : VERSION { $$ = IP6HDR_VERSION; }
+ | PRIORITY { $$ = IP6HDR_PRIORITY; }
+ | FLOWLABEL { $$ = IP6HDR_FLOWLABEL; }
+ | LENGTH { $$ = IP6HDR_LENGTH; }
+ | NEXTHDR { $$ = IP6HDR_NEXTHDR; }
+ | HOPLIMIT { $$ = IP6HDR_HOPLIMIT; }
+ | SADDR { $$ = IP6HDR_SADDR; }
+ | DADDR { $$ = IP6HDR_DADDR; }
+ ;
+
+auth_hdr_expr : AH auth_hdr_field
+ {
+ $$ = payload_expr_alloc(&@$, &payload_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(&@$, &payload_esp, $2);
+ }
+ ;
+
+esp_hdr_field : SPI { $$ = ESPHDR_SPI; }
+ | SEQUENCE { $$ = ESPHDR_SEQUENCE; }
+ ;
+
+comp_hdr_expr : COMP comp_hdr_field
+ {
+ $$ = payload_expr_alloc(&@$, &payload_comp, $2);
+ }
+ ;
+
+comp_hdr_field : NEXTHDR { $$ = COMPHDR_NEXTHDR; }
+ | FLAGS { $$ = COMPHDR_FLAGS; }
+ | CPI { $$ = COMPHDR_CPI; }
+ ;
+
+udp_hdr_expr : UDP udp_hdr_field
+ {
+ $$ = payload_expr_alloc(&@$, &payload_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(&@$, &payload_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(&@$, &payload_tcp, $2);
+ }
+ ;
+
+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; }
+ ;
+
+dccp_hdr_expr : DCCP dccp_hdr_field
+ {
+ $$ = payload_expr_alloc(&@$, &payload_dccp, $2);
+ }
+ ;
+
+dccp_hdr_field : SPORT { $$ = DCCPHDR_SPORT; }
+ | DPORT { $$ = DCCPHDR_DPORT; }
+ ;
+
+sctp_hdr_expr : SCTP sctp_hdr_field
+ {
+ $$ = payload_expr_alloc(&@$, &payload_sctp, $2);
+ }
+ ;
+
+sctp_hdr_field : SPORT { $$ = SCTPHDR_SPORT; }
+ | DPORT { $$ = SCTPHDR_DPORT; }
+ | VTAG { $$ = SCTPHDR_VTAG; }
+ | CHECKSUM { $$ = SCTPHDR_CHECKSUM; }
+ ;
+
+exthdr_expr : hbh_hdr_expr
+ | rt_hdr_expr
+ | rt0_hdr_expr
+ | rt2_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; }
+ ;
+
+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; }
+ ;
+
+%%
diff --git a/src/payload.c b/src/payload.c
new file mode 100644
index 00000000..b7fbcb36
--- /dev/null
+++ b/src/payload.c
@@ -0,0 +1,908 @@
+/*
+ * Payload expression protocol and type definitions and related functions.
+ *
+ * Copyright (c) 2008 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 <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <net/if_arp.h>
+#include <arpa/inet.h>
+#include <linux/netfilter.h>
+
+#include <rule.h>
+#include <expression.h>
+#include <payload.h>
+#include <headers.h>
+#include <gmputil.h>
+#include <utils.h>
+
+static const char *payload_base_names[] = {
+ [PAYLOAD_BASE_INVALID] = "invalid",
+ [PAYLOAD_BASE_LL_HDR] = "link layer",
+ [PAYLOAD_BASE_NETWORK_HDR] = "network layer",
+ [PAYLOAD_BASE_TRANSPORT_HDR] = "transport layer",
+};
+
+static const char *payload_base_tokens[] = {
+ [PAYLOAD_BASE_INVALID] = "invalid",
+ [PAYLOAD_BASE_LL_HDR] = "ll",
+ [PAYLOAD_BASE_NETWORK_HDR] = "nh",
+ [PAYLOAD_BASE_TRANSPORT_HDR] = "th",
+};
+
+static const struct payload_template payload_unknown_template =
+ PAYLOAD_TEMPLATE("unknown", &invalid_type, 0, 0);
+
+static const struct payload_desc payload_unknown_desc = {
+ .name = "unknown",
+ .base = PAYLOAD_BASE_INVALID,
+};
+
+static void payload_expr_print(const struct expr *expr)
+{
+ const struct payload_desc *desc;
+ const struct payload_template *tmpl;
+
+ desc = expr->payload.desc;
+ tmpl = expr->payload.tmpl;
+ if (desc != NULL && tmpl != NULL)
+ printf("%s %s", desc->name, tmpl->token);
+ else
+ printf("payload @%s,%u,%u",
+ payload_base_tokens[expr->payload.base],
+ expr->payload.offset, expr->len);
+}
+
+static const struct expr_ops payload_expr_ops = {
+ .type = EXPR_PAYLOAD,
+ .name = "payload",
+ .print = payload_expr_print,
+};
+
+struct expr *payload_expr_alloc(const struct location *loc,
+ const struct payload_desc *desc,
+ unsigned int type)
+{
+ const struct payload_template *tmpl;
+ enum payload_bases base;
+ struct expr *expr;
+ unsigned int flags = 0;
+
+ if (desc != NULL) {
+ tmpl = &desc->templates[type];
+ base = desc->base;
+ if (type == desc->protocol_key)
+ flags = PAYLOAD_PROTOCOL_EXPR;
+ } else {
+ tmpl = &payload_unknown_template;
+ base = PAYLOAD_BASE_INVALID;
+ }
+
+ expr = expr_alloc(loc, &payload_expr_ops, tmpl->dtype,
+ BYTEORDER_BIG_ENDIAN, tmpl->len);
+ expr->payload.desc = desc;
+ expr->payload.tmpl = tmpl;
+ expr->payload.base = base;
+ expr->payload.offset = tmpl->offset;
+ expr->payload.flags = flags;
+ return expr;
+}
+
+void payload_init_raw(struct expr *expr, enum payload_bases base,
+ unsigned int offset, unsigned int len)
+{
+ expr->payload.base = base;
+ expr->payload.offset = offset;
+ expr->len = len;
+}
+
+/**
+ * payload_select_proto - find protocol description by protocol value linking
+ * it to lower layer protocol
+ *
+ * @base: lower layer protocol description
+ * @num: protocol value
+ */
+static const struct payload_desc *
+payload_select_proto(const struct payload_desc *base, unsigned int num)
+{
+ unsigned int i;
+
+ for (i = 0; i < array_size(base->protocols); i++) {
+ if (base->protocols[i].num == num)
+ return base->protocols[i].desc;
+ }
+ return NULL;
+}
+
+/**
+ * payload_proto_val - return protocol number linking two protocols together
+ *
+ * @base: lower layer protocol description
+ * @desc: upper layer protocol description
+ */
+static unsigned int payload_proto_val(const struct payload_desc *base,
+ const struct payload_desc *desc)
+{
+ unsigned int i;
+
+ for (i = 0; i < array_size(base->protocols); i++) {
+ if (base->protocols[i].desc == desc)
+ return base->protocols[i].num;
+ }
+ return 0;
+}
+
+static const struct dev_payload_desc dev_payload_desc[] = {
+ DEV_PAYLOAD_DESC(ARPHRD_ETHER, &payload_eth),
+};
+
+/**
+ * payload_dev_type - return arphrd type linking a device and a protocol together
+ *
+ * @desc: the protocol description
+ * @res: pointer to result
+ */
+static int payload_dev_type(const struct payload_desc *desc, uint16_t *res)
+{
+ unsigned int i;
+
+ for (i = 0; i < array_size(dev_payload_desc); i++) {
+ if (dev_payload_desc[i].desc == desc) {
+ *res = dev_payload_desc[i].type;
+ return 0;
+ }
+ }
+ return -1;
+}
+
+/**
+ * payload_dev_desc - return protocol description for an arphrd type
+ *
+ * @type: the arphrd type
+ */
+static const struct payload_desc *payload_dev_desc(uint16_t type)
+{
+ unsigned int i;
+
+ for (i = 0; i < array_size(dev_payload_desc); i++) {
+ if (dev_payload_desc[i].type == type)
+ return dev_payload_desc[i].desc;
+ }
+ return NULL;
+}
+
+static const struct payload_hook_desc payload_hooks[] = {
+ [NFPROTO_BRIDGE] = PAYLOAD_HOOK(PAYLOAD_BASE_LL_HDR, &payload_eth),
+ [NFPROTO_IPV4] = PAYLOAD_HOOK(PAYLOAD_BASE_NETWORK_HDR, &payload_ip),
+ [NFPROTO_IPV6] = PAYLOAD_HOOK(PAYLOAD_BASE_NETWORK_HDR, &payload_ip6),
+ [NFPROTO_ARP] = PAYLOAD_HOOK(PAYLOAD_BASE_NETWORK_HDR, &payload_arp),
+};
+
+/**
+ * payload_ctx_init - initialize payload context for a given hook family
+ *
+ * @ctx: payload context
+ * @family: hook family
+ */
+void payload_ctx_init(struct payload_ctx *ctx, unsigned int family)
+{
+ const struct payload_hook_desc *h = &payload_hooks[family];
+
+ memset(ctx, 0, sizeof(*ctx));
+ ctx->family = family;
+ ctx->protocol[h->base].desc = h->desc;
+}
+
+/**
+ * payload_ctx_update_meta - update payload context with meta expression
+ *
+ * @ctx: payload context
+ * @expr: relational meta expression
+ *
+ * Update LL payload context based on IIFTYPE meta match in non-LL hooks.
+ */
+void payload_ctx_update_meta(struct payload_ctx *ctx, const struct expr *expr)
+{
+ const struct payload_hook_desc *h = &payload_hooks[ctx->family];
+ const struct expr *left = expr->left, *right = expr->right;
+ const struct payload_desc *desc;
+
+ if (left->meta.key != NFT_META_IIFTYPE)
+ return;
+
+ assert(expr->op == OP_EQ);
+ if (h->base < PAYLOAD_BASE_NETWORK_HDR)
+ return;
+
+ desc = payload_dev_desc(mpz_get_uint16(right->value));
+ if (desc == NULL)
+ desc = &payload_unknown_desc;
+
+ ctx->protocol[PAYLOAD_BASE_LL_HDR].location = expr->location;
+ ctx->protocol[PAYLOAD_BASE_LL_HDR].desc = desc;
+}
+
+/**
+ * payload_ctx_update - update payload context
+ *
+ * @ctx: payload context
+ * @expr: relational payload expression
+ *
+ * Update payload context for relational payload expressions.
+ */
+void payload_ctx_update(struct payload_ctx *ctx, const struct expr *expr)
+{
+ const struct expr *left = expr->left, *right = expr->right;
+ const struct payload_desc *base, *desc;
+
+ if (!(left->payload.flags & PAYLOAD_PROTOCOL_EXPR))
+ return;
+
+ assert(expr->op == OP_EQ);
+ base = ctx->protocol[left->payload.base].desc;
+ desc = payload_select_proto(base, mpz_get_uint32(right->value));
+
+ ctx->protocol[left->payload.base + 1].location = expr->location;
+ ctx->protocol[left->payload.base + 1].desc = desc;
+}
+
+/**
+ * payload_gen_dependency - generate match expression on payload dependency
+ *
+ * @ctx: evaluation context
+ * @expr: payload expression
+ * @res: dependency expression
+ *
+ * Generate matches on protocol dependencies. There are two different kinds
+ * of dependencies:
+ *
+ * - A payload expression for a base above the hook base requires a match
+ * on the protocol value in the lower layer header.
+ *
+ * - A payload expression for a base below the hook base is invalid in the
+ * output path since the lower layer header does not exist when the packet
+ * is classified. In the input path a payload expressions for a base exactly
+ * one below the hook base is valid. In this case a match on the device type
+ * is required to verify that we're dealing with the expected protocol.
+ *
+ * Note: since it is unknown to userspace which hooks a chain is called from,
+ * it is not explicitly verified. The NFT_META_IIFTYPE match will only match
+ * in the input path though.
+ */
+int payload_gen_dependency(struct eval_ctx *ctx, const struct expr *expr,
+ struct expr **res)
+{
+ const struct payload_hook_desc *h = &payload_hooks[ctx->pctx.family];
+ const struct payload_desc *desc;
+ const struct payload_template *tmpl;
+ struct expr *dep, *left, *right;
+ unsigned int protocol;
+ uint16_t type;
+
+ if (expr->payload.base < h->base) {
+ if (expr->payload.base < h->base - 1)
+ return expr_error(ctx, expr,
+ "payload base is invalid for this "
+ "family");
+
+ if (payload_dev_type(expr->payload.desc, &type) < 0)
+ return expr_error(ctx, expr,
+ "protocol specification is invalid "
+ "for this family");
+
+ left = meta_expr_alloc(&expr->location, NFT_META_IIFTYPE);
+ right = constant_expr_alloc(&expr->location, &arphrd_type,
+ BYTEORDER_HOST_ENDIAN,
+ 2 * BITS_PER_BYTE, &type);
+
+ dep = relational_expr_alloc(&expr->location, OP_EQ, left, right);
+ *res = dep;
+ return 0;
+ }
+
+ desc = ctx->pctx.protocol[expr->payload.base - 1].desc;
+ if (desc == NULL)
+ return expr_error(ctx, expr,
+ "ambiguous payload specification: "
+ "no %s protocol specified",
+ payload_base_names[expr->payload.base - 1]);
+
+ tmpl = &desc->templates[desc->protocol_key];
+ left = payload_expr_alloc(&expr->location, desc, desc->protocol_key);
+ protocol = payload_proto_val(desc, expr->payload.desc);
+ right = constant_expr_alloc(&expr->location, tmpl->dtype,
+ BYTEORDER_HOST_ENDIAN,
+ tmpl->len, &protocol);
+
+ dep = relational_expr_alloc(&expr->location, OP_EQ, left, right);
+ payload_ctx_update(&ctx->pctx, dep);
+ *res = dep;
+ return 0;
+}
+
+/**
+ * payload_expr_complete - fill in type information of a raw payload expr
+ *
+ * @expr: the payload expression
+ * @ctx: payload context
+ *
+ * Complete the type of a raw payload expression based on the context. If
+ * insufficient information is available the expression remains unchanged.
+ */
+void payload_expr_complete(struct expr *expr, const struct payload_ctx *ctx)
+{
+ const struct payload_desc *desc;
+ const struct payload_template *tmpl;
+ unsigned int i;
+
+ assert(expr->ops->type == EXPR_PAYLOAD);
+
+ desc = ctx->protocol[expr->payload.base].desc;
+ if (desc == NULL)
+ return;
+ assert(desc->base == expr->payload.base);
+
+ for (i = 0; i < array_size(desc->templates); i++) {
+ tmpl = &desc->templates[i];
+ if (tmpl->offset != expr->payload.offset ||
+ tmpl->len != expr->len)
+ continue;
+ expr->dtype = tmpl->dtype;
+ expr->payload.desc = desc;
+ expr->payload.tmpl = tmpl;
+ return;
+ }
+}
+
+/**
+ * payload_expr_expand - expand raw merged adjacent payload expressions into its
+ * original components
+ *
+ * @list: list to append expanded payload expressions to
+ * @expr: the payload expression to expand
+ * @ctx: payload context
+ *
+ * Expand a merged adjacent payload expression into its original components
+ * by splitting elements off the beginning matching a payload template.
+ *
+ * Note: this requires all payload templates to be specified in ascending
+ * offset order.
+ */
+void payload_expr_expand(struct list_head *list, struct expr *expr,
+ const struct payload_ctx *ctx)
+{
+ const struct payload_desc *desc;
+ const struct payload_template *tmpl;
+ struct expr *new;
+ unsigned int i;
+
+ assert(expr->ops->type == EXPR_PAYLOAD);
+
+ desc = ctx->protocol[expr->payload.base].desc;
+ if (desc == NULL)
+ goto raw;
+ assert(desc->base == expr->payload.base);
+
+ for (i = 1; i < array_size(desc->templates); i++) {
+ tmpl = &desc->templates[i];
+ if (tmpl->offset != expr->payload.offset)
+ continue;
+
+ if (tmpl->len <= expr->len) {
+ new = payload_expr_alloc(&expr->location, desc, i);
+ list_add_tail(&new->list, list);
+ expr->len -= tmpl->len;
+ expr->payload.offset += tmpl->len;
+ if (expr->len == 0)
+ return;
+ } else
+ break;
+ }
+raw:
+ new = payload_expr_alloc(&expr->location, NULL, 0);
+ payload_init_raw(new, expr->payload.base, expr->payload.offset,
+ expr->len);
+ list_add_tail(&new->list, list);
+}
+
+/**
+ * payload_is_adjacent - return whether two payload expressions refer to
+ * adjacent header locations
+ *
+ * @e1: first payload expression
+ * @e2: second payload expression
+ */
+bool payload_is_adjacent(const struct expr *e1, const struct expr *e2)
+{
+ if (e1->payload.base == e2->payload.base &&
+ e1->payload.offset + e1->len == e2->payload.offset)
+ return true;
+ return false;
+}
+
+/**
+ * payload_expr_join - join two adjacent payload expressions
+ *
+ * @e1: first payload expression
+ * @e2: second payload expression
+ */
+struct expr *payload_expr_join(const struct expr *e1, const struct expr *e2)
+{
+ struct expr *expr;
+
+ assert(payload_is_adjacent(e1, e2));
+
+ expr = payload_expr_alloc(&internal_location, NULL, 0);
+ expr->payload.base = e1->payload.base;
+ expr->payload.offset = e1->payload.offset;
+ expr->len = e1->len + e2->len;
+ return expr;
+}
+
+#define HDR_TEMPLATE(__name, __dtype, __type, __member) \
+ PAYLOAD_TEMPLATE(__name, __dtype, \
+ offsetof(__type, __member) * 8, \
+ field_sizeof(__type, __member) * 8)
+
+#define HDR_FIELD(__name, __struct, __member) \
+ HDR_TEMPLATE(__name, &integer_type, __struct, __member)
+#define HDR_BITFIELD(__name, __dtype, __offset, __len) \
+ PAYLOAD_TEMPLATE(__name, __dtype, __offset, __len)
+#define HDR_TYPE(__name, __dtype, __struct, __member) \
+ HDR_TEMPLATE(__name, __dtype, __struct, __member)
+
+#define INET_PROTOCOL(__name, __struct, __member) \
+ HDR_TYPE(__name, &inet_protocol_type, __struct, __member)
+#define INET_SERVICE(__name, __struct, __member) \
+ HDR_TYPE(__name, &inet_service_type, __struct, __member)
+
+/*
+ * AH
+ */
+
+#define AHHDR_FIELD(__name, __member) \
+ HDR_FIELD(__name, struct ip_auth_hdr, __member)
+
+const struct payload_desc payload_ah = {
+ .name = "ah",
+ .base = PAYLOAD_BASE_TRANSPORT_HDR,
+ .protocol_key = AHHDR_NEXTHDR,
+ .protocols = {
+ PAYLOAD_PROTO(IPPROTO_ESP, &payload_esp),
+ PAYLOAD_PROTO(IPPROTO_AH, &payload_ah),
+ PAYLOAD_PROTO(IPPROTO_COMP, &payload_comp),
+ PAYLOAD_PROTO(IPPROTO_UDP, &payload_udp),
+ PAYLOAD_PROTO(IPPROTO_UDPLITE, &payload_udplite),
+ PAYLOAD_PROTO(IPPROTO_TCP, &payload_tcp),
+ PAYLOAD_PROTO(IPPROTO_DCCP, &payload_dccp),
+ PAYLOAD_PROTO(IPPROTO_SCTP, &payload_sctp),
+ },
+ .templates = {
+ [AHHDR_NEXTHDR] = INET_PROTOCOL("nexthdr", struct ip_auth_hdr, nexthdr),
+ [AHHDR_HDRLENGTH] = AHHDR_FIELD("hdrlength", hdrlen),
+ [AHHDR_RESERVED] = AHHDR_FIELD("reserved", reserved),
+ [AHHDR_SPI] = AHHDR_FIELD("spi", spi),
+ [AHHDR_SEQUENCE] = AHHDR_FIELD("sequence", seq_no),
+ },
+};
+
+/*
+ * ESP
+ */
+
+#define ESPHDR_FIELD(__name, __member) \
+ HDR_FIELD(__name, struct ip_esp_hdr, __member)
+
+const struct payload_desc payload_esp = {
+ .name = "esp",
+ .base = PAYLOAD_BASE_TRANSPORT_HDR,
+ .templates = {
+ [ESPHDR_SPI] = ESPHDR_FIELD("spi", spi),
+ [ESPHDR_SEQUENCE] = ESPHDR_FIELD("sequence", seq_no),
+ },
+};
+
+/*
+ * IPCOMP
+ */
+
+#define COMPHDR_FIELD(__name, __member) \
+ HDR_FIELD(__name, struct ip_comp_hdr, __member)
+
+const struct payload_desc payload_comp = {
+ .name = "comp",
+ .base = PAYLOAD_BASE_TRANSPORT_HDR,
+ .protocol_key = COMPHDR_NEXTHDR,
+ .protocols = {
+ PAYLOAD_PROTO(IPPROTO_ESP, &payload_esp),
+ PAYLOAD_PROTO(IPPROTO_AH, &payload_ah),
+ PAYLOAD_PROTO(IPPROTO_COMP, &payload_comp),
+ PAYLOAD_PROTO(IPPROTO_UDP, &payload_udp),
+ PAYLOAD_PROTO(IPPROTO_UDPLITE, &payload_udplite),
+ PAYLOAD_PROTO(IPPROTO_TCP, &payload_tcp),
+ PAYLOAD_PROTO(IPPROTO_DCCP, &payload_dccp),
+ PAYLOAD_PROTO(IPPROTO_SCTP, &payload_sctp),
+ },
+ .templates = {
+ [COMPHDR_NEXTHDR] = INET_PROTOCOL("nexthdr", struct ip_comp_hdr, nexthdr),
+ [COMPHDR_FLAGS] = COMPHDR_FIELD("flags", flags),
+ [COMPHDR_CPI] = COMPHDR_FIELD("cpi", cpi),
+ },
+};
+
+/*
+ * ICMP
+ */
+
+#include <netinet/ip_icmp.h>
+
+static const struct symbol_table icmp_type_tbl = {
+ .byteorder = BYTEORDER_BIG_ENDIAN,
+ .size = BITS_PER_BYTE,
+ .symbols = {
+ SYMBOL("echo-reply", ICMP_ECHOREPLY),
+ SYMBOL("destination-unreachable", ICMP_DEST_UNREACH),
+ SYMBOL("source-quench", ICMP_SOURCE_QUENCH),
+ SYMBOL("redirect", ICMP_REDIRECT),
+ SYMBOL("echo-request", ICMP_ECHO),
+ SYMBOL("time-exceeded", ICMP_TIME_EXCEEDED),
+ SYMBOL("parameter-problem", ICMP_PARAMETERPROB),
+ SYMBOL("timestamp-request", ICMP_TIMESTAMP),
+ SYMBOL("timestamp-reply", ICMP_TIMESTAMPREPLY),
+ SYMBOL("info-request", ICMP_INFO_REQUEST),
+ SYMBOL("info-reply", ICMP_INFO_REPLY),
+ SYMBOL("address-mask-request", ICMP_ADDRESS),
+ SYMBOL("address-mask-reply", ICMP_ADDRESSREPLY),
+ SYMBOL_LIST_END
+ },
+};
+
+static const struct datatype icmp_type_type = {
+ .type = TYPE_ICMP_TYPE,
+ .name = "ICMP type",
+ .basetype = &integer_type,
+ .sym_tbl = &icmp_type_tbl,
+};
+
+#define ICMPHDR_FIELD(__name, __member) \
+ HDR_FIELD(__name, struct icmphdr, __member)
+#define ICMPHDR_TYPE(__name, __type, __member) \
+ HDR_TYPE(__name, __type, struct icmphdr, __member)
+
+const struct payload_desc payload_icmp = {
+ .name = "icmp",
+ .base = PAYLOAD_BASE_TRANSPORT_HDR,
+ .templates = {
+ [ICMPHDR_TYPE] = ICMPHDR_TYPE("type", &icmp_type_type, type),
+ [ICMPHDR_CODE] = ICMPHDR_FIELD("code", code),
+ [ICMPHDR_CHECKSUM] = ICMPHDR_FIELD("checksum", checksum),
+ [ICMPHDR_ID] = ICMPHDR_FIELD("id", un.echo.id),
+ [ICMPHDR_SEQ] = ICMPHDR_FIELD("sequence", un.echo.sequence),
+ [ICMPHDR_GATEWAY] = ICMPHDR_FIELD("gateway", un.gateway),
+ [ICMPHDR_MTU] = ICMPHDR_FIELD("mtu", un.frag.mtu),
+ },
+};
+
+/*
+ * UDP/UDP-Lite
+ */
+
+#include <netinet/udp.h>
+#define UDPHDR_FIELD(__name, __member) \
+ HDR_FIELD(__name, struct udphdr, __member)
+
+const struct payload_desc payload_udp = {
+ .name = "udp",
+ .base = PAYLOAD_BASE_TRANSPORT_HDR,
+ .templates = {
+ [UDPHDR_SPORT] = INET_SERVICE("sport", struct udphdr, source),
+ [UDPHDR_DPORT] = INET_SERVICE("dport", struct udphdr, dest),
+ [UDPHDR_LENGTH] = UDPHDR_FIELD("length", len),
+ [UDPHDR_CHECKSUM] = UDPHDR_FIELD("checksum", check),
+ },
+};
+
+const struct payload_desc payload_udplite = {
+ .name = "udplite",
+ .base = PAYLOAD_BASE_TRANSPORT_HDR,
+ .templates = {
+ [UDPHDR_SPORT] = INET_SERVICE("sport", struct udphdr, source),
+ [UDPHDR_DPORT] = INET_SERVICE("dport", struct udphdr, dest),
+ [UDPHDR_CSUMCOV] = UDPHDR_FIELD("csumcov", len),
+ [UDPHDR_CHECKSUM] = UDPHDR_FIELD("checksum", check),
+ },
+};
+
+/*
+ * TCP
+ */
+
+#include <netinet/tcp.h>
+
+static const struct symbol_table tcp_flag_tbl = {
+ .byteorder = BYTEORDER_BIG_ENDIAN,
+ .size = BITS_PER_BYTE,
+ .symbols = {
+ SYMBOL("fin", TCP_FLAG_FIN),
+ SYMBOL("syn", TCP_FLAG_SYN),
+ SYMBOL("rst", TCP_FLAG_RST),
+ SYMBOL("psh", TCP_FLAG_PSH),
+ SYMBOL("ack", TCP_FLAG_ACK),
+ SYMBOL("urh", TCP_FLAG_URG),
+ SYMBOL("ecn", TCP_FLAG_ECN),
+ SYMBOL("cwr", TCP_FLAG_CWR),
+ SYMBOL_LIST_END
+ },
+};
+
+static const struct datatype tcp_flag_type = {
+ .type = TYPE_TCP_FLAG,
+ .name = "TCP flag",
+ .basetype = &bitmask_type,
+ .sym_tbl = &tcp_flag_tbl,
+};
+
+#define TCPHDR_FIELD(__name, __member) \
+ HDR_FIELD(__name, struct tcphdr, __member)
+
+const struct payload_desc payload_tcp = {
+ .name = "tcp",
+ .base = PAYLOAD_BASE_TRANSPORT_HDR,
+ .templates = {
+ [TCPHDR_SPORT] = INET_SERVICE("sport", struct tcphdr, source),
+ [TCPHDR_DPORT] = INET_SERVICE("dport", struct tcphdr, dest),
+ [TCPHDR_SEQ] = TCPHDR_FIELD("sequence", seq),
+ [TCPHDR_ACKSEQ] = TCPHDR_FIELD("ackseq", ack_seq),
+ [TCPHDR_DOFF] = {},
+ [TCPHDR_RESERVED] = {},
+ [TCPHDR_FLAGS] = HDR_BITFIELD("flags", &tcp_flag_type,
+ 13 * BITS_PER_BYTE,
+ BITS_PER_BYTE),
+ [TCPHDR_WINDOW] = TCPHDR_FIELD("window", window),
+ [TCPHDR_CHECKSUM] = TCPHDR_FIELD("checksum", check),
+ [TCPHDR_URGPTR] = TCPHDR_FIELD("urgptr", urg_ptr),
+ },
+};
+
+/*
+ * DCCP
+ */
+
+#define DCCPHDR_FIELD(__name, __member) \
+ HDR_FIELD(__name, struct dccp_hdr, __member)
+
+const struct payload_desc payload_dccp = {
+ .name = "dccp",
+ .base = PAYLOAD_BASE_TRANSPORT_HDR,
+ .templates = {
+ [DCCPHDR_SPORT] = INET_SERVICE("sport", struct dccp_hdr, dccph_sport),
+ [DCCPHDR_DPORT] = INET_SERVICE("dport", struct dccp_hdr, dccph_dport),
+ },
+};
+
+/*
+ * SCTP
+ */
+
+#define SCTPHDR_FIELD(__name, __member) \
+ HDR_FIELD(__name, struct sctphdr, __member)
+
+const struct payload_desc payload_sctp = {
+ .name = "sctp",
+ .base = PAYLOAD_BASE_TRANSPORT_HDR,
+ .templates = {
+ [SCTPHDR_SPORT] = INET_SERVICE("sport", struct sctphdr, source),
+ [SCTPHDR_DPORT] = INET_SERVICE("dport", struct sctphdr, dest),
+ [SCTPHDR_VTAG] = SCTPHDR_FIELD("vtag", vtag),
+ [SCTPHDR_CHECKSUM] = SCTPHDR_FIELD("checksum", checksum),
+ },
+};
+
+/*
+ * IPv4
+ */
+
+#include <netinet/ip.h>
+#define IPHDR_FIELD(__name, __member) \
+ HDR_FIELD(__name, struct iphdr, __member)
+#define IPHDR_ADDR(__name, __member) \
+ HDR_TYPE(__name, &ipaddr_type, struct iphdr, __member)
+
+const struct payload_desc payload_ip = {
+ .name = "ip",
+ .base = PAYLOAD_BASE_NETWORK_HDR,
+ .protocol_key = IPHDR_PROTOCOL,
+ .protocols = {
+ PAYLOAD_PROTO(IPPROTO_ICMP, &payload_icmp),
+ PAYLOAD_PROTO(IPPROTO_ESP, &payload_esp),
+ PAYLOAD_PROTO(IPPROTO_AH, &payload_ah),
+ PAYLOAD_PROTO(IPPROTO_COMP, &payload_comp),
+ PAYLOAD_PROTO(IPPROTO_UDP, &payload_udp),
+ PAYLOAD_PROTO(IPPROTO_UDPLITE, &payload_udplite),
+ PAYLOAD_PROTO(IPPROTO_TCP, &payload_tcp),
+ PAYLOAD_PROTO(IPPROTO_DCCP, &payload_dccp),
+ PAYLOAD_PROTO(IPPROTO_SCTP, &payload_sctp),
+ },
+ .templates = {
+ [IPHDR_VERSION] = HDR_BITFIELD("version", &integer_type, 0, 4),
+ [IPHDR_HDRLENGTH] = HDR_BITFIELD("hdrlength", &integer_type, 4, 4),
+ [IPHDR_TOS] = IPHDR_FIELD("tos", tos),
+ [IPHDR_LENGTH] = IPHDR_FIELD("length", tot_len),
+ [IPHDR_ID] = IPHDR_FIELD("id", id),
+ [IPHDR_FRAG_OFF] = IPHDR_FIELD("frag-off", frag_off),
+ [IPHDR_TTL] = IPHDR_FIELD("ttl", ttl),
+ [IPHDR_PROTOCOL] = INET_PROTOCOL("protocol", struct iphdr, protocol),
+ [IPHDR_CHECKSUM] = IPHDR_FIELD("checksum", check),
+ [IPHDR_SADDR] = IPHDR_ADDR("saddr", saddr),
+ [IPHDR_DADDR] = IPHDR_ADDR("daddr", daddr),
+ },
+};
+
+/*
+ * IPv6
+ */
+
+#define IP6HDR_FIELD(__name, __member) \
+ HDR_FIELD(__name, struct ipv6hdr, __member)
+#define IP6HDR_ADDR(__name, __member) \
+ HDR_TYPE(__name, &ip6addr_type, struct ipv6hdr, __member)
+#define IP6HDR_PROTOCOL(__name, __member) \
+ HDR_TYPE(__name, &inet_service_type, struct ipv6hdr, __member)
+
+const struct payload_desc payload_ip6 = {
+ .name = "ip6",
+ .base = PAYLOAD_BASE_NETWORK_HDR,
+ .protocol_key = IP6HDR_NEXTHDR,
+ .protocols = {
+ PAYLOAD_PROTO(IPPROTO_ESP, &payload_esp),
+ PAYLOAD_PROTO(IPPROTO_AH, &payload_ah),
+ PAYLOAD_PROTO(IPPROTO_COMP, &payload_comp),
+ PAYLOAD_PROTO(IPPROTO_UDP, &payload_udp),
+ PAYLOAD_PROTO(IPPROTO_UDPLITE, &payload_udplite),
+ PAYLOAD_PROTO(IPPROTO_TCP, &payload_tcp),
+ PAYLOAD_PROTO(IPPROTO_DCCP, &payload_dccp),
+ PAYLOAD_PROTO(IPPROTO_SCTP, &payload_sctp),
+ },
+ .templates = {
+ [IP6HDR_VERSION] = HDR_BITFIELD("version", &integer_type, 0, 4),
+ [IP6HDR_PRIORITY] = HDR_BITFIELD("priority", &integer_type, 4, 4),
+ [IP6HDR_FLOWLABEL] = IP6HDR_FIELD("flowlabel", flow_lbl),
+ [IP6HDR_LENGTH] = IP6HDR_FIELD("length", payload_len),
+ [IP6HDR_NEXTHDR] = INET_PROTOCOL("nexthdr", struct ipv6hdr, nexthdr),
+ [IP6HDR_HOPLIMIT] = IP6HDR_FIELD("hoplimit", hop_limit),
+ [IP6HDR_SADDR] = IP6HDR_ADDR("saddr", saddr),
+ [IP6HDR_DADDR] = IP6HDR_ADDR("daddr", daddr),
+ },
+};
+
+/*
+ * ARP
+ */
+
+#include <net/if_arp.h>
+
+static const struct symbol_table arpop_tbl = {
+ .byteorder = BYTEORDER_HOST_ENDIAN,
+ .size = 2 * BITS_PER_BYTE,
+ .symbols = {
+ SYMBOL("request", ARPOP_REQUEST),
+ SYMBOL("reply", ARPOP_REPLY),
+ SYMBOL("rrequest", ARPOP_RREQUEST),
+ SYMBOL("rreply", ARPOP_REPLY),
+ SYMBOL("inrequest", ARPOP_InREQUEST),
+ SYMBOL("inreply", ARPOP_InREPLY),
+ SYMBOL("nak", ARPOP_NAK),
+ },
+};
+
+static const struct datatype arpop_type = {
+ .type = TYPE_ARPOP,
+ .name = "ARP operation",
+ .basetype = &integer_type,
+ .sym_tbl = &arpop_tbl,
+};
+
+#define ARPHDR_TYPE(__name, __type, __member) \
+ HDR_TYPE(__name, __type, struct arphdr, __member)
+#define ARPHDR_FIELD(__name, __member) \
+ HDR_FIELD(__name, struct arphdr, __member)
+
+const struct payload_desc payload_arp = {
+ .name = "arp",
+ .base = PAYLOAD_BASE_NETWORK_HDR,
+ .templates = {
+ [ARPHDR_HRD] = ARPHDR_FIELD("htype", ar_hrd),
+ [ARPHDR_PRO] = ARPHDR_TYPE("ptype", &ethertype_type, ar_pro),
+ [ARPHDR_HLN] = ARPHDR_FIELD("hlen", ar_hln),
+ [ARPHDR_PLN] = ARPHDR_FIELD("plen", ar_pln),
+ [ARPHDR_OP] = ARPHDR_TYPE("operation", &arpop_type, ar_op),
+ },
+};
+
+/*
+ * VLAN
+ */
+
+#include <net/ethernet.h>
+
+#define VLANHDR_BITFIELD(__name, __offset, __len) \
+ HDR_BITFIELD(__name, &integer_type, __offset, __len)
+#define VLANHDR_TYPE(__name, __type, __member) \
+ HDR_TYPE(__name, __type, struct vlan_hdr, __member)
+
+const struct payload_desc payload_vlan = {
+ .name = "vlan",
+ .base = PAYLOAD_BASE_LL_HDR,
+ .protocol_key = VLANHDR_TYPE,
+ .protocols = {
+ PAYLOAD_PROTO(ETH_P_IP, &payload_ip),
+ PAYLOAD_PROTO(ETH_P_ARP, &payload_arp),
+ PAYLOAD_PROTO(ETH_P_IPV6, &payload_ip6),
+ PAYLOAD_PROTO(ETH_P_8021Q, &payload_vlan),
+
+ },
+ .templates = {
+ [VLANHDR_VID] = VLANHDR_BITFIELD("id", 0, 12),
+ [VLANHDR_CFI] = VLANHDR_BITFIELD("cfi", 12, 1),
+ [VLANHDR_PCP] = VLANHDR_BITFIELD("pcp", 13, 3),
+ [VLANHDR_TYPE] = VLANHDR_TYPE("type", &ethertype_type, vlan_type),
+ },
+};
+
+/*
+ * Ethernet
+ */
+
+static const struct symbol_table ethertype_tbl = {
+ .byteorder = BYTEORDER_HOST_ENDIAN,
+ .size = 2 * BITS_PER_BYTE,
+ .symbols = {
+ SYMBOL("ip", ETH_P_IP),
+ SYMBOL("arp", ETH_P_ARP),
+ SYMBOL("ipv6", ETH_P_IPV6),
+ SYMBOL("vlan", ETH_P_8021Q),
+ SYMBOL_LIST_END
+ },
+};
+
+const struct datatype ethertype_type = {
+ .type = TYPE_ETHERTYPE,
+ .name = "Ethernet protocol",
+ .basetype = &integer_type,
+ .sym_tbl = &ethertype_tbl,
+};
+
+#define ETHHDR_TEMPLATE(__name, __dtype, __member) \
+ HDR_TEMPLATE(__name, __dtype, struct ether_header, __member)
+#define ETHHDR_TYPE(__name, __member) \
+ ETHHDR_TEMPLATE(__name, &ethertype_type, __member)
+#define ETHHDR_ADDR(__name, __member) \
+ ETHHDR_TEMPLATE(__name, &lladdr_type, __member)
+
+const struct payload_desc payload_eth = {
+ .name = "eth",
+ .base = PAYLOAD_BASE_LL_HDR,
+ .protocol_key = ETHHDR_TYPE,
+ .protocols = {
+ PAYLOAD_PROTO(ETH_P_IP, &payload_ip),
+ PAYLOAD_PROTO(ETH_P_ARP, &payload_arp),
+ PAYLOAD_PROTO(ETH_P_IPV6, &payload_ip6),
+ PAYLOAD_PROTO(ETH_P_8021Q, &payload_vlan),
+ },
+ .templates = {
+ [ETHHDR_DADDR] = ETHHDR_ADDR("daddr", ether_dhost),
+ [ETHHDR_SADDR] = ETHHDR_ADDR("saddr", ether_shost),
+ [ETHHDR_TYPE] = ETHHDR_TYPE("type", ether_type),
+ },
+};
diff --git a/src/rbtree.c b/src/rbtree.c
new file mode 100644
index 00000000..325c0123
--- /dev/null
+++ b/src/rbtree.c
@@ -0,0 +1,388 @@
+/*
+ * Red Black Trees
+ * (C) 1999 Andrea Arcangeli <andrea@suse.de>
+ * (C) 2002 David Woodhouse <dwmw2@infradead.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <rbtree.h>
+
+static void __rb_rotate_left(struct rb_node *node, struct rb_root *root)
+{
+ struct rb_node *right = node->rb_right;
+ struct rb_node *parent = rb_parent(node);
+
+ if ((node->rb_right = right->rb_left))
+ rb_set_parent(right->rb_left, node);
+ right->rb_left = node;
+
+ rb_set_parent(right, parent);
+
+ if (parent)
+ {
+ if (node == parent->rb_left)
+ parent->rb_left = right;
+ else
+ parent->rb_right = right;
+ }
+ else
+ root->rb_node = right;
+ rb_set_parent(node, right);
+}
+
+static void __rb_rotate_right(struct rb_node *node, struct rb_root *root)
+{
+ struct rb_node *left = node->rb_left;
+ struct rb_node *parent = rb_parent(node);
+
+ if ((node->rb_left = left->rb_right))
+ rb_set_parent(left->rb_right, node);
+ left->rb_right = node;
+
+ rb_set_parent(left, parent);
+
+ if (parent)
+ {
+ if (node == parent->rb_right)
+ parent->rb_right = left;
+ else
+ parent->rb_left = left;
+ }
+ else
+ root->rb_node = left;
+ rb_set_parent(node, left);
+}
+
+void rb_insert_color(struct rb_node *node, struct rb_root *root)
+{
+ struct rb_node *parent, *gparent;
+
+ while ((parent = rb_parent(node)) && rb_is_red(parent))
+ {
+ gparent = rb_parent(parent);
+
+ if (parent == gparent->rb_left)
+ {
+ {
+ register struct rb_node *uncle = gparent->rb_right;
+ if (uncle && rb_is_red(uncle))
+ {
+ rb_set_black(uncle);
+ rb_set_black(parent);
+ rb_set_red(gparent);
+ node = gparent;
+ continue;
+ }
+ }
+
+ if (parent->rb_right == node)
+ {
+ register struct rb_node *tmp;
+ __rb_rotate_left(parent, root);
+ tmp = parent;
+ parent = node;
+ node = tmp;
+ }
+
+ rb_set_black(parent);
+ rb_set_red(gparent);
+ __rb_rotate_right(gparent, root);
+ } else {
+ {
+ register struct rb_node *uncle = gparent->rb_left;
+ if (uncle && rb_is_red(uncle))
+ {
+ rb_set_black(uncle);
+ rb_set_black(parent);
+ rb_set_red(gparent);
+ node = gparent;
+ continue;
+ }
+ }
+
+ if (parent->rb_left == node)
+ {
+ register struct rb_node *tmp;
+ __rb_rotate_right(parent, root);
+ tmp = parent;
+ parent = node;
+ node = tmp;
+ }
+
+ rb_set_black(parent);
+ rb_set_red(gparent);
+ __rb_rotate_left(gparent, root);
+ }
+ }
+
+ rb_set_black(root->rb_node);
+}
+
+static void __rb_erase_color(struct rb_node *node, struct rb_node *parent,
+ struct rb_root *root)
+{
+ struct rb_node *other;
+
+ while ((!node || rb_is_black(node)) && node != root->rb_node)
+ {
+ if (parent->rb_left == node)
+ {
+ other = parent->rb_right;
+ if (rb_is_red(other))
+ {
+ rb_set_black(other);
+ rb_set_red(parent);
+ __rb_rotate_left(parent, root);
+ other = parent->rb_right;
+ }
+ if ((!other->rb_left || rb_is_black(other->rb_left)) &&
+ (!other->rb_right || rb_is_black(other->rb_right)))
+ {
+ rb_set_red(other);
+ node = parent;
+ parent = rb_parent(node);
+ }
+ else
+ {
+ if (!other->rb_right || rb_is_black(other->rb_right))
+ {
+ struct rb_node *o_left;
+ if ((o_left = other->rb_left))
+ rb_set_black(o_left);
+ rb_set_red(other);
+ __rb_rotate_right(other, root);
+ other = parent->rb_right;
+ }
+ rb_set_color(other, rb_color(parent));
+ rb_set_black(parent);
+ if (other->rb_right)
+ rb_set_black(other->rb_right);
+ __rb_rotate_left(parent, root);
+ node = root->rb_node;
+ break;
+ }
+ }
+ else
+ {
+ other = parent->rb_left;
+ if (rb_is_red(other))
+ {
+ rb_set_black(other);
+ rb_set_red(parent);
+ __rb_rotate_right(parent, root);
+ other = parent->rb_left;
+ }
+ if ((!other->rb_left || rb_is_black(other->rb_left)) &&
+ (!other->rb_right || rb_is_black(other->rb_right)))
+ {
+ rb_set_red(other);
+ node = parent;
+ parent = rb_parent(node);
+ }
+ else
+ {
+ if (!other->rb_left || rb_is_black(other->rb_left))
+ {
+ register struct rb_node *o_right;
+ if ((o_right = other->rb_right))
+ rb_set_black(o_right);
+ rb_set_red(other);
+ __rb_rotate_left(other, root);
+ other = parent->rb_left;
+ }
+ rb_set_color(other, rb_color(parent));
+ rb_set_black(parent);
+ if (other->rb_left)
+ rb_set_black(other->rb_left);
+ __rb_rotate_right(parent, root);
+ node = root->rb_node;
+ break;
+ }
+ }
+ }
+ if (node)
+ rb_set_black(node);
+}
+
+void rb_erase(struct rb_node *node, struct rb_root *root)
+{
+ struct rb_node *child, *parent;
+ int color;
+
+ if (!node->rb_left)
+ child = node->rb_right;
+ else if (!node->rb_right)
+ child = node->rb_left;
+ else
+ {
+ struct rb_node *old = node, *left;
+
+ node = node->rb_right;
+ while ((left = node->rb_left) != NULL)
+ node = left;
+ child = node->rb_right;
+ parent = rb_parent(node);
+ color = rb_color(node);
+
+ if (child)
+ rb_set_parent(child, parent);
+ if (parent == old) {
+ parent->rb_right = child;
+ parent = node;
+ } else
+ parent->rb_left = child;
+
+ node->rb_parent_color = old->rb_parent_color;
+ node->rb_right = old->rb_right;
+ node->rb_left = old->rb_left;
+
+ if (rb_parent(old))
+ {
+ if (rb_parent(old)->rb_left == old)
+ rb_parent(old)->rb_left = node;
+ else
+ rb_parent(old)->rb_right = node;
+ } else
+ root->rb_node = node;
+
+ rb_set_parent(old->rb_left, node);
+ if (old->rb_right)
+ rb_set_parent(old->rb_right, node);
+ goto color;
+ }
+
+ parent = rb_parent(node);
+ color = rb_color(node);
+
+ if (child)
+ rb_set_parent(child, parent);
+ if (parent)
+ {
+ if (parent->rb_left == node)
+ parent->rb_left = child;
+ else
+ parent->rb_right = child;
+ }
+ else
+ root->rb_node = child;
+
+ color:
+ if (color == RB_BLACK)
+ __rb_erase_color(child, parent, root);
+}
+
+/*
+ * This function returns the first node (in sort order) of the tree.
+ */
+struct rb_node *rb_first(struct rb_root *root)
+{
+ struct rb_node *n;
+
+ n = root->rb_node;
+ if (!n)
+ return NULL;
+ while (n->rb_left)
+ n = n->rb_left;
+ return n;
+}
+
+struct rb_node *rb_last(struct rb_root *root)
+{
+ struct rb_node *n;
+
+ n = root->rb_node;
+ if (!n)
+ return NULL;
+ while (n->rb_right)
+ n = n->rb_right;
+ return n;
+}
+
+struct rb_node *rb_next(struct rb_node *node)
+{
+ struct rb_node *parent;
+
+ if (rb_parent(node) == node)
+ return NULL;
+
+ /* If we have a right-hand child, go down and then left as far
+ as we can. */
+ if (node->rb_right) {
+ node = node->rb_right;
+ while (node->rb_left)
+ node=node->rb_left;
+ return node;
+ }
+
+ /* No right-hand children. Everything down and left is
+ smaller than us, so any 'next' node must be in the general
+ direction of our parent. Go up the tree; any time the
+ ancestor is a right-hand child of its parent, keep going
+ up. First time it's a left-hand child of its parent, said
+ parent is our 'next' node. */
+ while ((parent = rb_parent(node)) && node == parent->rb_right)
+ node = parent;
+
+ return parent;
+}
+
+struct rb_node *rb_prev(struct rb_node *node)
+{
+ struct rb_node *parent;
+
+ if (rb_parent(node) == node)
+ return NULL;
+
+ /* If we have a left-hand child, go down and then right as far
+ as we can. */
+ if (node->rb_left) {
+ node = node->rb_left;
+ while (node->rb_right)
+ node=node->rb_right;
+ return node;
+ }
+
+ /* No left-hand children. Go up till we find an ancestor which
+ is a right-hand child of its parent */
+ while ((parent = rb_parent(node)) && node == parent->rb_left)
+ node = parent;
+
+ return parent;
+}
+
+void rb_replace_node(struct rb_node *victim, struct rb_node *new,
+ struct rb_root *root)
+{
+ struct rb_node *parent = rb_parent(victim);
+
+ /* Set the surrounding nodes to point to the replacement */
+ if (parent) {
+ if (victim == parent->rb_left)
+ parent->rb_left = new;
+ else
+ parent->rb_right = new;
+ } else {
+ root->rb_node = new;
+ }
+ if (victim->rb_left)
+ rb_set_parent(victim->rb_left, new);
+ if (victim->rb_right)
+ rb_set_parent(victim->rb_right, new);
+
+ /* Copy the pointers/colour from the victim to the replacement */
+ *new = *victim;
+}
diff --git a/src/rule.c b/src/rule.c
new file mode 100644
index 00000000..e86c78aa
--- /dev/null
+++ b/src/rule.c
@@ -0,0 +1,441 @@
+/*
+ * Copyright (c) 2008 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 <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+
+#include <statement.h>
+#include <rule.h>
+#include <utils.h>
+
+
+void handle_free(struct handle *h)
+{
+ xfree(h->table);
+ xfree(h->chain);
+}
+
+void handle_merge(struct handle *dst, const struct handle *src)
+{
+ if (dst->family == 0)
+ dst->family = src->family;
+ if (dst->table == NULL && src->table != NULL)
+ dst->table = xstrdup(src->table);
+ if (dst->chain == NULL && src->chain != NULL)
+ dst->chain = xstrdup(src->chain);
+ if (dst->handle == 0)
+ dst->handle = src->handle;
+}
+
+struct rule *rule_alloc(const struct location *loc, const struct handle *h)
+{
+ struct rule *rule;
+
+ rule = xzalloc(sizeof(*rule));
+ rule->location = *loc;
+ init_list_head(&rule->list);
+ init_list_head(&rule->stmts);
+ if (h != NULL)
+ rule->handle = *h;
+ return rule;
+}
+
+void rule_free(struct rule *rule)
+{
+ stmt_list_free(&rule->stmts);
+ handle_free(&rule->handle);
+ xfree(rule);
+}
+
+void rule_print(const struct rule *rule)
+{
+ const struct stmt *stmt;
+
+ list_for_each_entry(stmt, &rule->stmts, list) {
+ printf(" ");
+ stmt->ops->print(stmt);
+ }
+ printf("\n");
+}
+
+struct chain *chain_alloc(const char *name)
+{
+ struct chain *chain;
+
+ chain = xzalloc(sizeof(*chain));
+ init_list_head(&chain->rules);
+ if (name != NULL)
+ chain->handle.chain = xstrdup(name);
+ return chain;
+}
+
+void chain_free(struct chain *chain)
+{
+ struct rule *rule, *next;
+
+ list_for_each_entry_safe(rule, next, &chain->rules, list)
+ rule_free(rule);
+ handle_free(&chain->handle);
+ xfree(chain);
+}
+
+void chain_add_hash(struct chain *chain, struct table *table)
+{
+ list_add_tail(&chain->list, &table->chains);
+}
+
+struct chain *chain_lookup(const struct table *table, const struct handle *h)
+{
+ struct chain *chain;
+
+ list_for_each_entry(chain, &table->chains, list) {
+ if (!strcmp(chain->handle.chain, h->chain))
+ return chain;
+ }
+ return NULL;
+}
+
+static void chain_print(const struct chain *chain)
+{
+ struct rule *rule;
+
+ printf("\tchain %s {\n", chain->handle.chain);
+ list_for_each_entry(rule, &chain->rules, list) {
+ printf("\t\t");
+ rule_print(rule);
+ }
+ printf("\t}\n");
+}
+
+struct table *table_alloc(void)
+{
+ struct table *table;
+
+ table = xzalloc(sizeof(*table));
+ init_list_head(&table->chains);
+ return table;
+}
+
+void table_free(struct table *table)
+{
+ struct chain *chain, *next;
+
+ list_for_each_entry_safe(chain, next, &table->chains, list)
+ chain_free(chain);
+ handle_free(&table->handle);
+ xfree(table);
+}
+
+static LIST_HEAD(table_list);
+
+void table_add_hash(struct table *table)
+{
+ list_add_tail(&table->list, &table_list);
+}
+
+struct table *table_lookup(const struct handle *h)
+{
+ struct table *table;
+
+ list_for_each_entry(table, &table_list, list) {
+ if (table->handle.family == h->family &&
+ !strcmp(table->handle.table, h->table))
+ return table;
+ }
+ return NULL;
+}
+
+static void table_print(const struct table *table)
+{
+ struct chain *chain;
+ const char *delim = "";
+
+ printf("table %s {\n", table->handle.table);
+ list_for_each_entry(chain, &table->chains, list) {
+ printf("%s", delim);
+ chain_print(chain);
+ delim = "\n";
+ }
+ printf("}\n");
+}
+
+struct cmd *cmd_alloc(enum cmd_ops op, enum cmd_obj obj,
+ const struct handle *h, void *data)
+{
+ struct cmd *cmd;
+
+ cmd = xzalloc(sizeof(*cmd));
+ cmd->op = op;
+ cmd->obj = obj;
+ cmd->handle = *h;
+ cmd->data = data;
+ return cmd;
+}
+
+void cmd_free(struct cmd *cmd)
+{
+ handle_free(&cmd->handle);
+ if (cmd->data != NULL) {
+ switch (cmd->obj) {
+ case CMD_OBJ_RULE:
+ rule_free(cmd->rule);
+ break;
+ case CMD_OBJ_CHAIN:
+ chain_free(cmd->chain);
+ break;
+ case CMD_OBJ_TABLE:
+ table_free(cmd->table);
+ break;
+ default:
+ BUG();
+ }
+ }
+ xfree(cmd);
+}
+
+#include <netlink.h>
+
+static int do_add_chain(struct netlink_ctx *ctx, const struct handle *h,
+ struct chain *chain)
+{
+ struct rule *rule;
+
+ if (netlink_add_chain(ctx, h, chain) < 0)
+ return -1;
+ if (chain != NULL) {
+ list_for_each_entry(rule, &chain->rules, list) {
+ if (netlink_add_rule(ctx, &rule->handle, rule) < 0)
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static int do_add_table(struct netlink_ctx *ctx, const struct handle *h,
+ struct table *table)
+{
+ struct chain *chain;
+
+ if (netlink_add_table(ctx, h, table) < 0)
+ return -1;
+ if (table != NULL) {
+ list_for_each_entry(chain, &table->chains, list) {
+ if (do_add_chain(ctx, &chain->handle, chain) < 0)
+ return -1;
+ }
+ }
+ return 0;
+}
+
+static int do_command_add(struct netlink_ctx *ctx, struct cmd *cmd)
+{
+ switch (cmd->obj) {
+ case CMD_OBJ_TABLE:
+ return do_add_table(ctx, &cmd->handle, cmd->table);
+ case CMD_OBJ_CHAIN:
+ return do_add_chain(ctx, &cmd->handle, cmd->chain);
+ case CMD_OBJ_RULE:
+ return netlink_add_rule(ctx, &cmd->handle, cmd->rule);
+ default:
+ BUG();
+ }
+ return 0;
+}
+
+static int do_command_delete(struct netlink_ctx *ctx, struct cmd *cmd)
+{
+ switch (cmd->obj) {
+ case CMD_OBJ_TABLE:
+ return netlink_delete_table(ctx, &cmd->handle);
+ case CMD_OBJ_CHAIN:
+ return netlink_delete_chain(ctx, &cmd->handle);
+ case CMD_OBJ_RULE:
+ return netlink_delete_rule(ctx, &cmd->handle);
+ default:
+ BUG();
+ }
+}
+
+static int do_command_list(struct netlink_ctx *ctx, struct cmd *cmd)
+{
+ struct table *table;
+ struct chain *chain;
+ struct rule *rule, *next;
+
+ switch (cmd->obj) {
+ case CMD_OBJ_TABLE:
+ if (netlink_list_table(ctx, &cmd->handle) < 0)
+ return -1;
+ break;
+ case CMD_OBJ_CHAIN:
+ if (netlink_list_chain(ctx, &cmd->handle) < 0)
+ return -1;
+ break;
+ default:
+ BUG();
+ }
+
+ table = NULL;
+ list_for_each_entry_safe(rule, next, &ctx->list, list) {
+ table = table_lookup(&rule->handle);
+ if (table == NULL) {
+ table = table_alloc();
+ handle_merge(&table->handle, &rule->handle);
+ table_add_hash(table);
+ }
+
+ chain = chain_lookup(table, &rule->handle);
+ if (chain == NULL) {
+ chain = chain_alloc(rule->handle.chain);
+ chain_add_hash(chain, table);
+ }
+
+ list_move_tail(&rule->list, &chain->rules);
+ }
+
+ if (table != NULL)
+ table_print(table);
+ else
+ printf("table %s does not exist\n", cmd->handle.table);
+ return 0;
+}
+
+static int do_command_flush(struct netlink_ctx *ctx, struct cmd *cmd)
+{
+ switch (cmd->obj) {
+ case CMD_OBJ_TABLE:
+ return netlink_flush_table(ctx, &cmd->handle);
+ case CMD_OBJ_CHAIN:
+ return netlink_flush_chain(ctx, &cmd->handle);
+ default:
+ BUG();
+ }
+ return 0;
+}
+
+int do_command(struct netlink_ctx *ctx, struct cmd *cmd)
+{
+ switch (cmd->op) {
+ case CMD_ADD:
+ return do_command_add(ctx, cmd);
+ case CMD_DELETE:
+ return do_command_delete(ctx, cmd);
+ case CMD_LIST:
+ return do_command_list(ctx, cmd);
+ case CMD_FLUSH:
+ return do_command_flush(ctx, cmd);
+ default:
+ BUG();
+ }
+}
+
+static int payload_match_stmt_cmp(const void *p1, const void *p2)
+{
+ const struct stmt *s1 = *(struct stmt * const *)p1;
+ const struct stmt *s2 = *(struct stmt * const *)p2;
+ const struct expr *e1 = s1->expr, *e2 = s2->expr;
+ int d;
+
+ d = e1->left->payload.base - e2->left->payload.base;
+ if (d != 0)
+ return d;
+ return e1->left->payload.offset - e2->left->payload.offset;
+}
+
+static void payload_do_merge(struct stmt *sa[], unsigned int n)
+{
+ struct expr *last, *this, *expr;
+ struct stmt *stmt;
+ unsigned int i;
+
+ qsort(sa, n, sizeof(sa[0]), payload_match_stmt_cmp);
+
+ last = sa[0]->expr;
+ for (i = 1; i < n; i++) {
+ stmt = sa[i];
+ this = stmt->expr;
+
+ if (!payload_is_adjacent(last->left, this->left) ||
+ last->op != this->op) {
+ last = this;
+ continue;
+ }
+
+ expr = payload_expr_join(last->left, this->left);
+ expr_free(last->left);
+ last->left = expr;
+
+ expr = constant_expr_join(last->right, this->right);
+ expr_free(last->right);
+ last->right = expr;
+
+ list_del(&stmt->list);
+ stmt_free(stmt);
+ }
+}
+
+/**
+ * payload_try_merge - try to merge consecutive payload match statements
+ *
+ * @rule: nftables rule
+ *
+ * Locate sequences of payload match statements referring to adjacent
+ * header locations and merge those using only equality relations.
+ *
+ * As a side-effect, payload match statements are ordered in ascending
+ * order according to the location of the payload.
+ */
+static void payload_try_merge(const struct rule *rule)
+{
+ struct stmt *sa[rule->num_stmts];
+ struct stmt *stmt, *next;
+ unsigned int idx = 0;
+
+ list_for_each_entry_safe(stmt, next, &rule->stmts, list) {
+ /* Must not merge across other statements */
+ if (stmt->ops->type != STMT_EXPRESSION)
+ goto do_merge;
+
+ if (stmt->expr->ops->type != EXPR_RELATIONAL)
+ continue;
+ if (stmt->expr->left->ops->type != EXPR_PAYLOAD)
+ continue;
+ if (stmt->expr->right->ops->type != EXPR_VALUE)
+ continue;
+ switch (stmt->expr->op) {
+ case OP_EQ:
+ case OP_NEQ:
+ break;
+ default:
+ continue;
+ }
+
+ sa[idx++] = stmt;
+ continue;
+do_merge:
+ if (idx < 2)
+ continue;
+ payload_do_merge(sa, idx);
+ idx = 0;
+ }
+
+ if (idx > 1)
+ payload_do_merge(sa, idx);
+}
+
+struct error_record *rule_postprocess(struct rule *rule)
+{
+ payload_try_merge(rule);
+ rule_print(rule);
+ return NULL;
+}
diff --git a/src/scanner.l b/src/scanner.l
new file mode 100644
index 00000000..dc6341da
--- /dev/null
+++ b/src/scanner.l
@@ -0,0 +1,581 @@
+/*
+ * Copyright (c) 2007-2008 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 <limits.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <linux/types.h>
+#include <linux/netfilter.h>
+
+#include <nftables.h>
+#include <erec.h>
+#include <rule.h>
+#include <parser.h>
+#include "parser.h"
+
+#define YY_NO_INPUT
+
+/*
+ * Work around flex behaviour when reaching the end of buffer: normally, flex
+ * regexes are greedy, when reaching the end of buffer however it tries to
+ * match whatever is left in the buffer and only backs up in case it doesn't
+ * match *any* pattern. Since we accept unquoted strings, this means any partial
+ * token will be recognized as string.
+ *
+ * Make sure to only pass input to flex linewise to avoid this.
+ */
+#define YY_INPUT(buf,result,max_size) \
+{ \
+ long n = 0; \
+ errno = 0; \
+ while ((result = fread(buf, 1, max_size, yyin)) == 0 && \
+ ferror(yyin)) { \
+ if (errno != EINTR) { \
+ YY_FATAL_ERROR( "input in flex scanner failed" ); \
+ break; \
+ } \
+ errno = 0; \
+ clearerr(yyin); \
+ } \
+ if (result > 1) { \
+ while (result > 1 && buf[result - 1] != '\n') \
+ result--, n++; \
+ result--, n++; \
+ fseek(yyin, -n, SEEK_CUR); \
+ } \
+}
+
+static void scanner_pop_buffer(yyscan_t scanner);
+
+
+static void init_pos(struct parser_state *state)
+{
+ state->indesc->lineno = 1;
+ state->indesc->column = 1;
+ state->indesc->token_offset = 0;
+ state->indesc->line_offset = 0;
+}
+
+static void update_pos(struct parser_state *state, struct location *loc,
+ int len)
+{
+ loc->indesc = state->indesc;
+ loc->first_line = state->indesc->lineno;
+ loc->first_column = state->indesc->column;
+ loc->last_column = state->indesc->column + len - 1;
+ state->indesc->column += len;
+}
+
+static void update_offset(struct parser_state *state, struct location *loc,
+ unsigned int len)
+{
+ state->indesc->token_offset += len;
+ loc->token_offset = state->indesc->token_offset;
+ loc->line_offset = state->indesc->line_offset;
+}
+
+static void reset_pos(struct parser_state *state, struct location *loc)
+{
+ state->indesc->line_offset = state->indesc->token_offset;
+ state->indesc->lineno += 1;
+ state->indesc->column = 1;
+ loc->line_offset = state->indesc->line_offset;
+}
+
+#define YY_USER_ACTION { \
+ update_pos(yyget_extra(yyscanner), yylloc, yyleng); \
+ update_offset(yyget_extra(yyscanner), yylloc, yyleng); \
+}
+
+/* avoid warnings with -Wmissing-prototypes */
+extern int yyget_column(yyscan_t);
+extern void yyset_column(int, yyscan_t);
+
+%}
+
+space [ ]
+tab \t
+newline \n
+digit [0-9]
+hexdigit [0-9a-fA-F]
+decstring {digit}+
+hexstring 0[xX]{hexdigit}+
+range ({decstring}?:{decstring}?)
+letter [a-zA-Z]
+string ({letter})({letter}|{digit}|[/\-_\.])*
+quotedstring \"[^"]*\"
+comment #.*$
+slash \/
+
+hex4 ([[:xdigit:]]{1,4})
+v680 (({hex4}:){7}{hex4})
+v670 ((:)(:{hex4}{7}))
+v671 ((({hex4}:){1})(:{hex4}{6}))
+v672 ((({hex4}:){2})(:{hex4}{5}))
+v673 ((({hex4}:){3})(:{hex4}{4}))
+v674 ((({hex4}:){4})(:{hex4}{3}))
+v675 ((({hex4}:){5})(:{hex4}{2}))
+v676 ((({hex4}:){6})(:{hex4}{1}))
+v677 ((({hex4}:){7})(:))
+v67 ({v670}|{v671}|{v672}|{v673}|{v674}|{v675}|{v676}|{v677})
+v660 ((:)(:{hex4}{6}))
+v661 ((({hex4}:){1})(:{hex4}{5}))
+v662 ((({hex4}:){2})(:{hex4}{4}))
+v663 ((({hex4}:){3})(:{hex4}{3}))
+v664 ((({hex4}:){4})(:{hex4}{2}))
+v665 ((({hex4}:){5})(:{hex4}{1}))
+v666 ((({hex4}:){6})(:))
+v66 ({v660}|{v661}|{v662}|{v663}|{v664}|{v665}|{v666})
+v650 ((:)(:{hex4}{5}))
+v651 ((({hex4}:){1})(:{hex4}{4}))
+v652 ((({hex4}:){2})(:{hex4}{3}))
+v653 ((({hex4}:){3})(:{hex4}{2}))
+v654 ((({hex4}:){4})(:{hex4}{1}))
+v655 ((({hex4}:){5})(:))
+v65 ({v650}|{v651}|{v652}|{v653}|{v654}|{v655})
+v640 ((:)(:{hex4}{4}))
+v641 ((({hex4}:){1})(:{hex4}{3}))
+v642 ((({hex4}:){2})(:{hex4}{2}))
+v643 ((({hex4}:){3})(:{hex4}{1}))
+v644 ((({hex4}:){4})(:))
+v64 ({v640}|{v641}|{v642}|{v643}|{v644})
+v630 ((:)(:{hex4}{3}))
+v631 ((({hex4}:){1})(:{hex4}{2}))
+v632 ((({hex4}:){2})(:{hex4}{1}))
+v633 ((({hex4}:){3})(:))
+v63 ({v630}|{v631}|{v632}|{v633})
+v620 ((:)(:{hex4}{2}))
+v621 ((({hex4}:){1})(:{hex4}{1}))
+v622 ((({hex4}:){2})(:))
+v62 ({v620}|{v621}|{v622})
+v610 ((:)(:{hex4}{1}))
+v611 ((({hex4}:){1})(:))
+v61 ({v610}|{v611})
+v60 (::)
+
+macaddr (([[:xdigit:]]{1,2}:){5}[[:xdigit:]]{1,2})
+ip4addr (([[:digit:]]{1,3}"."){3}([[:digit:]]{1,3}))
+ip6addr ({v680}|{v67}|{v66}|{v65}|{v64}|{v63}|{v62}|{v61}|{v60})
+
+addrstring ({macaddr}|{ip4addr}|{ip6addr})
+
+%option prefix="nft_"
+%option reentrant
+%option noyywrap
+%option nounput
+%option bison-bridge
+%option bison-locations
+%option debug
+%option yylineno
+%option nodefault
+%option warn
+
+%%
+
+"==" { return EQ; }
+"!=" { return NEQ; }
+"<=" { return LTE; }
+"<" { return LT; }
+">=" { return GTE; }
+">" { return GT; }
+"," { return COMMA; }
+"." { return DOT; }
+":" { return COLON; }
+";" { return SEMICOLON; }
+"{" { return '{'; }
+"}" { return '}'; }
+"[" { return '['; }
+"]" { return ']'; }
+"(" { return '('; }
+")" { return ')'; }
+"<<" { return LSHIFT; }
+">>" { return RSHIFT; }
+"^" { return CARET; }
+"&" { return AMPERSAND; }
+"|" { return '|'; }
+"!" { return NOT; }
+"/" { return SLASH; }
+"-" { return DASH; }
+"*" { return ASTERISK; }
+"@" { return AT; }
+"=>" { return ARROW; }
+"map" { return MAP; }
+"vmap" { return VMAP; }
+"set" { return SET; }
+
+"NF_INET_PRE_ROUTING" { yylval->val = NF_INET_PRE_ROUTING; return HOOKNUM; }
+"NF_INET_LOCAL_IN" { yylval->val = NF_INET_LOCAL_IN; return HOOKNUM; }
+"NF_INET_FORWARD" { yylval->val = NF_INET_FORWARD; return HOOKNUM; }
+"NF_INET_LOCAL_OUT" { yylval->val = NF_INET_LOCAL_OUT; return HOOKNUM; }
+"NF_INET_POST_ROUTING" { yylval->val = NF_INET_POST_ROUTING; return HOOKNUM; }
+
+"include" { return INCLUDE; }
+
+"describe" { return DESCRIBE; }
+
+"hook" { return HOOK; }
+"table" { return TABLE; }
+"chain" { return CHAIN; }
+"rule" { return RULE; }
+"handle" { return HANDLE; }
+
+"accept" { return ACCEPT; }
+"drop" { return DROP; }
+"continue" { return CONTINUE; }
+"jump" { return JUMP; }
+"goto" { return GOTO; }
+"return" { return RETURN; }
+"queue" { return QUEUE; }
+
+"add" { return ADD; }
+"delete" { return DELETE; }
+"list" { return LIST; }
+"flush" { return FLUSH; }
+
+"counter" { return COUNTER; }
+
+"log" { return LOG; }
+"prefix" { return PREFIX; }
+"group" { return GROUP; }
+"snaplen" { return SNAPLEN; }
+"queue-threshold" { return QUEUE_THRESHOLD; }
+
+"limit" { return LIMIT; }
+"rate" { return RATE; }
+
+"nanosecond" { return NANOSECOND; }
+"microsecond" { return MICROSECOND; }
+"millisecond" { return MILLISECOND; }
+"second" { return SECOND; }
+"minute" { return MINUTE; }
+"hour" { return HOUR; }
+"day" { return DAY; }
+"week" { return WEEK; }
+
+"reject" { return _REJECT; }
+
+"snat" { return SNAT; }
+"dnat" { return DNAT; }
+
+"ll" { return LL_HDR; }
+"nh" { return NETWORK_HDR; }
+"th" { return TRANSPORT_HDR; }
+
+"bridge" { return BRIDGE; }
+
+"eth" { return ETH; }
+"saddr" { return SADDR; }
+"daddr" { return DADDR; }
+"type" { return TYPE; }
+
+"vlan" { return VLAN; }
+"id" { return ID; }
+"cfi" { return CFI; }
+"pcp" { return PCP; }
+
+"arp" { return ARP; }
+"htype" { return HTYPE; }
+"ptype" { return PTYPE; }
+"hlen" { return HLEN; }
+"plen" { return PLEN; }
+"operation" { return OPERATION; }
+
+"ip" { return IP; }
+"version" { return VERSION; }
+"hdrlength" { return HDRLENGTH; }
+"tos" { return TOS; }
+"length" { return LENGTH; }
+"frag-off" { return FRAG_OFF; }
+"ttl" { return TTL; }
+"protocol" { return PROTOCOL; }
+"checksum" { return CHECKSUM; }
+
+"icmp" { return ICMP; }
+"code" { return CODE; }
+"sequence" { return SEQUENCE; }
+"gateway" { return GATEWAY; }
+"mtu" { return MTU; }
+
+"ip6" { return IP6; }
+"priority" { return PRIORITY; }
+"flowlabel" { return FLOWLABEL; }
+"nexthdr" { return NEXTHDR; }
+"hoplimit" { return HOPLIMIT; }
+
+"ah" { return AH; }
+"reserved" { return RESERVED; }
+"spi" { return SPI; }
+
+"esp" { return ESP; }
+
+"comp" { return COMP; }
+"flags" { return FLAGS; }
+"cpi" { return CPI; }
+
+"udp" { return UDP; }
+"sport" { return SPORT; }
+"dport" { return DPORT; }
+
+"tcp" { return TCP; }
+"ackseq" { return ACKSEQ; }
+"doff" { return DOFF; }
+"window" { return WINDOW; }
+"urgptr" { return URGPTR; }
+
+"dccp" { return DCCP; }
+
+"sctp" { return SCTP; }
+"vtag" { return VTAG; }
+
+"rt" { return RT; }
+"rt0" { return RT0; }
+"rt2" { return RT2; }
+"seg-left" { return SEG_LEFT; }
+"addr" { return ADDR; }
+
+"hbh" { return HBH; }
+
+"frag" { return FRAG; }
+"reserved2" { return RESERVED2; }
+"more-fragments" { return MORE_FRAGMENTS; }
+
+"dst" { return DST; }
+
+"mh" { return MH; }
+
+"meta" { return META; }
+"mark" { return MARK; }
+"iif" { return IIF; }
+"iifname" { return IIFNAME; }
+"iiftype" { return IIFTYPE; }
+"oif" { return OIF; }
+"oifname" { return OIFNAME; }
+"oiftype" { return OIFTYPE; }
+"skuid" { return SKUID; }
+"skgid" { return SKGID; }
+"nftrace" { return NFTRACE; }
+"rtclassid" { return RTCLASSID; }
+"secmark" { return SECMARK; }
+
+"ct" { return CT; }
+"direction" { return DIRECTION; }
+"state" { return STATE; }
+"status" { return STATUS; }
+"expiration" { return EXPIRATION; }
+"helper" { return HELPER; }
+"proto-src" { return PROTO_SRC; }
+"proto-dst" { return PROTO_DST; }
+
+{addrstring} {
+ yylval->string = xstrdup(yytext);
+ return STRING;
+ }
+
+{decstring} {
+ errno = 0;
+ yylval->val = strtoull(yytext, NULL, 0);
+ if (errno != 0)
+ BUG();
+ return NUM;
+ }
+
+{hexstring} {
+ errno = 0;
+ yylval->val = strtoull(yytext, NULL, 0);
+ if (errno != 0)
+ BUG();
+ return NUM;
+ }
+
+{quotedstring} {
+ yytext[yyleng - 1] = '\0';
+ yylval->string = xstrdup(yytext + 1);
+ return QUOTED_STRING;
+ }
+
+{string} {
+ yylval->string = xstrdup(yytext);
+ return STRING;
+ }
+
+\\{newline} {
+ reset_pos(yyget_extra(yyscanner), yylloc);
+ }
+
+{newline} {
+ reset_pos(yyget_extra(yyscanner), yylloc);
+ return NEWLINE;
+ }
+
+{tab} {
+ /*
+ * Compensate difference between visible length
+ * and real length.
+ */
+ struct parser_state *state = yyget_extra(yyscanner);
+ unsigned int diff;
+
+ diff = TABSIZE - strlen("\t");
+ diff -= (state->indesc->column -
+ strlen("\t") - 1) % TABSIZE;
+
+ update_pos(state, yylloc, diff);
+ }
+
+{space}+
+{comment}
+
+<<EOF>> {
+ update_pos(yyget_extra(yyscanner), yylloc, 1);
+ scanner_pop_buffer(yyscanner);
+ if (YY_CURRENT_BUFFER == NULL)
+ return TOKEN_EOF;
+ }
+
+. { return JUNK; }
+
+%%
+
+static void scanner_pop_buffer(yyscan_t scanner)
+{
+ struct parser_state *state = yyget_extra(scanner);
+
+ yypop_buffer_state(scanner);
+ state->indesc = &state->indescs[--state->indesc_idx - 1];
+}
+
+static struct error_record *scanner_push_file(void *scanner, const char *filename,
+ FILE *f, const struct location *loc)
+{
+ struct parser_state *state = yyget_extra(scanner);
+ YY_BUFFER_STATE b;
+
+ if (state->indesc_idx == MAX_INCLUDE_DEPTH) {
+ fclose(f);
+ return error(loc, "Include nested too deeply, max %u levels",
+ MAX_INCLUDE_DEPTH);
+ }
+
+ b = yy_create_buffer(f, YY_BUF_SIZE, scanner);
+ yypush_buffer_state(b, scanner);
+
+ state->indesc = &state->indescs[state->indesc_idx++];
+ if (loc != NULL)
+ state->indesc->location = *loc;
+ state->indesc->type = INDESC_FILE;
+ state->indesc->name = xstrdup(filename);
+ state->indesc->fd = fileno(f);
+ init_pos(state);
+ return NULL;
+}
+
+int scanner_read_file(void *scanner, const char *filename,
+ const struct location *loc)
+{
+ struct parser_state *state = yyget_extra(scanner);
+ struct error_record *erec;
+ FILE *f;
+
+ f = fopen(filename, "r");
+ if (f == NULL) {
+ erec = error(loc, "Could not open file \"%s\": %s\n",
+ filename, strerror(errno));
+ goto err;
+ }
+
+ erec = scanner_push_file(scanner, filename, f, loc);
+ if (erec != NULL)
+ goto err;
+ return 0;
+
+err:
+ erec_queue(erec, state->msgs);
+ return -1;
+}
+
+int scanner_include_file(void *scanner, const char *filename,
+ const struct location *loc)
+{
+ struct parser_state *state = yyget_extra(scanner);
+ struct error_record *erec;
+ char buf[PATH_MAX];
+ const char *name = buf;
+ unsigned int i;
+ FILE *f;
+
+ f = NULL;
+ for (i = 0; i < INCLUDE_PATHS_MAX; i++) {
+ if (include_paths[i] == NULL)
+ break;
+ snprintf(buf, sizeof(buf), "%s/%s", include_paths[i], filename);
+ f = fopen(buf, "r");
+ if (f != NULL)
+ break;
+ }
+ if (f == NULL) {
+ f = fopen(filename, "r");
+ if (f == NULL) {
+ erec = error(loc, "Could not open file \"%s\": %s\n",
+ filename, strerror(errno));
+ goto err;
+ }
+ name = filename;
+ }
+
+ erec = scanner_push_file(scanner, name, f, loc);
+ if (erec != NULL)
+ goto err;
+ return 0;
+
+err:
+ erec_queue(erec, state->msgs);
+ return -1;
+}
+
+void scanner_push_buffer(void *scanner, const struct input_descriptor *indesc,
+ const char *buffer)
+{
+ struct parser_state *state = yyget_extra(scanner);
+ YY_BUFFER_STATE b;
+
+ state->indesc = &state->indescs[state->indesc_idx++];
+ memcpy(state->indesc, indesc, sizeof(*state->indesc));
+ state->indesc->data = buffer;
+
+ b = yy_scan_string(buffer, scanner);
+ assert(b != NULL);
+ init_pos(state);
+}
+
+void *scanner_init(struct parser_state *state)
+{
+ yyscan_t scanner;
+
+ state->indesc = state->indescs;
+
+ yylex_init(&scanner);
+ yyset_extra(state, scanner),
+ yyset_out(NULL, scanner);
+
+ return scanner;
+}
+
+void scanner_destroy(struct parser_state *scanner)
+{
+ struct parser_state *state = yyget_extra(scanner);
+
+ /* Can't free indesc name - locations might still be in use */
+ while (state->indesc_idx--)
+ yypop_buffer_state(scanner);
+
+ yylex_destroy(scanner);
+}
diff --git a/src/segtree.c b/src/segtree.c
new file mode 100644
index 00000000..9e59bb6e
--- /dev/null
+++ b/src/segtree.c
@@ -0,0 +1,541 @@
+/*
+ * Copyright (c) 2008 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 <stdlib.h>
+#include <inttypes.h>
+#include <arpa/inet.h>
+
+#include <expression.h>
+#include <gmputil.h>
+#include <utils.h>
+#include <rbtree.h>
+
+/**
+ * struct seg_tree - segment tree
+ *
+ * @root: the rbtree's root
+ * @type: the datatype of the dimension
+ * @dwidth: width of the dimension
+ * @byteorder: byteorder of elements
+ */
+struct seg_tree {
+ struct rb_root root;
+ const struct datatype *type;
+ unsigned int dwidth;
+ enum byteorder byteorder;
+};
+
+enum elementary_interval_flags {
+ EI_F_INTERVAL_END = 0x1,
+};
+
+/**
+ * struct elementary_interval - elementary interval [left, right]
+ *
+ * @rb_node: seg_tree rb node
+ * @list: list node for linearized tree
+ * @left: left endpoint
+ * @right: right endpoint
+ * @size: interval size (right - left)
+ * @flags: flags
+ * @expr: associated expression
+ */
+struct elementary_interval {
+ union {
+ struct rb_node rb_node;
+ struct list_head list;
+ };
+
+ mpz_t left;
+ mpz_t right;
+ mpz_t size;
+
+ enum elementary_interval_flags flags;
+ struct expr *expr;
+};
+
+static void seg_tree_init(struct seg_tree *tree, const struct expr *set)
+{
+ struct expr *first;
+
+ first = list_entry(set->expressions.next, struct expr, list);
+ tree->root = RB_ROOT;
+ tree->dwidth = set->len;
+ tree->type = set->dtype;
+ tree->byteorder = first->byteorder;
+}
+
+static struct elementary_interval *ei_alloc(const mpz_t left, const mpz_t right,
+ struct expr *expr,
+ enum elementary_interval_flags flags)
+{
+ struct elementary_interval *ei;
+
+ ei = xzalloc(sizeof(*ei));
+ mpz_init_set(ei->left, left);
+ mpz_init_set(ei->right, right);
+ mpz_init(ei->size);
+ mpz_sub(ei->size, right, left);
+ if (expr != NULL)
+ ei->expr = expr_get(expr);
+ ei->flags = flags;
+ return ei;
+}
+
+static void ei_destroy(struct elementary_interval *ei)
+{
+ mpz_clear(ei->left);
+ mpz_clear(ei->right);
+ mpz_clear(ei->size);
+ if (ei->expr != NULL)
+ expr_free(ei->expr);
+ xfree(ei);
+}
+
+/**
+ * ei_lookup - find elementary interval containing point p
+ *
+ * @tree: segment tree
+ * @p: the point
+ */
+static struct elementary_interval *ei_lookup(struct seg_tree *tree, const mpz_t p)
+{
+ struct rb_node *n = tree->root.rb_node;
+ struct elementary_interval *ei;
+
+ while (n != NULL) {
+ ei = rb_entry(n, struct elementary_interval, rb_node);
+
+ if (mpz_cmp(p, ei->left) >= 0 &&
+ mpz_cmp(p, ei->right) <= 0)
+ return ei;
+ else if (mpz_cmp(p, ei->left) <= 0)
+ n = n->rb_left;
+ else if (mpz_cmp(p, ei->right) > 0)
+ n = n->rb_right;
+ }
+ return NULL;
+}
+
+static void ei_remove(struct seg_tree *tree, struct elementary_interval *ei)
+{
+ rb_erase(&ei->rb_node, &tree->root);
+}
+
+static void __ei_insert(struct seg_tree *tree, struct elementary_interval *new)
+{
+ struct rb_node **p = &tree->root.rb_node;
+ struct rb_node *parent = NULL;
+ struct elementary_interval *ei;
+
+ while (*p != NULL) {
+ parent = *p;
+ ei = rb_entry(parent, struct elementary_interval, rb_node);
+
+ if (mpz_cmp(new->left, ei->left) >= 0 &&
+ mpz_cmp(new->left, ei->right) <= 0)
+ break;
+ else if (mpz_cmp(new->left, ei->left) <= 0)
+ p = &(*p)->rb_left;
+ else if (mpz_cmp(new->left, ei->left) > 0)
+ p = &(*p)->rb_right;
+ }
+
+ rb_link_node(&new->rb_node, parent, p);
+ rb_insert_color(&new->rb_node, &tree->root);
+}
+
+/**
+ * ei_insert - insert an elementary interval into the tree
+ *
+ * @tree: the seg_tree
+ * @new: the elementary interval
+ *
+ * New entries take precedence over existing ones. Insertions are assumed to
+ * be ordered by descending interval size, meaning the new interval never
+ * extends over more than two existing intervals.
+ */
+static void ei_insert(struct seg_tree *tree, struct elementary_interval *new)
+{
+ struct elementary_interval *lei, *rei;
+ mpz_t p;
+
+ mpz_init2(p, tree->dwidth);
+
+ /*
+ * Lookup the intervals containing the left and right endpoints.
+ */
+ lei = ei_lookup(tree, new->left);
+ rei = ei_lookup(tree, new->right);
+
+ pr_debug("insert: [%Zx %Zx]\n", new->left, new->right);
+
+ if (lei != NULL && rei != NULL && lei == rei) {
+ /*
+ * The new interval is entirely contained in the same interval,
+ * split it into two parts:
+ *
+ * [lei_left, new_left) and (new_right, rei_right]
+ */
+ pr_debug("split [%Zx %Zx]\n", lei->left, lei->right);
+
+ ei_remove(tree, lei);
+
+ mpz_sub_ui(p, new->left, 1);
+ if (mpz_cmp(lei->left, p) <= 0)
+ __ei_insert(tree, ei_alloc(lei->left, p, lei->expr, 0));
+
+ mpz_add_ui(p, new->right, 1);
+ if (mpz_cmp(p, rei->right) < 0)
+ __ei_insert(tree, ei_alloc(p, rei->right, lei->expr, 0));
+ ei_destroy(lei);
+ } else {
+ if (lei != NULL) {
+ /*
+ * Left endpoint is within lei, adjust it so we have:
+ *
+ * [lei_left, new_left)[new_left, new_right]
+ */
+ pr_debug("adjust left [%Zx %Zx]\n", lei->left, lei->right);
+
+ mpz_sub_ui(lei->right, new->left, 1);
+ mpz_sub(lei->size, lei->right, lei->left);
+ if (mpz_sgn(lei->size) < 0) {
+ ei_remove(tree, lei);
+ ei_destroy(lei);
+ }
+ }
+ if (rei != NULL) {
+ /*
+ * Right endpoint is within rei, adjust it so we have:
+ *
+ * [new_left, new_right](new_right, rei_right]
+ */
+ pr_debug("adjust right [%Zx %Zx]\n", rei->left, rei->right);
+
+ mpz_add_ui(rei->left, new->right, 1);
+ mpz_sub(rei->size, rei->right, rei->left);
+ if (mpz_sgn(rei->size) < 0) {
+ ei_remove(tree, rei);
+ ei_destroy(rei);
+ }
+ }
+ }
+
+ __ei_insert(tree, new);
+
+ mpz_clear(p);
+}
+
+static void range_low(mpz_t rop, struct expr *expr)
+{
+ switch (expr->ops->type) {
+ case EXPR_VALUE:
+ return mpz_set(rop, expr->value);
+ case EXPR_PREFIX:
+ return range_low(rop, expr->expr);
+ case EXPR_RANGE:
+ return range_low(rop, expr->left);
+ case EXPR_MAPPING:
+ return range_low(rop, expr->left);
+ default:
+ BUG();
+ }
+}
+
+static void range_high(mpz_t rop, const struct expr *expr)
+{
+ mpz_t tmp;
+
+ switch (expr->ops->type) {
+ case EXPR_VALUE:
+ return mpz_set(rop, expr->value);
+ case EXPR_PREFIX:
+ range_low(rop, expr->expr);
+ mpz_init_bitmask(tmp, expr->len - expr->prefix_len);
+ mpz_add(rop, rop, tmp);
+ mpz_clear(tmp);
+ return;
+ case EXPR_RANGE:
+ return range_high(rop, expr->right);
+ case EXPR_MAPPING:
+ return range_high(rop, expr->left);
+ default:
+ BUG();
+ }
+}
+
+/*
+ * Sort intervals according to their priority, which is defined inversely to
+ * their size.
+ *
+ * The beginning of the interval is used as secondary sorting criterion. This
+ * makes sure that overlapping ranges with equal priority are next to each
+ * other, allowing to easily detect unsolvable conflicts during insertion.
+ *
+ * Note: unsolvable conflicts can only occur when using ranges or two identical
+ * prefix specifications.
+ */
+static int interval_cmp(const void *p1, const void *p2)
+{
+ const struct elementary_interval *e1 = *(void * const *)p1;
+ const struct elementary_interval *e2 = *(void * const *)p2;
+ mpz_t d;
+ int ret;
+
+ mpz_init(d);
+
+ mpz_sub(d, e2->size, e1->size);
+ if (mpz_cmp_ui(d, 0))
+ ret = mpz_sgn(d);
+ else
+ ret = mpz_cmp(e1->left, e2->left);
+
+ mpz_clear(d);
+ return ret;
+}
+
+static bool interval_conflict(const struct elementary_interval *e1,
+ const struct elementary_interval *e2)
+{
+ if (mpz_cmp(e1->left, e2->left) <= 0 &&
+ mpz_cmp(e1->right, e2->left) >= 0 &&
+ mpz_cmp(e1->size, e2->size) == 0)
+ return true;
+ else
+ return false;
+}
+
+static void set_to_segtree(struct expr *set, struct seg_tree *tree)
+{
+ struct elementary_interval *intervals[set->size];
+ struct elementary_interval *ei;
+ struct expr *i, *next;
+ unsigned int n;
+ mpz_t low, high;
+
+ mpz_init2(low, tree->dwidth);
+ mpz_init2(high, tree->dwidth);
+
+ /*
+ * Convert elements to intervals and sort by priority.
+ */
+ n = 0;
+ list_for_each_entry_safe(i, next, &set->expressions, list) {
+ range_low(low, i);
+ range_high(high, i);
+ ei = ei_alloc(low, high, i, 0);
+ intervals[n++] = ei;
+
+ list_del(&i->list);
+ expr_free(i);
+ }
+ qsort(intervals, n, sizeof(intervals[0]), interval_cmp);
+
+ /*
+ * Insert elements into tree
+ */
+ for (n = 0; n < set->size; n++) {
+ if (n < set->size - 1 &&
+ interval_conflict(intervals[n], intervals[n+1]))
+ printf("conflict\n");
+ ei_insert(tree, intervals[n]);
+ }
+
+ mpz_clear(high);
+ mpz_clear(low);
+}
+
+static void segtree_linearize(struct list_head *list, struct seg_tree *tree)
+{
+ struct rb_node *node, *next;
+ struct elementary_interval *ei, *nei, *prev = NULL;
+ mpz_t p, q;
+
+ mpz_init2(p, tree->dwidth);
+ mpz_init2(q, tree->dwidth);
+
+ /*
+ * Convert the tree of open intervals to half-closed map expressions.
+ */
+ rb_for_each_entry_safe(ei, node, next, &tree->root, rb_node) {
+ pr_debug("iter: [%Zx %Zx]\n", ei->left, ei->right);
+
+ if (prev == NULL) {
+ /*
+ * If the first segment doesn't begin at zero, insert a
+ * non-matching segment to cover [0, first_left).
+ */
+ if (mpz_cmp_ui(ei->left, 0)) {
+ mpz_set_ui(p, 0);
+ mpz_sub_ui(q, ei->left, 1);
+ nei = ei_alloc(p, q, NULL, EI_F_INTERVAL_END);
+ list_add_tail(&nei->list, list);
+ }
+ } else {
+ /*
+ * If the previous segment doesn't end directly left to
+ * this one, insert a non-matching segment to cover
+ * (prev_right, ei_left).
+ */
+ mpz_add_ui(p, prev->right, 1);
+ if (mpz_cmp(p, ei->left) < 0) {
+ mpz_sub_ui(q, ei->left, 1);
+ nei = ei_alloc(p, q, NULL, EI_F_INTERVAL_END);
+ list_add_tail(&nei->list, list);
+ } else if (ei->expr->ops->type != EXPR_MAPPING) {
+ mpz_set(prev->right, ei->right);
+ ei_remove(tree, ei);
+ ei_destroy(ei);
+ continue;
+ }
+ }
+
+ ei_remove(tree, ei);
+ list_add_tail(&ei->list, list);
+
+ prev = ei;
+ }
+
+ /*
+ * If the last segment doesn't end at the right side of the dimension,
+ * insert a non-matching segment to cover (last_right, end].
+ */
+ if (mpz_scan0(prev->right, 0) != tree->dwidth) {
+ mpz_add_ui(p, prev->right, 1);
+ mpz_bitmask(q, tree->dwidth);
+ nei = ei_alloc(p, q, NULL, EI_F_INTERVAL_END);
+ list_add_tail(&nei->list, list);
+ }
+
+ mpz_clear(p);
+ mpz_clear(q);
+}
+
+static void set_insert_interval(struct expr *set, struct seg_tree *tree,
+ const struct elementary_interval *ei)
+{
+ struct expr *expr;
+
+ expr = constant_expr_alloc(&internal_location, tree->type,
+ tree->byteorder, tree->dwidth, NULL);
+ mpz_set(expr->value, ei->left);
+
+ if (ei->expr != NULL && ei->expr->ops->type == EXPR_MAPPING)
+ expr = mapping_expr_alloc(&ei->expr->location, expr,
+ expr_get(ei->expr->right));
+
+ if (ei->flags & EI_F_INTERVAL_END)
+ expr->flags |= EXPR_F_INTERVAL_END;
+
+ compound_expr_add(set, expr);
+}
+
+void set_to_intervals(struct expr *set)
+{
+ struct elementary_interval *ei, *next;
+ struct seg_tree tree;
+ LIST_HEAD(list);
+
+ seg_tree_init(&tree, set);
+ set_to_segtree(set, &tree);
+ segtree_linearize(&list, &tree);
+
+ list_for_each_entry_safe(ei, next, &list, list) {
+ pr_debug("list: [%.*Zx %.*Zx]\n",
+ 2 * tree.dwidth / BITS_PER_BYTE, ei->left,
+ 2 * tree.dwidth / BITS_PER_BYTE, ei->right);
+ set_insert_interval(set, &tree, ei);
+ ei_destroy(ei);
+ }
+
+ expr_print(set); printf("\n");
+}
+
+static bool range_is_prefix(const mpz_t range)
+{
+ mpz_t tmp;
+
+ mpz_init_set(tmp, range);
+ mpz_add_ui(tmp, tmp, 1);
+ mpz_and(tmp, range, tmp);
+ return !mpz_cmp_ui(tmp, 0);
+}
+
+// FIXME: does not support maps
+extern void interval_map_decompose(struct expr *set);
+
+void interval_map_decompose(struct expr *set)
+{
+ struct expr *ranges[set->size];
+ struct expr *i, *tmp, *prefix, *low = NULL;
+ unsigned int n, size, prefix_len;
+ mpz_t range, p;
+
+ mpz_init(range);
+ mpz_init(p);
+
+ size = set->size;
+ n = 0;
+ list_for_each_entry_safe_reverse(i, tmp, &set->expressions, list) {
+ compound_expr_remove(set, i);
+ ranges[n++] = i;
+ }
+
+ for (n = 0; n < size; n++) {
+ i = ranges[n];
+
+ if (low == NULL) {
+ if (i->flags & EXPR_F_INTERVAL_END) {
+ /*
+ * End of interval mark
+ */
+ expr_free(i);
+ continue;
+ } else {
+ /*
+ * Start a new interval
+ */
+ low = i;
+ continue;
+ }
+ } else
+ expr_get(low);
+
+ mpz_sub(range, i->value, low->value);
+ mpz_sub_ui(range, range, 1);
+
+ mpz_and(p, low->value, range);
+
+ if (!mpz_cmp_ui(range, 0))
+ compound_expr_add(set, low);
+ else if (!range_is_prefix(range) || mpz_cmp_ui(p, 0)) {
+ mpz_add(range, range, low->value);
+ tmp = constant_expr_alloc(&low->location, low->dtype,
+ low->byteorder, low->len,
+ NULL);
+ mpz_set(tmp->value, range);
+
+ tmp = range_expr_alloc(&low->location, low,tmp);
+ compound_expr_add(set, tmp);
+ } else {
+ prefix_len = i->len - mpz_scan0(range, 0);
+ prefix = prefix_expr_alloc(&low->location, low,
+ prefix_len);
+ compound_expr_add(set, prefix);
+ }
+
+ if (i->flags & EXPR_F_INTERVAL_END) {
+ expr_free(low);
+ low = NULL;
+ }
+ expr_free(i);
+ }
+}
diff --git a/src/statement.c b/src/statement.c
new file mode 100644
index 00000000..1a3ea3c1
--- /dev/null
+++ b/src/statement.c
@@ -0,0 +1,210 @@
+/*
+ * Copyright (c) 2008 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 <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <string.h>
+
+#include <statement.h>
+#include <utils.h>
+#include <list.h>
+
+struct stmt *stmt_alloc(const struct location *loc,
+ const struct stmt_ops *ops)
+{
+ struct stmt *stmt;
+
+ stmt = xzalloc(sizeof(*stmt));
+ init_list_head(&stmt->list);
+ stmt->location = *loc;
+ stmt->ops = ops;
+ return stmt;
+}
+
+void stmt_free(struct stmt *stmt)
+{
+ if (stmt->ops->destroy)
+ stmt->ops->destroy(stmt);
+ xfree(stmt);
+}
+
+void stmt_list_free(struct list_head *list)
+{
+ struct stmt *i, *next;
+
+ list_for_each_entry_safe(i, next, list, list) {
+ list_del(&i->list);
+ stmt_free(i);
+ }
+}
+
+void stmt_print(const struct stmt *stmt)
+{
+ stmt->ops->print(stmt);
+}
+
+static void expr_stmt_print(const struct stmt *stmt)
+{
+ expr_print(stmt->expr);
+}
+
+static void expr_stmt_destroy(struct stmt *stmt)
+{
+ expr_free(stmt->expr);
+}
+
+static const struct stmt_ops expr_stmt_ops = {
+ .type = STMT_EXPRESSION,
+ .name = "expression",
+ .print = expr_stmt_print,
+ .destroy = expr_stmt_destroy,
+};
+
+struct stmt *expr_stmt_alloc(const struct location *loc, struct expr *expr)
+{
+ struct stmt *stmt;
+
+ stmt = stmt_alloc(loc, &expr_stmt_ops);
+ stmt->expr = expr;
+ return stmt;
+}
+
+static const struct stmt_ops verdict_stmt_ops = {
+ .type = STMT_VERDICT,
+ .name = "verdict",
+ .print = expr_stmt_print,
+ .destroy = expr_stmt_destroy,
+};
+
+struct stmt *verdict_stmt_alloc(const struct location *loc, struct expr *expr)
+{
+ struct stmt *stmt;
+
+ stmt = stmt_alloc(loc, &verdict_stmt_ops);
+ stmt->expr = expr;
+ return stmt;
+}
+
+static void counter_stmt_print(const struct stmt *stmt)
+{
+ printf("counter packets %" PRIu64 " bytes %" PRIu64,
+ stmt->counter.packets, stmt->counter.bytes);
+}
+
+static const struct stmt_ops counter_stmt_ops = {
+ .type = STMT_COUNTER,
+ .name = "counter",
+ .print = counter_stmt_print,
+};
+
+struct stmt *counter_stmt_alloc(const struct location *loc)
+{
+ return stmt_alloc(loc, &counter_stmt_ops);
+}
+
+static void log_stmt_print(const struct stmt *stmt)
+{
+ printf("log");
+ if (stmt->log.prefix != NULL)
+ printf(" prefix \"%s\"", stmt->log.prefix);
+ if (stmt->log.group)
+ printf(" group %u", stmt->log.group);
+ if (stmt->log.snaplen)
+ printf(" snaplen %u", stmt->log.snaplen);
+ if (stmt->log.qthreshold)
+ printf(" threshold %u", stmt->log.qthreshold);
+}
+
+static void log_stmt_destroy(struct stmt *stmt)
+{
+ xfree(stmt->log.prefix);
+}
+
+static const struct stmt_ops log_stmt_ops = {
+ .type = STMT_LOG,
+ .name = "log",
+ .print = log_stmt_print,
+ .destroy = log_stmt_destroy,
+};
+
+struct stmt *log_stmt_alloc(const struct location *loc)
+{
+ return stmt_alloc(loc, &log_stmt_ops);
+}
+
+static void limit_stmt_print(const struct stmt *stmt)
+{
+ printf("limit rate %" PRIu64 " depth %" PRIu64,
+ stmt->limit.rate, stmt->limit.depth);
+}
+
+static const struct stmt_ops limit_stmt_ops = {
+ .type = STMT_LIMIT,
+ .name = "limit",
+ .print = limit_stmt_print,
+};
+
+struct stmt *limit_stmt_alloc(const struct location *loc)
+{
+ return stmt_alloc(loc, &limit_stmt_ops);
+}
+
+static void reject_stmt_print(const struct stmt *stmt)
+{
+ printf("reject");
+}
+
+static const struct stmt_ops reject_stmt_ops = {
+ .type = STMT_REJECT,
+ .name = "reject",
+ .print = reject_stmt_print,
+};
+
+struct stmt *reject_stmt_alloc(const struct location *loc)
+{
+ return stmt_alloc(loc, &reject_stmt_ops);
+}
+
+static void nat_stmt_print(const struct stmt *stmt)
+{
+ static const char *nat_types[] = {
+ [NFT_NAT_SNAT] = "snat",
+ [NFT_NAT_DNAT] = "dnat",
+ };
+
+ printf("%s ", nat_types[stmt->nat.type]);
+ if (stmt->nat.addr)
+ expr_print(stmt->nat.addr);
+ if (stmt->nat.proto) {
+ printf(":");
+ expr_print(stmt->nat.proto);
+ }
+}
+
+static void nat_stmt_destroy(struct stmt *stmt)
+{
+ expr_free(stmt->nat.addr);
+ expr_free(stmt->nat.proto);
+}
+
+static const struct stmt_ops nat_stmt_ops = {
+ .type = STMT_NAT,
+ .name = "nat",
+ .print = nat_stmt_print,
+ .destroy = nat_stmt_destroy,
+};
+
+struct stmt *nat_stmt_alloc(const struct location *loc)
+{
+ return stmt_alloc(loc, &nat_stmt_ops);
+}
diff --git a/src/utils.c b/src/utils.c
new file mode 100644
index 00000000..dcb1c8c6
--- /dev/null
+++ b/src/utils.c
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2008 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 <stdlib.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+
+#include <nftables.h>
+#include <utils.h>
+
+void __noreturn memory_allocation_error(void)
+{
+ fprintf(stderr, "Memory allocation failure\n");
+ exit(NFT_EXIT_NOMEM);
+}
+
+void xfree(const void *ptr)
+{
+ free((void *)ptr);
+}
+
+void *xmalloc(size_t size)
+{
+ void *ptr;
+
+ ptr = malloc(size);
+ if (ptr == NULL)
+ memory_allocation_error();
+ return ptr;
+}
+
+void *xrealloc(void *ptr, size_t size)
+{
+ assert(ptr != NULL);
+ ptr = realloc(ptr, size);
+ if (ptr == NULL && size != 0)
+ memory_allocation_error();
+ return ptr;
+}
+
+void *xzalloc(size_t size)
+{
+ void *ptr;
+
+ ptr = xmalloc(size);
+ memset(ptr, 0, size);
+ return ptr;
+}
+
+char *xstrdup(const char *s)
+{
+ char *res;
+
+ assert(s != NULL);
+ res = strdup(s);
+ if (res == NULL)
+ memory_allocation_error();
+ return res;
+}