haproxy/src/quic_sock.c
Willy Tarreau f509065191 MEDIUM: fd: merge fdtab[].ev and state for FD_EV_* and FD_POLL_* into state
For a long time we've had fdtab[].ev and fdtab[].state which contain two
arbitrary sets of information, one is mostly the configuration plus some
shutdown reports and the other one is the latest polling status report
which also contains some sticky error and shutdown reports.

These ones used to be stored into distinct chars, complicating certain
operations and not even allowing to clearly see concurrent accesses (e.g.
fd_delete_orphan() would set the state to zero while fd_insert() would
only set the event to zero).

This patch creates a single uint with the two sets in it, still delimited
at the byte level for better readability. The original FD_EV_* values
remained at the lowest bit levels as they are also known by their bit
value. The next step will consist in merging the remaining bits into it.

The whole bits are now cleared both in fd_insert() and _fd_delete_orphan()
because after a complete check, it is certain that in both cases these
functions are the only ones touching these areas. Indeed, for
_fd_delete_orphan(), the thread_mask has already been zeroed before a
poller can call fd_update_event() which would touch the state, so it
is certain that _fd_delete_orphan() is alone. Regarding fd_insert(),
only one thread will get an FD at any moment, and it as this FD has
already been released by _fd_delete_orphan() by definition it is certain
that previous users have definitely stopped touching it.

Strictly speaking there's no need for clearing the state again in
fd_insert() but it's cheap and will remove some doubts during some
troubleshooting sessions.
2021-04-07 18:04:39 +02:00

227 lines
5.8 KiB
C

/*
* QUIC socket management.
*
* Copyright 2020 HAProxy Technologies, Frédéric Lécaille <flecaille@haproxy.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*
*/
#include <errno.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <haproxy/connection.h>
#include <haproxy/listener.h>
#include <haproxy/xprt_quic.h>
/* This function is called from the protocol layer accept() in order to
* instantiate a new session on behalf of a given listener and frontend. It
* returns a positive value upon success, 0 if the connection can be ignored,
* or a negative value upon critical failure. The accepted connection is
* closed if we return <= 0. If no handshake is needed, it immediately tries
* to instantiate a new stream. The connection must already have been filled
* with the incoming connection handle (a fd), a target (the listener) and a
* source address.
*/
int quic_session_accept(struct connection *cli_conn)
{
struct listener *l = __objt_listener(cli_conn->target);
struct proxy *p = l->bind_conf->frontend;
struct session *sess;
cli_conn->proxy_netns = l->rx.settings->netns;
if (conn_prepare(cli_conn, l->rx.proto, l->bind_conf->xprt) < 0)
goto out_free_conn;
/* This flag is ordinarily set by conn_ctrl_init() which cannot
* be called for now.
*/
cli_conn->flags |= CO_FL_CTRL_READY;
/* wait for a PROXY protocol header */
if (l->options & LI_O_ACC_PROXY)
cli_conn->flags |= CO_FL_ACCEPT_PROXY;
/* wait for a NetScaler client IP insertion protocol header */
if (l->options & LI_O_ACC_CIP)
cli_conn->flags |= CO_FL_ACCEPT_CIP;
/* Add the handshake pseudo-XPRT */
if (cli_conn->flags & (CO_FL_ACCEPT_PROXY | CO_FL_ACCEPT_CIP)) {
if (xprt_add_hs(cli_conn) != 0)
goto out_free_conn;
}
if (conn_xprt_start(cli_conn) < 0)
goto out_free_conn;
sess = session_new(p, l, &cli_conn->obj_type);
if (!sess)
goto out_free_conn;
conn_set_owner(cli_conn, sess, NULL);
return 1;
out_free_sess:
/* prevent call to listener_release during session_free. It will be
* done below, for all errors. */
sess->listener = NULL;
session_free(sess);
out_free_conn:
cli_conn->qc->conn = NULL;
conn_stop_tracking(cli_conn);
conn_xprt_close(cli_conn);
conn_free(cli_conn);
out:
return 0;
}
/*
* Inspired from session_accept_fd().
* Instantiate a new connection (connection struct) to be attached to <qc>
* QUIC connection of <l> listener.
* Returns 1 if succeeded, 0 if not.
*/
static int new_quic_cli_conn(struct quic_conn *qc, struct listener *l,
struct sockaddr_storage *saddr)
{
struct connection *cli_conn;
struct sockaddr_storage *dst;
dst = NULL;
if (unlikely((cli_conn = conn_new(&l->obj_type)) == NULL))
goto out;
if (!sockaddr_alloc(&dst, saddr, sizeof *saddr))
goto out_free_conn;
qc->conn = cli_conn;
cli_conn->qc = qc;
cli_conn->dst = dst;
cli_conn->handle.fd = l->rx.fd;
cli_conn->flags |= CO_FL_ADDR_FROM_SET;
cli_conn->target = &l->obj_type;
/* XXX Should not be there. */
l->accept = quic_session_accept;
return 1;
out_free_conn:
conn_stop_tracking(cli_conn);
conn_xprt_close(cli_conn);
conn_free(cli_conn);
qc->conn = NULL;
out:
return 0;
}
/* Tests if the receiver supports accepting connections. Returns positive on
* success, 0 if not possible
*/
int quic_sock_accepting_conn(const struct receiver *rx)
{
return 1;
}
/* Accept an incoming connection from listener <l>, and return it, as well as
* a CO_AC_* status code into <status> if not null. Null is returned on error.
* <l> must be a valid listener with a valid frontend.
*/
struct connection *quic_sock_accept_conn(struct listener *l, int *status)
{
struct quic_conn *qc;
struct quic_rx_packet *pkt;
struct quic_cid *odcid;
int ret, ipv4;
qc = NULL;
pkt = LIST_ELEM(l->rx.qpkts.n, struct quic_rx_packet *, rx_list);
/* Should never happen. */
if (&pkt->rx_list == &l->rx.qpkts)
goto err;
qc = pkt->qc;
LIST_DEL(&pkt->rx_list);
if (!new_quic_cli_conn(qc, l, &pkt->saddr))
goto err;
ipv4 = pkt->saddr.ss_family == AF_INET;
if (!qc_new_conn_init(qc, ipv4, &l->rx.odcids, &l->rx.cids,
pkt->dcid.data, pkt->dcid.len,
pkt->scid.data, pkt->scid.len))
goto err;
odcid = &qc->params.original_destination_connection_id;
/* Copy the transport parameters. */
qc->params = l->bind_conf->quic_params;
/* Copy original_destination_connection_id transport parameter. */
memcpy(odcid->data, &pkt->dcid, pkt->odcid_len);
odcid->len = pkt->odcid_len;
/* Copy the initial source connection ID. */
quic_cid_cpy(&qc->params.initial_source_connection_id, &qc->scid);
qc->enc_params_len =
quic_transport_params_encode(qc->enc_params,
qc->enc_params + sizeof qc->enc_params,
&qc->params, 1);
if (!qc->enc_params_len)
goto err;
ret = CO_AC_DONE;
done:
if (status)
*status = ret;
return qc ? qc->conn : NULL;
err:
ret = CO_AC_PAUSE;
goto done;
}
/* Function called on a read event from a listening socket. It tries
* to handle as many connections as possible.
*/
void quic_sock_fd_iocb(int fd)
{
ssize_t ret;
struct buffer *buf;
struct listener *l = objt_listener(fdtab[fd].owner);
/* Source address */
struct sockaddr_storage saddr = {0};
socklen_t saddrlen;
if (!l)
ABORT_NOW();
if (!(fdtab[fd].state & FD_POLL_IN) || !fd_recv_ready(fd))
return;
buf = get_trash_chunk();
saddrlen = sizeof saddr;
do {
ret = recvfrom(fd, buf->area, buf->size, 0,
(struct sockaddr *)&saddr, &saddrlen);
if (ret < 0) {
if (errno == EINTR)
continue;
if (errno == EAGAIN)
fd_cant_recv(fd);
return;
}
} while (0);
buf->data = ret;
quic_lstnr_dgram_read(buf->area, buf->data, l, &saddr);
}