Skip to content

Instantly share code, notes, and snippets.

@gunk1n
Last active October 29, 2025 23:57
Show Gist options
  • Select an option

  • Save gunk1n/4e3f7574452cc1ccf0b0f724300d7f60 to your computer and use it in GitHub Desktop.

Select an option

Save gunk1n/4e3f7574452cc1ccf0b0f724300d7f60 to your computer and use it in GitHub Desktop.
🔘 This script checks the availability of a range of IP addresses based on a specified pattern. It sends ICMP ping requests to each address in sequence and displays the results with color‑coded output.
#!/bin/bash
# Цвета
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
# Порты для проверки (можно изменить)
TCP_PORTS="22 80 443 53 21 25 110 143 993 995 3389"
UDP_PORTS="53 67 68 123 161 500 4500"
# Настройки многопоточности
THREADS=10
PING_TIMEOUT=2
TCP_TIMEOUT=2
UDP_TIMEOUT=3
# Переменные для сохранения в файл
OUTPUT_FILE="scan_results_$(date +%Y%m%d_%H%M%S).txt"
SAVE_TO_FILE=true
show_help() {
local width=120
local inner_width=$((width - 4))
local indent=" "
frame_line() {
local text="$1"
if [ -z "$text" ]; then
printf "%${inner_width}s\n" ""
else
local padded="${indent}${text}"
printf "%-${inner_width}s\n" "${padded:0:$inner_width}"
fi
}
line=$(printf '%*s' "$width" | tr ' ' '-')
printf '+%s+\n' "$line"
frame_line ""
frame_line "Usage: $0 <subnet> <range> [threads]"
frame_line " $0 --subnets <subnet1> <subnet2> ... --range <range> [threads]"
frame_line " $0 --file <filename> --range <range> [threads]"
frame_line ""
frame_line "Description:"
frame_line " Fast parallel scanning of IP ranges with ICMP, TCP/UDP ports and PTR checks"
frame_line ""
frame_line "Arguments:"
frame_line " Single subnet mode:"
frame_line " <subnet> Base subnet in format NNN.NNN.NNN. (each NNN: 1–255, no leading zeros)"
frame_line " <range> Number of IPs to scan (0–255)"
frame_line " [threads] Number of parallel threads (default: $THREADS, max: 50)"
frame_line ""
frame_line " Multiple subnets mode:"
frame_line " --subnets List of subnets to scan"
frame_line " --file File containing list of subnets (one per line)"
frame_line " --range Number of IPs to scan for each subnet (0–255)"
frame_line " [threads] Number of parallel threads"
frame_line ""
frame_line "Options:"
frame_line " -h, --help Display this help message and exit"
frame_line ""
frame_line "Examples:"
frame_line " Single subnet:"
frame_line " $0 192.168.1. 5"
frame_line " $0 10.0.0. 10 20"
frame_line ""
frame_line " Multiple subnets:"
frame_line " $0 --subnets 192.168.1. 10.0.0. 172.16.1. --range 10"
frame_line " $0 --file subnets.txt --range 50 25"
frame_line " $0 --help"
frame_line ""
frame_line "File format for --file:"
frame_line " 192.168.1."
frame_line " 10.0.0."
frame_line " 172.16.1."
frame_line ""
frame_line "Performance features:"
frame_line " - Parallel scanning with configurable threads"
frame_line " - Bulk ICMP ping for initial host discovery"
frame_line " - Sequential port scanning only for alive hosts"
frame_line " - Optimized timeouts for each check type"
frame_line ""
frame_line "Output format:"
frame_line " PING: {✅ is alive (value_ms), ❌ is not reachable}"
frame_line " TCP: {✅ open, ❌ close} port0,port1,portX"
frame_line " UDP: {✅ open, ❌ close} port0,port1,portX"
frame_line " PTR: {✅ value_domain, ❌ the record was not found}"
frame_line ""
frame_line "File output:"
frame_line " - Results are saved to: scan_results_TIMESTAMP.txt"
frame_line " - Only alive hosts are saved to file"
frame_line " - Full output shown in console"
frame_line ""
frame_line "Colors:"
frame_line " ${GREEN}Green${NC} → Host is reachable / Port is open"
frame_line " ${RED}Red${NC} → Host is unreachable / Port is closed"
frame_line " ${YELLOW}Yellow${NC} → Warning / Timeout"
frame_line " ${BLUE}Blue${NC} → Information / Progress"
frame_line ""
frame_line "Exit codes:"
frame_line " 0 — Success"
frame_line " 1 — Validation error"
frame_line " 2 — Required tools missing"
frame_line ""
frame_line ""
line=$(printf '%*s' "$width" | tr ' ' '-')
printf '+%s+\n' "$line"
}
# Функция для сохранения информации о доступном хосте в файл
save_to_file() {
local ip="$1"
local ping_time="$2"
local tcp_ports="$3"
local udp_ports="$4"
local ptr_record="$5"
if [ "$SAVE_TO_FILE" = true ]; then
{
echo "=========================================="
echo "Host: $ip"
echo "=========================================="
echo "PING: ✅ is alive (${ping_time}ms)"
echo "TCP: $tcp_ports"
echo "UDP: $udp_ports"
echo "PTR: $ptr_record"
echo ""
} >> "$OUTPUT_FILE"
fi
}
# Функция проверки TCP порта (оптимизированная)
check_tcp_port() {
local host=$1
local port=$2
if timeout $TCP_TIMEOUT bash -c "echo >/dev/tcp/$host/$port" 2>/dev/null; then
echo -n "✅"
else
echo -n "❌"
fi
}
# Функция проверки UDP порта (оптимизированная)
check_udp_port() {
local host=$1
local port=$2
if command -v nmap &> /dev/null; then
# Параллельный nmap сканер
if timeout $UDP_TIMEOUT nmap -sU -p $port --host-timeout ${UDP_TIMEOUT}s $host 2>/dev/null | grep -q "$port/udp.*open"; then
echo -n "✅"
else
echo -n "❌"
fi
else
# Базовая UDP проверка
if timeout $UDP_TIMEOUT bash -c "echo >/dev/udp/$host/$port" 2>/dev/null; then
echo -n "✅"
else
echo -n "❌"
fi
fi
}
# Функция проверки PTR записи
check_ptr_record() {
local ip=$1
local ptr_result
if command -v dig &> /dev/null; then
ptr_result=$(dig +short +time=2 +tries=1 -x "$ip" 2>/dev/null | head -1)
elif command -v nslookup &> /dev/null; then
ptr_result=$(timeout 2 nslookup "$ip" 2>/dev/null | grep "name =" | awk '{print $4}' | sed 's/\.$//')
else
ptr_result=""
fi
if [ -n "$ptr_result" ]; then
echo -e "${GREEN}✅ $ptr_result${NC}"
else
echo -e "${RED}❌ the record was not found${NC}"
fi
}
# Функция для получения PTR записи без цветов (для файла)
get_ptr_record_plain() {
local ip=$1
local ptr_result
if command -v dig &> /dev/null; then
ptr_result=$(dig +short +time=2 +tries=1 -x "$ip" 2>/dev/null | head -1)
elif command -v nslookup &> /dev/null; then
ptr_result=$(timeout 2 nslookup "$ip" 2>/dev/null | grep "name =" | awk '{print $4}' | sed 's/\.$//')
else
ptr_result=""
fi
if [ -n "$ptr_result" ]; then
echo "✅ $ptr_result"
else
echo "❌ the record was not found"
fi
}
# Функция сканирования одного хоста
scan_host() {
local ip="$1"
local ping_time="$2"
local output="==========================================\n"
output+="Scanning $ip...\n"
output+="==========================================\n"
# Переменные для сохранения в файл
local tcp_results=""
local udp_results=""
local ptr_result_plain=""
if [ -n "$ping_time" ] && [ "$ping_time" != "null" ]; then
output+="PING: ${GREEN}✅ is alive (${ping_time}ms)${NC}\n"
# TCP проверки
output+="TCP: "
tcp_results=""
first_tcp=true
for port in $TCP_PORTS; do
if [ "$first_tcp" = true ]; then
first_tcp=false
else
output+=","
tcp_results+=","
fi
local tcp_result
tcp_result=$(check_tcp_port "$ip" "$port")
output+="${tcp_result}$port"
tcp_results+="${tcp_result}$port"
done
output+="\n"
# UDP проверки
output+="UDP: "
udp_results=""
first_udp=true
for port in $UDP_PORTS; do
if [ "$first_udp" = true ]; then
first_udp=false
else
output+=","
udp_results+=","
fi
local udp_result
udp_result=$(check_udp_port "$ip" "$port")
output+="${udp_result}$port"
udp_results+="${udp_result}$port"
done
output+="\n"
# PTR запись
output+="PTR: "
local ptr_result
ptr_result=$(check_ptr_record "$ip")
output+="$ptr_result\n"
# Получаем PTR запись без цветов для файла
ptr_result_plain=$(get_ptr_record_plain "$ip")
# Сохраняем в файл
save_to_file "$ip" "$ping_time" "$tcp_results" "$udp_results" "$ptr_result_plain"
else
output+="PING: ${RED}❌ is not reachable${NC}\n"
output+="TCP: ${YELLOW}⏭️ skipped (host unreachable)${NC}\n"
output+="UDP: ${YELLOW}⏭️ skipped (host unreachable)${NC}\n"
output+="PTR: ${YELLOW}⏭️ skipped (host unreachable)${NC}\n"
fi
output+="\n"
echo -e "$output"
}
# Функция для параллельного выполнения
run_parallel() {
local function_name="$1"
local max_jobs="$2"
local -a items=("${@:3}")
local -a pids=()
local running_jobs=0
local index=0
while [ $index -lt ${#items[@]} ] || [ ${#pids[@]} -gt 0 ]; do
# Запускаем новые задачи если есть свободные слоты
while [ $running_jobs -lt $max_jobs ] && [ $index -lt ${#items[@]} ]; do
$function_name "${items[$index]}" &
pids+=($!)
running_jobs=$((running_jobs + 1))
index=$((index + 1))
done
# Проверяем завершенные процессы
local new_pids=()
for pid in "${pids[@]}"; do
if kill -0 "$pid" 2>/dev/null; then
new_pids+=($pid)
else
running_jobs=$((running_jobs - 1))
wait "$pid" 2>/dev/null
fi
done
pids=("${new_pids[@]}")
sleep 0.1
done
}
# Функция массового ping
bulk_ping() {
local subnet="$1"
local limit="$2"
local -n result_ref="$3"
local count=0
local total=$limit
echo -e "${BLUE}Pinging $total hosts in $subnet*...${NC}" >&2
# Параллельный ping
for ((i=0; i<limit; i++)); do
local ip="${subnet}${i}"
{
# Пробуем ping с таймаутом
if ping -c 1 -W $PING_TIMEOUT "$ip" &>/dev/null; then
# Если пинг успешен, получаем время
local ping_result
ping_result=$(ping -c 1 -W $PING_TIMEOUT "$ip" 2>/dev/null | grep "time=" | head -1)
if [ -n "$ping_result" ]; then
local ping_time
ping_time=$(echo "$ping_result" | grep -o "time=[0-9.]*" | cut -d= -f2)
result_ref["$ip"]="$ping_time"
echo -e "${GREEN}✓ Alive: $ip (${ping_time}ms)${NC}" >&2
else
result_ref["$ip"]="1"
echo -e "${GREEN}✓ Alive: $ip (1ms)${NC}" >&2
fi
fi
} &
count=$((count + 1))
# Ограничиваем количество параллельных процессов
if [[ $(jobs -r -p | wc -l) -ge $THREADS ]]; then
wait -n
fi
done
# Ждем завершения всех процессов
wait
# Альтернативный метод с fping (если нужен)
if command -v fping &> /dev/null && [ ${#result_ref[@]} -eq 0 ]; then
echo -e "${YELLOW}Trying fping method...${NC}" >&2
local ping_list=()
for ((i=0; i<limit; i++)); do
ping_list+=("${subnet}${i}")
done
# Используем fping в простом режиме
for ip in "${ping_list[@]}"; do
if fping -c 1 -t 1000 "$ip" &>/dev/null; then
result_ref["$ip"]="1"
echo -e "${GREEN}✓ Alive (fping): $ip${NC}" >&2
fi
done
fi
}
# Вспомогательная функция для параллельного сканирования
scan_single_host() {
local task="$1"
local ip="${task%:*}"
local ping_time="${task#*:}"
scan_host "$ip" "$ping_time"
}
# Функция для чтения подсетей из файла
read_subnets_from_file() {
local filename="$1"
local -n subnets_ref="$2"
if [ ! -f "$filename" ]; then
echo "Error: File '$filename' not found."
exit 1
fi
while IFS= read -r line || [ -n "$line" ]; do
# Пропускаем пустые строки и комментарии
line=$(echo "$line" | sed 's/#.*//')
if [ -n "$line" ]; then
# Проверяем формат подсети
if [[ "$line" =~ ^([1-9][0-9]{0,2}|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.([1-9][0-9]{0,2}|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.([1-9][0-9]{0,2}|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.$ ]]; then
subnets_ref+=("$line")
else
echo "Warning: Invalid subnet format '$line' in file, skipping."
fi
fi
done < "$filename"
}
# Функция валидации подсети
validate_subnet() {
local subnet="$1"
if ! [[ "$subnet" =~ ^([1-9][0-9]{0,2}|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.([1-9][0-9]{0,2}|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.([1-9][0-9]{0,2}|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.$ ]]; then
echo "Error: Invalid subnet '$subnet'. Must be in format NNN.NNN.NNN. where each NNN is 1–255 (no leading zeros)."
return 1
fi
return 0
}
# Основная функция сканирования
main_scan() {
local subnets=("$@")
local total_hosts=0
# Подсчитываем общее количество хостов
for subnet in "${subnets[@]}"; do
total_hosts=$((total_hosts + range))
done
echo -e "\n${BLUE}Starting parallel scan of ${#subnets[@]} subnets, $total_hosts total hosts using $THREADS threads...${NC}"
echo -e "${BLUE}Subnets: ${subnets[*]}${NC}"
echo -e "${BLUE}Range per subnet: 0-$((range-1))${NC}"
echo -e "${BLUE}Ports - TCP: $TCP_PORTS${NC}"
echo -e "${BLUE}Ports - UDP: $UDP_PORTS${NC}"
# Инициализация файла результатов
if [ "$SAVE_TO_FILE" = true ]; then
echo -e "${BLUE}Results will be saved to: $OUTPUT_FILE${NC}"
{
echo "Network Scan Results"
echo "===================="
echo "Date: $(date)"
echo "Subnets: ${subnets[*]}"
echo "Range per subnet: 0-$((range-1))"
echo "Total subnets: ${#subnets[@]}"
echo "Total hosts scanned: $total_hosts"
echo ""
echo "ALIVE HOSTS:"
echo "============"
echo ""
} > "$OUTPUT_FILE"
fi
echo ""
start_time=$(date +%s)
local total_alive_hosts=0
# Сканируем каждую подсеть
for subnet in "${subnets[@]}"; do
echo -e "${BLUE}=========================================${NC}"
echo -e "${BLUE}Scanning subnet: $subnet${NC}"
echo -e "${BLUE}=========================================${NC}"
# Этап 1: Массовое обнаружение хостов
echo -e "${BLUE}Stage 1: Host discovery...${NC}"
declare -A alive_hosts
bulk_ping "$subnet" "$range" alive_hosts
echo -e "${GREEN}Found ${#alive_hosts[@]} alive hosts in $subnet*${NC}"
total_alive_hosts=$((total_alive_hosts + ${#alive_hosts[@]}))
# Этап 2: Параллельное сканирование доступных хостов
if [ ${#alive_hosts[@]} -gt 0 ]; then
echo -e "${BLUE}Stage 2: Parallel port scanning...${NC}"
# Создаем массив для задач сканирования
scan_tasks=()
for ((i=0; i<range; i++)); do
ip="${subnet}${i}"
if [ -n "${alive_hosts[$ip]}" ]; then
scan_tasks+=("$ip:${alive_hosts[$ip]}")
else
scan_tasks+=("$ip:null")
fi
done
# Запускаем параллельное сканирование
run_parallel scan_single_host "$THREADS" "${scan_tasks[@]}"
else
echo -e "${YELLOW}No alive hosts found in $subnet*, skipping port scanning.${NC}"
fi
echo ""
done
end_time=$(date +%s)
duration=$((end_time - start_time))
# Добавляем итоги в файл
if [ "$SAVE_TO_FILE" = true ]; then
{
echo ""
echo "SCAN SUMMARY:"
echo "============="
echo "Scan completed: $(date)"
echo "Duration: ${duration} seconds"
echo "Total subnets: ${#subnets[@]}"
echo "Total hosts scanned: $total_hosts"
echo "Total alive hosts found: $total_alive_hosts"
} >> "$OUTPUT_FILE"
echo -e "${GREEN}Results saved to: $OUTPUT_FILE${NC}"
fi
echo -e "${GREEN}Scan completed in ${duration} seconds${NC}"
echo -e "${GREEN}Scanned ${#subnets[@]} subnets, $total_hosts hosts, found $total_alive_hosts alive hosts${NC}"
}
# Парсинг аргументов
parse_arguments() {
local subnets=()
local range_set=false
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
show_help
exit 0
;;
--subnets)
shift
while [[ $# -gt 0 ]] && [[ $1 != --* ]]; do
if validate_subnet "$1"; then
subnets+=("$1")
else
exit 1
fi
shift
done
;;
--file)
if [ $# -lt 2 ]; then
echo "Error: --file requires a filename argument."
exit 1
fi
shift
read_subnets_from_file "$1" subnets
shift
;;
--range)
if [ $# -lt 2 ]; then
echo "Error: --range requires a numeric argument."
exit 1
fi
shift
if [[ "$1" =~ ^[0-9]+$ ]] && [ "$1" -ge 0 ] && [ "$1" -le 255 ]; then
range="$1"
range_set=true
else
echo "Error: <range> must be between 0 and 255 (inclusive)."
exit 1
fi
shift
;;
*)
# Одиночный режим (backward compatibility)
if [ ${#subnets[@]} -eq 0 ] && ! $range_set; then
if validate_subnet "$1"; then
subnets+=("$1")
else
exit 1
fi
shift
if [[ $# -gt 0 ]] && [[ "$1" =~ ^[0-9]+$ ]]; then
if [ "$1" -ge 0 ] && [ "$1" -le 255 ]; then
range="$1"
range_set=true
else
echo "Error: <range> must be between 0 and 255 (inclusive)."
exit 1
fi
shift
fi
else
# Последний аргумент - threads
if [[ "$1" =~ ^[0-9]+$ ]] && [ "$1" -ge 1 ] && [ "$1" -le 50 ]; then
THREADS="$1"
else
echo "Error: <threads> must be between 1 and 50."
exit 1
fi
shift
fi
;;
esac
done
# Проверка обязательных параметров
if [ ${#subnets[@]} -eq 0 ]; then
echo "Error: No subnets specified."
echo "Use '$0 --help' for usage information."
exit 1
fi
if ! $range_set; then
echo "Error: Range not specified."
echo "Use --range to specify the number of IPs to scan per subnet."
exit 1
fi
# Запускаем сканирование
main_scan "${subnets[@]}"
}
# 1. Проверка утилит
echo -e "${BLUE}Checking required utilities...${NC}"
if ! command -v ping &> /dev/null; then
echo "Error: 'ping' command not found. Please install it."
exit 2
fi
if command -v fping &> /dev/null; then
echo -e "${GREEN}✓ fping available (fallback method)${NC}"
else
echo -e "${YELLOW}⚠ fping not found, using standard ping${NC}"
fi
if ! command -v nmap &> /dev/null; then
echo -e "${YELLOW}⚠ 'nmap' not found. UDP port checking will be limited.${NC}"
fi
if ! command -v dig &> /dev/null && ! command -v nslookup &> /dev/null; then
echo -e "${YELLOW}⚠ Neither 'dig' nor 'nslookup' found. PTR records cannot be checked.${NC}"
fi
# 2. Парсинг аргументов и запуск
parse_arguments "$@"
exit 0
#!/bin/bash
# Цвета
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
PURPLE='\033[0;35m'
CYAN='\033[0;36m'
NC='\033[0m'
# Порты для проверки (можно изменить)
TCP_PORTS="22 80 443 53 21 25 110 143 993 995 3389"
UDP_PORTS="53 67 68 123 161 500 4500"
# Настройки многопоточности
THREADS=10
PING_TIMEOUT=2
TCP_TIMEOUT=2
UDP_TIMEOUT=3
# Переменные для сохранения в файл
OUTPUT_FILE="scan_results_$(date +%Y%m%d_%H%M%S).txt"
SAVE_TO_FILE=true
# Режим вывода: detailed (подробный) или progress (прогресс-бар)
OUTPUT_MODE="detailed"
show_help() {
local width=120
local inner_width=$((width - 4))
local indent=" "
frame_line() {
local text="$1"
if [ -z "$text" ]; then
printf "%${inner_width}s\n" ""
else
local padded="${indent}${text}"
printf "%-${inner_width}s\n" "${padded:0:$inner_width}"
fi
}
line=$(printf '%*s' "$width" | tr ' ' '-')
printf '+%s+\n' "$line"
frame_line ""
frame_line "Usage: $0 <subnet> <range> [threads]"
frame_line " $0 --subnets <subnet1> <subnet2> ... --range <range> [threads]"
frame_line " $0 --file <filename> --range <range> [threads]"
frame_line ""
frame_line "Description:"
frame_line " Fast parallel scanning of IP ranges with ICMP, TCP/UDP ports and PTR checks"
frame_line ""
frame_line "Arguments:"
frame_line " Single subnet mode:"
frame_line " <subnet> Base subnet in format NNN.NNN.NNN. (each NNN: 1–255, no leading zeros)"
frame_line " <range> Number of IPs to scan (0–255)"
frame_line " [threads] Number of parallel threads (default: $THREADS, max: 50)"
frame_line ""
frame_line " Multiple subnets mode:"
frame_line " --subnets List of subnets to scan"
frame_line " --file File containing list of subnets (one per line)"
frame_line " --range Number of IPs to scan for each subnet (0–255)"
frame_line " [threads] Number of parallel threads"
frame_line ""
frame_line "Output modes:"
frame_line " --mode detailed Detailed output (default)"
frame_line " --mode progress Progress bar with compact results"
frame_line ""
frame_line "Options:"
frame_line " -h, --help Display this help message and exit"
frame_line ""
frame_line "Examples:"
frame_line " Single subnet:"
frame_line " $0 192.168.1. 5"
frame_line " $0 10.0.0. 10 20"
frame_line ""
frame_line " Multiple subnets:"
frame_line " $0 --subnets 192.168.1. 10.0.0. 172.16.1. --range 10"
frame_line " $0 --file subnets.txt --range 50 25 --mode progress"
frame_line " $0 --help"
frame_line ""
frame_line "File format for --file:"
frame_line " 192.168.1."
frame_line " 10.0.0."
frame_line " 172.16.1."
frame_line ""
frame_line "Performance features:"
frame_line " - Parallel scanning with configurable threads"
frame_line " - Bulk ICMP ping for initial host discovery"
frame_line " - Sequential port scanning only for alive hosts"
frame_line " - Optimized timeouts for each check type"
frame_line ""
frame_line "Output format:"
frame_line " PING: {✅ is alive (value_ms), ❌ is not reachable}"
frame_line " TCP: {✅ open, ❌ close} port0,port1,portX"
frame_line " UDP: {✅ open, ❌ close} port0,port1,portX"
frame_line " PTR: {✅ value_domain, ❌ the record was not found}"
frame_line ""
frame_line "File output:"
frame_line " - Results are saved to: scan_results_TIMESTAMP.txt"
frame_line " - Only alive hosts are saved to file"
frame_line " - Full output shown in console"
frame_line ""
frame_line "Colors:"
frame_line " ${GREEN}Green${NC} → Host is reachable / Port is open"
frame_line " ${RED}Red${NC} → Host is unreachable / Port is closed"
frame_line " ${YELLOW}Yellow${NC} → Warning / Timeout"
frame_line " ${BLUE}Blue${NC} → Information / Progress"
frame_line ""
frame_line "Exit codes:"
frame_line " 0 — Success"
frame_line " 1 — Validation error"
frame_line " 2 — Required tools missing"
frame_line ""
frame_line ""
line=$(printf '%*s' "$width" | tr ' ' '-')
printf '+%s+\n' "$line"
}
# Функция для отображения прогресс-бара
show_progress() {
local current="$1"
local total="$2"
local width=50
local percentage=$((current * 100 / total))
local completed=$((current * width / total))
local remaining=$((width - completed))
printf "\r${CYAN}Progress: [${NC}"
printf "%*s" $completed | tr ' ' '█'
printf "%*s" $remaining | tr ' ' '░'
printf "${CYAN}] ${percentage}%% (%d/%d)${NC}" $current $total
}
# Функция для компактного вывода хоста
show_compact_host() {
local ip="$1"
local ping_time="$2"
local tcp_ports="$3"
local udp_ports="$4"
local ptr_record="$5"
if [ -n "$ping_time" ] && [ "$ping_time" != "null" ]; then
# Считаем открытые TCP порты
local open_tcp=$(echo "$tcp_ports" | grep -o '✅' | wc -l)
local total_tcp=$(echo "$tcp_ports" | grep -o '❌\|✅' | wc -l)
# Считаем открытые UDP порты
local open_udp=$(echo "$udp_ports" | grep -o '✅' | wc -l)
local total_udp=$(echo "$udp_ports" | grep -o '❌\|✅' | wc -l)
# Извлекаем PTR запись
local ptr_domain=""
if [[ "$ptr_record" =~ ✅\ (.*) ]]; then
ptr_domain="${BASH_REMATCH[1]}"
fi
printf "${GREEN}✓ ${ip}${NC} (${ping_time}ms) | "
printf "TCP: ${open_tcp}/${total_tcp} | "
printf "UDP: ${open_udp}/${total_udp}"
if [ -n "$ptr_domain" ]; then
printf " | PTR: ${ptr_domain}"
fi
printf "\n"
fi
}
# Функция для сохранения информации о доступном хосте в файл
save_to_file() {
local ip="$1"
local ping_time="$2"
local tcp_ports="$3"
local udp_ports="$4"
local ptr_record="$5"
if [ "$SAVE_TO_FILE" = true ]; then
{
echo "=========================================="
echo "Host: $ip"
echo "=========================================="
echo "PING: ✅ is alive (${ping_time}ms)"
echo "TCP: $tcp_ports"
echo "UDP: $udp_ports"
echo "PTR: $ptr_record"
echo ""
} >> "$OUTPUT_FILE"
fi
}
# Функция проверки TCP порта (оптимизированная)
check_tcp_port() {
local host=$1
local port=$2
if timeout $TCP_TIMEOUT bash -c "echo >/dev/tcp/$host/$port" 2>/dev/null; then
echo -n "✅"
else
echo -n "❌"
fi
}
# Функция проверки UDP порта (оптимизированная)
check_udp_port() {
local host=$1
local port=$2
if command -v nmap &> /dev/null; then
# Параллельный nmap сканер
if timeout $UDP_TIMEOUT nmap -sU -p $port --host-timeout ${UDP_TIMEOUT}s $host 2>/dev/null | grep -q "$port/udp.*open"; then
echo -n "✅"
else
echo -n "❌"
fi
else
# Базовая UDP проверка
if timeout $UDP_TIMEOUT bash -c "echo >/dev/udp/$host/$port" 2>/dev/null; then
echo -n "✅"
else
echo -n "❌"
fi
fi
}
# Функция проверки PTR записи
check_ptr_record() {
local ip=$1
local ptr_result
if command -v dig &> /dev/null; then
ptr_result=$(dig +short +time=2 +tries=1 -x "$ip" 2>/dev/null | head -1)
elif command -v nslookup &> /dev/null; then
ptr_result=$(timeout 2 nslookup "$ip" 2>/dev/null | grep "name =" | awk '{print $4}' | sed 's/\.$//')
else
ptr_result=""
fi
if [ -n "$ptr_result" ]; then
echo -e "${GREEN}✅ $ptr_result${NC}"
else
echo -e "${RED}❌ the record was not found${NC}"
fi
}
# Функция для получения PTR записи без цветов (для файла)
get_ptr_record_plain() {
local ip=$1
local ptr_result
if command -v dig &> /dev/null; then
ptr_result=$(dig +short +time=2 +tries=1 -x "$ip" 2>/dev/null | head -1)
elif command -v nslookup &> /dev/null; then
ptr_result=$(timeout 2 nslookup "$ip" 2>/dev/null | grep "name =" | awk '{print $4}' | sed 's/\.$//')
else
ptr_result=""
fi
if [ -n "$ptr_result" ]; then
echo "✅ $ptr_result"
else
echo "❌ the record was not found"
fi
}
# Функция сканирования одного хоста
scan_host() {
local ip="$1"
local ping_time="$2"
# Переменные для сохранения в файл
local tcp_results=""
local udp_results=""
local ptr_result_plain=""
if [ -n "$ping_time" ] && [ "$ping_time" != "null" ]; then
if [ "$OUTPUT_MODE" = "detailed" ]; then
local output="==========================================\n"
output+="Scanning $ip...\n"
output+="==========================================\n"
output+="PING: ${GREEN}✅ is alive (${ping_time}ms)${NC}\n"
fi
# TCP проверки
tcp_results=""
first_tcp=true
for port in $TCP_PORTS; do
if [ "$first_tcp" = true ]; then
first_tcp=false
else
tcp_results+=","
fi
local tcp_result
tcp_result=$(check_tcp_port "$ip" "$port")
tcp_results+="${tcp_result}$port"
if [ "$OUTPUT_MODE" = "detailed" ]; then
if [ "$first_tcp" = false ]; then
output+=","
fi
output+="${tcp_result}$port"
fi
done
if [ "$OUTPUT_MODE" = "detailed" ]; then
output+="\n"
# UDP проверки
output+="UDP: "
fi
udp_results=""
first_udp=true
for port in $UDP_PORTS; do
if [ "$first_udp" = true ]; then
first_udp=false
else
udp_results+=","
fi
local udp_result
udp_result=$(check_udp_port "$ip" "$port")
udp_results+="${udp_result}$port"
if [ "$OUTPUT_MODE" = "detailed" ]; then
if [ "$first_udp" = false ]; then
output+=","
fi
output+="${udp_result}$port"
fi
done
if [ "$OUTPUT_MODE" = "detailed" ]; then
output+="\n"
# PTR запись
output+="PTR: "
local ptr_result
ptr_result=$(check_ptr_record "$ip")
output+="$ptr_result\n"
else
# Компактный режим - просто получаем PTR
ptr_result_plain=$(get_ptr_record_plain "$ip")
fi
# Получаем PTR запись без цветов для файла
if [ -z "$ptr_result_plain" ]; then
ptr_result_plain=$(get_ptr_record_plain "$ip")
fi
# Сохраняем в файл
save_to_file "$ip" "$ping_time" "$tcp_results" "$udp_results" "$ptr_result_plain"
# Вывод в зависимости от режима
if [ "$OUTPUT_MODE" = "detailed" ]; then
output+="\n"
echo -e "$output"
else
show_compact_host "$ip" "$ping_time" "$tcp_results" "$udp_results" "$ptr_result_plain"
fi
else
if [ "$OUTPUT_MODE" = "detailed" ]; then
local output="==========================================\n"
output+="Scanning $ip...\n"
output+="==========================================\n"
output+="PING: ${RED}❌ is not reachable${NC}\n"
output+="TCP: ${YELLOW}⏭️ skipped (host unreachable)${NC}\n"
output+="UDP: ${YELLOW}⏭️ skipped (host unreachable)${NC}\n"
output+="PTR: ${YELLOW}⏭️ skipped (host unreachable)${NC}\n"
output+="\n"
echo -e "$output"
fi
# В компактном режиме недоступные хосты не показываем
fi
}
# Функция для параллельного выполнения
run_parallel() {
local function_name="$1"
local max_jobs="$2"
local -a items=("${@:3}")
local -a pids=()
local running_jobs=0
local index=0
local completed=0
local total=${#items[@]}
# Инициализация прогресс-бара
if [ "$OUTPUT_MODE" = "progress" ]; then
echo -e "\n${CYAN}Scanning hosts...${NC}"
show_progress 0 $total
fi
while [ $index -lt ${#items[@]} ] || [ ${#pids[@]} -gt 0 ]; do
# Запускаем новые задачи если есть свободные слоты
while [ $running_jobs -lt $max_jobs ] && [ $index -lt ${#items[@]} ]; do
$function_name "${items[$index]}" &
pids+=($!)
running_jobs=$((running_jobs + 1))
index=$((index + 1))
done
# Проверяем завершенные процессы
local new_pids=()
for pid in "${pids[@]}"; do
if kill -0 "$pid" 2>/dev/null; then
new_pids+=($pid)
else
running_jobs=$((running_jobs - 1))
wait "$pid" 2>/dev/null
completed=$((completed + 1))
# Обновляем прогресс-бар
if [ "$OUTPUT_MODE" = "progress" ]; then
show_progress $completed $total
fi
fi
done
pids=("${new_pids[@]}")
sleep 0.1
done
# Завершаем прогресс-бар
if [ "$OUTPUT_MODE" = "progress" ]; then
show_progress $total $total
echo -e "\n"
fi
}
# Функция массового ping
bulk_ping() {
local subnet="$1"
local limit="$2"
local -n result_ref="$3"
local count=0
local total=$limit
if [ "$OUTPUT_MODE" = "detailed" ]; then
echo -e "${BLUE}Pinging $total hosts in $subnet*...${NC}" >&2
else
echo -e "${CYAN}Discovering hosts in $subnet*...${NC}" >&2
fi
# Параллельный ping
for ((i=0; i<limit; i++)); do
local ip="${subnet}${i}"
{
# Пробуем ping с таймаутом
if ping -c 1 -W $PING_TIMEOUT "$ip" &>/dev/null; then
# Если пинг успешен, получаем время
local ping_result
ping_result=$(ping -c 1 -W $PING_TIMEOUT "$ip" 2>/dev/null | grep "time=" | head -1)
if [ -n "$ping_result" ]; then
local ping_time
ping_time=$(echo "$ping_result" | grep -o "time=[0-9.]*" | cut -d= -f2)
result_ref["$ip"]="$ping_time"
if [ "$OUTPUT_MODE" = "detailed" ]; then
echo -e "${GREEN}✓ Alive: $ip (${ping_time}ms)${NC}" >&2
fi
else
result_ref["$ip"]="1"
if [ "$OUTPUT_MODE" = "detailed" ]; then
echo -e "${GREEN}✓ Alive: $ip (1ms)${NC}" >&2
fi
fi
fi
} &
count=$((count + 1))
# Ограничиваем количество параллельных процессов
if [[ $(jobs -r -p | wc -l) -ge $THREADS ]]; then
wait -n
fi
done
# Ждем завершения всех процессов
wait
# Альтернативный метод с fping (если нужен)
if command -v fping &> /dev/null && [ ${#result_ref[@]} -eq 0 ]; then
if [ "$OUTPUT_MODE" = "detailed" ]; then
echo -e "${YELLOW}Trying fping method...${NC}" >&2
fi
local ping_list=()
for ((i=0; i<limit; i++)); do
ping_list+=("${subnet}${i}")
done
# Используем fping в простом режиме
for ip in "${ping_list[@]}"; do
if fping -c 1 -t 1000 "$ip" &>/dev/null; then
result_ref["$ip"]="1"
if [ "$OUTPUT_MODE" = "detailed" ]; then
echo -e "${GREEN}✓ Alive (fping): $ip${NC}" >&2
fi
fi
done
fi
}
# Вспомогательная функция для параллельного сканирования
scan_single_host() {
local task="$1"
local ip="${task%:*}"
local ping_time="${task#*:}"
scan_host "$ip" "$ping_time"
}
# Функция для чтения подсетей из файла
read_subnets_from_file() {
local filename="$1"
local -n subnets_ref="$2"
if [ ! -f "$filename" ]; then
echo "Error: File '$filename' not found."
exit 1
fi
while IFS= read -r line || [ -n "$line" ]; do
# Пропускаем пустые строки и комментарии
line=$(echo "$line" | sed 's/#.*//')
if [ -n "$line" ]; then
# Проверяем формат подсети
if [[ "$line" =~ ^([1-9][0-9]{0,2}|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.([1-9][0-9]{0,2}|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.([1-9][0-9]{0,2}|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.$ ]]; then
subnets_ref+=("$line")
else
echo "Warning: Invalid subnet format '$line' in file, skipping."
fi
fi
done < "$filename"
}
# Функция валидации подсети
validate_subnet() {
local subnet="$1"
if ! [[ "$subnet" =~ ^([1-9][0-9]{0,2}|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.([1-9][0-9]{0,2}|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.([1-9][0-9]{0,2}|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.$ ]]; then
echo "Error: Invalid subnet '$subnet'. Must be in format NNN.NNN.NNN. where each NNN is 1–255 (no leading zeros)."
return 1
fi
return 0
}
# Основная функция сканирования
main_scan() {
local subnets=("$@")
local total_hosts=0
# Подсчитываем общее количество хостов
for subnet in "${subnets[@]}"; do
total_hosts=$((total_hosts + range))
done
if [ "$OUTPUT_MODE" = "detailed" ]; then
echo -e "\n${BLUE}Starting parallel scan of ${#subnets[@]} subnets, $total_hosts total hosts using $THREADS threads...${NC}"
echo -e "${BLUE}Subnets: ${subnets[*]}${NC}"
echo -e "${BLUE}Range per subnet: 0-$((range-1))${NC}"
echo -e "${BLUE}Ports - TCP: $TCP_PORTS${NC}"
echo -e "${BLUE}Ports - UDP: $UDP_PORTS${NC}"
else
echo -e "\n${CYAN}Scanning ${#subnets[@]} subnets, $total_hosts hosts, $THREADS threads, mode: $OUTPUT_MODE${NC}"
fi
# Инициализация файла результатов
if [ "$SAVE_TO_FILE" = true ]; then
echo -e "${BLUE}Results will be saved to: $OUTPUT_FILE${NC}"
{
echo "Network Scan Results"
echo "===================="
echo "Date: $(date)"
echo "Subnets: ${subnets[*]}"
echo "Range per subnet: 0-$((range-1))"
echo "Total subnets: ${#subnets[@]}"
echo "Total hosts scanned: $total_hosts"
echo "Output mode: $OUTPUT_MODE"
echo ""
echo "ALIVE HOSTS:"
echo "============"
echo ""
} > "$OUTPUT_FILE"
fi
if [ "$OUTPUT_MODE" = "detailed" ]; then
echo ""
fi
start_time=$(date +%s)
local total_alive_hosts=0
# Сканируем каждую подсеть
for subnet in "${subnets[@]}"; do
if [ "$OUTPUT_MODE" = "detailed" ]; then
echo -e "${BLUE}=========================================${NC}"
echo -e "${BLUE}Scanning subnet: $subnet${NC}"
echo -e "${BLUE}=========================================${NC}"
else
echo -e "${CYAN}Subnet: $subnet*${NC}"
fi
# Этап 1: Массовое обнаружение хостов
if [ "$OUTPUT_MODE" = "detailed" ]; then
echo -e "${BLUE}Stage 1: Host discovery...${NC}"
fi
declare -A alive_hosts
bulk_ping "$subnet" "$range" alive_hosts
if [ "$OUTPUT_MODE" = "detailed" ]; then
echo -e "${GREEN}Found ${#alive_hosts[@]} alive hosts in $subnet*${NC}"
fi
total_alive_hosts=$((total_alive_hosts + ${#alive_hosts[@]}))
# Этап 2: Параллельное сканирование доступных хостов
if [ ${#alive_hosts[@]} -gt 0 ]; then
if [ "$OUTPUT_MODE" = "detailed" ]; then
echo -e "${BLUE}Stage 2: Parallel port scanning...${NC}"
fi
# Создаем массив для задач сканирования
scan_tasks=()
for ((i=0; i<range; i++)); do
ip="${subnet}${i}"
if [ -n "${alive_hosts[$ip]}" ]; then
scan_tasks+=("$ip:${alive_hosts[$ip]}")
else
scan_tasks+=("$ip:null")
fi
done
# Запускаем параллельное сканирование
run_parallel scan_single_host "$THREADS" "${scan_tasks[@]}"
else
if [ "$OUTPUT_MODE" = "detailed" ]; then
echo -e "${YELLOW}No alive hosts found in $subnet*, skipping port scanning.${NC}"
fi
fi
if [ "$OUTPUT_MODE" = "detailed" ]; then
echo ""
fi
done
end_time=$(date +%s)
duration=$((end_time - start_time))
# Добавляем итоги в файл
if [ "$SAVE_TO_FILE" = true ]; then
{
echo ""
echo "SCAN SUMMARY:"
echo "============="
echo "Scan completed: $(date)"
echo "Duration: ${duration} seconds"
echo "Total subnets: ${#subnets[@]}"
echo "Total hosts scanned: $total_hosts"
echo "Total alive hosts found: $total_alive_hosts"
} >> "$OUTPUT_FILE"
echo -e "${GREEN}Results saved to: $OUTPUT_FILE${NC}"
fi
echo -e "${GREEN}Scan completed in ${duration} seconds${NC}"
echo -e "${GREEN}Scanned ${#subnets[@]} subnets, $total_hosts hosts, found $total_alive_hosts alive hosts${NC}"
}
# Парсинг аргументов
parse_arguments() {
local subnets=()
local range_set=false
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
show_help
exit 0
;;
--subnets)
shift
while [[ $# -gt 0 ]] && [[ $1 != --* ]]; do
if validate_subnet "$1"; then
subnets+=("$1")
else
exit 1
fi
shift
done
;;
--file)
if [ $# -lt 2 ]; then
echo "Error: --file requires a filename argument."
exit 1
fi
shift
read_subnets_from_file "$1" subnets
shift
;;
--range)
if [ $# -lt 2 ]; then
echo "Error: --range requires a numeric argument."
exit 1
fi
shift
if [[ "$1" =~ ^[0-9]+$ ]] && [ "$1" -ge 0 ] && [ "$1" -le 255 ]; then
range="$1"
range_set=true
else
echo "Error: <range> must be between 0 and 255 (inclusive)."
exit 1
fi
shift
;;
--mode)
if [ $# -lt 2 ]; then
echo "Error: --mode requires an argument (detailed or progress)."
exit 1
fi
shift
case $1 in
detailed|progress)
OUTPUT_MODE="$1"
;;
*)
echo "Error: --mode must be 'detailed' or 'progress'."
exit 1
;;
esac
shift
;;
*)
# Одиночный режим (backward compatibility)
if [ ${#subnets[@]} -eq 0 ] && ! $range_set; then
if validate_subnet "$1"; then
subnets+=("$1")
else
exit 1
fi
shift
if [[ $# -gt 0 ]] && [[ "$1" =~ ^[0-9]+$ ]]; then
if [ "$1" -ge 0 ] && [ "$1" -le 255 ]; then
range="$1"
range_set=true
else
echo "Error: <range> must be between 0 and 255 (inclusive)."
exit 1
fi
shift
fi
else
# Последний аргумент - threads
if [[ "$1" =~ ^[0-9]+$ ]] && [ "$1" -ge 1 ] && [ "$1" -le 50 ]; then
THREADS="$1"
else
echo "Error: <threads> must be between 1 and 50."
exit 1
fi
shift
fi
;;
esac
done
# Проверка обязательных параметров
if [ ${#subnets[@]} -eq 0 ]; then
echo "Error: No subnets specified."
echo "Use '$0 --help' for usage information."
exit 1
fi
if ! $range_set; then
echo "Error: Range not specified."
echo "Use --range to specify the number of IPs to scan per subnet."
exit 1
fi
# Запускаем сканирование
main_scan "${subnets[@]}"
}
# 1. Проверка утилит
echo -e "${BLUE}Checking required utilities...${NC}"
if ! command -v ping &> /dev/null; then
echo "Error: 'ping' command not found. Please install it."
exit 2
fi
if command -v fping &> /dev/null; then
echo -e "${GREEN}✓ fping available (fallback method)${NC}"
else
echo -e "${YELLOW}⚠ fping not found, using standard ping${NC}"
fi
if ! command -v nmap &> /dev/null; then
echo -e "${YELLOW}⚠ 'nmap' not found. UDP port checking will be limited.${NC}"
fi
if ! command -v dig &> /dev/null && ! command -v nslookup &> /dev/null; then
echo -e "${YELLOW}⚠ Neither 'dig' nor 'nslookup' found. PTR records cannot be checked.${NC}"
fi
# 2. Парсинг аргументов и запуск
parse_arguments "$@"
exit 0
#!/bin/bash
# Цвета
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
# Порты для проверки (можно изменить)
TCP_PORTS="22 80 443 53 21 25 110 143 993 995 3389"
UDP_PORTS="53 67 68 123 161 500 4500"
# Настройки многопоточности
THREADS=10
PING_TIMEOUT=2
TCP_TIMEOUT=2
UDP_TIMEOUT=3
# Переменные для сохранения в файл
OUTPUT_FILE="scan_results_$(date +%Y%m%d_%H%M%S).txt"
SAVE_TO_FILE=true
show_help() {
local width=120
local inner_width=$((width - 4))
local indent=" "
frame_line() {
local text="$1"
if [ -z "$text" ]; then
printf "%${inner_width}s\n" ""
else
local padded="${indent}${text}"
printf "%-${inner_width}s\n" "${padded:0:$inner_width}"
fi
}
line=$(printf '%*s' "$width" | tr ' ' '-')
printf '+%s+\n' "$line"
frame_line ""
frame_line "Usage: $0 <subnet> <range> [threads]"
frame_line ""
frame_line "Description:"
frame_line " Fast parallel scanning of IP range with ICMP, TCP/UDP ports and PTR checks"
frame_line ""
frame_line "Arguments:"
frame_line " <subnet> Base subnet in format NNN.NNN.NNN. (each NNN: 1–255, no leading zeros)"
frame_line " <range> Number of IPs to scan (0–255)"
frame_line " [threads] Number of parallel threads (default: $THREADS, max: 50)"
frame_line ""
frame_line "Options:"
frame_line " -h, --help Display this help message and exit"
frame_line ""
frame_line "Examples:"
frame_line " $0 192.168.1. 5"
frame_line " $0 10.0.0. 10 20"
frame_line " $0 --help"
frame_line ""
frame_line "Performance features:"
frame_line " - Parallel scanning with configurable threads"
frame_line " - Bulk ICMP ping for initial host discovery"
frame_line " - Sequential port scanning only for alive hosts"
frame_line " - Optimized timeouts for each check type"
frame_line ""
frame_line "Output format:"
frame_line " PING: {✅ is alive (value_ms), ❌ is not reachable}"
frame_line " TCP: {✅ open, ❌ close} port0,port1,portX"
frame_line " UDP: {✅ open, ❌ close} port0,port1,portX"
frame_line " PTR: {✅ value_domain, ❌ the record was not found}"
frame_line ""
frame_line "File output:"
frame_line " - Results are saved to: scan_results_TIMESTAMP.txt"
frame_line " - Only alive hosts are saved to file"
frame_line " - Full output shown in console"
frame_line ""
frame_line "Colors:"
frame_line " ${GREEN}Green${NC} → Host is reachable / Port is open"
frame_line " ${RED}Red${NC} → Host is unreachable / Port is closed"
frame_line " ${YELLOW}Yellow${NC} → Warning / Timeout"
frame_line " ${BLUE}Blue${NC} → Information / Progress"
frame_line ""
frame_line "Exit codes:"
frame_line " 0 — Success"
frame_line " 1 — Validation error"
frame_line " 2 — Required tools missing"
frame_line ""
frame_line ""
line=$(printf '%*s' "$width" | tr ' ' '-')
printf '+%s+\n' "$line"
}
# Функция для сохранения информации о доступном хосте в файл
save_to_file() {
local ip="$1"
local ping_time="$2"
local tcp_ports="$3"
local udp_ports="$4"
local ptr_record="$5"
if [ "$SAVE_TO_FILE" = true ]; then
{
echo "=========================================="
echo "Host: $ip"
echo "=========================================="
echo "PING: ✅ is alive (${ping_time}ms)"
echo "TCP: $tcp_ports"
echo "UDP: $udp_ports"
echo "PTR: $ptr_record"
echo ""
} >> "$OUTPUT_FILE"
fi
}
# Функция проверки TCP порта (оптимизированная)
check_tcp_port() {
local host=$1
local port=$2
if timeout $TCP_TIMEOUT bash -c "echo >/dev/tcp/$host/$port" 2>/dev/null; then
echo -n "✅"
else
echo -n "❌"
fi
}
# Функция проверки UDP порта (оптимизированная)
check_udp_port() {
local host=$1
local port=$2
if command -v nmap &> /dev/null; then
# Параллельный nmap сканер
if timeout $UDP_TIMEOUT nmap -sU -p $port --host-timeout ${UDP_TIMEOUT}s $host 2>/dev/null | grep -q "$port/udp.*open"; then
echo -n "✅"
else
echo -n "❌"
fi
else
# Базовая UDP проверка
if timeout $UDP_TIMEOUT bash -c "echo >/dev/udp/$host/$port" 2>/dev/null; then
echo -n "✅"
else
echo -n "❌"
fi
fi
}
# Функция проверки PTR записи
check_ptr_record() {
local ip=$1
local ptr_result
if command -v dig &> /dev/null; then
ptr_result=$(dig +short +time=2 +tries=1 -x "$ip" 2>/dev/null | head -1)
elif command -v nslookup &> /dev/null; then
ptr_result=$(timeout 2 nslookup "$ip" 2>/dev/null | grep "name =" | awk '{print $4}' | sed 's/\.$//')
else
ptr_result=""
fi
if [ -n "$ptr_result" ]; then
echo -e "${GREEN}✅ $ptr_result${NC}"
else
echo -e "${RED}❌ the record was not found${NC}"
fi
}
# Функция для получения PTR записи без цветов (для файла)
get_ptr_record_plain() {
local ip=$1
local ptr_result
if command -v dig &> /dev/null; then
ptr_result=$(dig +short +time=2 +tries=1 -x "$ip" 2>/dev/null | head -1)
elif command -v nslookup &> /dev/null; then
ptr_result=$(timeout 2 nslookup "$ip" 2>/dev/null | grep "name =" | awk '{print $4}' | sed 's/\.$//')
else
ptr_result=""
fi
if [ -n "$ptr_result" ]; then
echo "✅ $ptr_result"
else
echo "❌ the record was not found"
fi
}
# Функция сканирования одного хоста
scan_host() {
local ip="$1"
local ping_time="$2"
local output="==========================================\n"
output+="Scanning $ip...\n"
output+="==========================================\n"
# Переменные для сохранения в файл
local tcp_results=""
local udp_results=""
local ptr_result_plain=""
if [ -n "$ping_time" ] && [ "$ping_time" != "null" ]; then
output+="PING: ${GREEN}✅ is alive (${ping_time}ms)${NC}\n"
# TCP проверки
output+="TCP: "
tcp_results=""
first_tcp=true
for port in $TCP_PORTS; do
if [ "$first_tcp" = true ]; then
first_tcp=false
else
output+=","
tcp_results+=","
fi
local tcp_result
tcp_result=$(check_tcp_port "$ip" "$port")
output+="${tcp_result}$port"
tcp_results+="${tcp_result}$port"
done
output+="\n"
# UDP проверки
output+="UDP: "
udp_results=""
first_udp=true
for port in $UDP_PORTS; do
if [ "$first_udp" = true ]; then
first_udp=false
else
output+=","
udp_results+=","
fi
local udp_result
udp_result=$(check_udp_port "$ip" "$port")
output+="${udp_result}$port"
udp_results+="${udp_result}$port"
done
output+="\n"
# PTR запись
output+="PTR: "
local ptr_result
ptr_result=$(check_ptr_record "$ip")
output+="$ptr_result\n"
# Получаем PTR запись без цветов для файла
ptr_result_plain=$(get_ptr_record_plain "$ip")
# Сохраняем в файл
save_to_file "$ip" "$ping_time" "$tcp_results" "$udp_results" "$ptr_result_plain"
else
output+="PING: ${RED}❌ is not reachable${NC}\n"
output+="TCP: ${YELLOW}⏭️ skipped (host unreachable)${NC}\n"
output+="UDP: ${YELLOW}⏭️ skipped (host unreachable)${NC}\n"
output+="PTR: ${YELLOW}⏭️ skipped (host unreachable)${NC}\n"
fi
output+="\n"
echo -e "$output"
}
# Функция для параллельного выполнения
run_parallel() {
local function_name="$1"
local max_jobs="$2"
local -a items=("${@:3}")
local -a pids=()
local running_jobs=0
local index=0
while [ $index -lt ${#items[@]} ] || [ ${#pids[@]} -gt 0 ]; do
# Запускаем новые задачи если есть свободные слоты
while [ $running_jobs -lt $max_jobs ] && [ $index -lt ${#items[@]} ]; do
$function_name "${items[$index]}" &
pids+=($!)
running_jobs=$((running_jobs + 1))
index=$((index + 1))
done
# Проверяем завершенные процессы
local new_pids=()
for pid in "${pids[@]}"; do
if kill -0 "$pid" 2>/dev/null; then
new_pids+=($pid)
else
running_jobs=$((running_jobs - 1))
wait "$pid" 2>/dev/null
fi
done
pids=("${new_pids[@]}")
sleep 0.1
done
}
# Функция массового ping
bulk_ping() {
local subnet="$1"
local limit="$2"
local -n result_ref="$3"
local count=0
local total=$limit
echo -e "${BLUE}Pinging $total hosts...${NC}" >&2
# Параллельный ping
for ((i=0; i<limit; i++)); do
local ip="${subnet}${i}"
{
# Пробуем ping с таймаутом
if ping -c 1 -W $PING_TIMEOUT "$ip" &>/dev/null; then
# Если пинг успешен, получаем время
local ping_result
ping_result=$(ping -c 1 -W $PING_TIMEOUT "$ip" 2>/dev/null | grep "time=" | head -1)
if [ -n "$ping_result" ]; then
local ping_time
ping_time=$(echo "$ping_result" | grep -o "time=[0-9.]*" | cut -d= -f2)
result_ref["$ip"]="$ping_time"
echo -e "${GREEN}✓ Alive: $ip (${ping_time}ms)${NC}" >&2
else
result_ref["$ip"]="1"
echo -e "${GREEN}✓ Alive: $ip (1ms)${NC}" >&2
fi
fi
} &
count=$((count + 1))
# Ограничиваем количество параллельных процессов
if [[ $(jobs -r -p | wc -l) -ge $THREADS ]]; then
wait -n
fi
done
# Ждем завершения всех процессов
wait
# Альтернативный метод с fping (если нужен)
if command -v fping &> /dev/null && [ ${#result_ref[@]} -eq 0 ]; then
echo -e "${YELLOW}Trying fping method...${NC}" >&2
local ping_list=()
for ((i=0; i<limit; i++)); do
ping_list+=("${subnet}${i}")
done
# Используем fping в простом режиме
for ip in "${ping_list[@]}"; do
if fping -c 1 -t 1000 "$ip" &>/dev/null; then
result_ref["$ip"]="1"
echo -e "${GREEN}✓ Alive (fping): $ip${NC}" >&2
fi
done
fi
}
# Вспомогательная функция для параллельного сканирования
scan_single_host() {
local task="$1"
local ip="${task%:*}"
local ping_time="${task#*:}"
scan_host "$ip" "$ping_time"
}
# 1. Проверка на --help / -h
if [[ "$1" == "--help" ]] || [[ "$1" == "-h" ]]; then
show_help
exit 0
fi
# 2. Проверка количества аргументов
if [ $# -lt 2 ]; then
echo "Error: At least 2 arguments required."
echo "Use '$0 --help' for usage information."
exit 1
fi
# 3. Валидация subnet
subnet="$1"
if ! [[ "$subnet" =~ ^([1-9][0-9]{0,2}|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.([1-9][0-9]{0,2}|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.([1-9][0-9]{0,2}|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.$ ]]; then
echo "Error: <subnet> must be in format NNN.NNN.NNN. where each NNN is 1–255 (no leading zeros)."
echo "Example: 192.168.1."
exit 1
fi
# 4. Валидация range
range="$2"
if ! [[ "$range" =~ ^[0-9]+$ ]]; then
echo "Error: <range> must be a non-negative integer."
exit 1
fi
if [ "$range" -lt 0 ] || [ "$range" -gt 255 ]; then
echo "Error: <range> must be between 0 and 255 (inclusive)."
exit 1
fi
# 5. Валидация threads (опциональный параметр)
if [ $# -ge 3 ]; then
if [[ "$3" =~ ^[0-9]+$ ]] && [ "$3" -ge 1 ] && [ "$3" -le 50 ]; then
THREADS="$3"
else
echo "Error: <threads> must be between 1 and 50."
exit 1
fi
fi
# 6. Проверка утилит
echo -e "${BLUE}Checking required utilities...${NC}"
if ! command -v ping &> /dev/null; then
echo "Error: 'ping' command not found. Please install it."
exit 2
fi
if command -v fping &> /dev/null; then
echo -e "${GREEN}✓ fping available (fallback method)${NC}"
else
echo -e "${YELLOW}⚠ fping not found, using standard ping${NC}"
fi
if ! command -v nmap &> /dev/null; then
echo -e "${YELLOW}⚠ 'nmap' not found. UDP port checking will be limited.${NC}"
fi
if ! command -v dig &> /dev/null && ! command -v nslookup &> /dev/null; then
echo -e "${YELLOW}⚠ Neither 'dig' nor 'nslookup' found. PTR records cannot be checked.${NC}"
fi
# 7. Основное сканирование
echo -e "\n${BLUE}Starting parallel scan of $range hosts using $THREADS threads...${NC}"
echo -e "${BLUE}Subnet: $subnet${NC}"
echo -e "${BLUE}Ports - TCP: $TCP_PORTS${NC}"
echo -e "${BLUE}Ports - UDP: $UDP_PORTS${NC}"
# Инициализация файла результатов
if [ "$SAVE_TO_FILE" = true ]; then
echo -e "${BLUE}Results will be saved to: $OUTPUT_FILE${NC}"
{
echo "Network Scan Results"
echo "===================="
echo "Date: $(date)"
echo "Subnet: $subnet"
echo "Range: 0-$((range-1))"
echo "Total hosts scanned: $range"
echo ""
echo "ALIVE HOSTS:"
echo "============"
echo ""
} > "$OUTPUT_FILE"
fi
echo ""
start_time=$(date +%s)
# Этап 1: Массовое обнаружение хостов
echo -e "${BLUE}Stage 1: Host discovery...${NC}"
declare -A alive_hosts
bulk_ping "$subnet" "$range" alive_hosts
echo -e "${GREEN}Found ${#alive_hosts[@]} alive hosts out of $range${NC}"
# Этап 2: Параллельное сканирование доступных хостов
if [ ${#alive_hosts[@]} -gt 0 ]; then
echo -e "${BLUE}Stage 2: Parallel port scanning...${NC}"
# Создаем массив для задач сканирования
scan_tasks=()
for ((i=0; i<range; i++)); do
ip="${subnet}${i}"
if [ -n "${alive_hosts[$ip]}" ]; then
scan_tasks+=("$ip:${alive_hosts[$ip]}")
else
scan_tasks+=("$ip:null")
fi
done
# Запускаем параллельное сканирование
run_parallel scan_single_host "$THREADS" "${scan_tasks[@]}"
else
echo -e "${RED}No alive hosts found!${NC}"
echo -e "${YELLOW}Debug info:${NC}"
echo -e "${YELLOW}- Subnet: $subnet${NC}"
echo -e "${YELLOW}- Range: 0 to $((range-1))${NC}"
echo -e "${YELLOW}- Threads: $THREADS${NC}"
echo -e "${YELLOW}- Ping timeout: ${PING_TIMEOUT}s${NC}"
# Тестируем конкретные адреса которые должны быть доступны
echo -e "\n${YELLOW}Testing specific addresses manually:${NC}"
test_addresses=("${subnet}1" "${subnet}4" "${subnet}10" "${subnet}100")
for test_ip in "${test_addresses[@]}"; do
echo -n "Testing $test_ip: "
if ping -c 1 -W 3 "$test_ip" &>/dev/null; then
echo -e "${GREEN}REACHABLE${NC}"
else
echo -e "${RED}UNREACHABLE${NC}"
fi
done
fi
end_time=$(date +%s)
duration=$((end_time - start_time))
# Добавляем итоги в файл
if [ "$SAVE_TO_FILE" = true ]; then
{
echo ""
echo "SCAN SUMMARY:"
echo "============="
echo "Scan completed: $(date)"
echo "Duration: ${duration} seconds"
echo "Total hosts scanned: $range"
echo "Alive hosts found: ${#alive_hosts[@]}"
} >> "$OUTPUT_FILE"
echo -e "${GREEN}Results saved to: $OUTPUT_FILE${NC}"
fi
echo -e "${GREEN}Scan completed in ${duration} seconds${NC}"
echo -e "${GREEN}Scanned $range hosts, found ${#alive_hosts[@]} alive hosts${NC}"
exit 0
#!/bin/bash
# Цвета
RED='\033[0;31m'
GREEN='\033[0;32m'
NC='\033[0m'
show_help() {
local width=120 # Общая ширина рамки (можно менять)
local inner_width=$((width - 4)) # Ширина текста внутри (минус: |, пробел, пробел, |)
local indent=" " # Отступ внутри рамки (слева)
# Функция для форматирования строки в рамке
frame_line_0() {
local text="$1"
if [ -z "$text" ]; then
# Пустая строка — просто отступы и границы
printf "| %${inner_width}s |\n" ""
else
# Добавляем отступ, обрезаем/дополняем до ширины
local padded="${indent}${text}"
printf "| %-${inner_width}s |\n" "${padded:0:$inner_width}"
fi
}
frame_line() {
local text="$1"
if [ -z "$text" ]; then
# Пустая строка — просто отступы и границы
printf "%${inner_width}s\n" ""
else
# Добавляем отступ, обрезаем/дополняем до ширины
local padded="${indent}${text}"
printf "%-${inner_width}s\n" "${padded:0:$inner_width}"
fi
}
# Верхняя граница: +----------+
line=$(printf '%*s' "$width" | tr ' ' '-')
printf '+%s+\n' "$line"
# Пустая строка после верхней границы
frame_line ""
# Основной контент
frame_line "Usage: $0 <subnet> <range>"
frame_line ""
frame_line "Description:"
frame_line " Pings a range of IP addresses based on the provided subnet and range."
frame_line ""
frame_line "Arguments:"
frame_line " <subnet> Base subnet in format NNN.NNN.NNN. (each NNN: 1–255, no leading zeros)"
frame_line " Example: 192.168.1."
frame_line " <range> Number of IPs to ping (0–255). IPs will be: subnet0, subnet1, ..., subnet<range-1>"
frame_line ""
frame_line "Options:"
frame_line " -h, --help Display this help message and exit"
frame_line ""
frame_line "Examples:"
frame_line " $0 192.168.1. 5"
frame_line " → Pings: 192.168.1.0, 192.168.1.1, ..., 192.168.1.4"
frame_line " $0 10.0.0. 3"
frame_line " → Pings: 10.0.0.0, 10.0.0.1, 10.0.0.2"
frame_line " $0 --help"
frame_line " → Shows this help"
frame_line ""
frame_line "Validation rules:"
frame_line " - <subnet> must end with a dot (.)"
frame_line " - Each octet in <subnet> must be 1–255 (no leading zeros like '01' or '007')"
frame_line " - <range> must be an integer from 0 to 255"
frame_line ""
frame_line "Exit codes:"
frame_line " 0 — Success (all checks passed, pinging completed)"
frame_line " 1 — Validation error or invalid arguments"
frame_line ""
frame_line "Colors:"
frame_line " ${GREEN}Green${NC} → Host is reachable"
frame_line " ${RED}Red${NC} → Host is unreachable"
# Пустая строка перед нижней границей
frame_line ""
# Нижняя граница: +----------+
line=$(printf '%*s' "$width" | tr ' ' '-')
printf '+%s+\n' "$line"
}
# 1. Проверка на --help / -h
if [[ "$1" == "--help" ]] || [[ "$1" == "-h" ]]; then
show_help
exit 0
fi
# 2. Проверка количества аргументов
if [ $# -ne 2 ]; then
echo "Error: Exactly 2 arguments required."
echo "Use '$0 --help' for usage information."
exit 1
fi
# 3. Валидация subnet (первый аргумент)
subnet="$1"
if ! [[ "$subnet" =~ ^([1-9][0-9]{0,2}|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.([1-9][0-9]{0,2}|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.([1-9][0-9]{0,2}|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.$ ]]; then
echo "Error: <subnet> must be in format NNN.NNN.NNN. where each NNN is 1–255 (no leading zeros)."
echo "Example: 192.168.1."
echo "Use '$0 --help' for more details."
exit 1
fi
# 4. Валидация range (второй аргумент)
range="$2"
if ! [[ "$range" =~ ^[0-9]+$ ]]; then
echo "Error: <range> must be a non-negative integer."
echo "Use '$0 --help' for valid range (0–255)."
exit 1
fi
if [ "$range" -lt 0 ] || [ "$range" -gt 255 ]; then
echo "Error: <range> must be between 0 and 255 (inclusive)."
echo "Use '$0 --help' for details."
exit 1
fi
# 5. Основные переменные
NET="$subnet"
LIMIT="$range"
# 6. Цикл пинг-запросов
for ((i=0; i<LIMIT; i++)); do
ADR="${NET}${i}"
echo "Pinging $ADR..."
if ping -c 1 -W 1 "$ADR" &> /dev/null; then
echo -e "${GREEN}$ADR is alive${NC}"
else
echo -e "${RED}$ADR is not reachable${NC}"
fi
done
exit 0
@gunk1n
Copy link
Copy Markdown
Author

gunk1n commented Oct 29, 2025

1. Скачиваем

wget https://gist.githubusercontent.com/... -O ~/oh-my-hostvds-extended.sh
wget https://gist.githubusercontent.com/... -O ~/oh-my-hostvds-extended-v2.sh

2. Даём права

chmod +x ~/oh-my-hostvds*.sh

3. Смотрим справку

~/oh-my-hostvds*.sh --help

@gunk1n
Copy link
Copy Markdown
Author

gunk1n commented Oct 29, 2025

Без скачивания

  • bash <(curl -Ls https://gist.githubusercontent.com/...) --help

  • bash <(curl -Ls https://gist.githubusercontent.com/...) -h

  • bash <(curl -Ls https://gist.githubusercontent.com/...) 192.168.1. 10

@gunk1n
Copy link
Copy Markdown
Author

gunk1n commented Oct 29, 2025

Новые возможности (oh-my-hostvds-extended-v2-sh):

1. Три режима работы:

Одиночная подсеть (совместимость):

./oh-my-hostvds-extended-v2.sh 192.168.1. 50

Несколько подсетей через аргументы:

./oh-my-hostvds-extended-v2.sh --subnets 192.168.1. 10.0.0. 172.16.1. --range 20

Подсети из файла:

./oh-my-hostvds-extended-v2.sh --file subnets.txt --range 50 25

2. Формат файла subnets.txt:

192.168.1.
10.0.0.
172.16.1.
# Это комментарий
172.17.1.

3. Примеры использования:

# Сканирование трех подсетей по 10 адресов в каждой
./oh-my-hostvds-extended-v2.sh --subnets 192.168.1. 10.0.0. 172.16.1. --range 10

# Сканирование подсетей из файла с 50 потоками
./oh-my-hostvds-extended-v2.sh --file my_subnets.txt --range 100 50

# Совместимость со старым форматом
./oh-my-hostvds-extended-v2.sh 192.168.1. 50 25

4. Улучшенный вывод:

Starting parallel scan of 3 subnets, 30 total hosts using 10 threads...
Subnets: 192.168.1. 10.0.0. 172.16.1.
Range per subnet: 0-9

=========================================
Scanning subnet: 192.168.1.
=========================================
Stage 1: Host discovery...
Pinging 10 hosts in 192.168.1.*...
✓ Alive: 192.168.1.1 (12.5ms)
Found 3 alive hosts in 192.168.1.*
Stage 2: Parallel port scanning...

5. Сводка по всем подсетям:

  • Общее количество подсетей
  • Общее количество хостов
  • Общее количество найденных активных хостов
  • Время выполнения

Теперь вы можете сканировать несколько подсетей одновременно с сохранением всех результатов в один файл!

@gunk1n
Copy link
Copy Markdown
Author

gunk1n commented Oct 29, 2025

Добавленные возможности (oh-my-hostvds-extended.sh):

1. Автоматическое сохранение в файл:

  • Файл создается с timestamp: scan_results_20231201_143022.txt
  • Сохраняется только информация о доступных хостах
  • Полный вывод по-прежнему показывается в консоли

2. Формат файла результатов:

Network Scan Results
====================
Date: Fri Dec 1 14:30:22 UTC 2023
Subnet: 109.248.162.
Range: 0-199
Total hosts scanned: 200

ALIVE HOSTS:
============

==========================================
Host: 109.248.162.1
==========================================
PING: ✅ is alive (12.5ms)
TCP: ✅22,✅80,❌443,❌53,❌21,❌25,❌110,❌143,❌993,❌995,❌3389
UDP: ❌53,❌67,❌68,✅123,❌161,❌500,❌4500
PTR: ✅ host1.example.com

==========================================
Host: 109.248.162.4
==========================================
PING: ✅ is alive (8.2ms)
TCP: ❌22,✅80,✅443,❌53,❌21,❌25,❌110,❌143,❌993,❌995,❌3389
UDP: ❌53,❌67,❌68,✅123,❌161,❌500,❌4500
PTR: ❌ the record was not found

SCAN SUMMARY:
=============
Scan completed: Fri Dec 1 14:30:25 UTC 2023
Duration: 3 seconds
Total hosts scanned: 200
Alive hosts found: 15

3. Особенности сохранения:

  • ✅ Только доступные хосты
  • ✅ Без цветового форматирования (для удобства чтения)
  • ✅ Включает сводку сканирования
  • ✅ Автоматическое имя файла с датой и временем

4. Пример использования:

./oh-my-hostvds-extended.sh 109.248.162. 200 50

После выполнения сканирования вы увидите:

Results saved to: scan_results_20231201_143022.txt

Теперь у вас есть полный лог всех доступных хостов в удобном для анализа формате!

@gunk1n
Copy link
Copy Markdown
Author

gunk1n commented Oct 29, 2025

Новые возможности (oh-my-hostvds-extended-v3.sh):

1. Два режима вывода:

Подробный режим (по умолчанию):

./oh-my-hostvds-extended-v3.sh --subnets 192.168.1. 10.0.0. --range 10 --mode detailed

Вывод как раньше - полная информация по каждому хосту.

Режим с прогресс-баром:

./oh-my-hostvds-extended-v3.sh --subnets 192.168.1. 10.0.0. --range 10 --mode progress

2. Прогресс-бар:

Progress: [██████████████████████████████████████████████████] 100% (100/100)

3. Компактный вывод результатов:

В режиме progress показывается только краткая информация о доступных хостах:

✓ 192.168.1.1 (12.5ms) | TCP: 2/11 | UDP: 1/7 | PTR: router.local
✓ 192.168.1.4 (8.2ms) | TCP: 3/11 | UDP: 1/7
✓ 10.0.0.1 (15.3ms) | TCP: 1/11 | UDP: 0/7 | PTR: server.example.com

4. Примеры использования:

# Подробный режим (по умолчанию)
./oh-my-hostvds-extended-v3.sh --subnets 192.168.1. 10.0.0. --range 20

# Режим прогресс-бара
./oh-my-hostvds-extended-v3.sh --file subnets.txt --range 50 --mode progress 25

# Смешанный режим - разные подсети в разных режимах
./oh-my-hostvds-extended-v3.sh 192.168.1. 50 --mode progress

5. Особенности режима progress:

  • ✅ Прогресс-бар показывает общий прогресс сканирования
  • ✅ Недоступные хосты не показываются (только доступные)
  • ✅ Компактная информация: IP, время ping, открытые порты, PTR
  • ✅ Сохранение в файл работает в обоих режимах одинаково

6. Визуальное отличие:

  • Detailed: Много текста, подходит для отладки
  • Progress: Минималистичный вывод, подходит для мониторинга больших сетей

Теперь вы можете выбирать режим вывода в зависимости от задачи!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment