用UFW来实现端口转发,理解PREROUTING和POSTROUTING链

我不用iptables转发是因为iptables太复杂了,而且现在很多服务器都压根没有iptables-save,而且有些服务器配置生效还不给起作用,网上查了ufw转发,虽然很多命令都是没啥问题的,但是为什么都不顺带提一下要修改 DEFAULT_FORWARD_POLICY,这个呢?因为默认转发策略是 DROP 爬了多少坑,顺便测下 -A POSTROUTING -j MASQUERADE 具体生效方式。

(English version translate by GPT-3.5)

光速结论

如果对基本的vim都比较熟练的话,就直接贴命令了,10秒讲完的就不长篇大论了

  1. 首先编辑 /etc/default/ufw ,并找到 18行左右的 DEFAULT_FORWARD_POLICY,将值改为true

    1
    vi /etc/default/ufw
  2. 设置 sysctl.confnet.ipv4.ip_forward=1 注释去掉,并确保值为1,然后执行 sysctl -p 生效

    1
    2
    vi /etc/sysctl.conf
    sysctl -p
  3. 编辑 /etc/ufw/before.rules,并在 *filter 上方,一般在最顶部,添加如下命令,33810 端口是坚听端口, 200.120.130.140:33800 是转发端口,如果要支持UDP的话就再复制一份 -A PREROUTING xxxx 然后将 tcp改成udp即可

    1
    2
    3
    4
    5
    *nat
    :POSTROUTING ACCEPT [0:0]
    -A PREROUTING -p tcp --dport 监听端口 -j DNAT --to-destination 目标转发IP地址:目标转发端口
    -A POSTROUTING -j MASQUERADE
    COMMIT
  4. 重启ufw即可

    1
    ufw reload

单步结论

假设我现在有一台服务器A是 200.130.140.160,另一台服务器B是 200.120.130.140,那么我想通过服务器A的33810端口,能访问到服务器B的33800端口,那么我可以这么输入

  1. 首先编辑 /etc/default/ufw

    1
    vi /etc/default/ufw
  2. 设置 允许转发

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    # Set the default output policy to ACCEPT, DROP, or REJECT. Please note that if
    # you change this you will most likely want to adjust your rules.
    DEFAULT_OUTPUT_POLICY="ACCEPT"

    # Set the default forward policy to ACCEPT, DROP or REJECT. Please note that
    # if you change this you will most likely want to adjust your rules
    DEFAULT_FORWARD_POLICY="ACCEPT" // 找到这一行,大概在18行这里,将其改成 ACCEPT

    # Set the default application policy to ACCEPT, DROP, REJECT or SKIP. Please
    # note that setting this to ACCEPT may be a security risk. See 'man ufw' for
    # details
    DEFAULT_APPLICATION_POLICY="SKIP"
  3. 设置 sysctl.conf

    1
    vi /etc/sysctl.conf
  4. net.ipv4.ip_forward=1 注释去掉,并确保值为1

    1
    2
    # Uncomment the next line to enable packet forwarding for IPv4
    net.ipv4.ip_forward=1 // 进去后大概在第28行,将注释解除,或者在这个文件底部增加一条这个记录
  5. 运行 sysctl -p 生效

    1
    sysctl -p

    运行后,确保输出的内容中包含 net.ipv4.ip_forward = 1 这一行,就想下面一样

    1
    2
    root@test-server-1:~# sysctl -p
    net.ipv4.ip_forward = 1
  6. 编辑 /etc/ufw/before.rules

    1
    vi /etc/ufw/before.rules
  7. *filter 之前添加如下命令

    1
    2
    3
    4
    5
    *nat
    :POSTROUTING ACCEPT [0:0]
    -A PREROUTING -p tcp --dport 33810 -j DNAT --to-destination 200.120.130.140:33800
    -A POSTROUTING -j MASQUERADE
    COMMIT

    修改后的文件看起来就像这样

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    // 这里是这个文件的一开头
    #
    # rules.before
    #
    # Rules that should be run before the ufw command line added rules. Custom
    # rules should be added to one of these chains:
    # ufw-before-input
    # ufw-before-output
    # ufw-before-forward
    #
    // 添加的内容开始 #########################################
    *nat
    :POSTROUTING ACCEPT [0:0]
    -A PREROUTING -p tcp --dport 33810 -j DNAT --to-destination 200.120.130.140:33800
    -A POSTROUTING -j MASQUERADE
    COMMIT
    // 添加的内容结束 #########################################

    // 这里开始,是文件原来的内容
    # Don't delete these required lines, otherwise there will be errors
    *filter
    :ufw-before-input - [0:0]
    :ufw-before-output - [0:0]
    :ufw-before-forward - [0:0]
    :ufw-not-local - [0:0]
    # End required lines
  8. 重启ufw

    1
    ufw reload

ufw重启注意!!

重启ufw可能会导致iptables规则重复添加,ufw reload的时候不会删除iptables原有的规则,就会导致iptables有2条一摸一样的nat规则,如果这里出现修改,可能就会导致不生效,此时可以用下面命令进行删除,重复几条删几次

最好不要用 iptables -F,否则可能其他程序,例如Docker,会立马失联

1
iptables -t nat -D PREROUTING -p tcp --dport 33810 -j DNAT --to-destination 200.120.130.140:33800

理解NAT PREROUTING POSTROUTING

在这之前

在理解prerouting链之前,得先知道IP数据包是什么样子的,虽然这个表格并没有完整表达数据包完整结构,但是数据包大致长这样

数据链路层(MAC层)
源MAC地址(32) 目标MAC地址(32) 网络层(IP) 数据
源IP地址(32) 目标IP地址(32) 协议类型(8) 传输层(TCP/UDP) 数据
源端口号(16) 目标端口号(16) 应用层(例如 HTTP数据) 数据
报文类型(8) 消息长度(16) 消息体

可以看到数据包是一层层下来的,最底层绿色的,也就是我们应用的数据,例如http header这些信息,而往上会封装一层IP信息,这里表明这个数据包发旺哪一个IP,理论上,只要没有遇到NAT Network Address Translation 网络地址转换协议,那么这个源IP和目标IP都不会变化。

当然实际数据没有没有外面包着里面的说法,它实际是从左到右像如下顺序拼接的

源MAC地址(32) 目标MAC地址(32) 源IP地址(32) 目标IP地址(32) 协议类型(8) 源端口号(16) 目标端口号(16) 报文类型(8) 消息长度(16) 消息体

什么是PREROUTING和POSTROUTING

这里可以将其拆分成 pre routing和post routing,routing就是路由嘛(这里别理解成日常例会,就像memory是内存不是记忆。。。)pre表示pre-xxxx,即有在xxx之前,这里就可以理解成,在路由表前,而post,即在路由表后,每一个计算机里面都有自己的路由表(route print),所以这里的prerouting就表示,数据包从网卡进来,到路由表前,postrouting就是数据包在路由表判断后,即将离开计算机从网卡出去的数据,而转发就是在这2个地方做了文章。

详细解释

我们这里拿转发来描述,我(用户)想请求 200.130.140.160:33810 端口,并希望通过ufw转发到我另一台服务器 200.120.130.140:33810,从而实现功能(很简单的需求)

  1. 首先我们数据包从本机出发,本机肯定是访问服务器A的端口,所以数据包如下(我们假设本机连接互联网,有公网IP 1.0.0.1,这里就不纠结是否经过路由器,是否内网IP问题了)

    网络层(IP) 数据
    1.0.0.1 200.130.140.160 TCP 传输层(TCP/UDP) 数据
    31212 33810 应用层(例如 HTTP数据)
    数据
  2. 数据包进入服务器A的ETH0网卡(此时数据包还没到服务器A路由表),此时PREROUTING规则生效,生效规则是当33800端口进来的TCP数据,将目标IP改成200.120.130.140,目标端口改成33810,此时数据包就变成如下样子

    网络层(IP) 数据
    1.0.0.1 200.120.130.140 TCP 传输层(TCP/UDP) 数据
    31212 33800 应用层(例如 HTTP数据)
    数据
  3. 数据包进入服务器A路由表,此时路由表判断该数据是发往 200.120.130.140 的,因此数据即将从服务器A的网卡出去,在出去前,POSTROUTING生效了,POSTROUTING发现自己需要对IP进行伪装,然后它会将数据包进行修改,改成如下样子,将原始IP改为自己的IP,同时在自己的NAT表中记录下 稍后发给自己这个数据包,要返回给真实的 1.0.0.1,同时服务器A会开启一个随机端口51241

    网络层(IP) 数据
    200.130.140.160 200.120.130.140 TCP 传输层(TCP/UDP) 数据
    51241 33800 应用层(例如 HTTP数据)
    数据
  4. 数据包正式从服务器A出发前往服务器B,服务器B进行处理后,准备进行数据回发,此时IP和端口如下,源端口和目标端口调换,数据包回程

    网络层(IP) 数据
    200.120.130.140 200.130.140.160 TCP 传输层(TCP/UDP) 数据
    33800 51241 应用层(例如 HTTP数据)
    数据
  5. 数据包回到服务器A,进入PREROUTING规则,此时服务器A查询NAT规则,发现这个数据包在POSTROUTING进行了伪装,需要进行还原其真实IP 1.0.0.1 ,此时51241端口完成使命,端口关闭

    网络层(IP) 数据
    200.120.130.140 1.0.0.1 TCP 传输层(TCP/UDP) 数据
    33800 51241(Closed) 应用层(例如 HTTP数据)
    数据
  6. 数据包经过路由表,在POSTROUTING也就是即将离开网卡之前,它发现之前IP地址经过伪装,要进行反伪装,这下所有的IP都还原成原来的样子了

    网络层(IP) 数据
    200.130.140.160 1.0.0.1 TCP 传输层(TCP/UDP) 数据
    33810 31212 应用层(例如 HTTP数据)
    数据
  7. 数据回到用户这边,成功得到了想要的数据,这样,应该能看懂了吧。