From 41a086d7ec4cb6b2a032cc93bb0692769fc54ba5 Mon Sep 17 00:00:00 2001 From: yumoqing Date: Sun, 30 Nov 2025 12:24:03 +0800 Subject: [PATCH] bugfix --- scripts/gateway.sh.j2 | 139 ++++++++++++++++----------------- scripts/gfw_rules_generator.py | 49 +++++++----- 2 files changed, 98 insertions(+), 90 deletions(-) diff --git a/scripts/gateway.sh.j2 b/scripts/gateway.sh.j2 index d0e4cee..cbf4114 100644 --- a/scripts/gateway.sh.j2 +++ b/scripts/gateway.sh.j2 @@ -22,8 +22,8 @@ REMOTE_SSH_IP="{{ remote_ssh_ip }}" # 远程服务器的IP地址 REMOTE_SSH_PORT="{{ remote_ssh_port }}" # 远程服务器的SSH端口 LOCAL_SOCKS5_PORT="{{ local_socks5_port }}" # SSH SOCKS5 代理在本机监听的端口 (127.0.0.1:1080) -# 透明代理工具 redsocks2 监听端口 -REDSOCKS_PORT="{{ redsocks_port }}" # redsocks2 在本机监听的端口 +# 透明代理工具 redsocks 监听端口 +REDSOCKS_PORT="{{ redsocks_port }}" # redsocks 在本机监听的端口 # DNSMASQ 配置 DNSMASQ_LISTEN_IP="${GATEWAY_LAN_IP}" # dnsmasq 监听的IP @@ -80,12 +80,12 @@ sudo apt update || log_error "apt update 失败。" log_info "禁用 UFW 以避免与 iptables 冲突..." sudo ufw disable || log_warn "UFW 未运行或禁用失败,请手动确认。" -log_info "安装必要工具:net-tools, iptables-persistent, ipset, dnsmasq, redsocks2, git, python3-pip, isc-dhcp-server..." +log_info "安装必要工具:net-tools, iptables-persistent, ipset, dnsmasq, redsocks, git, python3-pip, isc-dhcp-server..." install_package net-tools install_package iptables-persistent install_package ipset install_package dnsmasq -install_package redsocks2 +install_package redsocks install_package git install_package python3-pip install_package isc-dhcp-server # 新增 DHCP 服务器安装 @@ -172,32 +172,45 @@ log_info "ISC DHCP Server 已配置并启动,在 ${LAN_INTERFACE} 上提供 DH # ============================================================================== # 3. 启动 SSH SOCKS5 代理 (保持不变) # ============================================================================== -log_info "启动 SSH SOCKS5 代理 (${REMOTE_SSH_USER}@${REMOTE_SSH_IP}:${REMOTE_SSH_PORT})..." -# 检查 SSH 密钥是否存在,否则可能需要密码或生成密钥 -if [ ! -f "$HOME/.ssh/id_rsa" ] && [ ! -f "$HOME/.ssh/id_dsa" ]; then - log_warn "SSH 密钥文件不存在。SSH 连接可能需要输入密码或先生成密钥对。" - log_warn "请确保可以无密码SSH登录到远程服务器,或手动输入密码完成连接。" -fi +log_info "${curuser}:配置 SSH SOCKS5 代理 Systemd 服务..." +SSH_SOCKS5_SERVICE_FILE="/etc/systemd/system/ssh-socks5.service" -# 确保旧的代理进程被杀死 -sudo pkill -f "ssh -D ${LOCAL_SOCKS5_PORT}" || true +# 动态创建 Service 文件 +cat < /dev/null +[Unit] +Description=SSH SOCKS5 Proxy Service +After=network-online.target + +[Service] +User=${curuser} +Type=simple +# 注意:这里需要确保用户 {{ remote_ssh_user }} 是一个实际存在的用户,并且可以访>问其 $HOME/.ssh +# 为了简化,我们暂时用root运行,但更推荐使用非root用户 +ExecStart=/usr/bin/ssh -D ${LOCAL_SOCKS5_PORT} -N -p ${REMOTE_SSH_PORT} ${REMOTE_SSH_USER}@${REMOTE_SSH_IP} -o ExitOnForwardFailure=yes -o ServerAliveInterval=60 -o ServerAliveCountMax=3 +# 使用 Restart 策略确保连接断开时自动重连 +Restart=always +RestartSec=5 + +[Install] +WantedBy=multi-user.target +EOF + +log_info "启动并启用 SSH SOCKS5 代理服务..." +sudo systemctl daemon-reload +sudo systemctl enable ssh-socks5.service || log_error "启用 ssh-socks5 服务失败>。" +sudo systemctl restart ssh-socks5.service || log_error "启动 ssh-socks5 服务失败 +。请检查连接和免密登录配置。" +sleep 5 # 留出时间让服务启动和重试 -# 启动 SSH SOCKS5 代理,后台运行 -sudo ssh -D ${LOCAL_SOCKS5_PORT} -N -f -p ${REMOTE_SSH_PORT} ${REMOTE_SSH_USER}@${REMOTE_SSH_IP} -o ExitOnForwardFailure=yes -if [ $? -ne 0 ]; then - log_error "SSH SOCKS5 代理启动失败。请检查远程SSH服务器信息和连接。" -fi -log_info "SSH SOCKS5 代理已在 127.0.0.1:${LOCAL_SOCKS5_PORT} 启动。" -# 验证代理是否在监听 (需要一点时间启动) -sleep 2 if ! ss -tnlp | grep ":${LOCAL_SOCKS5_PORT}" &>/dev/null; then log_error "SSH SOCKS5 代理端口 ${LOCAL_SOCKS5_PORT} 未在监听。请手动检查 SSH 进程。" fi +log_info "SSH SOCKS5 代理已通过 Systemd 启动,并在 127.0.0.1:${LOCAL_SOCKS5_PORT} 监听,支持自动重连。" # ============================================================================== -# 4. 配置 redsocks2 (透明 SOCKS5 代理) (保持不变) +# 4. 配置 redsocks (透明 SOCKS5 代理) (保持不变) # ============================================================================== -log_info "配置 redsocks2 透明代理..." +log_info "配置 redsocks 透明代理..." # 备份现有配置 sudo cp /etc/redsocks.conf /etc/redsocks.conf.bak_$(date +%Y%m%d%H%M%S) || log_warn "备份 redsocks.conf 失败。" @@ -216,14 +229,13 @@ redsocks { ip = 127.0.0.1; # SSH SOCKS5 代理的地址 port = ${LOCAL_SOCKS5_PORT}; # SSH SOCKS5 代理的端口 - type = socks5; # SOCKS5 代理 } EOF -sudo systemctl restart redsocks2 || log_error "重启 redsocks2 服务失败。" -sudo systemctl enable redsocks2 || log_error "启用 redsocks2 服务失败。" -log_info "redsocks2 透明代理已配置并启动,监听 127.0.0.1:${REDSOCKS_PORT}。" +sudo systemctl restart redsocks || log_error "重启 redsocks 服务失败。" +sudo systemctl enable redsocks || log_error "启用 redsocks 服务失败。" +log_info "redsocks 透明代理已配置并启动,监听 127.0.0.1:${REDSOCKS_PORT}。" # ============================================================================== # 5. 配置 dnsmasq 智能 DNS 转发 (保持不变) @@ -262,55 +274,40 @@ log_info "dnsmasq 智能 DNS 转发已初步配置。" # ============================================================================== # 6. 安装和配置 gfwlist2new (保持不变) # ============================================================================== -log_info "安装和配置 gfwlist2new 工具..." +log_info "集成和配置 GFW 规则生成器 (gfw_rules_generator.py)..." +LOCAL_SCRIPT_NAME="gfw_rules_generator.py" if [ ! -d "${GFWLIST2NEW_DIR}" ]; then - log_info "克隆 gfwlist2new 仓库..." - sudo git clone "{{ gfwlist2new_repo }}" "${GFWLIST2NEW_DIR}" || log_error "克隆 gfwlist2new 仓库失败。" + log_info "创建脚本目录..." + sudo mkdir -p "${GFWLIST2NEW_DIR}" || log_error "创建目录 ${GFWLIST2NEW_DIR} 失败。" fi +cp $LOCAL_SCRIPT_NAME ${GFWLIST2NEW_DIR} + +# 确保安装 requests 依赖 +log_info "安装 Python 依赖:requests..." +sudo pip3 install requests || log_error "安装 requests 依赖失败。" + +# 写入新的 Python 脚本(假设您已将上面的内容保存到临时文件并复制到这里) +# 推荐您手动将上面的 Python 代码保存到 ${GFWLIST2NEW_DIR}/${LOCAL_SCRIPT_NAME} +# 如果要用脚本自动写入,代码会非常长,这里跳过写入步骤,假设文件已存在。 +log_info "请确保 gfw_rules_generator.py 文件已存在于 ${GFWLIST2NEW_DIR}/" cd "${GFWLIST2NEW_DIR}" -log_info "安装 gfwlist2new 依赖..." -sudo pip3 install -r requirements.txt || log_error "安装 gfwlist2new 依赖失败。" -log_info "配置 gfwlist2new 的配置文件 config.conf..." -# 备份现有配置 -sudo cp config.conf config.conf.bak_$(date +%Y%m%d%H%M%S) || log_warn "备份 gfwlist2new config.conf 失败。" +# 定义 SOCKS5 代理地址 +SOCKS5_SERVER_ADDR="127.0.0.1:${LOCAL_SOCKS5_PORT}" -# 更新 config.conf 以适应我们的需求 -# gfwlist2new 默认会将 gfwlist 中的域名指向代理IP -# IPSET_NAME 是 ipset 列表的名称 -# DNSMASQ_CONF_PATH 是 dnsmasq 配置文件路径 -# SOCKS5_SERVER 是代理服务器的IP和端口 (这里是 redsocks2 监听的IP和端口) -cat < /dev/null -[DEFAULT] -# IPSet Config -IPSET_NAME = gfwlist -IPSET_FILE = /etc/ipset/gfwlist.conf +log_info "执行 GFW 规则生成脚本..." +# 使用 -p, -f, -d 参数执行新脚本 +sudo python3 "${LOCAL_SCRIPT_NAME}" -p "${SOCKS5_SERVER_ADDR}" -f "{{ foreign_dns1 }} {{foreign_dns2 }}" -d "{{ domestic_dns1 }} {{ domestic_dns2 }}" || log_error "规则生成脚本运行失败。" -# Dnsmasq Config -DNSMASQ_CONF_PATH = /etc/dnsmasq.d/gfwlist_router.conf -DNSMASQ_LOG_PATH = /var/log/dnsmasq.log -DNS_PROXY_SERVER = 127.0.0.1#${REDSOCKS_PORT} # 这里的端口要指向 redsocks2 - -# Proxy Config -SOCKS5_SERVER = 127.0.0.1:${LOCAL_SOCKS5_PORT} # 这里的端口是SSH SOCKS5代理 - -# Other -GITHUB_RAW_URL = raw.githubusercontent.com -EOF - -log_info "执行 gfwlist2new 生成 ipset 和 dnsmasq 规则..." -# 第一次运行,生成规则并应用 -sudo python3 gfwlist2new.py -s "${SOCKS5_SERVER}" -f "{{ foreign_dns }}" -d "{{ domestic_dns }}" || log_error "gfwlist2new 运行失败。" -# 重启 dnsmasq 应用新规则 sudo systemctl restart dnsmasq || log_error "重启 dnsmasq (gfwlist) 失败。" -log_info "设置 gfwlist2new 定时更新任务 (每天凌晨 3:00)..." -(sudo crontab -l 2>/dev/null; echo "0 3 * * * cd ${GFWLIST2NEW_DIR} && sudo python3 gfwlist2new.py -s ${SOCKS5_SERVER} -f {{ foreign_dns }} -d {{ domestic_dns }} && sudo systemctl restart dnsmasq") | sudo crontab - -log_info "gfwlist2new 配置完成并设置定时更新。" +log_info "设置 GFW 规则定时更新任务 (每天凌晨 3:00)..." +# 设置定时任务 +(sudo crontab -l 2>/dev/null; echo "0 3 * * * cd ${GFWLIST2NEW_DIR} && sudo python3 ${LOCAL_SCRIPT_NAME} -p ${SOCKS5_SERVER_ADDR} -f "{{ foreign_dns1 }} {{ foreign_dns2 }}" -d "{{ domestic_dns1 }} {{domestic_dns1}}" && sudo systemctl restart dnsmasq") | sudo crontab - +log_info "规则生成器配置完成并设置定时更新。" cd - > /dev/null - # ============================================================================== # 7. 配置 IPTABLES 规则实现透明代理和 NAT (保持不变) # ============================================================================== @@ -333,16 +330,16 @@ sudo iptables -A FORWARD -i {{ lan_interface }} -o {{ wan_interface }} -j ACCEPT sudo iptables -A FORWARD -o {{ lan_interface }} -i {{ wan_interface }} -m state --state RELATED,ESTABLISHED -j ACCEPT log_info "已配置 IP 转发规则。" -# 3. 透明代理规则 (使用 redsocks2 和 ipset) -# 重定向到 redsocks2 的流量,不处理来自 redsocks2 本身的流量 +# 3. 透明代理规则 (使用 redsocks 和 ipset) +# 重定向到 redsocks 的流量,不处理来自 redsocks 本身的流量 sudo iptables -t nat -A PREROUTING -i {{ lan_interface }} -p tcp -s {{ gateway_lan_cidr }} -m set --match-set gfwlist dst -j REDIRECT --to-ports ${REDSOCKS_PORT} -log_info "已配置透明代理重定向规则 (所有端口,目标IP在 gfwlist 中的内网TCP流量到 redsocks2)。" +log_info "已配置透明代理重定向规则 (所有端口,目标IP在 gfwlist 中的内网TCP流量到 redsocks)。" -# 排除 redsocks2 自身流量循环 -# 这一步非常重要,避免 redsocks2 自己产生流量又被 iptables 捕获 +# 排除 redsocks 自身流量循环 +# 这一步非常重要,避免 redsocks 自己产生流量又被 iptables 捕获 sudo iptables -t nat -A OUTPUT -p tcp -d 127.0.0.1 --dport ${REDSOCKS_PORT} -j RETURN sudo iptables -t nat -A OUTPUT -p tcp -d 127.0.0.1 --dport ${LOCAL_SOCKS5_PORT} -j RETURN -log_info "已配置排除 redsocks2 自身流量循环的规则。" +log_info "已配置排除 redsocks 自身流量循环的规则。" # 保存 iptables 规则 sudo netfilter-persistent save || log_error "保存 iptables 规则失败。" @@ -351,7 +348,7 @@ log_info "IPTABLES 规则已配置并保存。" log_info "----------------------------------------------------------------------------------" log_info "网关主机配置完成!" -log_info "请检查 redsocks2 和 ssh -D 进程是否正常运行。" +log_info "请检查 redsocks 和 ssh -D 进程是否正常运行。" log_info "请注意:SSH SOCKS5 代理可能需要手动输入密码或配置免密登录才能持久运行。" log_info "内网主机现在应该可以自动通过 DHCP 获取 IP 并访问网络。" log_info "----------------------------------------------------------------------------------" diff --git a/scripts/gfw_rules_generator.py b/scripts/gfw_rules_generator.py index 4a5cab3..187df0a 100644 --- a/scripts/gfw_rules_generator.py +++ b/scripts/gfw_rules_generator.py @@ -10,12 +10,18 @@ GFWLIST_URL = "https://raw.githubusercontent.com/gfwlist/gfwlist/master/gfwlist. def log_info(message): print(f"[INFO] {message}") -def generate_rules(proxy_server, foreign_dns, domestic_dns, ipset_name="gfwlist"): +# 注意: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() @@ -27,9 +33,9 @@ def generate_rules(proxy_server, foreign_dns, domestic_dns, ipset_name="gfwlist" content = response.text except requests.exceptions.RequestException as e: - log_info(f"下载 GFWList 失败,使用本地缓存:{e}") - # 允许继续运行,依赖旧的规则文件 - content = "" + log_info(f"下载 GFWList 失败,请检查网络连接: {e}") + # 如果下载失败,中止脚本以避免生成空文件 + sys.exit(1) domains = set() for line in content.splitlines(): @@ -37,7 +43,7 @@ def generate_rules(proxy_server, foreign_dns, domestic_dns, ipset_name="gfwlist" if not line or line.startswith(('#', '!')): continue - # 提取域名(忽略 IP、通配符规则和特殊标记) + # 简单提取域名 if line.startswith('||'): domain = line[2:] elif line.startswith('.'): @@ -47,28 +53,31 @@ def generate_rules(proxy_server, foreign_dns, domestic_dns, ipset_name="gfwlist" 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" - # 确保 foreign_dns 包含端口 (例如 127.0.0.1#1086) - proxy_dns_server = f"{foreign_dns}#{proxy_server.split(':')[1]}" + # 代理的上游 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\n") + f.write(f"# Dnsmasq rules generated by gfw_rules_generator.py on {os.path.basename(__file__)}\n") - # 设置国内 DNS - f.write(f"server={domestic_dns}\n") + # 写入所有国内 DNS 服务器 + for dns in domestic_dns_list: + f.write(f"server={dns}\n") - # GFWList 域名设置 + # GFWList 域名设置 (通过代理的上游 DNS 解析,并加入 IPSet) for domain in sorted(list(domains)): - # 1. 将域名加入 IPSet 集合 + # 1. 将域名加入 IPSet 集合 (由 dnsmasq 动态处理) f.write(f"ipset=/{domain}/{ipset_name}\n") - # 2. 通过代理的 DNS 服务器解析 + # 2. 通过代理的 DNS 服务器解析 (流量会走 redsocks/ssh) f.write(f"server=/{domain}/{proxy_dns_server}\n") log_info(f"已生成 Dnsmasq 规则到 {dnsmasq_conf_path},包含 {len(domains)} 个域名。") @@ -76,10 +85,9 @@ def generate_rules(proxy_server, foreign_dns, domestic_dns, ipset_name="gfwlist" log_info(f"写入 Dnsmasq 文件失败: {e}") sys.exit(1) - # 3. 确保 IPSet 存在 - # 脚本不负责添加 IP 地址(由 Dnsmasq 在运行时动态添加) - # 但需要确保 IPSet 集合本身存在。 + # 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: @@ -90,9 +98,11 @@ def generate_rules(proxy_server, foreign_dns, domestic_dns, ipset_name="gfwlist" 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", required=True, help="Foreign DNS IP (e.g., 8.8.8.8)") - parser.add_argument("-d", "--domestic-dns", required=True, help="Domestic DNS IP (e.g., 223.5.5.5)") + 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() @@ -100,3 +110,4 @@ if __name__ == "__main__": log_info("开始生成 GFWList 规则...") generate_rules(args.proxy, args.foreign_dns, args.domestic_dns, args.name) log_info("规则生成完成。") +