Продолжим круиз по стеку протоколов TCP/IP, и на этот раз остановимся на ICMP.
В реализации этого протокола нет ничего сложного – он примитивен как амёба. Однако в умелых руках ICMP превращается в грозное оружие, из-за чего Microsoft поспешила на корню отрубить к нему доступ, оставив прогерам на растерзание лишь пару функций из библиотеки iphlpapi.dll – вот их список:
5.3.0. Назначение протокола ICMP
Под аббревиатурой ICMP кроется "Internet Control Message Protocol". Протокол был введён для обмена сообщениями об ошибках между маршрутизаторами. Например отправили мы из своей сети пакет адресату, а адресат находится в другой сети и в данный момент по каким-то причинам не доступен (узел выключен, или кабель перебит). В этом случае, можно долго томиться в ожидании ответа, и по истечении тайм-аута так и не узнать об истинной причине отказа. Здесь и приходит на помощь ICMP, который оправляет нам смс'ку с кодом ошибки.
Примечательным является тот факт, что ICMP является частью протокола IP и делит с ним ложе на сетевом уровне(2) стека TCP/IP. Такой расклад приводит к тому, что хоть маршрутизатор и отправит нам сообщение об ошибке, нет никакой гарантии что мы его получим. Из протоколов сетевого стека, гарантировать доставку может только протокол транспортного уровня TCP, а он находится по лестнице выше IP. Задачей протокола ICMP является передача информации о проблемах в коммуникационной среде, но отнюдь не повышение уровня надёжности IP.
В глобальных сетях маршрутизатор (он-же шлюз и роутер) представляет собой специально выделенный для перенаправления трафика компьютер. Он имеет карту IP-адресов своих клиентов, в числе которых и следующие в цепочки шлюзы. Всё-что делает маршрутизатор, это сверяет поле с адресом получателя в IP-заголовке пакета со своей таблицей маршрутизации, и если такого IP-адреса не обнаруживает, то перенаправляет трафик соседнему маршрутизатору. На рисунке представлена схема подключения нескольких локальных сетей, при помощи маршрутизаторов R1-RN (route):
Здесь видно, что если узел из сети(B) захочет переслать пакет например узлу в сети(С), то пакет ждёт экстремальное путешествие – это коммутатор и сервак в своём периметре... после чего пакет выходит наружу и прямиком попадёт в лапы маршрутизатора R2. В маршрутной карте R2 прописаны IP-адреса только узлов сети(В) и оккупировавших его с двух сторон соседних маршрутизаторов R1 и R3.
Теперь R2 уменьшает в IP-заголовке пакета значение TTL на единицу, и перенаправляет пакет по цепочке дальше, в объятия маршрутизатора R3. Тот видит, что указанный IP-адрес имеется в его таблице, и пропускает пакет в свою сеть. В контексте данной темы, декремент значения TTL для нас в приоритете – именно на этот факт опирается утилита TRACERT, которая работает как-раз по протоколу ICMP.
Нужно иметь в виду, что маршрутизаторы общаются между собой по своим правилам. Если сеть небольшая, таблица маршрутизации может заполняться админом вручную. Однако в больших сетях таблицы заполняются уже динамически, самим маршрутизатором при помощи ARP-запросов. Например он может на автомате определять добавление новой сети, недоступность пункта назначения по тайм-ауту, добавление в сеть нового маршрутизатора, который может обеспечить более короткий путь к месту назначения и т.д.
Протоколом маршрутизации внутри автономных систем являлся протокол внутреннего шлюза "Interior Gateway Protocol" или IGP. Его расширением стал "Routing Information Protocol" RIP (не путать с посмертной надписью). Однако новый протокол "Open Shortest Path First" OSPF имеет более продвинутый набор возможностей, и в дословном переводе означает "Сначала открывать самый короткий путь" – в этом случае маршрутизатор отправляет ICMP сообщение типа(5) - Redirect. На радость устаревшим IGP и RIP, большинство маршрутизаторов поддерживают все перечисленные протоколы, хотя пальмовую ветвь носит OSPF.
5.3.1. Обзор протокола ICMP и типы его ошибок
Сообщения ICMP обычно содержат информацию об ошибках при обработке пакетов маршрутизаторами. Вполне возможна ситуация, когда сообщения будут передаваться в непрерывном режиме, полностью забивая пропускную способность канала. Чтобы предотвратить такой беспредел, маршрутизаторы не должны передавать сообщения ICMP, которые связаны с ошибками самих-же сообщений ICMP. При возникновении ошибок в процессе обработки нескольких кадров одного (фрагментированного) пакета, сообщения должны передаваться только для первого фрагмента. В прошлой части говорилось, что первым считается тот, у которого значение "Offset" в заголовке равно нулю.
В качестве несущего, ICMP использует ненадёжный и без установки связи протокол IP. Существуют две категории сообщений – сообщения об ошибках, и управляющие запросы. Первые сообщают о проблемах, с которыми может столкнуться маршрутизатор при обработке пакета. В свою очередь управляющие сообщения всегда ходят парами "запрос-ответ", и помогают админу выудить конкретную информацию у маршрутизатора – это касается таких инструментов как PING и TRACEROUTE.
Сообщения ICMP инкапсулируются в пакеты IP и имеют свой формат. Именно по этой причине мастдай наглухо забил гвоздями дверь на этот уровнь, т.к. при благоприятных условиях мы сами должны были формировать заголовки IP и ICMP, а значит без труда могли подменить IP-адрес отправителя. Такой/левый пакет по конвейеру поступил-бы на физический уровень стека TCP/IP, где драйвер TDI дополнил-бы пакет только MAC-адресом и выпустил-бы его в белый свет. В следующей части мы вернёмся к этому вопросу и рассмотрим его подробней.
Детали протокола ICMP описывает
Обычно прикладные программы только получают сообщения об ошибках – отправкой занимаются системные демоны. В числе запросов которые мы могли-бы отправить можно выделить лишь сообщение с типом(8) – это знакомый большинству из нас эхо-запрос PING. Но и в этом случае, как упоминалось выше, мастдай (редиска) забрал у нас эти права, а взамен вручил уже готовую функцию IcmpSendEcho(). Так-что нам остаётся лишь фильтровать входящий трафик на сообщения с ошибками. Выход – или ставить W2K (тогда о переносимости приложения можно забыть), или менять коня на Linux (что для привыкших к Win смерти подобно).
5.3.2. Практика – пишем утилиту PING
Здесь может возникнуть логичный вопрос – зачем тратить время и силы на свой пинг, когда в системе есть уже готовая утилита? Есть-то она есть, только каждый раз звать её из своего приложения слишком накладно. Можно даже вставить в код профайлер, и засечь время выполнения внешней утилиты – результат явно не обрадует. Если мы захотим из своего кода пропинговать не один, а например сразу сотни узлов (создав для каждого свой поток), то проще добежать до них пешком, чем воспользоваться системной тулзой PING. Поэтому практичней вызывать оптом и врозницу всего одну функцию IcmpSendEcho() с таким прототипом.. В случае ошибки, в регистре EAX она возвращает (-1), иначе – кол-во ответов хоста (как правило один):
Данные мы можем не отправлять, зато необходимо предварительно заполнить структуру "IP_OPTIONS_INFO", внутри которой имеются два интересных поля – это TTL и флаги. Под виндой, флагов всего два и они дают постановку МАС-уровню – фрагментировать пакет, если данные в нём превышают лимит (MF = more), или-же не фрагментировать его ни при каких обстоятельствах (DF = don't fragment). Значение в поле TTL определяет макс кол-во маршрутизаторов, которые сможет преодолеть наш PING:
После того как IcmpSendEcho() отправит запрос, удалённый хост заполнит наш приёмный буфер специальным пакетом. Этот пакет описывает структура WinSock под названием ICMP_ECHO_REPLY – вот её формат:
Таким образом, от нас требуется лишь задать опции, и для пущей вероятности в цикле отправить несколько раз один и тот-же запрос. Причём в каждой итерации цикла получим свою порцию инфы, которую пропарсив выведем на экран. Возможный вариант кода самопального PING представлен ниже:
Скрин снифера " Wireshark" показывает, что ICMP-пакеты не выходят из уровня IP.
Хоть я и отправлял запросы из своего пользовательского приложения, транспортные уровни TCP и UDP стека протоколов остались не при делах, и даже не подозревают об отправки мной запроса. Другими словами, в протоколе ICMP вообще отсутствует понятие порт, и весь обмен происходит исключительно на уровне маршрутизаторов IP.
5.3.3. Утилита TRACEROUTE
..это из той-же кухни, что и PING.
Основное назначение – хитростью отследить маршрут пакета от источника, к месту назначения. Мы уже знаем, что каждый маршрутизатор в сети уменьшает на единицу значение TTL в IP-заголовке пакета. Если маршрутизатор обнаруживает пакет с TTL=0, он дропает его и отправляет нам ICMP-сообщение типа(11), что означает "Лимит времени истёк" (см.таблицу с кодами ошибок выше). Так-как сообщение отправляет именно маршрутизатор, значит он-же и вставит в сообщение свой IP-адрес. Айпишник в полученной мессаге сдаёт маршрутизатор с потрахами и нам остаётся лишь вывести его на экран.
TRACEROUT – чемпион по бегу в мешках. Он прыгает от маршрутизатора к маршрутизатору собирая их айпи. Важным моментом в этой махинации является то, что установив сначала TTL=1, при каждом последующем запросе мы должны увеличивать его на единицу, чтобы очередной маршрутизатор передавал наш запрос на один шаг дальше. Когда запрос минуя все маршрутизаторы дойдёт до адресата, ошибка в поле "Status" структуры ICMP_ECHO_REPLY будет равна нулю, ..т.е. всё ок! В противном случае, там будет красоваться код 0x2B05 (11013) = IP_TTL_EXPIRED_TRANSIT. Возможные варианты этого поля перечислены ниже:
Воспользовавшись этим нехитрым приёмом, мы можем написать свой TRACERT. Вообще, функционал любого кода зависит от конкретной задачи, поэтому я не стал тут выворачиваться наизнанку, и ограничился лишь выводом IP-адресов и времени прохождения пакета в обе стороны. Вот пример исходника, который вы можете заточить под себя..
На всякий/пожарный, в скрепке цепляю обновлённый вариант инклуд, в который добавил некоторые структуры и константы ошибок.
В реализации этого протокола нет ничего сложного – он примитивен как амёба. Однако в умелых руках ICMP превращается в грозное оружие, из-за чего Microsoft поспешила на корню отрубить к нему доступ, оставив прогерам на растерзание лишь пару функций из библиотеки iphlpapi.dll – вот их список:
- IcmpCreateFile() – создать ICMP сессию;
- IcmpSendEcho() – отправить эхо-запрос получателю;
- IcmpCloseHandle() – закрыть текущую сессию.
5.3.0. Назначение протокола ICMP
Под аббревиатурой ICMP кроется "Internet Control Message Protocol". Протокол был введён для обмена сообщениями об ошибках между маршрутизаторами. Например отправили мы из своей сети пакет адресату, а адресат находится в другой сети и в данный момент по каким-то причинам не доступен (узел выключен, или кабель перебит). В этом случае, можно долго томиться в ожидании ответа, и по истечении тайм-аута так и не узнать об истинной причине отказа. Здесь и приходит на помощь ICMP, который оправляет нам смс'ку с кодом ошибки.
Примечательным является тот факт, что ICMP является частью протокола IP и делит с ним ложе на сетевом уровне(2) стека TCP/IP. Такой расклад приводит к тому, что хоть маршрутизатор и отправит нам сообщение об ошибке, нет никакой гарантии что мы его получим. Из протоколов сетевого стека, гарантировать доставку может только протокол транспортного уровня TCP, а он находится по лестнице выше IP. Задачей протокола ICMP является передача информации о проблемах в коммуникационной среде, но отнюдь не повышение уровня надёжности IP.
В глобальных сетях маршрутизатор (он-же шлюз и роутер) представляет собой специально выделенный для перенаправления трафика компьютер. Он имеет карту IP-адресов своих клиентов, в числе которых и следующие в цепочки шлюзы. Всё-что делает маршрутизатор, это сверяет поле с адресом получателя в IP-заголовке пакета со своей таблицей маршрутизации, и если такого IP-адреса не обнаруживает, то перенаправляет трафик соседнему маршрутизатору. На рисунке представлена схема подключения нескольких локальных сетей, при помощи маршрутизаторов R1-RN (route):
Здесь видно, что если узел из сети(B) захочет переслать пакет например узлу в сети(С), то пакет ждёт экстремальное путешествие – это коммутатор и сервак в своём периметре... после чего пакет выходит наружу и прямиком попадёт в лапы маршрутизатора R2. В маршрутной карте R2 прописаны IP-адреса только узлов сети(В) и оккупировавших его с двух сторон соседних маршрутизаторов R1 и R3.
Теперь R2 уменьшает в IP-заголовке пакета значение TTL на единицу, и перенаправляет пакет по цепочке дальше, в объятия маршрутизатора R3. Тот видит, что указанный IP-адрес имеется в его таблице, и пропускает пакет в свою сеть. В контексте данной темы, декремент значения TTL для нас в приоритете – именно на этот факт опирается утилита TRACERT, которая работает как-раз по протоколу ICMP.
Нужно иметь в виду, что маршрутизаторы общаются между собой по своим правилам. Если сеть небольшая, таблица маршрутизации может заполняться админом вручную. Однако в больших сетях таблицы заполняются уже динамически, самим маршрутизатором при помощи ARP-запросов. Например он может на автомате определять добавление новой сети, недоступность пункта назначения по тайм-ауту, добавление в сеть нового маршрутизатора, который может обеспечить более короткий путь к месту назначения и т.д.
Протоколом маршрутизации внутри автономных систем являлся протокол внутреннего шлюза "Interior Gateway Protocol" или IGP. Его расширением стал "Routing Information Protocol" RIP (не путать с посмертной надписью). Однако новый протокол "Open Shortest Path First" OSPF имеет более продвинутый набор возможностей, и в дословном переводе означает "Сначала открывать самый короткий путь" – в этом случае маршрутизатор отправляет ICMP сообщение типа(5) - Redirect. На радость устаревшим IGP и RIP, большинство маршрутизаторов поддерживают все перечисленные протоколы, хотя пальмовую ветвь носит OSPF.
5.3.1. Обзор протокола ICMP и типы его ошибок
Сообщения ICMP обычно содержат информацию об ошибках при обработке пакетов маршрутизаторами. Вполне возможна ситуация, когда сообщения будут передаваться в непрерывном режиме, полностью забивая пропускную способность канала. Чтобы предотвратить такой беспредел, маршрутизаторы не должны передавать сообщения ICMP, которые связаны с ошибками самих-же сообщений ICMP. При возникновении ошибок в процессе обработки нескольких кадров одного (фрагментированного) пакета, сообщения должны передаваться только для первого фрагмента. В прошлой части говорилось, что первым считается тот, у которого значение "Offset" в заголовке равно нулю.
В качестве несущего, ICMP использует ненадёжный и без установки связи протокол IP. Существуют две категории сообщений – сообщения об ошибках, и управляющие запросы. Первые сообщают о проблемах, с которыми может столкнуться маршрутизатор при обработке пакета. В свою очередь управляющие сообщения всегда ходят парами "запрос-ответ", и помогают админу выудить конкретную информацию у маршрутизатора – это касается таких инструментов как PING и TRACEROUTE.
Сообщения ICMP инкапсулируются в пакеты IP и имеют свой формат. Именно по этой причине мастдай наглухо забил гвоздями дверь на этот уровнь, т.к. при благоприятных условиях мы сами должны были формировать заголовки IP и ICMP, а значит без труда могли подменить IP-адрес отправителя. Такой/левый пакет по конвейеру поступил-бы на физический уровень стека TCP/IP, где драйвер TDI дополнил-бы пакет только MAC-адресом и выпустил-бы его в белый свет. В следующей части мы вернёмся к этому вопросу и рассмотрим его подробней.
Детали протокола ICMP описывает
Ссылка скрыта от гостей
Если коротко, то нужно создать структуру ICMP_Header и разместив её после IP-заголовка, отправить получателю посредством WinSock функции sendto(). В таблицах ниже представлена структура ICMP-сообщения и возможные варианты поля Type:Обычно прикладные программы только получают сообщения об ошибках – отправкой занимаются системные демоны. В числе запросов которые мы могли-бы отправить можно выделить лишь сообщение с типом(8) – это знакомый большинству из нас эхо-запрос PING. Но и в этом случае, как упоминалось выше, мастдай (редиска) забрал у нас эти права, а взамен вручил уже готовую функцию IcmpSendEcho(). Так-что нам остаётся лишь фильтровать входящий трафик на сообщения с ошибками. Выход – или ставить W2K (тогда о переносимости приложения можно забыть), или менять коня на Linux (что для привыкших к Win смерти подобно).
5.3.2. Практика – пишем утилиту PING
Здесь может возникнуть логичный вопрос – зачем тратить время и силы на свой пинг, когда в системе есть уже готовая утилита? Есть-то она есть, только каждый раз звать её из своего приложения слишком накладно. Можно даже вставить в код профайлер, и засечь время выполнения внешней утилиты – результат явно не обрадует. Если мы захотим из своего кода пропинговать не один, а например сразу сотни узлов (создав для каждого свой поток), то проще добежать до них пешком, чем воспользоваться системной тулзой PING. Поэтому практичней вызывать оптом и врозницу всего одну функцию IcmpSendEcho() с таким прототипом.. В случае ошибки, в регистре EAX она возвращает (-1), иначе – кол-во ответов хоста (как правило один):
C-подобный:
IcmpSendEcho(
IcmpHandle ; хэндл ICMP сессии, получим от IcmpCreateFile()
DestinationAddress ; IP-адрес получателя
RequestData ; указатель на необязательные данные
RequestSize ; ..размер этих данных
RequestOptions ; ВАЖНО!!! указатель на структуру "IP_OPTIONS_INFO"
ReplyBuffer ; указатель на приёмный буфер
ReplySize ; ..его длинна
Timeout ; значение тайм-аута (ожидания ответа)
);
Данные мы можем не отправлять, зато необходимо предварительно заполнить структуру "IP_OPTIONS_INFO", внутри которой имеются два интересных поля – это TTL и флаги. Под виндой, флагов всего два и они дают постановку МАС-уровню – фрагментировать пакет, если данные в нём превышают лимит (MF = more), или-же не фрагментировать его ни при каких обстоятельствах (DF = don't fragment). Значение в поле TTL определяет макс кол-во маршрутизаторов, которые сможет преодолеть наш PING:
C-подобный:
struct IP_OPTIONS_INFO ;// Всего 8 байт
Ttl db 0 ; сколько маршрутизаторов пройти
Tos db 0 ; тип сервиса =0 (для icmp)
Flags db 0 ; IP_FLAG_xx
OptionsSize db 0 ; размер доп.опций
OptionsData dd 0 ; указатель на них
ends
После того как IcmpSendEcho() отправит запрос, удалённый хост заполнит наш приёмный буфер специальным пакетом. Этот пакет описывает структура WinSock под названием ICMP_ECHO_REPLY – вот её формат:
C-подобный:
struct ICMP_ECHO_REPLY ;// Всего 64 байт
Address dd 0 ; адрес отправителя (хоста или шлюза)
Status dd 0 ; код ошибки операции
RoundTripTime dd 0 ; время трафика "туда-сюда"
DataSize dw 0 ; размер данных
Reserved dw 0 ; (выравнивание..)
DataPointer dd 0 ; указатель на данные
Options dd 0,0 ; опции из "IP_OPTIONS_INFO"
icmpData db 36 dup(0) ; данные (если есть)
ends
Таким образом, от нас требуется лишь задать опции, и для пущей вероятности в цикле отправить несколько раз один и тот-же запрос. Причём в каждой итерации цикла получим свою порцию инфы, которую пропарсив выведем на экран. Возможный вариант кода самопального PING представлен ниже:
C-подобный:
format pe console
entry start
include 'win32ax.inc'
;----------
.data
wsa WSADATA
ipOpt IP_OPTIONS_INFO
capt db 13,10,' PING example v0.1'
db 13,10,' ******************************'
db 13,10,' Type host.......: ',0
ping db 13,10
db 13,10,' Host.........: %s'
db 13,10,' Time.........: %.2ld ms'
db 13,10,' TTL..........: %d'
db 13,10,' Packet size..: %d bytes',0
succes db 13,10
db 13,10,' ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'
db 13,10,' PING successful..'
db 13,10,' Press any key for exit...',0
frmt db '%s',0 ; спецификатор для scanf()
hndl dd 0 ; под дескриптор ICMP сессии
addr dd 0 ; под IP-адрес назначения
count dd 2 ; счётчик запросов PING
outPack db 'Marylin example Ping' ; необязательные данные,
outPackLen dd $ - outPack ; ..и их размер.
inPack db 128 dup(0) ; приёмный буфер
errMes db 128 dup(0) ; буфер для строки с ошибкой
hostName db 64 dup(0) ; сюда юзер введёт имя хоста для пинга
;---------
.code
start:
;//--- Выводим шапку и принимаем имя хоста у юзера -------
invoke WSAStartup,0x0101,wsa ;
cinvoke printf,capt ;
cinvoke scanf,frmt,hostName ;
;//--- Обрабатываем введённую строку и получаем IP назначения -----------
invoke inet_addr,hostName ;// это IP вида "127.0.0.1" ???
cmp eax,-1 ; 0xFFFFFFFF = ошибка
jnz @create ; если нет ошибки..
invoke gethostbyname,hostName ;// это имя типа "mail.ru" ???
or eax,eax ; 0 = ошибка
jnz @name ; если No_Zero..
stdcall GetError ; иначе: встречаем её!
jmp @exit ; ..и на выход.
@name: ; юзер ввёл имя (не IP)
virtual at eax ; в EAX лежит адрес структуры "hostent"
host hostent ; определяем её как виртуальную
end virtual ;
mov eax,[host.h_addr_list] ; берём из "hostent" 4-байтный IP
mov eax,[eax] ; ^^^
mov eax,[eax] ; ^^^
@create: mov [addr],eax ; запомнить его в переменной!
;//--- Создаём ICMP сессию и задаём опции -------
invoke IcmpCreateFile ; создать сессию!
mov [hndl],eax ; запомнить её дескриптор
mov [ipOpt.Ttl],32 ; выставляем для пинга TTL=32
mov [ipOpt.Flags],IP_FLAG_DF ; флаг = не фрагментировать
;//--- Непосредственно PING в цикле -------------
@send: mov eax,[addr] ; EAX = IP назначения
invoke IcmpSendEcho,[hndl],eax,\ ; отправить эхо-запрос!!!
outPack,[outPackLen],\ ; ^^^
ipOpt,inPack,128,3000 ; ^^^ тайм-аут = 3000 ms
;//--- Парсим приёмный буфер ICMP_ECHO_REPLY ----
xor ebx,ebx ; EBX, EDX = 0
xor edx,edx ;
mov esi,inPack ; ESI = адрес буфера с ответом
mov eax,[esi] ; EAX = IP отправителя
invoke inet_ntoa,eax ; перевести его в строку
mov bl,[ipOpt.Ttl] ; EBX = TTL
mov ecx,dword[esi+8] ; ECX = "RoundTripTime" потраченое время
mov dx,word[esi+12] ; EDX = размер данных в байтах
cinvoke printf,ping,eax,ecx,ebx,edx ; вывод бодяги на экран!
dec [count] ; счётчик -1
jnz @send ; повторить, если счётчик не нуль
;//--- Финальная стадия ------------------------
cinvoke printf,succes ; мессага "Всё ОК!"
@exit: invoke IcmpCloseHandle,[hndl] ; прибить ECHO-сессию
cinvoke scanf,frmt,frmt+5 ; ждём нажатия клавиши..
invoke ExitProcess,0 ; на выход!
;//---------------------------------------------
;//--- Вспомогательная функция обработки ошибок
;//--- выводит характер ошибки в текстовом виде.
;//---------------------------------------------
GetError:
invoke WSAGetLastError
invoke FormatMessage,0x1000,0,eax,0,errMes,128,0
invoke CharToOem,errMes,errMes
cinvoke printf,errMes
ret
;//--- Секция импорта ------
section '.idata' import data readable ;
library kernel32,'kernel32.dll',\ ; импортируемые библиотеки
user32,'user32.dll',\ ; ^^^
wsock32,'wsock32.dll',\ ; ^^^
msvcrt,'msvcrt.dll',\ ; ^^^
iphlp,'iphlpapi.dll' ; ^^^
import iphlp, IcmpSendEcho,'IcmpSendEcho',IcmpCreateFile,'IcmpCreateFile',\
IcmpCloseHandle,'IcmpCloseHandle'
import msvcrt, printf,'printf',scanf,'scanf'
include 'api\kernel32.inc'
include 'api\user32.inc'
include 'api\wsock32.inc'
Скрин снифера " Wireshark" показывает, что ICMP-пакеты не выходят из уровня IP.
Хоть я и отправлял запросы из своего пользовательского приложения, транспортные уровни TCP и UDP стека протоколов остались не при делах, и даже не подозревают об отправки мной запроса. Другими словами, в протоколе ICMP вообще отсутствует понятие порт, и весь обмен происходит исключительно на уровне маршрутизаторов IP.
5.3.3. Утилита TRACEROUTE
..это из той-же кухни, что и PING.
Основное назначение – хитростью отследить маршрут пакета от источника, к месту назначения. Мы уже знаем, что каждый маршрутизатор в сети уменьшает на единицу значение TTL в IP-заголовке пакета. Если маршрутизатор обнаруживает пакет с TTL=0, он дропает его и отправляет нам ICMP-сообщение типа(11), что означает "Лимит времени истёк" (см.таблицу с кодами ошибок выше). Так-как сообщение отправляет именно маршрутизатор, значит он-же и вставит в сообщение свой IP-адрес. Айпишник в полученной мессаге сдаёт маршрутизатор с потрахами и нам остаётся лишь вывести его на экран.
TRACEROUT – чемпион по бегу в мешках. Он прыгает от маршрутизатора к маршрутизатору собирая их айпи. Важным моментом в этой махинации является то, что установив сначала TTL=1, при каждом последующем запросе мы должны увеличивать его на единицу, чтобы очередной маршрутизатор передавал наш запрос на один шаг дальше. Когда запрос минуя все маршрутизаторы дойдёт до адресата, ошибка в поле "Status" структуры ICMP_ECHO_REPLY будет равна нулю, ..т.е. всё ок! В противном случае, там будет красоваться код 0x2B05 (11013) = IP_TTL_EXPIRED_TRANSIT. Возможные варианты этого поля перечислены ниже:
C-подобный:
; ICMP_ECHO_REPLY поле "Status"
;================================
IP_SUCCESS = 0 ; успешно
IP_BUF_TOO_SMALL = 11001 ; Буфер ответов был слишком маленьким.
IP_DEST_NET_UNREACHABLE = 11002 ; Сеть назначения недоступна.
IP_DEST_HOST_UNREACHABLE = 11003 ; Хост назначения был недоступен.
IP_DEST_PROT_UNREACHABLE = 11004 ; Протокол назначения был недоступен.
IP_DEST_PORT_UNREACHABLE = 11005 ; Порт назначения был недоступен.
IP_NO_RESOURCES = 11006 ; Недостаточно ресурсов IP.
IP_BAD_OPTION = 11007 ; Указан неверный IP-адрес.
IP_HW_ERROR = 11008 ; Произошла аппаратная ошибка.
IP_PACKET_TOO_BIG = 11009 ; Пакет был слишком большим.
IP_REQ_TIMED_OUT = 11010 ; Время запроса истекло.
IP_BAD_REQ = 11011 ; Плохая просьба
IP_BAD_ROUTE = 11012 ; Плохой маршрут
IP_TTL_EXPIRED_TRANSIT = 11013 ; Время жизни (TTL) истекло в пути
IP_TTL_EXPIRED_REASSEM = 11014 ; TTL истекло при повторной сборки фрагмента
IP_PARAM_PROBLEM = 11015 ; Проблема параметров.
IP_SOURCE_QUENCH = 11016 ; Датаграммы приходят слишком быстро
IP_OPTION_TOO_BIG = 11017 ; Вариант IP был слишком велик.
IP_BAD_DESTINATION = 11018 ; Плохое место назначения.
IP_GENERAL_FAILURE = 11050 ; искажённый ICMP пакет
Воспользовавшись этим нехитрым приёмом, мы можем написать свой TRACERT. Вообще, функционал любого кода зависит от конкретной задачи, поэтому я не стал тут выворачиваться наизнанку, и ограничился лишь выводом IP-адресов и времени прохождения пакета в обе стороны. Вот пример исходника, который вы можете заточить под себя..
C-подобный:
format pe console
entry start
include 'win32ax.inc'
;----------
.data
wsa WSADATA
echoReply ICMP_ECHO_REPLY
ipOpt IP_OPTIONS_INFO
capt db 13,10,' TRACERT example v0.1'
db 13,10,' ******************************'
db 13,10,' Host...: ',0
crlf db 13,10,0
ping db 13,10,' TTL= %02d %02d ms %s',0
succes db 13,10
db 13,10,' ~~~~~~'
db 13,10,' TRACE successful..'
db 13,10,' Press any key for exit...',0
frmt db '%s',0 ;
hndl dd 0 ;
addr dd 0 ;
errMes db 128 dup(0) ;
hostName db 64 dup(0) ;
;----------
.code
start:
;//---- Шапка и запрашиваем имя хоста ------
invoke WSAStartup,0x0101,wsa
cinvoke printf,capt
cinvoke scanf,frmt,hostName
cinvoke printf,crlf
;//---- Вычисляем IP-адрес назначения ------
stdcall GetDestAddress ; (см.функцию в хвосте)
mov [addr],eax ; запомнить его в переменной!
;//---- Создаём сессию и задаём опции ------
invoke IcmpCreateFile
mov [hndl],eax
mov [ipOpt.Ttl],1 ;// выставляем стартовый TTL=1
mov [ipOpt.Flags],IP_FLAG_DF ; флаг = не фрагментировать пакет
;//---- Стартуем трейс.. -------------------
@trace: mov eax,[addr] ; EAX = IP удалённого хоста
invoke IcmpSendEcho,[hndl],eax,0,0,\ ; данных нет(0,0)
ipOpt,echoReply,64,2000 ; тайм-аут =2 сек
;-- парсим приёмный буфер ------------------
mov eax,[echoReply.Address] ; EAX = IP отправителя
invoke inet_ntoa,eax ; перевести его в строку
mov ecx,[echoReply.RoundTripTime] ; ECX = время туда-обратно
xor ebx,ebx ;
mov bl,[ipOpt.Ttl] ; EBX = текущий TTL
cinvoke printf,ping,ebx,ecx,eax ; вывод на экран!
inc byte[ipOpt.Ttl] ;// Важно!!! TTL+1
cmp [echoReply.Status],0 ; проверить статут на SUCCESS!
jnz @trace ; повторить, если не нуль..
;//---- Закругляемся.. ---------------------
cinvoke printf,<' <--- %s',0>,hostName
cinvoke printf,succes ; мессага "Всё ОК!"
invoke IcmpCloseHandle,[hndl] ; прибить ECHO-сессию
@exit: cinvoke scanf,frmt,frmt+5 ; ждём нажатия клавиши..
invoke ExitProcess,0 ; на выход!
;//*****************************************
;//---- Вспомогательные функции
;//*****************************************
GetError:
invoke WSAGetLastError
invoke FormatMessage,0x1000,0,eax,0,errMes,128,0
invoke CharToOem,errMes,errMes
cinvoke printf,errMes
ret
GetDestAddress:
invoke inet_addr,hostName ;// это IP вида "127.0.0.1" ???
cmp eax,-1 ; 0xFFFFFFFF = ошибка
jnz @ok ; если нет..
invoke gethostbyname,hostName ;// это имя типа "mail.ru" ???
or eax,eax ; 0 = ошибка
jnz @name ; если No_Zero..
stdcall GetError ; иначе: встречаем её!
pop eax ;
jmp @exit ; на выход с ошибкой.
@name: ; юзер ввёл имя (не IP)
virtual at eax ; в EAX лежит адрес структуры "hostent"
host hostent ; определяем её как виртуальную
end virtual ;
mov eax,[host.h_addr_list] ; берём из неё 4-байтный IP
mov eax,[eax] ; ^^^
mov eax,[eax] ; ^^^
@ok: ret
;//--- Секция импорта ------
section '.idata' import data readable ;
library kernel32,'kernel32.dll',\ ; импортируемые библиотеки
user32,'user32.dll',\
wsock32,'wsock32.dll',\ ; ^^^
msvcrt,'msvcrt.dll',\ ; ^^^
iphlp,'iphlpapi.dll' ; ^^^
import iphlp, IcmpSendEcho,'IcmpSendEcho',IcmpCreateFile,'IcmpCreateFile',\
IcmpCloseHandle,'IcmpCloseHandle'
import msvcrt, printf,'printf',scanf,'scanf'
include 'api\kernel32.inc'
include 'api\user32.inc'
include 'api\wsock32.inc'
На всякий/пожарный, в скрепке цепляю обновлённый вариант инклуд, в который добавил некоторые структуры и константы ошибок.
Вложения
Последнее редактирование: