Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion lib/core-net/client/connect2.c
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,11 @@ lws_client_connect_2_dnsreq_MAY_CLOSE_WSI(struct lws *wsi)


#if defined(LWS_WITH_TLS)
/* we will skip HTTPS DNS query on this wsi to prevent double binding */
if (wsi->tls.use_ssl & LCCSCF_USE_SSL) {
lws_async_dns_query(wsi->a.context, wsi->tsi, adsin,
LWS_ADNS_RECORD_HTTPS, lws_client_connect_3_https_cb,
NULL, wsi, NULL);
}
#endif
n = lws_async_dns_query(wsi->a.context, wsi->tsi, adsin,
LWS_ADNS_RECORD_A, lws_client_connect_3_connect,
Expand Down
129 changes: 118 additions & 11 deletions lib/core-net/client/connect3.c
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,58 @@ lws_client_happy_eyeballs_cb(lws_sorted_usec_list_t *sul)
lws_client_connect_3_connect(wsi, NULL, NULL, 0, NULL);
}

void
lws_client_h3_grace_cb(lws_sorted_usec_list_t *sul)
{
struct lws *wsi = lws_container_of(sul, struct lws, sul_h3_grace);

lwsl_wsi_notice(wsi, "H3 grace timer expired, abandoning QUIC race");

/* Mark H3 as FAILED in cache with 5s TTL */
if (wsi->a.context->h3_cap_cache && wsi->stash && wsi->stash->cis[CIS_HOST]) {
lws_h3_state_t state = LWS_H3_STATE_FAILED_IGNORE;
lws_cache_write_through(wsi->a.context->h3_cap_cache, wsi->stash->cis[CIS_HOST],
(const uint8_t *)&state, sizeof(state),
lws_now_usecs() + (5000000ll), NULL);
}

/* Abort QUIC and revert to TCP */
if (wsi->role_ops && strcmp(wsi->role_ops->name, "quic") == 0) {
if (lws_socket_is_valid(wsi->desc.sockfd)) {
struct lws_context_per_thread *pt = &wsi->a.context->pt[(int)wsi->tsi];
lws_pt_lock(pt, __func__);
__remove_wsi_socket_from_fds(wsi);
lws_pt_unlock(pt);
compatible_close(wsi->desc.sockfd);
wsi->desc.sockfd = LWS_SOCK_INVALID;
}

const struct lws_role_ops *r = lws_role_by_name("h2");
if (!r) r = lws_role_by_name("h1");
if (r) {
lws_role_transition(wsi, LWSIFR_CLIENT, LRS_WAITING_CONNECT, r);
}

/* Promote the first parallel TCP connection */
int first_valid = -1;
for (int i = 0; i < wsi->parallel_count; i++) {
if (wsi->parallel_conns[i].is_valid) {
first_valid = i;
break;
}
}
if (first_valid != -1) {
wsi->desc.sockfd = wsi->parallel_conns[first_valid].desc.sockfd;
wsi->position_in_fds_table = wsi->parallel_conns[first_valid].position_in_fds_table;
wsi->parallel_conns[first_valid].is_valid = 0;
/* We changed the primary fd, the event loop will trigger POLLOUT if it's connected */
} else {
/* No TCP sockets survived? Fail connection. */
lws_client_connect_3_connect(wsi, NULL, NULL, 0, NULL);
}
}
}

void
lws_client_dns_retry_timeout(lws_sorted_usec_list_t *sul)
{
Expand Down Expand Up @@ -229,6 +281,21 @@ promote_parallel_fd(struct lws *wsi, int pidx)
}
#endif

struct lws *
lws_client_connect_3_https_cb(struct lws *wsi, const char *ads,
const struct addrinfo *result, int n, void *opaque)
{
struct lws *real_wsi = (struct lws *)opaque;
if (n == 0 && result && real_wsi->a.context->h3_cap_cache) {
lws_h3_state_t state = LWS_H3_STATE_HTTPS_RECORD_EXISTS;
/* Cache the capability with a 1 hour TTL */
lws_cache_write_through(real_wsi->a.context->h3_cap_cache, ads,
(const uint8_t *)&state, sizeof(state),
lws_now_usecs() + (3600ll * LWS_US_PER_SEC), NULL);
}
return real_wsi;
}

struct lws *
lws_client_connect_3_connect(struct lws *wsi, const char *ads,
const struct addrinfo *result, int n, void *opaque)
Expand Down Expand Up @@ -323,9 +390,10 @@ lws_client_connect_3_connect(struct lws *wsi, const char *ads,
struct lws_pollfd *pollfd = (struct lws_pollfd *)opaque;
lws_sockfd_type check_fd = pollfd ? pollfd->fd : LWS_SOCK_INVALID;

if (lwsi_state(wsi) == LRS_WAITING_CONNECT &&
int is_quic_race = (wsi->role_ops && !strcmp(wsi->role_ops->name, "quic") && wsi->sul_h3_grace.list.owner);
if ((lwsi_state(wsi) == LRS_WAITING_CONNECT || is_quic_race) &&
(lws_socket_is_valid(wsi->desc.sockfd) || wsi->parallel_count > 0)) {
if (!wsi->sul_connect_timeout.list.owner)
if (lwsi_state(wsi) == LRS_WAITING_CONNECT && !wsi->sul_connect_timeout.list.owner)
/* no ongoing timeout for one */
goto connect_to;

Expand All @@ -343,6 +411,19 @@ lws_client_connect_3_connect(struct lws *wsi, const char *ads,

switch (lws_client_connect_check(wsi, check_fd, &real_errno)) {
case LCCCR_CONNECTED:
if (is_quic_race && pidx != -1) {
int saved_pos_tmp = wsi->position_in_fds_table;
lws_sock_file_fd_type saved_fd_tmp = wsi->desc;
lwsl_wsi_notice(wsi, "TCP connected, waiting for QUIC grace");
wsi->desc.sockfd = wsi->parallel_conns[pidx].desc.sockfd;
wsi->position_in_fds_table = wsi->parallel_conns[pidx].position_in_fds_table;
if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) {
/* ignore */
}
wsi->desc = saved_fd_tmp;
wsi->position_in_fds_table = saved_pos_tmp;
return NULL;
}
lws_sul_cancel(&wsi->sul_happy_eyeballs);
if (pidx != -1)
promote_parallel_fd(wsi, pidx);
Expand Down Expand Up @@ -496,13 +577,13 @@ lws_client_connect_3_connect(struct lws *wsi, const char *ads,
#endif
{
af = wsi->sa46_peer.sa4.sin_family;
int want_udp = 0;
#if defined(LWS_WITH_UDP)
new_fd = socket(wsi->sa46_peer.sa4.sin_family,
(wsi->udp || !strcmp(wsi->role_ops->name, "quic")) ? SOCK_DGRAM : SOCK_STREAM, 0);
want_udp = wsi->udp || (wsi->role_ops && !strcmp(wsi->role_ops->name, "quic") && !is_parallel);
#else
new_fd = socket(wsi->sa46_peer.sa4.sin_family,
(!strcmp(wsi->role_ops->name, "quic")) ? SOCK_DGRAM : SOCK_STREAM, 0);
want_udp = (wsi->role_ops && !strcmp(wsi->role_ops->name, "quic") && !is_parallel);
#endif
new_fd = socket(wsi->sa46_peer.sa4.sin_family, want_udp ? SOCK_DGRAM : SOCK_STREAM, 0);
}

if (!lws_socket_is_valid(new_fd)) {
Expand Down Expand Up @@ -871,6 +952,27 @@ lws_client_connect_3_connect(struct lws *wsi, const char *ads,
#endif

return wsi;
} else if (!is_parallel && wsi->role_ops && !strcmp(wsi->role_ops->name, "quic")) {
/* QUIC connect immediately succeeds. Schedule grace and happy eyeballs. */
uint32_t grace_us = 200000;
if (wsi->a.context->h3_cap_cache && wsi->stash && wsi->stash->cis[CIS_HOST]) {
const void *item = NULL;
size_t item_len = 0;
if (!lws_cache_item_get(wsi->a.context->h3_cap_cache, wsi->stash->cis[CIS_HOST], &item, &item_len)) {
lws_h3_state_t state = *(lws_h3_state_t *)item;
if (state == LWS_H3_STATE_KNOWN_GOOD || state == LWS_H3_STATE_HTTPS_RECORD_EXISTS)
grace_us = 3000000;
}
}
lwsl_wsi_notice(wsi, "QUIC socket created, starting grace timer %uus", (unsigned int)grace_us);
lws_sul_schedule(wsi->a.context, wsi->tsi, &wsi->sul_h3_grace,
lws_client_h3_grace_cb, grace_us);

if (wsi->dns_sorted_list.count) {
extern void lws_client_happy_eyeballs_cb(lws_sorted_usec_list_t *sul);
lws_sul_schedule(wsi->a.context, wsi->tsi, &wsi->sul_happy_eyeballs,
lws_client_happy_eyeballs_cb, 1);
}
}

conn_good:
Expand All @@ -891,13 +993,18 @@ lws_client_connect_3_connect(struct lws *wsi, const char *ads,
}

#if defined(LWS_WITH_CLIENT)
/* kill all remaining parallel connections */
for (int i = 0; i < wsi->parallel_count; i++) {
if (wsi->parallel_conns[i].is_valid) {
lws_remove_parallel_fd_safely(wsi, i);
int is_quic_race = (wsi->role_ops && !strcmp(wsi->role_ops->name, "quic") && wsi->sul_h3_grace.list.owner);
if (!is_quic_race) {
/* kill all remaining parallel connections */
for (int i = 0; i < wsi->parallel_count; i++) {
if (wsi->parallel_conns[i].is_valid) {
lws_remove_parallel_fd_safely(wsi, i);
}
}
wsi->parallel_count = 0;
} else {
lwsl_wsi_notice(wsi, "QUIC reached conn_good, keeping %d parallel TCP sockets alive", wsi->parallel_count);
}
wsi->parallel_count = 0;
#endif

/*
Expand Down
5 changes: 5 additions & 0 deletions lib/core-net/private-lib-core-net.h
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,7 @@ struct lws_context_per_thread {
lws_sorted_usec_list_t sul_peer_limits;
#endif


#if !defined(LWS_PLAT_FREERTOS)
struct lws *fake_wsi; /* used for callbacks where there's no wsi */
#endif
Expand Down Expand Up @@ -875,6 +876,7 @@ struct lws {

struct lws_client_parallel_conn parallel_conns[LWS_MAX_PARALLEL_CONNS];
lws_sorted_usec_list_t sul_happy_eyeballs;
lws_sorted_usec_list_t sul_h3_grace;
uint8_t parallel_count;


Expand Down Expand Up @@ -1017,6 +1019,9 @@ struct lws {
char tsi; /* thread service index we belong to */
char protocol_interpret_idx;
char redirects;
#if defined(LWS_WITH_CLIENT)
char conn_race_log[32];
#endif
uint8_t rxflow_bitmap;
uint8_t bound_vhost_index;
uint8_t lsp_channel; /* which of stdin/out/err */
Expand Down
8 changes: 8 additions & 0 deletions lib/core-net/service.c
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,14 @@ lws_handle_POLLOUT_event(struct lws *wsi, struct lws_pollfd *pollfd)
if (wsi->socket_is_permanently_unusable)
return 0;

#if defined(LWS_WITH_CLIENT)
/* Intercept POLLOUT for parallel sockets if we are racing H3 */
if (pollfd && pollfd->fd != wsi->desc.sockfd) {
lws_client_connect_3_connect(wsi, NULL, NULL, 0, pollfd);
return 0;
}
#endif

vwsi->leave_pollout_active = 0;
vwsi->handling_pollout = 1;
/*
Expand Down
12 changes: 12 additions & 0 deletions lib/core/context.c
Original file line number Diff line number Diff line change
Expand Up @@ -1622,6 +1622,17 @@ lws_create_context(const struct lws_context_creation_info *info)
lwsl_cx_err(context, "Failed to init ALPN cache");
goto bail;
}

cci.name = "H3CAP";
cci.max_footprint = 4096;
cci.max_items = 256;
cci.max_payload = 8; /* Just an enum */

context->h3_cap_cache = lws_cache_create(&cci);
if (!context->h3_cap_cache) {
lwsl_cx_err(context, "Failed to init H3CAP cache");
goto bail;
}
}
#endif

Expand Down Expand Up @@ -2455,6 +2466,7 @@ lws_context_destroy(struct lws_context *context)

#if defined(LWS_WITH_CLIENT)
lws_cache_destroy(&context->alpn_cache);
lws_cache_destroy(&context->h3_cap_cache);
#endif


Expand Down
8 changes: 8 additions & 0 deletions lib/core/private-lib-core.h
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,13 @@ typedef struct lws_buflist {
void *heap_alloc;
} lws_buflist_t;

typedef enum {
LWS_H3_STATE_UNKNOWN,
LWS_H3_STATE_HTTPS_RECORD_EXISTS,
LWS_H3_STATE_ALTSVC_EXISTS,
LWS_H3_STATE_KNOWN_GOOD,
LWS_H3_STATE_FAILED_IGNORE
} lws_h3_state_t;

/*
* the rest is managed per-context, that includes
Expand Down Expand Up @@ -588,6 +595,7 @@ struct lws_context {

#if defined(LWS_WITH_CLIENT)
struct lws_cache_ttl_lru *alpn_cache;
struct lws_cache_ttl_lru *h3_cap_cache;
#endif

#if defined(LWS_WITH_SYS_NTPCLIENT)
Expand Down
4 changes: 4 additions & 0 deletions lib/roles/private-lib-roles.h
Original file line number Diff line number Diff line change
Expand Up @@ -457,3 +457,7 @@ lws_client_connect_4_established(struct lws *wsi, struct lws *wsi_piggyback,
struct lws *
lws_client_connect_3_connect(struct lws *wsi, const char *ads,
const struct addrinfo *result, int n, void *opaque);

struct lws *
lws_client_connect_3_https_cb(struct lws *wsi, const char *ads,
const struct addrinfo *result, int n, void *opaque);
34 changes: 33 additions & 1 deletion lib/roles/quic/ops-quic.c
Original file line number Diff line number Diff line change
Expand Up @@ -1038,7 +1038,15 @@ rops_handle_POLLOUT_quic(struct lws *wsi)

#if defined(LWS_WITH_TLS)
if (wsi->tls.use_ssl & LCCSCF_USE_SSL) {
/* The BIO was already created in connect.c, just init QUIC TLS */
if (!wsi->tls.ssl) {
const char *cce = NULL;
if (lws_client_create_tls(wsi, &cce, 0) == CCTLS_RETURN_ERROR) {
lwsl_wsi_err(wsi, "Failed to create TLS BIO: %s", cce ? cce : "unknown");
return LWS_HP_RET_BAIL_DIE;
}
}

/* The BIO was already created, just init QUIC TLS */
if (lws_tls_quic_init(wsi, quic_secret_cb)) {
lwsl_wsi_err(wsi, "Failed to init QUIC TLS");
return LWS_HP_RET_BAIL_DIE;
Expand Down Expand Up @@ -2363,6 +2371,30 @@ rops_alpn_negotiated_quic(struct lws *wsi, const char *alpn)
#endif
nwsi->quic.qn->alpn_migrated = 1;

#if defined(LWS_WITH_CLIENT)
/*
* QUIC succeeded! Resolve the race by cancelling the grace timer
* and killing parallel TCP connections.
*/
if (lwsi_role_client(wsi)) {
lws_sul_cancel(&wsi->sul_h3_grace);

for (int i = 0; i < wsi->parallel_count; i++) {
if (wsi->parallel_conns[i].is_valid) {
lws_remove_parallel_fd_safely(wsi, i);
}
}
wsi->parallel_count = 0;

if (wsi->a.context->h3_cap_cache && wsi->stash && wsi->stash->cis[CIS_HOST]) {
lws_h3_state_t state = LWS_H3_STATE_KNOWN_GOOD;
lws_cache_write_through(wsi->a.context->h3_cap_cache, wsi->stash->cis[CIS_HOST],
(const uint8_t *)&state, sizeof(state),
lws_now_usecs() + (3600ll * LWS_US_PER_SEC), NULL);
}
}
#endif

/*
* The quic child stream is migrating to be a child of nwsi.
* So we disconnect it from the listener socket first.
Expand Down
18 changes: 12 additions & 6 deletions lib/system/async-dns/async-dns.c
Original file line number Diff line number Diff line change
Expand Up @@ -1250,16 +1250,22 @@ lws_async_dns_query(struct lws_context *context, int tsi, const char *name,

c = (qtype & LWS_ADNS_NOCACHE) ? NULL : lws_adns_get_cache(dns, name);
qtype = (adns_query_type_t)(qtype & ~(uint32_t)LWS_ADNS_NOCACHE);
if (c && qtype != LWS_ADNS_RECORD_A && qtype != LWS_ADNS_RECORD_AAAA) {
lws_adns_rr_t *rr = c->rr_results;
if (c) {
int found = 0;
while (rr) {
if (rr->type == qtype) {
if (qtype == LWS_ADNS_RECORD_A || qtype == LWS_ADNS_RECORD_AAAA) {
if (c->results)
found = 1;
break;
} else {
lws_adns_rr_t *rr = c->rr_results;
while (rr) {
if (rr->type == qtype) {
found = 1;
break;
}
rr = rr->next;
}
rr = rr->next;
}

if (!found) {
lwsl_cx_info(context, "%s: cached but missing 0x%x, bypassing", name, qtype);
lws_dll2_remove(&c->list); /* Remove from cache list, let sul expire it */
Expand Down
Loading
Loading