Для получения доступа к BPF необходимо открыть (вызвав функцию open
) еще не открытое каким-либо другим процессом устройство BPF. Скажем, можно попробовать /dev/bpf0
, и если будет возвращена ошибка EBUSY
, то — /dev/bpf1
, и т.д. Когда устройство будет открыто, потребуется выполнить примерно 12 команд ioctl
для задания характеристик устройства, таких как загрузка фильтра, время ожидания для считывания, размер буфера, присоединение канального уровня к устройству BPF, включение смешанного режима, и т.д. Затем с помощью функций read
и write
осуществляется ввод и вывод.
29.3. DLPI: интерфейс поставщика канального уровня
SVR4 обеспечивает доступ к канальному уровню через DLPI (Data Link Provider Interface — интерфейс поставщика канального уровня). DLPI — это не зависящий от протокола интерфейс, разработанный в AT&T и служащий средством связи с сервисами, обеспечиваемыми канальным уровнем [124]. Доступ к DLPI осуществляется посредством отправки и получения сообщений через потоки STREAMS.
Для подсоединения к канальному уровню приложение просто открывает устройство (например, le0
) с помощью команды open и использует запрос DL_ATTACH_REQ
. Но для эффективной работы используются два дополнительных модуля: pfmod
, который осуществляет фильтрацию внутри ядра, и bufmod
, буферизующий данные, предназначенные для приложения. Это показано на рис. 29.2.
Рис. 29.2. Захват пакета с использованием DLPI, pfmod и bufmod
Концептуально DLPI аналогичен BPF. pfmod
поддерживает фильтрацию внутри ядра, используя псевдопроцессор, a bufmod
сокращает количество данных и системных вызовов, поддерживая длину захвата и время ожидания для считывания.
Одно интересное различие, тем не менее, заключается в том, что для BPF и фильтров pfmod
используются разные типы псевдопроцессоров. Фильтр BPF — это pfmod
использует дерево булевых выражений. В первом случае естественным является отображение в код для вычислительной машины с регистровой организацией, а во втором — в код для машины со стековой организацией [72]. В статье [72] показано, что реализация CFG, используемая в BPF, обычно работает быстрее, чем дерево булевых выражений, в 3-20 раз в зависимости от сложности фильтра.
Еще одно отличие состоит в том, что BPF всегда выполняет фильтрацию перед копированием пакета, чтобы не копировать те пакеты, которые будут сброшены фильтром. В некоторых реализациях DLPI пакеты сначала копируются в модуль pfmod
, который затем может сбрасывать их.
29.4. Linux: SOCK_PACKET и PF_PACKET
Существует два метода получения пакетов канального уровня в Linux. Первоначальный метод получил более широкое распространение, но является менее гибким. Он состоит в создании сокета типа SOCK_PACKET
. Новый метод, предоставляющий больше возможностей для настройки фильтров и оптимизации производительности, состоит в создании сокета семейства PF_PACKET
. В любом случае мы должны обладать правами привилегированного пользователя (аналогичные необходимым для создания символьного сокета), а третий аргумент функции socket должен быть ненулевым значением, задающим тип кадра Ethernet. При использовании сокетов PF_PACKET
второй аргумент socket
может быть константой SOCK_DGRAM
(для получения обработанных пакетов без заголовка канального уровня) или SOCK_RAW
(для получения пакетов целиком). Сокеты SOCK_PACKET
передают пакеты только целиком. Например, для получения всех кадров канального уровня мы пишем:
fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); /* в новых системах */
или
fd = socket(AF_INET, SOCK_PACKET, htons(ETH_P_ALL)); /* в старых системах */
В результате этого будут возвращены кадры для всех протоколов, получаемые канальным уровнем. Если нам нужны кадры IPv4, то вызов будет таким:
fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP)); /* в новых системах */
fd = socket(AF_INET, SOCK_PACKET, htons(ETH_P_IP)); /* в старых системах */
Другие константы, которые могут использоваться в качестве последнего аргумента, — это, например, ETH_P_ARP
и ETH_P_IPV6
.