UNIX: разработка сетевых приложений - Уильям Стивенс
Шрифт:
Интервал:
Закладка:
33 if ((datalink = pcap_datalink(pd)) < 0)
34 err_quit("pcap_datalink: %s", pcap_geterr(pd));
35 if (verbose)
36 printf("datalink = %dn", datalink);
37 }
Выбор устройства для захвата пакетов10-14 Если устройство для захвата пакетов не было задано (с помощью параметра командной строки -i), то выбор этого устройства осуществляется с помощью функции pcap_lookupdev. С помощью запроса SIOCGIFCONF функции ioctl выбирается включенное устройство с минимальным порядковым номером, но только не устройство обратной связи. Многие из библиотечных функций pcap возвращают сообщения об ошибках в виде строк. Единственным аргументом функции pcap_lookupdev является массив, в который записывается строка с сообщением об ошибке.
Открываем устройство15-17 Функция pcap_open_live открывает устройство. Слово live присутствует в названии функции потому, что здесь имеется в виду фактическое устройство для захвата пакетов, а не файл, содержащий предыдущие сохраненные пакеты. Первым аргументом функции является имя устройства, вторым — количество байтов, которое нужно сохранять для каждого пакета (значение shaplen, которое мы инициализировали числом 200 в листинге 29.2), а третий аргумент — это флаг, указывающий на смешанный режим. Четвертый аргумент — это значение времени ожидания в миллисекундах, а пятый — указатель на массив, содержащий сообщения об ошибках.
Если установлен флаг смешанного режима, интерфейс переходит в этот режим, в результате чего он принимает все пакеты, проходящие по кабелю. Это обычное состояние программы tcpdump. Тем не менее в нашем примере ответы сервера DNS будут посланы непосредственно на наш узел (то есть можно обойтись без смешанного режима).
Четвертый аргумент — время ожидания при считывании. Вместо того чтобы возвращать пакет процессу каждый раз, когда приходит очередной пакет (что может быть весьма неэффективно, так как в этом случае потребуется выполнять множество операций копирования отдельных пакетов из ядра в процесс), это делается, когда считывающий буфер устройства оказывается заполненным либо когда истекает время ожидания. Если время ожидания при считывании равно нулю, то каждый пакет будет переправляться процессу, как только будет получен.
Получение сетевого адреса и маски подсети18-23 Функция pcap_lookupnet возвращает сетевой адрес и маску подсети для устройства захвата пакетов. При вызове функции pcap_compile, которая будет вызвана следующей, нужно задать маску подсети, поскольку с помощью маски фильтр пакетов определяет, является ли IP-адрес адресом широковещательной передачи для данной подсети.
Компиляция фильтра пакетов24-30 Функция pcap_compile получает строку, построенную нами как массив cmd, и компилирует ее, создавая тем самым программу для фильтрации (записывая ее в fcode). Эта программа будет отбирать те пакеты, которые мы хотим получить.
Загрузка программы фильтрации31-32 Функция pcap_setfilter получает только что скомпилированную программу фильтрации и загружает ее в устройство для захвата пакетов. Таким образом инициируется захват пакетов, выбранных нами путем настройки фильтра.
Определение типа канального уровня33-36 Функция pcap_datalink возвращает тип канального уровня для устройства захвата пакетов. Эта информация нужна нам при захвате пакетов для того, чтобы определить размер заголовка канального уровня, который будет добавлен в начало каждого считываемого нами пакета (см. листинг 29.10).
После вызова функции open_pcap функция main вызывает функцию test_udp, показанную в листинге 29.6. Эта функция посылает запрос DNS и считывает ответ сервера.
Листинг 29.6. Функция test_udp: отправка запросов и считывание ответов
//udpcksum/udpcksum.c
12 void
13 test_udp(void)
14 {
15 volatile int nsent = 0, timeout = 3;
16 struct udpiphdr *ui;
17 Signal(SIGALRM, sig_alrm);
18 if (sigsetjmp(jmpbuf, 1)) {
19 if (nsent >= 3)
20 err_quit("no response");
21 printf("timeoutn");
22 timeout *= 2; /* геометрическая прогрессия: 3, 6, 12 */
23 }
24 canjump = 1; /* siglongjmp разрешен */
25 send_dns_query();
26 nsent++;
27 alarm(timeout);
28 ui = udp_read();
29 canjump = 0;
30 alarm(0);
31 if (ui->ui_sum == 0)
32 printf("UDP checksums offn");
33 else
34 printf("UDP checksums onn");
35 if (verbose)
36 printf("received UDP checksum = %xn", ntohs(ui->ui_sum));
37 }
Переменные volatile15 Нам нужно, чтобы две динамические локальные переменные nsent и timeout сохраняли свои значения после возвращения siglongjmp из обработчика сигнала в нашу функцию. Реализация допускает восстановление значений динамических локальных переменных, предшествовавших вызову функции sigsetjump [110, с. 178], но добавление спецификатора volatile предотвращает это восстановление.
Установление обработчика сигналов и буфера перехода15-16 Для сигнала SIGALRM устанавливается обработчик сигнала, а функция sigsetjmp устанавливает буфер перехода для функции siglongjmp. (Эти две функции подробно описаны в разделе 10.15 [110].) Значение 1 во втором аргументе функции sigsetjmp указывает, что требуется сохранить текущую маску сигнала, так как мы будем вызывать функцию siglongjmp из нашего обработчика сигнала.
Функция siglongjmp19-23 Этот фрагмент кода выполняется, только когда функция siglongjmp вызывается из нашего обработчика сигнала. Вызов указывает на возникновение условий, при которых мы входим в состояние ожидания: мы отправили запрос, на который не пришло никакого ответа. Если после того, как мы отправим три запроса, ответа не будет, мы прекращаем выполнение кода. По истечении времени ожидания, отведенного на получение ответа, мы выводим соответствующее сообщение и увеличиваем значение времени ожидания в два раза, то есть задаем экспоненциальное смещение (exponential backoff), которое также описано в разделе 20.5. Первое значение времени ожидания равно 3 с, затем — 6 с и 12 с.
Причина, по которой в этом примере мы используем функции sigsetjmp и siglongjmp, вместо того чтобы просто перехватывать ошибку EINTR (как мы поступили в листинге 14.1), заключается в том, что библиотечные функции захвата пакетов (которые вызываются из нашей функции udp_read) заново запускают операцию чтения в случае возвращения ошибки EINTR. Поскольку мы не хотим модифицировать библиотечные функции, единственным решением для нас является перехватывание сигнала SIGALRM и выполнение нелокального перехода (оператора goto), который возвращает управление в наш код, а не в библиотечную функцию.
Отправка запроса DNS и считывание ответа25-26 Функция send_dns_query (см. листинг 29.8) отправляет запрос DNS на сервер имен. Функция dns_read считывает ответ. Мы вызываем функцию alarm для предотвращения «вечной» блокировки функции read. Если истекает заданное (в секундах) время ожидания, генерируется сигнал SIGALRM, и наш обработчик сигнала вызывает функцию siglongjmp.
Анализ полученной контрольной суммы UDP27-32 Если значение полученной контрольной суммы UDP равно нулю, это значит, что сервер не вычислил и не отправил контрольную сумму.
В листинге 29.7 показана наша функция sig_alrm — обработчик сигнала SIGALRM.
Листинг 29.7. Функция sig_alrm: обработка сигнала SIGALRM
//udpcksum/udpcksum.c
1 #include "udpcksum.h"
2 #include <setjmp.h>
3 static sigjmp_buf jmpbuf;
4 static int canjump;
5 void
6 sig_alrm(int signo)
7 {
8 if (canjump == 0)
9 return;
10 siglongjmp(jmpbuf, 1);
11 }
8-10 Флаг canjump был установлен в листинге 29.6 после инициализации буфера перехода функцией sigsetjmp. Если флаг был установлен, в результате вызова функции siglongjmp управление осуществляется таким образом, как если бы функция sigsetjmp из листинга 29.6 возвратила бы значение 1.
В листинге 29.8 показана функция send_dns_query, посылающая запрос UDP на сервер DNS. Эта функция формирует запрос DNS.
Листинг 29.8. Функция send_dns_query: отправка запроса UDP на сервер DNS
//udpcksum/senddnsquery-raw.c
6 void
7 send_dns_query(void)
8 {
9 size_t nbytes;
10 char *buf, *ptr;