-
Notifications
You must be signed in to change notification settings - Fork 203
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
[LibOS,PAL] Gramine doesn't support EINPROGRESS
(non-blocking sockets connect)
#1641
Comments
I see two ways of fixing it. Implement proper
|
/* | |
* Sockets can be in 4 states: NEW, BOUND, LISTENING and CONNECTED. | |
* | |
* +------------------+ | |
* | | | |
* | | | |
* bind() listen() V accept() old socket | |
* +--> NEW --------------------> BOUND -------------> LISTEN --------------+ | |
* | | | ^ new socket | |
* | | | | | | |
* | | | | | | |
* | | connect() | | disconnect() | | |
* | | | | (if it was bound) | | |
* | | connect() | | | | |
* | | | | | | |
* | | V | | | |
* | +---------------------> CONNECTED <--------------------------------+ | |
* | | | |
* | disconnect() | | |
* | (if it was not bound) | | |
* +------------------------------+ | |
* | |
*/ |
This diagram worked very well for us until now. This diagram also puts limitations that make introducing SOCK_CONNECTING
quite non-trivial.
One problem is that Gramine doesn't have a proper place where to move from SOCK_CONNECTING
to SOCK_CONNECTED
. The Linux host doesn't inform us on such a state transition. Moreover, there seems to be no "definite" system call at which we could verify the state of the connection (select/poll/epoll? but it doesn't seem to be 100%, more like a recommendation).
Another problem is that there are some socket metadata updates on a successful connect()
, which only happen at the connect()
point:
Lines 233 to 237 in 24a581c
if (sock->state != SOCK_BOUND) { | |
assert(sock->state == SOCK_NEW); | |
assert(!sock->was_bound); | |
pal_to_linux_sockaddr(&pal_local_addr, &sock->local_addr, &sock->local_addrlen); | |
} |
If we imagine that we split the connection logic into "CONNECTING actions" and "CONNECTED actions", we need to do actual connect()
host syscall in the former set of actions, and we need to update the sock->local_addr
metadata in the latter set of actions. But there is no separate API to get local_addr
. I especially don't like to introduce it in the SGX case, because it creates a new OCALL and a new attack vector.
Propagate EINPROGRESS but consider the connection established
Basically, Gramine should return to the app that EINPROGRESS happens. But internally, Gramine continues as if the connection is established (i.e. moves to SOCK_CONNECTED
state and updates socket metadata).
We don't need to do anything with select/poll/epoll -- the POLLOUT action will be reported by the host when the socket is finally connected (or the connection is broken).
One problem is that I am not sure if a EINPROGRESS-failing connect can still update the local_addr
. Well, if not, we can patch the getsockname()
syscall to retrieve the local address, but it's the same as the second problem in the previous section.
Another problem is that getsockopt(s, SOL_SOCKET, SO_ERROR, ...)
will return 0 (success), because we don't have any way of checking for EINPROGRESS/ECONNREFUSED status of the connection.
I feel like both ways require non-trivial changes in our sockets code:
libos/src/sys/libos_socket.c
libos/src/net/ip.c
pal/src/host/linux/pal_sockets.c
pal/src/host/linux-sgx/host_ocalls.c
First, I'm more in favor of opt#1 -- Implement proper
Yeah, I understand that there may not have "definite" syscalls, but in practice I think it's still good enough for most of the reasonable workloads -- if we put the non-blocking sockets into
Sorry I don't get this -- why do we need a separate API (and which API) to get |
"Can't we update the metadata" -- but how will you do it? What PAL API will you call? There is no API to "get local IP address" in PAL. This "getting local IP address" is only achieved currently through the use of the "connect" API ( That's what I meant -- we don't have a separate PAL API to get this info. |
Thanks for the explanations! Now I understand -- I agree that we'd better reuse/extend some existing PAL APIs (if possible and applicable), like |
Update: The good news is that Linux binds the local address during This means that we can get the Note: I updated the example C program above, to reflect the |
Update: |
Update:
I updated the test C program to check for these errors. |
The PR #1643 is ready for review. It should fix this problem. |
Description of the problem
Gramine doesn't support non-blocking sockets connect, see these snippets in our code:
gramine/libos/src/net/ip.c
Lines 224 to 229 in 24a581c
gramine/pal/src/host/linux/pal_sockets.c
Lines 277 to 302 in 24a581c
gramine/pal/src/host/linux-sgx/host_ocalls.c
Lines 507 to 533 in 24a581c
Non-blocking sockets connect is a curious corner case. From
man 2 connect
:We can boil it down to two things:
SOCKET_NEW
andSOCKET_CONNECTED
, non-blocking sockets can be in theSOCKET_CONNECTING
state. In this state, no operation except for select/poll/epoll is possible.SOCKET_CONNECTING
, Gramine does the hacks above -- by blocking on waiting until the socket is fully connected.The problem with Gramine's current approach: a non-blocking socket effectively becomes blocking, in this specific case of the
connect()
syscall.The real-world consequence: if a remote peer is unresponsive, the connect operation blocks for a long time before it times out. On my Linux box, this timeout is 60 seconds.
This problem was independently reported on two workloads.
Steps to reproduce
There is no simple way to emulate
EINPROGRESS
. For this, basically anSYN
TCP packet must be delayed or dropped during the TCP/IP handshake.So I emulated using IP tables modification, which works fine on my Ubuntu 20.04:
SYN
packet on local port 12345 (this emulates unresponsive peer):And here's my testing code. I just modified
CI-Examples/helloworld
C file:Now we can test the following cases:
#if 0
. Theconnect()
syscall will immediately fail with ECONNREFUSED.connect()
syscall will first fail with EINPROGRESS, and then poll will immediately succeed, and the socket will have ECONNREFUSED.SYN
packet. Theconnect()
syscall will first fail with EINPROGRESS, and then poll will time out after 10 seconds.SYN
packet. Start the app, wait for 3 seconds. Restore IP tables. Now theconnect()
syscall will first fail with EINPROGRESS, and then poll will wait for these 3 seconds, and then the socket will have ECONNREFUSED.Gramine commit hash
24a581c
The text was updated successfully, but these errors were encountered: