概述
连接跟踪是很多网络服务和应用的基础。例如,kubernetes的service,ServiceMesh sidecar,4层负载均衡软件LVS/IPVS,容器网络,OpenvSwitch,OpenStack安全组等等都是依赖连接跟踪。
概念
顾名思义,连接跟踪就是跟踪或维护网络连接及其状态的。
上图中,Linux主机的IP地址是10.1.1.2,包含3个连接:
1、10.1.1.2:55667 <-> 10.2.2.2.:80:本地发起的连接,用于访问外部HTTP/TCP服务。
2、10.3.3.3:23456 <-> 10.3.3.2.:21:外部连接访问该节点的FTP/TCP服务。
3、10.1.1.2:33987 <-> 10.4.4.4.:53:本地发起的连接,用于访问外部DNS/UDP服务。
Conntrack模块负责发现和记录这些连接及其状态,包括:
- 提取数据包的五元组,区分数据包和相关连接。
- 为所有连接维护一个“数据库”(连接跟踪表),存储连接的创建时间、发送的数据包、发送的字节信息等。
- 回收陈旧的连接信息(GC)。
- 为上层功能服务,例如NAT。
但是请注意,“连接跟踪”中的术语“连接”不同于我们常在TCP/IP栈中所指的“连接”概念。简而言之:
- 在TCP/IP协议栈中,“连接”是4层概念。TCP是一种面向连接的协议,所有的报文都需要得到确认(ACK),并且有重传机制。UDP是一种无连接协议,不需要确认(ACK),也不需要重传。
- 在连接跟踪中,五元组唯一地定义数据流,数据流表示连接。
后面我们将看到即使ICMP(3层协议)也有连接数据。但并不是所有的协议都有连接跟踪。
我们所说的“连接”,在大多数情况下是指后者,即“连接跟踪”上下文中的“连接”。
理论
有了上述概念,让我们来分析连接跟踪的基本理论。要跟踪一个节点上所有连接的状态,我们需要:
1、读取(或过滤)通过该节点的每个数据包,并分析数据包。
2、设置一个“数据库”记录这些连接的状态。
3、根据数据包提取信息,及时更新连接状态到数据库(连接跟踪表)。
例如:
1、当读取一个TCP SYC数据包时,我们将确认这是在尝试新的连接,我们需要创建一个新的连接跟踪记录。
2、当得到一个属于现有连接的数据包时,我们需要更新连接跟踪的统计信息,例如发送的字节数、发送的数据包数、超时值等。
3、当超过30分钟没有包匹配conntrack条目时,我们考虑从数据库中删除该条目。
除了上述功能外,性能也是值得我们关注的,因为conntrack模块会对每一个数据包进行过滤和分析。性能是相当重要的,但它们超出了本文的范围。在稍后介绍内核conntrack实现时,我们将再次回到性能问题。
另外,最好有一些管理工具,方便使用conntrack。
Netfilter
Linux内核中的连接跟踪是在Netfilter框架中作为一个模块实现的。
Netfilter是内核内部的一个包操作和过滤框架。它在内核中提供了几个挂钩点hook,因此可以完成包读取、过滤和许多其他处理。
更清楚地说,钩子(hook)是一种在包的遍历路径上放置多个检查点的机制。当一个包到达一个钩子时,首先检查规则,检查结果可能是:
1、通过:对数据包不做任何处理,把它推回到原来的路径,让它通过。
2、修改:替换网络地址(NAT),然后推回到原来的路径,继续通过。
3、丢弃:例如,通过在这个检查(挂钩)点配置的防火墙规则。
注意,conntrack模块只提取连接信息并维护其数据库,它不会修改或丢弃数据包。修改和删除是由其他模块完成的,例如NAT。
Netfilter是Linux内核中最早的网络框架之一,它最初于1998年开发,2000年合并到内核2.4.x主线中。经过20多年的发展,它变得非常复杂,以至于在某些场景中导致性能下降,我们稍后将对此进行更多讨论。
进一步考虑
根据我们在上一节的讨论,连接跟踪的概念独立于Netfilter,而后者只是连接跟踪的一种实现。
换句话说,只要具备了钩子(hook)功能——能钩住经过系统的每一个数据包——我们就可以实现自己的连接跟踪。
Cilium是Kubernetes的一个云原生网络解决方案,实现了这样的连接跟踪和NAT机制。实现的基础:
1、基于BPF钩子来勾住数据包(BPF类似于Netfilter的Hook)。
2、基于BPF钩子实现一个全新的连接跟踪和NAT模块。内核版本4.19+。
因此,可以完全移除整个Netfilter模块,Cilium还可以为Kubernetes提供网络功能例如ClusterIP,NodePort,ExternalIP和负载均衡。
因为它的连接跟踪实现独立于Netfilter,Cilium的连接跟踪和NAT信息不是存储在系统的连接跟踪表和NAT表中。因此,通常使用的网络工具conntrack/netstats/ss/lsof无法查询到相关信息,你必须使用Cilium的方式,例如;
$ cilium bpf nat list
$ cilium bpf ct list global
而且,配置也是独立的,你需要指定Cilium的参数,例如命令行参数-bpf-ct-tcp-max。
我们澄清了conntrack的概念是独立于NAT模块的,但是出于性能考虑,代码可能是耦合的。例如,当对conntrack表执行GC时,它将有效地删除NAT表中的相关条目,而不是为NAT表维护一个单独的GC循环。
使用场景
我们来看看一些基于conntrack的具体网络应用程序/函数。
NAT网络地址转换
顾名思义,NAT转换(数据包)网络地址(IP+端口)。
上图中,假设节点IP 10.1.1.2可以被其他节点访问,但是内部网络地址范围在192.168.1.0/24的,外部访问不了,类似节点上容器地址。这表明:
1、数据包原地址在192.168.1.0/24范围内可以被发送,因为出口路由只依赖于目的IP。
2、但是,响应数据包(目的IP范围是在192.168.1.0/24内)就无法返回了,因为192.168.1.0/24在节点上是不能直接被外界访问的。
这种情况的一种解决方案就是:
1、在发送原地址范围在192.168.1.0/24的数据包时,将原地址IP替换成节点IP 10.1.1.2然后再发送出去。
2、接收到返回数据包,做相反的操作,并转发给原始发送者。
这只是NAT的底层工作机制。
容器网络默认是网桥模式,使用的就是上面的NAT机制来实现容器与外部节点的通信的。在节点内,每个Docker容器分配一个本地IP地址。该地址支持节点内容器之间的通信,但当与节点外的服务通信时,通信将被NAT处理。
NAT也可以替换源端口。这并不难理解:每个IP地址可以使用完整的端口范围(例如1~65535)。假设我们有两个连接:
- 192.168.1.2:3333 <–> NAT <–> 10.2.2.2:80
- 192.168.1.3:3333 <–> NAT <–> 10.2.2.2:80
如果NAT只将源IP地址替换为节点IP地址,则上述两个不同连接NAT处理好后是:- 10.1.1.2:3333 <–> 10.2.2.2:80
- 10.1.1.2:3333 <–> 10.2.2.2:80
两个连接混在一起无法区分,返回数据将不能做地址转换操作,因此,如果发生冲突,NAT也会替换源端口。
NAT还可以进一步分类:
- SNAT:原地址转换。
- DNAT:目的地址转换。
- Full NAT:原地址和目的地址都转换。
上图中使用的是SNAT转换。NAT依赖于连接跟踪,并且NAT是连接跟踪最重要使用场景。
4层负载均衡(L4LB)
让我们再进一步讨论,基于NAT模式的4层负载均衡。L4LB是根据数据包的L3+L4信息来转发流量的,例如src/dst ip(原地址/目的地址),src/dst端口。
VIP(虚地址):是实现4层负载均衡的一种方式:
- 具有不同真实IP的多个后端节点注册到相同的虚IP (VIP)
-
来自客户端的流量首先到达VIP,然后负载均衡到特定的后端ip。
如果L4LB采用NAT模式(VIP和Real ip之间),L4LB会对客户端和服务器之间的流量进行全NAT转换,数据流如下图所示:
有状态防火墙
有状态防火墙是相对于早期的无状态防火墙而言。很明显,要提供有状态防火墙,必须跟踪数据流和状态—这正是连接跟踪所做的事情。
我们来看一个具体例子:OpenStack安全组,主机防火墙解决方案。
OpenStack security group
安全组提供的是虚拟机级别的安全隔离,它是通过在虚拟机的主机网络设备上应用有状态防火墙规则来实现的。在当时,最成熟的有状态防火墙可能是Netfilter/iptables。
现在回到每个计算节点内部的网络拓扑:
每个计算节点用一个OVS桥接(br-int)连接(集成)它内部的所有虚拟机vm。如果只考虑网络连接,每个VM应该直接连接到br-int。但问题来了:
- OVS早期版本没有连接跟踪模块conntrack。
- Linux内核有conntrack模块,基于连接跟踪的防火墙工作在IP层(L3),通过iptables操作。
- OVS是L2模块,这意味着它不能利用L3模块,因此,无法对虚拟机的OVS(节点侧)网络设备设置防火墙。
OpenStack通过在每个VM和br-int之间插入一个Linux桥来解决这个问题,如上图所示。
Linux 网桥也是一个L2模块,所以不能使用iptables。但是,它有一种称为ebtables的L2过滤机制,可以跳转到iptables规则,从而使Netfilter/iptables可行。
但是这种解决方法很难看,并且会导致严重的性能问题。因此,在2016年,RedHat提出了一种OVS连接跟踪解决方案,可以在具有安全组功能的情况下关闭Linux桥接。