在C#中侦听ICMP数据包

我有一个SIP应用程序,需要发送UDP数据包来设置SIP呼叫。 SIP具有应对传递失败的超时机制。 我希望能够做的另一件事是检测UDP套接字是否已关闭,以便等待SIP使用的32s重新传输间隔。

我所指的情况是,尝试发送到UDP套接字导致远程主机生成ICMP目标无法访问的数据包。 如果我尝试将UDP数据包发送到正在运行的主机但该端口没有监听,我可以看到ICMP消息通过数据包跟踪器返回,但问题是如何从我的C#代码访问该数据包?

我正在玩原始套接字,但尚未能够获得我的程序接收的ICMP数据包。 即使ICMP消息到达我的PC,下面的示例也从未收到数据包。

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响应进入我的PC,尝试从我知道它没有收听的端口上发送UDP数据包从10.0.0.100(我的PC)到10.0.0.138(我的路由器)。 我的问题是如何利用这些ICMP数据包来实现UDP发送失败而不是仅仅等待应用程序在任意一段时间后超时?

ICMP响应UDP发送

差不多3年后,我偶然发现了http://www.codeproject.com/Articles/17031/A-Network-Sniffer-in-C ,这给了我足够的提示,帮助我找到一个在Windows上接收ICMP数据包的解决方案7(不知道Vista,原始问题是关于但我怀疑这个解决方案会起作用)。

两个关键点是套接字必须绑定到单个特定IP地址而不是IPAddress.Any和设置SIO_RCVALL标志的IOControl调用。

 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 Port Unreachable数据包。

 netsh advfirewall firewall add rule name="All ICMP v4" dir=in action=allow protocol=icmpv4:any,any 

Icmp使用的标识符对于每个icmp“session”(对于每个icmp套接字)似乎都是不同的。 因此,对同一套接字未发送的icmp数据包回复将被有效地过滤掉 。 这就是为什么这段代码不起作用的原因。 (我不确定这一点。在查看一些ICMP流量之后,这只是一个假设。)

您可以简单地ping主机并查看是否可以访问它,然后尝试使用SIP。 但是,如果其他主机过滤掉icmp,则无效。

一个丑陋(但工作)的解决方案是使用winpcap(将此作为唯一可行的解​​决方案似乎太糟糕了。)

我的意思是使用winpcap是你可以捕获ICMP流量 ,然后查看捕获的数据包是否与你的UDP数据包无法传递有关

以下是捕获tcp数据包的示例: http ://www.tamirgal.com/home/SourceView.aspx?Item = SharpPcap&File = 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 unreachable,并在udp套接字中返回错误。

Google用于连接udp套接字。

网上有很多post提到无法在Vista上访问ICMP Port Unreachable数据包的问题。

堆栈应该在收到ICMP时返回exception。 但它至少在Vista上没有。 因此,您正在尝试解决方法。

我不喜欢那些说不可能的答案,但似乎就是这样。 所以我建议你回到最初的问题,这是SIP的长时间超时。

  • 您可以让用户配置超时(因此符合规范)。
  • 您可以在超时结束前开始执行其他操作(例如检查其他代理)。
  • 您可以缓存已知的错误目标(但这需要良好的缓存管理。
  • 如果icmp和udp没有给出正确的错误消息,请尝试使用tcp或其他协议。 只是为了获得所需的信息。

(一切皆有可能,它可能需要很多资源。)

我把它写成一个单独的答案,因为细节与我之前写的完全不同。

因此,基于Kalmi关于会话ID的评论,它让我思考为什么我可以在同一台机器上打开两个ping程序,并且响应不会交叉。 它们都是ICMP,因此都使用无端口原始套接字。 这意味着IP堆栈中的某些内容,必须知道这些响应的目的是什么。 对于ping,事实certificate在ICMP包的数据中使用了一个ID作为ECHO REQUEST和ECHO REPLY的一部分。

然后我在维基百科上发表了关于ICMP的评论:

虽然ICMP消息包含在标准IP数据报中,但ICMP消息通常作为特殊情况处理,区别于普通IP处理,而不是作为IP的正常子协议处理。 在许多情况下,有必要检查ICMP消息的内容并将相应的错误消息传递给生成原始IP数据包的应用程序,即提示发送ICMP消息的IP数据包。

这是(间接) 在这里阐述的:

互联网标头加上原始数据报数据的前64位。 主机使用此数据将消息与适当的进程进行匹配。 如果更高级别的协议使用端口号,则假定它们位于原始数据报数据的前64个数据位中。

由于您使用的是使用端口的UDP,因此网络堆栈可能会将ICMP消息路由回原始套接字。 这就是为什么你的新的,独立的套接字永远不会收到这些消息。 我想UDP会吃掉ICMP消息。

如果我是正确的,一个解决方案是打开一个原始套接字并手动创建UDP数据包,监听返回的任何内容,并根据需要处理UDP和ICMP消息。 我不确定代码中会出现什么样的情况,但我认为它不会太难,并且可能被认为比winpcap解决方案更“优雅”。

此外,此链接http://www.networksorcery.com/enp/default1003.htm似乎是低级网络协议的绝佳资源。

我希望这有帮助。

所以你想以编程方式获取目标无法访问的返回icmp数据包? 一个艰难的。 我会说网络堆栈会在你可以到达附近之前将其吸收。

我认为纯粹的C#方法不会在这里起作用。 你需要使用一个驱动程序级别的拦截来获取一个钩子。看看这个使用windows的ipfiltdrv.sys来捕获数据包(icmp,tcp,udp等)并使用托管代码读取/播放它的应用程序( C#)。

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

  • [287]莪