Что в этом фрагменте неправильно? Имеет ли это значение?
Глава 28
Символьные сокеты
28.1. Введение
Символьные, или неструктурированные, сокеты( raw sockets) обеспечивают три возможности, не предоставляемые обычными сокетами TCP и UDP.
1. Символьные сокеты
позволяют читать и записывать пакеты ICMPv4, IGMPv4 и ICMPv6. Например, программа
ping
посылает эхо-запросы ICMP и получает эхо-ответы ICMP. (Наша оригинальная версия программы
ping
приведена в разделе 28.5.) Демон маршрутизации многоадресной передачи
mrouted
посылает и получает пакеты IGMPv4.
2. Эта возможность также позволяет реализовывать как пользовательские процессы те приложения, которые построены с использованием протоколов ICMP и IGMP, вместо того чтобы помещать большее количество кода в ядро. Например, подобным образом построен демон обнаружения маршрутов (
in.rdisc
в системе Solaris 2.x. В приложении F книги [111] рассказывается, как можно получить исходный код открытой версии). Этот демон обрабатывает два типа сообщений ICMP, о которых ядро ничего не знает (извещение маршрутизатора и запрос маршрутизатору).
С помощью символьных сокетов процесс может читать и записывать IPv4-дейтаграммы с полем протокола IPv4, которое не обрабатывается ядром. Посмотрите еще раз на 8-разрядное поле протокола IPv4, изображенное на рис. А.1. Большинство ядер обрабатывают дейтаграммы, содержащие значения поля протокола 1 (ICMP), 2 (IGMP), 6 (TCP) и 17 (UDP). Но для этого поля определено гораздо большее количество значений, полный список которых приведен в реестре IANA «Номера протоколов» (Protocol Numbers). Например, протокол маршрутизации OSPF не использует протоколы TCP или UDP, а работает напрямую с протоколом IP, устанавливая в поле протокола значение 89 для IP-дейтаграмм. Программа
gated
, реализующая OSPF, должна использовать для чтения и записи таких IP-дейтаграмм символьный сокет, поскольку они содержат значение поля протокола, о котором ничего не известно ядру. Эта возможность также переносится в версию IPv6.
3. С помощью символьных сокетов процесс может построить собственный заголовок IPv4 при помощи параметра сокета
IP_HDRINCL
. Такую возможность имеет смысл использовать, например, для построения собственного пакета UDP или TCP. Подобный пример приведен в разделе 29.7.
В данной главе описывается создание символьных сокетов, а также операции ввода и вывода с этими сокетами. Далее приводятся версии программ
ping
и
traceroute
, работающие как с версией IPv4, так и с версией IPv6.
28.2. Создание символьных сокетов
При создании символьных сокетов выполняются следующие шаги:
1. Символьный сокет создается функцией
socket
со вторым аргументом
SOCK_RAW
. Третий аргумент (протокол) обычно ненулевой. Например, для создания символьного сокета IPv4 следует написать:
int sockfd;
sockfd = socket(AF_INET, SOCK_RAW, protocol);
где
protocol
— одна из констант
IPPROTO_xxx
, определенных в подключенном заголовочном файле
<netinet/in.h>
, например
IPPROTO_ICMP
.
Только привилегированный пользователь может создать символьный сокет. Такой подход предотвращает отправку IP-дейтаграмм в сеть обычными пользователями.
2. Параметр сокета
IP_HDRINCL
может быть установлен следующим образом:
const int on = 1;
if (setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on)) < 0)
обработка ошибки
В следующем разделе описывается действие этого параметра.
3. На символьном сокете можно вызвать функцию
bind
, но это делается редко. Эта функция устанавливает только локальный адрес: на символьном сокете нет понятия порта. Что касается вывода, вызов функции
bind
устанавливает IP-адрес отправителя, который будет использоваться для дейтаграмм, отправляемых на символьном сокете (только если не установлен параметр сокета
IP_HDRINCL
). Если функция
bind
не вызывается, ядро использует в качестве IP-адреса отправителя основной IP-адрес исходящего интерфейса.
4. На символьном сокете можно вызвать функцию
connect
, но это делается редко. Эта функция устанавливает только внешний адрес, так как на символьном сокете нет понятия порта. О выводе можно сказать, что вызов функции connect позволяет нам вызвать функцию
write
или
send
вместо
sendto
, поскольку IP-адрес получателя уже определен.
28.3. Вывод на символьном сокете
Вывод на символьном сокете регулируется следующими правилами:
1. Стандартный вывод выполняется путем вызова функции
sendto
или
sendmsg
и определения IP-адреса получателя. Функции
write
,
writev
и
send
также можно использовать, если сокет был присоединен.
2. Если не установлен параметр сокета
IP_HDRINCL
, то начальный адрес данных, предназначенных для записи ядром, указывает на первый байт, следующий за IP-заголовком, поскольку ядро будет строить IP-заголовок и добавлять его к началу данных из процесса. Ядро устанавливает поле протокола создаваемого заголовка IPv4 равным значению третьего аргумента функции
socket
.
3. Если параметр сокета
IP_HDRINCL
установлен, то начальный адрес данных, предназначенных для записи ядром, указывает на первый байт IP-заголовка. Размер данных для записи должен включать размер IP-заголовка вызывающего процесса. Процесс полностью формирует IP-заголовок, за исключением того, что, во-первых, значение поля идентификации IPv4 может быть нулевым (что указывает ядру на необходимость самостоятельно установить это значение), во-вторых, ядро всегда вычисляет и сохраняет контрольную сумму заголовка IPv4, в-третьих, включает или не включает параметры IP (см. раздел 27.2).