This commit is contained in:
Ground-Zerro
2024-07-21 19:54:43 +11:00
parent d6b00ac1cb
commit bfb13a67a6
6 changed files with 294 additions and 137 deletions

View File

@@ -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) # Вызов основной функции

9
dm-light/config.ini Normal file
View File

@@ -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

201
dm-light/dm-light-router.py Normal file
View File

@@ -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())

View File

@@ -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) # Вызов основной функции

View File

@@ -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

View File

@@ -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