Вопрос
У меня есть SIP-приложение, которому необходимо отправлять UDP-пакеты для настройки SIP-вызовов.В SIP есть механизм тайм-аута для устранения сбоев доставки.Еще одна вещь, которую я хотел бы иметь возможность сделать, это определить, закрыт ли UDP-сокет, чтобы дождаться 32-секундного интервала повторной передачи, используемого SIP.
Случаи, на которые я ссылаюсь, - это когда попытка отправки в сокет UDP приводит к тому, что удаленный хост генерирует пакет недоступности назначения ICMP.Если я попытаюсь отправить 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 завершилась неудачей, а не просто ждать истечения времени ожидания приложения по истечении произвольного периода?
Решение
Почти 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
Другие советы
Icmp использует идентификатор , который, по-видимому, отличается для каждого icmp " сеанса " (для каждого сокета icmp). Таким образом, ответ на пакет icmp, не отправленный одним и тем же сокетом , будет отфильтрован для вас. Вот почему этот кусок кода не будет работать. (Я не уверен в этом. Это всего лишь предположение после рассмотрения некоторого трафика ICMP.)
Вы можете просто пропинговать хост и посмотреть, сможете ли вы достичь его или нет, а затем попробовать свой SIP. Однако это не сработает, если другой хост отфильтровывает icmp.
Уродливое (но работающее) решение использует winpcap . (Иметь это как единственное работающее решение кажется слишком плохим, чтобы быть правдой.)
Под winpcap я подразумеваю, что вы можете перехватить ICMP-трафик , а затем узнать, относится ли полученный пакет к отправке вашего UDP-пакета или нет .
Вот пример для захвата пакетов tcp: http://www.tamirgal.com/home/ ? SourceView.aspx Item = SharpPcap & амп; Файл = Example6.DumpTCP.cs (Это не должно быть слишком сложно сделать то же самое с ICMP.)
ОБНОВЛЕНИЕ: я думаю, что схожу с ума .... Этот фрагмент кода, который вы опубликовали, также работает для меня ...
Следующий фрагмент кода хорошо работает для меня (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");
}
}
}
}
}
Просто используйте подключенные сокеты udp, и ОС совпадет с недоступным icmp и вернет ошибку в сокете udp.
Google для подключенных сокетов udp.
В Интернете есть несколько сообщений, в которых упоминается проблема недоступности пакетов ICMP-порта, которые больше не доступны в Vista.
- http://www.eggheadcafe.com/software/aspnet/31961998/icmp-port-unreachable-and.aspx
- http://social.msdn.microsoft.com/Forums/en-US/Offtopic/thread/5bd8b275-cc6f-43cd-949d-7c411973b2f3/
Стек должен выдать вам исключение при получении ICMP.Но это не так, по крайней мере, в Vista.И, следовательно, вы пытаетесь найти обходной путь.
Мне не нравятся ответы, в которых говорится, что это невозможно, но, похоже, так оно и есть.Поэтому я предлагаю вам вернуться на шаг назад к исходной проблеме, которая заключалась в длительных тайм-аутах в SIP.
- Вы могли бы позволить пользователю настроить время ожидания (следовательно, в некотором роде соответствовать спецификации).
- Вы можете начать выполнять другие действия (например, проверять другие прокси) до того, как истечет время ожидания.
- Вы могли бы кэшировать известные поврежденные места назначения (но для этого потребуется хорошее управление кэшем.
- Если icmp и udp не выдают надлежащих сообщений об ошибках, попробуйте tcp или другой протокол.Просто для того, чтобы получить желаемую информацию.
(Все возможно, просто для этого может потребоваться много ресурсов.)
Я пишу это как отдельный ответ, поскольку детали полностью отличаются от того, что я написал ранее.
Итак, основываясь на комментарии 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 - отличный ресурс для сетевых протоколов низкого уровня.
Надеюсь, это поможет.
Итак, вы хотите получить возвращаемый icmp-пакет dest unreachable программным путем?Трудный вопрос.Я бы сказал, что сетевой стек впитывает это до того, как вы сможете к нему приблизиться.
Я не думаю, что чистый подход C # здесь сработает.Вам нужно будет использовать перехват на уровне драйвера, чтобы подключиться.Взгляните на это приложение, которое использует Windows ' ipfiltdrv.sys для захвата пакетов (icmp, tcp, udp и т.д.) И чтения / воспроизведения с ними с помощью управляемого кода (c #).
http://www.codeproject.com/KB/IP/firewall_sniffer.aspx?display=Print
- Ойсин