如何绑定UDP套接字仅接收特定源IP(192.168.0.3)的数据包?
解决方法:仅接收指定源IP和端口的UDP数据包
你之前的误解在于,bind函数中设置的servaddr.sin_addr是本地网卡的监听地址,用来指定套接字监听哪个本地IP的端口,而不是过滤发送方的源地址。所以绑定对方IP或者设备名是无效的,因为bind不负责源地址过滤。以下是三种可行的实现方案:
方案1:接收后手动过滤(最简单直接)
每次通过recvfrom接收数据包时,获取发送方的地址信息,判断是否符合192.168.0.3:6516的条件,不符合则直接丢弃。这种方法逻辑清晰,容易调试,适合大多数场景。
示例代码:
#include <stdio.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> // 假设gOptions已初始化,包含sockfd等字段 void handle_udp_packets() { struct sockaddr_in peer_addr; socklen_t peer_len = sizeof(peer_addr); char buf[1024]; ssize_t recv_len; // 预定义目标源IP和端口 const in_addr_t target_src_ip = inet_addr("192.168.0.3"); const uint16_t target_src_port = htons(6516); while (1) { recv_len = recvfrom(gOptions.sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&peer_addr, &peer_len); if (recv_len < 0) { perror("recvfrom failed"); continue; } // 校验源IP和端口 if (peer_addr.sin_family == AF_INET && peer_addr.sin_port == target_src_port && peer_addr.sin_addr.s_addr == target_src_ip) { // 处理符合条件的数据包 printf("Received valid packet from 192.168.0.3:6516, length: %zd\n", recv_len); } else { // 丢弃不符合条件的数据包 continue; } } }
方案2:BPF套接字过滤器(内核层面过滤)
通过setsockopt给套接字附加BPF(Berkeley Packet Filter)规则,让内核直接过滤掉不符合条件的数据包,减少用户态的无效处理开销。适合对性能要求较高的场景。
示例代码:
#include <linux/filter.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> void attach_bpf_filter(int sockfd) { // BPF规则:仅允许源IP=192.168.0.3,源端口=6516的UDP包 struct sock_filter filter_rules[] = { // 检查以太网帧类型是否为IP(0x0800) BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 12), BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x0800, 0, 5), // 检查IP协议是否为UDP(17) BPF_STMT(BPF_LD + BPF_B + BPF_ABS, 23), BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 0, 3), // 检查UDP源端口是否为6516(网络字节序0x1984) BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 20 + 16), // IP头固定20字节,UDP源端口偏移16 BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x1984, 0, 1), // 检查IP源地址是否为192.168.0.3(网络字节序0xc0a80003) BPF_STMT(BPF_LD + BPF_W + BPF_ABS, 20 + 12), // IP源地址偏移12 BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0xc0a80003, 0, 1), // 允许接收符合条件的数据包(返回最大长度) BPF_STMT(BPF_RET + BPF_K, 0xFFFF), // 丢弃不符合条件的数据包 BPF_STMT(BPF_RET + BPF_K, 0) }; struct sock_fprog filter_prog = { .len = sizeof(filter_rules) / sizeof(filter_rules[0]), .filter = filter_rules }; if (setsockopt(sockfd, SOL_SOCKET, SO_ATTACH_FILTER, &filter_prog, sizeof(filter_prog)) < 0) { perror("Failed to attach BPF filter"); close(sockfd); exit(EXIT_FAILURE); } } // 使用方式:在bind之后调用attach_bpf_filter(gOptions.sockfd);
方案3:iptables系统层面过滤(全局生效)
如果希望整个系统仅接收来自192.168.0.3:6516、发往本地6516端口的UDP包,可以添加iptables规则。注意:该规则是全局的,会影响所有监听该端口的程序。
执行以下命令:
# 仅对eno3网卡生效,丢弃非192.168.0.3:6516发往本地6516的UDP包 iptables -A INPUT -i eno3 -p udp --dport 6516 ! -s 192.168.0.3 --sport 6516 -j DROP
如果需要永久保存规则,可根据你的Linux发行版执行对应的保存命令(如iptables-save > /etc/iptables/rules.v4)。
内容的提问来源于stack exchange,提问作者Stone Cooper
相关产品推荐
相关产品推荐

