Получение асинхронных ошибок 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-сообщение, которое должно быть передано данному приложению.