Files
scan/main.py
T
2026-08-03 14:46:27 +00:00

383 lines
16 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
Скрипт для анализа VLAN на множестве роутеров OpenWrt.
Для каждого роутера:
1. Находит VLAN с наибольшим количеством портов
2. Отключает все порты в нем (кроме 6t)
3. Запускает iperf3 -c 172.20.100.203 --bidir на роутере
4. Сохраняет результат в отдельный файл
5. Включает порты обратно
"""
import paramiko
import re
import sys
import time
import datetime
import os
import threading
from concurrent.futures import ThreadPoolExecutor, as_completed
class RouterTester:
def __init__(self, host, username, password, port=22):
self.host = host
self.username = username
self.password = password
self.port = port
self.ssh = None
self.vlan_info = None
self.ports_toggled = []
def connect(self):
"""Подключается к роутеру по SSH."""
try:
self.ssh = paramiko.SSHClient()
self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
self.ssh.connect(
self.host,
port=self.port,
username=self.username,
password=self.password,
timeout=10
)
return True
except Exception as e:
print(f"{self.host}: Ошибка подключения: {e}")
return False
def close(self):
"""Закрывает SSH-соединение."""
if self.ssh:
self.ssh.close()
def exec_command(self, command):
"""Выполняет команду на роутере и возвращает вывод."""
stdin, stdout, stderr = self.ssh.exec_command(command)
output = stdout.read().decode('utf-8')
error = stderr.read().decode('utf-8')
return output, error
def get_switch_config(self):
"""Получает конфигурацию коммутатора."""
output, error = self.exec_command('swconfig dev switch0 show')
if error:
print(f"{self.host}: Ошибка swconfig: {error}")
return None
return output
def parse_vlans(self, config_text):
"""Парсит VLAN из вывода swconfig."""
vlans = []
current_vlan = None
lines = config_text.split('\n')
for line in lines:
line = line.strip()
vlan_match = re.match(r'^VLAN\s+(\d+):$', line)
if vlan_match:
if current_vlan:
vlans.append(current_vlan)
current_vlan = {'vid': None, 'ports': [], 'vlan_num': int(vlan_match.group(1))}
continue
if current_vlan is not None:
vid_match = re.match(r'^vid:\s+(\d+)$', line)
if vid_match:
current_vlan['vid'] = int(vid_match.group(1))
continue
ports_match = re.match(r'^ports:\s+(.+)$', line)
if ports_match:
ports_str = ports_match.group(1)
current_vlan['ports'] = [p.strip() for p in ports_str.split() if p.strip()]
continue
if current_vlan and current_vlan.get('vid') is not None:
vlans.append(current_vlan)
return vlans
def find_vlan_with_most_ports(self, vlans):
"""Находит VLAN с максимальным количеством портов."""
if not vlans:
return None
return sorted(vlans, key=lambda v: len(v['ports']), reverse=True)[0]
def get_ports_to_toggle(self, ports):
"""Возвращает порты для отключения (исключая 6t)."""
return [p for p in ports if p != '6t']
def disable_ports(self, ports):
"""Отключает указанные порты."""
print(f" {self.host}: Отключаю порты: {' '.join(ports)}")
for port in ports:
port_num = port.replace('t', '')
command = f'swconfig dev switch0 port {port_num} set port_disabled 1'
self.exec_command(command)
self.exec_command('swconfig dev switch0 set apply')
self.ports_toggled = ports
time.sleep(1)
def enable_ports(self):
"""Включает порты обратно."""
if not self.ports_toggled:
return
print(f" {self.host}: Включаю порты: {' '.join(self.ports_toggled)}")
for port in self.ports_toggled:
port_num = port.replace('t', '')
command = f'swconfig dev switch0 port {port_num} set port_disabled 0'
self.exec_command(command)
self.exec_command('swconfig dev switch0 set apply')
time.sleep(1)
def run_iperf_test(self, server_ip, output_file, duration=10, port=5201):
"""
Запускает iperf3 -c на роутере с флагом --bidir.
"""
print(f" {self.host}: Запуск iperf3 -c {server_ip} --bidir (duration={duration}с)")
# Временный файл на роутере
temp_file = '/tmp/iperf3_result.log'
# Команда с флагом --bidir
command = f'iperf3 -c {server_ip} -t {duration} -p {port} --bidir > {temp_file} 2>&1'
# Запускаем iperf3 в фоновом режиме
self.exec_command(command)
# Ждем завершения теста
time.sleep(duration + 3)
# Проверяем, завершился ли процесс
output, _ = self.exec_command('ps aux | grep "iperf3 -c" | grep -v grep')
if output.strip():
print(f" {self.host}: ⚠️ iperf3 все еще работает, принудительно останавливаю...")
self.exec_command('pkill -f "iperf3 -c"')
time.sleep(1)
# Читаем результат
output, error = self.exec_command(f'cat {temp_file}')
self.exec_command(f'rm -f {temp_file}')
if error:
print(f" {self.host}: ❌ Ошибка при чтении результата: {error}")
return False
# Сохраняем результат
try:
with open(output_file, 'w') as f:
f.write(f"=== iperf3 тест (bidir) ===\n")
f.write(f"Роутер: {self.host}\n")
f.write(f"Сервер: {server_ip}:{port}\n")
f.write(f"Режим: bidirectional (--bidir)\n")
f.write(f"Длительность: {duration} сек\n")
f.write(f"Время запуска: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
f.write("=" * 50 + "\n\n")
f.write(output)
# Проверяем наличие ошибок в выводе
if "error" in output.lower() or "failed" in output.lower():
print(f" {self.host}: ⚠️ iperf3 завершился с ошибкой")
return False
print(f" {self.host}: ✅ iperf3 тест завершен успешно")
return True
except Exception as e:
print(f" {self.host}: ❌ Ошибка при сохранении результата: {e}")
return False
def check_iperf3(self):
"""Проверяет наличие iperf3 на роутере."""
output, _ = self.exec_command('which iperf3')
return bool(output.strip())
def process(self, server_ip, output_dir, duration=10, port=5201):
"""
Полный процесс обработки одного роутера.
"""
print(f"\n{'=' * 50}")
print(f"🔍 Обработка роутера: {self.host}")
print(f"{'=' * 50}")
# Подключаемся
if not self.connect():
return False
try:
# Проверяем iperf3
if not self.check_iperf3():
print(f" {self.host}: ❌ iperf3 не установлен")
print(f" {self.host}: Установите: opkg update && opkg install iperf3")
return False
# Получаем конфигурацию VLAN
config = self.get_switch_config()
if config is None:
return False
# Парсим VLAN
vlans = self.parse_vlans(config)
if not vlans:
print(f" {self.host}: ❌ VLAN не найдены")
return False
# Находим VLAN с наибольшим количеством портов
best_vlan = self.find_vlan_with_most_ports(vlans)
print(f" {self.host}: Выбран VLAN {best_vlan['vlan_num']} (vid: {best_vlan['vid']})")
print(f" {self.host}: Порты: {' '.join(best_vlan['ports'])}")
# Определяем порты для отключения
ports_to_toggle = self.get_ports_to_toggle(best_vlan['ports'])
if not ports_to_toggle:
print(f" {self.host}: ⚠️ Нет портов для отключения (только 6t)")
return False
print(f" {self.host}: Будут отключены порты: {' '.join(ports_to_toggle)}")
# Генерируем имя файла
timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
output_file = os.path.join(output_dir, f'iperf3_{self.host}_{timestamp}.log')
# Шаг 1: Отключаем порты
print(f" {self.host}: --- Шаг 1: Отключение портов ---")
self.disable_ports(ports_to_toggle)
# Шаг 2: Запускаем iperf3 с --bidir
print(f" {self.host}: --- Шаг 2: iperf3 тест ---")
success = self.run_iperf_test(server_ip, output_file, duration, port)
# Шаг 3: Включаем порты обратно
print(f" {self.host}: --- Шаг 3: Включение портов ---")
self.enable_ports()
return success
except Exception as e:
print(f" {self.host}: ❌ Критическая ошибка: {e}")
# Пытаемся включить порты в случае ошибки
try:
self.enable_ports()
except:
pass
return False
finally:
self.close()
def main():
# Настройки iperf3
IPERF_SERVER = '172.20.100.203'
IPERF_PORT = 5201
IPERF_DURATION = 10
# Директория для результатов
OUTPUT_DIR = './iperf_results'
if not os.path.exists(OUTPUT_DIR):
os.makedirs(OUTPUT_DIR)
# Список роутеров
routers = [
#{'host': '192.168.16.4', 'username': 'root', 'password': 'root'},
#{'host': '192.168.16.8', 'username': 'root', 'password': 'root'},
#{'host': '192.168.16.9', 'username': 'root', 'password': 'root'},
#{'host': '192.168.16.10', 'username': 'root', 'password': 'root'},
#{'host': '192.168.16.11', 'username': 'root', 'password': 'root'},
#{'host': '192.168.16.27', 'username': 'root', 'password': 'root'},
#{'host': '192.168.16.28', 'username': 'root', 'password': 'root'},
#{'host': '192.168.16.29', 'username': 'root', 'password': 'root'},
#{'host': '192.168.16.30', 'username': 'root', 'password': 'root'},
#{'host': '192.168.16.32', 'username': 'root', 'password': 'root'},
#{'host': '192.168.16.34', 'username': 'root', 'password': 'root'},
#{'host': '192.168.16.35', 'username': 'root', 'password': 'root'},
#{'host': '192.168.16.38', 'username': 'root', 'password': 'root'},
#{'host': '192.168.16.39', 'username': 'root', 'password': 'root'},
#{'host': '192.168.16.40', 'username': 'root', 'password': 'root'},
#{'host': '192.168.16.41', 'username': 'root', 'password': 'root'},
#{'host': '192.168.16.42', 'username': 'root', 'password': 'root'},
#{'host': '192.168.16.43', 'username': 'root', 'password': 'root'},
#{'host': '192.168.16.44', 'username': 'root', 'password': 'root'},
#{'host': '192.168.16.45', 'username': 'root', 'password': 'root'},
#{'host': '192.168.16.49', 'username': 'root', 'password': 'root'},
#{'host': '192.168.16.50', 'username': 'root', 'password': 'root'},
#{'host': '192.168.16.51', 'username': 'root', 'password': 'root'},
#{'host': '192.168.16.55', 'username': 'root', 'password': 'root'},
#{'host': '192.168.16.57', 'username': 'root', 'password': 'root'},
#{'host': '192.168.16.58', 'username': 'root', 'password': 'root'},
#{'host': '192.168.16.62', 'username': 'root', 'password': 'root'},
#{'host': '192.168.16.65', 'username': 'root', 'password': 'root'},
#{'host': '192.168.16.66', 'username': 'root', 'password': 'root'},
#{'host': '192.168.16.67', 'username': 'root', 'password': 'root'},
#{'host': '192.168.16.68', 'username': 'root', 'password': 'root'},
#{'host': '192.168.16.69', 'username': 'root', 'password': 'root'},
#{'host': '192.168.16.70', 'username': 'root', 'password': 'root'},
#{'host': '192.168.16.76', 'username': 'root', 'password': 'root'},
#{'host': '192.168.16.81', 'username': 'root', 'password': 'root'},
{'host': '192.168.16.82', 'username': 'root', 'password': 'root'},
{'host': '192.168.16.84', 'username': 'root', 'password': 'root'},
]
print(f"🚀 Запуск тестирования {len(routers)} роутеров")
print(f"📡 iperf3 сервер: {IPERF_SERVER}:{IPERF_PORT}")
print(f"⏱️ Длительность теста: {IPERF_DURATION} сек")
print(f"📁 Результаты сохраняются в: {OUTPUT_DIR}")
print("=" * 60)
# Статистика
success_count = 0
fail_count = 0
results = []
# Обрабатываем роутеры последовательно
for i, router_config in enumerate(routers, 1):
print(f"\n📌 [{i}/{len(routers)}] Обработка роутера {router_config['host']}")
tester = RouterTester(
router_config['host'],
router_config['username'],
router_config['password']
)
success = tester.process(
IPERF_SERVER,
OUTPUT_DIR,
duration=IPERF_DURATION,
port=IPERF_PORT
)
if success:
success_count += 1
results.append(f"{router_config['host']}: УСПЕШНО")
else:
fail_count += 1
results.append(f"{router_config['host']}: ОШИБКА")
# Небольшая пауза между роутерами
if i < len(routers):
print(f"\n⏳ Пауза 3 секунды перед следующим роутером...")
time.sleep(3)
# Итоговая статистика
print("\n" + "=" * 60)
print("📊 ИТОГОВАЯ СТАТИСТИКА")
print("=" * 60)
for result in results:
print(result)
print("-" * 60)
print(f"✅ Успешно: {success_count}")
print(f"❌ Ошибок: {fail_count}")
print(f"📁 Все результаты сохранены в: {OUTPUT_DIR}")
print("=" * 60)
if __name__ == '__main__':
main()