我有一个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发送失败而不是等待应用程序在任意时间段后超时?
答案 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的长时间超时。
(一切皆有可能,它可能需要大量资源。)
答案 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