mirror of
https://github.com/Ground-Zerro/DomainMapper.git
synced 2025-12-10 01:47:18 +07:00
update
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
<details>
|
<details>
|
||||||
<summary>Что нового (нажать, чтобы открыть)</summary>
|
<summary>Что нового (нажать, чтобы открыть)</summary>
|
||||||
|
|
||||||
|
- Реворк работы с DNS серверами. Прогрессбар. Разделение файла на части для некоторых форматов. Обновлена утилита Сonvert.
|
||||||
- Keenetic BAT формат сохранения. Небольшие изменения в интерфейсе. Некоторые доработки/улучшения.
|
- Keenetic BAT формат сохранения. Небольшие изменения в интерфейсе. Некоторые доработки/улучшения.
|
||||||
- Доабвлены некоторые [оналйн кинотеатры](https://github.com/Ground-Zerro/DomainMapper/blob/main/platforms/dns-onlinetheater.txt). Запрос @Andrey_schumacher
|
- Доабвлены некоторые [оналйн кинотеатры](https://github.com/Ground-Zerro/DomainMapper/blob/main/platforms/dns-onlinetheater.txt). Запрос @Andrey_schumacher
|
||||||
- Добавлены списки от [ITDog](https://t.me/itdoginfo/36).
|
- Добавлены списки от [ITDog](https://t.me/itdoginfo/36).
|
||||||
@@ -66,6 +67,7 @@
|
|||||||
- Агрегация маршрутов в /16 (255.255.0.0) и /24 (255.255.255.0) подсети. Комбинированный режим /24 + /32.
|
- Агрегация маршрутов в /16 (255.255.0.0) и /24 (255.255.255.0) подсети. Комбинированный режим /24 + /32.
|
||||||
- Фильтрация IP-адресов Cloudflare (опционально).
|
- Фильтрация IP-адресов Cloudflare (опционально).
|
||||||
- Множество форматов сохранения результата.
|
- Множество форматов сохранения результата.
|
||||||
|
- Разделение больших файлов на части для некоторых форматов.
|
||||||
|
|
||||||
|
|
||||||
**Ключевые особенности**
|
**Ключевые особенности**
|
||||||
@@ -173,6 +175,6 @@ curl -L -s "https://raw.githubusercontent.com/Ground-Zerro/DomainMapper/refs/hea
|
|||||||
|
|
||||||
# ☕ Поддержка
|
# ☕ Поддержка
|
||||||
|
|
||||||
Если проект оказался Вам полезен — можно поддержать автора:
|
Если проект оказался Вам полезен — можно поблагодарить автора:
|
||||||
|
|
||||||
- [Поддержать на Boosty](https://boosty.to/ground_zerro)
|
- [Поддержать на Boosty](https://boosty.to/ground_zerro)
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ exit /b 0
|
|||||||
|
|
||||||
REM Проверка и установка необходимых модулей Python
|
REM Проверка и установка необходимых модулей Python
|
||||||
:CheckModules
|
:CheckModules
|
||||||
set "modules=requests dnspython ipaddress configparser httpx colorama"
|
set "modules=dnspython httpx colorama tqdm"
|
||||||
echo.
|
echo.
|
||||||
echo Проверка необходимых библиотек...
|
echo Проверка необходимых библиотек...
|
||||||
|
|
||||||
@@ -87,10 +87,26 @@ if ERRORLEVEL 1 (
|
|||||||
exit /b 1
|
exit /b 1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
echo Копирование файлов на рабочий стол...
|
||||||
|
|
||||||
|
if exist domain-ip-resolve.txt (
|
||||||
move /y domain-ip-resolve.txt %UserProfile%\Desktop\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 Программа завершена.
|
echo Программа завершена.
|
||||||
del /q /f main.py
|
del /q /f main.py
|
||||||
endlocal
|
endlocal
|
||||||
echo файл скопирован в %UserProfile%\Desktop\domain-ip-resolve.txt
|
|
||||||
pause
|
pause
|
||||||
exit /b 0
|
exit /b 0
|
||||||
|
|||||||
@@ -18,8 +18,9 @@ subnet =
|
|||||||
# Имя выходного файла
|
# Имя выходного файла
|
||||||
filename = domain-ip-resolve.txt
|
filename = domain-ip-resolve.txt
|
||||||
|
|
||||||
# Количество потоков (по умолчанию 20)
|
# Лимит запросов к каждому DNS серверу (запросов в секунду, по умолчанию 50)
|
||||||
threads =
|
# Контролирует максимальное количество DNS запросов к одному серверу в секунду
|
||||||
|
rate_limit = 50
|
||||||
|
|
||||||
# Формат результата (ip, unix, win, mikrotik, ovpn, wireguard, cidr, keenetic bat и т.д.)
|
# Формат результата (ip, unix, win, mikrotik, ovpn, wireguard, cidr, keenetic bat и т.д.)
|
||||||
filetype =
|
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 configparser
|
||||||
import ipaddress
|
import ipaddress
|
||||||
import os
|
import os
|
||||||
from asyncio import Semaphore
|
import time
|
||||||
from collections import defaultdict
|
from collections import defaultdict, deque
|
||||||
from typing import Dict, List, Set, Tuple, Optional
|
from typing import Dict, List, Set, Tuple, Optional
|
||||||
|
|
||||||
import dns.asyncresolver
|
import dns.asyncresolver
|
||||||
import httpx
|
import httpx
|
||||||
from colorama import Fore, Style, init
|
from colorama import Fore, Style, init
|
||||||
|
from tqdm import tqdm
|
||||||
|
|
||||||
init(autoreset=True)
|
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):
|
def yellow(text):
|
||||||
return f"{Fore.YELLOW}{text}{Style.RESET_ALL}"
|
return f"{Fore.YELLOW}{text}{Style.RESET_ALL}"
|
||||||
|
|
||||||
@@ -60,7 +173,7 @@ def read_config(cfg_file):
|
|||||||
config = config['DomainMapper']
|
config = config['DomainMapper']
|
||||||
|
|
||||||
service = config.get('service') or ''
|
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'
|
filename = config.get('filename') or 'domain-ip-resolve.txt'
|
||||||
cloudflare = config.get('cloudflare') or ''
|
cloudflare = config.get('cloudflare') or ''
|
||||||
filetype = config.get('filetype') or ''
|
filetype = config.get('filetype') or ''
|
||||||
@@ -79,7 +192,7 @@ def read_config(cfg_file):
|
|||||||
print(f"{yellow(f'Загружена конфигурация из {cfg_file}:')}")
|
print(f"{yellow(f'Загружена конфигурация из {cfg_file}:')}")
|
||||||
print(f"{Style.BRIGHT}Сервисы для проверки:{Style.RESET_ALL} {service if service else 'спросить у пользователя'}")
|
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} {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-адресов 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}Агрегация 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 'спросить у пользователя'}")
|
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}Локальный список платформ:{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 'выключен'}")
|
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:
|
except Exception as e:
|
||||||
print(f"{yellow(f'Ошибка загрузки {cfg_file}:')} {e}\n{Style.BRIGHT}Используются настройки 'по умолчанию'.{Style.RESET_ALL}")
|
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):
|
def gateway_input(gateway):
|
||||||
if not gateway:
|
if not gateway:
|
||||||
@@ -116,11 +229,107 @@ def ken_gateway_input(ken_gateway):
|
|||||||
else:
|
else:
|
||||||
return ken_gateway
|
return ken_gateway
|
||||||
|
|
||||||
def get_semaphore(request_limit):
|
class DNSServerWorker:
|
||||||
return defaultdict(lambda: Semaphore(request_limit))
|
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):
|
async def add_domain(self, domain: str):
|
||||||
return get_semaphore(request_limit)
|
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]:
|
async def load_urls(url: str) -> Dict[str, str]:
|
||||||
try:
|
try:
|
||||||
@@ -224,67 +433,43 @@ async def load_dns_names(url_or_file: str) -> List[str]:
|
|||||||
print(f"Ошибка при чтении файла {url_or_file}: {e}")
|
print(f"Ошибка при чтении файла {url_or_file}: {e}")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
async def resolve_domain_batch(domains: List[str], resolver: dns.asyncresolver.Resolver,
|
async def resolve_dns_with_workers(service: str, dns_names: List[str],
|
||||||
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],
|
|
||||||
dns_servers: List[Tuple[str, List[str]]],
|
dns_servers: List[Tuple[str, List[str]]],
|
||||||
cloudflare_ips: Set[str], unique_ips_all_services: Set[str],
|
cloudflare_ips: Set[str], unique_ips_all_services: Set[str],
|
||||||
semaphore_dict: Dict, stats: Dict[str, int],
|
stats: Dict[str, int], include_cloudflare: bool,
|
||||||
include_cloudflare: bool, batch_size: int = 50) -> str:
|
rate_limit: int, stats_lock: asyncio.Lock = None) -> str:
|
||||||
try:
|
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)]
|
workers = []
|
||||||
|
|
||||||
tasks = []
|
|
||||||
|
|
||||||
for batch in domain_batches:
|
|
||||||
for server_name, servers in dns_servers:
|
for server_name, servers in dns_servers:
|
||||||
resolver = dns.asyncresolver.Resolver()
|
worker = DNSServerWorker(server_name, servers, rate_limit, stats_lock)
|
||||||
resolver.nameservers = servers
|
workers.append(worker)
|
||||||
|
|
||||||
tasks.append(resolve_domain_batch(
|
for domain in dns_names:
|
||||||
batch, resolver, semaphore_dict[server_name],
|
for worker in workers:
|
||||||
server_name, stats, cloudflare_ips, include_cloudflare
|
await worker.add_domain(domain)
|
||||||
))
|
|
||||||
|
|
||||||
max_concurrent_tasks = min(len(tasks), 100)
|
tasks = [worker.process_queue(stats) for worker in workers]
|
||||||
|
|
||||||
results = []
|
await asyncio.gather(*tasks)
|
||||||
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)
|
|
||||||
|
|
||||||
for result in batch_results:
|
all_nameservers = set()
|
||||||
if not isinstance(result, Exception):
|
for _, servers in dns_servers:
|
||||||
results.extend(result)
|
all_nameservers.update(servers)
|
||||||
|
|
||||||
unique_ips_current_service = set()
|
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:
|
if ip_address not in unique_ips_all_services:
|
||||||
unique_ips_current_service.add(ip_address)
|
unique_ips_current_service.add(ip_address)
|
||||||
unique_ips_all_services.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:
|
except Exception as e:
|
||||||
print(f"Ошибка при обработке файла: {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 process_file_format(filename, filetype, gateway, selected_service, mk_list_name, mk_comment, subnet, ken_gateway):
|
||||||
def read_file(filename):
|
def read_file(filename):
|
||||||
try:
|
try:
|
||||||
@@ -554,6 +779,11 @@ def process_file_format(filename, filetype, gateway, selected_service, mk_list_n
|
|||||||
if filetype.lower() in formatters:
|
if filetype.lower() in formatters:
|
||||||
write_file(filename, ips, formatters[filetype.lower()])
|
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():
|
async def main():
|
||||||
parser = argparse.ArgumentParser(description="DNS resolver script with custom config file.")
|
parser = argparse.ArgumentParser(description="DNS resolver script with custom config file.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
@@ -566,7 +796,7 @@ async def main():
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
config_file = args.config
|
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,
|
dns_server_indices, mk_list_name, subnet, ken_gateway, localplatform,
|
||||||
localdns, mk_comment) = read_config(config_file)
|
localdns, mk_comment) = read_config(config_file)
|
||||||
|
|
||||||
@@ -596,7 +826,6 @@ async def main():
|
|||||||
cloudflare_ips = set()
|
cloudflare_ips = set()
|
||||||
|
|
||||||
unique_ips_all_services = set()
|
unique_ips_all_services = set()
|
||||||
semaphore = init_semaphores(request_limit)
|
|
||||||
|
|
||||||
stats = {
|
stats = {
|
||||||
'null_ips_count': 0,
|
'null_ips_count': 0,
|
||||||
@@ -605,23 +834,57 @@ async def main():
|
|||||||
'domain_errors': 0
|
'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 = []
|
tasks = []
|
||||||
|
|
||||||
for service_name in selected_services:
|
for service_name in selected_services:
|
||||||
if service_name == 'Custom DNS list':
|
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,
|
service_name, local_dns_names, selected_dns_servers,
|
||||||
cloudflare_ips, unique_ips_all_services, semaphore,
|
cloudflare_ips, unique_ips_all_services,
|
||||||
stats, include_cloudflare
|
stats, include_cloudflare, rate_limit,
|
||||||
|
stats_lock
|
||||||
))
|
))
|
||||||
else:
|
else:
|
||||||
url_or_file = urls[service_name]
|
url_or_file = urls[service_name]
|
||||||
dns_names = await load_dns_names(url_or_file)
|
dns_names = await load_dns_names(url_or_file)
|
||||||
if dns_names:
|
if dns_names:
|
||||||
tasks.append(resolve_dns_optimized(
|
tasks.append(resolve_dns_with_workers(
|
||||||
service_name, dns_names, selected_dns_servers,
|
service_name, dns_names, selected_dns_servers,
|
||||||
cloudflare_ips, unique_ips_all_services, semaphore,
|
cloudflare_ips, unique_ips_all_services,
|
||||||
stats, include_cloudflare
|
stats, include_cloudflare, rate_limit,
|
||||||
|
stats_lock
|
||||||
))
|
))
|
||||||
|
|
||||||
if tasks:
|
if tasks:
|
||||||
@@ -635,32 +898,31 @@ async def main():
|
|||||||
with open(filename, 'w', encoding='utf-8') as file:
|
with open(filename, 'w', encoding='utf-8') as file:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
print(f"\n{yellow('Проверка завершена.')}")
|
await periodic_updater.stop()
|
||||||
print(f"{Style.BRIGHT}Всего обработано DNS имен:{Style.RESET_ALL} {stats['total_domains_processed']}")
|
progress_tracker.close()
|
||||||
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]))
|
|
||||||
|
|
||||||
|
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)
|
subnet = subnet_input(subnet)
|
||||||
if subnet != '32':
|
if subnet != '32':
|
||||||
group_ips_in_subnets_optimized(filename, subnet)
|
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:
|
if run_command:
|
||||||
print("\nВыполнение команды после завершения скрипта...")
|
print("\nВыполнение команды после завершения скрипта...")
|
||||||
os.system(run_command)
|
os.system(run_command)
|
||||||
else:
|
else:
|
||||||
|
if not file_was_split:
|
||||||
print(f"\n{Style.BRIGHT}Результаты сохранены в файл:{Style.RESET_ALL}", filename)
|
print(f"\n{Style.BRIGHT}Результаты сохранены в файл:{Style.RESET_ALL}", filename)
|
||||||
if os.name == 'nt':
|
if os.name == 'nt':
|
||||||
input(f"Нажмите {green('Enter')} для выхода...")
|
input(f"Нажмите {green('Enter')} для выхода...")
|
||||||
|
|
||||||
|
print(f"\n{Style.BRIGHT}Если есть желание, можно угостить автора чашечкой какао:{Style.RESET_ALL} {green('https://boosty.to/ground_zerro')}")
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print(f"\n{red('Программа прервана пользователем')}")
|
print(f"\n{red('Программа прервана пользователем')}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
configparser~=7.0.1
|
dnspython>=2.6.1
|
||||||
ipaddress~=1.0.23
|
httpx>=0.27.0
|
||||||
dnspython~=2.6.1
|
colorama>=0.4.6
|
||||||
httpx~=0.27.0
|
tqdm>=4.66.0
|
||||||
colorama~=0.4.6
|
|
||||||
requests~=2.31.0
|
requests>=2.31.0
|
||||||
beautifulsoup4~=4.12.3
|
beautifulsoup4>=4.12.3
|
||||||
|
|||||||
@@ -71,17 +71,25 @@
|
|||||||
|
|
||||||
### Функции
|
### Функции
|
||||||
|
|
||||||
- Загрузка списка IP-адресов из файла.
|
- Извлечение IP-адресов из файла (файл может содержать любой текст - IP автоматически извлекаются).
|
||||||
- Агрегация IP-адресов в подсети с масками `/16`, `/24`, или объединение нескольких подсетей.
|
- Исключение IP-адресов Cloudflare из итогового списка (опционально).
|
||||||
- Исключение IP-адресов Cloudflare из итогового списка (при необходимости).
|
- Агрегация IP-адресов в подсети:
|
||||||
|
- `/16` (255.255.0.0)
|
||||||
|
- `/24` (255.255.255.0)
|
||||||
|
- Mix режим (`/24` + `/32`)
|
||||||
- Поддержка различных форматов маршрутизации:
|
- Поддержка различных форматов маршрутизации:
|
||||||
- Windows (`route add`)
|
- Только IP-адреса
|
||||||
- Unix (`ip route`)
|
- Windows route (`route add`)
|
||||||
- Keenetic (`ip route` с интерфейсом)
|
- Linux route (`ip route`)
|
||||||
- Mikrotik (`/ip firewall`)
|
- Keenetic BAT (`route add` для bat-файлов)
|
||||||
|
- Keenetic CLI (`ip route` с интерфейсом)
|
||||||
|
- Mikrotik firewall (`/ip/firewall/address-list`)
|
||||||
- WireGuard
|
- WireGuard
|
||||||
- OpenVPN
|
- OpenVPN (`push "route"`)
|
||||||
- CIDR (с указанием маски)
|
- CIDR нотация
|
||||||
|
- Автоматическое разделение больших файлов на части (для Keenetic BAT формата, max 999 строк).
|
||||||
|
- Удаление исходного файла после разделения на части.
|
||||||
|
- Проверка наличия входного файла с выводом инструкций при его отсутствии.
|
||||||
|
|
||||||
### Использование
|
### Использование
|
||||||
|
|
||||||
@@ -91,7 +99,14 @@
|
|||||||
pip install -r requirements.txt
|
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. Запустите скрипт:
|
3. Запустите скрипт:
|
||||||
|
|
||||||
@@ -99,7 +114,18 @@
|
|||||||
python convert.py
|
python convert.py
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Следуйте подсказкам на экране.
|
4. Следуйте интерактивным подсказкам на экране:
|
||||||
|
- Выберите, нужно ли исключить IP-адреса Cloudflare (1 - да, Enter - нет)
|
||||||
|
- Выберите агрегацию подсетей (1 - /16, 2 - /24, 3 - mix, Enter - без агрегации)
|
||||||
|
- Выберите формат сохранения (1-8 или Enter для простого списка IP)
|
||||||
|
- При необходимости укажите шлюз/интерфейс/имя списка
|
||||||
|
|
||||||
|
5. Результат будет сохранен в файл `ip.txt` (или в несколько файлов, если был выбран формат с автоматическим разделением).
|
||||||
|
|
||||||
|
### Примечания
|
||||||
|
|
||||||
|
- Если файл `ip.txt` не найден, скрипт выведет подробную инструкцию по его созданию и корректно завершится.
|
||||||
|
- Для формата Keenetic BAT файл автоматически разделяется на части по 999 строк, исходный файл удаляется.
|
||||||
|
|
||||||
|
|
||||||
## split
|
## split
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import ipaddress
|
import ipaddress
|
||||||
|
import os
|
||||||
import re
|
import re
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from colorama import Fore, Style, init
|
from colorama import Fore, Style, init
|
||||||
|
|
||||||
# Цвета
|
|
||||||
init(autoreset=True)
|
init(autoreset=True)
|
||||||
|
|
||||||
|
|
||||||
@@ -28,12 +29,9 @@ def red(text):
|
|||||||
def magneta(text):
|
def magneta(text):
|
||||||
return f"{Fore.MAGENTA}{text}{Style.RESET_ALL}"
|
return f"{Fore.MAGENTA}{text}{Style.RESET_ALL}"
|
||||||
|
|
||||||
|
|
||||||
def blue(text):
|
def blue(text):
|
||||||
return f"{Fore.BLUE}{text}{Style.RESET_ALL}"
|
return f"{Fore.BLUE}{text}{Style.RESET_ALL}"
|
||||||
|
|
||||||
|
|
||||||
# IP шлюза для win и unix
|
|
||||||
def gateway_input(gateway):
|
def gateway_input(gateway):
|
||||||
if not gateway:
|
if not gateway:
|
||||||
input_gateway = input(f"Укажите {green('IP шлюза')} или {green('имя интерфейса')}: ")
|
input_gateway = input(f"Укажите {green('IP шлюза')} или {green('имя интерфейса')}: ")
|
||||||
@@ -41,8 +39,6 @@ def gateway_input(gateway):
|
|||||||
else:
|
else:
|
||||||
return gateway
|
return gateway
|
||||||
|
|
||||||
|
|
||||||
# IP шлюза и имя интерфейса для keenetic
|
|
||||||
def ken_gateway_input(ken_gateway):
|
def ken_gateway_input(ken_gateway):
|
||||||
if not ken_gateway:
|
if not ken_gateway:
|
||||||
input_ken_gateway = input(
|
input_ken_gateway = input(
|
||||||
@@ -51,8 +47,6 @@ def ken_gateway_input(ken_gateway):
|
|||||||
else:
|
else:
|
||||||
return ken_gateway
|
return ken_gateway
|
||||||
|
|
||||||
|
|
||||||
# Загрузка IP-адресов cloudflare
|
|
||||||
async def get_cloudflare_ips():
|
async def get_cloudflare_ips():
|
||||||
try:
|
try:
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
@@ -74,17 +68,22 @@ async def get_cloudflare_ips():
|
|||||||
print("Ошибка при получении IP адресов Cloudflare:", e)
|
print("Ошибка при получении IP адресов Cloudflare:", e)
|
||||||
return set()
|
return set()
|
||||||
|
|
||||||
|
|
||||||
# Промт cloudflare фильтр
|
|
||||||
def check_include_cloudflare(cloudflare):
|
def check_include_cloudflare(cloudflare):
|
||||||
if cloudflare in ['yes', 'y', 'no', 'n']:
|
if cloudflare in ['yes', 'y', 'no', 'n']:
|
||||||
return cloudflare in ['yes', 'y']
|
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):
|
def mk_list_name_input(mk_list_name):
|
||||||
if not mk_list_name:
|
if not mk_list_name:
|
||||||
input_mk_list_name = input(f"Введите {green('LIST_NAME')} для Mikrotik firewall: ")
|
input_mk_list_name = input(f"Введите {green('LIST_NAME')} для Mikrotik firewall: ")
|
||||||
@@ -92,80 +91,117 @@ def mk_list_name_input(mk_list_name):
|
|||||||
else:
|
else:
|
||||||
return mk_list_name
|
return mk_list_name
|
||||||
|
|
||||||
|
|
||||||
# Уплотняем имена сервисов
|
|
||||||
def comment(selected_service):
|
def comment(selected_service):
|
||||||
return ",".join(["".join(word.title() for word in s.split()) for s in selected_service])
|
return ",".join(["".join(word.title() for word in s.split()) for s in selected_service])
|
||||||
|
|
||||||
|
|
||||||
# Промт на объединение IP в подсети
|
|
||||||
def subnet_input(subnet):
|
def subnet_input(subnet):
|
||||||
if not subnet:
|
if not subnet:
|
||||||
subnet = input(
|
choice = input(
|
||||||
f"\n{yellow('Объединить IP-адреса в подсети?')}"
|
f"\n{yellow('Объединить IP-адреса в подсети?')}"
|
||||||
f"\n{green('16')} - сократить до /16 (255.255.0.0)"
|
f"\n1. сократить до {green('/16')} (255.255.0.0)"
|
||||||
f"\n{green('24')} - сократить до /24 (255.255.255.0)"
|
f"\n2. сократить до {green('/24')} (255.255.255.0)"
|
||||||
f"\n{green('mix')} - сократить до /24 (255.255.255.0) и /32 (255.255.255.255)"
|
f"\n3. сократить до {green('/24')} + {green('/32')} (255.255.255.0 и 255.255.255.255)"
|
||||||
f"\n{green('Enter')} - пропустить: "
|
f"\n{green('Enter')} - пропустить"
|
||||||
).strip().lower()
|
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'
|
return subnet if subnet in {'16', '24', 'mix'} else '32'
|
||||||
|
|
||||||
|
def group_ips_in_subnets_optimized(filename: str, subnet: str):
|
||||||
# Агрегация маршрутов
|
|
||||||
def group_ips_in_subnets(filename, subnet):
|
|
||||||
try:
|
try:
|
||||||
with open(filename, 'r', encoding='utf-8') as file:
|
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()
|
subnets = set()
|
||||||
|
|
||||||
def process_ips(subnet):
|
if subnet == "16":
|
||||||
for ip in ips:
|
for ip in ips:
|
||||||
try:
|
try:
|
||||||
if subnet == "16":
|
|
||||||
# Преобразуем в /16 (два последних октета заменяются на 0.0)
|
|
||||||
network = ipaddress.IPv4Network(f"{ip}/16", strict=False)
|
network = ipaddress.IPv4Network(f"{ip}/16", strict=False)
|
||||||
subnets.add(f"{network.network_address}")
|
subnets.add(str(network.network_address))
|
||||||
elif subnet == "24":
|
except ValueError:
|
||||||
# Преобразуем в /24 (последний октет заменяется на 0)
|
continue
|
||||||
network = ipaddress.IPv4Network(f"{ip}/24", strict=False)
|
print(f"{Style.BRIGHT}IP-адреса агрегированы до /16 подсети{Style.RESET_ALL}")
|
||||||
subnets.add(f"{network.network_address}")
|
|
||||||
except ValueError as e:
|
|
||||||
print(f"Ошибка в IP адресе: {ip} - {e}")
|
|
||||||
|
|
||||||
if subnet in ["24", "16"]:
|
elif subnet == "24":
|
||||||
process_ips(subnet)
|
for ip in ips:
|
||||||
print(f"{Style.BRIGHT}IP-адреса агрегированы до /{subnet} подсети{Style.RESET_ALL}")
|
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":
|
elif subnet == "mix":
|
||||||
octet_groups = {}
|
octet_groups = defaultdict(list)
|
||||||
for ip in ips:
|
for ip in ips:
|
||||||
key = '.'.join(ip.split('.')[:3]) # Группировка по первым трем октетам
|
key = '.'.join(ip.split('.')[:3])
|
||||||
if key not in octet_groups:
|
|
||||||
octet_groups[key] = []
|
|
||||||
octet_groups[key].append(ip)
|
octet_groups[key].append(ip)
|
||||||
|
|
||||||
# IP-адреса с совпадающими первыми тремя октетами
|
for key, group in octet_groups.items():
|
||||||
network_24 = {key + '.0' for key, group in octet_groups.items() if
|
if len(group) > 1:
|
||||||
len(group) > 1} # Базовый IP для /24 подсетей
|
subnets.add(key + '.0')
|
||||||
# Удаляем IP с совпадающими первыми тремя октетами из множества
|
else:
|
||||||
ips -= {ip for group in octet_groups.values() if len(group) > 1 for ip in group}
|
subnets.update(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}")
|
print(f"{Style.BRIGHT}IP-адреса агрегированы до масок /24 и /32{Style.RESET_ALL}")
|
||||||
|
|
||||||
with open(filename, 'w', encoding='utf-8') as file:
|
with open(filename, 'w', encoding='utf-8') as file:
|
||||||
for subnet in sorted(subnets):
|
for subnet_ip in sorted(subnets, key=lambda x: ipaddress.IPv4Address(x.split('/')[0])):
|
||||||
file.write(subnet + '\n')
|
file.write(subnet_ip + '\n')
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Ошибка при обработке файла: {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)
|
||||||
def process_file_format(filename, filetype, gateway, selected_service, mk_list_name, subnet, ken_gateway):
|
|
||||||
|
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):
|
def read_file(filename):
|
||||||
try:
|
try:
|
||||||
with open(filename, 'r', encoding='utf-8') as file:
|
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:
|
else:
|
||||||
file.write('\n'.join(formatted_ips))
|
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"
|
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:
|
if not filetype:
|
||||||
filetype = input(f"""
|
user_input = input(f"""
|
||||||
{yellow('В каком формате сохранить файл?')}
|
{yellow('В каком формате сохранить файл?')}
|
||||||
{green('win')} - route add {cyan('IP')} mask {net_mask} {cyan('GATEWAY')}
|
1. {green('win')} - route add {cyan('IP')} mask {net_mask} {cyan('GATEWAY')}
|
||||||
{green('unix')} - ip route {cyan('IP')}/{subnet} {cyan('GATEWAY')}
|
2. {green('unix')} - ip route {cyan('IP')}/{subnet} {cyan('GATEWAY')}
|
||||||
{green('keenetic')} - ip route {cyan('IP')}/{subnet} {cyan('GATEWAY GATEWAY_NAME')} auto !{comment(selected_service)}
|
3. {green('keenetic bat')} - route add {cyan('IP')} mask {net_mask} 0.0.0.0
|
||||||
{green('cidr')} - {cyan('IP')}/{subnet}
|
4. {green('keenetic cli')} - ip route {cyan('IP')}/{subnet} {cyan('GATEWAY GATEWAY_NAME')} auto !{comment(selected_service)}
|
||||||
{green('mikrotik')} - /ip/firewall/address-list add list={cyan("LIST_NAME")} comment="{comment(selected_service)}" address={cyan("IP")}/{subnet}
|
5. {green('cidr')} - {cyan('IP')}/{subnet}
|
||||||
{green('ovpn')} - push "route {cyan('IP')} {net_mask}"
|
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}
|
||||||
{green('wireguard')} - {cyan('IP')}/{subnet}, {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')}
|
{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)
|
ips = read_file(filename)
|
||||||
if not ips:
|
if not ips:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Дополнительные запросы в зависимости от формата файла
|
if filetype in ['win', 'unix']:
|
||||||
if filetype in ['win', 'unix']: # Запрашиваем IP шлюза для win и unix
|
|
||||||
gateway = gateway_input(gateway)
|
gateway = gateway_input(gateway)
|
||||||
elif filetype == 'keenetic': # Запрашиваем IP шлюза и имя интерфейса для keenetic
|
elif filetype == 'keenetic cli':
|
||||||
ken_gateway = ken_gateway_input(ken_gateway)
|
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)
|
mk_list_name = mk_list_name_input(mk_list_name)
|
||||||
|
|
||||||
# обычный формат
|
|
||||||
formatters = {
|
formatters = {
|
||||||
'win': lambda ip: f"route add {ip} mask {net_mask} {gateway}",
|
'win': lambda ip: f"route add {ip} mask {net_mask} {gateway}",
|
||||||
'unix': lambda ip: f"ip route {ip}/{subnet} {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}",
|
'cidr': lambda ip: f"{ip}/{subnet}",
|
||||||
'ovpn': lambda ip: f'push "route {ip} {net_mask}"',
|
'ovpn': lambda ip: f'push "route {ip} {net_mask}"',
|
||||||
'mikrotik': lambda
|
'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}',
|
||||||
ip: f'/ip/firewall/address-list add list={mk_list_name} comment="{comment(selected_service)}" address={ip}/{subnet}',
|
|
||||||
'wireguard': lambda ip: f"{ip}/{subnet}"
|
'wireguard': lambda ip: f"{ip}/{subnet}"
|
||||||
}
|
}
|
||||||
|
|
||||||
# mix формат
|
|
||||||
if subnet == "mix":
|
if subnet == "mix":
|
||||||
if filetype.lower() == 'win': # Обработка для win
|
if filetype in ['win', 'keenetic bat']:
|
||||||
mix_formatter = lambda ip: f"{ip.strip()} mask 255.255.255.0" if ip.endswith(
|
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"
|
||||||
'.0') else f"{ip.strip()} mask 255.255.255.255"
|
elif filetype.lower() == 'ovpn':
|
||||||
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"
|
||||||
mix_formatter = lambda ip: f"{ip.strip()} 255.255.255.0" if ip.endswith(
|
else:
|
||||||
'.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"
|
mix_formatter = lambda ip: f"{ip.strip()}/24" if ip.endswith('.0') else f"{ip.strip()}/32"
|
||||||
|
|
||||||
formatters.update({
|
formatters.update({
|
||||||
'win': lambda ip: f"route add {mix_formatter(ip)} {gateway}",
|
'win': lambda ip: f"route add {mix_formatter(ip)} {gateway}",
|
||||||
'unix': lambda ip: f"ip route {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)}",
|
'cidr': lambda ip: f"{mix_formatter(ip)}",
|
||||||
'ovpn': lambda ip: f'push "route {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)}"
|
'wireguard': lambda ip: f"{mix_formatter(ip)}"
|
||||||
})
|
})
|
||||||
|
|
||||||
# Запись в файл
|
|
||||||
if filetype.lower() in formatters:
|
if filetype.lower() in formatters:
|
||||||
write_file(filename, ips, formatters[filetype.lower()])
|
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():
|
async def main():
|
||||||
filename = "ip.txt"
|
filename = "ip.txt"
|
||||||
cloudflare = None
|
cloudflare = None
|
||||||
@@ -257,42 +303,45 @@ async def main():
|
|||||||
gateway = None
|
gateway = None
|
||||||
selected_services = ["Service"]
|
selected_services = ["Service"]
|
||||||
mk_list_name = None
|
mk_list_name = None
|
||||||
|
mk_comment = 'off'
|
||||||
ken_gateway = None
|
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')
|
ip_pattern = re.compile(r'\b(?:\d{1,3}\.){3}\d{1,3}\b')
|
||||||
|
|
||||||
# Открываем файл и читаем строки
|
|
||||||
with open(filename, 'r') as file:
|
with open(filename, 'r') as file:
|
||||||
# Создаем множество для хранения уникальных IP-адресов
|
|
||||||
ips = set()
|
ips = set()
|
||||||
|
|
||||||
# Проходим по каждой строке файла
|
|
||||||
for line in file:
|
for line in file:
|
||||||
# Ищем все IP-адреса в строке
|
|
||||||
found_ips = ip_pattern.findall(line)
|
found_ips = ip_pattern.findall(line)
|
||||||
# Добавляем найденные IP-адреса в множество
|
|
||||||
ips.update(found_ips)
|
ips.update(found_ips)
|
||||||
|
|
||||||
# Фильтр Cloudflare
|
|
||||||
include_cloudflare = check_include_cloudflare(cloudflare)
|
include_cloudflare = check_include_cloudflare(cloudflare)
|
||||||
if include_cloudflare: # Загрузка IP-адресов Cloudflare
|
if include_cloudflare:
|
||||||
cloudflare_ips = await get_cloudflare_ips()
|
cloudflare_ips = await get_cloudflare_ips()
|
||||||
else:
|
else:
|
||||||
cloudflare_ips = set()
|
cloudflare_ips = set()
|
||||||
|
|
||||||
# Удаляем IP-адреса Cloudflare
|
|
||||||
ips -= cloudflare_ips
|
ips -= cloudflare_ips
|
||||||
|
|
||||||
with open(filename, 'w', encoding='utf-8') as file:
|
with open(filename, 'w', encoding='utf-8') as file:
|
||||||
for ip in sorted(ips):
|
for ip in sorted(ips):
|
||||||
file.write(ip + '\n')
|
file.write(ip + '\n')
|
||||||
|
|
||||||
# Группировка IP-адресов в подсети
|
|
||||||
subnet = subnet_input(subnet)
|
subnet = subnet_input(subnet)
|
||||||
if subnet != '32': # Если не '32', вызываем функцию для агрегации
|
if subnet != '32':
|
||||||
group_ips_in_subnets(filename, subnet)
|
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__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -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