Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GetSocketOption/SetSocketOption should not close underlying socket #59055

Closed
Dweeberly opened this issue Sep 13, 2021 · 5 comments · Fixed by #59925
Closed

GetSocketOption/SetSocketOption should not close underlying socket #59055

Dweeberly opened this issue Sep 13, 2021 · 5 comments · Fixed by #59925
Labels
area-System.Net.Sockets enhancement Product code improvement that does NOT require public API changes/additions in-pr There is an active PR which will close this issue when it is merged tenet-compatibility Incompatibility with previous versions or .NET Framework
Milestone

Comments

@Dweeberly
Copy link

Dweeberly commented Sep 13, 2021

Description

Assume that _socket is an open (active) TCP socket. The following call will throw an exception and close the socket

Socket _socket;
_socket.NoDelay = true;
_socket.Blocking = true;
_socket.ReceiveTimeout = -1;
_socket.SendTimeout = -1;
...
IPAddress ipaddr = <setup your server ip>;
IPEndPoint svrEndPoint = new IPEndPoint(ipaddr, _serverPort);
var listener = new Socket(ipaddr.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
listener.Bind(svrEndPoint);
listener.Listen(2);
_socket = listener.Accept();
_socket.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontLinger)

This option (DontLinger) isn't supported on Linux so the exception is appropriate, but closing the socket isn't. To quote a friend this "violates the principle of least astonishment".

Configuration

Version: .NET core 5.0
OS: Linux Ubuntu 20.04.lts
Arch: x64
Configuration: The option isn't supported on this Linux distro but is on Windows. My code caught the exception but did not expect the socket to be closed.

Regression?

I do not believe this is a regression.

Other information

Here is what I see in the codebase.
in /src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs
The GetSocketOption methods call into the OS:

...
SocketError errorCode = SocketPal.GetSockOpt(
    _handle,
    optionLevel,
    optionName,
    out optionValue);
...
// Throw an appropriate SocketException if the native call fails.
if (errorCode != SocketError.Success)
{
    UpdateStatusAfterSocketErrorAndThrowException(errorCode);
}

So if the call returns a not supported error, UpdateStatusAfterSocketErrorAndThrowExecption is called.
That calls an internal method named UpdateStatusAfterSocketError which:

if (_isConnected && (_handle.IsInvalid || (errorCode != SocketError.WouldBlock &&
        errorCode != SocketError.IOPending && errorCode != SocketError.NoBufferSpaceAvailable &&
        errorCode != SocketError.TimedOut)))
{
    // The socket is no longer a valid socket.
    if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, "Invalidating socket.");
    SetToDisconnected();
}

Honestly, that looks correct to me, but perhaps the errorCode isn't what I would be expecting for an unsupported option name, or the handled has somehow (?) become invalid. I wasn't able to look at this in the debugger, so I can't say what the conditions were or if this is even the right spot (but I don't see anywhere else)

A few nights ago I was down in some mono code and that clearly would have created this effect. It check the options first and if they failed an exception was thrown (never calling the OS). I assume that code is no longer used in the v5 .net runtime.

Workaround is the check the Linger option.

@dotnet-issue-labeler dotnet-issue-labeler bot added area-System.Net.Sockets untriaged New issue has not been triaged by the area owner labels Sep 13, 2021
@ghost
Copy link

ghost commented Sep 13, 2021

Tagging subscribers to this area: @dotnet/ncl
See info in area-owners.md if you want to be subscribed.

Issue Details

Description

Assume that _socket is an open (active) TCP socket. The following call will throw an exception and close the socket

Socket _socket;
_socket.NoDelay = true;
_socket.Blocking = true;
_socket.ReceiveTimeout = -1;
_socket.SendTimeout = -1;
...
IPAddress ipaddr = <setup your server ip>;
IPEndPoint svrEndPoint = new IPEndPoint(ipaddr, _serverPort);
var listener = new Socket(ipaddr.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
listener.Bind(svrEndPoint);
listener.Listen(2);
_socket = listener.Accept();
_socket.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontLinger)

This option (DontLinger) isn't supported on Linux so the exception is appropriate, but closing the socket isn't. To quote a friend this "violates the principle of least astonishment".

Configuration

Version: .NET core 5.0
OS: Linux Ubuntu 20.04.lts
Arch: x64
Configuration: The option isn't supported on this Linux distro but is on Windows. My code caught the exception but did not expect the socket to be closed.

Regression?

I do not believe this is a regression.

Other information

Here is what I see in the codebase.
in /src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.cs
The GetSocketOption methods call into the OS:

...
            SocketError errorCode = SocketPal.GetSockOpt(
                _handle,
                optionLevel,
                optionName,
                out optionValue);
...
            // Throw an appropriate SocketException if the native call fails.
            if (errorCode != SocketError.Success)
            {
                UpdateStatusAfterSocketErrorAndThrowException(errorCode);
            }

So if the call returns a not supported error, UpdateStatusAfterSocketErrorAndThrowExecption is called.
That calls an internal method named UpdateStatusAfterSocketError which:

            if (_isConnected && (_handle.IsInvalid || (errorCode != SocketError.WouldBlock &&
                    errorCode != SocketError.IOPending && errorCode != SocketError.NoBufferSpaceAvailable &&
                    errorCode != SocketError.TimedOut)))
            {
                // The socket is no longer a valid socket.
                if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, "Invalidating socket.");
                SetToDisconnected();
            }

Honestly, that looks correct to me, but perhaps the errorCode isn't what I would be expecting for an unsupported option name, or the handled has somehow (?) become invalid. I wasn't able to look at this in the debugger, so I can't say what the conditions were or if this is even the right spot (but I don't see anywhere else)

A few nights ago I was down in some mono code and that clearly would have created this effect. It check the options first and if they failed an exception was thrown (never calling the OS). I assume that code is no longer used in the v5 .net runtime.

Author: Dweeberly
Assignees: -
Labels:

area-System.Net.Sockets, untriaged

Milestone: -

@antonfirsov
Copy link
Member

@tmds @wfurt would it make sense to implement SocketOptionName.DontLinger on top of SO_LINGER so that DontLinger controls the value of linger.l_onoff? Or at least make it gettable?

To me it looks like this is what it does on Windows:
https://docs.microsoft.com/en-us/windows/win32/winsock/sol-socket-socket-options

@antonfirsov antonfirsov added the tenet-compatibility Incompatibility with previous versions or .NET Framework label Sep 14, 2021
@tmds
Copy link
Member

tmds commented Sep 14, 2021

@tmds @wfurt would it make sense to implement SocketOptionName.DontLinger on top of SO_LINGER so that DontLinger controls the value of linger.l_onoff? Or at least make it gettable?

The issue with the setter is that we need to pick a timeout. And a getter is not so useful, this is an option a user will want to set.

SocketOptionName.Linger can be used instead.

We should consider to change Get/SetSocketOption to still throw an exception for unsupported options and not close the socket.

@wfurt
Copy link
Member

wfurt commented Sep 14, 2021

I would think we can pick reasonable default for timeout oof needed. But aside from that I would agree that failing to set options should not close the underlying socket.

@karelz
Copy link
Member

karelz commented Sep 21, 2021

Triage: We agree we should not close underlying sockets when set options fails. Retitling to track it here.

@karelz karelz changed the title On Linux GetSocketOption will close socket on Don'tLinger request GetSocketOption should not close underlying socket Sep 21, 2021
@karelz karelz changed the title GetSocketOption should not close underlying socket GetSocketOption/SetSocketOption should not close underlying socket Sep 21, 2021
@karelz karelz added this to the Future milestone Sep 21, 2021
@karelz karelz added enhancement Product code improvement that does NOT require public API changes/additions and removed untriaged New issue has not been triaged by the area owner labels Sep 21, 2021
@ghost ghost added the in-pr There is an active PR which will close this issue when it is merged label Oct 4, 2021
@karelz karelz modified the milestones: Future, 7.0.0 Oct 14, 2021
@ghost ghost locked as resolved and limited conversation to collaborators Nov 13, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-System.Net.Sockets enhancement Product code improvement that does NOT require public API changes/additions in-pr There is an active PR which will close this issue when it is merged tenet-compatibility Incompatibility with previous versions or .NET Framework
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants