EtherNet/IP I/O 丢包排查
一、先确认丢包现象
1.1 从应用层确认
# 查看 EtherNet/IP 主站/从站应用的日志或状态
# 常见表现:连接超时断开、数据不更新、ForwardOpen 失败重连
- 检查 EtherNet/IP 协议栈是否打印了超时(Timeout)或连接丢失(Connection Loss)日志
- 确认是偶发丢包 还是持续丢包,间隔是否有规律
1.2 确认是 I/O(隐式)丢包还是显式丢包
| 丢包类型 | 传输层 | 端口 | 表现 |
|---|---|---|---|
| I/O 隐式丢包 | UDP | 2222 | 数据跳变、更新滞后、连接超时 |
| 显式丢包 | TCP | 44818 | 请求无响应、配置失败 |
I/O 隐式报文走 UDP,天然不保证可靠送达,丢包排查重点在 UDP 层。
二、逐层排查(自底向上)
第 1 层:物理链路
bash
# 查看以太网接口状态
ip link show
# 关注: state UP/DOWN, NO-CARRIER
# 查看接口错误统计
ip -s link show eth0
# 关注: errors, dropped, overruns, carrier
# 查看详细以太网统计
ethtool -S eth0
# 关注: rx_crc_errors, rx_missed_errors, tx_errors, rx_fifo_errors, tx_fifo_errors
# rx_long_length_errors, rx_short_length_errors, rx_align_errors
# 检查双工和速率是否匹配
ethtool eth0
# Speed 和 Duplex 必须与交换机端口一致(Full duplex)
# 检查网线/光模块
ethtool -m eth0 # 光模块诊断信息(SFP/QSFP)
常见物理层问题:
- 网线质量差、过长(>100m 铜缆)
- 双工不匹配(半双工 ↔ 全双工)→ CRC 错误激增
- EMI/电磁干扰(强电电缆与网线平行走线)
- 交换机端口损坏或缓冲区溢出
第 2 层:MAC / 数据链路层
bash
# 查看 ARP 表
ip neigh show
# 关注: REACHABLE vs STALE vs FAILED
# 实时监控 ARP 变化
watch -n 1 'ip neigh show'
# 抓包看 MAC 层重传
tcpdump -i eth0 ether proto 0x0800 -v -c 1000
# 关注是否有大量 ARP 请求(IP 冲突可能)
常见链路层问题:
- 目标设备 IP 与 MAC 映射失效(ARP 表老化)
- 网络中存在 IP 冲突
- 交换机 MAC 表溢出导致泛洪
第 3 层:IP / 网络层
bash
# 系统层面网络错误统计
netstat -s
# 关注:
# - UDP: receive errors, receive buffer errors, packet receive errors
# - IP: fragments failed, reassembly failures
# 查看所有接口的网络统计
cat /proc/net/dev
# 关注各接口的 drop 和 error 列
# 查看 UDP 协议详细统计
cat /proc/net/udp
cat /proc/net/snmp | grep Udp
# 关注: InErrors, RcvbufErrors, InCsumErrors, InDatagrams vs OutDatagrams
# 网络接口丢包快照(持续监控)
watch -n 1 'cat /proc/net/dev | grep eth0'
UDP 丢包关键指标:
bash
netstat -su
重点关注:
packet receive errors--- 接收缓冲区满导致的丢包receive buffer errors--- 内核 UDP 接收缓冲区溢出packets to unknown port received--- 端口未监听就收到包
第 4 层:UDP 缓冲区 / 内核层面
这是 EtherNet/IP I/O 丢包最常见的原因之一。
bash
# 查看当前 UDP 缓冲区大小
sysctl net.core.rmem_default # 默认接收缓冲区
sysctl net.core.rmem_max # 最大接收缓冲区
sysctl net.core.wmem_default
sysctl net.core.wmem_max
# 查看当前实际使用情况
cat /proc/net/udp
# 列: sl local_address rem_address st tx_queue rx_queue ...
# rx_queue > 0 说明应用来不及取包
# 查看 socket 级别的丢包
ss -u -a
# 关注 Recv-Q 列(接收队列积压)
# 实时监控 socket 接收队列
watch -n 0.5 'ss -u -a | grep 2222'
调优建议:
bash
# 增大 UDP 接收缓冲区(临时生效)
sysctl -w net.core.rmem_max=16777216 # 16MB
sysctl -w net.core.rmem_default=2097152 # 2MB
# 或直接针对 socket 设置大缓冲区(应用层面,代码中设置 SO_RCVBUF)
# int bufsize = 2 * 1024 * 1024; // 2MB
# setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &bufsize, sizeof(bufsize));
三、抓包分析(核心手段)
3.1 使用 tcpdump 抓取 I/O 数据流
bash
# 抓取 EtherNet/IP I/O 流量(UDP 端口 2222)
tcpdump -i eth0 udp port 2222 -w eip_io.pcap -v
# 运行一段时间后 Ctrl+C 停止
# 实时查看 I/O 包
tcpdump -i eth0 udp port 2222 -nn -v
# 关注: 发包时间间隔是否均匀,是否有长时间无包的间隔
# 统计每秒 I/O 包数量(与 RPI 对比)
tcpdump -i eth0 udp port 2222 -nn | pv -l -i 1 > /dev/null
3.2 用 Wireshark 离线分析
bash
# 在 Linux 上抓,复制到有图形界面的机器用 Wireshark 打开
# 或直接使用 tshark(Wireshark 命令行版)
tshark -r eip_io.pcap -Y "cip" -T fields \
-e frame.time_relative \
-e cip.connid \
-e cip.seqcount
3.3 关键分析维度
用 Wireshark 打开 pcap 后,按以下维度分析:
| 分析项 | Wireshark 操作 | 需关注的异常 |
|---|---|---|
| 时间间隔 | Statistics → IO Graph | 间隔突增 > 2×RPI |
| 序列号连续性 | 过滤 cip.connid==xxx,查看 seqcount |
序列号跳变/不连续 |
| Connection ID | 过滤 cip.connid |
是否有异常的 CID 变化 |
| 包大小 | 统计 Length 字段 | 长度是否与预期 Assembly 数据大小一致 |
| RPI 准确性 | 计算相邻包时间差 | 实际间隔是否偏离设定的 RPI |
| 连接状态 | 过滤 cip.service == 0x4E |
是否有意外 ForwardClose |
Wireshark IO Graph 示例配置(在 Linux 上可用 tshark 替代):
bash
# 统计每 100ms 的 I/O 包数量
tshark -r eip_io.pcap -Y "udp.port == 2222" -T fields \
-e frame.time_relative | awk '{
bucket=int($1*10);
count[bucket]++
} END {
for(b in count) print b, count[b]
}' | sort -n
# 检查序列号连续性
tshark -r eip_io.pcap -Y "cip" -T fields \
-e frame.number -e cip.connid -e cip.seqcount | head -100
四、系统层面排查
4.1 CPU 与实时性
bash
# 查看 CPU 使用率,关注是否有 CPU 核被打满
top -H
# 关注 EIP 应用线程的 CPU 占用和状态
# 查看中断分布
cat /proc/interrupts | grep eth
# 网卡中断是否集中在单核 → 可能导致 IRQ 风暴丢包
# 查看软中断
cat /proc/softirqs
# NET_RX 和 NET_TX 统计是否异常增长
# 查看进程上下文切换
pidstat -w -p <EIP_PID> 1
# 过高说明频繁切换,影响实时性
4.2 中断与 CPU 亲和性
bash
# 查看网卡中断号
ethtool -x eth0 # 查看 RSS 队列分布
grep eth0 /proc/interrupts # 查看中断在各 CPU 上的分布
# 将网卡中断分散到多核(减少单核瓶颈)
# 通过 irqbalance 自动均衡:
systemctl status irqbalance
# 或手动设置中断亲和性:
echo f > /proc/irq/<IRQ_NUM>/smp_affinity # 绑定到 CPU 0-3
4.3 内核调度策略
bash
# 查看应用线程调度策略
chrt -p <TID>
# 如果 EIP 应用需要实时性,设置为 FIFO 调度
chrt -f -p 50 <TID> # SCHED_FIFO, 优先级 50
# 设置实时优先级需 root 权限
# 永久生效需配置 /etc/security/limits.conf:
# @realtime - rtprio 99
4.4 网络协议栈调优汇总
bash
# ============= /etc/sysctl.conf 推荐配置 =============
# UDP 缓冲区
net.core.rmem_max = 16777216
net.core.rmem_default = 2097152
net.core.wmem_max = 16777216
net.core.wmem_default = 2097152
# 网络设备队列(避免 NIC ring buffer 满导致丢包)
net.core.netdev_max_backlog = 5000
net.core.netdev_budget = 600
# 减少 UDP 校验和开销(对本地可靠网络)
# 注意:仅在信任网络质量时启用
# net.ipv4.udp_checksum = 0
# 禁用大页内存导致的内存碎片
vm.compaction_proactiveness = 0
# 应用后生效
# sysctl -p
4.5 网卡 Ring Buffer
bash
# 查看网卡 ring buffer 大小
ethtool -g eth0
# 关注: RX: 当前值和最大值
# 增大 ring buffer(如果支持)
ethtool -G eth0 rx 4096 tx 4096
# 查看是否因 ring buffer 溢出丢包
ethtool -S eth0 | grep -E "rx_dropped|rx_missed|rx_fifo|rx_over"
# 任何 > 0 的值都要警惕
五、应用层排查
5.1 EtherNet/IP 协议栈层面
# 检查 EIP 应用日志
# 典型开源协议栈日志标志:
# - OpENer: "connection timeout", "missed packet", "RPI violation"
# - libplctag: "timeout", "connection lost"
# 关注以下指标:
# 1. Connection Timeout Multiplier 设置
# - 连接超时 = RPI × Timeout Multiplier
# - 默认 Multiplier=4,丢包超过这个倍数就断开连接
# 2. RPI 值是否合理(太小 → 网络压力大,太大 → 数据更新慢)
# - 典型值:5ms ~ 100ms
# - 若 RPI < 10ms,对网络质量要求极高
5.2 检查 RPI 与网络能力的匹配
bash
# 假设 RPI = 5ms,每个 I/O 包 500 字节
# 每秒包数 = 1000 / 5 = 200 pps (单向)
# 若 10 个连接: 200 × 2(双向) × 10 = 4000 pps
# 检查网卡是否能承受此速率
# 实际测量 pps
tcpdump -i eth0 udp port 2222 -nn -c 10000 | pv -l > /dev/null
# 对比 pps 是否接近网卡/CPU处理极限
5.3 连接数量
bash
# 查看当前 EIP 连接数
ss -u -a | grep 2222 | wc -l
# 过高的连接数 → 考虑拆分为多个 Scanner
六、交换机 / 网络设备排查
6.1 交换机侧
# 工业交换机常见问题:
# 1. IGMP Snooping 未正确配置(使用多播时)
# - 多播 I/O 包被交换机丢弃
# - 解决:启用 IGMP Snooping 并正确配置 Querier
# 2. QoS 未配置
# - I/O 包被大流量(如固件下载)挤占
# - 解决:给 UDP 2222 端口配置高优先级(DSCP/CoS)
# 3. 交换机端口缓冲区溢出
# - 微突发(microburst)导致瞬时丢包
# - 解决:增大端口缓冲区或降低 RPI
# 4. 广播风暴
# - 环路导致 → STP/RSTP/DLR 未开启或配置错误
# - 解决:启用环网保护协议
6.2 网络拓扑
# 检查拓扑:
# - EIP I/O 连接应尽可能在同 VLAN / 同子网
# - 跨 VLAN 路由会增加延迟和抖动
# - 链形/环形拓扑中任意节点故障会影响下游
# - 考虑使用 DLR(Device Level Ring)或 PRP(Parallel Redundancy Protocol)
七、排查流程总结
发现 I/O 丢包
│
▼
┌──────────────────────────┐
│ 1. 确认丢包范围 │
│ 单个连接 or 全部连接? │
│ 偶发 or 持续? │
└──────────┬───────────────┘
│
▼
┌──────────────────────────┐
│ 2. 物理层检查 │
│ 网线/光模块/双工匹配 │
│ ethtool -S eth0 │
│ 有 CRC/物理错误 → 换线 │
└──────────┬───────────────┘
│ 正常
▼
┌──────────────────────────┐
│ 3. 抓包分析 (tcpdump) │
│ udp port 2222 │
│ Wireshark 查看间隔/序列号 │
│ 包确实丢了 → 下一步 │
└──────────┬───────────────┘
│
▼
┌──────────────────────────┐
│ 4. 系统层面检查 │
│ netstat -su 看 UDP 错误 │
│ 检查 ring buffer 溢出 │
│ 检查 socket Recv-Q │
│ 有溢出 → 调大缓冲区 │
└──────────┬───────────────┘
│ 正常
▼
┌──────────────────────────┐
│ 5. CPU/实时性检查 │
│ 中断分布/调度策略 │
│ CPU 打满 → 调整亲和性 │
│ 非实时 → 改用 FIFO 调度 │
└──────────┬───────────────┘
│ 正常
▼
┌──────────────────────────┐
│ 6. 交换机/网络检查 │
│ IGMP Snooping / QoS │
│ 端口统计 / 缓冲区溢出 │
│ 拓扑冗余/环网状态 │
└──────────┬───────────────┘
│
▼
┌──────────────────────────┐
│ 7. 应用层检查 │
│ RPI 是否合理 │
│ Timeout Multiplier │
│ 连接数量是否超限 │
└──────────────────────────┘
八、快速诊断命令合集
bash
#!/bin/bash
# eip_diag.sh --- EtherNet/IP I/O 丢包快速诊断
IFACE="eth0"
EIP_PORT=2222
echo "=== 1. 接口状态 ==="
ip link show $IFACE
echo ""
echo "=== 2. 接口错误统计 ==="
ip -s link show $IFACE
echo ""
echo "=== 3. 网卡硬件统计 ==="
ethtool -S $IFACE | grep -iE "drop|error|miss|fifo|over|crc"
echo ""
echo "=== 4. Ring Buffer 大小 ==="
ethtool -g $IFACE
echo ""
echo "=== 5. UDP 统计 ==="
netstat -su | grep -A 10 Udp:
echo ""
echo "=== 6. Socket 接收队列 ==="
ss -u -a | grep $EIP_PORT
echo ""
echo "=== 7. 内核丢包计数 ==="
cat /proc/net/dev | awk -v iface="$IFACE:" '$1==iface {print "RX drop:",$5,"TX drop:",$9}'
echo ""
echo "=== 8. 中断分布 ==="
grep $IFACE /proc/interrupts | head -5
echo ""
echo "=== 9. CPU 负载 ==="
uptime
echo ""
echo "=== 10. 当前 UDP 缓冲区配置 ==="
sysctl net.core.rmem_max net.core.rmem_default net.core.wmem_max net.core.wmem_default