Index: trunk/src/libs/curl-8.0.1/lib/cf-https-connect.c
===================================================================
--- trunk/src/libs/curl-8.0.1/lib/cf-https-connect.c	(revision 99459)
+++ trunk/src/libs/curl-8.0.1/lib/cf-https-connect.c	(revision 99459)
@@ -0,0 +1,569 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+
+#if !defined(CURL_DISABLE_HTTP) && !defined(USE_HYPER)
+
+#include "urldata.h"
+#include <curl/curl.h>
+#include "curl_log.h"
+#include "cfilters.h"
+#include "connect.h"
+#include "multiif.h"
+#include "cf-https-connect.h"
+#include "http2.h"
+#include "vquic/vquic.h"
+
+/* The last 3 #include files should be in this order */
+#include "curl_printf.h"
+#include "curl_memory.h"
+#include "memdebug.h"
+
+
+typedef enum {
+  CF_HC_INIT,
+  CF_HC_CONNECT,
+  CF_HC_SUCCESS,
+  CF_HC_FAILURE
+} cf_hc_state;
+
+struct cf_hc_baller {
+  const char *name;
+  struct Curl_cfilter *cf;
+  CURLcode result;
+  struct curltime started;
+  int reply_ms;
+  bool enabled;
+};
+
+static void cf_hc_baller_reset(struct cf_hc_baller *b,
+                               struct Curl_easy *data)
+{
+  if(b->cf) {
+    Curl_conn_cf_close(b->cf, data);
+    Curl_conn_cf_discard_chain(&b->cf, data);
+    b->cf = NULL;
+  }
+  b->result = CURLE_OK;
+  b->reply_ms = -1;
+}
+
+static bool cf_hc_baller_is_active(struct cf_hc_baller *b)
+{
+  return b->enabled && b->cf && !b->result;
+}
+
+static bool cf_hc_baller_has_started(struct cf_hc_baller *b)
+{
+  return !!b->cf;
+}
+
+static int cf_hc_baller_reply_ms(struct cf_hc_baller *b,
+                                 struct Curl_easy *data)
+{
+  if(b->reply_ms < 0)
+    b->cf->cft->query(b->cf, data, CF_QUERY_CONNECT_REPLY_MS,
+                      &b->reply_ms, NULL);
+  return b->reply_ms;
+}
+
+static bool cf_hc_baller_data_pending(struct cf_hc_baller *b,
+                                      const struct Curl_easy *data)
+{
+  return b->cf && !b->result && b->cf->cft->has_data_pending(b->cf, data);
+}
+
+struct cf_hc_ctx {
+  cf_hc_state state;
+  const struct Curl_dns_entry *remotehost;
+  struct curltime started;  /* when connect started */
+  CURLcode result;          /* overall result */
+  struct cf_hc_baller h3_baller;
+  struct cf_hc_baller h21_baller;
+  int soft_eyeballs_timeout_ms;
+  int hard_eyeballs_timeout_ms;
+};
+
+static void cf_hc_baller_init(struct cf_hc_baller *b,
+                              struct Curl_cfilter *cf,
+                              struct Curl_easy *data,
+                              const char *name,
+                              int transport)
+{
+  struct cf_hc_ctx *ctx = cf->ctx;
+  struct Curl_cfilter *save = cf->next;
+
+  b->name = name;
+  cf->next = NULL;
+  b->started = Curl_now();
+  b->result = Curl_cf_setup_insert_after(cf, data, ctx->remotehost,
+                                         transport, CURL_CF_SSL_ENABLE);
+  b->cf = cf->next;
+  cf->next = save;
+}
+
+static CURLcode cf_hc_baller_connect(struct cf_hc_baller *b,
+                                     struct Curl_cfilter *cf,
+                                     struct Curl_easy *data,
+                                     bool *done)
+{
+  struct Curl_cfilter *save = cf->next;
+
+  cf->next = b->cf;
+  b->result = Curl_conn_cf_connect(cf->next, data, FALSE, done);
+  b->cf = cf->next; /* it might mutate */
+  cf->next = save;
+  return b->result;
+}
+
+static void cf_hc_reset(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+  struct cf_hc_ctx *ctx = cf->ctx;
+
+  if(ctx) {
+    cf_hc_baller_reset(&ctx->h3_baller, data);
+    cf_hc_baller_reset(&ctx->h21_baller, data);
+    ctx->state = CF_HC_INIT;
+    ctx->result = CURLE_OK;
+    ctx->hard_eyeballs_timeout_ms = data->set.happy_eyeballs_timeout;
+    ctx->soft_eyeballs_timeout_ms = data->set.happy_eyeballs_timeout / 2;
+  }
+}
+
+static CURLcode baller_connected(struct Curl_cfilter *cf,
+                                 struct Curl_easy *data,
+                                 struct cf_hc_baller *winner)
+{
+  struct cf_hc_ctx *ctx = cf->ctx;
+  CURLcode result = CURLE_OK;
+
+  DEBUGASSERT(winner->cf);
+  if(winner != &ctx->h3_baller)
+    cf_hc_baller_reset(&ctx->h3_baller, data);
+  if(winner != &ctx->h21_baller)
+    cf_hc_baller_reset(&ctx->h21_baller, data);
+
+  DEBUGF(LOG_CF(data, cf, "connect+handshake %s: %dms, 1st data: %dms",
+                winner->name, (int)Curl_timediff(Curl_now(), winner->started),
+                cf_hc_baller_reply_ms(winner, data)));
+  cf->next = winner->cf;
+  winner->cf = NULL;
+
+  switch(cf->conn->alpn) {
+  case CURL_HTTP_VERSION_3:
+    infof(data, "using HTTP/3");
+    break;
+  case CURL_HTTP_VERSION_2:
+#ifdef USE_NGHTTP2
+    /* Using nghttp2, we add the filter "below" us, so when the conn
+     * closes, we tear it down for a fresh reconnect */
+    result = Curl_http2_switch_at(cf, data);
+    if(result) {
+      ctx->state = CF_HC_FAILURE;
+      ctx->result = result;
+      return result;
+    }
+#endif
+    infof(data, "using HTTP/2");
+    break;
+  case CURL_HTTP_VERSION_1_1:
+    infof(data, "using HTTP/1.1");
+    break;
+  default:
+    infof(data, "using HTTP/1.x");
+    break;
+  }
+  ctx->state = CF_HC_SUCCESS;
+  cf->connected = TRUE;
+  Curl_conn_cf_cntrl(cf->next, data, TRUE,
+                     CF_CTRL_CONN_INFO_UPDATE, 0, NULL);
+  return result;
+}
+
+
+static bool time_to_start_h21(struct Curl_cfilter *cf,
+                              struct Curl_easy *data,
+                              struct curltime now)
+{
+  struct cf_hc_ctx *ctx = cf->ctx;
+  timediff_t elapsed_ms;
+
+  if(!ctx->h21_baller.enabled || cf_hc_baller_has_started(&ctx->h21_baller))
+    return FALSE;
+
+  if(!ctx->h3_baller.enabled || !cf_hc_baller_is_active(&ctx->h3_baller))
+    return TRUE;
+
+  elapsed_ms = Curl_timediff(now, ctx->started);
+  if(elapsed_ms >= ctx->hard_eyeballs_timeout_ms) {
+    DEBUGF(LOG_CF(data, cf, "hard timeout of %dms reached, starting h21",
+                  ctx->hard_eyeballs_timeout_ms));
+    return TRUE;
+  }
+
+  if(elapsed_ms >= ctx->soft_eyeballs_timeout_ms) {
+    if(cf_hc_baller_reply_ms(&ctx->h3_baller, data) < 0) {
+      DEBUGF(LOG_CF(data, cf, "soft timeout of %dms reached, h3 has not "
+                    "seen any data, starting h21",
+                    ctx->soft_eyeballs_timeout_ms));
+      return TRUE;
+    }
+    /* set the effective hard timeout again */
+    Curl_expire(data, ctx->hard_eyeballs_timeout_ms - elapsed_ms,
+                EXPIRE_ALPN_EYEBALLS);
+  }
+  return FALSE;
+}
+
+static CURLcode cf_hc_connect(struct Curl_cfilter *cf,
+                              struct Curl_easy *data,
+                              bool blocking, bool *done)
+{
+  struct cf_hc_ctx *ctx = cf->ctx;
+  struct curltime now;
+  CURLcode result = CURLE_OK;
+
+  (void)blocking;
+  if(cf->connected) {
+    *done = TRUE;
+    return CURLE_OK;
+  }
+
+  *done = FALSE;
+  now = Curl_now();
+  switch(ctx->state) {
+  case CF_HC_INIT:
+    DEBUGASSERT(!ctx->h3_baller.cf);
+    DEBUGASSERT(!ctx->h21_baller.cf);
+    DEBUGASSERT(!cf->next);
+    DEBUGF(LOG_CF(data, cf, "connect, init"));
+    ctx->started = now;
+    if(ctx->h3_baller.enabled) {
+      cf_hc_baller_init(&ctx->h3_baller, cf, data, "h3", TRNSPRT_QUIC);
+      if(ctx->h21_baller.enabled)
+        Curl_expire(data, ctx->soft_eyeballs_timeout_ms, EXPIRE_ALPN_EYEBALLS);
+    }
+    else if(ctx->h21_baller.enabled)
+      cf_hc_baller_init(&ctx->h21_baller, cf, data, "h21",
+                       cf->conn->transport);
+    ctx->state = CF_HC_CONNECT;
+    /* FALLTHROUGH */
+
+  case CF_HC_CONNECT:
+    if(cf_hc_baller_is_active(&ctx->h3_baller)) {
+      result = cf_hc_baller_connect(&ctx->h3_baller, cf, data, done);
+      if(!result && *done) {
+        result = baller_connected(cf, data, &ctx->h3_baller);
+        goto out;
+      }
+    }
+
+    if(time_to_start_h21(cf, data, now)) {
+      cf_hc_baller_init(&ctx->h21_baller, cf, data, "h21",
+                        cf->conn->transport);
+    }
+
+    if(cf_hc_baller_is_active(&ctx->h21_baller)) {
+      DEBUGF(LOG_CF(data, cf, "connect, check h21"));
+      result = cf_hc_baller_connect(&ctx->h21_baller, cf, data, done);
+      if(!result && *done) {
+        result = baller_connected(cf, data, &ctx->h21_baller);
+        goto out;
+      }
+    }
+
+    if((!ctx->h3_baller.enabled || ctx->h3_baller.result) &&
+       (!ctx->h21_baller.enabled || ctx->h21_baller.result)) {
+      /* both failed or disabled. we give up */
+      DEBUGF(LOG_CF(data, cf, "connect, all failed"));
+      result = ctx->result = ctx->h3_baller.enabled?
+                              ctx->h3_baller.result : ctx->h21_baller.result;
+      ctx->state = CF_HC_FAILURE;
+      goto out;
+    }
+    result = CURLE_OK;
+    *done = FALSE;
+    break;
+
+  case CF_HC_FAILURE:
+    result = ctx->result;
+    cf->connected = FALSE;
+    *done = FALSE;
+    break;
+
+  case CF_HC_SUCCESS:
+    result = CURLE_OK;
+    cf->connected = TRUE;
+    *done = TRUE;
+    break;
+  }
+
+out:
+  DEBUGF(LOG_CF(data, cf, "connect -> %d, done=%d", result, *done));
+  return result;
+}
+
+static int cf_hc_get_select_socks(struct Curl_cfilter *cf,
+                                  struct Curl_easy *data,
+                                  curl_socket_t *socks)
+{
+  struct cf_hc_ctx *ctx = cf->ctx;
+  size_t i, j, s;
+  int brc, rc = GETSOCK_BLANK;
+  curl_socket_t bsocks[MAX_SOCKSPEREASYHANDLE];
+  struct cf_hc_baller *ballers[2];
+
+  if(cf->connected)
+    return cf->next->cft->get_select_socks(cf->next, data, socks);
+
+  ballers[0] = &ctx->h3_baller;
+  ballers[1] = &ctx->h21_baller;
+  for(i = s = 0; i < sizeof(ballers)/sizeof(ballers[0]); i++) {
+    struct cf_hc_baller *b = ballers[i];
+    if(!cf_hc_baller_is_active(b))
+      continue;
+    brc = Curl_conn_cf_get_select_socks(b->cf, data, bsocks);
+    DEBUGF(LOG_CF(data, cf, "get_selected_socks(%s) -> %x", b->name, brc));
+    if(!brc)
+      continue;
+    for(j = 0; j < MAX_SOCKSPEREASYHANDLE && s < MAX_SOCKSPEREASYHANDLE; ++j) {
+      if((brc & GETSOCK_WRITESOCK(j)) || (brc & GETSOCK_READSOCK(j))) {
+        socks[s] = bsocks[j];
+        if(brc & GETSOCK_WRITESOCK(j))
+          rc |= GETSOCK_WRITESOCK(s);
+        if(brc & GETSOCK_READSOCK(j))
+          rc |= GETSOCK_READSOCK(s);
+        s++;
+      }
+    }
+  }
+  DEBUGF(LOG_CF(data, cf, "get_selected_socks -> %x", rc));
+  return rc;
+}
+
+static bool cf_hc_data_pending(struct Curl_cfilter *cf,
+                               const struct Curl_easy *data)
+{
+  struct cf_hc_ctx *ctx = cf->ctx;
+
+  if(cf->connected)
+    return cf->next->cft->has_data_pending(cf->next, data);
+
+  DEBUGF(LOG_CF((struct Curl_easy *)data, cf, "data_pending"));
+  return cf_hc_baller_data_pending(&ctx->h3_baller, data)
+         || cf_hc_baller_data_pending(&ctx->h21_baller, data);
+}
+
+static struct curltime get_max_baller_time(struct Curl_cfilter *cf,
+                                          struct Curl_easy *data,
+                                          int query)
+{
+  struct cf_hc_ctx *ctx = cf->ctx;
+  struct Curl_cfilter *cfb;
+  struct curltime t, tmax;
+
+  memset(&tmax, 0, sizeof(tmax));
+  memset(&t, 0, sizeof(t));
+  cfb = ctx->h21_baller.enabled? ctx->h21_baller.cf : NULL;
+  if(cfb && !cfb->cft->query(cfb, data, query, NULL, &t)) {
+    if((t.tv_sec || t.tv_usec) && Curl_timediff_us(t, tmax) > 0)
+      tmax = t;
+  }
+  memset(&t, 0, sizeof(t));
+  cfb = ctx->h3_baller.enabled? ctx->h3_baller.cf : NULL;
+  if(cfb && !cfb->cft->query(cfb, data, query, NULL, &t)) {
+    if((t.tv_sec || t.tv_usec) && Curl_timediff_us(t, tmax) > 0)
+      tmax = t;
+  }
+  return tmax;
+}
+
+static CURLcode cf_hc_query(struct Curl_cfilter *cf,
+                            struct Curl_easy *data,
+                            int query, int *pres1, void *pres2)
+{
+  if(!cf->connected) {
+    switch(query) {
+    case CF_QUERY_TIMER_CONNECT: {
+      struct curltime *when = pres2;
+      *when = get_max_baller_time(cf, data, CF_QUERY_TIMER_CONNECT);
+      return CURLE_OK;
+    }
+    case CF_QUERY_TIMER_APPCONNECT: {
+      struct curltime *when = pres2;
+      *when = get_max_baller_time(cf, data, CF_QUERY_TIMER_APPCONNECT);
+      return CURLE_OK;
+    }
+    default:
+      break;
+    }
+  }
+  return cf->next?
+    cf->next->cft->query(cf->next, data, query, pres1, pres2) :
+    CURLE_UNKNOWN_OPTION;
+}
+
+static void cf_hc_close(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+  DEBUGF(LOG_CF(data, cf, "close"));
+  cf_hc_reset(cf, data);
+  cf->connected = FALSE;
+
+  if(cf->next) {
+    cf->next->cft->close(cf->next, data);
+    Curl_conn_cf_discard_chain(&cf->next, data);
+  }
+}
+
+static void cf_hc_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+  struct cf_hc_ctx *ctx = cf->ctx;
+
+  (void)data;
+  DEBUGF(LOG_CF(data, cf, "destroy"));
+  cf_hc_reset(cf, data);
+  Curl_safefree(ctx);
+}
+
+struct Curl_cftype Curl_cft_http_connect = {
+  "HTTPS-CONNECT",
+  0,
+  CURL_LOG_DEFAULT,
+  cf_hc_destroy,
+  cf_hc_connect,
+  cf_hc_close,
+  Curl_cf_def_get_host,
+  cf_hc_get_select_socks,
+  cf_hc_data_pending,
+  Curl_cf_def_send,
+  Curl_cf_def_recv,
+  Curl_cf_def_cntrl,
+  Curl_cf_def_conn_is_alive,
+  Curl_cf_def_conn_keep_alive,
+  cf_hc_query,
+};
+
+static CURLcode cf_hc_create(struct Curl_cfilter **pcf,
+                             struct Curl_easy *data,
+                             const struct Curl_dns_entry *remotehost,
+                             bool try_h3, bool try_h21)
+{
+  struct Curl_cfilter *cf = NULL;
+  struct cf_hc_ctx *ctx;
+  CURLcode result = CURLE_OK;
+
+  (void)data;
+  ctx = calloc(sizeof(*ctx), 1);
+  if(!ctx) {
+    result = CURLE_OUT_OF_MEMORY;
+    goto out;
+  }
+  ctx->remotehost = remotehost;
+  ctx->h3_baller.enabled = try_h3;
+  ctx->h21_baller.enabled = try_h21;
+
+  result = Curl_cf_create(&cf, &Curl_cft_http_connect, ctx);
+  if(result)
+    goto out;
+  ctx = NULL;
+  cf_hc_reset(cf, data);
+
+out:
+  *pcf = result? NULL : cf;
+  free(ctx);
+  return result;
+}
+
+CURLcode Curl_cf_http_connect_add(struct Curl_easy *data,
+                                  struct connectdata *conn,
+                                  int sockindex,
+                                  const struct Curl_dns_entry *remotehost,
+                                  bool try_h3, bool try_h21)
+{
+  struct Curl_cfilter *cf;
+  CURLcode result = CURLE_OK;
+
+  DEBUGASSERT(data);
+  result = cf_hc_create(&cf, data, remotehost, try_h3, try_h21);
+  if(result)
+    goto out;
+  Curl_conn_cf_add(data, conn, sockindex, cf);
+out:
+  return result;
+}
+
+CURLcode
+Curl_cf_http_connect_insert_after(struct Curl_cfilter *cf_at,
+                                  struct Curl_easy *data,
+                                  const struct Curl_dns_entry *remotehost,
+                                  bool try_h3, bool try_h21)
+{
+  struct Curl_cfilter *cf;
+  CURLcode result;
+
+  DEBUGASSERT(data);
+  result = cf_hc_create(&cf, data, remotehost, try_h3, try_h21);
+  if(result)
+    goto out;
+  Curl_conn_cf_insert_after(cf_at, cf);
+out:
+  return result;
+}
+
+CURLcode Curl_cf_https_setup(struct Curl_easy *data,
+                             struct connectdata *conn,
+                             int sockindex,
+                             const struct Curl_dns_entry *remotehost)
+{
+  bool try_h3 = FALSE, try_h21 = TRUE; /* defaults, for now */
+  CURLcode result = CURLE_OK;
+
+  (void)sockindex;
+  (void)remotehost;
+
+  if(!conn->bits.tls_enable_alpn)
+    goto out;
+
+  if(data->state.httpwant == CURL_HTTP_VERSION_3ONLY) {
+    result = Curl_conn_may_http3(data, conn);
+    if(result) /* can't do it */
+      goto out;
+    try_h3 = TRUE;
+    try_h21 = FALSE;
+  }
+  else if(data->state.httpwant >= CURL_HTTP_VERSION_3) {
+    /* We assume that silently not even trying H3 is ok here */
+    /* TODO: should we fail instead? */
+    try_h3 = (Curl_conn_may_http3(data, conn) == CURLE_OK);
+    try_h21 = TRUE;
+  }
+
+  result = Curl_cf_http_connect_add(data, conn, sockindex, remotehost,
+                                    try_h3, try_h21);
+out:
+  return result;
+}
+
+#endif /* !defined(CURL_DISABLE_HTTP) && !defined(USE_HYPER) */
Index: trunk/src/libs/curl-8.0.1/lib/cf-https-connect.h
===================================================================
--- trunk/src/libs/curl-8.0.1/lib/cf-https-connect.h	(revision 99459)
+++ trunk/src/libs/curl-8.0.1/lib/cf-https-connect.h	(revision 99459)
@@ -0,0 +1,58 @@
+#ifndef HEADER_CURL_CF_HTTP_H
+#define HEADER_CURL_CF_HTTP_H
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+#include "curl_setup.h"
+
+#if !defined(CURL_DISABLE_HTTP) && !defined(USE_HYPER)
+
+struct Curl_cfilter;
+struct Curl_easy;
+struct connectdata;
+struct Curl_cftype;
+struct Curl_dns_entry;
+
+extern struct Curl_cftype Curl_cft_http_connect;
+
+CURLcode Curl_cf_http_connect_add(struct Curl_easy *data,
+                                  struct connectdata *conn,
+                                  int sockindex,
+                                  const struct Curl_dns_entry *remotehost,
+                                  bool try_h3, bool try_h21);
+
+CURLcode
+Curl_cf_http_connect_insert_after(struct Curl_cfilter *cf_at,
+                                  struct Curl_easy *data,
+                                  const struct Curl_dns_entry *remotehost,
+                                  bool try_h3, bool try_h21);
+
+
+CURLcode Curl_cf_https_setup(struct Curl_easy *data,
+                             struct connectdata *conn,
+                             int sockindex,
+                             const struct Curl_dns_entry *remotehost);
+
+
+#endif /* !defined(CURL_DISABLE_HTTP) && !defined(USE_HYPER) */
+#endif /* HEADER_CURL_CF_HTTP_H */
Index: trunk/src/libs/curl-8.0.1/lib/cf-socket.c
===================================================================
--- trunk/src/libs/curl-8.0.1/lib/cf-socket.c	(revision 99459)
+++ trunk/src/libs/curl-8.0.1/lib/cf-socket.c	(revision 99459)
@@ -0,0 +1,1921 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h> /* <netinet/tcp.h> may need it */
+#endif
+#ifdef HAVE_SYS_UN_H
+#include <sys/un.h> /* for sockaddr_un */
+#endif
+#ifdef HAVE_LINUX_TCP_H
+#include <linux/tcp.h>
+#elif defined(HAVE_NETINET_TCP_H)
+#include <netinet/tcp.h>
+#endif
+#ifdef HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif
+#ifdef HAVE_NETDB_H
+#include <netdb.h>
+#endif
+#ifdef HAVE_FCNTL_H
+#include <fcntl.h>
+#endif
+#ifdef HAVE_ARPA_INET_H
+#include <arpa/inet.h>
+#endif
+
+#ifdef __VMS
+#include <in.h>
+#include <inet.h>
+#endif
+
+#include "urldata.h"
+#include "sendf.h"
+#include "if2ip.h"
+#include "strerror.h"
+#include "cfilters.h"
+#include "cf-socket.h"
+#include "connect.h"
+#include "select.h"
+#include "url.h" /* for Curl_safefree() */
+#include "multiif.h"
+#include "sockaddr.h" /* required for Curl_sockaddr_storage */
+#include "inet_ntop.h"
+#include "inet_pton.h"
+#include "progress.h"
+#include "warnless.h"
+#include "conncache.h"
+#include "multihandle.h"
+#include "share.h"
+#include "version_win32.h"
+
+/* The last 3 #include files should be in this order */
+#include "curl_printf.h"
+#include "curl_memory.h"
+#include "memdebug.h"
+
+
+static void tcpnodelay(struct Curl_easy *data, curl_socket_t sockfd)
+{
+#if defined(TCP_NODELAY)
+  curl_socklen_t onoff = (curl_socklen_t) 1;
+  int level = IPPROTO_TCP;
+#if !defined(CURL_DISABLE_VERBOSE_STRINGS)
+  char buffer[STRERROR_LEN];
+#else
+  (void) data;
+#endif
+
+  if(setsockopt(sockfd, level, TCP_NODELAY, (void *)&onoff,
+                sizeof(onoff)) < 0)
+    infof(data, "Could not set TCP_NODELAY: %s",
+          Curl_strerror(SOCKERRNO, buffer, sizeof(buffer)));
+#else
+  (void)data;
+  (void)sockfd;
+#endif
+}
+
+#ifdef SO_NOSIGPIPE
+/* The preferred method on Mac OS X (10.2 and later) to prevent SIGPIPEs when
+   sending data to a dead peer (instead of relying on the 4th argument to send
+   being MSG_NOSIGNAL). Possibly also existing and in use on other BSD
+   systems? */
+static void nosigpipe(struct Curl_easy *data,
+                      curl_socket_t sockfd)
+{
+  int onoff = 1;
+  if(setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, (void *)&onoff,
+                sizeof(onoff)) < 0) {
+#if !defined(CURL_DISABLE_VERBOSE_STRINGS)
+    char buffer[STRERROR_LEN];
+    infof(data, "Could not set SO_NOSIGPIPE: %s",
+          Curl_strerror(SOCKERRNO, buffer, sizeof(buffer)));
+#endif
+  }
+}
+#else
+#define nosigpipe(x,y) Curl_nop_stmt
+#endif
+
+#if defined(__DragonFly__) || defined(HAVE_WINSOCK2_H)
+/* DragonFlyBSD and Windows use millisecond units */
+#define KEEPALIVE_FACTOR(x) (x *= 1000)
+#else
+#define KEEPALIVE_FACTOR(x)
+#endif
+
+#if defined(HAVE_WINSOCK2_H) && !defined(SIO_KEEPALIVE_VALS)
+#define SIO_KEEPALIVE_VALS    _WSAIOW(IOC_VENDOR,4)
+
+struct tcp_keepalive {
+  u_long onoff;
+  u_long keepalivetime;
+  u_long keepaliveinterval;
+};
+#endif
+
+static void
+tcpkeepalive(struct Curl_easy *data,
+             curl_socket_t sockfd)
+{
+  int optval = data->set.tcp_keepalive?1:0;
+
+  /* only set IDLE and INTVL if setting KEEPALIVE is successful */
+  if(setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE,
+        (void *)&optval, sizeof(optval)) < 0) {
+    infof(data, "Failed to set SO_KEEPALIVE on fd %d", sockfd);
+  }
+  else {
+#if defined(SIO_KEEPALIVE_VALS)
+    struct tcp_keepalive vals;
+    DWORD dummy;
+    vals.onoff = 1;
+    optval = curlx_sltosi(data->set.tcp_keepidle);
+    KEEPALIVE_FACTOR(optval);
+    vals.keepalivetime = optval;
+    optval = curlx_sltosi(data->set.tcp_keepintvl);
+    KEEPALIVE_FACTOR(optval);
+    vals.keepaliveinterval = optval;
+    if(WSAIoctl(sockfd, SIO_KEEPALIVE_VALS, (LPVOID) &vals, sizeof(vals),
+                NULL, 0, &dummy, NULL, NULL) != 0) {
+      infof(data, "Failed to set SIO_KEEPALIVE_VALS on fd %d: %d",
+            (int)sockfd, WSAGetLastError());
+    }
+#else
+#ifdef TCP_KEEPIDLE
+    optval = curlx_sltosi(data->set.tcp_keepidle);
+    KEEPALIVE_FACTOR(optval);
+    if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPIDLE,
+          (void *)&optval, sizeof(optval)) < 0) {
+      infof(data, "Failed to set TCP_KEEPIDLE on fd %d", sockfd);
+    }
+#elif defined(TCP_KEEPALIVE)
+    /* Mac OS X style */
+    optval = curlx_sltosi(data->set.tcp_keepidle);
+    KEEPALIVE_FACTOR(optval);
+    if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPALIVE,
+      (void *)&optval, sizeof(optval)) < 0) {
+      infof(data, "Failed to set TCP_KEEPALIVE on fd %d", sockfd);
+    }
+#endif
+#ifdef TCP_KEEPINTVL
+    optval = curlx_sltosi(data->set.tcp_keepintvl);
+    KEEPALIVE_FACTOR(optval);
+    if(setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPINTVL,
+          (void *)&optval, sizeof(optval)) < 0) {
+      infof(data, "Failed to set TCP_KEEPINTVL on fd %d", sockfd);
+    }
+#endif
+#endif
+  }
+}
+
+void Curl_sock_assign_addr(struct Curl_sockaddr_ex *dest,
+                           const struct Curl_addrinfo *ai,
+                           int transport)
+{
+  /*
+   * The Curl_sockaddr_ex structure is basically libcurl's external API
+   * curl_sockaddr structure with enough space available to directly hold
+   * any protocol-specific address structures. The variable declared here
+   * will be used to pass / receive data to/from the fopensocket callback
+   * if this has been set, before that, it is initialized from parameters.
+   */
+  dest->family = ai->ai_family;
+  switch(transport) {
+  case TRNSPRT_TCP:
+    dest->socktype = SOCK_STREAM;
+    dest->protocol = IPPROTO_TCP;
+    break;
+  case TRNSPRT_UNIX:
+    dest->socktype = SOCK_STREAM;
+    dest->protocol = IPPROTO_IP;
+    break;
+  default: /* UDP and QUIC */
+    dest->socktype = SOCK_DGRAM;
+    dest->protocol = IPPROTO_UDP;
+    break;
+  }
+  dest->addrlen = ai->ai_addrlen;
+
+  if(dest->addrlen > sizeof(struct Curl_sockaddr_storage))
+     dest->addrlen = sizeof(struct Curl_sockaddr_storage);
+  memcpy(&dest->sa_addr, ai->ai_addr, dest->addrlen);
+}
+
+static CURLcode socket_open(struct Curl_easy *data,
+                            struct Curl_sockaddr_ex *addr,
+                            curl_socket_t *sockfd)
+{
+  DEBUGASSERT(data);
+  DEBUGASSERT(data->conn);
+  if(data->set.fopensocket) {
+   /*
+    * If the opensocket callback is set, all the destination address
+    * information is passed to the callback. Depending on this information the
+    * callback may opt to abort the connection, this is indicated returning
+    * CURL_SOCKET_BAD; otherwise it will return a not-connected socket. When
+    * the callback returns a valid socket the destination address information
+    * might have been changed and this 'new' address will actually be used
+    * here to connect.
+    */
+    Curl_set_in_callback(data, true);
+    *sockfd = data->set.fopensocket(data->set.opensocket_client,
+                                    CURLSOCKTYPE_IPCXN,
+                                    (struct curl_sockaddr *)addr);
+    Curl_set_in_callback(data, false);
+  }
+  else {
+    /* opensocket callback not set, so simply create the socket now */
+    *sockfd = socket(addr->family, addr->socktype, addr->protocol);
+  }
+
+  if(*sockfd == CURL_SOCKET_BAD)
+    /* no socket, no connection */
+    return CURLE_COULDNT_CONNECT;
+
+#if defined(ENABLE_IPV6) && defined(HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID)
+  if(data->conn->scope_id && (addr->family == AF_INET6)) {
+    struct sockaddr_in6 * const sa6 = (void *)&addr->sa_addr;
+    sa6->sin6_scope_id = data->conn->scope_id;
+  }
+#endif
+  return CURLE_OK;
+}
+
+/*
+ * Create a socket based on info from 'conn' and 'ai'.
+ *
+ * 'addr' should be a pointer to the correct struct to get data back, or NULL.
+ * 'sockfd' must be a pointer to a socket descriptor.
+ *
+ * If the open socket callback is set, used that!
+ *
+ */
+CURLcode Curl_socket_open(struct Curl_easy *data,
+                            const struct Curl_addrinfo *ai,
+                            struct Curl_sockaddr_ex *addr,
+                            int transport,
+                            curl_socket_t *sockfd)
+{
+  struct Curl_sockaddr_ex dummy;
+
+  if(!addr)
+    /* if the caller doesn't want info back, use a local temp copy */
+    addr = &dummy;
+
+  Curl_sock_assign_addr(addr, ai, transport);
+  return socket_open(data, addr, sockfd);
+}
+
+static int socket_close(struct Curl_easy *data, struct connectdata *conn,
+                        int use_callback, curl_socket_t sock)
+{
+  if(use_callback && conn && conn->fclosesocket) {
+    int rc;
+    Curl_multi_closed(data, sock);
+    Curl_set_in_callback(data, true);
+    rc = conn->fclosesocket(conn->closesocket_client, sock);
+    Curl_set_in_callback(data, false);
+    return rc;
+  }
+
+  if(conn)
+    /* tell the multi-socket code about this */
+    Curl_multi_closed(data, sock);
+
+  sclose(sock);
+
+  return 0;
+}
+
+/*
+ * Close a socket.
+ *
+ * 'conn' can be NULL, beware!
+ */
+int Curl_socket_close(struct Curl_easy *data, struct connectdata *conn,
+                      curl_socket_t sock)
+{
+  return socket_close(data, conn, FALSE, sock);
+}
+
+#ifdef USE_WINSOCK
+/* When you run a program that uses the Windows Sockets API, you may
+   experience slow performance when you copy data to a TCP server.
+
+   https://support.microsoft.com/kb/823764
+
+   Work-around: Make the Socket Send Buffer Size Larger Than the Program Send
+   Buffer Size
+
+   The problem described in this knowledge-base is applied only to pre-Vista
+   Windows.  Following function trying to detect OS version and skips
+   SO_SNDBUF adjustment for Windows Vista and above.
+*/
+#define DETECT_OS_NONE 0
+#define DETECT_OS_PREVISTA 1
+#define DETECT_OS_VISTA_OR_LATER 2
+
+void Curl_sndbufset(curl_socket_t sockfd)
+{
+  int val = CURL_MAX_WRITE_SIZE + 32;
+  int curval = 0;
+  int curlen = sizeof(curval);
+
+  static int detectOsState = DETECT_OS_NONE;
+
+  if(detectOsState == DETECT_OS_NONE) {
+    if(curlx_verify_windows_version(6, 0, 0, PLATFORM_WINNT,
+                                    VERSION_GREATER_THAN_EQUAL))
+      detectOsState = DETECT_OS_VISTA_OR_LATER;
+    else
+      detectOsState = DETECT_OS_PREVISTA;
+  }
+
+  if(detectOsState == DETECT_OS_VISTA_OR_LATER)
+    return;
+
+  if(getsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, (char *)&curval, &curlen) == 0)
+    if(curval > val)
+      return;
+
+  setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, (const char *)&val, sizeof(val));
+}
+#endif
+
+static CURLcode bindlocal(struct Curl_easy *data, struct connectdata *conn,
+                          curl_socket_t sockfd, int af, unsigned int scope)
+{
+  struct Curl_sockaddr_storage sa;
+  struct sockaddr *sock = (struct sockaddr *)&sa;  /* bind to this address */
+  curl_socklen_t sizeof_sa = 0; /* size of the data sock points to */
+  struct sockaddr_in *si4 = (struct sockaddr_in *)&sa;
+#ifdef ENABLE_IPV6
+  struct sockaddr_in6 *si6 = (struct sockaddr_in6 *)&sa;
+#endif
+
+  struct Curl_dns_entry *h = NULL;
+  unsigned short port = data->set.localport; /* use this port number, 0 for
+                                                "random" */
+  /* how many port numbers to try to bind to, increasing one at a time */
+  int portnum = data->set.localportrange;
+  const char *dev = data->set.str[STRING_DEVICE];
+  int error;
+#ifdef IP_BIND_ADDRESS_NO_PORT
+  int on = 1;
+#endif
+#ifndef ENABLE_IPV6
+  (void)scope;
+#endif
+
+  /*************************************************************
+   * Select device to bind socket to
+   *************************************************************/
+  if(!dev && !port)
+    /* no local kind of binding was requested */
+    return CURLE_OK;
+
+  memset(&sa, 0, sizeof(struct Curl_sockaddr_storage));
+
+  if(dev && (strlen(dev)<255) ) {
+    char myhost[256] = "";
+    int done = 0; /* -1 for error, 1 for address found */
+    bool is_interface = FALSE;
+    bool is_host = FALSE;
+    static const char *if_prefix = "if!";
+    static const char *host_prefix = "host!";
+
+    if(strncmp(if_prefix, dev, strlen(if_prefix)) == 0) {
+      dev += strlen(if_prefix);
+      is_interface = TRUE;
+    }
+    else if(strncmp(host_prefix, dev, strlen(host_prefix)) == 0) {
+      dev += strlen(host_prefix);
+      is_host = TRUE;
+    }
+
+    /* interface */
+    if(!is_host) {
+#ifdef SO_BINDTODEVICE
+      /* I am not sure any other OSs than Linux that provide this feature,
+       * and at the least I cannot test. --Ben
+       *
+       * This feature allows one to tightly bind the local socket to a
+       * particular interface.  This will force even requests to other
+       * local interfaces to go out the external interface.
+       *
+       *
+       * Only bind to the interface when specified as interface, not just
+       * as a hostname or ip address.
+       *
+       * interface might be a VRF, eg: vrf-blue, which means it cannot be
+       * converted to an IP address and would fail Curl_if2ip. Simply try
+       * to use it straight away.
+       */
+      if(setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE,
+                    dev, (curl_socklen_t)strlen(dev) + 1) == 0) {
+        /* This is typically "errno 1, error: Operation not permitted" if
+         * you're not running as root or another suitable privileged
+         * user.
+         * If it succeeds it means the parameter was a valid interface and
+         * not an IP address. Return immediately.
+         */
+        return CURLE_OK;
+      }
+#endif
+
+      switch(Curl_if2ip(af,
+#ifdef ENABLE_IPV6
+                        scope, conn->scope_id,
+#endif
+                        dev, myhost, sizeof(myhost))) {
+        case IF2IP_NOT_FOUND:
+          if(is_interface) {
+            /* Do not fall back to treating it as a host name */
+            failf(data, "Couldn't bind to interface '%s'", dev);
+            return CURLE_INTERFACE_FAILED;
+          }
+          break;
+        case IF2IP_AF_NOT_SUPPORTED:
+          /* Signal the caller to try another address family if available */
+          return CURLE_UNSUPPORTED_PROTOCOL;
+        case IF2IP_FOUND:
+          is_interface = TRUE;
+          /*
+           * We now have the numerical IP address in the 'myhost' buffer
+           */
+          infof(data, "Local Interface %s is ip %s using address family %i",
+                dev, myhost, af);
+          done = 1;
+          break;
+      }
+    }
+    if(!is_interface) {
+      /*
+       * This was not an interface, resolve the name as a host name
+       * or IP number
+       *
+       * Temporarily force name resolution to use only the address type
+       * of the connection. The resolve functions should really be changed
+       * to take a type parameter instead.
+       */
+      unsigned char ipver = conn->ip_version;
+      int rc;
+
+      if(af == AF_INET)
+        conn->ip_version = CURL_IPRESOLVE_V4;
+#ifdef ENABLE_IPV6
+      else if(af == AF_INET6)
+        conn->ip_version = CURL_IPRESOLVE_V6;
+#endif
+
+      rc = Curl_resolv(data, dev, 80, FALSE, &h);
+      if(rc == CURLRESOLV_PENDING)
+        (void)Curl_resolver_wait_resolv(data, &h);
+      conn->ip_version = ipver;
+
+      if(h) {
+        /* convert the resolved address, sizeof myhost >= INET_ADDRSTRLEN */
+        Curl_printable_address(h->addr, myhost, sizeof(myhost));
+        infof(data, "Name '%s' family %i resolved to '%s' family %i",
+              dev, af, myhost, h->addr->ai_family);
+        Curl_resolv_unlock(data, h);
+        if(af != h->addr->ai_family) {
+          /* bad IP version combo, signal the caller to try another address
+             family if available */
+          return CURLE_UNSUPPORTED_PROTOCOL;
+        }
+        done = 1;
+      }
+      else {
+        /*
+         * provided dev was no interface (or interfaces are not supported
+         * e.g. solaris) no ip address and no domain we fail here
+         */
+        done = -1;
+      }
+    }
+
+    if(done > 0) {
+#ifdef ENABLE_IPV6
+      /* IPv6 address */
+      if(af == AF_INET6) {
+#ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID
+        char *scope_ptr = strchr(myhost, '%');
+        if(scope_ptr)
+          *(scope_ptr++) = '\0';
+#endif
+        if(Curl_inet_pton(AF_INET6, myhost, &si6->sin6_addr) > 0) {
+          si6->sin6_family = AF_INET6;
+          si6->sin6_port = htons(port);
+#ifdef HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID
+          if(scope_ptr) {
+            /* The "myhost" string either comes from Curl_if2ip or from
+               Curl_printable_address. The latter returns only numeric scope
+               IDs and the former returns none at all.  So the scope ID, if
+               present, is known to be numeric */
+            unsigned long scope_id = strtoul(scope_ptr, NULL, 10);
+            if(scope_id > UINT_MAX)
+              return CURLE_UNSUPPORTED_PROTOCOL;
+
+            si6->sin6_scope_id = (unsigned int)scope_id;
+          }
+#endif
+        }
+        sizeof_sa = sizeof(struct sockaddr_in6);
+      }
+      else
+#endif
+      /* IPv4 address */
+      if((af == AF_INET) &&
+         (Curl_inet_pton(AF_INET, myhost, &si4->sin_addr) > 0)) {
+        si4->sin_family = AF_INET;
+        si4->sin_port = htons(port);
+        sizeof_sa = sizeof(struct sockaddr_in);
+      }
+    }
+
+    if(done < 1) {
+      /* errorbuf is set false so failf will overwrite any message already in
+         the error buffer, so the user receives this error message instead of a
+         generic resolve error. */
+      data->state.errorbuf = FALSE;
+      failf(data, "Couldn't bind to '%s'", dev);
+      return CURLE_INTERFACE_FAILED;
+    }
+  }
+  else {
+    /* no device was given, prepare sa to match af's needs */
+#ifdef ENABLE_IPV6
+    if(af == AF_INET6) {
+      si6->sin6_family = AF_INET6;
+      si6->sin6_port = htons(port);
+      sizeof_sa = sizeof(struct sockaddr_in6);
+    }
+    else
+#endif
+    if(af == AF_INET) {
+      si4->sin_family = AF_INET;
+      si4->sin_port = htons(port);
+      sizeof_sa = sizeof(struct sockaddr_in);
+    }
+  }
+#ifdef IP_BIND_ADDRESS_NO_PORT
+  (void)setsockopt(sockfd, SOL_IP, IP_BIND_ADDRESS_NO_PORT, &on, sizeof(on));
+#endif
+  for(;;) {
+    if(bind(sockfd, sock, sizeof_sa) >= 0) {
+      /* we succeeded to bind */
+      struct Curl_sockaddr_storage add;
+      curl_socklen_t size = sizeof(add);
+      memset(&add, 0, sizeof(struct Curl_sockaddr_storage));
+      if(getsockname(sockfd, (struct sockaddr *) &add, &size) < 0) {
+        char buffer[STRERROR_LEN];
+        data->state.os_errno = error = SOCKERRNO;
+        failf(data, "getsockname() failed with errno %d: %s",
+              error, Curl_strerror(error, buffer, sizeof(buffer)));
+        return CURLE_INTERFACE_FAILED;
+      }
+      infof(data, "Local port: %hu", port);
+      conn->bits.bound = TRUE;
+      return CURLE_OK;
+    }
+
+    if(--portnum > 0) {
+      port++; /* try next port */
+      if(port == 0)
+        break;
+      infof(data, "Bind to local port %hu failed, trying next", port - 1);
+      /* We re-use/clobber the port variable here below */
+      if(sock->sa_family == AF_INET)
+        si4->sin_port = ntohs(port);
+#ifdef ENABLE_IPV6
+      else
+        si6->sin6_port = ntohs(port);
+#endif
+    }
+    else
+      break;
+  }
+  {
+    char buffer[STRERROR_LEN];
+    data->state.os_errno = error = SOCKERRNO;
+    failf(data, "bind failed with errno %d: %s",
+          error, Curl_strerror(error, buffer, sizeof(buffer)));
+  }
+
+  return CURLE_INTERFACE_FAILED;
+}
+
+/*
+ * verifyconnect() returns TRUE if the connect really has happened.
+ */
+static bool verifyconnect(curl_socket_t sockfd, int *error)
+{
+  bool rc = TRUE;
+#ifdef SO_ERROR
+  int err = 0;
+  curl_socklen_t errSize = sizeof(err);
+
+#ifdef WIN32
+  /*
+   * In October 2003 we effectively nullified this function on Windows due to
+   * problems with it using all CPU in multi-threaded cases.
+   *
+   * In May 2004, we bring it back to offer more info back on connect failures.
+   * Gisle Vanem could reproduce the former problems with this function, but
+   * could avoid them by adding this SleepEx() call below:
+   *
+   *    "I don't have Rational Quantify, but the hint from his post was
+   *    ntdll::NtRemoveIoCompletion(). So I'd assume the SleepEx (or maybe
+   *    just Sleep(0) would be enough?) would release whatever
+   *    mutex/critical-section the ntdll call is waiting on.
+   *
+   *    Someone got to verify this on Win-NT 4.0, 2000."
+   */
+
+#ifdef _WIN32_WCE
+  Sleep(0);
+#else
+  SleepEx(0, FALSE);
+#endif
+
+#endif
+
+  if(0 != getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (void *)&err, &errSize))
+    err = SOCKERRNO;
+#ifdef _WIN32_WCE
+  /* Old WinCE versions don't support SO_ERROR */
+  if(WSAENOPROTOOPT == err) {
+    SET_SOCKERRNO(0);
+    err = 0;
+  }
+#endif
+#if defined(EBADIOCTL) && defined(__minix)
+  /* Minix 3.1.x doesn't support getsockopt on UDP sockets */
+  if(EBADIOCTL == err) {
+    SET_SOCKERRNO(0);
+    err = 0;
+  }
+#endif
+  if((0 == err) || (EISCONN == err))
+    /* we are connected, awesome! */
+    rc = TRUE;
+  else
+    /* This wasn't a successful connect */
+    rc = FALSE;
+  if(error)
+    *error = err;
+#else
+  (void)sockfd;
+  if(error)
+    *error = SOCKERRNO;
+#endif
+  return rc;
+}
+
+CURLcode Curl_socket_connect_result(struct Curl_easy *data,
+                                    const char *ipaddress, int error)
+{
+  char buffer[STRERROR_LEN];
+
+  switch(error) {
+  case EINPROGRESS:
+  case EWOULDBLOCK:
+#if defined(EAGAIN)
+#if (EAGAIN) != (EWOULDBLOCK)
+    /* On some platforms EAGAIN and EWOULDBLOCK are the
+     * same value, and on others they are different, hence
+     * the odd #if
+     */
+  case EAGAIN:
+#endif
+#endif
+    return CURLE_OK;
+
+  default:
+    /* unknown error, fallthrough and try another address! */
+    infof(data, "Immediate connect fail for %s: %s",
+          ipaddress, Curl_strerror(error, buffer, sizeof(buffer)));
+    data->state.os_errno = error;
+    /* connect failed */
+    return CURLE_COULDNT_CONNECT;
+  }
+}
+
+#ifdef USE_RECV_BEFORE_SEND_WORKAROUND
+struct io_buffer {
+  char *bufr;
+  size_t allc;           /* size of the current allocation */
+  size_t head;           /* bufr index for next read */
+  size_t tail;           /* bufr index for next write */
+};
+
+static void io_buffer_reset(struct io_buffer *iob)
+{
+  if(iob->bufr)
+    free(iob->bufr);
+  memset(iob, 0, sizeof(*iob));
+}
+#endif /* USE_RECV_BEFORE_SEND_WORKAROUND */
+
+struct cf_socket_ctx {
+  int transport;
+  struct Curl_sockaddr_ex addr;      /* address to connect to */
+  curl_socket_t sock;                /* current attempt socket */
+#ifdef USE_RECV_BEFORE_SEND_WORKAROUND
+  struct io_buffer recv_buffer;
+#endif
+  char r_ip[MAX_IPADR_LEN];          /* remote IP as string */
+  int r_port;                        /* remote port number */
+  char l_ip[MAX_IPADR_LEN];          /* local IP as string */
+  int l_port;                        /* local port number */
+  struct curltime started_at;        /* when socket was created */
+  struct curltime connected_at;      /* when socket connected/got first byte */
+  struct curltime first_byte_at;     /* when first byte was recvd */
+  int error;                         /* errno of last failure or 0 */
+  BIT(got_first_byte);               /* if first byte was received */
+  BIT(accepted);                     /* socket was accepted, not connected */
+  BIT(active);
+};
+
+static void cf_socket_ctx_init(struct cf_socket_ctx *ctx,
+                               const struct Curl_addrinfo *ai,
+                               int transport)
+{
+  memset(ctx, 0, sizeof(*ctx));
+  ctx->sock = CURL_SOCKET_BAD;
+  ctx->transport = transport;
+  Curl_sock_assign_addr(&ctx->addr, ai, transport);
+}
+
+static void cf_socket_close(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+  struct cf_socket_ctx *ctx = cf->ctx;
+
+  if(ctx && CURL_SOCKET_BAD != ctx->sock) {
+    if(ctx->active) {
+      /* We share our socket at cf->conn->sock[cf->sockindex] when active.
+       * If it is no longer there, someone has stolen (and hopefully
+       * closed it) and we just forget about it.
+       */
+      if(ctx->sock == cf->conn->sock[cf->sockindex]) {
+        DEBUGF(LOG_CF(data, cf, "cf_socket_close(%d, active)",
+                     (int)ctx->sock));
+        socket_close(data, cf->conn, !ctx->accepted, ctx->sock);
+        cf->conn->sock[cf->sockindex] = CURL_SOCKET_BAD;
+      }
+      else {
+        DEBUGF(LOG_CF(data, cf, "cf_socket_close(%d) no longer at "
+                      "conn->sock[], discarding", (int)ctx->sock));
+        /* TODO: we do not want this to happen. Need to check which
+         * code is messing with conn->sock[cf->sockindex] */
+      }
+      ctx->sock = CURL_SOCKET_BAD;
+      if(cf->sockindex == FIRSTSOCKET)
+        cf->conn->remote_addr = NULL;
+    }
+    else {
+      /* this is our local socket, we did never publish it */
+      DEBUGF(LOG_CF(data, cf, "cf_socket_close(%d, not active)",
+                    (int)ctx->sock));
+      sclose(ctx->sock);
+      ctx->sock = CURL_SOCKET_BAD;
+    }
+#ifdef USE_RECV_BEFORE_SEND_WORKAROUND
+    io_buffer_reset(&ctx->recv_buffer);
+#endif
+    ctx->active = FALSE;
+    memset(&ctx->started_at, 0, sizeof(ctx->started_at));
+    memset(&ctx->connected_at, 0, sizeof(ctx->connected_at));
+  }
+
+  cf->connected = FALSE;
+}
+
+static void cf_socket_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+  struct cf_socket_ctx *ctx = cf->ctx;
+
+  cf_socket_close(cf, data);
+  DEBUGF(LOG_CF(data, cf, "destroy"));
+  free(ctx);
+  cf->ctx = NULL;
+}
+
+static CURLcode set_local_ip(struct Curl_cfilter *cf,
+                             struct Curl_easy *data)
+{
+  struct cf_socket_ctx *ctx = cf->ctx;
+
+#ifdef HAVE_GETSOCKNAME
+  char buffer[STRERROR_LEN];
+  struct Curl_sockaddr_storage ssloc;
+  curl_socklen_t slen = sizeof(struct Curl_sockaddr_storage);
+
+  memset(&ssloc, 0, sizeof(ssloc));
+  if(getsockname(ctx->sock, (struct sockaddr*) &ssloc, &slen)) {
+    int error = SOCKERRNO;
+    failf(data, "getsockname() failed with errno %d: %s",
+          error, Curl_strerror(error, buffer, sizeof(buffer)));
+    return CURLE_FAILED_INIT;
+  }
+  if(!Curl_addr2string((struct sockaddr*)&ssloc, slen,
+                       ctx->l_ip, &ctx->l_port)) {
+    failf(data, "ssloc inet_ntop() failed with errno %d: %s",
+          errno, Curl_strerror(errno, buffer, sizeof(buffer)));
+    return CURLE_FAILED_INIT;
+  }
+#else
+  (void)data;
+  ctx->l_ip[0] = 0;
+  ctx->l_port = -1;
+#endif
+  return CURLE_OK;
+}
+
+static CURLcode set_remote_ip(struct Curl_cfilter *cf,
+                              struct Curl_easy *data)
+{
+  struct cf_socket_ctx *ctx = cf->ctx;
+
+  /* store remote address and port used in this connection attempt */
+  if(!Curl_addr2string(&ctx->addr.sa_addr, ctx->addr.addrlen,
+                       ctx->r_ip, &ctx->r_port)) {
+    char buffer[STRERROR_LEN];
+
+    ctx->error = errno;
+    /* malformed address or bug in inet_ntop, try next address */
+    failf(data, "sa_addr inet_ntop() failed with errno %d: %s",
+          errno, Curl_strerror(errno, buffer, sizeof(buffer)));
+    return CURLE_FAILED_INIT;
+  }
+  return CURLE_OK;
+}
+
+static CURLcode cf_socket_open(struct Curl_cfilter *cf,
+                              struct Curl_easy *data)
+{
+  struct cf_socket_ctx *ctx = cf->ctx;
+  int error = 0;
+  bool isconnected = FALSE;
+  CURLcode result = CURLE_COULDNT_CONNECT;
+  bool is_tcp;
+  const char *ipmsg;
+
+  (void)data;
+  DEBUGASSERT(ctx->sock == CURL_SOCKET_BAD);
+  ctx->started_at = Curl_now();
+  result = socket_open(data, &ctx->addr, &ctx->sock);
+  if(result)
+    goto out;
+
+  result = set_remote_ip(cf, data);
+  if(result)
+    goto out;
+
+#ifdef ENABLE_IPV6
+  if(ctx->addr.family == AF_INET6)
+    ipmsg = "  Trying [%s]:%d...";
+  else
+#endif
+    ipmsg = "  Trying %s:%d...";
+  infof(data, ipmsg, ctx->r_ip, ctx->r_port);
+
+#ifdef ENABLE_IPV6
+  is_tcp = (ctx->addr.family == AF_INET
+            || ctx->addr.family == AF_INET6) &&
+           ctx->addr.socktype == SOCK_STREAM;
+#else
+  is_tcp = (ctx->addr.family == AF_INET) &&
+           ctx->addr.socktype == SOCK_STREAM;
+#endif
+  if(is_tcp && data->set.tcp_nodelay)
+    tcpnodelay(data, ctx->sock);
+
+  nosigpipe(data, ctx->sock);
+
+  Curl_sndbufset(ctx->sock);
+
+  if(is_tcp && data->set.tcp_keepalive)
+    tcpkeepalive(data, ctx->sock);
+
+  if(data->set.fsockopt) {
+    /* activate callback for setting socket options */
+    Curl_set_in_callback(data, true);
+    error = data->set.fsockopt(data->set.sockopt_client,
+                               ctx->sock,
+                               CURLSOCKTYPE_IPCXN);
+    Curl_set_in_callback(data, false);
+
+    if(error == CURL_SOCKOPT_ALREADY_CONNECTED)
+      isconnected = TRUE;
+    else if(error) {
+      result = CURLE_ABORTED_BY_CALLBACK;
+      goto out;
+    }
+  }
+
+  /* possibly bind the local end to an IP, interface or port */
+  if(ctx->addr.family == AF_INET
+#ifdef ENABLE_IPV6
+     || ctx->addr.family == AF_INET6
+#endif
+    ) {
+    result = bindlocal(data, cf->conn, ctx->sock, ctx->addr.family,
+                       Curl_ipv6_scope(&ctx->addr.sa_addr));
+    if(result) {
+      if(result == CURLE_UNSUPPORTED_PROTOCOL) {
+        /* The address family is not supported on this interface.
+           We can continue trying addresses */
+        result = CURLE_COULDNT_CONNECT;
+      }
+      goto out;
+    }
+  }
+
+  /* set socket non-blocking */
+  (void)curlx_nonblock(ctx->sock, TRUE);
+
+out:
+  if(result) {
+    if(ctx->sock != CURL_SOCKET_BAD) {
+      socket_close(data, cf->conn, TRUE, ctx->sock);
+      ctx->sock = CURL_SOCKET_BAD;
+    }
+  }
+  else if(isconnected) {
+    set_local_ip(cf, data);
+    ctx->connected_at = Curl_now();
+    cf->connected = TRUE;
+  }
+  DEBUGF(LOG_CF(data, cf, "cf_socket_open() -> %d, fd=%d", result, ctx->sock));
+  return result;
+}
+
+static int do_connect(struct Curl_cfilter *cf, struct Curl_easy *data,
+                      bool is_tcp_fastopen)
+{
+  struct cf_socket_ctx *ctx = cf->ctx;
+#ifdef TCP_FASTOPEN_CONNECT
+  int optval = 1;
+#endif
+  int rc = -1;
+
+  (void)data;
+  if(is_tcp_fastopen) {
+#if defined(CONNECT_DATA_IDEMPOTENT) /* Darwin */
+#  if defined(HAVE_BUILTIN_AVAILABLE)
+    /* while connectx function is available since macOS 10.11 / iOS 9,
+       it did not have the interface declared correctly until
+       Xcode 9 / macOS SDK 10.13 */
+    if(__builtin_available(macOS 10.11, iOS 9.0, tvOS 9.0, watchOS 2.0, *)) {
+      sa_endpoints_t endpoints;
+      endpoints.sae_srcif = 0;
+      endpoints.sae_srcaddr = NULL;
+      endpoints.sae_srcaddrlen = 0;
+      endpoints.sae_dstaddr = &ctx->addr.sa_addr;
+      endpoints.sae_dstaddrlen = ctx->addr.addrlen;
+
+      rc = connectx(ctx->sock, &endpoints, SAE_ASSOCID_ANY,
+                    CONNECT_RESUME_ON_READ_WRITE | CONNECT_DATA_IDEMPOTENT,
+                    NULL, 0, NULL, NULL);
+    }
+    else {
+      rc = connect(ctx->sock, &ctx->addr.sa_addr, ctx->addr.addrlen);
+    }
+#  else
+    rc = connect(ctx->sock, &ctx->addr.sa_addr, ctx->addr.addrlen);
+#  endif /* HAVE_BUILTIN_AVAILABLE */
+#elif defined(TCP_FASTOPEN_CONNECT) /* Linux >= 4.11 */
+    if(setsockopt(ctx->sock, IPPROTO_TCP, TCP_FASTOPEN_CONNECT,
+                  (void *)&optval, sizeof(optval)) < 0)
+      infof(data, "Failed to enable TCP Fast Open on fd %d", ctx->sock);
+
+    rc = connect(ctx->sock, &ctx->addr.sa_addr, ctx->addr.addrlen);
+#elif defined(MSG_FASTOPEN) /* old Linux */
+    if(cf->conn->given->flags & PROTOPT_SSL)
+      rc = connect(ctx->sock, &ctx->addr.sa_addr, ctx->addr.addrlen);
+    else
+      rc = 0; /* Do nothing */
+#endif
+  }
+  else {
+    rc = connect(ctx->sock, &ctx->addr.sa_addr, ctx->addr.addrlen);
+  }
+  return rc;
+}
+
+static CURLcode cf_tcp_connect(struct Curl_cfilter *cf,
+                               struct Curl_easy *data,
+                               bool blocking, bool *done)
+{
+  struct cf_socket_ctx *ctx = cf->ctx;
+  CURLcode result = CURLE_COULDNT_CONNECT;
+  int rc = 0;
+
+  (void)data;
+  if(cf->connected) {
+    *done = TRUE;
+    return CURLE_OK;
+  }
+
+  /* TODO: need to support blocking connect? */
+  if(blocking)
+    return CURLE_UNSUPPORTED_PROTOCOL;
+
+  *done = FALSE; /* a very negative world view is best */
+  if(ctx->sock == CURL_SOCKET_BAD) {
+
+    result = cf_socket_open(cf, data);
+    if(result)
+      goto out;
+
+    if(cf->connected) {
+      *done = TRUE;
+      return CURLE_OK;
+    }
+
+    /* Connect TCP socket */
+    rc = do_connect(cf, data, cf->conn->bits.tcp_fastopen);
+    if(-1 == rc) {
+      result = Curl_socket_connect_result(data, ctx->r_ip, SOCKERRNO);
+      goto out;
+    }
+  }
+
+#ifdef mpeix
+  /* Call this function once now, and ignore the results. We do this to
+     "clear" the error state on the socket so that we can later read it
+     reliably. This is reported necessary on the MPE/iX operating
+     system. */
+  (void)verifyconnect(ctx->sock, NULL);
+#endif
+  /* check socket for connect */
+  rc = SOCKET_WRITABLE(ctx->sock, 0);
+
+  if(rc == 0) { /* no connection yet */
+    DEBUGF(LOG_CF(data, cf, "not connected yet"));
+    return CURLE_OK;
+  }
+  else if(rc == CURL_CSELECT_OUT || cf->conn->bits.tcp_fastopen) {
+    if(verifyconnect(ctx->sock, &ctx->error)) {
+      /* we are connected with TCP, awesome! */
+      ctx->connected_at = Curl_now();
+      set_local_ip(cf, data);
+      *done = TRUE;
+      cf->connected = TRUE;
+      DEBUGF(LOG_CF(data, cf, "connected"));
+      return CURLE_OK;
+    }
+  }
+  else if(rc & CURL_CSELECT_ERR) {
+    (void)verifyconnect(ctx->sock, &ctx->error);
+    result = CURLE_COULDNT_CONNECT;
+  }
+
+out:
+  if(result) {
+    if(ctx->error) {
+      data->state.os_errno = ctx->error;
+      SET_SOCKERRNO(ctx->error);
+#ifndef CURL_DISABLE_VERBOSE_STRINGS
+      {
+        char buffer[STRERROR_LEN];
+        infof(data, "connect to %s port %u failed: %s",
+              ctx->r_ip, ctx->r_port,
+              Curl_strerror(ctx->error, buffer, sizeof(buffer)));
+      }
+#endif
+    }
+    if(ctx->sock != CURL_SOCKET_BAD) {
+      socket_close(data, cf->conn, TRUE, ctx->sock);
+      ctx->sock = CURL_SOCKET_BAD;
+    }
+    *done = FALSE;
+  }
+  return result;
+}
+
+static void cf_socket_get_host(struct Curl_cfilter *cf,
+                               struct Curl_easy *data,
+                               const char **phost,
+                               const char **pdisplay_host,
+                               int *pport)
+{
+  (void)data;
+  *phost = cf->conn->host.name;
+  *pdisplay_host = cf->conn->host.dispname;
+  *pport = cf->conn->port;
+}
+
+static int cf_socket_get_select_socks(struct Curl_cfilter *cf,
+                                      struct Curl_easy *data,
+                                      curl_socket_t *socks)
+{
+  struct cf_socket_ctx *ctx = cf->ctx;
+  int rc = GETSOCK_BLANK;
+
+  (void)data;
+  if(!cf->connected && ctx->sock != CURL_SOCKET_BAD) {
+    socks[0] = ctx->sock;
+    rc |= GETSOCK_WRITESOCK(0);
+  }
+
+  return rc;
+}
+
+#ifdef USE_RECV_BEFORE_SEND_WORKAROUND
+
+static CURLcode pre_receive_plain(struct Curl_cfilter *cf,
+                                  struct Curl_easy *data)
+{
+  struct cf_socket_ctx *ctx = cf->ctx;
+  struct io_buffer * const iob = &ctx->recv_buffer;
+
+  /* WinSock will destroy unread received data if send() is
+     failed.
+     To avoid lossage of received data, recv() must be
+     performed before every send() if any incoming data is
+     available. However, skip this, if buffer is already full. */
+  if((cf->conn->handler->protocol&PROTO_FAMILY_HTTP) != 0 &&
+     cf->conn->recv[cf->sockindex] == Curl_conn_recv &&
+     (!iob->bufr || (iob->allc > iob->tail))) {
+    const int readymask = Curl_socket_check(ctx->sock, CURL_SOCKET_BAD,
+                                            CURL_SOCKET_BAD, 0);
+    if(readymask != -1 && (readymask & CURL_CSELECT_IN) != 0) {
+      size_t bytestorecv = iob->allc - iob->tail;
+      ssize_t nread;
+      /* Have some incoming data */
+      if(!iob->bufr) {
+        /* Use buffer double default size for intermediate buffer */
+        iob->allc = 2 * data->set.buffer_size;
+        iob->bufr = malloc(iob->allc);
+        if(!iob->bufr)
+          return CURLE_OUT_OF_MEMORY;
+        iob->tail = 0;
+        iob->head = 0;
+        bytestorecv = iob->allc;
+      }
+
+      nread = sread(ctx->sock, iob->bufr + iob->tail, bytestorecv);
+      if(nread > 0)
+        iob->tail += (size_t)nread;
+    }
+  }
+  return CURLE_OK;
+}
+
+static ssize_t get_pre_recved(struct Curl_cfilter *cf, char *buf, size_t len)
+{
+  struct cf_socket_ctx *ctx = cf->ctx;
+  struct io_buffer * const iob = &ctx->recv_buffer;
+  size_t copysize;
+  if(!iob->bufr)
+    return 0;
+
+  DEBUGASSERT(iob->allc > 0);
+  DEBUGASSERT(iob->tail <= iob->allc);
+  DEBUGASSERT(iob->head <= iob->tail);
+  /* Check and process data that already received and storied in internal
+     intermediate buffer */
+  if(iob->tail > iob->head) {
+    copysize = CURLMIN(len, iob->tail - iob->head);
+    memcpy(buf, iob->bufr + iob->head, copysize);
+    iob->head += copysize;
+  }
+  else
+    copysize = 0; /* buffer was allocated, but nothing was received */
+
+  /* Free intermediate buffer if it has no unprocessed data */
+  if(iob->head == iob->tail)
+    io_buffer_reset(iob);
+
+  return (ssize_t)copysize;
+}
+#endif  /* USE_RECV_BEFORE_SEND_WORKAROUND */
+
+static bool cf_socket_data_pending(struct Curl_cfilter *cf,
+                                   const struct Curl_easy *data)
+{
+  struct cf_socket_ctx *ctx = cf->ctx;
+  int readable;
+
+#ifdef USE_RECV_BEFORE_SEND_WORKAROUND
+  if(ctx->recv_buffer.bufr && ctx->recv_buffer.allc &&
+     ctx->recv_buffer.tail > ctx->recv_buffer.head)
+     return TRUE;
+#endif
+
+  (void)data;
+  readable = SOCKET_READABLE(ctx->sock, 0);
+  return (readable > 0 && (readable & CURL_CSELECT_IN));
+}
+
+static ssize_t cf_socket_send(struct Curl_cfilter *cf, struct Curl_easy *data,
+                              const void *buf, size_t len, CURLcode *err)
+{
+  struct cf_socket_ctx *ctx = cf->ctx;
+  curl_socket_t fdsave;
+  ssize_t nwritten;
+
+  *err = CURLE_OK;
+
+#ifdef USE_RECV_BEFORE_SEND_WORKAROUND
+  /* WinSock will destroy unread received data if send() is
+     failed.
+     To avoid lossage of received data, recv() must be
+     performed before every send() if any incoming data is
+     available. */
+  if(pre_receive_plain(cf, data)) {
+    *err = CURLE_OUT_OF_MEMORY;
+    return -1;
+  }
+#endif
+
+  fdsave = cf->conn->sock[cf->sockindex];
+  cf->conn->sock[cf->sockindex] = ctx->sock;
+
+#if defined(MSG_FASTOPEN) && !defined(TCP_FASTOPEN_CONNECT) /* Linux */
+  if(cf->conn->bits.tcp_fastopen) {
+    nwritten = sendto(ctx->sock, buf, len, MSG_FASTOPEN,
+                      &cf->conn->remote_addr->sa_addr,
+                      cf->conn->remote_addr->addrlen);
+    cf->conn->bits.tcp_fastopen = FALSE;
+  }
+  else
+#endif
+    nwritten = swrite(ctx->sock, buf, len);
+
+  if(-1 == nwritten) {
+    int sockerr = SOCKERRNO;
+
+    if(
+#ifdef WSAEWOULDBLOCK
+      /* This is how Windows does it */
+      (WSAEWOULDBLOCK == sockerr)
+#else
+      /* errno may be EWOULDBLOCK or on some systems EAGAIN when it returned
+         due to its inability to send off data without blocking. We therefore
+         treat both error codes the same here */
+      (EWOULDBLOCK == sockerr) || (EAGAIN == sockerr) || (EINTR == sockerr) ||
+      (EINPROGRESS == sockerr)
+#endif
+      ) {
+      /* this is just a case of EWOULDBLOCK */
+      *err = CURLE_AGAIN;
+    }
+    else {
+      char buffer[STRERROR_LEN];
+      failf(data, "Send failure: %s",
+            Curl_strerror(sockerr, buffer, sizeof(buffer)));
+      data->state.os_errno = sockerr;
+      *err = CURLE_SEND_ERROR;
+    }
+  }
+
+  DEBUGF(LOG_CF(data, cf, "send(len=%zu) -> %d, err=%d",
+                len, (int)nwritten, *err));
+  cf->conn->sock[cf->sockindex] = fdsave;
+  return nwritten;
+}
+
+static ssize_t cf_socket_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
+                              char *buf, size_t len, CURLcode *err)
+{
+  struct cf_socket_ctx *ctx = cf->ctx;
+  curl_socket_t fdsave;
+  ssize_t nread;
+
+  *err = CURLE_OK;
+
+#ifdef USE_RECV_BEFORE_SEND_WORKAROUND
+  /* Check and return data that already received and storied in internal
+     intermediate buffer */
+  nread = get_pre_recved(cf, buf, len);
+  if(nread > 0) {
+    *err = CURLE_OK;
+    return nread;
+  }
+#endif
+
+  fdsave = cf->conn->sock[cf->sockindex];
+  cf->conn->sock[cf->sockindex] = ctx->sock;
+
+  nread = sread(ctx->sock, buf, len);
+
+  if(-1 == nread) {
+    int sockerr = SOCKERRNO;
+
+    if(
+#ifdef WSAEWOULDBLOCK
+      /* This is how Windows does it */
+      (WSAEWOULDBLOCK == sockerr)
+#else
+      /* errno may be EWOULDBLOCK or on some systems EAGAIN when it returned
+         due to its inability to send off data without blocking. We therefore
+         treat both error codes the same here */
+      (EWOULDBLOCK == sockerr) || (EAGAIN == sockerr) || (EINTR == sockerr)
+#endif
+      ) {
+      /* this is just a case of EWOULDBLOCK */
+      *err = CURLE_AGAIN;
+    }
+    else {
+      char buffer[STRERROR_LEN];
+      failf(data, "Recv failure: %s",
+            Curl_strerror(sockerr, buffer, sizeof(buffer)));
+      data->state.os_errno = sockerr;
+      *err = CURLE_RECV_ERROR;
+    }
+  }
+
+  DEBUGF(LOG_CF(data, cf, "recv(len=%zu) -> %d, err=%d", len, (int)nread,
+                *err));
+  if(nread > 0 && !ctx->got_first_byte) {
+    ctx->first_byte_at = Curl_now();
+    ctx->got_first_byte = TRUE;
+  }
+  cf->conn->sock[cf->sockindex] = fdsave;
+  return nread;
+}
+
+static void conn_set_primary_ip(struct Curl_cfilter *cf,
+                                struct Curl_easy *data)
+{
+  struct cf_socket_ctx *ctx = cf->ctx;
+#ifdef HAVE_GETPEERNAME
+  char buffer[STRERROR_LEN];
+  struct Curl_sockaddr_storage ssrem;
+  curl_socklen_t plen;
+  int port;
+
+  plen = sizeof(ssrem);
+  memset(&ssrem, 0, plen);
+  if(getpeername(ctx->sock, (struct sockaddr*) &ssrem, &plen)) {
+    int error = SOCKERRNO;
+    failf(data, "getpeername() failed with errno %d: %s",
+          error, Curl_strerror(error, buffer, sizeof(buffer)));
+    return;
+  }
+  if(!Curl_addr2string((struct sockaddr*)&ssrem, plen,
+                       cf->conn->primary_ip, &port)) {
+    failf(data, "ssrem inet_ntop() failed with errno %d: %s",
+          errno, Curl_strerror(errno, buffer, sizeof(buffer)));
+    return;
+  }
+#else
+  cf->conn->primary_ip[0] = 0;
+  (void)data;
+#endif
+}
+
+static void cf_socket_active(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+  struct cf_socket_ctx *ctx = cf->ctx;
+
+  /* use this socket from now on */
+  cf->conn->sock[cf->sockindex] = ctx->sock;
+  /* the first socket info gets set at conn and data */
+  if(cf->sockindex == FIRSTSOCKET) {
+    cf->conn->remote_addr = &ctx->addr;
+  #ifdef ENABLE_IPV6
+    cf->conn->bits.ipv6 = (ctx->addr.family == AF_INET6)? TRUE : FALSE;
+  #endif
+    conn_set_primary_ip(cf, data);
+    set_local_ip(cf, data);
+    Curl_persistconninfo(data, cf->conn, ctx->l_ip, ctx->l_port);
+  }
+  ctx->active = TRUE;
+}
+
+static CURLcode cf_socket_cntrl(struct Curl_cfilter *cf,
+                                struct Curl_easy *data,
+                                int event, int arg1, void *arg2)
+{
+  struct cf_socket_ctx *ctx = cf->ctx;
+
+  (void)arg1;
+  (void)arg2;
+  switch(event) {
+  case CF_CTRL_CONN_INFO_UPDATE:
+    cf_socket_active(cf, data);
+    break;
+  case CF_CTRL_DATA_SETUP:
+    Curl_persistconninfo(data, cf->conn, ctx->l_ip, ctx->l_port);
+    break;
+  }
+  return CURLE_OK;
+}
+
+static bool cf_socket_conn_is_alive(struct Curl_cfilter *cf,
+                                    struct Curl_easy *data,
+                                    bool *input_pending)
+{
+  struct cf_socket_ctx *ctx = cf->ctx;
+  struct pollfd pfd[1];
+  int r;
+
+  *input_pending = FALSE;
+  (void)data;
+  if(!ctx || ctx->sock == CURL_SOCKET_BAD)
+    return FALSE;
+
+  /* Check with 0 timeout if there are any events pending on the socket */
+  pfd[0].fd = ctx->sock;
+  pfd[0].events = POLLRDNORM|POLLIN|POLLRDBAND|POLLPRI;
+  pfd[0].revents = 0;
+
+  r = Curl_poll(pfd, 1, 0);
+  if(r < 0) {
+    DEBUGF(LOG_CF(data, cf, "is_alive: poll error, assume dead"));
+    return FALSE;
+  }
+  else if(r == 0) {
+    DEBUGF(LOG_CF(data, cf, "is_alive: poll timeout, assume alive"));
+    return TRUE;
+  }
+  else if(pfd[0].revents & (POLLERR|POLLHUP|POLLPRI|POLLNVAL)) {
+    DEBUGF(LOG_CF(data, cf, "is_alive: err/hup/etc events, assume dead"));
+    return FALSE;
+  }
+
+  DEBUGF(LOG_CF(data, cf, "is_alive: valid events, looks alive"));
+  *input_pending = TRUE;
+  return TRUE;
+}
+
+static CURLcode cf_socket_query(struct Curl_cfilter *cf,
+                                struct Curl_easy *data,
+                                int query, int *pres1, void *pres2)
+{
+  struct cf_socket_ctx *ctx = cf->ctx;
+
+  switch(query) {
+  case CF_QUERY_SOCKET:
+    DEBUGASSERT(pres2);
+    *((curl_socket_t *)pres2) = ctx->sock;
+    return CURLE_OK;
+  case CF_QUERY_CONNECT_REPLY_MS:
+    if(ctx->got_first_byte) {
+      timediff_t ms = Curl_timediff(ctx->first_byte_at, ctx->started_at);
+      *pres1 = (ms < INT_MAX)? (int)ms : INT_MAX;
+    }
+    else
+      *pres1 = -1;
+    return CURLE_OK;
+  case CF_QUERY_TIMER_CONNECT: {
+    struct curltime *when = pres2;
+    switch(ctx->transport) {
+    case TRNSPRT_UDP:
+    case TRNSPRT_QUIC:
+      /* Since UDP connected sockets work different from TCP, we use the
+       * time of the first byte from the peer as the "connect" time. */
+      if(ctx->got_first_byte) {
+        *when = ctx->first_byte_at;
+        break;
+      }
+      /* FALLTHROUGH */
+    default:
+      *when = ctx->connected_at;
+      break;
+    }
+    return CURLE_OK;
+  }
+  default:
+    break;
+  }
+  return cf->next?
+    cf->next->cft->query(cf->next, data, query, pres1, pres2) :
+    CURLE_UNKNOWN_OPTION;
+}
+
+struct Curl_cftype Curl_cft_tcp = {
+  "TCP",
+  CF_TYPE_IP_CONNECT,
+  CURL_LOG_DEFAULT,
+  cf_socket_destroy,
+  cf_tcp_connect,
+  cf_socket_close,
+  cf_socket_get_host,
+  cf_socket_get_select_socks,
+  cf_socket_data_pending,
+  cf_socket_send,
+  cf_socket_recv,
+  cf_socket_cntrl,
+  cf_socket_conn_is_alive,
+  Curl_cf_def_conn_keep_alive,
+  cf_socket_query,
+};
+
+CURLcode Curl_cf_tcp_create(struct Curl_cfilter **pcf,
+                            struct Curl_easy *data,
+                            struct connectdata *conn,
+                            const struct Curl_addrinfo *ai,
+                            int transport)
+{
+  struct cf_socket_ctx *ctx = NULL;
+  struct Curl_cfilter *cf = NULL;
+  CURLcode result;
+
+  (void)data;
+  (void)conn;
+  DEBUGASSERT(transport == TRNSPRT_TCP);
+  ctx = calloc(sizeof(*ctx), 1);
+  if(!ctx) {
+    result = CURLE_OUT_OF_MEMORY;
+    goto out;
+  }
+  cf_socket_ctx_init(ctx, ai, transport);
+
+  result = Curl_cf_create(&cf, &Curl_cft_tcp, ctx);
+
+out:
+  *pcf = (!result)? cf : NULL;
+  if(result) {
+    Curl_safefree(cf);
+    Curl_safefree(ctx);
+  }
+
+  return result;
+}
+
+static CURLcode cf_udp_setup_quic(struct Curl_cfilter *cf,
+                               struct Curl_easy *data)
+{
+  struct cf_socket_ctx *ctx = cf->ctx;
+  int rc;
+
+  /* QUIC needs a connected socket, nonblocking */
+  DEBUGASSERT(ctx->sock != CURL_SOCKET_BAD);
+
+  rc = connect(ctx->sock, &ctx->addr.sa_addr, ctx->addr.addrlen);
+  if(-1 == rc) {
+    return Curl_socket_connect_result(data, ctx->r_ip, SOCKERRNO);
+  }
+  set_local_ip(cf, data);
+  DEBUGF(LOG_CF(data, cf, "%s socket %d connected: [%s:%d] -> [%s:%d]",
+         (ctx->transport == TRNSPRT_QUIC)? "QUIC" : "UDP",
+         ctx->sock, ctx->l_ip, ctx->l_port, ctx->r_ip, ctx->r_port));
+
+  (void)curlx_nonblock(ctx->sock, TRUE);
+  switch(ctx->addr.family) {
+#if defined(__linux__) && defined(IP_MTU_DISCOVER)
+  case AF_INET: {
+    int val = IP_PMTUDISC_DO;
+    (void)setsockopt(ctx->sock, IPPROTO_IP, IP_MTU_DISCOVER, &val,
+                     sizeof(val));
+    break;
+  }
+#endif
+#if defined(__linux__) && defined(IPV6_MTU_DISCOVER)
+  case AF_INET6: {
+    int val = IPV6_PMTUDISC_DO;
+    (void)setsockopt(ctx->sock, IPPROTO_IPV6, IPV6_MTU_DISCOVER, &val,
+                     sizeof(val));
+    break;
+  }
+#endif
+  }
+  return CURLE_OK;
+}
+
+static CURLcode cf_udp_connect(struct Curl_cfilter *cf,
+                               struct Curl_easy *data,
+                               bool blocking, bool *done)
+{
+  struct cf_socket_ctx *ctx = cf->ctx;
+  CURLcode result = CURLE_COULDNT_CONNECT;
+
+  (void)blocking;
+  if(cf->connected) {
+    *done = TRUE;
+    return CURLE_OK;
+  }
+  *done = FALSE;
+  if(ctx->sock == CURL_SOCKET_BAD) {
+    result = cf_socket_open(cf, data);
+    if(result) {
+      DEBUGF(LOG_CF(data, cf, "cf_udp_connect(), open failed -> %d", result));
+      if(ctx->sock != CURL_SOCKET_BAD) {
+        socket_close(data, cf->conn, TRUE, ctx->sock);
+        ctx->sock = CURL_SOCKET_BAD;
+      }
+      goto out;
+    }
+
+    if(ctx->transport == TRNSPRT_QUIC) {
+      result = cf_udp_setup_quic(cf, data);
+      if(result)
+        goto out;
+      DEBUGF(LOG_CF(data, cf, "cf_udp_connect(), opened socket=%d (%s:%d)",
+                    ctx->sock, ctx->l_ip, ctx->l_port));
+    }
+    else {
+      DEBUGF(LOG_CF(data, cf, "cf_udp_connect(), opened socket=%d "
+                    "(unconnected)", ctx->sock));
+    }
+    *done = TRUE;
+    cf->connected = TRUE;
+  }
+out:
+  return result;
+}
+
+struct Curl_cftype Curl_cft_udp = {
+  "UDP",
+  CF_TYPE_IP_CONNECT,
+  CURL_LOG_DEFAULT,
+  cf_socket_destroy,
+  cf_udp_connect,
+  cf_socket_close,
+  cf_socket_get_host,
+  cf_socket_get_select_socks,
+  cf_socket_data_pending,
+  cf_socket_send,
+  cf_socket_recv,
+  cf_socket_cntrl,
+  cf_socket_conn_is_alive,
+  Curl_cf_def_conn_keep_alive,
+  cf_socket_query,
+};
+
+CURLcode Curl_cf_udp_create(struct Curl_cfilter **pcf,
+                            struct Curl_easy *data,
+                            struct connectdata *conn,
+                            const struct Curl_addrinfo *ai,
+                            int transport)
+{
+  struct cf_socket_ctx *ctx = NULL;
+  struct Curl_cfilter *cf = NULL;
+  CURLcode result;
+
+  (void)data;
+  (void)conn;
+  DEBUGASSERT(transport == TRNSPRT_UDP || transport == TRNSPRT_QUIC);
+  ctx = calloc(sizeof(*ctx), 1);
+  if(!ctx) {
+    result = CURLE_OUT_OF_MEMORY;
+    goto out;
+  }
+  cf_socket_ctx_init(ctx, ai, transport);
+
+  result = Curl_cf_create(&cf, &Curl_cft_udp, ctx);
+
+out:
+  *pcf = (!result)? cf : NULL;
+  if(result) {
+    Curl_safefree(cf);
+    Curl_safefree(ctx);
+  }
+
+  return result;
+}
+
+/* this is the TCP filter which can also handle this case */
+struct Curl_cftype Curl_cft_unix = {
+  "UNIX",
+  CF_TYPE_IP_CONNECT,
+  CURL_LOG_DEFAULT,
+  cf_socket_destroy,
+  cf_tcp_connect,
+  cf_socket_close,
+  cf_socket_get_host,
+  cf_socket_get_select_socks,
+  cf_socket_data_pending,
+  cf_socket_send,
+  cf_socket_recv,
+  cf_socket_cntrl,
+  cf_socket_conn_is_alive,
+  Curl_cf_def_conn_keep_alive,
+  cf_socket_query,
+};
+
+CURLcode Curl_cf_unix_create(struct Curl_cfilter **pcf,
+                             struct Curl_easy *data,
+                             struct connectdata *conn,
+                             const struct Curl_addrinfo *ai,
+                             int transport)
+{
+  struct cf_socket_ctx *ctx = NULL;
+  struct Curl_cfilter *cf = NULL;
+  CURLcode result;
+
+  (void)data;
+  (void)conn;
+  DEBUGASSERT(transport == TRNSPRT_UNIX);
+  ctx = calloc(sizeof(*ctx), 1);
+  if(!ctx) {
+    result = CURLE_OUT_OF_MEMORY;
+    goto out;
+  }
+  cf_socket_ctx_init(ctx, ai, transport);
+
+  result = Curl_cf_create(&cf, &Curl_cft_unix, ctx);
+
+out:
+  *pcf = (!result)? cf : NULL;
+  if(result) {
+    Curl_safefree(cf);
+    Curl_safefree(ctx);
+  }
+
+  return result;
+}
+
+static CURLcode cf_tcp_accept_connect(struct Curl_cfilter *cf,
+                                      struct Curl_easy *data,
+                                      bool blocking, bool *done)
+{
+  /* we start accepted, if we ever close, we cannot go on */
+  (void)data;
+  (void)blocking;
+  if(cf->connected) {
+    *done = TRUE;
+    return CURLE_OK;
+  }
+  return CURLE_FAILED_INIT;
+}
+
+struct Curl_cftype Curl_cft_tcp_accept = {
+  "TCP-ACCEPT",
+  CF_TYPE_IP_CONNECT,
+  CURL_LOG_DEFAULT,
+  cf_socket_destroy,
+  cf_tcp_accept_connect,
+  cf_socket_close,
+  cf_socket_get_host,              /* TODO: not accurate */
+  cf_socket_get_select_socks,
+  cf_socket_data_pending,
+  cf_socket_send,
+  cf_socket_recv,
+  cf_socket_cntrl,
+  cf_socket_conn_is_alive,
+  Curl_cf_def_conn_keep_alive,
+  cf_socket_query,
+};
+
+CURLcode Curl_conn_tcp_listen_set(struct Curl_easy *data,
+                                  struct connectdata *conn,
+                                  int sockindex, curl_socket_t *s)
+{
+  CURLcode result;
+  struct Curl_cfilter *cf = NULL;
+  struct cf_socket_ctx *ctx = NULL;
+
+  /* replace any existing */
+  Curl_conn_cf_discard_all(data, conn, sockindex);
+  DEBUGASSERT(conn->sock[sockindex] == CURL_SOCKET_BAD);
+
+  ctx = calloc(sizeof(*ctx), 1);
+  if(!ctx) {
+    result = CURLE_OUT_OF_MEMORY;
+    goto out;
+  }
+  ctx->transport = conn->transport;
+  ctx->sock = *s;
+  ctx->accepted = FALSE;
+  result = Curl_cf_create(&cf, &Curl_cft_tcp_accept, ctx);
+  if(result)
+    goto out;
+  Curl_conn_cf_add(data, conn, sockindex, cf);
+
+  conn->sock[sockindex] = ctx->sock;
+  set_local_ip(cf, data);
+  ctx->active = TRUE;
+  ctx->connected_at = Curl_now();
+  cf->connected = TRUE;
+  DEBUGF(LOG_CF(data, cf, "Curl_conn_tcp_listen_set(%d)", (int)ctx->sock));
+
+out:
+  if(result) {
+    Curl_safefree(cf);
+    Curl_safefree(ctx);
+  }
+  return result;
+}
+
+static void set_accepted_remote_ip(struct Curl_cfilter *cf,
+                                   struct Curl_easy *data)
+{
+  struct cf_socket_ctx *ctx = cf->ctx;
+#ifdef HAVE_GETPEERNAME
+  char buffer[STRERROR_LEN];
+  struct Curl_sockaddr_storage ssrem;
+  curl_socklen_t plen;
+
+  ctx->r_ip[0] = 0;
+  ctx->r_port = 0;
+  plen = sizeof(ssrem);
+  memset(&ssrem, 0, plen);
+  if(getpeername(ctx->sock, (struct sockaddr*) &ssrem, &plen)) {
+    int error = SOCKERRNO;
+    failf(data, "getpeername() failed with errno %d: %s",
+          error, Curl_strerror(error, buffer, sizeof(buffer)));
+    return;
+  }
+  if(!Curl_addr2string((struct sockaddr*)&ssrem, plen,
+                       ctx->r_ip, &ctx->r_port)) {
+    failf(data, "ssrem inet_ntop() failed with errno %d: %s",
+          errno, Curl_strerror(errno, buffer, sizeof(buffer)));
+    return;
+  }
+#else
+  ctx->r_ip[0] = 0;
+  ctx->r_port = 0;
+  (void)data;
+#endif
+}
+
+CURLcode Curl_conn_tcp_accepted_set(struct Curl_easy *data,
+                                    struct connectdata *conn,
+                                    int sockindex, curl_socket_t *s)
+{
+  struct Curl_cfilter *cf = NULL;
+  struct cf_socket_ctx *ctx = NULL;
+
+  cf = conn->cfilter[sockindex];
+  if(!cf || cf->cft != &Curl_cft_tcp_accept)
+    return CURLE_FAILED_INIT;
+
+  ctx = cf->ctx;
+  /* discard the listen socket */
+  socket_close(data, conn, TRUE, ctx->sock);
+  ctx->sock = *s;
+  conn->sock[sockindex] = ctx->sock;
+  set_accepted_remote_ip(cf, data);
+  set_local_ip(cf, data);
+  ctx->active = TRUE;
+  ctx->accepted = TRUE;
+  ctx->connected_at = Curl_now();
+  cf->connected = TRUE;
+  DEBUGF(LOG_CF(data, cf, "accepted_set(sock=%d, remote=%s port=%d)",
+         (int)ctx->sock, ctx->r_ip, ctx->r_port));
+
+  return CURLE_OK;
+}
+
+bool Curl_cf_is_socket(struct Curl_cfilter *cf)
+{
+  return cf && (cf->cft == &Curl_cft_tcp ||
+                cf->cft == &Curl_cft_udp ||
+                cf->cft == &Curl_cft_unix ||
+                cf->cft == &Curl_cft_tcp_accept);
+}
+
+CURLcode Curl_cf_socket_peek(struct Curl_cfilter *cf,
+                             struct Curl_easy *data,
+                             curl_socket_t *psock,
+                             const struct Curl_sockaddr_ex **paddr,
+                             const char **pr_ip_str, int *pr_port,
+                             const char **pl_ip_str, int *pl_port)
+{
+  if(Curl_cf_is_socket(cf) && cf->ctx) {
+    struct cf_socket_ctx *ctx = cf->ctx;
+
+    if(psock)
+      *psock = ctx->sock;
+    if(paddr)
+      *paddr = &ctx->addr;
+    if(pr_ip_str)
+      *pr_ip_str = ctx->r_ip;
+    if(pr_port)
+      *pr_port = ctx->r_port;
+    if(pl_port ||pl_ip_str) {
+      set_local_ip(cf, data);
+      if(pl_ip_str)
+        *pl_ip_str = ctx->l_ip;
+      if(pl_port)
+        *pl_port = ctx->l_port;
+    }
+    return CURLE_OK;
+  }
+  return CURLE_FAILED_INIT;
+}
+
Index: trunk/src/libs/curl-8.0.1/lib/cf-socket.h
===================================================================
--- trunk/src/libs/curl-8.0.1/lib/cf-socket.h	(revision 99459)
+++ trunk/src/libs/curl-8.0.1/lib/cf-socket.h	(revision 99459)
@@ -0,0 +1,185 @@
+#ifndef HEADER_CURL_CF_SOCKET_H
+#define HEADER_CURL_CF_SOCKET_H
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+#include "curl_setup.h"
+
+#include "nonblock.h" /* for curlx_nonblock(), formerly Curl_nonblock() */
+#include "sockaddr.h"
+
+struct Curl_addrinfo;
+struct Curl_cfilter;
+struct Curl_easy;
+struct connectdata;
+struct Curl_sockaddr_ex;
+
+/*
+ * The Curl_sockaddr_ex structure is basically libcurl's external API
+ * curl_sockaddr structure with enough space available to directly hold any
+ * protocol-specific address structures. The variable declared here will be
+ * used to pass / receive data to/from the fopensocket callback if this has
+ * been set, before that, it is initialized from parameters.
+ */
+struct Curl_sockaddr_ex {
+  int family;
+  int socktype;
+  int protocol;
+  unsigned int addrlen;
+  union {
+    struct sockaddr addr;
+    struct Curl_sockaddr_storage buff;
+  } _sa_ex_u;
+};
+#define sa_addr _sa_ex_u.addr
+
+
+/*
+ * Create a socket based on info from 'conn' and 'ai'.
+ *
+ * Fill in 'addr' and 'sockfd' accordingly if OK is returned. If the open
+ * socket callback is set, used that!
+ *
+ */
+CURLcode Curl_socket_open(struct Curl_easy *data,
+                            const struct Curl_addrinfo *ai,
+                            struct Curl_sockaddr_ex *addr,
+                            int transport,
+                            curl_socket_t *sockfd);
+
+int Curl_socket_close(struct Curl_easy *data, struct connectdata *conn,
+                      curl_socket_t sock);
+
+/**
+ * Determine the curl code for a socket connect() == -1 with errno.
+ */
+CURLcode Curl_socket_connect_result(struct Curl_easy *data,
+                                    const char *ipaddress, int error);
+
+#ifdef USE_WINSOCK
+/* When you run a program that uses the Windows Sockets API, you may
+   experience slow performance when you copy data to a TCP server.
+
+   https://support.microsoft.com/kb/823764
+
+   Work-around: Make the Socket Send Buffer Size Larger Than the Program Send
+   Buffer Size
+
+*/
+void Curl_sndbufset(curl_socket_t sockfd);
+#else
+#define Curl_sndbufset(y) Curl_nop_stmt
+#endif
+
+/**
+ * Assign the address `ai` to the Curl_sockaddr_ex `dest` and
+ * set the transport used.
+ */
+void Curl_sock_assign_addr(struct Curl_sockaddr_ex *dest,
+                           const struct Curl_addrinfo *ai,
+                           int transport);
+
+/**
+ * Creates a cfilter that opens a TCP socket to the given address
+ * when calling its `connect` implementation.
+ * The filter will not touch any connection/data flags and can be
+ * used in happy eyeballing. Once selected for use, its `_active()`
+ * method needs to be called.
+ */
+CURLcode Curl_cf_tcp_create(struct Curl_cfilter **pcf,
+                            struct Curl_easy *data,
+                            struct connectdata *conn,
+                            const struct Curl_addrinfo *ai,
+                            int transport);
+
+/**
+ * Creates a cfilter that opens a UDP socket to the given address
+ * when calling its `connect` implementation.
+ * The filter will not touch any connection/data flags and can be
+ * used in happy eyeballing. Once selected for use, its `_active()`
+ * method needs to be called.
+ */
+CURLcode Curl_cf_udp_create(struct Curl_cfilter **pcf,
+                            struct Curl_easy *data,
+                            struct connectdata *conn,
+                            const struct Curl_addrinfo *ai,
+                            int transport);
+
+/**
+ * Creates a cfilter that opens a UNIX socket to the given address
+ * when calling its `connect` implementation.
+ * The filter will not touch any connection/data flags and can be
+ * used in happy eyeballing. Once selected for use, its `_active()`
+ * method needs to be called.
+ */
+CURLcode Curl_cf_unix_create(struct Curl_cfilter **pcf,
+                             struct Curl_easy *data,
+                             struct connectdata *conn,
+                             const struct Curl_addrinfo *ai,
+                             int transport);
+
+/**
+ * Creates a cfilter that keeps a listening socket.
+ */
+CURLcode Curl_conn_tcp_listen_set(struct Curl_easy *data,
+                                  struct connectdata *conn,
+                                  int sockindex,
+                                  curl_socket_t *s);
+
+/**
+ * Replace the listen socket with the accept()ed one.
+ */
+CURLcode Curl_conn_tcp_accepted_set(struct Curl_easy *data,
+                                    struct connectdata *conn,
+                                    int sockindex,
+                                    curl_socket_t *s);
+
+/**
+ * Return TRUE iff `cf` is a socket filter.
+ */
+bool Curl_cf_is_socket(struct Curl_cfilter *cf);
+
+/**
+ * Peek at the socket and remote ip/port the socket filter is using.
+ * The filter owns all returned values.
+ * @param psock             pointer to hold socket descriptor or NULL
+ * @param paddr             pointer to hold addr reference or NULL
+ * @param pr_ip_str         pointer to hold remote addr as string or NULL
+ * @param pr_port           pointer to hold remote port number or NULL
+ * @param pl_ip_str         pointer to hold local addr as string or NULL
+ * @param pl_port           pointer to hold local port number or NULL
+ * Returns error if the filter is of invalid type.
+ */
+CURLcode Curl_cf_socket_peek(struct Curl_cfilter *cf,
+                             struct Curl_easy *data,
+                             curl_socket_t *psock,
+                             const struct Curl_sockaddr_ex **paddr,
+                             const char **pr_ip_str, int *pr_port,
+                             const char **pl_ip_str, int *pl_port);
+
+extern struct Curl_cftype Curl_cft_tcp;
+extern struct Curl_cftype Curl_cft_udp;
+extern struct Curl_cftype Curl_cft_unix;
+extern struct Curl_cftype Curl_cft_tcp_accept;
+
+#endif /* HEADER_CURL_CF_SOCKET_H */
Index: trunk/src/libs/curl-8.0.1/lib/curl_log.c
===================================================================
--- trunk/src/libs/curl-8.0.1/lib/curl_log.c	(revision 99459)
+++ trunk/src/libs/curl-8.0.1/lib/curl_log.c	(revision 99459)
@@ -0,0 +1,223 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+
+#include <curl/curl.h>
+
+#include "curl_log.h"
+#include "urldata.h"
+#include "easyif.h"
+#include "cfilters.h"
+#include "timeval.h"
+#include "multiif.h"
+#include "strcase.h"
+
+#include "cf-socket.h"
+#include "connect.h"
+#include "http2.h"
+#include "http_proxy.h"
+#include "cf-https-connect.h"
+#include "socks.h"
+#include "strtok.h"
+#include "vtls/vtls.h"
+#include "vquic/vquic.h"
+
+/* The last 3 #include files should be in this order */
+#include "curl_printf.h"
+#include "curl_memory.h"
+#include "memdebug.h"
+
+
+void Curl_debug(struct Curl_easy *data, curl_infotype type,
+                char *ptr, size_t size)
+{
+  if(data->set.verbose) {
+    static const char s_infotype[CURLINFO_END][3] = {
+      "* ", "< ", "> ", "{ ", "} ", "{ ", "} " };
+    if(data->set.fdebug) {
+      bool inCallback = Curl_is_in_callback(data);
+      Curl_set_in_callback(data, true);
+      (void)(*data->set.fdebug)(data, type, ptr, size, data->set.debugdata);
+      Curl_set_in_callback(data, inCallback);
+    }
+    else {
+      switch(type) {
+      case CURLINFO_TEXT:
+      case CURLINFO_HEADER_OUT:
+      case CURLINFO_HEADER_IN:
+        fwrite(s_infotype[type], 2, 1, data->set.err);
+        fwrite(ptr, size, 1, data->set.err);
+        break;
+      default: /* nada */
+        break;
+      }
+    }
+  }
+}
+
+
+/* Curl_failf() is for messages stating why we failed.
+ * The message SHALL NOT include any LF or CR.
+ */
+void Curl_failf(struct Curl_easy *data, const char *fmt, ...)
+{
+  DEBUGASSERT(!strchr(fmt, '\n'));
+  if(data->set.verbose || data->set.errorbuffer) {
+    va_list ap;
+    int len;
+    char error[CURL_ERROR_SIZE + 2];
+    va_start(ap, fmt);
+    len = mvsnprintf(error, CURL_ERROR_SIZE, fmt, ap);
+
+    if(data->set.errorbuffer && !data->state.errorbuf) {
+      strcpy(data->set.errorbuffer, error);
+      data->state.errorbuf = TRUE; /* wrote error string */
+    }
+    error[len++] = '\n';
+    error[len] = '\0';
+    Curl_debug(data, CURLINFO_TEXT, error, len);
+    va_end(ap);
+  }
+}
+
+/* Curl_infof() is for info message along the way */
+#define MAXINFO 2048
+
+void Curl_infof(struct Curl_easy *data, const char *fmt, ...)
+{
+  DEBUGASSERT(!strchr(fmt, '\n'));
+  if(data && data->set.verbose) {
+    va_list ap;
+    int len;
+    char buffer[MAXINFO + 2];
+    va_start(ap, fmt);
+    len = mvsnprintf(buffer, MAXINFO, fmt, ap);
+    va_end(ap);
+    buffer[len++] = '\n';
+    buffer[len] = '\0';
+    Curl_debug(data, CURLINFO_TEXT, buffer, len);
+  }
+}
+
+#ifdef DEBUGBUILD
+
+void Curl_log_cf_debug(struct Curl_easy *data, struct Curl_cfilter *cf,
+                       const char *fmt, ...)
+{
+  DEBUGASSERT(cf);
+  if(data && Curl_log_cf_is_debug(cf)) {
+    va_list ap;
+    int len;
+    char buffer[MAXINFO + 2];
+    len = msnprintf(buffer, MAXINFO, "[CONN-%ld%s-%s] ",
+                    cf->conn->connection_id, cf->sockindex? "/2" : "",
+                    cf->cft->name);
+    va_start(ap, fmt);
+    len += mvsnprintf(buffer + len, MAXINFO - len, fmt, ap);
+    va_end(ap);
+    buffer[len++] = '\n';
+    buffer[len] = '\0';
+    Curl_debug(data, CURLINFO_TEXT, buffer, len);
+  }
+}
+
+
+static struct Curl_cftype *cf_types[] = {
+  &Curl_cft_tcp,
+  &Curl_cft_udp,
+  &Curl_cft_unix,
+  &Curl_cft_tcp_accept,
+  &Curl_cft_happy_eyeballs,
+  &Curl_cft_setup,
+#ifdef USE_NGHTTP2
+  &Curl_cft_nghttp2,
+#endif
+#ifdef USE_SSL
+  &Curl_cft_ssl,
+  &Curl_cft_ssl_proxy,
+#endif
+#if !defined(CURL_DISABLE_PROXY)
+#if !defined(CURL_DISABLE_HTTP)
+  &Curl_cft_http_proxy,
+#endif /* !CURL_DISABLE_HTTP */
+  &Curl_cft_haproxy,
+  &Curl_cft_socks_proxy,
+#endif /* !CURL_DISABLE_PROXY */
+#ifdef ENABLE_QUIC
+  &Curl_cft_http3,
+#endif
+#if !defined(CURL_DISABLE_HTTP) && !defined(USE_HYPER)
+  &Curl_cft_http_connect,
+#endif
+  NULL,
+};
+
+#ifndef ARRAYSIZE
+#define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0]))
+#endif
+
+CURLcode Curl_log_init(void)
+{
+  const char *setting = getenv("CURL_DEBUG");
+  if(setting) {
+    char *token, *tok_buf, *tmp;
+    size_t i;
+
+    tmp = strdup(setting);
+    if(!tmp)
+      return CURLE_OUT_OF_MEMORY;
+
+    token = strtok_r(tmp, ", ", &tok_buf);
+    while(token) {
+      for(i = 0; cf_types[i]; ++i) {
+        if(strcasecompare(token, cf_types[i]->name)) {
+          cf_types[i]->log_level = CURL_LOG_DEBUG;
+          break;
+        }
+      }
+      token = strtok_r(NULL, ", ", &tok_buf);
+    }
+    free(tmp);
+  }
+  return CURLE_OK;
+}
+#else /* DEBUGBUILD */
+
+CURLcode Curl_log_init(void)
+{
+  return CURLE_OK;
+}
+
+#if !defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L)
+void Curl_log_cf_debug(struct Curl_easy *data, struct Curl_cfilter *cf,
+                       const char *fmt, ...)
+{
+  (void)data;
+  (void)cf;
+  (void)fmt;
+}
+#endif
+
+#endif /* !DEBUGBUILD */
Index: trunk/src/libs/curl-8.0.1/lib/curl_log.h
===================================================================
--- trunk/src/libs/curl-8.0.1/lib/curl_log.h	(revision 99459)
+++ trunk/src/libs/curl-8.0.1/lib/curl_log.h	(revision 99459)
@@ -0,0 +1,138 @@
+#ifndef HEADER_CURL_LOG_H
+#define HEADER_CURL_LOG_H
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+struct Curl_easy;
+struct Curl_cfilter;
+
+/**
+ * Init logging, return != 0 on failure.
+ */
+CURLcode Curl_log_init(void);
+
+
+void Curl_infof(struct Curl_easy *, const char *fmt, ...);
+void Curl_failf(struct Curl_easy *, const char *fmt, ...);
+
+#if defined(CURL_DISABLE_VERBOSE_STRINGS)
+
+#if defined(HAVE_VARIADIC_MACROS_C99)
+#define infof(...)  Curl_nop_stmt
+#elif defined(HAVE_VARIADIC_MACROS_GCC)
+#define infof(x...)  Curl_nop_stmt
+#else
+#error "missing VARIADIC macro define, fix and rebuild!"
+#endif
+
+#else /* CURL_DISABLE_VERBOSE_STRINGS */
+
+#define infof Curl_infof
+
+#endif /* CURL_DISABLE_VERBOSE_STRINGS */
+
+#define failf Curl_failf
+
+
+#define CURL_LOG_DEFAULT  0
+#define CURL_LOG_DEBUG    1
+#define CURL_LOG_TRACE    2
+
+
+/* the function used to output verbose information */
+void Curl_debug(struct Curl_easy *data, curl_infotype type,
+                char *ptr, size_t size);
+
+#ifdef DEBUGBUILD
+
+/* explainer: we have some mix configuration and werror settings
+ * that define HAVE_VARIADIC_MACROS_C99 even though C89 is enforced
+ * on gnuc and some other compiler. Need to treat carefully.
+ */
+#if defined(HAVE_VARIADIC_MACROS_C99) && \
+    defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)
+
+#define LOG_CF(data, cf, ...) \
+  do { if(Curl_log_cf_is_debug(cf)) \
+         Curl_log_cf_debug(data, cf, __VA_ARGS__); } while(0)
+#else
+#define LOG_CF Curl_log_cf_debug
+#endif
+
+void Curl_log_cf_debug(struct Curl_easy *data, struct Curl_cfilter *cf,
+#if defined(__GNUC__) && !defined(printf) &&                    \
+  defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && \
+  !defined(__MINGW32__)
+                       const char *fmt, ...)
+                       __attribute__((format(printf, 3, 4)));
+#else
+                       const char *fmt, ...);
+#endif
+
+#define Curl_log_cf_is_debug(cf) \
+    ((cf) && (cf)->cft->log_level >= CURL_LOG_DEBUG)
+
+#else /* !DEBUGBUILD */
+
+#if defined(HAVE_VARIADIC_MACROS_C99) && \
+    defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)
+#define LOG_CF(...)               Curl_nop_stmt
+#define Curl_log_cf_debug(...)    Curl_nop_stmt
+#elif defined(HAVE_VARIADIC_MACROS_GCC) && \
+    defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)
+#define LOG_CF(x...)              Curl_nop_stmt
+#define Curl_log_cf_debug(x...)   Curl_nop_stmt
+#else
+#define LOG_CF                    Curl_log_cf_debug
+/* without c99, we seem unable to completely define away this function. */
+void Curl_log_cf_debug(struct Curl_easy *data, struct Curl_cfilter *cf,
+                       const char *fmt, ...);
+#endif
+
+#define Curl_log_cf_is_debug(x)   ((void)(x), FALSE)
+
+#endif  /* !DEBUGBUILD */
+
+#define LOG_CF_IS_DEBUG(x)        Curl_log_cf_is_debug(x)
+
+/* Macros intended for DEBUGF logging, use like:
+ * DEBUGF(infof(data, CFMSG(cf, "this filter %s rocks"), "very much"));
+ * and it will output:
+ * [CONN-1-0][CF-SSL] this filter very much rocks
+ * on connection #1 with sockindex 0 for filter of type "SSL". */
+#define DMSG(d,msg)  \
+  "[CONN-%ld] "msg, (d)->conn->connection_id
+#define DMSGI(d,i,msg)  \
+  "[CONN-%ld-%d] "msg, (d)->conn->connection_id, (i)
+#define CMSG(c,msg)  \
+  "[CONN-%ld] "msg, (c)->connection_id
+#define CMSGI(c,i,msg)  \
+  "[CONN-%ld-%d] "msg, (c)->connection_id, (i)
+#define CFMSG(cf,msg)  \
+  "[CONN-%ld-%d][CF-%s] "msg, (cf)->conn->connection_id, \
+  (cf)->sockindex, (cf)->cft->name
+
+
+
+#endif /* HEADER_CURL_LOG_H */
Index: trunk/src/libs/curl-8.0.1/lib/vquic/curl_msh3.c
===================================================================
--- trunk/src/libs/curl-8.0.1/lib/vquic/curl_msh3.c	(revision 99459)
+++ trunk/src/libs/curl-8.0.1/lib/vquic/curl_msh3.c	(revision 99459)
@@ -0,0 +1,850 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+
+#ifdef USE_MSH3
+
+#include "urldata.h"
+#include "timeval.h"
+#include "multiif.h"
+#include "sendf.h"
+#include "curl_log.h"
+#include "cfilters.h"
+#include "cf-socket.h"
+#include "connect.h"
+#include "progress.h"
+#include "h2h3.h"
+#include "curl_msh3.h"
+#include "socketpair.h"
+#include "vquic/vquic.h"
+
+/* The last 3 #include files should be in this order */
+#include "curl_printf.h"
+#include "curl_memory.h"
+#include "memdebug.h"
+
+#define DEBUG_CF 1
+
+#if DEBUG_CF && defined(DEBUGBUILD)
+#define CF_DEBUGF(x) x
+#else
+#define CF_DEBUGF(x) do { } while(0)
+#endif
+
+#define MSH3_REQ_INIT_BUF_LEN 16384
+#define MSH3_REQ_MAX_BUF_LEN 0x100000
+
+#ifdef _WIN32
+#define msh3_lock CRITICAL_SECTION
+#define msh3_lock_initialize(lock) InitializeCriticalSection(lock)
+#define msh3_lock_uninitialize(lock) DeleteCriticalSection(lock)
+#define msh3_lock_acquire(lock) EnterCriticalSection(lock)
+#define msh3_lock_release(lock) LeaveCriticalSection(lock)
+#else /* !_WIN32 */
+#include <pthread.h>
+#define msh3_lock pthread_mutex_t
+#define msh3_lock_initialize(lock) do { \
+  pthread_mutexattr_t attr; \
+  pthread_mutexattr_init(&attr); \
+  pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); \
+  pthread_mutex_init(lock, &attr); \
+  pthread_mutexattr_destroy(&attr); \
+}while(0)
+#define msh3_lock_uninitialize(lock) pthread_mutex_destroy(lock)
+#define msh3_lock_acquire(lock) pthread_mutex_lock(lock)
+#define msh3_lock_release(lock) pthread_mutex_unlock(lock)
+#endif /* _WIN32 */
+
+
+static void MSH3_CALL msh3_conn_connected(MSH3_CONNECTION *Connection,
+                                          void *IfContext);
+static void MSH3_CALL msh3_conn_shutdown_complete(MSH3_CONNECTION *Connection,
+                                          void *IfContext);
+static void MSH3_CALL msh3_conn_new_request(MSH3_CONNECTION *Connection,
+                                          void *IfContext,
+                                          MSH3_REQUEST *Request);
+static void MSH3_CALL msh3_header_received(MSH3_REQUEST *Request,
+                                           void *IfContext,
+                                           const MSH3_HEADER *Header);
+static bool MSH3_CALL msh3_data_received(MSH3_REQUEST *Request,
+                                        void *IfContext, uint32_t *Length,
+                                        const uint8_t *Data);
+static void MSH3_CALL msh3_complete(MSH3_REQUEST *Request, void *IfContext,
+                                    bool Aborted, uint64_t AbortError);
+static void MSH3_CALL msh3_shutdown_complete(MSH3_REQUEST *Request,
+                                             void *IfContext);
+static void MSH3_CALL msh3_data_sent(MSH3_REQUEST *Request,
+                                     void *IfContext, void *SendContext);
+
+
+void Curl_msh3_ver(char *p, size_t len)
+{
+  uint32_t v[4];
+  MsH3Version(v);
+  (void)msnprintf(p, len, "msh3/%d.%d.%d.%d", v[0], v[1], v[2], v[3]);
+}
+
+#define SP_LOCAL   0
+#define SP_REMOTE  1
+
+struct cf_msh3_ctx {
+  MSH3_API *api;
+  MSH3_CONNECTION *qconn;
+  struct Curl_sockaddr_ex addr;
+  curl_socket_t sock[2]; /* fake socket pair until we get support in msh3 */
+  char l_ip[MAX_IPADR_LEN];          /* local IP as string */
+  int l_port;                        /* local port number */
+  struct curltime connect_started;   /* time the current attempt started */
+  struct curltime handshake_at;      /* time connect handshake finished */
+  /* Flags written by msh3/msquic thread */
+  bool handshake_complete;
+  bool handshake_succeeded;
+  bool connected;
+  /* Flags written by curl thread */
+  BIT(verbose);
+  BIT(active);
+};
+
+static const MSH3_CONNECTION_IF msh3_conn_if = {
+  msh3_conn_connected,
+  msh3_conn_shutdown_complete,
+  msh3_conn_new_request
+};
+
+static void MSH3_CALL msh3_conn_connected(MSH3_CONNECTION *Connection,
+                                          void *IfContext)
+{
+  struct cf_msh3_ctx *ctx = IfContext;
+  (void)Connection;
+  if(ctx->verbose)
+    CF_DEBUGF(fprintf(stderr, "* [MSH3] evt: connected\n"));
+  ctx->handshake_succeeded = true;
+  ctx->connected = true;
+  ctx->handshake_complete = true;
+}
+
+static void MSH3_CALL msh3_conn_shutdown_complete(MSH3_CONNECTION *Connection,
+                                          void *IfContext)
+{
+  struct cf_msh3_ctx *ctx = IfContext;
+  (void)Connection;
+  if(ctx->verbose)
+    CF_DEBUGF(fprintf(stderr, "* [MSH3] evt: shutdown complete\n"));
+  ctx->connected = false;
+  ctx->handshake_complete = true;
+}
+
+static void MSH3_CALL msh3_conn_new_request(MSH3_CONNECTION *Connection,
+                                          void *IfContext,
+                                          MSH3_REQUEST *Request)
+{
+  (void)Connection;
+  (void)IfContext;
+  (void)Request;
+}
+
+static const MSH3_REQUEST_IF msh3_request_if = {
+  msh3_header_received,
+  msh3_data_received,
+  msh3_complete,
+  msh3_shutdown_complete,
+  msh3_data_sent
+};
+
+static CURLcode msh3_data_setup(struct Curl_cfilter *cf,
+                                struct Curl_easy *data)
+{
+  struct HTTP *stream = data->req.p.http;
+  (void)cf;
+
+  DEBUGASSERT(stream);
+  if(!stream->recv_buf) {
+    DEBUGF(LOG_CF(data, cf, "req: setup"));
+    stream->recv_buf = malloc(MSH3_REQ_INIT_BUF_LEN);
+    if(!stream->recv_buf) {
+      return CURLE_OUT_OF_MEMORY;
+    }
+    stream->req = ZERO_NULL;
+    msh3_lock_initialize(&stream->recv_lock);
+    stream->recv_buf_alloc = MSH3_REQ_INIT_BUF_LEN;
+    stream->recv_buf_max = MSH3_REQ_MAX_BUF_LEN;
+    stream->recv_header_len = 0;
+    stream->recv_header_complete = false;
+    stream->recv_data_len = 0;
+    stream->recv_data_complete = false;
+    stream->recv_error = CURLE_OK;
+  }
+  return CURLE_OK;
+}
+
+/* Requires stream->recv_lock to be held */
+static bool msh3request_ensure_room(struct HTTP *stream, size_t len)
+{
+  uint8_t *new_recv_buf;
+  const size_t cur_recv_len = stream->recv_header_len + stream->recv_data_len;
+
+  if(cur_recv_len + len > stream->recv_buf_alloc) {
+    size_t new_recv_buf_alloc_len = stream->recv_buf_alloc;
+    do {
+      new_recv_buf_alloc_len <<= 1; /* TODO - handle overflow */
+    } while(cur_recv_len + len > new_recv_buf_alloc_len);
+    CF_DEBUGF(fprintf(stderr, "* enlarging buffer to %zu\n",
+              new_recv_buf_alloc_len));
+    new_recv_buf = malloc(new_recv_buf_alloc_len);
+    if(!new_recv_buf) {
+      CF_DEBUGF(fprintf(stderr, "* FAILED: enlarging buffer to %zu\n",
+                new_recv_buf_alloc_len));
+      return false;
+    }
+    if(cur_recv_len) {
+      memcpy(new_recv_buf, stream->recv_buf, cur_recv_len);
+    }
+    stream->recv_buf_alloc = new_recv_buf_alloc_len;
+    free(stream->recv_buf);
+    stream->recv_buf = new_recv_buf;
+  }
+  return true;
+}
+
+static void MSH3_CALL msh3_header_received(MSH3_REQUEST *Request,
+                                           void *IfContext,
+                                           const MSH3_HEADER *Header)
+{
+  struct Curl_easy *data = IfContext;
+  struct HTTP *stream = data->req.p.http;
+  size_t total_len;
+  (void)Request;
+
+  if(stream->recv_header_complete) {
+    CF_DEBUGF(fprintf(stderr, "* ignoring header after data\n"));
+    return;
+  }
+
+  msh3_lock_acquire(&stream->recv_lock);
+
+  if((Header->NameLength == 7) &&
+     !strncmp(H2H3_PSEUDO_STATUS, (char *)Header->Name, 7)) {
+    total_len = 10 + Header->ValueLength;
+    if(!msh3request_ensure_room(stream, total_len)) {
+      CF_DEBUGF(fprintf(stderr, "* ERROR: unable to buffer: %.*s\n",
+                (int)Header->NameLength, Header->Name));
+      stream->recv_error = CURLE_OUT_OF_MEMORY;
+      goto release_lock;
+    }
+    msnprintf((char *)stream->recv_buf + stream->recv_header_len,
+              stream->recv_buf_alloc - stream->recv_header_len,
+              "HTTP/3 %.*s \r\n", (int)Header->ValueLength, Header->Value);
+  }
+  else {
+    total_len = 4 + Header->NameLength + Header->ValueLength;
+    if(!msh3request_ensure_room(stream, total_len)) {
+      CF_DEBUGF(fprintf(stderr, "* ERROR: unable to buffer: %.*s\n",
+                (int)Header->NameLength, Header->Name));
+      stream->recv_error = CURLE_OUT_OF_MEMORY;
+      goto release_lock;
+    }
+    msnprintf((char *)stream->recv_buf + stream->recv_header_len,
+              stream->recv_buf_alloc - stream->recv_header_len,
+              "%.*s: %.*s\r\n",
+              (int)Header->NameLength, Header->Name,
+              (int)Header->ValueLength, Header->Value);
+  }
+
+  stream->recv_header_len += total_len;
+  data->state.drain = 1;
+
+release_lock:
+  msh3_lock_release(&stream->recv_lock);
+}
+
+static bool MSH3_CALL msh3_data_received(MSH3_REQUEST *Request,
+                                         void *IfContext, uint32_t *Length,
+                                         const uint8_t *Data)
+{
+  struct Curl_easy *data = IfContext;
+  struct HTTP *stream = data->req.p.http;
+  size_t cur_recv_len = stream->recv_header_len + stream->recv_data_len;
+
+  (void)Request;
+  if(data && data->set.verbose)
+    CF_DEBUGF(fprintf(stderr, "* [MSH3] req: evt: received %u. %zu buffered, "
+              "%zu allocated\n",
+              *Length, cur_recv_len, stream->recv_buf_alloc));
+  /* TODO - Update this code to limit data bufferring by `stream->recv_buf_max`
+     and return `false` when we reach that limit. Then, when curl drains some
+     of the buffer, making room, call MsH3RequestSetReceiveEnabled to enable
+     receive callbacks again. */
+  msh3_lock_acquire(&stream->recv_lock);
+
+  if(!stream->recv_header_complete) {
+    if(data && data->set.verbose)
+      CF_DEBUGF(fprintf(stderr, "* [MSH3] req: Headers complete!\n"));
+    if(!msh3request_ensure_room(stream, 2)) {
+      stream->recv_error = CURLE_OUT_OF_MEMORY;
+      goto release_lock;
+    }
+    stream->recv_buf[stream->recv_header_len++] = '\r';
+    stream->recv_buf[stream->recv_header_len++] = '\n';
+    stream->recv_header_complete = true;
+    cur_recv_len += 2;
+  }
+  if(!msh3request_ensure_room(stream, *Length)) {
+    stream->recv_error = CURLE_OUT_OF_MEMORY;
+    goto release_lock;
+  }
+  memcpy(stream->recv_buf + cur_recv_len, Data, *Length);
+  stream->recv_data_len += (size_t)*Length;
+  data->state.drain = 1;
+
+release_lock:
+  msh3_lock_release(&stream->recv_lock);
+  return true;
+}
+
+static void MSH3_CALL msh3_complete(MSH3_REQUEST *Request, void *IfContext,
+                                    bool Aborted, uint64_t AbortError)
+{
+  struct Curl_easy *data = IfContext;
+  struct HTTP *stream = data->req.p.http;
+
+  (void)Request;
+  (void)AbortError;
+  if(data && data->set.verbose)
+    CF_DEBUGF(fprintf(stderr, "* [MSH3] req: evt: complete, aborted=%s\n",
+              Aborted ? "true" : "false"));
+  msh3_lock_acquire(&stream->recv_lock);
+  if(Aborted) {
+    stream->recv_error = CURLE_HTTP3; /* TODO - how do we pass AbortError? */
+  }
+  stream->recv_header_complete = true;
+  stream->recv_data_complete = true;
+  msh3_lock_release(&stream->recv_lock);
+}
+
+static void MSH3_CALL msh3_shutdown_complete(MSH3_REQUEST *Request,
+                                             void *IfContext)
+{
+  struct Curl_easy *data = IfContext;
+  struct HTTP *stream = data->req.p.http;
+  (void)Request;
+  (void)stream;
+}
+
+static void MSH3_CALL msh3_data_sent(MSH3_REQUEST *Request,
+                                     void *IfContext, void *SendContext)
+{
+  struct Curl_easy *data = IfContext;
+  struct HTTP *stream = data->req.p.http;
+  (void)Request;
+  (void)stream;
+  (void)SendContext;
+}
+
+static ssize_t cf_msh3_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
+                            char *buf, size_t len, CURLcode *err)
+{
+  struct HTTP *stream = data->req.p.http;
+  size_t outsize = 0;
+
+  (void)cf;
+  DEBUGF(LOG_CF(data, cf, "req: recv with %zu byte buffer", len));
+
+  if(stream->recv_error) {
+    failf(data, "request aborted");
+    data->state.drain = 0;
+    *err = stream->recv_error;
+    return -1;
+  }
+
+  *err = CURLE_OK;
+  msh3_lock_acquire(&stream->recv_lock);
+
+  if(stream->recv_header_len) {
+    outsize = len;
+    if(stream->recv_header_len < outsize) {
+      outsize = stream->recv_header_len;
+    }
+    memcpy(buf, stream->recv_buf, outsize);
+    if(outsize < stream->recv_header_len + stream->recv_data_len) {
+      memmove(stream->recv_buf, stream->recv_buf + outsize,
+              stream->recv_header_len + stream->recv_data_len - outsize);
+    }
+    stream->recv_header_len -= outsize;
+    DEBUGF(LOG_CF(data, cf, "req: returned %zu bytes of header", outsize));
+  }
+  else if(stream->recv_data_len) {
+    outsize = len;
+    if(stream->recv_data_len < outsize) {
+      outsize = stream->recv_data_len;
+    }
+    memcpy(buf, stream->recv_buf, outsize);
+    if(outsize < stream->recv_data_len) {
+      memmove(stream->recv_buf, stream->recv_buf + outsize,
+              stream->recv_data_len - outsize);
+    }
+    stream->recv_data_len -= outsize;
+    DEBUGF(LOG_CF(data, cf, "req: returned %zu bytes of data", outsize));
+    if(stream->recv_data_len == 0 && stream->recv_data_complete)
+      data->state.drain = 1;
+  }
+  else if(stream->recv_data_complete) {
+    DEBUGF(LOG_CF(data, cf, "req: receive complete"));
+    data->state.drain = 0;
+  }
+  else {
+    DEBUGF(LOG_CF(data, cf, "req: nothing here, call again"));
+    *err = CURLE_AGAIN;
+    outsize = -1;
+  }
+
+  msh3_lock_release(&stream->recv_lock);
+
+  return (ssize_t)outsize;
+}
+
+static ssize_t cf_msh3_send(struct Curl_cfilter *cf, struct Curl_easy *data,
+                            const void *buf, size_t len, CURLcode *err)
+{
+  struct cf_msh3_ctx *ctx = cf->ctx;
+  struct HTTP *stream = data->req.p.http;
+  struct h2h3req *hreq;
+  size_t hdrlen = 0;
+  size_t sentlen = 0;
+
+  /* Sizes must match for cast below to work" */
+  DEBUGASSERT(sizeof(MSH3_HEADER) == sizeof(struct h2h3pseudo));
+  DEBUGF(LOG_CF(data, cf, "req: send %zu bytes", len));
+
+  if(!stream->req) {
+    /* The first send on the request contains the headers and possibly some
+       data. Parse out the headers and create the request, then if there is
+       any data left over go ahead and send it too. */
+
+    *err = msh3_data_setup(cf, data);
+    if(*err) {
+      failf(data, "could not setup data");
+      return -1;
+    }
+
+    *err = Curl_pseudo_headers(data, buf, len, &hdrlen, &hreq);
+    if(*err) {
+      failf(data, "Curl_pseudo_headers failed");
+      return -1;
+    }
+
+    DEBUGF(LOG_CF(data, cf, "req: send %zu headers", hreq->entries));
+    stream->req = MsH3RequestOpen(ctx->qconn, &msh3_request_if, data,
+                                  (MSH3_HEADER*)hreq->header, hreq->entries,
+                                  hdrlen == len ? MSH3_REQUEST_FLAG_FIN :
+                                  MSH3_REQUEST_FLAG_NONE);
+    Curl_pseudo_free(hreq);
+    if(!stream->req) {
+      failf(data, "request open failed");
+      *err = CURLE_SEND_ERROR;
+      return -1;
+    }
+    *err = CURLE_OK;
+    return len;
+  }
+
+  DEBUGF(LOG_CF(data, cf, "req: send %zd body bytes", len));
+  if(len > 0xFFFFFFFF) {
+    /* msh3 doesn't support size_t sends currently. */
+    *err = CURLE_SEND_ERROR;
+    return -1;
+  }
+
+  /* TODO - Need an explicit signal to know when to FIN. */
+  if(!MsH3RequestSend(stream->req, MSH3_REQUEST_FLAG_FIN, buf, (uint32_t)len,
+                      stream)) {
+    *err = CURLE_SEND_ERROR;
+    return -1;
+  }
+
+  /* TODO - msh3/msquic will hold onto this memory until the send complete
+     event. How do we make sure curl doesn't free it until then? */
+  sentlen += len;
+  *err = CURLE_OK;
+  return sentlen;
+}
+
+static int cf_msh3_get_select_socks(struct Curl_cfilter *cf,
+                                    struct Curl_easy *data,
+                                    curl_socket_t *socks)
+{
+  struct cf_msh3_ctx *ctx = cf->ctx;
+  struct HTTP *stream = data->req.p.http;
+  int bitmap = GETSOCK_BLANK;
+
+  if(stream && ctx->sock[SP_LOCAL] != CURL_SOCKET_BAD) {
+    socks[0] = ctx->sock[SP_LOCAL];
+
+    if(stream->recv_error) {
+      bitmap |= GETSOCK_READSOCK(0);
+      data->state.drain = 1;
+    }
+    else if(stream->recv_header_len || stream->recv_data_len) {
+      bitmap |= GETSOCK_READSOCK(0);
+      data->state.drain = 1;
+    }
+  }
+  DEBUGF(LOG_CF(data, cf, "select_sock %u -> %d",
+                (uint32_t)data->state.drain, bitmap));
+
+  return bitmap;
+}
+
+static bool cf_msh3_data_pending(struct Curl_cfilter *cf,
+                                 const struct Curl_easy *data)
+{
+  struct HTTP *stream = data->req.p.http;
+
+  (void)cf;
+  DEBUGF(LOG_CF((struct Curl_easy *)data, cf, "data pending = %hhu",
+                (bool)(stream->recv_header_len || stream->recv_data_len)));
+  return stream->recv_header_len || stream->recv_data_len;
+}
+
+static void cf_msh3_active(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+  struct cf_msh3_ctx *ctx = cf->ctx;
+
+  /* use this socket from now on */
+  cf->conn->sock[cf->sockindex] = ctx->sock[SP_LOCAL];
+  /* the first socket info gets set at conn and data */
+  if(cf->sockindex == FIRSTSOCKET) {
+    cf->conn->remote_addr = &ctx->addr;
+  #ifdef ENABLE_IPV6
+    cf->conn->bits.ipv6 = (ctx->addr.family == AF_INET6)? TRUE : FALSE;
+  #endif
+    Curl_persistconninfo(data, cf->conn, ctx->l_ip, ctx->l_port);
+  }
+  ctx->active = TRUE;
+}
+
+static CURLcode cf_msh3_data_event(struct Curl_cfilter *cf,
+                                   struct Curl_easy *data,
+                                   int event, int arg1, void *arg2)
+{
+  struct HTTP *stream = data->req.p.http;
+  CURLcode result = CURLE_OK;
+
+  (void)arg1;
+  (void)arg2;
+  switch(event) {
+  case CF_CTRL_DATA_SETUP:
+    result = msh3_data_setup(cf, data);
+    break;
+  case CF_CTRL_DATA_DONE:
+    DEBUGF(LOG_CF(data, cf, "req: done"));
+    if(stream) {
+      if(stream->recv_buf) {
+        Curl_safefree(stream->recv_buf);
+        msh3_lock_uninitialize(&stream->recv_lock);
+      }
+      if(stream->req) {
+        MsH3RequestClose(stream->req);
+        stream->req = ZERO_NULL;
+      }
+    }
+    break;
+  case CF_CTRL_DATA_DONE_SEND:
+    DEBUGF(LOG_CF(data, cf, "req: send done"));
+    stream->upload_done = TRUE;
+    break;
+  case CF_CTRL_CONN_INFO_UPDATE:
+    DEBUGF(LOG_CF(data, cf, "req: update info"));
+    cf_msh3_active(cf, data);
+    break;
+  default:
+    break;
+  }
+  return result;
+}
+
+static CURLcode cf_connect_start(struct Curl_cfilter *cf,
+                                 struct Curl_easy *data)
+{
+  struct cf_msh3_ctx *ctx = cf->ctx;
+  bool verify = !!cf->conn->ssl_config.verifypeer;
+  MSH3_ADDR addr = {0};
+  memcpy(&addr, &ctx->addr.sa_addr, ctx->addr.addrlen);
+  MSH3_SET_PORT(&addr, (uint16_t)cf->conn->remote_port);
+  ctx->verbose = (data && data->set.verbose);
+
+  if(verify && (cf->conn->ssl_config.CAfile || cf->conn->ssl_config.CApath)) {
+    /* TODO: need a way to provide trust anchors to MSH3 */
+#ifdef DEBUGBUILD
+    /* we need this for our test cases to run */
+    DEBUGF(LOG_CF(data, cf, "non-standard CA not supported, "
+                  "switching off verifypeer in DEBUG mode"));
+    verify = 0;
+#else
+    DEBUGF(LOG_CF(data, cf, "non-standard CA not supported, "
+                  "attempting with built-in verification"));
+#endif
+  }
+
+  DEBUGF(LOG_CF(data, cf, "connecting to %s:%d (verify=%d)",
+                cf->conn->host.name, (int)cf->conn->remote_port, verify));
+
+  ctx->api = MsH3ApiOpen();
+  if(!ctx->api) {
+    failf(data, "can't create msh3 api");
+    return CURLE_FAILED_INIT;
+  }
+
+  ctx->qconn = MsH3ConnectionOpen(ctx->api,
+                                  &msh3_conn_if,
+                                  ctx,
+                                  cf->conn->host.name,
+                                  &addr,
+                                  !verify);
+  if(!ctx->qconn) {
+    failf(data, "can't create msh3 connection");
+    if(ctx->api) {
+      MsH3ApiClose(ctx->api);
+      ctx->api = NULL;
+    }
+    return CURLE_FAILED_INIT;
+  }
+
+  return CURLE_OK;
+}
+
+static CURLcode cf_msh3_connect(struct Curl_cfilter *cf,
+                                struct Curl_easy *data,
+                                bool blocking, bool *done)
+{
+  struct cf_msh3_ctx *ctx = cf->ctx;
+  CURLcode result = CURLE_OK;
+
+  (void)blocking;
+  if(cf->connected) {
+    *done = TRUE;
+    return CURLE_OK;
+  }
+
+  if(ctx->sock[SP_LOCAL] == CURL_SOCKET_BAD) {
+    if(Curl_socketpair(AF_UNIX, SOCK_STREAM, 0, &ctx->sock[0]) < 0) {
+      ctx->sock[SP_LOCAL] = CURL_SOCKET_BAD;
+      ctx->sock[SP_REMOTE] = CURL_SOCKET_BAD;
+      return CURLE_COULDNT_CONNECT;
+    }
+  }
+
+  *done = FALSE;
+  if(!ctx->qconn) {
+    ctx->connect_started = Curl_now();
+    result = cf_connect_start(cf, data);
+    if(result)
+      goto out;
+  }
+
+  if(ctx->handshake_complete) {
+    ctx->handshake_at = Curl_now();
+    if(ctx->handshake_succeeded) {
+      cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
+      cf->conn->httpversion = 30;
+      cf->conn->bundle->multiuse = BUNDLE_MULTIPLEX;
+      cf->connected = TRUE;
+      cf->conn->alpn = CURL_HTTP_VERSION_3;
+      *done = TRUE;
+      connkeep(cf->conn, "HTTP/3 default");
+      Curl_pgrsTime(data, TIMER_APPCONNECT);
+    }
+    else {
+      failf(data, "failed to connect, handshake failed");
+      result = CURLE_COULDNT_CONNECT;
+    }
+  }
+
+out:
+  return result;
+}
+
+static void cf_msh3_close(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+  struct cf_msh3_ctx *ctx = cf->ctx;
+
+  (void)data;
+  if(ctx) {
+    DEBUGF(LOG_CF(data, cf, "destroying"));
+    if(ctx->qconn)
+      MsH3ConnectionClose(ctx->qconn);
+    if(ctx->api)
+      MsH3ApiClose(ctx->api);
+
+    if(ctx->active) {
+      /* We share our socket at cf->conn->sock[cf->sockindex] when active.
+       * If it is no longer there, someone has stolen (and hopefully
+       * closed it) and we just forget about it.
+       */
+      if(ctx->sock[SP_LOCAL] == cf->conn->sock[cf->sockindex]) {
+        DEBUGF(LOG_CF(data, cf, "cf_msh3_close(%d) active",
+                      (int)ctx->sock[SP_LOCAL]));
+        cf->conn->sock[cf->sockindex] = CURL_SOCKET_BAD;
+      }
+      else {
+        DEBUGF(LOG_CF(data, cf, "cf_socket_close(%d) no longer at "
+                      "conn->sock[], discarding", (int)ctx->sock[SP_LOCAL]));
+        ctx->sock[SP_LOCAL] = CURL_SOCKET_BAD;
+      }
+      if(cf->sockindex == FIRSTSOCKET)
+        cf->conn->remote_addr = NULL;
+    }
+    if(ctx->sock[SP_LOCAL] != CURL_SOCKET_BAD) {
+      sclose(ctx->sock[SP_LOCAL]);
+    }
+    if(ctx->sock[SP_REMOTE] != CURL_SOCKET_BAD) {
+      sclose(ctx->sock[SP_REMOTE]);
+    }
+    memset(ctx, 0, sizeof(*ctx));
+    ctx->sock[SP_LOCAL] = CURL_SOCKET_BAD;
+    ctx->sock[SP_REMOTE] = CURL_SOCKET_BAD;
+  }
+}
+
+static void cf_msh3_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+  cf_msh3_close(cf, data);
+  free(cf->ctx);
+  cf->ctx = NULL;
+}
+
+static CURLcode cf_msh3_query(struct Curl_cfilter *cf,
+                              struct Curl_easy *data,
+                              int query, int *pres1, void *pres2)
+{
+  struct cf_msh3_ctx *ctx = cf->ctx;
+
+  switch(query) {
+  case CF_QUERY_MAX_CONCURRENT: {
+    /* TODO: we do not have access to this so far, fake it */
+    (void)ctx;
+    *pres1 = 100;
+    return CURLE_OK;
+  }
+  case CF_QUERY_TIMER_CONNECT: {
+    struct curltime *when = pres2;
+    /* we do not know when the first byte arrived */
+    if(cf->connected)
+      *when = ctx->handshake_at;
+    return CURLE_OK;
+  }
+  case CF_QUERY_TIMER_APPCONNECT: {
+    struct curltime *when = pres2;
+    if(cf->connected)
+      *when = ctx->handshake_at;
+    return CURLE_OK;
+  }
+  default:
+    break;
+  }
+  return cf->next?
+    cf->next->cft->query(cf->next, data, query, pres1, pres2) :
+    CURLE_UNKNOWN_OPTION;
+}
+
+static bool cf_msh3_conn_is_alive(struct Curl_cfilter *cf,
+                                  struct Curl_easy *data,
+                                  bool *input_pending)
+{
+  struct cf_msh3_ctx *ctx = cf->ctx;
+
+  (void)data;
+  *input_pending = FALSE;
+  return ctx && ctx->sock[SP_LOCAL] != CURL_SOCKET_BAD && ctx->qconn &&
+         ctx->connected;
+}
+
+struct Curl_cftype Curl_cft_http3 = {
+  "HTTP/3",
+  CF_TYPE_IP_CONNECT | CF_TYPE_SSL | CF_TYPE_MULTIPLEX,
+  0,
+  cf_msh3_destroy,
+  cf_msh3_connect,
+  cf_msh3_close,
+  Curl_cf_def_get_host,
+  cf_msh3_get_select_socks,
+  cf_msh3_data_pending,
+  cf_msh3_send,
+  cf_msh3_recv,
+  cf_msh3_data_event,
+  cf_msh3_conn_is_alive,
+  Curl_cf_def_conn_keep_alive,
+  cf_msh3_query,
+};
+
+CURLcode Curl_cf_msh3_create(struct Curl_cfilter **pcf,
+                             struct Curl_easy *data,
+                             struct connectdata *conn,
+                             const struct Curl_addrinfo *ai)
+{
+  struct cf_msh3_ctx *ctx = NULL;
+  struct Curl_cfilter *cf = NULL;
+  CURLcode result;
+
+  (void)data;
+  (void)conn;
+  (void)ai; /* TODO: msh3 resolves itself? */
+  ctx = calloc(sizeof(*ctx), 1);
+  if(!ctx) {
+    result = CURLE_OUT_OF_MEMORY;
+    goto out;
+  }
+  Curl_sock_assign_addr(&ctx->addr, ai, TRNSPRT_QUIC);
+  ctx->sock[SP_LOCAL] = CURL_SOCKET_BAD;
+  ctx->sock[SP_REMOTE] = CURL_SOCKET_BAD;
+
+  result = Curl_cf_create(&cf, &Curl_cft_http3, ctx);
+
+out:
+  *pcf = (!result)? cf : NULL;
+  if(result) {
+    Curl_safefree(cf);
+    Curl_safefree(ctx);
+  }
+
+  return result;
+}
+
+bool Curl_conn_is_msh3(const struct Curl_easy *data,
+                       const struct connectdata *conn,
+                       int sockindex)
+{
+  struct Curl_cfilter *cf = conn? conn->cfilter[sockindex] : NULL;
+
+  (void)data;
+  for(; cf; cf = cf->next) {
+    if(cf->cft == &Curl_cft_http3)
+      return TRUE;
+    if(cf->cft->flags & CF_TYPE_IP_CONNECT)
+      return FALSE;
+  }
+  return FALSE;
+}
+
+#endif /* USE_MSH3 */
Index: trunk/src/libs/curl-8.0.1/lib/vquic/curl_msh3.h
===================================================================
--- trunk/src/libs/curl-8.0.1/lib/vquic/curl_msh3.h	(revision 99459)
+++ trunk/src/libs/curl-8.0.1/lib/vquic/curl_msh3.h	(revision 99459)
@@ -0,0 +1,46 @@
+#ifndef HEADER_CURL_VQUIC_CURL_MSH3_H
+#define HEADER_CURL_VQUIC_CURL_MSH3_H
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+
+#ifdef USE_MSH3
+
+#include <msh3.h>
+
+void Curl_msh3_ver(char *p, size_t len);
+
+CURLcode Curl_cf_msh3_create(struct Curl_cfilter **pcf,
+                             struct Curl_easy *data,
+                             struct connectdata *conn,
+                             const struct Curl_addrinfo *ai);
+
+bool Curl_conn_is_msh3(const struct Curl_easy *data,
+                       const struct connectdata *conn,
+                       int sockindex);
+
+#endif /* USE_MSQUIC */
+
+#endif /* HEADER_CURL_VQUIC_CURL_MSH3_H */
Index: trunk/src/libs/curl-8.0.1/lib/vquic/curl_ngtcp2.c
===================================================================
--- trunk/src/libs/curl-8.0.1/lib/vquic/curl_ngtcp2.c	(revision 99459)
+++ trunk/src/libs/curl-8.0.1/lib/vquic/curl_ngtcp2.c	(revision 99459)
@@ -0,0 +1,2550 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+
+#ifdef USE_NGTCP2
+#include <ngtcp2/ngtcp2.h>
+#include <nghttp3/nghttp3.h>
+
+#ifdef USE_OPENSSL
+#include <openssl/err.h>
+#ifdef OPENSSL_IS_BORINGSSL
+#include <ngtcp2/ngtcp2_crypto_boringssl.h>
+#else
+#include <ngtcp2/ngtcp2_crypto_openssl.h>
+#endif
+#include "vtls/openssl.h"
+#elif defined(USE_GNUTLS)
+#include <ngtcp2/ngtcp2_crypto_gnutls.h>
+#include "vtls/gtls.h"
+#elif defined(USE_WOLFSSL)
+#include <ngtcp2/ngtcp2_crypto_wolfssl.h>
+#include "vtls/wolfssl.h"
+#endif
+
+#include "urldata.h"
+#include "sendf.h"
+#include "strdup.h"
+#include "rand.h"
+#include "multiif.h"
+#include "strcase.h"
+#include "cfilters.h"
+#include "cf-socket.h"
+#include "connect.h"
+#include "progress.h"
+#include "strerror.h"
+#include "dynbuf.h"
+#include "select.h"
+#include "vquic.h"
+#include "vquic_int.h"
+#include "h2h3.h"
+#include "vtls/keylog.h"
+#include "vtls/vtls.h"
+#include "curl_ngtcp2.h"
+
+#include "warnless.h"
+
+/* The last 3 #include files should be in this order */
+#include "curl_printf.h"
+#include "curl_memory.h"
+#include "memdebug.h"
+
+
+#define H3_ALPN_H3_29 "\x5h3-29"
+#define H3_ALPN_H3 "\x2h3"
+
+/*
+ * This holds outgoing HTTP/3 stream data that is used by nghttp3 until acked.
+ * It is used as a circular buffer. Add new bytes at the end until it reaches
+ * the far end, then start over at index 0 again.
+ */
+
+#define H3_SEND_SIZE (256*1024)
+struct h3out {
+  uint8_t buf[H3_SEND_SIZE];
+  size_t used;   /* number of bytes used in the buffer */
+  size_t windex; /* index in the buffer where to start writing the next
+                    data block */
+};
+
+#define QUIC_MAX_STREAMS (256*1024)
+#define QUIC_MAX_DATA (1*1024*1024)
+#define QUIC_IDLE_TIMEOUT (60*NGTCP2_SECONDS)
+#define QUIC_HANDSHAKE_TIMEOUT (10*NGTCP2_SECONDS)
+
+#ifdef USE_OPENSSL
+#define QUIC_CIPHERS                                                          \
+  "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_"               \
+  "POLY1305_SHA256:TLS_AES_128_CCM_SHA256"
+#define QUIC_GROUPS "P-256:X25519:P-384:P-521"
+#elif defined(USE_GNUTLS)
+#define QUIC_PRIORITY \
+  "NORMAL:-VERS-ALL:+VERS-TLS1.3:-CIPHER-ALL:+AES-128-GCM:+AES-256-GCM:" \
+  "+CHACHA20-POLY1305:+AES-128-CCM:-GROUP-ALL:+GROUP-SECP256R1:" \
+  "+GROUP-X25519:+GROUP-SECP384R1:+GROUP-SECP521R1:" \
+  "%DISABLE_TLS13_COMPAT_MODE"
+#elif defined(USE_WOLFSSL)
+#define QUIC_CIPHERS                                                          \
+  "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_"               \
+  "POLY1305_SHA256:TLS_AES_128_CCM_SHA256"
+#define QUIC_GROUPS "P-256:P-384:P-521"
+#endif
+
+
+/*
+ * Store ngtcp2 version info in this buffer.
+ */
+void Curl_ngtcp2_ver(char *p, size_t len)
+{
+  const ngtcp2_info *ng2 = ngtcp2_version(0);
+  const nghttp3_info *ht3 = nghttp3_version(0);
+  (void)msnprintf(p, len, "ngtcp2/%s nghttp3/%s",
+                  ng2->version_str, ht3->version_str);
+}
+
+struct cf_ngtcp2_ctx {
+  struct cf_quic_ctx q;
+  ngtcp2_path connected_path;
+  ngtcp2_conn *qconn;
+  ngtcp2_cid dcid;
+  ngtcp2_cid scid;
+  uint32_t version;
+  ngtcp2_settings settings;
+  ngtcp2_transport_params transport_params;
+  ngtcp2_connection_close_error last_error;
+  ngtcp2_crypto_conn_ref conn_ref;
+#ifdef USE_OPENSSL
+  SSL_CTX *sslctx;
+  SSL *ssl;
+#elif defined(USE_GNUTLS)
+  struct gtls_instance *gtls;
+#elif defined(USE_WOLFSSL)
+  WOLFSSL_CTX *sslctx;
+  WOLFSSL *ssl;
+#endif
+  struct cf_call_data call_data;
+  nghttp3_conn *h3conn;
+  nghttp3_settings h3settings;
+  int qlogfd;
+  struct curltime started_at;        /* time the current attempt started */
+  struct curltime handshake_at;      /* time connect handshake finished */
+  struct curltime first_byte_at;     /* when first byte was recvd */
+  struct curltime reconnect_at;    /* time the next attempt should start */
+  BIT(got_first_byte);               /* if first byte was received */
+};
+
+/* How to access `call_data` from a cf_ngtcp2 filter */
+#define CF_CTX_CALL_DATA(cf)  \
+  ((struct cf_ngtcp2_ctx *)(cf)->ctx)->call_data
+
+
+/* ngtcp2 default congestion controller does not perform pacing. Limit
+   the maximum packet burst to MAX_PKT_BURST packets. */
+#define MAX_PKT_BURST 10
+
+static CURLcode cf_process_ingress(struct Curl_cfilter *cf,
+                                   struct Curl_easy *data);
+static CURLcode cf_flush_egress(struct Curl_cfilter *cf,
+                                struct Curl_easy *data);
+static int cb_h3_acked_stream_data(nghttp3_conn *conn, int64_t stream_id,
+                                   uint64_t datalen, void *user_data,
+                                   void *stream_user_data);
+
+static ngtcp2_conn *get_conn(ngtcp2_crypto_conn_ref *conn_ref)
+{
+  struct Curl_cfilter *cf = conn_ref->user_data;
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  return ctx->qconn;
+}
+
+static ngtcp2_tstamp timestamp(void)
+{
+  struct curltime ct = Curl_now();
+  return ct.tv_sec * NGTCP2_SECONDS + ct.tv_usec * NGTCP2_MICROSECONDS;
+}
+
+#ifdef DEBUG_NGTCP2
+static void quic_printf(void *user_data, const char *fmt, ...)
+{
+  struct Curl_cfilter *cf = user_data;
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+
+  (void)ctx;  /* TODO: need an easy handle to infof() message */
+  va_list ap;
+  va_start(ap, fmt);
+  vfprintf(stderr, fmt, ap);
+  va_end(ap);
+  fprintf(stderr, "\n");
+}
+#endif
+
+static void qlog_callback(void *user_data, uint32_t flags,
+                          const void *data, size_t datalen)
+{
+  struct Curl_cfilter *cf = user_data;
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  (void)flags;
+  if(ctx->qlogfd != -1) {
+    ssize_t rc = write(ctx->qlogfd, data, datalen);
+    if(rc == -1) {
+      /* on write error, stop further write attempts */
+      close(ctx->qlogfd);
+      ctx->qlogfd = -1;
+    }
+  }
+
+}
+
+static void quic_settings(struct cf_ngtcp2_ctx *ctx,
+                          struct Curl_easy *data)
+{
+  ngtcp2_settings *s = &ctx->settings;
+  ngtcp2_transport_params *t = &ctx->transport_params;
+  size_t stream_win_size = CURL_MAX_READ_SIZE;
+
+  ngtcp2_settings_default(s);
+  ngtcp2_transport_params_default(t);
+#ifdef DEBUG_NGTCP2
+  s->log_printf = quic_printf;
+#else
+  s->log_printf = NULL;
+#endif
+
+  (void)data;
+  s->initial_ts = timestamp();
+  s->handshake_timeout = QUIC_HANDSHAKE_TIMEOUT;
+  s->max_window = 100 * stream_win_size;
+  s->max_stream_window = stream_win_size;
+
+  t->initial_max_data = 10 * stream_win_size;
+  t->initial_max_stream_data_bidi_local = stream_win_size;
+  t->initial_max_stream_data_bidi_remote = stream_win_size;
+  t->initial_max_stream_data_uni = stream_win_size;
+  t->initial_max_streams_bidi = QUIC_MAX_STREAMS;
+  t->initial_max_streams_uni = QUIC_MAX_STREAMS;
+  t->max_idle_timeout = QUIC_IDLE_TIMEOUT;
+  if(ctx->qlogfd != -1) {
+    s->qlog.write = qlog_callback;
+  }
+}
+
+#ifdef USE_OPENSSL
+static void keylog_callback(const SSL *ssl, const char *line)
+{
+  (void)ssl;
+  Curl_tls_keylog_write_line(line);
+}
+#elif defined(USE_GNUTLS)
+static int keylog_callback(gnutls_session_t session, const char *label,
+                    const gnutls_datum_t *secret)
+{
+  gnutls_datum_t crandom;
+  gnutls_datum_t srandom;
+
+  gnutls_session_get_random(session, &crandom, &srandom);
+  if(crandom.size != 32) {
+    return -1;
+  }
+
+  Curl_tls_keylog_write(label, crandom.data, secret->data, secret->size);
+  return 0;
+}
+#elif defined(USE_WOLFSSL)
+#if defined(HAVE_SECRET_CALLBACK)
+static void keylog_callback(const WOLFSSL *ssl, const char *line)
+{
+  (void)ssl;
+  Curl_tls_keylog_write_line(line);
+}
+#endif
+#endif
+
+static int init_ngh3_conn(struct Curl_cfilter *cf);
+
+#ifdef USE_OPENSSL
+static CURLcode quic_ssl_ctx(SSL_CTX **pssl_ctx,
+                             struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+  struct connectdata *conn = cf->conn;
+  CURLcode result = CURLE_FAILED_INIT;
+  SSL_CTX *ssl_ctx = SSL_CTX_new(TLS_method());
+
+  if(!ssl_ctx) {
+    result = CURLE_OUT_OF_MEMORY;
+    goto out;
+  }
+
+#ifdef OPENSSL_IS_BORINGSSL
+  if(ngtcp2_crypto_boringssl_configure_client_context(ssl_ctx) != 0) {
+    failf(data, "ngtcp2_crypto_boringssl_configure_client_context failed");
+    goto out;
+  }
+#else
+  if(ngtcp2_crypto_openssl_configure_client_context(ssl_ctx) != 0) {
+    failf(data, "ngtcp2_crypto_openssl_configure_client_context failed");
+    goto out;
+  }
+#endif
+
+  SSL_CTX_set_default_verify_paths(ssl_ctx);
+
+#ifdef OPENSSL_IS_BORINGSSL
+  if(SSL_CTX_set1_curves_list(ssl_ctx, QUIC_GROUPS) != 1) {
+    failf(data, "SSL_CTX_set1_curves_list failed");
+    goto out;
+  }
+#else
+  if(SSL_CTX_set_ciphersuites(ssl_ctx, QUIC_CIPHERS) != 1) {
+    char error_buffer[256];
+    ERR_error_string_n(ERR_get_error(), error_buffer, sizeof(error_buffer));
+    failf(data, "SSL_CTX_set_ciphersuites: %s", error_buffer);
+    goto out;
+  }
+
+  if(SSL_CTX_set1_groups_list(ssl_ctx, QUIC_GROUPS) != 1) {
+    failf(data, "SSL_CTX_set1_groups_list failed");
+    goto out;
+  }
+#endif
+
+  /* Open the file if a TLS or QUIC backend has not done this before. */
+  Curl_tls_keylog_open();
+  if(Curl_tls_keylog_enabled()) {
+    SSL_CTX_set_keylog_callback(ssl_ctx, keylog_callback);
+  }
+
+  result = Curl_ssl_setup_x509_store(cf, data, ssl_ctx);
+  if(result)
+    goto out;
+
+  /* OpenSSL always tries to verify the peer, this only says whether it should
+   * fail to connect if the verification fails, or if it should continue
+   * anyway. In the latter case the result of the verification is checked with
+   * SSL_get_verify_result() below. */
+  SSL_CTX_set_verify(ssl_ctx, conn->ssl_config.verifypeer ?
+                     SSL_VERIFY_PEER : SSL_VERIFY_NONE, NULL);
+
+  /* give application a chance to interfere with SSL set up. */
+  if(data->set.ssl.fsslctx) {
+    Curl_set_in_callback(data, true);
+    result = (*data->set.ssl.fsslctx)(data, ssl_ctx,
+                                      data->set.ssl.fsslctxp);
+    Curl_set_in_callback(data, false);
+    if(result) {
+      failf(data, "error signaled by ssl ctx callback");
+      goto out;
+    }
+  }
+  result = CURLE_OK;
+
+out:
+  *pssl_ctx = result? NULL : ssl_ctx;
+  if(result && ssl_ctx)
+    SSL_CTX_free(ssl_ctx);
+  return result;
+}
+
+static CURLcode quic_set_client_cert(struct Curl_cfilter *cf,
+                                     struct Curl_easy *data)
+{
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  SSL_CTX *ssl_ctx = ctx->sslctx;
+  const struct ssl_config_data *ssl_config;
+
+  ssl_config = Curl_ssl_get_config(data, FIRSTSOCKET);
+  DEBUGASSERT(ssl_config);
+
+  if(ssl_config->primary.clientcert || ssl_config->primary.cert_blob
+     || ssl_config->cert_type) {
+    return Curl_ossl_set_client_cert(
+        data, ssl_ctx, ssl_config->primary.clientcert,
+        ssl_config->primary.cert_blob, ssl_config->cert_type,
+        ssl_config->key, ssl_config->key_blob,
+        ssl_config->key_type, ssl_config->key_passwd);
+  }
+
+  return CURLE_OK;
+}
+
+/** SSL callbacks ***/
+
+static CURLcode quic_init_ssl(struct Curl_cfilter *cf,
+                              struct Curl_easy *data)
+{
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  const uint8_t *alpn = NULL;
+  size_t alpnlen = 0;
+
+  (void)data;
+  DEBUGASSERT(!ctx->ssl);
+  ctx->ssl = SSL_new(ctx->sslctx);
+
+  SSL_set_app_data(ctx->ssl, &ctx->conn_ref);
+  SSL_set_connect_state(ctx->ssl);
+  SSL_set_quic_use_legacy_codepoint(ctx->ssl, 0);
+
+  alpn = (const uint8_t *)H3_ALPN_H3_29 H3_ALPN_H3;
+  alpnlen = sizeof(H3_ALPN_H3_29) - 1 + sizeof(H3_ALPN_H3) - 1;
+  if(alpn)
+    SSL_set_alpn_protos(ctx->ssl, alpn, (int)alpnlen);
+
+  /* set SNI */
+  SSL_set_tlsext_host_name(ctx->ssl, cf->conn->host.name);
+  return CURLE_OK;
+}
+#elif defined(USE_GNUTLS)
+static CURLcode quic_init_ssl(struct Curl_cfilter *cf,
+                              struct Curl_easy *data)
+{
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  CURLcode result;
+  gnutls_datum_t alpn[2];
+  /* this will need some attention when HTTPS proxy over QUIC get fixed */
+  const char * const hostname = cf->conn->host.name;
+  long * const pverifyresult = &data->set.ssl.certverifyresult;
+  int rc;
+
+  DEBUGASSERT(ctx->gtls == NULL);
+  ctx->gtls = calloc(1, sizeof(*(ctx->gtls)));
+  if(!ctx->gtls)
+    return CURLE_OUT_OF_MEMORY;
+
+  result = gtls_client_init(data, &cf->conn->ssl_config, &data->set.ssl,
+                            hostname, ctx->gtls, pverifyresult);
+  if(result)
+    return result;
+
+  gnutls_session_set_ptr(ctx->gtls->session, &ctx->conn_ref);
+
+  if(ngtcp2_crypto_gnutls_configure_client_session(ctx->gtls->session) != 0) {
+    DEBUGF(LOG_CF(data, cf,
+                  "ngtcp2_crypto_gnutls_configure_client_session failed\n"));
+    return CURLE_QUIC_CONNECT_ERROR;
+  }
+
+  rc = gnutls_priority_set_direct(ctx->gtls->session, QUIC_PRIORITY, NULL);
+  if(rc < 0) {
+    DEBUGF(LOG_CF(data, cf, "gnutls_priority_set_direct failed: %s\n",
+                  gnutls_strerror(rc)));
+    return CURLE_QUIC_CONNECT_ERROR;
+  }
+
+  /* Open the file if a TLS or QUIC backend has not done this before. */
+  Curl_tls_keylog_open();
+  if(Curl_tls_keylog_enabled()) {
+    gnutls_session_set_keylog_function(ctx->gtls->session, keylog_callback);
+  }
+
+  /* strip the first byte (the length) from NGHTTP3_ALPN_H3 */
+  alpn[0].data = (unsigned char *)H3_ALPN_H3_29 + 1;
+  alpn[0].size = sizeof(H3_ALPN_H3_29) - 2;
+  alpn[1].data = (unsigned char *)H3_ALPN_H3 + 1;
+  alpn[1].size = sizeof(H3_ALPN_H3) - 2;
+
+  gnutls_alpn_set_protocols(ctx->gtls->session,
+                            alpn, 2, GNUTLS_ALPN_MANDATORY);
+  return CURLE_OK;
+}
+#elif defined(USE_WOLFSSL)
+
+static CURLcode quic_ssl_ctx(WOLFSSL_CTX **pssl_ctx,
+                             struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+  struct connectdata *conn = cf->conn;
+  CURLcode result = CURLE_FAILED_INIT;
+  WOLFSSL_CTX *ssl_ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method());
+
+  if(!ssl_ctx) {
+    result = CURLE_OUT_OF_MEMORY;
+    goto out;
+  }
+
+  if(ngtcp2_crypto_wolfssl_configure_client_context(ssl_ctx) != 0) {
+    failf(data, "ngtcp2_crypto_wolfssl_configure_client_context failed");
+    goto out;
+  }
+
+  wolfSSL_CTX_set_default_verify_paths(ssl_ctx);
+
+  if(wolfSSL_CTX_set_cipher_list(ssl_ctx, QUIC_CIPHERS) != 1) {
+    char error_buffer[256];
+    ERR_error_string_n(ERR_get_error(), error_buffer, sizeof(error_buffer));
+    failf(data, "SSL_CTX_set_ciphersuites: %s", error_buffer);
+    goto out;
+  }
+
+  if(wolfSSL_CTX_set1_groups_list(ssl_ctx, (char *)QUIC_GROUPS) != 1) {
+    failf(data, "SSL_CTX_set1_groups_list failed");
+    goto out;
+  }
+
+  /* Open the file if a TLS or QUIC backend has not done this before. */
+  Curl_tls_keylog_open();
+  if(Curl_tls_keylog_enabled()) {
+#if defined(HAVE_SECRET_CALLBACK)
+    wolfSSL_CTX_set_keylog_callback(ssl_ctx, keylog_callback);
+#else
+    failf(data, "wolfSSL was built without keylog callback");
+    goto out;
+#endif
+  }
+
+  if(conn->ssl_config.verifypeer) {
+    const char * const ssl_cafile = conn->ssl_config.CAfile;
+    const char * const ssl_capath = conn->ssl_config.CApath;
+
+    wolfSSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL);
+    if(conn->ssl_config.CAfile || conn->ssl_config.CApath) {
+      /* tell wolfSSL where to find CA certificates that are used to verify
+         the server's certificate. */
+      if(!wolfSSL_CTX_load_verify_locations(ssl_ctx, ssl_cafile, ssl_capath)) {
+        /* Fail if we insist on successfully verifying the server. */
+        failf(data, "error setting certificate verify locations:"
+              "  CAfile: %s CApath: %s",
+              ssl_cafile ? ssl_cafile : "none",
+              ssl_capath ? ssl_capath : "none");
+        goto out;
+      }
+      infof(data, " CAfile: %s", ssl_cafile ? ssl_cafile : "none");
+      infof(data, " CApath: %s", ssl_capath ? ssl_capath : "none");
+    }
+#ifdef CURL_CA_FALLBACK
+    else {
+      /* verifying the peer without any CA certificates won't work so
+         use wolfssl's built-in default as fallback */
+      wolfSSL_CTX_set_default_verify_paths(ssl_ctx);
+    }
+#endif
+  }
+  else {
+    wolfSSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE, NULL);
+  }
+
+  /* give application a chance to interfere with SSL set up. */
+  if(data->set.ssl.fsslctx) {
+    Curl_set_in_callback(data, true);
+    result = (*data->set.ssl.fsslctx)(data, ssl_ctx,
+                                      data->set.ssl.fsslctxp);
+    Curl_set_in_callback(data, false);
+    if(result) {
+      failf(data, "error signaled by ssl ctx callback");
+      goto out;
+    }
+  }
+  result = CURLE_OK;
+
+out:
+  *pssl_ctx = result? NULL : ssl_ctx;
+  if(result && ssl_ctx)
+    SSL_CTX_free(ssl_ctx);
+  return result;
+}
+
+/** SSL callbacks ***/
+
+static CURLcode quic_init_ssl(struct Curl_cfilter *cf,
+                              struct Curl_easy *data)
+{
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  const uint8_t *alpn = NULL;
+  size_t alpnlen = 0;
+  /* this will need some attention when HTTPS proxy over QUIC get fixed */
+  const char * const hostname = cf->conn->host.name;
+
+  (void)data;
+  DEBUGASSERT(!ctx->ssl);
+  ctx->ssl = wolfSSL_new(ctx->sslctx);
+
+  wolfSSL_set_app_data(ctx->ssl, &ctx->conn_ref);
+  wolfSSL_set_connect_state(ctx->ssl);
+  wolfSSL_set_quic_use_legacy_codepoint(ctx->ssl, 0);
+
+  alpn = (const uint8_t *)H3_ALPN_H3_29 H3_ALPN_H3;
+  alpnlen = sizeof(H3_ALPN_H3_29) - 1 + sizeof(H3_ALPN_H3) - 1;
+  if(alpn)
+    wolfSSL_set_alpn_protos(ctx->ssl, alpn, (int)alpnlen);
+
+  /* set SNI */
+  wolfSSL_UseSNI(ctx->ssl, WOLFSSL_SNI_HOST_NAME,
+                 hostname, (unsigned short)strlen(hostname));
+
+  return CURLE_OK;
+}
+#endif /* defined(USE_WOLFSSL) */
+
+static int cb_handshake_completed(ngtcp2_conn *tconn, void *user_data)
+{
+  (void)user_data;
+  (void)tconn;
+  return 0;
+}
+
+static void report_consumed_data(struct Curl_cfilter *cf,
+                                 struct Curl_easy *data,
+                                 size_t consumed)
+{
+  struct HTTP *stream = data->req.p.http;
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+
+  /* the HTTP/1.1 response headers are written to the buffer, but
+   * consuming those does not count against flow control. */
+  if(stream->recv_buf_nonflow) {
+    if(consumed >= stream->recv_buf_nonflow) {
+      consumed -= stream->recv_buf_nonflow;
+      stream->recv_buf_nonflow = 0;
+    }
+    else {
+      stream->recv_buf_nonflow -= consumed;
+      consumed = 0;
+    }
+  }
+  if(consumed > 0) {
+    DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] consumed %zu DATA bytes",
+                  stream->stream3_id, consumed));
+    ngtcp2_conn_extend_max_stream_offset(ctx->qconn, stream->stream3_id,
+                                         consumed);
+    ngtcp2_conn_extend_max_offset(ctx->qconn, consumed);
+  }
+  if(!stream->closed && data->state.drain
+     && !stream->memlen
+     && !Curl_dyn_len(&stream->overflow)) {
+     /* nothing buffered any more */
+     data->state.drain = 0;
+  }
+}
+
+static int cb_recv_stream_data(ngtcp2_conn *tconn, uint32_t flags,
+                               int64_t stream_id, uint64_t offset,
+                               const uint8_t *buf, size_t buflen,
+                               void *user_data, void *stream_user_data)
+{
+  struct Curl_cfilter *cf = user_data;
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  nghttp3_ssize nconsumed;
+  int fin = (flags & NGTCP2_STREAM_DATA_FLAG_FIN) ? 1 : 0;
+  struct Curl_easy *data = stream_user_data;
+  (void)offset;
+  (void)data;
+
+  nconsumed =
+    nghttp3_conn_read_stream(ctx->h3conn, stream_id, buf, buflen, fin);
+  DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] read_stream(len=%zu) -> %zd",
+                stream_id, buflen, nconsumed));
+  if(nconsumed < 0) {
+    ngtcp2_connection_close_error_set_application_error(
+        &ctx->last_error,
+        nghttp3_err_infer_quic_app_error_code((int)nconsumed), NULL, 0);
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  /* number of bytes inside buflen which consists of framing overhead
+   * including QPACK HEADERS. In other words, it does not consume payload of
+   * DATA frame. */
+  ngtcp2_conn_extend_max_stream_offset(tconn, stream_id, nconsumed);
+  ngtcp2_conn_extend_max_offset(tconn, nconsumed);
+
+  return 0;
+}
+
+static int
+cb_acked_stream_data_offset(ngtcp2_conn *tconn, int64_t stream_id,
+                            uint64_t offset, uint64_t datalen, void *user_data,
+                            void *stream_user_data)
+{
+  struct Curl_cfilter *cf = user_data;
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  int rv;
+  (void)stream_id;
+  (void)tconn;
+  (void)offset;
+  (void)datalen;
+  (void)stream_user_data;
+
+  rv = nghttp3_conn_add_ack_offset(ctx->h3conn, stream_id, datalen);
+  if(rv) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+
+static int cb_stream_close(ngtcp2_conn *tconn, uint32_t flags,
+                           int64_t stream3_id, uint64_t app_error_code,
+                           void *user_data, void *stream_user_data)
+{
+  struct Curl_cfilter *cf = user_data;
+  struct Curl_easy *data = stream_user_data;
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  int rv;
+
+  (void)tconn;
+  (void)data;
+  /* stream is closed... */
+
+  if(!(flags & NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET)) {
+    app_error_code = NGHTTP3_H3_NO_ERROR;
+  }
+
+  rv = nghttp3_conn_close_stream(ctx->h3conn, stream3_id,
+                                 app_error_code);
+  DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] quic close(err=%"
+                PRIu64 ") -> %d", stream3_id, app_error_code, rv));
+  if(rv) {
+    ngtcp2_connection_close_error_set_application_error(
+        &ctx->last_error, nghttp3_err_infer_quic_app_error_code(rv), NULL, 0);
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+
+static int cb_stream_reset(ngtcp2_conn *tconn, int64_t stream_id,
+                           uint64_t final_size, uint64_t app_error_code,
+                           void *user_data, void *stream_user_data)
+{
+  struct Curl_cfilter *cf = user_data;
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  struct Curl_easy *data = stream_user_data;
+  int rv;
+  (void)tconn;
+  (void)final_size;
+  (void)app_error_code;
+  (void)data;
+
+  rv = nghttp3_conn_shutdown_stream_read(ctx->h3conn, stream_id);
+  DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] reset -> %d", stream_id, rv));
+  if(rv) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+
+static int cb_stream_stop_sending(ngtcp2_conn *tconn, int64_t stream_id,
+                                  uint64_t app_error_code, void *user_data,
+                                  void *stream_user_data)
+{
+  struct Curl_cfilter *cf = user_data;
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  int rv;
+  (void)tconn;
+  (void)app_error_code;
+  (void)stream_user_data;
+
+  rv = nghttp3_conn_shutdown_stream_read(ctx->h3conn, stream_id);
+  if(rv) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+
+static int cb_extend_max_local_streams_bidi(ngtcp2_conn *tconn,
+                                            uint64_t max_streams,
+                                            void *user_data)
+{
+  (void)tconn;
+  (void)max_streams;
+  (void)user_data;
+
+  return 0;
+}
+
+static int cb_extend_max_stream_data(ngtcp2_conn *tconn, int64_t stream_id,
+                                     uint64_t max_data, void *user_data,
+                                     void *stream_user_data)
+{
+  struct Curl_cfilter *cf = user_data;
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  int rv;
+  (void)tconn;
+  (void)max_data;
+  (void)stream_user_data;
+
+  rv = nghttp3_conn_unblock_stream(ctx->h3conn, stream_id);
+  if(rv) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+
+static void cb_rand(uint8_t *dest, size_t destlen,
+                    const ngtcp2_rand_ctx *rand_ctx)
+{
+  CURLcode result;
+  (void)rand_ctx;
+
+  result = Curl_rand(NULL, dest, destlen);
+  if(result) {
+    /* cb_rand is only used for non-cryptographic context.  If Curl_rand
+       failed, just fill 0 and call it *random*. */
+    memset(dest, 0, destlen);
+  }
+}
+
+static int cb_get_new_connection_id(ngtcp2_conn *tconn, ngtcp2_cid *cid,
+                                    uint8_t *token, size_t cidlen,
+                                    void *user_data)
+{
+  CURLcode result;
+  (void)tconn;
+  (void)user_data;
+
+  result = Curl_rand(NULL, cid->data, cidlen);
+  if(result)
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  cid->datalen = cidlen;
+
+  result = Curl_rand(NULL, token, NGTCP2_STATELESS_RESET_TOKENLEN);
+  if(result)
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+
+  return 0;
+}
+
+static int cb_recv_rx_key(ngtcp2_conn *tconn, ngtcp2_crypto_level level,
+                          void *user_data)
+{
+  struct Curl_cfilter *cf = user_data;
+  (void)tconn;
+
+  if(level != NGTCP2_CRYPTO_LEVEL_APPLICATION) {
+    return 0;
+  }
+
+  if(init_ngh3_conn(cf) != CURLE_OK) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+
+static ngtcp2_callbacks ng_callbacks = {
+  ngtcp2_crypto_client_initial_cb,
+  NULL, /* recv_client_initial */
+  ngtcp2_crypto_recv_crypto_data_cb,
+  cb_handshake_completed,
+  NULL, /* recv_version_negotiation */
+  ngtcp2_crypto_encrypt_cb,
+  ngtcp2_crypto_decrypt_cb,
+  ngtcp2_crypto_hp_mask_cb,
+  cb_recv_stream_data,
+  cb_acked_stream_data_offset,
+  NULL, /* stream_open */
+  cb_stream_close,
+  NULL, /* recv_stateless_reset */
+  ngtcp2_crypto_recv_retry_cb,
+  cb_extend_max_local_streams_bidi,
+  NULL, /* extend_max_local_streams_uni */
+  cb_rand,
+  cb_get_new_connection_id,
+  NULL, /* remove_connection_id */
+  ngtcp2_crypto_update_key_cb, /* update_key */
+  NULL, /* path_validation */
+  NULL, /* select_preferred_addr */
+  cb_stream_reset,
+  NULL, /* extend_max_remote_streams_bidi */
+  NULL, /* extend_max_remote_streams_uni */
+  cb_extend_max_stream_data,
+  NULL, /* dcid_status */
+  NULL, /* handshake_confirmed */
+  NULL, /* recv_new_token */
+  ngtcp2_crypto_delete_crypto_aead_ctx_cb,
+  ngtcp2_crypto_delete_crypto_cipher_ctx_cb,
+  NULL, /* recv_datagram */
+  NULL, /* ack_datagram */
+  NULL, /* lost_datagram */
+  ngtcp2_crypto_get_path_challenge_data_cb,
+  cb_stream_stop_sending,
+  NULL, /* version_negotiation */
+  cb_recv_rx_key,
+  NULL, /* recv_tx_key */
+  NULL, /* early_data_rejected */
+};
+
+static int cf_ngtcp2_get_select_socks(struct Curl_cfilter *cf,
+                                      struct Curl_easy *data,
+                                      curl_socket_t *socks)
+{
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  struct SingleRequest *k = &data->req;
+  int rv = GETSOCK_BLANK;
+  struct HTTP *stream = data->req.p.http;
+  struct cf_call_data save;
+
+  CF_DATA_SAVE(save, cf, data);
+  socks[0] = ctx->q.sockfd;
+
+  /* in an HTTP/3 connection we can basically always get a frame so we should
+     always be ready for one */
+  rv |= GETSOCK_READSOCK(0);
+
+  /* we're still uploading or the HTTP/2 layer wants to send data */
+  if((k->keepon & KEEP_SENDBITS) == KEEP_SEND &&
+     (!stream->h3out || stream->h3out->used < H3_SEND_SIZE) &&
+     ngtcp2_conn_get_cwnd_left(ctx->qconn) &&
+     ngtcp2_conn_get_max_data_left(ctx->qconn) &&
+     nghttp3_conn_is_stream_writable(ctx->h3conn, stream->stream3_id))
+    rv |= GETSOCK_WRITESOCK(0);
+
+  DEBUGF(LOG_CF(data, cf, "get_select_socks -> %x (sock=%d)",
+                rv, (int)socks[0]));
+  CF_DATA_RESTORE(cf, save);
+  return rv;
+}
+
+static void notify_drain(struct Curl_cfilter *cf,
+                         struct Curl_easy *data)
+{
+  (void)cf;
+  if(!data->state.drain) {
+    data->state.drain = 1;
+    Curl_expire(data, 0, EXPIRE_RUN_NOW);
+  }
+}
+
+
+static int cb_h3_stream_close(nghttp3_conn *conn, int64_t stream_id,
+                              uint64_t app_error_code, void *user_data,
+                              void *stream_user_data)
+{
+  struct Curl_cfilter *cf = user_data;
+  struct Curl_easy *data = stream_user_data;
+  struct HTTP *stream = data->req.p.http;
+  (void)conn;
+  (void)stream_id;
+  (void)app_error_code;
+  (void)cf;
+
+  DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] h3 close(err=%" PRIx64 ")",
+                stream_id, app_error_code));
+  stream->closed = TRUE;
+  stream->error3 = app_error_code;
+  if(app_error_code == NGHTTP3_H3_INTERNAL_ERROR) {
+    /* TODO: we do not get a specific error when the remote end closed
+     * the response before it was complete. */
+    stream->reset = TRUE;
+  }
+  notify_drain(cf, data);
+  return 0;
+}
+
+/*
+ * write_resp_raw() copies response data in raw format to the `data`'s
+  * receive buffer. If not enough space is available, it appends to the
+ * `data`'s overflow buffer.
+ */
+static CURLcode write_resp_raw(struct Curl_cfilter *cf,
+                               struct Curl_easy *data,
+                               const void *mem, size_t memlen,
+                               bool flow)
+{
+  struct HTTP *stream = data->req.p.http;
+  CURLcode result = CURLE_OK;
+  const char *buf = mem;
+  size_t ncopy = memlen;
+  /* copy as much as possible to the receive buffer */
+  if(stream->len) {
+    size_t len = CURLMIN(ncopy, stream->len);
+    memcpy(stream->mem + stream->memlen, buf, len);
+    stream->len -= len;
+    stream->memlen += len;
+    buf += len;
+    ncopy -= len;
+    DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] resp_raw: added %zu bytes"
+                  " to data buffer", stream->stream3_id, len));
+  }
+  /* copy the rest to the overflow buffer */
+  if(ncopy) {
+    result = Curl_dyn_addn(&stream->overflow, buf, ncopy);
+    DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] resp_raw: added %zu bytes"
+                  " to overflow buffer -> %d",
+                  stream->stream3_id, ncopy, result));
+    notify_drain(cf, data);
+  }
+
+  if(!flow)
+    stream->recv_buf_nonflow += memlen;
+  if(CF_DATA_CURRENT(cf) != data) {
+    notify_drain(cf, data);
+  }
+  return result;
+}
+
+static int cb_h3_recv_data(nghttp3_conn *conn, int64_t stream3_id,
+                           const uint8_t *buf, size_t buflen,
+                           void *user_data, void *stream_user_data)
+{
+  struct Curl_cfilter *cf = user_data;
+  struct Curl_easy *data = stream_user_data;
+  CURLcode result;
+
+  (void)conn;
+  (void)stream3_id;
+
+  result = write_resp_raw(cf, data, buf, buflen, TRUE);
+  return result? -1 : 0;
+}
+
+static int cb_h3_deferred_consume(nghttp3_conn *conn, int64_t stream3_id,
+                                  size_t consumed, void *user_data,
+                                  void *stream_user_data)
+{
+  struct Curl_cfilter *cf = user_data;
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  (void)conn;
+  (void)stream_user_data;
+
+  /* nghttp3 has consumed bytes on the QUIC stream and we need to
+   * tell the QUIC connection to increase its flow control */
+  ngtcp2_conn_extend_max_stream_offset(ctx->qconn, stream3_id, consumed);
+  ngtcp2_conn_extend_max_offset(ctx->qconn, consumed);
+  return 0;
+}
+
+/* Decode HTTP status code.  Returns -1 if no valid status code was
+   decoded. (duplicate from http2.c) */
+static int decode_status_code(const uint8_t *value, size_t len)
+{
+  int i;
+  int res;
+
+  if(len != 3) {
+    return -1;
+  }
+
+  res = 0;
+
+  for(i = 0; i < 3; ++i) {
+    char c = value[i];
+
+    if(c < '0' || c > '9') {
+      return -1;
+    }
+
+    res *= 10;
+    res += c - '0';
+  }
+
+  return res;
+}
+
+static int cb_h3_end_headers(nghttp3_conn *conn, int64_t stream_id,
+                             int fin, void *user_data, void *stream_user_data)
+{
+  struct Curl_cfilter *cf = user_data;
+  struct Curl_easy *data = stream_user_data;
+  struct HTTP *stream = data->req.p.http;
+  CURLcode result = CURLE_OK;
+  (void)conn;
+  (void)stream_id;
+  (void)fin;
+  (void)cf;
+
+  /* add a CRLF only if we've received some headers */
+  if(stream->firstheader) {
+    result = write_resp_raw(cf, data, "\r\n", 2, FALSE);
+    if(result) {
+      return -1;
+    }
+  }
+
+  DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] end_headers(status_code=%d",
+                stream_id, stream->status_code));
+  if(stream->status_code / 100 != 1) {
+    stream->bodystarted = TRUE;
+  }
+  return 0;
+}
+
+static int cb_h3_recv_header(nghttp3_conn *conn, int64_t stream_id,
+                             int32_t token, nghttp3_rcbuf *name,
+                             nghttp3_rcbuf *value, uint8_t flags,
+                             void *user_data, void *stream_user_data)
+{
+  struct Curl_cfilter *cf = user_data;
+  nghttp3_vec h3name = nghttp3_rcbuf_get_buf(name);
+  nghttp3_vec h3val = nghttp3_rcbuf_get_buf(value);
+  struct Curl_easy *data = stream_user_data;
+  struct HTTP *stream = data->req.p.http;
+  CURLcode result = CURLE_OK;
+  (void)conn;
+  (void)stream_id;
+  (void)token;
+  (void)flags;
+  (void)cf;
+
+  if(token == NGHTTP3_QPACK_TOKEN__STATUS) {
+    char line[14]; /* status line is always 13 characters long */
+    size_t ncopy;
+
+    DEBUGASSERT(!stream->firstheader);
+    stream->status_code = decode_status_code(h3val.base, h3val.len);
+    DEBUGASSERT(stream->status_code != -1);
+    ncopy = msnprintf(line, sizeof(line), "HTTP/3 %03d \r\n",
+                      stream->status_code);
+    DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] status: %s",
+                  stream_id, line));
+    result = write_resp_raw(cf, data, line, ncopy, FALSE);
+    if(result) {
+      return -1;
+    }
+    stream->firstheader = TRUE;
+  }
+  else {
+    /* store as an HTTP1-style header */
+    DEBUGASSERT(stream->firstheader);
+    DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] header: %.*s: %.*s",
+                  stream_id, (int)h3name.len, h3name.base,
+                  (int)h3val.len, h3val.base));
+    result = write_resp_raw(cf, data, h3name.base, h3name.len, FALSE);
+    if(result) {
+      return -1;
+    }
+    result = write_resp_raw(cf, data, ": ", 2, FALSE);
+    if(result) {
+      return -1;
+    }
+    result = write_resp_raw(cf, data, h3val.base, h3val.len, FALSE);
+    if(result) {
+      return -1;
+    }
+    result = write_resp_raw(cf, data, "\r\n", 2, FALSE);
+    if(result) {
+      return -1;
+    }
+  }
+  return 0;
+}
+
+static int cb_h3_stop_sending(nghttp3_conn *conn, int64_t stream_id,
+                              uint64_t app_error_code, void *user_data,
+                              void *stream_user_data)
+{
+  struct Curl_cfilter *cf = user_data;
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  int rv;
+  (void)conn;
+  (void)stream_user_data;
+
+  rv = ngtcp2_conn_shutdown_stream_read(ctx->qconn, stream_id, app_error_code);
+  if(rv && rv != NGTCP2_ERR_STREAM_NOT_FOUND) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+
+static int cb_h3_reset_stream(nghttp3_conn *conn, int64_t stream_id,
+                              uint64_t app_error_code, void *user_data,
+                              void *stream_user_data) {
+  struct Curl_cfilter *cf = user_data;
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  struct Curl_easy *data = stream_user_data;
+  int rv;
+  (void)conn;
+  (void)data;
+
+  rv = ngtcp2_conn_shutdown_stream_write(ctx->qconn, stream_id,
+                                         app_error_code);
+  DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] reset -> %d", stream_id, rv));
+  if(rv && rv != NGTCP2_ERR_STREAM_NOT_FOUND) {
+    return NGTCP2_ERR_CALLBACK_FAILURE;
+  }
+
+  return 0;
+}
+
+static nghttp3_callbacks ngh3_callbacks = {
+  cb_h3_acked_stream_data, /* acked_stream_data */
+  cb_h3_stream_close,
+  cb_h3_recv_data,
+  cb_h3_deferred_consume,
+  NULL, /* begin_headers */
+  cb_h3_recv_header,
+  cb_h3_end_headers,
+  NULL, /* begin_trailers */
+  cb_h3_recv_header,
+  NULL, /* end_trailers */
+  cb_h3_stop_sending,
+  NULL, /* end_stream */
+  cb_h3_reset_stream,
+  NULL /* shutdown */
+};
+
+static int init_ngh3_conn(struct Curl_cfilter *cf)
+{
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  CURLcode result;
+  int rc;
+  int64_t ctrl_stream_id, qpack_enc_stream_id, qpack_dec_stream_id;
+
+  if(ngtcp2_conn_get_max_local_streams_uni(ctx->qconn) < 3) {
+    return CURLE_QUIC_CONNECT_ERROR;
+  }
+
+  nghttp3_settings_default(&ctx->h3settings);
+
+  rc = nghttp3_conn_client_new(&ctx->h3conn,
+                               &ngh3_callbacks,
+                               &ctx->h3settings,
+                               nghttp3_mem_default(),
+                               cf);
+  if(rc) {
+    result = CURLE_OUT_OF_MEMORY;
+    goto fail;
+  }
+
+  rc = ngtcp2_conn_open_uni_stream(ctx->qconn, &ctrl_stream_id, NULL);
+  if(rc) {
+    result = CURLE_QUIC_CONNECT_ERROR;
+    goto fail;
+  }
+
+  rc = nghttp3_conn_bind_control_stream(ctx->h3conn, ctrl_stream_id);
+  if(rc) {
+    result = CURLE_QUIC_CONNECT_ERROR;
+    goto fail;
+  }
+
+  rc = ngtcp2_conn_open_uni_stream(ctx->qconn, &qpack_enc_stream_id, NULL);
+  if(rc) {
+    result = CURLE_QUIC_CONNECT_ERROR;
+    goto fail;
+  }
+
+  rc = ngtcp2_conn_open_uni_stream(ctx->qconn, &qpack_dec_stream_id, NULL);
+  if(rc) {
+    result = CURLE_QUIC_CONNECT_ERROR;
+    goto fail;
+  }
+
+  rc = nghttp3_conn_bind_qpack_streams(ctx->h3conn, qpack_enc_stream_id,
+                                       qpack_dec_stream_id);
+  if(rc) {
+    result = CURLE_QUIC_CONNECT_ERROR;
+    goto fail;
+  }
+
+  return CURLE_OK;
+  fail:
+
+  return result;
+}
+
+static void drain_overflow_buffer(struct Curl_cfilter *cf,
+                                  struct Curl_easy *data)
+{
+  struct HTTP *stream = data->req.p.http;
+  size_t overlen = Curl_dyn_len(&stream->overflow);
+  size_t ncopy = CURLMIN(overlen, stream->len);
+
+  (void)cf;
+  if(ncopy > 0) {
+    memcpy(stream->mem + stream->memlen,
+           Curl_dyn_ptr(&stream->overflow), ncopy);
+    stream->len -= ncopy;
+    stream->memlen += ncopy;
+    if(ncopy != overlen)
+      /* make the buffer only keep the tail */
+      (void)Curl_dyn_tail(&stream->overflow, overlen - ncopy);
+    else {
+      Curl_dyn_reset(&stream->overflow);
+    }
+  }
+}
+
+static ssize_t recv_closed_stream(struct Curl_cfilter *cf,
+                                  struct Curl_easy *data,
+                                  CURLcode *err)
+{
+  struct HTTP *stream = data->req.p.http;
+  ssize_t nread = -1;
+
+  (void)cf;
+
+  if(stream->reset) {
+    failf(data,
+          "HTTP/3 stream %" PRId64 " reset by server", stream->stream3_id);
+    *err = CURLE_PARTIAL_FILE;
+    DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] cf_recv, was reset -> %d",
+                  stream->stream3_id, *err));
+    goto out;
+  }
+  else if(stream->error3 != NGHTTP3_H3_NO_ERROR) {
+    failf(data,
+          "HTTP/3 stream %" PRId64 " was not closed cleanly: (err 0x%" PRIx64
+          ")",
+          stream->stream3_id, stream->error3);
+    *err = CURLE_HTTP3;
+    DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] cf_recv, closed uncleanly"
+                  " -> %d", stream->stream3_id, *err));
+    goto out;
+  }
+
+  if(!stream->bodystarted) {
+    failf(data,
+          "HTTP/3 stream %" PRId64 " was closed cleanly, but before getting"
+          " all response header fields, treated as error",
+          stream->stream3_id);
+    *err = CURLE_HTTP3;
+    DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] cf_recv, closed incomplete"
+                  " -> %d", stream->stream3_id, *err));
+    goto out;
+  }
+  else {
+    DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] cf_recv, closed ok"
+                  " -> %d", stream->stream3_id, *err));
+  }
+  *err = CURLE_OK;
+  nread = 0;
+
+out:
+  data->state.drain = 0;
+  return nread;
+}
+
+/* incoming data frames on the h3 stream */
+static ssize_t cf_ngtcp2_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
+                              char *buf, size_t len, CURLcode *err)
+{
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  struct HTTP *stream = data->req.p.http;
+  ssize_t nread = -1;
+  struct cf_call_data save;
+
+  (void)ctx;
+
+  CF_DATA_SAVE(save, cf, data);
+  DEBUGASSERT(cf->connected);
+  DEBUGASSERT(ctx);
+  DEBUGASSERT(ctx->qconn);
+  DEBUGASSERT(ctx->h3conn);
+  *err = CURLE_OK;
+
+  DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] cf_recv(len=%zu) start",
+                stream->stream3_id, len));
+  /* TODO: this implementation of response DATA buffering is fragile.
+   * It makes the following assumptions:
+   * - the `buf` passed here has the same lifetime as the easy handle
+   * - data returned in `buf` from this call is immediately used and `buf`
+   *   can be overwritten during any handling of other transfers at
+   *   this connection.
+   */
+  if(!stream->memlen) {
+    /* `buf` was not known before or is currently not used by stream,
+     * assign it (again). */
+    stream->mem = buf;
+    stream->len = len;
+  }
+
+  /* if there's data in the overflow buffer, move as much
+     as possible to the receive buffer now */
+  drain_overflow_buffer(cf, data);
+
+  if(cf_process_ingress(cf, data)) {
+    *err = CURLE_RECV_ERROR;
+    nread = -1;
+    goto out;
+  }
+
+  if(stream->memlen) {
+    nread = stream->memlen;
+    /* reset to allow more data to come */
+    /* TODO: very brittle buffer use design:
+     * - stream->mem has now `nread` bytes of response data
+     * - we assume that the caller will use those immediately and
+     *   we can overwrite that with new data on our next invocation from
+     *   anywhere.
+     */
+    stream->mem = buf;
+    stream->memlen = 0;
+    stream->len = len;
+    /* extend the stream window with the data we're consuming and send out
+       any additional packets to tell the server that we can receive more */
+    DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] cf_recv -> %zd bytes",
+                  stream->stream3_id, nread));
+    report_consumed_data(cf, data, nread);
+    if(cf_flush_egress(cf, data)) {
+      *err = CURLE_SEND_ERROR;
+      nread = -1;
+    }
+    goto out;
+  }
+
+  if(stream->closed) {
+    nread = recv_closed_stream(cf, data, err);
+    goto out;
+  }
+
+  DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] cf_recv -> EAGAIN",
+                stream->stream3_id));
+  *err = CURLE_AGAIN;
+  nread = -1;
+out:
+  if(cf_flush_egress(cf, data)) {
+    *err = CURLE_SEND_ERROR;
+    nread = -1;
+    goto out;
+  }
+
+  CF_DATA_RESTORE(cf, save);
+  return nread;
+}
+
+/* this amount of data has now been acked on this stream */
+static int cb_h3_acked_stream_data(nghttp3_conn *conn, int64_t stream_id,
+                                   uint64_t datalen, void *user_data,
+                                   void *stream_user_data)
+{
+  struct Curl_cfilter *cf = user_data;
+  struct Curl_easy *data = stream_user_data;
+  struct HTTP *stream = data->req.p.http;
+  (void)user_data;
+
+  (void)cf;
+  if(!data->set.postfields) {
+    stream->h3out->used -= datalen;
+    DEBUGF(LOG_CF(data, cf, "cb_h3_acked_stream_data, %"PRIu64" bytes, "
+                  "%zd left unacked", datalen, stream->h3out->used));
+    DEBUGASSERT(stream->h3out->used < H3_SEND_SIZE);
+
+    if(stream->h3out->used == 0) {
+      int rv = nghttp3_conn_resume_stream(conn, stream_id);
+      if(rv) {
+        return NGTCP2_ERR_CALLBACK_FAILURE;
+      }
+    }
+  }
+  return 0;
+}
+
+static nghttp3_ssize cb_h3_readfunction(nghttp3_conn *conn, int64_t stream_id,
+                                        nghttp3_vec *vec, size_t veccnt,
+                                        uint32_t *pflags, void *user_data,
+                                        void *stream_user_data)
+{
+  struct Curl_cfilter *cf = user_data;
+  struct Curl_easy *data = stream_user_data;
+  size_t nread;
+  struct HTTP *stream = data->req.p.http;
+  (void)cf;
+  (void)conn;
+  (void)stream_id;
+  (void)user_data;
+  (void)veccnt;
+
+  if(data->set.postfields) {
+    vec[0].base = data->set.postfields;
+    vec[0].len = data->state.infilesize;
+    *pflags = NGHTTP3_DATA_FLAG_EOF;
+    return 1;
+  }
+
+  if(stream->upload_len && H3_SEND_SIZE <= stream->h3out->used) {
+    return NGHTTP3_ERR_WOULDBLOCK;
+  }
+
+  nread = CURLMIN(stream->upload_len, H3_SEND_SIZE - stream->h3out->used);
+  if(nread > 0) {
+    /* nghttp3 wants us to hold on to the data until it tells us it is okay to
+       delete it. Append the data at the end of the h3out buffer. Since we can
+       only return consecutive data, copy the amount that fits and the next
+       part comes in next invoke. */
+    struct h3out *out = stream->h3out;
+    if(nread + out->windex > H3_SEND_SIZE)
+      nread = H3_SEND_SIZE - out->windex;
+
+    memcpy(&out->buf[out->windex], stream->upload_mem, nread);
+
+    /* that's the chunk we return to nghttp3 */
+    vec[0].base = &out->buf[out->windex];
+    vec[0].len = nread;
+
+    out->windex += nread;
+    out->used += nread;
+
+    if(out->windex == H3_SEND_SIZE)
+      out->windex = 0; /* wrap */
+    stream->upload_mem += nread;
+    stream->upload_len -= nread;
+    if(data->state.infilesize != -1) {
+      stream->upload_left -= nread;
+      if(!stream->upload_left)
+        *pflags = NGHTTP3_DATA_FLAG_EOF;
+    }
+    DEBUGF(LOG_CF(data, cf, "cb_h3_readfunction %zd bytes%s (at %zd unacked)",
+                  nread, *pflags == NGHTTP3_DATA_FLAG_EOF?" EOF":"",
+                  out->used));
+  }
+  if(stream->upload_done && !stream->upload_len &&
+     (stream->upload_left <= 0)) {
+    DEBUGF(LOG_CF(data, cf, "cb_h3_readfunction sets EOF"));
+    *pflags = NGHTTP3_DATA_FLAG_EOF;
+    return nread ? 1 : 0;
+  }
+  else if(!nread) {
+    return NGHTTP3_ERR_WOULDBLOCK;
+  }
+  return 1;
+}
+
+/* Index where :authority header field will appear in request header
+   field list. */
+#define AUTHORITY_DST_IDX 3
+
+static CURLcode h3_stream_open(struct Curl_cfilter *cf,
+                               struct Curl_easy *data,
+                               const void *mem,
+                               size_t len)
+{
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  struct HTTP *stream = data->req.p.http;
+  size_t nheader;
+  CURLcode result = CURLE_OK;
+  nghttp3_nv *nva = NULL;
+  int64_t stream3_id;
+  int rc = 0;
+  struct h3out *h3out = NULL;
+  struct h2h3req *hreq = NULL;
+
+  rc = ngtcp2_conn_open_bidi_stream(ctx->qconn, &stream3_id, NULL);
+  if(rc) {
+    failf(data, "can get bidi streams");
+    goto fail;
+  }
+
+  stream->stream3_id = stream3_id;
+  stream->h3req = TRUE;
+  Curl_dyn_init(&stream->overflow, CURL_MAX_READ_SIZE);
+  stream->recv_buf_nonflow = 0;
+
+  result = Curl_pseudo_headers(data, mem, len, NULL, &hreq);
+  if(result)
+    goto fail;
+  nheader = hreq->entries;
+
+  nva = malloc(sizeof(nghttp3_nv) * nheader);
+  if(!nva) {
+    result = CURLE_OUT_OF_MEMORY;
+    goto fail;
+  }
+  else {
+    unsigned int i;
+    for(i = 0; i < nheader; i++) {
+      nva[i].name = (unsigned char *)hreq->header[i].name;
+      nva[i].namelen = hreq->header[i].namelen;
+      nva[i].value = (unsigned char *)hreq->header[i].value;
+      nva[i].valuelen = hreq->header[i].valuelen;
+      nva[i].flags = NGHTTP3_NV_FLAG_NONE;
+    }
+  }
+
+  switch(data->state.httpreq) {
+  case HTTPREQ_POST:
+  case HTTPREQ_POST_FORM:
+  case HTTPREQ_POST_MIME:
+  case HTTPREQ_PUT: {
+    nghttp3_data_reader data_reader;
+    if(data->state.infilesize != -1)
+      stream->upload_left = data->state.infilesize;
+    else
+      /* data sending without specifying the data amount up front */
+      stream->upload_left = -1; /* unknown, but not zero */
+
+    data_reader.read_data = cb_h3_readfunction;
+
+    h3out = calloc(sizeof(struct h3out), 1);
+    if(!h3out) {
+      result = CURLE_OUT_OF_MEMORY;
+      goto fail;
+    }
+    stream->h3out = h3out;
+
+    rc = nghttp3_conn_submit_request(ctx->h3conn, stream->stream3_id,
+                                     nva, nheader, &data_reader, data);
+    if(rc)
+      goto fail;
+    break;
+  }
+  default:
+    stream->upload_left = 0; /* nothing left to send */
+    rc = nghttp3_conn_submit_request(ctx->h3conn, stream->stream3_id,
+                                     nva, nheader, NULL, data);
+    if(rc)
+      goto fail;
+    break;
+  }
+
+  Curl_safefree(nva);
+
+  infof(data, "Using HTTP/3 Stream ID: %" PRId64 " (easy handle %p)",
+        stream3_id, (void *)data);
+  DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] opened for %s",
+                stream3_id, data->state.url));
+
+  Curl_pseudo_free(hreq);
+  return CURLE_OK;
+
+fail:
+  if(rc) {
+    switch(rc) {
+    case NGHTTP3_ERR_CONN_CLOSING:
+      DEBUGF(LOG_CF(data, cf, "h3sid[%"PRId64"] failed to send, "
+                    "connection is closing", stream->stream3_id));
+      result = CURLE_RECV_ERROR;
+      break;
+    default:
+      DEBUGF(LOG_CF(data, cf, "h3sid[%"PRId64"] failed to send -> %d (%s)",
+                    stream->stream3_id, rc, ngtcp2_strerror(rc)));
+      result = CURLE_SEND_ERROR;
+      break;
+    }
+  }
+  free(nva);
+  Curl_pseudo_free(hreq);
+  return result;
+}
+
+static ssize_t cf_ngtcp2_send(struct Curl_cfilter *cf, struct Curl_easy *data,
+                              const void *buf, size_t len, CURLcode *err)
+{
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  ssize_t sent = 0;
+  struct HTTP *stream = data->req.p.http;
+  struct cf_call_data save;
+
+  CF_DATA_SAVE(save, cf, data);
+  DEBUGASSERT(cf->connected);
+  DEBUGASSERT(ctx->qconn);
+  DEBUGASSERT(ctx->h3conn);
+  *err = CURLE_OK;
+
+  if(stream->closed) {
+    *err = CURLE_HTTP3;
+    sent = -1;
+    goto out;
+  }
+
+  if(!stream->h3req) {
+    CURLcode result = h3_stream_open(cf, data, buf, len);
+    if(result) {
+      DEBUGF(LOG_CF(data, cf, "failed to open stream -> %d", result));
+      sent = -1;
+      goto out;
+    }
+    /* Assume that mem of length len only includes HTTP/1.1 style
+       header fields.  In other words, it does not contain request
+       body. */
+    sent = len;
+  }
+  else {
+    DEBUGF(LOG_CF(data, cf, "ngh3_stream_send() wants to send %zd bytes",
+                  len));
+    if(!stream->upload_len) {
+      stream->upload_mem = buf;
+      stream->upload_len = len;
+      (void)nghttp3_conn_resume_stream(ctx->h3conn, stream->stream3_id);
+    }
+    else {
+      *err = CURLE_AGAIN;
+      sent = -1;
+      goto out;
+    }
+  }
+
+  if(cf_flush_egress(cf, data)) {
+    *err = CURLE_SEND_ERROR;
+    sent = -1;
+    goto out;
+  }
+
+  /* Reset post upload buffer after resumed. */
+  if(stream->upload_mem) {
+    if(data->set.postfields) {
+      sent = len;
+    }
+    else {
+      sent = len - stream->upload_len;
+    }
+
+    stream->upload_mem = NULL;
+    stream->upload_len = 0;
+
+    if(sent == 0) {
+      *err = CURLE_AGAIN;
+      sent = -1;
+      goto out;
+    }
+  }
+out:
+  CF_DATA_RESTORE(cf, save);
+  return sent;
+}
+
+static CURLcode qng_verify_peer(struct Curl_cfilter *cf,
+                                struct Curl_easy *data)
+{
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  CURLcode result = CURLE_OK;
+  const char *hostname, *disp_hostname;
+  int port;
+  char *snihost;
+
+  Curl_conn_get_host(data, cf->sockindex, &hostname, &disp_hostname, &port);
+  snihost = Curl_ssl_snihost(data, hostname, NULL);
+  if(!snihost)
+      return CURLE_PEER_FAILED_VERIFICATION;
+
+  cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
+  cf->conn->httpversion = 30;
+  cf->conn->bundle->multiuse = BUNDLE_MULTIPLEX;
+
+  if(cf->conn->ssl_config.verifyhost) {
+#ifdef USE_OPENSSL
+    X509 *server_cert;
+    server_cert = SSL_get_peer_certificate(ctx->ssl);
+    if(!server_cert) {
+      return CURLE_PEER_FAILED_VERIFICATION;
+    }
+    result = Curl_ossl_verifyhost(data, cf->conn, server_cert);
+    X509_free(server_cert);
+    if(result)
+      return result;
+#elif defined(USE_GNUTLS)
+    result = Curl_gtls_verifyserver(data, ctx->gtls->session,
+                                    &cf->conn->ssl_config, &data->set.ssl,
+                                    hostname, disp_hostname,
+                                    data->set.str[STRING_SSL_PINNEDPUBLICKEY]);
+    if(result)
+      return result;
+#elif defined(USE_WOLFSSL)
+    if(wolfSSL_check_domain_name(ctx->ssl, snihost) == SSL_FAILURE)
+      return CURLE_PEER_FAILED_VERIFICATION;
+#endif
+    infof(data, "Verified certificate just fine");
+  }
+  else
+    infof(data, "Skipped certificate verification");
+#ifdef USE_OPENSSL
+  if(data->set.ssl.certinfo)
+    /* asked to gather certificate info */
+    (void)Curl_ossl_certchain(data, ctx->ssl);
+#endif
+  return result;
+}
+
+static CURLcode cf_process_ingress(struct Curl_cfilter *cf,
+                                   struct Curl_easy *data)
+{
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  ssize_t recvd;
+  int rv;
+  uint8_t buf[65536];
+  int bufsize = (int)sizeof(buf);
+  size_t pktcount = 0, total_recvd = 0;
+  struct sockaddr_storage remote_addr;
+  socklen_t remote_addrlen;
+  ngtcp2_path path;
+  ngtcp2_tstamp ts = timestamp();
+  ngtcp2_pkt_info pi = { 0 };
+
+  for(;;) {
+    remote_addrlen = sizeof(remote_addr);
+    while((recvd = recvfrom(ctx->q.sockfd, (char *)buf, bufsize, 0,
+                            (struct sockaddr *)&remote_addr,
+                            &remote_addrlen)) == -1 &&
+          SOCKERRNO == EINTR)
+      ;
+    if(recvd == -1) {
+      if(SOCKERRNO == EAGAIN || SOCKERRNO == EWOULDBLOCK) {
+        DEBUGF(LOG_CF(data, cf, "ingress, recvfrom -> EAGAIN"));
+        goto out;
+      }
+      if(!cf->connected && SOCKERRNO == ECONNREFUSED) {
+        const char *r_ip;
+        int r_port;
+        Curl_cf_socket_peek(cf->next, data, NULL, NULL,
+                            &r_ip, &r_port, NULL, NULL);
+        failf(data, "ngtcp2: connection to %s port %u refused",
+              r_ip, r_port);
+        return CURLE_COULDNT_CONNECT;
+      }
+      failf(data, "ngtcp2: recvfrom() unexpectedly returned %zd (errno=%d)",
+                  recvd, SOCKERRNO);
+      return CURLE_RECV_ERROR;
+    }
+
+    if(recvd > 0 && !ctx->got_first_byte) {
+      ctx->first_byte_at = Curl_now();
+      ctx->got_first_byte = TRUE;
+    }
+
+    ++pktcount;
+    total_recvd += recvd;
+
+    ngtcp2_addr_init(&path.local, (struct sockaddr *)&ctx->q.local_addr,
+                     ctx->q.local_addrlen);
+    ngtcp2_addr_init(&path.remote, (struct sockaddr *)&remote_addr,
+                     remote_addrlen);
+
+    rv = ngtcp2_conn_read_pkt(ctx->qconn, &path, &pi, buf, recvd, ts);
+    if(rv) {
+      DEBUGF(LOG_CF(data, cf, "ingress, read_pkt -> %s",
+                    ngtcp2_strerror(rv)));
+      if(!ctx->last_error.error_code) {
+        if(rv == NGTCP2_ERR_CRYPTO) {
+          ngtcp2_connection_close_error_set_transport_error_tls_alert(
+              &ctx->last_error,
+              ngtcp2_conn_get_tls_alert(ctx->qconn), NULL, 0);
+        }
+        else {
+          ngtcp2_connection_close_error_set_transport_error_liberr(
+              &ctx->last_error, rv, NULL, 0);
+        }
+      }
+
+      if(rv == NGTCP2_ERR_CRYPTO)
+        /* this is a "TLS problem", but a failed certificate verification
+           is a common reason for this */
+        return CURLE_PEER_FAILED_VERIFICATION;
+      return CURLE_RECV_ERROR;
+    }
+  }
+
+out:
+  (void)pktcount;
+  (void)total_recvd;
+  DEBUGF(LOG_CF(data, cf, "ingress, recvd %zu packets with %zd bytes",
+                pktcount, total_recvd));
+  return CURLE_OK;
+}
+
+static CURLcode cf_flush_egress(struct Curl_cfilter *cf,
+                                struct Curl_easy *data)
+{
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  int rv;
+  size_t sent;
+  ngtcp2_ssize outlen;
+  uint8_t *outpos = ctx->q.pktbuf;
+  size_t max_udp_payload_size =
+      ngtcp2_conn_get_max_tx_udp_payload_size(ctx->qconn);
+  size_t path_max_udp_payload_size =
+      ngtcp2_conn_get_path_max_tx_udp_payload_size(ctx->qconn);
+  size_t max_pktcnt =
+      CURLMIN(MAX_PKT_BURST, ctx->q.pktbuflen / max_udp_payload_size);
+  size_t pktcnt = 0;
+  size_t gsolen = 0;  /* this disables gso until we have a clue */
+  ngtcp2_path_storage ps;
+  ngtcp2_tstamp ts = timestamp();
+  ngtcp2_tstamp expiry;
+  ngtcp2_duration timeout;
+  int64_t stream_id;
+  nghttp3_ssize veccnt;
+  int fin;
+  nghttp3_vec vec[16];
+  ngtcp2_ssize ndatalen;
+  uint32_t flags;
+  CURLcode curlcode;
+
+  rv = ngtcp2_conn_handle_expiry(ctx->qconn, ts);
+  if(rv) {
+    failf(data, "ngtcp2_conn_handle_expiry returned error: %s",
+          ngtcp2_strerror(rv));
+    ngtcp2_connection_close_error_set_transport_error_liberr(&ctx->last_error,
+                                                             rv, NULL, 0);
+    return CURLE_SEND_ERROR;
+  }
+
+  if(ctx->q.num_blocked_pkt) {
+    curlcode = vquic_send_blocked_pkt(cf, data, &ctx->q);
+    if(curlcode) {
+      if(curlcode == CURLE_AGAIN) {
+        Curl_expire(data, 1, EXPIRE_QUIC);
+        return CURLE_OK;
+      }
+      return curlcode;
+    }
+  }
+
+  ngtcp2_path_storage_zero(&ps);
+
+  for(;;) {
+    veccnt = 0;
+    stream_id = -1;
+    fin = 0;
+
+    if(ctx->h3conn && ngtcp2_conn_get_max_data_left(ctx->qconn)) {
+      veccnt = nghttp3_conn_writev_stream(ctx->h3conn, &stream_id, &fin, vec,
+                                          sizeof(vec) / sizeof(vec[0]));
+      if(veccnt < 0) {
+        failf(data, "nghttp3_conn_writev_stream returned error: %s",
+              nghttp3_strerror((int)veccnt));
+        ngtcp2_connection_close_error_set_application_error(
+            &ctx->last_error,
+            nghttp3_err_infer_quic_app_error_code((int)veccnt), NULL, 0);
+        return CURLE_SEND_ERROR;
+      }
+    }
+
+    flags = NGTCP2_WRITE_STREAM_FLAG_MORE |
+            (fin ? NGTCP2_WRITE_STREAM_FLAG_FIN : 0);
+    outlen = ngtcp2_conn_writev_stream(ctx->qconn, &ps.path, NULL, outpos,
+                                       max_udp_payload_size,
+                                       &ndatalen, flags, stream_id,
+                                       (const ngtcp2_vec *)vec, veccnt, ts);
+    if(outlen == 0) {
+      /* ngtcp2 does not want to send more packets, if the buffer is
+       * not empty, send that now */
+      if(outpos != ctx->q.pktbuf) {
+        curlcode = vquic_send_packet(cf, data, &ctx->q, ctx->q.pktbuf,
+                               outpos - ctx->q.pktbuf, gsolen, &sent);
+        if(curlcode) {
+          if(curlcode == CURLE_AGAIN) {
+            vquic_push_blocked_pkt(cf, &ctx->q, ctx->q.pktbuf + sent,
+                                   outpos - ctx->q.pktbuf - sent,
+                                   gsolen);
+            Curl_expire(data, 1, EXPIRE_QUIC);
+            return CURLE_OK;
+          }
+          return curlcode;
+        }
+      }
+      /* done for now */
+      goto out;
+    }
+    if(outlen < 0) {
+      switch(outlen) {
+      case NGTCP2_ERR_STREAM_DATA_BLOCKED:
+        assert(ndatalen == -1);
+        nghttp3_conn_block_stream(ctx->h3conn, stream_id);
+        continue;
+      case NGTCP2_ERR_STREAM_SHUT_WR:
+        assert(ndatalen == -1);
+        nghttp3_conn_shutdown_stream_write(ctx->h3conn, stream_id);
+        continue;
+      case NGTCP2_ERR_WRITE_MORE:
+        /* ngtcp2 wants to send more. update the flow of the stream whose data
+         * is in the buffer and continue */
+        assert(ndatalen >= 0);
+        rv = nghttp3_conn_add_write_offset(ctx->h3conn, stream_id, ndatalen);
+        if(rv) {
+          failf(data, "nghttp3_conn_add_write_offset returned error: %s\n",
+                nghttp3_strerror(rv));
+          return CURLE_SEND_ERROR;
+        }
+        continue;
+      default:
+        assert(ndatalen == -1);
+        failf(data, "ngtcp2_conn_writev_stream returned error: %s",
+              ngtcp2_strerror((int)outlen));
+        ngtcp2_connection_close_error_set_transport_error_liberr(
+            &ctx->last_error, (int)outlen, NULL, 0);
+        return CURLE_SEND_ERROR;
+      }
+    }
+    else if(ndatalen >= 0) {
+      /* ngtcp2 thinks it has added all it wants. Update the stream  */
+      rv = nghttp3_conn_add_write_offset(ctx->h3conn, stream_id, ndatalen);
+      if(rv) {
+        failf(data, "nghttp3_conn_add_write_offset returned error: %s\n",
+              nghttp3_strerror(rv));
+        return CURLE_SEND_ERROR;
+      }
+    }
+
+    /* advance to the end of the buffered packet data */
+    outpos += outlen;
+
+    if(pktcnt == 0) {
+      /* first packet buffer chunk. use this as gsolen. It's how ngtcp2
+       * indicates the intended segment size. */
+      gsolen = outlen;
+    }
+    else if((size_t)outlen > gsolen ||
+            (gsolen > path_max_udp_payload_size && (size_t)outlen != gsolen)) {
+      /* Packet larger than path_max_udp_payload_size is PMTUD probe
+         packet and it might not be sent because of EMSGSIZE. Send
+         them separately to minimize the loss. */
+      /* send the pktbuf *before* the last addition */
+      curlcode = vquic_send_packet(cf, data, &ctx->q, ctx->q.pktbuf,
+                             outpos - outlen - ctx->q.pktbuf, gsolen, &sent);
+      if(curlcode) {
+        if(curlcode == CURLE_AGAIN) {
+          /* blocked, add the pktbuf *before* and *at* the last addition
+           * separately to the blocked packages */
+          vquic_push_blocked_pkt(cf, &ctx->q, ctx->q.pktbuf + sent,
+                           outpos - outlen - ctx->q.pktbuf - sent, gsolen);
+          vquic_push_blocked_pkt(cf, &ctx->q, outpos - outlen, outlen, outlen);
+          Curl_expire(data, 1, EXPIRE_QUIC);
+          return CURLE_OK;
+        }
+        return curlcode;
+      }
+      /* send the pktbuf *at* the last addition */
+      curlcode = vquic_send_packet(cf, data, &ctx->q, outpos - outlen, outlen,
+                                   outlen, &sent);
+      if(curlcode) {
+        if(curlcode == CURLE_AGAIN) {
+          assert(0 == sent);
+          vquic_push_blocked_pkt(cf, &ctx->q, outpos - outlen, outlen, outlen);
+          Curl_expire(data, 1, EXPIRE_QUIC);
+          return CURLE_OK;
+        }
+        return curlcode;
+      }
+      /* pktbuf has been completely sent */
+      pktcnt = 0;
+      outpos = ctx->q.pktbuf;
+      continue;
+    }
+
+    if(++pktcnt >= max_pktcnt || (size_t)outlen < gsolen) {
+      /* enough packets or last one is shorter than the intended
+       * segment size, indicating that it is time to send. */
+      curlcode = vquic_send_packet(cf, data, &ctx->q, ctx->q.pktbuf,
+                                   outpos - ctx->q.pktbuf, gsolen, &sent);
+      if(curlcode) {
+        if(curlcode == CURLE_AGAIN) {
+          vquic_push_blocked_pkt(cf, &ctx->q, ctx->q.pktbuf + sent,
+                                 outpos - ctx->q.pktbuf - sent, gsolen);
+          Curl_expire(data, 1, EXPIRE_QUIC);
+          return CURLE_OK;
+        }
+        return curlcode;
+      }
+      /* pktbuf has been completely sent */
+      pktcnt = 0;
+      outpos = ctx->q.pktbuf;
+    }
+  }
+
+out:
+  /* non-errored exit. check when we should run again. */
+  expiry = ngtcp2_conn_get_expiry(ctx->qconn);
+  if(expiry != UINT64_MAX) {
+    if(expiry <= ts) {
+      timeout = 0;
+    }
+    else {
+      timeout = expiry - ts;
+      if(timeout % NGTCP2_MILLISECONDS) {
+        timeout += NGTCP2_MILLISECONDS;
+      }
+    }
+    Curl_expire(data, timeout / NGTCP2_MILLISECONDS, EXPIRE_QUIC);
+  }
+
+  return CURLE_OK;
+}
+
+/*
+ * Called from transfer.c:data_pending to know if we should keep looping
+ * to receive more data from the connection.
+ */
+static bool cf_ngtcp2_data_pending(struct Curl_cfilter *cf,
+                                   const struct Curl_easy *data)
+{
+  /* We may have received more data than we're able to hold in the receive
+     buffer and allocated an overflow buffer. Since it's possible that
+     there's no more data coming on the socket, we need to keep reading
+     until the overflow buffer is empty. */
+  const struct HTTP *stream = data->req.p.http;
+  (void)cf;
+  return Curl_dyn_len(&stream->overflow) > 0;
+}
+
+static CURLcode cf_ngtcp2_data_event(struct Curl_cfilter *cf,
+                                     struct Curl_easy *data,
+                                     int event, int arg1, void *arg2)
+{
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  CURLcode result = CURLE_OK;
+  struct cf_call_data save;
+
+  CF_DATA_SAVE(save, cf, data);
+  (void)arg1;
+  (void)arg2;
+  switch(event) {
+  case CF_CTRL_DATA_DONE: {
+    struct HTTP *stream = data->req.p.http;
+    Curl_dyn_free(&stream->overflow);
+    free(stream->h3out);
+    break;
+  }
+  case CF_CTRL_DATA_DONE_SEND: {
+    struct HTTP *stream = data->req.p.http;
+    stream->upload_done = TRUE;
+    (void)nghttp3_conn_resume_stream(ctx->h3conn, stream->stream3_id);
+    break;
+  }
+  case CF_CTRL_DATA_IDLE:
+    if(timestamp() >= ngtcp2_conn_get_expiry(ctx->qconn)) {
+      if(cf_flush_egress(cf, data)) {
+        result = CURLE_SEND_ERROR;
+      }
+    }
+    break;
+  default:
+    break;
+  }
+  CF_DATA_RESTORE(cf, save);
+  return result;
+}
+
+static void cf_ngtcp2_ctx_clear(struct cf_ngtcp2_ctx *ctx)
+{
+  struct cf_call_data save = ctx->call_data;
+
+  if(ctx->qlogfd != -1) {
+    close(ctx->qlogfd);
+  }
+#ifdef USE_OPENSSL
+  if(ctx->ssl)
+    SSL_free(ctx->ssl);
+  if(ctx->sslctx)
+    SSL_CTX_free(ctx->sslctx);
+#elif defined(USE_GNUTLS)
+  if(ctx->gtls) {
+    if(ctx->gtls->cred)
+      gnutls_certificate_free_credentials(ctx->gtls->cred);
+    if(ctx->gtls->session)
+      gnutls_deinit(ctx->gtls->session);
+    free(ctx->gtls);
+  }
+#elif defined(USE_WOLFSSL)
+  if(ctx->ssl)
+    wolfSSL_free(ctx->ssl);
+  if(ctx->sslctx)
+    wolfSSL_CTX_free(ctx->sslctx);
+#endif
+  vquic_ctx_free(&ctx->q);
+  if(ctx->h3conn)
+    nghttp3_conn_del(ctx->h3conn);
+  if(ctx->qconn)
+    ngtcp2_conn_del(ctx->qconn);
+
+  memset(ctx, 0, sizeof(*ctx));
+  ctx->qlogfd = -1;
+  ctx->call_data = save;
+}
+
+static void cf_ngtcp2_close(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  struct cf_call_data save;
+
+  CF_DATA_SAVE(save, cf, data);
+  if(ctx && ctx->qconn) {
+    char buffer[NGTCP2_MAX_UDP_PAYLOAD_SIZE];
+    ngtcp2_tstamp ts;
+    ngtcp2_ssize rc;
+
+    DEBUGF(LOG_CF(data, cf, "close"));
+    ts = timestamp();
+    rc = ngtcp2_conn_write_connection_close(ctx->qconn, NULL, /* path */
+                                            NULL, /* pkt_info */
+                                            (uint8_t *)buffer, sizeof(buffer),
+                                            &ctx->last_error, ts);
+    if(rc > 0) {
+      while((send(ctx->q.sockfd, buffer, (SEND_TYPE_ARG3)rc, 0) == -1) &&
+            SOCKERRNO == EINTR);
+    }
+
+    cf_ngtcp2_ctx_clear(ctx);
+  }
+
+  cf->connected = FALSE;
+  CF_DATA_RESTORE(cf, save);
+}
+
+static void cf_ngtcp2_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  struct cf_call_data save;
+
+  CF_DATA_SAVE(save, cf, data);
+  DEBUGF(LOG_CF(data, cf, "destroy"));
+  if(ctx) {
+    cf_ngtcp2_ctx_clear(ctx);
+    free(ctx);
+  }
+  cf->ctx = NULL;
+  /* No CF_DATA_RESTORE(cf, save) possible */
+  (void)save;
+}
+
+/*
+ * Might be called twice for happy eyeballs.
+ */
+static CURLcode cf_connect_start(struct Curl_cfilter *cf,
+                                 struct Curl_easy *data)
+{
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  int rc;
+  int rv;
+  CURLcode result;
+  const struct Curl_sockaddr_ex *sockaddr;
+  int qfd;
+
+  ctx->version = NGTCP2_PROTO_VER_MAX;
+#ifdef USE_OPENSSL
+  result = quic_ssl_ctx(&ctx->sslctx, cf, data);
+  if(result)
+    return result;
+
+  result = quic_set_client_cert(cf, data);
+  if(result)
+    return result;
+#elif defined(USE_WOLFSSL)
+  result = quic_ssl_ctx(&ctx->sslctx, cf, data);
+  if(result)
+    return result;
+#endif
+
+  result = quic_init_ssl(cf, data);
+  if(result)
+    return result;
+
+  ctx->dcid.datalen = NGTCP2_MAX_CIDLEN;
+  result = Curl_rand(data, ctx->dcid.data, NGTCP2_MAX_CIDLEN);
+  if(result)
+    return result;
+
+  ctx->scid.datalen = NGTCP2_MAX_CIDLEN;
+  result = Curl_rand(data, ctx->scid.data, NGTCP2_MAX_CIDLEN);
+  if(result)
+    return result;
+
+  (void)Curl_qlogdir(data, ctx->scid.data, NGTCP2_MAX_CIDLEN, &qfd);
+  ctx->qlogfd = qfd; /* -1 if failure above */
+  quic_settings(ctx, data);
+
+  result = vquic_ctx_init(&ctx->q,
+                          NGTCP2_MAX_PMTUD_UDP_PAYLOAD_SIZE * MAX_PKT_BURST);
+  if(result)
+    return result;
+
+  Curl_cf_socket_peek(cf->next, data, &ctx->q.sockfd,
+                      &sockaddr, NULL, NULL, NULL, NULL);
+  ctx->q.local_addrlen = sizeof(ctx->q.local_addr);
+  rv = getsockname(ctx->q.sockfd, (struct sockaddr *)&ctx->q.local_addr,
+                   &ctx->q.local_addrlen);
+  if(rv == -1)
+    return CURLE_QUIC_CONNECT_ERROR;
+
+  ngtcp2_addr_init(&ctx->connected_path.local,
+                   (struct sockaddr *)&ctx->q.local_addr,
+                   ctx->q.local_addrlen);
+  ngtcp2_addr_init(&ctx->connected_path.remote,
+                   &sockaddr->sa_addr, sockaddr->addrlen);
+
+  rc = ngtcp2_conn_client_new(&ctx->qconn, &ctx->dcid, &ctx->scid,
+                              &ctx->connected_path,
+                              NGTCP2_PROTO_VER_V1, &ng_callbacks,
+                              &ctx->settings, &ctx->transport_params,
+                              NULL, cf);
+  if(rc)
+    return CURLE_QUIC_CONNECT_ERROR;
+
+#ifdef USE_GNUTLS
+  ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->gtls->session);
+#else
+  ngtcp2_conn_set_tls_native_handle(ctx->qconn, ctx->ssl);
+#endif
+
+  ngtcp2_connection_close_error_default(&ctx->last_error);
+
+  ctx->conn_ref.get_conn = get_conn;
+  ctx->conn_ref.user_data = cf;
+
+  return CURLE_OK;
+}
+
+static CURLcode cf_ngtcp2_connect(struct Curl_cfilter *cf,
+                                  struct Curl_easy *data,
+                                  bool blocking, bool *done)
+{
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  CURLcode result = CURLE_OK;
+  struct cf_call_data save;
+  struct curltime now;
+
+  if(cf->connected) {
+    *done = TRUE;
+    return CURLE_OK;
+  }
+
+  /* Connect the UDP filter first */
+  if(!cf->next->connected) {
+    result = Curl_conn_cf_connect(cf->next, data, blocking, done);
+    if(result || !*done)
+      return result;
+  }
+
+  *done = FALSE;
+  now = Curl_now();
+
+  CF_DATA_SAVE(save, cf, data);
+
+  if(ctx->reconnect_at.tv_sec && Curl_timediff(now, ctx->reconnect_at) < 0) {
+    /* Not time yet to attempt the next connect */
+    DEBUGF(LOG_CF(data, cf, "waiting for reconnect time"));
+    goto out;
+  }
+
+  if(!ctx->qconn) {
+    ctx->started_at = now;
+    result = cf_connect_start(cf, data);
+    if(result)
+      goto out;
+    result = cf_flush_egress(cf, data);
+    /* we do not expect to be able to recv anything yet */
+    goto out;
+  }
+
+  result = cf_process_ingress(cf, data);
+  if(result)
+    goto out;
+
+  result = cf_flush_egress(cf, data);
+  if(result)
+    goto out;
+
+  if(ngtcp2_conn_get_handshake_completed(ctx->qconn)) {
+    ctx->handshake_at = now;
+    DEBUGF(LOG_CF(data, cf, "handshake complete after %dms",
+           (int)Curl_timediff(now, ctx->started_at)));
+    result = qng_verify_peer(cf, data);
+    if(!result) {
+      DEBUGF(LOG_CF(data, cf, "peer verified"));
+      cf->connected = TRUE;
+      cf->conn->alpn = CURL_HTTP_VERSION_3;
+      *done = TRUE;
+      connkeep(cf->conn, "HTTP/3 default");
+    }
+  }
+
+out:
+  if(result == CURLE_RECV_ERROR && ctx->qconn &&
+     ngtcp2_conn_is_in_draining_period(ctx->qconn)) {
+    /* When a QUIC server instance is shutting down, it may send us a
+     * CONNECTION_CLOSE right away. Our connection then enters the DRAINING
+     * state.
+     * This may be a stopping of the service or it may be that the server
+     * is reloading and a new instance will start serving soon.
+     * In any case, we tear down our socket and start over with a new one.
+     * We re-open the underlying UDP cf right now, but do not start
+     * connecting until called again.
+     */
+    int reconn_delay_ms = 200;
+
+    DEBUGF(LOG_CF(data, cf, "connect, remote closed, reconnect after %dms",
+                  reconn_delay_ms));
+    Curl_conn_cf_close(cf->next, data);
+    cf_ngtcp2_ctx_clear(ctx);
+    result = Curl_conn_cf_connect(cf->next, data, FALSE, done);
+    if(!result && *done) {
+      *done = FALSE;
+      ctx->reconnect_at = now;
+      ctx->reconnect_at.tv_usec += reconn_delay_ms * 1000;
+      Curl_expire(data, reconn_delay_ms, EXPIRE_QUIC);
+      result = CURLE_OK;
+    }
+  }
+
+#ifndef CURL_DISABLE_VERBOSE_STRINGS
+  if(result) {
+    const char *r_ip;
+    int r_port;
+
+    Curl_cf_socket_peek(cf->next, data, NULL, NULL,
+                        &r_ip, &r_port, NULL, NULL);
+    infof(data, "QUIC connect to %s port %u failed: %s",
+          r_ip, r_port, curl_easy_strerror(result));
+  }
+#endif
+  DEBUGF(LOG_CF(data, cf, "connect -> %d, done=%d", result, *done));
+  CF_DATA_RESTORE(cf, save);
+  return result;
+}
+
+static CURLcode cf_ngtcp2_query(struct Curl_cfilter *cf,
+                                struct Curl_easy *data,
+                                int query, int *pres1, void *pres2)
+{
+  struct cf_ngtcp2_ctx *ctx = cf->ctx;
+  struct cf_call_data save;
+
+  switch(query) {
+  case CF_QUERY_MAX_CONCURRENT: {
+    const ngtcp2_transport_params *rp;
+    DEBUGASSERT(pres1);
+
+    CF_DATA_SAVE(save, cf, data);
+    rp = ngtcp2_conn_get_remote_transport_params(ctx->qconn);
+    if(rp)
+      *pres1 = (rp->initial_max_streams_bidi > INT_MAX)?
+                 INT_MAX : (int)rp->initial_max_streams_bidi;
+    else  /* not arrived yet? */
+      *pres1 = Curl_multi_max_concurrent_streams(data->multi);
+    DEBUGF(LOG_CF(data, cf, "query max_conncurrent -> %d", *pres1));
+    CF_DATA_RESTORE(cf, save);
+    return CURLE_OK;
+  }
+  case CF_QUERY_CONNECT_REPLY_MS:
+    if(ctx->got_first_byte) {
+      timediff_t ms = Curl_timediff(ctx->first_byte_at, ctx->started_at);
+      *pres1 = (ms < INT_MAX)? (int)ms : INT_MAX;
+    }
+    else
+      *pres1 = -1;
+    return CURLE_OK;
+  case CF_QUERY_TIMER_CONNECT: {
+    struct curltime *when = pres2;
+    if(ctx->got_first_byte)
+      *when = ctx->first_byte_at;
+    return CURLE_OK;
+  }
+  case CF_QUERY_TIMER_APPCONNECT: {
+    struct curltime *when = pres2;
+    if(cf->connected)
+      *when = ctx->handshake_at;
+    return CURLE_OK;
+  }
+  default:
+    break;
+  }
+  return cf->next?
+    cf->next->cft->query(cf->next, data, query, pres1, pres2) :
+    CURLE_UNKNOWN_OPTION;
+}
+
+static bool cf_ngtcp2_conn_is_alive(struct Curl_cfilter *cf,
+                                    struct Curl_easy *data,
+                                    bool *input_pending)
+{
+  bool alive = TRUE;
+
+  *input_pending = FALSE;
+  if(!cf->next || !cf->next->cft->is_alive(cf->next, data, input_pending))
+    return FALSE;
+
+  if(*input_pending) {
+    /* This happens before we've sent off a request and the connection is
+       not in use by any other transfer, there shouldn't be any data here,
+       only "protocol frames" */
+    *input_pending = FALSE;
+    Curl_attach_connection(data, cf->conn);
+    if(cf_process_ingress(cf, data))
+      alive = FALSE;
+    else {
+      alive = TRUE;
+    }
+    Curl_detach_connection(data);
+  }
+
+  return alive;
+}
+
+struct Curl_cftype Curl_cft_http3 = {
+  "HTTP/3",
+  CF_TYPE_IP_CONNECT | CF_TYPE_SSL | CF_TYPE_MULTIPLEX,
+  0,
+  cf_ngtcp2_destroy,
+  cf_ngtcp2_connect,
+  cf_ngtcp2_close,
+  Curl_cf_def_get_host,
+  cf_ngtcp2_get_select_socks,
+  cf_ngtcp2_data_pending,
+  cf_ngtcp2_send,
+  cf_ngtcp2_recv,
+  cf_ngtcp2_data_event,
+  cf_ngtcp2_conn_is_alive,
+  Curl_cf_def_conn_keep_alive,
+  cf_ngtcp2_query,
+};
+
+CURLcode Curl_cf_ngtcp2_create(struct Curl_cfilter **pcf,
+                               struct Curl_easy *data,
+                               struct connectdata *conn,
+                               const struct Curl_addrinfo *ai)
+{
+  struct cf_ngtcp2_ctx *ctx = NULL;
+  struct Curl_cfilter *cf = NULL, *udp_cf = NULL;
+  CURLcode result;
+
+  (void)data;
+  ctx = calloc(sizeof(*ctx), 1);
+  if(!ctx) {
+    result = CURLE_OUT_OF_MEMORY;
+    goto out;
+  }
+  ctx->qlogfd = -1;
+  cf_ngtcp2_ctx_clear(ctx);
+
+  result = Curl_cf_create(&cf, &Curl_cft_http3, ctx);
+  if(result)
+    goto out;
+
+  result = Curl_cf_udp_create(&udp_cf, data, conn, ai, TRNSPRT_QUIC);
+  if(result)
+    goto out;
+
+  cf->conn = conn;
+  udp_cf->conn = cf->conn;
+  udp_cf->sockindex = cf->sockindex;
+  cf->next = udp_cf;
+
+out:
+  *pcf = (!result)? cf : NULL;
+  if(result) {
+    if(udp_cf)
+      Curl_conn_cf_discard(udp_cf, data);
+    Curl_safefree(cf);
+    Curl_safefree(ctx);
+  }
+  return result;
+}
+
+bool Curl_conn_is_ngtcp2(const struct Curl_easy *data,
+                         const struct connectdata *conn,
+                         int sockindex)
+{
+  struct Curl_cfilter *cf = conn? conn->cfilter[sockindex] : NULL;
+
+  (void)data;
+  for(; cf; cf = cf->next) {
+    if(cf->cft == &Curl_cft_http3)
+      return TRUE;
+    if(cf->cft->flags & CF_TYPE_IP_CONNECT)
+      return FALSE;
+  }
+  return FALSE;
+}
+
+#endif
Index: trunk/src/libs/curl-8.0.1/lib/vquic/curl_ngtcp2.h
===================================================================
--- trunk/src/libs/curl-8.0.1/lib/vquic/curl_ngtcp2.h	(revision 99459)
+++ trunk/src/libs/curl-8.0.1/lib/vquic/curl_ngtcp2.h	(revision 99459)
@@ -0,0 +1,61 @@
+#ifndef HEADER_CURL_VQUIC_CURL_NGTCP2_H
+#define HEADER_CURL_VQUIC_CURL_NGTCP2_H
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+
+#ifdef USE_NGTCP2
+
+#ifdef HAVE_NETINET_UDP_H
+#include <netinet/udp.h>
+#endif
+
+#include <ngtcp2/ngtcp2_crypto.h>
+#include <nghttp3/nghttp3.h>
+#ifdef USE_OPENSSL
+#include <openssl/ssl.h>
+#elif defined(USE_WOLFSSL)
+#include <wolfssl/options.h>
+#include <wolfssl/ssl.h>
+#include <wolfssl/quic.h>
+#endif
+
+struct Curl_cfilter;
+
+#include "urldata.h"
+
+void Curl_ngtcp2_ver(char *p, size_t len);
+
+CURLcode Curl_cf_ngtcp2_create(struct Curl_cfilter **pcf,
+                             struct Curl_easy *data,
+                             struct connectdata *conn,
+                             const struct Curl_addrinfo *ai);
+
+bool Curl_conn_is_ngtcp2(const struct Curl_easy *data,
+                         const struct connectdata *conn,
+                         int sockindex);
+#endif
+
+#endif /* HEADER_CURL_VQUIC_CURL_NGTCP2_H */
Index: trunk/src/libs/curl-8.0.1/lib/vquic/curl_quiche.c
===================================================================
--- trunk/src/libs/curl-8.0.1/lib/vquic/curl_quiche.c	(revision 99459)
+++ trunk/src/libs/curl-8.0.1/lib/vquic/curl_quiche.c	(revision 99459)
@@ -0,0 +1,1464 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+
+#ifdef USE_QUICHE
+#include <quiche.h>
+#include <openssl/err.h>
+#include <openssl/ssl.h>
+#include "urldata.h"
+#include "cfilters.h"
+#include "cf-socket.h"
+#include "sendf.h"
+#include "strdup.h"
+#include "rand.h"
+#include "strcase.h"
+#include "multiif.h"
+#include "connect.h"
+#include "progress.h"
+#include "strerror.h"
+#include "vquic.h"
+#include "vquic_int.h"
+#include "curl_quiche.h"
+#include "transfer.h"
+#include "h2h3.h"
+#include "vtls/openssl.h"
+#include "vtls/keylog.h"
+
+/* The last 3 #include files should be in this order */
+#include "curl_printf.h"
+#include "curl_memory.h"
+#include "memdebug.h"
+
+
+#define QUIC_MAX_STREAMS (256*1024)
+#define QUIC_MAX_DATA (1*1024*1024)
+#define QUIC_IDLE_TIMEOUT (60 * 1000) /* milliseconds */
+
+/* how many UDP packets to send max in one call */
+#define MAX_PKT_BURST 10
+#define MAX_UDP_PAYLOAD_SIZE  1452
+
+/*
+ * Store quiche version info in this buffer.
+ */
+void Curl_quiche_ver(char *p, size_t len)
+{
+  (void)msnprintf(p, len, "quiche/%s", quiche_version());
+}
+
+static void keylog_callback(const SSL *ssl, const char *line)
+{
+  (void)ssl;
+  Curl_tls_keylog_write_line(line);
+}
+
+static SSL_CTX *quic_ssl_ctx(struct Curl_easy *data)
+{
+  SSL_CTX *ssl_ctx = SSL_CTX_new(TLS_method());
+
+  SSL_CTX_set_alpn_protos(ssl_ctx,
+                          (const uint8_t *)QUICHE_H3_APPLICATION_PROTOCOL,
+                          sizeof(QUICHE_H3_APPLICATION_PROTOCOL) - 1);
+
+  SSL_CTX_set_default_verify_paths(ssl_ctx);
+
+  /* Open the file if a TLS or QUIC backend has not done this before. */
+  Curl_tls_keylog_open();
+  if(Curl_tls_keylog_enabled()) {
+    SSL_CTX_set_keylog_callback(ssl_ctx, keylog_callback);
+  }
+
+  {
+    struct connectdata *conn = data->conn;
+    if(conn->ssl_config.verifypeer) {
+      const char * const ssl_cafile = conn->ssl_config.CAfile;
+      const char * const ssl_capath = conn->ssl_config.CApath;
+      if(ssl_cafile || ssl_capath) {
+        SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL);
+        /* tell OpenSSL where to find CA certificates that are used to verify
+           the server's certificate. */
+        if(!SSL_CTX_load_verify_locations(ssl_ctx, ssl_cafile, ssl_capath)) {
+          /* Fail if we insist on successfully verifying the server. */
+          failf(data, "error setting certificate verify locations:"
+                "  CAfile: %s CApath: %s",
+                ssl_cafile ? ssl_cafile : "none",
+                ssl_capath ? ssl_capath : "none");
+          return NULL;
+        }
+        infof(data, " CAfile: %s", ssl_cafile ? ssl_cafile : "none");
+        infof(data, " CApath: %s", ssl_capath ? ssl_capath : "none");
+      }
+#ifdef CURL_CA_FALLBACK
+      else {
+        /* verifying the peer without any CA certificates won't work so
+           use openssl's built-in default as fallback */
+        SSL_CTX_set_default_verify_paths(ssl_ctx);
+      }
+#endif
+    }
+  }
+  return ssl_ctx;
+}
+
+struct quic_handshake {
+  char *buf;       /* pointer to the buffer */
+  size_t alloclen; /* size of allocation */
+  size_t len;      /* size of content in buffer */
+  size_t nread;    /* how many bytes have been read */
+};
+
+struct h3_event_node {
+  struct h3_event_node *next;
+  quiche_h3_event *ev;
+};
+
+struct cf_quiche_ctx {
+  struct cf_quic_ctx q;
+  quiche_conn *qconn;
+  quiche_config *cfg;
+  quiche_h3_conn *h3c;
+  quiche_h3_config *h3config;
+  uint8_t scid[QUICHE_MAX_CONN_ID_LEN];
+  SSL_CTX *sslctx;
+  SSL *ssl;
+  struct curltime started_at;        /* time the current attempt started */
+  struct curltime handshake_at;      /* time connect handshake finished */
+  struct curltime first_byte_at;     /* when first byte was recvd */
+  struct curltime reconnect_at;      /* time the next attempt should start */
+  BIT(goaway);                       /* got GOAWAY from server */
+  BIT(got_first_byte);               /* if first byte was received */
+};
+
+
+#ifdef DEBUG_QUICHE
+static void quiche_debug_log(const char *line, void *argp)
+{
+  (void)argp;
+  fprintf(stderr, "%s\n", line);
+}
+#endif
+
+static void h3_clear_pending(struct Curl_easy *data)
+{
+  struct HTTP *stream = data->req.p.http;
+
+  if(stream->pending) {
+    struct h3_event_node *node, *next;
+    for(node = stream->pending; node; node = next) {
+      next = node->next;
+      quiche_h3_event_free(node->ev);
+      free(node);
+    }
+    stream->pending = NULL;
+  }
+}
+
+static void cf_quiche_ctx_clear(struct cf_quiche_ctx *ctx)
+{
+  if(ctx) {
+    vquic_ctx_free(&ctx->q);
+    if(ctx->qconn)
+      quiche_conn_free(ctx->qconn);
+    if(ctx->h3config)
+      quiche_h3_config_free(ctx->h3config);
+    if(ctx->h3c)
+      quiche_h3_conn_free(ctx->h3c);
+    if(ctx->cfg)
+      quiche_config_free(ctx->cfg);
+    memset(ctx, 0, sizeof(*ctx));
+  }
+}
+
+static void notify_drain(struct Curl_cfilter *cf,
+                         struct Curl_easy *data)
+{
+  (void)cf;
+  data->state.drain = 1;
+  Curl_expire(data, 0, EXPIRE_RUN_NOW);
+}
+
+static CURLcode h3_add_event(struct Curl_cfilter *cf,
+                             struct Curl_easy *data,
+                             int64_t stream3_id, quiche_h3_event *ev)
+{
+  struct Curl_easy *mdata;
+  struct h3_event_node *node, **pnext;
+
+  DEBUGASSERT(data->multi);
+  for(mdata = data->multi->easyp; mdata; mdata = mdata->next) {
+    if(mdata->req.p.http && mdata->req.p.http->stream3_id == stream3_id) {
+      break;
+    }
+  }
+
+  if(!mdata) {
+    DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"] event discarded, easy handle "
+                  "not found", stream3_id));
+    quiche_h3_event_free(ev);
+    return CURLE_OK;
+  }
+
+  node = calloc(sizeof(*node), 1);
+  if(!node) {
+    quiche_h3_event_free(ev);
+    return CURLE_OUT_OF_MEMORY;
+  }
+  node->ev = ev;
+  /* append to process them in order of arrival */
+  pnext = &mdata->req.p.http->pending;
+  while(*pnext) {
+    pnext = &((*pnext)->next);
+  }
+  *pnext = node;
+  notify_drain(cf, mdata);
+  return CURLE_OK;
+}
+
+struct h3h1header {
+  char *dest;
+  size_t destlen; /* left to use */
+  size_t nlen; /* used */
+};
+
+static int cb_each_header(uint8_t *name, size_t name_len,
+                          uint8_t *value, size_t value_len,
+                          void *argp)
+{
+  struct h3h1header *headers = (struct h3h1header *)argp;
+  size_t olen = 0;
+
+  if((name_len == 7) && !strncmp(H2H3_PSEUDO_STATUS, (char *)name, 7)) {
+    msnprintf(headers->dest,
+              headers->destlen, "HTTP/3 %.*s \r\n",
+              (int) value_len, value);
+  }
+  else if(!headers->nlen) {
+    return CURLE_HTTP3;
+  }
+  else {
+    msnprintf(headers->dest,
+              headers->destlen, "%.*s: %.*s\r\n",
+              (int)name_len, name, (int) value_len, value);
+  }
+  olen = strlen(headers->dest);
+  headers->destlen -= olen;
+  headers->nlen += olen;
+  headers->dest += olen;
+  return 0;
+}
+
+static ssize_t cf_recv_body(struct Curl_cfilter *cf,
+                                struct Curl_easy *data,
+                                char *buf, size_t len,
+                                CURLcode *err)
+{
+  struct cf_quiche_ctx *ctx = cf->ctx;
+  struct HTTP *stream = data->req.p.http;
+  ssize_t nread;
+  size_t offset = 0;
+
+  if(!stream->firstbody) {
+    /* add a header-body separator CRLF */
+    offset = 2;
+  }
+  nread = quiche_h3_recv_body(ctx->h3c, ctx->qconn, stream->stream3_id,
+                              (unsigned char *)buf + offset, len - offset);
+  if(nread >= 0) {
+    DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"][DATA] len=%zd",
+                  stream->stream3_id, nread));
+    if(!stream->firstbody) {
+      stream->firstbody = TRUE;
+      buf[0] = '\r';
+      buf[1] = '\n';
+      nread += offset;
+    }
+  }
+  else if(nread == -1) {
+    *err = CURLE_AGAIN;
+    stream->h3_recving_data = FALSE;
+  }
+  else {
+    failf(data, "Error %zd in HTTP/3 response body for stream[%"PRId64"]",
+          nread, stream->stream3_id);
+    stream->closed = TRUE;
+    stream->reset = TRUE;
+    streamclose(cf->conn, "Reset of stream");
+    stream->h3_recving_data = FALSE;
+    nread = -1;
+    *err = stream->h3_got_header? CURLE_PARTIAL_FILE : CURLE_RECV_ERROR;
+  }
+  return nread;
+}
+
+#ifdef DEBUGBUILD
+static const char *cf_ev_name(quiche_h3_event *ev)
+{
+  switch(quiche_h3_event_type(ev)) {
+  case QUICHE_H3_EVENT_HEADERS:
+    return "HEADERS";
+  case QUICHE_H3_EVENT_DATA:
+    return "DATA";
+  case QUICHE_H3_EVENT_RESET:
+    return "RESET";
+  case QUICHE_H3_EVENT_FINISHED:
+    return "FINISHED";
+  case QUICHE_H3_EVENT_GOAWAY:
+    return "GOAWAY";
+  default:
+    return "Unknown";
+  }
+}
+#else
+#define cf_ev_name(x)   ""
+#endif
+
+static ssize_t h3_process_event(struct Curl_cfilter *cf,
+                                struct Curl_easy *data,
+                                char *buf, size_t len,
+                                int64_t stream3_id,
+                                quiche_h3_event *ev,
+                                CURLcode *err)
+{
+  struct HTTP *stream = data->req.p.http;
+  ssize_t recvd = 0;
+  int rc;
+  struct h3h1header headers;
+
+  DEBUGASSERT(stream3_id == stream->stream3_id);
+
+  *err = CURLE_OK;
+  switch(quiche_h3_event_type(ev)) {
+  case QUICHE_H3_EVENT_HEADERS:
+    stream->h3_got_header = TRUE;
+    headers.dest = buf;
+    headers.destlen = len;
+    headers.nlen = 0;
+    rc = quiche_h3_event_for_each_header(ev, cb_each_header, &headers);
+    if(rc) {
+      failf(data, "Error %d in HTTP/3 response header for stream[%"PRId64"]",
+            rc, stream3_id);
+      *err = CURLE_RECV_ERROR;
+      recvd = -1;
+      break;
+    }
+    recvd = headers.nlen;
+    DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"][HEADERS] len=%zd",
+                  stream3_id, recvd));
+    break;
+
+  case QUICHE_H3_EVENT_DATA:
+    DEBUGASSERT(!stream->closed);
+    stream->h3_recving_data = TRUE;
+    recvd = cf_recv_body(cf, data, buf, len, err);
+    if(recvd < 0) {
+      if(*err != CURLE_AGAIN)
+        return -1;
+      recvd = 0;
+    }
+    break;
+
+  case QUICHE_H3_EVENT_RESET:
+      DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"][RESET]", stream3_id));
+    stream->closed = TRUE;
+    stream->reset = TRUE;
+    /* streamclose(cf->conn, "Reset of stream");*/
+    stream->h3_recving_data = FALSE;
+    break;
+
+  case QUICHE_H3_EVENT_FINISHED:
+    DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"][FINISHED]", stream3_id));
+    stream->closed = TRUE;
+    /* streamclose(cf->conn, "End of stream");*/
+    stream->h3_recving_data = FALSE;
+    break;
+
+  case QUICHE_H3_EVENT_GOAWAY:
+    DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"][GOAWAY]", stream3_id));
+    break;
+
+  default:
+    DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"] recv, unhandled event %d",
+                  stream3_id, quiche_h3_event_type(ev)));
+    break;
+  }
+  return recvd;
+}
+
+static ssize_t h3_process_pending(struct Curl_cfilter *cf,
+                                  struct Curl_easy *data,
+                                  char *buf, size_t len,
+                                  CURLcode *err)
+{
+  struct HTTP *stream = data->req.p.http;
+  struct h3_event_node *node = stream->pending, **pnext = &stream->pending;
+  ssize_t recvd = 0, erecvd;
+
+  *err = CURLE_OK;
+  DEBUGASSERT(stream);
+  while(node && len) {
+    erecvd = h3_process_event(cf, data, buf, len,
+                              stream->stream3_id, node->ev, err);
+    quiche_h3_event_free(node->ev);
+    *pnext = node->next;
+    free(node);
+    node = *pnext;
+    if(erecvd < 0) {
+      DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"] process event -> %d",
+                    stream->stream3_id, *err));
+      return erecvd;
+    }
+    recvd += erecvd;
+    *err = CURLE_OK;
+    buf += erecvd;
+    len -= erecvd;
+  }
+  return recvd;
+}
+
+static CURLcode cf_process_ingress(struct Curl_cfilter *cf,
+                                   struct Curl_easy *data)
+{
+  struct cf_quiche_ctx *ctx = cf->ctx;
+  int64_t stream3_id = data->req.p.http? data->req.p.http->stream3_id : -1;
+  uint8_t buf[65536];
+  int bufsize = (int)sizeof(buf);
+  struct sockaddr_storage remote_addr;
+  socklen_t remote_addrlen;
+  quiche_recv_info recv_info;
+  ssize_t recvd, nread;
+  ssize_t total = 0, pkts = 0;
+
+  DEBUGASSERT(ctx->qconn);
+
+  /* in case the timeout expired */
+  quiche_conn_on_timeout(ctx->qconn);
+
+  do {
+    remote_addrlen = sizeof(remote_addr);
+    while((recvd = recvfrom(ctx->q.sockfd, (char *)buf, bufsize, 0,
+                            (struct sockaddr *)&remote_addr,
+                            &remote_addrlen)) == -1 &&
+          SOCKERRNO == EINTR)
+      ;
+    if(recvd < 0) {
+      if((SOCKERRNO == EAGAIN) || (SOCKERRNO == EWOULDBLOCK)) {
+        break;
+      }
+      if(SOCKERRNO == ECONNREFUSED) {
+        const char *r_ip;
+        int r_port;
+        Curl_cf_socket_peek(cf->next, data, NULL, NULL,
+                            &r_ip, &r_port, NULL, NULL);
+        failf(data, "quiche: connection to %s:%u refused",
+              r_ip, r_port);
+        return CURLE_COULDNT_CONNECT;
+      }
+      failf(data, "quiche: recvfrom() unexpectedly returned %zd "
+            "(errno: %d, socket %d)", recvd, SOCKERRNO, ctx->q.sockfd);
+      return CURLE_RECV_ERROR;
+    }
+
+    total += recvd;
+    ++pkts;
+    if(recvd > 0 && !ctx->got_first_byte) {
+      ctx->first_byte_at = Curl_now();
+      ctx->got_first_byte = TRUE;
+    }
+    recv_info.from = (struct sockaddr *) &remote_addr;
+    recv_info.from_len = remote_addrlen;
+    recv_info.to = (struct sockaddr *) &ctx->q.local_addr;
+    recv_info.to_len = ctx->q.local_addrlen;
+
+    nread = quiche_conn_recv(ctx->qconn, buf, recvd, &recv_info);
+    if(nread < 0) {
+      if(QUICHE_ERR_DONE == nread) {
+        DEBUGF(LOG_CF(data, cf, "ingress, quiche is DONE"));
+        return CURLE_OK;
+      }
+      else if(QUICHE_ERR_TLS_FAIL == nread) {
+        long verify_ok = SSL_get_verify_result(ctx->ssl);
+        if(verify_ok != X509_V_OK) {
+          failf(data, "SSL certificate problem: %s",
+                X509_verify_cert_error_string(verify_ok));
+          return CURLE_PEER_FAILED_VERIFICATION;
+        }
+      }
+      else {
+        failf(data, "quiche_conn_recv() == %zd", nread);
+        return CURLE_RECV_ERROR;
+      }
+    }
+    else if(nread < recvd) {
+      DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] ingress, quiche only "
+                    "accepted %zd/%zd bytes",
+                    stream3_id, nread, recvd));
+    }
+
+  } while(pkts < 1000); /* arbitrary */
+
+  DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] ingress, recvd %zd bytes "
+                "in %zd packets", stream3_id, total, pkts));
+  return CURLE_OK;
+}
+
+/*
+ * flush_egress drains the buffers and sends off data.
+ * Calls failf() on errors.
+ */
+static CURLcode cf_flush_egress(struct Curl_cfilter *cf,
+                                struct Curl_easy *data)
+{
+  struct cf_quiche_ctx *ctx = cf->ctx;
+  int64_t stream3_id = data->req.p.http? data->req.p.http->stream3_id : -1;
+  quiche_send_info send_info;
+  ssize_t outlen, total_len = 0;
+  size_t max_udp_payload_size =
+    quiche_conn_max_send_udp_payload_size(ctx->qconn);
+  size_t gsolen = max_udp_payload_size;
+  size_t sent, pktcnt = 0;
+  CURLcode result;
+  int64_t timeout_ns;
+
+  ctx->q.no_gso = TRUE;
+  if(ctx->q.num_blocked_pkt) {
+    result = vquic_send_blocked_pkt(cf, data, &ctx->q);
+    if(result) {
+      if(result == CURLE_AGAIN) {
+        DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] egress, still not "
+                      "able to send blocked packet", stream3_id));
+        Curl_expire(data, 1, EXPIRE_QUIC);
+        return CURLE_OK;
+      }
+      goto out;
+    }
+  }
+
+  for(;;) {
+    outlen = quiche_conn_send(ctx->qconn, ctx->q.pktbuf, max_udp_payload_size,
+                              &send_info);
+    if(outlen == QUICHE_ERR_DONE) {
+      result = CURLE_OK;
+      goto out;
+    }
+
+    if(outlen < 0) {
+      failf(data, "quiche_conn_send returned %zd", outlen);
+      result = CURLE_SEND_ERROR;
+      goto out;
+    }
+
+    /* send the pktbuf *before* the last addition */
+    result = vquic_send_packet(cf, data, &ctx->q, ctx->q.pktbuf,
+                               outlen, gsolen, &sent);
+    ++pktcnt;
+    total_len += outlen;
+    if(result) {
+      if(result == CURLE_AGAIN) {
+        /* blocked, add the pktbuf *before* and *at* the last addition
+         * separately to the blocked packages */
+        DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] egress, pushing blocked "
+                      "packet with %zd bytes", stream3_id, outlen));
+        vquic_push_blocked_pkt(cf, &ctx->q, ctx->q.pktbuf, outlen, gsolen);
+        Curl_expire(data, 1, EXPIRE_QUIC);
+        return CURLE_OK;
+      }
+      goto out;
+    }
+  }
+
+out:
+  timeout_ns = quiche_conn_timeout_as_nanos(ctx->qconn);
+  if(timeout_ns % 1000000)
+    timeout_ns += 1000000;
+    /* expire resolution is milliseconds */
+  Curl_expire(data, (timeout_ns / 1000000), EXPIRE_QUIC);
+  if(pktcnt)
+    DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] egress, sent %zd packets "
+                  "with %zd bytes", stream3_id, pktcnt, total_len));
+  return result;
+}
+
+static ssize_t recv_closed_stream(struct Curl_cfilter *cf,
+                                  struct Curl_easy *data,
+                                  CURLcode *err)
+{
+  struct HTTP *stream = data->req.p.http;
+  ssize_t nread = -1;
+
+  if(stream->reset) {
+    failf(data,
+          "HTTP/3 stream %" PRId64 " reset by server", stream->stream3_id);
+    *err = stream->h3_got_header? CURLE_PARTIAL_FILE : CURLE_RECV_ERROR;
+    DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] cf_recv, was reset -> %d",
+                  stream->stream3_id, *err));
+    goto out;
+  }
+
+  if(!stream->h3_got_header) {
+    failf(data,
+          "HTTP/3 stream %" PRId64 " was closed cleanly, but before getting"
+          " all response header fields, treated as error",
+          stream->stream3_id);
+    /* *err = CURLE_PARTIAL_FILE; */
+    *err = CURLE_RECV_ERROR;
+    DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] cf_recv, closed incomplete"
+                  " -> %d", stream->stream3_id, *err));
+    goto out;
+  }
+  else {
+    DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] cf_recv, closed ok"
+                  " -> %d", stream->stream3_id, *err));
+  }
+  *err = CURLE_OK;
+  nread = 0;
+
+out:
+  return nread;
+}
+
+static CURLcode cf_poll_events(struct Curl_cfilter *cf,
+                               struct Curl_easy *data)
+{
+  struct cf_quiche_ctx *ctx = cf->ctx;
+  struct HTTP *stream = data->req.p.http;
+  quiche_h3_event *ev;
+
+  /* Take in the events and distribute them to the transfers. */
+  while(1) {
+    int64_t stream3_id = quiche_h3_conn_poll(ctx->h3c, ctx->qconn, &ev);
+    if(stream3_id < 0) {
+      /* nothing more to do */
+      break;
+    }
+    DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"] recv, queue event %s "
+                  "for [h3sid=%"PRId64"]",
+                  stream? stream->stream3_id : -1, cf_ev_name(ev),
+                  stream3_id));
+    if(h3_add_event(cf, data, stream3_id, ev) != CURLE_OK) {
+      return CURLE_OUT_OF_MEMORY;
+    }
+  }
+  return CURLE_OK;
+}
+
+static ssize_t cf_recv_transfer_data(struct Curl_cfilter *cf,
+                                     struct Curl_easy *data,
+                                      char *buf, size_t len,
+                                      CURLcode *err)
+{
+  struct HTTP *stream = data->req.p.http;
+  ssize_t recvd = -1;
+  size_t offset = 0;
+
+  if(stream->h3_recving_data) {
+    /* try receiving body first */
+    recvd = cf_recv_body(cf, data, buf, len, err);
+    if(recvd < 0) {
+      if(*err != CURLE_AGAIN)
+        return -1;
+      recvd = 0;
+    }
+    if(recvd > 0) {
+      offset = recvd;
+    }
+  }
+
+  if(offset < len && stream->pending) {
+    /* process any pending events for `data` first. if there are,
+     * return so the transfer can handle those. We do not want to
+     * progress ingress while events are pending here. */
+    recvd = h3_process_pending(cf, data, buf + offset, len - offset, err);
+    if(recvd < 0) {
+      if(*err != CURLE_AGAIN)
+        return -1;
+      recvd = 0;
+    }
+    if(recvd > 0) {
+      offset += recvd;
+    }
+  }
+
+  if(offset) {
+    *err = CURLE_OK;
+    return offset;
+  }
+  *err = CURLE_AGAIN;
+  return 0;
+}
+
+static ssize_t cf_quiche_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
+                              char *buf, size_t len, CURLcode *err)
+{
+  struct HTTP *stream = data->req.p.http;
+  ssize_t recvd = -1;
+
+  *err = CURLE_AGAIN;
+
+  recvd = cf_recv_transfer_data(cf, data, buf, len, err);
+  if(recvd)
+    goto out;
+  if(stream->closed) {
+    recvd = recv_closed_stream(cf, data, err);
+    goto out;
+  }
+
+  /* we did get nothing from the quiche buffers or pending events.
+   * Take in more data from the connection, any error is fatal */
+  if(cf_process_ingress(cf, data)) {
+    DEBUGF(LOG_CF(data, cf, "h3_stream_recv returns on ingress"));
+    *err = CURLE_RECV_ERROR;
+    recvd = -1;
+    goto out;
+  }
+  /* poll quiche and distribute the events to the transfers */
+  *err = cf_poll_events(cf, data);
+  if(*err) {
+    recvd = -1;
+    goto out;
+  }
+
+  /* try to receive again for this transfer */
+  recvd = cf_recv_transfer_data(cf, data, buf, len, err);
+  if(recvd)
+    goto out;
+  if(stream->closed) {
+    recvd = recv_closed_stream(cf, data, err);
+    goto out;
+  }
+  recvd = -1;
+  *err = CURLE_AGAIN;
+  data->state.drain = 0;
+
+out:
+  if(cf_flush_egress(cf, data)) {
+    DEBUGF(LOG_CF(data, cf, "cf_recv, flush egress failed"));
+    *err = CURLE_SEND_ERROR;
+    return -1;
+  }
+  DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"] cf_recv -> %zd, err=%d",
+                stream->stream3_id, recvd, *err));
+  if(recvd > 0)
+    notify_drain(cf, data);
+  return recvd;
+}
+
+/* Index where :authority header field will appear in request header
+   field list. */
+#define AUTHORITY_DST_IDX 3
+
+static CURLcode cf_http_request(struct Curl_cfilter *cf,
+                                struct Curl_easy *data,
+                                const void *mem,
+                                size_t len)
+{
+  struct cf_quiche_ctx *ctx = cf->ctx;
+  struct HTTP *stream = data->req.p.http;
+  size_t nheader;
+  int64_t stream3_id;
+  quiche_h3_header *nva = NULL;
+  CURLcode result = CURLE_OK;
+  struct h2h3req *hreq = NULL;
+
+  stream->h3req = TRUE; /* send off! */
+  stream->closed = FALSE;
+  stream->reset = FALSE;
+
+  result = Curl_pseudo_headers(data, mem, len, NULL, &hreq);
+  if(result)
+    goto fail;
+  nheader = hreq->entries;
+
+  nva = malloc(sizeof(quiche_h3_header) * nheader);
+  if(!nva) {
+    result = CURLE_OUT_OF_MEMORY;
+    goto fail;
+  }
+  else {
+    unsigned int i;
+    for(i = 0; i < nheader; i++) {
+      nva[i].name = (unsigned char *)hreq->header[i].name;
+      nva[i].name_len = hreq->header[i].namelen;
+      nva[i].value = (unsigned char *)hreq->header[i].value;
+      nva[i].value_len = hreq->header[i].valuelen;
+    }
+  }
+
+  switch(data->state.httpreq) {
+  case HTTPREQ_POST:
+  case HTTPREQ_POST_FORM:
+  case HTTPREQ_POST_MIME:
+  case HTTPREQ_PUT:
+    if(data->state.infilesize != -1)
+      stream->upload_left = data->state.infilesize;
+    else
+      /* data sending without specifying the data amount up front */
+      stream->upload_left = -1; /* unknown, but not zero */
+
+    stream->upload_done = !stream->upload_left;
+    stream3_id = quiche_h3_send_request(ctx->h3c, ctx->qconn, nva, nheader,
+                                        stream->upload_done);
+    break;
+  default:
+    stream->upload_left = 0;
+    stream->upload_done = TRUE;
+    stream3_id = quiche_h3_send_request(ctx->h3c, ctx->qconn, nva, nheader,
+                                        TRUE);
+    break;
+  }
+
+  Curl_safefree(nva);
+
+  if(stream3_id < 0) {
+    if(QUICHE_H3_ERR_STREAM_BLOCKED == stream3_id) {
+      DEBUGF(LOG_CF(data, cf, "send_request(%s, body_len=%ld) rejected "
+                    "with H3_ERR_STREAM_BLOCKED",
+                    data->state.url, (long)stream->upload_left));
+      result = CURLE_AGAIN;
+      goto fail;
+    }
+    else {
+      DEBUGF(LOG_CF(data, cf, "send_request(%s, body_len=%ld) -> %" PRId64,
+                    data->state.url, (long)stream->upload_left, stream3_id));
+    }
+    result = CURLE_SEND_ERROR;
+    goto fail;
+  }
+
+  stream->stream3_id = stream3_id;
+  infof(data, "Using HTTP/3 Stream ID: %" PRId64 " (easy handle %p)",
+        stream3_id, (void *)data);
+  DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] opened for %s",
+                stream3_id, data->state.url));
+
+  Curl_pseudo_free(hreq);
+  return CURLE_OK;
+
+fail:
+  free(nva);
+  Curl_pseudo_free(hreq);
+  return result;
+}
+
+static ssize_t cf_quiche_send(struct Curl_cfilter *cf, struct Curl_easy *data,
+                              const void *buf, size_t len, CURLcode *err)
+{
+  struct cf_quiche_ctx *ctx = cf->ctx;
+  struct HTTP *stream = data->req.p.http;
+  ssize_t nwritten;
+
+  DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] cf_send(len=%zu) start",
+                stream->h3req? stream->stream3_id : -1, len));
+  *err = cf_process_ingress(cf, data);
+  if(*err)
+    return -1;
+
+  if(!stream->h3req) {
+    CURLcode result = cf_http_request(cf, data, buf, len);
+    if(result) {
+      *err = result;
+      return -1;
+    }
+    nwritten = len;
+  }
+  else {
+    nwritten = quiche_h3_send_body(ctx->h3c, ctx->qconn, stream->stream3_id,
+                                   (uint8_t *)buf, len, FALSE);
+    DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRId64 "] send body(len=%zu) -> %zd",
+                  stream->stream3_id, len, nwritten));
+    if(nwritten == QUICHE_H3_ERR_DONE) {
+      /* no error, nothing to do (flow control?) */
+      *err = CURLE_AGAIN;
+      nwritten = -1;
+    }
+    else if(nwritten == QUICHE_H3_TRANSPORT_ERR_FINAL_SIZE) {
+      DEBUGF(LOG_CF(data, cf, "send_body(len=%zu) -> exceeds size", len));
+      *err = CURLE_SEND_ERROR;
+      nwritten = -1;
+    }
+    else if(nwritten < 0) {
+      DEBUGF(LOG_CF(data, cf, "send_body(len=%zu) -> SEND_ERROR", len));
+      *err = CURLE_SEND_ERROR;
+      nwritten = -1;
+    }
+    else {
+      *err = CURLE_OK;
+    }
+  }
+
+  if(cf_flush_egress(cf, data)) {
+    *err = CURLE_SEND_ERROR;
+    return -1;
+  }
+
+  return nwritten;
+}
+
+static bool stream_is_writeable(struct Curl_cfilter *cf,
+                                struct Curl_easy *data)
+{
+  struct cf_quiche_ctx *ctx = cf->ctx;
+  struct HTTP *stream = data->req.p.http;
+
+  /* surely, there must be a better way */
+  quiche_stream_iter *qiter = quiche_conn_writable(ctx->qconn);
+  if(qiter) {
+    uint64_t stream_id;
+    while(quiche_stream_iter_next(qiter, &stream_id)) {
+      if(stream_id == (uint64_t)stream->stream3_id)
+        return TRUE;
+    }
+    quiche_stream_iter_free(qiter);
+  }
+  return FALSE;
+}
+
+static int cf_quiche_get_select_socks(struct Curl_cfilter *cf,
+                                      struct Curl_easy *data,
+                                      curl_socket_t *socks)
+{
+  struct cf_quiche_ctx *ctx = cf->ctx;
+  struct SingleRequest *k = &data->req;
+  int rv = GETSOCK_BLANK;
+
+  socks[0] = ctx->q.sockfd;
+
+  /* in an HTTP/3 connection we can basically always get a frame so we should
+     always be ready for one */
+  rv |= GETSOCK_READSOCK(0);
+
+  /* we're still uploading or the HTTP/3 layer wants to send data */
+  if(((k->keepon & KEEP_SENDBITS) == KEEP_SEND)
+     && stream_is_writeable(cf, data))
+    rv |= GETSOCK_WRITESOCK(0);
+
+  return rv;
+}
+
+/*
+ * Called from transfer.c:data_pending to know if we should keep looping
+ * to receive more data from the connection.
+ */
+static bool cf_quiche_data_pending(struct Curl_cfilter *cf,
+                                   const struct Curl_easy *data)
+{
+  struct HTTP *stream = data->req.p.http;
+
+  if(stream->pending) {
+    DEBUGF(LOG_CF((struct Curl_easy *)data, cf,
+                   "[h3sid=%"PRId64"] has event pending", stream->stream3_id));
+    return TRUE;
+  }
+  if(stream->h3_recving_data) {
+    DEBUGF(LOG_CF((struct Curl_easy *)data, cf,
+                   "[h3sid=%"PRId64"] is receiving DATA", stream->stream3_id));
+    return TRUE;
+  }
+  if(data->state.drain) {
+    DEBUGF(LOG_CF((struct Curl_easy *)data, cf,
+                   "[h3sid=%"PRId64"] is draining", stream->stream3_id));
+    return TRUE;
+  }
+  return FALSE;
+}
+
+static CURLcode cf_quiche_data_event(struct Curl_cfilter *cf,
+                                     struct Curl_easy *data,
+                                     int event, int arg1, void *arg2)
+{
+  struct cf_quiche_ctx *ctx = cf->ctx;
+  CURLcode result = CURLE_OK;
+
+  (void)arg1;
+  (void)arg2;
+  switch(event) {
+  case CF_CTRL_DATA_DONE: {
+    struct HTTP *stream = data->req.p.http;
+    DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"] easy handle is %s",
+                  stream->stream3_id, arg1? "cancelled" : "done"));
+    h3_clear_pending(data);
+    break;
+  }
+  case CF_CTRL_DATA_DONE_SEND: {
+    struct HTTP *stream = data->req.p.http;
+    ssize_t sent;
+    stream->upload_done = TRUE;
+    sent = quiche_h3_send_body(ctx->h3c, ctx->qconn, stream->stream3_id,
+                               NULL, 0, TRUE);
+    DEBUGF(LOG_CF(data, cf, "[h3sid=%"PRId64"] send_body FINISHED",
+                  stream->stream3_id));
+    if(sent < 0)
+      return CURLE_SEND_ERROR;
+    break;
+  }
+  case CF_CTRL_DATA_IDLE:
+    /* anything to do? */
+    break;
+  default:
+    break;
+  }
+  return result;
+}
+
+static CURLcode cf_verify_peer(struct Curl_cfilter *cf,
+                               struct Curl_easy *data)
+{
+  struct cf_quiche_ctx *ctx = cf->ctx;
+  CURLcode result = CURLE_OK;
+
+  cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
+  cf->conn->httpversion = 30;
+  cf->conn->bundle->multiuse = BUNDLE_MULTIPLEX;
+
+  if(cf->conn->ssl_config.verifyhost) {
+    X509 *server_cert;
+    server_cert = SSL_get_peer_certificate(ctx->ssl);
+    if(!server_cert) {
+      result = CURLE_PEER_FAILED_VERIFICATION;
+      goto out;
+    }
+    result = Curl_ossl_verifyhost(data, cf->conn, server_cert);
+    X509_free(server_cert);
+    if(result)
+      goto out;
+  }
+  else
+    DEBUGF(LOG_CF(data, cf, "Skipped certificate verification"));
+
+  ctx->h3config = quiche_h3_config_new();
+  if(!ctx->h3config) {
+    result = CURLE_OUT_OF_MEMORY;
+    goto out;
+  }
+
+  /* Create a new HTTP/3 connection on the QUIC connection. */
+  ctx->h3c = quiche_h3_conn_new_with_transport(ctx->qconn, ctx->h3config);
+  if(!ctx->h3c) {
+    result = CURLE_OUT_OF_MEMORY;
+    goto out;
+  }
+  if(data->set.ssl.certinfo)
+    /* asked to gather certificate info */
+    (void)Curl_ossl_certchain(data, ctx->ssl);
+
+out:
+  if(result) {
+    if(ctx->h3config) {
+      quiche_h3_config_free(ctx->h3config);
+      ctx->h3config = NULL;
+    }
+    if(ctx->h3c) {
+      quiche_h3_conn_free(ctx->h3c);
+      ctx->h3c = NULL;
+    }
+  }
+  return result;
+}
+
+static CURLcode cf_connect_start(struct Curl_cfilter *cf,
+                                 struct Curl_easy *data)
+{
+  struct cf_quiche_ctx *ctx = cf->ctx;
+  int rv;
+  CURLcode result;
+  const struct Curl_sockaddr_ex *sockaddr;
+
+  DEBUGASSERT(ctx->q.sockfd != CURL_SOCKET_BAD);
+
+#ifdef DEBUG_QUICHE
+  /* initialize debug log callback only once */
+  static int debug_log_init = 0;
+  if(!debug_log_init) {
+    quiche_enable_debug_logging(quiche_debug_log, NULL);
+    debug_log_init = 1;
+  }
+#endif
+
+  result = vquic_ctx_init(&ctx->q, MAX_UDP_PAYLOAD_SIZE * MAX_PKT_BURST);
+  if(result)
+    return result;
+
+  ctx->cfg = quiche_config_new(QUICHE_PROTOCOL_VERSION);
+  if(!ctx->cfg) {
+    failf(data, "can't create quiche config");
+    return CURLE_FAILED_INIT;
+  }
+  quiche_config_set_max_idle_timeout(ctx->cfg, QUIC_IDLE_TIMEOUT);
+  quiche_config_set_initial_max_data(ctx->cfg, QUIC_MAX_DATA);
+  quiche_config_set_initial_max_stream_data_bidi_local(
+    ctx->cfg, QUIC_MAX_DATA);
+  quiche_config_set_initial_max_stream_data_bidi_remote(
+    ctx->cfg, QUIC_MAX_DATA);
+  quiche_config_set_initial_max_stream_data_uni(ctx->cfg, QUIC_MAX_DATA);
+  quiche_config_set_initial_max_streams_bidi(ctx->cfg, QUIC_MAX_STREAMS);
+  quiche_config_set_initial_max_streams_uni(ctx->cfg, QUIC_MAX_STREAMS);
+  quiche_config_set_application_protos(ctx->cfg,
+                                       (uint8_t *)
+                                       QUICHE_H3_APPLICATION_PROTOCOL,
+                                       sizeof(QUICHE_H3_APPLICATION_PROTOCOL)
+                                       - 1);
+
+  DEBUGASSERT(!ctx->ssl);
+  DEBUGASSERT(!ctx->sslctx);
+  ctx->sslctx = quic_ssl_ctx(data);
+  if(!ctx->sslctx)
+    return CURLE_QUIC_CONNECT_ERROR;
+  ctx->ssl = SSL_new(ctx->sslctx);
+  if(!ctx->ssl)
+    return CURLE_QUIC_CONNECT_ERROR;
+
+  SSL_set_app_data(ctx->ssl, cf);
+  SSL_set_tlsext_host_name(ctx->ssl, cf->conn->host.name);
+
+  result = Curl_rand(data, ctx->scid, sizeof(ctx->scid));
+  if(result)
+    return result;
+
+  Curl_cf_socket_peek(cf->next, data, &ctx->q.sockfd,
+                      &sockaddr, NULL, NULL, NULL, NULL);
+  ctx->q.local_addrlen = sizeof(ctx->q.local_addr);
+  rv = getsockname(ctx->q.sockfd, (struct sockaddr *)&ctx->q.local_addr,
+                   &ctx->q.local_addrlen);
+  if(rv == -1)
+    return CURLE_QUIC_CONNECT_ERROR;
+
+  ctx->qconn = quiche_conn_new_with_tls((const uint8_t *)ctx->scid,
+                                      sizeof(ctx->scid), NULL, 0,
+                                      (struct sockaddr *)&ctx->q.local_addr,
+                                      ctx->q.local_addrlen,
+                                      &sockaddr->sa_addr, sockaddr->addrlen,
+                                      ctx->cfg, ctx->ssl, false);
+  if(!ctx->qconn) {
+    failf(data, "can't create quiche connection");
+    return CURLE_OUT_OF_MEMORY;
+  }
+
+  /* Known to not work on Windows */
+#if !defined(WIN32) && defined(HAVE_QUICHE_CONN_SET_QLOG_FD)
+  {
+    int qfd;
+    (void)Curl_qlogdir(data, ctx->scid, sizeof(ctx->scid), &qfd);
+    if(qfd != -1)
+      quiche_conn_set_qlog_fd(ctx->qconn, qfd,
+                              "qlog title", "curl qlog");
+  }
+#endif
+
+  result = cf_flush_egress(cf, data);
+  if(result)
+    return result;
+
+  {
+    unsigned char alpn_protocols[] = QUICHE_H3_APPLICATION_PROTOCOL;
+    unsigned alpn_len, offset = 0;
+
+    /* Replace each ALPN length prefix by a comma. */
+    while(offset < sizeof(alpn_protocols) - 1) {
+      alpn_len = alpn_protocols[offset];
+      alpn_protocols[offset] = ',';
+      offset += 1 + alpn_len;
+    }
+
+    DEBUGF(LOG_CF(data, cf, "Sent QUIC client Initial, ALPN: %s",
+                   alpn_protocols + 1));
+  }
+
+  return CURLE_OK;
+}
+
+static CURLcode cf_quiche_connect(struct Curl_cfilter *cf,
+                                  struct Curl_easy *data,
+                                  bool blocking, bool *done)
+{
+  struct cf_quiche_ctx *ctx = cf->ctx;
+  CURLcode result = CURLE_OK;
+  struct curltime now;
+
+  if(cf->connected) {
+    *done = TRUE;
+    return CURLE_OK;
+  }
+
+  /* Connect the UDP filter first */
+  if(!cf->next->connected) {
+    result = Curl_conn_cf_connect(cf->next, data, blocking, done);
+    if(result || !*done)
+      return result;
+  }
+
+  *done = FALSE;
+  now = Curl_now();
+
+  if(ctx->reconnect_at.tv_sec && Curl_timediff(now, ctx->reconnect_at) < 0) {
+    /* Not time yet to attempt the next connect */
+    DEBUGF(LOG_CF(data, cf, "waiting for reconnect time"));
+    goto out;
+  }
+
+  if(!ctx->qconn) {
+    result = cf_connect_start(cf, data);
+    if(result)
+      goto out;
+    ctx->started_at = now;
+    result = cf_flush_egress(cf, data);
+    /* we do not expect to be able to recv anything yet */
+    goto out;
+  }
+
+  result = cf_process_ingress(cf, data);
+  if(result)
+    goto out;
+
+  result = cf_flush_egress(cf, data);
+  if(result)
+    goto out;
+
+  if(quiche_conn_is_established(ctx->qconn)) {
+    DEBUGF(LOG_CF(data, cf, "handshake complete after %dms",
+           (int)Curl_timediff(now, ctx->started_at)));
+    ctx->handshake_at = now;
+    result = cf_verify_peer(cf, data);
+    if(!result) {
+      DEBUGF(LOG_CF(data, cf, "peer verified"));
+      cf->connected = TRUE;
+      cf->conn->alpn = CURL_HTTP_VERSION_3;
+      *done = TRUE;
+      connkeep(cf->conn, "HTTP/3 default");
+    }
+  }
+  else if(quiche_conn_is_draining(ctx->qconn)) {
+    /* When a QUIC server instance is shutting down, it may send us a
+     * CONNECTION_CLOSE right away. Our connection then enters the DRAINING
+     * state.
+     * This may be a stopping of the service or it may be that the server
+     * is reloading and a new instance will start serving soon.
+     * In any case, we tear down our socket and start over with a new one.
+     * We re-open the underlying UDP cf right now, but do not start
+     * connecting until called again.
+     */
+    int reconn_delay_ms = 200;
+
+    DEBUGF(LOG_CF(data, cf, "connect, remote closed, reconnect after %dms",
+                  reconn_delay_ms));
+    Curl_conn_cf_close(cf->next, data);
+    cf_quiche_ctx_clear(ctx);
+    result = Curl_conn_cf_connect(cf->next, data, FALSE, done);
+    if(!result && *done) {
+      *done = FALSE;
+      ctx->reconnect_at = Curl_now();
+      ctx->reconnect_at.tv_usec += reconn_delay_ms * 1000;
+      Curl_expire(data, reconn_delay_ms, EXPIRE_QUIC);
+      result = CURLE_OK;
+    }
+  }
+
+out:
+#ifndef CURL_DISABLE_VERBOSE_STRINGS
+  if(result && result != CURLE_AGAIN) {
+    const char *r_ip;
+    int r_port;
+
+    Curl_cf_socket_peek(cf->next, data, NULL, NULL,
+                        &r_ip, &r_port, NULL, NULL);
+    infof(data, "connect to %s port %u failed: %s",
+          r_ip, r_port, curl_easy_strerror(result));
+  }
+#endif
+  return result;
+}
+
+static void cf_quiche_close(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+  struct cf_quiche_ctx *ctx = cf->ctx;
+
+  (void)data;
+  if(ctx) {
+    if(ctx->qconn) {
+      (void)quiche_conn_close(ctx->qconn, TRUE, 0, NULL, 0);
+      /* flushing the egress is not a failsafe way to deliver all the
+         outstanding packets, but we also don't want to get stuck here... */
+      (void)cf_flush_egress(cf, data);
+    }
+    cf_quiche_ctx_clear(ctx);
+  }
+}
+
+static void cf_quiche_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+  struct cf_quiche_ctx *ctx = cf->ctx;
+
+  (void)data;
+  cf_quiche_ctx_clear(ctx);
+  free(ctx);
+  cf->ctx = NULL;
+}
+
+static CURLcode cf_quiche_query(struct Curl_cfilter *cf,
+                                struct Curl_easy *data,
+                                int query, int *pres1, void *pres2)
+{
+  struct cf_quiche_ctx *ctx = cf->ctx;
+
+  switch(query) {
+  case CF_QUERY_MAX_CONCURRENT: {
+    uint64_t max_streams = CONN_INUSE(cf->conn);
+    if(!ctx->goaway) {
+      max_streams += quiche_conn_peer_streams_left_bidi(ctx->qconn);
+    }
+    *pres1 = (max_streams > INT_MAX)? INT_MAX : (int)max_streams;
+    DEBUGF(LOG_CF(data, cf, "query: MAX_CONCURRENT -> %d", *pres1));
+    return CURLE_OK;
+  }
+  case CF_QUERY_CONNECT_REPLY_MS:
+    if(ctx->got_first_byte) {
+      timediff_t ms = Curl_timediff(ctx->first_byte_at, ctx->started_at);
+      *pres1 = (ms < INT_MAX)? (int)ms : INT_MAX;
+    }
+    else
+      *pres1 = -1;
+    return CURLE_OK;
+  case CF_QUERY_TIMER_CONNECT: {
+    struct curltime *when = pres2;
+    if(ctx->got_first_byte)
+      *when = ctx->first_byte_at;
+    return CURLE_OK;
+  }
+  case CF_QUERY_TIMER_APPCONNECT: {
+    struct curltime *when = pres2;
+    if(cf->connected)
+      *when = ctx->handshake_at;
+    return CURLE_OK;
+  }
+  default:
+    break;
+  }
+  return cf->next?
+    cf->next->cft->query(cf->next, data, query, pres1, pres2) :
+    CURLE_UNKNOWN_OPTION;
+}
+
+static bool cf_quiche_conn_is_alive(struct Curl_cfilter *cf,
+                                    struct Curl_easy *data,
+                                    bool *input_pending)
+{
+  bool alive = TRUE;
+
+  *input_pending = FALSE;
+  if(!cf->next || !cf->next->cft->is_alive(cf->next, data, input_pending))
+    return FALSE;
+
+  if(*input_pending) {
+    /* This happens before we've sent off a request and the connection is
+       not in use by any other transfer, there shouldn't be any data here,
+       only "protocol frames" */
+    *input_pending = FALSE;
+    Curl_attach_connection(data, cf->conn);
+    if(cf_process_ingress(cf, data))
+      alive = FALSE;
+    else {
+      alive = TRUE;
+    }
+    Curl_detach_connection(data);
+  }
+
+  return alive;
+}
+
+struct Curl_cftype Curl_cft_http3 = {
+  "HTTP/3",
+  CF_TYPE_IP_CONNECT | CF_TYPE_SSL | CF_TYPE_MULTIPLEX,
+  0,
+  cf_quiche_destroy,
+  cf_quiche_connect,
+  cf_quiche_close,
+  Curl_cf_def_get_host,
+  cf_quiche_get_select_socks,
+  cf_quiche_data_pending,
+  cf_quiche_send,
+  cf_quiche_recv,
+  cf_quiche_data_event,
+  cf_quiche_conn_is_alive,
+  Curl_cf_def_conn_keep_alive,
+  cf_quiche_query,
+};
+
+CURLcode Curl_cf_quiche_create(struct Curl_cfilter **pcf,
+                               struct Curl_easy *data,
+                               struct connectdata *conn,
+                               const struct Curl_addrinfo *ai)
+{
+  struct cf_quiche_ctx *ctx = NULL;
+  struct Curl_cfilter *cf = NULL, *udp_cf = NULL;
+  CURLcode result;
+
+  (void)data;
+  (void)conn;
+  ctx = calloc(sizeof(*ctx), 1);
+  if(!ctx) {
+    result = CURLE_OUT_OF_MEMORY;
+    goto out;
+  }
+
+  result = Curl_cf_create(&cf, &Curl_cft_http3, ctx);
+  if(result)
+    goto out;
+
+  result = Curl_cf_udp_create(&udp_cf, data, conn, ai, TRNSPRT_QUIC);
+  if(result)
+    goto out;
+
+  udp_cf->conn = cf->conn;
+  udp_cf->sockindex = cf->sockindex;
+  cf->next = udp_cf;
+
+out:
+  *pcf = (!result)? cf : NULL;
+  if(result) {
+    if(udp_cf)
+      Curl_conn_cf_discard(udp_cf, data);
+    Curl_safefree(cf);
+    Curl_safefree(ctx);
+  }
+
+  return result;
+}
+
+bool Curl_conn_is_quiche(const struct Curl_easy *data,
+                         const struct connectdata *conn,
+                         int sockindex)
+{
+  struct Curl_cfilter *cf = conn? conn->cfilter[sockindex] : NULL;
+
+  (void)data;
+  for(; cf; cf = cf->next) {
+    if(cf->cft == &Curl_cft_http3)
+      return TRUE;
+    if(cf->cft->flags & CF_TYPE_IP_CONNECT)
+      return FALSE;
+  }
+  return FALSE;
+}
+
+#endif
Index: trunk/src/libs/curl-8.0.1/lib/vquic/curl_quiche.h
===================================================================
--- trunk/src/libs/curl-8.0.1/lib/vquic/curl_quiche.h	(revision 99459)
+++ trunk/src/libs/curl-8.0.1/lib/vquic/curl_quiche.h	(revision 99459)
@@ -0,0 +1,50 @@
+#ifndef HEADER_CURL_VQUIC_CURL_QUICHE_H
+#define HEADER_CURL_VQUIC_CURL_QUICHE_H
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+
+#ifdef USE_QUICHE
+
+#include <quiche.h>
+#include <openssl/ssl.h>
+
+struct Curl_cfilter;
+struct Curl_easy;
+
+void Curl_quiche_ver(char *p, size_t len);
+
+CURLcode Curl_cf_quiche_create(struct Curl_cfilter **pcf,
+                               struct Curl_easy *data,
+                               struct connectdata *conn,
+                               const struct Curl_addrinfo *ai);
+
+bool Curl_conn_is_quiche(const struct Curl_easy *data,
+                         const struct connectdata *conn,
+                         int sockindex);
+
+#endif
+
+#endif /* HEADER_CURL_VQUIC_CURL_QUICHE_H */
Index: trunk/src/libs/curl-8.0.1/lib/vquic/vquic_int.h
===================================================================
--- trunk/src/libs/curl-8.0.1/lib/vquic/vquic_int.h	(revision 99459)
+++ trunk/src/libs/curl-8.0.1/lib/vquic/vquic_int.h	(revision 99459)
@@ -0,0 +1,72 @@
+#ifndef HEADER_CURL_VQUIC_QUIC_INT_H
+#define HEADER_CURL_VQUIC_QUIC_INT_H
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+
+#ifdef ENABLE_QUIC
+
+struct vquic_blocked_pkt {
+  const uint8_t *pkt;
+  size_t pktlen;
+  size_t gsolen;
+};
+
+struct cf_quic_ctx {
+  curl_socket_t sockfd;
+  struct sockaddr_storage local_addr;
+  socklen_t local_addrlen;
+  struct vquic_blocked_pkt blocked_pkt[2];
+  uint8_t *pktbuf;
+  /* the number of entries in blocked_pkt */
+  size_t num_blocked_pkt;
+  size_t num_blocked_pkt_sent;
+  /* the packets blocked by sendmsg (EAGAIN or EWOULDBLOCK) */
+  size_t pktbuflen;
+  /* the number of processed entries in blocked_pkt */
+  bool no_gso;
+};
+
+CURLcode vquic_ctx_init(struct cf_quic_ctx *qctx, size_t pktbuflen);
+void vquic_ctx_free(struct cf_quic_ctx *qctx);
+
+CURLcode vquic_send_packet(struct Curl_cfilter *cf,
+                           struct Curl_easy *data,
+                           struct cf_quic_ctx *qctx,
+                           const uint8_t *pkt, size_t pktlen, size_t gsolen,
+                           size_t *psent);
+
+void vquic_push_blocked_pkt(struct Curl_cfilter *cf,
+                            struct cf_quic_ctx *qctx,
+                            const uint8_t *pkt, size_t pktlen, size_t gsolen);
+
+CURLcode vquic_send_blocked_pkt(struct Curl_cfilter *cf,
+                                struct Curl_easy *data,
+                                struct cf_quic_ctx *qctx);
+
+
+#endif /* !ENABLE_QUIC */
+
+#endif /* HEADER_CURL_VQUIC_QUIC_INT_H */
