Files
linux-scripts/network-info-exporter.sh
T
chiefgeek a1a17e81a1 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.
2026-05-25 03:31:08 +02:00

523 lines
21 KiB
Bash
Executable File

#!/bin/bash
################################################################################
# Script Name: network-info-exporter.sh
# Description: Prometheus exporter for Linux network metrics
#
# Collects interface statistics, connection states, routing info, firewall
# rules, DNS configuration, protocol statistics, and latency measurements.
#
# Author: Phil Connor
# Contact: contact@mylinux.work
# Website: https://mylinux.work
# License: MIT
# Version: 3.0
#
# Usage:
# # Output to stdout
# ./network-info-exporter.sh
#
# # Textfile collector mode (atomic write)
# ./network-info-exporter.sh --textfile
#
# # Custom output file
# ./network-info-exporter.sh -o /path/to/metrics.prom
#
################################################################################
# ============================================================================
# CONFIGURATION VARIABLES
# ============================================================================
TEXTFILE_DIR="/var/lib/node_exporter"
OUTPUT_FILE=""
HOSTNAME=$(hostname)
PING_TARGETS="${PING_TARGETS:-8.8.8.8 1.1.1.1 google.com}"
PING_COUNT="${PING_COUNT:-5}"
PING_TIMEOUT="${PING_TIMEOUT:-2}"
# ============================================================================
# HELPER FUNCTIONS
# ============================================================================
show_usage() {
cat <<EOF
Usage: $0 [OPTIONS]
Export Linux network metrics as Prometheus metrics.
MODES:
--textfile Write to node_exporter textfile collector
(writes to $TEXTFILE_DIR/network_info.prom)
OPTIONS:
-o, --output Output file path (for custom locations)
-h, --help Show this help message
EXAMPLES:
$0 # Output to stdout
$0 --textfile # Write to textfile collector
$0 -o /tmp/network.prom # Write to custom file
EOF
exit 0
}
parse_args() {
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help) show_usage ;;
--textfile) OUTPUT_FILE="$TEXTFILE_DIR/network_info.prom"; shift ;;
-o|--output) OUTPUT_FILE="$2"; shift 2 ;;
*) echo "Unknown option: $1" >&2; exit 1 ;;
esac
done
}
# ============================================================================
# METRIC GENERATION
# ============================================================================
generate_metrics() {
local START_TIME
START_TIME=$(date +%s.%N)
# --- System info ---
echo "# HELP network_exporter_info Exporter and system information"
echo "# TYPE network_exporter_info gauge"
local kernel arch os_name os_version
kernel=$(uname -r)
arch=$(uname -m)
if [[ -f /etc/os-release ]]; then
. /etc/os-release
os_name="${NAME:-unknown}"
os_version="${VERSION_ID:-unknown}"
else
os_name="unknown"
os_version="unknown"
fi
echo "network_exporter_info{hostname=\"${HOSTNAME}\",kernel=\"${kernel}\",arch=\"${arch}\",os=\"${os_name}\",version=\"${os_version}\"} 1"
echo ""
# --- Interface counters from /proc/net/dev ---
if [[ -r /proc/net/dev ]]; then
awk '
NR > 2 {
sub(/:/, " ")
iface = $1
if (iface == "lo") next
devices[count++] = iface
rx_bytes[iface] = $2; rx_packets[iface] = $3
rx_errors[iface] = $4; rx_dropped[iface] = $5
tx_bytes[iface] = $10; tx_packets[iface] = $11
tx_errors[iface] = $12; tx_dropped[iface] = $13
}
END {
metrics["receive_bytes"] = "Total bytes received"; t["receive_bytes"] = "counter"
metrics["receive_packets"] = "Total packets received"; t["receive_packets"] = "counter"
metrics["receive_errors"] = "Total receive errors"; t["receive_errors"] = "counter"
metrics["receive_dropped"] = "Total receive drops"; t["receive_dropped"] = "counter"
metrics["transmit_bytes"] = "Total bytes transmitted"; t["transmit_bytes"] = "counter"
metrics["transmit_packets"] = "Total packets transmitted"; t["transmit_packets"] = "counter"
metrics["transmit_errors"] = "Total transmit errors"; t["transmit_errors"] = "counter"
metrics["transmit_dropped"] = "Total transmit drops"; t["transmit_dropped"] = "counter"
order[0]="receive_bytes"; order[1]="receive_packets"
order[2]="receive_errors"; order[3]="receive_dropped"
order[4]="transmit_bytes"; order[5]="transmit_packets"
order[6]="transmit_errors"; order[7]="transmit_dropped"
for (m = 0; m < 8; m++) {
key = order[m]
name = "network_" key "_total"
printf "# HELP %s %s\n", name, metrics[key]
printf "# TYPE %s %s\n", name, t[key]
for (i = 0; i < count; i++) {
d = devices[i]
if (key == "receive_bytes") v = rx_bytes[d]
else if (key == "receive_packets") v = rx_packets[d]
else if (key == "receive_errors") v = rx_errors[d]
else if (key == "receive_dropped") v = rx_dropped[d]
else if (key == "transmit_bytes") v = tx_bytes[d]
else if (key == "transmit_packets") v = tx_packets[d]
else if (key == "transmit_errors") v = tx_errors[d]
else if (key == "transmit_dropped") v = tx_dropped[d]
printf "%s{device=\"%s\",hostname=\"%s\"} %s\n", name, d, ENVIRON["HOSTNAME"], v
}
print ""
}
}
' /proc/net/dev
fi
# --- Interface status, MTU, queue length, speed ---
echo "# HELP network_interface_up Interface operational status (1=up, 0=down)"
echo "# TYPE network_interface_up gauge"
echo "# HELP network_interface_mtu Interface MTU size"
echo "# TYPE network_interface_mtu gauge"
echo "# HELP network_interface_tx_queue_length Interface transmit queue length"
echo "# TYPE network_interface_tx_queue_length gauge"
echo "# HELP network_interface_speed_mbps Interface link speed in Mbps"
echo "# TYPE network_interface_speed_mbps gauge"
for iface_path in /sys/class/net/*; do
iface=$(basename "$iface_path")
[[ "$iface" == "lo" ]] && continue
# status
local operstate
operstate=$(cat "$iface_path/operstate" 2>/dev/null) || operstate="unknown"
if [[ "$operstate" == "up" ]]; then
echo "network_interface_up{device=\"${iface}\",hostname=\"${HOSTNAME}\"} 1"
else
echo "network_interface_up{device=\"${iface}\",hostname=\"${HOSTNAME}\"} 0"
fi
# mtu
local mtu
mtu=$(cat "$iface_path/mtu" 2>/dev/null) || mtu=0
echo "network_interface_mtu{device=\"${iface}\",hostname=\"${HOSTNAME}\"} ${mtu}"
# tx queue length
local qlen
qlen=$(cat "$iface_path/tx_queue_len" 2>/dev/null) || qlen=0
echo "network_interface_tx_queue_length{device=\"${iface}\",hostname=\"${HOSTNAME}\"} ${qlen}"
# speed (only for physical interfaces with valid speed)
if [[ -r "$iface_path/speed" ]]; then
local speed
speed=$(cat "$iface_path/speed" 2>/dev/null) || speed=-1
if [[ "$speed" =~ ^[0-9]+$ ]] && [[ "$speed" -gt 0 ]]; then
echo "network_interface_speed_mbps{device=\"${iface}\",hostname=\"${HOSTNAME}\"} ${speed}"
fi
fi
done
echo ""
# --- Routing ---
echo "# HELP network_routes_total Route count by protocol"
echo "# TYPE network_routes_total gauge"
if command -v ip &>/dev/null; then
local ipv4_routes ipv6_routes default_routes
ipv4_routes=$(ip route show 2>/dev/null | wc -l)
ipv6_routes=$(ip -6 route show 2>/dev/null | wc -l)
default_routes=$(ip route show default 2>/dev/null | wc -l)
echo "network_routes_total{family=\"ipv4\",hostname=\"${HOSTNAME}\"} ${ipv4_routes}"
echo "network_routes_total{family=\"ipv6\",hostname=\"${HOSTNAME}\"} ${ipv6_routes}"
echo "network_routes_total{type=\"default\",hostname=\"${HOSTNAME}\"} ${default_routes}"
fi
echo ""
# --- DNS nameservers ---
echo "# HELP network_dns_nameservers_configured Number of configured DNS nameservers"
echo "# TYPE network_dns_nameservers_configured gauge"
local ns_count=0
if [[ -r /etc/resolv.conf ]]; then
ns_count=$(grep -c "^nameserver" /etc/resolv.conf 2>/dev/null) || ns_count=0
fi
echo "network_dns_nameservers_configured{hostname=\"${HOSTNAME}\"} ${ns_count}"
echo ""
# --- Firewall rules ---
echo "# HELP network_firewall_rules_total Firewall rule count"
echo "# TYPE network_firewall_rules_total gauge"
local ipt_rules=0 ip6t_rules=0
if command -v iptables &>/dev/null; then
ipt_rules=$(iptables -S 2>/dev/null | grep -c '^-A' 2>/dev/null) || ipt_rules=0
fi
if command -v ip6tables &>/dev/null; then
ip6t_rules=$(ip6tables -S 2>/dev/null | grep -c '^-A' 2>/dev/null) || ip6t_rules=0
fi
echo "network_firewall_rules_total{family=\"ipv4\",hostname=\"${HOSTNAME}\"} ${ipt_rules}"
echo "network_firewall_rules_total{family=\"ipv6\",hostname=\"${HOSTNAME}\"} ${ip6t_rules}"
echo ""
# --- Network service status ---
if command -v systemctl &>/dev/null; then
echo "# HELP network_service_active Network service status (1=active, 0=inactive)"
echo "# TYPE network_service_active gauge"
for svc in NetworkManager networking systemd-networkd dhcpcd wpa_supplicant; do
local status=0
systemctl is-active "$svc" &>/dev/null && status=1
echo "network_service_active{service=\"${svc}\",hostname=\"${HOSTNAME}\"} ${status}"
done
echo ""
fi
# --- Socket stats from /proc/net/sockstat ---
if [[ -r /proc/net/sockstat ]]; then
echo "# HELP network_sockets_inuse Sockets currently in use by protocol"
echo "# TYPE network_sockets_inuse gauge"
local tcp_inuse udp_inuse raw_inuse
tcp_inuse=$(awk '/^TCP:/ {print $3}' /proc/net/sockstat 2>/dev/null) || tcp_inuse=0
udp_inuse=$(awk '/^UDP:/ {print $3}' /proc/net/sockstat 2>/dev/null) || udp_inuse=0
raw_inuse=$(awk '/^RAW:/ {print $3}' /proc/net/sockstat 2>/dev/null) || raw_inuse=0
echo "network_sockets_inuse{protocol=\"tcp\",hostname=\"${HOSTNAME}\"} ${tcp_inuse:-0}"
echo "network_sockets_inuse{protocol=\"udp\",hostname=\"${HOSTNAME}\"} ${udp_inuse:-0}"
echo "network_sockets_inuse{protocol=\"raw\",hostname=\"${HOSTNAME}\"} ${raw_inuse:-0}"
echo ""
fi
# --- TCP retransmissions and listen overflows from /proc/net/netstat ---
if [[ -r /proc/net/netstat ]]; then
local tcp_retrans=0 listen_overflows=0 listen_drops=0
# Parse TcpExt header+values pair
local headers values
headers=$(grep "^TcpExt:" /proc/net/netstat | head -1)
values=$(grep "^TcpExt:" /proc/net/netstat | tail -1)
if [[ -n "$headers" && -n "$values" ]]; then
tcp_retrans=$(paste <(echo "$headers" | tr ' ' '\n') <(echo "$values" | tr ' ' '\n') | awk -F'\t' '$1=="TCPRetransSegs" {print $2}')
listen_overflows=$(paste <(echo "$headers" | tr ' ' '\n') <(echo "$values" | tr ' ' '\n') | awk -F'\t' '$1=="ListenOverflows" {print $2}')
listen_drops=$(paste <(echo "$headers" | tr ' ' '\n') <(echo "$values" | tr ' ' '\n') | awk -F'\t' '$1=="ListenDrops" {print $2}')
fi
echo "# HELP network_tcp_retransmit_segments_total TCP segments retransmitted"
echo "# TYPE network_tcp_retransmit_segments_total counter"
echo "network_tcp_retransmit_segments_total{hostname=\"${HOSTNAME}\"} ${tcp_retrans:-0}"
echo ""
echo "# HELP network_tcp_listen_overflows_total TCP listen queue overflows"
echo "# TYPE network_tcp_listen_overflows_total counter"
echo "network_tcp_listen_overflows_total{hostname=\"${HOSTNAME}\"} ${listen_overflows:-0}"
echo ""
echo "# HELP network_tcp_listen_drops_total TCP listen queue drops"
echo "# TYPE network_tcp_listen_drops_total counter"
echo "network_tcp_listen_drops_total{hostname=\"${HOSTNAME}\"} ${listen_drops:-0}"
echo ""
fi
# --- ARP table ---
if [[ -r /proc/net/arp ]]; then
echo "# HELP network_arp_entries_total ARP table entries"
echo "# TYPE network_arp_entries_total gauge"
local arp_count
arp_count=$(($(wc -l < /proc/net/arp) - 1))
[[ $arp_count -lt 0 ]] && arp_count=0
echo "network_arp_entries_total{hostname=\"${HOSTNAME}\"} ${arp_count}"
echo ""
fi
# --- UDP sockets ---
if [[ -r /proc/net/udp ]]; then
echo "# HELP network_udp_sockets_total UDP sockets"
echo "# TYPE network_udp_sockets_total gauge"
local udp_sockets
udp_sockets=$(($(wc -l < /proc/net/udp) - 1))
[[ $udp_sockets -lt 0 ]] && udp_sockets=0
echo "network_udp_sockets_total{hostname=\"${HOSTNAME}\"} ${udp_sockets}"
echo ""
fi
# --- ICMP stats from /proc/net/snmp ---
if [[ -r /proc/net/snmp ]]; then
local icmp_header icmp_values
icmp_header=$(grep "^Icmp:" /proc/net/snmp | head -1)
icmp_values=$(grep "^Icmp:" /proc/net/snmp | tail -1)
if [[ -n "$icmp_header" && -n "$icmp_values" ]]; then
local icmp_in icmp_out icmp_err_in icmp_err_out
icmp_in=$(paste <(echo "$icmp_header" | tr ' ' '\n') <(echo "$icmp_values" | tr ' ' '\n') | awk -F'\t' '$1=="InMsgs" {print $2}')
icmp_out=$(paste <(echo "$icmp_header" | tr ' ' '\n') <(echo "$icmp_values" | tr ' ' '\n') | awk -F'\t' '$1=="OutMsgs" {print $2}')
icmp_err_in=$(paste <(echo "$icmp_header" | tr ' ' '\n') <(echo "$icmp_values" | tr ' ' '\n') | awk -F'\t' '$1=="InErrors" {print $2}')
icmp_err_out=$(paste <(echo "$icmp_header" | tr ' ' '\n') <(echo "$icmp_values" | tr ' ' '\n') | awk -F'\t' '$1=="OutErrors" {print $2}')
echo "# HELP network_icmp_messages_total ICMP messages by direction"
echo "# TYPE network_icmp_messages_total counter"
echo "network_icmp_messages_total{direction=\"in\",hostname=\"${HOSTNAME}\"} ${icmp_in:-0}"
echo "network_icmp_messages_total{direction=\"out\",hostname=\"${HOSTNAME}\"} ${icmp_out:-0}"
echo ""
echo "# HELP network_icmp_errors_total ICMP errors by direction"
echo "# TYPE network_icmp_errors_total counter"
echo "network_icmp_errors_total{direction=\"in\",hostname=\"${HOSTNAME}\"} ${icmp_err_in:-0}"
echo "network_icmp_errors_total{direction=\"out\",hostname=\"${HOSTNAME}\"} ${icmp_err_out:-0}"
echo ""
fi
fi
# --- TCP connections by state ---
if [[ -r /proc/net/tcp ]]; then
echo "# HELP network_tcp_connections TCP connections by state"
echo "# TYPE network_tcp_connections gauge"
# hex state codes: 01=ESTABLISHED, 06=TIME_WAIT, 0A=LISTEN
local tcp_estab tcp_listen tcp_tw tcp_close_wait tcp_syn_recv
tcp_estab=$(awk '$4=="01" {c++} END {print c+0}' /proc/net/tcp 2>/dev/null)
tcp_listen=$(awk '$4=="0A" {c++} END {print c+0}' /proc/net/tcp 2>/dev/null)
tcp_tw=$(awk '$4=="06" {c++} END {print c+0}' /proc/net/tcp 2>/dev/null)
tcp_close_wait=$(awk '$4=="08" {c++} END {print c+0}' /proc/net/tcp 2>/dev/null)
tcp_syn_recv=$(awk '$4=="03" {c++} END {print c+0}' /proc/net/tcp 2>/dev/null)
echo "network_tcp_connections{state=\"established\",hostname=\"${HOSTNAME}\"} ${tcp_estab}"
echo "network_tcp_connections{state=\"listen\",hostname=\"${HOSTNAME}\"} ${tcp_listen}"
echo "network_tcp_connections{state=\"time_wait\",hostname=\"${HOSTNAME}\"} ${tcp_tw}"
echo "network_tcp_connections{state=\"close_wait\",hostname=\"${HOSTNAME}\"} ${tcp_close_wait}"
echo "network_tcp_connections{state=\"syn_recv\",hostname=\"${HOSTNAME}\"} ${tcp_syn_recv}"
echo ""
fi
# --- Conntrack ---
if [[ -r /proc/sys/net/netfilter/nf_conntrack_count ]]; then
echo "# HELP network_nf_conntrack_entries Conntrack table entries"
echo "# TYPE network_nf_conntrack_entries gauge"
echo "network_nf_conntrack_entries{hostname=\"${HOSTNAME}\"} $(cat /proc/sys/net/netfilter/nf_conntrack_count 2>/dev/null || echo 0)"
fi
if [[ -r /proc/sys/net/netfilter/nf_conntrack_max ]]; then
echo "# HELP network_nf_conntrack_entries_limit Conntrack table maximum"
echo "# TYPE network_nf_conntrack_entries_limit gauge"
echo "network_nf_conntrack_entries_limit{hostname=\"${HOSTNAME}\"} $(cat /proc/sys/net/netfilter/nf_conntrack_max 2>/dev/null || echo 0)"
echo ""
fi
# --- Latency (parallel ping) ---
if command -v ping &>/dev/null; then
read -ra targets <<< "$PING_TARGETS"
local tmp_files=() pids=() labels=()
for target in "${targets[@]}"; do
[[ -z "$target" ]] && continue
local label
label=$(echo "$target" | tr -c 'a-zA-Z0-9._-' '_' | sed 's/_*$//')
labels+=("$label")
local tmp
tmp=$(mktemp)
tmp_files+=("$tmp")
(
set +e
timeout $((PING_COUNT * PING_TIMEOUT + 5)) ping -c "$PING_COUNT" -W "$PING_TIMEOUT" "$target" > "$tmp" 2>&1
echo "EXIT=$?" >> "$tmp"
) &
pids+=($!)
done
[[ ${#pids[@]} -gt 0 ]] && wait "${pids[@]}"
# Collect parsed results
local all_min=() all_avg=() all_max=() all_stddev=() all_loss=()
for i in "${!tmp_files[@]}"; do
local tmp="${tmp_files[$i]}"
local exit_code min_v=0 avg_v=0 max_v=0 stddev_v=0 loss_v=100
if [[ -f "$tmp" ]]; then
exit_code=$(grep "^EXIT=" "$tmp" | tail -1 | cut -d= -f2)
if [[ "${exit_code:-1}" -eq 0 ]]; then
local stats
stats=$(grep -E 'rtt min/avg/max/(mdev|stddev)' "$tmp" | head -1)
if [[ -n "$stats" ]]; then
local vals
vals=$(echo "$stats" | cut -d= -f2 | awk '{print $1}')
min_v=$(echo "$vals" | cut -d/ -f1)
avg_v=$(echo "$vals" | cut -d/ -f2)
max_v=$(echo "$vals" | cut -d/ -f3)
stddev_v=$(echo "$vals" | cut -d/ -f4)
fi
loss_v=$(grep -oP '\d+(?=% packet loss)' "$tmp" | head -1)
fi
rm -f "$tmp"
fi
all_min+=("${min_v:-0}")
all_avg+=("${avg_v:-0}")
all_max+=("${max_v:-0}")
all_stddev+=("${stddev_v:-0}")
all_loss+=("${loss_v:-100}")
done
echo "# HELP network_ping_rtt_min_milliseconds Minimum ping RTT"
echo "# TYPE network_ping_rtt_min_milliseconds gauge"
for i in "${!labels[@]}"; do
echo "network_ping_rtt_min_milliseconds{target=\"${labels[$i]}\",hostname=\"${HOSTNAME}\"} ${all_min[$i]}"
done
echo ""
echo "# HELP network_ping_rtt_avg_milliseconds Average ping RTT"
echo "# TYPE network_ping_rtt_avg_milliseconds gauge"
for i in "${!labels[@]}"; do
echo "network_ping_rtt_avg_milliseconds{target=\"${labels[$i]}\",hostname=\"${HOSTNAME}\"} ${all_avg[$i]}"
done
echo ""
echo "# HELP network_ping_rtt_max_milliseconds Maximum ping RTT"
echo "# TYPE network_ping_rtt_max_milliseconds gauge"
for i in "${!labels[@]}"; do
echo "network_ping_rtt_max_milliseconds{target=\"${labels[$i]}\",hostname=\"${HOSTNAME}\"} ${all_max[$i]}"
done
echo ""
echo "# HELP network_ping_rtt_stddev_milliseconds Ping RTT standard deviation (jitter)"
echo "# TYPE network_ping_rtt_stddev_milliseconds gauge"
for i in "${!labels[@]}"; do
echo "network_ping_rtt_stddev_milliseconds{target=\"${labels[$i]}\",hostname=\"${HOSTNAME}\"} ${all_stddev[$i]}"
done
echo ""
echo "# HELP network_ping_packet_loss_percent Packet loss percentage"
echo "# TYPE network_ping_packet_loss_percent gauge"
for i in "${!labels[@]}"; do
echo "network_ping_packet_loss_percent{target=\"${labels[$i]}\",hostname=\"${HOSTNAME}\"} ${all_loss[$i]}"
done
echo ""
fi
# --- Exporter timing ---
local END_TIME DURATION
END_TIME=$(date +%s.%N)
DURATION=$(echo "$END_TIME - $START_TIME" | bc)
echo "# HELP network_exporter_duration_seconds Time to generate all metrics"
echo "# TYPE network_exporter_duration_seconds gauge"
echo "network_exporter_duration_seconds{hostname=\"${HOSTNAME}\"} ${DURATION}"
echo ""
echo "# HELP network_exporter_last_run_timestamp Unix timestamp of last successful run"
echo "# TYPE network_exporter_last_run_timestamp gauge"
echo "network_exporter_last_run_timestamp{hostname=\"${HOSTNAME}\"} $(date +%s)"
}
# ============================================================================
# MAIN EXECUTION
# ============================================================================
main() {
parse_args "$@"
if [[ $EUID -ne 0 ]]; then
echo "Error: This script must be run as root" >&2
exit 1
fi
if [ -n "$OUTPUT_FILE" ]; then
local output_dir
output_dir="$(dirname "$OUTPUT_FILE")"
mkdir -p "$output_dir"
local temp_file
temp_file=$(mktemp "${output_dir}/.network_info.XXXXXX")
if ! generate_metrics > "$temp_file" 2>/dev/null; then
rm -f "$temp_file"
echo "ERROR: Failed to generate metrics" >&2
exit 1
fi
local file_lines
file_lines=$(wc -l < "$temp_file" 2>/dev/null || echo 0)
if [ "$file_lines" -lt 10 ]; then
rm -f "$temp_file"
echo "ERROR: Metrics file too small ($file_lines lines), keeping previous" >&2
exit 1
fi
chmod 644 "$temp_file"
mv -f "$temp_file" "$OUTPUT_FILE"
echo "Metrics written to $OUTPUT_FILE ($file_lines lines)" >&2
else
generate_metrics
fi
}
main "$@"