Печать ICMP-сообщений
Далее рассмотрим форматирование и печать ICMP-сообщений. Это делает функция print_dg, показанная в листинге 4.4. Передаваемый этой функции буфер имеет структуру, показанную на рис. 4.21.
Из рис. 4.21 видно, что буфер содержит IP-заголовок, за которым идет собственно ICMP-сообщение.
Рис.4.21. ICMP-сообщение, передаваемое функции print_dg
Листинг 4.4. Функция printjdg
1 static void print_dg( char *dg, int len )
2 {
3 struct ip *ip;
4 struct icmp *icmp;
5 struct hostent *hp;
6 char *hname;
7 int hl;
8 static char *redirect_code[] =
9 {
10 "сеть", "хост",
11 "тип сервиса и сеть", "тип сервиса и хост"
12 };
13 static char *timexceed_code [ ] =
14 {
15 "транзите", "сборке"
16 }
17 static char *param_code[] =
18 {
19 "Плохой IP-заголовок", "Нет обязательной опции"
20 };
21 ip = ( struct ip * )dg;
22 if ( ip->ip_v !=4)
23 {
24 error( 0, 0, "IP-датаграмма не версии 4\n" );
25 return;
26 }
27 hl = ip->ip_hl « 2; /* Длина IP-заголовка в байтах. */
28 if ( len < hl + ICMP_MINLEN )
29 {
30 error( 0, 0, "short datagram (%d bytes) from %s\n",
31 len, inet_ntoa( ip->ip_src ) );
32 return;
33 }
34 hp = gethostbyaddr( ( char * )&ip->ip_src, 4, AF_INET );
35 if ( hp == NULL )
36 hname = "";
37 else
38 hname = hp->h_name;
39 icmp = ( struct icmp * }( dg + hl ); /* ICMP-пакет. */
40 printf( "ICMP %s (%d) от %s (%s)\n",
41 get_type( icmp->icmp_type ),
42 icmp->icmp_type, hname, inet_ntoa( ip->ip_src ) );
43 if ( icmp->icmp_type == ICMP_UNREACH )
44 print_unreachable( icmp );
45 else if ( icmp->icmp_type == ICMP_REDIRECT )
46 printf( "\tПеренаправление на %s\n", icmp->icmp_code <=
47 redirect_code[ icmp->icmp_code ] : "Некорректный код" );
48 else if ( icmp->icmp_type == ICMP_TIMXCEED )
49 printf( "\tTTL == 0 при %s\n", icmp->icmp_code <= 1 ?
50 timexceed_code[ icmp->icmp_code] : "Некорректный код" );
51 else if ( icmp->icmp_type == ICMP_PARAMPROB )
52 printf ( "\t%s\n", icmp->icmp_code <= 1 ?
53 param_code[ icmp->icmp_code ] : "Некорректный код" );
54 }
Получение указателя на IP-заголовок и проверка корректности пакета
21 Записываем в переменную ip указатель на только что прочитанную датаграмму, приведенный к типу struct ip *.
22-26 Поле ip_v - это версия протокола IP. Если протокол не совпадает с IPv4, то печатаем сообщение об ошибке и выходим.
27-33 Поле ip_hl содержит длину заголовка в 32-байтных словах. Умножаем его на 4, чтобы получить длину в байтах, и сохраняем результат в переменной hl. Затем проверяем, что длина ICMP-сообщения не меньше минимально допустимой величины.
Получение имени хоста отправителя
34-38 Используем адрес источника в ICMP-сообщении, чтобы найти имя хоста отправителя. Если gethostbyaddr вернет NULL, то записываем в hname пустую строку, в обратном случае - имя хоста.
Пропуск IP-заголовка и печать отправителя и типа
39-42 Устанавливаем указатель icmp на первый байт, следующий за IP-заголовком. Этот указатель используется далее для получения типа ICMP-сообщения (icmp_type) и печати типа, адреса и имени хоста отправителя. Для получения ASCII-представления типа ICMP вызываем функцию get_type, текст которой приведен в листинге 4.5.
Печать информации, соответствующей типу
43-44 Если это одно из ICMP-сообщений о недоступности, то вызываем функцию print_unreachable (листинг 4.6) для печати дополнительной информации.
45-47 Если это сообщение о перенаправлении, то получаем тип перенаправления из поля icmp_code и печатаем его.
48-50 Если это сообщение об истечении времени существования, из поля icmp_code узнаем, произошло ли это во время транзита или сборки датаграммы, и печатаем результат.
51-53 Если это сообщение о некорректном параметре, из поля icmp_code определяем, в чем ошибка, и печатаем результат.
Функция get_type очевидна. Вы проверяете допустимость кода типа и возвращаете указатель на соответствующую строку (листинг 4.5).
Листинг 4.5. Функция getjype
1 static char *get_type( unsigned icmptype )
2 {
3 static char *type[] =
4 {
5 "Эхо-ответ", /* 0*/
6 "ICMP Тип 1", /* 1*/
7 "ICMP Тип 2", /* 2*/
8 "Пункт назначения недоступен", /* 3*/
9 "Источник приостановлен", /* 4*/
10 "Перенаправление", /* 5*/
11 "ICMP Тип 6", /* 6*/
12 "ICMP Тип 7", /* 7*/
13 "Эхо-запрос", /* 8*/
14 "Отклик маршрутизатора", /* 9*/
15 "Поиск маршрутизаторов", /* 10*/
16 "Истекло время существования", /* 11*/
17 "Неверный параметр", /* 12*/
18 "Запрос временного штампа", /* 13*/
19 "Ответ на запрос временного штампа", /* 14*/
20 "Запрос информации", /* 15*/
21 "Ответ на запрос информации", /* 16*/
22 "Запрос маски адреса", /* 17*/
23 "Ответ на запрос маски адреса" /* 18*/
24 }
25 if ( icmptype < ( sizeof( type ) / sizeof ( type[ 0 ]) ) )
26 return type[ icmptype ];
27 return "НЕИЗВЕСТНЫЙ ТИП";
28 }
Последняя функция - это print_unreachable. ICMP-сообщения о недоступности содержат IP-заголовок и первые восемь байт той IP-датаграммы, из-за которой было сгенерировано сообщение о недоступности. Это позволяет узнать адреса и номера портов отправителя и предполагаемого получателя недоставленного сообщения.
Структура IP-датаграммы, прочитанной из простого сокета в составе ICMP-сообщения о недоступности, показана на рис. 4.22. Та часть, которую уже обработала функция print_dg, заштрихована, она не передается в print_unreachable. Приведены также входной параметр функции print_unreachable - icmp и локальные переменные ip и udp.
Рис. 4.22. ICMP-сообщение о недоступности
Функция print_unreachable извлекает информацию из заголовка и первых восьми байт включенной IP-датаграммы. Хотя вы пометили байты как UDP-заголовок, это мог быть и заголовок TCP: номера портов в обоих случаях находятся в одной и той же позиции. Формат UDP-заголовка показан на рис. 4.23.
Рис. 4.23. UDP-заголовок
Текст функции print_unreachable приведен в листинге 4.6.
Листинг4.6. Функцияprint_unreachable
1 static void print_unreachable( struct icmp *icmp )
2 {
3 struct ip *ip;
4 struct udphdr *udp;
5 char laddr[ 15 + 1 ] ;
6 static char *unreach[] =
7 {
8 "Сеть недоступна", /* 0 */
9 "Хост недоступен", /* 1 */
10 "Протокол недоступен", /* 2 */
11 "Порт недоступен", /* 3 */
12 "Нужна фрагментация, поднят бит DF", /* 4 */
13 "Ошибка маршрутизации от источника", /* 5 */
14 "Сеть назначения неизвестна", /* 6 */
15 "Хост назначения неизвестен", /* 7 */
16 "Хост источника изолирован", /* 8 */
17 "Сеть назначения закрыта администратором ", /* 9 */
18 "Хост назначения закрыт администратором ", /* 10 */
19 "Сеть недоступна для типа сервиса", /* 11 */
20 "Хост недоступен для типа сервиса", /* 12 */
21 "Связь запрещена администратором", /* 13 */
22 "Нарушение предшествования хостов", /* 14 */
23 "Действует отсечка предшествования" /* 15 */
24 };
25 ip = ( struct ip * )( ( char * )icmp + 8 );
26 udp = ( struct udphdr *)((char *)ip + (ip->ip_hl « 2 ) );
27 strcpy( laddr, inet_ntoa( ip->ip_src ) );
28 printf( "\t%s\n\tИст.: %s.%d, Назн.: %s.%d\n",
29 icmp->icmp_code < ( sizeof( unreach ) /
30 sizeof( unreach[ 0 ] ) )?
31 unreach[ icmp->icmp_code ] : "Некорректный код",
32 laddr, ntohs( udp->uh_sport ),
33 inet_ntoa( ip->ip_dst ), ntohs( udp->uh_dport ) );
34 }
Установка указателей и получение адреса источника
25-26 Начинаем с установки указателей ip и udp соответственно на IP-заголовок и первые восемь байт вложенной IP-датаграммы.
27 Копируем адрес источника из IP-заголовка в локальную переменную laddr.
Печать адресов, портов и типа сообщения
28-33 Печатаем адреса и номера портов источника и назначения, а также уточненный тип сообщения о недоступности.
В качестве примера использования программы ICMP приведено несколько юследних ICMP-сообщений, полученных при запуске traceroute (совет 35).
traceroute -q 1 netcom4.netcom.com
Опция -q 1 означает, что traceroute должна посылать пробный запрос только один раз, а не три, как принято по умолчанию.
ICMP Истекло время существования (11) от hl-0.mig-fl-gwl.icg.net
(165.236.144.110)
TTL == 0 во время транзита
ICMP Истекло время существования (11) от sl0-0-0.dfw-tx-
gwl.icg.net (165.236.32.74)
TTL == 0 во время транзита
ICMP Истекло время существования (11) от dfw-tx-gw2.icg.net
(163.179.1.133)
TTL == 0 во время транзита
ICMP Пункт назначения недоступен (3) от netcom4.netcom.com
(199.183.9.104)
Порт недоступен
Ист. 205.184.-142.71.45935, Назн. 199.183.9.104.33441
Обычно нет необходимости следить с помощью icmp за работой traceroute, но это может быть очень полезно для поиска причин отсутствия связи.