diff --git a/.gitignore b/.gitignore index ecbfcaa..b8e7264 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .idea -.venv \ No newline at end of file +.venv +config.ini \ No newline at end of file diff --git a/README.md b/README.md index 916b165..58e16f9 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/config.ini.example b/config.ini.example index a2463b8..42be887 100644 --- a/config.ini.example +++ b/config.ini.example @@ -5,6 +5,7 @@ script = rollback_script = threads = table = +priority = gateway = interface = run = diff --git a/redirector.py b/redirector.py index 5ea57b8..c92ece7 100644 --- a/redirector.py +++ b/redirector.py @@ -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__":