Добавлен режим блокировки через роутинг в blackhole

This commit is contained in:
Sazonov Andrey 2026-04-01 17:38:17 +03:00
parent bcd6df2d27
commit db9f9d06bc
3 changed files with 84 additions and 61 deletions

View File

@ -4,6 +4,7 @@
- резолва доменов (с разных DNS)
- объединения IP / CIDR
- генерации routing-скрипта для policy routing
- дополнительно можно использовать секцию `[Blackhole]` для блокировки нежелательных IP
---
@ -42,6 +43,21 @@ run = # Shell команда которая выполнитс
```
- Можно добавить несколько секций для нескольких конфигураций
#### Пример секции `[Blackhole]`
```ini
[Blackhole]
exclude_cloudflare = yes
filename = blackhole_list.txt
script = blackhole_ON.sh
rollback_script = blackhole_OFF.sh
threads = 50
table = 1001
priority = 10
run =
```
- Все домены и IP из файла `blackhole_list.txt` будут недоступны.
---
## Входной файл

View File

@ -9,3 +9,13 @@ priority =
gateway =
interface =
run =
[Blackhole]
exclude_cloudflare =
filename =
script =
rollback_script =
threads =
table =
priority =
run =

View File

@ -56,8 +56,11 @@ def read_config(cfg_file, section=None):
for sec in sections:
cfg = config[sec]
is_blackhole = sec.lower() == "blackhole"
configs.append({
"name": sec,
"is_blackhole": is_blackhole,
"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")),
@ -66,7 +69,7 @@ def read_config(cfg_file, section=None):
"gateway": cfg.get("gateway", ""),
"interface": cfg.get("interface", ""),
"table": cfg.get("table", "1010"),
"priority": cfg.get("priority", "101"),
"priority": cfg.get("priority", "10" if is_blackhole else "101"),
"run": cfg.get("run", "")
})
@ -104,8 +107,7 @@ def load_forced_list(cfg):
except Exception as e:
logging.error(f"Ошибка чтения входящих данных: {e}")
exit(1)
if len(domains) == 0 and len(ips) == 0:
logging.info(f"В файле {cfg['filename']} не найдено доменов и IP")
return domains, ips
@ -154,11 +156,8 @@ async def resolve_domain(domain, dns_name, server, semaphore):
try:
answer = await resolver.resolve(domain)
return [r.address for r in answer]
except Exception as e:
logging.warning(
f"DNS ошибка [{dns_name} - {server} | {domain}]: {e}"
)
logging.warning(f"DNS ошибка [{dns_name} - {server} | {domain}]: {e}")
return []
@ -173,7 +172,6 @@ def normalize_ip(ip):
else:
return f"{addr}/32"
return ip
except ValueError:
return ip
@ -192,6 +190,48 @@ async def get_cloudflare_ips():
return result
# ================= SCRIPT GENERATORS =================
def generate_policy_script(cfg, ips):
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 {cfg["priority"]}\n'
f'fi\n\n'
f'ip route flush table {cfg["table"]}\n\n'
)
for ip in ips:
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')
def generate_blackhole_script(cfg, ips):
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 {cfg["priority"]}\n'
f'fi\n\n'
f'ip route flush table {cfg["table"]}\n\n'
)
for ip in ips:
f.write(f'ip route replace blackhole {ip} table {cfg["table"]}\n')
def generate_rollback(cfg):
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"]} 2>/dev/null\n'
)
# ================= MAIN =================
async def main():
setup_logging()
@ -210,85 +250,42 @@ async def main():
domains, ready_ips = load_forced_list(cfg)
resolvers = get_resolvers()
logging.info(f"Домены: {len(domains)} | IP/CIDR: {len(ready_ips)}")
tasks = []
for dns_name, servers in resolvers:
for server in servers:
for domain in domains:
tasks.append(
resolve_domain(domain, dns_name, server, semaphore)
)
tasks.append(resolve_domain(domain, dns_name, server, semaphore))
results = await asyncio.gather(*tasks)
all_ips = set(ready_ips)
for res in results:
all_ips.update(res)
logging.info(f"Всего IP до фильтра: {len(all_ips)}")
if cfg["exclude_cloudflare"]:
cf_ips = await get_cloudflare_ips()
before = 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)}")
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)}")
normalized_ips = sorted({normalize_ip(ip) for ip in all_ips})
normalized_ips = {normalize_ip(ip) for ip in all_ips}
result_file = os.path.join(BASE_DIR, f"{cfg['name']}_result_ips.txt")
with open(result_file, "w") as f:
for ip in sorted(normalized_ips):
f.write(ip + "\n")
if cfg["is_blackhole"]:
generate_blackhole_script(cfg, normalized_ips)
else:
if not cfg["gateway"] and not cfg["interface"]:
logging.error("Не указан Gateway или Interface")
exit(1)
generate_policy_script(cfg, normalized_ips)
if not cfg["gateway"] and not cfg["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"]}"; 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'
)
for ip in sorted(normalized_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')
generate_rollback(cfg)
os.chmod(cfg["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"]} 2>/dev/null\n'
)
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"])