поправил, добавил возможность выбора определённой секции

This commit is contained in:
pikus 2026-03-20 20:54:44 +03:00
parent ac21080bff
commit d8b70cf993
4 changed files with 111 additions and 103 deletions

3
.gitignore vendored
View File

@ -1,2 +1,3 @@
.idea
.venv
.venv
config.ini

View File

@ -29,16 +29,18 @@ cp config.ini.example config.ini
```ini
[redirector]
threads = # Количество потоков для dns-резолва (50)
exclude_cloudflare = # Исключить адреса CloudFlare (yes, no)
filename = # Имя файла в котором списки ip, cidr и доменов (input.txt)
script = # Имя готового скрипта (apply_routing.sh)
rollback_script = # Имя скрипта для отката изменений (rollback_routing.sh)
exclude_cloudflare = # Исключить адреса CloudFlare (yes, no)
threads = # Количество потоков для dns-резолва (50)
table = # Номер таблицы маршрутизации (1010)
priority = # Приоритет таблицы (ниже цифра - выше приоритет)
gateway = # IP шлюза на который направлять адреса (10.10.0.1)
interface = # Интерфейс на который направлять адреса (awg0)
table = # Номер таблицы маршрутизации (1010)
run = # Shell команда которая выполнится после отработки скрипта (echo "lol kek")
```
- Можно добавить несколько секций для нескольких конфигураций
---
@ -60,19 +62,20 @@ example.org
## Запуск
```bash
venv/bin/python redirector.py
```
или
- Обработать все секции в конфиге:
```bash
python redirector.py
```
- Или определённую секцию:
```bash
python redirector.py --env custom
```
---
## Результат
### ON скрипт
### Создать и наполнить таблицу маршрутизацию
```
apply_routing.sh
@ -80,7 +83,7 @@ apply_routing.sh
Добавляет таблицу (если её нет) и маршруты в таблицу.
### OFF скрипт (rollback)
### Очистить и удалить таблицу маршрутизации
```
rollback_routing.sh

View File

@ -5,6 +5,7 @@ script =
rollback_script =
threads =
table =
priority =
gateway =
interface =
run =

View File

@ -24,7 +24,7 @@ def setup_logging():
handler = RotatingFileHandler(
log_file,
maxBytes=2 * 1024 * 1024, # 2MB
maxBytes=2 * 1024 * 1024,
backupCount=5
)
@ -40,28 +40,37 @@ def read_config(cfg_file, section=None):
config = configparser.ConfigParser()
config.read(cfg_file)
if section:
section = section.capitalize()
configs = []
if section and section in config:
cfg = config[section]
if section:
if section not in config:
logging.error(f"Секция {section} не найдена")
exit(1)
sections = [section]
logging.info(f"Используется конфиг: {section}")
else:
default_section = config.sections()[0]
cfg = config[default_section]
logging.info(f"Используется дефолтный конфиг: {default_section}")
sections = config.sections()
logging.info(f"Используются ВСЕ конфиги: {', '.join(sections)}")
return {
"threads": int(cfg.get("threads", "50")),
"filename": os.path.join(BASE_DIR, cfg.get("filename", "forced_list.txt")),
"script": os.path.join(BASE_DIR, cfg.get("script", "forced_vpn_ON.sh")),
"rollback_script": os.path.join(BASE_DIR, cfg.get("rollback_script", "forced_vpn_OFF.sh")),
"exclude_cloudflare": cfg.get("exclude_cloudflare", "no").lower() in ["yes", "y"],
"gateway": cfg.get("gateway", ""),
"interface": cfg.get("interface", ""),
"table": cfg.get("table", "1010"),
"run": cfg.get("run", "")
}
for sec in sections:
cfg = config[sec]
configs.append({
"name": sec,
"threads": int(cfg.get("threads", "50")),
"filename": os.path.join(BASE_DIR, cfg.get("filename", f"{sec}_forced_list.txt")),
"script": os.path.join(BASE_DIR, cfg.get("script", f"{sec}_ON.sh")),
"rollback_script": os.path.join(BASE_DIR, cfg.get("rollback_script", f"{sec}_OFF.sh")),
"exclude_cloudflare": cfg.get("exclude_cloudflare", "no").lower() in ["yes", "y"],
"gateway": cfg.get("gateway", ""),
"interface": cfg.get("interface", ""),
"table": cfg.get("table", "1010"),
"priority": cfg.get("priority", "101"),
"run": cfg.get("run", "")
})
return configs
def load_forced_list(cfg):
@ -162,105 +171,99 @@ async def main():
setup_logging()
parser = argparse.ArgumentParser()
parser.add_argument("-c", "--config", default=os.path.join(BASE_DIR, "config.ini"))
parser.add_argument("--env", help="Config section: test/prod")
parser.add_argument("--env", help="Config section")
args = parser.parse_args()
cfg = read_config(args.config, args.env)
config_path = os.path.join(BASE_DIR, "config.ini")
configs = read_config(config_path, args.env)
semaphore = Semaphore(cfg["threads"])
for cfg in configs:
logging.info(f"=== Обработка: {cfg['name']} ===")
domains, ready_ips = load_forced_list(cfg)
resolvers = get_resolvers()
semaphore = Semaphore(cfg["threads"])
logging.info(f"Домены: {len(domains)} | IP/CIDR: {len(ready_ips)}")
domains, ready_ips = load_forced_list(cfg)
resolvers = get_resolvers()
tasks = []
logging.info(f"Домены: {len(domains)} | IP/CIDR: {len(ready_ips)}")
for name, servers in resolvers:
resolver = dns.asyncresolver.Resolver()
resolver.nameservers = servers
tasks = []
for domain in domains:
tasks.append(resolve_domain(domain, resolver, semaphore))
for _, servers in resolvers:
resolver = dns.asyncresolver.Resolver()
resolver.nameservers = servers
results = await asyncio.gather(*tasks)
for domain in domains:
tasks.append(resolve_domain(domain, resolver, semaphore))
all_ips = set(ready_ips)
results = await asyncio.gather(*tasks)
for res in results:
all_ips.update(res)
all_ips = set(ready_ips)
logging.info(f"Всего IP до фильтра: {len(all_ips)}")
for res in results:
all_ips.update(res)
if cfg["exclude_cloudflare"]:
cf_ips = await get_cloudflare_ips()
before = len(all_ips)
logging.info(f"Всего IP до фильтра: {len(all_ips)}")
all_ips = {ip for ip in all_ips if ip not in cf_ips}
logging.info(f"Удалено Cloudflare IP: {before - len(all_ips)}")
if cfg["exclude_cloudflare"]:
cf_ips = await get_cloudflare_ips()
before = len(all_ips)
before_private = len(all_ips)
all_ips = {ip for ip in all_ips if is_public_ip(ip)}
logging.info(f"Удалено приватных IP: {before_private - len(all_ips)}")
logging.info(f"Финальных IP: {len(all_ips)}")
all_ips = {ip for ip in all_ips if ip not in cf_ips}
logging.info(f"Удалено Cloudflare IP: {before - len(all_ips)}")
result_file = os.path.join(BASE_DIR, "result_ips.txt")
with open(result_file, "w") as f:
for ip in sorted(all_ips):
f.write(ip + "\n")
before_private = len(all_ips)
all_ips = {ip for ip in all_ips if is_public_ip(ip)}
logging.info(f"Удалено приватных IP: {before_private - len(all_ips)}")
logging.info(f"Финальных IP: {len(all_ips)}")
gateway = ""
interface = ""
if cfg["gateway"]:
gateway = cfg["gateway"]
result_file = os.path.join(BASE_DIR, f"{cfg['name']}_result_ips.txt")
with open(result_file, "w") as f:
for ip in sorted(all_ips):
f.write(ip + "\n")
if cfg["interface"]:
interface = cfg["interface"]
if not cfg["gateway"] and not cfg["interface"]:
logging.error("Не указан Gateway и Interface, выход.")
exit(1)
if not gateway and not interface:
logging.error("Не указан Gateway и Interface, выход.")
exit(1)
with open(cfg["script"], "w") as f:
f.write(
f'#!/bin/bash\n\n'
f'if ! ip rule list | grep -q "lookup {cfg["table"]}.*priority {cfg["priority"]}"; then\n'
f' ip rule add table {cfg["table"]} priority {cfg["priority"]}\n'
f'fi\n\n'
f'ip route flush table {cfg["table"]}\n\n'
)
with open(cfg["script"], "w") as f:
f.write(
f'#!/bin/bash\n\n'
f'if ! ip rule list | grep -q "lookup {cfg["table"]}"; then\n'
f' ip rule add table {cfg["table"]} priority 101\n'
f'fi\n\n'
f'ip route flush table {cfg["table"]}\n\n'
)
for ip in sorted(all_ips):
if "/" in ip:
if cfg["gateway"]:
f.write(f'ip route replace {ip} via {cfg["gateway"]} table {cfg["table"]}\n')
else:
f.write(f'ip route replace {ip} dev {cfg["interface"]} table {cfg["table"]}\n')
else:
if cfg["gateway"]:
f.write(f'ip route replace {ip}/32 via {cfg["gateway"]} table {cfg["table"]}\n')
else:
f.write(f'ip route replace {ip}/32 dev {cfg["interface"]} table {cfg["table"]}\n')
for ip in sorted(all_ips):
if "/" in ip:
if cfg["gateway"]:
f.write(f'ip route replace {ip} via {cfg["gateway"]} table {cfg["table"]}\n')
elif cfg["interface"]:
f.write(f'ip route replace {ip} dev {cfg["interface"]} table {cfg["table"]}\n')
os.chmod(cfg["script"], 0o755)
else:
if cfg["gateway"]:
f.write(f'ip route replace {ip}/32 via {cfg["gateway"]} table {cfg["table"]}\n')
elif cfg["interface"]:
f.write(f'ip route replace {ip}/32 dev {cfg["interface"]} table {cfg["table"]}\n')
with open(cfg["rollback_script"], "w") as f:
f.write(
f'#!/bin/bash\n\n'
f'ip route flush table {cfg["table"]}\n'
f'ip rule del table {cfg["table"]} priority {cfg["priority"]} 2>/dev/null\n'
)
os.chmod(cfg["script"], 0o755)
os.chmod(cfg["rollback_script"], 0o755)
with open(cfg["rollback_script"], "w") as f:
f.write(
f'#!/bin/bash\n\n'
f'ip route flush table {cfg["table"]}\n'
f'ip rule del table {cfg["table"]} priority 101 2>/dev/null\n'
)
logging.info(f"ON: {cfg['script']}")
logging.info(f"OFF: {cfg['rollback_script']}")
os.chmod(cfg["rollback_script"], 0o755)
logging.info(f"ON: {cfg['script']}")
logging.info(f"OFF: {cfg['rollback_script']}")
if cfg["run"]:
logging.info("Запуск post-команды")
os.system(cfg["run"])
if cfg["run"]:
logging.info("Запуск post-команды")
os.system(cfg["run"])
if __name__ == "__main__":