Listen for ICMP packets in C#
Asked Answered
U

7

9

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?

ICMP responses to UDP send

Ugo answered 9/3, 2009 at 14:51 Comment(0)
U
23

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.

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.

netsh advfirewall firewall add rule name="All ICMP v4" dir=in action=allow protocol=icmpv4:any,any
Ugo answered 7/2, 2012 at 10:13 Comment(4)
I don't know if it helps or not. But using I have been using Socket.BeginReceiveFrom and from time to time it will fail with a SocketException of SocketError.ConnectionReset (10054). Under UDP protocol this a notification that the socket received an ICMP Port Unreachable message.Overthrow
That same exception is thrown no matter how you receive from the socket. The problem I always had was that the exception didn't tell you which remote host the ICMP response came from. That's what the ICMP listener can do.Ugo
Late +1 for still caring to provide a solution after 3 years!Surtout
Try as I might, I haven't been able to get this to work on Win10 (many years later). The ICMP packets are never returned to the socket and are likely being filtered out. Haven't found a working solution after a full day of trying.Farquhar
M
4

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):

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");
                }
            }

        }
    }
}
Maryrosemarys answered 20/3, 2009 at 2:0 Comment(4)
When you say works do you mean you are receiving ICMP packets if you send a ping or something? I'm pretty sure that's what I was using when I was doing UDP sends to unreachbale ports and wasn't able to receive the ICMP. I'll have to check again.Ugo
I only tested with unreachable hosts and tcp... I will try portsLyonnaise
yep... works fine with udp and existing hosts... I added that commented out part for testing..Lyonnaise
I can confirm this as well. The code I was using and Kalmi's sample work on XP and 2k3 but not on Vista. My app is destined for 2k3 so that's good news!Ugo
M
3

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.)

Maryrosemarys answered 15/3, 2009 at 7:20 Comment(3)
I agree. If nothing else is available that's a good solution. I just can't help thinking there is another way. Really the UDP Send should be throwing an exception when the ICMP message is received.Ugo
UDP is stateless, so I don't think it should.Lyonnaise
Upvoting to make sure the right person gets the bounty, not that this post diserves it.Preposterous
R
3

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.

Remonstrant answered 8/8, 2011 at 15:40 Comment(1)
UDP client will throw an socket exception for a ICMP Port Unreachable but not for a ICMP Host Unreachable or in fact for any other ICMP "unreachable" messageSupposed
P
2

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

  • Oisin
Phytopathology answered 18/3, 2009 at 17:14 Comment(0)
L
2

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.

  • 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.

(Anything is possible, it just may take a lot of resources.)

Landlordism answered 20/3, 2009 at 9:25 Comment(0)
L
1

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:

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.

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.

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.

Lasley answered 18/3, 2009 at 16:53 Comment(4)
Your point about ICMP session id's makes perfect sense to me. That's actually the crux of the problem, why aren't the ICMP messages indicating the non-deliverable UDP packets delivered to my appliation? For some reason Windows doesn't seem to match them to the app becuase they were for a UDP packet.Ugo
I did think about trying to multi-plex UDP and ICMP over the same raw socket but you'd probably need to do all the UDP processing but apart from that when you create a raw socket with Windows you have to select whether it's IP or ICMP, you can't have both.Ugo
Yes I think you would have to make it a raw IP socket, and handle all the ICMP messages. This may be more complex than it is worth, but from what I was able to read about IP and ICMP it may be the only non-winpcap style answer.Lasley
To clarify: I think your application is getting the ICMP message, but the UDP stack is suppressing it. OF course this is guesswork, since I didn't see how you created and used the socket for the original UDP message.Lasley

© 2022 - 2024 — McMap. All rights reserved.