C语言实现ping程序时recvfrom()异常收到出站ICMP请求包问题求助
问题根因
你使用的是ICMP类型的原始套接字,这类套接字的默认工作机制是捕获本机所有匹配协议的报文,包括你自己程序发出去的出站ICMP回显请求(type=8),也包括从环回口/物理网卡收到的入站ICMP回显应答(type=0)。你ping环回地址时,自己发的请求会先进入接收队列,之后才是内核返回的应答,所以就出现了奇数次调用recvfrom拿到请求、偶数次拿到应答的规律。
可行解决方案
方案1(通用跨平台,最推荐)
在接收逻辑中增加报文过滤规则,只有同时满足以下所有条件的报文才视为有效应答,否则直接丢弃并继续调用recvfrom读取下一个报文:
- ICMP报文类型为0(回显应答)
- ICMP报文中的标识符字段和你发送请求时填写的标识符完全一致(一般用当前进程PID作为标识符,可避免和系统中其他ping进程的报文冲突)
- ICMP报文的序列号和本次发送的请求序列号匹配
修改后的接收逻辑参考:
// Receive struct timeval timeout = {3, 0};//3s setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); while(1) { if ((n = recvfrom(sockfd, buf, BUF_SIZE, 0, (struct sockaddr *) &from, &fromlen)) < 0) { printf("Time out! \n"); // 超时跳出循环 break; } // 解包后判断是否是本次期望的应答 int ret = unpack(buf, n); if (ret == 0) { // 是有效应答,计数后跳出循环 nreceived++; break; } else if (ret == -1) { // 不是目标应答,继续读下一个报文 continue; } }
你只需要在unpack函数中补充上面提到的类型、标识符、序列号校验逻辑,校验不通过返回-1即可。
方案2(Linux平台专用,性能更高)
可以给原始套接字挂载BPF(伯克利包过滤)规则,直接在内核层面过滤掉不符合要求的报文,减少用户态内存拷贝的开销。
核心配置代码参考:
// 构造过滤ICMP回显应答、且标识符为当前进程PID的BPF规则 struct sock_filter bpf_code[] = { // 加载ICMP类型字段 BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(struct icmphdr, type)), // 判断是否是回显应答(type=0),不是就丢弃 BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ICMP_ECHOREPLY, 1, 0), BPF_STMT(BPF_RET + BPF_K, 0), // 加载ICMP标识符字段 BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct icmphdr, un.echo.id)), // 判断是否是本进程的ID,不是就丢弃 BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, htons(getpid()), 1, 0), BPF_STMT(BPF_RET + BPF_K, 0), // 匹配成功,返回全报文 BPF_STMT(BPF_RET + BPF_K, (u_int)-1), }; struct sock_fprog bpf_prog = { .len = sizeof(bpf_code)/sizeof(bpf_code[0]), .filter = bpf_code, }; // 给套接字挂载过滤规则 setsockopt(sockfd, SOL_SOCKET, SO_ATTACH_FILTER, &bpf_prog, sizeof(bpf_prog));
内容的提问来源于stack exchange,提问作者Fresher
相关产品推荐
相关产品推荐

