【问题标题】:Listen for ICMP packets in C#在 C# 中侦听 ICMP 数据包
【发布时间】:2009-03-09 14:51:46
【问题描述】:

我有一个 SIP 应用程序,它需要发送 UDP 数据包来设置 SIP 呼叫。 SIP 有一个超时机制来处理交付失败。我想做的另一件事是检测 UDP 套接字是否已关闭,以便必须等待 SIP 使用的 32 秒重传间隔。

我所指的情况是尝试发送到 UDP 套接字导致远程主机生成 ICMP Destination Unreachable 数据包。如果我尝试向启动但端口未侦听的主机发送 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,从我知道它没有侦听的端口上尝试从 10.0.0.100(我的 PC)到 10.0.0.138(我的路由器)发送 UDP 数据包。我的问题是如何利用这些 ICMP 数据包来实现 UDP 发送失败,而不是等待应用程序在任意时间后超时?

【问题讨论】:

    标签: c# icmp


    【解决方案1】:

    将近 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 端口不可达数据包。

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

    【讨论】:

    • 我不知道它是否有帮助。但是使用我一直在使用 Socket.BeginReceiveFrom 时,它会因 SocketError.ConnectionReset (10054) 的 SocketException 而失败。在 UDP 协议下,这是套接字收到 ICMP Port Unreachable 消息的通知。
    • 无论您如何从套接字接收,都会引发相同的异常。我一直遇到的问题是异常没有告诉您 ICMP 响应来自哪个远程主机。这就是 ICMP 侦听器可以做的事情。
    • 迟 +1 表示仍然关心 3 年后提供解决方案!
    【解决方案2】:

    更新:我想我快疯了......你发布的那段代码也对我有用......

    以下代码对我来说很好(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");
                    }
                }
    
            }
        }
    }
    

    【讨论】:

    • 当您说工作时,您的意思是如果您发送 ping 或其他东西,您正在接收 ICMP 数据包?我很确定这就是我在将 UDP 发送到无法到达的端口并且无法接收 ICMP 时使用的。我得再检查一次。
    • 我只测试了无法访问的主机和tcp...我会尝试端口
    • 是的...适用于 udp 和现有主机...我添加了注释掉的部分以进行测试..
    • 我也可以确认这一点。我使用的代码和 Kalmi 的示例适用于 XP 和 2k3,但不适用于 Vista。我的应用适用于 2k3,所以这是个好消息!
    【解决方案3】:

    Icmp 使用的 标识符 对于每个 icmp “会话”(对于每个 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 做同样的事情应该不会太难。)

    【讨论】:

    • 我同意。如果没有其他可用的,这是一个很好的解决方案。我只是忍不住想还有另一种方法。当收到 ICMP 消息时,UDP 发送确实应该抛出异常。
    • UDP 是无状态的,所以我认为它不应该。
    • 支持以确保正确的人获得赏金,而不是这篇帖子不利于它。
    【解决方案4】:

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

    Google 连接的 udp 套接字。

    【讨论】:

    • UDP 客户端将为 ICMP 端口不可达但不会为 ICMP 主机不可达或任何其他 ICMP“不可达”消息抛出套接字异常
    【解决方案5】:

    所以您想以编程方式获取 dest 无法到达的返回 icmp 数据包?一个艰难的。我会说网络堆栈在你靠近它之前就把它吸收了。

    我认为纯 C# 方法在这里行不通。您需要使用驱动程序级别拦截来获取挂钩。看看这个使用 Windows 的 ipfiltdrv.sys 捕获数据包(icmp、tcp、udp 等)并使用托管代码读取/播放它们的应用程序( c#)。

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

    • 欧辛

    【讨论】:

      【解决方案6】:

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

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

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

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

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

      【讨论】:

        【解决方案7】:

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

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

        然后我在维基百科上看到了这条关于ICMP的评论:

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

        详细说明(间接)here

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

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

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

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

        我希望这会有所帮助。

        【讨论】:

        • 您关于 ICMP 会话 ID 的观点对我来说非常有意义。这实际上是问题的症结所在,为什么 ICMP 消息没有指示无法传递的 UDP 数据包传递到我的应用程序?由于某种原因,Windows 似乎无法将它们与应用程序匹配,因为它们用于 UDP 数据包。
        • 我确实考虑过尝试在同一个原始套接字上多路复用 UDP 和 ICMP,但您可能需要进行所有 UDP 处理,但除此之外,当您使用 Windows 创建原始套接字时,您必须选择是IP还是ICMP,不能同时拥有。
        • 是的,我认为您必须将其设为原始 IP 套接字,并处理所有 ICMP 消息。这可能比它的价值更复杂,但从我能读到的关于 IP 和 ICMP 的内容来看,它可能是唯一非 winpcap 风格的答案。
        • 澄清一下:我认为您的应用程序正在获取 ICMP 消息,但 UDP 堆栈正在抑制它。当然这是猜测,因为我没有看到您如何为原始 UDP 消息创建和使用套接字。
        猜你喜欢
        • 2012-12-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-08-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多