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

ICMP responses to UDP send

有帮助吗?

解决方案

将近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

其他提示

ICMP 正在使用 标识符 这对于每个 icmp“会话”(对于每个 icmp 套接字)似乎都是不同的。所以 回复非同一套接字发送的 icmp 数据包 是有帮助的 过滤掉 为你。这就是为什么那段代码不起作用的原因。 (我对此不太确定。这只是查看一些 ICMP 流量后的假设。)

您可以简单地 ping 主机并查看是否可以访问它,然后尝试您的 SIP 操作。但是,如果其他主机过滤掉 icmp,则这将不起作用。

一个丑陋(但有效)的解决方案正在使用 温普卡. (将此作为唯一有效的解决方案似乎太糟糕了,令人难以置信。)

我所说的使用 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套接字。

网络上有很多帖子提到在 Vista 上无法再访问 ICMP 端口不可达数据包的问题。

当堆栈收到 ICMP 时,它应该返回一个异常。但事实并非如此,至少在 Vista 上是这样。因此你正在尝试一种解决方法。

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

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

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

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

因此,基于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 ,似乎是低级网络协议的绝佳资源。

我希望这会有所帮助。

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

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

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

  • 奥辛
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top