Программирование в сетях Windows. Э. Джонс, Д. Оланд
Скачать 2.88 Mb.
|
printf(" -r record route\n"); printf(" host remote machine to printfC" datasize can be up to 1 KB\n"); ExitProcess(-i); tttxo * // Функция: FilllCMPData (Г < asie) ellrtw { (evre) т! } »+ 0) . cwzp. 4 0 4 ЧАСТЬ II Интерфейс прикладного программирования Winsock . Листинг 13-1. (продолжение) экИ, / / Описание: т \ II Вспомогательная функция для заполнения полей нашего запроса void FillICMPData(char *icmp_data, mt datasize) ;|. IcmpHeader *icmp_hdr = NULL; char «datapart = NULL; Outr icmp_hdr = (IcmpHeader*)icmp_data; *I \ icmp_hdr->i_type = ICMP_ECHO; // Эхо-запрос ICHP icmp_hdr->i_code = 0 ; \ icmp_hdr->i_id = (USHORT)GetCurrentProcessId(); о V icmp_hdr->i_cksum = 0; ; V icmp_hdr->i_seq = 0; \ % v TO. wflY datapart = icmp_data + sizeof(IcmpHeader); > // Поместите какие-нибудь данные в буфер memset(datapart,'Е', datasize - sizeof(IcmpHeader)); // Функция: checksum *etv // "eb, // Описание: // Вычисляет 16-битную комплементарную сумму оо»Яа J0 " // для указанного буфера с заголовком .eeteb i'u USHORT checksum(USHORT «buffer, i n t size) unsigned long cksum=0; ajteu :u k . < ЧЧ while (size > 1) , :SN- ' i' cksum += «buffer++; \ 4 size -= sizeof (USHORT); -лц i f (size) i q { i • cksum += *(UCHAR*)buffer; cksum = (cksum » 16) + (cksum & Oxffff); cksum += (cksum »16); return (USHORT)Ccksum); // Функция: DecodelPOptions н»л|"ЛАВА 13 Просты* «Ф^ИЫ 405 Листинг 13-1. (продолжение) Л С г // Описание: .'о1ТЗл; •• // При наличии расширенного заголовка находит параметры IP // в нем и выводит значения параметра записи маршрута ткм ,„Л void DecodeIPOptions(char *buf, int bytes) IpOptionHeader «ipopt = NULL; IN_ADDR maddr; , • «. ttfc-q int i; ,Jt«< r HOSTENT «host = NULL; ipopt = (IpOptionHeader *)(buf + 20); ЭЗ_<Ш] t-itrtqi'..,) printf("RR: " ) ; f o r ( i = 0; i < (ipopt->ptr / 4) - 1; i++) ei is* & г ,.^ц inaddr.S_un.S_addr = i p o p t - > a d d r [ i ] ; { i f ( i != 0) p r i n t f ( " " ) ; «s»f >эдкаП \\ host = gethostbyaddr((char *)&inaddr.S_un.S_addr, X 1 » sizeof(inaddr.S_un.S_addr), AF_INET); Jicwwi) if (host) } printf("(X-15s) Xs\n", inet_ntoa(inaddr), host->h_nane); jrnit // Функция: DecodelCMPHeader // ;nitJei // Описание: { II Ответом является пакет IP. Следует декодировать заголовок IP // для нахождения данных ICMP ч^ ^XeV Ътч II > void DecodeICMPHeader(char «buf, int bytes, tni struct sockaddr_in *from) { • ! m IpHeader .iphdr = NULL; IcmpHeader «icmphdr = NULL; ,3Zi? s unsigned short iphdrlen; DWORD tick; static int lcmpcount = 0; « iphdr = (IpHeader *)buf; // Количество 32-х битных слов помноженное на 4 равно числу байт см. след. стр. 4 0 6 ЧАСТЬ II Интерфейс прикладного программирования Wfcwock Л и с т и н г 1 3 - 1 . (продолжение) (wsmxX4ib®Qf\) . f - £ f ч«»Тй*»И iphdrlen = iphdr->h_len * 4; \\ t i c k = GetTickCount(); » :вмнв&кг>0 \\ Ю¥ ац птнлбн щР \\ if ( ( i p h d r l e n == MAX_IP_HDR_SIZE) && (licmpcount)X «н« *щовив и чеч в \\ DecodeIPOptions(buf, bytes); \\ •> tnt Л«><1« -поЛЩОЧ 90 tolov if (bytes < iphdrlen + ICMP_MIN) } { printf("Too few bytes from Xs\n", inet_ntoa(f rom->sin_addr)); '' *» } icmphdr = (IcmpHeader«)(buf + iphdrlen); if (icmphdr->i_type != ICMP.ECHOREPLY) { printf("nonecho type %d recvd\n", icmphdr->i_type)noqf) > x ,0 » xjnoi return; } // Проверка, что это ICMP-отклик на отправленное нами сообщение! // if (icmphdr->i_id != (USHORT)GetCurrentProcessIdO) { П printf("someone else's packet!\n"); n/t- l >'-t, return ; e l * } '-* printf("Xd bytes from Xs:", bytes, inet_ntoa(from->sin_addr)); { p r i n t f ( " icmp_seq = %d. ", icmphdr->i_seq); ;mn?ei p r i n t f ( " time: %d ms", t i c k - icmphdr->tiinestamp); p r i n t f ( " \ n " ) ; V. icmpcount++; t ' Ь return; t: .• щ 41 void ValidateArgs(int argc, ohir **argv) int 1; bRecordRoute = FALSE; lpdest = NULL; datasize = DEF_PACKET_SIZE; for(i = 1; i < argc; i++) { if ((argv[i][0] == •-') | wjytf tat J J I (argv[i][0] == Лив* п«(1э) (iSCit» 4J 0 . = V)) switch (tolower(argv[i][1])) ГЛАВА 13 Простые сокеты 407 Листинг 13-1. {продолжение) case ' r - : // Параметр записи маршрута bRecordRoute = TRUE; i break; \ default: usage(argv[O]); break; { eV else if (isdigit(argv[i][O])) datasize = atoi(argv[i]); else «x lpdest = argv[i]; woeu s s 1С ft ITO I I II Функция: main •c \\ -э \\ v •3 \\ -a \\ •>, \\ • * , \ ' • » \ \ V 1 . // Описание: // Настраивает простой сокет ICMP и создает заголовок ICMP. Добавляет Ы э т II соответствующий расширенный заголовок IP и начинает рассылку и_А8Ч // эхо-запросов ICMP по конечным точкам. Для каждой отправки и в) •* // приема мы задаем таймаут во избежание простоя, когда конечная } // точка не отвечает. Принятый пакет декодируется. tnn<3 i n t m a i n ( i n t a r g c , c h a r * * a r g v ) WSADATA wsaData; >тв* ои .що/ SOCKET sockRaw = INVALID_S0CKETfOTt struct soGJttddr.in dest, from; i n t *« ** f bread, *• fromlen = sizeof(from), timeout = 1000, >J- ret; ,, char *icmp_data = NULL^.O T •recvbuf = NULL; ((' unsigned int addr = 0; "> USHORT seq_no = 0; struct hostent *hp = NULL; (8 IpOptionHeader ipopt; C» «S» ем V\ t -d •n) if (WSAStartup(HAKEW0RD(2, 2), &wsaData) != 0) { printf("WSAStartup() failed: Xd\n", GetLastErrorO); return - 1 ; см. след. стр. 4 0 8 ЧАСТЬ II Интерфейс прикладного программирования Winsock Листинг 13-1. {продолжение) г-£МнмтсшТ1 } в q М \\ . J V a l i d a t e A r g s ( a r g c , a r g v ) ; \\ffl * е 1 DO«-S // Я о // Параметрам SO_RCVTIMEO и SO_SNDTIMEO требуется флаг // WSA_FLAG_OVERLAPPED. Если последний параметр WSASocket - NULL, // весь ввод-вывод на сокете синхронный, внутренний код ожидания // пользовательского режима никогда не будет выполнен, а потому { // окончательно блокируется ввод-вывод в режиме ядра. // Для сокета, созданного функцией socket, атрибут перекрытого // ввода-вывода задается автоматически. Однако в данном случае // создания простого сокета нужно использовать WSASocket. // . i // Если вы хотите использовать таймаут с синхронным неперекрытым { // сокетом, созданным WSASocket с последним параметром // равным NULL, задайте таймаут функцией select либо задействуйте \\ // WSAEventSelect и укажите таймаут в функции ^чдкн^Ф \\ // WSAWaitForMultipleEvents. \\ // аэмпО \\ sockRaw = WSASocket (AF_INET, SOCK.RAW, IPPROTO_ICMP, NULL, 0, г Н \\ WSA_FLAG_OVERLAPPED); )0 \\ if (sockRaw == INVALID_SOCKET) < B \\ { »qr. \\ printf("WSASocket() failed: Xd\n", WSAGetLaetErrorO); ,', т у return -1; \\ if (bRecordRoute) * II Настройка отправки расширенного заголовка // IP с каждым ICMP-пакетом fMooe 1- ZeroMemory(&ipopt, s i z e o f ( i p o p t ) ) ; ipopt.code = IP_RECORD_ROUTE; // Параметр записи маршрута i p o p t . p t r = 4; // Укажите смещение для первого адреса i p o p t . l e n = 39; // Длина расширенного заголовка ret = setsockopt(sockRaw, IPPROTO_IP, IP_OPTIONS, t (char *)&ipopt, s i z e o f ( i p o p t ) ) ; i» if ( r e t == SOCKET.ERROR) - s ?ol ben. < , l " printf("setsockopt(IP_OPTIONS) f a i l e d : X // ,t / ' , > - . - bread = setsockopt(sockRaw, SOL_SOCKET, SO_RCVTIHE0, (charOWimeout, sizeof(tinieout)); if(bread == SOCKET.ERROR) { printf("setsockopt(SO_RCVTIHEO) f a i l e d : Xd\n". WSAGetLastErrorO); 13 Простые сокеты 4 0 9 Листинг 13-1. (продолжение) т«. return - 1 ; -> > .1 timeout = 1000; bread = setsockopt(sockRaw, SOL_SOCKET, SO_SNDTIMEO, (char*)&timeout, slzeof(timeout)); if (bread == SOCKET_ERROR) { pnntf("setsockopt(SO_SNDTIMEO) failed: Xd\n", WSAGetLastErrorQ); ,,.-,,,_ return - 1 ; } memset(&dest, 0, sizeof(dest)); '" // ,"'// // При необходимости разрешается имя конечной точки " !П * II '" dest.sin_family = AF_INET; •' " if ((dest.sin_addr.s_addr = inet_addr(lpdest)) == INADDR.NONE) f >i- { ij if ((hp = gethostbyname(lpdest)) != NULL) ; memcpy(&(dest.sin_addr), hp->h_addr, hp->h_length); dest.sin_family = hp->h_addrtype; printf("dest.sin_addr = Xs\n", inet_ntoa(dest.sin_addr)); else i- ru- printf("gethostbyname() failed: Xd\n", WSAGetLastErrorO); }ii . >t.> п return - 1 ; , <, > ! я // Создание ICMP-пакета datasize += sizeof(IcmpHeader); icmp.data = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, MAX_PACKET); recvbuf = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, MAX_PACKET); if (!icmp_data) < printf("HeapAlloc() failed: Xd\n", GetLastErrorO); return -1; memset(icmp_data,0,MAX_PACKET); FillICMPData(icmp_data,datasize); // Начало отправки/приема ICMP-пакетов whiled) i см. след. стр. 410 ЧАСТЬ II Интерфейс прикладного программирования Winsock Листинг 13-1. {продолжение) it ч - static int nCount = 0; ,f muJei int bwrote, { if (nCount++ == 4) ei»Te* • bend break, .» beei4) M. } ((IcmpHeader»)icmp_data)->i_cksum = 0; ((IcmpHeader*)icmp_data)->timestamp = 6etTickCount(); ((IcmpHeader*)icmp_data)->i_seq = seq_no++; { ((IcmpHeader*)icmp_data)->i_cksum = lid *)r»3*ea checksum((USHORT*)icmp_data, datasize); » < \\ bwrote = sendto(sockRaw, icmp_data, datasize, 0, (struct sockaddr*)&dest, sizeof(dest)); if (bwrote == SOCKET_ERROR) { if (WSAGetLastErrorO == WSAETIMEDOUT) • > { *e ) i i printf('timed out\n"); } continue; } ма pnntf('sendtoO failed- Xd\n", WSAGetLastErrorO); ) return -1, < > M l * if (bwrote < datasize) } { iin printf("Wrote %d bytes\n", bwrote); } bread = recvfrom(sockRaw, recvbuf, MAX_PACKET, 0, (struct sockaddr*)&from, &fromlen); if (bread == SOCKET_ERROR) \ { e m ^ s " W 0 I e»H«fteo0 \\ if (WSAGetLastErrorO == WSAETIMEDOUT) \\ { r ' printf("timed out\n"); continue; } printf("recvfrom() failed' Xd\n", WSAGetLastErrorO); b.qets-.') >i return -1; ) } I {,). )-. DecodeICMPHeader(recvbuf, bread, &from); о ,{ Sleep(IOOO), »m } ЧР II Очистка 'i // if (sockRaw "= INVALID_SOCKET) closesocket(sockRaw), HeapFree(GetProcessHeap(), 0, recvbuf); HeapFree(GetProcessHeap(), 0, icmp_data), ГЛАВА 13 Простые сокеты 411 Листинг 13-1. {продолжение) WSACleanupO; return 0; Одна из значимых особенностей этого примера Ping — использование параметра сокета IP_OPTIONS Мы применяем параметр записи маршрута IP, чтобы когда ICMP-пакет достигнет маршрутизатора, его IP-адрес был добав- лен в расширенный заголовок IP в месте, обозначенном полем смещения Это смещение увеличивается на 4 каждый раз, когда маршрутизатор добав- ляет адрес, поскольку для IP версии 4 длина IP-адреса равна 4 байтам В этой книге мы не касаемся IP версии 6, поскольку имеющиеся платформы Win- dows пока не поддерживают ее После приема эхо-ответа расширенный за- головок (option header) декодируется и на экран выводятся IP-адреса и име- на узлов пройденных маршрутизаторов. (Подробнее о других типах доступ- ных параметров IP — в главе 9) Программа Traceroute Это еще один ценный сетевой инструмент, позволяющий определять IP-ад- реса маршрутизаторов, проходимых запросом на пути к определенному узлу Ping также позволяет определить IP-адреса маршрутизаторов, используя па- раметр записи маршрута в расширенном заголовке IP, однако его возмож- ности ограничены всего девятью транзитами — максимальным простран- ством, предназначенным для адресов в расширенном заголовке Транзит происходит всякий раз, когда IP-дейтаграмма проходит маршрутизатор для перехода в другую физическую сеть Для маршрутов с более чем девятью та- кими переходами используйте Traceroute В основе Traceroute лежит идея отправки UDP-пакета адресату и посте- пенного изменения времени жизни (time-to-live, TTL) Первоначально TTL пакета равен 1, и когда пакет достигает первого маршрутизатора, его TTL сбрасывае гея, и маршрутизатор генерирует ICMP-пакет со сведениями о пре- вышении лимита времени Тогда начальное значение TTL увеличивается на 1, так что на сей раз UDP-пакет достигает следующего маршрутизатора, а тот тоже отсылает ICMP-пакет по превышению лимита времени Совокупность этих ICMP-сообщений дает список IP-адресов, пройденных на пути к конеч- ной точке Когда TTL увеличится настолько, что UDP-пакет достигнет иско- мой конечной точки, возвращается ICMP-сообщение о недостижимости пор- та, поскольку на получателе ни один процесс не ждет вашего сообщения Traceroute полезна тем, что дает подробнейшую информацию о маршруте до конкретного узла — это часто необходимо при многоадресной передаче или проблемах с маршрутизацией Впрочем, программная реализация функ- циональности Traceroute необходима реже, чем Ping Существует два способа выполнить программу Traceroute Во-первых, вы можете использовать UDP-пакеты и отправлять дейтаграммы, постепенно изменяя TTL Каждый раз по истечении TTL будет возвращаться ICMP-сообще- 4 1 2 ЧАСТЬ II Интерфейс прикладного программирования Wmsock ние. Этот метод требует наличия обычного сокета протокола UDP (см. главу 7) для отправки сообщений, а также сокета типа SOCK_RAW под протокол IPPROTOJCMP для чтения возвращенных сообщений. TTL для UDP-сокета можно контролировать через параметр сокета IPJTTL, либо создать UDP-co- кет и использовать параметр IP_HDRINCL (см. далее в этой главе), чтобы вруч- ную задать TTL в заголовке IP. Но это чересчур трудоемкая задача. Другой метод — просто отправлять ICMP-пакеты адресату с постепенным изменением TTL В результате также возвращаются ICMP-сообщения об ошиб- ке по истечении TTL. Это похоже на пример Ping, в том смысле, что требу- ется только один сокет (для протокола ICMP). В папке с примерами на при- лагаемом компакт-диске есть пример реализации Traceroute с использова- нием ICMP-пакетов (Traceroute.c). Протокол IGMP Протокол IGMP используется многоадресным вещанием IP для управления членством в многоадресных группах. (Об использовании Winsock для вступ- ления в такую группу и выхода из нее — в главе 11.) Когда программа соеди- няется с многоадресной группой, она отправляет ЮМР-сообщение каждому маршрутизатору локальной сети, используя специальный адрес группы всех маршрутизаторов — 224-0.0.2. Сообщение информирует маршрутизаторы о наличии получателя для любых данных, предназначенных для многоадресной группы. Только после этого маршрутизаторы начинают перенаправлять дан- ные группы зарегистрированному получателю. Без этой возможности много- адресная передача данных ничем не отличалась бы от широковещания. Некоторую путаницу создают две версии протокола IGMP: версии 1 и 2, которые описаны в RFC 1112 и RFC 2236 соответственно. Главное различие между ними — версия 1 не позволяет узлу дать маршрутизатору указание о прекращении отправления данных, предназначенных для многоадресной группы. Другими словами, когда узел решает выйти из группы, он не уведом- ляет об этом маршрутизатор, который продолжает распространять данные для этой группы, пока не убеждается в отсутствии отклика. Версия 2 предус- мотривает четкое и немедленное уведомление узлами маршрутизаторов о выходе из группы. Кроме того, немного различаются форматы заголовка двух версий (рис. 13-2 и 13-3)- Оба заголовка просты, поскольку имеют раз- мер всего 8 байт. ' ч i < ton йерсия \ШР , 8-бишзб яеяегюльзуеше толе 16-йшая контрольная сумма НЗМР зг-i ,г Рис. 13-2. Формат заголовка IGMPvi Г Л А В А 13 Простые сокеты 413 8-битный тип IGMP 8-битное поле максимального времени ожидания 16-битная контрольная сумма 1GMP 32-битный адрес многоадресной рассылки (fP-адрес класса D) Рис. 13-3. Формат заголовка IGMPv2 В версии 1 два четырехбитных поля: первое — версия IGMP, второе — тип сообщения. В версии 2 эти два поля заменяет одно восьмибитное Неисполь- зуемое в версии 1 поле становится полем максимального времени ожидания (Max Response Time). Оно используется только при передаче запроса о член- стве: определяет максимальное время ожидания ответа в десятых долях се- кунды. Во всех других случаях отправитель задает в этом поле 0, и получа- тели игнорируют его. Для IGMPvl полем версии всегда является первое, а в поле типа задается одно из двух возможных значений- 0x1 — запрос о членстве в группе, 0x2 — отчет о членстве. Маршрутизаторы используют запрос о членстве узла (0x1), чтобы определить, к какой многоадресной группе причислен узел В этом случае полю адреса группы присваивается 0. Маршрутизатор отправляет пакет по адресу всех узлов (224.0 0 1) Если узлы по-прежнему являются членами какой-либо группы, каждый из них возвращает отчет о членстве (тип ЮМР-сообщения 0x2) вместе с адре- сом группы, в которой зарегистрирован. При первичной регистрации узла в группе сообщение с отчетом о членстве также отправляется на групповой адрес маршрутизаторов. В версии 2 протокола IGMP предусмотрены следующие типы сообщений. Ш 0x11 — запрос о членстве; • 0x12 — отчет о членстве версии 1; В 0x16 — отчет о членстве версии 2; ; j ^ Ш 0x17 — выход из группы. ^„ t . Как видите, добавлены два новых типа сообщения: отчет версии 2 (0x16) и выход из группы (0x17). Два других сообщения аналогичны сообщениям версии 1 (запрос и отчет по членству), хотя их значения различаются. Од- нако не забывайте, что IGMP-пакет версии 2 содержит поля версии и типа сообщения в одном поле, а 0x11 в двоичном исчислении — 00010001, что похоже на пакет IGMP со значением 1 в поле версии и типа. Запрос о членстве (0x11) в версии 2 слегка отличается от такого запроса в версии 1, поскольку позволяет выяснить членство для конкретного груп- пового адреса. Механизм таков: группе отправляется пакет запроса о членстве с конкрет- ным адресом в поле адреса. Кроме того, поле максимального времени ожи- дания позволяет задать время, в течение которого узлы должны отвечать на запрос до прекращения перенаправления им многоадресного трафика ука- занной группы. 4 1 4 ЧАСТЬ И Интерфейс прикладного программирования Winsock Использование IP_HDRINCL Как уже упоминалось, с простыми сокетами могут работать только некото- рые протоколы типа ICMP и IGMP Нельзя создать простой сокет при помо- щи IPPROTOJJDP и манипулировать заголовком UDP, это же относится к TCP Для манипулирования заголовком IP, TCP или UDP (да и любого друго- го вложенного в IP протокола) необходимо использовать с простым соке- том параметр IP_HDRINCL, который позволяет сформировать собственный заголовок IP или других протоколов Если вы хотите реализовать собственную схему протокола вложенного в IP, создайте простой сокет и используйте в качестве ссылки на протокол значение IPPROTO_RAW Это позволит самостоятельно задать поле протокола в заголовке IP и сформировать заголовок протокола В этом разделе мы по шагам рассмотрим формирование собственных пакетов UDP Разобравшись, как манипулировать заголовком UDP, вы без труда сможете создать собствен- ные заголовки протокола и управлять другими протоколами под IP При использовании параметра IPHDRINCL требуется самостоятельно заполнять заголовок IP для каждого вызова отправки, а также заголовки лю- бых других вложенных протоколов (см главу 9, рис 9-3) Заголовок UDP проще Его величина всего 8 байт, при том он содержит всего четыре поля (рис 13-4) Первые два хранят 16-битные номера портов отправителя и ад- ресата Третье поле определяет длину UDP, равную суммарной длине заголов- ка UDP и данных (в байтах) Четвертое — поле контрольной суммы, которое мы рассмотрим вкратце В конце пакета UDP размещаются данные 11Мйт*ый яорт отправителя 16-бйТнаядля«аШР 16-битная контрошш сумма UDP Рис. 13-4. Формат заголовка UDP Поскольку UDP — ненадежный протокол, вычисление контрольной сум- мы не обязательно, однако, для полноты картины мы рассмотрим и этот ас- пект В отличие от контрольной суммы IP, которая охватывает только заго- ловок IP, контрольная сумма UDP включает данные и часть заголовка IP До- полнительные поля, требуемые для вычисления контрольной суммы UDP, получили название псевдозаголовка Он состоит из следующих элементов В 32-битный IP-адрес отправителя (заголовок IP), эп Ш 32-битный IP-адрес получателя (заголовок IP), Ш 8-битное обнуленное поле, • 8-битное поле протокола, • 16-битная длина UDP Помимо этих элементов существуют заголовок UDP и данные Метод вы- числения контрольной суммы аналогичен IP и ICMP 1б-битная комплемен- тарная сумма Если данные выражены нечетным числом, для вычисления конт- |