Пакетные сокеты используются для отправления или приема пакетов на уровне
драйвера устройства (Уровень 2 OSI). Это позволяет пользователю реализовывать в
пространстве пользователя протокольные модули над физическим уровнем.
socket_type
равен либо
SOCK_RAW
для raw-пакетов (включая заголовок установки соединения), либо
SOCK_DGRAM
для подготовленных пакетов без заголовка уровня соединения. Информация
заголовка уровня соединения в общем формате предоставлена в
sockaddr_ll.
protocol
- номер протокола в соответствии с IEEE 802.3 в сетевом порядке байтов. Список
возможных протоколов приведен в файле
<linux/if_ether.h>
Если protocol содержит значение
htons(ETH_P_ALL),
то программой будут приниматься все протоколы.
Все входящие пакеты этого типа протокола будут передаваться пакетному сокету до
того, как они будут переданы протоколам, реализованным в ядре.
Открывать пакетные сокеты могут лишь процессы с идентификатором эффективного
пользователя, равным нулю, или имеющие возможность
CAP_NET_RAW.
Пакеты
SOCK_RAW
передаются драйверу устройства и принимаются от него без всяких изменений
данных пакета. При получении пакета адрес обрабатывается и передается в
стандартной структуре адреса
sockaddr_ll.
При передаче пакета буфер пользователя должен содержать заголовок физического
уровня. Пакет передается без изменений драйверу сетевого интерфейса, указанному
в адресе назначения. Hекоторые драйверы устройств всегда добавляют к заголовку
и другие заголовки.
SOCK_RAW
похож, но не совместим с устаревшим
SOCK_PACKET
для Linux 2.0.
SOCK_DGRAM
работает на несколько более высоком уровне. Физический заголовок удаляется
перед тем, как пакет отправляется пользователю. Пакеты, посылаемые через пакетный сокет
SOCK_DGRAM,
получают перед постановкой в очередь подходящий заголовок физического уровня в
соответствии с информацией об адресе назначения из
sockaddr_ll.
По умолчанию, все пакеты указанного типа протокола передаются пакетному сокету.
Для получения пакетов только определенного интерфейса используйте
bind(2):
это соединит пакетный сокет с интерфейсом, адрес которого указывается в
структуре
struct sockaddr_ll.
Для соединения используются только поля адреса
sll_protocol
и
sll_ifindex.
Операция
connect(2)
с пакетными сокетами не поддерживается.
Когда флаг
MSG_TRUNC
передается
recvmsg(2),
recv(2),
recvfrom(2),
то всегда возвращается действительная текущая длина пакета, даже
если она больше, чем буфер.
ТИПЫ АДРЕСОВ
sockaddr_ll является не зависимым от устройства адресом физического уровня.
struct sockaddr_ll {
unsigned short sll_family; /* Всегда AF_PACKET */
unsigned short sll_protocol; /* Протокол физического уровня */
int sll_ifindex; /* Hомер интерфейса */
unsigned short sll_hatype; /* Тип заголовка */
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 соответствует любому интерфейсу (возможно только для bind).
sll_hatype
- тип ARP, определенный в файле
linux/if_arp.h.
sll_pkttype
содержит тип пакета. Возможные типы:
PACKET_HOST
для пакетов, направляемых в локальную машину;
PACKET_BROADCAST
для широковещательной передачи пакета физического уровня;
PACKET_MULTICAST
для пакета, передаваемого по много адрес физического уровня;
PACKET_OTHERHOST
для пакета на другую машину, перехваченного драйвером устройства в
режиме прослушивания (promiscuous);
PACKET_OUTGOING
для пакетов, исходящих с локальной машины, запетленной на пакетный сокет. Эти
типы имеют значение только для приема.
sll_addr
и
sll_halen
содержат адрес физического уровня (например IEEE 802.3) и его длину. Точная
интерпретация зависит от устройства.
Когда вы посылаете покеты, то достаточно определить только
sll_family,
sll_addr,
sll_halen,
sll_ifindex.
Остальные поля должны быть равны 0.
sll_hatype
и
sll_pkttype
определяются для вас по принимаемым пакетам.
Для привязки используются только
sll_protocol
и
sll_ifindex.
ОПЦИИ СОКЕТОВ
Пакетные сокеты могут быть использованы для настройки многоадресного вещания
физического уровня и режима прослушивания. Это делается с помощью вызова
setsockopt(2)
на пакетный сокет с SOL_PACKET и одной из опций:
PACKET_ADD_MEMBERSHIP
для добавления соединения (binding), или
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
разрешает прием всех пакетов с общего носителя - этот режим часто называют
"режимом прослушивания" (``promiscuous mod''),
PACKET_MR_MULTICAST
соединяет (bind) сокет с многоадресной группой физического уровня, указанной в
mr_address
и
mr_alen,
а
PACKET_MR_ALLMULTI
указывает сокету принимать все многоадресные пакеты, приходящие на интерфейс.
В дополнение ко всему, для этих целей могут быть использованы традиционные
ioctl-вызовы:
SIOCSIFFLAGS,
SIOCADDMULTI,
SIOCDELMULTI.
СИСТЕМНЫЕ ВЫЗОВЫ
SIOCGSTAMP
может быть использован для получения временных меток последнего принятого
пакета. Аргументом является структура
struct timeval.
Кроме того, возможно использование всех стандартынх вызовов ioctl, определенных в
netdevice(7)
и
socket(7).
ОБРАБОТКА ОШИБОК
Пакетные сокеты не обрабатывают ошибки за исключением тех, которые возникают при
передаче пакета драйверу устройства. Они не имеют понятия об отложенной обработке ошибок.
СОВМЕСТИМОСТЬ
В Linux 2.0 единственным способом получить пакетный сокет был вызов
socket(PF_INET, SOCK_PACKET, protocol).
Этот способ все еще поддерживается, но использовать его настоятельно не рекомендуется.
Основное отличие между этими двумя способами состоит в том, что
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
- это имя устройства в виде строки, оканчивающейся нулем, например, eth0.
Эта структура устарела и не должна быть указана в новом коде.
ЗАМЕЧАНИЯ
В переносимых программах рекомендуется использовать
PF_PACKET
с помощью
pcap(3);
хотя это относится только к вспомогательному набору возможностей
PF_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. Поэтому подключиться (to bind) к
ETH_P_802_3
невозможно; вместо этого подключайтесь к
ETH_P_802_2
и выполняйте мультиплексирование протокола сами. По умолчанию для отправки
используется стандартная инкапсуляция Ethernet DIX со вставленным протоколом.
Пакетные сокеты не обрабатываются входящими и исходящими правилами сетевого
экрана.
НАЙДЕННЫЕ ОШИБКИ
ENETDOWN
Интерфейс неактивен.
ENOTCONN
Hе передан адрес интерфейса.
ENODEV
Hеизвестное имя устройства или индекс интерфейса,
указанные в адресе интерфейса.
EMSGSIZE
Размер пакета больше, чем размер интерфейса MTU.
ENOBUFS
Hедостаточно памяти для размещения в ней пакета.
EFAULT
Пользователь передал неправильный адрес памяти.
EINVAL
Hеверный аргумент.
ENXIO
Адрес интерфейса содержит неправильный индекс интерфейса.
EPERM
Пользователь не имеет прав на выполнение этой операции.
EADDRNOTAVAIL
Передан неизвестный адрес многоадресной группы.
ENOENT
Пакет не получен.
Дополнительные ошибки могут генерироваться низкоуровенвым драйвером.
ВЕРСИИ
PF_PACKET
появился в Linux 2.2. Ранние версии Linux поддерживали только
SOCK_PACKET.
НАЙДЕННЫЕ ОШИБКИ
glibc 2.1 не имеет определения
SOL_PACKET.
Рекомендуемое решение:
#ifndef SOL_PACKET
#define SOL_PACKET 263
#endif
Это поправка внесена в более поздние версии и не встречается в системах lic5.
Обработка IEEE 802.2/803.3 LLC должна считаться ошибкой.
Фильтры сокетов не описаны.
Расширение
MSG_TRUNC
для recvmsg является очень плохо сделанной правкой и должна быть заменена
на управляющее сообщение. На данный момент нет иного выхода,
кроме как определение настоящего адреса назначения пакета по SOCK_DGRAM.
БЛАГОДАРНОСТИ
Эта страница руководства была написана Энди Клином (Andi Kleen) при участии
Мэтью Вилкокса (Matthew Wilcox).
PF_PACKET в Linux 2.2 (на основе кода Алана Кокса (Alan Cox) и др.)
был реализован Алексеем Кузнецовым (Alexey Kuznetsov).