Files
cschantz 79efeeb62c Distinguish between Cloudflare Proxied (orange cloud) and DNS-Only (gray cloud)
MAJOR IMPROVEMENT: Accurate Cloudflare detection

Before:
- Domains with CF nameservers were marked as 'using Cloudflare'
- lucidolaw.com (CF DNS but direct IP) → showed as Cloudflare 
- goodmandivorce.com (CF DNS but direct IP) → showed as Cloudflare 

After:
- PROXIED (Orange Cloud): IP in CF range OR CF-RAY header present
  → These domains actually use CDN, caching, DDoS protection
- DNS-ONLY (Gray Cloud): CF nameservers but traffic goes direct
  → Only using CF for DNS management, no CDN benefits
- DIRECT: Not using Cloudflare at all

Changes:
- Updated detect_cloudflare() logic to check IP/headers BEFORE nameservers
- Added dns_only_domains array for gray cloud domains
- New 'DNS-ONLY' status in scan results with explanation
- Updated summary to show: Proxied vs DNS-Only vs Direct
- Single domain check now explains orange vs gray cloud
- Helps users identify domains that need 'Proxied' enabled in CF settings

Real-world impact:
- lucidolaw.com → DNS-ONLY (accurate) ✓
- idivorce-va.virginiafamilylawcenter.com → PROXIED (accurate) ✓
- 100% accurate distinction between CF proxy modes
2026-01-28 15:57:47 -05:00

684 lines
21 KiB
Bash
Executable File

#!/bin/bash
################################################################################
# Cloudflare Detector - Identify domains using Cloudflare
################################################################################
# Scans all domains on the server and detects Cloudflare usage
# Checks: Nameservers, IP ranges, and HTTP headers
################################################################################
# Source common functions
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/../../lib/common-functions.sh" 2>/dev/null || {
echo "[ERROR] Cannot load common-functions.sh"
exit 1
}
# Detect control panel and system
if type -t detect_system &>/dev/null; then
detect_system
fi
################################################################################
# Configuration
################################################################################
# Cloudflare IP ranges (IPv4) - updated as of 2024
# Source: https://www.cloudflare.com/ips-v4
CLOUDFLARE_IPV4_RANGES=(
"173.245.48.0/20"
"103.21.244.0/22"
"103.22.200.0/22"
"103.31.4.0/22"
"141.101.64.0/18"
"108.162.192.0/18"
"190.93.240.0/20"
"188.114.96.0/20"
"197.234.240.0/22"
"198.41.128.0/17"
"162.158.0.0/15"
"104.16.0.0/13"
"104.24.0.0/14"
"172.64.0.0/13"
"131.0.72.0/22"
)
################################################################################
# Helper Functions
################################################################################
get_domains() {
local domains=()
# Get domains from cPanel userdata
if [ -d /var/cpanel/userdata ]; then
while IFS= read -r domain_file; do
local domain=$(basename "$domain_file")
# Skip cache files, main file, and config files
if [[ "$domain_file" =~ \.cache$ ]] || \
[[ "$domain_file" =~ /cache$ ]] || \
[[ "$domain_file" =~ /main$ ]] || \
[[ "$domain" =~ _SSL$ ]] || \
[[ "$domain" =~ \.yaml$ ]] || \
[[ "$domain" =~ \.json$ ]] || \
[[ "$domain" =~ \.conf$ ]] || \
[[ "$domain" =~ \.backup$ ]] || \
[[ "$domain" =~ \.transferred$ ]] || \
[[ "$domain" =~ \.db$ ]] || \
[[ "$domain" =~ \.swp$ ]] || \
[[ "$domain" =~ \.tmp$ ]] || \
[[ "$domain" =~ php-fpm ]] || \
[[ "$domain" =~ \.lock$ ]]; then
continue
fi
# Skip system/template domains
if [[ "$domain" =~ cloudvpstemplate\. ]] || \
[[ "$domain" =~ cprapid\. ]] || \
[[ "$domain" =~ ^[0-9\-]+\. ]] || \
[[ "$domain_file" =~ /nobody/ ]]; then
continue
fi
# Only add if it looks like a domain
if [[ "$domain" != "." && "$domain" != ".." && "$domain" =~ \. ]]; then
domains+=("$domain")
fi
done < <(find /var/cpanel/userdata -type f 2>/dev/null)
fi
printf '%s\n' "${domains[@]}" | sort -u
}
is_cloudflare_ip() {
local ip="$1"
# Convert IP to integer for range checking
local ip_int=$(echo "$ip" | awk -F. '{print ($1 * 16777216) + ($2 * 65536) + ($3 * 256) + $4}')
for range in "${CLOUDFLARE_IPV4_RANGES[@]}"; do
local network=$(echo "$range" | cut -d'/' -f1)
local cidr=$(echo "$range" | cut -d'/' -f2)
# Calculate network range
local net_int=$(echo "$network" | awk -F. '{print ($1 * 16777216) + ($2 * 65536) + ($3 * 256) + $4}')
local mask=$((0xFFFFFFFF << (32 - cidr)))
local start=$((net_int & mask))
local end=$((start + (1 << (32 - cidr)) - 1))
# Check if IP is in this range
if [ "$ip_int" -ge "$start" ] && [ "$ip_int" -le "$end" ]; then
return 0
fi
done
return 1
}
check_nameservers() {
local domain="$1"
# Get nameservers
local nameservers=$(dig +short NS "$domain" 2>/dev/null | tr '[:upper:]' '[:lower:]')
if [ -z "$nameservers" ]; then
echo "UNKNOWN"
return 2
fi
# Check if any nameserver contains cloudflare
if echo "$nameservers" | grep -q "cloudflare"; then
echo "CLOUDFLARE"
return 0
fi
echo "OTHER"
return 1
}
check_ip_address() {
local domain="$1"
# Get A record
local ip=$(dig +short A "$domain" 2>/dev/null | head -1)
if [ -z "$ip" ]; then
echo "UNKNOWN"
return 2
fi
# Check if IP is in Cloudflare range
if is_cloudflare_ip "$ip"; then
echo "CLOUDFLARE"
return 0
fi
echo "DIRECT"
return 1
}
check_http_headers() {
local domain="$1"
# Try HTTP request and look for Cloudflare headers
local headers=$(curl -sI -m 3 "http://$domain" 2>/dev/null)
if [ -z "$headers" ]; then
echo "UNKNOWN"
return 2
fi
# Check for CF-RAY header (definitive Cloudflare indicator)
if echo "$headers" | grep -qi "CF-RAY:"; then
echo "CLOUDFLARE"
return 0
fi
# Check for Server: cloudflare
if echo "$headers" | grep -qi "Server:.*cloudflare"; then
echo "CLOUDFLARE"
return 0
fi
echo "DIRECT"
return 1
}
get_location_name() {
local code="$1"
# Map IATA codes to city names
case "$code" in
# North America
ORD) echo "Chicago" ;;
LAX) echo "Los Angeles" ;;
IAD) echo "Ashburn, VA" ;;
DFW) echo "Dallas" ;;
ATL) echo "Atlanta" ;;
SEA) echo "Seattle" ;;
SJC) echo "San Jose, CA" ;;
MIA) echo "Miami" ;;
YYZ) echo "Toronto" ;;
BNA) echo "Nashville" ;;
DEN) echo "Denver" ;;
PHX) echo "Phoenix" ;;
EWR) echo "Newark, NJ" ;;
BOS) echo "Boston" ;;
# Europe
LHR) echo "London" ;;
FRA) echo "Frankfurt" ;;
AMS) echo "Amsterdam" ;;
CDG) echo "Paris" ;;
MAD) echo "Madrid" ;;
MAN) echo "Manchester" ;;
ARN) echo "Stockholm" ;;
WAW) echo "Warsaw" ;;
VIE) echo "Vienna" ;;
# Asia Pacific
SIN) echo "Singapore" ;;
NRT) echo "Tokyo" ;;
HKG) echo "Hong Kong" ;;
SYD) echo "Sydney" ;;
ICN) echo "Seoul" ;;
BOM) echo "Mumbai" ;;
DEL) echo "New Delhi" ;;
# Middle East
DXB) echo "Dubai" ;;
TLV) echo "Tel Aviv" ;;
# South America
GRU) echo "São Paulo" ;;
EZE) echo "Buenos Aires" ;;
# If unknown, show the code
*) echo "$code" ;;
esac
}
get_cloudflare_location() {
local domain="$1"
# Get CF-RAY header which contains datacenter code
local cf_ray=$(curl -sI -m 3 "http://$domain" 2>/dev/null | grep -i "CF-RAY:" | head -1 | awk '{print $2}' | tr -d '\r')
if [ -z "$cf_ray" ]; then
echo "N/A"
return 1
fi
# Extract datacenter code (last 3 characters, e.g., "ORD" from "9c1b340e5cdacc3a-ORD")
local colo=$(echo "$cf_ray" | awk -F'-' '{print $NF}')
if [ -n "$colo" ]; then
# Return city name instead of code
local city=$(get_location_name "$colo")
echo "$city"
return 0
fi
echo "N/A"
return 1
}
domain_resolves() {
local domain="$1"
# Check if domain has any A records
local ip=$(dig +short A "$domain" 2>/dev/null | head -1)
# Also check AAAA for IPv6-only domains
if [ -z "$ip" ]; then
ip=$(dig +short AAAA "$domain" 2>/dev/null | head -1)
fi
# Return 0 if domain resolves, 1 if it doesn't
[ -n "$ip" ]
}
detect_cloudflare() {
local domain="$1"
# Skip domains that don't resolve at all
if ! domain_resolves "$domain"; then
echo "NXDOMAIN"
return 3
fi
local ns_result=$(check_nameservers "$domain")
local ip_result=$(check_ip_address "$domain")
local http_result=$(check_http_headers "$domain")
# PROXIED (Orange Cloud): Traffic goes through Cloudflare CDN
# Confirmed by: IP in Cloudflare range OR CF-RAY header present
# This is what most people mean by "using Cloudflare"
if [ "$ip_result" = "CLOUDFLARE" ] || [ "$http_result" = "CLOUDFLARE" ]; then
echo "CLOUDFLARE"
return 0
fi
# DNS-ONLY (Gray Cloud): Using Cloudflare nameservers but traffic is direct
# These domains use CF for DNS management but NOT for CDN/caching/protection
if [ "$ns_result" = "CLOUDFLARE" ] && [ "$ip_result" = "DIRECT" ]; then
echo "DNS-ONLY"
return 4
fi
# DIRECT: Not using Cloudflare at all
if [ "$ns_result" = "OTHER" ] && [ "$ip_result" = "DIRECT" ] && [ "$http_result" = "DIRECT" ]; then
echo "DIRECT"
return 1
fi
# Otherwise, uncertain
echo "UNKNOWN"
return 2
}
################################################################################
# Main Functions
################################################################################
scan_all_domains() {
print_banner "Cloudflare Detection Scan"
echo "Scanning all domains on this server..."
echo "Checking: Nameservers, IP addresses, HTTP headers"
echo ""
# Get all domains
local domains=$(get_domains)
if [ -z "$domains" ]; then
print_error "No domains found on this server"
echo ""
echo "This tool requires cPanel with configured domains"
press_enter
return 1
fi
local domain_count=$(echo "$domains" | wc -l)
echo "Found $domain_count domains to check..."
echo ""
sleep 1
# Arrays to store results
local -a cloudflare_domains=()
local -a cloudflare_locations=()
local -a dns_only_domains=()
local -a direct_domains=()
local -a unknown_domains=()
local -a nxdomain_domains=()
# Progress tracking
local current=0
while IFS= read -r domain; do
current=$((current + 1))
# Show progress
printf "\r[%d/%d] Checking: %-50s" "$current" "$domain_count" "$domain"
# Detect Cloudflare
local result=$(detect_cloudflare "$domain")
case "$result" in
"CLOUDFLARE")
cloudflare_domains+=("$domain")
# Get Cloudflare datacenter location
local location=$(get_cloudflare_location "$domain")
cloudflare_locations+=("$location")
;;
"DNS-ONLY")
dns_only_domains+=("$domain")
;;
"DIRECT")
direct_domains+=("$domain")
;;
"NXDOMAIN")
nxdomain_domains+=("$domain")
;;
*)
unknown_domains+=("$domain")
;;
esac
done <<< "$domains"
echo ""
echo ""
# Display results
echo "═══════════════════════════════════════════════════════════════"
echo " SCAN RESULTS"
echo "═══════════════════════════════════════════════════════════════"
echo ""
# Cloudflare proxied domains (orange cloud)
if [ ${#cloudflare_domains[@]} -gt 0 ]; then
print_success "🔶 Cloudflare Proxied (Orange Cloud): ${#cloudflare_domains[@]}"
echo " Traffic routed through Cloudflare CDN - caching, DDoS protection, etc."
echo ""
for i in "${!cloudflare_domains[@]}"; do
local domain="${cloudflare_domains[$i]}"
local location="${cloudflare_locations[$i]}"
if [ "$location" != "N/A" ]; then
printf " ✓ %-50s [%s]\n" "$domain" "$location"
else
echo "$domain"
fi
done
echo ""
else
echo "🔶 Domains using Cloudflare: 0"
echo ""
fi
# DNS-only domains (gray cloud)
if [ ${#dns_only_domains[@]} -gt 0 ]; then
print_warning "☁️ Cloudflare DNS-Only (Gray Cloud): ${#dns_only_domains[@]}"
echo ""
echo " These domains use Cloudflare nameservers but traffic goes DIRECT to your server."
echo " Not using: CDN caching, DDoS protection, or Cloudflare features."
echo ""
for domain in "${dns_only_domains[@]}"; do
echo "$domain"
done
echo ""
fi
# Direct domains
if [ ${#direct_domains[@]} -gt 0 ]; then
print_info "🌐 Domains NOT using Cloudflare: ${#direct_domains[@]}"
echo ""
for domain in "${direct_domains[@]}"; do
echo "$domain"
done
echo ""
else
echo "🌐 Domains NOT using Cloudflare: 0"
echo ""
fi
# NXDOMAIN domains (don't resolve)
if [ ${#nxdomain_domains[@]} -gt 0 ]; then
print_warning "⚠ Domains that don't resolve (NXDOMAIN): ${#nxdomain_domains[@]}"
echo ""
for domain in "${nxdomain_domains[@]}"; do
echo "$domain"
done
echo ""
echo " 💡 Tip: These domains are configured in cPanel but don't have DNS records."
echo " Consider removing them or checking your DNS configuration."
echo ""
fi
# Unknown domains
if [ ${#unknown_domains[@]} -gt 0 ]; then
print_warning "❓ Uncertain (DNS/connectivity issues): ${#unknown_domains[@]}"
echo ""
for domain in "${unknown_domains[@]}"; do
echo " ? $domain"
done
echo ""
fi
echo "═══════════════════════════════════════════════════════════════"
echo ""
echo "Summary:"
echo " Total domains: $domain_count"
echo " Cloudflare Proxy: ${#cloudflare_domains[@]} (orange cloud - using CDN/caching)"
if [ ${#dns_only_domains[@]} -gt 0 ]; then
echo " Cloudflare DNS: ${#dns_only_domains[@]} (gray cloud - DNS only)"
fi
echo " Direct: ${#direct_domains[@]} (not using Cloudflare)"
if [ ${#nxdomain_domains[@]} -gt 0 ]; then
echo " NXDOMAIN: ${#nxdomain_domains[@]} (don't resolve)"
fi
if [ ${#unknown_domains[@]} -gt 0 ]; then
echo " Unknown: ${#unknown_domains[@]}"
fi
echo ""
press_enter
}
check_single_domain() {
print_banner "Check Single Domain"
echo ""
read -p "Enter domain name to check: " domain
if [ -z "$domain" ]; then
print_error "No domain entered"
press_enter
return 1
fi
echo ""
echo "Checking: $domain"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
# Check if domain resolves
if ! domain_resolves "$domain"; then
print_error "✗ Domain does not resolve (NXDOMAIN)"
echo ""
echo "This domain has no DNS A or AAAA records."
echo "It may be misconfigured, deleted, or not yet propagated."
echo ""
press_enter
return 1
fi
# Check nameservers
print_info "1. Nameserver Check:"
local ns_result=$(check_nameservers "$domain")
local nameservers=$(dig +short NS "$domain" 2>/dev/null)
echo " Nameservers:"
if [ -n "$nameservers" ]; then
echo "$nameservers" | sed 's/^/ /'
else
echo " (none found)"
fi
echo " Status: $ns_result"
echo ""
# Check IP address
print_info "2. IP Address Check:"
local ip=$(dig +short A "$domain" 2>/dev/null | head -1)
if [ -n "$ip" ]; then
echo " IP: $ip"
local ip_result=$(check_ip_address "$domain")
echo " Status: $ip_result"
else
echo " (no A record found)"
fi
echo ""
# Check HTTP headers
print_info "3. HTTP Header Check:"
echo " Testing: http://$domain"
local http_result=$(check_http_headers "$domain")
echo " Status: $http_result"
# Get location if using Cloudflare
if [ "$http_result" = "CLOUDFLARE" ]; then
local location=$(get_cloudflare_location "$domain")
echo " Datacenter: $location"
fi
echo ""
# Final result
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
local final_result=$(detect_cloudflare "$domain")
case "$final_result" in
"CLOUDFLARE")
local location=$(get_cloudflare_location "$domain")
if [ "$location" != "N/A" ]; then
print_success "$domain is PROXIED through Cloudflare (Datacenter: $location)"
else
print_success "$domain is PROXIED through Cloudflare"
fi
echo ""
echo " 🔶 Orange Cloud: Traffic goes through Cloudflare CDN"
echo " Benefits: Caching, DDoS protection, firewall, etc."
;;
"DNS-ONLY")
print_warning "$domain uses Cloudflare DNS-ONLY (Gray Cloud)"
echo ""
echo " ☁️ Traffic goes DIRECT to your server (not proxied)"
echo " Using: Cloudflare nameservers for DNS management"
echo " NOT using: CDN caching, DDoS protection, firewall"
echo ""
echo " 💡 Tip: To enable full Cloudflare protection, set to 'Proxied'"
echo " (orange cloud) in your Cloudflare DNS settings."
;;
"DIRECT")
print_info "$domain is NOT using Cloudflare"
;;
*)
print_warning "? Could not determine Cloudflare status for $domain"
;;
esac
echo ""
press_enter
}
show_info() {
print_banner "About Cloudflare Detection"
echo ""
echo "How Detection Works:"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "This tool checks three indicators to detect Cloudflare:"
echo ""
echo " 1. Nameservers:"
echo " Checks if domain uses Cloudflare nameservers"
echo " (e.g., ns1.cloudflare.com)"
echo ""
echo " 2. IP Address:"
echo " Checks if A record points to Cloudflare IP range"
echo " (e.g., 104.16.x.x, 172.64.x.x, etc.)"
echo ""
echo " 3. HTTP Headers:"
echo " Checks for CF-RAY header in HTTP response"
echo " (definitive proof of Cloudflare proxy)"
echo ""
echo " 4. Datacenter Location:"
echo " Extracts IATA airport code from CF-RAY header"
echo " (e.g., ORD=Chicago, LAX=Los Angeles, LHR=London)"
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "Why This Matters:"
echo ""
echo " • Cloudflare domains hide origin server IP"
echo " • Useful for cache debugging (Cloudflare vs Varnish)"
echo " • Important for firewall/security configuration"
echo " • Helps identify which domains get CDN benefits"
echo " • Datacenter location useful for debugging regional outages"
echo ""
echo "Common Datacenter Codes:"
echo " ORD - Chicago | LAX - Los Angeles | IAD - Virginia"
echo " DFW - Dallas | ATL - Atlanta | SEA - Seattle"
echo " LHR - London | FRA - Frankfurt | AMS - Amsterdam"
echo " SYD - Sydney | SIN - Singapore | NRT - Tokyo"
echo ""
press_enter
}
################################################################################
# Main Menu
################################################################################
show_menu() {
clear
print_banner "Cloudflare Domain Detector"
echo ""
echo -e "${BOLD}Scan Options:${NC}"
echo ""
echo " 1) Scan All Domains - Check all domains on this server"
echo " 2) Check Single Domain - Detailed check for one domain"
echo " 3) About / How It Works - Detection methodology"
echo ""
echo " 0) Back to Website Menu"
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo -n "Select option: "
}
main() {
while true; do
show_menu
read -r choice
case $choice in
1) scan_all_domains ;;
2) check_single_domain ;;
3) show_info ;;
0)
clear
exit 0
;;
*)
echo ""
print_error "Invalid option"
sleep 1
;;
esac
done
}
# Run main menu
main