Linux下Python3使用AF_INET原始套接字捕获自定义IPv4包问题
解决原始套接字接收自定义IPv4包的问题
核心问题原因
你创建的IPPROTO_RAW类型原始套接字在Linux系统中仅用于发送,内核不会将任何IP数据包投递到这类套接字,这就是recvfrom无法捕获数据的根本原因。另外,系统返回"ICMP: Protocol Unreachable"是因为内核收到自定义协议包后,发现没有进程监听对应的协议号,因此主动返回不可达响应。
接收自定义IPv4包的解决方案
方案1:使用对应自定义协议号的SOCK_RAW套接字
针对每个自定义协议号,创建单独的SOCK_RAW套接字,内核会将匹配该协议号的IP包投递到这个套接字。
代码示例
假设你的自定义协议号为253(IANA保留的私有协议范围为240-254,可选用未分配的编号):
import socket # 自定义协议号(根据实际需求修改) CUSTOM_PROTOCOL = 253 INTERFACE = "enp3s0f1" SO_BINDTODEVICE = 25 # 创建接收专用的原始套接字,指定自定义协议号 recv_sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, CUSTOM_PROTOCOL) # 绑定到指定接口,仅接收该接口的数据包 recv_sock.setsockopt(socket.SOL_SOCKET, SO_BINDTODEVICE, INTERFACE.encode("utf-8")) # 循环接收数据 while True: data, addr = recv_sock.recvfrom(65535) # 解析IPv4头(前20字节为基本IP头) ip_header = data[:20] received_proto = ip_header[9] if received_proto == CUSTOM_PROTOCOL: payload = data[20:] print(f"收到自定义协议包,源地址:{addr},负载:{payload}")
说明
- 发送时可继续使用原有的
IPPROTO_RAW套接字,但需在自行封装的IP头中将protocol字段设置为自定义协议号。 - 绑定
SO_BINDTODEVICE后,该套接字只会接收指定接口的数据包,符合你的需求。
方案2:使用PF_PACKET套接字(接收多种自定义协议)
如果需要在同一个套接字中接收多种自定义协议,可使用PF_PACKET类型的套接字直接捕获以太网帧,自行解析IP头中的协议字段进行筛选。
代码示例
import socket INTERFACE = "enp3s0f1" ETH_P_IP = 0x0800 # 仅捕获IPv4以太网帧 # 创建PF_PACKET原始套接字 sock = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(ETH_P_IP)) sock.bind((INTERFACE, 0)) # 定义需要监听的自定义协议号列表 TARGET_PROTOCOLS = {253, 254} while True: data, addr = sock.recvfrom(65535) # 跳过以太网帧头(前14字节),提取IP数据包 ip_packet = data[14:] ip_header = ip_packet[:20] received_proto = ip_header[9] # 筛选目标自定义协议 if received_proto in TARGET_PROTOCOLS: payload = ip_packet[20:] print(f"收到自定义协议{received_proto},源接口:{addr[0]},负载:{payload}")
关键问题解答
能否在同一套接字上接收多种自定义协议?
- 使用
AF_INET类型的SOCK_RAW套接字不行,每个这类套接字只能绑定一个特定的IP协议号。 - 使用
PF_PACKET套接字可以实现,通过自行解析IP头的协议字段,在一个套接字中筛选并处理多种自定义协议。
- 使用
如何仅接收单个接口的数据?
- 两种方案都可以通过设置
SO_BINDTODEVICE套接字选项(方案1),或者绑定接口时指定接口名(方案2的sock.bind((INTERFACE, 0)))来实现仅接收目标接口的数据包。
- 两种方案都可以通过设置
内容的提问来源于stack exchange,提问作者WakkaTrout
相关产品推荐
相关产品推荐

