mirror of
http://git.haproxy.org/git/haproxy.git/
synced 2025-02-20 12:46:56 +00:00
MEDIUM: quic: improve fatal error handling on send
Send is conducted through qc_send_ppkts() for a QUIC connection. There is two types of error which can be encountered on sendto() or affiliated syscalls : * transient error. In this case, sending is simulated with the remaining data and retransmission process is used to have the opportunity to retry emission * fatal error. If this happens, the connection should be closed as soon as possible. This is done via qc_kill_conn() function. Until this patch, only ECONNREFUSED errno was considered as fatal. Modify the QUIC send API to be able to differentiate transient and fatal errors more easily. This is done by fixing the return value of the sendto() wrapper qc_snd_buf() : * on fatal error, a negative error code is returned. This is now the case for every errno except EAGAIN, EWOULDBLOCK, ENOTCONN, EINPROGRESS and EBADF. * on a transient error, 0 is returned. This is the case for the listed errno values above and also if a partial send has been conducted by the kernel. * on success, the return value of sendto() syscall is returned. This commit will be useful to be able to handle transient error with a quic-conn owned socket. In this case, the socket should be subscribed to the poller and no simulated send will be conducted. This commit allows errno management to be confined in the quic-sock module which is a nice cleanup. On a final note, EBADF should be considered as fatal. This will be the subject of a next commit. This should be backported up to 2.7.
This commit is contained in:
parent
7b8aac4439
commit
1febc2d316
@ -44,7 +44,7 @@ struct connection *quic_sock_accept_conn(struct listener *l, int *status);
|
||||
struct task *quic_lstnr_dghdlr(struct task *t, void *ctx, unsigned int state);
|
||||
void quic_lstnr_sock_fd_iocb(int fd);
|
||||
int qc_snd_buf(struct quic_conn *qc, const struct buffer *buf, size_t count,
|
||||
int flags, int *syscall_errno);
|
||||
int flags);
|
||||
int qc_rcv_buf(struct quic_conn *qc);
|
||||
|
||||
/* Set default value for <qc> socket as uninitialized. */
|
||||
|
@ -15,7 +15,6 @@
|
||||
#include <haproxy/quic_conn.h>
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
@ -3446,11 +3445,12 @@ static int qc_prep_pkts(struct quic_conn *qc, struct buffer *buf,
|
||||
|
||||
/* Send datagrams stored in <buf>.
|
||||
*
|
||||
* This function returns 1 for success. Even if sendto() syscall failed,
|
||||
* buffer is drained and packets are considered as emitted and this function returns 1
|
||||
* There is a unique exception when sendto() fails with ECONNREFUSED as errno,
|
||||
* this function returns 0.
|
||||
* QUIC loss detection mechanism is used as a back door way to retry sending.
|
||||
* This function returns 1 for success. On error, there is several behavior
|
||||
* depending on underlying sendto() error :
|
||||
* - for a fatal error, 0 is returned and connection is killed.
|
||||
* - a transient error is assimilated to a success case with 1 returned.
|
||||
* Remaining data are purged from the buffer and will eventually be detected
|
||||
* as lost which gives the opportunity to retry sending.
|
||||
*/
|
||||
int qc_send_ppkts(struct buffer *buf, struct ssl_sock_ctx *ctx)
|
||||
{
|
||||
@ -3490,18 +3490,13 @@ int qc_send_ppkts(struct buffer *buf, struct ssl_sock_ctx *ctx)
|
||||
* quic-conn fd management.
|
||||
*/
|
||||
if (!skip_sendto) {
|
||||
int syscall_errno;
|
||||
|
||||
syscall_errno = 0;
|
||||
if (qc_snd_buf(qc, &tmpbuf, tmpbuf.data, 0, &syscall_errno)) {
|
||||
if (syscall_errno == ECONNREFUSED) {
|
||||
/* Let's kill this connection asap. */
|
||||
TRACE_PROTO("UDP port unreachable", QUIC_EV_CONN_SPPKTS, qc);
|
||||
qc_kill_conn(qc);
|
||||
b_del(buf, buf->data);
|
||||
goto leave;
|
||||
}
|
||||
|
||||
int ret = qc_snd_buf(qc, &tmpbuf, tmpbuf.data, 0);
|
||||
if (ret < 0) {
|
||||
qc_kill_conn(qc);
|
||||
b_del(buf, buf->data);
|
||||
goto leave;
|
||||
}
|
||||
else if (!ret) {
|
||||
skip_sendto = 1;
|
||||
TRACE_ERROR("sendto error, simulate sending for the rest of data", QUIC_EV_CONN_SPPKTS, qc);
|
||||
}
|
||||
|
@ -501,17 +501,16 @@ static void quic_conn_sock_fd_iocb(int fd)
|
||||
/* Send a datagram stored into <buf> buffer with <sz> as size.
|
||||
* The caller must ensure there is at least <sz> bytes in this buffer.
|
||||
*
|
||||
* Returns 0 on success else non-zero. When failed, this function also
|
||||
* sets <*syscall_errno> to the errno only when the send*() syscall failed.
|
||||
* As the C library will never set errno to 0, the caller must set
|
||||
* <*syscall_errno> to 0 before calling this function to be sure to get
|
||||
* the correct errno in case a send*() syscall failure.
|
||||
* Returns the total bytes sent over the socket. 0 is returned if a transient
|
||||
* error is encountered which allows send to be retry later. A negative value
|
||||
* is used for a fatal error which guarantee that all future send operation for
|
||||
* this connection will fail.
|
||||
*
|
||||
* TODO standardize this function for a generic UDP sendto wrapper. This can be
|
||||
* done by removing the <qc> arg and replace it with address/port.
|
||||
*/
|
||||
int qc_snd_buf(struct quic_conn *qc, const struct buffer *buf, size_t sz,
|
||||
int flags, int *syscall_errno)
|
||||
int flags)
|
||||
{
|
||||
ssize_t ret;
|
||||
|
||||
@ -619,31 +618,29 @@ int qc_snd_buf(struct quic_conn *qc, const struct buffer *buf, size_t sz,
|
||||
EXTRA_COUNTERS_GET(prx->extra_counters_fe,
|
||||
&quic_stats_module);
|
||||
|
||||
*syscall_errno = errno;
|
||||
/* TODO adjust errno for UDP context. */
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK ||
|
||||
errno == ENOTCONN || errno == EINPROGRESS || errno == EBADF) {
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK)
|
||||
HA_ATOMIC_INC(&prx_counters->socket_full);
|
||||
else
|
||||
HA_ATOMIC_INC(&prx_counters->sendto_err);
|
||||
|
||||
/* transient error */
|
||||
TRACE_PRINTF(TRACE_LEVEL_USER, QUIC_EV_CONN_SPPKTS, qc, 0, 0, 0,
|
||||
"UDP send failure errno=%d (%s)", errno, strerror(errno));
|
||||
return 0;
|
||||
}
|
||||
else if (errno) {
|
||||
/* TODO unlisted errno : handle it explicitly.
|
||||
* ECONNRESET may be encounter on quic-conn socket.
|
||||
*/
|
||||
else {
|
||||
/* unrecoverable error */
|
||||
HA_ATOMIC_INC(&prx_counters->sendto_err_unknown);
|
||||
TRACE_PRINTF(TRACE_LEVEL_USER, QUIC_EV_CONN_SPPKTS, qc, 0, 0, 0,
|
||||
"UDP send failure errno=%d (%s)", errno, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Note that one must not consider that this macro will not modify errno. */
|
||||
TRACE_PRINTF(TRACE_LEVEL_DEVELOPER, QUIC_EV_CONN_LPKT, qc, 0, 0, 0,
|
||||
"syscall error (errno=%d)", *syscall_errno);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (ret != sz)
|
||||
return 1;
|
||||
return 0;
|
||||
|
||||
/* we count the total bytes sent, and the send rate for 32-byte blocks.
|
||||
* The reason for the latter is that freq_ctr are limited to 4GB and
|
||||
@ -652,7 +649,7 @@ int qc_snd_buf(struct quic_conn *qc, const struct buffer *buf, size_t sz,
|
||||
_HA_ATOMIC_ADD(&th_ctx->out_bytes, ret);
|
||||
update_freq_ctr(&th_ctx->out_32bps, (ret + 16) / 32);
|
||||
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Receive datagram on <qc> FD-owned socket.
|
||||
|
Loading…
Reference in New Issue
Block a user