diff options
Diffstat (limited to '')
-rw-r--r-- | collectors/nfacct.plugin/Makefile.am | 8 | ||||
l--------- | collectors/nfacct.plugin/README.md | 1 | ||||
-rw-r--r-- | collectors/nfacct.plugin/integrations/netfilter.md | 132 | ||||
-rw-r--r-- | collectors/nfacct.plugin/metadata.yaml | 133 | ||||
-rw-r--r-- | collectors/nfacct.plugin/plugin_nfacct.c | 879 |
5 files changed, 1153 insertions, 0 deletions
diff --git a/collectors/nfacct.plugin/Makefile.am b/collectors/nfacct.plugin/Makefile.am new file mode 100644 index 00000000..161784b8 --- /dev/null +++ b/collectors/nfacct.plugin/Makefile.am @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +AUTOMAKE_OPTIONS = subdir-objects +MAINTAINERCLEANFILES = $(srcdir)/Makefile.in + +dist_noinst_DATA = \ + README.md \ + $(NULL) diff --git a/collectors/nfacct.plugin/README.md b/collectors/nfacct.plugin/README.md new file mode 120000 index 00000000..ea320d13 --- /dev/null +++ b/collectors/nfacct.plugin/README.md @@ -0,0 +1 @@ +integrations/netfilter.md
\ No newline at end of file diff --git a/collectors/nfacct.plugin/integrations/netfilter.md b/collectors/nfacct.plugin/integrations/netfilter.md new file mode 100644 index 00000000..831b6fb5 --- /dev/null +++ b/collectors/nfacct.plugin/integrations/netfilter.md @@ -0,0 +1,132 @@ +<!--startmeta +custom_edit_url: "https://github.com/netdata/netdata/edit/master/collectors/nfacct.plugin/README.md" +meta_yaml: "https://github.com/netdata/netdata/edit/master/collectors/nfacct.plugin/metadata.yaml" +sidebar_label: "Netfilter" +learn_status: "Published" +learn_rel_path: "Data Collection/Linux Systems/Firewall" +most_popular: False +message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE COLLECTOR'S metadata.yaml FILE" +endmeta--> + +# Netfilter + + +<img src="https://netdata.cloud/img/netfilter.png" width="150"/> + + +Plugin: nfacct.plugin +Module: nfacct.plugin + +<img src="https://img.shields.io/badge/maintained%20by-Netdata-%2300ab44" /> + +## Overview + +Monitor Netfilter metrics for optimal packet filtering and manipulation. Keep tabs on packet counts, dropped packets, and error rates to secure network operations. + +Netdata uses libmnl (https://www.netfilter.org/projects/libmnl/index.html) to collect information. + +This collector is supported on all platforms. + +This collector supports collecting metrics from multiple instances of this integration, including remote instances. + +This plugin needs setuid. + +### Default Behavior + +#### Auto-Detection + +This plugin uses socket to connect with netfilter to collect data + +#### Limits + +The default configuration for this integration does not impose any limits on data collection. + +#### Performance Impact + +The default configuration for this integration is not expected to impose a significant performance impact on the system. + + +## Metrics + +Metrics grouped by *scope*. + +The scope defines the instance that the metric belongs to. An instance is uniquely identified by a set of labels. + + + +### Per Netfilter instance + + + +This scope has no labels. + +Metrics: + +| Metric | Dimensions | Unit | +|:------|:----------|:----| +| netfilter.netlink_new | new, ignore, invalid | connections/s | +| netfilter.netlink_changes | insert, delete, delete_list | changes/s | +| netfilter.netlink_search | searched, search_restart, found | searches/s | +| netfilter.netlink_errors | icmp_error, insert_failed, drop, early_drop | events/s | +| netfilter.netlink_expect | created, deleted, new | expectations/s | +| netfilter.nfacct_packets | a dimension per nfacct object | packets/s | +| netfilter.nfacct_bytes | a dimension per nfacct object | kilobytes/s | + + + +## Alerts + +There are no alerts configured by default for this integration. + + +## Setup + +### Prerequisites + +#### Install required packages + +Install `libmnl-dev` and `libnetfilter-acct-dev` using the package manager of your system. + + + +### Configuration + +#### File + +The configuration file name for this integration is `netdata.conf`. +Configuration for this specific integration is located in the `[plugin:nfacct]` section within that file. + +The file format is a modified INI syntax. The general structure is: + +```ini +[section1] + option1 = some value + option2 = some other value + +[section2] + option3 = some third value +``` +You can edit the configuration file using the `edit-config` script from the +Netdata [config directory](https://github.com/netdata/netdata/blob/master/docs/configure/nodes.md#the-netdata-config-directory). + +```bash +cd /etc/netdata 2>/dev/null || cd /opt/netdata/etc/netdata +sudo ./edit-config netdata.conf +``` +#### Options + + + +<details><summary>Config options</summary> + +| Name | Description | Default | Required | +|:----|:-----------|:-------|:--------:| +| update every | Data collection frequency. | 1 | no | +| command options | Additinal parameters for collector | | no | + +</details> + +#### Examples +There are no configuration examples. + + diff --git a/collectors/nfacct.plugin/metadata.yaml b/collectors/nfacct.plugin/metadata.yaml new file mode 100644 index 00000000..943471a3 --- /dev/null +++ b/collectors/nfacct.plugin/metadata.yaml @@ -0,0 +1,133 @@ +plugin_name: nfacct.plugin +modules: + - meta: + plugin_name: nfacct.plugin + module_name: nfacct.plugin + monitored_instance: + name: Netfilter + link: 'https://www.netfilter.org/' + categories: + - data-collection.linux-systems.firewall-metrics + icon_filename: 'netfilter.png' + related_resources: + integrations: + list: [] + info_provided_to_referring_integrations: + description: '' + keywords: [] + most_popular: false + overview: + data_collection: + metrics_description: 'Monitor Netfilter metrics for optimal packet filtering and manipulation. Keep tabs on packet counts, dropped packets, and error rates to secure network operations.' + method_description: 'Netdata uses libmnl (https://www.netfilter.org/projects/libmnl/index.html) to collect information.' + supported_platforms: + include: [] + exclude: [] + multi_instance: true + additional_permissions: + description: 'This plugin needs setuid.' + default_behavior: + auto_detection: + description: 'This plugin uses socket to connect with netfilter to collect data' + limits: + description: '' + performance_impact: + description: '' + setup: + prerequisites: + list: + - title: Install required packages + description: | + Install `libmnl-dev` and `libnetfilter-acct-dev` using the package manager of your system. + configuration: + file: + name: 'netdata.conf' + section_name: '[plugin:nfacct]' + description: 'This is netdata main configuration file' + options: + description: '' + folding: + title: 'Config options' + enabled: true + list: + - name: update every + description: Data collection frequency. + default_value: 1 + required: false + - name: command options + description: Additinal parameters for collector + default_value: "" + required: false + examples: + folding: + enabled: true + title: '' + list: [] + troubleshooting: + problems: + list: [] + alerts: [] + metrics: + folding: + title: Metrics + enabled: false + description: "" + availability: [] + scopes: + - name: global + description: "" + labels: [] + metrics: + - name: netfilter.netlink_new + description: Connection Tracker New Connections + unit: "connections/s" + chart_type: line + dimensions: + - name: new + - name: ignore + - name: invalid + - name: netfilter.netlink_changes + description: Connection Tracker Changes + unit: "changes/s" + chart_type: line + dimensions: + - name: insert + - name: delete + - name: delete_list + - name: netfilter.netlink_search + description: Connection Tracker Searches + unit: "searches/s" + chart_type: line + dimensions: + - name: searched + - name: search_restart + - name: found + - name: netfilter.netlink_errors + description: Connection Tracker Errors + unit: "events/s" + chart_type: line + dimensions: + - name: icmp_error + - name: insert_failed + - name: drop + - name: early_drop + - name: netfilter.netlink_expect + description: Connection Tracker Expectations + unit: "expectations/s" + chart_type: line + dimensions: + - name: created + - name: deleted + - name: new + - name: netfilter.nfacct_packets + description: Netfilter Accounting Packets + unit: "packets/s" + chart_type: line + dimensions: + - name: a dimension per nfacct object + - name: netfilter.nfacct_bytes + description: Netfilter Accounting Bandwidth + unit: "kilobytes/s" + chart_type: line + dimensions: + - name: a dimension per nfacct object diff --git a/collectors/nfacct.plugin/plugin_nfacct.c b/collectors/nfacct.plugin/plugin_nfacct.c new file mode 100644 index 00000000..2863cd7e --- /dev/null +++ b/collectors/nfacct.plugin/plugin_nfacct.c @@ -0,0 +1,879 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "libnetdata/libnetdata.h" +#include "libnetdata/required_dummies.h" + +#include <linux/netfilter/nfnetlink_conntrack.h> +#include <libmnl/libmnl.h> +#include <libnetfilter_acct/libnetfilter_acct.h> + +#define PLUGIN_NFACCT_NAME "nfacct.plugin" + +#define NETDATA_CHART_PRIO_NETFILTER_NEW 8701 +#define NETDATA_CHART_PRIO_NETFILTER_CHANGES 8702 +#define NETDATA_CHART_PRIO_NETFILTER_EXPECT 8703 +#define NETDATA_CHART_PRIO_NETFILTER_ERRORS 8705 +#define NETDATA_CHART_PRIO_NETFILTER_SEARCH 8710 + +#define NETDATA_CHART_PRIO_NETFILTER_PACKETS 8906 +#define NETDATA_CHART_PRIO_NETFILTER_BYTES 8907 + +#define NFACCT_RESTART_EVERY_SECONDS 86400 // restart the plugin every this many seconds + +static inline size_t mnl_buffer_size() { + long s = MNL_SOCKET_BUFFER_SIZE; + if(s <= 0) return 8192; + return (size_t)s; +} + +// variables +static int debug = 0; +static int netdata_update_every = 1; + +#define RRD_TYPE_NET_STAT_NETFILTER "netfilter" +#define RRD_TYPE_NET_STAT_CONNTRACK "netlink" + +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]; + + struct nlattr *tb_exp[CTA_STATS_EXP_MAX+1]; + const char *attr2name_exp[CTA_STATS_EXP_MAX+1]; + kernel_uint_t metrics_exp[CTA_STATS_EXP_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 = {}, + .tb_exp = {}, + .attr2name_exp = { + [CTA_STATS_EXP_NEW] = "new", + [CTA_STATS_EXP_CREATE] = "created", + [CTA_STATS_EXP_DELETE] = "deleted", + }, + .metrics_exp = {} +}; + + +static int nfstat_init(int update_every) { + nfstat_root.update_every = update_every; + + nfstat_root.buf_size = mnl_buffer_size(); + nfstat_root.buf = mallocz(nfstat_root.buf_size); + + nfstat_root.mnl = mnl_socket_open(NETLINK_NETFILTER); + if(!nfstat_root.mnl) { + collector_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) { + collector_error("NFSTAT: mnl_socket_bind() failed"); + return 1; + } + nfstat_root.portid = mnl_socket_get_portid(nfstat_root.mnl); + + return 0; +} + +static struct nlmsghdr * nfct_mnl_nlmsghdr_put(char *buf, uint16_t subsys, uint16_t type, uint8_t family, uint32_t seq) { + struct nlmsghdr *nlh; + struct nfgenmsg *nfh; + + nlh = mnl_nlmsg_put_header(buf); + nlh->nlmsg_type = (subsys << 8) | type; + nlh->nlmsg_flags = NLM_F_REQUEST|NLM_F_DUMP; + nlh->nlmsg_seq = seq; + + nfh = mnl_nlmsg_put_extra_header(nlh, sizeof(struct nfgenmsg)); + nfh->nfgen_family = family; + nfh->version = NFNETLINK_V0; + nfh->res_id = 0; + + return nlh; +} + +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) { + collector_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_conntrack() { + // zero all metrics - we will sum the metrics of all CPUs later + int i; + for (i = 0; i < CTA_STATS_MAX+1; i++) + nfstat_root.metrics[i] = 0; + + // prepare the request + nfstat_root.nlh = nfct_mnl_nlmsghdr_put(nfstat_root.buf, NFNL_SUBSYS_CTNETLINK, IPCTNL_MSG_CT_GET_STATS_CPU, AF_UNSPEC, nfstat_root.seq); + + // send the request + if(mnl_socket_sendto(nfstat_root.mnl, nfstat_root.nlh, nfstat_root.nlh->nlmsg_len) < 0) { + collector_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) { + collector_error("NFSTAT: error communicating with kernel. This plugin can only work when netdata runs as root."); + return 1; + } + + return 0; +} + +static int nfexp_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_EXP_MAX) < 0) + return MNL_CB_OK; + + if (mnl_attr_validate(attr, MNL_TYPE_U32) < 0) { + collector_error("NFSTAT EXP: mnl_attr_validate() failed"); + return MNL_CB_ERROR; + } + + tb[type] = attr; + return MNL_CB_OK; +} + +static int nfstat_callback_exp(const struct nlmsghdr *nlh, void *data) { + (void)data; + + struct nfgenmsg *nfg = mnl_nlmsg_get_payload(nlh); + + mnl_attr_parse(nlh, sizeof(*nfg), nfexp_stats_attr_cb, nfstat_root.tb_exp); + + int i; + for (i = 0; i < CTA_STATS_EXP_MAX+1; i++) { + if (nfstat_root.tb_exp[i]) { + nfstat_root.metrics_exp[i] += ntohl(mnl_attr_get_u32(nfstat_root.tb_exp[i])); + } + } + + return MNL_CB_OK; +} + +static int nfstat_collect_conntrack_expectations() { + // zero all metrics - we will sum the metrics of all CPUs later + int i; + for (i = 0; i < CTA_STATS_EXP_MAX+1; i++) + nfstat_root.metrics_exp[i] = 0; + + // prepare the request + nfstat_root.nlh = nfct_mnl_nlmsghdr_put(nfstat_root.buf, NFNL_SUBSYS_CTNETLINK_EXP, IPCTNL_MSG_EXP_GET_STATS_CPU, AF_UNSPEC, nfstat_root.seq); + + // send the request + if(mnl_socket_sendto(nfstat_root.mnl, nfstat_root.nlh, nfstat_root.nlh->nlmsg_len) < 0) { + collector_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_exp + , NULL + ) <= MNL_CB_STOP) + break; + } + + // verify we run without issues + if (ret == -1) { + collector_error("NFSTAT: error communicating with kernel. This plugin can only work when netdata runs as root."); + return 1; + } + + return 0; +} + +static int nfstat_collect() { + nfstat_root.seq++; + + if(nfstat_collect_conntrack()) + return 1; + + if(nfstat_collect_conntrack_expectations()) + return 1; + + return 0; +} + +static void nfstat_send_metrics() { + static int new_chart_generated = 0, changes_chart_generated = 0, search_chart_generated = 0, errors_chart_generated = 0, expect_chart_generated = 0; + + if(!new_chart_generated) { + new_chart_generated = 1; + + printf("CHART %s.%s '' 'Connection Tracker New Connections' 'connections/s' %s '' line %d %d %s\n" + , RRD_TYPE_NET_STAT_NETFILTER + , RRD_TYPE_NET_STAT_CONNTRACK "_new" + , RRD_TYPE_NET_STAT_CONNTRACK + , NETDATA_CHART_PRIO_NETFILTER_NEW + , nfstat_root.update_every + , PLUGIN_NFACCT_NAME + ); + printf("DIMENSION %s '' incremental 1 1\n", nfstat_root.attr2name[CTA_STATS_NEW]); + printf("DIMENSION %s '' incremental -1 1\n", nfstat_root.attr2name[CTA_STATS_IGNORE]); + printf("DIMENSION %s '' incremental -1 1\n", nfstat_root.attr2name[CTA_STATS_INVALID]); + } + + printf( + "BEGIN %s.%s\n" + , RRD_TYPE_NET_STAT_NETFILTER + , RRD_TYPE_NET_STAT_CONNTRACK "_new" + ); + printf( + "SET %s = %lld\n" + , nfstat_root.attr2name[CTA_STATS_NEW] + , (collected_number) nfstat_root.metrics[CTA_STATS_NEW] + ); + printf( + "SET %s = %lld\n" + , nfstat_root.attr2name[CTA_STATS_IGNORE] + , (collected_number) nfstat_root.metrics[CTA_STATS_IGNORE] + ); + printf( + "SET %s = %lld\n" + , nfstat_root.attr2name[CTA_STATS_INVALID] + , (collected_number) nfstat_root.metrics[CTA_STATS_INVALID] + ); + printf("END\n"); + + // ---------------------------------------------------------------- + + if(!changes_chart_generated) { + changes_chart_generated = 1; + + printf("CHART %s.%s '' 'Connection Tracker Changes' 'changes/s' %s '' line %d %d detail %s\n" + , RRD_TYPE_NET_STAT_NETFILTER + , RRD_TYPE_NET_STAT_CONNTRACK "_changes" + , RRD_TYPE_NET_STAT_CONNTRACK + , NETDATA_CHART_PRIO_NETFILTER_CHANGES + , nfstat_root.update_every + , PLUGIN_NFACCT_NAME + ); + printf("DIMENSION %s '' incremental 1 1\n", nfstat_root.attr2name[CTA_STATS_INSERT]); + printf("DIMENSION %s '' incremental -1 1\n", nfstat_root.attr2name[CTA_STATS_DELETE]); + printf("DIMENSION %s '' incremental -1 1\n", nfstat_root.attr2name[CTA_STATS_DELETE_LIST]); + } + + printf( + "BEGIN %s.%s\n" + , RRD_TYPE_NET_STAT_NETFILTER + , RRD_TYPE_NET_STAT_CONNTRACK "_changes" + ); + printf( + "SET %s = %lld\n" + , nfstat_root.attr2name[CTA_STATS_INSERT] + , (collected_number) nfstat_root.metrics[CTA_STATS_INSERT] + ); + printf( + "SET %s = %lld\n" + , nfstat_root.attr2name[CTA_STATS_DELETE] + , (collected_number) nfstat_root.metrics[CTA_STATS_DELETE] + ); + printf( + "SET %s = %lld\n" + , nfstat_root.attr2name[CTA_STATS_DELETE_LIST] + , (collected_number) nfstat_root.metrics[CTA_STATS_DELETE_LIST] + ); + printf("END\n"); + + // ---------------------------------------------------------------- + + if(!search_chart_generated) { + search_chart_generated = 1; + + printf("CHART %s.%s '' 'Connection Tracker Searches' 'searches/s' %s '' line %d %d detail %s\n" + , RRD_TYPE_NET_STAT_NETFILTER + , RRD_TYPE_NET_STAT_CONNTRACK "_search" + , RRD_TYPE_NET_STAT_CONNTRACK + , NETDATA_CHART_PRIO_NETFILTER_SEARCH + , nfstat_root.update_every + , PLUGIN_NFACCT_NAME + ); + printf("DIMENSION %s '' incremental 1 1\n", nfstat_root.attr2name[CTA_STATS_SEARCHED]); + printf("DIMENSION %s '' incremental -1 1\n", nfstat_root.attr2name[CTA_STATS_SEARCH_RESTART]); + printf("DIMENSION %s '' incremental 1 1\n", nfstat_root.attr2name[CTA_STATS_FOUND]); + } + + printf( + "BEGIN %s.%s\n" + , RRD_TYPE_NET_STAT_NETFILTER + , RRD_TYPE_NET_STAT_CONNTRACK "_search" + ); + printf( + "SET %s = %lld\n" + , nfstat_root.attr2name[CTA_STATS_SEARCHED] + , (collected_number) nfstat_root.metrics[CTA_STATS_SEARCHED] + ); + printf( + "SET %s = %lld\n" + , nfstat_root.attr2name[CTA_STATS_SEARCH_RESTART] + , (collected_number) nfstat_root.metrics[CTA_STATS_SEARCH_RESTART] + ); + printf( + "SET %s = %lld\n" + , nfstat_root.attr2name[CTA_STATS_FOUND] + , (collected_number) nfstat_root.metrics[CTA_STATS_FOUND] + ); + printf("END\n"); + + // ---------------------------------------------------------------- + + if(!errors_chart_generated) { + errors_chart_generated = 1; + + printf("CHART %s.%s '' 'Connection Tracker Errors' 'events/s' %s '' line %d %d detail %s\n" + , RRD_TYPE_NET_STAT_NETFILTER + , RRD_TYPE_NET_STAT_CONNTRACK "_errors" + , RRD_TYPE_NET_STAT_CONNTRACK + , NETDATA_CHART_PRIO_NETFILTER_ERRORS + , nfstat_root.update_every + , PLUGIN_NFACCT_NAME + ); + printf("DIMENSION %s '' incremental 1 1\n", nfstat_root.attr2name[CTA_STATS_ERROR]); + printf("DIMENSION %s '' incremental -1 1\n", nfstat_root.attr2name[CTA_STATS_INSERT_FAILED]); + printf("DIMENSION %s '' incremental -1 1\n", nfstat_root.attr2name[CTA_STATS_DROP]); + printf("DIMENSION %s '' incremental -1 1\n", nfstat_root.attr2name[CTA_STATS_EARLY_DROP]); + } + + printf( + "BEGIN %s.%s\n" + , RRD_TYPE_NET_STAT_NETFILTER + , RRD_TYPE_NET_STAT_CONNTRACK "_errors" + ); + printf( + "SET %s = %lld\n" + , nfstat_root.attr2name[CTA_STATS_ERROR] + , (collected_number) nfstat_root.metrics[CTA_STATS_ERROR] + ); + printf( + "SET %s = %lld\n" + , nfstat_root.attr2name[CTA_STATS_INSERT_FAILED] + , (collected_number) nfstat_root.metrics[CTA_STATS_INSERT_FAILED] + ); + printf( + "SET %s = %lld\n" + , nfstat_root.attr2name[CTA_STATS_DROP] + , (collected_number) nfstat_root.metrics[CTA_STATS_DROP] + ); + printf( + "SET %s = %lld\n" + , nfstat_root.attr2name[CTA_STATS_EARLY_DROP] + , (collected_number) nfstat_root.metrics[CTA_STATS_EARLY_DROP] + ); + printf("END\n"); + + // ---------------------------------------------------------------- + + if(!expect_chart_generated) { + expect_chart_generated = 1; + + printf("CHART %s.%s '' 'Connection Tracker Expectations' 'expectations/s' %s '' line %d %d detail %s\n" + , RRD_TYPE_NET_STAT_NETFILTER + , RRD_TYPE_NET_STAT_CONNTRACK "_expect" + , RRD_TYPE_NET_STAT_CONNTRACK + , NETDATA_CHART_PRIO_NETFILTER_EXPECT + , nfstat_root.update_every + , PLUGIN_NFACCT_NAME + ); + printf("DIMENSION %s '' incremental 1 1\n", nfstat_root.attr2name[CTA_STATS_EXP_CREATE]); + printf("DIMENSION %s '' incremental -1 1\n", nfstat_root.attr2name[CTA_STATS_EXP_DELETE]); + printf("DIMENSION %s '' incremental 1 1\n", nfstat_root.attr2name[CTA_STATS_EXP_NEW]); + } + + printf( + "BEGIN %s.%s\n" + , RRD_TYPE_NET_STAT_NETFILTER + , RRD_TYPE_NET_STAT_CONNTRACK "_expect" + ); + printf( + "SET %s = %lld\n" + , nfstat_root.attr2name[CTA_STATS_EXP_CREATE] + , (collected_number) nfstat_root.metrics[CTA_STATS_EXP_CREATE] + ); + printf( + "SET %s = %lld\n" + , nfstat_root.attr2name[CTA_STATS_EXP_DELETE] + , (collected_number) nfstat_root.metrics[CTA_STATS_EXP_DELETE] + ); + printf( + "SET %s = %lld\n" + , nfstat_root.attr2name[CTA_STATS_EXP_NEW] + , (collected_number) nfstat_root.metrics[CTA_STATS_EXP_NEW] + ); + printf("END\n"); +} + + +struct nfacct_data { + char *name; + uint32_t hash; + + uint64_t pkts; + uint64_t bytes; + + int packets_dimension_added; + int bytes_dimension_added; + + int updated; + + struct nfacct_data *next; +}; + +static struct { + int update_every; + char *buf; + size_t buf_size; + struct mnl_socket *mnl; + struct nlmsghdr *nlh; + unsigned int seq; + uint32_t portid; + struct nfacct *nfacct_buffer; + struct nfacct_data *nfacct_metrics; +} nfacct_root = { + .update_every = 1, + .buf = NULL, + .buf_size = 0, + .mnl = NULL, + .nlh = NULL, + .seq = 0, + .portid = 0, + .nfacct_buffer = NULL, + .nfacct_metrics = NULL +}; + +static inline struct nfacct_data *nfacct_data_get(const char *name, uint32_t hash) { + struct nfacct_data *d = NULL, *last = NULL; + for(d = nfacct_root.nfacct_metrics; d ; last = d, d = d->next) { + if(unlikely(d->hash == hash && !strcmp(d->name, name))) + return d; + } + + d = callocz(1, sizeof(struct nfacct_data)); + d->name = strdupz(name); + d->hash = hash; + + if(!last) { + d->next = nfacct_root.nfacct_metrics; + nfacct_root.nfacct_metrics = d; + } + else { + d->next = last->next; + last->next = d; + } + + return d; +} + +static int nfacct_init(int update_every) { + nfacct_root.update_every = update_every; + + nfacct_root.buf_size = mnl_buffer_size(); + nfacct_root.buf = mallocz(nfacct_root.buf_size); + + nfacct_root.nfacct_buffer = nfacct_alloc(); + if(!nfacct_root.nfacct_buffer) { + collector_error("nfacct.plugin: nfacct_alloc() failed."); + return 0; + } + + nfacct_root.seq = (unsigned int)now_realtime_sec() - 1; + + nfacct_root.mnl = mnl_socket_open(NETLINK_NETFILTER); + if(!nfacct_root.mnl) { + collector_error("nfacct.plugin: mnl_socket_open() failed"); + return 1; + } + + if(mnl_socket_bind(nfacct_root.mnl, 0, MNL_SOCKET_AUTOPID) < 0) { + collector_error("nfacct.plugin: mnl_socket_bind() failed"); + return 1; + } + nfacct_root.portid = mnl_socket_get_portid(nfacct_root.mnl); + + return 0; +} + +static int nfacct_callback(const struct nlmsghdr *nlh, void *data) { + (void)data; + + if(nfacct_nlmsg_parse_payload(nlh, nfacct_root.nfacct_buffer) < 0) { + collector_error("NFACCT: nfacct_nlmsg_parse_payload() failed."); + return MNL_CB_OK; + } + + const char *name = nfacct_attr_get_str(nfacct_root.nfacct_buffer, NFACCT_ATTR_NAME); + uint32_t hash = simple_hash(name); + + struct nfacct_data *d = nfacct_data_get(name, hash); + + d->pkts = nfacct_attr_get_u64(nfacct_root.nfacct_buffer, NFACCT_ATTR_PKTS); + d->bytes = nfacct_attr_get_u64(nfacct_root.nfacct_buffer, NFACCT_ATTR_BYTES); + d->updated = 1; + + return MNL_CB_OK; +} + +static int nfacct_collect() { + // mark all old metrics as not-updated + struct nfacct_data *d; + for(d = nfacct_root.nfacct_metrics; d ; d = d->next) + d->updated = 0; + + // prepare the request + nfacct_root.seq++; + nfacct_root.nlh = nfacct_nlmsg_build_hdr(nfacct_root.buf, NFNL_MSG_ACCT_GET, NLM_F_DUMP, (uint32_t)nfacct_root.seq); + if(!nfacct_root.nlh) { + collector_error("NFACCT: nfacct_nlmsg_build_hdr() failed"); + return 1; + } + + // send the request + if(mnl_socket_sendto(nfacct_root.mnl, nfacct_root.nlh, nfacct_root.nlh->nlmsg_len) < 0) { + collector_error("NFACCT: mnl_socket_sendto() failed"); + return 1; + } + + // get the reply + ssize_t ret; + while((ret = mnl_socket_recvfrom(nfacct_root.mnl, nfacct_root.buf, nfacct_root.buf_size)) > 0) { + if(mnl_cb_run( + nfacct_root.buf + , (size_t)ret + , nfacct_root.seq + , nfacct_root.portid + , nfacct_callback + , NULL + ) <= 0) + break; + } + + // verify we run without issues + if (ret == -1) { + collector_error("NFACCT: error communicating with kernel. This plugin can only work when netdata runs as root."); + return 1; + } + + return 0; +} + +static void nfacct_send_metrics() { + static int bytes_chart_generated = 0, packets_chart_generated = 0; + + if(!nfacct_root.nfacct_metrics) return; + struct nfacct_data *d; + + if(!packets_chart_generated) { + packets_chart_generated = 1; + printf("CHART netfilter.nfacct_packets '' 'Netfilter Accounting Packets' 'packets/s' 'nfacct' '' stacked %d %d %s\n" + , NETDATA_CHART_PRIO_NETFILTER_PACKETS + , nfacct_root.update_every + , PLUGIN_NFACCT_NAME + ); + } + + for(d = nfacct_root.nfacct_metrics; d ; d = d->next) { + if(likely(d->updated)) { + if(unlikely(!d->packets_dimension_added)) { + d->packets_dimension_added = 1; + printf( + "CHART netfilter.nfacct_packets '' 'Netfilter Accounting Packets' 'packets/s' 'nfacct' '' stacked %d %d %s\n", + NETDATA_CHART_PRIO_NETFILTER_PACKETS, + nfacct_root.update_every, + PLUGIN_NFACCT_NAME); + printf("DIMENSION %s '' incremental 1 %d\n", d->name, nfacct_root.update_every); + } + } + } + printf("BEGIN netfilter.nfacct_packets\n"); + for(d = nfacct_root.nfacct_metrics; d ; d = d->next) { + if(likely(d->updated)) { + printf("SET %s = %lld\n" + , d->name + , (collected_number)d->pkts + ); + } + } + printf("END\n"); + + // ---------------------------------------------------------------- + + if(!bytes_chart_generated) { + bytes_chart_generated = 1; + printf("CHART netfilter.nfacct_bytes '' 'Netfilter Accounting Bandwidth' 'kilobytes/s' 'nfacct' '' stacked %d %d %s\n" + , NETDATA_CHART_PRIO_NETFILTER_BYTES + , nfacct_root.update_every + , PLUGIN_NFACCT_NAME + ); + } + + for(d = nfacct_root.nfacct_metrics; d ; d = d->next) { + if(likely(d->updated)) { + if(unlikely(!d->bytes_dimension_added)) { + d->bytes_dimension_added = 1; + printf( + "CHART netfilter.nfacct_bytes '' 'Netfilter Accounting Bandwidth' 'kilobytes/s' 'nfacct' '' stacked %d %d %s\n", + NETDATA_CHART_PRIO_NETFILTER_BYTES, + nfacct_root.update_every, + PLUGIN_NFACCT_NAME); + printf("DIMENSION %s '' incremental 1 %d\n", d->name, 1000 * nfacct_root.update_every); + } + } + } + printf("BEGIN netfilter.nfacct_bytes\n"); + for(d = nfacct_root.nfacct_metrics; d ; d = d->next) { + if(likely(d->updated)) { + printf("SET %s = %lld\n" + , d->name + , (collected_number)d->bytes + ); + } + } + printf("END\n"); +} + +static void nfacct_signal_handler(int signo) +{ + exit((signo == SIGPIPE)?1:0); +} + +// When Netdata crashes this plugin was becoming zombie, +// this function was added to remove it when sigpipe and other signals are received. +void nfacct_signals() +{ + int signals[] = { SIGPIPE, SIGINT, SIGTERM, 0}; + int i; + struct sigaction sa; + sa.sa_flags = 0; + sa.sa_handler = nfacct_signal_handler; + + // ignore all signals while we run in a signal handler + sigfillset(&sa.sa_mask); + + for (i = 0; signals[i]; i++) { + if(sigaction(signals[i], &sa, NULL) == -1) + collector_error("Cannot add the handler to signal %d", signals[i]); + } +} + +int main(int argc, char **argv) { + clocks_init(); + nd_log_initialize_for_external_plugins("nfacct.plugin"); + + // ------------------------------------------------------------------------ + // parse command line parameters + + int i, freq = 0; + for(i = 1; i < argc ; i++) { + if(isdigit(*argv[i]) && !freq) { + int n = str2i(argv[i]); + if(n > 0 && n < 86400) { + freq = n; + continue; + } + } + else if(strcmp("version", argv[i]) == 0 || strcmp("-version", argv[i]) == 0 || strcmp("--version", argv[i]) == 0 || strcmp("-v", argv[i]) == 0 || strcmp("-V", argv[i]) == 0) { + printf("nfacct.plugin %s\n", VERSION); + exit(0); + } + else if(strcmp("debug", argv[i]) == 0) { + debug = 1; + continue; + } + else if(strcmp("-h", argv[i]) == 0 || strcmp("--help", argv[i]) == 0) { + fprintf(stderr, + "\n" + " netdata nfacct.plugin %s\n" + " Copyright (C) 2015-2017 Costa Tsaousis <costa@tsaousis.gr>\n" + " Released under GNU General Public License v3 or later.\n" + " All rights reserved.\n" + "\n" + " This program is a data collector plugin for netdata.\n" + "\n" + " Available command line options:\n" + "\n" + " COLLECTION_FREQUENCY data collection frequency in seconds\n" + " minimum: %d\n" + "\n" + " debug enable verbose output\n" + " default: disabled\n" + "\n" + " -v\n" + " -V\n" + " --version print version and exit\n" + "\n" + " -h\n" + " --help print this message and exit\n" + "\n" + " For more information:\n" + " https://github.com/netdata/netdata/tree/master/collectors/nfacct.plugin\n" + "\n" + , VERSION + , netdata_update_every + ); + exit(1); + } + + collector_error("nfacct.plugin: ignoring parameter '%s'", argv[i]); + } + + nfacct_signals(); + + errno = 0; + + if(freq >= netdata_update_every) + netdata_update_every = freq; + else if(freq) + collector_error("update frequency %d seconds is too small for NFACCT. Using %d.", freq, netdata_update_every); + + if (debug) + fprintf(stderr, "nfacct.plugin: calling nfacct_init()\n"); + int nfacct = !nfacct_init(netdata_update_every); + + if (debug) + fprintf(stderr, "nfacct.plugin: calling nfstat_init()\n"); + int nfstat = !nfstat_init(netdata_update_every); + + // ------------------------------------------------------------------------ + // the main loop + + if(debug) fprintf(stderr, "nfacct.plugin: starting data collection\n"); + + time_t started_t = now_monotonic_sec(); + + size_t iteration; + usec_t step = netdata_update_every * USEC_PER_SEC; + + heartbeat_t hb; + heartbeat_init(&hb); + for(iteration = 0; 1; iteration++) { + usec_t dt = heartbeat_next(&hb, step); + + if(unlikely(netdata_exit)) break; + + if(debug && iteration) + fprintf(stderr, "nfacct.plugin: iteration %zu, dt %"PRIu64" usec\n" + , iteration + , dt + ); + + if(likely(nfacct)) { + if(debug) fprintf(stderr, "nfacct.plugin: calling nfacct_collect()\n"); + nfacct = !nfacct_collect(); + + if(likely(nfacct)) { + if(debug) fprintf(stderr, "nfacct.plugin: calling nfacct_send_metrics()\n"); + nfacct_send_metrics(); + } + } + + if(likely(nfstat)) { + if(debug) fprintf(stderr, "nfacct.plugin: calling nfstat_collect()\n"); + nfstat = !nfstat_collect(); + + if(likely(nfstat)) { + if(debug) fprintf(stderr, "nfacct.plugin: calling nfstat_send_metrics()\n"); + nfstat_send_metrics(); + } + } + + fflush(stdout); + + if (now_monotonic_sec() - started_t > NFACCT_RESTART_EVERY_SECONDS) { + collector_info("NFACCT reached my lifetime expectancy. Exiting to restart."); + fprintf(stdout, "EXIT\n"); + fflush(stdout); + exit(0); + } + } +} |