微信对接服务器在进行一些关键信息的请求时,需要校验请求方的IP地址,如果请求方的IP地址并没在在设置的IP池中,并会发生请求错误。这是出于安全考虑的一种方式,处理方式无可厚非。但当我们想在本地部署微信的对接服务器时,却带来了不小的问题。
由于本地的服务器往往处于NAT内部,同时微信的对接地址的域名的DNS解析也不固定,这会导致同样一台服务器,在不同的时间请求同一个微信的对接地址时,最终的出口IP不一致。
本文阐述了在FreeBSD操作下,结合使用WireGuard及nginx完成的本地微信部署的一种方案。
基本介绍
在未进行改造前,基本情况如下:
在开发过程中,可以使用端口映射等方案完成本地服务器对接微信服务器的主动请求,比如用户进行扫码或是用户向公众号发送消息时,微信服务器会主动发起请求:
在测试过程中,我们也可以通过本地服务器直接向微信服务器发起请求,比如请求临时二维码:
但在生产环境下,正式的微信公众号会对请求二维码等操作的请求IP做校验,所以在进行维护的过程中,我们需要获取当前本地服务器所在网络下的出口网关的所有的IP。
但当前的问题是,该网关的IP可能是动态的(比如可能是在负载均衡下的多IP中的1个,比如运营商重新进行了网络规划,比如运营商对一些小区的出口IP采用动态分配的方式)。所以我们无法将本地服务器应用到生产环境。
而如果在发送请求时,走如下链路,则可以解决这个问题:
此时,由于虚拟线路的存在,本地服务器最终发送的请求,则全部由具有公网IP的云服务器进行转发,而微信服务器做校验的时候,获取到的请求IP则会永远是云服务器的地址,而这个服务器的IP是固定的,且是我们可以掌握的。
要想实现这个功能,需要做以下几点工作:
- 搭建虚拟线路
- 本地服务器将对微信服务器的请求交给云服务器进行转发
- 云服务器提供网关的功能
- 云服务器提供NAT的功能
WireGuard
FreeBSD服务端
WireGuard是一种端对端的VPN解决方案,特点是配置简单、灵活。FreeBSD是款Unix操作系统,特点是坚如盘石般的稳定。
在FreeBSD14中的安装步骤如下:
$ pkg install wireguard-go wireguard-tools
$ wg genkey | tee /usr/local/etc/wireguard/privatekey | wg pubkey > /usr/local/etc/wireguard/publickey
$ wg genkey | tee /usr/local/etc/wireguard/client_privatekey | wg pubkey > /usr/local/etc/wireguard/client_publickey
此时将在/usr/local/etc/wireguard/
生成两对密钥(也可以只生成1对,另一对去客户端生成)。
安装完成后,进行配置 /usr/local/etc/wireguard/wg0.conf
,示例内容如下:
[Interface]
# 随便配置个私有地址,比如10.0.0.1/24,笔者当前的校园网中使用了10段的私有地址,所以此处使用一个B类的172.29段的私有地址.
Address = 172.29.247.1/24
# 监听的端口,走的是UDP协议,如果有前置
ListenPort = 18120
PrivateKey = 这里粘贴privatekey的内容
[Peer]
PublicKey = 这里粘贴client_publickey的内容
# 172.29.247.2为客户端IP,172.29.247与前面保持一致,后面的数字是2-254中的任意1个
AllowedIPs = 172.29.247.2/32
保存后退出,如果云服务器有防火墙设置,要开放对应的端口的UDP协议。然后在服务端运行 wg-quick up wg0
来启动服务端。
客户端
客户端的安装请参考WireGuard的官方站点,在此只给出客户端的相关配置:
[Interface]
PrivateKey = 粘贴client_privatekey中的内容
# 地址与服务端配置的要相同,后面是 /24 不是 /32
Address = 172.29.247.2/24
[Peer]
PublicKey = 粘贴publickey中的内容
AllowedIPs = 这个值是通过站点生成的,后面有说明
#
Endpoint = 云服务器的IP地址:在服务端设置的端口(比如我当前的为18120)
AllowedIPs
可以通过站点来快带生成,比如使用 https://www.procustodibus.com/blog/2021/03/wireguard-allowedips-calculator/
这是个比较典型的配置,表示:除私有IP网段外,其它的请求全部走VPN线路。但特别要注意几点:
- 如果服务器的IP地址是公网(比如我们当前就是,那么需要在
Disallowed IPs
的最后追加,服务器ip/32
,不然wg无法连通(感觉这算是wg的一个BUG,即然是远程服务器IP,那么应该默认禁用) - 复制生成的信息后,在最后追加:
,当前wg网段
,比如我当前追加的值为:,172.29.247.0/24
- 如果DNS服务器地址不是私网,也需要加入到
Disallowed IPs
中,不然连接成功后会导致DNS解析错误。
客户端启动后,使用wg命令如果能看到lastest handshake
信息说明握手成功。如果不成功,则需要从以下几个方面入手。
- ping一下服务端的私有IP地址,比如当前:
ping 172.29.247.1
- 查看日志,由于是客户端发起的,所以主要看客户端日志
- 查看服务端防火墙对应的端口,协议是否开放的正确
- 断开wg客户端,ping服务端IP,看是否能通;然后启动wg客户端,再ping服务端IP,如果此时不通说明AllowedIPs中并有没有服务端IP。这时候应该重新生成AllowedIPs
- 查看路由表,打印出来然后复制给deepseek,问它目标地址为
172.29.247.1
及 服务器公网IP的流量转发是否一个转发给了隧道,另一个转发给了本地网关。如果不是,仍然研究 AllowedIPs
如果各方面都没有问题的话,当前客户端应该能够ping通 172.29.247.1
,但却发现上不了其它的网了,这是正常的。下一步我们还需要启用freebsd的网关及NAT功能。
FreeBSD网关
使用FreeBSD做网关必然是不会有问题的,国内知名的网络管理系统Panabit就是用FreeBSD做的流控。首先我们开启网关功能,NAT功能:ee /etc/rc.conf
gateway_enable="YES"
ipnat_enable="YES"
ipnat_rules="/etc/ipnat.rules"
保存退出,做编辑 /etc/ipnat.rules
:
map <可以上外网的网卡> 172.29.247.0/24 -> 0/32
其中172.29.247.0/24
为wg设置的网段.
然后开启转发功能:
echo 'net.inet.ip.forwarding=1' >> /etc/sysctl.conf
sysctl net.inet.ip.forwarding=1
几项完成后,我们reboot
一下,reboot
成功后执行wg-quick up wg0
重启启动vpn。
此时FreeBSD便成功充当起网关功能了。这时候可以由客户端直接发起对某个IP地址的ping操作,比如ping 223.5.5.5
,如果成功的收到数据包,说明当前转发正确。
有些时候,本地服务器设置了指定的DNS服务器,这些DNS服务器并不对外提供解析服务(笔者所在的校园网即是如此),而连接到VPN后这些DNS解析就失效了,这时候可以使用手动配置公众的DNS来解决,比如:114.114.114.114
,223.5.5.5
等。
当客户端无法ping通外网地址时,则建议按以下步骤排查:
- 使用
netstat -rn
查看客户端路由 - 客户端使用
traceroute 目标ip
追踪数据包转发过程,看卡到哪个点了。如果VPN已经正确的进行了转发,则尝试换个目标IP。 - 如果数据转发根本就没到VPN服务端,那么按本文前面 客户端 中提到的方案来解决,这说明VPN本身就有问题,而没有在数据转发上。
- 查看服务端的隧道对应的网卡的数据包,看在进 行ping操作时,该网卡是否收到了请求数据包:
tcpdump -i <隧道对应的网卡> host 当前正在ping的IP
,比如:tcpdump -i tun5 host 101.132.60.229
,如果没有在ping的过程中,没有显示任何信息,说明VPN连接错误。 - 继续查看服务端连接外网的网卡是否接收到了隧道转发的数据包:
tcpdump -i <服务端连接外网的网卡> host 当前正在ping的IP
,比如:tcpdump -i vtnet0 host 101.132.60.229
。如果在ping的过程中没有任何显示,则问题在服务器转发功能上,确认服务器的转发功能是否开启。 - 确认nat规划:
ipnat -F
清空现有规则,ipnat -C
清空状态表,ipnat -f /etc/ipnat.rules
# 重新加载 NAT 规则 ,ipnat -l
查看当前nat情况。 - 确认nat规则:
map <可以上外网的网卡> 172.29.247.0/24 -> 0/32
其中所有的项都需要对应好。
一般而言,至此FreeBSD的转发功能就已经成功的完成了。在询问deepseek时,它给出的优选答案是通过设置防火墙的方法来设置nat,这个方法还需要考虑其它的端口的开放问题,而直接启用NAT会使这个过程变得更简单一些。
nginx
由于当前客户端与服务端之间的联通是OK的,客户端通过服务端发送除私有网段的所有请求。所以:在服务端常规的安装nginx后,可以将请求转发给客户端,而客户端向微信服务端发起请求时,也会正常的交给服务端来进行转发,此时微信服务端接收到的请求IP就固定为我们的服务端了。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。