如何绕过运营商封锁正常使用WireGuard | WireGuard被封锁解决方案
Wireguard被封处理
背景
为了安全访问家里的设备,我选用Wireguard进行异地组网,配合DDNS效果很好;但是在使用一段时间后,经常会发现无法连接的情况,此时只要变更Wireguard的端口,就可以恢复正常。说明运营商进行了封锁。
为什么会被封锁,猜测的原理
组网的过程中,我发现如果不配置心跳,长时间不访问,可能无法及时连接,所以我给peer配置了心跳
[Peer]
PersistentKeepalive = 25
那么,如果外部有10到几十个设备,通过DDNS的域名访问,定时发送心跳到Wireguard的服务端,这就有很强的规律,揭示了这台宽带设备上可能运行了后台服务,会被运营商标记为高风险。
我们是国内的访问,应该不会涉及到GFW之类的防火墙;那么极有可能是链路中的某些运营商,(可能是因为跨运营商的网间流量费结算问题),故意会阻断某些看起来像是非商业服务的UDP协议。
反正UDP协议多用在一些暴力的、高速的、PCDN、直播推流之类的灰色的地带。正常人,谁不用TCP协议呢?这种高成本的垃圾UDP流量,可能会被统计到,进而阻止家用宽带广泛使用UDP服务。
因为UDP和TCP的报文协议里,端口的偏移量是一样的,想要开发一套流量统计程序,还是很方便的。
解决方案
如何对抗这种无厘头的封禁行为?我们也没有明显的违规,只是异地组网罢了。步骤如下:
家用宽带路由器上设置一批端口映射到wireguard
- 假设我们的wireguard是端口 51820 暴漏在家用宽带上;就配置一批端口映射 11000-15000全部映射到51820端口。
- 如果您使用的Openwrt系统,可以简单的在防火墙里,配置端口映射规则;端口映射是支持11000-15000的区间映射
- 此时,外部通过UDP访问11000-15000端口,实际内部访问的是51820端口,都是一样的服务。
外部wireguard的组网端,配置自动化脚本,一旦链路不通,自动切换端口
以windows为例,如何配置自动切换。
- 安装windows版本的官方wireguard程序。
- 准备一个目录 xxx, 里面放你配置好的wireguard的配置文件wg_home.conf,可以用默认的端口51820
- 在目录 xxx中,复制 wg_home.conf一份,并改名为 wg_home.conf.vm 并且把默认的51820端口改为
${PORT}
- 注册 wg_home.conf 为服务,wireguard /installtunnelservice C:\path\to\xxx\wg_home.conf
需要使用管理员账号,如果找不到wireguard命令,请使用全路径,举例:C:\Program Files\WireGuard\wireguard.exe /installtunnelservice C:\path\to\xxx\wg_home.conf
- 此时,会自动注册一个开机自动启动的服务:WireGuardTunnel$wg_home
- 在目录 xxx中,创建文件check_wg.py, 并配置到windows的定时任务里,一分钟一次【自行网上搜索如何配置】,使用管理员权限执行。
- check_wg.py的内容如下【按照自己的需求,修改 10.250.250.2 为对端的Wireguard的peer端的ip;默认端口的随机范围是11000-15000,你可以自行修改,跟 路由器的端口范围一致】:
import os
import sys
import time
import datetime
import random
import subprocess
import shutil
# 日志相关配置
LOG_DIR = os.path.join(os.path.dirname(__file__), "logs")
LOG_FILE = os.path.join(LOG_DIR, datetime.datetime.now().strftime("%Y-%m-%d") + ".log")
MAX_DAYS = 3
# wireguard服务名(请根据实际修改)
WG_SERVICE_NAME = "WireGuardTunnel$wg_home" # 或者你实际的服务名
def write_log(msg):
if not os.path.exists(LOG_DIR):
os.makedirs(LOG_DIR)
with open(LOG_FILE, "a", encoding="utf-8") as f:
f.write(f"[{datetime.datetime.now().isoformat()}] {msg}\n")
print(msg)
def clean_old_logs():
"""只保留最近3天的日志文件"""
if not os.path.exists(LOG_DIR):
return
files = sorted(
[f for f in os.listdir(LOG_DIR) if f.endswith(".log")],
key=lambda x: x
)
if len(files) > MAX_DAYS:
for old_file in files[:-MAX_DAYS]:
try:
os.remove(os.path.join(LOG_DIR, old_file))
write_log(f"删除老日志: {old_file}")
except Exception as e:
write_log(f"删除日志失败: {old_file}, {e}")
def ping_target(ip):
"""ping目标IP, 通则返回True, 否则False"""
cmd = ["ping", "-n", "1", "-w", "2000", ip]
try:
res = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding="gbk")
return res.returncode == 0
except Exception as e:
write_log(f"Ping异常: {e}")
return False
def pick_random_port():
return random.randint(11000, 15000)
def gen_wg_conf(port):
"""生成wg_home.conf文件"""
tpl_path = os.path.join(os.path.dirname(__file__), "wg_home.conf.vm")
out_path = os.path.join(os.path.dirname(__file__), "wg_home.conf")
if not os.path.exists(tpl_path):
write_log("模板文件wg_home.conf.vm不存在!")
return False
with open(tpl_path, "r", encoding="utf-8") as f:
content = f.read()
content = content.replace("${PORT}", str(port))
with open(out_path, "w", encoding="utf-8") as f:
f.write(content)
write_log(f"已生成wg_home.conf,端口为: {port}")
return True
def restart_wg_service():
"""重启WireGuard服务"""
try:
# Windows服务名可能不同,请自行调整
stop_cmd = f'net stop "{WG_SERVICE_NAME}"'
start_cmd = f'net start "{WG_SERVICE_NAME}"'
print(stop_cmd)
print(start_cmd)
write_log("正在停止WireGuard服务...")
subprocess.run(stop_cmd, shell=True)
time.sleep(2)
write_log("正在启动WireGuard服务...")
subprocess.run(start_cmd, shell=True, check=True)
write_log("WireGuard服务重启完成。")
except subprocess.CalledProcessError as e:
write_log(f"重启服务失败: {e}")
def main():
clean_old_logs()
target_ip = "10.250.250.2" # 请根据实际目标修改
write_log(f"开始检测 {target_ip} 连通性...")
if ping_target(target_ip):
write_log(f"{target_ip} ping通,程序退出。")
sys.exit(0)
else:
write_log(f"{target_ip} ping不通,准备切换WireGuard端口...")
port = pick_random_port()
if not gen_wg_conf(port):
write_log("生成配置失败,退出。")
sys.exit(2)
restart_wg_service()
if __name__ == "__main__":
main()
- 这段python脚本的原理是:定时检测Wireguard的Peer对端,如果通了,直接结束;如果不通,随机选择一个端口,然后渲染 wg_home.conf.vm 的 ${PORT}的对端的端口为随机值;覆盖到 wg_home.conf;然后重启wireguard的服务;此时我们使用新的随机端口访问Wireguard,就可以无视运营商的封锁了