import argparse import base64 import requests import os import sys # GFWList 官方(或常见)的地址 GFWLIST_URL = "https://raw.githubusercontent.com/gfwlist/gfwlist/master/gfwlist.txt" def log_info(message): print(f"[INFO] {message}") # 注意:foreign_dns_list 和 domestic_dns_list 现在是列表 def generate_rules(proxy_server, foreign_dns_list, domestic_dns_list, ipset_name="gfwlist"): """ 生成 Dnsmasq 和 IPSet 规则文件。 """ if not foreign_dns_list or not domestic_dns_list: log_info("错误:必须提供至少一个国内 DNS 和一个国外 DNS。") sys.exit(1) try: log_info(f"正在从 {GFWLIST_URL} 下载 GFWList...") # 尝试通过代理下载 GFWList (这里假设环境已配置或可以直接访问 GitHub) response = requests.get(GFWLIST_URL, timeout=30) response.raise_for_status() # 1. 解码 GFWList try: content = base64.b64decode(response.text).decode('utf-8') except: # 如果内容不是 base64 编码,则直接使用 content = response.text except requests.exceptions.RequestException as e: log_info(f"下载 GFWList 失败,请检查网络连接: {e}") # 如果下载失败,中止脚本以避免生成空文件 sys.exit(1) domains = set() for line in content.splitlines(): line = line.strip() if not line or line.startswith(('#', '!')): continue # 简单提取域名 if line.startswith('||'): domain = line[2:] elif line.startswith('.'): domain = line[1:] elif '/' in line: continue else: domain = line # 过滤掉 IP 地址,只保留域名 if domain and '.' in domain and not domain.startswith('*.'): domains.add(domain) # 2. 生成 DNSMasq 配置文件 dnsmasq_conf_path = "/etc/dnsmasq.d/gfwlist_router.conf" # 代理的上游 DNS (只使用提供的第一个国外 DNS) primary_foreign_dns = foreign_dns_list[0] proxy_port = proxy_server.split(':')[1] proxy_dns_server = f"{primary_foreign_dns}#{proxy_port}" try: with open(dnsmasq_conf_path, 'w') as f: f.write(f"# Dnsmasq rules generated by gfw_rules_generator.py on {os.path.basename(__file__)}\n") # 写入所有国内 DNS 服务器 for dns in domestic_dns_list: f.write(f"server={dns}\n") # GFWList 域名设置 (通过代理的上游 DNS 解析,并加入 IPSet) for domain in sorted(list(domains)): # 1. 将域名加入 IPSet 集合 (由 dnsmasq 动态处理) f.write(f"ipset=/{domain}/{ipset_name}\n") # 2. 通过代理的 DNS 服务器解析 (流量会走 redsocks/ssh) f.write(f"server=/{domain}/{proxy_dns_server}\n") log_info(f"已生成 Dnsmasq 规则到 {dnsmasq_conf_path},包含 {len(domains)} 个域名。") except IOError as e: log_info(f"写入 Dnsmasq 文件失败: {e}") sys.exit(1) # 3. 确保 IPSet 集合存在 (必须在 Dnsmasq 启动前存在) try: # 使用 hash:ip 集合,如果集合已存在则清除其内容 (flush) os.system(f"sudo ipset create {ipset_name} hash:ip family inet hashsize 1024 maxelem 65536 &>/dev/null || sudo ipset flush {ipset_name}") log_info(f"已确保 IPSet 集合 '{ipset_name}' 存在。") except Exception as e: log_info(f"创建 IPSet 失败: {e}") sys.exit(1) return True if __name__ == "__main__": parser = argparse.ArgumentParser(description="GFWList Rules Generator for Dnsmasq/IPSet") # 允许接受多个参数 (nargs='+') parser.add_argument("-p", "--proxy", required=True, help="SOCKS5 Proxy server (e.g., 127.0.0.1:1086)") parser.add_argument("-f", "--foreign-dns", nargs='+', required=True, help="Foreign DNS IP(s) (e.g., 8.8.8.8 9.9.9.9)") parser.add_argument("-d", "--domestic-dns", nargs='+', required=True, help="Domestic DNS IP(s) (e.g., 223.5.5.5 114.114.114.114)") parser.add_argument("-n", "--name", default="gfwlist", help="IPSet Name") args = parser.parse_args() log_info("开始生成 GFWList 规则...") generate_rules(args.proxy, args.foreign_dns, args.domestic_dns, args.name) log_info("规则生成完成。")