/* * (C) 2012 by Pablo Neira Ayuso * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This code has been sponsored by Sophos Astaro */ #include #include #include #include #include #include #include /* getprotobynumber */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* inet_ntoa */ #include #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_update_type { NFT_COMPAT_TABLE_ADD, NFT_COMPAT_TABLE_FLUSH, NFT_COMPAT_CHAIN_ADD, NFT_COMPAT_CHAIN_USER_ADD, NFT_COMPAT_CHAIN_USER_DEL, NFT_COMPAT_CHAIN_USER_FLUSH, NFT_COMPAT_CHAIN_UPDATE, NFT_COMPAT_CHAIN_RENAME, NFT_COMPAT_CHAIN_ZERO, NFT_COMPAT_RULE_APPEND, NFT_COMPAT_RULE_INSERT, NFT_COMPAT_RULE_REPLACE, NFT_COMPAT_RULE_DELETE, NFT_COMPAT_RULE_FLUSH, NFT_COMPAT_SET_ADD, }; 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; uint8_t implicit: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; } 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 int batch_chain_add(struct nft_handle *h, enum obj_update_type type, struct nftnl_chain *c) { return batch_add(h, type, c) ? 0 : -1; } 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); } 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 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 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 bool nft_table_initialized(const struct nft_handle *h, enum nft_table_type type) { return h->cache->table[type].initialized; } static int nft_table_builtin_add(struct nft_handle *h, const struct builtin_table *_t) { struct nftnl_table *t; int ret; if (nft_table_initialized(h, _t->type)) 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); 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) { struct nftnl_chain *c; c = nft_chain_builtin_alloc(table, chain, NF_ACCEPT); if (c == NULL) return; batch_chain_add(h, NFT_COMPAT_CHAIN_ADD, c); nftnl_chain_list_add_tail(c, h->cache->table[table->type].chains); } /* 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; ichains[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) { struct nftnl_chain_list *list; struct nftnl_chain *c; 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++) { list = nft_chain_list_get(h, table->name, table->chains[i].name); if (!list) continue; c = nftnl_chain_list_lookup_byname(list, table->chains[i].name); if (c != NULL) continue; nft_chain_builtin_add(h, table, &table->chains[i]); } } static int nft_xt_builtin_init(struct nft_handle *h, const char *table) { const struct builtin_table *t; t = nft_table_builtin_find(h, table); if (t == NULL) return -1; if (nft_table_initialized(h, t->type)) return 0; if (nft_table_builtin_add(h, t) < 0) return -1; nft_chain_builtin_init(h, t); h->cache->table[t->type].initialized = true; 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; } 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, const struct builtin_table *t) { 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->portid = mnl_socket_get_portid(h->nl); h->tables = t; h->cache = &h->__cache[0]; INIT_LIST_HEAD(&h->obj_list); INIT_LIST_HEAD(&h->err_list); return 0; } void nft_fini(struct nft_handle *h) { flush_chain_cache(h, NULL); 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_table_builtin_add(h, _t); _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; int ret; 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 errno = EINVAL; if (c == NULL) return 0; ret = batch_chain_add(h, NFT_COMPAT_CHAIN_UPDATE, c); /* the core expects 1 for success and 0 for error */ return ret == 0 ? 1 : 0; } 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; 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); return batch_set_add(h, NFT_COMPAT_SET_ADD, s) ? s : NULL; } 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; 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); } s = add_anon_set(h, table, 0, type, len, cnt); if (!s) return -ENOMEM; set_id = nftnl_set_get_u32(s, NFTNL_SET_ID); 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); 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); } static 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; } static struct nftnl_chain * nft_chain_find(struct nft_handle *h, const char *table, const char *chain); int nft_rule_append(struct nft_handle *h, const char *chain, const char *table, void *data, struct nftnl_rule *ref, bool verbose) { struct nftnl_chain *c; struct nftnl_rule *r; int type; nft_xt_builtin_init(h, table); /* Since ebtables user-defined chain policies are implemented as last * rule in nftables, rule cache is required here to treat them right. */ if (h->family == NFPROTO_BRIDGE) { c = nft_chain_find(h, table, chain); if (c && !nft_chain_builtin(c)) nft_build_cache(h, c); } nft_fn = nft_rule_append; r = nft_rule_new(h, chain, table, data); if (r == NULL) return 0; 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) { nftnl_rule_free(r); 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(r); } else { c = nft_chain_find(h, table, chain); if (!c) { errno = ENOENT; return 0; } nftnl_chain_rule_add_tail(r, c); } 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)) && ops->save_counters) ops->save_counters(&cs); /* 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_handle *h, struct nftnl_chain_list *list) { struct nft_family_ops *ops = h->ops; struct nftnl_chain_list_iter *iter; struct nftnl_chain *c; iter = nftnl_chain_list_iter_create(list); if (iter == NULL) return 0; c = nftnl_chain_list_iter_next(iter); while (c != NULL) { const char *policy = NULL; if (nft_chain_builtin(c)) { uint32_t pol = NF_ACCEPT; if (nftnl_chain_get(c, NFTNL_CHAIN_POLICY)) pol = nftnl_chain_get_u32(c, NFTNL_CHAIN_POLICY); policy = policy_name[pol]; } else if (h->family == NFPROTO_BRIDGE) { if (nftnl_chain_is_set(c, NFTNL_CHAIN_POLICY)) { uint32_t pol; pol = nftnl_chain_get_u32(c, NFTNL_CHAIN_POLICY); policy = policy_name[pol]; } else { policy = "RETURN"; } } if (ops->save_chain) ops->save_chain(c, policy); c = nftnl_chain_list_iter_next(iter); } nftnl_chain_list_iter_destroy(iter); return 1; } static int nft_chain_save_rules(struct nft_handle *h, struct nftnl_chain *c, unsigned int format) { struct nftnl_rule_iter *iter; struct nftnl_rule *r; iter = nftnl_rule_iter_create(c); if (iter == NULL) return 1; r = nftnl_rule_iter_next(iter); while (r != NULL) { nft_rule_print_save(h, r, NFT_RULE_APPEND, 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 nftnl_chain_list_iter *iter; struct nftnl_chain_list *list; struct nftnl_chain *c; int ret = 0; list = nft_chain_list_get(h, table, NULL); if (!list) return 0; iter = nftnl_chain_list_iter_create(list); if (!iter) return 0; c = nftnl_chain_list_iter_next(iter); while (c) { nft_build_cache(h, c); ret = nft_chain_save_rules(h, c, format); if (ret != 0) break; c = nftnl_chain_list_iter_next(iter); } nftnl_chain_list_iter_destroy(iter); /* the core expects 1 for success and 0 for error */ return ret == 0 ? 1 : 0; } static void __nft_rule_flush(struct nft_handle *h, const char *table, const char *chain, bool verbose, bool implicit) { 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->implicit = implicit; } int nft_rule_flush(struct nft_handle *h, const char *chain, const char *table, bool verbose) { struct nftnl_chain_list_iter *iter; struct nftnl_chain_list *list; struct nftnl_chain *c = NULL; int ret = 0; nft_xt_builtin_init(h, table); nft_fn = nft_rule_flush; if (chain || verbose) { list = nft_chain_list_get(h, table, chain); if (list == NULL) { ret = 1; goto err; } } if (chain) { c = nftnl_chain_list_lookup_byname(list, chain); if (!c) { errno = ENOENT; return 0; } } if (chain || !verbose) { __nft_rule_flush(h, table, chain, verbose, false); flush_rule_cache(h, table, c); return 1; } iter = nftnl_chain_list_iter_create(list); if (iter == NULL) { ret = 1; goto err; } c = nftnl_chain_list_iter_next(iter); while (c != NULL) { chain = nftnl_chain_get_str(c, NFTNL_CHAIN_NAME); __nft_rule_flush(h, table, chain, verbose, false); flush_rule_cache(h, table, c); c = nftnl_chain_list_iter_next(iter); } nftnl_chain_list_iter_destroy(iter); err: /* 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) { struct nftnl_chain_list *list; struct nftnl_chain *c; int ret; nft_fn = nft_chain_user_add; nft_xt_builtin_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); ret = batch_chain_add(h, NFT_COMPAT_CHAIN_USER_ADD, c); list = nft_chain_list_get(h, table, chain); if (list) nftnl_chain_list_add(c, list); /* the core expects 1 for success and 0 for error */ return ret == 0 ? 1 : 0; } int nft_chain_restore(struct nft_handle *h, const char *chain, const char *table) { struct nftnl_chain_list *list; struct nftnl_chain *c; bool created = false; int ret; c = nft_chain_find(h, table, chain); if (c) { /* Apparently -n still flushes existing user defined * chains that are redefined. */ if (h->noflush) __nft_rule_flush(h, table, chain, false, true); } else { c = nftnl_chain_alloc(); if (!c) return -1; nftnl_chain_set_str(c, NFTNL_CHAIN_TABLE, table); nftnl_chain_set_str(c, NFTNL_CHAIN_NAME, chain); created = true; } if (h->family == NFPROTO_BRIDGE) nftnl_chain_set_u32(c, NFTNL_CHAIN_POLICY, NF_ACCEPT); if (!created) return 0; ret = batch_chain_add(h, NFT_COMPAT_CHAIN_USER_ADD, c); list = nft_chain_list_get(h, table, chain); if (list) nftnl_chain_list_add(c, list); return ret; } /* 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 nftnl_chain *c, void *data) { struct chain_user_del_data *d = data; struct nft_handle *h = d->handle; int ret; /* 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)); /* This triggers required policy rule deletion. */ if (h->family == NFPROTO_BRIDGE) nft_build_cache(h, c); /* XXX This triggers a fast lookup from the kernel. */ nftnl_chain_unset(c, NFTNL_CHAIN_HANDLE); ret = batch_chain_add(h, NFT_COMPAT_CHAIN_USER_DEL, c); if (ret) return -1; nftnl_chain_list_del(c); 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 nftnl_chain_list *list; struct nftnl_chain *c; int ret = 0; nft_fn = nft_chain_user_del; list = nft_chain_list_get(h, table, chain); if (list == NULL) return 0; if (chain) { c = nftnl_chain_list_lookup_byname(list, 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 = nftnl_chain_list_foreach(list, __nft_chain_user_del, &d); out: /* the core expects 1 for success and 0 for error */ return ret == 0 ? 1 : 0; } static struct nftnl_chain * nft_chain_find(struct nft_handle *h, const char *table, const char *chain) { struct nftnl_chain_list *list; list = nft_chain_list_get(h, table, chain); if (list == NULL) return NULL; return nftnl_chain_list_lookup_byname(list, chain); } 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; uint64_t handle; int ret; nft_fn = nft_chain_user_rename; if (nft_chain_exists(h, table, newname)) { errno = EEXIST; return 0; } nft_xt_builtin_init(h, table); /* Config load changed errno. Ensure genuine info for our callers. */ errno = 0; /* Find the old chain to be renamed */ c = nft_chain_find(h, table, chain); if (c == NULL) { errno = ENOENT; return 0; } handle = nftnl_chain_get_u64(c, 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); ret = batch_chain_add(h, NFT_COMPAT_CHAIN_RENAME, c); /* the core expects 1 for success and 0 for error */ return ret == 0 ? 1 : 0; } bool nft_table_find(struct nft_handle *h, const char *tablename) { struct nftnl_table_list_iter *iter; struct nftnl_table_list *list; struct nftnl_table *t; bool ret = false; list = nftnl_table_list_get(h); if (list == NULL) goto err; iter = nftnl_table_list_iter_create(list); if (iter == NULL) goto err; t = nftnl_table_list_iter_next(iter); while (t != NULL) { const char *this_tablename = nftnl_table_get(t, NFTNL_TABLE_NAME); if (strcmp(tablename, this_tablename) == 0) { ret = true; break; } t = nftnl_table_list_iter_next(iter); } nftnl_table_list_iter_destroy(iter); err: return ret; } int nft_for_each_table(struct nft_handle *h, int (*func)(struct nft_handle *h, const char *tablename, void *data), void *data) { struct nftnl_table_list *list; struct nftnl_table_list_iter *iter; struct nftnl_table *t; list = nftnl_table_list_get(h); if (list == NULL) return -1; iter = nftnl_table_list_iter_create(list); if (iter == NULL) return -1; t = nftnl_table_list_iter_next(iter); while (t != NULL) { const char *tablename = nftnl_table_get(t, NFTNL_TABLE_NAME); func(h, tablename, data); t = nftnl_table_list_iter_next(iter); } nftnl_table_list_iter_destroy(iter); 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].initialized = false; flush_chain_cache(h, table); return 0; } int nft_table_flush(struct nft_handle *h, const char *table) { struct nftnl_table_list_iter *iter; struct nftnl_table_list *list; struct nftnl_table *t; bool exists = false; int ret = 0; nft_fn = nft_table_flush; list = nftnl_table_list_get(h); if (list == NULL) { ret = -1; goto err_out; } iter = nftnl_table_list_iter_create(list); if (iter == NULL) { ret = -1; goto err_table_list; } t = nftnl_table_list_iter_next(iter); while (t != NULL) { const char *table_name = nftnl_table_get_str(t, NFTNL_TABLE_NAME); if (strcmp(table_name, table) == 0) { exists = true; break; } t = nftnl_table_list_iter_next(iter); } ret = __nft_table_flush(h, table, exists); nftnl_table_list_iter_destroy(iter); err_table_list: err_out: /* the core expects 1 for success and 0 for error */ return ret == 0 ? 1 : 0; } void nft_table_new(struct nft_handle *h, const char *table) { nft_xt_builtin_init(h, table); } 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 struct nftnl_rule * nft_rule_find(struct nft_handle *h, struct nftnl_chain *c, void *data, int rulenum) { struct nftnl_rule *r; struct nftnl_rule_iter *iter; bool found = false; nft_build_cache(h, c); 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 = h->ops->rule_find(h, r, data); 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, void *data, bool verbose) { struct nftnl_chain *c; struct nftnl_rule *r; nft_fn = nft_rule_check; c = nft_chain_find(h, table, chain); if (!c) goto fail_enoent; r = nft_rule_find(h, c, data, -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, void *data, bool verbose) { int ret = 0; struct nftnl_chain *c; struct nftnl_rule *r; nft_fn = nft_rule_delete; c = nft_chain_find(h, table, chain); if (!c) { errno = ENOENT; return 0; } r = nft_rule_find(h, c, data, -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 iptables_command_state *cs, struct nftnl_rule *ref, bool verbose) { struct nftnl_rule *r; uint64_t ref_id; r = nft_rule_new(h, chain, table, cs); if (r == NULL) return NULL; 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)) { nftnl_rule_free(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, void *data, int rulenum, bool verbose) { struct nftnl_rule *r = NULL, *new_rule; struct nftnl_chain *c; nft_xt_builtin_init(h, table); 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, data, rulenum); if (r == NULL) { /* special case: iptables allows to insert into * rule_count + 1 position. */ r = nft_rule_find(h, c, data, rulenum - 1); if (r != NULL) return nft_rule_append(h, chain, table, data, NULL, verbose); errno = E2BIG; goto err; } } new_rule = nft_rule_add(h, chain, table, data, 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); 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_chain *c; struct nftnl_rule *r; 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, void *data, int rulenum, bool verbose) { int ret = 0; struct nftnl_chain *c; struct nftnl_rule *r; nft_fn = nft_rule_replace; c = nft_chain_find(h, table, chain); if (!c) { errno = ENOENT; return 0; } r = nft_rule_find(h, c, data, 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, data, 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 nftnl_chain *c, unsigned int format) { 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); } 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 nftnl_chain_list *list; struct nftnl_chain_list_iter *iter; struct nftnl_chain *c; bool found = false; nft_xt_builtin_init(h, table); nft_assert_table_compatible(h, table, chain); list = nft_chain_list_get(h, table, chain); if (!list) return 0; if (chain) { c = nftnl_chain_list_lookup_byname(list, chain); if (!c) return 0; if (!rulenum) { if (ops->print_table_header) ops->print_table_header(table); __nft_print_header(h, c, format); } __nft_rule_list(h, c, rulenum, format, ops->print_rule); return 1; } iter = nftnl_chain_list_iter_create(list); if (iter == NULL) return 0; if (ops->print_table_header) ops->print_table_header(table); c = nftnl_chain_list_iter_next(iter); while (c != NULL) { if (found) printf("\n"); __nft_print_header(h, c, format); __nft_rule_list(h, c, rulenum, format, ops->print_rule); found = true; c = nftnl_chain_list_iter_next(iter); } nftnl_chain_list_iter_destroy(iter); 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); } static int __nftnl_rule_list_chain_save(struct nftnl_chain *c, void *data) { 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; } static int nftnl_rule_list_chain_save(struct nft_handle *h, const char *chain, struct nftnl_chain_list *list, int counters) { struct nftnl_chain *c; if (chain) { c = nftnl_chain_list_lookup_byname(list, chain); if (!c) return 0; __nftnl_rule_list_chain_save(c, &counters); return 1; } nftnl_chain_list_foreach(list, __nftnl_rule_list_chain_save, &counters); return 1; } int nft_rule_list_save(struct nft_handle *h, const char *chain, const char *table, int rulenum, int counters) { struct nftnl_chain_list *list; struct nftnl_chain_list_iter *iter; unsigned int format = 0; struct nftnl_chain *c; int ret = 0; nft_xt_builtin_init(h, table); nft_assert_table_compatible(h, table, chain); list = nft_chain_list_get(h, table, chain); if (!list) return 0; /* Dump policies and custom chains first */ if (!rulenum) nftnl_rule_list_chain_save(h, chain, list, counters); if (counters < 0) format = FMT_C_COUNTS; else if (counters == 0) format = FMT_NOCOUNTS; if (chain) { c = nftnl_chain_list_lookup_byname(list, chain); if (!c) return 0; return __nft_rule_list(h, c, rulenum, format, list_save); } /* Now dump out rules in this table */ iter = nftnl_chain_list_iter_create(list); if (iter == NULL) return 0; c = nftnl_chain_list_iter_next(iter); while (c != NULL) { ret = __nft_rule_list(h, c, rulenum, format, list_save); c = nftnl_chain_list_iter_next(iter); } nftnl_chain_list_iter_destroy(iter); return ret; } int nft_rule_zero_counters(struct nft_handle *h, const char *chain, const char *table, int rulenum) { struct iptables_command_state cs = {}; struct nftnl_chain *c; struct nftnl_rule *r; 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; ret = nft_rule_append(h, chain, table, &cs, 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: case NFT_COMPAT_RULE_DELETE: break; case NFT_COMPAT_RULE_FLUSH: nftnl_rule_free(o->rule); break; case NFT_COMPAT_SET_ADD: nftnl_set_free(o->set); 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 nftnl_chain *c; struct obj_update *n, *tmp; bool exists; h->error.lineno = 0; list_for_each_entry_safe(n, tmp, &h->obj_list, head) { if (n->implicit) { batch_obj_del(h, n); continue; } 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) { /* -restore -n flushes existing rules from redefined user-chain */ __nft_rule_flush(h, tablename, chainname, false, true); n->skip = 1; } else if (!c) { n->skip = 0; } 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_RULE_FLUSH: case NFT_COMPAT_SET_ADD: 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) 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; } 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); i=0; 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); if (!r) return -1; udata = nftnl_udata_buf_alloc(NFT_USERDATA_MAXLEN); if (!udata) return -1; if (!nftnl_udata_put_u32(udata, UDATA_TYPE_EBTABLES_POLICY, 1)) return -1; 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)) { nftnl_rule_free(r); return -1; } return 0; } int ebt_set_user_chain_policy(struct nft_handle *h, const char *table, const char *chain, const char *policy) { struct nftnl_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, "RETURN")) pval = NFT_RETURN; else return 0; nft_build_cache(h, c); nftnl_chain_set_u32(c, NFTNL_CHAIN_POLICY, pval); return 1; } static void nft_bridge_commit_prepare(struct nft_handle *h) { const struct builtin_table *t; struct nftnl_chain_list *list; int i; for (i = 0; i < NFT_TABLE_MAX; i++) { t = &h->tables[i]; if (!t->name) continue; list = h->cache->table[t->type].chains; if (!list) continue; nftnl_chain_list_foreach(list, ebt_add_policy_rule, h); } } int nft_commit(struct nft_handle *h) { return nft_action(h, NFT_COMPAT_COMMIT); } int nft_bridge_commit(struct nft_handle *h) { nft_bridge_commit_prepare(h); return nft_commit(h); } int nft_abort(struct nft_handle *h) { return nft_action(h, NFT_COMPAT_ABORT); } int nft_abort_policy_rule(struct nft_handle *h, const char *table) { struct obj_update *n, *tmp; list_for_each_entry_safe(n, tmp, &h->obj_list, head) { if (n->type != NFT_COMPAT_RULE_APPEND && n->type != NFT_COMPAT_RULE_DELETE) continue; if (strcmp(table, nftnl_rule_get_str(n->rule, NFTNL_RULE_TABLE))) continue; if (!nft_rule_is_policy_rule(n->rule)) continue; batch_obj_del(h, n); } return 0; } int nft_compatible_revision(const char *name, uint8_t rev, int opt) { struct mnl_socket *nl; char buf[16536]; struct nlmsghdr *nlh; uint32_t portid, seq, type = 0; uint32_t pf = AF_INET; int ret = 0; switch (opt) { case IPT_SO_GET_REVISION_MATCH: break; case IP6T_SO_GET_REVISION_MATCH: pf = AF_INET6; break; case IPT_SO_GET_REVISION_TARGET: type = 1; break; case IP6T_SO_GET_REVISION_TARGET: type = 1; pf = AF_INET6; break; default: /* No revision support (arp, ebtables), assume latest version ok */ return 1; } nlh = mnl_nlmsg_put_header(buf); nlh->nlmsg_type = (NFNL_SUBSYS_NFT_COMPAT << 8) | NFNL_MSG_COMPAT_GET; nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; nlh->nlmsg_seq = seq = time(NULL); struct nfgenmsg *nfg = mnl_nlmsg_put_extra_header(nlh, sizeof(*nfg)); nfg->nfgen_family = pf; nfg->version = NFNETLINK_V0; nfg->res_id = 0; mnl_attr_put_strz(nlh, NFTA_COMPAT_NAME, name); mnl_attr_put_u32(nlh, NFTA_COMPAT_REV, htonl(rev)); mnl_attr_put_u32(nlh, NFTA_COMPAT_TYPE, htonl(type)); DEBUGP("requesting `%s' rev=%d type=%d via nft_compat\n", name, rev, type); nl = mnl_socket_open(NETLINK_NETFILTER); if (nl == NULL) return 0; if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) goto err; portid = mnl_socket_get_portid(nl); if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) goto err; ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); if (ret == -1) goto err; ret = mnl_cb_run(buf, ret, seq, portid, NULL, NULL); if (ret == -1) goto err; err: mnl_socket_close(nl); return ret < 0 ? 0 : 1; } /* Translates errno numbers into more human-readable form than strerror. */ const char *nft_strerror(int err) { unsigned int i; static struct table_struct { void *fn; int err; const char *message; } table[] = { { nft_chain_user_del, ENOTEMPTY, "Chain is not empty" }, { nft_chain_user_del, EINVAL, "Can't delete built-in chain" }, { nft_chain_user_del, EBUSY, "Directory not empty" }, { nft_chain_user_del, EMLINK, "Can't delete chain with references left" }, { nft_chain_user_add, EEXIST, "Chain already exists" }, { nft_chain_user_rename, EEXIST, "File exists" }, { nft_rule_insert, E2BIG, "Index of insertion too big" }, { nft_rule_check, ENOENT, "Bad rule (does a matching rule exist in that chain?)" }, { nft_rule_replace, E2BIG, "Index of replacement too big" }, { nft_rule_delete_num, E2BIG, "Index of deletion too big" }, /* { TC_READ_COUNTER, E2BIG, "Index of counter too big" }, { TC_ZERO_COUNTER, E2BIG, "Index of counter too big" }, */ /* ENOENT for DELETE probably means no matching rule */ { nft_rule_delete, ENOENT, "Bad rule (does a matching rule exist in that chain?)" }, { nft_chain_set, ENOENT, "Bad built-in chain name" }, { nft_chain_set, EINVAL, "Bad policy name" }, { nft_chain_set, ENXIO, "Bad table name" }, { NULL, ELOOP, "Loop found in table" }, { NULL, EPERM, "Permission denied (you must be root)" }, { NULL, 0, "Incompatible with this kernel" }, { NULL, ENOPROTOOPT, "iptables who? (do you need to insmod?)" }, { NULL, ENOSYS, "Will be implemented real soon. I promise ;)" }, { NULL, ENOMEM, "Memory allocation problem" }, { NULL, ENOENT, "No chain/target/match by that name" }, }; for (i = 0; i < ARRAY_SIZE(table); i++) { if ((!table[i].fn || table[i].fn == nft_fn) && table[i].err == err) return table[i].message; } return strerror(err); } static int recover_rule_compat(struct nftnl_rule *r) { struct nftnl_expr_iter *iter; struct nftnl_expr *e; uint32_t reg; int ret = -1; iter = nftnl_expr_iter_create(r); if (!iter) return -1; next_expr: e = nftnl_expr_iter_next(iter); if (!e) goto out; if (strcmp("meta", nftnl_expr_get_str(e, NFTNL_EXPR_NAME)) || nftnl_expr_get_u32(e, NFTNL_EXPR_META_KEY) != NFT_META_L4PROTO) goto next_expr; reg = nftnl_expr_get_u32(e, NFTNL_EXPR_META_DREG); e = nftnl_expr_iter_next(iter); if (!e) goto out; if (strcmp("cmp", nftnl_expr_get_str(e, NFTNL_EXPR_NAME)) || reg != nftnl_expr_get_u32(e, NFTNL_EXPR_CMP_SREG)) goto next_expr; add_compat(r, nftnl_expr_get_u8(e, NFTNL_EXPR_CMP_DATA), nftnl_expr_get_u32(e, NFTNL_EXPR_CMP_OP) == NFT_CMP_NEQ); ret = 0; out: nftnl_expr_iter_destroy(iter); return ret; } struct chain_zero_data { struct nft_handle *handle; bool verbose; }; static int __nft_chain_zero_counters(struct nftnl_chain *c, void *data) { struct chain_zero_data *d = data; struct nft_handle *h = d->handle; struct nftnl_rule_iter *iter; struct nftnl_rule *r; if (d->verbose) fprintf(stdout, "Zeroing chain `%s'\n", nftnl_chain_get_str(c, NFTNL_CHAIN_NAME)); if (nftnl_chain_is_set(c, NFTNL_CHAIN_HOOKNUM)) { /* zero base chain counters. */ nftnl_chain_set_u64(c, NFTNL_CHAIN_PACKETS, 0); nftnl_chain_set_u64(c, NFTNL_CHAIN_BYTES, 0); nftnl_chain_unset(c, NFTNL_CHAIN_HANDLE); if (batch_chain_add(h, NFT_COMPAT_CHAIN_ZERO, c)) return -1; } nft_build_cache(h, c); iter = nftnl_rule_iter_create(c); if (iter == NULL) return -1; r = nftnl_rule_iter_next(iter); while (r != NULL) { struct nftnl_expr_iter *ei; struct nftnl_expr *e; bool zero_needed; ei = nftnl_expr_iter_create(r); if (!ei) break; e = nftnl_expr_iter_next(ei); zero_needed = false; while (e != NULL) { const char *en = nftnl_expr_get_str(e, NFTNL_EXPR_NAME); if (strcmp(en, "counter") == 0 && ( nftnl_expr_get_u64(e, NFTNL_EXPR_CTR_PACKETS) || nftnl_expr_get_u64(e, NFTNL_EXPR_CTR_BYTES))) { nftnl_expr_set_u64(e, NFTNL_EXPR_CTR_PACKETS, 0); nftnl_expr_set_u64(e, NFTNL_EXPR_CTR_BYTES, 0); zero_needed = true; } e = nftnl_expr_iter_next(ei); } nftnl_expr_iter_destroy(ei); if (zero_needed) { /* * Unset RULE_POSITION for older kernels, we want to replace * rule based on its handle only. */ recover_rule_compat(r); nftnl_rule_unset(r, NFTNL_RULE_POSITION); if (!batch_rule_add(h, NFT_COMPAT_RULE_REPLACE, r)) { nftnl_rule_iter_destroy(iter); return -1; } } r = nftnl_rule_iter_next(iter); } nftnl_rule_iter_destroy(iter); return 0; } int nft_chain_zero_counters(struct nft_handle *h, const char *chain, const char *table, bool verbose) { struct nftnl_chain_list *list; struct chain_zero_data d = { .handle = h, .verbose = verbose, }; struct nftnl_chain *c; int ret = 0; list = nft_chain_list_get(h, table, chain); if (list == NULL) goto err; if (chain) { c = nftnl_chain_list_lookup_byname(list, chain); if (!c) { errno = ENOENT; return 0; } ret = __nft_chain_zero_counters(c, &d); goto err; } ret = nftnl_chain_list_foreach(list, __nft_chain_zero_counters, &d); err: /* the core expects 1 for success and 0 for error */ return ret == 0 ? 1 : 0; } uint32_t nft_invflags2cmp(uint32_t invflags, uint32_t flag) { if (invflags & flag) return NFT_CMP_NEQ; return NFT_CMP_EQ; } static const char *supported_exprs[] = { "match", "target", "payload", "meta", "cmp", "bitwise", "counter", "immediate", "lookup", }; static int nft_is_expr_compatible(struct nftnl_expr *expr, void *data) { const char *name = nftnl_expr_get_str(expr, NFTNL_EXPR_NAME); int i; for (i = 0; i < ARRAY_SIZE(supported_exprs); i++) { if (strcmp(supported_exprs[i], name) == 0) return 0; } if (!strcmp(name, "limit") && nftnl_expr_get_u32(expr, NFTNL_EXPR_LIMIT_TYPE) == NFT_LIMIT_PKTS && nftnl_expr_get_u32(expr, NFTNL_EXPR_LIMIT_FLAGS) == 0) return 0; return -1; } static int nft_is_rule_compatible(struct nftnl_rule *rule, void *data) { return nftnl_expr_foreach(rule, nft_is_expr_compatible, NULL); } static int nft_is_chain_compatible(struct nftnl_chain *c, void *data) { const struct builtin_table *table; const struct builtin_chain *chain; const char *tname, *cname, *type; struct nft_handle *h = data; enum nf_inet_hooks hook; int prio; nft_build_cache(h, c); if (nftnl_rule_foreach(c, nft_is_rule_compatible, NULL)) return -1; if (!nft_chain_builtin(c)) return 0; tname = nftnl_chain_get_str(c, NFTNL_CHAIN_TABLE); table = nft_table_builtin_find(h, tname); if (!table) return -1; cname = nftnl_chain_get_str(c, NFTNL_CHAIN_NAME); chain = nft_chain_builtin_find(table, cname); if (!chain) return -1; type = nftnl_chain_get_str(c, NFTNL_CHAIN_TYPE); prio = nftnl_chain_get_u32(c, NFTNL_CHAIN_PRIO); hook = nftnl_chain_get_u32(c, NFTNL_CHAIN_HOOKNUM); if (strcmp(type, chain->type) || prio != chain->prio || hook != chain->hook) return -1; return 0; } bool nft_is_table_compatible(struct nft_handle *h, const char *table, const char *chain) { struct nftnl_chain_list *clist; clist = nft_chain_list_get(h, table, chain); if (clist == NULL) return false; if (nftnl_chain_list_foreach(clist, nft_is_chain_compatible, h)) return false; return true; } void nft_assert_table_compatible(struct nft_handle *h, const char *table, const char *chain) { const char *pfx = "", *sfx = ""; if (nft_is_table_compatible(h, table, chain)) return; if (chain) { pfx = "chain `"; sfx = "' in "; } else { chain = ""; } xtables_error(OTHER_PROBLEM, "%s%s%stable `%s' is incompatible, use 'nft' tool.\n", pfx, chain, sfx, table); }