28.7. Демон сообщений ICMP
28.7. Демон сообщений ICMP
Получение асинхронных ошибок ICMP на сокет UDP всегда было и продолжает оставаться проблемой. Ядро получает сообщения об ошибках ICMP, но они редко доставляются приложениям, которым необходимо о них знать. Мы видели, что для получения этих ошибок в API сокетов требуется присоединение сокета UDP к одному IP-адресу (см. раздел 8.11). Причина такого ограничения заключается в том, что единственная ошибка, возвращаемая функцией recvfrom, является целочисленным кодом errno, а если приложение посылает дейтаграммы по нескольким адресам, а затем вызывает recvfrom, то данная функция не может сообщить приложению, какая из дейтаграмм вызвала ошибку.
В данном разделе предлагается решение, не требующее никаких изменений в ядре. Мы предлагаем демон ICMP-сообщений icmpd, который создает символьный сокет ICMPv4 и символьный сокет ICMPv6 и получает все ICMP-сообщения, направляемые к ним ядром. Он также создает потоковый сокет домена Unix, связывает его (при помощи функции bind) с полным именем /tmp/icmpd и прослушивает входящие соединения (устанавливаемые при помощи функции connect) клиентов с этим сокетом. Схема соединений изображена на рис. 28.7.
Рис. 28.7. Демон icmpd: создание сокетов
Приложение UDP (являющееся клиентом для демона) сначала создает сокет UDP, для которого оно хочет получать асинхронные ошибки. Приложение должно связать (функция bind) с этим сокетом динамически назначаемый порт; для чего это делается, будет пояснено далее. Затем оно создает доменный сокет Unix и присоединяется (функция connect) к заранее известному полному имени файла демона. Это показано на рис. 28.8.
Рис. 28.8. Приложение создает свой сокет UDP и доменный сокет Unix
Далее приложение «передает» свой UDP-сокет демону через соединение домена Unix, используя технологию передачи дескрипторов, как показано в разделе 15.7. Такой подход позволяет демону получить копию сокета, так что он может вызвать функцию getsockname и получить номер порта, связанный с сокетом. На рис. 28.9 показана передача сокета.
Рис. 28.9. Пересылка сокета UDP демону через доменный сокет Unix
После того как демон получает номер порта, связанный с UDP-сокетом, он закрывает свою копию сокета, и мы возвращаемся к схеме, приведенной на рис. 28.8.
ПРИМЕЧАНИЕ
Если узел поддерживает передачу данных, идентифицирующих отправителя (см. раздел 15.8), приложение также может послать эти данные демону. Затем демон может проверить, можно ли допускать данного пользователя к данному устройству.
В таком случае в результате любой ошибки ICMP, полученной демоном в ответ на UDP-дейтаграмму, посланную с порта, который связан с UDP-сокетом приложения, демон посылает приложению сообщение (о котором мы рассказываем чуть ниже) через доменный сокет Unix. Тогда приложение должно использовать функцию select или poll, чтобы обеспечить ожидание прибытия данных либо на UDP-сокет, либо на доменный сокет Unix.
Сначала рассмотрим исходный код приложения, использующего данный демон, а затем и сам демон. В листинге 28.20 приведен заголовочный файл, подключаемый и к приложению, и к демону.
Листинг 28.20. Заголовочный файл unpicmpd.h
//icmpd/unpicmpd.h
1 #ifndef __unpicmp_h
2 #define __unpicmp_h
3 #include "unp.h"
4 #define ICMPD_PATH "/tmp/icmpd" /* известное имя сервера */
5 struct icmpd_err {
6 int icmpd_errno; /* EHOSTUNREACH, EMSGSIZE, ECONNREFUSED */
7 char icmpd_type; /* фактический тип ICMPv[46] */
8 char icmpd_code; /* фактический код ICMPv[46] */
9 socklen_t icmpd_len; /* длина последующей структуры sockaddr{} */
10 struct sockaddr_storage icmpd_dest; /* универсальная структура
sockaddr_storage */
11 };
12 #endif /* __unpicmp_h */
4-11 Определяются известное полное имя сервера и структура icmpd_err, передаваемая от сервера приложению сразу, как только получено ICMP-сообщение, которое должно быть передано данному приложению.
6-8 Проблема в том, что типы сообщений ICMPv4 отличаются численно (а иногда и концептуально) от типов сообщений ICMPv6 (см. табл. А.5 и А.6). Возвращаются реальные значения типа (type) и кода (code), но мы также отображаем соответствующие им значения errno (icmpd_errno), взятые из последнего столбца табл. А.5 и А.6. Приложение может использовать эти значения вместо зависящих от протокола значений ICMPv4 и ICMPv6. В табл. 28.1 показаны обрабатываемые сообщения ICMP и соответствующие им значения errno.
Таблица 28.1. Значения переменной icmpd_errno, сопоставляющей ошибки ICMPv4 и ICMPv6
icmpd_errno Ошибка ICMPv4 Ошибка ICMPv6 ECONNREFUSED Port unreachable (Порт недоступен) Port unreachable (Порт недоступен) EMSGSIZE Fragmentation needed but DF bit set (Необходима фрагментация, но установлен бит DF) Packet too big (Слишком большой пакет) EHOSTUNREACH Time exceeded (Превышено время передачи) Time exceeded (Превышено время передачи) EHOSTUNREACH Source quench (Отключение отправителя) EHOSTUNREACH Все другие сообщения о недоступности получателя (Destination unreachable) Все другие сообщения о недоступности получателя (Destination unreachable)Демон возвращает пять типов ошибок ICMP:
1. «Port unreachable» (Порт недоступен) означает, что сокет не связан с портом получателя на IP-адресе получателя.
2. «Packet too big» (Слишком большой пакет) используется при определении транспортной MTU. В настоящее время нет определенного API, позволяющего UDP-приложениям осуществлять определение транспортной MTU. Если ядро поддерживает определение транспортной MTU для UDP, то обычно получение данной ошибки ICMP заставляет ядро записать новое значение транспортной MTU в таблицу маршрутизации ядра, но UDP-приложение, пославшее дейтаграмму, не извещается. Вместо этого приложение должно дождаться истечения тайм-аута и повторно послать дейтаграмму, и тогда ядро найдет новое (меньшее) значение MTU в своей таблице маршрутизации и фрагментирует дейтаграмму. Передача этой ошибки приложению позволяет ему ускорить повторную передачу дейтаграммы, и возможно, приложение сможет уменьшить размер посылаемой дейтаграммы.
3. Ошибка «Time exceeded» (Превышено время передачи) обычно возникает с кодом 0 и означает, что либо значение поля TTL (в случае IPv4), либо предельное количество транзитных узлов (в случае IPv6) достигло нуля. Обычно это свидетельствует о зацикливании маршрута, что, возможно, является временной ошибкой.
4. Ошибка «Source quench» (Отключение отправителя) ICMPv4 хотя и рассматривается в RFC 1812 [6] как устаревшая, может быть послана маршрутизаторами (или неправильно сконфигурированными узлами, действующими как маршрутизаторы). Такие ошибки означают, что пакет отброшен, и поэтому обрабатываются как ошибки недоступности получателя. Следует отметить, что в версии IPv6 нет ошибки отключения отправителя.
5. Все остальные ошибки недоступности получателя (Destination unreachble) означают, что пакет сброшен.
10 Элемент icmpd_dest является структурой адреса сокета, содержащей IP-адрес получателя и порта дейтаграммы, сгенерировавшей ICMP-ошибку. Этот элемент может быть структурой sockaddr_in для ICMPv4 либо структурой sockaddr_in6 для ICMPv6. Если приложение посылает дейтаграммы по нескольким адресам, оно, вероятно, имеет по одной структуре адреса сокета на каждый адрес. Возвращая эту информацию в структуре адреса сокета, приложение может сравнить ее со своими собственными структурами для поиска той, которая вызвала ошибку. Тип sockaddr_storage используется для того, чтобы в структуре можно было хранить адреса любого типа, поддерживаемого системой.
Данный текст является ознакомительным фрагментом.