diff options
Diffstat (limited to 'iptables/nft.c')
-rw-r--r-- | iptables/nft.c | 2542 |
1 files changed, 2542 insertions, 0 deletions
diff --git a/iptables/nft.c b/iptables/nft.c new file mode 100644 index 00000000..1237659f --- /dev/null +++ b/iptables/nft.c @@ -0,0 +1,2542 @@ +/* + * (C) 2012 by Pablo Neira Ayuso <pablo@netfilter.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This code has been sponsored by Sophos Astaro <http://www.sophos.com> + */ + +#include <unistd.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <stdbool.h> +#include <errno.h> +#include <netdb.h> /* getprotobynumber */ +#include <time.h> +#include <stdarg.h> +#include <inttypes.h> + +#include <xtables.h> +#include <libiptc/libxtc.h> +#include <libiptc/xtcshared.h> + +#include <stdlib.h> +#include <string.h> + +#include <linux/netfilter/x_tables.h> +#include <linux/netfilter_ipv4/ip_tables.h> +#include <linux/netfilter_ipv6/ip6_tables.h> +#include <netinet/ip6.h> + +#include <linux/netlink.h> +#include <linux/netfilter/nfnetlink.h> +#include <linux/netfilter/nf_tables.h> +#include <linux/netfilter/nf_tables_compat.h> + +#include <libmnl/libmnl.h> +#include <libnftnl/table.h> +#include <libnftnl/chain.h> +#include <libnftnl/rule.h> +#include <libnftnl/expr.h> + +#include <netinet/in.h> /* inet_ntoa */ +#include <arpa/inet.h> + +#include "nft.h" +#include "xshared.h" /* proto_to_name */ +#include "nft-shared.h" +#include "xtables-config-parser.h" + +static void *nft_fn; + +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 LIST_HEAD(batch_page_list); +static int batch_num_pages; + +struct batch_page { + struct list_head head; + struct mnl_nlmsg_batch *batch; +}; + +/* selected batch page is 256 Kbytes long to load ruleset of + * half a million rules without hitting -EMSGSIZE due to large + * iovec. + */ +#define BATCH_PAGE_SIZE getpagesize() * 32 + +static struct mnl_nlmsg_batch *mnl_nft_batch_alloc(void) +{ + static char *buf; + + /* libmnl needs higher buffer to handle batch overflows */ + buf = malloc(BATCH_PAGE_SIZE + getpagesize()); + if (buf == NULL) + return NULL; + + return mnl_nlmsg_batch_start(buf, BATCH_PAGE_SIZE); +} + +static struct mnl_nlmsg_batch * +mnl_nft_batch_page_add(struct mnl_nlmsg_batch *batch) +{ + struct batch_page *batch_page; + + batch_page = malloc(sizeof(struct batch_page)); + if (batch_page == NULL) + return NULL; + + batch_page->batch = batch; + list_add_tail(&batch_page->head, &batch_page_list); + batch_num_pages++; + + return mnl_nft_batch_alloc(); +} + +static int nlbuffsiz; + +static void mnl_nft_set_sndbuffer(const struct mnl_socket *nl) +{ + int newbuffsiz; + + if (batch_num_pages * BATCH_PAGE_SIZE <= nlbuffsiz) + return; + + newbuffsiz = batch_num_pages * BATCH_PAGE_SIZE; + + /* Rise sender buffer length to avoid hitting -EMSGSIZE */ + if (setsockopt(mnl_socket_get_fd(nl), SOL_SOCKET, SO_SNDBUFFORCE, + &newbuffsiz, sizeof(socklen_t)) < 0) + return; + + nlbuffsiz = newbuffsiz; +} + +static ssize_t mnl_nft_socket_sendmsg(const struct mnl_socket *nl) +{ + static const struct sockaddr_nl snl = { + .nl_family = AF_NETLINK + }; + struct iovec iov[batch_num_pages]; + struct msghdr msg = { + .msg_name = (struct sockaddr *) &snl, + .msg_namelen = sizeof(snl), + .msg_iov = iov, + .msg_iovlen = batch_num_pages, + }; + struct batch_page *batch_page, *next; + int i = 0; + + mnl_nft_set_sndbuffer(nl); + + list_for_each_entry_safe(batch_page, next, &batch_page_list, head) { + iov[i].iov_base = mnl_nlmsg_batch_head(batch_page->batch); + iov[i].iov_len = mnl_nlmsg_batch_size(batch_page->batch); + i++; +#ifdef NL_DEBUG + mnl_nlmsg_fprintf(stdout, + mnl_nlmsg_batch_head(batch_page->batch), + mnl_nlmsg_batch_size(batch_page->batch), + sizeof(struct nfgenmsg)); +#endif + list_del(&batch_page->head); + free(batch_page->batch); + free(batch_page); + batch_num_pages--; + } + + return sendmsg(mnl_socket_get_fd(nl), &msg, 0); +} + +static int cb_err(const struct nlmsghdr *nlh, void *data) +{ + /* We can provide better error reporting than iptables-restore */ + errno = EINVAL; + return MNL_CB_ERROR; +} + +static mnl_cb_t cb_ctl_array[NLMSG_MIN_TYPE] = { + [NLMSG_ERROR] = cb_err, +}; + +static int mnl_nft_batch_talk(struct nft_handle *h) +{ + int ret, fd = mnl_socket_get_fd(h->nl); + char rcv_buf[MNL_SOCKET_BUFFER_SIZE]; + fd_set readfds; + struct timeval tv = { + .tv_sec = 0, + .tv_usec = 0 + }; + int err = 0; + + ret = mnl_nft_socket_sendmsg(h->nl); + if (ret == -1) { + perror("mnl_socket_sendmsg"); + return -1; + } + + FD_ZERO(&readfds); + FD_SET(fd, &readfds); + + /* receive and digest all the acknowledgments from the kernel. */ + ret = select(fd+1, &readfds, NULL, NULL, &tv); + if (ret == -1) { + perror("select"); + return -1; + } + while (ret > 0 && FD_ISSET(fd, &readfds)) { + ret = mnl_socket_recvfrom(h->nl, rcv_buf, sizeof(rcv_buf)); + if (ret == -1) { + perror("mnl_socket_recvfrom"); + return -1; + } + + ret = mnl_cb_run2(rcv_buf, ret, 0, h->portid, + NULL, NULL, cb_ctl_array, + MNL_ARRAY_SIZE(cb_ctl_array)); + /* Continue on error, make sure we get all acknoledgments */ + if (ret == -1) + err = errno; + + ret = select(fd+1, &readfds, NULL, NULL, &tv); + if (ret == -1) { + perror("select"); + return -1; + } + FD_ZERO(&readfds); + FD_SET(fd, &readfds); + } + return err ? -1 : 0; +} + +static void mnl_nft_batch_put(struct mnl_nlmsg_batch *batch, int type, + uint32_t seq) +{ + struct nlmsghdr *nlh; + struct nfgenmsg *nfg; + + nlh = mnl_nlmsg_put_header(mnl_nlmsg_batch_current(batch)); + nlh->nlmsg_type = type; + nlh->nlmsg_flags = NLM_F_REQUEST; + nlh->nlmsg_seq = seq; + + nfg = mnl_nlmsg_put_extra_header(nlh, sizeof(*nfg)); + nfg->nfgen_family = AF_INET; + nfg->version = NFNETLINK_V0; + nfg->res_id = NFNL_SUBSYS_NFTABLES; + + if (!mnl_nlmsg_batch_next(batch)) + mnl_nft_batch_page_add(batch); +} + +static void mnl_nft_batch_begin(struct mnl_nlmsg_batch *batch, uint32_t seq) +{ + mnl_nft_batch_put(batch, NFNL_MSG_BATCH_BEGIN, seq); +} + +static void mnl_nft_batch_end(struct mnl_nlmsg_batch *batch, uint32_t seq) +{ + mnl_nft_batch_put(batch, NFNL_MSG_BATCH_END, seq); +} + +struct builtin_table xtables_ipv4[TABLES_MAX] = { + [RAW] = { + .name = "raw", + .chains = { + { + .name = "PREROUTING", + .type = "filter", + .prio = -300, /* NF_IP_PRI_RAW */ + .hook = NF_INET_PRE_ROUTING, + }, + { + .name = "OUTPUT", + .type = "filter", + .prio = -300, /* NF_IP_PRI_RAW */ + .hook = NF_INET_LOCAL_OUT, + }, + }, + }, + [MANGLE] = { + .name = "mangle", + .chains = { + { + .name = "PREROUTING", + .type = "filter", + .prio = -150, /* NF_IP_PRI_MANGLE */ + .hook = NF_INET_PRE_ROUTING, + }, + { + .name = "INPUT", + .type = "filter", + .prio = -150, /* NF_IP_PRI_MANGLE */ + .hook = NF_INET_LOCAL_IN, + }, + { + .name = "FORWARD", + .type = "filter", + .prio = -150, /* NF_IP_PRI_MANGLE */ + .hook = NF_INET_FORWARD, + }, + { + .name = "OUTPUT", + .type = "route", + .prio = -150, /* NF_IP_PRI_MANGLE */ + .hook = NF_INET_LOCAL_OUT, + }, + { + .name = "POSTROUTING", + .type = "filter", + .prio = -150, /* NF_IP_PRI_MANGLE */ + .hook = NF_INET_POST_ROUTING, + }, + }, + }, + [FILTER] = { + .name = "filter", + .chains = { + { + .name = "INPUT", + .type = "filter", + .prio = 0, /* NF_IP_PRI_FILTER */ + .hook = NF_INET_LOCAL_IN, + }, + { + .name = "FORWARD", + .type = "filter", + .prio = 0, /* NF_IP_PRI_FILTER */ + .hook = NF_INET_FORWARD, + }, + { + .name = "OUTPUT", + .type = "filter", + .prio = 0, /* NF_IP_PRI_FILTER */ + .hook = NF_INET_LOCAL_OUT, + }, + }, + }, + [SECURITY] = { + .name = "security", + .chains = { + { + .name = "INPUT", + .type = "filter", + .prio = 150, /* NF_IP_PRI_SECURITY */ + .hook = NF_INET_LOCAL_IN, + }, + { + .name = "FORWARD", + .type = "filter", + .prio = 150, /* NF_IP_PRI_SECURITY */ + .hook = NF_INET_FORWARD, + }, + { + .name = "OUTPUT", + .type = "filter", + .prio = 150, /* NF_IP_PRI_SECURITY */ + .hook = NF_INET_LOCAL_OUT, + }, + }, + }, + [NAT] = { + .name = "nat", + .chains = { + { + .name = "PREROUTING", + .type = "nat", + .prio = -100, /* NF_IP_PRI_NAT_DST */ + .hook = NF_INET_PRE_ROUTING, + }, + { + .name = "INPUT", + .type = "nat", + .prio = 100, /* NF_IP_PRI_NAT_SRC */ + .hook = NF_INET_LOCAL_IN, + }, + { + .name = "POSTROUTING", + .type = "nat", + .prio = 100, /* NF_IP_PRI_NAT_SRC */ + .hook = NF_INET_POST_ROUTING, + }, + { + .name = "OUTPUT", + .type = "nat", + .prio = -100, /* NF_IP_PRI_NAT_DST */ + .hook = NF_INET_LOCAL_OUT, + }, + }, + }, +}; + +#include <linux/netfilter_arp.h> + +struct builtin_table xtables_arp[TABLES_MAX] = { + [FILTER] = { + .name = "filter", + .chains = { + { + .name = "INPUT", + .type = "filter", + .prio = NF_IP_PRI_FILTER, + .hook = NF_ARP_IN, + }, + { + .name = "FORWARD", + .type = "filter", + .prio = NF_IP_PRI_FILTER, + .hook = NF_ARP_FORWARD, + }, + { + .name = "OUTPUT", + .type = "filter", + .prio = NF_IP_PRI_FILTER, + .hook = NF_ARP_OUT, + }, + }, + }, +}; + +int +nft_table_builtin_add(struct nft_handle *h, struct builtin_table *_t, + bool dormant) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + struct nft_table *t; + int ret; + + if (_t->initialized) + return 0; + + t = nft_table_alloc(); + if (t == NULL) + return -1; + + nft_table_attr_set(t, NFT_TABLE_ATTR_NAME, (char *)_t->name); + if (dormant) { + nft_table_attr_set_u32(t, NFT_TABLE_ATTR_FLAGS, + NFT_TABLE_F_DORMANT); + } + + nlh = nft_table_nlmsg_build_hdr(buf, NFT_MSG_NEWTABLE, h->family, + NLM_F_ACK|NLM_F_EXCL, h->seq); + nft_table_nlmsg_build_payload(nlh, t); + nft_table_free(t); + +#ifdef NLDEBUG + char tmp[1024]; + + nft_table_snprintf(tmp, sizeof(tmp), t, 0, 0); + printf("DEBUG: table: %s\n", tmp); + mnl_nlmsg_fprintf(stdout, nlh, nlh->nlmsg_len, sizeof(struct nfgenmsg)); +#endif + + ret = mnl_talk(h, nlh, NULL, NULL); + if (ret == 0 || errno == EEXIST) + _t->initialized = true; + + return ret; +} + +struct nft_chain * +nft_chain_builtin_alloc(struct builtin_table *table, + struct builtin_chain *chain, int policy) +{ + struct nft_chain *c; + + c = nft_chain_alloc(); + if (c == NULL) + return NULL; + + 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, chain->prio); + nft_chain_attr_set_u32(c, NFT_CHAIN_ATTR_POLICY, policy); + nft_chain_attr_set(c, NFT_CHAIN_ATTR_TYPE, (char *)chain->type); + + return c; +} + +void +nft_chain_builtin_add(struct nft_handle *h, struct builtin_table *table, + struct builtin_chain *chain, int policy) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + struct nft_chain *c; + + c = nft_chain_builtin_alloc(table, chain, policy); + if (c == NULL) + return; + + /* NLM_F_CREATE requests module autoloading */ + nlh = nft_chain_nlmsg_build_hdr(buf, NFT_MSG_NEWCHAIN, h->family, + NLM_F_ACK|NLM_F_EXCL|NLM_F_CREATE, + h->seq); + nft_chain_nlmsg_build_payload(nlh, c); + nft_chain_free(c); + + mnl_talk(h, nlh, NULL, NULL); +} + +/* find if built-in table already exists */ +struct builtin_table * +nft_table_builtin_find(struct nft_handle *h, const char *table) +{ + int i; + bool found = false; + + for (i=0; i<TABLES_MAX; i++) { + if (h->tables[i].name == NULL) + break; + + if (strcmp(h->tables[i].name, table) != 0) + continue; + + found = true; + break; + } + + return found ? &h->tables[i] : NULL; +} + +/* find if built-in chain already exists */ +struct builtin_chain * +nft_chain_builtin_find(struct builtin_table *t, const char *chain) +{ + int i; + bool found = false; + + for (i=0; i<NF_IP_NUMHOOKS && t->chains[i].name != NULL; i++) { + if (strcmp(t->chains[i].name, chain) != 0) + continue; + + found = true; + break; + } + return found ? &t->chains[i] : NULL; +} + +static void +__nft_chain_builtin_init(struct nft_handle *h, + struct builtin_table *table, const char *chain, + int policy) +{ + int i, default_policy; + + /* Initialize all built-in chains. Exception, for e one received as + * parameter, set the default policy as requested. + */ + for (i=0; i<NF_IP_NUMHOOKS && table->chains[i].name != NULL; i++) { + if (chain && strcmp(table->chains[i].name, chain) == 0) + default_policy = policy; + else + default_policy = NF_ACCEPT; + + nft_chain_builtin_add(h, table, &table->chains[i], + default_policy); + } +} + +int +nft_chain_builtin_init(struct nft_handle *h, const char *table, + const char *chain, int policy) +{ + int ret = 0; + struct builtin_table *t; + + t = nft_table_builtin_find(h, table); + if (t == NULL) { + ret = -1; + goto out; + } + if (nft_table_builtin_add(h, t, false) < 0) { + /* Built-in table already initialized, skip. */ + if (errno == EEXIST) + goto out; + } + __nft_chain_builtin_init(h, t, chain, policy); +out: + 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_init(struct nft_handle *h, struct builtin_table *t) +{ + h->nl = 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); + h->tables = t; + + INIT_LIST_HEAD(&h->rule_list); + + h->batch = mnl_nft_batch_alloc(); + + return 0; +} + +void nft_fini(struct nft_handle *h) +{ + mnl_socket_close(h->nl); + free(mnl_nlmsg_batch_head(h->batch)); + mnl_nlmsg_batch_stop(h->batch); +} + +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, h->family, + 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, h->family, + NLM_F_ACK|NLM_F_EXCL, h->seq); + nft_chain_nlmsg_build_payload(nlh, c); + + return mnl_talk(h, nlh, NULL, NULL); +} + +int nft_table_set_dormant(struct nft_handle *h, const char *table) +{ + int ret = 0, i; + struct builtin_table *t; + + t = nft_table_builtin_find(h, table); + if (t == NULL) { + ret = -1; + goto out; + } + /* Add this table as dormant */ + if (nft_table_builtin_add(h, t, true) < 0) { + /* Built-in table already initialized, skip. */ + if (errno == EEXIST) + goto out; + } + for (i=0; t->chains[i].name != NULL && i<NF_INET_NUMHOOKS; i++) + __nft_chain_builtin_init(h, t, t->chains[i].name, NF_ACCEPT); +out: + return ret; +} + +int nft_table_wake_dormant(struct nft_handle *h, const char *table) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + struct nft_table *t; + + t = nft_table_alloc(); + if (t == NULL) + return -1; + + nft_table_attr_set(t, NFT_TABLE_ATTR_NAME, (char *)table); + nft_table_attr_set_u32(t, NFT_TABLE_ATTR_FLAGS, 0); + + nlh = nft_table_nlmsg_build_hdr(buf, NFT_MSG_NEWTABLE, h->family, + NLM_F_ACK, h->seq); + nft_table_nlmsg_build_payload(nlh, t); + nft_table_free(t); + + 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\n", 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; + struct builtin_table *_t; + struct builtin_chain *_c; + + _t = nft_table_builtin_find(h, table); + /* if this built-in table does not exists, create it */ + if (_t != NULL) + nft_table_builtin_add(h, _t, false); + + _c = nft_chain_builtin_find(_t, chain); + if (_c != NULL) { + /* This is a built-in chain */ + c = nft_chain_builtin_alloc(_t, _c, policy); + if (c == NULL) + return -1; + } else { + errno = ENOENT; + return -1; + } + + 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, h->family, + h->restore ? NLM_F_ACK|NLM_F_CREATE : + NLM_F_ACK, h->seq); + nft_chain_nlmsg_build_payload(nlh, c); + + nft_chain_print_debug(c, nlh); + + nft_chain_free(c); + + return mnl_talk(h, nlh, NULL, NULL); +} + +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 int __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 -ENOMEM; + + memcpy(info, m->data, m->u.match_size - sizeof(*m)); + nft_rule_expr_set(e, NFT_EXPR_MT_INFO, info, m->u.match_size - sizeof(*m)); + + return 0; +} + +int add_match(struct nft_rule *r, struct xt_entry_match *m) +{ + struct nft_rule_expr *expr; + int ret; + + expr = nft_rule_expr_alloc("match"); + if (expr == NULL) + return -ENOMEM; + + ret = __add_match(expr, m); + nft_rule_add_expr(r, expr); + + return ret; +} + +static int __add_target(struct nft_rule_expr *e, struct xt_entry_target *t) +{ + void *info; + + 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); + + info = calloc(1, t->u.target_size); + if (info == NULL) + return -ENOMEM; + + memcpy(info, t->data, t->u.target_size - sizeof(*t)); + nft_rule_expr_set(e, NFT_EXPR_TG_INFO, info, t->u.target_size - sizeof(*t)); + + return 0; +} + +int add_target(struct nft_rule *r, struct xt_entry_target *t) +{ + struct nft_rule_expr *expr; + int ret; + + expr = nft_rule_expr_alloc("target"); + if (expr == NULL) + return -ENOMEM; + + ret = __add_target(expr, t); + nft_rule_add_expr(r, expr); + + return ret; +} + +int 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 -ENOMEM; + + 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); + + return 0; +} + +int add_verdict(struct nft_rule *r, int verdict) +{ + struct nft_rule_expr *expr; + + expr = nft_rule_expr_alloc("immediate"); + if (expr == NULL) + return -ENOMEM; + + 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); + + return 0; +} + +int add_action(struct nft_rule *r, struct iptables_command_state *cs, + bool goto_set) +{ + int ret = 0; + + /* If no target at all, add nothing (default to continue) */ + if (cs->target != NULL) { + /* Standard target? */ + if (strcmp(cs->jumpto, XTC_LABEL_ACCEPT) == 0) + ret = add_verdict(r, NF_ACCEPT); + else if (strcmp(cs->jumpto, XTC_LABEL_DROP) == 0) + ret = add_verdict(r, NF_DROP); + else if (strcmp(cs->jumpto, XTC_LABEL_RETURN) == 0) + ret = add_verdict(r, NFT_RETURN); + else + ret = add_target(r, cs->target->t); + } else if (strlen(cs->jumpto) > 0) { + /* Not standard, then it's a go / jump to chain */ + if (goto_set) + ret = add_jumpto(r, cs->jumpto, NFT_GOTO); + else + ret = add_jumpto(r, cs->jumpto, NFT_JUMP); + } + return ret; +} + +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\n", tmp); + mnl_nlmsg_fprintf(stdout, nlh, nlh->nlmsg_len, sizeof(struct nfgenmsg)); +#endif +} + +int 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 -ENOMEM; + + 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); + + return 0; +} + +void add_compat(struct nft_rule *r, uint32_t proto, bool inv) +{ + nft_rule_attr_set_u32(r, NFT_RULE_ATTR_COMPAT_PROTO, proto); + nft_rule_attr_set_u32(r, NFT_RULE_ATTR_COMPAT_FLAGS, + inv ? NFT_RULE_COMPAT_F_INV : 0); +} + +static struct nft_rule * +nft_rule_new(struct nft_handle *h, const char *chain, const char *table, + void *data) +{ + struct nft_rule *r; + + r = nft_rule_alloc(); + if (r == NULL) + return NULL; + + nft_rule_attr_set_u32(r, NFT_RULE_ATTR_FAMILY, h->family); + nft_rule_attr_set(r, NFT_RULE_ATTR_TABLE, (char *)table); + nft_rule_attr_set(r, NFT_RULE_ATTR_CHAIN, (char *)chain); + + if (h->ops->add(r, data) < 0) + goto err; + + return r; +err: + nft_rule_free(r); + return NULL; +} + +enum rule_update_type { + NFT_DO_APPEND, + NFT_DO_INSERT, + NFT_DO_REPLACE, + NFT_DO_DELETE, + NFT_DO_FLUSH, + NFT_DO_COMMIT, + NFT_DO_ABORT, +}; + +struct rule_update { + struct list_head head; + enum rule_update_type type; + struct nft_rule *rule; +}; + +static int rule_update_add(struct nft_handle *h, enum rule_update_type type, + struct nft_rule *r) +{ + struct rule_update *rupd; + + rupd = calloc(1, sizeof(struct rule_update)); + if (rupd == NULL) + return -1; + + rupd->rule = r; + rupd->type = type; + list_add_tail(&rupd->head, &h->rule_list); + h->rule_list_num++; + + return 0; +} + +int +nft_rule_append(struct nft_handle *h, const char *chain, const char *table, + void *data, uint64_t handle, bool verbose) +{ + struct nft_rule *r; + int type; + + /* If built-in chains don't exist for this table, create them */ + if (nft_xtables_config_load(h, XTABLES_CONFIG_DEFAULT, 0) < 0) + nft_chain_builtin_init(h, table, chain, NF_ACCEPT); + + nft_fn = nft_rule_append; + + r = nft_rule_new(h, chain, table, data); + if (r == NULL) + return 0; + + if (handle > 0) { + nft_rule_attr_set(r, NFT_RULE_ATTR_HANDLE, &handle); + type = NFT_DO_REPLACE; + } else + type = NFT_DO_APPEND; + + if (rule_update_add(h, type, r) < 0) + nft_rule_free(r); + + return 1; +} + +void +nft_rule_print_save(const void *data, + struct nft_rule *r, enum nft_rule_print type, + unsigned int format) +{ + const char *chain = nft_rule_attr_get_str(r, NFT_RULE_ATTR_CHAIN); + int family = nft_rule_attr_get_u8(r, NFT_RULE_ATTR_FAMILY); + struct nft_family_ops *ops; + + /* print chain name */ + switch(type) { + case NFT_RULE_APPEND: + printf("-A %s ", chain); + break; + case NFT_RULE_DEL: + printf("-D %s ", chain); + break; + } + + ops = nft_family_ops_lookup(family); + + if (ops->save_firewall) + ops->save_firewall(data, format); + +} + +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_tail(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; + struct nft_chain_list *list; + + list = nft_chain_list_alloc(); + if (list == NULL) { + errno = ENOMEM; + return NULL; + } + + nlh = nft_chain_nlmsg_build_hdr(buf, NFT_MSG_GETCHAIN, h->family, + NLM_F_DUMP, h->seq); + + mnl_talk(h, nlh, nft_chain_list_cb, list); + + 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 [%"PRIu64":%"PRIu64"]\n", chain, policy_name[pol], + pkts, bytes); + } else + printf(":%s - [%"PRIu64":%"PRIu64"]\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) + 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; + + basechain = nft_chain_builtin(c); + nft_chain_print_save(c, basechain); +next: + c = nft_chain_list_iter_next(iter); + } + + nft_chain_list_iter_destroy(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_tail(r, list); + + return MNL_CB_OK; +out: + nft_rule_free(r); + nft_rule_list_free(list); +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) + return 0; + + nlh = nft_rule_nlmsg_build_hdr(buf, NFT_MSG_GETRULE, h->family, + NLM_F_DUMP, h->seq); + + ret = mnl_talk(h, nlh, nft_rule_list_cb, list); + if (ret < 0) { + 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) + return 0; + + iter = nft_rule_list_iter_create(list); + if (iter == NULL) + 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); + struct iptables_command_state cs = {}; + + if (strcmp(table, rule_table) != 0) + goto next; + + nft_rule_to_iptables_command_state(r, &cs); + + nft_rule_print_save(&cs, r, NFT_RULE_APPEND, + counters ? 0 : FMT_NOCOUNTS); + +next: + r = nft_rule_list_iter_next(iter); + } + + nft_rule_list_iter_destroy(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) +{ + 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); + + if (rule_update_add(h, NFT_DO_FLUSH, r) < 0) + nft_rule_free(r); +} + +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) + goto err; + + 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); + + if (chain != NULL) + break; +next: + c = nft_chain_list_iter_next(iter); + } + + nft_chain_list_iter_destroy(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; + + /* If built-in chains don't exist for this table, create them */ + if (nft_xtables_config_load(h, XTABLES_CONFIG_DEFAULT, 0) < 0) + nft_chain_builtin_init(h, table, NULL, NF_ACCEPT); + + c = nft_chain_alloc(); + if (c == NULL) + return 0; + + 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, h->family, + 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); + + /* 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; + + nlh = nft_chain_nlmsg_build_hdr(buf, NFT_MSG_DELCHAIN, h->family, + NLM_F_ACK, h->seq); + nft_chain_nlmsg_build_payload(nlh, c); + + return mnl_talk(h, nlh, NULL, 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) + goto err; + + 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++; + + if (chain != NULL) + break; +next: + c = nft_chain_list_iter_next(iter); + } + + nft_chain_list_iter_destroy(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; +} + +struct nft_chain * +nft_chain_list_find(struct nft_chain_list *list, + const char *table, const char *chain) +{ + struct nft_chain_list_iter *iter; + struct nft_chain *c; + + iter = nft_chain_list_iter_create(list); + if (iter == NULL) + return NULL; + + 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 (strcmp(chain, chain_name) != 0) + goto next; + + nft_chain_list_iter_destroy(iter); + return c; +next: + c = nft_chain_list_iter_next(iter); + } + nft_chain_list_iter_destroy(iter); + return NULL; +} + +static struct nft_chain * +nft_chain_find(struct nft_handle *h, const char *table, const char *chain) +{ + struct nft_chain_list *list; + + list = nft_chain_list_get(h); + if (list == NULL) + return NULL; + + return nft_chain_list_find(list, table, chain); +} + +int nft_chain_user_rename(struct nft_handle *h,const char *chain, + const char *table, const char *newname) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + struct nft_chain *c; + uint64_t handle; + int ret; + + /* If built-in chains don't exist for this table, create them */ + if (nft_xtables_config_load(h, XTABLES_CONFIG_DEFAULT, 0) < 0) + nft_chain_builtin_init(h, table, NULL, NF_ACCEPT); + + /* Find the old chain to be renamed */ + c = nft_chain_find(h, table, chain); + if (c == NULL) { + errno = ENOENT; + return -1; + } + handle = nft_chain_attr_get_u64(c, NFT_CHAIN_ATTR_HANDLE); + + /* Now prepare the new name for the chain */ + 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 *)newname); + nft_chain_attr_set_u64(c, NFT_CHAIN_ATTR_HANDLE, handle); + + nlh = nft_chain_nlmsg_build_hdr(buf, NFT_MSG_NEWCHAIN, h->family, + NLM_F_ACK, h->seq); + nft_chain_nlmsg_build_payload(nlh, c); + nft_chain_free(c); + + ret = mnl_talk(h, nlh, NULL, NULL); + + /* the core expects 1 for success and 0 for error */ + return ret == 0 ? 1 : 0; +} + +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_tail(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; + struct nft_table_list *list; + + list = nft_table_list_alloc(); + if (list == NULL) + return 0; + + nlh = nft_rule_nlmsg_build_hdr(buf, NFT_MSG_GETTABLE, h->family, + NLM_F_DUMP, h->seq); + + mnl_talk(h, nlh, nft_table_list_cb, list); + + 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) + 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) + 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; +} + +int nft_table_purge_chains(struct nft_handle *h, const char *this_table, + struct nft_chain_list *chain_list) +{ + struct nft_chain_list_iter *iter; + struct nft_chain *chain_obj; + + iter = nft_chain_list_iter_create(chain_list); + if (iter == NULL) + return 0; + + chain_obj = nft_chain_list_iter_next(iter); + while (chain_obj != NULL) { + const char *table = + nft_chain_attr_get_str(chain_obj, NFT_CHAIN_ATTR_TABLE); + + if (strcmp(this_table, table) != 0) + goto next; + + if (nft_chain_builtin(chain_obj)) + goto next; + + if ( __nft_chain_del(h, chain_obj) < 0) { + if (errno != EBUSY) + return -1; + } +next: + chain_obj = nft_chain_list_iter_next(iter); + } + nft_chain_list_iter_destroy(iter); + + return 0; +} + +static int __nft_rule_del(struct nft_handle *h, struct nft_rule_list *list, + struct nft_rule *r) +{ + int ret; + + nft_rule_list_del(r); + + ret = rule_update_add(h, NFT_DO_DELETE, r); + if (ret < 0) { + nft_rule_free(r); + return -1; + } + return 1; +} + +struct nft_rule_list *nft_rule_list_create(struct nft_handle *h) +{ + return nft_rule_list_get(h); +} + +void nft_rule_list_destroy(struct nft_rule_list *list) +{ + nft_rule_list_free(list); +} + +static struct nft_rule * +nft_rule_find(struct nft_handle *h, struct nft_rule_list *list, + const char *chain, const char *table, void *data, int rulenum) +{ + struct nft_rule *r; + struct nft_rule_list_iter *iter; + int rule_ctr = 0; + bool found = false; + + iter = nft_rule_list_iter_create(list); + if (iter == NULL) + 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); + + 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) + goto next; + found = true; + break; + } else { + found = h->ops->rule_find(h->ops, r, data); + if (found) + break; + } +next: + rule_ctr++; + r = nft_rule_list_iter_next(iter); + } + + nft_rule_list_iter_destroy(iter); + + return found ? r : NULL; +} + +int nft_rule_check(struct nft_handle *h, const char *chain, + const char *table, void *data, bool verbose) +{ + struct nft_rule_list *list; + int ret; + + nft_fn = nft_rule_check; + + list = nft_rule_list_create(h); + if (list == NULL) + return 0; + + ret = nft_rule_find(h, list, chain, table, data, -1) ? 1 : 0; + if (ret == 0) + errno = ENOENT; + + nft_rule_list_destroy(list); + + return ret; +} + +int nft_rule_delete(struct nft_handle *h, const char *chain, + const char *table, void *data, bool verbose) +{ + int ret = 0; + struct nft_rule *r; + struct nft_rule_list *list; + + nft_fn = nft_rule_delete; + + list = nft_rule_list_create(h); + if (list == NULL) + return 0; + + r = nft_rule_find(h, list, chain, table, data, -1); + if (r != NULL) { + ret =__nft_rule_del(h, list, r); + if (ret < 0) + errno = ENOMEM; + } else + errno = ENOENT; + + nft_rule_list_destroy(list); + + return ret; +} + +static int +nft_rule_add(struct nft_handle *h, const char *chain, + const char *table, struct iptables_command_state *cs, + uint64_t handle, bool verbose) +{ + struct nft_rule *r; + + r = nft_rule_new(h, chain, table, cs); + if (r == NULL) + return 0; + + if (handle > 0) + nft_rule_attr_set_u64(r, NFT_RULE_ATTR_POSITION, handle); + + if (rule_update_add(h, NFT_DO_INSERT, r) < 0) { + nft_rule_free(r); + return 0; + } + + return 1; +} + +int nft_rule_insert(struct nft_handle *h, const char *chain, + const char *table, void *data, int rulenum, bool verbose) +{ + struct nft_rule_list *list; + struct nft_rule *r; + uint64_t handle = 0; + + /* If built-in chains don't exist for this table, create them */ + if (nft_xtables_config_load(h, XTABLES_CONFIG_DEFAULT, 0) < 0) + nft_chain_builtin_init(h, table, chain, NF_ACCEPT); + + nft_fn = nft_rule_insert; + + if (rulenum > 0) { + list = nft_rule_list_create(h); + if (list == NULL) + goto err; + + r = nft_rule_find(h, list, chain, table, data, rulenum); + if (r == NULL) { + errno = ENOENT; + goto err; + } + + handle = nft_rule_attr_get_u64(r, NFT_RULE_ATTR_HANDLE); + DEBUGP("adding after rule handle %"PRIu64"\n", handle); + + nft_rule_list_destroy(list); + } + + return nft_rule_add(h, chain, table, data, handle, verbose); +err: + nft_rule_list_destroy(list); + return 0; +} + +int nft_rule_delete_num(struct nft_handle *h, const char *chain, + const char *table, int rulenum, bool verbose) +{ + int ret = 0; + struct nft_rule *r; + struct nft_rule_list *list; + + nft_fn = nft_rule_delete_num; + + list = nft_rule_list_create(h); + if (list == NULL) + return 0; + + r = nft_rule_find(h, list, chain, table, NULL, rulenum); + if (r != NULL) { + ret = 1; + + DEBUGP("deleting rule by number %d\n", rulenum); + ret = __nft_rule_del(h, list, r); + if (ret < 0) + errno = ENOMEM; + } else + errno = ENOENT; + + nft_rule_list_destroy(list); + + return ret; +} + +int nft_rule_replace(struct nft_handle *h, const char *chain, + const char *table, void *data, int rulenum, bool verbose) +{ + int ret = 0; + struct nft_rule *r; + struct nft_rule_list *list; + + nft_fn = nft_rule_replace; + + list = nft_rule_list_create(h); + if (list == NULL) + return 0; + + r = nft_rule_find(h, list, chain, table, data, rulenum); + if (r != NULL) { + DEBUGP("replacing rule with handle=%llu\n", + (unsigned long long) + nft_rule_attr_get_u64(r, NFT_RULE_ATTR_HANDLE)); + + ret = nft_rule_append(h, chain, table, data, + nft_rule_attr_get_u64(r, NFT_RULE_ATTR_HANDLE), + verbose); + } else + errno = ENOENT; + + nft_rule_list_destroy(list); + + return ret; +} + +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); + xtables_print_num(counters->pcnt, (format|FMT_NOTABLE)); + fputs("packets, ", stdout); + xtables_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 int +__nft_rule_list(struct nft_handle *h, const char *chain, const char *table, + int rulenum, unsigned int format, + void (*cb)(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; + + list = nft_rule_list_get(h); + if (list == NULL) + return 0; + + iter = nft_rule_list_iter_create(list); + if (iter == NULL) + goto err; + + 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 && rule_ctr != rulenum) { + /* List by rule number case */ + goto next; + } + + cb(r, rule_ctr, format); + if (rulenum > 0 && rule_ctr == rulenum) { + ret = 1; + break; + } + +next: + r = nft_rule_list_iter_next(iter); + } + + nft_rule_list_iter_destroy(iter); +err: + 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) +{ + const struct nft_family_ops *ops; + struct nft_chain_list *list; + struct nft_chain_list_iter *iter; + struct nft_chain *c; + bool found = false; + + /* If built-in chains don't exist for this table, create them */ + if (nft_xtables_config_load(h, XTABLES_CONFIG_DEFAULT, 0) < 0) + nft_chain_builtin_init(h, table, NULL, NF_ACCEPT); + + ops = nft_family_ops_lookup(h->family); + + if (chain && rulenum) { + __nft_rule_list(h, chain, table, + rulenum, format, ops->print_firewall); + return 1; + } + + list = nft_chain_dump(h); + + iter = nft_chain_list_iter_create(list); + if (iter == NULL) + goto err; + + 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; + + if (found) + printf("\n"); + + print_header(format, chain_name, policy_name[policy], + &ctrs, basechain, refs); + + __nft_rule_list(h, chain_name, table, + rulenum, format, ops->print_firewall); + + /* we printed the chain we wanted, stop processing. */ + if (chain) + break; + + found = true; + +next: + c = nft_chain_list_iter_next(iter); + } + + nft_chain_list_iter_destroy(iter); +err: + nft_chain_list_free(list); + + return 1; +} + +static void +list_save(struct nft_rule *r, unsigned int num, unsigned int format) +{ + struct iptables_command_state cs = {}; + + nft_rule_to_iptables_command_state(r, &cs); + + nft_rule_print_save(&cs, r, NFT_RULE_APPEND, !(format & FMT_NOCOUNTS)); +} + +static int +nft_rule_list_chain_save(struct nft_handle *h, const char *chain, + 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) + 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 || + (chain && strcmp(chain, chain_name) != 0)) + goto next; + + /* this is a base chain */ + if (nft_chain_builtin(c)) { + printf("-P %s %s", chain_name, policy_name[policy]); + + if (counters) { + printf(" -c %"PRIu64" %"PRIu64"\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); + } + + nft_chain_list_iter_destroy(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; + int ret = 1; + + list = nft_chain_dump(h); + + /* Dump policies and custom chains first */ + if (!rulenum) + nft_rule_list_chain_save(h, chain, table, list, counters); + + /* Now dump out rules in this table */ + iter = nft_chain_list_iter_create(list); + if (iter == NULL) + goto err; + + 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; + + ret = __nft_rule_list(h, chain_name, table, rulenum, + counters ? 0 : FMT_NOCOUNTS, list_save); + + /* we printed the chain we wanted, stop processing. */ + if (chain) + break; +next: + c = nft_chain_list_iter_next(iter); + } + + nft_chain_list_iter_destroy(iter); +err: + nft_chain_list_free(list); + + return ret; +} + +int nft_rule_zero_counters(struct nft_handle *h, const char *chain, + const char *table, int rulenum) +{ + struct iptables_command_state cs = {}; + struct nft_rule_list *list; + struct nft_rule *r; + int ret = 0; + + nft_fn = nft_rule_delete; + + list = nft_rule_list_create(h); + if (list == NULL) + return 0; + + r = nft_rule_find(h, list, chain, table, NULL, rulenum); + if (r == NULL) { + errno = ENOENT; + ret = 1; + goto error; + } + + nft_rule_to_iptables_command_state(r, &cs); + + cs.counters.pcnt = cs.counters.bcnt = 0; + + ret = nft_rule_append(h, chain, table, &cs, + nft_rule_attr_get_u64(r, NFT_RULE_ATTR_HANDLE), + false); + +error: + nft_rule_list_destroy(list); + + return ret; +} + +static int nft_action(struct nft_handle *h, int action) +{ + int flags = NLM_F_CREATE, type; + struct rule_update *n, *tmp; + struct nlmsghdr *nlh; + uint32_t seq = 1; + int ret; + + mnl_nft_batch_begin(h->batch, seq++); + + list_for_each_entry_safe(n, tmp, &h->rule_list, head) { + switch (n->type) { + case NFT_DO_APPEND: + type = NFT_MSG_NEWRULE; + flags |= NLM_F_APPEND; + break; + case NFT_DO_INSERT: + type = NFT_MSG_NEWRULE; + break; + case NFT_DO_REPLACE: + type = NFT_MSG_NEWRULE; + flags |= NLM_F_REPLACE; + break; + case NFT_DO_DELETE: + case NFT_DO_FLUSH: + type = NFT_MSG_DELRULE; + break; + default: + return 0; + } + + nlh = nft_rule_nlmsg_build_hdr(mnl_nlmsg_batch_current(h->batch), + type, h->family, flags, seq++); + nft_rule_nlmsg_build_payload(nlh, n->rule); + nft_rule_print_debug(n->rule, nlh); + + h->rule_list_num--; + list_del(&n->head); + nft_rule_free(n->rule); + free(n); + + if (!mnl_nlmsg_batch_next(h->batch)) + h->batch = mnl_nft_batch_page_add(h->batch); + } + + switch (action) { + case NFT_DO_COMMIT: + mnl_nft_batch_end(h->batch, seq++); + break; + case NFT_DO_ABORT: + break; + } + + if (!mnl_nlmsg_batch_is_empty(h->batch)) + h->batch = mnl_nft_batch_page_add(h->batch); + + ret = mnl_nft_batch_talk(h); + if (ret < 0) + perror("mnl_nft_batch_talk:"); + + mnl_nlmsg_batch_reset(h->batch); + + return ret == 0 ? 1 : 0; +} + +int nft_commit(struct nft_handle *h) +{ + return nft_action(h, NFT_DO_COMMIT); +} + +int nft_abort(struct nft_handle *h) +{ + return nft_action(h, NFT_DO_ABORT); +} + +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 || + opt == IP6T_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); +} + +static void xtables_config_perror(uint32_t flags, const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + + if (flags & NFT_LOAD_VERBOSE) + vfprintf(stderr, fmt, args); + + va_end(args); +} + +int nft_xtables_config_load(struct nft_handle *h, const char *filename, + uint32_t flags) +{ + 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 = NULL; + struct nft_chain_list_iter *citer = NULL; + struct nft_table *table; + struct nft_chain *chain; + uint32_t table_family, chain_family; + bool found = false; + + if (h->restore) + return 0; + + if (xtables_config_parse(filename, table_list, chain_list) < 0) { + if (errno == ENOENT) { + xtables_config_perror(flags, + "configuration file `%s' does not exists\n", + filename); + } else { + xtables_config_perror(flags, + "Fatal error parsing config file: %s\n", + strerror(errno)); + } + goto err; + } + + /* Stage 1) create tables */ + titer = nft_table_list_iter_create(table_list); + while ((table = nft_table_list_iter_next(titer)) != NULL) { + table_family = nft_table_attr_get_u32(table, + NFT_TABLE_ATTR_FAMILY); + if (h->family != table_family) + continue; + + found = true; + + if (nft_table_add(h, table) < 0) { + if (errno == EEXIST) { + xtables_config_perror(flags, + "table `%s' already exists, skipping\n", + (char *)nft_table_attr_get(table, NFT_TABLE_ATTR_NAME)); + } else { + xtables_config_perror(flags, + "table `%s' cannot be create, reason `%s'. Exitting\n", + (char *)nft_table_attr_get(table, NFT_TABLE_ATTR_NAME), + strerror(errno)); + goto err; + } + continue; + } + xtables_config_perror(flags, "table `%s' has been created\n", + (char *)nft_table_attr_get(table, NFT_TABLE_ATTR_NAME)); + } + nft_table_list_iter_destroy(titer); + nft_table_list_free(table_list); + + if (!found) + goto err; + + /* Stage 2) create chains */ + citer = nft_chain_list_iter_create(chain_list); + while ((chain = nft_chain_list_iter_next(citer)) != NULL) { + chain_family = nft_chain_attr_get_u32(chain, + NFT_CHAIN_ATTR_TABLE); + if (h->family != chain_family) + continue; + + if (nft_chain_add(h, chain) < 0) { + if (errno == EEXIST) { + xtables_config_perror(flags, + "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 { + xtables_config_perror(flags, + "chain `%s' cannot be create, reason `%s'. Exitting\n", + (char *)nft_chain_attr_get(chain, NFT_CHAIN_ATTR_NAME), + strerror(errno)); + goto err; + } + continue; + } + + xtables_config_perror(flags, + "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)); + } + nft_chain_list_iter_destroy(citer); + nft_chain_list_free(chain_list); + + return 0; + +err: + nft_table_list_free(table_list); + nft_chain_list_free(chain_list); + + if (titer != NULL) + nft_table_list_iter_destroy(titer); + if (citer != NULL) + nft_chain_list_iter_destroy(citer); + + return -1; +} + +int nft_chain_zero_counters(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; + struct nlmsghdr *nlh; + char buf[MNL_SOCKET_BUFFER_SIZE]; + int ret = 0; + + list = nft_chain_list_get(h); + if (list == NULL) + goto err; + + iter = nft_chain_list_iter_create(list); + if (iter == NULL) + goto err; + + c = nft_chain_list_iter_next(iter); + while (c != NULL) { + const char *chain_name = + nft_chain_attr_get(c, NFT_CHAIN_ATTR_NAME); + const char *chain_table = + nft_chain_attr_get(c, NFT_CHAIN_ATTR_TABLE); + + if (strcmp(table, chain_table) != 0) + goto next; + + if (chain != NULL && strcmp(chain, chain_name) != 0) + goto next; + + nft_chain_attr_set_u64(c, NFT_CHAIN_ATTR_PACKETS, 0); + nft_chain_attr_set_u64(c, NFT_CHAIN_ATTR_BYTES, 0); + + nft_chain_attr_unset(c, NFT_CHAIN_ATTR_HANDLE); + + nlh = nft_chain_nlmsg_build_hdr(buf, NFT_MSG_NEWCHAIN, + h->family, NLM_F_ACK, h->seq); + + nft_chain_nlmsg_build_payload(nlh, c); + + ret = mnl_talk(h, nlh, NULL, NULL); + if (ret < 0) + perror("mnl_talk:nft_chain_zero_counters"); + + if (chain != NULL) + break; +next: + c = nft_chain_list_iter_next(iter); + } + + nft_chain_list_iter_destroy(iter); + +err: + nft_chain_list_free(list); + + /* the core expects 1 for success and 0 for error */ + return ret == 0 ? 1 : 0; +} |