Листинг 28.9. Функция sig_alrm: обработчик сигнала SIGALRM
//ping/sig_alrm.c
1 #include "ping.h"
2 void
3 sig_alrm(int signo)
4 {
5 (*pr->fsend)();
6 alarm(1);
7 return;
8 }
Функция
send_v4
, приведенная в листинге 28.10, строит ICMPv4 сообщение эхо-запроса и записывает его в символьный сокет.
Листинг 28.10. Функция send_v4: построение эхо-запроса ICMPv4 и его отправка
//ping/send_v4.c
1 #include "ping.h"
2 void
3 send_v4(void)
4 {
5 int len;
6 struct icmp *icmp;
7 icmp = (struct icmp*)sendbuf;
8 icmp->icmp_type = ICMP_ECHO;
9 icmp->icmp_code = 0;
10 icmp->icmp_id = pid;
11 icmp->icmp_seq = nsent++;
12 memset(icmp->icmp_data, 0xa5, datalen); /* заполнение по шаблону */
13 Gettimeofday((struct timeval*)icmp->icmp_data, NULL);
14 len = 8 + datalen; /* контрольная сумма по заголовку и данным */
15 icmp->icmp_cksum = 0;
16 icmp->icmp_cksum = in_cksum((u_short*)icmp, len);
17 Sendto(sockfd, sendbuf, len, 0, pr->sasend, pr->salen);
18 }
Формирование ICMP-сообщения
7-13
ICMPv4 сообщение сформировано. В поле идентификатора установлен идентификатор нашего процесса, а порядковый номер установлен как глобальная переменная
nset
, которая затем увеличивается на 1 для следующего пакета. Текущее время сохраняется в части данных ICMP-сообщения.
Вычисление контрольной суммы ICMP
14-16
Для вычисления контрольной суммы ICMP значение поля контрольной суммы устанавливается равным 0, затем вызывается функция
in_cksum
, а результат сохраняется в поле контрольной суммы. Контрольная сумма ICMPv4 вычисляется по ICMPv4-заголовку и всем следующим за ним данным.
Отправка дейтаграммы
17
ICMP-сообщение отправлено на символьный сокет. Поскольку параметр сокета
IP_HDRINCL
не установлен, ядро составляет заголовок IPv4 и добавляет его в начало нашего буфера.
Контрольная сумма Интернета является суммой обратных кодов 16-разрядных значений. Если длина данных является нечетным числом, то для вычисления контрольной суммы к данным дописывается один нулевой байт. Перед вычислением контрольной суммы поле контрольной суммы должно быть установлено в 0. Такой алгоритм применяется для вычисления контрольных сумм IPv4, ICMPv4, IGMPv4, ICMPv6, UDP и TCP. В RFC 1071 [12] содержится дополнительная информация и несколько числовых примеров. В разделе 8.7 книги [128] более подробно рассказывается об этом алгоритме, а также приводится более эффективная его реализация. В нашем случае контрольную сумму вычисляет функция
in_cksum
, приведенная в листинге 28.11.
Листинг 28.11. Функция in_cksum: вычисление контрольной суммы Интернета
//libfree/in_cksum.c
1 uint16_t
2 in_cksum(uint16_t *addr, int len)
3 {
4 int nleft = len;
5 uint32_t sum = 0;
6 uint16_t *w = addr;
7 uint16_t answer = 0;
8 /*
9 * Наш алгоритм прост: к 32-разрядному аккумулятору sum мы добавляем
10 * 16-разрядные слова, а затем записываем все биты переноса из старших
11 * 16 разрядов в младшие 16 разрядов.
12 */
13 while (nleft > 1) {
14 sum += *w++;
15 nleft -= 2;
16 }
17 /* при необходимости добавляем четный байт */
18 if (nleft == 1) {
19 *(unsigned char*)(&answer) = *(unsigned char*)w;
20 sum += answer;
21 }
22 /* перемещение битов переноса из старших 16 разрядов в младшие */
23 sum = (sum >> 16) + (sum & 0xffff); /* добавление старших 16 к младшим */
24 sum += (sum >> 16); /* добавление переноса */
25 answer = ~sum; /* обрезаем по 16 разрядам */
26 return(answer);
27 }
Алгоритм вычисления контрольной суммы Интернета
1-27
Первый цикл
while
вычисляет сумму всех 16-битовых значений. Если длина нечетная, то к сумме добавляется конечный байт. Алгоритм, приведенный в листинге 28.11, является простым алгоритмом, подходящим для программы
ping
, но неудовлетворительным для больших объемов вычислений контрольных сумм, производимых ядром.
ПРИМЕЧАНИЕ
Эта функция взята из общедоступной версии программы ping, написанной Майком Мюссом (Mike Muuss).
Последней функцией нашей программы
ping
является функция
send_v6
, приведенная в листинге 28.12, которая формирует и посылает эхо-запросы ICMPv6.
Функция
send_v6
аналогична функции
send_v4
, но обратите внимание, что она не вычисляет контрольную сумму. Как отмечалось ранее, поскольку для вычисления контрольной суммы ICMPv6 используется адрес отправителя из IPv6-заголовка, данная контрольная сумма вычисляется для нас ядром, после того как ядро выяснит адрес отправителя.