From 384958620abab397062b67fb2763e813b63f74f0 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Thu, 27 Sep 2012 19:12:53 +0200 Subject: use nf_tables and nf_tables compatibility interface This patch adds the following utilities: * xtables * xtables-restore * xtables-save * xtables-config They all use Patrick's nf_tables infrastructure plus my compatibility layer. xtables, xtables-restore and xtables-save are syntax compatible with ip[6]tables, ip[6]tables-restore and ip[6]tables-save. Semantics aims to be similar, still the main exception is that there is no commit operation. Thus, we incrementally add/delete rules without entire table locking. The following options are also not yet implemented: -Z (this requires adding expr->ops->reset(...) so nft_counters can reset internal state of expressions while dumping it) -R and -E (this requires adding this feature to nf_tables) -f (can be implemented with expressions: payload 6 (2-bytes) + bitwise a&b^!b + cmp neq 0) -IPv6 support. But those are a matter of time to get them done. A new utility, xtables-config, is available to register tables and chains. By default there is a configuration file that adds backward compatible tables and chains under iptables/etc/xtables.conf. You have to call this utility first to register tables and chains. However, it would be possible to automagically register tables and chains while using xtables and xtables-restore to get similar operation than with iptables. Signed-off-by: Pablo Neira Ayuso --- iptables/Makefile.am | 25 +- iptables/ip6tables.c | 1 + iptables/iptables.c | 1 + iptables/nft.c | 2764 ++++++++++++++++++++++++++++++++++++++ iptables/nft.h | 62 + iptables/xshared.h | 1 + iptables/xtables-config-parser.y | 213 +++ iptables/xtables-config-syntax.l | 53 + iptables/xtables-config.c | 107 ++ iptables/xtables-multi.c | 10 + iptables/xtables-multi.h | 4 + iptables/xtables-restore.c | 417 ++++++ iptables/xtables-save.c | 122 ++ iptables/xtables-standalone.c | 80 ++ iptables/xtables.c | 1251 +++++++++++++++++ 15 files changed, 5109 insertions(+), 2 deletions(-) create mode 100644 iptables/nft.c create mode 100644 iptables/nft.h create mode 100644 iptables/xtables-config-parser.y create mode 100644 iptables/xtables-config-syntax.l create mode 100644 iptables/xtables-config.c create mode 100644 iptables/xtables-restore.c create mode 100644 iptables/xtables-save.c create mode 100644 iptables/xtables-standalone.c create mode 100644 iptables/xtables.c (limited to 'iptables') diff --git a/iptables/Makefile.am b/iptables/Makefile.am index a4246eb3..2b1f3fa4 100644 --- a/iptables/Makefile.am +++ b/iptables/Makefile.am @@ -1,7 +1,8 @@ # -*- Makefile -*- AM_CFLAGS = ${regular_CFLAGS} -AM_CPPFLAGS = ${regular_CPPFLAGS} -I${top_builddir}/include -I${top_srcdir}/include ${kinclude_CPPFLAGS} +AM_CPPFLAGS = ${regular_CPPFLAGS} -I${top_builddir}/include -I${top_srcdir}/include ${kinclude_CPPFLAGS} ${libmnl_CPPFLAGS} ${libnftables_CPPFLAGS} +AM_YFLAGS = -d xtables_multi_SOURCES = xtables-multi.c iptables-xml.c xtables_multi_CFLAGS = ${AM_CFLAGS} @@ -24,11 +25,27 @@ endif xtables_multi_SOURCES += xshared.c xtables_multi_LDADD += ../libxtables/libxtables.la -lm +if ENABLE_NFTABLES +if HAVE_LIBMNL +if HAVE_LIBNFTABLES +xtables_multi_SOURCES += xtables-save.c xtables-restore.c \ + xtables-standalone.c xtables.c nft.c \ + xtables-config-parser.y xtables-config-syntax.l \ + xtables-config.c +xtables_multi_LDADD += -lmnl -lnftables +xtables_multi_CFLAGS += -DENABLE_NFTABLES +# yacc and lex generate dirty code +xtables_multi-xtables-config-parser.o xtables_multi-xtables-config-syntax.o: AM_CFLAGS += -Wno-missing-prototypes -Wno-missing-declarations -Wno-implicit-function-declaration -Wno-nested-externs -Wno-undef -Wno-redundant-decls +endif +endif +endif + sbin_PROGRAMS = xtables-multi man_MANS = iptables.8 iptables-restore.8 iptables-save.8 \ iptables-xml.1 ip6tables.8 ip6tables-restore.8 \ ip6tables-save.8 iptables-extensions.8 -CLEANFILES = iptables.8 +CLEANFILES = iptables.8 \ + xtables-config-parser.c xtables-config-syntax.c vx_bin_links = iptables-xml if ENABLE_IPV4 @@ -37,6 +54,9 @@ endif if ENABLE_IPV6 v6_sbin_links = ip6tables ip6tables-restore ip6tables-save endif +if ENABLE_NFTABLES +x_sbin_links = xtables xtables-restore xtables-save xtables-config +endif iptables-extensions.8: iptables-extensions.8.tmpl ../extensions/matches.man ../extensions/targets.man ${AM_VERBOSE_GEN} sed \ @@ -52,3 +72,4 @@ install-exec-hook: for i in ${vx_bin_links}; do ${LN_S} -f "${sbindir}/xtables-multi" "${DESTDIR}${bindir}/$$i"; done; for i in ${v4_sbin_links}; do ${LN_S} -f xtables-multi "${DESTDIR}${sbindir}/$$i"; done; for i in ${v6_sbin_links}; do ${LN_S} -f xtables-multi "${DESTDIR}${sbindir}/$$i"; done; + for i in ${x_sbin_links}; do ${LN_S} -f xtables-multi "${DESTDIR}${sbindir}/$$i"; done; diff --git a/iptables/ip6tables.c b/iptables/ip6tables.c index a5199d5e..2ebfd6c0 100644 --- a/iptables/ip6tables.c +++ b/iptables/ip6tables.c @@ -121,6 +121,7 @@ struct xtables_globals ip6tables_globals = { .program_version = IPTABLES_VERSION, .orig_opts = original_opts, .exit_err = ip6tables_exit_error, + .compat_rev = xtables_compatible_revision, }; /* Table of legal combinations of commands and options. If any of the diff --git a/iptables/iptables.c b/iptables/iptables.c index 5cd2596e..471bff06 100644 --- a/iptables/iptables.c +++ b/iptables/iptables.c @@ -120,6 +120,7 @@ struct xtables_globals iptables_globals = { .program_version = IPTABLES_VERSION, .orig_opts = original_opts, .exit_err = iptables_exit_error, + .compat_rev = xtables_compatible_revision, }; /* Table of legal combinations of commands and options. If any of the diff --git a/iptables/nft.c b/iptables/nft.c new file mode 100644 index 00000000..91383bfb --- /dev/null +++ b/iptables/nft.c @@ -0,0 +1,2764 @@ +/* + * (C) 2012 by Pablo Neira Ayuso + * + * 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 code has been sponsored by Sophos Astaro + */ + +#if 0 +#define DEBUGP(x, args...) fprintf(stdout, x, ## args) +#define NLDEBUG +#define DEBUG_DEL +#else +#define DEBUGP(x, args...) +#endif + +#include +#include +#include +#include +#include +#include +#include /* getprotobynumber */ +#include + +#include +#include +#include + +#include +#include + +#include +#include /* FIXME: only IPV4 by now */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include /* inet_ntoa */ +#include + +#include "nft.h" +#include "xshared.h" /* proto_to_name */ + +static void *nft_fn; + +static int mnl_talk(struct nft_handle *h, struct nlmsghdr *nlh, + int (*cb)(const struct nlmsghdr *nlh, void *data), + void *data) +{ + int ret; + char buf[MNL_SOCKET_BUFFER_SIZE]; + + if (mnl_socket_sendto(h->nl, nlh, nlh->nlmsg_len) < 0) { + perror("mnl_socket_send"); + return -1; + } + + ret = mnl_socket_recvfrom(h->nl, buf, sizeof(buf)); + while (ret > 0) { + ret = mnl_cb_run(buf, ret, h->seq, h->portid, cb, data); + if (ret <= 0) + break; + + ret = mnl_socket_recvfrom(h->nl, buf, sizeof(buf)); + } + if (ret == -1) { + return -1; + } + + return 0; +} + +static void nft_table_init_one(struct nft_handle *h, const char *name, int portid) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + struct nft_table *t; + + t = nft_table_alloc(); + if (t == NULL) + return; + + nft_table_attr_set(t, NFT_TABLE_ATTR_NAME, (char *)name); + + nlh = nft_table_nlmsg_build_hdr(buf, NFT_MSG_NEWTABLE, AF_INET, + NLM_F_ACK|NLM_F_EXCL, h->seq); + nft_table_nlmsg_build_payload(nlh, t); + nft_table_free(t); + + if (mnl_talk(h, nlh, NULL, NULL) < 0) { + if (errno != EEXIST) + perror("mnl-talk:nft_table_init_one"); + } +} + +#define FILTER 0 +#define MANGLE 1 +#define RAW 2 +#define SECURITY 3 +#define TABLES_MAX 4 + +static struct default_table { + const char *name; + uint32_t prio; +} tables[TABLES_MAX] = { + [RAW] = { + .name = "raw", + .prio = -300, /* NF_IP_PRI_RAW */ + }, + [MANGLE] = { + .name = "mangle", + .prio = -150, /* NF_IP_PRI_MANGLE */ + }, + [FILTER] = { + .name = "filter", + .prio = 0, /* NF_IP_PRI_FILTER */ + }, + [SECURITY] = { + .name = "security", + .prio = 150, /* NF_IP_PRI_SECURITY */ + }, + /* nat already registered by nf_tables */ +}; + +static void nft_table_init(struct nft_handle *h) +{ + int i; + + for (i=0; iportid); +} + +static struct default_chain { + const char *name; + uint32_t hook; +} chains[TABLES_MAX][NF_IP_NUMHOOKS] = { + [FILTER] = { + { + .name = "INPUT", + .hook = NF_INET_LOCAL_IN, + }, + { + .name = "FORWARD", + .hook = NF_INET_FORWARD, + }, + { + .name = "OUTPUT", + .hook = NF_INET_LOCAL_OUT, + }, + }, + [MANGLE] = { + { + .name = "PREROUTING", + .hook = NF_INET_PRE_ROUTING, + }, + { + .name = "INPUT", + .hook = NF_INET_LOCAL_IN, + }, + { + .name = "FORWARD", + .hook = NF_INET_FORWARD, + }, + { + .name = "OUTPUT", + .hook = NF_INET_LOCAL_OUT, + }, + { + .name = "POSTROUTING", + .hook = NF_INET_POST_ROUTING, + }, + }, + [RAW] = { + { + .name = "PREROUTING", + .hook = NF_INET_PRE_ROUTING, + }, + { + .name = "OUTPUT", + .hook = NF_INET_LOCAL_OUT, + }, + }, + [SECURITY] = { + { + .name = "INPUT", + .hook = NF_INET_LOCAL_IN, + }, + { + .name = "FORWARD", + .hook = NF_INET_FORWARD, + }, + { + .name = "OUTPUT", + .hook = NF_INET_LOCAL_OUT, + }, + }, +}; + +static void +nft_chain_default_add(struct nft_handle *h, struct default_table *table, + struct default_chain *chain, int policy) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + struct nft_chain *c; + + c = nft_chain_alloc(); + if (c == NULL) + return; + + nft_chain_attr_set(c, NFT_CHAIN_ATTR_TABLE, (char *)table->name); + nft_chain_attr_set(c, NFT_CHAIN_ATTR_NAME, (char *)chain->name); + nft_chain_attr_set_u32(c, NFT_CHAIN_ATTR_HOOKNUM, chain->hook); + nft_chain_attr_set_u32(c, NFT_CHAIN_ATTR_PRIO, table->prio); + nft_chain_attr_set_u32(c, NFT_CHAIN_ATTR_POLICY, policy); + + nlh = nft_chain_nlmsg_build_hdr(buf, NFT_MSG_NEWCHAIN, AF_INET, + NLM_F_ACK|NLM_F_EXCL, h->seq); + nft_chain_nlmsg_build_payload(nlh, c); + nft_chain_free(c); + + if (mnl_talk(h, nlh, NULL, NULL) < 0) { + if (errno != EEXIST) + perror("mnl_talk:nft_chain_default_add"); + } +} + +static void nft_chain_init(struct nft_handle *h) +{ + int i, j; + + for (i=0; inl = mnl_socket_open(NETLINK_NETFILTER); + if (h->nl == NULL) { + perror("mnl_socket_open"); + return -1; + } + + if (mnl_socket_bind(h->nl, 0, MNL_SOCKET_AUTOPID) < 0) { + perror("mnl_socket_bind"); + return -1; + } + h->portid = mnl_socket_get_portid(h->nl); + +/* + * In case we want to inconditionally register all tables / chain. + * This is not flexible and performance consuming. + * + * nft_table_init(h); + * nft_chain_init(h); + */ + + return 0; +} + +void nft_fini(struct nft_handle *h) +{ + mnl_socket_close(h->nl); +} + +int nft_table_add(struct nft_handle *h, const struct nft_table *t) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + + nlh = nft_table_nlmsg_build_hdr(buf, NFT_MSG_NEWTABLE, AF_INET, + NLM_F_ACK|NLM_F_EXCL, h->seq); + nft_table_nlmsg_build_payload(nlh, t); + + return mnl_talk(h, nlh, NULL, NULL); +} + +int nft_chain_add(struct nft_handle *h, const struct nft_chain *c) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + + nlh = nft_chain_nlmsg_build_hdr(buf, NFT_MSG_NEWCHAIN, AF_INET, + NLM_F_ACK|NLM_F_EXCL, h->seq); + nft_chain_nlmsg_build_payload(nlh, c); + + return mnl_talk(h, nlh, NULL, NULL); +} + +static void nft_chain_print_debug(struct nft_chain *c, struct nlmsghdr *nlh) +{ +#ifdef NLDEBUG + char tmp[1024]; + + nft_chain_snprintf(tmp, sizeof(tmp), c, 0, 0); + printf("DEBUG: chain: %s", tmp); + mnl_nlmsg_fprintf(stdout, nlh, nlh->nlmsg_len, sizeof(struct nfgenmsg)); +#endif +} + +static int +__nft_chain_set(struct nft_handle *h, const char *table, + const char *chain, int policy, + const struct xt_counters *counters) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + struct nft_chain *c; + int ret; + + c = nft_chain_alloc(); + if (c == NULL) + return -1; + + nft_chain_attr_set(c, NFT_CHAIN_ATTR_TABLE, (char *)table); + nft_chain_attr_set(c, NFT_CHAIN_ATTR_NAME, (char *)chain); + nft_chain_attr_set_u32(c, NFT_CHAIN_ATTR_POLICY, policy); + if (counters) { + nft_chain_attr_set_u64(c, NFT_CHAIN_ATTR_BYTES, + counters->bcnt); + nft_chain_attr_set_u64(c, NFT_CHAIN_ATTR_PACKETS, + counters->pcnt); + } + + nlh = nft_chain_nlmsg_build_hdr(buf, NFT_MSG_NEWCHAIN, AF_INET, + NLM_F_ACK, h->seq); + nft_chain_nlmsg_build_payload(nlh, c); + + nft_chain_print_debug(c, nlh); + + nft_chain_free(c); + + ret = mnl_talk(h, nlh, NULL, NULL); + if (ret < 0) + perror("mnl_talk:__nft_chain_policy"); + + return ret; +} + +int nft_chain_set(struct nft_handle *h, const char *table, + const char *chain, const char *policy, + const struct xt_counters *counters) +{ + int ret = -1; + + nft_fn = nft_chain_set; + + if (strcmp(policy, "DROP") == 0) + ret = __nft_chain_set(h, table, chain, NF_DROP, counters); + else if (strcmp(policy, "ACCEPT") == 0) + ret = __nft_chain_set(h, table, chain, NF_ACCEPT, counters); + + /* the core expects 1 for success and 0 for error */ + return ret == 0 ? 1 : 0; +} + +static void __add_match(struct nft_rule_expr *e, struct xt_entry_match *m) +{ + void *info; + + nft_rule_expr_set(e, NFT_EXPR_MT_NAME, m->u.user.name, strlen(m->u.user.name)); + nft_rule_expr_set_u32(e, NFT_EXPR_MT_REV, m->u.user.revision); + + info = calloc(1, m->u.match_size); + if (info == NULL) + return; + + memcpy(info, m->data, m->u.match_size); + nft_rule_expr_set(e, NFT_EXPR_MT_INFO, info, m->u.match_size - sizeof(*m)); +} + +static void add_match(struct nft_rule *r, struct xt_entry_match *m) +{ + struct nft_rule_expr *expr; + + expr = nft_rule_expr_alloc("match"); + if (expr == NULL) + return; + + __add_match(expr, m); + nft_rule_add_expr(r, expr); +} + +static void __add_target(struct nft_rule_expr *e, struct xt_entry_target *t) +{ + void *info = NULL; + + nft_rule_expr_set(e, NFT_EXPR_TG_NAME, t->u.user.name, + strlen(t->u.user.name)); + nft_rule_expr_set_u32(e, NFT_EXPR_TG_REV, t->u.user.revision); + + if (info == NULL) { + info = calloc(1, t->u.target_size); + if (info == NULL) + return; + + memcpy(info, t->data, t->u.target_size); + } + + nft_rule_expr_set(e, NFT_EXPR_TG_INFO, info, t->u.target_size - sizeof(*t)); +} + +static void add_target(struct nft_rule *r, struct xt_entry_target *t) +{ + struct nft_rule_expr *expr; + + expr = nft_rule_expr_alloc("target"); + if (expr == NULL) + return; + + __add_target(expr, t); + nft_rule_add_expr(r, expr); +} + +static void add_jumpto(struct nft_rule *r, const char *name, int verdict) +{ + struct nft_rule_expr *expr; + + expr = nft_rule_expr_alloc("immediate"); + if (expr == NULL) + return; + + nft_rule_expr_set_u32(expr, NFT_EXPR_IMM_DREG, NFT_REG_VERDICT); + nft_rule_expr_set_u32(expr, NFT_EXPR_IMM_VERDICT, verdict); + nft_rule_expr_set_str(expr, NFT_EXPR_IMM_CHAIN, (char *)name); + nft_rule_add_expr(r, expr); +} + +static void add_verdict(struct nft_rule *r, int verdict) +{ + struct nft_rule_expr *expr; + + expr = nft_rule_expr_alloc("immediate"); + if (expr == NULL) + return; + + nft_rule_expr_set_u32(expr, NFT_EXPR_IMM_DREG, NFT_REG_VERDICT); + nft_rule_expr_set_u32(expr, NFT_EXPR_IMM_VERDICT, verdict); + nft_rule_add_expr(r, expr); +} + +static void nft_rule_print_debug(struct nft_rule *r, struct nlmsghdr *nlh) +{ +#ifdef NLDEBUG + char tmp[1024]; + + nft_rule_snprintf(tmp, sizeof(tmp), r, 0, 0); + printf("DEBUG: rule: %s", tmp); + mnl_nlmsg_fprintf(stdout, nlh, nlh->nlmsg_len, sizeof(struct nfgenmsg)); +#endif +} + +static void add_meta(struct nft_rule *r, uint32_t key) +{ + struct nft_rule_expr *expr; + + expr = nft_rule_expr_alloc("meta"); + if (expr == NULL) + return; + + nft_rule_expr_set_u32(expr, NFT_EXPR_META_KEY, key); + nft_rule_expr_set_u32(expr, NFT_EXPR_META_DREG, NFT_REG_1); + + nft_rule_add_expr(r, expr); +} + +static void add_payload(struct nft_rule *r, int offset, int len) +{ + struct nft_rule_expr *expr; + + expr = nft_rule_expr_alloc("payload"); + if (expr == NULL) + return; + + nft_rule_expr_set_u32(expr, NFT_EXPR_PAYLOAD_BASE, + NFT_PAYLOAD_NETWORK_HEADER); + nft_rule_expr_set_u32(expr, NFT_EXPR_PAYLOAD_DREG, NFT_REG_1); + nft_rule_expr_set_u32(expr, NFT_EXPR_PAYLOAD_OFFSET, offset); + nft_rule_expr_set_u32(expr, NFT_EXPR_PAYLOAD_LEN, len); + + nft_rule_add_expr(r, expr); +} + +static void add_cmp_ptr(struct nft_rule *r, uint32_t op, void *data, size_t len) +{ + struct nft_rule_expr *expr; + + expr = nft_rule_expr_alloc("cmp"); + if (expr == NULL) + return; + + nft_rule_expr_set_u8(expr, NFT_EXPR_CMP_SREG, NFT_REG_1); + nft_rule_expr_set_u8(expr, NFT_EXPR_CMP_OP, op); + nft_rule_expr_set(expr, NFT_EXPR_CMP_DATA, data, len); + + nft_rule_add_expr(r, expr); +} + +static void add_cmp_u32(struct nft_rule *r, uint32_t val, uint32_t op) +{ + add_cmp_ptr(r, op, &val, sizeof(val)); +} + +static void add_counters(struct nft_rule *r, uint64_t packets, uint64_t bytes) +{ + struct nft_rule_expr *expr; + + expr = nft_rule_expr_alloc("counter"); + if (expr == NULL) + return; + + nft_rule_expr_set_u64(expr, NFT_EXPR_CTR_BYTES, packets); + nft_rule_expr_set_u64(expr, NFT_EXPR_CTR_PACKETS, bytes); + + nft_rule_add_expr(r, expr); +} + +int +nft_rule_add(struct nft_handle *h, const char *chain, const char *table, + struct iptables_command_state *cs, bool append, bool verbose) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + struct xtables_rule_match *matchp; + struct nft_rule *r; + int ret = 1; + uint32_t op; + int flags = append ? NLM_F_APPEND : 0; + + nft_fn = nft_rule_add; + + r = nft_rule_alloc(); + if (r == NULL) { + ret = 0; + goto err; + } + + nft_rule_attr_set(r, NFT_RULE_ATTR_TABLE, (char *)table); + nft_rule_attr_set(r, NFT_RULE_ATTR_CHAIN, (char *)chain); + + if (cs->fw.ip.iniface[0] != '\0') { + int iface_len = strlen(cs->fw.ip.iniface); + + if (cs->fw.ip.invflags & IPT_INV_VIA_IN) + op = NFT_CMP_NEQ; + else + op = NFT_CMP_EQ; + + if (cs->fw.ip.iniface[iface_len - 1] == '+') { + add_meta(r, NFT_META_IIFNAME); + add_cmp_ptr(r, op, cs->fw.ip.iniface, iface_len - 1); + } else { + add_meta(r, NFT_META_IIF); + add_cmp_u32(r, if_nametoindex(cs->fw.ip.iniface), op); + } + } + if (cs->fw.ip.outiface[0] != '\0') { + int iface_len = strlen(cs->fw.ip.outiface); + + if (cs->fw.ip.invflags & IPT_INV_VIA_OUT) + op = NFT_CMP_NEQ; + else + op = NFT_CMP_EQ; + + if (cs->fw.ip.outiface[iface_len - 1] == '+') { + add_meta(r, NFT_META_OIFNAME); + add_cmp_ptr(r, op, cs->fw.ip.outiface, iface_len - 1); + } else { + add_meta(r, NFT_META_OIF); + add_cmp_u32(r, if_nametoindex(cs->fw.ip.outiface), op); + } + } + if (cs->fw.ip.src.s_addr != 0) { + add_payload(r, offsetof(struct iphdr, saddr), 4); + if (cs->fw.ip.invflags & IPT_INV_SRCIP) + op = NFT_CMP_NEQ; + else + op = NFT_CMP_EQ; + + add_cmp_u32(r, cs->fw.ip.src.s_addr, op); + } + if (cs->fw.ip.dst.s_addr != 0) { + add_payload(r, offsetof(struct iphdr, daddr), 4); + if (cs->fw.ip.invflags & IPT_INV_DSTIP) + op = NFT_CMP_NEQ; + else + op = NFT_CMP_EQ; + + add_cmp_u32(r, cs->fw.ip.dst.s_addr, op); + } + if (cs->fw.ip.proto != 0) { + add_payload(r, offsetof(struct iphdr, protocol), 1); + if (cs->fw.ip.invflags & XT_INV_PROTO) + op = NFT_CMP_NEQ; + else + op = NFT_CMP_EQ; + + add_cmp_u32(r, cs->fw.ip.proto, op); + } + + for (matchp = cs->matches; matchp; matchp = matchp->next) + add_match(r, matchp->match->m); + + /* Counters need to me added before the target, otherwise they are + * increased for each rule because of the way nf_tables works. + */ + add_counters(r, cs->counters.pcnt, cs->counters.bcnt); + + /* If no target at all, add nothing (default to continue) */ + if (cs->target != NULL) { + /* Standard target? */ + if (strcmp(cs->jumpto, XTC_LABEL_ACCEPT) == 0) + add_verdict(r, NF_ACCEPT); + else if (strcmp(cs->jumpto, XTC_LABEL_DROP) == 0) + add_verdict(r, NF_DROP); + else if (strcmp(cs->jumpto, XTC_LABEL_RETURN) == 0) + add_verdict(r, NFT_RETURN); + else + add_target(r, cs->target->t); + } else if (strlen(cs->jumpto) > 0) { + /* Not standard, then it's a go / jump to chain */ + if (cs->fw.ip.flags & IPT_F_GOTO) + add_jumpto(r, cs->jumpto, NFT_GOTO); + else + add_jumpto(r, cs->jumpto, NFT_JUMP); + } + + /* NLM_F_CREATE autoloads the built-in table if it does not exists */ + nlh = nft_rule_nlmsg_build_hdr(buf, NFT_MSG_NEWRULE, AF_INET, + NLM_F_ACK|NLM_F_CREATE|flags, h->seq); + nft_rule_nlmsg_build_payload(nlh, r); + + nft_rule_print_debug(r, nlh); + + nft_rule_free(r); + + ret = mnl_talk(h, nlh, NULL, NULL); + if (ret < 0) + perror("mnl_talk:nft_rule_add"); + +err: + /* the core expects 1 for success and 0 for error */ + return ret == 0 ? 1 : 0; +} + +static void nft_match_save(struct nft_rule_expr *expr) +{ + const char *name; + const struct xtables_match *match; + struct xt_entry_match *emu; + const void *mtinfo; + size_t len; + + name = nft_rule_expr_get_str(expr, NFT_EXPR_MT_NAME); + + match = xtables_find_match(name, XTF_TRY_LOAD, NULL); + if (match == NULL) + return; + + mtinfo = nft_rule_expr_get(expr, NFT_EXPR_MT_INFO, &len); + if (mtinfo == NULL) + return; + + emu = calloc(1, sizeof(struct xt_entry_match) + len); + if (emu == NULL) + return; + + memcpy(&emu->data, mtinfo, len); + + if (match->alias) + printf("-m %s", match->alias(emu)); + else + printf("-m %s", match->name); + + /* FIXME missing parameter */ + match->save(NULL, emu); + + printf(" "); + + free(emu); +} + +static void nft_target_save(struct nft_rule_expr *expr) +{ + const char *name; + const struct xtables_target *target; + struct xt_entry_target *emu; + const void *tginfo; + size_t len; + + name = nft_rule_expr_get_str(expr, NFT_EXPR_TG_NAME); + + /* Standard target not supported, we use native immediate expression */ + if (strcmp(name, "") == 0) { + printf("ERROR: standard target seen, should not happen\n"); + return; + } + + target = xtables_find_target(name, XTF_TRY_LOAD); + if (target == NULL) + return; + + tginfo = nft_rule_expr_get(expr, NFT_EXPR_TG_INFO, &len); + if (tginfo == NULL) + return; + + emu = calloc(1, sizeof(struct xt_entry_match) + len); + if (emu == NULL) + return; + + memcpy(emu->data, tginfo, len); + + if (target->alias) + printf("-j %s", target->alias(emu)); + else + printf("-j %s", target->name); + + /* FIXME missing parameter */ + target->save(NULL, emu); + + free(emu); +} + +static void nft_immediate_save(struct nft_rule_expr *expr) +{ + uint32_t verdict; + + verdict = nft_rule_expr_get_u32(expr, NFT_EXPR_IMM_VERDICT); + + switch(verdict) { + case NF_ACCEPT: + printf("-j ACCEPT"); + break; + case NF_DROP: + printf("-j DROP"); + break; + case NFT_RETURN: + printf("-j RETURN"); + break; + case NFT_GOTO: + printf("-g %s", + nft_rule_expr_get_str(expr, NFT_EXPR_IMM_CHAIN)); + break; + case NFT_JUMP: + printf("-j %s", + nft_rule_expr_get_str(expr, NFT_EXPR_IMM_CHAIN)); + break; + } +} + +static void +nft_print_meta(struct nft_rule_expr *e, struct nft_rule_expr_iter *iter) +{ + uint8_t key = nft_rule_expr_get_u8(e, NFT_EXPR_META_KEY); + uint32_t value; + const char *name; + char ifname[IFNAMSIZ]; + const char *ifname_ptr; + size_t len; + + e = nft_rule_expr_iter_next(iter); + if (e == NULL) + return; + + name = nft_rule_expr_get_str(e, NFT_RULE_EXPR_ATTR_NAME); + /* meta should be followed by cmp */ + if (strcmp(name, "cmp") != 0) { + DEBUGP("skipping no cmp after meta\n"); + return; + } + + switch(key) { + case NFT_META_IIF: + value = nft_rule_expr_get_u32(e, NFT_EXPR_CMP_DATA); + if_indextoname(value, ifname); + + switch(nft_rule_expr_get_u8(e, NFT_EXPR_CMP_OP)) { + case NFT_CMP_EQ: + printf("-i %s ", ifname); + break; + case NFT_CMP_NEQ: + printf("! -i %s ", ifname); + break; + } + break; + case NFT_META_OIF: + value = nft_rule_expr_get_u32(e, NFT_EXPR_CMP_DATA); + if_indextoname(value, ifname); + + switch(nft_rule_expr_get_u8(e, NFT_EXPR_CMP_OP)) { + case NFT_CMP_EQ: + printf("-o %s ", ifname); + break; + case NFT_CMP_NEQ: + printf("! -o %s ", ifname); + break; + } + break; + case NFT_META_IIFNAME: + ifname_ptr = nft_rule_expr_get(e, NFT_EXPR_CMP_DATA, &len); + memcpy(ifname, ifname_ptr, len); + ifname[len] = '\0'; + + /* if this is zero, then assume this is a interface mask */ + if (if_nametoindex(ifname) == 0) { + ifname[len] = '+'; + ifname[len+1] = '\0'; + } + + switch(nft_rule_expr_get_u8(e, NFT_EXPR_CMP_OP)) { + case NFT_CMP_EQ: + printf("-i %s ", ifname); + break; + case NFT_CMP_NEQ: + printf("! -i %s ", ifname); + break; + } + break; + case NFT_META_OIFNAME: + ifname_ptr = nft_rule_expr_get(e, NFT_EXPR_CMP_DATA, &len); + memcpy(ifname, ifname_ptr, len); + ifname[len] = '\0'; + + /* if this is zero, then assume this is a interface mask */ + if (if_nametoindex(ifname) == 0) { + ifname[len] = '+'; + ifname[len+1] = '\0'; + } + + switch(nft_rule_expr_get_u8(e, NFT_EXPR_CMP_OP)) { + case NFT_CMP_EQ: + printf("-o %s ", ifname); + break; + case NFT_CMP_NEQ: + printf("! -o %s ", ifname); + break; + } + break; + default: + DEBUGP("unknown meta key %d\n", key); + break; + } +} + +static void +get_cmp_data(struct nft_rule_expr_iter *iter, void *data, size_t dlen, bool *inv) +{ + struct nft_rule_expr *e; + const char *name; + size_t len; + uint8_t op; + + e = nft_rule_expr_iter_next(iter); + if (e == NULL) + return; + + name = nft_rule_expr_get_str(e, NFT_RULE_EXPR_ATTR_NAME); + if (strcmp(name, "cmp") != 0) { + DEBUGP("skipping no cmp after meta\n"); + return; + } + + memcpy(data, nft_rule_expr_get(e, NFT_EXPR_CMP_DATA, &len), dlen); + op = nft_rule_expr_get_u8(e, NFT_EXPR_CMP_OP); + if (op == NFT_CMP_NEQ) + *inv = true; + else + *inv = false; +} + +static void print_proto(uint16_t proto, int invert) +{ + const struct protoent *pent = getprotobynumber(proto); + + if (invert) + printf("! "); + + if (pent) { + printf("-p %s ", pent->p_name); + return; + } + + printf("-p %u ", proto); +} + +static const char *mask_to_str(uint32_t mask) +{ + static char mask_str[sizeof("255.255.255.255")]; + uint32_t bits, hmask = ntohl(mask); + struct in_addr mask_addr = { + .s_addr = mask, + }; + int i; + + if (mask == 0xFFFFFFFFU) { + sprintf(mask_str, "32"); + return mask_str; + } + + i = 32; + bits = 0xFFFFFFFEU; + while (--i >= 0 && hmask != bits) + bits <<= 1; + if (i >= 0) + sprintf(mask_str, "%u", i); + else + sprintf(mask_str, "%s", inet_ntoa(mask_addr)); + + return mask_str; +} + +static void +nft_print_payload(struct nft_rule_expr *e, struct nft_rule_expr_iter *iter) +{ + uint32_t offset; + bool inv; + + offset = nft_rule_expr_get_u32(e, NFT_EXPR_PAYLOAD_OFFSET); + + switch(offset) { + struct in_addr addr; + uint8_t proto; + + case offsetof(struct iphdr, saddr): + get_cmp_data(iter, &addr, sizeof(addr), &inv); + if (inv) + printf("! -s %s/%s ", inet_ntoa(addr), + mask_to_str(0xffffffff)); + else + printf("-s %s/%s ", inet_ntoa(addr), + mask_to_str(0xffffffff)); + break; + case offsetof(struct iphdr, daddr): + get_cmp_data(iter, &addr, sizeof(addr), &inv); + if (inv) + printf("! -d %s/%s ", inet_ntoa(addr), + mask_to_str(0xffffffff)); + else + printf("-d %s/%s ", inet_ntoa(addr), + mask_to_str(0xffffffff)); + break; + case offsetof(struct iphdr, protocol): + get_cmp_data(iter, &proto, sizeof(proto), &inv); + print_proto(proto, inv); + break; + default: + DEBUGP("unknown payload offset %d\n", offset); + break; + } +} + +static void +nft_print_counters(struct nft_rule_expr *e, struct nft_rule_expr_iter *iter, + bool counters) +{ + if (counters) { + printf("-c %lu %lu ", + nft_rule_expr_get_u64(e, NFT_EXPR_CTR_PACKETS), + nft_rule_expr_get_u64(e, NFT_EXPR_CTR_BYTES)); + } +} + +static void nft_rule_print_save(struct nft_rule *r, bool counters) +{ + struct nft_rule_expr_iter *iter; + struct nft_rule_expr *expr; + + /* print chain name */ + printf("-A %s ", nft_rule_attr_get_str(r, NFT_RULE_ATTR_CHAIN)); + + iter = nft_rule_expr_iter_create(r); + if (iter == NULL) + return; + + expr = nft_rule_expr_iter_next(iter); + while (expr != NULL) { + const char *name = + nft_rule_expr_get_str(expr, NFT_RULE_EXPR_ATTR_NAME); + + if (strcmp(name, "counter") == 0) { + nft_print_counters(expr, iter, counters); + } else if (strcmp(name, "payload") == 0) { + nft_print_payload(expr, iter); + } else if (strcmp(name, "meta") == 0) { + nft_print_meta(expr, iter); + } else if (strcmp(name, "match") == 0) { + nft_match_save(expr); + } else if (strcmp(name, "target") == 0) { + nft_target_save(expr); + } else if (strcmp(name, "immediate") == 0) { + nft_immediate_save(expr); + } + + expr = nft_rule_expr_iter_next(iter); + } + + printf("\n"); +} + +static int nft_chain_list_cb(const struct nlmsghdr *nlh, void *data) +{ + struct nft_chain *c; + struct nft_chain_list *list = data; + + c = nft_chain_alloc(); + if (c == NULL) { + perror("OOM"); + goto err; + } + + if (nft_chain_nlmsg_parse(nlh, c) < 0) { + perror("nft_rule_nlmsg_parse"); + goto out; + } + + nft_chain_list_add(c, list); + + return MNL_CB_OK; +out: + nft_chain_free(c); +err: + return MNL_CB_OK; +} + +static struct nft_chain_list *nft_chain_list_get(struct nft_handle *h) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + int ret; + struct nft_chain_list *list; + + list = nft_chain_list_alloc(); + if (list == NULL) { + DEBUGP("cannot allocate rule list\n"); + return 0; + } + + nlh = nft_chain_nlmsg_build_hdr(buf, NFT_MSG_GETCHAIN, AF_INET, + NLM_F_DUMP, h->seq); + + ret = mnl_talk(h, nlh, nft_chain_list_cb, list); + if (ret < 0) + perror("mnl_talk:nft_chain_list_get"); + + return list; +} + +struct nft_chain_list *nft_chain_dump(struct nft_handle *h) +{ + return nft_chain_list_get(h); +} + +static const char *policy_name[NF_ACCEPT+1] = { + [NF_DROP] = "DROP", + [NF_ACCEPT] = "ACCEPT", +}; + +static void nft_chain_print_save(struct nft_chain *c, bool basechain) +{ + const char *chain = nft_chain_attr_get_str(c, NFT_CHAIN_ATTR_NAME); + uint64_t pkts = nft_chain_attr_get_u64(c, NFT_CHAIN_ATTR_PACKETS); + uint64_t bytes = nft_chain_attr_get_u64(c, NFT_CHAIN_ATTR_BYTES); + + /* print chain name */ + if (basechain) { + uint32_t pol = NF_ACCEPT; + + /* no default chain policy? don't crash, display accept */ + if (nft_chain_attr_get(c, NFT_CHAIN_ATTR_POLICY)) + pol = nft_chain_attr_get_u32(c, NFT_CHAIN_ATTR_POLICY); + + printf(":%s %s [%lu:%lu]\n", chain, policy_name[pol], + pkts, bytes); + } else + printf(":%s - [%lu:%lu]\n", chain, pkts, bytes); +} + +int nft_chain_save(struct nft_handle *h, struct nft_chain_list *list, + const char *table) +{ + struct nft_chain_list_iter *iter; + struct nft_chain *c; + + iter = nft_chain_list_iter_create(list); + if (iter == NULL) { + DEBUGP("cannot allocate rule list iterator\n"); + return 0; + } + + c = nft_chain_list_iter_next(iter); + while (c != NULL) { + const char *chain_table = + nft_chain_attr_get_str(c, NFT_CHAIN_ATTR_TABLE); + bool basechain = false; + + if (strcmp(table, chain_table) != 0) + goto next; + + if (nft_chain_attr_get(c, NFT_CHAIN_ATTR_HOOKNUM)) + basechain = true; + + nft_chain_print_save(c, basechain); +next: + c = nft_chain_list_iter_next(iter); + } + + nft_chain_list_free(list); + + return 1; +} + +static int nft_rule_list_cb(const struct nlmsghdr *nlh, void *data) +{ + struct nft_rule *r; + struct nft_rule_list *list = data; + + r = nft_rule_alloc(); + if (r == NULL) { + perror("OOM"); + goto err; + } + + if (nft_rule_nlmsg_parse(nlh, r) < 0) { + perror("nft_rule_nlmsg_parse"); + goto out; + } + + nft_rule_list_add(r, list); + + return MNL_CB_OK; +out: + nft_rule_free(r); +err: + return MNL_CB_OK; +} + +static struct nft_rule_list *nft_rule_list_get(struct nft_handle *h) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + struct nft_rule_list *list; + int ret; + + list = nft_rule_list_alloc(); + if (list == NULL) { + DEBUGP("cannot allocate rule list\n"); + return 0; + } + + nlh = nft_rule_nlmsg_build_hdr(buf, NFT_MSG_GETRULE, AF_INET, + NLM_F_DUMP, h->seq); + + ret = mnl_talk(h, nlh, nft_rule_list_cb, list); + if (ret < 0) { + perror("mnl_talk:nft_rule_save"); + nft_rule_list_free(list); + return NULL; + } + + return list; +} + +int nft_rule_save(struct nft_handle *h, const char *table, bool counters) +{ + struct nft_rule_list *list; + struct nft_rule_list_iter *iter; + struct nft_rule *r; + + list = nft_rule_list_get(h); + if (list == NULL) { + DEBUGP("cannot retrieve rule list from kernel\n"); + return 0; + } + + iter = nft_rule_list_iter_create(list); + if (iter == NULL) { + DEBUGP("cannot allocate rule list iterator\n"); + return 0; + } + + r = nft_rule_list_iter_next(iter); + while (r != NULL) { + const char *rule_table = + nft_rule_attr_get_str(r, NFT_RULE_ATTR_TABLE); + + if (strcmp(table, rule_table) != 0) + goto next; + + nft_rule_print_save(r, counters); + +next: + r = nft_rule_list_iter_next(iter); + } + + nft_rule_list_free(list); + + /* the core expects 1 for success and 0 for error */ + return 1; +} + +static void +__nft_rule_flush(struct nft_handle *h, const char *table, const char *chain) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + struct nft_rule *r; + + r = nft_rule_alloc(); + if (r == NULL) + return; + + nft_rule_attr_set(r, NFT_RULE_ATTR_TABLE, (char *)table); + nft_rule_attr_set(r, NFT_RULE_ATTR_CHAIN, (char *)chain); + + /* Delete all rules in this table + chain */ + nlh = nft_chain_nlmsg_build_hdr(buf, NFT_MSG_DELRULE, AF_INET, + NLM_F_ACK, h->seq); + nft_rule_nlmsg_build_payload(nlh, r); + nft_rule_free(r); + + if (mnl_talk(h, nlh, NULL, NULL) < 0) { + if (errno != EEXIST) + perror("mnl_talk:__nft_rule_flush"); + } +} + +int nft_rule_flush(struct nft_handle *h, const char *chain, const char *table) +{ + int ret; + struct nft_chain_list *list; + struct nft_chain_list_iter *iter; + struct nft_chain *c; + + nft_fn = nft_rule_flush; + + list = nft_chain_list_get(h); + if (list == NULL) { + ret = 0; + goto err; + } + + iter = nft_chain_list_iter_create(list); + if (iter == NULL) { + DEBUGP("cannot allocate rule list iterator\n"); + return 0; + } + + c = nft_chain_list_iter_next(iter); + while (c != NULL) { + const char *table_name = + nft_chain_attr_get_str(c, NFT_CHAIN_ATTR_TABLE); + const char *chain_name = + nft_chain_attr_get_str(c, NFT_CHAIN_ATTR_NAME); + + if (strcmp(table, table_name) != 0) + goto next; + + if (chain != NULL && strcmp(chain, chain_name) != 0) + goto next; + + __nft_rule_flush(h, table_name, chain_name); + +next: + c = nft_chain_list_iter_next(iter); + } + +err: + nft_chain_list_free(list); + + /* the core expects 1 for success and 0 for error */ + return ret == 0 ? 1 : 0; +} + +int nft_chain_user_add(struct nft_handle *h, const char *chain, const char *table) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + struct nft_chain *c; + int ret; + + c = nft_chain_alloc(); + if (c == NULL) { + DEBUGP("cannot allocate chain\n"); + return -1; + } + + nft_chain_attr_set(c, NFT_CHAIN_ATTR_TABLE, (char *)table); + nft_chain_attr_set(c, NFT_CHAIN_ATTR_NAME, (char *)chain); + + nlh = nft_chain_nlmsg_build_hdr(buf, NFT_MSG_NEWCHAIN, AF_INET, + NLM_F_ACK|NLM_F_EXCL, h->seq); + nft_chain_nlmsg_build_payload(nlh, c); + nft_chain_free(c); + + ret = mnl_talk(h, nlh, NULL, NULL); + if (ret < 0) { + if (errno != EEXIST) + perror("mnl_talk:nft_chain_add"); + } + + /* the core expects 1 for success and 0 for error */ + return ret == 0 ? 1 : 0; +} + +static int __nft_chain_del(struct nft_handle *h, struct nft_chain *c) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + int ret; + + nlh = nft_chain_nlmsg_build_hdr(buf, NFT_MSG_DELCHAIN, AF_INET, + NLM_F_ACK, h->seq); + nft_chain_nlmsg_build_payload(nlh, c); + + ret = mnl_talk(h, nlh, NULL, NULL); + if (ret < 0) { + if (errno != EEXIST && errno != ENOENT) + perror("mnl_talk:__nft_chain_del"); + } + + return ret; +} + +static bool nft_chain_builtin(struct nft_chain *c) +{ + /* Check if this chain has hook number, in that case is built-in. + * Should we better export the flags to user-space via nf_tables? + */ + return nft_chain_attr_get(c, NFT_CHAIN_ATTR_HOOKNUM) != NULL; +} + +int nft_chain_user_del(struct nft_handle *h, const char *chain, const char *table) +{ + struct nft_chain_list *list; + struct nft_chain_list_iter *iter; + struct nft_chain *c; + int ret = 0; + int deleted_ctr = 0; + + list = nft_chain_list_get(h); + if (list == NULL) + goto err; + + iter = nft_chain_list_iter_create(list); + if (iter == NULL) { + DEBUGP("cannot allocate rule list iterator\n"); + return 0; + } + + c = nft_chain_list_iter_next(iter); + while (c != NULL) { + const char *table_name = + nft_chain_attr_get_str(c, NFT_CHAIN_ATTR_TABLE); + const char *chain_name = + nft_chain_attr_get_str(c, NFT_CHAIN_ATTR_NAME); + + /* don't delete built-in chain */ + if (nft_chain_builtin(c)) + goto next; + + if (strcmp(table, table_name) != 0) + goto next; + + if (chain != NULL && strcmp(chain, chain_name) != 0) + goto next; + + ret = __nft_chain_del(h, c); + if (ret < 0) + break; + + deleted_ctr++; +next: + c = nft_chain_list_iter_next(iter); + } + +err: + nft_chain_list_free(list); + + /* chain not found */ + if (ret < 0 && deleted_ctr == 0) + errno = ENOENT; + + /* the core expects 1 for success and 0 for error */ + return ret == 0 ? 1 : 0; +} + +int nft_chain_user_rename(struct nft_handle *h,const char *chain, + const char *table, const char *newname) +{ + int ret; + + /* XXX need new operation in nf_tables to support this */ + ret = nft_chain_user_del(h, chain, table); + if (ret < 0) + return ret; + + return nft_chain_user_add(h, newname, table); +} + +static int nft_table_list_cb(const struct nlmsghdr *nlh, void *data) +{ + struct nft_table *t; + struct nft_table_list *list = data; + + t = nft_table_alloc(); + if (t == NULL) { + perror("OOM"); + goto err; + } + + if (nft_table_nlmsg_parse(nlh, t) < 0) { + perror("nft_rule_nlmsg_parse"); + goto out; + } + + nft_table_list_add(t, list); + + return MNL_CB_OK; +out: + nft_table_free(t); +err: + return MNL_CB_OK; +} + +static struct nft_table_list *nft_table_list_get(struct nft_handle *h) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + int ret; + struct nft_table_list *list; + + list = nft_table_list_alloc(); + if (list == NULL) { + DEBUGP("cannot allocate table list\n"); + return 0; + } + + nlh = nft_rule_nlmsg_build_hdr(buf, NFT_MSG_GETTABLE, AF_INET, + NLM_F_DUMP, h->seq); + + ret = mnl_talk(h, nlh, nft_table_list_cb, list); + if (ret < 0) + perror("mnl_talk:nft_table_list_get"); + + return list; +} + +bool nft_table_find(struct nft_handle *h, const char *tablename) +{ + struct nft_table_list *list; + struct nft_table_list_iter *iter; + struct nft_table *t; + bool ret = false; + + list = nft_table_list_get(h); + if (list == NULL) + goto err; + + iter = nft_table_list_iter_create(list); + if (iter == NULL) { + DEBUGP("cannot allocate rule list iterator\n"); + goto err; + } + + t = nft_table_list_iter_next(iter); + while (t != NULL) { + const char *this_tablename = + nft_table_attr_get(t, NFT_TABLE_ATTR_NAME); + + if (strcmp(tablename, this_tablename) == 0) + return true; + + t = nft_table_list_iter_next(iter); + } + + nft_table_list_free(list); + +err: + return ret; +} + +int nft_for_each_table(struct nft_handle *h, + int (*func)(struct nft_handle *h, const char *tablename, bool counters), + bool counters) +{ + int ret = 1; + struct nft_table_list *list; + struct nft_table_list_iter *iter; + struct nft_table *t; + + list = nft_table_list_get(h); + if (list == NULL) { + ret = 0; + goto err; + } + + iter = nft_table_list_iter_create(list); + if (iter == NULL) { + DEBUGP("cannot allocate rule list iterator\n"); + return 0; + } + + t = nft_table_list_iter_next(iter); + while (t != NULL) { + const char *tablename = + nft_table_attr_get(t, NFT_TABLE_ATTR_NAME); + + func(h, tablename, counters); + + t = nft_table_list_iter_next(iter); + } + + nft_table_list_free(list); + +err: + /* the core expects 1 for success and 0 for error */ + return ret == 0 ? 1 : 0; +} + +static inline int +match_different(const struct xt_entry_match *a, + const unsigned char *a_elems, + const unsigned char *b_elems, + unsigned char **maskptr) +{ + const struct xt_entry_match *b; + unsigned int i; + + /* Offset of b is the same as a. */ + b = (void *)b_elems + ((unsigned char *)a - a_elems); + + if (a->u.match_size != b->u.match_size) + return 1; + + if (strcmp(a->u.user.name, b->u.user.name) != 0) + return 1; + + *maskptr += XT_ALIGN(sizeof(*a)); + + for (i = 0; i < a->u.match_size - XT_ALIGN(sizeof(*a)); i++) + if (((a->data[i] ^ b->data[i]) & (*maskptr)[i]) != 0) + return 1; + *maskptr += i; + return 0; +} + +static bool +is_same(const struct iptables_command_state *a, const struct iptables_command_state *b) +{ + unsigned int i; + + /* Always compare head structures: ignore mask here. */ + if (a->fw.ip.src.s_addr != b->fw.ip.src.s_addr + || a->fw.ip.dst.s_addr != b->fw.ip.dst.s_addr + || a->fw.ip.smsk.s_addr != b->fw.ip.smsk.s_addr + || a->fw.ip.dmsk.s_addr != b->fw.ip.dmsk.s_addr + || a->fw.ip.proto != b->fw.ip.proto + || a->fw.ip.flags != b->fw.ip.flags + || a->fw.ip.invflags != b->fw.ip.invflags) { + DEBUGP("different src/dst/proto/flags/invflags\n"); + return false; + } + + for (i = 0; i < IFNAMSIZ; i++) { + if (a->fw.ip.iniface_mask[i] != b->fw.ip.iniface_mask[i]) { + DEBUGP("different iniface mask %x, %x (%d)\n", + a->fw.ip.iniface_mask[i] & 0xff, b->fw.ip.iniface_mask[i] & 0xff, i); + return false; + } + if ((a->fw.ip.iniface[i] & a->fw.ip.iniface_mask[i]) + != (b->fw.ip.iniface[i] & b->fw.ip.iniface_mask[i])) { + DEBUGP("different iniface\n"); + return false; + } + if (a->fw.ip.outiface_mask[i] != b->fw.ip.outiface_mask[i]) { + DEBUGP("different outiface mask\n"); + return false; + } + if ((a->fw.ip.outiface[i] & a->fw.ip.outiface_mask[i]) + != (b->fw.ip.outiface[i] & b->fw.ip.outiface_mask[i])) { + DEBUGP("different outiface\n"); + return false; + } + } + + return true; +} + +static void +nft_parse_meta(struct nft_rule_expr *e, struct nft_rule_expr_iter *iter, + struct iptables_command_state *cs) +{ + uint8_t key = nft_rule_expr_get_u8(e, NFT_EXPR_META_KEY); + uint32_t value; + const char *name; + const void *ifname; + size_t len; + + e = nft_rule_expr_iter_next(iter); + if (e == NULL) + return; + + name = nft_rule_expr_get_str(e, NFT_RULE_EXPR_ATTR_NAME); + if (strcmp(name, "cmp") != 0) { + DEBUGP("skipping no cmp after meta\n"); + return; + } + + switch(key) { + case NFT_META_IIF: + value = nft_rule_expr_get_u32(e, NFT_EXPR_CMP_DATA); + if (nft_rule_expr_get_u8(e, NFT_EXPR_CMP_OP) == NFT_CMP_NEQ) + cs->fw.ip.invflags |= IPT_INV_VIA_IN; + + if_indextoname(value, cs->fw.ip.iniface); + + memset(cs->fw.ip.iniface_mask, 0xff, + strlen(cs->fw.ip.iniface)+1); + break; + case NFT_META_OIF: + value = nft_rule_expr_get_u32(e, NFT_EXPR_CMP_DATA); + if (nft_rule_expr_get_u8(e, NFT_EXPR_CMP_OP) == NFT_CMP_NEQ) + cs->fw.ip.invflags |= IPT_INV_VIA_OUT; + + if_indextoname(value, cs->fw.ip.outiface); + + memset(cs->fw.ip.outiface_mask, 0xff, + strlen(cs->fw.ip.outiface)+1); + break; + case NFT_META_IIFNAME: + ifname = nft_rule_expr_get(e, NFT_EXPR_CMP_DATA, &len); + if (nft_rule_expr_get_u8(e, NFT_EXPR_CMP_OP) == NFT_CMP_NEQ) + cs->fw.ip.invflags |= IPT_INV_VIA_IN; + + memcpy(cs->fw.ip.iniface, ifname, len); + cs->fw.ip.iniface[len] = '\0'; + + /* If zero, then this is an interface mask */ + if (if_nametoindex(cs->fw.ip.iniface) == 0) { + cs->fw.ip.iniface[len] = '+'; + cs->fw.ip.iniface[len+1] = '\0'; + } + + memset(cs->fw.ip.iniface_mask, 0xff, len); + break; + case NFT_META_OIFNAME: + ifname = nft_rule_expr_get(e, NFT_EXPR_CMP_DATA, &len); + if (nft_rule_expr_get_u8(e, NFT_EXPR_CMP_OP) == NFT_CMP_NEQ) + cs->fw.ip.invflags |= IPT_INV_VIA_OUT; + + memcpy(cs->fw.ip.outiface, ifname, len); + cs->fw.ip.outiface[len] = '\0'; + + /* If zero, then this is an interface mask */ + if (if_nametoindex(cs->fw.ip.outiface) == 0) { + cs->fw.ip.outiface[len] = '+'; + cs->fw.ip.outiface[len+1] = '\0'; + } + + memset(cs->fw.ip.outiface_mask, 0xff, len); + break; + default: + DEBUGP("unknown meta key %d\n", key); + break; + } +} + +static void +nft_parse_payload(struct nft_rule_expr *e, struct nft_rule_expr_iter *iter, + struct iptables_command_state *cs) +{ + uint32_t offset; + bool inv; + + offset = nft_rule_expr_get_u32(e, NFT_EXPR_PAYLOAD_OFFSET); + + switch(offset) { + struct in_addr addr; + uint8_t proto; + + case offsetof(struct iphdr, saddr): + get_cmp_data(iter, &addr, sizeof(addr), &inv); + cs->fw.ip.src.s_addr = addr.s_addr; + cs->fw.ip.smsk.s_addr = 0xffffffff; + if (inv) + cs->fw.ip.invflags |= IPT_INV_SRCIP; + break; + case offsetof(struct iphdr, daddr): + get_cmp_data(iter, &addr, sizeof(addr), &inv); + cs->fw.ip.dst.s_addr = addr.s_addr; + cs->fw.ip.dmsk.s_addr = 0xffffffff; + if (inv) + cs->fw.ip.invflags |= IPT_INV_DSTIP; + break; + case offsetof(struct iphdr, protocol): + get_cmp_data(iter, &proto, sizeof(proto), &inv); + cs->fw.ip.proto = proto; + if (inv) + cs->fw.ip.invflags |= IPT_INV_PROTO; + break; + default: + DEBUGP("unknown payload offset %d\n", offset); + break; + } +} + +static void +nft_parse_counter(struct nft_rule_expr *e, struct nft_rule_expr_iter *iter, + struct xt_counters *counters) +{ + counters->pcnt = nft_rule_expr_get_u64(e, NFT_EXPR_CTR_PACKETS); + counters->bcnt = nft_rule_expr_get_u64(e, NFT_EXPR_CTR_BYTES); +} + +static void +nft_parse_immediate(struct nft_rule_expr *e, struct nft_rule_expr_iter *iter, + struct iptables_command_state *cs) +{ + int verdict = nft_rule_expr_get_u32(e, NFT_EXPR_IMM_VERDICT); + const char *chain = nft_rule_expr_get_str(e, NFT_EXPR_IMM_CHAIN); + + /* Standard target? */ + switch(verdict) { + case NF_ACCEPT: + cs->jumpto = "ACCEPT"; + return; + case NF_DROP: + cs->jumpto = "DROP"; + return; + case NFT_RETURN: + cs->jumpto = "RETURN"; + return; + case NFT_GOTO: + cs->fw.ip.flags |= IPT_F_GOTO; + case NFT_JUMP: + cs->jumpto = chain; + return; + } +} + +static void +nft_rule_to_iptables_command_state(struct nft_rule *r, + struct iptables_command_state *cs) +{ + struct nft_rule_expr_iter *iter; + struct nft_rule_expr *expr; + + iter = nft_rule_expr_iter_create(r); + if (iter == NULL) + return; + + expr = nft_rule_expr_iter_next(iter); + while (expr != NULL) { + const char *name = + nft_rule_expr_get_str(expr, NFT_RULE_EXPR_ATTR_NAME); + + if (strcmp(name, "counter") == 0) { + nft_parse_counter(expr, iter, &cs->counters); + } else if (strcmp(name, "payload") == 0) { + nft_parse_payload(expr, iter, cs); + } else if (strcmp(name, "meta") == 0) { + nft_parse_meta(expr, iter, cs); + } else if (strcmp(name, "immediate") == 0) { + nft_parse_immediate(expr, iter, cs); + } + + expr = nft_rule_expr_iter_next(iter); + } + + nft_rule_expr_iter_destroy(iter); +} + +static int matches_howmany(struct xtables_rule_match *matches) +{ + struct xtables_rule_match *matchp; + int matches_ctr = 0; + + for (matchp = matches; matchp; matchp = matchp->next) + matches_ctr++; + + return matches_ctr; +} + +static bool +__find_match(struct nft_rule_expr *expr, struct xtables_rule_match *matches) +{ + const char *matchname = nft_rule_expr_get_str(expr, NFT_EXPR_MT_NAME); + /* Netlink aligns this match info, don't trust this length variable */ + const char *data = nft_rule_expr_get_str(expr, NFT_EXPR_MT_INFO); + struct xtables_rule_match *matchp; + bool found = false; + + for (matchp = matches; matchp; matchp = matchp->next) { + struct xt_entry_match *m = matchp->match->m; + + if (strcmp(m->u.user.name, matchname) != 0) { + DEBUGP("mismatching match name\n"); + continue; + } + + if (memcmp(data, m->data, m->u.user.match_size - sizeof(*m)) != 0) { + DEBUGP("mismatch match data\n"); + continue; + } + found = true; + break; + } + + return found; +} + +static bool find_matches(struct xtables_rule_match *matches, struct nft_rule *r) +{ + struct nft_rule_expr_iter *iter; + struct nft_rule_expr *expr; + int kernel_matches = 0; + + iter = nft_rule_expr_iter_create(r); + if (iter == NULL) { + DEBUGP("cannot allocate rule list iterator\n"); + return false; + } + + expr = nft_rule_expr_iter_next(iter); + while (expr != NULL) { + const char *name = + nft_rule_expr_get_str(expr, NFT_RULE_EXPR_ATTR_NAME); + + if (strcmp(name, "match") == 0) { + if (!__find_match(expr, matches)) + return false; + + kernel_matches++; + } + expr = nft_rule_expr_iter_next(iter); + } + nft_rule_expr_iter_destroy(iter); + + /* same number of matches? */ + if (matches_howmany(matches) != kernel_matches) + return false; + + return true; +} + +static bool __find_target(struct nft_rule_expr *expr, struct xt_entry_target *t) +{ + size_t len; + const char *tgname = nft_rule_expr_get_str(expr, NFT_EXPR_TG_NAME); + /* Netlink aligns this target info, don't trust this length variable */ + const char *data = nft_rule_expr_get(expr, NFT_EXPR_TG_INFO, &len); + + if (strcmp(t->u.user.name, tgname) != 0) { + DEBUGP("mismatching target name\n"); + return false; + } + + if (memcmp(data, t->data, t->u.user.target_size - sizeof(*t)) != 0) + return false; + + return true; +} + +static int targets_howmany(struct xtables_target *target) +{ + return target != NULL ? 1 : 0; +} + +static bool +find_target(struct xtables_target *target, struct nft_rule *r) +{ + struct nft_rule_expr_iter *iter; + struct nft_rule_expr *expr; + int kernel_targets = 0; + + /* Special case: we use native immediate expressions to emulated + * standard targets. Also, we don't want to crash with no targets. + */ + if (target == NULL || strcmp(target->name, "standard") == 0) + return true; + + iter = nft_rule_expr_iter_create(r); + if (iter == NULL) { + DEBUGP("cannot allocate rule list iterator\n"); + return false; + } + + expr = nft_rule_expr_iter_next(iter); + while (expr != NULL) { + const char *name = + nft_rule_expr_get_str(expr, NFT_RULE_EXPR_ATTR_NAME); + + if (strcmp(name, "target") == 0) { + /* we may support several targets in the future */ + if (!__find_target(expr, target->t)) + return false; + + kernel_targets++; + } + expr = nft_rule_expr_iter_next(iter); + } + nft_rule_expr_iter_destroy(iter); + + /* same number of targets? */ + if (targets_howmany(target) != kernel_targets) { + DEBUGP("kernel targets is %d but we passed %d\n", + kernel_targets, targets_howmany(target)); + return false; + } + + return true; +} + +static bool +find_immediate(struct nft_rule *r, const char *jumpto) +{ + struct nft_rule_expr_iter *iter; + struct nft_rule_expr *expr; + + iter = nft_rule_expr_iter_create(r); + if (iter == NULL) { + DEBUGP("cannot allocate rule list iterator\n"); + return false; + } + + expr = nft_rule_expr_iter_next(iter); + while (expr != NULL) { + const char *name = + nft_rule_expr_get_str(expr, NFT_RULE_EXPR_ATTR_NAME); + + if (strcmp(name, "immediate") == 0) { + int verdict = nft_rule_expr_get_u32(expr, NFT_EXPR_IMM_VERDICT); + const char *verdict_name = NULL; + + /* No target specified but immediate shows up, this + * is not the rule we are looking for. + */ + if (strlen(jumpto) == 0) + return false; + + switch(verdict) { + case NF_ACCEPT: + verdict_name = "ACCEPT"; + break; + case NF_DROP: + verdict_name = "DROP"; + break; + case NFT_RETURN: + verdict_name = "RETURN"; + break; + } + + /* Standard target? */ + if (verdict_name && strcmp(jumpto, verdict_name) != 0) + return false; + } + expr = nft_rule_expr_iter_next(iter); + } + nft_rule_expr_iter_destroy(iter); + + return true; +} + +static void +__nft_rule_del(struct nft_handle *h, struct nft_rule *r) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + int ret; + + nlh = nft_rule_nlmsg_build_hdr(buf, NFT_MSG_DELRULE, AF_INET, + NLM_F_ACK, h->seq); + nft_rule_nlmsg_build_payload(nlh, r); + + nft_rule_print_debug(r, nlh); + + ret = mnl_talk(h, nlh, NULL, NULL); + if (ret < 0) + perror("mnl_talk:nft_rule_del"); +} + +static int +__nft_rule_check(struct nft_handle *h, const char *chain, const char *table, + struct iptables_command_state *cs, + bool delete, int rulenum, bool verbose) +{ + struct nft_rule_list *list; + struct nft_rule_list_iter *iter; + struct nft_rule *r; + int ret = 0; + int rule_ctr = 0; + bool found = false; + + list = nft_rule_list_get(h); + if (list == NULL) { + DEBUGP("cannot retrieve rule list from kernel\n"); + return 0; + } + + iter = nft_rule_list_iter_create(list); + if (iter == NULL) { + DEBUGP("cannot allocate rule list iterator\n"); + return 0; + } + + r = nft_rule_list_iter_next(iter); + while (r != NULL) { + const char *rule_table = + nft_rule_attr_get_str(r, NFT_RULE_ATTR_TABLE); + const char *rule_chain = + nft_rule_attr_get_str(r, NFT_RULE_ATTR_CHAIN); + struct iptables_command_state this = {}; + + if (strcmp(table, rule_table) != 0 || + strcmp(chain, rule_chain) != 0) { + DEBUGP("different chain / table\n"); + goto next; + } + + if (rulenum >= 0) { + /* Delete by rule number case */ + if (rule_ctr != rulenum) { + rule_ctr++; + goto next; + } + } else { + /* Delete by matching rule case */ + DEBUGP("comparing with... "); +#ifdef DEBUG_DEL + nft_rule_print_save(r, 0); +#endif + + nft_rule_to_iptables_command_state(r, &this); + + if (!is_same(cs, &this)) + goto next; + + if (!find_matches(cs->matches, r)) { + DEBUGP("matches not found\n"); + goto next; + } + + if (!find_target(cs->target, r)) { + DEBUGP("target not found\n"); + goto next; + } + + if (!find_immediate(r, cs->jumpto)) { + DEBUGP("immediate not found\n"); + goto next; + } + + found = true; + break; + } +next: + r = nft_rule_list_iter_next(iter); + } + + if (found) { + ret = 1; + + if (delete) { + DEBUGP("deleting rule\n"); + __nft_rule_del(h, r); + } + } + + nft_rule_list_iter_destroy(iter); + nft_rule_list_free(list); + + if (ret == 0) + errno = ENOENT; + + return ret; +} + +int nft_rule_check(struct nft_handle *h, const char *chain, + const char *table, struct iptables_command_state *e, + bool verbose) +{ + nft_fn = nft_rule_check; + + return __nft_rule_check(h, chain, table, e, false, -1, verbose); +} + +int nft_rule_delete(struct nft_handle *h, const char *chain, + const char *table, struct iptables_command_state *e, + bool verbose) +{ + nft_fn = nft_rule_delete; + + return __nft_rule_check(h, chain, table, e, true, -1, verbose); +} + +int nft_rule_delete_num(struct nft_handle *h, const char *chain, + const char *table, int rulenum, + bool verbose) +{ + nft_fn = nft_rule_delete_num; + + return __nft_rule_check(h, chain, table, NULL, true, rulenum, verbose); +} + +int nft_rule_replace(struct nft_handle *h, const char *chain, + const char *table, struct iptables_command_state *cs, + int rulenum, bool verbose) +{ + int ret; + + nft_fn = nft_rule_replace; + + ret = __nft_rule_check(h, chain, table, NULL, true, rulenum, verbose); + if (ret < 0) + return ret; + + /* XXX needs to be inserted in position, this is appending */ + return nft_rule_add(h, chain, table, cs, true, verbose); +} + +/* + * iptables print output emulation + */ + +#define FMT_NUMERIC 0x0001 +#define FMT_NOCOUNTS 0x0002 +#define FMT_KILOMEGAGIGA 0x0004 +#define FMT_OPTIONS 0x0008 +#define FMT_NOTABLE 0x0010 +#define FMT_NOTARGET 0x0020 +#define FMT_VIA 0x0040 +#define FMT_NONEWLINE 0x0080 +#define FMT_LINENUMBERS 0x0100 + +#define FMT_PRINT_RULE (FMT_NOCOUNTS | FMT_OPTIONS | FMT_VIA \ + | FMT_NUMERIC | FMT_NOTABLE) +#define FMT(tab,notab) ((format) & FMT_NOTABLE ? (notab) : (tab)) + +static void +print_num(uint64_t number, unsigned int format) +{ + if (format & FMT_KILOMEGAGIGA) { + if (number > 99999) { + number = (number + 500) / 1000; + if (number > 9999) { + number = (number + 500) / 1000; + if (number > 9999) { + number = (number + 500) / 1000; + if (number > 9999) { + number = (number + 500) / 1000; + printf(FMT("%4lluT ","%lluT "), (unsigned long long)number); + } + else printf(FMT("%4lluG ","%lluG "), (unsigned long long)number); + } + else printf(FMT("%4lluM ","%lluM "), (unsigned long long)number); + } else + printf(FMT("%4lluK ","%lluK "), (unsigned long long)number); + } else + printf(FMT("%5llu ","%llu "), (unsigned long long)number); + } else + printf(FMT("%8llu ","%llu "), (unsigned long long)number); +} + +static void +print_header(unsigned int format, const char *chain, const char *pol, + const struct xt_counters *counters, bool basechain, uint32_t refs) +{ + printf("Chain %s", chain); + if (basechain) { + printf(" (policy %s", pol); + if (!(format & FMT_NOCOUNTS)) { + fputc(' ', stdout); + print_num(counters->pcnt, (format|FMT_NOTABLE)); + fputs("packets, ", stdout); + print_num(counters->bcnt, (format|FMT_NOTABLE)); + fputs("bytes", stdout); + } + printf(")\n"); + } else { + printf(" (%u references)\n", refs); + } + + if (format & FMT_LINENUMBERS) + printf(FMT("%-4s ", "%s "), "num"); + if (!(format & FMT_NOCOUNTS)) { + if (format & FMT_KILOMEGAGIGA) { + printf(FMT("%5s ","%s "), "pkts"); + printf(FMT("%5s ","%s "), "bytes"); + } else { + printf(FMT("%8s ","%s "), "pkts"); + printf(FMT("%10s ","%s "), "bytes"); + } + } + if (!(format & FMT_NOTARGET)) + printf(FMT("%-9s ","%s "), "target"); + fputs(" prot ", stdout); + if (format & FMT_OPTIONS) + fputs("opt", stdout); + if (format & FMT_VIA) { + printf(FMT(" %-6s ","%s "), "in"); + printf(FMT("%-6s ","%s "), "out"); + } + printf(FMT(" %-19s ","%s "), "source"); + printf(FMT(" %-19s "," %s "), "destination"); + printf("\n"); +} + +static void +print_match(struct nft_rule_expr *expr, int numeric) +{ + size_t len; + const char *match_name = nft_rule_expr_get_str(expr, NFT_EXPR_MT_NAME); + const void *match_info = nft_rule_expr_get(expr, NFT_EXPR_MT_INFO, &len); + const struct xtables_match *match = + xtables_find_match(match_name, XTF_TRY_LOAD, NULL); + struct xt_entry_match *m = + calloc(1, sizeof(struct xt_entry_match) + len); + + /* emulate struct xt_entry_match since ->print needs it */ + memcpy((void *)&m->data, match_info, len); + + if (match) { + if (match->print) + /* FIXME missing first parameter */ + match->print(NULL, m, numeric); + else + printf("%s ", match_name); + } else { + if (match_name[0]) + printf("UNKNOWN match `%s' ", match_name); + } + + free(m); +} + +static void +print_firewall(const struct iptables_command_state *cs, struct nft_rule *r, + unsigned int num, unsigned int format) +{ + const struct xtables_target *target = NULL; + const char *targname = NULL; + const void *targinfo = NULL; + uint8_t flags; + char buf[BUFSIZ]; + struct nft_rule_expr_iter *iter; + struct nft_rule_expr *expr; + struct xt_entry_target *t; + size_t target_len = 0; + + iter = nft_rule_expr_iter_create(r); + if (iter == NULL) { + DEBUGP("cannot allocate rule list iterator\n"); + return; + } + + expr = nft_rule_expr_iter_next(iter); + while (expr != NULL) { + const char *name = + nft_rule_expr_get_str(expr, NFT_RULE_EXPR_ATTR_NAME); + + if (strcmp(name, "target") == 0) { + targname = nft_rule_expr_get_str(expr, NFT_EXPR_TG_NAME); + targinfo = nft_rule_expr_get(expr, NFT_EXPR_TG_INFO, &target_len); + break; + } else if (strcmp(name, "immediate") == 0) { + uint32_t verdict = + nft_rule_expr_get_u32(expr, NFT_EXPR_IMM_VERDICT); + + switch(verdict) { + case NF_ACCEPT: + targname = "ACCEPT"; + break; + case NF_DROP: + targname = "DROP"; + break; + case NFT_RETURN: + targname = "RETURN"; + break; + case NFT_GOTO: + targname = nft_rule_expr_get_str(expr, NFT_EXPR_IMM_CHAIN); + break; + case NFT_JUMP: + targname = nft_rule_expr_get_str(expr, NFT_EXPR_IMM_CHAIN); + break; + } + } + expr = nft_rule_expr_iter_next(iter); + } + nft_rule_expr_iter_destroy(iter); + + flags = cs->fw.ip.flags; + + if (format & FMT_LINENUMBERS) + printf(FMT("%-4u ", "%u "), num); + + if (!(format & FMT_NOCOUNTS)) { + print_num(cs->counters.pcnt, format); + print_num(cs->counters.bcnt, format); + } + + if (!(format & FMT_NOTARGET)) + printf(FMT("%-9s ", "%s "), targname ? targname : ""); + + fputc(cs->fw.ip.invflags & XT_INV_PROTO ? '!' : ' ', stdout); + { + const char *pname = + proto_to_name(cs->fw.ip.proto, format&FMT_NUMERIC); + if (pname) + printf(FMT("%-5s", "%s "), pname); + else + printf(FMT("%-5hu", "%hu "), cs->fw.ip.proto); + } + + if (format & FMT_OPTIONS) { + if (format & FMT_NOTABLE) + fputs("opt ", stdout); + fputc(cs->fw.ip.invflags & IPT_INV_FRAG ? '!' : '-', stdout); + fputc(flags & IPT_F_FRAG ? 'f' : '-', stdout); + fputc(' ', stdout); + } + + if (format & FMT_VIA) { + char iface[IFNAMSIZ+2]; + if (cs->fw.ip.invflags & IPT_INV_VIA_IN) { + iface[0] = '!'; + iface[1] = '\0'; + } + else iface[0] = '\0'; + + if (cs->fw.ip.iniface[0] != '\0') { + strcat(iface, cs->fw.ip.iniface); + } + else if (format & FMT_NUMERIC) strcat(iface, "*"); + else strcat(iface, "any"); + printf(FMT(" %-6s ","in %s "), iface); + + if (cs->fw.ip.invflags & IPT_INV_VIA_OUT) { + iface[0] = '!'; + iface[1] = '\0'; + } + else iface[0] = '\0'; + + if (cs->fw.ip.outiface[0] != '\0') { + strcat(iface, cs->fw.ip.outiface); + } + else if (format & FMT_NUMERIC) strcat(iface, "*"); + else strcat(iface, "any"); + printf(FMT("%-6s ","out %s "), iface); + } + + fputc(cs->fw.ip.invflags & IPT_INV_SRCIP ? '!' : ' ', stdout); + if (cs->fw.ip.smsk.s_addr == 0L && !(format & FMT_NUMERIC)) + printf(FMT("%-19s ","%s "), "anywhere"); + else { + if (format & FMT_NUMERIC) + strcpy(buf, xtables_ipaddr_to_numeric(&cs->fw.ip.src)); + else + strcpy(buf, xtables_ipaddr_to_anyname(&cs->fw.ip.src)); + strcat(buf, xtables_ipmask_to_numeric(&cs->fw.ip.smsk)); + printf(FMT("%-19s ","%s "), buf); + } + + fputc(cs->fw.ip.invflags & IPT_INV_DSTIP ? '!' : ' ', stdout); + if (cs->fw.ip.dmsk.s_addr == 0L && !(format & FMT_NUMERIC)) + printf(FMT("%-19s ","-> %s"), "anywhere"); + else { + if (format & FMT_NUMERIC) + strcpy(buf, xtables_ipaddr_to_numeric(&cs->fw.ip.dst)); + else + strcpy(buf, xtables_ipaddr_to_anyname(&cs->fw.ip.dst)); + strcat(buf, xtables_ipmask_to_numeric(&cs->fw.ip.dmsk)); + printf(FMT("%-19s ","-> %s"), buf); + } + + if (format & FMT_NOTABLE) + fputs(" ", stdout); + +#ifdef IPT_F_GOTO + if(cs->fw.ip.flags & IPT_F_GOTO) + printf("[goto] "); +#endif + + iter = nft_rule_expr_iter_create(r); + if (iter == NULL) { + DEBUGP("cannot allocate rule list iterator\n"); + return; + } + + expr = nft_rule_expr_iter_next(iter); + while (expr != NULL) { + const char *name = + nft_rule_expr_get_str(expr, NFT_RULE_EXPR_ATTR_NAME); + + if (strcmp(name, "match") == 0) + print_match(expr, format & FMT_NUMERIC); + + expr = nft_rule_expr_iter_next(iter); + } + nft_rule_expr_iter_destroy(iter); + + t = calloc(1, sizeof(struct xt_entry_target) + target_len); + if (t == NULL) + return; + + /* emulate struct xt_entry_match since ->print needs it */ + memcpy((void *)&t->data, targinfo, target_len); + + if (targname) { + target = xtables_find_target(targname, XTF_TRY_LOAD); + if (target) { + if (target->print) + /* FIXME missing first parameter */ + target->print(NULL, t, format & FMT_NUMERIC); + } else + printf("[%ld bytes of unknown target data] ", + target_len); + } + free(t); + + if (!(format & FMT_NONEWLINE)) + fputc('\n', stdout); +} + +static int +__nft_rule_list(struct nft_handle *h, struct nft_chain *c, const char *table, + int rulenum, unsigned int format, + void (*cb)(const struct iptables_command_state *cs, + struct nft_rule *r, unsigned int num, + unsigned int format)) +{ + struct nft_rule_list *list; + struct nft_rule_list_iter *iter; + struct nft_rule *r; + int rule_ctr = 0, ret = 0; + const char *chain = nft_chain_attr_get_str(c, NFT_CHAIN_ATTR_NAME); + + list = nft_rule_list_get(h); + if (list == NULL) { + DEBUGP("cannot retrieve rule list from kernel\n"); + return 0; + } + + iter = nft_rule_list_iter_create(list); + if (iter == NULL) { + DEBUGP("cannot allocate rule list iterator\n"); + return 0; + } + + r = nft_rule_list_iter_next(iter); + while (r != NULL) { + const char *rule_table = + nft_rule_attr_get_str(r, NFT_RULE_ATTR_TABLE); + const char *rule_chain = + nft_rule_attr_get_str(r, NFT_RULE_ATTR_CHAIN); + + rule_ctr++; + + if (strcmp(table, rule_table) != 0 || + strcmp(chain, rule_chain) != 0) + goto next; + + if (rulenum > 0) { + /* List by rule number case */ + if (rule_ctr != rulenum) { + rule_ctr++; + goto next; + } + } else { + struct iptables_command_state cs = {}; + /* Show all rules case */ + nft_rule_to_iptables_command_state(r, &cs); + + cb(&cs, r, rule_ctr, format); + } +next: + r = nft_rule_list_iter_next(iter); + } + + nft_rule_list_iter_destroy(iter); + nft_rule_list_free(list); + + if (ret == 0) + errno = ENOENT; + + return ret; +} + +int nft_rule_list(struct nft_handle *h, const char *chain, const char *table, + int rulenum, unsigned int format) +{ + struct nft_chain_list *list; + struct nft_chain_list_iter *iter; + struct nft_chain *c; + + list = nft_chain_dump(h); + + iter = nft_chain_list_iter_create(list); + if (iter == NULL) { + DEBUGP("cannot allocate rule list iterator\n"); + return 0; + } + + c = nft_chain_list_iter_next(iter); + while (c != NULL) { + const char *chain_table = + nft_chain_attr_get_str(c, NFT_CHAIN_ATTR_TABLE); + const char *chain_name = + nft_chain_attr_get_str(c, NFT_CHAIN_ATTR_NAME); + uint32_t policy = + nft_chain_attr_get_u32(c, NFT_CHAIN_ATTR_POLICY); + uint32_t refs = + nft_chain_attr_get_u32(c, NFT_CHAIN_ATTR_USE); + struct xt_counters ctrs = { + .pcnt = nft_chain_attr_get_u64(c, NFT_CHAIN_ATTR_PACKETS), + .bcnt = nft_chain_attr_get_u64(c, NFT_CHAIN_ATTR_BYTES), + }; + bool basechain = false; + + if (nft_chain_attr_get(c, NFT_CHAIN_ATTR_HOOKNUM)) + basechain = true; + + if (strcmp(table, chain_table) != 0) + goto next; + if (chain && strcmp(chain, chain_name) != 0) + goto next; + + print_header(format, chain_name, policy_name[policy], &ctrs, + basechain, refs); + + /* this is a base chain */ + if (nft_chain_attr_get(c, NFT_CHAIN_ATTR_HOOKNUM)) { + __nft_rule_list(h, c, table, rulenum, format, + print_firewall); + } +next: + c = nft_chain_list_iter_next(iter); + } + + nft_chain_list_free(list); + + return 1; +} + +static void +list_save(const struct iptables_command_state *cs, struct nft_rule *r, + unsigned int num, unsigned int format) +{ + nft_rule_print_save(r, !(format & FMT_NOCOUNTS)); +} + +static int +nft_rule_list_chain_save(struct nft_handle *h, const char *table, + struct nft_chain_list *list, int counters) +{ + struct nft_chain_list_iter *iter; + struct nft_chain *c; + + iter = nft_chain_list_iter_create(list); + if (iter == NULL) { + DEBUGP("cannot allocate rule list iterator\n"); + return 0; + } + + c = nft_chain_list_iter_next(iter); + while (c != NULL) { + const char *chain_table = + nft_chain_attr_get_str(c, NFT_CHAIN_ATTR_TABLE); + const char *chain_name = + nft_chain_attr_get_str(c, NFT_CHAIN_ATTR_NAME); + uint32_t policy = + nft_chain_attr_get_u32(c, NFT_CHAIN_ATTR_POLICY); + + if (strcmp(table, chain_table) != 0) + goto next; + + /* this is a base chain */ + if (nft_chain_attr_get(c, NFT_CHAIN_ATTR_HOOKNUM)) { + printf("-P %s %s", chain_name, policy_name[policy]); + + if (counters) { + printf(" -c %lu %lu\n", + nft_chain_attr_get_u64(c, NFT_CHAIN_ATTR_PACKETS), + nft_chain_attr_get_u64(c, NFT_CHAIN_ATTR_BYTES)); + } else + printf("\n"); + } else { + printf("-N %s\n", chain_name); + } +next: + c = nft_chain_list_iter_next(iter); + } + + return 1; +} + +int nft_rule_list_save(struct nft_handle *h, const char *chain, + const char *table, int rulenum, int counters) +{ + struct nft_chain_list *list; + struct nft_chain_list_iter *iter; + struct nft_chain *c; + + list = nft_chain_dump(h); + + /* Dump policies and custom chains first */ + nft_rule_list_chain_save(h, table, list, counters); + + /* Now dump out rules in this table */ + iter = nft_chain_list_iter_create(list); + if (iter == NULL) { + DEBUGP("cannot allocate rule list iterator\n"); + return 0; + } + + c = nft_chain_list_iter_next(iter); + while (c != NULL) { + const char *chain_table = + nft_chain_attr_get_str(c, NFT_CHAIN_ATTR_TABLE); + const char *chain_name = + nft_chain_attr_get_str(c, NFT_CHAIN_ATTR_NAME); + + if (strcmp(table, chain_table) != 0) + goto next; + if (chain && strcmp(chain, chain_name) != 0) + goto next; + + __nft_rule_list(h, c, table, rulenum, + counters ? 0 : FMT_NOCOUNTS, list_save); +next: + c = nft_chain_list_iter_next(iter); + } + + nft_chain_list_free(list); + + return 1; +} + +int nft_compatible_revision(const char *name, uint8_t rev, int opt) +{ + struct mnl_socket *nl; + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + uint32_t portid, seq, type; + int ret = 0; + + if (opt == IPT_SO_GET_REVISION_MATCH) + type = 0; + else + type = 1; + + nlh = mnl_nlmsg_put_header(buf); + nlh->nlmsg_type = (NFNL_SUBSYS_NFT_COMPAT << 8) | NFNL_MSG_COMPAT_GET; + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + nlh->nlmsg_seq = seq = time(NULL); + + struct nfgenmsg *nfg = mnl_nlmsg_put_extra_header(nlh, sizeof(*nfg)); + nfg->nfgen_family = AF_INET; + nfg->version = NFNETLINK_V0; + nfg->res_id = 0; + + mnl_attr_put_strz(nlh, NFTA_COMPAT_NAME, name); + mnl_attr_put_u32(nlh, NFTA_COMPAT_REV, htonl(rev)); + mnl_attr_put_u32(nlh, NFTA_COMPAT_TYPE, htonl(type)); + + DEBUGP("requesting `%s' rev=%d type=%d via nft_compat\n", + name, rev, type); + + nl = mnl_socket_open(NETLINK_NETFILTER); + if (nl == NULL) { + perror("mnl_socket_open"); + return 0; + } + + if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) { + perror("mnl_socket_bind"); + goto err; + } + portid = mnl_socket_get_portid(nl); + + if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { + perror("mnl_socket_send"); + goto err; + } + + ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); + if (ret == -1) { + perror("mnl_socket_recvfrom"); + goto err; + } + + ret = mnl_cb_run(buf, ret, seq, portid, NULL, NULL); + if (ret == -1) { + perror("mnl_cb_run"); + goto err; + } + +err: + mnl_socket_close(nl); + + return ret < 0 ? 0 : 1; +} + +/* Translates errno numbers into more human-readable form than strerror. */ +const char *nft_strerror(int err) +{ + unsigned int i; + static struct table_struct { + void *fn; + int err; + const char *message; + } table[] = + { + { nft_chain_user_del, ENOTEMPTY, "Chain is not empty" }, + { nft_chain_user_del, EINVAL, "Can't delete built-in chain" }, + { nft_chain_user_del, EMLINK, + "Can't delete chain with references left" }, + { nft_chain_user_add, EEXIST, "Chain already exists" }, + { nft_rule_add, E2BIG, "Index of insertion too big" }, + { nft_rule_replace, E2BIG, "Index of replacement too big" }, + { nft_rule_delete_num, E2BIG, "Index of deletion too big" }, +/* { TC_READ_COUNTER, E2BIG, "Index of counter too big" }, + { TC_ZERO_COUNTER, E2BIG, "Index of counter too big" }, */ + { nft_rule_add, ELOOP, "Loop found in table" }, + { nft_rule_add, EINVAL, "Target problem" }, + /* ENOENT for DELETE probably means no matching rule */ + { nft_rule_delete, ENOENT, + "Bad rule (does a matching rule exist in that chain?)" }, + { nft_chain_set, ENOENT, "Bad built-in chain name" }, + { nft_chain_set, EINVAL, "Bad policy name" }, + { NULL, EPERM, "Permission denied (you must be root)" }, + { NULL, 0, "Incompatible with this kernel" }, + { NULL, ENOPROTOOPT, "iptables who? (do you need to insmod?)" }, + { NULL, ENOSYS, "Will be implemented real soon. I promise ;)" }, + { NULL, ENOMEM, "Memory allocation problem" }, + { NULL, ENOENT, "No chain/target/match by that name" }, + }; + + for (i = 0; i < sizeof(table)/sizeof(struct table_struct); i++) { + if ((!table[i].fn || table[i].fn == nft_fn) + && table[i].err == err) + return table[i].message; + } + + return strerror(err); +} diff --git a/iptables/nft.h b/iptables/nft.h new file mode 100644 index 00000000..f5a9efb8 --- /dev/null +++ b/iptables/nft.h @@ -0,0 +1,62 @@ +#ifndef _NFT_H_ +#define _NFT_H_ + +#include "xshared.h" + +struct nft_handle { + struct mnl_socket *nl; + uint32_t portid; + uint32_t seq; +}; + +int nft_init(struct nft_handle *h); +void nft_fini(struct nft_handle *h); + +/* + * Operations with tables. + */ +struct nft_table; + +int nft_table_add(struct nft_handle *h, const struct nft_table *t); +int nft_for_each_table(struct nft_handle *h, int (*func)(struct nft_handle *h, const char *tablename, bool counters), bool counters); +bool nft_table_find(struct nft_handle *h, const char *tablename); + +/* + * Operations with chains. + */ +struct nft_chain; + +int nft_chain_add(struct nft_handle *h, const struct nft_chain *c); +int nft_chain_set(struct nft_handle *h, const char *table, const char *chain, const char *policy, const struct xt_counters *counters); +struct nft_chain_list *nft_chain_dump(struct nft_handle *h); +int nft_chain_save(struct nft_handle *h, struct nft_chain_list *list, const char *table); +int nft_chain_user_add(struct nft_handle *h, const char *chain, const char *table); +int nft_chain_user_del(struct nft_handle *h, const char *chain, const char *table); +int nft_chain_user_rename(struct nft_handle *h, const char *chain, const char *table, const char *newname); + +/* + * Operations with rule-set. + */ +struct nft_rule; + +int nft_rule_add(struct nft_handle *h, const char *chain, const char *table, struct iptables_command_state *cmd, bool append, bool verbose); +int nft_rule_check(struct nft_handle *h, const char *chain, const char *table, struct iptables_command_state *cmd, bool verbose); +int nft_rule_delete(struct nft_handle *h, const char *chain, const char *table, struct iptables_command_state *cmd, bool verbose); +int nft_rule_delete_num(struct nft_handle *h, const char *chain, const char *table, int rulenum, bool verbose); +int nft_rule_replace(struct nft_handle *h, const char *chain, const char *table, struct iptables_command_state *cmd, int rulenum, bool verbose); +int nft_rule_list(struct nft_handle *h, const char *chain, const char *table, int rulenum, unsigned int format); +int nft_rule_list_save(struct nft_handle *h, const char *chain, const char *table, int rulenum, int counters); +int nft_rule_save(struct nft_handle *h, const char *table, bool counters); +int nft_rule_flush(struct nft_handle *h, const char *chain, const char *table); + +/* + * revision compatibility. + */ +int nft_compatible_revision(const char *name, uint8_t rev, int opt); + +/* + * Error reporting. + */ +const char *nft_strerror(int err); + +#endif diff --git a/iptables/xshared.h b/iptables/xshared.h index 1e2b9b8e..27c5b786 100644 --- a/iptables/xshared.h +++ b/iptables/xshared.h @@ -58,6 +58,7 @@ struct iptables_command_state { unsigned int options; struct xtables_rule_match *matches; struct xtables_target *target; + struct xt_counters counters; char *protocol; int proto_used; const char *jumpto; diff --git a/iptables/xtables-config-parser.y b/iptables/xtables-config-parser.y new file mode 100644 index 00000000..fe5bcbf1 --- /dev/null +++ b/iptables/xtables-config-parser.y @@ -0,0 +1,213 @@ +%{ +/* + * (C) 2012 by Pablo Neira Ayuso + * + * 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 software has been sponsored by Sophos Astaro + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +extern char *yytext; +extern int yylineno; + +static LIST_HEAD(xtables_stack); + +struct stack_elem { + struct list_head head; + int token; + size_t size; + char data[]; +}; + +static void *stack_push(int token, size_t size) +{ + struct stack_elem *e; + + e = calloc(1, sizeof(struct stack_elem) + size); + + e->token = token; + e->size = size; + + list_add(&e->head, &xtables_stack); + + return e->data; +} + +static struct stack_elem *stack_pop(void) +{ + struct stack_elem *e; + + e = list_entry(xtables_stack.next, struct stack_elem, head); + + if (&e->head == &xtables_stack) + return NULL; + + list_del(&e->head); + return e; +} + +static inline void stack_put_i32(void *data, int value) +{ + memcpy(data, &value, sizeof(int)); +} + +static inline void stack_put_str(void *data, const char *str) +{ + memcpy(data, str, strlen(str)+1); +} + +static void stack_free(struct stack_elem *e) +{ + free(e); +} + +%} + +%union { + int val; + char *string; +} + +%token T_TABLE +%token T_CHAIN +%token T_HOOK +%token T_PRIO + +%token T_STRING +%token T_INTEGER + +%% + +configfile : + | lines + ; + +lines : line + | lines line + ; + +line : table + ; + +table : T_TABLE T_STRING T_PRIO T_INTEGER '{' chains '}' + { + /* added in reverse order to pop it in order */ + void *data = stack_push(T_PRIO, sizeof(int32_t)); + stack_put_i32(data, $4); + data = stack_push(T_TABLE, strlen($2)); + stack_put_str(data, $2); + } + ; + +chains : chain + | chains chain + ; + +chain : T_CHAIN T_STRING T_HOOK T_STRING + { + /* added in reverse order to pop it in order */ + void *data = stack_push(T_HOOK, strlen($4)); + stack_put_str(data, $4); + data = stack_push(T_CHAIN, strlen($2)); + stack_put_str(data, $2); + } + ; + +%% + +int __attribute__((noreturn)) +yyerror(char *msg) +{ + fprintf(stderr, "parsing config file in line (%d), symbol '%s': %s\n", + yylineno, yytext, msg); + exit(EXIT_FAILURE); +} + +static int hooknametonum(const char *hookname) +{ + if (strcmp(hookname, "NF_INET_LOCAL_IN") == 0) + return NF_INET_LOCAL_IN; + else if (strcmp(hookname, "NF_INET_FORWARD") == 0) + return NF_INET_FORWARD; + else if (strcmp(hookname, "NF_INET_LOCAL_OUT") == 0) + return NF_INET_LOCAL_OUT; + else if (strcmp(hookname, "NF_INET_PRE_ROUTING") == 0) + return NF_INET_PRE_ROUTING; + else if (strcmp(hookname, "NF_INET_POST_ROUTING") == 0) + return NF_INET_POST_ROUTING; + + return -1; +} + +int xtables_config_parse(char *filename, struct nft_table_list *table_list, + struct nft_chain_list *chain_list) +{ + FILE *fp; + struct stack_elem *e; + struct nft_table *table = NULL; + struct nft_chain *chain = NULL; + int prio = 0; + + fp = fopen(filename, "r"); + if (!fp) + return -1; + + yyrestart(fp); + yyparse(); + fclose(fp); + + for (e = stack_pop(); e != NULL; e = stack_pop()) { + switch(e->token) { + case T_TABLE: + table = nft_table_alloc(); + if (table == NULL) { + perror("nft_table_alloc"); + return -1; + } + nft_table_attr_set(table, NFT_TABLE_ATTR_NAME, e->data); + nft_table_list_add(table, table_list); + break; + case T_PRIO: + prio = *((int32_t *)e->data); + break; + case T_CHAIN: + chain = nft_chain_alloc(); + if (chain == NULL) { + perror("nft_chain_alloc"); + return -1; + } + nft_chain_attr_set(chain, NFT_CHAIN_ATTR_TABLE, + (char *)nft_table_attr_get(table, NFT_TABLE_ATTR_NAME)); + nft_chain_attr_set(chain, NFT_CHAIN_ATTR_NAME, e->data); + nft_chain_list_add(chain, chain_list); + break; + case T_HOOK: + nft_chain_attr_set_u32(chain, NFT_CHAIN_ATTR_HOOKNUM, + hooknametonum(e->data)); + nft_chain_attr_set_s32(chain, NFT_CHAIN_ATTR_PRIO, prio); + break; + default: + printf("unknown token type %d\n", e->token); + break; + } + stack_free(e); + } + + return 0; +} diff --git a/iptables/xtables-config-syntax.l b/iptables/xtables-config-syntax.l new file mode 100644 index 00000000..7a66ef39 --- /dev/null +++ b/iptables/xtables-config-syntax.l @@ -0,0 +1,53 @@ +%{ +/* + * (C) 2012 by Pablo Neira Ayuso + * + * 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 software has been sponsored by Sophos Astaro + */ + +#include +#include "xtables-config-parser.h" +%} + +%option yylineno +%option noinput +%option nounput + +ws [ \t]+ +comment #.*$ +nl [\n\r] + +is_on [o|O][n|N] +is_off [o|O][f|F][f|F] +integer [\-\+]?[0-9]+ +string [a-zA-Z][a-zA-Z0-9\.\-\_]* + +%% +"table" { return T_TABLE; } +"chain" { return T_CHAIN; } +"hook" { return T_HOOK; } +"prio" { return T_PRIO; } + +{integer} { yylval.val = atoi(yytext); return T_INTEGER; } +{string} { yylval.string = strdup(yytext); return T_STRING; } + +{comment} ; +{ws} ; +{nl} ; + +<> { yyterminate(); } + +. { return yytext[0]; } + +%% + +int +yywrap() +{ + return 1; +} diff --git a/iptables/xtables-config.c b/iptables/xtables-config.c new file mode 100644 index 00000000..16918bf6 --- /dev/null +++ b/iptables/xtables-config.c @@ -0,0 +1,107 @@ +/* + * (C) 2012 by Pablo Neira Ayuso + * + * 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 code has been sponsored by Sophos Astaro + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "xtables-multi.h" +#include "xtables-config-parser.h" + +#include "nft.h" + +extern int xtables_config_parse(const char *filename, + struct nft_table_list *table_list, + struct nft_chain_list *chain_list); + +#define XTABLES_CONFIG_DEFAULT "/etc/xtables.conf" + +int xtables_config_main(int argc, char *argv[]) +{ + struct nft_table_list *table_list = nft_table_list_alloc(); + struct nft_chain_list *chain_list = nft_chain_list_alloc(); + struct nft_table_list_iter *titer; + struct nft_chain_list_iter *citer; + struct nft_table *table; + struct nft_chain *chain; + const char *filename = NULL; + struct nft_handle h; + + if (argc > 2) { + fprintf(stderr, "Usage: %s []\n", argv[0]); + return EXIT_SUCCESS; + } + if (argc == 1) + filename = XTABLES_CONFIG_DEFAULT; + else + filename = argv[1]; + + if (xtables_config_parse(filename, table_list, chain_list) < 0) { + if (errno == ENOENT) { + fprintf(stderr, "configuration file `%s' does not " + "exists\n", filename); + } else { + fprintf(stderr, "Fatal error: %s\n", strerror(errno)); + } + return EXIT_FAILURE; + } + + nft_init(&h); + + /* Stage 1) create tables */ + titer = nft_table_list_iter_create(table_list); + while ((table = nft_table_list_iter_next(titer)) != NULL) { + if (nft_table_add(&h, table) < 0) { + if (errno == EEXIST) { + printf("table `%s' already exists, skipping\n", + (char *)nft_table_attr_get(table, NFT_TABLE_ATTR_NAME)); + } else { + printf("table `%s' cannot be create, reason `%s'. Exitting\n", + (char *)nft_table_attr_get(table, NFT_TABLE_ATTR_NAME), + strerror(errno)); + return EXIT_FAILURE; + } + continue; + } + printf("table `%s' has been created\n", + (char *)nft_table_attr_get(table, NFT_TABLE_ATTR_NAME)); + } + + /* Stage 2) create chains */ + citer = nft_chain_list_iter_create(chain_list); + while ((chain = nft_chain_list_iter_next(citer)) != NULL) { + if (nft_chain_add(&h, chain) < 0) { + if (errno == EEXIST) { + printf("chain `%s' already exists in table `%s', skipping\n", + (char *)nft_chain_attr_get(chain, NFT_CHAIN_ATTR_NAME), + (char *)nft_chain_attr_get(chain, NFT_CHAIN_ATTR_TABLE)); + } else { + printf("chain `%s' cannot be create, reason `%s'. Exitting\n", + (char *)nft_chain_attr_get(chain, NFT_CHAIN_ATTR_NAME), + strerror(errno)); + return EXIT_FAILURE; + } + continue; + } + + printf("chain `%s' in table `%s' has been created\n", + (char *)nft_chain_attr_get(chain, NFT_CHAIN_ATTR_NAME), + (char *)nft_chain_attr_get(chain, NFT_CHAIN_ATTR_TABLE)); + } + + return EXIT_SUCCESS; +} diff --git a/iptables/xtables-multi.c b/iptables/xtables-multi.c index 8014d5fb..c1746434 100644 --- a/iptables/xtables-multi.c +++ b/iptables/xtables-multi.c @@ -13,6 +13,10 @@ #include "ip6tables-multi.h" #endif +#ifdef ENABLE_NFTABLES +#include "xtables-multi.h" +#endif + static const struct subcommand multi_subcommands[] = { #ifdef ENABLE_IPV4 {"iptables", iptables_main}, @@ -31,6 +35,12 @@ static const struct subcommand multi_subcommands[] = { {"save6", ip6tables_save_main}, {"ip6tables-restore", ip6tables_restore_main}, {"restore6", ip6tables_restore_main}, +#endif +#ifdef ENABLE_NFTABLES + {"xtables", xtables_main}, + {"xtables-save", xtables_save_main}, + {"xtables-restore", xtables_restore_main}, + {"xtables-config", xtables_config_main}, #endif {NULL}, }; diff --git a/iptables/xtables-multi.h b/iptables/xtables-multi.h index 615724b1..be2b3ad8 100644 --- a/iptables/xtables-multi.h +++ b/iptables/xtables-multi.h @@ -2,5 +2,9 @@ #define _XTABLES_MULTI_H 1 extern int iptables_xml_main(int, char **); +extern int xtables_main(int, char **); +extern int xtables_save_main(int, char **); +extern int xtables_restore_main(int, char **); +extern int xtables_config_main(int, char **); #endif /* _XTABLES_MULTI_H */ diff --git a/iptables/xtables-restore.c b/iptables/xtables-restore.c new file mode 100644 index 00000000..09922a0c --- /dev/null +++ b/iptables/xtables-restore.c @@ -0,0 +1,417 @@ +/* Code to restore the iptables state, from file by iptables-save. + * (C) 2000-2002 by Harald Welte + * based on previous code from Rusty Russell + * + * This code is distributed under the terms of GNU GPL v2 + */ + +#include +#include +#include +#include +#include +#include +#include "iptables.h" +#include "xtables.h" +#include "libiptc/libiptc.h" +#include "xtables-multi.h" +#include "nft.h" + +#ifdef DEBUG +#define DEBUGP(x, args...) fprintf(stderr, x, ## args) +#else +#define DEBUGP(x, args...) +#endif + +static int binary = 0, counters = 0, verbose = 0, noflush = 0; + +/* Keeping track of external matches and targets. */ +static const struct option options[] = { + {.name = "binary", .has_arg = false, .val = 'b'}, + {.name = "counters", .has_arg = false, .val = 'c'}, + {.name = "verbose", .has_arg = false, .val = 'v'}, + {.name = "test", .has_arg = false, .val = 't'}, + {.name = "help", .has_arg = false, .val = 'h'}, + {.name = "noflush", .has_arg = false, .val = 'n'}, + {.name = "modprobe", .has_arg = true, .val = 'M'}, + {.name = "table", .has_arg = true, .val = 'T'}, + {NULL}, +}; + +static void print_usage(const char *name, const char *version) __attribute__((noreturn)); + +#define prog_name xtables_globals.program_name + +static void print_usage(const char *name, const char *version) +{ + fprintf(stderr, "Usage: %s [-b] [-c] [-v] [-t] [-h]\n" + " [ --binary ]\n" + " [ --counters ]\n" + " [ --verbose ]\n" + " [ --test ]\n" + " [ --help ]\n" + " [ --noflush ]\n" + " [ --table= ]\n" + " [ --modprobe=]\n", name); + + exit(1); +} + +static int parse_counters(char *string, struct xt_counters *ctr) +{ + unsigned long long pcnt, bcnt; + int ret; + + ret = sscanf(string, "[%llu:%llu]", &pcnt, &bcnt); + ctr->pcnt = pcnt; + ctr->bcnt = bcnt; + return ret == 2; +} + +/* global new argv and argc */ +static char *newargv[255]; +static int newargc; + +/* function adding one argument to newargv, updating newargc + * returns true if argument added, false otherwise */ +static int add_argv(char *what) { + DEBUGP("add_argv: %s\n", what); + if (what && newargc + 1 < ARRAY_SIZE(newargv)) { + newargv[newargc] = strdup(what); + newargv[++newargc] = NULL; + return 1; + } else { + xtables_error(PARAMETER_PROBLEM, + "Parser cannot handle more arguments\n"); + return 0; + } +} + +static void free_argv(void) { + int i; + + for (i = 0; i < newargc; i++) + free(newargv[i]); +} + +static void add_param_to_argv(char *parsestart) +{ + int quote_open = 0, escaped = 0, param_len = 0; + char param_buffer[1024], *curchar; + + /* After fighting with strtok enough, here's now + * a 'real' parser. According to Rusty I'm now no + * longer a real hacker, but I can live with that */ + + for (curchar = parsestart; *curchar; curchar++) { + if (quote_open) { + if (escaped) { + param_buffer[param_len++] = *curchar; + escaped = 0; + continue; + } else if (*curchar == '\\') { + escaped = 1; + continue; + } else if (*curchar == '"') { + quote_open = 0; + *curchar = ' '; + } else { + param_buffer[param_len++] = *curchar; + continue; + } + } else { + if (*curchar == '"') { + quote_open = 1; + continue; + } + } + + if (*curchar == ' ' + || *curchar == '\t' + || * curchar == '\n') { + if (!param_len) { + /* two spaces? */ + continue; + } + + param_buffer[param_len] = '\0'; + + /* check if table name specified */ + if (!strncmp(param_buffer, "-t", 2) + || !strncmp(param_buffer, "--table", 8)) { + xtables_error(PARAMETER_PROBLEM, + "The -t option (seen in line %u) cannot be " + "used in xtables-restore.\n", line); + exit(1); + } + + add_argv(param_buffer); + param_len = 0; + } else { + /* regular character, copy to buffer */ + param_buffer[param_len++] = *curchar; + + if (param_len >= sizeof(param_buffer)) + xtables_error(PARAMETER_PROBLEM, + "Parameter too long!"); + } + } +} + +int +xtables_restore_main(int argc, char *argv[]) +{ + struct nft_handle h; + char buffer[10240]; + int c; + char curtable[XT_TABLE_MAXNAMELEN + 1]; + FILE *in; + int in_table = 0, testing = 0; + const char *tablename = NULL; + const struct xtc_ops *ops = &iptc_ops; + + line = 0; + + xtables_globals.program_name = "xtables-restore"; + c = xtables_init_all(&xtables_globals, NFPROTO_IPV4); + if (c < 0) { + fprintf(stderr, "%s/%s Failed to initialize xtables\n", + xtables_globals.program_name, + xtables_globals.program_version); + exit(1); + } +#if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS) + init_extensions(); + init_extensions4(); +#endif + + nft_init(&h); + + while ((c = getopt_long(argc, argv, "bcvthnM:T:", options, NULL)) != -1) { + switch (c) { + case 'b': + binary = 1; + break; + case 'c': + counters = 1; + break; + case 'v': + verbose = 1; + break; + case 't': + testing = 1; + break; + case 'h': + print_usage("xtables-restore", + IPTABLES_VERSION); + break; + case 'n': + noflush = 1; + break; + case 'M': + xtables_modprobe_program = optarg; + break; + case 'T': + tablename = optarg; + break; + } + } + + if (optind == argc - 1) { + in = fopen(argv[optind], "re"); + if (!in) { + fprintf(stderr, "Can't open %s: %s\n", argv[optind], + strerror(errno)); + exit(1); + } + } + else if (optind < argc) { + fprintf(stderr, "Unknown arguments found on commandline\n"); + exit(1); + } + else in = stdin; + + /* Grab standard input. */ + while (fgets(buffer, sizeof(buffer), in)) { + int ret = 0; + + line++; + if (buffer[0] == '\n') + continue; + else if (buffer[0] == '#') { + if (verbose) + fputs(buffer, stdout); + continue; + } else if ((strcmp(buffer, "COMMIT\n") == 0) && (in_table)) { + /* FIXME commit/testing operation not supported */ + if (!testing) { + DEBUGP("Calling commit\n"); + ret = 1; + } else { + DEBUGP("Not calling commit, testing\n"); + ret = 1; + } + in_table = 0; + } else if ((buffer[0] == '*') && (!in_table)) { + /* New table */ + char *table; + + table = strtok(buffer+1, " \t\n"); + DEBUGP("line %u, table '%s'\n", line, table); + if (!table) { + xtables_error(PARAMETER_PROBLEM, + "%s: line %u table name invalid\n", + xt_params->program_name, line); + exit(1); + } + strncpy(curtable, table, XT_TABLE_MAXNAMELEN); + curtable[XT_TABLE_MAXNAMELEN] = '\0'; + + if (tablename && (strcmp(tablename, table) != 0)) + continue; + + if (noflush == 0) { + DEBUGP("Cleaning all chains of table '%s'\n", + table); + nft_rule_flush(&h, NULL, table); + + DEBUGP("Deleting all user-defined chains " + "of table '%s'\n", table); + nft_chain_user_del(&h, NULL, table); + } + + ret = 1; + in_table = 1; + + } else if ((buffer[0] == ':') && (in_table)) { + /* New chain. */ + char *policy, *chain = NULL; + struct xt_counters count = {}; + + chain = strtok(buffer+1, " \t\n"); + DEBUGP("line %u, chain '%s'\n", line, chain); + if (!chain) { + xtables_error(PARAMETER_PROBLEM, + "%s: line %u chain name invalid\n", + xt_params->program_name, line); + exit(1); + } + + if (strlen(chain) >= XT_EXTENSION_MAXNAMELEN) + xtables_error(PARAMETER_PROBLEM, + "Invalid chain name `%s' " + "(%u chars max)", + chain, XT_EXTENSION_MAXNAMELEN - 1); + + policy = strtok(NULL, " \t\n"); + DEBUGP("line %u, policy '%s'\n", line, policy); + if (!policy) { + xtables_error(PARAMETER_PROBLEM, + "%s: line %u policy invalid\n", + xt_params->program_name, line); + exit(1); + } + + if (strcmp(policy, "-") != 0) { + if (counters) { + char *ctrs; + ctrs = strtok(NULL, " \t\n"); + + if (!ctrs || !parse_counters(ctrs, &count)) + xtables_error(PARAMETER_PROBLEM, + "invalid policy counters " + "for chain '%s'\n", chain); + + } + + DEBUGP("Setting policy of chain %s to %s\n", + chain, policy); + } + + if (nft_chain_set(&h, curtable, chain, policy, &count) < 0) { + xtables_error(OTHER_PROBLEM, + "Can't set policy `%s'" + " on `%s' line %u: %s\n", + policy, chain, line, + ops->strerror(errno)); + } + + ret = 1; + + } else if (in_table) { + int a; + char *ptr = buffer; + char *pcnt = NULL; + char *bcnt = NULL; + char *parsestart; + + /* reset the newargv */ + newargc = 0; + + if (buffer[0] == '[') { + /* we have counters in our input */ + ptr = strchr(buffer, ']'); + if (!ptr) + xtables_error(PARAMETER_PROBLEM, + "Bad line %u: need ]\n", + line); + + pcnt = strtok(buffer+1, ":"); + if (!pcnt) + xtables_error(PARAMETER_PROBLEM, + "Bad line %u: need :\n", + line); + + bcnt = strtok(NULL, "]"); + if (!bcnt) + xtables_error(PARAMETER_PROBLEM, + "Bad line %u: need ]\n", + line); + + /* start command parsing after counter */ + parsestart = ptr + 1; + } else { + /* start command parsing at start of line */ + parsestart = buffer; + } + + add_argv(argv[0]); + add_argv("-t"); + add_argv(curtable); + + if (counters && pcnt && bcnt) { + add_argv("--set-counters"); + add_argv((char *) pcnt); + add_argv((char *) bcnt); + } + + add_param_to_argv(parsestart); + + DEBUGP("calling do_command4(%u, argv, &%s, handle):\n", + newargc, curtable); + + for (a = 0; a < newargc; a++) + DEBUGP("argv[%u]: %s\n", a, newargv[a]); + + ret = do_commandx(&h, newargc, newargv, &newargv[2]); + + free_argv(); + fflush(stdout); + } + if (tablename && (strcmp(tablename, curtable) != 0)) + continue; + if (!ret) { + fprintf(stderr, "%s: line %u failed\n", + xt_params->program_name, line); + exit(1); + } + } + if (in_table) { + fprintf(stderr, "%s: COMMIT expected at line %u\n", + xt_params->program_name, line + 1); + exit(1); + } + + fclose(in); + return 0; +} diff --git a/iptables/xtables-save.c b/iptables/xtables-save.c new file mode 100644 index 00000000..046c948d --- /dev/null +++ b/iptables/xtables-save.c @@ -0,0 +1,122 @@ +/* Code to save the xtables state, in human readable-form. */ +/* (C) 1999 by Paul 'Rusty' Russell and + * (C) 2000-2002 by Harald Welte + * (C) 2012 by Pablo Neira Ayuso + * + * This code is distributed under the terms of GNU GPL v2 + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include "libiptc/libiptc.h" +#include "iptables.h" +#include "xtables-multi.h" +#include "nft.h" + +#include + +#ifndef NO_SHARED_LIBS +#include +#endif + +static bool show_counters = false; + +static const struct option options[] = { + {.name = "counters", .has_arg = false, .val = 'c'}, + {.name = "dump", .has_arg = false, .val = 'd'}, + {.name = "table", .has_arg = true, .val = 't'}, + {.name = "modprobe", .has_arg = true, .val = 'M'}, + {NULL}, +}; + +static int +do_output(struct nft_handle *h, const char *tablename, bool counters) +{ + struct nft_chain_list *chain_list; + + if (!tablename) + return nft_for_each_table(h, do_output, counters); + + if (!nft_table_find(h, tablename)) { + printf("Table `%s' does not exist\n", tablename); + return 0; + } + + chain_list = nft_chain_dump(h); + + time_t now = time(NULL); + + printf("# Generated by xtables-save v%s on %s", + IPTABLES_VERSION, ctime(&now)); + printf("*%s\n", tablename); + + /* Dump out chain names first, + * thereby preventing dependency conflicts */ + nft_chain_save(h, chain_list, tablename); + nft_rule_save(h, tablename, counters); + + now = time(NULL); + printf("COMMIT\n"); + printf("# Completed on %s", ctime(&now)); + + return 1; +} + +/* Format: + * :Chain name POLICY packets bytes + * rule + */ +int +xtables_save_main(int argc, char *argv[]) +{ + const char *tablename = NULL; + struct nft_handle h; + int c; + + xtables_globals.program_name = "xtables-save"; + /* XXX xtables_init_all does several things we don't want */ + c = xtables_init_all(&xtables_globals, NFPROTO_IPV4); + if (c < 0) { + fprintf(stderr, "%s/%s Failed to initialize xtables\n", + xtables_globals.program_name, + xtables_globals.program_version); + exit(1); + } +#if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS) + init_extensions(); + init_extensions4(); +#endif + nft_init(&h); + + while ((c = getopt_long(argc, argv, "bcdt:", options, NULL)) != -1) { + switch (c) { + case 'c': + show_counters = true; + break; + + case 't': + /* Select specific table. */ + tablename = optarg; + break; + case 'M': + xtables_modprobe_program = optarg; + break; + case 'd': + do_output(&h, tablename, show_counters); + exit(0); + } + } + + if (optind < argc) { + fprintf(stderr, "Unknown arguments found on commandline\n"); + exit(1); + } + + return !do_output(&h, tablename, show_counters); +} diff --git a/iptables/xtables-standalone.c b/iptables/xtables-standalone.c new file mode 100644 index 00000000..f746c902 --- /dev/null +++ b/iptables/xtables-standalone.c @@ -0,0 +1,80 @@ +/* + * Author: Paul.Russell@rustcorp.com.au and mneuling@radlogic.com.au + * + * Based on the ipchains code by Paul Russell and Michael Neuling + * + * (C) 2000-2002 by the netfilter coreteam : + * Paul 'Rusty' Russell + * Marc Boucher + * James Morris + * Harald Welte + * Jozsef Kadlecsik + * + * iptables -- IP firewall administration for kernels with + * firewall table (aimed for the 2.3 kernels) + * + * See the accompanying manual page iptables(8) for information + * about proper usage of this program. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include "xtables-multi.h" +#include "nft.h" + +int +xtables_main(int argc, char *argv[]) +{ + int ret; + char *table = "filter"; + struct nft_handle h; + + iptables_globals.program_name = "xtables"; + ret = xtables_init_all(&xtables_globals, NFPROTO_IPV4); + if (ret < 0) { + fprintf(stderr, "%s/%s Failed to initialize xtables\n", + iptables_globals.program_name, + iptables_globals.program_version); + exit(1); + } +#if defined(ALL_INCLUSIVE) || defined(NO_SHARED_LIBS) + init_extensions(); + init_extensions4(); +#endif + + nft_init(&h); + + ret = do_commandx(&h, argc, argv, &table); + if (!ret) { + if (errno == EINVAL) { + fprintf(stderr, "iptables: %s. " + "Run `dmesg' for more information.\n", + nft_strerror(errno)); + } else { + fprintf(stderr, "iptables: %s.\n", + nft_strerror(errno)); + } + if (errno == EAGAIN) { + exit(RESOURCE_PROBLEM); + } + } + + exit(!ret); +} diff --git a/iptables/xtables.c b/iptables/xtables.c new file mode 100644 index 00000000..a6875757 --- /dev/null +++ b/iptables/xtables.c @@ -0,0 +1,1251 @@ +/* Code to take an iptables-style command line and do it. */ + +/* + * Author: Paul.Russell@rustcorp.com.au and mneuling@radlogic.com.au + * + * (C) 2000-2002 by the netfilter coreteam : + * Paul 'Rusty' Russell + * Marc Boucher + * James Morris + * Harald Welte + * Jozsef Kadlecsik + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "xshared.h" +#include "nft.h" + +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif + +#define FMT_NUMERIC 0x0001 +#define FMT_NOCOUNTS 0x0002 +#define FMT_KILOMEGAGIGA 0x0004 +#define FMT_OPTIONS 0x0008 +#define FMT_NOTABLE 0x0010 +#define FMT_NOTARGET 0x0020 +#define FMT_VIA 0x0040 +#define FMT_NONEWLINE 0x0080 +#define FMT_LINENUMBERS 0x0100 + +#define FMT_PRINT_RULE (FMT_NOCOUNTS | FMT_OPTIONS | FMT_VIA \ + | FMT_NUMERIC | FMT_NOTABLE) +#define FMT(tab,notab) ((format) & FMT_NOTABLE ? (notab) : (tab)) + + +#define CMD_NONE 0x0000U +#define CMD_INSERT 0x0001U +#define CMD_DELETE 0x0002U +#define CMD_DELETE_NUM 0x0004U +#define CMD_REPLACE 0x0008U +#define CMD_APPEND 0x0010U +#define CMD_LIST 0x0020U +#define CMD_FLUSH 0x0040U +#define CMD_ZERO 0x0080U +#define CMD_NEW_CHAIN 0x0100U +#define CMD_DELETE_CHAIN 0x0200U +#define CMD_SET_POLICY 0x0400U +#define CMD_RENAME_CHAIN 0x0800U +#define CMD_LIST_RULES 0x1000U +#define CMD_ZERO_NUM 0x2000U +#define CMD_CHECK 0x4000U +#define NUMBER_OF_CMD 16 +static const char cmdflags[] = { 'I', 'D', 'D', 'R', 'A', 'L', 'F', 'Z', + 'N', 'X', 'P', 'E', 'S', 'Z', 'C' }; + +#define OPT_FRAGMENT 0x00800U +#define NUMBER_OF_OPT ARRAY_SIZE(optflags) +static const char optflags[] += { 'n', 's', 'd', 'p', 'j', 'v', 'x', 'i', 'o', '0', 'c', 'f'}; + +static struct option original_opts[] = { + {.name = "append", .has_arg = 1, .val = 'A'}, + {.name = "delete", .has_arg = 1, .val = 'D'}, + {.name = "check", .has_arg = 1, .val = 'C'}, + {.name = "insert", .has_arg = 1, .val = 'I'}, + {.name = "replace", .has_arg = 1, .val = 'R'}, + {.name = "list", .has_arg = 2, .val = 'L'}, + {.name = "list-rules", .has_arg = 2, .val = 'S'}, + {.name = "flush", .has_arg = 2, .val = 'F'}, + {.name = "zero", .has_arg = 2, .val = 'Z'}, + {.name = "new-chain", .has_arg = 1, .val = 'N'}, + {.name = "delete-chain", .has_arg = 2, .val = 'X'}, + {.name = "rename-chain", .has_arg = 1, .val = 'E'}, + {.name = "policy", .has_arg = 1, .val = 'P'}, + {.name = "source", .has_arg = 1, .val = 's'}, + {.name = "destination", .has_arg = 1, .val = 'd'}, + {.name = "src", .has_arg = 1, .val = 's'}, /* synonym */ + {.name = "dst", .has_arg = 1, .val = 'd'}, /* synonym */ + {.name = "protocol", .has_arg = 1, .val = 'p'}, + {.name = "in-interface", .has_arg = 1, .val = 'i'}, + {.name = "jump", .has_arg = 1, .val = 'j'}, + {.name = "table", .has_arg = 1, .val = 't'}, + {.name = "match", .has_arg = 1, .val = 'm'}, + {.name = "numeric", .has_arg = 0, .val = 'n'}, + {.name = "out-interface", .has_arg = 1, .val = 'o'}, + {.name = "verbose", .has_arg = 0, .val = 'v'}, + {.name = "exact", .has_arg = 0, .val = 'x'}, + {.name = "fragments", .has_arg = 0, .val = 'f'}, + {.name = "version", .has_arg = 0, .val = 'V'}, + {.name = "help", .has_arg = 2, .val = 'h'}, + {.name = "line-numbers", .has_arg = 0, .val = '0'}, + {.name = "modprobe", .has_arg = 1, .val = 'M'}, + {.name = "set-counters", .has_arg = 1, .val = 'c'}, + {.name = "goto", .has_arg = 1, .val = 'g'}, + {.name = "ipv4", .has_arg = 0, .val = '4'}, + {.name = "ipv6", .has_arg = 0, .val = '6'}, + {NULL}, +}; + +void iptables_exit_error(enum xtables_exittype status, const char *msg, ...) __attribute__((noreturn, format(printf,2,3))); + +struct xtables_globals xtables_globals = { + .option_offset = 0, + .program_version = IPTABLES_VERSION, + .orig_opts = original_opts, + .exit_err = iptables_exit_error, + .compat_rev = nft_compatible_revision, +}; + +/* Table of legal combinations of commands and options. If any of the + * given commands make an option legal, that option is legal (applies to + * CMD_LIST and CMD_ZERO only). + * Key: + * + compulsory + * x illegal + * optional + */ + +static const char commands_v_options[NUMBER_OF_CMD][NUMBER_OF_OPT] = +/* Well, it's better than "Re: Linux vs FreeBSD" */ +{ + /* -n -s -d -p -j -v -x -i -o --line -c -f */ +/*INSERT*/ {'x',' ',' ',' ',' ',' ','x',' ',' ','x',' ',' '}, +/*DELETE*/ {'x',' ',' ',' ',' ',' ','x',' ',' ','x','x',' '}, +/*DELETE_NUM*/{'x','x','x','x','x',' ','x','x','x','x','x','x'}, +/*REPLACE*/ {'x',' ',' ',' ',' ',' ','x',' ',' ','x',' ',' '}, +/*APPEND*/ {'x',' ',' ',' ',' ',' ','x',' ',' ','x',' ',' '}, +/*LIST*/ {' ','x','x','x','x',' ',' ','x','x',' ','x','x'}, +/*FLUSH*/ {'x','x','x','x','x',' ','x','x','x','x','x','x'}, +/*ZERO*/ {'x','x','x','x','x',' ','x','x','x','x','x','x'}, +/*ZERO_NUM*/ {'x','x','x','x','x',' ','x','x','x','x','x','x'}, +/*NEW_CHAIN*/ {'x','x','x','x','x',' ','x','x','x','x','x','x'}, +/*DEL_CHAIN*/ {'x','x','x','x','x',' ','x','x','x','x','x','x'}, +/*SET_POLICY*/{'x','x','x','x','x',' ','x','x','x','x',' ','x'}, +/*RENAME*/ {'x','x','x','x','x',' ','x','x','x','x','x','x'}, +/*LIST_RULES*/{'x','x','x','x','x',' ','x','x','x','x','x','x'}, +/*CHECK*/ {'x',' ',' ',' ',' ',' ','x',' ',' ','x','x',' '}, +}; + +static const int inverse_for_options[NUMBER_OF_OPT] = +{ +/* -n */ 0, +/* -s */ IPT_INV_SRCIP, +/* -d */ IPT_INV_DSTIP, +/* -p */ XT_INV_PROTO, +/* -j */ 0, +/* -v */ 0, +/* -x */ 0, +/* -i */ IPT_INV_VIA_IN, +/* -o */ IPT_INV_VIA_OUT, +/*--line*/ 0, +/* -c */ 0, +/* -f */ IPT_INV_FRAG, +}; + +#define opts xtables_globals.opts +#define prog_name xtables_globals.program_name +#define prog_vers xtables_globals.program_version + +/* Primitive headers... */ +/* defined in netinet/in.h */ +#if 0 +#ifndef IPPROTO_ESP +#define IPPROTO_ESP 50 +#endif +#ifndef IPPROTO_AH +#define IPPROTO_AH 51 +#endif +#endif + +enum { + IPT_DOTTED_ADDR = 0, + IPT_DOTTED_MASK +}; + +static void __attribute__((noreturn)) +exit_tryhelp(int status) +{ + if (line != -1) + fprintf(stderr, "Error occurred at line: %d\n", line); + fprintf(stderr, "Try `%s -h' or '%s --help' for more information.\n", + prog_name, prog_name); + xtables_free_opts(1); + exit(status); +} + +static void +exit_printhelp(const struct xtables_rule_match *matches) +{ + printf("%s v%s\n\n" +"Usage: %s -[ACD] chain rule-specification [options]\n" +" %s -I chain [rulenum] rule-specification [options]\n" +" %s -R chain rulenum rule-specification [options]\n" +" %s -D chain rulenum [options]\n" +" %s -[LS] [chain [rulenum]] [options]\n" +" %s -[FZ] [chain] [options]\n" +" %s -[NX] chain\n" +" %s -E old-chain-name new-chain-name\n" +" %s -P chain target [options]\n" +" %s -h (print this help information)\n\n", + prog_name, prog_vers, prog_name, prog_name, + prog_name, prog_name, prog_name, prog_name, + prog_name, prog_name, prog_name, prog_name); + + printf( +"Commands:\n" +"Either long or short options are allowed.\n" +" --append -A chain Append to chain\n" +" --check -C chain Check for the existence of a rule\n" +" --delete -D chain Delete matching rule from chain\n" +" --delete -D chain rulenum\n" +" Delete rule rulenum (1 = first) from chain\n" +" --insert -I chain [rulenum]\n" +" Insert in chain as rulenum (default 1=first)\n" +" --replace -R chain rulenum\n" +" Replace rule rulenum (1 = first) in chain\n" +" --list -L [chain [rulenum]]\n" +" List the rules in a chain or all chains\n" +" --list-rules -S [chain [rulenum]]\n" +" Print the rules in a chain or all chains\n" +" --flush -F [chain] Delete all rules in chain or all chains\n" +" --zero -Z [chain [rulenum]]\n" +" Zero counters in chain or all chains\n" +" --new -N chain Create a new user-defined chain\n" +" --delete-chain\n" +" -X [chain] Delete a user-defined chain\n" +" --policy -P chain target\n" +" Change policy on chain to target\n" +" --rename-chain\n" +" -E old-chain new-chain\n" +" Change chain name, (moving any references)\n" + +"Options:\n" +" --ipv4 -4 Nothing (line is ignored by ip6tables-restore)\n" +" --ipv6 -6 Error (line is ignored by iptables-restore)\n" +"[!] --proto -p proto protocol: by number or name, eg. `tcp'\n" +"[!] --source -s address[/mask][...]\n" +" source specification\n" +"[!] --destination -d address[/mask][...]\n" +" destination specification\n" +"[!] --in-interface -i input name[+]\n" +" network interface name ([+] for wildcard)\n" +" --jump -j target\n" +" target for rule (may load target extension)\n" +#ifdef IPT_F_GOTO +" --goto -g chain\n" +" jump to chain with no return\n" +#endif +" --match -m match\n" +" extended match (may load extension)\n" +" --numeric -n numeric output of addresses and ports\n" +"[!] --out-interface -o output name[+]\n" +" network interface name ([+] for wildcard)\n" +" --table -t table table to manipulate (default: `filter')\n" +" --verbose -v verbose mode\n" +" --line-numbers print line numbers when listing\n" +" --exact -x expand numbers (display exact values)\n" +"[!] --fragment -f match second or further fragments only\n" +" --modprobe= try to insert modules using this command\n" +" --set-counters PKTS BYTES set the counter during insert/append\n" +"[!] --version -V print package version.\n"); + + print_extension_helps(xtables_targets, matches); + exit(0); +} + +static void +generic_opt_check(int command, int options) +{ + int i, j, legal = 0; + + /* Check that commands are valid with options. Complicated by the + * fact that if an option is legal with *any* command given, it is + * legal overall (ie. -z and -l). + */ + for (i = 0; i < NUMBER_OF_OPT; i++) { + legal = 0; /* -1 => illegal, 1 => legal, 0 => undecided. */ + + for (j = 0; j < NUMBER_OF_CMD; j++) { + if (!(command & (1< 1; option >>= 1, ptr++); + + return *ptr; +} + +static char +cmd2char(int option) +{ + const char *ptr; + for (ptr = cmdflags; option > 1; option >>= 1, ptr++); + + return *ptr; +} + +static void +add_command(unsigned int *cmd, const int newcmd, const int othercmds, + int invert) +{ + if (invert) + xtables_error(PARAMETER_PROBLEM, "unexpected ! flag"); + if (*cmd & (~othercmds)) + xtables_error(PARAMETER_PROBLEM, "Cannot use -%c with -%c\n", + cmd2char(newcmd), cmd2char(*cmd & (~othercmds))); + *cmd |= newcmd; +} + +/* + * All functions starting with "parse" should succeed, otherwise + * the program fails. + * Most routines return pointers to static data that may change + * between calls to the same or other routines with a few exceptions: + * "host_to_addr", "parse_hostnetwork", and "parse_hostnetworkmask" + * return global static data. +*/ + +/* Christophe Burki wants `-p 6' to imply `-m tcp'. */ +/* Can't be zero. */ +static int +parse_rulenumber(const char *rule) +{ + unsigned int rulenum; + + if (!xtables_strtoui(rule, NULL, &rulenum, 1, INT_MAX)) + xtables_error(PARAMETER_PROBLEM, + "Invalid rule number `%s'", rule); + + return rulenum; +} + +static const char * +parse_target(const char *targetname) +{ + const char *ptr; + + if (strlen(targetname) < 1) + xtables_error(PARAMETER_PROBLEM, + "Invalid target name (too short)"); + + if (strlen(targetname) >= XT_EXTENSION_MAXNAMELEN) + xtables_error(PARAMETER_PROBLEM, + "Invalid target name `%s' (%u chars max)", + targetname, XT_EXTENSION_MAXNAMELEN - 1); + + for (ptr = targetname; *ptr; ptr++) + if (isspace(*ptr)) + xtables_error(PARAMETER_PROBLEM, + "Invalid target name `%s'", targetname); + return targetname; +} + +static void +set_option(unsigned int *options, unsigned int option, uint8_t *invflg, + int invert) +{ + if (*options & option) + xtables_error(PARAMETER_PROBLEM, "multiple -%c flags not allowed", + opt2char(option)); + *options |= option; + + if (invert) { + unsigned int i; + for (i = 0; 1 << i != option; i++); + + if (!inverse_for_options[i]) + xtables_error(PARAMETER_PROBLEM, + "cannot have ! before -%c", + opt2char(option)); + *invflg |= inverse_for_options[i]; + } +} + +static int +add_entry(const char *chain, + const char *table, + struct iptables_command_state *cs, + unsigned int nsaddrs, + const struct in_addr saddrs[], + const struct in_addr smasks[], + unsigned int ndaddrs, + const struct in_addr daddrs[], + const struct in_addr dmasks[], + bool verbose, struct nft_handle *h, bool append) +{ + unsigned int i, j; + int ret = 1; + + for (i = 0; i < nsaddrs; i++) { + cs->fw.ip.src.s_addr = saddrs[i].s_addr; + cs->fw.ip.smsk.s_addr = smasks[i].s_addr; + for (j = 0; j < ndaddrs; j++) { + cs->fw.ip.dst.s_addr = daddrs[j].s_addr; + cs->fw.ip.dmsk.s_addr = dmasks[j].s_addr; + + ret = nft_rule_add(h, chain, table, cs, append, verbose); + } + } + + return ret; +} + +static int +replace_entry(const char *chain, const char *table, + struct iptables_command_state *cs, + unsigned int rulenum, + const struct in_addr *saddr, const struct in_addr *smask, + const struct in_addr *daddr, const struct in_addr *dmask, + bool verbose, struct nft_handle *h) +{ + cs->fw.ip.src.s_addr = saddr->s_addr; + cs->fw.ip.dst.s_addr = daddr->s_addr; + cs->fw.ip.smsk.s_addr = smask->s_addr; + cs->fw.ip.dmsk.s_addr = dmask->s_addr; + + return nft_rule_replace(h, chain, table, cs, rulenum, verbose); +} + +static int +delete_entry(const char *chain, const char *table, + struct iptables_command_state *cs, + unsigned int nsaddrs, + const struct in_addr saddrs[], + const struct in_addr smasks[], + unsigned int ndaddrs, + const struct in_addr daddrs[], + const struct in_addr dmasks[], + bool verbose, + struct nft_handle *h) +{ + unsigned int i, j; + int ret = 1; + + for (i = 0; i < nsaddrs; i++) { + cs->fw.ip.src.s_addr = saddrs[i].s_addr; + cs->fw.ip.smsk.s_addr = smasks[i].s_addr; + for (j = 0; j < ndaddrs; j++) { + cs->fw.ip.dst.s_addr = daddrs[j].s_addr; + cs->fw.ip.dmsk.s_addr = dmasks[j].s_addr; + ret = nft_rule_delete(h, chain, table, cs, verbose); + } + } + + return ret; +} + +static int +check_entry(const char *chain, const char *table, + struct iptables_command_state *cs, + unsigned int nsaddrs, const struct in_addr *saddrs, + const struct in_addr *smasks, unsigned int ndaddrs, + const struct in_addr *daddrs, const struct in_addr *dmasks, + bool verbose, struct nft_handle *h) +{ + unsigned int i, j; + int ret = 1; + + for (i = 0; i < nsaddrs; i++) { + cs->fw.ip.src.s_addr = saddrs[i].s_addr; + cs->fw.ip.smsk.s_addr = smasks[i].s_addr; + for (j = 0; j < ndaddrs; j++) { + cs->fw.ip.dst.s_addr = daddrs[j].s_addr; + cs->fw.ip.dmsk.s_addr = dmasks[j].s_addr; + ret = nft_rule_check(h, chain, table, cs, verbose); + } + } + + return ret; +} + +static int +zero_entries(const xt_chainlabel chain, int verbose, + struct xtc_handle *handle) +{ + /* XXX iterate over chains and reset counters */ + return 1; +} + +static int +list_entries(struct nft_handle *h, const char *chain, const char *table, + int rulenum, int verbose, int numeric, int expanded, + int linenumbers) +{ + unsigned int format; + + format = FMT_OPTIONS; + if (!verbose) + format |= FMT_NOCOUNTS; + else + format |= FMT_VIA; + + if (numeric) + format |= FMT_NUMERIC; + + if (!expanded) + format |= FMT_KILOMEGAGIGA; + + if (linenumbers) + format |= FMT_LINENUMBERS; + + /* FIXME should return found or not, and errno = ENOENT in such case */ + return nft_rule_list(h, chain, table, rulenum, format); +} + +static int +list_rules(struct nft_handle *h, const char *chain, const char *table, + int rulenum, int counters) +{ + if (counters) + counters = -1; /* iptables -c format */ + + nft_rule_list_save(h, chain, table, rulenum, counters); + + /* FIXME found */ + return 1; +} + +static void clear_rule_matches(struct xtables_rule_match **matches) +{ + struct xtables_rule_match *matchp, *tmp; + + for (matchp = *matches; matchp;) { + tmp = matchp->next; + if (matchp->match->m) { + free(matchp->match->m); + matchp->match->m = NULL; + } + if (matchp->match == matchp->match->next) { + free(matchp->match); + matchp->match = NULL; + } + free(matchp); + matchp = tmp; + } + + *matches = NULL; +} + +static void command_jump(struct iptables_command_state *cs) +{ + size_t size; + + set_option(&cs->options, OPT_JUMP, &cs->fw.ip.invflags, cs->invert); + cs->jumpto = parse_target(optarg); + /* TRY_LOAD (may be chain name) */ + cs->target = xtables_find_target(cs->jumpto, XTF_TRY_LOAD); + + if (cs->target == NULL) + return; + + size = XT_ALIGN(sizeof(struct xt_entry_target)) + + cs->target->size; + + cs->target->t = xtables_calloc(1, size); + cs->target->t->u.target_size = size; + if (cs->target->real_name == NULL) { + strcpy(cs->target->t->u.user.name, cs->jumpto); + } else { + /* Alias support for userspace side */ + strcpy(cs->target->t->u.user.name, cs->target->real_name); + if (!(cs->target->ext_flags & XTABLES_EXT_ALIAS)) + fprintf(stderr, "Notice: The %s target is converted into %s target " + "in rule listing and saving.\n", + cs->jumpto, cs->target->real_name); + } + cs->target->t->u.user.revision = cs->target->revision; + xs_init_target(cs->target); + + if (cs->target->x6_options != NULL) + opts = xtables_options_xfrm(iptables_globals.orig_opts, opts, + cs->target->x6_options, + &cs->target->option_offset); + else + opts = xtables_merge_options(iptables_globals.orig_opts, opts, + cs->target->extra_opts, + &cs->target->option_offset); + if (opts == NULL) + xtables_error(OTHER_PROBLEM, "can't alloc memory!"); +} + +static void command_match(struct iptables_command_state *cs) +{ + struct xtables_match *m; + size_t size; + + if (cs->invert) + xtables_error(PARAMETER_PROBLEM, + "unexpected ! flag before --match"); + + m = xtables_find_match(optarg, XTF_LOAD_MUST_SUCCEED, &cs->matches); + size = XT_ALIGN(sizeof(struct xt_entry_match)) + m->size; + m->m = xtables_calloc(1, size); + m->m->u.match_size = size; + if (m->real_name == NULL) { + strcpy(m->m->u.user.name, m->name); + } else { + strcpy(m->m->u.user.name, m->real_name); + if (!(m->ext_flags & XTABLES_EXT_ALIAS)) + fprintf(stderr, "Notice: the %s match is converted into %s match " + "in rule listing and saving.\n", m->name, m->real_name); + } + m->m->u.user.revision = m->revision; + xs_init_match(m); + if (m == m->next) + return; + /* Merge options for non-cloned matches */ + if (m->x6_options != NULL) + opts = xtables_options_xfrm(iptables_globals.orig_opts, opts, + m->x6_options, &m->option_offset); + else if (m->extra_opts != NULL) + opts = xtables_merge_options(iptables_globals.orig_opts, opts, + m->extra_opts, &m->option_offset); + if (opts == NULL) + xtables_error(OTHER_PROBLEM, "can't alloc memory!"); +} + +int do_commandx(struct nft_handle *h, int argc, char *argv[], char **table) +{ + struct iptables_command_state cs; + unsigned int nsaddrs = 0, ndaddrs = 0; + struct in_addr *saddrs = NULL, *smasks = NULL; + struct in_addr *daddrs = NULL, *dmasks = NULL; + + int verbose = 0; + const char *chain = NULL; + const char *shostnetworkmask = NULL, *dhostnetworkmask = NULL; + const char *policy = NULL, *newname = NULL; + unsigned int rulenum = 0, command = 0; + const char *pcnt = NULL, *bcnt = NULL; + int ret = 1; + struct xtables_match *m; + struct xtables_rule_match *matchp; + struct xtables_target *t; + unsigned long long cnt; + + memset(&cs, 0, sizeof(cs)); + cs.jumpto = ""; + cs.argv = argv; + + /* re-set optind to 0 in case do_command4 gets called + * a second time */ + optind = 0; + + /* clear mflags in case do_command4 gets called a second time + * (we clear the global list of all matches for security)*/ + for (m = xtables_matches; m; m = m->next) + m->mflags = 0; + + for (t = xtables_targets; t; t = t->next) { + t->tflags = 0; + t->used = 0; + } + + /* Suppress error messages: we may add new options if we + demand-load a protocol. */ + opterr = 0; + + opts = xt_params->orig_opts; + while ((cs.c = getopt_long(argc, argv, + "-:A:C:D:R:I:L::S::M:F::Z::N:X::E:P:Vh::o:p:s:d:j:i:fbvnt:m:xc:g:46", + opts, NULL)) != -1) { + switch (cs.c) { + /* + * Command selection + */ + case 'A': + add_command(&command, CMD_APPEND, CMD_NONE, + cs.invert); + chain = optarg; + break; + + case 'C': + add_command(&command, CMD_CHECK, CMD_NONE, + cs.invert); + chain = optarg; + break; + + case 'D': + add_command(&command, CMD_DELETE, CMD_NONE, + cs.invert); + chain = optarg; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') { + rulenum = parse_rulenumber(argv[optind++]); + command = CMD_DELETE_NUM; + } + break; + + case 'R': + add_command(&command, CMD_REPLACE, CMD_NONE, + cs.invert); + chain = optarg; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + rulenum = parse_rulenumber(argv[optind++]); + else + xtables_error(PARAMETER_PROBLEM, + "-%c requires a rule number", + cmd2char(CMD_REPLACE)); + break; + + case 'I': + add_command(&command, CMD_INSERT, CMD_NONE, + cs.invert); + chain = optarg; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + rulenum = parse_rulenumber(argv[optind++]); + else rulenum = 1; + break; + + case 'L': + add_command(&command, CMD_LIST, + CMD_ZERO | CMD_ZERO_NUM, cs.invert); + if (optarg) chain = optarg; + else if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + chain = argv[optind++]; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + rulenum = parse_rulenumber(argv[optind++]); + break; + + case 'S': + add_command(&command, CMD_LIST_RULES, + CMD_ZERO|CMD_ZERO_NUM, cs.invert); + if (optarg) chain = optarg; + else if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + chain = argv[optind++]; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + rulenum = parse_rulenumber(argv[optind++]); + break; + + case 'F': + add_command(&command, CMD_FLUSH, CMD_NONE, + cs.invert); + if (optarg) chain = optarg; + else if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + chain = argv[optind++]; + break; + + case 'Z': + add_command(&command, CMD_ZERO, CMD_LIST|CMD_LIST_RULES, + cs.invert); + if (optarg) chain = optarg; + else if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + chain = argv[optind++]; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') { + rulenum = parse_rulenumber(argv[optind++]); + command = CMD_ZERO_NUM; + } + break; + + case 'N': + if (optarg && (*optarg == '-' || *optarg == '!')) + xtables_error(PARAMETER_PROBLEM, + "chain name not allowed to start " + "with `%c'\n", *optarg); + if (xtables_find_target(optarg, XTF_TRY_LOAD)) + xtables_error(PARAMETER_PROBLEM, + "chain name may not clash " + "with target name\n"); + add_command(&command, CMD_NEW_CHAIN, CMD_NONE, + cs.invert); + chain = optarg; + break; + + case 'X': + add_command(&command, CMD_DELETE_CHAIN, CMD_NONE, + cs.invert); + if (optarg) chain = optarg; + else if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + chain = argv[optind++]; + break; + + case 'E': + add_command(&command, CMD_RENAME_CHAIN, CMD_NONE, + cs.invert); + chain = optarg; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + newname = argv[optind++]; + else + xtables_error(PARAMETER_PROBLEM, + "-%c requires old-chain-name and " + "new-chain-name", + cmd2char(CMD_RENAME_CHAIN)); + break; + + case 'P': + add_command(&command, CMD_SET_POLICY, CMD_NONE, + cs.invert); + chain = optarg; + if (optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + policy = argv[optind++]; + else + xtables_error(PARAMETER_PROBLEM, + "-%c requires a chain and a policy", + cmd2char(CMD_SET_POLICY)); + break; + + case 'h': + if (!optarg) + optarg = argv[optind]; + + /* iptables -p icmp -h */ + if (!cs.matches && cs.protocol) + xtables_find_match(cs.protocol, + XTF_TRY_LOAD, &cs.matches); + + exit_printhelp(cs.matches); + + /* + * Option selection + */ + case 'p': + set_option(&cs.options, OPT_PROTOCOL, &cs.fw.ip.invflags, + cs.invert); + + /* Canonicalize into lower case */ + for (cs.protocol = optarg; *cs.protocol; cs.protocol++) + *cs.protocol = tolower(*cs.protocol); + + cs.protocol = optarg; + cs.fw.ip.proto = xtables_parse_protocol(cs.protocol); + + if (cs.fw.ip.proto == 0 + && (cs.fw.ip.invflags & XT_INV_PROTO)) + xtables_error(PARAMETER_PROBLEM, + "rule would never match protocol"); + break; + + case 's': + set_option(&cs.options, OPT_SOURCE, &cs.fw.ip.invflags, + cs.invert); + shostnetworkmask = optarg; + break; + + case 'd': + set_option(&cs.options, OPT_DESTINATION, &cs.fw.ip.invflags, + cs.invert); + dhostnetworkmask = optarg; + break; + +#ifdef IPT_F_GOTO + case 'g': + set_option(&cs.options, OPT_JUMP, &cs.fw.ip.invflags, + cs.invert); + cs.fw.ip.flags |= IPT_F_GOTO; + cs.jumpto = parse_target(optarg); + break; +#endif + + case 'j': + command_jump(&cs); + break; + + + case 'i': + if (*optarg == '\0') + xtables_error(PARAMETER_PROBLEM, + "Empty interface is likely to be " + "undesired"); + set_option(&cs.options, OPT_VIANAMEIN, &cs.fw.ip.invflags, + cs.invert); + xtables_parse_interface(optarg, + cs.fw.ip.iniface, + cs.fw.ip.iniface_mask); + break; + + case 'o': + if (*optarg == '\0') + xtables_error(PARAMETER_PROBLEM, + "Empty interface is likely to be " + "undesired"); + set_option(&cs.options, OPT_VIANAMEOUT, &cs.fw.ip.invflags, + cs.invert); + xtables_parse_interface(optarg, + cs.fw.ip.outiface, + cs.fw.ip.outiface_mask); + break; + + case 'f': + set_option(&cs.options, OPT_FRAGMENT, &cs.fw.ip.invflags, + cs.invert); + cs.fw.ip.flags |= IPT_F_FRAG; + break; + + case 'v': + if (!verbose) + set_option(&cs.options, OPT_VERBOSE, + &cs.fw.ip.invflags, cs.invert); + verbose++; + break; + + case 'm': + command_match(&cs); + break; + + case 'n': + set_option(&cs.options, OPT_NUMERIC, &cs.fw.ip.invflags, + cs.invert); + break; + + case 't': + if (cs.invert) + xtables_error(PARAMETER_PROBLEM, + "unexpected ! flag before --table"); + *table = optarg; + break; + + case 'x': + set_option(&cs.options, OPT_EXPANDED, &cs.fw.ip.invflags, + cs.invert); + break; + + case 'V': + if (cs.invert) + printf("Not %s ;-)\n", prog_vers); + else + printf("%s v%s\n", + prog_name, prog_vers); + exit(0); + + case '0': + set_option(&cs.options, OPT_LINENUMBERS, &cs.fw.ip.invflags, + cs.invert); + break; + + case 'M': + xtables_modprobe_program = optarg; + break; + + case 'c': + + set_option(&cs.options, OPT_COUNTERS, &cs.fw.ip.invflags, + cs.invert); + pcnt = optarg; + bcnt = strchr(pcnt + 1, ','); + if (bcnt) + bcnt++; + if (!bcnt && optind < argc && argv[optind][0] != '-' + && argv[optind][0] != '!') + bcnt = argv[optind++]; + if (!bcnt) + xtables_error(PARAMETER_PROBLEM, + "-%c requires packet and byte counter", + opt2char(OPT_COUNTERS)); + + if (sscanf(pcnt, "%llu", &cnt) != 1) + xtables_error(PARAMETER_PROBLEM, + "-%c packet counter not numeric", + opt2char(OPT_COUNTERS)); + cs.counters.pcnt = cnt; + + if (sscanf(bcnt, "%llu", &cnt) != 1) + xtables_error(PARAMETER_PROBLEM, + "-%c byte counter not numeric", + opt2char(OPT_COUNTERS)); + cs.counters.bcnt = cnt; + break; + + case '4': + /* This is indeed the IPv4 iptables */ + break; + + case '6': + /* This is not the IPv6 ip6tables */ + if (line != -1) + return 1; /* success: line ignored */ + fprintf(stderr, "This is the IPv4 version of iptables.\n"); + exit_tryhelp(2); + + case 1: /* non option */ + if (optarg[0] == '!' && optarg[1] == '\0') { + if (cs.invert) + xtables_error(PARAMETER_PROBLEM, + "multiple consecutive ! not" + " allowed"); + cs.invert = TRUE; + optarg[0] = '\0'; + continue; + } + fprintf(stderr, "Bad argument `%s'\n", optarg); + exit_tryhelp(2); + + default: + if (command_default(&cs, &iptables_globals) == 1) + /* cf. ip6tables.c */ + continue; + break; + } + cs.invert = FALSE; + } + + if (strcmp(*table, "nat") == 0 && + ((policy != NULL && strcmp(policy, "DROP") == 0) || + (cs.jumpto != NULL && strcmp(cs.jumpto, "DROP") == 0))) + xtables_error(PARAMETER_PROBLEM, + "\nThe \"nat\" table is not intended for filtering, " + "the use of DROP is therefore inhibited.\n\n"); + + for (matchp = cs.matches; matchp; matchp = matchp->next) + xtables_option_mfcall(matchp->match); + if (cs.target != NULL) + xtables_option_tfcall(cs.target); + + /* Fix me: must put inverse options checking here --MN */ + + if (optind < argc) + xtables_error(PARAMETER_PROBLEM, + "unknown arguments found on commandline"); + if (!command) + xtables_error(PARAMETER_PROBLEM, "no command specified"); + if (cs.invert) + xtables_error(PARAMETER_PROBLEM, + "nothing appropriate following !"); + + if (command & (CMD_REPLACE | CMD_INSERT | CMD_DELETE | CMD_APPEND | CMD_CHECK)) { + if (!(cs.options & OPT_DESTINATION)) + dhostnetworkmask = "0.0.0.0/0"; + if (!(cs.options & OPT_SOURCE)) + shostnetworkmask = "0.0.0.0/0"; + } + + if (shostnetworkmask) + xtables_ipparse_multiple(shostnetworkmask, &saddrs, + &smasks, &nsaddrs); + + if (dhostnetworkmask) + xtables_ipparse_multiple(dhostnetworkmask, &daddrs, + &dmasks, &ndaddrs); + + if ((nsaddrs > 1 || ndaddrs > 1) && + (cs.fw.ip.invflags & (IPT_INV_SRCIP | IPT_INV_DSTIP))) + xtables_error(PARAMETER_PROBLEM, "! not allowed with multiple" + " source or destination IP addresses"); + + if (command == CMD_REPLACE && (nsaddrs != 1 || ndaddrs != 1)) + xtables_error(PARAMETER_PROBLEM, "Replacement rule does not " + "specify a unique address"); + + generic_opt_check(command, cs.options); + + if (chain != NULL && strlen(chain) >= XT_EXTENSION_MAXNAMELEN) + xtables_error(PARAMETER_PROBLEM, + "chain name `%s' too long (must be under %u chars)", + chain, XT_EXTENSION_MAXNAMELEN); + + if (command == CMD_APPEND + || command == CMD_DELETE + || command == CMD_CHECK + || command == CMD_INSERT + || command == CMD_REPLACE) { + if (strcmp(chain, "PREROUTING") == 0 + || strcmp(chain, "INPUT") == 0) { + /* -o not valid with incoming packets. */ + if (cs.options & OPT_VIANAMEOUT) + xtables_error(PARAMETER_PROBLEM, + "Can't use -%c with %s\n", + opt2char(OPT_VIANAMEOUT), + chain); + } + + if (strcmp(chain, "POSTROUTING") == 0 + || strcmp(chain, "OUTPUT") == 0) { + /* -i not valid with outgoing packets */ + if (cs.options & OPT_VIANAMEIN) + xtables_error(PARAMETER_PROBLEM, + "Can't use -%c with %s\n", + opt2char(OPT_VIANAMEIN), + chain); + } + + /* + * Contrary to what iptables does, we assume that any jumpto + * is a custom chain jumps (if no target is found). Later on, + * nf_table will spot the error if the chain does not exists. + */ + } + + switch (command) { + case CMD_APPEND: + ret = add_entry(chain, *table, &cs, + nsaddrs, saddrs, smasks, + ndaddrs, daddrs, dmasks, + cs.options&OPT_VERBOSE, + h, true); + break; + case CMD_DELETE: + ret = delete_entry(chain, *table, &cs, + nsaddrs, saddrs, smasks, + ndaddrs, daddrs, dmasks, + cs.options&OPT_VERBOSE, h); + break; + case CMD_DELETE_NUM: + ret = nft_rule_delete_num(h, chain, *table, rulenum - 1, verbose); + break; + case CMD_CHECK: + ret = check_entry(chain, *table, &cs, + nsaddrs, saddrs, smasks, + ndaddrs, daddrs, dmasks, + cs.options&OPT_VERBOSE, h); + break; + case CMD_REPLACE: + /* FIXME replace at rulenum */ + ret = replace_entry(chain, *table, &cs, rulenum - 1, + saddrs, smasks, daddrs, dmasks, + cs.options&OPT_VERBOSE, h); + break; + case CMD_INSERT: + /* FIXME insert at rulenum */ + ret = add_entry(chain, *table, &cs, + nsaddrs, saddrs, smasks, + ndaddrs, daddrs, dmasks, + cs.options&OPT_VERBOSE, h, false); + break; + case CMD_FLUSH: + ret = nft_rule_flush(h, chain, *table); + break; + case CMD_ZERO: + /* FIXME */ +// ret = zero_entries(chain, cs.options&OPT_VERBOSE, *handle); + break; + case CMD_ZERO_NUM: + /* FIXME */ +// ret = iptc_zero_counter(chain, rulenum, *handle); + break; + case CMD_LIST: + case CMD_LIST|CMD_ZERO: + case CMD_LIST|CMD_ZERO_NUM: + /* FIXME */ + ret = list_entries(h, chain, *table, + rulenum, + cs.options&OPT_VERBOSE, + cs.options&OPT_NUMERIC, + cs.options&OPT_EXPANDED, + cs.options&OPT_LINENUMBERS); +/* if (ret && (command & CMD_ZERO)) + ret = zero_entries(chain, + cs.options&OPT_VERBOSE, *handle); + if (ret && (command & CMD_ZERO_NUM)) + ret = iptc_zero_counter(chain, rulenum, *handle); */ + break; + case CMD_LIST_RULES: + case CMD_LIST_RULES|CMD_ZERO: + case CMD_LIST_RULES|CMD_ZERO_NUM: + /* FIXME */ + ret = list_rules(h, chain, *table, rulenum, cs.options&OPT_VERBOSE); +/* if (ret && (command & CMD_ZERO)) + ret = zero_entries(chain, + cs.options&OPT_VERBOSE, *handle); + if (ret && (command & CMD_ZERO_NUM)) + ret = iptc_zero_counter(chain, rulenum, *handle); */ + break; + case CMD_NEW_CHAIN: + ret = nft_chain_user_add(h, chain, *table); + break; + case CMD_DELETE_CHAIN: + ret = nft_chain_user_del(h, chain, *table); + break; + case CMD_RENAME_CHAIN: + /* XXX iptables allows renaming an used chain, we don't */ + ret = nft_chain_user_rename(h, chain, *table, newname); + break; + case CMD_SET_POLICY: + ret = nft_chain_set(h, *table, chain, policy, NULL); + if (ret < 0) + xtables_error(PARAMETER_PROBLEM, "Wrong policy `%s'\n", + policy); + break; + default: + /* We should never reach this... */ + exit_tryhelp(2); + } + +/* if (verbose > 1) + dump_entries(*handle); */ + + clear_rule_matches(&cs.matches); + + free(saddrs); + free(smasks); + free(daddrs); + free(dmasks); + xtables_free_opts(1); + + return ret; +} -- cgit v1.2.3