在 C# 中侦听 ICMP 数据包
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/626541/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Listen for ICMP packets in C#
提问by sipwiz
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.
我有一个 SIP 应用程序,需要发送 UDP 数据包来设置 SIP 呼叫。SIP 具有超时机制来应对传送失败。我希望能够做的另一件事是检测 UDP 套接字是否已关闭,以便必须等待 SIP 使用的 32 秒重传间隔。
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?
我所指的情况是当尝试发送到 UDP 套接字导致远程主机生成 ICMP Destination Unreachable 数据包时。如果我尝试将 UDP 数据包发送到已启动但端口未侦听的主机,我可以看到 ICMP 消息通过数据包跟踪器返回,但问题是如何从我的 C# 代码访问它?
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.
我正在使用原始套接字,但还没有能够让我的程序接收 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());
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?
下面是一个wireshark 跟踪,显示了进入我的 PC 的 ICMP 响应,因为它尝试在我知道它没有侦听的端口上将 UDP 数据包从 10.0.0.100(我的 PC)发送到 10.0.0.138(我的路由器)。我的问题是如何利用这些 ICMP 数据包来实现 UDP 发送失败,而不仅仅是等待应用程序在任意时间段后超时?
采纳答案by sipwiz
Nearly 3 years later and I stumbled across http://www.codeproject.com/Articles/17031/A-Network-Sniffer-in-Cwhich 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).
将近 3 年后,我偶然发现了http://www.codeproject.com/Articles/17031/A-Network-Sniffer-in-C,它给了我足够的提示,帮助我找到在 Windows 上接收 ICMP 数据包的解决方案7(不知道Vista,最初的问题是关于它的,但我怀疑这个解决方案会起作用)。
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.
两个关键点是套接字必须绑定到单个特定 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();
I also had to set a firewall rule to allow ICMP Port Unreachable packets to be received.
我还必须设置防火墙规则以允许接收 ICMP Port Unreachable 数据包。
netsh advfirewall firewall add rule name="All ICMP v4" dir=in action=allow protocol=icmpv4:any,any
回答by Tarnay Kálmán
Icmp is using an identifierwhich seems to be different for every icmp "session" (for every icmp socket). So the reply to an icmp packet not sent by the same socketis helpfully filtered outfor 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.)
icmp使用的标识符对于每个 icmp“会话”(对于每个 icmp 套接字)似乎都不同。所以,并非由同一个套接字发送一个ICMP数据包的答复是有益过滤掉你。这就是为什么那段代码不起作用的原因。(我不确定这一点。这只是在查看了一些 ICMP 流量后的假设。)
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.
你可以简单地 ping 主机,看看你是否可以访问它,然后尝试你的 SIP 事情。但是,如果其他主机过滤掉 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.)
一个丑陋(但有效)的解决方案是使用winpcap。(将此作为唯一可行的解决方案似乎太糟糕了。)
What I mean by using winpcap is the you could capture ICMP trafficand then see if the captured packet is about your UDP packet being undeliverable or not.
我使用 winpcap 的意思是您可以捕获 ICMP 流量,然后查看捕获的数据包是否与您的 UDP 数据包无法传递有关。
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.)
下面是一个捕获 tcp 数据包的例子:http://www.tamirgal.com/home/SourceView.aspx?Item=SharpPcap&File= Example6.DumpTCP.cs(用 ICMP 做同样的事情应该不会太难。)
回答by grieve
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.
因此,基于 Kalmi 关于会话 ID 的评论,它让我思考为什么我可以在同一台机器上打开两个 ping 程序,并且响应不会交叉。它们都是 ICMP,因此都使用无端口的原始套接字。这意味着 IP 堆栈中的某些内容必须知道这些响应用于哪个套接字。对于 ping,结果证明在 ICMP 包的数据中使用了一个 ID,作为 ECHO REQUEST 和 ECHO REPLY 的一部分。
Then I ran across this comment on wikipedia about ICMP:
然后我在维基百科上看到了关于ICMP 的评论:
Although ICMP messages are contained within standard IP datagrams, ICMP messages are usually processed as a special case, distinguished from normal IP processing, rather than processed as a normal sub-protocol of IP. In many cases, it is necessary to inspect the contents of the ICMP message and deliver the appropriate error message to the application that generated the original IP packet, the one that prompted the sending of the ICMP message.
尽管 ICMP 消息包含在标准 IP 数据报中,但 ICMP 消息通常作为一种特殊情况进行处理,区别于正常的 IP 处理,而不是作为 IP 的正常子协议进行处理。在许多情况下,有必要检查 ICMP 消息的内容并将适当的错误消息传递给生成原始 IP 数据包的应用程序,该应用程序提示发送 ICMP 消息。
Which was elaborated on (indirectly) here:
The internet header plus the first 64 bits of the original datagram's data. This data is used by the host to match the message to the appropriate process. If a higher level protocol uses port numbers, they are assumed to be in the first 64 data bits of the original datagram's data.
Internet 报头加上原始数据报数据的前 64 位。主机使用此数据将消息与适当的进程匹配。如果更高级别的协议使用端口号,则假定它们位于原始数据报数据的前 64 个数据位中。
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.
由于您使用的是使用端口的 UDP,因此网络堆栈可能会将 ICMP 消息路由回原始套接字。这就是为什么您的新的、独立的套接字永远不会收到这些消息的原因。我想 UDP 会吃掉 ICMP 消息。
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.
如果我是对的,一个解决方案是打开一个原始套接字并手动创建您的 UDP 数据包,监听返回的任何内容,并根据需要处理 UDP 和 ICMP 消息。我不确定这在代码中会是什么样子,但我认为它不会太难,并且可能被认为比 winpcap 解决方案更“优雅”。
Additionally this link, http://www.networksorcery.com/enp/default1003.htm, appears to be a great resource for low level network protocols.
此外,此链接http://www.networksorcery.com/enp/default1003.htm似乎是低级网络协议的重要资源。
I hope this helps.
我希望这有帮助。
回答by x0n
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.
那么您想以编程方式获取 dest 不可达的返回 icmp 数据包吗?一个艰难的。我会说网络堆栈在你接近它之前吸收了它。
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#).
我认为纯 C# 方法在这里行不通。您需要使用驱动程序级拦截来获取钩子。看看这个应用程序,它使用 Windows 的 ipfiltdrv.sys 来捕获数据包(icmp、tcp、udp 等)并使用托管代码读取/播放它们( C#)。
http://www.codeproject.com/KB/IP/firewall_sniffer.aspx?display=Print
http://www.codeproject.com/KB/IP/firewall_sniffer.aspx?display=Print
- Oisin
- 奥辛
回答by Tarnay Kálmán
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):
以下代码对我来说很好用(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");
}
}
}
}
}
回答by Conor OG
There are a number of posts on the web mentioning the problem of ICMP Port Unreachable packets no longer being accessible on Vista.
网上有很多帖子提到了在 Vista 上无法再访问 ICMP Port Unreachable 数据包的问题。
- http://www.eggheadcafe.com/software/aspnet/31961998/icmp-port-unreachable-and.aspx
- http://social.msdn.microsoft.com/Forums/en-US/Offtopic/thread/5bd8b275-cc6f-43cd-949d-7c411973b2f3/
- http://www.eggheadcafe.com/software/aspnet/31961998/icmp-port-unreachable-and.aspx
- http://social.msdn.microsoft.com/Forums/en-US/Offtopic/thread/5bd8b275-cc6f-43cd-949d-7c411973b2f3/
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.
堆栈在收到 ICMP 时应该会返回一个异常。但事实并非如此,至少在 Vista 上是这样。因此,您正在尝试一种解决方法。
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.
我不喜欢说不可能的答案,但似乎是这样。所以我建议你回到最初的问题,即 SIP 中的长时间超时。
- You could let the user configure the timeout (hence sort of complying with the spec).
- You can start doing other things (like checking other proxies) before the timeout ends.
- You could cache known bad destinations (but that would need good management of the cache.
- If icmp, and udp don't give proper error messages, try tcp or another protocol. Just to elicit the desired information.
- 您可以让用户配置超时(因此符合规范)。
- 您可以在超时结束之前开始做其他事情(例如检查其他代理)。
- 您可以缓存已知的错误目的地(但这需要对缓存进行良好的管理。
- 如果 icmp 和 udp 没有给出正确的错误消息,请尝试使用 tcp 或其他协议。只是为了引出所需的信息。
(Anything is possible, it just may take a lot of resources.)
(一切皆有可能,只是可能需要大量资源。)
回答by haesbaert
Just use connected udp sockets and the OS will match the icmp unreachable and return an error in the udp socket.
只需使用连接的 udp 套接字,操作系统将匹配 icmp 不可达并在 udp 套接字中返回错误。
Google for connected udp sockets.
谷歌连接的 udp 套接字。