88551536e6
Amp-Thread-ID: https://ampcode.com/threads/T-019cc404-c628-759e-a50b-f5eeea35b91f Co-authored-by: Amp <amp@ampcode.com>
629 lines
23 KiB
Bash
Executable File
629 lines
23 KiB
Bash
Executable File
#!/bin/bash
|
|
################################################################################
|
|
# Script Name: iptables-blocklist-metrics.sh
|
|
# Version: 2.0
|
|
# Description: Prometheus exporter for iptables threat feed blocking metrics
|
|
# Author: Phil Connor
|
|
# Contact: contact@mylinux.work
|
|
# Website: https://mylinux.work
|
|
# License: MIT
|
|
################################################################################
|
|
|
|
# Ensure PATH includes sbin (for ipset/iptables when run from cron)
|
|
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$PATH"
|
|
#
|
|
# EXPORTED METRICS:
|
|
# - iptables_blocklist_info - Exporter metadata
|
|
# - iptables_blocklist_enabled_feeds - Count of enabled feeds
|
|
# - iptables_blocklist_ipset_size - IPs per feed ipset (IPv4/v6)
|
|
# - iptables_blocklist_blocked_total - Block counts per feed (1h, 24h)
|
|
# - iptables_blocklist_effectiveness - Blocks per 1000 IPs (24h)
|
|
# - iptables_blocklist_last_update_timestamp - Feed cache file mtime
|
|
# - iptables_blocklist_cache_age_seconds - Age of feed cache files
|
|
# - iptables_blocklist_file_size_bytes - Feed parsed file sizes
|
|
# - iptables_blocklist_ip_version_ratio - IPv4 vs IPv6 distribution per feed
|
|
# - iptables_blocklist_total_unique_ips - Total unique IPs across all feeds
|
|
# - iptables_blocklist_total_rules - Total iptables rules
|
|
# - iptables_blocklist_rule_packets - Packet counts from iptables rules
|
|
# - iptables_blocklist_rule_bytes - Byte counts from iptables rules
|
|
# - iptables_blocklist_conntrack_entries - Current conntrack entries
|
|
# - iptables_blocklist_conntrack_max - Maximum conntrack entries
|
|
# - iptables_blocklist_conntrack_usage_percent - Conntrack usage percentage
|
|
# - iptables_blocklist_whitelist_size - Whitelist ipset sizes
|
|
# - iptables_blocklist_exporter_runtime_seconds - Script execution time
|
|
|
|
CONFIG_DIR="/etc/iptables-threats"
|
|
CACHE_DIR="$CONFIG_DIR/cache"
|
|
FEEDS_CONFIG="$CONFIG_DIR/feeds.conf"
|
|
IPSET_PREFIX="iptables-feed"
|
|
WHITELIST_IPSET="iptables-whitelist"
|
|
WHITELIST_IPSET_V6="iptables-whitelist-v6"
|
|
LOG_FILE="/var/log/iptables-threats.log"
|
|
|
|
TEXTFILE_DIR="/var/lib/node_exporter"
|
|
OUTPUT_FILE=""
|
|
HTTP_MODE=false
|
|
HTTP_PORT=9419
|
|
SCRIPT_START_TIME=$(date +%s)
|
|
LOCK_FILE="/var/run/iptables-blocklist-metrics.lock"
|
|
|
|
show_usage() {
|
|
cat <<EOF
|
|
Usage: $0 [OPTIONS]
|
|
|
|
Export per-feed iptables threat statistics as Prometheus metrics.
|
|
|
|
MODES:
|
|
--textfile Write to node_exporter textfile collector
|
|
--http Run HTTP server on port $HTTP_PORT
|
|
|
|
OPTIONS:
|
|
-p, --port HTTP port (default: 9419)
|
|
-o, --output Output file
|
|
-h, --help Show this help
|
|
|
|
EXAMPLES:
|
|
# Write to textfile collector
|
|
$0 --textfile
|
|
|
|
# Run as HTTP server
|
|
$0 --http --port 9419
|
|
|
|
# Generate metrics to stdout
|
|
$0
|
|
|
|
EOF
|
|
exit 0
|
|
}
|
|
|
|
parse_args() {
|
|
while [[ $# -gt 0 ]]; do
|
|
case $1 in
|
|
-h|--help) show_usage ;;
|
|
--textfile) OUTPUT_FILE="$TEXTFILE_DIR/iptables_blocklist.prom"; shift ;;
|
|
--http) HTTP_MODE=true; shift ;;
|
|
-p|--port) HTTP_PORT="$2"; shift 2 ;;
|
|
-o|--output) OUTPUT_FILE="$2"; shift 2 ;;
|
|
*) echo "Unknown: $1"; exit 1 ;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
get_ipset_size() {
|
|
local ipset_name="$1"
|
|
local size
|
|
size=$(ipset list "$ipset_name" 2>/dev/null | grep '^[0-9a-fA-F.:]' | wc -l 2>/dev/null)
|
|
echo "${size:-0}"
|
|
}
|
|
|
|
get_feed_blocks() {
|
|
local feed="$1"
|
|
local period="$2"
|
|
local count
|
|
count=$(journalctl -k --since "$period" 2>/dev/null | grep "\[THREAT:${feed}\]" | wc -l 2>/dev/null)
|
|
echo "${count:-0}"
|
|
}
|
|
|
|
get_feed_blocks_v6() {
|
|
local feed="$1"
|
|
local period="$2"
|
|
local count
|
|
count=$(journalctl -k --since "$period" 2>/dev/null | grep "\[THREAT-v6:${feed}\]" | wc -l 2>/dev/null)
|
|
echo "${count:-0}"
|
|
}
|
|
|
|
get_file_timestamp() {
|
|
[ -f "$1" ] && stat -c %Y "$1" 2>/dev/null || echo "0"
|
|
}
|
|
|
|
get_file_size() {
|
|
[ -f "$1" ] && stat -c %s "$1" 2>/dev/null || echo "0"
|
|
}
|
|
|
|
get_cache_age() {
|
|
if [ -f "$1" ]; then
|
|
echo $(($(date +%s) - $(stat -c %Y "$1" 2>/dev/null || echo 0)))
|
|
else
|
|
echo "0"
|
|
fi
|
|
}
|
|
|
|
get_iptables_rule_stats() {
|
|
local chain="$1"
|
|
local feed="$2"
|
|
# Extract packet and byte counts from iptables -L -v -n -x (exact numbers, no human-readable K/M/G)
|
|
iptables -L "$chain" -v -n -x 2>/dev/null | grep "${IPSET_PREFIX}-${feed}" | head -1 | awk '{print $1"|"$2}'
|
|
}
|
|
|
|
get_total_unique_ips() {
|
|
local ip_version="$1"
|
|
local count=0
|
|
|
|
if [ "$ip_version" = "4" ]; then
|
|
count=$(cat "$CACHE_DIR/"*-v4.parsed 2>/dev/null | sort -u | wc -l 2>/dev/null)
|
|
elif [ "$ip_version" = "6" ]; then
|
|
count=$(cat "$CACHE_DIR/"*-v6.parsed 2>/dev/null | sort -u | wc -l 2>/dev/null)
|
|
fi
|
|
|
|
echo "${count:-0}"
|
|
}
|
|
|
|
get_conntrack_count() {
|
|
if [ -f /proc/sys/net/netfilter/nf_conntrack_count ]; then
|
|
cat /proc/sys/net/netfilter/nf_conntrack_count
|
|
else
|
|
echo "0"
|
|
fi
|
|
}
|
|
|
|
get_conntrack_max() {
|
|
if [ -f /proc/sys/net/netfilter/nf_conntrack_max ]; then
|
|
cat /proc/sys/net/netfilter/nf_conntrack_max
|
|
else
|
|
echo "0"
|
|
fi
|
|
}
|
|
|
|
get_ipset_memory() {
|
|
local ipset_name="$1"
|
|
local mem
|
|
mem=$(ipset list "$ipset_name" -t 2>/dev/null | grep "Size in memory:" | awk '{print $4}')
|
|
echo "${mem:-0}"
|
|
}
|
|
|
|
get_cache_disk_usage() {
|
|
if [ -d "$CACHE_DIR" ]; then
|
|
df -B1 "$CACHE_DIR" 2>/dev/null | tail -1 | awk '{print $3"|"$4"|"$5}'
|
|
else
|
|
echo "0|0|0%"
|
|
fi
|
|
}
|
|
|
|
get_total_cache_size() {
|
|
if [ -d "$CACHE_DIR" ]; then
|
|
du -sb "$CACHE_DIR" 2>/dev/null | awk '{print $1}'
|
|
else
|
|
echo "0"
|
|
fi
|
|
}
|
|
|
|
acquire_lock() {
|
|
if [ -f "$LOCK_FILE" ]; then
|
|
local pid=$(cat "$LOCK_FILE" 2>/dev/null)
|
|
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
|
|
echo "ERROR: Another instance is already running (PID: $pid)" >&2
|
|
exit 1
|
|
else
|
|
echo "Removing stale lock file" >&2
|
|
rm -f "$LOCK_FILE"
|
|
fi
|
|
fi
|
|
echo $$ > "$LOCK_FILE"
|
|
trap cleanup EXIT INT TERM
|
|
}
|
|
|
|
cleanup() {
|
|
rm -f "$LOCK_FILE"
|
|
}
|
|
|
|
generate_metrics() {
|
|
local start_time=$(date +%s)
|
|
local current_time=$(date +%s)
|
|
|
|
cat <<EOF
|
|
# HELP iptables_blocklist_info Per-feed iptables threat blocking info
|
|
# TYPE iptables_blocklist_info gauge
|
|
iptables_blocklist_info{mode="per-feed",version="2.0"} 1
|
|
|
|
# HELP iptables_blocklist_enabled_feeds Total enabled feeds
|
|
# TYPE iptables_blocklist_enabled_feeds gauge
|
|
iptables_blocklist_enabled_feeds $(grep -c '^1|' "$FEEDS_CONFIG" 2>/dev/null || echo 0)
|
|
|
|
# HELP iptables_blocklist_ipset_size Number of IPs per feed ipset
|
|
# TYPE iptables_blocklist_ipset_size gauge
|
|
EOF
|
|
|
|
# Only export metrics for ipsets that actually exist
|
|
for ipset_name in $(ipset list -n 2>/dev/null | grep "^${IPSET_PREFIX}-"); do
|
|
# Extract feed name and IP version
|
|
local feed_name="${ipset_name#${IPSET_PREFIX}-}"
|
|
local ip_version="4"
|
|
|
|
if [[ "$feed_name" =~ -v6$ ]]; then
|
|
feed_name="${feed_name%-v6}"
|
|
ip_version="6"
|
|
fi
|
|
|
|
# Get status from config
|
|
local status="disabled"
|
|
if grep -q "^1|${feed_name}|" "$FEEDS_CONFIG" 2>/dev/null; then
|
|
status="enabled"
|
|
fi
|
|
|
|
local size=$(get_ipset_size "$ipset_name")
|
|
echo "iptables_blocklist_ipset_size{feed=\"$feed_name\",ip_version=\"$ip_version\",status=\"$status\"} $size"
|
|
done
|
|
|
|
cat <<EOF
|
|
|
|
# HELP iptables_blocklist_blocked_total Blocked attempts per feed
|
|
# TYPE iptables_blocklist_blocked_total counter
|
|
EOF
|
|
|
|
# Per-feed block counts (IPv4 and IPv6)
|
|
if [ -f "$FEEDS_CONFIG" ]; then
|
|
while IFS='|' read -r enabled name url type description; do
|
|
[[ "$enabled" =~ ^#.*$ ]] && continue
|
|
[[ -z "$enabled" ]] && continue
|
|
[ "$enabled" != "1" ] && continue
|
|
|
|
local blocks_1h_v4 blocks_24h_v4 blocks_1h_v6 blocks_24h_v6
|
|
blocks_1h_v4=$(get_feed_blocks "$name" "1 hour ago")
|
|
blocks_24h_v4=$(get_feed_blocks "$name" "24 hours ago")
|
|
blocks_1h_v6=$(get_feed_blocks_v6 "$name" "1 hour ago")
|
|
blocks_24h_v6=$(get_feed_blocks_v6 "$name" "24 hours ago")
|
|
|
|
echo "iptables_blocklist_blocked_total{feed=\"$name\",ip_version=\"4\",period=\"1h\"} $blocks_1h_v4"
|
|
echo "iptables_blocklist_blocked_total{feed=\"$name\",ip_version=\"4\",period=\"24h\"} $blocks_24h_v4"
|
|
echo "iptables_blocklist_blocked_total{feed=\"$name\",ip_version=\"6\",period=\"1h\"} $blocks_1h_v6"
|
|
echo "iptables_blocklist_blocked_total{feed=\"$name\",ip_version=\"6\",period=\"24h\"} $blocks_24h_v6"
|
|
done < "$FEEDS_CONFIG"
|
|
fi
|
|
|
|
# Feed effectiveness (blocks per 1000 IPs)
|
|
cat <<EOF
|
|
|
|
# HELP iptables_blocklist_effectiveness Blocks per 1000 IPs in feed (24h)
|
|
# TYPE iptables_blocklist_effectiveness gauge
|
|
EOF
|
|
|
|
if [ -f "$FEEDS_CONFIG" ]; then
|
|
while IFS='|' read -r enabled name url type description; do
|
|
[[ "$enabled" =~ ^#.*$ ]] && continue
|
|
[[ -z "$enabled" ]] && continue
|
|
[ "$enabled" != "1" ] && continue
|
|
|
|
local ipset_size blocks_v4 blocks_v6 effectiveness_v4 effectiveness_v6
|
|
ipset_size=$(get_ipset_size "${IPSET_PREFIX}-${name}")
|
|
blocks_v4=$(get_feed_blocks "$name" "24 hours ago")
|
|
blocks_v6=$(get_feed_blocks_v6 "$name" "24 hours ago")
|
|
|
|
# Strip whitespace and ensure integers
|
|
ipset_size=$(echo "$ipset_size" | tr -d '\n' | tr -d ' ')
|
|
blocks_v4=$(echo "$blocks_v4" | tr -d '\n' | tr -d ' ')
|
|
blocks_v6=$(echo "$blocks_v6" | tr -d '\n' | tr -d ' ')
|
|
ipset_size=${ipset_size:-0}
|
|
blocks_v4=${blocks_v4:-0}
|
|
blocks_v6=${blocks_v6:-0}
|
|
|
|
if [ "$ipset_size" -gt 0 ] 2>/dev/null; then
|
|
effectiveness_v4=$(awk "BEGIN {printf \"%.2f\", ($blocks_v4 / $ipset_size) * 1000}" 2>/dev/null || echo "0")
|
|
effectiveness_v6=$(awk "BEGIN {printf \"%.2f\", ($blocks_v6 / $ipset_size) * 1000}" 2>/dev/null || echo "0")
|
|
else
|
|
effectiveness_v4="0"
|
|
effectiveness_v6="0"
|
|
fi
|
|
|
|
echo "iptables_blocklist_effectiveness{feed=\"$name\",ip_version=\"4\"} $effectiveness_v4"
|
|
echo "iptables_blocklist_effectiveness{feed=\"$name\",ip_version=\"6\"} $effectiveness_v6"
|
|
done < "$FEEDS_CONFIG"
|
|
fi
|
|
|
|
# Feed update/cache metrics
|
|
cat <<EOF
|
|
|
|
# HELP iptables_blocklist_last_update_timestamp Feed cache file last modified timestamp
|
|
# TYPE iptables_blocklist_last_update_timestamp gauge
|
|
EOF
|
|
|
|
if [ -f "$FEEDS_CONFIG" ]; then
|
|
while IFS='|' read -r enabled name url type description; do
|
|
[[ "$enabled" =~ ^#.*$ ]] && continue
|
|
[[ -z "$enabled" ]] && continue
|
|
|
|
local v4_file="${CACHE_DIR}/${name}-v4.parsed"
|
|
local v6_file="${CACHE_DIR}/${name}-v6.parsed"
|
|
local v4_ts v6_ts
|
|
v4_ts=$(get_file_timestamp "$v4_file")
|
|
v6_ts=$(get_file_timestamp "$v6_file")
|
|
|
|
echo "iptables_blocklist_last_update_timestamp{feed=\"$name\",ip_version=\"4\"} $v4_ts"
|
|
echo "iptables_blocklist_last_update_timestamp{feed=\"$name\",ip_version=\"6\"} $v6_ts"
|
|
done < "$FEEDS_CONFIG"
|
|
fi
|
|
|
|
cat <<EOF
|
|
|
|
# HELP iptables_blocklist_cache_age_seconds Age of feed cache files
|
|
# TYPE iptables_blocklist_cache_age_seconds gauge
|
|
EOF
|
|
|
|
if [ -f "$FEEDS_CONFIG" ]; then
|
|
while IFS='|' read -r enabled name url type description; do
|
|
[[ "$enabled" =~ ^#.*$ ]] && continue
|
|
[[ -z "$enabled" ]] && continue
|
|
|
|
local v4_file="${CACHE_DIR}/${name}-v4.parsed"
|
|
local v6_file="${CACHE_DIR}/${name}-v6.parsed"
|
|
local v4_age v6_age
|
|
v4_age=$(get_cache_age "$v4_file")
|
|
v6_age=$(get_cache_age "$v6_file")
|
|
|
|
echo "iptables_blocklist_cache_age_seconds{feed=\"$name\",ip_version=\"4\"} $v4_age"
|
|
echo "iptables_blocklist_cache_age_seconds{feed=\"$name\",ip_version=\"6\"} $v6_age"
|
|
done < "$FEEDS_CONFIG"
|
|
fi
|
|
|
|
cat <<EOF
|
|
|
|
# HELP iptables_blocklist_file_size_bytes Feed parsed file sizes
|
|
# TYPE iptables_blocklist_file_size_bytes gauge
|
|
EOF
|
|
|
|
if [ -f "$FEEDS_CONFIG" ]; then
|
|
while IFS='|' read -r enabled name url type description; do
|
|
[[ "$enabled" =~ ^#.*$ ]] && continue
|
|
[[ -z "$enabled" ]] && continue
|
|
|
|
local v4_file="${CACHE_DIR}/${name}-v4.parsed"
|
|
local v6_file="${CACHE_DIR}/${name}-v6.parsed"
|
|
local v4_size v6_size
|
|
v4_size=$(get_file_size "$v4_file")
|
|
v6_size=$(get_file_size "$v6_file")
|
|
|
|
echo "iptables_blocklist_file_size_bytes{feed=\"$name\",ip_version=\"4\",type=\"parsed\"} $v4_size"
|
|
echo "iptables_blocklist_file_size_bytes{feed=\"$name\",ip_version=\"6\",type=\"parsed\"} $v6_size"
|
|
done < "$FEEDS_CONFIG"
|
|
fi
|
|
|
|
# IP version distribution ratio
|
|
cat <<EOF
|
|
|
|
# HELP iptables_blocklist_ip_version_ratio Ratio of IPv4 to IPv6 addresses per feed
|
|
# TYPE iptables_blocklist_ip_version_ratio gauge
|
|
EOF
|
|
|
|
if [ -f "$FEEDS_CONFIG" ]; then
|
|
while IFS='|' read -r enabled name url type description; do
|
|
[[ "$enabled" =~ ^#.*$ ]] && continue
|
|
[[ -z "$enabled" ]] && continue
|
|
|
|
local v4_size v6_size total ratio_v4 ratio_v6
|
|
v4_size=$(get_ipset_size "${IPSET_PREFIX}-${name}")
|
|
v6_size=$(get_ipset_size "${IPSET_PREFIX}-${name}-v6")
|
|
|
|
v4_size=${v4_size:-0}
|
|
v6_size=${v6_size:-0}
|
|
total=$((v4_size + v6_size))
|
|
|
|
if [ "$total" -gt 0 ] 2>/dev/null; then
|
|
ratio_v4=$(awk "BEGIN {printf \"%.4f\", $v4_size / $total}" 2>/dev/null || echo "0")
|
|
ratio_v6=$(awk "BEGIN {printf \"%.4f\", $v6_size / $total}" 2>/dev/null || echo "0")
|
|
else
|
|
ratio_v4="0"
|
|
ratio_v6="0"
|
|
fi
|
|
|
|
echo "iptables_blocklist_ip_version_ratio{feed=\"$name\",version=\"4\"} $ratio_v4"
|
|
echo "iptables_blocklist_ip_version_ratio{feed=\"$name\",version=\"6\"} $ratio_v6"
|
|
done < "$FEEDS_CONFIG"
|
|
fi
|
|
|
|
# Total metrics
|
|
cat <<EOF
|
|
|
|
# HELP iptables_blocklist_total_unique_ips Total unique IPs across all feeds
|
|
# TYPE iptables_blocklist_total_unique_ips gauge
|
|
iptables_blocklist_total_unique_ips{ip_version="4"} $(get_total_unique_ips "4")
|
|
iptables_blocklist_total_unique_ips{ip_version="6"} $(get_total_unique_ips "6")
|
|
|
|
# HELP iptables_blocklist_total_rules Total iptables rules
|
|
# TYPE iptables_blocklist_total_rules gauge
|
|
iptables_blocklist_total_rules $(iptables -S 2>/dev/null | wc -l)
|
|
|
|
# HELP iptables_blocklist_rule_packets Packet counts from iptables rules
|
|
# TYPE iptables_blocklist_rule_packets counter
|
|
EOF
|
|
|
|
if [ -f "$FEEDS_CONFIG" ]; then
|
|
while IFS='|' read -r enabled name url type description; do
|
|
[[ "$enabled" =~ ^#.*$ ]] && continue
|
|
[[ -z "$enabled" ]] && continue
|
|
[ "$enabled" != "1" ] && continue
|
|
|
|
local stats_log stats_drop packets_log bytes_log packets_drop bytes_drop
|
|
|
|
stats_log=$(iptables -L INPUT -v -n -x 2>/dev/null | grep "${IPSET_PREFIX}-${name}" | grep LOG | head -1 | awk '{print $1"|"$2}')
|
|
stats_drop=$(iptables -L INPUT -v -n -x 2>/dev/null | grep "${IPSET_PREFIX}-${name}" | grep DROP | head -1 | awk '{print $1"|"$2}')
|
|
|
|
if [ -n "$stats_log" ]; then
|
|
packets_log=$(echo "$stats_log" | cut -d'|' -f1)
|
|
bytes_log=$(echo "$stats_log" | cut -d'|' -f2)
|
|
echo "iptables_blocklist_rule_packets{feed=\"$name\",ip_version=\"4\",action=\"log\"} ${packets_log:-0}"
|
|
fi
|
|
|
|
if [ -n "$stats_drop" ]; then
|
|
packets_drop=$(echo "$stats_drop" | cut -d'|' -f1)
|
|
bytes_drop=$(echo "$stats_drop" | cut -d'|' -f2)
|
|
echo "iptables_blocklist_rule_packets{feed=\"$name\",ip_version=\"4\",action=\"drop\"} ${packets_drop:-0}"
|
|
fi
|
|
done < "$FEEDS_CONFIG"
|
|
fi
|
|
|
|
cat <<EOF
|
|
|
|
# HELP iptables_blocklist_rule_bytes Byte counts from iptables rules
|
|
# TYPE iptables_blocklist_rule_bytes counter
|
|
EOF
|
|
|
|
if [ -f "$FEEDS_CONFIG" ]; then
|
|
while IFS='|' read -r enabled name url type description; do
|
|
[[ "$enabled" =~ ^#.*$ ]] && continue
|
|
[[ -z "$enabled" ]] && continue
|
|
[ "$enabled" != "1" ] && continue
|
|
|
|
local stats_log stats_drop packets_log bytes_log packets_drop bytes_drop
|
|
|
|
stats_log=$(iptables -L INPUT -v -n -x 2>/dev/null | grep "${IPSET_PREFIX}-${name}" | grep LOG | head -1 | awk '{print $1"|"$2}')
|
|
stats_drop=$(iptables -L INPUT -v -n -x 2>/dev/null | grep "${IPSET_PREFIX}-${name}" | grep DROP | head -1 | awk '{print $1"|"$2}')
|
|
|
|
if [ -n "$stats_log" ]; then
|
|
packets_log=$(echo "$stats_log" | cut -d'|' -f1)
|
|
bytes_log=$(echo "$stats_log" | cut -d'|' -f2)
|
|
echo "iptables_blocklist_rule_bytes{feed=\"$name\",ip_version=\"4\",action=\"log\"} ${bytes_log:-0}"
|
|
fi
|
|
|
|
if [ -n "$stats_drop" ]; then
|
|
packets_drop=$(echo "$stats_drop" | cut -d'|' -f1)
|
|
bytes_drop=$(echo "$stats_drop" | cut -d'|' -f2)
|
|
echo "iptables_blocklist_rule_bytes{feed=\"$name\",ip_version=\"4\",action=\"drop\"} ${bytes_drop:-0}"
|
|
fi
|
|
done < "$FEEDS_CONFIG"
|
|
fi
|
|
|
|
cat <<EOF
|
|
|
|
# HELP iptables_blocklist_ipset_memory_bytes Memory used by each ipset
|
|
# TYPE iptables_blocklist_ipset_memory_bytes gauge
|
|
EOF
|
|
|
|
if [ -f "$FEEDS_CONFIG" ]; then
|
|
while IFS='|' read -r enabled name url type description; do
|
|
[[ "$enabled" =~ ^#.*$ ]] && continue
|
|
[[ -z "$enabled" ]] && continue
|
|
|
|
mem_v4=$(get_ipset_memory "${IPSET_PREFIX}-${name}")
|
|
mem_v6=$(get_ipset_memory "${IPSET_PREFIX}-${name}-v6")
|
|
|
|
echo "iptables_blocklist_ipset_memory_bytes{feed=\"$name\",ip_version=\"4\"} $mem_v4"
|
|
echo "iptables_blocklist_ipset_memory_bytes{feed=\"$name\",ip_version=\"6\"} $mem_v6"
|
|
done < "$FEEDS_CONFIG"
|
|
fi
|
|
|
|
# Conntrack metrics
|
|
local conntrack_count conntrack_max conntrack_usage
|
|
conntrack_count=$(get_conntrack_count)
|
|
conntrack_max=$(get_conntrack_max)
|
|
|
|
if [ "$conntrack_max" -gt 0 ] 2>/dev/null; then
|
|
conntrack_usage=$(awk "BEGIN {printf \"%.2f\", ($conntrack_count / $conntrack_max) * 100}" 2>/dev/null || echo "0")
|
|
else
|
|
conntrack_usage="0"
|
|
fi
|
|
|
|
# Cache disk metrics
|
|
local disk_info cache_size disk_used disk_avail disk_pct
|
|
disk_info=$(get_cache_disk_usage)
|
|
cache_size=$(get_total_cache_size)
|
|
disk_used=$(echo "$disk_info" | cut -d'|' -f1)
|
|
disk_avail=$(echo "$disk_info" | cut -d'|' -f2)
|
|
disk_pct=$(echo "$disk_info" | cut -d'|' -f3 | tr -d '%')
|
|
|
|
cat <<EOF
|
|
|
|
# HELP iptables_blocklist_conntrack_entries Current conntrack entries
|
|
# TYPE iptables_blocklist_conntrack_entries gauge
|
|
iptables_blocklist_conntrack_entries $conntrack_count
|
|
|
|
# HELP iptables_blocklist_conntrack_max Maximum conntrack entries
|
|
# TYPE iptables_blocklist_conntrack_max gauge
|
|
iptables_blocklist_conntrack_max $conntrack_max
|
|
|
|
# HELP iptables_blocklist_conntrack_usage_percent Conntrack usage percentage
|
|
# TYPE iptables_blocklist_conntrack_usage_percent gauge
|
|
iptables_blocklist_conntrack_usage_percent $conntrack_usage
|
|
|
|
# HELP iptables_blocklist_cache_disk_used_bytes Disk space used by cache partition
|
|
# TYPE iptables_blocklist_cache_disk_used_bytes gauge
|
|
iptables_blocklist_cache_disk_used_bytes $disk_used
|
|
|
|
# HELP iptables_blocklist_cache_disk_available_bytes Disk space available on cache partition
|
|
# TYPE iptables_blocklist_cache_disk_available_bytes gauge
|
|
iptables_blocklist_cache_disk_available_bytes $disk_avail
|
|
|
|
# HELP iptables_blocklist_cache_disk_usage_percent Cache partition disk usage percentage
|
|
# TYPE iptables_blocklist_cache_disk_usage_percent gauge
|
|
iptables_blocklist_cache_disk_usage_percent ${disk_pct:-0}
|
|
|
|
# HELP iptables_blocklist_cache_total_size_bytes Total size of cache directory
|
|
# TYPE iptables_blocklist_cache_total_size_bytes gauge
|
|
iptables_blocklist_cache_total_size_bytes $cache_size
|
|
|
|
# HELP iptables_blocklist_whitelist_size Whitelist ipset size
|
|
# TYPE iptables_blocklist_whitelist_size gauge
|
|
iptables_blocklist_whitelist_size{ip_version="4"} $(get_ipset_size "$WHITELIST_IPSET")
|
|
iptables_blocklist_whitelist_size{ip_version="6"} $(get_ipset_size "$WHITELIST_IPSET_V6")
|
|
|
|
# HELP iptables_blocklist_exporter_runtime_seconds Exporter runtime in seconds
|
|
# TYPE iptables_blocklist_exporter_runtime_seconds gauge
|
|
iptables_blocklist_exporter_runtime_seconds $((current_time - start_time))
|
|
EOF
|
|
echo ""
|
|
}
|
|
|
|
run_http_server() {
|
|
echo "Starting iptables blocklist exporter on port $HTTP_PORT..."
|
|
|
|
if ! command -v nc >/dev/null 2>&1; then
|
|
echo "ERROR: netcat (nc) is required for HTTP mode"
|
|
echo "Install with: yum install nmap-ncat (RHEL/CentOS)"
|
|
echo " or: apt install netcat (Debian/Ubuntu)"
|
|
exit 1
|
|
fi
|
|
|
|
while true; do
|
|
{
|
|
read -r request
|
|
if [[ "$request" =~ ^GET\ /metrics ]]; then
|
|
echo -e "HTTP/1.1 200 OK\r\nContent-Type: text/plain; version=0.0.4\r\n\r"
|
|
generate_metrics
|
|
else
|
|
echo -e "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r"
|
|
echo "<h1>iptables Blocklist Metrics Exporter</h1>"
|
|
echo "<p>Per-feed threat blocking statistics</p>"
|
|
echo "<p><a href='/metrics'>Metrics</a></p>"
|
|
fi
|
|
} | nc -l -p "$HTTP_PORT" -q 1 2>/dev/null
|
|
done
|
|
}
|
|
|
|
main() {
|
|
parse_args "$@"
|
|
|
|
[ ! -d "$CONFIG_DIR" ] && { echo "ERROR: $CONFIG_DIR not found. Run iptables-blocklists.sh first" >&2; exit 1; }
|
|
|
|
# Prevent multiple instances (skip for HTTP mode as it should run continuously)
|
|
[ "$HTTP_MODE" != true ] && acquire_lock
|
|
|
|
if [ "$HTTP_MODE" = true ]; then
|
|
run_http_server
|
|
elif [ -n "$OUTPUT_FILE" ]; then
|
|
# Ensure output directory exists
|
|
mkdir -p "$(dirname "$OUTPUT_FILE")"
|
|
|
|
# Create temp file in /tmp (not in node_exporter directory!)
|
|
# This prevents node_exporter from seeing partial writes
|
|
local temp_file=$(mktemp /tmp/iptables_metrics.XXXXXX)
|
|
|
|
# Generate metrics to temp file
|
|
generate_metrics > "$temp_file"
|
|
|
|
# FORCE NEW INODE: Delete old file first, then move
|
|
# Some node_exporter versions cache file descriptors
|
|
rm -f "$OUTPUT_FILE"
|
|
|
|
# Move temp file to final location
|
|
mv "$temp_file" "$OUTPUT_FILE"
|
|
|
|
# Ensure node_exporter user can read it
|
|
chmod 644 "$OUTPUT_FILE"
|
|
|
|
# Force filesystem sync (optional but helps)
|
|
sync
|
|
else
|
|
generate_metrics
|
|
fi
|
|
}
|
|
|
|
main "$@"
|