Фильтр BPF не работает

Кто-нибудь может предположить, почему эта (классическая) программа BPF иногда пропускает пакеты без DHCP-ответа:

# Load the Ethertype field
BPF_LD | BPF_H | BPF_ABS    12
# And reject the packet if it's not 0x0800 (IPv4)
BPF_JMP | BPF_JEQ | BPF_K   0x0800    0    8

# Load the IP protocol field
BPF_LD | BPF_B | BPF_ABS    23
# And reject the packet if it's not 17 (UDP)
BPF_JMP | BPF_JEQ | BPF_K   17        0    6

# Check that the packet has not been fragmented
BPF_LD | BPF_H | BPF_ABS    20
BPF_JMP | BPF_JSET | BPF_K  0x1fff    4    0

# Load the IP header length field
BPF_LDX | BPF_B | BPF_MSH   14
# And load that offset + 16 to get the UDP destination port
BPF_LD | BPF_IND | BPF_H    16
# And reject the packet if the destination port is not 68
BPF_JMP | BPF_JEQ | BPF_K   68        0    1

# Accept the frame
BPF_RET | BPF_K             1500
# Reject the frame
BPF_RET | BPF_K             0

Он не пропускает каждый кадр, но при большой нагрузке на сеть довольно часто дает сбои. Я тестирую это с помощью этой программы Python 3:

import ctypes
import struct
import socket
ETH_P_ALL = 0x0003
SO_ATTACH_FILTER = 26
SO_ATTACH_BPF = 50
filters = [
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,
]

filters = bytes(filters)

b = ctypes.create_string_buffer(filters)
mem_addr_of_filters = ctypes.addressof(b)
pf = struct.pack("HL", 11, mem_addr_of_filters)
pf = bytes(pf)

def main():                                                                                     
    sock = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(ETH_P_ALL))            
    sock.bind(("eth0", ETH_P_ALL))                                                              
    sock.setsockopt(socket.SOL_SOCKET, SO_ATTACH_FILTER, pf)                                    
#    sock.send(req)                                                                             
    sock.settimeout(1)                                                                          
    try:                                                                                        
        data = sock.recv(1500)                                                                  
                                                                                                
        if data[35] == 0x43:                                                                    
            return                                                                              
        print('Packet got through: 0x{:02x} 0x{:02x}, 0x{:02x}, 0x{:02x}'.format(data[12], data[13], data
    except:                   
        print('Timeout')                                                            
        return                                              
    sock.close()                                            
                              
for ii in range(1000):                                                                                   
    main()                    

Если я сделаю это во время передачи большого основного файла на хост, на котором запущен этот скрипт, он не достигнет односекундного тайм-аута в подавляющем большинстве, но не во всех случаях. При более легкой нагрузке сбои случаются намного реже - например, вращение по ssh-ссылке во время приема сокета; иногда он проходит 1000 итераций без сбоев.

Рассматриваемый хост - Linux 4.9.0. Ядро имеет CONFIG_BPF=y.

Изменить

Для более простой версии того же вопроса, почему эта программа BPF вообще пропускает любые пакеты:

BPF_RET | BPF_K    0

Редактировать 2 Приведенные выше тесты проводились на компьютере с ARM64. Я перепроверил на amd64/Linux 5.9.0. Я все еще вижу неудачи, хотя и не так много.


person Tom    schedule 04.01.2021    source источник


Ответы (1)


Я получил ответ на LKML с объяснением этого.

Проблема в том, что фильтр применяется, когда кадр поступает на интерфейс, а не когда он передается в пользовательское пространство с помощью recv(). Поэтому при большой нагрузке кадры приходят между созданием сокета с socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(ETH_P_ALL)) и применением фильтра с sock.setsockopt(socket.SOL_SOCKET, SO_ATTACH_FILTER, pf). Эти кадры стоят в очереди; после применения фильтра к последующим прибывающим пакетам применяется фильтр.

Таким образом, после применения фильтра необходимо удалить из сокета все стоящие в очереди кадры, прежде чем вы сможете положиться на фильтр.

person Tom    schedule 19.01.2021
comment
Не могли бы вы дать ссылку на обсуждение вверх по течению? - person pchaigno; 19.01.2021
comment
lkml.org/lkml/2021/1/15/835 - person Tom; 20.01.2021