| /* |
| * (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, ð_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 |