20 iov.iov_len = sizeof(recvbuf);
21 msg.msg_name = pr->sarecv;
22 msg.msg_iov = &iov
23 msg.msg_iovlen = 1;
24 msg.msg_control = controlbuf;
25 for (;;) {
26 msg.msg_namelen = pr->salen;
27 msg.msg_controllen = sizeof(controlbuf);
28 n = recvmsg(sockfd, &msg, 0);
29 if (n < 0) {
30 if (errno == EINTR)
31 continue;
32 else
33 err_sys("recvmsg error");
24 }
35 Gettimeofday(&tval, NULL);
36 (*pr->fproc)(recvbuf, n, &msg, &tval);
37 }
38 }
12-13
Создается символьный сокет, соответствующий выбранному протоколу. В вызове функции setuid
нашему эффективному идентификатору пользователя присваивается фактический идентификатор пользователя. Для создания символьных сокетов программа должна иметь права привилегированного пользователя, но когда символьный сокет уже создан, от этих прав можно отказаться. Всегда разумнее отказаться от лишних прав, если в них нет необходимости, например на тот случай, если в программе есть скрытая ошибка, которой кто-либо может воспользоваться.
14-15
Мы выполняем функцию инициализации для выбранного протокола. Для IPv6 такая функция представлена в листинге 28.7.
16-17
Пытаемся установить размер приемного буфера сокета, равный 61 440 байт (60×1024) — этот размер больше задаваемого по умолчанию. Это делается в расчете на случай, когда пользователь проверяет качество связи с помощью программы ping
, обращаясь либо к широковещательному адресу IPv4, либо к групповому адресу. В обоих случаях может быть получено большое количество ответов. Увеличивая размер буфера, мы уменьшаем вероятность того, что приемный буфер переполнится.
18
Запускаем обработчик сигнала, который, как мы увидим, посылает пакет и создает сигнал SIGALRM
один раз в секунду. Обычно обработчик сигналов не запускается напрямую, как у нас, но это можно делать. Обработчик сигналов является обычной функцией языка С, просто в нормальных условиях он асинхронно запускается ядром.
19-24
Мы записываем значения в неизменяемые поля структур msghdr
и iovec
, которые будут передаваться функции recvmsg
.
25-37
Основной цикл программы является бесконечным циклом, считывающим все пакеты, возвращаемые на символьный сокет ICMP. Вызывается функция gettimeofday
для регистрации времени получения пакета, а затем вызывается соответствующая функция протокола (proc_v4
или proc_v6
) для обработки ICMP-сообщения.
В листинге 28.5 приведена функция tv_sub
, вычисляющая разность двух структур timeval
и сохраняющая результат в первой из них.
Листинг 28.5. Функция tv_sub: вычитание двух структур timeval
//lib.tv_sub.c
1 #include "unp.h"
2 void
3 tv_sub(struct timeval *out, struct timeval *in)
4 {
5 if ((out->tv_usec -= in->tv_usec) < 0) { /* out -= in */
6 --out->tv_sec;
7 out->tv_usec += 1000000;
8 }
9 out->tv_sec -= in->tv_sec;
10 }
В листинге 28.6 приведена функция proc_v4
, обрабатывающая все принимаемые сообщения ICMPv4. Можно также обратиться к рис. А.1, на котором изображен формат заголовка IPv4. Кроме того, следует осознавать, что к тому моменту, когда процесс получает на символьном сокете ICMP-сообщение, ядро уже проверило, что основные поля в заголовке IPv4 и в сообщении ICMPv4 действительны [128, с. 214, с. 311].
Листинг 28.6. Функция proc_v4: обработка сообщений ICMPv4
//ping/prov_v4.c
1 #include "ping.h"
2 void
3 proc_v4(char *ptr, ssize_t len, struct msghdr *msg, struct timeval *tvrecv)
4 {
5 int hlen1, icmplen;
6 double rtt;
7 struct ip *ip;
8 struct icmp *icmp;
9 struct timeval *tvsend;
10 ip = (struct ip*)ptr; /* начало IP-заголовка */
11 hlen1 = ip->ip_hl << 2; /* длина IP-заголовка */
12 if (ip->ip_p != IPPROTO_ICMP)
13 return; /* не ICMP */