Уильям Стивенс - UNIX: разработка сетевых приложений
■ возвращаемое значение msg_flags;
■ адрес получателя полученной дейтаграммы (из параметра сокета IP_RECVDSTADDR);
■ индекс интерфейса, на котором была получена дейтаграмма (параметр сокета IP_RECVIF).
Чтобы можно было получить два последних элемента, мы определяем в нашем заголовке unp.h следующую структуру:
struct in_pktinfo {
struct in_addr ipi_addr; /* IPv4-адрес получателя */
int ipi_ifindex; /* индекс интерфейса, на котором была
получена дейтаграмма */
};
Мы выбрали имена структуры и ее элементов так, чтобы получить определенное сходство со структурой IPv6 in6_pktinfo, возвращающей те же два элемента для сокета IPv6 (см. раздел 22.8). Наша функция recvfrom_flags будет получать в качестве аргумента указатель на структуру in_pktinfo, и если этот указатель не нулевой, возвращать структуру через указатель.
Проблема построения этой структуры состоит в том, что неясно, что возвращать, если недоступна информация, которая должна быть получена из параметра сокета IP_RECVDSTADDR (то есть реализация не поддерживает данный параметр сокета). Обработать индекс интерфейса легко, поскольку нулевое значение может использоваться как указание на то, что индекс неизвестен. Но для IP-адреса все 32-разрядные значения являются действительными. Мы выбрали такое решение: адрес получателя 0.0.0.0 возвращается в том случае, когда действительное значение недоступно. Хотя это реальный IP-адрес, использовать его в качестве IP-адреса получателя не разрешается (RFC 1122 [10]). Он будет действителен только в качестве IP-адреса отправителя во время начальной загрузки узла, когда узел еще не знает своего IP-адреса.
ПРИМЕЧАНИЕК сожалению, Беркли-ядра принимают дейтаграммы, предназначенные для адреса 0.0.0.0 [128, с. 218-219]. Это устаревшие адреса широковещательной передачи, генерируемые ядрами 4.2BSD.
Первая часть нашей функции recvfrom_flags представлена в листинге 22.1[1]. Эта функция предназначена для использования с сокетом UDP.
Листинг 22.1. Функция recvfrom_flags: вызов функции recvmsg
//adviо/recvfromflags.c
1 #include "unp.h"
2 #include <sys/param.h> /* макрос ALIGN для макроса CMSG_NXTHDR() */
3 ssize_t
4 recvfrom_flags(int fd, void *ptr, size_t nbytes, int *flagsp,
5 SA *sa, socklen_t *salenptr, struct unp_in_pktinfo *pktp)
6 {
7 struct msghdr msg;
8 struct iovec iov[1];
9 ssize_t n;
10 #ifdef HAVE_MSGHDR_MSG_CONTROL
11 struct cmsghdr *cmptr;
12 union {
13 struct cmsghdr cm;
14 char control[CMSG_SPACE(sizeof(struct in_addr)) +
15 CMSG_SPACE(sizeof(struct unp_in_pktinfo))];
16 } control_un;
17 msg.msg_control = control_un.control;
18 msg.msg_controllen = sizeof(control_un.control);
19 msg.msg_flags = 0;
20 #else
21 bzero(&msg, sizeof(msg)); /* обнуление msg_accrightslen = 0 */
22 #endif
23 msg.msg_name = sa;
24 msg.msg_namelen = *salenptr;
25 iov[0].iov_base = ptr;
26 iov[0].iov_len = nbytes;
27 msg.msg_iov = iov;
28 msg.msg_iovlen = 1;
29 if ((n = recvmsg(fd, &msg, *flagsp)) < 0)
30 return(n);
31 *salenptr = msg.msg_namelen; /* возвращение результатов */
32 if (pktp)
33 bzero(pktp, sizeof(struct unp_in_pktinfo)); /* 0.0.0.0. интерфейс = 0 */
Подключаемые файлы1-2 Использование макроопределения CMSG_NXTHDR требует подключения заголовочного файла <sys/param.h>.
Аргументы функции3-5 Аргументы функции аналогичны аргументам функции recvfrom за исключением того, что четвертый аргумент является указателем на целочисленный флаг (так что мы можем возвратить флаги, возвращаемые функцией recvmsg), а седьмой аргумент новый: это указатель на структуру unp_in_pktinfo, содержащую IPv4-адрес получателя пришедшей дейтаграммы и индекс интерфейса, на котором дейтаграмма была получена.
Различия реализаций10-22 При работе со структурой msghdr и различными константами MSG_XXX мы встречаемся со множеством различий в реализациях. Одним из вариантов обработки таких различий может быть использование имеющейся в языке С возможности условного подключения (директива #ifdef). Если реализация поддерживает элемент msg_control, то выделяется пространство для хранения значений, возвращаемых параметрами сокета IP_RECVDSTADDR и IP_RECVIF, и соответствующие элементы инициализируются.
Заполнение структуры msghdr и вызов функции recvmsg23-33 Заполняется структура msghdr и вызывается функция recvmsg. Значения элементов msg_namelen и msg_flags должны быть переданы обратно вызывающему процессу. Они являются аргументами типа «значение-результат». Мы также инициализируем структуру вызывающего процесса unp_in_pktinfo, устанавливая IP-адрес 0.0.0.0 и индекс интерфейса 0.
В листинге 22.2 показана вторая часть нашей функции.
Листинг 22.2. Функция recvfrom_flags: возвращаемые флаги и адрес получателя
//advio/recvfromflags.c
34 #ifndef HAVE_MSGHDR_MSG_CONTROL
35 *flagsp = 0; /* возвращение результатов */
36 return(n);
37 #else
38 *flagsp = msg.msg_flags; /* возвращение результатов */
39 if (msg.msg_controllen < sizeof(struct cmsghdr) ||
40 (msg.msg_flags & MSG_CTRUNC) || pktp == NULL)
41 return(n);
42 for (cmptr = CMSG_FIRSTHDR(&msg); cmptr != NULL;
43 cmptr = CMSG_NXTHDR(&msg, cmptr)) {
44 #ifdef IP_RECVDSTADDR
45 if (cmptr->cmsg_level == IPPROTO_IP &&
46 cmptr->cmsg_type == IP_RECVDSTADDR) {
47 memcpy(&pktp->ipi_addr, CMSG_DATA(cmptr),
48 sizeof(struct in_addr));
49 continue;
50 }
51 #endif
52 #ifdef IP_RECVIF
53 if (cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_RECVIF) {
54 struct sockaddr_dl *sdl;
55 sdl = (struct sockaddr_dl*)CMSG_DATA(cmptr);
56 pktp->ipi_ifindex = sdl->sdl_index;
57 continue;
58 }
59 #endif
60 err_quit("unknown ancillary data, len = %d, level = %d, type = %d",
61 cmptr->cmsg_len, cmptr->cmsg_level, cmptr->cmsg_type);
62 }
63 return(n);
64 #endif /* HAVE_MSGHDR_MSG_CONTROL */
65 }
34-37 Если реализация не поддерживает элемента msg_control, мы просто обнуляем возвращаемые флаги и завершаем функцию. Оставшаяся часть функции обрабатывает информацию, содержащуюся в структуре msg_control.
Возвращение при отсутствии управляющей информации38-41 Мы возвращаем значение msg_flags и передаем управление вызывающей функции в том случае, если нет никакой управляющей информации, управляющая информация была обрезана или вызывающий процесс не требует возвращения структуры unp_in_pktinfo.
Обработка вспомогательных данных42-43 Мы обрабатываем произвольное количество объектов вспомогательных данных с помощью макросов CMSG_FIRSTHDR и CMSG_NEXTHDR.
Обработка параметра сокета IP_RECVDSTADDR47-54 Если в составе управляющей информации был возвращен IP-адрес получателя (см. рис. 14.2), он возвращается вызывающему процессу.
Обработка параметра сокета IP_RECVIF55-63 Если в составе управляющей информации был возвращен индекс интерфейса, он возвращается вызывающему процессу. На рис. 22.1 показано содержимое возвращенного объекта вспомогательных данных.
Рис. 22.1. Объект вспомогательных данных, возвращаемый для параметра IP_RECVIF
Вспомните структуру адреса сокета канального уровня (см. листинг 18.1). Данные, возвращаемые в объекте вспомогательных данных, представлены в одной из этих структур, но длины трех элементов являются нулевыми (длина имени, адреса и селектора). Следовательно, нет никакой необходимости указывать эти значения, и таким образом структура имеет размер 8 байт, а не 20, как было в листинге 18.1. Возвращаемая нами информация — это индекс интерфейса.
Пример: вывод IP-адреса получателя и флага обрезки дейтаграммы
Для проверки нашей функции мы изменим функцию dg_echo (см. листинг 8.2) так, чтобы она вызывала функцию recvfrom_flags вместо функции recvfrom. Новая версия функции dg_echo показана в листинге 22.3.
Листинг 22.3. Функция dg_echo, вызывающая нашу функцию recvfrom_flags
//advio/dgechoaddr.c
1 #include "unpifi.h"
2 #undef MAXLINE
3 #define MAXLINE 20 /* устанавливаем новое значение, чтобы
пронаблюдать обрезку дейтаграмм */