前言
以往都是用FRP来进行转发的,不过这种方案只能让外网通过公网IP特定端口访问内网,不能让内网的设备的端口映射到外网IP的相应端口上,使主动发出去的数据包也是经过公网IP的。最近想开一个游戏服就受到这个特性的折磨,于是想着看看能不能组个内网,然后建立NAT关系。
一开始是用PPTP的,然后在受限机上用iptables SNAT其IP到建立的虚拟网卡的IP上,此方案并不可行,貌似是不能把本地地址NAT到本地地址上。后来查了很多地方,还换用了WireGuard,终于找到了解决方案。
设备:都是Ubuntu Server 20.04
以下命令假设在root权限下执行,注意权限管理。
建立局域网
首先要用WireGuard建立一个局域网(子网:10.0.0.0/24),参考配置如下(上为服务器,下为客户端,均保存到/etc/wireguard/wg0.conf):
[Interface]
Address = 10.0.0.1/24
PrivateKey = <Key>
ListenPort = 51820
[Peer]
PublicKey = <Key>
AllowedIPs = 10.0.0.31
[Interface]
PrivateKey = <Key>
ListenPort = 21841
Address = 10.0.0.31/24
[Peer]
PublicKey = <Key>
AllowedIPs = 0.0.0.0/0
Endpoint = <IP>:51820
PersistentKeepalive = 25
需要执行的其他命令:
生成密钥(用于加密链接)
$ wg keygen > private_key
$ wg pubkey < private_key > public_key
启用设备
# wg-quick up wg0
转发服务器流量
我们假设服务器公网IP为A.B.C.D,需要转发的端口是tcp/E(A.B.C.E:E->受限设备:E)。
把服务器接收到的流量转发给受限设备
# iptables -t nat -A PREROUTING -p tcp -d A.B.C.D --dport E -j DNAT --to-destination 10.0.0.31:E
从受限设备发出的流量要改写为服务器IP(如果只需要让外网能访问内网,则不需要这一步)
# iptables -t nat -A POSTROUTING -p tcp -s 10.0.0.31 --sport E -j SNAT --to-source A.B.C.D:E
开启内核功能:端口转发
# sysctl -w net.ipv4.ip_forward=1
转发受限机流量
请注意:iptables -t nat -A POSTROUTING -p tcp –sport E -j SNAT –to-source 10.0.0.31:E 不可取。
要让受限机的流量经过服务器才能进行转发,但是我们不希望受限机访问外网的流量全部走服务器(服务器流量有限、宽带不如家用宽带高),所以我们的思路是:给需要转发的包打上标记,走路由表。下面命令走路由表100,打上标记0x64,注意防止冲突。假设受限机所在子网为192.168.1.0/24。
有0x64标记的数据包走100号路由表
# ip rule add fwmark 0x64 table 100
100号路由表的默认网关是服务器
# ip route add table 100 default via 10.0.0.1
给E端口发往子网外的包打上标记。由于没有用新的链,所以使用取反
# iptables -t mangle -A OUTPUT -p tcp ! -d 192.168.1.0/24 --sport E -j MARK --set-mark 0x64
给送往服务器的包写上正确的IP
# iptables -t nat -A POSTROUTING -o wg0 -p tcp --sport E -j SNAT --to-source 10.0.0.31:E
尾声
至此,端口转发已配置完毕。有关配置持久化的问题本文不探讨。
使用UDP协议的话,在回应阶段可能会对应错误端口,可以针对其关闭CONNTRACK解决。
番外
什么?!还有nftables这种好东西(ufw和firewalld不如就不写了吧……)?!
参考配置:
服务器:
table ip proxy {
define lip = A.B.C.D
define cip = 10.0.0.31
# 受限机和服务器同端口
define tcpports = {E, ...}
define udpports = {E, ...}
# 端口映射
chain prerouting {
type filter hook prerouting priority 0; policy accept;
# type nat然后dnat to $cip也行
tcp dport $tcpports ip daddr set $cip
udp dport $udpports ip daddr set $cip
}
chain postrouting {
type nat hook postrouting priority 0; policy accept;
tcp sport $tcpports ip saddr set $lip
udp sport $udpports ip saddr set $lip
}
}
客户端:
table ip proxy {
define pip = 10.0.0.31
define lnet = 192.168.1.0/24
define rmark = 0x64
define odev = wg0
define tcpports = {E, ...}
define udpports = {E, ...}
# 打上标记,走路由表
chain output {
type route hook output priority 0; policy accept;
ip daddr $lnet return
tcp sport $tcpports mark set $rmark
udp sport $udpports mark set $rmark
}
# 出网卡时改写为正确的IP
chain postrouting {
type filter hook postrouting priority 0; policy accept;
meta oifname $odev ip saddr set $pip
}
}