万物之中, 希望至美.

iptables 端口转发

2020.06.09

iptables 工作在 OSI 七层模型的二、三、四层,操作的是 Linux 内核中的 netfilter。

netfilter的工作流程大致就是 “当一个数据包到达 Linux 的网络接口(网卡)的时候,对这个数据包进行处理”,而 iptables 可以改变或者控制这个数据包的处理过程。

netfilter 内部分为三个表:filter、nat、mangle,每个表都有不同的操作链(Chains):

  • 在 nat(网络地址翻译表,Network Address Translation)中,也就是用以实现地址转换和端口转发功能的这个表,定义了 PREROUTING、POSTROUTING、OUTPUT 这三个链,后续会对这三个链进行详细的说明;
  • 在 filter(过滤)表中,定义了 INPUT、OUTPUT 和 FORWARD 这三个 Chain,即对数据包的输入、输出和转发三个过滤链,对 filter 表的操作与控制也是我们实现防火墙功能的一个重要手段;
  • 而 netfilter 的 mangle 表则是一个自定义表,里面包括了上面的所说的 filter 以及 nat 表中的各个 chains,它可以让我们进行一些自定义操作,同时,这个 mangle 表中的 chains 在 netfilter 对数据包的处理流程中,有着更高的优先级。

iptables 工作流程

iptables-chains

PREROUTING(DNAT)

PREROUTING 这个 Chain 在整个处理流程的最前面,当一个数据包到达 Linux 的网络接口(网卡)的时候,先经过 mangle 表的 PREROUTING,然后是 nat 表的PREROUTING ,从这个 chain 的名字可以看出,这个 chain 是在路由之前要过的,即预路由(pre-routing)。

那么为什么要在路由之前过呢?

我们可以看上面这个流程图,图中有一个菱形的部分叫 ROUTING,这个 ROUTING 就是 Linux 系统的 Route box,即路由系统,它可以实现一些很高深的功能,如:策略路由之类高级特性,这里我们不多做解释,因为我也没搞懂(逃

单说 PREROUTING 链,在这个链中,我们对数据包的操作是 DNAT,即改变目的地址 和/或 端口,通常用于端口转发,也就是说一个数据包进来的时候,我们要改变它的目的地址 和/或 端口,这里我们可以先想想,如果一个数据包在改变路由地址之前就被放进了 Route box,让系统选好路之后再改变目的地址,那么选路就可能是错的,或者说是毫无意义了,所以,这个 PREROUTING 一定要在放进 Route box 之前做。

案例:我们有一台主机的公网 IP 为 223.1.1.1/24,而系统中 eth0 网卡在内网中的 IP 为192.168.1.1/24,与此同时,我们的内网中有一台 Web 服务器,IP 地址为 192.168.1.2/24,那么我们如何通过外网用户通过 223.1.1.1/24 这个公网 IP 来访问我们的 Web 服务器呢?

此时,我们就可以在这个 PERROUTING 链上定义一个规则,把访问 223.1.1.1:80 的用户的目的 IP 给改变一下,改编为 192.168.1.2:80,这样就实现了外网用户对内网服务器的访问了,当然,这个端口是比较灵活的,我们定义任何一个端口的转发,不一定非得是 80 → 80。

FORWARD

接着往下走,这个包已经通过了两个 PREROUTING 链了,这时候出现了一个分支转折的地方,也就是图中下方的那个菱形 FORWARD,即转发,这里有一个对目的地址的判断(这里同样说明了 PREROUTING 一定要在最先,不仅要在 route box 之前,甚至是这个对目的地址的判断之前,因为我们可能将一个去往 xxx.xxx.xxx.xxx 的 IP 地址转到我们自己的 IP 规则里,所以 PREROUTING 是最先处理这个数据包的 Chain)。

如果包的目的地址是本机 IP 的话,那么数据包向上,走入 INPUT 链处理,然后进入 LOCAL PROCESS,如果非本地,那么就进入 FORWARD 链进行过滤。

FORWARD 处理流程:

  1. 当 Linux 系统收到了一个目的 IP 地址不是本机的网络数据包,Linux 系统会把这个包丢弃,因为默认情况下,Linux 系统的三层包转发功能是关闭的,如果要让 Linux 实现转发,则需要打开这个转发功能,通过改变系统参数来实现,命令如下:

    sysctl net.ipv4.ip_forward = 1
    # 或者
    echo "1" > /proc/sys/net/ipv4/ip_forward
    
  2. 此时,我们就让 Linux 系统允许转发网络包了,如果这个网络数据包的目的 IP 地址不是本机,那么,它将进入到 FORWARD 链,在 FORWARD 链里面,我们可以详细的规则来处理这个包,如:是否允许它通过、对这个包的方向流程进行一些改变等。这里处理这个包的链的流程为:先是 mangle 表的 FORWARD ,然后是 filter 表的 FORWARD。

POSTROUTING(SNAT)

假设,一个数据包被我们的过滤规则给放过去了,即 ACCEPT 了,它将进入到 POSTROUTING 部分,同样,我们从这个链的名字可以看出,POSTROUTING 链应该是最后一个链,即一个数据包被发送出 Linux 系统的最后一个环节,这也是及其重要的一个环节,这个时候 Linux 系统已经完成了对这个包的路由(选路工作),已经找到了合适的网络接口发送出这个包了,在这个链里面我们进行重要的操作,即被称为 SNAT 的一个动作:修改源 IP 地址

那么问题来了,为什么要修改源 IP 地址呢?

最常见的应该就是上面说的那个案例,在一个内网中的多台机器共享一个或几个公网 IP 来访问外网,因为我们的内网地址是私有的,加入让 Linux 系统给路由出去,源地址不变的情况下,这个包应该是可以到达目的地的,但是却返回不回来,因为 Internet 上的大部分路由节点不会转发私有地址的数据包,也就是说,在不使用合法 IP 的情况下,我们的数据包是有去无回的。

这时,可能你就会问了:“既然这样,我们就不用私有 IP 了,我自己分配自己的合法 IP 地址不行吗?那样数据包不就可以回来了吗?”我只能说你这就 too young too simple 了,IP 地址是 ICANN 来分配的,你的数据包或许能发到目的地,但是回来的时候,人家可不会转到你那里,Internet 上的路由器中的路由表会把这个返回的数据包送到那个合法的获得了 IP 地址的地方去,你同样收不到,而且这种行为有可能会被定义为一种 IP 欺骗,很多设备会把这样的包在接入端就给过滤掉,可能都到不了要访问的服务器。

那么 Linux 系统如何做 SNAT 呢?比如一个内网的 192.168.1.1 的 PC 访问 202.2.2.2 的一个 Web 服务器,内网接口在收到这个包之后把原来 PC 的 IP 地址 192.168.1.1 改为 223.1.1.1 的合法地址然后发送出去,同时在自己的 ip_conntrack 表里面做一个记录,记录是内网的哪个 IP 的哪个端口访问了这个 Web 服务器的地址,自己把它的源地址改成多少了,端口改成多少了,以便这个 Web 服务器返回数据包的时候将它准确的发回给请求方,即地址 192.168.1.1 的这台 PC。

端口转发

启用转发

sysctl net.ipv4.ip_forward = 1
# 或者
echo "1" > /proc/sys/net/ipv4/ip_forward

配置 PREROUTING

把访问 192.168.1.206 的 80 端口的流量转发到 192.168.1.197 的 80 端口:

iptables -t nat -A PREROUTING -d 192.168.1.206/32 -p tcp -m tcp --dport 80 -j DNAT --to-destination 192.168.1.197:80

配置 FORWARD

设置 filter 表的 FORWARD 规则

iptables -I FORWARD -d 192.168.1.197/32 --dport 80 -j ACCEPT

配置 POSTROUTING

把访问 192.168.1.197 的 80 端口返回的数据再通过 SNAT 的方式将数据报文的源地址改为 192.168.1.206 发送出去:

iptables -t nat -A POSTROUTING -d 192.168.1.197/32 -p tcp -m tcp --dport 80 -j SNAT --to-source 192.168.1.206

参考链接

  1. 通过iptables实现端口转发和内网共享上网
comments powered by Disqus