在C#中侦听ICMP数据包

时间:2009-03-09 14:51:47

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

ICMP responses to UDP send

7 个答案:

答案 0 :(得分:13)

差不多3年后,我偶然发现了http://www.codeproject.com/Articles/17031/A-Network-Sniffer-in-C,这给了我足够的暗示,帮助我找到在Windows 7上接收ICMP数据包的解决方案(不知道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

答案 1 :(得分:3)

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做同样的事情也不应该太难。)

答案 2 :(得分:3)

更新:我想我已经疯了......你发布的那段代码也适合我......

以下代码对我来说很好(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 :(得分:3)

只需使用连接的udp套接字,操作系统将匹配icmp unreachable,并在udp套接字中返回错误。

Google用于连接udp套接字。

答案 4 :(得分:2)

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

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

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

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

(一切皆有可能,它可能需要大量资源。)

答案 5 :(得分:1)

我将此作为一个单独的答案,因为细节与我之前写的完全不同。

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

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

  

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

详细阐述了(间接)here

  

互联网标题加上前64位   原始数据报数据的位。   主机使用此数据进行匹配   给适当的消息   处理。如果是更高级别的协议   使用端口号,它们被假定为   在第一个64位数据位   原始数据报的数据。

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

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

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

我希望这会有所帮助。

答案 6 :(得分:1)

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

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

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

  • [287]莪