fixik
This commit is contained in:
parent
77097e382d
commit
c38e6552fe
154
redirector.py
154
redirector.py
|
|
@ -5,6 +5,8 @@ import asyncio
|
|||
import configparser
|
||||
import ipaddress
|
||||
import os
|
||||
import logging
|
||||
from logging.handlers import RotatingFileHandler
|
||||
from asyncio import Semaphore
|
||||
|
||||
import dns.asyncresolver
|
||||
|
|
@ -13,11 +15,41 @@ import httpx
|
|||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
def read_config(cfg_file):
|
||||
# ================= LOGGING =================
|
||||
def setup_logging():
|
||||
log_dir = os.path.join(BASE_DIR, "logs")
|
||||
os.makedirs(log_dir, exist_ok=True)
|
||||
|
||||
log_file = os.path.join(log_dir, "redirector.log")
|
||||
|
||||
handler = RotatingFileHandler(
|
||||
log_file,
|
||||
maxBytes=2 * 1024 * 1024, # 2MB
|
||||
backupCount=5
|
||||
)
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s [%(levelname)s] %(message)s",
|
||||
handlers=[handler, logging.StreamHandler()]
|
||||
)
|
||||
|
||||
|
||||
# ================= CONFIG =================
|
||||
def read_config(cfg_file, section=None):
|
||||
config = configparser.ConfigParser()
|
||||
config.read(cfg_file)
|
||||
|
||||
cfg = config['Redirector']
|
||||
if section:
|
||||
section = section.capitalize()
|
||||
|
||||
if section and section in config:
|
||||
cfg = config[section]
|
||||
logging.info(f"Используется конфиг: {section}")
|
||||
else:
|
||||
default_section = config.sections()[0]
|
||||
cfg = config[default_section]
|
||||
logging.info(f"Используется дефолтный конфиг: {default_section}")
|
||||
|
||||
return {
|
||||
"threads": int(cfg.get("threads", "50")),
|
||||
|
|
@ -35,28 +67,32 @@ def load_forced_list(cfg):
|
|||
domains = []
|
||||
ips = set()
|
||||
|
||||
with open(cfg["filename"], "r", encoding="utf-8") as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
try:
|
||||
with open(cfg["filename"], "r", encoding="utf-8") as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
|
||||
if not line or line.startswith("#"):
|
||||
continue
|
||||
if not line or line.startswith("#"):
|
||||
continue
|
||||
|
||||
try:
|
||||
ip = ipaddress.ip_address(line)
|
||||
ips.add(str(ip))
|
||||
continue
|
||||
except ValueError:
|
||||
pass
|
||||
try:
|
||||
ip = ipaddress.ip_address(line)
|
||||
ips.add(str(ip))
|
||||
continue
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
try:
|
||||
net = ipaddress.ip_network(line, strict=False)
|
||||
ips.add(str(net))
|
||||
continue
|
||||
except ValueError:
|
||||
pass
|
||||
try:
|
||||
net = ipaddress.ip_network(line, strict=False)
|
||||
ips.add(str(net))
|
||||
continue
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
domains.append(line)
|
||||
domains.append(line)
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Ошибка чтения входящих данных: {e}")
|
||||
|
||||
return domains, ips
|
||||
|
||||
|
|
@ -72,12 +108,38 @@ def get_resolvers():
|
|||
]
|
||||
|
||||
|
||||
def is_public_ip(ip_str):
|
||||
try:
|
||||
if "/" in ip_str:
|
||||
net = ipaddress.ip_network(ip_str, strict=False)
|
||||
return not (
|
||||
net.is_private
|
||||
or net.is_loopback
|
||||
or net.is_link_local
|
||||
or net.is_multicast
|
||||
or net.is_reserved
|
||||
)
|
||||
else:
|
||||
ip = ipaddress.ip_address(ip_str)
|
||||
return not (
|
||||
ip.is_private
|
||||
or ip.is_loopback
|
||||
or ip.is_link_local
|
||||
or ip.is_multicast
|
||||
or ip.is_reserved
|
||||
or ip.is_unspecified
|
||||
)
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
|
||||
async def resolve_domain(domain, resolver, semaphore):
|
||||
async with semaphore:
|
||||
try:
|
||||
answer = await resolver.resolve(domain)
|
||||
return [r.address for r in answer]
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
logging.warning(f"DNS ошибка {domain}: {e}")
|
||||
return []
|
||||
|
||||
|
||||
|
|
@ -88,28 +150,29 @@ async def get_cloudflare_ips():
|
|||
|
||||
result = set()
|
||||
for line in r.text.splitlines():
|
||||
line = line.strip()
|
||||
if "/" in line:
|
||||
net = ipaddress.ip_network(line)
|
||||
for ip in net:
|
||||
result.add(str(ip))
|
||||
net = ipaddress.ip_network(line.strip())
|
||||
for ip in net:
|
||||
result.add(str(ip))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
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")
|
||||
args = parser.parse_args()
|
||||
|
||||
cfg = read_config(args.config)
|
||||
cfg = read_config(args.config, args.env)
|
||||
|
||||
semaphore = Semaphore(cfg["threads"])
|
||||
|
||||
domains, ready_ips = load_forced_list(cfg)
|
||||
resolvers = get_resolvers()
|
||||
|
||||
print(f"Домены: {len(domains)} | IP/CIDR: {len(ready_ips)}")
|
||||
logging.info(f"Домены: {len(domains)} | IP/CIDR: {len(ready_ips)}")
|
||||
|
||||
tasks = []
|
||||
|
||||
|
|
@ -125,28 +188,33 @@ async def main():
|
|||
all_ips = set(ready_ips)
|
||||
|
||||
for res in results:
|
||||
for ip in res:
|
||||
all_ips.add(ip)
|
||||
all_ips.update(res)
|
||||
|
||||
print(f"Всего IP до фильтра: {len(all_ips)}")
|
||||
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}
|
||||
print(f"Удалено Cloudflare IP: {before - len(all_ips)}")
|
||||
logging.info(f"Удалено Cloudflare IP: {before - len(all_ips)}")
|
||||
|
||||
print(f"Финальных IP: {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)}")
|
||||
|
||||
with open(cfg["script"], "w", encoding="utf-8") as f:
|
||||
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")
|
||||
|
||||
with open(cfg["script"], "w") as f:
|
||||
f.write(
|
||||
f'#!/bin/bash\n\n'
|
||||
f'# Добавляем правило если нет\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'# Чистим таблицу\n'
|
||||
f'ip route flush table {cfg["table"]}\n\n'
|
||||
)
|
||||
|
||||
|
|
@ -158,24 +226,22 @@ async def main():
|
|||
|
||||
os.chmod(cfg["script"], 0o755)
|
||||
|
||||
with open(cfg["rollback_script"], "w", encoding="utf-8") as f:
|
||||
with open(cfg["rollback_script"], "w") as f:
|
||||
f.write(
|
||||
f'#!/bin/bash\n\n'
|
||||
f'# Чистим таблицу\n'
|
||||
f'ip route flush table {cfg["table"]}\n\n'
|
||||
f'# Удаляем правило если есть\n'
|
||||
f'ip route flush table {cfg["table"]}\n'
|
||||
f'ip rule del table {cfg["table"]} priority 101 2>/dev/null\n'
|
||||
)
|
||||
|
||||
os.chmod(cfg["rollback_script"], 0o755)
|
||||
|
||||
print(f"ON script: {cfg['script']}")
|
||||
print(f"OFF script: {cfg['rollback_script']}")
|
||||
logging.info(f"ON: {cfg['script']}")
|
||||
logging.info(f"OFF: {cfg['rollback_script']}")
|
||||
|
||||
if cfg["run"]:
|
||||
print("Запуск скрипта...")
|
||||
logging.info("Запуск post-команды")
|
||||
os.system(cfg["run"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
asyncio.run(main())
|
||||
Loading…
Reference in New Issue
Block a user