BPF 过滤器在 Docker 容器内工作吗?
Do BPF filters work inside Docker containers?
我有一些 Python 代码可以打开套接字并使用 socket.setsockopt(..., SO_ATTACH_FILTER)
将 BPF 过滤器附加到它上面。不幸的是,生成过滤器的代码与其他内容有点混乱,但下面有一个可运行的大纲演示了我正在尝试做的事情。过滤器检查 EthernetType 字段是否为 ETH_P_IP
、IP 协议是否为 IPPROTO_UDP
以及目标端口是否为 68 - 这应该将数据包限制为仅 DHCP 响应。
运行 主机上带有 sudo python3 test.py
的下面的脚本每次都会导致超时,除非我在超时发生之前手动执行 DHCP 租约续订(或者偶尔如果某些东西续订 DHCP 租约在网络上)。但是,如果我在具有主机模式网络的 Docker 容器内做同样的事情,它几乎不会导致超时,总是会收到一个数据包,而且几乎不会收到 DHCP 响应。
docker 容器是 运行 --privileged --net=host
并且是 root 用户。
我需要做些什么才能让数据包过滤器在容器内工作吗?还是根本不可能?
import ctypes
import struct
import socket
ETH_P_ALL = 0x0003
SO_ATTACH_FILTER = 26
filters = bytes([0x28, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x08, 0x00, 0x08, 0x00
0x00, 0x30, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x06, 0x11, 0x00, 0x00, 0x00,
0x28, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x45, 0x00, 0x04, 0x00, 0xff, 0x1f, 0x00, 0x00, 0xb1,
0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x15, 0x00,
0x00, 0x01, 0x44, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0xdc, 0x05, 0x00, 0x00, 0x06, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00])
b = ctypes.create_string_buffer(filters)
mem_addr_of_filters = ctypes.addressof(b)
pf = struct.pack("HL", 11, mem_addr_of_filters)
def main():
sock = socket.socket(socket.PF_PACKET, socket.SOCK_RAW, socket.htons(ETH_P_ALL))
sock.bind(("eth0_bridge", ETH_P_ALL))
sock.settimeout(2)
sock.setsockopt(socket.SOL_SOCKET, SO_ATTACH_FILTER, pf)
try:
data = sock.recv(1500)
except:
print('Timeout')
exit(-1)
print('No timeout')
main()
这里的问题是 BPF 过滤器仅在 收到数据 时应用,而不是在进程从套接字缓冲区中取出数据时应用。因此,在绑定的套接字 (sock.bind(...)
) 和应用的过滤器 (sock.setsockopt(...)
) 之间有一个 window,可以接收和缓冲与过滤器不匹配的数据包。对 sock.recv(1500)
的后续调用将收到这样的数据包,即使它与过滤器不匹配。
避免此问题的唯一方法似乎是使用此序列:
- 将套接字设置为non-blocking模式
- 应用过滤器
- 接收数据包直到没有数据包接收
- 将套接字设置为阻塞模式
- 设置套接字超时
- 收到您感兴趣的数据包。
这会排出在绑定和应用过滤器之间收到的所有 non-matching 数据包。当然,它也可能会丢失您感兴趣的数据包。这对我的场景来说不是问题,但对您来说可能是。
我有一些 Python 代码可以打开套接字并使用 socket.setsockopt(..., SO_ATTACH_FILTER)
将 BPF 过滤器附加到它上面。不幸的是,生成过滤器的代码与其他内容有点混乱,但下面有一个可运行的大纲演示了我正在尝试做的事情。过滤器检查 EthernetType 字段是否为 ETH_P_IP
、IP 协议是否为 IPPROTO_UDP
以及目标端口是否为 68 - 这应该将数据包限制为仅 DHCP 响应。
运行 主机上带有 sudo python3 test.py
的下面的脚本每次都会导致超时,除非我在超时发生之前手动执行 DHCP 租约续订(或者偶尔如果某些东西续订 DHCP 租约在网络上)。但是,如果我在具有主机模式网络的 Docker 容器内做同样的事情,它几乎不会导致超时,总是会收到一个数据包,而且几乎不会收到 DHCP 响应。
docker 容器是 运行 --privileged --net=host
并且是 root 用户。
我需要做些什么才能让数据包过滤器在容器内工作吗?还是根本不可能?
import ctypes
import struct
import socket
ETH_P_ALL = 0x0003
SO_ATTACH_FILTER = 26
filters = bytes([0x28, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x08, 0x00, 0x08, 0x00
0x00, 0x30, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x06, 0x11, 0x00, 0x00, 0x00,
0x28, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x45, 0x00, 0x04, 0x00, 0xff, 0x1f, 0x00, 0x00, 0xb1,
0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x15, 0x00,
0x00, 0x01, 0x44, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0xdc, 0x05, 0x00, 0x00, 0x06, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00])
b = ctypes.create_string_buffer(filters)
mem_addr_of_filters = ctypes.addressof(b)
pf = struct.pack("HL", 11, mem_addr_of_filters)
def main():
sock = socket.socket(socket.PF_PACKET, socket.SOCK_RAW, socket.htons(ETH_P_ALL))
sock.bind(("eth0_bridge", ETH_P_ALL))
sock.settimeout(2)
sock.setsockopt(socket.SOL_SOCKET, SO_ATTACH_FILTER, pf)
try:
data = sock.recv(1500)
except:
print('Timeout')
exit(-1)
print('No timeout')
main()
这里的问题是 BPF 过滤器仅在 收到数据 时应用,而不是在进程从套接字缓冲区中取出数据时应用。因此,在绑定的套接字 (sock.bind(...)
) 和应用的过滤器 (sock.setsockopt(...)
) 之间有一个 window,可以接收和缓冲与过滤器不匹配的数据包。对 sock.recv(1500)
的后续调用将收到这样的数据包,即使它与过滤器不匹配。
避免此问题的唯一方法似乎是使用此序列:
- 将套接字设置为non-blocking模式
- 应用过滤器
- 接收数据包直到没有数据包接收
- 将套接字设置为阻塞模式
- 设置套接字超时
- 收到您感兴趣的数据包。
这会排出在绑定和应用过滤器之间收到的所有 non-matching 数据包。当然,它也可能会丢失您感兴趣的数据包。这对我的场景来说不是问题,但对您来说可能是。