Press "Enter" to skip to content

利用WireGuard进行端口转发

前言

以往都是用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
    }
}