blob: bde4ca72d3fcc8555fb6c3034b1aeec68406d904 [file] [log] [blame]
/*
* (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 <assert.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 <linux/netfilter/xt_limit.h>
#include <libmnl/libmnl.h>
#include <libnftnl/gen.h>
#include <libnftnl/table.h>
#include <libnftnl/chain.h>
#include <libnftnl/rule.h>
#include <libnftnl/expr.h>
#include <libnftnl/set.h>
#include <libnftnl/udata.h>
#include <libnftnl/batch.h>
#include <netinet/in.h> /* inet_ntoa */
#include <arpa/inet.h>
#include "nft.h"
#include "xshared.h" /* proto_to_name */
#include "nft-cache.h"
#include "nft-shared.h"
#include "nft-bridge.h" /* EBT_NOPROTO */
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[32768];
if (mnl_socket_sendto(h->nl, nlh, nlh->nlmsg_len) < 0)
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;
}
#define NFT_NLMSG_MAXSIZE (UINT16_MAX + getpagesize())
/* 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 nftnl_batch *mnl_batch_init(void)
{
struct nftnl_batch *batch;
batch = nftnl_batch_alloc(BATCH_PAGE_SIZE, NFT_NLMSG_MAXSIZE);
if (batch == NULL)
return NULL;
return batch;
}
static void mnl_nft_batch_continue(struct nftnl_batch *batch)
{
assert(nftnl_batch_update(batch) >= 0);
}
static uint32_t mnl_batch_begin(struct nftnl_batch *batch, uint32_t genid, uint32_t seqnum)
{
struct nlmsghdr *nlh;
nlh = nftnl_batch_begin(nftnl_batch_buffer(batch), seqnum);
mnl_attr_put_u32(nlh, NFTA_GEN_ID, htonl(genid));
mnl_nft_batch_continue(batch);
return seqnum;
}
static void mnl_batch_end(struct nftnl_batch *batch, uint32_t seqnum)
{
nftnl_batch_end(nftnl_batch_buffer(batch), seqnum);
mnl_nft_batch_continue(batch);
}
static void mnl_batch_reset(struct nftnl_batch *batch)
{
nftnl_batch_free(batch);
}
struct mnl_err {
struct list_head head;
int err;
uint32_t seqnum;
};
static void mnl_err_list_node_add(struct list_head *err_list, int error,
int seqnum)
{
struct mnl_err *err = malloc(sizeof(struct mnl_err));
err->seqnum = seqnum;
err->err = error;
list_add_tail(&err->head, err_list);
}
static void mnl_err_list_free(struct mnl_err *err)
{
list_del(&err->head);
free(err);
}
static void mnl_set_sndbuffer(struct nft_handle *h)
{
int newbuffsiz = nftnl_batch_iovec_len(h->batch) * BATCH_PAGE_SIZE;
if (newbuffsiz <= h->nlsndbuffsiz)
return;
/* Rise sender buffer length to avoid hitting -EMSGSIZE */
if (setsockopt(mnl_socket_get_fd(h->nl), SOL_SOCKET, SO_SNDBUFFORCE,
&newbuffsiz, sizeof(socklen_t)) < 0)
return;
h->nlsndbuffsiz = newbuffsiz;
}
static void mnl_set_rcvbuffer(struct nft_handle *h, int numcmds)
{
int newbuffsiz = getpagesize() * numcmds;
if (newbuffsiz <= h->nlrcvbuffsiz)
return;
/* Rise receiver buffer length to avoid hitting -ENOBUFS */
if (setsockopt(mnl_socket_get_fd(h->nl), SOL_SOCKET, SO_RCVBUFFORCE,
&newbuffsiz, sizeof(socklen_t)) < 0)
return;
h->nlrcvbuffsiz = newbuffsiz;
}
static ssize_t mnl_nft_socket_sendmsg(struct nft_handle *h, int numcmds)
{
static const struct sockaddr_nl snl = {
.nl_family = AF_NETLINK
};
uint32_t iov_len = nftnl_batch_iovec_len(h->batch);
struct iovec iov[iov_len];
struct msghdr msg = {
.msg_name = (struct sockaddr *) &snl,
.msg_namelen = sizeof(snl),
.msg_iov = iov,
.msg_iovlen = iov_len,
};
mnl_set_sndbuffer(h);
mnl_set_rcvbuffer(h, numcmds);
nftnl_batch_iovec(h->batch, iov, iov_len);
return sendmsg(mnl_socket_get_fd(h->nl), &msg, 0);
}
static int mnl_batch_talk(struct nft_handle *h, int numcmds)
{
const struct mnl_socket *nl = h->nl;
int ret, fd = mnl_socket_get_fd(nl), portid = mnl_socket_get_portid(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, numcmds);
if (ret == -1)
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)
return -1;
while (ret > 0 && FD_ISSET(fd, &readfds)) {
struct nlmsghdr *nlh = (struct nlmsghdr *)rcv_buf;
ret = mnl_socket_recvfrom(nl, rcv_buf, sizeof(rcv_buf));
if (ret == -1)
return -1;
ret = mnl_cb_run(rcv_buf, ret, 0, portid, NULL, NULL);
/* Continue on error, make sure we get all acknowledgments */
if (ret == -1) {
mnl_err_list_node_add(&h->err_list, errno,
nlh->nlmsg_seq);
err = -1;
}
ret = select(fd+1, &readfds, NULL, NULL, &tv);
if (ret == -1)
return -1;
FD_ZERO(&readfds);
FD_SET(fd, &readfds);
}
return err;
}
enum obj_action {
NFT_COMPAT_COMMIT,
NFT_COMPAT_ABORT,
};
struct obj_update {
struct list_head head;
enum obj_update_type type:8;
uint8_t skip:1;
unsigned int seq;
union {
struct nftnl_table *table;
struct nftnl_chain *chain;
struct nftnl_rule *rule;
struct nftnl_set *set;
void *ptr;
};
struct {
unsigned int lineno;
} error;
};
static int mnl_append_error(const struct nft_handle *h,
const struct obj_update *o,
const struct mnl_err *err,
char *buf, unsigned int len)
{
static const char *type_name[] = {
[NFT_COMPAT_TABLE_ADD] = "TABLE_ADD",
[NFT_COMPAT_TABLE_FLUSH] = "TABLE_FLUSH",
[NFT_COMPAT_CHAIN_ADD] = "CHAIN_ADD",
[NFT_COMPAT_CHAIN_USER_ADD] = "CHAIN_USER_ADD",
[NFT_COMPAT_CHAIN_USER_DEL] = "CHAIN_USER_DEL",
[NFT_COMPAT_CHAIN_USER_FLUSH] = "CHAIN_USER_FLUSH",
[NFT_COMPAT_CHAIN_UPDATE] = "CHAIN_UPDATE",
[NFT_COMPAT_CHAIN_RENAME] = "CHAIN_RENAME",
[NFT_COMPAT_CHAIN_ZERO] = "CHAIN_ZERO",
[NFT_COMPAT_RULE_APPEND] = "RULE_APPEND",
[NFT_COMPAT_RULE_INSERT] = "RULE_INSERT",
[NFT_COMPAT_RULE_REPLACE] = "RULE_REPLACE",
[NFT_COMPAT_RULE_DELETE] = "RULE_DELETE",
[NFT_COMPAT_RULE_FLUSH] = "RULE_FLUSH",
[NFT_COMPAT_SET_ADD] = "SET_ADD",
};
char errmsg[256];
char tcr[128];
if (o->error.lineno)
snprintf(errmsg, sizeof(errmsg), "\nline %u: %s failed (%s)",
o->error.lineno, type_name[o->type], strerror(err->err));
else
snprintf(errmsg, sizeof(errmsg), " %s failed (%s)",
type_name[o->type], strerror(err->err));
switch (o->type) {
case NFT_COMPAT_TABLE_ADD:
case NFT_COMPAT_TABLE_FLUSH:
snprintf(tcr, sizeof(tcr), "table %s",
nftnl_table_get_str(o->table, NFTNL_TABLE_NAME));
break;
case NFT_COMPAT_CHAIN_ADD:
case NFT_COMPAT_CHAIN_ZERO:
case NFT_COMPAT_CHAIN_USER_ADD:
case NFT_COMPAT_CHAIN_USER_DEL:
case NFT_COMPAT_CHAIN_USER_FLUSH:
case NFT_COMPAT_CHAIN_UPDATE:
case NFT_COMPAT_CHAIN_RENAME:
snprintf(tcr, sizeof(tcr), "chain %s",
nftnl_chain_get_str(o->chain, NFTNL_CHAIN_NAME));
break;
case NFT_COMPAT_RULE_APPEND:
case NFT_COMPAT_RULE_INSERT:
case NFT_COMPAT_RULE_REPLACE:
case NFT_COMPAT_RULE_DELETE:
case NFT_COMPAT_RULE_FLUSH:
snprintf(tcr, sizeof(tcr), "rule in chain %s",
nftnl_rule_get_str(o->rule, NFTNL_RULE_CHAIN));
#if 0
{
nft_rule_print_save(h, o->rule, NFT_RULE_APPEND, FMT_NOCOUNTS);
}
#endif
break;
case NFT_COMPAT_SET_ADD:
snprintf(tcr, sizeof(tcr), "set %s",
nftnl_set_get_str(o->set, NFTNL_SET_NAME));
break;
case NFT_COMPAT_RULE_LIST:
case NFT_COMPAT_RULE_CHECK:
case NFT_COMPAT_CHAIN_RESTORE:
case NFT_COMPAT_RULE_SAVE:
case NFT_COMPAT_RULE_ZERO:
case NFT_COMPAT_BRIDGE_USER_CHAIN_UPDATE:
assert(0);
break;
}
return snprintf(buf, len, "%s: %s", errmsg, tcr);
}
static struct obj_update *batch_add(struct nft_handle *h, enum obj_update_type type, void *ptr)
{
struct obj_update *obj;
obj = calloc(1, sizeof(struct obj_update));
if (obj == NULL)
return NULL;
obj->ptr = ptr;
obj->error.lineno = h->error.lineno;
obj->type = type;
list_add_tail(&obj->head, &h->obj_list);
h->obj_list_num++;
return obj;
}
static struct obj_update *
batch_table_add(struct nft_handle *h, enum obj_update_type type,
struct nftnl_table *t)
{
return batch_add(h, type, t);
}
static struct obj_update *
batch_set_add(struct nft_handle *h, enum obj_update_type type,
struct nftnl_set *s)
{
return batch_add(h, type, s);
}
static struct obj_update *
batch_chain_add(struct nft_handle *h, enum obj_update_type type,
struct nftnl_chain *c)
{
return batch_add(h, type, c);
}
static struct obj_update *
batch_rule_add(struct nft_handle *h, enum obj_update_type type,
struct nftnl_rule *r)
{
return batch_add(h, type, r);
}
static void batch_obj_del(struct nft_handle *h, struct obj_update *o);
static void batch_chain_flush(struct nft_handle *h,
const char *table, const char *chain)
{
struct obj_update *obj, *tmp;
list_for_each_entry_safe(obj, tmp, &h->obj_list, head) {
struct nftnl_rule *r = obj->ptr;
switch (obj->type) {
case NFT_COMPAT_RULE_APPEND:
case NFT_COMPAT_RULE_INSERT:
case NFT_COMPAT_RULE_REPLACE:
case NFT_COMPAT_RULE_DELETE:
break;
default:
continue;
}
if (table &&
strcmp(table, nftnl_rule_get_str(r, NFTNL_RULE_TABLE)))
continue;
if (chain &&
strcmp(chain, nftnl_rule_get_str(r, NFTNL_RULE_CHAIN)))
continue;
batch_obj_del(h, obj);
}
}
const struct builtin_table xtables_ipv4[NFT_TABLE_MAX] = {
[NFT_TABLE_RAW] = {
.name = "raw",
.type = NFT_TABLE_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,
},
},
},
[NFT_TABLE_MANGLE] = {
.name = "mangle",
.type = NFT_TABLE_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,
},
},
},
[NFT_TABLE_FILTER] = {
.name = "filter",
.type = NFT_TABLE_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,
},
},
},
[NFT_TABLE_SECURITY] = {
.name = "security",
.type = NFT_TABLE_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,
},
},
},
[NFT_TABLE_NAT] = {
.name = "nat",
.type = NFT_TABLE_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>
const struct builtin_table xtables_arp[NFT_TABLE_MAX] = {
[NFT_TABLE_FILTER] = {
.name = "filter",
.type = NFT_TABLE_FILTER,
.chains = {
{
.name = "INPUT",
.type = "filter",
.prio = NF_IP_PRI_FILTER,
.hook = NF_ARP_IN,
},
{
.name = "OUTPUT",
.type = "filter",
.prio = NF_IP_PRI_FILTER,
.hook = NF_ARP_OUT,
},
},
},
};
#include <linux/netfilter_bridge.h>
const struct builtin_table xtables_bridge[NFT_TABLE_MAX] = {
[NFT_TABLE_FILTER] = {
.name = "filter",
.type = NFT_TABLE_FILTER,
.chains = {
{
.name = "INPUT",
.type = "filter",
.prio = NF_BR_PRI_FILTER_BRIDGED,
.hook = NF_BR_LOCAL_IN,
},
{
.name = "FORWARD",
.type = "filter",
.prio = NF_BR_PRI_FILTER_BRIDGED,
.hook = NF_BR_FORWARD,
},
{
.name = "OUTPUT",
.type = "filter",
.prio = NF_BR_PRI_FILTER_BRIDGED,
.hook = NF_BR_LOCAL_OUT,
},
},
},
[NFT_TABLE_NAT] = {
.name = "nat",
.type = NFT_TABLE_NAT,
.chains = {
{
.name = "PREROUTING",
.type = "filter",
.prio = NF_BR_PRI_NAT_DST_BRIDGED,
.hook = NF_BR_PRE_ROUTING,
},
{
.name = "OUTPUT",
.type = "filter",
.prio = NF_BR_PRI_NAT_DST_OTHER,
.hook = NF_BR_LOCAL_OUT,
},
{
.name = "POSTROUTING",
.type = "filter",
.prio = NF_BR_PRI_NAT_SRC,
.hook = NF_BR_POST_ROUTING,
},
},
},
};
static int nft_table_builtin_add(struct nft_handle *h,
const struct builtin_table *_t)
{
struct nftnl_table *t;
int ret;
if (h->cache->table[_t->type].exists)
return 0;
t = nftnl_table_alloc();
if (t == NULL)
return -1;
nftnl_table_set_str(t, NFTNL_TABLE_NAME, _t->name);
ret = batch_table_add(h, NFT_COMPAT_TABLE_ADD, t) ? 0 : - 1;
return ret;
}
static struct nftnl_chain *
nft_chain_builtin_alloc(const struct builtin_table *table,
const struct builtin_chain *chain, int policy)
{
struct nftnl_chain *c;
c = nftnl_chain_alloc();
if (c == NULL)
return NULL;
nftnl_chain_set_str(c, NFTNL_CHAIN_TABLE, table->name);
nftnl_chain_set_str(c, NFTNL_CHAIN_NAME, chain->name);
nftnl_chain_set_u32(c, NFTNL_CHAIN_HOOKNUM, chain->hook);
nftnl_chain_set_u32(c, NFTNL_CHAIN_PRIO, chain->prio);
if (policy >= 0)
nftnl_chain_set_u32(c, NFTNL_CHAIN_POLICY, policy);
nftnl_chain_set_str(c, NFTNL_CHAIN_TYPE, chain->type);
return c;
}
static void nft_chain_builtin_add(struct nft_handle *h,
const struct builtin_table *table,
const struct builtin_chain *chain,
bool fake)
{
struct nftnl_chain *c;
c = nft_chain_builtin_alloc(table, chain, NF_ACCEPT);
if (c == NULL)
return;
if (!fake)
batch_chain_add(h, NFT_COMPAT_CHAIN_ADD, c);
nft_cache_add_chain(h, table, c);
}
/* find if built-in table already exists */
const struct builtin_table *
nft_table_builtin_find(struct nft_handle *h, const char *table)
{
int i;
bool found = false;
for (i = 0; i < NFT_TABLE_MAX; i++) {
if (h->tables[i].name == NULL)
continue;
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 */
const struct builtin_chain *
nft_chain_builtin_find(const 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,
const struct builtin_table *table)
{
int i;
/* Initialize built-in chains if they don't exist yet */
for (i=0; i < NF_INET_NUMHOOKS && table->chains[i].name != NULL; i++) {
if (nft_chain_find(h, table->name, table->chains[i].name))
continue;
nft_chain_builtin_add(h, table, &table->chains[i], false);
}
}
static const struct builtin_table *
nft_xt_builtin_table_init(struct nft_handle *h, const char *table)
{
const struct builtin_table *t;
if (!h->cache_init)
return NULL;
t = nft_table_builtin_find(h, table);
if (t == NULL)
return NULL;
if (nft_table_builtin_add(h, t) < 0)
return NULL;
return t;
}
static int nft_xt_builtin_init(struct nft_handle *h, const char *table,
const char *chain)
{
const struct builtin_table *t;
const struct builtin_chain *c;
if (!h->cache_init)
return 0;
t = nft_xt_builtin_table_init(h, table);
if (!t)
return -1;
if (h->cache_req.level < NFT_CL_CHAINS)
return 0;
if (!chain) {
nft_chain_builtin_init(h, t);
return 0;
}
c = nft_chain_builtin_find(t, chain);
if (!c)
return -1;
if (h->cache->table[t->type].base_chains[c->hook])
return 0;
nft_chain_builtin_add(h, t, c, false);
return 0;
}
static bool nft_chain_builtin(struct nftnl_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 nftnl_chain_get(c, NFTNL_CHAIN_HOOKNUM) != NULL;
}
static int __nft_xt_fake_builtin_chains(struct nft_handle *h,
const char *table, void *data)
{
const char *chain = data ? *(const char **)data : NULL;
const struct builtin_table *t;
struct nft_chain **bcp;
int i;
t = nft_table_builtin_find(h, table);
if (!t)
return -1;
bcp = h->cache->table[t->type].base_chains;
for (i = 0; i < NF_INET_NUMHOOKS && t->chains[i].name; i++) {
if (bcp[t->chains[i].hook])
continue;
if (chain && strcmp(chain, t->chains[i].name))
continue;
nft_chain_builtin_add(h, t, &t->chains[i], true);
}
return 0;
}
int nft_xt_fake_builtin_chains(struct nft_handle *h,
const char *table, const char *chain)
{
if (table)
return __nft_xt_fake_builtin_chains(h, table, &chain);
return nft_for_each_table(h, __nft_xt_fake_builtin_chains, &chain);
}
int nft_restart(struct nft_handle *h)
{
mnl_socket_close(h->nl);
h->nl = mnl_socket_open(NETLINK_NETFILTER);
if (h->nl == NULL)
return -1;
if (mnl_socket_bind(h->nl, 0, MNL_SOCKET_AUTOPID) < 0)
return -1;
h->portid = mnl_socket_get_portid(h->nl);
h->nlsndbuffsiz = 0;
h->nlrcvbuffsiz = 0;
return 0;
}
int nft_init(struct nft_handle *h, int family, const struct builtin_table *t)
{
memset(h, 0, sizeof(*h));
h->nl = mnl_socket_open(NETLINK_NETFILTER);
if (h->nl == NULL)
return -1;
if (mnl_socket_bind(h->nl, 0, MNL_SOCKET_AUTOPID) < 0) {
mnl_socket_close(h->nl);
return -1;
}
h->ops = nft_family_ops_lookup(family);
if (!h->ops)
xtables_error(PARAMETER_PROBLEM, "Unknown family");
h->portid = mnl_socket_get_portid(h->nl);
h->tables = t;
h->cache = &h->__cache[0];
h->family = family;
INIT_LIST_HEAD(&h->obj_list);
INIT_LIST_HEAD(&h->err_list);
INIT_LIST_HEAD(&h->cmd_list);
INIT_LIST_HEAD(&h->cache_req.chain_list);
return 0;
}
void nft_fini(struct nft_handle *h)
{
struct list_head *pos, *n;
list_for_each_safe(pos, n, &h->cmd_list)
nft_cmd_free(list_entry(pos, struct nft_cmd, head));
list_for_each_safe(pos, n, &h->obj_list)
batch_obj_del(h, list_entry(pos, struct obj_update, head));
list_for_each_safe(pos, n, &h->err_list)
mnl_err_list_free(list_entry(pos, struct mnl_err, head));
nft_release_cache(h);
mnl_socket_close(h->nl);
}
static void nft_chain_print_debug(struct nftnl_chain *c, struct nlmsghdr *nlh)
{
#ifdef NLDEBUG
char tmp[1024];
nftnl_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 struct nftnl_chain *nft_chain_new(struct nft_handle *h,
const char *table, const char *chain,
int policy,
const struct xt_counters *counters)
{
struct nftnl_chain *c;
const struct builtin_table *_t;
const struct builtin_chain *_c;
_t = nft_table_builtin_find(h, table);
if (!_t) {
errno = ENXIO;
return NULL;
}
/* if this built-in table does not exists, create it */
nft_xt_builtin_init(h, table, chain);
_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 NULL;
} else {
errno = ENOENT;
return NULL;
}
if (counters) {
nftnl_chain_set_u64(c, NFTNL_CHAIN_BYTES,
counters->bcnt);
nftnl_chain_set_u64(c, NFTNL_CHAIN_PACKETS,
counters->pcnt);
}
return c;
}
int nft_chain_set(struct nft_handle *h, const char *table,
const char *chain, const char *policy,
const struct xt_counters *counters)
{
struct nftnl_chain *c = NULL;
nft_fn = nft_chain_set;
if (strcmp(policy, "DROP") == 0)
c = nft_chain_new(h, table, chain, NF_DROP, counters);
else if (strcmp(policy, "ACCEPT") == 0)
c = nft_chain_new(h, table, chain, NF_ACCEPT, counters);
else if (strcmp(policy, "-") == 0)
c = nft_chain_new(h, table, chain, -1, counters);
else
errno = EINVAL;
if (c == NULL)
return 0;
if (!batch_chain_add(h, NFT_COMPAT_CHAIN_UPDATE, c))
return 0;
/* the core expects 1 for success and 0 for error */
return 1;
}
static int __add_match(struct nftnl_expr *e, struct xt_entry_match *m)
{
void *info;
nftnl_expr_set(e, NFTNL_EXPR_MT_NAME, m->u.user.name, strlen(m->u.user.name));
nftnl_expr_set_u32(e, NFTNL_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));
nftnl_expr_set(e, NFTNL_EXPR_MT_INFO, info, m->u.match_size - sizeof(*m));
return 0;
}
static int add_nft_limit(struct nftnl_rule *r, struct xt_entry_match *m)
{
struct xt_rateinfo *rinfo = (void *)m->data;
static const uint32_t mult[] = {
XT_LIMIT_SCALE*24*60*60, /* day */
XT_LIMIT_SCALE*60*60, /* hour */
XT_LIMIT_SCALE*60, /* min */
XT_LIMIT_SCALE, /* sec */
};
struct nftnl_expr *expr;
int i;
expr = nftnl_expr_alloc("limit");
if (!expr)
return -ENOMEM;
for (i = 1; i < ARRAY_SIZE(mult); i++) {
if (rinfo->avg > mult[i] ||
mult[i] / rinfo->avg < mult[i] % rinfo->avg)
break;
}
nftnl_expr_set_u32(expr, NFTNL_EXPR_LIMIT_TYPE, NFT_LIMIT_PKTS);
nftnl_expr_set_u32(expr, NFTNL_EXPR_LIMIT_FLAGS, 0);
nftnl_expr_set_u64(expr, NFTNL_EXPR_LIMIT_RATE,
mult[i - 1] / rinfo->avg);
nftnl_expr_set_u64(expr, NFTNL_EXPR_LIMIT_UNIT,
mult[i - 1] / XT_LIMIT_SCALE);
nftnl_expr_set_u32(expr, NFTNL_EXPR_LIMIT_BURST, rinfo->burst);
nftnl_rule_add_expr(r, expr);
return 0;
}
static struct nftnl_set *add_anon_set(struct nft_handle *h, const char *table,
uint32_t flags, uint32_t key_type,
uint32_t key_len, uint32_t size)
{
static uint32_t set_id = 0;
struct nftnl_set *s;
struct nft_cmd *cmd;
s = nftnl_set_alloc();
if (!s)
return NULL;
nftnl_set_set_u32(s, NFTNL_SET_FAMILY, h->family);
nftnl_set_set_str(s, NFTNL_SET_TABLE, table);
nftnl_set_set_str(s, NFTNL_SET_NAME, "__set%d");
nftnl_set_set_u32(s, NFTNL_SET_ID, ++set_id);
nftnl_set_set_u32(s, NFTNL_SET_FLAGS,
NFT_SET_ANONYMOUS | NFT_SET_CONSTANT | flags);
nftnl_set_set_u32(s, NFTNL_SET_KEY_TYPE, key_type);
nftnl_set_set_u32(s, NFTNL_SET_KEY_LEN, key_len);
nftnl_set_set_u32(s, NFTNL_SET_DESC_SIZE, size);
cmd = nft_cmd_new(h, NFT_COMPAT_SET_ADD, table, NULL, NULL, -1, false);
if (!cmd) {
nftnl_set_free(s);
return NULL;
}
cmd->obj.set = s;
return s;
}
static struct nftnl_expr *
gen_payload(uint32_t base, uint32_t offset, uint32_t len, uint32_t dreg)
{
struct nftnl_expr *e = nftnl_expr_alloc("payload");
if (!e)
return NULL;
nftnl_expr_set_u32(e, NFTNL_EXPR_PAYLOAD_BASE, base);
nftnl_expr_set_u32(e, NFTNL_EXPR_PAYLOAD_OFFSET, offset);
nftnl_expr_set_u32(e, NFTNL_EXPR_PAYLOAD_LEN, len);
nftnl_expr_set_u32(e, NFTNL_EXPR_PAYLOAD_DREG, dreg);
return e;
}
static struct nftnl_expr *
gen_lookup(uint32_t sreg, const char *set_name, uint32_t set_id, uint32_t flags)
{
struct nftnl_expr *e = nftnl_expr_alloc("lookup");
if (!e)
return NULL;
nftnl_expr_set_u32(e, NFTNL_EXPR_LOOKUP_SREG, sreg);
nftnl_expr_set_str(e, NFTNL_EXPR_LOOKUP_SET, set_name);
nftnl_expr_set_u32(e, NFTNL_EXPR_LOOKUP_SET_ID, set_id);
nftnl_expr_set_u32(e, NFTNL_EXPR_LOOKUP_FLAGS, flags);
return e;
}
/* simplified nftables:include/netlink.h, netlink_padded_len() */
#define NETLINK_ALIGN 4
/* from nftables:include/datatype.h, TYPE_BITS */
#define CONCAT_TYPE_BITS 6
/* from nftables:include/datatype.h, enum datatypes */
#define NFT_DATATYPE_IPADDR 7
#define NFT_DATATYPE_ETHERADDR 9
static int __add_nft_among(struct nft_handle *h, const char *table,
struct nftnl_rule *r, struct nft_among_pair *pairs,
int cnt, bool dst, bool inv, bool ip)
{
uint32_t set_id, type = NFT_DATATYPE_ETHERADDR, len = ETH_ALEN;
/* { !dst, dst } */
static const int eth_addr_off[] = {
offsetof(struct ether_header, ether_shost),
offsetof(struct ether_header, ether_dhost)
};
static const int ip_addr_off[] = {
offsetof(struct iphdr, saddr),
offsetof(struct iphdr, daddr)
};
struct nftnl_expr *e;
struct nftnl_set *s;
uint32_t flags = 0;
int idx = 0;
if (ip) {
type = type << CONCAT_TYPE_BITS | NFT_DATATYPE_IPADDR;
len += sizeof(struct in_addr) + NETLINK_ALIGN - 1;
len &= ~(NETLINK_ALIGN - 1);
flags = NFT_SET_INTERVAL;
}
s = add_anon_set(h, table, flags, type, len, cnt);
if (!s)
return -ENOMEM;
set_id = nftnl_set_get_u32(s, NFTNL_SET_ID);
if (ip) {
uint8_t field_len[2] = { ETH_ALEN, sizeof(struct in_addr) };
nftnl_set_set_data(s, NFTNL_SET_DESC_CONCAT,
field_len, sizeof(field_len));
}
for (idx = 0; idx < cnt; idx++) {
struct nftnl_set_elem *elem = nftnl_set_elem_alloc();
if (!elem)
return -ENOMEM;
nftnl_set_elem_set(elem, NFTNL_SET_ELEM_KEY,
&pairs[idx], len);
if (ip) {
struct in_addr tmp = pairs[idx].in;
if (tmp.s_addr == INADDR_ANY)
pairs[idx].in.s_addr = INADDR_BROADCAST;
nftnl_set_elem_set(elem, NFTNL_SET_ELEM_KEY_END,
&pairs[idx], len);
pairs[idx].in = tmp;
}
nftnl_set_elem_add(s, elem);
}
e = gen_payload(NFT_PAYLOAD_LL_HEADER,
eth_addr_off[dst], ETH_ALEN, NFT_REG_1);
if (!e)
return -ENOMEM;
nftnl_rule_add_expr(r, e);
if (ip) {
e = gen_payload(NFT_PAYLOAD_NETWORK_HEADER, ip_addr_off[dst],
sizeof(struct in_addr), NFT_REG32_02);
if (!e)
return -ENOMEM;
nftnl_rule_add_expr(r, e);
}
e = gen_lookup(NFT_REG_1, "__set%d", set_id, inv);
if (!e)
return -ENOMEM;
nftnl_rule_add_expr(r, e);
return 0;
}
static int add_nft_among(struct nft_handle *h,
struct nftnl_rule *r, struct xt_entry_match *m)
{
struct nft_among_data *data = (struct nft_among_data *)m->data;
const char *table = nftnl_rule_get(r, NFTNL_RULE_TABLE);
if ((data->src.cnt && data->src.ip) ||
(data->dst.cnt && data->dst.ip)) {
uint16_t eth_p_ip = htons(ETH_P_IP);
add_meta(r, NFT_META_PROTOCOL);
add_cmp_ptr(r, NFT_CMP_EQ, &eth_p_ip, 2);
}
if (data->src.cnt)
__add_nft_among(h, table, r, data->pairs, data->src.cnt,
false, data->src.inv, data->src.ip);
if (data->dst.cnt)
__add_nft_among(h, table, r, data->pairs + data->src.cnt,
data->dst.cnt, true, data->dst.inv,
data->dst.ip);
return 0;
}
int add_match(struct nft_handle *h,
struct nftnl_rule *r, struct xt_entry_match *m)
{
struct nftnl_expr *expr;
int ret;
if (!strcmp(m->u.user.name, "limit"))
return add_nft_limit(r, m);
else if (!strcmp(m->u.user.name, "among"))
return add_nft_among(h, r, m);
expr = nftnl_expr_alloc("match");
if (expr == NULL)
return -ENOMEM;
ret = __add_match(expr, m);
nftnl_rule_add_expr(r, expr);
return ret;
}
static int __add_target(struct nftnl_expr *e, struct xt_entry_target *t)
{
void *info;
nftnl_expr_set(e, NFTNL_EXPR_TG_NAME, t->u.user.name,
strlen(t->u.user.name));
nftnl_expr_set_u32(e, NFTNL_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));
nftnl_expr_set(e, NFTNL_EXPR_TG_INFO, info, t->u.target_size - sizeof(*t));
return 0;
}
static int add_meta_nftrace(struct nftnl_rule *r)
{
struct nftnl_expr *expr;
expr = nftnl_expr_alloc("immediate");
if (expr == NULL)
return -ENOMEM;
nftnl_expr_set_u32(expr, NFTNL_EXPR_IMM_DREG, NFT_REG32_01);
nftnl_expr_set_u8(expr, NFTNL_EXPR_IMM_DATA, 1);
nftnl_rule_add_expr(r, expr);
expr = nftnl_expr_alloc("meta");
if (expr == NULL)
return -ENOMEM;
nftnl_expr_set_u32(expr, NFTNL_EXPR_META_KEY, NFT_META_NFTRACE);
nftnl_expr_set_u32(expr, NFTNL_EXPR_META_SREG, NFT_REG32_01);
nftnl_rule_add_expr(r, expr);
return 0;
}
int add_target(struct nftnl_rule *r, struct xt_entry_target *t)
{
struct nftnl_expr *expr;
int ret;
if (strcmp(t->u.user.name, "TRACE") == 0)
return add_meta_nftrace(r);
expr = nftnl_expr_alloc("target");
if (expr == NULL)
return -ENOMEM;
ret = __add_target(expr, t);
nftnl_rule_add_expr(r, expr);
return ret;
}
int add_jumpto(struct nftnl_rule *r, const char *name, int verdict)
{
struct nftnl_expr *expr;
expr = nftnl_expr_alloc("immediate");
if (expr == NULL)
return -ENOMEM;
nftnl_expr_set_u32(expr, NFTNL_EXPR_IMM_DREG, NFT_REG_VERDICT);
nftnl_expr_set_u32(expr, NFTNL_EXPR_IMM_VERDICT, verdict);
nftnl_expr_set_str(expr, NFTNL_EXPR_IMM_CHAIN, (char *)name);
nftnl_rule_add_expr(r, expr);
return 0;
}
int add_verdict(struct nftnl_rule *r, int verdict)
{
struct nftnl_expr *expr;
expr = nftnl_expr_alloc("immediate");
if (expr == NULL)
return -ENOMEM;
nftnl_expr_set_u32(expr, NFTNL_EXPR_IMM_DREG, NFT_REG_VERDICT);
nftnl_expr_set_u32(expr, NFTNL_EXPR_IMM_VERDICT, verdict);
nftnl_rule_add_expr(r, expr);
return 0;
}
int add_action(struct nftnl_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 nftnl_rule *r, struct nlmsghdr *nlh)
{
#ifdef NLDEBUG
char tmp[1024];
nftnl_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 nftnl_rule *r, uint64_t packets, uint64_t bytes)
{
struct nftnl_expr *expr;
expr = nftnl_expr_alloc("counter");
if (expr == NULL)
return -ENOMEM;
nftnl_expr_set_u64(expr, NFTNL_EXPR_CTR_PACKETS, packets);
nftnl_expr_set_u64(expr, NFTNL_EXPR_CTR_BYTES, bytes);
nftnl_rule_add_expr(r, expr);
return 0;
}
enum udata_type {
UDATA_TYPE_COMMENT,
UDATA_TYPE_EBTABLES_POLICY,
__UDATA_TYPE_MAX,
};
#define UDATA_TYPE_MAX (__UDATA_TYPE_MAX - 1)
static int parse_udata_cb(const struct nftnl_udata *attr, void *data)
{
unsigned char *value = nftnl_udata_get(attr);
uint8_t type = nftnl_udata_type(attr);
uint8_t len = nftnl_udata_len(attr);
const struct nftnl_udata **tb = data;
switch (type) {
case UDATA_TYPE_COMMENT:
if (value[len - 1] != '\0')
return -1;
break;
case UDATA_TYPE_EBTABLES_POLICY:
break;
default:
return 0;
}
tb[type] = attr;
return 0;
}
char *get_comment(const void *data, uint32_t data_len)
{
const struct nftnl_udata *tb[UDATA_TYPE_MAX + 1] = {};
if (nftnl_udata_parse(data, data_len, parse_udata_cb, tb) < 0)
return NULL;
if (!tb[UDATA_TYPE_COMMENT])
return NULL;
return nftnl_udata_get(tb[UDATA_TYPE_COMMENT]);
}
void add_compat(struct nftnl_rule *r, uint32_t proto, bool inv)
{
nftnl_rule_set_u32(r, NFTNL_RULE_COMPAT_PROTO, proto);
nftnl_rule_set_u32(r, NFTNL_RULE_COMPAT_FLAGS,
inv ? NFT_RULE_COMPAT_F_INV : 0);
}
struct nftnl_rule *
nft_rule_new(struct nft_handle *h, const char *chain, const char *table,
void *data)
{
struct nftnl_rule *r;
r = nftnl_rule_alloc();
if (r == NULL)
return NULL;
nftnl_rule_set_u32(r, NFTNL_RULE_FAMILY, h->family);
nftnl_rule_set_str(r, NFTNL_RULE_TABLE, table);
nftnl_rule_set_str(r, NFTNL_RULE_CHAIN, chain);
if (h->ops->add(h, r, data) < 0)
goto err;
return r;
err:
nftnl_rule_free(r);
return NULL;
}
int
nft_rule_append(struct nft_handle *h, const char *chain, const char *table,
struct nftnl_rule *r, struct nftnl_rule *ref, bool verbose)
{
struct nft_chain *c;
int type;
nft_xt_builtin_init(h, table, chain);
nft_fn = nft_rule_append;
if (ref) {
nftnl_rule_set_u64(r, NFTNL_RULE_HANDLE,
nftnl_rule_get_u64(ref, NFTNL_RULE_HANDLE));
type = NFT_COMPAT_RULE_REPLACE;
} else
type = NFT_COMPAT_RULE_APPEND;
if (batch_rule_add(h, type, r) == NULL)
return 0;
if (verbose)
h->ops->print_rule(h, r, 0, FMT_PRINT_RULE);
if (ref) {
nftnl_chain_rule_insert_at(r, ref);
nftnl_chain_rule_del(ref);
nftnl_rule_free(ref);
} else {
c = nft_chain_find(h, table, chain);
if (!c) {
errno = ENOENT;
return 0;
}
nftnl_chain_rule_add_tail(r, c->nftnl);
}
return 1;
}
void
nft_rule_print_save(struct nft_handle *h, const struct nftnl_rule *r,
enum nft_rule_print type, unsigned int format)
{
const char *chain = nftnl_rule_get_str(r, NFTNL_RULE_CHAIN);
struct iptables_command_state cs = {};
struct nft_family_ops *ops = h->ops;
ops->rule_to_cs(h, r, &cs);
if (!(format & (FMT_NOCOUNTS | FMT_C_COUNTS)))
printf("[%llu:%llu] ", (unsigned long long)cs.counters.pcnt,
(unsigned long long)cs.counters.bcnt);
/* print chain name */
switch(type) {
case NFT_RULE_APPEND:
printf("-A %s ", chain);
break;
case NFT_RULE_DEL:
printf("-D %s ", chain);
break;
}
if (ops->save_rule)
ops->save_rule(&cs, format);
if (ops->clear_cs)
ops->clear_cs(&cs);
}
static bool nft_rule_is_policy_rule(struct nftnl_rule *r)
{
const struct nftnl_udata *tb[UDATA_TYPE_MAX + 1] = {};
const void *data;
uint32_t len;
if (!nftnl_rule_is_set(r, NFTNL_RULE_USERDATA))
return false;
data = nftnl_rule_get_data(r, NFTNL_RULE_USERDATA, &len);
if (nftnl_udata_parse(data, len, parse_udata_cb, tb) < 0)
return NULL;
if (!tb[UDATA_TYPE_EBTABLES_POLICY] ||
nftnl_udata_get_u32(tb[UDATA_TYPE_EBTABLES_POLICY]) != 1)
return false;
return true;
}
static struct nftnl_rule *nft_chain_last_rule(struct nftnl_chain *c)
{
struct nftnl_rule *r = NULL, *last;
struct nftnl_rule_iter *iter;
iter = nftnl_rule_iter_create(c);
if (!iter)
return NULL;
do {
last = r;
r = nftnl_rule_iter_next(iter);
} while (r);
nftnl_rule_iter_destroy(iter);
return last;
}
void nft_bridge_chain_postprocess(struct nft_handle *h,
struct nftnl_chain *c)
{
struct nftnl_rule *last = nft_chain_last_rule(c);
struct nftnl_expr_iter *iter;
struct nftnl_expr *expr;
int verdict;
if (!last || !nft_rule_is_policy_rule(last))
return;
iter = nftnl_expr_iter_create(last);
if (!iter)
return;
expr = nftnl_expr_iter_next(iter);
if (!expr ||
strcmp("counter", nftnl_expr_get_str(expr, NFTNL_EXPR_NAME)))
goto out_iter;
expr = nftnl_expr_iter_next(iter);
if (!expr ||
strcmp("immediate", nftnl_expr_get_str(expr, NFTNL_EXPR_NAME)) ||
!nftnl_expr_is_set(expr, NFTNL_EXPR_IMM_VERDICT))
goto out_iter;
verdict = nftnl_expr_get_u32(expr, NFTNL_EXPR_IMM_VERDICT);
switch (verdict) {
case NF_ACCEPT:
case NF_DROP:
break;
default:
goto out_iter;
}
nftnl_chain_set_u32(c, NFTNL_CHAIN_POLICY, verdict);
if (batch_rule_add(h, NFT_COMPAT_RULE_DELETE, last) == NULL)
fprintf(stderr, "Failed to delete old policy rule\n");
nftnl_chain_rule_del(last);
out_iter:
nftnl_expr_iter_destroy(iter);
}
static const char *policy_name[NF_ACCEPT+1] = {
[NF_DROP] = "DROP",
[NF_ACCEPT] = "ACCEPT",
};
int nft_chain_save(struct nft_chain *nc, void *data)
{
struct nftnl_chain *c = nc->nftnl;
struct nft_handle *h = data;
const char *policy = NULL;
if (nftnl_chain_is_set(c, NFTNL_CHAIN_POLICY)) {
policy = policy_name[nftnl_chain_get_u32(c, NFTNL_CHAIN_POLICY)];
} else if (nft_chain_builtin(c)) {
policy = "ACCEPT";
} else if (h->family == NFPROTO_BRIDGE) {
policy = "RETURN";
}
if (h->ops->save_chain)
h->ops->save_chain(c, policy);
return 0;
}
struct nft_rule_save_data {
struct nft_handle *h;
unsigned int format;
};
static int nft_rule_save_cb(struct nft_chain *c, void *data)
{
struct nft_rule_save_data *d = data;
struct nftnl_rule_iter *iter;
struct nftnl_rule *r;
iter = nftnl_rule_iter_create(c->nftnl);
if (iter == NULL)
return 1;
r = nftnl_rule_iter_next(iter);
while (r != NULL) {
nft_rule_print_save(d->h, r, NFT_RULE_APPEND, d->format);
r = nftnl_rule_iter_next(iter);
}
nftnl_rule_iter_destroy(iter);
return 0;
}
int nft_rule_save(struct nft_handle *h, const char *table, unsigned int format)
{
struct nft_rule_save_data d = {
.h = h,
.format = format,
};
int ret;
ret = nft_chain_foreach(h, table, nft_rule_save_cb, &d);
/* the core expects 1 for success and 0 for error */
return ret == 0 ? 1 : 0;
}
struct nftnl_set *nft_set_batch_lookup_byid(struct nft_handle *h,
uint32_t set_id)
{
struct obj_update *n;
list_for_each_entry(n, &h->obj_list, head) {
if (n->type == NFT_COMPAT_SET_ADD &&
nftnl_set_get_u32(n->set, NFTNL_SET_ID) == set_id)
return n->set;
}
return NULL;
}
static void
__nft_rule_flush(struct nft_handle *h, const char *table,
const char *chain, bool verbose, bool skip)
{
struct obj_update *obj;
struct nftnl_rule *r;
if (verbose && chain)
fprintf(stdout, "Flushing chain `%s'\n", chain);
r = nftnl_rule_alloc();
if (r == NULL)
return;
nftnl_rule_set_str(r, NFTNL_RULE_TABLE, table);
if (chain)
nftnl_rule_set_str(r, NFTNL_RULE_CHAIN, chain);
obj = batch_rule_add(h, NFT_COMPAT_RULE_FLUSH, r);
if (!obj) {
nftnl_rule_free(r);
return;
}
obj->skip = skip;
}
struct nft_rule_flush_data {
struct nft_handle *h;
const char *table;
bool verbose;
};
static int nft_rule_flush_cb(struct nft_chain *c, void *data)
{
const char *chain = nftnl_chain_get_str(c->nftnl, NFTNL_CHAIN_NAME);
struct nft_rule_flush_data *d = data;
batch_chain_flush(d->h, d->table, chain);
__nft_rule_flush(d->h, d->table, chain, d->verbose, false);
flush_rule_cache(d->h, d->table, c);
return 0;
}
int nft_rule_flush(struct nft_handle *h, const char *chain, const char *table,
bool verbose)
{
struct nft_rule_flush_data d = {
.h = h,
.table = table,
.verbose = verbose,
};
struct nft_chain *c = NULL;
int ret = 0;
nft_fn = nft_rule_flush;
if (chain || verbose)
nft_xt_builtin_init(h, table, chain);
else if (!nft_table_find(h, table))
return 1;
if (chain) {
c = nft_chain_find(h, table, chain);
if (!c) {
errno = ENOENT;
return 0;
}
}
if (chain || !verbose) {
batch_chain_flush(h, table, chain);
__nft_rule_flush(h, table, chain, verbose, false);
flush_rule_cache(h, table, c);
return 1;
}
ret = nft_chain_foreach(h, table, nft_rule_flush_cb, &d);
/* 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)
{
const struct builtin_table *t;
struct nftnl_chain *c;
nft_fn = nft_chain_user_add;
t = nft_xt_builtin_table_init(h, table);
if (nft_chain_exists(h, table, chain)) {
errno = EEXIST;
return 0;
}
c = nftnl_chain_alloc();
if (c == NULL)
return 0;
nftnl_chain_set_str(c, NFTNL_CHAIN_TABLE, table);
nftnl_chain_set_str(c, NFTNL_CHAIN_NAME, chain);
if (h->family == NFPROTO_BRIDGE)
nftnl_chain_set_u32(c, NFTNL_CHAIN_POLICY, NF_ACCEPT);
if (!batch_chain_add(h, NFT_COMPAT_CHAIN_USER_ADD, c))
return 0;
nft_cache_add_chain(h, t, c);
/* the core expects 1 for success and 0 for error */
return 1;
}
int nft_chain_restore(struct nft_handle *h, const char *chain, const char *table)
{
const struct builtin_table *t;
struct obj_update *obj;
struct nftnl_chain *c;
struct nft_chain *nc;
bool created = false;
t = nft_xt_builtin_table_init(h, table);
nc = nft_chain_find(h, table, chain);
if (!nc) {
c = nftnl_chain_alloc();
if (!c)
return 0;
nftnl_chain_set_str(c, NFTNL_CHAIN_TABLE, table);
nftnl_chain_set_str(c, NFTNL_CHAIN_NAME, chain);
created = true;
nft_cache_add_chain(h, t, c);
} else {
c = nc->nftnl;
/* If the chain should vanish meanwhile, kernel genid changes
* and the transaction is refreshed enabling the chain add
* object. With the handle still set, kernel interprets it as a
* chain replace job and errors since it is not found anymore.
*/
nftnl_chain_unset(c, NFTNL_CHAIN_HANDLE);
}
__nft_rule_flush(h, table, chain, false, created);
obj = batch_chain_add(h, NFT_COMPAT_CHAIN_USER_ADD, c);
if (!obj)
return 0;
obj->skip = !created;
/* the core expects 1 for success and 0 for error */
return 1;
}
/* From linux/netlink.h */
#ifndef NLM_F_NONREC
#define NLM_F_NONREC 0x100 /* Do not delete recursively */
#endif
struct chain_user_del_data {
struct nft_handle *handle;
bool verbose;
int builtin_err;
};
static int __nft_chain_user_del(struct nft_chain *nc, void *data)
{
struct chain_user_del_data *d = data;
struct nftnl_chain *c = nc->nftnl;
struct nft_handle *h = d->handle;
/* don't delete built-in chain */
if (nft_chain_builtin(c))
return d->builtin_err;
if (d->verbose)
fprintf(stdout, "Deleting chain `%s'\n",
nftnl_chain_get_str(c, NFTNL_CHAIN_NAME));
/* XXX This triggers a fast lookup from the kernel. */
nftnl_chain_unset(c, NFTNL_CHAIN_HANDLE);
if (!batch_chain_add(h, NFT_COMPAT_CHAIN_USER_DEL, c))
return -1;
/* nftnl_chain is freed when deleting the batch object */
nc->nftnl = NULL;
nft_chain_list_del(nc);
nft_chain_free(nc);
return 0;
}
int nft_chain_user_del(struct nft_handle *h, const char *chain,
const char *table, bool verbose)
{
struct chain_user_del_data d = {
.handle = h,
.verbose = verbose,
};
struct nft_chain *c;
int ret = 0;
nft_fn = nft_chain_user_del;
if (chain) {
c = nft_chain_find(h, table, chain);
if (!c) {
errno = ENOENT;
return 0;
}
d.builtin_err = -2;
ret = __nft_chain_user_del(c, &d);
if (ret == -2)
errno = EINVAL;
goto out;
}
ret = nft_chain_foreach(h, table, __nft_chain_user_del, &d);
out:
/* the core expects 1 for success and 0 for error */
return ret == 0 ? 1 : 0;
}
bool nft_chain_exists(struct nft_handle *h,
const char *table, const char *chain)
{
const struct builtin_table *t = nft_table_builtin_find(h, table);
/* xtables does not support custom tables */
if (!t)
return false;
if (nft_chain_builtin_find(t, chain))
return true;
return !!nft_chain_find(h, table, chain);
}
int nft_chain_user_rename(struct nft_handle *h,const char *chain,
const char *table, const char *newname)
{
struct nftnl_chain *c;
struct nft_chain *nc;
uint64_t handle;
nft_fn = nft_chain_user_rename;
if (nft_chain_exists(h, table, newname)) {
errno = EEXIST;
return 0;
}
/* Find the old chain to be renamed */
nc = nft_chain_find(h, table, chain);
if (nc == NULL) {
errno = ENOENT;
return 0;
}
handle = nftnl_chain_get_u64(nc->nftnl, NFTNL_CHAIN_HANDLE);
/* Now prepare the new name for the chain */
c = nftnl_chain_alloc();
if (c == NULL)
return 0;
nftnl_chain_set_str(c, NFTNL_CHAIN_TABLE, table);
nftnl_chain_set_str(c, NFTNL_CHAIN_NAME, newname);
nftnl_chain_set_u64(c, NFTNL_CHAIN_HANDLE, handle);
if (!batch_chain_add(h, NFT_COMPAT_CHAIN_RENAME, c))
return 0;
/* the core expects 1 for success and 0 for error */
return 1;
}
bool nft_table_find(struct nft_handle *h, const char *tablename)
{
const struct builtin_table *t;
t = nft_table_builtin_find(h, tablename);
return t ? h->cache->table[t->type].exists : false;
}
int nft_for_each_table(struct nft_handle *h,
int (*func)(struct nft_handle *h, const char *tablename, void *data),
void *data)
{
int i;
for (i = 0; i < NFT_TABLE_MAX; i++) {
if (h->tables[i].name == NULL)
continue;
if (!h->cache->table[h->tables[i].type].exists)
continue;
func(h, h->tables[i].name, data);
}
return 0;
}
static int __nft_table_flush(struct nft_handle *h, const char *table, bool exists)
{
const struct builtin_table *_t;
struct obj_update *obj;
struct nftnl_table *t;
t = nftnl_table_alloc();
if (t == NULL)
return -1;
nftnl_table_set_str(t, NFTNL_TABLE_NAME, table);
obj = batch_table_add(h, NFT_COMPAT_TABLE_FLUSH, t);
if (!obj) {
nftnl_table_free(t);
return -1;
}
if (!exists)
obj->skip = 1;
_t = nft_table_builtin_find(h, table);
assert(_t);
h->cache->table[_t->type].exists = false;
flush_chain_cache(h, table);
return 0;
}
int nft_table_flush(struct nft_handle *h, const char *table)
{
const struct builtin_table *t;
int ret = 0;
nft_fn = nft_table_flush;
t = nft_table_builtin_find(h, table);
if (!t)
return 0;
ret = __nft_table_flush(h, table, h->cache->table[t->type].exists);
/* the core expects 1 for success and 0 for error */
return ret == 0 ? 1 : 0;
}
static int __nft_rule_del(struct nft_handle *h, struct nftnl_rule *r)
{
struct obj_update *obj;
nftnl_rule_list_del(r);
if (!nftnl_rule_get_u64(r, NFTNL_RULE_HANDLE))
nftnl_rule_set_u32(r, NFTNL_RULE_ID, ++h->rule_id);
obj = batch_rule_add(h, NFT_COMPAT_RULE_DELETE, r);
if (!obj) {
nftnl_rule_free(r);
return -1;
}
return 1;
}
static bool nft_rule_cmp(struct nft_handle *h, struct nftnl_rule *r,
struct nftnl_rule *rule)
{
struct iptables_command_state _cs = {}, this = {}, *cs = &_cs;
bool ret = false;
h->ops->rule_to_cs(h, r, &this);
h->ops->rule_to_cs(h, rule, cs);
DEBUGP("comparing with... ");
#ifdef DEBUG_DEL
nft_rule_print_save(h, r, NFT_RULE_APPEND, 0);
#endif
if (!h->ops->is_same(cs, &this))
goto out;
if (!compare_matches(cs->matches, this.matches)) {
DEBUGP("Different matches\n");
goto out;
}
if (!compare_targets(cs->target, this.target)) {
DEBUGP("Different target\n");
goto out;
}
if ((!cs->target || !this.target) &&
strcmp(cs->jumpto, this.jumpto) != 0) {
DEBUGP("Different verdict\n");
goto out;
}
ret = true;
out:
h->ops->clear_cs(&this);
h->ops->clear_cs(cs);
return ret;
}
static struct nftnl_rule *
nft_rule_find(struct nft_handle *h, struct nft_chain *nc,
struct nftnl_rule *rule, int rulenum)
{
struct nftnl_chain *c = nc->nftnl;
struct nftnl_rule *r;
struct nftnl_rule_iter *iter;
bool found = false;
if (rulenum >= 0)
/* Delete by rule number case */
return nftnl_rule_lookup_byindex(c, rulenum);
iter = nftnl_rule_iter_create(c);
if (iter == NULL)
return 0;
r = nftnl_rule_iter_next(iter);
while (r != NULL) {
found = nft_rule_cmp(h, r, rule);
if (found)
break;
r = nftnl_rule_iter_next(iter);
}
nftnl_rule_iter_destroy(iter);
return found ? r : NULL;
}
int nft_rule_check(struct nft_handle *h, const char *chain,
const char *table, struct nftnl_rule *rule, bool verbose)
{
struct nftnl_rule *r;
struct nft_chain *c;
nft_fn = nft_rule_check;
c = nft_chain_find(h, table, chain);
if (!c)
goto fail_enoent;
r = nft_rule_find(h, c, rule, -1);
if (r == NULL)
goto fail_enoent;
if (verbose)
h->ops->print_rule(h, r, 0, FMT_PRINT_RULE);
return 1;
fail_enoent:
errno = ENOENT;
return 0;
}
int nft_rule_delete(struct nft_handle *h, const char *chain,
const char *table, struct nftnl_rule *rule, bool verbose)
{
int ret = 0;
struct nftnl_rule *r;
struct nft_chain *c;
nft_fn = nft_rule_delete;
c = nft_chain_find(h, table, chain);
if (!c) {
errno = ENOENT;
return 0;
}
r = nft_rule_find(h, c, rule, -1);
if (r != NULL) {
ret =__nft_rule_del(h, r);
if (ret < 0)
errno = ENOMEM;
if (verbose)
h->ops->print_rule(h, r, 0, FMT_PRINT_RULE);
} else
errno = ENOENT;
return ret;
}
static struct nftnl_rule *
nft_rule_add(struct nft_handle *h, const char *chain,
const char *table, struct nftnl_rule *r,
struct nftnl_rule *ref, bool verbose)
{
uint64_t ref_id;
if (ref) {
ref_id = nftnl_rule_get_u64(ref, NFTNL_RULE_HANDLE);
if (ref_id > 0) {
nftnl_rule_set_u64(r, NFTNL_RULE_POSITION, ref_id);
DEBUGP("adding after rule handle %"PRIu64"\n", ref_id);
} else {
ref_id = nftnl_rule_get_u32(ref, NFTNL_RULE_ID);
if (!ref_id) {
ref_id = ++h->rule_id;
nftnl_rule_set_u32(ref, NFTNL_RULE_ID, ref_id);
}
nftnl_rule_set_u32(r, NFTNL_RULE_POSITION_ID, ref_id);
DEBUGP("adding after rule ID %"PRIu64"\n", ref_id);
}
}
if (!batch_rule_add(h, NFT_COMPAT_RULE_INSERT, r))
return NULL;
if (verbose)
h->ops->print_rule(h, r, 0, FMT_PRINT_RULE);
return r;
}
int nft_rule_insert(struct nft_handle *h, const char *chain,
const char *table, struct nftnl_rule *new_rule, int rulenum,
bool verbose)
{
struct nftnl_rule *r = NULL;
struct nft_chain *c;
nft_xt_builtin_init(h, table, chain);
nft_fn = nft_rule_insert;
c = nft_chain_find(h, table, chain);
if (!c) {
errno = ENOENT;
goto err;
}
if (rulenum > 0) {
r = nft_rule_find(h, c, new_rule, rulenum);
if (r == NULL) {
/* special case: iptables allows to insert into
* rule_count + 1 position.
*/
r = nft_rule_find(h, c, new_rule, rulenum - 1);
if (r != NULL)
return nft_rule_append(h, chain, table,
new_rule, NULL, verbose);
errno = E2BIG;
goto err;
}
}
new_rule = nft_rule_add(h, chain, table, new_rule, r, verbose);
if (!new_rule)
goto err;
if (r)
nftnl_chain_rule_insert_at(new_rule, r);
else
nftnl_chain_rule_add(new_rule, c->nftnl);
return 1;
err:
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 nftnl_rule *r;
struct nft_chain *c;
nft_fn = nft_rule_delete_num;
c = nft_chain_find(h, table, chain);
if (!c) {
errno = ENOENT;
return 0;
}
r = nft_rule_find(h, c, NULL, rulenum);
if (r != NULL) {
DEBUGP("deleting rule by number %d\n", rulenum);
ret = __nft_rule_del(h, r);
if (ret < 0)
errno = ENOMEM;
} else
errno = E2BIG;
return ret;
}
int nft_rule_replace(struct nft_handle *h, const char *chain,
const char *table, struct nftnl_rule *rule,
int rulenum, bool verbose)
{
int ret = 0;
struct nftnl_rule *r;
struct nft_chain *c;
nft_fn = nft_rule_replace;
c = nft_chain_find(h, table, chain);
if (!c) {
errno = ENOENT;
return 0;
}
r = nft_rule_find(h, c, rule, rulenum);
if (r != NULL) {
DEBUGP("replacing rule with handle=%llu\n",
(unsigned long long)
nftnl_rule_get_u64(r, NFTNL_RULE_HANDLE));
ret = nft_rule_append(h, chain, table, rule, r, verbose);
} else
errno = E2BIG;
return ret;
}
static int
__nft_rule_list(struct nft_handle *h, struct nftnl_chain *c,
int rulenum, unsigned int format,
void (*cb)(struct nft_handle *h, struct nftnl_rule *r,
unsigned int num, unsigned int format))
{
struct nftnl_rule_iter *iter;
struct nftnl_rule *r;
int rule_ctr = 0;
if (rulenum > 0) {
r = nftnl_rule_lookup_byindex(c, rulenum - 1);
if (!r)
/* iptables-legacy returns 0 when listing for
* valid chain but invalid rule number
*/
return 1;
cb(h, r, rulenum, format);
return 1;
}
iter = nftnl_rule_iter_create(c);
if (iter == NULL)
return 0;
r = nftnl_rule_iter_next(iter);
while (r != NULL) {
cb(h, r, ++rule_ctr, format);
r = nftnl_rule_iter_next(iter);
}
nftnl_rule_iter_destroy(iter);
return 1;
}
static int nft_rule_count(struct nft_handle *h, struct nftnl_chain *c)
{
struct nftnl_rule_iter *iter;
struct nftnl_rule *r;
int rule_ctr = 0;
iter = nftnl_rule_iter_create(c);
if (iter == NULL)
return 0;
r = nftnl_rule_iter_next(iter);
while (r != NULL) {
rule_ctr++;
r = nftnl_rule_iter_next(iter);
}
nftnl_rule_iter_destroy(iter);
return rule_ctr;
}
static void __nft_print_header(struct nft_handle *h,
struct nft_chain *nc, unsigned int format)
{
struct nftnl_chain *c = nc->nftnl;
const char *chain_name = nftnl_chain_get_str(c, NFTNL_CHAIN_NAME);
bool basechain = !!nftnl_chain_get(c, NFTNL_CHAIN_HOOKNUM);
uint32_t refs = nftnl_chain_get_u32(c, NFTNL_CHAIN_USE);
uint32_t entries = nft_rule_count(h, c);
struct xt_counters ctrs = {
.pcnt = nftnl_chain_get_u64(c, NFTNL_CHAIN_PACKETS),
.bcnt = nftnl_chain_get_u64(c, NFTNL_CHAIN_BYTES),
};
const char *pname = NULL;
if (nftnl_chain_is_set(c, NFTNL_CHAIN_POLICY))
pname = policy_name[nftnl_chain_get_u32(c, NFTNL_CHAIN_POLICY)];
h->ops->print_header(format, chain_name, pname,
&ctrs, basechain, refs - entries, entries);
}
struct nft_rule_list_cb_data {
struct nft_handle *h;
unsigned int format;
int rulenum;
bool found;
bool save_fmt;
void (*cb)(struct nft_handle *h, struct nftnl_rule *r,
unsigned int num, unsigned int format);
};
static int nft_rule_list_cb(struct nft_chain *c, void *data)
{
struct nft_rule_list_cb_data *d = data;
if (!d->save_fmt) {
if (d->found)
printf("\n");
d->found = true;
__nft_print_header(d->h, c, d->format);
}
return __nft_rule_list(d->h, c->nftnl, d->rulenum, d->format, d->cb);
}
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 = h->ops;
struct nft_rule_list_cb_data d = {
.h = h,
.format = format,
.rulenum = rulenum,
.cb = ops->print_rule,
};
struct nft_chain *c;
nft_xt_fake_builtin_chains(h, table, chain);
nft_assert_table_compatible(h, table, chain);
if (chain) {
c = nft_chain_find(h, table, chain);
if (!c)
return 0;
if (rulenum)
d.save_fmt = true; /* skip header printing */
else if (ops->print_table_header)
ops->print_table_header(table);
nft_rule_list_cb(c, &d);
return 1;
}
if (ops->print_table_header)
ops->print_table_header(table);
nft_chain_foreach(h, table, nft_rule_list_cb, &d);
return 1;
}
static void
list_save(struct nft_handle *h, struct nftnl_rule *r,
unsigned int num, unsigned int format)
{
nft_rule_print_save(h, r, NFT_RULE_APPEND, format);
}
int nft_chain_foreach(struct nft_handle *h, const char *table,
int (*cb)(struct nft_chain *c, void *data),
void *data)
{
const struct builtin_table *t;
struct nft_chain_list *list;
struct nft_chain *c, *c_bak;
int i, ret;
t = nft_table_builtin_find(h, table);
if (!t)
return -1;
for (i = 0; i < NF_INET_NUMHOOKS; i++) {
c = h->cache->table[t->type].base_chains[i];
if (!c)
continue;
ret = cb(c, data);
if (ret < 0)
return ret;
}
list = h->cache->table[t->type].chains;
if (!list)
return -1;
list_for_each_entry_safe(c, c_bak, &list->list, head) {
ret = cb(c, data);
if (ret < 0)
return ret;
}
return 0;
}
static int nft_rule_list_chain_save(struct nft_chain *nc, void *data)
{
struct nftnl_chain *c = nc->nftnl;
const char *chain_name = nftnl_chain_get_str(c, NFTNL_CHAIN_NAME);
uint32_t policy = nftnl_chain_get_u32(c, NFTNL_CHAIN_POLICY);
int *counters = data;
if (!nft_chain_builtin(c)) {
printf("-N %s\n", chain_name);
return 0;
}
/* this is a base chain */
printf("-P %s %s", chain_name, policy_name[policy]);
if (*counters)
printf(" -c %"PRIu64" %"PRIu64,
nftnl_chain_get_u64(c, NFTNL_CHAIN_PACKETS),
nftnl_chain_get_u64(c, NFTNL_CHAIN_BYTES));
printf("\n");
return 0;
}
int nft_rule_list_save(struct nft_handle *h, const char *chain,
const char *table, int rulenum, int counters)
{
struct nft_rule_list_cb_data d = {
.h = h,
.rulenum = rulenum,
.save_fmt = true,
.cb = list_save,
};
struct nft_chain *c;
int ret = 0;
nft_xt_fake_builtin_chains(h, table, chain);
nft_assert_table_compatible(h, table, chain);
if (counters < 0)
d.format = FMT_C_COUNTS;
else if (counters == 0)
d.format = FMT_NOCOUNTS;
if (chain) {
c = nft_chain_find(h, table, chain);
if (!c)
return 0;
if (!rulenum)
nft_rule_list_chain_save(c, &counters);
return nft_rule_list_cb(c, &d);
}
/* Dump policies and custom chains first */
nft_chain_foreach(h, table, nft_rule_list_chain_save, &counters);
/* Now dump out rules in this table */
ret = nft_chain_foreach(h, table, nft_rule_list_cb, &d);
return ret == 0 ? 1 : 0;
}
int nft_rule_zero_counters(struct nft_handle *h, const char *chain,
const char *table, int rulenum)
{
struct iptables_command_state cs = {};
struct nftnl_rule *r, *new_rule;
struct nft_chain *c;
int ret = 0;
nft_fn = nft_rule_delete;
c = nft_chain_find(h, table, chain);
if (!c)
return 0;
r = nft_rule_find(h, c, NULL, rulenum);
if (r == NULL) {
errno = ENOENT;
ret = 1;
goto error;
}
nft_rule_to_iptables_command_state(h, r, &cs);
cs.counters.pcnt = cs.counters.bcnt = 0;
new_rule = nft_rule_new(h, chain, table, &cs);
if (!new_rule)
return 1;
ret = nft_rule_append(h, chain, table, new_rule, r, false);
error:
return ret;
}
static void nft_compat_table_batch_add(struct nft_handle *h, uint16_t type,
uint16_t flags, uint32_t seq,
struct nftnl_table *table)
{
struct nlmsghdr *nlh;
nlh = nftnl_table_nlmsg_build_hdr(nftnl_batch_buffer(h->batch),
type, h->family, flags, seq);
nftnl_table_nlmsg_build_payload(nlh, table);
}
static void nft_compat_set_batch_add(struct nft_handle *h, uint16_t type,
uint16_t flags, uint32_t seq,
struct nftnl_set *set)
{
struct nlmsghdr *nlh;
nlh = nftnl_nlmsg_build_hdr(nftnl_batch_buffer(h->batch),
type, h->family, flags, seq);
nftnl_set_nlmsg_build_payload(nlh, set);
}
static void nft_compat_setelem_batch_add(struct nft_handle *h, uint16_t type,
uint16_t flags, uint32_t *seq,
struct nftnl_set *set)
{
struct nftnl_set_elems_iter *iter;
struct nlmsghdr *nlh;
iter = nftnl_set_elems_iter_create(set);
if (!iter)
return;
while (nftnl_set_elems_iter_cur(iter)) {
(*seq)++;
mnl_nft_batch_continue(h->batch);
nlh = nftnl_nlmsg_build_hdr(nftnl_batch_buffer(h->batch),
type, h->family, flags, *seq);
if (nftnl_set_elems_nlmsg_build_payload_iter(nlh, iter) <= 0)
break;
}
nftnl_set_elems_iter_destroy(iter);
}
static void nft_compat_chain_batch_add(struct nft_handle *h, uint16_t type,
uint16_t flags, uint32_t seq,
struct nftnl_chain *chain)
{
struct nlmsghdr *nlh;
nlh = nftnl_chain_nlmsg_build_hdr(nftnl_batch_buffer(h->batch),
type, h->family, flags, seq);
nftnl_chain_nlmsg_build_payload(nlh, chain);
nft_chain_print_debug(chain, nlh);
}
static void nft_compat_rule_batch_add(struct nft_handle *h, uint16_t type,
uint16_t flags, uint32_t seq,
struct nftnl_rule *rule)
{
struct nlmsghdr *nlh;
nlh = nftnl_rule_nlmsg_build_hdr(nftnl_batch_buffer(h->batch),
type, h->family, flags, seq);
nftnl_rule_nlmsg_build_payload(nlh, rule);
nft_rule_print_debug(rule, nlh);
}
static void batch_obj_del(struct nft_handle *h, struct obj_update *o)
{
switch (o->type) {
case NFT_COMPAT_TABLE_ADD:
case NFT_COMPAT_TABLE_FLUSH:
nftnl_table_free(o->table);
break;
case NFT_COMPAT_CHAIN_ZERO:
case NFT_COMPAT_CHAIN_USER_ADD:
case NFT_COMPAT_CHAIN_ADD:
break;
case NFT_COMPAT_CHAIN_USER_DEL:
case NFT_COMPAT_CHAIN_USER_FLUSH:
case NFT_COMPAT_CHAIN_UPDATE:
case NFT_COMPAT_CHAIN_RENAME:
nftnl_chain_free(o->chain);
break;
case NFT_COMPAT_RULE_APPEND:
case NFT_COMPAT_RULE_INSERT:
case NFT_COMPAT_RULE_REPLACE:
break;
case NFT_COMPAT_RULE_DELETE:
case NFT_COMPAT_RULE_FLUSH:
nftnl_rule_free(o->rule);
break;
case NFT_COMPAT_SET_ADD:
nftnl_set_free(o->set);
break;
case NFT_COMPAT_RULE_LIST:
case NFT_COMPAT_RULE_CHECK:
case NFT_COMPAT_CHAIN_RESTORE:
case NFT_COMPAT_RULE_SAVE:
case NFT_COMPAT_RULE_ZERO:
case NFT_COMPAT_BRIDGE_USER_CHAIN_UPDATE:
assert(0);
break;
}
h->obj_list_num--;
list_del(&o->head);
free(o);
}
static void nft_refresh_transaction(struct nft_handle *h)
{
const char *tablename, *chainname;
const struct nft_chain *c;
struct obj_update *n, *tmp;
bool exists;
h->error.lineno = 0;
list_for_each_entry_safe(n, tmp, &h->obj_list, head) {
switch (n->type) {
case NFT_COMPAT_TABLE_FLUSH:
tablename = nftnl_table_get_str(n->table, NFTNL_TABLE_NAME);
if (!tablename)
continue;
exists = nft_table_find(h, tablename);
if (exists)
n->skip = 0;
else
n->skip = 1;
break;
case NFT_COMPAT_CHAIN_USER_ADD:
tablename = nftnl_chain_get_str(n->chain, NFTNL_CHAIN_TABLE);
if (!tablename)
continue;
chainname = nftnl_chain_get_str(n->chain, NFTNL_CHAIN_NAME);
if (!chainname)
continue;
if (!h->noflush)
break;
c = nft_chain_find(h, tablename, chainname);
if (c) {
n->skip = 1;
} else if (!c) {
n->skip = 0;
}
break;
case NFT_COMPAT_RULE_FLUSH:
tablename = nftnl_rule_get_str(n->rule, NFTNL_RULE_TABLE);
if (!tablename)
continue;
chainname = nftnl_rule_get_str(n->rule, NFTNL_RULE_CHAIN);
if (!chainname)
continue;
n->skip = !nft_chain_find(h, tablename, chainname);
break;
case NFT_COMPAT_TABLE_ADD:
case NFT_COMPAT_CHAIN_ADD:
case NFT_COMPAT_CHAIN_ZERO:
case NFT_COMPAT_CHAIN_USER_DEL:
case NFT_COMPAT_CHAIN_USER_FLUSH:
case NFT_COMPAT_CHAIN_UPDATE:
case NFT_COMPAT_CHAIN_RENAME:
case NFT_COMPAT_RULE_APPEND:
case NFT_COMPAT_RULE_INSERT:
case NFT_COMPAT_RULE_REPLACE:
case NFT_COMPAT_RULE_DELETE:
case NFT_COMPAT_SET_ADD:
case NFT_COMPAT_RULE_LIST:
case NFT_COMPAT_RULE_CHECK:
case NFT_COMPAT_CHAIN_RESTORE:
case NFT_COMPAT_RULE_SAVE:
case NFT_COMPAT_RULE_ZERO:
case NFT_COMPAT_BRIDGE_USER_CHAIN_UPDATE:
break;
}
}
}
static int nft_action(struct nft_handle *h, int action)
{
struct obj_update *n, *tmp;
struct mnl_err *err, *ne;
unsigned int buflen, i, len;
bool show_errors = true;
char errmsg[1024];
uint32_t seq;
int ret = 0;
retry:
seq = 1;
h->batch = mnl_batch_init();
mnl_batch_begin(h->batch, h->nft_genid, seq++);
h->nft_genid++;
list_for_each_entry(n, &h->obj_list, head) {
if (n->skip) {
n->seq = 0;
continue;
}
n->seq = seq++;
switch (n->type) {
case NFT_COMPAT_TABLE_ADD:
nft_compat_table_batch_add(h, NFT_MSG_NEWTABLE,
NLM_F_CREATE, n->seq,
n->table);
break;
case NFT_COMPAT_TABLE_FLUSH:
nft_compat_table_batch_add(h, NFT_MSG_DELTABLE,
0,
n->seq, n->table);
break;
case NFT_COMPAT_CHAIN_ADD:
case NFT_COMPAT_CHAIN_ZERO:
nft_compat_chain_batch_add(h, NFT_MSG_NEWCHAIN,
NLM_F_CREATE, n->seq,
n->chain);
break;
case NFT_COMPAT_CHAIN_USER_ADD:
nft_compat_chain_batch_add(h, NFT_MSG_NEWCHAIN,
NLM_F_EXCL, n->seq,
n->chain);
break;
case NFT_COMPAT_CHAIN_USER_DEL:
nft_compat_chain_batch_add(h, NFT_MSG_DELCHAIN,
NLM_F_NONREC, n->seq,
n->chain);
break;
case NFT_COMPAT_CHAIN_USER_FLUSH:
nft_compat_chain_batch_add(h, NFT_MSG_DELCHAIN,
0, n->seq,
n->chain);
break;
case NFT_COMPAT_CHAIN_UPDATE:
nft_compat_chain_batch_add(h, NFT_MSG_NEWCHAIN,
h->restore ?
NLM_F_CREATE : 0,
n->seq, n->chain);
break;
case NFT_COMPAT_CHAIN_RENAME:
nft_compat_chain_batch_add(h, NFT_MSG_NEWCHAIN, 0,
n->seq, n->chain);
break;
case NFT_COMPAT_RULE_APPEND:
nft_compat_rule_batch_add(h, NFT_MSG_NEWRULE,
NLM_F_CREATE | NLM_F_APPEND,
n->seq, n->rule);
break;
case NFT_COMPAT_RULE_INSERT:
nft_compat_rule_batch_add(h, NFT_MSG_NEWRULE,
NLM_F_CREATE, n->seq,
n->rule);
break;
case NFT_COMPAT_RULE_REPLACE:
nft_compat_rule_batch_add(h, NFT_MSG_NEWRULE,
NLM_F_CREATE | NLM_F_REPLACE,
n->seq, n->rule);
break;
case NFT_COMPAT_RULE_DELETE:
case NFT_COMPAT_RULE_FLUSH:
nft_compat_rule_batch_add(h, NFT_MSG_DELRULE, 0,
n->seq, n->rule);
break;
case NFT_COMPAT_SET_ADD:
nft_compat_set_batch_add(h, NFT_MSG_NEWSET,
NLM_F_CREATE, n->seq, n->set);
nft_compat_setelem_batch_add(h, NFT_MSG_NEWSETELEM,
NLM_F_CREATE, &n->seq, n->set);
seq = n->seq;
break;
case NFT_COMPAT_RULE_LIST:
case NFT_COMPAT_RULE_CHECK:
case NFT_COMPAT_CHAIN_RESTORE:
case NFT_COMPAT_RULE_SAVE:
case NFT_COMPAT_RULE_ZERO:
case NFT_COMPAT_BRIDGE_USER_CHAIN_UPDATE:
assert(0);
}
mnl_nft_batch_continue(h->batch);
}
switch (action) {
case NFT_COMPAT_COMMIT:
mnl_batch_end(h->batch, seq++);
break;
case NFT_COMPAT_ABORT:
break;
}
errno = 0;
ret = mnl_batch_talk(h, seq);
if (ret && errno == ERESTART) {
nft_rebuild_cache(h);
nft_refresh_transaction(h);
list_for_each_entry_safe(err, ne, &h->err_list, head)
mnl_err_list_free(err);
mnl_batch_reset(h->batch);
goto retry;
}
i = 0;
buflen = sizeof(errmsg);
list_for_each_entry_safe(n, tmp, &h->obj_list, head) {
list_for_each_entry_safe(err, ne, &h->err_list, head) {
if (err->seqnum > n->seq)
break;
if (err->seqnum == n->seq && show_errors) {
if (n->error.lineno == 0)
show_errors = false;
len = mnl_append_error(h, n, err, errmsg + i, buflen);
if (len > 0 && len <= buflen) {
buflen -= len;
i += len;
}
}
mnl_err_list_free(err);
}
batch_obj_del(h, n);
}
nft_release_cache(h);
mnl_batch_reset(h->batch);
if (i)
xtables_error(RESOURCE_PROBLEM, "%s", errmsg);
return ret == 0 ? 1 : 0;
}
static int ebt_add_policy_rule(struct nftnl_chain *c, void *data)
{
uint32_t policy = nftnl_chain_get_u32(c, NFTNL_CHAIN_POLICY);
struct iptables_command_state cs = {
.eb.bitmask = EBT_NOPROTO,
};
struct nftnl_udata_buf *udata;
struct nft_handle *h = data;
struct nftnl_rule *r;
const char *pname;
if (nftnl_chain_get(c, NFTNL_CHAIN_HOOKNUM))
return 0; /* ignore base chains */
if (!nftnl_chain_is_set(c, NFTNL_CHAIN_POLICY))
return 0;
nftnl_chain_unset(c, NFTNL_CHAIN_POLICY);
switch (policy) {
case NFT_RETURN:
return 0; /* return policy is default for nft chains */
case NF_ACCEPT:
pname = "ACCEPT";
break;
case NF_DROP:
pname = "DROP";
break;
default:
return -1;
}
command_jump(&cs, pname);
r = nft_rule_new(h, nftnl_chain_get_str(c, NFTNL_CHAIN_NAME),
nftnl_chain_get_str(c, NFTNL_CHAIN_TABLE), &cs);
ebt_cs_clean(&cs);
if (!r)
return -1;
udata = nftnl_udata_buf_alloc(NFT_USERDATA_MAXLEN);
if (!udata)
goto err_free_rule;
if (!nftnl_udata_put_u32(udata, UDATA_TYPE_EBTABLES_POLICY, 1))
goto err_free_rule;
nftnl_rule_set_data(r, NFTNL_RULE_USERDATA,
nftnl_udata_buf_data(udata),
nftnl_udata_buf_len(udata));
nftnl_udata_buf_free(udata);
if (!batch_rule_add(h, NFT_COMPAT_RULE_APPEND, r))
goto err_free_rule;
/* add the rule to chain so it is freed later */
nftnl_chain_rule_add_tail(r, c);
return 0;
err_free_rule:
nftnl_rule_free(r);
return -1;
}
int ebt_set_user_chain_policy(struct nft_handle *h, const char *table,
const char *chain, const char *policy)
{
struct nft_chain *c = nft_chain_find(h, table, chain);
int pval;
if (!c)
return 0;
if (!strcmp(policy, "DROP"))
pval = NF_DROP;
else if (!strcmp(policy, "ACCEPT"))
pval = NF_ACCEPT;
else if (!strcmp(policy