PACKET(7) Руководство программиста Linux PACKET(7)

ИМЯ

packet - пакетный интерфейс на уровне устройства

ОБЗОР

#include <sys/socket.h>
#include <linux/if_packet.h>
#include <net/ethernet.h> /* протоколы L2 */
packet_socket = socket(AF_PACKET, int socket_type, int protocol);

ОПИСАНИЕ

Пакетные сокеты используются для приёма и передачи неструктурированных пакетов на уровне драйвера устройства (второй уровень OSI). Они позволяют пользователю реализовывать модули протоколов в пользовательском пространстве поверх физического уровня.

Значением socket_type может быть SOCK_RAW — для неструктурированных пакетов, содержащих заголовок уровня связи, или SOCK_DGRAM — для подготовленных (cooked) пакетов без заголовка уровня связи. Информация заголовка уровня связи имеет общий формат и предоставляется структурой sockaddr_ll. В protocol содержится номер протокола согласно IEEE 802.3 в сетевом порядке байт. Список допустимых протоколов можно найти в заголовочном файле <linux/if_ether.h>. Если значение протокола равно htons(ETH_P_ALL), то принимаются все протоколы. Все входящие пакеты с этим типом протокола будут переданы в пакетный сокет до их передачи протоколам, реализуемым в ядре.

Для создания пакетного сокета процесс должен иметь мандат CAP_NET_RAW в пользовательском пространстве имён, определяемом по его сетевому пространству имён.

Пакеты SOCK_RAW передаются в и из драйвера устройства без каких-либо изменений в данных пакета. При получении пакета, адрес по-прежнему анализируется и передаётся в стандартной адресной структуре sockaddr_ll. При отправке пакета, выделенный пользователем буфер должен содержать заголовок физического уровня. Этот пакет затем ставится без изменений в очередь сетевого драйвера интерфейса, определяемого адресом назначения. Некоторые драйверы устройств всегда добавляют другой заголовок. Пакеты SOCK_RAW похожи, но не совместимы с устаревшими AF_INET/SOCK_PACKET из Linux 2.0.

При типе SOCK_DGRAM обработка происходит на чуть более высоком уровне. Физический заголовок удаляется перед передачей пакета пользователю. Пакеты, посылаемые через пакетный сокет SOCK_DGRAM, перед постановкой в очередь получают подходящий заголовок физического уровня на основе информации из адреса назначения в sockaddr_ll.

По умолчанию, все пакеты заданного типа протокола передаются в пакетный сокет. Для получения пакетов только из определённого интерфейса используйте bind(2), задав адрес в struct sockaddr_ll для привязки пакетного сокета к интерфейсу. Поля, используемые для привязывания: sll_family (должно быть равно AF_PACKET), sll_protocol и sll_ifindex.

Операция connect(2) не поддерживается для пакетных сокетов.

Если в recvmsg(2), recv(2) или recvfrom(2) передаётся флаг MSG_TRUNC, то возвращается реальная длина пакета в канале, даже если значение длиннее буфера.

Типы адресов

Структура sockaddr_ll описывает независимый от устройства адрес на физическом уровне.

struct sockaddr_ll {

    unsigned short sll_family;   /* всегда равно AF_PACKET */

    unsigned short sll_protocol; /* протокол физического уровня */

    int            sll_ifindex;  /* номер интерфейса */

    unsigned short sll_hatype;   /* тип аппаратного ARP */

    unsigned char  sll_pkttype;  /* тип пакета */

    unsigned char  sll_halen;    /* длина адреса */

    unsigned char  sll_addr[8];  /* адрес на физическом уровне */
};

Поля этой структуры имеют следующее назначение:

  • Поле sll_protocol содержит стандартные типы протокола ethernet в сетевом порядке байт; значения определены в заголовочном файле <linux/if_ether.h>. Это значение по умолчанию для протокола сокета.
  • Поле sll_ifindex содержит индекс интерфейса (смотрите netdevice(7)); 0 означает любой интерфейс (допустимо только для привязывания). Поле sll_hatype содержит тип ARP; значения хранятся в заголовочном файле <linux/if_arp.h>.
  • В поле sll_pkttype содержится тип пакета. Допустимые типы: PACKET_HOST — пакет предназначен локальному узлу, PACKET_BROADCAST — широковещательный пакет физического уровня, PACKET_MULTICAST — пакет, посланный на групповой (multicast) адрес физического уровня, PACKET_OTHERHOST — пакет предназначен не тому узлу, где он пойман драйвером устройства в неразборчивом режиме, PACKET_OUTGOING — пакет, поступивший от локального узла и завёрнутый обратно в пакетный сокет. Эти типы имеют смысл только для принятых пакетов.
  • В полях sll_addr и sll_halen содержится адрес физического уровня (например, IEEE 802.3) и его длина. Конкретный смысл зависит от устройства.

Когда вы посылаете пакеты, достаточно указать sll_family, sll_addr, sll_halen, sll_ifindex и sll_protocol. Остальные поля должны равняться 0. Поля sll_hatype и sll_pkttype заполняются в получаемых пакетах для вашей информированности.

Параметры сокета

Параметры пакетных сокетов настраиваются вызовом setsockopt(2) с уровнем SOL_PACKET.

Пакетные сокеты можно использовать для настройки неразборчивого режима и групповой рассылки на физическом уровне. Параметр PACKET_ADD_MEMBERSHIP добавляет привязку, PACKET_DROP_MEMBERSHIP отменяет её. Для обоих в качестве аргумента передаётся структура packet_mreq:
struct packet_mreq {

    int            mr_ifindex;    /* индекс интерфейса */

    unsigned short mr_type;       /* действие */

    unsigned short mr_alen;       /* длина адреса */

    unsigned char  mr_address[8]; /* адрес физ-кого уровня */
};
В mr_ifindex содержится индекс интерфейса, состояние которого нужно изменить. В поле mr_type указывается какое действие нужно выполнить. Значение PACKET_MR_PROMISC включает приём всех пакетов из общего носителя (часто называется «неразборчивый режим»), PACKET_MR_MULTICAST привязывает сокет к групповой рассылке физического уровня, задаваемой в mr_address и mr_alen, а PACKET_MR_ALLMULTI заставляет сокет принимать все пакеты групповых рассылок, поступающих на интерфейс.
Также, для тех же целей можно использовать обычные ioctl SIOCSIFFLAGS, SIOCADDMULTI, SIOCDELMULTI.
Если включён этот двоичный параметр, то пакетный сокет передаёт структуру метаданных вместе с каждым пакетом в управляющем поле recvmsg(2). Структуру можно прочитать с помощью cmsg(3). Она определена как:
struct tpacket_auxdata {

    __u32 tp_status;

    __u32 tp_len;      /* длина пакета */

    __u32 tp_snaplen;  /* захваченная длина */

    __u16 tp_mac;

    __u16 tp_net;

    __u16 tp_vlan_tci;

    __u16 tp_padding;
};
Для масштабирования обработки на несколько нитей, пакетные сокеты можно объединять в разветвлённую группу (fanout group). В этом режиме каждый подходящий пакет ставится в очередь только одного сокета в группе. Сокет добавляется в разветвлённую группу вызовом setsockopt(2) с уровнем SOL_PACKET и параметром PACKET_FANOUT. Каждое сетевое пространство имён может включать до 65536 независимых групп. Сокет выбирает группу по закодированному ID в первых 16 битах целочисленного значения параметра. Первый пакетный сокет, подключаемый к группе неявно её создаёт. Для успешного подключения к существующей группе все дальнейшие пакетные сокеты должны иметь тот же протокол, настройки устройства, режим разветвления и флаги (смотрите далее). Пакетные сокеты могут покинуть группу только при закрытия сокета. Группа удаляется после закрытия последнего сокета.
Для разветвления поддерживается несколько алгоритмов распределения трафика по сокетам:
  • Режим по умолчанию PACKET_FANOUT_HASH посылает пакеты из одного потока в один и тот же сокет для обеспечения упорядочивания по потоку. Для каждого пакета выбирается сокет, получаемый из хэша потока пакетов, взятого по модулю количества сокетов в группе, где хэш потока — это хэш адреса сетевого уровня и необязательных полей портов транспортного уровня.
  • Режим балансировки нагрузки PACKET_FANOUT_LB реализует карусельный алгоритм.
  • В режиме PACKET_FANOUT_CPU выбираются сокеты исходя из ЦП, на который поступил пакет.
  • В режиме PACKET_FANOUT_ROLLOVER все данные обрабатываются одним сокетом, следующий задействуется, если текущий занят (backlogged).
  • В режиме PACKET_FANOUT_RND сокет выбирается согласно генератору псевдослучайных чисел.
  • В режиме PACKET_FANOUT_QM (доступен, начиная с Linux 3.14) сокет выбирается с помощью записанного queue_mapping из полученной skb.
Режимы разветвления могут учитывать дополнительные параметры. Фрагментация IP приводит к тому, что пакеты одного потока имеют разные хэши потоков. Если установлен флаг PACKET_FANOUT_FLAG_DEFRAG, то пакеты будут дефрагментироваться перед применением разветвления, что позволит сохранить порядок даже в этом случае. Параметры режима разветвления задаются во вторых 16 битах целочисленного значения параметра. Флаг PACKET_FANOUT_FLAG_ROLLOVER включает механизм перекатывания в качестве запасного: если первоначальный алгоритм разветвления выбрал занятый сокет, то пакет переходит на следующий доступный.
Когда в кольце передачи обнаруживается некорректный пакет, то по умолчанию его состояние в tp_status сбрасывается в TP_STATUS_WRONG_FORMAT и происходит немедленная отмена передачи. Некорректный пакет блокирует как свою отправку, так и всех следующих пакетов в очереди. Ошибка в формате должна быть исправлена, соответствующий tp_status сброшен в значение TP_STATUS_SEND_REQUEST, а передача перезапущена с помощью send(2). Однако, если задан параметр PACKET_LOSS, то все некорректные пакеты будут пропускаться, их tp_status сбрасываться в TP_STATUS_AVAILABLE и процесс передачи продолжаться.
По умолчанию, в кольцо приёма пакетов сразу за пакетом записывается структура метаданных и заполнитель для выравнивания. Этот целочисленный параметр резервирует дополнительное свободное место.
Включает создание отображаемого в памяти кольцевого буфера асинхронного приёма пакетов. Пакетный сокет резервирует непрерывную область в адресном пространстве приложения, размечает её как массив пакетных слотов и последовательно копирует пакеты (не более tp_snaplen) в слоты. В начале каждого пакета помещается структура метаданных, похожая на tpacket_auxdata. В поле протокола кодируется смещение данных от начала заголовка метаданных. В tp_net хранится смещение сетевого уровня. Если тип пакетного сокета — SOCK_DGRAM, то это делается и для tp_mac. Если тип — SOCK_RAW, то в этом поле хранится смещение на кадр канального уровня. Пакетный сокет и приложение обмениваются началом и концом кольца через поле tp_status. Пакетному сокету принадлежат все слоты со значением tp_status равным TP_STATUS_KERNEL. После заполнения слота, изменяется состояние слота и права на него передаются приложению. При нормальной работе в новом значении tp_status, как минимум, установлен бит TP_STATUS_USER, что показывает, что принятый пакет был сохранён. Когда приложение заканчивает обработку пакета, оно передаёт права на слот обратно сокету посредством установки tp_status в значение TP_STATUS_KERNEL.
Для пакетных сокетов реализовано несколько вариантов пакетных колец. Информацию о реализации можно найти в файле Documentation/networking/packet_mmap.txt из дерева исходного кода ядра Linux.
Возвращает статистику по пакетному сокету в виде структуры
struct tpacket_stats {

    unsigned int tp_packets;  /* общее количество пакетов */

    unsigned int tp_drops;    /* кол-во отброшенных пакетов */
};
При получении статистики сбрасываются внутренние счётчики. Если используется вариант кольца TPACKET_V3, то статистика имеет другую структуру.
В кольце приёма пакетов всегда сохраняется метка времени в заголовке метаданных. По умолчанию, это метка генерируется ПО при копировании пакета в кольцо. Данный целочисленный параметр задаёт тип метки времени. Кроме значения по умолчанию, поддерживается два аппаратных формата, описанных в файле Documentation/networking/timestamping.txt из дерева исходного кода ядра Linux.
Включает создание отображаемого в памяти кольцевого буфера передачи пакетов. Этот параметр подобен PACKET_RX_RING и имеет те же аргументы. Приложение записывает пакеты в слоты со значением tp_status равным TP_STATUS_AVAILABLE и планирует их для передачи делая значение tp_status равным TP_STATUS_SEND_REQUEST. Когда пакеты готовы к передаче, приложение вызывает send(2) или его вариант. Поля buf и len в этом вызове игнорируются. Если передаётся адрес с помощью sendto(2) или sendmsg(2), то он заменяет сокетное значение по умолчанию. При успешной передаче сокет сбрасывает значение tp_status в TP_STATUS_AVAILABLE. При ошибке передача немедленно прерывается, если не задан PACKET_LOSS.
По умолчанию, PACKET_RX_RING создаёт кольцо приёма пакетов по варианту TPACKET_V1. Для создания другого варианта, задайте желаемый, указав целочисленное значение в этом параметре перед созданием кольца.
По умолчанию, пакеты, посылаемые через пакетные сокеты, проходят через уровень ядра qdisc (управление трафиком), что правильно в подавляющем большинстве случаев. Для программно-аппаратных комплексов, использующих пакетные фильтры для затопления сети — например, для тестирования устройств под нагрузкой, подобно тому, как это делает pktgen — этот уровень можно не задействовать, установив целочисленной параметр в 1. Побочным эффектом будет отмена пакетной буферизации на уровне qdisc, что приведёт к увеличению отброшенных пакетов при занятости передающих очередей сетевого устройства; поэтому, пользуйтесь с осторожностью.

Вызовы ioctl

Вызов SIOCGSTAMP можно использовать для получения метки времени последнего полученного пакета. Аргументом является struct timeval.

Также, для пакетных сокетов работают все стандартные ioctl, определённые в netdevice(7) и socket(7).

Обработка ошибок

Пакетные сокеты не выполняют обработку ошибок, кроме ошибок, которые возникают при передаче пакета драйверу устройства. В них не заложен принцип ожидания ошибки.

ОШИБКИ

Передан неизвестный адрес групповой рассылки.
Пользователь передал неправильный адрес памяти.
Неверный аргумент.
Пакет больше, чем интерфейс MTU.
Интерфейс не поднят.
Недостаточно памяти для размещения пакета.
В адресе интерфейса указано неизвестное имя устройства или индекс интерфейса.
Пакет не принят.
Не передан адрес интерфейса.
В адресе интерфейса содержится некорректный индекс интерфейса.
У пользователя недостаточно прав для выполнения этой операции.

Также, драйвером низкого уровня могут генерироваться другие ошибки.

ВЕРСИИ

AF_PACKET появился в Linux 2.2. В ранних версиях Linux поддерживался только SOCK_PACKET.

ЗАМЕЧАНИЯ

Для переносимых программ предлагается использовать AF_PACKET в pcap(3), хотя это покрывает не весь набор возможностей AF_PACKET.

Пакетные сокеты SOCK_DGRAM не пытаются создать или разобрать заголовок IEEE 802.2 LLC из кадров IEEE 802.3. Если для отправки в качестве протокола указан ETH_P_802_3, то ядро создаёт кадр 802.3 и заполняет поле длины; пользователь передаёт заголовок LLC в пакете уже полностью заполненным. Входящие пакеты 802.3 не уплотняются по полям протокола DSAP/SSAP; вместо этого они передаются пользователю как протокол ETH_P_802_2 с начальным заголовком LLC. То есть невозможно выполнить привязку к ETH_P_802_3; вместо этого выполняйте привязку к ETH_P_802_2 и выполняйте протокольное уплотнение самостоятельно. По умолчанию, отправка происходит в стандартной упаковке Ethernet DIX с заполненным полем протокола.

Пакетные сокеты недоступны (not subject) во входной и выходной цепочках межсетевого экрана.

Совместимость

В Linux 2.0 единственным способом получить пакетный сокет является вызов:

socket(AF_INET, SOCK_PACKET, протокол)

Он всё ещё поддерживается, но устарел и настоятельно не рекомендуется. Основным отличием между методами — для указания интерфейса через SOCK_PACKET используется старая struct sockaddr_pkt, которая не предоставляет независимого физического уровня.

struct sockaddr_pkt {

    unsigned short spkt_family;

    unsigned char  spkt_device[14];

    unsigned short spkt_protocol;
};

В spkt_family содержится тип устройства, в spkt_protocol — тип протокола IEEE 802.3, определённый в <sys/if_ether.h>, а в spkt_device — имя устройства в виде строки с null в конце, например, eth0.

Эта структура устарела и не должна использоваться в новом коде.

ДЕФЕКТЫ

Способ обработки IEEE 802.2/803.3 LLC не считается за дефектный.

Не описаны сокетные фильтры.

Расширение MSG_TRUNC recvmsg(2) является неудачным решением и должно быть заменено на управляющее сообщение. Пока нет способа получить первоначальный адрес назначения пакетов через SOCK_DGRAM.

СМОТРИТЕ ТАКЖЕ

socket(2), pcap(3), capabilities(7), ip(7), raw(7), socket(7)

В RFC 894 описана упаковка стандартного IP Ethernet. В RFC 1700 описана упаковка IP IEEE 802.3.

Заголовочный файл <linux/if_ether.h> содержит протоколы физического уровня.

Дерево исходного кода ядра Linux. В /Documentation/networking/filter.txt описано как к пакетным сокетам применять Berkeley Packet Filters. В /tools/testing/selftests/net/psock_tpacket.c содержится пример исходного кода для всех доступных версий PACKET_RX_RING и PACKET_TX_RING.

2017-09-15 Linux