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
DTLSv1_listen() regression on 1.1.x #6934
Comments
Is there anything I can do? |
All our recent focus has been on getting the 1.1.1 release out the door - which (all being well) should happen on Tuesday. I'm hoping to get some cycles to look at this after that. |
kind ping I don't expect you to fix the problem, just have a look at it and comment on the PR, please. |
Oops. Thanks for the reminder and your patience. I've been working on this today. I think I have a fix, but it needs more work yet. |
Oh wow! If you have something to test, just throw it at me! :-) |
Previously when a ClientHello arrives with a valid cookie using DTLSv1_listen() we only "peeked" at the message and left it on the underlying fd. This works fine for single threaded applications but for multi-threaded apps this does not work since the fd is typically reused for the server thread, while a new fd is created and connected for the client. By "peeking" we leave the message on the server fd, and consequently we think we've received another valid ClientHello and so we create yet another fd for the client, and so on until we run out of fds. In this new approach we remove the ClientHello and buffer it in the SSL object. Fixes openssl#6934
Please try out the fix in #7375. In the email you linked to you mentioned the dtls_udp_echo.c program. I've tried using this with the patched version. It works fine on FreeBSD. However it doesn't seem to work on Linux. I also tried the pre-rewrite version of DTLSv1_listen() in OpenSSL 1.0.2 and that didn't work on Linux either. Most of the time it fails to complete the handshake. Every now and then it all goes through smoothly. AFAICT linux seems to have a problem with creating multiple fds bound to the same address. So if you do this:
Then
Then it works everytime. Obviously though that isn't very helpful ;-) That's what happens on my machine anyway. It looks a bit like a kernel bug. Or possibly (more likely) me be stupid somehow. |
Looks like its a linux load balancing feature: https://lwn.net/Articles/542629/ Which leaves me wondering how you are supposed to do this on Linux. |
I don't understand, why do you need SO_REUSEPORT? |
Just doing SO_REUSEADDR has the same issue. |
Give me a day or two such I can test it and talk to Linux netdev folks in case this is a kernel issue. |
PBCAK. I take it back (I had a toy reproducer where it didn't make a difference...but looks like there is another problem with that). Commenting out the SO_REUSEPORT code from the dtls_udp_echo.c program solves the issue. I suspect when that code was written (a long time ago) SO_REUSEPORT support was not in the linux kernel so this conditionally compiled code never got run on linux, and it worked just fine. SO_REUSEPORT now is available on Linux but doesn't work the way dtls_udp_echo.c expects it to. Anyway problem solved. |
Grr...I meant SO_REUSEPORT not SO_REUSEADDR...I edited the above comment accordingly. |
Previously when a ClientHello arrives with a valid cookie using DTLSv1_listen() we only "peeked" at the message and left it on the underlying fd. This works fine for single threaded applications but for multi-threaded apps this does not work since the fd is typically reused for the server thread, while a new fd is created and connected for the client. By "peeking" we leave the message on the server fd, and consequently we think we've received another valid ClientHello and so we create yet another fd for the client, and so on until we run out of fds. In this new approach we remove the ClientHello and buffer it in the SSL object. Fixes #6934 Reviewed-by: Ben Kaduk <kaduk@mit.edu> (Merged from #7375) (cherry picked from commit 079ef6b)
This is now fixed in master and 1.1.1 (1.1.0 is only receiving security fixes now). |
I hope it's ok to comment on this issue, but the advertised approach to create a connected a socket like:
is totally flawed because this operation is not atomic. The window of opportunity between Yes, one may say that UDP is unreliable by definition, but artificially dropping packets when they have already reached their destination looks fishy at best. Unfortunately, there's no way to bind a socket both to local and remote addresses atomically on a modern system (except for macOS which has I have this approach running reliably without DTLS and now need to implement it for DTLS. The problem with |
Maybe I'm misunderstanding what you wrote - but why would you want to use DTLSv1_listen() on a connected socket? It's whole purpose is to listen on an unconnected socket. |
Thanks for your quick reply! I guess the best way to illustrate the problem is to provide a code snippet. Please check out this gist: https://gist.github.com/neoxic/0d9314ec756d37ca4303bced49b94543 |
Again in a nutshell, How will OpenSSL handle this situation? What error will |
I believe it will just be silently skipped.
Not at the moment...at least not with any kind of libssl support. I suppose you could do something application side to check for this, but it wouldn't be pretty. I'm not sure what a good libssl solution would like like. libssl is inherently connection based. hmmmm.....I wonder if you could have some kind of multiplexing BIO the worked with an unconnected fd that had a set of associated child BIOs (one for each peer) which were effectively "connected". The job of the parent multiplexing BIO would be to forward packets to the right child BIO based on the source of the packet. |
Writing a custom BIO is always an option indeed. Though it doesn't look like an easy solution for anyone who just wants to implement reliable DTLS support. The other way around this problem would be a |
Thanks for bringing up this topic @neoxic! |
I was thinking more of providing such a BIO in libssl.
This could possibly be achieved with DTLSv1_listen() as it is and using a mem BIO, i.e. you create a mem bio, and then peek at the packets in your real fd. If they should be handled by DTLSv1_listen(), then read them out and push them into the mem bio. DTLSv1_listen() then only ever reads packets from the mem bio. |
I assume that we'd still have a main "listen" socket. Either that should also push it in that new bio, or DTLSv1_listen() should look at both. |
I can speak only for MbedTLS which handles TLS/DTLS very consistently using a similar yet much more simple concept of basic I/O (bio) over a session, connection or whatever you call it. Please note that MbedTLS is also connection oriented, but what differs is that MbedTLS gives full control to the user over how a (D)TLS session is handled by allowing to process a handshake and a shutdown for a session in progressions, not just until the user's intervention is unavoidable (WANT_READ/WANT_WRITE). Important stages of So, instead of handling certain stages of a handshake on the BIO level by messing around with custom multiplexing, peeking into packets, etc., it is much easier to just let the calling party know exactly what needs to be scheduled/done next by returning corresponding results from MbedTLS also has "demo" |
Needs a bit of thought to try and figure out exactly how that would work. |
I'll stick to this approach for now. Thanks, Matt! |
Unfortunately, the mem BIO trick doesn't work with Also, I was quite surprised to discover that there's no way to pass an "opaque user pointer" to a callback in OpenSSL to circumvent the above issue. The There seems to be no other way but to implement my own custom BIO to solve this issue. OpenSSL's API looks more and more outlandish with each passing day. |
Many callbacks do allow that - but apparently not the cookie gen callback. However it does pass a pointer to the SSL. You could use |
Oh, I didn't know about |
Improving the DTLSv1_listen() situation would be rather a feature as it would require API changes and additions. |
Just wanted to drop in and thank @mattcaswell on the memory BIO hint as that solved accepting multiple simultaneous connections for me. The full solution maybe worth a writeup. |
The rewrite of DTLSv1_listen() makes multi client DTLS server impossible on OpenSSL 1.1.x.
DTLSv1_listen() peeks into the kernel queue, but does not consume the client hello. Therefore
the common approach, creating a new socket for the client and off loading it, is doomed to failure since the hello is pending and the application logic thinks a new client approached.
This PR seems to address the regression but sadly didn't get much attention yet:
#5024
For more details see my mail:
https://mta.openssl.org/pipermail/openssl-users/2018-August/008498.html
The text was updated successfully, but these errors were encountered: