8

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

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

ICMP 对 UDP 的响应发送

4

7 回答 7

18

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

netsh advfirewall firewall add rule name="All ICMP v4" dir=in action=allow protocol=icmpv4:any,any
于 2012-02-07T10:13:11.300 回答
4

更新:我想我快疯了......你发布的那段代码也为我工作......

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

        }
    }
}
于 2009-03-20T02:00:57.910 回答
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 做同样的事情应该不会太难。)

于 2009-03-15T07:20:55.357 回答
3

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

谷歌连接的 udp 套接字。

于 2011-08-08T15:40:14.167 回答
2

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

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

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

  • 奥辛
于 2009-03-18T17:14:19.750 回答
2

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

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

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

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

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

于 2009-03-20T09:25:19.353 回答
1

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

因此,根据 Kalmi 关于会话 ID 的评论,我开始思考为什么我可以在同一台机器上打开两个 ping 程序,并且响应不会交叉。它们都是 ICMP,因此都使用无端口原始套接字。这意味着 IP 堆栈中的某些东西必须知道这些响应的目的是什么套接字。对于 ping,事实证明在 ICMP 包的数据中使用了一个 ID,作为 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似乎是低级网络协议的绝佳资源。

我希望这有帮助。

于 2009-03-18T16:53:45.247 回答