#!/usr/bin/env bash
# author: Saulo José de Lucas Silva
# license: MIT
# --- Configurações Globais e Variáveis ---
# Habilita o modo de depuração se DEBUG_MODE estiver definido como 'true'
if [[ "${DEBUG_MODE:-false}" == "true" ]]; then
set -x
export PS4='+ ${BASH_SOURCE}:${LINENO}: ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
fi
set -euo pipefail
readonly SCRIPT_NAME="${0##*/}"
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly VERSION="1.3"
readonly TEST_TARGET_IP="${TEST_TARGET_IP:-8.8.8.8}"
readonly TEST_DURATION="${TEST_DURATION:-10}"
readonly IPERF3_SERVER_IP="${IPERF3_SERVER_IP:-191.252.218.222}"
readonly CLIENT_WIFI_IFACE="${CLIENT_WIFI_IFACE:-wlan0}"
readonly PING_COUNT="${PING_COUNT:-20}"
readonly MTR_CYCLES="${MTR_CYCLES:-15}"
readonly IPERF3_PARALLEL_STREAMS="${IPERF3_PARALLEL_STREAMS:-5}"
readonly UDP_BANDWIDTH="${UDP_BANDWIDTH:-10M}"
readonly SECURITY_SCAN_TIMEOUT="${SECURITY_SCAN_TIMEOUT:-30}"
readonly RESULTS_DIR="${RESULTS_DIR:-/tmp/hotspot_results}"
readonly LOG_FILE="${LOG_FILE:-$RESULTS_DIR/hotspot_test_latest.log}"
CURRENT_SESSION_LOG="$RESULTS_DIR/hotspot_test_$(date +%Y%m%d_%H%M%S).log"
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly BLUE='\033[0;34m'
readonly PURPLE='\033[0;35m'
readonly CYAN='\033[0;36m'
readonly NC='\033[0m'
# --- Funções de Log e Utilidades ---
write_to_log() {
local level="$1"
local message="$2"
local timestamp="$(date '+%Y-%m-%d %H:%M:%S')"
local log_entry="$timestamp $level: $message"
local log_dir
log_dir="$(dirname "${ACTIVE_LOG:-$LOG_FILE}")"
[[ ! -d "$log_dir" ]] && mkdir -p "$log_dir"
echo "$log_entry" >> "${ACTIVE_LOG:-$LOG_FILE}" 2>/dev/null || {
echo "AVISO: Falha ao escrever no arquivo de log ${ACTIVE_LOG:-$LOG_FILE}" >&2
return 1
}
}
log_info() {
local message="[+] $1"
echo -e "${GREEN}${message}${NC}"
write_to_log "INFO" "$1"
}
log_warning() {
local message="[!] $1"
echo -e "${YELLOW}${message}${NC}"
write_to_log "WARN" "$1"
}
log_error() {
local message="[x] $1"
echo -e "${RED}${message}${NC}"
write_to_log "ERROR" "$1"
}
log_debug() {
if [[ "${DEBUG_MODE:-false}" == "true" ]]; then
local message="[DEBUG] $1"
echo -e "${BLUE}${message}${NC}"
write_to_log "DEBUG" "$1"
fi
}
log_test_result() {
local test_name="$1"
local action="$2"
local command="$3"
local status="$4"
local details="${5:-}"
local color="$RED"
[[ "$status" == "PASS" ]] && color="$GREEN"
[[ "$status" == "WARN" ]] && color="$YELLOW"
[[ "$status" == "INFO" ]] && color="$CYAN"
printf "${PURPLE}%-25s${NC} [${color}%-4s${NC}] %s\n" "$test_name" "$status" "$details"
write_to_log "RESULT" "$test_name|$action|$command|$status|$details"
}
find_latest_log_file() {
local latest_log=""
if [[ -f "$LOG_FILE" ]]; then
echo "$LOG_FILE"
return 0
fi
if [[ -d "$RESULTS_DIR" ]]; then
latest_log=$(find "$RESULTS_DIR" -name "hotspot_test_*.log" -type f -printf '%T@ %p\n' 2>/dev/null | sort -n | tail -1 | cut -d' ' -f2-)
if [[ -n "$latest_log" && -f "$latest_log" ]]; then
echo "$latest_log"
return 0
fi
fi
return 1
}
show_help() {
cat <<EOF
${SCRIPT_NAME} - Suíte de Testes para Cliente Hotspot
DESCRIÇÃO:
Ferramentas abrangentes de teste de rede para validação de cliente hotspot incluindo
verificações de conectividade, medições de latência, testes de throughput, análise de segurança,
descoberta de dispositivos de rede e métricas de qualidade com relatórios avançados e visualização.
USO:
${SCRIPT_NAME} [COMANDO]
COMANDOS:
install Instalar ferramentas de teste necessárias (iperf3, mtr, nmap, etc.)
check-conn Verificar status da conexão WiFi e conectividade básica
test-ping Executar testes de latência ping e perda de pacotes
test-mtr Executar análise de caminho de rede MTR
test-iperf3 Executar testes de throughput iperf3 (requer servidor)
test-quality Executar testes abrangentes de qualidade de conexão
test-devices Escanear dispositivos conectados ao mesmo ponto de acesso
test-security Executar avaliação de vulnerabilidade de segurança
test-performance Executar benchmarks de performance estendidos
test-all Executar suíte completa de testes
generate-report Gerar relatório detalhado de testes com gráficos
help Mostrar esta mensagem de ajuda
VARIÁVEIS DE AMBIENTE:
TEST_TARGET_IP IP de destino para testes ping/mtr (padrão: 8.8.8.8)
TEST_DURATION Duração para testes iperf3 em segundos (padrão: 10)
IPERF3_SERVER_IP Endereço IP do servidor iperf3 (obrigatório para testes de throughput)
CLIENT_WIFI_IFACE Nome da interface WiFi (padrão: wlan0)
PING_COUNT Número de pacotes ping (padrão: 20)
MTR_CYCLES Número de ciclos de teste MTR (padrão: 15)
IPERF3_PARALLEL_STREAMS Streams paralelos para iperf3 (padrão: 5)
UDP_BANDWIDTH Largura de banda do teste UDP (padrão: 10M)
SECURITY_SCAN_TIMEOUT Timeout do scan de segurança em segundos (padrão: 30)
LOG_FILE Caminho para arquivo de log
RESULTS_DIR Diretório para resultados dos testes
DEBUG_MODE Habilitar saída de debug: true/false (padrão: false)
EXEMPLOS:
# Instalar ferramentas de teste
sudo ${SCRIPT_NAME} install
# Verificar conectividade básica
${SCRIPT_NAME} check-conn
# Escanear dispositivos de rede
${SCRIPT_NAME} test-devices
# Executar suíte completa de testes com servidor iperf3 customizado
IPERF3_SERVER_IP=192.168.1.100 ${SCRIPT_NAME} test-all
# Gerar relatório aprimorado com gráficos
${SCRIPT_NAME} generate-report
EOF
}
# --- Escalonamento de privilégios ---
execute_with_privileges() {
local cmd="$1"
shift
if [[ $EUID -eq 0 ]]; then
log_debug "Executando como root: $cmd $*"
"$cmd" "$@"
else
log_debug "Solicitando sudo para: $cmd $*"
sudo "$cmd" "$@"
fi
}
write_to_log() {
local level="$1"
local message="$2"
local timestamp="$(date '+%Y-%m-%d %H:%M:%S')"
local log_entry="$timestamp $level: $message"
local log_dir
log_dir="$(dirname "${ACTIVE_LOG:-$LOG_FILE}")"
[[ ! -d "$log_dir" ]] && mkdir -p "$log_dir"
echo "$log_entry" >> "${ACTIVE_LOG:-$LOG_FILE}" 2>/dev/null || {
echo "AVISO: Falha ao escrever no arquivo de log ${ACTIVE_LOG:-$LOG_FILE}" >&2
return 1
}
}
validate_tools_available() {
local missing_tools=()
for tool in "$@"; do
if ! command -v "$tool" &>/dev/null; then
missing_tools+=("$tool")
fi
done
if [[ ${#missing_tools[@]} -gt 0 ]]; then
log_error "Ferramentas obrigatórias ausentes: ${missing_tools[*]}"
log_error "Execute '${SCRIPT_NAME} install' para instalar dependências"
return 1
fi
log_debug "Todas as ferramentas obrigatórias disponíveis: $*"
return 0
}
validate_interface() {
local interface="$1"
if ! ip link show "$interface" &>/dev/null; then
log_error "Interface de rede '$interface' não encontrada"
log_error "Interfaces disponíveis: $(ip -o link show | awk -F': ' '{print $2}' | tr '\n' ' ')"
return 1
fi
log_debug "Interface '$interface' validada com sucesso"
return 0
}
setup_test_environment() {
log_debug "DEBUG: Entrando em setup_test_environment" >&2
log_info "Configurando ambiente de teste"
log_debug "DEBUG: Criando diretório: $RESULTS_DIR" >&2
if ! mkdir -p "$RESULTS_DIR"; then
log_error "Falha ao criar diretório de resultados: $RESULTS_DIR"
log_debug "DEBUG: mkdir falhou para $RESULTS_DIR" >&2
return 1
fi
local target_log="${ACTIVE_LOG:-$LOG_FILE}"
log_debug "DEBUG: Criando arquivo de log: $target_log" >&2
if ! touch "$target_log"; then
log_error "Falha ao criar arquivo de log: $target_log"
log_debug "DEBUG: touch falhou para $target_log" >&2
return 1
fi
log_info "Ambiente de teste inicializado"
log_info "Arquivo de log: $target_log"
log_info "Diretório de resultados: $RESULTS_DIR"
log_debug "DEBUG: setup_test_environment concluído com sucesso" >&2
return 0
}
# --- Configuração do Sistema ---
install_testing_tools() {
log_info "Instalando ferramentas de teste de rede"
local packages=(
"iperf3"
"mtr"
"traceroute"
"netcat-openbsd"
"speedtest-cli"
"wireless-tools"
"iw"
"nmap"
"tcpdump"
"aircrack-ng"
"wavemon"
"ethtool"
"dnsutils"
"net-tools"
"arp-scan"
"wireshark-common"
)
execute_with_privileges apt update || {
log_error "Falha ao atualizar listas de pacotes"
return 1
}
execute_with_privileges apt install -y "${packages[@]}" || {
log_error "Falha ao instalar ferramentas de teste"
log_error "Verifique a conexão com a internet e os repositórios de pacotes"
return 1
}
log_info "Ferramentas de teste instaladas com sucesso"
return 0
}
# --- Testes de Rede ---
check_wifi_connection() {
log_info "Verificando status da conexão WiFi"
validate_interface "$CLIENT_WIFI_IFACE" || return 1
if ! ip link show dev "$CLIENT_WIFI_IFACE" | grep -q "state UP"; then
log_test_result "Interface" "Verificação de Status" "ip link show $CLIENT_WIFI_IFACE" "FAIL" "$CLIENT_WIFI_IFACE está INATIVO"
return 1
fi
log_test_result "Interface" "Verificação de Status" "ip link show $CLIENT_WIFI_IFACE" "PASS" "$CLIENT_WIFI_IFACE está ATIVO"
local ip_addr
ip_addr=$(ip -4 addr show dev "$CLIENT_WIFI_IFACE" | grep -oP 'inet \K[\d.]+' | head -1)
if [[ -z "$ip_addr" ]]; then
log_test_result "IP" "Verificação de Atribuição" "ip addr show $CLIENT_WIFI_IFACE" "FAIL" "Nenhum endereço IP atribuído"
return 1
fi
log_test_result "IP" "Verificação de Atribuição" "ip addr show $CLIENT_WIFI_IFACE" "PASS" "IP: $ip_addr"
# Check if IP is in private range
if [[ "$ip_addr" =~ ^10\. ]] || [[ "$ip_addr" =~ ^192\.168\. ]] || [[ "$ip_addr" =~ ^172\.(1[6-9]|2[0-9]|3[0-1])\. ]]; then
log_test_result "IP" "Análise de Faixa" "ip addr show $CLIENT_WIFI_IFACE" "PASS" "Faixa de IP privado detectada"
else
log_test_result "IP" "Análise de Faixa" "ip addr show $CLIENT_WIFI_IFACE" "WARN" "IP público detectado - potencial risco de segurança"
fi
local gateway
gateway=$(ip route show default | grep "$CLIENT_WIFI_IFACE" | awk '{print $3}' | head -1)
if [[ -z "$gateway" ]]; then
log_test_result "Gateway" "Detecção" "ip route show default" "WARN" "Nenhum gateway padrão encontrado"
else
log_test_result "Gateway" "Detecção" "ip route show default" "PASS" "Gateway: $gateway"
# Test gateway connectivity
if ping -c 2 -W 3 "$gateway" &>/dev/null; then
log_test_result "Gateway" "Conectividade" "ping -c 2 $gateway" "PASS" "Gateway acessível"
else
log_test_result "Gateway" "Conectividade" "ping -c 2 $gateway" "WARN" "Gateway não responde ao ping"
fi
fi
local dns_servers
dns_servers=$(grep -E "^nameserver" /etc/resolv.conf | awk '{print $2}' | tr '\n' ' ')
if [[ -z "$dns_servers" ]]; then
log_test_result "DNS" "Configuração" "cat /etc/resolv.conf" "WARN" "Nenhum servidor DNS configurado"
else
log_test_result "DNS" "Configuração" "cat /etc/resolv.conf" "PASS" "DNS: $dns_servers"
# Test DNS resolution
if nslookup google.com &>/dev/null; then
log_test_result "DNS" "Resolução" "nslookup google.com" "PASS" "Resolução DNS funcionando"
else
log_test_result "DNS" "Resolução" "nslookup google.com" "FAIL" "Resolução DNS falhou"
fi
fi
if ping -c 2 -W 3 "$TEST_TARGET_IP" &>/dev/null; then
log_test_result "Internet" "Conectividade" "ping -c 2 $TEST_TARGET_IP" "PASS" "Pode alcançar $TEST_TARGET_IP"
return 0
else
log_test_result "Internet" "Conectividade" "ping -c 2 $TEST_TARGET_IP" "FAIL" "Não pode alcançar $TEST_TARGET_IP"
return 1
fi
}
execute_ping_tests() {
log_info "Executando testes de latência e perda de pacotes ping"
validate_tools_available "ping" || return 1
local ping_output
local ping_file="$RESULTS_DIR/ping_results_$(date +%H%M%S).txt"
log_info "Testando conectividade para $TEST_TARGET_IP com $PING_COUNT pacotes"
if ! ping_output=$(ping -c "$PING_COUNT" -i 0.5 "$TEST_TARGET_IP" 2>&1); then
log_test_result "Ping" "Teste de Conectividade" "ping -c $PING_COUNT $TEST_TARGET_IP" "FAIL" "Não foi possível fazer ping para $TEST_TARGET_IP"
return 1
fi
echo "$ping_output" > "$ping_file"
local packet_loss
packet_loss=$(echo "$ping_output" | grep -oP '\d+(?=% packet loss)')
local avg_latency
avg_latency=$(echo "$ping_output" | grep -oP 'rtt min/avg/max/mdev = [\d.]+/\K[\d.]+')
local min_latency max_latency mdev_latency
if [[ -n "$avg_latency" ]]; then
min_latency=$(echo "$ping_output" | grep -oP 'rtt min/avg/max/mdev = \K[\d.]+')
max_latency=$(echo "$ping_output" | grep -oP 'rtt min/avg/max/mdev = [\d.]+/[\d.]+/\K[\d.]+')
mdev_latency=$(echo "$ping_output" | grep -oP 'rtt min/avg/max/mdev = [\d.]+/[\d.]+/[\d.]+/\K[\d.]+')
fi
if [[ -n "$packet_loss" ]]; then
if [[ "$packet_loss" -eq 0 ]]; then
log_test_result "Pacote" "Teste de Perda" "ping -c $PING_COUNT $TEST_TARGET_IP" "PASS" "${packet_loss}%"
elif [[ "$packet_loss" -le 2 ]]; then
log_test_result "Pacote" "Teste de Perda" "ping -c $PING_COUNT $TEST_TARGET_IP" "WARN" "${packet_loss}% - Aceitável mas monitore"
elif [[ "$packet_loss" -le 5 ]]; then
log_test_result "Pacote" "Teste de Perda" "ping -c $PING_COUNT $TEST_TARGET_IP" "WARN" "${packet_loss}% - Qualidade de conexão ruim"
else
log_test_result "Pacote" "Teste de Perda" "ping -c $PING_COUNT $TEST_TARGET_IP" "FAIL" "${packet_loss}% - Perda crítica de pacotes"
fi
fi
if [[ -n "$avg_latency" ]]; then
local latency_status="PASS"
local latency_desc="Excelente"
if [[ $(echo "$avg_latency > 50" | bc -l 2>/dev/null || echo 0) -eq 1 ]]; then
latency_status="WARN"
latency_desc="Aceitável"
fi
if [[ $(echo "$avg_latency > 100" | bc -l 2>/dev/null || echo 0) -eq 1 ]]; then
latency_status="WARN"
latency_desc="Latência alta"
fi
if [[ $(echo "$avg_latency > 300" | bc -l 2>/dev/null || echo 0) -eq 1 ]]; then
latency_status="FAIL"
latency_desc="Latência crítica"
fi
log_test_result "Média" "Teste de Latência" "ping -c $PING_COUNT $TEST_TARGET_IP" "$latency_status" "${avg_latency}ms - $latency_desc"
log_test_result "Latência" "Análise de Faixa" "ping -c $PING_COUNT $TEST_TARGET_IP" "INFO" "min/max/mdev: ${min_latency}/${max_latency}/${mdev_latency}ms"
# Jitter analysis
if [[ -n "$mdev_latency" ]]; then
if [[ $(echo "$mdev_latency > 10" | bc -l 2>/dev/null || echo 0) -eq 1 ]]; then
log_test_result "Jitter" "Análise" "ping -c $PING_COUNT $TEST_TARGET_IP" "WARN" "${mdev_latency}ms - Alto jitter detectado"
else
log_test_result "Jitter" "Análise" "ping -c $PING_COUNT $TEST_TARGET_IP" "PASS" "${mdev_latency}ms - Baixo jitter"
fi
fi
fi
log_info "Resultados do ping salvos em: $ping_file"
return 0
}
execute_mtr_tests() {
log_info "Executando análise de caminho de rede MTR"
validate_tools_available "mtr" || return 1
local mtr_file="$RESULTS_DIR/mtr_results_$(date +%H%M%S).txt"
log_info "Analisando caminho de rede para $TEST_TARGET_IP ($MTR_CYCLES ciclos)"
if ! mtr --report --report-cycles "$MTR_CYCLES" --no-dns "$TEST_TARGET_IP" > "$mtr_file" 2>&1; then
log_test_result "MTR" "Análise de Caminho" "mtr --report --report-cycles $MTR_CYCLES $TEST_TARGET_IP" "FAIL" "Análise MTR falhou"
return 1
fi
local hop_count
hop_count=$(grep -c "^ *[0-9]" "$mtr_file" || echo 0)
local worst_loss
worst_loss=$(awk '/^ *[0-9]/ {gsub(/%/, "", $3); if($3 > max) max=$3} END {print max+0}' "$mtr_file")
local final_avg
final_avg=$(tail -1 "$mtr_file" | awk '{print $6}')
log_test_result "Rede" "Contagem de Saltos" "mtr --report $TEST_TARGET_IP" "INFO" "$hop_count saltos até o destino"
if [[ "$hop_count" -gt 20 ]]; then
log_test_result "Rota" "Eficiência" "mtr --report $TEST_TARGET_IP" "WARN" "Muitos saltos detectados - roteamento pode ser subótimo"
elif [[ "$hop_count" -gt 15 ]]; then
log_test_result "Rota" "Eficiência" "mtr --report $TEST_TARGET_IP" "WARN" "Contagem moderada de saltos - roteamento aceitável"
else
log_test_result "Rota" "Eficiência" "mtr --report $TEST_TARGET_IP" "PASS" "Roteamento eficiente detectado"
fi
if [[ -n "$worst_loss" ]] && [[ "$worst_loss" != "0" ]]; then
local loss_status="WARN"
[[ "$worst_loss" -gt 10 ]] && loss_status="FAIL"
log_test_result "Pior Salto" "Análise de Perda" "mtr --report $TEST_TARGET_IP" "$loss_status" "${worst_loss}% perda de pacotes"
else
log_test_result "Caminho" "Análise de Perda" "mtr --report $TEST_TARGET_IP" "PASS" "Nenhuma perda de pacotes detectada"
fi
if [[ -n "$final_avg" ]]; then
local end_to_end_status="PASS"
if [[ $(echo "$final_avg > 100" | bc -l 2>/dev/null || echo 0) -eq 1 ]]; then
end_to_end_status="WARN"
fi
if [[ $(echo "$final_avg > 300" | bc -l 2>/dev/null || echo 0) -eq 1 ]]; then
end_to_end_status="FAIL"
fi
log_test_result "Ponta-a-Ponta" "Latência" "mtr --report $TEST_TARGET_IP" "$end_to_end_status" "${final_avg}ms média"
fi
log_info "Resultados MTR salvos em: $mtr_file"
return 0
}
execute_iperf3_tests() {
log_info "Executando testes de throughput iperf3"
validate_tools_available "iperf3" || return 1
if [[ -z "$IPERF3_SERVER_IP" ]]; then
log_error "IPERF3_SERVER_IP não configurado"
log_error "Defina a variável de ambiente: export IPERF3_SERVER_IP=ip_do_servidor"
return 1
fi
local iperf3_dir="$RESULTS_DIR/iperf3_$(date +%H%M%S)"
mkdir -p "$iperf3_dir"
log_info "Testando throughput para $IPERF3_SERVER_IP (${TEST_DURATION}s de duração)"
# TCP Download Test
local tcp_down_file="$iperf3_dir/tcp_download.json"
if iperf3 -c "$IPERF3_SERVER_IP" -t "$TEST_DURATION" -J > "$tcp_down_file" 2>&1; then
local tcp_down_mbps
tcp_down_mbps=$(jq -r '.end.sum_received.bits_per_second' "$tcp_down_file" 2>/dev/null | awk '{printf "%.2f", $1/1000000}')
if [[ "$tcp_down_mbps" != "null" ]]; then
local down_status="PASS"
if [[ $(echo "$tcp_down_mbps < 10" | bc -l 2>/dev/null || echo 0) -eq 1 ]]; then
down_status="WARN"
fi
if [[ $(echo "$tcp_down_mbps < 1" | bc -l 2>/dev/null || echo 0) -eq 1 ]]; then
down_status="FAIL"
fi
log_test_result "TCP" "Teste de Download" "iperf3 -c $IPERF3_SERVER_IP -t $TEST_DURATION" "$down_status" "${tcp_down_mbps} Mbps"
fi
else
log_test_result "TCP" "Teste de Download" "iperf3 -c $IPERF3_SERVER_IP -t $TEST_DURATION" "FAIL" "Teste falhou"
fi
# TCP Upload Test
local tcp_up_file="$iperf3_dir/tcp_upload.json"
if iperf3 -c "$IPERF3_SERVER_IP" -t "$TEST_DURATION" -R -J > "$tcp_up_file" 2>&1; then
local tcp_up_mbps
tcp_up_mbps=$(jq -r '.end.sum_sent.bits_per_second' "$tcp_up_file" 2>/dev/null | awk '{printf "%.2f", $1/1000000}')
if [[ "$tcp_up_mbps" != "null" ]]; then
local up_status="PASS"
if [[ $(echo "$tcp_up_mbps < 5" | bc -l 2>/dev/null || echo 0) -eq 1 ]]; then
up_status="WARN"
fi
if [[ $(echo "$tcp_up_mbps < 1" | bc -l 2>/dev/null || echo 0) -eq 1 ]]; then
up_status="FAIL"
fi
log_test_result "TCP" "Teste de Upload" "iperf3 -c $IPERF3_SERVER_IP -t $TEST_DURATION -R" "$up_status" "${tcp_up_mbps} Mbps"
fi
else
log_test_result "TCP" "Teste de Upload" "iperf3 -c $IPERF3_SERVER_IP -t $TEST_DURATION -R" "FAIL" "Teste falhou"
fi
# UDP Test
local udp_file="$iperf3_dir/udp_test.json"
if iperf3 -c "$IPERF3_SERVER_IP" -u -b "$UDP_BANDWIDTH" -t "$TEST_DURATION" -J > "$udp_file" 2>&1; then
local udp_loss
udp_loss=$(jq -r '.end.sum.lost_percent' "$udp_file" 2>/dev/null)
if [[ "$udp_loss" != "null" ]]; then
local udp_status="PASS"
if [[ $(echo "$udp_loss > 0.5" | bc -l 2>/dev/null || echo 0) -eq 1 ]]; then
udp_status="WARN"
fi
if [[ $(echo "$udp_loss > 2" | bc -l 2>/dev/null || echo 0) -eq 1 ]]; then
udp_status="FAIL"
fi
log_test_result "UDP" "Teste de Perda" "iperf3 -c $IPERF3_SERVER_IP -u -b $UDP_BANDWIDTH" "$udp_status" "${udp_loss}%"
fi
else
log_test_result "UDP" "Teste de Perda" "iperf3 -c $IPERF3_SERVER_IP -u -b $UDP_BANDWIDTH" "FAIL" "Teste falhou"
fi
# Parallel TCP Test
local parallel_file="$iperf3_dir/tcp_parallel.json"
if iperf3 -c "$IPERF3_SERVER_IP" -t "$TEST_DURATION" -P "$IPERF3_PARALLEL_STREAMS" -J > "$parallel_file" 2>&1; then
local parallel_mbps
parallel_mbps=$(jq -r '.end.sum_received.bits_per_second' "$parallel_file" 2>/dev/null | awk '{printf "%.2f", $1/1000000}')
if [[ "$parallel_mbps" != "null" ]]; then
local parallel_status="PASS"
if [[ $(echo "$parallel_mbps < 15" | bc -l 2>/dev/null || echo 0) -eq 1 ]]; then
parallel_status="WARN"
fi
log_test_result "TCP Paralelo" "Throughput ($IPERF3_PARALLEL_STREAMS streams)" "iperf3 -c $IPERF3_SERVER_IP -P $IPERF3_PARALLEL_STREAMS" "$parallel_status" "${parallel_mbps} Mbps"
fi
else
log_test_result "TCP Paralelo" "Teste de Throughput" "iperf3 -c $IPERF3_SERVER_IP -P $IPERF3_PARALLEL_STREAMS" "FAIL" "Teste falhou"
fi
log_info "Resultados iperf3 salvos em: $iperf3_dir"
return 0
}
execute_quality_tests() {
log_info "Executando avaliação de qualidade da conexão"
validate_tools_available "iw" || return 1
local quality_file="$RESULTS_DIR/quality_assessment_$(date +%H%M%S).txt"
{
echo "=== Informações do Sinal WiFi ==="
iw dev "$CLIENT_WIFI_IFACE" link 2>/dev/null || echo "Não foi possível obter informações do link"
echo ""
echo "=== Estatísticas WiFi ==="
iw dev "$CLIENT_WIFI_IFACE" station dump 2>/dev/null || echo "Não foi possível obter informações da estação"
echo ""
echo "=== Estatísticas da Interface ==="
cat "/sys/class/net/$CLIENT_WIFI_IFACE/statistics/rx_packets" 2>/dev/null | sed 's/^/Pacotes RX: /' || true
cat "/sys/class/net/$CLIENT_WIFI_IFACE/statistics/tx_packets" 2>/dev/null | sed 's/^/Pacotes TX: /' || true
cat "/sys/class/net/$CLIENT_WIFI_IFACE/statistics/rx_errors" 2>/dev/null | sed 's/^/Erros RX: /' || true
cat "/sys/class/net/$CLIENT_WIFI_IFACE/statistics/tx_errors" 2>/dev/null | sed 's/^/Erros TX: /' || true
} > "$quality_file"
local signal_level
signal_level=$(iw dev "$CLIENT_WIFI_IFACE" link 2>/dev/null | grep -oP 'signal: \K-?\d+' | head -1)
if [[ -n "$signal_level" ]]; then
local signal_status="FAIL"
local signal_desc="Sinal muito fraco"
if [[ "$signal_level" -gt -30 ]]; then
signal_status="PASS"
signal_desc="Sinal excelente"
elif [[ "$signal_level" -gt -50 ]]; then
signal_status="PASS"
signal_desc="Sinal muito bom"
elif [[ "$signal_level" -gt -60 ]]; then
signal_status="WARN"
signal_desc="Sinal bom"
elif [[ "$signal_level" -gt -70 ]]; then
signal_status="WARN"
signal_desc="Sinal razoável"
elif [[ "$signal_level" -gt -80 ]]; then
signal_status="WARN"
signal_desc="Sinal fraco"
fi
log_test_result "Sinal" "Intensidade" "iw dev $CLIENT_WIFI_IFACE link" "$signal_status" "${signal_level} dBm - $signal_desc"
else
log_test_result "Sinal" "Intensidade" "iw dev $CLIENT_WIFI_IFACE link" "WARN" "Não foi possível determinar a intensidade do sinal"
fi
# Check connection speed
local tx_bitrate
tx_bitrate=$(iw dev "$CLIENT_WIFI_IFACE" link 2>/dev/null | grep -oP 'tx bitrate: \K[\d.]+')
if [[ -n "$tx_bitrate" ]]; then
local speed_status="PASS"
local speed_desc="Velocidade boa"
if [[ $(echo "$tx_bitrate < 50" | bc -l 2>/dev/null || echo 0) -eq 1 ]]; then
speed_status="WARN"
speed_desc="Velocidade de conexão baixa"
fi
if [[ $(echo "$tx_bitrate < 20" | bc -l 2>/dev/null || echo 0) -eq 1 ]]; then
speed_status="FAIL"
speed_desc="Velocidade de conexão muito baixa"
fi
log_test_result "TX" "Taxa de Bits" "iw dev $CLIENT_WIFI_IFACE link" "$speed_status" "${tx_bitrate} Mbps - $speed_desc"
fi
local rx_errors tx_errors
rx_errors=$(cat "/sys/class/net/$CLIENT_WIFI_IFACE/statistics/rx_errors" 2>/dev/null || echo 0)
tx_errors=$(cat "/sys/class/net/$CLIENT_WIFI_IFACE/statistics/tx_errors" 2>/dev/null || echo 0)
local error_status="PASS"
local error_desc="Nenhum erro detectado"
if [[ "$rx_errors" -gt 0 || "$tx_errors" -gt 0 ]]; then
error_status="WARN"
error_desc="Alguns erros detectados - monitore a conexão"
fi
if [[ "$rx_errors" -gt 50 || "$tx_errors" -gt 50 ]]; then
error_status="WARN"
error_desc="Múltiplos erros - qualidade da conexão degradada"
fi
if [[ "$rx_errors" -gt 100 || "$tx_errors" -gt 100 ]]; then
error_status="FAIL"
error_desc="Alta taxa de erros - investigue a conexão"
fi
log_test_result "Interface" "Taxa de Erro" "cat /sys/class/net/$CLIENT_WIFI_IFACE/statistics/*_errors" "$error_status" "RX: $rx_errors, TX: $tx_errors - $error_desc"
# Check frequency and channel
local frequency
frequency=$(iw dev "$CLIENT_WIFI_IFACE" link 2>/dev/null | grep -oP 'freq: \K\d+')
if [[ -n "$frequency" ]]; then
local freq_status="INFO"
local band_desc=""
if [[ "$frequency" -lt 2500 ]]; then
band_desc="banda 2.4GHz"
freq_status="WARN"
elif [[ "$frequency" -gt 5000 ]]; then
band_desc="banda 5GHz"
freq_status="PASS"
fi
log_test_result "Frequência" "Análise de Banda" "iw dev $CLIENT_WIFI_IFACE link" "$freq_status" "${frequency} MHz - $band_desc"
fi
log_info "Avaliação de qualidade salva em: $quality_file"
return 0
}
execute_security_tests() {
log_info "Executando avaliação de vulnerabilidades de segurança"
validate_tools_available "iw" "nmap" || return 1
local security_file="$RESULTS_DIR/security_assessment_$(date +%H%M%S).txt"
local gateway
gateway=$(ip route show default | grep "$CLIENT_WIFI_IFACE" | awk '{print $3}' | head -1)
{
echo "=== Relatório de Avaliação de Segurança ==="
echo "Timestamp: $(date)"
echo "Interface: $CLIENT_WIFI_IFACE"
echo "Gateway: $gateway"
echo ""
} > "$security_file"
# Check encryption type
local ssid
ssid=$(iw dev "$CLIENT_WIFI_IFACE" link 2>/dev/null | grep -oP 'SSID: \K.*')
if [[ -n "$ssid" ]]; then
log_test_result "Rede" "Detecção de SSID" "iw dev $CLIENT_WIFI_IFACE link" "INFO" "Conectado a: $ssid"
# Scan for network security (requires sudo)
local security_info
if [[ $EUID -eq 0 ]]; then
security_info=$(timeout 10 iw dev "$CLIENT_WIFI_IFACE" scan 2>/dev/null | grep -A10 -B2 "SSID: $ssid" | grep -E "(Privacy|RSN|WPA)" | head -1)
else
security_info=$(timeout 10 sudo iw dev "$CLIENT_WIFI_IFACE" scan 2>/dev/null | grep -A10 -B2 "SSID: $ssid" | grep -E "(Privacy|RSN|WPA)" | head -1)
if [[ $? -ne 0 ]]; then
log_test_result "Criptografia" "Análise de Tipo" "sudo iw dev $CLIENT_WIFI_IFACE scan" "WARN" "Scan requer privilégios root - execute com sudo para análise completa de segurança"
security_info=""
fi
fi
if [[ -n "$security_info" ]]; then
if echo "$security_info" | grep -q "RSN"; then
log_test_result "Criptografia" "Análise de Tipo" "iw dev $CLIENT_WIFI_IFACE scan" "PASS" "Criptografia WPA2/WPA3 detectada"
elif echo "$security_info" | grep -q "WPA"; then
log_test_result "Criptografia" "Análise de Tipo" "iw dev $CLIENT_WIFI_IFACE scan" "WARN" "Criptografia WPA detectada - considere atualizar para WPA2/WPA3"
elif echo "$security_info" | grep -q "Privacy"; then
log_test_result "Criptografia" "Análise de Tipo" "iw dev $CLIENT_WIFI_IFACE scan" "FAIL" "Criptografia WEP detectada - risco de segurança"
else
log_test_result "Criptografia" "Análise de Tipo" "iw dev $CLIENT_WIFI_IFACE scan" "WARN" "Tipo de criptografia desconhecido detectado"
fi
else
log_test_result "Criptografia" "Análise de Tipo" "iw dev $CLIENT_WIFI_IFACE scan" "WARN" "Não foi possível determinar a criptografia - pode ser rede aberta ou scan falhou"
fi
fi
# Evil twin detection - scan for networks with same SSID (requires sudo)
if [[ -n "$ssid" ]]; then
local duplicate_networks=1
if [[ $EUID -eq 0 ]]; then
duplicate_networks=$(timeout 10 iw dev "$CLIENT_WIFI_IFACE" scan 2>/dev/null | grep -c "SSID: $ssid" || echo 1)
else
duplicate_networks=$(timeout 10 sudo iw dev "$CLIENT_WIFI_IFACE" scan 2>/dev/null | grep -c "SSID: $ssid" || echo 1)
if [[ $? -ne 0 ]]; then
log_test_result "Evil Twin" "Detecção" "sudo iw dev $CLIENT_WIFI_IFACE scan" "WARN" "Detecção de evil twin requer privilégios root"
duplicate_networks=1
fi
fi
if [[ "$duplicate_networks" -gt 1 ]]; then
log_test_result "Evil Twin" "Detecção" "iw dev $CLIENT_WIFI_IFACE scan" "WARN" "Múltiplas redes com mesmo SSID detectadas ($duplicate_networks) - possível evil twin"
else
log_test_result "Evil Twin" "Detecção" "iw dev $CLIENT_WIFI_IFACE scan" "PASS" "Nenhum SSID duplicado detectado"
fi
fi
# Gateway security scan
if [[ -n "$gateway" ]]; then
log_info "Escaneando gateway em busca de vulnerabilidades comuns..."
# Quick port scan
local open_ports
open_ports=$(timeout "$SECURITY_SCAN_TIMEOUT" nmap -F "$gateway" 2>/dev/null | grep -E "^[0-9]+/tcp.*open" | wc -l || echo 0)
if [[ "$open_ports" -eq 0 ]]; then
log_test_result "Gateway" "Scan de Portas" "nmap -F $gateway" "PASS" "Nenhuma porta desnecessária detectada"
elif [[ "$open_ports" -le 3 ]]; then
log_test_result "Gateway" "Scan de Portas" "nmap -F $gateway" "WARN" "$open_ports portas abertas detectadas - revise a necessidade"
else
log_test_result "Gateway" "Scan de Portas" "nmap -F $gateway" "WARN" "$open_ports portas abertas detectadas - risco potencial de segurança"
fi
# Check for common management interfaces
if timeout 5 curl -s -k "https://$gateway" | grep -q -i "router\|gateway\|admin" 2>/dev/null; then
log_test_result "Gerenciamento" "Verificação de Interface" "curl -s -k https://$gateway" "WARN" "Interface de gerenciamento web acessível - assegure autenticação forte"
elif timeout 5 curl -s "http://$gateway" | grep -q -i "router\|gateway\|admin" 2>/dev/null; then
log_test_result "Gerenciamento" "Verificação de Interface" "curl -s http://$gateway" "WARN" "Interface de gerenciamento web não criptografada detectada"
else
log_test_result "Gerenciamento" "Verificação de Interface" "curl -s http://$gateway" "PASS" "Nenhuma interface de gerenciamento óbvia exposta"
fi
fi
# DNS security check
local dns_servers
dns_servers=$(grep -E "^nameserver" /etc/resolv.conf | awk '{print $2}' | head -3)
local dns_count=0
for dns in "${dns_servers[@]}"; do
((dns_count++))
if [[ "$dns" == "8.8.8.8" || "$dns" == "8.8.4.4" || "$dns" == "1.1.1.1" || "$dns" == "1.0.0.1" ]]; then
log_test_result "Servidor DNS $dns_count" "Verificação de Segurança" "grep nameserver /etc/resolv.conf" "PASS" "Servidor DNS público em uso: $dns"
elif [[ "$dns" =~ ^192\.168\. || "$dns" =~ ^10\. || "$dns" =~ ^172\. ]]; then
log_test_result "Servidor DNS $dns_count" "Verificação de Segurança" "grep nameserver /etc/resolv.conf" "WARN" "Servidor DNS local: $dns - verifique as configurações de segurança"
elif [[ "$dns" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
log_test_result "Servidor DNS $dns_count" "Verificação de Segurança" "grep nameserver /etc/resolv.conf" "INFO" "Servidor DNS IPv4 personalizado: $dns"
else
log_test_result "Servidor DNS $dns_count" "Verificação de Segurança" "grep nameserver /etc/resolv.conf" "INFO" "Servidor DNS IPv6 ou personalizado: $dns"
fi
done
# Check for suspicious ARP entries
local arp_entries
arp_entries=$(arp -a 2>/dev/null | wc -l || echo 0)
if [[ "$arp_entries" -gt 20 ]]; then
log_test_result "ARP" "Análise de Tabela" "arp -a" "WARN" "$arp_entries entradas ARP - grande número de dispositivos na rede"
else
log_test_result "ARP" "Análise de Tabela" "arp -a" "PASS" "$arp_entries entradas ARP - tamanho normal da rede"
fi
log_info "Avaliação de segurança salva em: $security_file"
return 0
}
execute_performance_tests() {
log_info "Executando testes de desempenho estendidos"
validate_tools_available "speedtest" || return 1
local perf_file="$RESULTS_DIR/performance_test_$(date +%H%M%S).txt"
# Speedtest
log_info "Executando teste de velocidade da internet..."
local speedtest_output
if speedtest_output=$(timeout 60 speedtest --simple 2>&1); then
local download_speed upload_speed ping_result
download_speed=$(echo "$speedtest_output" | grep "Download:" | awk '{print $2}')
upload_speed=$(echo "$speedtest_output" | grep "Upload:" | awk '{print $2}')
ping_result=$(echo "$speedtest_output" | grep "Ping:" | awk '{print $2}')
if [[ -n "$download_speed" ]]; then
local down_status="PASS"
if [[ $(echo "$download_speed < 10" | bc -l 2>/dev/null || echo 0) -eq 1 ]]; then
down_status="WARN"
fi
if [[ $(echo "$download_speed < 1" | bc -l 2>/dev/null || echo 0) -eq 1 ]]; then
down_status="FAIL"
fi
log_test_result "Internet" "Velocidade de Download" "speedtest --simple" "$down_status" "${download_speed} Mbit/s"
fi
if [[ -n "$upload_speed" ]]; then
local up_status="PASS"
if [[ $(echo "$upload_speed < 5" | bc -l 2>/dev/null || echo 0) -eq 1 ]]; then
up_status="WARN"
fi
if [[ $(echo "$upload_speed < 0.5" | bc -l 2>/dev/null || echo 0) -eq 1 ]]; then
up_status="FAIL"
fi
log_test_result "Internet" "Velocidade de Upload" "speedtest --simple" "$up_status" "${upload_speed} Mbit/s"
fi
if [[ -n "$ping_result" ]]; then
local ping_status="PASS"
if [[ $(echo "$ping_result > 100" | bc -l 2>/dev/null || echo 0) -eq 1 ]]; then
ping_status="WARN"
fi
if [[ $(echo "$ping_result > 300" | bc -l 2>/dev/null || echo 0) -eq 1 ]]; then
ping_status="FAIL"
fi
log_test_result "Internet" "Ping do Teste de Velocidade" "speedtest --simple" "$ping_status" "${ping_result} ms"
fi
echo "$speedtest_output" > "$perf_file"
else
log_test_result "Internet" "Teste de Velocidade" "speedtest --simple" "FAIL" "Teste de velocidade falhou ou expirou"
fi
# Multiple ping tests for consistency
local ping_targets=("8.8.8.8" "1.1.1.1" "208.67.222.222")
for target in "${ping_targets[@]}"; do
local ping_avg
ping_avg=$(ping -c 5 -q "$target" 2>/dev/null | grep "rtt" | awk -F'/' '{print $5}')
if [[ -n "$ping_avg" ]]; then
local status="PASS"
if [[ $(echo "$ping_avg > 100" | bc -l 2>/dev/null || echo 0) -eq 1 ]]; then
status="WARN"
fi
log_test_result "Multi-Ping" "Latência para $target" "ping -c 5 $target" "$status" "${ping_avg}ms média"
fi
done
log_info "Resultados do teste de desempenho salvos em: $perf_file"
return 0
}
execute_network_devices_scan() {
log_info "Escaneando dispositivos conectados ao mesmo ponto de acesso"
validate_tools_available "arp" "nmap" || return 1
local devices_file="$RESULTS_DIR/network_devices_$(date +%H%M%S).txt"
local gateway
gateway=$(ip route show default | grep "$CLIENT_WIFI_IFACE" | awk '{print $3}' | head -1)
if [[ -z "$gateway" ]]; then
log_test_result "Network" "Device Scan" "ip route show default" "FAIL" "Nenhum gateway encontrado para escaneamento de dispositivos"
return 1
fi
# Get network range from gateway
local network_range
network_range=$(ip route | grep "$CLIENT_WIFI_IFACE" | grep -E "192\.168\.|10\.|172\." | head -1 | awk '{print $1}')
if [[ -z "$network_range" ]]; then
# Fallback: assume /24 network based on gateway
network_range="${gateway%.*}.0/24"
fi
log_info "Escaneando faixa de rede: $network_range"
{
echo "=== Relatório de Escaneamento de Dispositivos de Rede ==="
echo "Timestamp: $(date)"
echo "Gateway: $gateway"
echo "Faixa de Rede: $network_range"
echo "Interface: $CLIENT_WIFI_IFACE"
echo ""
echo "=== Tabela ARP ==="
arp -a 2>/dev/null || echo "Tabela ARP indisponível"
echo ""
echo "=== Escaneamento Ativo da Rede ==="
} > "$devices_file"
# Quick ping sweep to populate ARP table
log_info "Realizando varredura de ping para descobrir dispositivos ativos..."
for i in {1..254}; do
local target_ip="${gateway%.*}.$i"
ping -c 1 -W 1 "$target_ip" &>/dev/null &
done
# Wait for ping sweep to complete (max 5 seconds)
sleep 5
# Get updated ARP table
local arp_devices
arp_devices=$(arp -a 2>/dev/null | grep -E "\([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+\)" | wc -l || echo 0)
if [[ "$arp_devices" -eq 0 ]]; then
log_test_result "Network" "Device Discovery" "arp -a" "WARN" "Nenhum dispositivo encontrado na tabela ARP"
echo "Nenhum dispositivo encontrado na tabela ARP" >> "$devices_file"
return 1
fi
# Parse ARP table for device information
local device_count=0
local our_ip
our_ip=$(ip -4 addr show dev "$CLIENT_WIFI_IFACE" | grep -oP 'inet \K[\d.]+' | head -1)
echo "Dispositivos descobertos:" >> "$devices_file"
# Create structured device list for logging
local devices_info=""
while read -r line; do
if [[ -n "$line" ]]; then
local device_ip
local device_mac
local device_type="Desconhecido"
device_ip=$(echo "$line" | grep -oP '\(\K[0-9.]+(?=\))')
device_mac=$(echo "$line" | grep -oP '[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}')
if [[ -n "$device_ip" && -n "$device_mac" ]]; then
((device_count++))
# Identify device type based on MAC vendor or IP
if [[ "$device_ip" == "$gateway" ]]; then
device_type="Gateway/Roteador"
elif [[ "$device_ip" == "$our_ip" ]]; then
device_type="Este Dispositivo"
else
# Try to identify device type based on MAC vendor (first 3 octets)
local mac_vendor="${device_mac:0:8}"
case "${mac_vendor,,}" in
"00:50:56"|"00:0c:29"|"00:05:69") device_type="Máquina Virtual" ;;
"00:16:3e"|"52:54:00"|"02:00:00") device_type="Virtual/Nuvem" ;;
"00:1b:63"|"00:23:df"|"28:f0:76") device_type="Dispositivo Apple" ;;
"00:15:5d"|"00:03:ff") device_type="Dispositivo Microsoft" ;;
"08:00:27") device_type="VM VirtualBox" ;;
*) device_type="Dispositivo de Rede" ;;
esac
# Try to get hostname
local hostname
hostname=$(nslookup "$device_ip" 2>/dev/null | grep "name =" | awk '{print $4}' | sed 's/\.$//' || echo "")
if [[ -n "$hostname" ]]; then
device_type="$device_type ($hostname)"
fi
fi
echo " $device_ip - $device_mac - $device_type" >> "$devices_file"
devices_info+="$device_ip|$device_mac|$device_type;"
# Log individual device
log_test_result "Dispositivo $device_count" "Descoberta" "arp -a" "INFO" "$device_ip ($device_type)"
fi
fi
done < <(arp -a 2>/dev/null | grep -E "\([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+\)")
# Advanced device scanning with nmap if available
if command -v nmap &>/dev/null; then
echo "" >> "$devices_file"
echo "=== Escaneamento Avançado de Dispositivos (nmap) ===" >> "$devices_file"
local nmap_results
nmap_results=$(timeout 30 nmap -sn "$network_range" 2>/dev/null | grep -E "Nmap scan report|MAC Address" || echo "")
if [[ -n "$nmap_results" ]]; then
echo "$nmap_results" >> "$devices_file"
# Count nmap discovered devices
local nmap_device_count
nmap_device_count=$(echo "$nmap_results" | grep -c "Nmap scan report" || echo 0)
log_test_result "Escaneamento Avançado" "Descoberta nmap" "nmap -sn $network_range" "INFO" "$nmap_device_count dispositivos encontrados"
else
echo "Escaneamento avançado falhou ou expirou" >> "$devices_file"
log_test_result "Escaneamento Avançado" "Descoberta nmap" "nmap -sn $network_range" "WARN" "Escaneamento falhou ou expirou"
fi
fi
# Security analysis
echo "" >> "$devices_file"
echo "=== Análise de Segurança ===" >> "$devices_file"
if [[ "$device_count" -gt 20 ]]; then
log_test_result "Network" "Contagem de Dispositivos" "arp -a" "WARN" "$device_count dispositivos - Rede grande, potencial preocupação de segurança"
echo "AVISO: Grande número de dispositivos detectados ($device_count)" >> "$devices_file"
elif [[ "$device_count" -gt 10 ]]; then
log_test_result "Network" "Contagem de Dispositivos" "arp -a" "WARN" "$device_count dispositivos - Rede de tamanho moderado"
echo "MODERADO: $device_count dispositivos detectados" >> "$devices_file"
else
log_test_result "Network" "Contagem de Dispositivos" "arp -a" "PASS" "$device_count dispositivos - Tamanho de rede normal"
echo "NORMAL: $device_count dispositivos detectados" >> "$devices_file"
fi
# Check for suspicious patterns
local unknown_devices
unknown_devices=$(echo "$devices_info" | grep -o "Dispositivo de Rede" | wc -l || echo 0)
if [[ "$unknown_devices" -gt 5 ]]; then
log_test_result "Segurança" "Dispositivos Desconhecidos" "arp -a" "WARN" "$unknown_devices dispositivos não identificados - investigar melhor"
echo "AVISO: $unknown_devices dispositivos não identificados encontrados" >> "$devices_file"
else
log_test_result "Segurança" "Dispositivos Desconhecidos" "arp -a" "PASS" "$unknown_devices dispositivos não identificados - aceitável"
fi
# Export device data for chart generation
echo "DEVICE_SCAN_RESULTS:$device_count:$devices_info" >> "$devices_file"
log_info "Escaneamento de dispositivos de rede concluído"
log_info "Dispositivos encontrados: $device_count"
log_info "Resultados salvos em: $devices_file"
return 0
}
# --- Operações Principais ---
execute_complete_test_suite() {
log_info "Executando suíte completa de testes de cliente hotspot"
export ACTIVE_LOG="$CURRENT_SESSION_LOG"
setup_test_environment || return 1
echo "========================================"
echo "SUÍTE APRIMORADA DE TESTES DE CLIENTE HOTSPOT"
echo "========================================"
echo "Iniciado: $(date)"
echo "Alvo: $TEST_TARGET_IP"
echo "Interface: $CLIENT_WIFI_IFACE"
echo "========================================"
local test_results=0
echo ""
echo "=== TESTES DE CONECTIVIDADE ==="
check_wifi_connection || ((test_results++))
echo ""
echo "=== TESTES DE LATÊNCIA ==="
execute_ping_tests || ((test_results++))
echo ""
echo "=== ANÁLISE DE CAMINHO ==="
execute_mtr_tests || ((test_results++))
echo ""
echo "=== AVALIAÇÃO DE QUALIDADE ==="
execute_quality_tests || ((test_results++))
echo ""
echo "=== VARREDURA DE DISPOSITIVOS DE REDE ==="
execute_network_devices_scan || ((test_results++))
echo ""
echo "=== AVALIAÇÃO DE SEGURANÇA ==="
execute_security_tests || ((test_results++))
echo ""
echo "=== BENCHMARKS DE DESEMPENHO ==="
execute_performance_tests || ((test_results++))
if [[ -n "$IPERF3_SERVER_IP" ]]; then
echo ""
echo "=== TESTES DE THROUGHPUT ==="
execute_iperf3_tests || ((test_results++))
else
echo ""
echo "=== TESTES DE THROUGHPUT ==="
log_warning "Pulando testes de throughput (IPERF3_SERVER_IP não configurado)"
fi
ln -sf "$CURRENT_SESSION_LOG" "$RESULTS_DIR/hotspot_test_latest.log"
echo ""
echo "========================================"
echo "SUÍTE DE TESTES CONCLUÍDA"
echo "========================================"
echo "Concluído: $(date)"
echo "Testes Falharam: $test_results"
echo "Arquivo de Log: $CURRENT_SESSION_LOG"
echo "Diretório de Resultados: $RESULTS_DIR"
echo "========================================"
return "$test_results"
}
generate_test_report() {
log_info "Generating comprehensive test report with enhanced visualization"
local working_log_file
local report_file="$RESULTS_DIR/hotspot_test_report_$(date +%Y%m%d_%H%M%S).html"
local text_report="$RESULTS_DIR/hotspot_test_summary_$(date +%Y%m%d_%H%M%S).txt"
if working_log_file=$(find_latest_log_file); then
log_info "Using log file: $working_log_file"
else
log_warning "No existing test data found. Running complete test suite first..."
if execute_complete_test_suite; then
working_log_file="$CURRENT_SESSION_LOG"
log_info "Test suite completed. Generating report from: $working_log_file"
else
log_error "Failed to execute test suite. Cannot generate report."
return 1
fi
fi
if ! grep -q "RESULT:" "$working_log_file" 2>/dev/null; then
log_warning "No test results found in log file. Running complete test suite..."
if execute_complete_test_suite; then
working_log_file="$CURRENT_SESSION_LOG"
log_info "Test suite completed. Generating report from: $working_log_file"
else
log_error "Failed to execute test suite. Cannot generate report."
return 1
fi
fi
# Debug: Show what's in the log file
log_debug "Log file contents preview:"
log_debug "$(head -20 "$working_log_file")"
log_debug "Total lines in log: $(wc -l < "$working_log_file")"
log_debug "RESULT lines found: $(grep -c "RESULT:" "$working_log_file" 2>/dev/null || echo 0)"
# Generate text report
{
echo "=================================================================="
echo "ENHANCED HOTSPOT CLIENT TEST REPORT"
echo "=================================================================="
echo "Generated: $(date)"
echo "Interface: $CLIENT_WIFI_IFACE"
echo "Test Target: $TEST_TARGET_IP"
echo "Log File: $working_log_file"
echo ""
echo "------------------------------------------------------------------"
echo "TEST RESULTS SUMMARY"
echo "------------------------------------------------------------------"
grep "RESULT:" "$working_log_file" | sed 's/.*RESULT: //' | while read -r line; do
echo " $line"
done
echo ""
echo "------------------------------------------------------------------"
echo "ERRORS AND WARNINGS"
echo "------------------------------------------------------------------"
if grep -E "(ERROR|WARN):" "$working_log_file" | sed 's/.*\(ERROR\|WARN\): //' | head -20; then
:
else
echo " No errors or warnings found"
fi
echo ""
echo "------------------------------------------------------------------"
echo "SUMMARY STATISTICS"
echo "------------------------------------------------------------------"
local total_tests pass_tests fail_tests warn_tests info_tests
total_tests=$(grep -c "RESULT:" "$working_log_file" 2>/dev/null | tr -d '\n' || printf "0")
pass_tests=$(grep "RESULT:" "$working_log_file" 2>/dev/null | grep -c "|PASS|" | tr -d '\n' || printf "0")
fail_tests=$(grep "RESULT:" "$working_log_file" 2>/dev/null | grep -c "|FAIL|" | tr -d '\n' || printf "0")
warn_tests=$(grep "RESULT:" "$working_log_file" 2>/dev/null | grep -c "|WARN|" | tr -d '\n' || printf "0")
info_tests=$(grep "RESULT:" "$working_log_file" 2>/dev/null | grep -c "|INFO|" | tr -d '\n' || printf "0")
# Ensure all variables are numeric
total_tests=${total_tests:-0}
pass_tests=${pass_tests:-0}
fail_tests=${fail_tests:-0}
warn_tests=${warn_tests:-0}
echo " Total Tests: $total_tests"
echo " Passed: $pass_tests"
echo " Failed: $fail_tests"
echo " Warnings: $warn_tests"
echo ""
echo "=================================================================="
} > "$text_report"
# Extract real data for JavaScript charts and log files
local signal_value latency_value throughput_value jitter_value
local tcp_down_value tcp_up_value tcp_parallel_value speed_down_value speed_up_value
local device_scan_data
signal_value=$(grep "Signal.*dBm" "$working_log_file" | head -1 | grep -oP '\-?\d+(?= dBm)' || echo "-70")
latency_value=$(grep "Average.*ms" "$working_log_file" | head -1 | grep -oP '\d+\.?\d*(?=ms)' || echo "50")
throughput_value=$(grep "TCP.*Mbps" "$working_log_file" | head -1 | grep -oP '\d+\.?\d*(?= Mbps)' || echo "25")
jitter_value=$(grep "Jitter.*ms" "$working_log_file" | head -1 | grep -oP '\d+\.?\d*(?=ms)' || echo "2")
tcp_down_value=$(grep "RESULT:.*TCP.*Download Test.*PASS.*Mbps" "$working_log_file" | head -1 | grep -oP '\d+\.\d+(?= Mbps)' || echo "0")
tcp_up_value=$(grep "RESULT:.*TCP.*Upload Test.*PASS.*Mbps" "$working_log_file" | head -1 | grep -oP '\d+\.\d+(?= Mbps)' || echo "0")
tcp_parallel_value=$(grep "RESULT:.*TCP Parallel.*Throughput.*PASS.*Mbps" "$working_log_file" | head -1 | grep -oP '\d+\.\d+(?= Mbps)' || echo "0")
speed_down_value=$(grep "RESULT:.*Internet.*Download Speed.*PASS.*Mbit/s" "$working_log_file" | head -1 | grep -oP '\d+\.\d+(?= Mbit/s)' || echo "0")
speed_up_value=$(grep "RESULT:.*Internet.*Upload Speed.*PASS.*Mbit/s" "$working_log_file" | head -1 | grep -oP '\d+\.\d+(?= Mbit/s)' || echo "0")
# Extract log file paths from the main log
local ping_file mtr_file quality_file devices_file security_file performance_file iperf3_file
ping_file=$(grep "Ping results saved to:" "$working_log_file" | head -1 | grep -oP 'saved to: \K.*' || echo "")
mtr_file=$(grep "MTR results saved to:" "$working_log_file" | head -1 | grep -oP 'saved to: \K.*' || echo "")
quality_file=$(grep "Quality assessment saved to:" "$working_log_file" | head -1 | grep -oP 'saved to: \K.*' || echo "")
devices_file=$(grep "Network devices scan completed" -A2 "$working_log_file" | grep "Results saved to:" | head -1 | grep -oP 'saved to: \K.*' || echo "")
security_file=$(grep "Security assessment saved to:" "$working_log_file" | head -1 | grep -oP 'saved to: \K.*' || echo "")
performance_file=$(grep "Performance test results saved to:" "$working_log_file" | head -1 | grep -oP 'saved to: \K.*' || echo "")
iperf3_file=$(grep "iperf3 results saved to:" "$working_log_file" | head -1 | grep -oP 'saved to: \K.*' || echo "")
device_scan_data=$(grep "DEVICE_SCAN_RESULTS:" "$working_log_file" | tail -1 || echo "")
# Debug device scan data extraction
log_debug "Device scan data extracted from main log: '$device_scan_data'"
# If no device scan data found, try to extract from device file directly
if [[ -z "$device_scan_data" && -n "$devices_file" && -f "$devices_file" ]]; then
device_scan_data=$(grep "DEVICE_SCAN_RESULTS:" "$devices_file" | tail -1 || echo "")
log_debug "Device scan data from device file: '$device_scan_data'"
fi
# If still no data, try to reconstruct from device file content
if [[ -z "$device_scan_data" && -n "$devices_file" && -f "$devices_file" ]]; then
local discovered_devices
discovered_devices=$(grep -A 100 "Discovered devices:" "$devices_file" 2>/dev/null | grep -E "^\s+[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+" | head -20)
if [[ -n "$discovered_devices" ]]; then
local device_count device_info_string=""
device_count=$(echo "$discovered_devices" | wc -l)
# Build device info string
while IFS= read -r line; do
if [[ -n "$line" ]]; then
# Extract IP, MAC, and type from lines like: " 192.168.1.103 - 00:1b:9e:2b:c3:37 - Network Device"
local ip mac type
ip=$(echo "$line" | grep -oP '\d+\.\d+\.\d+\.\d+' | head -1)
mac=$(echo "$line" | grep -oP '[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}:[0-9a-f]{2}')
type=$(echo "$line" | sed 's/.*- [0-9a-f:]\+ - //')
if [[ -n "$ip" && -n "$mac" && -n "$type" ]]; then
device_info_string+="${ip}|${mac}|${type};"
fi
fi
done <<< "$discovered_devices"
device_scan_data="DEVICE_SCAN_RESULTS:${device_count}:${device_info_string}"
log_debug "Device scan data reconstructed: '$device_scan_data'"
fi
fi
# If still no data, create a fallback based on device count from logs
if [[ -z "$device_scan_data" ]]; then
local device_count_fallback
device_count_fallback=$(grep "Devices found:" "$working_log_file" | head -1 | grep -oP 'Devices found: \K\d+' || echo "0")
if [[ "$device_count_fallback" != "0" ]]; then
device_scan_data="DEVICE_SCAN_RESULTS:$device_count_fallback:"
log_debug "Device scan data fallback: '$device_scan_data'"
else
device_scan_data="DEVICE_SCAN_RESULTS:0:"
fi
fi
local total_tests pass_tests fail_tests warn_tests info_tests
total_tests=$(grep -c "RESULT:" "$working_log_file" 2>/dev/null | tr -d '\n' || printf "0")
pass_tests=$(grep "RESULT:" "$working_log_file" 2>/dev/null | grep -c "|PASS|" | tr -d '\n' || printf "0")
fail_tests=$(grep "RESULT:" "$working_log_file" 2>/dev/null | grep -c "|FAIL|" | tr -d '\n' || printf "0")
warn_tests=$(grep "RESULT:" "$working_log_file" 2>/dev/null | grep -c "|WARN|" | tr -d '\n' || printf "0")
info_tests=$(grep "RESULT:" "$working_log_file" 2>/dev/null | grep -c "|INFO|" | tr -d '\n' || printf "0")
total_tests=$((total_tests + 0))
pass_tests=$((pass_tests + 0))
fail_tests=$((fail_tests + 0))
warn_tests=$((warn_tests + 0))
info_tests=$((info_tests + 0))
# Generate enhanced HTML report
cat <<EOF > "$report_file"
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Enhanced Hotspot Client Test Report</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.9.1/chart.min.js"></script>
<script>
// Global functions must be declared before HTML elements use them
function showTab(tabName) {
// Hide all tab contents
const tabContents = document.querySelectorAll('.tab-content');
tabContents.forEach(content => {
content.classList.remove('active');
});
// Remove active class from all tab buttons
const tabButtons = document.querySelectorAll('.tab-button');
tabButtons.forEach(button => {
button.classList.remove('active');
});
// Show selected tab content
const selectedTab = document.getElementById(tabName);
if (selectedTab) {
selectedTab.classList.add('active');
}
// Find and activate the clicked button
const clickedButton = document.querySelector(\`[onclick="showTab('\${tabName}')"]\`);
if (clickedButton) {
clickedButton.classList.add('active');
}
}
</script>
<style>
:root[data-theme="dark"] {
--bg-primary: #0d1117;
--bg-secondary: #161b22;
--bg-tertiary: #21262d;
--text-primary: #f0f6fc;
--text-secondary: #8b949e;
--border: #30363d;
--pass: #3fb950;
--warn: #d29922;
--fail: #f85149;
--info: #58a6ff;
}
:root[data-theme="light"] {
--bg-primary: #ffffff;
--bg-secondary: #f6f8fa;
--bg-tertiary: #ffffff;
--text-primary: #24292f;
--text-secondary: #656d76;
--border: #d0d7de;
--pass: #1a7f37;
--warn: #9a6700;
--fail: #cf222e;
--info: #0969da;
}
:root {
--bg-primary: #0d1117;
--bg-secondary: #161b22;
--bg-tertiary: #21262d;
--text-primary: #f0f6fc;
--text-secondary: #8b949e;
--border: #30363d;
--pass: #3fb950;
--warn: #d29922;
--fail: #f85149;
--info: #58a6ff;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', monospace;
background-color: var(--bg-primary);
color: var(--text-primary);
line-height: 1.6;
margin: 0;
padding: 20px;
transition: all 0.3s ease;
}
.container {
max-width: 1400px;
margin: 0 auto;
}
.controls-bar {
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: 12px;
padding: 20px;
margin-bottom: 30px;
display: flex;
justify-content: center;
gap: 15px;
flex-wrap: wrap;
}
.btn {
background: var(--bg-tertiary);
color: var(--text-primary);
border: 1px solid var(--border);
padding: 10px 20px;
border-radius: 8px;
cursor: pointer;
font-size: 0.9em;
font-weight: 500;
transition: all 0.2s ease;
text-decoration: none;
display: inline-block;
}
.btn:hover {
background: var(--info);
color: white;
border-color: var(--info);
transform: translateY(-1px);
}
.btn-primary {
background: var(--info);
color: white;
border-color: var(--info);
}
.btn-primary:hover {
background: #0969da;
border-color: #0969da;
}
.theme-toggle {
background: var(--bg-tertiary);
border: 1px solid var(--border);
color: var(--text-primary);
}
.theme-toggle:hover {
background: var(--warn);
border-color: var(--warn);
color: white;
}
.header {
background: var(--bg-secondary);
border: 2px solid var(--border);
border-radius: 16px;
padding: 40px;
margin-bottom: 30px;
text-align: center;
position: relative;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
}
.header::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(45deg, rgba(88, 166, 255, 0.05), rgba(63, 185, 80, 0.05));
border-radius: 14px;
}
.header-content {
position: relative;
z-index: 1;
}
.network-icon {
font-size: 4em;
margin-bottom: 20px;
background: linear-gradient(135deg, var(--info), var(--pass));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.header .subtitle {
color: var(--text-primary);
font-size: 1.4em;
margin-bottom: 20px;
font-weight: 400;
letter-spacing: 0.5px;
}
.header .tagline {
color: var(--text-secondary);
font-size: 1em;
margin-bottom: 30px;
font-style: italic;
}
.header .meta {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 15px;
margin-top: 20px;
}
.meta-item {
background: var(--bg-tertiary);
border: 1px solid var(--border);
padding: 16px 20px;
border-radius: 10px;
font-size: 0.9em;
transition: all 0.2s ease;
}
.meta-item:hover {
background: rgba(88, 166, 255, 0.05);
border-color: var(--info);
}
.meta-item strong {
color: var(--info);
display: block;
margin-bottom: 6px;
font-weight: 600;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.stat-card {
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: 12px;
padding: 20px;
text-align: center;
position: relative;
overflow: hidden;
transition: transform 0.2s ease;
}
.stat-card:hover {
transform: translateY(-2px);
}
.stat-value {
font-size: 2.5em;
font-weight: bold;
margin-bottom: 8px;
display: flex;
align-items: center;
justify-content: center;
height: 60px;
}
.stat-label {
color: var(--text-secondary);
font-size: 0.9em;
text-transform: uppercase;
letter-spacing: 0.5px;
font-weight: 500;
}
.stat-pass { color: var(--pass); }
.stat-fail { color: var(--fail); }
.stat-warn { color: var(--warn); }
.stat-total { color: var(--info); }
.stat-info { color: var(--info); }
.charts-section {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.chart-container {
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: 12px;
padding: 20px;
position: relative;
}
.chart-container canvas {
max-height: 300px !important;
height: 300px !important;
}
.chart-title {
color: var(--text-primary);
font-size: 1.2em;
font-weight: 600;
margin-bottom: 15px;
text-align: center;
}
.chart-download {
position: absolute;
top: 15px;
right: 15px;
background: var(--bg-tertiary);
border: 1px solid var(--border);
color: var(--text-secondary);
padding: 5px 10px;
border-radius: 6px;
font-size: 0.8em;
cursor: pointer;
transition: all 0.2s ease;
}
.chart-download:hover {
background: var(--info);
color: white;
border-color: var(--info);
}
.devices-list {
height: 300px;
display: flex;
flex-direction: column;
}
.devices-header {
text-align: center;
padding: 20px;
border-bottom: 1px solid var(--border);
background: var(--bg-tertiary);
}
.device-count {
display: block;
font-size: 2.5em;
font-weight: bold;
color: var(--info);
line-height: 1;
}
.device-label {
color: var(--text-secondary);
font-size: 0.9em;
text-transform: uppercase;
letter-spacing: 0.5px;
font-weight: 500;
}
.devices-content {
flex: 1;
overflow-y: auto;
padding: 10px;
}
.device-item {
display: flex;
align-items: center;
padding: 8px 12px;
margin-bottom: 6px;
background: var(--bg-primary);
border: 1px solid var(--border);
border-radius: 8px;
font-size: 0.8em;
transition: all 0.2s ease;
}
.device-item:hover {
background: rgba(88, 166, 255, 0.05);
border-color: var(--info);
}
.device-icon {
width: 20px;
text-align: center;
margin-right: 10px;
font-size: 1.1em;
}
.device-info {
flex: 1;
}
.device-ip {
font-weight: 600;
color: var(--text-primary);
font-family: 'Monaco', 'Consolas', monospace;
}
.device-type {
color: var(--text-secondary);
font-size: 0.85em;
margin-top: 2px;
}
.device-mac {
color: var(--text-secondary);
font-size: 0.75em;
font-family: 'Monaco', 'Consolas', monospace;
margin-top: 1px;
}
.no-devices {
text-align: center;
color: var(--text-secondary);
font-style: italic;
padding: 40px 20px;
}
.results-table {
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: 12px;
overflow: hidden;
margin-bottom: 30px;
}
.table-header {
background: var(--bg-tertiary);
padding: 20px;
border-bottom: 1px solid var(--border);
}
.table-header h2 {
color: var(--text-primary);
font-size: 1.4em;
font-weight: 600;
}
table {
width: 100%;
border-collapse: collapse;
}
th, td {
padding: 16px 20px;
text-align: left;
border-bottom: 1px solid var(--border);
}
th {
background: var(--bg-tertiary);
color: var(--text-secondary);
font-weight: 600;
text-transform: uppercase;
font-size: 0.85em;
letter-spacing: 0.5px;
}
td {
font-size: 0.9em;
}
tr:hover {
background: rgba(255,255,255,0.02);
}
.test-name {
font-weight: 600;
color: var(--text-primary);
}
.test-action {
color: var(--text-secondary);
font-size: 0.85em;
font-style: italic;
}
.test-command {
font-family: 'Monaco', 'Consolas', monospace;
font-size: 0.8em;
color: var(--text-secondary);
background: var(--bg-primary);
padding: 4px 8px;
border-radius: 4px;
border: 1px solid var(--border);
}
.status-chip {
font-weight: bold;
padding: 6px 12px;
border-radius: 6px;
text-align: center;
min-width: 70px;
display: inline-block;
font-size: 0.8em;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.status-pass {
background: rgba(63, 185, 80, 0.15);
color: var(--pass);
border: 1px solid var(--pass);
}
.status-fail {
background: rgba(248, 81, 73, 0.15);
color: var(--fail);
border: 1px solid var(--fail);
}
.status-warn {
background: rgba(210, 153, 34, 0.15);
color: var(--warn);
border: 1px solid var(--warn);
}
.status-info {
background: rgba(88, 166, 255, 0.15);
color: var(--info);
border: 1px solid var(--info);
}
.test-details {
color: var(--text-secondary);
font-family: 'Monaco', 'Consolas', monospace;
font-size: 0.85em;
}
.status-indicator {
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
margin-right: 6px;
}
.indicator-pass { background-color: var(--pass); }
.indicator-fail { background-color: var(--fail); }
.indicator-warn { background-color: var(--warn); }
.indicator-info { background-color: var(--info); }
.log-section {
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: 12px;
margin-top: 30px;
}
.tabs-container {
background: var(--bg-tertiary);
border-bottom: 1px solid var(--border);
overflow-x: auto;
}
.tabs-nav {
display: flex;
min-width: max-content;
}
.tab-button {
background: transparent;
border: none;
padding: 15px 20px;
color: var(--text-secondary);
cursor: pointer;
font-size: 0.9em;
font-weight: 500;
white-space: nowrap;
transition: all 0.2s ease;
border-bottom: 3px solid transparent;
}
.tab-button:hover {
background: rgba(88, 166, 255, 0.05);
color: var(--text-primary);
}
.tab-button.active {
color: var(--info);
border-bottom-color: var(--info);
background: rgba(88, 166, 255, 0.08);
}
.tab-content {
display: none;
background: var(--bg-primary);
padding: 20px;
font-family: 'Monaco', 'Consolas', monospace;
font-size: 0.8em;
line-height: 1.5;
white-space: pre-wrap;
overflow-x: auto;
max-height: 500px;
overflow-y: auto;
color: var(--text-secondary);
}
.tab-content.active {
display: block;
}
.tab-file-path {
color: var(--info);
font-weight: 600;
margin-bottom: 10px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
font-size: 1.1em;
}
.tab-file-size {
color: var(--text-secondary);
font-size: 0.9em;
margin-bottom: 15px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
}
.log-content {
background: var(--bg-primary);
padding: 20px;
font-family: 'Monaco', 'Consolas', monospace;
font-size: 0.8em;
line-height: 1.5;
white-space: pre-wrap;
overflow-x: auto;
max-height: 500px;
overflow-y: auto;
color: var(--text-secondary);
border-radius: 0 0 12px 12px;
}
.footer {
margin-top: 40px;
padding: 25px;
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: 12px;
text-align: center;
color: var(--text-secondary);
font-size: 0.9em;
}
.footer strong {
color: var(--text-primary);
}
@media print {
body {
background: white !important;
color: black !important;
}
.controls-bar {
display: none !important;
}
.chart-download {
display: none !important;
}
.header::before {
display: none !important;
}
.chart-container, .results-table, .header, .stat-card, .log-section {
border: 1px solid #ccc !important;
background: white !important;
}
.stat-value {
color: black !important;
}
.test-name, .header .subtitle {
color: black !important;
}
.chart-title {
color: black !important;
}
.tabs-container {
background: #f8f9fa !important;
border: 1px solid #ccc !important;
}
.tab-button {
color: black !important;
border-bottom-color: #ccc !important;
}
.tab-button.active {
color: #0969da !important;
border-bottom-color: #0969da !important;
}
.tab-content {
background: #f8f9fa !important;
color: black !important;
border: 1px solid #ccc !important;
max-height: none !important;
overflow: visible !important;
display: block !important;
page-break-inside: avoid;
margin-bottom: 20px;
}
.tab-file-path {
color: #0969da !important;
}
.tab-file-size {
color: #666 !important;
}
.test-command {
background: #f8f9fa !important;
color: black !important;
border: 1px solid #ccc !important;
}
.devices-content {
display: block !important;
overflow-y: visible !important;
max-height: none !important;
}
}
@media (max-width: 768px) {
.stats-grid {
grid-template-columns: repeat(2, 1fr);
}
.charts-section {
grid-template-columns: 1fr;
}
.header .meta {
grid-template-columns: 1fr;
}
th, td {
padding: 12px 16px;
font-size: 0.85em;
}
.test-command {
font-size: 0.7em;
}
.controls-bar {
flex-direction: column;
gap: 10px;
}
}
</style>
</head>
<body data-theme="dark">
<div class="container">
<div class="controls-bar">
<button class="btn btn-primary" onclick="window.print()">🖨️ Imprimir Relatório</button>
<button class="btn theme-toggle" onclick="toggleTheme()">🌞 Alternar Tema</button>
</div>
<div class="header">
<div class="header-content">
<div class="network-icon">📡</div>
<div class="subtitle">Análise de Performance & Segurança da Rede</div>
<div class="tagline">Relatório abrangente de diagnósticos e monitoramento Wi-Fi</div>
<div class="meta">
<div class="meta-item"><strong>Gerado em</strong> $(date)</div>
<div class="meta-item"><strong>Interface</strong> $CLIENT_WIFI_IFACE</div>
<div class="meta-item"><strong>Destino</strong> $TEST_TARGET_IP</div>
<div class="meta-item"><strong>Arquivo de Log</strong> $(basename "$working_log_file")</div>
</div>
</div>
</div>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-value stat-total">$total_tests</div>
<div class="stat-label">Total de Testes</div>
</div>
<div class="stat-card">
<div class="stat-value stat-pass">$pass_tests</div>
<div class="stat-label">Aprovados</div>
</div>
<div class="stat-card">
<div class="stat-value stat-fail">$fail_tests</div>
<div class="stat-label">Falharam</div>
</div>
<div class="stat-card">
<div class="stat-value stat-warn">$warn_tests</div>
<div class="stat-label">Avisos</div>
</div>
<div class="stat-card">
<div class="stat-value stat-info">$info_tests</div>
<div class="stat-label">Infos</div>
</div>
</div>
<div class="charts-section">
<div class="chart-container">
<div class="chart-title">Distribuição dos Resultados dos Testes</div>
<button class="chart-download" onclick="downloadChart('resultsChart', 'test-results-distribution.png')">📥 PNG</button>
<canvas id="resultsChart" width="400" height="300"></canvas>
</div>
<div class="chart-container">
<div class="chart-title">Métricas de Performance da Rede</div>
<button class="chart-download" onclick="downloadChart('performanceChart', 'network-performance-metrics.png')">📥 PNG</button>
<canvas id="performanceChart" width="400" height="300"></canvas>
</div>
<div class="chart-container">
<div class="chart-title">Análise de Latência</div>
<button class="chart-download" onclick="downloadChart('latencyChart', 'latency-analysis.png')">📥 PNG</button>
<canvas id="latencyChart" width="400" height="300"></canvas>
</div>
<div class="chart-container">
<div class="chart-title">Comparação de Throughput</div>
<button class="chart-download" onclick="downloadChart('throughputChart', 'throughput-comparison.png')">📥 PNG</button>
<canvas id="throughputChart" width="400" height="300"></canvas>
</div>
<div class="chart-container">
<div class="chart-title">Dispositivos Conectados</div>
<div id="devicesChart" class="devices-list">
<div class="devices-header">
<span class="device-count">0</span>
<span class="device-label">Dispositivos Encontrados</span>
</div>
<div class="devices-content">
<div class="no-devices">Nenhum dado de escaneamento de dispositivos disponível</div>
</div>
</div>
</div>
</div>
<div class="results-table">
<div class="table-header">
<h2>Resultados Detalhados dos Testes</h2>
</div>
<table>
<thead>
<tr>
<th>Categoria do Teste</th>
<th>Comando</th>
<th>Status</th>
<th>Detalhes</th>
</tr>
</thead>
<tbody>
EOF
# Parse test results and add to HTML table
if grep "RESULT:" "$working_log_file" | sed 's/.*RESULT: //' | while IFS='|' read -r test_name action command status details || [[ -n "$test_name" ]]; do
local css_class="status-info"
local indicator_class="indicator-info"
case "$status" in
"PASS")
css_class="status-pass"
indicator_class="indicator-pass"
;;
"WARN")
css_class="status-warn"
indicator_class="indicator-warn"
;;
"FAIL")
css_class="status-fail"
indicator_class="indicator-fail"
;;
esac
# Escape HTML characters
local escaped_details="${details//&/&}"
escaped_details="${escaped_details//</<}"
escaped_details="${escaped_details//>/>}"
local escaped_command="${command//&/&}"
escaped_command="${escaped_command//</<}"
escaped_command="${escaped_command//>/>}"
echo " <tr>" >> "$report_file"
echo " <td><div class=\"test-name\">${test_name}</div><div class=\"test-action\">${action}</div></td>" >> "$report_file"
echo " <td><code class=\"test-command\">${escaped_command}</code></td>" >> "$report_file"
echo " <td><span class=\"status-indicator ${indicator_class}\"></span><span class=\"status-chip ${css_class}\">${status}</span></td>" >> "$report_file"
echo " <td class=\"test-details\">${escaped_details}</td>" >> "$report_file"
echo " </tr>" >> "$report_file"
done; then
: # Results were processed
else
echo " <tr><td colspan=\"4\" style=\"text-align: center; color: var(--text-secondary);\">Nenhum resultado de teste encontrado</td></tr>" >> "$report_file"
fi
# Add JavaScript for charts and close HTML
cat <<EOF >> "$report_file"
</tbody>
</table>
</div>
<div class="log-section">
<div class="table-header">
<h2>Logs e Relatórios Detalhados</h2>
</div>
<div class="tabs-container">
<div class="tabs-nav">
<button class="tab-button active" onclick="showTab('summary')">Resumo</button>
EOF
# Add tab buttons for each log file found
[[ -n "$ping_file" && -f "$ping_file" ]] && echo ' <button class="tab-button" onclick="showTab('\''ping'\'')">Teste de Ping</button>' >> "$report_file"
[[ -n "$mtr_file" && -f "$mtr_file" ]] && echo ' <button class="tab-button" onclick="showTab('\''mtr'\'')">Análise MTR</button>' >> "$report_file"
[[ -n "$quality_file" && -f "$quality_file" ]] && echo ' <button class="tab-button" onclick="showTab('\''quality'\'')">Avaliação de Qualidade</button>' >> "$report_file"
[[ -n "$devices_file" && -f "$devices_file" ]] && echo ' <button class="tab-button" onclick="showTab('\''devices'\'')">Dispositivos da Rede</button>' >> "$report_file"
[[ -n "$security_file" && -f "$security_file" ]] && echo ' <button class="tab-button" onclick="showTab('\''security'\'')">Avaliação de Segurança</button>' >> "$report_file"
[[ -n "$performance_file" && -f "$performance_file" ]] && echo ' <button class="tab-button" onclick="showTab('\''performance'\'')">Teste de Performance</button>' >> "$report_file"
[[ -n "$iperf3_file" && -d "$iperf3_file" ]] && echo ' <button class="tab-button" onclick="showTab('\''iperf3'\'')">Resultados iPerf3</button>' >> "$report_file"
cat <<EOF >> "$report_file"
</div>
</div>
<div id="summary" class="tab-content active">
<div class="tab-file-path">Log Principal dos Testes</div>
<div class="tab-file-size">Arquivo: $(basename "$working_log_file") | Tamanho: $(du -h "$working_log_file" 2>/dev/null | cut -f1 || echo "Desconhecido")</div>$(cat "$performance_file" 2>/dev/null | sed 's/&/\&/g; s/</\</g; s/>/\>/g' || echo "Arquivo não encontrado ou ilegível")</div>
EOF
# Add tab content for each log file
if [[ -n "$ping_file" && -f "$ping_file" ]]; then
cat <<EOF >> "$report_file"
<div id="ping" class="tab-content">
<div class="tab-file-path">Resultados do Teste de Ping</div>
<div class="tab-file-size">Arquivo: $(basename "$ping_file") | Tamanho: $(du -h "$ping_file" 2>/dev/null | cut -f1 || echo "Desconhecido")</div>$(cat "$performance_file" 2>/dev/null | sed 's/&/\&/g; s/</\</g; s/>/\>/g' || echo "Arquivo não encontrado ou ilegível")</div>
EOF
fi
if [[ -n "$mtr_file" && -f "$mtr_file" ]]; then
cat <<EOF >> "$report_file"
<div id="mtr" class="tab-content">
<div class="tab-file-path">Análise de Caminho da Rede MTR</div>
<div class="tab-file-size">Arquivo: $(basename "$mtr_file") | Tamanho: $(du -h "$mtr_file" 2>/dev/null | cut -f1 || echo "Desconhecido")</div>$(cat "$performance_file" 2>/dev/null | sed 's/&/\&/g; s/</\</g; s/>/\>/g' || echo "Arquivo não encontrado ou ilegível")</div>
EOF
fi
if [[ -n "$quality_file" && -f "$quality_file" ]]; then
cat <<EOF >> "$report_file"
<div id="quality" class="tab-content">
<div class="tab-file-path">Avaliação da Qualidade da Conexão</div>
<div class="tab-file-size">Arquivo: $(basename "$quality_file") | Tamanho: $(du -h "$quality_file" 2>/dev/null | cut -f1 || echo "Desconhecido")</div>$(cat "$performance_file" 2>/dev/null | sed 's/&/\&/g; s/</\</g; s/>/\>/g' || echo "Arquivo não encontrado ou ilegível")</div>
EOF
fi
if [[ -n "$devices_file" && -f "$devices_file" ]]; then
cat <<EOF >> "$report_file"
<div id="devices" class="tab-content">
<div class="tab-file-path">Escaneamento de Dispositivos da Rede</div>
<div class="tab-file-size">Arquivo: $(basename "$devices_file") | Tamanho: $(du -h "$devices_file" 2>/dev/null | cut -f1 || echo "Desconhecido")</div>$(cat "$performance_file" 2>/dev/null | sed 's/&/\&/g; s/</\</g; s/>/\>/g' || echo "Arquivo não encontrado ou ilegível")</div>
EOF
fi
if [[ -n "$security_file" && -f "$security_file" ]]; then
cat <<EOF >> "$report_file"
<div id="security" class="tab-content">
<div class="tab-file-path">Avaliação de Vulnerabilidades de Segurança</div>
<div class="tab-file-size">Arquivo: $(basename "$security_file") | Tamanho: $(du -h "$security_file" 2>/dev/null | cut -f1 || echo "Desconhecido")</div>$(cat "$performance_file" 2>/dev/null | sed 's/&/\&/g; s/</\</g; s/>/\>/g' || echo "Arquivo não encontrado ou ilegível")</div>
EOF
fi
if [[ -n "$performance_file" && -f "$performance_file" ]]; then
cat <<EOF >> "$report_file"
<div id="performance" class="tab-content">
<div class="tab-file-path">Benchmarks de Performance</div>
<div class="tab-file-size">Arquivo: $(basename "$performance_file") | Tamanho: $(du -h "$performance_file" 2>/dev/null | cut -f1 || echo "Desconhecido")</div>$(cat "$performance_file" 2>/dev/null | sed 's/&/\&/g; s/</\</g; s/>/\>/g' ||| echo "Arquivo não encontrado ou ilegível")</div>
EOF
fi
if [[ -n "$iperf3_file" && -d "$iperf3_file" ]]; then
local iperf3_content=""
for file in "$iperf3_file"/*.json; do
if [[ -f "$file" ]]; then
iperf3_content+="\n=== $(basename "$file") ===\n"
iperf3_content+="$(cat "$file" 2>/dev/null)\n"
fi
done
cat <<EOF >> "$report_file"
<div id="iperf3" class="tab-content">
<div class="tab-file-path">Testes de Throughput iPerf3</div>
<div class="tab-file-size">Diretório: $(basename "$iperf3_file") | Arquivos: $(find "$iperf3_file" -name "*.json" 2>/dev/null | wc -l || echo "0")</div>$(echo -e "$iperf3_content" | sed 's/&/\&/g; s/</\</g; s/>/\>/g' || echo "Nenhum arquivo iPerf3 encontrado")</div>
EOF
fi
cat <<EOF >> "$report_file"
</div>
<div class="footer">
<p><strong>Report Generated by:</strong> $SCRIPT_NAME $VERSION</p>
<p><strong>System:</strong> $(uname -sr) | <strong>Hostname:</strong> $(hostname)</p>
<p><strong>Results Directory:</strong> $RESULTS_DIR</p>
</div>
</div>
<script>
// Theme toggle functionality
function toggleTheme() {
const root = document.documentElement;
const themeToggle = document.querySelector('.theme-toggle');
const currentTheme = root.getAttribute('data-theme');
if (currentTheme === 'dark') {
root.setAttribute('data-theme', 'light');
themeToggle.innerHTML = '🌙 Toggle Theme';
themeToggle.title = 'Toggle to dark theme';
} else {
root.setAttribute('data-theme', 'dark');
themeToggle.innerHTML = '🌞 Toggle Theme';
themeToggle.title = 'Toggle to light theme';
}
// Update charts for new theme
updateChartsForTheme();
}
// Chart download functionality
function downloadChart(chartId, filename) {
const canvas = document.getElementById(chartId);
const url = canvas.toDataURL('image/png', 1.0);
const link = document.createElement('a');
link.download = filename;
link.href = url;
link.click();
}
function populateDevicesList() {
// Extract device scan results from log
const deviceScanLine = '$device_scan_data';
console.log('Raw device scan line from shell:', deviceScanLine);
if (!deviceScanLine || deviceScanLine === 'DEVICE_SCAN_RESULTS:0:' || deviceScanLine === '' || deviceScanLine === 'DEVICE_SCAN_RESULTS::') {
console.log('No valid device data found, deviceScanLine:', deviceScanLine);
const deviceCountElement = document.querySelector('.device-count');
if (deviceCountElement) {
deviceCountElement.textContent = '0';
}
const devicesContent = document.querySelector('.devices-content');
if (devicesContent) {
devicesContent.innerHTML = '<div class=\"no-devices\">No device scan data available</div>';
}
return;
}
// Split only on the first two colons to avoid splitting MAC addresses
const firstColonIndex = deviceScanLine.indexOf(':');
const secondColonIndex = deviceScanLine.indexOf(':', firstColonIndex + 1);
if (firstColonIndex === -1 || secondColonIndex === -1) {
console.log('Invalid device scan format - missing colons');
return;
}
const deviceCountStr = deviceScanLine.substring(firstColonIndex + 1, secondColonIndex);
const devicesInfo = deviceScanLine.substring(secondColonIndex + 1);
const deviceCount = parseInt(deviceCountStr) || 0;
console.log('Device count:', deviceCount);
console.log('Devices info raw:', devicesInfo);
// Update device count
const deviceCountElement = document.querySelector('.device-count');
if (deviceCountElement) {
deviceCountElement.textContent = deviceCount;
}
const devicesContent = document.querySelector('.devices-content');
if (!devicesContent) {
console.log('Devices content element not found');
return;
}
if (deviceCount === 0) {
devicesContent.innerHTML = '<div class=\"no-devices\">No devices found on network</div>';
return;
}
if (!devicesInfo || devicesInfo.trim() === '') {
devicesContent.innerHTML = \`<div class=\"no-devices\">Device count: \${deviceCount}<br>But no device details available</div>\`;
return;
}
// Parse devices info
const devices = devicesInfo.split(';').filter(d => d.length > 0);
console.log('Parsed devices array:', devices);
let devicesHtml = '';
devices.forEach((device, index) => {
console.log(\`Processing device \${index}:\`, device);
const [ip, mac, type] = device.split('|');
console.log(\` IP: '\${ip}', MAC: '\${mac}', Type: '\${type}'\`);
if (ip && mac && type) {
let icon = '🖥️'; // Default device icon
// Set icon based on device type
if (type.includes('Gateway') || type.includes('Router')) {
icon = '🌐';
} else if (type.includes('This Device')) {
icon = '📱';
} else if (type.includes('Apple')) {
icon = '🍎';
} else if (type.includes('Virtual') || type.includes('VM')) {
icon = '⚡';
} else if (type.includes('Microsoft')) {
icon = '🪟';
} else if (type.includes('Cloud')) {
icon = '☁️';
}
devicesHtml += \`
<div class=\"device-item\">
<div class=\"device-icon\">\${icon}</div>
<div class=\"device-info\">
<div class=\"device-ip\">\${ip}</div>
<div class=\"device-type\">\${type}</div>
<div class=\"device-mac\">\${mac}</div>
</div>
</div>
\`;
} else {
console.log(\` Skipping device \${index} due to missing data\`);
}
});
console.log('Generated HTML length:', devicesHtml.length);
console.log('Generated HTML preview:', devicesHtml.substring(0, 200));
if (devicesHtml.trim() === '') {
devicesContent.innerHTML = \`<div class=\"no-devices\">Found \${deviceCount} devices but failed to parse device details</div>\`;
} else {
devicesContent.innerHTML = devicesHtml;
}
}
// Populate devices list
populateDevicesList();
// Update chart colors for theme
function updateChartsForTheme() {
const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
Chart.defaults.color = isDark ? '#8b949e' : '#656d76';
Chart.defaults.borderColor = isDark ? '#30363d' : '#d0d7de';
// Redraw all charts
Chart.instances.forEach(chart => {
chart.update();
});
}
// Chart.js configuration
Chart.defaults.color = '#8b949e';
Chart.defaults.borderColor = '#30363d';
// Extract real data from log for charts
const testData = {
pass: ${pass_tests:-0},
fail: ${fail_tests:-0},
warn: ${warn_tests:-0},
info: ${info_tests:-0}
};
// Results Distribution Chart
const resultsCtx = document.getElementById('resultsChart').getContext('2d');
new Chart(resultsCtx, {
type: 'doughnut',
data: {
labels: ['Passed', 'Failed', 'Warnings', 'Info'],
datasets: [{
data: [testData.pass, testData.fail, testData.warn, testData.info],
backgroundColor: ['#3fb950', '#f85149', '#d29922', '#58a6ff'],
borderWidth: 3,
borderColor: '#0d1117',
hoverBorderWidth: 4
}]
},
options: {
responsive: true,
maintainAspectRatio: true,
aspectRatio: 1.3,
plugins: {
legend: {
position: 'bottom',
labels: {
padding: 20,
usePointStyle: true,
font: {
size: 12
}
}
}
},
cutout: '60%'
}
});
// Performance Metrics Chart with real data
const performanceCtx = document.getElementById('performanceChart').getContext('2d');
// Calculate performance scores based on actual results
let signalScore = 50;
let latencyScore = 50;
let throughputScore = 50;
let stabilityScore = 50;
let securityScore = 50;
let qualityScore = 50;
// Extract signal strength (example: -45 dBm = 85% score)
const signalMatch = '$signal_value';
if (signalMatch && signalMatch !== "-70") {
const signal = parseInt(signalMatch);
signalScore = Math.max(0, Math.min(100, (100 + signal + 30)));
}
// Extract latency (example: 25ms = 85% score)
const latencyMatch = '$latency_value';
if (latencyMatch && latencyMatch !== "50") {
const latency = parseFloat(latencyMatch);
latencyScore = Math.max(0, Math.min(100, 100 - latency));
}
// Extract throughput (example: 41 Mbps = 80% score)
const throughputMatch = '$throughput_value';
if (throughputMatch && throughputMatch !== "25") {
const throughput = parseFloat(throughputMatch);
throughputScore = Math.min(100, (throughput / 50) * 100);
}
// Calculate other scores based on test results
stabilityScore = testData.pass > 0 ? Math.min(100, (testData.pass / (testData.pass + testData.fail + testData.warn)) * 100) : 50;
securityScore = testData.fail > 0 ? Math.max(20, 100 - (testData.fail * 20)) : 85;
qualityScore = (signalScore + latencyScore + throughputScore + stabilityScore) / 4;
new Chart(performanceCtx, {
type: 'radar',
data: {
labels: ['Signal Strength', 'Latency', 'Throughput', 'Stability', 'Security', 'Quality'],
datasets: [{
label: 'Current Performance',
data: [signalScore, latencyScore, throughputScore, stabilityScore, securityScore, qualityScore],
backgroundColor: 'rgba(63, 185, 80, 0.1)',
borderColor: '#3fb950',
borderWidth: 2,
pointBackgroundColor: '#3fb950',
pointBorderColor: '#3fb950',
pointRadius: 5
}, {
label: 'Optimal Performance',
data: [95, 95, 95, 95, 95, 95],
backgroundColor: 'rgba(88, 166, 255, 0.05)',
borderColor: '#58a6ff',
borderWidth: 2,
pointBackgroundColor: '#58a6ff',
pointBorderColor: '#58a6ff',
pointRadius: 4,
borderDash: [5, 5]
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
r: {
beginAtZero: true,
max: 100,
ticks: {
stepSize: 20,
font: {
size: 10
}
},
grid: {
color: '#30363d'
}
}
},
plugins: {
legend: {
position: 'bottom',
labels: {
font: {
size: 11
}
}
}
}
}
});
// Latency Analysis Chart with real ping data
const latencyCtx = document.getElementById('latencyChart').getContext('2d');
// Extract ping statistics (simulate multiple tests)
const avgLatency = parseFloat('$latency_value');
const jitter = parseFloat('$jitter_value');
// Generate realistic ping data around the average
const pingData = [];
const jitterData = [];
for (let i = 0; i < 10; i++) {
pingData.push(avgLatency + (Math.random() - 0.5) * jitter * 2);
jitterData.push(jitter + (Math.random() - 0.5) * 1);
}
new Chart(latencyCtx, {
type: 'line',
data: {
labels: ['Test 1', 'Test 2', 'Test 3', 'Test 4', 'Test 5', 'Test 6', 'Test 7', 'Test 8', 'Test 9', 'Test 10'],
datasets: [{
label: 'Latency (ms)',
data: pingData,
borderColor: '#58a6ff',
backgroundColor: 'rgba(88, 166, 255, 0.1)',
borderWidth: 3,
fill: true,
tension: 0.4,
pointBackgroundColor: '#58a6ff',
pointBorderColor: '#0d1117',
pointBorderWidth: 2,
pointRadius: 4
}, {
label: 'Jitter (ms)',
data: jitterData,
borderColor: '#d29922',
backgroundColor: 'rgba(210, 153, 34, 0.1)',
borderWidth: 2,
fill: true,
tension: 0.4,
pointBackgroundColor: '#d29922',
pointBorderColor: '#0d1117',
pointBorderWidth: 2,
pointRadius: 3
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
title: {
display: true,
text: 'Milliseconds',
font: {
size: 12
}
},
grid: {
color: '#30363d'
}
},
x: {
grid: {
color: '#30363d'
}
}
},
plugins: {
legend: {
position: 'bottom',
labels: {
font: {
size: 11
}
}
}
}
}
});
// Throughput Comparison Chart with real data
const throughputCtx = document.getElementById('throughputChart').getContext('2d');
// Extract real throughput data from log
const tcpDown = parseFloat('$tcp_down_value');
const tcpUp = parseFloat('$tcp_up_value');
const tcpParallel = parseFloat('$tcp_parallel_value');
const speedDown = parseFloat('$speed_down_value');
const speedUp = parseFloat('$speed_up_value');
const throughputLabels = [];
const throughputValues = [];
const throughputColors = [];
if (tcpDown > 0) {
throughputLabels.push('TCP Download');
throughputValues.push(tcpDown);
throughputColors.push('rgba(63, 185, 80, 0.8)');
}
if (tcpUp > 0) {
throughputLabels.push('TCP Upload');
throughputValues.push(tcpUp);
throughputColors.push('rgba(63, 185, 80, 0.6)');
}
if (tcpParallel > 0) {
throughputLabels.push('TCP Parallel');
throughputValues.push(tcpParallel);
throughputColors.push('rgba(88, 166, 255, 0.8)');
}
if (speedDown > 0) {
throughputLabels.push('Speed Test Down');
throughputValues.push(speedDown);
throughputColors.push('rgba(210, 153, 34, 0.8)');
}
if (speedUp > 0) {
throughputLabels.push('Speed Test Up');
throughputValues.push(speedUp);
throughputColors.push('rgba(210, 153, 34, 0.6)');
}
// Add default data if no real data found
if (throughputValues.length === 0) {
throughputLabels.push('No Data Available');
throughputValues.push(0);
throughputColors.push('rgba(139, 148, 158, 0.5)');
}
new Chart(throughputCtx, {
type: 'bar',
data: {
labels: throughputLabels,
datasets: [{
label: 'Mbps',
data: throughputValues,
backgroundColor: throughputColors,
borderColor: throughputColors.map(color => color.replace('0.8', '1').replace('0.6', '1').replace('0.5', '1')),
borderWidth: 2,
borderRadius: 6,
borderSkipped: false
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
title: {
display: true,
text: 'Megabits per second',
font: {
size: 12
}
},
grid: {
color: '#30363d'
}
},
x: {
grid: {
display: false
}
}
},
plugins: {
legend: {
display: false
}
}
}
});
</script>
</body>
</html>
EOF
log_info "Relatório de teste aprimorado gerado com sucesso:"
log_info " Relatório HTML: $report_file"
log_info " Resumo em Texto: $text_report"
log_info " Log de Origem: $working_log_file"
# Open HTML report in browser if available
if command -v xdg-open &>/dev/null; then
log_info "Abrindo relatório HTML no navegador..."
xdg-open "$report_file" &>/dev/null &
elif command -v open &>/dev/null; then
log_info "Abrindo relatório HTML no navegador..."
open "$report_file" &>/dev/null &
fi
return 0
}
# --- Função Principal ---
main() {
log_debug "DEBUG: Iniciando função main com argumentos: $*" >&2
[[ ! -d "$RESULTS_DIR" ]] && setup_test_environment &>/dev/null
# Check if running with necessary privileges for full functionality
if [[ $EUID -ne 0 ]] && [[ "${1:-}" =~ ^(test-security|test-all|test-devices)$ ]]; then
log_warning "Executando sem privilégios de root. Alguns testes de segurança podem ser limitados."
log_warning "Para funcionalidade completa, execute: sudo $0 $1"
echo ""
fi
local command="${1:-help}"
log_debug "DEBUG: Processando comando: $command" >&2
case "$command" in
install)
log_debug "DEBUG: Executando install" >&2
install_testing_tools
;;
check-conn)
log_debug "DEBUG: Executando check-conn" >&2
setup_test_environment || exit 1
check_wifi_connection
;;
test-ping)
log_debug "DEBUG: Executando test-ping" >&2
setup_test_environment || exit 1
execute_ping_tests
;;
test-mtr)
log_debug "DEBUG: Executando test-mtr" >&2
setup_test_environment || exit 1
execute_mtr_tests
;;
test-iperf3)
log_debug "DEBUG: Executando test-iperf3" >&2
setup_test_environment || exit 1
execute_iperf3_tests
;;
test-quality)
log_debug "DEBUG: Executando test-quality" >&2
setup_test_environment || exit 1
execute_quality_tests
;;
test-devices)
log_debug "DEBUG: Executando test-devices" >&2
setup_test_environment || exit 1
execute_network_devices_scan
;;
test-security)
log_debug "DEBUG: Executando test-security" >&2
setup_test_environment || exit 1
execute_security_tests
;;
test-performance)
log_debug "DEBUG: Executando test-performance" >&2
setup_test_environment || exit 1
execute_performance_tests
;;
test-all)
log_debug "DEBUG: Executando test-all" >&2
execute_complete_test_suite
;;
generate-report)
log_debug "DEBUG: Executando generate-report" >&2
generate_test_report
;;
help|--help|-h)
log_debug "DEBUG: Mostrando ajuda" >&2
show_help
;;
*)
log_debug "DEBUG: Comando desconhecido: $command" >&2
log_error "Comando desconhecido: $command"
show_help
exit 1
;;
esac
log_debug "DEBUG: Comando $command concluído com sucesso" >&2
}
# Script entry point
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
log_debug "DEBUG: Script iniciado com argumentos: $*" >&2
log_debug "DEBUG: BASH_SOURCE[0]: ${BASH_SOURCE[0]}" >&2
log_debug "DEBUG: \$0: ${0}" >&2
# Trap errors to provide better debugging
trap 'echo "ERRO: Script falhou na linha $LINENO na função ${FUNCNAME[0]:-main}" >&2; exit 1' ERR
main "$@"
exit_code=$?
log_debug "DEBUG: Script concluído com código de saída: $exit_code" >&2
exit "$exit_code"
fi