summaryrefslogtreecommitdiffstats
path: root/src/netlink.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/netlink.c')
-rw-r--r--src/netlink.c547
1 files changed, 544 insertions, 3 deletions
diff --git a/src/netlink.c b/src/netlink.c
index 10951f96..22150f20 100644
--- a/src/netlink.c
+++ b/src/netlink.c
@@ -21,6 +21,8 @@
#include <libnftnl/chain.h>
#include <libnftnl/expr.h>
#include <libnftnl/set.h>
+#include <libnftnl/common.h>
+#include <linux/netfilter/nfnetlink.h>
#include <linux/netfilter/nf_tables.h>
#include <linux/netfilter.h>
@@ -33,6 +35,7 @@
#include <erec.h>
static struct mnl_socket *nf_sock;
+static struct mnl_socket *nf_mon_sock;
const struct input_descriptor indesc_netlink = {
.name = "netlink",
@@ -43,12 +46,18 @@ const struct location netlink_location = {
.indesc = &indesc_netlink,
};
-static void __init netlink_open_sock(void)
+static struct mnl_socket *nfsock_open(void)
{
- nf_sock = mnl_socket_open(NETLINK_NETFILTER);
- if (nf_sock == NULL)
+ struct mnl_socket *s = mnl_socket_open(NETLINK_NETFILTER);
+ if (s == NULL)
netlink_open_error();
+ return s;
+}
+
+static void __init netlink_open_sock(void)
+{
+ nf_sock = nfsock_open();
fcntl(mnl_socket_get_fd(nf_sock), F_SETFL, O_NONBLOCK);
mnl_batch_init();
}
@@ -56,6 +65,20 @@ static void __init netlink_open_sock(void)
static void __exit netlink_close_sock(void)
{
mnl_socket_close(nf_sock);
+ if (nf_mon_sock)
+ mnl_socket_close(nf_mon_sock);
+}
+
+static void netlink_open_mon_sock(void)
+{
+ nf_mon_sock = nfsock_open();
+}
+
+void __noreturn netlink_abi_error(void)
+{
+ fprintf(stderr, "E: Contact urgently your Linux kernel vendor. "
+ "Netlink ABI is broken: %s\n", strerror(errno));
+ exit(NFT_EXIT_FAILURE);
}
int netlink_io_error(struct netlink_ctx *ctx, const struct location *loc,
@@ -1062,3 +1085,521 @@ struct nft_ruleset *netlink_dump_ruleset(struct netlink_ctx *ctx,
return rs;
}
+
+static struct nft_table *netlink_table_alloc(const struct nlmsghdr *nlh)
+{
+ struct nft_table *nlt = nft_table_alloc();
+ if (nlt == NULL)
+ memory_allocation_error();
+
+ if (nft_table_nlmsg_parse(nlh, nlt) < 0)
+ netlink_abi_error();
+
+ return nlt;
+}
+
+static struct nft_chain *netlink_chain_alloc(const struct nlmsghdr *nlh)
+{
+ struct nft_chain *nlc = nft_chain_alloc();
+ if (nlc == NULL)
+ memory_allocation_error();
+
+ if (nft_chain_nlmsg_parse(nlh, nlc) < 0)
+ netlink_abi_error();
+
+ return nlc;
+}
+
+static struct nft_set *netlink_set_alloc(const struct nlmsghdr *nlh)
+{
+ struct nft_set *nls = nft_set_alloc();
+ if (nls == NULL)
+ memory_allocation_error();
+
+ if (nft_set_nlmsg_parse(nlh, nls) < 0)
+ netlink_abi_error();
+
+ return nls;
+}
+
+static struct nft_set *netlink_setelem_alloc(const struct nlmsghdr *nlh)
+{
+ struct nft_set *nls = nft_set_alloc();
+ if (nls == NULL)
+ memory_allocation_error();
+
+ if (nft_set_elems_nlmsg_parse(nlh, nls) < 0)
+ netlink_abi_error();
+
+ return nls;
+}
+
+static struct nft_rule *netlink_rule_alloc(const struct nlmsghdr *nlh)
+{
+ struct nft_rule *nlr = nft_rule_alloc();
+ if (nlr == NULL)
+ memory_allocation_error();
+
+ if (nft_rule_nlmsg_parse(nlh, nlr) < 0)
+ netlink_abi_error();
+
+ return nlr;
+}
+
+static uint32_t netlink_msg2nftnl_of(uint32_t msg)
+{
+ switch (msg) {
+ case NFT_MSG_NEWTABLE:
+ case NFT_MSG_NEWCHAIN:
+ case NFT_MSG_NEWSET:
+ case NFT_MSG_NEWSETELEM:
+ case NFT_MSG_NEWRULE:
+ return NFT_OF_EVENT_NEW;
+ case NFT_MSG_DELTABLE:
+ case NFT_MSG_DELCHAIN:
+ case NFT_MSG_DELSET:
+ case NFT_MSG_DELSETELEM:
+ case NFT_MSG_DELRULE:
+ return NFT_OF_EVENT_DEL;
+ }
+
+ return 0;
+}
+
+static int netlink_events_table_cb(const struct nlmsghdr *nlh, int type,
+ struct netlink_mon_handler *monh)
+{
+ uint32_t family;
+ struct nft_table *nlt = netlink_table_alloc(nlh);
+
+ if (monh->format == NFT_OUTPUT_DEFAULT) {
+ if (type == NFT_MSG_NEWTABLE) {
+ if (nlh->nlmsg_flags & NLM_F_EXCL)
+ printf("update table ");
+ else
+ printf("add table ");
+ } else {
+ printf("delete table ");
+ }
+
+ family = nft_table_attr_get_u32(nlt, NFT_TABLE_ATTR_FAMILY);
+
+ printf("%s %s\n", family2str(family),
+ nft_table_attr_get_str(nlt, NFT_TABLE_ATTR_NAME));
+ } else {
+ nft_table_fprintf(stdout, nlt, monh->format,
+ netlink_msg2nftnl_of(type));
+ fprintf(stdout, "\n");
+ }
+
+ nft_table_free(nlt);
+ return MNL_CB_OK;
+}
+
+static int netlink_events_chain_cb(const struct nlmsghdr *nlh, int type,
+ struct netlink_mon_handler *monh)
+{
+ struct chain *c;
+ uint32_t family;
+ struct nft_chain *nlc = netlink_chain_alloc(nlh);
+
+ if (monh->format == NFT_OUTPUT_DEFAULT) {
+ if (type == NFT_MSG_NEWCHAIN) {
+ if (nlh->nlmsg_flags & NLM_F_EXCL)
+ printf("update ");
+ else
+ printf("add ");
+
+ c = netlink_delinearize_chain(monh->ctx, nlc);
+ chain_print_plain(c);
+ chain_free(c);
+ } else {
+ family = nft_chain_attr_get_u32(nlc,
+ NFT_CHAIN_ATTR_FAMILY);
+ printf("delete chain %s %s %s\n", family2str(family),
+ nft_chain_attr_get_str(nlc,
+ NFT_CHAIN_ATTR_TABLE),
+ nft_chain_attr_get_str(nlc,
+ NFT_CHAIN_ATTR_NAME));
+ }
+ } else {
+ nft_chain_fprintf(stdout, nlc, monh->format,
+ netlink_msg2nftnl_of(type));
+ fprintf(stdout, "\n");
+ }
+
+ nft_chain_free(nlc);
+ return MNL_CB_OK;
+}
+
+static int netlink_events_set_cb(const struct nlmsghdr *nlh, int type,
+ struct netlink_mon_handler *monh)
+{
+ struct set *set;
+ uint32_t family, flags;
+ struct nft_set *nls = netlink_set_alloc(nlh);
+
+ flags = nft_set_attr_get_u32(nls, NFT_SET_ATTR_FLAGS);
+ if (flags & SET_F_ANONYMOUS)
+ goto out;
+
+ if (monh->format == NFT_OUTPUT_DEFAULT) {
+ if (type == NFT_MSG_NEWSET) {
+ printf("add ");
+ set = netlink_delinearize_set(monh->ctx, nls);
+ set_print_plain(set);
+ set_free(set);
+ } else {
+ family = nft_set_attr_get_u32(nls,
+ NFT_SET_ATTR_FAMILY);
+ printf("delete set %s %s %s",
+ family2str(family),
+ nft_set_attr_get_str(nls, NFT_SET_ATTR_TABLE),
+ nft_set_attr_get_str(nls, NFT_SET_ATTR_NAME));
+ }
+
+ printf("\n");
+
+ } else {
+ nft_set_fprintf(stdout, nls, monh->format,
+ netlink_msg2nftnl_of(type));
+ fprintf(stdout, "\n");
+ }
+
+out:
+ nft_set_free(nls);
+ return MNL_CB_OK;
+}
+
+static int netlink_events_setelem_cb(const struct nlmsghdr *nlh, int type,
+ struct netlink_mon_handler *monh)
+{
+ struct nft_set_elem *nlse;
+ struct nft_set_elems_iter *nlsei;
+ struct set *dummyset;
+ struct set *set;
+ const char *setname, *table;
+ uint32_t family;
+ struct nft_set *nls = netlink_setelem_alloc(nlh);
+
+ table = nft_set_attr_get_str(nls, NFT_SET_ATTR_TABLE);
+ setname = nft_set_attr_get_str(nls, NFT_SET_ATTR_NAME);
+ family = nft_set_attr_get_u32(nls, NFT_SET_ATTR_FAMILY);
+
+ set = set_lookup_global(family, table, setname);
+ if (set == NULL) {
+ fprintf(stderr, "W: Received event for an unknown set.");
+ goto out;
+ }
+
+ if (monh->format == NFT_OUTPUT_DEFAULT) {
+ if (set->flags & SET_F_ANONYMOUS)
+ goto out;
+
+ /* we want to 'delinearize' the set_elem, but don't
+ * modify the original cached set. This path is only
+ * used by named sets, so use a dummy set.
+ */
+ dummyset = set_alloc(monh->loc);
+ dummyset->keytype = set->keytype;
+ dummyset->datatype = set->datatype;
+ dummyset->init = set_expr_alloc(monh->loc);
+
+ nlsei = nft_set_elems_iter_create(nls);
+ if (nlsei == NULL)
+ memory_allocation_error();
+
+ nlse = nft_set_elems_iter_next(nlsei);
+ while (nlse != NULL) {
+ if (netlink_delinearize_setelem(nlse, dummyset) < 0) {
+ set_free(dummyset);
+ nft_set_elems_iter_destroy(nlsei);
+ goto out;
+ }
+ nlse = nft_set_elems_iter_next(nlsei);
+ }
+ nft_set_elems_iter_destroy(nlsei);
+
+ if (type == NFT_MSG_NEWSETELEM)
+ printf("add ");
+ else
+ printf("delete ");
+
+ printf("element %s %s %s ", family2str(family), table, setname);
+ expr_print(dummyset->init);
+ printf("\n");
+
+ set_free(dummyset);
+ } else {
+ nft_set_fprintf(stdout, nls, monh->format,
+ netlink_msg2nftnl_of(type));
+ fprintf(stdout, "\n");
+ }
+
+out:
+ nft_set_free(nls);
+ return MNL_CB_OK;
+}
+
+static int netlink_events_rule_cb(const struct nlmsghdr *nlh, int type,
+ struct netlink_mon_handler *monh)
+{
+ struct rule *r;
+ uint32_t fam;
+ const char *family;
+ const char *table;
+ const char *chain;
+ uint64_t handle;
+ struct nft_rule *nlr = netlink_rule_alloc(nlh);
+
+ if (monh->format == NFT_OUTPUT_DEFAULT) {
+ fam = nft_rule_attr_get_u32(nlr, NFT_RULE_ATTR_FAMILY);
+ family = family2str(fam);
+ table = nft_rule_attr_get_str(nlr, NFT_RULE_ATTR_TABLE);
+ chain = nft_rule_attr_get_str(nlr, NFT_RULE_ATTR_CHAIN);
+ handle = nft_rule_attr_get_u64(nlr, NFT_RULE_ATTR_HANDLE);
+
+ if (type == NFT_MSG_NEWRULE) {
+ r = netlink_delinearize_rule(monh->ctx, nlr);
+
+ printf("add rule %s %s %s", family, table, chain);
+ rule_print(r);
+ printf("\n");
+
+ rule_free(r);
+ goto out;
+ }
+
+ printf("delete rule %s %s %s handle %u\n",
+ family, table, chain, (unsigned int)handle);
+ } else {
+ nft_rule_fprintf(stdout, nlr, monh->format,
+ netlink_msg2nftnl_of(type));
+ fprintf(stdout, "\n");
+ }
+
+out:
+ nft_rule_free(nlr);
+ return MNL_CB_OK;
+}
+
+static void netlink_events_cache_addtable(struct netlink_mon_handler *monh,
+ const struct nlmsghdr *nlh)
+{
+ struct table *t;
+ struct nft_table *nlt = netlink_table_alloc(nlh);
+
+ t = netlink_delinearize_table(monh->ctx, nlt);
+ table_add_hash(t);
+
+ nft_table_free(nlt);
+}
+
+static void netlink_events_cache_deltable(struct netlink_mon_handler *monh,
+ const struct nlmsghdr *nlh)
+{
+ struct table *t;
+ struct handle h;
+ struct nft_table *nlt = netlink_table_alloc(nlh);
+
+ h.family = nft_table_attr_get_u32(nlt, NFT_TABLE_ATTR_FAMILY);
+ h.table = nft_table_attr_get_str(nlt, NFT_TABLE_ATTR_NAME);
+
+ t = table_lookup(&h);
+ if (t == NULL)
+ goto out;
+
+ list_del(&t->list);
+ table_free(t);
+
+out:
+ nft_table_free(nlt);
+}
+
+static void netlink_events_cache_addset(struct netlink_mon_handler *monh,
+ const struct nlmsghdr *nlh)
+{
+ struct set *s;
+ LIST_HEAD(msgs);
+ struct table *t;
+ struct netlink_ctx set_tmpctx;
+ struct nft_set *nls = netlink_set_alloc(nlh);
+
+ memset(&set_tmpctx, 0, sizeof(set_tmpctx));
+ init_list_head(&set_tmpctx.list);
+ init_list_head(&msgs);
+ set_tmpctx.msgs = &msgs;
+
+ s = netlink_delinearize_set(&set_tmpctx, nls);
+ s->init = set_expr_alloc(monh->loc);
+
+ t = table_lookup(&s->handle);
+ if (t == NULL) {
+ fprintf(stderr, "W: Unable to cache set: table not found.\n");
+ goto out;
+ }
+
+ set_add_hash(s, t);
+out:
+ nft_set_free(nls);
+}
+
+static void netlink_events_cache_addsetelem(struct netlink_mon_handler *monh,
+ const struct nlmsghdr *nlh)
+{
+ struct set *set;
+ struct nft_set_elem *nlse;
+ struct nft_set_elems_iter *nlsei;
+ const char *table, *setname;
+ struct nft_set *nls = netlink_setelem_alloc(nlh);
+
+ table = nft_set_attr_get_str(nls, NFT_SET_ATTR_TABLE);
+ setname = nft_set_attr_get_str(nls, NFT_SET_ATTR_NAME);
+
+ set = set_lookup_global(nft_set_attr_get_u32(nls, NFT_SET_ATTR_FAMILY),
+ table, setname);
+ if (set == NULL) {
+ fprintf(stderr,
+ "W: Unable to cache set_elem. Set not found.\n");
+ goto out;
+ }
+
+ nlsei = nft_set_elems_iter_create(nls);
+ if (nlsei == NULL)
+ memory_allocation_error();
+
+ nlse = nft_set_elems_iter_next(nlsei);
+ while (nlse != NULL) {
+ if (netlink_delinearize_setelem(nlse, set) < 0) {
+ fprintf(stderr,
+ "W: Unable to cache set_elem. "
+ "Delinearize failed.\n");
+ nft_set_elems_iter_destroy(nlsei);
+ goto out;
+ }
+ nlse = nft_set_elems_iter_next(nlsei);
+ }
+ nft_set_elems_iter_destroy(nlsei);
+
+out:
+ nft_set_free(nls);
+}
+
+static void netlink_events_cache_delsets(struct netlink_mon_handler *monh,
+ const struct nlmsghdr *nlh)
+{
+ struct set *s;
+ uint32_t family;
+ struct nft_rule_expr *nlre;
+ struct nft_rule_expr_iter *nlrei;
+ const char *expr_name, *set_name, *table;
+ struct nft_rule *nlr = netlink_rule_alloc(nlh);
+
+ nlrei = nft_rule_expr_iter_create(nlr);
+ if (nlrei == NULL)
+ memory_allocation_error();
+
+ family = nft_rule_attr_get_u32(nlr, NFT_RULE_ATTR_FAMILY);
+ table = nft_rule_attr_get_str(nlr, NFT_RULE_ATTR_TABLE);
+
+ nlre = nft_rule_expr_iter_next(nlrei);
+ while (nlre != NULL) {
+ expr_name = nft_rule_expr_get_str(nlre,
+ NFT_RULE_EXPR_ATTR_NAME);
+ if (strcmp(expr_name, "lookup") != 0)
+ goto next;
+
+ set_name = nft_rule_expr_get_str(nlre, NFT_EXPR_LOOKUP_SET);
+ s = set_lookup_global(family, table, set_name);
+ if (s == NULL)
+ goto next;
+
+ list_del(&s->list);
+ set_free(s);
+next:
+ nlre = nft_rule_expr_iter_next(nlrei);
+ }
+ nft_rule_expr_iter_destroy(nlrei);
+
+ nft_rule_free(nlr);
+}
+
+static void netlink_events_cache_update(struct netlink_mon_handler *monh,
+ const struct nlmsghdr *nlh, int type)
+{
+ if (!monh->cache_needed)
+ return;
+
+ switch (type) {
+ case NFT_MSG_NEWTABLE:
+ netlink_events_cache_addtable(monh, nlh);
+ break;
+ case NFT_MSG_DELTABLE:
+ netlink_events_cache_deltable(monh, nlh);
+ break;
+ case NFT_MSG_NEWSET:
+ netlink_events_cache_addset(monh, nlh);
+ break;
+ case NFT_MSG_NEWSETELEM:
+ netlink_events_cache_addsetelem(monh, nlh);
+ break;
+ case NFT_MSG_DELRULE:
+ /* there are no notification for anon-set deletion */
+ netlink_events_cache_delsets(monh, nlh);
+ break;
+ }
+}
+
+static int netlink_events_cb(const struct nlmsghdr *nlh, void *data)
+{
+ int ret = MNL_CB_OK;
+ int type = nlh->nlmsg_type & 0xFF;
+ struct netlink_mon_handler *monh = (struct netlink_mon_handler *)data;
+
+ netlink_events_cache_update(monh, nlh, type);
+
+ if (!(monh->monitor_flags & (1 << type)))
+ return ret;
+
+ switch (type) {
+ case NFT_MSG_NEWTABLE:
+ case NFT_MSG_DELTABLE:
+ ret = netlink_events_table_cb(nlh, type, monh);
+ break;
+ case NFT_MSG_NEWCHAIN:
+ case NFT_MSG_DELCHAIN:
+ ret = netlink_events_chain_cb(nlh, type, monh);
+ break;
+ case NFT_MSG_NEWSET:
+ case NFT_MSG_DELSET: /* nft {add|delete} set */
+ ret = netlink_events_set_cb(nlh, type, monh);
+ break;
+ case NFT_MSG_NEWSETELEM:
+ case NFT_MSG_DELSETELEM: /* nft {add|delete} element */
+ ret = netlink_events_setelem_cb(nlh, type, monh);
+ break;
+ case NFT_MSG_NEWRULE:
+ case NFT_MSG_DELRULE:
+ ret = netlink_events_rule_cb(nlh, type, monh);
+ break;
+ default:
+ BUG("Unknow event received from netlink.\n");
+ break;
+ }
+
+ return ret;
+}
+
+int netlink_monitor(struct netlink_mon_handler *monhandler)
+{
+ netlink_open_mon_sock();
+
+ if (mnl_socket_bind(nf_mon_sock, (1 << (NFNLGRP_NFTABLES-1)),
+ MNL_SOCKET_AUTOPID) < 0)
+ return netlink_io_error(monhandler->ctx, monhandler->loc,
+ "Could not bind to netlink socket %s",
+ strerror(errno));
+
+ return mnl_nft_event_listener(nf_mon_sock, netlink_events_cb,
+ monhandler);
+}