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

[BUG] Connection timeout on macOS when using acquired UDP socket #2178

Closed
acmanderson opened this issue Oct 27, 2021 · 2 comments · Fixed by #2342
Closed

[BUG] Connection timeout on macOS when using acquired UDP socket #2178

acmanderson opened this issue Oct 27, 2021 · 2 comments · Fixed by #2342
Labels
[docs] Area: Improvements or additions to documentation Type: Maintenance Work required to maintain or clean up the code
Milestone

Comments

@acmanderson
Copy link

Describe the bug
On macOS, using srt_bind_acquire followed by srt_connect will fail with SRT_ENOSERVER.

To Reproduce
Steps to reproduce the behavior:

  1. On macOS, create a non-SRT UDP socket pointing to the listening address of an SRT server. I'm using srtgo with the following snippet to connect to the destination and return the socket's file descriptor:
    func connect() (int, error) {
        udpConn, err := net.DialUDP("udp", nil, &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 7005})
        if err != nil {
    	return 0, err
        }
        var fd int
        syscallConn, err := udpConn.SyscallConn()
        if err != nil {
    	return 0, err
        }
        if err := syscallConn.Control(func(_fd uintptr) {
    	fd = int(_fd)
        }); err != nil {
    	return 0, err
        }
        return fd, nil
    }
  2. Use srt_bind_acquire on the UDP socket.
  3. Use srt_connect to attempt to connect to the server.
  4. SRT_ENOSERVER should be returned.

Expected behavior
The UDP socket will be properly reused by the SRT library as the client socket. I'm not totally sure this is supported behavior, but it works when running on Linux.

Desktop (please provide the following information):

  • OS: macOS 11.6
  • SRT Version / commit ID: 1.4.3 and 1.4.4

Additional context
I've narrowed this down to a difference in the sendmsg syscall between Linux and macOS/FreeBSD. On macOS, calling sendmsg on a connected socket with a destination address in msghdr will fail with EISCONN. Per the FreeBSD sendmsg man page:

[EISCONN] A destination address was specified and the socket is already connected.

Per the Linux sendmsg man page:

EISCONN The connection-mode socket was connected already but a recipient was specified. (Now either this error is returned, or the recipient specification is ignored.)

The srt::CChannel::sendto function always sets the destination address in its sendmsg call.

I'm not much of a C developer, but I was able to get around this issue by replacing the sendmsg call with the following:

int res = ::sendmsg(m_iSocket, &mh, 0);
if (res == -1 && errno == EISCONN)
{
    mh.msg_name = nullptr;
    mh.msg_namelen = 0;
    res = ::sendmsg(m_iSocket, &mh, 0);
}

I imagine this is a non-optimal solution to the problem, but it demonstrates the cause of the issue.

@acmanderson acmanderson added the Type: Bug Indicates an unexpected problem or unintended behavior label Oct 27, 2021
@ethouris
Copy link
Collaborator

ethouris commented Oct 28, 2021

@maxsharabayko I think it should be mentioned in the documentation that srt_bind_acquire requires that the passed UDP socket shall not be connected, otherwise it won't work.

I think this thing isn't BSD specific at all - it's also mentioned in the Linux manpage:

The msg_name field is used on an unconnected socket to specify the target address for a datagram. 
It points to a buffer containing the address; the msg_namelen field should be set to the size of the
address.  For  a  connected socket, these fields should be specified as NULL and 0, respectively.

The matter is then that we do not support connected sockets in srt_bind_acquire and I don't even think it is possible. That also doesn't sound logic that you first "connect" the UDP socket yourself and then expect SRT to "connect" this socket, possibly to a different target address.

I've read a bit the documentation of the UDP support in Go library and I think I understand why you call udpConn.SyscallConn on the UDPConn object - because this is the only way to obtain the UDP socket. The problem is that it returns you this socket, but it's already connected. There could be a way to fix it though (manpage for connect(2)):

       Generally, connection-based protocol sockets may successfully connect() only once;
connectionless protocol sockets may use connect() multiple times to change their association. 
Connectionless sockets may dissolve the association by connecting to an address
with the sa_family member of sockaddr set to AF_UNSPEC (supported on Linux since kernel 2.2).

So, maybe there's a way to "unconnect" this socket by calling syscall.Connect on it with the unspecified address, as described above. After that you should have no problems with using it with srt_bind_acquire. Might be that specifying nil as the target address in the DialUDP call should have the same result, although I'm not sure because the documentation says: If the IP field of raddr is nil or an unspecified IP address, the local system is assumed. - not sure what it means.

Just one more thing - if you use srt_bind_acquire, make sure you know what you are doing. When SRT "acquires" a socket, it is understood that this socket is for the SRT's sole use and you can't use it for anything else. Generally this function is for a case when you want to configure your socket some specific - including nonportable - way. This may include also binding.

@maxsharabayko maxsharabayko added the [docs] Area: Improvements or additions to documentation label Oct 28, 2021
@maxsharabayko maxsharabayko added this to the v1.4.5 milestone Oct 28, 2021
@acmanderson
Copy link
Author

acmanderson commented Oct 28, 2021

Thanks for the feedback, I think I was mainly confused by Go's semantics for connected/unconnected sockets. If I use net.ListenUDP instead of net.DialUDP, things appear to work as expected.

This isn't an issue for me currently, but I suppose I could see a use case where SRT can take over a connected socket and use send instead of sendmsg to write to the socket. I was initially exploring how my application could meet some security requirements I have around restricting certain syscalls. Ultimately sendmsg works for my use case, but my thinking is it could be useful to allow an application using SRT to restrict the use of the sendmsg syscall. I'm not sure if that makes sense as a use case, just thinking out loud.

@maxsharabayko maxsharabayko added Type: Maintenance Work required to maintain or clean up the code and removed Type: Bug Indicates an unexpected problem or unintended behavior labels May 24, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[docs] Area: Improvements or additions to documentation Type: Maintenance Work required to maintain or clean up the code
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants