+
+
+// ----------------------------------------------------------------------------
+// DO_NFSTAT - collect netfilter connection tracker statistics via netlink
+// example: https://github.com/formorer/pkg-conntrack-tools/blob/master/src/conntrack.c
+
+#ifdef HAVE_LINUX_NETFILTER_NFNETLINK_CONNTRACK_H
+#define DO_NFSTAT 1
+
+#include <linux/netfilter/nfnetlink_conntrack.h>
+
+static struct {
+ int update_every;
+ char *buf;
+ size_t buf_size;
+ struct mnl_socket *mnl;
+ struct nlmsghdr *nlh;
+ struct nfgenmsg *nfh;
+ unsigned int seq;
+ uint32_t portid;
+ struct nlattr *tb[CTA_STATS_MAX+1];
+ const char *attr2name[CTA_STATS_MAX+1];
+ kernel_uint_t metrics[CTA_STATS_MAX+1];
+} nfstat_root = {
+ .update_every = 1,
+ .buf = NULL,
+ .buf_size = 0,
+ .mnl = NULL,
+ .nlh = NULL,
+ .nfh = NULL,
+ .seq = 0,
+ .portid = 0,
+ .tb = {},
+ .attr2name = {
+ [CTA_STATS_SEARCHED] = "searched",
+ [CTA_STATS_FOUND] = "found",
+ [CTA_STATS_NEW] = "new",
+ [CTA_STATS_INVALID] = "invalid",
+ [CTA_STATS_IGNORE] = "ignore",
+ [CTA_STATS_DELETE] = "delete",
+ [CTA_STATS_DELETE_LIST] = "delete_list",
+ [CTA_STATS_INSERT] = "insert",
+ [CTA_STATS_INSERT_FAILED] = "insert_failed",
+ [CTA_STATS_DROP] = "drop",
+ [CTA_STATS_EARLY_DROP] = "early_drop",
+ [CTA_STATS_ERROR] = "icmp_error",
+ [CTA_STATS_SEARCH_RESTART] = "search_restart",
+ },
+ .metrics = {}
+};
+
+
+static int nfstat_init(int update_every) {
+ nfstat_root.update_every = update_every;
+
+ nfstat_root.buf_size = (size_t)MNL_SOCKET_BUFFER_SIZE;
+ nfstat_root.buf = mallocz(nfstat_root.buf_size);
+
+ nfstat_root.mnl = mnl_socket_open(NETLINK_NETFILTER);
+ if(!nfstat_root.mnl) {
+ error("NFSTAT: mnl_socket_open() failed");
+ return 1;
+ }
+
+ nfstat_root.seq = (unsigned int)now_realtime_sec() - 1;
+
+ if(mnl_socket_bind(nfstat_root.mnl, 0, MNL_SOCKET_AUTOPID) < 0) {
+ error("NFSTAT: mnl_socket_bind() failed");
+ return 1;
+ }
+ nfstat_root.portid = mnl_socket_get_portid(nfstat_root.mnl);
+
+ return 0;
+}
+
+static void nfstat_cleanup() {
+ if(nfstat_root.mnl) {
+ mnl_socket_close(nfstat_root.mnl);
+ nfstat_root.mnl = NULL;
+ }
+
+ freez(nfstat_root.buf);
+ nfstat_root.buf = NULL;
+ nfstat_root.buf_size = 0;
+}
+
+static int nfct_stats_attr_cb(const struct nlattr *attr, void *data) {
+ const struct nlattr **tb = data;
+ int type = mnl_attr_get_type(attr);
+
+ if (mnl_attr_type_valid(attr, CTA_STATS_MAX) < 0)
+ return MNL_CB_OK;
+
+ if (mnl_attr_validate(attr, MNL_TYPE_U32) < 0) {
+ error("NFSTAT: mnl_attr_validate() failed");
+ return MNL_CB_ERROR;
+ }
+
+ tb[type] = attr;
+ return MNL_CB_OK;
+}
+
+static int nfstat_callback(const struct nlmsghdr *nlh, void *data) {
+ (void)data;
+
+ struct nfgenmsg *nfg = mnl_nlmsg_get_payload(nlh);
+
+ mnl_attr_parse(nlh, sizeof(*nfg), nfct_stats_attr_cb, nfstat_root.tb);
+
+ // printf("cpu=%-4u\t", ntohs(nfg->res_id));
+
+ int i;
+ // add the metrics of this CPU into the metrics
+ for (i = 0; i < CTA_STATS_MAX+1; i++) {
+ if (nfstat_root.tb[i]) {
+ // printf("%s=%u ", nfstat_root.attr2name[i], ntohl(mnl_attr_get_u32(nfstat_root.tb[i])));
+ nfstat_root.metrics[i] += ntohl(mnl_attr_get_u32(nfstat_root.tb[i]));
+ }
+ }
+ // printf("\n");
+
+ return MNL_CB_OK;
+}
+
+static int nfstat_collect() {
+ int i;
+
+ // zero all metrics - we will sum the metrics of all CPUs later
+ for (i = 0; i < CTA_STATS_MAX+1; i++)
+ nfstat_root.metrics[i] = 0;
+
+ // prepare the request
+ nfstat_root.nlh = mnl_nlmsg_put_header(nfstat_root.buf);
+ nfstat_root.nlh->nlmsg_type = (NFNL_SUBSYS_CTNETLINK << 8) | IPCTNL_MSG_CT_GET_STATS_CPU;
+ nfstat_root.nlh->nlmsg_flags = NLM_F_REQUEST|NLM_F_DUMP;
+ nfstat_root.nlh->nlmsg_seq = nfstat_root.seq++;
+
+ nfstat_root.nfh = mnl_nlmsg_put_extra_header(nfstat_root.nlh, sizeof(struct nfgenmsg));
+ nfstat_root.nfh->nfgen_family = AF_UNSPEC;
+ nfstat_root.nfh->version = NFNETLINK_V0;
+ nfstat_root.nfh->res_id = 0;
+
+ // send the request
+ if(mnl_socket_sendto(nfstat_root.mnl, nfstat_root.nlh, nfstat_root.nlh->nlmsg_len) < 0) {
+ error("NFSTAT: mnl_socket_sendto() failed");
+ return 1;
+ }
+
+ // get the reply
+ ssize_t ret;
+ while ((ret = mnl_socket_recvfrom(nfstat_root.mnl, nfstat_root.buf, nfstat_root.buf_size)) > 0) {
+ if(mnl_cb_run(
+ nfstat_root.buf
+ , (size_t)ret
+ , nfstat_root.nlh->nlmsg_seq
+ , nfstat_root.portid
+ , nfstat_callback
+ , NULL
+ ) <= MNL_CB_STOP)
+ break;
+ }
+
+ // verify we run without issues
+ if (ret == -1) {
+ error("NFSTAT: error communicating with kernel. This plugin can only work when netdata runs as root.");
+ return 1;
+ }
+
+ return 0;
+}
+
+#define RRD_TYPE_NET_STAT_NETFILTER "netfilter2"
+#define RRD_TYPE_NET_STAT_CONNTRACK "conntrack2"
+
+static void nfstat_send_metrics() {
+
+ {
+ static RRDSET *st_new = NULL;
+ static RRDDIM *rd_new = NULL, *rd_ignore = NULL, *rd_invalid = NULL;
+
+ if(!st_new) {
+ st_new = rrdset_create_localhost(
+ RRD_TYPE_NET_STAT_NETFILTER
+ , RRD_TYPE_NET_STAT_CONNTRACK "_new"
+ , NULL
+ , RRD_TYPE_NET_STAT_CONNTRACK
+ , NULL
+ , "Connection Tracker New Connections"
+ , "connections/s"
+ , 3001
+ , nfstat_root.update_every
+ , RRDSET_TYPE_LINE
+ );
+
+ rd_new = rrddim_add(st_new, nfstat_root.attr2name[CTA_STATS_NEW], NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_ignore = rrddim_add(st_new, nfstat_root.attr2name[CTA_STATS_IGNORE], NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_invalid = rrddim_add(st_new, nfstat_root.attr2name[CTA_STATS_INVALID], NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else
+ rrdset_next(st_new);
+
+ rrddim_set_by_pointer(st_new, rd_new, (collected_number) nfstat_root.metrics[CTA_STATS_NEW]);
+ rrddim_set_by_pointer(st_new, rd_ignore, (collected_number) nfstat_root.metrics[CTA_STATS_IGNORE]);
+ rrddim_set_by_pointer(st_new, rd_invalid, (collected_number) nfstat_root.metrics[CTA_STATS_INVALID]);
+
+ rrdset_done(st_new);
+ }
+
+ // ----------------------------------------------------------------
+
+ {
+ static RRDSET *st_changes = NULL;
+ static RRDDIM *rd_inserted = NULL, *rd_deleted = NULL, *rd_delete_list = NULL;
+
+ if(!st_changes) {
+ st_changes = rrdset_create_localhost(
+ RRD_TYPE_NET_STAT_NETFILTER
+ , RRD_TYPE_NET_STAT_CONNTRACK "_changes"
+ , NULL
+ , RRD_TYPE_NET_STAT_CONNTRACK
+ , NULL
+ , "Connection Tracker Changes"
+ , "changes/s"
+ , 3002
+ , nfstat_root.update_every
+ , RRDSET_TYPE_LINE
+ );
+ rrdset_flag_set(st_changes, RRDSET_FLAG_DETAIL);
+
+ rd_inserted = rrddim_add(st_changes, nfstat_root.attr2name[CTA_STATS_INSERT], NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_deleted = rrddim_add(st_changes, nfstat_root.attr2name[CTA_STATS_DELETE], NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_delete_list = rrddim_add(st_changes, nfstat_root.attr2name[CTA_STATS_DELETE_LIST], NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else
+ rrdset_next(st_changes);
+
+ rrddim_set_by_pointer(st_changes, rd_inserted, (collected_number) nfstat_root.metrics[CTA_STATS_INSERT]);
+ rrddim_set_by_pointer(st_changes, rd_deleted, (collected_number) nfstat_root.metrics[CTA_STATS_DELETE]);
+ rrddim_set_by_pointer(st_changes, rd_delete_list, (collected_number) nfstat_root.metrics[CTA_STATS_DELETE_LIST]);
+
+ rrdset_done(st_changes);
+ }
+
+ // ----------------------------------------------------------------
+
+ {
+ static RRDSET *st_search = NULL;
+ static RRDDIM *rd_searched = NULL, *rd_restarted = NULL, *rd_found = NULL;
+
+ if(!st_search) {
+ st_search = rrdset_create_localhost(
+ RRD_TYPE_NET_STAT_NETFILTER
+ , RRD_TYPE_NET_STAT_CONNTRACK "_search"
+ , NULL
+ , RRD_TYPE_NET_STAT_CONNTRACK
+ , NULL
+ , "Connection Tracker Searches"
+ , "searches/s"
+ , 3010
+ , nfstat_root.update_every
+ , RRDSET_TYPE_LINE
+ );
+ rrdset_flag_set(st_search, RRDSET_FLAG_DETAIL);
+
+ rd_searched = rrddim_add(st_search, nfstat_root.attr2name[CTA_STATS_SEARCHED], NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_restarted = rrddim_add(st_search, nfstat_root.attr2name[CTA_STATS_SEARCH_RESTART], NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_found = rrddim_add(st_search, nfstat_root.attr2name[CTA_STATS_FOUND], NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else
+ rrdset_next(st_search);
+
+ rrddim_set_by_pointer(st_search, rd_searched, (collected_number) nfstat_root.metrics[CTA_STATS_SEARCHED]);
+ rrddim_set_by_pointer(st_search, rd_restarted, (collected_number) nfstat_root.metrics[CTA_STATS_SEARCH_RESTART]);
+ rrddim_set_by_pointer(st_search, rd_found, (collected_number) nfstat_root.metrics[CTA_STATS_FOUND]);
+
+ rrdset_done(st_search);
+ }
+
+ // ----------------------------------------------------------------
+
+ {
+ static RRDSET *st_errors = NULL;
+ static RRDDIM *rd_error = NULL, *rd_insert_failed = NULL, *rd_drop = NULL, *rd_early_drop = NULL;
+
+ if(!st_errors) {
+ st_errors = rrdset_create_localhost(
+ RRD_TYPE_NET_STAT_NETFILTER
+ , RRD_TYPE_NET_STAT_CONNTRACK "_errors"
+ , NULL
+ , RRD_TYPE_NET_STAT_CONNTRACK
+ , NULL
+ , "Connection Tracker Errors"
+ , "events/s"
+ , 3005
+ , nfstat_root.update_every
+ , RRDSET_TYPE_LINE
+ );
+ rrdset_flag_set(st_errors, RRDSET_FLAG_DETAIL);
+
+ rd_error = rrddim_add(st_errors, nfstat_root.attr2name[CTA_STATS_ERROR], NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_insert_failed = rrddim_add(st_errors, nfstat_root.attr2name[CTA_STATS_INSERT_FAILED], NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_drop = rrddim_add(st_errors, nfstat_root.attr2name[CTA_STATS_DROP], NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ rd_early_drop = rrddim_add(st_errors, nfstat_root.attr2name[CTA_STATS_EARLY_DROP], NULL, -1, 1, RRD_ALGORITHM_INCREMENTAL);
+ }
+ else
+ rrdset_next(st_errors);
+
+ rrddim_set_by_pointer(st_errors, rd_error, (collected_number) nfstat_root.metrics[CTA_STATS_ERROR]);
+ rrddim_set_by_pointer(st_errors, rd_insert_failed, (collected_number) nfstat_root.metrics[CTA_STATS_INSERT_FAILED]);
+ rrddim_set_by_pointer(st_errors, rd_drop, (collected_number) nfstat_root.metrics[CTA_STATS_DROP]);
+ rrddim_set_by_pointer(st_errors, rd_early_drop, (collected_number) nfstat_root.metrics[CTA_STATS_EARLY_DROP]);
+
+ rrdset_done(st_errors);
+ }
+}
+
+#endif // HAVE_LINUX_NETFILTER_NFNETLINK_CONNTRACK_H
+
+
+// ----------------------------------------------------------------------------
+// DO_NFACCT - collect netfilter accounting statistics via netlink
+
+#ifdef HAVE_LIBNETFILTER_ACCT
+#define DO_NFACCT 1
+