因Python socket限制用ctypes调用libc accept时出现EFAULT错误,求助
我来帮你捋捋这个EFAULT错误的来龙去脉,毕竟用ctypes直接怼系统接口的坑我也踩过不少😉
首先得明确:EFAULT本质是内核没法访问你传给它的内存地址,说白了就是你给accept的参数(尤其是客户端地址相关的指针)有问题,内核读/写不了那块内存。下面是几个最可能的排查方向和解决方法:
1. 别在sockaddr结构体上偷懒
不同地址族对应的sockaddr结构体完全不一样,比如你用AF_PACKET就得用sockaddr_ll,AF_NETLINK用sockaddr_nl,绝对不能随便用个字节串或者空指针糊弄。而且必须用ctypes的结构体类型分配内存,还要传它的指针给accept,不能直接传结构体实例本身。
举个正确的结构体定义+内存分配例子(以AF_PACKET为例):
import ctypes from ctypes import cdll, Structure, byref, c_uint16, c_int32, c_uint8, c_uint32 libc = cdll.LoadLibrary('libc.so.6') # 对应AF_PACKET的地址结构体 class sockaddr_ll(Structure): _fields_ = [ ("sll_family", c_uint16), ("sll_protocol", c_uint16), ("sll_ifindex", c_int32), ("sll_hatype", c_uint16), ("sll_pkttype", c_uint8), ("sll_halen", c_uint8), ("sll_addr", c_uint8 * 8) ] # 分配客户端地址内存和长度变量(必须用ctypes的类型,不能用Python原生int/bytes) client_addr = sockaddr_ll() addr_len = c_uint32(ctypes.sizeof(client_addr))
然后调用accept的时候一定要传指针:
conn_fd = libc.accept(sock_fd, byref(client_addr), byref(addr_len))
2. 给函数绑定正确的参数类型
别让ctypes自动猜参数类型!如果不给libc.accept设置argtypes和restype,ctypes会乱转参数,很容易把指针截断或者类型不匹配,直接导致内核拿错内存地址。
正确的类型绑定示例:
# 根据你用的地址族,把第二个参数换成对应的结构体指针类型 libc.accept.argtypes = [ctypes.c_int, byref(sockaddr_ll), byref(c_uint32)] libc.accept.restype = ctypes.c_int # 顺便把socket/bind/listen也绑定上,避免其他环节出问题 libc.socket.argtypes = [ctypes.c_int, ctypes.c_int, ctypes.c_int] libc.socket.restype = ctypes.c_int libc.bind.argtypes = [ctypes.c_int, Structure, c_uint32] libc.bind.restype = ctypes.c_int
3. 确认前置步骤的正确性
虽然你说bind和listen成功了,但还是要核对:
- 绑定的地址结构体是不是完全符合对应地址族的要求?比如
AF_PACKET需要指定正确的网卡ifindex,AF_NETLINK要设置nl_pid。 listen的backlog参数是不是合法值(比如1-128之间,别传负数或者超大数)?
4. 检查内存权限
如果你是手动用mmap分配的内存,要确保权限是PROT_READ | PROT_WRITE,不然内核没法读写这块内存也会触发EFAULT。不过用ctypes的结构体实例或者create_string_buffer分配的内存一般都是没问题的。
最后给你一个完整的小示例(AF_PACKET版本),可以对照着改自己的代码:
import ctypes import os from ctypes import cdll, Structure, byref, c_int, c_uint16, c_int32, c_uint8, c_uint32 libc = cdll.LoadLibrary('libc.so.6') class sockaddr_ll(Structure): _fields_ = [ ("sll_family", c_uint16), ("sll_protocol", c_uint16), ("sll_ifindex", c_int32), ("sll_hatype", c_uint16), ("sll_pkttype", c_uint8), ("sll_halen", c_uint8), ("sll_addr", c_uint8 * 8) ] # 绑定函数类型 libc.socket.argtypes = [c_int, c_int, c_int] libc.socket.restype = c_int libc.bind.argtypes = [c_int, Structure, c_uint32] libc.bind.restype = c_int libc.listen.argtypes = [c_int, c_int] libc.listen.restype = c_int libc.accept.argtypes = [c_int, byref(sockaddr_ll), byref(c_uint32)] libc.accept.restype = c_int AF_PACKET = 17 SOCK_RAW = 3 ETH_P_ALL = 0x0003 # 创建套接字 sock_fd = libc.socket(AF_PACKET, SOCK_RAW, ETH_P_ALL) if sock_fd == -1: raise OSError(f"Socket create failed: {os.strerror(ctypes.get_errno())}") # 绑定到网卡(替换成你的网卡ifindex,用ip link show可以查) bind_addr = sockaddr_ll() bind_addr.sll_family = AF_PACKET bind_addr.sll_protocol = ETH_P_ALL bind_addr.sll_ifindex = 1 # 比如eth0的ifindex是1 if libc.bind(sock_fd, bind_addr, c_uint32(ctypes.sizeof(bind_addr))) == -1: raise OSError(f"Bind failed: {os.strerror(ctypes.get_errno())}") if libc.listen(sock_fd, 5) == -1: raise OSError(f"Listen failed: {os.strerror(ctypes.get_errno())}") # 接受连接 client_addr = sockaddr_ll() addr_len = c_uint32(ctypes.sizeof(client_addr)) conn_fd = libc.accept(sock_fd, byref(client_addr), byref(addr_len)) if conn_fd == -1: errno = ctypes.get_errno() print(f"Accept failed with errno {errno}: {os.strerror(errno)}") else: print(f"Accepted connection, fd: {conn_fd}")
如果还是有问题,建议打印ctypes.get_errno()对应的错误描述,再仔细核对参数的指针传递——90%的EFAULT都是指针传错了。
内容的提问来源于stack exchange,提问作者Patrick Sullivan

