☰
Linux连接跟踪流量统计实战:从conntrack精确追踪每一条连接的带宽消耗
2026/10/24 0:52:07 网站建设 项目流程

前一阵子帮某客户调一台NAT网关,业务方反馈说有个内网IP流量异常,几乎占满了出口带宽。我用iptables规则加计数器排查,结果不理想——规则是按网段写的,计数器只能看到整个网段的累计量,根本定位不到具体连接。当时我直接在网关上开启了连接跟踪的字节计数,几分钟内就抓到了具体连接,看清是某个服务在持续向外同步数据。这件事让我决定把“连接跟踪流量统计”这套方法系统地写出来。

连接跟踪(connection tracking)是Linux内核netfilter框架里负责维护连接状态的核心机制,NAT、状态防火墙都依赖它。而“连接跟踪流量统计”就是利用这条现成的连接信息,按连接、按IP、按端口实时统计包数和字节数。它特别适合NAT网关、防火墙、云服务器这类场景,能补上ifstat、iptables计数器、抓包工具在“按连接维度统计流量”上的短板。这篇文章会覆盖原理、命令、脚本实现和生产环境里的坑,适合Linux运维、网络工程师、安全同学参考。

1. 为什么先想到连接跟踪?

1.1 传统工具的短板

在正规流量统计工具还没部署到位的时候,大家通常会用下面这几种方式凑合,但它们各自的短板很明显:

  • ifstat或/proc/net/dev:只能看整个网卡的进出流量总和,无法回答“哪个IP流量大”。
  • iptables计数器:按规则匹配累加,规则写粗了统计就粗,写细了规则数量爆炸,而且规则顺序会影响统计结果。
  • tcpdump抓包:要另起进程抓全量流量,高带宽下CPU扛不住,通常只能抓样本或抓固定时间窗口。
  • ss/netstat:只能看本机socket,经过网关转发的那部分流量它完全看不到。
  • NetFlow/sFlow:需要网卡或交换机支持,纯软件环境往往没有现成数据源。

这些工具的另一个共性问题:它们都游离在“连接”这个概念之外。可实际上,真正需要判断“谁在消耗带宽”的时候,最自然的单位就是一条连接(五元组)。连接是谁建立的、源地址是什么、访问了哪个端口、持续多长时间,这些信息只有连接跟踪层才有。

1.2 连接跟踪能补上哪块拼图

连接跟踪最有价值的地方在于:它本来就在数据路径上。每个经过网关的数据包,内核都会在连接跟踪模块里查表、更新状态。如果我们把计数开关打开,它就顺手把这一个方向上的包数和字节数累加到对应连接条目里。你做统计的动作,只是去读一条已经存在的数据,而不是像抓包那样额外复制一份数据流。

还有一点是“无状态匹配”做不到的:iptables计数器是按规则匹配的,一个连接如果命中了多条规则,你能看到每条规则的计数,但很难还原“这一条连接总共跑了多少流量”。连接跟踪则是一连接一条目,无论这个连接匹配了多少条规则,它的累计字节数就集中放在一个地方,查起来非常清爽。

所以我的习惯是:只要机器上有Linux内核,优先从conntrack拿流量数据。不用装额外的agent,也不用担心采样误差,拿到的就是内核逐一数过的精确计数。它不能替代流量分析平台,但作为“应急定位”和“轻量统计”的工具,性价比极高。

2. 连接跟踪的计数机制:从钩子到字节计数

2.1 netfilter钩子与连接状态的建立

要理解连接跟踪怎么统计流量,先得知道它在协议栈里站在哪。Linux网络协议栈在数据包路径上安排了几个hook点,分别是PREROUTING、INPUT、FORWARD、OUTPUT、POSTROUTING。连接跟踪模块注册在PREROUTING和OUTPUT这两个位置,也就是说,不管是外部进来的包、本机发出的包,还是经过转发的包,都会先经过连接跟踪的处理。

一个数据包到达时,连接跟踪模块会先查连接表。如果这个包属于一条已有连接,就直接更新对应条目的状态和计数;如果找不到匹配项,就判断是否新建一条连接。这里有个容易误解的点:一条TCP连接要三次握手才能“建立”,但连接跟踪里只要看到第一个SYN包,就会先创建一个状态为NEW的条目,等收到应答报文后,状态会更新为ESTABLISHED。所以你在连接表里看到的ESTABLISHED条目,代表的不是应用层握手完成,而是“两个方向都见过报文了”。

状态机里有几个关键状态:NEW表示只见过单方向报文,ESTABLISHED表示两个方向都见过,RELATED表示与某条已有连接关联的子连接(典型的如FTP数据连接),INVALID表示无法识别的报文。做流量统计时,我们通常只关注ESTABLISHED和NEW这两类,RELATED的连接一般量不大,但遇到FTP等协议时也不能忽略。

2.2 连接条目的双向计数与开启方式

连接跟踪条目里记录的是两个方向各自的信息,每个方向都包含源地址、目的地址、源端口、目的端口,以及包数(packets)和字节数(bytes)。之所以强调“每个方向”,是因为在很多场景下,连接的上下行流量差异非常大。比如一台服务器向外同步数据,原始方向(客户端到服务器)字节数可能很小,回应方向(服务器到客户端)字节数却很大;如果你只统计了单方向,就会得出完全错误的结论。

在部分内核版本和发行版里,字节计数不是默认开启的,需要通过sysctl打开:

sysctl -w net.netfilter.nf_conntrack_acct=1

如果想永久生效,在/etc/sysctl.conf或/etc/sysctl.d/下的配置文件里加一行:

net.netfilter.nf_conntrack_acct = 1

有个细节需要注意:这个开关开启后,对已经存在于连接表里的旧条目可能不会立即生效。因为计数是在报文更新连接条目时累加的,旧条目在创建时没有初始化计数器字段,新报文到达后内核才会补上计数。保险起见,建议开启后清空一次连接表,或者等待旧连接自然超时:

conntrack -F

清空连接表会让所有现网连接重新建立状态,代价是连接跟踪表会短时重建,NAT映射也会重新学习。生产环境操作要挑低峰期,而且确保防火墙规则不是依赖conntrack状态来放行的,否则清表瞬间可能误伤正在进行的连接。

2.3 超时、哈希表与内存占用:影响统计的前提参数

在做流量统计之前,最好先摸清连接跟踪的几个基础参数,因为它们直接决定了你“能看到多长时间的流量”。

TCP连接的Establish状态超时时间,默认是432000秒,也就是5天。UDP默认只有30秒,ICMP是30秒。这意味着一条TCP长连接如果持续存在,它的计数器会一直累加,直到连接关闭或超时;而UDP“连接”只要30秒没有报文,条目就会消失,计数器归零。理解了这个机制,你就知道为什么“瞬时流量”和“累计流量”要从不同时间尺度去统计。

连接表大小由nf_conntrack_max控制,哈希桶数量由nf_conntrack_buckets控制。粗略估算,每个连接条目大概占用300字节左右内存,再加上哈希表本身的开销。调大连接表前要算清楚内存账:

# 查看当前值和统计信息 sysctl net.netfilter.nf_conntrack_max sysctl net.netfilter.nf_conntrack_buckets conntrack -S

如果连接数超过nf_conntrack_max,新连接就会被丢弃,表现为业务方报“网络不通”,但你ping网关可能又是通的。做流量统计的人如果忽略了这层,可能会看到某个IP的累计字节数莫名其妙暴涨或瞬间消失——那不是流量真的消失了,而是连接条目因为表满被强制回收了。这个问题我在第4部分还会重点展开。

3. 命令行实时读取连接流量

3.1 字段解读

读取连接跟踪数据有两条路径:一条是直接读/proc/net/nf_conntrack文本文件,另一条是用conntrack工具通过netlink读取。前者简单直接,后者功能更强,还支持事件流模式。

先看一个典型的连接条目长什么样。假设我用conntrack -L -o extended查看,会得到类似这样的行:

ipv4 2 tcp 6 431990 ESTABLISHED src=192.168.1.10 dst=192.0.2.5 sport=42345 dport=443 packets=1024 bytes=1048576 src=192.0.2.5 dst=192.168.1.10 sport=443 dport=42345 packets=2048 bytes=2097152 [ASSURED] mark=0 zone=0 use=2

逐个字段拆开看:

  • ipv4 2:地址族,2表示IPv4。
  • tcp 6:协议名和协议号。
  • 431990:条目剩余生命周期(秒),会不断减少。
  • ESTABLISHED:连接状态。
  • 第一组src/dst/sport/dport:原始方向的五元组。
  • 第一组packets/bytes:原始方向的包数和字节数。
  • 第二组src/dst/sport/dport:应答方向(replies)的五元组。注意这里src是原始方向的目的IP,dst是原始方向的源IP,端口也相应反转。
  • 第二组packets/bytes:应答方向的包数和字节数。
  • [ASSURED]:双向都有流量的标记。
  • mark/zone/use:防火墙标记、zone区域和引用计数,一般统计用不到。

从/proc/net/nf_conntrack读取的内容格式基本一致,只是每行开头没有“ipv4 2”这种格式化前缀,而是直接以协议信息开头。这个文件内容非常直接,适合写脚本快速解析。

3.2 用Python脚本统计每个内网IP的实时流量

实际工作中,我最常做的统计维度是“按内网IP聚合”,也就是不管这个IP访问了多少个外部地址,把它的所有连接字节数加起来,看谁占的带宽最多。下面这个Python脚本就是干这个事的:

#!/usr/bin/env python3 import collections import sys # 统计每个内网IP当前的累计流量,并输出Top N def read_conntrack(path="/proc/net/nf_conntrack"): stats = collections.Counter() try: with open(path, "r") as f: for line in f: if not line.startswith("ipv4"): continue parts = line.split() # 从第6个字段起,是src=... dst=... 这种形式 src_ip = None sport = None dport = None packets = None bytes_ = None for field in parts[6:]: if field.startswith("src="): src_ip = field.split("=", 1)[1] elif field.startswith("sport="): sport = int(field.split("=", 1)[1]) elif field.startswith("dport="): dport = int(field.split("=", 1)[1]) elif field.startswith("packets="): if packets is None: packets = int(field.split("=", 1)[1]) elif field.startswith("bytes="): if bytes_ is None: bytes_ = int(field.split("=", 1)[1]) if src_ip and bytes_ is not None: stats[src_ip] += bytes_ except FileNotFoundError: print("未找到连接跟踪表,请确认内核已加载 nf_conntrack 模块") sys.exit(1) return stats if __name__ == "__main__": top = int(sys.argv[1]) if len(sys.argv) > 1 else 10 for ip, total in read_conntrack().most_common(top): print(f"{ip}\t{total / 1024 / 1024:.1f} MB")

这段脚本的逻辑很简单:逐行解析,遇到src=就取源IP,遇到第一组bytes=就取原始方向的字节数。注意我用了“如果packets还没赋值就取第一个”的小技巧,这样不会把应答方向的计数也累加进去。实际生产中,如果还要分TCP/UDP、按端口聚合,可以在解析时把协议字段和端口也存下来,逻辑是一样的。

脚本运行后,输出类似这样:

192.168.1.10 428.5 MB 192.168.1.63 102.1 MB 192.168.1.22 12.3 MB

如果你只是想快速看一眼,不想写脚本,可以直接用命令行组合:

conntrack -L -o extended | awk '{for(i=1;i<=NF;i++) if($i ~ /^src=/){split($i,a,"="); s[a[2]]+=1}} END{for(ip in s) print ip, s[ip]}' | sort -k2 -rn | head

当然这种awk写法统计的是条目数,不是字节数,做粗筛可以用,精确定位还是建议用Python。

3.3 事件流方式:conntrack -E 做增量统计

conntrack -L是“读快照”,每次执行都读取当前连接表的全部内容。如果你要持续观察新连接的建立和销毁,或者想做增量统计,更好的方式是conntrack -E事件流模式:

conntrack -E -o extended

这个命令会阻塞,持续输出内核推送的连接跟踪事件。新连接创建(NEW)、状态更新(UPDATE)、连接销毁(DESTROY)都会产生事件。每行事件里同样带有packets和bytes信息,可以在应用层按事件变化量做累加。

事件流模式的一个坑是:netlink socket缓冲区有限。如果新连接事件产生的速度超过了用户态程序的消费速度,内核会静默丢弃部分事件,你的统计就会出现缺口。生产环境长时间跑conntrack -E,建议显式调大缓冲区:

conntrack -E -o extended -b 52428800

不同版本对参数的支持略有差异,可以用conntrack -E -h查看本机的参数。我个人建议:如果只是应急观察几十分钟,用-E没问题;如果要长期作为统计数据源,要么做断线重连和完整性校验,要么改成定时读快照做差量,后者更容易保持在生产环境稳定运行。

4. 生产环境中的三个大坑:NAT、分片和表溢出

4.1 NAT场景下原始方向与应答方向的地址变换

连接跟踪本身就是NAT的基础,一个数据包做地址转换时,转换前后的信息都记录在同一个条目里。这就带来一个统计上的坑:如果你在NAT网关上读连接表,看到的“原始方向源地址”是转换前的内网地址,而“应答方向的目的地址”也是内网地址,两个方向看起来好像都指向内网IP。

举个例子。内网192.168.1.10访问公网192.0.2.5,网关做SNAT后,数据包源地址变成了网关公网IP。但在连接跟踪条目里,第一组还是记录src=192.168.1.10 dst=192.0.2.5,第二组记录src=192.0.2.5 dst=192.168.1.10。所以在统计“内网IP流量”时,按原始方向的src取就一定没错。

反过来,如果你只统计了NAT转换后的地址,也就是网关公网IP,就会把全内网的流量全算到网关头上,什么都定位不出来。另外,如果网关同时做DNAT,把公网IP的某个端口映射到内网服务器,内网服务器的IP反而会出现在原始方向的dst字段上。统计前先画清楚NAT拓扑,比写脚本更重要。

4.2 分片与特殊协议对计数的干扰

IP分片是连接跟踪的老问题。当一个数据包被分片后,只有第一个分片带有完整传输层头,后续分片可能无法被正常匹配到连接条目。内核通过nf_defrag_ipv4模块做分片重组,让连接跟踪和NAT能够正确处理分片报文。如果这个模块没加载,分片报文可能被当作INVALID丢弃,而连接计数也会偏低。

生产环境看流量统计时,如果发现某个IP的字节数和实际业务流量对不上,尤其大量使用UDP大包的时候,排查思路之一就是看是否涉及分片重组。检查模块是否加载:

lsmod | grep nf_defrag_ipv4

特殊协议也需要额外照顾。比如GRE隧道、PPTP等,连接跟踪需要对应模块才能正确识别。PPTP还涉及控制连接和数据连接的关联,没有加载nf_conntrack_pptp的话,数据连接可能完全不被跟踪。这类协议的流量统计,准确率天然不如TCP/UDP,如果业务里有隧道,建议不要只看连接跟踪,配合端口镜像一起判断。

4.3 nf_conntrack表满导致丢包:统计变负数真相

前面提到过,连接表满了会导致新连接无法建立。这个问题的表现很有迷惑性。有次我帮某个机房排查,业务方反馈“某个IP累计流量突然变小了”。我查连接表,发现那个IP的累计字节数确实在往下掉,但不是流量倒流,而是表满后旧条目被强制回收,计数器随之消失,再从新连接重新累计。

判断表是否满,看两个指标:

conntrack -S

输出里重点看insert_failed、drop和early_drop。insert_failed表示新建连接失败次数,如果持续增长,说明表容量已经不够了。最直接的处理是调大nf_conntrack_max,但前提是内存够。粗略估算,如果设到100万个连接条目,光连接跟踪就要占用300MB左右内存,哈希表开销另算。调参前最好用free确认内存余量:

sysctl -w net.netfilter.nf_conntrack_max=524288 sysctl -w net.netfilter.nf_conntrack_buckets=131072

还有一个办法是调短TCP超时时间,让空闲连接更快释放:

sysctl -w net.netfilter.nf_conntrack_tcp_timeout_established=7200

不过调超时会改变NAT和防火墙的行为,比如长连接如果超过超时时间,中间没有报文,连接状态会被丢弃,后续报文会被当作新连接处理,可能导致需要重新认证。所以这个参数要结合具体业务调,不能一刀切。

5. 统计之后怎么用:落地到监控和策略联动

5.1 定时快照与长期存储的取舍

连接跟踪数据本质上是“当前状态”,不是历史时序数据。要做长期趋势分析,有两种思路:

一种是用cron定时抓快照,比如每分钟执行一次conntrack -L -o extended,把结果解析后写入文件或时序数据库。缺点是:两次快照之间如果连接建立了又销毁,累计字节数就被漏掉了;优点是实现简单、稳定性好,服务器重启也不怕丢事件。适合对精度要求不高的容量规划场景。

另一种是用事件流模式把conntrack -E的输出持续导入时序数据库,每个事件都作为一条记录。这样不会漏掉短生命周期连接,但对数据接入端的写入性能要求高,而且-E断线重连时要处理事件缺失的问题。我个人在“查历史流量峰值”场景下,一般用快照就够了;只有在做计费、做用户流量明细时,才值得上事件流。

如果要把数据存进时序库,字段建议设计成:时间戳、源IP、源端口、目的IP、目的端口、协议、方向、包数增量、字节数增量。方向这个字段很重要,因为连接跟踪条目天然分两个方向,统一存成一个值不利于后续查询。

5.2 与iptables/nftables策略联动

连接跟踪统计不只是用来看,还能用来触发策略。比较常见的是nftables里按字节数做控制。比如要对内网某个IP限速,可以在FORWARD链上匹配conntrack状态,用limit或meter做令牌桶:

nft add table ip filter nft add chain ip filter forward { type filter hook forward priority 0 \; } nft add rule ip filter forward ip saddr 192.168.1.10 ct state established,related counter limit rate 10 mbytes/second accept

这里的ct state就是直接用了连接跟踪的状态信息,而counter则会在规则命中时计数。和生产环境里的做法类似,你可以先跑一段统计,确认异常流量来自哪些IP,再针对这些IP叠加限速或日志规则。相比直接用iptables的-m conntrack,nftables的语法更清晰,ct关键字也更直观。

需要提醒的是,动态改防火墙规则有风险。如果是自动化脚本在做阈值触发,一定要加“恢复阈值”,比如字节速率超过100MB持续5分钟才限速,低于20MB持续10分钟才解除,否则规则会在临界值反复抖动,业务体验会非常糟糕。

5.3 适合不同规模环境的方案选型参考

环境规模推荐方案理由
单台Linux网关/小规模NAT/proc/net/nf_conntrack+ Python脚本零依赖,读取方便,解析成本可控
数台网关、需要集中查看conntrack -L快照 + 定时采集写入时序库实现简单,稳定性好,适合长期运行
连接吞吐很高的核心网关conntrack -E事件流或内核态eBPF方案避免用户态反复读表,减少CPU开销和事件丢失
大型网络、需要出口全景交换机NetFlow/sFlow或硬件探针连接跟踪只在单机视角,无法覆盖跨设备流量路径

选型时没必要一上来就上重方案。大部分场景,一台小网关用Python脚本就够了。如果日后连接数上去了,再平滑迁移到事件流或eBPF,方案之间数据格式可以保持统一。

6. 个人实操手记:踩过的坑和最终推荐做法

最后说点实际经验,都是踩过坑之后才总结出来的。

第一,开启计数器后,记得清一次连接表。有次我改了nf_conntrack_acct=1,然后继续读/proc/net/nf_conntrack,发现很多旧连接没有packets和bytes,一开始还以为是内核版本太老不支持。后来才知道是旧条目没有补上计数。清表之后,新条目就正常了。

第二,读/proc/net/nf_conntrack的时候,尽量在脚本里加上“字段缺失就跳过”的容错。连接表是一直在变化的,你读文件的时候,可能有旧连接刚好销毁,也可能有新连接刚插入,解析到一半字段对不上很正常。脚本别因为一行解析失败就整体崩溃,跳过即可。

第三,做“哪个IP流量最大”这种Top N统计时,别忘了把/proc/net/nf_conntrack里标记为[UNREPLIED]的单向连接也算进去。有些攻击流量或端口扫描流量就是单向的,如果只统计ESTABLISHED状态,你会漏掉最需要关注的那部分。但也要注意,单向连接可能只是UDP应用还没来得及回复,或者TCP还在握手阶段,不能一看到单向就觉得是攻击。

第四,如果机器上同时有iptables和nftables在用,别混着读数据。iptables的conntrack模块和nftables的ct关键字读取的是同一套连接跟踪表,但两者在规则匹配时的计数器位置不一样。做流量统计只读conntrack本身就好,不要试图用防火墙规则的计数器反推连接流量。

第五,生产环境长期观察,建议用systemd timer或cron跑快照脚本,输出带着时间戳和主机名的CSV或JSON,方便后续汇总。脚本里记得处理连接表读取耗时,连接数很大的时候,一次读取慢则几百毫秒,如果定时周期太短,可能还没读完下一次就开始了,会造成CPU堆积。

现在这套方法在我内部环境里的几台网关上跑了大半年,日常流量突增、异常IP定位、容量规划都在用。比起重新部署一整套流量分析系统,它几乎零成本,效果却非常直接。如果你手里也有一台Linux网关,正愁定位不到流量大户,不妨先开个计数,写个二十行脚本跑起来,比口头扯皮快得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询