Skip to content

Linux UDP Socket Poll with SelectRead not returning true when ICMP port unreachable available #8637

Open
@wasabii

Description

@wasabii

Description

This might be some underlying platform thing that I'm not understanding. This also requires a little bit of understanding how UDP sockets are supposed to work.

When a UDP packet is sent, the remote host has the possibility of responding with a ICMP port unreachable. No guarantee it will, of course, but the possibility exists. If such a packet has been received in response to a UDP send, it ends up with the next receive socket operation returning an error of some kind. On Windows this is generally ECONNRESET. On Linux this is generally ECONNREFUSED.

.NET sockets, on Windows, dutifully report theses errors up. So, to determine whether you can early-exit an attempt to send a UDP connection, you can trap the SocketException, check for either ConnectionReset. Another thing you can do is use Socket.Poll to wait for a timeout for either new data or a port refusal. So, generally, the following code works in a receive method.

if (socket.Poll(timeout, SelectMode.SelectRead) == false)
    throw new global::java.net.SocketTimeoutException("Receive timed out.");

length = socket.ReceiveFrom(packet.buf, packet.offset, packet.bufLength, SocketFlags.None, ref remoteEndpoint);

In the case of a ICMP unreachable, Poll returns immediately with true, and then the subsequent ReceiveFrom throws.

This however doesn't seem to be working for me on Linux.

What's happening on Linux is that Poll returns immediately with FALSE. No timeout wait. But no way to know that there was no timeout. If I just brute force through:

socket.Poll(timeout, SelectMode.SelectRead)
length = socket.ReceiveFrom(packet.buf, packet.offset, packet.bufLength, SocketFlags.None, ref remoteEndpoint);

Then i properly get ConnectionRefused on ReceiveFrom, with Poll exiting immediately.

Poll DOES wait if no ICMP port unreachable was sent.

It looks like the socket is aware of the ICMP unreachable: it exits immediately, not waiting for the timeout. And it's capable of throwing the exception on the subsequent ReceiveFrom. But the return value of Poll is wrong.

I am trying this on .NET 6.

Reproduction Steps

        public static void Main(string[] args)
        {
            // create a new socket to be a client
            var sock = new Socket(SocketType.Dgram, ProtocolType.Udp);
            var addr = IPAddress.Loopback;
            sock.Connect(addr, 7441);
            sock.Send(new byte[] { 0x00 });

            // should return true
            var r = sock.Poll(1000000, SelectMode.SelectRead);
            if (r == false)
                throw new Exception();

            var b = new byte[1024];
            sock.Receive(b, SocketFlags.None);
        }

This code should exit by throwing the Connection Refused exception, but does not. It fails with the generic exception.

Expected behavior

Poll should return true if it exists because of a waiting ICMP packet.

Actual behavior

It returns false.

Regression?

Unknown. It's isolated to Linux on Core.

Known Workarounds

No response

Configuration

No response

Other information

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    Pri3Indicates issues/PRs that are low priorityarea-System.Net.SocketsuntriagedNew issue has not been triaged by the area owner

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions