64883d4f02
Enable interpretation of backslash escapes in log messages across all debian scripts for consistent formatting and proper escape sequence handling
443 lines
15 KiB
Bash
443 lines
15 KiB
Bash
#!/bin/bash
|
|
# Script Dinâmico de Configuração de Rede para Raspberry Pi
|
|
# Localização: /usr/local/bin/configure_network.sh
|
|
# Descrição: Configura MAC address persistente para interface eth0 e monitoriza estado da rede
|
|
|
|
# --- Configurações ---
|
|
LOG_FILE="/var/log/network_config.log"
|
|
UDEV_RULES_FILE="/etc/udev/rules.d/81-mac-spoof.rules"
|
|
IFACE="eth0"
|
|
WLAN_IFACE="wlan0"
|
|
MAX_LOG_SIZE_KB=1024 # 1MB
|
|
|
|
# --- Funções Auxiliares ---
|
|
|
|
# Função de Log (modificada para usar logger)
|
|
log() {
|
|
local message="$1"
|
|
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
|
|
|
# Escreve no ficheiro de log
|
|
echo -e "$timestamp: $message" | tee -a "$LOG_FILE"
|
|
|
|
# Envia para o syslog (opcional)
|
|
logger -t "network_config" "$message"
|
|
}
|
|
|
|
# Função para limitar o tamanho do log
|
|
limit_log_size() {
|
|
local log_file="$1"
|
|
local max_size_kb="$2"
|
|
local max_size_bytes=$((max_size_kb * 1024))
|
|
|
|
if [ -f "$log_file" ]; then
|
|
local current_size=$(stat -c %s "$log_file" 2>/dev/null || wc -c < "$log_file" 2>/dev/null)
|
|
|
|
if [ "$current_size" -gt "$max_size_bytes" ]; then
|
|
log "Aviso: Ficheiro de log $log_file excedeu $max_size_kb KB. A truncar..."
|
|
tail -n 500 "$log_file" > "${log_file}.tmp" && mv "${log_file}.tmp" "$log_file"
|
|
log "Ficheiro de log truncado. As últimas 500 linhas foram mantidas."
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# Função para verificar se a interface tem IP
|
|
has_ip_address() {
|
|
local iface="$1"
|
|
if ip addr show "$iface" | grep -q "inet "; then
|
|
return 0
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Função para perguntar confirmação ao utilizador
|
|
ask_confirmation() {
|
|
local message="$1"
|
|
local default="$2" # "y" or "n"
|
|
while true; do
|
|
if [ "$default" = "y" ]; then
|
|
read -p "$message [Y/n]: " choice < /dev/tty
|
|
else
|
|
read -p "$message [y/N]: " choice < /dev/tty
|
|
fi
|
|
# Default choice if user just presses Enter
|
|
if [ -z "$choice" ]; then
|
|
choice="$default"
|
|
fi
|
|
case "$choice" in
|
|
y|Y|yes|Yes|YES)
|
|
return 0
|
|
;;
|
|
n|N|no|No|NO)
|
|
return 1
|
|
;;
|
|
*)
|
|
echo "Por favor, responda sim ou não."
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
# Função para gerar MAC único baseado no Serial do CPU
|
|
generate_unique_mac() {
|
|
local serial
|
|
serial=$(awk '/Serial/ {print $3}' /proc/cpuinfo | tr -d ' ')
|
|
|
|
if [ -z "$serial" ]; then
|
|
# Fallback: generate random MAC if serial not available
|
|
printf '%02X:%02X:%02X:%02X:%02X:%02X' $((0x02 | (RANDOM % 256) & 0xFE)) \
|
|
$((RANDOM % 256)) \
|
|
$((RANDOM % 256)) \
|
|
$((RANDOM % 256)) \
|
|
$((RANDOM % 256)) \
|
|
$((RANDOM % 256))
|
|
else
|
|
local hash
|
|
hash=$(echo -n "$serial-dm9601" | md5sum | cut -c1-10)
|
|
echo "02:${hash:0:2}:${hash:2:2}:${hash:4:2}:${hash:6:2}:${hash:8:2}"
|
|
fi
|
|
}
|
|
|
|
# Função para obter o MAC atual da interface
|
|
get_current_mac() {
|
|
local interface="$1"
|
|
ip link show "$interface" | awk '/ether/ {print $2}' | tr '[:upper:]' '[:lower:]'
|
|
}
|
|
|
|
# Função para definir MAC imediatamente
|
|
set_mac() {
|
|
local interface="$1"
|
|
local mac="$2"
|
|
|
|
if ! ip link show "$interface" &> /dev/null; then
|
|
log "Interface $interface não encontrada em set_mac."
|
|
return 1
|
|
fi
|
|
|
|
log "A definir MAC $mac em $interface..."
|
|
|
|
if ip link set "$interface" down && \
|
|
ip link set dev "$interface" address "$mac" && \
|
|
ip link set "$interface" up; then
|
|
log "MAC alterado com sucesso na sessão atual."
|
|
return 0
|
|
else
|
|
log "Falha ao alterar MAC na sessão atual."
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Função para criar/atualizar regra Udev
|
|
setup_udev_persistence() {
|
|
local interface="$1"
|
|
local target_mac="$2"
|
|
local original_mac
|
|
local rule_content
|
|
|
|
original_mac=$(get_current_mac "$interface")
|
|
target_mac=$(echo "$target_mac" | tr '[:upper:]' '[:lower:]')
|
|
|
|
if [ -z "$original_mac" ]; then
|
|
log "Erro crítico: Não foi possível obter o MAC original para criar regra udev."
|
|
return 1
|
|
fi
|
|
|
|
# Cria o conteúdo da regra
|
|
rule_content="ACTION==\"add\", SUBSYSTEM==\"net\", ATTR{address}==\"$original_mac\", RUN+=\"/usr/bin/ip link set dev \$name address $target_mac\""
|
|
|
|
# Cria diretório se não existir
|
|
mkdir -p "$(dirname "$UDEV_RULES_FILE")"
|
|
|
|
# Escreve no ficheiro de regras
|
|
echo "$rule_content" > "$UDEV_RULES_FILE"
|
|
|
|
# Verifica se o ficheiro foi criado com sucesso
|
|
if [ -f "$UDEV_RULES_FILE" ]; then
|
|
log "Sucesso: Ficheiro de regras Udev criado/atualizado em $UDEV_RULES_FILE."
|
|
log "Conteúdo da regra: $rule_content"
|
|
else
|
|
log "Erro: Falha ao criar/atualizar o ficheiro de regras Udev em $UDEV_RULES_FILE."
|
|
return 1
|
|
fi
|
|
|
|
# Recarrega as regras do udev
|
|
if udevadm control --reload-rules && udevadm trigger; then
|
|
log "Regras Udev recarregadas e aplicadas com sucesso."
|
|
return 0
|
|
else
|
|
log "Erro: Falha ao recarregar/aplicar regras Udev."
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Função para verificar se a persistência está configurada corretamente
|
|
is_persistence_configured() {
|
|
local current_mac
|
|
local udev_mac
|
|
|
|
if [ ! -f "$UDEV_RULES_FILE" ] || [ ! -s "$UDEV_RULES_FILE" ]; then
|
|
log "Ficheiro de persistência não encontrado ou vazio."
|
|
return 1
|
|
fi
|
|
|
|
current_mac=$(get_current_mac "$IFACE")
|
|
udev_mac=$(grep -oP 'address \K([0-9a-f]{2}:){5}[0-9a-f]{2}' "$UDEV_RULES_FILE")
|
|
|
|
if [ "$current_mac" = "$udev_mac" ]; then
|
|
log "Persistência verificada: MAC atual ($current_mac) coincide com regra Udev."
|
|
return 0
|
|
else
|
|
log "Persistência desatualizada: MAC atual ($current_mac) não coincide com regra Udev ($udev_mac)."
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Função para desativar Wi-Fi
|
|
disable_wifi() {
|
|
# Cria diretório de log se não existir
|
|
mkdir -p /var/log
|
|
|
|
# Aguarda 60 segundos antes de verificar IP e desativar Wi-Fi
|
|
log "A aguardar 60 segundos antes de verificar IP de $IFACE e desativar Wi-Fi..."
|
|
sleep 60
|
|
|
|
# Verifica se eth0 tem IP antes de desativar Wi-Fi
|
|
if has_ip_address "$IFACE"; then
|
|
if ip link show "$WLAN_IFACE" &> /dev/null; then
|
|
if ip link set "$WLAN_IFACE" down; then
|
|
log "Wi-Fi ($WLAN_IFACE) desativado com sucesso ($IFACE tem IP)"
|
|
else
|
|
log "Falha ao desativar Wi-Fi ($WLAN_IFACE)"
|
|
fi
|
|
else
|
|
log "Interface $WLAN_IFACE não encontrada (Wi-Fi já desativado?)"
|
|
fi
|
|
else
|
|
log "$IFACE não tem endereço IP. Wi-Fi permanece ativo."
|
|
fi
|
|
}
|
|
|
|
# Função para forçar renovação de IP
|
|
force_ip_renewal() {
|
|
local iface="$1"
|
|
local success=0
|
|
|
|
# Tentativa 1: Reiniciar dhcpcd (Raspberry Pi padrão)
|
|
if systemctl is-active --quiet dhcpcd; then
|
|
log "Tentativa 1/3: Reiniciando dhcpcd para renovar IP em $iface..."
|
|
if systemctl restart dhcpcd; then
|
|
log "dhcpcd reiniciado com sucesso."
|
|
success=1
|
|
else
|
|
log "Falha ao reiniciar dhcpcd."
|
|
fi
|
|
fi
|
|
|
|
# Tentativa 2: Usar dhclient (se disponível)
|
|
if [ "$success" -eq 0 ] && command -v dhclient > /dev/null; then
|
|
log "Tentativa 2/3: Usando dhclient para renovar IP em $iface..."
|
|
if dhclient -r "$iface" && dhclient "$iface"; then
|
|
log "IP renovado com sucesso usando dhclient."
|
|
success=1
|
|
else
|
|
log "Falha ao renovar IP usando dhclient."
|
|
fi
|
|
fi
|
|
|
|
# Tentativa 3: Reiniciar a interface
|
|
if [ "$success" -eq 0 ]; then
|
|
log "Tentativa 3/3: Reiniciando interface $iface..."
|
|
if ip link set "$iface" down && sleep 2 && ip link set "$iface" up; then
|
|
log "Interface $iface reiniciada. IP deve ser atribuído em breve."
|
|
success=1
|
|
else
|
|
log "Falha ao reiniciar interface $iface."
|
|
fi
|
|
fi
|
|
|
|
return "$success"
|
|
}
|
|
|
|
# Tarefa em Background para monitorizar rede
|
|
monitor_network_task() {
|
|
log "Monitor: A iniciar monitorização contínua da rede..."
|
|
|
|
while true; do
|
|
sleep 60 # Verifica a cada 60 segundos
|
|
|
|
if ip link show "$IFACE" &> /dev/null; then
|
|
# Verifica se eth0 está sem cabo (NO-CARRIER)
|
|
if ip link show "$IFACE" | grep -q "NO-CARRIER"; then
|
|
log "Monitor: Interface $IFACE sem cabo (NO-CARRIER). A ativar Wi-Fi..."
|
|
if ip link show "$WLAN_IFACE" &> /dev/null; then
|
|
if ip link set "$WLAN_IFACE" up; then
|
|
log "Monitor: Wi-Fi ($WLAN_IFACE) ativado com sucesso."
|
|
else
|
|
log "Monitor: Falha ao ativar Wi-Fi ($WLAN_IFACE)."
|
|
fi
|
|
else
|
|
log "Monitor: Interface $WLAN_IFACE não encontrada."
|
|
fi
|
|
else
|
|
# eth0 está com cabo, verifica se tem IP
|
|
if has_ip_address "$IFACE"; then
|
|
log "Monitor: Interface $IFACE ativa e com IP. A verificar Wi-Fi..."
|
|
if ip link show "$WLAN_IFACE" &> /dev/null && ip link show "$WLAN_IFACE" | grep -q "UP"; then
|
|
if ip link set "$WLAN_IFACE" down; then
|
|
log "Monitor: Wi-Fi ($WLAN_IFACE) desativado (eth0 tem IP)."
|
|
else
|
|
log "Monitor: Falha ao desativar Wi-Fi ($WLAN_IFACE)."
|
|
fi
|
|
else
|
|
log "Monitor: Wi-Fi ($WLAN_IFACE) já está desativado ou não encontrado."
|
|
fi
|
|
else
|
|
log "Monitor: Interface $IFACE ativa mas sem IP. Tentando forçar renovação de IP..."
|
|
if force_ip_renewal "$IFACE"; then
|
|
log "Monitor: Renovação de IP bem-sucedida. Wi-Fi permanece ativo até confirmação."
|
|
else
|
|
log "Monitor: Falha ao renovar IP."
|
|
if ip link show "$WLAN_IFACE" &> /dev/null && ip link show "$WLAN_IFACE" | grep -q "DOWN"; then
|
|
log "Ativando Wi-Fi como fallback..."
|
|
if ip link set "$WLAN_IFACE" up; then
|
|
log "Monitor: Wi-Fi ($WLAN_IFACE) ativado como fallback."
|
|
else
|
|
log "Monitor: Falha ao ativar Wi-Fi ($WLAN_IFACE)."
|
|
fi
|
|
fi
|
|
fi
|
|
fi
|
|
fi
|
|
else
|
|
log "Monitor: Interface $IFACE não encontrada."
|
|
fi
|
|
done
|
|
}
|
|
|
|
# Função principal para configurar MAC
|
|
configure_mac() {
|
|
local target_mac="$1"
|
|
|
|
# Validação do MAC
|
|
if [[ ! "$target_mac" =~ ^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$ ]]; then
|
|
log "MAC Address inválido: $target_mac"
|
|
return 1
|
|
fi
|
|
|
|
log "A configurar sistema com MAC: $target_mac"
|
|
|
|
# Aplica o MAC na sessão atual
|
|
if set_mac "$IFACE" "$target_mac"; then
|
|
# Atualiza a regra Udev independentemente do estado anterior
|
|
if setup_udev_persistence "$IFACE" "$target_mac"; then
|
|
log "Configuração de MAC e persistência concluída com sucesso."
|
|
return 0
|
|
else
|
|
log "Aviso: MAC definido mas persistência falhou."
|
|
return 1
|
|
fi
|
|
else
|
|
log "Aviso: Falha ao definir MAC em tempo real, mas a tentar criar persistência..."
|
|
if setup_udev_persistence "$IFACE" "$target_mac"; then
|
|
log "Persistência criada, mas MAC não foi aplicado na sessão atual."
|
|
return 1
|
|
else
|
|
log "Erro: Falha completa na configuração de MAC."
|
|
return 1
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# --- Execução Principal ---
|
|
if [ "$(id -u)" -ne 0 ]; then
|
|
echo "Este script deve ser executado como root." >&2
|
|
exit 1
|
|
fi
|
|
|
|
mkdir -p "$(dirname "$LOG_FILE")"
|
|
limit_log_size "$LOG_FILE" "$MAX_LOG_SIZE_KB" # Verifica o tamanho do log no início
|
|
|
|
# 1. Verificar se a interface existe
|
|
if ! ip link show "$IFACE" &> /dev/null; then
|
|
log "Erro: Interface $IFACE não disponível. A sair."
|
|
exit 1
|
|
fi
|
|
|
|
# 2. Verificar se já existe configuração de persistência
|
|
if is_persistence_configured; then
|
|
log "Configuração de persistência verificada. A saltar configuração inicial."
|
|
|
|
# Verifica se eth0 tem IP antes de desativar Wi-Fi
|
|
if has_ip_address "$IFACE"; then
|
|
if ! pgrep -f "disable_wifi" > /dev/null; then
|
|
disable_wifi &
|
|
else
|
|
log "disable_wifi já está em execução."
|
|
fi
|
|
else
|
|
log "$IFACE não tem endereço IP. Wi-Fi permanece ativo."
|
|
fi
|
|
|
|
monitor_network_task &
|
|
exit 0
|
|
fi
|
|
|
|
# 3. Determinar o MAC Address
|
|
TARGET_MAC=""
|
|
|
|
# Verificar argumento ($1)
|
|
if [ -n "$1" ]; then
|
|
TARGET_MAC="$1"
|
|
log "MAC definido via argumento: $TARGET_MAC"
|
|
else
|
|
# Verificar se é boot (não interativo e sem /dev/tty disponível)
|
|
if [ ! -t 0 ] && [ ! -e /dev/tty ]; then
|
|
# Modo boot: Não fazer nada, assumindo que já foi configurado
|
|
log "Modo boot: A saltar configuração de MAC (assumindo que já foi configurado)"
|
|
|
|
if has_ip_address "$IFACE"; then
|
|
if ! pgrep -f "disable_wifi" > /dev/null; then
|
|
disable_wifi &
|
|
else
|
|
log "disable_wifi já está em execução."
|
|
fi
|
|
else
|
|
log "$IFACE não tem endereço IP. Wi-Fi permanece ativo."
|
|
fi
|
|
exit 0
|
|
else
|
|
# Modo interativo: Perguntar ao utilizador o que fazer
|
|
if ask_confirmation "Deseja configurar um novo MAC para a interface $IFACE?" "y"; then
|
|
if ask_confirmation "Deseja gerar um novo MAC único automaticamente?" "y"; then
|
|
TARGET_MAC=$(generate_unique_mac)
|
|
log "MAC gerado automaticamente: $TARGET_MAC"
|
|
else
|
|
# Pergunta para introduzir MAC manualmente
|
|
while true; do
|
|
read -p "Introduza o MAC (formato XX:XX:XX:XX:XX:XX): " TARGET_MAC < /dev/tty
|
|
if [[ "$TARGET_MAC" =~ ^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$ ]]; then
|
|
break
|
|
else
|
|
echo "Formato de MAC inválido. Por favor, introduza no formato XX:XX:XX:XX:XX:XX."
|
|
fi
|
|
done
|
|
fi
|
|
else
|
|
log "Configuração de MAC cancelada pelo utilizador"
|
|
exit 0
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# 4. Configurar MAC e persistência
|
|
if configure_mac "$TARGET_MAC"; then
|
|
log "Configuração concluída com sucesso."
|
|
else
|
|
log "Aviso: Configuração concluída com alguns erros."
|
|
fi
|
|
|
|
# 5. Lançar monitor de rede
|
|
monitor_network_task &
|
|
|
|
exit 0 |