连接跟踪流量统计,这是Linux网络运维里一个特别容易被忽视,但关键时刻能救命的角落。很多人搞iptables、搞防火墙规则精通得很,一说到“你线上每秒多少新建连接?连接表占用多少?到底哪个IP在持续往外发包?”就哑火了。原因很简单,连接跟踪(conntrack)和流量统计这两件事,平时不会主动出现在监控面板上,等出了事故你才想起去查,往往已经晚了。
这篇东西,我把它定位成一篇实操经验汇总。我不打算从头讲netfilter的论文级原理,更多是站在一个长期跟连接表、计数器、NAT规则打交道的老运维视角,把连接跟踪是怎么回事、流量统计到底有哪几条路可以走、每一步怎么落地、踩过哪些坑,一次说清楚。适合刚接手公司网关、负载均衡或者K8s节点的同行,也适合那些被“conntrack表满了”折磨过的新手。看完你至少能自己搭一套简单但可靠的流量统计脚本,也能在连接表异常时快速定位方向。
1. 连接跟踪到底在干什么,为什么流量统计绕不开它
1.1 连接跟踪的本质:内核里的“状态记账本”
连接跟踪,全称connection tracking,是Linux内核netfilter框架提供的一项基础能力。它的核心工作,是把经过主机的数据流抽象成一条“连接”,记录这条连接的五元组信息(协议、源IP、源端口、目的IP、目的端口),连同当前所处的状态:NEW(新建)、ESTABLISHED(已建立)、RELATED(关联连接,比如FTP的数据通道)、INVALID(无效)。
打个比方你就懂了。连接跟踪表就像餐厅前台的服务员,每一桌客人进来,他会记一下桌号、人数、点没点菜、吃到哪个阶段。后面上菜、加单、结账,全靠这本账。没有这个账本,防火墙的state规则没法做——你凭什么判断这个包是回应之前的请求,还是外部主动打进来的新攻击?NAT也一样,源地址转换后回程的包要能逆转翻译回去,必须查连接跟踪表里存的映射关系。
所以只要你的机器做了NAT、开了防火墙并且用了state/ct状态匹配、或者跑了Docker、Kubernetes这种重度依赖网络策略的容器平台,连接跟踪就一定是开启的。
1.2 连接跟踪表和流量统计的关系
连接跟踪表本身不统计字节数。它只管“谁跟谁在通信,现在什么状态”。但正因为它的存在,内核可以在连接创建、更新、销毁的各个生命周期节点上,天然挂钩各种计数逻辑。这就是为什么我们在iptables规则里能看到-c计数器,在nftables里能定义counter,在conntrack工具里能查到bytes=和packets=字段。
换句话说,连接跟踪给了你两个维度的数据:一个是连接数(横向规模),一个是流量字节(纵向体量)。这两个维度合在一起,才能回答运维里最常问的“谁在消耗带宽”“谁在疯狂发起连接”“NAT网关的负载是否到了瓶颈”。
1.3 什么时候你会真正需要做流量统计
我遇到过好几种典型场景,逼着你必须认真做连接跟踪流量统计:
第一种,公司出口NAT设备,领导想搞清楚每个部门、每个应用占了多大带宽。这时候你得按源IP聚合流量,光看交换机的端口流量没有任何意义。
第二种,后端服务突然被大量异常请求打满,但前端所有QPS指标都是正常的。查了半天,发现是某个内网服务在跟外网建立大量短连接,连接表被打爆了。这时候你得看清楚到底哪个进程、哪个目的IP在制造连接风暴。
第三种,做安全审计。要回溯某段时间某个IP的往来流量,连接跟踪日志是最好的“黑匣子”。
这些场景,没有一套顺手可用的连接跟踪流量统计手段,基本就是抓瞎。
2. 流量统计的常见实现路径——三选一,看你有什么牌
2.1 路径一:内核计数器的被动统计(iptables/nftables自带)
这是最朴素也最可靠的办法。iptables的每条规则自带两个计数器:packets和bytes。你只需要把统计用的规则放在合适位置,内核会默默帮你累加数据。
它的优势在于:不需要额外开服务,不占额外CPU,数据是内核精确计数的,不丢包,不采样。非常适合做“总量统计”和“按固定维度聚合统计”,比如按源IP段、目的端口、协议来查流量。
但它的短板也很明显:聚合维度不够灵活。你写了十条规则按十个IP统计,那以后想按“IP和端口组合”查,就得加规则;计数器是累计值,想要速率必须拉差值后自己算;另外,iptables计数器在规则被刷新或者机器重启后会清零,需要配套的持久化采集方案。
2.2 路径二:连接跟踪日志的被动采集(基于日志的大数据统计)
内核提供了一个开关net.netfilter.nf_conntrack_acct,开启后,连接跟踪会对每条连接记录packets和bytes数据。你可以通过conntrack -L命令随时查看某条连接已经传了多少字节、多少个包。
更进一步,可以用ctnetlink机制把连接事件(新建、更新、销毁)推送到用户态,通过ulogd2这类工具落日志,或者自己写一段C/Python用netlink库接收消息。每条连接的字节数、包数都能被记录到。
好处是统计维度非常自由——你可以存下来做离线分析、按任意字段group by,也可以回溯历史某条连接的流量。代价是:量大的机器上,日志会产生海量事件,磁盘和CPU都有压力;实时性弱于计数器方案;而且如果连接长时间不断开,字节数只累计不输出,需要按超时兜底。
2.3 路径三:流量采样与镜像分析(NetFlow/sFlow/pcap旁路)
这条路,严格来说已经不算连接跟踪流量统计了,但实际运维里经常拿来补充。思路是把流量旁路出来,用采样协议(sFlow/NetFlow)或者全量抓包(pcap)的方式,交给外部分析器(比如ELK、SQL数据库)做聚合。
优势是灵活、不碰生产路径、能拿到L7应用层信息;劣势是需要额外部署,硬件交换机支持sFlow还好,纯服务器环境下还得做分光器或者端口镜像,成本高,采样有概率丢数据,不适合对准确性极其敏感的对账场景。
2.4 三条路径的选型对照
| 维度 | 内核计数器 | 连接跟踪日志 | 采样/镜像 |
|---|---|---|---|
| 精确度 | 精确 | 精确 | 抽样,有误差 |
| 实时性 | 秒级(主动拉取) | 事件级推送 | 秒级 |
| 系统开销 | 极低 | 连接量大的主机有压力 | 网络和存储压力高 |
| 持久化 | 需自己采集 | 天然写入日志 | 分析端存储 |
| 聚合灵活度 | 受限于规则配置 | 任意维度SQL查询 | 最灵活,含L7 |
| 适合场景 | 网关/NAT计数器 | 安全审计、连接级明细 | 全链路监控、协议分析 |
我个人的建议是:日常监控、容量预警这种,以路径一为主,简单直接;需要做安全审计或查历史连接时,路径二配合conntrack工具临时看,甚至靠journalctl慢慢捞;规模大且有专业流量分析平台的,再加路径三作为补充。
3. 实操环节:五种最常用的连接跟踪流量统计手段
3.1 看一台机器当前活跃连接数,摸清压力
先别急着写统计脚本,第一步应该是能快速回答“这台机器当前同时在跑多少条连接”。用一行命令:
# 查看当前连接跟踪总数 cat /proc/sys/net/netfilter/nf_conntrack_count # 查看系统允许的最大连接跟踪数 cat /proc/sys/net/netfilter/nf_conntrack_max当nf_conntrack_count接近nf_conntrack_max的时候,事情就危险了——新连接会被内核直接丢弃,表现为:服务端口telnet不上但ping通,业务日志里出现“connection reset”且有周期性。这个数字,应该进你的监控系统,告警阈值我一般设在最大值的80%左右。
3.2 iptables计数器直接查流量,最简单粗暴
假设你想统计“访问本机8080端口的流量总量”,直接加一条规则:
iptables -I INPUT 1 -p tcp --dport 8080注意,我没给-j ACCEPT,只是把规则挂在那里纯计数。因为INPUT链默认处理完所有规则会回到链策略,只要不显式跳转,计数规则不影响包的去向。这样加规则多少有点“脏”,更干净的做法是建一条独立的统计链:
iptables -N TRAFFIC_STAT iptables -A INPUT -j TRAFFIC_STAT iptables -A TRAFFIC_STAT -p tcp --dport 8080随后,想读取某条规则累计的包数和字节数:
iptables -L TRAFFIC_STAT -v -x-x参数很关键,作用是让计数器输出精确数字,而不是缩写成12K、34M这种带单位的格式,方便脚本解析。这个参数至少在需要把数据丢给监控系统的场景里是必须的。
3.3 按IP聚合统计流量,拿脚本说话
计数器方案最实用的形态,是写一个脚本定时拉取计数器,计算差值,得到速率。我常用的逻辑如下:
# 清空指定链的计数器(或重建规则全部归零) iptables -Z TRAFFIC_STAT # 确认规则存在后,每分钟执行一次: # 先保存上一次的计数器,读取当前值,相减,再除以间隔时间,即得字节速率 prev_bytes=$(cat /tmp/traffic_last.txt) curr_bytes=$(iptables -L TRAFFIC_STAT -v -x -n | awk -v port=8080 '$3=="tcp" && $4=="dport" {print $2}') echo "$curr_bytes" > /tmp/traffic_last.txt rate_bytes=$(( (curr_bytes - prev_bytes) / 60 ))实际生产环境里,我会把这个逻辑编程成一段守护脚本,输出成timestamp ip bytes bytes_per_sec的格式,丢给Prometheus文本文件采集器或者写进InfluxDB。核心思路就是:不要指望iptables给你速率,它只给累计增量,速率永远是自己的时序系统算出来的。
3.4 用conntrack命令查看连接级明细,深挖单条连接
如果你的问题不是“总量多大”,而是“哪条连接在跑大流量”,那就要进连接跟踪表细看。工具是conntrack,安装包一般叫conntrack-tools。
# 查看当前所有连接,过滤出字节数排序前10 conntrack -L -o extended | awk '{print $NF}' | sort -rn | head -10conntrack -L输出长格式时,每条连接末尾会有bytes=... packets=...字段。-o extended这个参数很关键,它额外输出标记、secmark等扩展信息,也确保字节数字段出现在解析里。如果要做每IP聚合:
conntrack -L -o extended | awk '/udp|tcp/ { for(i=1;i<=NF;i++) { if($i ~ /^src=/) {split($i,a,"="); src=a[2]} if($i ~ /^bytes=/) {split($i,b,"="); bytes=b[2]} } total[src]+=bytes } END {for(ip in total) print total[ip], ip}' | sort -rn | head -20这里的坑在于,conntrack -L在高连接数的机器上本身就是重操作,它会读全部连接表,可能占用CPU几十毫秒甚至更多。生产环境大表(比如超过十万条连接)我是绝对不用它做高频轮询的——只用于事故现场手动排查。
3.5 nftables下的统计实现——现代服务器更推荐
如果你用的发行版已经全面转向nftables(比如新装的Debian 12、Ubuntu 22.04+,而且没有老iptables套件),那么统计写作nftables规则要干净得多。nftables原生支持counter关键字,能直接嵌在规则里:
nft add table inet traffic_stat nft add chain inet traffic_stat count_in '{ type filter hook input priority 0; }' nft add rule inet traffic_stat count_in tcp dport 8080 counter读取时也更方便:
nft list table inet traffic_stat输出里直接能看到packets 12345 bytes 6789012。nftables的counter天生支持动态map,你可以定义一张“IP地址->计数器”的映射表,然后一行规则对每个新源IP自动建计数器:
nft add table inet traffic_stat nft add set inet traffic_stat per_ip '{ type ipv4_addr; flags dynamic; }' nft add rule inet traffic_stat count_in ip saddr @per_ip counter之后每来一个不同的源IP,集合里会自动增加一条记录,并配一个独立的计数器。这功能比iptables的“限制规则条数”思路灵活太多,也是我近两年主推的方案。
4. 深度解析:连接跟踪表为什么会满,以及它怎么拖垮流量统计
4.1 表满的连锁反应,比想象中严重
连接跟踪表设计成固定容量的哈希表,默认值因发行版而异,常见是65536、131072或者262144。当表满时,内核无法为新建连接分配跟踪条目,于是静默丢包,丢弃的位置在netfilter框架的nf_conntrack_in阶段,后面你再有防火墙、NAT都没机会执行。结果是:外部新请求进不来,但Server端已经在跑的ESTABLISHED连接不受影响。
这种故障最有迷惑性的特征是“半通”——用户访问部分失败,部分成功,老连接正常,重启服务后短暂恢复又恶化。如果你对这种场景没经验,很容易误判成应用层问题,反复重启服务浪费时间。
4.2 哪些流量会大量消耗连接表条目
UDP和TCP短连接是大头。TCP短连接三次握手建一条,四次挥手后条目不会立刻消失,默认TCP连接的超时时间是432000秒(5天)——你没看错,一条已经关闭的TCP连接,conntrack条目最长可以赖在表里5天才被回收。UDP默认超时30秒,反而短得多。
所以一条TCP连接只要存活超过超时时间,或者关闭后没被立即回收,就会长期占据一个表项。高并发短连接的服务(比如Web API、RPC调用)非常容易把表堆满。业界普遍的调优手法是修正超时参数:
# 缩短TCP连接超时,从5天改为1小时 sysctl -w net.netfilter.nf_conntrack_tcp_timeout_established=3600 # 短连接场景可以进一步改小,但要评估业务是否有长连接另外还有两个隐藏消耗:一是NAT情况下同一个内网IP的大量连接,源端口很快耗尽,但如果五元组不同,conntrack照样需要独立条目;二是有很多“半开连接”的场景,SYN Flood攻击会让表里充满SYN_SENT状态的残留条目。
4.3 表满之后的流量统计直接就废了
这里有个恶性循环值得单独拉出来说。连接跟踪表满后,新连接无法被跟踪,意味着你通过conntrack日志做流量统计的那些路径,天然漏掉了被丢弃的连接流量。表现在你的统计系统上,就是流量数字突然掉下去了,但实际带宽占用可能还很高。只看统计结果的话,你会误以为攻击停了,其实只是连接跟踪已经被打瘫了。
所以做流量统计必须联动监控连接跟踪表的使用率。表的使用率本身,才是这台机器网络健康度的“体温计”。一旦超过80%,统计数字的可信度就要打折扣,得先处理表满问题,再来谈流量分析。
5. 实战案例复盘:一次连接表告警背后的流量统计排查
5.1 现象与初步定位
去年我接手一个内网网关的维护,某天凌晨告警:网关的nf_conntrack_count超过阈值,且连续三次采样都在攀升。开始时我没有着急调大nf_conntrack_max,因为机器的内存并不紧张,盲调大只会掩盖问题——如果真是攻击流量,你调再大也会被打满。
我的排查顺序:先看系统连接跟踪状态里的TCP状态分布:
cat /proc/net/nf_conntrack | awk -F ' ' '{print $4}' | sort | uniq -c | sort -rn输出的状态里,大量集中在SYN_SENT和TIME_WAIT。SYN_SENT偏多说明可能有对外异常连接在积压,TIME_WAIT大是正常的,但配合连接数持续上涨,就不是正常的了。
5.2 顺着连接表挖出元凶
接着我用conntrack -L过滤出SYN_SENT状态的连接,按目的IP聚一下:
conntrack -L -o extended | grep SYN_SENT | awk -F 'dst=' '{print $2}' | awk '{print $1}' | sort | uniq -c | sort -rn | head结果一目了然,有一个外网IP被大量去连,而且都是SYN_SENT——对方根本不回包。netstat -antp确认是某个Java服务在疯狂重试外部接口调用,对方接口有问题,重试机制没做熔断,很快把连接表堆满。
这个案例最有价值的经验是:先看连接跟踪的状态分布,再看异常的远端IP,最后回到应用层的重试逻辑。流量统计的表象是“连接数暴涨”,但真实凶手是应用层没有保护。我当时把服务的超时时间从3秒改成1秒,并且加了熔断,连接跟踪表立刻回到正常水位。
5.3 事后补上的流量统计方案
处理完事故后,我给网关加了完整的流量统计与告警体系:
/proc/sys/net/netfilter/nf_conntrack_count每30秒采集一次,超过nf_conntrack_max的75%发P2告警,90%发P1。- 按源IP聚类统计TCP SYN连接速率,每5分钟计算一次,超过基线十倍以上触发告警。
- iptables统计链按月保留,每天早上把前一天的累计字节和包数推到表格存储,供业务方按IP查月度用量。
这套体系上线之后,同样的问题再次出现时,监控能在我还没被用户投诉之前就发出告警。
6. 常见问题速查与避坑经验
6.1 为什么计数器统计的流量和实际带宽对不上
这件事最容易引起业务方质疑。首先要搞清楚,iptables计数器统计的是“经过这台主机网络栈处理的IP层流量”,不含以太网帧头、不含交换机层面的广播流量,也不含被DPDK这种用户态驱动绕过的数据包。如果你的机器上有部分流量走XDP/DPDK旁路了,那计数器天生就统计不到。
另外,本机回环流量(lo口)算不算你的业务流量,取决于你规则链挂的位置。我做统计时通常会把lo接口排除在外,避免重复计数。
6.2 conntrack日志丢失,统计出现空洞
用conntrack日志方案时,事件通过netlink socket传递给用户态,如果用户态程序处理不过来,内核socket缓冲区满了就会丢弃消息。这种丢弃是静默的,日志里看不出异常,只会表现为某段时间的连接事件缺失。
解决思路:一是加大接收缓冲区(net.core.rmem_default);二是做降级——如果事件吞吐量过大,就不追求逐条记录,改为周期性全量快照的增量式统计,牺牲实时性换完整性;三是把连接表Dump快照定期落盘,比如每5分钟把/proc/net/nf_conntrack导出压缩存一份,事故后直接翻历史快照就行。
6.3 计数器反向增长,数据出现气泡
我踩过一次很典型的坑。写统计脚本时,拿iptables计数器差值计算速率,结果符号写反了还是其次,问题是计数器在某个时间点突然跳变到超大值。查原因,发现是有人手动清空了规则,重新加载后计数器归零,然后脚本认为是“反向回绕”,算出来的速率变成了天量数字。
这个问题的鲁棒解法是:脚本里判定如果当前计数比上次小,就丢弃这一轮差值,一律输出0,而不是输出负数。同时规则文件的变更操作要规范,走自动化发布,避免人工干预计数器。
6.4 如何应对Kubernetes节点上的连接跟踪统计盲区
Kubernetes节点上,Kube-proxy的iptables或IPVS模式大量依赖连接跟踪。特别是IPVS模式,它本身有更多连接条目,因为每个Service的每个后端组合都会占据独立的连接跟踪项。流量统计的难点在于:Service的ClusterIP只是虚拟IP,你按源IP分类能聚合,按目标Service归类就有难度了。
我的建议是:K8s场景下连接跟踪统计尽量按Pod所在节点和网段聚合,配合Service Mesh的sidecar流量指标综合判断。纯靠conntrack做细粒度业务流量拆解,不仅效率低,而且容易误判。
7. 终点之前,留几句实用闲话
最后说点不写在教科书里的感受。连接跟踪流量统计这个技术点,不像高并发框架那样能带来炫酷的架构升级,也出不了什么精彩的PPT,但在真实的生产环境里,它就是我这种人赖以为生的“底层能力”。说实话我自己最初踩的坑,很多都不是技术难题,而是没把“连接跟踪表也是要监控的”这件事当回事。给主机加内存、加CPU、加带宽,都不如提前把nf_conntrack_count盯牢来得实在。
还有一个心得是:统计不是为了做日报,而是为了发现“异常”。一个稳定运行的系统,连接数、字节数、包数在时间轴上应该是平滑的。任何突兀的抖动,都值得你去连接跟踪表里翻一翻。与其研究更花哨的流量分析平台,不如先把自己的计数器脚本和告警阈值搞好。
如果你现在手头正好管着几台NAT网关或K8s节点,建议今天就跑一遍:
cat /proc/net/nf_conntrack | wc -l cat /proc/sys/net/netfilter/nf_conntrack_max先把这两个数记下来,再顺手看一眼你的监控系统里有没有这两个指标。没有的话,补上它们,比任何花架子优化都实在。