C# 监听 ICMP 数据包
我有一个 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 发送失败,而不是仅仅等待应用程序在任意时间段后超时?
I have a SIP application that needs to send UDP packets to set up the SIP calls. SIP has a timeout mechanism to cope with delivery failures. An additional thing I would like to be able to do is detect whether a UDP socket is closed in order having to wait the 32s retransmit interval SIP uses.
The cases I am referring to are when an attempt to send to a UDP socket results in an ICMP Destination Unreachable packet being generated by the remote host. If I attempt to send a UDP packet to a host that's up but that the port is not listening I can see the ICMP message arriving back with a packet tracer but the question is how do I get access to that from my C# code?
I'm playing around with raw sockets but as yet have not been able to get the ICMP packets to be received by my program. The sample below never receives a packet even though ICMP messages are arriving on my 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());
Below is a wireshark trace showing the ICMP responses coming into my PC from an attempt to send a UDP packet from 10.0.0.100 (my PC) to 10.0.0.138 (my router) on a port I know it's not listening on. My problem is how to make use of those ICMP packets to realise the UDP send has failed rather than just waiting for the application to timeout after an arbitrary period?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
近 3 年后,我偶然发现了 http://www.codeproject.com/Articles/ 17031/A-Network-Sniffer-in-C 这给了我足够的提示,帮助我找到在 Windows 7 上接收 ICMP 数据包的解决方案(不知道 Vista,最初的问题是关于 Vista 的,但是我怀疑这个解决方案会起作用)。
两个关键点是套接字必须绑定到单个特定 IP 地址而不是 IPAddress.Any 以及设置 SIO_RCVALL 标志的 IOControl 调用。
我还必须设置防火墙规则以允许接收 ICMP 端口不可达数据包。
Nearly 3 years later and I stumbled across http://www.codeproject.com/Articles/17031/A-Network-Sniffer-in-C which gave me enough of a hint to help me find a solution to receiving ICMP packets on Windows 7 (don't know about Vista, which the original question was about but I suspect this solution would work).
The two key points are that the socket has to be bound to a single specific IP address rather than IPAddress.Any and the IOControl call which sets the SIO_RCVALL flag.
I also had to set a firewall rule to allow ICMP Port Unreachable packets to be received.
更新:我想我要疯了......你发布的那段代码也对我有用......
下面的代码对我来说工作得很好(xp sp3):
UPDATE: I think I'm going crazy.... That piece of code that you posted is also working for me...
The following piece of code works fine for me(xp sp3):
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 is using an identifier which seems to be different for every icmp "session" (for every icmp socket). So the reply to an icmp packet not sent by the same socket is helpfully filtered out for you. This is why that piece of code won't work. (I'm not sure about this. It's just an assumption after looking at some ICMP traffic.)
You could simply ping the host and see whether you can reach it or not and then try your SIP thing. However that won't work if the other host is filtering out icmp.
An ugly (but working) solution is using winpcap. (Having this as the only working solutions just seems to be too bad to be true.)
What I mean by using winpcap is the you could capture ICMP traffic and then see if the captured packet is about your UDP packet being undeliverable or not.
Here is an example for capturing tcp packets:
http://www.tamirgal.com/home/SourceView.aspx?Item=SharpPcap&File=Example6.DumpTCP.cs
(It shouldn't be too hard to do the same with ICMP.)
只需使用已连接的 udp 套接字,操作系统就会匹配 icmp 不可达并在 udp 套接字中返回错误。
Google 连接的 udp 套接字。
Just use connected udp sockets and the OS will match the icmp unreachable and return an error in the udp socket.
Google for connected udp sockets.
那么您想以编程方式获取目标无法到达的返回 icmp 数据包吗? 一个艰难的。 我想说网络堆栈在你接近它之前就吸收了它。
我认为纯 C# 方法在这里不起作用。 您需要使用驱动程序级别拦截来获取挂钩。看看这个应用程序,它使用 Windows 的 ipfiltdrv.sys 来捕获数据包(icmp、tcp、udp 等)并使用托管代码读取/播放它们( C#)。
http://www.codeproject.com/KB/IP/firewall_sniffer。 aspx?display=Print
So you want to pick up the dest unreachable return icmp packet programmatically? A tough one. I'd say the network stack soaks that up before you can get anywhere near it.
I don't think a pure C# approach will work here. You'll need to use a driver level intercept to get a hook in. Take a look at this app that uses windows' ipfiltdrv.sys to trap packets (icmp,tcp,udp etc) and read/play with them with managed code (c#).
http://www.codeproject.com/KB/IP/firewall_sniffer.aspx?display=Print
网络上有很多帖子提到在 Vista 上无法再访问 ICMP 端口不可达数据包的问题。
堆栈当它收到 ICMP 时应该给你一个异常。 但事实并非如此,至少在 Vista 上是这样。 因此你正在尝试一种解决方法。
我不喜欢说这是不可能的答案,但看起来就是这样。 所以我建议你回到最初的问题,即 SIP 中的长时间超时。
超时(因此有点遵守
规格)。
超时结束。
良好的缓存管理。
(一切皆有可能,只是可能需要大量资源。)
There are a number of posts on the web mentioning the problem of ICMP Port Unreachable packets no longer being accessible on Vista.
The stack should give you back an exception when it receives the ICMP. But it doesn't, at least on Vista. And hence you are trying a workaround.
I don't like answers that say it's not possible, but it seems that way. So I suggest you go back a step to the original problem, which was long timeouts in SIP.
timeout (hence sort of complying with
the spec).
the timeout ends.
good management of the cache.
(Anything is possible, it just may take a lot of resources.)
我将其作为一个单独的答案来写,因为细节与我之前写的完全不同。
因此,根据 Kalmi 关于会话 ID 的评论,它让我思考为什么我可以在同一台机器上打开两个 ping 程序,并且响应不会交叉。 它们都是 ICMP,因此都使用无端口原始套接字。 这意味着 IP 堆栈中的某些内容必须知道这些响应的目的是哪个套接字。 对于 ping,结果表明 ICMP 数据包的数据中使用了一个 ID,作为 ECHO REQUEST 和 ECHO REPLY 的一部分。
然后我在维基百科上看到了关于 ICMP 的评论:
此处对此进行了(间接)详细说明:
由于您使用的是使用端口的 UDP,因此网络堆栈可能会将 ICMP 消息路由回原始套接字。 这就是为什么您的新的独立套接字永远不会接收这些消息。 我想 UDP 会吃掉 ICMP 消息。
如果我是正确的,解决此问题的一种方法是打开原始套接字并手动创建 UDP 数据包,侦听返回的任何内容,并根据需要处理 UDP 和 ICMP 消息。 我不确定代码中会是什么样子,但我不认为这会太困难,并且可能被认为比 winpcap 解决方案更“优雅”。
另外此链接,http://www.networksorcery.com/enp/default1003.htm,似乎是低级网络协议的重要资源。
我希望这有帮助。
I am writing this as a separate answer, since the details are completely different from the one I wrote earlier.
So based on the comment from Kalmi about the session ID, it got me to thinking about why I can open up two ping programs on the same machine, and the responses don't cross over. They are both ICMP, therefore both using port-less raw sockets. That means something in the IP stack, has to know what socket those responses were intended for. For ping it turns out there is an ID used in the data of the ICMP package as part of ECHO REQUEST and ECHO REPLY.
Then I ran across this comment on wikipedia about ICMP:
Which was elaborated on (indirectly) here:
Since you are using UDP, which uses ports, it is possible the network stack is routing the ICMP message back to the original socket. This is why your new, and separate, socket is never receiving those messages. I imagine UDP eats the ICMP message.
If I am correct, one solution to this is to open a raw socket and manually create your UDP packets, listen for the anything coming back, and handle UDP and ICMP messages as appropriate. I am not sure what that would look like in code, but I don't imagine it would be too difficult, and may be considered more "elegant" than the winpcap solution.
Additionally this link, http://www.networksorcery.com/enp/default1003.htm, appears to be a great resource for low level network protocols.
I hope this helps.