a1a17e81a1
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.
477 lines
20 KiB
Bash
477 lines
20 KiB
Bash
#!/usr/bin/env bash
|
|
|
|
#########################################################################################
|
|
#### network-diag.sh — One-shot network diagnostics dump for Linux servers ####
|
|
#### Shows interfaces, routes, DNS, firewall rules, listening ports, connections ####
|
|
#### ####
|
|
#### Author: Phil Connor ####
|
|
#### Contact: contact@mylinux.work ####
|
|
#### License: MIT ####
|
|
#### Version 1.00 ####
|
|
#### ####
|
|
#### Usage: ####
|
|
#### ./network-diag.sh ####
|
|
#### ./network-diag.sh --section interfaces,dns ####
|
|
#### ####
|
|
#### See --help for all options. ####
|
|
#########################################################################################
|
|
|
|
set -euo pipefail
|
|
|
|
# ── Defaults ──────────────────────────────────────────────────────────
|
|
SECTIONS="${SECTIONS:-all}"
|
|
VERBOSE="${VERBOSE:-false}"
|
|
COLOR="${COLOR:-auto}"
|
|
|
|
# ── State ─────────────────────────────────────────────────────────────
|
|
SCRIPT_NAME="$(basename "$0")"
|
|
readonly SCRIPT_NAME
|
|
|
|
# ── Colors ────────────────────────────────────────────────────────────
|
|
setup_colors() {
|
|
if [[ "$COLOR" == "never" ]]; then
|
|
RED="" GREEN="" YELLOW="" CYAN="" BOLD="" DIM="" RESET=""
|
|
return
|
|
fi
|
|
if [[ "$COLOR" == "always" ]] || [[ -t 1 ]]; then
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[0;33m'
|
|
CYAN='\033[0;36m'
|
|
BOLD='\033[1m'
|
|
DIM='\033[2m'
|
|
RESET='\033[0m'
|
|
else
|
|
RED="" GREEN="" YELLOW="" CYAN="" BOLD="" DIM="" RESET=""
|
|
fi
|
|
}
|
|
|
|
# ── Logging ───────────────────────────────────────────────────────────
|
|
log() { echo -e "${DIM}[INFO]${RESET} $*"; }
|
|
warn() { echo -e "${YELLOW}[WARN]${RESET} $*" >&2; }
|
|
verbose() { if [[ "$VERBOSE" == "true" ]]; then echo -e "${DIM}[DEBUG]${RESET} $*"; fi; }
|
|
|
|
# ── Helpers ───────────────────────────────────────────────────────────
|
|
section_header() {
|
|
echo ""
|
|
echo -e " ${BOLD}${CYAN}── $1 ──${RESET}"
|
|
echo ""
|
|
}
|
|
|
|
field() {
|
|
printf " ${BOLD}%-22s${RESET} %s\n" "$1" "$2"
|
|
}
|
|
|
|
field_color() {
|
|
printf " ${BOLD}%-22s${RESET} %b\n" "$1" "$2"
|
|
}
|
|
|
|
should_show() {
|
|
[[ "$SECTIONS" == "all" ]] || [[ ",$SECTIONS," == *",$1,"* ]]
|
|
}
|
|
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
# INTERFACES
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
|
|
show_interfaces() {
|
|
section_header "Network Interfaces"
|
|
|
|
printf " ${BOLD}%-16s %-18s %-20s %-8s %s${RESET}\n" "INTERFACE" "IP ADDRESS" "MAC" "STATE" "MTU"
|
|
printf " %s\n" "$(printf '%.0s─' {1..72})"
|
|
|
|
if command -v ip &>/dev/null; then
|
|
local ifaces
|
|
ifaces=$(ip -o link show 2>/dev/null | awk -F': ' '{print $2}' | sed 's/@.*//')
|
|
|
|
while IFS= read -r iface; do
|
|
[[ -z "$iface" ]] && continue
|
|
|
|
local ip_addr mac state mtu
|
|
ip_addr=$(ip -4 -o addr show "$iface" 2>/dev/null | awk '{print $4}' | head -1)
|
|
ip_addr="${ip_addr:-—}"
|
|
mac=$(ip link show "$iface" 2>/dev/null | awk '/link\/ether/ {print $2}')
|
|
mac="${mac:-—}"
|
|
state=$(ip link show "$iface" 2>/dev/null | grep -oP 'state \K\S+' || echo "UNKNOWN")
|
|
mtu=$(ip link show "$iface" 2>/dev/null | grep -oP 'mtu \K\d+' || echo "—")
|
|
|
|
local state_color="$DIM"
|
|
if [[ "$state" == "UP" ]]; then
|
|
state_color="$GREEN"
|
|
elif [[ "$state" == "DOWN" ]]; then
|
|
state_color="$RED"
|
|
fi
|
|
|
|
printf " %-16s %-18s %-20s %b%-8s%b %s\n" \
|
|
"$iface" "$ip_addr" "$mac" "$state_color" "$state" "$RESET" "$mtu"
|
|
done <<< "$ifaces"
|
|
else
|
|
warn "ip command not available"
|
|
ifconfig 2>/dev/null || echo " No interface information available"
|
|
fi
|
|
|
|
# IPv6 addresses
|
|
if command -v ip &>/dev/null; then
|
|
local ipv6_count
|
|
ipv6_count=$(ip -6 -o addr show scope global 2>/dev/null | wc -l)
|
|
if [[ "$ipv6_count" -gt 0 ]]; then
|
|
echo ""
|
|
echo -e " ${BOLD}IPv6 Global Addresses:${RESET}"
|
|
ip -6 -o addr show scope global 2>/dev/null | while IFS= read -r line; do
|
|
local v6_iface v6_addr
|
|
v6_iface=$(echo "$line" | awk '{print $2}')
|
|
v6_addr=$(echo "$line" | awk '{print $4}')
|
|
printf " %-16s %s\n" "$v6_iface" "$v6_addr"
|
|
done
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
# ROUTES
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
|
|
show_routes() {
|
|
section_header "Routing"
|
|
|
|
if command -v ip &>/dev/null; then
|
|
# Default route
|
|
local default_route
|
|
default_route=$(ip route show default 2>/dev/null | head -1)
|
|
if [[ -n "$default_route" ]]; then
|
|
field "Default route:" "$default_route"
|
|
else
|
|
field_color "Default route:" "${YELLOW}None${RESET}"
|
|
fi
|
|
|
|
echo ""
|
|
echo -e " ${BOLD}Routing Table:${RESET}"
|
|
printf " %-24s %-18s %-12s %s\n" "DESTINATION" "GATEWAY" "DEVICE" "PROTO"
|
|
printf " %s\n" "$(printf '%.0s─' {1..65})"
|
|
|
|
ip route show 2>/dev/null | while IFS= read -r line; do
|
|
local dest gw dev proto
|
|
dest=$(echo "$line" | awk '{print $1}')
|
|
gw=$(echo "$line" | grep -oP 'via \K\S+' || echo "—")
|
|
dev=$(echo "$line" | grep -oP 'dev \K\S+' || echo "—")
|
|
proto=$(echo "$line" | grep -oP 'proto \K\S+' || echo "—")
|
|
printf " %-24s %-18s %-12s %s\n" "$dest" "$gw" "$dev" "$proto"
|
|
done
|
|
else
|
|
route -n 2>/dev/null || echo " No routing information available"
|
|
fi
|
|
}
|
|
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
# DNS
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
|
|
show_dns() {
|
|
section_header "DNS Configuration"
|
|
|
|
# /etc/resolv.conf
|
|
if [[ -f /etc/resolv.conf ]]; then
|
|
echo -e " ${BOLD}/etc/resolv.conf:${RESET}"
|
|
|
|
local nameservers search_domains
|
|
nameservers=$(grep "^nameserver" /etc/resolv.conf 2>/dev/null | awk '{print $2}')
|
|
search_domains=$(grep "^search" /etc/resolv.conf 2>/dev/null | sed 's/^search //')
|
|
|
|
if [[ -n "$nameservers" ]]; then
|
|
while IFS= read -r ns; do
|
|
printf " Nameserver: %s\n" "$ns"
|
|
done <<< "$nameservers"
|
|
fi
|
|
|
|
if [[ -n "${search_domains:-}" ]]; then
|
|
printf " Search: %s\n" "$search_domains"
|
|
fi
|
|
|
|
# Check if resolv.conf is a symlink (systemd-resolved)
|
|
if [[ -L /etc/resolv.conf ]]; then
|
|
local link_target
|
|
link_target=$(readlink -f /etc/resolv.conf)
|
|
printf " ${DIM}(symlink → %s)${RESET}\n" "$link_target"
|
|
fi
|
|
fi
|
|
|
|
# systemd-resolved
|
|
if command -v resolvectl &>/dev/null; then
|
|
echo ""
|
|
echo -e " ${BOLD}systemd-resolved:${RESET}"
|
|
resolvectl status 2>/dev/null | grep -E "DNS Server|DNS Domain|DNSSEC" | while IFS= read -r line; do
|
|
printf " %s\n" "$line"
|
|
done
|
|
elif command -v systemd-resolve &>/dev/null; then
|
|
echo ""
|
|
echo -e " ${BOLD}systemd-resolved:${RESET}"
|
|
systemd-resolve --status 2>/dev/null | grep -E "DNS Server|DNS Domain|DNSSEC" | while IFS= read -r line; do
|
|
printf " %s\n" "$line"
|
|
done
|
|
fi
|
|
}
|
|
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
# FIREWALL
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
|
|
show_firewall() {
|
|
section_header "Firewall"
|
|
|
|
local fw_found=false
|
|
|
|
# iptables
|
|
if command -v iptables &>/dev/null; then
|
|
fw_found=true
|
|
echo -e " ${BOLD}iptables:${RESET}"
|
|
|
|
local ipt_rules
|
|
ipt_rules=$(iptables -S 2>/dev/null | wc -l || echo "0")
|
|
printf " Rules: %d\n" "$ipt_rules"
|
|
|
|
if [[ "$VERBOSE" == "true" ]]; then
|
|
iptables -L -n --line-numbers 2>/dev/null | while IFS= read -r line; do
|
|
printf " %s\n" "$line"
|
|
done
|
|
else
|
|
# Summary by chain
|
|
iptables -S 2>/dev/null | awk '/^-A/ {print $2}' | sort | uniq -c | sort -rn | while IFS= read -r line; do
|
|
printf " %s\n" "$line"
|
|
done
|
|
fi
|
|
fi
|
|
|
|
# nftables
|
|
if command -v nft &>/dev/null; then
|
|
fw_found=true
|
|
echo ""
|
|
echo -e " ${BOLD}nftables:${RESET}"
|
|
|
|
local nft_tables
|
|
nft_tables=$(nft list tables 2>/dev/null | wc -l || echo "0")
|
|
printf " Tables: %d\n" "$nft_tables"
|
|
|
|
if [[ "$VERBOSE" == "true" ]]; then
|
|
nft list ruleset 2>/dev/null | while IFS= read -r line; do
|
|
printf " %s\n" "$line"
|
|
done
|
|
else
|
|
nft list tables 2>/dev/null | while IFS= read -r line; do
|
|
printf " %s\n" "$line"
|
|
done
|
|
fi
|
|
fi
|
|
|
|
# ufw
|
|
if command -v ufw &>/dev/null; then
|
|
fw_found=true
|
|
echo ""
|
|
echo -e " ${BOLD}UFW:${RESET}"
|
|
local ufw_status
|
|
ufw_status=$(ufw status 2>/dev/null | head -1 || echo "Unknown")
|
|
printf " %s\n" "$ufw_status"
|
|
fi
|
|
|
|
# firewalld
|
|
if command -v firewall-cmd &>/dev/null; then
|
|
fw_found=true
|
|
echo ""
|
|
echo -e " ${BOLD}firewalld:${RESET}"
|
|
local fwd_state
|
|
fwd_state=$(firewall-cmd --state 2>/dev/null || echo "not running")
|
|
printf " State: %s\n" "$fwd_state"
|
|
|
|
if [[ "$fwd_state" == "running" ]]; then
|
|
local active_zones
|
|
active_zones=$(firewall-cmd --get-active-zones 2>/dev/null | head -5)
|
|
if [[ -n "$active_zones" ]]; then
|
|
printf " Active zones:\n"
|
|
echo "$active_zones" | while IFS= read -r line; do
|
|
printf " %s\n" "$line"
|
|
done
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
if [[ "$fw_found" == "false" ]]; then
|
|
log "No firewall tools detected (iptables, nftables, ufw, firewalld)"
|
|
fi
|
|
}
|
|
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
# LISTENING PORTS
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
|
|
show_ports() {
|
|
section_header "Listening Ports"
|
|
|
|
if command -v ss &>/dev/null; then
|
|
printf " ${BOLD}%-8s %-30s %-8s %s${RESET}\n" "PROTO" "LISTEN ADDRESS" "PORT" "PROCESS"
|
|
printf " %s\n" "$(printf '%.0s─' {1..70})"
|
|
|
|
ss -tlnp 2>/dev/null | tail -n +2 | while IFS= read -r line; do
|
|
local addr port_str proto proc_info
|
|
proto="tcp"
|
|
addr=$(echo "$line" | awk '{print $4}')
|
|
port_str="${addr##*:}"
|
|
proc_info=$(echo "$line" | grep -oP 'users:\(\("\K[^"]+' || echo "—")
|
|
printf " %-8s %-30s %-8s %s\n" "$proto" "$addr" "$port_str" "$proc_info"
|
|
done
|
|
|
|
# UDP listeners
|
|
local udp_count
|
|
udp_count=$(ss -ulnp 2>/dev/null | tail -n +2 | wc -l)
|
|
if [[ "$udp_count" -gt 0 ]]; then
|
|
echo ""
|
|
ss -ulnp 2>/dev/null | tail -n +2 | while IFS= read -r line; do
|
|
local addr port_str proc_info
|
|
addr=$(echo "$line" | awk '{print $4}')
|
|
port_str="${addr##*:}"
|
|
proc_info=$(echo "$line" | grep -oP 'users:\(\("\K[^"]+' || echo "—")
|
|
printf " %-8s %-30s %-8s %s\n" "udp" "$addr" "$port_str" "$proc_info"
|
|
done
|
|
fi
|
|
|
|
local total_tcp total_udp
|
|
total_tcp=$(ss -tlnp 2>/dev/null | tail -n +2 | wc -l)
|
|
total_udp=$(ss -ulnp 2>/dev/null | tail -n +2 | wc -l)
|
|
echo ""
|
|
field "TCP listeners:" "$total_tcp"
|
|
field "UDP listeners:" "$total_udp"
|
|
elif command -v netstat &>/dev/null; then
|
|
netstat -tlnp 2>/dev/null | while IFS= read -r line; do
|
|
printf " %s\n" "$line"
|
|
done
|
|
else
|
|
warn "Neither ss nor netstat available"
|
|
fi
|
|
}
|
|
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
# CONNECTIONS
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
|
|
show_connections() {
|
|
section_header "Active Connections"
|
|
|
|
if command -v ss &>/dev/null; then
|
|
echo -e " ${BOLD}Connections by state:${RESET}"
|
|
printf " %-20s %s\n" "STATE" "COUNT"
|
|
printf " %s\n" "$(printf '%.0s─' {1..30})"
|
|
|
|
ss -tan 2>/dev/null | tail -n +2 | awk '{print $1}' | sort | uniq -c | sort -rn | while IFS= read -r line; do
|
|
local count state
|
|
count=$(echo "$line" | awk '{print $1}')
|
|
state=$(echo "$line" | awk '{print $2}')
|
|
|
|
local color=""
|
|
case "$state" in
|
|
ESTAB) color="$GREEN" ;;
|
|
TIME-WAIT) color="$YELLOW" ;;
|
|
CLOSE-WAIT) color="$RED" ;;
|
|
*) color="" ;;
|
|
esac
|
|
|
|
printf " %b%-20s%b %s\n" "$color" "$state" "$RESET" "$count"
|
|
done
|
|
|
|
local total_conn
|
|
total_conn=$(ss -tan 2>/dev/null | tail -n +2 | wc -l)
|
|
echo ""
|
|
field "Total connections:" "$total_conn"
|
|
elif command -v netstat &>/dev/null; then
|
|
netstat -tan 2>/dev/null | awk '/^tcp/ {print $6}' | sort | uniq -c | sort -rn | while IFS= read -r line; do
|
|
printf " %s\n" "$line"
|
|
done
|
|
else
|
|
warn "Neither ss nor netstat available"
|
|
fi
|
|
}
|
|
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
# USAGE
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
|
|
usage() {
|
|
cat <<EOF
|
|
${SCRIPT_NAME} — One-shot network diagnostics dump for Linux servers
|
|
|
|
USAGE:
|
|
${SCRIPT_NAME} [OPTIONS]
|
|
|
|
OPTIONS:
|
|
--section SECTIONS Comma-separated sections to show (default: all)
|
|
Available: interfaces, routes, dns, firewall, ports,
|
|
connections
|
|
--verbose Enable debug output (show full firewall rules)
|
|
--no-color Disable colored output
|
|
--help Show this help
|
|
|
|
ENVIRONMENT VARIABLES:
|
|
SECTIONS Sections to display (default: all)
|
|
COLOR Color mode: auto, always, never (default: auto)
|
|
|
|
EXAMPLES:
|
|
# Full network dump
|
|
./network-diag.sh
|
|
|
|
# Interfaces and DNS only
|
|
./network-diag.sh --section interfaces,dns
|
|
|
|
# Verbose firewall rules
|
|
./network-diag.sh --section firewall --verbose
|
|
|
|
# Pipe-friendly
|
|
./network-diag.sh --no-color | tee /tmp/netdiag-\$(hostname).txt
|
|
EOF
|
|
}
|
|
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
# ARGUMENT PARSING
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
|
|
parse_args() {
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--section)
|
|
SECTIONS="$2"; shift 2 ;;
|
|
--verbose)
|
|
VERBOSE="true"; shift ;;
|
|
--no-color)
|
|
COLOR="never"; shift ;;
|
|
--help|-h)
|
|
setup_colors
|
|
usage
|
|
exit 0 ;;
|
|
*)
|
|
echo "Unknown option: $1" >&2
|
|
echo "Run ${SCRIPT_NAME} --help for usage" >&2
|
|
exit 1 ;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
# MAIN
|
|
# ══════════════════════════════════════════════════════════════════════
|
|
|
|
main() {
|
|
parse_args "$@"
|
|
setup_colors
|
|
|
|
echo ""
|
|
echo -e "${BOLD}Network Diagnostics — $(hostname -f 2>/dev/null || hostname)${RESET}"
|
|
echo -e "${DIM}$(date '+%Y-%m-%d %H:%M:%S %Z')${RESET}"
|
|
|
|
should_show "interfaces" && show_interfaces
|
|
should_show "routes" && show_routes
|
|
should_show "dns" && show_dns
|
|
should_show "firewall" && show_firewall
|
|
should_show "ports" && show_ports
|
|
should_show "connections" && show_connections
|
|
|
|
echo ""
|
|
}
|
|
|
|
main "$@"
|