RIOT-OS / RIOT

RIOT - The friendly OS for IoT
https://riot-os.org
GNU Lesser General Public License v2.1
4.9k stars 1.98k forks source link

sockets: conn: recvfrom without (explicit) bind #4320

Closed OlegHahm closed 8 years ago

OlegHahm commented 8 years ago

If I understand the code and its behavior correctly, it is not possible to call POSIX recvfrom() without a bind() before which is not what you would expect for a connectionless (aka DGRAM aka UDP) socket. Or am I missing something?

miri64 commented 8 years ago

Is #4326 addressing this?

OlegHahm commented 8 years ago

Yes. At least partly.

cgundogan commented 8 years ago

Does it really make sense to call recvfrom() without binding to a port first? From my understanding, POSIX sendto() will choose a random ephemeral port if not bound to a port, but recvfrom() must know on which port to receive. How else would the network stack know to which application incoming traffic should go to?

OlegHahm commented 8 years ago

Does it really make sense to call recvfrom() without binding to a port first?

Of course it does, how would you implement a client otherwise?

but recvfrom() must know on which port to receive. How else would the network stack know to which application incoming traffic should go to?

You still have the socket struct to identify the "connection".

cgundogan commented 8 years ago

And what is the intention of not binding before receiving? Letting the system choose a random port for the socket endpoint? How will this port then be announced to other nodes so that they know where to send UDP unicast traffic to? With binding before receiving, the port can at least be configured on all nodes statically.

Of course it does, how would you implement a client otherwise?

A client doesn't recvfrom(). IMO - and this may be semantics - as soon as you receive, you are a server.

OlegHahm commented 8 years ago

Let A be a UDP server and B a UDP client. A offers a service at the well-known port X. B sends a request to A:X using the sendto() call. As you said a random ephemeral port Y is used as source port. A identifies the remote peer by the tuple of source address and port, i.e. B:Y. In order to receive the response from A to its initial request, B calls recvfrom().

According to http://stackoverflow.com/questions/15861321/how-to-listen-for-udp-datagrams-from-a-certain-host-using-recvfrom and the man pages, one may use connect() to filter the ports on the receiving side.

miri64 commented 8 years ago

Mh, now that @cgundogan is pointing this out I'm wondering these things, too. I thought it was a fuck-up on my part, but then I realized, that interacting with packets without bind only makes sense for sending, but not receiving.

miri64 commented 8 years ago

In order to receive the response from A to its initial request, B calls recvfrom().

@OlegHahm but doesn't the sending you described above effectively "binds" the the socket to a port? If not, we don't need a "conn" object for UDP anyway btw, just functions to send and receive.

OlegHahm commented 8 years ago

@OlegHahm but doesn't the sending you described above effectively "binds" the the socket to a port?

That's my understanding, yes, and everything I found on stackoverflow so far (e.g. http://stackoverflow.com/questions/6189831/whats-the-purpose-of-using-sendto-recvfrom-instead-of-connect-send-recv-with-ud or http://stackoverflow.com/questions/8636717/is-it-always-required-to-bind-a-socket) seems to support this.

If not, we don't need a "conn" object for UDP anyway btw, just functions to send and receive.

Couldn't agree more. ;) IMO conn is terribly complex for simple UDP connections.

cgundogan commented 8 years ago

On windows ( - (: - ) at least it seems necessary to bind a socket to a port before recvfrom [1]:

s [in]
A descriptor identifying a bound socket.

WSAEINVAL
The socket has not been bound with bind, or an unknown flag was specified, or MSG_OOB was specified for a socket with SO_OOBINLINE enabled, or (for byte stream-style sockets only) len was zero or negative.

[1] https://msdn.microsoft.com/en-us/library/windows/desktop/ms740120%28v=vs.85%29.aspx

OlegHahm commented 8 years ago

"RIOT - if your device is too constrained to run Windows" is a pretty poor punch line in my opinion.

OlegHahm commented 8 years ago

And btw http://msdn.microsoft.com/en-us/library/windows/desktop/ms740120%28v=vs.85%29.aspx states: "Explicit binding is discouraged for client applications. For client applications using this function, the socket can become bound implicitly to a local address through sendto, WSASendTo, or WSAJoinLeaf." and "Note If a socket is opened, a setsockopt call is made, and then a sendto call is made, Windows Sockets performs an implicit bind function call. If the socket is unbound, unique values are assigned to the local association by the system, and the socket is then marked as bound. "

cgundogan commented 8 years ago

Yes, I can see your point and understand the use case. But isn't this just a little fix going in the direction of adding bind() to send[to]() functions if the socket was identified as unbound? This would be the same as binding before receiving (and possible sending in between)

cgundogan commented 8 years ago

It is however still necessary to bind the socket if you call recvfrom without any sending in between. And this is the use case I had in mind when reading the title of this issue.

OlegHahm commented 8 years ago

As far as I understand it, calling recvfrom() without a bind is valid for DGRAM and would result in receiving a datagram received for any port.

cgundogan commented 8 years ago

This is very hard for me to imagine. I couldn't find any serious sources in the webz that would support this fact. Any application could "hijack/eavesdrop" other applications' udp traffic (without being a RAW socket) if that would be true. I have to test this on my machine before I can believe that..

OlegHahm commented 8 years ago

Maybe this title is more to the point.

OlegHahm commented 8 years ago

Any application could "hijack/eavesdrop" other applications udp traffic (without being a RAW socket) if that would be true.

No, why? If the socket is bound (explicitly by calling bind() or implicitly by calling sendto() before) the OS/network stack should deliver these packets to the corresponding socket. If it is not bound, there's nothing to hijack.

cgundogan commented 8 years ago

I did reuse this code that can be found in [1] to start a udp server. (I created a gist here [2]) I then cut out the bind and all port related stuff from this source file and compiled it.

recvfrom() does not return with an error, but blocks and waits for an incoming message. I then tried with netcat to send udp traffic to ... I don't know which port? I tried to investigate with ss -s and ss -lu. But I couldn't find any fitting port to this application. Netcatting with an arbitrary port would not send a udp datagram to this specific program.

If I call a sendto() before doing recvfrom(), I was able to see a port with ss -lu and send udp datagrams via netcat to it.

So what I learn from this: recvfrom() does not implicitly bind to a port, but using recvfrom() without binding explicitly or implicitly doesn't work in terms of receiving datagrams.

[1] https://www.cs.cmu.edu/afs/cs/academic/class/15213-f99/www/class26/udpserver.c [2] https://gist.github.com/cgundogan/25bc8e16d506460d4d98

OlegHahm commented 8 years ago

Would make sense to look into the POSIX standard here, but I guess that's of secondary interest. More important is to allow recvfrom() with an implicit bind.

cgundogan commented 8 years ago

More important is to allow recvfrom() with an implicit bind.

Do you mean that recvfrom() should do the implicit bind? Or were you referring to preceding calls to sendto()?

OlegHahm commented 8 years ago

I think it doesn't matter so much if sendto() or recvfrom() does the actual binding. IMO (and that's why I decided for this option) one can omit the binding if there is no recvfrom() call. However, binding during sendto() may tell the OS early on that there might be an application interested in this data. This would matter if we allow, for instance, only one receiver per port.

OlegHahm commented 8 years ago

solved with #4326