#python #linux #docker #bpf
#python #linux #docker #bpf
Вопрос:
У меня есть некоторый код на Python, который открывает сокет и подключает к нему фильтр BPF с помощью socket.setsockopt(..., SO_ATTACH_FILTER)
. К сожалению, код, который генерирует фильтр, немного запутан с другими вещами, но ниже есть выполнимая схема, демонстрирующая, что я пытаюсь сделать. Фильтр проверяет, что поле 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()
Комментарии:
1. В конце первой строки вашего
filters
массива байтов отсутствует запятая. На всякий случай, это просто ошибка копирования-вставки и есть ли у вас эта запятая в вашем коде?2.
tcpdump ip proto \udp and dst port 68
Работает ли в вашем контейнере? Он должен использовать тот же фильтр.3. @Qeole — Да, tcpdump работает так, как ожидалось. Однако оказывается, что это не относится конкретно к контейнеру docker, поэтому я собираюсь задать другой вопрос.
Ответ №1:
Проблема здесь заключалась в том, что фильтры BPF применяются только при получении данных, а не при извлечении данных из буфера сокета. Таким образом, существует окно между привязкой сокета ( sock.bind(...)
) и применяемым фильтром ( sock.setsockopt(...)
), где пакеты, которые не соответствуют фильтру, могут быть получены и буферизованы. Последующий вызов sock.recv(1500)
получит такой пакет, даже если он не соответствует фильтру.
Похоже, единственный способ избежать этой проблемы — использовать эту последовательность:
- Установите сокет в неблокирующий режим
- Примените фильтр
- Принимайте пакеты до тех пор, пока не останется пакетов для приема
- Установите сокет в режим блокировки
- Установите время ожидания сокета
- Получите интересующий вас пакет.
Это приводит к удалению любых несоответствующих пакетов, которые были получены между привязкой и применяемым фильтром. Конечно, он также может пропускать интересующие вас пакеты. Это не проблема для моего сценария, но может быть для вашего.