From 1d4550d74815ff49c0bdcd85a4f63c8172b47755 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B5=D0=B9=20=D0=A5=D0=B0?= =?UTF-8?q?=D0=BB=D0=B5=D1=86=D0=BA=D0=B8=D0=B9?= Date: Mon, 3 Aug 2026 14:46:27 +0000 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D1=82?= =?UTF-8?q?=D1=8C=20main.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 383 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 383 insertions(+) create mode 100644 main.py diff --git a/main.py b/main.py new file mode 100644 index 0000000..8dceeb1 --- /dev/null +++ b/main.py @@ -0,0 +1,383 @@ +#!/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() \ No newline at end of file