reverse-probe.sh 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. #!/usr/bin/env bash
  2. # net-ops :: reverse-probe.sh
  3. # Diagnose a TARGET host from OUTSIDE — useful when the local probe on the
  4. # target says "all good" but external services / users still report problems.
  5. # Runs from this machine against a target host you can reach (LAN, tailnet,
  6. # public IP, etc).
  7. #
  8. # Usage:
  9. # scripts/reverse-probe.sh <host> # use default ports/checks
  10. # scripts/reverse-probe.sh <host> [port...] # add custom TCP ports to probe
  11. #
  12. # Examples:
  13. # scripts/reverse-probe.sh example.local
  14. # scripts/reverse-probe.sh 100.84.X.X 8080 5432
  15. # scripts/reverse-probe.sh api.mycompany.com 443
  16. set -u
  17. TARGET="${1:-}"
  18. if [[ -z "$TARGET" ]]; then
  19. echo "Usage: $0 <host> [extra_tcp_port ...]" >&2
  20. exit 1
  21. fi
  22. shift
  23. EXTRA_PORTS=("$@")
  24. DEFAULT_PORTS=(22 80 443)
  25. TIMEOUT=4
  26. # shellcheck source=_lib/redact.sh
  27. source "$(dirname "$0")/_lib/redact.sh"
  28. # shellcheck source=_lib/output.sh
  29. source "$(dirname "$0")/_lib/output.sh"
  30. parse_redact_flag "$@"
  31. parse_output_flags "$@"
  32. maybe_redact_self "$TARGET" "$@"
  33. # Resolve target — separates DNS issues from reachability issues
  34. section "1. NAME RESOLUTION FROM HERE"
  35. if [[ "$TARGET" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
  36. pass "Target is literal IP" "$TARGET"
  37. TARGET_IP="$TARGET"
  38. else
  39. resolved=$(dig +short +time=3 +tries=1 "$TARGET" 2>/dev/null | head -1)
  40. if [[ -n "$resolved" ]]; then
  41. pass "Resolved $TARGET (dig, bypass resolver)" "$resolved"
  42. TARGET_IP="$resolved"
  43. else
  44. fail "Resolved $TARGET" "no answer from local DNS — can't proceed past name layer"
  45. emit_summary
  46. exit 1
  47. fi
  48. fi
  49. section "2. ICMP REACHABILITY"
  50. if ping -c 2 -W $((TIMEOUT * 1000)) "$TARGET_IP" >/dev/null 2>&1; then
  51. pass "Ping $TARGET_IP"
  52. else
  53. fail "Ping $TARGET_IP" "no ICMP response (or ICMP filtered)"
  54. fi
  55. section "3. TCP PORT REACHABILITY"
  56. # De-duplicate ports — extras may overlap defaults
  57. all_ports=$(printf '%s\n' "${DEFAULT_PORTS[@]}" ${EXTRA_PORTS[@]+"${EXTRA_PORTS[@]}"} | awk '!seen[$0]++')
  58. while read -r port; do
  59. [[ -z "$port" ]] && continue
  60. if nc -zv -G "$TIMEOUT" "$TARGET_IP" "$port" >/dev/null 2>&1; then
  61. pass "TCP/$port -> $TARGET_IP" "open"
  62. else
  63. fail "TCP/$port -> $TARGET_IP" "closed or filtered"
  64. fi
  65. done <<< "$all_ports"
  66. section "4. TLS / HTTPS HEALTH (if 443 open)"
  67. if nc -zv -G "$TIMEOUT" "$TARGET_IP" 443 >/dev/null 2>&1; then
  68. if [[ "$TARGET" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
  69. # Connect by IP; cert check will fail SNI but we can still probe
  70. out=$(curl -sS -o /dev/null -w "%{http_code}|%{time_total}" --max-time "$TIMEOUT" -k "https://$TARGET_IP" 2>&1)
  71. pass "HTTPS to IP (cert SNI may not match)" "$out"
  72. else
  73. out=$(curl -sS -o /dev/null -w "%{http_code}|%{time_total}" --max-time "$TIMEOUT" "https://$TARGET" 2>&1)
  74. if [[ "$out" =~ ^[0-9]+\|[0-9.]+$ ]]; then
  75. pass "HTTPS to $TARGET" "$out"
  76. else
  77. fail "HTTPS to $TARGET" "$out"
  78. fi
  79. fi
  80. fi
  81. section "5. PATH / ROUTING"
  82. case "$(uname -s)" in
  83. Darwin)
  84. # macOS traceroute: -w timeout (sec), -m max hops, -q probes per hop
  85. info " traceroute (first 8 hops):"
  86. traceroute -n -w 2 -q 1 -m 8 "$TARGET_IP" 2>/dev/null | head -10 | sed 's/^/ /' || true
  87. ;;
  88. Linux)
  89. if command -v traceroute >/dev/null 2>&1; then
  90. info " traceroute (first 8 hops):"
  91. traceroute -n -w 2 -q 1 -m 8 "$TARGET_IP" 2>/dev/null | head -10 | sed 's/^/ /' || true
  92. elif command -v mtr >/dev/null 2>&1; then
  93. info " mtr report (5 cycles):"
  94. mtr -nrc 5 "$TARGET_IP" 2>/dev/null | tail -10 | sed 's/^/ /' || true
  95. fi
  96. ;;
  97. esac
  98. emit_summary