0%

Iptables 转发规则

iptables 是 linux 中的防火墙模块,用于网络包的过滤与转发,一共可以分为五链四表。

image

iptbles 的匹配规则

一台主机中,可能会具有多个网卡,这些网卡会有不同的网络地址,但是所有网卡会共用同一个 iptables 匹配规则,也会共用宿主机的路由转发规则。

在路由选择中,只有最终判断为本地地址,如 127.0.0.1,本机的网卡地址的网络包才会被转发到 INPUT,而 docker 虚拟网卡上挂载的地址,则会被转发到虚拟网卡上,而不是直接进入INPUT 链中。注意,虚拟网卡上的转发是不经过 iptables 的,一个网络包到达 docker0 网卡后只会经过网卡的转发规则,直接被转发到对应的进程中。在进入 docker 进程后,使用的 iptables 规则和宿主机 iptables 规则是不相同的。

外部的流量在PREROUTING 链进行 DNAT 后,会根据目的网络地址来判断是否能够匹配本地路由地址,否则将会被转发到 FORWARD 链中。

内部的流量流出时,会直接根据目的地址判断是否为本地流量,如果是本地流量则不经过 OUTPUT 链,直接被转发到 INPUT 链中。这意味着 localhost 与 127.0.0.1 这两个地址是不会经过任何的DNAT 的。如果目的地址不是本机地址,那么会经过 OUTPUT 和 POSTROUTING链进行 DNAT 和 SNAT,这时候会再次通过路由表判断流量的地址转发到具体的网卡上。

不容易理解的地方

如果一个主机挂载了一个 docker0 网卡,那么 iptables 的转发规则就会稍微有些难以理解。

  • 172.0.0.x 网段不是本机的 ip 地址,因此在路由选择中不会转发给 INPUT,而是会转发给 FORWARD,然后经过 POSTROUTING,然后转发给 docker0 网卡,docker0 网卡会根据目的 ip 地址将网络包转发给虚拟机,注意此时的虚拟机收到网络包后已经不与宿主机使用同一个 iptables。
  • localhost 和 127.0.0.1 会经过路由选择后直接转发给 INPUT,不能够经过任何的 DNAT 修改,因此针对 localhost 的转发都是无效的。
  • DNAT 和 SNAT 是具有记忆性的。如果一次 tcp 中,宿主机给出流量做了 DNAT,那么收到这个 tcp 的回包时,将会自动进行反操作,因此 docker 中的虚拟容器只需要一次 DNAT 就可以访问外界流量了。

如何做 NAT 转换

1、首先需要开启端口转发
2、 sudo iptables -t nat -A PREROUTING -d x.x.x.x -p tcp -dport x -j DNAT --to-destinaton x.x.x.x:x

3、 sudo iptables -t nat -A POSTROUTING -d x.x.x.x -p tcp -dport x -j SNAT --to-source x.x.x.x

4、需要在 FORWARD 链中放行流量,例如

sudo iptables -A FORWARD -i eth0 -o eth0 -j ACCEPT

5、如果只需要在本主机进行端口转换,则应该使用重定向来代替

sudo iptables -t nat -A PREROUTING -d 127.0.0.1 -dport x -j REDIRECT to-ports x

利用 string模块完成应用层过滤

iptables 的 string 模块可以将网络包中的关键词进行匹配,从而实现禁止访问某些 url,禁止访问某些内容,或者禁止回复某些内容的效果。具体代码如下:

sudo iptables -t filter INPUT -m string --algo bm --string 'sf' -j DROP

但是,iptables 不能够确定字符串出现的位置,因此可能会出现误伤的情况。

k8s proxy 网络模型

iptables 模式

image

注意,所有的k8s 标记只分为两种,一种是需要转发的标记0x400,一种是需要丢弃的标记0x800。前者会最终在 POST 中被转发。

下面解释一下为什么会有需要丢弃的数据包。由于规则链的建立并不是原子性的,所以可能会出现一种情况,规则链建立一半时,已经有相关的数据包到达了——这是一个非常危险的情况,可能会导致一些未定义的行为。因此,在规则正在建立的过程中,需要将相关数据包标记为 0x800,并最终丢弃掉。也就是 0x800 是用于标识规则链是否完全建立的。

另外一种情况是按照用户的需求,对一些虚拟容器进行隔离操作。

iptables - clusterIP访问模式

这种访问模式一般是用于访问启动了多个实例的服务,并且需要进行负载均衡。客户端访问的链路可以表示为:

PREROUTING -> KUBE-SERVICE -> KUBE-SVC-SERV -> KUBE-SEP-POD

​ 所有外部流量都会被 KUBE-SERVICE 链截获,并检查是否有流量是流向某一clusterIP的,并将流量传递给对应的 KUBE-SVC-SERV。KUBE-SVC-SERV 根据设置的负载均衡算法,将流量转发给对应的 KUBE-SEP-POD 链,并最终在该链中进行 DNAT 处理。

iptables - NodePort 访问模式

这种方式通常用来访问单独启动的服务实例。客户端的访问链路可以表示为:

PREROUTING -> KUBE-SERVICE -> KUBE-NODEPORTS -> KUBE-SVC-XXX -> KUBE-SEP-XXX

单独启动的服务实例会单独注册在 KUBE-SERVICE链中,当clusterIP匹配完成后,才会进行 NodePort 规则匹配。使用 nodePort 方式来访问一个容器,其转发链路要更长,因此性能要稍差。

ipvs 模式

image
ipvs 因为继承了 lvs 模块,所以其实是更加简单的。只需要在流量入口处进行流量区分和标记就可以。客户端进行访问的链路可以表示为:

PREROUTING -> KUBE-SERVICES -> KUBE-CLUSTER-IP -> INPUT -> KUBE-FIREWALL -> POSTROUTING

或者为:

PREROUTING –> KUBE-SERVICES –> KUBE-NODE-PORT –> INPUT –> KUBE-FIREWALL –> POSTROUTING

  • KUBE-SERVICES 链负责劫持所有的流量,进行粗删选,根据主机所设置的网段号来初步判断流量目标是否是k8s 服务。
  • KUBE-CLUSTER-IP 负责第二次筛选,将网段正确的流量匹配ipset集合,如果通过集合,则标记为0x2000,如果失败则标记为0x4000。
  • 如果 KUBE-SERVICES 链没有通过筛选,则进入 KUBE-NODE-PORT 链中,判断是否是单独开启的服务实例。
  • INPUT 与 KUBE-FIREWALL:判断是否有标记为 0x4000 的流量,如果有,直接丢弃。
  • IPVS 与 POSTROUTING:将通过匹配的流量通过 nat 的方式转发到负载均衡算法计算后的地址。使用 nat 是因为可以很好地进行端口映射,这样会使得调度机制更加灵活,