ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

基于Raw Socket的以太网温湿度传感器UDP报文监听与负载解析

基于Raw Socket的以太网温湿度传感器UDP报文监听与负载解析 1. 从一个抓包需求说起为什么要绕过应用层去年帮一个做智慧农业的团队排查数据采集问题他们部署了二十多台以太网温湿度传感器走的是 UDP 上报上位机偶尔会丢几帧数据。应用层日志里什么都看不到因为程序压根没收到包。当时第一反应是拿 Wireshark 抓一下但现场环境是一台工控机装不了图形界面工具而且他们希望把监听逻辑直接嵌进自己的采集服务里做到边收边诊断。这个需求其实很典型以太网温湿度传感器通过 UDP 把数据推到某个端口应用层程序只关心解析后的温湿度值但一旦出现丢包、乱序、格式异常应用层是瞎的。你只能看到没收到数据看不到数据到底有没有到达网卡。这时候就需要把监听点下沉到网络层用 Raw Socket 直接抓取经过网卡的以太网帧自己剥掉 IP 头和 UDP 头拿到最原始的负载。标题里说的绕过应用层直接解析负载核心就是这个意思。常规做法是应用层 bind 一个 UDP 端口内核帮你把 UDP 头剥好了再交给你。Raw Socket 不一样它拿到的是更底层的东西你需要自己处理协议栈的解析工作。好处是你能看到所有到达网卡的报文包括那些因为端口没监听、校验和错误、缓冲区溢出而被内核丢弃的包。这篇文章适合几类人看一是做工业数据采集、物联网网关的开发者手上有一堆 UDP 上报的传感器二是想搞明白 Raw Socket 到底怎么用、和普通 UDP Socket 差在哪的工程师三是对 Modbus、以太网传感器协议格式感兴趣想自己写解析器的朋友。我会把整个思路、代码结构、踩过的坑都摊开讲尽量让你看完能直接抄作业。需要提前说明的是Raw Socket 需要管理员/root 权限这是硬性门槛绕不过去。另外不同操作系统对 Raw Socket 的支持差异很大本文以 Linux 为主Windows 部分会单独提一下差异点。2. 方案选型Raw Socket、AF_PACKET 与 libpcap 怎么选2.1 三种抓包路径的本质区别在动手之前得先把抓包这件事的几种实现路径理清楚不然很容易选错工具后面越写越别扭。第一种是普通 UDP Socket。你socket(AF_INET, SOCK_DGRAM, 0)然后 bind 到某个端口内核网络栈会把以太网帧、IP 包、UDP 包全部解析完只把 payload 交给你。这是最省事的但也是视野最窄的凡是没通过校验、端口不匹配、被防火墙拦掉的包你一概看不到。第二种是Raw SocketAF_INET SOCK_RAW。你告诉内核我要接收某种协议的原始数据比如IPPROTO_UDP内核会把 IP 头之后的内容交给你也就是完整的 UDP 报文UDP 头 负载。注意它给的是 IP 层之上的东西以太网头已经被剥掉了。这个层级的优势是能拿到所有 UDP 报文不管端口是多少也不管有没有程序在监听。第三种是链路层原始套接字AF_PACKET SOCK_RAWLinux 特有。这个更底层连以太网头都给你你能看到源 MAC、目的 MAC、以太网类型字段。libpcap/Wireshark 底层用的就是这个。如果你需要按 MAC 地址过滤、需要看 VLAN 标签、需要处理非 IP 协议就得用这一层。标题里说的是基于 Raw Socket 的以太网温湿度传感器 UDP 报文监听从以太网和直接解析负载这两个关键词看AF_PACKET 是最贴合的选择因为它能让你看到完整的以太网帧真正做到从网卡往上自己剥。但如果你的传感器就是标准 IP/UDP 封装用 AF_INET 的 Raw Socket 也够用代码还简单些。2.2 为什么不用 libpcap有人会问既然 libpcap 这么成熟为什么不直接用它答案取决于你的目标如果你只是临时抓包分析libpcap 加个过滤器表达式几行代码搞定没必要自己造轮子。如果你要把监听能力嵌进生产服务长期运行、需要和业务逻辑深度耦合、需要自定义统计和告警那 libpcap 反而成了负担。它有自己的缓冲区管理、自己的线程模型你很难精细控制这一帧什么时候被处理、处理失败怎么办。我当时的场景是后者采集服务本身就是一个常驻进程监听逻辑要和采集逻辑共享内存里的统计计数器还要在检测到异常帧时立刻触发告警。用 libpcap 的话得额外做一层数据搬运不如直接用 AF_PACKET 来得直接。2.3 选型对比表方案可见层级权限要求跨平台性适用场景普通 UDP Socket仅 payload无极好正常业务收数AF_INET Raw SocketIP 之上root/管理员较好抓所有 UDP/TCPAF_PACKET Raw Socket以太网帧root仅 Linux深度诊断、MAC 过滤libpcap以太网帧root好通用抓包分析选型的原则很简单能用高层就不用低层除非高层满足不了你的诊断需求。温湿度传感器这种场景一旦出现应用层收不到但网卡有包的情况你就必须下沉到 AF_PACKET因为只有它能告诉你包到底有没有到网卡。3. 以太网温湿度传感器的报文长什么样3.1 典型的数据上报格式在写解析代码之前必须搞清楚传感器到底发的是什么。以太网温湿度传感器市面上品牌很多但上报格式无非几类第一类是纯自定义二进制厂商自己定协议比如固定 20 字节前 2 字节是帧头0xAA 0x55接着是设备 ID、温度定点数、湿度、校验和。这种最常见也最需要抓包逆向。第二类是Modbus RTU over UDP把串口上的 Modbus RTU 帧原封不动塞进 UDP 负载里。这种在工业现场特别多因为很多传感器原本是 RS485 接口加个串口转以太网模块就变成 UDP 上报了。热搜词里 Modbus 出现频率极高说明这是主流。第三类是Modbus TCP带 MBAP 头7 字节事务 ID、协议 ID、长度、单元 ID后面跟 PDU。这种是标准以太网协议解析相对规范。第四类是JSON 或 ASCII 文本少数高端型号会这么干可读性好但报文大。3.2 Modbus RTU over UDP 的帧结构既然 Modbus 是热词这里重点说一下 Modbus RTU 帧的结构因为它是绕过应用层解析时最常遇到的格式。一个标准的 Modbus RTU 读保持寄存器响应帧长这样[从站地址 1B][功能码 1B][字节数 1B][数据 NB][CRC 2B]比如从站地址 0x01功能码 0x03读保持寄存器返回 4 个寄存器8 字节数据01 03 08 00 64 01 2C 00 5A 00 E1 XX XX其中00 64是第一个寄存器十进制 100可能代表温度 10.0℃需要除以 1001 2C是 300代表湿度 30.0%。CRC 是最后两字节低字节在前。关键点在于Modbus RTU 的 CRC 校验是必须自己算的。如果你用普通 UDP Socket收到的负载里就带着 CRC但很多应用层程序偷懒不校验导致错误数据被当成正常值。用 Raw Socket 抓包时你可以对每一帧都做 CRC 校验把校验失败的帧单独统计出来这对诊断链路质量极有价值。3.3 如何确认传感器的真实格式我的经验是不要相信文档要相信抓包。厂商文档经常和实际固件不一致尤其是小厂。正确流程是先用普通 UDP Socket 收几帧打印十六进制。对照文档猜字段含义重点看哪些字节在温度变化时会变。如果文档说是 Modbus就按 Modbus 帧结构去套算 CRC 验证。用 Raw Socket 抓同样的帧对比两者是否一致确认没有丢帧。这里有个小技巧让传感器在已知条件下上报比如用手捂住传感器让温度上升或者对着它哈气让湿度飙升观察哪几个字节跟着变基本就能定位温湿度字段的位置。4. 用 AF_PACKET 搭建监听的核心实现4.1 创建原始套接字的完整流程Linux 下用 AF_PACKET 抓包的骨架代码大概是这样#include sys/socket.h #include linux/if_packet.h #include linux/if_ether.h #include net/if.h #include arpa/inet.h int fd socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); if (fd 0) { perror(socket); return -1; } struct sockaddr_ll sll; memset(sll, 0, sizeof(sll)); sll.sll_family AF_PACKET; sll.sll_protocol htons(ETH_P_ALL); sll.sll_ifindex if_nametoindex(eth0); if (bind(fd, (struct sockaddr *)sll, sizeof(sll)) 0) { perror(bind); return -1; }几个关键点必须说清楚ETH_P_ALL表示接收所有以太网类型的帧。如果你只想收 IP 帧可以用ETH_P_IP能减少无关流量。bind到具体网卡通过if_nametoindex拿到索引非常重要。不 bind 的话你会收到所有网卡的帧在有多网卡的工控机上会收到一堆无关数据。权限问题这段代码必须以 root 运行或者给可执行文件加CAP_NET_RAW能力。生产环境建议用后者别整个服务跑 root。4.2 从以太网帧到 UDP 负载的逐层剥离收到一帧后数据是完整的以太网帧。你需要自己一层层剥unsigned char buf[65536]; int n recv(fd, buf, sizeof(buf), 0); struct ethhdr *eth (struct ethhdr *)buf; if (ntohs(eth-h_proto) ! ETH_P_IP) return; // 只处理 IP 帧 struct iphdr *ip (struct iphdr *)(buf sizeof(struct ethhdr)); if (ip-protocol ! IPPROTO_UDP) return; // 只处理 UDP int ip_hlen ip-ihl * 4; struct udphdr *udp (struct udphdr *)((unsigned char *)ip ip_hlen); int udp_hlen ntohs(udp-len); unsigned char *payload (unsigned char *)udp sizeof(struct udphdr); int payload_len udp_hlen - sizeof(struct udphdr);这里有几个容易翻车的地方第一IP 头长度不是固定的。ip-ihl是 4 位字段单位是 4 字节所以实际头长是ihl * 4。如果 IP 选项存在头长会大于 20。硬编码 20 字节是新手最常见的错误。第二字节序。网络字节序是大端x86 是小端所有多字节字段都要ntohs/ntohl转换。eth-h_proto、ip-tot_len、udp-len都要转。第三UDP 长度字段。udp-len包含 UDP 头本身8 字节所以 payload 长度要减掉 8。别直接用n - 14 - ip_hlen因为以太网帧可能有填充小于 60 字节的帧会被填充到 60用 UDP 长度字段更准。4.3 过滤逻辑只留你关心的帧生产环境网卡上流量很杂ARP、广播、其他设备的包都会进来。如果每帧都做完整解析CPU 会被吃满。过滤要分层做第一层以太网类型。在 socket 创建时用ETH_P_IP就过滤掉了 ARP 等。第二层目的端口。传感器上报端口通常是固定的比如 8888 或 502。在解析出 UDP 头后立刻判断ntohs(udp-dest) 8888不匹配直接 return。第三层源 IP 或 MAC。如果知道传感器的 IP 段可以进一步过滤。我实测下来在用户态做端口过滤比在内核态用 BPF 过滤器慢但胜在灵活。如果流量真的很大比如千兆线速建议还是挂一个 BPF 过滤器用setsockopt加SO_ATTACH_FILTER。不过温湿度传感器这种场景每秒几十帧顶天了用户态过滤完全够用。5. 负载解析从原始字节到温湿度值5.1 自定义二进制协议的解析假设抓包发现传感器发的是 16 字节自定义帧格式如下偏移长度含义02帧头 0xAA5522设备 ID42温度有符号单位 0.1℃62湿度无符号单位 0.1%86保留142CRC16解析代码if (payload_len 16) return; if (payload[0] ! 0xAA || payload[1] ! 0x55) return; uint16_t dev_id (payload[2] 8) | payload[3]; int16_t temp_raw (int16_t)((payload[4] 8) | payload[5]); uint16_t humi_raw (payload[6] 8) | payload[7]; float temperature temp_raw / 10.0f; float humidity humi_raw / 10.0f;注意温度用int16_t因为可能为负。湿度一般不会为负用uint16_t。大端序在这里是假设实际要以抓包为准有些厂商用小端那就得反过来拼。5.2 Modbus RTU 帧的 CRC 校验如果负载是 Modbus RTUCRC 校验是绕不开的。标准 CRC16/Modbus 算法uint16_t modbus_crc(const uint8_t *data, int len) { uint16_t crc 0xFFFF; for (int i 0; i len; i) { crc ^ data[i]; for (int j 0; j 8; j) { if (crc 0x0001) { crc (crc 1) ^ 0xA001; } else { crc 1; } } } return crc; }校验时帧的最后两字节是 CRC低字节在前、高字节在后。计算前 N-2 字节的 CRC和最后两字节比对uint16_t calc modbus_crc(payload, payload_len - 2); uint16_t recv payload[payload_len - 2] | (payload[payload_len - 1] 8); if (calc ! recv) { crc_error_count; return; }这个 CRC 校验是 Raw Socket 监听相比普通 UDP 收数的核心增值点。普通收数程序往往不校验错误数据直接进数据库Raw Socket 监听可以统计 CRC 错误率一旦发现某台传感器错误率飙升基本就是线路干扰或设备老化。5.3 解析结果的落地方式解析出来的数据往哪放取决于你的目标纯诊断打印到日志或者写进环形缓冲区供事后分析。业务采集和正常采集逻辑合并做去重同一帧可能被两个 socket 都收到。实时告警CRC 错误、帧头异常、超时未上报都触发告警。我建议监听和采集用两套独立的 socket监听用 Raw Socket采集用普通 UDP Socket。这样即使监听逻辑出问题也不影响正常采集。两者收到的数据通过设备 ID 时间戳做关联能精确定位这一帧到底是被内核丢了还是被应用层丢了。6. 实操中踩过的坑与排查技巧6.1 权限与能力配置最常见的报错就是socket: Operation not permitted。解决办法有两个一是直接sudo运行简单粗暴但不适合生产。二是给可执行文件加能力sudo setcap cap_net_rawep /path/to/your_program这样普通用户也能跑。注意setcap在文件被重新编译后会失效需要重新设置。另外某些文件系统比如某些容器环境不支持扩展属性setcap会失败那就只能退回 root 运行。6.2 收不到包先查这几个地方现象可能原因排查方法完全收不到网卡名写错ip link确认接口名只收到部分未 bind 到网卡检查sll_ifindex收到但解析失败字节序搞反打印原始 hex 对比高负载丢包缓冲区太小调大SO_RCVBUF混杂模式未开只收到本机流量设置PACKET_MR_PROMISC混杂模式这个点特别容易被忽略。默认情况下网卡只接收目的 MAC 是自己或广播的帧。如果传感器是发给另一台设备的你的监听程序就看不到。要抓所有帧得开混杂模式struct packet_mreq mr; memset(mr, 0, sizeof(mr)); mr.mr_ifindex if_nametoindex(eth0); mr.mr_type PACKET_MR_PROMISC; setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, mr, sizeof(mr));不过要注意在交换机环境下混杂模式也抓不到其他端口的流量除非你做了端口镜像。这是网络拓扑决定的不是代码问题。6.3 缓冲区与性能调优Raw Socket 默认接收缓冲区不大高流量下会丢包。调优int rcvbuf 4 * 1024 * 1024; setsockopt(fd, SOL_SOCKET, SO_RCVBUF, rcvbuf, sizeof(rcvbuf));另外recv是阻塞的如果解析逻辑慢会拖累收包。建议收包和解析分两个线程收包线程只做recv和入队解析线程从队列取。队列用无锁环形缓冲区避免锁竞争。我实测过单线程处理每秒几千帧没问题但一旦超过一万帧丢包率就上来了。温湿度传感器场景通常远低于这个量级但如果你要监听整个车间的设备就得考虑多线程或 BPF 过滤。6.4 Windows 平台的差异Windows 下没有 AF_PACKET得用SIO_RCVALL把网卡设成混杂模式然后recv拿到的也是完整 IP 包不含以太网头。代码结构类似但 API 完全不同需要WSAIoctl。另外 Windows 的 Raw Socket 对发送限制很多接收相对宽松。如果你的部署环境是 Windows建议直接用 Npcap别自己折腾 Raw Socket。7. 这套方案还能怎么扩展监听逻辑跑通之后能做的事情比想象中多。我后来在这个基础上加了几个功能实用性很高。第一是链路质量画像。按设备 ID 统计每个传感器的上报间隔、CRC 错误率、丢帧率画成趋势图。哪台设备开始不稳定一眼就能看出来比等它彻底坏掉再修主动得多。第二是协议逆向辅助。对于格式未知的传感器把抓到的帧按字节位置做统计看每个字节的取值范围和变化规律。帧头通常是固定值校验字段通常随机分布温湿度字段则和物理量相关。这个统计工具帮我省了大量猜协议的时间。第三是异常帧留存。CRC 错误、长度异常的帧单独存一份原始 hex 到文件方便事后分析。正常帧不用存省空间。第四是和 Modbus 工具联动。如果确认是 Modbus RTU over UDP可以把抓到的帧导出用 Modbus Poll 或 Modbus Slave 做回放测试验证解析逻辑是否正确。热搜里 Modbus 工具出现频率很高说明这是大家的刚需。最后分享一个我个人的习惯任何监听程序上线前先用 iperf3 打 UDP 流做压力测试。iperf3 可以指定 UDP 带宽和包长能快速验证你的监听程序在目标流量下会不会丢包。温湿度传感器流量小但如果你要监听的是整个网段这个测试就很有必要了。
返回列表