Добавлен режим блокировки через роутинг в blackhole
This commit is contained in:
parent
bcd6df2d27
commit
db9f9d06bc
16
README.md
16
README.md
|
|
@ -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` будут недоступны.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Входной файл
|
||||
|
|
|
|||
|
|
@ -9,3 +9,13 @@ priority =
|
|||
gateway =
|
||||
interface =
|
||||
run =
|
||||
|
||||
[Blackhole]
|
||||
exclude_cloudflare =
|
||||
filename =
|
||||
script =
|
||||
rollback_script =
|
||||
threads =
|
||||
table =
|
||||
priority =
|
||||
run =
|
||||
119
redirector.py
119
redirector.py
|
|
@ -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"])
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user