Прослушивание ICMP-пакетов в C # - PullRequest
7 голосов
/ 09 марта 2009

У меня есть приложение SIP, которое должно отправлять пакеты UDP для настройки вызовов SIP. SIP имеет механизм тайм-аута для обработки ошибок доставки. Еще одна вещь, которую я хотел бы сделать, это определить, закрыт ли сокет UDP, чтобы ждать 32-секундного интервала повторной передачи, который использует SIP.

Я имею в виду случаи, когда попытка отправки на сокет UDP приводит к тому, что удаленный хост генерирует пакет ICMP Destination Unreachable. Если я пытаюсь отправить пакет UDP на хост, который работает, но порт не прослушивает, я вижу сообщение ICMP, возвращаемое с трассировщиком пакетов, но вопрос в том, как мне получить к нему доступ из моего кода C #?

Я играю с необработанными сокетами, но пока не смог получить пакеты ICMP, которые будут получены моей программой. Приведенный ниже пример никогда не получает пакет, даже если на мой ПК поступают сообщения ICMP.

Socket icmpListener = new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.Icmp);
icmpListener.Bind(new IPEndPoint(IPAddress.Any, 0));

byte[] buffer = new byte[4096];
EndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
int bytesRead = icmpListener.ReceiveFrom(buffer, ref remoteEndPoint);
logger.Debug("ICMPListener received " + bytesRead + " from " + remoteEndPoint.ToString());

Ниже приведена трассировка Wireshark, показывающая ответы ICMP, поступающие на мой ПК от попытки отправить пакет UDP с 10.0.0.100 (мой ПК) на 10.0.0.138 (мой маршрутизатор) через порт, который я знаю, что он не прослушивает. Моя проблема заключается в том, как использовать эти ICMP-пакеты, чтобы понять, что отправка UDP не удалась, вместо того, чтобы просто ждать, пока приложение истекло через произвольный период?

ICMP responses to UDP send

Ответы [ 7 ]

13 голосов
/ 07 февраля 2012

Спустя почти 3 года, и я наткнулся на http://www.codeproject.com/Articles/17031/A-Network-Sniffer-in-C, который дал мне достаточно подсказки, чтобы помочь мне найти решение для получения пакетов ICMP в Windows 7 (не знаю насчет Vista, первоначальный вопрос был о, но я подозреваю, что это решение будет работать).

Два ключевых момента заключаются в том, что сокет должен быть привязан к одному конкретному IP-адресу, а не к IPAddress.Any, и к вызову IOControl, который устанавливает флаг SIO_RCVALL.

Socket icmpListener = new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.Icmp);
icmpListener.Bind(new IPEndPoint(IPAddress.Parse("10.1.1.2"), 0));
icmpListener.IOControl(IOControlCode.ReceiveAll, new byte[] { 1, 0, 0, 0 }, new byte[] { 1, 0, 0, 0 });

byte[] buffer = new byte[4096];
EndPoint remoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
int bytesRead = icmpListener.ReceiveFrom(buffer, ref remoteEndPoint);
Console.WriteLine("ICMPListener received " + bytesRead + " from " + remoteEndPoint);
Console.ReadLine();

Мне также пришлось установить правило брандмауэра, чтобы разрешить получение пакетов, недоступных через порт ICMP.

netsh advfirewall firewall add rule name="All ICMP v4" dir=in action=allow protocol=icmpv4:any,any
3 голосов
/ 08 августа 2011

Просто используйте подключенные сокеты udp, и ОС совпадет с недоступным icmp и вернет ошибку в сокете udp.

Google для подключенных сокетов udp.

3 голосов
/ 20 марта 2009

ОБНОВЛЕНИЕ: Я думаю, что схожу с ума .... Этот кусок кода, который вы опубликовали, также работает для меня ...

У меня работает следующий фрагмент кода (xp sp3):

using System;
using System.Net;
using System.Net.Sockets;

namespace icmp_capture
{
    class Program
    {
        static void Main(string[] args)
        {            
            IPEndPoint ipMyEndPoint = new IPEndPoint(IPAddress.Any, 0);
            EndPoint myEndPoint = (ipMyEndPoint);
            Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.Icmp);            
            socket.Bind(myEndPoint);
            while (true)
            {

                /*                
                //SEND SOME BS (you will get a nice infinite loop if you uncomment this)
                var udpClient = new UdpClient("192.168.2.199", 666);   //**host must exist if it's in the same subnet (if not routed)**              
                Byte[] messagebyte = Encoding.Default.GetBytes("hi".ToCharArray());                
                int s = udpClient.Send(messagebyte, messagebyte.Length);
                */

                Byte[] ReceiveBuffer = new Byte[256];
                var nBytes = socket.ReceiveFrom(ReceiveBuffer, 256, 0, ref myEndPoint);
                if (ReceiveBuffer[20] == 3)// ICMP type = Delivery failed
                {
                    Console.WriteLine("Delivery failed");
                    Console.WriteLine("Returned by: " + myEndPoint.ToString());
                    Console.WriteLine("Destination: " + ReceiveBuffer[44] + "." + ReceiveBuffer[45] + "." + ReceiveBuffer[46] + "." + ReceiveBuffer[47]);
                    Console.WriteLine("---------------");
                }
                else {
                    Console.WriteLine("Some (not delivery failed) ICMP packet ignored");
                }
            }

        }
    }
}
3 голосов
/ 15 марта 2009

Icmp использует идентификатор , который, по-видимому, отличается для каждой "сессии" icmp (для каждого сокета icmp). Таким образом, ответ на пакет icmp, не отправленный тем же сокетом , для вас отфильтрован . Вот почему этот кусок кода не будет работать. (Я не уверен в этом. Это всего лишь предположение после рассмотрения некоторого трафика ICMP.)

Вы можете просто пропинговать хост и посмотреть, сможете ли вы достичь его или нет, а затем попробовать свой SIP. Однако это не сработает, если другой хост отфильтровывает icmp.

Уродливое (но работающее) решение использует winpcap . (Иметь это как единственное действующее решение кажется слишком плохим, чтобы быть правдой.)

Я имею в виду, используя winpcap, что вы можете перехватить ICMP-трафик , а затем посмотреть, соответствует ли перехваченный пакет тому, что ваш UDP-пакет недоставлен или нет .

Вот пример для захвата пакетов tcp: http://www.tamirgal.com/home/SourceView.aspx?Item=SharpPcap&File=Example6.DumpTCP.cs (Это не должно быть слишком сложно сделать то же самое с ICMP.)

2 голосов
/ 20 марта 2009

В Интернете есть несколько публикаций, в которых упоминается проблема недоступности пакетов ICMP Port, недоступных в Vista.

Стек должен выдавать вам исключение при получении ICMP. Но это не так, по крайней мере, на Vista. И, следовательно, вы пытаетесь обойти.

Мне не нравятся ответы, в которых говорится, что это невозможно, но кажется, что так. Поэтому я предлагаю вам вернуться к первоначальной проблеме, которая заключалась в длительных таймаутах в SIP.

  • Вы можете позволить пользователю настроить тайм-аут (следовательно, своего рода соответствие спецификация).
  • Вы можете начать делать другие вещи (например, проверять другие прокси), прежде чем тайм-аут заканчивается.
  • Вы можете кэшировать известные плохие места назначения (но для этого потребуется хорошее управление кешем.
  • Если icmp и udp не выдают правильных сообщений об ошибках, попробуйте tcp или другой протокол. Просто чтобы получить нужную информацию.

(Все возможно, просто может потребоваться много ресурсов.)

1 голос
/ 18 марта 2009

Итак, вы хотите программно подобрать dest недостижимый возвратный пакет icmp? Трудный Я бы сказал, что сетевой стек впитывает это до того, как вы приблизитесь к нему.

Я не думаю, что чистый подход C # будет работать здесь. Вам понадобится перехват уровня на уровне драйвера, чтобы подключиться. Взгляните на это приложение, которое использует Windows ipfiltdrv.sys для перехвата пакетов (icmp, tcp, udp и т. Д.) И чтения / воспроизведения их с помощью управляемого кода C #).

http://www.codeproject.com/KB/IP/firewall_sniffer.aspx?display=Print

  • Oisin
1 голос
/ 18 марта 2009

Я пишу это как отдельный ответ, так как детали полностью отличаются от того, что я написал ранее.

Итак, основываясь на комментарии Kalmi об идентификаторе сеанса, я подумал о том, почему я могу открыть две программы ping на одной машине, и ответы не пересекаются. Они оба ICMP, поэтому оба используют необработанные сокеты без портов. Это означает, что что-то в стеке IP должно знать, для какого сокета предназначались эти ответы. Для проверки связи выясняется, что в данных пакета ICMP используется идентификатор, являющийся частью ECHO REQUEST и ECHO REPLY.

Затем я наткнулся на этот комментарий в Википедии о ICMP :

Хотя сообщения ICMP содержатся в рамках стандартных дейтаграмм IP, ICMP сообщения обычно обрабатываются как особый случай, отличающийся от обычная обработка IP, а не обрабатывается как обычный подпротокол IP. Во многих случаях необходимо проверить содержимое ICMP сообщение и доставить соответствующий сообщение об ошибке в приложении, которое сгенерировал исходный IP-пакет, тот, который вызвал отправку ICMP-сообщение.

Который был разработан (косвенно) здесь :

Интернет-заголовок плюс первые 64 биты исходных данных дейтаграммы. Эти данные используются хостом для соответствия сообщение соответствующему процесс. Если протокол более высокого уровня использует номера портов, предполагается, что они быть в первых 64 битах данных исходные данные дейтаграммы.

Поскольку вы используете UDP, который использует порты, возможно, сетевой стек перенаправляет сообщение ICMP обратно в исходный сокет. Вот почему ваш новый отдельный сокет никогда не получает эти сообщения. Я полагаю, UDP использует ICMP-сообщение.

Если я прав, одно из решений этого - открыть необработанный сокет и вручную создать ваши UDP-пакеты, прослушать все, что возвращается, и обработать сообщения UDP и ICMP соответствующим образом. Я не уверен, как это будет выглядеть в коде, но я не думаю, что это будет слишком сложно и может считаться более «элегантным», чем решение winpcap.

Кроме того, эта ссылка http://www.networksorcery.com/enp/default1003.htm, представляется отличным ресурсом для сетевых протоколов низкого уровня.

Надеюсь, это поможет.

...