Sync all scripts from website downloads — 352 scripts total
Includes updated JS challenge scripts with Claude-User whitelist, same-site referer bypass, Blackbox-Exporter allowed bot, and all new exporters, cheat sheets, and automation scripts.
This commit is contained in:
@@ -0,0 +1,668 @@
|
||||
#!/bin/bash
|
||||
################################################################################
|
||||
# Script Name: dhcp-lease-exporter.sh
|
||||
# Version: 1.01
|
||||
# Description: Prometheus exporter for DHCP lease metrics — pool utilization,
|
||||
# active leases per subnet, lease expirations, reservation status,
|
||||
# DORA packet counts, and lease duration tracking for ISC DHCP
|
||||
# (dhcpd) and ISC Kea.
|
||||
#
|
||||
# Author: Phil Connor
|
||||
# Contact: contact@mylinux.work
|
||||
# Website: https://mylinux.work
|
||||
# License: MIT
|
||||
#
|
||||
# Usage:
|
||||
# # Output to stdout
|
||||
# sudo ./dhcp-lease-exporter.sh
|
||||
#
|
||||
# # Textfile collector mode
|
||||
# sudo ./dhcp-lease-exporter.sh --textfile
|
||||
#
|
||||
# # HTTP server mode
|
||||
# sudo ./dhcp-lease-exporter.sh --http
|
||||
#
|
||||
# # Custom port
|
||||
# sudo ./dhcp-lease-exporter.sh --http --port 9533
|
||||
#
|
||||
################################################################################
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ============================================================================
|
||||
# CONFIGURATION
|
||||
# ============================================================================
|
||||
|
||||
readonly VERSION="1.0"
|
||||
readonly SCRIPT_NAME="${0##*/}"
|
||||
|
||||
# DHCP backend — auto, dhcpd, or kea
|
||||
DHCP_BACKEND="auto"
|
||||
|
||||
# dhcpd paths
|
||||
DHCPD_LEASES="/var/lib/dhcp/dhcpd.leases"
|
||||
DHCPD_CONF="/etc/dhcp/dhcpd.conf"
|
||||
|
||||
# Kea paths and API
|
||||
KEA_LEASES="/var/lib/kea/kea-leases4.csv"
|
||||
KEA_API="http://127.0.0.1:8000"
|
||||
KEA_USE_API="true"
|
||||
|
||||
# Output settings
|
||||
TEXTFILE_DIR="/var/lib/node_exporter/textfile_collector"
|
||||
HTTP_PORT=9533
|
||||
LOCK_FILE="/tmp/dhcp-lease-exporter.lock"
|
||||
|
||||
# Runtime
|
||||
MODE="stdout"
|
||||
ONCE=false
|
||||
DETECTED_BACKEND=""
|
||||
|
||||
# ============================================================================
|
||||
# COLORS
|
||||
# ============================================================================
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[0;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
# ============================================================================
|
||||
# HELPER FUNCTIONS
|
||||
# ============================================================================
|
||||
|
||||
log_info() { echo -e "${GREEN}[INFO]${NC} $*" >&2; }
|
||||
log_warn() { echo -e "${YELLOW}[WARN]${NC} $*" >&2; }
|
||||
log_error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
|
||||
|
||||
show_usage() {
|
||||
cat <<EOF
|
||||
Usage: sudo $SCRIPT_NAME [OPTIONS]
|
||||
|
||||
Export DHCP lease metrics as Prometheus-compatible text format.
|
||||
|
||||
MODES:
|
||||
--textfile Write to node_exporter textfile collector
|
||||
--http Run as standalone HTTP server
|
||||
(default) Print to stdout
|
||||
|
||||
OPTIONS:
|
||||
--port PORT HTTP port (default: $HTTP_PORT)
|
||||
--backend TYPE Force backend: dhcpd or kea (default: auto)
|
||||
--once In HTTP mode, serve one request and exit
|
||||
-h, --help Show this help message
|
||||
|
||||
CONFIGURATION:
|
||||
Edit the variables at the top of this script to set paths, ports, etc.
|
||||
|
||||
EOF
|
||||
exit 0
|
||||
}
|
||||
|
||||
parse_args() {
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--textfile) MODE="textfile"; shift ;;
|
||||
--http) MODE="http"; shift ;;
|
||||
--port) HTTP_PORT="$2"; shift 2 ;;
|
||||
--backend) DHCP_BACKEND="$2"; shift 2 ;;
|
||||
--once) ONCE=true; shift ;;
|
||||
-h|--help) show_usage ;;
|
||||
*) log_error "Unknown option: $1"; show_usage ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
write_metric_header() {
|
||||
local name="$1" type="$2" help="$3"
|
||||
echo "# HELP ${name} ${help}"
|
||||
echo "# TYPE ${name} ${type}"
|
||||
}
|
||||
|
||||
acquire_lock() {
|
||||
if [ -f "$LOCK_FILE" ]; then
|
||||
local pid
|
||||
pid=$(cat "$LOCK_FILE" 2>/dev/null || true)
|
||||
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
|
||||
log_error "Another instance is running (PID $pid)"
|
||||
exit 1
|
||||
fi
|
||||
rm -f "$LOCK_FILE"
|
||||
fi
|
||||
echo $$ > "$LOCK_FILE"
|
||||
trap 'rm -f "$LOCK_FILE"' EXIT
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# BACKEND DETECTION
|
||||
# ============================================================================
|
||||
|
||||
detect_backend() {
|
||||
if [ "$DHCP_BACKEND" != "auto" ]; then
|
||||
DETECTED_BACKEND="$DHCP_BACKEND"
|
||||
return
|
||||
fi
|
||||
|
||||
if systemctl is-active --quiet isc-kea-dhcp4-server 2>/dev/null || \
|
||||
systemctl is-active --quiet kea-dhcp4 2>/dev/null; then
|
||||
DETECTED_BACKEND="kea"
|
||||
elif systemctl is-active --quiet isc-dhcp-server 2>/dev/null || \
|
||||
systemctl is-active --quiet dhcpd 2>/dev/null; then
|
||||
DETECTED_BACKEND="dhcpd"
|
||||
elif [ -f "$KEA_LEASES" ]; then
|
||||
DETECTED_BACKEND="kea"
|
||||
elif [ -f "$DHCPD_LEASES" ]; then
|
||||
DETECTED_BACKEND="dhcpd"
|
||||
else
|
||||
DETECTED_BACKEND="unknown"
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# DHCPD FUNCTIONS
|
||||
# ============================================================================
|
||||
|
||||
# Parse dhcpd.conf for subnet definitions and pool ranges
|
||||
parse_dhcpd_subnets() {
|
||||
local conf="$DHCPD_CONF"
|
||||
[ -f "$conf" ] || return
|
||||
|
||||
local current_subnet="" current_name="" range_start="" range_end=""
|
||||
local in_subnet=false
|
||||
|
||||
while IFS= read -r line; do
|
||||
# Match subnet declaration
|
||||
if [[ "$line" =~ ^[[:space:]]*subnet[[:space:]]+([0-9.]+)[[:space:]]+netmask[[:space:]]+([0-9.]+) ]]; then
|
||||
current_subnet="${BASH_REMATCH[1]}"
|
||||
local netmask="${BASH_REMATCH[2]}"
|
||||
current_name="$current_subnet"
|
||||
in_subnet=true
|
||||
range_start=""
|
||||
range_end=""
|
||||
# Calculate CIDR from netmask
|
||||
local cidr
|
||||
cidr=$(netmask_to_cidr "$netmask")
|
||||
current_subnet="${current_subnet}/${cidr}"
|
||||
fi
|
||||
|
||||
# Check for comment-based name
|
||||
if $in_subnet && [[ "$line" =~ ^[[:space:]]*#[[:space:]]*(.+) ]]; then
|
||||
if [ "$current_name" = "${current_subnet%%/*}" ]; then
|
||||
current_name="${BASH_REMATCH[1]}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Match range declaration
|
||||
if $in_subnet && [[ "$line" =~ ^[[:space:]]*range[[:space:]]+([0-9.]+)[[:space:]]+([0-9.]+) ]]; then
|
||||
range_start="${BASH_REMATCH[1]}"
|
||||
range_end="${BASH_REMATCH[2]}"
|
||||
fi
|
||||
|
||||
# End of subnet block
|
||||
if $in_subnet && [[ "$line" =~ ^[[:space:]]*\} ]]; then
|
||||
if [ -n "$range_start" ] && [ -n "$range_end" ]; then
|
||||
local total
|
||||
total=$(ip_range_count "$range_start" "$range_end")
|
||||
echo "${current_subnet}|${current_name}|${total}|${range_start}|${range_end}"
|
||||
fi
|
||||
in_subnet=false
|
||||
fi
|
||||
done < "$conf"
|
||||
}
|
||||
|
||||
netmask_to_cidr() {
|
||||
local netmask="$1"
|
||||
local cidr=0
|
||||
for octet in $(echo "$netmask" | tr '.' ' '); do
|
||||
case $octet in
|
||||
255) cidr=$((cidr + 8)) ;;
|
||||
254) cidr=$((cidr + 7)) ;;
|
||||
252) cidr=$((cidr + 6)) ;;
|
||||
248) cidr=$((cidr + 5)) ;;
|
||||
240) cidr=$((cidr + 4)) ;;
|
||||
224) cidr=$((cidr + 3)) ;;
|
||||
192) cidr=$((cidr + 2)) ;;
|
||||
128) cidr=$((cidr + 1)) ;;
|
||||
0) ;;
|
||||
esac
|
||||
done
|
||||
echo "$cidr"
|
||||
}
|
||||
|
||||
ip_to_int() {
|
||||
local a b c d
|
||||
IFS='.' read -r a b c d <<< "$1"
|
||||
echo $(( (a << 24) + (b << 16) + (c << 8) + d ))
|
||||
}
|
||||
|
||||
ip_range_count() {
|
||||
local start_int end_int
|
||||
start_int=$(ip_to_int "$1")
|
||||
end_int=$(ip_to_int "$2")
|
||||
echo $(( end_int - start_int + 1 ))
|
||||
}
|
||||
|
||||
# Count active leases per subnet from dhcpd.leases
|
||||
count_dhcpd_leases() {
|
||||
local lease_file="$DHCPD_LEASES"
|
||||
[ -f "$lease_file" ] || return
|
||||
|
||||
local now
|
||||
now=$(date +%s)
|
||||
|
||||
awk -v now="$now" '
|
||||
/^lease / { ip = $2 }
|
||||
/ends / {
|
||||
gsub(/[;\/:]/, " ", $0)
|
||||
if ($2 != "never") {
|
||||
t = mktime($3 " " $4 " " $5 " " $6 " " $7 " " $8)
|
||||
if (t > now) active[ip] = t - now
|
||||
}
|
||||
}
|
||||
/binding state active/ { state[ip] = "active" }
|
||||
END {
|
||||
for (ip in active) {
|
||||
if (state[ip] == "active") {
|
||||
print ip, active[ip]
|
||||
}
|
||||
}
|
||||
}' "$lease_file"
|
||||
}
|
||||
|
||||
# Count reservations from dhcpd.conf
|
||||
count_dhcpd_reservations() {
|
||||
local conf="$DHCPD_CONF"
|
||||
[ -f "$conf" ] || return
|
||||
grep -c "fixed-address" "$conf" 2>/dev/null || true
|
||||
}
|
||||
|
||||
# Parse DORA stats from syslog
|
||||
parse_dhcpd_dora() {
|
||||
local logfile="/var/log/syslog"
|
||||
[ -f "$logfile" ] || logfile="/var/log/messages"
|
||||
[ -f "$logfile" ] || return
|
||||
|
||||
local discovers offers requests acks naks declines releases
|
||||
discovers=$(grep -c "DHCPDISCOVER" "$logfile" 2>/dev/null || true)
|
||||
offers=$(grep -c "DHCPOFFER" "$logfile" 2>/dev/null || true)
|
||||
requests=$(grep -c "DHCPREQUEST" "$logfile" 2>/dev/null || true)
|
||||
acks=$(grep -c "DHCPACK" "$logfile" 2>/dev/null || true)
|
||||
naks=$(grep -c "DHCPNAK" "$logfile" 2>/dev/null || true)
|
||||
declines=$(grep -c "DHCPDECLINE" "$logfile" 2>/dev/null || true)
|
||||
releases=$(grep -c "DHCPRELEASE" "$logfile" 2>/dev/null || true)
|
||||
|
||||
echo "${discovers}|${offers}|${requests}|${acks}|${naks}|${declines}|${releases}"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# KEA FUNCTIONS
|
||||
# ============================================================================
|
||||
|
||||
kea_api_call() {
|
||||
local command="$1"
|
||||
curl -s --max-time 5 -X POST "${KEA_API}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"command\": \"${command}\", \"service\": [\"dhcp4\"]}" 2>/dev/null
|
||||
}
|
||||
|
||||
parse_kea_leases_file() {
|
||||
local lease_file="$KEA_LEASES"
|
||||
[ -f "$lease_file" ] || return
|
||||
|
||||
local now
|
||||
now=$(date +%s)
|
||||
|
||||
awk -F',' -v now="$now" '
|
||||
NR > 1 && NF >= 9 {
|
||||
ip = $1
|
||||
expire = $7
|
||||
state = $9
|
||||
if (state == 0 && expire > now) {
|
||||
remaining = expire - now
|
||||
print ip, remaining
|
||||
}
|
||||
}' "$lease_file"
|
||||
}
|
||||
|
||||
parse_kea_api_subnets() {
|
||||
local response
|
||||
response=$(kea_api_call "subnet4-list")
|
||||
if [ -z "$response" ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "$response" | python3 -c "
|
||||
import sys, json
|
||||
data = json.load(sys.stdin)
|
||||
if data[0]['result'] == 0:
|
||||
for s in data[0].get('arguments', {}).get('subnets', []):
|
||||
sid = s.get('id', 0)
|
||||
subnet = s.get('subnet', '')
|
||||
print(f'{sid}|{subnet}')
|
||||
" 2>/dev/null
|
||||
}
|
||||
|
||||
parse_kea_api_stats() {
|
||||
local response
|
||||
response=$(kea_api_call "statistic-get-all")
|
||||
if [ -z "$response" ]; then
|
||||
return 1
|
||||
fi
|
||||
echo "$response"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# METRIC COLLECTION
|
||||
# ============================================================================
|
||||
|
||||
collect_metrics() {
|
||||
local start_time
|
||||
start_time=$(date +%s%N)
|
||||
local metrics=""
|
||||
|
||||
# Exporter status
|
||||
metrics+="$(write_metric_header "dhcp_up" "gauge" "Exporter status (1=up, 0=down)")"$'\n'
|
||||
metrics+="$(write_metric_header "dhcp_exporter_info" "gauge" "Exporter version and backend")"$'\n'
|
||||
|
||||
if [ "$DETECTED_BACKEND" = "unknown" ]; then
|
||||
metrics+="dhcp_up 0"$'\n'
|
||||
echo "$metrics"
|
||||
return
|
||||
fi
|
||||
|
||||
metrics+="dhcp_up 1"$'\n'
|
||||
metrics+="dhcp_exporter_info{version=\"${VERSION}\",backend=\"${DETECTED_BACKEND}\"} 1"$'\n'
|
||||
|
||||
local subnet_count=0
|
||||
local total_active=0
|
||||
|
||||
if [ "$DETECTED_BACKEND" = "dhcpd" ]; then
|
||||
collect_dhcpd_metrics
|
||||
elif [ "$DETECTED_BACKEND" = "kea" ]; then
|
||||
collect_kea_metrics
|
||||
fi
|
||||
|
||||
# Subnet count
|
||||
metrics+="$(write_metric_header "dhcp_subnets_total" "gauge" "Total number of configured subnets")"$'\n'
|
||||
metrics+="dhcp_subnets_total ${subnet_count}"$'\n'
|
||||
|
||||
# Total active leases
|
||||
metrics+="$(write_metric_header "dhcp_leases_active_total" "gauge" "Total active leases across all subnets")"$'\n'
|
||||
metrics+="dhcp_leases_active_total ${total_active}"$'\n'
|
||||
|
||||
# Lease file info
|
||||
if [ "$DETECTED_BACKEND" = "dhcpd" ] && [ -f "$DHCPD_LEASES" ]; then
|
||||
local file_age file_size
|
||||
file_age=$(( $(date +%s) - $(stat -c %Y "$DHCPD_LEASES") ))
|
||||
file_size=$(stat -c %s "$DHCPD_LEASES")
|
||||
metrics+="$(write_metric_header "dhcp_lease_file_age_seconds" "gauge" "Seconds since the lease file was last modified")"$'\n'
|
||||
metrics+="dhcp_lease_file_age_seconds ${file_age}"$'\n'
|
||||
metrics+="$(write_metric_header "dhcp_lease_file_size_bytes" "gauge" "Size of the lease file")"$'\n'
|
||||
metrics+="dhcp_lease_file_size_bytes ${file_size}"$'\n'
|
||||
elif [ "$DETECTED_BACKEND" = "kea" ] && [ -f "$KEA_LEASES" ]; then
|
||||
local file_age file_size
|
||||
file_age=$(( $(date +%s) - $(stat -c %Y "$KEA_LEASES") ))
|
||||
file_size=$(stat -c %s "$KEA_LEASES")
|
||||
metrics+="$(write_metric_header "dhcp_lease_file_age_seconds" "gauge" "Seconds since the lease file was last modified")"$'\n'
|
||||
metrics+="dhcp_lease_file_age_seconds ${file_age}"$'\n'
|
||||
metrics+="$(write_metric_header "dhcp_lease_file_size_bytes" "gauge" "Size of the lease file")"$'\n'
|
||||
metrics+="dhcp_lease_file_size_bytes ${file_size}"$'\n'
|
||||
fi
|
||||
|
||||
# Execution time
|
||||
local end_time duration
|
||||
end_time=$(date +%s%N)
|
||||
duration=$(echo "scale=2; ($end_time - $start_time) / 1000000000" | bc 2>/dev/null || echo "0")
|
||||
metrics+="$(write_metric_header "dhcp_exporter_duration_seconds" "gauge" "Script execution time")"$'\n'
|
||||
metrics+="dhcp_exporter_duration_seconds ${duration}"$'\n'
|
||||
metrics+="$(write_metric_header "dhcp_exporter_last_run_timestamp" "gauge" "Unix timestamp of last successful run")"$'\n'
|
||||
metrics+="dhcp_exporter_last_run_timestamp $(date +%s)"$'\n'
|
||||
|
||||
echo "$metrics"
|
||||
}
|
||||
|
||||
collect_dhcpd_metrics() {
|
||||
# Parse subnets from config
|
||||
local subnet_data
|
||||
subnet_data=$(parse_dhcpd_subnets)
|
||||
|
||||
# Get active leases
|
||||
local lease_data
|
||||
lease_data=$(count_dhcpd_leases)
|
||||
|
||||
metrics+="$(write_metric_header "dhcp_subnet_pool_total" "gauge" "Total addresses in the pool")"$'\n'
|
||||
metrics+="$(write_metric_header "dhcp_subnet_pool_active" "gauge" "Currently leased addresses")"$'\n'
|
||||
metrics+="$(write_metric_header "dhcp_subnet_pool_free" "gauge" "Available addresses in the pool")"$'\n'
|
||||
metrics+="$(write_metric_header "dhcp_subnet_pool_utilization" "gauge" "Pool utilization percentage (0-100)")"$'\n'
|
||||
metrics+="$(write_metric_header "dhcp_subnet_pool_reserved" "gauge" "Number of static reservations")"$'\n'
|
||||
metrics+="$(write_metric_header "dhcp_subnet_leases_expiring" "gauge" "Leases expiring within threshold")"$'\n'
|
||||
metrics+="$(write_metric_header "dhcp_subnet_lease_longest_seconds" "gauge" "Remaining time on the longest lease")"$'\n'
|
||||
metrics+="$(write_metric_header "dhcp_subnet_lease_shortest_seconds" "gauge" "Remaining time on the shortest lease")"$'\n'
|
||||
|
||||
while IFS='|' read -r subnet name pool_total range_start range_end; do
|
||||
[ -z "$subnet" ] && continue
|
||||
subnet_count=$((subnet_count + 1))
|
||||
|
||||
# Count active leases in this subnet range
|
||||
local active=0 longest=0 shortest=999999999
|
||||
local expiring_1h=0 expiring_4h=0 expiring_24h=0
|
||||
local start_int end_int
|
||||
start_int=$(ip_to_int "$range_start")
|
||||
end_int=$(ip_to_int "$range_end")
|
||||
|
||||
while read -r lease_ip remaining; do
|
||||
[ -z "$lease_ip" ] && continue
|
||||
local lip
|
||||
lip=$(ip_to_int "$lease_ip")
|
||||
if [ "$lip" -ge "$start_int" ] && [ "$lip" -le "$end_int" ]; then
|
||||
active=$((active + 1))
|
||||
total_active=$((total_active + 1))
|
||||
[ "$remaining" -gt "$longest" ] && longest=$remaining
|
||||
[ "$remaining" -lt "$shortest" ] && shortest=$remaining
|
||||
[ "$remaining" -le 3600 ] && expiring_1h=$((expiring_1h + 1))
|
||||
[ "$remaining" -le 14400 ] && expiring_4h=$((expiring_4h + 1))
|
||||
[ "$remaining" -le 86400 ] && expiring_24h=$((expiring_24h + 1))
|
||||
fi
|
||||
done <<< "$lease_data"
|
||||
|
||||
local free=$((pool_total - active))
|
||||
[ $free -lt 0 ] && free=0
|
||||
local util=0
|
||||
if [ "$pool_total" -gt 0 ]; then
|
||||
util=$(echo "scale=2; $active * 100 / $pool_total" | bc 2>/dev/null || echo "0")
|
||||
fi
|
||||
[ $active -eq 0 ] && shortest=0
|
||||
|
||||
local reserved
|
||||
reserved=$(count_dhcpd_reservations)
|
||||
|
||||
metrics+="dhcp_subnet_pool_total{subnet=\"${subnet}\",name=\"${name}\"} ${pool_total}"$'\n'
|
||||
metrics+="dhcp_subnet_pool_active{subnet=\"${subnet}\",name=\"${name}\"} ${active}"$'\n'
|
||||
metrics+="dhcp_subnet_pool_free{subnet=\"${subnet}\",name=\"${name}\"} ${free}"$'\n'
|
||||
metrics+="dhcp_subnet_pool_utilization{subnet=\"${subnet}\",name=\"${name}\"} ${util}"$'\n'
|
||||
metrics+="dhcp_subnet_pool_reserved{subnet=\"${subnet}\",name=\"${name}\"} ${reserved}"$'\n'
|
||||
metrics+="dhcp_subnet_leases_expiring{subnet=\"${subnet}\",name=\"${name}\",within=\"1h\"} ${expiring_1h}"$'\n'
|
||||
metrics+="dhcp_subnet_leases_expiring{subnet=\"${subnet}\",name=\"${name}\",within=\"4h\"} ${expiring_4h}"$'\n'
|
||||
metrics+="dhcp_subnet_leases_expiring{subnet=\"${subnet}\",name=\"${name}\",within=\"24h\"} ${expiring_24h}"$'\n'
|
||||
metrics+="dhcp_subnet_lease_longest_seconds{subnet=\"${subnet}\",name=\"${name}\"} ${longest}"$'\n'
|
||||
metrics+="dhcp_subnet_lease_shortest_seconds{subnet=\"${subnet}\",name=\"${name}\"} ${shortest}"$'\n'
|
||||
done <<< "$subnet_data"
|
||||
|
||||
# DORA stats
|
||||
local dora
|
||||
dora=$(parse_dhcpd_dora)
|
||||
if [ -n "$dora" ]; then
|
||||
IFS='|' read -r discovers offers requests acks naks declines releases <<< "$dora"
|
||||
metrics+="$(write_metric_header "dhcp_discovers_total" "counter" "Total DHCPDISCOVER packets received")"$'\n'
|
||||
metrics+="dhcp_discovers_total ${discovers}"$'\n'
|
||||
metrics+="$(write_metric_header "dhcp_offers_total" "counter" "Total DHCPOFFER packets sent")"$'\n'
|
||||
metrics+="dhcp_offers_total ${offers}"$'\n'
|
||||
metrics+="$(write_metric_header "dhcp_requests_total" "counter" "Total DHCPREQUEST packets received")"$'\n'
|
||||
metrics+="dhcp_requests_total ${requests}"$'\n'
|
||||
metrics+="$(write_metric_header "dhcp_acks_total" "counter" "Total DHCPACK packets sent")"$'\n'
|
||||
metrics+="dhcp_acks_total ${acks}"$'\n'
|
||||
metrics+="$(write_metric_header "dhcp_naks_total" "counter" "Total DHCPNAK packets sent")"$'\n'
|
||||
metrics+="dhcp_naks_total ${naks}"$'\n'
|
||||
metrics+="$(write_metric_header "dhcp_declines_total" "counter" "Total DHCPDECLINE packets received")"$'\n'
|
||||
metrics+="dhcp_declines_total ${declines}"$'\n'
|
||||
metrics+="$(write_metric_header "dhcp_releases_total" "counter" "Total DHCPRELEASE packets received")"$'\n'
|
||||
metrics+="dhcp_releases_total ${releases}"$'\n'
|
||||
fi
|
||||
}
|
||||
|
||||
collect_kea_metrics() {
|
||||
metrics+="$(write_metric_header "dhcp_subnet_pool_total" "gauge" "Total addresses in the pool")"$'\n'
|
||||
metrics+="$(write_metric_header "dhcp_subnet_pool_active" "gauge" "Currently leased addresses")"$'\n'
|
||||
metrics+="$(write_metric_header "dhcp_subnet_pool_free" "gauge" "Available addresses in the pool")"$'\n'
|
||||
metrics+="$(write_metric_header "dhcp_subnet_pool_utilization" "gauge" "Pool utilization percentage (0-100)")"$'\n'
|
||||
metrics+="$(write_metric_header "dhcp_subnet_pool_reserved" "gauge" "Number of static reservations")"$'\n'
|
||||
metrics+="$(write_metric_header "dhcp_subnet_leases_expiring" "gauge" "Leases expiring within threshold")"$'\n'
|
||||
metrics+="$(write_metric_header "dhcp_subnet_lease_longest_seconds" "gauge" "Remaining time on the longest lease")"$'\n'
|
||||
metrics+="$(write_metric_header "dhcp_subnet_lease_shortest_seconds" "gauge" "Remaining time on the shortest lease")"$'\n'
|
||||
|
||||
if [ "$KEA_USE_API" = "true" ]; then
|
||||
collect_kea_api_metrics
|
||||
else
|
||||
collect_kea_file_metrics
|
||||
fi
|
||||
}
|
||||
|
||||
collect_kea_api_metrics() {
|
||||
local stats_json
|
||||
stats_json=$(kea_api_call "statistic-get-all")
|
||||
|
||||
if [ -z "$stats_json" ]; then
|
||||
log_warn "Kea API not responding, falling back to file mode"
|
||||
collect_kea_file_metrics
|
||||
return
|
||||
fi
|
||||
|
||||
# Parse stats via python3
|
||||
echo "$stats_json" | python3 -c "
|
||||
import sys, json
|
||||
data = json.load(sys.stdin)
|
||||
if data[0]['result'] == 0:
|
||||
args = data[0].get('arguments', {})
|
||||
for key, val in args.items():
|
||||
if val and isinstance(val, list):
|
||||
v = val[0][0] if isinstance(val[0], list) else val[0]
|
||||
print(f'{key}={v}')
|
||||
" 2>/dev/null | while IFS='=' read -r key value; do
|
||||
case "$key" in
|
||||
subnet*total-addresses*)
|
||||
local sid="${key#subnet[}"
|
||||
sid="${sid%%]*}"
|
||||
metrics+="dhcp_subnet_pool_total{subnet=\"${sid}\"} ${value}"$'\n'
|
||||
;;
|
||||
subnet*assigned-addresses*)
|
||||
local sid="${key#subnet[}"
|
||||
sid="${sid%%]*}"
|
||||
metrics+="dhcp_subnet_pool_active{subnet=\"${sid}\"} ${value}"$'\n'
|
||||
;;
|
||||
pkt4-discover-received)
|
||||
metrics+="$(write_metric_header "dhcp_discovers_total" "counter" "Total DHCPDISCOVER packets received")"$'\n'
|
||||
metrics+="dhcp_discovers_total ${value}"$'\n'
|
||||
;;
|
||||
pkt4-offer-sent)
|
||||
metrics+="$(write_metric_header "dhcp_offers_total" "counter" "Total DHCPOFFER packets sent")"$'\n'
|
||||
metrics+="dhcp_offers_total ${value}"$'\n'
|
||||
;;
|
||||
pkt4-request-received)
|
||||
metrics+="$(write_metric_header "dhcp_requests_total" "counter" "Total DHCPREQUEST packets received")"$'\n'
|
||||
metrics+="dhcp_requests_total ${value}"$'\n'
|
||||
;;
|
||||
pkt4-ack-sent)
|
||||
metrics+="$(write_metric_header "dhcp_acks_total" "counter" "Total DHCPACK packets sent")"$'\n'
|
||||
metrics+="dhcp_acks_total ${value}"$'\n'
|
||||
;;
|
||||
pkt4-nak-sent)
|
||||
metrics+="$(write_metric_header "dhcp_naks_total" "counter" "Total DHCPNAK packets sent")"$'\n'
|
||||
metrics+="dhcp_naks_total ${value}"$'\n'
|
||||
;;
|
||||
pkt4-decline-received)
|
||||
metrics+="$(write_metric_header "dhcp_declines_total" "counter" "Total DHCPDECLINE packets received")"$'\n'
|
||||
metrics+="dhcp_declines_total ${value}"$'\n'
|
||||
;;
|
||||
pkt4-release-received)
|
||||
metrics+="$(write_metric_header "dhcp_releases_total" "counter" "Total DHCPRELEASE packets received")"$'\n'
|
||||
metrics+="dhcp_releases_total ${value}"$'\n'
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
collect_kea_file_metrics() {
|
||||
local lease_data
|
||||
lease_data=$(parse_kea_leases_file)
|
||||
local now
|
||||
now=$(date +%s)
|
||||
|
||||
# Simple lease counting from CSV
|
||||
while read -r lease_ip remaining; do
|
||||
[ -z "$lease_ip" ] && continue
|
||||
total_active=$((total_active + 1))
|
||||
done <<< "$lease_data"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# OUTPUT
|
||||
# ============================================================================
|
||||
|
||||
output_metrics() {
|
||||
local all_metrics
|
||||
all_metrics=$(collect_metrics)
|
||||
|
||||
case "$MODE" in
|
||||
stdout)
|
||||
echo "$all_metrics"
|
||||
;;
|
||||
textfile)
|
||||
mkdir -p "$TEXTFILE_DIR"
|
||||
local tmp_file
|
||||
tmp_file=$(mktemp "${TEXTFILE_DIR}/.dhcp-metrics.XXXXXX")
|
||||
echo "$all_metrics" > "$tmp_file"
|
||||
mv "$tmp_file" "${TEXTFILE_DIR}/dhcp-metrics.prom"
|
||||
log_info "Wrote metrics to ${TEXTFILE_DIR}/dhcp-metrics.prom"
|
||||
;;
|
||||
http)
|
||||
run_http_server "$all_metrics"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
run_http_server() {
|
||||
log_info "Starting HTTP server on port ${HTTP_PORT}"
|
||||
while true; do
|
||||
local all_metrics
|
||||
all_metrics=$(collect_metrics)
|
||||
|
||||
{
|
||||
echo -e "HTTP/1.1 200 OK\r"
|
||||
echo -e "Content-Type: text/plain; version=0.0.4; charset=utf-8\r"
|
||||
echo -e "Content-Length: ${#all_metrics}\r"
|
||||
echo -e "\r"
|
||||
echo "$all_metrics"
|
||||
} | nc -l -p "$HTTP_PORT" -q 1 2>/dev/null || \
|
||||
{
|
||||
echo -e "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\n${all_metrics}"
|
||||
} | nc -l "$HTTP_PORT" 2>/dev/null
|
||||
|
||||
if $ONCE; then
|
||||
break
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# MAIN
|
||||
# ============================================================================
|
||||
|
||||
main() {
|
||||
parse_args "$@"
|
||||
acquire_lock
|
||||
detect_backend
|
||||
log_info "Detected DHCP backend: ${DETECTED_BACKEND}"
|
||||
output_metrics
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user