diff --git a/dm-light/WgRoute.py b/dm-light/WgRoute.py deleted file mode 100644 index bda85b8..0000000 --- a/dm-light/WgRoute.py +++ /dev/null @@ -1,92 +0,0 @@ -import dns.resolver -import os -from concurrent.futures import ThreadPoolExecutor -import glob - -routerconf = "work.conf" - -# Сброс счетчиков, объявление переменных -successful_resolutions = 0 -failed_resolutions = 0 -unresolved_domains_file_name = "unresolved_domains.txt" # Имя файла с необработанными доменами -result_file_name = "result.txt" # Имя файла результатов -domain_files_pattern = "domain/*.txt" # Имя папки с txt файлами DNS -pub_dns = "8.8.8.8" # Публичный DNS № 1 - - -# Функция записи разрешенных IP-адресов в файл .conf -def write_allowed_ips(resolved_ips): - # Открываем файл routerconf для чтения и чтения содержимого - with open(routerconf, 'r') as f: - lines = f.readlines() - - # Находим строку с "AllowedIPs = " и удаляем имеющиеся IP-адреса после нее - for i, line in enumerate(lines): - if line.startswith("AllowedIPs = "): - ips = ", ".join(resolved_ips) + "/32\n" - lines[i] = "AllowedIPs = " + ips - break - - # Записываем обновленное содержимое в файл - with open(routerconf, 'w') as f: - f.writelines(lines) - - -# Функция разрешения DNS-имени с использованием заданного DNS-сервера -def resolve_dns(domain): - global successful_resolutions, failed_resolutions - ip_addresses = [] # Изменение на список для хранения нескольких IP-адресов - try: - answers = dns.resolver.resolve(domain, 'A') - for rdata in answers: - ip_address = rdata.address - successful_resolutions += 1 - print(f"{domain} IP адрес: {ip_address}") - ip_addresses.append(ip_address) # Добавление IP-адреса в список - return domain, ip_addresses # Возвращаем домен и список IP-адресов - except dns.resolver.NXDOMAIN: - failed_resolutions += 1 - print(f"Не удалось обработать домен: {domain}, Не существует") - return None, None - except dns.resolver.NoAnswer: - failed_resolutions += 1 - print(f"Не удалось обработать домен: {domain}, Нет ответа") - return None, None - except dns.resolver.Timeout: - failed_resolutions += 1 - print(f"Не удалось обработать домен: {domain}, Тайм-аут") - return None, None - - -# Основная -def resolve_dns_in_threads(domain_files, num_threads=20): - global successful_resolutions, failed_resolutions - resolved_ips = set() # Создание множества для хранения IP обработанных доменов - - # Выполнение резолва в нескольких потоках - with ThreadPoolExecutor(max_workers=num_threads) as executor: - futures = [] - for domain_file in domain_files: - if os.path.isfile(domain_file): - with open(domain_file, 'r', encoding='utf-8-sig') as f: - domains = [line.strip() for line in f] - for domain in domains: - future = executor.submit(resolve_dns, domain) - futures.append(future) - - for future in futures: - domain, ip_addresses = future.result() - if ip_addresses: - resolved_ips.update(ip_addresses) # Добавление всех IP-адресов в множество - - # Запись разрешенных IP-адресов в файл routerconf - write_allowed_ips(resolved_ips) # Запись разрешенных IP-адресов в файл routerconf - - print(f"\nСопоставлено IP адресов доменам:", successful_resolutions) - print(f"Не удалось обработать доменных имен:", failed_resolutions) - - -if __name__ == "__main__": - script_directory = os.path.dirname(os.path.abspath(__file__)) # Получение пути к директории с исполняемым файлом - domain_files = glob.glob(os.path.join(script_directory, domain_files_pattern)) # Создание списка txt файлов в директории "domain" - resolve_dns_in_threads(domain_files) # Вызов основной функции diff --git a/dm-light/config.ini b/dm-light/config.ini new file mode 100644 index 0000000..85212e8 --- /dev/null +++ b/dm-light/config.ini @@ -0,0 +1,9 @@ +[Router] +router_ip = 192.168.1.1 +router_port = 22 +login = user +password = secret +eth_id = Wireguard0 +domain_folder = domain +public_dns_1 = 8.8.8.8 +public_dns_2 = 8.8.4.4 diff --git a/dm-light/dm-light-router.py b/dm-light/dm-light-router.py new file mode 100644 index 0000000..df7fb0e --- /dev/null +++ b/dm-light/dm-light-router.py @@ -0,0 +1,201 @@ +import asyncio +import configparser +import logging +import os +import socket +from typing import List, Optional, Dict + +import asyncssh +from dnslib import DNSRecord + + +# Настройка логгирования +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') + + +# Множество для хранения уникальных IP-адресов +unique_ip_addresses = set() + + +# Чтение конфигурационного файла +def read_config(filename: str) -> Optional[configparser.SectionProxy]: + config = configparser.ConfigParser() + try: + config.read(filename) + logging.info(f"Файл конфигурации {filename} загружен.") + return config['Router'] + except KeyError: + logging.error(f"Секция 'Router' отсутствует в файле конфигурации {filename}.") + return None + except Exception as e: + logging.error(f"Ошибка загрузки файла конфигурации {filename}: {e}") + return None + + +# Загрузка доменных имен из файлов в папке +def load_domain_names_from_folder(folder: str) -> List[str]: + domains = [] + try: + for filename in os.listdir(folder): + if filename.endswith('.txt'): + with open(os.path.join(folder, filename), 'r', encoding='utf-8-sig') as file: + domains.extend([line.strip() for line in file if line.strip()]) + logging.info(f"Доменные имена загружены из папки {folder}.") + except Exception as e: + logging.error(f"Ошибка загрузки доменных имен из папки {folder}: {e}") + return domains + + +# Отправка DNS запроса к публичному DNS серверу +async def send_dns_query(domain: str, dns_servers: List[str]) -> List[str]: + loop = asyncio.get_event_loop() + resolved_addresses = [] + for current_dns in dns_servers: + try: + query = DNSRecord.question(domain) + data = query.pack() + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as client_socket: + client_socket.settimeout(2) + await loop.run_in_executor(None, client_socket.sendto, data, (current_dns, 53)) + response, _ = await loop.run_in_executor(None, client_socket.recvfrom, 1024) + dns_record = DNSRecord.parse(response) + for r in dns_record.rr: + if r.rtype == 1: # A record + resolved_addresses.append(str(r.rdata)) + except socket.timeout: + logging.warning(f"Тайм-аут при отправке DNS запроса к {current_dns}") + except Exception as e: + logging.error(f"Ошибка отправки DNS запроса: {e}") + return resolved_addresses + + +# Поиск DNS имени в фильтре +def compare_dns(f_domain: str, domain_list: List[str]) -> bool: + name_parts = f_domain.rstrip('.').split('.') + for filter_domain in domain_list: + filter_domain_parts = filter_domain.split('.') + if len(name_parts) < len(filter_domain_parts): + continue + match = all(name_parts[i] == filter_domain_parts[i] for i in range(-1, -len(filter_domain_parts) - 1, -1)) + if match: + return True + return False + + +# Класс для пула SSH соединений +class SSHConnectionPool: + def __init__(self, max_size: int): + self.pool = asyncio.Queue(max_size) + self.max_size = max_size + self.size = 0 + + async def get_connection(self, router_ip: str, ssh_port: int, login: str, + password: str) -> asyncssh.SSHClientConnection: + if self.pool.empty() and self.size < self.max_size: + connection = await asyncssh.connect( + router_ip, port=ssh_port, username=login, password=password, known_hosts=None + ) + self.size += 1 + return connection + else: + return await self.pool.get() + + async def release_connection(self, connection: asyncssh.SSHClientConnection): + await self.pool.put(connection) + + async def close_all(self): + while not self.pool.empty(): + connection = await self.pool.get() + connection.close() + self.size -= 1 + + +# Инициализация пула SSH соединений +ssh_pool = SSHConnectionPool(max_size=5) + + +# Отправка команд через SSH с повторными попытками +async def send_commands_via_ssh(router_ip: str, ssh_port: int, login: str, password: str, commands: List[str]) -> None: + max_retries = 3 + for attempt in range(max_retries): + try: + connection = await ssh_pool.get_connection(router_ip, ssh_port, login, password) + for command in commands: + logging.info(f"Executing command: {command}") + result = await connection.run(command) + logging.info(f"Command result: {result.stdout}") + if result.stderr: + logging.error(f"Command error: {result.stderr}") + await ssh_pool.release_connection(connection) + break # Выход из цикла после успешного выполнения команд + except (asyncssh.Error, asyncio.TimeoutError, OSError) as e: + logging.error(f"Ошибка при выполнении команд через SSH: {e}. Попытка {attempt + 1} из {max_retries}.") + if attempt + 1 == max_retries: + raise + await asyncio.sleep(5) # Подождать 5 секунд перед повторной попыткой + + +# Основная функция +async def execute_tasks() -> None: + config_data = read_config('config.ini') + if not config_data: + return + + try: + router_ip = config_data['router_ip'] + router_port = int(config_data['router_port']) + login = config_data['login'] + password = config_data['password'] + eth_id = config_data['eth_id'] + domain_folder = config_data['domain_folder'] + public_dns_1 = config_data['public_dns_1'] + public_dns_2 = config_data['public_dns_2'] + + # Загрузка доменных имен из папки + domain_list = load_domain_names_from_folder(domain_folder) + + # Инициализация списка DNS серверов + dns_servers = [public_dns_1, public_dns_2] + + except KeyError as e: + logging.error(f"Ошибка чтения параметров конфигурации: отсутствует ключ {e}") + return + + # Этап 1: Разрешение всех DNS имен + domain_to_addresses: Dict[str, List[str]] = {} + for domain in domain_list: + resolved_addresses = await send_dns_query(domain, dns_servers) + if resolved_addresses: + logging.info(f"Resolved {domain} to {resolved_addresses}") + domain_to_addresses[domain] = resolved_addresses + + # Этап 2: Отправка команд SSH для добавления маршрутов + commands = [] + for domain, addresses in domain_to_addresses.items(): + for address in addresses: + if address.rstrip('.') not in unique_ip_addresses: + commands.append(f"ip route {address.rstrip('.')}/32 {eth_id}") + unique_ip_addresses.add(address.rstrip('.')) + + if commands: + # Разделение команд на блоки по 5 штук + command_chunks = [commands[i:i + 5] for i in range(0, len(commands), 5)] + for chunk in command_chunks: + try: + await asyncio.wait_for( + send_commands_via_ssh(router_ip, router_port, login, password, chunk), timeout=5 + ) + except (asyncssh.Error, asyncio.TimeoutError, OSError) as e: + logging.error(f"Ошибка при выполнении команд через SSH: {e}") + + await ssh_pool.close_all() + + +async def main() -> None: + while True: + await execute_tasks() + logging.info("Ожидание 30 минут до следующего выполнения...") + await asyncio.sleep(1800) # Ожидание 30 минут (1800 секунд) + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/dm-light/dm-light.py b/dm-light/dm-light.py index 863a600..fd83745 100644 --- a/dm-light/dm-light.py +++ b/dm-light/dm-light.py @@ -1,6 +1,8 @@ import glob import os -from concurrent.futures import ThreadPoolExecutor +import threading +import time +from concurrent.futures import ThreadPoolExecutor, as_completed import dns.resolver @@ -11,7 +13,9 @@ unresolved_domains = set() unresolved_domains_file_name = "unresolved_domains.txt" # Имя файла с необработанными доменами result_file_name = "result.txt" # Имя файла результатов domain_files_pattern = "domain/*.txt" # Имя папки с txt файлами DNS -pub_dns = "8.8.8.8" # Публичный DNS № 1 +pub_dns_primary = "8.8.8.8" # Публичный DNS № 1 +pub_dns_secondary = "8.8.4.4" # Публичный DNS № 2 +max_requests = 20 # Максимальное число запросов к DNS # Постобработка файла вывода @@ -24,7 +28,8 @@ def post_process_output(input_file, output_file): ip_address = line.strip() # удаление повторов IP адресов if ip_address not in unique_addresses: unique_addresses.add(ip_address) - f.write(f"route add {ip_address} mask 255.255.255.255 0.0.0.0\n") # Запись результатов в заданном формате + f.write( + f"route add {ip_address} mask 255.255.255.255 0.0.0.0\n") # Запись результатов в заданном формате # Функция записи необработанных доменов в файл @@ -35,78 +40,103 @@ def write_unresolved_domains(unresolved_domains): # Функция записи IP обработанных доменов в файл -def write_resolved_ip(resolved_ip): +def write_resolved_ip(resolved_ips): with open(result_file, 'w', encoding='utf-8-sig') as f: - for ip_address in resolved_ip: + for ip_address in resolved_ips: f.write(ip_address + '\n') +# Функция для контроля числа запросов +class RateLimitedResolver: + def __init__(self, primary_dns, secondary_dns, max_requests): + self.resolver = dns.resolver.Resolver() + self.resolver.nameservers = [primary_dns, secondary_dns] + self.max_requests = max_requests + self.request_count = 0 + self.lock = threading.Lock() + + def resolve(self, domain): + with self.lock: + if self.request_count >= self.max_requests: + print("Достигнуто максимальное число запросов, приостановка выполнения.") + time.sleep(1) # Пауза перед следующим запросом + self.request_count = 0 # Сброс счетчика после паузы + self.request_count += 1 + + try: + answers = self.resolver.resolve(domain, 'A') + return [rdata.address for rdata in answers] + except Exception as e: + print(f"Ошибка при разрешении домена {domain}: {e}") + return None + + # Функция разрешения DNS-имени с использованием заданного DNS-сервера -def resolve_dns(domain): +def resolve_dns(domain, rate_limited_resolver): global successful_resolutions, failed_resolutions, unresolved_domains ip_addresses = [] # Изменение на список для хранения нескольких IP-адресов try: - answers = dns.resolver.resolve(domain, 'A') - for rdata in answers: - ip_address = rdata.address + ip_addresses = rate_limited_resolver.resolve(domain) + if ip_addresses: successful_resolutions += 1 - print(f"{domain} IP адрес: {ip_address}") - ip_addresses.append(ip_address) # Добавление IP-адреса в список - return ip_addresses # Возвращаем список IP-адресов - except dns.resolver.NXDOMAIN: + print(f"{domain} IP адрес(а): {', '.join(ip_addresses)}") + else: + failed_resolutions += 1 + unresolved_domains.add(domain) + print(f"Не удалось обработать домен: {domain}") + except Exception as e: failed_resolutions += 1 unresolved_domains.add(domain) - print(f"Не удалось обработать домен: {domain}, Не существует") - return None - except dns.resolver.NoAnswer: - failed_resolutions += 1 - unresolved_domains.add(domain) - print(f"Не удалось обработать домен: {domain}, Нет ответа") - return None - except dns.resolver.Timeout: - failed_resolutions += 1 - unresolved_domains.add(domain) - print(f"Не удалось обработать домен: {domain}, Тайм-аут") - return None + print(f"Не удалось обработать домен: {domain}, Ошибка: {e}") + return (domain, ip_addresses) # Возвращаем кортеж # Основная def resolve_dns_in_threads(domain_files, result_file, num_threads=20): - global successful_resolutions, failed_resolutions - unresolved_domains = set() # Создание множества для хранения необработанных доменов + global successful_resolutions, failed_resolutions, unresolved_domains resolved_ips = set() # Создание множества для хранения IP обработанных доменов + rate_limited_resolver = RateLimitedResolver(pub_dns_primary, pub_dns_secondary, max_requests) + # Выполнение резолва в нескольких потоках with ThreadPoolExecutor(max_workers=num_threads) as executor: + futures = [] for domain_file in domain_files: if os.path.isfile(domain_file): with open(domain_file, 'r', encoding='utf-8-sig') as f: domains = [line.strip() for line in f] - results = executor.map(resolve_dns, domains) - - # Открыть файл для записи результатов - with open(result_file, 'a', encoding='utf-8-sig') as result_f: - for domain, ip_addresses in zip(domains, results): - if ip_addresses: - resolved_ips.update(ip_addresses) # Добавление всех IP-адресов в множество - for ip_address in ip_addresses: - result_f.write(f"{domain} IP адрес: {ip_address}\n") # Запись каждого IP-адреса - else: - unresolved_domains.add(domain) # Добавление необработанных доменов в множество + for domain in domains: + futures.append(executor.submit(resolve_dns, domain, rate_limited_resolver)) + + # Обработка результатов + with open(result_file, 'a', encoding='utf-8-sig') as result_f: + for future in as_completed(futures): + result = future.result() + if result is not None: + domain, ip_addresses = result + if ip_addresses: + resolved_ips.update(ip_addresses) # Добавление всех IP-адресов в множество + for ip_address in ip_addresses: + result_f.write(f"{domain} IP адрес: {ip_address}\n") # Запись каждого IP-адреса + else: + unresolved_domains.add(domain) # Добавление необработанных доменов в множество # Запись множеств в соответствующие файлы write_resolved_ip(resolved_ips) # Запись IP обработанных доменов в файл write_unresolved_domains(unresolved_domains) # Запись необработанных доменов в файл post_process_output(result_file, result_file) # Вызов функции постобработки файла результатов - - print(f"\nСопоставлено IP адресов доменам:", successful_resolutions) - print(f"Не удалось обработать доменных имен:", failed_resolutions) - input("Нажмите Enter для продолжения...") # Для пользователей Windows при запуске из проводника + + print(f"\nСопоставлено IP адресов доменам: {successful_resolutions}") + print(f"Не удалось обработать доменных имен: {failed_resolutions}") + if os.name == 'nt': # Для пользователей Windows при запуске из проводника + input("Нажмите Enter для выхода...") if __name__ == "__main__": script_directory = os.path.dirname(os.path.abspath(__file__)) # Получение пути к директории с исполняемым файлом result_file = os.path.join(script_directory, result_file_name) # Формирование пути к файлу результатов - unresolved_file = os.path.join(script_directory, unresolved_domains_file_name) # Формирование пути к файлу с необработанными доменами - domain_files = glob.glob(os.path.join(script_directory, domain_files_pattern)) # Создание списка txt файлов в директории "domain" + unresolved_file = os.path.join(script_directory, + unresolved_domains_file_name) # Формирование пути к файлу с необработанными доменами + domain_files = glob.glob( + os.path.join(script_directory, domain_files_pattern)) # Создание списка txt файлов в директории "domain" resolve_dns_in_threads(domain_files, result_file) # Вызов основной функции diff --git a/dm-light/requirements.txt b/dm-light/requirements.txt index 707711b..71c5215 100644 --- a/dm-light/requirements.txt +++ b/dm-light/requirements.txt @@ -1 +1,7 @@ -dnspython==2.6.1 +requests~=2.31.0 +dnspython~=2.6.1 +ipaddress~=1.0.23 +configparser~=6.0.1 + +asyncssh~=2.15.0 +dnslib~=0.9.25 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index fbaf878..71c5215 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,6 @@ requests~=2.31.0 dnspython~=2.6.1 ipaddress~=1.0.23 configparser~=6.0.1 + +asyncssh~=2.15.0 +dnslib~=0.9.25 \ No newline at end of file