BUG/MEDIUM: quic: fix tasklet_wakeup loop on connection closing

It is possible to trigger a loop of tasklets calls if a QUIC connection
is interrupted abruptly by the client. This is caused by the following
interaction :
* FD iocb is woken up for read. This causes a wakeup on quic_conn
  tasklet.
* quic_conn_io_cb is run and try to read but fails as the connection
  socket is closed (typically with a ECONNREFUSED). FD read is
  subscribed to the poller via qc_rcv_buf() which will cause the loop.

The looping will stop automatically once the idle-timeout is expired and
the connection instance is finally released.

To fix this, ensure FD read is subscribed only for transient error cases
(EAGAIN or similar). All other cases are considered as fatal and thus
all future read operations will fail. Note that for the moment, nothing
is reported on the quic_conn which may not skip future reception. This
should be improved in a future commit to accelerate connection closing.

This bug can be reproduced on a frequent occurence by interrupting the
following command. Quic traces should be activated on haproxy side to
detect the loop :
$ ngtcp2-client --tp-file=/tmp/ngtcp2-tp.txt \
  --session-file=/tmp/ngtcp2-session.txt \
  -r 0.3 -t 0.3 --exit-on-all-streams-close 127.0.0.1 20443 \
  "http://127.0.0.1:20443/?s=1024"

This must be backported up to 2.7.
This commit is contained in:
Amaury Denoyelle 2023-08-11 16:10:34 +02:00
parent d355bce7e4
commit 7f80d51812

View File

@ -704,7 +704,9 @@ int qc_rcv_buf(struct quic_conn *qc)
get_net_port(&qc->local_addr));
if (ret <= 0) {
/* Subscribe FD for future reception. */
fd_want_recv(qc->fd);
if (errno == EAGAIN || errno == EWOULDBLOCK || errno == ENOTCONN)
fd_want_recv(qc->fd);
/* TODO handle other error codes as fatal on the connection. */
break;
}