#!/usr/bin/env bash
readonly SCRIPT_NAME=$(basename "$0")
readonly DEPENDENCIES=("curl" "nslookup" "netcat")
readonly COLOR_WHITE="\033[97m"
readonly COLOR_RED="\033[31m"
readonly COLOR_GREEN="\033[32m"
readonly COLOR_BLUE="\033[36m"
readonly COLOR_ORANGE="\033[33m"
readonly COLOR_RESET="\033[0m"
readonly CURL_SEPARATOR="--UNIQUE-SEPARATOR--"
# Default values
TIMEOUT=5
RETRIES=2
MODE="both"
USER_AGENT="Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:129.0) Gecko/20100101 Firefox/129.0"
DOMAINS_FILE=""
IP_VERSION="4"
PROXY=""
SINGLE_DOMAIN=""
PROTOCOL="both"
JSON_OUTPUT=false
readonly DPI_BLOCKED_SITES=(
"youtube.com"
"discord.com"
"instagram.com"
"facebook.com"
"x.com"
"patreon.com"
"linkedin.com"
"rutracker.org"
"nnmclub.to"
"digitalocean.com"
"medium.com"
"ntc.party"
"amnezia.org"
"getoutline.org"
"mailfence.com"
"flibusta.is"
"rezka.ag"
)
readonly GEO_BLOCKED_SITES=(
"spotify.com"
"netflix.com"
"swagger.io"
"snyk.io"
"mongodb.com"
"autodesk.com"
"graylog.org"
"redis.io"
)
readonly MSG_AVAILABLE="Available"
readonly MSG_BLOCKED="Blocked"
readonly MSG_BLOCKED_TEMPLATE="$MSG_BLOCKED or site didn't respond after %ss timeout"
readonly MSG_REDIRECT="Redirected"
readonly MSG_ACCESS_DENIED="Denied"
readonly MSG_OTHER="Responded with status code"
declare -A DEPENDENCY_COMMANDS=(
[curl]="curl"
[nslookup]="nslookup"
[netcat]="nc"
)
error_exit() {
local message="$1"
local exit_code="${2:-1}"
printf "[%b%s%b] %b%s%b\n" "$COLOR_RED" "ERROR" "$COLOR_RESET" "$COLOR_WHITE" "$message" "$COLOR_RESET" >&2
display_help
exit "$exit_code"
}
log_time() {
printf "[%b%s%b] %s\n" "$COLOR_ORANGE" "$(date '+%M:%S:%3N')" "$COLOR_RESET" "$1" >&2
}
display_help() {
cat </dev/null 2>&1
}
check_missing_dependencies() {
local missing_pkgs=()
local cmd
for pkg in "${DEPENDENCIES[@]}"; do
cmd="${DEPENDENCY_COMMANDS[$pkg]:-$pkg}"
if ! is_installed "$cmd"; then
missing_pkgs+=("$pkg")
fi
done
echo "${missing_pkgs[@]}"
}
prompt_for_installation() {
local missing_pkgs=("$@")
echo "Missing dependencies: ${missing_pkgs[*]}"
read -r -p "Do you want to install them? [y/N]: " answer
answer=${answer,,}
case "${answer,,}" in
y | yes)
return 0
;;
*)
exit 0
;;
esac
}
get_package_manager() {
# Check if the script is running in Termux
if [[ -d /data/data/com.termux ]]; then
echo "termux"
return
fi
if [[ -f /etc/os-release ]]; then
. /etc/os-release
case "$ID" in
debian | ubuntu)
echo "apt"
;;
arch)
echo "pacman"
;;
fedora)
echo "dnf"
;;
*)
error_exit "Unknown distribution: $ID. Please install dependencies manually."
;;
esac
else
error_exit "File /etc/os-release not found, unable to determine distribution. Please install dependencies manually."
fi
}
install_with_package_manager() {
local pkg_manager="$1"
local use_sudo=""
local packages=()
shift
for dep in "$@"; do
case "$pkg_manager" in
apt | termux)
case "$dep" in
nslookup) packages+=("dnsutils") ;;
netcat) packages+=("netcat-openbsd") ;;
*) packages+=("$dep") ;;
esac
;;
pacman)
case "$dep" in
nslookup) packages+=("bind") ;;
netcat) packages+=("openbsd-netcat") ;;
*) packages+=("$dep") ;;
esac
;;
dnf)
case "$dep" in
nslookup) packages+=("bind-utils") ;;
netcat) packages+=("netcat") ;;
*) packages+=("$dep") ;;
esac
;;
*)
packages+=("$dep")
;;
esac
done
if [[ "${EUID:-$(id -u)}" -ne 0 ]]; then
use_sudo="sudo"
fi
case "$pkg_manager" in
apt)
$use_sudo apt update
$use_sudo env NEEDRESTART_MODE=a apt install -y "${packages[@]}"
;;
pacman)
$use_sudo pacman -Syy --noconfirm "${packages[@]}"
;;
dnf)
$use_sudo dnf install -y "${packages[@]}"
;;
termux)
apt update
apt install -y "${packages[@]}"
;;
*)
error_exit "Unknown package manager: $pkg_manager"
;;
esac
}
install_dependencies() {
local missing_packages
local pkg_manager
read -r -a missing_packages <<<"$(check_missing_dependencies)"
if [[ ${#missing_packages[@]} -eq 0 ]]; then
return 0
fi
prompt_for_installation "${missing_packages[@]}"
pkg_manager=$(get_package_manager)
install_with_package_manager "$pkg_manager" "${missing_packages[@]}"
}
check_ipv6_support() {
if [[ -n $(ip -6 addr show scope global 2>/dev/null) ]]; then
return 0
fi
return 1
}
parse_arguments() {
while [[ $# -gt 0 ]]; do
case $1 in
-h | --help)
display_help
exit 0
;;
-m | --mode)
if [[ $2 == "dpi" || $2 == "geoblock" || $2 == "both" ]]; then
MODE=$2
else
error_exit "Invalid mode: $2. Valid modes are: dpi, geoblock, both"
fi
shift 2
;;
-t | --timeout)
if [[ "$2" =~ ^[0-9]+$ ]]; then
TIMEOUT=$2
else
error_exit "Invalid timeout value: $2. Timeout must be a positive integer"
fi
shift 2
;;
-r | --retries)
if [[ "$2" =~ ^[0-9]+$ ]]; then
RETRIES=$2
else
error_exit "Invalid retries value: $2. Retry count must be a positive integer"
fi
shift 2
;;
-u | --user-agent)
if [[ -n "$2" ]]; then
USER_AGENT=$2
else
error_exit "User-Agent cannot be empty"
fi
shift 2
;;
-f | --file)
if [[ -n "${2:-}" ]]; then
if [[ -f "$2" ]]; then
DOMAINS_FILE="$2"
else
error_exit "File '$2' does not exist"
fi
else
error_exit "File path cannot be empty"
fi
shift 2
;;
-6 | --ipv6)
if ! check_ipv6_support; then
error_exit "IPv6 is not supported on this system"
fi
IP_VERSION="6"
shift
;;
-p | --proxy)
if [[ -n "${2:-}" ]]; then
PROXY="$2"
else
error_exit "Proxy address cannot be empty"
fi
shift 2
;;
-d | --domain)
if [[ -n "$2" ]]; then
SINGLE_DOMAIN="$2"
else
error_exit "Domain cannot be empty"
fi
shift 2
;;
--http-only)
PROTOCOL="http"
shift
;;
--https-only)
PROTOCOL="https"
shift
;;
-j | --json)
JSON_OUTPUT=true
shift
;;
*)
error_exit "Unknown option: $1"
;;
esac
done
}
print_header() {
local mode
cat <<'EOF'
---------------------------------------------------------------------------------
██████╗███████╗███╗ ██╗███████╗ ██████╗ ██████╗ ██████╗██╗ ██╗███████╗ ██████╗██╗ ██╗
██╔════╝██╔════╝████╗ ██║██╔════╝██╔═══██╗██╔══██╗██╔════╝██║ ██║██╔════╝██╔════╝██║ ██╔╝
██║ █████╗ ██╔██╗ ██║███████╗██║ ██║██████╔╝██║ ███████║█████╗ ██║ █████╔╝
██║ ██╔══╝ ██║╚██╗██║╚════██║██║ ██║██╔══██╗██║ ██╔══██║██╔══╝ ██║ ██╔═██╗
╚██████╗███████╗██║ ╚████║███████║╚██████╔╝██║ ██║╚██████╗██║ ██║███████╗╚██████╗██║ ██╗
╚═════╝╚══════╝╚═╝ ╚═══╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝╚══════╝ ╚═════╝╚═╝ ╚═╝
---------------------------------------------------------------------------------
EOF
printf "\nTimeout set to: %b%ss%b\n" "$COLOR_WHITE" "$TIMEOUT" "$COLOR_RESET"
printf "Retries set to: %b%s%b\n" "$COLOR_WHITE" "$RETRIES" "$COLOR_RESET"
case $MODE in
dpi)
mode="DPI"
;;
geoblock)
mode="Geoblock"
;;
both)
mode="DPI and Geoblock"
;;
esac
if [[ -z "$DOMAINS_FILE" ]] && [[ -z "$SINGLE_DOMAIN" ]]; then
printf "Mode set to: %b%s%b\n" "$COLOR_WHITE" "$mode" "$COLOR_RESET"
fi
printf "User-Agent set to: %b%s%b\n" "$COLOR_WHITE" "$USER_AGENT" "$COLOR_RESET"
if [[ -n "$DOMAINS_FILE" ]]; then
printf "Domain mode set to: %buser domains from %s%b\n" "$COLOR_WHITE" "$DOMAINS_FILE" "$COLOR_RESET"
elif [[ -n "$SINGLE_DOMAIN" ]]; then
printf "Checking single domain: %b%s%b\n" "$COLOR_WHITE" "$SINGLE_DOMAIN" "$COLOR_RESET"
else
printf "Domain mode set to: %bpredefined domains%b\n" "$COLOR_WHITE" "$COLOR_RESET"
fi
printf "IP version set to: %bIPv%s%b\n" "$COLOR_WHITE" "$IP_VERSION" "$COLOR_RESET"
if [ -n "$PROXY" ]; then
printf "SOCKS5 proxy set to: %b%s%b\n" "$COLOR_WHITE" "$PROXY" "$COLOR_RESET"
fi
case $PROTOCOL in
http)
printf "Protocol set to: %bHTTP only%b\n" "$COLOR_WHITE" "$COLOR_RESET"
;;
https)
printf "Protocol set to: %bHTTPS only%b\n" "$COLOR_WHITE" "$COLOR_RESET"
;;
both)
printf "Protocol set to: %bHTTP and HTTPS%b\n" "$COLOR_WHITE" "$COLOR_RESET"
;;
esac
}
read_domains_from_file() {
local file=$1
local domains=()
while IFS= read -r line || [[ -n "$line" ]]; do
if [[ -n "$line" && ! "$line" =~ ^[[:space:]]*# ]]; then
line=$(echo "$line" | xargs)
if [[ -n "$line" ]]; then
domains+=("$line")
fi
fi
done <"$file"
echo "${domains[@]}"
}
execute_curl() {
local url=$1
local protocol=$2
local follow_redirects=$3
local ip_version_to_use=${4:-$IP_VERSION}
local curl_output
local curl_opts=(
-s
-o /dev/null
-w "%{http_code}${CURL_SEPARATOR}%{redirect_url}"
--retry-connrefused
--retry-all-errors
--retry "$RETRIES"
--connect-timeout "$TIMEOUT"
--max-time "$TIMEOUT"
-"$ip_version_to_use"
-A "$USER_AGENT"
-H "Sec-Fetch-Site: none"
-H "Accept-Language: en-US,en;q=0.5"
-H "Accept-Encoding: gzip, deflate, br, zstd"
)
if [ -n "$PROXY" ]; then
curl_opts+=(--proxy "socks5://$PROXY")
fi
if [ "$follow_redirects" = true ]; then
curl_opts+=(-L)
fi
if curl_output=$(curl "${curl_opts[@]}" "${protocol}://${url}"); then
echo "$curl_output"
else
echo "000${CURL_SEPARATOR}"
fi
}
format_result() {
local protocol=$1
local status_code=$2
local redirect_url=$3
local msg
if [ -z "$status_code" ] || [ "$status_code" = "000" ] || [ "$status_code" -eq 0 ]; then
msg=$(printf "$MSG_BLOCKED_TEMPLATE" "$TIMEOUT")
elif [ "$status_code" -ge 300 ] && [ "$status_code" -lt 400 ]; then
if [[ -z "$redirect_url" ]]; then
redirect_url=""
fi
msg=$(printf "$MSG_REDIRECT (%s) to %b%s%b" "$status_code" "$COLOR_WHITE" "$redirect_url" "$COLOR_RESET")
elif [ "$status_code" -eq 200 ]; then
msg="$MSG_AVAILABLE ($status_code)"
elif [ "$status_code" -eq 403 ]; then
msg="$MSG_ACCESS_DENIED ($status_code)"
else
msg="$MSG_OTHER $status_code"
fi
first_word="${msg%% *}"
rest="${msg#* }"
case "$first_word" in
Blocked)
first_word_color=$COLOR_RED
;;
Available)
first_word_color=$COLOR_GREEN
;;
Redirected)
first_word_color=$COLOR_BLUE
;;
Denied)
first_word_color=$COLOR_RED
;;
*)
first_word_color=$COLOR_ORANGE
;;
esac
printf " %b%s%b: %b%s%b %s\n" "$COLOR_WHITE" "$protocol" "$COLOR_RESET" "$first_word_color" "$first_word" "$COLOR_RESET" "$rest"
}
get_domains_to_check() {
if [[ -n "$SINGLE_DOMAIN" ]]; then
echo "$SINGLE_DOMAIN"
elif [[ -z "$DOMAINS_FILE" ]]; then
case $MODE in
# TODO: Replace echo with function
dpi) echo "${DPI_BLOCKED_SITES[@]}" ;;
geoblock) echo "${GEO_BLOCKED_SITES[@]}" ;;
both) echo "${DPI_BLOCKED_SITES[@]}" "${GEO_BLOCKED_SITES[@]}" ;;
esac
else
read_domains_from_file "$DOMAINS_FILE"
fi
}
get_single_check_result() {
local domain=$1
local protocol=$2
local follow_redirects=$3
local ip_version=$4
local response status_code redirect_url
response=$(execute_curl "$domain" "$protocol" "$follow_redirects" "$ip_version")
status_code="${response%%$CURL_SEPARATOR*}"
redirect_url="${response#*$CURL_SEPARATOR}"
jq -n \
--argjson status "${status_code:-0}" \
--arg redirect_url "${redirect_url:-}" \
'
{
"status": ($status|tonumber),
"redirect_url": (if $redirect_url == "" then null else $redirect_url end)
}
'
}
gather_single_domain_result() {
local domain=$1
local ipv6_supported
local http_ipv4=null http_ipv6=null https_ipv4=null https_ipv6=null
check_ipv6_support && ipv6_supported=true || ipv6_supported=false
if [[ "$PROTOCOL" == "both" || "$PROTOCOL" == "http" ]]; then
http_ipv4=$(get_single_check_result "$domain" "HTTP" false 4)
if $ipv6_supported; then
http_ipv6=$(get_single_check_result "$domain" "HTTP" false 6)
fi
fi
if [[ "$PROTOCOL" == "both" || "$PROTOCOL" == "https" ]]; then
https_ipv4=$(get_single_check_result "$domain" "HTTPS" true 4)
if $ipv6_supported; then
https_ipv6=$(get_single_check_result "$domain" "HTTPS" true 6)
fi
fi
jq -n \
--arg service "$domain" \
--argjson http_ipv4 "$http_ipv4" \
--argjson http_ipv6 "$http_ipv6" \
--argjson https_ipv4 "$https_ipv4" \
--argjson https_ipv6 "$https_ipv6" \
'
{
"service": $service,
"http": {
"ipv4": $http_ipv4,
"ipv6": $http_ipv6
},
"https": {
"ipv4": $https_ipv4,
"ipv6": $https_ipv6
}
}
'
}
get_domain_ip() {
local domain=$1
nslookup "$domain" 2>/dev/null | awk '/^Address: / && !/#/ {print $2; exit}' || true
}
is_ip_reachable() {
local ip="$1"
nc -z -w "$TIMEOUT" "$ip" 443 2>/dev/null
}
print_ip_status() {
local ip="$1"
local status color
if is_ip_reachable "$ip"; then
status="$MSG_AVAILABLE"
color="$COLOR_GREEN"
else
status="$MSG_BLOCKED"
color="$COLOR_RED"
fi
printf " %bIP connectivity%b: %b%s%b\n" "$COLOR_WHITE" "$COLOR_RESET" "$color" "$status" "$COLOR_RESET"
}
print_domain_header() {
local domain="$1"
local ip_address="$2"
printf "\n--------------------------------\n\n"
printf "Testing %b%s%b (%s):\n" "$COLOR_WHITE" "$domain" "$COLOR_RESET" "$ip_address"
}
print_domain_error() {
local domain="$1"
local ip_address="$2"
local error_code="$3"
print_domain_header "$domain" "$ip_address"
case "$error_code" in
nxdomain)
printf " %bDomain does not exist%b\n" "$COLOR_ORANGE" "$COLOR_RESET"
;;
blocked_by_ip)
print_ip_status "$ip_address"
;;
*)
printf " %bUnknown error%b\n" "$COLOR_RED" "$COLOR_RESET"
;;
esac
}
make_json_error() {
local domain="$1"
local error_code="$2"
case "$error_code" in
nxdomain)
jq -n --arg service "$domain" '
{
"service": $service,
"error": "Domain does not exist",
"error_code": "nxdomain",
"http": null,
"https": null
}
'
;;
blocked_by_ip)
jq -n --arg service "$domain" '
{
"service": $service,
"error": "Blocked by IP",
"error_code": "blocked_by_ip",
"http": null,
"https": null
}
'
;;
*)
jq -n --arg service "$domain" --arg code "$error_code" '
{
"service": $service,
"error": "Unknown error",
"error_code": $code,
"http": null,
"https": null
}
'
;;
esac
}
print_single_domain_text_result() {
local result_item=$1
local ip_address=$2
local domain
local http_result https_result
domain=$(echo "$result_item" | jq -r '.service')
if [[ "$PROTOCOL" == "both" || "$PROTOCOL" == "http" ]]; then
http_result=$(echo "$result_item" | jq -r '.http.ipv4 // .http.ipv6 // {}')
http_status=$(echo "$http_result" | jq -r '.status // "000"')
http_redirect=$(echo "$http_result" | jq -r '.redirect_url // ""')
format_result "HTTP" "$http_status" "$http_redirect"
fi
if [[ "$PROTOCOL" == "both" || "$PROTOCOL" == "https" ]]; then
https_result=$(echo "$result_item" | jq -r '.https.ipv4 // .https.ipv6 // {}')
https_status=$(echo "$https_result" | jq -r '.status // "000"')
https_redirect=$(echo "$https_result" | jq -r '.redirect_url // ""')
format_result "HTTPS" "$https_status" "$https_redirect"
fi
}
run_checks_and_print() {
local domains
local all_results_json="[]"
read -r -a domains <<<"$(get_domains_to_check)"
if ! $JSON_OUTPUT; then
print_header
fi
for domain in "${domains[@]}"; do
# --- НАЧАЛО ЛОГИРОВАНИЯ ДЛЯ ДОМЕНА ---
log_time "Начало обработки домена '$domain'"
local ip_address
log_time "-> Выполняю get_domain_ip (nslookup)..."
ip_address=$(get_domain_ip "$domain")
log_time "<- get_domain_ip завершен. IP: '${ip_address:-не найден}'"
if [[ -z "$ip_address" ]]; then
log_time "!! IP не найден, пропускаем домен."
if $JSON_OUTPUT; then
all_results_json=$(echo "$all_results_json" | jq --argjson item "$(make_json_error "$domain" nxdomain)" '. + [$item]')
else
print_domain_error "$domain" "" nxdomain
fi
continue
fi
log_time "-> Выполняю is_ip_reachable (netcat) для $ip_address..."
if ! is_ip_reachable "$ip_address"; then
log_time "!! IP $ip_address недоступен. Пропускаем домен."
if $JSON_OUTPUT; then
all_results_json=$(echo "$all_results_json" | jq --argjson item "$(make_json_error "$domain" blocked_by_ip)" '. + [$item]')
else
print_domain_error "$domain" "$ip_address" blocked_by_ip
fi
continue
fi
log_time "<- is_ip_reachable завершен. IP доступен."
local domain_result_json
log_time "-> Выполняю gather_single_domain_result (все curl запросы)..."
domain_result_json=$(gather_single_domain_result "$domain")
log_time "<- gather_single_domain_result завершен."
if $JSON_OUTPUT; then
all_results_json=$(echo "$all_results_json" | jq --argjson item "$domain_result_json" '. + [$item]')
else
log_time "-> Выполняю печать результатов для '$domain'..."
print_domain_header "$domain" "$ip_address"
print_ip_status "$ip_address"
print_single_domain_text_result "$domain_result_json" "$ip_address"
log_time "<- Печать результатов для '$domain' завершена."
fi
done
if $JSON_OUTPUT; then
# (здесь остальная часть функции без изменений)
local ipv6_supported
check_ipv6_support && ipv6_supported=true || ipv6_supported=false
local ip_version_param_val
if $ipv6_supported; then
ip_version_param_val="IPv4 & IPv6"
else
ip_version_param_val="IPv4"
fi
local params_json
params_json=$(jq -n \
--arg timeout "${TIMEOUT}s" \
--arg retries "$RETRIES" \
--arg mode "${MODE^^}" \
--arg user_agent "$USER_AGENT" \
--arg domain_mode "$(if [[ -n "$DOMAINS_FILE" ]]; then echo "user domains from $DOMAINS_FILE"; elif [[ -n "$SINGLE_DOMAIN" ]]; then echo "single domain"; else echo "predefined domains"; fi)" \
--arg ip_version "$ip_version_param_val" \
--arg protocol "$(if [[ "$PROTOCOL" == "both" ]]; then echo "HTTP and HTTPS"; elif [[ "$PROTOCOL" == "http" ]]; then echo "HTTP only"; else echo "HTTPS only"; fi)" \
'
[
{"key":"timeout", "value":$timeout},
{"key":"retries", "value":$retries},
{"key":"mode", "value":$mode},
{"key":"user_agent", "value":$user_agent},
{"key":"domain_mode", "value":$domain_mode},
{"key":"ip_version", "value":$ip_version},
{"key":"protocol", "value":$protocol}
]
')
jq -n \
--argjson params "$params_json" \
--argjson results "$all_results_json" \
'
{
"version": 1,
"params": $params,
"results": $results
}
'
fi
}
main() {
set -euo pipefail
install_dependencies
parse_arguments "$@"
run_checks_and_print
}
main "$@"