// SPDX-License-Identifier: GPL-3.0-or-later #include "plugin_freebsd.h" #include #define FREE_MEM_THRESHOLD 10000 // number of unused chunks that trigger memory freeing #define COMMON_IPFW_ERROR() error("DISABLED: ipfw.packets chart"); \ error("DISABLED: ipfw.bytes chart"); \ error("DISABLED: ipfw.dyn_active chart"); \ error("DISABLED: ipfw.dyn_expired chart"); \ error("DISABLED: ipfw.mem chart"); // -------------------------------------------------------------------------------------------------------------------- // ipfw int do_ipfw(int update_every, usec_t dt) { (void)dt; #if __FreeBSD__ >= 11 static int do_static = -1, do_dynamic = -1, do_mem = -1; if (unlikely(do_static == -1)) { do_static = config_get_boolean("plugin:freebsd:ipfw", "counters for static rules", 1); do_dynamic = config_get_boolean("plugin:freebsd:ipfw", "number of dynamic rules", 1); do_mem = config_get_boolean("plugin:freebsd:ipfw", "allocated memory", 1); } // variables for getting ipfw configuration int error; static int ipfw_socket = -1; static ipfw_cfg_lheader *cfg = NULL; ip_fw3_opheader *op3 = NULL; static socklen_t *optlen = NULL, cfg_size = 0; // variables for static rules handling ipfw_obj_ctlv *ctlv = NULL; ipfw_obj_tlv *rbase = NULL; int rcnt = 0; int n, seen; struct ip_fw_rule *rule; struct ip_fw_bcounter *cntr; int c = 0; char rule_num_str[12]; // variables for dynamic rules handling caddr_t dynbase = NULL; size_t dynsz = 0; size_t readsz = sizeof(*cfg);; int ttype = 0; ipfw_obj_tlv *tlv; ipfw_dyn_rule *dyn_rule; uint16_t rulenum, prev_rulenum = IPFW_DEFAULT_RULE; unsigned srn, static_rules_num = 0; static size_t dyn_rules_num_size = 0; static struct dyn_rule_num { uint16_t rule_num; uint32_t active_rules; uint32_t expired_rules; } *dyn_rules_num = NULL; uint32_t *dyn_rules_counter; if (likely(do_static | do_dynamic | do_mem)) { // initialize the smallest ipfw_cfg_lheader possible if (unlikely((optlen == NULL) || (cfg == NULL))) { optlen = reallocz(optlen, sizeof(socklen_t)); *optlen = cfg_size = 32; cfg = reallocz(cfg, *optlen); } // get socket descriptor and initialize ipfw_cfg_lheader structure if (unlikely(ipfw_socket == -1)) ipfw_socket = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); if (unlikely(ipfw_socket == -1)) { error("FREEBSD: can't get socket for ipfw configuration"); error("FREEBSD: run netdata as root to get access to ipfw data"); COMMON_IPFW_ERROR(); return 1; } bzero(cfg, 32); cfg->flags = IPFW_CFG_GET_STATIC | IPFW_CFG_GET_COUNTERS | IPFW_CFG_GET_STATES; op3 = &cfg->opheader; op3->opcode = IP_FW_XGET; // get ifpw configuration size than get configuration *optlen = cfg_size; error = getsockopt(ipfw_socket, IPPROTO_IP, IP_FW3, op3, optlen); if (error) if (errno != ENOMEM) { error("FREEBSD: ipfw socket reading error"); COMMON_IPFW_ERROR(); return 1; } if ((cfg->size > cfg_size) || ((cfg_size - cfg->size) > sizeof(struct dyn_rule_num) * FREE_MEM_THRESHOLD)) { *optlen = cfg_size = cfg->size; cfg = reallocz(cfg, *optlen); bzero(cfg, 32); cfg->flags = IPFW_CFG_GET_STATIC | IPFW_CFG_GET_COUNTERS | IPFW_CFG_GET_STATES; op3 = &cfg->opheader; op3->opcode = IP_FW_XGET; error = getsockopt(ipfw_socket, IPPROTO_IP, IP_FW3, op3, optlen); if (error) { error("FREEBSD: ipfw socket reading error"); COMMON_IPFW_ERROR(); return 1; } } // go through static rules configuration structures ctlv = (ipfw_obj_ctlv *) (cfg + 1); if (cfg->flags & IPFW_CFG_GET_STATIC) { /* We've requested static rules */ if (ctlv->head.type == IPFW_TLV_TBLNAME_LIST) { readsz += ctlv->head.length; ctlv = (ipfw_obj_ctlv *) ((caddr_t) ctlv + ctlv->head.length); } if (ctlv->head.type == IPFW_TLV_RULE_LIST) { rbase = (ipfw_obj_tlv *) (ctlv + 1); rcnt = ctlv->count; readsz += ctlv->head.length; ctlv = (ipfw_obj_ctlv *) ((caddr_t) ctlv + ctlv->head.length); } } if ((cfg->flags & IPFW_CFG_GET_STATES) && (readsz != *optlen)) { /* We may have some dynamic states */ dynsz = *optlen - readsz; /* Skip empty header */ if (dynsz != sizeof(ipfw_obj_ctlv)) dynbase = (caddr_t) ctlv; else dynsz = 0; } // -------------------------------------------------------------------- if (likely(do_mem)) { static RRDSET *st_mem = NULL; static RRDDIM *rd_dyn_mem = NULL; static RRDDIM *rd_stat_mem = NULL; if (unlikely(!st_mem)) { st_mem = rrdset_create_localhost("ipfw", "mem", NULL, "memory allocated", NULL, "Memory allocated by rules", "bytes", "freebsd.plugin", "ipfw", NETDATA_CHART_PRIO_IPFW_MEM, update_every, RRDSET_TYPE_STACKED ); rrdset_flag_set(st_mem, RRDSET_FLAG_DETAIL); rd_dyn_mem = rrddim_add(st_mem, "dynamic", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); rd_stat_mem = rrddim_add(st_mem, "static", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); } else rrdset_next(st_mem); rrddim_set_by_pointer(st_mem, rd_dyn_mem, dynsz); rrddim_set_by_pointer(st_mem, rd_stat_mem, *optlen - dynsz); rrdset_done(st_mem); } // -------------------------------------------------------------------- static RRDSET *st_packets = NULL, *st_bytes = NULL; RRDDIM *rd_packets = NULL, *rd_bytes = NULL; if (likely(do_static || do_dynamic)) { if (likely(do_static)) { if (unlikely(!st_packets)) st_packets = rrdset_create_localhost("ipfw", "packets", NULL, "static rules", NULL, "Packets", "packets/s", "freebsd.plugin", "ipfw", NETDATA_CHART_PRIO_IPFW_PACKETS, update_every, RRDSET_TYPE_STACKED ); else rrdset_next(st_packets); if (unlikely(!st_bytes)) st_bytes = rrdset_create_localhost("ipfw", "bytes", NULL, "static rules", NULL, "Bytes", "bytes/s", "freebsd.plugin", "ipfw", NETDATA_CHART_PRIO_IPFW_BYTES, update_every, RRDSET_TYPE_STACKED ); else rrdset_next(st_bytes); } for (n = seen = 0; n < rcnt; n++, rbase = (ipfw_obj_tlv *) ((caddr_t) rbase + rbase->length)) { cntr = (struct ip_fw_bcounter *) (rbase + 1); rule = (struct ip_fw_rule *) ((caddr_t) cntr + cntr->size); if (rule->rulenum != prev_rulenum) static_rules_num++; if (rule->rulenum > IPFW_DEFAULT_RULE) break; if (likely(do_static)) { sprintf(rule_num_str, "%"PRIu32"_%"PRIu32"", (uint32_t)rule->rulenum, (uint32_t)rule->id); rd_packets = rrddim_find_active(st_packets, rule_num_str); if (unlikely(!rd_packets)) rd_packets = rrddim_add(st_packets, rule_num_str, NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); rrddim_set_by_pointer(st_packets, rd_packets, cntr->pcnt); rd_bytes = rrddim_find_active(st_bytes, rule_num_str); if (unlikely(!rd_bytes)) rd_bytes = rrddim_add(st_bytes, rule_num_str, NULL, 1, 1, RRD_ALGORITHM_INCREMENTAL); rrddim_set_by_pointer(st_bytes, rd_bytes, cntr->bcnt); } c += rbase->length; seen++; } if (likely(do_static)) { rrdset_done(st_packets); rrdset_done(st_bytes); } } // -------------------------------------------------------------------- // go through dynamic rules configuration structures if (likely(do_dynamic && (dynsz > 0))) { if ((dyn_rules_num_size < sizeof(struct dyn_rule_num) * static_rules_num) || ((dyn_rules_num_size - sizeof(struct dyn_rule_num) * static_rules_num) > sizeof(struct dyn_rule_num) * FREE_MEM_THRESHOLD)) { dyn_rules_num_size = sizeof(struct dyn_rule_num) * static_rules_num; dyn_rules_num = reallocz(dyn_rules_num, dyn_rules_num_size); } bzero(dyn_rules_num, sizeof(struct dyn_rule_num) * static_rules_num); dyn_rules_num->rule_num = IPFW_DEFAULT_RULE; if (dynsz > 0 && ctlv->head.type == IPFW_TLV_DYNSTATE_LIST) { dynbase += sizeof(*ctlv); dynsz -= sizeof(*ctlv); ttype = IPFW_TLV_DYN_ENT; } while (dynsz > 0) { tlv = (ipfw_obj_tlv *) dynbase; if (tlv->type != ttype) break; dyn_rule = (ipfw_dyn_rule *) (tlv + 1); bcopy(&dyn_rule->rule, &rulenum, sizeof(rulenum)); for (srn = 0; srn < (static_rules_num - 1); srn++) { if (dyn_rule->expire > 0) dyn_rules_counter = &dyn_rules_num[srn].active_rules; else dyn_rules_counter = &dyn_rules_num[srn].expired_rules; if (dyn_rules_num[srn].rule_num == rulenum) { (*dyn_rules_counter)++; break; } if (dyn_rules_num[srn].rule_num == IPFW_DEFAULT_RULE) { dyn_rules_num[srn].rule_num = rulenum; dyn_rules_num[srn + 1].rule_num = IPFW_DEFAULT_RULE; (*dyn_rules_counter)++; break; } } dynsz -= tlv->length; dynbase += tlv->length; } // -------------------------------------------------------------------- static RRDSET *st_active = NULL, *st_expired = NULL; RRDDIM *rd_active = NULL, *rd_expired = NULL; if (unlikely(!st_active)) st_active = rrdset_create_localhost("ipfw", "active", NULL, "dynamic_rules", NULL, "Active rules", "rules", "freebsd.plugin", "ipfw", NETDATA_CHART_PRIO_IPFW_ACTIVE, update_every, RRDSET_TYPE_STACKED ); else rrdset_next(st_active); if (unlikely(!st_expired)) st_expired = rrdset_create_localhost("ipfw", "expired", NULL, "dynamic_rules", NULL, "Expired rules", "rules", "freebsd.plugin", "ipfw", NETDATA_CHART_PRIO_IPFW_EXPIRED, update_every, RRDSET_TYPE_STACKED ); else rrdset_next(st_expired); for (srn = 0; (srn < (static_rules_num - 1)) && (dyn_rules_num[srn].rule_num != IPFW_DEFAULT_RULE); srn++) { sprintf(rule_num_str, "%d", dyn_rules_num[srn].rule_num); rd_active = rrddim_find_active(st_active, rule_num_str); if (unlikely(!rd_active)) rd_active = rrddim_add(st_active, rule_num_str, NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); rrddim_set_by_pointer(st_active, rd_active, dyn_rules_num[srn].active_rules); rd_expired = rrddim_find_active(st_expired, rule_num_str); if (unlikely(!rd_expired)) rd_expired = rrddim_add(st_expired, rule_num_str, NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); rrddim_set_by_pointer(st_expired, rd_expired, dyn_rules_num[srn].expired_rules); } rrdset_done(st_active); rrdset_done(st_expired); } } return 0; #else error("FREEBSD: ipfw charts supported for FreeBSD 11.0 and newer releases only"); COMMON_IPFW_ERROR(); return 1; #endif }