mirror of
https://github.com/Ground-Zerro/DomainMapper.git
synced 2025-12-10 01:47:18 +07:00
Compare commits
14 Commits
b8ec267ae3
...
ff01ca1738
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ff01ca1738 | ||
|
|
c39753de3f | ||
|
|
1f1422c69d | ||
|
|
50c8910dcf | ||
|
|
af64289c0c | ||
|
|
521026aff6 | ||
|
|
c097a7dcd5 | ||
|
|
64abff705f | ||
|
|
487093e254 | ||
|
|
a493beec6f | ||
|
|
fd893ac674 | ||
|
|
b2c50641b7 | ||
|
|
f90af7fec7 | ||
|
|
e11e5696cf |
4
.gitattributes
vendored
4
.gitattributes
vendored
@@ -1,3 +1,3 @@
|
||||
* text=auto
|
||||
*.bat text eol=crlf
|
||||
*.cmd text eol=crlf
|
||||
*.bat -text
|
||||
*.cmd -text
|
||||
@@ -2,6 +2,7 @@
|
||||
<details>
|
||||
<summary>Что нового (нажать, чтобы открыть)</summary>
|
||||
|
||||
- Реворк работы с DNS серверами. Прогрессбар. Разделение файла на части для некоторых форматов. Обновлена утилита Сonvert.
|
||||
- Keenetic BAT формат сохранения. Небольшие изменения в интерфейсе. Некоторые доработки/улучшения.
|
||||
- Доабвлены некоторые [оналйн кинотеатры](https://github.com/Ground-Zerro/DomainMapper/blob/main/platforms/dns-onlinetheater.txt). Запрос @Andrey_schumacher
|
||||
- Добавлены списки от [ITDog](https://t.me/itdoginfo/36).
|
||||
@@ -66,6 +67,7 @@
|
||||
- Агрегация маршрутов в /16 (255.255.0.0) и /24 (255.255.255.0) подсети. Комбинированный режим /24 + /32.
|
||||
- Фильтрация IP-адресов Cloudflare (опционально).
|
||||
- Множество форматов сохранения результата.
|
||||
- Разделение больших файлов на части для некоторых форматов.
|
||||
|
||||
|
||||
**Ключевые особенности**
|
||||
@@ -173,6 +175,6 @@ curl -L -s "https://raw.githubusercontent.com/Ground-Zerro/DomainMapper/refs/hea
|
||||
|
||||
# ☕ Поддержка
|
||||
|
||||
Если проект оказался Вам полезен — можно поддержать автора:
|
||||
Если проект оказался Вам полезен — можно поблагодарить автора:
|
||||
|
||||
- [Поддержать на Boosty](https://boosty.to/ground_zerro)
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
**Можете попробовать эти варианты:**
|
||||
- Запустить PowerShell и выполнить команду:
|
||||
```
|
||||
irm https://github.com/Ground-Zerro/DomainMapper/raw/main/Windows/Win.bat -OutFile "$env:TEMP\Win.bat"; cmd /c "$env:TEMP\Win.bat"
|
||||
irm https://github.com/Ground-Zerro/DomainMapper/raw/refs/heads/main/Windows/Win.bat -OutFile "$env:TEMP\Win.bat"; cmd /c "$env:TEMP\Win.bat"
|
||||
```
|
||||
- Открыть командную строку Windows и выполнить команду:
|
||||
```
|
||||
powershell -Command "irm https://github.com/Ground-Zerro/DomainMapper/raw/main/Windows/Win.bat -OutFile $env:TEMP\Win.bat" && cmd /c "%TEMP%\Win.bat"
|
||||
powershell -Command "irm https://github.com/Ground-Zerro/DomainMapper/raw/refs/heads/main/Windows/Win.bat -OutFile $env:TEMP\Win.bat" && cmd /c "%TEMP%\Win.bat"
|
||||
```
|
||||
|
||||
@@ -47,7 +47,7 @@ exit /b 0
|
||||
|
||||
REM Проверка и установка необходимых модулей Python
|
||||
:CheckModules
|
||||
set "modules=requests dnspython ipaddress configparser httpx colorama"
|
||||
set "modules=dnspython httpx colorama tqdm"
|
||||
echo.
|
||||
echo Проверка необходимых библиотек...
|
||||
|
||||
@@ -87,10 +87,26 @@ if ERRORLEVEL 1 (
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
move /y domain-ip-resolve.txt %UserProfile%\Desktop\domain-ip-resolve.txt
|
||||
echo Копирование файлов на рабочий стол...
|
||||
|
||||
if exist domain-ip-resolve.txt (
|
||||
move /y domain-ip-resolve.txt %UserProfile%\Desktop\domain-ip-resolve.txt
|
||||
echo Файл скопирован в %UserProfile%\Desktop\domain-ip-resolve.txt
|
||||
) else (
|
||||
echo Поиск разделенных файлов...
|
||||
set "found=0"
|
||||
for %%f in (domain-ip-resolve_p*.txt) do (
|
||||
move /y "%%f" "%UserProfile%\Desktop\%%f"
|
||||
echo Файл %%f скопирован на рабочий стол
|
||||
set "found=1"
|
||||
)
|
||||
if "!found!"=="0" (
|
||||
echo Не найдено файлов для копирования.
|
||||
)
|
||||
)
|
||||
|
||||
echo Программа завершена.
|
||||
del /q /f main.py
|
||||
endlocal
|
||||
echo файл скопирован в %UserProfile%\Desktop\domain-ip-resolve.txt
|
||||
pause
|
||||
exit /b 0
|
||||
|
||||
@@ -18,8 +18,9 @@ subnet =
|
||||
# Имя выходного файла
|
||||
filename = domain-ip-resolve.txt
|
||||
|
||||
# Количество потоков (по умолчанию 20)
|
||||
threads =
|
||||
# Лимит запросов к каждому DNS серверу (запросов в секунду, по умолчанию 50)
|
||||
# Контролирует максимальное количество DNS запросов к одному серверу в секунду
|
||||
rate_limit = 50
|
||||
|
||||
# Формат результата (ip, unix, win, mikrotik, ovpn, wireguard, cidr, keenetic bat и т.д.)
|
||||
filetype =
|
||||
|
||||
325
domain-ip-resolve.txt
Normal file
325
domain-ip-resolve.txt
Normal file
@@ -0,0 +1,325 @@
|
||||
103.136.41.171
|
||||
104.192.140.24
|
||||
104.192.140.25
|
||||
104.192.140.26
|
||||
104.244.43.131
|
||||
104.247.81.99
|
||||
107.22.247.231
|
||||
108.181.60.131
|
||||
116.202.120.165
|
||||
116.202.120.166
|
||||
12.5.163.52
|
||||
128.150.221.244
|
||||
13.107.21.200
|
||||
13.107.213.46
|
||||
13.107.246.46
|
||||
13.107.253.51
|
||||
13.216.68.41
|
||||
13.227.180.4
|
||||
13.248.188.196
|
||||
13.249.91.105
|
||||
13.249.91.27
|
||||
13.249.91.55
|
||||
13.249.91.77
|
||||
13.33.252.118
|
||||
13.33.252.123
|
||||
13.33.252.125
|
||||
13.33.252.128
|
||||
13.33.252.129
|
||||
13.33.252.35
|
||||
13.33.252.55
|
||||
13.33.252.61
|
||||
13.33.252.62
|
||||
13.33.252.7
|
||||
13.33.252.87
|
||||
13.33.252.90
|
||||
13.35.93.102
|
||||
13.35.93.103
|
||||
13.35.93.12
|
||||
13.35.93.125
|
||||
13.83.13.216
|
||||
13.91.95.74
|
||||
138.68.89.1
|
||||
140.82.112.21
|
||||
141.95.92.64
|
||||
142.229.226.127
|
||||
142.229.246.30
|
||||
142.250.196.104
|
||||
142.250.196.110
|
||||
142.250.207.46
|
||||
143.166.136.12
|
||||
143.166.30.172
|
||||
145.239.35.18
|
||||
146.75.112.157
|
||||
146.75.112.159
|
||||
150.171.22.11
|
||||
150.171.27.11
|
||||
150.171.28.11
|
||||
151.101.0.81
|
||||
151.101.1.194
|
||||
151.101.1.224
|
||||
151.101.128.81
|
||||
151.101.129.194
|
||||
151.101.129.224
|
||||
151.101.131.42
|
||||
151.101.192.81
|
||||
151.101.193.194
|
||||
151.101.193.224
|
||||
151.101.195.1
|
||||
151.101.195.42
|
||||
151.101.3.1
|
||||
151.101.3.42
|
||||
151.101.64.81
|
||||
151.101.65.194
|
||||
151.101.65.224
|
||||
151.101.67.42
|
||||
157.240.31.35
|
||||
16.15.178.207
|
||||
16.15.181.233
|
||||
160.79.104.10
|
||||
161.35.220.135
|
||||
162.55.241.153
|
||||
165.22.91.219
|
||||
172.105.192.79
|
||||
172.105.69.103
|
||||
172.233.219.123
|
||||
172.233.219.49
|
||||
172.233.219.78
|
||||
172.237.146.25
|
||||
172.237.146.38
|
||||
172.237.146.8
|
||||
178.248.237.68
|
||||
179.43.150.83
|
||||
179.43.151.32
|
||||
179.43.166.40
|
||||
18.154.132.120
|
||||
18.154.132.30
|
||||
18.154.132.61
|
||||
18.154.132.68
|
||||
18.154.132.83
|
||||
18.154.132.86
|
||||
18.154.132.87
|
||||
18.154.132.9
|
||||
18.154.144.104
|
||||
18.154.144.18
|
||||
18.154.144.70
|
||||
18.154.144.88
|
||||
18.158.198.204
|
||||
18.164.124.48
|
||||
18.164.124.69
|
||||
18.164.124.80
|
||||
18.164.124.92
|
||||
18.173.219.124
|
||||
18.173.219.5
|
||||
18.173.219.85
|
||||
18.173.219.98
|
||||
18.192.161.188
|
||||
18.192.252.77
|
||||
18.192.94.166
|
||||
18.193.221.227
|
||||
18.197.250.227
|
||||
18.197.5.201
|
||||
18.207.85.246
|
||||
18.238.80.11
|
||||
18.238.80.111
|
||||
18.238.80.17
|
||||
18.238.80.20
|
||||
18.238.80.43
|
||||
18.238.80.53
|
||||
18.238.80.73
|
||||
18.238.80.84
|
||||
18.64.122.111
|
||||
18.64.122.116
|
||||
18.64.122.55
|
||||
18.64.122.75
|
||||
185.110.92.40
|
||||
185.110.92.41
|
||||
185.155.96.196
|
||||
185.70.42.25
|
||||
185.70.42.42
|
||||
185.81.128.108
|
||||
190.115.31.47
|
||||
192.0.66.233
|
||||
192.104.24.52
|
||||
192.124.249.107
|
||||
194.55.26.46
|
||||
194.55.30.46
|
||||
195.12.177.96
|
||||
195.123.208.131
|
||||
195.16.73.95
|
||||
195.245.213.251
|
||||
195.245.213.252
|
||||
198.148.79.54
|
||||
198.202.211.1
|
||||
199.83.44.71
|
||||
20.27.177.116
|
||||
204.79.197.200
|
||||
204.8.99.144
|
||||
204.8.99.146
|
||||
212.53.146.29
|
||||
212.58.244.129
|
||||
212.58.244.210
|
||||
212.58.249.206
|
||||
212.58.249.207
|
||||
213.166.70.101
|
||||
213.183.31.3
|
||||
217.140.110.36
|
||||
23.192.45.250
|
||||
23.192.45.251
|
||||
23.192.46.10
|
||||
23.192.46.16
|
||||
23.192.46.17
|
||||
23.192.46.18
|
||||
23.192.46.19
|
||||
23.192.46.8
|
||||
23.192.46.9
|
||||
23.192.47.66
|
||||
23.192.47.88
|
||||
23.208.232.54
|
||||
23.219.68.213
|
||||
23.35.101.86
|
||||
23.36.17.19
|
||||
23.36.17.218
|
||||
23.39.216.112
|
||||
23.53.3.132
|
||||
23.53.3.137
|
||||
23.53.3.138
|
||||
3.120.236.241
|
||||
3.122.12.167
|
||||
3.163.158.102
|
||||
3.163.158.51
|
||||
3.163.158.77
|
||||
3.163.158.83
|
||||
3.163.165.106
|
||||
3.163.165.108
|
||||
3.163.165.117
|
||||
3.163.165.21
|
||||
3.163.165.58
|
||||
3.163.165.65
|
||||
3.163.165.67
|
||||
3.163.165.77
|
||||
3.165.160.119
|
||||
3.165.160.125
|
||||
3.165.160.82
|
||||
3.165.160.93
|
||||
3.168.122.102
|
||||
3.168.122.30
|
||||
3.168.122.70
|
||||
3.168.122.96
|
||||
3.168.2.124
|
||||
3.168.2.34
|
||||
3.168.2.45
|
||||
3.168.2.73
|
||||
3.168.73.124
|
||||
3.168.73.14
|
||||
3.168.73.40
|
||||
3.168.73.5
|
||||
3.219.7.240
|
||||
3.70.4.80
|
||||
3.72.128.20
|
||||
3.93.103.92
|
||||
3.93.83.52
|
||||
31.13.82.174
|
||||
31.13.82.36
|
||||
31.13.82.52
|
||||
34.120.127.130
|
||||
34.172.121.65
|
||||
34.193.227.236
|
||||
34.210.63.15
|
||||
34.217.249.79
|
||||
34.240.120.87
|
||||
34.78.67.165
|
||||
34.8.0.82
|
||||
34.96.84.62
|
||||
35.156.83.59
|
||||
35.157.126.60
|
||||
35.157.160.164
|
||||
35.167.41.134
|
||||
35.186.224.24
|
||||
35.212.37.82
|
||||
37.1.201.40
|
||||
37.220.83.128
|
||||
44.227.138.182
|
||||
44.234.232.238
|
||||
44.237.234.25
|
||||
44.241.61.155
|
||||
44.242.60.85
|
||||
45.137.66.127
|
||||
5.61.53.100
|
||||
5.9.141.28
|
||||
50.112.202.115
|
||||
51.15.27.55
|
||||
52.13.171.212
|
||||
52.13.57.203
|
||||
52.175.140.176
|
||||
52.209.241.99
|
||||
52.216.60.56
|
||||
52.217.132.208
|
||||
52.217.174.112
|
||||
52.217.223.32
|
||||
52.217.234.232
|
||||
52.26.244.222
|
||||
52.29.75.203
|
||||
52.32.28.26
|
||||
52.33.95.61
|
||||
52.49.216.222
|
||||
52.50.101.102
|
||||
52.57.208.252
|
||||
52.72.30.131
|
||||
52.84.20.107
|
||||
52.84.20.112
|
||||
52.84.20.55
|
||||
52.84.20.75
|
||||
52.88.5.90
|
||||
54.144.73.197
|
||||
54.163.108.212
|
||||
54.167.157.63
|
||||
54.167.246.209
|
||||
54.221.253.164
|
||||
54.231.200.248
|
||||
54.235.8.174
|
||||
54.247.109.89
|
||||
54.68.22.26
|
||||
54.86.126.30
|
||||
63.34.30.85
|
||||
64.227.45.125
|
||||
66.254.114.41
|
||||
67.22.51.32
|
||||
67.22.51.33
|
||||
67.22.51.34
|
||||
67.22.51.35
|
||||
67.22.51.36
|
||||
67.22.51.37
|
||||
67.22.51.38
|
||||
67.22.51.39
|
||||
69.55.53.168
|
||||
69.55.53.169
|
||||
69.55.53.170
|
||||
69.55.53.171
|
||||
69.55.53.172
|
||||
72.163.4.185
|
||||
75.2.124.44
|
||||
76.223.63.197
|
||||
77.37.83.161
|
||||
77.86.162.1
|
||||
77.86.162.2
|
||||
78.46.102.85
|
||||
82.221.104.145
|
||||
84.15.66.97
|
||||
84.246.85.45
|
||||
87.245.208.97
|
||||
91.108.98.62
|
||||
91.200.40.44
|
||||
91.216.218.44
|
||||
94.130.182.82
|
||||
95.216.145.1
|
||||
99.80.51.179
|
||||
99.83.136.94
|
||||
99.84.234.112
|
||||
99.84.234.128
|
||||
99.84.234.31
|
||||
99.84.234.42
|
||||
99.84.234.5
|
||||
99.84.234.61
|
||||
99.84.234.89
|
||||
99.84.234.96
|
||||
420
main.py
420
main.py
@@ -3,16 +3,129 @@ import asyncio
|
||||
import configparser
|
||||
import ipaddress
|
||||
import os
|
||||
from asyncio import Semaphore
|
||||
from collections import defaultdict
|
||||
import time
|
||||
from collections import defaultdict, deque
|
||||
from typing import Dict, List, Set, Tuple, Optional
|
||||
|
||||
import dns.asyncresolver
|
||||
import httpx
|
||||
from colorama import Fore, Style, init
|
||||
from tqdm import tqdm
|
||||
|
||||
init(autoreset=True)
|
||||
|
||||
class ProgressTracker:
|
||||
def __init__(self, total: int, stats: Dict, unique_ips_set: Set[str],
|
||||
num_dns_servers: int = 1, rate_limit: int = 10, domains_count: int = 0):
|
||||
self.total = total
|
||||
self.stats = stats
|
||||
self.unique_ips = unique_ips_set
|
||||
self.pbar = None
|
||||
self.lock = asyncio.Lock()
|
||||
self.num_dns_servers = num_dns_servers
|
||||
self.rate_limit = rate_limit
|
||||
self.domains_count = domains_count
|
||||
self.effective_rate = num_dns_servers * rate_limit
|
||||
self.start_time = time.time()
|
||||
|
||||
def start(self):
|
||||
self.pbar = tqdm(
|
||||
total=self.total,
|
||||
bar_format='[{bar:40}] {percentage:3.1f}% | Прошло: {elapsed} | Осталось (примерно): {desc}',
|
||||
unit=' запр',
|
||||
ncols=120,
|
||||
leave=True,
|
||||
mininterval=0,
|
||||
desc='расчет...'
|
||||
)
|
||||
|
||||
async def update_progress(self):
|
||||
if self.pbar:
|
||||
async with self.lock:
|
||||
processed = self.stats.get('total_domains_processed', 0)
|
||||
remaining_time = self.calculate_remaining_time()
|
||||
|
||||
self.pbar.n = processed
|
||||
self.pbar.set_description_str(remaining_time)
|
||||
self.pbar.refresh()
|
||||
|
||||
def format_time(self, seconds: float) -> str:
|
||||
if seconds < 0:
|
||||
seconds = 0
|
||||
mins = int(seconds // 60)
|
||||
secs = int(seconds % 60)
|
||||
return f"{mins:02d}:{secs:02d}"
|
||||
|
||||
def calculate_remaining_time(self) -> str:
|
||||
processed = self.stats.get('total_domains_processed', 0)
|
||||
remaining = self.total - processed
|
||||
|
||||
if self.effective_rate > 0:
|
||||
time_remaining = remaining / self.effective_rate
|
||||
return self.format_time(time_remaining)
|
||||
return "00:00"
|
||||
|
||||
def close(self):
|
||||
if self.pbar:
|
||||
self.pbar.n = self.total
|
||||
self.pbar.refresh()
|
||||
self.pbar.close()
|
||||
|
||||
elapsed = time.time() - self.stats['start_time']
|
||||
total = self.stats['total_domains']
|
||||
processed = self.stats['total_domains_processed']
|
||||
errors = self.stats['domain_errors']
|
||||
|
||||
error_pct = (errors / total * 100) if total > 0 else 0
|
||||
total_ips_found = len(self.unique_ips) + self.stats['null_ips_count'] + self.stats.get('cloudflare_ips_count', 0)
|
||||
null_pct = (self.stats['null_ips_count'] / total_ips_found * 100) if total_ips_found > 0 else 0
|
||||
cf_pct = (self.stats.get('cloudflare_ips_count', 0) / total_ips_found * 100) if total_ips_found > 0 else 0
|
||||
|
||||
print(f"\n{yellow('Проверка завершена.')}")
|
||||
print(f"{Style.BRIGHT}Всего обработано DNS имен:{Style.RESET_ALL} {processed} из {total}")
|
||||
print(f"{Style.BRIGHT}Разрешено уникальных IP-адресов:{Style.RESET_ALL} {len(self.unique_ips)}")
|
||||
print(f"{Style.BRIGHT}Ошибок разрешения доменов:{Style.RESET_ALL} {errors} ({error_pct:.1f}%)")
|
||||
|
||||
if self.stats['null_ips_count'] > 0:
|
||||
print(f"{Style.BRIGHT}Исключено IP-адресов 'заглушек':{Style.RESET_ALL} {self.stats['null_ips_count']} ({null_pct:.1f}%)")
|
||||
|
||||
if self.stats.get('cloudflare_ips_count', 0) > 0:
|
||||
print(f"{Style.BRIGHT}Исключено IP-адресов Cloudflare:{Style.RESET_ALL} {self.stats['cloudflare_ips_count']} ({cf_pct:.1f}%)")
|
||||
|
||||
class PeriodicProgressUpdater:
|
||||
def __init__(self, progress_tracker: ProgressTracker, stats: Dict):
|
||||
self.progress_tracker = progress_tracker
|
||||
self.stats = stats
|
||||
self.is_running = False
|
||||
self.task = None
|
||||
|
||||
async def start(self):
|
||||
if not self.is_running:
|
||||
self.is_running = True
|
||||
self.task = asyncio.create_task(self._periodic_update())
|
||||
|
||||
async def stop(self):
|
||||
if self.is_running:
|
||||
self.is_running = False
|
||||
if self.task:
|
||||
self.task.cancel()
|
||||
try:
|
||||
await self.task
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
|
||||
async def _periodic_update(self):
|
||||
await asyncio.sleep(2)
|
||||
while self.is_running:
|
||||
try:
|
||||
await self.progress_tracker.update_progress()
|
||||
await asyncio.sleep(2)
|
||||
except asyncio.CancelledError:
|
||||
break
|
||||
except Exception as e:
|
||||
print(f"Error in periodic progress update: {e}")
|
||||
await asyncio.sleep(2)
|
||||
|
||||
def yellow(text):
|
||||
return f"{Fore.YELLOW}{text}{Style.RESET_ALL}"
|
||||
|
||||
@@ -60,7 +173,7 @@ def read_config(cfg_file):
|
||||
config = config['DomainMapper']
|
||||
|
||||
service = config.get('service') or ''
|
||||
request_limit = int(config.get('threads') or 15)
|
||||
rate_limit = int(config.get('rate_limit') or 50)
|
||||
filename = config.get('filename') or 'domain-ip-resolve.txt'
|
||||
cloudflare = config.get('cloudflare') or ''
|
||||
filetype = config.get('filetype') or ''
|
||||
@@ -79,7 +192,7 @@ def read_config(cfg_file):
|
||||
print(f"{yellow(f'Загружена конфигурация из {cfg_file}:')}")
|
||||
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}Лимит запросов к каждому DNS серверу (запросов/сек):{Style.RESET_ALL} {rate_limit}")
|
||||
print(f"{Style.BRIGHT}Фильтрация IP-адресов Cloudflare:{Style.RESET_ALL} {'включена' if cloudflare in ['y', 'yes'] else 'выключена' if cloudflare in ['n', 'no'] else 'спросить у пользователя'}")
|
||||
print(f"{Style.BRIGHT}Агрегация IP-адресов:{Style.RESET_ALL} {'mix режим /24 (255.255.255.0) + /32 (255.255.255.255)' if subnet == 'mix' else 'до /16 подсети (255.255.0.0)' if subnet == '16' else 'до /24 подсети (255.255.255.0)' if subnet == '24' else 'выключена' if subnet in ['n', 'no'] else 'спросить у пользователя'}")
|
||||
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 'Mikrotik CLI' if filetype == 'mikrotik' else 'open vpn' if filetype == 'ovpn' else 'Keenetic CLI' if filetype == 'keenetic' else 'Wireguard' if filetype == 'wireguard' else 'спросить у пользователя'}")
|
||||
@@ -96,11 +209,11 @@ def read_config(cfg_file):
|
||||
print(f"{Style.BRIGHT}Локальный список платформ:{Style.RESET_ALL} {'включен' if str(localplatform).strip().lower() in ('yes', 'y') else 'выключен'}")
|
||||
print(f"{Style.BRIGHT}Локальный список DNS серверов:{Style.RESET_ALL} {'включен' if str(localdns).strip().lower() in ('yes', 'y') else 'выключен'}")
|
||||
|
||||
return service, request_limit, filename, cloudflare, filetype, gateway, run_command, dns_server_indices, mk_list_name, subnet, ken_gateway, localplatform, localdns, mk_comment
|
||||
return service, rate_limit, filename, cloudflare, filetype, gateway, run_command, dns_server_indices, mk_list_name, subnet, ken_gateway, localplatform, localdns, mk_comment
|
||||
|
||||
except Exception as e:
|
||||
print(f"{yellow(f'Ошибка загрузки {cfg_file}:')} {e}\n{Style.BRIGHT}Используются настройки 'по умолчанию'.{Style.RESET_ALL}")
|
||||
return '', 20, 'domain-ip-resolve.txt', '', '', '', '', [], '', '', '', '', '', 'off'
|
||||
return '', 50, 'domain-ip-resolve.txt', '', '', '', '', [], '', '', '', '', '', 'off'
|
||||
|
||||
def gateway_input(gateway):
|
||||
if not gateway:
|
||||
@@ -116,11 +229,107 @@ def ken_gateway_input(ken_gateway):
|
||||
else:
|
||||
return ken_gateway
|
||||
|
||||
def get_semaphore(request_limit):
|
||||
return defaultdict(lambda: Semaphore(request_limit))
|
||||
class DNSServerWorker:
|
||||
def __init__(self, name: str, nameservers: List[str], rate_limit: int = 10, stats_lock=None):
|
||||
self.name = name
|
||||
self.nameservers = nameservers
|
||||
self.rate_limit = rate_limit
|
||||
self.queue = asyncio.Queue()
|
||||
self.request_times = deque()
|
||||
self.results = []
|
||||
self.stats = {
|
||||
'processed': 0,
|
||||
'errors': 0,
|
||||
'success': 0
|
||||
}
|
||||
self.stats_lock = stats_lock or asyncio.Lock()
|
||||
self.rate_limit_lock = asyncio.Lock()
|
||||
|
||||
def init_semaphores(request_limit):
|
||||
return get_semaphore(request_limit)
|
||||
async def add_domain(self, domain: str):
|
||||
await self.queue.put(domain)
|
||||
|
||||
async def _enforce_rate_limit(self):
|
||||
async with self.rate_limit_lock:
|
||||
now = time.monotonic()
|
||||
|
||||
while self.request_times and now - self.request_times[0] >= 1.0:
|
||||
self.request_times.popleft()
|
||||
|
||||
if len(self.request_times) >= self.rate_limit:
|
||||
sleep_time = 1.0 - (now - self.request_times[0])
|
||||
if sleep_time > 0:
|
||||
await asyncio.sleep(sleep_time)
|
||||
now = time.monotonic()
|
||||
while self.request_times and now - self.request_times[0] >= 1.0:
|
||||
self.request_times.popleft()
|
||||
|
||||
self.request_times.append(now)
|
||||
|
||||
async def process_queue(self, global_stats: Dict[str, int]):
|
||||
resolver = dns.asyncresolver.Resolver()
|
||||
resolver.nameservers = self.nameservers
|
||||
resolver.timeout = 10.0
|
||||
resolver.lifetime = 15.0
|
||||
|
||||
domains = []
|
||||
while not self.queue.empty():
|
||||
domain = await self.queue.get()
|
||||
domains.append(domain)
|
||||
|
||||
async def process_single_domain(domain):
|
||||
await self._enforce_rate_limit()
|
||||
|
||||
try:
|
||||
response = await resolver.resolve(domain)
|
||||
ips = [ip.address for ip in response]
|
||||
|
||||
async with self.stats_lock:
|
||||
global_stats['total_domains_processed'] += 1
|
||||
self.stats['processed'] += 1
|
||||
self.stats['success'] += 1
|
||||
|
||||
return ips
|
||||
except dns.resolver.NoNameservers:
|
||||
async with self.stats_lock:
|
||||
global_stats['total_domains_processed'] += 1
|
||||
global_stats['domain_errors'] += 1
|
||||
self.stats['processed'] += 1
|
||||
self.stats['errors'] += 1
|
||||
return []
|
||||
except dns.resolver.Timeout:
|
||||
async with self.stats_lock:
|
||||
global_stats['total_domains_processed'] += 1
|
||||
global_stats['domain_errors'] += 1
|
||||
self.stats['processed'] += 1
|
||||
self.stats['errors'] += 1
|
||||
return []
|
||||
except dns.resolver.NXDOMAIN:
|
||||
async with self.stats_lock:
|
||||
global_stats['total_domains_processed'] += 1
|
||||
global_stats['domain_errors'] += 1
|
||||
self.stats['processed'] += 1
|
||||
self.stats['errors'] += 1
|
||||
return []
|
||||
except dns.resolver.NoAnswer:
|
||||
async with self.stats_lock:
|
||||
global_stats['total_domains_processed'] += 1
|
||||
global_stats['domain_errors'] += 1
|
||||
self.stats['processed'] += 1
|
||||
self.stats['errors'] += 1
|
||||
return []
|
||||
except Exception:
|
||||
async with self.stats_lock:
|
||||
global_stats['total_domains_processed'] += 1
|
||||
global_stats['domain_errors'] += 1
|
||||
self.stats['processed'] += 1
|
||||
self.stats['errors'] += 1
|
||||
return []
|
||||
|
||||
results = await asyncio.gather(*[process_single_domain(domain) for domain in domains], return_exceptions=True)
|
||||
|
||||
for result in results:
|
||||
if isinstance(result, list):
|
||||
self.results.extend(result)
|
||||
|
||||
async def load_urls(url: str) -> Dict[str, str]:
|
||||
try:
|
||||
@@ -224,67 +433,43 @@ async def load_dns_names(url_or_file: str) -> List[str]:
|
||||
print(f"Ошибка при чтении файла {url_or_file}: {e}")
|
||||
return []
|
||||
|
||||
async def resolve_domain_batch(domains: List[str], resolver: dns.asyncresolver.Resolver,
|
||||
semaphore: Semaphore, dns_server_name: str,
|
||||
stats: Dict[str, int], cloudflare_ips: Set[str],
|
||||
include_cloudflare: bool) -> List[str]:
|
||||
async with semaphore:
|
||||
resolved_ips = []
|
||||
for domain in domains:
|
||||
try:
|
||||
stats['total_domains_processed'] += 1
|
||||
response = await resolver.resolve(domain)
|
||||
ips = [ip.address for ip in response]
|
||||
|
||||
for ip_address in ips:
|
||||
if ip_address in ('127.0.0.1', '0.0.0.0') or ip_address in resolver.nameservers:
|
||||
stats['null_ips_count'] += 1
|
||||
elif include_cloudflare and ip_address in cloudflare_ips:
|
||||
stats['cloudflare_ips_count'] += 1
|
||||
else:
|
||||
resolved_ips.append(ip_address)
|
||||
print(f"{Fore.BLUE}{domain} IP-адрес: {ip_address} - {dns_server_name}{Style.RESET_ALL}")
|
||||
|
||||
except Exception:
|
||||
stats['domain_errors'] += 1
|
||||
|
||||
return resolved_ips
|
||||
|
||||
async def resolve_dns_optimized(service: str, dns_names: List[str],
|
||||
async def resolve_dns_with_workers(service: str, dns_names: List[str],
|
||||
dns_servers: List[Tuple[str, List[str]]],
|
||||
cloudflare_ips: Set[str], unique_ips_all_services: Set[str],
|
||||
semaphore_dict: Dict, stats: Dict[str, int],
|
||||
include_cloudflare: bool, batch_size: int = 50) -> str:
|
||||
stats: Dict[str, int], include_cloudflare: bool,
|
||||
rate_limit: int, stats_lock: asyncio.Lock = None) -> str:
|
||||
try:
|
||||
print(f"{Fore.YELLOW}Загрузка DNS имен платформы {service}...{Style.RESET_ALL}")
|
||||
if stats_lock is None:
|
||||
stats_lock = asyncio.Lock()
|
||||
|
||||
domain_batches = [dns_names[i:i + batch_size] for i in range(0, len(dns_names), batch_size)]
|
||||
|
||||
tasks = []
|
||||
|
||||
for batch in domain_batches:
|
||||
workers = []
|
||||
for server_name, servers in dns_servers:
|
||||
resolver = dns.asyncresolver.Resolver()
|
||||
resolver.nameservers = servers
|
||||
worker = DNSServerWorker(server_name, servers, rate_limit, stats_lock)
|
||||
workers.append(worker)
|
||||
|
||||
tasks.append(resolve_domain_batch(
|
||||
batch, resolver, semaphore_dict[server_name],
|
||||
server_name, stats, cloudflare_ips, include_cloudflare
|
||||
))
|
||||
for domain in dns_names:
|
||||
for worker in workers:
|
||||
await worker.add_domain(domain)
|
||||
|
||||
max_concurrent_tasks = min(len(tasks), 100)
|
||||
tasks = [worker.process_queue(stats) for worker in workers]
|
||||
|
||||
results = []
|
||||
for i in range(0, len(tasks), max_concurrent_tasks):
|
||||
batch_tasks = tasks[i:i + max_concurrent_tasks]
|
||||
batch_results = await asyncio.gather(*batch_tasks, return_exceptions=True)
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
for result in batch_results:
|
||||
if not isinstance(result, Exception):
|
||||
results.extend(result)
|
||||
all_nameservers = set()
|
||||
for _, servers in dns_servers:
|
||||
all_nameservers.update(servers)
|
||||
|
||||
unique_ips_current_service = set()
|
||||
for ip_address in results:
|
||||
for worker in workers:
|
||||
for ip_address in worker.results:
|
||||
if ip_address in ('127.0.0.1', '0.0.0.0') or ip_address in all_nameservers:
|
||||
stats['null_ips_count'] += 1
|
||||
continue
|
||||
|
||||
if include_cloudflare and ip_address in cloudflare_ips:
|
||||
stats['cloudflare_ips_count'] += 1
|
||||
continue
|
||||
|
||||
if ip_address not in unique_ips_all_services:
|
||||
unique_ips_current_service.add(ip_address)
|
||||
unique_ips_all_services.add(ip_address)
|
||||
@@ -465,6 +650,46 @@ def group_ips_in_subnets_optimized(filename: str, subnet: str):
|
||||
except Exception as e:
|
||||
print(f"Ошибка при обработке файла: {e}")
|
||||
|
||||
def split_file_by_lines(filename: str, max_lines: int = 999):
|
||||
try:
|
||||
with open(filename, 'r', encoding='utf-8') as file:
|
||||
lines = file.readlines()
|
||||
|
||||
total_lines = len(lines)
|
||||
|
||||
if total_lines <= max_lines:
|
||||
return False
|
||||
|
||||
base_name = filename.rsplit('.', 1)[0] if '.' in filename else filename
|
||||
extension = '.' + filename.rsplit('.', 1)[1] if '.' in filename else '.txt'
|
||||
|
||||
num_parts = (total_lines + max_lines - 1) // max_lines
|
||||
|
||||
print(f"\n{Style.BRIGHT}Результаты сохранены в файлы:{Style.RESET_ALL}")
|
||||
for part in range(num_parts):
|
||||
start_index = part * max_lines
|
||||
end_index = min((part + 1) * max_lines, total_lines)
|
||||
|
||||
part_filename = f"{base_name}_p{part + 1}{extension}"
|
||||
|
||||
with open(part_filename, 'w', encoding='utf-8') as file:
|
||||
file.writelines(lines[start_index:end_index])
|
||||
|
||||
print(f"{Style.BRIGHT}{part_filename} ({end_index - start_index} строк){Style.RESET_ALL}")
|
||||
|
||||
print(f"{Style.BRIGHT}Разделение завершено. Создано {num_parts} частей{Style.RESET_ALL}")
|
||||
|
||||
try:
|
||||
os.remove(filename)
|
||||
except Exception as e:
|
||||
print(f"{red('Не удалось удалить исходный файл:')} {e}")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"{red('Ошибка при разделении файла:')} {e}")
|
||||
return False
|
||||
|
||||
def process_file_format(filename, filetype, gateway, selected_service, mk_list_name, mk_comment, subnet, ken_gateway):
|
||||
def read_file(filename):
|
||||
try:
|
||||
@@ -554,6 +779,11 @@ def process_file_format(filename, filetype, gateway, selected_service, mk_list_n
|
||||
if filetype.lower() in formatters:
|
||||
write_file(filename, ips, formatters[filetype.lower()])
|
||||
|
||||
if filetype.lower() == 'keenetic bat':
|
||||
return split_file_by_lines(filename, max_lines=999)
|
||||
|
||||
return False
|
||||
|
||||
async def main():
|
||||
parser = argparse.ArgumentParser(description="DNS resolver script with custom config file.")
|
||||
parser.add_argument(
|
||||
@@ -566,7 +796,7 @@ async def main():
|
||||
|
||||
try:
|
||||
config_file = args.config
|
||||
(service, request_limit, filename, cloudflare, filetype, gateway, run_command,
|
||||
(service, rate_limit, filename, cloudflare, filetype, gateway, run_command,
|
||||
dns_server_indices, mk_list_name, subnet, ken_gateway, localplatform,
|
||||
localdns, mk_comment) = read_config(config_file)
|
||||
|
||||
@@ -596,7 +826,6 @@ async def main():
|
||||
cloudflare_ips = set()
|
||||
|
||||
unique_ips_all_services = set()
|
||||
semaphore = init_semaphores(request_limit)
|
||||
|
||||
stats = {
|
||||
'null_ips_count': 0,
|
||||
@@ -605,23 +834,57 @@ async def main():
|
||||
'domain_errors': 0
|
||||
}
|
||||
|
||||
total_domains = 0
|
||||
for service_name in selected_services:
|
||||
if service_name == 'Custom DNS list':
|
||||
total_domains += len(local_dns_names)
|
||||
else:
|
||||
url_or_file = urls[service_name]
|
||||
print(f"{Style.BRIGHT}Загрузка DNS имен платформы{Style.RESET_ALL} {service_name}...")
|
||||
dns_names = await load_dns_names(url_or_file)
|
||||
total_domains += len(dns_names)
|
||||
|
||||
domains_count = total_domains
|
||||
total_domains *= len(selected_dns_servers)
|
||||
stats['total_domains'] = total_domains
|
||||
stats['start_time'] = time.time()
|
||||
|
||||
print(f"{Style.BRIGHT}Загружено {domains_count} DNS имен.{Style.RESET_ALL}\n{yellow('Резолвинг...')}")
|
||||
|
||||
progress_tracker = ProgressTracker(
|
||||
total=total_domains,
|
||||
stats=stats,
|
||||
unique_ips_set=unique_ips_all_services,
|
||||
num_dns_servers=len(selected_dns_servers),
|
||||
rate_limit=rate_limit,
|
||||
domains_count=domains_count
|
||||
)
|
||||
progress_tracker.start()
|
||||
|
||||
stats_lock = asyncio.Lock()
|
||||
|
||||
periodic_updater = PeriodicProgressUpdater(progress_tracker, stats)
|
||||
await periodic_updater.start()
|
||||
|
||||
tasks = []
|
||||
|
||||
for service_name in selected_services:
|
||||
if service_name == 'Custom DNS list':
|
||||
tasks.append(resolve_dns_optimized(
|
||||
tasks.append(resolve_dns_with_workers(
|
||||
service_name, local_dns_names, selected_dns_servers,
|
||||
cloudflare_ips, unique_ips_all_services, semaphore,
|
||||
stats, include_cloudflare
|
||||
cloudflare_ips, unique_ips_all_services,
|
||||
stats, include_cloudflare, rate_limit,
|
||||
stats_lock
|
||||
))
|
||||
else:
|
||||
url_or_file = urls[service_name]
|
||||
dns_names = await load_dns_names(url_or_file)
|
||||
if dns_names:
|
||||
tasks.append(resolve_dns_optimized(
|
||||
tasks.append(resolve_dns_with_workers(
|
||||
service_name, dns_names, selected_dns_servers,
|
||||
cloudflare_ips, unique_ips_all_services, semaphore,
|
||||
stats, include_cloudflare
|
||||
cloudflare_ips, unique_ips_all_services,
|
||||
stats, include_cloudflare, rate_limit,
|
||||
stats_lock
|
||||
))
|
||||
|
||||
if tasks:
|
||||
@@ -635,32 +898,31 @@ async def main():
|
||||
with open(filename, 'w', encoding='utf-8') as file:
|
||||
pass
|
||||
|
||||
print(f"\n{yellow('Проверка завершена.')}")
|
||||
print(f"{Style.BRIGHT}Всего обработано DNS имен:{Style.RESET_ALL} {stats['total_domains_processed']}")
|
||||
print(f"{Style.BRIGHT}Разрешено IP-адресов из DNS имен:{Style.RESET_ALL} {len(unique_ips_all_services)}")
|
||||
print(f"{Style.BRIGHT}Ошибок разрешения доменов:{Style.RESET_ALL} {stats['domain_errors']}")
|
||||
if stats['null_ips_count'] > 0:
|
||||
print(f"{Style.BRIGHT}Исключено IP-адресов 'заглушек':{Style.RESET_ALL} {stats['null_ips_count']}")
|
||||
if include_cloudflare:
|
||||
print(f"{Style.BRIGHT}Исключено IP-адресов Cloudflare:{Style.RESET_ALL} {stats['cloudflare_ips_count']}")
|
||||
print(f"{Style.BRIGHT}Использовались DNS серверы:{Style.RESET_ALL} " + ', '.join(
|
||||
[f'{pair[0]} ({", ".join(pair[1])})' for pair in selected_dns_servers]))
|
||||
await periodic_updater.stop()
|
||||
progress_tracker.close()
|
||||
|
||||
print(f"{Style.BRIGHT}Использовались DNS серверы:{Style.RESET_ALL} " + ', '.join(
|
||||
[pair[0] for pair in selected_dns_servers]))
|
||||
|
||||
print(f"\n{yellow('Обработка результатов...')}")
|
||||
|
||||
subnet = subnet_input(subnet)
|
||||
if subnet != '32':
|
||||
group_ips_in_subnets_optimized(filename, subnet)
|
||||
|
||||
process_file_format(filename, filetype, gateway, selected_services, mk_list_name, mk_comment, subnet, ken_gateway)
|
||||
file_was_split = process_file_format(filename, filetype, gateway, selected_services, mk_list_name, mk_comment, subnet, ken_gateway)
|
||||
|
||||
if run_command:
|
||||
print("\nВыполнение команды после завершения скрипта...")
|
||||
os.system(run_command)
|
||||
else:
|
||||
if not file_was_split:
|
||||
print(f"\n{Style.BRIGHT}Результаты сохранены в файл:{Style.RESET_ALL}", filename)
|
||||
if os.name == 'nt':
|
||||
input(f"Нажмите {green('Enter')} для выхода...")
|
||||
|
||||
print(f"\n{Style.BRIGHT}Если есть желание, можно угостить автора чашечкой какао:{Style.RESET_ALL} {green('https://boosty.to/ground_zerro')}")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print(f"\n{red('Программа прервана пользователем')}")
|
||||
except Exception as e:
|
||||
|
||||
130
old/config.ini
130
old/config.ini
@@ -1,130 +0,0 @@
|
||||
[DomainMapper]
|
||||
# Локальный режим - загружать список сервисов и/или DNS серверов из локального файла
|
||||
# yes - включить
|
||||
# no или пусто - выключить
|
||||
localplatform = yes
|
||||
localdns = yes
|
||||
|
||||
# Имена сервисов, разделенные запятыми, для разрешения доменных имен в IP-адреса без запроса у пользователя
|
||||
# опции:
|
||||
# пустое значение - пользователю будет выведено меню выбора
|
||||
# all - проверить все сервисы
|
||||
# Antifilter community edition - список заблокированных DNS имен формируемый сообществом
|
||||
# ITDog Inside - ресурсы, в том числе зарубежные, которые блокируются или сами блокируют доступ из России.
|
||||
# ITDog Outside - ресурсы, которые доступны только внутри России и блокируют доступ из-за рубежа.
|
||||
# Youtube
|
||||
# Facebook
|
||||
# Openai
|
||||
# Tik-Tok
|
||||
# Instagram
|
||||
# Twitter
|
||||
# Netflix
|
||||
# Bing
|
||||
# Adobe
|
||||
# Apple
|
||||
# Google
|
||||
# Torrent Trackers - торрент трекеры
|
||||
# Search engines - поисковые системы
|
||||
# Github Copilot - ИИ помощник от github
|
||||
# Twitch
|
||||
# Discord
|
||||
# Jetbrains
|
||||
# Xbox
|
||||
# Telegram
|
||||
# Online movie theaters - некоторые из популярных оналйн кинотеатров
|
||||
# custom - Custom DNS list, это файл "custom-dns-list.txt" расположенный в одном каталоге со скриптом
|
||||
service =
|
||||
|
||||
# DNS сервера (номер), разделенные пробелом, которые будут использоваться для разрешения доменных имен
|
||||
# опции:
|
||||
# пустое значение - пользователю будет выведено меню выбора
|
||||
# 0 - использовать все доступные DNS серверы
|
||||
# 1 - Системный DNS
|
||||
# 2 - Google Public DNS
|
||||
# 3 - Quad9
|
||||
# 4 - Cloudflare DNS
|
||||
# 5 - OpenDNS
|
||||
# 6 - Cisco Umbrella
|
||||
# 7 - DNS.Watch
|
||||
# 8 - Dyn
|
||||
# 9 - CleanBrowsing
|
||||
# 10 - Alternate DNS
|
||||
# 11 - AdGuard DNS
|
||||
# 12 - Control D
|
||||
# 13 - Yandex (основной)
|
||||
dnsserver =
|
||||
|
||||
# Включить фильтрацию IP-адресов cloudflare и не записывать их в файл результатов
|
||||
# опции:
|
||||
# пустое значение - пользователю будет выведено меню выбора
|
||||
# yes - исключить IP адреса cloudflare из итогового списка
|
||||
# no - оставить IP адреса cloudflare в итоговом списке
|
||||
cloudflare =
|
||||
|
||||
# Сгруппировать подсети
|
||||
# опции:
|
||||
# пустое значение - пользователю будет выведено меню выбора
|
||||
# 16 - группировка подсетей до /16 (255.255.0.0)
|
||||
# 24 - группировка подсетей до /24 (255.255.255.0)
|
||||
# mix - /24 и /32 в одном файле
|
||||
# no - оставить как есть
|
||||
subnet =
|
||||
|
||||
# Имя конечного файла
|
||||
# опции:
|
||||
# пустое значение - "domain-ip-resolve.txt" в каталоге со скриптом
|
||||
# имя_файла - файл с указанным именем будет сохранен в каталоге со скриптом
|
||||
# полный_путь/имя_файла - файл будет сохранен с указанным именем в указанной каталоге
|
||||
filename =
|
||||
|
||||
# Количество потоков сканирования, если не указано - будет использоваться 20 потоков
|
||||
threads = 5
|
||||
|
||||
# Формат сохранения файла результатов
|
||||
# опции:
|
||||
# пустое значение - пользователю будет выведено меню выбора
|
||||
# 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
|
||||
# ovpn - push "route %IP% 255.255.255.255"
|
||||
# wireguard - %IP%/32, и т.д...
|
||||
# keenetic - ip route %IP%/32 %gateway% auto !%LIST_NAME%
|
||||
filetype =
|
||||
|
||||
# адрес шлюза или имя интерфейса - используется при сохранении IP-адресов в 'win' и 'unix' формате
|
||||
# опции:
|
||||
# пустое значение - пользователю будет выведен запрос с подсказкой
|
||||
# укажите IP-адрес шлюза или имя интерфейса
|
||||
gateway =
|
||||
|
||||
# адрес шлюза или имя интерфейса - используется при сохранении IP-адресов в 'keenetic' формате
|
||||
# опции:
|
||||
# пустое значение - пользователю будет выведен запрос с подсказкой
|
||||
# укажите IP-адрес шлюза или имя интерфейса или IP-адрес шлюза и через пробел имя интерфейса
|
||||
keenetic =
|
||||
|
||||
# имя списка - используется при сохранении IP-адресов в 'mikrotik' формате
|
||||
# опции:
|
||||
# пустое значение - пользователю будет выведен запрос с подсказкой
|
||||
# укажите имя
|
||||
listname =
|
||||
|
||||
# комментарий - используется при сохранении IP-адресов в 'mikrotik' формате
|
||||
# опции:
|
||||
# on - если вам нужен comment=%SERVICE_NAME% в строке
|
||||
# off - убрать comment=%SERVICE_NAME% из строки
|
||||
mk_comment = off
|
||||
|
||||
|
||||
# Показывать сведения о загруженной конфигурации при запуске скрипта
|
||||
# опции:
|
||||
# yes или пустое значение - показывать
|
||||
# no - скрыть
|
||||
cfginfo = yes
|
||||
|
||||
# Команда для консоли после завершения скриптом всех операций, может быть полезно для автоматизации и комбинирования с другим скриптом, кодом или программой
|
||||
# опции:
|
||||
# исполняемая_команда_для_консоли
|
||||
run =
|
||||
641
old/main.py
641
old/main.py
@@ -1,641 +0,0 @@
|
||||
import argparse
|
||||
import asyncio
|
||||
import configparser
|
||||
import ipaddress
|
||||
import os
|
||||
from asyncio import Semaphore
|
||||
from collections import defaultdict
|
||||
|
||||
import dns.asyncresolver
|
||||
import httpx
|
||||
from colorama import Fore, Style, init
|
||||
|
||||
# Цвета
|
||||
init(autoreset=True)
|
||||
|
||||
|
||||
def yellow(text):
|
||||
return f"{Fore.YELLOW}{text}{Style.RESET_ALL}"
|
||||
|
||||
|
||||
def green(text):
|
||||
return f"{Fore.GREEN}{text}{Style.RESET_ALL}"
|
||||
|
||||
|
||||
def cyan(text):
|
||||
return f"{Fore.CYAN}{text}{Style.RESET_ALL}"
|
||||
|
||||
|
||||
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(cfg_file):
|
||||
try:
|
||||
config = configparser.ConfigParser()
|
||||
with open(cfg_file, 'r', encoding='utf-8') as file:
|
||||
config.read_file(file)
|
||||
if 'DomainMapper' in config:
|
||||
config = config['DomainMapper']
|
||||
service = config.get('service') or ''
|
||||
request_limit = int(config.get('threads') or 20)
|
||||
filename = config.get('filename') or 'domain-ip-resolve.txt'
|
||||
cloudflare = config.get('cloudflare') or ''
|
||||
filetype = config.get('filetype') or ''
|
||||
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 ''
|
||||
subnet = config.get('subnet') or ''
|
||||
cfginfo = config.get('cfginfo') or 'yes' # Не возвращаем его в main
|
||||
ken_gateway = config.get('keenetic') or ''
|
||||
localplatform = config.get('localplatform') or ''
|
||||
localdns = config.get('localdns') or ''
|
||||
mk_comment = config.get('mk_comment') or 'off'
|
||||
|
||||
if cfginfo in ['yes', 'y']:
|
||||
print(f"{yellow(f'Загружена конфигурация из {cfg_file}:')}")
|
||||
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 in ['y', 'yes'] else 'вЫключена' if cloudflare in ['n', 'no'] else 'спросить у пользователя'}")
|
||||
print(f"{Style.BRIGHT}Агрегация IP-адресов:{Style.RESET_ALL} {'mix режим /24 (255.255.255.0) + /32 (255.255.255.255)' if subnet == 'mix' else 'до /16 подсети (255.255.0.0)' if subnet == '16' else 'до /24 подсети (255.255.255.0)' if subnet == '24' else 'вЫключена' if subnet in ['n', 'no'] else 'спросить у пользователя'}")
|
||||
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 'Mikrotik CLI' if filetype == 'mikrotik' else 'open vpn' if filetype == 'ovpn' else 'Keenetic CLI' if filetype == 'keenetic' else 'Wireguard' if filetype == 'wireguard' else 'спросить у пользователя'}")
|
||||
if filetype in ['win', 'unix', '']:
|
||||
print(f"{Style.BRIGHT}Шлюз/Имя интерфейса для Windows и Linux route:{Style.RESET_ALL} {gateway if gateway else 'спросить у пользователя'}")
|
||||
if filetype in ['keenetic', '']:
|
||||
print(f"{Style.BRIGHT}Шлюз/Имя интерфейса для Keenetic CLI:{Style.RESET_ALL} {ken_gateway if ken_gateway else 'спросить у пользователя'}")
|
||||
if filetype in ['mikrotik', '']:
|
||||
print(f"{Style.BRIGHT}Имя списка для Mikrotik firewall:{Style.RESET_ALL} {mk_list_name if mk_list_name else 'спросить у пользователя'}")
|
||||
print(f"{Style.BRIGHT}'comment=' в Mikrotik firewall:{Style.RESET_ALL} {'вЫключен' if mk_comment == 'off' else 'включен'}")
|
||||
print(f"{Style.BRIGHT}Сохранить результат в файл:{Style.RESET_ALL} {filename}")
|
||||
print(f"{Style.BRIGHT}Выполнить по завершению:{Style.RESET_ALL} {run_command if run_command else 'не указано'}")
|
||||
if localplatform in ['yes', 'y'] or localdns in ['yes', 'y']:
|
||||
print(f"\n{red('!!! Включен локальный режим !!!')}")
|
||||
print(f"{Style.BRIGHT}Список сервисов будет загружен из:{Style.RESET_ALL} {'файла platformdb' if localplatform in ['yes', 'y'] else 'сети'}")
|
||||
print(f"{Style.BRIGHT}Список DNS серверов будет загружен из:{Style.RESET_ALL} {'файла dnsdb' if localdns in ['yes', 'y'] else 'сети'}")
|
||||
|
||||
return service, request_limit, filename, cloudflare, filetype, gateway, run_command, dns_server_indices, mk_list_name, subnet, ken_gateway, localplatform, localdns, mk_comment
|
||||
|
||||
except Exception as e:
|
||||
print(f"{yellow(f'Ошибка загрузки {cfg_file}:')} {e}\n{Style.BRIGHT}Используются настройки 'по умолчанию'.{Style.RESET_ALL}")
|
||||
return '', 20, 'domain-ip-resolve.txt', '', '', '', '', [], '', '', '', '', '', 'off'
|
||||
|
||||
|
||||
# IP шлюза для win и unix
|
||||
def gateway_input(gateway):
|
||||
if not gateway:
|
||||
input_gateway = input(f"Укажите {green('IP шлюза')} или {green('имя интерфейса')}: ")
|
||||
return input_gateway.strip() if input_gateway else None
|
||||
else:
|
||||
return gateway
|
||||
|
||||
|
||||
# IP шлюза и имя интерфейса для keenetic
|
||||
def ken_gateway_input(ken_gateway):
|
||||
if not ken_gateway:
|
||||
input_ken_gateway = input(f"Укажите {green('IP шлюза')} или {green('имя интерфейса')} или {green('IP шлюза')} и через пробел {green('имя интерфейса')}: ")
|
||||
return input_ken_gateway.strip() if input_ken_gateway else None
|
||||
else:
|
||||
return ken_gateway
|
||||
|
||||
|
||||
# Ограничение числа запросов
|
||||
def get_semaphore(request_limit):
|
||||
return defaultdict(lambda: Semaphore(request_limit))
|
||||
|
||||
|
||||
# Инициализация semaphore для ограничения запросов
|
||||
def init_semaphores(request_limit):
|
||||
return get_semaphore(request_limit)
|
||||
|
||||
|
||||
# Загрузка списка платформ из сети
|
||||
async def load_urls(url):
|
||||
try:
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.get(url)
|
||||
response.raise_for_status()
|
||||
text = response.text
|
||||
lines = text.split('\n')
|
||||
urls = {}
|
||||
for line in lines:
|
||||
if line.strip():
|
||||
service, url = line.split(': ', 1)
|
||||
urls[service.strip()] = url.strip()
|
||||
return urls
|
||||
except Exception as e:
|
||||
print(f"Ошибка при загрузке списка платформ: {e}")
|
||||
return {}
|
||||
|
||||
|
||||
# Загрузка списка платформ из локального файла
|
||||
async def load_urls_from_file():
|
||||
try:
|
||||
with open('platformdb', 'r', encoding='utf-8') as file:
|
||||
urls = {}
|
||||
for line in file:
|
||||
if line.strip():
|
||||
service, url = line.split(': ', 1)
|
||||
urls[service.strip()] = url.strip()
|
||||
return urls
|
||||
except Exception as e:
|
||||
print(f"Ошибка при загрузке списка платформ: {e}")
|
||||
return {}
|
||||
|
||||
|
||||
# Загрузка списка DNS серверов
|
||||
async def load_dns_servers(url):
|
||||
try:
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.get(url)
|
||||
response.raise_for_status()
|
||||
text = response.text
|
||||
lines = text.split('\n')
|
||||
dns_servers = {}
|
||||
for line in lines:
|
||||
if line.strip():
|
||||
service, servers = line.split(': ', 1)
|
||||
dns_servers[service.strip()] = servers.strip().split()
|
||||
return dns_servers
|
||||
except Exception as e:
|
||||
print(f"Ошибка при загрузке списка DNS серверов: {e}")
|
||||
return {}
|
||||
|
||||
|
||||
# Загрузка списка DNS серверов из локального файла
|
||||
async def load_dns_from_file():
|
||||
try:
|
||||
with open('dnsdb', 'r') as file:
|
||||
dns_servers = {}
|
||||
for line in file:
|
||||
if line.strip():
|
||||
service, servers = line.split(': ', 1)
|
||||
dns_servers[service.strip()] = servers.strip().split()
|
||||
return dns_servers
|
||||
except Exception as e:
|
||||
print(f"Ошибка при загрузке списка DNS серверов: {e}")
|
||||
return {}
|
||||
|
||||
|
||||
# Загрузка IP-адресов cloudflare
|
||||
async def get_cloudflare_ips():
|
||||
try:
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.get("https://www.cloudflare.com/ips-v4/")
|
||||
response.raise_for_status()
|
||||
text = response.text
|
||||
cloudflare_ips = set()
|
||||
for line in text.splitlines():
|
||||
line = line.strip()
|
||||
if '/' in line:
|
||||
try:
|
||||
ip_network = ipaddress.ip_network(line)
|
||||
for ip in ip_network:
|
||||
cloudflare_ips.add(str(ip))
|
||||
except ValueError:
|
||||
continue
|
||||
return cloudflare_ips
|
||||
except Exception as e:
|
||||
print("Ошибка при получении IP адресов Cloudflare:", e)
|
||||
return set()
|
||||
|
||||
|
||||
# Загрузка списков DNS имен из сети и локальных файлов
|
||||
async def load_dns_names(url_or_file):
|
||||
if url_or_file.startswith("http"):
|
||||
async with httpx.AsyncClient() as client:
|
||||
try:
|
||||
response = await client.get(url_or_file)
|
||||
response.raise_for_status()
|
||||
return response.text.splitlines()
|
||||
except httpx.HTTPStatusError as e:
|
||||
print(f"Ошибка при загрузке DNS имен: {e}")
|
||||
return []
|
||||
else:
|
||||
# Локальный файл
|
||||
with open(url_or_file, 'r', encoding='utf-8') as file:
|
||||
return file.read().splitlines()
|
||||
|
||||
|
||||
async def resolve_domain(domain, resolver, semaphore, dns_server_name, null_ips_count, cloudflare_ips,
|
||||
cloudflare_ips_count, total_domains_processed, include_cloudflare):
|
||||
async with semaphore:
|
||||
try:
|
||||
total_domains_processed[0] += 1
|
||||
response = await resolver.resolve(domain)
|
||||
ips = [ip.address for ip in response]
|
||||
filtered_ips = []
|
||||
for ip_address in ips:
|
||||
if ip_address in ('127.0.0.1', '0.0.0.0') or ip_address in resolver.nameservers:
|
||||
null_ips_count[0] += 1
|
||||
elif include_cloudflare and ip_address in cloudflare_ips:
|
||||
cloudflare_ips_count[0] += 1
|
||||
else:
|
||||
filtered_ips.append(ip_address)
|
||||
print(f"{Fore.BLUE}{domain} IP-адрес: {ip_address} - {dns_server_name}{Style.RESET_ALL}")
|
||||
return filtered_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, total_domains_processed, include_cloudflare):
|
||||
try:
|
||||
print(f"{Fore.YELLOW}Загрузка DNS имен платформы {service}...{Style.RESET_ALL}")
|
||||
|
||||
tasks = []
|
||||
for server_name, servers in dns_servers:
|
||||
resolver = dns.asyncresolver.Resolver()
|
||||
resolver.nameservers = servers
|
||||
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, total_domains_processed,
|
||||
include_cloudflare))
|
||||
|
||||
results = await asyncio.gather(*tasks)
|
||||
|
||||
unique_ips_current_service = set()
|
||||
for result in results:
|
||||
for ip_address in result:
|
||||
if ip_address not in unique_ips_all_services:
|
||||
unique_ips_current_service.add(ip_address)
|
||||
unique_ips_all_services.add(ip_address)
|
||||
|
||||
return '\n'.join(unique_ips_current_service) + '\n'
|
||||
except Exception as e:
|
||||
print(f"Не удалось сопоставить IP адреса {service} его доменным именам.", e)
|
||||
return ""
|
||||
|
||||
|
||||
def check_service_config(service, urls, local_dns_names):
|
||||
if service:
|
||||
services = [s.strip() for s in service.split(',')]
|
||||
if "custom" in services:
|
||||
services.remove("custom")
|
||||
if local_dns_names:
|
||||
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:
|
||||
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}. Custom DNS list")
|
||||
|
||||
selection = input(f"\nУкажите {green('номера')} платформ через пробел и нажмите {green('Enter')}: ")
|
||||
if selection.strip():
|
||||
selections = selection.split()
|
||||
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:
|
||||
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:
|
||||
services.append('Custom DNS list')
|
||||
break
|
||||
return services
|
||||
|
||||
|
||||
# Промт cloudflare фильтр
|
||||
def check_include_cloudflare(cloudflare):
|
||||
if cloudflare in ['yes', 'y', 'no', 'n']:
|
||||
return cloudflare in ['yes', 'y']
|
||||
return input(f"\n{yellow('Исключить IP адреса Cloudflare из итогового списка?')}"
|
||||
f"\n{green('yes')} - исключить"
|
||||
f"\n{green('Enter')} - оставить: ").strip().lower() in ['yes', 'y']
|
||||
|
||||
def check_dns_servers(dns_servers, dns_server_indices):
|
||||
# Получение системных DNS серверов
|
||||
system_dns_servers = dns.asyncresolver.Resolver().nameservers
|
||||
|
||||
# Формирование списка всех доступных серверов
|
||||
dns_server_options = [('Системный DNS', system_dns_servers)] + list(dns_servers.items())
|
||||
|
||||
selected_dns_servers = []
|
||||
|
||||
# Если указаны индексы серверов в конфиге
|
||||
if dns_server_indices:
|
||||
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. Выбрать все")
|
||||
for idx, (name, servers) in enumerate(dns_server_options, 1):
|
||||
print(f"{idx}. {name}: {', '.join(servers)}")
|
||||
|
||||
selection = input(f"\nУкажите {green('номера')} DNS серверов через пробел и нажмите {green('Enter')}: ")
|
||||
if selection.strip():
|
||||
selections = selection.split()
|
||||
if '0' in selections:
|
||||
selected_dns_servers = dns_server_options
|
||||
break
|
||||
else:
|
||||
for sel in selections:
|
||||
if sel.isdigit():
|
||||
sel = int(sel)
|
||||
if 1 <= sel <= len(dns_server_options):
|
||||
selected_dns_servers.append(dns_server_options[sel - 1])
|
||||
break
|
||||
|
||||
return selected_dns_servers
|
||||
|
||||
|
||||
# комментарй для microtik firewall
|
||||
def mk_list_name_input(mk_list_name):
|
||||
if not mk_list_name:
|
||||
input_mk_list_name = input(f"Введите {green('LIST_NAME')} для Mikrotik firewall: ")
|
||||
return input_mk_list_name.strip() if input_mk_list_name else None
|
||||
else:
|
||||
return mk_list_name
|
||||
|
||||
|
||||
# Уплотняем имена сервисов
|
||||
def comment(selected_service):
|
||||
return ",".join(["".join(word.title() for word in s.split()) for s in selected_service])
|
||||
|
||||
|
||||
# Промт на объединение IP в подсети
|
||||
def subnet_input(subnet):
|
||||
if not subnet: # Проверяем, является ли значение пустым
|
||||
subnet = input(
|
||||
f"\n{yellow('Объединить IP-адреса в подсети?')} "
|
||||
f"\n{green('16')} - сократить до /16 (255.255.0.0)"
|
||||
f"\n{green('24')} - сократить до /24 (255.255.255.0)"
|
||||
f"\n{green('mix')} - сократить до /24 (255.255.255.0) и /32 (255.255.255.255)"
|
||||
f"\n{green('Enter')} - пропустить: "
|
||||
).strip().lower()
|
||||
|
||||
return subnet if subnet in {'16', '24', 'mix'} else '32'
|
||||
|
||||
|
||||
# Агрегация маршрутов
|
||||
def group_ips_in_subnets(filename, subnet):
|
||||
try:
|
||||
with open(filename, 'r', encoding='utf-8') as file:
|
||||
ips = {line.strip() for line in file if line.strip()} # Собираем уникальные IP адреса
|
||||
|
||||
subnets = set()
|
||||
|
||||
def process_ips(subnet):
|
||||
for ip in ips:
|
||||
try:
|
||||
if subnet == "16":
|
||||
# Преобразуем в /16 (два последних октета заменяются на 0.0)
|
||||
network = ipaddress.IPv4Network(f"{ip}/16", strict=False)
|
||||
subnets.add(f"{network.network_address}")
|
||||
elif subnet == "24":
|
||||
# Преобразуем в /24 (последний октет заменяется на 0)
|
||||
network = ipaddress.IPv4Network(f"{ip}/24", strict=False)
|
||||
subnets.add(f"{network.network_address}")
|
||||
except ValueError as e:
|
||||
print(f"Ошибка в IP адресе: {ip} - {e}")
|
||||
|
||||
if subnet in ["24", "16"]:
|
||||
process_ips(subnet)
|
||||
print(f"{Style.BRIGHT}IP-адреса агрегированы до /{subnet} подсети{Style.RESET_ALL}")
|
||||
|
||||
elif subnet == "mix":
|
||||
octet_groups = {}
|
||||
for ip in ips:
|
||||
key = '.'.join(ip.split('.')[:3]) # Группировка по первым трем октетам
|
||||
if key not in octet_groups:
|
||||
octet_groups[key] = []
|
||||
octet_groups[key].append(ip)
|
||||
|
||||
# IP-адреса с совпадающими первыми тремя октетами
|
||||
network_24 = {key + '.0' for key, group in octet_groups.items() if
|
||||
len(group) > 1} # Базовый IP для /24 подсетей
|
||||
# Удаляем IP с совпадающими первыми тремя октетами из множества
|
||||
ips -= {ip for group in octet_groups.values() if len(group) > 1 for ip in group}
|
||||
# Оставляем только IP без указания маски для /24 и одиночных IP
|
||||
subnets.update(ips) # IP без маски для одиночных IP
|
||||
subnets.update(network_24) # Базовые IP для /24 подсетей
|
||||
print(f"{Style.BRIGHT}IP-адреса агрегированы до масок /24 и /32{Style.RESET_ALL}")
|
||||
|
||||
with open(filename, 'w', encoding='utf-8') as file:
|
||||
for subnet in sorted(subnets):
|
||||
file.write(subnet + '\n')
|
||||
|
||||
except Exception as e:
|
||||
print(f"Ошибка при обработке файла: {e}")
|
||||
|
||||
|
||||
# Выбор формата сохранения результатов
|
||||
def process_file_format(filename, filetype, gateway, selected_service, mk_list_name, mk_comment, subnet, ken_gateway):
|
||||
def read_file(filename):
|
||||
try:
|
||||
with open(filename, 'r', encoding='utf-8') as file:
|
||||
return file.readlines()
|
||||
except Exception as e:
|
||||
print(f"Ошибка чтения файла: {e}")
|
||||
return None
|
||||
|
||||
def write_file(filename, ips, formatter):
|
||||
formatted_ips = [formatter(ip.strip()) for ip in ips]
|
||||
with open(filename, 'w', encoding='utf-8') as file:
|
||||
if filetype.lower() == 'wireguard':
|
||||
file.write(', '.join(formatted_ips))
|
||||
else:
|
||||
file.write('\n'.join(formatted_ips))
|
||||
|
||||
# Определение маски подсети
|
||||
net_mask = subnet if subnet == "mix" else "255.255.0.0" if subnet == "16" else "255.255.255.0" if subnet == "24" else "255.255.255.255"
|
||||
|
||||
if not filetype:
|
||||
filetype = input(f"""
|
||||
{yellow('В каком формате сохранить файл?')}
|
||||
{green('win')} - route add {cyan('IP')} mask {net_mask} {cyan('GATEWAY')}
|
||||
{green('unix')} - ip route {cyan('IP')}/{subnet} {cyan('GATEWAY')}
|
||||
{green('keenetic')} - ip route {cyan('IP')}/{subnet} {cyan('GATEWAY GATEWAY_NAME')} auto !{comment(selected_service)}
|
||||
{green('cidr')} - {cyan('IP')}/{subnet}
|
||||
{green('mikrotik')} - /ip/firewall/address-list add list={cyan("LIST_NAME")}{f' comment="{comment(selected_service)}"' if mk_comment != "off" else ""} address={cyan("IP")}/{subnet}
|
||||
{green('ovpn')} - push "route {cyan('IP')} {net_mask}"
|
||||
{green('wireguard')} - {cyan('IP')}/{subnet}, {cyan('IP')}/{subnet}, и т.д...
|
||||
{green('Enter')} - {cyan('IP')}
|
||||
Ваш выбор: """)
|
||||
|
||||
ips = read_file(filename)
|
||||
if not ips:
|
||||
return
|
||||
|
||||
# Дополнительные запросы в зависимости от формата файла
|
||||
if filetype in ['win', 'unix']: # Запрашиваем IP шлюза для win и unix
|
||||
gateway = gateway_input(gateway)
|
||||
elif filetype == 'keenetic': # Запрашиваем IP шлюза и имя интерфейса для keenetic
|
||||
ken_gateway = ken_gateway_input(ken_gateway)
|
||||
elif filetype == 'mikrotik': # Запрашиваем ввод комментария для microtik firewall
|
||||
mk_list_name = mk_list_name_input(mk_list_name)
|
||||
|
||||
# обычный формат
|
||||
formatters = {
|
||||
'win': lambda ip: f"route add {ip} mask {net_mask} {gateway}",
|
||||
'unix': lambda ip: f"ip route {ip}/{subnet} {gateway}",
|
||||
'keenetic': lambda ip: f"ip route {ip}/{subnet} {ken_gateway} auto !{comment(selected_service)}",
|
||||
'cidr': lambda ip: f"{ip}/{subnet}",
|
||||
'ovpn': lambda ip: f'push "route {ip} {net_mask}"',
|
||||
'mikrotik': lambda ip: f'/ip/firewall/address-list add list={mk_list_name}' + (f' comment="{comment(selected_service)}"' if mk_comment != "off" else "") + f' address={ip}/{subnet}',
|
||||
'wireguard': lambda ip: f"{ip}/{subnet}"
|
||||
}
|
||||
|
||||
# mix формат
|
||||
if subnet == "mix":
|
||||
if filetype.lower() == 'win': # Обработка для win
|
||||
mix_formatter = lambda ip: f"{ip.strip()} mask 255.255.255.0" if ip.endswith(
|
||||
'.0') else f"{ip.strip()} mask 255.255.255.255"
|
||||
elif filetype.lower() == 'ovpn': # Обработка для ovpn
|
||||
mix_formatter = lambda ip: f"{ip.strip()} 255.255.255.0" if ip.endswith(
|
||||
'.0') else f"{ip.strip()} 255.255.255.255"
|
||||
else: # Обработка для остальных форматов
|
||||
mix_formatter = lambda ip: f"{ip.strip()}/24" if ip.endswith('.0') else f"{ip.strip()}/32"
|
||||
|
||||
formatters.update({
|
||||
'win': lambda ip: f"route add {mix_formatter(ip)} {gateway}",
|
||||
'unix': lambda ip: f"ip route {mix_formatter(ip)} {gateway}",
|
||||
'keenetic': lambda ip: f"ip route {mix_formatter(ip)} {ken_gateway} auto !{comment(selected_service)}",
|
||||
'cidr': lambda ip: f"{mix_formatter(ip)}",
|
||||
'ovpn': lambda ip: f'push "route {mix_formatter(ip)}"',
|
||||
'mikrotik': lambda ip: f'/ip/firewall/address-list add list={mk_list_name}' + (f' comment="{comment(selected_service)}"' if mk_comment != "off" else "") + f' address={mix_formatter(ip)}',
|
||||
'wireguard': lambda ip: f"{mix_formatter(ip)}"
|
||||
})
|
||||
|
||||
# Запись в файл
|
||||
if filetype.lower() in formatters:
|
||||
write_file(filename, ips, formatters[filetype.lower()])
|
||||
|
||||
|
||||
# Стартуем
|
||||
async def main():
|
||||
# Парсинг аргументов командной строки
|
||||
parser = argparse.ArgumentParser(description="DNS resolver script with custom config file.")
|
||||
parser.add_argument(
|
||||
'-c', '--config',
|
||||
type=str,
|
||||
default='config.ini',
|
||||
help='Путь к конфигурационному файлу (по умолчанию: config.ini)'
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
# Инициализация настроек из переданного конфигурационного файла
|
||||
config_file = args.config
|
||||
(service, request_limit, filename, cloudflare, filetype, gateway, run_command, dns_server_indices, mk_list_name, subnet, ken_gateway, localplatform, localdns, mk_comment) = read_config(config_file)
|
||||
|
||||
# Загрузка списка платформ
|
||||
if localplatform in ['yes', 'y']:
|
||||
urls = await load_urls_from_file()
|
||||
|
||||
else:
|
||||
platform_db_url = "https://raw.githubusercontent.com/Ground-Zerro/DomainMapper/main/platformdb"
|
||||
urls = await load_urls(platform_db_url)
|
||||
|
||||
# Подхват "custom-dns-list.txt" если существует
|
||||
local_dns_names = []
|
||||
if os.path.exists('custom-dns-list.txt'):
|
||||
with open('custom-dns-list.txt', 'r', encoding='utf-8') 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-серверов
|
||||
if localdns in ['yes', 'y']:
|
||||
dns_servers = await load_dns_from_file()
|
||||
|
||||
else:
|
||||
dns_db_url = "https://raw.githubusercontent.com/Ground-Zerro/DomainMapper/main/dnsdb"
|
||||
dns_servers = await load_dns_servers(dns_db_url)
|
||||
|
||||
# Выбор DNS-серверов
|
||||
selected_dns_servers = check_dns_servers(dns_servers, dns_server_indices)
|
||||
|
||||
# Фильтр Cloudflare
|
||||
include_cloudflare = check_include_cloudflare(cloudflare)
|
||||
if include_cloudflare: # Загрузка IP-адресов Cloudflare
|
||||
cloudflare_ips = await get_cloudflare_ips()
|
||||
else:
|
||||
cloudflare_ips = set()
|
||||
|
||||
unique_ips_all_services = set()
|
||||
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 == '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,
|
||||
total_domains_processed, include_cloudflare))
|
||||
|
||||
else:
|
||||
url_or_file = urls[service]
|
||||
dns_names = await load_dns_names(url_or_file)
|
||||
tasks.append(resolve_dns(service, dns_names, selected_dns_servers, cloudflare_ips, unique_ips_all_services,
|
||||
semaphore, null_ips_count, cloudflare_ips_count, total_domains_processed,
|
||||
include_cloudflare))
|
||||
|
||||
results = await asyncio.gather(*tasks)
|
||||
|
||||
with open(filename, 'w', encoding='utf-8') as file:
|
||||
for result in results:
|
||||
file.write(result)
|
||||
|
||||
print(f"\n{yellow('Проверка завершена.')}")
|
||||
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"{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)}")
|
||||
|
||||
# Группировка IP-адресов в подсети
|
||||
subnet = subnet_input(subnet)
|
||||
if subnet != '32': # Если не '32', вызываем функцию для агрегации
|
||||
group_ips_in_subnets(filename, subnet)
|
||||
|
||||
process_file_format(filename, filetype, gateway, selected_services, mk_list_name, mk_comment, subnet, ken_gateway)
|
||||
|
||||
if run_command:
|
||||
print("\nВыполнение команды после завершения скрипта...")
|
||||
os.system(run_command)
|
||||
else:
|
||||
print(f"\n{Style.BRIGHT}Результаты сохранены в файл:{Style.RESET_ALL}", filename)
|
||||
if os.name == 'nt':
|
||||
input(f"Нажмите {green('Enter')} для выхода...")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -12,6 +12,7 @@ youtu.be
|
||||
youtube.com
|
||||
youtubei.googleapis.com
|
||||
yt4.ggpht.com
|
||||
yt3.ggpht.com
|
||||
ytimg.com
|
||||
ytimg.l.google.com
|
||||
r1---sn-2gb7sn7r.googlevideo.com
|
||||
@@ -7342,6 +7343,7 @@ rr3---sn-5hneknek.googlevideo.com
|
||||
rr3---sn-5hneknes.googlevideo.com
|
||||
rr3---sn-5oxmp55u-8pxe.googlevideo.com
|
||||
rr3---sn-8vq54voxu-5qce.googlevideo.com
|
||||
rr3---sn-8ph2xajvh-axql.googlevideo.com
|
||||
rr3---sn-a5mekn6d.googlevideo.com
|
||||
rr3---sn-a5mekn6l.googlevideo.com
|
||||
rr3---sn-a5mekn6r.googlevideo.com
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
configparser~=7.0.1
|
||||
ipaddress~=1.0.23
|
||||
dnspython~=2.6.1
|
||||
httpx~=0.27.0
|
||||
colorama~=0.4.6
|
||||
requests~=2.31.0
|
||||
beautifulsoup4~=4.12.3
|
||||
dnspython>=2.6.1
|
||||
httpx>=0.27.0
|
||||
colorama>=0.4.6
|
||||
tqdm>=4.66.0
|
||||
|
||||
requests>=2.31.0
|
||||
beautifulsoup4>=4.12.3
|
||||
|
||||
@@ -71,27 +71,44 @@
|
||||
|
||||
### Функции
|
||||
|
||||
- Загрузка списка IP-адресов из файла.
|
||||
- Агрегация IP-адресов в подсети с масками `/16`, `/24`, или объединение нескольких подсетей.
|
||||
- Исключение IP-адресов Cloudflare из итогового списка (при необходимости).
|
||||
- Извлечение IP-адресов из файла (файл может содержать любой текст - IP автоматически извлекаются).
|
||||
- Исключение IP-адресов Cloudflare из итогового списка (опционально).
|
||||
- Агрегация IP-адресов в подсети:
|
||||
- `/16` (255.255.0.0)
|
||||
- `/24` (255.255.255.0)
|
||||
- Mix режим (`/24` + `/32`)
|
||||
- Поддержка различных форматов маршрутизации:
|
||||
- Windows (`route add`)
|
||||
- Unix (`ip route`)
|
||||
- Keenetic (`ip route` с интерфейсом)
|
||||
- Mikrotik (`/ip firewall`)
|
||||
- Только IP-адреса
|
||||
- Windows route (`route add`)
|
||||
- Linux route (`ip route`)
|
||||
- Keenetic BAT (`route add` для bat-файлов)
|
||||
- Keenetic CLI (`ip route` с интерфейсом)
|
||||
- Mikrotik firewall (`/ip/firewall/address-list`)
|
||||
- WireGuard
|
||||
- OpenVPN
|
||||
- CIDR (с указанием маски)
|
||||
- OpenVPN (`push "route"`)
|
||||
- CIDR нотация
|
||||
- Автоматическое разделение больших файлов на части (для Keenetic BAT формата, max 999 строк).
|
||||
- Удаление исходного файла после разделения на части.
|
||||
- Проверка наличия входного файла с выводом инструкций при его отсутствии.
|
||||
|
||||
### Использование
|
||||
|
||||
#### Linux/macOS
|
||||
|
||||
1. Установите [зависимости](https://github.com/Ground-Zerro/DomainMapper/blob/main/requirements.txt):
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
2. Поместите файл c IP-адресами `ip.txt` в корневую директорию проекта. Файл может содержать любой текст и IP-адреса в любом виде - лишнее будет убрано автоматически.
|
||||
2. Создайте файл `ip.txt` в директории со скриптом и добавьте в него IP-адреса (по одному на строку) или любой текст содержащий IP-адреса.
|
||||
|
||||
Пример содержимого `ip.txt`:
|
||||
```
|
||||
192.168.1.1
|
||||
10.0.0.1
|
||||
Какой-то текст с IP: 172.16.0.1
|
||||
```
|
||||
|
||||
3. Запустите скрипт:
|
||||
|
||||
@@ -99,8 +116,21 @@
|
||||
python convert.py
|
||||
```
|
||||
|
||||
4. Следуйте подсказкам на экране.
|
||||
4. Следуйте интерактивным подсказкам на экране:
|
||||
- Выберите, нужно ли исключить IP-адреса Cloudflare (1 - да, Enter - нет)
|
||||
- Выберите агрегацию подсетей (1 - /16, 2 - /24, 3 - mix, Enter - без агрегации)
|
||||
- Выберите формат сохранения (1-8 или Enter для простого списка IP)
|
||||
- При необходимости укажите шлюз/интерфейс/имя списка
|
||||
|
||||
5. Результат будет сохранен в файл `ip.txt` (или в несколько файлов, если был выбран формат с автоматическим разделением).
|
||||
|
||||
#### Windows
|
||||
|
||||
1. Скачайте файл [convert.bat](https://github.com/Ground-Zerro/DomainMapper/raw/refs/heads/main/utilities/win/convert.bat)
|
||||
|
||||
2. Создайте файл `ip.txt` в той же директории, где находится `convert.bat`, и добавьте в него IP-адреса
|
||||
|
||||
3. Запустите `convert.bat`
|
||||
|
||||
## split
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import asyncio
|
||||
import ipaddress
|
||||
import os
|
||||
import re
|
||||
from collections import defaultdict
|
||||
|
||||
import httpx
|
||||
from colorama import Fore, Style, init
|
||||
|
||||
# Цвета
|
||||
init(autoreset=True)
|
||||
|
||||
|
||||
@@ -28,12 +29,9 @@ def red(text):
|
||||
def magneta(text):
|
||||
return f"{Fore.MAGENTA}{text}{Style.RESET_ALL}"
|
||||
|
||||
|
||||
def blue(text):
|
||||
return f"{Fore.BLUE}{text}{Style.RESET_ALL}"
|
||||
|
||||
|
||||
# IP шлюза для win и unix
|
||||
def gateway_input(gateway):
|
||||
if not gateway:
|
||||
input_gateway = input(f"Укажите {green('IP шлюза')} или {green('имя интерфейса')}: ")
|
||||
@@ -41,8 +39,6 @@ def gateway_input(gateway):
|
||||
else:
|
||||
return gateway
|
||||
|
||||
|
||||
# IP шлюза и имя интерфейса для keenetic
|
||||
def ken_gateway_input(ken_gateway):
|
||||
if not ken_gateway:
|
||||
input_ken_gateway = input(
|
||||
@@ -51,8 +47,6 @@ def ken_gateway_input(ken_gateway):
|
||||
else:
|
||||
return ken_gateway
|
||||
|
||||
|
||||
# Загрузка IP-адресов cloudflare
|
||||
async def get_cloudflare_ips():
|
||||
try:
|
||||
async with httpx.AsyncClient() as client:
|
||||
@@ -74,17 +68,22 @@ async def get_cloudflare_ips():
|
||||
print("Ошибка при получении IP адресов Cloudflare:", e)
|
||||
return set()
|
||||
|
||||
|
||||
# Промт cloudflare фильтр
|
||||
def check_include_cloudflare(cloudflare):
|
||||
if cloudflare in ['yes', 'y', 'no', 'n']:
|
||||
return cloudflare in ['yes', 'y']
|
||||
return input(f"\n{yellow('Исключить IP адреса Cloudflare из итогового списка?')}"
|
||||
f"\n{green('yes')} - исключить"
|
||||
f"\n{green('Enter')} - оставить: ").strip().lower() in ['yes', 'y']
|
||||
|
||||
user_input = input(
|
||||
f"\n{yellow('Исключить IP адреса Cloudflare из итогового списка?')}"
|
||||
f"\n1. исключить"
|
||||
f"\n{green('Enter')} - оставить"
|
||||
f"\nВаш выбор: "
|
||||
).strip()
|
||||
|
||||
if user_input == '1':
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
# комментарий для microtik firewall
|
||||
def mk_list_name_input(mk_list_name):
|
||||
if not mk_list_name:
|
||||
input_mk_list_name = input(f"Введите {green('LIST_NAME')} для Mikrotik firewall: ")
|
||||
@@ -92,80 +91,117 @@ def mk_list_name_input(mk_list_name):
|
||||
else:
|
||||
return mk_list_name
|
||||
|
||||
|
||||
# Уплотняем имена сервисов
|
||||
def comment(selected_service):
|
||||
return ",".join(["".join(word.title() for word in s.split()) for s in selected_service])
|
||||
|
||||
|
||||
# Промт на объединение IP в подсети
|
||||
def subnet_input(subnet):
|
||||
if not subnet:
|
||||
subnet = input(
|
||||
f"\n{yellow('Объединить IP-адреса в подсети?')} "
|
||||
f"\n{green('16')} - сократить до /16 (255.255.0.0)"
|
||||
f"\n{green('24')} - сократить до /24 (255.255.255.0)"
|
||||
f"\n{green('mix')} - сократить до /24 (255.255.255.0) и /32 (255.255.255.255)"
|
||||
f"\n{green('Enter')} - пропустить: "
|
||||
).strip().lower()
|
||||
choice = input(
|
||||
f"\n{yellow('Объединить IP-адреса в подсети?')}"
|
||||
f"\n1. сократить до {green('/16')} (255.255.0.0)"
|
||||
f"\n2. сократить до {green('/24')} (255.255.255.0)"
|
||||
f"\n3. сократить до {green('/24')} + {green('/32')} (255.255.255.0 и 255.255.255.255)"
|
||||
f"\n{green('Enter')} - пропустить"
|
||||
f"\nВаш выбор: "
|
||||
).strip()
|
||||
|
||||
if choice == '1':
|
||||
subnet = '16'
|
||||
elif choice == '2':
|
||||
subnet = '24'
|
||||
elif choice == '3':
|
||||
subnet = 'mix'
|
||||
else:
|
||||
subnet = '32'
|
||||
|
||||
return subnet if subnet in {'16', '24', 'mix'} else '32'
|
||||
|
||||
|
||||
# Агрегация маршрутов
|
||||
def group_ips_in_subnets(filename, subnet):
|
||||
def group_ips_in_subnets_optimized(filename: str, subnet: str):
|
||||
try:
|
||||
with open(filename, 'r', encoding='utf-8') as file:
|
||||
ips = {line.strip() for line in file if line.strip()} # Собираем уникальные IP адреса
|
||||
ips = {line.strip() for line in file if line.strip()}
|
||||
|
||||
subnets = set()
|
||||
|
||||
def process_ips(subnet):
|
||||
if subnet == "16":
|
||||
for ip in ips:
|
||||
try:
|
||||
if subnet == "16":
|
||||
# Преобразуем в /16 (два последних октета заменяются на 0.0)
|
||||
network = ipaddress.IPv4Network(f"{ip}/16", strict=False)
|
||||
subnets.add(f"{network.network_address}")
|
||||
elif subnet == "24":
|
||||
# Преобразуем в /24 (последний октет заменяется на 0)
|
||||
network = ipaddress.IPv4Network(f"{ip}/24", strict=False)
|
||||
subnets.add(f"{network.network_address}")
|
||||
except ValueError as e:
|
||||
print(f"Ошибка в IP адресе: {ip} - {e}")
|
||||
subnets.add(str(network.network_address))
|
||||
except ValueError:
|
||||
continue
|
||||
print(f"{Style.BRIGHT}IP-адреса агрегированы до /16 подсети{Style.RESET_ALL}")
|
||||
|
||||
if subnet in ["24", "16"]:
|
||||
process_ips(subnet)
|
||||
print(f"{Style.BRIGHT}IP-адреса агрегированы до /{subnet} подсети{Style.RESET_ALL}")
|
||||
elif subnet == "24":
|
||||
for ip in ips:
|
||||
try:
|
||||
network = ipaddress.IPv4Network(f"{ip}/24", strict=False)
|
||||
subnets.add(str(network.network_address))
|
||||
except ValueError:
|
||||
continue
|
||||
print(f"{Style.BRIGHT}IP-адреса агрегированы до /24 подсети{Style.RESET_ALL}")
|
||||
|
||||
elif subnet == "mix":
|
||||
octet_groups = {}
|
||||
octet_groups = defaultdict(list)
|
||||
for ip in ips:
|
||||
key = '.'.join(ip.split('.')[:3]) # Группировка по первым трем октетам
|
||||
if key not in octet_groups:
|
||||
octet_groups[key] = []
|
||||
key = '.'.join(ip.split('.')[:3])
|
||||
octet_groups[key].append(ip)
|
||||
|
||||
# IP-адреса с совпадающими первыми тремя октетами
|
||||
network_24 = {key + '.0' for key, group in octet_groups.items() if
|
||||
len(group) > 1} # Базовый IP для /24 подсетей
|
||||
# Удаляем IP с совпадающими первыми тремя октетами из множества
|
||||
ips -= {ip for group in octet_groups.values() if len(group) > 1 for ip in group}
|
||||
# Оставляем только IP без указания маски для /24 и одиночных IP
|
||||
subnets.update(ips) # IP без маски для одиночных IP
|
||||
subnets.update(network_24) # Базовые IP для /24 подсетей
|
||||
for key, group in octet_groups.items():
|
||||
if len(group) > 1:
|
||||
subnets.add(key + '.0')
|
||||
else:
|
||||
subnets.update(group)
|
||||
|
||||
print(f"{Style.BRIGHT}IP-адреса агрегированы до масок /24 и /32{Style.RESET_ALL}")
|
||||
|
||||
with open(filename, 'w', encoding='utf-8') as file:
|
||||
for subnet in sorted(subnets):
|
||||
file.write(subnet + '\n')
|
||||
for subnet_ip in sorted(subnets, key=lambda x: ipaddress.IPv4Address(x.split('/')[0])):
|
||||
file.write(subnet_ip + '\n')
|
||||
|
||||
except Exception as e:
|
||||
print(f"Ошибка при обработке файла: {e}")
|
||||
|
||||
def split_file_by_lines(filename: str, max_lines: int = 999):
|
||||
try:
|
||||
with open(filename, 'r', encoding='utf-8') as file:
|
||||
lines = file.readlines()
|
||||
|
||||
# Выбор формата сохранения результатов
|
||||
def process_file_format(filename, filetype, gateway, selected_service, mk_list_name, subnet, ken_gateway):
|
||||
total_lines = len(lines)
|
||||
|
||||
if total_lines <= max_lines:
|
||||
return False
|
||||
|
||||
base_name = filename.rsplit('.', 1)[0] if '.' in filename else filename
|
||||
extension = '.' + filename.rsplit('.', 1)[1] if '.' in filename else '.txt'
|
||||
|
||||
num_parts = (total_lines + max_lines - 1) // max_lines
|
||||
|
||||
print(f"\n{Style.BRIGHT}Результаты сохранены в файлы:{Style.RESET_ALL}")
|
||||
for part in range(num_parts):
|
||||
start_index = part * max_lines
|
||||
end_index = min((part + 1) * max_lines, total_lines)
|
||||
|
||||
part_filename = f"{base_name}_p{part + 1}{extension}"
|
||||
|
||||
with open(part_filename, 'w', encoding='utf-8') as file:
|
||||
file.writelines(lines[start_index:end_index])
|
||||
|
||||
print(f"{Style.BRIGHT}{part_filename} ({end_index - start_index} строк){Style.RESET_ALL}")
|
||||
|
||||
print(f"{Style.BRIGHT}Разделение завершено. Создано {num_parts} частей{Style.RESET_ALL}")
|
||||
|
||||
try:
|
||||
os.remove(filename)
|
||||
except Exception as e:
|
||||
print(f"{red('Не удалось удалить исходный файл:')} {e}")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"{red('Ошибка при разделении файла:')} {e}")
|
||||
return False
|
||||
def process_file_format(filename, filetype, gateway, selected_service, mk_list_name, mk_comment, subnet, ken_gateway):
|
||||
def read_file(filename):
|
||||
try:
|
||||
with open(filename, 'r', encoding='utf-8') as file:
|
||||
@@ -182,73 +218,83 @@ def process_file_format(filename, filetype, gateway, selected_service, mk_list_n
|
||||
else:
|
||||
file.write('\n'.join(formatted_ips))
|
||||
|
||||
# Определение маски подсети
|
||||
net_mask = subnet if subnet == "mix" else "255.255.0.0" if subnet == "16" else "255.255.255.0" if subnet == "24" else "255.255.255.255"
|
||||
|
||||
if not filetype:
|
||||
filetype = input(f"""
|
||||
user_input = input(f"""
|
||||
{yellow('В каком формате сохранить файл?')}
|
||||
{green('win')} - route add {cyan('IP')} mask {net_mask} {cyan('GATEWAY')}
|
||||
{green('unix')} - ip route {cyan('IP')}/{subnet} {cyan('GATEWAY')}
|
||||
{green('keenetic')} - ip route {cyan('IP')}/{subnet} {cyan('GATEWAY GATEWAY_NAME')} auto !{comment(selected_service)}
|
||||
{green('cidr')} - {cyan('IP')}/{subnet}
|
||||
{green('mikrotik')} - /ip/firewall/address-list add list={cyan("LIST_NAME")} comment="{comment(selected_service)}" address={cyan("IP")}/{subnet}
|
||||
{green('ovpn')} - push "route {cyan('IP')} {net_mask}"
|
||||
{green('wireguard')} - {cyan('IP')}/{subnet}, {cyan('IP')}/{subnet}, и т.д...
|
||||
1. {green('win')} - route add {cyan('IP')} mask {net_mask} {cyan('GATEWAY')}
|
||||
2. {green('unix')} - ip route {cyan('IP')}/{subnet} {cyan('GATEWAY')}
|
||||
3. {green('keenetic bat')} - route add {cyan('IP')} mask {net_mask} 0.0.0.0
|
||||
4. {green('keenetic cli')} - ip route {cyan('IP')}/{subnet} {cyan('GATEWAY GATEWAY_NAME')} auto !{comment(selected_service)}
|
||||
5. {green('cidr')} - {cyan('IP')}/{subnet}
|
||||
6. {green('mikrotik')} - /ip/firewall/address-list add list={cyan("LIST_NAME")}{f' comment="{comment(selected_service)}"' if mk_comment != "off" else ""} address={cyan("IP")}/{subnet}
|
||||
7. {green('ovpn')} - push "route {cyan('IP')} {net_mask}"
|
||||
8. {green('wireguard')} - {cyan('IP')}/{subnet}, {cyan('IP')}/{subnet}, и т.д...
|
||||
{green('Enter')} - {cyan('IP')}
|
||||
Ваш выбор: """)
|
||||
Ваш выбор: """).strip()
|
||||
|
||||
mapping = {
|
||||
'1': 'win',
|
||||
'2': 'unix',
|
||||
'3': 'keenetic bat',
|
||||
'4': 'keenetic cli',
|
||||
'5': 'cidr',
|
||||
'6': 'mikrotik',
|
||||
'7': 'ovpn',
|
||||
'8': 'wireguard'
|
||||
}
|
||||
filetype = mapping.get(user_input, '')
|
||||
|
||||
ips = read_file(filename)
|
||||
if not ips:
|
||||
return
|
||||
|
||||
# Дополнительные запросы в зависимости от формата файла
|
||||
if filetype in ['win', 'unix']: # Запрашиваем IP шлюза для win и unix
|
||||
if filetype in ['win', 'unix']:
|
||||
gateway = gateway_input(gateway)
|
||||
elif filetype == 'keenetic': # Запрашиваем IP шлюза и имя интерфейса для keenetic
|
||||
elif filetype == 'keenetic cli':
|
||||
ken_gateway = ken_gateway_input(ken_gateway)
|
||||
elif filetype == 'mikrotik': # Запрашиваем ввод комментария для microtik firewall
|
||||
elif filetype == 'mikrotik':
|
||||
mk_list_name = mk_list_name_input(mk_list_name)
|
||||
|
||||
# обычный формат
|
||||
formatters = {
|
||||
'win': lambda ip: f"route add {ip} mask {net_mask} {gateway}",
|
||||
'unix': lambda ip: f"ip route {ip}/{subnet} {gateway}",
|
||||
'keenetic': lambda ip: f"ip route {ip}/{subnet} {ken_gateway} auto !{comment(selected_service)}",
|
||||
'keenetic bat': lambda ip: f"route add {ip} mask {net_mask} 0.0.0.0",
|
||||
'keenetic cli': lambda ip: f"ip route {ip}/{subnet} {ken_gateway} auto !{comment(selected_service)}",
|
||||
'cidr': lambda ip: f"{ip}/{subnet}",
|
||||
'ovpn': lambda ip: f'push "route {ip} {net_mask}"',
|
||||
'mikrotik': lambda
|
||||
ip: f'/ip/firewall/address-list add list={mk_list_name} comment="{comment(selected_service)}" address={ip}/{subnet}',
|
||||
'mikrotik': lambda ip: f'/ip/firewall/address-list add list={mk_list_name}' + (f' comment="{comment(selected_service)}"' if mk_comment != "off" else "") + f' address={ip}/{subnet}',
|
||||
'wireguard': lambda ip: f"{ip}/{subnet}"
|
||||
}
|
||||
|
||||
# mix формат
|
||||
if subnet == "mix":
|
||||
if filetype.lower() == 'win': # Обработка для win
|
||||
mix_formatter = lambda ip: f"{ip.strip()} mask 255.255.255.0" if ip.endswith(
|
||||
'.0') else f"{ip.strip()} mask 255.255.255.255"
|
||||
elif filetype.lower() == 'ovpn': # Обработка для ovpn
|
||||
mix_formatter = lambda ip: f"{ip.strip()} 255.255.255.0" if ip.endswith(
|
||||
'.0') else f"{ip.strip()} 255.255.255.255"
|
||||
else: # Обработка для остальных форматов
|
||||
if filetype in ['win', 'keenetic bat']:
|
||||
mix_formatter = lambda ip: f"{ip.strip()} mask 255.255.255.0" if ip.endswith('.0') else f"{ip.strip()} mask 255.255.255.255"
|
||||
elif filetype.lower() == 'ovpn':
|
||||
mix_formatter = lambda ip: f"{ip.strip()} 255.255.255.0" if ip.endswith('.0') else f"{ip.strip()} 255.255.255.255"
|
||||
else:
|
||||
mix_formatter = lambda ip: f"{ip.strip()}/24" if ip.endswith('.0') else f"{ip.strip()}/32"
|
||||
|
||||
formatters.update({
|
||||
'win': lambda ip: f"route add {mix_formatter(ip)} {gateway}",
|
||||
'unix': lambda ip: f"ip route {mix_formatter(ip)} {gateway}",
|
||||
'keenetic': lambda ip: f"ip route {mix_formatter(ip)} {ken_gateway} auto !{comment(selected_service)}",
|
||||
'keenetic bat': lambda ip: f"route add {mix_formatter(ip)} 0.0.0.0",
|
||||
'keenetic cli': lambda ip: f"ip route {mix_formatter(ip)} {ken_gateway} auto !{comment(selected_service)}",
|
||||
'cidr': lambda ip: f"{mix_formatter(ip)}",
|
||||
'ovpn': lambda ip: f'push "route {mix_formatter(ip)}"',
|
||||
'mikrotik': lambda ip: f'/ip/firewall/address-list add list={mk_list_name} comment="{comment(selected_service)}" address={mix_formatter(ip)}',
|
||||
'mikrotik': lambda ip: f'/ip/firewall/address-list add list={mk_list_name}' + (f' comment="{comment(selected_service)}"' if mk_comment != "off" else "") + f' address={mix_formatter(ip)}',
|
||||
'wireguard': lambda ip: f"{mix_formatter(ip)}"
|
||||
})
|
||||
|
||||
# Запись в файл
|
||||
if filetype.lower() in formatters:
|
||||
write_file(filename, ips, formatters[filetype.lower()])
|
||||
|
||||
if filetype.lower() == 'keenetic bat':
|
||||
return split_file_by_lines(filename, max_lines=999)
|
||||
|
||||
return False
|
||||
|
||||
# Стартуем
|
||||
async def main():
|
||||
filename = "ip.txt"
|
||||
cloudflare = None
|
||||
@@ -257,42 +303,45 @@ async def main():
|
||||
gateway = None
|
||||
selected_services = ["Service"]
|
||||
mk_list_name = None
|
||||
mk_comment = 'off'
|
||||
ken_gateway = None
|
||||
|
||||
if not os.path.exists(filename):
|
||||
print(f"\n{red(f'Ошибка: файл {filename} не найден!')}")
|
||||
print(f"{yellow('Инструкция:')}")
|
||||
print(f"1. Создайте файл {green(filename)} в текущей директории")
|
||||
print(f"2. Добавьте в него IP-адреса (по одному на строку) или текст содержащий IP-адреса")
|
||||
print(f"3. Запустите скрипт снова")
|
||||
return
|
||||
|
||||
ip_pattern = re.compile(r'\b(?:\d{1,3}\.){3}\d{1,3}\b')
|
||||
|
||||
# Открываем файл и читаем строки
|
||||
with open(filename, 'r') as file:
|
||||
# Создаем множество для хранения уникальных IP-адресов
|
||||
ips = set()
|
||||
|
||||
# Проходим по каждой строке файла
|
||||
for line in file:
|
||||
# Ищем все IP-адреса в строке
|
||||
found_ips = ip_pattern.findall(line)
|
||||
# Добавляем найденные IP-адреса в множество
|
||||
ips.update(found_ips)
|
||||
|
||||
# Фильтр Cloudflare
|
||||
include_cloudflare = check_include_cloudflare(cloudflare)
|
||||
if include_cloudflare: # Загрузка IP-адресов Cloudflare
|
||||
if include_cloudflare:
|
||||
cloudflare_ips = await get_cloudflare_ips()
|
||||
else:
|
||||
cloudflare_ips = set()
|
||||
|
||||
# Удаляем IP-адреса Cloudflare
|
||||
ips -= cloudflare_ips
|
||||
|
||||
with open(filename, 'w', encoding='utf-8') as file:
|
||||
for ip in sorted(ips):
|
||||
file.write(ip + '\n')
|
||||
|
||||
# Группировка IP-адресов в подсети
|
||||
subnet = subnet_input(subnet)
|
||||
if subnet != '32': # Если не '32', вызываем функцию для агрегации
|
||||
group_ips_in_subnets(filename, subnet)
|
||||
if subnet != '32':
|
||||
group_ips_in_subnets_optimized(filename, subnet)
|
||||
|
||||
process_file_format(filename, filetype, gateway, selected_services, mk_list_name, subnet, ken_gateway)
|
||||
file_was_split = process_file_format(filename, filetype, gateway, selected_services, mk_list_name, mk_comment, subnet, ken_gateway)
|
||||
|
||||
if not file_was_split:
|
||||
print(f"\n{Style.BRIGHT}Результаты сохранены в файл:{Style.RESET_ALL} {filename}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -6,15 +6,8 @@
|
||||
## convert
|
||||
|
||||
**Использование:**
|
||||
- Открыть командную строку Windows и выполнить команду:
|
||||
```
|
||||
powershell -Command "irm https://raw.githubusercontent.com/Ground-Zerro/DomainMapper/refs/heads/main/utilities/win/convert.bat -OutFile $env:TEMP\convert.bat" && cmd /c "%TEMP%\convert.bat"
|
||||
```
|
||||
или
|
||||
- Запустить PowerShell и выполнить команду:
|
||||
```
|
||||
irm https://raw.githubusercontent.com/Ground-Zerro/DomainMapper/refs/heads/main/utilities/win/convert.bat -OutFile "$env:TEMP\convert.bat"; cmd /c "$env:TEMP\convert.bat"
|
||||
```
|
||||
1. Скачайте файл [convert.bat](https://github.com/Ground-Zerro/DomainMapper/raw/refs/heads/main/utilities/win/convert.bat)
|
||||
|
||||
**Можете:**
|
||||
- Скачать convert.bat и запустить его.
|
||||
2. Создайте файл `ip.txt` в той же директории, где находится `convert.bat`, и добавьте в него IP-адреса
|
||||
|
||||
3. Запустите `convert.bat`
|
||||
@@ -65,35 +65,49 @@ for %%m in (%modules%) do (
|
||||
|
||||
goto :DownloadMain
|
||||
|
||||
REM Загрузка и запуск main.py
|
||||
REM Загрузка и запуск convert.py
|
||||
:DownloadMain
|
||||
echo Загрузка Domain Mapper Converter...
|
||||
powershell -Command "if ($PSVersionTable.PSVersion.Major -ge 3) {Invoke-WebRequest -Uri 'https://github.com/Ground-Zerro/DomainMapper/raw/refs/heads/main/utilities/convert.py' -OutFile 'main.py'} else {Start-BitsTransfer -Source 'https://raw.githubusercontent.com/Ground-Zerro/DomainMapper/main/main.py' -Destination 'main.py'}"
|
||||
powershell -Command "if ($PSVersionTable.PSVersion.Major -ge 3) {Invoke-WebRequest -Uri 'https://github.com/Ground-Zerro/DomainMapper/raw/refs/heads/main/utilities/convert.py' -OutFile 'convert.py'} else {Start-BitsTransfer -Source 'https://raw.githubusercontent.com/Ground-Zerro/DomainMapper/main/utilities/convert.py' -Destination 'convert.py'}"
|
||||
|
||||
if not exist "main.py" (
|
||||
if not exist "convert.py" (
|
||||
echo Ошибка загрузки Domain Mapper Converter.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
@echo. >> ip.txt
|
||||
if not exist "ip.txt" (
|
||||
echo.
|
||||
echo Файл ip.txt не найден.
|
||||
echo Создайте файл ip.txt в текущей директории и добавьте в него IP-адреса.
|
||||
echo.
|
||||
choice /C YN /M "Создать пустой файл ip.txt сейчас?"
|
||||
if ERRORLEVEL 2 (
|
||||
echo Завершение работы.
|
||||
del /q /f convert.py
|
||||
pause
|
||||
exit /b 1
|
||||
) else (
|
||||
echo. > ip.txt
|
||||
echo Файл ip.txt создан. Добавьте в него IP-адреса и запустите скрипт снова.
|
||||
del /q /f convert.py
|
||||
pause
|
||||
exit /b 0
|
||||
)
|
||||
)
|
||||
|
||||
cls
|
||||
REM Запуск main.py
|
||||
echo Запускаем...
|
||||
python main.py
|
||||
python convert.py
|
||||
if ERRORLEVEL 1 (
|
||||
echo Ошибка выполнения main.py.
|
||||
echo Ошибка выполнения convert.py.
|
||||
pause
|
||||
del /q /f main.py
|
||||
del /q /f convert.py
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
move /y ip.txt %UserProfile%\Desktop\ip.txt
|
||||
echo Программа завершена.
|
||||
del /q /f main.py
|
||||
del /q /f convert.py
|
||||
endlocal
|
||||
echo файл скопирован в %UserProfile%\Desktop\ip.txt
|
||||
pause
|
||||
exit /b 0
|
||||
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
# Попытка перенести DomainMapper на WEB платформу для размещения желающими на собсвтенном хостинге.
|
||||
|
||||
Не уверен, что закончу начатое.
|
||||
|
||||
Предложения в виде **pull requests** приветствуются.
|
||||
|
||||
|
||||
```
|
||||
bash <(curl -s https://raw.githubusercontent.com/Ground-Zerro/DomainMapper/refs/heads/main/web/web_install.sh)
|
||||
|
||||
```
|
||||
44
web/app.py
44
web/app.py
@@ -1,44 +0,0 @@
|
||||
import os
|
||||
import subprocess
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from pydantic import BaseModel
|
||||
import uvicorn
|
||||
|
||||
# Определение модели для данных запроса
|
||||
class RunScriptRequest(BaseModel):
|
||||
config: str
|
||||
userId: str
|
||||
|
||||
# Инициализация FastAPI приложения
|
||||
app = FastAPI()
|
||||
|
||||
@app.post("/run")
|
||||
async def run_script(request: RunScriptRequest):
|
||||
config_content = request.config
|
||||
user_id = request.userId
|
||||
|
||||
# Создание имени файла конфигурации
|
||||
config_filename = f"config-id_{user_id}.ini"
|
||||
try:
|
||||
# Запись конфигурации в файл
|
||||
with open(config_filename, 'w') as f:
|
||||
f.write(config_content)
|
||||
|
||||
# Выполнение команды через subprocess
|
||||
result = subprocess.run(
|
||||
['python3', 'main.py', '-c', config_filename],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True
|
||||
)
|
||||
|
||||
# Возвращение результатов выполнения скрипта
|
||||
return {"stdout": result.stdout, "stderr": result.stderr}
|
||||
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Ошибка: {str(e)}")
|
||||
|
||||
# Запуск приложения (для использования с Uvicorn)
|
||||
if __name__ == "__main__":
|
||||
# Запуск FastAPI с использованием Uvicorn
|
||||
uvicorn.run(app, host="0.0.0.0", port=5000)
|
||||
233
web/index.html
233
web/index.html
@@ -1,233 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>DNS Resolver Settings</title>
|
||||
<script>
|
||||
function validateForm(event) {
|
||||
const services = document.querySelectorAll('input[name="services"]:checked');
|
||||
const dnsServers = document.querySelectorAll('input[name="dns_servers"]:checked');
|
||||
const format = document.querySelector('input[name="format"]:checked');
|
||||
|
||||
if (services.length === 0) {
|
||||
alert("Выберите хотя бы один сервис.");
|
||||
event.preventDefault();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dnsServers.length === 0) {
|
||||
alert("Выберите хотя бы один DNS сервер.");
|
||||
event.preventDefault();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!format) {
|
||||
alert("Выберите формат сохранения.");
|
||||
event.preventDefault();
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((format.value === 'unix' || format.value === 'win' || format.value === 'keenetic') &&
|
||||
!document.getElementById('gateway').value) {
|
||||
alert("Укажите IP шлюза.");
|
||||
event.preventDefault();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function generateConfig(event) {
|
||||
event.preventDefault();
|
||||
|
||||
if (!validateForm(event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const services = Array.from(document.querySelectorAll('input[name="services"]:checked')).map(el => el.value).join(',');
|
||||
const dnsServers = Array.from(document.querySelectorAll('input[name="dns_servers"]:checked')).map(el => el.value).join(' ');
|
||||
const format = document.querySelector('input[name="format"]:checked').value;
|
||||
const gateway = document.getElementById('gateway')?.value || '';
|
||||
const commentary = document.getElementById('commentary')?.value || '';
|
||||
const cloudflare = document.querySelector('input[name="cloudflare"]:checked') ? 'yes' : 'no';
|
||||
const aggregation = document.querySelector('input[name="aggregation"]:checked').value;
|
||||
|
||||
const userId = Math.floor(Math.random() * 100000); // Случайный ID пользователя
|
||||
const filename = `out-id_${userId}.txt`;
|
||||
|
||||
const config = `
|
||||
[DomainMapper]
|
||||
localplatform = no
|
||||
localdns = no
|
||||
service = ${services}
|
||||
dnsserver = ${dnsServers}
|
||||
cloudflare = ${cloudflare}
|
||||
subnet = ${aggregation}
|
||||
filename = ${filename}
|
||||
filetype = ${format}
|
||||
gateway = ${gateway}
|
||||
keenetic = ${gateway}
|
||||
listname = ${commentary}
|
||||
mk_comment = off
|
||||
cfginfo = no
|
||||
run =
|
||||
`;
|
||||
|
||||
sendConfigToServer(config, userId);
|
||||
}
|
||||
|
||||
function sendConfigToServer(config, userId) {
|
||||
fetch('/run', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ config: config, userId: userId })
|
||||
})
|
||||
.then(response => response.text())
|
||||
.then(data => {
|
||||
document.getElementById('progress').innerText = `Конфигурация создана и скрипт запущен. Результат: ${data}`;
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Ошибка при отправке конфигурации:', error);
|
||||
document.getElementById('progress').innerText = 'Ошибка при выполнении.';
|
||||
});
|
||||
}
|
||||
|
||||
function loadServices() {
|
||||
const serviceUrl = 'https://raw.githubusercontent.com/Ground-Zerro/DomainMapper/refs/heads/main/platformdb';
|
||||
|
||||
fetch(serviceUrl)
|
||||
.then(response => response.text())
|
||||
.then(data => {
|
||||
const lines = data.split('\n');
|
||||
const servicesContainer = document.getElementById('servicesContainer');
|
||||
|
||||
lines.forEach(line => {
|
||||
const parts = line.split(':');
|
||||
if (parts.length > 1) {
|
||||
const serviceName = parts[0].trim();
|
||||
const label = document.createElement('label');
|
||||
const input = document.createElement('input');
|
||||
input.type = 'checkbox';
|
||||
input.name = 'services';
|
||||
input.value = serviceName;
|
||||
label.appendChild(input);
|
||||
label.appendChild(document.createTextNode(` ${serviceName}`));
|
||||
servicesContainer.appendChild(label);
|
||||
servicesContainer.appendChild(document.createElement('br'));
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(error => console.error('Error loading services:', error));
|
||||
}
|
||||
|
||||
function loadDNSServers() {
|
||||
const dnsUrl = 'https://raw.githubusercontent.com/Ground-Zerro/DomainMapper/refs/heads/main/dnsdb';
|
||||
|
||||
fetch(dnsUrl)
|
||||
.then(response => response.text())
|
||||
.then(data => {
|
||||
const lines = data.split('\n');
|
||||
const dnsServersContainer = document.getElementById('dnsServersContainer');
|
||||
|
||||
lines.forEach(line => {
|
||||
const parts = line.split(':');
|
||||
if (parts.length > 1) {
|
||||
const dnsServerName = parts[0].trim();
|
||||
const label = document.createElement('label');
|
||||
const input = document.createElement('input');
|
||||
input.type = 'checkbox';
|
||||
input.name = 'dns_servers';
|
||||
input.value = dnsServerName;
|
||||
label.appendChild(input);
|
||||
label.appendChild(document.createTextNode(` ${dnsServerName}`));
|
||||
dnsServersContainer.appendChild(label);
|
||||
dnsServersContainer.appendChild(document.createElement('br'));
|
||||
}
|
||||
});
|
||||
})
|
||||
.catch(error => console.error('Error loading DNS servers:', error));
|
||||
}
|
||||
|
||||
function toggleGatewayField() {
|
||||
const format = document.querySelector('input[name="format"]:checked');
|
||||
const gatewayField = document.getElementById('gatewayField');
|
||||
if (format && (format.value === 'unix' || format.value === 'win' || format.value === 'keenetic')) {
|
||||
gatewayField.style.display = 'block';
|
||||
} else {
|
||||
gatewayField.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function toggleCommentField() {
|
||||
const format = document.querySelector('input[name="format"]:checked');
|
||||
const commentField = document.getElementById('commentaryField');
|
||||
if (format && (format.value === 'mikrotik' || format.value === 'keenetic')) {
|
||||
commentField.style.display = 'block';
|
||||
} else {
|
||||
commentField.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
window.onload = function() {
|
||||
loadServices();
|
||||
loadDNSServers();
|
||||
|
||||
toggleGatewayField();
|
||||
toggleCommentField();
|
||||
};
|
||||
|
||||
document.addEventListener('change', function(event) {
|
||||
if (event.target.name === 'format') {
|
||||
toggleGatewayField();
|
||||
toggleCommentField();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<h2>Настройки</h2>
|
||||
<form id="dnsForm" onsubmit="generateConfig(event)">
|
||||
<label><strong>Список сервисов:</strong></label><br>
|
||||
<div id="servicesContainer"></div><br>
|
||||
|
||||
<label><strong>Список используемых DNS серверов:</strong></label><br>
|
||||
<div id="dnsServersContainer"></div><br>
|
||||
|
||||
<label><strong>Фильтрация Cloudflare:</strong></label><br>
|
||||
<input type="checkbox" name="cloudflare" value="yes"> Исключить Cloudflare<br><br>
|
||||
|
||||
<label><strong>Агрегация подсетей:</strong></label><br>
|
||||
<input type="radio" name="aggregation" value="16"> До /16 (255.255.0.0)<br>
|
||||
<input type="radio" name="aggregation" value="24"> До /24 (255.255.255.0)<br>
|
||||
<input type="radio" name="aggregation" value="mix"> Микс /24 и /32<br>
|
||||
<input type="radio" name="aggregation" value="none" checked> Не агрегировать<br><br>
|
||||
|
||||
<label><strong>Формат сохранения:</strong></label><br>
|
||||
<input type="radio" name="format" value="ip"> IP (только IP адреса)<br>
|
||||
<input type="radio" name="format" value="cidr"> CIDR (%IP%/32)<br>
|
||||
<input type="radio" name="format" value="win"> Windows Route (route add %IP% mask 255.255.255.255 %gateway%)<br>
|
||||
<input type="radio" name="format" value="unix"> Unix Route (ip route %IP%/32 %gateway%)<br>
|
||||
<input type="radio" name="format" value="wg"> Wireguard/AmneziaWG (%IP%/32, %IP%/32, и т.д...)<br>
|
||||
<input type="radio" name="format" value="openvpn"> Open VPN (push "route %IP% 255.255.255.255")<br>
|
||||
<input type="radio" name="format" value="keenetic"> Keenetic CLI (ip route %IP%/32 %gateway% auto !%commentary%)<br>
|
||||
<input type="radio" name="format" value="mikrotik"> Mikrotik firewall (/ip/firewall/address-list add list=%commentary% address=%IP%/32)<br><br>
|
||||
|
||||
<div id="gatewayField" style="display:none;">
|
||||
<label><strong>Укажите IP шлюза или имя интерфейса:</strong></label><br>
|
||||
<input type="text" id="gateway" name="gateway" placeholder="IP или имя шлюза"><br><br>
|
||||
</div>
|
||||
|
||||
<div id="commentaryField" style="display:none;">
|
||||
<label><strong>Укажите комментарий для списка:</strong></label><br>
|
||||
<input type="text" id="commentary" name="commentary" placeholder="Комментарий для списка"><br><br>
|
||||
</div>
|
||||
|
||||
<button type="submit">Создать конфигурацию</button>
|
||||
</form>
|
||||
|
||||
<div id="progress"></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,144 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e # Завершение скрипта при ошибке
|
||||
set -u # Завершение при использовании необъявленных переменных
|
||||
|
||||
# Переменные
|
||||
USERNAME="test123"
|
||||
APP_DIR="/home/$USERNAME/dns_resolver_app"
|
||||
SERVICE_FILE="/etc/systemd/system/dns_resolver.service"
|
||||
NGINX_CONF="/etc/nginx/sites-available/dns_resolver"
|
||||
EMAIL_ADR="email@example.com"
|
||||
DOMAIN_NAME="your-domain.com"
|
||||
|
||||
# Проверка существования пользователя
|
||||
if ! id "$USERNAME" &>/dev/null; then
|
||||
echo "Пользователь $USERNAME не существует."
|
||||
read -p "Хотите создать пользователя? (y/n): " CREATE_USER
|
||||
if [[ "$CREATE_USER" =~ ^[Yy]$ ]]; then
|
||||
sudo useradd -m -s /bin/bash "$USERNAME"
|
||||
echo "Пользователь $USERNAME успешно создан."
|
||||
else
|
||||
echo "Скрипт завершён, так как пользователь не существует."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Убедиться, что пользователь $USERNAME и www-data имеют общую группу
|
||||
sudo usermod -aG www-data "$USERNAME"
|
||||
|
||||
# Обновление системы и установка зависимостей
|
||||
echo "Обновляем систему и устанавливаем зависимости..."
|
||||
sudo apt update && sudo apt upgrade -y
|
||||
sudo apt install python3 python3-pip python3-venv gunicorn nginx certbot python3-certbot-nginx -y
|
||||
|
||||
# Создание директории приложения
|
||||
if [[ ! -d "$APP_DIR" ]]; then
|
||||
echo "Создаем директорию приложения..."
|
||||
sudo mkdir -p "$APP_DIR"
|
||||
sudo chown -R "$USERNAME:www-data" "$APP_DIR"
|
||||
sudo chmod -R 750 "$APP_DIR"
|
||||
else
|
||||
echo "Директория приложения уже существует. Пропускаем."
|
||||
fi
|
||||
|
||||
# Создание виртуального окружения от имени www-data
|
||||
if [[ ! -d "$APP_DIR/venv" ]]; then
|
||||
echo "Создаем виртуальное окружение..."
|
||||
sudo -u www-data python3 -m venv "$APP_DIR/venv"
|
||||
sudo chown -R "$USERNAME:www-data" "$APP_DIR/venv"
|
||||
sudo chmod -R 750 "$APP_DIR/venv"
|
||||
else
|
||||
echo "Виртуальное окружение уже существует. Пропускаем."
|
||||
fi
|
||||
|
||||
# Загрузка файла requirements.txt
|
||||
REQUIREMENTS_URL="https://raw.githubusercontent.com/Ground-Zerro/DomainMapper/refs/heads/main/requirements.txt"
|
||||
if curl --head --fail "$REQUIREMENTS_URL" &>/dev/null; then
|
||||
curl -o "$APP_DIR/requirements.txt" "$REQUIREMENTS_URL"
|
||||
echo "Файл requirements.txt успешно загружен."
|
||||
else
|
||||
echo "Ошибка: Файл requirements.txt недоступен."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Установка зависимостей Python от имени www-data
|
||||
echo "Устанавливаем зависимости Python..."
|
||||
sudo -u www-data bash -c "source $APP_DIR/venv/bin/activate && pip install -r $APP_DIR/requirements.txt fastapi uvicorn pydantic gunicorn"
|
||||
|
||||
# Загрузка файлов приложения
|
||||
FILES=("index.html" "app.py" "main.py")
|
||||
for FILE in "${FILES[@]}"; do
|
||||
URL="https://raw.githubusercontent.com/Ground-Zerro/DomainMapper/refs/heads/main/web/$FILE"
|
||||
if curl --head --fail "$URL" &>/dev/null; then
|
||||
curl -o "$APP_DIR/$FILE" "$URL"
|
||||
echo "Файл $FILE успешно загружен."
|
||||
sudo chown "$USERNAME:www-data" "$APP_DIR/$FILE"
|
||||
sudo chmod 640 "$APP_DIR/$FILE"
|
||||
else
|
||||
echo "Ошибка: Файл $FILE недоступен."
|
||||
fi
|
||||
done
|
||||
|
||||
# Проверка прав доступа
|
||||
sudo chown -R "$USERNAME:www-data" "$APP_DIR"
|
||||
sudo chmod -R 750 "$APP_DIR"
|
||||
|
||||
# Создание системного сервиса
|
||||
echo "Создаем системный сервис..."
|
||||
sudo bash -c "cat <<EOF > $SERVICE_FILE
|
||||
[Unit]
|
||||
Description=DNS Resolver Web App
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
User=www-data
|
||||
Group=www-data
|
||||
WorkingDirectory=$APP_DIR
|
||||
ExecStart=$APP_DIR/venv/bin/gunicorn -w 4 -k uvicorn.workers.UvicornWorker --bind 127.0.0.1:5000 app:app
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF"
|
||||
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable --now dns_resolver
|
||||
|
||||
# Настройка Nginx
|
||||
if [[ ! -f "$NGINX_CONF" ]]; then
|
||||
echo "Настраиваем Nginx..."
|
||||
sudo bash -c "cat <<EOF > $NGINX_CONF
|
||||
server {
|
||||
listen 80;
|
||||
server_name $DOMAIN_NAME;
|
||||
|
||||
root $APP_DIR;
|
||||
index index.html;
|
||||
|
||||
location / {
|
||||
try_files \$uri /index.html;
|
||||
}
|
||||
|
||||
location /run {
|
||||
proxy_pass http://127.0.0.1:5000;
|
||||
proxy_set_header Host \$host;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto \$scheme;
|
||||
}
|
||||
|
||||
error_page 404 /index.html;
|
||||
}
|
||||
EOF"
|
||||
|
||||
sudo ln -sf "$NGINX_CONF" /etc/nginx/sites-enabled/
|
||||
sudo nginx -t && sudo systemctl restart nginx
|
||||
else
|
||||
echo "Конфигурация Nginx уже существует. Пропускаем."
|
||||
fi
|
||||
|
||||
# Настройка HTTPS
|
||||
echo "Настраиваем HTTPS..."
|
||||
sudo certbot --nginx -n --agree-tos --email "$EMAIL_ADR" -d "$DOMAIN_NAME"
|
||||
|
||||
echo "Скрипт выполнен успешно. Приложение доступно по адресу https://$DOMAIN_NAME"
|
||||
Reference in New Issue
Block a user