Commit f8f9ab2d authored by Jens Axboe's avatar Jens Axboe

io_uring/net: ensure socket is marked connected on connect retry

io_uring does non-blocking connection attempts, which can yield some
unexpected results if a connect request is re-attempted by an an
application. This is equivalent to the following sync syscall sequence:

sock = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP);
connect(sock, &addr, sizeof(addr);

ret == -1 and errno == EINPROGRESS expected here. Now poll for POLLOUT
on sock, and when that returns, we expect the socket to be connected.
But if we follow that procedure with:

connect(sock, &addr, sizeof(addr));

you'd expect ret == -1 and errno == EISCONN here, but you actually get
ret == 0. If we attempt the connection one more time, then we get EISCON
as expected.

io_uring used to do this, but turns out that bluetooth fails with EBADFD
if you attempt to re-connect. Also looks like EISCONN _could_ occur with
this sequence.

Retain the ->in_progress logic, but work-around a potential EISCONN or
EBADFD error and only in those cases look at the sock_error(). This
should work in general and avoid the odd sequence of a repeated connect
request returning success when the socket is already connected.

This is all a side effect of the socket state being in a CONNECTING
state when we get EINPROGRESS, and only a re-connect or other related
operation will turn that into CONNECTED.

Cc: stable@vger.kernel.org
Fixes: 3fb1bd68 ("io_uring/net: handle -EINPROGRESS correct for IORING_OP_CONNECT")
Link: https://github.com/axboe/liburing/issues/980Signed-off-by: default avatarJens Axboe <axboe@kernel.dk>
parent 0df96fb7
...@@ -1461,16 +1461,6 @@ int io_connect(struct io_kiocb *req, unsigned int issue_flags) ...@@ -1461,16 +1461,6 @@ int io_connect(struct io_kiocb *req, unsigned int issue_flags)
int ret; int ret;
bool force_nonblock = issue_flags & IO_URING_F_NONBLOCK; bool force_nonblock = issue_flags & IO_URING_F_NONBLOCK;
if (connect->in_progress) {
struct socket *socket;
ret = -ENOTSOCK;
socket = sock_from_file(req->file);
if (socket)
ret = sock_error(socket->sk);
goto out;
}
if (req_has_async_data(req)) { if (req_has_async_data(req)) {
io = req->async_data; io = req->async_data;
} else { } else {
...@@ -1490,9 +1480,7 @@ int io_connect(struct io_kiocb *req, unsigned int issue_flags) ...@@ -1490,9 +1480,7 @@ int io_connect(struct io_kiocb *req, unsigned int issue_flags)
&& force_nonblock) { && force_nonblock) {
if (ret == -EINPROGRESS) { if (ret == -EINPROGRESS) {
connect->in_progress = true; connect->in_progress = true;
return -EAGAIN; } else if (ret == -ECONNABORTED) {
}
if (ret == -ECONNABORTED) {
if (connect->seen_econnaborted) if (connect->seen_econnaborted)
goto out; goto out;
connect->seen_econnaborted = true; connect->seen_econnaborted = true;
...@@ -1506,6 +1494,16 @@ int io_connect(struct io_kiocb *req, unsigned int issue_flags) ...@@ -1506,6 +1494,16 @@ int io_connect(struct io_kiocb *req, unsigned int issue_flags)
memcpy(req->async_data, &__io, sizeof(__io)); memcpy(req->async_data, &__io, sizeof(__io));
return -EAGAIN; return -EAGAIN;
} }
if (connect->in_progress) {
/*
* At least bluetooth will return -EBADFD on a re-connect
* attempt, and it's (supposedly) also valid to get -EISCONN
* which means the previous result is good. For both of these,
* grab the sock_error() and use that for the completion.
*/
if (ret == -EBADFD || ret == -EISCONN)
ret = sock_error(sock_from_file(req->file)->sk);
}
if (ret == -ERESTARTSYS) if (ret == -ERESTARTSYS)
ret = -EINTR; ret = -EINTR;
out: out:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment