|
#!/usr/bin/env bash |
|
set -uo pipefail |
|
|
|
usage() { |
|
cat <<EOF |
|
Usage: $0 -f servers.csv -d domain [-r record] [-n tries] [-p parallelism] |
|
-f CSV file (with header, including ip_address, as_org, city, country_code) |
|
-d Domain to query |
|
-r DNS record type to query (A, NS, MX, TXT, etc.; default: MX) |
|
-n Number of attempts per server (default: 3) |
|
-p Max parallel jobs (default: 10) |
|
EOF |
|
exit 1 |
|
} |
|
|
|
# defaults |
|
tries=3 |
|
parallelism=10 |
|
record="MX" |
|
|
|
# parse options |
|
while getopts ":f:d:r:n:p:" opt; do |
|
case $opt in |
|
f) file="$OPTARG" ;; |
|
d) domain="$OPTARG" ;; |
|
r) record="${OPTARG^^}" ;; # uppercase record type |
|
n) tries="$OPTARG" ;; |
|
p) parallelism="$OPTARG" ;; |
|
*) usage ;; |
|
esac |
|
done |
|
[[ -z "${file:-}" || -z "${domain:-}" ]] && usage |
|
|
|
# prepare workspace |
|
mapfile -t lines < <(tail -n +2 "$file" | sed 's/\r$//') |
|
workdir=$(mktemp -d) |
|
lockfile="$workdir/.lock" |
|
touch "$lockfile" |
|
trap 'rm -rf "$workdir"' EXIT |
|
|
|
# header |
|
printf "\nQuerying %-4s records for %-25s\n\n" "$record" "$domain" |
|
printf "%-15s | %-20s | %-15s | %-10s | %-2s | %8s | %9s | %11s | %15s\n" \ |
|
"IP Address" "Org" "City" "ASN" "CC" "Attempts" "Successes" "Reliability" "Avg Delay(ms)" |
|
printf -- "-----------------+----------------------+-----------------+------------+----+----------+-----------+-------------+----------------\n" |
|
|
|
# per-server worker |
|
process_server(){ |
|
local line=$1 |
|
IFS=',' read -r ip _ asn org cc city _ _ _ _ <<<"$line" |
|
ip="${ip//[[:space:]]/}" || return |
|
[[ -z "$ip" ]] && return |
|
|
|
local success=0 total_delay=0 |
|
for ((i=1; i<=tries; i++)); do |
|
if out=$(dig +time=5 +tries=1 +stats "$record" "$domain" @"$ip" 2>&1); then |
|
if grep -qE "^[^;].+${record}[[:space:]]" <<<"$out"; then |
|
(( success++ )) |
|
delay=$(awk '/^;; Query time:/ {print $4}' <<<"$out") |
|
total_delay=$(( total_delay + delay )) |
|
fi |
|
fi |
|
done |
|
|
|
local avg_delay=0 |
|
(( success > 0 )) && avg_delay=$(( total_delay / success )) |
|
local reliability |
|
reliability=$(awk "BEGIN{printf \"%.1f\", ($success/$tries)*100}") |
|
|
|
# flag any server that worked |
|
if (( success > 0 )); then |
|
touch "$workdir/${ip//./-}.ok" |
|
fi |
|
|
|
# save raw for summary |
|
printf "%s|%s|%s|%s|%s|%d|%d|%s|%d\n" \ |
|
"$ip" "${org:-—}" "${city:-—}" "${asn:-—}" "${cc:-—}" \ |
|
"$tries" "$success" "$reliability" "$avg_delay" \ |
|
> "$workdir/${ip//./-}.out" |
|
|
|
# real-time row (locked) |
|
local disp_avg="—" |
|
(( success > 0 )) && disp_avg="$avg_delay" |
|
flock -x "$lockfile" -c \ |
|
"printf \"%-15s | %-20s | %-15s | %-10s | %-2s | %8d | %9d | %10s%% | %15s\n\" \ |
|
\"$ip\" \"${org:-—}\" \"${city:-—}\" \"${asn:-—}\" \"${cc:-—}\" \ |
|
\"$tries\" \"$success\" \"$reliability\" \"$disp_avg\"" |
|
} |
|
|
|
# semaphore |
|
wait_for_slot(){ |
|
while (( $(jobs -rp | wc -l) >= parallelism )); do |
|
sleep 0.05 |
|
done |
|
} |
|
|
|
# dispatch |
|
for line in "${lines[@]}"; do |
|
wait_for_slot |
|
process_server "$line" & |
|
done |
|
|
|
wait |
|
|
|
# final aggregation |
|
total_servers=0 |
|
total_successes=0 |
|
total_delay_sum=0 |
|
|
|
for f in "$workdir"/*.out; do |
|
IFS='|' read -r ip org city asn cc att succ reli avg <<<"$(<"$f")" |
|
(( total_servers++ )) |
|
total_successes=$(( total_successes + succ )) |
|
total_delay_sum=$(( total_delay_sum + avg )) |
|
done |
|
|
|
# count servers that worked |
|
total_ok=$(ls "$workdir"/*.ok 2>/dev/null | wc -l) |
|
|
|
# summary |
|
echo -e "\n📊 Overall Summary:" |
|
echo "---------------------------" |
|
echo "Record Type : $record" |
|
echo "Domain : $domain" |
|
echo "Total DNS Servers Tested : $total_servers" |
|
echo "Total Successful Queries : $total_successes" |
|
echo "Servers that worked : $total_ok / $total_servers" |
|
|
|
if (( total_servers > 0 )); then |
|
avg_reliability=$(awk "BEGIN{printf \"%.1f\", ($total_successes/($total_servers*tries))*100}") |
|
avg_response_delay=$(( total_delay_sum / total_servers )) |
|
echo "Average Reliability : $avg_reliability%" |
|
echo "Average Response Delay : ${avg_response_delay} ms" |
|
else |
|
echo "Average Reliability : —" |
|
echo "Average Response Delay : —" |
|
fi |