UNIX: разработка сетевых приложений - Уильям Стивенс
Шрифт:
Интервал:
Закладка:
Следующая функция — это udp_read, показанная в листинге 29.11. Она вызывается из кода, представленного в листинге 29.6.
Листинг 29.11. Функция udp_read: чтение очередного пакета из устройства захвата пакетов
//udpcksum/udpread.c
7 struct udpiphdr*
8 udp_read(void)
9 {
10 int len;
11 char *ptr;
12 struct ether_header *eptr;
13 for (;;) {
14 ptr = next_pcap(&len);
15 switch (datalink) {
16 case DLT_NULL: /* заголовок обратной петли = 4 байта */
17 return (udp_check(ptr + 4, len — 4));
18 case DLT_EN10MB:
19 eptr = (struct ether_header*)ptr;
20 if (ntohs(eptr->ether_type) != ETHERTYPE_IP)
21 err_quit("Ethernet type not IP", ntohs(eptr->ether_type));
22 return (udp_check(ptr + 14, len — 14));
23 case DLT_SLIP: /* заголовок SLIP = 24 байта */
24 return (udp_check(ptr + 24, len — 24));
25 case DLT_PPP: /* заголовок PPP = 24 байта */
26 return (udp_check(ptr + 24, len — 24));
27 default:
28 err_quit("unsupported datalink (%d)", datalink);
29 }
30 }
31 }
14-29 Наша функция next_pcap (см. листинг 29.12) возвращает следующий пакет из устройства захвата пакетов. Поскольку заголовки канального уровня различаются в зависимости от фактического типа устройства, мы применяем ветвление в зависимости от значения, возвращаемого функцией pcap_datalink.
ПРИМЕЧАНИЕСдвиги на 4, 14 и 24 байта объясняются на рис. 31.9 [128]. Сдвиг, равный 24 байтам, показанный для заголовков SLIP и PPP, применяется в BSD/OS 2.1.
Несмотря на то, что в названии DLT_EN10MB фигурирует обозначение «10МВ», этот тип канального уровня используется для сетей Ethernet, в которых скорость передачи данных равна 100 Мбит/с.
Наша функция udp_check (см. листинг 29.13) исследует пакет и проверяет поля в заголовках IP и UDP.
В листинге 29.12 показана функция next_pcap, возвращающая следующий пакет из устройства захвата пакетов.
Листинг 29.12. Функция next_pcap: возвращает следующий пакет
//udpcksum/pcap.c
38 char*
39 next_pcap(int *len)
40 {
41 char *ptr;
42 struct pcap_pkthdr hdr;
43 /* продолжаем следить, пока пакет не будет готов */
44 while ((ptr = (char*)pcap_next(pd, &hdr)) == NULL);
45 *len = hdr.caplen; /* длина захваченного пакета */
46 return (ptr);
47 }
43-44 Мы вызываем библиотечную функцию pcap_next, возвращающую следующий пакет. Указатель на пакет является возвращаемым значением данной функции, а второй аргумент указывает на структуру pcap_pkthdr, которая тоже возвращается заполненной:
struct pcap_pkthdr {
struct timeval ts; /* временная метка */
bpf_u_int32 caplen; /* длина захваченного фрагмента */
bpf_u_int32 len; /* полная длина пакета, находящегося в канале */
};
Временная отметка относится к тому моменту, когда пакет был считан устройством захвата пакетов, в противоположность моменту фактической передачи пакета процессу, которая может произойти чуть позже. Переменная caplen содержит длину захваченных данных (вспомним, что в листинге 29.2 нашей переменной shaplen было присвоено значение 200 и она являлась вторым аргументом функции pcap_open_live в листинге 29.5). Назначение устройства захвата пакетов состоит в захвате заголовков, а не всего содержимого каждого пакета. Переменная len — это полная длина пакета, находящегося в канале. Значение caplen будет всегда меньше или равно значению len.
45-46 Перехваченная часть пакета возвращается через указатель (аргумент функции), и возвращаемым значением функции является указатель на пакет. Следует помнить, что указатель на пакет указывает фактически на заголовок канального уровня, который представляет собой 14-байтовый заголовок Ethernet в случае кадра Ethernet или 4-байтовый псевдоканальный (pseudo-link) заголовок в случае закольцовки на себя.
Если мы посмотрим на библиотечную реализацию функции pcap_next, мы увидим, что между различными функциями существует некоторое «разделение труда», схематически изображенное на рис. 29.5. Наше приложение вызывает функции pcap_, среди которых есть как зависящие, так и не зависящие от устройства захвата пакетов. Например, мы показываем, что реализация BPF вызывает функцию read, в то время как реализация DLPI вызывает функцию getmsg, а реализация Linux вызывает recvfrom.
Рис. 29.5. Организация вызовов функций для чтения из библиотеки захвата пакетов
Наша функция udp_check проверяет различные поля в заголовках IP и UDP. Она показана в листинге 29.13. Эту проверку необходимо выполнить, так как при получении пакета от устройства захвата пакетов уровень IP не замечает этого пакета. Для символьного сокета это не так.
44-61 Длина пакета должна включать хотя бы заголовки IP и UDP. Версия IP проверяется вместе с длиной и контрольной суммой заголовка IP. Если поле протокола указывает на дейтаграмму UDP, функция возвращает указатель на объединенный заголовок IP/UDP. В противном случае программа завершается, так как фильтр захвата пакетов, заданный при вызове функции pcap_setfilter в листинге 29.5, не должен возвращать пакеты никакого другого типа.
Листинг 29.13. Функция udp_check: проверка полей в заголовках IP и UDP
//udpcksum/udpread.c
38 struct udpiphdr*
39 udp_check(char *ptr, int len)
40 {
41 int hlen;
42 struct ip *ip;
43 struct udpiphdr *ui;
44 if (len < sizeof(struct ip) + sizeof(struct udphdr))
45 err_quit("len = %d", len);
46 /* минимальная проверка заголовка IP */
47 ip = (struct ip*)ptr;
48 if (ip->ip_v != IPVERSION)
49 err_quit("ip_v = %d", ip->ip_v);
50 hlen = ip->ip_hl << 2;
51 if (hlen < sizeof(struct ip))
52 err_quit("ip_hl = %d", ip->ip_hl);
53 if (len < hlen + sizeof(struct udphdr))
54 err_quit("len = %d, hlen = %d", len, hlen);
55 if ((ip->ip_sum = in_cksum((u_short )ip, hlen)) != 0)
56 err_quit("ip checksum error");
57 if (ip->ip_p == IPPROTO_UDP) {
58 ui = (struct udpiphdr*)ip;
59 return (ui);
60 } else
61 err_quit("not a UDP packet");
62 }
Функция cleanup, показанная в листинге 29.14, вызывается из функции main непосредственно перед тем, как программа завершается, а также вызывается в качестве обработчика сигнала в случае, если пользователь прерывает выполнение программы (см. листинг 29.4).
Листинг 29.14. Функция cleanup
//udpcksum/cleanup.c
2 void
3 cleanup(int signo)
4 {
5 struct pcap_stat stat;
6 fflush(stdout);
7 putc('n', stdout);
8 if (verbose) {
9 if (pcap_stats(pd, &stat) < 0)
10 err_quit("pcap_stats: %sn", pcap_geterr(pd));
11 printf("%d packets received by filtern", stat.ps_recv);
12 printf("%d packets dropped by kerneln", stat.ps_drop);
13 }
14 exit(0);
15 }
Получение и вывод статистики по захвату пакетов8-13 Функция pcap_stats получает статистику захвата пакетов: общее количество полученных фильтром пакетов и количество пакетов, переданных ядру.
Пример
Сначала мы запустим нашу программу с аргументом командной строки -0 и убедимся, что сервер имен отвечает на приходящие дейтаграммы, не содержащие контрольной суммы. Мы также задаем флаг -v.
macosx # udpcksum -i en1 -0 -v bridget.rudoff.com domain
device = en1
local net = 172.24.37.64. netmask = 255.255.255.224
cmd = udp and src host 206.168.112.96 and src port 53
datalink = 1
sent: 36 bytes of data
UDP checksums on
received UDP checksum = 9d15
3 packets received by filter
0 packets dropped by kernel
Затем мы запускаем нашу программу, обращаясь к локальному серверу имен, в котором отключен подсчет контрольных сумм. Чем дальше, тем сложнее становится найти сервер имен с отключенным подсчетом контрольных сумм.
macosx # udpcksum -i en1 -v freebsd4.unpbook.com domain
device = en1
localnet = 172.24.37.64, netmask = 255.255.255.224
cmd = udp and src host 172.24.37.94 and src port 53
datalink = 1
sent: 36 bytes of data
UDP checksums off
received UDP checksum = 0
3 packets received by filter
0 packets dropped by kernel
Функции libnet
В этом разделе приводятся альтернативные версии функций open_output и send_dns_query, в которых вместо символьных сокетов используются функции библиотеки libnet. Библиотека libnet берет на себя заботу о множестве деталей, в частности, устраняет проблемы с переносимостью, связанные с вычислением контрольных сумм и порядком байтов в заголовке, о которых мы говорили выше. Функция open output представлена в листинге 29.15.