From 6196e8a0cfbb757c86b34501c5808572dd3e291d Mon Sep 17 00:00:00 2001 From: Ground-Zerro <161684825+Ground-Zerro@users.noreply.github.com> Date: Tue, 13 Aug 2024 17:02:57 +1100 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D1=82=D0=B5=D0=BB=D1=8C=D1=81=D0=BA=D0=B8=D0=B9=20=D1=81?= =?UTF-8?q?=D0=BF=D0=B8=D1=81=D0=BE=D0=BA,=20=D0=BA=D0=BE=D1=81=D0=BC?= =?UTF-8?q?=D0=B5=D1=82=D0=B8=D0=BA=D0=B0,=20bugfix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Добавлен резолв своего DNS листа: - Создать "custom-dns-list.txt", записать в него DNS имена (одна строчка - одно имя) и положить рядом со скриптом. - Список будет подхвачен и отображен в меню как "Custom DNS list". Добавлен формат сохранения файла для Mikrotik CLI Исправлена работа с config.ini Косметика. --- README.md | 12 ++- config.ini | 30 ++++++-- custom-dns-list.txt | 2 + main.py | 173 ++++++++++++++++++++++++++++---------------- requirements.txt | 9 +-- 5 files changed, 144 insertions(+), 82 deletions(-) create mode 100644 custom-dns-list.txt diff --git a/README.md b/README.md index 3b2afcf..4bb30d8 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ - Google - Torrent Truckers - Search engines +- Личный список **Функции:** @@ -25,10 +26,9 @@ - Итоговый список содержит только уникальные IP-адреса исключая дубликаты, также фильтруются IP-адреса самих DNS-серверов, заглушки в виде редиректа на localhost и (по желанию) IP-адреса Cloudflare. - Возможен выбор DNS сервера из установленного в системе, а также Google Public DNS, Quad9, Cloudflare DNS, OpenDNS, Cisco Umbrella, DNS.Watch, Dyn, CleanBrowsing, Alternate DNS, AdGuard DNS, Control D или все сразу. - Разрешение DNS имени происходит используя каждый из указанных пользователем DNS серверов и не останавливается при первом же успешном получении его IP-адреса. +- Пользователь может создать свой список с DNS именами, необходимыми лично ему. - С помощью конфигурационного файла можно настроить все параметры работы в т.ч. задать список сервисов, формат сохранения, количество потоков, имя выводного файла и другие. -*Обратите внимание, что для эффекта от точечной маршрутизации близкого к 100% резолвить DNS имена необходимо из сети, в которой предполагается их использование при помощи DNS серверов, настроенных в роутере/хосте...* - **Автоматизация:** Конфигурационный файл позволяет настроить работу скрипта в "молчаливом" режиме - без промтов к пользователю. @@ -47,9 +47,7 @@ pip3 install -r requirements.txt **Использование:** - Запустить с помощью Python. Для работы необходим только "main.py" и (по желанию) файл "config.ini". +*Работа с личным списком DNS:* +- Создать файл "custom-dns-list.txt", записать в него DNS имена (одна строчка - одно имя) и положить рядом со скриптом. Список будет подхвачен при запуске и отображен в меню как "Custom DNS list". - - - -###### Протестировано в Ubuntu 20.04 и Windows 10/11 - +##### Протестировано в Ubuntu 20.04 и Windows 10/11 diff --git a/config.ini b/config.ini index 599ed4b..83923ec 100644 --- a/config.ini +++ b/config.ini @@ -1,6 +1,7 @@ [DomainMapper] -# Имена сервисов, разделенные запятыми, для разрешения доменных имен в IP-адреса без запроса у пользователя, если не указано (по умолчанию) - пользователю будет выведено меню выбора +# Имена сервисов, разделенные запятыми, для разрешения доменных имен в IP-адреса без запроса у пользователя # опции: +# пустое значение - пользователю будет выведено меню выбора # all - проверить все сервисы # Antifilter community edition - список заблокированных DNS имен формируемый сообществом # Youtube @@ -16,10 +17,12 @@ # Google # Tor-Truckers - торрент трекеры # Search-engines - поисковые системы +# custom - Custom DNS list, это файл "custom-dns-list.txt" расположенный в одном каталоге со скриптом service = -# DNS сервера (номер), разделенные пробелом, которые будут использоваться для разрешения доменных имен, если не указано (по умолчанию) - пользователю будет выведено меню выбора +# DNS сервера (номер), разделенные пробелом, которые будут использоваться для разрешения доменных имен # опции: +# пустое значение - пользователю будет выведено меню выбора # 0 - использовать все доступные DNS серверы # 1 - Системный DNS # 2 - Google Public DNS @@ -35,32 +38,45 @@ service = # 12 - Control D dnsserver = -# Включить фильтрацию IP-адресов cloudflare и не записывать их в файл результатов, если не указано (по умолчанию) - пользователю будет выведен запрос с подсказкой +# Включить фильтрацию IP-адресов cloudflare и не записывать их в файл результатов # опции: +# пустое значение - пользователю будет выведено меню выбора # yes - исключить IP адреса cloudflare из итогового списка # no - оставить IP адреса cloudflare в итоговом списке cloudflare = -# Имя конечного файла, если не указано (по умолчанию) - будет использоваться имя фала "domain-ip-resolve.txt" в каталоге со скриптом +# Имя конечного файла # опции: +# пустое значение - "domain-ip-resolve.txt" в каталоге со скриптом # имя_файла - файл с указанным именем будет сохранени в каталоге со скриптом # полный_путь/имя_файла - файл будет сохранен с указанным именем в указанной каталоге filename = -# Количество потоков сканирования, если не указано (по умолчанию) - будет использоваться 20 потоков +# Количество потоков сканирования, если не указано - будет использоваться 20 потоков threads = -# Формат сохранения файла результатов, если не указано (по умолчанию) - пользователю будет выведен запрос с подсказкой +# Формат сохранения файла результатов # опции: +# пустое значение - пользователю будет выведено меню выбора # ip - только IP адрес # unix - ip rote %IP%/32 %gateway% # cidr - IP/32 # win - rote add %IP% mask 255.255.255.255 %gateway% +# mikrotik - /ip/firewall/address-list add list=%LIST_NAME% comment=%SERVICE_NAME% address=%IP%/32 filetype = -# адрес шлюза - используется при сохранении IP-адресов в 'win' или 'unix' формате, если не указан (по умолчанию) - пользователю будет выведен запрос с подсказкой +# адрес шлюза - используется при сохранении IP-адресов в 'win' или 'unix' формате +# опции: +# пустое значение - пользователю будет выведен запрос с подсказкой +# укажите IP-адрес шлюза или имя интерфейса gateway = +# имя списка - используется при сохранении IP-адресов в 'mikrotik' формате +# опции: +# пустое значение - пользователю будет выведен запрос с подсказкой +# укажите имя +listname = + # Команда для консоли после завершения скриптом всех операций, может быть полезно для автоматизации и комбинирования с другим скриптом, кодом или программой # опции: # исполняемая_команда_для_консоли diff --git a/custom-dns-list.txt b/custom-dns-list.txt new file mode 100644 index 0000000..7255105 --- /dev/null +++ b/custom-dns-list.txt @@ -0,0 +1,2 @@ +exemple.ru +1.exemple.com \ No newline at end of file diff --git a/main.py b/main.py index 4634546..5186d16 100644 --- a/main.py +++ b/main.py @@ -25,6 +25,11 @@ def cyan(text): def red(text): return f"{Fore.RED}{text}{Style.RESET_ALL}" +def magneta(text): + return f"{Fore.MAGENTA}{text}{Style.RESET_ALL}" + +def blue(text): + return f"{Fore.BLUE}{text}{Style.RESET_ALL}" # Читаем конфигурацию def read_config(filename): @@ -42,21 +47,23 @@ def read_config(filename): gateway = config.get('gateway') or '' run_command = config.get('run') or '' dns_server_indices = list(map(int, config.get('dnsserver', '').split())) if config.get('dnsserver') else [] + mk_list_name = config.get('listname') or '' print(f"{yellow('Загружена конфигурация из config.ini:')}") - print(f"Сервисы для проверки: {service if service else 'не указаны'}") - print(f"Использовать DNS сервер: {dns_server_indices if dns_server_indices else 'не указано'}") - print(f"Количество потоков: {request_limit}") - print(f"Фильтр Cloudflare: {'включен' if cloudflare == 'yes' else 'выключен' if cloudflare == 'no' else 'не указано'}") - print(f"Файл результатов: {filename}") - print(f"Формат сохранения: {'только IP' if filetype == 'ip' else 'Linux route' if filetype == 'unix' else 'CIDR-нотация' if filetype == 'cidr' else 'Windows route' if filetype == 'win' else 'не указан'}") - print(f"Шлюз для маршрутов: {gateway if gateway else 'не указан'}") - print(f"Выполнить по завершению: {run_command if run_command else 'не указано'}") - return service, request_limit, filename, cloudflare, filetype, gateway, run_command, dns_server_indices + print(f"{Style.BRIGHT}Сервисы для проверки:{Style.RESET_ALL} {service if service else 'спросить у пользователя'}") + print(f"{Style.BRIGHT}Использовать DNS сервер:{Style.RESET_ALL} {dns_server_indices if dns_server_indices else 'спросить у пользователя'}") + print(f"{Style.BRIGHT}Количество одновременных запросов к одному DNS серверу:{Style.RESET_ALL} {request_limit}") + print(f"{Style.BRIGHT}Фильтр IP-адресов Cloudflare:{Style.RESET_ALL} {'включен' if cloudflare == 'yes' else 'выключен' if cloudflare == 'no' else 'спросить у пользователя'}") + print(f"{Style.BRIGHT}Сохранить результаты в файл:{Style.RESET_ALL} {filename}") + print(f"{Style.BRIGHT}Формат сохранения:{Style.RESET_ALL} {'только IP' if filetype == 'ip' else 'Linux route' if filetype == 'unix' else 'CIDR-нотация' if filetype == 'cidr' else 'Windows route' if filetype == 'win' else 'CLI Mikrotik firewall' if filetype == 'mikrotik' else 'спросить у пользователя'}") + print(f"{Style.BRIGHT}Шлюз/Имя интерфейса для маршрутов:{Style.RESET_ALL} {gateway if gateway else 'спросить у пользователя'}") + print(f"{Style.BRIGHT}Имя списка для Mikrotik firewall:{Style.RESET_ALL} {mk_list_name if mk_list_name else 'спросить у пользователя'}") + print(f"{Style.BRIGHT}Выполнить по завершению:{Style.RESET_ALL} {run_command if run_command else 'не указано'}") + return service, request_limit, filename, cloudflare, filetype, gateway, run_command, dns_server_indices, mk_list_name except Exception as e: - print(f"{yellow('Ошибка загрузки config.ini:')} {e}\nИспользуются настройки 'по умолчанию'.") - return '', 20, 'domain-ip-resolve.txt', '', '', '', '', [] + print(f"{yellow('Ошибка загрузки config.ini:')} {e}\n{Style.BRIGHT}Используются настройки 'по умолчанию'.{Style.RESET_ALL}") + return '', 20, 'domain-ip-resolve.txt', '', '', '', '', [], '' def gateway_input(gateway): @@ -67,6 +74,15 @@ def gateway_input(gateway): else: return gateway +# Для microtik +def mk_list_name_input(mk_list_name): + if not mk_list_name: + input_mk_list_name = input(f"Введите {green('имя списка')} для firewall: ") + if input_mk_list_name: + return input_mk_list_name.strip() + else: + return mk_list_name + # Ограничение числа запросов def get_semaphore(request_limit): @@ -134,10 +150,10 @@ async def get_cloudflare_ips(): return set() -async def resolve_domain(domain, resolver, semaphore, dns_server_name, null_ips_count, cloudflare_ips, - cloudflare_ips_count): +async def resolve_domain(domain, resolver, semaphore, dns_server_name, null_ips_count, cloudflare_ips, cloudflare_ips_count, total_domains_processed): async with semaphore: try: + total_domains_processed[0] += 1 response = await resolver.resolve(domain) ips = [ip.address for ip in response] for ip_address in ips: @@ -146,15 +162,14 @@ async def resolve_domain(domain, resolver, semaphore, dns_server_name, null_ips_ elif ip_address in cloudflare_ips: cloudflare_ips_count[0] += 1 else: - print(f"{Fore.CYAN}{domain} IP-адрес: {ip_address} - {dns_server_name}{Style.RESET_ALL}") + print(f"{Fore.BLUE}{domain} IP-адрес: {ip_address} - {dns_server_name}{Style.RESET_ALL}") return ips except Exception as e: print(f"{Fore.RED}Не удалось получить IP-адрес: {domain} - {dns_server_name}{Style.RESET_ALL}") return [] -async def resolve_dns(service, dns_names, dns_servers, cloudflare_ips, unique_ips_all_services, semaphore, null_ips_count, - cloudflare_ips_count): +async def resolve_dns(service, dns_names, dns_servers, cloudflare_ips, unique_ips_all_services, semaphore, null_ips_count, cloudflare_ips_count, total_domains_processed): try: print(f"{Fore.YELLOW}Анализ DNS имен платформы {service}...{Style.RESET_ALL}") @@ -165,8 +180,7 @@ async def resolve_dns(service, dns_names, dns_servers, cloudflare_ips, unique_ip for domain in dns_names: domain = domain.strip() if domain: - tasks.append(resolve_domain(domain, resolver, semaphore[server_name], server_name, null_ips_count, - cloudflare_ips, cloudflare_ips_count)) + tasks.append(resolve_domain(domain, resolver, semaphore[server_name], server_name, null_ips_count, cloudflare_ips, cloudflare_ips_count, total_domains_processed)) results = await asyncio.gather(*tasks) @@ -184,39 +198,47 @@ async def resolve_dns(service, dns_names, dns_servers, cloudflare_ips, unique_ip def check_service_config(service, urls, local_dns_names): + services = [] if service: - if service.strip().lower() == "all": - services = list(urls.keys()) + services = [s.strip() for s in service.split(',')] + if "custom" in services: + services.remove("custom") if local_dns_names: - services.append("Мой список DNS") - return services - else: - return [s.strip() for s in service.split(',')] + services.append("Custom DNS list") + if "all" in services: + services = list(urls.keys()) + if local_dns_names and "Custom DNS list" not in services: + services.append("Custom DNS list") + elif not services: + services = list(urls.keys()) + if local_dns_names and "Custom DNS list" not in services: + services.append("Custom DNS list") else: - selected_services = [] while True: print(f"\n{yellow('Выберите сервисы:')}") print("0. Выбрать все") for idx, (service, url) in enumerate(urls.items(), 1): print(f"{idx}. {service.capitalize()}") if local_dns_names: - print(f"{len(urls) + 1}. Мой список DNS") + print(f"{len(urls) + 1}. Custom DNS list") - selection = input(f"\nУкажите номера сервисов через пробел и нажмите {green('Enter')}: ") + selection = input(f"\nУкажите номера платформ через пробел и нажмите {green('Enter')}: ") if selection.strip(): selections = selection.split() - if '0' in selections: # User selected all services - selected_services = list(urls.keys()) - if local_dns_names: - selected_services.append('Мой список DNS') + if '0' in selections: + services = list(urls.keys()) + if local_dns_names and "Custom DNS list" not in services: + services.append('Custom DNS list') break else: - selected_services = [list(urls.keys())[int(sel) - 1] for sel in selections if sel.isdigit() - and 1 <= int(sel) <= len(urls)] + services = [list(urls.keys())[int(sel) - 1] for sel in selections if sel.isdigit() + and 1 <= int(sel) <= len(urls)] if str(len(urls) + 1) in selections and local_dns_names: - selected_services.append('Мой список DNS') + services.append('Custom DNS list') break - return selected_services + return services + + # Промт на исключение IP-адресов cloudflare @@ -231,17 +253,25 @@ def check_include_cloudflare(cloudflare): def check_dns_servers(dns_servers, dns_server_indices): + # Получение системных DNS серверов system_dns_servers = dns.asyncresolver.Resolver().nameservers - selected_dns_servers = [] + # Формирование списка всех доступных серверов dns_server_options = [('Системный DNS', system_dns_servers)] + list(dns_servers.items()) + selected_dns_servers = [] + + # Если указаны индексы серверов в конфиге if dns_server_indices: - for idx in dns_server_indices: - if 0 <= idx <= len(dns_server_options): - selected_dns_servers.append((dns_server_options[idx][0], dns_server_options[idx][1])) + if 0 in dns_server_indices: # Если указано 0, выбираем все доступные DNS серверы + selected_dns_servers = dns_server_options + else: + for idx in dns_server_indices: + if 1 <= idx <= len(dns_server_options): # Корректируем индекс на 1 меньше, чтобы соответствовать списку + selected_dns_servers.append(dns_server_options[idx - 1]) return selected_dns_servers + # Если индексы не указаны, запрашиваем у пользователя выбор серверов while True: print(f"\n{yellow('Какие DNS сервера использовать?')}") print("0. Выбрать все") @@ -259,21 +289,21 @@ def check_dns_servers(dns_servers, dns_server_indices): if sel.isdigit(): sel = int(sel) if 1 <= sel <= len(dns_server_options): - selected_dns_servers.append( - (dns_server_options[sel - 1][0], dns_server_options[sel - 1][1])) + selected_dns_servers.append(dns_server_options[sel - 1]) break return selected_dns_servers # Выбор формата сохранения списка разрешенных DNS имен -def process_file_format(filename, filetype, gateway): +def process_file_format(filename, filetype, gateway, selected_service, mk_list_name): if not filetype: filetype = input(f"\n{yellow('В каком формате сохранить файл?')}" - f"\n{green('win')} - route add IP mask MASK GATEWAY" - f"\n{green('unix')} - ip route IP/MASK GATEWAY" - f"\n{green('cidr')} - IP/MASK" - f"\n{green('Пустое значение')} - только IP" + f"\n{green('win')} - route add {cyan('IP')} mask {cyan('MASK GATEWAY')}" + f"\n{green('unix')} - ip route {cyan('IP/MASK GATEWAY')}" + f"\n{green('cidr')} - {cyan('IP/MASK')}" + f"\n{green('mikrotik')} - /ip/firewall/address-list add list={cyan('LIST_NAME')} comment={cyan('SERVICE_NAME')} address={cyan('IP/MASK')}" + f"\n{green('Enter')} - только {cyan('IP')}" f"\nВаш выбор: ") if filetype.lower() in ['win', 'unix']: @@ -305,6 +335,24 @@ def process_file_format(filename, filetype, gateway): with open(filename, 'w', encoding='utf-8-sig') as file: for ip in ips: file.write(f"{ip.strip()}/32\n") + + elif filetype.lower() == 'mikrotik': + mk_list_name = mk_list_name_input(mk_list_name) + + try: + with open(filename, 'r', encoding='utf-8-sig') as file: + ips = file.readlines() + except Exception as e: + print(f"Ошибка чтения файла: {e}") + return + + if ips: + with open(filename, 'w', encoding='utf-8-sig') as file: + for ip in ips: + file.write(f'/ip/firewall/address-list add list={mk_list_name} comment={str(selected_service).replace("[", "").replace("]", "").replace("\'", "")} address={ip.strip()}/32\n') + + + else: pass @@ -312,19 +360,19 @@ def process_file_format(filename, filetype, gateway): # Ну чо, погнали?! async def main(): # Инициализация настроек из config.ini - service, request_limit, filename, cloudflare, filetype, gateway, run_command, dns_server_indices = read_config('config.ini') + service, request_limit, filename, cloudflare, filetype, gateway, run_command, dns_server_indices, mk_list_name = read_config('config.ini') # Load URLs platform_db_url = "https://raw.githubusercontent.com/Ground-Zerro/DomainMapper/main/platformdb" urls = await load_urls(platform_db_url) - # Load local DNS names from "my-dns-list.txt" if it exists + # Load local DNS names from "custom-dns-list.txt" if it exists local_dns_names = [] - if os.path.exists('my-dns-list.txt'): - with open('my-dns-list.txt', 'r', encoding='utf-8-sig') as file: + if os.path.exists('custom-dns-list.txt'): + with open('custom-dns-list.txt', 'r', encoding='utf-8-sig') as file: local_dns_names = [line.strip() for line in file if line.strip()] - # Выбор сервисов + # Выбор платформ selected_services = check_service_config(service, urls, local_dns_names) # Загрузка списка DNS-серверов @@ -337,7 +385,6 @@ async def main(): # Инициализация IP-адресов Cloudflare cloudflare_ips = await get_cloudflare_ips() - # Фильтр Cloudflare include_cloudflare = check_include_cloudflare(cloudflare) @@ -345,21 +392,21 @@ async def main(): semaphore = init_semaphores(request_limit) null_ips_count = [0] cloudflare_ips_count = [0] + total_domains_processed = [0] tasks = [] for service in selected_services: - if service == 'Мой список DNS': + if service == 'Custom DNS list': tasks.append(resolve_dns(service, local_dns_names, selected_dns_servers, cloudflare_ips, unique_ips_all_services, - semaphore, null_ips_count, cloudflare_ips_count)) + semaphore, null_ips_count, cloudflare_ips_count, total_domains_processed)) else: - # Загрузка DNS имен сервисов dns_names_url = urls[service] async with httpx.AsyncClient() as client: response = await client.get(dns_names_url) response.raise_for_status() dns_names = response.text.splitlines() tasks.append(resolve_dns(service, dns_names, selected_dns_servers, cloudflare_ips, unique_ips_all_services, - semaphore, null_ips_count, cloudflare_ips_count)) + semaphore, null_ips_count, cloudflare_ips_count, total_domains_processed)) results = await asyncio.gather(*tasks) @@ -368,23 +415,23 @@ async def main(): file.write(result) print(f"\n{yellow('Проверка завершена.')}") - print("Использовались DNS сервера: " + ', '.join( + print(f"{Style.BRIGHT}Использовались DNS сервера:{Style.RESET_ALL} " + ', '.join( [f'{pair[0]} ({", ".join(pair[1])})' for pair in selected_dns_servers])) + print(f"{Style.BRIGHT}Всего обработано DNS имен:{Style.RESET_ALL} {total_domains_processed[0]}.") if include_cloudflare: - print(f"Исключено IP-адресов Cloudflare: {cloudflare_ips_count[0]}") - print(f"Исключено IP-адресов 'заглушек': {null_ips_count[0]}") - print(f"Разрешено IP-адресов из DNS имен: {len(unique_ips_all_services)}") + print(f"{Style.BRIGHT}Исключено IP-адресов Cloudflare:{Style.RESET_ALL} {cloudflare_ips_count[0]}") + print(f"{Style.BRIGHT}Исключено IP-адресов 'заглушек':{Style.RESET_ALL} {null_ips_count[0]}") + print(f"{Style.BRIGHT}Разрешено IP-адресов из DNS имен:{Style.RESET_ALL} {len(unique_ips_all_services)}") - process_file_format(filename, filetype, gateway) + process_file_format(filename, filetype, gateway, selected_services, mk_list_name) if run_command: print("\nВыполнение команды после завершения скрипта...") os.system(run_command) else: - print("\nРезультаты сохранены в файл:", filename) + print(f"\n{Style.BRIGHT}Результаты сохранены в файл:{Style.RESET_ALL}", filename) if os.name == 'nt': input(f"Нажмите {green('Enter')} для выхода...") - if __name__ == "__main__": asyncio.run(main()) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 7728cde..f0961a9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,5 @@ -dnspython~=2.6.1 -ipaddress~=1.0.23 -configparser~=7.0.0 - -httpx~=0.27.0 +configparser~=7.0.0 +ipaddress~=1.0.23 +dnspython~=2.6.1 +httpx~=0.27.0 colorama~=0.4.6 \ No newline at end of file