From 11ba4361aaecf2f1f82ef841146c4c90173d2aca Mon Sep 17 00:00:00 2001
From: Curl Upstream <curl-library@lists.haxx.se>
Date: Mon, 20 Feb 2023 08:24:52 +0100
Subject: curl 2023-02-20 (046209e5)

Code extracted from:

    https://github.com/curl/curl.git

at commit 046209e561b7e9b5aab1aef7daebf29ee6e6e8c7 (curl-7_88_1).
---
 CMake/CMakeConfigurableFile.in     |    2 +-
 CMake/CurlSymbolHiding.cmake       |    2 +-
 CMake/CurlTests.c                  |    2 +-
 CMake/FindBearSSL.cmake            |    2 +-
 CMake/FindBrotli.cmake             |    6 +-
 CMake/FindCARES.cmake              |    2 +-
 CMake/FindGSS.cmake                |    6 +-
 CMake/FindLibPSL.cmake             |    2 +-
 CMake/FindLibSSH2.cmake            |    2 +-
 CMake/FindMSH3.cmake               |    2 +-
 CMake/FindMbedTLS.cmake            |    2 +-
 CMake/FindNGHTTP2.cmake            |    2 +-
 CMake/FindNGHTTP3.cmake            |    2 +-
 CMake/FindNGTCP2.cmake             |    2 +-
 CMake/FindNSS.cmake                |    2 +-
 CMake/FindQUICHE.cmake             |    2 +-
 CMake/FindWolfSSL.cmake            |    2 +-
 CMake/FindZstd.cmake               |    2 +-
 CMake/Macros.cmake                 |    2 +-
 CMake/OtherTests.cmake             |    2 +-
 CMake/Platforms/WindowsCache.cmake |    2 +-
 CMake/Utilities.cmake              |    2 +-
 CMake/cmake_uninstall.cmake.in     |    2 +-
 CMake/curl-config.cmake.in         |    2 +-
 CMakeLists.txt                     |   36 +-
 COPYING                            |    2 +-
 include/curl/curl.h                |   16 +-
 include/curl/curlver.h             |   12 +-
 include/curl/easy.h                |    2 +-
 include/curl/header.h              |    2 +-
 include/curl/mprintf.h             |    2 +-
 include/curl/multi.h               |    2 +-
 include/curl/options.h             |    2 +-
 include/curl/stdcheaders.h         |    2 +-
 include/curl/system.h              |   20 +-
 include/curl/typecheck-gcc.h       |    8 +-
 include/curl/urlapi.h              |    4 +-
 include/curl/websockets.h          |    3 +-
 lib/CMakeLists.txt                 |    5 +-
 lib/Makefile.inc                   |   24 +-
 lib/altsvc.c                       |    2 +-
 lib/altsvc.h                       |    2 +-
 lib/amigaos.c                      |    2 +-
 lib/amigaos.h                      |    2 +-
 lib/arpa_telnet.h                  |    2 +-
 lib/asyn-ares.c                    |    2 +-
 lib/asyn-thread.c                  |    2 +-
 lib/asyn.h                         |    2 +-
 lib/base64.c                       |    2 +-
 lib/bufref.c                       |    2 +-
 lib/bufref.h                       |    2 +-
 lib/c-hyper.c                      |   24 +-
 lib/c-hyper.h                      |    2 +-
 lib/cf-http.c                      |  518 ++++++++
 lib/cf-http.h                      |   58 +
 lib/cf-socket.c                    | 1908 +++++++++++++++++++++++++++
 lib/cf-socket.h                    |  192 +++
 lib/cfilters.c                     |  511 +++++---
 lib/cfilters.h                     |  331 ++++-
 lib/conncache.c                    |    4 +-
 lib/conncache.h                    |    4 +-
 lib/connect.c                      | 2428 ++++++++++++++--------------------
 lib/connect.h                      |  137 +-
 lib/content_encoding.c             |   46 +-
 lib/content_encoding.h             |    5 +-
 lib/cookie.c                       |   24 +-
 lib/cookie.h                       |    2 +-
 lib/curl_addrinfo.c                |    2 +-
 lib/curl_addrinfo.h                |    2 +-
 lib/curl_base64.h                  |    2 +-
 lib/curl_config.h.cmake            |    2 +-
 lib/curl_ctype.h                   |    4 +-
 lib/curl_des.c                     |    2 +-
 lib/curl_des.h                     |    2 +-
 lib/curl_endian.c                  |    2 +-
 lib/curl_endian.h                  |    2 +-
 lib/curl_fnmatch.c                 |    2 +-
 lib/curl_fnmatch.h                 |    2 +-
 lib/curl_get_line.c                |    2 +-
 lib/curl_get_line.h                |    2 +-
 lib/curl_gethostname.c             |    2 +-
 lib/curl_gethostname.h             |    2 +-
 lib/curl_gssapi.c                  |    2 +-
 lib/curl_gssapi.h                  |    2 +-
 lib/curl_hmac.h                    |    2 +-
 lib/curl_krb5.h                    |    2 +-
 lib/curl_ldap.h                    |    2 +-
 lib/curl_log.c                     |  223 ++++
 lib/curl_log.h                     |  138 ++
 lib/curl_md4.h                     |    2 +-
 lib/curl_md5.h                     |    2 +-
 lib/curl_memory.h                  |    2 +-
 lib/curl_memrchr.c                 |    2 +-
 lib/curl_memrchr.h                 |    2 +-
 lib/curl_multibyte.c               |    2 +-
 lib/curl_multibyte.h               |    2 +-
 lib/curl_ntlm_core.c               |   15 +-
 lib/curl_ntlm_core.h               |    8 +-
 lib/curl_ntlm_wb.c                 |    2 +-
 lib/curl_ntlm_wb.h                 |    2 +-
 lib/curl_path.c                    |    2 +-
 lib/curl_path.h                    |    2 +-
 lib/curl_printf.h                  |    2 +-
 lib/curl_range.c                   |    2 +-
 lib/curl_range.h                   |    2 +-
 lib/curl_rtmp.c                    |    4 +-
 lib/curl_rtmp.h                    |    2 +-
 lib/curl_sasl.c                    |    5 +-
 lib/curl_sasl.h                    |    8 +-
 lib/curl_setup.h                   |   27 +-
 lib/curl_setup_once.h              |    2 +-
 lib/curl_sha256.h                  |    4 +-
 lib/curl_sspi.c                    |    2 +-
 lib/curl_sspi.h                    |    2 +-
 lib/curl_threads.c                 |    2 +-
 lib/curl_threads.h                 |    2 +-
 lib/curlx.h                        |    2 +-
 lib/dict.c                         |   88 +-
 lib/dict.h                         |    2 +-
 lib/doh.c                          |    4 +-
 lib/doh.h                          |    2 +-
 lib/dynbuf.c                       |    2 +-
 lib/dynbuf.h                       |    2 +-
 lib/easy.c                         |   49 +-
 lib/easy_lock.h                    |    2 +-
 lib/easygetopt.c                   |    2 +-
 lib/easyif.h                       |    2 +-
 lib/easyoptions.c                  |    6 +-
 lib/easyoptions.h                  |    2 +-
 lib/escape.c                       |   62 +-
 lib/escape.h                       |    2 +-
 lib/file.c                         |    2 +-
 lib/file.h                         |    2 +-
 lib/fileinfo.c                     |    2 +-
 lib/fileinfo.h                     |    2 +-
 lib/fopen.c                        |    3 +-
 lib/fopen.h                        |    2 +-
 lib/formdata.c                     |    2 +-
 lib/formdata.h                     |    2 +-
 lib/ftp.c                          |   49 +-
 lib/ftp.h                          |   51 +-
 lib/ftplistparser.c                |    2 +-
 lib/ftplistparser.h                |    2 +-
 lib/functypes.h                    |    2 +-
 lib/getenv.c                       |    2 +-
 lib/getinfo.c                      |    2 +-
 lib/getinfo.h                      |    2 +-
 lib/gopher.c                       |    2 +-
 lib/gopher.h                       |    2 +-
 lib/h2h3.c                         |    9 +-
 lib/h2h3.h                         |    3 +-
 lib/hash.c                         |    2 +-
 lib/hash.h                         |    2 +-
 lib/headers.c                      |    2 +-
 lib/headers.h                      |    2 +-
 lib/hmac.c                         |    2 +-
 lib/hostasyn.c                     |    2 +-
 lib/hostip.c                       |    2 +-
 lib/hostip.h                       |    2 +-
 lib/hostip4.c                      |    2 +-
 lib/hostip6.c                      |    2 +-
 lib/hostsyn.c                      |    2 +-
 lib/hsts.c                         |   30 +-
 lib/hsts.h                         |    4 +-
 lib/http.c                         |  194 +--
 lib/http.h                         |  111 +-
 lib/http2.c                        | 2175 +++++++++++++++++--------------
 lib/http2.h                        |   61 +-
 lib/http_aws_sigv4.c               |    6 +-
 lib/http_aws_sigv4.h               |    2 +-
 lib/http_chunks.c                  |    2 +-
 lib/http_chunks.h                  |    2 +-
 lib/http_digest.c                  |    2 +-
 lib/http_digest.h                  |    2 +-
 lib/http_negotiate.c               |    2 +-
 lib/http_negotiate.h               |    2 +-
 lib/http_ntlm.c                    |    2 +-
 lib/http_ntlm.h                    |    2 +-
 lib/http_proxy.c                   |  338 +++--
 lib/http_proxy.h                   |   19 +-
 lib/idn.c                          |   32 +-
 lib/idn.h                          |   10 +-
 lib/if2ip.c                        |    2 +-
 lib/if2ip.h                        |    2 +-
 lib/imap.c                         |   18 +-
 lib/imap.h                         |   24 +-
 lib/inet_ntop.h                    |    2 +-
 lib/inet_pton.c                    |    2 +-
 lib/inet_pton.h                    |    2 +-
 lib/krb5.c                         |   33 +-
 lib/ldap.c                         |    2 +-
 lib/libcurl.rc                     |    2 +-
 lib/llist.c                        |    2 +-
 lib/llist.h                        |    2 +-
 lib/md4.c                          |   12 +-
 lib/md5.c                          |    2 +-
 lib/memdebug.c                     |    2 +-
 lib/memdebug.h                     |    2 +-
 lib/mime.c                         |    2 +-
 lib/mime.h                         |    2 +-
 lib/mprintf.c                      |    2 +-
 lib/mqtt.c                         |    4 +-
 lib/mqtt.h                         |    2 +-
 lib/multi.c                        |   52 +-
 lib/multihandle.h                  |   15 +-
 lib/multiif.h                      |    2 +-
 lib/netrc.c                        |    2 +-
 lib/netrc.h                        |    2 +-
 lib/nonblock.c                     |    2 +-
 lib/nonblock.h                     |    2 +-
 lib/noproxy.c                      |   15 +-
 lib/noproxy.h                      |    5 +-
 lib/openldap.c                     |    4 +-
 lib/parsedate.c                    |    2 +-
 lib/parsedate.h                    |    2 +-
 lib/pingpong.c                     |    2 +-
 lib/pingpong.h                     |    2 +-
 lib/pop3.c                         |   14 +-
 lib/pop3.h                         |   12 +-
 lib/progress.c                     |   38 +-
 lib/progress.h                     |    9 +-
 lib/psl.c                          |    2 +-
 lib/psl.h                          |    2 +-
 lib/quic.h                         |   68 -
 lib/rand.c                         |    2 +-
 lib/rand.h                         |    2 +-
 lib/rename.c                       |    2 +-
 lib/rename.h                       |    2 +-
 lib/rtsp.c                         |   37 +-
 lib/rtsp.h                         |    2 +-
 lib/select.c                       |    2 +-
 lib/select.h                       |    2 +-
 lib/sendf.c                        |  359 +----
 lib/sendf.h                        |   47 +-
 lib/setopt.c                       |  245 ++--
 lib/setopt.h                       |    2 +-
 lib/setup-os400.h                  |    4 +-
 lib/setup-vms.h                    |    2 +-
 lib/setup-win32.h                  |    2 +-
 lib/sha256.c                       |    4 +-
 lib/share.c                        |   34 +-
 lib/share.h                        |    8 +-
 lib/sigpipe.h                      |    2 +-
 lib/slist.c                        |    2 +-
 lib/slist.h                        |    2 +-
 lib/smb.c                          |   10 +-
 lib/smb.h                          |    4 +-
 lib/smtp.c                         |   16 +-
 lib/smtp.h                         |   20 +-
 lib/sockaddr.h                     |    2 +-
 lib/socketpair.c                   |   80 +-
 lib/socketpair.h                   |    2 +-
 lib/socks.c                        |  322 ++---
 lib/socks.h                        |   13 +-
 lib/socks_gssapi.c                 |   48 +-
 lib/socks_sspi.c                   |   35 +-
 lib/speedcheck.c                   |    2 +-
 lib/speedcheck.h                   |    2 +-
 lib/splay.c                        |    2 +-
 lib/splay.h                        |    2 +-
 lib/strcase.c                      |    2 +-
 lib/strcase.h                      |    2 +-
 lib/strdup.c                       |    4 +-
 lib/strdup.h                       |    4 +-
 lib/strerror.c                     |    5 +-
 lib/strerror.h                     |    2 +-
 lib/strtok.c                       |    2 +-
 lib/strtok.h                       |    2 +-
 lib/strtoofft.c                    |    2 +-
 lib/strtoofft.h                    |    2 +-
 lib/system_win32.c                 |    2 +-
 lib/system_win32.h                 |    2 +-
 lib/telnet.c                       |    2 +-
 lib/telnet.h                       |    2 +-
 lib/tftp.c                         |   11 +-
 lib/tftp.h                         |    2 +-
 lib/timediff.c                     |    2 +-
 lib/timediff.h                     |    2 +-
 lib/timeval.c                      |    2 +-
 lib/timeval.h                      |    2 +-
 lib/transfer.c                     |  114 +-
 lib/transfer.h                     |    2 +-
 lib/url.c                          |  262 ++--
 lib/url.h                          |   18 +-
 lib/urlapi-int.h                   |    2 +-
 lib/urlapi.c                       |  127 +-
 lib/urldata.h                      |  189 ++-
 lib/vauth/cleartext.c              |    5 +-
 lib/vauth/cram.c                   |    2 +-
 lib/vauth/digest.c                 |    2 +-
 lib/vauth/digest.h                 |    2 +-
 lib/vauth/digest_sspi.c            |    4 +-
 lib/vauth/gsasl.c                  |    2 +-
 lib/vauth/krb5_gssapi.c            |    4 +-
 lib/vauth/krb5_sspi.c              |    2 +-
 lib/vauth/ntlm.c                   |    2 +-
 lib/vauth/ntlm.h                   |    2 +-
 lib/vauth/ntlm_sspi.c              |    2 +-
 lib/vauth/oauth2.c                 |    5 +-
 lib/vauth/spnego_gssapi.c          |    2 +-
 lib/vauth/spnego_sspi.c            |    2 +-
 lib/vauth/vauth.c                  |    2 +-
 lib/vauth/vauth.h                  |    2 +-
 lib/version.c                      |    8 +-
 lib/version_win32.c                |    2 +-
 lib/version_win32.h                |    2 +-
 lib/vquic/curl_msh3.c              |  841 ++++++++++++
 lib/vquic/curl_msh3.h              |   46 +
 lib/vquic/curl_ngtcp2.c            | 2515 ++++++++++++++++++++++++++++++++++++
 lib/vquic/curl_ngtcp2.h            |   61 +
 lib/vquic/curl_quiche.c            | 1433 ++++++++++++++++++++
 lib/vquic/curl_quiche.h            |   50 +
 lib/vquic/msh3.c                   |  527 --------
 lib/vquic/msh3.h                   |   40 -
 lib/vquic/ngtcp2.c                 | 2266 --------------------------------
 lib/vquic/ngtcp2.h                 |   93 --
 lib/vquic/quiche.c                 |  892 -------------
 lib/vquic/quiche.h                 |   58 -
 lib/vquic/vquic.c                  |  316 ++++-
 lib/vquic/vquic.h                  |   32 +-
 lib/vquic/vquic_int.h              |   72 ++
 lib/vssh/libssh.c                  |    2 +-
 lib/vssh/libssh2.c                 |   19 +-
 lib/vssh/ssh.h                     |    2 +-
 lib/vssh/wolfssh.c                 |    2 +-
 lib/vtls/bearssl.c                 |   59 +-
 lib/vtls/bearssl.h                 |    2 +-
 lib/vtls/gskit.c                   |   14 +-
 lib/vtls/gskit.h                   |    2 +-
 lib/vtls/gtls.c                    |   85 +-
 lib/vtls/gtls.h                    |    2 +-
 lib/vtls/hostcheck.c               |    2 +-
 lib/vtls/hostcheck.h               |    2 +-
 lib/vtls/keylog.c                  |    2 +-
 lib/vtls/keylog.h                  |    2 +-
 lib/vtls/mbedtls.c                 |   64 +-
 lib/vtls/mbedtls.h                 |    4 +-
 lib/vtls/mbedtls_threadlock.c      |    4 +-
 lib/vtls/mbedtls_threadlock.h      |    4 +-
 lib/vtls/nss.c                     |   70 +-
 lib/vtls/nssg.h                    |    2 +-
 lib/vtls/openssl.c                 |  795 +++++-------
 lib/vtls/openssl.h                 |   11 +-
 lib/vtls/rustls.c                  |  105 +-
 lib/vtls/rustls.h                  |    2 +-
 lib/vtls/schannel.c                |   67 +-
 lib/vtls/schannel.h                |    8 +-
 lib/vtls/schannel_verify.c         |    6 +-
 lib/vtls/sectransp.c               |  166 ++-
 lib/vtls/sectransp.h               |    4 +-
 lib/vtls/vtls.c                    |  478 +++++--
 lib/vtls/vtls.h                    |   59 +-
 lib/vtls/vtls_int.h                |   18 +-
 lib/vtls/wolfssl.c                 |   77 +-
 lib/vtls/wolfssl.h                 |    2 +-
 lib/vtls/x509asn1.c                |   25 +-
 lib/vtls/x509asn1.h                |    2 +-
 lib/warnless.c                     |    2 +-
 lib/warnless.h                     |    2 +-
 lib/wildcard.c                     |    2 +-
 lib/wildcard.h                     |    2 +-
 lib/ws.c                           |  142 +-
 lib/ws.h                           |   18 +-
 363 files changed, 14502 insertions(+), 9925 deletions(-)
 create mode 100644 lib/cf-http.c
 create mode 100644 lib/cf-http.h
 create mode 100644 lib/cf-socket.c
 create mode 100644 lib/cf-socket.h
 create mode 100644 lib/curl_log.c
 create mode 100644 lib/curl_log.h
 delete mode 100644 lib/quic.h
 create mode 100644 lib/vquic/curl_msh3.c
 create mode 100644 lib/vquic/curl_msh3.h
 create mode 100644 lib/vquic/curl_ngtcp2.c
 create mode 100644 lib/vquic/curl_ngtcp2.h
 create mode 100644 lib/vquic/curl_quiche.c
 create mode 100644 lib/vquic/curl_quiche.h
 delete mode 100644 lib/vquic/msh3.c
 delete mode 100644 lib/vquic/msh3.h
 delete mode 100644 lib/vquic/ngtcp2.c
 delete mode 100644 lib/vquic/ngtcp2.h
 delete mode 100644 lib/vquic/quiche.c
 delete mode 100644 lib/vquic/quiche.h
 create mode 100644 lib/vquic/vquic_int.h

diff --git a/CMake/CMakeConfigurableFile.in b/CMake/CMakeConfigurableFile.in
index b93e753..a3d2bc4 100644
--- a/CMake/CMakeConfigurableFile.in
+++ b/CMake/CMakeConfigurableFile.in
@@ -5,7 +5,7 @@
 #                            | (__| |_| |  _ <| |___
 #                             \___|\___/|_| \_\_____|
 #
-# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+# 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
diff --git a/CMake/CurlSymbolHiding.cmake b/CMake/CurlSymbolHiding.cmake
index 75215a1..142e919 100644
--- a/CMake/CurlSymbolHiding.cmake
+++ b/CMake/CurlSymbolHiding.cmake
@@ -5,7 +5,7 @@
 #                            | (__| |_| |  _ <| |___
 #                             \___|\___/|_| \_\_____|
 #
-# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+# 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
diff --git a/CMake/CurlTests.c b/CMake/CurlTests.c
index 6a9fdea..3dbba3c 100644
--- a/CMake/CurlTests.c
+++ b/CMake/CurlTests.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/CMake/FindBearSSL.cmake b/CMake/FindBearSSL.cmake
index 88d5e87..56a064e 100644
--- a/CMake/FindBearSSL.cmake
+++ b/CMake/FindBearSSL.cmake
@@ -5,7 +5,7 @@
 #                            | (__| |_| |  _ <| |___
 #                             \___|\___/|_| \_\_____|
 #
-# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+# 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
diff --git a/CMake/FindBrotli.cmake b/CMake/FindBrotli.cmake
index 833e181..11ab7f8 100644
--- a/CMake/FindBrotli.cmake
+++ b/CMake/FindBrotli.cmake
@@ -5,7 +5,7 @@
 #                            | (__| |_| |  _ <| |___
 #                             \___|\___/|_| \_\_____|
 #
-# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+# 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
@@ -28,7 +28,7 @@ find_path(BROTLI_INCLUDE_DIR "brotli/decode.h")
 find_library(BROTLICOMMON_LIBRARY NAMES brotlicommon)
 find_library(BROTLIDEC_LIBRARY NAMES brotlidec)
 
-find_package_handle_standard_args(BROTLI
+find_package_handle_standard_args(Brotli
     FOUND_VAR
       BROTLI_FOUND
     REQUIRED_VARS
@@ -36,7 +36,7 @@ find_package_handle_standard_args(BROTLI
       BROTLICOMMON_LIBRARY
       BROTLI_INCLUDE_DIR
     FAIL_MESSAGE
-      "Could NOT find BROTLI"
+      "Could NOT find Brotli"
 )
 
 set(BROTLI_INCLUDE_DIRS ${BROTLI_INCLUDE_DIR})
diff --git a/CMake/FindCARES.cmake b/CMake/FindCARES.cmake
index 99cf31d..fa75891 100644
--- a/CMake/FindCARES.cmake
+++ b/CMake/FindCARES.cmake
@@ -5,7 +5,7 @@
 #                            | (__| |_| |  _ <| |___
 #                             \___|\___/|_| \_\_____|
 #
-# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+# 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
diff --git a/CMake/FindGSS.cmake b/CMake/FindGSS.cmake
index ec2bd57..b244e61 100644
--- a/CMake/FindGSS.cmake
+++ b/CMake/FindGSS.cmake
@@ -5,7 +5,7 @@
 #                            | (__| |_| |  _ <| |___
 #                             \___|\___/|_| \_\_____|
 #
-# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+# 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
@@ -181,14 +181,14 @@ if(NOT _GSS_FOUND) #not found by pkg-config. Let's take more traditional approac
         set(GSS_FLAVOUR "MIT")
       else()
         # prevent compiling the header - just check if we can include it
-        set(CMAKE_REQUIRED_DEFINITIONS "${CMAKE_REQUIRED_DEFINITIONS} -D__ROKEN_H__")
+        list(APPEND CMAKE_REQUIRED_DEFINITIONS -D__ROKEN_H__)
         check_include_file( "roken.h" _GSS_HAVE_ROKEN_H)
 
         check_include_file( "heimdal/roken.h" _GSS_HAVE_HEIMDAL_ROKEN_H)
         if(_GSS_HAVE_ROKEN_H OR _GSS_HAVE_HEIMDAL_ROKEN_H)
           set(GSS_FLAVOUR "Heimdal")
         endif()
-        set(CMAKE_REQUIRED_DEFINITIONS "")
+        list(REMOVE_ITEM CMAKE_REQUIRED_DEFINITIONS -D__ROKEN_H__)
       endif()
     else()
       # I'm not convinced if this is the right way but this is what autotools do at the moment
diff --git a/CMake/FindLibPSL.cmake b/CMake/FindLibPSL.cmake
index 66abdd7..e3bd68d 100644
--- a/CMake/FindLibPSL.cmake
+++ b/CMake/FindLibPSL.cmake
@@ -5,7 +5,7 @@
 #                            | (__| |_| |  _ <| |___
 #                             \___|\___/|_| \_\_____|
 #
-# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+# 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
diff --git a/CMake/FindLibSSH2.cmake b/CMake/FindLibSSH2.cmake
index 0ec7f7e..a0c251a 100644
--- a/CMake/FindLibSSH2.cmake
+++ b/CMake/FindLibSSH2.cmake
@@ -5,7 +5,7 @@
 #                            | (__| |_| |  _ <| |___
 #                             \___|\___/|_| \_\_____|
 #
-# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+# 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
diff --git a/CMake/FindMSH3.cmake b/CMake/FindMSH3.cmake
index 96477e2..7d9c6b6 100644
--- a/CMake/FindMSH3.cmake
+++ b/CMake/FindMSH3.cmake
@@ -5,7 +5,7 @@
 #                            | (__| |_| |  _ <| |___
 #                             \___|\___/|_| \_\_____|
 #
-# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+# 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
diff --git a/CMake/FindMbedTLS.cmake b/CMake/FindMbedTLS.cmake
index fcd6717..814bd97 100644
--- a/CMake/FindMbedTLS.cmake
+++ b/CMake/FindMbedTLS.cmake
@@ -5,7 +5,7 @@
 #                            | (__| |_| |  _ <| |___
 #                             \___|\___/|_| \_\_____|
 #
-# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+# 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
diff --git a/CMake/FindNGHTTP2.cmake b/CMake/FindNGHTTP2.cmake
index 6d70c4a..3957646 100644
--- a/CMake/FindNGHTTP2.cmake
+++ b/CMake/FindNGHTTP2.cmake
@@ -5,7 +5,7 @@
 #                            | (__| |_| |  _ <| |___
 #                             \___|\___/|_| \_\_____|
 #
-# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+# 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
diff --git a/CMake/FindNGHTTP3.cmake b/CMake/FindNGHTTP3.cmake
index 8d8ebc1..9b13e6c 100644
--- a/CMake/FindNGHTTP3.cmake
+++ b/CMake/FindNGHTTP3.cmake
@@ -5,7 +5,7 @@
 #                            | (__| |_| |  _ <| |___
 #                             \___|\___/|_| \_\_____|
 #
-# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+# 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
diff --git a/CMake/FindNGTCP2.cmake b/CMake/FindNGTCP2.cmake
index 61e54c2..9f4e9f2 100644
--- a/CMake/FindNGTCP2.cmake
+++ b/CMake/FindNGTCP2.cmake
@@ -5,7 +5,7 @@
 #                            | (__| |_| |  _ <| |___
 #                             \___|\___/|_| \_\_____|
 #
-# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+# 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
diff --git a/CMake/FindNSS.cmake b/CMake/FindNSS.cmake
index 6742dda..ccddf42 100644
--- a/CMake/FindNSS.cmake
+++ b/CMake/FindNSS.cmake
@@ -5,7 +5,7 @@
 #                            | (__| |_| |  _ <| |___
 #                             \___|\___/|_| \_\_____|
 #
-# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+# 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
diff --git a/CMake/FindQUICHE.cmake b/CMake/FindQUICHE.cmake
index fc47027..0488463 100644
--- a/CMake/FindQUICHE.cmake
+++ b/CMake/FindQUICHE.cmake
@@ -5,7 +5,7 @@
 #                            | (__| |_| |  _ <| |___
 #                             \___|\___/|_| \_\_____|
 #
-# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+# 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
diff --git a/CMake/FindWolfSSL.cmake b/CMake/FindWolfSSL.cmake
index 986f01e..d67c0eb 100644
--- a/CMake/FindWolfSSL.cmake
+++ b/CMake/FindWolfSSL.cmake
@@ -5,7 +5,7 @@
 #                            | (__| |_| |  _ <| |___
 #                             \___|\___/|_| \_\_____|
 #
-# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+# 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
diff --git a/CMake/FindZstd.cmake b/CMake/FindZstd.cmake
index 2d65404..973e6ad 100644
--- a/CMake/FindZstd.cmake
+++ b/CMake/FindZstd.cmake
@@ -5,7 +5,7 @@
 #                            | (__| |_| |  _ <| |___
 #                             \___|\___/|_| \_\_____|
 #
-# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+# 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
diff --git a/CMake/Macros.cmake b/CMake/Macros.cmake
index 4d7380e..e12bf30 100644
--- a/CMake/Macros.cmake
+++ b/CMake/Macros.cmake
@@ -5,7 +5,7 @@
 #                            | (__| |_| |  _ <| |___
 #                             \___|\___/|_| \_\_____|
 #
-# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+# 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
diff --git a/CMake/OtherTests.cmake b/CMake/OtherTests.cmake
index ed8d28a..fa1e458 100644
--- a/CMake/OtherTests.cmake
+++ b/CMake/OtherTests.cmake
@@ -5,7 +5,7 @@
 #                            | (__| |_| |  _ <| |___
 #                             \___|\___/|_| \_\_____|
 #
-# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+# 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
diff --git a/CMake/Platforms/WindowsCache.cmake b/CMake/Platforms/WindowsCache.cmake
index 3cb4ffe..cef31b5 100644
--- a/CMake/Platforms/WindowsCache.cmake
+++ b/CMake/Platforms/WindowsCache.cmake
@@ -5,7 +5,7 @@
 #                            | (__| |_| |  _ <| |___
 #                             \___|\___/|_| \_\_____|
 #
-# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+# 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
diff --git a/CMake/Utilities.cmake b/CMake/Utilities.cmake
index 78bfd6f..9ff38e3 100644
--- a/CMake/Utilities.cmake
+++ b/CMake/Utilities.cmake
@@ -5,7 +5,7 @@
 #                            | (__| |_| |  _ <| |___
 #                             \___|\___/|_| \_\_____|
 #
-# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+# 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
diff --git a/CMake/cmake_uninstall.cmake.in b/CMake/cmake_uninstall.cmake.in
index 55801f5..47aec8d 100644
--- a/CMake/cmake_uninstall.cmake.in
+++ b/CMake/cmake_uninstall.cmake.in
@@ -5,7 +5,7 @@
 #                            | (__| |_| |  _ <| |___
 #                             \___|\___/|_| \_\_____|
 #
-# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+# 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
diff --git a/CMake/curl-config.cmake.in b/CMake/curl-config.cmake.in
index 496a92d..dbe4ed2 100644
--- a/CMake/curl-config.cmake.in
+++ b/CMake/curl-config.cmake.in
@@ -5,7 +5,7 @@
 #                            | (__| |_| |  _ <| |___
 #                             \___|\___/|_| \_\_____|
 #
-# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+# 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
diff --git a/CMakeLists.txt b/CMakeLists.txt
index b435207..b97704b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -5,7 +5,7 @@
 #                            | (__| |_| |  _ <| |___
 #                             \___|\___/|_| \_\_____|
 #
-# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+# 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
@@ -60,7 +60,7 @@
 # to ON or OFF), the symbol detection will be skipped.  If the
 # variable is NOT DEFINED, the symbol detection will be performed.
 
-cmake_minimum_required(VERSION 3.2...3.16 FATAL_ERROR)
+cmake_minimum_required(VERSION 3.7...3.16 FATAL_ERROR)
 
 set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMake;${CMAKE_MODULE_PATH}")
 include(Utilities)
@@ -107,7 +107,7 @@ if(WIN32)
   set(CURL_TARGET_WINDOWS_VERSION "" CACHE STRING "Minimum target Windows version as hex string")
   if(CURL_TARGET_WINDOWS_VERSION)
     add_definitions(-D_WIN32_WINNT=${CURL_TARGET_WINDOWS_VERSION})
-    set(CMAKE_REQUIRED_DEFINITIONS "${CMAKE_REQUIRED_DEFINITIONS} -D_WIN32_WINNT=${CURL_TARGET_WINDOWS_VERSION}")
+    list(APPEND CMAKE_REQUIRED_DEFINITIONS -D_WIN32_WINNT=${CURL_TARGET_WINDOWS_VERSION})
     set(CURL_TEST_DEFINES "${CURL_TEST_DEFINES} -D_WIN32_WINNT=${CURL_TARGET_WINDOWS_VERSION}")
   endif()
   if(ENABLE_UNICODE)
@@ -333,7 +333,7 @@ include(CheckCSourceCompiles)
 
 # On windows preload settings
 if(WIN32)
-  set(CMAKE_REQUIRED_DEFINITIONS "${CMAKE_REQUIRED_DEFINITIONS} -D_WINSOCKAPI_=")
+  list(APPEND CMAKE_REQUIRED_DEFINITIONS -D_WINSOCKAPI_=)
   include(${CMAKE_CURRENT_SOURCE_DIR}/CMake/Platforms/WindowsCache.cmake)
 endif()
 
@@ -358,24 +358,6 @@ if(WIN32)
   check_library_exists_concat("winmm"  getch        HAVE_LIBWINMM)
 endif()
 
-# This check below for use of deprecated symbols is only temporary and is to
-# be removed again after a year's service. Remove after November 25, 2022.
-set(CURL_RECONFIG_REQUIRED 0)
-foreach(_LIB GSSAPI OPENLDAP LIBSSH LIBSSH2 BEARSSL MBEDTLS NSS OPENSSL
-        SCHANNEL SECTRANSP WOLFSSL)
-  if(CMAKE_USE_${_LIB})
-    set(CURL_RECONFIG_REQUIRED 1)
-    message(SEND_ERROR "The option CMAKE_USE_${_LIB} was renamed to CURL_USE_${_LIB}.")
-  endif()
-endforeach()
-if(CMAKE_USE_WINSSL)
-  set(CURL_RECONFIG_REQUIRED 1)
-  message(SEND_ERROR "The option CMAKE_USE_WINSSL was renamed to CURL_USE_SCHANNEL.")
-endif()
-if(CURL_RECONFIG_REQUIRED)
-  message(FATAL_ERROR "Reconfig required")
-endif()
-
 # check SSL libraries
 # TODO support GnuTLS
 option(CURL_ENABLE_SSL "Enable SSL support" ON)
@@ -420,7 +402,6 @@ if(CURL_USE_SCHANNEL)
 endif()
 if(CURL_WINDOWS_SSPI)
   set(USE_WINDOWS_SSPI ON)
-  set(CMAKE_REQUIRED_DEFINITIONS "${CMAKE_REQUIRED_DEFINITIONS} -DSECURITY_WIN32")
 endif()
 
 if(CURL_USE_SECTRANSP)
@@ -467,8 +448,6 @@ if(CURL_USE_OPENSSL)
   if(NOT DEFINED HAVE_BORINGSSL)
     check_symbol_exists(OPENSSL_IS_BORINGSSL "openssl/base.h" HAVE_BORINGSSL)
   endif()
-
-  add_definitions(-DOPENSSL_SUPPRESS_DEPRECATED)
 endif()
 
 if(CURL_USE_MBEDTLS)
@@ -663,7 +642,7 @@ if(NOT CURL_DISABLE_LDAP)
           return 0;
         }"
       )
-      set(CMAKE_REQUIRED_DEFINITIONS "${CMAKE_REQUIRED_DEFINITIONS} -DLDAP_DEPRECATED=1")
+      list(APPEND CMAKE_REQUIRED_DEFINITIONS -DLDAP_DEPRECATED=1)
       list(APPEND CMAKE_REQUIRED_LIBRARIES ${CMAKE_LDAP_LIB})
       if(HAVE_LIBLBER)
         list(APPEND CMAKE_REQUIRED_LIBRARIES ${CMAKE_LBER_LIB})
@@ -1048,6 +1027,7 @@ check_symbol_exists(socket        "${CURL_INCLUDES}" HAVE_SOCKET)
 check_symbol_exists(socketpair    "${CURL_INCLUDES}" HAVE_SOCKETPAIR)
 check_symbol_exists(recv          "${CURL_INCLUDES}" HAVE_RECV)
 check_symbol_exists(send          "${CURL_INCLUDES}" HAVE_SEND)
+check_symbol_exists(sendmsg       "${CURL_INCLUDES}" HAVE_SENDMSG)
 check_symbol_exists(select        "${CURL_INCLUDES}" HAVE_SELECT)
 check_symbol_exists(strdup        "${CURL_INCLUDES}" HAVE_STRDUP)
 check_symbol_exists(strtok_r      "${CURL_INCLUDES}" HAVE_STRTOK_R)
@@ -1092,7 +1072,7 @@ check_symbol_exists(setrlimit      "${CURL_INCLUDES}" HAVE_SETRLIMIT)
 
 if(NOT MSVC OR (MSVC_VERSION GREATER_EQUAL 1900))
   # earlier MSVC compilers had faulty snprintf implementations
-  check_symbol_exists(snprintf       "${CURL_INCLUDES}" HAVE_SNPRINTF)
+  check_symbol_exists(snprintf       "stdio.h" HAVE_SNPRINTF)
 endif()
 check_function_exists(mach_absolute_time HAVE_MACH_ABSOLUTE_TIME)
 check_symbol_exists(inet_ntop      "${CURL_INCLUDES}" HAVE_INET_NTOP)
@@ -1295,7 +1275,7 @@ if(WIN32)
 
   # Check if crypto functions in wincrypt.h are actually available
   if(HAVE_WINCRYPT_H)
-    check_symbol_exists(CryptAcquireContext "${CURL_INCLUDES}" USE_WINCRYPT)
+    check_symbol_exists(CryptAcquireContext "windows.h;wincrypt.h" USE_WINCRYPT)
   endif()
   if(USE_WINCRYPT)
     set(USE_WIN32_CRYPTO ON)
diff --git a/COPYING b/COPYING
index 90f05ad..d1eab3e 100644
--- a/COPYING
+++ b/COPYING
@@ -1,6 +1,6 @@
 COPYRIGHT AND PERMISSION NOTICE
 
-Copyright (c) 1996 - 2022, Daniel Stenberg, <daniel@haxx.se>, and many
+Copyright (c) 1996 - 2023, Daniel Stenberg, <daniel@haxx.se>, and many
 contributors, see the THANKS file.
 
 All rights reserved.
diff --git a/include/curl/curl.h b/include/curl/curl.h
index 139df99..8cc0b6f 100644
--- a/include/curl/curl.h
+++ b/include/curl/curl.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -167,7 +167,7 @@ typedef enum {
   CURLSSLBACKEND_SECURETRANSPORT = 9,
   CURLSSLBACKEND_AXTLS                  CURL_DEPRECATED(7.61.0, "") = 10,
   CURLSSLBACKEND_MBEDTLS = 11,
-  CURLSSLBACKEND_MESALINK = 12,
+  CURLSSLBACKEND_MESALINK               CURL_DEPRECATED(7.82.0, "") = 12,
   CURLSSLBACKEND_BEARSSL = 13,
   CURLSSLBACKEND_RUSTLS = 14
 } curl_sslbackend;
@@ -248,7 +248,7 @@ typedef int (*curl_xferinfo_callback)(void *clientp,
 
 #ifndef CURL_MAX_READ_SIZE
   /* The maximum receive buffer size configurable via CURLOPT_BUFFERSIZE. */
-#define CURL_MAX_READ_SIZE 524288
+#define CURL_MAX_READ_SIZE (10*1024*1024)
 #endif
 
 #ifndef CURL_MAX_WRITE_SIZE
@@ -2259,8 +2259,13 @@ enum {
   CURL_HTTP_VERSION_2TLS, /* use version 2 for HTTPS, version 1.1 for HTTP */
   CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE,  /* please use HTTP 2 without HTTP/1.1
                                            Upgrade */
-  CURL_HTTP_VERSION_3 = 30, /* Makes use of explicit HTTP/3 without fallback.
-                               Use CURLOPT_ALTSVC to enable HTTP/3 upgrade */
+  CURL_HTTP_VERSION_3 = 30, /* Use HTTP/3, fallback to HTTP/2 or HTTP/1 if
+                               needed. For HTTPS only. For HTTP, this option
+                               makes libcurl return error. */
+  CURL_HTTP_VERSION_3ONLY = 31, /* Use HTTP/3 without fallback. For HTTPS
+                                   only. For HTTP, this makes libcurl
+                                   return error. */
+
   CURL_HTTP_VERSION_LAST /* *ILLEGAL* http version */
 };
 
@@ -2953,6 +2958,7 @@ typedef enum {
   CURL_LOCK_DATA_SSL_SESSION,
   CURL_LOCK_DATA_CONNECT,
   CURL_LOCK_DATA_PSL,
+  CURL_LOCK_DATA_HSTS,
   CURL_LOCK_DATA_LAST
 } curl_lock_data;
 
diff --git a/include/curl/curlver.h b/include/curl/curlver.h
index 3487d1b..f4bb8b4 100644
--- a/include/curl/curlver.h
+++ b/include/curl/curlver.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -28,17 +28,17 @@
    a script at release-time. This was made its own header file in 7.11.2 */
 
 /* This is the global package copyright */
-#define LIBCURL_COPYRIGHT "1996 - 2022 Daniel Stenberg, <daniel@haxx.se>."
+#define LIBCURL_COPYRIGHT "Daniel Stenberg, <daniel@haxx.se>."
 
 /* This is the version number of the libcurl package from which this header
    file origins: */
-#define LIBCURL_VERSION "7.87.0-DEV"
+#define LIBCURL_VERSION "7.88.1-DEV"
 
 /* The numeric version number is also available "in parts" by using these
    defines: */
 #define LIBCURL_VERSION_MAJOR 7
-#define LIBCURL_VERSION_MINOR 87
-#define LIBCURL_VERSION_PATCH 0
+#define LIBCURL_VERSION_MINOR 88
+#define LIBCURL_VERSION_PATCH 1
 
 /* This is the numeric version of the libcurl version number, meant for easier
    parsing and comparisons by programs. The LIBCURL_VERSION_NUM define will
@@ -59,7 +59,7 @@
    CURL_VERSION_BITS() macro since curl's own configure script greps for it
    and needs it to contain the full number.
 */
-#define LIBCURL_VERSION_NUM 0x075700
+#define LIBCURL_VERSION_NUM 0x075801
 
 /*
  * This is the date and time when the full source package was created. The
diff --git a/include/curl/easy.h b/include/curl/easy.h
index 98ee888..394668a 100644
--- a/include/curl/easy.h
+++ b/include/curl/easy.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/include/curl/header.h b/include/curl/header.h
index 1598c6f..8df11e1 100644
--- a/include/curl/header.h
+++ b/include/curl/header.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2018 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/include/curl/mprintf.h b/include/curl/mprintf.h
index 06ef5c6..e652a65 100644
--- a/include/curl/mprintf.h
+++ b/include/curl/mprintf.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/include/curl/multi.h b/include/curl/multi.h
index c956d28..30a3d93 100644
--- a/include/curl/multi.h
+++ b/include/curl/multi.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/include/curl/options.h b/include/curl/options.h
index a792687..1ed76a9 100644
--- a/include/curl/options.h
+++ b/include/curl/options.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2018 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/include/curl/stdcheaders.h b/include/curl/stdcheaders.h
index 82e1b5f..7451aa3 100644
--- a/include/curl/stdcheaders.h
+++ b/include/curl/stdcheaders.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/include/curl/system.h b/include/curl/system.h
index 11db51e..def7739 100644
--- a/include/curl/system.h
+++ b/include/curl/system.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -227,16 +227,14 @@
 #  define CURL_TYPEOF_CURL_SOCKLEN_T unsigned int
 
 #elif defined(__OS400__)
-#  if defined(__ILEC400__)
-#    define CURL_TYPEOF_CURL_OFF_T     long long
-#    define CURL_FORMAT_CURL_OFF_T     "lld"
-#    define CURL_FORMAT_CURL_OFF_TU    "llu"
-#    define CURL_SUFFIX_CURL_OFF_T     LL
-#    define CURL_SUFFIX_CURL_OFF_TU    ULL
-#    define CURL_TYPEOF_CURL_SOCKLEN_T socklen_t
-#    define CURL_PULL_SYS_TYPES_H      1
-#    define CURL_PULL_SYS_SOCKET_H     1
-#  endif
+#  define CURL_TYPEOF_CURL_OFF_T     long long
+#  define CURL_FORMAT_CURL_OFF_T     "lld"
+#  define CURL_FORMAT_CURL_OFF_TU    "llu"
+#  define CURL_SUFFIX_CURL_OFF_T     LL
+#  define CURL_SUFFIX_CURL_OFF_TU    ULL
+#  define CURL_TYPEOF_CURL_SOCKLEN_T socklen_t
+#  define CURL_PULL_SYS_TYPES_H      1
+#  define CURL_PULL_SYS_SOCKET_H     1
 
 #elif defined(__MVS__)
 #  if defined(__IBMC__) || defined(__IBMCPP__)
diff --git a/include/curl/typecheck-gcc.h b/include/curl/typecheck-gcc.h
index bf655bb..bc8d7a7 100644
--- a/include/curl/typecheck-gcc.h
+++ b/include/curl/typecheck-gcc.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -42,9 +42,8 @@
  */
 #define curl_easy_setopt(handle, option, value)                         \
   __extension__({                                                       \
-      CURL_IGNORE_DEPRECATION(__typeof__(option) _curl_opt = option;)   \
+      CURLoption _curl_opt = (option);                                  \
       if(__builtin_constant_p(_curl_opt)) {                             \
-        (void) option;                                                  \
         CURL_IGNORE_DEPRECATION(                                        \
           if(curlcheck_long_option(_curl_opt))                          \
             if(!curlcheck_long(value))                                  \
@@ -120,9 +119,8 @@
 /* wraps curl_easy_getinfo() with typechecking */
 #define curl_easy_getinfo(handle, info, arg)                            \
   __extension__({                                                       \
-      CURL_IGNORE_DEPRECATION(__typeof__(info) _curl_info = info;)      \
+      CURLINFO _curl_info = (info);                                     \
       if(__builtin_constant_p(_curl_info)) {                            \
-        (void) info;                                                    \
         CURL_IGNORE_DEPRECATION(                                        \
           if(curlcheck_string_info(_curl_info))                         \
             if(!curlcheck_arr((arg), char *))                           \
diff --git a/include/curl/urlapi.h b/include/curl/urlapi.h
index e15c213..b97b534 100644
--- a/include/curl/urlapi.h
+++ b/include/curl/urlapi.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2018 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -62,6 +62,7 @@ typedef enum {
   CURLUE_BAD_SCHEME,          /* 27 */
   CURLUE_BAD_SLASHES,         /* 28 */
   CURLUE_BAD_USER,            /* 29 */
+  CURLUE_LACKS_IDN,           /* 30 */
   CURLUE_LAST
 } CURLUcode;
 
@@ -95,6 +96,7 @@ typedef enum {
 #define CURLU_NO_AUTHORITY (1<<10)      /* Allow empty authority when the
                                            scheme is unknown. */
 #define CURLU_ALLOW_SPACE (1<<11)       /* Allow spaces in the URL */
+#define CURLU_PUNYCODE (1<<12)          /* get the host name in pynycode */
 
 typedef struct Curl_URL CURLU;
 
diff --git a/include/curl/websockets.h b/include/curl/websockets.h
index 4d57f91..fd6a916 100644
--- a/include/curl/websockets.h
+++ b/include/curl/websockets.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -33,6 +33,7 @@ struct curl_ws_frame {
   int flags;            /* See the CURLWS_* defines */
   curl_off_t offset;    /* the offset of this data into the frame */
   curl_off_t bytesleft; /* number of pending bytes left of the payload */
+  size_t len;           /* size of the current data chunk */
 };
 
 /* flag bits */
diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt
index 5ca5357..ef2295b 100644
--- a/lib/CMakeLists.txt
+++ b/lib/CMakeLists.txt
@@ -5,7 +5,7 @@
 #                            | (__| |_| |  _ <| |___
 #                             \___|\___/|_| \_\_____|
 #
-# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+# 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
@@ -108,11 +108,12 @@ set_target_properties(${LIB_NAME} PROPERTIES
 
 if(CMAKE_SYSTEM_NAME STREQUAL "AIX" OR
   CMAKE_SYSTEM_NAME STREQUAL "Linux" OR
+  CMAKE_SYSTEM_NAME STREQUAL "Darwin" OR
   CMAKE_SYSTEM_NAME STREQUAL "GNU/kFreeBSD" OR
 
   # FreeBSD comes with the a.out and elf flavours
   # but a.out was supported up to version 3.x and
-  # elf from 3.x. I cannot imagine someone runnig
+  # elf from 3.x. I cannot imagine someone running
   # CMake on those ancient systems
   CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR
 
diff --git a/lib/Makefile.inc b/lib/Makefile.inc
index 9eafa93..c28b475 100644
--- a/lib/Makefile.inc
+++ b/lib/Makefile.inc
@@ -5,7 +5,7 @@
 #                            | (__| |_| |  _ <| |___
 #                             \___|\___/|_| \_\_____|
 #
-# Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+# 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
@@ -79,16 +79,17 @@ LIB_VTLS_HFILES =           \
   vtls/x509asn1.h
 
 LIB_VQUIC_CFILES = \
-  vquic/msh3.c   \
-  vquic/ngtcp2.c   \
-  vquic/quiche.c   \
+  vquic/curl_msh3.c   \
+  vquic/curl_ngtcp2.c   \
+  vquic/curl_quiche.c   \
   vquic/vquic.c
 
 LIB_VQUIC_HFILES = \
-  vquic/msh3.h   \
-  vquic/ngtcp2.h   \
-  vquic/quiche.h   \
-  vquic/vquic.h
+  vquic/curl_msh3.h   \
+  vquic/curl_ngtcp2.h   \
+  vquic/curl_quiche.h   \
+  vquic/vquic.h    \
+  vquic/vquic_int.h
 
 LIB_VSSH_CFILES =  \
   vssh/libssh.c    \
@@ -106,6 +107,8 @@ LIB_CFILES =         \
   base64.c           \
   bufref.c           \
   c-hyper.c          \
+  cf-http.c          \
+  cf-socket.c        \
   cfilters.c         \
   conncache.c        \
   connect.c          \
@@ -118,6 +121,7 @@ LIB_CFILES =         \
   curl_get_line.c    \
   curl_gethostname.c \
   curl_gssapi.c      \
+  curl_log.c         \
   curl_memrchr.c     \
   curl_multibyte.c   \
   curl_ntlm_core.c   \
@@ -229,6 +233,8 @@ LIB_HFILES =         \
   asyn.h             \
   bufref.h           \
   c-hyper.h          \
+  cf-http.h          \
+  cf-socket.h        \
   cfilters.h         \
   conncache.h        \
   connect.h          \
@@ -246,6 +252,7 @@ LIB_HFILES =         \
   curl_hmac.h        \
   curl_krb5.h        \
   curl_ldap.h        \
+  curl_log.h         \
   curl_md4.h         \
   curl_md5.h         \
   curl_memory.h      \
@@ -312,7 +319,6 @@ LIB_HFILES =         \
   pop3.h             \
   progress.h         \
   psl.h              \
-  quic.h             \
   rand.h             \
   rename.h           \
   rtsp.h             \
diff --git a/lib/altsvc.c b/lib/altsvc.c
index ec18e38..31a7abc 100644
--- a/lib/altsvc.c
+++ b/lib/altsvc.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2019 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/altsvc.h b/lib/altsvc.h
index 2751d27..7fea143 100644
--- a/lib/altsvc.h
+++ b/lib/altsvc.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2019 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/amigaos.c b/lib/amigaos.c
index e8c2fc0..b0a9500 100644
--- a/lib/amigaos.c
+++ b/lib/amigaos.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/amigaos.h b/lib/amigaos.h
index 9abfb59..7997ede 100644
--- a/lib/amigaos.h
+++ b/lib/amigaos.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/arpa_telnet.h b/lib/arpa_telnet.h
index 523f7f5..de13738 100644
--- a/lib/arpa_telnet.h
+++ b/lib/arpa_telnet.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/asyn-ares.c b/lib/asyn-ares.c
index 4436da3..19fe853 100644
--- a/lib/asyn-ares.c
+++ b/lib/asyn-ares.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/asyn-thread.c b/lib/asyn-thread.c
index 705f0f6..4d7f860 100644
--- a/lib/asyn-thread.c
+++ b/lib/asyn-thread.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/asyn.h b/lib/asyn.h
index 1aab21a..7e207c4 100644
--- a/lib/asyn.h
+++ b/lib/asyn.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/base64.c b/lib/base64.c
index bacd627..e1b7b72 100644
--- a/lib/base64.c
+++ b/lib/base64.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/bufref.c b/lib/bufref.c
index 91b0374..ce686b6 100644
--- a/lib/bufref.c
+++ b/lib/bufref.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2021 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/bufref.h b/lib/bufref.h
index 96b818b..dd424f1 100644
--- a/lib/bufref.h
+++ b/lib/bufref.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2021 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/c-hyper.c b/lib/c-hyper.c
index 65f5581..9c7632d 100644
--- a/lib/c-hyper.c
+++ b/lib/c-hyper.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -485,7 +485,7 @@ CURLcode Curl_hyper_stream(struct Curl_easy *data,
     if(k->upgr101 == UPGR101_WS) {
       if(http_status == 101) {
         /* verify the response */
-        result = Curl_ws_accept(data);
+        result = Curl_ws_accept(data, NULL, 0);
         if(result)
           return result;
       }
@@ -1128,6 +1128,16 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done)
       goto error;
   }
 
+#ifdef HAVE_LIBZ
+  /* we only consider transfer-encoding magic if libz support is built-in */
+  result = Curl_transferencode(data);
+  if(result)
+    goto error;
+  result = Curl_hyper_header(data, headers, data->state.aptr.te);
+  if(result)
+    goto error;
+#endif
+
   if(!Curl_checkheaders(data, STRCONST("Accept-Encoding")) &&
      data->set.str[STRING_ENCODING]) {
     Curl_safefree(data->state.aptr.accept_encoding);
@@ -1144,16 +1154,6 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done)
   else
     Curl_safefree(data->state.aptr.accept_encoding);
 
-#ifdef HAVE_LIBZ
-  /* we only consider transfer-encoding magic if libz support is built-in */
-  result = Curl_transferencode(data);
-  if(result)
-    goto error;
-  result = Curl_hyper_header(data, headers, data->state.aptr.te);
-  if(result)
-    goto error;
-#endif
-
   result = cookies(data, conn, headers);
   if(result)
     goto error;
diff --git a/lib/c-hyper.h b/lib/c-hyper.h
index 70507ad..4218cda 100644
--- a/lib/c-hyper.h
+++ b/lib/c-hyper.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/cf-http.c b/lib/cf-http.c
new file mode 100644
index 0000000..2ee3d4d
--- /dev/null
+++ b/lib/cf-http.c
@@ -0,0 +1,518 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  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-http.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", TRNSPRT_TCP);
+    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", TRNSPRT_TCP);
+    }
+
+    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 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,
+  Curl_cf_def_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) */
diff --git a/lib/cf-http.h b/lib/cf-http.h
new file mode 100644
index 0000000..6a39527
--- /dev/null
+++ b/lib/cf-http.h
@@ -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 */
diff --git a/lib/cf-socket.c b/lib/cf-socket.c
new file mode 100644
index 0000000..2549f34
--- /dev/null
+++ b/lib/cf-socket.c
@@ -0,0 +1,1908 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  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 && addr->socktype == SOCK_DGRAM) {
+      /* This is icky and seems, at least, to happen on macOS:
+       * we get sockfd == 0 and if called again, we get a valid one > 0.
+       * If we close the 0, we sometimes get failures in multi poll, as
+       * 0 seems also be the fd for the sockpair used for WAKEUP polling.
+       * Very strange. Maybe this code should be ifdef'ed for macOS, but
+       * on "real" OS, fd 0 is stdin and we never see that. So...
+       */
+      fake_sclose(*sockfd);
+      *sockfd = socket(addr->family, addr->socktype, addr->protocol);
+      DEBUGF(infof(data, "QUIRK: UDP socket() gave handle 0, 2nd attempt %d",
+                   (int)*sockfd));
+    }
+  }
+
+  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);
+}
+
+bool Curl_socket_is_dead(curl_socket_t sock)
+{
+  int sval;
+  bool ret_val = TRUE;
+
+  sval = SOCKET_READABLE(sock, 0);
+  if(sval == 0)
+    /* timeout */
+    ret_val = FALSE;
+
+  return ret_val;
+}
+
+
+#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, 0, 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;
+
+    /* 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_CONN_REPORT_STATS:
+    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) {
+        Curl_pgrsTimeWas(data, TIMER_CONNECT, ctx->first_byte_at);
+        break;
+      }
+      /* FALLTHROUGH */
+    default:
+      Curl_pgrsTimeWas(data, TIMER_CONNECT, ctx->connected_at);
+      break;
+    }
+    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)
+{
+  struct cf_socket_ctx *ctx = cf->ctx;
+  int sval;
+
+  (void)data;
+  if(!ctx || ctx->sock == CURL_SOCKET_BAD)
+    return FALSE;
+
+  sval = SOCKET_READABLE(ctx->sock, 0);
+  if(sval == 0) {
+    /* timeout */
+    return TRUE;
+  }
+  else if(sval & CURL_CSELECT_ERR) {
+    /* socket is in an error state */
+    return FALSE;
+  }
+  else if(sval & CURL_CSELECT_IN) {
+    /* readable with no error. could still be closed */
+/* Minix 3.1 doesn't support any flags on recv; just assume socket is OK */
+#ifdef MSG_PEEK
+    /* use the socket */
+    char buf;
+    if(recv((RECV_TYPE_ARG1)ctx->sock, (RECV_TYPE_ARG2)&buf,
+            (RECV_TYPE_ARG3)1, (RECV_TYPE_ARG4)MSG_PEEK) == 0) {
+      return FALSE;   /* FIN received */
+    }
+#endif
+    return 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;
+  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_remote_ip(cf, data);
+  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;
+}
+
+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_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, "Curl_conn_tcp_accepted_set(%d)", (int)ctx->sock));
+
+  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;
+}
+
diff --git a/lib/cf-socket.h b/lib/cf-socket.h
new file mode 100644
index 0000000..f6eb810
--- /dev/null
+++ b/lib/cf-socket.h
@@ -0,0 +1,192 @@
+#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);
+
+/*
+ * This function should return TRUE if the socket is to be assumed to
+ * be dead. Most commonly this happens when the server has closed the
+ * connection due to inactivity.
+ */
+bool Curl_socket_is_dead(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 */
diff --git a/lib/cfilters.c b/lib/cfilters.c
index bcb33da..2af0dd8 100644
--- a/lib/cfilters.c
+++ b/lib/cfilters.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -34,9 +34,6 @@
 #include "multiif.h"
 #include "progress.h"
 #include "warnless.h"
-#include "http_proxy.h"
-#include "socks.h"
-#include "vtls/vtls.h"
 
 /* The last 3 #include files should be in this order */
 #include "curl_printf.h"
@@ -54,89 +51,116 @@ void Curl_cf_def_destroy_this(struct Curl_cfilter *cf, struct Curl_easy *data)
   (void)data;
 }
 
-CURLcode Curl_cf_def_setup(struct Curl_cfilter *cf,
-                           struct Curl_easy *data,
-                           const struct Curl_dns_entry *remotehost)
-{
-  DEBUGASSERT(cf->next);
-  return cf->next->cft->setup(cf->next, data, remotehost);
-}
-
-void     Curl_cf_def_attach_data(struct Curl_cfilter *cf,
-                                 struct Curl_easy *data)
-{
-  (void)cf;
-  (void)data;
-}
-
-void     Curl_cf_def_detach_data(struct Curl_cfilter *cf,
-                                 struct Curl_easy *data)
-{
-  (void)cf;
-  (void)data;
-}
-
 void Curl_cf_def_close(struct Curl_cfilter *cf, struct Curl_easy *data)
 {
-  DEBUGASSERT(cf->next);
   cf->connected = FALSE;
-  cf->next->cft->close(cf->next, data);
+  if(cf->next)
+    cf->next->cft->close(cf->next, data);
 }
 
 CURLcode Curl_cf_def_connect(struct Curl_cfilter *cf,
                              struct Curl_easy *data,
                              bool blocking, bool *done)
 {
-  DEBUGASSERT(cf->next);
-  return cf->next->cft->connect(cf->next, data, blocking, done);
+  CURLcode result;
+
+  if(cf->connected) {
+    *done = TRUE;
+    return CURLE_OK;
+  }
+  if(cf->next) {
+    result = cf->next->cft->connect(cf->next, data, blocking, done);
+    if(!result && *done) {
+      cf->connected = TRUE;
+    }
+    return result;
+  }
+  *done = FALSE;
+  return CURLE_FAILED_INIT;
 }
 
 void Curl_cf_def_get_host(struct Curl_cfilter *cf, struct Curl_easy *data,
                           const char **phost, const char **pdisplay_host,
                           int *pport)
 {
-  DEBUGASSERT(cf->next);
-  cf->next->cft->get_host(cf->next, data, phost, pdisplay_host, pport);
+  if(cf->next)
+    cf->next->cft->get_host(cf->next, data, phost, pdisplay_host, pport);
+  else {
+    *phost = cf->conn->host.name;
+    *pdisplay_host = cf->conn->host.dispname;
+    *pport = cf->conn->port;
+  }
 }
 
 int Curl_cf_def_get_select_socks(struct Curl_cfilter *cf,
                                  struct Curl_easy *data,
                                  curl_socket_t *socks)
 {
-  DEBUGASSERT(cf->next);
-  return cf->next->cft->get_select_socks(cf->next, data, socks);
+  return cf->next?
+    cf->next->cft->get_select_socks(cf->next, data, socks) : 0;
 }
 
 bool Curl_cf_def_data_pending(struct Curl_cfilter *cf,
                               const struct Curl_easy *data)
 {
-  DEBUGASSERT(cf->next);
-  return cf->next->cft->has_data_pending(cf->next, data);
+  return cf->next?
+    cf->next->cft->has_data_pending(cf->next, data) : FALSE;
 }
 
 ssize_t  Curl_cf_def_send(struct Curl_cfilter *cf, struct Curl_easy *data,
                           const void *buf, size_t len, CURLcode *err)
 {
-  DEBUGASSERT(cf->next);
-  return cf->next->cft->do_send(cf->next, data, buf, len, err);
+  return cf->next?
+    cf->next->cft->do_send(cf->next, data, buf, len, err) :
+    CURLE_RECV_ERROR;
 }
 
 ssize_t  Curl_cf_def_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
                           char *buf, size_t len, CURLcode *err)
 {
-  DEBUGASSERT(cf->next);
-  return cf->next->cft->do_recv(cf->next, data, buf, len, err);
+  return cf->next?
+    cf->next->cft->do_recv(cf->next, data, buf, len, err) :
+    CURLE_SEND_ERROR;
 }
 
-void Curl_conn_cf_discard_all(struct Curl_easy *data,
-                              struct connectdata *conn, int index)
+bool Curl_cf_def_conn_is_alive(struct Curl_cfilter *cf,
+                               struct Curl_easy *data)
 {
-  struct Curl_cfilter *cfn, *cf = conn->cfilter[index];
+  return cf->next?
+    cf->next->cft->is_alive(cf->next, data) :
+    FALSE; /* pessimistic in absence of data */
+}
+
+CURLcode Curl_cf_def_conn_keep_alive(struct Curl_cfilter *cf,
+                                     struct Curl_easy *data)
+{
+  return cf->next?
+    cf->next->cft->keep_alive(cf->next, data) :
+    CURLE_OK;
+}
+
+CURLcode Curl_cf_def_query(struct Curl_cfilter *cf,
+                           struct Curl_easy *data,
+                           int query, int *pres1, void *pres2)
+{
+  return cf->next?
+    cf->next->cft->query(cf->next, data, query, pres1, pres2) :
+    CURLE_UNKNOWN_OPTION;
+}
+
+void Curl_conn_cf_discard_chain(struct Curl_cfilter **pcf,
+                                struct Curl_easy *data)
+{
+  struct Curl_cfilter *cfn, *cf = *pcf;
 
   if(cf) {
-    conn->cfilter[index] = NULL;
+    *pcf = NULL;
     while(cf) {
       cfn = cf->next;
+      /* prevent destroying filter to mess with its sub-chain, since
+       * we have the reference now and will call destroy on it.
+       */
+      cf->next = NULL;
       cf->cft->destroy(cf, data);
       free(cf);
       cf = cfn;
@@ -144,6 +168,12 @@ void Curl_conn_cf_discard_all(struct Curl_easy *data,
   }
 }
 
+void Curl_conn_cf_discard_all(struct Curl_easy *data,
+                              struct connectdata *conn, int index)
+{
+  Curl_conn_cf_discard_chain(&conn->cfilter[index], data);
+}
+
 void Curl_conn_close(struct Curl_easy *data, int index)
 {
   struct Curl_cfilter *cf;
@@ -160,7 +190,6 @@ ssize_t Curl_conn_recv(struct Curl_easy *data, int num, char *buf,
                        size_t len, CURLcode *code)
 {
   struct Curl_cfilter *cf;
-  ssize_t nread;
 
   DEBUGASSERT(data);
   DEBUGASSERT(data->conn);
@@ -169,13 +198,9 @@ ssize_t Curl_conn_recv(struct Curl_easy *data, int num, char *buf,
     cf = cf->next;
   }
   if(cf) {
-    nread = cf->cft->do_recv(cf, data, buf, len, code);
-    /* DEBUGF(infof(data, "Curl_conn_recv(handle=%p, index=%d)"
-           "-> %ld, err=%d", data, num, nread, *code));*/
-    return nread;
+    return cf->cft->do_recv(cf, data, buf, len, code);
   }
-  failf(data, "no filter connected, conn=%ld, sockindex=%d",
-        data->conn->connection_id, num);
+  failf(data, CMSGI(data->conn, num, "recv: no filter connected"));
   *code = CURLE_FAILED_INIT;
   return -1;
 }
@@ -184,7 +209,6 @@ ssize_t Curl_conn_send(struct Curl_easy *data, int num,
                        const void *mem, size_t len, CURLcode *code)
 {
   struct Curl_cfilter *cf;
-  ssize_t nwritten;
 
   DEBUGASSERT(data);
   DEBUGASSERT(data->conn);
@@ -193,13 +217,10 @@ ssize_t Curl_conn_send(struct Curl_easy *data, int num,
     cf = cf->next;
   }
   if(cf) {
-    nwritten = cf->cft->do_send(cf, data, mem, len, code);
-    /* DEBUGF(infof(data, "Curl_conn_send(handle=%p, index=%d, len=%ld)"
-           " -> %ld, err=%d", data, num, len, nwritten, *code));*/
-    return nwritten;
+    return cf->cft->do_send(cf, data, mem, len, code);
   }
-  failf(data, "no filter connected, conn=%ld, sockindex=%d",
-        data->conn->connection_id, num);
+  failf(data, CMSGI(data->conn, num, "send: no filter connected"));
+  DEBUGASSERT(0);
   *code = CURLE_FAILED_INIT;
   return -1;
 }
@@ -234,12 +255,31 @@ void Curl_conn_cf_add(struct Curl_easy *data,
   DEBUGASSERT(!cf->conn);
   DEBUGASSERT(!cf->next);
 
-  DEBUGF(infof(data, CMSGI(conn, index, "cf_add(filter=%s)"),
-               cf->cft->name));
   cf->next = conn->cfilter[index];
   cf->conn = conn;
   cf->sockindex = index;
   conn->cfilter[index] = cf;
+  DEBUGF(LOG_CF(data, cf, "added"));
+}
+
+void Curl_conn_cf_insert_after(struct Curl_cfilter *cf_at,
+                               struct Curl_cfilter *cf_new)
+{
+  struct Curl_cfilter *tail, **pnext;
+
+  DEBUGASSERT(cf_at);
+  DEBUGASSERT(cf_new);
+  DEBUGASSERT(!cf_new->conn);
+
+  tail = cf_at->next;
+  cf_at->next = cf_new;
+  do {
+    cf_new->conn = cf_at->conn;
+    cf_new->sockindex = cf_at->sockindex;
+    pnext = &cf_new->next;
+    cf_new = cf_new->next;
+  } while(cf_new);
+  *pnext = tail;
 }
 
 void Curl_conn_cf_discard(struct Curl_cfilter *cf, struct Curl_easy *data)
@@ -259,97 +299,54 @@ void Curl_conn_cf_discard(struct Curl_cfilter *cf, struct Curl_easy *data)
   free(cf);
 }
 
-ssize_t Curl_conn_cf_send(struct Curl_cfilter *cf, struct Curl_easy *data,
-                          const void *buf, size_t len, CURLcode *err)
+CURLcode Curl_conn_cf_connect(struct Curl_cfilter *cf,
+                              struct Curl_easy *data,
+                              bool blocking, bool *done)
 {
-  return cf->cft->do_send(cf, data, buf, len, err);
+  if(cf)
+    return cf->cft->connect(cf, data, blocking, done);
+  return CURLE_FAILED_INIT;
 }
 
-ssize_t Curl_conn_cf_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
-                          char *buf, size_t len, CURLcode *err)
+void Curl_conn_cf_close(struct Curl_cfilter *cf, struct Curl_easy *data)
 {
-  return cf->cft->do_recv(cf, data, buf, len, err);
+  if(cf)
+    cf->cft->close(cf, data);
 }
 
-CURLcode Curl_conn_setup(struct Curl_easy *data,
-                         struct connectdata *conn,
-                         int sockindex,
-                         const struct Curl_dns_entry *remotehost,
-                         int ssl_mode)
+int Curl_conn_cf_get_select_socks(struct Curl_cfilter *cf,
+                                  struct Curl_easy *data,
+                                  curl_socket_t *socks)
 {
-  struct Curl_cfilter *cf;
-  CURLcode result;
-
-  DEBUGASSERT(data);
-  /* If no filter is set, we have the "default" setup of connection filters.
-   * The filter chain from botton to top will be:
-   * - SOCKET       socket filter for outgoing connection to remotehost
-   * if http_proxy tunneling is engaged:
-   *    - SSL                 if proxytype is CURLPROXY_HTTPS
-   *    - HTTP_PROXY_TUNNEL
-   * otherwise, if socks_proxy is engaged:
-   *    - SOCKS_PROXY_TUNNEL
-   * - SSL          if conn->handler has PROTOPT_SSL
-   */
-  if(!conn->cfilter[sockindex]) {
-    DEBUGF(infof(data, DMSGI(data, sockindex, "setup, init filter chain")));
-    result = Curl_conn_socket_set(data, conn, sockindex);
-    if(result)
-      goto out;
-
-#ifndef CURL_DISABLE_PROXY
-    if(conn->bits.socksproxy) {
-      result = Curl_conn_socks_proxy_add(data, conn, sockindex);
-      if(result)
-        goto out;
-    }
+  if(cf)
+    return cf->cft->get_select_socks(cf, data, socks);
+  return 0;
+}
 
-    if(conn->bits.httpproxy) {
-#ifdef USE_SSL
-      if(conn->http_proxy.proxytype == CURLPROXY_HTTPS) {
-        result = Curl_ssl_cfilter_proxy_add(data, conn, sockindex);
-        if(result)
-          goto out;
-      }
-#endif /* USE_SSL */
-
-#if !defined(CURL_DISABLE_HTTP)
-      if(conn->bits.tunnel_proxy) {
-        result = Curl_conn_http_proxy_add(data, conn, sockindex);
-        if(result)
-          goto out;
-      }
-#endif /* !CURL_DISABLE_HTTP */
-    }
-#endif /* !CURL_DISABLE_PROXY */
-
-#ifdef USE_SSL
-    if(ssl_mode == CURL_CF_SSL_ENABLE
-      || (ssl_mode != CURL_CF_SSL_DISABLE
-           && conn->handler->flags & PROTOPT_SSL)) {
-      result = Curl_ssl_cfilter_add(data, conn, sockindex);
-      if(result)
-        goto out;
-    }
-#else
-    (void)ssl_mode;
-#endif /* USE_SSL */
-
-#if !defined(CURL_DISABLE_PROXY) && !defined(CURL_DISABLE_HTTP)
-    if(data->set.haproxyprotocol) {
-      result = Curl_conn_haproxy_add(data, conn, sockindex);
-      if(result)
-        goto out;
-    }
-#endif /* !CURL_DISABLE_PROXY && !CURL_DISABLE_HTTP */
+bool Curl_conn_cf_data_pending(struct Curl_cfilter *cf,
+                               const struct Curl_easy *data)
+{
+  if(cf)
+    return cf->cft->has_data_pending(cf, data);
+  return FALSE;
+}
 
-  }
-  DEBUGASSERT(conn->cfilter[sockindex]);
-  cf = data->conn->cfilter[sockindex];
-  result = cf->cft->setup(cf, data, remotehost);
+ssize_t Curl_conn_cf_send(struct Curl_cfilter *cf, struct Curl_easy *data,
+                          const void *buf, size_t len, CURLcode *err)
+{
+  if(cf)
+    return cf->cft->do_send(cf, data, buf, len, err);
+  *err = CURLE_SEND_ERROR;
+  return -1;
+}
 
-out:
-  return result;
+ssize_t Curl_conn_cf_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
+                          char *buf, size_t len, CURLcode *err)
+{
+  if(cf)
+    return cf->cft->do_recv(cf, data, buf, len, err);
+  *err = CURLE_RECV_ERROR;
+  return -1;
 }
 
 CURLcode Curl_conn_connect(struct Curl_easy *data,
@@ -358,16 +355,26 @@ CURLcode Curl_conn_connect(struct Curl_easy *data,
                            bool *done)
 {
   struct Curl_cfilter *cf;
-  CURLcode result;
+  CURLcode result = CURLE_OK;
 
   DEBUGASSERT(data);
+  DEBUGASSERT(data->conn);
 
   cf = data->conn->cfilter[sockindex];
   DEBUGASSERT(cf);
-  result = cf->cft->connect(cf, data, blocking, done);
+  if(!cf)
+    return CURLE_FAILED_INIT;
+
+  *done = cf->connected;
+  if(!*done) {
+    result = cf->cft->connect(cf, data, blocking, done);
+    if(!result && *done) {
+      Curl_conn_ev_update_info(data, data->conn);
+      Curl_conn_ev_report_stats(data, data->conn);
+      data->conn->keepalive = Curl_now();
+    }
+  }
 
-  DEBUGF(infof(data, DMSGI(data, sockindex, "connect(block=%d)-> %d, done=%d"),
-         blocking, result, *done));
   return result;
 }
 
@@ -394,11 +401,10 @@ bool Curl_conn_is_ip_connected(struct Curl_easy *data, int sockindex)
   return FALSE;
 }
 
-bool Curl_conn_is_ssl(struct Curl_easy *data, int sockindex)
+bool Curl_conn_is_ssl(struct connectdata *conn, int sockindex)
 {
-  struct Curl_cfilter *cf = data->conn? data->conn->cfilter[sockindex] : NULL;
+  struct Curl_cfilter *cf = conn? conn->cfilter[sockindex] : NULL;
 
-  (void)data;
   for(; cf; cf = cf->next) {
     if(cf->cft->flags & CF_TYPE_SSL)
       return TRUE;
@@ -408,6 +414,19 @@ bool Curl_conn_is_ssl(struct Curl_easy *data, int sockindex)
   return FALSE;
 }
 
+bool Curl_conn_is_multiplex(struct connectdata *conn, int sockindex)
+{
+  struct Curl_cfilter *cf = conn? conn->cfilter[sockindex] : NULL;
+
+  for(; cf; cf = cf->next) {
+    if(cf->cft->flags & CF_TYPE_MULTIPLEX)
+      return TRUE;
+    if(cf->cft->flags & CF_TYPE_IP_CONNECT
+       || cf->cft->flags & CF_TYPE_SSL)
+      return FALSE;
+  }
+  return FALSE;
+}
 
 bool Curl_conn_data_pending(struct Curl_easy *data, int sockindex)
 {
@@ -416,8 +435,6 @@ bool Curl_conn_data_pending(struct Curl_easy *data, int sockindex)
   (void)data;
   DEBUGASSERT(data);
   DEBUGASSERT(data->conn);
-  if(Curl_recv_has_postponed_data(data->conn, sockindex))
-    return TRUE;
 
   cf = data->conn->cfilter[sockindex];
   while(cf && !cf->connected) {
@@ -437,46 +454,16 @@ int Curl_conn_get_select_socks(struct Curl_easy *data, int sockindex,
   DEBUGASSERT(data);
   DEBUGASSERT(data->conn);
   cf = data->conn->cfilter[sockindex];
+
+  /* if the next one is not yet connected, that's the one we want */
+  while(cf && cf->next && !cf->next->connected)
+    cf = cf->next;
   if(cf) {
     return cf->cft->get_select_socks(cf, data, socks);
   }
   return GETSOCK_BLANK;
 }
 
-void Curl_conn_attach_data(struct connectdata *conn,
-                           struct Curl_easy *data)
-{
-  size_t i;
-  struct Curl_cfilter *cf;
-
-  for(i = 0; i < ARRAYSIZE(conn->cfilter); ++i) {
-    cf = conn->cfilter[i];
-    if(cf) {
-      while(cf) {
-        cf->cft->attach_data(cf, data);
-        cf = cf->next;
-      }
-    }
-  }
-}
-
-void Curl_conn_detach_data(struct connectdata *conn,
-                           struct Curl_easy *data)
-{
-  size_t i;
-  struct Curl_cfilter *cf;
-
-  for(i = 0; i < ARRAYSIZE(conn->cfilter); ++i) {
-    cf = conn->cfilter[i];
-    if(cf) {
-      while(cf) {
-        cf->cft->detach_data(cf, data);
-        cf = cf->next;
-      }
-    }
-  }
-}
-
 void Curl_conn_get_host(struct Curl_easy *data, int sockindex,
                         const char **phost, const char **pdisplay_host,
                         int *pport)
@@ -491,7 +478,7 @@ void Curl_conn_get_host(struct Curl_easy *data, int sockindex,
   else {
     /* Some filter ask during shutdown for this, mainly for debugging
      * purposes. We hand out the defaults, however this is not always
-     * accurate, as the connction might be tunneled, etc. But all that
+     * accurate, as the connection might be tunneled, etc. But all that
      * state is already gone here. */
     *phost = data->conn->host.name;
     *pdisplay_host = data->conn->host.dispname;
@@ -499,4 +486,158 @@ void Curl_conn_get_host(struct Curl_easy *data, int sockindex,
   }
 }
 
+CURLcode Curl_cf_def_cntrl(struct Curl_cfilter *cf,
+                                struct Curl_easy *data,
+                                int event, int arg1, void *arg2)
+{
+  (void)cf;
+  (void)data;
+  (void)event;
+  (void)arg1;
+  (void)arg2;
+  return CURLE_OK;
+}
+
+CURLcode Curl_conn_cf_cntrl(struct Curl_cfilter *cf,
+                            struct Curl_easy *data,
+                            bool ignore_result,
+                            int event, int arg1, void *arg2)
+{
+  CURLcode result = CURLE_OK;
+
+  for(; cf; cf = cf->next) {
+    if(Curl_cf_def_cntrl == cf->cft->cntrl)
+      continue;
+    result = cf->cft->cntrl(cf, data, event, arg1, arg2);
+    if(!ignore_result && result)
+      break;
+  }
+  return result;
+}
+
+curl_socket_t Curl_conn_cf_get_socket(struct Curl_cfilter *cf,
+                                      struct Curl_easy *data)
+{
+  curl_socket_t sock;
+  if(cf && !cf->cft->query(cf, data, CF_QUERY_SOCKET, NULL, &sock))
+    return sock;
+  return CURL_SOCKET_BAD;
+}
+
+curl_socket_t Curl_conn_get_socket(struct Curl_easy *data, int sockindex)
+{
+  struct Curl_cfilter *cf;
+
+  cf = data->conn? data->conn->cfilter[sockindex] : NULL;
+  /* if the top filter has not connected, ask it (and its sub-filters)
+   * for the socket. Otherwise conn->sock[sockindex] should have it.
+   */
+  if(cf && !cf->connected)
+    return Curl_conn_cf_get_socket(cf, data);
+  return data->conn? data->conn->sock[sockindex] : CURL_SOCKET_BAD;
+}
+
+static CURLcode cf_cntrl_all(struct connectdata *conn,
+                             struct Curl_easy *data,
+                             bool ignore_result,
+                             int event, int arg1, void *arg2)
+{
+  CURLcode result = CURLE_OK;
+  size_t i;
+
+  for(i = 0; i < ARRAYSIZE(conn->cfilter); ++i) {
+    result = Curl_conn_cf_cntrl(conn->cfilter[i], data, ignore_result,
+                                event, arg1, arg2);
+    if(!ignore_result && result)
+      break;
+  }
+  return result;
+}
+
+void Curl_conn_ev_data_attach(struct connectdata *conn,
+                              struct Curl_easy *data)
+{
+  cf_cntrl_all(conn, data, TRUE, CF_CTRL_DATA_ATTACH, 0, NULL);
+}
+
+void Curl_conn_ev_data_detach(struct connectdata *conn,
+                              struct Curl_easy *data)
+{
+  cf_cntrl_all(conn, data, TRUE, CF_CTRL_DATA_DETACH, 0, NULL);
+}
+
+CURLcode Curl_conn_ev_data_setup(struct Curl_easy *data)
+{
+  return cf_cntrl_all(data->conn, data, FALSE,
+                      CF_CTRL_DATA_SETUP, 0, NULL);
+}
+
+CURLcode Curl_conn_ev_data_idle(struct Curl_easy *data)
+{
+  return cf_cntrl_all(data->conn, data, FALSE,
+                      CF_CTRL_DATA_IDLE, 0, NULL);
+}
+
+/**
+ * Notify connection filters that the transfer represented by `data`
+ * is donw with sending data (e.g. has uploaded everything).
+ */
+void Curl_conn_ev_data_done_send(struct Curl_easy *data)
+{
+  cf_cntrl_all(data->conn, data, TRUE, CF_CTRL_DATA_DONE_SEND, 0, NULL);
+}
+
+/**
+ * Notify connection filters that the transfer represented by `data`
+ * is finished - eventually premature, e.g. before being complete.
+ */
+void Curl_conn_ev_data_done(struct Curl_easy *data, bool premature)
+{
+  cf_cntrl_all(data->conn, data, TRUE, CF_CTRL_DATA_DONE, premature, NULL);
+}
+
+CURLcode Curl_conn_ev_data_pause(struct Curl_easy *data, bool do_pause)
+{
+  return cf_cntrl_all(data->conn, data, FALSE,
+                      CF_CTRL_DATA_PAUSE, do_pause, NULL);
+}
+
+void Curl_conn_ev_update_info(struct Curl_easy *data,
+                              struct connectdata *conn)
+{
+  cf_cntrl_all(conn, data, TRUE, CF_CTRL_CONN_INFO_UPDATE, 0, NULL);
+}
+
+void Curl_conn_ev_report_stats(struct Curl_easy *data,
+                               struct connectdata *conn)
+{
+  cf_cntrl_all(conn, data, TRUE, CF_CTRL_CONN_REPORT_STATS, 0, NULL);
+}
+
+bool Curl_conn_is_alive(struct Curl_easy *data, struct connectdata *conn)
+{
+  struct Curl_cfilter *cf = conn->cfilter[FIRSTSOCKET];
+  return cf && !cf->conn->bits.close && cf->cft->is_alive(cf, data);
+}
+
+CURLcode Curl_conn_keep_alive(struct Curl_easy *data,
+                              struct connectdata *conn,
+                              int sockindex)
+{
+  struct Curl_cfilter *cf = conn->cfilter[sockindex];
+  return cf? cf->cft->keep_alive(cf, data) : CURLE_OK;
+}
+
+size_t Curl_conn_get_max_concurrent(struct Curl_easy *data,
+                                     struct connectdata *conn,
+                                     int sockindex)
+{
+  CURLcode result;
+  int n = 0;
+
+  struct Curl_cfilter *cf = conn->cfilter[sockindex];
+  result = cf? cf->cft->query(cf, data, CF_QUERY_MAX_CONCURRENT,
+                              &n, NULL) : CURLE_UNKNOWN_OPTION;
+  return (result || n <= 0)? 1 : (size_t)n;
+}
 
diff --git a/lib/cfilters.h b/lib/cfilters.h
index 4b81b42..94dc53f 100644
--- a/lib/cfilters.h
+++ b/lib/cfilters.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -36,11 +36,6 @@ struct connectdata;
 typedef void     Curl_cft_destroy_this(struct Curl_cfilter *cf,
                                        struct Curl_easy *data);
 
-/* Setup the connection for `data`, using destination `remotehost`.
- */
-typedef CURLcode Curl_cft_setup(struct Curl_cfilter *cf,
-                                struct Curl_easy *data,
-                                const struct Curl_dns_entry *remotehost);
 typedef void     Curl_cft_close(struct Curl_cfilter *cf,
                                 struct Curl_easy *data);
 
@@ -89,29 +84,89 @@ typedef ssize_t  Curl_cft_recv(struct Curl_cfilter *cf,
                                size_t len,             /* amount to read */
                                CURLcode *err);         /* error to return */
 
-typedef void     Curl_cft_attach_data(struct Curl_cfilter *cf,
-                                      struct Curl_easy *data);
-typedef void     Curl_cft_detach_data(struct Curl_cfilter *cf,
-                                      struct Curl_easy *data);
+typedef bool     Curl_cft_conn_is_alive(struct Curl_cfilter *cf,
+                                        struct Curl_easy *data);
+
+typedef CURLcode Curl_cft_conn_keep_alive(struct Curl_cfilter *cf,
+                                          struct Curl_easy *data);
 
 /**
- * The easy handle `data` is being detached (no longer served)
- * by connection `conn`. All filters are informed to release any resources
- * related to `data`.
- * Note: there may be several `data` attached to a connection at the same
- * time.
+ * Events/controls for connection filters, their arguments and
+ * return code handling. Filter callbacks are invoked "top down".
+ * Return code handling:
+ * "first fail" meaning that the first filter returning != CURLE_OK, will
+ *              abort further event distribution and determine the result.
+ * "ignored" meaning return values are ignored and the event is distributed
+ *           to all filters in the chain. Overall result is always CURLE_OK.
  */
-void Curl_conn_detach(struct connectdata *conn, struct Curl_easy *data);
+/*      data event                          arg1       arg2     return */
+#define CF_CTRL_DATA_ATTACH           1  /* 0          NULL     ignored */
+#define CF_CTRL_DATA_DETACH           2  /* 0          NULL     ignored */
+#define CF_CTRL_DATA_SETUP            4  /* 0          NULL     first fail */
+#define CF_CTRL_DATA_IDLE             5  /* 0          NULL     first fail */
+#define CF_CTRL_DATA_PAUSE            6  /* on/off     NULL     first fail */
+#define CF_CTRL_DATA_DONE             7  /* premature  NULL     ignored */
+#define CF_CTRL_DATA_DONE_SEND        8  /* 0          NULL     ignored */
+/* update conn info at connection and data */
+#define CF_CTRL_CONN_INFO_UPDATE (256+0) /* 0          NULL     ignored */
+/* report conn statistics (timers) for connection and data */
+#define CF_CTRL_CONN_REPORT_STATS (256+1) /* 0         NULL     ignored */
 
+/**
+ * Handle event/control for the filter.
+ * Implementations MUST NOT chain calls to cf->next.
+ */
+typedef CURLcode Curl_cft_cntrl(struct Curl_cfilter *cf,
+                                struct Curl_easy *data,
+                                int event, int arg1, void *arg2);
+
+
+/**
+ * Queries to ask via a `Curl_cft_query *query` method on a cfilter chain.
+ * - MAX_CONCURRENT: the maximum number of parallel transfers the filter
+ *                   chain expects to handle at the same time.
+ *                   default: 1 if no filter overrides.
+ * - CONNECT_REPLY_MS: milliseconds until the first indication of a server
+ *                   response was received on a connect. For TCP, this
+ *                   reflects the time until the socket connected. On UDP
+ *                   this gives the time the first bytes from the server
+ *                   were received.
+ *                   -1 if not determined yet.
+ * - CF_QUERY_SOCKET: the socket used by the filter chain
+ */
+/*      query                             res1       res2     */
+#define CF_QUERY_MAX_CONCURRENT     1  /* number     -        */
+#define CF_QUERY_CONNECT_REPLY_MS   2  /* number     -        */
+#define CF_QUERY_SOCKET             3  /* -          curl_socket_t */
+
+/**
+ * Query the cfilter for properties. Filters ignorant of a query will
+ * pass it "down" the filter chain.
+ */
+typedef CURLcode Curl_cft_query(struct Curl_cfilter *cf,
+                                struct Curl_easy *data,
+                                int query, int *pres1, void *pres2);
+
+/**
+ * Type flags for connection filters. A filter can have none, one or
+ * many of those. Use to evaluate state/capabilities of a filter chain.
+ *
+ * CF_TYPE_IP_CONNECT: provides an IP connection or sth equivalent, like
+ *                     a CONNECT tunnel, a UNIX domain socket, a QUIC
+ *                     connection, etc.
+ * CF_TYPE_SSL:        provide SSL/TLS
+ * CF_TYPE_MULTIPLEX:  provides multiplexing of easy handles
+ */
 #define CF_TYPE_IP_CONNECT  (1 << 0)
 #define CF_TYPE_SSL         (1 << 1)
+#define CF_TYPE_MULTIPLEX   (1 << 2)
 
 /* A connection filter type, e.g. specific implementation. */
 struct Curl_cftype {
   const char *name;                       /* name of the filter type */
-  long flags;                             /* flags of filter type */
+  int flags;                              /* flags of filter type */
+  int log_level;                          /* log level for such filters */
   Curl_cft_destroy_this *destroy;         /* destroy resources of this cf */
-  Curl_cft_setup *setup;                  /* setup for a connection */
   Curl_cft_connect *connect;              /* establish connection */
   Curl_cft_close *close;                  /* close conn */
   Curl_cft_get_host *get_host;            /* host filter talks to */
@@ -119,8 +174,10 @@ struct Curl_cftype {
   Curl_cft_data_pending *has_data_pending;/* conn has data pending */
   Curl_cft_send *do_send;                 /* send data */
   Curl_cft_recv *do_recv;                 /* receive data */
-  Curl_cft_attach_data *attach_data;      /* data is being handled here */
-  Curl_cft_detach_data *detach_data;      /* data is no longer handled here */
+  Curl_cft_cntrl *cntrl;                  /* events/control */
+  Curl_cft_conn_is_alive *is_alive;       /* FALSE if conn is dead, Jim! */
+  Curl_cft_conn_keep_alive *keep_alive;   /* try to keep it alive */
+  Curl_cft_query *query;                  /* query filter chain */
 };
 
 /* A connection filter instance, e.g. registered at a connection */
@@ -129,7 +186,7 @@ struct Curl_cfilter {
   struct Curl_cfilter *next;     /* next filter in chain */
   void *ctx;                     /* filter type specific settings */
   struct connectdata *conn;      /* the connection this filter belongs to */
-  int sockindex;                 /* TODO: like to get rid off this */
+  int sockindex;                 /* the index the filter is installed at */
   BIT(connected);                /* != 0 iff this filter is connected */
 };
 
@@ -139,9 +196,6 @@ void Curl_cf_def_destroy_this(struct Curl_cfilter *cf,
 
 /* Default implementations for the type functions, implementing pass-through
  * the filter chain. */
-CURLcode Curl_cf_def_setup(struct Curl_cfilter *cf,
-                           struct Curl_easy *data,
-                           const struct Curl_dns_entry *remotehost);
 void     Curl_cf_def_close(struct Curl_cfilter *cf, struct Curl_easy *data);
 CURLcode Curl_cf_def_connect(struct Curl_cfilter *cf,
                              struct Curl_easy *data,
@@ -158,16 +212,22 @@ ssize_t  Curl_cf_def_send(struct Curl_cfilter *cf, struct Curl_easy *data,
                           const void *buf, size_t len, CURLcode *err);
 ssize_t  Curl_cf_def_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
                           char *buf, size_t len, CURLcode *err);
-void     Curl_cf_def_attach_data(struct Curl_cfilter *cf,
-                                 struct Curl_easy *data);
-void     Curl_cf_def_detach_data(struct Curl_cfilter *cf,
-                                 struct Curl_easy *data);
+CURLcode Curl_cf_def_cntrl(struct Curl_cfilter *cf,
+                                struct Curl_easy *data,
+                                int event, int arg1, void *arg2);
+bool     Curl_cf_def_conn_is_alive(struct Curl_cfilter *cf,
+                                   struct Curl_easy *data);
+CURLcode Curl_cf_def_conn_keep_alive(struct Curl_cfilter *cf,
+                                     struct Curl_easy *data);
+CURLcode Curl_cf_def_query(struct Curl_cfilter *cf,
+                           struct Curl_easy *data,
+                           int query, int *pres1, void *pres2);
 
 /**
  * Create a new filter instance, unattached to the filter chain.
  * Use Curl_conn_cf_add() to add it to the chain.
  * @param pcf  on success holds the created instance
- * @parm cft   the filter type
+ * @param cft   the filter type
  * @param ctx  the type specific context to use
  */
 CURLcode Curl_cf_create(struct Curl_cfilter **pcf,
@@ -176,7 +236,7 @@ CURLcode Curl_cf_create(struct Curl_cfilter **pcf,
 
 /**
  * Add a filter instance to the `sockindex` filter chain at connection
- * `data->conn`. The filter must not already be attached. It is inserted at
+ * `conn`. The filter must not already be attached. It is inserted at
  * the start of the chain (top).
  */
 void Curl_conn_cf_add(struct Curl_easy *data,
@@ -185,11 +245,11 @@ void Curl_conn_cf_add(struct Curl_easy *data,
                       struct Curl_cfilter *cf);
 
 /**
- * Remove and destroy all filters at chain `sockindex` on connection `conn`.
+ * Insert a filter (chain) after `cf_at`.
+ * `cf_new` must not already be attached.
  */
-void Curl_conn_cf_discard_all(struct Curl_easy *data,
-                              struct connectdata *conn,
-                              int sockindex);
+void Curl_conn_cf_insert_after(struct Curl_cfilter *cf_at,
+                               struct Curl_cfilter *cf_new);
 
 /**
  * Discard, e.g. remove and destroy a specific filter instance.
@@ -198,29 +258,51 @@ void Curl_conn_cf_discard_all(struct Curl_easy *data,
  */
 void Curl_conn_cf_discard(struct Curl_cfilter *cf, struct Curl_easy *data);
 
+/**
+ * Discard all cfilters starting with `*pcf` and clearing it afterwards.
+ */
+void Curl_conn_cf_discard_chain(struct Curl_cfilter **pcf,
+                                struct Curl_easy *data);
+
+/**
+ * Remove and destroy all filters at chain `sockindex` on connection `conn`.
+ */
+void Curl_conn_cf_discard_all(struct Curl_easy *data,
+                              struct connectdata *conn,
+                              int sockindex);
+
 
+CURLcode Curl_conn_cf_connect(struct Curl_cfilter *cf,
+                              struct Curl_easy *data,
+                              bool blocking, bool *done);
+void Curl_conn_cf_close(struct Curl_cfilter *cf, struct Curl_easy *data);
+int Curl_conn_cf_get_select_socks(struct Curl_cfilter *cf,
+                                  struct Curl_easy *data,
+                                  curl_socket_t *socks);
+bool Curl_conn_cf_data_pending(struct Curl_cfilter *cf,
+                               const struct Curl_easy *data);
 ssize_t Curl_conn_cf_send(struct Curl_cfilter *cf, struct Curl_easy *data,
                           const void *buf, size_t len, CURLcode *err);
 ssize_t Curl_conn_cf_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
                           char *buf, size_t len, CURLcode *err);
+CURLcode Curl_conn_cf_cntrl(struct Curl_cfilter *cf,
+                            struct Curl_easy *data,
+                            bool ignore_result,
+                            int event, int arg1, void *arg2);
+
+/**
+ * Get the socket used by the filter chain starting at `cf`.
+ * Returns CURL_SOCKET_BAD if not available.
+ */
+curl_socket_t Curl_conn_cf_get_socket(struct Curl_cfilter *cf,
+                                      struct Curl_easy *data);
+
 
 #define CURL_CF_SSL_DEFAULT  -1
 #define CURL_CF_SSL_DISABLE  0
 #define CURL_CF_SSL_ENABLE   1
 
 /**
- * Setup the filter chain at `sockindex` in connection `conn`, invoking
- * the instance `setup(remotehost)` methods. If no filter chain is
- * installed yet, inspects the configuration in `data` to install a
- * suitable filter chain.
- */
-CURLcode Curl_conn_setup(struct Curl_easy *data,
-                         struct connectdata *conn,
-                         int sockindex,
-                         const struct Curl_dns_entry *remotehost,
-                         int ssl_mode);
-
-/**
  * Bring the filter chain at `sockindex` for connection `data->conn` into
  * connected state. Which will set `*done` to TRUE.
  * This can be called on an already connected chain with no side effects.
@@ -248,7 +330,12 @@ bool Curl_conn_is_ip_connected(struct Curl_easy *data, int sockindex);
  * (or will be once connected). This will return FALSE, if SSL
  * is only used in proxying and not for the tunnel itself.
  */
-bool Curl_conn_is_ssl(struct Curl_easy *data, int sockindex);
+bool Curl_conn_is_ssl(struct connectdata *conn, int sockindex);
+
+/**
+ * Connection provides multiplexing of easy handles at `socketindex`.
+ */
+bool Curl_conn_is_multiplex(struct connectdata *conn, int sockindex);
 
 /**
  * Close the filter chain at `sockindex` for connection `data->conn`.
@@ -264,6 +351,12 @@ bool Curl_conn_data_pending(struct Curl_easy *data,
                             int sockindex);
 
 /**
+ * Return the socket used on data's connection for the index.
+ * Returns CURL_SOCKET_BAD if not available.
+ */
+curl_socket_t Curl_conn_get_socket(struct Curl_easy *data, int sockindex);
+
+/**
  * Get any select fd flags and the socket filters at chain `sockindex`
  * at connection `conn` might be waiting for.
  */
@@ -289,13 +382,12 @@ ssize_t Curl_conn_send(struct Curl_easy *data, int sockindex,
                        const void *buf, size_t len, CURLcode *code);
 
 /**
- * The easy handle `data` is being attached (served) by connection `conn`.
- * All filters are informed to adapt to handling `data`.
- * Note: there may be several `data` attached to a connection at the same
- * time.
+ * The easy handle `data` is being attached to `conn`. This does
+ * not mean that data will actually do a transfer. Attachment is
+ * also used for temporary actions on the connection.
  */
-void Curl_conn_attach_data(struct connectdata *conn,
-                           struct Curl_easy *data);
+void Curl_conn_ev_data_attach(struct connectdata *conn,
+                              struct Curl_easy *data);
 
 /**
  * The easy handle `data` is being detached (no longer served)
@@ -304,12 +396,141 @@ void Curl_conn_attach_data(struct connectdata *conn,
  * Note: there may be several `data` attached to a connection at the same
  * time.
  */
-void Curl_conn_detach_data(struct connectdata *conn,
-                           struct Curl_easy *data);
+void Curl_conn_ev_data_detach(struct connectdata *conn,
+                              struct Curl_easy *data);
+
+/**
+ * Notify connection filters that they need to setup data for
+ * a transfer.
+ */
+CURLcode Curl_conn_ev_data_setup(struct Curl_easy *data);
+
+/**
+ * Notify connection filters that now would be a good time to
+ * perform any idle, e.g. time related, actions.
+ */
+CURLcode Curl_conn_ev_data_idle(struct Curl_easy *data);
+
+/**
+ * Notify connection filters that the transfer represented by `data`
+ * is donw with sending data (e.g. has uploaded everything).
+ */
+void Curl_conn_ev_data_done_send(struct Curl_easy *data);
+
+/**
+ * Notify connection filters that the transfer represented by `data`
+ * is finished - eventually premature, e.g. before being complete.
+ */
+void Curl_conn_ev_data_done(struct Curl_easy *data, bool premature);
+
+/**
+ * Notify connection filters that the transfer of data is paused/unpaused.
+ */
+CURLcode Curl_conn_ev_data_pause(struct Curl_easy *data, bool do_pause);
+
+/**
+ * Inform connection filters to update their info in `conn`.
+ */
+void Curl_conn_ev_update_info(struct Curl_easy *data,
+                              struct connectdata *conn);
+
+/**
+ * Inform connection filters to report statistics.
+ */
+void Curl_conn_ev_report_stats(struct Curl_easy *data,
+                               struct connectdata *conn);
+
+/**
+ * Check if FIRSTSOCKET's cfilter chain deems connection alive.
+ */
+bool Curl_conn_is_alive(struct Curl_easy *data, struct connectdata *conn);
+
+/**
+ * Try to upkeep the connection filters at sockindex.
+ */
+CURLcode Curl_conn_keep_alive(struct Curl_easy *data,
+                              struct connectdata *conn,
+                              int sockindex);
 
 void Curl_conn_get_host(struct Curl_easy *data, int sockindex,
                         const char **phost, const char **pdisplay_host,
                         int *pport);
 
+/**
+ * Get the maximum number of parallel transfers the connection
+ * expects to be able to handle at `sockindex`.
+ */
+size_t Curl_conn_get_max_concurrent(struct Curl_easy *data,
+                                    struct connectdata *conn,
+                                    int sockindex);
+
+
+/**
+ * Types and macros used to keep the current easy handle in filter calls,
+ * allowing for nested invocations. See #10336.
+ *
+ * `cf_call_data` is intended to be a member of the cfilter's `ctx` type.
+ * A filter defines the macro `CF_CTX_CALL_DATA` to give access to that.
+ *
+ * With all values 0, the default, this indicates that there is no cfilter
+ * call with `data` ongoing.
+ * Macro `CF_DATA_SAVE` preserves the current `cf_call_data` in a local
+ * variable and sets the `data` given, incrementing the `depth` counter.
+ *
+ * Macro `CF_DATA_RESTORE` restores the old values from the local variable,
+ * while checking that `depth` values are as expected (debug build), catching
+ * cases where a "lower" RESTORE was not called.
+ *
+ * Finally, macro `CF_DATA_CURRENT` gives the easy handle of the current
+ * invocation.
+ */
+struct cf_call_data {
+  struct Curl_easy *data;
+#ifdef DEBUGBUILD
+  int depth;
+#endif
+};
+
+/**
+ * define to access the `struct cf_call_data for a cfilter. Normally
+ * a member in the cfilter's `ctx`.
+ *
+ * #define CF_CTX_CALL_DATA(cf)   -> struct cf_call_data instance
+*/
+
+#ifdef DEBUGBUILD
+
+#define CF_DATA_SAVE(save, cf, data) \
+  do { \
+    (save) = CF_CTX_CALL_DATA(cf); \
+    DEBUGASSERT((save).data == NULL || (save).depth > 0); \
+    CF_CTX_CALL_DATA(cf).depth++;  \
+    CF_CTX_CALL_DATA(cf).data = (struct Curl_easy *)data; \
+  } while(0)
+
+#define CF_DATA_RESTORE(cf, save) \
+  do { \
+    DEBUGASSERT(CF_CTX_CALL_DATA(cf).depth == (save).depth + 1); \
+    DEBUGASSERT((save).data == NULL || (save).depth > 0); \
+    CF_CTX_CALL_DATA(cf) = (save); \
+  } while(0)
+
+#else /* DEBUGBUILD */
+
+#define CF_DATA_SAVE(save, cf, data) \
+  do { \
+    (save) = CF_CTX_CALL_DATA(cf); \
+    CF_CTX_CALL_DATA(cf).data = (struct Curl_easy *)data; \
+  } while(0)
+
+#define CF_DATA_RESTORE(cf, save) \
+  do { \
+    CF_CTX_CALL_DATA(cf) = (save); \
+  } while(0)
+
+#endif /* !DEBUGBUILD */
+
+#define CF_DATA_CURRENT(cf) \
+  ((cf)? (CF_CTX_CALL_DATA(cf).data) : NULL)
 
 #endif /* HEADER_CURL_CFILTERS_H */
diff --git a/lib/conncache.c b/lib/conncache.c
index a557ac6..c21b96c 100644
--- a/lib/conncache.c
+++ b/lib/conncache.c
@@ -5,8 +5,8 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2012 - 2016, Linus Nielsen Feltzing, <linus@haxx.se>
- * Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Linus Nielsen Feltzing, <linus@haxx.se>
+ * 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
diff --git a/lib/conncache.h b/lib/conncache.h
index 94664bc..959767d 100644
--- a/lib/conncache.h
+++ b/lib/conncache.h
@@ -7,8 +7,8 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2015 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
- * Copyright (C) 2012 - 2014, Linus Nielsen Feltzing, <linus@haxx.se>
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Linus Nielsen Feltzing, <linus@haxx.se>
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/lib/connect.c b/lib/connect.c
index af04138..993a7f9 100644
--- a/lib/connect.c
+++ b/lib/connect.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -59,106 +59,30 @@
 #include "strerror.h"
 #include "cfilters.h"
 #include "connect.h"
+#include "cf-http.h"
+#include "cf-socket.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 "vtls/vtls.h" /* for Curl_ssl_check_cxn() */
+#include "vtls/vtls.h" /* for vtsl cfilters */
 #include "progress.h"
 #include "warnless.h"
 #include "conncache.h"
 #include "multihandle.h"
 #include "share.h"
 #include "version_win32.h"
-#include "quic.h"
+#include "vquic/vquic.h" /* for quic cfilters */
+#include "http_proxy.h"
+#include "socks.h"
 
 /* The last 3 #include files should be in this order */
 #include "curl_printf.h"
 #include "curl_memory.h"
 #include "memdebug.h"
 
-static bool verifyconnect(curl_socket_t sockfd, int *error);
-
-#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
-  }
-}
-
-static CURLcode
-singleipconnect(struct Curl_easy *data,
-                struct connectdata *conn,
-                const struct Curl_addrinfo *ai, /* start connecting to this */
-                int tempindex);          /* 0 or 1 among the temp ones */
 
 /*
  * Curl_timeleft() returns the amount of milliseconds left allowed for the
@@ -230,387 +154,6 @@ timediff_t Curl_timeleft(struct Curl_easy *data,
   return timeout_ms;
 }
 
-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, 0, 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;
-}
-
-/* update tempaddr[tempindex] (to the next entry), makes sure to stick
-   to the correct family */
-static struct Curl_addrinfo *ainext(struct connectdata *conn,
-                                    int tempindex,
-                                    bool next) /* use next entry? */
-{
-  struct Curl_addrinfo *ai = conn->tempaddr[tempindex];
-  if(ai && next)
-    ai = ai->ai_next;
-  while(ai && (ai->ai_family != conn->tempfamily[tempindex]))
-    ai = ai->ai_next;
-  conn->tempaddr[tempindex] = ai;
-  return ai;
-}
-
-/* Used within the multi interface. Try next IP address, returns error if no
-   more address exists or error */
-static CURLcode trynextip(struct Curl_easy *data,
-                          struct connectdata *conn,
-                          int sockindex,
-                          int tempindex)
-{
-  CURLcode result = CURLE_COULDNT_CONNECT;
-
-  /* First clean up after the failed socket.
-     Don't close it yet to ensure that the next IP's socket gets a different
-     file descriptor, which can prevent bugs when the curl_multi_socket_action
-     interface is used with certain select() replacements such as kqueue. */
-  curl_socket_t fd_to_close = conn->tempsock[tempindex];
-  conn->tempsock[tempindex] = CURL_SOCKET_BAD;
-
-  if(sockindex == FIRSTSOCKET) {
-    struct Curl_addrinfo *ai = conn->tempaddr[tempindex];
-
-    while(ai) {
-      result = singleipconnect(data, conn, ai, tempindex);
-      if(result == CURLE_COULDNT_CONNECT) {
-        ai = ainext(conn, tempindex, TRUE);
-        continue;
-      }
-      break;
-    }
-  }
-
-  if(fd_to_close != CURL_SOCKET_BAD)
-    Curl_closesocket(data, conn, fd_to_close);
-
-  return result;
-}
-
 /* Copies connection info into the transfer handle to make it available when
    the transfer handle is no longer associated with the connection. */
 void Curl_persistconninfo(struct Curl_easy *data, struct connectdata *conn,
@@ -629,6 +172,28 @@ void Curl_persistconninfo(struct Curl_easy *data, struct connectdata *conn,
   data->info.conn_local_port = local_port;
 }
 
+static const struct Curl_addrinfo *
+addr_first_match(const struct Curl_addrinfo *addr, int family)
+{
+  while(addr) {
+    if(addr->ai_family == family)
+      return addr;
+    addr = addr->ai_next;
+  }
+  return NULL;
+}
+
+static const struct Curl_addrinfo *
+addr_next_match(const struct Curl_addrinfo *addr, int family)
+{
+  while(addr && addr->ai_next) {
+    addr = addr->ai_next;
+    if(addr->ai_family == family)
+      return addr;
+  }
+  return NULL;
+}
+
 /* retrieves ip address and port from a sockaddr structure.
    note it calls Curl_inet_ntop which sets errno on fail, not SOCKERRNO. */
 bool Curl_addr2string(struct sockaddr *sa, curl_socklen_t salen,
@@ -686,623 +251,493 @@ bool Curl_addr2string(struct sockaddr *sa, curl_socklen_t salen,
   return FALSE;
 }
 
-/* retrieves the start/end point information of a socket of an established
-   connection */
-void Curl_conninfo_remote(struct Curl_easy *data,
-                          struct connectdata *conn, curl_socket_t sockfd)
-{
-#ifdef HAVE_GETPEERNAME
-  char buffer[STRERROR_LEN];
-  struct Curl_sockaddr_storage ssrem;
-  curl_socklen_t plen;
-  int port;
-  plen = sizeof(struct Curl_sockaddr_storage);
-  memset(&ssrem, 0, sizeof(ssrem));
-  if(getpeername(sockfd, (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,
-                       conn->primary_ip, &port)) {
-    failf(data, "ssrem inet_ntop() failed with errno %d: %s",
-          errno, Curl_strerror(errno, buffer, sizeof(buffer)));
-    return;
-  }
-#else
-  (void)data;
-  (void)conn;
-  (void)sockfd;
-#endif
-}
+struct connfind {
+  long id_tofind;
+  struct connectdata *found;
+};
 
-/* retrieves the start/end point information of a socket of an established
-   connection */
-void Curl_conninfo_local(struct Curl_easy *data, curl_socket_t sockfd,
-                         char *local_ip, int *local_port)
+static int conn_is_conn(struct Curl_easy *data,
+                        struct connectdata *conn, void *param)
 {
-#ifdef HAVE_GETSOCKNAME
-  char buffer[STRERROR_LEN];
-  struct Curl_sockaddr_storage ssloc;
-  curl_socklen_t slen;
-  slen = sizeof(struct Curl_sockaddr_storage);
-  memset(&ssloc, 0, sizeof(ssloc));
-  if(getsockname(sockfd, (struct sockaddr*) &ssloc, &slen)) {
-    int error = SOCKERRNO;
-    failf(data, "getsockname() failed with errno %d: %s",
-          error, Curl_strerror(error, buffer, sizeof(buffer)));
-    return;
-  }
-  if(!Curl_addr2string((struct sockaddr*)&ssloc, slen,
-                       local_ip, local_port)) {
-    failf(data, "ssloc inet_ntop() failed with errno %d: %s",
-          errno, Curl_strerror(errno, buffer, sizeof(buffer)));
-    return;
-  }
-#else
+  struct connfind *f = (struct connfind *)param;
   (void)data;
-  (void)sockfd;
-  (void)local_ip;
-  (void)local_port;
-#endif
+  if(conn->connection_id == f->id_tofind) {
+    f->found = conn;
+    return 1;
+  }
+  return 0;
 }
 
-/* retrieves the start/end point information of a socket of an established
-   connection */
-void Curl_updateconninfo(struct Curl_easy *data, struct connectdata *conn,
-                         curl_socket_t sockfd)
+/*
+ * Used to extract socket and connectdata struct for the most recent
+ * transfer on the given Curl_easy.
+ *
+ * The returned socket will be CURL_SOCKET_BAD in case of failure!
+ */
+curl_socket_t Curl_getconnectinfo(struct Curl_easy *data,
+                                  struct connectdata **connp)
 {
-  /* 'local_ip' and 'local_port' get filled with local's numerical
-     ip address and port number whenever an outgoing connection is
-     **established** from the primary socket to a remote address. */
-  char local_ip[MAX_IPADR_LEN] = "";
-  int local_port = -1;
-
-  if(!conn->bits.reuse &&
-     (conn->transport != TRNSPRT_TCP || !conn->bits.tcp_fastopen))
-    Curl_conninfo_remote(data, conn, sockfd);
-  Curl_conninfo_local(data, sockfd, local_ip, &local_port);
-
-  /* persist connection info in session handle */
-  Curl_persistconninfo(data, conn, local_ip, local_port);
+  DEBUGASSERT(data);
+
+  /* this works for an easy handle:
+   * - that has been used for curl_easy_perform()
+   * - that is associated with a multi handle, and whose connection
+   *   was detached with CURLOPT_CONNECT_ONLY
+   */
+  if((data->state.lastconnect_id != -1) && (data->multi_easy || data->multi)) {
+    struct connectdata *c;
+    struct connfind find;
+    find.id_tofind = data->state.lastconnect_id;
+    find.found = NULL;
+
+    Curl_conncache_foreach(data,
+                           data->share && (data->share->specifier
+                           & (1<< CURL_LOCK_DATA_CONNECT))?
+                           &data->share->conn_cache:
+                           data->multi_easy?
+                           &data->multi_easy->conn_cache:
+                           &data->multi->conn_cache, &find, conn_is_conn);
+
+    if(!find.found) {
+      data->state.lastconnect_id = -1;
+      return CURL_SOCKET_BAD;
+    }
+
+    c = find.found;
+    if(connp)
+      /* only store this if the caller cares for it */
+      *connp = c;
+    return c->sock[FIRSTSOCKET];
+  }
+  return CURL_SOCKET_BAD;
 }
 
 /*
- * post_connect() is called after a successful connect to the peer
+ * Curl_conncontrol() marks streams or connection for closure.
  */
-static void post_connect(struct Curl_easy *data,
-                       struct connectdata *conn,
-                       int sockindex)
+void Curl_conncontrol(struct connectdata *conn,
+                      int ctrl /* see defines in header */
+#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
+                      , const char *reason
+#endif
+  )
 {
-  Curl_updateconninfo(data, conn, conn->sock[sockindex]);
-  Curl_verboseconnect(data, conn);
-  data->info.numconnects++; /* to track the number of connections made */
+  /* close if a connection, or a stream that isn't multiplexed. */
+  /* This function will be called both before and after this connection is
+     associated with a transfer. */
+  bool closeit, is_multiplex;
+  DEBUGASSERT(conn);
+#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
+  (void)reason; /* useful for debugging */
+#endif
+  is_multiplex = Curl_conn_is_multiplex(conn, FIRSTSOCKET);
+  closeit = (ctrl == CONNCTRL_CONNECTION) ||
+    ((ctrl == CONNCTRL_STREAM) && !is_multiplex);
+  if((ctrl == CONNCTRL_STREAM) && is_multiplex)
+    ;  /* stream signal on multiplex conn never affects close state */
+  else if((bit)closeit != conn->bits.close) {
+    conn->bits.close = closeit; /* the only place in the source code that
+                                   should assign this bit */
+  }
 }
 
-/*
- * is_connected() checks if the socket has connected.
+/**
+ * job walking the matching addr infos, creating a sub-cfilter with the
+ * provided method `cf_create` and running setup/connect on it.
  */
-static CURLcode is_connected(struct Curl_easy *data,
-                             struct connectdata *conn,
-                             int sockindex,
-                             bool *connected)
-{
-  CURLcode result = CURLE_OK;
-  timediff_t allow;
-  int error = 0;
-  struct curltime now;
-  int rc = 0;
-  int i;
+struct eyeballer {
+  const char *name;
+  const struct Curl_addrinfo *addr;  /* List of addresses to try, not owned */
+  int ai_family;                     /* matching address family only */
+  cf_ip_connect_create *cf_create;   /* for creating cf */
+  struct Curl_cfilter *cf;           /* current sub-cfilter connecting */
+  struct eyeballer *primary;         /* eyeballer this one is backup for */
+  timediff_t delay_ms;               /* delay until start */
+  struct curltime started;           /* start of current attempt */
+  timediff_t timeoutms;              /* timeout for current attempt */
+  expire_id timeout_id;              /* ID for Curl_expire() */
+  CURLcode result;
+  int error;
+  BIT(has_started);                  /* attempts have started */
+  BIT(is_done);                      /* out of addresses/time */
+  BIT(connected);                    /* cf has connected */
+};
 
-  DEBUGASSERT(sockindex >= FIRSTSOCKET && sockindex <= SECONDARYSOCKET);
 
-  *connected = FALSE; /* a very negative world view is best */
+typedef enum {
+  SCFST_INIT,
+  SCFST_WAITING,
+  SCFST_DONE
+} cf_connect_state;
 
-  now = Curl_now();
+struct cf_he_ctx {
+  int transport;
+  cf_ip_connect_create *cf_create;
+  const struct Curl_dns_entry *remotehost;
+  cf_connect_state state;
+  struct eyeballer *baller[2];
+  struct eyeballer *winner;
+  struct curltime started;
+};
 
-  /* Check if any of the conn->tempsock we use for establishing connections
-   * succeeded and, if so, close any ongoing other ones.
-   * Transfer the successful conn->tempsock to conn->sock[sockindex]
-   * and set conn->tempsock to CURL_SOCKET_BAD.
-   * If transport is QUIC, we need to shutdown the ongoing 'other'
-   * connect attempts in a QUIC appropriate way. */
-  for(i = 0; i<2; i++) {
-    const int other = i ^ 1;
-    if(conn->tempsock[i] == CURL_SOCKET_BAD)
-      continue;
-    error = 0;
-#ifdef ENABLE_QUIC
-    if(conn->transport == TRNSPRT_QUIC) {
-      result = Curl_quic_is_connected(data, conn, i, connected);
-      if(!result && *connected) {
-        /* use this socket from now on */
-        conn->sock[sockindex] = conn->tempsock[i];
-        conn->ip_addr = conn->tempaddr[i];
-        conn->tempsock[i] = CURL_SOCKET_BAD;
-        post_connect(data, conn, sockindex);
-        connkeep(conn, "HTTP/3 default");
-        if(conn->tempsock[other] != CURL_SOCKET_BAD)
-          Curl_quic_disconnect(data, conn, other);
-        return CURLE_OK;
-      }
-      /* When a QUIC connect attempt fails, the better error explanation is in
-         'result' and not in errno */
-      if(result) {
-        conn->tempsock[i] = CURL_SOCKET_BAD;
-        error = SOCKERRNO;
-      }
-    }
-    else
-#endif
-    {
-#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(conn->tempsock[i], NULL);
-#endif
+static CURLcode eyeballer_new(struct eyeballer **pballer,
+                              cf_ip_connect_create *cf_create,
+                              const struct Curl_addrinfo *addr,
+                              int ai_family,
+                              struct eyeballer *primary,
+                              timediff_t delay_ms,
+                              timediff_t timeout_ms,
+                              expire_id timeout_id)
+{
+  struct eyeballer *baller;
 
-      /* check socket for connect */
-      rc = SOCKET_WRITABLE(conn->tempsock[i], 0);
-    }
+  *pballer = NULL;
+  baller = calloc(1, sizeof(*baller) + 1000);
+  if(!baller)
+    return CURLE_OUT_OF_MEMORY;
 
-    if(rc == 0) { /* no connection yet */
-      if(Curl_timediff(now, conn->connecttime) >=
-         conn->timeoutms_per_addr[i]) {
-        infof(data, "After %" CURL_FORMAT_TIMEDIFF_T
-              "ms connect time, move on!", conn->timeoutms_per_addr[i]);
-        error = ETIMEDOUT;
-      }
-
-      /* should we try another protocol family? */
-      if(i == 0 && !conn->bits.parallel_connect &&
-         (Curl_timediff(now, conn->connecttime) >=
-          data->set.happy_eyeballs_timeout)) {
-        conn->bits.parallel_connect = TRUE; /* starting now */
-        trynextip(data, conn, sockindex, 1);
-      }
-    }
-    else if(rc == CURL_CSELECT_OUT || conn->bits.tcp_fastopen) {
-      if(verifyconnect(conn->tempsock[i], &error)) {
-        /* we are connected with TCP, awesome! */
-
-        /* use this socket from now on */
-        conn->sock[sockindex] = conn->tempsock[i];
-        conn->ip_addr = conn->tempaddr[i];
-        conn->tempsock[i] = CURL_SOCKET_BAD;
+  baller->name = ((ai_family == AF_INET)? "ipv4" : (
 #ifdef ENABLE_IPV6
-        conn->bits.ipv6 = (conn->ip_addr->ai_family == AF_INET6)?TRUE:FALSE;
-#endif
-
-        /* close the other socket, if open */
-        if(conn->tempsock[other] != CURL_SOCKET_BAD) {
-          Curl_closesocket(data, conn, conn->tempsock[other]);
-          conn->tempsock[other] = CURL_SOCKET_BAD;
-        }
-
-        *connected = TRUE;
-        return CURLE_OK;
-      }
-    }
-    else if(rc & CURL_CSELECT_ERR) {
-      (void)verifyconnect(conn->tempsock[i], &error);
-    }
-
-    /*
-     * The connection failed here, we should attempt to connect to the "next
-     * address" for the given host. But first remember the latest error.
-     */
-    if(error) {
-      data->state.os_errno = error;
-      SET_SOCKERRNO(error);
-      if(conn->tempaddr[i]) {
-        CURLcode status;
-#ifndef CURL_DISABLE_VERBOSE_STRINGS
-        char ipaddress[MAX_IPADR_LEN];
-        char buffer[STRERROR_LEN];
-        Curl_printable_address(conn->tempaddr[i], ipaddress,
-                               sizeof(ipaddress));
-#ifdef ENABLE_QUIC
-        if(conn->transport == TRNSPRT_QUIC) {
-          infof(data, "connect to %s port %u failed: %s",
-                ipaddress, conn->port, curl_easy_strerror(result));
-        }
-        else
-#endif
-        infof(data, "connect to %s port %u failed: %s",
-              ipaddress, conn->port,
-              Curl_strerror(error, buffer, sizeof(buffer)));
-#endif
-
-        allow = Curl_timeleft(data, &now, TRUE);
-        conn->timeoutms_per_addr[i] = conn->tempaddr[i]->ai_next == NULL ?
-          allow : allow / 2;
-        ainext(conn, i, TRUE);
-        status = trynextip(data, conn, sockindex, i);
-        if((status != CURLE_COULDNT_CONNECT) ||
-           conn->tempsock[other] == CURL_SOCKET_BAD) {
-          /* the last attempt failed and no other sockets remain open */
-          if(!result)
-            result = status;
-        }
-      }
-    }
-  }
-
-  /*
-   * Now that we've checked whether we are connected, check whether we've
-   * already timed out.
-   *
-   * First figure out how long time we have left to connect */
-
-  allow = Curl_timeleft(data, &now, TRUE);
-
-  if(allow < 0) {
-    /* time-out, bail out, go home */
-    failf(data, "Connection timeout after %ld ms",
-          Curl_timediff(now, data->progress.t_startsingle));
-    return CURLE_OPERATION_TIMEDOUT;
-  }
-
-  if(result &&
-     (conn->tempsock[0] == CURL_SOCKET_BAD) &&
-     (conn->tempsock[1] == CURL_SOCKET_BAD)) {
-    /* no more addresses to try */
-    const char *hostname;
-    CURLcode failreason = result;
-
-    /* if the first address family runs out of addresses to try before the
-       happy eyeball timeout, go ahead and try the next family now */
-    result = trynextip(data, conn, sockindex, 1);
-    if(!result)
-      return result;
-
-    result = failreason;
-
-#ifndef CURL_DISABLE_PROXY
-    if(conn->bits.socksproxy)
-      hostname = conn->socks_proxy.host.name;
-    else if(conn->bits.httpproxy)
-      hostname = conn->http_proxy.host.name;
-    else
-#endif
-      if(conn->bits.conn_to_host)
-        hostname = conn->conn_to_host.name;
-    else
-      hostname = conn->host.name;
-
-    failf(data, "Failed to connect to %s port %u after "
-          "%" CURL_FORMAT_TIMEDIFF_T " ms: %s",
-          hostname, conn->port,
-          Curl_timediff(now, data->progress.t_startsingle),
-          curl_easy_strerror(result));
-
-    Curl_quic_disconnect(data, conn, 0);
-    Curl_quic_disconnect(data, conn, 1);
-
-#ifdef WSAETIMEDOUT
-    if(WSAETIMEDOUT == data->state.os_errno)
-      result = CURLE_OPERATION_TIMEDOUT;
-#elif defined(ETIMEDOUT)
-    if(ETIMEDOUT == data->state.os_errno)
-      result = CURLE_OPERATION_TIMEDOUT;
-#endif
-  }
-  else
-    result = CURLE_OK; /* still trying */
-
-  return result;
+                  (ai_family == AF_INET6)? "ipv6" :
+#endif
+                  "ip"));
+  baller->cf_create = cf_create;
+  baller->addr = addr;
+  baller->ai_family = ai_family;
+  baller->primary = primary;
+  baller->delay_ms = delay_ms;
+  baller->timeoutms = addr_next_match(baller->addr, baller->ai_family)?
+                        timeout_ms / 2 : timeout_ms;
+  baller->timeout_id = timeout_id;
+  baller->result = CURLE_COULDNT_CONNECT;
+
+  *pballer = baller;
+  return CURLE_OK;
 }
 
-static void tcpnodelay(struct Curl_easy *data, curl_socket_t sockfd)
+static void baller_close(struct eyeballer *baller,
+                          struct Curl_easy *data)
 {
-#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
+  if(baller && baller->cf) {
+    Curl_conn_cf_discard_chain(&baller->cf, data);
+  }
 }
 
-#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)
+static void baller_free(struct eyeballer *baller,
+                         struct Curl_easy *data)
 {
-  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
+  if(baller) {
+    baller_close(baller, data);
+    free(baller);
   }
 }
-#else
-#define nosigpipe(x,y) Curl_nop_stmt
-#endif
 
-#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)
+static void baller_next_addr(struct eyeballer *baller)
 {
-  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));
+  baller->addr = addr_next_match(baller->addr, baller->ai_family);
 }
-#endif
 
 /*
- * singleipconnect()
+ * Initiate a connect attempt walk.
  *
  * Note that even on connect fail it returns CURLE_OK, but with 'sock' set to
  * CURL_SOCKET_BAD. Other errors will however return proper errors.
- *
- * singleipconnect() connects to the given IP only, and it may return without
- * having connected.
  */
-static CURLcode singleipconnect(struct Curl_easy *data,
-                                struct connectdata *conn,
-                                const struct Curl_addrinfo *ai,
-                                int tempindex)
+static void baller_initiate(struct Curl_cfilter *cf,
+                            struct Curl_easy *data,
+                            struct eyeballer *baller)
 {
-  struct Curl_sockaddr_ex addr;
-  int rc = -1;
-  int error = 0;
-  bool isconnected = FALSE;
-  curl_socket_t sockfd;
+  struct cf_he_ctx *ctx = cf->ctx;
+  struct Curl_cfilter *cf_prev = baller->cf;
+  struct Curl_cfilter *wcf;
   CURLcode result;
-  char ipaddress[MAX_IPADR_LEN];
-  int port;
-  bool is_tcp;
-#ifdef TCP_FASTOPEN_CONNECT
-  int optval = 1;
-#endif
-  const char *ipmsg;
-  char buffer[STRERROR_LEN];
-  curl_socket_t *sockp = &conn->tempsock[tempindex];
-  *sockp = CURL_SOCKET_BAD;
 
-  result = Curl_socket(data, ai, &addr, &sockfd);
+
+  /* Don't close a previous cfilter yet to ensure that the next IP's
+     socket gets a different file descriptor, which can prevent bugs when
+     the curl_multi_socket_action interface is used with certain select()
+     replacements such as kqueue. */
+  result = baller->cf_create(&baller->cf, data, cf->conn, baller->addr,
+                             ctx->transport);
   if(result)
-    return result;
+    goto out;
 
-  /* store remote address and port used in this connection attempt */
-  if(!Curl_addr2string(&addr.sa_addr, addr.addrlen,
-                       ipaddress, &port)) {
-    /* 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)));
-    Curl_closesocket(data, conn, sockfd);
-    return CURLE_OK;
+  /* the new filter might have sub-filters */
+  for(wcf = baller->cf; wcf; wcf = wcf->next) {
+    wcf->conn = cf->conn;
+    wcf->sockindex = cf->sockindex;
   }
-#ifdef ENABLE_IPV6
-  if(addr.family == AF_INET6)
-    ipmsg = "  Trying [%s]:%d...";
-  else
-#endif
-    ipmsg = "  Trying %s:%d...";
-  infof(data, ipmsg, ipaddress, port);
-
-#ifdef ENABLE_IPV6
-  is_tcp = (addr.family == AF_INET || addr.family == AF_INET6) &&
-    addr.socktype == SOCK_STREAM;
-#else
-  is_tcp = (addr.family == AF_INET) && addr.socktype == SOCK_STREAM;
-#endif
-  if(is_tcp && data->set.tcp_nodelay)
-    tcpnodelay(data, sockfd);
 
-  nosigpipe(data, sockfd);
+  if(addr_next_match(baller->addr, baller->ai_family)) {
+    Curl_expire(data, baller->timeoutms, baller->timeout_id);
+  }
 
-  Curl_sndbufset(sockfd);
+out:
+  if(result) {
+    DEBUGF(LOG_CF(data, cf, "%s failed", baller->name));
+    baller_close(baller, data);
+  }
+  if(cf_prev)
+    Curl_conn_cf_discard_chain(&cf_prev, data);
+  baller->result = result;
+}
 
-  if(is_tcp && data->set.tcp_keepalive)
-    tcpkeepalive(data, sockfd);
+/**
+ * Start a connection attempt on the current baller address.
+ * Will return CURLE_OK on the first address where a socket
+ * could be created and the non-blocking connect started.
+ * Returns error when all remaining addresses have been tried.
+ */
+static CURLcode baller_start(struct Curl_cfilter *cf,
+                             struct Curl_easy *data,
+                             struct eyeballer *baller,
+                             timediff_t timeoutms)
+{
+  baller->error = 0;
+  baller->connected = FALSE;
+  baller->has_started = TRUE;
+
+  while(baller->addr) {
+    baller->started = Curl_now();
+    baller->timeoutms = addr_next_match(baller->addr, baller->ai_family) ?
+                         timeoutms / 2 : timeoutms;
+    baller_initiate(cf, data, baller);
+    if(!baller->result)
+      break;
+    baller_next_addr(baller);
+  }
+  if(!baller->addr) {
+    baller->is_done = TRUE;
+  }
+  return baller->result;
+}
 
-  if(data->set.fsockopt) {
-    /* activate callback for setting socket options */
-    Curl_set_in_callback(data, true);
-    error = data->set.fsockopt(data->set.sockopt_client,
-                               sockfd,
-                               CURLSOCKTYPE_IPCXN);
-    Curl_set_in_callback(data, false);
 
-    if(error == CURL_SOCKOPT_ALREADY_CONNECTED)
-      isconnected = TRUE;
-    else if(error) {
-      Curl_closesocket(data, conn, sockfd); /* close the socket and bail out */
-      return CURLE_ABORTED_BY_CALLBACK;
-    }
+/* Used within the multi interface. Try next IP address, returns error if no
+   more address exists or error */
+static CURLcode baller_start_next(struct Curl_cfilter *cf,
+                                  struct Curl_easy *data,
+                                  struct eyeballer *baller,
+                                  timediff_t timeoutms)
+{
+  if(cf->sockindex == FIRSTSOCKET) {
+    baller_next_addr(baller);
+    baller_start(cf, data, baller, timeoutms);
   }
+  else {
+    baller->error = 0;
+    baller->connected = FALSE;
+    baller->has_started = TRUE;
+    baller->is_done = TRUE;
+    baller->result = CURLE_COULDNT_CONNECT;
+  }
+  return baller->result;
+}
 
-  /* possibly bind the local end to an IP, interface or port */
-  if(addr.family == AF_INET
-#ifdef ENABLE_IPV6
-     || addr.family == AF_INET6
+static CURLcode baller_connect(struct Curl_cfilter *cf,
+                               struct Curl_easy *data,
+                               struct eyeballer *baller,
+                               struct curltime *now,
+                               bool *connected)
+{
+  (void)cf;
+  *connected = baller->connected;
+  if(!baller->result &&  !*connected) {
+    /* evaluate again */
+    baller->result = Curl_conn_cf_connect(baller->cf, data, 0, connected);
+
+    if(!baller->result) {
+      if (*connected) {
+        baller->connected = TRUE;
+        baller->is_done = TRUE;
+      }
+      else if(Curl_timediff(*now, baller->started) >= baller->timeoutms) {
+        infof(data, "%s connect timeout after %" CURL_FORMAT_TIMEDIFF_T
+              "ms, move on!", baller->name, baller->timeoutms);
+#if defined(ETIMEDOUT)
+        baller->error = ETIMEDOUT;
 #endif
-    ) {
-    result = bindlocal(data, conn, sockfd, addr.family,
-                       Curl_ipv6_scope(&addr.sa_addr));
-    if(result) {
-      Curl_closesocket(data, conn, sockfd); /* close socket and bail out */
-      if(result == CURLE_UNSUPPORTED_PROTOCOL) {
-        /* The address family is not supported on this interface.
-           We can continue trying addresses */
-        return CURLE_COULDNT_CONNECT;
+        baller->result = CURLE_OPERATION_TIMEDOUT;
       }
-      return result;
     }
   }
+  return baller->result;
+}
 
-  /* set socket non-blocking */
-  (void)curlx_nonblock(sockfd, TRUE);
+/*
+ * is_connected() checks if the socket has connected.
+ */
+static CURLcode is_connected(struct Curl_cfilter *cf,
+                             struct Curl_easy *data,
+                             bool *connected)
+{
+  struct cf_he_ctx *ctx = cf->ctx;
+  struct connectdata *conn = cf->conn;
+  CURLcode result;
+  struct curltime now;
+  size_t i;
+  int ongoing, not_started;
+  const char *hostname;
 
-  conn->connecttime = Curl_now();
-  if(conn->num_addr > 1) {
-    Curl_expire(data, conn->timeoutms_per_addr[0], EXPIRE_DNS_PER_NAME);
-    Curl_expire(data, conn->timeoutms_per_addr[1], EXPIRE_DNS_PER_NAME2);
-  }
+  /* Check if any of the conn->tempsock we use for establishing connections
+   * succeeded and, if so, close any ongoing other ones.
+   * Transfer the successful conn->tempsock to conn->sock[sockindex]
+   * and set conn->tempsock to CURL_SOCKET_BAD.
+   * If transport is QUIC, we need to shutdown the ongoing 'other'
+   * cot ballers in a QUIC appropriate way. */
+evaluate:
+  *connected = FALSE; /* a very negative world view is best */
+  now = Curl_now();
+  ongoing = not_started = 0;
+  for(i = 0; i < sizeof(ctx->baller)/sizeof(ctx->baller[0]); i++) {
+    struct eyeballer *baller = ctx->baller[i];
 
-  /* Connect TCP and QUIC sockets */
-  if(!isconnected && (conn->transport != TRNSPRT_UDP)) {
-    if(conn->bits.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 = &addr.sa_addr;
-        endpoints.sae_dstaddrlen = addr.addrlen;
-
-        rc = connectx(sockfd, &endpoints, SAE_ASSOCID_ANY,
-                      CONNECT_RESUME_ON_READ_WRITE | CONNECT_DATA_IDEMPOTENT,
-                      NULL, 0, NULL, NULL);
+    if(!baller || baller->is_done)
+      continue;
+
+    if(!baller->has_started) {
+      ++not_started;
+      continue;
+    }
+    baller->result = baller_connect(cf, data, baller, &now, connected);
+    DEBUGF(LOG_CF(data, cf, "%s connect -> %d, connected=%d",
+                  baller->name, baller->result, *connected));
+
+    if(!baller->result) {
+      if(*connected) {
+        /* connected, declare the winner */
+        ctx->winner = baller;
+        ctx->baller[i] = NULL;
+        break;
       }
-      else {
-        rc = connect(sockfd, &addr.sa_addr, addr.addrlen);
+      else { /* still waiting */
+        ++ongoing;
       }
-#  else
-      rc = connect(sockfd, &addr.sa_addr, addr.addrlen);
-#  endif /* HAVE_BUILTIN_AVAILABLE */
-#elif defined(TCP_FASTOPEN_CONNECT) /* Linux >= 4.11 */
-      if(setsockopt(sockfd, IPPROTO_TCP, TCP_FASTOPEN_CONNECT,
-                    (void *)&optval, sizeof(optval)) < 0)
-        infof(data, "Failed to enable TCP Fast Open on fd %d", sockfd);
-
-      rc = connect(sockfd, &addr.sa_addr, addr.addrlen);
-#elif defined(MSG_FASTOPEN) /* old Linux */
-      if(conn->given->flags & PROTOPT_SSL)
-        rc = connect(sockfd, &addr.sa_addr, addr.addrlen);
-      else
-        rc = 0; /* Do nothing */
-#endif
     }
-    else {
-      rc = connect(sockfd, &addr.sa_addr, addr.addrlen);
+    else if(!baller->is_done) {
+      /* The baller failed to connect, start its next attempt */
+      if(baller->error) {
+        data->state.os_errno = baller->error;
+        SET_SOCKERRNO(baller->error);
+      }
+      baller_start_next(cf, data, baller, Curl_timeleft(data, &now, TRUE));
+      if(baller->is_done) {
+        DEBUGF(LOG_CF(data, cf, "%s done", baller->name));
+      }
+      else {
+        /* next attempt was started */
+        DEBUGF(LOG_CF(data, cf, "%s trying next", baller->name));
+        ++ongoing;
+      }
     }
+  }
 
-    if(-1 == rc)
-      error = SOCKERRNO;
-#ifdef ENABLE_QUIC
-    else if(conn->transport == TRNSPRT_QUIC) {
-      /* pass in 'sockfd' separately since it hasn't been put into the
-         tempsock array at this point */
-      result = Curl_quic_connect(data, conn, sockfd, tempindex,
-                                 &addr.sa_addr, addr.addrlen);
-      if(result)
-        error = SOCKERRNO;
+  if(ctx->winner) {
+    *connected = TRUE;
+    return CURLE_OK;
+  }
+
+  /* Nothing connected, check the time before we might
+   * start new ballers or return ok. */
+  if((ongoing || not_started) && Curl_timeleft(data, &now, TRUE) < 0) {
+    failf(data, "Connection timeout after %ld ms",
+          Curl_timediff(now, data->progress.t_startsingle));
+    return CURLE_OPERATION_TIMEDOUT;
+  }
+
+  /* Check if we have any waiting ballers to start now. */
+  if(not_started > 0) {
+    int added = 0;
+
+    for(i = 0; i < sizeof(ctx->baller)/sizeof(ctx->baller[0]); i++) {
+      struct eyeballer *baller = ctx->baller[i];
+
+      if(!baller || baller->has_started)
+        continue;
+      /* We start its primary baller has failed to connect or if
+       * its start delay_ms have expired */
+      if((baller->primary && baller->primary->is_done) ||
+          Curl_timediff(now, ctx->started) >= baller->delay_ms) {
+        baller_start(cf, data, baller, Curl_timeleft(data, &now, TRUE));
+        if(baller->is_done) {
+          DEBUGF(LOG_CF(data, cf, "%s done", baller->name));
+        }
+        else {
+          DEBUGF(LOG_CF(data, cf, "%s starting (timeout=%ldms)",
+                        baller->name, baller->timeoutms));
+          ++ongoing;
+          ++added;
+        }
+      }
     }
-#endif
+    if(added > 0)
+      goto evaluate;
   }
-  else {
-    *sockp = sockfd;
+
+  if(ongoing > 0) {
+    /* We are still trying, return for more waiting */
+    *connected = FALSE;
     return CURLE_OK;
   }
 
-  if(-1 == rc) {
-    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
-      result = CURLE_OK;
+  /* all ballers have failed to connect. */
+  DEBUGF(LOG_CF(data, cf, "all eyeballers failed"));
+  result = CURLE_COULDNT_CONNECT;
+  for(i = 0; i < sizeof(ctx->baller)/sizeof(ctx->baller[0]); i++) {
+    struct eyeballer *baller = ctx->baller[i];
+    DEBUGF(LOG_CF(data, cf, "%s assess started=%d, result=%d",
+                  baller?baller->name:NULL,
+                  baller?baller->has_started:0,
+                  baller?baller->result:0));
+    if(baller && baller->has_started && baller->result) {
+      result = baller->result;
       break;
-
-    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 */
-      Curl_closesocket(data, conn, sockfd);
-      result = CURLE_COULDNT_CONNECT;
     }
   }
 
-  if(!result)
-    *sockp = sockfd;
+#ifndef CURL_DISABLE_PROXY
+  if(conn->bits.socksproxy)
+    hostname = conn->socks_proxy.host.name;
+  else if(conn->bits.httpproxy)
+    hostname = conn->http_proxy.host.name;
+  else
+#endif
+    if(conn->bits.conn_to_host)
+      hostname = conn->conn_to_host.name;
+  else
+    hostname = conn->host.name;
+
+  failf(data, "Failed to connect to %s port %u after "
+        "%" CURL_FORMAT_TIMEDIFF_T " ms: %s",
+        hostname, conn->port,
+        Curl_timediff(now, data->progress.t_startsingle),
+        curl_easy_strerror(result));
+
+#ifdef WSAETIMEDOUT
+  if(WSAETIMEDOUT == data->state.os_errno)
+    result = CURLE_OPERATION_TIMEDOUT;
+#elif defined(ETIMEDOUT)
+  if(ETIMEDOUT == data->state.os_errno)
+    result = CURLE_OPERATION_TIMEDOUT;
+#endif
 
   return result;
 }
 
 /*
- * TCP connect to the given host with timeout, proxy or remote doesn't matter.
- * There might be more than one IP address to try out. Fill in the passed
- * pointer with the connected socket.
+ * Connect to the given host with timeout, proxy or remote doesn't matter.
+ * There might be more than one IP address to try out.
  */
-
-CURLcode Curl_connecthost(struct Curl_easy *data,
-                          struct connectdata *conn,  /* context */
-                          const struct Curl_dns_entry *remotehost)
+static CURLcode start_connect(struct Curl_cfilter *cf,
+                              struct Curl_easy *data,
+                              const struct Curl_dns_entry *remotehost)
 {
+  struct cf_he_ctx *ctx = cf->ctx;
+  struct connectdata *conn = cf->conn;
   CURLcode result = CURLE_COULDNT_CONNECT;
-  int i;
+  int ai_family0, ai_family1;
   timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE);
+  const struct Curl_addrinfo *addr0, *addr1;
 
   if(timeout_ms < 0) {
     /* a precaution, no need to continue if time already is up */
@@ -1310,58 +745,76 @@ CURLcode Curl_connecthost(struct Curl_easy *data,
     return CURLE_OPERATION_TIMEDOUT;
   }
 
-  conn->num_addr = Curl_num_addresses(remotehost->addr);
-  conn->tempaddr[0] = conn->tempaddr[1] = remotehost->addr;
-  conn->tempsock[0] = conn->tempsock[1] = CURL_SOCKET_BAD;
-
-  /* Max time for the next connection attempt */
-  conn->timeoutms_per_addr[0] =
-    conn->tempaddr[0]->ai_next == NULL ? timeout_ms : timeout_ms / 2;
-  conn->timeoutms_per_addr[1] =
-    conn->tempaddr[1]->ai_next == NULL ? timeout_ms : timeout_ms / 2;
+  ctx->started = Curl_now();
 
+  /* remotehost->addr is the list of addresses from the resolver, each
+   * with an address family. The list has at least one entry, possibly
+   * many more.
+   * We try at most 2 at a time, until we either get a connection or
+   * run out of addresses to try. Since likelihood of success is tied
+   * to the address family (e.g. IPV6 might not work at all ), we want
+   * the 2 connect attempt ballers to try different families, if possible.
+   *
+   */
   if(conn->ip_version == CURL_IPRESOLVE_WHATEVER) {
     /* any IP version is allowed */
-    conn->tempfamily[0] = conn->tempaddr[0]?
-      conn->tempaddr[0]->ai_family:0;
+    ai_family0 = remotehost->addr?
+      remotehost->addr->ai_family : 0;
 #ifdef ENABLE_IPV6
-    conn->tempfamily[1] = conn->tempfamily[0] == AF_INET6 ?
+    ai_family1 = ai_family0 == AF_INET6 ?
       AF_INET : AF_INET6;
 #else
-    conn->tempfamily[1] = AF_UNSPEC;
+    ai_family1 = AF_UNSPEC;
 #endif
   }
   else {
     /* only one IP version is allowed */
-    conn->tempfamily[0] = (conn->ip_version == CURL_IPRESOLVE_V4) ?
+    ai_family0 = (conn->ip_version == CURL_IPRESOLVE_V4) ?
       AF_INET :
 #ifdef ENABLE_IPV6
       AF_INET6;
 #else
       AF_UNSPEC;
 #endif
-    conn->tempfamily[1] = AF_UNSPEC;
-
-    ainext(conn, 0, FALSE); /* find first address of the right type */
+    ai_family1 = AF_UNSPEC;
   }
 
-  ainext(conn, 1, FALSE); /* assigns conn->tempaddr[1] accordingly */
-
-  DEBUGF(infof(data, "family0 == %s, family1 == %s",
-               conn->tempfamily[0] == AF_INET ? "v4" : "v6",
-               conn->tempfamily[1] == AF_INET ? "v4" : "v6"));
+  /* Get the first address in the list that matches the family,
+   * this might give NULL, if we do not have any matches. */
+  addr0 = addr_first_match(remotehost->addr, ai_family0);
+  addr1 = addr_first_match(remotehost->addr, ai_family1);
+  if(!addr0 && addr1) {
+    /* switch around, so a single baller always uses addr0 */
+    addr0 = addr1;
+    ai_family0 = ai_family1;
+    addr1 = NULL;
+  }
 
-  /* get through the list in family order in case of quick failures */
-  for(i = 0; (i < 2) && result; i++) {
-    while(conn->tempaddr[i]) {
-      result = singleipconnect(data, conn, conn->tempaddr[i], i);
-      if(!result)
-        break;
-      ainext(conn, i, TRUE);
-    }
+  /* We found no address that matches our criteria, we cannot connect */
+  if(!addr0) {
+    return CURLE_COULDNT_CONNECT;
   }
+
+  memset(ctx->baller, 0, sizeof(ctx->baller));
+  result = eyeballer_new(&ctx->baller[0], ctx->cf_create, addr0, ai_family0,
+                          NULL, 0, /* no primary/delay, start now */
+                          timeout_ms,  EXPIRE_DNS_PER_NAME);
   if(result)
     return result;
+  DEBUGF(LOG_CF(data, cf, "created %s (timeout %ldms)",
+                ctx->baller[0]->name, ctx->baller[0]->timeoutms));
+  if(addr1) {
+    /* second one gets a delayed start */
+    result = eyeballer_new(&ctx->baller[1], ctx->cf_create, addr1, ai_family1,
+                            ctx->baller[0], /* wait on that to fail */
+                            /* or start this delayed */
+                            data->set.happy_eyeballs_timeout,
+                            timeout_ms,  EXPIRE_DNS_PER_NAME2);
+    if(result)
+      return result;
+    DEBUGF(LOG_CF(data, cf, "created %s (timeout %ldms)",
+                  ctx->baller[1]->name, ctx->baller[1]->timeoutms));
+  }
 
   Curl_expire(data, data->set.happy_eyeballs_timeout,
               EXPIRE_HAPPY_EYEBALLS);
@@ -1369,310 +822,335 @@ CURLcode Curl_connecthost(struct Curl_easy *data,
   return CURLE_OK;
 }
 
-struct connfind {
-  long id_tofind;
-  struct connectdata *found;
-};
-
-static int conn_is_conn(struct Curl_easy *data,
-                        struct connectdata *conn, void *param)
+static void cf_he_ctx_clear(struct Curl_cfilter *cf, struct Curl_easy *data)
 {
-  struct connfind *f = (struct connfind *)param;
-  (void)data;
-  if(conn->connection_id == f->id_tofind) {
-    f->found = conn;
-    return 1;
+  struct cf_he_ctx *ctx = cf->ctx;
+  size_t i;
+
+  DEBUGASSERT(ctx);
+  DEBUGASSERT(data);
+  for(i = 0; i < sizeof(ctx->baller)/sizeof(ctx->baller[0]); i++) {
+    baller_free(ctx->baller[i], data);
+    ctx->baller[i] = NULL;
   }
-  return 0;
+  baller_free(ctx->winner, data);
+  ctx->winner = NULL;
 }
 
-/*
- * Used to extract socket and connectdata struct for the most recent
- * transfer on the given Curl_easy.
- *
- * The returned socket will be CURL_SOCKET_BAD in case of failure!
- */
-curl_socket_t Curl_getconnectinfo(struct Curl_easy *data,
-                                  struct connectdata **connp)
+static int cf_he_get_select_socks(struct Curl_cfilter *cf,
+                                  struct Curl_easy *data,
+                                  curl_socket_t *socks)
 {
-  DEBUGASSERT(data);
+  struct cf_he_ctx *ctx = cf->ctx;
+  size_t i, s;
+  int wrc, rc = GETSOCK_BLANK;
+  curl_socket_t wsocks[MAX_SOCKSPEREASYHANDLE];
 
-  /* this works for an easy handle:
-   * - that has been used for curl_easy_perform()
-   * - that is associated with a multi handle, and whose connection
-   *   was detached with CURLOPT_CONNECT_ONLY
-   */
-  if((data->state.lastconnect_id != -1) && (data->multi_easy || data->multi)) {
-    struct connectdata *c;
-    struct connfind find;
-    find.id_tofind = data->state.lastconnect_id;
-    find.found = NULL;
+  if(cf->connected)
+    return cf->next->cft->get_select_socks(cf->next, data, socks);
 
-    Curl_conncache_foreach(data,
-                           data->share && (data->share->specifier
-                           & (1<< CURL_LOCK_DATA_CONNECT))?
-                           &data->share->conn_cache:
-                           data->multi_easy?
-                           &data->multi_easy->conn_cache:
-                           &data->multi->conn_cache, &find, conn_is_conn);
+  for(i = s = 0; i < sizeof(ctx->baller)/sizeof(ctx->baller[0]); i++) {
+    struct eyeballer *baller = ctx->baller[i];
+    if(!baller || !baller->cf)
+      continue;
 
-    if(!find.found) {
-      data->state.lastconnect_id = -1;
-      return CURL_SOCKET_BAD;
+    wrc = Curl_conn_cf_get_select_socks(baller->cf, data, wsocks);
+    if(wrc) {
+      /* TODO: we assume we get at most one socket back */
+      socks[s] = wsocks[0];
+      if(wrc & GETSOCK_WRITESOCK(0))
+        rc |= GETSOCK_WRITESOCK(s);
+      if(wrc & GETSOCK_READSOCK(0))
+        rc |= GETSOCK_READSOCK(s);
+      s++;
     }
-
-    c = find.found;
-    if(connp)
-      /* only store this if the caller cares for it */
-      *connp = c;
-    return c->sock[FIRSTSOCKET];
   }
-  return CURL_SOCKET_BAD;
+  return rc;
 }
 
-/*
- * Check if a connection seems to be alive.
- */
-bool Curl_connalive(struct Curl_easy *data, struct connectdata *conn)
+static CURLcode cf_he_connect(struct Curl_cfilter *cf,
+                              struct Curl_easy *data,
+                              bool blocking, bool *done)
 {
-  (void)data;
-  /* First determine if ssl */
-  if(Curl_conn_is_ssl(data, FIRSTSOCKET)) {
-    /* use the SSL context */
-    if(!Curl_ssl_check_cxn(data, conn))
-      return false;   /* FIN received */
+  struct cf_he_ctx *ctx = cf->ctx;
+  CURLcode result = CURLE_OK;
+
+  if(cf->connected) {
+    *done = TRUE;
+    return CURLE_OK;
   }
-/* Minix 3.1 doesn't support any flags on recv; just assume socket is OK */
-#ifdef MSG_PEEK
-  else if(conn->sock[FIRSTSOCKET] == CURL_SOCKET_BAD)
-    return false;
-  else {
-    /* use the socket */
-    char buf;
-    if(recv((RECV_TYPE_ARG1)conn->sock[FIRSTSOCKET], (RECV_TYPE_ARG2)&buf,
-            (RECV_TYPE_ARG3)1, (RECV_TYPE_ARG4)MSG_PEEK) == 0) {
-      return false;   /* FIN received */
-    }
+
+  (void)blocking; /* TODO: do we want to support this? */
+  DEBUGASSERT(ctx);
+  *done = FALSE;
+
+  switch(ctx->state) {
+    case SCFST_INIT:
+      DEBUGASSERT(CURL_SOCKET_BAD == Curl_conn_cf_get_socket(cf, data));
+      DEBUGASSERT(!cf->connected);
+      result = start_connect(cf, data, ctx->remotehost);
+      if(result)
+        return result;
+      ctx->state = SCFST_WAITING;
+      /* FALLTHROUGH */
+    case SCFST_WAITING:
+      result = is_connected(cf, data, done);
+      if(!result && *done) {
+        DEBUGASSERT(ctx->winner);
+        DEBUGASSERT(ctx->winner->cf);
+        DEBUGASSERT(ctx->winner->cf->connected);
+        /* we have a winner. Install and activate it.
+         * close/free all others. */
+        ctx->state = SCFST_DONE;
+        cf->connected = TRUE;
+        cf->next = ctx->winner->cf;
+        ctx->winner->cf = NULL;
+        cf_he_ctx_clear(cf, data);
+        Curl_conn_cf_cntrl(cf->next, data, TRUE,
+                           CF_CTRL_CONN_INFO_UPDATE, 0, NULL);
+
+        if(cf->conn->handler->protocol & PROTO_FAMILY_SSH)
+          Curl_pgrsTime(data, TIMER_APPCONNECT); /* we're connected already */
+        Curl_verboseconnect(data, cf->conn);
+        data->info.numconnects++; /* to track the # of connections made */
+      }
+      break;
+    case SCFST_DONE:
+      *done = TRUE;
+      break;
   }
-#endif
-  return true;
+  return result;
 }
 
-/*
- * Close a socket.
- *
- * 'conn' can be NULL, beware!
- */
-int Curl_closesocket(struct Curl_easy *data, struct connectdata *conn,
-                     curl_socket_t sock)
+static void cf_he_close(struct Curl_cfilter *cf,
+                        struct Curl_easy *data)
 {
-  if(conn && conn->fclosesocket) {
-    if((sock == conn->sock[SECONDARYSOCKET]) && conn->bits.sock_accepted)
-      /* if this socket matches the second socket, and that was created with
-         accept, then we MUST NOT call the callback but clear the accepted
-         status */
-      conn->bits.sock_accepted = FALSE;
-    else {
-      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;
-    }
-  }
+  struct cf_he_ctx *ctx = cf->ctx;
 
-  if(conn)
-    /* tell the multi-socket code about this */
-    Curl_multi_closed(data, sock);
-
-  sclose(sock);
+  DEBUGF(LOG_CF(data, cf, "close"));
+  cf_he_ctx_clear(cf, data);
+  cf->connected = FALSE;
+  ctx->state = SCFST_INIT;
 
-  return 0;
+  if(cf->next) {
+    cf->next->cft->close(cf->next, data);
+    Curl_conn_cf_discard_chain(&cf->next, data);
+  }
 }
 
-/*
- * 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(struct Curl_easy *data,
-                     const struct Curl_addrinfo *ai,
-                     struct Curl_sockaddr_ex *addr,
-                     curl_socket_t *sockfd)
+static bool cf_he_data_pending(struct Curl_cfilter *cf,
+                               const struct Curl_easy *data)
 {
-  struct connectdata *conn = data->conn;
-  struct Curl_sockaddr_ex dummy;
-
-  if(!addr)
-    /* if the caller doesn't want info back, use a local temp copy */
-    addr = &dummy;
-
-  /*
-   * 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 cf_he_ctx *ctx = cf->ctx;
+  size_t i;
 
-  addr->family = ai->ai_family;
-  switch(conn->transport) {
-  case TRNSPRT_TCP:
-    addr->socktype = SOCK_STREAM;
-    addr->protocol = IPPROTO_TCP;
-    break;
-  case TRNSPRT_UNIX:
-    addr->socktype = SOCK_STREAM;
-    addr->protocol = IPPROTO_IP;
-    break;
-  default: /* UDP and QUIC */
-    addr->socktype = SOCK_DGRAM;
-    addr->protocol = IPPROTO_UDP;
-    break;
-  }
-  addr->addrlen = ai->ai_addrlen;
-
-  if(addr->addrlen > sizeof(struct Curl_sockaddr_storage))
-     addr->addrlen = sizeof(struct Curl_sockaddr_storage);
-  memcpy(&addr->sa_addr, ai->ai_addr, addr->addrlen);
-
-  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(cf->connected)
+    return cf->next->cft->has_data_pending(cf->next, data);
 
-  if(*sockfd == CURL_SOCKET_BAD)
-    /* no socket, no connection */
-    return CURLE_COULDNT_CONNECT;
+  for(i = 0; i < sizeof(ctx->baller)/sizeof(ctx->baller[0]); i++) {
+    struct eyeballer *baller = ctx->baller[i];
+    if(!baller || !baller->cf)
+      continue;
+    if(baller->cf->cft->has_data_pending(baller->cf, data))
+      return TRUE;
+  }
+  return FALSE;
+}
 
-  if(conn->transport == TRNSPRT_QUIC) {
-    /* QUIC sockets need to be nonblocking */
-    (void)curlx_nonblock(*sockfd, TRUE);
-    switch(addr->family) {
-#if defined(__linux__) && defined(IP_MTU_DISCOVER)
-    case AF_INET: {
-      int val = IP_PMTUDISC_DO;
-      (void)setsockopt(*sockfd, IPPROTO_IP, IP_MTU_DISCOVER, &val,
-                       sizeof(val));
-      break;
+static CURLcode cf_he_query(struct Curl_cfilter *cf,
+                            struct Curl_easy *data,
+                            int query, int *pres1, void *pres2)
+{
+  struct cf_he_ctx *ctx = cf->ctx;
+
+  if(!cf->connected) {
+    switch(query) {
+    case CF_QUERY_CONNECT_REPLY_MS: {
+      int reply_ms = -1;
+      size_t i;
+
+      for(i = 0; i < sizeof(ctx->baller)/sizeof(ctx->baller[0]); i++) {
+        struct eyeballer *baller = ctx->baller[i];
+        int breply_ms;
+
+        if(baller && baller->cf &&
+           !baller->cf->cft->query(baller->cf, data, query,
+                                   &breply_ms, NULL)) {
+          if(breply_ms >= 0 && (reply_ms < 0 || breply_ms < reply_ms))
+            reply_ms = breply_ms;
+        }
+      }
+      *pres1 = reply_ms;
+      DEBUGF(LOG_CF(data, cf, "query connect reply: %dms", *pres1));
+      return CURLE_OK;
     }
-#endif
-#if defined(__linux__) && defined(IPV6_MTU_DISCOVER)
-    case AF_INET6: {
-      int val = IPV6_PMTUDISC_DO;
-      (void)setsockopt(*sockfd, IPPROTO_IPV6, IPV6_MTU_DISCOVER, &val,
-                       sizeof(val));
+
+    default:
       break;
     }
-#endif
-    }
-  }
-
-#if defined(ENABLE_IPV6) && defined(HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID)
-  if(conn->scope_id && (addr->family == AF_INET6)) {
-    struct sockaddr_in6 * const sa6 = (void *)&addr->sa_addr;
-    sa6->sin6_scope_id = conn->scope_id;
   }
-#endif
 
-  return CURLE_OK;
+  return cf->next?
+    cf->next->cft->query(cf->next, data, query, pres1, pres2) :
+    CURLE_UNKNOWN_OPTION;
 }
 
-/*
- * Curl_conncontrol() marks streams or connection for closure.
- */
-void Curl_conncontrol(struct connectdata *conn,
-                      int ctrl /* see defines in header */
-#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
-                      , const char *reason
-#endif
-  )
+static void cf_he_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
 {
-  /* close if a connection, or a stream that isn't multiplexed. */
-  /* This function will be called both before and after this connection is
-     associated with a transfer. */
-  bool closeit;
-  DEBUGASSERT(conn);
-#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
-  (void)reason; /* useful for debugging */
-#endif
-  closeit = (ctrl == CONNCTRL_CONNECTION) ||
-    ((ctrl == CONNCTRL_STREAM) && !(conn->handler->flags & PROTOPT_STREAM));
-  if((ctrl == CONNCTRL_STREAM) &&
-     (conn->handler->flags & PROTOPT_STREAM))
-    ;
-  else if((bit)closeit != conn->bits.close) {
-    conn->bits.close = closeit; /* the only place in the source code that
-                                   should assign this bit */
+  struct cf_he_ctx *ctx = cf->ctx;
+
+  DEBUGF(LOG_CF(data, cf, "destroy"));
+  if(ctx) {
+    cf_he_ctx_clear(cf, data);
   }
+  /* release any resources held in state */
+  Curl_safefree(ctx);
 }
 
-typedef enum {
-  SCFST_INIT,
-  SCFST_WAITING,
-  SCFST_DONE
-} cf_connect_state;
-
-struct socket_cf_ctx {
-  const struct Curl_dns_entry *remotehost;
-  cf_connect_state state;
+struct Curl_cftype Curl_cft_happy_eyeballs = {
+  "HAPPY-EYEBALLS",
+  0,
+  CURL_LOG_DEFAULT,
+  cf_he_destroy,
+  cf_he_connect,
+  cf_he_close,
+  Curl_cf_def_get_host,
+  cf_he_get_select_socks,
+  cf_he_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_he_query,
 };
 
-static int socket_cf_get_select_socks(struct Curl_cfilter *cf,
-                                      struct Curl_easy *data,
-                                      curl_socket_t *socks)
+CURLcode Curl_cf_happy_eyeballs_create(struct Curl_cfilter **pcf,
+                                       struct Curl_easy *data,
+                                       struct connectdata *conn,
+                                       cf_ip_connect_create *cf_create,
+                                       const struct Curl_dns_entry *remotehost,
+                                       int transport)
 {
-  struct connectdata *conn = cf->conn;
-  int i, s, rc = GETSOCK_BLANK;
+  struct cf_he_ctx *ctx = NULL;
+  CURLcode result;
 
   (void)data;
-  if(cf->connected) {
-    return rc;
+  (void)conn;
+  *pcf = NULL;
+  ctx = calloc(sizeof(*ctx), 1);
+  if(!ctx) {
+    result = CURLE_OUT_OF_MEMORY;
+    goto out;
   }
+  ctx->transport = transport;
+  ctx->cf_create = cf_create;
+  ctx->remotehost = remotehost;
 
-  for(i = s = 0; i<2; i++) {
-    if(conn->tempsock[i] != CURL_SOCKET_BAD) {
-      socks[s] = conn->tempsock[i];
-      rc |= GETSOCK_WRITESOCK(s);
+  result = Curl_cf_create(pcf, &Curl_cft_happy_eyeballs, ctx);
+
+out:
+  if(result) {
+    Curl_safefree(*pcf);
+    Curl_safefree(ctx);
+  }
+  return result;
+}
+
+struct transport_provider {
+  int transport;
+  cf_ip_connect_create *cf_create;
+};
+
+static
+#ifndef DEBUGBUILD
+const
+#endif
+struct transport_provider transport_providers[] = {
+  { TRNSPRT_TCP, Curl_cf_tcp_create },
 #ifdef ENABLE_QUIC
-      if(conn->transport == TRNSPRT_QUIC)
-        /* when connecting QUIC, we want to read the socket too */
-        rc |= GETSOCK_READSOCK(s);
+  { TRNSPRT_QUIC, Curl_cf_quic_create },
 #endif
-      s++;
+  { TRNSPRT_UDP, Curl_cf_udp_create },
+  { TRNSPRT_UNIX, Curl_cf_unix_create },
+};
+
+#ifndef ARRAYSIZE
+#define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0]))
+#endif
+
+static cf_ip_connect_create *get_cf_create(int transport)
+{
+  size_t i;
+  for(i = 0; i < ARRAYSIZE(transport_providers); ++i) {
+    if(transport == transport_providers[i].transport)
+      return transport_providers[i].cf_create;
+  }
+  return NULL;
+}
+
+#ifdef DEBUGBUILD
+void Curl_debug_set_transport_provider(int transport,
+                                       cf_ip_connect_create *cf_create)
+{
+  size_t i;
+  for(i = 0; i < ARRAYSIZE(transport_providers); ++i) {
+    if(transport == transport_providers[i].transport) {
+      transport_providers[i].cf_create = cf_create;
+      return;
     }
   }
+}
+#endif /* DEBUGBUILD */
 
-  return rc;
+static CURLcode cf_he_insert_after(struct Curl_cfilter *cf_at,
+                                   struct Curl_easy *data,
+                                   const struct Curl_dns_entry *remotehost,
+                                   int transport)
+{
+  cf_ip_connect_create *cf_create;
+  struct Curl_cfilter *cf;
+  CURLcode result;
+
+  /* Need to be first */
+  DEBUGASSERT(cf_at);
+  cf_create = get_cf_create(transport);
+  if(!cf_create) {
+    DEBUGF(LOG_CF(data, cf_at, "unsupported transport type %d", transport));
+    return CURLE_UNSUPPORTED_PROTOCOL;
+  }
+  result = Curl_cf_happy_eyeballs_create(&cf, data, cf_at->conn,
+                                         cf_create, remotehost,
+                                         transport);
+  if(result)
+    return result;
+
+  Curl_conn_cf_insert_after(cf_at, cf);
+  return CURLE_OK;
 }
 
-static CURLcode socket_cf_connect(struct Curl_cfilter *cf,
-                                  struct Curl_easy *data,
-                                  bool blocking, bool *done)
+typedef enum {
+  CF_SETUP_INIT,
+  CF_SETUP_CNNCT_EYEBALLS,
+  CF_SETUP_CNNCT_SOCKS,
+  CF_SETUP_CNNCT_HTTP_PROXY,
+  CF_SETUP_CNNCT_HAPROXY,
+  CF_SETUP_CNNCT_SSL,
+  CF_SETUP_DONE
+} cf_setup_state;
+
+struct cf_setup_ctx {
+  cf_setup_state state;
+  const struct Curl_dns_entry *remotehost;
+  int ssl_mode;
+  int transport;
+};
+
+static CURLcode cf_setup_connect(struct Curl_cfilter *cf,
+                                 struct Curl_easy *data,
+                                 bool blocking, bool *done)
 {
-  struct connectdata *conn = cf->conn;
-  int sockindex = cf->sockindex;
-  struct socket_cf_ctx *ctx = cf->ctx;
+  struct cf_setup_ctx *ctx = cf->ctx;
   CURLcode result = CURLE_OK;
 
   if(cf->connected) {
@@ -1680,253 +1158,241 @@ static CURLcode socket_cf_connect(struct Curl_cfilter *cf,
     return CURLE_OK;
   }
 
-  (void)blocking;
-  DEBUGASSERT(ctx);
-  *done = FALSE;
-  switch(ctx->state) {
-    case SCFST_INIT:
-      DEBUGASSERT(CURL_SOCKET_BAD == conn->sock[sockindex]);
-      DEBUGASSERT(!cf->connected);
-      result = Curl_connecthost(data, conn, ctx->remotehost);
-      if(!result)
-        ctx->state = SCFST_WAITING;
-      break;
-    case SCFST_WAITING:
-      result = is_connected(data, conn, sockindex, done);
-      if(!result && *done) {
-        Curl_pgrsTime(data, TIMER_CONNECT);    /* we're connected already */
-        if(Curl_conn_is_ssl(data, FIRSTSOCKET) ||
-           (conn->handler->protocol & PROTO_FAMILY_SSH))
-          Curl_pgrsTime(data, TIMER_APPCONNECT); /* we're connected already */
-        post_connect(data, conn, sockindex);
-        ctx->state = SCFST_DONE;
-        cf->connected = TRUE;
-      }
-      break;
-    case SCFST_DONE:
-      *done = TRUE;
-      break;
+  /* connect current sub-chain */
+connect_sub_chain:
+  if(cf->next && !cf->next->connected) {
+    result = Curl_conn_cf_connect(cf->next, data, blocking, done);
+    if(result || !*done)
+      return result;
   }
-  return result;
-}
 
-static CURLcode socket_cf_setup(struct Curl_cfilter *cf,
-                                struct Curl_easy *data,
-                                const struct Curl_dns_entry *remotehost)
-{
-  struct socket_cf_ctx *ctx = cf->ctx;
+  if(ctx->state < CF_SETUP_CNNCT_EYEBALLS) {
+    result = cf_he_insert_after(cf, data, ctx->remotehost, ctx->transport);
+    if(result)
+      return result;
+    ctx->state = CF_SETUP_CNNCT_EYEBALLS;
+    if(!cf->next || !cf->next->connected)
+      goto connect_sub_chain;
+  }
 
-  (void)data;
-  DEBUGASSERT(ctx);
-  if(ctx->remotehost != remotehost) {
-    if(ctx->remotehost) {
-      /* switching dns entry? TODO: reset? */
-    }
-    ctx->remotehost = remotehost;
+  /* sub-chain connected, do we need to add more? */
+#ifndef CURL_DISABLE_PROXY
+  if(ctx->state < CF_SETUP_CNNCT_SOCKS && cf->conn->bits.socksproxy) {
+    result = Curl_cf_socks_proxy_insert_after(cf, data);
+    if(result)
+      return result;
+    ctx->state = CF_SETUP_CNNCT_SOCKS;
+    if(!cf->next || !cf->next->connected)
+      goto connect_sub_chain;
   }
-  DEBUGF(infof(data, CFMSG(cf, "setup(remotehost=%s)"),
-         cf->conn->hostname_resolve));
-  return CURLE_OK;
-}
 
-static void socket_cf_close(struct Curl_cfilter *cf,
-                            struct Curl_easy *data)
-{
-  int sockindex = cf->sockindex;
-  struct socket_cf_ctx *ctx = cf->ctx;
-
- DEBUGASSERT(ctx);
-   /* close possibly still open sockets */
-  if(CURL_SOCKET_BAD != cf->conn->sock[sockindex]) {
-    Curl_closesocket(data, cf->conn, cf->conn->sock[sockindex]);
-    cf->conn->sock[sockindex] = CURL_SOCKET_BAD;
+  if(ctx->state < CF_SETUP_CNNCT_HTTP_PROXY && cf->conn->bits.httpproxy) {
+#ifdef USE_SSL
+    if(cf->conn->http_proxy.proxytype == CURLPROXY_HTTPS
+       && !Curl_conn_is_ssl(cf->conn, cf->sockindex)) {
+      result = Curl_cf_ssl_proxy_insert_after(cf, data);
+      if(result)
+        return result;
+    }
+#endif /* USE_SSL */
+
+#if !defined(CURL_DISABLE_HTTP)
+    if(cf->conn->bits.tunnel_proxy) {
+      result = Curl_cf_http_proxy_insert_after(cf, data);
+      if(result)
+        return result;
+    }
+#endif /* !CURL_DISABLE_HTTP */
+    ctx->state = CF_SETUP_CNNCT_HTTP_PROXY;
+    if(!cf->next || !cf->next->connected)
+      goto connect_sub_chain;
   }
-  if(CURL_SOCKET_BAD != cf->conn->tempsock[sockindex]) {
-    Curl_closesocket(data, cf->conn, cf->conn->tempsock[sockindex]);
-    cf->conn->tempsock[sockindex] = CURL_SOCKET_BAD;
+#endif /* !CURL_DISABLE_PROXY */
+
+  if(ctx->state < CF_SETUP_CNNCT_HAPROXY) {
+#if !defined(CURL_DISABLE_PROXY)
+    if(data->set.haproxyprotocol) {
+      if(Curl_conn_is_ssl(cf->conn, cf->sockindex)) {
+        failf(data, "haproxy protocol not support with SSL "
+              "encryption in place (QUIC?)");
+        return CURLE_UNSUPPORTED_PROTOCOL;
+      }
+      result = Curl_cf_haproxy_insert_after(cf, data);
+      if(result)
+        return result;
+    }
+#endif /* !CURL_DISABLE_PROXY */
+    ctx->state = CF_SETUP_CNNCT_HAPROXY;
+    if(!cf->next || !cf->next->connected)
+      goto connect_sub_chain;
   }
-  cf->connected = FALSE;
-  ctx->state = SCFST_INIT;
-}
 
-static void socket_cf_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 bool socket_cf_data_pending(struct Curl_cfilter *cf,
-                                   const struct Curl_easy *data)
-{
-  int readable;
-  (void)data;
-  DEBUGASSERT(cf);
+  if(ctx->state < CF_SETUP_CNNCT_SSL) {
+#ifdef USE_SSL
+    if((ctx->ssl_mode == CURL_CF_SSL_ENABLE
+        || (ctx->ssl_mode != CURL_CF_SSL_DISABLE
+           && cf->conn->handler->flags & PROTOPT_SSL)) /* we want SSL */
+       && !Curl_conn_is_ssl(cf->conn, cf->sockindex)) { /* it is missing */
+      result = Curl_cf_ssl_insert_after(cf, data);
+      if(result)
+        return result;
+    }
+#endif /* USE_SSL */
+    ctx->state = CF_SETUP_CNNCT_SSL;
+    if(!cf->next || !cf->next->connected)
+      goto connect_sub_chain;
+  }
 
-  readable = SOCKET_READABLE(cf->conn->sock[cf->sockindex], 0);
-  return (readable > 0 && (readable & CURL_CSELECT_IN));
+  ctx->state = CF_SETUP_DONE;
+  cf->connected = TRUE;
+  *done = TRUE;
+  return CURLE_OK;
 }
 
-static ssize_t socket_cf_send(struct Curl_cfilter *cf, struct Curl_easy *data,
-                              const void *buf, size_t len, CURLcode *err)
+static void cf_setup_close(struct Curl_cfilter *cf,
+                           struct Curl_easy *data)
 {
-  ssize_t nwritten;
-  nwritten = Curl_send_plain(data, cf->sockindex, buf, len, err);
-  return nwritten;
-}
+  struct cf_setup_ctx *ctx = cf->ctx;
 
-static ssize_t socket_cf_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
-                              char *buf, size_t len, CURLcode *err)
-{
-  ssize_t nread;
-  nread = Curl_recv_plain(data, cf->sockindex, buf, len, err);
-  return nread;
+  DEBUGF(LOG_CF(data, cf, "close"));
+  cf->connected = FALSE;
+  ctx->state = CF_SETUP_INIT;
+
+  if(cf->next) {
+    cf->next->cft->close(cf->next, data);
+    Curl_conn_cf_discard_chain(&cf->next, data);
+  }
 }
 
-static void socket_cf_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
+static void cf_setup_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
 {
-  struct socket_cf_ctx *state = cf->ctx;
+  struct cf_setup_ctx *ctx = cf->ctx;
 
   (void)data;
-  if(cf->connected) {
-    socket_cf_close(cf, data);
-  }
-  /* release any resources held in state */
-  Curl_safefree(state);
+  DEBUGF(LOG_CF(data, cf, "destroy"));
+  Curl_safefree(ctx);
 }
 
-static const struct Curl_cftype cft_socket = {
-  "SOCKET",
-  CF_TYPE_IP_CONNECT,
-  socket_cf_destroy,
-  socket_cf_setup,
-  socket_cf_connect,
-  socket_cf_close,
-  socket_cf_get_host,
-  socket_cf_get_select_socks,
-  socket_cf_data_pending,
-  socket_cf_send,
-  socket_cf_recv,
-  Curl_cf_def_attach_data,
-  Curl_cf_def_detach_data,
+
+struct Curl_cftype Curl_cft_setup = {
+  "SETUP",
+  0,
+  CURL_LOG_DEFAULT,
+  cf_setup_destroy,
+  cf_setup_connect,
+  cf_setup_close,
+  Curl_cf_def_get_host,
+  Curl_cf_def_get_select_socks,
+  Curl_cf_def_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,
+  Curl_cf_def_query,
 };
 
-CURLcode Curl_conn_socket_set(struct Curl_easy *data,
-                              struct connectdata *conn,
-                              int sockindex)
+static CURLcode cf_setup_create(struct Curl_cfilter **pcf,
+                                struct Curl_easy *data,
+                                const struct Curl_dns_entry *remotehost,
+                                int transport,
+                                int ssl_mode)
 {
-  CURLcode result;
   struct Curl_cfilter *cf = NULL;
-  struct socket_cf_ctx *scf_ctx = NULL;
+  struct cf_setup_ctx *ctx;
+  CURLcode result = CURLE_OK;
 
-  /* Need to be first */
-  DEBUGASSERT(conn);
-  DEBUGASSERT(!conn->cfilter[sockindex]);
-  scf_ctx = calloc(sizeof(*scf_ctx), 1);
-  if(!scf_ctx) {
+  (void)data;
+  ctx = calloc(sizeof(*ctx), 1);
+  if(!ctx) {
     result = CURLE_OUT_OF_MEMORY;
     goto out;
   }
-  result = Curl_cf_create(&cf, &cft_socket, scf_ctx);
+  ctx->state = CF_SETUP_INIT;
+  ctx->remotehost = remotehost;
+  ctx->ssl_mode = ssl_mode;
+  ctx->transport = transport;
+
+  result = Curl_cf_create(&cf, &Curl_cft_setup, ctx);
   if(result)
     goto out;
-  Curl_conn_cf_add(data, conn, sockindex, cf);
+  ctx = NULL;
 
 out:
-  if(result) {
-    Curl_safefree(cf);
-    Curl_safefree(scf_ctx);
-  }
+  *pcf = result? NULL : cf;
+  free(ctx);
   return result;
 }
 
-static CURLcode socket_accept_cf_connect(struct Curl_cfilter *cf,
-                                         struct Curl_easy *data,
-                                         bool blocking, bool *done)
+CURLcode Curl_cf_setup_add(struct Curl_easy *data,
+                           struct connectdata *conn,
+                           int sockindex,
+                           const struct Curl_dns_entry *remotehost,
+                           int transport,
+                           int ssl_mode)
 {
-  /* 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_cfilter *cf;
+  CURLcode result = CURLE_OK;
+
+  DEBUGASSERT(data);
+  result = cf_setup_create(&cf, data, remotehost, transport, ssl_mode);
+  if(result)
+    goto out;
+  Curl_conn_cf_add(data, conn, sockindex, cf);
+out:
+  return result;
 }
 
-static CURLcode socket_accept_cf_setup(struct Curl_cfilter *cf,
-                                       struct Curl_easy *data,
-                                       const struct Curl_dns_entry *remotehost)
+CURLcode Curl_cf_setup_insert_after(struct Curl_cfilter *cf_at,
+                                    struct Curl_easy *data,
+                                    const struct Curl_dns_entry *remotehost,
+                                    int transport,
+                                    int ssl_mode)
 {
-  /* we start accepted, if we ever close, we cannot go on */
-  (void)data;
-  (void)remotehost;
-  if(cf->connected) {
-    return CURLE_OK;
-  }
-  return CURLE_FAILED_INIT;
-}
+  struct Curl_cfilter *cf;
+  CURLcode result;
 
-static const struct Curl_cftype cft_socket_accept = {
-  "SOCKET-ACCEPT",
-  CF_TYPE_IP_CONNECT,
-  socket_cf_destroy,
-  socket_accept_cf_setup,
-  socket_accept_cf_connect,
-  socket_cf_close,
-  socket_cf_get_host,              /* TODO: not accurate */
-  Curl_cf_def_get_select_socks,
-  socket_cf_data_pending,
-  socket_cf_send,
-  socket_cf_recv,
-  Curl_cf_def_attach_data,
-  Curl_cf_def_detach_data,
-};
+  DEBUGASSERT(data);
+  result = cf_setup_create(&cf, data, remotehost, transport, ssl_mode);
+  if(result)
+    goto out;
+  Curl_conn_cf_insert_after(cf_at, cf);
+out:
+  return result;
+}
 
-CURLcode Curl_conn_socket_accepted_set(struct Curl_easy *data,
-                                       struct connectdata *conn,
-                                       int sockindex, curl_socket_t *s)
+CURLcode Curl_conn_setup(struct Curl_easy *data,
+                         struct connectdata *conn,
+                         int sockindex,
+                         const struct Curl_dns_entry *remotehost,
+                         int ssl_mode)
 {
-  CURLcode result;
-  struct Curl_cfilter *cf = NULL;
-  struct socket_cf_ctx *scf_ctx = NULL;
+  CURLcode result = CURLE_OK;
 
-  cf = conn->cfilter[sockindex];
-  if(cf && cf->cft == &cft_socket_accept) {
-    /* already an accept filter installed, just replace the socket */
-    scf_ctx = cf->ctx;
-    result = CURLE_OK;
-  }
-  else {
-    /* replace any existing */
-    Curl_conn_cf_discard_all(data, conn, sockindex);
-    scf_ctx = calloc(sizeof(*scf_ctx), 1);
-    if(!scf_ctx) {
-      result = CURLE_OUT_OF_MEMORY;
-      goto out;
-    }
-    result = Curl_cf_create(&cf, &cft_socket_accept, scf_ctx);
+  DEBUGASSERT(data);
+  DEBUGASSERT(conn->handler);
+
+#if !defined(CURL_DISABLE_HTTP) && !defined(USE_HYPER)
+  if(!conn->cfilter[sockindex] &&
+     conn->handler->protocol == CURLPROTO_HTTPS &&
+     (ssl_mode == CURL_CF_SSL_ENABLE || ssl_mode != CURL_CF_SSL_DISABLE)) {
+
+    result = Curl_cf_https_setup(data, conn, sockindex, remotehost);
     if(result)
       goto out;
-    Curl_conn_cf_add(data, conn, sockindex, cf);
   }
+#endif /* !defined(CURL_DISABLE_HTTP) && !defined(USE_HYPER) */
 
-   /* close any existing socket and replace */
-  Curl_closesocket(data, conn, conn->sock[sockindex]);
-  conn->sock[sockindex] = *s;
-  conn->bits.sock_accepted = TRUE;
-  cf->connected = TRUE;
-  scf_ctx->state = SCFST_DONE;
+  /* Still no cfilter set, apply default. */
+  if(!conn->cfilter[sockindex]) {
+    result = Curl_cf_setup_add(data, conn, sockindex, remotehost,
+                               conn->transport, ssl_mode);
+    if(result)
+      goto out;
+  }
 
+  DEBUGASSERT(conn->cfilter[sockindex]);
 out:
-  if(result) {
-    Curl_safefree(cf);
-    Curl_safefree(scf_ctx);
-  }
   return result;
 }
+
diff --git a/lib/connect.h b/lib/connect.h
index 1e90a85..e4fa10c 100644
--- a/lib/connect.h
+++ b/lib/connect.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -29,9 +29,7 @@
 #include "sockaddr.h"
 #include "timeval.h"
 
-CURLcode Curl_connecthost(struct Curl_easy *data,
-                          struct connectdata *conn,
-                          const struct Curl_dns_entry *host);
+struct Curl_dns_entry;
 
 /* generic function that returns how much time there's left to run, according
    to the timeouts set */
@@ -53,67 +51,8 @@ curl_socket_t Curl_getconnectinfo(struct Curl_easy *data,
 bool Curl_addr2string(struct sockaddr *sa, curl_socklen_t salen,
                       char *addr, int *port);
 
-/*
- * Check if a connection seems to be alive.
- */
-bool Curl_connalive(struct Curl_easy *data, struct connectdata *conn);
-
-#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
-
-void Curl_updateconninfo(struct Curl_easy *data, struct connectdata *conn,
-                         curl_socket_t sockfd);
-void Curl_conninfo_remote(struct Curl_easy *data, struct connectdata *conn,
-                          curl_socket_t sockfd);
-void Curl_conninfo_local(struct Curl_easy *data, curl_socket_t sockfd,
-                         char *local_ip, int *local_port);
 void Curl_persistconninfo(struct Curl_easy *data, struct connectdata *conn,
                           char *local_ip, int local_port);
-int Curl_closesocket(struct Curl_easy *data, struct connectdata *conn,
-                     curl_socket_t sock);
-
-/*
- * 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(struct Curl_easy *data,
-                     const struct Curl_addrinfo *ai,
-                     struct Curl_sockaddr_ex *addr,
-                     curl_socket_t *sockfd);
 
 /*
  * Curl_conncontrol() marks the end of a connection/stream. The 'closeit'
@@ -148,13 +87,71 @@ void Curl_conncontrol(struct connectdata *conn,
 #define connkeep(x,y) Curl_conncontrol(x, CONNCTRL_KEEP)
 #endif
 
-CURLcode Curl_conn_socket_set(struct Curl_easy *data,
+/**
+ * Create a cfilter for making an "ip" connection to the
+ * given address, using parameters from `conn`. The "ip" connection
+ * can be a TCP socket, a UDP socket or even a QUIC connection.
+ *
+ * It MUST use only the supplied `ai` for its connection attempt.
+ *
+ * Such a filter may be used in "happy eyeball" scenarios, and its
+ * `connect` implementation needs to support non-blocking. Once connected,
+ * it MAY be installed in the connection filter chain to serve transfers.
+ */
+typedef CURLcode cf_ip_connect_create(struct Curl_cfilter **pcf,
+                                      struct Curl_easy *data,
+                                      struct connectdata *conn,
+                                      const struct Curl_addrinfo *ai,
+                                      int transport);
+
+/**
+ * Create a happy eyeball connection filter that uses the, once resolved,
+ * address information to connect on ip families based on connection
+ * configuration.
+ * @param pcf        output, the created cfilter
+ * @param data       easy handle used in creation
+ * @param conn       connection the filter is created for
+ * @param cf_create  method to create the sub-filters performing the
+ *                   actual connects.
+ */
+CURLcode
+Curl_cf_happy_eyeballs_create(struct Curl_cfilter **pcf,
+                              struct Curl_easy *data,
                               struct connectdata *conn,
-                              int sockindex);
-
-CURLcode Curl_conn_socket_accepted_set(struct Curl_easy *data,
-                                       struct connectdata *conn,
-                                       int sockindex,
-                                       curl_socket_t *s);
+                              cf_ip_connect_create *cf_create,
+                              const struct Curl_dns_entry *remotehost,
+                              int transport);
+
+CURLcode Curl_cf_setup_add(struct Curl_easy *data,
+                           struct connectdata *conn,
+                           int sockindex,
+                           const struct Curl_dns_entry *remotehost,
+                           int transport,
+                           int ssl_mode);
+
+CURLcode Curl_cf_setup_insert_after(struct Curl_cfilter *cf_at,
+                                    struct Curl_easy *data,
+                                    const struct Curl_dns_entry *remotehost,
+                                    int transport,
+                                    int ssl_mode);
+
+/**
+ * Setup the cfilters at `sockindex` in connection `conn`.
+ * If no filter chain is installed yet, inspects the configuration
+ * in `data` and `conn? to install a suitable filter chain.
+ */
+CURLcode Curl_conn_setup(struct Curl_easy *data,
+                         struct connectdata *conn,
+                         int sockindex,
+                         const struct Curl_dns_entry *remotehost,
+                         int ssl_mode);
+
+extern struct Curl_cftype Curl_cft_happy_eyeballs;
+extern struct Curl_cftype Curl_cft_setup;
+
+#ifdef DEBUGBUILD
+void Curl_debug_set_transport_provider(int transport,
+                                       cf_ip_connect_create *cf_create);
+#endif
 
 #endif /* HEADER_CURL_CONNECT_H */
diff --git a/lib/content_encoding.c b/lib/content_encoding.c
index bfc13e2..2bd4fef 100644
--- a/lib/content_encoding.c
+++ b/lib/content_encoding.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -977,7 +977,8 @@ static const struct content_encoding error_encoding = {
 static struct contenc_writer *
 new_unencoding_writer(struct Curl_easy *data,
                       const struct content_encoding *handler,
-                      struct contenc_writer *downstream)
+                      struct contenc_writer *downstream,
+                      int order)
 {
   struct contenc_writer *writer;
 
@@ -987,6 +988,7 @@ new_unencoding_writer(struct Curl_easy *data,
   if(writer) {
     writer->handler = handler;
     writer->downstream = downstream;
+    writer->order = order;
     if(handler->init_writer(data, writer)) {
       free(writer);
       writer = NULL;
@@ -1042,10 +1044,10 @@ static const struct content_encoding *find_encoding(const char *name,
 /* Set-up the unencoding stack from the Content-Encoding header value.
  * See RFC 7231 section 3.1.2.2. */
 CURLcode Curl_build_unencoding_stack(struct Curl_easy *data,
-                                     const char *enclist, int maybechunked)
+                                     const char *enclist, int is_transfer)
 {
   struct SingleRequest *k = &data->req;
-  int counter = 0;
+  unsigned int order = is_transfer? 2: 1;
 
   do {
     const char *name;
@@ -1062,7 +1064,7 @@ CURLcode Curl_build_unencoding_stack(struct Curl_easy *data,
         namelen = enclist - name + 1;
 
     /* Special case: chunked encoding is handled at the reader level. */
-    if(maybechunked && namelen == 7 && strncasecompare(name, "chunked", 7)) {
+    if(is_transfer && namelen == 7 && strncasecompare(name, "chunked", 7)) {
       k->chunk = TRUE;             /* chunks coming our way. */
       Curl_httpchunk_init(data);   /* init our chunky engine. */
     }
@@ -1071,7 +1073,8 @@ CURLcode Curl_build_unencoding_stack(struct Curl_easy *data,
       struct contenc_writer *writer;
 
       if(!k->writer_stack) {
-        k->writer_stack = new_unencoding_writer(data, &client_encoding, NULL);
+        k->writer_stack = new_unencoding_writer(data, &client_encoding,
+                                                NULL, 0);
 
         if(!k->writer_stack)
           return CURLE_OUT_OF_MEMORY;
@@ -1080,16 +1083,29 @@ CURLcode Curl_build_unencoding_stack(struct Curl_easy *data,
       if(!encoding)
         encoding = &error_encoding;  /* Defer error at stack use. */
 
-      if(++counter >= MAX_ENCODE_STACK) {
-        failf(data, "Reject response due to %u content encodings",
-              counter);
+      if(k->writer_stack_depth++ >= MAX_ENCODE_STACK) {
+        failf(data, "Reject response due to more than %u content encodings",
+              MAX_ENCODE_STACK);
         return CURLE_BAD_CONTENT_ENCODING;
       }
       /* Stack the unencoding stage. */
-      writer = new_unencoding_writer(data, encoding, k->writer_stack);
-      if(!writer)
-        return CURLE_OUT_OF_MEMORY;
-      k->writer_stack = writer;
+      if(order >= k->writer_stack->order) {
+        writer = new_unencoding_writer(data, encoding,
+                                       k->writer_stack, order);
+        if(!writer)
+          return CURLE_OUT_OF_MEMORY;
+        k->writer_stack = writer;
+      }
+      else {
+        struct contenc_writer *w = k->writer_stack;
+        while(w->downstream && order < w->downstream->order)
+          w = w->downstream;
+        writer = new_unencoding_writer(data, encoding,
+                                       w->downstream, order);
+        if(!writer)
+          return CURLE_OUT_OF_MEMORY;
+        w->downstream = writer;
+      }
     }
   } while(*enclist);
 
@@ -1099,11 +1115,11 @@ CURLcode Curl_build_unencoding_stack(struct Curl_easy *data,
 #else
 /* Stubs for builds without HTTP. */
 CURLcode Curl_build_unencoding_stack(struct Curl_easy *data,
-                                     const char *enclist, int maybechunked)
+                                     const char *enclist, int is_transfer)
 {
   (void) data;
   (void) enclist;
-  (void) maybechunked;
+  (void) is_transfer;
   return CURLE_NOT_BUILT_IN;
 }
 
diff --git a/lib/content_encoding.h b/lib/content_encoding.h
index 3c278cf..56e7f97 100644
--- a/lib/content_encoding.h
+++ b/lib/content_encoding.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -28,6 +28,7 @@
 struct contenc_writer {
   const struct content_encoding *handler;  /* Encoding handler. */
   struct contenc_writer *downstream;  /* Downstream writer. */
+  unsigned int order; /* Ordering within writer stack. */
 };
 
 /* Content encoding writer. */
@@ -46,7 +47,7 @@ struct content_encoding {
 
 
 CURLcode Curl_build_unencoding_stack(struct Curl_easy *data,
-                                     const char *enclist, int maybechunked);
+                                     const char *enclist, int is_transfer);
 CURLcode Curl_unencode_write(struct Curl_easy *data,
                              struct contenc_writer *writer,
                              const char *buf, size_t nbytes);
diff --git a/lib/cookie.c b/lib/cookie.c
index bccf2e8..c457b2d 100644
--- a/lib/cookie.c
+++ b/lib/cookie.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -329,7 +329,7 @@ static char *sanitize_cookie_path(const char *cookie_path)
  */
 void Curl_cookie_loadfiles(struct Curl_easy *data)
 {
-  struct curl_slist *list = data->state.cookielist;
+  struct curl_slist *list = data->set.cookielist;
   if(list) {
     Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);
     while(list) {
@@ -347,8 +347,6 @@ void Curl_cookie_loadfiles(struct Curl_easy *data)
         data->cookies = newcookies;
       list = list->next;
     }
-    curl_slist_free_all(data->state.cookielist); /* clean up list */
-    data->state.cookielist = NULL; /* don't do this again! */
     Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE);
   }
 }
@@ -1301,7 +1299,7 @@ struct CookieInfo *Curl_cookie_init(struct Curl_easy *data,
      */
     remove_expired(c);
 
-    if(fromfile && fp)
+    if(fromfile)
       fclose(fp);
   }
 
@@ -1800,12 +1798,10 @@ void Curl_flush_cookies(struct Curl_easy *data, bool cleanup)
   CURLcode res;
 
   if(data->set.str[STRING_COOKIEJAR]) {
-    if(data->state.cookielist) {
-      /* If there is a list of cookie files to read, do it first so that
-         we have all the told files read before we write the new jar.
-         Curl_cookie_loadfiles() LOCKS and UNLOCKS the share itself! */
-      Curl_cookie_loadfiles(data);
-    }
+    /* If there is a list of cookie files to read, do it first so that
+       we have all the told files read before we write the new jar.
+       Curl_cookie_loadfiles() LOCKS and UNLOCKS the share itself! */
+    Curl_cookie_loadfiles(data);
 
     Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);
 
@@ -1816,12 +1812,6 @@ void Curl_flush_cookies(struct Curl_easy *data, bool cleanup)
             data->set.str[STRING_COOKIEJAR], curl_easy_strerror(res));
   }
   else {
-    if(cleanup && data->state.cookielist) {
-      /* since nothing is written, we can just free the list of cookie file
-         names */
-      curl_slist_free_all(data->state.cookielist); /* clean up list */
-      data->state.cookielist = NULL;
-    }
     Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);
   }
 
diff --git a/lib/cookie.h b/lib/cookie.h
index abc0a2e..39bb08b 100644
--- a/lib/cookie.h
+++ b/lib/cookie.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/curl_addrinfo.c b/lib/curl_addrinfo.c
index bcea883..35a0635 100644
--- a/lib/curl_addrinfo.c
+++ b/lib/curl_addrinfo.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/curl_addrinfo.h b/lib/curl_addrinfo.h
index b778121..c757c49 100644
--- a/lib/curl_addrinfo.h
+++ b/lib/curl_addrinfo.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/curl_base64.h b/lib/curl_base64.h
index 85368a1..806d443 100644
--- a/lib/curl_base64.h
+++ b/lib/curl_base64.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/curl_config.h.cmake b/lib/curl_config.h.cmake
index 8f8964e..eca71bd 100644
--- a/lib/curl_config.h.cmake
+++ b/lib/curl_config.h.cmake
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/curl_ctype.h b/lib/curl_ctype.h
index dc6b8ca..1d1d60c 100644
--- a/lib/curl_ctype.h
+++ b/lib/curl_ctype.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -27,7 +27,7 @@
 #define ISLOWHEXALHA(x) (((x) >= 'a') && ((x) <= 'f'))
 #define ISUPHEXALHA(x) (((x) >= 'A') && ((x) <= 'F'))
 
-#define ISLOWCNTRL(x) ((x) >= 0 && ((x) <= 0x1f))
+#define ISLOWCNTRL(x) ((unsigned char)(x) <= 0x1f)
 #define IS7F(x) ((x) == 0x7f)
 
 #define ISLOWPRINT(x) (((x) >= 9) && ((x) <= 0x0d))
diff --git a/lib/curl_des.c b/lib/curl_des.c
index a2bf648..5c623b3 100644
--- a/lib/curl_des.c
+++ b/lib/curl_des.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2015 - 2022, Steve Holme, <steve_holme@hotmail.com>.
+ * Copyright (C) Steve Holme, <steve_holme@hotmail.com>.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/lib/curl_des.h b/lib/curl_des.h
index c1c1674..6ec450a 100644
--- a/lib/curl_des.h
+++ b/lib/curl_des.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2015 - 2022, Steve Holme, <steve_holme@hotmail.com>.
+ * Copyright (C) Steve Holme, <steve_holme@hotmail.com>.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/lib/curl_endian.c b/lib/curl_endian.c
index 3cc7734..11c662a 100644
--- a/lib/curl_endian.c
+++ b/lib/curl_endian.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/curl_endian.h b/lib/curl_endian.h
index 08d52e7..fa28321 100644
--- a/lib/curl_endian.h
+++ b/lib/curl_endian.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/curl_fnmatch.c b/lib/curl_fnmatch.c
index b8a85a9..5f9ca4f 100644
--- a/lib/curl_fnmatch.c
+++ b/lib/curl_fnmatch.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/curl_fnmatch.h b/lib/curl_fnmatch.h
index 8324be5..595646f 100644
--- a/lib/curl_fnmatch.h
+++ b/lib/curl_fnmatch.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/curl_get_line.c b/lib/curl_get_line.c
index 0d8c285..686abe7 100644
--- a/lib/curl_get_line.c
+++ b/lib/curl_get_line.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/curl_get_line.h b/lib/curl_get_line.h
index b2a534d..0ff32c5 100644
--- a/lib/curl_get_line.h
+++ b/lib/curl_get_line.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/curl_gethostname.c b/lib/curl_gethostname.c
index 4747e93..706b2e6 100644
--- a/lib/curl_gethostname.c
+++ b/lib/curl_gethostname.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/curl_gethostname.h b/lib/curl_gethostname.h
index b736096..9281d9c 100644
--- a/lib/curl_gethostname.h
+++ b/lib/curl_gethostname.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/curl_gssapi.c b/lib/curl_gssapi.c
index 01ab48e..c4c4f88 100644
--- a/lib/curl_gssapi.c
+++ b/lib/curl_gssapi.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2011 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/curl_gssapi.h b/lib/curl_gssapi.h
index b4ed482..7b9a534 100644
--- a/lib/curl_gssapi.h
+++ b/lib/curl_gssapi.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2011 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/curl_hmac.h b/lib/curl_hmac.h
index 36c0bd6..11625c0 100644
--- a/lib/curl_hmac.h
+++ b/lib/curl_hmac.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/curl_krb5.h b/lib/curl_krb5.h
index ccd6f10..ccf6b96 100644
--- a/lib/curl_krb5.h
+++ b/lib/curl_krb5.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/curl_ldap.h b/lib/curl_ldap.h
index ba3ede4..8a1d807 100644
--- a/lib/curl_ldap.h
+++ b/lib/curl_ldap.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/curl_log.c b/lib/curl_log.c
new file mode 100644
index 0000000..b5ce1c7
--- /dev/null
+++ b/lib/curl_log.c
@@ -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-http.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 */
diff --git a/lib/curl_log.h b/lib/curl_log.h
new file mode 100644
index 0000000..ad6143f
--- /dev/null
+++ b/lib/curl_log.h
@@ -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 */
diff --git a/lib/curl_md4.h b/lib/curl_md4.h
index 8049355..03567b9 100644
--- a/lib/curl_md4.h
+++ b/lib/curl_md4.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/curl_md5.h b/lib/curl_md5.h
index 7893296..ec2512f 100644
--- a/lib/curl_md5.h
+++ b/lib/curl_md5.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/curl_memory.h b/lib/curl_memory.h
index 092fc9f..7af1391 100644
--- a/lib/curl_memory.h
+++ b/lib/curl_memory.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/curl_memrchr.c b/lib/curl_memrchr.c
index c329a61..3f3dc6d 100644
--- a/lib/curl_memrchr.c
+++ b/lib/curl_memrchr.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/curl_memrchr.h b/lib/curl_memrchr.h
index e7654e1..a1a4ba0 100644
--- a/lib/curl_memrchr.h
+++ b/lib/curl_memrchr.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/curl_multibyte.c b/lib/curl_multibyte.c
index 309dccb..522ea34 100644
--- a/lib/curl_multibyte.c
+++ b/lib/curl_multibyte.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/curl_multibyte.h b/lib/curl_multibyte.h
index 9297148..ddac1f6 100644
--- a/lib/curl_multibyte.h
+++ b/lib/curl_multibyte.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/curl_ntlm_core.c b/lib/curl_ntlm_core.c
index 690f8f7..25d2526 100644
--- a/lib/curl_ntlm_core.c
+++ b/lib/curl_ntlm_core.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -36,12 +36,13 @@
 /* Please keep the SSL backend-specific #if branches in this order:
 
    1. USE_OPENSSL
-   2. USE_GNUTLS
-   3. USE_NSS
-   4. USE_MBEDTLS
-   5. USE_SECTRANSP
-   6. USE_OS400CRYPTO
-   7. USE_WIN32_CRYPTO
+   2. USE_WOLFSSL
+   3. USE_GNUTLS
+   4. USE_NSS
+   5. USE_MBEDTLS
+   6. USE_SECTRANSP
+   7. USE_OS400CRYPTO
+   8. USE_WIN32_CRYPTO
 
    This ensures that:
    - the same SSL branch gets activated throughout this source
diff --git a/lib/curl_ntlm_core.h b/lib/curl_ntlm_core.h
index 60444c9..33b651f 100644
--- a/lib/curl_ntlm_core.h
+++ b/lib/curl_ntlm_core.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -37,11 +37,11 @@
 #define NTLM_NEEDS_NSS_INIT
 #endif
 
-#ifdef USE_WOLFSSL
+#if defined(USE_OPENSSL)
+#  include <openssl/ssl.h>
+#elif defined(USE_WOLFSSL)
 #  include <wolfssl/options.h>
 #  include <wolfssl/openssl/ssl.h>
-#elif defined(USE_OPENSSL)
-#  include <openssl/ssl.h>
 #endif
 
 /* Helpers to generate function byte arguments in little endian order */
diff --git a/lib/curl_ntlm_wb.c b/lib/curl_ntlm_wb.c
index fcf5075..a10e2a1 100644
--- a/lib/curl_ntlm_wb.c
+++ b/lib/curl_ntlm_wb.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/curl_ntlm_wb.h b/lib/curl_ntlm_wb.h
index 1f04db8..37704c0 100644
--- a/lib/curl_ntlm_wb.h
+++ b/lib/curl_ntlm_wb.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/curl_path.c b/lib/curl_path.c
index f00e3ee..2df8687 100644
--- a/lib/curl_path.c
+++ b/lib/curl_path.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/curl_path.h b/lib/curl_path.h
index 98e56ea..9ed09de 100644
--- a/lib/curl_path.h
+++ b/lib/curl_path.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/curl_printf.h b/lib/curl_printf.h
index 3823828..6d3d492 100644
--- a/lib/curl_printf.h
+++ b/lib/curl_printf.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/curl_range.c b/lib/curl_range.c
index 4999936..d499953 100644
--- a/lib/curl_range.c
+++ b/lib/curl_range.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/curl_range.h b/lib/curl_range.h
index 33570ab..77679e2 100644
--- a/lib/curl_range.h
+++ b/lib/curl_range.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/curl_rtmp.c b/lib/curl_rtmp.c
index 1932cb4..2679a2c 100644
--- a/lib/curl_rtmp.c
+++ b/lib/curl_rtmp.c
@@ -5,8 +5,8 @@
  *                | (__| |_| |  _ <| |___
  *                 \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
- * Copyright (C) 2012, Howard Chu, <hyc@highlandsun.com>
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Howard Chu, <hyc@highlandsun.com>
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/lib/curl_rtmp.h b/lib/curl_rtmp.h
index f856085..9b93ee0 100644
--- a/lib/curl_rtmp.h
+++ b/lib/curl_rtmp.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2010 - 2022, Howard Chu, <hyc@highlandsun.com>
+ * Copyright (C) Howard Chu, <hyc@highlandsun.com>
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/lib/curl_sasl.c b/lib/curl_sasl.c
index 46ee800..119fb9b 100644
--- a/lib/curl_sasl.c
+++ b/lib/curl_sasl.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -36,7 +36,8 @@
 #include "curl_setup.h"
 
 #if !defined(CURL_DISABLE_IMAP) || !defined(CURL_DISABLE_SMTP) || \
-  !defined(CURL_DISABLE_POP3)
+  !defined(CURL_DISABLE_POP3) || \
+  (!defined(CURL_DISABLE_LDAP) && defined(USE_OPENLDAP))
 
 #include <curl/curl.h>
 #include "urldata.h"
diff --git a/lib/curl_sasl.h b/lib/curl_sasl.h
index c709d56..e94e643 100644
--- a/lib/curl_sasl.h
+++ b/lib/curl_sasl.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -125,9 +125,9 @@ struct SASL {
   unsigned short authmechs;  /* Accepted authentication mechanisms */
   unsigned short prefmech;   /* Preferred authentication mechanism */
   unsigned short authused;   /* Auth mechanism used for the connection */
-  bool resetprefs;           /* For URL auth option parsing. */
-  bool mutual_auth;          /* Mutual authentication enabled (GSSAPI only) */
-  bool force_ir;             /* Protocol always supports initial response */
+  BIT(resetprefs);           /* For URL auth option parsing. */
+  BIT(mutual_auth);          /* Mutual authentication enabled (GSSAPI only) */
+  BIT(force_ir);             /* Protocol always supports initial response */
 };
 
 /* This is used to test whether the line starts with the given mechanism */
diff --git a/lib/curl_setup.h b/lib/curl_setup.h
index 434b86c..a8cc715 100644
--- a/lib/curl_setup.h
+++ b/lib/curl_setup.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -777,14 +777,16 @@ endings either CRLF or LF so 't' is appropriate.
 #define FOPEN_APPENDTEXT "a"
 #endif
 
-/* WinSock destroys recv() buffer when send() failed.
- * Enabled automatically for Windows and for Cygwin as Cygwin sockets are
- * wrappers for WinSock sockets. https://github.com/curl/curl/issues/657
- * Define DONT_USE_RECV_BEFORE_SEND_WORKAROUND to force disable workaround.
+/* Windows workaround to recv before every send, because apparently Winsock
+ * destroys destroys recv() buffer when send() failed.
+ * This workaround is now disabled by default since it caused hard to fix bugs.
+ * Define USE_RECV_BEFORE_SEND_WORKAROUND to enable it.
+ * https://github.com/curl/curl/issues/657
+ * https://github.com/curl/curl/pull/10409
  */
 #if !defined(DONT_USE_RECV_BEFORE_SEND_WORKAROUND)
 #  if defined(WIN32) || defined(__CYGWIN__)
-#    define USE_RECV_BEFORE_SEND_WORKAROUND
+/* #    define USE_RECV_BEFORE_SEND_WORKAROUND */
 #  endif
 #else  /* DONT_USE_RECV_BEFORE_SEND_WORKAROUND */
 #  ifdef USE_RECV_BEFORE_SEND_WORKAROUND
@@ -836,6 +838,13 @@ int getpwuid_r(uid_t uid, struct passwd *pwd, char *buf,
 #define USE_HTTP3
 #endif
 
+/* Certain Windows implementations are not aligned with what curl expects,
+   so always use the local one on this platform. E.g. the mingw-w64
+   implementation can return wrong results for non-ASCII inputs. */
+#if defined(HAVE_BASENAME) && defined(WIN32)
+#undef HAVE_BASENAME
+#endif
+
 #if defined(USE_UNIX_SOCKETS) && defined(WIN32)
 #  if defined(__MINGW32__) && !defined(LUP_SECURE)
      typedef u_short ADDRESS_FAMILY; /* Classic mingw, 11y+ old mingw-w64 */
@@ -853,4 +862,10 @@ int getpwuid_r(uid_t uid, struct passwd *pwd, char *buf,
 #  endif
 #endif
 
+/* OpenSSLv3 marks DES, MD5 and ENGINE functions deprecated but we have no
+   replacements (yet) so tell the compiler to not warn for them. */
+#ifdef USE_OPENSSL
+#define OPENSSL_SUPPRESS_DEPRECATED
+#endif
+
 #endif /* HEADER_CURL_SETUP_H */
diff --git a/lib/curl_setup_once.h b/lib/curl_setup_once.h
index ac4a7f1..e68eb50 100644
--- a/lib/curl_setup_once.h
+++ b/lib/curl_setup_once.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/curl_sha256.h b/lib/curl_sha256.h
index 39523af..c5e157b 100644
--- a/lib/curl_sha256.h
+++ b/lib/curl_sha256.h
@@ -7,8 +7,8 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2017, Florin Petriuc, <petriuc.florin@gmail.com>
- * Copyright (C) 2018 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Florin Petriuc, <petriuc.florin@gmail.com>
+ * 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
diff --git a/lib/curl_sspi.c b/lib/curl_sspi.c
index 33108c4..eb21e7e 100644
--- a/lib/curl_sspi.c
+++ b/lib/curl_sspi.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/curl_sspi.h b/lib/curl_sspi.h
index ad11130..9816d59 100644
--- a/lib/curl_sspi.h
+++ b/lib/curl_sspi.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/curl_threads.c b/lib/curl_threads.c
index dff6167..e13e294 100644
--- a/lib/curl_threads.c
+++ b/lib/curl_threads.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/curl_threads.h b/lib/curl_threads.h
index 63392f6..facbc73 100644
--- a/lib/curl_threads.h
+++ b/lib/curl_threads.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/curlx.h b/lib/curlx.h
index 1796afa..7a753d6 100644
--- a/lib/curlx.h
+++ b/lib/curlx.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/dict.c b/lib/dict.c
index 993373e..0ce62a0 100644
--- a/lib/dict.c
+++ b/lib/dict.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -98,37 +98,27 @@ const struct Curl_handler Curl_handler_dict = {
   PROTOPT_NONE | PROTOPT_NOURLQUERY     /* flags */
 };
 
-static char *unescape_word(const char *inputbuff)
+#define DYN_DICT_WORD 10000
+static char *unescape_word(const char *input)
 {
-  char *newp = NULL;
-  char *dictp;
-  size_t len;
-
-  CURLcode result = Curl_urldecode(inputbuff, 0, &newp, &len,
-                                   REJECT_NADA);
-  if(!newp || result)
-    return NULL;
-
-  dictp = malloc(len*2 + 1); /* add one for terminating zero */
-  if(dictp) {
-    char *ptr;
-    char ch;
-    int olen = 0;
-    /* According to RFC2229 section 2.2, these letters need to be escaped with
-       \[letter] */
-    for(ptr = newp;
-        (ch = *ptr) != 0;
-        ptr++) {
-      if((ch <= 32) || (ch == 127) ||
-          (ch == '\'') || (ch == '\"') || (ch == '\\')) {
-        dictp[olen++] = '\\';
-      }
-      dictp[olen++] = ch;
-    }
-    dictp[olen] = 0;
+  struct dynbuf out;
+  const char *ptr;
+  CURLcode result = CURLE_OK;
+  Curl_dyn_init(&out, DYN_DICT_WORD);
+
+  /* According to RFC2229 section 2.2, these letters need to be escaped with
+     \[letter] */
+  for(ptr = input; *ptr; ptr++) {
+    char ch = *ptr;
+    if((ch <= 32) || (ch == 127) ||
+       (ch == '\'') || (ch == '\"') || (ch == '\\'))
+      result = Curl_dyn_addn(&out, "\\", 1);
+    if(!result)
+      result = Curl_dyn_addn(&out, ptr, 1);
+    if(result)
+      return NULL;
   }
-  free(newp);
-  return dictp;
+  return Curl_dyn_ptr(&out);
 }
 
 /* sendf() sends formatted data to the server */
@@ -178,20 +168,25 @@ static CURLcode sendf(curl_socket_t sockfd, struct Curl_easy *data,
 static CURLcode dict_do(struct Curl_easy *data, bool *done)
 {
   char *word;
-  char *eword;
+  char *eword = NULL;
   char *ppath;
   char *database = NULL;
   char *strategy = NULL;
   char *nthdef = NULL; /* This is not part of the protocol, but required
                           by RFC 2229 */
-  CURLcode result = CURLE_OK;
+  CURLcode result;
   struct connectdata *conn = data->conn;
   curl_socket_t sockfd = conn->sock[FIRSTSOCKET];
 
-  char *path = data->state.up.path;
+  char *path;
 
   *done = TRUE; /* unconditionally */
 
+  /* url-decode path before further evaluation */
+  result = Curl_urldecode(data->state.up.path, 0, &path, NULL, REJECT_CTRL);
+  if(result)
+    return result;
+
   if(strncasecompare(path, DICT_MATCH, sizeof(DICT_MATCH)-1) ||
      strncasecompare(path, DICT_MATCH2, sizeof(DICT_MATCH2)-1) ||
      strncasecompare(path, DICT_MATCH3, sizeof(DICT_MATCH3)-1)) {
@@ -225,8 +220,10 @@ static CURLcode dict_do(struct Curl_easy *data, bool *done)
     }
 
     eword = unescape_word(word);
-    if(!eword)
-      return CURLE_OUT_OF_MEMORY;
+    if(!eword) {
+      result = CURLE_OUT_OF_MEMORY;
+      goto error;
+    }
 
     result = sendf(sockfd, data,
                    "CLIENT " LIBCURL_NAME " " LIBCURL_VERSION "\r\n"
@@ -239,11 +236,9 @@ static CURLcode dict_do(struct Curl_easy *data, bool *done)
                    strategy,
                    eword);
 
-    free(eword);
-
     if(result) {
       failf(data, "Failed sending DICT request");
-      return result;
+      goto error;
     }
     Curl_setup_transfer(data, FIRSTSOCKET, -1, FALSE, -1); /* no upload */
   }
@@ -273,8 +268,10 @@ static CURLcode dict_do(struct Curl_easy *data, bool *done)
     }
 
     eword = unescape_word(word);
-    if(!eword)
-      return CURLE_OUT_OF_MEMORY;
+    if(!eword) {
+      result = CURLE_OUT_OF_MEMORY;
+      goto error;
+    }
 
     result = sendf(sockfd, data,
                    "CLIENT " LIBCURL_NAME " " LIBCURL_VERSION "\r\n"
@@ -285,11 +282,9 @@ static CURLcode dict_do(struct Curl_easy *data, bool *done)
                    database,
                    eword);
 
-    free(eword);
-
     if(result) {
       failf(data, "Failed sending DICT request");
-      return result;
+      goto error;
     }
     Curl_setup_transfer(data, FIRSTSOCKET, -1, FALSE, -1);
   }
@@ -310,13 +305,16 @@ static CURLcode dict_do(struct Curl_easy *data, bool *done)
                      "QUIT\r\n", ppath);
       if(result) {
         failf(data, "Failed sending DICT request");
-        return result;
+        goto error;
       }
 
       Curl_setup_transfer(data, FIRSTSOCKET, -1, FALSE, -1);
     }
   }
 
-  return CURLE_OK;
+  error:
+  free(eword);
+  free(path);
+  return result;
 }
 #endif /* CURL_DISABLE_DICT */
diff --git a/lib/dict.h b/lib/dict.h
index b283a0d..ba9a927 100644
--- a/lib/dict.h
+++ b/lib/dict.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/doh.c b/lib/doh.c
index 3b1d5d6..2e6a377 100644
--- a/lib/doh.c
+++ b/lib/doh.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2018 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -396,6 +396,7 @@ struct Curl_addrinfo *Curl_doh(struct Curl_easy *data,
     goto error;
   dohp->pending++;
 
+#ifdef ENABLE_IPV6
   if((conn->ip_version != CURL_IPRESOLVE_V4) && Curl_ipv6works(data)) {
     /* create IPv6 DoH request */
     result = dohprobe(data, &dohp->probe[DOH_PROBE_SLOT_IPADDR_V6],
@@ -405,6 +406,7 @@ struct Curl_addrinfo *Curl_doh(struct Curl_easy *data,
       goto error;
     dohp->pending++;
   }
+#endif
   return NULL;
 
   error:
diff --git a/lib/doh.h b/lib/doh.h
index 678e807..7d7b694 100644
--- a/lib/doh.h
+++ b/lib/doh.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2018 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/dynbuf.c b/lib/dynbuf.c
index 0b1cf9a..847f6f6 100644
--- a/lib/dynbuf.c
+++ b/lib/dynbuf.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2020 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/dynbuf.h b/lib/dynbuf.h
index 04a728c..57ad62b 100644
--- a/lib/dynbuf.h
+++ b/lib/dynbuf.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2020 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/easy.c b/lib/easy.c
index d7f93be..db03026 100644
--- a/lib/easy.c
+++ b/lib/easy.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -65,6 +65,7 @@
 #include "easyif.h"
 #include "multiif.h"
 #include "select.h"
+#include "cfilters.h"
 #include "sendf.h" /* for failf function prototype */
 #include "connect.h" /* for Curl_getconnectinfo */
 #include "slist.h"
@@ -113,7 +114,7 @@ static curl_simple_lock s_lock = CURL_SIMPLE_LOCK_INIT;
 #if defined(_WIN32_WCE)
 #define system_strdup _strdup
 #elif !defined(HAVE_STRDUP)
-#define system_strdup curlx_strdup
+#define system_strdup Curl_strdup
 #else
 #define system_strdup strdup
 #endif
@@ -164,6 +165,11 @@ static CURLcode global_init(long flags, bool memoryfuncs)
 #endif
   }
 
+  if(Curl_log_init()) {
+    DEBUGF(fprintf(stderr, "Error: Curl_log_init failed\n"));
+    goto fail;
+  }
+
   if(!Curl_ssl_init()) {
     DEBUGF(fprintf(stderr, "Error: Curl_ssl_init failed\n"));
     goto fail;
@@ -913,11 +919,9 @@ struct Curl_easy *curl_easy_duphandle(struct Curl_easy *data)
       goto fail;
   }
 
-  /* duplicate all values in 'change' */
-  if(data->state.cookielist) {
-    outcurl->state.cookielist =
-      Curl_slist_duplicate(data->state.cookielist);
-    if(!outcurl->state.cookielist)
+  if(data->set.cookielist) {
+    outcurl->set.cookielist = Curl_slist_duplicate(data->set.cookielist);
+    if(!outcurl->set.cookielist)
       goto fail;
   }
 #endif
@@ -1003,8 +1007,8 @@ struct Curl_easy *curl_easy_duphandle(struct Curl_easy *data)
 
   if(outcurl) {
 #ifndef CURL_DISABLE_COOKIES
-    curl_slist_free_all(outcurl->state.cookielist);
-    outcurl->state.cookielist = NULL;
+    curl_slist_free_all(outcurl->set.cookielist);
+    outcurl->set.cookielist = NULL;
 #endif
     Curl_safefree(outcurl->state.buffer);
     Curl_dyn_free(&outcurl->state.headerb);
@@ -1101,7 +1105,7 @@ CURLcode curl_easy_pause(struct Curl_easy *data, int action)
   k->keepon = newstate;
 
   if(!(newstate & KEEP_RECV_PAUSE)) {
-    Curl_http2_stream_pause(data, FALSE);
+    Curl_conn_ev_data_pause(data, FALSE);
 
     if(data->state.tempcount) {
       /* there are buffers for sending that can be delivered as the receive
@@ -1224,7 +1228,7 @@ CURLcode curl_easy_recv(struct Curl_easy *data, void *buffer, size_t buflen,
     return result;
 
   *n = (size_t)n1;
-
+  infof(data, "reached %s:%d", __FILE__, __LINE__);
   return CURLE_OK;
 }
 
@@ -1294,29 +1298,34 @@ static int conn_upkeep(struct Curl_easy *data,
                        struct connectdata *conn,
                        void *param)
 {
-  /* Param is unused. */
-  (void)param;
+  struct curltime *now = param;
 
-  if(conn->handler->connection_check) {
-    /* briefly attach the connection to this transfer for the purpose of
-       checking it */
-    Curl_attach_connection(data, conn);
+  if(Curl_timediff(*now, conn->keepalive) <= data->set.upkeep_interval_ms)
+    return 0;
 
+  /* briefly attach for action */
+  Curl_attach_connection(data, conn);
+  if(conn->handler->connection_check) {
     /* Do a protocol-specific keepalive check on the connection. */
     conn->handler->connection_check(data, conn, CONNCHECK_KEEPALIVE);
-    /* detach the connection again */
-    Curl_detach_connection(data);
   }
+  else {
+    /* Do the generic action on the FIRSTSOCKE filter chain */
+    Curl_conn_keep_alive(data, conn, FIRSTSOCKET);
+  }
+  Curl_detach_connection(data);
 
+  conn->keepalive = *now;
   return 0; /* continue iteration */
 }
 
 static CURLcode upkeep(struct conncache *conn_cache, void *data)
 {
+  struct curltime now = Curl_now();
   /* Loop over every connection and make connection alive. */
   Curl_conncache_foreach(data,
                          conn_cache,
-                         data,
+                         &now,
                          conn_upkeep);
   return CURLE_OK;
 }
diff --git a/lib/easy_lock.h b/lib/easy_lock.h
index d96e56b..5fa9477 100644
--- a/lib/easy_lock.h
+++ b/lib/easy_lock.h
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/easygetopt.c b/lib/easygetopt.c
index a639bb3..2b8a521 100644
--- a/lib/easygetopt.c
+++ b/lib/easygetopt.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             ___|___/|_| ______|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel.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
diff --git a/lib/easyif.h b/lib/easyif.h
index 205382c..570ebef 100644
--- a/lib/easyif.h
+++ b/lib/easyif.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/easyoptions.c b/lib/easyoptions.c
index 03b814e..a9c1efd 100644
--- a/lib/easyoptions.c
+++ b/lib/easyoptions.c
@@ -1,11 +1,11 @@
 /***************************************************************************
  *                                  _   _ ____  _
- *  Project                     ___| | | |  _ | |
+ *  Project                     ___| | | |  _ \| |
  *                             / __| | | | |_) | |
  *                            | (__| |_| |  _ <| |___
- *                             ___|___/|_| ______|
+ *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel.se>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel.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
diff --git a/lib/easyoptions.h b/lib/easyoptions.h
index 33f816d..24b4cd9 100644
--- a/lib/easyoptions.h
+++ b/lib/easyoptions.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/escape.c b/lib/escape.c
index ed59838..56aa2b3 100644
--- a/lib/escape.c
+++ b/lib/escape.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -97,7 +97,7 @@ char *curl_easy_escape(struct Curl_easy *data, const char *string,
     return strdup("");
 
   while(length--) {
-    unsigned char in = *string; /* we need to treat the characters unsigned */
+    unsigned char in = *string++; /* treat the characters unsigned */
 
     if(Curl_isunreserved(in)) {
       /* append this */
@@ -106,15 +106,28 @@ char *curl_easy_escape(struct Curl_easy *data, const char *string,
     }
     else {
       /* encode it */
-      if(Curl_dyn_addf(&d, "%%%02X", in))
+      const char hex[] = "0123456789ABCDEF";
+      char out[3]={'%'};
+      out[1] = hex[in>>4];
+      out[2] = hex[in & 0xf];
+      if(Curl_dyn_addn(&d, out, 3))
         return NULL;
     }
-    string++;
   }
 
   return Curl_dyn_ptr(&d);
 }
 
+static const unsigned char hextable[] = {
+  0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0,       /* 0x30 - 0x3f */
+  0, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x40 - 0x4f */
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,       /* 0x50 - 0x5f */
+  0, 10, 11, 12, 13, 14, 15                             /* 0x60 - 0x66 */
+};
+
+/* the input is a single hex digit */
+#define onehex2dec(x) hextable[x - '0']
+
 /*
  * Curl_urldecode() URL decodes the given string.
  *
@@ -137,54 +150,47 @@ CURLcode Curl_urldecode(const char *string, size_t length,
 {
   size_t alloc;
   char *ns;
-  size_t strindex = 0;
-  unsigned long hex;
 
   DEBUGASSERT(string);
   DEBUGASSERT(ctrl >= REJECT_NADA); /* crash on TRUE/FALSE */
 
-  alloc = (length?length:strlen(string)) + 1;
-  ns = malloc(alloc);
+  alloc = (length?length:strlen(string));
+  ns = malloc(alloc + 1);
 
   if(!ns)
     return CURLE_OUT_OF_MEMORY;
 
-  while(--alloc > 0) {
+  /* store output string */
+  *ostring = ns;
+
+  while(alloc) {
     unsigned char in = *string;
     if(('%' == in) && (alloc > 2) &&
        ISXDIGIT(string[1]) && ISXDIGIT(string[2])) {
       /* this is two hexadecimal digits following a '%' */
-      char hexstr[3];
-      char *ptr;
-      hexstr[0] = string[1];
-      hexstr[1] = string[2];
-      hexstr[2] = 0;
-
-      hex = strtoul(hexstr, &ptr, 16);
+      in = (unsigned char)(onehex2dec(string[1]) << 4) | onehex2dec(string[2]);
 
-      in = curlx_ultouc(hex); /* this long is never bigger than 255 anyway */
-
-      string += 2;
-      alloc -= 2;
+      string += 3;
+      alloc -= 3;
+    }
+    else {
+      string++;
+      alloc--;
     }
 
     if(((ctrl == REJECT_CTRL) && (in < 0x20)) ||
        ((ctrl == REJECT_ZERO) && (in == 0))) {
-      free(ns);
+      Curl_safefree(*ostring);
       return CURLE_URL_MALFORMAT;
     }
 
-    ns[strindex++] = in;
-    string++;
+    *ns++ = in;
   }
-  ns[strindex] = 0; /* terminate it */
+  *ns = 0; /* terminate it */
 
   if(olen)
     /* store output size */
-    *olen = strindex;
-
-  /* store output string */
-  *ostring = ns;
+    *olen = ns - *ostring;
 
   return CURLE_OK;
 }
diff --git a/lib/escape.h b/lib/escape.h
index 61d4611..cdbb712 100644
--- a/lib/escape.h
+++ b/lib/escape.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/file.c b/lib/file.c
index 3f642be..51c5d07 100644
--- a/lib/file.c
+++ b/lib/file.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/file.h b/lib/file.h
index 826d453..4565525 100644
--- a/lib/file.h
+++ b/lib/file.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/fileinfo.c b/lib/fileinfo.c
index 7bbf24b..409b38f 100644
--- a/lib/fileinfo.c
+++ b/lib/fileinfo.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2010 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/fileinfo.h b/lib/fileinfo.h
index 5bad718..af44212 100644
--- a/lib/fileinfo.h
+++ b/lib/fileinfo.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2010 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/fopen.c b/lib/fopen.c
index ad3691b..f710dbf 100644
--- a/lib/fopen.c
+++ b/lib/fopen.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -106,7 +106,6 @@ fail:
 
   free(tempstore);
 
-  *tempname = NULL;
   return result;
 }
 
diff --git a/lib/fopen.h b/lib/fopen.h
index 289e55f..e3a919d 100644
--- a/lib/fopen.h
+++ b/lib/fopen.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/formdata.c b/lib/formdata.c
index b30e8de..2bdb9f2 100644
--- a/lib/formdata.c
+++ b/lib/formdata.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/formdata.h b/lib/formdata.h
index c6c6397..caabb63 100644
--- a/lib/formdata.h
+++ b/lib/formdata.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/ftp.c b/lib/ftp.c
index 8f0ac2e..7766f76 100644
--- a/lib/ftp.c
+++ b/lib/ftp.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -61,6 +61,7 @@
 #include "strcase.h"
 #include "vtls/vtls.h"
 #include "cfilters.h"
+#include "cf-socket.h"
 #include "connect.h"
 #include "strerror.h"
 #include "inet_ntop.h"
@@ -285,8 +286,8 @@ static CURLcode AcceptServerConnect(struct Curl_easy *data)
   conn->bits.do_more = FALSE;
 
   (void)curlx_nonblock(s, TRUE); /* enable non-blocking */
-  /* Replace any filter on SECONDARY with one listeing on this socket */
-  result = Curl_conn_socket_accepted_set(data, conn, SECONDARYSOCKET, &s);
+  /* Replace any filter on SECONDARY with one listening on this socket */
+  result = Curl_conn_tcp_accepted_set(data, conn, SECONDARYSOCKET, &s);
   if(result)
     return result;
 
@@ -819,26 +820,11 @@ static int ftp_domore_getsock(struct Curl_easy *data,
 
   if(FTP_STOP == ftpc->state) {
     int bits = GETSOCK_READSOCK(0);
-    bool any = FALSE;
 
     /* if stopped and still in this state, then we're also waiting for a
        connect on the secondary connection */
     socks[0] = conn->sock[FIRSTSOCKET];
-
-    if(!data->set.ftp_use_port) {
-      int s;
-      int i;
-      /* PORT is used to tell the server to connect to us, and during that we
-         don't do happy eyeballs, but we do if we connect to the server */
-      for(s = 1, i = 0; i<2; i++) {
-        if(conn->tempsock[i] != CURL_SOCKET_BAD) {
-          socks[s] = conn->tempsock[i];
-          bits |= GETSOCK_WRITESOCK(s++);
-          any = TRUE;
-        }
-      }
-    }
-    if(!any) {
+    if(conn->sock[SECONDARYSOCKET] != CURL_SOCKET_BAD) {
       socks[1] = conn->sock[SECONDARYSOCKET];
       bits |= GETSOCK_WRITESOCK(1) | GETSOCK_READSOCK(1);
     }
@@ -1024,9 +1010,9 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data,
 
     if(*addr != '\0') {
       /* attempt to get the address of the given interface name */
-      switch(Curl_if2ip(conn->ip_addr->ai_family,
+      switch(Curl_if2ip(conn->remote_addr->family,
 #ifdef ENABLE_IPV6
-                        Curl_ipv6_scope(conn->ip_addr->ai_addr),
+                        Curl_ipv6_scope(&conn->remote_addr->sa_addr),
                         conn->scope_id,
 #endif
                         addr, hbuf, sizeof(hbuf))) {
@@ -1097,7 +1083,7 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data,
   portsock = CURL_SOCKET_BAD;
   error = 0;
   for(ai = res; ai; ai = ai->ai_next) {
-    if(Curl_socket(data, ai, NULL, &portsock)) {
+    if(Curl_socket_open(data, ai, NULL, conn->transport, &portsock)) {
       error = SOCKERRNO;
       continue;
     }
@@ -1266,9 +1252,8 @@ static CURLcode ftp_state_use_port(struct Curl_easy *data,
   /* store which command was sent */
   ftpc->count1 = fcmd;
 
-  /* Replace any filter on SECONDARY with one listeing on this socket */
-  result = Curl_conn_socket_accepted_set(data, conn, SECONDARYSOCKET,
-                                         &portsock);
+  /* Replace any filter on SECONDARY with one listening on this socket */
+  result = Curl_conn_tcp_listen_set(data, conn, SECONDARYSOCKET, &portsock);
   if(result)
     goto out;
   portsock = CURL_SOCKET_BAD; /* now held in filter */
@@ -1279,7 +1264,7 @@ out:
     state(data, FTP_STOP);
   }
   if(portsock != CURL_SOCKET_BAD)
-    Curl_closesocket(data, conn, portsock);
+    Curl_socket_close(data, conn, portsock);
   free(addr);
   return result;
 }
@@ -1954,7 +1939,7 @@ static CURLcode ftp_state_pasv_resp(struct Curl_easy *data,
 
     /* postponed address resolution in case of tcp fastopen */
     if(conn->bits.tcp_fastopen && !conn->bits.reuse && !ftpc->newhost[0]) {
-      Curl_conninfo_remote(data, conn, conn->sock[FIRSTSOCKET]);
+      Curl_conn_ev_update_info(data, conn);
       Curl_safefree(ftpc->newhost);
       ftpc->newhost = strdup(control_address(conn));
       if(!ftpc->newhost)
@@ -2569,13 +2554,11 @@ static CURLcode ftp_state_loggedin(struct Curl_easy *data)
 
 /* for USER and PASS responses */
 static CURLcode ftp_state_user_resp(struct Curl_easy *data,
-                                    int ftpcode,
-                                    ftpstate instate)
+                                    int ftpcode)
 {
   CURLcode result = CURLE_OK;
   struct connectdata *conn = data->conn;
   struct ftp_conn *ftpc = &conn->proto.ftpc;
-  (void)instate; /* no use for this yet */
 
   /* some need password anyway, and others just return 2xx ignored */
   if((ftpcode == 331) && (ftpc->state == FTP_USER)) {
@@ -2670,7 +2653,7 @@ static CURLcode ftp_statemachine(struct Curl_easy *data,
         /* 230 User logged in - already! Take as 220 if TLS required. */
         if(data->set.use_ssl <= CURLUSESSL_TRY ||
            conn->bits.ftp_use_control_ssl)
-          return ftp_state_user_resp(data, ftpcode, ftpc->state);
+          return ftp_state_user_resp(data, ftpcode);
       }
       else if(ftpcode != 220) {
         failf(data, "Got a %03d ftp-server response when 220 was expected",
@@ -2742,7 +2725,7 @@ static CURLcode ftp_statemachine(struct Curl_easy *data,
       if((ftpcode == 234) || (ftpcode == 334)) {
         /* this was BLOCKING, keep it so for now */
         bool done;
-        if(!Curl_conn_is_ssl(data, FIRSTSOCKET)) {
+        if(!Curl_conn_is_ssl(conn, FIRSTSOCKET)) {
           result = Curl_ssl_cfilter_add(data, conn, FIRSTSOCKET);
           if(result) {
             /* we failed and bail out */
@@ -2775,7 +2758,7 @@ static CURLcode ftp_statemachine(struct Curl_easy *data,
 
     case FTP_USER:
     case FTP_PASS:
-      result = ftp_state_user_resp(data, ftpcode, ftpc->state);
+      result = ftp_state_user_resp(data, ftpcode);
       break;
 
     case FTP_ACCT:
diff --git a/lib/ftp.h b/lib/ftp.h
index 7f6f432..65efa6f 100644
--- a/lib/ftp.h
+++ b/lib/ftp.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -42,7 +42,7 @@ CURLcode Curl_GetFTPResponse(struct Curl_easy *data, ssize_t *nread,
 /****************************************************************************
  * FTP unique setup
  ***************************************************************************/
-typedef enum {
+enum {
   FTP_STOP,    /* do nothing state, stops the state machine */
   FTP_WAIT220, /* waiting for the initial 220 response immediately after
                   a connect */
@@ -80,7 +80,8 @@ typedef enum {
   FTP_STOR, /* generic state for STOR and APPE */
   FTP_QUIT,
   FTP_LAST  /* never used */
-} ftpstate;
+};
+typedef unsigned char ftpstate; /* use the enum values */
 
 struct ftp_parselist_data; /* defined later in ftplistparser.c */
 
@@ -122,38 +123,38 @@ struct ftp_conn {
   char *entrypath; /* the PWD reply when we logged on */
   char *file;    /* url-decoded file name (or path) */
   char **dirs;   /* realloc()ed array for path components */
-  int dirdepth;  /* number of entries used in the 'dirs' array */
-  bool dont_check;  /* Set to TRUE to prevent the final (post-transfer)
-                       file size and 226/250 status check. It should still
-                       read the line, just ignore the result. */
-  bool ctl_valid;   /* Tells Curl_ftp_quit() whether or not to do anything. If
-                       the connection has timed out or been closed, this
-                       should be FALSE when it gets to Curl_ftp_quit() */
-  bool cwddone;     /* if it has been determined that the proper CWD combo
-                       already has been done */
-  int cwdcount;     /* number of CWD commands issued */
-  bool cwdfail;     /* set TRUE if a CWD command fails, as then we must prevent
-                       caching the current directory */
-  bool wait_data_conn; /* this is set TRUE if data connection is waited */
-  /* newhost is the (allocated) IP addr or host name to connect the data
-     connection to */
-  unsigned short newport;
   char *newhost;
   char *prevpath;   /* url-decoded conn->path from the previous transfer */
   char transfertype; /* set by ftp_transfertype for use by Curl_client_write()a
                         and others (A/I or zero) */
-  int count1; /* general purpose counter for the state machine */
-  int count2; /* general purpose counter for the state machine */
-  int count3; /* general purpose counter for the state machine */
-  ftpstate state; /* always use ftp.c:state() to change state! */
-  ftpstate state_saved; /* transfer type saved to be reloaded after
-                           data connection is established */
   curl_off_t retr_size_saved; /* Size of retrieved file saved */
   char *server_os;     /* The target server operating system. */
   curl_off_t known_filesize; /* file size is different from -1, if wildcard
                                 LIST parsing was done and wc_statemach set
                                 it */
+  int dirdepth;  /* number of entries used in the 'dirs' array */
+  int cwdcount;     /* number of CWD commands issued */
+  int count1; /* general purpose counter for the state machine */
+  int count2; /* general purpose counter for the state machine */
+  int count3; /* general purpose counter for the state machine */
+  /* newhost is the (allocated) IP addr or host name to connect the data
+     connection to */
+  unsigned short newport;
+  ftpstate state; /* always use ftp.c:state() to change state! */
+  ftpstate state_saved; /* transfer type saved to be reloaded after data
+                           connection is established */
   BIT(ftp_trying_alternative);
+  BIT(dont_check);  /* Set to TRUE to prevent the final (post-transfer)
+                       file size and 226/250 status check. It should still
+                       read the line, just ignore the result. */
+  BIT(ctl_valid);   /* Tells Curl_ftp_quit() whether or not to do anything. If
+                       the connection has timed out or been closed, this
+                       should be FALSE when it gets to Curl_ftp_quit() */
+  BIT(cwddone);     /* if it has been determined that the proper CWD combo
+                       already has been done */
+  BIT(cwdfail);     /* set TRUE if a CWD command fails, as then we must prevent
+                       caching the current directory */
+  BIT(wait_data_conn); /* this is set TRUE if data connection is waited */
 };
 
 #define DEFAULT_ACCEPT_TIMEOUT   60000 /* milliseconds == one minute */
diff --git a/lib/ftplistparser.c b/lib/ftplistparser.c
index 3d529ef..f7c6434 100644
--- a/lib/ftplistparser.c
+++ b/lib/ftplistparser.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/ftplistparser.h b/lib/ftplistparser.h
index 0a80543..3d9a43b 100644
--- a/lib/ftplistparser.h
+++ b/lib/ftplistparser.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/functypes.h b/lib/functypes.h
index 8891b1d..075c02e 100644
--- a/lib/functypes.h
+++ b/lib/functypes.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/getenv.c b/lib/getenv.c
index 5f00fd1..8069784 100644
--- a/lib/getenv.c
+++ b/lib/getenv.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/getinfo.c b/lib/getinfo.c
index 3a24c65..826ffd0 100644
--- a/lib/getinfo.c
+++ b/lib/getinfo.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/getinfo.h b/lib/getinfo.h
index 1b5e8c2..56bb440 100644
--- a/lib/getinfo.h
+++ b/lib/getinfo.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/gopher.c b/lib/gopher.c
index 6fbb7de..4a11d93 100644
--- a/lib/gopher.c
+++ b/lib/gopher.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/gopher.h b/lib/gopher.h
index 4ea269d..9e3365b 100644
--- a/lib/gopher.h
+++ b/lib/gopher.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/h2h3.c b/lib/h2h3.c
index 3a9288d..3b21699 100644
--- a/lib/h2h3.c
+++ b/lib/h2h3.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -118,6 +118,7 @@ static header_instruction inspect_header(const char *name, size_t namelen,
 CURLcode Curl_pseudo_headers(struct Curl_easy *data,
                              const char *mem, /* the request */
                              const size_t len /* size of request */,
+                             size_t* hdrlen /* opt size of headers read */,
                              struct h2h3req **hp)
 {
   struct connectdata *conn = data->conn;
@@ -291,6 +292,12 @@ CURLcode Curl_pseudo_headers(struct Curl_easy *data,
     }
   }
 
+  if(hdrlen) {
+    /* Skip trailing CRLF */
+    end += 4;
+    *hdrlen = end - mem;
+  }
+
   hreq->entries = nheader;
   *hp = hreq;
 
diff --git a/lib/h2h3.h b/lib/h2h3.h
index c35b706..396c12c 100644
--- a/lib/h2h3.h
+++ b/lib/h2h3.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -51,6 +51,7 @@ struct h2h3req {
 CURLcode Curl_pseudo_headers(struct Curl_easy *data,
                              const char *request,
                              const size_t len,
+                             size_t* hdrlen /* optional */,
                              struct h2h3req **hp);
 
 /*
diff --git a/lib/hash.c b/lib/hash.c
index b6a2a33..06ce92c 100644
--- a/lib/hash.c
+++ b/lib/hash.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/hash.h b/lib/hash.h
index 5b59bf1..9cfffc2 100644
--- a/lib/hash.h
+++ b/lib/hash.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/headers.c b/lib/headers.c
index 978c918..22e0e01 100644
--- a/lib/headers.c
+++ b/lib/headers.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/headers.h b/lib/headers.h
index 96332db..a5229ea 100644
--- a/lib/headers.h
+++ b/lib/headers.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/hmac.c b/lib/hmac.c
index dfb0db5..8d8de17 100644
--- a/lib/hmac.c
+++ b/lib/hmac.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/hostasyn.c b/lib/hostasyn.c
index df50d13..536eb73 100644
--- a/lib/hostasyn.c
+++ b/lib/hostasyn.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/hostip.c b/lib/hostip.c
index dd427a2..9738806 100644
--- a/lib/hostip.c
+++ b/lib/hostip.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/hostip.h b/lib/hostip.h
index 3b1d814..e0d13cd 100644
--- a/lib/hostip.h
+++ b/lib/hostip.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/hostip4.c b/lib/hostip4.c
index 109bd1e..9140180 100644
--- a/lib/hostip4.c
+++ b/lib/hostip4.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/hostip6.c b/lib/hostip6.c
index af8bc23..6b0ba55 100644
--- a/lib/hostip6.c
+++ b/lib/hostip6.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/hostsyn.c b/lib/hostsyn.c
index 73d1e50..ca8b075 100644
--- a/lib/hostsyn.c
+++ b/lib/hostsyn.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/hsts.c b/lib/hsts.c
index c449120..64cbae1 100644
--- a/lib/hsts.c
+++ b/lib/hsts.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2020 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -39,6 +39,7 @@
 #include "parsedate.h"
 #include "fopen.h"
 #include "rename.h"
+#include "share.h"
 
 /* The last 3 #include files should be in this order */
 #include "curl_printf.h"
@@ -425,14 +426,23 @@ static CURLcode hsts_add(struct hsts *h, char *line)
   if(2 == rc) {
     time_t expires = strcmp(date, UNLIMITED) ? Curl_getdate_capped(date) :
       TIME_T_MAX;
-    CURLcode result;
+    CURLcode result = CURLE_OK;
     char *p = host;
     bool subdomain = FALSE;
+    struct stsentry *e;
     if(p[0] == '.') {
       p++;
       subdomain = TRUE;
     }
-    result = hsts_create(h, p, subdomain, expires);
+    /* only add it if not already present */
+    e = Curl_hsts(h, p, subdomain);
+    if(!e)
+      result = hsts_create(h, p, subdomain, expires);
+    else {
+      /* the same host name, use the largest expire time */
+      if(expires > e->expires)
+        e->expires = expires;
+    }
     if(result)
       return result;
   }
@@ -551,4 +561,18 @@ CURLcode Curl_hsts_loadcb(struct Curl_easy *data, struct hsts *h)
   return CURLE_OK;
 }
 
+void Curl_hsts_loadfiles(struct Curl_easy *data)
+{
+  struct curl_slist *l = data->set.hstslist;
+  if(l) {
+    Curl_share_lock(data, CURL_LOCK_DATA_HSTS, CURL_LOCK_ACCESS_SINGLE);
+
+    while(l) {
+      (void)Curl_hsts_loadfile(data, data->hsts, l->data);
+      l = l->next;
+    }
+    Curl_share_unlock(data, CURL_LOCK_DATA_HSTS);
+  }
+}
+
 #endif /* CURL_DISABLE_HTTP || CURL_DISABLE_HSTS */
diff --git a/lib/hsts.h b/lib/hsts.h
index 0e36a77..d3431a5 100644
--- a/lib/hsts.h
+++ b/lib/hsts.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2020 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -59,9 +59,11 @@ CURLcode Curl_hsts_loadfile(struct Curl_easy *data,
                             struct hsts *h, const char *file);
 CURLcode Curl_hsts_loadcb(struct Curl_easy *data,
                           struct hsts *h);
+void Curl_hsts_loadfiles(struct Curl_easy *data);
 #else
 #define Curl_hsts_cleanup(x)
 #define Curl_hsts_loadcb(x,y) CURLE_OK
 #define Curl_hsts_save(x,y,z)
+#define Curl_hsts_loadfiles(x)
 #endif /* CURL_DISABLE_HTTP || CURL_DISABLE_HSTS */
 #endif /* HEADER_CURL_HSTS_H */
diff --git a/lib/http.c b/lib/http.c
index 1b75022..cb585e7 100644
--- a/lib/http.c
+++ b/lib/http.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -62,6 +62,7 @@
 #include "cookie.h"
 #include "vauth/vauth.h"
 #include "vtls/vtls.h"
+#include "vquic/vquic.h"
 #include "http_digest.h"
 #include "http_ntlm.h"
 #include "curl_ntlm_wb.h"
@@ -150,7 +151,7 @@ const struct Curl_handler Curl_handler_ws = {
   http_getsock_do,                      /* doing_getsock */
   ZERO_NULL,                            /* domore_getsock */
   ZERO_NULL,                            /* perform_getsock */
-  ZERO_NULL,                            /* disconnect */
+  Curl_ws_disconnect,                   /* disconnect */
   ZERO_NULL,                            /* readwrite */
   ZERO_NULL,                            /* connection_check */
   ZERO_NULL,                            /* attach connection */
@@ -204,7 +205,7 @@ const struct Curl_handler Curl_handler_wss = {
   http_getsock_do,                      /* doing_getsock */
   ZERO_NULL,                            /* domore_getsock */
   ZERO_NULL,                            /* perform_getsock */
-  ZERO_NULL,                            /* disconnect */
+  Curl_ws_disconnect,                   /* disconnect */
   ZERO_NULL,                            /* readwrite */
   ZERO_NULL,                            /* connection_check */
   ZERO_NULL,                            /* attach connection */
@@ -218,41 +219,6 @@ const struct Curl_handler Curl_handler_wss = {
 
 #endif
 
-static CURLcode h3_setup_conn(struct Curl_easy *data,
-                              struct connectdata *conn)
-{
-#ifdef ENABLE_QUIC
-  /* We want HTTP/3 directly, setup the filter chain ourself,
-   * overriding the default behaviour. */
-  DEBUGASSERT(conn->transport == TRNSPRT_QUIC);
-
-  if(!(conn->handler->flags & PROTOPT_SSL)) {
-    failf(data, "HTTP/3 requested for non-HTTPS URL");
-    return CURLE_URL_MALFORMAT;
-  }
-#ifndef CURL_DISABLE_PROXY
-  if(conn->bits.socksproxy) {
-    failf(data, "HTTP/3 is not supported over a SOCKS proxy");
-    return CURLE_URL_MALFORMAT;
-  }
-  if(conn->bits.httpproxy && conn->bits.tunnel_proxy) {
-    failf(data, "HTTP/3 is not supported over a HTTP proxy");
-    return CURLE_URL_MALFORMAT;
-  }
-#endif
-
-  DEBUGF(infof(data, "HTTP/3 direct conn setup(conn #%ld, index=%d)",
-         conn->connection_id, FIRSTSOCKET));
-  return Curl_conn_socket_set(data, conn, FIRSTSOCKET);
-
-#else /* ENABLE_QUIC */
-  (void)conn;
-  (void)data;
-  DEBUGF(infof(data, "QUIC is not supported in this build"));
-  return CURLE_NOT_BUILT_IN;
-#endif /* !ENABLE_QUIC */
-}
-
 static CURLcode http_setup_conn(struct Curl_easy *data,
                                 struct connectdata *conn)
 {
@@ -268,19 +234,16 @@ static CURLcode http_setup_conn(struct Curl_easy *data,
   Curl_mime_initpart(&http->form);
   data->req.p.http = http;
 
-  if(data->state.httpwant == CURL_HTTP_VERSION_3) {
+  if((data->state.httpwant == CURL_HTTP_VERSION_3)
+     || (data->state.httpwant == CURL_HTTP_VERSION_3ONLY)) {
+    CURLcode result = Curl_conn_may_http3(data, conn);
+    if(result)
+      return result;
+
+     /* TODO: HTTP lower version eyeballing */
     conn->transport = TRNSPRT_QUIC;
   }
 
-  if(conn->transport == TRNSPRT_QUIC) {
-    return h3_setup_conn(data, conn);
-  }
-  else {
-    if(!CONN_INUSE(conn))
-      /* if not already multi-using, setup connection details */
-      Curl_http2_setup_conn(conn);
-    Curl_http2_setup_req(data);
-  }
   return CURLE_OK;
 }
 
@@ -1256,8 +1219,8 @@ static size_t readmoredata(char *buffer,
                            size_t nitems,
                            void *userp)
 {
-  struct Curl_easy *data = (struct Curl_easy *)userp;
-  struct HTTP *http = data->req.p.http;
+  struct HTTP *http = (struct HTTP *)userp;
+  struct Curl_easy *data = http->backup.data;
   size_t fullsize = size * nitems;
 
   if(!http->postsize)
@@ -1309,6 +1272,7 @@ static size_t readmoredata(char *buffer,
  */
 CURLcode Curl_buffer_send(struct dynbuf *in,
                           struct Curl_easy *data,
+                          struct HTTP *http,
                           /* add the number of sent bytes to this
                              counter */
                           curl_off_t *bytes_written,
@@ -1321,14 +1285,13 @@ CURLcode Curl_buffer_send(struct dynbuf *in,
   char *ptr;
   size_t size;
   struct connectdata *conn = data->conn;
-  struct HTTP *http = data->req.p.http;
   size_t sendsize;
   curl_socket_t sockfd;
   size_t headersize;
 
   DEBUGASSERT(socketindex <= SECONDARYSOCKET);
 
-  sockfd = conn->sock[socketindex];
+  sockfd = Curl_conn_get_socket(data, socketindex);
 
   /* The looping below is required since we use non-blocking sockets, but due
      to the circumstances we will just loop and try again and again etc */
@@ -1456,10 +1419,11 @@ CURLcode Curl_buffer_send(struct dynbuf *in,
         http->backup.fread_in = data->state.in;
         http->backup.postdata = http->postdata;
         http->backup.postsize = http->postsize;
+        http->backup.data = data;
 
         /* set the new pointers for the request-sending */
         data->state.fread_func = (curl_read_callback)readmoredata;
-        data->state.in = (void *)data;
+        data->state.in = (void *)http;
         http->postdata = ptr;
         http->postsize = (curl_off_t)size;
 
@@ -1468,7 +1432,6 @@ CURLcode Curl_buffer_send(struct dynbuf *in,
 
         http->send_buffer = *in; /* copy the whole struct */
         http->sending = HTTPSEND_REQUEST;
-
         return CURLE_OK;
       }
       http->sending = HTTPSEND_BODY;
@@ -1579,8 +1542,8 @@ static int http_getsock_do(struct Curl_easy *data,
                            curl_socket_t *socks)
 {
   /* write mode */
-  (void)data;
-  socks[0] = conn->sock[FIRSTSOCKET];
+  (void)conn;
+  socks[0] = Curl_conn_get_socket(data, FIRSTSOCKET);
   return GETSOCK_WRITESOCK(0);
 }
 
@@ -1610,8 +1573,6 @@ CURLcode Curl_http_done(struct Curl_easy *data,
     return CURLE_OK;
 
   Curl_dyn_free(&http->send_buffer);
-  Curl_http2_done(data, premature);
-  Curl_quic_done(data, premature);
   Curl_mime_cleanpart(&http->form);
   Curl_dyn_reset(&data->state.headerb);
   Curl_hyper_done(data);
@@ -1664,17 +1625,10 @@ bool Curl_use_http_1_1plus(const struct Curl_easy *data,
 static const char *get_http_string(const struct Curl_easy *data,
                                    const struct connectdata *conn)
 {
-#ifdef ENABLE_QUIC
-  if((data->state.httpwant == CURL_HTTP_VERSION_3) ||
-     (conn->httpversion == 30))
+  if(Curl_conn_is_http3(data, conn, FIRSTSOCKET))
     return "3";
-#endif
-
-#ifdef USE_NGHTTP2
-  if(conn->proto.httpc.h2)
+  if(Curl_conn_is_http2(data, conn, FIRSTSOCKET))
     return "2";
-#endif
-
   if(Curl_use_http_1_1plus(data, conn))
     return "1.1";
 
@@ -2359,7 +2313,7 @@ CURLcode Curl_http_bodysend(struct Curl_easy *data, struct connectdata *conn,
   curl_off_t included_body = 0;
 #else
   /* from this point down, this function should not be used */
-#define Curl_buffer_send(a,b,c,d,e) CURLE_OK
+#define Curl_buffer_send(a,b,c,d,e,f) CURLE_OK
 #endif
   CURLcode result = CURLE_OK;
   struct HTTP *http = data->req.p.http;
@@ -2403,7 +2357,8 @@ CURLcode Curl_http_bodysend(struct Curl_easy *data, struct connectdata *conn,
     Curl_pgrsSetUploadSize(data, http->postsize);
 
     /* this sends the buffer and frees all the buffer resources */
-    result = Curl_buffer_send(r, data, &data->info.request_size, 0,
+    result = Curl_buffer_send(r, data, data->req.p.http,
+                              &data->info.request_size, 0,
                               FIRSTSOCKET);
     if(result)
       failf(data, "Failed sending PUT request");
@@ -2424,7 +2379,8 @@ CURLcode Curl_http_bodysend(struct Curl_easy *data, struct connectdata *conn,
       if(result)
         return result;
 
-      result = Curl_buffer_send(r, data, &data->info.request_size, 0,
+      result = Curl_buffer_send(r, data, data->req.p.http,
+                                &data->info.request_size, 0,
                                 FIRSTSOCKET);
       if(result)
         failf(data, "Failed sending POST request");
@@ -2440,8 +2396,7 @@ CURLcode Curl_http_bodysend(struct Curl_easy *data, struct connectdata *conn,
        we don't upload data chunked, as RFC2616 forbids us to set both
        kinds of headers (Transfer-Encoding: chunked and Content-Length) */
     if(http->postsize != -1 && !data->req.upload_chunky &&
-       (conn->bits.authneg ||
-        !Curl_checkheaders(data, STRCONST("Content-Length")))) {
+       (!Curl_checkheaders(data, STRCONST("Content-Length")))) {
       /* we allow replacing this header if not during auth negotiation,
          although it isn't very wise to actually set your own */
       result = Curl_dyn_addf(r,
@@ -2495,7 +2450,8 @@ CURLcode Curl_http_bodysend(struct Curl_easy *data, struct connectdata *conn,
     http->sending = HTTPSEND_BODY;
 
     /* this sends the buffer and frees all the buffer resources */
-    result = Curl_buffer_send(r, data, &data->info.request_size, 0,
+    result = Curl_buffer_send(r, data, data->req.p.http,
+                              &data->info.request_size, 0,
                               FIRSTSOCKET);
     if(result)
       failf(data, "Failed sending POST request");
@@ -2561,7 +2517,7 @@ CURLcode Curl_http_bodysend(struct Curl_easy *data, struct connectdata *conn,
 
       /* In HTTP2, we send request body in DATA frame regardless of
          its size. */
-      if(conn->httpversion != 20 &&
+      if(conn->httpversion < 20 &&
          !data->state.expect100header &&
          (http->postsize < MAX_INITIAL_POST_SIZE)) {
         /* if we don't use expect: 100  AND
@@ -2612,11 +2568,10 @@ CURLcode Curl_http_bodysend(struct Curl_easy *data, struct connectdata *conn,
       else {
         /* A huge POST coming up, do data separate from the request */
         http->postdata = data->set.postfields;
-
         http->sending = HTTPSEND_BODY;
-
+        http->backup.data = data;
         data->state.fread_func = (curl_read_callback)readmoredata;
-        data->state.in = (void *)data;
+        data->state.in = (void *)http;
 
         /* set the upload size to the progress meter */
         Curl_pgrsSetUploadSize(data, http->postsize);
@@ -2655,7 +2610,8 @@ CURLcode Curl_http_bodysend(struct Curl_easy *data, struct connectdata *conn,
       }
     }
     /* issue the request */
-    result = Curl_buffer_send(r, data, &data->info.request_size, included_body,
+    result = Curl_buffer_send(r, data, data->req.p.http,
+                              &data->info.request_size, included_body,
                               FIRSTSOCKET);
 
     if(result)
@@ -2671,7 +2627,8 @@ CURLcode Curl_http_bodysend(struct Curl_easy *data, struct connectdata *conn,
       return result;
 
     /* issue the request */
-    result = Curl_buffer_send(r, data, &data->info.request_size, 0,
+    result = Curl_buffer_send(r, data, data->req.p.http,
+                              &data->info.request_size, 0,
                               FIRSTSOCKET);
     if(result)
       failf(data, "Failed sending HTTP request");
@@ -3021,50 +2978,27 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done)
      the rest of the request in the PERFORM phase. */
   *done = TRUE;
 
-  if(conn->transport != TRNSPRT_QUIC) {
-    if(conn->httpversion < 20) { /* unless the connection is re-used and
-                                    already http2 */
-      switch(conn->alpn) {
-      case CURL_HTTP_VERSION_2:
-        conn->httpversion = 20; /* we know we're on HTTP/2 now */
-
-        result = Curl_http2_switched(data, NULL, 0);
-        if(result)
-          return result;
-        break;
-      case CURL_HTTP_VERSION_1_1:
-        /* continue with HTTP/1.1 when explicitly requested */
-        break;
-      default:
-        /* Check if user wants to use HTTP/2 with clear TCP */
-#ifdef USE_NGHTTP2
-        if(data->state.httpwant == CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE) {
-#ifndef CURL_DISABLE_PROXY
-          if(conn->bits.httpproxy && !conn->bits.tunnel_proxy) {
-            /* We don't support HTTP/2 proxies yet. Also it's debatable
-               whether or not this setting should apply to HTTP/2 proxies. */
-            infof(data, "Ignoring HTTP/2 prior knowledge due to proxy");
-            break;
-          }
-#endif
-          DEBUGF(infof(data, "HTTP/2 over clean TCP"));
-          conn->httpversion = 20;
-
-          result = Curl_http2_switched(data, NULL, 0);
-          if(result)
-            return result;
-        }
-#endif
-        break;
-      }
-    }
-    else {
-      /* prepare for an http2 request */
-      result = Curl_http2_setup(data, conn);
+  switch(conn->alpn) {
+  case CURL_HTTP_VERSION_3:
+    DEBUGASSERT(Curl_conn_is_http3(data, conn, FIRSTSOCKET));
+    break;
+  case CURL_HTTP_VERSION_2:
+    DEBUGASSERT(Curl_conn_is_http2(data, conn, FIRSTSOCKET));
+    break;
+  case CURL_HTTP_VERSION_1_1:
+    /* continue with HTTP/1.1 when explicitly requested */
+    break;
+  default:
+    /* Check if user wants to use HTTP/2 with clear TCP */
+    if(Curl_http2_may_switch(data, conn, FIRSTSOCKET)) {
+      DEBUGF(infof(data, "HTTP/2 over clean TCP"));
+      result = Curl_http2_switch(data, conn, FIRSTSOCKET);
       if(result)
         return result;
     }
+    break;
   }
+
   http = data->req.p.http;
   DEBUGASSERT(http);
 
@@ -3224,7 +3158,7 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done)
   }
 
   if(!(conn->handler->flags&PROTOPT_SSL) &&
-     conn->httpversion != 20 &&
+     conn->httpversion < 20 &&
      (data->state.httpwant == CURL_HTTP_VERSION_2)) {
     /* append HTTP2 upgrade magic stuff to the HTTP request if it isn't done
        over SSL */
@@ -3236,8 +3170,10 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done)
   }
 
   result = Curl_http_cookies(data, conn, &req);
+#ifdef USE_WEBSOCKETS
   if(!result && conn->handler->protocol&(CURLPROTO_WS|CURLPROTO_WSS))
     result = Curl_ws_request(data, &req);
+#endif
   if(!result)
     result = Curl_add_timecondition(data, &req);
   if(!result)
@@ -3282,7 +3218,7 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done)
     }
   }
 
-  if((conn->httpversion == 20) && data->req.upload_chunky)
+  if((conn->httpversion >= 20) && data->req.upload_chunky)
     /* upload_chunky was set above to set up the request in a chunky fashion,
        but is disabled here again to avoid that the chunked encoded version is
        actually used when sending the request body over h2 */
@@ -3669,7 +3605,8 @@ CURLcode Curl_http_header(struct Curl_easy *data, struct connectdata *conn,
 #endif
             )) {
     /* the ALPN of the current request */
-    enum alpnid id = (conn->httpversion == 20) ? ALPN_h2 : ALPN_h1;
+    enum alpnid id = (conn->httpversion == 30)? ALPN_h3 :
+                       (conn->httpversion == 20) ? ALPN_h2 : ALPN_h1;
     result = Curl_altsvc_parse(data, data->asi,
                                headp + strlen("Alt-Svc:"),
                                id, conn->host.name,
@@ -3963,7 +3900,8 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data,
 
             /* switch to http2 now. The bytes after response headers
                are also processed here, otherwise they are lost. */
-            result = Curl_http2_switched(data, k->str, *nread);
+            result = Curl_http2_upgrade(data, conn, FIRSTSOCKET,
+                                        k->str, *nread);
             if(result)
               return result;
             *nread = 0;
@@ -3971,7 +3909,7 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data,
 #ifdef USE_WEBSOCKETS
           else if(k->upgr101 == UPGR101_WS) {
             /* verify the response */
-            result = Curl_ws_accept(data);
+            result = Curl_ws_accept(data, k->str, *nread);
             if(result)
               return result;
             k->header = FALSE; /* no more header to parse! */
@@ -4191,11 +4129,8 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data,
            stream.  In order to do this, we keep reading until we
            close the stream. */
         if(0 == k->maxdownload
-#if defined(USE_NGHTTP2)
-           && !((conn->handler->protocol & PROTO_FAMILY_HTTP) &&
-                conn->httpversion == 20)
-#endif
-           )
+           && !Curl_conn_is_http2(data, conn, FIRSTSOCKET)
+           && !Curl_conn_is_http3(data, conn, FIRSTSOCKET))
           *stop_reading = TRUE;
 
         if(*stop_reading) {
@@ -4290,7 +4225,6 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data,
           }
           if(conn->httpversion < 20) {
             conn->bundle->multiuse = BUNDLE_NO_MULTIUSE;
-            infof(data, "Mark bundle as not supporting multiuse");
           }
         }
         else if(!nc) {
diff --git a/lib/http.h b/lib/http.h
index ecfe4ee..444abc0 100644
--- a/lib/http.h
+++ b/lib/http.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -24,6 +24,11 @@
  *
  ***************************************************************************/
 #include "curl_setup.h"
+
+#if defined(USE_MSH3) && !defined(_WIN32)
+#include <pthread.h>
+#endif
+
 #include "ws.h"
 
 typedef enum {
@@ -37,11 +42,7 @@ typedef enum {
 
 #ifndef CURL_DISABLE_HTTP
 
-#ifdef USE_NGHTTP2
-#include <nghttp2/nghttp2.h>
-#endif
-
-#if defined(_WIN32) && defined(ENABLE_QUIC)
+#if defined(ENABLE_QUIC) || defined(USE_NGHTTP2)
 #include <stdint.h>
 #endif
 
@@ -73,8 +74,10 @@ char *Curl_checkProxyheaders(struct Curl_easy *data,
                              const struct connectdata *conn,
                              const char *thisheader,
                              const size_t thislen);
+struct HTTP; /* see below */
 CURLcode Curl_buffer_send(struct dynbuf *in,
                           struct Curl_easy *data,
+                          struct HTTP *http,
                           curl_off_t *bytes_written,
                           curl_off_t included_body_bytes,
                           int socketindex);
@@ -179,29 +182,6 @@ CURLcode Curl_http_auth_act(struct Curl_easy *data);
 struct h3out; /* see ngtcp2 */
 #endif
 
-#ifdef USE_MSH3
-#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) { \
-  pthread_mutexattr_t attr; \
-  pthread_mutexattr_init(&attr); \
-  pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); \
-  pthread_mutex_init(lock, &attr); \
-  pthread_mutexattr_destroy(&attr); \
-}
-#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 */
-#endif /* USE_MSH3 */
-
 /****************************************************************************
  * HTTP unique setup
  ***************************************************************************/
@@ -220,6 +200,7 @@ struct HTTP {
     void *fread_in;           /* backup storage for fread_in pointer */
     const char *postdata;
     curl_off_t postsize;
+    struct Curl_easy *data;
   } backup;
 
   enum {
@@ -258,7 +239,6 @@ struct HTTP {
 #if defined(USE_NGHTTP2) || defined(USE_NGHTTP3)
   bool bodystarted;
   int status_code; /* HTTP status code */
-  bool closed; /* TRUE on HTTP2 stream close */
   char *mem;     /* points to a buffer in memory to store received data */
   size_t len;    /* size of the buffer 'mem' points to */
   size_t memlen; /* size of data copied to mem */
@@ -268,6 +248,8 @@ struct HTTP {
   const uint8_t *upload_mem; /* points to a buffer to read from */
   size_t upload_len; /* size of the buffer 'upload_mem' points to */
   curl_off_t upload_left; /* number of bytes left to upload */
+  bool closed; /* TRUE on stream close */
+  bool reset;  /* TRUE on stream reset */
 #endif
 
 #ifdef ENABLE_QUIC
@@ -278,20 +260,25 @@ struct HTTP {
   bool firstheader;  /* FALSE until headers arrive */
   bool firstbody;  /* FALSE until body arrives */
   bool h3req;    /* FALSE until request is issued */
-#endif
+#endif /* !USE_MSH3 */
   bool upload_done;
-#endif
+#endif /* ENABLE_QUIC */
 #ifdef USE_NGHTTP3
-  size_t unacked_window;
+  size_t recv_buf_nonflow; /* buffered bytes, not counting for flow control */
   struct h3out *h3out; /* per-stream buffers for upload */
   struct dynbuf overflow; /* excess data received during a single Curl_read */
-#endif
+#endif /* USE_NGHTTP3 */
 #ifdef USE_MSH3
   struct MSH3_REQUEST *req;
-  msh3_lock recv_lock;
+#ifdef _WIN32
+  CRITICAL_SECTION recv_lock;
+#else /* !_WIN32 */
+  pthread_mutex_t recv_lock;
+#endif /* _WIN32 */
   /* Receive Buffer (Headers and Data) */
   uint8_t* recv_buf;
   size_t recv_buf_alloc;
+  size_t recv_buf_max;
   /* Receive Headers */
   size_t recv_header_len;
   bool recv_header_complete;
@@ -300,53 +287,13 @@ struct HTTP {
   bool recv_data_complete;
   /* General Receive Error */
   CURLcode recv_error;
-#endif
-};
-
-#ifdef USE_NGHTTP2
-/* h2 settings for this connection */
-struct h2settings {
-  uint32_t max_concurrent_streams;
-  bool enable_push;
-};
-#endif
-
-struct http_conn {
-#ifdef USE_NGHTTP2
-#define H2_BINSETTINGS_LEN 80
-  uint8_t binsettings[H2_BINSETTINGS_LEN];
-  size_t  binlen; /* length of the binsettings data */
-
-  /* We associate the connectdata struct with the connection, but we need to
-     make sure we can identify the current "driving" transfer. This is a
-     work-around for the lack of nghttp2_session_set_user_data() in older
-     nghttp2 versions that we want to support. (Added in 1.31.0) */
-  struct Curl_easy *trnsfr;
-
-  nghttp2_session *h2;
-  Curl_send *send_underlying; /* underlying send Curl_send callback */
-  Curl_recv *recv_underlying; /* underlying recv Curl_recv callback */
-  char *inbuf; /* buffer to receive data from underlying socket */
-  size_t inbuflen; /* number of bytes filled in inbuf */
-  size_t nread_inbuf; /* number of bytes read from in inbuf */
-  /* We need separate buffer for transmission and reception because we
-     may call nghttp2_session_send() after the
-     nghttp2_session_mem_recv() but mem buffer is still not full. In
-     this case, we wrongly sends the content of mem buffer if we share
-     them for both cases. */
-  int32_t pause_stream_id; /* stream ID which paused
-                              nghttp2_session_mem_recv */
-  size_t drain_total; /* sum of all stream's UrlState.drain */
-
-  /* this is a hash of all individual streams (Curl_easy structs) */
-  struct h2settings settings;
-
-  /* list of settings that will be sent */
-  nghttp2_settings_entry local_settings[3];
-  size_t local_settings_num;
-#else
-  int unused; /* prevent a compiler warning */
-#endif
+#endif /* USE_MSH3 */
+#ifdef USE_QUICHE
+  bool h3_got_header; /* TRUE when h3 stream has recvd some HEADER */
+  bool h3_recving_data; /* TRUE when h3 stream is reading DATA */
+  bool h3_body_pending; /* TRUE when h3 stream may have more body DATA */
+  struct h3_event_node *pending;
+#endif /* USE_QUICHE */
 };
 
 CURLcode Curl_http_size(struct Curl_easy *data);
diff --git a/lib/http2.c b/lib/http2.c
index b9d3245..bdb5e73 100644
--- a/lib/http2.c
+++ b/lib/http2.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -35,6 +35,7 @@
 #include "strcase.h"
 #include "multiif.h"
 #include "url.h"
+#include "cfilters.h"
 #include "connect.h"
 #include "strtoofft.h"
 #include "strdup.h"
@@ -63,124 +64,322 @@
 
 #define HTTP2_HUGE_WINDOW_SIZE (32 * 1024 * 1024) /* 32 MB */
 
-#ifdef DEBUG_HTTP2
-#define H2BUGF(x) x
-#else
-#define H2BUGF(x) do { } while(0)
-#endif
 
-static ssize_t http2_recv(struct Curl_easy *data, int sockindex,
-                          char *mem, size_t len, CURLcode *err);
-static bool http2_connisdead(struct Curl_easy *data,
-                             struct connectdata *conn);
-static int h2_session_send(struct Curl_easy *data,
-                           nghttp2_session *h2);
-static int h2_process_pending_input(struct Curl_easy *data,
-                                    struct http_conn *httpc,
-                                    CURLcode *err);
+#define H2_SETTINGS_IV_LEN  3
+#define H2_BINSETTINGS_LEN 80
 
-/*
- * Curl_http2_init_state() is called when the easy handle is created and
- * allows for HTTP/2 specific init of state.
- */
-void Curl_http2_init_state(struct UrlState *state)
+static int populate_settings(nghttp2_settings_entry *iv,
+                             struct Curl_easy *data)
+{
+  iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
+  iv[0].value = Curl_multi_max_concurrent_streams(data->multi);
+
+  iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
+  iv[1].value = HTTP2_HUGE_WINDOW_SIZE;
+
+  iv[2].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH;
+  iv[2].value = data->multi->push_cb != NULL;
+
+  return 3;
+}
+
+static size_t populate_binsettings(uint8_t *binsettings,
+                                   struct Curl_easy *data)
+{
+  nghttp2_settings_entry iv[H2_SETTINGS_IV_LEN];
+  int ivlen;
+
+  ivlen = populate_settings(iv, data);
+  /* this returns number of bytes it wrote */
+  return nghttp2_pack_settings_payload(binsettings, H2_BINSETTINGS_LEN,
+                                       iv, ivlen);
+}
+
+struct cf_h2_ctx {
+  nghttp2_session *h2;
+  uint32_t max_concurrent_streams;
+  bool enable_push;
+  /* The easy handle used in the current filter call, cleared at return */
+  struct cf_call_data call_data;
+
+  char *inbuf; /* buffer to receive data from underlying socket */
+  size_t inbuflen; /* number of bytes filled in inbuf */
+  size_t nread_inbuf; /* number of bytes read from in inbuf */
+
+  struct dynbuf outbuf;
+
+  /* We need separate buffer for transmission and reception because we
+     may call nghttp2_session_send() after the
+     nghttp2_session_mem_recv() but mem buffer is still not full. In
+     this case, we wrongly sends the content of mem buffer if we share
+     them for both cases. */
+  int32_t pause_stream_id; /* stream ID which paused
+                              nghttp2_session_mem_recv */
+  size_t drain_total; /* sum of all stream's UrlState.drain */
+};
+
+/* How to access `call_data` from a cf_h2 filter */
+#define CF_CTX_CALL_DATA(cf)  \
+  ((struct cf_h2_ctx *)(cf)->ctx)->call_data
+
+
+static void cf_h2_ctx_clear(struct cf_h2_ctx *ctx)
+{
+  struct cf_call_data save = ctx->call_data;
+
+  if(ctx->h2) {
+    nghttp2_session_del(ctx->h2);
+  }
+  free(ctx->inbuf);
+  Curl_dyn_free(&ctx->outbuf);
+  memset(ctx, 0, sizeof(*ctx));
+  ctx->call_data = save;
+}
+
+static void cf_h2_ctx_free(struct cf_h2_ctx *ctx)
 {
-  state->stream_weight = NGHTTP2_DEFAULT_WEIGHT;
+  if(ctx) {
+    cf_h2_ctx_clear(ctx);
+    free(ctx);
+  }
+}
+
+static int h2_client_new(struct Curl_cfilter *cf,
+                         nghttp2_session_callbacks *cbs)
+{
+  struct cf_h2_ctx *ctx = cf->ctx;
+
+#if NGHTTP2_VERSION_NUM < 0x013200
+  /* before 1.50.0 */
+  return nghttp2_session_client_new(&ctx->h2, cbs, cf);
+#else
+  nghttp2_option *o;
+  int rc = nghttp2_option_new(&o);
+  if(rc)
+    return rc;
+  /* turn off RFC 9113 leading and trailing white spaces validation against
+     HTTP field value. */
+  nghttp2_option_set_no_rfc9113_leading_and_trailing_ws_validation(o, 1);
+  rc = nghttp2_session_client_new2(&ctx->h2, cbs, cf, o);
+  nghttp2_option_del(o);
+  return rc;
+#endif
 }
 
+static ssize_t send_callback(nghttp2_session *h2,
+                             const uint8_t *mem, size_t length, int flags,
+                             void *userp);
+static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame,
+                         void *userp);
+static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags,
+                              int32_t stream_id,
+                              const uint8_t *mem, size_t len, void *userp);
+static int on_stream_close(nghttp2_session *session, int32_t stream_id,
+                           uint32_t error_code, void *userp);
+static int on_begin_headers(nghttp2_session *session,
+                            const nghttp2_frame *frame, void *userp);
+static int on_header(nghttp2_session *session, const nghttp2_frame *frame,
+                     const uint8_t *name, size_t namelen,
+                     const uint8_t *value, size_t valuelen,
+                     uint8_t flags,
+                     void *userp);
+static int error_callback(nghttp2_session *session, const char *msg,
+                          size_t len, void *userp);
+
 /*
- * Curl_http2_init_userset() is called when the easy handle is created and
- * allows for HTTP/2 specific user-set fields.
+ * multi_connchanged() is called to tell that there is a connection in
+ * this multi handle that has changed state (multiplexing become possible, the
+ * number of allowed streams changed or similar), and a subsequent use of this
+ * multi handle should move CONNECT_PEND handles back to CONNECT to have them
+ * retry.
  */
-void Curl_http2_init_userset(struct UserDefined *set)
+static void multi_connchanged(struct Curl_multi *multi)
 {
-  set->stream_weight = NGHTTP2_DEFAULT_WEIGHT;
+  multi->recheckstate = TRUE;
 }
 
-static int http2_getsock(struct Curl_easy *data,
-                         struct connectdata *conn,
-                         curl_socket_t *sock)
+static CURLcode http2_data_setup(struct Curl_cfilter *cf,
+                                 struct Curl_easy *data)
 {
-  const struct http_conn *c = &conn->proto.httpc;
-  struct SingleRequest *k = &data->req;
-  int bitmap = GETSOCK_BLANK;
   struct HTTP *stream = data->req.p.http;
 
-  sock[0] = conn->sock[FIRSTSOCKET];
+  (void)cf;
+  DEBUGASSERT(stream);
+  DEBUGASSERT(data->state.buffer);
 
-  if(!(k->keepon & KEEP_RECV_PAUSE))
-    /* Unless paused - in an HTTP/2 connection we can basically always get a
-       frame so we should always be ready for one */
-    bitmap |= GETSOCK_READSOCK(FIRSTSOCKET);
+  stream->stream_id = -1;
 
-  /* we're (still uploading OR the HTTP/2 layer wants to send data) AND
-     there's a window to send data in */
-  if((((k->keepon & (KEEP_SEND|KEEP_SEND_PAUSE)) == KEEP_SEND) ||
-      nghttp2_session_want_write(c->h2)) &&
-     (nghttp2_session_get_remote_window_size(c->h2) &&
-      nghttp2_session_get_stream_remote_window_size(c->h2,
-                                                    stream->stream_id)))
-    bitmap |= GETSOCK_WRITESOCK(FIRSTSOCKET);
+  Curl_dyn_init(&stream->header_recvbuf, DYN_H2_HEADERS);
+  Curl_dyn_init(&stream->trailer_recvbuf, DYN_H2_TRAILERS);
 
-  return bitmap;
+  stream->bodystarted = FALSE;
+  stream->status_code = -1;
+  stream->pausedata = NULL;
+  stream->pauselen = 0;
+  stream->closed = FALSE;
+  stream->close_handled = FALSE;
+  stream->memlen = 0;
+  stream->error = NGHTTP2_NO_ERROR;
+  stream->upload_left = 0;
+  stream->upload_mem = NULL;
+  stream->upload_len = 0;
+  stream->mem = data->state.buffer;
+  stream->len = data->set.buffer_size;
+
+  return CURLE_OK;
 }
 
 /*
- * http2_stream_free() free HTTP2 stream related data
+ * Initialize the cfilter context
  */
-static void http2_stream_free(struct HTTP *http)
+static CURLcode cf_h2_ctx_init(struct Curl_cfilter *cf,
+                               struct Curl_easy *data,
+                               bool via_h1_upgrade)
 {
-  if(http) {
-    Curl_dyn_free(&http->header_recvbuf);
-    for(; http->push_headers_used > 0; --http->push_headers_used) {
-      free(http->push_headers[http->push_headers_used - 1]);
+  struct cf_h2_ctx *ctx = cf->ctx;
+  struct HTTP *stream = data->req.p.http;
+  CURLcode result = CURLE_OUT_OF_MEMORY;
+  int rc;
+  nghttp2_session_callbacks *cbs = NULL;
+
+  DEBUGASSERT(!ctx->h2);
+  ctx->inbuf = malloc(H2_BUFSIZE);
+  if(!ctx->inbuf)
+      goto out;
+  /* we want to aggregate small frames, SETTINGS, PRIO, UPDATES */
+  Curl_dyn_init(&ctx->outbuf, 4*1024);
+
+  rc = nghttp2_session_callbacks_new(&cbs);
+  if(rc) {
+    failf(data, "Couldn't initialize nghttp2 callbacks");
+    goto out;
+  }
+
+  nghttp2_session_callbacks_set_send_callback(cbs, send_callback);
+  nghttp2_session_callbacks_set_on_frame_recv_callback(cbs, on_frame_recv);
+  nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
+    cbs, on_data_chunk_recv);
+  nghttp2_session_callbacks_set_on_stream_close_callback(cbs, on_stream_close);
+  nghttp2_session_callbacks_set_on_begin_headers_callback(
+    cbs, on_begin_headers);
+  nghttp2_session_callbacks_set_on_header_callback(cbs, on_header);
+  nghttp2_session_callbacks_set_error_callback(cbs, error_callback);
+
+  /* The nghttp2 session is not yet setup, do it */
+  rc = h2_client_new(cf, cbs);
+  if(rc) {
+    failf(data, "Couldn't initialize nghttp2");
+    goto out;
+  }
+  ctx->max_concurrent_streams = DEFAULT_MAX_CONCURRENT_STREAMS;
+
+  result = http2_data_setup(cf, data);
+  if(result)
+    goto out;
+
+  if(via_h1_upgrade) {
+    /* HTTP/1.1 Upgrade issued. H2 Settings have already been submitted
+     * in the H1 request and we upgrade from there. This stream
+     * is opened implicitly as #1. */
+    uint8_t binsettings[H2_BINSETTINGS_LEN];
+    size_t  binlen; /* length of the binsettings data */
+
+    binlen = populate_binsettings(binsettings, data);
+
+    stream->stream_id = 1;
+    /* queue SETTINGS frame (again) */
+    rc = nghttp2_session_upgrade2(ctx->h2, binsettings, binlen,
+                                  data->state.httpreq == HTTPREQ_HEAD,
+                                  NULL);
+    if(rc) {
+      failf(data, "nghttp2_session_upgrade2() failed: %s(%d)",
+            nghttp2_strerror(rc), rc);
+      result = CURLE_HTTP2;
+      goto out;
+    }
+
+    rc = nghttp2_session_set_stream_user_data(ctx->h2, stream->stream_id,
+                                              data);
+    if(rc) {
+      infof(data, "http/2: failed to set user_data for stream %u",
+            stream->stream_id);
+      DEBUGASSERT(0);
     }
-    free(http->push_headers);
-    http->push_headers = NULL;
   }
-}
+  else {
+    nghttp2_settings_entry iv[H2_SETTINGS_IV_LEN];
+    int ivlen;
 
-/*
- * Disconnects *a* connection used for HTTP/2. It might be an old one from the
- * connection cache and not the "main" one. Don't touch the easy handle!
- */
+    /* H2 Settings need to be submitted. Stream is not open yet. */
+    DEBUGASSERT(stream->stream_id == -1);
 
-static CURLcode http2_disconnect(struct Curl_easy *data,
-                                 struct connectdata *conn,
-                                 bool dead_connection)
-{
-  struct http_conn *c = &conn->proto.httpc;
-  (void)dead_connection;
-#ifndef DEBUG_HTTP2
-  (void)data;
-#endif
+    ivlen = populate_settings(iv, data);
+    rc = nghttp2_submit_settings(ctx->h2, NGHTTP2_FLAG_NONE,
+                                 iv, ivlen);
+    if(rc) {
+      failf(data, "nghttp2_submit_settings() failed: %s(%d)",
+            nghttp2_strerror(rc), rc);
+      result = CURLE_HTTP2;
+      goto out;
+    }
+  }
 
-  H2BUGF(infof(data, "HTTP/2 DISCONNECT starts now"));
+  rc = nghttp2_session_set_local_window_size(ctx->h2, NGHTTP2_FLAG_NONE, 0,
+                                             HTTP2_HUGE_WINDOW_SIZE);
+  if(rc) {
+    failf(data, "nghttp2_session_set_local_window_size() failed: %s(%d)",
+          nghttp2_strerror(rc), rc);
+    result = CURLE_HTTP2;
+    goto out;
+  }
 
-  nghttp2_session_del(c->h2);
-  Curl_safefree(c->inbuf);
+  /* all set, traffic will be send on connect */
+  result = CURLE_OK;
 
-  H2BUGF(infof(data, "HTTP/2 DISCONNECT done"));
+out:
+  if(cbs)
+    nghttp2_session_callbacks_del(cbs);
+  return result;
+}
 
-  return CURLE_OK;
+static CURLcode  h2_session_send(struct Curl_cfilter *cf,
+                                 struct Curl_easy *data);
+static int h2_process_pending_input(struct Curl_cfilter *cf,
+                                    struct Curl_easy *data,
+                                    CURLcode *err);
+
+/*
+ * http2_stream_free() free HTTP2 stream related data
+ */
+static void http2_stream_free(struct HTTP *stream)
+{
+  if(stream) {
+    Curl_dyn_free(&stream->header_recvbuf);
+    for(; stream->push_headers_used > 0; --stream->push_headers_used) {
+      free(stream->push_headers[stream->push_headers_used - 1]);
+    }
+    free(stream->push_headers);
+    stream->push_headers = NULL;
+  }
 }
 
 /*
  * The server may send us data at any point (e.g. PING frames). Therefore,
  * we cannot assume that an HTTP/2 socket is dead just because it is readable.
  *
- * Instead, if it is readable, run Curl_connalive() to peek at the socket
+ * Check the lower filters first and, if successful, peek at the socket
  * and distinguish between closed and data.
  */
-static bool http2_connisdead(struct Curl_easy *data, struct connectdata *conn)
+static bool http2_connisdead(struct Curl_cfilter *cf, struct Curl_easy *data)
 {
+  struct cf_h2_ctx *ctx = cf->ctx;
   int sval;
   bool dead = TRUE;
 
-  if(conn->bits.close)
+  if(!cf->next || !cf->next->cft->is_alive(cf->next, data))
     return TRUE;
 
-  sval = SOCKET_READABLE(conn->sock[FIRSTSOCKET], 0);
+  sval = SOCKET_READABLE(Curl_conn_cf_get_socket(cf, data), 0);
   if(sval == 0) {
     /* timeout */
     dead = FALSE;
@@ -190,177 +389,57 @@ static bool http2_connisdead(struct Curl_easy *data, struct connectdata *conn)
     dead = TRUE;
   }
   else if(sval & CURL_CSELECT_IN) {
-    /* readable with no error. could still be closed */
-    dead = !Curl_connalive(data, conn);
-    if(!dead) {
-      /* 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" */
-      CURLcode result;
-      struct http_conn *httpc = &conn->proto.httpc;
-      ssize_t nread = -1;
-      if(httpc->recv_underlying)
-        /* if called "too early", this pointer isn't setup yet! */
-        nread = ((Curl_recv *)httpc->recv_underlying)(
-          data, FIRSTSOCKET, httpc->inbuf, H2_BUFSIZE, &result);
-      if(nread != -1) {
-        H2BUGF(infof(data,
-                     "%d bytes stray data read before trying h2 connection",
-                     (int)nread));
-        httpc->nread_inbuf = 0;
-        httpc->inbuflen = nread;
-        if(h2_process_pending_input(data, httpc, &result) < 0)
-          /* immediate error, considered dead */
-          dead = TRUE;
-      }
-      else
-        /* the read failed so let's say this is dead anyway */
+    /* 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" */
+    CURLcode result;
+    ssize_t nread = -1;
+
+    Curl_attach_connection(data, cf->conn);
+    nread = Curl_conn_cf_recv(cf->next, data,
+                              ctx->inbuf, H2_BUFSIZE, &result);
+    dead = FALSE;
+    if(nread != -1) {
+      DEBUGF(LOG_CF(data, cf, "%d bytes stray data read before trying "
+                    "h2 connection", (int)nread));
+      ctx->nread_inbuf = 0;
+      ctx->inbuflen = nread;
+      if(h2_process_pending_input(cf, data, &result) < 0)
+        /* immediate error, considered dead */
         dead = TRUE;
     }
+    else
+      /* the read failed so let's say this is dead anyway */
+      dead = TRUE;
+    Curl_detach_connection(data);
   }
 
   return dead;
 }
 
-/*
- * Set the transfer that is currently using this HTTP/2 connection.
- */
-static void set_transfer(struct http_conn *c,
-                         struct Curl_easy *data)
-{
-  c->trnsfr = data;
-}
-
-/*
- * Get the transfer that is currently using this HTTP/2 connection.
- */
-static struct Curl_easy *get_transfer(struct http_conn *c)
-{
-  DEBUGASSERT(c && c->trnsfr);
-  return c->trnsfr;
-}
-
-static unsigned int http2_conncheck(struct Curl_easy *data,
-                                    struct connectdata *conn,
-                                    unsigned int checks_to_perform)
+static CURLcode http2_send_ping(struct Curl_cfilter *cf,
+                                struct Curl_easy *data)
 {
-  unsigned int ret_val = CONNRESULT_NONE;
-  struct http_conn *c = &conn->proto.httpc;
+  struct cf_h2_ctx *ctx = cf->ctx;
   int rc;
-  bool send_frames = false;
-
-  if(checks_to_perform & CONNCHECK_ISDEAD) {
-    if(http2_connisdead(data, conn))
-      ret_val |= CONNRESULT_DEAD;
-  }
-
-  if(checks_to_perform & CONNCHECK_KEEPALIVE) {
-    struct curltime now = Curl_now();
-    timediff_t elapsed = Curl_timediff(now, conn->keepalive);
 
-    if(elapsed > data->set.upkeep_interval_ms) {
-      /* Perform an HTTP/2 PING */
-      rc = nghttp2_submit_ping(c->h2, 0, ZERO_NULL);
-      if(!rc) {
-        /* Successfully added a PING frame to the session. Need to flag this
-           so the frame is sent. */
-        send_frames = true;
-      }
-      else {
-       failf(data, "nghttp2_submit_ping() failed: %s(%d)",
-             nghttp2_strerror(rc), rc);
-      }
-
-      conn->keepalive = now;
-    }
+  rc = nghttp2_submit_ping(ctx->h2, 0, ZERO_NULL);
+  if(rc) {
+    failf(data, "nghttp2_submit_ping() failed: %s(%d)",
+          nghttp2_strerror(rc), rc);
+   return CURLE_HTTP2;
   }
 
-  if(send_frames) {
-    set_transfer(c, data); /* set the transfer */
-    rc = nghttp2_session_send(c->h2);
-    if(rc)
-      failf(data, "nghttp2_session_send() failed: %s(%d)",
-            nghttp2_strerror(rc), rc);
+  rc = nghttp2_session_send(ctx->h2);
+  if(rc) {
+    failf(data, "nghttp2_session_send() failed: %s(%d)",
+          nghttp2_strerror(rc), rc);
+    return CURLE_SEND_ERROR;
   }
-
-  return ret_val;
-}
-
-/* called from http_setup_conn */
-void Curl_http2_setup_req(struct Curl_easy *data)
-{
-  struct HTTP *http = data->req.p.http;
-  http->bodystarted = FALSE;
-  http->status_code = -1;
-  http->pausedata = NULL;
-  http->pauselen = 0;
-  http->closed = FALSE;
-  http->close_handled = FALSE;
-  http->mem = NULL;
-  http->len = 0;
-  http->memlen = 0;
-  http->error = NGHTTP2_NO_ERROR;
-}
-
-/* called from http_setup_conn */
-void Curl_http2_setup_conn(struct connectdata *conn)
-{
-  conn->proto.httpc.settings.max_concurrent_streams =
-    DEFAULT_MAX_CONCURRENT_STREAMS;
+  return CURLE_OK;
 }
 
 /*
- * HTTP2 handler interface. This isn't added to the general list of protocols
- * but will be used at run-time when the protocol is dynamically switched from
- * HTTP to HTTP2.
- */
-static const struct Curl_handler Curl_handler_http2 = {
-  "HTTP",                               /* scheme */
-  ZERO_NULL,                            /* setup_connection */
-  Curl_http,                            /* do_it */
-  Curl_http_done,                       /* done */
-  ZERO_NULL,                            /* do_more */
-  ZERO_NULL,                            /* connect_it */
-  ZERO_NULL,                            /* connecting */
-  ZERO_NULL,                            /* doing */
-  http2_getsock,                        /* proto_getsock */
-  http2_getsock,                        /* doing_getsock */
-  ZERO_NULL,                            /* domore_getsock */
-  http2_getsock,                        /* perform_getsock */
-  http2_disconnect,                     /* disconnect */
-  ZERO_NULL,                            /* readwrite */
-  http2_conncheck,                      /* connection_check */
-  ZERO_NULL,                            /* attach connection */
-  PORT_HTTP,                            /* defport */
-  CURLPROTO_HTTP,                       /* protocol */
-  CURLPROTO_HTTP,                       /* family */
-  PROTOPT_STREAM                        /* flags */
-};
-
-static const struct Curl_handler Curl_handler_http2_ssl = {
-  "HTTPS",                              /* scheme */
-  ZERO_NULL,                            /* setup_connection */
-  Curl_http,                            /* do_it */
-  Curl_http_done,                       /* done */
-  ZERO_NULL,                            /* do_more */
-  ZERO_NULL,                            /* connect_it */
-  ZERO_NULL,                            /* connecting */
-  ZERO_NULL,                            /* doing */
-  http2_getsock,                        /* proto_getsock */
-  http2_getsock,                        /* doing_getsock */
-  ZERO_NULL,                            /* domore_getsock */
-  http2_getsock,                        /* perform_getsock */
-  http2_disconnect,                     /* disconnect */
-  ZERO_NULL,                            /* readwrite */
-  http2_conncheck,                      /* connection_check */
-  ZERO_NULL,                            /* attach connection */
-  PORT_HTTP,                            /* defport */
-  CURLPROTO_HTTPS,                      /* protocol */
-  CURLPROTO_HTTP,                       /* family */
-  PROTOPT_SSL | PROTOPT_STREAM          /* flags */
-};
-
-/*
  * Store nghttp2 version info in this buffer.
  */
 void Curl_http2_ver(char *p, size_t len)
@@ -369,31 +448,75 @@ void Curl_http2_ver(char *p, size_t len)
   (void)msnprintf(p, len, "nghttp2/%s", h2->version_str);
 }
 
+static CURLcode flush_output(struct Curl_cfilter *cf,
+                             struct Curl_easy *data)
+{
+  struct cf_h2_ctx *ctx = cf->ctx;
+  size_t buflen = Curl_dyn_len(&ctx->outbuf);
+  ssize_t written;
+  CURLcode result;
+
+  if(!buflen)
+    return CURLE_OK;
+
+  DEBUGF(LOG_CF(data, cf, "h2 conn flush %zu bytes", buflen));
+  written = Curl_conn_cf_send(cf->next, data, Curl_dyn_ptr(&ctx->outbuf),
+                              buflen, &result);
+  if(written < 0) {
+    return result;
+  }
+  if((size_t)written < buflen) {
+    Curl_dyn_tail(&ctx->outbuf, buflen - (size_t)written);
+    return CURLE_AGAIN;
+  }
+  else {
+    Curl_dyn_reset(&ctx->outbuf);
+  }
+  return CURLE_OK;
+}
+
 /*
  * The implementation of nghttp2_send_callback type. Here we write |data| with
  * size |length| to the network and return the number of bytes actually
  * written. See the documentation of nghttp2_send_callback for the details.
  */
 static ssize_t send_callback(nghttp2_session *h2,
-                             const uint8_t *mem, size_t length, int flags,
+                             const uint8_t *buf, size_t blen, int flags,
                              void *userp)
 {
-  struct connectdata *conn = (struct connectdata *)userp;
-  struct http_conn *c = &conn->proto.httpc;
-  struct Curl_easy *data = get_transfer(c);
+  struct Curl_cfilter *cf = userp;
+  struct cf_h2_ctx *ctx = cf->ctx;
+  struct Curl_easy *data = CF_DATA_CURRENT(cf);
   ssize_t written;
   CURLcode result = CURLE_OK;
+  size_t buflen = Curl_dyn_len(&ctx->outbuf);
 
   (void)h2;
   (void)flags;
+  DEBUGASSERT(data);
 
-  if(!c->send_underlying)
-    /* called before setup properly! */
-    return NGHTTP2_ERR_CALLBACK_FAILURE;
-
-  written = ((Curl_send*)c->send_underlying)(data, FIRSTSOCKET,
-                                             mem, length, &result);
+  if(blen < 1024 && (buflen + blen + 1 < ctx->outbuf.toobig)) {
+    result = Curl_dyn_addn(&ctx->outbuf, buf, blen);
+    if(result) {
+      failf(data, "Failed to add data to output buffer");
+      return NGHTTP2_ERR_CALLBACK_FAILURE;
+    }
+    return blen;
+  }
+  if(buflen) {
+    /* not adding, flush buffer */
+    result = flush_output(cf, data);
+    if(result) {
+      if(result == CURLE_AGAIN) {
+        return NGHTTP2_ERR_WOULDBLOCK;
+      }
+      failf(data, "Failed sending HTTP2 data");
+      return NGHTTP2_ERR_CALLBACK_FAILURE;
+    }
+  }
 
+  DEBUGF(LOG_CF(data, cf, "h2 conn send %zu bytes", blen));
+  written = Curl_conn_cf_send(cf->next, data, buf, blen, &result);
   if(result == CURLE_AGAIN) {
     return NGHTTP2_ERR_WOULDBLOCK;
   }
@@ -467,26 +590,33 @@ char *curl_pushheader_byname(struct curl_pushheaders *h, const char *header)
 /*
  * This specific transfer on this connection has been "drained".
  */
-static void drained_transfer(struct Curl_easy *data,
-                             struct http_conn *httpc)
+static void drained_transfer(struct Curl_cfilter *cf,
+                             struct Curl_easy *data)
 {
-  DEBUGASSERT(httpc->drain_total >= data->state.drain);
-  httpc->drain_total -= data->state.drain;
-  data->state.drain = 0;
+  if(data->state.drain) {
+    struct cf_h2_ctx *ctx = cf->ctx;
+    DEBUGASSERT(ctx->drain_total > 0);
+    ctx->drain_total--;
+    data->state.drain = 0;
+  }
 }
 
 /*
  * Mark this transfer to get "drained".
  */
-static void drain_this(struct Curl_easy *data,
-                       struct http_conn *httpc)
+static void drain_this(struct Curl_cfilter *cf,
+                       struct Curl_easy *data)
 {
-  data->state.drain++;
-  httpc->drain_total++;
-  DEBUGASSERT(httpc->drain_total >= data->state.drain);
+  if(!data->state.drain) {
+    struct cf_h2_ctx *ctx = cf->ctx;
+    data->state.drain = 1;
+    ctx->drain_total++;
+    DEBUGASSERT(ctx->drain_total > 0);
+  }
 }
 
-static struct Curl_easy *duphandle(struct Curl_easy *data)
+static struct Curl_easy *h2_duphandle(struct Curl_cfilter *cf,
+                                      struct Curl_easy *data)
 {
   struct Curl_easy *second = curl_easy_duphandle(data);
   if(second) {
@@ -497,9 +627,8 @@ static struct Curl_easy *duphandle(struct Curl_easy *data)
     }
     else {
       second->req.p.http = http;
-      Curl_dyn_init(&http->header_recvbuf, DYN_H2_HEADERS);
-      Curl_http2_setup_req(second);
-      second->state.stream_weight = data->state.stream_weight;
+      http2_data_setup(cf, second);
+      second->state.priority.weight = data->state.priority.weight;
     }
   }
   return second;
@@ -559,22 +688,23 @@ static int set_transfer_url(struct Curl_easy *data,
   return 0;
 }
 
-static int push_promise(struct Curl_easy *data,
-                        struct connectdata *conn,
+static int push_promise(struct Curl_cfilter *cf,
+                        struct Curl_easy *data,
                         const nghttp2_push_promise *frame)
 {
+  struct cf_h2_ctx *ctx = cf->ctx;
   int rv; /* one of the CURL_PUSH_* defines */
-  H2BUGF(infof(data, "PUSH_PROMISE received, stream %u",
-               frame->promised_stream_id));
+
+  DEBUGF(LOG_CF(data, cf, "[h2sid=%u] PUSH_PROMISE received",
+                frame->promised_stream_id));
   if(data->multi->push_cb) {
     struct HTTP *stream;
     struct HTTP *newstream;
     struct curl_pushheaders heads;
     CURLMcode rc;
-    struct http_conn *httpc;
     size_t i;
     /* clone the parent */
-    struct Curl_easy *newhandle = duphandle(data);
+    struct Curl_easy *newhandle = h2_duphandle(cf, data);
     if(!newhandle) {
       infof(data, "failed to duplicate handle");
       rv = CURL_PUSH_DENY; /* FAIL HARD */
@@ -584,7 +714,7 @@ static int push_promise(struct Curl_easy *data,
     heads.data = data;
     heads.frame = frame;
     /* ask the application */
-    H2BUGF(infof(data, "Got PUSH_PROMISE, ask application"));
+    DEBUGF(LOG_CF(data, cf, "Got PUSH_PROMISE, ask application"));
 
     stream = data->req.p.http;
     if(!stream) {
@@ -630,7 +760,7 @@ static int push_promise(struct Curl_easy *data,
 
     /* approved, add to the multi handle and immediately switch to PERFORM
        state with the given connection !*/
-    rc = Curl_multi_add_perform(data->multi, newhandle, conn);
+    rc = Curl_multi_add_perform(data->multi, newhandle, cf->conn);
     if(rc) {
       infof(data, "failed to add handle to multi");
       http2_stream_free(newhandle->req.p.http);
@@ -640,8 +770,7 @@ static int push_promise(struct Curl_easy *data,
       goto fail;
     }
 
-    httpc = &conn->proto.httpc;
-    rv = nghttp2_session_set_stream_user_data(httpc->h2,
+    rv = nghttp2_session_set_stream_user_data(ctx->h2,
                                               frame->promised_stream_id,
                                               newhandle);
     if(rv) {
@@ -655,84 +784,82 @@ static int push_promise(struct Curl_easy *data,
     Curl_dyn_init(&newstream->trailer_recvbuf, DYN_H2_TRAILERS);
   }
   else {
-    H2BUGF(infof(data, "Got PUSH_PROMISE, ignore it"));
+    DEBUGF(LOG_CF(data, cf, "Got PUSH_PROMISE, ignore it"));
     rv = CURL_PUSH_DENY;
   }
   fail:
   return rv;
 }
 
-/*
- * multi_connchanged() is called to tell that there is a connection in
- * this multi handle that has changed state (multiplexing become possible, the
- * number of allowed streams changed or similar), and a subsequent use of this
- * multi handle should move CONNECT_PEND handles back to CONNECT to have them
- * retry.
- */
-static void multi_connchanged(struct Curl_multi *multi)
-{
-  multi->recheckstate = TRUE;
-}
-
 static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame,
                          void *userp)
 {
-  struct connectdata *conn = (struct connectdata *)userp;
-  struct http_conn *httpc = &conn->proto.httpc;
+  struct Curl_cfilter *cf = userp;
+  struct cf_h2_ctx *ctx = cf->ctx;
   struct Curl_easy *data_s = NULL;
   struct HTTP *stream = NULL;
-  struct Curl_easy *data = get_transfer(httpc);
+  struct Curl_easy *data = CF_DATA_CURRENT(cf);
   int rv;
   size_t left, ncopy;
   int32_t stream_id = frame->hd.stream_id;
   CURLcode result;
 
+  DEBUGASSERT(data);
   if(!stream_id) {
     /* stream ID zero is for connection-oriented stuff */
-    if(frame->hd.type == NGHTTP2_SETTINGS) {
-      uint32_t max_conn = httpc->settings.max_concurrent_streams;
-      H2BUGF(infof(data, "Got SETTINGS"));
-      httpc->settings.max_concurrent_streams =
-        nghttp2_session_get_remote_settings(
+    DEBUGASSERT(data);
+    switch(frame->hd.type) {
+    case NGHTTP2_SETTINGS: {
+      uint32_t max_conn = ctx->max_concurrent_streams;
+      DEBUGF(LOG_CF(data, cf, "recv frame SETTINGS"));
+      ctx->max_concurrent_streams = nghttp2_session_get_remote_settings(
           session, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS);
-      httpc->settings.enable_push =
-        nghttp2_session_get_remote_settings(
+      ctx->enable_push = nghttp2_session_get_remote_settings(
           session, NGHTTP2_SETTINGS_ENABLE_PUSH);
-      H2BUGF(infof(data, "MAX_CONCURRENT_STREAMS == %d",
-                   httpc->settings.max_concurrent_streams));
-      H2BUGF(infof(data, "ENABLE_PUSH == %s",
-                   httpc->settings.enable_push?"TRUE":"false"));
-      if(max_conn != httpc->settings.max_concurrent_streams) {
+      DEBUGF(LOG_CF(data, cf, "MAX_CONCURRENT_STREAMS == %d",
+                    ctx->max_concurrent_streams));
+      DEBUGF(LOG_CF(data, cf, "ENABLE_PUSH == %s",
+                    ctx->enable_push ? "TRUE" : "false"));
+      if(data && max_conn != ctx->max_concurrent_streams) {
         /* only signal change if the value actually changed */
-        infof(data,
-              "Connection state changed (MAX_CONCURRENT_STREAMS == %u)!",
-              httpc->settings.max_concurrent_streams);
+        DEBUGF(LOG_CF(data, cf, "MAX_CONCURRENT_STREAMS now %u",
+                      ctx->max_concurrent_streams));
+        multi_connchanged(data->multi);
+      }
+      break;
+    }
+    case NGHTTP2_GOAWAY:
+      if(data) {
+        infof(data, "recveived GOAWAY, error=%d, last_stream=%u",
+                    frame->goaway.error_code, frame->goaway.last_stream_id);
         multi_connchanged(data->multi);
       }
+      break;
+    case NGHTTP2_WINDOW_UPDATE:
+      DEBUGF(LOG_CF(data, cf, "recv frame WINDOW_UPDATE"));
+      break;
+    default:
+      DEBUGF(LOG_CF(data, cf, "recv frame %x on 0", frame->hd.type));
     }
     return 0;
   }
   data_s = nghttp2_session_get_stream_user_data(session, stream_id);
   if(!data_s) {
-    H2BUGF(infof(data,
-                 "No Curl_easy associated with stream: %u",
-                 stream_id));
+    DEBUGF(LOG_CF(data, cf, "[h2sid=%u] No Curl_easy associated",
+                  stream_id));
     return 0;
   }
 
   stream = data_s->req.p.http;
   if(!stream) {
-    H2BUGF(infof(data_s, "No proto pointer for stream: %u",
-                 stream_id));
+    DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] No proto pointer", stream_id));
     return NGHTTP2_ERR_CALLBACK_FAILURE;
   }
 
-  H2BUGF(infof(data_s, "on_frame_recv() header %x stream %u",
-               frame->hd.type, stream_id));
-
   switch(frame->hd.type) {
   case NGHTTP2_DATA:
     /* If body started on this stream, then receiving DATA is illegal. */
+    DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] recv frame DATA", stream_id));
     if(!stream->bodystarted) {
       rv = nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
                                      stream_id, NGHTTP2_PROTOCOL_ERROR);
@@ -741,8 +868,17 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame,
         return NGHTTP2_ERR_CALLBACK_FAILURE;
       }
     }
+    if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
+      /* Stream has ended. If there is pending data, ensure that read
+         will occur to consume it. */
+      if(!data->state.drain && stream->memlen) {
+        drain_this(cf, data_s);
+        Curl_expire(data, 0, EXPIRE_RUN_NOW);
+      }
+    }
     break;
   case NGHTTP2_HEADERS:
+    DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] recv frame HEADERS", stream_id));
     if(stream->bodystarted) {
       /* Only valid HEADERS after body started is trailer HEADERS.  We
          buffer them in on_header callback. */
@@ -776,19 +912,18 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame,
     stream->nread_header_recvbuf += ncopy;
 
     DEBUGASSERT(stream->mem);
-    H2BUGF(infof(data_s, "Store %zu bytes headers from stream %u at %p",
-                 ncopy, stream_id, stream->mem));
+    DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] %zu header bytes, at %p",
+                  stream_id, ncopy, (void *)stream->mem));
 
     stream->len -= ncopy;
     stream->memlen += ncopy;
 
-    drain_this(data_s, httpc);
-    /* if we receive data for another handle, wake that up */
-    if(get_transfer(httpc) != data_s)
-      Curl_expire(data_s, 0, EXPIRE_RUN_NOW);
+    drain_this(cf, data_s);
+    Curl_expire(data_s, 0, EXPIRE_RUN_NOW);
     break;
   case NGHTTP2_PUSH_PROMISE:
-    rv = push_promise(data_s, conn, &frame->push_promise);
+    DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] recv PUSH_PROMISE", stream_id));
+    rv = push_promise(cf, data_s, &frame->push_promise);
     if(rv) { /* deny! */
       int h2;
       DEBUGASSERT((rv > CURL_PUSH_OK) && (rv <= CURL_PUSH_ERROROUT));
@@ -798,14 +933,18 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame,
       if(nghttp2_is_fatal(h2))
         return NGHTTP2_ERR_CALLBACK_FAILURE;
       else if(rv == CURL_PUSH_ERROROUT) {
-        DEBUGF(infof(data_s, "Fail the parent stream (too)"));
+        DEBUGF(LOG_CF(data_s, cf, "Fail the parent stream (too)"));
         return NGHTTP2_ERR_CALLBACK_FAILURE;
       }
     }
     break;
+  case NGHTTP2_RST_STREAM:
+    DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] recv RST", stream_id));
+    stream->reset = TRUE;
+    break;
   default:
-    H2BUGF(infof(data_s, "Got frame type %x for stream %u",
-                 frame->hd.type, stream_id));
+    DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] recv frame %x",
+                  stream_id, frame->hd.type));
     break;
   }
   return 0;
@@ -815,15 +954,15 @@ static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags,
                               int32_t stream_id,
                               const uint8_t *mem, size_t len, void *userp)
 {
+  struct Curl_cfilter *cf = userp;
+  struct cf_h2_ctx *ctx = cf->ctx;
   struct HTTP *stream;
   struct Curl_easy *data_s;
   size_t nread;
-  struct connectdata *conn = (struct connectdata *)userp;
-  struct http_conn *httpc = &conn->proto.httpc;
-  (void)session;
   (void)flags;
 
   DEBUGASSERT(stream_id); /* should never be a zero stream ID here */
+  DEBUGASSERT(CF_DATA_CURRENT(cf));
 
   /* get the stream from the hash based on Stream ID */
   data_s = nghttp2_session_get_stream_user_data(session, stream_id);
@@ -831,8 +970,8 @@ static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags,
     /* Receiving a Stream ID not in the hash should not happen - unless
        we have aborted a transfer artificially and there were more data
        in the pipeline. Silently ignore. */
-    H2BUGF(fprintf(stderr, "Data for stream %u but it doesn't exist\n",
-                   stream_id));
+    DEBUGF(LOG_CF(CF_DATA_CURRENT(cf), cf, "[h2sid=%u] Data for unknown",
+                  stream_id));
     return 0;
   }
 
@@ -846,36 +985,38 @@ static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags,
   stream->len -= nread;
   stream->memlen += nread;
 
-  drain_this(data_s, &conn->proto.httpc);
-
   /* if we receive data for another handle, wake that up */
-  if(get_transfer(httpc) != data_s)
+  if(CF_DATA_CURRENT(cf) != data_s) {
+    drain_this(cf, data_s);
     Curl_expire(data_s, 0, EXPIRE_RUN_NOW);
+  }
 
-  H2BUGF(infof(data_s, "%zu data received for stream %u "
-               "(%zu left in buffer %p, total %zu)",
-               nread, stream_id,
-               stream->len, stream->mem,
-               stream->memlen));
+  DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] %zu DATA recvd, "
+                "(buffer now holds %zu, %zu still free in %p)",
+                stream_id, nread,
+                stream->memlen, stream->len, (void *)stream->mem));
 
   if(nread < len) {
     stream->pausedata = mem + nread;
     stream->pauselen = len - nread;
-    H2BUGF(infof(data_s, "NGHTTP2_ERR_PAUSE - %zu bytes out of buffer"
-                 ", stream %u",
-                 len - nread, stream_id));
-    data_s->conn->proto.httpc.pause_stream_id = stream_id;
-
+    DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] %zu not recvd -> NGHTTP2_ERR_PAUSE",
+                  stream_id, len - nread));
+    ctx->pause_stream_id = stream_id;
+    drain_this(cf, data_s);
     return NGHTTP2_ERR_PAUSE;
   }
 
+#if 0
   /* pause execution of nghttp2 if we received data for another handle
      in order to process them first. */
-  if(get_transfer(httpc) != data_s) {
-    data_s->conn->proto.httpc.pause_stream_id = stream_id;
-
+  if(CF_DATA_CURRENT(cf) != data_s) {
+    ctx->pause_stream_id = stream_id;
+    DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] not call_data -> NGHTTP2_ERR_PAUSE",
+                  stream_id));
+    drain_this(cf, data_s);
     return NGHTTP2_ERR_PAUSE;
   }
+#endif
 
   return 0;
 }
@@ -883,15 +1024,15 @@ static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags,
 static int on_stream_close(nghttp2_session *session, int32_t stream_id,
                            uint32_t error_code, void *userp)
 {
+  struct Curl_cfilter *cf = userp;
+  struct cf_h2_ctx *ctx = cf->ctx;
   struct Curl_easy *data_s;
   struct HTTP *stream;
-  struct connectdata *conn = (struct connectdata *)userp;
   int rv;
   (void)session;
   (void)stream_id;
 
   if(stream_id) {
-    struct http_conn *httpc;
     /* get the stream from the hash based on Stream ID, stream ID zero is for
        connection-oriented stuff */
     data_s = nghttp2_session_get_stream_user_data(session, stream_id);
@@ -900,16 +1041,17 @@ static int on_stream_close(nghttp2_session *session, int32_t stream_id,
          decided to reject stream (e.g., PUSH_PROMISE). */
       return 0;
     }
-    H2BUGF(infof(data_s, "on_stream_close(), %s (err %d), stream %u",
-                 nghttp2_http2_strerror(error_code), error_code, stream_id));
+    DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] on_stream_close(), %s (err %d)",
+                  stream_id, nghttp2_http2_strerror(error_code), error_code));
     stream = data_s->req.p.http;
     if(!stream)
       return NGHTTP2_ERR_CALLBACK_FAILURE;
 
     stream->closed = TRUE;
-    httpc = &conn->proto.httpc;
-    drain_this(data_s, httpc);
-    Curl_expire(data_s, 0, EXPIRE_RUN_NOW);
+    if(CF_DATA_CURRENT(cf) != data_s) {
+      drain_this(cf, data_s);
+      Curl_expire(data_s, 0, EXPIRE_RUN_NOW);
+    }
     stream->error = error_code;
 
     /* remove the entry from the hash as the stream is now gone */
@@ -919,12 +1061,12 @@ static int on_stream_close(nghttp2_session *session, int32_t stream_id,
             stream_id);
       DEBUGASSERT(0);
     }
-    if(stream_id == httpc->pause_stream_id) {
-      H2BUGF(infof(data_s, "Stopped the pause stream"));
-      httpc->pause_stream_id = 0;
+    if(stream_id == ctx->pause_stream_id) {
+      DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] closed the pause stream",
+                    stream_id));
+      ctx->pause_stream_id = 0;
     }
-    H2BUGF(infof(data_s, "Removed stream %u hash", stream_id));
-    stream->stream_id = 0; /* cleared */
+    DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] closed, cleared", stream_id));
   }
   return 0;
 }
@@ -932,16 +1074,17 @@ static int on_stream_close(nghttp2_session *session, int32_t stream_id,
 static int on_begin_headers(nghttp2_session *session,
                             const nghttp2_frame *frame, void *userp)
 {
+  struct Curl_cfilter *cf = userp;
   struct HTTP *stream;
   struct Curl_easy *data_s = NULL;
-  (void)userp;
 
+  (void)cf;
   data_s = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id);
   if(!data_s) {
     return 0;
   }
 
-  H2BUGF(infof(data_s, "on_begin_headers() was called"));
+  DEBUGF(LOG_CF(data_s, cf, "on_begin_headers() was called"));
 
   if(frame->hd.type != NGHTTP2_HEADERS) {
     return 0;
@@ -989,11 +1132,10 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame,
                      uint8_t flags,
                      void *userp)
 {
+  struct Curl_cfilter *cf = userp;
   struct HTTP *stream;
   struct Curl_easy *data_s;
   int32_t stream_id = frame->hd.stream_id;
-  struct connectdata *conn = (struct connectdata *)userp;
-  struct http_conn *httpc = &conn->proto.httpc;
   CURLcode result;
   (void)flags;
 
@@ -1020,13 +1162,14 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame,
     if(!strcmp(H2H3_PSEUDO_AUTHORITY, (const char *)name)) {
       /* pseudo headers are lower case */
       int rc = 0;
-      char *check = aprintf("%s:%d", conn->host.name, conn->remote_port);
+      char *check = aprintf("%s:%d", cf->conn->host.name,
+                            cf->conn->remote_port);
       if(!check)
         /* no memory */
         return NGHTTP2_ERR_CALLBACK_FAILURE;
       if(!strcasecompare(check, (const char *)value) &&
-         ((conn->remote_port != conn->given->defport) ||
-          !strcasecompare(conn->host.name, (const char *)value))) {
+         ((cf->conn->remote_port != cf->conn->given->defport) ||
+          !strcasecompare(cf->conn->host.name, (const char *)value))) {
         /* This is push is not for the same authority that was asked for in
          * the URL. RFC 7540 section 8.2 says: "A client MUST treat a
          * PUSH_PROMISE for which the server is not authoritative as a stream
@@ -1075,11 +1218,13 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame,
 
   if(stream->bodystarted) {
     /* This is a trailer */
-    H2BUGF(infof(data_s, "h2 trailer: %.*s: %.*s", namelen, name, valuelen,
-                 value));
+    DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] trailer: %.*s: %.*s",
+                  stream->stream_id,
+                  (int)namelen, name,
+                  (int)valuelen, value));
     result = Curl_dyn_addf(&stream->trailer_recvbuf,
-                           "%.*s: %.*s\r\n", namelen, name,
-                           valuelen, value);
+                           "%.*s: %.*s\r\n", (int)namelen, name,
+                           (int)valuelen, value);
     if(result)
       return NGHTTP2_ERR_CALLBACK_FAILURE;
 
@@ -1110,11 +1255,11 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame,
     if(result)
       return NGHTTP2_ERR_CALLBACK_FAILURE;
     /* if we receive data for another handle, wake that up */
-    if(get_transfer(httpc) != data_s)
+    if(CF_DATA_CURRENT(cf) != data_s)
       Curl_expire(data_s, 0, EXPIRE_RUN_NOW);
 
-    H2BUGF(infof(data_s, "h2 status: HTTP/2 %03d (easy %p)",
-                 stream->status_code, data_s));
+    DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] status: HTTP/2 %03d",
+                  stream->stream_id, stream->status_code));
     return 0;
   }
 
@@ -1134,11 +1279,13 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame,
   if(result)
     return NGHTTP2_ERR_CALLBACK_FAILURE;
   /* if we receive data for another handle, wake that up */
-  if(get_transfer(httpc) != data_s)
+  if(CF_DATA_CURRENT(cf) != data_s)
     Curl_expire(data_s, 0, EXPIRE_RUN_NOW);
 
-  H2BUGF(infof(data_s, "h2 header: %.*s: %.*s", namelen, name, valuelen,
-               value));
+  DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] header: %.*s: %.*s",
+                stream->stream_id,
+                (int)namelen, name,
+                (int)valuelen, value));
 
   return 0; /* 0 is successful */
 }
@@ -1150,12 +1297,13 @@ static ssize_t data_source_read_callback(nghttp2_session *session,
                                          nghttp2_data_source *source,
                                          void *userp)
 {
+  struct Curl_cfilter *cf = userp;
   struct Curl_easy *data_s;
   struct HTTP *stream = NULL;
   size_t nread;
   (void)source;
-  (void)userp;
 
+  (void)cf;
   if(stream_id) {
     /* get the stream from the hash based on Stream ID, stream ID zero is for
        connection-oriented stuff */
@@ -1186,9 +1334,8 @@ static ssize_t data_source_read_callback(nghttp2_session *session,
   else if(nread == 0)
     return NGHTTP2_ERR_DEFERRED;
 
-  H2BUGF(infof(data_s, "data_source_read_callback: "
-               "returns %zu bytes stream %u",
-               nread, stream_id));
+  DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] data_source_read_callback: "
+                "returns %zu bytes", stream_id, nread));
 
   return nread;
 }
@@ -1207,147 +1354,56 @@ static int error_callback(nghttp2_session *session,
 }
 #endif
 
-static void populate_settings(struct Curl_easy *data,
-                              struct http_conn *httpc)
-{
-  nghttp2_settings_entry *iv = httpc->local_settings;
-
-  iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
-  iv[0].value = Curl_multi_max_concurrent_streams(data->multi);
-
-  iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
-  iv[1].value = HTTP2_HUGE_WINDOW_SIZE;
-
-  iv[2].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH;
-  iv[2].value = data->multi->push_cb != NULL;
-
-  httpc->local_settings_num = 3;
-}
-
-void Curl_http2_done(struct Curl_easy *data, bool premature)
+static void http2_data_done(struct Curl_cfilter *cf,
+                            struct Curl_easy *data, bool premature)
 {
-  struct HTTP *http = data->req.p.http;
-  struct http_conn *httpc = &data->conn->proto.httpc;
+  struct cf_h2_ctx *ctx = cf->ctx;
+  struct HTTP *stream = data->req.p.http;
 
   /* there might be allocated resources done before this got the 'h2' pointer
      setup */
-  Curl_dyn_free(&http->header_recvbuf);
-  Curl_dyn_free(&http->trailer_recvbuf);
-  if(http->push_headers) {
+  Curl_dyn_free(&stream->header_recvbuf);
+  Curl_dyn_free(&stream->trailer_recvbuf);
+  if(stream->push_headers) {
     /* if they weren't used and then freed before */
-    for(; http->push_headers_used > 0; --http->push_headers_used) {
-      free(http->push_headers[http->push_headers_used - 1]);
+    for(; stream->push_headers_used > 0; --stream->push_headers_used) {
+      free(stream->push_headers[stream->push_headers_used - 1]);
     }
-    free(http->push_headers);
-    http->push_headers = NULL;
+    free(stream->push_headers);
+    stream->push_headers = NULL;
   }
 
-  if(!(data->conn->handler->protocol&PROTO_FAMILY_HTTP) ||
-     !httpc->h2) /* not HTTP/2 ? */
+  if(!ctx || !ctx->h2)
     return;
 
   /* do this before the reset handling, as that might clear ->stream_id */
-  if(http->stream_id == httpc->pause_stream_id) {
-    H2BUGF(infof(data, "DONE the pause stream (%u)", http->stream_id));
-    httpc->pause_stream_id = 0;
+  if(stream->stream_id && stream->stream_id == ctx->pause_stream_id) {
+    DEBUGF(LOG_CF(data, cf, "[h2sid=%u] DONE, the pause stream",
+                  stream->stream_id));
+    ctx->pause_stream_id = 0;
   }
-  if(premature || (!http->closed && http->stream_id)) {
+
+  if(premature || (!stream->closed && stream->stream_id)) {
     /* RST_STREAM */
-    set_transfer(httpc, data); /* set the transfer */
-    H2BUGF(infof(data, "RST stream %u", http->stream_id));
-    if(!nghttp2_submit_rst_stream(httpc->h2, NGHTTP2_FLAG_NONE,
-                                  http->stream_id, NGHTTP2_STREAM_CLOSED))
-      (void)nghttp2_session_send(httpc->h2);
+    DEBUGF(LOG_CF(data, cf, "[h2sid=%u] RST", stream->stream_id));
+    if(!nghttp2_submit_rst_stream(ctx->h2, NGHTTP2_FLAG_NONE,
+                                  stream->stream_id, NGHTTP2_STREAM_CLOSED))
+      (void)nghttp2_session_send(ctx->h2);
   }
 
   if(data->state.drain)
-    drained_transfer(data, httpc);
+    drained_transfer(cf, data);
 
   /* -1 means unassigned and 0 means cleared */
-  if(http->stream_id > 0) {
-    int rv = nghttp2_session_set_stream_user_data(httpc->h2,
-                                                  http->stream_id, 0);
+  if(nghttp2_session_get_stream_user_data(ctx->h2, stream->stream_id)) {
+    int rv = nghttp2_session_set_stream_user_data(ctx->h2,
+                                                  stream->stream_id, 0);
     if(rv) {
       infof(data, "http/2: failed to clear user_data for stream %u",
-            http->stream_id);
+            stream->stream_id);
       DEBUGASSERT(0);
     }
-    set_transfer(httpc, NULL);
-    http->stream_id = 0;
-  }
-}
-
-static int client_new(struct connectdata *conn,
-                      nghttp2_session_callbacks *callbacks)
-{
-#if NGHTTP2_VERSION_NUM < 0x013200
-  /* before 1.50.0 */
-  return nghttp2_session_client_new(&conn->proto.httpc.h2, callbacks, conn);
-#else
-  nghttp2_option *o;
-  int rc = nghttp2_option_new(&o);
-  if(rc)
-    return rc;
-  /* turn off RFC 9113 leading and trailing white spaces validation against
-     HTTP field value. */
-  nghttp2_option_set_no_rfc9113_leading_and_trailing_ws_validation(o, 1);
-  rc = nghttp2_session_client_new2(&conn->proto.httpc.h2, callbacks, conn,
-                                   o);
-  nghttp2_option_del(o);
-  return rc;
-#endif
-}
-
-/*
- * Initialize nghttp2 for a Curl connection
- */
-static CURLcode http2_init(struct Curl_easy *data, struct connectdata *conn)
-{
-  if(!conn->proto.httpc.h2) {
-    int rc;
-    nghttp2_session_callbacks *callbacks;
-
-    conn->proto.httpc.inbuf = malloc(H2_BUFSIZE);
-    if(!conn->proto.httpc.inbuf)
-      return CURLE_OUT_OF_MEMORY;
-
-    rc = nghttp2_session_callbacks_new(&callbacks);
-
-    if(rc) {
-      failf(data, "Couldn't initialize nghttp2 callbacks");
-      return CURLE_OUT_OF_MEMORY; /* most likely at least */
-    }
-
-    /* nghttp2_send_callback */
-    nghttp2_session_callbacks_set_send_callback(callbacks, send_callback);
-    /* nghttp2_on_frame_recv_callback */
-    nghttp2_session_callbacks_set_on_frame_recv_callback
-      (callbacks, on_frame_recv);
-    /* nghttp2_on_data_chunk_recv_callback */
-    nghttp2_session_callbacks_set_on_data_chunk_recv_callback
-      (callbacks, on_data_chunk_recv);
-    /* nghttp2_on_stream_close_callback */
-    nghttp2_session_callbacks_set_on_stream_close_callback
-      (callbacks, on_stream_close);
-    /* nghttp2_on_begin_headers_callback */
-    nghttp2_session_callbacks_set_on_begin_headers_callback
-      (callbacks, on_begin_headers);
-    /* nghttp2_on_header_callback */
-    nghttp2_session_callbacks_set_on_header_callback(callbacks, on_header);
-
-    nghttp2_session_callbacks_set_error_callback(callbacks, error_callback);
-
-    /* The nghttp2 session is not yet setup, do it */
-    rc = client_new(conn, callbacks);
-
-    nghttp2_session_callbacks_del(callbacks);
-
-    if(rc) {
-      failf(data, "Couldn't initialize nghttp2");
-      return CURLE_OUT_OF_MEMORY; /* most likely at least */
-    }
   }
-  return CURLE_OK;
 }
 
 /*
@@ -1357,26 +1413,18 @@ CURLcode Curl_http2_request_upgrade(struct dynbuf *req,
                                     struct Curl_easy *data)
 {
   CURLcode result;
-  ssize_t binlen;
   char *base64;
   size_t blen;
-  struct connectdata *conn = data->conn;
   struct SingleRequest *k = &data->req;
-  uint8_t *binsettings = conn->proto.httpc.binsettings;
-  struct http_conn *httpc = &conn->proto.httpc;
-
-  populate_settings(data, httpc);
+  uint8_t binsettings[H2_BINSETTINGS_LEN];
+  size_t  binlen; /* length of the binsettings data */
 
-  /* this returns number of bytes it wrote */
-  binlen = nghttp2_pack_settings_payload(binsettings, H2_BINSETTINGS_LEN,
-                                         httpc->local_settings,
-                                         httpc->local_settings_num);
+  binlen = populate_binsettings(binsettings, data);
   if(binlen <= 0) {
     failf(data, "nghttp2 unexpectedly failed on pack_settings_payload");
     Curl_dyn_free(req);
     return CURLE_FAILED_INIT;
   }
-  conn->proto.httpc.binlen = binlen;
 
   result = Curl_base64url_encode((const char *)binsettings, binlen,
                                  &base64, &blen);
@@ -1400,10 +1448,10 @@ CURLcode Curl_http2_request_upgrade(struct dynbuf *req,
 /*
  * Returns nonzero if current HTTP/2 session should be closed.
  */
-static int should_close_session(struct http_conn *httpc)
+static int should_close_session(struct cf_h2_ctx *ctx)
 {
-  return httpc->drain_total == 0 && !nghttp2_session_want_read(httpc->h2) &&
-    !nghttp2_session_want_write(httpc->h2);
+  return ctx->drain_total == 0 && !nghttp2_session_want_read(ctx->h2) &&
+    !nghttp2_session_want_write(ctx->h2);
 }
 
 /*
@@ -1412,65 +1460,65 @@ static int should_close_session(struct http_conn *httpc)
  * This function returns 0 if it succeeds, or -1 and error code will
  * be assigned to *err.
  */
-static int h2_process_pending_input(struct Curl_easy *data,
-                                    struct http_conn *httpc,
+static int h2_process_pending_input(struct Curl_cfilter *cf,
+                                    struct Curl_easy *data,
                                     CURLcode *err)
 {
+  struct cf_h2_ctx *ctx = cf->ctx;
   ssize_t nread;
-  char *inbuf;
   ssize_t rv;
 
-  nread = httpc->inbuflen - httpc->nread_inbuf;
-  inbuf = httpc->inbuf + httpc->nread_inbuf;
+  nread = ctx->inbuflen - ctx->nread_inbuf;
+  if(nread) {
+    char *inbuf = ctx->inbuf + ctx->nread_inbuf;
 
-  set_transfer(httpc, data); /* set the transfer */
-  rv = nghttp2_session_mem_recv(httpc->h2, (const uint8_t *)inbuf, nread);
-  if(rv < 0) {
-    failf(data,
-          "h2_process_pending_input: nghttp2_session_mem_recv() returned "
-          "%zd:%s", rv, nghttp2_strerror((int)rv));
-    *err = CURLE_RECV_ERROR;
-    return -1;
-  }
+    rv = nghttp2_session_mem_recv(ctx->h2, (const uint8_t *)inbuf, nread);
+    if(rv < 0) {
+      failf(data,
+            "h2_process_pending_input: nghttp2_session_mem_recv() returned "
+            "%zd:%s", rv, nghttp2_strerror((int)rv));
+      *err = CURLE_RECV_ERROR;
+      return -1;
+    }
 
-  if(nread == rv) {
-    H2BUGF(infof(data,
-                 "h2_process_pending_input: All data in connection buffer "
-                 "processed"));
-    httpc->inbuflen = 0;
-    httpc->nread_inbuf = 0;
-  }
-  else {
-    httpc->nread_inbuf += rv;
-    H2BUGF(infof(data,
-                 "h2_process_pending_input: %zu bytes left in connection "
-                 "buffer",
-                 httpc->inbuflen - httpc->nread_inbuf));
+    if(nread == rv) {
+      DEBUGF(LOG_CF(data, cf, "all data in connection buffer processed"));
+      ctx->inbuflen = 0;
+      ctx->nread_inbuf = 0;
+    }
+    else {
+      ctx->nread_inbuf += rv;
+      DEBUGF(LOG_CF(data, cf, "h2_process_pending_input: %zu bytes left "
+                    "in connection buffer",
+                   ctx->inbuflen - ctx->nread_inbuf));
+    }
   }
 
-  rv = h2_session_send(data, httpc->h2);
+  rv = h2_session_send(cf, data);
   if(rv) {
     *err = CURLE_SEND_ERROR;
     return -1;
   }
 
-  if(nghttp2_session_check_request_allowed(httpc->h2) == 0) {
+  if(nghttp2_session_check_request_allowed(ctx->h2) == 0) {
     /* No more requests are allowed in the current session, so
        the connection may not be reused. This is set when a
        GOAWAY frame has been received or when the limit of stream
        identifiers has been reached. */
-    connclose(data->conn, "http/2: No new requests allowed");
+    connclose(cf->conn, "http/2: No new requests allowed");
   }
 
-  if(should_close_session(httpc)) {
+  if(should_close_session(ctx)) {
     struct HTTP *stream = data->req.p.http;
-    H2BUGF(infof(data,
+    DEBUGF(LOG_CF(data, cf,
                  "h2_process_pending_input: nothing to do in this session"));
-    if(stream->error)
+    if(stream->reset)
+      *err = CURLE_PARTIAL_FILE;
+    else if(stream->error)
       *err = CURLE_HTTP2;
     else {
       /* not an error per se, but should still close the connection */
-      connclose(data->conn, "GOAWAY received");
+      connclose(cf->conn, "GOAWAY received");
       *err = CURLE_OK;
     }
     return -1;
@@ -1478,79 +1526,72 @@ static int h2_process_pending_input(struct Curl_easy *data,
   return 0;
 }
 
-/*
- * Called from transfer.c:done_sending when we stop uploading.
- */
-CURLcode Curl_http2_done_sending(struct Curl_easy *data,
-                                 struct connectdata *conn)
+static CURLcode http2_data_done_send(struct Curl_cfilter *cf,
+                                     struct Curl_easy *data)
 {
+  struct cf_h2_ctx *ctx = cf->ctx;
   CURLcode result = CURLE_OK;
+  struct HTTP *stream = data->req.p.http;
 
-  if((conn->handler == &Curl_handler_http2_ssl) ||
-     (conn->handler == &Curl_handler_http2)) {
-    /* make sure this is only attempted for HTTP/2 transfers */
-    struct HTTP *stream = data->req.p.http;
-    struct http_conn *httpc = &conn->proto.httpc;
-    nghttp2_session *h2 = httpc->h2;
-
-    if(stream->upload_left) {
-      /* If the stream still thinks there's data left to upload. */
+  if(!ctx || !ctx->h2)
+    goto out;
 
-      stream->upload_left = 0; /* DONE! */
+  if(stream->upload_left) {
+    /* If the stream still thinks there's data left to upload. */
+    stream->upload_left = 0; /* DONE! */
 
-      /* resume sending here to trigger the callback to get called again so
-         that it can signal EOF to nghttp2 */
-      (void)nghttp2_session_resume_data(h2, stream->stream_id);
-      (void)h2_process_pending_input(data, httpc, &result);
-    }
+    /* resume sending here to trigger the callback to get called again so
+       that it can signal EOF to nghttp2 */
+    (void)nghttp2_session_resume_data(ctx->h2, stream->stream_id);
+    (void)h2_process_pending_input(cf, data, &result);
+  }
 
-    /* If nghttp2 still has pending frames unsent */
-    if(nghttp2_session_want_write(h2)) {
-      struct SingleRequest *k = &data->req;
-      int rv;
+  /* If nghttp2 still has pending frames unsent */
+  if(nghttp2_session_want_write(ctx->h2)) {
+    struct SingleRequest *k = &data->req;
+    int rv;
 
-      H2BUGF(infof(data, "HTTP/2 still wants to send data (easy %p)", data));
+    DEBUGF(LOG_CF(data, cf, "HTTP/2 still wants to send data"));
 
-      /* and attempt to send the pending frames */
-      rv = h2_session_send(data, h2);
-      if(rv)
-        result = CURLE_SEND_ERROR;
+    /* and attempt to send the pending frames */
+    rv = h2_session_send(cf, data);
+    if(rv)
+      result = CURLE_SEND_ERROR;
 
-      if(nghttp2_session_want_write(h2)) {
-         /* re-set KEEP_SEND to make sure we are called again */
-         k->keepon |= KEEP_SEND;
-      }
+    if(nghttp2_session_want_write(ctx->h2)) {
+       /* re-set KEEP_SEND to make sure we are called again */
+       k->keepon |= KEEP_SEND;
     }
   }
+
+out:
   return result;
 }
 
-static ssize_t http2_handle_stream_close(struct connectdata *conn,
+static ssize_t http2_handle_stream_close(struct Curl_cfilter *cf,
                                          struct Curl_easy *data,
                                          struct HTTP *stream, CURLcode *err)
 {
-  struct http_conn *httpc = &conn->proto.httpc;
+  struct cf_h2_ctx *ctx = cf->ctx;
 
-  if(httpc->pause_stream_id == stream->stream_id) {
-    httpc->pause_stream_id = 0;
+  if(ctx->pause_stream_id == stream->stream_id) {
+    ctx->pause_stream_id = 0;
   }
 
-  drained_transfer(data, httpc);
+  drained_transfer(cf, data);
 
-  if(httpc->pause_stream_id == 0) {
-    if(h2_process_pending_input(data, httpc, err) != 0) {
+  if(ctx->pause_stream_id == 0) {
+    if(h2_process_pending_input(cf, data, err) != 0) {
       return -1;
     }
   }
 
-  DEBUGASSERT(data->state.drain == 0);
-
   /* Reset to FALSE to prevent infinite loop in readwrite_data function. */
   stream->closed = FALSE;
   if(stream->error == NGHTTP2_REFUSED_STREAM) {
-    H2BUGF(infof(data, "REFUSED_STREAM (%u), try again on a new connection",
-                 stream->stream_id));
-    connclose(conn, "REFUSED_STREAM"); /* don't use this anymore */
+    DEBUGF(LOG_CF(data, cf, "[h2sid=%u] REFUSED_STREAM, try again on a new "
+                  "connection", stream->stream_id));
+    connclose(cf->conn, "REFUSED_STREAM"); /* don't use this anymore */
     data->state.refused_stream = TRUE;
     *err = CURLE_RECV_ERROR; /* trigger Curl_retry_request() later */
     return -1;
@@ -1597,10 +1638,24 @@ static ssize_t http2_handle_stream_close(struct connectdata *conn,
 
   stream->close_handled = TRUE;
 
-  H2BUGF(infof(data, "http2_recv returns 0, http2_handle_stream_close"));
+  DEBUGF(LOG_CF(data, cf, "http2_recv returns 0, http2_handle_stream_close"));
   return 0;
 }
 
+static int sweight_wanted(const struct Curl_easy *data)
+{
+  /* 0 weight is not set by user and we take the nghttp2 default one */
+  return data->set.priority.weight?
+    data->set.priority.weight : NGHTTP2_DEFAULT_WEIGHT;
+}
+
+static int sweight_in_effect(const struct Curl_easy *data)
+{
+  /* 0 weight is not set by user and we take the nghttp2 default one */
+  return data->state.priority.weight?
+    data->state.priority.weight : NGHTTP2_DEFAULT_WEIGHT;
+}
+
 /*
  * h2_pri_spec() fills in the pri_spec struct, used by nghttp2 to send weight
  * and dependency to the peer. It also stores the updated values in the state
@@ -1610,14 +1665,14 @@ static ssize_t http2_handle_stream_close(struct connectdata *conn,
 static void h2_pri_spec(struct Curl_easy *data,
                         nghttp2_priority_spec *pri_spec)
 {
-  struct HTTP *depstream = (data->set.stream_depends_on?
-                            data->set.stream_depends_on->req.p.http:NULL);
+  struct Curl_data_priority *prio = &data->set.priority;
+  struct HTTP *depstream = (prio->parent?
+                            prio->parent->req.p.http:NULL);
   int32_t depstream_id = depstream? depstream->stream_id:0;
-  nghttp2_priority_spec_init(pri_spec, depstream_id, data->set.stream_weight,
-                             data->set.stream_depends_e);
-  data->state.stream_weight = data->set.stream_weight;
-  data->state.stream_depends_e = data->set.stream_depends_e;
-  data->state.stream_depends_on = data->set.stream_depends_on;
+  nghttp2_priority_spec_init(pri_spec, depstream_id,
+                             sweight_wanted(data),
+                             data->set.priority.exclusive);
+  data->state.priority = *prio;
 }
 
 /*
@@ -1625,53 +1680,61 @@ static void h2_pri_spec(struct Curl_easy *data,
  * dependency settings and if so it submits a PRIORITY frame with the updated
  * info.
  */
-static int h2_session_send(struct Curl_easy *data,
-                           nghttp2_session *h2)
+static CURLcode h2_session_send(struct Curl_cfilter *cf,
+                                struct Curl_easy *data)
 {
+  struct cf_h2_ctx *ctx = cf->ctx;
   struct HTTP *stream = data->req.p.http;
-  struct http_conn *httpc = &data->conn->proto.httpc;
-  set_transfer(httpc, data);
-  if((data->set.stream_weight != data->state.stream_weight) ||
-     (data->set.stream_depends_e != data->state.stream_depends_e) ||
-     (data->set.stream_depends_on != data->state.stream_depends_on) ) {
+  int rv = 0;
+
+  if((sweight_wanted(data) != sweight_in_effect(data)) ||
+     (data->set.priority.exclusive != data->state.priority.exclusive) ||
+     (data->set.priority.parent != data->state.priority.parent) ) {
     /* send new weight and/or dependency */
     nghttp2_priority_spec pri_spec;
-    int rv;
 
     h2_pri_spec(data, &pri_spec);
-
-    H2BUGF(infof(data, "Queuing PRIORITY on stream %u (easy %p)",
-                 stream->stream_id, data));
+    DEBUGF(LOG_CF(data, cf, "[h2sid=%u] Queuing PRIORITY",
+                  stream->stream_id));
     DEBUGASSERT(stream->stream_id != -1);
-    rv = nghttp2_submit_priority(h2, NGHTTP2_FLAG_NONE, stream->stream_id,
-                                 &pri_spec);
+    rv = nghttp2_submit_priority(ctx->h2, NGHTTP2_FLAG_NONE,
+                                 stream->stream_id, &pri_spec);
     if(rv)
-      return rv;
+      goto out;
   }
 
-  return nghttp2_session_send(h2);
+  rv = nghttp2_session_send(ctx->h2);
+out:
+  if(nghttp2_is_fatal(rv)) {
+    DEBUGF(LOG_CF(data, cf, "nghttp2_session_send error (%s)%d",
+                  nghttp2_strerror(rv), rv));
+    return CURLE_SEND_ERROR;
+  }
+  return flush_output(cf, data);
 }
 
-static ssize_t http2_recv(struct Curl_easy *data, int sockindex,
-                          char *mem, size_t len, CURLcode *err)
+static ssize_t cf_h2_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
+                          char *buf, size_t len, CURLcode *err)
 {
-  ssize_t nread;
-  struct connectdata *conn = data->conn;
-  struct http_conn *httpc = &conn->proto.httpc;
+  struct cf_h2_ctx *ctx = cf->ctx;
   struct HTTP *stream = data->req.p.http;
+  ssize_t nread = -1;
+  struct cf_call_data save;
 
-  (void)sockindex; /* we always do HTTP2 on sockindex 0 */
+  CF_DATA_SAVE(save, cf, data);
 
-  if(should_close_session(httpc)) {
-    H2BUGF(infof(data,
-                 "http2_recv: nothing to do in this session"));
-    if(conn->bits.close) {
+  if(should_close_session(ctx)) {
+    DEBUGF(LOG_CF(data, cf, "http2_recv: nothing to do in this session"));
+    if(cf->conn->bits.close) {
       /* already marked for closure, return OK and we're done */
+      drained_transfer(cf, data);
       *err = CURLE_OK;
-      return 0;
+      nread = 0;
+      goto out;
     }
     *err = CURLE_HTTP2;
-    return -1;
+    nread = -1;
+    goto out;
   }
 
   /* Nullify here because we call nghttp2_session_send() and they
@@ -1690,74 +1753,67 @@ static ssize_t http2_recv(struct Curl_easy *data, int sockindex,
     size_t left =
       Curl_dyn_len(&stream->header_recvbuf) - stream->nread_header_recvbuf;
     size_t ncopy = CURLMIN(len, left);
-    memcpy(mem, Curl_dyn_ptr(&stream->header_recvbuf) +
+    memcpy(buf, Curl_dyn_ptr(&stream->header_recvbuf) +
            stream->nread_header_recvbuf, ncopy);
     stream->nread_header_recvbuf += ncopy;
 
-    H2BUGF(infof(data, "http2_recv: Got %d bytes from header_recvbuf",
-                 (int)ncopy));
-    return ncopy;
+    DEBUGF(LOG_CF(data, cf, "recv: Got %d bytes from header_recvbuf",
+                  (int)ncopy));
+    nread = ncopy;
+    goto out;
   }
 
-  H2BUGF(infof(data, "http2_recv: easy %p (stream %u) win %u/%u",
-               data, stream->stream_id,
-               nghttp2_session_get_local_window_size(httpc->h2),
-               nghttp2_session_get_stream_local_window_size(httpc->h2,
-                                                            stream->stream_id)
+  DEBUGF(LOG_CF(data, cf, "[h2sid=%u] recv: win %u/%u",
+                stream->stream_id,
+                nghttp2_session_get_local_window_size(ctx->h2),
+                nghttp2_session_get_stream_local_window_size(ctx->h2,
+                                                             stream->stream_id)
            ));
 
-  if((data->state.drain) && stream->memlen) {
-    H2BUGF(infof(data, "http2_recv: DRAIN %zu bytes stream %u (%p => %p)",
-                 stream->memlen, stream->stream_id,
-                 stream->mem, mem));
-    if(mem != stream->mem) {
+  if(stream->memlen) {
+    DEBUGF(LOG_CF(data, cf, "[h2sid=%u] recv: DRAIN %zu bytes (%p => %p)",
+                  stream->stream_id, stream->memlen,
+                  (void *)stream->mem, (void *)buf));
+    if(buf != stream->mem) {
       /* if we didn't get the same buffer this time, we must move the data to
          the beginning */
-      memmove(mem, stream->mem, stream->memlen);
+      memmove(buf, stream->mem, stream->memlen);
       stream->len = len - stream->memlen;
-      stream->mem = mem;
+      stream->mem = buf;
     }
-    if(httpc->pause_stream_id == stream->stream_id && !stream->pausedata) {
+
+    if(ctx->pause_stream_id == stream->stream_id && !stream->pausedata) {
       /* We have paused nghttp2, but we have no pause data (see
          on_data_chunk_recv). */
-      httpc->pause_stream_id = 0;
-      if(h2_process_pending_input(data, httpc, err) != 0) {
-        return -1;
+      ctx->pause_stream_id = 0;
+      if(h2_process_pending_input(cf, data, err) != 0) {
+        nread = -1;
+        goto out;
       }
     }
   }
   else if(stream->pausedata) {
-    DEBUGASSERT(httpc->pause_stream_id == stream->stream_id);
+    DEBUGASSERT(ctx->pause_stream_id == stream->stream_id);
     nread = CURLMIN(len, stream->pauselen);
-    memcpy(mem, stream->pausedata, nread);
+    memcpy(buf, stream->pausedata, nread);
 
     stream->pausedata += nread;
     stream->pauselen -= nread;
+    drain_this(cf, data);
 
     if(stream->pauselen == 0) {
-      H2BUGF(infof(data, "Unpaused by stream %u", stream->stream_id));
-      DEBUGASSERT(httpc->pause_stream_id == stream->stream_id);
-      httpc->pause_stream_id = 0;
+      DEBUGF(LOG_CF(data, cf, "[h2sid=%u] Unpaused", stream->stream_id));
+      DEBUGASSERT(ctx->pause_stream_id == stream->stream_id);
+      ctx->pause_stream_id = 0;
 
       stream->pausedata = NULL;
       stream->pauselen = 0;
-
-      /* When NGHTTP2_ERR_PAUSE is returned from
-         data_source_read_callback, we might not process DATA frame
-         fully.  Calling nghttp2_session_mem_recv() again will
-         continue to process DATA frame, but if there is no incoming
-         frames, then we have to call it again with 0-length data.
-         Without this, on_stream_close callback will not be called,
-         and stream could be hanged. */
-      if(h2_process_pending_input(data, httpc, err) != 0) {
-        return -1;
-      }
     }
-    H2BUGF(infof(data, "http2_recv: returns unpaused %zd bytes on stream %u",
-                 nread, stream->stream_id));
-    return nread;
+    DEBUGF(LOG_CF(data, cf, "[h2sid=%u] recv: returns unpaused %zd bytes",
+                  stream->stream_id, nread));
+    goto out;
   }
-  else if(httpc->pause_stream_id) {
+  else if(ctx->pause_stream_id) {
     /* If a stream paused nghttp2_session_mem_recv previously, and has
        not processed all data, it still refers to the buffer in
        nghttp2_session.  If we call nghttp2_session_mem_recv(), we may
@@ -1766,36 +1822,56 @@ static ssize_t http2_recv(struct Curl_easy *data, int sockindex,
        socket is not read.  But it seems that usually streams are
        notified with its drain property, and socket is read again
        quickly. */
-    if(stream->closed)
+    if(stream->closed) {
       /* closed overrides paused */
-      return 0;
-    H2BUGF(infof(data, "stream %u is paused, pause id: %u",
-                 stream->stream_id, httpc->pause_stream_id));
+      drained_transfer(cf, data);
+      nread = 0;
+      goto out;
+    }
+    DEBUGF(LOG_CF(data, cf, "[h2sid=%u] is paused, pause h2sid: %u",
+                  stream->stream_id, ctx->pause_stream_id));
     *err = CURLE_AGAIN;
-    return -1;
+    nread = -1;
+    goto out;
   }
   else {
-    /* remember where to store incoming data for this stream and how big the
-       buffer is */
-    stream->mem = mem;
+    /* We have nothing buffered for `data` and no other stream paused
+     * the processing of incoming data, we can therefore read new data
+     * from the network.
+     * If DATA is coming for this stream, we want to store it ad the
+     * `buf` passed in right away - saving us a copy.
+     */
+    stream->mem = buf;
     stream->len = len;
     stream->memlen = 0;
 
-    if(httpc->inbuflen == 0) {
-      nread = ((Curl_recv *)httpc->recv_underlying)(
-        data, FIRSTSOCKET, httpc->inbuf, H2_BUFSIZE, err);
+    if(ctx->inbuflen > 0) {
+      DEBUGF(LOG_CF(data, cf, "Use data left in connection buffer, nread=%zd",
+                    ctx->inbuflen - ctx->nread_inbuf));
+      if(h2_process_pending_input(cf, data, err))
+        return -1;
+    }
 
-      if(nread == -1) {
+    while(stream->memlen == 0          /* have no data for this stream */
+          && !ctx->pause_stream_id     /* we are not paused either */
+          && ctx->inbuflen == 0) {     /* and out input buffer is empty */
+      /* Receive data from the "lower" filters */
+      nread = Curl_conn_cf_recv(cf->next, data, ctx->inbuf, H2_BUFSIZE, err);
+      if(nread < 0) {
         if(*err != CURLE_AGAIN)
           failf(data, "Failed receiving HTTP2 data");
-        else if(stream->closed)
+        else if(stream->closed) {
           /* received when the stream was already closed! */
-          return http2_handle_stream_close(conn, data, stream, err);
+          nread = http2_handle_stream_close(cf, data, stream, err);
+          goto out;
+        }
 
-        return -1;
+        /* nothing to read from the lower layers, clear drain */
+        drained_transfer(cf, data);
+        nread = -1;
+        goto out;
       }
-
-      if(nread == 0) {
+      else if(nread == 0) {
         if(!stream->closed) {
           /* This will happen when the server or proxy server is SIGKILLed
              during data transfer. We should emit an error since our data
@@ -1803,107 +1879,133 @@ static ssize_t http2_recv(struct Curl_easy *data, int sockindex,
           failf(data, "HTTP/2 stream %u was not closed cleanly before"
                 " end of the underlying stream",
                 stream->stream_id);
-          *err = CURLE_HTTP2_STREAM;
-          return -1;
+          drained_transfer(cf, data);
+          *err = CURLE_PARTIAL_FILE;
+          nread = -1;
+          goto out;
         }
 
-        H2BUGF(infof(data, "end of stream"));
+        DEBUGF(LOG_CF(data, cf, "[h2sid=%u] end of stream",
+                      stream->stream_id));
         *err = CURLE_OK;
-        return 0;
+        nread = 0;
+        goto out;
       }
 
-      H2BUGF(infof(data, "nread=%zd", nread));
-
-      httpc->inbuflen = nread;
-
-      DEBUGASSERT(httpc->nread_inbuf == 0);
-    }
-    else {
-      nread = httpc->inbuflen - httpc->nread_inbuf;
-      (void)nread;  /* silence warning, used in debug */
-      H2BUGF(infof(data, "Use data left in connection buffer, nread=%zd",
-                   nread));
+      DEBUGF(LOG_CF(data, cf, "read %zd from connection", nread));
+      ctx->inbuflen = nread;
+      DEBUGASSERT(ctx->nread_inbuf == 0);
+      if(h2_process_pending_input(cf, data, err))
+        return -1;
     }
 
-    if(h2_process_pending_input(data, httpc, err))
-      return -1;
   }
+
   if(stream->memlen) {
     ssize_t retlen = stream->memlen;
-    H2BUGF(infof(data, "http2_recv: returns %zd for stream %u",
-                 retlen, stream->stream_id));
+
+    /* TODO: all this buffer handling is very brittle */
+    stream->len += stream->memlen;
     stream->memlen = 0;
 
-    if(httpc->pause_stream_id == stream->stream_id) {
+    if(ctx->pause_stream_id == stream->stream_id) {
       /* data for this stream is returned now, but this stream caused a pause
          already so we need it called again asap */
-      H2BUGF(infof(data, "Data returned for PAUSED stream %u",
-                   stream->stream_id));
-    }
-    else if(!stream->closed) {
-      drained_transfer(data, httpc);
+      DEBUGF(LOG_CF(data, cf, "[h2sid=%u] Data returned for PAUSED stream",
+                    stream->stream_id));
+      drain_this(cf, data);
+      Curl_expire(data, 0, EXPIRE_RUN_NOW);
     }
-    else
+    else if(stream->closed) {
+      if(stream->reset || stream->error) {
+        nread = http2_handle_stream_close(cf, data, stream, err);
+        goto out;
+      }
       /* this stream is closed, trigger a another read ASAP to detect that */
+      DEBUGF(LOG_CF(data, cf, "[h2sid=%u] is closed now, run again",
+                    stream->stream_id));
+      drain_this(cf, data);
       Curl_expire(data, 0, EXPIRE_RUN_NOW);
+    }
+    else {
+      drained_transfer(cf, data);
+    }
+
+    *err = CURLE_OK;
+    nread = retlen;
+    DEBUGF(LOG_CF(data, cf, "[h2sid=%u] cf_h2_recv -> %zd",
+                  stream->stream_id, nread));
+    goto out;
+  }
+
+  if(stream->closed) {
+    nread = http2_handle_stream_close(cf, data, stream, err);
+    goto out;
+  }
 
-    return retlen;
+  if(!data->state.drain && Curl_conn_cf_data_pending(cf->next, data)) {
+    DEBUGF(LOG_CF(data, cf, "[h2sid=%u] pending data, set drain",
+                  stream->stream_id));
+    drain_this(cf, data);
   }
-  if(stream->closed)
-    return http2_handle_stream_close(conn, data, stream, err);
   *err = CURLE_AGAIN;
-  H2BUGF(infof(data, "http2_recv returns AGAIN for stream %u",
-               stream->stream_id));
-  return -1;
+  nread = -1;
+  DEBUGF(LOG_CF(data, cf, "[h2sid=%u] recv -> AGAIN",
+                stream->stream_id));
+out:
+  CF_DATA_RESTORE(cf, save);
+  return nread;
 }
 
-static ssize_t http2_send(struct Curl_easy *data, int sockindex,
-                          const void *mem, size_t len, CURLcode *err)
+static ssize_t cf_h2_send(struct Curl_cfilter *cf, struct Curl_easy *data,
+                          const void *buf, size_t len, CURLcode *err)
 {
   /*
    * Currently, we send request in this function, but this function is also
    * used to send request body. It would be nice to add dedicated function for
    * request.
    */
+  struct cf_h2_ctx *ctx = cf->ctx;
   int rv;
-  struct connectdata *conn = data->conn;
-  struct http_conn *httpc = &conn->proto.httpc;
   struct HTTP *stream = data->req.p.http;
   nghttp2_nv *nva = NULL;
   size_t nheader;
   nghttp2_data_provider data_prd;
   int32_t stream_id;
-  nghttp2_session *h2 = httpc->h2;
   nghttp2_priority_spec pri_spec;
   CURLcode result;
   struct h2h3req *hreq;
+  struct cf_call_data save;
 
-  (void)sockindex;
-
-  H2BUGF(infof(data, "http2_send len=%zu", len));
+  CF_DATA_SAVE(save, cf, data);
+  DEBUGF(LOG_CF(data, cf, "send len=%zu", len));
 
   if(stream->stream_id != -1) {
     if(stream->close_handled) {
       infof(data, "stream %u closed", stream->stream_id);
       *err = CURLE_HTTP2_STREAM;
-      return -1;
+      len = -1;
+      goto out;
     }
     else if(stream->closed) {
-      return http2_handle_stream_close(conn, data, stream, err);
+      len = http2_handle_stream_close(cf, data, stream, err);
+      goto out;
     }
     /* If stream_id != -1, we have dispatched request HEADERS, and now
        are going to send or sending request body in DATA frame */
-    stream->upload_mem = mem;
+    stream->upload_mem = buf;
     stream->upload_len = len;
-    rv = nghttp2_session_resume_data(h2, stream->stream_id);
+    rv = nghttp2_session_resume_data(ctx->h2, stream->stream_id);
     if(nghttp2_is_fatal(rv)) {
       *err = CURLE_SEND_ERROR;
-      return -1;
+      len = -1;
+      goto out;
     }
-    rv = h2_session_send(data, h2);
-    if(nghttp2_is_fatal(rv)) {
-      *err = CURLE_SEND_ERROR;
-      return -1;
+    result = h2_session_send(cf, data);
+    if(result) {
+      *err = result;
+      len = -1;
+      goto out;
     }
     len -= stream->upload_len;
 
@@ -1912,10 +2014,11 @@ static ssize_t http2_send(struct Curl_easy *data, int sockindex,
     stream->upload_mem = NULL;
     stream->upload_len = 0;
 
-    if(should_close_session(httpc)) {
-      H2BUGF(infof(data, "http2_send: nothing to do in this session"));
+    if(should_close_session(ctx)) {
+      DEBUGF(LOG_CF(data, cf, "send: nothing to do in this session"));
       *err = CURLE_HTTP2;
-      return -1;
+      len = -1;
+      goto out;
     }
 
     if(stream->upload_left) {
@@ -1923,15 +2026,15 @@ static ssize_t http2_send(struct Curl_easy *data, int sockindex,
          following API will make nghttp2_session_want_write() return
          nonzero if remote window allows it, which then libcurl checks
          socket is writable or not.  See http2_perform_getsock(). */
-      nghttp2_session_resume_data(h2, stream->stream_id);
+      nghttp2_session_resume_data(ctx->h2, stream->stream_id);
     }
 
 #ifdef DEBUG_HTTP2
     if(!len) {
       infof(data, "http2_send: easy %p (stream %u) win %u/%u",
             data, stream->stream_id,
-            nghttp2_session_get_remote_window_size(httpc->h2),
-            nghttp2_session_get_stream_remote_window_size(httpc->h2,
+            nghttp2_session_get_remote_window_size(ctx->h2),
+            nghttp2_session_get_stream_remote_window_size(ctx->h2,
                                                           stream->stream_id)
         );
 
@@ -1939,13 +2042,14 @@ static ssize_t http2_send(struct Curl_easy *data, int sockindex,
     infof(data, "http2_send returns %zu for stream %u", len,
           stream->stream_id);
 #endif
-    return len;
+    goto out;
   }
 
-  result = Curl_pseudo_headers(data, mem, len, &hreq);
+  result = Curl_pseudo_headers(data, buf, len, NULL, &hreq);
   if(result) {
     *err = result;
-    return -1;
+    len = -1;
+    goto out;
   }
   nheader = hreq->entries;
 
@@ -1953,7 +2057,8 @@ static ssize_t http2_send(struct Curl_easy *data, int sockindex,
   if(!nva) {
     Curl_pseudo_free(hreq);
     *err = CURLE_OUT_OF_MEMORY;
-    return -1;
+    len = -1;
+    goto out;
   }
   else {
     unsigned int i;
@@ -1969,8 +2074,8 @@ static ssize_t http2_send(struct Curl_easy *data, int sockindex,
 
   h2_pri_spec(data, &pri_spec);
 
-  H2BUGF(infof(data, "http2_send request allowed %d (easy handle %p)",
-               nghttp2_session_check_request_allowed(h2), (void *)data));
+  DEBUGF(LOG_CF(data, cf, "send request allowed %d (easy handle %p)",
+                nghttp2_session_check_request_allowed(ctx->h2), (void *)data));
 
   switch(data->state.httpreq) {
   case HTTPREQ_POST:
@@ -1985,42 +2090,40 @@ static ssize_t http2_send(struct Curl_easy *data, int sockindex,
 
     data_prd.read_callback = data_source_read_callback;
     data_prd.source.ptr = NULL;
-    stream_id = nghttp2_submit_request(h2, &pri_spec, nva, nheader,
+    stream_id = nghttp2_submit_request(ctx->h2, &pri_spec, nva, nheader,
                                        &data_prd, data);
     break;
   default:
-    stream_id = nghttp2_submit_request(h2, &pri_spec, nva, nheader,
+    stream_id = nghttp2_submit_request(ctx->h2, &pri_spec, nva, nheader,
                                        NULL, data);
   }
 
   Curl_safefree(nva);
 
   if(stream_id < 0) {
-    H2BUGF(infof(data,
-                 "http2_send() nghttp2_submit_request error (%s)%u",
-                 nghttp2_strerror(stream_id), stream_id));
+    DEBUGF(LOG_CF(data, cf, "send: nghttp2_submit_request error (%s)%u",
+                  nghttp2_strerror(stream_id), stream_id));
     *err = CURLE_SEND_ERROR;
-    return -1;
+    len = -1;
+    goto out;
   }
 
   infof(data, "Using Stream ID: %u (easy handle %p)",
         stream_id, (void *)data);
   stream->stream_id = stream_id;
 
-  rv = h2_session_send(data, h2);
-  if(rv) {
-    H2BUGF(infof(data,
-                 "http2_send() nghttp2_session_send error (%s)%d",
-                 nghttp2_strerror(rv), rv));
-
-    *err = CURLE_SEND_ERROR;
-    return -1;
+  result = h2_session_send(cf, data);
+  if(result) {
+    *err = result;
+    len = -1;
+    goto out;
   }
 
-  if(should_close_session(httpc)) {
-    H2BUGF(infof(data, "http2_send: nothing to do in this session"));
+  if(should_close_session(ctx)) {
+    DEBUGF(LOG_CF(data, cf, "send: nothing to do in this session"));
     *err = CURLE_HTTP2;
-    return -1;
+    len = -1;
+    goto out;
   }
 
   /* If whole HEADERS frame was sent off to the underlying socket, the nghttp2
@@ -2030,169 +2133,126 @@ static ssize_t http2_send(struct Curl_easy *data, int sockindex,
      results that no writable socket check is performed. To workaround this,
      we issue nghttp2_session_resume_data() here to bring back DATA
      transmission from deferred state. */
-  nghttp2_session_resume_data(h2, stream->stream_id);
+  nghttp2_session_resume_data(ctx->h2, stream->stream_id);
 
+out:
+  CF_DATA_RESTORE(cf, save);
   return len;
 }
 
-CURLcode Curl_http2_setup(struct Curl_easy *data,
-                          struct connectdata *conn)
+static int cf_h2_get_select_socks(struct Curl_cfilter *cf,
+                                  struct Curl_easy *data,
+                                  curl_socket_t *sock)
 {
-  CURLcode result;
-  struct http_conn *httpc = &conn->proto.httpc;
+  struct cf_h2_ctx *ctx = cf->ctx;
+  struct SingleRequest *k = &data->req;
   struct HTTP *stream = data->req.p.http;
+  int bitmap = GETSOCK_BLANK;
+  struct cf_call_data save;
 
-  DEBUGASSERT(data->state.buffer);
+  CF_DATA_SAVE(save, cf, data);
+  sock[0] = Curl_conn_cf_get_socket(cf, data);
 
-  stream->stream_id = -1;
+  if(!(k->keepon & KEEP_RECV_PAUSE))
+    /* Unless paused - in an HTTP/2 connection we can basically always get a
+       frame so we should always be ready for one */
+    bitmap |= GETSOCK_READSOCK(0);
 
-  Curl_dyn_init(&stream->header_recvbuf, DYN_H2_HEADERS);
-  Curl_dyn_init(&stream->trailer_recvbuf, DYN_H2_TRAILERS);
+  /* we're (still uploading OR the HTTP/2 layer wants to send data) AND
+     there's a window to send data in */
+  if((((k->keepon & (KEEP_SEND|KEEP_SEND_PAUSE)) == KEEP_SEND) ||
+      nghttp2_session_want_write(ctx->h2)) &&
+     (nghttp2_session_get_remote_window_size(ctx->h2) &&
+      nghttp2_session_get_stream_remote_window_size(ctx->h2,
+                                                    stream->stream_id)))
+    bitmap |= GETSOCK_WRITESOCK(0);
 
-  stream->upload_left = 0;
-  stream->upload_mem = NULL;
-  stream->upload_len = 0;
-  stream->mem = data->state.buffer;
-  stream->len = data->set.buffer_size;
+  CF_DATA_RESTORE(cf, save);
+  return bitmap;
+}
 
-  multi_connchanged(data->multi);
-  /* below this point only connection related inits are done, which only needs
-     to be done once per connection */
 
-  if((conn->handler == &Curl_handler_http2_ssl) ||
-     (conn->handler == &Curl_handler_http2))
-    return CURLE_OK; /* already done */
+static CURLcode cf_h2_connect(struct Curl_cfilter *cf,
+                              struct Curl_easy *data,
+                              bool blocking, bool *done)
+{
+  struct cf_h2_ctx *ctx = cf->ctx;
+  CURLcode result = CURLE_OK;
+  struct cf_call_data save;
 
-  if(conn->handler->flags & PROTOPT_SSL)
-    conn->handler = &Curl_handler_http2_ssl;
-  else
-    conn->handler = &Curl_handler_http2;
+  if(cf->connected) {
+    *done = TRUE;
+    return CURLE_OK;
+  }
 
-  result = http2_init(data, conn);
-  if(result) {
-    Curl_dyn_free(&stream->header_recvbuf);
-    return result;
+  /* Connect the lower filters first */
+  if(!cf->next->connected) {
+    result = Curl_conn_cf_connect(cf->next, data, blocking, done);
+    if(result || !*done)
+      return result;
   }
 
-  infof(data, "Using HTTP2, server supports multiplexing");
+  *done = FALSE;
 
-  conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
-  conn->httpversion = 20;
-  conn->bundle->multiuse = BUNDLE_MULTIPLEX;
+  CF_DATA_SAVE(save, cf, data);
+  if(!ctx->h2) {
+    result = cf_h2_ctx_init(cf, data, FALSE);
+    if(result)
+      goto out;
+  }
 
-  httpc->inbuflen = 0;
-  httpc->nread_inbuf = 0;
+  if(-1 == h2_process_pending_input(cf, data, &result)) {
+    result = CURLE_HTTP2;
+    goto out;
+  }
 
-  httpc->pause_stream_id = 0;
-  httpc->drain_total = 0;
+  *done = TRUE;
+  cf->connected = TRUE;
+  result = CURLE_OK;
 
-  return CURLE_OK;
+out:
+  CF_DATA_RESTORE(cf, save);
+  return result;
 }
 
-CURLcode Curl_http2_switched(struct Curl_easy *data,
-                             const char *mem, size_t nread)
+static void cf_h2_close(struct Curl_cfilter *cf, struct Curl_easy *data)
 {
-  CURLcode result;
-  struct connectdata *conn = data->conn;
-  struct http_conn *httpc = &conn->proto.httpc;
-  int rv;
-  struct HTTP *stream = data->req.p.http;
-
-  result = Curl_http2_setup(data, conn);
-  if(result)
-    return result;
-
-  httpc->recv_underlying = conn->recv[FIRSTSOCKET];
-  httpc->send_underlying = conn->send[FIRSTSOCKET];
-  conn->recv[FIRSTSOCKET] = http2_recv;
-  conn->send[FIRSTSOCKET] = http2_send;
-
-  if(data->req.upgr101 == UPGR101_RECEIVED) {
-    /* stream 1 is opened implicitly on upgrade */
-    stream->stream_id = 1;
-    /* queue SETTINGS frame (again) */
-    rv = nghttp2_session_upgrade2(httpc->h2, httpc->binsettings, httpc->binlen,
-                                  data->state.httpreq == HTTPREQ_HEAD, NULL);
-    if(rv) {
-      failf(data, "nghttp2_session_upgrade2() failed: %s(%d)",
-            nghttp2_strerror(rv), rv);
-      return CURLE_HTTP2;
-    }
+  struct cf_h2_ctx *ctx = cf->ctx;
 
-    rv = nghttp2_session_set_stream_user_data(httpc->h2,
-                                              stream->stream_id,
-                                              data);
-    if(rv) {
-      infof(data, "http/2: failed to set user_data for stream %u",
-            stream->stream_id);
-      DEBUGASSERT(0);
-    }
-  }
-  else {
-    populate_settings(data, httpc);
+  if(ctx) {
+    struct cf_call_data save;
 
-    /* stream ID is unknown at this point */
-    stream->stream_id = -1;
-    rv = nghttp2_submit_settings(httpc->h2, NGHTTP2_FLAG_NONE,
-                                 httpc->local_settings,
-                                 httpc->local_settings_num);
-    if(rv) {
-      failf(data, "nghttp2_submit_settings() failed: %s(%d)",
-            nghttp2_strerror(rv), rv);
-      return CURLE_HTTP2;
-    }
+    CF_DATA_SAVE(save, cf, data);
+    cf_h2_ctx_clear(ctx);
+    CF_DATA_RESTORE(cf, save);
   }
+}
 
-  rv = nghttp2_session_set_local_window_size(httpc->h2, NGHTTP2_FLAG_NONE, 0,
-                                             HTTP2_HUGE_WINDOW_SIZE);
-  if(rv) {
-    failf(data, "nghttp2_session_set_local_window_size() failed: %s(%d)",
-          nghttp2_strerror(rv), rv);
-    return CURLE_HTTP2;
-  }
+static void cf_h2_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+  struct cf_h2_ctx *ctx = cf->ctx;
 
-  /* we are going to copy mem to httpc->inbuf.  This is required since
-     mem is part of buffer pointed by stream->mem, and callbacks
-     called by nghttp2_session_mem_recv() will write stream specific
-     data into stream->mem, overwriting data already there. */
-  if(H2_BUFSIZE < nread) {
-    failf(data, "connection buffer size is too small to store data following "
-          "HTTP Upgrade response header: buflen=%d, datalen=%zu",
-          H2_BUFSIZE, nread);
-    return CURLE_HTTP2;
+  (void)data;
+  if(ctx) {
+    cf_h2_ctx_free(ctx);
+    cf->ctx = NULL;
   }
-
-  infof(data, "Copying HTTP/2 data in stream buffer to connection buffer"
-        " after upgrade: len=%zu",
-        nread);
-
-  if(nread)
-    memcpy(httpc->inbuf, mem, nread);
-
-  httpc->inbuflen = nread;
-
-  DEBUGASSERT(httpc->nread_inbuf == 0);
-
-  if(-1 == h2_process_pending_input(data, httpc, &result))
-    return CURLE_HTTP2;
-
-  return CURLE_OK;
 }
 
-CURLcode Curl_http2_stream_pause(struct Curl_easy *data, bool pause)
+static CURLcode http2_data_pause(struct Curl_cfilter *cf,
+                                 struct Curl_easy *data,
+                                 bool pause)
 {
+  struct cf_h2_ctx *ctx = cf->ctx;
+
   DEBUGASSERT(data);
-  DEBUGASSERT(data->conn);
-  /* if it isn't HTTP/2, we're done */
-  if(!(data->conn->handler->protocol & PROTO_FAMILY_HTTP) ||
-     !data->conn->proto.httpc.h2)
-    return CURLE_OK;
 #ifdef NGHTTP2_HAS_SET_LOCAL_WINDOW_SIZE
-  else {
+  if(ctx && ctx->h2) {
     struct HTTP *stream = data->req.p.http;
-    struct http_conn *httpc = &data->conn->proto.httpc;
     uint32_t window = !pause * HTTP2_HUGE_WINDOW_SIZE;
-    int rv = nghttp2_session_set_local_window_size(httpc->h2,
+    CURLcode result;
+
+    int rv = nghttp2_session_set_local_window_size(ctx->h2,
                                                    NGHTTP2_FLAG_NONE,
                                                    stream->stream_id,
                                                    window);
@@ -2203,9 +2263,9 @@ CURLcode Curl_http2_stream_pause(struct Curl_easy *data, bool pause)
     }
 
     /* make sure the window update gets sent */
-    rv = h2_session_send(data, httpc->h2);
-    if(rv)
-      return CURLE_SEND_ERROR;
+    result = h2_session_send(cf, data);
+    if(result)
+      return result;
 
     DEBUGF(infof(data, "Set HTTP/2 window size to %u for stream %u",
                  window, stream->stream_id));
@@ -2214,7 +2274,7 @@ CURLcode Curl_http2_stream_pause(struct Curl_easy *data, bool pause)
     {
       /* read out the stream local window again */
       uint32_t window2 =
-        nghttp2_session_get_stream_local_window_size(httpc->h2,
+        nghttp2_session_get_stream_local_window_size(ctx->h2,
                                                      stream->stream_id);
       DEBUGF(infof(data, "HTTP/2 window size is now %u for stream %u",
                    window2, stream->stream_id));
@@ -2225,86 +2285,323 @@ CURLcode Curl_http2_stream_pause(struct Curl_easy *data, bool pause)
   return CURLE_OK;
 }
 
-CURLcode Curl_http2_add_child(struct Curl_easy *parent,
-                              struct Curl_easy *child,
-                              bool exclusive)
+static CURLcode cf_h2_cntrl(struct Curl_cfilter *cf,
+                            struct Curl_easy *data,
+                            int event, int arg1, void *arg2)
 {
-  if(parent) {
-    struct Curl_http2_dep **tail;
-    struct Curl_http2_dep *dep = calloc(1, sizeof(struct Curl_http2_dep));
-    if(!dep)
-      return CURLE_OUT_OF_MEMORY;
-    dep->data = child;
-
-    if(parent->set.stream_dependents && exclusive) {
-      struct Curl_http2_dep *node = parent->set.stream_dependents;
-      while(node) {
-        node->data->set.stream_depends_on = child;
-        node = node->next;
-      }
+  CURLcode result = CURLE_OK;
+  struct cf_call_data save;
 
-      tail = &child->set.stream_dependents;
-      while(*tail)
-        tail = &(*tail)->next;
+  (void)arg2;
 
-      DEBUGASSERT(!*tail);
-      *tail = parent->set.stream_dependents;
-      parent->set.stream_dependents = 0;
-    }
+  CF_DATA_SAVE(save, cf, data);
+  switch(event) {
+  case CF_CTRL_DATA_SETUP: {
+    result = http2_data_setup(cf, data);
+    break;
+  }
+  case CF_CTRL_DATA_PAUSE: {
+    result = http2_data_pause(cf, data, (arg1 != 0));
+    break;
+  }
+  case CF_CTRL_DATA_DONE_SEND: {
+    result = http2_data_done_send(cf, data);
+    break;
+  }
+  case CF_CTRL_DATA_DONE: {
+    http2_data_done(cf, data, arg1 != 0);
+    break;
+  }
+  default:
+    break;
+  }
+  CF_DATA_RESTORE(cf, save);
+  return result;
+}
 
-    tail = &parent->set.stream_dependents;
-    while(*tail) {
-      (*tail)->data->set.stream_depends_e = FALSE;
-      tail = &(*tail)->next;
-    }
+static bool cf_h2_data_pending(struct Curl_cfilter *cf,
+                               const struct Curl_easy *data)
+{
+  struct cf_h2_ctx *ctx = cf->ctx;
+  if(ctx && ctx->inbuflen > 0 && ctx->nread_inbuf > ctx->inbuflen)
+    return TRUE;
+  return cf->next? cf->next->cft->has_data_pending(cf->next, data) : FALSE;
+}
 
-    DEBUGASSERT(!*tail);
-    *tail = dep;
+static bool cf_h2_is_alive(struct Curl_cfilter *cf,
+                           struct Curl_easy *data)
+{
+  struct cf_h2_ctx *ctx = cf->ctx;
+  CURLcode result;
+  struct cf_call_data save;
+
+  CF_DATA_SAVE(save, cf, data);
+  result = (ctx && ctx->h2 && !http2_connisdead(cf, data));
+  CF_DATA_RESTORE(cf, save);
+  return result;
+}
+
+static CURLcode cf_h2_keep_alive(struct Curl_cfilter *cf,
+                                 struct Curl_easy *data)
+{
+  CURLcode result;
+  struct cf_call_data save;
+
+  CF_DATA_SAVE(save, cf, data);
+  result = http2_send_ping(cf, data);
+  CF_DATA_RESTORE(cf, save);
+  return result;
+}
+
+static CURLcode cf_h2_query(struct Curl_cfilter *cf,
+                            struct Curl_easy *data,
+                            int query, int *pres1, void *pres2)
+{
+  struct cf_h2_ctx *ctx = cf->ctx;
+  struct cf_call_data save;
+  size_t effective_max;
+
+  switch(query) {
+  case CF_QUERY_MAX_CONCURRENT:
+    DEBUGASSERT(pres1);
+
+    CF_DATA_SAVE(save, cf, data);
+    if(nghttp2_session_check_request_allowed(ctx->h2) == 0) {
+      /* the limit is what we have in use right now */
+      effective_max = CONN_INUSE(cf->conn);
+    }
+    else {
+      effective_max = ctx->max_concurrent_streams;
+    }
+    *pres1 = (effective_max > INT_MAX)? INT_MAX : (int)effective_max;
+    CF_DATA_RESTORE(cf, save);
+    return CURLE_OK;
+  default:
+    break;
   }
+  return cf->next?
+    cf->next->cft->query(cf->next, data, query, pres1, pres2) :
+    CURLE_UNKNOWN_OPTION;
+}
 
-  child->set.stream_depends_on = parent;
-  child->set.stream_depends_e = exclusive;
-  return CURLE_OK;
+struct Curl_cftype Curl_cft_nghttp2 = {
+  "HTTP/2",
+  CF_TYPE_MULTIPLEX,
+  CURL_LOG_DEFAULT,
+  cf_h2_destroy,
+  cf_h2_connect,
+  cf_h2_close,
+  Curl_cf_def_get_host,
+  cf_h2_get_select_socks,
+  cf_h2_data_pending,
+  cf_h2_send,
+  cf_h2_recv,
+  cf_h2_cntrl,
+  cf_h2_is_alive,
+  cf_h2_keep_alive,
+  cf_h2_query,
+};
+
+static CURLcode http2_cfilter_add(struct Curl_cfilter **pcf,
+                                  struct Curl_easy *data,
+                                  struct connectdata *conn,
+                                  int sockindex)
+{
+  struct Curl_cfilter *cf = NULL;
+  struct cf_h2_ctx *ctx;
+  CURLcode result = CURLE_OUT_OF_MEMORY;
+
+  DEBUGASSERT(data->conn);
+  ctx = calloc(sizeof(*ctx), 1);
+  if(!ctx)
+    goto out;
+
+  result = Curl_cf_create(&cf, &Curl_cft_nghttp2, ctx);
+  if(result)
+    goto out;
+
+  Curl_conn_cf_add(data, conn, sockindex, cf);
+  result = CURLE_OK;
+
+out:
+  if(result)
+    cf_h2_ctx_free(ctx);
+  *pcf = result? NULL : cf;
+  return result;
 }
 
-void Curl_http2_remove_child(struct Curl_easy *parent, struct Curl_easy *child)
+static CURLcode http2_cfilter_insert_after(struct Curl_cfilter *cf,
+                                           struct Curl_easy *data)
 {
-  struct Curl_http2_dep *last = 0;
-  struct Curl_http2_dep *data = parent->set.stream_dependents;
-  DEBUGASSERT(child->set.stream_depends_on == parent);
+  struct Curl_cfilter *cf_h2 = NULL;
+  struct cf_h2_ctx *ctx;
+  CURLcode result = CURLE_OUT_OF_MEMORY;
 
-  while(data && data->data != child) {
-    last = data;
-    data = data->next;
+  (void)data;
+  ctx = calloc(sizeof(*ctx), 1);
+  if(!ctx)
+    goto out;
+
+  result = Curl_cf_create(&cf_h2, &Curl_cft_nghttp2, ctx);
+  if(result)
+    goto out;
+
+  Curl_conn_cf_insert_after(cf, cf_h2);
+  result = CURLE_OK;
+
+out:
+  if(result)
+    cf_h2_ctx_free(ctx);
+  return result;
+}
+
+bool Curl_cf_is_http2(struct Curl_cfilter *cf, const struct Curl_easy *data)
+{
+  (void)data;
+  for(; cf; cf = cf->next) {
+    if(cf->cft == &Curl_cft_nghttp2)
+      return TRUE;
+    if(cf->cft->flags & CF_TYPE_IP_CONNECT)
+      return FALSE;
   }
+  return FALSE;
+}
 
-  DEBUGASSERT(data);
+bool Curl_conn_is_http2(const struct Curl_easy *data,
+                        const struct connectdata *conn,
+                        int sockindex)
+{
+  return conn? Curl_cf_is_http2(conn->cfilter[sockindex], data) : FALSE;
+}
 
-  if(data) {
-    if(last) {
-      last->next = data->next;
-    }
-    else {
-      parent->set.stream_dependents = data->next;
+bool Curl_http2_may_switch(struct Curl_easy *data,
+                           struct connectdata *conn,
+                           int sockindex)
+{
+  (void)sockindex;
+  if(data->state.httpwant == CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE) {
+#ifndef CURL_DISABLE_PROXY
+    if(conn->bits.httpproxy && !conn->bits.tunnel_proxy) {
+      /* We don't support HTTP/2 proxies yet. Also it's debatable
+         whether or not this setting should apply to HTTP/2 proxies. */
+      infof(data, "Ignoring HTTP/2 prior knowledge due to proxy");
+      return FALSE;
     }
-    free(data);
+#endif
+    return TRUE;
   }
+  return FALSE;
+}
+
+CURLcode Curl_http2_switch(struct Curl_easy *data,
+                           struct connectdata *conn, int sockindex)
+{
+  struct Curl_cfilter *cf;
+  CURLcode result;
+
+  DEBUGASSERT(!Curl_conn_is_http2(data, conn, sockindex));
+  DEBUGF(infof(data, DMSGI(data, sockindex, "switching to HTTP/2")));
+
+  result = http2_cfilter_add(&cf, data, conn, sockindex);
+  if(result)
+    return result;
+
+  result = cf_h2_ctx_init(cf, data, FALSE);
+  if(result)
+    return result;
 
-  child->set.stream_depends_on = 0;
-  child->set.stream_depends_e = FALSE;
+  conn->httpversion = 20; /* we know we're on HTTP/2 now */
+  conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
+  conn->bundle->multiuse = BUNDLE_MULTIPLEX;
+  multi_connchanged(data->multi);
+
+  if(cf->next) {
+    bool done;
+    return Curl_conn_cf_connect(cf, data, FALSE, &done);
+  }
+  return CURLE_OK;
 }
 
-void Curl_http2_cleanup_dependencies(struct Curl_easy *data)
+CURLcode Curl_http2_switch_at(struct Curl_cfilter *cf, struct Curl_easy *data)
 {
-  while(data->set.stream_dependents) {
-    struct Curl_easy *tmp = data->set.stream_dependents->data;
-    Curl_http2_remove_child(data, tmp);
-    if(data->set.stream_depends_on)
-      Curl_http2_add_child(data->set.stream_depends_on, tmp, FALSE);
+  struct Curl_cfilter *cf_h2;
+  CURLcode result;
+
+  DEBUGASSERT(!Curl_cf_is_http2(cf, data));
+
+  result = http2_cfilter_insert_after(cf, data);
+  if(result)
+    return result;
+
+  cf_h2 = cf->next;
+  result = cf_h2_ctx_init(cf_h2, data, FALSE);
+  if(result)
+    return result;
+
+  cf->conn->httpversion = 20; /* we know we're on HTTP/2 now */
+  cf->conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
+  cf->conn->bundle->multiuse = BUNDLE_MULTIPLEX;
+  multi_connchanged(data->multi);
+
+  if(cf_h2->next) {
+    bool done;
+    return Curl_conn_cf_connect(cf_h2, data, FALSE, &done);
   }
+  return CURLE_OK;
+}
 
-  if(data->set.stream_depends_on)
-    Curl_http2_remove_child(data->set.stream_depends_on, data);
+CURLcode Curl_http2_upgrade(struct Curl_easy *data,
+                            struct connectdata *conn, int sockindex,
+                            const char *mem, size_t nread)
+{
+  struct Curl_cfilter *cf;
+  struct cf_h2_ctx *ctx;
+  CURLcode result;
+
+  DEBUGASSERT(!Curl_conn_is_http2(data, conn, sockindex));
+  DEBUGF(infof(data, DMSGI(data, sockindex, "upgrading to HTTP/2")));
+  DEBUGASSERT(data->req.upgr101 == UPGR101_RECEIVED);
+
+  result = http2_cfilter_add(&cf, data, conn, sockindex);
+  if(result)
+    return result;
+
+  DEBUGASSERT(cf->cft == &Curl_cft_nghttp2);
+  ctx = cf->ctx;
+
+  result = cf_h2_ctx_init(cf, data, TRUE);
+  if(result)
+    return result;
+
+  if(nread) {
+    /* we are going to copy mem to httpc->inbuf.  This is required since
+       mem is part of buffer pointed by stream->mem, and callbacks
+       called by nghttp2_session_mem_recv() will write stream specific
+       data into stream->mem, overwriting data already there. */
+    if(H2_BUFSIZE < nread) {
+      failf(data, "connection buffer size is too small to store data "
+            "following HTTP Upgrade response header: buflen=%d, datalen=%zu",
+            H2_BUFSIZE, nread);
+      return CURLE_HTTP2;
+    }
+
+    infof(data, "Copying HTTP/2 data in stream buffer to connection buffer"
+          " after upgrade: len=%zu", nread);
+    DEBUGASSERT(ctx->nread_inbuf == 0);
+    memcpy(ctx->inbuf, mem, nread);
+    ctx->inbuflen = nread;
+  }
+
+  conn->httpversion = 20; /* we know we're on HTTP/2 now */
+  conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
+  conn->bundle->multiuse = BUNDLE_MULTIPLEX;
+  multi_connchanged(data->multi);
+
+  if(cf->next) {
+    bool done;
+    return Curl_conn_cf_connect(cf, data, FALSE, &done);
+  }
+  return CURLE_OK;
 }
 
 /* Only call this function for a transfer that already got an HTTP/2
@@ -2312,7 +2609,7 @@ void Curl_http2_cleanup_dependencies(struct Curl_easy *data)
 bool Curl_h2_http_1_1_error(struct Curl_easy *data)
 {
   struct HTTP *stream = data->req.p.http;
-  return (stream->error == NGHTTP2_HTTP_1_1_REQUIRED);
+  return (stream && stream->error == NGHTTP2_HTTP_1_1_REQUIRED);
 }
 
 #else /* !USE_NGHTTP2 */
diff --git a/lib/http2.h b/lib/http2.h
index 966bf75..f78fbf0 100644
--- a/lib/http2.h
+++ b/lib/http2.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -40,44 +40,41 @@ void Curl_http2_ver(char *p, size_t len);
 
 const char *Curl_http2_strerror(uint32_t err);
 
-CURLcode Curl_http2_init(struct connectdata *conn);
-void Curl_http2_init_state(struct UrlState *state);
-void Curl_http2_init_userset(struct UserDefined *set);
 CURLcode Curl_http2_request_upgrade(struct dynbuf *req,
                                     struct Curl_easy *data);
-CURLcode Curl_http2_setup(struct Curl_easy *data, struct connectdata *conn);
-CURLcode Curl_http2_switched(struct Curl_easy *data,
-                             const char *ptr, size_t nread);
-/* called from http_setup_conn */
-void Curl_http2_setup_conn(struct connectdata *conn);
-void Curl_http2_setup_req(struct Curl_easy *data);
-void Curl_http2_done(struct Curl_easy *data, bool premature);
-CURLcode Curl_http2_done_sending(struct Curl_easy *data,
-                                 struct connectdata *conn);
-CURLcode Curl_http2_add_child(struct Curl_easy *parent,
-                              struct Curl_easy *child,
-                              bool exclusive);
-void Curl_http2_remove_child(struct Curl_easy *parent,
-                             struct Curl_easy *child);
-void Curl_http2_cleanup_dependencies(struct Curl_easy *data);
-CURLcode Curl_http2_stream_pause(struct Curl_easy *data, bool pause);
 
 /* returns true if the HTTP/2 stream error was HTTP_1_1_REQUIRED */
 bool Curl_h2_http_1_1_error(struct Curl_easy *data);
+
+bool Curl_conn_is_http2(const struct Curl_easy *data,
+                        const struct connectdata *conn,
+                        int sockindex);
+bool Curl_cf_is_http2(struct Curl_cfilter *cf, const struct Curl_easy *data);
+
+bool Curl_http2_may_switch(struct Curl_easy *data,
+                           struct connectdata *conn,
+                           int sockindex);
+
+CURLcode Curl_http2_switch(struct Curl_easy *data,
+                           struct connectdata *conn, int sockindex);
+
+CURLcode Curl_http2_switch_at(struct Curl_cfilter *cf, struct Curl_easy *data);
+
+CURLcode Curl_http2_upgrade(struct Curl_easy *data,
+                            struct connectdata *conn, int sockindex,
+                            const char *ptr, size_t nread);
+
+extern struct Curl_cftype Curl_cft_nghttp2;
+
 #else /* USE_NGHTTP2 */
+
+#define Curl_cf_is_http2(a,b) FALSE
+#define Curl_conn_is_http2(a,b,c) FALSE
+#define Curl_http2_may_switch(a,b,c) FALSE
+
 #define Curl_http2_request_upgrade(x,y) CURLE_UNSUPPORTED_PROTOCOL
-#define Curl_http2_setup(x,y) CURLE_UNSUPPORTED_PROTOCOL
-#define Curl_http2_switched(x,y,z) CURLE_UNSUPPORTED_PROTOCOL
-#define Curl_http2_setup_conn(x) Curl_nop_stmt
-#define Curl_http2_setup_req(x)
-#define Curl_http2_init_state(x)
-#define Curl_http2_init_userset(x)
-#define Curl_http2_done(x,y)
-#define Curl_http2_done_sending(x,y) (void)y
-#define Curl_http2_add_child(x, y, z)
-#define Curl_http2_remove_child(x, y)
-#define Curl_http2_cleanup_dependencies(x)
-#define Curl_http2_stream_pause(x, y)
+#define Curl_http2_switch(a,b,c)        CURLE_UNSUPPORTED_PROTOCOL
+#define Curl_http2_upgrade(a,b,c,d,e)   CURLE_UNSUPPORTED_PROTOCOL
 #define Curl_h2_http_1_1_error(x) 0
 #endif
 
diff --git a/lib/http_aws_sigv4.c b/lib/http_aws_sigv4.c
index 8c6d1c9..f8ce169 100644
--- a/lib/http_aws_sigv4.c
+++ b/lib/http_aws_sigv4.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -48,9 +48,9 @@
   do {                                      \
     ret = Curl_hmacit(Curl_HMAC_SHA256,     \
                       (unsigned char *)k,   \
-                      (unsigned int)kl,     \
+                      kl,                   \
                       (unsigned char *)d,   \
-                      (unsigned int)dl, o); \
+                      dl, o);               \
     if(ret) {                               \
       goto fail;                            \
     }                                       \
diff --git a/lib/http_aws_sigv4.h b/lib/http_aws_sigv4.h
index 85755e9..57cc570 100644
--- a/lib/http_aws_sigv4.h
+++ b/lib/http_aws_sigv4.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/http_chunks.c b/lib/http_chunks.c
index c344e6d..bda00d3 100644
--- a/lib/http_chunks.c
+++ b/lib/http_chunks.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/http_chunks.h b/lib/http_chunks.h
index 2cf5507..ed50713 100644
--- a/lib/http_chunks.h
+++ b/lib/http_chunks.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/http_digest.c b/lib/http_digest.c
index f015f78..8daad99 100644
--- a/lib/http_digest.c
+++ b/lib/http_digest.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/http_digest.h b/lib/http_digest.h
index eea90b7..7d5cfc1 100644
--- a/lib/http_digest.h
+++ b/lib/http_digest.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/http_negotiate.c b/lib/http_negotiate.c
index 5909f85..153e3d4 100644
--- a/lib/http_negotiate.c
+++ b/lib/http_negotiate.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/http_negotiate.h b/lib/http_negotiate.h
index 6e2096c..76d8356 100644
--- a/lib/http_negotiate.h
+++ b/lib/http_negotiate.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/http_ntlm.c b/lib/http_ntlm.c
index a19cc0d..b845ddf 100644
--- a/lib/http_ntlm.c
+++ b/lib/http_ntlm.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/http_ntlm.h b/lib/http_ntlm.h
index cec63b8..f37572b 100644
--- a/lib/http_ntlm.h
+++ b/lib/http_ntlm.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/http_proxy.c b/lib/http_proxy.c
index e30730a..fdd092d 100644
--- a/lib/http_proxy.c
+++ b/lib/http_proxy.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -26,7 +26,7 @@
 
 #include "http_proxy.h"
 
-#if !defined(CURL_DISABLE_PROXY) && !defined(CURL_DISABLE_HTTP)
+#if !defined(CURL_DISABLE_PROXY)
 
 #include <curl/curl.h>
 #ifdef USE_HYPER
@@ -49,6 +49,9 @@
 #include "curl_memory.h"
 #include "memdebug.h"
 
+
+#if !defined(CURL_DISABLE_HTTP)
+
 typedef enum {
     TUNNEL_INIT,     /* init/default/no tunnel state */
     TUNNEL_CONNECT,  /* CONNECT request is being send */
@@ -63,8 +66,7 @@ struct tunnel_state {
   int sockindex;
   const char *hostname;
   int remote_port;
-  struct HTTP http_proxy;
-  struct HTTP *prot_save;
+  struct HTTP CONNECT;
   struct dynbuf rcvbuf;
   struct dynbuf req;
   size_t nsend;
@@ -149,17 +151,6 @@ static CURLcode tunnel_init(struct tunnel_state **pts,
   Curl_dyn_init(&ts->rcvbuf, DYN_PROXY_CONNECT_HEADERS);
   Curl_dyn_init(&ts->req, DYN_HTTP_REQUEST);
 
-  /* Curl_proxyCONNECT is based on a pointer to a struct HTTP at the
-   * member conn->proto.http; we want [protocol] through HTTP and we have
-   * to change the member temporarily for connecting to the HTTP
-   * proxy. After Curl_proxyCONNECT we have to set back the member to the
-   * original pointer
-   *
-   * This function might be called several times in the multi interface case
-   * if the proxy's CONNECT response is not instant.
-   */
-  ts->prot_save = data->req.p.http;
-  data->req.p.http = &ts->http_proxy;
   *pts =  ts;
   connkeep(conn, "HTTP proxy CONNECT");
   return tunnel_reinit(ts, conn, data);
@@ -183,34 +174,39 @@ static void tunnel_go_state(struct Curl_cfilter *cf,
   /* entering this one */
   switch(new_state) {
   case TUNNEL_INIT:
+    DEBUGF(LOG_CF(data, cf, "new tunnel state 'init'"));
     tunnel_reinit(ts, cf->conn, data);
     break;
 
   case TUNNEL_CONNECT:
+    DEBUGF(LOG_CF(data, cf, "new tunnel state 'connect'"));
     ts->tunnel_state = TUNNEL_CONNECT;
     ts->keepon = KEEPON_CONNECT;
     Curl_dyn_reset(&ts->rcvbuf);
     break;
 
   case TUNNEL_RECEIVE:
+    DEBUGF(LOG_CF(data, cf, "new tunnel state 'receive'"));
     ts->tunnel_state = TUNNEL_RECEIVE;
     break;
 
   case TUNNEL_RESPONSE:
+    DEBUGF(LOG_CF(data, cf, "new tunnel state 'response'"));
     ts->tunnel_state = TUNNEL_RESPONSE;
     break;
 
   case TUNNEL_ESTABLISHED:
+    DEBUGF(LOG_CF(data, cf, "new tunnel state 'established'"));
     infof(data, "CONNECT phase completed");
     data->state.authproxy.done = TRUE;
     data->state.authproxy.multipass = FALSE;
     /* FALLTHROUGH */
   case TUNNEL_FAILED:
+    DEBUGF(LOG_CF(data, cf, "new tunnel state 'failed'"));
     ts->tunnel_state = new_state;
     Curl_dyn_reset(&ts->rcvbuf);
     Curl_dyn_reset(&ts->req);
     /* restore the protocol pointer */
-    data->req.p.http = ts->prot_save;
     data->info.httpcode = 0; /* clear it as it might've been used for the
                                 proxy */
     /* If a proxy-authorization header was used for the proxy, then we should
@@ -271,10 +267,11 @@ static CURLcode CONNECT_host(struct Curl_easy *data,
 }
 
 #ifndef USE_HYPER
-static CURLcode start_CONNECT(struct Curl_easy *data,
-                              struct connectdata *conn,
+static CURLcode start_CONNECT(struct Curl_cfilter *cf,
+                              struct Curl_easy *data,
                               struct tunnel_state *ts)
 {
+  struct connectdata *conn = cf->conn;
   char *hostheader = NULL;
   char *host = NULL;
   const char *httpv;
@@ -338,7 +335,8 @@ static CURLcode start_CONNECT(struct Curl_easy *data,
     goto out;
 
   /* Send the connect request to the proxy */
-  result = Curl_buffer_send(&ts->req, data, &data->info.request_size, 0,
+  result = Curl_buffer_send(&ts->req, data, &ts->CONNECT,
+                            &data->info.request_size, 0,
                             ts->sockindex);
   ts->headerlines = 0;
 
@@ -356,7 +354,7 @@ static CURLcode send_CONNECT(struct Curl_easy *data,
                              bool *done)
 {
   struct SingleRequest *k = &data->req;
-  struct HTTP *http = data->req.p.http;
+  struct HTTP *http = &ts->CONNECT;
   CURLcode result = CURLE_OK;
 
   if(http->sending != HTTPSEND_REQUEST)
@@ -377,7 +375,7 @@ static CURLcode send_CONNECT(struct Curl_easy *data,
     result = Curl_write(data,
                         conn->writesockfd,  /* socket to send to */
                         k->upload_fromhere, /* buffer pointer */
-                        ts->nsend,           /* buffer size */
+                        ts->nsend,          /* buffer size */
                         &bytes_written);    /* actually sent */
     if(result)
       goto out;
@@ -398,13 +396,15 @@ out:
   return result;
 }
 
-static CURLcode on_resp_header(struct Curl_easy *data,
+static CURLcode on_resp_header(struct Curl_cfilter *cf,
+                               struct Curl_easy *data,
                                struct tunnel_state *ts,
                                const char *header)
 {
   CURLcode result = CURLE_OK;
   struct SingleRequest *k = &data->req;
   int subversion = 0;
+  (void)cf;
 
   if((checkprefix("WWW-Authenticate:", header) &&
       (401 == k->httpcode)) ||
@@ -416,8 +416,7 @@ static CURLcode on_resp_header(struct Curl_easy *data,
     if(!auth)
       return CURLE_OUT_OF_MEMORY;
 
-    DEBUGF(infof(data, "CONNECT: fwd auth header '%s'",
-           header));
+    DEBUGF(LOG_CF(data, cf, "CONNECT: fwd auth header '%s'", header));
     result = Curl_http_input_auth(data, proxy, auth);
 
     free(auth);
@@ -471,14 +470,14 @@ static CURLcode on_resp_header(struct Curl_easy *data,
   return result;
 }
 
-static CURLcode recv_CONNECT_resp(struct Curl_easy *data,
-                                  struct connectdata *conn,
+static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf,
+                                  struct Curl_easy *data,
                                   struct tunnel_state *ts,
                                   bool *done)
 {
   CURLcode result = CURLE_OK;
   struct SingleRequest *k = &data->req;
-  curl_socket_t tunnelsocket = conn->sock[ts->sockindex];
+  curl_socket_t tunnelsocket = Curl_conn_cf_get_socket(cf, data);
   char *linep;
   size_t perline;
   int error;
@@ -634,7 +633,7 @@ static CURLcode recv_CONNECT_resp(struct Curl_easy *data,
           /* without content-length or chunked encoding, we
              can't keep the connection alive since the close is
              the end signal so we bail out at once instead */
-          DEBUGF(infof(data, "CONNECT: no content-length or chunked"));
+          DEBUGF(LOG_CF(data, cf, "CONNECT: no content-length or chunked"));
           ts->keepon = KEEPON_DONE;
         }
       }
@@ -647,7 +646,7 @@ static CURLcode recv_CONNECT_resp(struct Curl_easy *data,
       continue;
     }
 
-    result = on_resp_header(data, ts, linep);
+    result = on_resp_header(cf, data, ts, linep);
     if(result)
       return result;
 
@@ -667,12 +666,13 @@ static CURLcode recv_CONNECT_resp(struct Curl_easy *data,
 
 #else /* USE_HYPER */
 /* The Hyper version of CONNECT */
-static CURLcode start_CONNECT(struct Curl_easy *data,
-                              struct connectdata *conn,
+static CURLcode start_CONNECT(struct Curl_cfilter *cf,
+                              struct Curl_easy *data,
                               struct tunnel_state *ts)
 {
+  struct connectdata *conn = cf->conn;
   struct hyptransfer *h = &data->hyp;
-  curl_socket_t tunnelsocket = conn->sock[ts->sockindex];
+  curl_socket_t tunnelsocket = Curl_conn_cf_get_socket(cf, data);
   hyper_io *io = NULL;
   hyper_request *req = NULL;
   hyper_headers *headers = NULL;
@@ -914,8 +914,8 @@ error:
   return result;
 }
 
-static CURLcode recv_CONNECT_resp(struct Curl_easy *data,
-                                  struct connectdata *conn,
+static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf,
+                                  struct Curl_easy *data,
                                   struct tunnel_state *ts,
                                   bool *done)
 {
@@ -925,7 +925,7 @@ static CURLcode recv_CONNECT_resp(struct Curl_easy *data,
 
   (void)ts;
   *done = FALSE;
-  result = Curl_hyper_stream(data, conn, &didwhat, done,
+  result = Curl_hyper_stream(data, cf->conn, &didwhat, done,
                              CURL_CSELECT_IN | CURL_CSELECT_OUT);
   if(result || !*done)
     return result;
@@ -972,7 +972,8 @@ static CURLcode CONNECT(struct Curl_cfilter *cf,
     switch(ts->tunnel_state) {
     case TUNNEL_INIT:
       /* Prepare the CONNECT request and make a first attempt to send. */
-      result = start_CONNECT(data, cf->conn, ts);
+      DEBUGF(LOG_CF(data, cf, "CONNECT start"));
+      result = start_CONNECT(cf, data, ts);
       if(result)
         goto out;
       tunnel_go_state(cf, ts, TUNNEL_CONNECT, data);
@@ -980,6 +981,7 @@ static CURLcode CONNECT(struct Curl_cfilter *cf,
 
     case TUNNEL_CONNECT:
       /* see that the request is completely sent */
+      DEBUGF(LOG_CF(data, cf, "CONNECT send"));
       result = send_CONNECT(data, cf->conn, ts, &done);
       if(result || !done)
         goto out;
@@ -988,7 +990,8 @@ static CURLcode CONNECT(struct Curl_cfilter *cf,
 
     case TUNNEL_RECEIVE:
       /* read what is there */
-      result = recv_CONNECT_resp(data, cf->conn, ts, &done);
+      DEBUGF(LOG_CF(data, cf, "CONNECT receive"));
+      result = recv_CONNECT_resp(cf, data, ts, &done);
       if(Curl_pgrsUpdate(data)) {
         result = CURLE_ABORTED_BY_CALLBACK;
         goto out;
@@ -1001,24 +1004,29 @@ static CURLcode CONNECT(struct Curl_cfilter *cf,
       /* FALLTHROUGH */
 
     case TUNNEL_RESPONSE:
+      DEBUGF(LOG_CF(data, cf, "CONNECT response"));
       if(data->req.newurl) {
         /* not the "final" response, we need to do a follow up request.
          * If the other side indicated a connection close, or if someone
-         * else told us to close this connection, do so now. */
+         * else told us to close this connection, do so now.
+         */
         if(ts->close_connection || conn->bits.close) {
-          /* Close the filter chain and trigger connect, non-blocking
-           * again, so the process is ongoing. This will
-           * a) the close resets our tunnel state
-           * b) the connect makes sure that there will be a socket
-           *    to select on again.
-           * We return and expect to be called again. */
+          /* Close this filter and the sub-chain, re-connect the
+           * sub-chain and continue. Closing this filter will
+           * reset our tunnel state. To avoid recursion, we return
+           * and expect to be called again.
+           */
+          DEBUGF(LOG_CF(data, cf, "CONNECT need to close+open"));
           infof(data, "Connect me again please");
-          Curl_conn_close(data, cf->sockindex);
-          result = cf->next->cft->connect(cf->next, data, FALSE, &done);
+          Curl_conn_cf_close(cf, data);
+          connkeep(conn, "HTTP proxy CONNECT");
+          result = Curl_conn_cf_connect(cf->next, data, FALSE, &done);
           goto out;
         }
-        /* staying on this connection, reset state */
-        tunnel_go_state(cf, ts, TUNNEL_INIT, data);
+        else {
+          /* staying on this connection, reset state */
+          tunnel_go_state(cf, ts, TUNNEL_INIT, data);
+        }
       }
       break;
 
@@ -1063,10 +1071,12 @@ static CURLcode http_proxy_cf_connect(struct Curl_cfilter *cf,
     return CURLE_OK;
   }
 
+  DEBUGF(LOG_CF(data, cf, "connect"));
   result = cf->next->cft->connect(cf->next, data, blocking, done);
   if(result || !*done)
     return result;
 
+  DEBUGF(LOG_CF(data, cf, "subchain is connected"));
   /* TODO: can we do blocking? */
   /* We want "seamless" operations through HTTP proxy tunnel */
 
@@ -1117,22 +1127,21 @@ static int http_proxy_cf_get_select_socks(struct Curl_cfilter *cf,
                                           curl_socket_t *socks)
 {
   struct tunnel_state *ts = cf->ctx;
-  struct connectdata *conn = cf->conn;
   int fds;
 
-  DEBUGASSERT(conn);
   fds = cf->next->cft->get_select_socks(cf->next, data, socks);
   if(!fds && cf->next->connected && !cf->connected) {
     /* If we are not connected, but the filter "below" is
      * and not waiting on something, we are tunneling. */
-    socks[0] = conn->sock[cf->sockindex];
+    socks[0] = Curl_conn_cf_get_socket(cf, data);
     if(ts) {
       /* when we've sent a CONNECT to a proxy, we should rather either
          wait for the socket to become readable to be able to get the
          response headers or if we're still sending the request, wait
          for write. */
-      if(ts->http_proxy.sending == HTTPSEND_REQUEST)
+      if(ts->CONNECT.sending == HTTPSEND_REQUEST) {
         return GETSOCK_WRITESOCK(0);
+      }
       return GETSOCK_READSOCK(0);
     }
     return GETSOCK_WRITESOCK(0);
@@ -1140,24 +1149,18 @@ static int http_proxy_cf_get_select_socks(struct Curl_cfilter *cf,
   return fds;
 }
 
-static void http_proxy_cf_detach_data(struct Curl_cfilter *cf,
-                                      struct Curl_easy *data)
-{
-  if(cf->ctx) {
-    tunnel_free(cf, data);
-  }
-}
-
 static void http_proxy_cf_destroy(struct Curl_cfilter *cf,
                                   struct Curl_easy *data)
 {
-  http_proxy_cf_detach_data(cf, data);
+  DEBUGF(LOG_CF(data, cf, "destroy"));
+  tunnel_free(cf, data);
 }
 
 static void http_proxy_cf_close(struct Curl_cfilter *cf,
                                 struct Curl_easy *data)
 {
   DEBUGASSERT(cf->next);
+  DEBUGF(LOG_CF(data, cf, "close"));
   cf->connected = FALSE;
   cf->next->cft->close(cf->next, data);
   if(cf->ctx) {
@@ -1166,11 +1169,11 @@ static void http_proxy_cf_close(struct Curl_cfilter *cf,
 }
 
 
-static const struct Curl_cftype cft_http_proxy = {
+struct Curl_cftype Curl_cft_http_proxy = {
   "HTTP-PROXY",
   CF_TYPE_IP_CONNECT,
+  0,
   http_proxy_cf_destroy,
-  Curl_cf_def_setup,
   http_proxy_cf_connect,
   http_proxy_cf_close,
   http_proxy_cf_get_host,
@@ -1178,8 +1181,10 @@ static const struct Curl_cftype cft_http_proxy = {
   Curl_cf_def_data_pending,
   Curl_cf_def_send,
   Curl_cf_def_recv,
-  Curl_cf_def_attach_data,
-  http_proxy_cf_detach_data,
+  Curl_cf_def_cntrl,
+  Curl_cf_def_conn_is_alive,
+  Curl_cf_def_conn_keep_alive,
+  Curl_cf_def_query,
 };
 
 CURLcode Curl_conn_http_proxy_add(struct Curl_easy *data,
@@ -1189,31 +1194,73 @@ CURLcode Curl_conn_http_proxy_add(struct Curl_easy *data,
   struct Curl_cfilter *cf;
   CURLcode result;
 
-  result = Curl_cf_create(&cf, &cft_http_proxy, NULL);
+  result = Curl_cf_create(&cf, &Curl_cft_http_proxy, NULL);
   if(!result)
     Curl_conn_cf_add(data, conn, sockindex, cf);
   return result;
 }
 
+CURLcode Curl_cf_http_proxy_insert_after(struct Curl_cfilter *cf_at,
+                                         struct Curl_easy *data)
+{
+  struct Curl_cfilter *cf;
+  CURLcode result;
+
+  (void)data;
+  result = Curl_cf_create(&cf, &Curl_cft_http_proxy, NULL);
+  if(!result)
+    Curl_conn_cf_insert_after(cf_at, cf);
+  return result;
+}
+
+#endif /* ! CURL_DISABLE_HTTP */
 
-static CURLcode send_haproxy_header(struct Curl_cfilter*cf,
-                                    struct Curl_easy *data)
+
+typedef enum {
+    HAPROXY_INIT,     /* init/default/no tunnel state */
+    HAPROXY_SEND,     /* data_out being sent */
+    HAPROXY_DONE      /* all work done */
+} haproxy_state;
+
+struct cf_haproxy_ctx {
+  int state;
+  struct dynbuf data_out;
+};
+
+static void cf_haproxy_ctx_reset(struct cf_haproxy_ctx *ctx)
 {
-  struct dynbuf req;
+  DEBUGASSERT(ctx);
+  ctx->state = HAPROXY_INIT;
+  Curl_dyn_reset(&ctx->data_out);
+}
+
+static void cf_haproxy_ctx_free(struct cf_haproxy_ctx *ctx)
+{
+  if(ctx) {
+    Curl_dyn_free(&ctx->data_out);
+    free(ctx);
+  }
+}
+
+static CURLcode cf_haproxy_date_out_set(struct Curl_cfilter*cf,
+                                        struct Curl_easy *data)
+{
+  struct cf_haproxy_ctx *ctx = cf->ctx;
   CURLcode result;
   const char *tcp_version;
-  Curl_dyn_init(&req, DYN_HAXPROXY);
 
+  DEBUGASSERT(ctx);
+  DEBUGASSERT(ctx->state == HAPROXY_INIT);
 #ifdef USE_UNIX_SOCKETS
   if(cf->conn->unix_domain_socket)
     /* the buffer is large enough to hold this! */
-    result = Curl_dyn_addn(&req, STRCONST("PROXY UNKNOWN\r\n"));
+    result = Curl_dyn_addn(&ctx->data_out, STRCONST("PROXY UNKNOWN\r\n"));
   else {
 #endif /* USE_UNIX_SOCKETS */
   /* Emit the correct prefix for IPv6 */
   tcp_version = cf->conn->bits.ipv6 ? "TCP6" : "TCP4";
 
-  result = Curl_dyn_addf(&req, "PROXY %s %s %s %i %i\r\n",
+  result = Curl_dyn_addf(&ctx->data_out, "PROXY %s %s %s %i %i\r\n",
                          tcp_version,
                          data->info.conn_local_ip,
                          data->info.conn_primary_ip,
@@ -1223,19 +1270,18 @@ static CURLcode send_haproxy_header(struct Curl_cfilter*cf,
 #ifdef USE_UNIX_SOCKETS
   }
 #endif /* USE_UNIX_SOCKETS */
-
-  if(!result)
-    result = Curl_buffer_send(&req, data, &data->info.request_size,
-                              0, FIRSTSOCKET);
   return result;
 }
 
-static CURLcode haproxy_cf_connect(struct Curl_cfilter *cf,
+static CURLcode cf_haproxy_connect(struct Curl_cfilter *cf,
                                    struct Curl_easy *data,
                                    bool blocking, bool *done)
 {
+  struct cf_haproxy_ctx *ctx = cf->ctx;
   CURLcode result;
+  size_t len;
 
+  DEBUGASSERT(ctx);
   if(cf->connected) {
     *done = TRUE;
     return CURLE_OK;
@@ -1245,28 +1291,120 @@ static CURLcode haproxy_cf_connect(struct Curl_cfilter *cf,
   if(result || !*done)
     return result;
 
-  result = send_haproxy_header(cf, data);
-  *done = (!result);
+  switch(ctx->state) {
+  case HAPROXY_INIT:
+    result = cf_haproxy_date_out_set(cf, data);
+    if(result)
+      goto out;
+    ctx->state = HAPROXY_SEND;
+    /* FALLTHROUGH */
+  case HAPROXY_SEND:
+    len = Curl_dyn_len(&ctx->data_out);
+    if(len > 0) {
+      ssize_t written = Curl_conn_send(data, cf->sockindex,
+                                       Curl_dyn_ptr(&ctx->data_out),
+                                       len, &result);
+      if(written < 0)
+        goto out;
+      Curl_dyn_tail(&ctx->data_out, len - (size_t)written);
+      if(Curl_dyn_len(&ctx->data_out) > 0) {
+        result = CURLE_OK;
+        goto out;
+      }
+    }
+    ctx->state = HAPROXY_DONE;
+    /* FALLTHROUGH */
+  default:
+    Curl_dyn_free(&ctx->data_out);
+    break;
+  }
+
+out:
+  *done = (!result) && (ctx->state == HAPROXY_DONE);
   cf->connected = *done;
   return result;
 }
 
-static const struct Curl_cftype cft_haproxy = {
+static void cf_haproxy_destroy(struct Curl_cfilter *cf,
+                               struct Curl_easy *data)
+{
+  (void)data;
+  DEBUGF(LOG_CF(data, cf, "destroy"));
+  cf_haproxy_ctx_free(cf->ctx);
+}
+
+static void cf_haproxy_close(struct Curl_cfilter *cf,
+                             struct Curl_easy *data)
+{
+  DEBUGF(LOG_CF(data, cf, "close"));
+  cf->connected = FALSE;
+  cf_haproxy_ctx_reset(cf->ctx);
+  if(cf->next)
+    cf->next->cft->close(cf->next, data);
+}
+
+static int cf_haproxy_get_select_socks(struct Curl_cfilter *cf,
+                                       struct Curl_easy *data,
+                                       curl_socket_t *socks)
+{
+  int fds;
+
+  fds = cf->next->cft->get_select_socks(cf->next, data, socks);
+  if(!fds && cf->next->connected && !cf->connected) {
+    /* If we are not connected, but the filter "below" is
+     * and not waiting on something, we are sending. */
+    socks[0] = Curl_conn_cf_get_socket(cf, data);
+    return GETSOCK_WRITESOCK(0);
+  }
+  return fds;
+}
+
+
+struct Curl_cftype Curl_cft_haproxy = {
   "HAPROXY",
   0,
-  Curl_cf_def_destroy_this,
-  Curl_cf_def_setup,
-  haproxy_cf_connect,
-  Curl_cf_def_close,
+  0,
+  cf_haproxy_destroy,
+  cf_haproxy_connect,
+  cf_haproxy_close,
   Curl_cf_def_get_host,
-  Curl_cf_def_get_select_socks,
+  cf_haproxy_get_select_socks,
   Curl_cf_def_data_pending,
   Curl_cf_def_send,
   Curl_cf_def_recv,
-  Curl_cf_def_attach_data,
-  Curl_cf_def_detach_data,
+  Curl_cf_def_cntrl,
+  Curl_cf_def_conn_is_alive,
+  Curl_cf_def_conn_keep_alive,
+  Curl_cf_def_query,
 };
 
+static CURLcode cf_haproxy_create(struct Curl_cfilter **pcf,
+                                  struct Curl_easy *data)
+{
+  struct Curl_cfilter *cf = NULL;
+  struct cf_haproxy_ctx *ctx;
+  CURLcode result;
+
+  (void)data;
+  ctx = calloc(sizeof(*ctx), 1);
+  if(!ctx) {
+    result = CURLE_OUT_OF_MEMORY;
+    goto out;
+  }
+  ctx->state = HAPROXY_INIT;
+  Curl_dyn_init(&ctx->data_out, DYN_HAXPROXY);
+
+  result = Curl_cf_create(&cf, &Curl_cft_haproxy, ctx);
+  if(result)
+    goto out;
+  ctx = NULL;
+
+out:
+  cf_haproxy_ctx_free(ctx);
+  *pcf = result? NULL : cf;
+  return result;
+}
+
 CURLcode Curl_conn_haproxy_add(struct Curl_easy *data,
                                struct connectdata *conn,
                                int sockindex)
@@ -1274,10 +1412,28 @@ CURLcode Curl_conn_haproxy_add(struct Curl_easy *data,
   struct Curl_cfilter *cf;
   CURLcode result;
 
-  result = Curl_cf_create(&cf, &cft_haproxy, NULL);
-  if(!result)
-    Curl_conn_cf_add(data, conn, sockindex, cf);
+  result = cf_haproxy_create(&cf, data);
+  if(result)
+    goto out;
+  Curl_conn_cf_add(data, conn, sockindex, cf);
+
+out:
+  return result;
+}
+
+CURLcode Curl_cf_haproxy_insert_after(struct Curl_cfilter *cf_at,
+                                      struct Curl_easy *data)
+{
+  struct Curl_cfilter *cf;
+  CURLcode result;
+
+  result = cf_haproxy_create(&cf, data);
+  if(result)
+    goto out;
+  Curl_conn_cf_insert_after(cf_at, cf);
+
+out:
   return result;
 }
 
-#endif /* !CURL_DISABLE_PROXY &6 ! CURL_DISABLE_HTTP */
+#endif /* !CURL_DISABLE_PROXY */
diff --git a/lib/http_proxy.h b/lib/http_proxy.h
index dfdc0e7..f573da2 100644
--- a/lib/http_proxy.h
+++ b/lib/http_proxy.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -27,8 +27,9 @@
 #include "curl_setup.h"
 #include "urldata.h"
 
-#if !defined(CURL_DISABLE_PROXY) && !defined(CURL_DISABLE_HTTP)
+#if !defined(CURL_DISABLE_PROXY)
 
+#if !defined(CURL_DISABLE_HTTP)
 /* Default proxy timeout in milliseconds */
 #define PROXY_TIMEOUT (3600*1000)
 
@@ -36,10 +37,22 @@ CURLcode Curl_conn_http_proxy_add(struct Curl_easy *data,
                                   struct connectdata *conn,
                                   int sockindex);
 
+CURLcode Curl_cf_http_proxy_insert_after(struct Curl_cfilter *cf_at,
+                                         struct Curl_easy *data);
+
+extern struct Curl_cftype Curl_cft_http_proxy;
+
+#endif /* !CURL_DISABLE_HTTP */
+
 CURLcode Curl_conn_haproxy_add(struct Curl_easy *data,
                                struct connectdata *conn,
                                int sockindex);
 
-#endif /* !CURL_DISABLE_PROXY && !CURL_DISABLE_HTTP */
+CURLcode Curl_cf_haproxy_insert_after(struct Curl_cfilter *cf_at,
+                                      struct Curl_easy *data);
+
+extern struct Curl_cftype Curl_cft_haproxy;
+
+#endif /* !CURL_DISABLE_PROXY */
 
 #endif /* HEADER_CURL_HTTP_PROXY_H */
diff --git a/lib/idn.c b/lib/idn.c
index 6255221..abba895 100644
--- a/lib/idn.c
+++ b/lib/idn.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -116,7 +116,7 @@ bool Curl_is_ASCII_name(const char *hostname)
  * Curl_idn_decode() returns an allocated IDN decoded string if it was
  * possible. NULL on error.
  */
-static char *Curl_idn_decode(const char *input)
+static char *idn_decode(const char *input)
 {
   char *decoded = NULL;
 #ifdef USE_LIBIDN2
@@ -144,24 +144,29 @@ static char *Curl_idn_decode(const char *input)
   return decoded;
 }
 
+char *Curl_idn_decode(const char *input)
+{
+  char *d = idn_decode(input);
+#ifdef USE_LIBIDN2
+  if(d) {
+    char *c = strdup(d);
+    idn2_free(d);
+    d = c;
+  }
+#endif
+  return d;
+}
+
 /*
  * Frees data allocated by idnconvert_hostname()
  */
 void Curl_free_idnconverted_hostname(struct hostname *host)
 {
-#if defined(USE_LIBIDN2)
   if(host->encalloc) {
-    idn2_free(host->encalloc); /* must be freed with idn2_free() since this was
-                                  allocated by libidn */
+    /* must be freed with idn2_free() if allocated by libidn */
+    Curl_idn_free(host->encalloc);
     host->encalloc = NULL;
   }
-#elif defined(USE_WIN32_IDN)
-  free(host->encalloc); /* must be freed with free() since this was
-                           allocated by Curl_win32_idn_to_ascii */
-  host->encalloc = NULL;
-#else
-  (void)host;
-#endif
 }
 
 #endif /* USE_IDN */
@@ -177,7 +182,7 @@ CURLcode Curl_idnconvert_hostname(struct hostname *host)
 #ifdef USE_IDN
   /* Check name for non-ASCII and convert hostname if we can */
   if(!Curl_is_ASCII_name(host->name)) {
-    char *decoded = Curl_idn_decode(host->name);
+    char *decoded = idn_decode(host->name);
     if(decoded) {
       /* successful */
       host->encalloc = decoded;
@@ -190,4 +195,3 @@ CURLcode Curl_idnconvert_hostname(struct hostname *host)
 #endif
   return CURLE_OK;
 }
-
diff --git a/lib/idn.h b/lib/idn.h
index 2d04efc..6c0bbb7 100644
--- a/lib/idn.h
+++ b/lib/idn.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -32,7 +32,15 @@ CURLcode Curl_idnconvert_hostname(struct hostname *host);
 #if defined(USE_LIBIDN2) || defined(USE_WIN32_IDN)
 #define USE_IDN
 void Curl_free_idnconverted_hostname(struct hostname *host);
+char *Curl_idn_decode(const char *input);
+#ifdef USE_LIBIDN2
+#define Curl_idn_free(x) idn2_free(x)
+#else
+#define Curl_idn_free(x) free(x)
+#endif
+
 #else
 #define Curl_free_idnconverted_hostname(x)
+#define Curl_idn_decode(x) NULL
 #endif
 #endif /* HEADER_CURL_IDN_H */
diff --git a/lib/if2ip.c b/lib/if2ip.c
index c291948..6bf0ce1 100644
--- a/lib/if2ip.c
+++ b/lib/if2ip.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/if2ip.h b/lib/if2ip.h
index 5d15459..1f97350 100644
--- a/lib/if2ip.h
+++ b/lib/if2ip.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2020, 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/imap.c b/lib/imap.c
index bd4c6f2..c2f675d 100644
--- a/lib/imap.c
+++ b/lib/imap.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -475,15 +475,17 @@ static CURLcode imap_perform_upgrade_tls(struct Curl_easy *data,
   /* Start the SSL connection */
   struct imap_conn *imapc = &conn->proto.imapc;
   CURLcode result;
+  bool ssldone = FALSE;
 
-  if(!Curl_conn_is_ssl(data, FIRSTSOCKET)) {
+  if(!Curl_conn_is_ssl(conn, FIRSTSOCKET)) {
     result = Curl_ssl_cfilter_add(data, conn, FIRSTSOCKET);
     if(result)
       goto out;
   }
 
-  result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &imapc->ssldone);
+  result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &ssldone);
   if(!result) {
+    imapc->ssldone = ssldone;
     if(imapc->state != IMAP_UPGRADETLS)
       state(data, IMAP_UPGRADETLS);
 
@@ -952,7 +954,7 @@ static CURLcode imap_state_capability_resp(struct Curl_easy *data,
       line += wordlen;
     }
   }
-  else if(data->set.use_ssl && !Curl_conn_is_ssl(data, FIRSTSOCKET)) {
+  else if(data->set.use_ssl && !Curl_conn_is_ssl(conn, FIRSTSOCKET)) {
     /* PREAUTH is not compatible with STARTTLS. */
     if(imapcode == IMAP_RESP_OK && imapc->tls_supported && !imapc->preauth) {
       /* Switch to TLS connection now */
@@ -1386,8 +1388,10 @@ static CURLcode imap_multi_statemach(struct Curl_easy *data, bool *done)
   struct imap_conn *imapc = &conn->proto.imapc;
 
   if((conn->handler->flags & PROTOPT_SSL) && !imapc->ssldone) {
-    result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &imapc->ssldone);
-    if(result || !imapc->ssldone)
+    bool ssldone = FALSE;
+    result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &ssldone);
+    imapc->ssldone = ssldone;
+    if(result || !ssldone)
       return result;
   }
 
@@ -1774,7 +1778,7 @@ static CURLcode imap_sendf(struct Curl_easy *data, const char *fmt, ...)
   /* Calculate the tag based on the connection ID and command ID */
   msnprintf(imapc->resptag, sizeof(imapc->resptag), "%c%03d",
             'A' + curlx_sltosi(data->conn->connection_id % 26),
-            (++imapc->cmdid)%1000);
+            ++imapc->cmdid);
 
   /* start with a blank buffer */
   Curl_dyn_reset(&imapc->dyn);
diff --git a/lib/imap.h b/lib/imap.h
index 43cc1e9..784ee97 100644
--- a/lib/imap.h
+++ b/lib/imap.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2009 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -72,19 +72,19 @@ struct IMAP {
    struct */
 struct imap_conn {
   struct pingpong pp;
-  imapstate state;            /* Always use imap.c:state() to change state! */
-  bool ssldone;               /* Is connect() over SSL done? */
-  bool preauth;               /* Is this connection PREAUTH? */
   struct SASL sasl;           /* SASL-related parameters */
-  unsigned int preftype;      /* Preferred authentication type */
-  unsigned int cmdid;         /* Last used command ID */
-  char resptag[5];            /* Response tag to wait for */
-  bool tls_supported;         /* StartTLS capability supported by server */
-  bool login_disabled;        /* LOGIN command disabled by server */
-  bool ir_supported;          /* Initial response supported by server */
+  struct dynbuf dyn;          /* for the IMAP commands */
   char *mailbox;              /* The last selected mailbox */
   char *mailbox_uidvalidity;  /* UIDVALIDITY parsed from select response */
-  struct dynbuf dyn;          /* for the IMAP commands */
+  imapstate state;            /* Always use imap.c:state() to change state! */
+  char resptag[5];            /* Response tag to wait for */
+  unsigned char preftype;     /* Preferred authentication type */
+  unsigned char cmdid;        /* Last used command ID */
+  BIT(ssldone);               /* Is connect() over SSL done? */
+  BIT(preauth);               /* Is this connection PREAUTH? */
+  BIT(tls_supported);         /* StartTLS capability supported by server */
+  BIT(login_disabled);        /* LOGIN command disabled by server */
+  BIT(ir_supported);          /* Initial response supported by server */
 };
 
 extern const struct Curl_handler Curl_handler_imap;
@@ -96,6 +96,6 @@ extern const struct Curl_handler Curl_handler_imaps;
 
 /* Authentication type values */
 #define IMAP_TYPE_NONE      0
-#define IMAP_TYPE_ANY       ~0U
+#define IMAP_TYPE_ANY       (IMAP_TYPE_CLEARTEXT|IMAP_TYPE_SASL)
 
 #endif /* HEADER_CURL_IMAP_H */
diff --git a/lib/inet_ntop.h b/lib/inet_ntop.h
index 18fbd8b..7c3ead4 100644
--- a/lib/inet_ntop.h
+++ b/lib/inet_ntop.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/inet_pton.c b/lib/inet_pton.c
index 47fb778..f2e17b8 100644
--- a/lib/inet_pton.c
+++ b/lib/inet_pton.c
@@ -1,6 +1,6 @@
 /* This is from the BIND 4.9.4 release, modified to compile by itself */
 
-/* Copyright (c) 2003 - 2022 by Internet Software Consortium.
+/* Copyright (c) Internet Software Consortium.
  *
  * Permission to use, copy, modify, and distribute this software for any
  * purpose with or without fee is hereby granted, provided that the above
diff --git a/lib/inet_pton.h b/lib/inet_pton.h
index 92ae93e..82fde7e 100644
--- a/lib/inet_pton.h
+++ b/lib/inet_pton.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/krb5.c b/lib/krb5.c
index 08a6825..3cd64e1 100644
--- a/lib/krb5.c
+++ b/lib/krb5.c
@@ -2,7 +2,7 @@
  *
  * Copyright (c) 1995, 1996, 1997, 1998, 1999 Kungliga Tekniska Högskolan
  * (Royal Institute of Technology, Stockholm, Sweden).
- * Copyright (c) 2004 - 2022 Daniel Stenberg
+ * Copyright (C) Daniel Stenberg
  * All rights reserved.
  *
  * SPDX-License-Identifier: BSD-3-Clause
@@ -46,6 +46,8 @@
 #endif
 
 #include "urldata.h"
+#include "cfilters.h"
+#include "cf-socket.h"
 #include "curl_base64.h"
 #include "ftp.h"
 #include "curl_gssapi.h"
@@ -207,8 +209,8 @@ krb5_auth(void *app_data, struct Curl_easy *data, struct connectdata *conn)
   gss_ctx_id_t *context = app_data;
   struct gss_channel_bindings_struct chan;
   size_t base64_sz = 0;
-  struct sockaddr_in **remote_addr =
-    (struct sockaddr_in **)&conn->ip_addr->ai_addr;
+  struct sockaddr_in *remote_addr =
+    (struct sockaddr_in *)(void *)&conn->remote_addr->sa_addr;
   char *stringp;
 
   if(getsockname(conn->sock[FIRSTSOCKET],
@@ -220,7 +222,7 @@ krb5_auth(void *app_data, struct Curl_easy *data, struct connectdata *conn)
   chan.initiator_address.value = &conn->local_addr.sin_addr.s_addr;
   chan.acceptor_addrtype = GSS_C_AF_INET;
   chan.acceptor_address.length = l - 4;
-  chan.acceptor_address.value = &(*remote_addr)->sin_addr.s_addr;
+  chan.acceptor_address.value = &remote_addr->sin_addr.s_addr;
   chan.application_data.length = 0;
   chan.application_data.value = NULL;
 
@@ -454,15 +456,15 @@ static int ftp_send_command(struct Curl_easy *data, const char *message, ...)
 /* Read |len| from the socket |fd| and store it in |to|. Return a CURLcode
    saying whether an error occurred or CURLE_OK if |len| was read. */
 static CURLcode
-socket_read(struct Curl_easy *data, curl_socket_t fd, void *to, size_t len)
+socket_read(struct Curl_easy *data, int sockindex, void *to, size_t len)
 {
   char *to_p = to;
   CURLcode result;
   ssize_t nread = 0;
 
   while(len > 0) {
-    result = Curl_read_plain(data, fd, to_p, len, &nread);
-    if(!result) {
+    nread = Curl_conn_recv(data, sockindex, to_p, len, &result);
+    if(nread > 0) {
       len -= nread;
       to_p += nread;
     }
@@ -480,7 +482,7 @@ socket_read(struct Curl_easy *data, curl_socket_t fd, void *to, size_t len)
    CURLcode saying whether an error occurred or CURLE_OK if |len| was
    written. */
 static CURLcode
-socket_write(struct Curl_easy *data, curl_socket_t fd, const void *to,
+socket_write(struct Curl_easy *data, int sockindex, const void *to,
              size_t len)
 {
   const char *to_p = to;
@@ -488,8 +490,8 @@ socket_write(struct Curl_easy *data, curl_socket_t fd, const void *to,
   ssize_t written;
 
   while(len > 0) {
-    result = Curl_write_plain(data, fd, to_p, len, &written);
-    if(!result) {
+    written = Curl_conn_send(data, sockindex, to_p, len, &result);
+    if(written > 0) {
       len -= written;
       to_p += written;
     }
@@ -502,7 +504,7 @@ socket_write(struct Curl_easy *data, curl_socket_t fd, const void *to,
   return CURLE_OK;
 }
 
-static CURLcode read_data(struct Curl_easy *data, curl_socket_t fd,
+static CURLcode read_data(struct Curl_easy *data, int sockindex,
                           struct krb5buffer *buf)
 {
   struct connectdata *conn = data->conn;
@@ -510,7 +512,7 @@ static CURLcode read_data(struct Curl_easy *data, curl_socket_t fd,
   CURLcode result;
   int nread;
 
-  result = socket_read(data, fd, &len, sizeof(len));
+  result = socket_read(data, sockindex, &len, sizeof(len));
   if(result)
     return result;
 
@@ -525,7 +527,7 @@ static CURLcode read_data(struct Curl_easy *data, curl_socket_t fd,
   if(!len || !buf->data)
     return CURLE_OUT_OF_MEMORY;
 
-  result = socket_read(data, fd, buf->data, len);
+  result = socket_read(data, sockindex, buf->data, len);
   if(result)
     return result;
   nread = conn->mech->decode(conn->app_data, buf->data, len,
@@ -554,13 +556,12 @@ static ssize_t sec_recv(struct Curl_easy *data, int sockindex,
   size_t bytes_read;
   size_t total_read = 0;
   struct connectdata *conn = data->conn;
-  curl_socket_t fd = conn->sock[sockindex];
 
   *err = CURLE_OK;
 
   /* Handle clear text response. */
   if(conn->sec_complete == 0 || conn->data_prot == PROT_CLEAR)
-    return Curl_recv_plain(data, sockindex, buffer, len, err);
+    return Curl_conn_recv(data, sockindex, buffer, len, err);
 
   if(conn->in_buffer.eof_flag) {
     conn->in_buffer.eof_flag = 0;
@@ -573,7 +574,7 @@ static ssize_t sec_recv(struct Curl_easy *data, int sockindex,
   buffer += bytes_read;
 
   while(len > 0) {
-    if(read_data(data, fd, &conn->in_buffer))
+    if(read_data(data, sockindex, &conn->in_buffer))
       return -1;
     if(conn->in_buffer.size == 0) {
       if(bytes_read > 0)
diff --git a/lib/ldap.c b/lib/ldap.c
index 92006d6..5e53f4c 100644
--- a/lib/ldap.c
+++ b/lib/ldap.c
@@ -5,7 +5,7 @@
  *                | (__| |_| |  _ <| |___
  *                 \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/libcurl.rc b/lib/libcurl.rc
index 23134a7..daa2d62 100644
--- a/lib/libcurl.rc
+++ b/lib/libcurl.rc
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/llist.c b/lib/llist.c
index fa2d366..5b6b033 100644
--- a/lib/llist.c
+++ b/lib/llist.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/llist.h b/lib/llist.h
index 2fcb91c..320580e 100644
--- a/lib/llist.h
+++ b/lib/llist.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/md4.c b/lib/md4.c
index c13b080..318e9da 100644
--- a/lib/md4.c
+++ b/lib/md4.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -86,11 +86,7 @@
 #include "memdebug.h"
 
 
-#if defined(USE_WOLFSSL) && !defined(WOLFSSL_NO_MD4)
-
-#elif defined(USE_OPENSSL) && !defined(OPENSSL_NO_MD4)
-
-#elif defined(USE_GNUTLS)
+#if defined(USE_GNUTLS)
 
 typedef struct md4_ctx MD4_CTX;
 
@@ -109,6 +105,10 @@ static void MD4_Final(unsigned char *result, MD4_CTX *ctx)
   md4_digest(ctx, MD4_DIGEST_SIZE, result);
 }
 
+#elif defined(USE_WOLFSSL) && !defined(WOLFSSL_NO_MD4)
+
+#elif defined(USE_OPENSSL) && !defined(OPENSSL_NO_MD4)
+
 #elif defined(AN_APPLE_OS)
 typedef CC_MD4_CTX MD4_CTX;
 
diff --git a/lib/md5.c b/lib/md5.c
index 9610e0c..f57ef39 100644
--- a/lib/md5.c
+++ b/lib/md5.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/memdebug.c b/lib/memdebug.c
index 15fb491..d6952a0 100644
--- a/lib/memdebug.c
+++ b/lib/memdebug.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/memdebug.h b/lib/memdebug.h
index 7fc90e8..c9eb5dc 100644
--- a/lib/memdebug.h
+++ b/lib/memdebug.h
@@ -8,7 +8,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/mime.c b/lib/mime.c
index e3f2821d..83846c5 100644
--- a/lib/mime.c
+++ b/lib/mime.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/mime.h b/lib/mime.h
index b9ea0f1..04adf2d 100644
--- a/lib/mime.h
+++ b/lib/mime.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/mprintf.c b/lib/mprintf.c
index 8a7c17a..5de935b 100644
--- a/lib/mprintf.c
+++ b/lib/mprintf.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1999 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/mqtt.c b/lib/mqtt.c
index 8ba826f..0b54bc0 100644
--- a/lib/mqtt.c
+++ b/lib/mqtt.c
@@ -5,8 +5,8 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2020 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
- * Copyright (C) 2019, Björn Stenberg, <bjorn@haxx.se>
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Björn Stenberg, <bjorn@haxx.se>
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/lib/mqtt.h b/lib/mqtt.h
index c400d9b..6396136 100644
--- a/lib/mqtt.h
+++ b/lib/mqtt.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2019 - 2022, Björn Stenberg, <bjorn@haxx.se>
+ * Copyright (C) Björn Stenberg, <bjorn@haxx.se>
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/lib/multi.c b/lib/multi.c
index b96ee7c..f020a0b 100644
--- a/lib/multi.c
+++ b/lib/multi.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -655,6 +655,9 @@ static CURLcode multi_done(struct Curl_easy *data,
       result = CURLE_ABORTED_BY_CALLBACK;
   }
 
+  /* Inform connection filters that this transfer is done */
+  Curl_conn_ev_data_done(data, premature);
+
   process_pending_handles(data->multi); /* connection / multiplex */
 
   CONNCACHE_LOCK(data);
@@ -709,12 +712,12 @@ static CURLcode multi_done(struct Curl_easy *data,
            conn->proxy_negotiate_state == GSS_AUTHRECV)
 #endif
      ) || conn->bits.close
-       || (premature && !(conn->handler->flags & PROTOPT_STREAM))) {
+       || (premature && !Curl_conn_is_multiplex(conn, FIRSTSOCKET))) {
     DEBUGF(infof(data, "multi_done, not re-using connection=%ld, forbid=%d"
-                 ", close=%d, premature=%d, stream=%d",
+                 ", close=%d, premature=%d, conn_multiplex=%d",
                  conn->connection_id,
                  data->set.reuse_forbid, conn->bits.close, premature,
-                 (conn->handler->flags & PROTOPT_STREAM)));
+                 Curl_conn_is_multiplex(conn, FIRSTSOCKET)));
     connclose(conn, "disconnecting");
     Curl_conncache_remove_conn(data, conn, FALSE);
     CONNCACHE_UNLOCK(data);
@@ -954,7 +957,7 @@ void Curl_detach_connection(struct Curl_easy *data)
 {
   struct connectdata *conn = data->conn;
   if(conn) {
-    Curl_conn_detach_data(conn, data);
+    Curl_conn_ev_data_detach(conn, data);
     Curl_llist_remove(&conn->easyq, &data->conn_queue, NULL);
   }
   data->conn = NULL;
@@ -973,9 +976,9 @@ void Curl_attach_connection(struct Curl_easy *data,
   data->conn = conn;
   Curl_llist_insert_next(&conn->easyq, conn->easyq.tail, data,
                          &data->conn_queue);
-  Curl_conn_attach_data(conn, data);
   if(conn->handler->attach)
     conn->handler->attach(data, conn);
+  Curl_conn_ev_data_attach(conn, data);
 }
 
 static int domore_getsock(struct Curl_easy *data,
@@ -1002,11 +1005,7 @@ static int protocol_getsock(struct Curl_easy *data,
 {
   if(conn->handler->proto_getsock)
     return conn->handler->proto_getsock(data, conn, socks);
-  /* Backup getsock logic. Since there is a live socket in use, we must wait
-     for it or it will be removed from watching when the multi_socket API is
-     used. */
-  socks[0] = conn->sock[FIRSTSOCKET];
-  return GETSOCK_READSOCK(0) | GETSOCK_WRITESOCK(0);
+  return Curl_conn_get_select_socks(data, FIRSTSOCKET, socks);
 }
 
 /* returns bitmapped flags for this handle and its sockets. The 'socks[]'
@@ -1111,6 +1110,22 @@ CURLMcode curl_multi_fdset(struct Curl_multi *multi,
   return CURLM_OK;
 }
 
+#ifdef USE_WINSOCK
+/* Reset FD_WRITE for TCP sockets. Nothing is actually sent. UDP sockets can't
+ * be reset this way because an empty datagram would be sent. #9203
+ *
+ * "On Windows the internal state of FD_WRITE as returned from
+ * WSAEnumNetworkEvents is only reset after successful send()."
+ */
+static void reset_socket_fdwrite(curl_socket_t s)
+{
+  int t;
+  int l = (int)sizeof(t);
+  if(!getsockopt(s, SOL_SOCKET, SO_TYPE, (char *)&t, &l) && t == SOCK_STREAM)
+    send(s, NULL, 0, 0);
+}
+#endif
+
 #define NUM_POLLS_ON_STACK 10
 
 static CURLMcode multi_wait(struct Curl_multi *multi,
@@ -1232,7 +1247,7 @@ static CURLMcode multi_wait(struct Curl_multi *multi,
           s = sockbunch[i];
 #ifdef USE_WINSOCK
           mask |= FD_WRITE|FD_CONNECT|FD_CLOSE;
-          send(s, NULL, 0, 0); /* reset FD_WRITE */
+          reset_socket_fdwrite(s);
 #endif
           ufds[nfds].fd = s;
           ufds[nfds].events = POLLOUT;
@@ -1266,7 +1281,7 @@ static CURLMcode multi_wait(struct Curl_multi *multi,
       mask |= FD_OOB;
     if(extra_fds[i].events & CURL_WAIT_POLLOUT) {
       mask |= FD_WRITE|FD_CONNECT|FD_CLOSE;
-      send(extra_fds[i].fd, NULL, 0, 0); /* reset FD_WRITE */
+      reset_socket_fdwrite(extra_fds[i].fd);
     }
     if(WSAEventSelect(extra_fds[i].fd, multi->wsa_event, mask) != 0) {
       if(ufds_malloc)
@@ -1862,6 +1877,15 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
     multistate(data, MSTATE_COMPLETED);
   }
 
+#ifdef DEBUGBUILD
+  if(!multi->warned) {
+    infof(data, "!!! WARNING !!!");
+    infof(data, "This is a debug build of libcurl, "
+          "do not use in production.");
+    multi->warned = true;
+  }
+#endif
+
   do {
     /* A "stream" here is a logical stream if the protocol can handle that
        (HTTP/2), or the full connection for older protocols */
@@ -3248,7 +3272,7 @@ CURLMcode curl_multi_setopt(struct Curl_multi *multi,
     multi->push_userp = va_arg(param, void *);
     break;
   case CURLMOPT_PIPELINING:
-    multi->multiplexing = va_arg(param, long) & CURLPIPE_MULTIPLEX;
+    multi->multiplexing = va_arg(param, long) & CURLPIPE_MULTIPLEX ? 1 : 0;
     break;
   case CURLMOPT_TIMERFUNCTION:
     multi->timer_cb = va_arg(param, curl_multi_timer_callback);
diff --git a/lib/multihandle.h b/lib/multihandle.h
index 5a83656..6cda65d 100644
--- a/lib/multihandle.h
+++ b/lib/multihandle.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -162,14 +162,17 @@ struct Curl_multi {
 #define IPV6_DEAD    1
 #define IPV6_WORKS   2
   unsigned char ipv6_up;       /* IPV6_* defined */
-  bool multiplexing;           /* multiplexing wanted */
-  bool recheckstate;           /* see Curl_multi_connchanged */
-  bool in_callback;            /* true while executing a callback */
+  BIT(multiplexing);           /* multiplexing wanted */
+  BIT(recheckstate);           /* see Curl_multi_connchanged */
+  BIT(in_callback);            /* true while executing a callback */
 #ifdef USE_OPENSSL
-  bool ssl_seeded;
+  BIT(ssl_seeded);
 #endif
-  bool dead; /* a callback returned error, everything needs to crash and
+  BIT(dead); /* a callback returned error, everything needs to crash and
                 burn */
+#ifdef DEBUGBUILD
+  BIT(warned);                 /* true after user warned of DEBUGBUILD */
+#endif
 };
 
 #endif /* HEADER_CURL_MULTIHANDLE_H */
diff --git a/lib/multiif.h b/lib/multiif.h
index 0cb9d4f..cae02cb 100644
--- a/lib/multiif.h
+++ b/lib/multiif.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/netrc.c b/lib/netrc.c
index 4461b84..aa1b80a 100644
--- a/lib/netrc.c
+++ b/lib/netrc.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/netrc.h b/lib/netrc.h
index 53d0056..9f2815f 100644
--- a/lib/netrc.h
+++ b/lib/netrc.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/nonblock.c b/lib/nonblock.c
index 8447b6f..f4eb656 100644
--- a/lib/nonblock.c
+++ b/lib/nonblock.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/nonblock.h b/lib/nonblock.h
index a42f443..4a1a615 100644
--- a/lib/nonblock.h
+++ b/lib/nonblock.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/noproxy.c b/lib/noproxy.c
index 9b13fe8..f1c1ed2 100644
--- a/lib/noproxy.c
+++ b/lib/noproxy.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -119,8 +119,10 @@ enum nametype {
 * Checks if the host is in the noproxy list. returns TRUE if it matches and
 * therefore the proxy should NOT be used.
 ****************************************************************/
-bool Curl_check_noproxy(const char *name, const char *no_proxy)
+bool Curl_check_noproxy(const char *name, const char *no_proxy,
+                        bool *spacesep)
 {
+  *spacesep = FALSE;
   /*
    * If we don't have a hostname at all, like for example with a FILE
    * transfer, we have nothing to interrogate the noproxy list with.
@@ -244,6 +246,15 @@ bool Curl_check_noproxy(const char *name, const char *no_proxy)
         if(match)
           return TRUE;
       } /* if(tokenlen) */
+      /* pass blanks after pattern */
+      while(ISBLANK(*p))
+        p++;
+      /* if not a comma! */
+      if(*p && (*p != ',')) {
+        *spacesep = TRUE;
+        continue;
+      }
+      /* pass any number of commas */
       while(*p == ',')
         p++;
     } /* while(*p) */
diff --git a/lib/noproxy.h b/lib/noproxy.h
index 8800a21..a3a6807 100644
--- a/lib/noproxy.h
+++ b/lib/noproxy.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -37,7 +37,8 @@ UNITTEST bool Curl_cidr6_match(const char *ipv6,
                                unsigned int bits);
 #endif
 
-bool Curl_check_noproxy(const char *name, const char *no_proxy);
+bool Curl_check_noproxy(const char *name, const char *no_proxy,
+                        bool *spacesep);
 
 #endif
 
diff --git a/lib/openldap.c b/lib/openldap.c
index ab81e57..b9feeda 100644
--- a/lib/openldap.c
+++ b/lib/openldap.c
@@ -5,8 +5,8 @@
  *                | (__| |_| |  _ <| |___
  *                 \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2011 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
- * Copyright (C) 2010, Howard Chu, <hyc@openldap.org>
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Howard Chu, <hyc@openldap.org>
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/lib/parsedate.c b/lib/parsedate.c
index 5ed8819..82ac1d8 100644
--- a/lib/parsedate.c
+++ b/lib/parsedate.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/parsedate.h b/lib/parsedate.h
index 4e43477..84c37f1 100644
--- a/lib/parsedate.h
+++ b/lib/parsedate.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/pingpong.c b/lib/pingpong.c
index 9b95580..2f4aa1c 100644
--- a/lib/pingpong.c
+++ b/lib/pingpong.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/pingpong.h b/lib/pingpong.h
index cefae07..80d3f77 100644
--- a/lib/pingpong.h
+++ b/lib/pingpong.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/pop3.c b/lib/pop3.c
index ce17f2a..36707e5 100644
--- a/lib/pop3.c
+++ b/lib/pop3.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -370,16 +370,18 @@ static CURLcode pop3_perform_upgrade_tls(struct Curl_easy *data,
   /* Start the SSL connection */
   struct pop3_conn *pop3c = &conn->proto.pop3c;
   CURLcode result;
+  bool ssldone = FALSE;
 
-  if(!Curl_conn_is_ssl(data, FIRSTSOCKET)) {
+  if(!Curl_conn_is_ssl(conn, FIRSTSOCKET)) {
     result = Curl_ssl_cfilter_add(data, conn, FIRSTSOCKET);
     if(result)
       goto out;
   }
 
-  result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &pop3c->ssldone);
+  result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &ssldone);
 
   if(!result) {
+    pop3c->ssldone = ssldone;
     if(pop3c->state != POP3_UPGRADETLS)
       state(data, POP3_UPGRADETLS);
 
@@ -769,7 +771,7 @@ static CURLcode pop3_state_capa_resp(struct Curl_easy *data, int pop3code,
     if(pop3code != '+')
       pop3c->authtypes |= POP3_TYPE_CLEARTEXT;
 
-    if(!data->set.use_ssl || Curl_conn_is_ssl(data, FIRSTSOCKET))
+    if(!data->set.use_ssl || Curl_conn_is_ssl(conn, FIRSTSOCKET))
       result = pop3_perform_authentication(data, conn);
     else if(pop3code == '+' && pop3c->tls_supported)
       /* Switch to TLS connection now */
@@ -1056,7 +1058,9 @@ static CURLcode pop3_multi_statemach(struct Curl_easy *data, bool *done)
   struct pop3_conn *pop3c = &conn->proto.pop3c;
 
   if((conn->handler->flags & PROTOPT_SSL) && !pop3c->ssldone) {
-    result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &pop3c->ssldone);
+    bool ssldone = FALSE;
+    result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &ssldone);
+    pop3c->ssldone = ssldone;
     if(result || !pop3c->ssldone)
       return result;
   }
diff --git a/lib/pop3.h b/lib/pop3.h
index bb0645f..83f0f83 100644
--- a/lib/pop3.h
+++ b/lib/pop3.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2009 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -62,16 +62,16 @@ struct POP3 {
 struct pop3_conn {
   struct pingpong pp;
   pop3state state;        /* Always use pop3.c:state() to change state! */
-  bool ssldone;           /* Is connect() over SSL done? */
-  bool tls_supported;     /* StartTLS capability supported by server */
   size_t eob;             /* Number of bytes of the EOB (End Of Body) that
                              have been received so far */
   size_t strip;           /* Number of bytes from the start to ignore as
                              non-body */
   struct SASL sasl;       /* SASL-related storage */
-  unsigned int authtypes; /* Accepted authentication types */
-  unsigned int preftype;  /* Preferred authentication type */
   char *apoptimestamp;    /* APOP timestamp from the server greeting */
+  unsigned char authtypes; /* Accepted authentication types */
+  unsigned char preftype;  /* Preferred authentication type */
+  BIT(ssldone);           /* Is connect() over SSL done? */
+  BIT(tls_supported);     /* StartTLS capability supported by server */
 };
 
 extern const struct Curl_handler Curl_handler_pop3;
@@ -84,7 +84,7 @@ extern const struct Curl_handler Curl_handler_pop3s;
 
 /* Authentication type values */
 #define POP3_TYPE_NONE      0
-#define POP3_TYPE_ANY       ~0U
+#define POP3_TYPE_ANY       (POP3_TYPE_CLEARTEXT|POP3_TYPE_APOP|POP3_TYPE_SASL)
 
 /* This is the 5-bytes End-Of-Body marker for POP3 */
 #define POP3_EOB "\x0d\x0a\x2e\x0d\x0a"
diff --git a/lib/progress.c b/lib/progress.c
index 4a1e1da..a222888 100644
--- a/lib/progress.c
+++ b/lib/progress.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -166,14 +166,11 @@ void Curl_pgrsResetTransferSizes(struct Curl_easy *data)
 
 /*
  *
- * Curl_pgrsTime(). Store the current time at the given label. This fetches a
- * fresh "now" and returns it.
- *
- * @unittest: 1399
+ * Curl_pgrsTimeWas(). Store the timestamp time at the given label.
  */
-struct curltime Curl_pgrsTime(struct Curl_easy *data, timerid timer)
+void Curl_pgrsTimeWas(struct Curl_easy *data, timerid timer,
+                      struct curltime timestamp)
 {
-  struct curltime now = Curl_now();
   timediff_t *delta = NULL;
 
   switch(timer) {
@@ -183,15 +180,15 @@ struct curltime Curl_pgrsTime(struct Curl_easy *data, timerid timer)
     break;
   case TIMER_STARTOP:
     /* This is set at the start of a transfer */
-    data->progress.t_startop = now;
+    data->progress.t_startop = timestamp;
     break;
   case TIMER_STARTSINGLE:
     /* This is set at the start of each single fetch */
-    data->progress.t_startsingle = now;
+    data->progress.t_startsingle = timestamp;
     data->progress.is_t_startransfer_set = false;
     break;
   case TIMER_STARTACCEPT:
-    data->progress.t_acceptdata = now;
+    data->progress.t_acceptdata = timestamp;
     break;
   case TIMER_NAMELOOKUP:
     delta = &data->progress.t_nslookup;
@@ -214,7 +211,7 @@ struct curltime Curl_pgrsTime(struct Curl_easy *data, timerid timer)
      * changing the t_starttransfer time.
      */
     if(data->progress.is_t_startransfer_set) {
-      return now;
+      return;
     }
     else {
       data->progress.is_t_startransfer_set = true;
@@ -224,15 +221,30 @@ struct curltime Curl_pgrsTime(struct Curl_easy *data, timerid timer)
     /* this is the normal end-of-transfer thing */
     break;
   case TIMER_REDIRECT:
-    data->progress.t_redirect = Curl_timediff_us(now, data->progress.start);
+    data->progress.t_redirect = Curl_timediff_us(timestamp,
+                                                 data->progress.start);
     break;
   }
   if(delta) {
-    timediff_t us = Curl_timediff_us(now, data->progress.t_startsingle);
+    timediff_t us = Curl_timediff_us(timestamp, data->progress.t_startsingle);
     if(us < 1)
       us = 1; /* make sure at least one microsecond passed */
     *delta += us;
   }
+}
+
+/*
+ *
+ * Curl_pgrsTime(). Store the current time at the given label. This fetches a
+ * fresh "now" and returns it.
+ *
+ * @unittest: 1399
+ */
+struct curltime Curl_pgrsTime(struct Curl_easy *data, timerid timer)
+{
+  struct curltime now = Curl_now();
+
+  Curl_pgrsTimeWas(data, timer, now);
   return now;
 }
 
diff --git a/lib/progress.h b/lib/progress.h
index a129315..0049cd0 100644
--- a/lib/progress.h
+++ b/lib/progress.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -57,6 +57,13 @@ timediff_t Curl_pgrsLimitWaitTime(curl_off_t cursize,
                                   curl_off_t limit,
                                   struct curltime start,
                                   struct curltime now);
+/**
+ * Update progress timer with the elapsed time from its start to `timestamp`.
+ * This allows updating timers later and is used by happy eyeballing, where
+ * we only want to record the winner's times.
+ */
+void Curl_pgrsTimeWas(struct Curl_easy *data, timerid timer,
+                      struct curltime timestamp);
 
 #define PGRS_HIDE    (1<<4)
 #define PGRS_UL_SIZE_KNOWN (1<<5)
diff --git a/lib/psl.c b/lib/psl.c
index 60c98a4..626a203 100644
--- a/lib/psl.c
+++ b/lib/psl.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/psl.h b/lib/psl.h
index 34f0a5c..23cfa92 100644
--- a/lib/psl.h
+++ b/lib/psl.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/quic.h b/lib/quic.h
deleted file mode 100644
index b357747..0000000
--- a/lib/quic.h
+++ /dev/null
@@ -1,68 +0,0 @@
-#ifndef HEADER_CURL_QUIC_H
-#define HEADER_CURL_QUIC_H
-/***************************************************************************
- *                                  _   _ ____  _
- *  Project                     ___| | | |  _ \| |
- *                             / __| | | | |_) | |
- *                            | (__| |_| |  _ <| |___
- *                             \___|\___/|_| \_\_____|
- *
- * Copyright (C) 1998 - 2022, 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
-#ifdef USE_NGTCP2
-#include "vquic/ngtcp2.h"
-#endif
-#ifdef USE_QUICHE
-#include "vquic/quiche.h"
-#endif
-#ifdef USE_MSH3
-#include "vquic/msh3.h"
-#endif
-
-#include "urldata.h"
-
-/* functions provided by the specific backends */
-CURLcode Curl_quic_connect(struct Curl_easy *data,
-                           struct connectdata *conn,
-                           curl_socket_t sockfd,
-                           int sockindex,
-                           const struct sockaddr *addr,
-                           socklen_t addrlen);
-CURLcode Curl_quic_is_connected(struct Curl_easy *data,
-                                struct connectdata *conn,
-                                int sockindex,
-                                bool *connected);
-void Curl_quic_ver(char *p, size_t len);
-CURLcode Curl_quic_done_sending(struct Curl_easy *data);
-void Curl_quic_done(struct Curl_easy *data, bool premature);
-bool Curl_quic_data_pending(const struct Curl_easy *data);
-void Curl_quic_disconnect(struct Curl_easy *data,
-                          struct connectdata *conn, int tempindex);
-CURLcode Curl_quic_idle(struct Curl_easy *data);
-
-#else /* ENABLE_QUIC */
-#define Curl_quic_done_sending(x)
-#define Curl_quic_done(x,y)
-#define Curl_quic_data_pending(x)
-#define Curl_quic_disconnect(x,y,z)
-#endif /* !ENABLE_QUIC */
-
-#endif /* HEADER_CURL_QUIC_H */
diff --git a/lib/rand.c b/lib/rand.c
index a549624..4b6ac07 100644
--- a/lib/rand.c
+++ b/lib/rand.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/rand.h b/lib/rand.h
index 30fc296..cbe0567 100644
--- a/lib/rand.h
+++ b/lib/rand.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/rename.c b/lib/rename.c
index cfb3699..97a66e9 100644
--- a/lib/rename.c
+++ b/lib/rename.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2020 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/rename.h b/lib/rename.h
index 9958e2c..0444082 100644
--- a/lib/rename.h
+++ b/lib/rename.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2020 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/rtsp.c b/lib/rtsp.c
index 75e620d..9d27929 100644
--- a/lib/rtsp.c
+++ b/lib/rtsp.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -38,6 +38,7 @@
 #include "strcase.h"
 #include "select.h"
 #include "connect.h"
+#include "cfilters.h"
 #include "strdup.h"
 /* The last 3 #include files should be in this order */
 #include "curl_printf.h"
@@ -134,36 +135,6 @@ static CURLcode rtsp_setup_connection(struct Curl_easy *data,
 
 
 /*
- * The server may send us RTP data at any point, and RTSPREQ_RECEIVE does not
- * want to block the application forever while receiving a stream. Therefore,
- * we cannot assume that an RTSP socket is dead just because it is readable.
- *
- * Instead, if it is readable, run Curl_connalive() to peek at the socket
- * and distinguish between closed and data.
- */
-static bool rtsp_connisdead(struct Curl_easy *data, struct connectdata *check)
-{
-  int sval;
-  bool ret_val = TRUE;
-
-  sval = SOCKET_READABLE(check->sock[FIRSTSOCKET], 0);
-  if(sval == 0) {
-    /* timeout */
-    ret_val = FALSE;
-  }
-  else if(sval & CURL_CSELECT_ERR) {
-    /* socket is in an error state */
-    ret_val = TRUE;
-  }
-  else if(sval & CURL_CSELECT_IN) {
-    /* readable with no error. could still be closed */
-    ret_val = !Curl_connalive(data, check);
-  }
-
-  return ret_val;
-}
-
-/*
  * Function to check on various aspects of a connection.
  */
 static unsigned int rtsp_conncheck(struct Curl_easy *data,
@@ -174,7 +145,7 @@ static unsigned int rtsp_conncheck(struct Curl_easy *data,
   (void)data;
 
   if(checks_to_perform & CONNCHECK_ISDEAD) {
-    if(rtsp_connisdead(data, conn))
+    if(!Curl_conn_is_alive(data, conn))
       ret_val |= CONNRESULT_DEAD;
   }
 
@@ -592,7 +563,7 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done)
   }
 
   /* issue the request */
-  result = Curl_buffer_send(&req_buffer, data,
+  result = Curl_buffer_send(&req_buffer, data, data->req.p.http,
                             &data->info.request_size, 0, FIRSTSOCKET);
   if(result) {
     failf(data, "Failed sending RTSP request");
diff --git a/lib/rtsp.h b/lib/rtsp.h
index fa6606a..6e55616 100644
--- a/lib/rtsp.h
+++ b/lib/rtsp.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/select.c b/lib/select.c
index 2ac0746..3b8d468 100644
--- a/lib/select.c
+++ b/lib/select.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/select.h b/lib/select.h
index f2cf8bb..5b1ca23 100644
--- a/lib/select.h
+++ b/lib/select.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/sendf.c b/lib/sendf.c
index 6326240..2b08271 100644
--- a/lib/sendf.c
+++ b/lib/sendf.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -138,149 +138,6 @@ static size_t convert_lineends(struct Curl_easy *data,
 }
 #endif /* CURL_DO_LINEEND_CONV && !CURL_DISABLE_FTP */
 
-#ifdef USE_RECV_BEFORE_SEND_WORKAROUND
-bool Curl_recv_has_postponed_data(struct connectdata *conn, int sockindex)
-{
-  struct postponed_data * const psnd = &(conn->postponed[sockindex]);
-  return psnd->buffer && psnd->allocated_size &&
-         psnd->recv_size > psnd->recv_processed;
-}
-
-static CURLcode pre_receive_plain(struct Curl_easy *data,
-                                  struct connectdata *conn, int num)
-{
-  const curl_socket_t sockfd = conn->sock[num];
-  struct postponed_data * const psnd = &(conn->postponed[num]);
-  size_t bytestorecv = psnd->allocated_size - psnd->recv_size;
-  ssize_t recvedbytes;
-
-  /* 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((conn->handler->protocol&PROTO_FAMILY_HTTP) != 0 &&
-     conn->recv[num] == Curl_conn_recv &&
-     (!psnd->buffer || bytestorecv)) {
-    const int readymask = Curl_socket_check(sockfd, CURL_SOCKET_BAD,
-                                            CURL_SOCKET_BAD, 0);
-    if(readymask != -1 && (readymask & CURL_CSELECT_IN) != 0) {
-      /* Have some incoming data */
-      if(!psnd->buffer) {
-        /* Use buffer double default size for intermediate buffer */
-        psnd->allocated_size = 2 * data->set.buffer_size;
-        psnd->buffer = malloc(psnd->allocated_size);
-        if(!psnd->buffer)
-          return CURLE_OUT_OF_MEMORY;
-        psnd->recv_size = 0;
-        psnd->recv_processed = 0;
-#ifdef DEBUGBUILD
-        psnd->bindsock = sockfd; /* Used only for DEBUGASSERT */
-#endif /* DEBUGBUILD */
-        bytestorecv = psnd->allocated_size;
-      }
-
-      DEBUGASSERT(psnd->bindsock == sockfd);
-      recvedbytes = sread(sockfd, psnd->buffer + psnd->recv_size,
-                          bytestorecv);
-      if(recvedbytes > 0)
-        psnd->recv_size += recvedbytes;
-    }
-  }
-  return CURLE_OK;
-}
-
-static ssize_t get_pre_recved(struct connectdata *conn, int num, char *buf,
-                              size_t len)
-{
-  struct postponed_data * const psnd = &(conn->postponed[num]);
-  size_t copysize;
-  if(!psnd->buffer)
-    return 0;
-
-  DEBUGASSERT(psnd->allocated_size > 0);
-  DEBUGASSERT(psnd->recv_size <= psnd->allocated_size);
-  DEBUGASSERT(psnd->recv_processed <= psnd->recv_size);
-  /* Check and process data that already received and storied in internal
-     intermediate buffer */
-  if(psnd->recv_size > psnd->recv_processed) {
-    DEBUGASSERT(psnd->bindsock == conn->sock[num]);
-    copysize = CURLMIN(len, psnd->recv_size - psnd->recv_processed);
-    memcpy(buf, psnd->buffer + psnd->recv_processed, copysize);
-    psnd->recv_processed += copysize;
-  }
-  else
-    copysize = 0; /* buffer was allocated, but nothing was received */
-
-  /* Free intermediate buffer if it has no unprocessed data */
-  if(psnd->recv_processed == psnd->recv_size) {
-    free(psnd->buffer);
-    psnd->buffer = NULL;
-    psnd->allocated_size = 0;
-    psnd->recv_size = 0;
-    psnd->recv_processed = 0;
-#ifdef DEBUGBUILD
-    psnd->bindsock = CURL_SOCKET_BAD;
-#endif /* DEBUGBUILD */
-  }
-  return (ssize_t)copysize;
-}
-#else  /* ! USE_RECV_BEFORE_SEND_WORKAROUND */
-/* Use "do-nothing" macros instead of functions when workaround not used */
-bool Curl_recv_has_postponed_data(struct connectdata *conn, int sockindex)
-{
-  (void)conn;
-  (void)sockindex;
-  return false;
-}
-#define pre_receive_plain(d,c,n) CURLE_OK
-#define get_pre_recved(c,n,b,l) 0
-#endif /* ! USE_RECV_BEFORE_SEND_WORKAROUND */
-
-/* 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);
-  }
-}
-
-/* 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_write() is an internal write function that sends data to the
  * server. Works with plain sockets, SCP, SSL or kerberos.
@@ -301,7 +158,7 @@ CURLcode Curl_write(struct Curl_easy *data,
   DEBUGASSERT(data);
   DEBUGASSERT(data->conn);
   conn = data->conn;
-  num = (sockfd == conn->sock[SECONDARYSOCKET]);
+  num = (sockfd != CURL_SOCKET_BAD && sockfd == conn->sock[SECONDARYSOCKET]);
 
 #ifdef CURLDEBUG
   {
@@ -338,153 +195,6 @@ CURLcode Curl_write(struct Curl_easy *data,
   }
 }
 
-/* Curl_send_plain sends raw data without a size restriction on 'len'. */
-ssize_t Curl_send_plain(struct Curl_easy *data, int num,
-                        const void *mem, size_t len, CURLcode *code)
-{
-  struct connectdata *conn;
-  curl_socket_t sockfd;
-  ssize_t bytes_written;
-
-  DEBUGASSERT(data);
-  DEBUGASSERT(data->conn);
-  conn = data->conn;
-  sockfd = conn->sock[num];
-  /* 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(data, conn, num)) {
-    *code = CURLE_OUT_OF_MEMORY;
-    return -1;
-  }
-
-#if defined(MSG_FASTOPEN) && !defined(TCP_FASTOPEN_CONNECT) /* Linux */
-  if(conn->bits.tcp_fastopen) {
-    bytes_written = sendto(sockfd, mem, len, MSG_FASTOPEN,
-                           conn->ip_addr->ai_addr, conn->ip_addr->ai_addrlen);
-    conn->bits.tcp_fastopen = FALSE;
-  }
-  else
-#endif
-    bytes_written = swrite(sockfd, mem, len);
-
-  *code = CURLE_OK;
-  if(-1 == bytes_written) {
-    int err = SOCKERRNO;
-
-    if(
-#ifdef WSAEWOULDBLOCK
-      /* This is how Windows does it */
-      (WSAEWOULDBLOCK == err)
-#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 == err) || (EAGAIN == err) || (EINTR == err) ||
-      (EINPROGRESS == err)
-#endif
-      ) {
-      /* this is just a case of EWOULDBLOCK */
-      *code = CURLE_AGAIN;
-    }
-    else {
-      char buffer[STRERROR_LEN];
-      failf(data, "Send failure: %s",
-            Curl_strerror(err, buffer, sizeof(buffer)));
-      data->state.os_errno = err;
-      *code = CURLE_SEND_ERROR;
-    }
-  }
-  return bytes_written;
-}
-
-/*
- * Curl_write_plain() is an internal write function that sends data to the
- * server using plain sockets only. Otherwise meant to have the exact same
- * proto as Curl_write().
- *
- * This function wraps Curl_send_plain(). The only difference besides the
- * prototype is '*written' (bytes written) is set to 0 on error.
- * 'sockfd' must be one of the connection's two main sockets and the value of
- * 'len' must not be changed.
- */
-CURLcode Curl_write_plain(struct Curl_easy *data,
-                          curl_socket_t sockfd,
-                          const void *mem,
-                          size_t len,
-                          ssize_t *written)
-{
-  CURLcode result;
-  struct connectdata *conn = data->conn;
-  int num;
-  DEBUGASSERT(conn);
-  DEBUGASSERT(sockfd == conn->sock[FIRSTSOCKET] ||
-              sockfd == conn->sock[SECONDARYSOCKET]);
-  if(sockfd != conn->sock[FIRSTSOCKET] &&
-     sockfd != conn->sock[SECONDARYSOCKET])
-    return CURLE_BAD_FUNCTION_ARGUMENT;
-
-  num = (sockfd == conn->sock[SECONDARYSOCKET]);
-
-  *written = Curl_send_plain(data, num, mem, len, &result);
-  if(*written == -1)
-    *written = 0;
-
-  return result;
-}
-
-/* Curl_recv_plain receives raw data without a size restriction on 'len'. */
-ssize_t Curl_recv_plain(struct Curl_easy *data, int num, char *buf,
-                        size_t len, CURLcode *code)
-{
-  struct connectdata *conn;
-  curl_socket_t sockfd;
-  ssize_t nread;
-  DEBUGASSERT(data);
-  DEBUGASSERT(data->conn);
-  conn = data->conn;
-  sockfd = conn->sock[num];
-  /* Check and return data that already received and storied in internal
-     intermediate buffer */
-  nread = get_pre_recved(conn, num, buf, len);
-  if(nread > 0) {
-    *code = CURLE_OK;
-    return nread;
-  }
-
-  nread = sread(sockfd, buf, len);
-
-  *code = CURLE_OK;
-  if(-1 == nread) {
-    int err = SOCKERRNO;
-
-    if(
-#ifdef WSAEWOULDBLOCK
-      /* This is how Windows does it */
-      (WSAEWOULDBLOCK == err)
-#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 == err) || (EAGAIN == err) || (EINTR == err)
-#endif
-      ) {
-      /* this is just a case of EWOULDBLOCK */
-      *code = CURLE_AGAIN;
-    }
-    else {
-      char buffer[STRERROR_LEN];
-      failf(data, "Recv failure: %s",
-            Curl_strerror(err, buffer, sizeof(buffer)));
-      data->state.os_errno = err;
-      *code = CURLE_RECV_ERROR;
-    }
-  }
-  return nread;
-}
-
 static CURLcode pausewrite(struct Curl_easy *data,
                            int type, /* what type of data */
                            const char *ptr,
@@ -498,8 +208,7 @@ static CURLcode pausewrite(struct Curl_easy *data,
   unsigned int i;
   bool newtype = TRUE;
 
-  /* If this transfers over HTTP/2, pause the stream! */
-  Curl_http2_stream_pause(data, TRUE);
+  Curl_conn_ev_data_pause(data, TRUE);
 
   if(s->tempcount) {
     for(i = 0; i< s->tempcount; i++) {
@@ -678,41 +387,6 @@ CURLcode Curl_client_write(struct Curl_easy *data,
 }
 
 /*
- * Curl_read_plain() is an internal read function that reads data from the
- * server using plain sockets only. Otherwise meant to have the exact same
- * proto as Curl_read().
- *
- * This function wraps Curl_recv_plain(). The only difference besides the
- * prototype is '*n' (bytes read) is set to 0 on error.
- * 'sockfd' must be one of the connection's two main sockets and the value of
- * 'sizerequested' must not be changed.
- */
-CURLcode Curl_read_plain(struct Curl_easy *data,   /* transfer */
-                         curl_socket_t sockfd,     /* read from this socket */
-                         char *buf,                /* store read data here */
-                         size_t sizerequested,     /* max amount to read */
-                         ssize_t *n)               /* amount bytes read */
-{
-  CURLcode result;
-  struct connectdata *conn = data->conn;
-  int num;
-  DEBUGASSERT(conn);
-  DEBUGASSERT(sockfd == conn->sock[FIRSTSOCKET] ||
-              sockfd == conn->sock[SECONDARYSOCKET]);
-  if(sockfd != conn->sock[FIRSTSOCKET] &&
-     sockfd != conn->sock[SECONDARYSOCKET])
-    return CURLE_BAD_FUNCTION_ARGUMENT;
-
-  num = (sockfd == conn->sock[SECONDARYSOCKET]);
-
-  *n = Curl_recv_plain(data, num, buf, sizerequested, &result);
-  if(*n == -1)
-    *n = 0;
-
-  return result;
-}
-
-/*
  * Internal read-from-socket function. This is meant to deal with plain
  * sockets, SSL sockets and kerberos sockets.
  *
@@ -752,30 +426,3 @@ out:
   return result;
 }
 
-/* return 0 on success */
-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;
-      }
-    }
-  }
-}
diff --git a/lib/sendf.h b/lib/sendf.h
index 8af5c46..d0c9275 100644
--- a/lib/sendf.h
+++ b/lib/sendf.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -26,26 +26,8 @@
 
 #include "curl_setup.h"
 
-void Curl_infof(struct Curl_easy *, const char *fmt, ...);
-void Curl_failf(struct Curl_easy *, const char *fmt, ...);
+#include "curl_log.h"
 
-#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 CLIENTWRITE_BODY    (1<<0)
 #define CLIENTWRITE_HEADER  (1<<1)
@@ -58,20 +40,6 @@ void Curl_failf(struct Curl_easy *, const char *fmt, ...);
 CURLcode Curl_client_write(struct Curl_easy *data, int type, char *ptr,
                            size_t len) WARN_UNUSED_RESULT;
 
-bool Curl_recv_has_postponed_data(struct connectdata *conn, int sockindex);
-
-/* internal read-function, does plain socket only */
-CURLcode Curl_read_plain(struct Curl_easy *data,
-                         curl_socket_t sockfd,
-                         char *buf,
-                         size_t sizerequested,
-                         ssize_t *n);
-
-ssize_t Curl_recv_plain(struct Curl_easy *data, int num, char *buf,
-                        size_t len, CURLcode *code);
-ssize_t Curl_send_plain(struct Curl_easy *data, int num,
-                        const void *mem, size_t len, CURLcode *code);
-
 /* internal read-function, does plain socket, SSL and krb4 */
 CURLcode Curl_read(struct Curl_easy *data, curl_socket_t sockfd,
                    char *buf, size_t buffersize,
@@ -83,15 +51,4 @@ CURLcode Curl_write(struct Curl_easy *data,
                     const void *mem, size_t len,
                     ssize_t *written);
 
-/* internal write-function, does plain sockets ONLY */
-CURLcode Curl_write_plain(struct Curl_easy *data,
-                          curl_socket_t sockfd,
-                          const void *mem, size_t len,
-                          ssize_t *written);
-
-/* the function used to output verbose information */
-void Curl_debug(struct Curl_easy *data, curl_infotype type,
-                char *ptr, size_t size);
-
-
 #endif /* HEADER_CURL_SENDF_H */
diff --git a/lib/setopt.c b/lib/setopt.c
index b77e95b..604693a 100644
--- a/lib/setopt.c
+++ b/lib/setopt.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -174,7 +174,7 @@ static CURLcode protocol2num(const char *str, curl_prot_t *val)
 
       *val |= h->protocol;
     }
-  } while(str++);
+  } while(str && str++);
 
   if(!*val)
     /* no protocol listed */
@@ -463,8 +463,8 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
          version_max >= CURL_SSLVERSION_MAX_LAST)
         return CURLE_BAD_FUNCTION_ARGUMENT;
 
-      primary->version = version;
-      primary->version_max = version_max;
+      primary->version = (unsigned char)version;
+      primary->version_max = (unsigned int)version_max;
     }
 #else
     result = CURLE_NOT_BUILT_IN;
@@ -732,13 +732,6 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
     data->set.sep_headers = (bool)((arg & CURLHEADER_SEPARATE)? TRUE: FALSE);
     break;
 
-  case CURLOPT_HTTP200ALIASES:
-    /*
-     * Set a list of aliases for HTTP 200 in response header
-     */
-    data->set.http200aliases = va_arg(param, struct curl_slist *);
-    break;
-
 #if !defined(CURL_DISABLE_COOKIES)
   case CURLOPT_COOKIE:
     /*
@@ -760,18 +753,18 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
         return CURLE_BAD_FUNCTION_ARGUMENT;
       /* append the cookie file name to the list of file names, and deal with
          them later */
-      cl = curl_slist_append(data->state.cookielist, argptr);
+      cl = curl_slist_append(data->set.cookielist, argptr);
       if(!cl) {
-        curl_slist_free_all(data->state.cookielist);
-        data->state.cookielist = NULL;
+        curl_slist_free_all(data->set.cookielist);
+        data->set.cookielist = NULL;
         return CURLE_OUT_OF_MEMORY;
       }
-      data->state.cookielist = cl; /* store the list for later use */
+      data->set.cookielist = cl; /* store the list for later use */
     }
     else {
       /* clear the list of cookie files */
-      curl_slist_free_all(data->state.cookielist);
-      data->state.cookielist = NULL;
+      curl_slist_free_all(data->set.cookielist);
+      data->set.cookielist = NULL;
 
       if(!data->share || !data->share->cookies) {
         /* throw away all existing cookies if this isn't a shared cookie
@@ -902,22 +895,38 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
      * the listed enums in curl/curl.h.
      */
     arg = va_arg(param, long);
-    if(arg < CURL_HTTP_VERSION_NONE)
-      return CURLE_BAD_FUNCTION_ARGUMENT;
+    switch(arg) {
+    case CURL_HTTP_VERSION_NONE:
+#ifdef USE_HTTP2
+      /* TODO: this seems an undesirable quirk to force a behaviour on
+       * lower implementations that they should recognize independantly? */
+      arg = CURL_HTTP_VERSION_2TLS;
+#endif
+      /* accepted */
+      break;
+    case CURL_HTTP_VERSION_1_0:
+    case CURL_HTTP_VERSION_1_1:
+      /* accepted */
+      break;
+#ifdef USE_HTTP2
+    case CURL_HTTP_VERSION_2_0:
+    case CURL_HTTP_VERSION_2TLS:
+    case CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE:
+      /* accepted */
+      break;
+#endif
 #ifdef ENABLE_QUIC
-    if(arg == CURL_HTTP_VERSION_3)
-      ;
-    else
+    case CURL_HTTP_VERSION_3:
+    case CURL_HTTP_VERSION_3ONLY:
+      /* accepted */
+      break;
 #endif
-#ifndef USE_HTTP2
-    if(arg >= CURL_HTTP_VERSION_2)
-      return CURLE_UNSUPPORTED_PROTOCOL;
-#else
-    if(arg >= CURL_HTTP_VERSION_LAST)
+    default:
+      /* not accepted */
+      if(arg < CURL_HTTP_VERSION_NONE)
+        return CURLE_BAD_FUNCTION_ARGUMENT;
       return CURLE_UNSUPPORTED_PROTOCOL;
-    if(arg == CURL_HTTP_VERSION_NONE)
-      arg = CURL_HTTP_VERSION_2TLS;
-#endif
+    }
     data->set.httpwant = (unsigned char)arg;
     break;
 
@@ -944,6 +953,13 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
     data->set.http09_allowed = arg ? TRUE : FALSE;
 #endif
     break;
+
+  case CURLOPT_HTTP200ALIASES:
+    /*
+     * Set a list of aliases for HTTP 200 in response header
+     */
+    data->set.http200aliases = va_arg(param, struct curl_slist *);
+    break;
 #endif   /* CURL_DISABLE_HTTP */
 
 #if !defined(CURL_DISABLE_HTTP) || !defined(CURL_DISABLE_SMTP) ||       \
@@ -1309,6 +1325,7 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
     data->set.krb = (data->set.str[STRING_KRB_LEVEL]) ? TRUE : FALSE;
     break;
 #endif
+#if !defined(CURL_DISABLE_FTP) || defined(USE_SSH)
   case CURLOPT_FTP_CREATE_MISSING_DIRS:
     /*
      * An FTP/SFTP option that modifies an upload to create missing
@@ -1322,6 +1339,26 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
     else
       data->set.ftp_create_missing_dirs = (unsigned char)arg;
     break;
+
+  case CURLOPT_POSTQUOTE:
+    /*
+     * List of RAW FTP commands to use after a transfer
+     */
+    data->set.postquote = va_arg(param, struct curl_slist *);
+    break;
+  case CURLOPT_PREQUOTE:
+    /*
+     * List of RAW FTP commands to use prior to RETR (Wesley Laxton)
+     */
+    data->set.prequote = va_arg(param, struct curl_slist *);
+    break;
+  case CURLOPT_QUOTE:
+    /*
+     * List of RAW FTP commands to use before a transfer
+     */
+    data->set.quote = va_arg(param, struct curl_slist *);
+    break;
+#endif
   case CURLOPT_READDATA:
     /*
      * FILE pointer to read the file to be uploaded from. Or possibly
@@ -1431,7 +1468,7 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
 
   case CURLOPT_TIMEOUT_MS:
     uarg = va_arg(param, unsigned long);
-    if(uarg >= UINT_MAX)
+    if(uarg > UINT_MAX)
       uarg = UINT_MAX;
     data->set.timeout = (unsigned int)uarg;
     break;
@@ -1449,7 +1486,7 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
 
   case CURLOPT_CONNECTTIMEOUT_MS:
     uarg = va_arg(param, unsigned long);
-    if(uarg >= UINT_MAX)
+    if(uarg > UINT_MAX)
       uarg = UINT_MAX;
     data->set.connecttimeout = (unsigned int)uarg;
     break;
@@ -1460,7 +1497,7 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
      * The maximum time for curl to wait for FTP server connect
      */
     uarg = va_arg(param, unsigned long);
-    if(uarg >= UINT_MAX)
+    if(uarg > UINT_MAX)
       uarg = UINT_MAX;
     data->set.accepttimeout = (unsigned int)uarg;
     break;
@@ -1506,24 +1543,6 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
                             va_arg(param, char *));
     break;
 
-  case CURLOPT_POSTQUOTE:
-    /*
-     * List of RAW FTP commands to use after a transfer
-     */
-    data->set.postquote = va_arg(param, struct curl_slist *);
-    break;
-  case CURLOPT_PREQUOTE:
-    /*
-     * List of RAW FTP commands to use prior to RETR (Wesley Laxton)
-     */
-    data->set.prequote = va_arg(param, struct curl_slist *);
-    break;
-  case CURLOPT_QUOTE:
-    /*
-     * List of RAW FTP commands to use before a transfer
-     */
-    data->set.quote = va_arg(param, struct curl_slist *);
-    break;
   case CURLOPT_RESOLVE:
     /*
      * List of HOST:PORT:[addresses] strings to populate the DNS cache with
@@ -1871,16 +1890,15 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
     arg = va_arg(param, long);
     if((arg < 0) || (arg > 65535))
       return CURLE_BAD_FUNCTION_ARGUMENT;
-    data->set.localportrange = curlx_sltosi(arg);
+    data->set.localportrange = curlx_sltous(arg);
     break;
   case CURLOPT_GSSAPI_DELEGATION:
     /*
      * GSS-API credential delegation bitmask
      */
-    arg = va_arg(param, long);
-    if(arg < CURLGSSAPI_DELEGATION_NONE)
-      return CURLE_BAD_FUNCTION_ARGUMENT;
-    data->set.gssapi_delegation = arg;
+    uarg = va_arg(param, unsigned long);
+    data->set.gssapi_delegation = (unsigned char)uarg&
+      (CURLGSSAPI_DELEGATION_POLICY_FLAG|CURLGSSAPI_DELEGATION_FLAG);
     break;
   case CURLOPT_SSL_VERIFYPEER:
     /*
@@ -2260,9 +2278,14 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
         data->cookies = NULL;
 #endif
 
+#ifndef CURL_DISABLE_HSTS
+      if(data->share->hsts == data->hsts)
+        data->hsts = NULL;
+#endif
+#ifdef USE_SSL
       if(data->share->sslsession == data->state.session)
         data->state.session = NULL;
-
+#endif
 #ifdef USE_LIBPSL
       if(data->psl == &data->share->psl)
         data->psl = data->multi? &data->multi->psl: NULL;
@@ -2296,10 +2319,19 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
         data->cookies = data->share->cookies;
       }
 #endif   /* CURL_DISABLE_HTTP */
+#ifndef CURL_DISABLE_HSTS
+      if(data->share->hsts) {
+        /* first free the private one if any */
+        Curl_hsts_cleanup(&data->hsts);
+        data->hsts = data->share->hsts;
+      }
+#endif   /* CURL_DISABLE_HTTP */
+#ifdef USE_SSL
       if(data->share->sslsession) {
         data->set.general_ssl.max_ssl_sessions = data->share->max_ssl_sessions;
         data->state.session = data->share->sslsession;
       }
+#endif
 #ifdef USE_LIBPSL
       if(data->share->specifier & (1 << CURL_LOCK_DATA_PSL))
         data->psl = &data->share->psl;
@@ -2515,6 +2547,14 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
                             va_arg(param, char *));
     break;
 
+  case CURLOPT_SSH_KNOWNHOSTS:
+    /*
+     * Store the file name to read known hosts from.
+     */
+    result = Curl_setstropt(&data->set.str[STRING_SSH_KNOWNHOSTS],
+                            va_arg(param, char *));
+    break;
+#ifdef USE_LIBSSH2
   case CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256:
     /*
      * Option to allow for the SHA256 of the host public key to be checked
@@ -2524,14 +2564,6 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
                             va_arg(param, char *));
     break;
 
-  case CURLOPT_SSH_KNOWNHOSTS:
-    /*
-     * Store the file name to read known hosts from.
-     */
-    result = Curl_setstropt(&data->set.str[STRING_SSH_KNOWNHOSTS],
-                            va_arg(param, char *));
-    break;
-#ifdef USE_LIBSSH2
   case CURLOPT_SSH_HOSTKEYFUNCTION:
     /* the callback to check the hostkey without the knownhost file */
     data->set.ssh_hostkeyfunc = va_arg(param, curl_sshhostkeycallback);
@@ -2544,6 +2576,7 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
     data->set.ssh_hostkeyfunc_userp = va_arg(param, void *);
     break;
 #endif
+
   case CURLOPT_SSH_KEYFUNCTION:
     /* setting to NULL is fine since the ssh.c functions themselves will
        then revert to use the internal default */
@@ -2590,7 +2623,8 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
       return CURLE_BAD_FUNCTION_ARGUMENT;
     data->set.new_file_perms = (unsigned int)arg;
     break;
-
+#endif
+#ifdef USE_SSH
   case CURLOPT_NEW_DIRECTORY_PERMS:
     /*
      * Uses these permissions instead of 0755
@@ -2825,52 +2859,33 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
   case CURLOPT_TLSAUTH_USERNAME:
     result = Curl_setstropt(&data->set.str[STRING_TLSAUTH_USERNAME],
                             va_arg(param, char *));
-    if(data->set.str[STRING_TLSAUTH_USERNAME] &&
-       !data->set.ssl.primary.authtype)
-      data->set.ssl.primary.authtype = CURL_TLSAUTH_SRP; /* default to SRP */
     break;
 #ifndef CURL_DISABLE_PROXY
   case CURLOPT_PROXY_TLSAUTH_USERNAME:
     result = Curl_setstropt(&data->set.str[STRING_TLSAUTH_USERNAME_PROXY],
                             va_arg(param, char *));
-    if(data->set.str[STRING_TLSAUTH_USERNAME_PROXY] &&
-       !data->set.proxy_ssl.primary.authtype)
-      data->set.proxy_ssl.primary.authtype = CURL_TLSAUTH_SRP; /* default to
-                                                                  SRP */
     break;
 #endif
   case CURLOPT_TLSAUTH_PASSWORD:
     result = Curl_setstropt(&data->set.str[STRING_TLSAUTH_PASSWORD],
                             va_arg(param, char *));
-    if(data->set.str[STRING_TLSAUTH_USERNAME] &&
-       !data->set.ssl.primary.authtype)
-      data->set.ssl.primary.authtype = CURL_TLSAUTH_SRP; /* default */
     break;
 #ifndef CURL_DISABLE_PROXY
   case CURLOPT_PROXY_TLSAUTH_PASSWORD:
     result = Curl_setstropt(&data->set.str[STRING_TLSAUTH_PASSWORD_PROXY],
                             va_arg(param, char *));
-    if(data->set.str[STRING_TLSAUTH_USERNAME_PROXY] &&
-       !data->set.proxy_ssl.primary.authtype)
-      data->set.proxy_ssl.primary.authtype = CURL_TLSAUTH_SRP; /* default */
     break;
 #endif
   case CURLOPT_TLSAUTH_TYPE:
     argptr = va_arg(param, char *);
-    if(!argptr ||
-       strncasecompare(argptr, "SRP", strlen("SRP")))
-      data->set.ssl.primary.authtype = CURL_TLSAUTH_SRP;
-    else
-      data->set.ssl.primary.authtype = CURL_TLSAUTH_NONE;
+    if(argptr && !strncasecompare(argptr, "SRP", strlen("SRP")))
+      return CURLE_BAD_FUNCTION_ARGUMENT;
     break;
 #ifndef CURL_DISABLE_PROXY
   case CURLOPT_PROXY_TLSAUTH_TYPE:
     argptr = va_arg(param, char *);
-    if(!argptr ||
-       strncasecompare(argptr, "SRP", strlen("SRP")))
-      data->set.proxy_ssl.primary.authtype = CURL_TLSAUTH_SRP;
-    else
-      data->set.proxy_ssl.primary.authtype = CURL_TLSAUTH_NONE;
+    if(argptr || !strncasecompare(argptr, "SRP", strlen("SRP")))
+      return CURLE_BAD_FUNCTION_ARGUMENT;
     break;
 #endif
 #endif
@@ -2956,29 +2971,23 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
     data->set.pipewait = (0 != va_arg(param, long)) ? TRUE : FALSE;
     break;
   case CURLOPT_STREAM_WEIGHT:
-#ifndef USE_NGHTTP2
-    return CURLE_NOT_BUILT_IN;
-#else
+#if defined(USE_HTTP2) || defined(USE_HTTP3)
     arg = va_arg(param, long);
     if((arg >= 1) && (arg <= 256))
-      data->set.stream_weight = (int)arg;
+      data->set.priority.weight = (int)arg;
     break;
+#else
+    return CURLE_NOT_BUILT_IN;
 #endif
   case CURLOPT_STREAM_DEPENDS:
   case CURLOPT_STREAM_DEPENDS_E:
   {
-#ifndef USE_NGHTTP2
-    return CURLE_NOT_BUILT_IN;
-#else
     struct Curl_easy *dep = va_arg(param, struct Curl_easy *);
     if(!dep || GOOD_EASY_HANDLE(dep)) {
-      if(data->set.stream_depends_on) {
-        Curl_http2_remove_child(data->set.stream_depends_on, data);
-      }
-      Curl_http2_add_child(dep, data, (option == CURLOPT_STREAM_DEPENDS_E));
+      return Curl_data_priority_add_child(dep, data,
+                                          option == CURLOPT_STREAM_DEPENDS_E);
     }
     break;
-#endif
   }
   case CURLOPT_CONNECT_TO:
     data->set.connect_to = va_arg(param, struct curl_slist *);
@@ -2988,7 +2997,7 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
     break;
   case CURLOPT_HAPPY_EYEBALLS_TIMEOUT_MS:
     uarg = va_arg(param, unsigned long);
-    if(uarg >= UINT_MAX)
+    if(uarg > UINT_MAX)
       uarg = UINT_MAX;
     data->set.happy_eyeballs_timeout = (unsigned int)uarg;
     break;
@@ -3049,19 +3058,39 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
   case CURLOPT_HSTSWRITEDATA:
     data->set.hsts_write_userp = va_arg(param, void *);
     break;
-  case CURLOPT_HSTS:
+  case CURLOPT_HSTS: {
+    struct curl_slist *h;
     if(!data->hsts) {
       data->hsts = Curl_hsts_init();
       if(!data->hsts)
         return CURLE_OUT_OF_MEMORY;
     }
     argptr = va_arg(param, char *);
-    result = Curl_setstropt(&data->set.str[STRING_HSTS], argptr);
-    if(result)
-      return result;
-    if(argptr)
-      (void)Curl_hsts_loadfile(data, data->hsts, argptr);
+    if(argptr) {
+      result = Curl_setstropt(&data->set.str[STRING_HSTS], argptr);
+      if(result)
+        return result;
+      /* this needs to build a list of file names to read from, so that it can
+         read them later, as we might get a shared HSTS handle to load them
+         into */
+      h = curl_slist_append(data->set.hstslist, argptr);
+      if(!h) {
+        curl_slist_free_all(data->set.hstslist);
+        data->set.hstslist = NULL;
+        return CURLE_OUT_OF_MEMORY;
+      }
+      data->set.hstslist = h; /* store the list for later use */
+    }
+    else {
+      /* clear the list of HSTS files */
+      curl_slist_free_all(data->set.hstslist);
+      data->set.hstslist = NULL;
+      if(!data->share || !data->share->hsts)
+        /* throw away the HSTS cache unless shared */
+        Curl_hsts_cleanup(&data->hsts);
+    }
     break;
+  }
   case CURLOPT_HSTS_CTRL:
     arg = va_arg(param, long);
     if(arg & CURLHSTS_ENABLE) {
diff --git a/lib/setopt.h b/lib/setopt.h
index ffc77a7..3c14a05 100644
--- a/lib/setopt.h
+++ b/lib/setopt.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/setup-os400.h b/lib/setup-os400.h
index 7854397..7595834 100644
--- a/lib/setup-os400.h
+++ b/lib/setup-os400.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -205,7 +205,7 @@ extern OM_uint32 Curl_gss_delete_sec_context_a(OM_uint32 * minor_status,
 extern int Curl_os400_connect(int sd, struct sockaddr *destaddr, int addrlen);
 extern int Curl_os400_bind(int sd, struct sockaddr *localaddr, int addrlen);
 extern int Curl_os400_sendto(int sd, char *buffer, int buflen, int flags,
-                             struct sockaddr *dstaddr, int addrlen);
+                             const struct sockaddr *dstaddr, int addrlen);
 extern int Curl_os400_recvfrom(int sd, char *buffer, int buflen, int flags,
                                struct sockaddr *fromaddr, int *addrlen);
 extern int Curl_os400_getpeername(int sd, struct sockaddr *addr, int *addrlen);
diff --git a/lib/setup-vms.h b/lib/setup-vms.h
index b570683..46657b2 100644
--- a/lib/setup-vms.h
+++ b/lib/setup-vms.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/setup-win32.h b/lib/setup-win32.h
index bc5f8ef..1394838 100644
--- a/lib/setup-win32.h
+++ b/lib/setup-win32.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/sha256.c b/lib/sha256.c
index c96a9fc..fdfd631 100644
--- a/lib/sha256.c
+++ b/lib/sha256.c
@@ -5,8 +5,8 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2017, Florin Petriuc, <petriuc.florin@gmail.com>
- * Copyright (C) 2018 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Florin Petriuc, <petriuc.florin@gmail.com>
+ * 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
diff --git a/lib/share.c b/lib/share.c
index 1a083e7..c0a8d80 100644
--- a/lib/share.c
+++ b/lib/share.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -29,9 +29,11 @@
 #include "share.h"
 #include "psl.h"
 #include "vtls/vtls.h"
-#include "curl_memory.h"
+#include "hsts.h"
 
-/* The last #include file should be: */
+/* The last 3 #include files should be in this order */
+#include "curl_printf.h"
+#include "curl_memory.h"
 #include "memdebug.h"
 
 struct Curl_share *
@@ -89,6 +91,18 @@ curl_share_setopt(struct Curl_share *share, CURLSHoption option, ...)
 #endif
       break;
 
+    case CURL_LOCK_DATA_HSTS:
+#ifndef CURL_DISABLE_HSTS
+      if(!share->hsts) {
+        share->hsts = Curl_hsts_init();
+        if(!share->hsts)
+          res = CURLSHE_NOMEM;
+      }
+#else   /* CURL_DISABLE_HSTS */
+      res = CURLSHE_NOT_BUILT_IN;
+#endif
+      break;
+
     case CURL_LOCK_DATA_SSL_SESSION:
 #ifdef USE_SSL
       if(!share->sslsession) {
@@ -141,6 +155,16 @@ curl_share_setopt(struct Curl_share *share, CURLSHoption option, ...)
 #endif
       break;
 
+    case CURL_LOCK_DATA_HSTS:
+#ifndef CURL_DISABLE_HSTS
+      if(share->hsts) {
+        Curl_hsts_cleanup(&share->hsts);
+      }
+#else   /* CURL_DISABLE_HSTS */
+      res = CURLSHE_NOT_BUILT_IN;
+#endif
+      break;
+
     case CURL_LOCK_DATA_SSL_SESSION:
 #ifdef USE_SSL
       Curl_safefree(share->sslsession);
@@ -207,6 +231,10 @@ curl_share_cleanup(struct Curl_share *share)
   Curl_cookie_cleanup(share->cookies);
 #endif
 
+#ifndef CURL_DISABLE_HSTS
+  Curl_hsts_cleanup(&share->hsts);
+#endif
+
 #ifdef USE_SSL
   if(share->sslsession) {
     size_t i;
diff --git a/lib/share.h b/lib/share.h
index 32be416..7f55aac 100644
--- a/lib/share.h
+++ b/lib/share.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -59,10 +59,14 @@ struct Curl_share {
 #ifdef USE_LIBPSL
   struct PslCache psl;
 #endif
-
+#ifndef CURL_DISABLE_HSTS
+  struct hsts *hsts;
+#endif
+#ifdef USE_SSL
   struct Curl_ssl_session *sslsession;
   size_t max_ssl_sessions;
   long sessionage;
+#endif
 };
 
 CURLSHcode Curl_share_lock(struct Curl_easy *, curl_lock_data,
diff --git a/lib/sigpipe.h b/lib/sigpipe.h
index d12b317..14ab25b 100644
--- a/lib/sigpipe.h
+++ b/lib/sigpipe.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/slist.c b/lib/slist.c
index 6c80722..366b247 100644
--- a/lib/slist.c
+++ b/lib/slist.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/slist.h b/lib/slist.h
index 4e5834c..9561fd0 100644
--- a/lib/slist.h
+++ b/lib/slist.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/smb.c b/lib/smb.c
index 48d5a2f..dc0abe7 100644
--- a/lib/smb.c
+++ b/lib/smb.c
@@ -5,8 +5,8 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2016 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
- * Copyright (C) 2014, Bill Nagel <wnagel@tycoint.com>, Exacq Technologies
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Bill Nagel <wnagel@tycoint.com>, Exacq Technologies
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -763,6 +763,11 @@ static CURLcode smb_request_state(struct Curl_easy *data, bool *done)
   void *msg = NULL;
   const struct smb_nt_create_response *smb_m;
 
+  if(data->set.upload && (data->state.infilesize < 0)) {
+    failf(data, "SMB upload needs to know the size up front");
+    return CURLE_SEND_ERROR;
+  }
+
   /* Start the request */
   if(req->state == SMB_REQUESTING) {
     result = smb_send_tree_connect(data);
@@ -993,6 +998,7 @@ static CURLcode smb_parse_url_path(struct Curl_easy *data,
   /* The share must be present */
   if(!slash) {
     Curl_safefree(smbc->share);
+    failf(data, "missing share in URL path for SMB");
     return CURLE_URL_MALFORMAT;
   }
 
diff --git a/lib/smb.h b/lib/smb.h
index 919f3ac..c35f3e9 100644
--- a/lib/smb.h
+++ b/lib/smb.h
@@ -7,8 +7,8 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2018, Bill Nagel <wnagel@tycoint.com>, Exacq Technologies
- * Copyright (C) 2018 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Bill Nagel <wnagel@tycoint.com>, Exacq Technologies
+ * 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
diff --git a/lib/smtp.c b/lib/smtp.c
index 6d0783f4..7a03030 100644
--- a/lib/smtp.c
+++ b/lib/smtp.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -398,15 +398,17 @@ static CURLcode smtp_perform_upgrade_tls(struct Curl_easy *data)
   struct connectdata *conn = data->conn;
   struct smtp_conn *smtpc = &conn->proto.smtpc;
   CURLcode result;
+  bool ssldone = FALSE;
 
-  if(!Curl_conn_is_ssl(data, FIRSTSOCKET)) {
+  if(!Curl_conn_is_ssl(conn, FIRSTSOCKET)) {
     result = Curl_ssl_cfilter_add(data, conn, FIRSTSOCKET);
     if(result)
       goto out;
   }
 
-  result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &smtpc->ssldone);
+  result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &ssldone);
   if(!result) {
+    smtpc->ssldone = ssldone;
     if(smtpc->state != SMTP_UPGRADETLS)
       state(data, SMTP_UPGRADETLS);
 
@@ -891,7 +893,7 @@ static CURLcode smtp_state_ehlo_resp(struct Curl_easy *data,
 
   if(smtpcode/100 != 2 && smtpcode != 1) {
     if(data->set.use_ssl <= CURLUSESSL_TRY
-       || Curl_conn_is_ssl(data, FIRSTSOCKET))
+       || Curl_conn_is_ssl(conn, FIRSTSOCKET))
       result = smtp_perform_helo(data, conn);
     else {
       failf(data, "Remote access denied: %d", smtpcode);
@@ -956,7 +958,7 @@ static CURLcode smtp_state_ehlo_resp(struct Curl_easy *data,
     }
 
     if(smtpcode != 1) {
-      if(data->set.use_ssl && !Curl_conn_is_ssl(data, FIRSTSOCKET)) {
+      if(data->set.use_ssl && !Curl_conn_is_ssl(conn, FIRSTSOCKET)) {
         /* We don't have a SSL/TLS connection yet, but SSL is requested */
         if(smtpc->tls_supported)
           /* Switch to TLS connection now */
@@ -1288,7 +1290,9 @@ static CURLcode smtp_multi_statemach(struct Curl_easy *data, bool *done)
   struct smtp_conn *smtpc = &conn->proto.smtpc;
 
   if((conn->handler->flags & PROTOPT_SSL) && !smtpc->ssldone) {
-    result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &smtpc->ssldone);
+    bool ssldone = FALSE;
+    result = Curl_conn_connect(data, FIRSTSOCKET, FALSE, &ssldone);
+    smtpc->ssldone = ssldone;
     if(result || !smtpc->ssldone)
       return result;
   }
diff --git a/lib/smtp.h b/lib/smtp.h
index 24c5589..7a04c21 100644
--- a/lib/smtp.h
+++ b/lib/smtp.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2009 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -57,28 +57,28 @@ struct SMTP {
   curl_pp_transfer transfer;
   char *custom;            /* Custom Request */
   struct curl_slist *rcpt; /* Recipient list */
-  bool rcpt_had_ok;        /* Whether any of RCPT TO commands (depends on
-                              total number of recipients) succeeded so far */
-  bool trailing_crlf;      /* Specifies if the trailing CRLF is present */
   int rcpt_last_error;     /* The last error received for RCPT TO command */
   size_t eob;              /* Number of bytes of the EOB (End Of Body) that
                               have been received so far */
+  BIT(rcpt_had_ok);        /* Whether any of RCPT TO commands (depends on
+                              total number of recipients) succeeded so far */
+  BIT(trailing_crlf);      /* Specifies if the trailing CRLF is present */
 };
 
 /* smtp_conn is used for struct connection-oriented data in the connectdata
    struct */
 struct smtp_conn {
   struct pingpong pp;
+  struct SASL sasl;        /* SASL-related storage */
   smtpstate state;         /* Always use smtp.c:state() to change state! */
-  bool ssldone;            /* Is connect() over SSL done? */
   char *domain;            /* Client address/name to send in the EHLO */
-  struct SASL sasl;        /* SASL-related storage */
-  bool tls_supported;      /* StartTLS capability supported by server */
-  bool size_supported;     /* If server supports SIZE extension according to
+  BIT(ssldone);            /* Is connect() over SSL done? */
+  BIT(tls_supported);      /* StartTLS capability supported by server */
+  BIT(size_supported);     /* If server supports SIZE extension according to
                               RFC 1870 */
-  bool utf8_supported;     /* If server supports SMTPUTF8 extension according
+  BIT(utf8_supported);     /* If server supports SMTPUTF8 extension according
                               to RFC 6531 */
-  bool auth_supported;     /* AUTH capability supported by server */
+  BIT(auth_supported);     /* AUTH capability supported by server */
 };
 
 extern const struct Curl_handler Curl_handler_smtp;
diff --git a/lib/sockaddr.h b/lib/sockaddr.h
index 77ec833..5a6bb20 100644
--- a/lib/sockaddr.h
+++ b/lib/sockaddr.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/socketpair.c b/lib/socketpair.c
index 0f8798f..b94c984 100644
--- a/lib/socketpair.c
+++ b/lib/socketpair.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2019 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -65,7 +65,7 @@ int Curl_socketpair(int domain, int type, int protocol,
   union {
     struct sockaddr_in inaddr;
     struct sockaddr addr;
-  } a, a2;
+  } a;
   curl_socket_t listener;
   curl_socklen_t addrlen = sizeof(a.inaddr);
   int reuse = 1;
@@ -85,9 +85,22 @@ int Curl_socketpair(int domain, int type, int protocol,
 
   socks[0] = socks[1] = CURL_SOCKET_BAD;
 
+#if defined(WIN32) || defined(__CYGWIN__)
+  /* don't set SO_REUSEADDR on Windows */
+  (void)reuse;
+#ifdef SO_EXCLUSIVEADDRUSE
+  {
+    int exclusive = 1;
+    if(setsockopt(listener, SOL_SOCKET, SO_EXCLUSIVEADDRUSE,
+                  (char *)&exclusive, (curl_socklen_t)sizeof(exclusive)) == -1)
+      goto error;
+  }
+#endif
+#else
   if(setsockopt(listener, SOL_SOCKET, SO_REUSEADDR,
                 (char *)&reuse, (curl_socklen_t)sizeof(reuse)) == -1)
     goto error;
+#endif
   if(bind(listener, &a.addr, sizeof(a.inaddr)) == -1)
     goto error;
   if(getsockname(listener, &a.addr, &addrlen) == -1 ||
@@ -107,24 +120,59 @@ int Curl_socketpair(int domain, int type, int protocol,
   pfd[0].fd = listener;
   pfd[0].events = POLLIN;
   pfd[0].revents = 0;
-  (void)Curl_poll(pfd, 1, 10*1000); /* 10 seconds */
+  (void)Curl_poll(pfd, 1, 1000); /* one second */
   socks[1] = accept(listener, NULL, NULL);
   if(socks[1] == CURL_SOCKET_BAD)
     goto error;
+  else {
+    struct curltime check;
+    struct curltime start = Curl_now();
+    char *p = (char *)&check;
+    size_t s = sizeof(check);
 
-  /* verify that nothing else connected */
-  addrlen = sizeof(a.inaddr);
-  if(getsockname(socks[0], &a.addr, &addrlen) == -1 ||
-     addrlen < (int)sizeof(a.inaddr))
-    goto error;
-  addrlen = sizeof(a2.inaddr);
-  if(getpeername(socks[1], &a2.addr, &addrlen) == -1 ||
-     addrlen < (int)sizeof(a2.inaddr))
-    goto error;
-  if(a.inaddr.sin_family != a2.inaddr.sin_family ||
-     a.inaddr.sin_addr.s_addr != a2.inaddr.sin_addr.s_addr ||
-     a.inaddr.sin_port != a2.inaddr.sin_port)
-    goto error;
+    /* write data to the socket */
+    swrite(socks[0], &start, sizeof(start));
+    /* verify that we read the correct data */
+    do {
+      ssize_t nread;
+
+      pfd[0].fd = socks[1];
+      pfd[0].events = POLLIN;
+      pfd[0].revents = 0;
+      (void)Curl_poll(pfd, 1, 1000); /* one second */
+
+      nread = sread(socks[1], p, s);
+      if(nread == -1) {
+        int sockerr = SOCKERRNO;
+        /* Don't block forever */
+        if(Curl_timediff(Curl_now(), start) > (60 * 1000))
+          goto error;
+        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
+          ) {
+          continue;
+        }
+        goto error;
+      }
+      s -= nread;
+      if(s) {
+        p += nread;
+        continue;
+      }
+      if(memcmp(&start, &check, sizeof(check)))
+        goto error;
+      break;
+    } while(1);
+  }
 
   sclose(listener);
   return 0;
diff --git a/lib/socketpair.h b/lib/socketpair.h
index de70df6..306ab5d 100644
--- a/lib/socketpair.h
+++ b/lib/socketpair.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2019 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/socks.c b/lib/socks.c
index d491e08..95c2b00 100644
--- a/lib/socks.c
+++ b/lib/socks.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -89,8 +89,8 @@ struct socks_state {
  *
  * This is STUPID BLOCKING behavior. Only used by the SOCKS GSSAPI functions.
  */
-int Curl_blockread_all(struct Curl_easy *data,   /* transfer */
-                       curl_socket_t sockfd,     /* read from this socket */
+int Curl_blockread_all(struct Curl_cfilter *cf,
+                       struct Curl_easy *data,   /* transfer */
                        char *buf,                /* store read data here */
                        ssize_t buffersize,       /* max amount to read */
                        ssize_t *n)               /* amount bytes read */
@@ -98,6 +98,8 @@ int Curl_blockread_all(struct Curl_easy *data,   /* transfer */
   ssize_t nread = 0;
   ssize_t allread = 0;
   int result;
+  CURLcode err = CURLE_OK;
+
   *n = 0;
   for(;;) {
     timediff_t timeout_ms = Curl_timeleft(data, NULL, TRUE);
@@ -108,15 +110,19 @@ int Curl_blockread_all(struct Curl_easy *data,   /* transfer */
     }
     if(!timeout_ms)
       timeout_ms = TIMEDIFF_T_MAX;
-    if(SOCKET_READABLE(sockfd, timeout_ms) <= 0) {
+    if(SOCKET_READABLE(cf->conn->sock[cf->sockindex], timeout_ms) <= 0) {
       result = ~CURLE_OK;
       break;
     }
-    result = Curl_read_plain(data, sockfd, buf, buffersize, &nread);
-    if(CURLE_AGAIN == result)
-      continue;
-    if(result)
-      break;
+    nread = Curl_conn_cf_recv(cf->next, data, buf, buffersize, &err);
+    if(nread <= 0) {
+      result = err;
+      if(CURLE_AGAIN == err)
+        continue;
+      if(err) {
+        break;
+      }
+    }
 
     if(buffersize == nread) {
       allread += nread;
@@ -192,6 +198,68 @@ static void socksstate(struct socks_state *sx, struct Curl_easy *data,
 #endif
 }
 
+static CURLproxycode socks_state_send(struct Curl_cfilter *cf,
+                                      struct socks_state *sx,
+                                      struct Curl_easy *data,
+                                      CURLproxycode failcode,
+                                      const char *description)
+{
+  ssize_t nwritten;
+  CURLcode result;
+
+  nwritten = Curl_conn_cf_send(cf->next, data, (char *)sx->outp,
+                               sx->outstanding, &result);
+  if(nwritten <= 0) {
+    if(CURLE_AGAIN == result) {
+      return CURLPX_OK;
+    }
+    else if(CURLE_OK == result) {
+      /* connection closed */
+      failf(data, "connection to proxy closed");
+      return CURLPX_CLOSED;
+    }
+    failf(data, "Failed to send %s: %s", description,
+          curl_easy_strerror(result));
+    return failcode;
+  }
+  DEBUGASSERT(sx->outstanding >= nwritten);
+  /* not done, remain in state */
+  sx->outstanding -= nwritten;
+  sx->outp += nwritten;
+  return CURLPX_OK;
+}
+
+static CURLproxycode socks_state_recv(struct Curl_cfilter *cf,
+                                      struct socks_state *sx,
+                                      struct Curl_easy *data,
+                                      CURLproxycode failcode,
+                                      const char *description)
+{
+  ssize_t nread;
+  CURLcode result;
+
+  nread = Curl_conn_cf_recv(cf->next, data, (char *)sx->outp,
+                            sx->outstanding, &result);
+  if(nread <= 0) {
+    if(CURLE_AGAIN == result) {
+      return CURLPX_OK;
+    }
+    else if(CURLE_OK == result) {
+      /* connection closed */
+      failf(data, "connection to proxy closed");
+      return CURLPX_CLOSED;
+    }
+    failf(data, "SOCKS4: Failed receiving %s: %s", description,
+          curl_easy_strerror(result));
+    return failcode;
+  }
+  /* remain in reading state */
+  DEBUGASSERT(sx->outstanding >= nread);
+  sx->outstanding -= nread;
+  sx->outp += nread;
+  return CURLPX_OK;
+}
+
 /*
 * This function logs in to a SOCKS4 proxy and sends the specifics to the final
 * destination server.
@@ -212,10 +280,8 @@ static CURLproxycode do_SOCKS4(struct Curl_cfilter *cf,
     (conn->socks_proxy.proxytype == CURLPROXY_SOCKS4A) ? TRUE : FALSE;
   unsigned char *socksreq = (unsigned char *)data->state.buffer;
   CURLcode result;
-  curl_socket_t sockfd = conn->sock[cf->sockindex];
+  CURLproxycode presult;
   struct Curl_dns_entry *dns = NULL;
-  ssize_t actualread;
-  ssize_t written;
 
   /* make sure that the buffer is at least 600 bytes */
   DEBUGASSERT(READBUFFER_MIN >= 600);
@@ -250,7 +316,7 @@ static CURLproxycode do_SOCKS4(struct Curl_cfilter *cf,
     /* DNS resolve only for SOCKS4, not SOCKS4a */
     if(!protocol4a) {
       enum resolve_t rc =
-        Curl_resolv(data, sx->hostname, sx->remote_port, FALSE, &dns);
+        Curl_resolv(data, sx->hostname, sx->remote_port, TRUE, &dns);
 
       if(rc == CURLRESOLV_ERROR)
         return CURLPX_RESOLVE_HOST;
@@ -375,19 +441,14 @@ static CURLproxycode do_SOCKS4(struct Curl_cfilter *cf,
     /* FALLTHROUGH */
   case CONNECT_REQ_SENDING:
     /* Send request */
-    result = Curl_write_plain(data, sockfd, (char *)sx->outp,
-                              sx->outstanding, &written);
-    if(result && (CURLE_AGAIN != result)) {
-      failf(data, "Failed to send SOCKS4 connect request.");
-      return CURLPX_SEND_CONNECT;
-    }
-    if(written != sx->outstanding) {
-      /* not done, remain in state */
-      sx->outstanding -= written;
-      sx->outp += written;
+    presult = socks_state_send(cf, sx, data, CURLPX_SEND_CONNECT,
+                               "SOCKS4 connect request");
+    if(CURLPX_OK != presult)
+      return presult;
+    else if(sx->outstanding) {
+      /* remain in sending state */
       return CURLPX_OK;
     }
-
     /* done sending! */
     sx->outstanding = 8; /* receive data size */
     sx->outp = socksreq;
@@ -396,22 +457,12 @@ static CURLproxycode do_SOCKS4(struct Curl_cfilter *cf,
     /* FALLTHROUGH */
   case CONNECT_SOCKS_READ:
     /* Receive response */
-    result = Curl_read_plain(data, sockfd, (char *)sx->outp,
-                             sx->outstanding, &actualread);
-    if(result && (CURLE_AGAIN != result)) {
-      failf(data, "SOCKS4: Failed receiving connect request ack: %s",
-            curl_easy_strerror(result));
-      return CURLPX_RECV_CONNECT;
-    }
-    else if(!result && !actualread) {
-      /* connection closed */
-      failf(data, "connection to proxy closed");
-      return CURLPX_CLOSED;
-    }
-    else if(actualread != sx->outstanding) {
+    presult = socks_state_recv(cf, sx, data, CURLPX_RECV_CONNECT,
+                               "connect request ack");
+    if(CURLPX_OK != presult)
+      return presult;
+    else if(sx->outstanding) {
       /* remain in reading state */
-      sx->outstanding -= actualread;
-      sx->outp += actualread;
       return CURLPX_OK;
     }
     sxstate(sx, data, CONNECT_DONE);
@@ -518,10 +569,8 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf,
   unsigned char *socksreq = (unsigned char *)data->state.buffer;
   char dest[256] = "unknown";  /* printable hostname:port */
   int idx;
-  ssize_t actualread;
-  ssize_t written;
   CURLcode result;
-  curl_socket_t sockfd = conn->sock[cf->sockindex];
+  CURLproxycode presult;
   bool socks5_resolve_local =
     (conn->socks_proxy.proxytype == CURLPROXY_SOCKS5) ? TRUE : FALSE;
   const size_t hostname_len = strlen(sx->hostname);
@@ -567,30 +616,25 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf,
     /* write the number of authentication methods */
     socksreq[1] = (unsigned char) (idx - 2);
 
-    result = Curl_write_plain(data, sockfd, socksreq, idx, &written);
-    if(result && (CURLE_AGAIN != result)) {
-      failf(data, "Unable to send initial SOCKS5 request.");
-      return CURLPX_SEND_CONNECT;
-    }
-    if(written != idx) {
-      sxstate(sx, data, CONNECT_SOCKS_SEND);
-      sx->outstanding = idx - written;
-      sx->outp = &socksreq[written];
+    sx->outp = socksreq;
+    sx->outstanding = idx;
+    presult = socks_state_send(cf, sx, data, CURLPX_SEND_CONNECT,
+                               "initial SOCKS5 request");
+    if(CURLPX_OK != presult)
+      return presult;
+    else if(sx->outstanding) {
+      /* remain in sending state */
       return CURLPX_OK;
     }
     sxstate(sx, data, CONNECT_SOCKS_READ);
     goto CONNECT_SOCKS_READ_INIT;
   case CONNECT_SOCKS_SEND:
-    result = Curl_write_plain(data, sockfd, (char *)sx->outp,
-                              sx->outstanding, &written);
-    if(result && (CURLE_AGAIN != result)) {
-      failf(data, "Unable to send initial SOCKS5 request.");
-      return CURLPX_SEND_CONNECT;
-    }
-    if(written != sx->outstanding) {
-      /* not done, remain in state */
-      sx->outstanding -= written;
-      sx->outp += written;
+    presult = socks_state_send(cf, sx, data, CURLPX_SEND_CONNECT,
+                               "initial SOCKS5 request");
+    if(CURLPX_OK != presult)
+      return presult;
+    else if(sx->outstanding) {
+      /* remain in sending state */
       return CURLPX_OK;
     }
     /* FALLTHROUGH */
@@ -600,21 +644,12 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf,
     sx->outp = socksreq; /* store it here */
     /* FALLTHROUGH */
   case CONNECT_SOCKS_READ:
-    result = Curl_read_plain(data, sockfd, (char *)sx->outp,
-                             sx->outstanding, &actualread);
-    if(result && (CURLE_AGAIN != result)) {
-      failf(data, "Unable to receive initial SOCKS5 response.");
-      return CURLPX_RECV_CONNECT;
-    }
-    else if(!result && !actualread) {
-      /* connection closed */
-      failf(data, "Connection to proxy closed");
-      return CURLPX_CLOSED;
-    }
-    else if(actualread != sx->outstanding) {
+    presult = socks_state_recv(cf, sx, data, CURLPX_RECV_CONNECT,
+                               "initial SOCKS5 response");
+    if(CURLPX_OK != presult)
+      return presult;
+    else if(sx->outstanding) {
       /* remain in reading state */
-      sx->outstanding -= actualread;
-      sx->outp += actualread;
       return CURLPX_OK;
     }
     else if(socksreq[0] != 5) {
@@ -634,7 +669,7 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf,
 #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)
     else if(allow_gssapi && (socksreq[1] == 1)) {
       sxstate(sx, data, CONNECT_GSSAPI_INIT);
-      result = Curl_SOCKS5_gssapi_negotiate(cf->sockindex, data);
+      result = Curl_SOCKS5_gssapi_negotiate(cf, data);
       if(result) {
         failf(data, "Unable to negotiate SOCKS5 GSS-API context.");
         return CURLPX_GSSAPI;
@@ -713,16 +748,12 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf,
   }
     /* FALLTHROUGH */
   case CONNECT_AUTH_SEND:
-    result = Curl_write_plain(data, sockfd, sx->outp,
-                              sx->outstanding, &written);
-    if(result && (CURLE_AGAIN != result)) {
-      failf(data, "Failed to send SOCKS5 sub-negotiation request.");
-      return CURLPX_SEND_AUTH;
-    }
-    if(sx->outstanding != written) {
-      /* remain in state */
-      sx->outstanding -= written;
-      sx->outp += written;
+    presult = socks_state_send(cf, sx, data, CURLPX_SEND_AUTH,
+                               "SOCKS5 sub-negotiation request");
+    if(CURLPX_OK != presult)
+      return presult;
+    else if(sx->outstanding) {
+      /* remain in sending state */
       return CURLPX_OK;
     }
     sx->outp = socksreq;
@@ -730,21 +761,12 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf,
     sxstate(sx, data, CONNECT_AUTH_READ);
     /* FALLTHROUGH */
   case CONNECT_AUTH_READ:
-    result = Curl_read_plain(data, sockfd, (char *)sx->outp,
-                             sx->outstanding, &actualread);
-    if(result && (CURLE_AGAIN != result)) {
-      failf(data, "Unable to receive SOCKS5 sub-negotiation response.");
-      return CURLPX_RECV_AUTH;
-    }
-    else if(!result && !actualread) {
-      /* connection closed */
-      failf(data, "connection to proxy closed");
-      return CURLPX_CLOSED;
-    }
-    else if(actualread != sx->outstanding) {
-      /* remain in state */
-      sx->outstanding -= actualread;
-      sx->outp += actualread;
+    presult = socks_state_recv(cf, sx, data, CURLPX_RECV_AUTH,
+                               "SOCKS5 sub-negotiation response");
+    if(CURLPX_OK != presult)
+      return presult;
+    else if(sx->outstanding) {
+      /* remain in reading state */
       return CURLPX_OK;
     }
     /* ignore the first (VER) byte */
@@ -761,7 +783,7 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf,
   case CONNECT_REQ_INIT:
     if(socks5_resolve_local) {
       enum resolve_t rc = Curl_resolv(data, sx->hostname, sx->remote_port,
-                                      FALSE, &dns);
+                                      TRUE, &dns);
 
       if(rc == CURLRESOLV_ERROR)
         return CURLPX_RESOLVE_HOST;
@@ -909,16 +931,12 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf,
     sxstate(sx, data, CONNECT_REQ_SENDING);
     /* FALLTHROUGH */
   case CONNECT_REQ_SENDING:
-    result = Curl_write_plain(data, sockfd, (char *)sx->outp,
-                              sx->outstanding, &written);
-    if(result && (CURLE_AGAIN != result)) {
-      failf(data, "Failed to send SOCKS5 connect request.");
-      return CURLPX_SEND_REQUEST;
-    }
-    if(sx->outstanding != written) {
-      /* remain in state */
-      sx->outstanding -= written;
-      sx->outp += written;
+    presult = socks_state_send(cf, sx, data, CURLPX_SEND_REQUEST,
+                               "SOCKS5 connect request");
+    if(CURLPX_OK != presult)
+      return presult;
+    else if(sx->outstanding) {
+      /* remain in send state */
       return CURLPX_OK;
     }
 #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)
@@ -932,25 +950,15 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf,
     sxstate(sx, data, CONNECT_REQ_READ);
     /* FALLTHROUGH */
   case CONNECT_REQ_READ:
-    result = Curl_read_plain(data, sockfd, (char *)sx->outp,
-                             sx->outstanding, &actualread);
-    if(result && (CURLE_AGAIN != result)) {
-      failf(data, "Failed to receive SOCKS5 connect request ack.");
-      return CURLPX_RECV_REQACK;
-    }
-    else if(!result && !actualread) {
-      /* connection closed */
-      failf(data, "connection to proxy closed");
-      return CURLPX_CLOSED;
-    }
-    else if(actualread != sx->outstanding) {
-      /* remain in state */
-      sx->outstanding -= actualread;
-      sx->outp += actualread;
+    presult = socks_state_recv(cf, sx, data, CURLPX_RECV_REQACK,
+                               "SOCKS5 connect request ack");
+    if(CURLPX_OK != presult)
+      return presult;
+    else if(sx->outstanding) {
+      /* remain in reading state */
       return CURLPX_OK;
     }
-
-    if(socksreq[0] != 5) { /* version */
+    else if(socksreq[0] != 5) { /* version */
       failf(data,
             "SOCKS5 reply has wrong version, version should be 5.");
       return CURLPX_BAD_VERSION;
@@ -1031,21 +1039,12 @@ static CURLproxycode do_SOCKS5(struct Curl_cfilter *cf,
 #endif
     /* FALLTHROUGH */
   case CONNECT_REQ_READ_MORE:
-    result = Curl_read_plain(data, sockfd, (char *)sx->outp,
-                             sx->outstanding, &actualread);
-    if(result && (CURLE_AGAIN != result)) {
-      failf(data, "Failed to receive SOCKS5 connect request ack.");
-      return CURLPX_RECV_ADDRESS;
-    }
-    else if(!result && !actualread) {
-      /* connection closed */
-      failf(data, "connection to proxy closed");
-      return CURLPX_CLOSED;
-    }
-    else if(actualread != sx->outstanding) {
-      /* remain in state */
-      sx->outstanding -= actualread;
-      sx->outp += actualread;
+    presult = socks_state_recv(cf, sx, data, CURLPX_RECV_ADDRESS,
+                               "SOCKS5 connect request address");
+    if(CURLPX_OK != presult)
+      return presult;
+    else if(sx->outstanding) {
+      /* remain in reading state */
       return CURLPX_OK;
     }
     sxstate(sx, data, CONNECT_DONE);
@@ -1151,7 +1150,6 @@ static CURLcode socks_proxy_cf_connect(struct Curl_cfilter *cf,
   result = connect_SOCKS(cf, sx, data);
   if(!result && sx->state == CONNECT_DONE) {
     cf->connected = TRUE;
-    Curl_updateconninfo(data, conn, conn->sock[cf->sockindex]);
     Curl_verboseconnect(data, conn);
     socks_proxy_cf_free(cf);
   }
@@ -1171,7 +1169,7 @@ static int socks_cf_get_select_socks(struct Curl_cfilter *cf,
   if(!fds && cf->next->connected && !cf->connected && sx) {
     /* If we are not connected, the filter below is and has nothing
      * to wait on, we determine what to wait for. */
-    socks[0] = cf->conn->sock[cf->sockindex];
+    socks[0] = Curl_conn_cf_get_socket(cf, data);
     switch(sx->state) {
     case CONNECT_RESOLVING:
     case CONNECT_SOCKS_READ:
@@ -1205,13 +1203,6 @@ static void socks_proxy_cf_destroy(struct Curl_cfilter *cf,
   socks_proxy_cf_free(cf);
 }
 
-static void socks_proxy_cf_detach_data(struct Curl_cfilter *cf,
-                                       struct Curl_easy *data)
-{
-  (void)data;
-  socks_proxy_cf_free(cf);
-}
-
 static void socks_cf_get_host(struct Curl_cfilter *cf,
                               struct Curl_easy *data,
                               const char **phost,
@@ -1229,11 +1220,11 @@ static void socks_cf_get_host(struct Curl_cfilter *cf,
   }
 }
 
-static const struct Curl_cftype cft_socks_proxy = {
+struct Curl_cftype Curl_cft_socks_proxy = {
   "SOCKS-PROXYY",
   CF_TYPE_IP_CONNECT,
+  0,
   socks_proxy_cf_destroy,
-  Curl_cf_def_setup,
   socks_proxy_cf_connect,
   socks_proxy_cf_close,
   socks_cf_get_host,
@@ -1241,8 +1232,10 @@ static const struct Curl_cftype cft_socks_proxy = {
   Curl_cf_def_data_pending,
   Curl_cf_def_send,
   Curl_cf_def_recv,
-  Curl_cf_def_attach_data,
-  socks_proxy_cf_detach_data,
+  Curl_cf_def_cntrl,
+  Curl_cf_def_conn_is_alive,
+  Curl_cf_def_conn_keep_alive,
+  Curl_cf_def_query,
 };
 
 CURLcode Curl_conn_socks_proxy_add(struct Curl_easy *data,
@@ -1252,10 +1245,23 @@ CURLcode Curl_conn_socks_proxy_add(struct Curl_easy *data,
   struct Curl_cfilter *cf;
   CURLcode result;
 
-  result = Curl_cf_create(&cf, &cft_socks_proxy, NULL);
+  result = Curl_cf_create(&cf, &Curl_cft_socks_proxy, NULL);
   if(!result)
     Curl_conn_cf_add(data, conn, sockindex, cf);
   return result;
 }
 
+CURLcode Curl_cf_socks_proxy_insert_after(struct Curl_cfilter *cf_at,
+                                          struct Curl_easy *data)
+{
+  struct Curl_cfilter *cf;
+  CURLcode result;
+
+  (void)data;
+  result = Curl_cf_create(&cf, &Curl_cft_socks_proxy, NULL);
+  if(!result)
+    Curl_conn_cf_insert_after(cf_at, cf);
+  return result;
+}
+
 #endif /* CURL_DISABLE_PROXY */
diff --git a/lib/socks.h b/lib/socks.h
index 2e2fa18..ba5b54a 100644
--- a/lib/socks.h
+++ b/lib/socks.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -37,8 +37,8 @@
  *
  * This is STUPID BLOCKING behavior
  */
-int Curl_blockread_all(struct Curl_easy *data,
-                       curl_socket_t sockfd,
+int Curl_blockread_all(struct Curl_cfilter *cf,
+                       struct Curl_easy *data,
                        char *buf,
                        ssize_t buffersize,
                        ssize_t *n);
@@ -47,7 +47,7 @@ int Curl_blockread_all(struct Curl_easy *data,
 /*
  * This function handles the SOCKS5 GSS-API negotiation and initialization
  */
-CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex,
+CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf,
                                       struct Curl_easy *data);
 #endif
 
@@ -55,6 +55,11 @@ CURLcode Curl_conn_socks_proxy_add(struct Curl_easy *data,
                                    struct connectdata *conn,
                                    int sockindex);
 
+CURLcode Curl_cf_socks_proxy_insert_after(struct Curl_cfilter *cf_at,
+                                          struct Curl_easy *data);
+
+extern struct Curl_cftype Curl_cft_socks_proxy;
+
 #endif /* CURL_DISABLE_PROXY */
 
 #endif  /* HEADER_CURL_SOCKS_H */
diff --git a/lib/socks_gssapi.c b/lib/socks_gssapi.c
index f14099f..2ede8c7 100644
--- a/lib/socks_gssapi.c
+++ b/lib/socks_gssapi.c
@@ -5,8 +5,8 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
- * Copyright (C) 2012, Markus Moeller, <markus_moeller@compuserve.com>
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Markus Moeller, <markus_moeller@compuserve.com>
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -30,6 +30,7 @@
 #include "curl_gssapi.h"
 #include "urldata.h"
 #include "sendf.h"
+#include "cfilters.h"
 #include "connect.h"
 #include "timeval.h"
 #include "socks.h"
@@ -101,14 +102,14 @@ static int check_gss_err(struct Curl_easy *data,
   return 0;
 }
 
-CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex,
+CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf,
                                       struct Curl_easy *data)
 {
-  struct connectdata *conn = data->conn;
-  curl_socket_t sock = conn->sock[sockindex];
+  struct connectdata *conn = cf->conn;
+  curl_socket_t sock = conn->sock[cf->sockindex];
   CURLcode code;
   ssize_t actualread;
-  ssize_t written;
+  ssize_t nwritten;
   int result;
   OM_uint32 gss_major_status, gss_minor_status, gss_status;
   OM_uint32 gss_ret_flags;
@@ -203,8 +204,8 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex,
       us_length = htons((short)gss_send_token.length);
       memcpy(socksreq + 2, &us_length, sizeof(short));
 
-      code = Curl_write_plain(data, sock, (char *)socksreq, 4, &written);
-      if(code || (4 != written)) {
+      nwritten = Curl_conn_cf_send(cf->next, data, (char *)socksreq, 4, &code);
+      if(code || (4 != nwritten)) {
         failf(data, "Failed to send GSS-API authentication request.");
         gss_release_name(&gss_status, &server);
         gss_release_buffer(&gss_status, &gss_recv_token);
@@ -213,10 +214,10 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex,
         return CURLE_COULDNT_CONNECT;
       }
 
-      code = Curl_write_plain(data, sock, (char *)gss_send_token.value,
-                              gss_send_token.length, &written);
-
-      if(code || ((ssize_t)gss_send_token.length != written)) {
+      nwritten = Curl_conn_cf_send(cf->next, data,
+                                   (char *)gss_send_token.value,
+                                   gss_send_token.length, &code);
+      if(code || ((ssize_t)gss_send_token.length != nwritten)) {
         failf(data, "Failed to send GSS-API authentication token.");
         gss_release_name(&gss_status, &server);
         gss_release_buffer(&gss_status, &gss_recv_token);
@@ -242,7 +243,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex,
      * +----+------+-----+----------------+
      */
 
-    result = Curl_blockread_all(data, sock, (char *)socksreq, 4, &actualread);
+    result = Curl_blockread_all(cf, data, (char *)socksreq, 4, &actualread);
     if(result || (actualread != 4)) {
       failf(data, "Failed to receive GSS-API authentication response.");
       gss_release_name(&gss_status, &server);
@@ -281,7 +282,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex,
       return CURLE_OUT_OF_MEMORY;
     }
 
-    result = Curl_blockread_all(data, sock, (char *)gss_recv_token.value,
+    result = Curl_blockread_all(cf, data, (char *)gss_recv_token.value,
                                 gss_recv_token.length, &actualread);
 
     if(result || (actualread != us_length)) {
@@ -410,8 +411,8 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex,
     memcpy(socksreq + 2, &us_length, sizeof(short));
   }
 
-  code = Curl_write_plain(data, sock, (char *)socksreq, 4, &written);
-  if(code  || (4 != written)) {
+  nwritten = Curl_conn_cf_send(cf->next, data, (char *)socksreq, 4, &code);
+  if(code  || (4 != nwritten)) {
     failf(data, "Failed to send GSS-API encryption request.");
     gss_release_buffer(&gss_status, &gss_w_token);
     gss_delete_sec_context(&gss_status, &gss_context, NULL);
@@ -420,17 +421,18 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex,
 
   if(data->set.socks5_gssapi_nec) {
     memcpy(socksreq, &gss_enc, 1);
-    code = Curl_write_plain(data, sock, socksreq, 1, &written);
-    if(code || ( 1 != written)) {
+    nwritten = Curl_conn_cf_send(cf->next, data, (char *)socksreq, 1, &code);
+    if(code || ( 1 != nwritten)) {
       failf(data, "Failed to send GSS-API encryption type.");
       gss_delete_sec_context(&gss_status, &gss_context, NULL);
       return CURLE_COULDNT_CONNECT;
     }
   }
   else {
-    code = Curl_write_plain(data, sock, (char *)gss_w_token.value,
-                            gss_w_token.length, &written);
-    if(code || ((ssize_t)gss_w_token.length != written)) {
+    nwritten = Curl_conn_cf_send(cf->next, data,
+                                 (char *)gss_w_token.value,
+                                 gss_w_token.length, &code);
+    if(code || ((ssize_t)gss_w_token.length != nwritten)) {
       failf(data, "Failed to send GSS-API encryption type.");
       gss_release_buffer(&gss_status, &gss_w_token);
       gss_delete_sec_context(&gss_status, &gss_context, NULL);
@@ -439,7 +441,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex,
     gss_release_buffer(&gss_status, &gss_w_token);
   }
 
-  result = Curl_blockread_all(data, sock, (char *)socksreq, 4, &actualread);
+  result = Curl_blockread_all(cf, data, (char *)socksreq, 4, &actualread);
   if(result || (actualread != 4)) {
     failf(data, "Failed to receive GSS-API encryption response.");
     gss_delete_sec_context(&gss_status, &gss_context, NULL);
@@ -470,7 +472,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex,
     gss_delete_sec_context(&gss_status, &gss_context, NULL);
     return CURLE_OUT_OF_MEMORY;
   }
-  result = Curl_blockread_all(data, sock, (char *)gss_recv_token.value,
+  result = Curl_blockread_all(cf, data, (char *)gss_recv_token.value,
                               gss_recv_token.length, &actualread);
 
   if(result || (actualread != us_length)) {
diff --git a/lib/socks_sspi.c b/lib/socks_sspi.c
index 210a0df..d1200ea 100644
--- a/lib/socks_sspi.c
+++ b/lib/socks_sspi.c
@@ -5,8 +5,8 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
- * Copyright (C) 2012, 2011, Markus Moeller, <markus_moeller@compuserve.com>
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Markus Moeller, <markus_moeller@compuserve.com>
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -29,6 +29,7 @@
 
 #include "urldata.h"
 #include "sendf.h"
+#include "cfilters.h"
 #include "connect.h"
 #include "strerror.h"
 #include "timeval.h"
@@ -62,11 +63,11 @@ static int check_sspi_err(struct Curl_easy *data,
 }
 
 /* This is the SSPI-using version of this function */
-CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex,
+CURLcode Curl_SOCKS5_gssapi_negotiate(struct Curl_cfilter *cf,
                                       struct Curl_easy *data)
 {
-  struct connectdata *conn = data->conn;
-  curl_socket_t sock = conn->sock[sockindex];
+  struct connectdata *conn = cf->conn;
+  curl_socket_t sock = conn->sock[cf->sockindex];
   CURLcode code;
   ssize_t actualread;
   ssize_t written;
@@ -206,7 +207,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex,
       us_length = htons((short)sspi_send_token.cbBuffer);
       memcpy(socksreq + 2, &us_length, sizeof(short));
 
-      code = Curl_write_plain(data, sock, (char *)socksreq, 4, &written);
+      written = Curl_conn_cf_send(cf->next, data, (char *)socksreq, 4, &code);
       if(code || (4 != written)) {
         failf(data, "Failed to send SSPI authentication request.");
         free(service_name);
@@ -219,8 +220,9 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex,
         return CURLE_COULDNT_CONNECT;
       }
 
-      code = Curl_write_plain(data, sock, (char *)sspi_send_token.pvBuffer,
-                              sspi_send_token.cbBuffer, &written);
+      written = Curl_conn_cf_send(cf->next, data,
+                                  (char *)sspi_send_token.pvBuffer,
+                                  sspi_send_token.cbBuffer, &code);
       if(code || (sspi_send_token.cbBuffer != (size_t)written)) {
         failf(data, "Failed to send SSPI authentication token.");
         free(service_name);
@@ -260,7 +262,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex,
      * +----+------+-----+----------------+
      */
 
-    result = Curl_blockread_all(data, sock, (char *)socksreq, 4, &actualread);
+    result = Curl_blockread_all(cf, data, (char *)socksreq, 4, &actualread);
     if(result || (actualread != 4)) {
       failf(data, "Failed to receive SSPI authentication response.");
       free(service_name);
@@ -300,7 +302,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex,
       s_pSecFn->DeleteSecurityContext(&sspi_context);
       return CURLE_OUT_OF_MEMORY;
     }
-    result = Curl_blockread_all(data, sock, (char *)sspi_recv_token.pvBuffer,
+    result = Curl_blockread_all(cf, data, (char *)sspi_recv_token.pvBuffer,
                                 sspi_recv_token.cbBuffer, &actualread);
 
     if(result || (actualread != us_length)) {
@@ -468,7 +470,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex,
     memcpy(socksreq + 2, &us_length, sizeof(short));
   }
 
-  code = Curl_write_plain(data, sock, (char *)socksreq, 4, &written);
+  written = Curl_conn_cf_send(cf->next, data, (char *)socksreq, 4, &code);
   if(code || (4 != written)) {
     failf(data, "Failed to send SSPI encryption request.");
     if(sspi_send_token.pvBuffer)
@@ -479,7 +481,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex,
 
   if(data->set.socks5_gssapi_nec) {
     memcpy(socksreq, &gss_enc, 1);
-    code = Curl_write_plain(data, sock, (char *)socksreq, 1, &written);
+    written = Curl_conn_cf_send(cf->next, data, (char *)socksreq, 1, &code);
     if(code || (1 != written)) {
       failf(data, "Failed to send SSPI encryption type.");
       s_pSecFn->DeleteSecurityContext(&sspi_context);
@@ -487,8 +489,9 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex,
     }
   }
   else {
-    code = Curl_write_plain(data, sock, (char *)sspi_send_token.pvBuffer,
-                            sspi_send_token.cbBuffer, &written);
+    written = Curl_conn_cf_send(cf->next, data,
+                                (char *)sspi_send_token.pvBuffer,
+                                sspi_send_token.cbBuffer, &code);
     if(code || (sspi_send_token.cbBuffer != (size_t)written)) {
       failf(data, "Failed to send SSPI encryption type.");
       if(sspi_send_token.pvBuffer)
@@ -500,7 +503,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex,
       s_pSecFn->FreeContextBuffer(sspi_send_token.pvBuffer);
   }
 
-  result = Curl_blockread_all(data, sock, (char *)socksreq, 4, &actualread);
+  result = Curl_blockread_all(cf, data, (char *)socksreq, 4, &actualread);
   if(result || (actualread != 4)) {
     failf(data, "Failed to receive SSPI encryption response.");
     s_pSecFn->DeleteSecurityContext(&sspi_context);
@@ -532,7 +535,7 @@ CURLcode Curl_SOCKS5_gssapi_negotiate(int sockindex,
     return CURLE_OUT_OF_MEMORY;
   }
 
-  result = Curl_blockread_all(data, sock, (char *)sspi_w_token[0].pvBuffer,
+  result = Curl_blockread_all(cf, data, (char *)sspi_w_token[0].pvBuffer,
                               sspi_w_token[0].cbBuffer, &actualread);
 
   if(result || (actualread != us_length)) {
diff --git a/lib/speedcheck.c b/lib/speedcheck.c
index 3ddc43d..580efbd 100644
--- a/lib/speedcheck.c
+++ b/lib/speedcheck.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/speedcheck.h b/lib/speedcheck.h
index cb44eb0..bff2f32 100644
--- a/lib/speedcheck.h
+++ b/lib/speedcheck.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/splay.c b/lib/splay.c
index 33b44aa..48e079b 100644
--- a/lib/splay.c
+++ b/lib/splay.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1997 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/splay.h b/lib/splay.h
index 015e2ca..dd1d07a 100644
--- a/lib/splay.h
+++ b/lib/splay.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1997 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/strcase.c b/lib/strcase.c
index 7fb9c80..7c0b4ef 100644
--- a/lib/strcase.c
+++ b/lib/strcase.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/strcase.h b/lib/strcase.h
index 192e0da..8c50bbc 100644
--- a/lib/strcase.h
+++ b/lib/strcase.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/strdup.c b/lib/strdup.c
index ac22b6d..07a6139 100644
--- a/lib/strdup.c
+++ b/lib/strdup.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -37,7 +37,7 @@
 #include "memdebug.h"
 
 #ifndef HAVE_STRDUP
-char *curlx_strdup(const char *str)
+char *Curl_strdup(const char *str)
 {
   size_t len;
   char *newstr;
diff --git a/lib/strdup.h b/lib/strdup.h
index fb46808..c3430b5 100644
--- a/lib/strdup.h
+++ b/lib/strdup.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -26,7 +26,7 @@
 #include "curl_setup.h"
 
 #ifndef HAVE_STRDUP
-extern char *curlx_strdup(const char *str);
+char *Curl_strdup(const char *str);
 #endif
 #ifdef WIN32
 wchar_t* Curl_wcsdup(const wchar_t* src);
diff --git a/lib/strerror.c b/lib/strerror.c
index b9a51e2..3ec10e3 100644
--- a/lib/strerror.c
+++ b/lib/strerror.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2004 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -550,6 +550,9 @@ curl_url_strerror(CURLUcode error)
   case CURLUE_BAD_USER:
     return "Bad user";
 
+  case CURLUE_LACKS_IDN:
+    return "libcurl lacks IDN support";
+
   case CURLUE_LAST:
     break;
   }
diff --git a/lib/strerror.h b/lib/strerror.h
index 658f16c..399712f 100644
--- a/lib/strerror.h
+++ b/lib/strerror.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/strtok.c b/lib/strtok.c
index 6120bcc..d8e1e81 100644
--- a/lib/strtok.c
+++ b/lib/strtok.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/strtok.h b/lib/strtok.h
index 641a3da..321cba2 100644
--- a/lib/strtok.h
+++ b/lib/strtok.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/strtoofft.c b/lib/strtoofft.c
index fb8d921..077b257 100644
--- a/lib/strtoofft.c
+++ b/lib/strtoofft.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/strtoofft.h b/lib/strtoofft.h
index 311dae4..34d293b 100644
--- a/lib/strtoofft.h
+++ b/lib/strtoofft.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/system_win32.c b/lib/system_win32.c
index bede9c7..0cdaf3b 100644
--- a/lib/system_win32.c
+++ b/lib/system_win32.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2016 - 2022, Steve Holme, <steve_holme@hotmail.com>.
+ * Copyright (C) Steve Holme, <steve_holme@hotmail.com>.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/lib/system_win32.h b/lib/system_win32.h
index 167804e..24899cb 100644
--- a/lib/system_win32.h
+++ b/lib/system_win32.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2016 - 2022, Steve Holme, <steve_holme@hotmail.com>.
+ * Copyright (C) Steve Holme, <steve_holme@hotmail.com>.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/lib/telnet.c b/lib/telnet.c
index 22bc81e..a964bfd 100644
--- a/lib/telnet.c
+++ b/lib/telnet.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/telnet.h b/lib/telnet.h
index 6dd99b4..30782d8 100644
--- a/lib/telnet.h
+++ b/lib/telnet.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/tftp.c b/lib/tftp.c
index 9e6d949..164d3c7 100644
--- a/lib/tftp.c
+++ b/lib/tftp.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -48,6 +48,7 @@
 
 #include "urldata.h"
 #include <curl/curl.h>
+#include "cf-socket.h"
 #include "transfer.h"
 #include "sendf.h"
 #include "tftp.h"
@@ -529,8 +530,8 @@ static CURLcode tftp_send_first(struct tftp_state_data *state,
        not have a size_t argument, like older unixes that want an 'int' */
     senddata = sendto(state->sockfd, (void *)state->spacket.data,
                       (SEND_TYPE_ARG3)sbytes, 0,
-                      data->conn->ip_addr->ai_addr,
-                      data->conn->ip_addr->ai_addrlen);
+                      &data->conn->remote_addr->sa_addr,
+                      data->conn->remote_addr->addrlen);
     if(senddata != (ssize_t)sbytes) {
       char buffer[STRERROR_LEN];
       failf(data, "%s", Curl_strerror(SOCKERRNO, buffer, sizeof(buffer)));
@@ -1014,7 +1015,7 @@ static CURLcode tftp_connect(struct Curl_easy *data, bool *done)
   state->requested_blksize = blksize;
 
   ((struct sockaddr *)&state->local_addr)->sa_family =
-    (CURL_SA_FAMILY_T)(conn->ip_addr->ai_family);
+    (CURL_SA_FAMILY_T)(conn->remote_addr->family);
 
   tftp_set_timeouts(state);
 
@@ -1033,7 +1034,7 @@ static CURLcode tftp_connect(struct Curl_easy *data, bool *done)
      * IPv4 and IPv6...
      */
     int rc = bind(state->sockfd, (struct sockaddr *)&state->local_addr,
-                  conn->ip_addr->ai_addrlen);
+                  conn->remote_addr->addrlen);
     if(rc) {
       char buffer[STRERROR_LEN];
       failf(data, "bind() failed; %s",
diff --git a/lib/tftp.h b/lib/tftp.h
index 3f1fda6..5d2d5da 100644
--- a/lib/tftp.h
+++ b/lib/tftp.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/timediff.c b/lib/timediff.c
index c589318..1b762bb 100644
--- a/lib/timediff.c
+++ b/lib/timediff.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/timediff.h b/lib/timediff.h
index 90e5474..fb318d4 100644
--- a/lib/timediff.h
+++ b/lib/timediff.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/timeval.c b/lib/timeval.c
index 647d7b0..dca1c6f 100644
--- a/lib/timeval.c
+++ b/lib/timeval.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/timeval.h b/lib/timeval.h
index 8d4fef4..92e484a 100644
--- a/lib/timeval.h
+++ b/lib/timeval.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/transfer.c b/lib/transfer.c
index ba0410f..69df214 100644
--- a/lib/transfer.c
+++ b/lib/transfer.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -73,6 +73,7 @@
 #include "url.h"
 #include "getinfo.h"
 #include "vtls/vtls.h"
+#include "vquic/vquic.h"
 #include "select.h"
 #include "multiif.h"
 #include "connect.h"
@@ -367,27 +368,12 @@ static int data_pending(struct Curl_easy *data)
 {
   struct connectdata *conn = data->conn;
 
-#ifdef ENABLE_QUIC
-  if(conn->transport == TRNSPRT_QUIC)
-    return Curl_quic_data_pending(data);
-#endif
-
   if(conn->handler->protocol&PROTO_FAMILY_FTP)
     return Curl_conn_data_pending(data, SECONDARYSOCKET);
 
   /* in the case of libssh2, we can never be really sure that we have emptied
      its internal buffers so we MUST always try until we get EAGAIN back */
   return conn->handler->protocol&(CURLPROTO_SCP|CURLPROTO_SFTP) ||
-#ifdef USE_NGHTTP2
-    /* For HTTP/2, we may read up everything including response body
-       with header fields in Curl_http_readwrite_headers. If no
-       content-length is provided, curl waits for the connection
-       close, which we emulate it using conn->proto.httpc.closed =
-       TRUE. The thing is if we read everything, then http2_recv won't
-       be called and we cannot signal the HTTP/2 stream has closed. As
-       a workaround, we return nonzero here to call http2_recv. */
-    ((conn->handler->protocol&PROTO_FAMILY_HTTP) && conn->httpversion >= 20) ||
-#endif
     Curl_conn_data_pending(data, FIRSTSOCKET);
 }
 
@@ -454,29 +440,16 @@ static CURLcode readwrite_data(struct Curl_easy *data,
     bool is_empty_data = FALSE;
     size_t buffersize = data->set.buffer_size;
     size_t bytestoread = buffersize;
-#ifdef USE_NGHTTP2
-    bool is_http2 = ((conn->handler->protocol & PROTO_FAMILY_HTTP) &&
-                     (conn->httpversion == 20));
-#endif
-    bool is_http3 =
-#ifdef ENABLE_QUIC
-      ((conn->handler->protocol & PROTO_FAMILY_HTTP) &&
-       (conn->httpversion == 30));
-#else
-      FALSE;
-#endif
-
-    if(
-#ifdef USE_NGHTTP2
-      /* For HTTP/2, read data without caring about the content length. This
-         is safe because body in HTTP/2 is always segmented thanks to its
-         framing layer. Meanwhile, we have to call Curl_read to ensure that
-         http2_handle_stream_close is called when we read all incoming bytes
-         for a particular stream. */
-      !is_http2 &&
-#endif
-      !is_http3 && /* Same reason mentioned above. */
-      k->size != -1 && !k->header) {
+    /* For HTTP/2 and HTTP/3, read data without caring about the content
+       length. This is safe because body in HTTP/2 is always segmented
+       thanks to its framing layer. Meanwhile, we have to call Curl_read
+       to ensure that http2_handle_stream_close is called when we read all
+       incoming bytes for a particular stream. */
+    bool is_http3 = Curl_conn_is_http3(data, conn, FIRSTSOCKET);
+    bool data_eof_handled = is_http3
+                            || Curl_conn_is_http2(data, conn, FIRSTSOCKET);
+
+    if(!data_eof_handled && k->size != -1 && !k->header) {
       /* make sure we don't read too much */
       curl_off_t totalleft = k->size - k->bytecount;
       if(totalleft < (curl_off_t)bytestoread)
@@ -499,7 +472,7 @@ static CURLcode readwrite_data(struct Curl_easy *data,
     else {
       /* read nothing but since we wanted nothing we consider this an OK
          situation to proceed from */
-      DEBUGF(infof(data, "readwrite_data: we're done"));
+      DEBUGF(infof(data, DMSG(data, "readwrite_data: we're done")));
       nread = 0;
     }
 
@@ -518,14 +491,9 @@ static CURLcode readwrite_data(struct Curl_easy *data,
       buf[nread] = 0;
     }
     else {
-      /* if we receive 0 or less here, either the http2 stream is closed or the
+      /* if we receive 0 or less here, either the data transfer is done or the
          server closed the connection and we bail out from this! */
-#ifdef USE_NGHTTP2
-      if(is_http2 && !nread)
-        DEBUGF(infof(data, "nread == 0, stream closed, bailing"));
-      else
-#endif
-      if(is_http3 && !nread)
+      if(data_eof_handled)
         DEBUGF(infof(data, "nread == 0, stream closed, bailing"));
       else
         DEBUGF(infof(data, "nread <= 0, server closed connection, bailing"));
@@ -776,8 +744,8 @@ static CURLcode readwrite_data(struct Curl_easy *data,
       k->keepon &= ~KEEP_RECV;
     }
 
-    if(k->keepon & KEEP_RECV_PAUSE) {
-      /* this is a paused transfer */
+    if((k->keepon & KEEP_RECV_PAUSE) || !(k->keepon & KEEP_RECV)) {
+      /* this is a paused or stopped transfer */
       break;
     }
 
@@ -799,19 +767,18 @@ static CURLcode readwrite_data(struct Curl_easy *data,
   }
 
 out:
-  DEBUGF(infof(data, "readwrite_data(handle=%p) -> %d", data, result));
+  if(result)
+    DEBUGF(infof(data, DMSG(data, "readwrite_data() -> %d"), result));
   return result;
 }
 
 CURLcode Curl_done_sending(struct Curl_easy *data,
                            struct SingleRequest *k)
 {
-  struct connectdata *conn = data->conn;
   k->keepon &= ~KEEP_SEND; /* we're done writing */
 
   /* These functions should be moved into the handler struct! */
-  Curl_http2_done_sending(data, conn);
-  Curl_quic_done_sending(data);
+  Curl_conn_ev_data_done_send(data);
 
   return CURLE_OK;
 }
@@ -1088,6 +1055,7 @@ CURLcode Curl_readwrite(struct connectdata *conn,
 {
   struct SingleRequest *k = &data->req;
   CURLcode result;
+  struct curltime now;
   int didwhat = 0;
 
   curl_socket_t fd_read;
@@ -1113,6 +1081,8 @@ CURLcode Curl_readwrite(struct connectdata *conn,
   if(data->state.drain) {
     select_res |= CURL_CSELECT_IN;
     DEBUGF(infof(data, "Curl_readwrite: forcibly told to drain data"));
+    if((k->keepon & KEEP_SENDBITS) == KEEP_SEND)
+      select_res |= CURL_CSELECT_OUT;
   }
 #endif
 
@@ -1155,7 +1125,7 @@ CURLcode Curl_readwrite(struct connectdata *conn,
   }
 #endif
 
-  k->now = Curl_now();
+  now = Curl_now();
   if(!didwhat) {
     /* no read no write, this is a timeout? */
     if(k->exp100 == EXP100_AWAITING_CONTINUE) {
@@ -1172,7 +1142,7 @@ CURLcode Curl_readwrite(struct connectdata *conn,
 
       */
 
-      timediff_t ms = Curl_timediff(k->now, k->start100);
+      timediff_t ms = Curl_timediff(now, k->start100);
       if(ms >= data->set.expect_100_timeout) {
         /* we've waited long enough, continue anyway */
         k->exp100 = EXP100_SEND_DATA;
@@ -1182,35 +1152,31 @@ CURLcode Curl_readwrite(struct connectdata *conn,
       }
     }
 
-#ifdef ENABLE_QUIC
-    if(conn->transport == TRNSPRT_QUIC) {
-      result = Curl_quic_idle(data);
-      if(result)
-        goto out;
-    }
-#endif
+    result = Curl_conn_ev_data_idle(data);
+    if(result)
+      goto out;
   }
 
   if(Curl_pgrsUpdate(data))
     result = CURLE_ABORTED_BY_CALLBACK;
   else
-    result = Curl_speedcheck(data, k->now);
+    result = Curl_speedcheck(data, now);
   if(result)
     goto out;
 
   if(k->keepon) {
-    if(0 > Curl_timeleft(data, &k->now, FALSE)) {
+    if(0 > Curl_timeleft(data, &now, FALSE)) {
       if(k->size != -1) {
         failf(data, "Operation timed out after %" CURL_FORMAT_TIMEDIFF_T
               " milliseconds with %" CURL_FORMAT_CURL_OFF_T " out of %"
               CURL_FORMAT_CURL_OFF_T " bytes received",
-              Curl_timediff(k->now, data->progress.t_startsingle),
+              Curl_timediff(now, data->progress.t_startsingle),
               k->bytecount, k->size);
       }
       else {
         failf(data, "Operation timed out after %" CURL_FORMAT_TIMEDIFF_T
               " milliseconds with %" CURL_FORMAT_CURL_OFF_T " bytes received",
-              Curl_timediff(k->now, data->progress.t_startsingle),
+              Curl_timediff(now, data->progress.t_startsingle),
               k->bytecount);
       }
       result = CURLE_OPERATION_TIMEDOUT;
@@ -1264,7 +1230,8 @@ CURLcode Curl_readwrite(struct connectdata *conn,
                             KEEP_RECV_PAUSE|KEEP_SEND_PAUSE))) ? TRUE : FALSE;
   result = CURLE_OK;
 out:
-  DEBUGF(infof(data, "Curl_readwrite(handle=%p) -> %d", data, result));
+  if(result)
+    DEBUGF(infof(data, DMSG(data, "Curl_readwrite() -> %d"), result));
   return result;
 }
 
@@ -1377,6 +1344,7 @@ CURLcode Curl_pretransfer(struct Curl_easy *data)
   data->state.authhost.want = data->set.httpauth;
   data->state.authproxy.want = data->set.proxyauth;
   Curl_safefree(data->info.wouldredirect);
+  Curl_data_priority_clear_state(data);
 
   if(data->state.httpreq == HTTPREQ_PUT)
     data->state.infilesize = data->set.filesize;
@@ -1389,15 +1357,16 @@ CURLcode Curl_pretransfer(struct Curl_easy *data)
   else
     data->state.infilesize = 0;
 
-#ifndef CURL_DISABLE_COOKIES
   /* If there is a list of cookie files to read, do it now! */
-  if(data->state.cookielist)
-    Curl_cookie_loadfiles(data);
-#endif
+  Curl_cookie_loadfiles(data);
+
   /* If there is a list of host pairs to deal with */
   if(data->state.resolve)
     result = Curl_loadhostpairs(data);
 
+  /* If there is a list of hsts files to read */
+  Curl_hsts_loadfiles(data);
+
   if(!result) {
     /* Allow data->set.use_port to set which port to use. This needs to be
      * disabled for example when we follow Location: headers to URLs using
@@ -1433,7 +1402,6 @@ CURLcode Curl_pretransfer(struct Curl_easy *data)
       }
     }
 #endif
-    Curl_http2_init_state(&data->state);
     result = Curl_hsts_loadcb(data, data->hsts);
   }
 
@@ -1871,7 +1839,7 @@ Curl_setup_transfer(
   httpsending = ((conn->handler->protocol&PROTO_FAMILY_HTTP) &&
                  (http->sending == HTTPSEND_REQUEST));
 
-  if(conn->bits.multiplex || conn->httpversion == 20 || httpsending) {
+  if(conn->bits.multiplex || conn->httpversion >= 20 || httpsending) {
     /* when multiplexing, the read/write sockets need to be the same! */
     conn->sockfd = sockindex == -1 ?
       ((writesockindex == -1 ? CURL_SOCKET_BAD : conn->sock[writesockindex])) :
diff --git a/lib/transfer.h b/lib/transfer.h
index 4092508..536ac24 100644
--- a/lib/transfer.h
+++ b/lib/transfer.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/url.c b/lib/url.c
index 3ab63a0..1bb93df 100644
--- a/lib/url.c
+++ b/lib/url.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -431,10 +431,17 @@ CURLcode Curl_close(struct Curl_easy **datap)
   Curl_dyn_free(&data->state.headerb);
   Curl_safefree(data->state.ulbuf);
   Curl_flush_cookies(data, TRUE);
+#ifndef CURL_DISABLE_COOKIES
+  curl_slist_free_all(data->set.cookielist); /* clean up list */
+#endif
   Curl_altsvc_save(data, data->asi, data->set.str[STRING_ALTSVC]);
   Curl_altsvc_cleanup(&data->asi);
   Curl_hsts_save(data, data->hsts, data->set.str[STRING_HSTS]);
-  Curl_hsts_cleanup(&data->hsts);
+#ifndef CURL_DISABLE_HSTS
+  if(!data->share || !data->share->hsts)
+    Curl_hsts_cleanup(&data->hsts);
+  curl_slist_free_all(data->set.hstslist); /* clean up list */
+#endif
 #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_CRYPTO_AUTH)
   Curl_http_auth_cleanup_digest(data);
 #endif
@@ -445,7 +452,7 @@ CURLcode Curl_close(struct Curl_easy **datap)
   Curl_resolver_cancel(data);
   Curl_resolver_cleanup(data->state.async.resolver);
 
-  Curl_http2_cleanup_dependencies(data);
+  Curl_data_priority_cleanup(data);
 
   /* No longer a dirty share, if it exists */
   if(data->share) {
@@ -531,11 +538,11 @@ CURLcode Curl_init_userdefined(struct Curl_easy *data)
   /* Timeout every 24 hours by default */
   set->general_ssl.ca_cache_timeout = 24 * 60 * 60;
 
-  set->proxyport = 0;
-  set->proxytype = CURLPROXY_HTTP; /* defaults to HTTP proxy */
   set->httpauth = CURLAUTH_BASIC;  /* defaults to basic */
 
 #ifndef CURL_DISABLE_PROXY
+  set->proxyport = 0;
+  set->proxytype = CURLPROXY_HTTP; /* defaults to HTTP proxy */
   set->proxyauth = CURLAUTH_BASIC; /* defaults to basic */
   /* SOCKS5 proxy auth defaults to username/password + GSS-API */
   set->socks5auth = CURLAUTH_BASIC | CURLAUTH_GSSAPI;
@@ -556,11 +563,11 @@ CURLcode Curl_init_userdefined(struct Curl_easy *data)
 #endif
   set->ssl.primary.verifypeer = TRUE;
   set->ssl.primary.verifyhost = TRUE;
-#ifdef USE_TLS_SRP
-  set->ssl.primary.authtype = CURL_TLSAUTH_NONE;
-#endif
-   /* defaults to any auth type */
+#ifdef USE_SSH
+  /* defaults to any auth type */
   set->ssh_auth_types = CURLSSH_AUTH_DEFAULT;
+  set->new_directory_perms = 0755; /* Default permissions */
+#endif
   set->ssl.primary.sessionid = TRUE; /* session ID caching enabled by
                                         default */
 #ifndef CURL_DISABLE_PROXY
@@ -568,7 +575,6 @@ CURLcode Curl_init_userdefined(struct Curl_easy *data)
 #endif
 
   set->new_file_perms = 0644;    /* Default permissions */
-  set->new_directory_perms = 0755; /* Default permissions */
   set->allowed_protocols = (curl_prot_t) CURLPROTO_ALL;
   set->redir_protocols = CURLPROTO_HTTP | CURLPROTO_HTTPS | CURLPROTO_FTP |
                          CURLPROTO_FTPS;
@@ -631,14 +637,15 @@ CURLcode Curl_init_userdefined(struct Curl_easy *data)
   set->maxage_conn = 118;
   set->maxlifetime_conn = 0;
   set->http09_allowed = FALSE;
-  set->httpwant =
 #ifdef USE_HTTP2
-    CURL_HTTP_VERSION_2TLS
+  set->httpwant = CURL_HTTP_VERSION_2TLS
 #else
-    CURL_HTTP_VERSION_1_1
+  set->httpwant = CURL_HTTP_VERSION_1_1
 #endif
     ;
-  Curl_http2_init_userset(set);
+#if defined(USE_HTTP2) || defined(USE_HTTP3)
+  memset(&set->priority, 0, sizeof(set->priority));
+#endif
   set->quick_exit = 0L;
   return result;
 }
@@ -698,45 +705,6 @@ CURLcode Curl_open(struct Curl_easy **curl)
   return result;
 }
 
-#ifdef USE_RECV_BEFORE_SEND_WORKAROUND
-static void conn_reset_postponed_data(struct connectdata *conn, int num)
-{
-  struct postponed_data * const psnd = &(conn->postponed[num]);
-  if(psnd->buffer) {
-    DEBUGASSERT(psnd->allocated_size > 0);
-    DEBUGASSERT(psnd->recv_size <= psnd->allocated_size);
-    DEBUGASSERT(psnd->recv_size ?
-                (psnd->recv_processed < psnd->recv_size) :
-                (psnd->recv_processed == 0));
-    DEBUGASSERT(psnd->bindsock != CURL_SOCKET_BAD);
-    free(psnd->buffer);
-    psnd->buffer = NULL;
-    psnd->allocated_size = 0;
-    psnd->recv_size = 0;
-    psnd->recv_processed = 0;
-#ifdef DEBUGBUILD
-    psnd->bindsock = CURL_SOCKET_BAD; /* used only for DEBUGASSERT */
-#endif /* DEBUGBUILD */
-  }
-  else {
-    DEBUGASSERT(psnd->allocated_size == 0);
-    DEBUGASSERT(psnd->recv_size == 0);
-    DEBUGASSERT(psnd->recv_processed == 0);
-    DEBUGASSERT(psnd->bindsock == CURL_SOCKET_BAD);
-  }
-}
-
-static void conn_reset_all_postponed_data(struct connectdata *conn)
-{
-  conn_reset_postponed_data(conn, 0);
-  conn_reset_postponed_data(conn, 1);
-}
-#else  /* ! USE_RECV_BEFORE_SEND_WORKAROUND */
-/* Use "do-nothing" macro instead of function when workaround not used */
-#define conn_reset_all_postponed_data(c) do {} while(0)
-#endif /* ! USE_RECV_BEFORE_SEND_WORKAROUND */
-
-
 static void conn_shutdown(struct Curl_easy *data)
 {
   DEBUGASSERT(data);
@@ -777,13 +745,14 @@ static void conn_free(struct Curl_easy *data, struct connectdata *conn)
   Curl_safefree(conn->sasl_authzid);
   Curl_safefree(conn->options);
   Curl_safefree(conn->oauth_bearer);
+#ifndef CURL_DISABLE_HTTP
   Curl_dyn_free(&conn->trailer);
+#endif
   Curl_safefree(conn->host.rawalloc); /* host name buffer */
   Curl_safefree(conn->conn_to_host.rawalloc); /* host name buffer */
   Curl_safefree(conn->hostname_resolve);
   Curl_safefree(conn->secondaryhostname);
 
-  conn_reset_all_postponed_data(conn);
   Curl_llist_destroy(&conn->easyq, NULL);
   Curl_safefree(conn->localdev);
   Curl_free_primary_ssl_config(&conn->ssl_config);
@@ -867,24 +836,6 @@ void Curl_disconnect(struct Curl_easy *data,
 }
 
 /*
- * This function should return TRUE if the socket is to be assumed to
- * be dead. Most commonly this happens when the server has closed the
- * connection due to inactivity.
- */
-static bool SocketIsDead(curl_socket_t sock)
-{
-  int sval;
-  bool ret_val = TRUE;
-
-  sval = SOCKET_READABLE(sock, 0);
-  if(sval == 0)
-    /* timeout */
-    ret_val = FALSE;
-
-  return ret_val;
-}
-
-/*
  * IsMultiplexingPossible()
  *
  * Return a bitmask with the available multiplexing options for the given
@@ -1014,8 +965,7 @@ static bool extract_if_dead(struct connectdata *conn,
 
     }
     else {
-      /* Use the general method for determining the death of a connection */
-      dead = SocketIsDead(conn->sock[FIRSTSOCKET]);
+      dead = !Curl_conn_is_alive(data, conn);
     }
 
     if(dead) {
@@ -1344,8 +1294,10 @@ ConnectionExists(struct Curl_easy *data,
       /* If multiplexing isn't enabled on the h2 connection and h1 is
          explicitly requested, handle it: */
       if((needle->handler->protocol & PROTO_FAMILY_HTTP) &&
-         (check->httpversion >= 20) &&
-         (data->state.httpwant < CURL_HTTP_VERSION_2_0))
+         (((check->httpversion >= 20) &&
+           (data->state.httpwant < CURL_HTTP_VERSION_2_0))
+          || ((check->httpversion >= 30) &&
+           (data->state.httpwant < CURL_HTTP_VERSION_3))))
         continue;
 
       if(get_protocol_family(needle->handler) == PROTO_FAMILY_SSH) {
@@ -1467,9 +1419,8 @@ ConnectionExists(struct Curl_easy *data,
 #ifdef USE_NGHTTP2
           /* If multiplexed, make sure we don't go over concurrency limit */
           if(check->bits.multiplex) {
-            /* Multiplexed connections can only be HTTP/2 for now */
-            struct http_conn *httpc = &check->proto.httpc;
-            if(multiplexed >= httpc->settings.max_concurrent_streams) {
+            if(multiplexed >= Curl_conn_get_max_concurrent(data, check,
+                                                           FIRSTSOCKET)) {
               infof(data, "MAX_CONCURRENT_STREAMS reached, skip (%zu)",
                     multiplexed);
               continue;
@@ -1551,15 +1502,9 @@ static struct connectdata *allocate_conn(struct Curl_easy *data)
 
   conn->sock[FIRSTSOCKET] = CURL_SOCKET_BAD;     /* no file descriptor */
   conn->sock[SECONDARYSOCKET] = CURL_SOCKET_BAD; /* no file descriptor */
-  conn->tempsock[0] = CURL_SOCKET_BAD; /* no file descriptor */
-  conn->tempsock[1] = CURL_SOCKET_BAD; /* no file descriptor */
   conn->connection_id = -1;    /* no ID */
   conn->port = -1; /* unknown at this point */
   conn->remote_port = -1; /* unknown at this point */
-#if defined(USE_RECV_BEFORE_SEND_WORKAROUND) && defined(DEBUGBUILD)
-  conn->postponed[0].bindsock = CURL_SOCKET_BAD; /* no file descriptor */
-  conn->postponed[1].bindsock = CURL_SOCKET_BAD; /* no file descriptor */
-#endif /* USE_RECV_BEFORE_SEND_WORKAROUND && DEBUGBUILD */
 
   /* Default protocol-independent behavior doesn't support persistent
      connections, so we set this to force-close. Protocols that support
@@ -2329,7 +2274,7 @@ static CURLcode parse_proxy(struct Curl_easy *data,
       result = CURLE_OUT_OF_MEMORY;
       goto error;
     }
-    /* path will be "/", if no path was was found */
+    /* path will be "/", if no path was found */
     if(strcmp("/", path)) {
       is_unix_proxy = TRUE;
       free(host);
@@ -2412,6 +2357,7 @@ static CURLcode create_conn_helper_init_proxy(struct Curl_easy *data,
   char *socksproxy = NULL;
   char *no_proxy = NULL;
   CURLcode result = CURLE_OK;
+  bool spacesep = FALSE;
 
   /*************************************************************
    * Extract the user and password from the authentication string
@@ -2458,7 +2404,8 @@ static CURLcode create_conn_helper_init_proxy(struct Curl_easy *data,
   }
 
   if(Curl_check_noproxy(conn->host.name, data->set.str[STRING_NOPROXY] ?
-                        data->set.str[STRING_NOPROXY] : no_proxy)) {
+                        data->set.str[STRING_NOPROXY] : no_proxy,
+                        &spacesep)) {
     Curl_safefree(proxy);
     Curl_safefree(socksproxy);
   }
@@ -2467,6 +2414,8 @@ static CURLcode create_conn_helper_init_proxy(struct Curl_easy *data,
     /* if the host is not in the noproxy list, detect proxy. */
     proxy = detect_proxy(data, conn);
 #endif /* CURL_DISABLE_HTTP */
+  if(spacesep)
+    infof(data, "space-separated NOPROXY patterns are deprecated");
 
   Curl_safefree(no_proxy);
 
@@ -2795,7 +2744,7 @@ static CURLcode override_login(struct Curl_easy *data,
         return CURLE_OUT_OF_MEMORY;
     }
     /* no user was set but a password, set a blank user */
-    if(userp && !*userp && *passwdp) {
+    if(!*userp && *passwdp) {
       *userp = strdup("");
       if(!*userp)
         return CURLE_OUT_OF_MEMORY;
@@ -3345,12 +3294,6 @@ static void reuse_conn(struct Curl_easy *data,
                        struct connectdata *temp,
                        struct connectdata *existing)
 {
-  /* 'local_ip' and 'local_port' get filled with local's numerical
-     ip address and port number whenever an outgoing connection is
-     **established** from the primary socket to a remote address. */
-  char local_ip[MAX_IPADR_LEN] = "";
-  int local_port = -1;
-
   /* get the user+password information from the temp struct since it may
    * be new for this request even when we re-use an existing connection */
   if(temp->user) {
@@ -3382,6 +3325,20 @@ static void reuse_conn(struct Curl_easy *data,
   }
 #endif
 
+  /* Finding a connection for reuse in the cache matches, among other
+   * things on the "remote-relevant" hostname. This is not necessarily
+   * the authority of the URL, e.g. conn->host. For example:
+   * - we use a proxy (not tunneling). we want to send all requests
+   *   that use the same proxy on this connection.
+   * - we have a "connect-to" setting that may redirect the hostname of
+   *   a new request to the same remote endpoint of an existing conn.
+   *   We want to reuse an existing conn to the remote endpoint.
+   * Since connection reuse does not match on conn->host necessarily, we
+   * switch `existing` conn to `temp` conn's host settings.
+   * TODO: is this correct in the case of TLS connections that have
+   *       used the original hostname in SNI to negotiate? Do we send
+   *       requests for another host through the different SNI?
+   */
   Curl_free_idnconverted_hostname(&existing->host);
   Curl_free_idnconverted_hostname(&existing->conn_to_host);
   Curl_safefree(existing->host.rawalloc);
@@ -3398,15 +3355,6 @@ static void reuse_conn(struct Curl_easy *data,
   existing->hostname_resolve = temp->hostname_resolve;
   temp->hostname_resolve = NULL;
 
-  /* persist connection info in session handle */
-  if(existing->transport == TRNSPRT_TCP) {
-    Curl_conninfo_local(data, existing->sock[FIRSTSOCKET],
-                        local_ip, &local_port);
-  }
-  Curl_persistconninfo(data, existing, local_ip, local_port);
-
-  conn_reset_all_postponed_data(temp); /* free buffers */
-
   /* re-use init */
   existing->bits.reuse = TRUE; /* yes, we're re-using here */
 
@@ -3880,6 +3828,13 @@ static CURLcode create_conn(struct Curl_easy *data,
    * Resolve the address of the server or proxy
    *************************************************************/
   result = resolve_server(data, conn, async);
+  if(result)
+    goto out;
+
+  /* Everything general done, inform filters that they need
+   * to prepare for a data transfer.
+   */
+  result = Curl_conn_ev_data_setup(data);
 
 out:
   return result;
@@ -4007,7 +3962,6 @@ CURLcode Curl_init_do(struct Curl_easy *data, struct connectdata *conn)
     data->state.httpreq = HTTPREQ_HEAD;
 
   k->start = Curl_now(); /* start time */
-  k->now = k->start;   /* current time is now */
   k->header = TRUE; /* assume header */
   k->bytecount = 0;
   k->ignorebody = FALSE;
@@ -4018,3 +3972,103 @@ CURLcode Curl_init_do(struct Curl_easy *data, struct connectdata *conn)
 
   return CURLE_OK;
 }
+
+#if defined(USE_HTTP2) || defined(USE_HTTP3)
+
+#ifdef USE_NGHTTP2
+
+static void priority_remove_child(struct Curl_easy *parent,
+                                  struct Curl_easy *child)
+{
+  struct Curl_data_prio_node **pnext = &parent->set.priority.children;
+  struct Curl_data_prio_node *pnode = parent->set.priority.children;
+
+  DEBUGASSERT(child->set.priority.parent == parent);
+  while(pnode && pnode->data != child) {
+    pnext = &pnode->next;
+    pnode = pnode->next;
+  }
+
+  DEBUGASSERT(pnode);
+  if(pnode) {
+    *pnext = pnode->next;
+    free(pnode);
+  }
+
+  child->set.priority.parent = 0;
+  child->set.priority.exclusive = FALSE;
+}
+
+CURLcode Curl_data_priority_add_child(struct Curl_easy *parent,
+                                      struct Curl_easy *child,
+                                      bool exclusive)
+{
+  if(child->set.priority.parent) {
+    priority_remove_child(child->set.priority.parent, child);
+  }
+
+  if(parent) {
+    struct Curl_data_prio_node **tail;
+    struct Curl_data_prio_node *pnode;
+
+    pnode = calloc(1, sizeof(*pnode));
+    if(!pnode)
+      return CURLE_OUT_OF_MEMORY;
+    pnode->data = child;
+
+    if(parent->set.priority.children && exclusive) {
+      /* exclusive: move all existing children underneath the new child */
+      struct Curl_data_prio_node *node = parent->set.priority.children;
+      while(node) {
+        node->data->set.priority.parent = child;
+        node = node->next;
+      }
+
+      tail = &child->set.priority.children;
+      while(*tail)
+        tail = &(*tail)->next;
+
+      DEBUGASSERT(!*tail);
+      *tail = parent->set.priority.children;
+      parent->set.priority.children = 0;
+    }
+
+    tail = &parent->set.priority.children;
+    while(*tail) {
+      (*tail)->data->set.priority.exclusive = FALSE;
+      tail = &(*tail)->next;
+    }
+
+    DEBUGASSERT(!*tail);
+    *tail = pnode;
+  }
+
+  child->set.priority.parent = parent;
+  child->set.priority.exclusive = exclusive;
+  return CURLE_OK;
+}
+
+#endif /* USE_NGHTTP2 */
+
+void Curl_data_priority_cleanup(struct Curl_easy *data)
+{
+#ifdef USE_NGHTTP2
+  while(data->set.priority.children) {
+    struct Curl_easy *tmp = data->set.priority.children->data;
+    priority_remove_child(data, tmp);
+    if(data->set.priority.parent)
+      Curl_data_priority_add_child(data->set.priority.parent, tmp, FALSE);
+  }
+
+  if(data->set.priority.parent)
+    priority_remove_child(data->set.priority.parent, data);
+#endif
+  (void)data;
+}
+
+void Curl_data_priority_clear_state(struct Curl_easy *data)
+{
+  memset(&data->state.priority, 0, sizeof(data->state.priority));
+}
+
+#endif /* defined(USE_HTTP2) || defined(USE_HTTP3) */
diff --git a/lib/url.h b/lib/url.h
index 1a03c56..3b58df4 100644
--- a/lib/url.h
+++ b/lib/url.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -59,4 +59,20 @@ const struct Curl_handler *Curl_builtin_scheme(const char *scheme,
 void Curl_verboseconnect(struct Curl_easy *data, struct connectdata *conn);
 #endif
 
+#if defined(USE_HTTP2) || defined(USE_HTTP3)
+void Curl_data_priority_cleanup(struct Curl_easy *data);
+void Curl_data_priority_clear_state(struct Curl_easy *data);
+#else
+#define Curl_data_priority_cleanup(x)
+#define Curl_data_priority_clear_state(x)
+#endif /* !(defined(USE_HTTP2) || defined(USE_HTTP3)) */
+
+#ifdef USE_NGHTTP2
+CURLcode Curl_data_priority_add_child(struct Curl_easy *parent,
+                                      struct Curl_easy *child,
+                                      bool exclusive);
+#else
+#define Curl_data_priority_add_child(x, y, z) CURLE_NOT_BUILT_IN
+#endif
+
 #endif /* HEADER_CURL_URL_H */
diff --git a/lib/urlapi-int.h b/lib/urlapi-int.h
index 43a83ef..28e5dd7 100644
--- a/lib/urlapi-int.h
+++ b/lib/urlapi-int.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/urlapi.c b/lib/urlapi.c
index b96af35..29927b3 100644
--- a/lib/urlapi.c
+++ b/lib/urlapi.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -33,6 +33,7 @@
 #include "inet_pton.h"
 #include "inet_ntop.h"
 #include "strdup.h"
+#include "idn.h"
 
 /* The last 3 #include files should be in this order */
 #include "curl_printf.h"
@@ -116,14 +117,11 @@ static const char *find_host_sep(const char *url)
 }
 
 /*
- * Decide in an encoding-independent manner whether a character in a URL must
- * be escaped. This is used in urlencode_str().
+ * Decide whether a character in a URL must be escaped.
  */
-static bool urlchar_needs_escaping(int c)
-{
-  return !(ISCNTRL(c) || ISSPACE(c) || ISGRAPH(c));
-}
+#define urlchar_needs_escaping(c) (!(ISCNTRL(c) || ISSPACE(c) || ISGRAPH(c)))
 
+static const char hexdigits[] = "0123456789abcdef";
 /* urlencode_str() writes data into an output dynbuf and URL-encodes the
  * spaces in the source URL accordingly.
  *
@@ -167,7 +165,10 @@ static CURLUcode urlencode_str(struct dynbuf *o, const char *url,
       left = FALSE;
 
     if(urlchar_needs_escaping(*iptr)) {
-      if(Curl_dyn_addf(o, "%%%02x", *iptr))
+      char out[3]={'%'};
+      out[1] = hexdigits[*iptr>>4];
+      out[2] = hexdigits[*iptr & 0xf];
+      if(Curl_dyn_addn(o, out, 3))
         return CURLUE_OUT_OF_MEMORY;
     }
     else {
@@ -492,35 +493,21 @@ static CURLUcode parse_hostname_login(struct Curl_URL *u,
 UNITTEST CURLUcode Curl_parse_port(struct Curl_URL *u, struct dynbuf *host,
                                    bool has_scheme)
 {
-  char *portptr = NULL;
-  char endbracket;
-  int len;
+  char *portptr;
   char *hostname = Curl_dyn_ptr(host);
   /*
    * Find the end of an IPv6 address, either on the ']' ending bracket or
    * a percent-encoded zone index.
    */
-  if(1 == sscanf(hostname, "[%*45[0123456789abcdefABCDEF:.]%c%n",
-                 &endbracket, &len)) {
-    if(']' == endbracket)
-      portptr = &hostname[len];
-    else if('%' == endbracket) {
-      int zonelen = len;
-      if(1 == sscanf(hostname + zonelen, "%*[^]]%c%n", &endbracket, &len)) {
-        if(']' != endbracket)
-          return CURLUE_BAD_IPV6;
-        portptr = &hostname[--zonelen + len + 1];
-      }
-      else
-        return CURLUE_BAD_IPV6;
-    }
-    else
+  if(hostname[0] == '[') {
+    portptr = strchr(hostname, ']');
+    if(!portptr)
       return CURLUE_BAD_IPV6;
-
+    portptr++;
     /* this is a RFC2732-style specified IP-address */
-    if(portptr && *portptr) {
+    if(*portptr) {
       if(*portptr != ':')
-        return CURLUE_BAD_IPV6;
+        return CURLUE_BAD_PORT_NUMBER;
     }
     else
       portptr = NULL;
@@ -584,11 +571,9 @@ static CURLUcode hostname_check(struct Curl_URL *u, char *hostname,
     hostname++;
     hlen -= 2;
 
-    if(hostname[hlen] != ']')
-      return CURLUE_BAD_IPV6;
-
-    /* only valid letters are ok */
+    /* only valid IPv6 letters are ok */
     len = strspn(hostname, l);
+
     if(hlen != len) {
       hlen = len;
       if(hostname[len] == '%') {
@@ -602,8 +587,7 @@ static CURLUcode hostname_check(struct Curl_URL *u, char *hostname,
         while(*h && (*h != ']') && (i < 15))
           zoneid[i++] = *h++;
         if(!i || (']' != *h))
-          /* impossible to reach? */
-          return CURLUE_MALFORMED_INPUT;
+          return CURLUE_BAD_IPV6;
         zoneid[i] = 0;
         u->zoneid = strdup(zoneid);
         if(!u->zoneid)
@@ -782,25 +766,28 @@ static CURLUcode decode_host(struct dynbuf *host)
  *
  * RETURNS
  *
- * an allocated dedotdotified output string
+ * Zero for success and 'out' set to an allocated dedotdotified string.
  */
-UNITTEST char *dedotdotify(const char *input, size_t clen);
-UNITTEST char *dedotdotify(const char *input, size_t clen)
+UNITTEST int dedotdotify(const char *input, size_t clen, char **outp);
+UNITTEST int dedotdotify(const char *input, size_t clen, char **outp)
 {
-  char *out = malloc(clen + 1);
   char *outptr;
   const char *orginput = input;
   char *queryp;
+  char *out;
+
+  *outp = NULL;
+  /* the path always starts with a slash, and a slash has not dot */
+  if((clen < 2) || !memchr(input, '.', clen))
+    return 0;
+
+  out = malloc(clen + 1);
   if(!out)
-    return NULL; /* out of memory */
+    return 1; /* out of memory */
 
   *out = 0; /* null-terminates, for inputs like "./" */
   outptr = out;
 
-  if(!*input)
-    /* zero length input string, return that */
-    return out;
-
   /*
    * To handle query-parts properly, we must find it and remove it during the
    * dotdot-operation and then append it again at the end to the output
@@ -905,7 +892,8 @@ UNITTEST char *dedotdotify(const char *input, size_t clen)
     memcpy(outptr, &orginput[oindex], qlen + 1); /* include zero byte */
   }
 
-  return out;
+  *outp = out;
+  return 0; /* success */
 }
 
 static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags)
@@ -1152,7 +1140,7 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags)
     size_t qlen = strlen(query) - fraglen; /* includes '?' */
     pathlen = strlen(path) - qlen - fraglen;
     if(qlen > 1) {
-      if(qlen && (flags & CURLU_URLENCODE)) {
+      if(flags & CURLU_URLENCODE) {
         struct dynbuf enc;
         Curl_dyn_init(&enc, CURL_MAX_INPUT_LENGTH);
         /* skip the leading question mark */
@@ -1199,8 +1187,8 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags)
     path = u->path = Curl_dyn_ptr(&enc);
   }
 
-  if(!pathlen) {
-    /* there is no path left, unset */
+  if(pathlen <= 1) {
+    /* there is no path left or just the slash, unset */
     path = NULL;
   }
   else {
@@ -1224,13 +1212,16 @@ static CURLUcode parseurl(const char *url, CURLU *u, unsigned int flags)
 
     if(!(flags & CURLU_PATH_AS_IS)) {
       /* remove ../ and ./ sequences according to RFC3986 */
-      char *newp = dedotdotify((char *)path, pathlen);
-      if(!newp) {
+      char *dedot;
+      int err = dedotdotify((char *)path, pathlen, &dedot);
+      if(err) {
         result = CURLUE_OUT_OF_MEMORY;
         goto fail;
       }
-      free(u->path);
-      u->path = newp;
+      if(dedot) {
+        free(u->path);
+        u->path = dedot;
+      }
     }
   }
 
@@ -1379,6 +1370,7 @@ CURLUcode curl_url_get(CURLU *u, CURLUPart what,
   char portbuf[7];
   bool urldecode = (flags & CURLU_URLDECODE)?1:0;
   bool urlencode = (flags & CURLU_URLENCODE)?1:0;
+  bool punycode = FALSE;
   bool plusdecode = FALSE;
   (void)flags;
   if(!u)
@@ -1408,6 +1400,7 @@ CURLUcode curl_url_get(CURLU *u, CURLUPart what,
   case CURLUPART_HOST:
     ptr = u->host;
     ifmissing = CURLUE_NO_HOST;
+    punycode = (flags & CURLU_PUNYCODE)?1:0;
     break;
   case CURLUPART_ZONEID:
     ptr = u->zoneid;
@@ -1460,6 +1453,7 @@ CURLUcode curl_url_get(CURLU *u, CURLUPart what,
     char *options = u->options;
     char *port = u->port;
     char *allochost = NULL;
+    punycode = (flags & CURLU_PUNYCODE)?1:0;
     if(u->scheme && strcasecompare("file", u->scheme)) {
       url = aprintf("file://%s%s%s",
                     u->path,
@@ -1514,6 +1508,17 @@ CURLUcode curl_url_get(CURLU *u, CURLUPart what,
         if(!allochost)
           return CURLUE_OUT_OF_MEMORY;
       }
+      else if(punycode) {
+        if(!Curl_is_ASCII_name(u->host)) {
+#ifndef USE_IDN
+          return CURLUE_LACKS_IDN;
+#else
+          allochost = Curl_idn_decode(u->host);
+          if(!allochost)
+            return CURLUE_OUT_OF_MEMORY;
+#endif
+        }
+      }
       else {
         /* only encode '%' in output host name */
         char *host = u->host;
@@ -1611,6 +1616,19 @@ CURLUcode curl_url_get(CURLU *u, CURLUPart what,
       free(*part);
       *part = Curl_dyn_ptr(&enc);
     }
+    else if(punycode) {
+      if(!Curl_is_ASCII_name(u->host)) {
+#ifndef USE_IDN
+        return CURLUE_LACKS_IDN;
+#else
+        char *allochost = Curl_idn_decode(*part);
+        if(!allochost)
+          return CURLUE_OUT_OF_MEMORY;
+        free(*part);
+        *part = allochost;
+#endif
+      }
+    }
 
     return CURLUE_OK;
   }
@@ -1807,7 +1825,10 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what,
             return CURLUE_OUT_OF_MEMORY;
         }
         else {
-          result = Curl_dyn_addf(&enc, "%%%02x", *i);
+          char out[3]={'%'};
+          out[1] = hexdigits[*i>>4];
+          out[2] = hexdigits[*i & 0xf];
+          result = Curl_dyn_addn(&enc, out, 3);
           if(result)
             return CURLUE_OUT_OF_MEMORY;
         }
diff --git a/lib/urldata.h b/lib/urldata.h
index 3d7545c..4cfffa7 100644
--- a/lib/urldata.h
+++ b/lib/urldata.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -113,23 +113,6 @@ typedef unsigned int curl_prot_t;
    input easier and better. */
 #define CURL_MAX_INPUT_LENGTH 8000000
 
-/* 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, (conn)->connection_id
-#define CMSGI(c,i,msg)  \
-  "[CONN-%ld-%d] "msg, (conn)->connection_id, (i)
-#define CFMSG(cf,msg)  \
-  "[CONN-%ld-%d][CF-%s] "msg, (cf)->conn->connection_id, \
-  (cf)->sockindex, (cf)->cft->name
-
 
 #include "cookie.h"
 #include "psl.h"
@@ -187,8 +170,8 @@ typedef CURLcode (*Curl_datastream)(struct Curl_easy *data,
 #include "mqtt.h"
 #include "wildcard.h"
 #include "multihandle.h"
-#include "quic.h"
 #include "c-hyper.h"
+#include "cf-socket.h"
 
 #ifdef HAVE_GSSAPI
 # ifdef HAVE_GSSGNU
@@ -268,8 +251,6 @@ typedef enum {
 struct ssl_backend_data;
 
 struct ssl_primary_config {
-  long version;          /* what version the client wants to use */
-  long version_max;      /* max supported version the client wants to use */
   char *CApath;          /* certificate dir (doesn't work on windows) */
   char *CAfile;          /* certificate to verify peer against */
   char *issuercert;      /* optional issuer certificate filename */
@@ -284,10 +265,11 @@ struct ssl_primary_config {
 #ifdef USE_TLS_SRP
   char *username; /* TLS username (for, e.g., SRP) */
   char *password; /* TLS password (for, e.g., SRP) */
-  enum CURL_TLSAUTH authtype; /* TLS authentication type (default SRP) */
 #endif
   char *curves;          /* list of curves to use */
   unsigned char ssl_options;  /* the CURLOPT_SSL_OPTIONS bitmask */
+  unsigned int version_max; /* max supported version the client wants to use */
+  unsigned char version;    /* what version the client wants to use */
   BIT(verifypeer);       /* set TRUE if this is desired */
   BIT(verifyhost);       /* set TRUE if CN/SAN must match hostname */
   BIT(verifystatus);     /* set TRUE if certificate status must be checked */
@@ -648,7 +630,6 @@ struct SingleRequest {
   curl_off_t pendingheader;      /* this many bytes left to send is actually
                                     header and not body */
   struct curltime start;         /* transfer started at this time */
-  struct curltime now;           /* current time */
   enum {
     HEADER_NORMAL,              /* no bad header at all */
     HEADER_PARTHEADER,          /* part of the chunk is a bad header, the rest
@@ -707,6 +688,7 @@ struct SingleRequest {
   struct dohdata *doh; /* DoH specific data for this request */
 #endif
   unsigned char setcookies;
+  unsigned char writer_stack_depth; /* Unencoding stack depth. */
   BIT(header);        /* incoming data has HTTP header */
   BIT(content_range); /* set TRUE if Content-Range: was found */
   BIT(upload_done);   /* set to TRUE when doing chunked transfer-encoding
@@ -783,8 +765,8 @@ struct Curl_handler {
   /* This function *MAY* be set to a protocol-dependent function that is run
    * by the curl_disconnect(), as a step in the disconnection.  If the handler
    * is called because the connection has been considered dead,
-   * dead_connection is set to TRUE. The connection is already disassociated
-   * from the transfer here.
+   * dead_connection is set to TRUE. The connection is (again) associated with
+   * the transfer here.
    */
   CURLcode (*disconnect)(struct Curl_easy *, struct connectdata *,
                          bool dead_connection);
@@ -830,7 +812,7 @@ struct Curl_handler {
 #define PROTOPT_CREDSPERREQUEST (1<<7) /* requires login credentials per
                                           request instead of per connection */
 #define PROTOPT_ALPN (1<<8) /* set ALPN for this */
-#define PROTOPT_STREAM (1<<9) /* a protocol with individual logical streams */
+/* (1<<9) was PROTOPT_STREAM, now free */
 #define PROTOPT_URLOPTIONS (1<<10) /* allow options part in the userinfo field
                                       of the URL */
 #define PROTOPT_PROXY_AS_HTTP (1<<11) /* allow this non-HTTP scheme over a
@@ -848,20 +830,6 @@ struct Curl_handler {
 #define CONNRESULT_NONE 0                /* No extra information. */
 #define CONNRESULT_DEAD (1<<0)           /* The connection is dead. */
 
-#ifdef USE_RECV_BEFORE_SEND_WORKAROUND
-struct postponed_data {
-  char *buffer;          /* Temporal store for received data during
-                            sending, must be freed */
-  size_t allocated_size; /* Size of temporal store */
-  size_t recv_size;      /* Size of received data during sending */
-  size_t recv_processed; /* Size of processed part of postponed data */
-#ifdef DEBUGBUILD
-  curl_socket_t bindsock;/* Structure must be bound to specific socket,
-                            used only for DEBUGASSERT */
-#endif /* DEBUGBUILD */
-};
-#endif /* USE_RECV_BEFORE_SEND_WORKAROUND */
-
 struct proxy_info {
   struct hostname host;
   int port;
@@ -909,16 +877,9 @@ struct connectdata {
      there is no name resolve done. */
   struct Curl_dns_entry *dns_entry;
 
-  /* 'ip_addr' is the particular IP we connected to. It points to a struct
-     within the DNS cache, so this pointer is only valid as long as the DNS
-     cache entry remains locked. It gets unlocked in multi_done() */
-  struct Curl_addrinfo *ip_addr;
-  struct Curl_addrinfo *tempaddr[2]; /* for happy eyeballs */
-
-#ifdef ENABLE_QUIC
-  struct quicsocket hequic[2]; /* two, for happy eyeballs! */
-  struct quicsocket *quic;
-#endif
+  /* 'remote_addr' is the particular IP we connected to. it is owned, set
+   * and NULLed by the connected socket filter (if there is one). */
+  const struct Curl_sockaddr_ex *remote_addr;
 
   struct hostname host;
   char *hostname_resolve; /* host name to resolve to address, allocated */
@@ -947,31 +908,16 @@ struct connectdata {
   struct curltime lastused; /* when returned to the connection cache */
   curl_socket_t sock[2]; /* two sockets, the second is used for the data
                             transfer when doing FTP */
-  curl_socket_t tempsock[2]; /* temporary sockets for happy eyeballs */
-  int tempfamily[2]; /* family used for the temp sockets */
   Curl_recv *recv[2];
   Curl_send *send[2];
   struct Curl_cfilter *cfilter[2]; /* connection filters */
 
-#ifdef USE_RECV_BEFORE_SEND_WORKAROUND
-  struct postponed_data postponed[2]; /* two buffers for two sockets */
-#endif /* USE_RECV_BEFORE_SEND_WORKAROUND */
   struct ssl_primary_config ssl_config;
 #ifndef CURL_DISABLE_PROXY
   struct ssl_primary_config proxy_ssl_config;
 #endif
   struct ConnectBits bits;    /* various state-flags for this connection */
 
- /* connecttime: when connect() is called on the current IP address. Used to
-    be able to track when to move on to try next IP - but only when the multi
-    interface is used. */
-  struct curltime connecttime;
-
-  /* The field below gets set in Curl_connecthost */
-  /* how long time in milliseconds to spend on trying to connect to each IP
-     address, per family */
-  timediff_t timeoutms_per_addr[2];
-
   const struct Curl_handler *handler; /* Connection's protocol handler */
   const struct Curl_handler *given;   /* The protocol first given */
 
@@ -1034,16 +980,15 @@ struct connectdata {
   struct negotiatedata proxyneg; /* state data for proxy Negotiate auth */
 #endif
 
+#ifndef CURL_DISABLE_HTTP
   /* for chunked-encoded trailer */
   struct dynbuf trailer;
+#endif
 
   union {
 #ifndef CURL_DISABLE_FTP
     struct ftp_conn ftpc;
 #endif
-#ifndef CURL_DISABLE_HTTP
-    struct http_conn httpc;
-#endif
 #ifdef USE_SSH
     struct ssh_conn sshc;
 #endif
@@ -1070,6 +1015,9 @@ struct connectdata {
 #ifndef CURL_DISABLE_MQTT
     struct mqtt_conn mqtt;
 #endif
+#ifdef USE_WEBSOCKETS
+    struct ws_conn ws;
+#endif
   } proto;
 
   struct connectbundle *bundle; /* The bundle we are member of */
@@ -1086,14 +1034,13 @@ struct connectdata {
      that subsequent bound-requested connections aren't accidentally re-using
      wrong connections. */
   char *localdev;
-  int localportrange;
+  unsigned short localportrange;
   int cselect_bits; /* bitmask of socket events */
   int waitfor;      /* current READ/WRITE bits to wait for */
 #if defined(HAVE_GSSAPI) || defined(USE_WINDOWS_SSPI)
   int socks5_gssapi_enctype;
 #endif
-  /* The field below gets set in Curl_connecthost */
-  int num_addr; /* number of addresses to try to connect to */
+  /* The field below gets set in connect.c:connecthost() */
   int port;        /* which port to use locally - to connect to */
   int remote_port; /* the remote port, not the proxy port! */
   int conn_to_port; /* the remote port to connect to. valid only if
@@ -1238,10 +1185,29 @@ struct auth {
                    should be RFC compliant */
 };
 
-struct Curl_http2_dep {
-  struct Curl_http2_dep *next;
+#ifdef USE_NGHTTP2
+struct Curl_data_prio_node {
+  struct Curl_data_prio_node *next;
   struct Curl_easy *data;
 };
+#endif
+
+/**
+ * Priority information for an easy handle in relation to others
+ * on the same connection.
+ * TODO: we need to adapt it to the new priority scheme as defined in RFC 9218
+ */
+struct Curl_data_priority {
+#ifdef USE_NGHTTP2
+  /* tree like dependencies only implemented in nghttp2 */
+  struct Curl_easy *parent;
+  struct Curl_data_prio_node *children;
+#endif
+  int weight;
+#ifdef USE_NGHTTP2
+  BIT(exclusive);
+#endif
+};
 
 /*
  * This struct is for holding data that was attempted to get sent to the user's
@@ -1270,6 +1236,7 @@ typedef enum {
   EXPIRE_TOOFAST,
   EXPIRE_QUIC,
   EXPIRE_FTP_ACCEPT,
+  EXPIRE_ALPN_EYEBALLS,
   EXPIRE_LAST /* not an actual timer, used as a marker only */
 } expire_id;
 
@@ -1389,24 +1356,17 @@ struct UrlState {
   size_t drain; /* Increased when this stream has data to read, even if its
                    socket is not necessarily is readable. Decreased when
                    checked. */
+  struct Curl_data_priority priority; /* shallow copy of data->set */
 #endif
 
   curl_read_callback fread_func; /* read callback/function */
   void *in;                      /* CURLOPT_READDATA */
-#ifdef USE_HTTP2
-  struct Curl_easy *stream_depends_on;
-  int stream_weight;
-#endif
   CURLU *uh; /* URL handle for the current parsed URL */
   struct urlpieces up;
   unsigned char httpreq; /* Curl_HttpReq; what kind of HTTP request (if any)
                             is this */
   char *url;        /* work URL, copied from UserDefined */
   char *referer;    /* referer string */
-#ifndef CURL_DISABLE_COOKIES
-  struct curl_slist *cookielist; /* list of cookie files set by
-                                    curl_easy_setopt(COOKIEFILE) calls */
-#endif
   struct curl_slist *resolve; /* set to point to the set.resolve list when
                                  this should be dealt with in pretransfer */
 #ifndef CURL_DISABLE_HTTP
@@ -1471,7 +1431,6 @@ struct UrlState {
   BIT(done); /* set to FALSE when Curl_init_do() is called and set to TRUE
                 when multi_done() is called, to prevent multi_done() to get
                 invoked twice when the multi interface is used. */
-  BIT(stream_depends_e); /* set or don't set the Exclusive bit */
   BIT(previouslypending); /* this transfer WAS in the multi->pending queue */
   BIT(cookie_engine);
   BIT(prefer_ascii);   /* ASCII rather than binary */
@@ -1620,15 +1579,9 @@ struct UserDefined {
   void *out;         /* CURLOPT_WRITEDATA */
   void *in_set;      /* CURLOPT_READDATA */
   void *writeheader; /* write the header to this if non-NULL */
-  unsigned short proxyport; /* If non-zero, use this port number by
-                               default. If the proxy string features a
-                               ":[port]" that one will override this. */
   unsigned short use_port; /* which port to use (when not using default) */
   unsigned long httpauth;  /* kind of HTTP authentication to use (bitmask) */
   unsigned long proxyauth; /* kind of proxy authentication to use (bitmask) */
-#ifndef CURL_DISABLE_PROXY
-  unsigned char socks5auth;/* kind of SOCKS5 authentication to use (bitmask) */
-#endif
   long maxredirs;    /* maximum no. of http(s) redirects to follow, set to -1
                         for infinity */
 
@@ -1638,8 +1591,9 @@ struct UserDefined {
                                of strlen(), and then the data *may* be binary
                                (contain zero bytes) */
   unsigned short localport; /* local port number to bind to */
-  int localportrange; /* number of additional port numbers to test in case the
-                         'localport' one can't be bind()ed */
+  unsigned short localportrange; /* number of additional port numbers to test
+                                    in case the 'localport' one can't be
+                                    bind()ed */
   curl_write_callback fwrite_func;   /* function that stores the output */
   curl_write_callback fwrite_header; /* function that stores headers */
   curl_write_callback fwrite_rtp;    /* function that stores interleaved RTP */
@@ -1661,7 +1615,13 @@ struct UserDefined {
   void *prereq_userp; /* pre-initial request user data */
 
   void *seek_client;    /* pointer to pass to the seek callback */
+#ifndef CURL_DISABLE_COOKIES
+  struct curl_slist *cookielist; /* list of cookie files set by
+                                    curl_easy_setopt(COOKIEFILE) calls */
+#endif
 #ifndef CURL_DISABLE_HSTS
+  struct curl_slist *hstslist; /* list of HSTS files set by
+                                  curl_easy_setopt(HSTS) calls */
   curl_hstsread_callback hsts_read;
   void *hsts_read_userp;
   curl_hstswrite_callback hsts_write;
@@ -1688,17 +1648,8 @@ struct UserDefined {
                                 download */
   curl_off_t set_resume_from;  /* continue [ftp] transfer from here */
   struct curl_slist *headers; /* linked list of extra headers */
-  struct curl_slist *proxyheaders; /* linked list of extra CONNECT headers */
   struct curl_httppost *httppost;  /* linked list of old POST data */
   curl_mimepart mimepost;  /* MIME/POST data. */
-  struct curl_slist *quote;     /* after connection is established */
-  struct curl_slist *postquote; /* after the transfer */
-  struct curl_slist *prequote; /* before the transfer, after type */
-  struct curl_slist *source_quote;  /* 3rd party quote */
-  struct curl_slist *source_prequote;  /* in 3rd party transfer mode - before
-                                          the transfer on source host */
-  struct curl_slist *source_postquote; /* in 3rd party transfer mode - after
-                                          the transfer on source host */
 #ifndef CURL_DISABLE_TELNET
   struct curl_slist *telnet_options; /* linked list of telnet options */
 #endif
@@ -1708,13 +1659,18 @@ struct UserDefined {
                                     the hostname and port to connect to */
   time_t timevalue;       /* what time to compare with */
   unsigned char timecondition; /* kind of time comparison: curl_TimeCond */
-  unsigned char proxytype; /* what kind of proxy: curl_proxytype */
   unsigned char method;   /* what kind of HTTP request: Curl_HttpReq */
   unsigned char httpwant; /* when non-zero, a specific HTTP version requested
                              to be used in the library's request(s) */
   struct ssl_config_data ssl;  /* user defined SSL stuff */
 #ifndef CURL_DISABLE_PROXY
   struct ssl_config_data proxy_ssl;  /* user defined SSL stuff for proxy */
+  struct curl_slist *proxyheaders; /* linked list of extra CONNECT headers */
+  unsigned short proxyport; /* If non-zero, use this port number by
+                               default. If the proxy string features a
+                               ":[port]" that one will override this. */
+  unsigned char proxytype; /* what kind of proxy: curl_proxytype */
+  unsigned char socks5auth;/* kind of SOCKS5 authentication to use (bitmask) */
 #endif
   struct ssl_general_config general_ssl; /* general user defined SSL stuff */
   int dns_cache_timeout; /* DNS cache timeout (seconds) */
@@ -1722,7 +1678,9 @@ struct UserDefined {
   unsigned int upload_buffer_size; /* size of upload buffer to use,
                                       keep it >= CURL_MAX_WRITE_SIZE */
   void *private_data; /* application-private data */
+#ifndef CURL_DISABLE_HTTP
   struct curl_slist *http200aliases; /* linked list of aliases for http200 */
+#endif
   unsigned char ipver; /* the CURL_IPRESOLVE_* defines in the public header
                           file 0 - whatever, 1 - v2, 2 - v6 */
   curl_off_t max_filesize; /* Maximum file size to download */
@@ -1732,26 +1690,32 @@ struct UserDefined {
   unsigned char ftp_ccc;   /* FTP CCC options: curl_ftpccc */
   unsigned int accepttimeout;   /* in milliseconds, 0 means no timeout */
 #endif
-  /* Desppie the name ftp_create_missing_dirs is for FTP(S) and SFTP
+#if !defined(CURL_DISABLE_FTP) || defined(USE_SSH)
+  struct curl_slist *quote;     /* after connection is established */
+  struct curl_slist *postquote; /* after the transfer */
+  struct curl_slist *prequote; /* before the transfer, after type */
+  /* Despite the name, ftp_create_missing_dirs is for FTP(S) and SFTP
      1 - create directories that don't exist
      2 - the same but also allow MKD to fail once
   */
   unsigned char ftp_create_missing_dirs;
+#endif
 #ifdef USE_LIBSSH2
   curl_sshhostkeycallback ssh_hostkeyfunc; /* hostkey check callback */
   void *ssh_hostkeyfunc_userp;         /* custom pointer to callback */
 #endif
-
+#ifdef USE_SSH
   curl_sshkeycallback ssh_keyfunc; /* key matching callback */
   void *ssh_keyfunc_userp;         /* custom pointer to callback */
+  int ssh_auth_types;    /* allowed SSH auth types */
+  unsigned int new_directory_perms; /* when creating remote dirs */
+#endif
 #ifndef CURL_DISABLE_NETRC
   unsigned char use_netrc;        /* enum CURL_NETRC_OPTION values  */
 #endif
   curl_usessl use_ssl;   /* if AUTH TLS is to be attempted etc, for FTP or
                             IMAP or POP3 or others! */
   unsigned int new_file_perms;      /* when creating remote files */
-  unsigned int new_directory_perms; /* when creating remote dirs */
-  int ssh_auth_types;    /* allowed SSH auth types */
   char *str[STRING_LAST]; /* array of strings, pointing to allocated memory */
   struct curl_blob *blobs[BLOB_LAST];
 #ifdef ENABLE_IPV6
@@ -1759,8 +1723,9 @@ struct UserDefined {
 #endif
   curl_prot_t allowed_protocols;
   curl_prot_t redir_protocols;
+#ifndef CURL_DISABLE_MIME
   unsigned int mime_options;      /* Mime option flags. */
-
+#endif
 #ifndef CURL_DISABLE_RTSP
   void *rtp_out;     /* write RTP to this if non-NULL */
   /* Common RTSP header options */
@@ -1775,8 +1740,9 @@ struct UserDefined {
                                     to pattern (e.g. if WILDCARDMATCH is on) */
   void *fnmatch_data;
 #endif
-  long gssapi_delegation; /* GSS-API credential delegation, see the
-                             documentation of CURLOPT_GSSAPI_DELEGATION */
+ /* GSS-API credential delegation, see the documentation of
+    CURLOPT_GSSAPI_DELEGATION */
+  unsigned char gssapi_delegation;
 
   int tcp_keepidle;     /* seconds in idle before sending keepalive probe */
   int tcp_keepintvl;    /* seconds between TCP keepalive probes */
@@ -1784,10 +1750,8 @@ struct UserDefined {
   size_t maxconnects;    /* Max idle connections in the connection cache */
 
   long expect_100_timeout; /* in milliseconds */
-#ifdef USE_HTTP2
-  struct Curl_easy *stream_depends_on;
-  int stream_weight;
-  struct Curl_http2_dep *stream_dependents;
+#if defined(USE_HTTP2) || defined(USE_HTTP3)
+  struct Curl_data_priority priority;
 #endif
   curl_resolver_start_callback resolver_start; /* optional callback called
                                                   before resolver start */
@@ -1798,8 +1762,10 @@ struct UserDefined {
   struct Curl_easy *dohfor; /* this is a DoH request for that transfer */
 #endif
   CURLU *uh; /* URL handle for the current parsed URL */
+#ifndef CURL_DISABLE_HTTP
   void *trailer_data; /* pointer to pass to trailer data callback */
   curl_trailer_callback trailer_callback; /* trailing data callback */
+#endif
   char keep_post;     /* keep POSTs as POSTs after a 30x request; each
                          bit represents a request, from 301 to 303 */
 #ifndef CURL_DISABLE_SMTP
@@ -1877,7 +1843,6 @@ struct UserDefined {
   BIT(suppress_connect_headers); /* suppress proxy CONNECT response headers
                                     from user callbacks */
   BIT(dns_shuffle_addresses); /* whether to shuffle addresses before use */
-  BIT(stream_depends_e); /* set or don't set the Exclusive bit */
   BIT(haproxyprotocol); /* whether to send HAProxy PROXY protocol v1
                            header */
   BIT(abstract_unix_socket);
diff --git a/lib/vauth/cleartext.c b/lib/vauth/cleartext.c
index b82b171..c651fc5 100644
--- a/lib/vauth/cleartext.c
+++ b/lib/vauth/cleartext.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -28,7 +28,8 @@
 #include "curl_setup.h"
 
 #if !defined(CURL_DISABLE_IMAP) || !defined(CURL_DISABLE_SMTP) ||       \
-  !defined(CURL_DISABLE_POP3)
+  !defined(CURL_DISABLE_POP3) || \
+  (!defined(CURL_DISABLE_LDAP) && defined(USE_OPENLDAP))
 
 #include <curl/curl.h>
 #include "urldata.h"
diff --git a/lib/vauth/cram.c b/lib/vauth/cram.c
index 475d31b..5894ed4 100644
--- a/lib/vauth/cram.c
+++ b/lib/vauth/cram.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/vauth/digest.c b/lib/vauth/digest.c
index c81ce10..b7a0d92 100644
--- a/lib/vauth/digest.c
+++ b/lib/vauth/digest.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/vauth/digest.h b/lib/vauth/digest.h
index d785bdd..68fdb28 100644
--- a/lib/vauth/digest.h
+++ b/lib/vauth/digest.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/vauth/digest_sspi.c b/lib/vauth/digest_sspi.c
index 6c95a3e..8fb8669 100644
--- a/lib/vauth/digest_sspi.c
+++ b/lib/vauth/digest_sspi.c
@@ -5,8 +5,8 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2014 - 2016, Steve Holme, <steve_holme@hotmail.com>.
- * Copyright (C) 2015 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Steve Holme, <steve_holme@hotmail.com>.
+ * 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
diff --git a/lib/vauth/gsasl.c b/lib/vauth/gsasl.c
index a73c644..c7d0a8d 100644
--- a/lib/vauth/gsasl.c
+++ b/lib/vauth/gsasl.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2020 - 2022, Simon Josefsson, <simon@josefsson.org>, et al.
+ * Copyright (C) Simon Josefsson, <simon@josefsson.org>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/lib/vauth/krb5_gssapi.c b/lib/vauth/krb5_gssapi.c
index bac7804..65eb3e1 100644
--- a/lib/vauth/krb5_gssapi.c
+++ b/lib/vauth/krb5_gssapi.c
@@ -5,8 +5,8 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2014 - 2019, Steve Holme, <steve_holme@hotmail.com>.
- * Copyright (C) 2015 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Steve Holme, <steve_holme@hotmail.com>.
+ * 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
diff --git a/lib/vauth/krb5_sspi.c b/lib/vauth/krb5_sspi.c
index 015bc66..c487149 100644
--- a/lib/vauth/krb5_sspi.c
+++ b/lib/vauth/krb5_sspi.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2014 - 2022, Steve Holme, <steve_holme@hotmail.com>.
+ * Copyright (C) Steve Holme, <steve_holme@hotmail.com>.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/lib/vauth/ntlm.c b/lib/vauth/ntlm.c
index 0141e17..2a5d4a4 100644
--- a/lib/vauth/ntlm.c
+++ b/lib/vauth/ntlm.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/vauth/ntlm.h b/lib/vauth/ntlm.h
index 14ebba2..31ce921 100644
--- a/lib/vauth/ntlm.h
+++ b/lib/vauth/ntlm.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/vauth/ntlm_sspi.c b/lib/vauth/ntlm_sspi.c
index 193576a..5118963 100644
--- a/lib/vauth/ntlm_sspi.c
+++ b/lib/vauth/ntlm_sspi.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/vauth/oauth2.c b/lib/vauth/oauth2.c
index 1604b30..a4adbdc 100644
--- a/lib/vauth/oauth2.c
+++ b/lib/vauth/oauth2.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -27,7 +27,8 @@
 #include "curl_setup.h"
 
 #if !defined(CURL_DISABLE_IMAP) || !defined(CURL_DISABLE_SMTP) || \
-  !defined(CURL_DISABLE_POP3)
+  !defined(CURL_DISABLE_POP3) || \
+  (!defined(CURL_DISABLE_LDAP) && defined(USE_OPENLDAP))
 
 #include <curl/curl.h>
 #include "urldata.h"
diff --git a/lib/vauth/spnego_gssapi.c b/lib/vauth/spnego_gssapi.c
index 25dff96..e1d52b7 100644
--- a/lib/vauth/spnego_gssapi.c
+++ b/lib/vauth/spnego_gssapi.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/vauth/spnego_sspi.c b/lib/vauth/spnego_sspi.c
index d845cac..d3245d0 100644
--- a/lib/vauth/spnego_sspi.c
+++ b/lib/vauth/spnego_sspi.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/vauth/vauth.c b/lib/vauth/vauth.c
index 58fe051..62fc7c4 100644
--- a/lib/vauth/vauth.c
+++ b/lib/vauth/vauth.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2014 - 2022, Steve Holme, <steve_holme@hotmail.com>.
+ * Copyright (C) Steve Holme, <steve_holme@hotmail.com>.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/lib/vauth/vauth.h b/lib/vauth/vauth.h
index c310c66..e17d7aa 100644
--- a/lib/vauth/vauth.h
+++ b/lib/vauth/vauth.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2014 - 2022, Steve Holme, <steve_holme@hotmail.com>.
+ * Copyright (C) Steve Holme, <steve_holme@hotmail.com>.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/lib/version.c b/lib/version.c
index b43a8bc..a69e2ca 100644
--- a/lib/version.c
+++ b/lib/version.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -24,12 +24,16 @@
 
 #include "curl_setup.h"
 
+#ifdef USE_NGHTTP2
+#include <nghttp2/nghttp2.h>
+#endif
+
 #include <curl/curl.h>
 #include "urldata.h"
 #include "vtls/vtls.h"
 #include "http2.h"
 #include "vssh/ssh.h"
-#include "quic.h"
+#include "vquic/vquic.h"
 #include "curl_printf.h"
 #include "easy_lock.h"
 
diff --git a/lib/version_win32.c b/lib/version_win32.c
index e8f14f9..872d5b4 100644
--- a/lib/version_win32.c
+++ b/lib/version_win32.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2016 - 2022, Steve Holme, <steve_holme@hotmail.com>.
+ * Copyright (C) Steve Holme, <steve_holme@hotmail.com>.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/lib/version_win32.h b/lib/version_win32.h
index 7a9a6a1..3899174 100644
--- a/lib/version_win32.h
+++ b/lib/version_win32.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2016 - 2022, Steve Holme, <steve_holme@hotmail.com>.
+ * Copyright (C) Steve Holme, <steve_holme@hotmail.com>.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/lib/vquic/curl_msh3.c b/lib/vquic/curl_msh3.c
new file mode 100644
index 0000000..1930703
--- /dev/null
+++ b/lib/vquic/curl_msh3.c
@@ -0,0 +1,841 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  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 cf_msh3_ctx *ctx = cf->ctx;
+  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;
+  case CF_CTRL_CONN_REPORT_STATS:
+    if(cf->sockindex == FIRSTSOCKET)
+      Curl_pgrsTimeWas(data, TIMER_APPCONNECT, ctx->handshake_at);
+    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;
+  }
+  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)
+{
+  struct cf_msh3_ctx *ctx = cf->ctx;
+
+  (void)data;
+  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 */
diff --git a/lib/vquic/curl_msh3.h b/lib/vquic/curl_msh3.h
new file mode 100644
index 0000000..33931f5
--- /dev/null
+++ b/lib/vquic/curl_msh3.h
@@ -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 */
diff --git a/lib/vquic/curl_ngtcp2.c b/lib/vquic/curl_ngtcp2.c
new file mode 100644
index 0000000..ffdaead
--- /dev/null
+++ b/lib/vquic/curl_ngtcp2.c
@@ -0,0 +1,2515 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  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"
+
+/* 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_SEND|KEEP_SEND_PAUSE)) == 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 resonse 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];
+  size_t bufsize = 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;
+  case CF_CTRL_CONN_REPORT_STATS:
+    if(cf->sockindex == FIRSTSOCKET) {
+      if(ctx->got_first_byte)
+        Curl_pgrsTimeWas(data, TIMER_CONNECT, ctx->first_byte_at);
+      Curl_pgrsTimeWas(data, TIMER_APPCONNECT, ctx->handshake_at);
+    }
+    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);
+    ctx->qlogfd = -1;
+  }
+#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->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, 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 */
+}
+
+/*
+ * 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;
+  default:
+    break;
+  }
+  return cf->next?
+    cf->next->cft->query(cf->next, data, query, pres1, pres2) :
+    CURLE_UNKNOWN_OPTION;
+}
+
+
+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,
+  Curl_cf_def_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;
+  }
+  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
diff --git a/lib/vquic/curl_ngtcp2.h b/lib/vquic/curl_ngtcp2.h
new file mode 100644
index 0000000..8813ec9
--- /dev/null
+++ b/lib/vquic/curl_ngtcp2.h
@@ -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 */
diff --git a/lib/vquic/curl_quiche.c b/lib/vquic/curl_quiche.c
new file mode 100644
index 0000000..54408d7
--- /dev/null
+++ b/lib/vquic/curl_quiche.c
@@ -0,0 +1,1433 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  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];
+  size_t bufsize = 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_SEND|KEEP_SEND_PAUSE)) == 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;
+  case CF_CTRL_CONN_REPORT_STATS:
+    if(cf->sockindex == FIRSTSOCKET) {
+      if(ctx->got_first_byte)
+        Curl_pgrsTimeWas(data, TIMER_CONNECT, ctx->first_byte_at);
+      Curl_pgrsTimeWas(data, TIMER_APPCONNECT, ctx->handshake_at);
+    }
+    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;
+  default:
+    break;
+  }
+  return cf->next?
+    cf->next->cft->query(cf->next, data, query, pres1, pres2) :
+    CURLE_UNKNOWN_OPTION;
+}
+
+
+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,
+  Curl_cf_def_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
diff --git a/lib/vquic/curl_quiche.h b/lib/vquic/curl_quiche.h
new file mode 100644
index 0000000..bce781c
--- /dev/null
+++ b/lib/vquic/curl_quiche.h
@@ -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 */
diff --git a/lib/vquic/msh3.c b/lib/vquic/msh3.c
deleted file mode 100644
index c3e58e7..0000000
--- a/lib/vquic/msh3.c
+++ /dev/null
@@ -1,527 +0,0 @@
-/***************************************************************************
- *                                  _   _ ____  _
- *  Project                     ___| | | |  _ \| |
- *                             / __| | | | |_) | |
- *                            | (__| |_| |  _ <| |___
- *                             \___|\___/|_| \_\_____|
- *
- * Copyright (C) 1998 - 2022, 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 "connect.h"
-#include "h2h3.h"
-#include "msh3.h"
-
-/* The last 3 #include files should be in this order */
-#include "curl_printf.h"
-#include "curl_memory.h"
-#include "memdebug.h"
-
-/* #define DEBUG_HTTP3 1 */
-#ifdef DEBUG_HTTP3
-#define H3BUGF(x) x
-#else
-#define H3BUGF(x) do { } while(0)
-#endif
-
-#define MSH3_REQ_INIT_BUF_LEN 8192
-
-static CURLcode msh3_do_it(struct Curl_easy *data, bool *done);
-static int msh3_getsock(struct Curl_easy *data,
-                        struct connectdata *conn, curl_socket_t *socks);
-static CURLcode msh3_disconnect(struct Curl_easy *data,
-                                struct connectdata *conn,
-                                bool dead_connection);
-static unsigned int msh3_conncheck(struct Curl_easy *data,
-                                   struct connectdata *conn,
-                                   unsigned int checks_to_perform);
-static Curl_recv msh3_stream_recv;
-static Curl_send msh3_stream_send;
-static void MSH3_CALL msh3_header_received(MSH3_REQUEST *Request,
-                                           void *IfContext,
-                                           const MSH3_HEADER *Header);
-static void 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(MSH3_REQUEST *Request, void *IfContext);
-
-static const struct Curl_handler msh3_curl_handler_http3 = {
-  "HTTPS",                              /* scheme */
-  ZERO_NULL,                            /* setup_connection */
-  msh3_do_it,                           /* do_it */
-  Curl_http_done,                       /* done */
-  ZERO_NULL,                            /* do_more */
-  ZERO_NULL,                            /* connect_it */
-  ZERO_NULL,                            /* connecting */
-  ZERO_NULL,                            /* doing */
-  msh3_getsock,                         /* proto_getsock */
-  msh3_getsock,                         /* doing_getsock */
-  ZERO_NULL,                            /* domore_getsock */
-  msh3_getsock,                         /* perform_getsock */
-  msh3_disconnect,                      /* disconnect */
-  ZERO_NULL,                            /* readwrite */
-  msh3_conncheck,                       /* connection_check */
-  ZERO_NULL,                            /* attach connection */
-  PORT_HTTP,                            /* defport */
-  CURLPROTO_HTTPS,                      /* protocol */
-  CURLPROTO_HTTP,                       /* family */
-  PROTOPT_SSL | PROTOPT_STREAM          /* flags */
-};
-
-static const MSH3_REQUEST_IF msh3_request_if = {
-  msh3_header_received,
-  msh3_data_received,
-  msh3_complete,
-  msh3_shutdown
-};
-
-void Curl_quic_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]);
-}
-
-CURLcode Curl_quic_connect(struct Curl_easy *data,
-                           struct connectdata *conn,
-                           curl_socket_t sockfd,
-                           int sockindex,
-                           const struct sockaddr *addr,
-                           socklen_t addrlen)
-{
-  struct quicsocket *qs = &conn->hequic[sockindex];
-  bool insecure = !conn->ssl_config.verifypeer;
-  memset(qs, 0, sizeof(*qs));
-
-  (void)sockfd;
-  (void)addr; /* TODO - Pass address along */
-  (void)addrlen;
-
-  H3BUGF(infof(data, "creating new api/connection"));
-
-  qs->api = MsH3ApiOpen();
-  if(!qs->api) {
-    failf(data, "can't create msh3 api");
-    return CURLE_FAILED_INIT;
-  }
-
-  qs->conn = MsH3ConnectionOpen(qs->api,
-                                conn->host.name,
-                                (uint16_t)conn->remote_port,
-                                insecure);
-  if(!qs->conn) {
-    failf(data, "can't create msh3 connection");
-    if(qs->api) {
-      MsH3ApiClose(qs->api);
-    }
-    return CURLE_FAILED_INIT;
-  }
-
-  return CURLE_OK;
-}
-
-CURLcode Curl_quic_is_connected(struct Curl_easy *data,
-                                struct connectdata *conn,
-                                int sockindex,
-                                bool *connected)
-{
-  struct quicsocket *qs = &conn->hequic[sockindex];
-  MSH3_CONNECTION_STATE state;
-
-  state = MsH3ConnectionGetState(qs->conn, false);
-  if(state == MSH3_CONN_HANDSHAKE_FAILED || state == MSH3_CONN_DISCONNECTED) {
-    failf(data, "failed to connect, state=%u", (uint32_t)state);
-    return CURLE_COULDNT_CONNECT;
-  }
-
-  if(state == MSH3_CONN_CONNECTED) {
-    H3BUGF(infof(data, "connection connected"));
-    *connected = true;
-    conn->quic = qs;
-    conn->recv[sockindex] = msh3_stream_recv;
-    conn->send[sockindex] = msh3_stream_send;
-    conn->handler = &msh3_curl_handler_http3;
-    conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
-    conn->httpversion = 30;
-    conn->bundle->multiuse = BUNDLE_MULTIPLEX;
-    /* TODO - Clean up other happy-eyeballs connection(s)? */
-  }
-
-  return CURLE_OK;
-}
-
-static int msh3_getsock(struct Curl_easy *data,
-                        struct connectdata *conn, curl_socket_t *socks)
-{
-  struct HTTP *stream = data->req.p.http;
-  int bitmap = GETSOCK_BLANK;
-
-  socks[0] = conn->sock[FIRSTSOCKET];
-
-  if(stream->recv_error) {
-    bitmap |= GETSOCK_READSOCK(FIRSTSOCKET);
-    data->state.drain++;
-  }
-  else if(stream->recv_header_len || stream->recv_data_len) {
-    bitmap |= GETSOCK_READSOCK(FIRSTSOCKET);
-    data->state.drain++;
-  }
-
-  H3BUGF(infof(data, "msh3_getsock %u", (uint32_t)data->state.drain));
-
-  return bitmap;
-}
-
-static CURLcode msh3_do_it(struct Curl_easy *data, bool *done)
-{
-  struct HTTP *stream = data->req.p.http;
-  H3BUGF(infof(data, "msh3_do_it"));
-  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_header_len = 0;
-  stream->recv_header_complete = false;
-  stream->recv_data_len = 0;
-  stream->recv_data_complete = false;
-  stream->recv_error = CURLE_OK;
-  return Curl_http(data, done);
-}
-
-static unsigned int msh3_conncheck(struct Curl_easy *data,
-                                   struct connectdata *conn,
-                                   unsigned int checks_to_perform)
-{
-  (void)data;
-  (void)conn;
-  (void)checks_to_perform;
-  H3BUGF(infof(data, "msh3_conncheck"));
-  return CONNRESULT_NONE;
-}
-
-static void disconnect(struct quicsocket *qs)
-{
-  if(qs->conn) {
-    MsH3ConnectionClose(qs->conn);
-    qs->conn = ZERO_NULL;
-  }
-  if(qs->api) {
-    MsH3ApiClose(qs->api);
-    qs->api = ZERO_NULL;
-  }
-}
-
-static CURLcode msh3_disconnect(struct Curl_easy *data,
-                                struct connectdata *conn, bool dead_connection)
-{
-  (void)data;
-  (void)dead_connection;
-  H3BUGF(infof(data, "disconnecting (msh3)"));
-  disconnect(conn->quic);
-  return CURLE_OK;
-}
-
-void Curl_quic_disconnect(struct Curl_easy *data, struct connectdata *conn,
-                          int tempindex)
-{
-  (void)data;
-  if(conn->transport == TRNSPRT_QUIC) {
-    H3BUGF(infof(data, "disconnecting QUIC index %u", tempindex));
-    disconnect(&conn->hequic[tempindex]);
-  }
-}
-
-/* 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);
-    new_recv_buf = malloc(new_recv_buf_alloc_len);
-    if(!new_recv_buf) {
-      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 HTTP *stream = IfContext;
-  size_t total_len;
-  (void)Request;
-
-  if(stream->recv_header_complete) {
-    H3BUGF(printf("* 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 = 9 + Header->ValueLength;
-    if(!msh3request_ensure_room(stream, total_len)) {
-      /* TODO - handle error */
-      goto release_lock;
-    }
-    msnprintf((char *)stream->recv_buf + stream->recv_header_len,
-              stream->recv_buf_alloc - stream->recv_header_len,
-              "HTTP/3 %.*s\n", (int)Header->ValueLength, Header->Value);
-  }
-  else {
-    total_len = Header->NameLength + 4 + Header->ValueLength;
-    if(!msh3request_ensure_room(stream, total_len)) {
-      /* TODO - handle error */
-      goto release_lock;
-    }
-    msnprintf((char *)stream->recv_buf + stream->recv_header_len,
-              stream->recv_buf_alloc - stream->recv_header_len,
-              "%.*s: %.*s\n",
-              (int)Header->NameLength, Header->Name,
-              (int)Header->ValueLength, Header->Value);
-  }
-
-  stream->recv_header_len += total_len - 1; /* don't include null-terminator */
-
-release_lock:
-  msh3_lock_release(&stream->recv_lock);
-}
-
-static void MSH3_CALL msh3_data_received(MSH3_REQUEST *Request,
-                                         void *IfContext, uint32_t Length,
-                                         const uint8_t *Data)
-{
-  struct HTTP *stream = IfContext;
-  size_t cur_recv_len = stream->recv_header_len + stream->recv_data_len;
-  (void)Request;
-  H3BUGF(printf("* msh3_data_received %u. %zu buffered, %zu allocated\n",
-                Length, cur_recv_len, stream->recv_buf_alloc));
-  msh3_lock_acquire(&stream->recv_lock);
-  if(!stream->recv_header_complete) {
-    H3BUGF(printf("* Headers complete!\n"));
-    if(!msh3request_ensure_room(stream, 2)) {
-      /* TODO - handle error */
-      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)) {
-    /* TODO - handle error */
-    goto release_lock;
-  }
-  memcpy(stream->recv_buf + cur_recv_len, Data, Length);
-  stream->recv_data_len += (size_t)Length;
-release_lock:
-  msh3_lock_release(&stream->recv_lock);
-}
-
-static void MSH3_CALL msh3_complete(MSH3_REQUEST *Request, void *IfContext,
-                                    bool Aborted, uint64_t AbortError)
-{
-  struct HTTP *stream = IfContext;
-  (void)Request;
-  (void)AbortError;
-  H3BUGF(printf("* msh3_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(MSH3_REQUEST *Request, void *IfContext)
-{
-  struct HTTP *stream = IfContext;
-  (void)Request;
-  (void)stream;
-}
-
-static ssize_t msh3_stream_send(struct Curl_easy *data,
-                                int sockindex,
-                                const void *mem,
-                                size_t len,
-                                CURLcode *curlcode)
-{
-  struct connectdata *conn = data->conn;
-  struct HTTP *stream = data->req.p.http;
-  struct quicsocket *qs = conn->quic;
-  struct h2h3req *hreq;
-
-  (void)sockindex;
-  /* Sizes must match for cast below to work" */
-  DEBUGASSERT(sizeof(MSH3_HEADER) == sizeof(struct h2h3pseudo));
-
-  H3BUGF(infof(data, "msh3_stream_send %zu", len));
-
-  if(!stream->req) {
-    *curlcode = Curl_pseudo_headers(data, mem, len, &hreq);
-    if(*curlcode) {
-      failf(data, "Curl_pseudo_headers failed");
-      return -1;
-    }
-    H3BUGF(infof(data, "starting request with %zu headers", hreq->entries));
-    stream->req = MsH3RequestOpen(qs->conn, &msh3_request_if, stream,
-                                 (MSH3_HEADER*)hreq->header, hreq->entries);
-    Curl_pseudo_free(hreq);
-    if(!stream->req) {
-      failf(data, "request open failed");
-      *curlcode = CURLE_SEND_ERROR;
-      return -1;
-    }
-    *curlcode = CURLE_OK;
-    return len;
-  }
-  H3BUGF(infof(data, "send %zd body bytes on request %p", len,
-               (void *)stream->req));
-  *curlcode = CURLE_SEND_ERROR;
-  return -1;
-}
-
-static ssize_t msh3_stream_recv(struct Curl_easy *data,
-                                int sockindex,
-                                char *buf,
-                                size_t buffersize,
-                                CURLcode *curlcode)
-{
-  struct HTTP *stream = data->req.p.http;
-  size_t outsize = 0;
-  (void)sockindex;
-  H3BUGF(infof(data, "msh3_stream_recv %zu", buffersize));
-
-  if(stream->recv_error) {
-    failf(data, "request aborted");
-    *curlcode = stream->recv_error;
-    return -1;
-  }
-
-  msh3_lock_acquire(&stream->recv_lock);
-
-  if(stream->recv_header_len) {
-    outsize = buffersize;
-    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;
-    H3BUGF(infof(data, "returned %zu bytes of headers", outsize));
-  }
-  else if(stream->recv_data_len) {
-    outsize = buffersize;
-    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;
-    H3BUGF(infof(data, "returned %zu bytes of data", outsize));
-  }
-  else if(stream->recv_data_complete) {
-    H3BUGF(infof(data, "receive complete"));
-  }
-
-  msh3_lock_release(&stream->recv_lock);
-
-  return (ssize_t)outsize;
-}
-
-CURLcode Curl_quic_done_sending(struct Curl_easy *data)
-{
-  struct connectdata *conn = data->conn;
-  H3BUGF(infof(data, "Curl_quic_done_sending"));
-  if(conn->handler == &msh3_curl_handler_http3) {
-    struct HTTP *stream = data->req.p.http;
-    stream->upload_done = TRUE;
-  }
-
-  return CURLE_OK;
-}
-
-void Curl_quic_done(struct Curl_easy *data, bool premature)
-{
-  struct HTTP *stream = data->req.p.http;
-  (void)premature;
-  H3BUGF(infof(data, "Curl_quic_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;
-    }
-  }
-}
-
-bool Curl_quic_data_pending(const struct Curl_easy *data)
-{
-  struct HTTP *stream = data->req.p.http;
-  H3BUGF(infof((struct Curl_easy *)data, "Curl_quic_data_pending"));
-  return stream->recv_header_len || stream->recv_data_len;
-}
-
-/*
- * Called from transfer.c:Curl_readwrite when neither HTTP level read
- * nor write is performed. It is a good place to handle timer expiry
- * for QUIC transport.
- */
-CURLcode Curl_quic_idle(struct Curl_easy *data)
-{
-  (void)data;
-  H3BUGF(infof(data, "Curl_quic_idle"));
-  return CURLE_OK;
-}
-
-#endif /* USE_MSH3 */
diff --git a/lib/vquic/msh3.h b/lib/vquic/msh3.h
deleted file mode 100644
index ce884d9..0000000
--- a/lib/vquic/msh3.h
+++ /dev/null
@@ -1,40 +0,0 @@
-#ifndef HEADER_CURL_VQUIC_MSH3_H
-#define HEADER_CURL_VQUIC_MSH3_H
-/***************************************************************************
- *                                  _   _ ____  _
- *  Project                     ___| | | |  _ \| |
- *                             / __| | | | |_) | |
- *                            | (__| |_| |  _ <| |___
- *                             \___|\___/|_| \_\_____|
- *
- * Copyright (C) 1998 - 2022, 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>
-
-struct quicsocket {
-  MSH3_API* api;
-  MSH3_CONNECTION* conn;
-};
-
-#endif /* USE_MSQUIC */
-
-#endif /* HEADER_CURL_VQUIC_MSH3_H */
diff --git a/lib/vquic/ngtcp2.c b/lib/vquic/ngtcp2.c
deleted file mode 100644
index f16b469..0000000
--- a/lib/vquic/ngtcp2.c
+++ /dev/null
@@ -1,2266 +0,0 @@
-/***************************************************************************
- *                                  _   _ ____  _
- *  Project                     ___| | | |  _ \| |
- *                             / __| | | | |_) | |
- *                            | (__| |_| |  _ <| |___
- *                             \___|\___/|_| \_\_____|
- *
- * Copyright (C) 1998 - 2022, 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 "ngtcp2.h"
-#include "multiif.h"
-#include "strcase.h"
-#include "cfilters.h"
-#include "connect.h"
-#include "strerror.h"
-#include "dynbuf.h"
-#include "vquic.h"
-#include "h2h3.h"
-#include "vtls/keylog.h"
-#include "vtls/vtls.h"
-
-/* The last 3 #include files should be in this order */
-#include "curl_printf.h"
-#include "curl_memory.h"
-#include "memdebug.h"
-
-/* #define DEBUG_NGTCP2 */
-#ifdef CURLDEBUG
-#define DEBUG_HTTP3
-#endif
-#ifdef DEBUG_HTTP3
-#define H3BUGF(x) x
-#else
-#define H3BUGF(x) do { } while(0)
-#endif
-
-#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)
-
-#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
-
-/* 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 ng_process_ingress(struct Curl_easy *data,
-                                   curl_socket_t sockfd,
-                                   struct quicsocket *qs);
-static CURLcode ng_flush_egress(struct Curl_easy *data, int sockfd,
-                                struct quicsocket *qs);
-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 quicsocket *qs = conn_ref->user_data;
-  return qs->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, ...)
-{
-  va_list ap;
-  (void)user_data; /* TODO, use this to do infof() instead long-term */
-  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 quicsocket *qs = (struct quicsocket *)user_data;
-  (void)flags;
-  if(qs->qlogfd != -1) {
-    ssize_t rc = write(qs->qlogfd, data, datalen);
-    if(rc == -1) {
-      /* on write error, stop further write attempts */
-      close(qs->qlogfd);
-      qs->qlogfd = -1;
-    }
-  }
-
-}
-
-static void quic_settings(struct quicsocket *qs,
-                          uint64_t stream_buffer_size)
-{
-  ngtcp2_settings *s = &qs->settings;
-  ngtcp2_transport_params *t = &qs->transport_params;
-  ngtcp2_settings_default(s);
-  ngtcp2_transport_params_default(t);
-#ifdef DEBUG_NGTCP2
-  s->log_printf = quic_printf;
-#else
-  s->log_printf = NULL;
-#endif
-  s->initial_ts = timestamp();
-  t->initial_max_stream_data_bidi_local = stream_buffer_size;
-  t->initial_max_stream_data_bidi_remote = QUIC_MAX_STREAMS;
-  t->initial_max_stream_data_uni = QUIC_MAX_STREAMS;
-  t->initial_max_data = QUIC_MAX_DATA;
-  t->initial_max_streams_bidi = 1;
-  t->initial_max_streams_uni = 3;
-  t->max_idle_timeout = QUIC_IDLE_TIMEOUT;
-  if(qs->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 quicsocket *qs);
-
-#ifdef USE_OPENSSL
-static SSL_CTX *quic_ssl_ctx(struct Curl_easy *data)
-{
-  struct connectdata *conn = data->conn;
-  SSL_CTX *ssl_ctx = SSL_CTX_new(TLS_method());
-
-#ifdef OPENSSL_IS_BORINGSSL
-  if(ngtcp2_crypto_boringssl_configure_client_context(ssl_ctx) != 0) {
-    failf(data, "ngtcp2_crypto_boringssl_configure_client_context failed");
-    return NULL;
-  }
-#else
-  if(ngtcp2_crypto_openssl_configure_client_context(ssl_ctx) != 0) {
-    failf(data, "ngtcp2_crypto_openssl_configure_client_context failed");
-    return NULL;
-  }
-#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");
-    return NULL;
-  }
-#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);
-    return NULL;
-  }
-
-  if(SSL_CTX_set1_groups_list(ssl_ctx, QUIC_GROUPS) != 1) {
-    failf(data, "SSL_CTX_set1_groups_list failed");
-    return NULL;
-  }
-#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);
-  }
-
-  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;
-}
-
-static CURLcode quic_set_client_cert(struct Curl_easy *data,
-                                     struct quicsocket *qs)
-{
-  SSL_CTX *ssl_ctx = qs->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 quicsocket *qs,
-                              struct Curl_easy *data,
-                              struct connectdata *conn)
-{
-  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 = qs->conn->host.name;
-
-  (void)data;
-  (void)conn;
-  DEBUGASSERT(!qs->ssl);
-  qs->ssl = SSL_new(qs->sslctx);
-
-  SSL_set_app_data(qs->ssl, &qs->conn_ref);
-  SSL_set_connect_state(qs->ssl);
-  SSL_set_quic_use_legacy_codepoint(qs->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(qs->ssl, alpn, (int)alpnlen);
-
-  /* set SNI */
-  SSL_set_tlsext_host_name(qs->ssl, hostname);
-  return CURLE_OK;
-}
-#elif defined(USE_GNUTLS)
-static CURLcode quic_init_ssl(struct quicsocket *qs,
-                              struct Curl_easy *data,
-                              struct connectdata *conn)
-{
-  CURLcode result;
-  gnutls_datum_t alpn[2];
-  /* this will need some attention when HTTPS proxy over QUIC get fixed */
-  const char * const hostname = qs->conn->host.name;
-  long * const pverifyresult = &data->set.ssl.certverifyresult;
-  int rc;
-
-  DEBUGASSERT(qs->gtls == NULL);
-  qs->gtls = calloc(1, sizeof(*(qs->gtls)));
-  if(!qs->gtls)
-    return CURLE_OUT_OF_MEMORY;
-
-  result = gtls_client_init(data, &conn->ssl_config, &data->set.ssl,
-                            hostname, qs->gtls, pverifyresult);
-  if(result)
-    return result;
-
-  gnutls_session_set_ptr(qs->gtls->session, &qs->conn_ref);
-
-  if(ngtcp2_crypto_gnutls_configure_client_session(qs->gtls->session) != 0) {
-    H3BUGF(fprintf(stderr,
-                   "ngtcp2_crypto_gnutls_configure_client_session failed\n"));
-    return CURLE_QUIC_CONNECT_ERROR;
-  }
-
-  rc = gnutls_priority_set_direct(qs->gtls->session, QUIC_PRIORITY, NULL);
-  if(rc < 0) {
-    H3BUGF(fprintf(stderr, "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(qs->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(qs->gtls->session, alpn, 2, GNUTLS_ALPN_MANDATORY);
-
-  return CURLE_OK;
-}
-#elif defined(USE_WOLFSSL)
-
-static WOLFSSL_CTX *quic_ssl_ctx(struct Curl_easy *data)
-{
-  struct connectdata *conn = data->conn;
-  WOLFSSL_CTX *ssl_ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method());
-
-  if(ngtcp2_crypto_wolfssl_configure_client_context(ssl_ctx) != 0) {
-    failf(data, "ngtcp2_crypto_wolfssl_configure_client_context failed");
-    return NULL;
-  }
-
-  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);
-    return NULL;
-  }
-
-  if(wolfSSL_CTX_set1_groups_list(ssl_ctx, (char *)QUIC_GROUPS) != 1) {
-    failf(data, "SSL_CTX_set1_groups_list failed");
-    return NULL;
-  }
-
-  /* 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");
-    return NULL;
-#endif
-  }
-
-  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) {
-      wolfSSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL);
-      /* 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");
-        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 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);
-  }
-
-  return ssl_ctx;
-}
-
-/** SSL callbacks ***/
-
-static CURLcode quic_init_ssl(struct quicsocket *qs,
-                              struct Curl_easy *data,
-                              struct connectdata *conn)
-{
-  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 = qs->conn->host.name;
-
-  (void)data;
-  (void)conn;
-  DEBUGASSERT(!qs->ssl);
-  qs->ssl = SSL_new(qs->sslctx);
-
-  wolfSSL_set_app_data(qs->ssl, &qs->conn_ref);
-  wolfSSL_set_connect_state(qs->ssl);
-  wolfSSL_set_quic_use_legacy_codepoint(qs->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(qs->ssl, alpn, (int)alpnlen);
-
-  /* set SNI */
-  wolfSSL_UseSNI(qs->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 extend_stream_window(ngtcp2_conn *tconn,
-                                 struct HTTP *stream)
-{
-  size_t thismuch = stream->unacked_window;
-  ngtcp2_conn_extend_max_stream_offset(tconn, stream->stream3_id, thismuch);
-  ngtcp2_conn_extend_max_offset(tconn, thismuch);
-  stream->unacked_window = 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 quicsocket *qs = (struct quicsocket *)user_data;
-  nghttp3_ssize nconsumed;
-  int fin = (flags & NGTCP2_STREAM_DATA_FLAG_FIN) ? 1 : 0;
-  (void)offset;
-  (void)stream_user_data;
-
-  nconsumed =
-    nghttp3_conn_read_stream(qs->h3conn, stream_id, buf, buflen, fin);
-  if(nconsumed < 0) {
-    ngtcp2_connection_close_error_set_application_error(
-        &qs->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 quicsocket *qs = (struct quicsocket *)user_data;
-  int rv;
-  (void)stream_id;
-  (void)tconn;
-  (void)offset;
-  (void)datalen;
-  (void)stream_user_data;
-
-  rv = nghttp3_conn_add_ack_offset(qs->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 stream_id, uint64_t app_error_code,
-                           void *user_data, void *stream_user_data)
-{
-  struct quicsocket *qs = (struct quicsocket *)user_data;
-  int rv;
-  (void)tconn;
-  (void)stream_user_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(qs->h3conn, stream_id,
-                                 app_error_code);
-  if(rv) {
-    ngtcp2_connection_close_error_set_application_error(
-        &qs->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 quicsocket *qs = (struct quicsocket *)user_data;
-  int rv;
-  (void)tconn;
-  (void)final_size;
-  (void)app_error_code;
-  (void)stream_user_data;
-
-  rv = nghttp3_conn_shutdown_stream_read(qs->h3conn, stream_id);
-  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 quicsocket *qs = (struct quicsocket *)user_data;
-  int rv;
-  (void)tconn;
-  (void)app_error_code;
-  (void)stream_user_data;
-
-  rv = nghttp3_conn_shutdown_stream_read(qs->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 quicsocket *qs = (struct quicsocket *)user_data;
-  int rv;
-  (void)tconn;
-  (void)max_data;
-  (void)stream_user_data;
-
-  rv = nghttp3_conn_unblock_stream(qs->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 quicsocket *qs = (struct quicsocket *)user_data;
-  (void)tconn;
-
-  if(level != NGTCP2_CRYPTO_LEVEL_APPLICATION) {
-    return 0;
-  }
-
-  if(init_ngh3_conn(qs) != 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 */
-};
-
-/*
- * Might be called twice for happy eyeballs.
- */
-CURLcode Curl_quic_connect(struct Curl_easy *data,
-                           struct connectdata *conn,
-                           curl_socket_t sockfd,
-                           int sockindex,
-                           const struct sockaddr *addr,
-                           socklen_t addrlen)
-{
-  int rc;
-  int rv;
-  CURLcode result;
-  ngtcp2_path path; /* TODO: this must be initialized properly */
-  struct quicsocket *qs = &conn->hequic[sockindex];
-  char ipbuf[40];
-  int port;
-  int qfd;
-
-  if(qs->conn)
-    Curl_quic_disconnect(data, conn, sockindex);
-  qs->conn = conn;
-
-  /* extract the used address as a string */
-  if(!Curl_addr2string((struct sockaddr*)addr, addrlen, ipbuf, &port)) {
-    char buffer[STRERROR_LEN];
-    failf(data, "ssrem inet_ntop() failed with errno %d: %s",
-          SOCKERRNO, Curl_strerror(SOCKERRNO, buffer, sizeof(buffer)));
-    return CURLE_BAD_FUNCTION_ARGUMENT;
-  }
-
-  infof(data, "Connect socket %d over QUIC to %s:%d",
-        sockfd, ipbuf, port);
-
-  qs->version = NGTCP2_PROTO_VER_MAX;
-#ifdef USE_OPENSSL
-  qs->sslctx = quic_ssl_ctx(data);
-  if(!qs->sslctx)
-    return CURLE_QUIC_CONNECT_ERROR;
-
-  result = quic_set_client_cert(data, qs);
-  if(result)
-    return result;
-#elif defined(USE_WOLFSSL)
-  qs->sslctx = quic_ssl_ctx(data);
-  if(!qs->sslctx)
-    return CURLE_QUIC_CONNECT_ERROR;
-#endif
-
-  result = quic_init_ssl(qs, data, conn);
-  if(result)
-    return result;
-
-  qs->dcid.datalen = NGTCP2_MAX_CIDLEN;
-  result = Curl_rand(data, qs->dcid.data, NGTCP2_MAX_CIDLEN);
-  if(result)
-    return result;
-
-  qs->scid.datalen = NGTCP2_MAX_CIDLEN;
-  result = Curl_rand(data, qs->scid.data, NGTCP2_MAX_CIDLEN);
-  if(result)
-    return result;
-
-  (void)Curl_qlogdir(data, qs->scid.data, NGTCP2_MAX_CIDLEN, &qfd);
-  qs->qlogfd = qfd; /* -1 if failure above */
-  quic_settings(qs, data->set.buffer_size);
-
-  qs->local_addrlen = sizeof(qs->local_addr);
-  rv = getsockname(sockfd, (struct sockaddr *)&qs->local_addr,
-                   &qs->local_addrlen);
-  if(rv == -1)
-    return CURLE_QUIC_CONNECT_ERROR;
-
-  ngtcp2_addr_init(&path.local, (struct sockaddr *)&qs->local_addr,
-                   qs->local_addrlen);
-  ngtcp2_addr_init(&path.remote, addr, addrlen);
-
-  rc = ngtcp2_conn_client_new(&qs->qconn, &qs->dcid, &qs->scid, &path,
-                              NGTCP2_PROTO_VER_V1, &ng_callbacks,
-                              &qs->settings, &qs->transport_params, NULL, qs);
-  if(rc)
-    return CURLE_QUIC_CONNECT_ERROR;
-
-#ifdef USE_GNUTLS
-  ngtcp2_conn_set_tls_native_handle(qs->qconn, qs->gtls->session);
-#else
-  ngtcp2_conn_set_tls_native_handle(qs->qconn, qs->ssl);
-#endif
-
-  ngtcp2_connection_close_error_default(&qs->last_error);
-
-#if defined(__linux__) && defined(UDP_SEGMENT) && defined(HAVE_SENDMSG)
-  qs->no_gso = FALSE;
-#else
-  qs->no_gso = TRUE;
-#endif
-
-  qs->num_blocked_pkt = 0;
-  qs->num_blocked_pkt_sent = 0;
-  memset(&qs->blocked_pkt, 0, sizeof(qs->blocked_pkt));
-
-  qs->pktbuflen = NGTCP2_MAX_PMTUD_UDP_PAYLOAD_SIZE * MAX_PKT_BURST;
-  qs->pktbuf = malloc(qs->pktbuflen);
-  if(!qs->pktbuf) {
-    ngtcp2_conn_del(qs->qconn);
-    qs->qconn = NULL;
-    return CURLE_OUT_OF_MEMORY;
-  }
-
-  qs->conn_ref.get_conn = get_conn;
-  qs->conn_ref.user_data = qs;
-
-  return CURLE_OK;
-}
-
-/*
- * Store ngtcp2 version info in this buffer.
- */
-void Curl_quic_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);
-}
-
-static int ng_getsock(struct Curl_easy *data, struct connectdata *conn,
-                      curl_socket_t *socks)
-{
-  struct SingleRequest *k = &data->req;
-  int bitmap = GETSOCK_BLANK;
-  struct HTTP *stream = data->req.p.http;
-  struct quicsocket *qs = conn->quic;
-
-  socks[0] = conn->sock[FIRSTSOCKET];
-
-  /* in an HTTP/2 connection we can basically always get a frame so we should
-     always be ready for one */
-  bitmap |= GETSOCK_READSOCK(FIRSTSOCKET);
-
-  /* we're still uploading or the HTTP/2 layer wants to send data */
-  if((k->keepon & (KEEP_SEND|KEEP_SEND_PAUSE)) == KEEP_SEND &&
-     (!stream->h3out || stream->h3out->used < H3_SEND_SIZE) &&
-     ngtcp2_conn_get_cwnd_left(qs->qconn) &&
-     ngtcp2_conn_get_max_data_left(qs->qconn) &&
-     nghttp3_conn_is_stream_writable(qs->h3conn, stream->stream3_id))
-    bitmap |= GETSOCK_WRITESOCK(FIRSTSOCKET);
-
-  return bitmap;
-}
-
-static void qs_disconnect(struct quicsocket *qs)
-{
-  char buffer[NGTCP2_MAX_UDP_PAYLOAD_SIZE];
-  ngtcp2_tstamp ts;
-  ngtcp2_ssize rc;
-
-  if(!qs->conn) /* already closed */
-    return;
-  ts = timestamp();
-  rc = ngtcp2_conn_write_connection_close(qs->qconn, NULL, /* path */
-                                          NULL, /* pkt_info */
-                                          (uint8_t *)buffer, sizeof(buffer),
-                                          &qs->last_error, ts);
-  if(rc > 0) {
-    while((send(qs->conn->sock[FIRSTSOCKET], buffer, rc, 0) == -1) &&
-          SOCKERRNO == EINTR);
-  }
-
-  qs->conn = NULL;
-  if(qs->qlogfd != -1) {
-    close(qs->qlogfd);
-    qs->qlogfd = -1;
-  }
-#ifdef USE_OPENSSL
-  if(qs->ssl)
-    SSL_free(qs->ssl);
-  qs->ssl = NULL;
-  SSL_CTX_free(qs->sslctx);
-#elif defined(USE_GNUTLS)
-  if(qs->gtls) {
-    if(qs->gtls->cred)
-      gnutls_certificate_free_credentials(qs->gtls->cred);
-    if(qs->gtls->session)
-      gnutls_deinit(qs->gtls->session);
-    free(qs->gtls);
-    qs->gtls = NULL;
-  }
-#elif defined(USE_WOLFSSL)
-  if(qs->ssl)
-    wolfSSL_free(qs->ssl);
-  qs->ssl = NULL;
-  wolfSSL_CTX_free(qs->sslctx);
-#endif
-  free(qs->pktbuf);
-  nghttp3_conn_del(qs->h3conn);
-  ngtcp2_conn_del(qs->qconn);
-}
-
-void Curl_quic_disconnect(struct Curl_easy *data,
-                          struct connectdata *conn,
-                          int tempindex)
-{
-  (void)data;
-  if(conn->transport == TRNSPRT_QUIC)
-    qs_disconnect(&conn->hequic[tempindex]);
-}
-
-static CURLcode ng_disconnect(struct Curl_easy *data,
-                              struct connectdata *conn,
-                              bool dead_connection)
-{
-  (void)dead_connection;
-  Curl_quic_disconnect(data, conn, 0);
-  Curl_quic_disconnect(data, conn, 1);
-  return CURLE_OK;
-}
-
-static unsigned int ng_conncheck(struct Curl_easy *data,
-                                 struct connectdata *conn,
-                                 unsigned int checks_to_perform)
-{
-  (void)data;
-  (void)conn;
-  (void)checks_to_perform;
-  return CONNRESULT_NONE;
-}
-
-static const struct Curl_handler Curl_handler_http3 = {
-  "HTTPS",                              /* scheme */
-  ZERO_NULL,                            /* setup_connection */
-  Curl_http,                            /* do_it */
-  Curl_http_done,                       /* done */
-  ZERO_NULL,                            /* do_more */
-  ZERO_NULL,                            /* connect_it */
-  ZERO_NULL,                            /* connecting */
-  ZERO_NULL,                            /* doing */
-  ng_getsock,                           /* proto_getsock */
-  ng_getsock,                           /* doing_getsock */
-  ZERO_NULL,                            /* domore_getsock */
-  ng_getsock,                           /* perform_getsock */
-  ng_disconnect,                        /* disconnect */
-  ZERO_NULL,                            /* readwrite */
-  ng_conncheck,                         /* connection_check */
-  ZERO_NULL,                            /* attach connection */
-  PORT_HTTP,                            /* defport */
-  CURLPROTO_HTTPS,                      /* protocol */
-  CURLPROTO_HTTP,                       /* family */
-  PROTOPT_SSL | PROTOPT_STREAM          /* flags */
-};
-
-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_easy *data = stream_user_data;
-  struct HTTP *stream = data->req.p.http;
-  (void)conn;
-  (void)stream_id;
-  (void)app_error_code;
-  (void)user_data;
-  H3BUGF(infof(data, "cb_h3_stream_close CALLED"));
-
-  stream->closed = TRUE;
-  stream->error3 = app_error_code;
-  Curl_expire(data, 0, EXPIRE_QUIC);
-  /* make sure that ngh3_stream_recv is called again to complete the transfer
-     even if there are no more packets to be received from the server. */
-  data->state.drain = 1;
-  return 0;
-}
-
-/*
- * write_data() copies data to the stream's receive buffer. If not enough
- * space is available in the receive buffer, it copies the rest to the
- * stream's overflow buffer.
- */
-static CURLcode write_data(struct HTTP *stream, const void *mem, size_t memlen)
-{
-  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, buf, len);
-    stream->len -= len;
-    stream->memlen += len;
-    stream->mem += len;
-    buf += len;
-    ncopy -= len;
-  }
-  /* copy the rest to the overflow buffer */
-  if(ncopy)
-    result = Curl_dyn_addn(&stream->overflow, buf, ncopy);
-  return result;
-}
-
-static int cb_h3_recv_data(nghttp3_conn *conn, int64_t stream_id,
-                           const uint8_t *buf, size_t buflen,
-                           void *user_data, void *stream_user_data)
-{
-  struct Curl_easy *data = stream_user_data;
-  struct HTTP *stream = data->req.p.http;
-  CURLcode result = CURLE_OK;
-  (void)conn;
-
-  result = write_data(stream, buf, buflen);
-  if(result) {
-    return -1;
-  }
-  stream->unacked_window += buflen;
-  (void)stream_id;
-  (void)user_data;
-  return 0;
-}
-
-static int cb_h3_deferred_consume(nghttp3_conn *conn, int64_t stream_id,
-                                  size_t consumed, void *user_data,
-                                  void *stream_user_data)
-{
-  struct quicsocket *qs = user_data;
-  (void)conn;
-  (void)stream_user_data;
-  (void)stream_id;
-
-  ngtcp2_conn_extend_max_stream_offset(qs->qconn, stream_id, consumed);
-  ngtcp2_conn_extend_max_offset(qs->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_easy *data = stream_user_data;
-  struct HTTP *stream = data->req.p.http;
-  CURLcode result = CURLE_OK;
-  (void)conn;
-  (void)stream_id;
-  (void)user_data;
-  (void)fin;
-
-  /* add a CRLF only if we've received some headers */
-  if(stream->firstheader) {
-    result = write_data(stream, "\r\n", 2);
-    if(result) {
-      return -1;
-    }
-  }
-
-  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)
-{
-  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)user_data;
-
-  if(token == NGHTTP3_QPACK_TOKEN__STATUS) {
-    char line[14]; /* status line is always 13 characters long */
-    size_t ncopy;
-    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);
-    result = write_data(stream, line, ncopy);
-    if(result) {
-      return -1;
-    }
-  }
-  else {
-    /* store as an HTTP1-style header */
-    result = write_data(stream, h3name.base, h3name.len);
-    if(result) {
-      return -1;
-    }
-    result = write_data(stream, ": ", 2);
-    if(result) {
-      return -1;
-    }
-    result = write_data(stream, h3val.base, h3val.len);
-    if(result) {
-      return -1;
-    }
-    result = write_data(stream, "\r\n", 2);
-    if(result) {
-      return -1;
-    }
-  }
-
-  stream->firstheader = TRUE;
-  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 quicsocket *qs = user_data;
-  int rv;
-  (void)conn;
-  (void)stream_user_data;
-
-  rv = ngtcp2_conn_shutdown_stream_read(qs->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 quicsocket *qs = user_data;
-  int rv;
-  (void)conn;
-  (void)stream_user_data;
-
-  rv = ngtcp2_conn_shutdown_stream_write(qs->qconn, stream_id, app_error_code);
-  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 quicsocket *qs)
-{
-  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(qs->qconn) < 3) {
-    return CURLE_QUIC_CONNECT_ERROR;
-  }
-
-  nghttp3_settings_default(&qs->h3settings);
-
-  rc = nghttp3_conn_client_new(&qs->h3conn,
-                               &ngh3_callbacks,
-                               &qs->h3settings,
-                               nghttp3_mem_default(),
-                               qs);
-  if(rc) {
-    result = CURLE_OUT_OF_MEMORY;
-    goto fail;
-  }
-
-  rc = ngtcp2_conn_open_uni_stream(qs->qconn, &ctrl_stream_id, NULL);
-  if(rc) {
-    result = CURLE_QUIC_CONNECT_ERROR;
-    goto fail;
-  }
-
-  rc = nghttp3_conn_bind_control_stream(qs->h3conn, ctrl_stream_id);
-  if(rc) {
-    result = CURLE_QUIC_CONNECT_ERROR;
-    goto fail;
-  }
-
-  rc = ngtcp2_conn_open_uni_stream(qs->qconn, &qpack_enc_stream_id, NULL);
-  if(rc) {
-    result = CURLE_QUIC_CONNECT_ERROR;
-    goto fail;
-  }
-
-  rc = ngtcp2_conn_open_uni_stream(qs->qconn, &qpack_dec_stream_id, NULL);
-  if(rc) {
-    result = CURLE_QUIC_CONNECT_ERROR;
-    goto fail;
-  }
-
-  rc = nghttp3_conn_bind_qpack_streams(qs->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 Curl_recv ngh3_stream_recv;
-static Curl_send ngh3_stream_send;
-
-static size_t drain_overflow_buffer(struct HTTP *stream)
-{
-  size_t overlen = Curl_dyn_len(&stream->overflow);
-  size_t ncopy = CURLMIN(overlen, stream->len);
-  if(ncopy > 0) {
-    memcpy(stream->mem, Curl_dyn_ptr(&stream->overflow), ncopy);
-    stream->len -= ncopy;
-    stream->mem += 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);
-  }
-  return ncopy;
-}
-
-/* incoming data frames on the h3 stream */
-static ssize_t ngh3_stream_recv(struct Curl_easy *data,
-                                int sockindex,
-                                char *buf,
-                                size_t buffersize,
-                                CURLcode *curlcode)
-{
-  struct connectdata *conn = data->conn;
-  curl_socket_t sockfd = conn->sock[sockindex];
-  struct HTTP *stream = data->req.p.http;
-  struct quicsocket *qs = conn->quic;
-
-  if(!stream->memlen) {
-    /* remember where to store incoming data for this stream and how big the
-       buffer is */
-    stream->mem = buf;
-    stream->len = buffersize;
-  }
-  /* else, there's data in the buffer already */
-
-  /* if there's data in the overflow buffer from a previous call, copy as much
-     as possible to the receive buffer before receiving more */
-  drain_overflow_buffer(stream);
-
-  if(ng_process_ingress(data, sockfd, qs)) {
-    *curlcode = CURLE_RECV_ERROR;
-    return -1;
-  }
-  if(ng_flush_egress(data, sockfd, qs)) {
-    *curlcode = CURLE_SEND_ERROR;
-    return -1;
-  }
-
-  if(stream->memlen) {
-    ssize_t memlen = stream->memlen;
-    /* data arrived */
-    *curlcode = CURLE_OK;
-    /* reset to allow more data to come */
-    stream->memlen = 0;
-    stream->mem = buf;
-    stream->len = buffersize;
-    /* 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 */
-    extend_stream_window(qs->qconn, stream);
-    if(ng_flush_egress(data, sockfd, qs)) {
-      *curlcode = CURLE_SEND_ERROR;
-      return -1;
-    }
-    return memlen;
-  }
-
-  if(stream->closed) {
-    if(stream->error3 != NGHTTP3_H3_NO_ERROR) {
-      failf(data,
-            "HTTP/3 stream %" PRId64 " was not closed cleanly: (err %" PRIu64
-            ")",
-            stream->stream3_id, stream->error3);
-      *curlcode = CURLE_HTTP3;
-      return -1;
-    }
-
-    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);
-      *curlcode = CURLE_HTTP3;
-      return -1;
-    }
-
-    *curlcode = CURLE_OK;
-    return 0;
-  }
-
-  infof(data, "ngh3_stream_recv returns 0 bytes and EAGAIN");
-  *curlcode = CURLE_AGAIN;
-  return -1;
-}
-
-/* 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_easy *data = stream_user_data;
-  struct HTTP *stream = data->req.p.http;
-  (void)user_data;
-
-  if(!data->set.postfields) {
-    stream->h3out->used -= datalen;
-    H3BUGF(infof(data,
-                 "cb_h3_acked_stream_data, %zd 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_easy *data = stream_user_data;
-  size_t nread;
-  struct HTTP *stream = data->req.p.http;
-  (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;
-    }
-    H3BUGF(infof(data, "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)) {
-    H3BUGF(infof(data, "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 http_request(struct Curl_easy *data, const void *mem,
-                             size_t len)
-{
-  struct connectdata *conn = data->conn;
-  struct HTTP *stream = data->req.p.http;
-  size_t nheader;
-  struct quicsocket *qs = conn->quic;
-  CURLcode result = CURLE_OK;
-  nghttp3_nv *nva = NULL;
-  int64_t stream3_id;
-  int rc;
-  struct h3out *h3out = NULL;
-  struct h2h3req *hreq = NULL;
-
-  rc = ngtcp2_conn_open_bidi_stream(qs->qconn, &stream3_id, NULL);
-  if(rc) {
-    failf(data, "can get bidi streams");
-    result = CURLE_SEND_ERROR;
-    goto fail;
-  }
-
-  stream->stream3_id = stream3_id;
-  stream->h3req = TRUE; /* senf off! */
-  Curl_dyn_init(&stream->overflow, CURL_MAX_READ_SIZE);
-
-  result = Curl_pseudo_headers(data, mem, len, &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(qs->h3conn, stream->stream3_id,
-                                     nva, nheader, &data_reader, data);
-    if(rc) {
-      result = CURLE_SEND_ERROR;
-      goto fail;
-    }
-    break;
-  }
-  default:
-    stream->upload_left = 0; /* nothing left to send */
-    rc = nghttp3_conn_submit_request(qs->h3conn, stream->stream3_id,
-                                     nva, nheader, NULL, data);
-    if(rc) {
-      result = CURLE_SEND_ERROR;
-      goto fail;
-    }
-    break;
-  }
-
-  Curl_safefree(nva);
-
-  infof(data, "Using HTTP/3 Stream ID: %x (easy handle %p)",
-        stream3_id, (void *)data);
-
-  Curl_pseudo_free(hreq);
-  return CURLE_OK;
-
-fail:
-  free(nva);
-  Curl_pseudo_free(hreq);
-  return result;
-}
-static ssize_t ngh3_stream_send(struct Curl_easy *data,
-                                int sockindex,
-                                const void *mem,
-                                size_t len,
-                                CURLcode *curlcode)
-{
-  ssize_t sent = 0;
-  struct connectdata *conn = data->conn;
-  struct quicsocket *qs = conn->quic;
-  curl_socket_t sockfd = conn->sock[sockindex];
-  struct HTTP *stream = data->req.p.http;
-
-  if(stream->closed) {
-    *curlcode = CURLE_HTTP3;
-    return -1;
-  }
-
-  if(!stream->h3req) {
-    CURLcode result = http_request(data, mem, len);
-    if(result) {
-      *curlcode = CURLE_SEND_ERROR;
-      return -1;
-    }
-    /* 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 {
-    H3BUGF(infof(data, "ngh3_stream_send() wants to send %zd bytes",
-                 len));
-    if(!stream->upload_len) {
-      stream->upload_mem = mem;
-      stream->upload_len = len;
-      (void)nghttp3_conn_resume_stream(qs->h3conn, stream->stream3_id);
-    }
-    else {
-      *curlcode = CURLE_AGAIN;
-      return -1;
-    }
-  }
-
-  if(ng_flush_egress(data, sockfd, qs)) {
-    *curlcode = CURLE_SEND_ERROR;
-    return -1;
-  }
-
-  /* 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) {
-      *curlcode = CURLE_AGAIN;
-      return -1;
-    }
-  }
-
-  *curlcode = CURLE_OK;
-  return sent;
-}
-
-static CURLcode ng_has_connected(struct Curl_easy *data,
-                                 struct connectdata *conn, int tempindex)
-{
-  CURLcode result = CURLE_OK;
-  const char *hostname, *disp_hostname;
-  int port;
-  char *snihost;
-
-  Curl_conn_get_host(data, FIRSTSOCKET, &hostname, &disp_hostname, &port);
-  snihost = Curl_ssl_snihost(data, hostname, NULL);
-  if(!snihost)
-      return CURLE_PEER_FAILED_VERIFICATION;
-
-  conn->recv[FIRSTSOCKET] = ngh3_stream_recv;
-  conn->send[FIRSTSOCKET] = ngh3_stream_send;
-  conn->handler = &Curl_handler_http3;
-  conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
-  conn->httpversion = 30;
-  conn->bundle->multiuse = BUNDLE_MULTIPLEX;
-  conn->quic = &conn->hequic[tempindex];
-
-  if(conn->ssl_config.verifyhost) {
-#ifdef USE_OPENSSL
-    X509 *server_cert;
-    server_cert = SSL_get_peer_certificate(conn->quic->ssl);
-    if(!server_cert) {
-      return CURLE_PEER_FAILED_VERIFICATION;
-    }
-    result = Curl_ossl_verifyhost(data, conn, server_cert);
-    X509_free(server_cert);
-    if(result)
-      return result;
-#elif defined(USE_GNUTLS)
-    result = Curl_gtls_verifyserver(data, conn->quic->gtls->session,
-                                    &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(conn->quic->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, conn->quic->ssl);
-#endif
-  return result;
-}
-
-/*
- * There can be multiple connection attempts going on in parallel.
- */
-CURLcode Curl_quic_is_connected(struct Curl_easy *data,
-                                struct connectdata *conn,
-                                int sockindex,
-                                bool *done)
-{
-  CURLcode result;
-  struct quicsocket *qs = &conn->hequic[sockindex];
-  curl_socket_t sockfd = conn->tempsock[sockindex];
-
-  result = ng_process_ingress(data, sockfd, qs);
-  if(result)
-    goto error;
-
-  result = ng_flush_egress(data, sockfd, qs);
-  if(result)
-    goto error;
-
-  if(ngtcp2_conn_get_handshake_completed(qs->qconn)) {
-    result = ng_has_connected(data, conn, sockindex);
-    if(!result)
-      *done = TRUE;
-  }
-
-  return result;
-  error:
-  (void)qs_disconnect(qs);
-  return result;
-
-}
-
-static CURLcode ng_process_ingress(struct Curl_easy *data,
-                                   curl_socket_t sockfd,
-                                   struct quicsocket *qs)
-{
-  ssize_t recvd;
-  int rv;
-  uint8_t buf[65536];
-  size_t bufsize = sizeof(buf);
-  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(sockfd, (char *)buf, bufsize, 0,
-                            (struct sockaddr *)&remote_addr,
-                            &remote_addrlen)) == -1 &&
-          SOCKERRNO == EINTR)
-      ;
-    if(recvd == -1) {
-      if(SOCKERRNO == EAGAIN || SOCKERRNO == EWOULDBLOCK)
-        break;
-
-      failf(data, "ngtcp2: recvfrom() unexpectedly returned %zd", recvd);
-      return CURLE_RECV_ERROR;
-    }
-
-    ngtcp2_addr_init(&path.local, (struct sockaddr *)&qs->local_addr,
-                     qs->local_addrlen);
-    ngtcp2_addr_init(&path.remote, (struct sockaddr *)&remote_addr,
-                     remote_addrlen);
-
-    rv = ngtcp2_conn_read_pkt(qs->qconn, &path, &pi, buf, recvd, ts);
-    if(rv) {
-      if(!qs->last_error.error_code) {
-        if(rv == NGTCP2_ERR_CRYPTO) {
-          ngtcp2_connection_close_error_set_transport_error_tls_alert(
-              &qs->last_error, ngtcp2_conn_get_tls_alert(qs->qconn), NULL, 0);
-        }
-        else {
-          ngtcp2_connection_close_error_set_transport_error_liberr(
-              &qs->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;
-    }
-  }
-
-  return CURLE_OK;
-}
-
-static CURLcode do_sendmsg(size_t *sent, struct Curl_easy *data, int sockfd,
-                           struct quicsocket *qs, const uint8_t *pkt,
-                           size_t pktlen, size_t gsolen);
-
-static CURLcode send_packet_no_gso(size_t *psent, struct Curl_easy *data,
-                                   int sockfd, struct quicsocket *qs,
-                                   const uint8_t *pkt, size_t pktlen,
-                                   size_t gsolen)
-{
-  const uint8_t *p, *end = pkt + pktlen;
-  size_t sent;
-
-  *psent = 0;
-
-  for(p = pkt; p < end; p += gsolen) {
-    size_t len = CURLMIN(gsolen, (size_t)(end - p));
-    CURLcode curlcode = do_sendmsg(&sent, data, sockfd, qs, p, len, len);
-    if(curlcode != CURLE_OK) {
-      return curlcode;
-    }
-    *psent += sent;
-  }
-
-  return CURLE_OK;
-}
-
-static CURLcode do_sendmsg(size_t *psent, struct Curl_easy *data, int sockfd,
-                           struct quicsocket *qs, const uint8_t *pkt,
-                           size_t pktlen, size_t gsolen)
-{
-#ifdef HAVE_SENDMSG
-  struct iovec msg_iov;
-  struct msghdr msg = {0};
-  ssize_t sent;
-#if defined(__linux__) && defined(UDP_SEGMENT)
-  uint8_t msg_ctrl[32];
-  struct cmsghdr *cm;
-#endif
-
-  *psent = 0;
-  msg_iov.iov_base = (uint8_t *)pkt;
-  msg_iov.iov_len = pktlen;
-  msg.msg_iov = &msg_iov;
-  msg.msg_iovlen = 1;
-
-#if defined(__linux__) && defined(UDP_SEGMENT)
-  if(pktlen > gsolen) {
-    /* Only set this, when we need it. macOS, for example,
-     * does not seem to like a msg_control of length 0. */
-    msg.msg_control = msg_ctrl;
-    assert(sizeof(msg_ctrl) >= CMSG_SPACE(sizeof(uint16_t)));
-    msg.msg_controllen = CMSG_SPACE(sizeof(uint16_t));
-    cm = CMSG_FIRSTHDR(&msg);
-    cm->cmsg_level = SOL_UDP;
-    cm->cmsg_type = UDP_SEGMENT;
-    cm->cmsg_len = CMSG_LEN(sizeof(uint16_t));
-    *(uint16_t *)(void *)CMSG_DATA(cm) = gsolen & 0xffff;
-  }
-#endif
-
-
-  while((sent = sendmsg(sockfd, &msg, 0)) == -1 && SOCKERRNO == EINTR)
-    ;
-
-  if(sent == -1) {
-    switch(SOCKERRNO) {
-    case EAGAIN:
-#if EAGAIN != EWOULDBLOCK
-    case EWOULDBLOCK:
-#endif
-      return CURLE_AGAIN;
-    case EMSGSIZE:
-      /* UDP datagram is too large; caused by PMTUD. Just let it be lost. */
-      break;
-    case EIO:
-      if(pktlen > gsolen) {
-        /* GSO failure */
-        failf(data, "sendmsg() returned %zd (errno %d); disable GSO", sent,
-              SOCKERRNO);
-        qs->no_gso = TRUE;
-        return send_packet_no_gso(psent, data, sockfd, qs, pkt, pktlen,
-                                  gsolen);
-      }
-      /* FALLTHROUGH */
-    default:
-      failf(data, "sendmsg() returned %zd (errno %d)", sent, SOCKERRNO);
-      return CURLE_SEND_ERROR;
-    }
-  }
-  else {
-    assert(pktlen == (size_t)sent);
-  }
-#else
-  ssize_t sent;
-  (void)qs;
-  (void)gsolen;
-
-  *psent = 0;
-
-  while((sent = send(sockfd, (const char *)pkt, pktlen, 0)) == -1 &&
-        SOCKERRNO == EINTR)
-    ;
-
-  if(sent == -1) {
-    if(SOCKERRNO == EAGAIN || SOCKERRNO == EWOULDBLOCK) {
-      return CURLE_AGAIN;
-    }
-    else {
-      failf(data, "send() returned %zd (errno %d)", sent, SOCKERRNO);
-      if(SOCKERRNO != EMSGSIZE) {
-        return CURLE_SEND_ERROR;
-      }
-      /* UDP datagram is too large; caused by PMTUD. Just let it be
-         lost. */
-    }
-  }
-#endif
-
-  *psent = pktlen;
-
-  return CURLE_OK;
-}
-
-static CURLcode send_packet(size_t *psent, struct Curl_easy *data, int sockfd,
-                            struct quicsocket *qs, const uint8_t *pkt,
-                            size_t pktlen, size_t gsolen)
-{
-  if(qs->no_gso && pktlen > gsolen) {
-    return send_packet_no_gso(psent, data, sockfd, qs, pkt, pktlen, gsolen);
-  }
-
-  return do_sendmsg(psent, data, sockfd, qs, pkt, pktlen, gsolen);
-}
-
-static void push_blocked_pkt(struct quicsocket *qs, const uint8_t *pkt,
-                             size_t pktlen, size_t gsolen)
-{
-  struct blocked_pkt *blkpkt;
-
-  assert(qs->num_blocked_pkt <
-         sizeof(qs->blocked_pkt) / sizeof(qs->blocked_pkt[0]));
-
-  blkpkt = &qs->blocked_pkt[qs->num_blocked_pkt++];
-
-  blkpkt->pkt = pkt;
-  blkpkt->pktlen = pktlen;
-  blkpkt->gsolen = gsolen;
-}
-
-static CURLcode send_blocked_pkt(struct Curl_easy *data, int sockfd,
-                                 struct quicsocket *qs)
-{
-  size_t sent;
-  CURLcode curlcode;
-  struct blocked_pkt *blkpkt;
-
-  for(; qs->num_blocked_pkt_sent < qs->num_blocked_pkt;
-      ++qs->num_blocked_pkt_sent) {
-    blkpkt = &qs->blocked_pkt[qs->num_blocked_pkt_sent];
-    curlcode = send_packet(&sent, data, sockfd, qs, blkpkt->pkt,
-                           blkpkt->pktlen, blkpkt->gsolen);
-
-    if(curlcode) {
-      if(curlcode == CURLE_AGAIN) {
-        blkpkt->pkt += sent;
-        blkpkt->pktlen -= sent;
-      }
-      return curlcode;
-    }
-  }
-
-  qs->num_blocked_pkt = 0;
-  qs->num_blocked_pkt_sent = 0;
-
-  return CURLE_OK;
-}
-
-static CURLcode ng_flush_egress(struct Curl_easy *data,
-                                int sockfd,
-                                struct quicsocket *qs)
-{
-  int rv;
-  size_t sent;
-  ngtcp2_ssize outlen;
-  uint8_t *outpos = qs->pktbuf;
-  size_t max_udp_payload_size =
-      ngtcp2_conn_get_max_tx_udp_payload_size(qs->qconn);
-  size_t path_max_udp_payload_size =
-      ngtcp2_conn_get_path_max_tx_udp_payload_size(qs->qconn);
-  size_t max_pktcnt =
-      CURLMIN(MAX_PKT_BURST, qs->pktbuflen / max_udp_payload_size);
-  size_t pktcnt = 0;
-  size_t gsolen;
-  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(qs->qconn, ts);
-  if(rv) {
-    failf(data, "ngtcp2_conn_handle_expiry returned error: %s",
-          ngtcp2_strerror(rv));
-    ngtcp2_connection_close_error_set_transport_error_liberr(&qs->last_error,
-                                                             rv, NULL, 0);
-    return CURLE_SEND_ERROR;
-  }
-
-  if(qs->num_blocked_pkt) {
-    curlcode = send_blocked_pkt(data, sockfd, qs);
-    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(qs->h3conn && ngtcp2_conn_get_max_data_left(qs->qconn)) {
-      veccnt = nghttp3_conn_writev_stream(qs->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(
-            &qs->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(qs->qconn, &ps.path, NULL, outpos,
-                                       max_udp_payload_size,
-                                       &ndatalen, flags, stream_id,
-                                       (const ngtcp2_vec *)vec, veccnt, ts);
-    if(outlen == 0) {
-      if(outpos != qs->pktbuf) {
-        curlcode = send_packet(&sent, data, sockfd, qs, qs->pktbuf,
-                               outpos - qs->pktbuf, gsolen);
-        if(curlcode) {
-          if(curlcode == CURLE_AGAIN) {
-            push_blocked_pkt(qs, qs->pktbuf + sent, outpos - qs->pktbuf - sent,
-                             gsolen);
-            Curl_expire(data, 1, EXPIRE_QUIC);
-            return CURLE_OK;
-          }
-          return curlcode;
-        }
-      }
-
-      break;
-    }
-    if(outlen < 0) {
-      switch(outlen) {
-      case NGTCP2_ERR_STREAM_DATA_BLOCKED:
-        assert(ndatalen == -1);
-        nghttp3_conn_block_stream(qs->h3conn, stream_id);
-        continue;
-      case NGTCP2_ERR_STREAM_SHUT_WR:
-        assert(ndatalen == -1);
-        nghttp3_conn_shutdown_stream_write(qs->h3conn, stream_id);
-        continue;
-      case NGTCP2_ERR_WRITE_MORE:
-        assert(ndatalen >= 0);
-        rv = nghttp3_conn_add_write_offset(qs->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(
-            &qs->last_error, (int)outlen, NULL, 0);
-        return CURLE_SEND_ERROR;
-      }
-    }
-    else if(ndatalen >= 0) {
-      rv = nghttp3_conn_add_write_offset(qs->h3conn, stream_id, ndatalen);
-      if(rv) {
-        failf(data, "nghttp3_conn_add_write_offset returned error: %s\n",
-              nghttp3_strerror(rv));
-        return CURLE_SEND_ERROR;
-      }
-    }
-
-    outpos += outlen;
-
-    if(pktcnt == 0) {
-      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. */
-      curlcode = send_packet(&sent, data, sockfd, qs, qs->pktbuf,
-                             outpos - outlen - qs->pktbuf, gsolen);
-      if(curlcode) {
-        if(curlcode == CURLE_AGAIN) {
-          push_blocked_pkt(qs, qs->pktbuf + sent,
-                           outpos - outlen - qs->pktbuf - sent, gsolen);
-          push_blocked_pkt(qs, outpos - outlen, outlen, outlen);
-          Curl_expire(data, 1, EXPIRE_QUIC);
-          return CURLE_OK;
-        }
-        return curlcode;
-      }
-      curlcode = send_packet(&sent, data, sockfd, qs, outpos - outlen, outlen,
-                             outlen);
-      if(curlcode) {
-        if(curlcode == CURLE_AGAIN) {
-          assert(0 == sent);
-          push_blocked_pkt(qs, outpos - outlen, outlen, outlen);
-          Curl_expire(data, 1, EXPIRE_QUIC);
-          return CURLE_OK;
-        }
-        return curlcode;
-      }
-
-      pktcnt = 0;
-      outpos = qs->pktbuf;
-      continue;
-    }
-
-    if(++pktcnt >= max_pktcnt || (size_t)outlen < gsolen) {
-      curlcode = send_packet(&sent, data, sockfd, qs, qs->pktbuf,
-                             outpos - qs->pktbuf, gsolen);
-      if(curlcode) {
-        if(curlcode == CURLE_AGAIN) {
-          push_blocked_pkt(qs, qs->pktbuf + sent, outpos - qs->pktbuf - sent,
-                           gsolen);
-          Curl_expire(data, 1, EXPIRE_QUIC);
-          return CURLE_OK;
-        }
-        return curlcode;
-      }
-
-      pktcnt = 0;
-      outpos = qs->pktbuf;
-    }
-  }
-
-  expiry = ngtcp2_conn_get_expiry(qs->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:done_sending when we stop HTTP/3 uploading.
- */
-CURLcode Curl_quic_done_sending(struct Curl_easy *data)
-{
-  struct connectdata *conn = data->conn;
-  DEBUGASSERT(conn);
-  if(conn->handler == &Curl_handler_http3) {
-    /* only for HTTP/3 transfers */
-    struct HTTP *stream = data->req.p.http;
-    struct quicsocket *qs = conn->quic;
-    stream->upload_done = TRUE;
-    (void)nghttp3_conn_resume_stream(qs->h3conn, stream->stream3_id);
-  }
-
-  return CURLE_OK;
-}
-
-/*
- * Called from http.c:Curl_http_done when a request completes.
- */
-void Curl_quic_done(struct Curl_easy *data, bool premature)
-{
-  (void)premature;
-  if(data->conn->handler == &Curl_handler_http3) {
-    /* only for HTTP/3 transfers */
-    struct HTTP *stream = data->req.p.http;
-    Curl_dyn_free(&stream->overflow);
-    free(stream->h3out);
-  }
-}
-
-/*
- * Called from transfer.c:data_pending to know if we should keep looping
- * to receive more data from the connection.
- */
-bool Curl_quic_data_pending(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;
-  return Curl_dyn_len(&stream->overflow) > 0;
-}
-
-/*
- * Called from transfer.c:Curl_readwrite when neither HTTP level read
- * nor write is performed. It is a good place to handle timer expiry
- * for QUIC transport.
- */
-CURLcode Curl_quic_idle(struct Curl_easy *data)
-{
-  struct connectdata *conn = data->conn;
-  curl_socket_t sockfd = conn->sock[FIRSTSOCKET];
-  struct quicsocket *qs = conn->quic;
-
-  if(ngtcp2_conn_get_expiry(qs->qconn) > timestamp()) {
-    return CURLE_OK;
-  }
-
-  if(ng_flush_egress(data, sockfd, qs)) {
-    return CURLE_SEND_ERROR;
-  }
-
-  return CURLE_OK;
-}
-
-#endif
diff --git a/lib/vquic/ngtcp2.h b/lib/vquic/ngtcp2.h
deleted file mode 100644
index 2265999..0000000
--- a/lib/vquic/ngtcp2.h
+++ /dev/null
@@ -1,93 +0,0 @@
-#ifndef HEADER_CURL_VQUIC_NGTCP2_H
-#define HEADER_CURL_VQUIC_NGTCP2_H
-/***************************************************************************
- *                                  _   _ ____  _
- *  Project                     ___| | | |  _ \| |
- *                             / __| | | | |_) | |
- *                            | (__| |_| |  _ <| |___
- *                             \___|\___/|_| \_\_____|
- *
- * Copyright (C) 1998 - 2022, 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 gtls_instance;
-
-struct blocked_pkt {
-  const uint8_t *pkt;
-  size_t pktlen;
-  size_t gsolen;
-};
-
-struct quicsocket {
-  struct connectdata *conn; /* point back to the connection */
-  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 sockaddr_storage local_addr;
-  socklen_t local_addrlen;
-  bool no_gso;
-  uint8_t *pktbuf;
-  size_t pktbuflen;
-  /* the number of entries in blocked_pkt */
-  size_t num_blocked_pkt;
-  /* the number of processed entries in blocked_pkt */
-  size_t num_blocked_pkt_sent;
-  /* the packets blocked by sendmsg (EAGAIN or EWOULDBLOCK) */
-  struct blocked_pkt blocked_pkt[2];
-
-  nghttp3_conn *h3conn;
-  nghttp3_settings h3settings;
-  int qlogfd;
-};
-
-#include "urldata.h"
-
-#endif
-
-#endif /* HEADER_CURL_VQUIC_NGTCP2_H */
diff --git a/lib/vquic/quiche.c b/lib/vquic/quiche.c
deleted file mode 100644
index 2b9a041..0000000
--- a/lib/vquic/quiche.c
+++ /dev/null
@@ -1,892 +0,0 @@
-/***************************************************************************
- *                                  _   _ ____  _
- *  Project                     ___| | | |  _ \| |
- *                             / __| | | | |_) | |
- *                            | (__| |_| |  _ <| |___
- *                             \___|\___/|_| \_\_____|
- *
- * Copyright (C) 1998 - 2022, 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 "sendf.h"
-#include "strdup.h"
-#include "rand.h"
-#include "quic.h"
-#include "strcase.h"
-#include "multiif.h"
-#include "connect.h"
-#include "strerror.h"
-#include "vquic.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 DEBUG_HTTP3
-/* #define DEBUG_QUICHE */
-#ifdef DEBUG_HTTP3
-#define H3BUGF(x) x
-#else
-#define H3BUGF(x) do { } while(0)
-#endif
-
-#define QUIC_MAX_STREAMS (256*1024)
-#define QUIC_MAX_DATA (1*1024*1024)
-#define QUIC_IDLE_TIMEOUT (60 * 1000) /* milliseconds */
-
-static CURLcode process_ingress(struct Curl_easy *data,
-                                curl_socket_t sockfd,
-                                struct quicsocket *qs);
-
-static CURLcode flush_egress(struct Curl_easy *data, curl_socket_t sockfd,
-                             struct quicsocket *qs);
-
-static CURLcode http_request(struct Curl_easy *data, const void *mem,
-                             size_t len);
-static Curl_recv h3_stream_recv;
-static Curl_send h3_stream_send;
-
-static int quiche_getsock(struct Curl_easy *data,
-                          struct connectdata *conn, curl_socket_t *socks)
-{
-  struct SingleRequest *k = &data->req;
-  int bitmap = GETSOCK_BLANK;
-
-  socks[0] = conn->sock[FIRSTSOCKET];
-
-  /* in an HTTP/2 connection we can basically always get a frame so we should
-     always be ready for one */
-  bitmap |= GETSOCK_READSOCK(FIRSTSOCKET);
-
-  /* we're still uploading or the HTTP/2 layer wants to send data */
-  if((k->keepon & (KEEP_SEND|KEEP_SEND_PAUSE)) == KEEP_SEND)
-    bitmap |= GETSOCK_WRITESOCK(FIRSTSOCKET);
-
-  return bitmap;
-}
-
-static CURLcode qs_disconnect(struct Curl_easy *data,
-                              struct quicsocket *qs)
-{
-  DEBUGASSERT(qs);
-  if(qs->conn) {
-    (void)quiche_conn_close(qs->conn, 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)flush_egress(data, qs->sockfd, qs);
-    quiche_conn_free(qs->conn);
-    qs->conn = NULL;
-  }
-  if(qs->h3config)
-    quiche_h3_config_free(qs->h3config);
-  if(qs->h3c)
-    quiche_h3_conn_free(qs->h3c);
-  if(qs->cfg) {
-    quiche_config_free(qs->cfg);
-    qs->cfg = NULL;
-  }
-  return CURLE_OK;
-}
-
-static CURLcode quiche_disconnect(struct Curl_easy *data,
-                                  struct connectdata *conn,
-                                  bool dead_connection)
-{
-  struct quicsocket *qs = conn->quic;
-  (void)dead_connection;
-  return qs_disconnect(data, qs);
-}
-
-void Curl_quic_disconnect(struct Curl_easy *data,
-                          struct connectdata *conn,
-                          int tempindex)
-{
-  if(conn->transport == TRNSPRT_QUIC)
-    qs_disconnect(data, &conn->hequic[tempindex]);
-}
-
-static unsigned int quiche_conncheck(struct Curl_easy *data,
-                                     struct connectdata *conn,
-                                     unsigned int checks_to_perform)
-{
-  (void)data;
-  (void)conn;
-  (void)checks_to_perform;
-  return CONNRESULT_NONE;
-}
-
-static CURLcode quiche_do(struct Curl_easy *data, bool *done)
-{
-  struct HTTP *stream = data->req.p.http;
-  stream->h3req = FALSE; /* not sent */
-  return Curl_http(data, done);
-}
-
-static const struct Curl_handler Curl_handler_http3 = {
-  "HTTPS",                              /* scheme */
-  ZERO_NULL,                            /* setup_connection */
-  quiche_do,                            /* do_it */
-  Curl_http_done,                       /* done */
-  ZERO_NULL,                            /* do_more */
-  ZERO_NULL,                            /* connect_it */
-  ZERO_NULL,                            /* connecting */
-  ZERO_NULL,                            /* doing */
-  quiche_getsock,                       /* proto_getsock */
-  quiche_getsock,                       /* doing_getsock */
-  ZERO_NULL,                            /* domore_getsock */
-  quiche_getsock,                       /* perform_getsock */
-  quiche_disconnect,                    /* disconnect */
-  ZERO_NULL,                            /* readwrite */
-  quiche_conncheck,                     /* connection_check */
-  ZERO_NULL,                            /* attach connection */
-  PORT_HTTP,                            /* defport */
-  CURLPROTO_HTTPS,                      /* protocol */
-  CURLPROTO_HTTP,                       /* family */
-  PROTOPT_SSL | PROTOPT_STREAM          /* flags */
-};
-
-#ifdef DEBUG_QUICHE
-static void quiche_debug_log(const char *line, void *argp)
-{
-  (void)argp;
-  fprintf(stderr, "%s\n", line);
-}
-#endif
-
-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;
-}
-
-static int quic_init_ssl(struct quicsocket *qs, struct connectdata *conn)
-{
-  /* this will need some attention when HTTPS proxy over QUIC get fixed */
-  const char * const hostname = conn->host.name;
-
-  DEBUGASSERT(!qs->ssl);
-  qs->ssl = SSL_new(qs->sslctx);
-
-  SSL_set_app_data(qs->ssl, qs);
-
-  /* set SNI */
-  SSL_set_tlsext_host_name(qs->ssl, hostname);
-  return 0;
-}
-
-
-CURLcode Curl_quic_connect(struct Curl_easy *data,
-                           struct connectdata *conn, curl_socket_t sockfd,
-                           int sockindex,
-                           const struct sockaddr *addr, socklen_t addrlen)
-{
-  CURLcode result;
-  struct quicsocket *qs = &conn->hequic[sockindex];
-  char ipbuf[40];
-  int port;
-  int rv;
-
-#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
-
-  (void)addr;
-  (void)addrlen;
-
-  qs->sockfd = sockfd;
-  qs->cfg = quiche_config_new(QUICHE_PROTOCOL_VERSION);
-  if(!qs->cfg) {
-    failf(data, "can't create quiche config");
-    return CURLE_FAILED_INIT;
-  }
-
-  quiche_config_set_max_idle_timeout(qs->cfg, QUIC_IDLE_TIMEOUT);
-  quiche_config_set_initial_max_data(qs->cfg, QUIC_MAX_DATA);
-  quiche_config_set_initial_max_stream_data_bidi_local(qs->cfg, QUIC_MAX_DATA);
-  quiche_config_set_initial_max_stream_data_bidi_remote(qs->cfg,
-                                                        QUIC_MAX_DATA);
-  quiche_config_set_initial_max_stream_data_uni(qs->cfg, QUIC_MAX_DATA);
-  quiche_config_set_initial_max_streams_bidi(qs->cfg, QUIC_MAX_STREAMS);
-  quiche_config_set_initial_max_streams_uni(qs->cfg, QUIC_MAX_STREAMS);
-  quiche_config_set_application_protos(qs->cfg,
-                                       (uint8_t *)
-                                       QUICHE_H3_APPLICATION_PROTOCOL,
-                                       sizeof(QUICHE_H3_APPLICATION_PROTOCOL)
-                                       - 1);
-
-  qs->sslctx = quic_ssl_ctx(data);
-  if(!qs->sslctx)
-    return CURLE_QUIC_CONNECT_ERROR;
-
-  if(quic_init_ssl(qs, conn))
-    return CURLE_QUIC_CONNECT_ERROR;
-
-  result = Curl_rand(data, qs->scid, sizeof(qs->scid));
-  if(result)
-    return result;
-
-  qs->local_addrlen = sizeof(qs->local_addr);
-  rv = getsockname(sockfd, (struct sockaddr *)&qs->local_addr,
-                   &qs->local_addrlen);
-  if(rv == -1)
-    return CURLE_QUIC_CONNECT_ERROR;
-
-  qs->conn = quiche_conn_new_with_tls((const uint8_t *) qs->scid,
-                                      sizeof(qs->scid), NULL, 0,
-                                      (struct sockaddr *)&qs->local_addr,
-                                      qs->local_addrlen, addr, addrlen,
-                                      qs->cfg, qs->ssl, false);
-  if(!qs->conn) {
-    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, qs->scid, sizeof(qs->scid), &qfd);
-    if(qfd != -1)
-      quiche_conn_set_qlog_fd(qs->conn, qfd,
-                              "qlog title", "curl qlog");
-  }
-#endif
-
-  result = flush_egress(data, sockfd, qs);
-  if(result)
-    return result;
-
-  /* extract the used address as a string */
-  if(!Curl_addr2string((struct sockaddr*)addr, addrlen, ipbuf, &port)) {
-    char buffer[STRERROR_LEN];
-    failf(data, "ssrem inet_ntop() failed with errno %d: %s",
-          SOCKERRNO, Curl_strerror(SOCKERRNO, buffer, sizeof(buffer)));
-    return CURLE_BAD_FUNCTION_ARGUMENT;
-  }
-
-  infof(data, "Connect socket %d over QUIC to %s:%ld",
-        sockfd, ipbuf, port);
-
-  Curl_persistconninfo(data, conn, NULL, -1);
-
-  {
-    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;
-    }
-
-    infof(data, "Sent QUIC client Initial, ALPN: %s",
-          alpn_protocols + 1);
-  }
-
-  return CURLE_OK;
-}
-
-static CURLcode quiche_has_connected(struct Curl_easy *data,
-                                     struct connectdata *conn,
-                                     int sockindex,
-                                     int tempindex)
-{
-  CURLcode result;
-  struct quicsocket *qs = conn->quic = &conn->hequic[tempindex];
-
-  conn->recv[sockindex] = h3_stream_recv;
-  conn->send[sockindex] = h3_stream_send;
-  conn->handler = &Curl_handler_http3;
-  conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
-  conn->httpversion = 30;
-  conn->bundle->multiuse = BUNDLE_MULTIPLEX;
-
-  if(conn->ssl_config.verifyhost) {
-    X509 *server_cert;
-    server_cert = SSL_get_peer_certificate(qs->ssl);
-    if(!server_cert) {
-      return CURLE_PEER_FAILED_VERIFICATION;
-    }
-    result = Curl_ossl_verifyhost(data, conn, server_cert);
-    X509_free(server_cert);
-    if(result)
-      return result;
-    infof(data, "Verified certificate just fine");
-  }
-  else
-    infof(data, "Skipped certificate verification");
-
-  qs->h3config = quiche_h3_config_new();
-  if(!qs->h3config)
-    return CURLE_OUT_OF_MEMORY;
-
-  /* Create a new HTTP/3 connection on the QUIC connection. */
-  qs->h3c = quiche_h3_conn_new_with_transport(qs->conn, qs->h3config);
-  if(!qs->h3c) {
-    result = CURLE_OUT_OF_MEMORY;
-    goto fail;
-  }
-  if(conn->hequic[1-tempindex].cfg) {
-    qs = &conn->hequic[1-tempindex];
-    quiche_config_free(qs->cfg);
-    quiche_conn_free(qs->conn);
-    qs->cfg = NULL;
-    qs->conn = NULL;
-  }
-  if(data->set.ssl.certinfo)
-    /* asked to gather certificate info */
-    (void)Curl_ossl_certchain(data, qs->ssl);
-
-  return CURLE_OK;
-  fail:
-  quiche_h3_config_free(qs->h3config);
-  quiche_h3_conn_free(qs->h3c);
-  return result;
-}
-
-/*
- * This function gets polled to check if this QUIC connection has connected.
- */
-CURLcode Curl_quic_is_connected(struct Curl_easy *data,
-                                struct connectdata *conn,
-                                int sockindex,
-                                bool *done)
-{
-  CURLcode result;
-  struct quicsocket *qs = &conn->hequic[sockindex];
-  curl_socket_t sockfd = conn->tempsock[sockindex];
-
-  result = process_ingress(data, sockfd, qs);
-  if(result)
-    goto error;
-
-  result = flush_egress(data, sockfd, qs);
-  if(result)
-    goto error;
-
-  if(quiche_conn_is_established(qs->conn)) {
-    *done = TRUE;
-    result = quiche_has_connected(data, conn, 0, sockindex);
-    DEBUGF(infof(data, "quiche established connection"));
-  }
-
-  return result;
-  error:
-  qs_disconnect(data, qs);
-  return result;
-}
-
-static CURLcode process_ingress(struct Curl_easy *data, int sockfd,
-                                struct quicsocket *qs)
-{
-  ssize_t recvd;
-  uint8_t *buf = (uint8_t *)data->state.buffer;
-  size_t bufsize = data->set.buffer_size;
-  struct sockaddr_storage from;
-  socklen_t from_len;
-  quiche_recv_info recv_info;
-
-  DEBUGASSERT(qs->conn);
-
-  /* in case the timeout expired */
-  quiche_conn_on_timeout(qs->conn);
-
-  do {
-    from_len = sizeof(from);
-
-    recvd = recvfrom(sockfd, buf, bufsize, 0,
-                     (struct sockaddr *)&from, &from_len);
-
-    if((recvd < 0) && ((SOCKERRNO == EAGAIN) || (SOCKERRNO == EWOULDBLOCK)))
-      break;
-
-    if(recvd < 0) {
-      failf(data, "quiche: recvfrom() unexpectedly returned %zd "
-            "(errno: %d, socket %d)", recvd, SOCKERRNO, sockfd);
-      return CURLE_RECV_ERROR;
-    }
-
-    recv_info.from = (struct sockaddr *) &from;
-    recv_info.from_len = from_len;
-    recv_info.to = (struct sockaddr *) &qs->local_addr;
-    recv_info.to_len = qs->local_addrlen;
-
-    recvd = quiche_conn_recv(qs->conn, buf, recvd, &recv_info);
-    if(recvd == QUICHE_ERR_DONE)
-      break;
-
-    if(recvd < 0) {
-      if(QUICHE_ERR_TLS_FAIL == recvd) {
-        long verify_ok = SSL_get_verify_result(qs->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;
-        }
-      }
-
-      failf(data, "quiche_conn_recv() == %zd", recvd);
-
-      return CURLE_RECV_ERROR;
-    }
-  } while(1);
-
-  return CURLE_OK;
-}
-
-/*
- * flush_egress drains the buffers and sends off data.
- * Calls failf() on errors.
- */
-static CURLcode flush_egress(struct Curl_easy *data, int sockfd,
-                             struct quicsocket *qs)
-{
-  ssize_t sent;
-  uint8_t out[1200];
-  int64_t timeout_ns;
-  quiche_send_info send_info;
-
-  do {
-    sent = quiche_conn_send(qs->conn, out, sizeof(out), &send_info);
-    if(sent == QUICHE_ERR_DONE)
-      break;
-
-    if(sent < 0) {
-      failf(data, "quiche_conn_send returned %zd", sent);
-      return CURLE_SEND_ERROR;
-    }
-
-    sent = send(sockfd, out, sent, 0);
-    if(sent < 0) {
-      failf(data, "send() returned %zd", sent);
-      return CURLE_SEND_ERROR;
-    }
-  } while(1);
-
-  /* time until the next timeout event, as nanoseconds. */
-  timeout_ns = quiche_conn_timeout_as_nanos(qs->conn);
-  if(timeout_ns)
-    /* expire uses milliseconds */
-    Curl_expire(data, (timeout_ns + 999999) / 1000000, EXPIRE_QUIC);
-
-  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\n",
-              (int) value_len, value);
-  }
-  else if(!headers->nlen) {
-    return CURLE_HTTP3;
-  }
-  else {
-    msnprintf(headers->dest,
-              headers->destlen, "%.*s: %.*s\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 h3_stream_recv(struct Curl_easy *data,
-                              int sockindex,
-                              char *buf,
-                              size_t buffersize,
-                              CURLcode *curlcode)
-{
-  ssize_t recvd = -1;
-  ssize_t rcode;
-  struct connectdata *conn = data->conn;
-  struct quicsocket *qs = conn->quic;
-  curl_socket_t sockfd = conn->sock[sockindex];
-  quiche_h3_event *ev;
-  int rc;
-  struct h3h1header headers;
-  struct HTTP *stream = data->req.p.http;
-  headers.dest = buf;
-  headers.destlen = buffersize;
-  headers.nlen = 0;
-
-  if(process_ingress(data, sockfd, qs)) {
-    infof(data, "h3_stream_recv returns on ingress");
-    *curlcode = CURLE_RECV_ERROR;
-    return -1;
-  }
-
-  if(qs->h3_recving) {
-    /* body receiving state */
-    rcode = quiche_h3_recv_body(qs->h3c, qs->conn, stream->stream3_id,
-                                (unsigned char *)buf, buffersize);
-    if(rcode <= 0) {
-      recvd = -1;
-      qs->h3_recving = FALSE;
-      /* fall through into the while loop below */
-    }
-    else
-      recvd = rcode;
-  }
-
-  while(recvd < 0) {
-    int64_t s = quiche_h3_conn_poll(qs->h3c, qs->conn, &ev);
-    if(s < 0)
-      /* nothing more to do */
-      break;
-
-    if(s != stream->stream3_id) {
-      /* another transfer, ignore for now */
-      infof(data, "Got h3 for stream %u, expects %u",
-            s, stream->stream3_id);
-      continue;
-    }
-
-    switch(quiche_h3_event_type(ev)) {
-    case QUICHE_H3_EVENT_HEADERS:
-      rc = quiche_h3_event_for_each_header(ev, cb_each_header, &headers);
-      if(rc) {
-        *curlcode = rc;
-        failf(data, "Error in HTTP/3 response header");
-        break;
-      }
-      recvd = headers.nlen;
-      break;
-    case QUICHE_H3_EVENT_DATA:
-      if(!stream->firstbody) {
-        /* add a header-body separator CRLF */
-        buf[0] = '\r';
-        buf[1] = '\n';
-        buf += 2;
-        buffersize -= 2;
-        stream->firstbody = TRUE;
-        recvd = 2; /* two bytes already */
-      }
-      else
-        recvd = 0;
-      rcode = quiche_h3_recv_body(qs->h3c, qs->conn, s, (unsigned char *)buf,
-                                  buffersize);
-      if(rcode <= 0) {
-        recvd = -1;
-        break;
-      }
-      qs->h3_recving = TRUE;
-      recvd += rcode;
-      break;
-
-    case QUICHE_H3_EVENT_RESET:
-      streamclose(conn, "Stream reset");
-      *curlcode = CURLE_PARTIAL_FILE;
-      return -1;
-
-    case QUICHE_H3_EVENT_FINISHED:
-      streamclose(conn, "End of stream");
-      recvd = 0; /* end of stream */
-      break;
-    default:
-      break;
-    }
-
-    quiche_h3_event_free(ev);
-  }
-  if(flush_egress(data, sockfd, qs)) {
-    *curlcode = CURLE_SEND_ERROR;
-    return -1;
-  }
-
-  *curlcode = (-1 == recvd)? CURLE_AGAIN : CURLE_OK;
-  if(recvd >= 0)
-    /* Get this called again to drain the event queue */
-    Curl_expire(data, 0, EXPIRE_QUIC);
-
-  data->state.drain = (recvd >= 0) ? 1 : 0;
-  return recvd;
-}
-
-static ssize_t h3_stream_send(struct Curl_easy *data,
-                              int sockindex,
-                              const void *mem,
-                              size_t len,
-                              CURLcode *curlcode)
-{
-  ssize_t sent;
-  struct connectdata *conn = data->conn;
-  struct quicsocket *qs = conn->quic;
-  curl_socket_t sockfd = conn->sock[sockindex];
-  struct HTTP *stream = data->req.p.http;
-
-  if(!stream->h3req) {
-    CURLcode result = http_request(data, mem, len);
-    if(result) {
-      *curlcode = CURLE_SEND_ERROR;
-      return -1;
-    }
-    sent = len;
-  }
-  else {
-    sent = quiche_h3_send_body(qs->h3c, qs->conn, stream->stream3_id,
-                               (uint8_t *)mem, len, FALSE);
-    if(sent == QUICHE_H3_ERR_DONE) {
-      sent = 0;
-    }
-    else if(sent < 0) {
-      *curlcode = CURLE_SEND_ERROR;
-      return -1;
-    }
-  }
-
-  if(flush_egress(data, sockfd, qs)) {
-    *curlcode = CURLE_SEND_ERROR;
-    return -1;
-  }
-
-  *curlcode = CURLE_OK;
-  return sent;
-}
-
-/*
- * Store quiche version info in this buffer.
- */
-void Curl_quic_ver(char *p, size_t len)
-{
-  (void)msnprintf(p, len, "quiche/%s", quiche_version());
-}
-
-/* Index where :authority header field will appear in request header
-   field list. */
-#define AUTHORITY_DST_IDX 3
-
-static CURLcode http_request(struct Curl_easy *data, const void *mem,
-                             size_t len)
-{
-  struct connectdata *conn = data->conn;
-  struct HTTP *stream = data->req.p.http;
-  size_t nheader;
-  int64_t stream3_id;
-  quiche_h3_header *nva = NULL;
-  struct quicsocket *qs = conn->quic;
-  CURLcode result = CURLE_OK;
-  struct h2h3req *hreq = NULL;
-
-  stream->h3req = TRUE; /* senf off! */
-
-  result = Curl_pseudo_headers(data, mem, len, &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 */
-
-    stream3_id = quiche_h3_send_request(qs->h3c, qs->conn, nva, nheader,
-                                        stream->upload_left ? FALSE: TRUE);
-    if((stream3_id >= 0) && data->set.postfields) {
-      ssize_t sent = quiche_h3_send_body(qs->h3c, qs->conn, stream3_id,
-                                         (uint8_t *)data->set.postfields,
-                                         stream->upload_left, TRUE);
-      if(sent <= 0) {
-        failf(data, "quiche_h3_send_body failed");
-        result = CURLE_SEND_ERROR;
-      }
-      stream->upload_left = 0; /* nothing left to send */
-    }
-    break;
-  default:
-    stream3_id = quiche_h3_send_request(qs->h3c, qs->conn, nva, nheader,
-                                        TRUE);
-    break;
-  }
-
-  Curl_safefree(nva);
-
-  if(stream3_id < 0) {
-    H3BUGF(infof(data, "quiche_h3_send_request returned %d",
-                 stream3_id));
-    result = CURLE_SEND_ERROR;
-    goto fail;
-  }
-
-  infof(data, "Using HTTP/3 Stream ID: %x (easy handle %p)",
-        stream3_id, (void *)data);
-  stream->stream3_id = stream3_id;
-
-  Curl_pseudo_free(hreq);
-  return CURLE_OK;
-
-fail:
-  free(nva);
-  Curl_pseudo_free(hreq);
-  return result;
-}
-
-/*
- * Called from transfer.c:done_sending when we stop HTTP/3 uploading.
- */
-CURLcode Curl_quic_done_sending(struct Curl_easy *data)
-{
-  struct connectdata *conn = data->conn;
-  DEBUGASSERT(conn);
-  if(conn->handler == &Curl_handler_http3) {
-    /* only for HTTP/3 transfers */
-    ssize_t sent;
-    struct HTTP *stream = data->req.p.http;
-    struct quicsocket *qs = conn->quic;
-    stream->upload_done = TRUE;
-    sent = quiche_h3_send_body(qs->h3c, qs->conn, stream->stream3_id,
-                               NULL, 0, TRUE);
-    if(sent < 0)
-      return CURLE_SEND_ERROR;
-  }
-
-  return CURLE_OK;
-}
-
-/*
- * Called from http.c:Curl_http_done when a request completes.
- */
-void Curl_quic_done(struct Curl_easy *data, bool premature)
-{
-  (void)data;
-  (void)premature;
-}
-
-/*
- * Called from transfer.c:data_pending to know if we should keep looping
- * to receive more data from the connection.
- */
-bool Curl_quic_data_pending(const struct Curl_easy *data)
-{
-  (void)data;
-  return FALSE;
-}
-
-/*
- * Called from transfer.c:Curl_readwrite when neither HTTP level read
- * nor write is performed. It is a good place to handle timer expiry
- * for QUIC transport.
- */
-CURLcode Curl_quic_idle(struct Curl_easy *data)
-{
-  (void)data;
-  return CURLE_OK;
-}
-
-#endif
diff --git a/lib/vquic/quiche.h b/lib/vquic/quiche.h
deleted file mode 100644
index 2da65f5..0000000
--- a/lib/vquic/quiche.h
+++ /dev/null
@@ -1,58 +0,0 @@
-#ifndef HEADER_CURL_VQUIC_QUICHE_H
-#define HEADER_CURL_VQUIC_QUICHE_H
-/***************************************************************************
- *                                  _   _ ____  _
- *  Project                     ___| | | |  _ \| |
- *                             / __| | | | |_) | |
- *                            | (__| |_| |  _ <| |___
- *                             \___|\___/|_| \_\_____|
- *
- * Copyright (C) 1998 - 2022, 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 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 quicsocket {
-  quiche_config *cfg;
-  quiche_conn *conn;
-  quiche_h3_conn *h3c;
-  quiche_h3_config *h3config;
-  uint8_t scid[QUICHE_MAX_CONN_ID_LEN];
-  curl_socket_t sockfd;
-  uint32_t version;
-  SSL_CTX *sslctx;
-  SSL *ssl;
-  bool h3_recving; /* TRUE when in h3-body-reading state */
-  struct sockaddr_storage local_addr;
-  socklen_t local_addrlen;
-};
-
-#endif
-
-#endif /* HEADER_CURL_VQUIC_QUICHE_H */
diff --git a/lib/vquic/vquic.c b/lib/vquic/vquic.c
index e52a4f3..5f4f30d 100644
--- a/lib/vquic/vquic.c
+++ b/lib/vquic/vquic.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -24,15 +24,26 @@
 
 #include "curl_setup.h"
 
-#ifdef ENABLE_QUIC
-
 #ifdef HAVE_FCNTL_H
 #include <fcntl.h>
 #endif
 #include "urldata.h"
 #include "dynbuf.h"
-#include "curl_printf.h"
+#include "cfilters.h"
+#include "curl_log.h"
+#include "curl_msh3.h"
+#include "curl_ngtcp2.h"
+#include "curl_quiche.h"
 #include "vquic.h"
+#include "vquic_int.h"
+
+/* The last 3 #include files should be in this order */
+#include "curl_printf.h"
+#include "curl_memory.h"
+#include "memdebug.h"
+
+
+#ifdef ENABLE_QUIC
 
 #ifdef O_BINARY
 #define QLOGMODE O_WRONLY|O_CREAT|O_BINARY
@@ -40,6 +51,231 @@
 #define QLOGMODE O_WRONLY|O_CREAT
 #endif
 
+void Curl_quic_ver(char *p, size_t len)
+{
+#ifdef USE_NGTCP2
+  Curl_ngtcp2_ver(p, len);
+#elif defined(USE_QUICHE)
+  Curl_quiche_ver(p, len);
+#elif defined(USE_MSH3)
+  Curl_msh3_ver(p, len);
+#endif
+}
+
+CURLcode vquic_ctx_init(struct cf_quic_ctx *qctx, size_t pktbuflen)
+{
+  qctx->num_blocked_pkt = 0;
+  qctx->num_blocked_pkt_sent = 0;
+  memset(&qctx->blocked_pkt, 0, sizeof(qctx->blocked_pkt));
+
+  qctx->pktbuflen = pktbuflen;
+  qctx->pktbuf = malloc(qctx->pktbuflen);
+  if(!qctx->pktbuf)
+    return CURLE_OUT_OF_MEMORY;
+
+#if defined(__linux__) && defined(UDP_SEGMENT) && defined(HAVE_SENDMSG)
+  qctx->no_gso = FALSE;
+#else
+  qctx->no_gso = TRUE;
+#endif
+
+  return CURLE_OK;
+}
+
+void vquic_ctx_free(struct cf_quic_ctx *qctx)
+{
+  free(qctx->pktbuf);
+  qctx->pktbuf = NULL;
+}
+
+static CURLcode send_packet_no_gso(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);
+
+static CURLcode do_sendmsg(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)
+{
+#ifdef HAVE_SENDMSG
+  struct iovec msg_iov;
+  struct msghdr msg = {0};
+  ssize_t sent;
+#if defined(__linux__) && defined(UDP_SEGMENT)
+  uint8_t msg_ctrl[32];
+  struct cmsghdr *cm;
+#endif
+
+  *psent = 0;
+  msg_iov.iov_base = (uint8_t *)pkt;
+  msg_iov.iov_len = pktlen;
+  msg.msg_iov = &msg_iov;
+  msg.msg_iovlen = 1;
+
+#if defined(__linux__) && defined(UDP_SEGMENT)
+  if(pktlen > gsolen) {
+    /* Only set this, when we need it. macOS, for example,
+     * does not seem to like a msg_control of length 0. */
+    msg.msg_control = msg_ctrl;
+    assert(sizeof(msg_ctrl) >= CMSG_SPACE(sizeof(uint16_t)));
+    msg.msg_controllen = CMSG_SPACE(sizeof(uint16_t));
+    cm = CMSG_FIRSTHDR(&msg);
+    cm->cmsg_level = SOL_UDP;
+    cm->cmsg_type = UDP_SEGMENT;
+    cm->cmsg_len = CMSG_LEN(sizeof(uint16_t));
+    *(uint16_t *)(void *)CMSG_DATA(cm) = gsolen & 0xffff;
+  }
+#endif
+
+
+  while((sent = sendmsg(qctx->sockfd, &msg, 0)) == -1 && SOCKERRNO == EINTR)
+    ;
+
+  if(sent == -1) {
+    switch(SOCKERRNO) {
+    case EAGAIN:
+#if EAGAIN != EWOULDBLOCK
+    case EWOULDBLOCK:
+#endif
+      return CURLE_AGAIN;
+    case EMSGSIZE:
+      /* UDP datagram is too large; caused by PMTUD. Just let it be lost. */
+      break;
+    case EIO:
+      if(pktlen > gsolen) {
+        /* GSO failure */
+        failf(data, "sendmsg() returned %zd (errno %d); disable GSO", sent,
+              SOCKERRNO);
+        qctx->no_gso = TRUE;
+        return send_packet_no_gso(cf, data, qctx, pkt, pktlen, gsolen, psent);
+      }
+      /* FALLTHROUGH */
+    default:
+      failf(data, "sendmsg() returned %zd (errno %d)", sent, SOCKERRNO);
+      return CURLE_SEND_ERROR;
+    }
+  }
+  else {
+    assert(pktlen == (size_t)sent);
+  }
+#else
+  ssize_t sent;
+  (void)gsolen;
+
+  *psent = 0;
+
+  while((sent = send(qctx->sockfd, (const char *)pkt, pktlen, 0)) == -1 &&
+        SOCKERRNO == EINTR)
+    ;
+
+  if(sent == -1) {
+    if(SOCKERRNO == EAGAIN || SOCKERRNO == EWOULDBLOCK) {
+      return CURLE_AGAIN;
+    }
+    else {
+      failf(data, "send() returned %zd (errno %d)", sent, SOCKERRNO);
+      if(SOCKERRNO != EMSGSIZE) {
+        return CURLE_SEND_ERROR;
+      }
+      /* UDP datagram is too large; caused by PMTUD. Just let it be
+         lost. */
+    }
+  }
+#endif
+  (void)cf;
+  *psent = pktlen;
+
+  return CURLE_OK;
+}
+
+static CURLcode send_packet_no_gso(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)
+{
+  const uint8_t *p, *end = pkt + pktlen;
+  size_t sent;
+
+  *psent = 0;
+
+  for(p = pkt; p < end; p += gsolen) {
+    size_t len = CURLMIN(gsolen, (size_t)(end - p));
+    CURLcode curlcode = do_sendmsg(cf, data, qctx, p, len, len, &sent);
+    if(curlcode != CURLE_OK) {
+      return curlcode;
+    }
+    *psent += sent;
+  }
+
+  return CURLE_OK;
+}
+
+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)
+{
+  if(qctx->no_gso && pktlen > gsolen) {
+    return send_packet_no_gso(cf, data, qctx, pkt, pktlen, gsolen, psent);
+  }
+
+  return do_sendmsg(cf, data, qctx, pkt, pktlen, gsolen, 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)
+{
+  struct vquic_blocked_pkt *blkpkt;
+
+  (void)cf;
+  assert(qctx->num_blocked_pkt <
+         sizeof(qctx->blocked_pkt) / sizeof(qctx->blocked_pkt[0]));
+
+  blkpkt = &qctx->blocked_pkt[qctx->num_blocked_pkt++];
+
+  blkpkt->pkt = pkt;
+  blkpkt->pktlen = pktlen;
+  blkpkt->gsolen = gsolen;
+}
+
+CURLcode vquic_send_blocked_pkt(struct Curl_cfilter *cf,
+                                struct Curl_easy *data,
+                                struct cf_quic_ctx *qctx)
+{
+  size_t sent;
+  CURLcode curlcode;
+  struct vquic_blocked_pkt *blkpkt;
+
+  (void)cf;
+  for(; qctx->num_blocked_pkt_sent < qctx->num_blocked_pkt;
+      ++qctx->num_blocked_pkt_sent) {
+    blkpkt = &qctx->blocked_pkt[qctx->num_blocked_pkt_sent];
+    curlcode = vquic_send_packet(cf, data, qctx, blkpkt->pkt,
+                                 blkpkt->pktlen, blkpkt->gsolen, &sent);
+
+    if(curlcode) {
+      if(curlcode == CURLE_AGAIN) {
+        blkpkt->pkt += sent;
+        blkpkt->pktlen -= sent;
+      }
+      return curlcode;
+    }
+  }
+
+  qctx->num_blocked_pkt = 0;
+  qctx->num_blocked_pkt_sent = 0;
+
+  return CURLE_OK;
+}
+
 /*
  * If the QLOGDIR environment variable is set, open and return a file
  * descriptor to write the log to.
@@ -84,4 +320,76 @@ CURLcode Curl_qlogdir(struct Curl_easy *data,
 
   return CURLE_OK;
 }
+
+CURLcode Curl_cf_quic_create(struct Curl_cfilter **pcf,
+                             struct Curl_easy *data,
+                             struct connectdata *conn,
+                             const struct Curl_addrinfo *ai,
+                             int transport)
+{
+  (void)transport;
+  DEBUGASSERT(transport == TRNSPRT_QUIC);
+#ifdef USE_NGTCP2
+  return Curl_cf_ngtcp2_create(pcf, data, conn, ai);
+#elif defined(USE_QUICHE)
+  return Curl_cf_quiche_create(pcf, data, conn, ai);
+#elif defined(USE_MSH3)
+  return Curl_cf_msh3_create(pcf, data, conn, ai);
+#else
+  *pcf = NULL;
+  (void)data;
+  (void)conn;
+  (void)ai;
+  return CURLE_NOT_BUILT_IN;
+#endif
+}
+
+bool Curl_conn_is_http3(const struct Curl_easy *data,
+                        const struct connectdata *conn,
+                        int sockindex)
+{
+#ifdef USE_NGTCP2
+  return Curl_conn_is_ngtcp2(data, conn, sockindex);
+#elif defined(USE_QUICHE)
+  return Curl_conn_is_quiche(data, conn, sockindex);
+#elif defined(USE_MSH3)
+  return Curl_conn_is_msh3(data, conn, sockindex);
+#else
+  return ((conn->handler->protocol & PROTO_FAMILY_HTTP) &&
+          (conn->httpversion == 30));
+#endif
+}
+
+CURLcode Curl_conn_may_http3(struct Curl_easy *data,
+                             const struct connectdata *conn)
+{
+  if(!(conn->handler->flags & PROTOPT_SSL)) {
+    failf(data, "HTTP/3 requested for non-HTTPS URL");
+    return CURLE_URL_MALFORMAT;
+  }
+#ifndef CURL_DISABLE_PROXY
+  if(conn->bits.socksproxy) {
+    failf(data, "HTTP/3 is not supported over a SOCKS proxy");
+    return CURLE_URL_MALFORMAT;
+  }
+  if(conn->bits.httpproxy && conn->bits.tunnel_proxy) {
+    failf(data, "HTTP/3 is not supported over a HTTP proxy");
+    return CURLE_URL_MALFORMAT;
+  }
 #endif
+
+  return CURLE_OK;
+}
+
+#else /* ENABLE_QUIC */
+
+CURLcode Curl_conn_may_http3(struct Curl_easy *data,
+                             const struct connectdata *conn)
+{
+  (void)conn;
+  (void)data;
+  DEBUGF(infof(data, "QUIC is not supported in this build"));
+  return CURLE_NOT_BUILT_IN;
+}
+
+#endif /* !ENABLE_QUIC */
diff --git a/lib/vquic/vquic.h b/lib/vquic/vquic.h
index 8f599a8..dc73957 100644
--- a/lib/vquic/vquic.h
+++ b/lib/vquic/vquic.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -27,10 +27,38 @@
 #include "curl_setup.h"
 
 #ifdef ENABLE_QUIC
+struct Curl_cfilter;
+struct Curl_easy;
+struct connectdata;
+struct Curl_addrinfo;
+
+void Curl_quic_ver(char *p, size_t len);
+
 CURLcode Curl_qlogdir(struct Curl_easy *data,
                       unsigned char *scid,
                       size_t scidlen,
                       int *qlogfdp);
-#endif
+
+
+CURLcode Curl_cf_quic_create(struct Curl_cfilter **pcf,
+                             struct Curl_easy *data,
+                             struct connectdata *conn,
+                             const struct Curl_addrinfo *ai,
+                             int transport);
+
+bool Curl_conn_is_http3(const struct Curl_easy *data,
+                        const struct connectdata *conn,
+                        int sockindex);
+
+extern struct Curl_cftype Curl_cft_http3;
+
+#else /* ENABLE_QUIC */
+
+#define Curl_conn_is_http3(a,b,c)   FALSE
+
+#endif /* !ENABLE_QUIC */
+
+CURLcode Curl_conn_may_http3(struct Curl_easy *data,
+                             const struct connectdata *conn);
 
 #endif /* HEADER_CURL_VQUIC_QUIC_H */
diff --git a/lib/vquic/vquic_int.h b/lib/vquic/vquic_int.h
new file mode 100644
index 0000000..42aba39
--- /dev/null
+++ b/lib/vquic/vquic_int.h
@@ -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 */
diff --git a/lib/vssh/libssh.c b/lib/vssh/libssh.c
index d9fa58a..1115318 100644
--- a/lib/vssh/libssh.c
+++ b/lib/vssh/libssh.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2017 - 2022 Red Hat, Inc.
+ * Copyright (C) Red Hat, Inc.
  *
  * Authors: Nikos Mavrogiannopoulos, Tomas Mraz, Stanislav Zidek,
  *          Robert Kolcun, Andreas Schneider
diff --git a/lib/vssh/libssh2.c b/lib/vssh/libssh2.c
index ce9229f..4703eb5 100644
--- a/lib/vssh/libssh2.c
+++ b/lib/vssh/libssh2.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -144,7 +144,7 @@ const struct Curl_handler Curl_handler_scp = {
   scp_disconnect,                       /* disconnect */
   ZERO_NULL,                            /* readwrite */
   ZERO_NULL,                            /* connection_check */
-  ssh_attach,
+  ssh_attach,                           /* attach */
   PORT_SSH,                             /* defport */
   CURLPROTO_SCP,                        /* protocol */
   CURLPROTO_SCP,                        /* family */
@@ -173,7 +173,7 @@ const struct Curl_handler Curl_handler_sftp = {
   sftp_disconnect,                      /* disconnect */
   ZERO_NULL,                            /* readwrite */
   ZERO_NULL,                            /* connection_check */
-  ssh_attach,
+  ssh_attach,                           /* attach */
   PORT_SSH,                             /* defport */
   CURLPROTO_SFTP,                       /* protocol */
   CURLPROTO_SFTP,                       /* family */
@@ -840,6 +840,8 @@ static CURLcode ssh_force_knownhost_key_type(struct Curl_easy *data)
 #endif
   static const char * const hostkey_method_ssh_rsa
     = "ssh-rsa";
+  static const char * const hostkey_method_ssh_rsa_all
+    = "rsa-sha2-256,rsa-sha2-512,ssh-rsa";
   static const char * const hostkey_method_ssh_dss
     = "ssh-dss";
 
@@ -914,7 +916,16 @@ static CURLcode ssh_force_knownhost_key_type(struct Curl_easy *data)
         break;
 #endif
       case LIBSSH2_KNOWNHOST_KEY_SSHRSA:
-        hostkey_method = hostkey_method_ssh_rsa;
+#ifdef HAVE_LIBSSH2_VERSION
+        if(libssh2_version(0x010900))
+          /* since 1.9.0 libssh2_session_method_pref() works as expected */
+          hostkey_method = hostkey_method_ssh_rsa_all;
+        else
+#endif
+          /* old libssh2 which cannot correctly remove unsupported methods due
+           * to bug in src/kex.c or does not support the new methods anyways.
+           */
+          hostkey_method = hostkey_method_ssh_rsa;
         break;
       case LIBSSH2_KNOWNHOST_KEY_SSHDSS:
         hostkey_method = hostkey_method_ssh_dss;
diff --git a/lib/vssh/ssh.h b/lib/vssh/ssh.h
index 13bb8aa..7c25555 100644
--- a/lib/vssh/ssh.h
+++ b/lib/vssh/ssh.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/vssh/wolfssh.c b/lib/vssh/wolfssh.c
index 6a8fb56..17d59ec 100644
--- a/lib/vssh/wolfssh.c
+++ b/lib/vssh/wolfssh.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2019 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/vtls/bearssl.c b/lib/vtls/bearssl.c
index d9c0ce0..7e3eb79 100644
--- a/lib/vtls/bearssl.c
+++ b/lib/vtls/bearssl.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2019 - 2022, Michael Forney, <mforney@mforney.org>
+ * Copyright (C) Michael Forney, <mforney@mforney.org>
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -58,7 +58,7 @@ struct ssl_backend_data {
   unsigned char buf[BR_SSL_BUFSIZE_BIDI];
   br_x509_trust_anchor *anchors;
   size_t anchors_len;
-  const char *protocols[2];
+  const char *protocols[ALPN_ENTRIES_MAX];
   /* SSL client context is active */
   bool active;
   /* size of pending write, yet to be flushed */
@@ -691,29 +691,17 @@ static CURLcode bearssl_connect_step1(struct Curl_cfilter *cf,
     Curl_ssl_sessionid_unlock(data);
   }
 
-  if(cf->conn->bits.tls_enable_alpn) {
-    int cur = 0;
-
-    /* NOTE: when adding more protocols here, increase the size of the
-     * protocols array in `struct ssl_backend_data`.
-     */
+  if(connssl->alpn) {
+    struct alpn_proto_buf proto;
+    size_t i;
 
-#ifdef USE_HTTP2
-    if(data->state.httpwant >= CURL_HTTP_VERSION_2
-#ifndef CURL_DISABLE_PROXY
-      && (!Curl_ssl_cf_is_proxy(cf) || !cf->conn->bits.tunnel_proxy)
-#endif
-      ) {
-      backend->protocols[cur++] = ALPN_H2;
-      infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_H2);
+    for(i = 0; i < connssl->alpn->count; ++i) {
+      backend->protocols[i] = connssl->alpn->entries[i];
     }
-#endif
-
-    backend->protocols[cur++] = ALPN_HTTP_1_1;
-    infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_1);
-
-    br_ssl_engine_set_protocol_names(&backend->ctx.eng,
-                                     backend->protocols, cur);
+    br_ssl_engine_set_protocol_names(&backend->ctx.eng, backend->protocols,
+                                     connssl->alpn->count);
+    Curl_alpn_to_proto_str(&proto, connssl->alpn);
+    infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data);
   }
 
   if((1 == Curl_inet_pton(AF_INET, hostname, &addr))
@@ -862,26 +850,11 @@ static CURLcode bearssl_connect_step3(struct Curl_cfilter *cf,
   DEBUGASSERT(backend);
 
   if(cf->conn->bits.tls_enable_alpn) {
-    const char *protocol;
-
-    protocol = br_ssl_engine_get_selected_protocol(&backend->ctx.eng);
-    if(protocol) {
-      infof(data, VTLS_INFOF_ALPN_ACCEPTED_1STR, protocol);
+    const char *proto;
 
-#ifdef USE_HTTP2
-      if(!strcmp(protocol, ALPN_H2))
-        cf->conn->alpn = CURL_HTTP_VERSION_2;
-      else
-#endif
-      if(!strcmp(protocol, ALPN_HTTP_1_1))
-        cf->conn->alpn = CURL_HTTP_VERSION_1_1;
-      else
-        infof(data, "ALPN, unrecognized protocol %s", protocol);
-      Curl_multiuse_state(data, cf->conn->alpn == CURL_HTTP_VERSION_2 ?
-                          BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE);
-    }
-    else
-      infof(data, VTLS_INFOF_NO_ALPN);
+    proto = br_ssl_engine_get_selected_protocol(&backend->ctx.eng);
+    Curl_alpn_set_negotiated(cf, data, (const unsigned char *)proto,
+                             proto? strlen(proto) : 0);
   }
 
   if(ssl_config->primary.sessionid) {
@@ -977,7 +950,7 @@ static CURLcode bearssl_connect_common(struct Curl_cfilter *cf,
 {
   CURLcode ret;
   struct ssl_connect_data *connssl = cf->ctx;
-  curl_socket_t sockfd = cf->conn->sock[cf->sockindex];
+  curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data);
   timediff_t timeout_ms;
   int what;
 
diff --git a/lib/vtls/bearssl.h b/lib/vtls/bearssl.h
index 5125359..b3651b0 100644
--- a/lib/vtls/bearssl.h
+++ b/lib/vtls/bearssl.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2019 - 2022, Michael Forney, <mforney@mforney.org>
+ * Copyright (C) Michael Forney, <mforney@mforney.org>
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/lib/vtls/gskit.c b/lib/vtls/gskit.c
index 2074dca..59fd27c 100644
--- a/lib/vtls/gskit.c
+++ b/lib/vtls/gskit.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -499,7 +499,7 @@ static void cancel_async_handshake(struct Curl_cfilter *cf,
   (void)data;
   DEBUGASSERT(BACKEND);
 
-  if(QsoCancelOperation(cf->conn->sock[cf->sockindex], 0) > 0)
+  if(QsoCancelOperation(Curl_conn_cf_get_socket(cf, data), 0) > 0)
     QsoWaitForIOCompletion(BACKEND->iocport, &cstat, (struct timeval *) NULL);
 }
 
@@ -532,7 +532,7 @@ static int pipe_ssloverssl(struct Curl_cfilter *cf, int directions)
   DEBUGASSERT(connssl_next->backend);
   n = 1;
   fds[0].fd = BACKEND->remotefd;
-  fds[1].fd = cf->conn->sock[cf->sockindex];
+  fds[1].fd = Curl_conn_cf_get_socket(cf, data);
 
   if(directions & SOS_READ) {
     fds[0].events |= POLLOUT;
@@ -847,7 +847,7 @@ static CURLcode gskit_connect_step1(struct Curl_cfilter *cf,
     result = set_numeric(data, BACKEND->handle, GSK_OS400_READ_TIMEOUT, 1);
   if(!result)
     result = set_numeric(data, BACKEND->handle, GSK_FD, BACKEND->localfd >= 0?
-                         BACKEND->localfd: cf->conn->sock[cf->sockindex]);
+                         BACKEND->localfd: Curl_conn_cf_get_socket(cf, data));
   if(!result)
     result = set_ciphers(cf, data, BACKEND->handle, &protoflags);
   if(!protoflags) {
@@ -1208,7 +1208,7 @@ static int gskit_shutdown(struct Curl_cfilter *cf,
 
   close_one(cf, data);
   rc = 0;
-  what = SOCKET_READABLE(cf->conn->sock[cf->sockindex],
+  what = SOCKET_READABLE(Curl_conn_cf_get_socket(cf, data),
                          SSL_SHUTDOWN_TIMEOUT);
 
   while(loop--) {
@@ -1230,7 +1230,7 @@ static int gskit_shutdown(struct Curl_cfilter *cf,
        notify alert from the server. No way to gsk_secure_soc_read() now, so
        use read(). */
 
-    nread = read(cf->conn->sock[cf->sockindex], buf, sizeof(buf));
+    nread = read(Curl_conn_cf_get_socket(cf, data), buf, sizeof(buf));
 
     if(nread < 0) {
       char buffer[STRERROR_LEN];
@@ -1241,7 +1241,7 @@ static int gskit_shutdown(struct Curl_cfilter *cf,
     if(nread <= 0)
       break;
 
-    what = SOCKET_READABLE(cf->conn->sock[cf->sockindex], 0);
+    what = SOCKET_READABLE(Curl_conn_cf_get_socket(cf, data), 0);
   }
 
   return rc;
diff --git a/lib/vtls/gskit.h b/lib/vtls/gskit.h
index cf923f6..c71e6a0 100644
--- a/lib/vtls/gskit.h
+++ b/lib/vtls/gskit.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/vtls/gtls.c b/lib/vtls/gtls.c
index 104dce6..07dfaa4 100644
--- a/lib/vtls/gtls.c
+++ b/lib/vtls/gtls.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -84,7 +84,7 @@ static ssize_t gtls_push(void *s, const void *buf, size_t blen)
 {
   struct Curl_cfilter *cf = s;
   struct ssl_connect_data *connssl = cf->ctx;
-  struct Curl_easy *data = connssl->call_data;
+  struct Curl_easy *data = CF_DATA_CURRENT(cf);
   ssize_t nwritten;
   CURLcode result;
 
@@ -102,7 +102,7 @@ static ssize_t gtls_pull(void *s, void *buf, size_t blen)
 {
   struct Curl_cfilter *cf = s;
   struct ssl_connect_data *connssl = cf->ctx;
-  struct Curl_easy *data = connssl->call_data;
+  struct Curl_easy *data = CF_DATA_CURRENT(cf);
   ssize_t nread;
   CURLcode result;
 
@@ -214,7 +214,7 @@ static CURLcode handshake(struct Curl_cfilter *cf,
   struct ssl_connect_data *connssl = cf->ctx;
   struct ssl_backend_data *backend = connssl->backend;
   gnutls_session_t session;
-  curl_socket_t sockfd = cf->conn->sock[cf->sockindex];
+  curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data);
 
   DEBUGASSERT(backend);
   session = backend->gtls.session;
@@ -434,12 +434,10 @@ CURLcode gtls_client_init(struct Curl_easy *data,
   }
 
 #ifdef USE_GNUTLS_SRP
-  if((config->authtype == CURL_TLSAUTH_SRP) &&
-     Curl_auth_allowed_to_host(data)) {
+  if(config->username && Curl_auth_allowed_to_host(data)) {
     infof(data, "Using TLS-SRP username: %s", config->username);
 
-    rc = gnutls_srp_allocate_client_credentials(
-           &gtls->srp_client_cred);
+    rc = gnutls_srp_allocate_client_credentials(&gtls->srp_client_cred);
     if(rc != GNUTLS_E_SUCCESS) {
       failf(data, "gnutls_srp_allocate_client_cred() failed: %s",
             gnutls_strerror(rc));
@@ -581,7 +579,7 @@ CURLcode gtls_client_init(struct Curl_easy *data,
 #ifdef USE_GNUTLS_SRP
   /* Only add SRP to the cipher list if SRP is requested. Otherwise
    * GnuTLS will disable TLS 1.3 support. */
-  if(config->authtype == CURL_TLSAUTH_SRP) {
+  if(config->username) {
     size_t len = strlen(prioritylist);
 
     char *prioritysrp = malloc(len + sizeof(GNUTLS_SRP) + 1);
@@ -646,7 +644,7 @@ CURLcode gtls_client_init(struct Curl_easy *data,
 
 #ifdef USE_GNUTLS_SRP
   /* put the credentials to the current session */
-  if(config->authtype == CURL_TLSAUTH_SRP) {
+  if(config->username) {
     rc = gnutls_credentials_set(gtls->session, GNUTLS_CRD_SRP,
                                 gtls->srp_client_cred);
     if(rc != GNUTLS_E_SUCCESS) {
@@ -700,32 +698,22 @@ gtls_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data)
   if(result)
     return result;
 
-  if(cf->conn->bits.tls_enable_alpn) {
-    int cur = 0;
-    gnutls_datum_t protocols[2];
+  if(connssl->alpn) {
+    struct alpn_proto_buf proto;
+    gnutls_datum_t alpn[ALPN_ENTRIES_MAX];
+    size_t i;
 
-#ifdef USE_HTTP2
-    if(data->state.httpwant >= CURL_HTTP_VERSION_2
-#ifndef CURL_DISABLE_PROXY
-       && (!Curl_ssl_cf_is_proxy(cf) || !cf->conn->bits.tunnel_proxy)
-#endif
-       ) {
-      protocols[cur].data = (unsigned char *)ALPN_H2;
-      protocols[cur].size = ALPN_H2_LENGTH;
-      cur++;
-      infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_H2);
+    for(i = 0; i < connssl->alpn->count; ++i) {
+      alpn[i].data = (unsigned char *)connssl->alpn->entries[i];
+      alpn[i].size = (unsigned)strlen(connssl->alpn->entries[i]);
     }
-#endif
-
-    protocols[cur].data = (unsigned char *)ALPN_HTTP_1_1;
-    protocols[cur].size = ALPN_HTTP_1_1_LENGTH;
-    cur++;
-    infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_1);
-
-    if(gnutls_alpn_set_protocols(backend->gtls.session, protocols, cur, 0)) {
+    if(gnutls_alpn_set_protocols(backend->gtls.session, alpn,
+                                 (unsigned)connssl->alpn->count, 0)) {
       failf(data, "failed setting ALPN");
       return CURLE_SSL_CONNECT_ERROR;
     }
+    Curl_alpn_to_proto_str(&proto, connssl->alpn);
+    infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data);
   }
 
   /* This might be a reconnect, so we check for a session ID in the cache
@@ -860,10 +848,8 @@ Curl_gtls_verifyserver(struct Curl_easy *data,
        config->verifyhost ||
        config->issuercert) {
 #ifdef USE_GNUTLS_SRP
-      if(ssl_config->primary.authtype == CURL_TLSAUTH_SRP
-         && ssl_config->primary.username
-         && !config->verifypeer
-         && gnutls_cipher_get(session)) {
+      if(ssl_config->primary.username && !config->verifypeer &&
+         gnutls_cipher_get(session)) {
         /* no peer cert, but auth is ok if we have SRP user and cipher and no
            peer verify */
       }
@@ -1271,28 +1257,10 @@ static CURLcode gtls_verifyserver(struct Curl_cfilter *cf,
     int rc;
 
     rc = gnutls_alpn_get_selected_protocol(session, &proto);
-    if(rc == 0) {
-      infof(data, VTLS_INFOF_ALPN_ACCEPTED_LEN_1STR, proto.size,
-            proto.data);
-
-#ifdef USE_HTTP2
-      if(proto.size == ALPN_H2_LENGTH &&
-         !memcmp(ALPN_H2, proto.data,
-                 ALPN_H2_LENGTH)) {
-        cf->conn->alpn = CURL_HTTP_VERSION_2;
-      }
-      else
-#endif
-      if(proto.size == ALPN_HTTP_1_1_LENGTH &&
-         !memcmp(ALPN_HTTP_1_1, proto.data, ALPN_HTTP_1_1_LENGTH)) {
-        cf->conn->alpn = CURL_HTTP_VERSION_1_1;
-      }
-    }
+    if(rc == 0)
+      Curl_alpn_set_negotiated(cf, data, proto.data, proto.size);
     else
-      infof(data, VTLS_INFOF_NO_ALPN);
-
-    Curl_multiuse_state(data, cf->conn->alpn == CURL_HTTP_VERSION_2 ?
-                        BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE);
+      Curl_alpn_set_negotiated(cf, data, NULL, 0);
   }
 
   if(ssl_config->primary.sessionid) {
@@ -1516,7 +1484,7 @@ static int gtls_shutdown(struct Curl_cfilter *cf,
     char buf[120];
 
     while(!done) {
-      int what = SOCKET_READABLE(cf->conn->sock[cf->sockindex],
+      int what = SOCKET_READABLE(Curl_conn_cf_get_socket(cf, data),
                                  SSL_SHUTDOWN_TIMEOUT);
       if(what > 0) {
         /* Something to read, let's do it and hope that it is the close
@@ -1556,8 +1524,7 @@ static int gtls_shutdown(struct Curl_cfilter *cf,
   gnutls_certificate_free_credentials(backend->gtls.cred);
 
 #ifdef USE_GNUTLS_SRP
-  if(ssl_config->primary.authtype == CURL_TLSAUTH_SRP
-     && ssl_config->primary.username != NULL)
+  if(ssl_config->primary.username)
     gnutls_srp_free_client_credentials(backend->gtls.srp_client_cred);
 #endif
 
diff --git a/lib/vtls/gtls.h b/lib/vtls/gtls.h
index 49c1c47..ac141e1 100644
--- a/lib/vtls/gtls.h
+++ b/lib/vtls/gtls.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/vtls/hostcheck.c b/lib/vtls/hostcheck.c
index 2a648f2..e827dc5 100644
--- a/lib/vtls/hostcheck.c
+++ b/lib/vtls/hostcheck.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/vtls/hostcheck.h b/lib/vtls/hostcheck.h
index d3c4eab..22a1ac2 100644
--- a/lib/vtls/hostcheck.h
+++ b/lib/vtls/hostcheck.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/vtls/keylog.c b/lib/vtls/keylog.c
index 1952a69..d37bb18 100644
--- a/lib/vtls/keylog.c
+++ b/lib/vtls/keylog.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/vtls/keylog.h b/lib/vtls/keylog.h
index 5d3c675..eff5bf3 100644
--- a/lib/vtls/keylog.h
+++ b/lib/vtls/keylog.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/vtls/mbedtls.c b/lib/vtls/mbedtls.c
index 0b81662..7f0f4e3 100644
--- a/lib/vtls/mbedtls.c
+++ b/lib/vtls/mbedtls.c
@@ -5,8 +5,8 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
- * Copyright (C) 2010 - 2011, Hoi-Ho Chan, <hoiho.chan@gmail.com>
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Hoi-Ho Chan, <hoiho.chan@gmail.com>
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -159,15 +159,14 @@ static void mbed_debug(void *context, int level, const char *f_name,
 static int bio_cf_write(void *bio, const unsigned char *buf, size_t blen)
 {
   struct Curl_cfilter *cf = bio;
-  struct ssl_connect_data *connssl = cf->ctx;
-  struct Curl_easy *data = connssl->call_data;
+  struct Curl_easy *data = CF_DATA_CURRENT(cf);
   ssize_t nwritten;
   CURLcode result;
 
   DEBUGASSERT(data);
   nwritten = Curl_conn_cf_send(cf->next, data, (char *)buf, blen, &result);
-  /* DEBUGF(infof(data, CFMSG(cf, "bio_cf_out_write(len=%d) -> %d, err=%d"),
-         blen, (int)nwritten, result)); */
+  DEBUGF(LOG_CF(data, cf, "bio_cf_out_write(len=%zu) -> %zd, err=%d",
+                blen, nwritten, result));
   if(nwritten < 0 && CURLE_AGAIN == result) {
     nwritten = MBEDTLS_ERR_SSL_WANT_WRITE;
   }
@@ -177,8 +176,7 @@ static int bio_cf_write(void *bio, const unsigned char *buf, size_t blen)
 static int bio_cf_read(void *bio, unsigned char *buf, size_t blen)
 {
   struct Curl_cfilter *cf = bio;
-  struct ssl_connect_data *connssl = cf->ctx;
-  struct Curl_easy *data = connssl->call_data;
+  struct Curl_easy *data = CF_DATA_CURRENT(cf);
   ssize_t nread;
   CURLcode result;
 
@@ -188,8 +186,8 @@ static int bio_cf_read(void *bio, unsigned char *buf, size_t blen)
     return 0;
 
   nread = Curl_conn_cf_recv(cf->next, data, (char *)buf, blen, &result);
-  /* DEBUGF(infof(data, CFMSG(cf, "bio_cf_in_read(len=%d) -> %d, err=%d"),
-         blen, (int)nread, result)); */
+  DEBUGF(LOG_CF(data, cf, "bio_cf_in_read(len=%zu) -> %zd, err=%d",
+                blen, nread, result));
   if(nread < 0 && CURLE_AGAIN == result) {
     nread = MBEDTLS_ERR_SSL_WANT_READ;
   }
@@ -648,14 +646,13 @@ mbed_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data)
   }
 
 #ifdef HAS_ALPN
-  if(cf->conn->bits.tls_enable_alpn) {
-    const char **p = &backend->protocols[0];
-#ifdef USE_HTTP2
-    if(data->state.httpwant >= CURL_HTTP_VERSION_2)
-      *p++ = ALPN_H2;
-#endif
-    *p++ = ALPN_HTTP_1_1;
-    *p = NULL;
+  if(connssl->alpn) {
+    struct alpn_proto_buf proto;
+    size_t i;
+
+    for(i = 0; i < connssl->alpn->count; ++i) {
+      backend->protocols[i] = connssl->alpn->entries[i];
+    }
     /* this function doesn't clone the protocols array, which is why we need
        to keep it around */
     if(mbedtls_ssl_conf_alpn_protocols(&backend->config,
@@ -663,8 +660,8 @@ mbed_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data)
       failf(data, "Failed setting ALPN protocols");
       return CURLE_SSL_CONNECT_ERROR;
     }
-    for(p = &backend->protocols[0]; *p; ++p)
-      infof(data, VTLS_INFOF_ALPN_OFFER_1STR, *p);
+    Curl_alpn_to_proto_str(&proto, connssl->alpn);
+    infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data);
   }
 #endif
 
@@ -844,28 +841,11 @@ mbed_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data)
   }
 
 #ifdef HAS_ALPN
-  if(cf->conn->bits.tls_enable_alpn) {
-    const char *next_protocol = mbedtls_ssl_get_alpn_protocol(&backend->ssl);
+  if(connssl->alpn) {
+    const char *proto = mbedtls_ssl_get_alpn_protocol(&backend->ssl);
 
-    if(next_protocol) {
-      infof(data, VTLS_INFOF_ALPN_ACCEPTED_1STR, next_protocol);
-#ifdef USE_HTTP2
-      if(!strncmp(next_protocol, ALPN_H2, ALPN_H2_LENGTH) &&
-         !next_protocol[ALPN_H2_LENGTH]) {
-        cf->conn->alpn = CURL_HTTP_VERSION_2;
-      }
-      else
-#endif
-        if(!strncmp(next_protocol, ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH) &&
-           !next_protocol[ALPN_HTTP_1_1_LENGTH]) {
-          cf->conn->alpn = CURL_HTTP_VERSION_1_1;
-        }
-    }
-    else {
-      infof(data, VTLS_INFOF_NO_ALPN);
-    }
-    Curl_multiuse_state(data, cf->conn->alpn == CURL_HTTP_VERSION_2 ?
-                        BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE);
+    Curl_alpn_set_negotiated(cf, data, (const unsigned char *)proto,
+                             proto? strlen(proto) : 0);
   }
 #endif
 
@@ -1081,7 +1061,7 @@ mbed_connect_common(struct Curl_cfilter *cf, struct Curl_easy *data,
 {
   CURLcode retcode;
   struct ssl_connect_data *connssl = cf->ctx;
-  curl_socket_t sockfd = cf->conn->sock[cf->sockindex];
+  curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data);
   timediff_t timeout_ms;
   int what;
 
diff --git a/lib/vtls/mbedtls.h b/lib/vtls/mbedtls.h
index ec3b43b..d8a0a06 100644
--- a/lib/vtls/mbedtls.h
+++ b/lib/vtls/mbedtls.h
@@ -7,8 +7,8 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
- * Copyright (C) 2010, Hoi-Ho Chan, <hoiho.chan@gmail.com>
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Hoi-Ho Chan, <hoiho.chan@gmail.com>
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/lib/vtls/mbedtls_threadlock.c b/lib/vtls/mbedtls_threadlock.c
index 7d019ed..bcb7106 100644
--- a/lib/vtls/mbedtls_threadlock.c
+++ b/lib/vtls/mbedtls_threadlock.c
@@ -5,8 +5,8 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2013 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
- * Copyright (C) 2010, 2011, Hoi-Ho Chan, <hoiho.chan@gmail.com>
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Hoi-Ho Chan, <hoiho.chan@gmail.com>
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/lib/vtls/mbedtls_threadlock.h b/lib/vtls/mbedtls_threadlock.h
index 22e8725..2b0bd41 100644
--- a/lib/vtls/mbedtls_threadlock.h
+++ b/lib/vtls/mbedtls_threadlock.h
@@ -7,8 +7,8 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2013 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
- * Copyright (C) 2010, Hoi-Ho Chan, <hoiho.chan@gmail.com>
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Hoi-Ho Chan, <hoiho.chan@gmail.com>
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
diff --git a/lib/vtls/nss.c b/lib/vtls/nss.c
index 03694d2..774cbdd 100644
--- a/lib/vtls/nss.c
+++ b/lib/vtls/nss.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -873,11 +873,11 @@ static void HandshakeCallback(PRFileDesc *sock, void *arg)
 #endif
     case SSL_NEXT_PROTO_NO_SUPPORT:
     case SSL_NEXT_PROTO_NO_OVERLAP:
-      infof(data, VTLS_INFOF_NO_ALPN);
+      Curl_alpn_set_negotiated(cf, data, NULL, 0);
       return;
 #ifdef SSL_ENABLE_ALPN
     case SSL_NEXT_PROTO_SELECTED:
-      infof(data, VTLS_INFOF_ALPN_ACCEPTED_LEN_1STR, buflen, buf);
+      Curl_alpn_set_negotiated(cf, data, buf, buflen);
       break;
 #endif
     default:
@@ -885,25 +885,6 @@ static void HandshakeCallback(PRFileDesc *sock, void *arg)
       break;
     }
 
-#ifdef USE_HTTP2
-    if(buflen == ALPN_H2_LENGTH &&
-       !memcmp(ALPN_H2, buf, ALPN_H2_LENGTH)) {
-      cf->conn->alpn = CURL_HTTP_VERSION_2;
-    }
-    else
-#endif
-    if(buflen == ALPN_HTTP_1_1_LENGTH &&
-       !memcmp(ALPN_HTTP_1_1, buf, ALPN_HTTP_1_1_LENGTH)) {
-      cf->conn->alpn = CURL_HTTP_VERSION_1_1;
-    }
-
-    /* This callback might get called when PR_Recv() is used within
-     * close_one() during a connection shutdown. At that point there might not
-     * be any "bundle" associated with the connection anymore.
-     */
-    if(conn->bundle)
-      Curl_multiuse_state(data, cf->conn->alpn == CURL_HTTP_VERSION_2 ?
-                          BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE);
   }
 }
 
@@ -1897,7 +1878,7 @@ static CURLcode nss_setup_connect(struct Curl_cfilter *cf,
   PRFileDesc *nspr_io_stub = NULL;
   PRBool ssl_no_cache;
   PRBool ssl_cbc_random_iv;
-  curl_socket_t sockfd = cf->conn->sock[cf->sockindex];
+  curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data);
   struct ssl_connect_data *connssl = cf->ctx;
   struct ssl_backend_data *backend = connssl->backend;
   struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
@@ -2163,27 +2144,17 @@ static CURLcode nss_setup_connect(struct Curl_cfilter *cf,
 #endif
 
 #if defined(SSL_ENABLE_ALPN)
-  if(cf->conn->bits.tls_enable_alpn) {
-    int cur = 0;
-    unsigned char protocols[128];
-
-#ifdef USE_HTTP2
-    if(data->state.httpwant >= CURL_HTTP_VERSION_2
-#ifndef CURL_DISABLE_PROXY
-       && (!Curl_ssl_cf_is_proxy(cf) || !cf->conn->bits.tunnel_proxy)
-#endif
-      ) {
-      protocols[cur++] = ALPN_H2_LENGTH;
-      memcpy(&protocols[cur], ALPN_H2, ALPN_H2_LENGTH);
-      cur += ALPN_H2_LENGTH;
-    }
-#endif
-    protocols[cur++] = ALPN_HTTP_1_1_LENGTH;
-    memcpy(&protocols[cur], ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH);
-    cur += ALPN_HTTP_1_1_LENGTH;
+  if(connssl->alpn) {
+    struct alpn_proto_buf proto;
 
-    if(SSL_SetNextProtoNego(backend->handle, protocols, cur) != SECSuccess)
+    result = Curl_alpn_to_proto_buf(&proto, connssl->alpn);
+    if(result || SSL_SetNextProtoNego(backend->handle, proto.data, proto.len)
+                   != SECSuccess) {
+      failf(data, "Error setting ALPN");
       goto error;
+    }
+    Curl_alpn_to_proto_str(&proto, connssl->alpn);
+    infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data);
   }
 #endif
 
@@ -2393,6 +2364,19 @@ static ssize_t nss_send(struct Curl_cfilter *cf,
   return rc; /* number of bytes */
 }
 
+static bool
+nss_data_pending(struct Curl_cfilter *cf, const struct Curl_easy *data)
+{
+  struct ssl_connect_data *connssl = cf->ctx;
+  PRFileDesc *fd = connssl->backend->handle->lower;
+  char buf;
+
+  (void) data;
+
+  /* Returns true in case of error to force reading. */
+  return PR_Recv(fd, (void *) &buf, 1, PR_MSG_PEEK, PR_INTERVAL_NO_WAIT) != 0;
+}
+
 static ssize_t nss_recv(struct Curl_cfilter *cf,
                         struct Curl_easy *data,    /* transfer */
                         char *buf,             /* store read data here */
@@ -2543,7 +2527,7 @@ const struct Curl_ssl Curl_ssl_nss = {
   nss_check_cxn,                /* check_cxn */
   /* NSS has no shutdown function provided and thus always fail */
   Curl_none_shutdown,           /* shutdown */
-  Curl_none_data_pending,       /* data_pending */
+  nss_data_pending,             /* data_pending */
   nss_random,                   /* random */
   nss_cert_status_request,      /* cert_status_request */
   nss_connect,                  /* connect */
diff --git a/lib/vtls/nssg.h b/lib/vtls/nssg.h
index 454a38f..ad7eef5 100644
--- a/lib/vtls/nssg.h
+++ b/lib/vtls/nssg.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/vtls/openssl.c b/lib/vtls/openssl.c
index e7a1caa..6557783 100644
--- a/lib/vtls/openssl.c
+++ b/lib/vtls/openssl.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -96,6 +96,7 @@
 #include "curl_memory.h"
 #include "memdebug.h"
 
+
 /* Uncomment the ALLOW_RENEG line to a real #define if you want to allow TLS
    renegotiations when built with BoringSSL. Renegotiating is non-compliant
    with HTTP/2 and "an extremely dangerous protocol feature". Beware.
@@ -260,6 +261,12 @@
 #define HAVE_OPENSSL_VERSION
 #endif
 
+#ifdef OPENSSL_IS_BORINGSSL
+typedef uint32_t sslerr_t;
+#else
+typedef unsigned long sslerr_t;
+#endif
+
 /*
  * Whether the OpenSSL version has the API needed to support sharing an
  * X509_STORE between connections. The API is:
@@ -277,17 +284,17 @@
 #endif /* !LIBRESSL_VERSION_NUMBER */
 
 struct ssl_backend_data {
-  struct Curl_easy *logger; /* transfer handle to pass trace logs to, only
-                               using sockindex 0 */
   /* these ones requires specific SSL-types */
   SSL_CTX* ctx;
   SSL*     handle;
   X509*    server_cert;
+  BIO_METHOD *bio_method;
   CURLcode io_result;       /* result of last BIO cfilter operation */
 #ifndef HAVE_KEYLOG_CALLBACK
   /* Set to true once a valid keylog entry has been created to avoid dupes. */
   bool     keylog_done;
 #endif
+  bool x509_store_setup;            /* x509 store has been set up */
 };
 
 #if defined(HAVE_SSL_X509_STORE_SHARE)
@@ -702,14 +709,14 @@ static int bio_cf_out_write(BIO *bio, const char *buf, int blen)
 {
   struct Curl_cfilter *cf = BIO_get_data(bio);
   struct ssl_connect_data *connssl = cf->ctx;
-  struct Curl_easy *data = connssl->call_data;
+  struct Curl_easy *data = CF_DATA_CURRENT(cf);
   ssize_t nwritten;
   CURLcode result = CURLE_SEND_ERROR;
 
   DEBUGASSERT(data);
   nwritten = Curl_conn_cf_send(cf->next, data, buf, blen, &result);
-  /* DEBUGF(infof(data, CFMSG(cf, "bio_cf_out_write(len=%d) -> %d, err=%d"),
-         blen, (int)nwritten, result)); */
+  DEBUGF(LOG_CF(data, cf, "bio_cf_out_write(len=%d) -> %d, err=%d",
+                blen, (int)nwritten, result));
   BIO_clear_retry_flags(bio);
   connssl->backend->io_result = result;
   if(nwritten < 0) {
@@ -723,7 +730,7 @@ static int bio_cf_in_read(BIO *bio, char *buf, int blen)
 {
   struct Curl_cfilter *cf = BIO_get_data(bio);
   struct ssl_connect_data *connssl = cf->ctx;
-  struct Curl_easy *data = connssl->call_data;
+  struct Curl_easy *data = CF_DATA_CURRENT(cf);
   ssize_t nread;
   CURLcode result = CURLE_RECV_ERROR;
 
@@ -733,64 +740,75 @@ static int bio_cf_in_read(BIO *bio, char *buf, int blen)
     return 0;
 
   nread = Curl_conn_cf_recv(cf->next, data, buf, blen, &result);
-  /* DEBUGF(infof(data, CFMSG(cf, "bio_cf_in_read(len=%d) -> %d, err=%d"),
-         blen, (int)nread, result)); */
+  DEBUGF(LOG_CF(data, cf, "bio_cf_in_read(len=%d) -> %d, err=%d",
+                blen, (int)nread, result));
   BIO_clear_retry_flags(bio);
   connssl->backend->io_result = result;
   if(nread < 0) {
     if(CURLE_AGAIN == result)
       BIO_set_retry_read(bio);
   }
+
+  /* Before returning server replies to the SSL instance, we need
+   * to have setup the x509 store or verification will fail. */
+  if(!connssl->backend->x509_store_setup) {
+    result = Curl_ssl_setup_x509_store(cf, data, connssl->backend->ctx);
+    if(result) {
+      connssl->backend->io_result = result;
+      return -1;
+    }
+    connssl->backend->x509_store_setup = TRUE;
+  }
+
   return (int)nread;
 }
 
-static BIO_METHOD *bio_cf_method = NULL;
-
 #if USE_PRE_1_1_API
 
 static BIO_METHOD bio_cf_meth_1_0 = {
-    BIO_TYPE_MEM,
-    "OpenSSL CF BIO",
-    bio_cf_out_write,
-    bio_cf_in_read,
-    NULL,                    /* puts is never called */
-    NULL,                    /* gets is never called */
-    bio_cf_ctrl,
-    bio_cf_create,
-    bio_cf_destroy,
-    NULL
+  BIO_TYPE_MEM,
+  "OpenSSL CF BIO",
+  bio_cf_out_write,
+  bio_cf_in_read,
+  NULL,                    /* puts is never called */
+  NULL,                    /* gets is never called */
+  bio_cf_ctrl,
+  bio_cf_create,
+  bio_cf_destroy,
+  NULL
 };
 
-static void bio_cf_init_methods(void)
+static BIO_METHOD *bio_cf_method_create(void)
 {
-  bio_cf_method = &bio_cf_meth_1_0;
+  return &bio_cf_meth_1_0;
 }
 
-#define bio_cf_free_methods() Curl_nop_stmt
+#define bio_cf_method_free(m) Curl_nop_stmt
 
 #else
 
-static void bio_cf_init_methods(void)
+static BIO_METHOD *bio_cf_method_create(void)
 {
-    bio_cf_method = BIO_meth_new(BIO_TYPE_MEM, "OpenSSL CF BIO");
-    BIO_meth_set_write(bio_cf_method, &bio_cf_out_write);
-    BIO_meth_set_read(bio_cf_method, &bio_cf_in_read);
-    BIO_meth_set_ctrl(bio_cf_method, &bio_cf_ctrl);
-    BIO_meth_set_create(bio_cf_method, &bio_cf_create);
-    BIO_meth_set_destroy(bio_cf_method, &bio_cf_destroy);
+  BIO_METHOD *m = BIO_meth_new(BIO_TYPE_MEM, "OpenSSL CF BIO");
+  if(m) {
+    BIO_meth_set_write(m, &bio_cf_out_write);
+    BIO_meth_set_read(m, &bio_cf_in_read);
+    BIO_meth_set_ctrl(m, &bio_cf_ctrl);
+    BIO_meth_set_create(m, &bio_cf_create);
+    BIO_meth_set_destroy(m, &bio_cf_destroy);
+  }
+  return m;
 }
 
-static void bio_cf_free_methods(void)
+static void bio_cf_method_free(BIO_METHOD *m)
 {
-    BIO_meth_free(bio_cf_method);
+  if(m)
+    BIO_meth_free(m);
 }
 
 #endif
 
 
-static bool ossl_attach_data(struct Curl_cfilter *cf,
-                             struct Curl_easy *data);
-
 /*
  * Number of bytes to read from the random number seed file. This must be
  * a finite value (because some entropy "files" like /dev/urandom have
@@ -922,54 +940,6 @@ static char *ossl_strerror(unsigned long error, char *buf, size_t size)
   return buf;
 }
 
-/* Return an extra data index for the transfer data.
- * This index can be used with SSL_get_ex_data() and SSL_set_ex_data().
- */
-static int ossl_get_ssl_data_index(void)
-{
-  static int ssl_ex_data_data_index = -1;
-  if(ssl_ex_data_data_index < 0) {
-    ssl_ex_data_data_index = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL);
-  }
-  return ssl_ex_data_data_index;
-}
-
-/* Return an extra data index for the associated Curl_cfilter instance.
- * This index can be used with SSL_get_ex_data() and SSL_set_ex_data().
- */
-static int ossl_get_ssl_cf_index(void)
-{
-  static int ssl_ex_data_cf_index = -1;
-  if(ssl_ex_data_cf_index < 0) {
-    ssl_ex_data_cf_index = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL);
-  }
-  return ssl_ex_data_cf_index;
-}
-
-/* Return an extra data index for the sockindex.
- * This index can be used with SSL_get_ex_data() and SSL_set_ex_data().
- */
-static int ossl_get_ssl_sockindex_index(void)
-{
-  static int sockindex_index = -1;
-  if(sockindex_index < 0) {
-    sockindex_index = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL);
-  }
-  return sockindex_index;
-}
-
-/* Return an extra data index for proxy boolean.
- * This index can be used with SSL_get_ex_data() and SSL_set_ex_data().
- */
-static int ossl_get_proxy_index(void)
-{
-  static int proxy_index = -1;
-  if(proxy_index < 0) {
-    proxy_index = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL);
-  }
-  return proxy_index;
-}
-
 static int passwd_callback(char *buf, int num, int encrypting,
                            void *global_passwd)
 {
@@ -1246,7 +1216,7 @@ SSL_CTX_use_certificate_chain_blob(SSL_CTX *ctx, const struct curl_blob *blob,
 
   if(ret) {
     X509 *ca;
-    unsigned long err;
+    sslerr_t err;
 
     if(!SSL_CTX_clear_chain_certs(ctx)) {
       ret = 0;
@@ -1768,14 +1738,8 @@ static int ossl_init(void)
   OpenSSL_add_all_algorithms();
 #endif
 
-  bio_cf_init_methods();
   Curl_tls_keylog_open();
 
-  /* Initialize the extra data indexes */
-  if(ossl_get_ssl_data_index() < 0 || ossl_get_ssl_cf_index() < 0 ||
-     ossl_get_ssl_sockindex_index() < 0 || ossl_get_proxy_index() < 0)
-    return 0;
-
   return 1;
 }
 
@@ -1814,7 +1778,6 @@ static void ossl_cleanup(void)
 #endif
 
   Curl_tls_keylog_close();
-  bio_cf_free_methods();
 }
 
 /*
@@ -1832,7 +1795,10 @@ static int ossl_check_cxn(struct Curl_cfilter *cf, struct Curl_easy *data)
 #ifdef MSG_PEEK
   char buf;
   ssize_t nread;
-  nread = recv((RECV_TYPE_ARG1)cf->conn->sock[cf->sockindex],
+  curl_socket_t sock = Curl_conn_cf_get_socket(cf, data);
+  if(sock == CURL_SOCKET_BAD)
+    return 0; /* no socket, consider closed */
+  nread = recv((RECV_TYPE_ARG1)sock,
                (RECV_TYPE_ARG2)&buf, (RECV_TYPE_ARG3)1,
                (RECV_TYPE_ARG4)MSG_PEEK);
   if(nread == 0)
@@ -1960,19 +1926,15 @@ static struct curl_slist *ossl_engines_list(struct Curl_easy *data)
   return list;
 }
 
-#define set_logger(connssl, data)                  \
-  connssl->backend->logger = data
-
 static void ossl_close(struct Curl_cfilter *cf, struct Curl_easy *data)
 {
   struct ssl_connect_data *connssl = cf->ctx;
   struct ssl_backend_data *backend = connssl->backend;
 
+  (void)data;
   DEBUGASSERT(backend);
 
   if(backend->handle) {
-    set_logger(connssl, data);
-
     if(cf->next && cf->next->connected) {
       char buf[32];
       /* Maybe the server has already sent a close notify alert.
@@ -1989,6 +1951,11 @@ static void ossl_close(struct Curl_cfilter *cf, struct Curl_easy *data)
   if(backend->ctx) {
     SSL_CTX_free(backend->ctx);
     backend->ctx = NULL;
+    backend->x509_store_setup = FALSE;
+  }
+  if(backend->bio_method) {
+    bio_cf_method_free(backend->bio_method);
+    backend->bio_method = NULL;
   }
 }
 
@@ -2026,7 +1993,7 @@ static int ossl_shutdown(struct Curl_cfilter *cf,
   if(backend->handle) {
     buffsize = (int)sizeof(buf);
     while(!done && loop--) {
-      int what = SOCKET_READABLE(cf->conn->sock[cf->sockindex],
+      int what = SOCKET_READABLE(Curl_conn_cf_get_socket(cf, data),
                                  SSL_SHUTDOWN_TIMEOUT);
       if(what > 0) {
         ERR_clear_error();
@@ -2155,6 +2122,22 @@ static bool subj_alt_hostcheck(struct Curl_easy *data,
   return FALSE;
 }
 
+static CURLcode
+ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn,
+                X509 *server_cert, const char *hostname,
+                const char *dispname);
+
+CURLcode Curl_ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn,
+                              X509 *server_cert)
+{
+  const char *hostname, *dispname;
+  int port;
+
+  (void)conn;
+  Curl_conn_get_host(data, FIRSTSOCKET, &hostname, &dispname, &port);
+  return ossl_verifyhost(data, conn, server_cert, hostname, dispname);
+}
+
 /* Quote from RFC2818 section 3.1 "Server Identity"
 
    If a subjectAltName extension of type dNSName is present, that MUST
@@ -2177,8 +2160,10 @@ static bool subj_alt_hostcheck(struct Curl_easy *data,
 
    This function is now used from ngtcp2 (QUIC) as well.
 */
-CURLcode Curl_ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn,
-                              X509 *server_cert)
+static CURLcode
+ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn,
+                X509 *server_cert, const char *hostname,
+                const char *dispname)
 {
   bool matched = FALSE;
   int target = GEN_DNS; /* target type, GEN_DNS or GEN_IPADD */
@@ -2192,12 +2177,9 @@ CURLcode Curl_ossl_verifyhost(struct Curl_easy *data, struct connectdata *conn,
   CURLcode result = CURLE_OK;
   bool dNSName = FALSE; /* if a dNSName field exists in the cert */
   bool iPAddress = FALSE; /* if a iPAddress field exists in the cert */
-  const char *hostname, *dispname;
-  int port;
   size_t hostlen;
 
   (void)conn;
-  Curl_conn_get_host(data, FIRSTSOCKET, &hostname, &dispname, &port);
   hostlen = strlen(hostname);
 
 #ifndef ENABLE_IPV6
@@ -2660,24 +2642,15 @@ static void ossl_trace(int direction, int ssl_ver, int content_type,
                        const void *buf, size_t len, SSL *ssl,
                        void *userp)
 {
-  char unknown[32];
-  const char *verstr = NULL;
-  struct connectdata *conn = userp;
-  int cf_idx = ossl_get_ssl_cf_index();
-  struct ssl_connect_data *connssl;
+  const char *verstr = "???";
+  struct Curl_cfilter *cf = userp;
   struct Curl_easy *data = NULL;
-  struct Curl_cfilter *cf;
-
-  DEBUGASSERT(cf_idx >= 0);
-  cf = (struct Curl_cfilter*) SSL_get_ex_data(ssl, cf_idx);
-  DEBUGASSERT(cf);
-  connssl = cf->ctx;
-  DEBUGASSERT(connssl);
-  DEBUGASSERT(connssl->backend);
-  data = connssl->backend->logger;
+  char unknown[32];
 
-  if(!conn || !data || !data->set.fdebug ||
-     (direction != 0 && direction != 1))
+  if(!cf)
+    return;
+  data = CF_DATA_CURRENT(cf);
+  if(!data || !data->set.fdebug || (direction && direction != 1))
     return;
 
  switch(ssl_ver) {
@@ -2722,6 +2695,9 @@ static void ossl_trace(int direction, int ssl_ver, int content_type,
    * For TLS 1.3, skip notification of the decrypted inner Content-Type.
    */
   if(ssl_ver
+#ifdef SSL3_RT_HEADER
+     && content_type != SSL3_RT_HEADER
+#endif
 #ifdef SSL3_RT_INNER_CONTENT_TYPE
      && content_type != SSL3_RT_INNER_CONTENT_TYPE
 #endif
@@ -2757,7 +2733,7 @@ static void ossl_trace(int direction, int ssl_ver, int content_type,
     }
 
     txt_len = msnprintf(ssl_buf, sizeof(ssl_buf),
-                        CFMSG(cf, "%s (%s), %s, %s (%d):\n"),
+                        "%s (%s), %s, %s (%d):\n",
                         verstr, direction?"OUT":"IN",
                         tls_rt_name, msg_name, msg_type);
     if(0 <= txt_len && (unsigned)txt_len < sizeof(ssl_buf)) {
@@ -2967,21 +2943,14 @@ static int ossl_new_session_cb(SSL *ssl, SSL_SESSION *ssl_sessionid)
   struct Curl_easy *data;
   struct Curl_cfilter *cf;
   const struct ssl_config_data *config;
-  curl_socket_t *sockindex_ptr;
-  int data_idx = ossl_get_ssl_data_index();
-  int cf_idx = ossl_get_ssl_cf_index();
-  int sockindex_idx = ossl_get_ssl_sockindex_index();
-  int proxy_idx = ossl_get_proxy_index();
+  struct ssl_connect_data *connssl;
   bool isproxy;
 
-  if(data_idx < 0 || cf_idx < 0 || sockindex_idx < 0 || proxy_idx < 0)
-    return 0;
-
-  cf = (struct Curl_cfilter*) SSL_get_ex_data(ssl, cf_idx);
-  data = (struct Curl_easy *) SSL_get_ex_data(ssl, data_idx);
+  cf = (struct Curl_cfilter*) SSL_get_app_data(ssl);
+  connssl = cf? cf->ctx : NULL;
+  data = connssl? CF_DATA_CURRENT(cf) : NULL;
   /* The sockindex has been stored as a pointer to an array element */
-  sockindex_ptr = (curl_socket_t*) SSL_get_ex_data(ssl, sockindex_idx);
-  if(!cf || !data || !sockindex_ptr)
+  if(!cf || !data)
     return 0;
 
   isproxy = Curl_ssl_cf_is_proxy(cf);
@@ -3075,7 +3044,7 @@ static CURLcode load_cacert_from_memory(X509_STORE *store,
   BIO_free(cbio);
 
   /* if we didn't end up importing anything, treat that as an error */
-  return (count > 0 ? CURLE_OK : CURLE_SSL_CACERT_BADFILE);
+  return (count > 0) ? CURLE_OK : CURLE_SSL_CACERT_BADFILE;
 }
 
 static CURLcode populate_x509_store(struct Curl_cfilter *cf,
@@ -3094,206 +3063,219 @@ static CURLcode populate_x509_store(struct Curl_cfilter *cf,
   const char * const ssl_crlfile = ssl_config->primary.CRLfile;
   const bool verifypeer = conn_config->verifypeer;
   bool imported_native_ca = false;
+  bool imported_ca_info_blob = false;
 
   if(!store)
     return CURLE_OUT_OF_MEMORY;
 
+  if(verifypeer) {
 #if defined(USE_WIN32_CRYPTO)
-  /* Import certificates from the Windows root certificate store if requested.
-     https://stackoverflow.com/questions/9507184/
-     https://github.com/d3x0r/SACK/blob/master/src/netlib/ssl_layer.c#L1037
-     https://datatracker.ietf.org/doc/html/rfc5280 */
-  if((conn_config->verifypeer || conn_config->verifyhost) &&
-     (ssl_config->native_ca_store)) {
-    HCERTSTORE hStore = CertOpenSystemStore(0, TEXT("ROOT"));
-
-    if(hStore) {
-      PCCERT_CONTEXT pContext = NULL;
-      /* The array of enhanced key usage OIDs will vary per certificate and is
-         declared outside of the loop so that rather than malloc/free each
-         iteration we can grow it with realloc, when necessary. */
-      CERT_ENHKEY_USAGE *enhkey_usage = NULL;
-      DWORD enhkey_usage_size = 0;
-
-      /* This loop makes a best effort to import all valid certificates from
-         the MS root store. If a certificate cannot be imported it is skipped.
-         'result' is used to store only hard-fail conditions (such as out of
-         memory) that cause an early break. */
-      result = CURLE_OK;
-      for(;;) {
-        X509 *x509;
-        FILETIME now;
-        BYTE key_usage[2];
-        DWORD req_size;
-        const unsigned char *encoded_cert;
+    /* Import certificates from the Windows root certificate store if
+       requested.
+       https://stackoverflow.com/questions/9507184/
+       https://github.com/d3x0r/SACK/blob/master/src/netlib/ssl_layer.c#L1037
+       https://datatracker.ietf.org/doc/html/rfc5280 */
+    if(ssl_config->native_ca_store) {
+      HCERTSTORE hStore = CertOpenSystemStore(0, TEXT("ROOT"));
+
+      if(hStore) {
+        PCCERT_CONTEXT pContext = NULL;
+        /* The array of enhanced key usage OIDs will vary per certificate and
+           is declared outside of the loop so that rather than malloc/free each
+           iteration we can grow it with realloc, when necessary. */
+        CERT_ENHKEY_USAGE *enhkey_usage = NULL;
+        DWORD enhkey_usage_size = 0;
+
+        /* This loop makes a best effort to import all valid certificates from
+           the MS root store. If a certificate cannot be imported it is
+           skipped. 'result' is used to store only hard-fail conditions (such
+           as out of memory) that cause an early break. */
+        result = CURLE_OK;
+        for(;;) {
+          X509 *x509;
+          FILETIME now;
+          BYTE key_usage[2];
+          DWORD req_size;
+          const unsigned char *encoded_cert;
 #if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
-        char cert_name[256];
+          char cert_name[256];
 #endif
 
-        pContext = CertEnumCertificatesInStore(hStore, pContext);
-        if(!pContext)
-          break;
+          pContext = CertEnumCertificatesInStore(hStore, pContext);
+          if(!pContext)
+            break;
 
 #if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
-        if(!CertGetNameStringA(pContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0,
-                               NULL, cert_name, sizeof(cert_name))) {
-          strcpy(cert_name, "Unknown");
-        }
-        infof(data, "SSL: Checking cert \"%s\"", cert_name);
+          if(!CertGetNameStringA(pContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0,
+                                 NULL, cert_name, sizeof(cert_name))) {
+            strcpy(cert_name, "Unknown");
+          }
+          infof(data, "SSL: Checking cert \"%s\"", cert_name);
 #endif
+          encoded_cert = (const unsigned char *)pContext->pbCertEncoded;
+          if(!encoded_cert)
+            continue;
 
-        encoded_cert = (const unsigned char *)pContext->pbCertEncoded;
-        if(!encoded_cert)
-          continue;
-
-        GetSystemTimeAsFileTime(&now);
-        if(CompareFileTime(&pContext->pCertInfo->NotBefore, &now) > 0 ||
-           CompareFileTime(&now, &pContext->pCertInfo->NotAfter) > 0)
-          continue;
-
-        /* If key usage exists check for signing attribute */
-        if(CertGetIntendedKeyUsage(pContext->dwCertEncodingType,
-                                   pContext->pCertInfo,
-                                   key_usage, sizeof(key_usage))) {
-          if(!(key_usage[0] & CERT_KEY_CERT_SIGN_KEY_USAGE))
+          GetSystemTimeAsFileTime(&now);
+          if(CompareFileTime(&pContext->pCertInfo->NotBefore, &now) > 0 ||
+             CompareFileTime(&now, &pContext->pCertInfo->NotAfter) > 0)
             continue;
-        }
-        else if(GetLastError())
-          continue;
-
-        /* If enhanced key usage exists check for server auth attribute.
-         *
-         * Note "In a Microsoft environment, a certificate might also have EKU
-         * extended properties that specify valid uses for the certificate."
-         * The call below checks both, and behavior varies depending on what is
-         * found. For more details see CertGetEnhancedKeyUsage doc.
-         */
-        if(CertGetEnhancedKeyUsage(pContext, 0, NULL, &req_size)) {
-          if(req_size && req_size > enhkey_usage_size) {
-            void *tmp = realloc(enhkey_usage, req_size);
-
-            if(!tmp) {
-              failf(data, "SSL: Out of memory allocating for OID list");
-              result = CURLE_OUT_OF_MEMORY;
-              break;
-            }
 
-            enhkey_usage = (CERT_ENHKEY_USAGE *)tmp;
-            enhkey_usage_size = req_size;
+          /* If key usage exists check for signing attribute */
+          if(CertGetIntendedKeyUsage(pContext->dwCertEncodingType,
+                                     pContext->pCertInfo,
+                                     key_usage, sizeof(key_usage))) {
+            if(!(key_usage[0] & CERT_KEY_CERT_SIGN_KEY_USAGE))
+              continue;
           }
+          else if(GetLastError())
+            continue;
 
-          if(CertGetEnhancedKeyUsage(pContext, 0, enhkey_usage, &req_size)) {
-            if(!enhkey_usage->cUsageIdentifier) {
-              /* "If GetLastError returns CRYPT_E_NOT_FOUND, the certificate is
-                 good for all uses. If it returns zero, the certificate has no
-                 valid uses." */
-              if((HRESULT)GetLastError() != CRYPT_E_NOT_FOUND)
-                continue;
+          /* If enhanced key usage exists check for server auth attribute.
+           *
+           * Note "In a Microsoft environment, a certificate might also have
+           * EKU extended properties that specify valid uses for the
+           * certificate."  The call below checks both, and behavior varies
+           * depending on what is found. For more details see
+           * CertGetEnhancedKeyUsage doc.
+           */
+          if(CertGetEnhancedKeyUsage(pContext, 0, NULL, &req_size)) {
+            if(req_size && req_size > enhkey_usage_size) {
+              void *tmp = realloc(enhkey_usage, req_size);
+
+              if(!tmp) {
+                failf(data, "SSL: Out of memory allocating for OID list");
+                result = CURLE_OUT_OF_MEMORY;
+                break;
+              }
+
+              enhkey_usage = (CERT_ENHKEY_USAGE *)tmp;
+              enhkey_usage_size = req_size;
             }
-            else {
-              DWORD i;
-              bool found = false;
-
-              for(i = 0; i < enhkey_usage->cUsageIdentifier; ++i) {
-                if(!strcmp("1.3.6.1.5.5.7.3.1" /* OID server auth */,
-                           enhkey_usage->rgpszUsageIdentifier[i])) {
-                  found = true;
-                  break;
-                }
+
+            if(CertGetEnhancedKeyUsage(pContext, 0, enhkey_usage, &req_size)) {
+              if(!enhkey_usage->cUsageIdentifier) {
+                /* "If GetLastError returns CRYPT_E_NOT_FOUND, the certificate
+                   is good for all uses. If it returns zero, the certificate
+                   has no valid uses." */
+                if((HRESULT)GetLastError() != CRYPT_E_NOT_FOUND)
+                  continue;
               }
+              else {
+                DWORD i;
+                bool found = false;
+
+                for(i = 0; i < enhkey_usage->cUsageIdentifier; ++i) {
+                  if(!strcmp("1.3.6.1.5.5.7.3.1" /* OID server auth */,
+                             enhkey_usage->rgpszUsageIdentifier[i])) {
+                    found = true;
+                    break;
+                  }
+                }
 
-              if(!found)
-                continue;
+                if(!found)
+                  continue;
+              }
             }
+            else
+              continue;
           }
           else
             continue;
-        }
-        else
-          continue;
 
-        x509 = d2i_X509(NULL, &encoded_cert, pContext->cbCertEncoded);
-        if(!x509)
-          continue;
+          x509 = d2i_X509(NULL, &encoded_cert, pContext->cbCertEncoded);
+          if(!x509)
+            continue;
 
-        /* Try to import the certificate. This may fail for legitimate reasons
-           such as duplicate certificate, which is allowed by MS but not
-           OpenSSL. */
-        if(X509_STORE_add_cert(store, x509) == 1) {
+          /* Try to import the certificate. This may fail for legitimate
+             reasons such as duplicate certificate, which is allowed by MS but
+             not OpenSSL. */
+          if(X509_STORE_add_cert(store, x509) == 1) {
 #if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
-          infof(data, "SSL: Imported cert \"%s\"", cert_name);
+            infof(data, "SSL: Imported cert \"%s\"", cert_name);
 #endif
-          imported_native_ca = true;
+            imported_native_ca = true;
+          }
+          X509_free(x509);
         }
-        X509_free(x509);
-      }
 
-      free(enhkey_usage);
-      CertFreeCertificateContext(pContext);
-      CertCloseStore(hStore, 0);
+        free(enhkey_usage);
+        CertFreeCertificateContext(pContext);
+        CertCloseStore(hStore, 0);
 
-      if(result)
-        return result;
+        if(result)
+          return result;
+      }
+      if(imported_native_ca)
+        infof(data, "successfully imported Windows CA store");
+      else
+        infof(data, "error importing Windows CA store, continuing anyway");
     }
-    if(imported_native_ca)
-      infof(data, "successfully imported Windows CA store");
-    else
-      infof(data, "error importing Windows CA store, continuing anyway");
-  }
 #endif
-
-  if(ca_info_blob) {
-    result = load_cacert_from_memory(store, ca_info_blob);
-    if(result) {
-      if(result == CURLE_OUT_OF_MEMORY ||
-         (verifypeer && !imported_native_ca)) {
+    if(ca_info_blob) {
+      result = load_cacert_from_memory(store, ca_info_blob);
+      if(result) {
         failf(data, "error importing CA certificate blob");
         return result;
       }
-      /* Only warn if no certificate verification is required. */
-      infof(data, "error importing CA certificate blob, continuing anyway");
+      else {
+        imported_ca_info_blob = true;
+        infof(data, "successfully imported CA certificate blob");
+      }
     }
-  }
 
-  if(verifypeer && !imported_native_ca && (ssl_cafile || ssl_capath)) {
+    if(ssl_cafile || ssl_capath) {
 #if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3)
-  /* OpenSSL 3.0.0 has deprecated SSL_CTX_load_verify_locations */
-    if(ssl_cafile &&
-       !X509_STORE_load_file(store, ssl_cafile)) {
-      /* Fail if we insist on successfully verifying the server. */
-      failf(data, "error setting certificate file: %s", ssl_cafile);
-      return CURLE_SSL_CACERT_BADFILE;
-    }
-    if(ssl_capath &&
-       !X509_STORE_load_path(store, ssl_capath)) {
-      /* Fail if we insist on successfully verifying the server. */
-      failf(data, "error setting certificate path: %s", ssl_capath);
-      return CURLE_SSL_CACERT_BADFILE;
-    }
+      /* OpenSSL 3.0.0 has deprecated SSL_CTX_load_verify_locations */
+      if(ssl_cafile && !X509_STORE_load_file(store, ssl_cafile)) {
+        if(!imported_native_ca && !imported_ca_info_blob) {
+          /* Fail if we insist on successfully verifying the server. */
+          failf(data, "error setting certificate file: %s", ssl_cafile);
+          return CURLE_SSL_CACERT_BADFILE;
+        }
+        else
+          infof(data, "error setting certificate file, continuing anyway");
+      }
+      if(ssl_capath && !X509_STORE_load_path(store, ssl_capath)) {
+        if(!imported_native_ca && !imported_ca_info_blob) {
+          /* Fail if we insist on successfully verifying the server. */
+          failf(data, "error setting certificate path: %s", ssl_capath);
+          return CURLE_SSL_CACERT_BADFILE;
+        }
+        else
+          infof(data, "error setting certificate path, continuing anyway");
+      }
 #else
-    /* tell OpenSSL where to find CA certificates that are used to verify the
-       server's certificate. */
-    if(!X509_STORE_load_locations(store, 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 CURLE_SSL_CACERT_BADFILE;
-    }
+      /* tell OpenSSL where to find CA certificates that are used to verify the
+         server's certificate. */
+      if(!X509_STORE_load_locations(store, ssl_cafile, ssl_capath)) {
+        if(!imported_native_ca && !imported_ca_info_blob) {
+          /* 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 CURLE_SSL_CACERT_BADFILE;
+        }
+        else {
+          infof(data, "error setting certificate verify locations,"
+                " continuing anyway");
+        }
+      }
 #endif
-    infof(data, " CAfile: %s", ssl_cafile ? ssl_cafile : "none");
-    infof(data, " CApath: %s", ssl_capath ? ssl_capath : "none");
-  }
+      infof(data, " CAfile: %s", ssl_cafile ? ssl_cafile : "none");
+      infof(data, " CApath: %s", ssl_capath ? ssl_capath : "none");
+    }
 
 #ifdef CURL_CA_FALLBACK
-  if(verifypeer &&
-     !ca_info_blob && !ssl_cafile && !ssl_capath && !imported_native_ca) {
-    /* verifying the peer without any CA certificates won't
-       work so use openssl's built-in default as fallback */
-    X509_STORE_set_default_paths(store);
-  }
+    if(!ssl_cafile && !ssl_capath &&
+       !imported_native_ca && !imported_ca_info_blob) {
+      /* verifying the peer without any CA certificates won't
+         work so use openssl's built-in default as fallback */
+      X509_STORE_set_default_paths(store);
+    }
 #endif
+  }
 
   if(ssl_crlfile) {
     /* tell OpenSSL where to find CRL file that is used to check certificate
@@ -3424,9 +3406,9 @@ static void set_cached_x509_store(struct Curl_cfilter *cf,
   }
 }
 
-static CURLcode set_up_x509_store(struct Curl_cfilter *cf,
-                                  struct Curl_easy *data,
-                                  struct ssl_backend_data *backend)
+CURLcode Curl_ssl_setup_x509_store(struct Curl_cfilter *cf,
+                                   struct Curl_easy *data,
+                                   SSL_CTX *ssl_ctx)
 {
   struct ssl_primary_config *conn_config = Curl_ssl_cf_get_primary_config(cf);
   struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
@@ -3446,10 +3428,10 @@ static CURLcode set_up_x509_store(struct Curl_cfilter *cf,
 
   cached_store = get_cached_x509_store(cf, data);
   if(cached_store && cache_criteria_met && X509_STORE_up_ref(cached_store)) {
-    SSL_CTX_set_cert_store(backend->ctx, cached_store);
+    SSL_CTX_set_cert_store(ssl_ctx, cached_store);
   }
   else {
-    X509_STORE *store = SSL_CTX_get_cert_store(backend->ctx);
+    X509_STORE *store = SSL_CTX_get_cert_store(ssl_ctx);
 
     result = populate_x509_store(cf, data, store);
     if(result == CURLE_OK && cache_criteria_met) {
@@ -3460,11 +3442,11 @@ static CURLcode set_up_x509_store(struct Curl_cfilter *cf,
   return result;
 }
 #else /* HAVE_SSL_X509_STORE_SHARE */
-static CURLcode set_up_x509_store(struct Curl_cfilter *cf,
-                                  struct Curl_easy *data,
-                                  struct ssl_backend_data *backend)
+CURLcode Curl_ssl_setup_x509_store(struct Curl_cfilter *cf,
+                                   struct Curl_easy *data,
+                                   SSL_CTX *ssl_ctx)
 {
-  X509_STORE *store = SSL_CTX_get_cert_store(backend->ctx);
+  X509_STORE *store = SSL_CTX_get_cert_store(ssl_ctx);
 
   return populate_x509_store(cf, data, store);
 }
@@ -3494,9 +3476,6 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf,
 #endif
 #endif
   const long int ssl_version = conn_config->version;
-#ifdef USE_OPENSSL_SRP
-  const enum CURL_TLSAUTH ssl_authtype = ssl_config->primary.authtype;
-#endif
   char * const ssl_cert = ssl_config->primary.clientcert;
   const struct curl_blob *ssl_cert_blob = ssl_config->primary.cert_blob;
   const char * const ssl_cert_type = ssl_config->cert_type;
@@ -3564,8 +3543,7 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf,
   if(data->set.fdebug && data->set.verbose) {
     /* the SSL trace callback is only used for verbose logging */
     SSL_CTX_set_msg_callback(backend->ctx, ossl_trace);
-    SSL_CTX_set_msg_callback_arg(backend->ctx, cf->conn);
-    set_logger(connssl, data);
+    SSL_CTX_set_msg_callback_arg(backend->ctx, cf);
   }
 #endif
 
@@ -3661,36 +3639,17 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf,
   SSL_CTX_set_options(backend->ctx, ctx_options);
 
 #ifdef HAS_ALPN
-  if(cf->conn->bits.tls_enable_alpn) {
-    int cur = 0;
-    unsigned char protocols[128];
-
-#ifdef USE_HTTP2
-    if(data->state.httpwant >= CURL_HTTP_VERSION_2
-#ifndef CURL_DISABLE_PROXY
-       && (!Curl_ssl_cf_is_proxy(cf) || !cf->conn->bits.tunnel_proxy)
-#endif
-      ) {
-      protocols[cur++] = ALPN_H2_LENGTH;
-
-      memcpy(&protocols[cur], ALPN_H2, ALPN_H2_LENGTH);
-      cur += ALPN_H2_LENGTH;
-      infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_H2);
-    }
-#endif
-
-    protocols[cur++] = ALPN_HTTP_1_1_LENGTH;
-    memcpy(&protocols[cur], ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH);
-    cur += ALPN_HTTP_1_1_LENGTH;
-    infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_1);
+  if(connssl->alpn) {
+    struct alpn_proto_buf proto;
 
-    /* expects length prefixed preference ordered list of protocols in wire
-     * format
-     */
-    if(SSL_CTX_set_alpn_protos(backend->ctx, protocols, cur)) {
+    result = Curl_alpn_to_proto_buf(&proto, connssl->alpn);
+    if(result ||
+       SSL_CTX_set_alpn_protos(backend->ctx, proto.data, proto.len)) {
       failf(data, "Error setting ALPN");
       return CURLE_SSL_CONNECT_ERROR;
     }
+    Curl_alpn_to_proto_str(&proto, connssl->alpn);
+    infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data);
   }
 #endif
 
@@ -3748,8 +3707,7 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf,
 #endif
 
 #ifdef USE_OPENSSL_SRP
-  if((ssl_authtype == CURL_TLSAUTH_SRP) &&
-     Curl_auth_allowed_to_host(data)) {
+  if(ssl_config->primary.username && Curl_auth_allowed_to_host(data)) {
     char * const ssl_username = ssl_config->primary.username;
     char * const ssl_password = ssl_config->primary.password;
     infof(data, "Using TLS-SRP username: %s", ssl_username);
@@ -3773,10 +3731,6 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf,
   }
 #endif
 
-  result = set_up_x509_store(cf, data, backend);
-  if(result)
-    return result;
-
   /* 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
@@ -3820,6 +3774,8 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf,
     return CURLE_OUT_OF_MEMORY;
   }
 
+  SSL_set_app_data(backend->handle, cf);
+
 #if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_TLSEXT) && \
     !defined(OPENSSL_NO_OCSP)
   if(conn_config->verifystatus)
@@ -3847,13 +3803,7 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf,
   }
 #endif
 
-  if(!ossl_attach_data(cf, data)) {
-    /* Maybe the internal errors of SSL_get_ex_new_index or SSL_set_ex_data */
-    failf(data, "SSL: ossl_attach_data failed: %s",
-          ossl_strerror(ERR_get_error(), error_buffer,
-                        sizeof(error_buffer)));
-    return CURLE_SSL_CONNECT_ERROR;
-  }
+  SSL_set_app_data(backend->handle, cf);
 
   if(ssl_config->primary.sessionid) {
     Curl_ssl_sessionid_lock(data);
@@ -3872,13 +3822,26 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf,
     Curl_ssl_sessionid_unlock(data);
   }
 
-  bio = BIO_new(bio_cf_method);
+  backend->bio_method = bio_cf_method_create();
+  if(!backend->bio_method)
+    return CURLE_OUT_OF_MEMORY;
+  bio = BIO_new(backend->bio_method);
   if(!bio)
     return CURLE_OUT_OF_MEMORY;
 
   BIO_set_data(bio, cf);
+#ifdef HAVE_SSL_SET0_WBIO
+  /* with OpenSSL v1.1.1 we get an alternative to SSL_set_bio() that works
+   * without backward compat quirks. Every call takes one reference, so we
+   * up it and pass. SSL* then owns it and will free.
+   * We check on the function in configure, since libressl and friends
+   * each have their own versions to add support for this. */
+  BIO_up_ref(bio);
+  SSL_set0_rbio(backend->handle, bio);
+  SSL_set0_wbio(backend->handle, bio);
+#else
   SSL_set_bio(backend->handle, bio, bio);
-
+#endif
   connssl->connecting_state = ssl_connect_2;
 
   return CURLE_OK;
@@ -3899,6 +3862,16 @@ static CURLcode ossl_connect_step2(struct Curl_cfilter *cf,
   ERR_clear_error();
 
   err = SSL_connect(backend->handle);
+
+  if(!backend->x509_store_setup) {
+    /* After having send off the ClientHello, we prepare the x509
+     * store to verify the coming certificate from the server */
+    CURLcode result = Curl_ssl_setup_x509_store(cf, data, backend->ctx);
+    if(result)
+      return result;
+    backend->x509_store_setup = TRUE;
+  }
+
 #ifndef HAVE_KEYLOG_CALLBACK
   if(Curl_tls_keylog_enabled()) {
     /* If key logging is enabled, wait for the handshake to complete and then
@@ -3933,7 +3906,7 @@ static CURLcode ossl_connect_step2(struct Curl_cfilter *cf,
     }
     else {
       /* untreated error */
-      unsigned long errdetail;
+      sslerr_t errdetail;
       char error_buffer[256]="";
       CURLcode result;
       long lerr;
@@ -4027,26 +4000,8 @@ static CURLcode ossl_connect_step2(struct Curl_cfilter *cf,
       const unsigned char *neg_protocol;
       unsigned int len;
       SSL_get0_alpn_selected(backend->handle, &neg_protocol, &len);
-      if(len) {
-        infof(data, VTLS_INFOF_ALPN_ACCEPTED_LEN_1STR, len, neg_protocol);
-
-#ifdef USE_HTTP2
-        if(len == ALPN_H2_LENGTH &&
-           !memcmp(ALPN_H2, neg_protocol, len)) {
-          cf->conn->alpn = CURL_HTTP_VERSION_2;
-        }
-        else
-#endif
-        if(len == ALPN_HTTP_1_1_LENGTH &&
-           !memcmp(ALPN_HTTP_1_1, neg_protocol, ALPN_HTTP_1_1_LENGTH)) {
-          cf->conn->alpn = CURL_HTTP_VERSION_1_1;
-        }
-      }
-      else
-        infof(data, VTLS_INFOF_NO_ALPN);
 
-      Curl_multiuse_state(data, cf->conn->alpn == CURL_HTTP_VERSION_2 ?
-                          BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE);
+      return Curl_alpn_set_negotiated(cf, data, neg_protocol, len);
     }
 #endif
 
@@ -4189,7 +4144,8 @@ static CURLcode servercert(struct Curl_cfilter *cf,
   BIO_free(mem);
 
   if(conn_config->verifyhost) {
-    result = Curl_ossl_verifyhost(data, conn, backend->server_cert);
+    result = ossl_verifyhost(data, conn, backend->server_cert,
+                             connssl->hostname, connssl->dispname);
     if(result) {
       X509_free(backend->server_cert);
       backend->server_cert = NULL;
@@ -4363,7 +4319,7 @@ static CURLcode ossl_connect_common(struct Curl_cfilter *cf,
 {
   CURLcode result = CURLE_OK;
   struct ssl_connect_data *connssl = cf->ctx;
-  curl_socket_t sockfd = cf->conn->sock[cf->sockindex];
+  curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data);
   int what;
 
   /* check if the connection has already been established */
@@ -4402,8 +4358,9 @@ static CURLcode ossl_connect_common(struct Curl_cfilter *cf,
     }
 
     /* if ssl is expecting something, check if it's available. */
-    if(connssl->connecting_state == ssl_connect_2_reading ||
-       connssl->connecting_state == ssl_connect_2_writing) {
+    if(!nonblocking &&
+       (connssl->connecting_state == ssl_connect_2_reading ||
+        connssl->connecting_state == ssl_connect_2_writing)) {
 
       curl_socket_t writefd = ssl_connect_2_writing ==
         connssl->connecting_state?sockfd:CURL_SOCKET_BAD;
@@ -4411,7 +4368,7 @@ static CURLcode ossl_connect_common(struct Curl_cfilter *cf,
         connssl->connecting_state?sockfd:CURL_SOCKET_BAD;
 
       what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd,
-                               nonblocking?0:timeout_ms);
+                               timeout_ms);
       if(what < 0) {
         /* fatal error */
         failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO);
@@ -4419,11 +4376,6 @@ static CURLcode ossl_connect_common(struct Curl_cfilter *cf,
         goto out;
       }
       if(0 == what) {
-        if(nonblocking) {
-          *done = FALSE;
-          result = CURLE_OK;
-          goto out;
-        }
         /* timeout */
         failf(data, "SSL connection timeout");
         result = CURLE_OPERATION_TIMEDOUT;
@@ -4511,7 +4463,7 @@ static ssize_t ossl_send(struct Curl_cfilter *cf,
      'size_t' */
   int err;
   char error_buffer[256];
-  unsigned long sslerror;
+  sslerr_t sslerror;
   int memlen;
   int rc;
   struct ssl_connect_data *connssl = cf->ctx;
@@ -4523,7 +4475,6 @@ static ssize_t ossl_send(struct Curl_cfilter *cf,
   ERR_clear_error();
 
   memlen = (len > (size_t)INT_MAX) ? INT_MAX : (int)len;
-  set_logger(connssl, data);
   rc = SSL_write(backend->handle, mem, memlen);
 
   if(rc <= 0) {
@@ -4620,7 +4571,6 @@ static ssize_t ossl_recv(struct Curl_cfilter *cf,
   ERR_clear_error();
 
   buffsize = (buffersize > (size_t)INT_MAX) ? INT_MAX : (int)buffersize;
-  set_logger(connssl, data);
   nread = (ssize_t)SSL_read(backend->handle, buf, buffsize);
 
   if(nread <= 0) {
@@ -4838,89 +4788,6 @@ static void *ossl_get_internals(struct ssl_connect_data *connssl,
          (void *)backend->ctx : (void *)backend->handle;
 }
 
-static bool ossl_attach_data(struct Curl_cfilter *cf,
-                             struct Curl_easy *data)
-{
-  struct ssl_connect_data *connssl = cf->ctx;
-  struct ssl_backend_data *backend = connssl->backend;
-  const struct ssl_config_data *config;
-
-  DEBUGASSERT(backend);
-
-  /* If we don't have SSL context, do nothing. */
-  if(!backend->handle)
-    return FALSE;
-
-  config = Curl_ssl_cf_get_config(cf, data);
-  if(config->primary.sessionid) {
-    int data_idx = ossl_get_ssl_data_index();
-    int cf_idx = ossl_get_ssl_cf_index();
-    int sockindex_idx = ossl_get_ssl_sockindex_index();
-    int proxy_idx = ossl_get_proxy_index();
-
-    if(data_idx >= 0 && cf_idx >= 0 && sockindex_idx >= 0 &&
-       proxy_idx >= 0) {
-      int data_status, cf_status, sockindex_status, proxy_status;
-
-      /* Store the data needed for the "new session" callback.
-       * The sockindex is stored as a pointer to an array element. */
-      data_status = SSL_set_ex_data(backend->handle, data_idx, data);
-      cf_status = SSL_set_ex_data(backend->handle, cf_idx, cf);
-      sockindex_status = SSL_set_ex_data(backend->handle, sockindex_idx,
-                                         cf->conn->sock + cf->sockindex);
-#ifndef CURL_DISABLE_PROXY
-      proxy_status = SSL_set_ex_data(backend->handle, proxy_idx,
-                                     Curl_ssl_cf_is_proxy(cf)?
-                                       (void *) 1 : NULL);
-#else
-      proxy_status = SSL_set_ex_data(backend->handle, proxy_idx, NULL);
-#endif
-      if(data_status && cf_status && sockindex_status && proxy_status)
-        return TRUE;
-    }
-    return FALSE;
-  }
-  return TRUE;
-}
-
-/*
- * Starting with TLS 1.3, the ossl_new_session_cb callback gets called after
- * the handshake. If the transfer that sets up the callback gets killed before
- * this callback arrives, we must make sure to properly clear the data to
- * avoid UAF problems. A future optimization could be to instead store another
- * transfer that might still be using the same connection.
- */
-
-static void ossl_detach_data(struct Curl_cfilter *cf,
-                             struct Curl_easy *data)
-{
-  struct ssl_config_data *ssl_config = Curl_ssl_cf_get_config(cf, data);
-  struct ssl_connect_data *connssl = cf->ctx;
-  struct ssl_backend_data *backend = connssl->backend;
-  DEBUGASSERT(backend);
-
-  /* If we don't have SSL context, do nothing. */
-  if(!backend->handle)
-    return;
-
-  if(ssl_config->primary.sessionid) {
-    int data_idx = ossl_get_ssl_data_index();
-    int cf_idx = ossl_get_ssl_cf_index();
-    int sockindex_idx = ossl_get_ssl_sockindex_index();
-    int proxy_idx = ossl_get_proxy_index();
-
-    if(data_idx >= 0 && cf_idx >= 0 && sockindex_idx >= 0 &&
-       proxy_idx >= 0) {
-      /* Disable references to data in "new session" callback to avoid
-       * accessing a stale pointer. */
-      SSL_set_ex_data(backend->handle, data_idx, NULL);
-      SSL_set_ex_data(backend->handle, cf_idx, NULL);
-      SSL_set_ex_data(backend->handle, sockindex_idx, NULL);
-      SSL_set_ex_data(backend->handle, proxy_idx, NULL);
-    }
-  }
-}
-
 static void ossl_free_multi_ssl_backend_data(
   struct multi_ssl_backend_data *mbackend)
 {
@@ -4974,8 +4841,8 @@ const struct Curl_ssl Curl_ssl_openssl = {
 #else
   NULL,                     /* sha256sum */
 #endif
-  ossl_attach_data,         /* use of data in this connection */
-  ossl_detach_data,         /* remote of data from this connection */
+  NULL,                     /* use of data in this connection */
+  NULL,                     /* remote of data from this connection */
   ossl_free_multi_ssl_backend_data, /* free_multi_ssl_backend_data */
   ossl_recv,                /* recv decrypted data */
   ossl_send,                /* send data to encrypt */
diff --git a/lib/vtls/openssl.h b/lib/vtls/openssl.h
index 9df4ecd..950faab 100644
--- a/lib/vtls/openssl.h
+++ b/lib/vtls/openssl.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -56,5 +56,14 @@ CURLcode Curl_ossl_set_client_cert(struct Curl_easy *data,
 
 CURLcode Curl_ossl_certchain(struct Curl_easy *data, SSL *ssl);
 
+/**
+ * Setup the OpenSSL X509_STORE in `ssl_ctx` for the cfilter `cf` and
+ * easy handle `data`. Will allow reuse of a shared cache if suitable
+ * and configured.
+ */
+CURLcode Curl_ssl_setup_x509_store(struct Curl_cfilter *cf,
+                                   struct Curl_easy *data,
+                                   SSL_CTX *ssl_ctx);
+
 #endif /* USE_OPENSSL */
 #endif /* HEADER_CURL_SSLUSE_H */
diff --git a/lib/vtls/rustls.c b/lib/vtls/rustls.c
index 27f4ec8..003533d 100644
--- a/lib/vtls/rustls.c
+++ b/lib/vtls/rustls.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2020 - 2022, Jacob Hoffman-Andrews,
+ * Copyright (C) Jacob Hoffman-Andrews,
  * <github@hoffman-andrews.com>
  *
  * This software is licensed as described in the file COPYING, which
@@ -150,6 +150,7 @@ cr_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
   size_t plain_bytes_copied = 0;
   rustls_result rresult = 0;
   char errorbuf[255];
+  size_t errorlen;
   rustls_io_result io_error;
 
   DEBUGASSERT(backend);
@@ -161,7 +162,7 @@ cr_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
   io_error = rustls_connection_read_tls(rconn, read_cb, &io_ctx,
                                         &tls_bytes_read);
   if(io_error == EAGAIN || io_error == EWOULDBLOCK) {
-    infof(data, CFMSG(cf, "cr_recv: EAGAIN or EWOULDBLOCK"));
+    DEBUGF(LOG_CF(data, cf, "cr_recv: EAGAIN or EWOULDBLOCK"));
   }
   else if(io_error) {
     char buffer[STRERROR_LEN];
@@ -171,12 +172,13 @@ cr_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
     return -1;
   }
 
-  infof(data, CFMSG(cf, "cr_recv: read %ld TLS bytes"), tls_bytes_read);
+  DEBUGF(LOG_CF(data, cf, "cr_recv: read %ld TLS bytes", tls_bytes_read));
 
   rresult = rustls_connection_process_new_packets(rconn);
   if(rresult != RUSTLS_RESULT_OK) {
-    rustls_error(rresult, errorbuf, sizeof(errorbuf), &n);
-    failf(data, "%.*s", n, errorbuf);
+    rustls_error(rresult, errorbuf, sizeof(errorbuf), &errorlen);
+    failf(data, "rustls_connection_process_new_packets: %.*s",
+      errorlen, errorbuf);
     *err = map_error(rresult);
     return -1;
   }
@@ -189,14 +191,21 @@ cr_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
       plainlen - plain_bytes_copied,
       &n);
     if(rresult == RUSTLS_RESULT_PLAINTEXT_EMPTY) {
-      infof(data, CFMSG(cf, "cr_recv: got PLAINTEXT_EMPTY. "
-            "will try again later."));
+      DEBUGF(LOG_CF(data, cf, "cr_recv: got PLAINTEXT_EMPTY. "
+                    "will try again later."));
       backend->data_pending = FALSE;
       break;
     }
+    else if(rresult == RUSTLS_RESULT_UNEXPECTED_EOF) {
+      failf(data, "rustls: peer closed TCP connection "
+        "without first closing TLS connection");
+      *err = CURLE_READ_ERROR;
+      return -1;
+    }
     else if(rresult != RUSTLS_RESULT_OK) {
       /* n always equals 0 in this case, don't need to check it */
-      failf(data, "error in rustls_connection_read: %d", rresult);
+      rustls_error(rresult, errorbuf, sizeof(errorbuf), &errorlen);
+      failf(data, "rustls_connection_read: %.*s", errorlen, errorbuf);
       *err = CURLE_READ_ERROR;
       return -1;
     }
@@ -207,7 +216,7 @@ cr_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
       break;
     }
     else {
-      infof(data, CFMSG(cf, "cr_recv: got %ld plain bytes"), n);
+      DEBUGF(LOG_CF(data, cf, "cr_recv: got %ld plain bytes", n));
       plain_bytes_copied += n;
     }
   }
@@ -254,22 +263,25 @@ cr_send(struct Curl_cfilter *cf, struct Curl_easy *data,
   size_t tlswritten_total = 0;
   rustls_result rresult;
   rustls_io_result io_error;
+  char errorbuf[256];
+  size_t errorlen;
 
   DEBUGASSERT(backend);
   rconn = backend->conn;
 
-  infof(data, CFMSG(cf, "cr_send: %ld plain bytes"), plainlen);
+  DEBUGF(LOG_CF(data, cf, "cr_send: %ld plain bytes", plainlen));
 
   if(plainlen > 0) {
     rresult = rustls_connection_write(rconn, plainbuf, plainlen,
                                       &plainwritten);
     if(rresult != RUSTLS_RESULT_OK) {
-      failf(data, "error in rustls_connection_write");
+      rustls_error(rresult, errorbuf, sizeof(errorbuf), &errorlen);
+      failf(data, "rustls_connection_write: %.*s", errorlen, errorbuf);
       *err = CURLE_WRITE_ERROR;
       return -1;
     }
     else if(plainwritten == 0) {
-      failf(data, "EOF in rustls_connection_write");
+      failf(data, "rustls_connection_write: EOF");
       *err = CURLE_WRITE_ERROR;
       return -1;
     }
@@ -282,8 +294,8 @@ cr_send(struct Curl_cfilter *cf, struct Curl_easy *data,
     io_error = rustls_connection_write_tls(rconn, write_cb, &io_ctx,
                                            &tlswritten);
     if(io_error == EAGAIN || io_error == EWOULDBLOCK) {
-      infof(data, CFMSG(cf, "cr_send: EAGAIN after %ld bytes"),
-            tlswritten_total);
+      DEBUGF(LOG_CF(data, cf, "cr_send: EAGAIN after %zu bytes",
+                    tlswritten_total));
       *err = CURLE_AGAIN;
       return -1;
     }
@@ -299,7 +311,7 @@ cr_send(struct Curl_cfilter *cf, struct Curl_easy *data,
       *err = CURLE_WRITE_ERROR;
       return -1;
     }
-    infof(data, CFMSG(cf, "cr_send: wrote %ld TLS bytes"), tlswritten);
+    DEBUGF(LOG_CF(data, cf, "cr_send: wrote %zu TLS bytes", tlswritten));
     tlswritten_total += tlswritten;
   }
 
@@ -349,22 +361,25 @@ cr_init_backend(struct Curl_cfilter *cf, struct Curl_easy *data,
   char errorbuf[256];
   size_t errorlen;
   int result;
-  rustls_slice_bytes alpn[2] = {
-    { (const uint8_t *)ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH },
-    { (const uint8_t *)ALPN_H2, ALPN_H2_LENGTH },
-  };
 
   DEBUGASSERT(backend);
   rconn = backend->conn;
 
   config_builder = rustls_client_config_builder_new();
-#ifdef USE_HTTP2
-  infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_H2);
-  rustls_client_config_builder_set_alpn_protocols(config_builder, alpn, 2);
-#else
-  rustls_client_config_builder_set_alpn_protocols(config_builder, alpn, 1);
-#endif
-  infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_1);
+  if(connssl->alpn) {
+    struct alpn_proto_buf proto;
+    rustls_slice_bytes alpn[ALPN_ENTRIES_MAX];
+    size_t i;
+
+    for(i = 0; i < connssl->alpn->count; ++i) {
+      alpn[i].data = (const uint8_t *)connssl->alpn->entries[i];
+      alpn[i].len = strlen(connssl->alpn->entries[i]);
+    }
+    rustls_client_config_builder_set_alpn_protocols(config_builder, alpn,
+                                                    connssl->alpn->count);
+    Curl_alpn_to_proto_str(&proto, connssl->alpn);
+    infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data);
+  }
   if(!verifypeer) {
     rustls_client_config_builder_dangerous_set_certificate_verifier(
       config_builder, cr_verify_none);
@@ -384,7 +399,7 @@ cr_init_backend(struct Curl_cfilter *cf, struct Curl_easy *data,
     result = rustls_root_cert_store_add_pem(roots, ca_info_blob->data,
                                             ca_info_blob->len, verifypeer);
     if(result != RUSTLS_RESULT_OK) {
-      failf(data, "failed to parse trusted certificates from blob");
+      failf(data, "rustls: failed to parse trusted certificates from blob");
       rustls_root_cert_store_free(roots);
       rustls_client_config_free(
         rustls_client_config_builder_build(config_builder));
@@ -394,7 +409,7 @@ cr_init_backend(struct Curl_cfilter *cf, struct Curl_easy *data,
     result = rustls_client_config_builder_use_roots(config_builder, roots);
     rustls_root_cert_store_free(roots);
     if(result != RUSTLS_RESULT_OK) {
-      failf(data, "failed to load trusted certificates");
+      failf(data, "rustls: failed to load trusted certificates");
       rustls_client_config_free(
         rustls_client_config_builder_build(config_builder));
       return CURLE_SSL_CACERT_BADFILE;
@@ -404,7 +419,7 @@ cr_init_backend(struct Curl_cfilter *cf, struct Curl_easy *data,
     result = rustls_client_config_builder_load_roots_from_file(
       config_builder, ssl_cafile);
     if(result != RUSTLS_RESULT_OK) {
-      failf(data, "failed to load trusted certificates");
+      failf(data, "rustls: failed to load trusted certificates");
       rustls_client_config_free(
         rustls_client_config_builder_build(config_builder));
       return CURLE_SSL_CACERT_BADFILE;
@@ -416,7 +431,7 @@ cr_init_backend(struct Curl_cfilter *cf, struct Curl_easy *data,
   {
     char *snihost = Curl_ssl_snihost(data, hostname, NULL);
     if(!snihost) {
-      failf(data, "Failed to set SNI");
+      failf(data, "rustls: failed to get SNI");
       return CURLE_SSL_CONNECT_ERROR;
     }
     result = rustls_client_connection_new(backend->config, snihost, &rconn);
@@ -439,29 +454,7 @@ cr_set_negotiated_alpn(struct Curl_cfilter *cf, struct Curl_easy *data,
   size_t len = 0;
 
   rustls_connection_get_alpn_protocol(rconn, &protocol, &len);
-  if(!protocol) {
-    infof(data, VTLS_INFOF_NO_ALPN);
-    return;
-  }
-
-#ifdef USE_HTTP2
-  if(len == ALPN_H2_LENGTH && 0 == memcmp(ALPN_H2, protocol, len)) {
-    infof(data, VTLS_INFOF_ALPN_ACCEPTED_1STR, ALPN_H2);
-    cf->conn->alpn = CURL_HTTP_VERSION_2;
-  }
-  else
-#endif
-  if(len == ALPN_HTTP_1_1_LENGTH &&
-      0 == memcmp(ALPN_HTTP_1_1, protocol, len)) {
-    infof(data, VTLS_INFOF_ALPN_ACCEPTED_1STR, ALPN_HTTP_1_1);
-    cf->conn->alpn = CURL_HTTP_VERSION_1_1;
-  }
-  else {
-    infof(data, "ALPN, negotiated an unrecognized protocol");
-  }
-
-  Curl_multiuse_state(data, cf->conn->alpn == CURL_HTTP_VERSION_2 ?
-                      BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE);
+  Curl_alpn_set_negotiated(cf, data, protocol, len);
 }
 
 static CURLcode
@@ -469,7 +462,7 @@ cr_connect_nonblocking(struct Curl_cfilter *cf,
                        struct Curl_easy *data, bool *done)
 {
   struct ssl_connect_data *const connssl = cf->ctx;
-  curl_socket_t sockfd = cf->conn->sock[cf->sockindex];
+  curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data);
   struct ssl_backend_data *const backend = connssl->backend;
   struct rustls_connection *rconn = NULL;
   CURLcode tmperr = CURLE_OK;
@@ -573,7 +566,7 @@ cr_get_select_socks(struct Curl_cfilter *cf, struct Curl_easy *data,
                     curl_socket_t *socks)
 {
   struct ssl_connect_data *const connssl = cf->ctx;
-  curl_socket_t sockfd = cf->conn->sock[cf->sockindex];
+  curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data);
   struct ssl_backend_data *const backend = connssl->backend;
   struct rustls_connection *rconn = NULL;
 
@@ -616,7 +609,7 @@ cr_close(struct Curl_cfilter *cf, struct Curl_easy *data)
     rustls_connection_send_close_notify(backend->conn);
     n = cr_send(cf, data, NULL, 0, &tmperr);
     if(n < 0) {
-      failf(data, "error sending close notify: %d", tmperr);
+      failf(data, "rustls: error sending close_notify: %d", tmperr);
     }
 
     rustls_connection_free(backend->conn);
diff --git a/lib/vtls/rustls.h b/lib/vtls/rustls.h
index 6b393dd..bfbe23d 100644
--- a/lib/vtls/rustls.h
+++ b/lib/vtls/rustls.h
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2020 - 2022, Jacob Hoffman-Andrews,
+ * Copyright (C) Jacob Hoffman-Andrews,
  * <github@hoffman-andrews.com>
  *
  * This software is licensed as described in the file COPYING, which
diff --git a/lib/vtls/schannel.c b/lib/vtls/schannel.c
index 7eab954..452fa40 100644
--- a/lib/vtls/schannel.c
+++ b/lib/vtls/schannel.c
@@ -5,9 +5,9 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
- * Copyright (C) 2012 - 2016, Marc Hoersken, <info@marc-hoersken.de>
- * Copyright (C) 2012, Mark Salisbury, <mark.salisbury@hp.com>
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Marc Hoersken, <info@marc-hoersken.de>
+ * Copyright (C) Mark Salisbury, <mark.salisbury@hp.com>
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -1105,7 +1105,7 @@ schannel_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data)
 #ifdef HAS_ALPN
   /* ALPN is only supported on Windows 8.1 / Server 2012 R2 and above.
      Also it doesn't seem to be supported for Wine, see curl bug #983. */
-  backend->use_alpn = cf->conn->bits.tls_enable_alpn &&
+  backend->use_alpn = connssl->alpn &&
     !GetProcAddress(GetModuleHandle(TEXT("ntdll")),
                     "wine_get_version") &&
     curlx_verify_windows_version(6, 3, 0, PLATFORM_WINNT,
@@ -1196,6 +1196,7 @@ schannel_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data)
     int list_start_index = 0;
     unsigned int *extension_len = NULL;
     unsigned short* list_len = NULL;
+    struct alpn_proto_buf proto;
 
     /* The first four bytes will be an unsigned int indicating number
        of bytes of data in the rest of the buffer. */
@@ -1215,25 +1216,22 @@ schannel_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data)
 
     list_start_index = cur;
 
-#ifdef USE_HTTP2
-    if(data->state.httpwant >= CURL_HTTP_VERSION_2) {
-      alpn_buffer[cur++] = ALPN_H2_LENGTH;
-      memcpy(&alpn_buffer[cur], ALPN_H2, ALPN_H2_LENGTH);
-      cur += ALPN_H2_LENGTH;
-      infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_H2);
+    result = Curl_alpn_to_proto_buf(&proto, connssl->alpn);
+    if(result) {
+      failf(data, "Error setting ALPN");
+      return CURLE_SSL_CONNECT_ERROR;
     }
-#endif
-
-    alpn_buffer[cur++] = ALPN_HTTP_1_1_LENGTH;
-    memcpy(&alpn_buffer[cur], ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH);
-    cur += ALPN_HTTP_1_1_LENGTH;
-    infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_1);
+    memcpy(&alpn_buffer[cur], proto.data, proto.len);
+    cur += proto.len;
 
     *list_len = curlx_uitous(cur - list_start_index);
     *extension_len = *list_len + sizeof(unsigned int) + sizeof(unsigned short);
 
     InitSecBuffer(&inbuf, SECBUFFER_APPLICATION_PROTOCOLS, alpn_buffer, cur);
     InitSecBufferDesc(&inbuf_desc, &inbuf, 1);
+
+    Curl_alpn_to_proto_str(&proto, connssl->alpn);
+    infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data);
   }
   else {
     InitSecBuffer(&inbuf, SECBUFFER_EMPTY, NULL, 0);
@@ -1727,40 +1725,23 @@ schannel_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data)
 
     if(alpn_result.ProtoNegoStatus ==
        SecApplicationProtocolNegotiationStatus_Success) {
-      unsigned char alpn = 0;
-
-      infof(data, VTLS_INFOF_ALPN_ACCEPTED_LEN_1STR,
-            alpn_result.ProtocolIdSize, alpn_result.ProtocolId);
+      unsigned char prev_alpn = cf->conn->alpn;
 
-#ifdef USE_HTTP2
-      if(alpn_result.ProtocolIdSize == ALPN_H2_LENGTH &&
-         !memcmp(ALPN_H2, alpn_result.ProtocolId, ALPN_H2_LENGTH)) {
-        alpn = CURL_HTTP_VERSION_2;
-      }
-      else
-#endif
-        if(alpn_result.ProtocolIdSize == ALPN_HTTP_1_1_LENGTH &&
-           !memcmp(ALPN_HTTP_1_1, alpn_result.ProtocolId,
-                   ALPN_HTTP_1_1_LENGTH)) {
-          alpn = CURL_HTTP_VERSION_1_1;
-        }
+      Curl_alpn_set_negotiated(cf, data, alpn_result.ProtocolId,
+                               alpn_result.ProtocolIdSize);
       if(backend->recv_renegotiating) {
-        if(alpn != cf->conn->alpn) {
+        if(prev_alpn != cf->conn->alpn &&
+           prev_alpn != CURL_HTTP_VERSION_NONE) {
+          /* Renegotiation selected a different protocol now, we cannot
+           * deal with this */
           failf(data, "schannel: server selected an ALPN protocol too late");
           return CURLE_SSL_CONNECT_ERROR;
         }
       }
-      else
-        cf->conn->alpn = alpn;
     }
     else {
       if(!backend->recv_renegotiating)
-        infof(data, VTLS_INFOF_NO_ALPN);
-    }
-
-    if(!backend->recv_renegotiating) {
-      Curl_multiuse_state(data, cf->conn->alpn == CURL_HTTP_VERSION_2 ?
-                          BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE);
+        Curl_alpn_set_negotiated(cf, data, NULL, 0);
     }
   }
 #endif
@@ -1841,7 +1822,7 @@ schannel_connect_common(struct Curl_cfilter *cf,
 {
   CURLcode result;
   struct ssl_connect_data *connssl = cf->ctx;
-  curl_socket_t sockfd = cf->conn->sock[cf->sockindex];
+  curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data);
   timediff_t timeout_ms;
   int what;
 
@@ -2056,7 +2037,7 @@ schannel_send(struct Curl_cfilter *cf, struct Curl_easy *data,
       }
       else if(!timeout_ms)
         timeout_ms = TIMEDIFF_T_MAX;
-      what = SOCKET_WRITABLE(cf->conn->sock[cf->sockindex], timeout_ms);
+      what = SOCKET_WRITABLE(Curl_conn_cf_get_socket(cf, data), timeout_ms);
       if(what < 0) {
         /* fatal error */
         failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO);
diff --git a/lib/vtls/schannel.h b/lib/vtls/schannel.h
index 6d4235a..7fae39f 100644
--- a/lib/vtls/schannel.h
+++ b/lib/vtls/schannel.h
@@ -7,8 +7,8 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2012, Marc Hoersken, <info@marc-hoersken.de>, et al.
- * Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Marc Hoersken, <info@marc-hoersken.de>, et al.
+ * 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
@@ -179,8 +179,8 @@ struct ssl_backend_data {
   size_t encdata_offset, decdata_offset;
   unsigned char *encdata_buffer, *decdata_buffer;
   /* encdata_is_incomplete: if encdata contains only a partial record that
-     can't be decrypted without another Curl_read_plain (that is, status is
-     SEC_E_INCOMPLETE_MESSAGE) then set this true. after Curl_read_plain writes
+     can't be decrypted without another recv() (that is, status is
+     SEC_E_INCOMPLETE_MESSAGE) then set this true. after an recv() adds
      more bytes into encdata then set this back to false. */
   bool encdata_is_incomplete;
   unsigned long req_flags, ret_flags;
diff --git a/lib/vtls/schannel_verify.c b/lib/vtls/schannel_verify.c
index e499216..d75ee8d 100644
--- a/lib/vtls/schannel_verify.c
+++ b/lib/vtls/schannel_verify.c
@@ -5,9 +5,9 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2012 - 2016, Marc Hoersken, <info@marc-hoersken.de>
- * Copyright (C) 2012, Mark Salisbury, <mark.salisbury@hp.com>
- * Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Marc Hoersken, <info@marc-hoersken.de>
+ * Copyright (C) Mark Salisbury, <mark.salisbury@hp.com>
+ * 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
diff --git a/lib/vtls/sectransp.c b/lib/vtls/sectransp.c
index ab79654..2e98169 100644
--- a/lib/vtls/sectransp.c
+++ b/lib/vtls/sectransp.c
@@ -5,8 +5,8 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
- * Copyright (C) 2012 - 2017, Nick Zitzmann, <nickzman@gmail.com>.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Nick Zitzmann, <nickzman@gmail.com>.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
@@ -136,6 +136,7 @@
 /* The last #include file should be: */
 #include "memdebug.h"
 
+
 /* From MacTypes.h (which we can't include because it isn't present in iOS: */
 #define ioErr -36
 #define paramErr -50
@@ -831,13 +832,15 @@ static OSStatus bio_cf_in_read(SSLConnectionRef connection,
   struct Curl_cfilter *cf = (struct Curl_cfilter *)connection;
   struct ssl_connect_data *connssl = cf->ctx;
   struct ssl_backend_data *backend = connssl->backend;
-  struct Curl_easy *data = connssl->call_data;
+  struct Curl_easy *data = CF_DATA_CURRENT(cf);
   ssize_t nread;
   CURLcode result;
   OSStatus rtn = noErr;
 
   DEBUGASSERT(data);
   nread = Curl_conn_cf_recv(cf->next, data, buf, *dataLength, &result);
+  DEBUGF(LOG_CF(data, cf, "bio_read(len=%zu) -> %zd, result=%d",
+                *dataLength, nread, result));
   if(nread < 0) {
     switch(result) {
       case CURLE_OK:
@@ -851,6 +854,9 @@ static OSStatus bio_cf_in_read(SSLConnectionRef connection,
     }
     nread = 0;
   }
+  else if((size_t)nread < *dataLength) {
+    rtn = errSSLWouldBlock;
+  }
   *dataLength = nread;
   return rtn;
 }
@@ -862,25 +868,30 @@ static OSStatus bio_cf_out_write(SSLConnectionRef connection,
   struct Curl_cfilter *cf = (struct Curl_cfilter *)connection;
   struct ssl_connect_data *connssl = cf->ctx;
   struct ssl_backend_data *backend = connssl->backend;
-  struct Curl_easy *data = connssl->call_data;
+  struct Curl_easy *data = CF_DATA_CURRENT(cf);
   ssize_t nwritten;
   CURLcode result;
-  OSStatus ortn = noErr;
+  OSStatus rtn = noErr;
 
   DEBUGASSERT(data);
   nwritten = Curl_conn_cf_send(cf->next, data, buf, *dataLength, &result);
+  DEBUGF(LOG_CF(data, cf, "bio_send(len=%zu) -> %zd, result=%d",
+                *dataLength, nwritten, result));
   if(nwritten <= 0) {
     if(result == CURLE_AGAIN) {
-      ortn = errSSLWouldBlock;
+      rtn = errSSLWouldBlock;
       backend->ssl_direction = true;
     }
     else {
-      ortn = ioErr;
+      rtn = ioErr;
     }
     nwritten = 0;
   }
+  else if((size_t)nwritten < *dataLength) {
+    rtn = errSSLWouldBlock;
+  }
   *dataLength = nwritten;
-  return ortn;
+  return rtn;
 }
 
 #ifndef CURL_DISABLE_VERBOSE_STRINGS
@@ -1625,7 +1636,6 @@ static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf,
   const bool verifypeer = conn_config->verifypeer;
   char * const ssl_cert = ssl_config->primary.clientcert;
   const struct curl_blob *ssl_cert_blob = ssl_config->primary.cert_blob;
-  bool isproxy = Curl_ssl_cf_is_proxy(cf);
 #ifdef ENABLE_IPV6
   struct in6_addr addr;
 #else
@@ -1638,6 +1648,7 @@ static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf,
 
   DEBUGASSERT(backend);
 
+  DEBUGF(LOG_CF(data, cf, "connect_step1"));
   GetDarwinVersionNumber(&darwinver_maj, &darwinver_min);
 #endif /* CURL_BUILD_MAC */
 
@@ -1785,33 +1796,28 @@ static CURLcode sectransp_connect_step1(struct Curl_cfilter *cf,
 #endif /* CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS */
 
 #if (CURL_BUILD_MAC_10_13 || CURL_BUILD_IOS_11) && HAVE_BUILTIN_AVAILABLE == 1
-  if(cf->conn->bits.tls_enable_alpn) {
+  if(connssl->alpn) {
     if(__builtin_available(macOS 10.13.4, iOS 11, tvOS 11, *)) {
+      struct alpn_proto_buf proto;
+      size_t i;
+      CFStringRef cstr;
       CFMutableArrayRef alpnArr = CFArrayCreateMutable(NULL, 0,
                                                        &kCFTypeArrayCallBacks);
-
-#ifdef USE_HTTP2
-      if(data->state.httpwant >= CURL_HTTP_VERSION_2
-#ifndef CURL_DISABLE_PROXY
-         && (!isproxy || !cf->conn->bits.tunnel_proxy)
-#endif
-        ) {
-        CFArrayAppendValue(alpnArr, CFSTR(ALPN_H2));
-        infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_H2);
+      for(i = 0; i < connssl->alpn->count; ++i) {
+        cstr = CFStringCreateWithCString(NULL, connssl->alpn->entries[i],
+                                         kCFStringEncodingUTF8);
+        if(!cstr)
+          return CURLE_OUT_OF_MEMORY;
+        CFArrayAppendValue(alpnArr, cstr);
+        CFRelease(cstr);
       }
-#endif
-
-      CFArrayAppendValue(alpnArr, CFSTR(ALPN_HTTP_1_1));
-      infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_1);
-
-      /* expects length prefixed preference ordered list of protocols in wire
-       * format
-       */
       err = SSLSetALPNProtocols(backend->ssl_ctx, alpnArr);
       if(err != noErr)
         infof(data, "WARNING: failed to set ALPN protocols; OSStatus %d",
               err);
       CFRelease(alpnArr);
+      Curl_alpn_to_proto_str(&proto, connssl->alpn);
+      infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data);
     }
   }
 #endif
@@ -2231,7 +2237,8 @@ static int append_cert_to_array(struct Curl_easy *data,
     return CURLE_OK;
 }
 
-static CURLcode verify_cert_buf(struct Curl_easy *data,
+static CURLcode verify_cert_buf(struct Curl_cfilter *cf,
+                                struct Curl_easy *data,
                                 const unsigned char *certbuf, size_t buflen,
                                 SSLContextRef ctx)
 {
@@ -2239,7 +2246,12 @@ static CURLcode verify_cert_buf(struct Curl_easy *data,
   long res;
   unsigned char *der;
   size_t derlen, offset = 0;
-
+  OSStatus ret;
+  SecTrustResultType trust_eval;
+  CFMutableArrayRef array = NULL;
+  SecTrustRef trust = NULL;
+  CURLcode result = CURLE_PEER_FAILED_VERIFICATION;
+  (void)cf;
   /*
    * Certbuf now contains the contents of the certificate file, which can be
    * - a single DER certificate,
@@ -2249,11 +2261,11 @@ static CURLcode verify_cert_buf(struct Curl_easy *data,
    * Go through certbuf, and convert any PEM certificate in it into DER
    * format.
    */
-  CFMutableArrayRef array = CFArrayCreateMutable(kCFAllocatorDefault, 0,
-                                                 &kCFTypeArrayCallBacks);
+  array = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
   if(!array) {
     failf(data, "SSL: out of memory creating CA certificate array");
-    return CURLE_OUT_OF_MEMORY;
+    result = CURLE_OUT_OF_MEMORY;
+    goto out;
   }
 
   while(offset < buflen) {
@@ -2265,10 +2277,10 @@ static CURLcode verify_cert_buf(struct Curl_easy *data,
      */
     res = pem_to_der((const char *)certbuf + offset, &der, &derlen);
     if(res < 0) {
-      CFRelease(array);
       failf(data, "SSL: invalid CA certificate #%d (offset %zu) in bundle",
             n, offset);
-      return CURLE_SSL_CACERT_BADFILE;
+      result = CURLE_SSL_CACERT_BADFILE;
+      goto out;
     }
     offset += res;
 
@@ -2276,8 +2288,9 @@ static CURLcode verify_cert_buf(struct Curl_easy *data,
       /* This is not a PEM file, probably a certificate in DER format. */
       rc = append_cert_to_array(data, certbuf, buflen, array);
       if(rc != CURLE_OK) {
-        CFRelease(array);
-        return rc;
+        DEBUGF(LOG_CF(data, cf, "append_cert for CA failed"));
+        result = rc;
+        goto out;
       }
       break;
     }
@@ -2289,63 +2302,73 @@ static CURLcode verify_cert_buf(struct Curl_easy *data,
     rc = append_cert_to_array(data, der, derlen, array);
     free(der);
     if(rc != CURLE_OK) {
-      CFRelease(array);
-      return rc;
+      DEBUGF(LOG_CF(data, cf, "append_cert for CA failed"));
+      result = rc;
+      goto out;
     }
   }
 
-  SecTrustRef trust;
-  OSStatus ret = SSLCopyPeerTrust(ctx, &trust);
+  ret = SSLCopyPeerTrust(ctx, &trust);
   if(!trust) {
     failf(data, "SSL: error getting certificate chain");
-    CFRelease(array);
-    return CURLE_PEER_FAILED_VERIFICATION;
+    goto out;
   }
   else if(ret != noErr) {
-    CFRelease(array);
     failf(data, "SSLCopyPeerTrust() returned error %d", ret);
-    return CURLE_PEER_FAILED_VERIFICATION;
+    goto out;
   }
 
+  DEBUGF(LOG_CF(data, cf, "setting %d trust anchors", n));
   ret = SecTrustSetAnchorCertificates(trust, array);
   if(ret != noErr) {
-    CFRelease(array);
-    CFRelease(trust);
     failf(data, "SecTrustSetAnchorCertificates() returned error %d", ret);
-    return CURLE_PEER_FAILED_VERIFICATION;
+    goto out;
   }
   ret = SecTrustSetAnchorCertificatesOnly(trust, true);
   if(ret != noErr) {
-    CFRelease(array);
-    CFRelease(trust);
     failf(data, "SecTrustSetAnchorCertificatesOnly() returned error %d", ret);
-    return CURLE_PEER_FAILED_VERIFICATION;
+    goto out;
   }
 
-  SecTrustResultType trust_eval = 0;
+  trust_eval = 0;
   ret = SecTrustEvaluate(trust, &trust_eval);
-  CFRelease(array);
-  CFRelease(trust);
   if(ret != noErr) {
     failf(data, "SecTrustEvaluate() returned error %d", ret);
-    return CURLE_PEER_FAILED_VERIFICATION;
+    goto out;
   }
 
   switch(trust_eval) {
     case kSecTrustResultUnspecified:
+      /* what does this really mean? */
+      DEBUGF(LOG_CF(data, cf, "trust result: Unspecified"));
+      result = CURLE_OK;
+      goto out;
     case kSecTrustResultProceed:
-      return CURLE_OK;
+      DEBUGF(LOG_CF(data, cf, "trust result: Proceed"));
+      result = CURLE_OK;
+      goto out;
 
     case kSecTrustResultRecoverableTrustFailure:
+      failf(data, "SSL: peer not verified:  RecoverableTrustFailure");
+      goto out;
     case kSecTrustResultDeny:
+      failf(data, "SSL: peer not verified:  Deny");
+      goto out;
     default:
-      failf(data, "SSL: certificate verification failed (result: %d)",
-            trust_eval);
-      return CURLE_PEER_FAILED_VERIFICATION;
+      failf(data, "SSL: perr not verified: result=%d", trust_eval);
+      goto out;
   }
+
+out:
+  if(trust)
+    CFRelease(trust);
+  if(array)
+    CFRelease(array);
+  return result;
 }
 
-static CURLcode verify_cert(struct Curl_easy *data, const char *cafile,
+static CURLcode verify_cert(struct Curl_cfilter *cf,
+                            struct Curl_easy *data, const char *cafile,
                             const struct curl_blob *ca_info_blob,
                             SSLContextRef ctx)
 {
@@ -2354,6 +2377,7 @@ static CURLcode verify_cert(struct Curl_easy *data, const char *cafile,
   size_t buflen;
 
   if(ca_info_blob) {
+    DEBUGF(LOG_CF(data, cf, "verify_peer, CA from config blob"));
     certbuf = (unsigned char *)malloc(ca_info_blob->len + 1);
     if(!certbuf) {
       return CURLE_OUT_OF_MEMORY;
@@ -2363,6 +2387,7 @@ static CURLcode verify_cert(struct Curl_easy *data, const char *cafile,
     certbuf[ca_info_blob->len]='\0';
   }
   else if(cafile) {
+    DEBUGF(LOG_CF(data, cf, "verify_peer, CA from file '%s'", cafile));
     if(read_cert(cafile, &certbuf, &buflen) < 0) {
       failf(data, "SSL: failed to read or invalid CA certificate");
       return CURLE_SSL_CACERT_BADFILE;
@@ -2371,7 +2396,7 @@ static CURLcode verify_cert(struct Curl_easy *data, const char *cafile,
   else
     return CURLE_SSL_CACERT_BADFILE;
 
-  result = verify_cert_buf(data, certbuf, buflen, ctx);
+  result = verify_cert_buf(cf, data, certbuf, buflen, ctx);
   free(certbuf);
   return result;
 }
@@ -2498,8 +2523,10 @@ static CURLcode sectransp_connect_step2(struct Curl_cfilter *cf,
               || ssl_connect_2_reading == connssl->connecting_state
               || ssl_connect_2_writing == connssl->connecting_state);
   DEBUGASSERT(backend);
+  DEBUGF(LOG_CF(data, cf, "connect_step2"));
 
   /* Here goes nothing: */
+check_handshake:
   err = SSLHandshake(backend->ssl_ctx);
 
   if(err != noErr) {
@@ -2514,14 +2541,14 @@ static CURLcode sectransp_connect_step2(struct Curl_cfilter *cf,
       case -9841:
         if((conn_config->CAfile || conn_config->ca_info_blob) &&
            conn_config->verifypeer) {
-          CURLcode result = verify_cert(data, conn_config->CAfile,
+          CURLcode result = verify_cert(cf, data, conn_config->CAfile,
                                         conn_config->ca_info_blob,
                                         backend->ssl_ctx);
           if(result)
             return result;
         }
         /* the documentation says we need to call SSLHandshake() again */
-        return sectransp_connect_step2(cf, data);
+        goto check_handshake;
 
       /* Problem with encrypt / decrypt */
       case errSSLPeerDecodeError:
@@ -2961,6 +2988,7 @@ static CURLcode sectransp_connect_step3(struct Curl_cfilter *cf,
 {
   struct ssl_connect_data *connssl = cf->ctx;
 
+  DEBUGF(LOG_CF(data, cf, "connect_step3"));
   /* There is no step 3!
    * Well, okay, let's collect server certificates, and if verbose mode is on,
    * let's print the details of the server certificates. */
@@ -2979,7 +3007,7 @@ sectransp_connect_common(struct Curl_cfilter *cf, struct Curl_easy *data,
 {
   CURLcode result;
   struct ssl_connect_data *connssl = cf->ctx;
-  curl_socket_t sockfd = cf->conn->sock[cf->sockindex];
+  curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data);
   int what;
 
   /* check if the connection has already been established */
@@ -3069,6 +3097,7 @@ sectransp_connect_common(struct Curl_cfilter *cf, struct Curl_easy *data,
   }
 
   if(ssl_connect_done == connssl->connecting_state) {
+    DEBUGF(LOG_CF(data, cf, "connected"));
     connssl->state = ssl_connection_complete;
     *done = TRUE;
   }
@@ -3114,6 +3143,7 @@ static void sectransp_close(struct Curl_cfilter *cf, struct Curl_easy *data)
   DEBUGASSERT(backend);
 
   if(backend->ssl_ctx) {
+    DEBUGF(LOG_CF(data, cf, "close"));
     (void)SSLClose(backend->ssl_ctx);
 #if CURL_BUILD_MAC_10_8 || CURL_BUILD_IOS
     if(SSLCreateContext)
@@ -3155,8 +3185,10 @@ static int sectransp_shutdown(struct Curl_cfilter *cf,
 
   rc = 0;
 
-  what = SOCKET_READABLE(cf->conn->sock[cf->sockindex], SSL_SHUTDOWN_TIMEOUT);
+  what = SOCKET_READABLE(Curl_conn_cf_get_socket(cf, data),
+                         SSL_SHUTDOWN_TIMEOUT);
 
+  DEBUGF(LOG_CF(data, cf, "shutdown"));
   while(loop--) {
     if(what < 0) {
       /* anything that gets here is fatally bad */
@@ -3183,7 +3215,7 @@ static int sectransp_shutdown(struct Curl_cfilter *cf,
     if(nread <= 0)
       break;
 
-    what = SOCKET_READABLE(cf->conn->sock[cf->sockindex], 0);
+    what = SOCKET_READABLE(Curl_conn_cf_get_socket(cf, data), 0);
   }
 
   return rc;
@@ -3225,6 +3257,7 @@ static int sectransp_check_cxn(struct Curl_cfilter *cf,
   DEBUGASSERT(backend);
 
   if(backend->ssl_ctx) {
+    DEBUGF(LOG_CF(data, cf, "check connection"));
     err = SSLGetSessionState(backend->ssl_ctx, &state);
     if(err == noErr)
       return state == kSSLConnected || state == kSSLHandshake;
@@ -3245,6 +3278,7 @@ static bool sectransp_data_pending(struct Curl_cfilter *cf,
   DEBUGASSERT(backend);
 
   if(backend->ssl_ctx) {  /* SSL is in use */
+    DEBUGF(LOG_CF((struct Curl_easy *)data, cf, "data_pending"));
     err = SSLGetBufferedReadSize(backend->ssl_ctx, &buffer);
     if(err == noErr)
       return buffer > 0UL;
@@ -3402,7 +3436,7 @@ static ssize_t sectransp_recv(struct Curl_cfilter *cf,
       case -9841:
         if((conn_config->CAfile || conn_config->ca_info_blob) &&
            conn_config->verifypeer) {
-          CURLcode result = verify_cert(data, conn_config->CAfile,
+          CURLcode result = verify_cert(cf, data, conn_config->CAfile,
                                         conn_config->ca_info_blob,
                                         backend->ssl_ctx);
           if(result)
diff --git a/lib/vtls/sectransp.h b/lib/vtls/sectransp.h
index 2d53b7c..0f1085a 100644
--- a/lib/vtls/sectransp.h
+++ b/lib/vtls/sectransp.h
@@ -7,8 +7,8 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2012 - 2014, Nick Zitzmann, <nickzman@gmail.com>.
- * Copyright (C) 2012 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) Nick Zitzmann, <nickzman@gmail.com>.
+ * 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
diff --git a/lib/vtls/vtls.c b/lib/vtls/vtls.c
index 873ee6b..15f6844 100644
--- a/lib/vtls/vtls.c
+++ b/lib/vtls/vtls.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -73,6 +73,7 @@
 #include "curl_memory.h"
 #include "memdebug.h"
 
+
 /* convenience macro to check if this handle is using a shared SSL session */
 #define SSLSESSION_SHARED(data) (data->share &&                        \
                                  (data->share->specifier &             \
@@ -150,7 +151,6 @@ Curl_ssl_config_matches(struct ssl_primary_config *data,
 #ifdef USE_TLS_SRP
      !Curl_timestrcmp(data->username, needle->username) &&
      !Curl_timestrcmp(data->password, needle->password) &&
-     (data->authtype == needle->authtype) &&
 #endif
      strcasecompare(data->cipher_list, needle->cipher_list) &&
      strcasecompare(data->cipher_list13, needle->cipher_list13) &&
@@ -173,9 +173,6 @@ Curl_clone_primary_ssl_config(struct ssl_primary_config *source,
   dest->verifystatus = source->verifystatus;
   dest->sessionid = source->sessionid;
   dest->ssl_options = source->ssl_options;
-#ifdef USE_TLS_SRP
-  dest->authtype = source->authtype;
-#endif
 
   CLONE_BLOB(cert_blob);
   CLONE_BLOB(ca_info_blob);
@@ -272,8 +269,8 @@ void Curl_ssl_cleanup(void)
 static bool ssl_prefs_check(struct Curl_easy *data)
 {
   /* check for CURLOPT_SSLVERSION invalid parameter value */
-  const long sslver = data->set.ssl.primary.version;
-  if((sslver < 0) || (sslver >= CURL_SSLVERSION_LAST)) {
+  const unsigned char sslver = data->set.ssl.primary.version;
+  if(sslver >= CURL_SSLVERSION_LAST) {
     failf(data, "Unrecognized parameter value passed via CURLOPT_SSLVERSION");
     return FALSE;
   }
@@ -293,7 +290,8 @@ static bool ssl_prefs_check(struct Curl_easy *data)
   return TRUE;
 }
 
-static struct ssl_connect_data *cf_ctx_new(struct Curl_easy *data)
+static struct ssl_connect_data *cf_ctx_new(struct Curl_easy *data,
+                                           const struct alpn_spec *alpn)
 {
   struct ssl_connect_data *ctx;
 
@@ -302,6 +300,7 @@ static struct ssl_connect_data *cf_ctx_new(struct Curl_easy *data)
   if(!ctx)
     return NULL;
 
+  ctx->alpn = alpn;
   ctx->backend = calloc(1, Curl_ssl->sizeof_ssl_backend_data);
   if(!ctx->backend) {
     free(ctx);
@@ -318,13 +317,6 @@ static void cf_ctx_free(struct ssl_connect_data *ctx)
   }
 }
 
-static void cf_ctx_set_data(struct Curl_cfilter *cf,
-                            struct Curl_easy *data)
-{
-  if(cf->ctx)
-    ((struct ssl_connect_data *)cf->ctx)->call_data = data;
-}
-
 static CURLcode ssl_connect(struct Curl_cfilter *cf, struct Curl_easy *data)
 {
   struct ssl_connect_data *connssl = cf->ctx;
@@ -339,7 +331,6 @@ static CURLcode ssl_connect(struct Curl_cfilter *cf, struct Curl_easy *data)
   result = Curl_ssl->connect_blocking(cf, data);
 
   if(!result) {
-    Curl_pgrsTime(data, TIMER_APPCONNECT); /* SSL is connected */
     DEBUGASSERT(connssl->state == ssl_connection_complete);
   }
 
@@ -615,19 +606,20 @@ int Curl_ssl_get_select_socks(struct Curl_cfilter *cf, struct Curl_easy *data,
                               curl_socket_t *socks)
 {
   struct ssl_connect_data *connssl = cf->ctx;
+  curl_socket_t sock = Curl_conn_cf_get_socket(cf->next, data);
 
-  (void)data;
-  if(connssl->connecting_state == ssl_connect_2_writing) {
-    /* write mode */
-    socks[0] = cf->conn->sock[FIRSTSOCKET];
-    return GETSOCK_WRITESOCK(0);
-  }
-  if(connssl->connecting_state == ssl_connect_2_reading) {
-    /* read mode */
-    socks[0] = cf->conn->sock[FIRSTSOCKET];
-    return GETSOCK_READSOCK(0);
+  if(sock != CURL_SOCKET_BAD) {
+    if(connssl->connecting_state == ssl_connect_2_writing) {
+      /* write mode */
+      socks[0] = sock;
+      return GETSOCK_WRITESOCK(0);
+    }
+    if(connssl->connecting_state == ssl_connect_2_reading) {
+      /* read mode */
+      socks[0] = sock;
+      return GETSOCK_READSOCK(0);
+    }
   }
-
   return GETSOCK_BLANK;
 }
 
@@ -685,20 +677,6 @@ void Curl_ssl_version(char *buffer, size_t size)
 #endif
 }
 
-/*
- * This function tries to determine connection status.
- *
- * Return codes:
- *     1 means the connection is still in place
- *     0 means the connection has been closed
- *    -1 means the connection status is unknown
- */
-int Curl_ssl_check_cxn(struct Curl_easy *data, struct connectdata *conn)
-{
-  struct Curl_cfilter *cf = Curl_ssl_cf_get_ssl(conn->cfilter[FIRSTSOCKET]);
-  return cf? Curl_ssl->check_cxn(cf, data) : -1;
-}
-
 void Curl_ssl_free_certinfo(struct Curl_easy *data)
 {
   struct curl_certinfo *ci = &data->info.certs;
@@ -1430,53 +1408,80 @@ CURLsslset Curl_init_sslset_nolock(curl_sslbackend id, const char *name,
 
 #ifdef USE_SSL
 
+static void free_hostname(struct ssl_connect_data *connssl)
+{
+  if(connssl->dispname != connssl->hostname)
+    free(connssl->dispname);
+  free(connssl->hostname);
+  connssl->hostname = connssl->dispname = NULL;
+}
+
 static void cf_close(struct Curl_cfilter *cf, struct Curl_easy *data)
 {
   struct ssl_connect_data *connssl = cf->ctx;
-  /* TODO: close_one closes BOTH conn->ssl AND conn->proxy_ssl for this
-   * sockindex (if in use). Gladly, it is safe to call more than once. */
   if(connssl) {
     Curl_ssl->close(cf, data);
     connssl->state = ssl_connection_none;
+    free_hostname(connssl);
   }
   cf->connected = FALSE;
 }
 
-static void reinit_hostname(struct Curl_cfilter *cf)
+static CURLcode reinit_hostname(struct Curl_cfilter *cf)
 {
   struct ssl_connect_data *connssl = cf->ctx;
+  const char *ehostname, *edispname;
+  int eport;
 
+  /* We need the hostname for SNI negotiation. Once handshaked, this
+   * remains the SNI hostname for the TLS connection. But when the
+   * connection is reused, the settings in cf->conn might change.
+   * So we keep a copy of the hostname we use for SNI.
+   */
 #ifndef CURL_DISABLE_PROXY
   if(Curl_ssl_cf_is_proxy(cf)) {
-    /* TODO: there is not definition for a proxy setup on a secondary conn */
-    connssl->hostname = cf->conn->http_proxy.host.name;
-    connssl->dispname = cf->conn->http_proxy.host.dispname;
-    connssl->port = cf->conn->http_proxy.port;
+    ehostname = cf->conn->http_proxy.host.name;
+    edispname = cf->conn->http_proxy.host.dispname;
+    eport = cf->conn->http_proxy.port;
   }
   else
 #endif
   {
-    /* TODO: secondaryhostname is set to the IP address we connect to
-     * in the FTP handler, it is assumed that host verification uses the
-     * hostname from FIRSTSOCKET */
-    if(cf->sockindex == SECONDARYSOCKET && 0) {
-      connssl->hostname = cf->conn->secondaryhostname;
-      connssl->dispname = connssl->hostname;
-      connssl->port = cf->conn->secondary_port;
+    ehostname = cf->conn->host.name;
+    edispname = cf->conn->host.dispname;
+    eport = cf->conn->remote_port;
+  }
+
+  /* change if ehostname changed */
+  if(ehostname && (!connssl->hostname
+                   || strcmp(ehostname, connssl->hostname))) {
+    free_hostname(connssl);
+    connssl->hostname = strdup(ehostname);
+    if(!connssl->hostname) {
+      free_hostname(connssl);
+      return CURLE_OUT_OF_MEMORY;
     }
+    if(!edispname || !strcmp(ehostname, edispname))
+      connssl->dispname = connssl->hostname;
     else {
-      connssl->hostname = cf->conn->host.name;
-      connssl->dispname = cf->conn->host.dispname;
-      connssl->port = cf->conn->remote_port;
+      connssl->dispname = strdup(edispname);
+      if(!connssl->dispname) {
+        free_hostname(connssl);
+        return CURLE_OUT_OF_MEMORY;
+      }
     }
   }
-  DEBUGASSERT(connssl->hostname);
+  connssl->port = eport;
+  return CURLE_OK;
 }
 
 static void ssl_cf_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
 {
-  cf_ctx_set_data(cf, data);
+  struct cf_call_data save;
+
+  CF_DATA_SAVE(save, cf, data);
   cf_close(cf, data);
+  CF_DATA_RESTORE(cf, save);
   cf_ctx_free(cf->ctx);
   cf->ctx = NULL;
 }
@@ -1484,10 +1489,12 @@ static void ssl_cf_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
 static void ssl_cf_close(struct Curl_cfilter *cf,
                          struct Curl_easy *data)
 {
-  cf_ctx_set_data(cf, data);
+  struct cf_call_data save;
+
+  CF_DATA_SAVE(save, cf, data);
   cf_close(cf, data);
   cf->next->cft->close(cf->next, data);
-  cf_ctx_set_data(cf, NULL);
+  CF_DATA_RESTORE(cf, save);
 }
 
 static CURLcode ssl_cf_connect(struct Curl_cfilter *cf,
@@ -1495,6 +1502,7 @@ static CURLcode ssl_cf_connect(struct Curl_cfilter *cf,
                                bool blocking, bool *done)
 {
   struct ssl_connect_data *connssl = cf->ctx;
+  struct cf_call_data save;
   CURLcode result;
 
   if(cf->connected) {
@@ -1502,7 +1510,7 @@ static CURLcode ssl_cf_connect(struct Curl_cfilter *cf,
     return CURLE_OK;
   }
 
-  cf_ctx_set_data(cf, data);
+  CF_DATA_SAVE(save, cf, data);
   (void)connssl;
   DEBUGASSERT(data->conn);
   DEBUGASSERT(data->conn == cf->conn);
@@ -1513,10 +1521,10 @@ static CURLcode ssl_cf_connect(struct Curl_cfilter *cf,
   if(result || !*done)
     goto out;
 
-  /* TODO: right now we do not fully control when hostname is set,
-   * assign it on each connect call. */
-  reinit_hostname(cf);
   *done = FALSE;
+  result = reinit_hostname(cf);
+  if(result)
+    goto out;
 
   if(blocking) {
     result = ssl_connect(cf, data);
@@ -1528,26 +1536,26 @@ static CURLcode ssl_cf_connect(struct Curl_cfilter *cf,
 
   if(!result && *done) {
     cf->connected = TRUE;
-    if(cf->sockindex == FIRSTSOCKET && !Curl_ssl_cf_is_proxy(cf))
-      Curl_pgrsTime(data, TIMER_APPCONNECT); /* SSL is connected */
+    connssl->handshake_done = Curl_now();
     DEBUGASSERT(connssl->state == ssl_connection_complete);
   }
 out:
-  cf_ctx_set_data(cf, NULL);
+  CF_DATA_RESTORE(cf, save);
   return result;
 }
 
 static bool ssl_cf_data_pending(struct Curl_cfilter *cf,
                                 const struct Curl_easy *data)
 {
+  struct cf_call_data save;
   bool result;
 
-  cf_ctx_set_data(cf, (struct Curl_easy *)data);
-  if(cf->ctx && Curl_ssl->data_pending(cf, data))
+  CF_DATA_SAVE(save, cf, data);
+  if(Curl_ssl->data_pending(cf, data))
     result = TRUE;
   else
     result = cf->next->cft->has_data_pending(cf->next, data);
-  cf_ctx_set_data(cf, NULL);
+  CF_DATA_RESTORE(cf, save);
   return result;
 }
 
@@ -1555,12 +1563,13 @@ static ssize_t ssl_cf_send(struct Curl_cfilter *cf,
                            struct Curl_easy *data, const void *buf, size_t len,
                            CURLcode *err)
 {
+  struct cf_call_data save;
   ssize_t nwritten;
 
+  CF_DATA_SAVE(save, cf, data);
   *err = CURLE_OK;
-  cf_ctx_set_data(cf, data);
   nwritten = Curl_ssl->send_plain(cf, data, buf, len, err);
-  cf_ctx_set_data(cf, NULL);
+  CF_DATA_RESTORE(cf, save);
   return nwritten;
 }
 
@@ -1568,12 +1577,13 @@ static ssize_t ssl_cf_recv(struct Curl_cfilter *cf,
                            struct Curl_easy *data, char *buf, size_t len,
                            CURLcode *err)
 {
+  struct cf_call_data save;
   ssize_t nread;
 
+  CF_DATA_SAVE(save, cf, data);
   *err = CURLE_OK;
-  cf_ctx_set_data(cf, data);
   nread = Curl_ssl->recv_plain(cf, data, buf, len, err);
-  cf_ctx_set_data(cf, NULL);
+  CF_DATA_RESTORE(cf, save);
   return nread;
 }
 
@@ -1581,39 +1591,72 @@ static int ssl_cf_get_select_socks(struct Curl_cfilter *cf,
                                    struct Curl_easy *data,
                                    curl_socket_t *socks)
 {
+  struct cf_call_data save;
   int result;
 
-  cf_ctx_set_data(cf, data);
+  CF_DATA_SAVE(save, cf, data);
   result = Curl_ssl->get_select_socks(cf, data, socks);
-  cf_ctx_set_data(cf, NULL);
+  CF_DATA_RESTORE(cf, save);
   return result;
 }
 
-static void ssl_cf_attach_data(struct Curl_cfilter *cf,
-                               struct Curl_easy *data)
+static CURLcode ssl_cf_cntrl(struct Curl_cfilter *cf,
+                             struct Curl_easy *data,
+                             int event, int arg1, void *arg2)
 {
-  if(Curl_ssl->attach_data) {
-    cf_ctx_set_data(cf, data);
-    Curl_ssl->attach_data(cf, data);
-    cf_ctx_set_data(cf, NULL);
+  struct ssl_connect_data *connssl = cf->ctx;
+  struct cf_call_data save;
+
+  (void)arg1;
+  (void)arg2;
+  switch(event) {
+  case CF_CTRL_CONN_REPORT_STATS:
+    if(cf->sockindex == FIRSTSOCKET && !Curl_ssl_cf_is_proxy(cf))
+      Curl_pgrsTimeWas(data, TIMER_APPCONNECT, connssl->handshake_done);
+    break;
+  case CF_CTRL_DATA_ATTACH:
+    if(Curl_ssl->attach_data) {
+      CF_DATA_SAVE(save, cf, data);
+      Curl_ssl->attach_data(cf, data);
+      CF_DATA_RESTORE(cf, save);
+    }
+    break;
+  case CF_CTRL_DATA_DETACH:
+    if(Curl_ssl->detach_data) {
+      CF_DATA_SAVE(save, cf, data);
+      Curl_ssl->detach_data(cf, data);
+      CF_DATA_RESTORE(cf, save);
+    }
+    break;
+  default:
+    break;
   }
+  return CURLE_OK;
 }
 
-static void ssl_cf_detach_data(struct Curl_cfilter *cf,
-                               struct Curl_easy *data)
+static bool cf_ssl_is_alive(struct Curl_cfilter *cf, struct Curl_easy *data)
 {
-  if(Curl_ssl->detach_data) {
-    cf_ctx_set_data(cf, data);
-    Curl_ssl->detach_data(cf, data);
-    cf_ctx_set_data(cf, NULL);
-  }
+  struct cf_call_data save;
+  bool result;
+  /*
+   * This function tries to determine connection status.
+   *
+   * Return codes:
+   *     1 means the connection is still in place
+   *     0 means the connection has been closed
+   *    -1 means the connection status is unknown
+   */
+  CF_DATA_SAVE(save, cf, data);
+  result = Curl_ssl->check_cxn(cf, data) != 0;
+  CF_DATA_RESTORE(cf, save);
+  return result;
 }
 
-static const struct Curl_cftype cft_ssl = {
+struct Curl_cftype Curl_cft_ssl = {
   "SSL",
   CF_TYPE_SSL,
+  CURL_LOG_DEFAULT,
   ssl_cf_destroy,
-  Curl_cf_def_setup,
   ssl_cf_connect,
   ssl_cf_close,
   Curl_cf_def_get_host,
@@ -1621,15 +1664,17 @@ static const struct Curl_cftype cft_ssl = {
   ssl_cf_data_pending,
   ssl_cf_send,
   ssl_cf_recv,
-  ssl_cf_attach_data,
-  ssl_cf_detach_data,
+  ssl_cf_cntrl,
+  cf_ssl_is_alive,
+  Curl_cf_def_conn_keep_alive,
+  Curl_cf_def_query,
 };
 
-static const struct Curl_cftype cft_ssl_proxy = {
+struct Curl_cftype Curl_cft_ssl_proxy = {
   "SSL-PROXY",
   CF_TYPE_SSL,
+  CURL_LOG_DEFAULT,
   ssl_cf_destroy,
-  Curl_cf_def_setup,
   ssl_cf_connect,
   ssl_cf_close,
   Curl_cf_def_get_host,
@@ -1637,65 +1682,107 @@ static const struct Curl_cftype cft_ssl_proxy = {
   ssl_cf_data_pending,
   ssl_cf_send,
   ssl_cf_recv,
-  ssl_cf_attach_data,
-  ssl_cf_detach_data,
+  ssl_cf_cntrl,
+  cf_ssl_is_alive,
+  Curl_cf_def_conn_keep_alive,
+  Curl_cf_def_query,
 };
 
-CURLcode Curl_ssl_cfilter_add(struct Curl_easy *data,
-                              struct connectdata *conn,
-                              int sockindex)
+static CURLcode cf_ssl_create(struct Curl_cfilter **pcf,
+                              struct Curl_easy *data,
+                              struct connectdata *conn)
 {
-  struct Curl_cfilter *cf;
+  struct Curl_cfilter *cf = NULL;
   struct ssl_connect_data *ctx;
   CURLcode result;
 
   DEBUGASSERT(data->conn);
-  ctx = cf_ctx_new(data);
+
+  ctx = cf_ctx_new(data, Curl_alpn_get_spec(data, conn));
   if(!ctx) {
     result = CURLE_OUT_OF_MEMORY;
     goto out;
   }
 
-  result = Curl_cf_create(&cf, &cft_ssl, ctx);
-  if(result)
-    goto out;
-
-  Curl_conn_cf_add(data, conn, sockindex, cf);
-
-  result = CURLE_OK;
+  result = Curl_cf_create(&cf, &Curl_cft_ssl, ctx);
 
 out:
   if(result)
     cf_ctx_free(ctx);
+  *pcf = result? NULL : cf;
   return result;
 }
 
-#ifndef CURL_DISABLE_PROXY
-CURLcode Curl_ssl_cfilter_proxy_add(struct Curl_easy *data,
-                                    struct connectdata *conn,
-                                    int sockindex)
+CURLcode Curl_ssl_cfilter_add(struct Curl_easy *data,
+                              struct connectdata *conn,
+                              int sockindex)
 {
   struct Curl_cfilter *cf;
+  CURLcode result;
+
+  result = cf_ssl_create(&cf, data, conn);
+  if(!result)
+    Curl_conn_cf_add(data, conn, sockindex, cf);
+  return result;
+}
+
+CURLcode Curl_cf_ssl_insert_after(struct Curl_cfilter *cf_at,
+                                  struct Curl_easy *data)
+{
+  struct Curl_cfilter *cf;
+  CURLcode result;
+
+  result = cf_ssl_create(&cf, data, cf_at->conn);
+  if(!result)
+    Curl_conn_cf_insert_after(cf_at, cf);
+  return result;
+}
+
+#ifndef CURL_DISABLE_PROXY
+static CURLcode cf_ssl_proxy_create(struct Curl_cfilter **pcf,
+                                    struct Curl_easy *data,
+                                    struct connectdata *conn)
+{
+  struct Curl_cfilter *cf = NULL;
   struct ssl_connect_data *ctx;
   CURLcode result;
 
-  ctx = cf_ctx_new(data);
+  ctx = cf_ctx_new(data, Curl_alpn_get_proxy_spec(data, conn));
   if(!ctx) {
     result = CURLE_OUT_OF_MEMORY;
     goto out;
   }
+  result = Curl_cf_create(&cf, &Curl_cft_ssl_proxy, ctx);
 
-  result = Curl_cf_create(&cf, &cft_ssl_proxy, ctx);
+out:
   if(result)
-    goto out;
+    cf_ctx_free(ctx);
+  *pcf = result? NULL : cf;
+  return result;
+}
 
-  Curl_conn_cf_add(data, conn, sockindex, cf);
+CURLcode Curl_ssl_cfilter_proxy_add(struct Curl_easy *data,
+                                    struct connectdata *conn,
+                                    int sockindex)
+{
+  struct Curl_cfilter *cf;
+  CURLcode result;
 
-  result = CURLE_OK;
+  result = cf_ssl_proxy_create(&cf, data, conn);
+  if(!result)
+    Curl_conn_cf_add(data, conn, sockindex, cf);
+  return result;
+}
 
-out:
-  if(result)
-    cf_ctx_free(ctx);
+CURLcode Curl_cf_ssl_proxy_insert_after(struct Curl_cfilter *cf_at,
+                                        struct Curl_easy *data)
+{
+  struct Curl_cfilter *cf;
+  CURLcode result;
+
+  result = cf_ssl_proxy_create(&cf, data, cf_at->conn);
+  if(!result)
+    Curl_conn_cf_insert_after(cf_at, cf);
   return result;
 }
 
@@ -1717,9 +1804,10 @@ void *Curl_ssl_get_internals(struct Curl_easy *data, int sockindex,
     /* get first filter in chain, if any is present */
     cf = Curl_ssl_cf_get_ssl(data->conn->cfilter[sockindex]);
     if(cf) {
-      cf_ctx_set_data(cf, data);
+      struct cf_call_data save;
+      CF_DATA_SAVE(save, cf, data);
       result = Curl_ssl->get_internals(cf->ctx, info);
-      cf_ctx_set_data(cf, NULL);
+      CF_DATA_RESTORE(cf, save);
     }
   }
   return result;
@@ -1733,7 +1821,7 @@ CURLcode Curl_ssl_cfilter_remove(struct Curl_easy *data,
 
   (void)data;
   for(; cf; cf = cf->next) {
-    if(cf->cft == &cft_ssl) {
+    if(cf->cft == &Curl_cft_ssl) {
       if(Curl_ssl->shut_down(cf, data))
         result = CURLE_SSL_SHUTDOWN_FAILED;
       Curl_conn_cf_discard(cf, data);
@@ -1749,7 +1837,7 @@ static struct Curl_cfilter *get_ssl_cf_engaged(struct connectdata *conn,
   struct Curl_cfilter *cf, *lowest_ssl_cf = NULL;
 
   for(cf = conn->cfilter[sockindex]; cf; cf = cf->next) {
-    if(cf->cft == &cft_ssl || cf->cft == &cft_ssl_proxy) {
+    if(cf->cft == &Curl_cft_ssl || cf->cft == &Curl_cft_ssl_proxy) {
       lowest_ssl_cf = cf;
       if(cf->connected || (cf->next && cf->next->connected)) {
         /* connected or about to start */
@@ -1762,7 +1850,7 @@ static struct Curl_cfilter *get_ssl_cf_engaged(struct connectdata *conn,
 
 bool Curl_ssl_cf_is_proxy(struct Curl_cfilter *cf)
 {
-  return (cf->cft == &cft_ssl_proxy);
+  return (cf->cft == &Curl_cft_ssl_proxy);
 }
 
 struct ssl_config_data *
@@ -1814,10 +1902,142 @@ Curl_ssl_get_primary_config(struct Curl_easy *data,
 struct Curl_cfilter *Curl_ssl_cf_get_ssl(struct Curl_cfilter *cf)
 {
   for(; cf; cf = cf->next) {
-    if(cf->cft == &cft_ssl || cf->cft == &cft_ssl_proxy)
+    if(cf->cft == &Curl_cft_ssl || cf->cft == &Curl_cft_ssl_proxy)
       return cf;
   }
   return NULL;
 }
 
+static const struct alpn_spec ALPN_SPEC_H10 = {
+  { ALPN_HTTP_1_0 }, 1
+};
+static const struct alpn_spec ALPN_SPEC_H11 = {
+  { ALPN_HTTP_1_1 }, 1
+};
+#ifdef USE_HTTP2
+static const struct alpn_spec ALPN_SPEC_H2_H11 = {
+  { ALPN_H2, ALPN_HTTP_1_1 }, 2
+};
+#endif
+
+const struct alpn_spec *
+Curl_alpn_get_spec(struct Curl_easy *data, struct connectdata *conn)
+{
+  if(!conn->bits.tls_enable_alpn)
+    return NULL;
+  if(data->state.httpwant == CURL_HTTP_VERSION_1_0)
+    return &ALPN_SPEC_H10;
+#ifdef USE_HTTP2
+  if(data->state.httpwant >= CURL_HTTP_VERSION_2)
+    return &ALPN_SPEC_H2_H11;
+#endif
+  return &ALPN_SPEC_H11;
+}
+
+const struct alpn_spec *
+Curl_alpn_get_proxy_spec(struct Curl_easy *data, struct connectdata *conn)
+{
+  if(!conn->bits.tls_enable_alpn)
+    return NULL;
+  if(data->state.httpwant == CURL_HTTP_VERSION_1_0)
+    return &ALPN_SPEC_H10;
+  return &ALPN_SPEC_H11;
+}
+
+CURLcode Curl_alpn_to_proto_buf(struct alpn_proto_buf *buf,
+                                const struct alpn_spec *spec)
+{
+  size_t i, len;
+  int off = 0;
+  unsigned char blen;
+
+  memset(buf, 0, sizeof(*buf));
+  for(i = 0; spec && i < spec->count; ++i) {
+    len = strlen(spec->entries[i]);
+    if(len >= ALPN_NAME_MAX)
+      return CURLE_FAILED_INIT;
+    blen = (unsigned  char)len;
+    if(off + blen + 1 >= (int)sizeof(buf->data))
+      return CURLE_FAILED_INIT;
+    buf->data[off++] = blen;
+    memcpy(buf->data + off, spec->entries[i], blen);
+    off += blen;
+  }
+  buf->len = off;
+  return CURLE_OK;
+}
+
+CURLcode Curl_alpn_to_proto_str(struct alpn_proto_buf *buf,
+                                const struct alpn_spec *spec)
+{
+  size_t i, len;
+  size_t off = 0;
+
+  memset(buf, 0, sizeof(*buf));
+  for(i = 0; spec && i < spec->count; ++i) {
+    len = strlen(spec->entries[i]);
+    if(len >= ALPN_NAME_MAX)
+      return CURLE_FAILED_INIT;
+    if(off + len + 2 >= (int)sizeof(buf->data))
+      return CURLE_FAILED_INIT;
+    if(off)
+      buf->data[off++] = ',';
+    memcpy(buf->data + off, spec->entries[i], len);
+    off += len;
+  }
+  buf->data[off] = '\0';
+  buf->len = (int)off;
+  return CURLE_OK;
+}
+
+CURLcode Curl_alpn_set_negotiated(struct Curl_cfilter *cf,
+                                  struct Curl_easy *data,
+                                  const unsigned char *proto,
+                                  size_t proto_len)
+{
+  int can_multi = 0;
+
+  if(proto && proto_len) {
+    if(proto_len == ALPN_HTTP_1_1_LENGTH &&
+            !memcmp(ALPN_HTTP_1_1, proto, ALPN_HTTP_1_1_LENGTH)) {
+      cf->conn->alpn = CURL_HTTP_VERSION_1_1;
+    }
+    else if(proto_len == ALPN_HTTP_1_0_LENGTH &&
+            !memcmp(ALPN_HTTP_1_0, proto, ALPN_HTTP_1_0_LENGTH)) {
+      cf->conn->alpn = CURL_HTTP_VERSION_1_0;
+    }
+#ifdef USE_HTTP2
+    else if(proto_len == ALPN_H2_LENGTH &&
+            !memcmp(ALPN_H2, proto, ALPN_H2_LENGTH)) {
+      cf->conn->alpn = CURL_HTTP_VERSION_2;
+      can_multi = 1;
+    }
+#endif
+#ifdef USE_HTTP3
+    else if(proto_len == ALPN_H3_LENGTH &&
+       !memcmp(ALPN_H3, proto, ALPN_H3_LENGTH)) {
+      cf->conn->alpn = CURL_HTTP_VERSION_3;
+      can_multi = 1;
+    }
+#endif
+    else {
+      cf->conn->alpn = CURL_HTTP_VERSION_NONE;
+      failf(data, "unsupported ALPN protocol: '%.*s'", (int)proto_len, proto);
+      /* TODO: do we want to fail this? Previous code just ignored it and
+       * some vtls backends even ignore the return code of this function. */
+      /* return CURLE_NOT_BUILT_IN; */
+      goto out;
+    }
+    infof(data, VTLS_INFOF_ALPN_ACCEPTED_LEN_1STR, (int)proto_len, proto);
+  }
+  else {
+    cf->conn->alpn = CURL_HTTP_VERSION_NONE;
+    infof(data, VTLS_INFOF_NO_ALPN);
+  }
+
+out:
+  Curl_multiuse_state(data, can_multi? BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE);
+  return CURLE_OK;
+}
+
 #endif /* USE_SSL */
diff --git a/lib/vtls/vtls.h b/lib/vtls/vtls.h
index 5ad64fc..0d9e74a 100644
--- a/lib/vtls/vtls.h
+++ b/lib/vtls/vtls.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -27,7 +27,6 @@
 
 struct connectdata;
 struct ssl_config_data;
-struct ssl_connect_data;
 struct ssl_primary_config;
 struct Curl_ssl_session;
 
@@ -53,6 +52,7 @@ struct Curl_ssl_session;
 /* Curl_multi SSL backend-specific data; declared differently by each SSL
    backend */
 struct multi_ssl_backend_data;
+struct Curl_cfilter;
 
 CURLsslset Curl_init_sslset_nolock(curl_sslbackend id, const char *name,
                                    const curl_ssl_backend ***avail);
@@ -68,8 +68,53 @@ CURLsslset Curl_init_sslset_nolock(curl_sslbackend id, const char *name,
 /* see https://www.iana.org/assignments/tls-extensiontype-values/ */
 #define ALPN_HTTP_1_1_LENGTH 8
 #define ALPN_HTTP_1_1 "http/1.1"
+#define ALPN_HTTP_1_0_LENGTH 8
+#define ALPN_HTTP_1_0 "http/1.0"
 #define ALPN_H2_LENGTH 2
 #define ALPN_H2 "h2"
+#define ALPN_H3_LENGTH 2
+#define ALPN_H3 "h3"
+
+/* conservative sizes on the ALPN entries and count we are handling,
+ * we can increase these if we ever feel the need or have to accommodate
+ * ALPN strings from the "outside". */
+#define ALPN_NAME_MAX     10
+#define ALPN_ENTRIES_MAX  3
+#define ALPN_PROTO_BUF_MAX   (ALPN_ENTRIES_MAX * (ALPN_NAME_MAX + 1))
+
+struct alpn_spec {
+  const char entries[ALPN_ENTRIES_MAX][ALPN_NAME_MAX];
+  size_t count; /* number of entries */
+};
+
+struct alpn_proto_buf {
+  unsigned char data[ALPN_PROTO_BUF_MAX];
+  int len;
+};
+
+CURLcode Curl_alpn_to_proto_buf(struct alpn_proto_buf *buf,
+                                const struct alpn_spec *spec);
+CURLcode Curl_alpn_to_proto_str(struct alpn_proto_buf *buf,
+                                const struct alpn_spec *spec);
+
+CURLcode Curl_alpn_set_negotiated(struct Curl_cfilter *cf,
+                                  struct Curl_easy *data,
+                                  const unsigned char *proto,
+                                  size_t proto_len);
+
+/**
+ * Get the ALPN specification to use for talking to remote host.
+ * May return NULL if ALPN is disabled on the connection.
+ */
+const struct alpn_spec *
+Curl_alpn_get_spec(struct Curl_easy *data, struct connectdata *conn);
+
+/**
+ * Get the ALPN specification to use for talking to the proxy.
+ * May return NULL if ALPN is disabled on the connection.
+ */
+const struct alpn_spec *
+Curl_alpn_get_proxy_spec(struct Curl_easy *data, struct connectdata *conn);
 
 
 char *Curl_ssl_snihost(struct Curl_easy *data, const char *host, size_t *olen);
@@ -95,7 +140,6 @@ struct curl_slist *Curl_ssl_engines_list(struct Curl_easy *data);
 /* init the SSL session ID cache */
 CURLcode Curl_ssl_initsessions(struct Curl_easy *, size_t);
 void Curl_ssl_version(char *buffer, size_t size);
-int Curl_ssl_check_cxn(struct Curl_easy *data, struct connectdata *conn);
 
 /* Certificate information list handling. */
 
@@ -156,6 +200,9 @@ CURLcode Curl_ssl_cfilter_add(struct Curl_easy *data,
                               struct connectdata *conn,
                               int sockindex);
 
+CURLcode Curl_cf_ssl_insert_after(struct Curl_cfilter *cf_at,
+                                  struct Curl_easy *data);
+
 CURLcode Curl_ssl_cfilter_remove(struct Curl_easy *data,
                                  int sockindex);
 
@@ -163,6 +210,8 @@ CURLcode Curl_ssl_cfilter_remove(struct Curl_easy *data,
 CURLcode Curl_ssl_cfilter_proxy_add(struct Curl_easy *data,
                                     struct connectdata *conn,
                                     int sockindex);
+CURLcode Curl_cf_ssl_proxy_insert_after(struct Curl_cfilter *cf_at,
+                                        struct Curl_easy *data);
 #endif /* !CURL_DISABLE_PROXY */
 
 /**
@@ -208,6 +257,9 @@ bool Curl_ssl_supports(struct Curl_easy *data, int ssl_option);
 void *Curl_ssl_get_internals(struct Curl_easy *data, int sockindex,
                              CURLINFO info, int n);
 
+extern struct Curl_cftype Curl_cft_ssl;
+extern struct Curl_cftype Curl_cft_ssl_proxy;
+
 #else /* if not USE_SSL */
 
 /* When SSL support is not present, just define away these function calls */
@@ -218,7 +270,6 @@ void *Curl_ssl_get_internals(struct Curl_easy *data, int sockindex,
 #define Curl_ssl_set_engine_default(x) CURLE_NOT_BUILT_IN
 #define Curl_ssl_engines_list(x) NULL
 #define Curl_ssl_initsessions(x,y) CURLE_OK
-#define Curl_ssl_check_cxn(d,x) 0
 #define Curl_ssl_free_certinfo(x) Curl_nop_stmt
 #define Curl_ssl_kill_session(x) Curl_nop_stmt
 #define Curl_ssl_random(x,y,z) ((void)x, CURLE_NOT_BUILT_IN)
diff --git a/lib/vtls/vtls_int.h b/lib/vtls/vtls_int.h
index 6710a2b..a20ca7d 100644
--- a/lib/vtls/vtls_int.h
+++ b/lib/vtls/vtls_int.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -33,18 +33,20 @@
 struct ssl_connect_data {
   ssl_connection_state state;
   ssl_connect_state connecting_state;
-  const char *hostname;             /* hostnaem for verification */
-  const char *dispname;             /* display version of hostname */
+  char *hostname;                   /* hostname for verification */
+  char *dispname;                   /* display version of hostname */
   int port;                         /* remote port at origin */
+  const struct alpn_spec *alpn;     /* ALPN to use or NULL for none */
   struct ssl_backend_data *backend; /* vtls backend specific props */
-  struct Curl_easy *call_data;      /* data handle used in current call,
-                                     * same as parameter passed, but available
-                                     * here for backend internal callbacks
-                                     * that need it. NULLed after at the
-                                     * end of each vtls filter invcocation. */
+  struct cf_call_data call_data;    /* data handle used in current call */
+  struct curltime handshake_done;   /* time when handshake finished */
 };
 
 
+#define CF_CTX_CALL_DATA(cf)  \
+  ((struct ssl_connect_data *)(cf)->ctx)->call_data
+
+
 /* Definitions for SSL Implementations */
 
 struct Curl_ssl {
diff --git a/lib/vtls/wolfssl.c b/lib/vtls/wolfssl.c
index 7cc4774..2e57899 100644
--- a/lib/vtls/wolfssl.c
+++ b/lib/vtls/wolfssl.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -218,29 +218,9 @@ static const struct group_name_map gnm[] = {
   { WOLFSSL_KYBER_LEVEL1, "KYBER_LEVEL1" },
   { WOLFSSL_KYBER_LEVEL3, "KYBER_LEVEL3" },
   { WOLFSSL_KYBER_LEVEL5, "KYBER_LEVEL5" },
-  { WOLFSSL_NTRU_HPS_LEVEL1, "NTRU_HPS_LEVEL1" },
-  { WOLFSSL_NTRU_HPS_LEVEL3, "NTRU_HPS_LEVEL3" },
-  { WOLFSSL_NTRU_HPS_LEVEL5, "NTRU_HPS_LEVEL5" },
-  { WOLFSSL_NTRU_HRSS_LEVEL3, "NTRU_HRSS_LEVEL3" },
-  { WOLFSSL_SABER_LEVEL1, "SABER_LEVEL1" },
-  { WOLFSSL_SABER_LEVEL3, "SABER_LEVEL3" },
-  { WOLFSSL_SABER_LEVEL5, "SABER_LEVEL5" },
-  { WOLFSSL_KYBER_90S_LEVEL1, "KYBER_90S_LEVEL1" },
-  { WOLFSSL_KYBER_90S_LEVEL3, "KYBER_90S_LEVEL3" },
-  { WOLFSSL_KYBER_90S_LEVEL5, "KYBER_90S_LEVEL5" },
-  { WOLFSSL_P256_NTRU_HPS_LEVEL1, "P256_NTRU_HPS_LEVEL1" },
-  { WOLFSSL_P384_NTRU_HPS_LEVEL3, "P384_NTRU_HPS_LEVEL3" },
-  { WOLFSSL_P521_NTRU_HPS_LEVEL5, "P521_NTRU_HPS_LEVEL5" },
-  { WOLFSSL_P384_NTRU_HRSS_LEVEL3, "P384_NTRU_HRSS_LEVEL3" },
-  { WOLFSSL_P256_SABER_LEVEL1, "P256_SABER_LEVEL1" },
-  { WOLFSSL_P384_SABER_LEVEL3, "P384_SABER_LEVEL3" },
-  { WOLFSSL_P521_SABER_LEVEL5, "P521_SABER_LEVEL5" },
   { WOLFSSL_P256_KYBER_LEVEL1, "P256_KYBER_LEVEL1" },
   { WOLFSSL_P384_KYBER_LEVEL3, "P384_KYBER_LEVEL3" },
   { WOLFSSL_P521_KYBER_LEVEL5, "P521_KYBER_LEVEL5" },
-  { WOLFSSL_P256_KYBER_90S_LEVEL1, "P256_KYBER_90S_LEVEL1" },
-  { WOLFSSL_P384_KYBER_90S_LEVEL3, "P384_KYBER_90S_LEVEL3" },
-  { WOLFSSL_P521_KYBER_90S_LEVEL5, "P521_KYBER_90S_LEVEL5" },
   { 0, NULL }
 };
 #endif
@@ -299,8 +279,7 @@ static long bio_cf_ctrl(WOLFSSL_BIO *bio, int cmd, long num, void *ptr)
 static int bio_cf_out_write(WOLFSSL_BIO *bio, const char *buf, int blen)
 {
   struct Curl_cfilter *cf = wolfSSL_BIO_get_data(bio);
-  struct ssl_connect_data *connssl = cf->ctx;
-  struct Curl_easy *data = connssl->call_data;
+  struct Curl_easy *data = CF_DATA_CURRENT(cf);
   ssize_t nwritten;
   CURLcode result = CURLE_OK;
 
@@ -315,8 +294,7 @@ static int bio_cf_out_write(WOLFSSL_BIO *bio, const char *buf, int blen)
 static int bio_cf_in_read(WOLFSSL_BIO *bio, char *buf, int blen)
 {
   struct Curl_cfilter *cf = wolfSSL_BIO_get_data(bio);
-  struct ssl_connect_data *connssl = cf->ctx;
-  struct Curl_easy *data = connssl->call_data;
+  struct Curl_easy *data = CF_DATA_CURRENT(cf);
   ssize_t nread;
   CURLcode result = CURLE_OK;
 
@@ -633,29 +611,18 @@ wolfssl_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data)
 #endif
 
 #ifdef HAVE_ALPN
-  if(cf->conn->bits.tls_enable_alpn) {
-    char protocols[128];
-    *protocols = '\0';
-
-    /* wolfSSL's ALPN protocol name list format is a comma separated string of
-       protocols in descending order of preference, eg: "h2,http/1.1" */
-
-#ifdef USE_HTTP2
-    if(data->state.httpwant >= CURL_HTTP_VERSION_2) {
-      strcpy(protocols + strlen(protocols), ALPN_H2 ",");
-      infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_H2);
-    }
-#endif
-
-    strcpy(protocols + strlen(protocols), ALPN_HTTP_1_1);
-    infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_1);
+  if(connssl->alpn) {
+    struct alpn_proto_buf proto;
+    CURLcode result;
 
-    if(wolfSSL_UseALPN(backend->handle, protocols,
-                       (unsigned)strlen(protocols),
+    result = Curl_alpn_to_proto_str(&proto, connssl->alpn);
+    if(result ||
+       wolfSSL_UseALPN(backend->handle, (char *)proto.data, proto.len,
                        WOLFSSL_ALPN_CONTINUE_ON_MISMATCH) != SSL_SUCCESS) {
       failf(data, "SSL: failed setting ALPN protocols");
       return CURLE_SSL_CONNECT_ERROR;
     }
+    infof(data, VTLS_INFOF_ALPN_OFFER_1STR, proto.data);
   }
 #endif /* HAVE_ALPN */
 
@@ -707,7 +674,7 @@ wolfssl_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data)
   }
 #else /* USE_BIO_CHAIN */
   /* pass the raw socket into the SSL layer */
-  if(!SSL_set_fd(backend->handle, (int)cf->conn->sock[cf->sockindex])) {
+  if(!SSL_set_fd(backend->handle, (int)Curl_conn_cf_get_socket(cf, data))) {
     failf(data, "SSL: SSL_set_fd failed");
     return CURLE_SSL_CONNECT_ERROR;
   }
@@ -883,25 +850,11 @@ wolfssl_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data)
     rc = wolfSSL_ALPN_GetProtocol(backend->handle, &protocol, &protocol_len);
 
     if(rc == SSL_SUCCESS) {
-      infof(data, VTLS_INFOF_ALPN_ACCEPTED_LEN_1STR, protocol_len, protocol);
-
-      if(protocol_len == ALPN_HTTP_1_1_LENGTH &&
-         !memcmp(protocol, ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH))
-        cf->conn->alpn = CURL_HTTP_VERSION_1_1;
-#ifdef USE_HTTP2
-      else if(data->state.httpwant >= CURL_HTTP_VERSION_2 &&
-              protocol_len == ALPN_H2_LENGTH &&
-              !memcmp(protocol, ALPN_H2, ALPN_H2_LENGTH))
-        cf->conn->alpn = CURL_HTTP_VERSION_2;
-#endif
-      else
-        infof(data, "ALPN, unrecognized protocol %.*s", protocol_len,
-              protocol);
-      Curl_multiuse_state(data, cf->conn->alpn == CURL_HTTP_VERSION_2 ?
-                          BUNDLE_MULTIPLEX : BUNDLE_NO_MULTIUSE);
+      Curl_alpn_set_negotiated(cf, data, (const unsigned char *)protocol,
+                               protocol_len);
     }
     else if(rc == SSL_ALPN_NOT_FOUND)
-      infof(data, VTLS_INFOF_NO_ALPN);
+      Curl_alpn_set_negotiated(cf, data, NULL, 0);
     else {
       failf(data, "ALPN, failure getting protocol, error %d", rc);
       return CURLE_SSL_CONNECT_ERROR;
@@ -1166,7 +1119,7 @@ wolfssl_connect_common(struct Curl_cfilter *cf,
 {
   CURLcode result;
   struct ssl_connect_data *connssl = cf->ctx;
-  curl_socket_t sockfd = cf->conn->sock[cf->sockindex];
+  curl_socket_t sockfd = Curl_conn_cf_get_socket(cf, data);
   int what;
 
   /* check if the connection has already been established */
diff --git a/lib/vtls/wolfssl.h b/lib/vtls/wolfssl.h
index b2e7c3f..a5ed848 100644
--- a/lib/vtls/wolfssl.h
+++ b/lib/vtls/wolfssl.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/vtls/x509asn1.c b/lib/vtls/x509asn1.c
index 4c1c9a8..39e4fb3 100644
--- a/lib/vtls/x509asn1.c
+++ b/lib/vtls/x509asn1.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -48,6 +48,7 @@
 #include "curl_ctype.h"
 #include "hostcheck.h"
 #include "vtls/vtls.h"
+#include "vtls/vtls_int.h"
 #include "sendf.h"
 #include "inet_pton.h"
 #include "curl_base64.h"
@@ -1313,7 +1314,8 @@ CURLcode Curl_verifyhost(struct Curl_cfilter *cf,
 
   /* Get the server IP address. */
 #ifdef ENABLE_IPV6
-  if(conn->bits.ipv6_ip && Curl_inet_pton(AF_INET6, connssl->hostname, &addr))
+  if(cf->conn->bits.ipv6_ip &&
+     Curl_inet_pton(AF_INET6, connssl->hostname, &addr))
     addrlen = sizeof(struct in6_addr);
   else
 #endif
@@ -1348,19 +1350,18 @@ CURLcode Curl_verifyhost(struct Curl_cfilter *cf,
           break;
         switch(name.tag) {
         case 2: /* DNS name. */
-          matched = 0;
           len = utf8asn1str(&dnsname, CURL_ASN1_IA5_STRING,
                             name.beg, name.end);
-          if(len > 0) {
-            if(size_t)len == strlen(dnsname)
-              matched = Curl_cert_hostcheck(dnsname, (size_t)len,
-                                            connssl->hostname, hostlen);
-            free(dnsname);
-          }
+          if(len > 0 && (size_t)len == strlen(dnsname))
+            matched = Curl_cert_hostcheck(dnsname, (size_t)len,
+                                          connssl->hostname, hostlen);
+          else
+            matched = 0;
+          free(dnsname);
           break;
 
         case 7: /* IP address. */
-          matched = (name.end - name.beg) == addrlen &&
+          matched = (size_t)(name.end - name.beg) == addrlen &&
             !memcmp(&addr, name.beg, addrlen);
           break;
         }
@@ -1406,8 +1407,10 @@ CURLcode Curl_verifyhost(struct Curl_cfilter *cf,
     failf(data, "SSL: unable to obtain common name from peer certificate");
   else {
     len = utf8asn1str(&dnsname, elem.tag, elem.beg, elem.end);
-    if(len < 0)
+    if(len < 0) {
+      free(dnsname);
       return CURLE_OUT_OF_MEMORY;
+    }
     if(strlen(dnsname) != (size_t) len)         /* Nul byte in string ? */
       failf(data, "SSL: illegal cert name field");
     else if(Curl_cert_hostcheck((const char *) dnsname,
diff --git a/lib/vtls/x509asn1.h b/lib/vtls/x509asn1.h
index eb8e959..5496de4 100644
--- a/lib/vtls/x509asn1.h
+++ b/lib/vtls/x509asn1.h
@@ -8,7 +8,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/warnless.c b/lib/warnless.c
index b00d7a5..10c91fb 100644
--- a/lib/warnless.c
+++ b/lib/warnless.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/warnless.h b/lib/warnless.h
index 4367099..99b2433 100644
--- a/lib/warnless.h
+++ b/lib/warnless.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/wildcard.c b/lib/wildcard.c
index a3e24b6..3b81c7a 100644
--- a/lib/wildcard.c
+++ b/lib/wildcard.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/wildcard.h b/lib/wildcard.h
index 21e933b..9dccab0 100644
--- a/lib/wildcard.h
+++ b/lib/wildcard.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 2010 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
diff --git a/lib/ws.c b/lib/ws.c
index c1b2622..0fc5e56 100644
--- a/lib/ws.c
+++ b/lib/ws.c
@@ -5,7 +5,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -115,12 +115,18 @@ CURLcode Curl_ws_request(struct Curl_easy *data, REQTYPE *req)
   return result;
 }
 
-CURLcode Curl_ws_accept(struct Curl_easy *data)
+/*
+ * 'nread' is number of bytes of websocket data already in the buffer at
+ * 'mem'.
+ */
+CURLcode Curl_ws_accept(struct Curl_easy *data,
+                        const char *mem, size_t nread)
 {
   struct SingleRequest *k = &data->req;
   struct HTTP *ws = data->req.p.http;
   struct connectdata *conn = data->conn;
   struct websocket *wsp = &data->req.p.http->ws;
+  struct ws_conn *wsc = &conn->proto.ws;
   CURLcode result;
 
   /* Verify the Sec-WebSocket-Accept response.
@@ -149,13 +155,21 @@ CURLcode Curl_ws_accept(struct Curl_easy *data)
 
   infof(data, "Received 101, switch to WebSocket; mask %02x%02x%02x%02x",
         ws->ws.mask[0], ws->ws.mask[1], ws->ws.mask[2], ws->ws.mask[3]);
+  Curl_dyn_init(&wsc->early, data->set.buffer_size);
+  if(nread) {
+    result = Curl_dyn_addn(&wsc->early, mem, nread);
+    if(result)
+      return result;
+    infof(data, "%zu bytes websocket payload", nread);
+    wsp->stillb = Curl_dyn_ptr(&wsc->early);
+    wsp->stillblen = Curl_dyn_len(&wsc->early);
+  }
   k->upgr101 = UPGR101_RECEIVED;
 
   if(data->set.connect_only)
     /* switch off non-blocking sockets */
     (void)curlx_nonblock(conn->sock[FIRSTSOCKET], FALSE);
 
-  wsp->oleft = 0;
   return result;
 }
 
@@ -183,12 +197,12 @@ static void ws_decode_shift(struct Curl_easy *data, size_t spent)
 
 /* ws_decode() decodes a binary frame into structured WebSocket data,
 
-   wpkt - the incoming raw data. If NULL, work on the already buffered data.
-   ilen - the size of the provided data, perhaps too little, perhaps too much
-   out - stored pointed to extracted data
+   data - the transfer
+   inbuf - incoming raw data. If NULL, work on the already buffered data.
+   inlen - size of the provided data, perhaps too little, perhaps too much
+   headlen - stored length of the frame header
    olen - stored length of the extracted data
    oleft - number of unread bytes pending to that belongs to this frame
-   more - if there is more data in there
    flags - stored bitmask about the frame
 
    Returns CURLE_AGAIN if there is only a partial frame in the buffer. Then it
@@ -246,6 +260,9 @@ static CURLcode ws_decode(struct Curl_easy *data,
     infof(data, "WS: received OPCODE PONG");
     *flags |= CURLWS_PONG;
     break;
+  default:
+    failf(data, "WS: unknown opcode: %x", opcode);
+    return CURLE_RECV_ERROR;
   }
 
   if(inbuf[1] & WSBIT_MASK) {
@@ -296,7 +313,7 @@ static CURLcode ws_decode(struct Curl_easy *data,
     *oleft = 0;             /* bytes yet to come (for this frame) */
   }
 
-  infof(data, "WS: received %zu bytes payload (%zu left, buflen was %zu)",
+  infof(data, "WS: received %Ou bytes payload (%Ou left, buflen was %zu)",
         payloadsize, *oleft, inlen);
   return CURLE_OK;
 }
@@ -339,9 +356,6 @@ size_t Curl_ws_writecb(char *buffer, size_t size /* 1 */,
 
         result = ws_decode(data, wsbuf, buflen,
                            &headlen, &write_len, &fb_left, &recvflags);
-        consumed += headlen;
-        wsbuf += headlen;
-        buflen -= headlen;
         if(result == CURLE_AGAIN)
           /* insufficient amount of data, keep it for later.
            * we pretend to have written all since we have a copy */
@@ -350,6 +364,10 @@ size_t Curl_ws_writecb(char *buffer, size_t size /* 1 */,
           infof(data, "WS: decode error %d", (int)result);
           return nitems - 1;
         }
+        consumed += headlen;
+        wsbuf += headlen;
+        buflen -= headlen;
+
         /* New frame. store details about the frame to be reachable with
            curl_ws_meta() from within the write callback */
         ws->ws.frame.age = 0;
@@ -392,7 +410,6 @@ size_t Curl_ws_writecb(char *buffer, size_t size /* 1 */,
   return nitems;
 }
 
-
 CURL_EXTERN CURLcode curl_ws_recv(struct Curl_easy *data, void *buffer,
                                   size_t buflen, size_t *nread,
                                   struct curl_ws_frame **metap)
@@ -409,7 +426,7 @@ CURL_EXTERN CURLcode curl_ws_recv(struct Curl_easy *data, void *buffer,
     return result;
 
   while(!done) {
-    size_t write_len;
+    size_t datalen;
     unsigned int recvflags;
 
     if(!wsp->stillblen) {
@@ -419,26 +436,32 @@ CURL_EXTERN CURLcode curl_ws_recv(struct Curl_easy *data, void *buffer,
                               data->set.buffer_size, &n);
       if(result)
         return result;
-      if(!n)
+      if(!n) {
         /* connection closed */
+        infof(data, "connection expectedly closed?");
         return CURLE_GOT_NOTHING;
+      }
       wsp->stillb = data->state.buffer;
       wsp->stillblen = n;
     }
 
-    infof(data, "WS: got %u websocket bytes to decode",
-          (int)wsp->stillblen);
+    infof(data, "WS: %u bytes left to decode", (int)wsp->stillblen);
     if(!wsp->frame.bytesleft) {
       size_t headlen;
       curl_off_t oleft;
       /* detect new frame */
       result = ws_decode(data, (unsigned char *)wsp->stillb, wsp->stillblen,
-                         &headlen, &write_len, &oleft, &recvflags);
+                         &headlen, &datalen, &oleft, &recvflags);
       if(result == CURLE_AGAIN)
         /* a packet fragment only */
         break;
       else if(result)
         return result;
+      if(datalen > buflen) {
+        size_t diff = datalen - buflen;
+        datalen = buflen;
+        oleft += diff;
+      }
       wsp->stillb += headlen;
       wsp->stillblen -= headlen;
       wsp->frame.offset = 0;
@@ -447,40 +470,45 @@ CURL_EXTERN CURLcode curl_ws_recv(struct Curl_easy *data, void *buffer,
     }
     else {
       /* existing frame, remaining payload handling */
-      write_len = wsp->frame.bytesleft;
-      if(write_len > wsp->stillblen)
-        write_len = wsp->stillblen;
+      datalen = wsp->frame.bytesleft;
+      if(datalen > wsp->stillblen)
+        datalen = wsp->stillblen;
+      if(datalen > buflen)
+        datalen = buflen;
+
+      wsp->frame.offset += wsp->frame.len;
+      wsp->frame.bytesleft -= datalen;
     }
+    wsp->frame.len = datalen;
 
     /* auto-respond to PINGs */
     if((wsp->frame.flags & CURLWS_PING) && !wsp->frame.bytesleft) {
-      infof(data, "WS: auto-respond to PING with a PONG");
+      size_t nsent = 0;
+      infof(data, "WS: auto-respond to PING with a PONG, %zu bytes payload",
+            datalen);
       /* send back the exact same content as a PONG */
-      result = curl_ws_send(data, wsp->stillb, write_len,
-                            &write_len, 0, CURLWS_PONG);
+      result = curl_ws_send(data, wsp->stillb, datalen, &nsent, 0,
+                            CURLWS_PONG);
       if(result)
         return result;
+      infof(data, "WS: bytesleft %zu datalen %zu",
+            wsp->frame.bytesleft, datalen);
+      /* we handled the data part of the PING, advance over that */
+      wsp->stillb += nsent;
+      wsp->stillblen -= nsent;
     }
-    else if(write_len || !wsp->frame.bytesleft) {
-      if(write_len > buflen)
-        write_len = buflen;
+    else if(datalen) {
       /* copy the payload to the user buffer */
-      memcpy(buffer, wsp->stillb, write_len);
-      *nread = write_len;
+      memcpy(buffer, wsp->stillb, datalen);
+      *nread = datalen;
       done = TRUE;
-    }
-    if(write_len) {
-      /* update buffer and frame info */
-      wsp->frame.offset += write_len;
-      DEBUGASSERT(wsp->frame.bytesleft >= (curl_off_t)write_len);
-      if(wsp->frame.bytesleft)
-        wsp->frame.bytesleft -= write_len;
-      DEBUGASSERT(write_len <= wsp->stillblen);
-      wsp->stillblen -= write_len;
+
+      wsp->stillblen -= datalen;
       if(wsp->stillblen)
-        wsp->stillb += write_len;
-      else
+        wsp->stillb += datalen;
+      else {
         wsp->stillb = NULL;
+      }
     }
   }
   *metap = &wsp->frame;
@@ -555,17 +583,27 @@ static size_t ws_packethead(struct Curl_easy *data,
   }
 
   if(!(flags & CURLWS_CONT)) {
-    /* if not marked as continuing, assume this is the final fragment */
-    firstbyte |= WSBIT_FIN | opcode;
+    if(!ws->ws.contfragment)
+      /* not marked as continuing, this is the final fragment */
+      firstbyte |= WSBIT_FIN | opcode;
+    else
+      /* marked as continuing, this is the final fragment; set CONT
+         opcode and FIN bit */
+      firstbyte |= WSBIT_FIN | WSBIT_OPCODE_CONT;
+
     ws->ws.contfragment = FALSE;
+    infof(data, "WS: set FIN");
   }
   else if(ws->ws.contfragment) {
     /* the previous fragment was not a final one and this isn't either, keep a
        CONT opcode and no FIN bit */
     firstbyte |= WSBIT_OPCODE_CONT;
+    infof(data, "WS: keep CONT, no FIN");
   }
   else {
+    firstbyte = opcode;
     ws->ws.contfragment = TRUE;
+    infof(data, "WS: set CONT, no FIN");
   }
   out[0] = firstbyte;
   if(len > 65535) {
@@ -686,8 +724,14 @@ CURL_EXTERN CURLcode curl_ws_send(struct Curl_easy *data, const void *buffer,
 
   infof(data, "WS: wanted to send %zu bytes, sent %zu bytes",
         headlen + buflen, written);
-  *sent = written;
 
+  if(!result) {
+    /* the *sent number only counts "payload", excluding the header */
+    if((size_t)written > headlen)
+      *sent = written - headlen;
+    else
+      *sent = 0;
+  }
   return result;
 }
 
@@ -698,6 +742,20 @@ void Curl_ws_done(struct Curl_easy *data)
   Curl_dyn_free(&wsp->buf);
 }
 
+CURLcode Curl_ws_disconnect(struct Curl_easy *data,
+                            struct connectdata *conn,
+                            bool dead_connection)
+{
+  struct ws_conn *wsc = &conn->proto.ws;
+  (void)data;
+  (void)dead_connection;
+  Curl_dyn_free(&wsc->early);
+
+  /* make sure this is non-blocking to avoid getting stuck in shutdown */
+  (void)curlx_nonblock(conn->sock[FIRSTSOCKET], TRUE);
+  return CURLE_OK;
+}
+
 CURL_EXTERN struct curl_ws_frame *curl_ws_meta(struct Curl_easy *data)
 {
   /* we only return something for websocket, called from within the callback
diff --git a/lib/ws.h b/lib/ws.h
index 2f3ed2d..176dda4 100644
--- a/lib/ws.h
+++ b/lib/ws.h
@@ -7,7 +7,7 @@
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * 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
@@ -46,24 +46,28 @@ struct websocket {
   size_t usedbuf; /* number of leading bytes in 'buf' the most recent complete
                      websocket frame uses */
   struct curl_ws_frame frame; /* the struct used for frame state */
-  curl_off_t oleft; /* outstanding number of payload bytes left from the
-                       server */
   size_t stillblen; /* number of bytes left in the buffer to deliver in
                          the next curl_ws_recv() call */
-  char *stillb; /* the stillblen pending bytes are here */
+  const char *stillb; /* the stillblen pending bytes are here */
   curl_off_t sleft; /* outstanding number of payload bytes left to send */
   unsigned int xori; /* xor index */
 };
 
-CURLcode Curl_ws_request(struct Curl_easy *data, REQTYPE *req);
-CURLcode Curl_ws_accept(struct Curl_easy *data);
+struct ws_conn {
+  struct dynbuf early; /* data already read when switching to ws */
+};
 
+CURLcode Curl_ws_request(struct Curl_easy *data, REQTYPE *req);
+CURLcode Curl_ws_accept(struct Curl_easy *data, const char *mem, size_t len);
 size_t Curl_ws_writecb(char *buffer, size_t size, size_t nitems, void *userp);
 void Curl_ws_done(struct Curl_easy *data);
-
+CURLcode Curl_ws_disconnect(struct Curl_easy *data,
+                            struct connectdata *conn,
+                            bool dead_connection);
 #else
 #define Curl_ws_request(x,y) CURLE_OK
 #define Curl_ws_done(x) Curl_nop_stmt
+#define Curl_ws_free(x) Curl_nop_stmt
 #endif
 
 #endif /* HEADER_CURL_WS_H */
-- 
cgit v0.12