Ubuntu 16.04下AF_PACKET套接字捕获指定网卡数据包异常求助
问题描述
在Ubuntu 16.04系统中,我尝试捕获指定以太网接口的所有数据包:打开AF_PACKET套接字并将指定接口设置为混杂模式后,虽能捕获该接口的数据包,但同时也会接收到其他以太网接口(如eth0)的数据包。
我尝试调用setsockopt并使用PACKET_DROP_MEMBERSHIP来过滤eth0接口,但并未生效,仍会捕获到eth0的数据包。
相关代码如下:
// The following code opens socket and set the specified intf to PACKET_MR_PROMISC // However, it also picks up packets from all other interfaces, for example, eth0 int fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); int interface_number = if_nametoindex(interface_name.c_str()); if (interface_number == -1) { syslog(LOG_ERR, "Can't get interface number for %s.\n", interface_name.c_str()); return -1; } per_socket_data->ifindex = interface_number; syslog(LOG_INFO, "intf %s, ifindex %d. \n", interface_name.c_str(), interface_number); // Switch to PROMISC mode struct packet_mreq sock_params; memset(&sock_params, 0, sizeof(sock_params)); sock_params.mr_type = PACKET_MR_PROMISC; sock_params.mr_ifindex = interface_number; int set_promisc = setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, (void *)&sock_params, sizeof(sock_params)); // The following code tries to prevent capturing packets from eth0. However, this does not work. int eth0_interface_number = if_nametoindex(eth0.c_str()); if (eth0_interface_number == -1) { syslog(LOG_ERR, "Can't get interface number for %s.\n", eth0.c_str()); return -1; } syslog(LOG_INFO, "intf %s, ifindex %d. \n", eth0.c_str(), eth0_interface_number); memset(&sock_params, 0, sizeof(sock_params)); sock_params.mr_ifindex = eth0_interface_number; int exclude_eth0 = setsockopt(fd, SOL_PACKET, PACKET_DROP_MEMBERSHIP, (void *)&sock_params, sizeof(sock_params));
解决方法
问题根源在于AF_PACKET套接字的默认行为:创建SOCK_RAW类型的AF_PACKET套接字时,默认会接收所有接口的数据包,PACKET_DROP_MEMBERSHIP仅用于移除通过PACKET_ADD_MEMBERSHIP加入的组(如多播组),无法直接过滤接口。
正确方案是将套接字绑定到目标接口,限制仅接收该接口的数据包,具体步骤如下:
- 创建套接字后,使用
sockaddr_ll结构体绑定到指定接口 - 保持混杂模式设置以捕获该接口的所有数据包
修改后的代码示例:
int fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); int interface_number = if_nametoindex(interface_name.c_str()); if (interface_number == -1) { syslog(LOG_ERR, "Can't get interface number for %s.\n", interface_name.c_str()); return -1; } per_socket_data->ifindex = interface_number; syslog(LOG_INFO, "intf %s, ifindex %d. \n", interface_name.c_str(), interface_number); // 绑定到指定接口 struct sockaddr_ll sll; memset(&sll, 0, sizeof(sll)); sll.sll_family = AF_PACKET; sll.sll_protocol = htons(ETH_P_ALL); sll.sll_ifindex = interface_number; if (bind(fd, (struct sockaddr *)&sll, sizeof(sll)) == -1) { syslog(LOG_ERR, "Failed to bind to interface %s: %s\n", interface_name.c_str(), strerror(errno)); close(fd); return -1; } // 设置混杂模式 struct packet_mreq sock_params; memset(&sock_params, 0, sizeof(sock_params)); sock_params.mr_type = PACKET_MR_PROMISC; sock_params.mr_ifindex = interface_number; int set_promisc = setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, (void *)&sock_params, sizeof(sock_params)); if (set_promisc == -1) { syslog(LOG_ERR, "Failed to set promiscuous mode: %s\n", strerror(errno)); close(fd); return -1; }
关键说明:
sockaddr_ll是链路层专用的地址结构体,通过sll_ifindex指定目标接口索引,绑定后套接字只会接收该接口的数据包- 绑定操作与混杂模式设置的顺序不影响效果,但建议先绑定再设置混杂模式
- 无需再使用
PACKET_DROP_MEMBERSHIP,绑定操作已自动过滤其他接口的数据包
内容的提问来源于stack exchange,提问作者Charles Ju
相关产品推荐
相关产品推荐

