← Commit list

671158242d

Stefan Eissing
Stefan Eissing
2023-02-02
SUCCESS Duration: 84s

Subject

connections: introduce http/3 happy eyeballs

Binary

Download Binary (785 KB)

Architecture & Compiler Flags

Architecture: x86_64-linux-gnu Compiler: gcc (Ubuntu 11.4.0-1ubuntu1~22.04.3) 11.4.0

—

Build Log

$ git checkout -f 671158242db32032a41e8da89da33d773b481ef8
Previous HEAD position was 63c53ea627 urlapi: skip the extra dedotdot alloc if no dot in path
HEAD is now at 671158242d connections: introduce http/3 happy eyeballs

$ git clean -fdx
Removing Makefile
Removing Makefile.in
Removing aclocal.m4
Removing autom4te.cache/
Removing compile
Removing config.guess
Removing config.log
Removing config.status
Removing config.sub
Removing configure
Removing curl-config
Removing depcomp
Removing docs/Makefile
Removing docs/Makefile.in
Removing docs/cmdline-opts/Makefile
Removing docs/cmdline-opts/Makefile.in
Removing docs/curl.1
Removing docs/examples/.deps/
Removing docs/examples/Makefile
Removing docs/examples/Makefile.in
Removing docs/libcurl/Makefile
Removing docs/libcurl/Makefile.in
Removing docs/libcurl/opts/Makefile
Removing docs/libcurl/opts/Makefile.in
Removing include/Makefile
Removing include/Makefile.in
Removing include/curl/Makefile
Removing include/curl/Makefile.in
Removing install-sh
Removing lib/.deps/
Removing lib/.dirstamp
Removing lib/.libs/
Removing lib/Makefile
Removing lib/Makefile.in
Removing lib/curl_config.h
Removing lib/curl_config.h.in
Removing lib/curl_multibyte.o
Removing lib/dynbuf.o
Removing lib/libcurl.la
Removing lib/libcurl.plist
Removing lib/libcurl.vers
Removing lib/libcurl_la-altsvc.lo
Removing lib/libcurl_la-altsvc.o
Removing lib/libcurl_la-amigaos.lo
Removing lib/libcurl_la-amigaos.o
Removing lib/libcurl_la-asyn-ares.lo
Removing lib/libcurl_la-asyn-ares.o
Removing lib/libcurl_la-asyn-thread.lo
Removing lib/libcurl_la-asyn-thread.o
Removing lib/libcurl_la-base64.lo
Removing lib/libcurl_la-base64.o
Removing lib/libcurl_la-bufref.lo
Removing lib/libcurl_la-bufref.o
Removing lib/libcurl_la-c-hyper.lo
Removing lib/libcurl_la-c-hyper.o
Removing lib/libcurl_la-cf-http.lo
Removing lib/libcurl_la-cf-http.o
Removing lib/libcurl_la-cf-socket.lo
Removing lib/libcurl_la-cf-socket.o
Removing lib/libcurl_la-cfilters.lo
Removing lib/libcurl_la-cfilters.o
Removing lib/libcurl_la-conncache.lo
Removing lib/libcurl_la-conncache.o
Removing lib/libcurl_la-connect.lo
Removing lib/libcurl_la-connect.o
Removing lib/libcurl_la-content_encoding.lo
Removing lib/libcurl_la-content_encoding.o
Removing lib/libcurl_la-cookie.lo
Removing lib/libcurl_la-cookie.o
Removing lib/libcurl_la-curl_addrinfo.lo
Removing lib/libcurl_la-curl_addrinfo.o
Removing lib/libcurl_la-curl_des.lo
Removing lib/libcurl_la-curl_des.o
Removing lib/libcurl_la-curl_endian.lo
Removing lib/libcurl_la-curl_endian.o
Removing lib/libcurl_la-curl_fnmatch.lo
Removing lib/libcurl_la-curl_fnmatch.o
Removing lib/libcurl_la-curl_get_line.lo
Removing lib/libcurl_la-curl_get_line.o
Removing lib/libcurl_la-curl_gethostname.lo
Removing lib/libcurl_la-curl_gethostname.o
Removing lib/libcurl_la-curl_gssapi.lo
Removing lib/libcurl_la-curl_gssapi.o
Removing lib/libcurl_la-curl_log.lo
Removing lib/libcurl_la-curl_log.o
Removing lib/libcurl_la-curl_memrchr.lo
Removing lib/libcurl_la-curl_memrchr.o
Removing lib/libcurl_la-curl_multibyte.lo
Removing lib/libcurl_la-curl_multibyte.o
Removing lib/libcurl_la-curl_ntlm_core.lo
Removing lib/libcurl_la-curl_ntlm_core.o
Removing lib/libcurl_la-curl_ntlm_wb.lo
Removing lib/libcurl_la-curl_ntlm_wb.o
Removing lib/libcurl_la-curl_path.lo
Removing lib/libcurl_la-curl_path.o
Removing lib/libcurl_la-curl_range.lo
Removing lib/libcurl_la-curl_range.o
Removing lib/libcurl_la-curl_rtmp.lo
Removing lib/libcurl_la-curl_rtmp.o
Removing lib/libcurl_la-curl_sasl.lo
Removing lib/libcurl_la-curl_sasl.o
Removing lib/libcurl_la-curl_sspi.lo
Removing lib/libcurl_la-curl_sspi.o
Removing lib/libcurl_la-curl_threads.lo
Removing lib/libcurl_la-curl_threads.o
Removing lib/libcurl_la-dict.lo
Removing lib/libcurl_la-dict.o
Removing lib/libcurl_la-doh.lo
Removing lib/libcurl_la-doh.o
Removing lib/libcurl_la-dynbuf.lo
Removing lib/libcurl_la-dynbuf.o
Removing lib/libcurl_la-easy.lo
Removing lib/libcurl_la-easy.o
Removing lib/libcurl_la-easygetopt.lo
Removing lib/libcurl_la-easygetopt.o
Removing lib/libcurl_la-easyoptions.lo
Removing lib/libcurl_la-easyoptions.o
Removing lib/libcurl_la-escape.lo
Removing lib/libcurl_la-escape.o
Removing lib/libcurl_la-file.lo
Removing lib/libcurl_la-file.o
Removing lib/libcurl_la-fileinfo.lo
Removing lib/libcurl_la-fileinfo.o
Removing lib/libcurl_la-fopen.lo
Removing lib/libcurl_la-fopen.o
Removing lib/libcurl_la-formdata.lo
Removing lib/libcurl_la-formdata.o
Removing lib/libcurl_la-ftp.lo
Removing lib/libcurl_la-ftp.o
Removing lib/libcurl_la-ftplistparser.lo
Removing lib/libcurl_la-ftplistparser.o
Removing lib/libcurl_la-getenv.lo
Removing lib/libcurl_la-getenv.o
Removing lib/libcurl_la-getinfo.lo
Removing lib/libcurl_la-getinfo.o
Removing lib/libcurl_la-gopher.lo
Removing lib/libcurl_la-gopher.o
Removing lib/libcurl_la-h2h3.lo
Removing lib/libcurl_la-h2h3.o
Removing lib/libcurl_la-hash.lo
Removing lib/libcurl_la-hash.o
Removing lib/libcurl_la-headers.lo
Removing lib/libcurl_la-headers.o
Removing lib/libcurl_la-hmac.lo
Removing lib/libcurl_la-hmac.o
Removing lib/libcurl_la-hostasyn.lo
Removing lib/libcurl_la-hostasyn.o
Removing lib/libcurl_la-hostip.lo
Removing lib/libcurl_la-hostip.o
Removing lib/libcurl_la-hostip4.lo
Removing lib/libcurl_la-hostip4.o
Removing lib/libcurl_la-hostip6.lo
Removing lib/libcurl_la-hostip6.o
Removing lib/libcurl_la-hostsyn.lo
Removing lib/libcurl_la-hostsyn.o
Removing lib/libcurl_la-hsts.lo
Removing lib/libcurl_la-hsts.o
Removing lib/libcurl_la-http.lo
Removing lib/libcurl_la-http.o
Removing lib/libcurl_la-http2.lo
Removing lib/libcurl_la-http2.o
Removing lib/libcurl_la-http_aws_sigv4.lo
Removing lib/libcurl_la-http_aws_sigv4.o
Removing lib/libcurl_la-http_chunks.lo
Removing lib/libcurl_la-http_chunks.o
Removing lib/libcurl_la-http_digest.lo
Removing lib/libcurl_la-http_digest.o
Removing lib/libcurl_la-http_negotiate.lo
Removing lib/libcurl_la-http_negotiate.o
Removing lib/libcurl_la-http_ntlm.lo
Removing lib/libcurl_la-http_ntlm.o
Removing lib/libcurl_la-http_proxy.lo
Removing lib/libcurl_la-http_proxy.o
Removing lib/libcurl_la-idn.lo
Removing lib/libcurl_la-idn.o
Removing lib/libcurl_la-if2ip.lo
Removing lib/libcurl_la-if2ip.o
Removing lib/libcurl_la-imap.lo
Removing lib/libcurl_la-imap.o
Removing lib/libcurl_la-inet_ntop.lo
Removing lib/libcurl_la-inet_ntop.o
Removing lib/libcurl_la-inet_pton.lo
Removing lib/libcurl_la-inet_pton.o
Removing lib/libcurl_la-krb5.lo
Removing lib/libcurl_la-krb5.o
Removing lib/libcurl_la-ldap.lo
Removing lib/libcurl_la-ldap.o
Removing lib/libcurl_la-llist.lo
Removing lib/libcurl_la-llist.o
Removing lib/libcurl_la-md4.lo
Removing lib/libcurl_la-md4.o
Removing lib/libcurl_la-md5.lo
Removing lib/libcurl_la-md5.o
Removing lib/libcurl_la-memdebug.lo
Removing lib/libcurl_la-memdebug.o
Removing lib/libcurl_la-mime.lo
Removing lib/libcurl_la-mime.o
Removing lib/libcurl_la-mprintf.lo
Removing lib/libcurl_la-mprintf.o
Removing lib/libcurl_la-mqtt.lo
Removing lib/libcurl_la-mqtt.o
Removing lib/libcurl_la-multi.lo
Removing lib/libcurl_la-multi.o
Removing lib/libcurl_la-netrc.lo
Removing lib/libcurl_la-netrc.o
Removing lib/libcurl_la-nonblock.lo
Removing lib/libcurl_la-nonblock.o
Removing lib/libcurl_la-noproxy.lo
Removing lib/libcurl_la-noproxy.o
Removing lib/libcurl_la-openldap.lo
Removing lib/libcurl_la-openldap.o
Removing lib/libcurl_la-parsedate.lo
Removing lib/libcurl_la-parsedate.o
Removing lib/libcurl_la-pingpong.lo
Removing lib/libcurl_la-pingpong.o
Removing lib/libcurl_la-pop3.lo
Removing lib/libcurl_la-pop3.o
Removing lib/libcurl_la-progress.lo
Removing lib/libcurl_la-progress.o
Removing lib/libcurl_la-psl.lo
Removing lib/libcurl_la-psl.o
Removing lib/libcurl_la-rand.lo
Removing lib/libcurl_la-rand.o
Removing lib/libcurl_la-rename.lo
Removing lib/libcurl_la-rename.o
Removing lib/libcurl_la-rtsp.lo
Removing lib/libcurl_la-rtsp.o
Removing lib/libcurl_la-select.lo
Removing lib/libcurl_la-select.o
Removing lib/libcurl_la-sendf.lo
Removing lib/libcurl_la-sendf.o
Removing lib/libcurl_la-setopt.lo
Removing lib/libcurl_la-setopt.o
Removing lib/libcurl_la-sha256.lo
Removing lib/libcurl_la-sha256.o
Removing lib/libcurl_la-share.lo
Removing lib/libcurl_la-share.o
Removing lib/libcurl_la-slist.lo
Removing lib/libcurl_la-slist.o
Removing lib/libcurl_la-smb.lo
Removing lib/libcurl_la-smb.o
Removing lib/libcurl_la-smtp.lo
Removing lib/libcurl_la-smtp.o
Removing lib/libcurl_la-socketpair.lo
Removing lib/libcurl_la-socketpair.o
Removing lib/libcurl_la-socks.lo
Removing lib/libcurl_la-socks.o
Removing lib/libcurl_la-socks_gssapi.lo
Removing lib/libcurl_la-socks_gssapi.o
Removing lib/libcurl_la-socks_sspi.lo
Removing lib/libcurl_la-socks_sspi.o
Removing lib/libcurl_la-speedcheck.lo
Removing lib/libcurl_la-speedcheck.o
Removing lib/libcurl_la-splay.lo
Removing lib/libcurl_la-splay.o
Removing lib/libcurl_la-strcase.lo
Removing lib/libcurl_la-strcase.o
Removing lib/libcurl_la-strdup.lo
Removing lib/libcurl_la-strdup.o
Removing lib/libcurl_la-strerror.lo
Removing lib/libcurl_la-strerror.o
Removing lib/libcurl_la-strtok.lo
Removing lib/libcurl_la-strtok.o
Removing lib/libcurl_la-strtoofft.lo
Removing lib/libcurl_la-strtoofft.o
Removing lib/libcurl_la-system_win32.lo
Removing lib/libcurl_la-system_win32.o
Removing lib/libcurl_la-telnet.lo
Removing lib/libcurl_la-telnet.o
Removing lib/libcurl_la-tftp.lo
Removing lib/libcurl_la-tftp.o
Removing lib/libcurl_la-timediff.lo
Removing lib/libcurl_la-timediff.o
Removing lib/libcurl_la-timeval.lo
Removing lib/libcurl_la-timeval.o
Removing lib/libcurl_la-transfer.lo
Removing lib/libcurl_la-transfer.o
Removing lib/libcurl_la-url.lo
Removing lib/libcurl_la-url.o
Removing lib/libcurl_la-urlapi.lo
Removing lib/libcurl_la-urlapi.o
Removing lib/libcurl_la-version.lo
Removing lib/libcurl_la-version.o
Removing lib/libcurl_la-version_win32.lo
Removing lib/libcurl_la-version_win32.o
Removing lib/libcurl_la-warnless.lo
Removing lib/libcurl_la-warnless.o
Removing lib/libcurl_la-wildcard.lo
Removing lib/libcurl_la-wildcard.o
Removing lib/libcurl_la-ws.lo
Removing lib/libcurl_la-ws.o
Removing lib/nonblock.o
Removing lib/stamp-h1
Removing lib/strtoofft.o
Removing lib/timediff.o
Removing lib/vauth/.deps/
Removing lib/vauth/.dirstamp
Removing lib/vauth/libcurl_la-cleartext.lo
Removing lib/vauth/libcurl_la-cleartext.o
Removing lib/vauth/libcurl_la-cram.lo
Removing lib/vauth/libcurl_la-cram.o
Removing lib/vauth/libcurl_la-digest.lo
Removing lib/vauth/libcurl_la-digest.o
Removing lib/vauth/libcurl_la-digest_sspi.lo
Removing lib/vauth/libcurl_la-digest_sspi.o
Removing lib/vauth/libcurl_la-gsasl.lo
Removing lib/vauth/libcurl_la-gsasl.o
Removing lib/vauth/libcurl_la-krb5_gssapi.lo
Removing lib/vauth/libcurl_la-krb5_gssapi.o
Removing lib/vauth/libcurl_la-krb5_sspi.lo
Removing lib/vauth/libcurl_la-krb5_sspi.o
Removing lib/vauth/libcurl_la-ntlm.lo
Removing lib/vauth/libcurl_la-ntlm.o
Removing lib/vauth/libcurl_la-ntlm_sspi.lo
Removing lib/vauth/libcurl_la-ntlm_sspi.o
Removing lib/vauth/libcurl_la-oauth2.lo
Removing lib/vauth/libcurl_la-oauth2.o
Removing lib/vauth/libcurl_la-spnego_gssapi.lo
Removing lib/vauth/libcurl_la-spnego_gssapi.o
Removing lib/vauth/libcurl_la-spnego_sspi.lo
Removing lib/vauth/libcurl_la-spnego_sspi.o
Removing lib/vauth/libcurl_la-vauth.lo
Removing lib/vauth/libcurl_la-vauth.o
Removing lib/version_win32.o
Removing lib/vquic/.deps/
Removing lib/vquic/.dirstamp
Removing lib/vquic/libcurl_la-curl_msh3.lo
Removing lib/vquic/libcurl_la-curl_msh3.o
Removing lib/vquic/libcurl_la-curl_ngtcp2.lo
Removing lib/vquic/libcurl_la-curl_ngtcp2.o
Removing lib/vquic/libcurl_la-curl_quiche.lo
Removing lib/vquic/libcurl_la-curl_quiche.o
Removing lib/vquic/libcurl_la-vquic.lo
Removing lib/vquic/libcurl_la-vquic.o
Removing lib/vssh/.deps/
Removing lib/vssh/.dirstamp
Removing lib/vssh/libcurl_la-libssh.lo
Removing lib/vssh/libcurl_la-libssh.o
Removing lib/vssh/libcurl_la-libssh2.lo
Removing lib/vssh/libcurl_la-libssh2.o
Removing lib/vssh/libcurl_la-wolfssh.lo
Removing lib/vssh/libcurl_la-wolfssh.o
Removing lib/vtls/.deps/
Removing lib/vtls/.dirstamp
Removing lib/vtls/libcurl_la-bearssl.lo
Removing lib/vtls/libcurl_la-bearssl.o
Removing lib/vtls/libcurl_la-gskit.lo
Removing lib/vtls/libcurl_la-gskit.o
Removing lib/vtls/libcurl_la-gtls.lo
Removing lib/vtls/libcurl_la-gtls.o
Removing lib/vtls/libcurl_la-hostcheck.lo
Removing lib/vtls/libcurl_la-hostcheck.o
Removing lib/vtls/libcurl_la-keylog.lo
Removing lib/vtls/libcurl_la-keylog.o
Removing lib/vtls/libcurl_la-mbedtls.lo
Removing lib/vtls/libcurl_la-mbedtls.o
Removing lib/vtls/libcurl_la-mbedtls_threadlock.lo
Removing lib/vtls/libcurl_la-mbedtls_threadlock.o
Removing lib/vtls/libcurl_la-nss.lo
Removing lib/vtls/libcurl_la-nss.o
Removing lib/vtls/libcurl_la-openssl.lo
Removing lib/vtls/libcurl_la-openssl.o
Removing lib/vtls/libcurl_la-rustls.lo
Removing lib/vtls/libcurl_la-rustls.o
Removing lib/vtls/libcurl_la-schannel.lo
Removing lib/vtls/libcurl_la-schannel.o
Removing lib/vtls/libcurl_la-schannel_verify.lo
Removing lib/vtls/libcurl_la-schannel_verify.o
Removing lib/vtls/libcurl_la-sectransp.lo
Removing lib/vtls/libcurl_la-sectransp.o
Removing lib/vtls/libcurl_la-vtls.lo
Removing lib/vtls/libcurl_la-vtls.o
Removing lib/vtls/libcurl_la-wolfssl.lo
Removing lib/vtls/libcurl_la-wolfssl.o
Removing lib/vtls/libcurl_la-x509asn1.lo
Removing lib/vtls/libcurl_la-x509asn1.o
Removing lib/warnless.o
Removing libcurl.pc
Removing libtool
Removing ltmain.sh
Removing m4/libtool.m4
Removing m4/ltoptions.m4
Removing m4/ltsugar.m4
Removing m4/ltversion.m4
Removing m4/lt~obsolete.m4
Removing missing
Removing packages/Makefile
Removing packages/Makefile.in
Removing packages/vms/Makefile
Removing packages/vms/Makefile.in
Removing scripts/Makefile
Removing scripts/Makefile.in
Removing src/.deps/
Removing src/.libs/
Removing src/Makefile
Removing src/Makefile.in
Removing src/curl
Removing src/slist_wc.o
Removing src/tool_binmode.o
Removing src/tool_bname.o
Removing src/tool_cb_dbg.o
Removing src/tool_cb_hdr.o
Removing src/tool_cb_prg.o
Removing src/tool_cb_rea.o
Removing src/tool_cb_see.o
Removing src/tool_cb_wrt.o
Removing src/tool_cfgable.o
Removing src/tool_dirhie.o
Removing src/tool_doswin.o
Removing src/tool_easysrc.o
Removing src/tool_filetime.o
Removing src/tool_findfile.o
Removing src/tool_formparse.o
Removing src/tool_getparam.o
Removing src/tool_getpass.o
Removing src/tool_help.o
Removing src/tool_helpers.o
Removing src/tool_hugehelp.c
Removing src/tool_hugehelp.o
Removing src/tool_libinfo.o
Removing src/tool_listhelp.o
Removing src/tool_main.o
Removing src/tool_msgs.o
Removing src/tool_operate.o
Removing src/tool_operhlp.o
Removing src/tool_paramhlp.o
Removing src/tool_parsecfg.o
Removing src/tool_progress.o
Removing src/tool_setopt.o
Removing src/tool_sleep.o
Removing src/tool_strdup.o
Removing src/tool_urlglob.o
Removing src/tool_util.o
Removing src/tool_vms.o
Removing src/tool_writeout.o
Removing src/tool_writeout_json.o
Removing src/tool_xattr.o
Removing test-driver
Removing tests/Makefile
Removing tests/Makefile.in
Removing tests/certs/Makefile
Removing tests/certs/Makefile.in
Removing tests/certs/scripts/Makefile
Removing tests/certs/scripts/Makefile.in
Removing tests/config
Removing tests/configurehelp.pm
Removing tests/data/Makefile
Removing tests/data/Makefile.in
Removing tests/libtest/.deps/
Removing tests/libtest/Makefile
Removing tests/libtest/Makefile.in
Removing tests/server/.deps/
Removing tests/server/Makefile
Removing tests/server/Makefile.in
Removing tests/tests-httpd/Makefile
Removing tests/tests-httpd/Makefile.in
Removing tests/tests-httpd/config.ini
Removing tests/unit/.deps/
Removing tests/unit/Makefile
Removing tests/unit/Makefile.in

$ autoreconf -fi
libtoolize: putting auxiliary files in '.'.
libtoolize: copying file './ltmain.sh'
libtoolize: putting macros in AC_CONFIG_MACRO_DIRS, 'm4'.
libtoolize: copying file 'm4/libtool.m4'
libtoolize: copying file 'm4/ltoptions.m4'
libtoolize: copying file 'm4/ltsugar.m4'
libtoolize: copying file 'm4/ltversion.m4'
libtoolize: copying file 'm4/lt~obsolete.m4'
libtoolize: Remember to add 'LT_INIT' to configure.ac.
configure.ac:123: installing './compile'
configure.ac:416: installing './config.guess'
configure.ac:416: installing './config.sub'
configure.ac:123: installing './install-sh'
configure.ac:129: installing './missing'
docs/examples/Makefile.am: installing './depcomp'
parallel-tests: installing './test-driver'

$ bash -c find . -name config.guess -exec cp /usr/share/misc/config.guess {} \;

$ bash -c find . -name config.sub -exec cp /usr/share/misc/config.sub {} \;

$ chmod +x configure

$ ./configure --disable-shared --without-ssl --disable-docs
checking whether to enable maintainer-specific portions of Makefiles... no
checking whether make supports nested variables... yes
checking whether to enable debug build options... no
checking whether to enable compiler optimizer... (assumed) yes
checking whether to enable strict compiler warnings... no
checking whether to enable compiler warnings as errors... no
checking whether to enable curl debug memory tracking... no
checking whether to enable hiding of library internal symbols... yes
checking whether to enable c-ares for DNS lookups... no
checking whether to disable dependency on -lrt... (assumed no)
checking whether to enable ECH support... no
checking for path separator... :
checking for sed... /usr/bin/sed
checking for grep... /usr/bin/grep
checking that grep -E works... yes
checking for ar... /usr/bin/ar
checking for a BSD-compatible install... /usr/bin/install -c
checking for gcc... ccache gcc
checking whether the C compiler works... yes
checking for C compiler default output file name... a.out
checking for suffix of executables... 
checking whether we are cross compiling... no
checking for suffix of object files... o
checking whether the compiler supports GNU C... yes
checking whether ccache gcc accepts -g... yes
checking for ccache gcc option to enable C11 features... none needed
checking whether ccache gcc understands -c and -o together... yes
checking how to run the C preprocessor... ccache gcc -E
checking for stdio.h... yes
checking for stdlib.h... yes
checking for string.h... yes
checking for inttypes.h... yes
checking for stdint.h... yes
checking for strings.h... yes
checking for sys/stat.h... yes
checking for sys/types.h... yes
checking for unistd.h... yes
checking for stdatomic.h... yes
checking if _Atomic is available... yes
checking for a sed that does not truncate output... (cached) /usr/bin/sed
checking for code coverage support... no
checking whether build environment is sane... yes
checking for a race-free mkdir -p... /usr/bin/mkdir -p
checking for gawk... gawk
checking whether make sets $(MAKE)... yes
checking whether make supports the include directive... yes (GNU style)
checking dependency style of ccache gcc... gcc3
checking curl version... 7.87.1-DEV
checking for httpd... no
checking for apache2... no
checking for apachectl... no
checking for apxs... no
configure: httpd/apache2 not in PATH, httpd tests disabled
configure: apxs not in PATH, httpd tests disabled
checking for apxs... no
checking for caddy... no
checking build system type... x86_64-pc-linux-gnu
checking host system type... x86_64-pc-linux-gnu
checking for grep that handles long lines and -e... (cached) /usr/bin/grep
checking for egrep... /usr/bin/grep -E
checking if OS is AIX (to define _ALL_SOURCE)... no
checking if _THREAD_SAFE is already defined... no
checking if _THREAD_SAFE is actually needed... no
checking if _THREAD_SAFE is onwards defined... no
checking if _REENTRANT is already defined... no
checking if _REENTRANT is actually needed... no
checking if _REENTRANT is onwards defined... no
checking for special C compiler options needed for large files... no
checking for _FILE_OFFSET_BITS value needed for large files... no
checking how to print strings... printf
checking for a sed that does not truncate output... (cached) /usr/bin/sed
checking for fgrep... /usr/bin/grep -F
checking for ld used by ccache gcc... /usr/bin/ld
checking if the linker (/usr/bin/ld) is GNU ld... yes
checking for BSD- or MS-compatible name lister (nm)... /usr/bin/nm -B
checking the name lister (/usr/bin/nm -B) interface... BSD nm
checking whether ln -s works... yes
checking the maximum length of command line arguments... 1572864
checking how to convert x86_64-pc-linux-gnu file names to x86_64-pc-linux-gnu format... func_convert_file_noop
checking how to convert x86_64-pc-linux-gnu file names to toolchain format... func_convert_file_noop
checking for /usr/bin/ld option to reload object files... -r
checking for objdump... objdump
checking how to recognize dependent libraries... pass_all
checking for dlltool... no
checking how to associate runtime and link libraries... printf %s\n
checking for archiver @FILE support... @
checking for strip... strip
checking for ranlib... ranlib
checking command to parse /usr/bin/nm -B output from ccache gcc object... ok
checking for sysroot... no
checking for a working dd... /usr/bin/dd
checking how to truncate binary pipes... /usr/bin/dd bs=4096 count=1
checking for mt... mt
checking if mt is a manifest tool... no
checking for dlfcn.h... yes
checking for objdir... .libs
checking if ccache gcc supports -fno-rtti -fno-exceptions... no
checking for ccache gcc option to produce PIC... -fPIC -DPIC
checking if ccache gcc PIC flag -fPIC -DPIC works... yes
checking if ccache gcc static flag -static works... yes
checking if ccache gcc supports -c -o file.o... yes
checking if ccache gcc supports -c -o file.o... (cached) yes
checking whether the ccache gcc linker (/usr/bin/ld -m elf_x86_64) supports shared libraries... yes
checking dynamic linker characteristics... GNU/Linux ld.so
checking how to hardcode library paths into programs... immediate
checking whether stripping libraries is possible... yes
checking if libtool supports shared libraries... yes
checking whether to build shared libraries... no
checking whether to build static libraries... yes
checking whether to build shared libraries with -version-info... yes
checking whether to build shared libraries with -no-undefined... no
checking whether to build shared libraries with -mimpure-text... no
checking whether to build shared libraries with PIC... yes
checking whether to build static libraries with PIC... yes
checking whether to build shared libraries only... no
checking whether to build static libraries only... yes
checking for windres... no
checking for inline... inline
checking if cpp -P is needed... yes
checking if cpp -P works... yes
checking if compiler is DEC/Compaq/HP C... no
checking if compiler is HP-UX C... no
checking if compiler is IBM C... no
checking if compiler is Intel C... no
checking if compiler is clang... no
checking if compiler is GNU C... yes
checking if compiler is LCC... no
checking if compiler is SGI MIPSpro C... no
checking if compiler is SGI MIPS C... no
checking if compiler is SunPro C... no
checking if compiler is Tiny C... no
checking if compiler accepts some basic options... yes
configure: compiler options added: -Werror-implicit-function-declaration 
checking if compiler optimizer assumed setting might be used... yes
checking if compiler accepts optimizer enabling options... yes
configure: compiler options added: -O2
checking if compiler accepts strict warning options... yes
configure: compiler options added: -Wno-system-headers 
checking if compiler halts on compilation errors... yes
checking if compiler halts on negative sized arrays... yes
checking if compiler halts on function prototype mismatch... yes
checking if compiler supports hiding library internal symbols... yes
checking for windows.h... no
checking whether build target is a native Windows one... no
checking whether build target supports WIN32 file API... no
checking whether build target supports WIN32 crypto API... no
checking for good-to-use Darwin CFLAGS... no
checking whether to link macOS CoreFoundation and SystemConfiguration framework... no
checking to see if the compiler supports __builtin_available()... no
checking whether to support http... yes
checking whether to support ftp... yes
checking whether to support file... yes
checking whether to support ldap... yes
checking whether to support ldaps... yes
checking whether to support rtsp... yes
checking whether to support proxies... yes
checking whether to support dict... yes
checking whether to support telnet... yes
checking whether to support tftp... yes
checking whether to support pop3... yes
checking whether to support imap... yes
checking whether to support smb... yes
checking whether to support smtp... yes
checking whether to support gopher... yes
checking whether to support mqtt... no
checking whether to provide built-in manual... yes
checking whether to enable generation of C code... yes
checking whether to use libgcc... no
checking if X/Open network library is required... no
checking for gethostbyname... yes
checking for windows.h... (cached) no
checking for winsock2.h... (cached) no
checking for proto/bsdsocket.h... no
checking for connect in libraries... yes
checking for sys/types.h... (cached) yes
checking for sys/time.h... yes
checking for monotonic clock_gettime... yes
checking for clock_gettime in libraries... no additional lib required
checking if monotonic clock_gettime works... yes
checking for pkg-config... /usr/bin/pkg-config
checking for zlib options with pkg-config... found
checking for zlib.h... yes
configure: found both libz and libz.h header
checking for BrotliDecoderDecompress in -lbrotlidec... no
checking for brotli/decode.h... no
checking for ZSTD_createDStream in -lzstd... no
checking for zstd.h... no
checking for lber.h... no
checking for ldap.h... no
checking for ldap_ssl.h... no
checking for LDAP libraries... cannot find LDAP libraries
checking whether to enable IPv6... yes
checking if struct sockaddr_in6 has sin6_scope_id member... yes
checking if argv can be written to... yes
checking if GSS-API support is requested... no
checking whether to enable Windows native SSL/TLS... no
checking whether to enable Secure Transport... no
checking whether to enable Amiga native SSL/TLS (AmiSSL v5)... no
checking for library containing psl_builtin... -lpsl
checking for library containing gsasl_init... no
checking for RTMP_Init in -lrtmp... no
checking whether versioned symbols are wanted... no
checking whether to enable Windows native IDN (Windows native builds only)... no
checking whether to build with libidn2... (assumed) yes
checking for pkg-config... (cached) /usr/bin/pkg-config
checking for libidn2 options with pkg-config... no
configure: IDN_LIBS: "-lidn2"
configure: IDN_LDFLAGS: ""
configure: IDN_CPPFLAGS: ""
configure: IDN_DIR: ""
checking if idn2_lookup_ul can be linked... no
checking for idn2.h... no
checking for pkg-config... (cached) /usr/bin/pkg-config
checking for libnghttp2 options with pkg-config... no
checking for nghttp2_session_set_local_window_size in -lnghttp2... no
checking for pkg-config... (cached) /usr/bin/pkg-config
checking for fish options with pkg-config... no
checking for sys/types.h... (cached) yes
checking for sys/time.h... (cached) yes
checking for sys/select.h... yes
checking for sys/socket.h... yes
checking for sys/ioctl.h... yes
checking for sys/uio.h... yes
checking for unistd.h... (cached) yes
checking for stdlib.h... (cached) yes
checking for arpa/inet.h... yes
checking for net/if.h... yes
checking for netinet/in.h... yes
checking for netinet/in6.h... no
checking for sys/un.h... yes
checking for linux/tcp.h... yes
checking for netinet/tcp.h... yes
checking for netinet/udp.h... yes
checking for netdb.h... yes
checking for sys/sockio.h... no
checking for sys/stat.h... (cached) yes
checking for sys/param.h... yes
checking for termios.h... yes
checking for termio.h... yes
checking for fcntl.h... yes
checking for io.h... no
checking for pwd.h... yes
checking for utime.h... yes
checking for sys/utime.h... no
checking for sys/poll.h... yes
checking for poll.h... yes
checking for socket.h... no
checking for sys/resource.h... yes
checking for libgen.h... yes
checking for locale.h... yes
checking for stdbool.h... yes
checking for arpa/tftp.h... yes
checking for sys/filio.h... no
checking for sys/wait.h... yes
checking for setjmp.h... yes
checking for an ANSI C-conforming const... yes
checking for compiler support of C99 variadic macro style... yes
checking for compiler support of old gcc variadic macro style... yes
checking for size_t... yes
checking for sys/types.h... (cached) yes
checking for sys/time.h... (cached) yes
checking for sys/socket.h... (cached) yes
checking for struct timeval... yes
checking run-time libs availability... fine
checking size of size_t... 8
checking size of long... 8
checking size of int... 4
checking size of time_t... 8
checking size of off_t... 8
checking size of curl_off_t... 8
checking for long long... yes
checking for ssize_t... yes
checking for bool... yes
checking for sa_family_t... yes
checking for suseconds_t... yes
checking if time_t is unsigned... no
checking for sys/types.h... (cached) yes
checking for poll.h... (cached) yes
checking for sys/poll.h... (cached) yes
checking for in_addr_t... yes
checking for struct sockaddr_storage... yes
checking for sys/select.h... (cached) yes
checking for sys/socket.h... (cached) yes
checking for select... yes
checking for sys/types.h... (cached) yes
checking for sys/socket.h... (cached) yes
checking for recv... yes
checking for sys/types.h... (cached) yes
checking for sys/socket.h... (cached) yes
checking for send... yes
checking for sys/types.h... (cached) yes
checking for sys/socket.h... (cached) yes
checking for MSG_NOSIGNAL... yes
checking for sys/types.h... (cached) yes
checking for unistd.h... (cached) yes
checking if alarm can be linked... yes
checking if alarm is prototyped... yes
checking if alarm is compilable... yes
checking if alarm usage allowed... yes
checking if alarm might be used... yes
checking for sys/types.h... (cached) yes
checking for string.h... (cached) yes
checking for strings.h... (cached) yes
checking for sys/types.h... (cached) yes
checking for libgen.h... (cached) yes
checking if basename can be linked... yes
checking if basename is prototyped... yes
checking if basename is compilable... yes
checking if basename usage allowed... yes
checking if basename might be used... yes
checking for sys/types.h... (cached) yes
checking for socket.h... (cached) no
checking if closesocket can be linked... no
checking if closesocket might be used... no
checking for sys/types.h... (cached) yes
checking for sys/socket.h... (cached) yes
checking if CloseSocket can be linked... no
checking if CloseSocket might be used... no
checking if connect can be linked... yes
checking if connect is prototyped... yes
checking if connect is compilable... yes
checking if connect usage allowed... yes
checking if connect might be used... yes
checking for sys/types.h... (cached) yes
checking for unistd.h... (cached) yes
checking for fcntl.h... (cached) yes
checking if fcntl can be linked... yes
checking if fcntl is prototyped... yes
checking if fcntl is compilable... yes
checking if fcntl usage allowed... yes
checking if fcntl might be used... yes
checking if fcntl O_NONBLOCK is compilable... yes
checking if fcntl O_NONBLOCK usage allowed... yes
checking if fcntl O_NONBLOCK might be used... yes
checking for windows.h... (cached) no
checking for winsock2.h... (cached) no
checking for ws2tcpip.h... (cached) no
checking for sys/types.h... (cached) yes
checking for netdb.h... (cached) yes
checking if freeaddrinfo can be linked... yes
checking if freeaddrinfo is prototyped... yes
checking if freeaddrinfo is compilable... yes
checking if freeaddrinfo usage allowed... yes
checking if freeaddrinfo might be used... yes
checking for sys/types.h... (cached) yes
checking for sys/xattr.h... yes
checking if fsetxattr can be linked... yes
checking if fsetxattr is prototyped... yes
checking if fsetxattr takes 5 args.... yes
checking if fsetxattr is compilable... yes
checking if fsetxattr usage allowed... yes
checking if fsetxattr might be used... yes
checking if ftruncate can be linked... yes
checking if ftruncate is prototyped... yes
checking if ftruncate is compilable... yes
checking if ftruncate usage allowed... yes
checking if ftruncate might be used... yes
checking for sys/types.h... (cached) yes
checking for stdlib.h... (cached) yes
checking if getaddrinfo can be linked... yes
checking if getaddrinfo is prototyped... yes
checking if getaddrinfo is compilable... yes
checking if getaddrinfo seems to work... yes
checking if getaddrinfo usage allowed... yes
checking if getaddrinfo might be used... yes
checking if getaddrinfo is threadsafe... yes
checking if gethostbyname can be linked... yes
checking if gethostbyname is prototyped... yes
checking if gethostbyname is compilable... yes
checking if gethostbyname usage allowed... yes
checking if gethostbyname might be used... yes
checking if gethostbyname_r can be linked... yes
checking if gethostbyname_r is prototyped... yes
checking if gethostbyname_r takes 3 args.... no
checking if gethostbyname_r takes 5 args.... no
checking if gethostbyname_r takes 6 args.... yes
checking if gethostbyname_r is compilable... yes
checking if gethostbyname_r usage allowed... yes
checking if gethostbyname_r might be used... yes
checking if gethostname can be linked... yes
checking if gethostname is prototyped... yes
checking if gethostname is compilable... yes
checking for gethostname arg 2 data type... size_t
checking if gethostname usage allowed... yes
checking if gethostname might be used... yes
checking if getpeername can be linked... yes
checking if getpeername is prototyped... yes
checking if getpeername is compilable... yes
checking if getpeername usage allowed... yes
checking if getpeername might be used... yes
checking if getsockname can be linked... yes
checking if getsockname is prototyped... yes
checking if getsockname is compilable... yes
checking if getsockname usage allowed... yes
checking if getsockname might be used... yes
checking for net/if.h... (cached) yes
checking if if_nametoindex can be linked... yes
checking if if_nametoindex is prototyped... yes
checking if if_nametoindex is compilable... yes
checking if if_nametoindex usage allowed... yes
checking if if_nametoindex might be used... yes
checking for sys/types.h... (cached) yes
checking for sys/socket.h... (cached) yes
checking for netinet/in.h... (cached) yes
checking for ifaddrs.h... yes
checking if getifaddrs can be linked... yes
checking if getifaddrs is prototyped... yes
checking if getifaddrs is compilable... yes
checking if getifaddrs seems to work... yes
checking if getifaddrs usage allowed... yes
checking if getifaddrs might be used... yes
checking for sys/types.h... (cached) yes
checking for sys/time.h... (cached) yes
checking if gmtime_r can be linked... yes
checking if gmtime_r is prototyped... yes
checking if gmtime_r is compilable... yes
checking if gmtime_r seems to work... yes
checking if gmtime_r usage allowed... yes
checking if gmtime_r might be used... yes
checking for sys/types.h... (cached) yes
checking for sys/socket.h... (cached) yes
checking for netinet/in.h... (cached) yes
checking for arpa/inet.h... (cached) yes
checking if inet_ntop can be linked... yes
checking if inet_ntop is prototyped... yes
checking if inet_ntop is compilable... yes
checking if inet_ntop seems to work... yes
checking if inet_ntop usage allowed... yes
checking if inet_ntop might be used... yes
checking if inet_pton can be linked... yes
checking if inet_pton is prototyped... yes
checking if inet_pton is compilable... yes
checking if inet_pton seems to work... yes
checking if inet_pton usage allowed... yes
checking if inet_pton might be used... yes
checking if ioctlsocket can be linked... no
checking if ioctlsocket might be used... no
checking if IoctlSocket can be linked... no
checking if IoctlSocket might be used... no
checking if memrchr can be linked... yes
checking if memrchr is prototyped... no
checking if memrchr might be used... no
checking if poll can be linked... yes
checking if poll is prototyped... yes
checking if poll is compilable... yes
checking if poll seems to work... yes
checking if poll usage allowed... yes
checking if poll might be used... yes
checking for sys/types.h... (cached) yes
checking for signal.h... yes
checking if sigaction can be linked... yes
checking if sigaction is prototyped... yes
checking if sigaction is compilable... yes
checking if sigaction usage allowed... yes
checking if sigaction might be used... yes
checking if siginterrupt can be linked... yes
checking if siginterrupt is prototyped... yes
checking if siginterrupt is compilable... yes
checking if siginterrupt usage allowed... yes
checking if siginterrupt might be used... yes
checking if signal can be linked... yes
checking if signal is prototyped... yes
checking if signal is compilable... yes
checking if signal usage allowed... yes
checking if signal might be used... yes
checking for sys/types.h... (cached) yes
checking for setjmp.h... (cached) yes
checking if sigsetjmp can be linked... no
checking if sigsetjmp seems a macro... yes
checking if sigsetjmp is compilable... yes
checking if sigsetjmp usage allowed... yes
checking if sigsetjmp might be used... yes
checking if socket can be linked... yes
checking if socket is prototyped... yes
checking if socket is compilable... yes
checking if socket usage allowed... yes
checking if socket might be used... yes
checking if socketpair can be linked... yes
checking if socketpair is prototyped... yes
checking if socketpair is compilable... yes
checking if socketpair usage allowed... yes
checking if socketpair might be used... yes
checking if strcasecmp can be linked... yes
checking if strcasecmp is prototyped... yes
checking if strcasecmp is compilable... yes
checking if strcasecmp usage allowed... yes
checking if strcasecmp might be used... yes
checking if strcmpi can be linked... no
checking if strcmpi might be used... no
checking if strdup can be linked... yes
checking if strdup is prototyped... yes
checking if strdup is compilable... yes
checking if strdup usage allowed... yes
checking if strdup might be used... yes
checking if strerror_r can be linked... yes
checking if strerror_r is prototyped... yes
checking if strerror_r is compilable... yes
checking if strerror_r is glibc like... no
checking if strerror_r is POSIX like... yes
checking if strerror_r seems to work... yes
checking if strerror_r usage allowed... yes
checking if strerror_r might be used... yes
checking if stricmp can be linked... no
checking if stricmp might be used... no
checking if strtok_r can be linked... yes
checking if strtok_r is prototyped... yes
checking if strtok_r is compilable... yes
checking if strtok_r usage allowed... yes
checking if strtok_r might be used... yes
checking if strtoll can be linked... yes
checking if strtoll is prototyped... yes
checking if strtoll is compilable... yes
checking if strtoll usage allowed... yes
checking if strtoll might be used... yes
checking for ccache gcc options needed to detect all undeclared functions... none needed
checking whether getpwuid_r is declared... yes
checking for fnmatch... yes
checking for fchmod... yes
checking for fork... yes
checking for geteuid... yes
checking for getpass_r... no
checking deeper for getpass_r... but still no
checking for getppid... yes
checking for getpwuid... yes
checking for getpwuid_r... yes
checking for getrlimit... yes
checking for gettimeofday... yes
checking for if_nametoindex... yes
checking for mach_absolute_time... no
checking deeper for mach_absolute_time... but still no
checking for pipe... yes
checking for sched_yield... yes
checking for sendmsg... yes
checking for setlocale... yes
checking for setmode... no
checking deeper for setmode... but still no
checking for setrlimit... yes
checking for snprintf... yes
checking for utime... yes
checking for utimes... yes
checking how to set a socket into non-blocking mode... fcntl O_NONBLOCK
checking for perl... /usr/bin/perl
checking for gnroff... no
checking for nroff... /usr/bin/nroff
checking how to use *nroff to get plain text from man pages... -man
checking whether to enable the threaded resolver... yes
checking whether to use POSIX threads for threaded resolver... auto
checking for pthread.h... yes
checking for pthread_create... yes
checking convert -I options to -isystem... yes
checking whether to enable verbose strings... yes
checking whether to enable SSPI support (Windows native builds only)... no
checking whether to enable cryptographic authentication methods... yes
checking whether to support NTLM... yes
checking whether to enable NTLM delegation to winbind's helper... no
checking whether to enable TLS-SRP authentication... yes
checking whether to enable Unix domain sockets... auto
checking for struct sockaddr_un.sun_path... yes
checking whether to support cookies... yes
checking whether to support socketpair... yes
checking whether to support HTTP authentication... yes
checking whether to support DoH... yes
checking whether to support the MIME API... yes
checking whether to support date parsing... yes
checking whether to support netrc parsing... yes
checking whether to support progress-meter... yes
checking whether to support DNS shuffling... yes
checking whether to support curl_easy_option*... yes
checking whether to support alt-svc... yes
checking whether to support headers-api... yes
configure: disables HSTS due to lack of SSL
checking whether to support WebSockets... no
checking whether hiding of library internal symbols will actually happen... yes
checking that generated files are newer than configure... done
configure: creating ./config.status
config.status: creating Makefile
config.status: creating docs/Makefile
config.status: creating docs/examples/Makefile
config.status: creating docs/libcurl/Makefile
config.status: creating docs/libcurl/opts/Makefile
config.status: creating docs/cmdline-opts/Makefile
config.status: creating include/Makefile
config.status: creating include/curl/Makefile
config.status: creating src/Makefile
config.status: creating lib/Makefile
config.status: creating scripts/Makefile
config.status: creating lib/libcurl.vers
config.status: creating lib/libcurl.plist
config.status: creating tests/Makefile
config.status: creating tests/config
config.status: creating tests/certs/Makefile
config.status: creating tests/certs/scripts/Makefile
config.status: creating tests/data/Makefile
config.status: creating tests/server/Makefile
config.status: creating tests/libtest/Makefile
config.status: creating tests/unit/Makefile
config.status: creating tests/tests-httpd/config.ini
config.status: creating tests/tests-httpd/Makefile
config.status: creating packages/Makefile
config.status: creating packages/vms/Makefile
config.status: creating curl-config
config.status: creating libcurl.pc
config.status: creating lib/curl_config.h
config.status: executing depfiles commands
config.status: executing libtool commands
configure: amending lib/Makefile
configure: amending src/Makefile
configure: amending tests/unit/Makefile
configure: amending tests/server/Makefile
configure: amending tests/libtest/Makefile
configure: amending docs/examples/Makefile
configure: Configured to build curl/libcurl:

  Host setup:       x86_64-pc-linux-gnu
  Install prefix:   /usr/local
  Compiler:         ccache gcc
   CFLAGS:          -Werror-implicit-function-declaration -O2 -Wno-system-headers
   CPPFLAGS:        
   LDFLAGS:         
   LIBS:            -lpsl -lz

  curl version:     7.87.1-DEV
  SSL:              no      (--with-{openssl,gnutls,nss,mbedtls,wolfssl,schannel,secure-transport,amissl,bearssl,rustls} )
  SSH:              no      (--with-{libssh,libssh2})
  zlib:             enabled
  brotli:           no      (--with-brotli)
  zstd:             no      (--with-zstd)
  GSS-API:          no      (--with-gssapi)
  GSASL:            no      (libgsasl not found)
  TLS-SRP:          no      (--enable-tls-srp)
  resolver:         POSIX threaded
  IPv6:             enabled
  Unix sockets:     enabled
  IDN:              no      (--with-{libidn2,winidn})
  Build libcurl:    Shared=no, Static=yes
  Built-in manual:  enabled
  --libcurl option: enabled (--disable-libcurl-option)
  Verbose errors:   enabled (--disable-verbose)
  Code coverage:    disabled
  SSPI:             no      (--enable-sspi)
  ca cert bundle:   no
  ca cert path:     
  ca fallback:      
  LDAP:             no      (--enable-ldap / --with-ldap-lib / --with-lber-lib)
  LDAPS:            no      (--enable-ldaps)
  RTSP:             enabled
  RTMP:             no      (--with-librtmp)
  PSL:              enabled
  Alt-svc:          enabled (--disable-alt-svc)
  Headers API:      enabled (--disable-headers-api)
  HSTS:             no      (--enable-hsts)
  HTTP1:            enabled (internal)
  HTTP2:            no      (--with-nghttp2, --with-hyper)
  HTTP3:            no      (--with-ngtcp2, --with-quiche --with-msh3)
  ECH:              no      (--enable-ech)
  WebSockets:       no      (--enable-websockets)
  Protocols:        DICT FILE FTP GOPHER HTTP IMAP MQTT POP3 RTSP SMTP TELNET TFTP
  Features:         AsynchDNS IPv6 Largefile PSL UnixSockets alt-svc libz threadsafe

configure: WARNING: unrecognized options: --disable-docs
configure: WARNING: Cannot find libraries for LDAP support: LDAP disabled
configure: WARNING: libgsasl was not found
configure: WARNING: Cannot find libraries for IDN support: IDN disabled
configure: WARNING: unrecognized options: --disable-docs

$ make -j3
Making all in lib
make[1]: Entering directory '/root/asi_db/builds/curl/_worktrees/w0/lib'
make  all-am
make[2]: Entering directory '/root/asi_db/builds/curl/_worktrees/w0/lib'
  CC       libcurl_la-altsvc.lo
  CC       libcurl_la-amigaos.lo
  CC       libcurl_la-asyn-ares.lo
  CC       libcurl_la-asyn-thread.lo
  CC       libcurl_la-base64.lo
  CC       libcurl_la-bufref.lo
  CC       libcurl_la-c-hyper.lo
  CC       libcurl_la-cf-http.lo
  CC       libcurl_la-cf-socket.lo
  CC       libcurl_la-cfilters.lo
  CC       libcurl_la-conncache.lo
  CC       libcurl_la-connect.lo
  CC       libcurl_la-content_encoding.lo
  CC       libcurl_la-cookie.lo
  CC       libcurl_la-curl_addrinfo.lo
  CC       libcurl_la-curl_des.lo
  CC       libcurl_la-curl_endian.lo
  CC       libcurl_la-curl_fnmatch.lo
  CC       libcurl_la-curl_get_line.lo
  CC       libcurl_la-curl_gethostname.lo
  CC       libcurl_la-curl_gssapi.lo
  CC       libcurl_la-curl_log.lo
  CC       libcurl_la-curl_memrchr.lo
  CC       libcurl_la-curl_multibyte.lo
  CC       libcurl_la-curl_ntlm_core.lo
  CC       libcurl_la-curl_ntlm_wb.lo
  CC       libcurl_la-curl_path.lo
  CC       libcurl_la-curl_range.lo
  CC       libcurl_la-curl_rtmp.lo
  CC       libcurl_la-curl_sasl.lo
  CC       libcurl_la-curl_sspi.lo
  CC       libcurl_la-curl_threads.lo
  CC       libcurl_la-dict.lo
  CC       libcurl_la-doh.lo
  CC       libcurl_la-dynbuf.lo
  CC       libcurl_la-easy.lo
  CC       libcurl_la-easygetopt.lo
  CC       libcurl_la-easyoptions.lo
  CC       libcurl_la-escape.lo
  CC       libcurl_la-file.lo
  CC       libcurl_la-fileinfo.lo
  CC       libcurl_la-fopen.lo
  CC       libcurl_la-formdata.lo
  CC       libcurl_la-ftp.lo
  CC       libcurl_la-ftplistparser.lo
  CC       libcurl_la-getenv.lo
  CC       libcurl_la-getinfo.lo
  CC       libcurl_la-gopher.lo
  CC       libcurl_la-h2h3.lo
  CC       libcurl_la-hash.lo
  CC       libcurl_la-headers.lo
  CC       libcurl_la-hmac.lo
  CC       libcurl_la-hostasyn.lo
  CC       libcurl_la-hostip.lo
  CC       libcurl_la-hostip4.lo
  CC       libcurl_la-hostip6.lo
  CC       libcurl_la-hostsyn.lo
  CC       libcurl_la-hsts.lo
  CC       libcurl_la-http.lo
  CC       libcurl_la-http2.lo
  CC       libcurl_la-http_chunks.lo
  CC       libcurl_la-http_digest.lo
  CC       libcurl_la-http_negotiate.lo
  CC       libcurl_la-http_ntlm.lo
  CC       libcurl_la-http_proxy.lo
  CC       libcurl_la-http_aws_sigv4.lo
  CC       libcurl_la-idn.lo
  CC       libcurl_la-if2ip.lo
  CC       libcurl_la-imap.lo
  CC       libcurl_la-inet_ntop.lo
  CC       libcurl_la-inet_pton.lo
  CC       libcurl_la-krb5.lo
  CC       libcurl_la-ldap.lo
  CC       libcurl_la-llist.lo
  CC       libcurl_la-md4.lo
  CC       libcurl_la-md5.lo
  CC       libcurl_la-memdebug.lo
  CC       libcurl_la-mime.lo
  CC       libcurl_la-mprintf.lo
  CC       libcurl_la-mqtt.lo
  CC       libcurl_la-multi.lo
  CC       libcurl_la-netrc.lo
  CC       libcurl_la-nonblock.lo
  CC       libcurl_la-noproxy.lo
  CC       libcurl_la-openldap.lo
  CC       libcurl_la-parsedate.lo
  CC       libcurl_la-pingpong.lo
  CC       libcurl_la-pop3.lo
  CC       libcurl_la-progress.lo
  CC       libcurl_la-psl.lo
  CC       libcurl_la-rand.lo
  CC       libcurl_la-rename.lo
  CC       libcurl_la-rtsp.lo
  CC       libcurl_la-select.lo
  CC       libcurl_la-sendf.lo
  CC       libcurl_la-setopt.lo
  CC       libcurl_la-sha256.lo
  CC       libcurl_la-share.lo
  CC       libcurl_la-slist.lo
  CC       libcurl_la-smb.lo
  CC       libcurl_la-smtp.lo
  CC       libcurl_la-socketpair.lo
  CC       libcurl_la-socks.lo
  CC       libcurl_la-socks_gssapi.lo
  CC       libcurl_la-socks_sspi.lo
  CC       libcurl_la-speedcheck.lo
  CC       libcurl_la-splay.lo
  CC       libcurl_la-strcase.lo
  CC       libcurl_la-strdup.lo
  CC       libcurl_la-strerror.lo
  CC       libcurl_la-strtok.lo
  CC       libcurl_la-strtoofft.lo
  CC       libcurl_la-system_win32.lo
  CC       libcurl_la-telnet.lo
  CC       libcurl_la-tftp.lo
  CC       libcurl_la-timediff.lo
  CC       libcurl_la-timeval.lo
  CC       libcurl_la-transfer.lo
  CC       libcurl_la-url.lo
  CC       libcurl_la-urlapi.lo
  CC       libcurl_la-version.lo
  CC       libcurl_la-version_win32.lo
  CC       libcurl_la-warnless.lo
  CC       libcurl_la-wildcard.lo
  CC       libcurl_la-ws.lo
  CC       vauth/libcurl_la-cleartext.lo
  CC       vauth/libcurl_la-cram.lo
  CC       vauth/libcurl_la-digest.lo
  CC       vauth/libcurl_la-digest_sspi.lo
  CC       vauth/libcurl_la-gsasl.lo
  CC       vauth/libcurl_la-krb5_gssapi.lo
  CC       vauth/libcurl_la-krb5_sspi.lo
  CC       vauth/libcurl_la-ntlm.lo
  CC       vauth/libcurl_la-ntlm_sspi.lo
  CC       vauth/libcurl_la-oauth2.lo
  CC       vauth/libcurl_la-spnego_gssapi.lo
  CC       vauth/libcurl_la-spnego_sspi.lo
  CC       vauth/libcurl_la-vauth.lo
  CC       vtls/libcurl_la-bearssl.lo
  CC       vtls/libcurl_la-gskit.lo
  CC       vtls/libcurl_la-gtls.lo
  CC       vtls/libcurl_la-hostcheck.lo
  CC       vtls/libcurl_la-keylog.lo
  CC       vtls/libcurl_la-mbedtls.lo
  CC       vtls/libcurl_la-mbedtls_threadlock.lo
  CC       vtls/libcurl_la-nss.lo
  CC       vtls/libcurl_la-openssl.lo
  CC       vtls/libcurl_la-rustls.lo
  CC       vtls/libcurl_la-schannel.lo
  CC       vtls/libcurl_la-schannel_verify.lo
  CC       vtls/libcurl_la-sectransp.lo
  CC       vtls/libcurl_la-vtls.lo
  CC       vtls/libcurl_la-wolfssl.lo
  CC       vtls/libcurl_la-x509asn1.lo
  CC       vquic/libcurl_la-curl_msh3.lo
  CC       vquic/libcurl_la-curl_ngtcp2.lo
  CC       vquic/libcurl_la-curl_quiche.lo
  CC       vquic/libcurl_la-vquic.lo
  CC       vssh/libcurl_la-libssh.lo
  CC       vssh/libcurl_la-libssh2.lo
  CC       vssh/libcurl_la-wolfssh.lo
  CCLD     libcurl.la
make[2]: Leaving directory '/root/asi_db/builds/curl/_worktrees/w0/lib'
make[1]: Leaving directory '/root/asi_db/builds/curl/_worktrees/w0/lib'
Making all in src
make[1]: Entering directory '/root/asi_db/builds/curl/_worktrees/w0/src'
Making all in ../docs
make[2]: Entering directory '/root/asi_db/builds/curl/_worktrees/w0/docs'
Making all in .
make[3]: Entering directory '/root/asi_db/builds/curl/_worktrees/w0/docs'
if test "..x" != "..x" -a -e "./curl.1"; then \
	/usr/bin/install -c -m 644 "./curl.1" /root/asi_db/builds/curl/_worktrees/w0/docs/curl.1; fi
cd cmdline-opts && make
make[4]: Entering directory '/root/asi_db/builds/curl/_worktrees/w0/docs/cmdline-opts'
generate ../../docs/curl.1
make[4]: Leaving directory '/root/asi_db/builds/curl/_worktrees/w0/docs/cmdline-opts'
make[3]: Leaving directory '/root/asi_db/builds/curl/_worktrees/w0/docs'
Making all in cmdline-opts
make[3]: Entering directory '/root/asi_db/builds/curl/_worktrees/w0/docs/cmdline-opts'
make[3]: Nothing to be done for 'all'.
make[3]: Leaving directory '/root/asi_db/builds/curl/_worktrees/w0/docs/cmdline-opts'
make[2]: Leaving directory '/root/asi_db/builds/curl/_worktrees/w0/docs'
make[2]: Entering directory '/root/asi_db/builds/curl/_worktrees/w0/src'
  CC       slist_wc.o
  CC       tool_binmode.o
  CC       tool_bname.o
  CC       tool_cb_dbg.o
  CC       tool_cb_hdr.o
  CC       tool_cb_prg.o
  CC       tool_cb_rea.o
  CC       tool_cb_see.o
  CC       tool_cb_wrt.o
  CC       tool_cfgable.o
  CC       tool_dirhie.o
  CC       tool_doswin.o
  CC       tool_easysrc.o
  CC       tool_filetime.o
  CC       tool_findfile.o
  CC       tool_formparse.o
  CC       tool_getparam.o
  CC       tool_getpass.o
  CC       tool_help.o
  CC       tool_helpers.o
  HUGE     tool_hugehelp.c
  CC       tool_libinfo.o
  CC       tool_listhelp.o
  CC       tool_main.o
  CC       tool_msgs.o
  CC       tool_operate.o
  CC       tool_operhlp.o
  CC       tool_paramhlp.o
  CC       tool_parsecfg.o
  CC       tool_progress.o
  CC       tool_strdup.o
  CC       tool_setopt.o
  CC       tool_sleep.o
  CC       tool_urlglob.o
  CC       tool_util.o
  CC       tool_vms.o
  CC       tool_writeout.o
  CC       tool_writeout_json.o
  CC       tool_xattr.o
  CC       ../lib/strtoofft.o
  CC       ../lib/timediff.o
  CC       ../lib/nonblock.o
  CC       ../lib/warnless.o
  CC       ../lib/curl_multibyte.o
  CC       ../lib/version_win32.o
  CC       ../lib/dynbuf.o
  CC       tool_hugehelp.o
  CCLD     curl
make[2]: Leaving directory '/root/asi_db/builds/curl/_worktrees/w0/src'
make[1]: Leaving directory '/root/asi_db/builds/curl/_worktrees/w0/src'
make[1]: Entering directory '/root/asi_db/builds/curl/_worktrees/w0'
make[1]: Nothing to be done for 'all-am'.
make[1]: Leaving directory '/root/asi_db/builds/curl/_worktrees/w0'

Commit Diff GitHub →

.github/scripts/spellcheck.words +3 -0configure.ac +19 -0docs/HTTP3.md +27 -1docs/examples/http3.c +2 -1lib/Makefile.inc +4 -2lib/cf-http.c +518 -0lib/cf-http.h +58 -0lib/cf-socket.c +197 -33lib/cf-socket.h +13 -7lib/cfilters.c +30 -1lib/cfilters.h +31 -2lib/connect.c +135 -27lib/connect.h +20 -6lib/curl_log.c +4 -0lib/http.c +28 -65lib/http.h +6 -2lib/http2.c +427 -273lib/http2.h +11 -3lib/http_proxy.c +11 -13lib/progress.c +24 -12lib/progress.h +7 -0lib/socks.c +1 -1lib/urldata.h +1 -0lib/vquic/curl_msh3.c +16 -7lib/vquic/curl_ngtcp2.c +244 -110lib/vquic/curl_quiche.c +186 -110lib/vquic/vquic.c +44 -4lib/vquic/vquic.h +6 -2lib/vtls/bearssl.c +15 -48lib/vtls/gskit.c +6 -6lib/vtls/gtls.c +16 -49lib/vtls/mbedtls.c +13 -36lib/vtls/nss.c +12 -52lib/vtls/openssl.c +11 -55lib/vtls/rustls.c +15 -52lib/vtls/schannel.c +21 -48lib/vtls/sectransp.c +17 -27lib/vtls/vtls.c +164 -24lib/vtls/vtls.h +43 -1lib/vtls/vtls_int.h +2 -0lib/vtls/wolfssl.c +12 -42tests/Makefile.am +1 -1tests/libtest/lib2502.c +1 -1tests/tests-httpd/Makefile.am +27 -0tests/tests-httpd/config.ini.in +6 -2tests/tests-httpd/conftest.py +1 -1tests/tests-httpd/scorecard.py +400 -0tests/tests-httpd/test_01_basic.py +5 -0tests/tests-httpd/test_02_download.py +42 -32tests/tests-httpd/test_03_goaway.py +8 -8tests/tests-httpd/test_04_stuttered.py +15 -12tests/tests-httpd/test_05_errors.py +9 -10tests/tests-httpd/test_06_eyeballs.py +86 -0tests/tests-httpd/test_07_upload.py +150 -0tests/tests-httpd/testenv/__init__.py +1 -0tests/tests-httpd/testenv/caddy.py +164 -0tests/tests-httpd/testenv/curl.py +69 -22tests/tests-httpd/testenv/env.py +101 -6tests/tests-httpd/testenv/httpd.py +12 -13tests/tests-httpd/testenv/nghttpx.py +76 -17tests/unit/unit2600.c +4 -1
.github/scripts/spellcheck.words+3 -0
@@ -335,6 +335,9 @@ IoT
ipadOS
IPCXN
IPv
+IPv4
+IPv4/6
+IPv6
IRIs
IRIX
Itanium
configure.ac+19 -0
@@ -311,6 +311,16 @@ AS_HELP_STRING([--with-test-nghttpx=PATH],[where to find nghttpx for testing]),
)
AC_SUBST(TEST_NGHTTPX)
+CADDY=caddy
+AC_ARG_WITH(test-caddy,dnl
+AS_HELP_STRING([--with-test-caddy=PATH],[where to find caddy for testing]),
+ CADDY=$withval
+ if test X"$OPT_CADDY" = "Xno" ; then
+ CADDY=""
+ fi
+)
+AC_SUBST(CADDY)
+
dnl we'd like a httpd+apachectl as test server
dnl
AC_ARG_WITH(test-httpd, [AS_HELP_STRING([--with-test-httpd=PATH],
@@ -366,6 +376,14 @@ fi
AC_PATH_PROG([APXS], [apxs])
AC_SUBST(HTTPD_NGHTTPX)
+dnl the Caddy server we might use in testing
+if test "x$TEST_CADDY" != "x"; then
+ CADDY="$TEST_CADDY"
+else
+ AC_PATH_PROG([CADDY], [caddy])
+fi
+AC_SUBST(CADDY)
+
dnl If no TLS choice has been made, check if it was explicitly disabled or
dnl error out to force the user to decide.
if test -z "$TLSCHOICE"; then
@@ -4646,6 +4664,7 @@ AC_CONFIG_FILES([Makefile \
tests/libtest/Makefile \
tests/unit/Makefile \
tests/tests-httpd/config.ini \
+ tests/tests-httpd/Makefile \
packages/Makefile \
packages/vms/Makefile \
curl-config \
docs/HTTP3.md+27 -1
@@ -239,7 +239,11 @@ directory, or copy `msquic.dll` and `msh3.dll` from that directory to the
# `--http3`
-Use HTTP/3 directly:
+Use only HTTP/3:
+
+ curl --http3-only https://nghttp2.org:4433/
+
+Use HTTP/3 with fallback to HTTP/2 or HTTP/1.1 (see "HTTPS eyeballing" below):
curl --http3 https://nghttp2.org:4433/
@@ -249,6 +253,28 @@ Upgrade via Alt-Svc:
See this [list of public HTTP/3 servers](https://bagder.github.io/HTTP3-test/)
+### HTTPS eyeballing
+
+With option `--http3` curl will attempt earlier HTTP versions as well should the connect
+attempt via HTTP/3 not succeed "fast enough". This strategy is similar to IPv4/6 happy
+eyeballing where the alternate address family is used in parallel after a short delay.
+
+The IPv4/6 eyeballing has a default of 200ms and you may override that via `--happy-eyeballs-timeout-ms value`.
+Since HTTP/3 is still relatively new, we decided to use this timeout also for the HTTP eyeballing - with a slight twist.
+
+The `happy-eyeballs-timeout-ms` value is the **hard** timeout, meaning after that time expired, a TLS connection is opened in addition to negotiate HTTP/2 or HTTP/1.1. At half of that value - currently - is the **soft** timeout. The soft timeout fires, when there has been **no data at all** seen from the server on the HTTP/3 connection.
+
+So, without you specifying anything, the hard timeout is 200ms and the soft is 100ms:
+
+ * Ideally, the whole QUIC handshake happens and curl has a HTTP/3 connection in less than 100ms.
+ * When QUIC is not supported (or UDP does not work for this network path), no reply is seen and the HTTP/2 TLS+TCP connection starts 100ms later.
+ * In the worst case, UDP replies start before 100ms, but drag on. This will start the TLS+TCP connection after 200ms.
+ * When the QUIC handshake fails, the TLS+TCP connection is attempted right away. For example, when the QUIC server presents the wrong certificate.
+
+The whole transfer only fails, when **both** QUIC and TLS+TCP fail to handshake or time out.
+
+Note that all this happens in addition to IP version happy eyeballing. If the name resolution for the server gives more than one IP address, curl will try all those until one succeeds - just as with all other protocols. And if those IP addresses contain both IPv6 and IPv4, those attempts will happen, delayed, in parallel (the actual eyeballing).
+
## Known Bugs
Check out the [list of known HTTP3 bugs](https://curl.se/docs/knownbugs.html#HTTP3).
docs/examples/http3.c+2 -1
@@ -40,7 +40,8 @@ int main(void)
/* Forcing HTTP/3 will make the connection fail if the server is not
accessible over QUIC + HTTP/3 on the given host and port.
Consider using CURLOPT_ALTSVC instead! */
- curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, (long)CURL_HTTP_VERSION_3);
+ curl_easy_setopt(curl, CURLOPT_HTTP_VERSION,
+ (long)CURL_HTTP_VERSION_3ONLY);
/* Perform the request, res will get the return code */
res = curl_easy_perform(curl);
lib/Makefile.inc+4 -2
@@ -107,7 +107,8 @@ LIB_CFILES = \
base64.c \
bufref.c \
c-hyper.c \
- cf-socket.c \
+ cf-http.c \
+ cf-socket.c \
cfilters.c \
conncache.c \
connect.c \
@@ -232,7 +233,8 @@ LIB_HFILES = \
asyn.h \
bufref.h \
c-hyper.h \
- cf-socket.h \
+ cf-http.h \
+ cf-socket.h \
cfilters.h \
conncache.h \
connect.h \
lib/cf-http.c+518 -0
@@ -0,0 +1,518 @@
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <[email protected]>, 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) */
lib/cf-http.h+58 -0
@@ -0,0 +1,58 @@
+#ifndef HEADER_CURL_CF_HTTP_H
+#define HEADER_CURL_CF_HTTP_H
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <[email protected]>, 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 */
lib/cf-socket.c+197 -33
@@ -250,9 +250,23 @@ static CURLcode socket_open(struct Curl_easy *data,
(struct curl_sockaddr *)addr);
Curl_set_in_callback(data, false);
}
- else
+ 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 shouldbe 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 */
@@ -769,11 +783,25 @@ struct cf_socket_ctx {
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;
@@ -785,27 +813,34 @@ static void cf_socket_close(struct Curl_cfilter *cf, struct Curl_easy *data)
* 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));
+ 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) local", (int)ctx->sock));
+ 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->sock = CURL_SOCKET_BAD;
ctx->active = FALSE;
+ memset(&ctx->started_at, 0, sizeof(ctx->started_at));
+ memset(&ctx->connected_at, 0, sizeof(ctx->connected_at));
}
cf->connected = FALSE;
@@ -882,8 +917,10 @@ static CURLcode cf_socket_open(struct Curl_cfilter *cf,
const char *ipmsg;
(void)data;
- ctx->sock = CURL_SOCKET_BAD;
+ DEBUGASSERT(ctx->sock == CURL_SOCKET_BAD);
+ ctx->started_at = Curl_now();
result = socket_open(data, &ctx->addr, &ctx->sock);
+ DEBUGF(LOG_CF(data, cf, "socket_open() -> %d, fd=%d", result, ctx->sock));
if(result)
goto out;
@@ -963,12 +1000,15 @@ out:
}
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)
+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
@@ -977,7 +1017,7 @@ static int do_connect(struct Curl_cfilter *cf, struct Curl_easy *data)
int rc = -1;
(void)data;
- if(cf->conn->bits.tcp_fastopen) {
+ 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,
@@ -1048,7 +1088,7 @@ static CURLcode cf_tcp_connect(struct Curl_cfilter *cf,
DEBUGF(LOG_CF(data, cf, "connect opened(%d)", (int)ctx->sock));
/* Connect TCP socket */
- rc = do_connect(cf, data);
+ 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;
@@ -1071,6 +1111,7 @@ static CURLcode cf_tcp_connect(struct Curl_cfilter *cf,
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;
@@ -1224,9 +1265,11 @@ 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.
@@ -1239,6 +1282,9 @@ static ssize_t cf_socket_send(struct Curl_cfilter *cf, struct Curl_easy *data,
}
#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,
@@ -1276,8 +1322,10 @@ static ssize_t cf_socket_send(struct Curl_cfilter *cf, struct Curl_easy *data,
*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;
}
@@ -1285,6 +1333,7 @@ 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;
@@ -1299,6 +1348,9 @@ static ssize_t cf_socket_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
}
#endif
+ fdsave = cf->conn->sock[cf->sockindex];
+ cf->conn->sock[cf->sockindex] = ctx->sock;
+
nread = sread(ctx->sock, buf, len);
if(-1 == nread) {
@@ -1326,8 +1378,14 @@ static ssize_t cf_socket_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
*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;
}
@@ -1374,6 +1432,7 @@ static void cf_socket_active(struct Curl_cfilter *cf, struct Curl_easy *data)
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;
@@ -1391,6 +1450,22 @@ static CURLcode cf_socket_cntrl(struct Curl_cfilter *cf,
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;
@@ -1434,6 +1509,33 @@ static bool cf_socket_conn_is_alive(struct Curl_cfilter *cf,
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,
@@ -1449,13 +1551,14 @@ struct Curl_cftype Curl_cft_tcp = {
cf_socket_cntrl,
cf_socket_conn_is_alive,
Curl_cf_def_conn_keep_alive,
- Curl_cf_def_query,
+ cf_socket_query,
};
CURLcode Curl_cf_tcp_create(struct Curl_cfilter **pcf,
struct Curl_easy *data,
struct connectdata *conn,
- const struct Curl_addrinfo *ai)
+ const struct Curl_addrinfo *ai,
+ int transport)
{
struct cf_socket_ctx *ctx = NULL;
struct Curl_cfilter *cf = NULL;
@@ -1463,14 +1566,13 @@ CURLcode Curl_cf_tcp_create(struct Curl_cfilter **pcf,
(void)data;
(void)conn;
+ DEBUGASSERT(transport == TRNSPRT_TCP);
ctx = calloc(sizeof(*ctx), 1);
if(!ctx) {
result = CURLE_OUT_OF_MEMORY;
goto out;
}
- ctx->transport = TRNSPRT_TCP;
- Curl_sock_assign_addr(&ctx->addr, ai, ctx->transport);
- ctx->sock = CURL_SOCKET_BAD;
+ cf_socket_ctx_init(ctx, ai, transport);
result = Curl_cf_create(&cf, &Curl_cft_tcp, ctx);
@@ -1484,6 +1586,46 @@ out:
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)
@@ -1500,17 +1642,29 @@ static CURLcode cf_udp_connect(struct Curl_cfilter *cf,
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 {
- set_local_ip(cf, data);
- *done = TRUE;
- cf->connected = TRUE;
+ DEBUGF(LOG_CF(data, cf, "cf_udp_connect(), opened socket=%d "
+ "(unconnected)", ctx->sock));
}
+ *done = TRUE;
+ cf->connected = TRUE;
}
+out:
return result;
}
@@ -1529,13 +1683,14 @@ struct Curl_cftype Curl_cft_udp = {
cf_socket_cntrl,
cf_socket_conn_is_alive,
Curl_cf_def_conn_keep_alive,
- Curl_cf_def_query,
+ cf_socket_query,
};
CURLcode Curl_cf_udp_create(struct Curl_cfilter **pcf,
struct Curl_easy *data,
struct connectdata *conn,
- const struct Curl_addrinfo *ai)
+ const struct Curl_addrinfo *ai,
+ int transport)
{
struct cf_socket_ctx *ctx = NULL;
struct Curl_cfilter *cf = NULL;
@@ -1543,14 +1698,13 @@ CURLcode Curl_cf_udp_create(struct Curl_cfilter **pcf,
(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;
}
- ctx->transport = TRNSPRT_UDP;
- Curl_sock_assign_addr(&ctx->addr, ai, ctx->transport);
- ctx->sock = CURL_SOCKET_BAD;
+ cf_socket_ctx_init(ctx, ai, transport);
result = Curl_cf_create(&cf, &Curl_cft_udp, ctx);
@@ -1580,13 +1734,14 @@ struct Curl_cftype Curl_cft_unix = {
cf_socket_cntrl,
cf_socket_conn_is_alive,
Curl_cf_def_conn_keep_alive,
- Curl_cf_def_query,
+ cf_socket_query,
};
CURLcode Curl_cf_unix_create(struct Curl_cfilter **pcf,
struct Curl_easy *data,
struct connectdata *conn,
- const struct Curl_addrinfo *ai)
+ const struct Curl_addrinfo *ai,
+ int transport)
{
struct cf_socket_ctx *ctx = NULL;
struct Curl_cfilter *cf = NULL;
@@ -1594,14 +1749,13 @@ CURLcode Curl_cf_unix_create(struct Curl_cfilter **pcf,
(void)data;
(void)conn;
+ DEBUGASSERT(transport == TRNSPRT_UNIX);
ctx = calloc(sizeof(*ctx), 1);
if(!ctx) {
result = CURLE_OUT_OF_MEMORY;
goto out;
}
- ctx->transport = TRNSPRT_UNIX;
- Curl_sock_assign_addr(&ctx->addr, ai, ctx->transport);
- ctx->sock = CURL_SOCKET_BAD;
+ cf_socket_ctx_init(ctx, ai, transport);
result = Curl_cf_create(&cf, &Curl_cft_unix, ctx);
@@ -1644,7 +1798,7 @@ struct Curl_cftype Curl_cft_tcp_accept = {
cf_socket_cntrl,
cf_socket_conn_is_alive,
Curl_cf_def_conn_keep_alive,
- Curl_cf_def_query,
+ cf_socket_query,
};
CURLcode Curl_conn_tcp_listen_set(struct Curl_easy *data,
@@ -1676,6 +1830,7 @@ CURLcode Curl_conn_tcp_listen_set(struct Curl_easy *data,
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));
@@ -1707,6 +1862,7 @@ CURLcode Curl_conn_tcp_accepted_set(struct Curl_easy *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));
@@ -1722,10 +1878,11 @@ bool Curl_cf_is_socket(struct Curl_cfilter *cf)
}
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 **premote_ip_str,
- int *premote_port)
+ 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;
@@ -1734,10 +1891,17 @@ CURLcode Curl_cf_socket_peek(struct Curl_cfilter *cf,
*psock = ctx->sock;
if(paddr)
*paddr = &ctx->addr;
- if(premote_ip_str)
- *premote_ip_str = ctx->r_ip;
- if(premote_port)
- *premote_port = ctx->r_port;
+ 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;
lib/cf-socket.h+13 -7
@@ -116,7 +116,8 @@ void Curl_sock_assign_addr(struct Curl_sockaddr_ex *dest,
CURLcode Curl_cf_tcp_create(struct Curl_cfilter **pcf,
struct Curl_easy *data,
struct connectdata *conn,
- const struct Curl_addrinfo *ai);
+ const struct Curl_addrinfo *ai,
+ int transport);
/**
* Creates a cfilter that opens a UDP socket to the given address
@@ -128,7 +129,8 @@ CURLcode Curl_cf_tcp_create(struct Curl_cfilter **pcf,
CURLcode Curl_cf_udp_create(struct Curl_cfilter **pcf,
struct Curl_easy *data,
struct connectdata *conn,
- const struct Curl_addrinfo *ai);
+ const struct Curl_addrinfo *ai,
+ int transport);
/**
* Creates a cfilter that opens a UNIX socket to the given address
@@ -140,7 +142,8 @@ CURLcode Curl_cf_udp_create(struct Curl_cfilter **pcf,
CURLcode Curl_cf_unix_create(struct Curl_cfilter **pcf,
struct Curl_easy *data,
struct connectdata *conn,
- const struct Curl_addrinfo *ai);
+ const struct Curl_addrinfo *ai,
+ int transport);
/**
* Creates a cfilter that keeps a listening socket.
@@ -168,15 +171,18 @@ bool Curl_cf_is_socket(struct Curl_cfilter *cf);
* 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 premote_ip_str pointer to hold remote addr as string or NULL
- * @param premote_port pointer to hold remote port number 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 **premote_ip_str,
- int *premote_port);
+ 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;
lib/cfilters.c+30 -1
@@ -141,7 +141,7 @@ CURLcode Curl_cf_def_conn_keep_alive(struct Curl_cfilter *cf,
CURLcode Curl_cf_def_query(struct Curl_cfilter *cf,
struct Curl_easy *data,
- int query, int *pres1, void **pres2)
+ int query, int *pres1, void *pres2)
{
return cf->next?
cf->next->cft->query(cf->next, data, query, pres1, pres2) :
@@ -370,6 +370,7 @@ CURLcode Curl_conn_connect(struct Curl_easy *data,
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();
}
}
@@ -514,6 +515,28 @@ CURLcode Curl_conn_cf_cntrl(struct Curl_cfilter *cf,
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,
@@ -585,6 +608,12 @@ void Curl_conn_ev_update_info(struct Curl_easy *data,
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];
lib/cfilters.h+31 -2
@@ -109,6 +109,8 @@ typedef CURLcode Curl_cft_conn_keep_alive(struct Curl_cfilter *cf,
#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.
@@ -124,9 +126,18 @@ typedef CURLcode Curl_cft_cntrl(struct Curl_cfilter *cf,
* - 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
@@ -134,7 +145,7 @@ typedef CURLcode Curl_cft_cntrl(struct Curl_cfilter *cf,
*/
typedef CURLcode Curl_cft_query(struct Curl_cfilter *cf,
struct Curl_easy *data,
- int query, int *pres1, void **pres2);
+ int query, int *pres1, void *pres2);
/**
* Type flags for connection filters. A filter can have none, one or
@@ -210,7 +221,7 @@ 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);
+ int query, int *pres1, void *pres2);
/**
* Create a new filter instance, unattached to the filter chain.
@@ -279,6 +290,12 @@ CURLcode Curl_conn_cf_cntrl(struct Curl_cfilter *cf,
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
@@ -333,6 +350,12 @@ void Curl_conn_close(struct Curl_easy *data, int sockindex);
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.
@@ -411,6 +434,12 @@ CURLcode Curl_conn_ev_data_pause(struct Curl_easy *data, bool do_pause);
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.
*/
lib/connect.c+135 -27
@@ -59,6 +59,7 @@
#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() */
@@ -445,6 +446,7 @@ static void baller_initiate(struct Curl_cfilter *cf,
struct Curl_easy *data,
struct eyeballer *baller)
{
+ struct cf_he_ctx *ctx = cf->ctx;
struct Curl_cfilter *cf_prev = baller->cf;
struct Curl_cfilter *wcf;
CURLcode result;
@@ -454,7 +456,8 @@ static void baller_initiate(struct Curl_cfilter *cf,
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);
+ result = baller->cf_create(&baller->cf, data, cf->conn, baller->addr,
+ ctx->transport);
if(result)
goto out;
@@ -877,7 +880,7 @@ static CURLcode cf_he_connect(struct Curl_cfilter *cf,
switch(ctx->state) {
case SCFST_INIT:
- DEBUGASSERT(CURL_SOCKET_BAD == cf->conn->sock[cf->sockindex]);
+ DEBUGASSERT(CURL_SOCKET_BAD == Curl_conn_cf_get_socket(cf, data));
DEBUGASSERT(!cf->connected);
result = start_connect(cf, data, ctx->remotehost);
if(result)
@@ -900,9 +903,7 @@ static CURLcode cf_he_connect(struct Curl_cfilter *cf,
Curl_conn_cf_cntrl(cf->next, data, TRUE,
CF_CTRL_CONN_INFO_UPDATE, 0, NULL);
- Curl_pgrsTime(data, TIMER_CONNECT); /* we're connected already */
- if(Curl_conn_is_ssl(cf->conn, FIRSTSOCKET) ||
- (cf->conn->handler->protocol & PROTO_FAMILY_SSH))
+ 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 */
@@ -950,6 +951,44 @@ static bool cf_he_data_pending(struct Curl_cfilter *cf,
return FALSE;
}
+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;
+ }
+
+ default:
+ break;
+ }
+ }
+
+ return cf->next?
+ cf->next->cft->query(cf->next, data, query, pres1, pres2) :
+ CURLE_UNKNOWN_OPTION;
+}
+
static void cf_he_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
{
struct cf_he_ctx *ctx = cf->ctx;
@@ -977,14 +1016,15 @@ struct Curl_cftype Curl_cft_happy_eyeballs = {
Curl_cf_def_cntrl,
Curl_cf_def_conn_is_alive,
Curl_cf_def_conn_keep_alive,
- Curl_cf_def_query,
+ cf_he_query,
};
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)
+ const struct Curl_dns_entry *remotehost,
+ int transport)
{
struct cf_he_ctx *ctx = NULL;
CURLcode result;
@@ -997,6 +1037,7 @@ CURLcode Curl_cf_happy_eyeballs_create(struct Curl_cfilter **pcf,
result = CURLE_OUT_OF_MEMORY;
goto out;
}
+ ctx->transport = transport;
ctx->cf_create = cf_create;
ctx->remotehost = remotehost;
@@ -1073,7 +1114,8 @@ static CURLcode cf_he_insert_after(struct Curl_cfilter *cf_at,
return CURLE_UNSUPPORTED_PROTOCOL;
}
result = Curl_cf_happy_eyeballs_create(&cf, data, cf_at->conn,
- cf_create, remotehost);
+ cf_create, remotehost,
+ transport);
if(result)
return result;
@@ -1095,6 +1137,7 @@ 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,
@@ -1118,8 +1161,7 @@ connect_sub_chain:
}
if(ctx->state < CF_SETUP_CNNCT_EYEBALLS) {
- result = cf_he_insert_after(cf, data, ctx->remotehost,
- cf->conn->transport);
+ result = cf_he_insert_after(cf, data, ctx->remotehost, ctx->transport);
if(result)
return result;
ctx->state = CF_SETUP_CNNCT_EYEBALLS;
@@ -1244,6 +1286,75 @@ struct Curl_cftype Curl_cft_setup = {
Curl_cf_def_query,
};
+static CURLcode cf_setup_create(struct Curl_cfilter **pcf,
+ struct Curl_easy *data,
+ const struct Curl_dns_entry *remotehost,
+ int transport,
+ int ssl_mode)
+{
+ struct Curl_cfilter *cf = NULL;
+ struct cf_setup_ctx *ctx;
+ CURLcode result = CURLE_OK;
+
+ (void)data;
+ ctx = calloc(sizeof(*ctx), 1);
+ if(!ctx) {
+ result = CURLE_OUT_OF_MEMORY;
+ goto out;
+ }
+ 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;
+ ctx = NULL;
+
+out:
+ *pcf = result? NULL : cf;
+ free(ctx);
+ return result;
+}
+
+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)
+{
+ 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;
+}
+
+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)
+{
+ struct Curl_cfilter *cf;
+ CURLcode result;
+
+ 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_setup(struct Curl_easy *data,
struct connectdata *conn,
int sockindex,
@@ -1251,34 +1362,31 @@ CURLcode Curl_conn_setup(struct Curl_easy *data,
int ssl_mode)
{
CURLcode result = CURLE_OK;
- struct cf_setup_ctx *ctx = NULL;
DEBUGASSERT(data);
- /* If no filter is set, we add the "default" setup connection filter.
- */
- if(!conn->cfilter[sockindex]) {
- struct Curl_cfilter *cf;
+ 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)) {
- ctx = calloc(sizeof(*ctx), 1);
- if(!ctx) {
- result = CURLE_OUT_OF_MEMORY;
+ result = Curl_cf_https_setup(data, conn, sockindex, remotehost);
+ if(result)
goto out;
- }
- ctx->state = CF_SETUP_INIT;
- ctx->remotehost = remotehost;
- ctx->ssl_mode = ssl_mode;
+ }
+#endif /* !defined(CURL_DISABLE_HTTP) && !defined(USE_HYPER) */
- result = Curl_cf_create(&cf, &Curl_cft_setup, ctx);
+ /* 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;
- ctx = NULL;
- Curl_conn_cf_add(data, conn, sockindex, cf);
}
DEBUGASSERT(conn->cfilter[sockindex]);
-
out:
- free(ctx);
return result;
}
lib/connect.h+20 -6
@@ -101,7 +101,8 @@ void Curl_conncontrol(struct connectdata *conn,
typedef CURLcode cf_ip_connect_create(struct Curl_cfilter **pcf,
struct Curl_easy *data,
struct connectdata *conn,
- const struct Curl_addrinfo *ai);
+ const struct Curl_addrinfo *ai,
+ int transport);
/**
* Create a happy eyeball connection filter that uses the, once resolved,
@@ -118,13 +119,26 @@ 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);
+ 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`, invoking
- * the instance `setup(remotehost)` methods. If no filter chain is
- * installed yet, inspects the configuration in `data` to install a
- * suitable filter chain.
+ * 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,
lib/curl_log.c+4 -0
@@ -38,6 +38,7 @@
#include "connect.h"
#include "http2.h"
#include "http_proxy.h"
+#include "cf-http.h"
#include "socks.h"
#include "strtok.h"
#include "vtls/vtls.h"
@@ -166,6 +167,9 @@ static struct Curl_cftype *cf_types[] = {
#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,
};
lib/http.c+28 -65
@@ -219,38 +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
-
- return CURLE_OK;
-#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)
{
@@ -266,13 +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);
- }
return CURLE_OK;
}
@@ -1320,7 +1291,7 @@ CURLcode Curl_buffer_send(struct dynbuf *in,
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 */
@@ -1571,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);
}
@@ -3008,33 +2979,25 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done)
the rest of the request in the PERFORM phase. */
*done = TRUE;
- if(Curl_conn_is_http3(data, conn, FIRSTSOCKET)
- || Curl_conn_is_http2(data, conn, FIRSTSOCKET)
- || conn->httpversion == 20 /* like to get rid of this */) {
- /* all fine, we are set */
- }
- else { /* undecided */
- switch(conn->alpn) {
- case CURL_HTTP_VERSION_2:
- result = Curl_http2_switch(data, conn, FIRSTSOCKET, NULL, 0);
+ 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;
-
- 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, NULL, 0);
- if(result)
- return result;
- }
- break;
}
+ break;
}
http = data->req.p.http;
@@ -3936,8 +3899,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_switch(data, conn, FIRSTSOCKET,
- k->str, *nread);
+ result = Curl_http2_upgrade(data, conn, FIRSTSOCKET,
+ k->str, *nread);
if(result)
return result;
*nread = 0;
lib/http.h+6 -2
@@ -248,7 +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 HTTP2 stream close */
+ bool closed; /* TRUE on stream close */
+ bool reset; /* TRUE on stream reset */
#endif
#ifdef ENABLE_QUIC
@@ -274,7 +275,6 @@ struct HTTP {
#else /* !_WIN32 */
pthread_mutex_t recv_lock;
#endif /* _WIN32 */
-
/* Receive Buffer (Headers and Data) */
uint8_t* recv_buf;
size_t recv_buf_alloc;
@@ -288,6 +288,10 @@ struct HTTP {
/* General Receive Error */
CURLcode recv_error;
#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 */
+#endif /* USE_QUICHE */
};
CURLcode Curl_http_size(struct Curl_easy *data);
lib/http2.c+427 -273
@@ -65,14 +65,6 @@
#define HTTP2_HUGE_WINDOW_SIZE (32 * 1024 * 1024) /* 32 MB */
-#define DEBUG_HTTP2
-#ifdef DEBUG_HTTP2
-#define H2BUGF(x) x
-#else
-#define H2BUGF(x) do { } while(0)
-#endif
-
-
#define H2_SETTINGS_IV_LEN 3
#define H2_BINSETTINGS_LEN 80
@@ -103,16 +95,12 @@ static size_t populate_binsettings(uint8_t *binsettings,
iv, ivlen);
}
-struct h2_cf_ctx {
+struct cf_h2_ctx {
nghttp2_session *h2;
uint32_t max_concurrent_streams;
bool enable_push;
-
- /* 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;
+ /* 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 */
@@ -128,19 +116,27 @@ struct h2_cf_ctx {
size_t drain_total; /* sum of all stream's UrlState.drain */
};
-static void h2_cf_ctx_clear(struct h2_cf_ctx *ctx)
+/* 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);
memset(ctx, 0, sizeof(*ctx));
+ ctx->call_data = save;
}
-static void h2_cf_ctx_free(struct h2_cf_ctx *ctx)
+static void cf_h2_ctx_free(struct cf_h2_ctx *ctx)
{
if(ctx) {
- h2_cf_ctx_clear(ctx);
+ cf_h2_ctx_clear(ctx);
free(ctx);
}
}
@@ -148,7 +144,7 @@ static void h2_cf_ctx_free(struct h2_cf_ctx *ctx)
static int h2_client_new(struct Curl_cfilter *cf,
nghttp2_session_callbacks *cbs)
{
- struct h2_cf_ctx *ctx = cf->ctx;
+ struct cf_h2_ctx *ctx = cf->ctx;
#if NGHTTP2_VERSION_NUM < 0x013200
/* before 1.50.0 */
@@ -233,11 +229,11 @@ static CURLcode http2_data_setup(struct Curl_cfilter *cf,
/*
* Initialize the cfilter context
*/
-static CURLcode h2_cf_ctx_init(struct Curl_cfilter *cf,
+static CURLcode cf_h2_ctx_init(struct Curl_cfilter *cf,
struct Curl_easy *data,
bool via_h1_upgrade)
{
- struct h2_cf_ctx *ctx = cf->ctx;
+ struct cf_h2_ctx *ctx = cf->ctx;
struct HTTP *stream = data->req.p.http;
CURLcode result = CURLE_OUT_OF_MEMORY;
int rc;
@@ -371,14 +367,14 @@ static void http2_stream_free(struct HTTP *stream)
*/
static bool http2_connisdead(struct Curl_cfilter *cf, struct Curl_easy *data)
{
- struct h2_cf_ctx *ctx = cf->ctx;
+ struct cf_h2_ctx *ctx = cf->ctx;
int sval;
bool dead = TRUE;
if(!cf->next || !cf->next->cft->is_alive(cf->next, data))
return TRUE;
- sval = SOCKET_READABLE(cf->conn->sock[cf->sockindex], 0);
+ sval = SOCKET_READABLE(Curl_conn_cf_get_socket(cf, data), 0);
if(sval == 0) {
/* timeout */
dead = FALSE;
@@ -399,9 +395,8 @@ static bool http2_connisdead(struct Curl_cfilter *cf, struct Curl_easy *data)
ctx->inbuf, H2_BUFSIZE, &result);
dead = FALSE;
if(nread != -1) {
- H2BUGF(infof(data,
- "%d bytes stray data read before trying h2 connection",
- (int)nread));
+ 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)
@@ -417,28 +412,10 @@ static bool http2_connisdead(struct Curl_cfilter *cf, struct Curl_easy *data)
return dead;
}
-/*
- * Set the transfer that is currently using this HTTP/2 connection.
- */
-static void set_transfer(struct h2_cf_ctx *ctx,
- struct Curl_easy *data)
-{
- ctx->trnsfr = data;
-}
-
-/*
- * Get the transfer that is currently using this HTTP/2 connection.
- */
-static struct Curl_easy *get_transfer(struct h2_cf_ctx *ctx)
-{
- DEBUGASSERT(ctx && ctx->trnsfr);
- return ctx->trnsfr;
-}
-
static CURLcode http2_send_ping(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
- struct h2_cf_ctx *ctx = cf->ctx;
+ struct cf_h2_ctx *ctx = cf->ctx;
int rc;
rc = nghttp2_submit_ping(ctx->h2, 0, ZERO_NULL);
@@ -448,7 +425,6 @@ static CURLcode http2_send_ping(struct Curl_cfilter *cf,
return CURLE_HTTP2;
}
- set_transfer(ctx, data); /* set the transfer */
rc = nghttp2_session_send(ctx->h2);
if(rc) {
failf(data, "nghttp2_session_send() failed: %s(%d)",
@@ -477,13 +453,13 @@ static ssize_t send_callback(nghttp2_session *h2,
void *userp)
{
struct Curl_cfilter *cf = userp;
- struct h2_cf_ctx *ctx = cf->ctx;
- struct Curl_easy *data = get_transfer(ctx);
+ struct Curl_easy *data = CF_DATA_CURRENT(cf);
ssize_t written;
CURLcode result = CURLE_OK;
(void)h2;
(void)flags;
+ DEBUGASSERT(data);
written = Curl_conn_cf_send(cf->next, data, buf, blen, &result);
if(result == CURLE_AGAIN) {
@@ -562,7 +538,7 @@ char *curl_pushheader_byname(struct curl_pushheaders *h, const char *header)
static void drained_transfer(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
- struct h2_cf_ctx *ctx = cf->ctx;
+ struct cf_h2_ctx *ctx = cf->ctx;
DEBUGASSERT(ctx->drain_total >= data->state.drain);
ctx->drain_total -= data->state.drain;
@@ -575,7 +551,7 @@ static void drained_transfer(struct Curl_cfilter *cf,
static void drain_this(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
- struct h2_cf_ctx *ctx = cf->ctx;
+ struct cf_h2_ctx *ctx = cf->ctx;
data->state.drain++;
ctx->drain_total++;
@@ -659,11 +635,11 @@ static int push_promise(struct Curl_cfilter *cf,
struct Curl_easy *data,
const nghttp2_push_promise *frame)
{
- struct h2_cf_ctx *ctx = cf->ctx;
+ 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;
@@ -681,7 +657,7 @@ static int push_promise(struct Curl_cfilter *cf,
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) {
@@ -751,7 +727,7 @@ static int push_promise(struct Curl_cfilter *cf,
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:
@@ -762,15 +738,16 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame,
void *userp)
{
struct Curl_cfilter *cf = userp;
- struct h2_cf_ctx *ctx = cf->ctx;
+ struct cf_h2_ctx *ctx = cf->ctx;
struct Curl_easy *data_s = NULL;
struct HTTP *stream = NULL;
- struct Curl_easy *data = get_transfer(ctx);
+ 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 */
DEBUGASSERT(data);
@@ -811,21 +788,21 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame,
}
data_s = nghttp2_session_get_stream_user_data(session, stream_id);
if(!data_s) {
- DEBUGF(LOG_CF(data, cf, "No Curl_easy associated with stream: %u",
+ DEBUGF(LOG_CF(data, cf, "[h2sid=%u] No Curl_easy associated",
stream_id));
return 0;
}
stream = data_s->req.p.http;
if(!stream) {
- DEBUGF(LOG_CF(data_s, cf, "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;
}
switch(frame->hd.type) {
case NGHTTP2_DATA:
/* If body started on this stream, then receiving DATA is illegal. */
- DEBUGF(LOG_CF(data_s, cf, "recv frame DATA stream %u", stream_id));
+ 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);
@@ -836,7 +813,7 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame,
}
break;
case NGHTTP2_HEADERS:
- DEBUGF(LOG_CF(data_s, cf, "recv frame HEADERS stream %u", stream_id));
+ 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. */
@@ -870,19 +847,17 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame,
stream->nread_header_recvbuf += ncopy;
DEBUGASSERT(stream->mem);
- DEBUGF(LOG_CF(data_s, cf, "%zu header bytes, stream %u at %p",
- ncopy, stream_id, (void *)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(cf, data_s);
- /* if we receive data for another handle, wake that up */
- if(get_transfer(ctx) != data_s)
- Curl_expire(data_s, 0, EXPIRE_RUN_NOW);
+ Curl_expire(data_s, 0, EXPIRE_RUN_NOW);
break;
case NGHTTP2_PUSH_PROMISE:
- DEBUGF(LOG_CF(data_s, cf, "recv frame PUSH_PROMISE stream %u", stream_id));
+ 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;
@@ -898,9 +873,13 @@ static int on_frame_recv(nghttp2_session *session, const nghttp2_frame *frame,
}
}
break;
+ case NGHTTP2_RST_STREAM:
+ DEBUGF(LOG_CF(data_s, cf, "[h2sid=%u] recv RST", stream_id));
+ stream->reset = TRUE;
+ break;
default:
- DEBUGF(LOG_CF(data_s, cf, "recv frame %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;
@@ -911,13 +890,14 @@ static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags,
const uint8_t *mem, size_t len, void *userp)
{
struct Curl_cfilter *cf = userp;
- struct h2_cf_ctx *ctx = cf->ctx;
+ struct cf_h2_ctx *ctx = cf->ctx;
struct HTTP *stream;
struct Curl_easy *data_s;
size_t nread;
(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);
@@ -925,8 +905,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;
}
@@ -940,36 +920,38 @@ static int on_data_chunk_recv(nghttp2_session *session, uint8_t flags,
stream->len -= nread;
stream->memlen += nread;
- drain_this(cf, data_s);
-
/* if we receive data for another handle, wake that up */
- if(get_transfer(ctx) != 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));
+ 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(ctx) != data_s) {
+ 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;
}
@@ -978,7 +960,7 @@ static int on_stream_close(nghttp2_session *session, int32_t stream_id,
uint32_t error_code, void *userp)
{
struct Curl_cfilter *cf = userp;
- struct h2_cf_ctx *ctx = cf->ctx;
+ struct cf_h2_ctx *ctx = cf->ctx;
struct Curl_easy *data_s;
struct HTTP *stream;
int rv;
@@ -994,15 +976,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;
- drain_this(cf, data_s);
- 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 */
@@ -1013,11 +997,11 @@ static int on_stream_close(nghttp2_session *session, int32_t stream_id,
DEBUGASSERT(0);
}
if(stream_id == ctx->pause_stream_id) {
- H2BUGF(infof(data_s, "Stopped the pause stream"));
+ 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;
}
@@ -1025,16 +1009,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;
@@ -1083,7 +1068,6 @@ static int on_header(nghttp2_session *session, const nghttp2_frame *frame,
void *userp)
{
struct Curl_cfilter *cf = userp;
- struct h2_cf_ctx *ctx = cf->ctx;
struct HTTP *stream;
struct Curl_easy *data_s;
int32_t stream_id = frame->hd.stream_id;
@@ -1169,8 +1153,10 @@ 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", (int)namelen, name,
- (int)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", (int)namelen, name,
(int)valuelen, value);
@@ -1204,11 +1190,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(ctx) != 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;
}
@@ -1228,11 +1214,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(ctx) != 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", (int)namelen, name,
- (int)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 */
}
@@ -1244,12 +1232,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 */
@@ -1280,9 +1269,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;
}
@@ -1304,7 +1292,7 @@ static int error_callback(nghttp2_session *session,
static void http2_data_done(struct Curl_cfilter *cf,
struct Curl_easy *data, bool premature)
{
- struct h2_cf_ctx *ctx = cf->ctx;
+ 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
@@ -1324,15 +1312,15 @@ static void http2_data_done(struct Curl_cfilter *cf,
return;
/* do this before the reset handling, as that might clear ->stream_id */
- if(stream->stream_id == ctx->pause_stream_id) {
- H2BUGF(infof(data, "DONE the pause stream (%u)", stream->stream_id));
+ 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 || (!stream->closed && stream->stream_id)) {
/* RST_STREAM */
- set_transfer(ctx, data); /* set the transfer */
- H2BUGF(infof(data, "RST stream %u", stream->stream_id));
+ 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);
@@ -1342,7 +1330,7 @@ static void http2_data_done(struct Curl_cfilter *cf,
drained_transfer(cf, data);
/* -1 means unassigned and 0 means cleared */
- if(stream->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) {
@@ -1350,8 +1338,6 @@ static void http2_data_done(struct Curl_cfilter *cf,
stream->stream_id);
DEBUGASSERT(0);
}
- set_transfer(ctx, NULL);
- stream->stream_id = 0;
}
}
@@ -1397,7 +1383,7 @@ CURLcode Curl_http2_request_upgrade(struct dynbuf *req,
/*
* Returns nonzero if current HTTP/2 session should be closed.
*/
-static int should_close_session(struct h2_cf_ctx *ctx)
+static int should_close_session(struct cf_h2_ctx *ctx)
{
return ctx->drain_total == 0 && !nghttp2_session_want_read(ctx->h2) &&
!nghttp2_session_want_write(ctx->h2);
@@ -1413,7 +1399,7 @@ static int h2_process_pending_input(struct Curl_cfilter *cf,
struct Curl_easy *data,
CURLcode *err)
{
- struct h2_cf_ctx *ctx = cf->ctx;
+ struct cf_h2_ctx *ctx = cf->ctx;
ssize_t nread;
char *inbuf;
ssize_t rv;
@@ -1421,7 +1407,6 @@ static int h2_process_pending_input(struct Curl_cfilter *cf,
nread = ctx->inbuflen - ctx->nread_inbuf;
inbuf = ctx->inbuf + ctx->nread_inbuf;
- set_transfer(ctx, data); /* set the transfer */
rv = nghttp2_session_mem_recv(ctx->h2, (const uint8_t *)inbuf, nread);
if(rv < 0) {
failf(data,
@@ -1432,17 +1417,14 @@ static int h2_process_pending_input(struct Curl_cfilter *cf,
}
if(nread == rv) {
- H2BUGF(infof(data,
- "h2_process_pending_input: All data in connection buffer "
- "processed"));
+ DEBUGF(LOG_CF(data, cf, "all data in connection buffer processed"));
ctx->inbuflen = 0;
ctx->nread_inbuf = 0;
}
else {
ctx->nread_inbuf += rv;
- H2BUGF(infof(data,
- "h2_process_pending_input: %zu bytes left in connection "
- "buffer",
+ DEBUGF(LOG_CF(data, cf, "h2_process_pending_input: %zu bytes left "
+ "in connection buffer",
ctx->inbuflen - ctx->nread_inbuf));
}
@@ -1462,9 +1444,11 @@ static int h2_process_pending_input(struct Curl_cfilter *cf,
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 */
@@ -1479,7 +1463,7 @@ static int h2_process_pending_input(struct Curl_cfilter *cf,
static CURLcode http2_data_done_send(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
- struct h2_cf_ctx *ctx = cf->ctx;
+ struct cf_h2_ctx *ctx = cf->ctx;
CURLcode result = CURLE_OK;
struct HTTP *stream = data->req.p.http;
@@ -1501,7 +1485,7 @@ static CURLcode http2_data_done_send(struct Curl_cfilter *cf,
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(cf, data);
@@ -1522,7 +1506,7 @@ static ssize_t http2_handle_stream_close(struct Curl_cfilter *cf,
struct Curl_easy *data,
struct HTTP *stream, CURLcode *err)
{
- struct h2_cf_ctx *ctx = cf->ctx;
+ struct cf_h2_ctx *ctx = cf->ctx;
if(ctx->pause_stream_id == stream->stream_id) {
ctx->pause_stream_id = 0;
@@ -1541,8 +1525,8 @@ static ssize_t http2_handle_stream_close(struct Curl_cfilter *cf,
/* 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));
+ 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 */
@@ -1590,7 +1574,7 @@ static ssize_t http2_handle_stream_close(struct Curl_cfilter *cf,
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;
}
@@ -1634,10 +1618,9 @@ static void h2_pri_spec(struct Curl_easy *data,
*/
static int h2_session_send(struct Curl_cfilter *cf, struct Curl_easy *data)
{
- struct h2_cf_ctx *ctx = cf->ctx;
+ struct cf_h2_ctx *ctx = cf->ctx;
struct HTTP *stream = data->req.p.http;
- set_transfer(ctx, data);
if((sweight_wanted(data) != sweight_in_effect(data)) ||
(data->set.priority.exclusive != data->state.priority.exclusive) ||
(data->set.priority.parent != data->state.priority.parent) ) {
@@ -1646,8 +1629,8 @@ static int h2_session_send(struct Curl_cfilter *cf, struct Curl_easy *data)
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(ctx->h2, NGHTTP2_FLAG_NONE,
stream->stream_id, &pri_spec);
@@ -1658,22 +1641,28 @@ static int h2_session_send(struct Curl_cfilter *cf, struct Curl_easy *data)
return nghttp2_session_send(ctx->h2);
}
-static ssize_t h2_cf_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
+static ssize_t cf_h2_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
char *buf, size_t len, CURLcode *err)
{
- struct h2_cf_ctx *ctx = cf->ctx;
+ struct cf_h2_ctx *ctx = cf->ctx;
struct HTTP *stream = data->req.p.http;
- ssize_t nread;
+ ssize_t nread = -1;
+ struct cf_call_data save;
+
+ CF_DATA_SAVE(save, cf, data);
if(should_close_session(ctx)) {
- H2BUGF(infof(data, "http2_recv: nothing to do in this session"));
+ 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
@@ -1696,22 +1685,23 @@ static ssize_t h2_cf_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
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(ctx->h2),
- nghttp2_session_get_stream_local_window_size(ctx->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, buf));
+ 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 */
@@ -1719,12 +1709,14 @@ static ssize_t h2_cf_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
stream->len = len - stream->memlen;
stream->mem = buf;
}
+
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). */
ctx->pause_stream_id = 0;
if(h2_process_pending_input(cf, data, err) != 0) {
- return -1;
+ nread = -1;
+ goto out;
}
}
}
@@ -1737,7 +1729,7 @@ static ssize_t h2_cf_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
stream->pauselen -= nread;
if(stream->pauselen == 0) {
- H2BUGF(infof(data, "Unpaused by stream %u", stream->stream_id));
+ DEBUGF(LOG_CF(data, cf, "[h2sid=%u] Unpaused", stream->stream_id));
DEBUGASSERT(ctx->pause_stream_id == stream->stream_id);
ctx->pause_stream_id = 0;
@@ -1752,12 +1744,13 @@ static ssize_t h2_cf_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
Without this, on_stream_close callback will not be called,
and stream could be hanged. */
if(h2_process_pending_input(cf, data, err) != 0) {
- return -1;
+ nread = -1;
+ goto out;
}
}
- 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(ctx->pause_stream_id) {
/* If a stream paused nghttp2_session_mem_recv previously, and has
@@ -1768,32 +1761,54 @@ static ssize_t h2_cf_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
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, ctx->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 */
+ /* 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(ctx->inbuflen == 0) {
+ 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;
+ }
+
+ while(stream->memlen == 0 /* have no data for this stream */
+ && !ctx->pause_stream_id /* we are not paused either */
+ && ctx->inbuflen == 0) { /* and out inpput buffer is emtpy */
/* 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(cf, 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;
}
else if(nread == 0) {
if(!stream->closed) {
@@ -1803,62 +1818,74 @@ static ssize_t h2_cf_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
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, "http2_recv: recvd %zd bytes", nread));
+ DEBUGF(LOG_CF(data, cf, "read %zd from connection", nread));
ctx->inbuflen = nread;
DEBUGASSERT(ctx->nread_inbuf == 0);
- }
- else {
- nread = ctx->inbuflen - ctx->nread_inbuf;
- H2BUGF(infof(data, "Use data left in connection buffer, nread=%zd",
- nread));
+ if(h2_process_pending_input(cf, data, err))
+ return -1;
}
- if(h2_process_pending_input(cf, data, err))
- return -1;
- if(Curl_conn_cf_data_pending(cf->next, data)) {
- H2BUGF(infof(data, "conn has pending data, set drain"));
- drain_this(cf, data);
- }
}
+
if(stream->memlen) {
ssize_t retlen = stream->memlen;
- H2BUGF(infof(data, "http2_recv: returns %zd for stream %u",
- retlen, stream->stream_id));
+ DEBUGF(LOG_CF(data, cf, "[h2sid=%u] recv: returns %zd",
+ stream->stream_id, retlen));
stream->memlen = 0;
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(cf, data);
+ 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) {
/* 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);
- return retlen;
+ nread = retlen;
+ goto out;
}
+
if(stream->closed)
return http2_handle_stream_close(cf, data, stream, err);
+
+ 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);
+ }
*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 h2_cf_send(struct Curl_cfilter *cf, struct Curl_easy *data,
+static ssize_t cf_h2_send(struct Curl_cfilter *cf, struct Curl_easy *data,
const void *buf, size_t len, CURLcode *err)
{
/*
@@ -1866,7 +1893,7 @@ static ssize_t h2_cf_send(struct Curl_cfilter *cf, struct Curl_easy *data,
* used to send request body. It would be nice to add dedicated function for
* request.
*/
- struct h2_cf_ctx *ctx = cf->ctx;
+ struct cf_h2_ctx *ctx = cf->ctx;
int rv;
struct HTTP *stream = data->req.p.http;
nghttp2_nv *nva = NULL;
@@ -1876,17 +1903,21 @@ static ssize_t h2_cf_send(struct Curl_cfilter *cf, struct Curl_easy *data,
nghttp2_priority_spec pri_spec;
CURLcode result;
struct h2h3req *hreq;
+ struct cf_call_data save;
- 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(cf, 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 */
@@ -1895,12 +1926,14 @@ static ssize_t h2_cf_send(struct Curl_cfilter *cf, struct Curl_easy *data,
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(cf, data);
if(nghttp2_is_fatal(rv)) {
*err = CURLE_SEND_ERROR;
- return -1;
+ len = -1;
+ goto out;
}
len -= stream->upload_len;
@@ -1910,9 +1943,10 @@ static ssize_t h2_cf_send(struct Curl_cfilter *cf, struct Curl_easy *data,
stream->upload_len = 0;
if(should_close_session(ctx)) {
- H2BUGF(infof(data, "http2_send: nothing to do in this session"));
+ 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) {
@@ -1936,13 +1970,14 @@ static ssize_t h2_cf_send(struct Curl_cfilter *cf, struct Curl_easy *data,
infof(data, "http2_send returns %zu for stream %u", len,
stream->stream_id);
#endif
- return len;
+ goto out;
}
result = Curl_pseudo_headers(data, buf, len, NULL, &hreq);
if(result) {
*err = result;
- return -1;
+ len = -1;
+ goto out;
}
nheader = hreq->entries;
@@ -1950,7 +1985,8 @@ static ssize_t h2_cf_send(struct Curl_cfilter *cf, struct Curl_easy *data,
if(!nva) {
Curl_pseudo_free(hreq);
*err = CURLE_OUT_OF_MEMORY;
- return -1;
+ len = -1;
+ goto out;
}
else {
unsigned int i;
@@ -1966,8 +2002,8 @@ static ssize_t h2_cf_send(struct Curl_cfilter *cf, struct Curl_easy *data,
h2_pri_spec(data, &pri_spec);
- H2BUGF(infof(data, "http2_send request allowed %d (easy handle %p)",
- nghttp2_session_check_request_allowed(ctx->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:
@@ -1993,11 +2029,11 @@ static ssize_t h2_cf_send(struct Curl_cfilter *cf, struct Curl_easy *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)",
@@ -2006,18 +2042,19 @@ static ssize_t h2_cf_send(struct Curl_cfilter *cf, struct Curl_easy *data,
rv = h2_session_send(cf, data);
if(rv) {
- H2BUGF(infof(data,
- "http2_send() nghttp2_session_send error (%s)%d",
- nghttp2_strerror(rv), rv));
+ DEBUGF(LOG_CF(data, cf, "send: nghttp2_session_send error (%s)%d",
+ nghttp2_strerror(rv), rv));
*err = CURLE_SEND_ERROR;
- return -1;
+ len = -1;
+ goto out;
}
if(should_close_session(ctx)) {
- H2BUGF(infof(data, "http2_send: nothing to do in this session"));
+ 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
@@ -2029,19 +2066,23 @@ static ssize_t h2_cf_send(struct Curl_cfilter *cf, struct Curl_easy *data,
transmission from deferred state. */
nghttp2_session_resume_data(ctx->h2, stream->stream_id);
+out:
+ CF_DATA_RESTORE(cf, save);
return len;
}
-static int h2_cf_get_select_socks(struct Curl_cfilter *cf,
+static int cf_h2_get_select_socks(struct Curl_cfilter *cf,
struct Curl_easy *data,
curl_socket_t *sock)
{
- struct h2_cf_ctx *ctx = cf->ctx;
+ 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;
- sock[0] = cf->conn->sock[cf->sockindex];
+ CF_DATA_SAVE(save, cf, data);
+ sock[0] = Curl_conn_cf_get_socket(cf, data);
if(!(k->keepon & KEEP_RECV_PAUSE))
/* Unless paused - in an HTTP/2 connection we can basically always get a
@@ -2057,16 +2098,18 @@ static int h2_cf_get_select_socks(struct Curl_cfilter *cf,
stream->stream_id)))
bitmap |= GETSOCK_WRITESOCK(0);
+ CF_DATA_RESTORE(cf, save);
return bitmap;
}
-static CURLcode h2_cf_connect(struct Curl_cfilter *cf,
+static CURLcode cf_h2_connect(struct Curl_cfilter *cf,
struct Curl_easy *data,
bool blocking, bool *done)
{
- struct h2_cf_ctx *ctx = cf->ctx;
+ struct cf_h2_ctx *ctx = cf->ctx;
CURLcode result = CURLE_OK;
+ struct cf_call_data save;
if(cf->connected) {
*done = TRUE;
@@ -2081,8 +2124,10 @@ static CURLcode h2_cf_connect(struct Curl_cfilter *cf,
}
*done = FALSE;
+
+ CF_DATA_SAVE(save, cf, data);
if(!ctx->h2) {
- result = h2_cf_ctx_init(cf, data, FALSE);
+ result = cf_h2_ctx_init(cf, data, FALSE);
if(result)
goto out;
}
@@ -2097,27 +2142,30 @@ static CURLcode h2_cf_connect(struct Curl_cfilter *cf,
result = CURLE_OK;
out:
+ CF_DATA_RESTORE(cf, save);
return result;
}
-static void h2_cf_close(struct Curl_cfilter *cf, struct Curl_easy *data)
+static void cf_h2_close(struct Curl_cfilter *cf, struct Curl_easy *data)
{
- struct h2_cf_ctx *ctx = cf->ctx;
+ struct cf_h2_ctx *ctx = cf->ctx;
- (void)data;
if(ctx) {
- /* GOAWAY? */
- h2_cf_ctx_clear(ctx);
+ struct cf_call_data save;
+
+ CF_DATA_SAVE(save, cf, data);
+ cf_h2_ctx_clear(ctx);
+ CF_DATA_RESTORE(cf, save);
}
}
-static void h2_cf_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
+static void cf_h2_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
{
- struct h2_cf_ctx *ctx = cf->ctx;
+ struct cf_h2_ctx *ctx = cf->ctx;
(void)data;
if(ctx) {
- h2_cf_ctx_free(ctx);
+ cf_h2_ctx_free(ctx);
cf->ctx = NULL;
}
}
@@ -2126,7 +2174,7 @@ static CURLcode http2_data_pause(struct Curl_cfilter *cf,
struct Curl_easy *data,
bool pause)
{
- struct h2_cf_ctx *ctx = cf->ctx;
+ struct cf_h2_ctx *ctx = cf->ctx;
DEBUGASSERT(data);
#ifdef NGHTTP2_HAS_SET_LOCAL_WINDOW_SIZE
@@ -2166,13 +2214,16 @@ static CURLcode http2_data_pause(struct Curl_cfilter *cf,
return CURLE_OK;
}
-static CURLcode h2_cf_cntrl(struct Curl_cfilter *cf,
+static CURLcode cf_h2_cntrl(struct Curl_cfilter *cf,
struct Curl_easy *data,
int event, int arg1, void *arg2)
{
CURLcode result = CURLE_OK;
+ struct cf_call_data save;
(void)arg2;
+
+ CF_DATA_SAVE(save, cf, data);
switch(event) {
case CF_CTRL_DATA_SETUP: {
result = http2_data_setup(cf, data);
@@ -2193,41 +2244,57 @@ static CURLcode h2_cf_cntrl(struct Curl_cfilter *cf,
default:
break;
}
+ CF_DATA_RESTORE(cf, save);
return result;
}
-static bool h2_cf_data_pending(struct Curl_cfilter *cf,
+static bool cf_h2_data_pending(struct Curl_cfilter *cf,
const struct Curl_easy *data)
{
- struct h2_cf_ctx *ctx = cf->ctx;
+ 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;
}
-static bool h2_cf_is_alive(struct Curl_cfilter *cf,
+static bool cf_h2_is_alive(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
- struct h2_cf_ctx *ctx = cf->ctx;
- return (ctx && ctx->h2 && !http2_connisdead(cf, 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 h2_cf_keep_alive(struct Curl_cfilter *cf,
+static CURLcode cf_h2_keep_alive(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
- return http2_send_ping(cf, 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 h2_cf_query(struct Curl_cfilter *cf,
+static CURLcode cf_h2_query(struct Curl_cfilter *cf,
struct Curl_easy *data,
- int query, int *pres1, void **pres2)
+ int query, int *pres1, void *pres2)
{
- struct h2_cf_ctx *ctx = cf->ctx;
+ 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);
@@ -2236,6 +2303,7 @@ static CURLcode h2_cf_query(struct Curl_cfilter *cf,
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;
@@ -2246,21 +2314,21 @@ static CURLcode h2_cf_query(struct Curl_cfilter *cf,
}
struct Curl_cftype Curl_cft_nghttp2 = {
- "NGHTTP2",
+ "HTTP/2",
CF_TYPE_MULTIPLEX,
CURL_LOG_DEFAULT,
- h2_cf_destroy,
- h2_cf_connect,
- h2_cf_close,
+ cf_h2_destroy,
+ cf_h2_connect,
+ cf_h2_close,
Curl_cf_def_get_host,
- h2_cf_get_select_socks,
- h2_cf_data_pending,
- h2_cf_send,
- h2_cf_recv,
- h2_cf_cntrl,
- h2_cf_is_alive,
- h2_cf_keep_alive,
- h2_cf_query,
+ 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,
@@ -2269,7 +2337,7 @@ static CURLcode http2_cfilter_add(struct Curl_cfilter **pcf,
int sockindex)
{
struct Curl_cfilter *cf = NULL;
- struct h2_cf_ctx *ctx;
+ struct cf_h2_ctx *ctx;
CURLcode result = CURLE_OUT_OF_MEMORY;
DEBUGASSERT(data->conn);
@@ -2286,17 +2354,38 @@ static CURLcode http2_cfilter_add(struct Curl_cfilter **pcf,
out:
if(result)
- h2_cf_ctx_free(ctx);
+ cf_h2_ctx_free(ctx);
*pcf = result? NULL : cf;
return result;
}
-bool Curl_conn_is_http2(const struct Curl_easy *data,
- const struct connectdata *conn,
- int sockindex)
+static CURLcode http2_cfilter_insert_after(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
{
- struct Curl_cfilter *cf = conn? conn->cfilter[sockindex] : NULL;
+ struct Curl_cfilter *cf_h2 = NULL;
+ struct cf_h2_ctx *ctx;
+ CURLcode result = CURLE_OUT_OF_MEMORY;
+
+ (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)
@@ -2307,6 +2396,13 @@ bool Curl_conn_is_http2(const struct Curl_easy *data,
return FALSE;
}
+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;
+}
+
bool Curl_http2_may_switch(struct Curl_easy *data,
struct connectdata *conn,
int sockindex)
@@ -2327,16 +2423,74 @@ bool Curl_http2_may_switch(struct Curl_easy *data,
}
CURLcode Curl_http2_switch(struct Curl_easy *data,
- struct connectdata *conn, int sockindex,
- const char *mem, size_t nread)
+ struct connectdata *conn, int sockindex)
{
struct Curl_cfilter *cf;
- struct h2_cf_ctx *ctx;
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;
+
+ 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;
+}
+
+CURLcode Curl_http2_switch_at(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+ 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;
+}
+
+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;
@@ -2344,7 +2498,7 @@ CURLcode Curl_http2_switch(struct Curl_easy *data,
DEBUGASSERT(cf->cft == &Curl_cft_nghttp2);
ctx = cf->ctx;
- result = h2_cf_ctx_init(cf, data, (data->req.upgr101 == UPGR101_RECEIVED));
+ result = cf_h2_ctx_init(cf, data, TRUE);
if(result)
return result;
lib/http2.h+11 -3
@@ -49,24 +49,32 @@ 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,
- const char *ptr, size_t nread);
+ 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_switch(a,b,c,d,e) CURLE_UNSUPPORTED_PROTOCOL
+#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
lib/http_proxy.c+11 -13
@@ -267,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;
@@ -476,7 +477,7 @@ static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf,
{
CURLcode result = CURLE_OK;
struct SingleRequest *k = &data->req;
- curl_socket_t tunnelsocket = cf->conn->sock[ts->sockindex];
+ curl_socket_t tunnelsocket = Curl_conn_cf_get_socket(cf, data);
char *linep;
size_t perline;
int error;
@@ -665,12 +666,13 @@ static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf,
#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;
@@ -971,7 +973,7 @@ static CURLcode CONNECT(struct Curl_cfilter *cf,
case TUNNEL_INIT:
/* Prepare the CONNECT request and make a first attempt to send. */
DEBUGF(LOG_CF(data, cf, "CONNECT start"));
- result = start_CONNECT(data, cf->conn, ts);
+ result = start_CONNECT(cf, data, ts);
if(result)
goto out;
tunnel_go_state(cf, ts, TUNNEL_CONNECT, data);
@@ -1125,15 +1127,13 @@ 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
@@ -1347,15 +1347,13 @@ static int cf_haproxy_get_select_socks(struct Curl_cfilter *cf,
struct Curl_easy *data,
curl_socket_t *socks)
{
- 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 sending. */
- socks[0] = conn->sock[cf->sockindex];
+ socks[0] = Curl_conn_cf_get_socket(cf, data);
return GETSOCK_WRITESOCK(0);
}
return fds;
lib/progress.c+24 -12
@@ -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;
}
lib/progress.h+7 -0
@@ -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)
lib/socks.c+1 -1
@@ -1169,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:
lib/urldata.h+1 -0
@@ -1233,6 +1233,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;
lib/vquic/curl_msh3.c+16 -7
@@ -34,6 +34,7 @@
#include "cfilters.h"
#include "cf-socket.h"
#include "connect.h"
+#include "progress.h"
#include "h2h3.h"
#include "curl_msh3.h"
#include "socketpair.h"
@@ -115,6 +116,8 @@ struct cf_msh3_ctx {
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;
@@ -491,11 +494,12 @@ 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 && cf->conn->sock[FIRSTSOCKET] != CURL_SOCKET_BAD) {
- socks[0] = cf->conn->sock[FIRSTSOCKET];
+ if(stream && ctx->sock[SP_LOCAL] != CURL_SOCKET_BAD) {
+ socks[0] = ctx->sock[SP_LOCAL];
if(stream->recv_error) {
bitmap |= GETSOCK_READSOCK(0);
@@ -544,6 +548,7 @@ 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;
@@ -553,7 +558,6 @@ static CURLcode cf_msh3_data_event(struct Curl_cfilter *cf,
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) {
@@ -567,16 +571,18 @@ static CURLcode cf_msh3_data_event(struct Curl_cfilter *cf,
}
}
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"));
+ 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;
@@ -657,12 +663,14 @@ static CURLcode cf_msh3_connect(struct Curl_cfilter *cf,
*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;
@@ -671,6 +679,7 @@ static CURLcode cf_msh3_connect(struct Curl_cfilter *cf,
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");
@@ -733,7 +742,7 @@ static void cf_msh3_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
static CURLcode cf_msh3_query(struct Curl_cfilter *cf,
struct Curl_easy *data,
- int query, int *pres1, void **pres2)
+ int query, int *pres1, void *pres2)
{
struct cf_msh3_ctx *ctx = cf->ctx;
lib/vquic/curl_ngtcp2.c+244 -110
@@ -53,8 +53,10 @@
#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 "h2h3.h"
#include "vtls/keylog.h"
@@ -155,11 +157,21 @@ struct cf_ngtcp2_ctx {
/* the packets blocked by sendmsg (EAGAIN or EWOULDBLOCK) */
struct blocked_pkt blocked_pkt[2];
+ 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. */
@@ -613,11 +625,14 @@ static int cb_recv_stream_data(ngtcp2_conn *tconn, uint32_t flags,
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)stream_user_data;
+ (void)data;
nconsumed =
nghttp3_conn_read_stream(ctx->h3conn, stream_id, buf, buflen, fin);
+ DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRIx64 "] read_stream(len=%zu) -> %zd",
+ stream_id, buflen, nconsumed));
if(nconsumed < 0) {
ngtcp2_connection_close_error_set_application_error(
&ctx->last_error,
@@ -662,11 +677,10 @@ static int cb_stream_close(ngtcp2_conn *tconn, uint32_t flags,
{
struct Curl_cfilter *cf = user_data;
struct cf_ngtcp2_ctx *ctx = cf->ctx;
- struct Curl_easy *data = stream_user_data;
int rv;
- (void)data;
(void)tconn;
+ (void)stream_user_data;
/* stream is closed... */
if(!(flags & NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET)) {
@@ -675,7 +689,6 @@ static int cb_stream_close(ngtcp2_conn *tconn, uint32_t flags,
rv = nghttp3_conn_close_stream(ctx->h3conn, stream_id,
app_error_code);
- DEBUGF(LOG_CF(data, cf, "[qsid=%" PRIx64 "] close -> %d", stream_id, rv));
if(rv) {
ngtcp2_connection_close_error_set_application_error(
&ctx->last_error, nghttp3_err_infer_quic_app_error_code(rv), NULL, 0);
@@ -858,7 +871,9 @@ static int cf_ngtcp2_get_select_socks(struct Curl_cfilter *cf,
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->sockfd;
/* in an HTTP/3 connection we can basically always get a frame so we should
@@ -873,6 +888,9 @@ static int cf_ngtcp2_get_select_socks(struct Curl_cfilter *cf,
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;
}
@@ -888,9 +906,15 @@ static int cb_h3_stream_close(nghttp3_conn *conn, int64_t stream_id,
(void)app_error_code;
(void)cf;
- DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRIx64 "] close", stream_id));
+ DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRIx64 "] 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;
+ }
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. */
@@ -919,8 +943,9 @@ static CURLcode write_data(struct HTTP *stream, const void *mem, size_t memlen)
ncopy -= len;
}
/* copy the rest to the overflow buffer */
- if(ncopy)
+ if(ncopy) {
result = Curl_dyn_addn(&stream->overflow, buf, ncopy);
+ }
return result;
}
@@ -1022,6 +1047,7 @@ static int cb_h3_recv_header(nghttp3_conn *conn, int64_t stream_id,
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;
@@ -1031,7 +1057,7 @@ static int cb_h3_recv_header(nghttp3_conn *conn, int64_t stream_id,
(void)stream_id;
(void)token;
(void)flags;
- (void)user_data;
+ (void)cf;
if(token == NGHTTP3_QPACK_TOKEN__STATUS) {
char line[14]; /* status line is always 13 characters long */
@@ -1040,6 +1066,8 @@ static int cb_h3_recv_header(nghttp3_conn *conn, int64_t stream_id,
DEBUGASSERT(stream->status_code != -1);
ncopy = msnprintf(line, sizeof(line), "HTTP/3 %03d \r\n",
stream->status_code);
+ DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRIx64 "] status: %s",
+ stream_id, line));
result = write_data(stream, line, ncopy);
if(result) {
return -1;
@@ -1047,6 +1075,9 @@ static int cb_h3_recv_header(nghttp3_conn *conn, int64_t stream_id,
}
else {
/* store as an HTTP1-style header */
+ DEBUGF(LOG_CF(data, cf, "[h3sid=%" PRIx64 "] header: %.*s: %.*s",
+ stream_id, (int)h3name.len, h3name.base,
+ (int)h3val.len, h3val.base));
result = write_data(stream, h3name.base, h3name.len);
if(result) {
return -1;
@@ -1208,7 +1239,10 @@ static ssize_t cf_ngtcp2_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
{
struct cf_ngtcp2_ctx *ctx = cf->ctx;
struct HTTP *stream = data->req.p.http;
+ ssize_t nread = -1;
+ struct cf_call_data save;
+ CF_DATA_SAVE(save, cf, data);
DEBUGASSERT(cf->connected);
DEBUGASSERT(ctx);
DEBUGASSERT(ctx->qconn);
@@ -1229,15 +1263,17 @@ static ssize_t cf_ngtcp2_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
if(cf_process_ingress(cf, data)) {
*err = CURLE_RECV_ERROR;
- return -1;
+ nread = -1;
+ goto out;
}
if(cf_flush_egress(cf, data)) {
*err = CURLE_SEND_ERROR;
- return -1;
+ nread = -1;
+ goto out;
}
if(stream->memlen) {
- ssize_t memlen = stream->memlen;
+ nread = stream->memlen;
/* data arrived */
/* reset to allow more data to come */
stream->memlen = 0;
@@ -1245,22 +1281,33 @@ static ssize_t cf_ngtcp2_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
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=%" PRIx64 "] recv, consumed %zd bytes",
+ stream->stream3_id, nread));
extend_stream_window(ctx->qconn, stream);
if(cf_flush_egress(cf, data)) {
*err = CURLE_SEND_ERROR;
- return -1;
+ nread = -1;
+ goto out;
}
- return memlen;
+ goto out;
}
if(stream->closed) {
- if(stream->error3 != NGHTTP3_H3_NO_ERROR) {
+ if(stream->reset) {
+ failf(data,
+ "HTTP/3 stream %" PRId64 " reset by server", stream->stream3_id);
+ *err = CURLE_PARTIAL_FILE;
+ nread = -1;
+ goto out;
+ }
+ else if(stream->error3 != NGHTTP3_H3_NO_ERROR) {
failf(data,
- "HTTP/3 stream %" PRId64 " was not closed cleanly: (err %" PRIu64
+ "HTTP/3 stream %" PRId64 " was not closed cleanly: (err 0x%" PRIx64
")",
stream->stream3_id, stream->error3);
*err = CURLE_HTTP3;
- return -1;
+ nread = -1;
+ goto out;
}
if(!stream->bodystarted) {
@@ -1269,15 +1316,20 @@ static ssize_t cf_ngtcp2_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
" all response header fields, treated as error",
stream->stream3_id);
*err = CURLE_HTTP3;
- return -1;
+ nread = -1;
+ goto out;
}
- return 0;
+ nread = 0;
+ goto out;
}
- infof(data, "ngh3_stream_recv returns 0 bytes and EAGAIN");
+ DEBUGF(LOG_CF(data, cf, "cf_ngtcp2_recv returns EAGAIN"));
*err = CURLE_AGAIN;
- return -1;
+ nread = -1;
+out:
+ CF_DATA_RESTORE(cf, save);
+ return nread;
}
/* this amount of data has now been acked on this stream */
@@ -1392,14 +1444,13 @@ static CURLcode h3_stream_open(struct Curl_cfilter *cf,
CURLcode result = CURLE_OK;
nghttp3_nv *nva = NULL;
int64_t stream3_id;
- int rc;
+ 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");
- result = CURLE_SEND_ERROR;
goto fail;
}
@@ -1449,22 +1500,22 @@ static CURLcode h3_stream_open(struct Curl_cfilter *cf,
}
stream->h3out = h3out;
+ DEBUGF(LOG_CF(data, cf, "h3[%"PRId64"] sending request %s, with_body=%d",
+ stream->stream3_id, data->state.url, !!stream->upload_left));
rc = nghttp3_conn_submit_request(ctx->h3conn, stream->stream3_id,
nva, nheader, &data_reader, data);
- if(rc) {
- result = CURLE_SEND_ERROR;
+ if(rc)
goto fail;
- }
break;
}
default:
+ DEBUGF(LOG_CF(data, cf, "h3[%"PRId64"] sending request %s",
+ stream->stream3_id, data->state.url));
stream->upload_left = 0; /* nothing left to send */
rc = nghttp3_conn_submit_request(ctx->h3conn, stream->stream3_id,
nva, nheader, NULL, data);
- if(rc) {
- result = CURLE_SEND_ERROR;
+ if(rc)
goto fail;
- }
break;
}
@@ -1479,6 +1530,20 @@ static CURLcode h3_stream_open(struct Curl_cfilter *cf,
return CURLE_OK;
fail:
+ if(rc) {
+ switch(rc) {
+ case NGHTTP3_ERR_CONN_CLOSING:
+ DEBUGF(LOG_CF(data, cf, "h3[%"PRId64"] failed to send, "
+ "connection is closing", stream->stream3_id));
+ result = CURLE_RECV_ERROR;
+ break;
+ default:
+ DEBUGF(LOG_CF(data, cf, "h3[%"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;
@@ -1490,23 +1555,26 @@ static ssize_t cf_ngtcp2_send(struct Curl_cfilter *cf, struct Curl_easy *data,
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);
DEBUGASSERT(ctx->qconn);
DEBUGASSERT(ctx->h3conn);
*err = CURLE_OK;
if(stream->closed) {
*err = CURLE_HTTP3;
- return -1;
+ sent = -1;
+ goto out;
}
if(!stream->h3req) {
CURLcode result = h3_stream_open(cf, data, buf, len);
if(result) {
- *err = CURLE_SEND_ERROR;
- return -1;
+ 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
@@ -1523,13 +1591,15 @@ static ssize_t cf_ngtcp2_send(struct Curl_cfilter *cf, struct Curl_easy *data,
}
else {
*err = CURLE_AGAIN;
- return -1;
+ sent = -1;
+ goto out;
}
}
if(cf_flush_egress(cf, data)) {
*err = CURLE_SEND_ERROR;
- return -1;
+ sent = -1;
+ goto out;
}
/* Reset post upload buffer after resumed. */
@@ -1546,10 +1616,12 @@ static ssize_t cf_ngtcp2_send(struct Curl_cfilter *cf, struct Curl_easy *data,
if(sent == 0) {
*err = CURLE_AGAIN;
- return -1;
+ sent = -1;
+ goto out;
}
}
-
+out:
+ CF_DATA_RESTORE(cf, save);
return sent;
}
@@ -1627,12 +1699,15 @@ static CURLcode cf_process_ingress(struct Curl_cfilter *cf,
SOCKERRNO == EINTR)
;
if(recvd == -1) {
- if(SOCKERRNO == EAGAIN || SOCKERRNO == EWOULDBLOCK)
- break;
+ if(SOCKERRNO == EAGAIN || SOCKERRNO == EWOULDBLOCK) {
+ DEBUGF(LOG_CF(data, cf, "ingress, recvfrom -> EAGAIN"));
+ goto out;
+ }
if(SOCKERRNO == ECONNREFUSED) {
const char *r_ip;
int r_port;
- Curl_cf_socket_peek(cf->next, NULL, NULL, &r_ip, &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;
@@ -1642,13 +1717,21 @@ static CURLcode cf_process_ingress(struct Curl_cfilter *cf,
return CURLE_RECV_ERROR;
}
+ if(recvd > 0 && !ctx->got_first_byte) {
+ ctx->first_byte_at = Curl_now();
+ ctx->got_first_byte = TRUE;
+ }
+
ngtcp2_addr_init(&path.local, (struct sockaddr *)&ctx->local_addr,
ctx->local_addrlen);
ngtcp2_addr_init(&path.remote, (struct sockaddr *)&remote_addr,
remote_addrlen);
+ DEBUGF(LOG_CF(data, cf, "ingress, recvd pkt of %zd bytes", recvd));
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(
@@ -1669,6 +1752,7 @@ static CURLcode cf_process_ingress(struct Curl_cfilter *cf,
}
}
+out:
return CURLE_OK;
}
@@ -1803,6 +1887,7 @@ static CURLcode send_packet(struct Curl_cfilter *cf,
{
struct cf_ngtcp2_ctx *ctx = cf->ctx;
+ DEBUGF(LOG_CF(data, cf, "egress, send %zu bytes", pktlen));
if(ctx->no_gso && pktlen > gsolen) {
return send_packet_no_gso(cf, data, pkt, pktlen, gsolen, psent);
}
@@ -2081,7 +2166,9 @@ static CURLcode cf_ngtcp2_data_event(struct Curl_cfilter *cf,
{
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) {
@@ -2102,60 +2189,73 @@ static CURLcode cf_ngtcp2_data_event(struct Curl_cfilter *cf,
case CF_CTRL_DATA_IDLE:
if(timestamp() >= ngtcp2_conn_get_expiry(ctx->qconn)) {
if(cf_flush_egress(cf, data)) {
- return CURLE_SEND_ERROR;
+ 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)
{
- if(ctx) {
- if(ctx->qlogfd != -1) {
- close(ctx->qlogfd);
- ctx->qlogfd = -1;
- }
+ 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);
+ 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);
- }
+ 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);
+ if(ctx->ssl)
+ wolfSSL_free(ctx->ssl);
+ if(ctx->sslctx)
+ wolfSSL_CTX_free(ctx->sslctx);
#endif
- free(ctx->pktbuf);
+ free(ctx->pktbuf);
+ if(ctx->h3conn)
nghttp3_conn_del(ctx->h3conn);
+ if(ctx->qconn)
ngtcp2_conn_del(ctx->qconn);
- memset(ctx, 0, sizeof(*ctx));
- }
+ 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;
- (void)data;
+ 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 */
@@ -2170,16 +2270,22 @@ static void cf_ngtcp2_close(struct Curl_cfilter *cf, struct Curl_easy *data)
}
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;
- (void)data;
- cf_ngtcp2_ctx_clear(ctx);
- free(ctx);
+ 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 */
}
/*
@@ -2194,45 +2300,8 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf,
CURLcode result;
ngtcp2_path path; /* TODO: this must be initialized properly */
const struct Curl_sockaddr_ex *sockaddr;
- const char *r_ip;
- int r_port;
int qfd;
- result = Curl_cf_socket_peek(cf->next, &ctx->sockfd,
- &sockaddr, &r_ip, &r_port);
- if(result)
- return result;
- DEBUGASSERT(ctx->sockfd != CURL_SOCKET_BAD);
-
- infof(data, "Connect socket %d over QUIC to %s:%d",
- ctx->sockfd, r_ip, r_port);
-
- rc = connect(ctx->sockfd, &sockaddr->sa_addr, sockaddr->addrlen);
- if(-1 == rc) {
- return Curl_socket_connect_result(data, r_ip, SOCKERRNO);
- }
-
- /* QUIC sockets need to be nonblocking */
- (void)curlx_nonblock(ctx->sockfd, TRUE);
- switch(sockaddr->family) {
-#if defined(__linux__) && defined(IP_MTU_DISCOVER)
- case AF_INET: {
- int val = IP_PMTUDISC_DO;
- (void)setsockopt(ctx->sockfd, 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->sockfd, IPPROTO_IPV6, IPV6_MTU_DISCOVER, &val,
- sizeof(val));
- break;
- }
-#endif
- }
-
ctx->version = NGTCP2_PROTO_VER_MAX;
#ifdef USE_OPENSSL
result = quic_ssl_ctx(&ctx->sslctx, cf, data);
@@ -2266,6 +2335,8 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf,
ctx->qlogfd = qfd; /* -1 if failure above */
quic_settings(ctx, data);
+ Curl_cf_socket_peek(cf->next, data, &ctx->sockfd,
+ &sockaddr, NULL, NULL, NULL, NULL);
ctx->local_addrlen = sizeof(ctx->local_addr);
rv = getsockname(ctx->sockfd, (struct sockaddr *)&ctx->local_addr,
&ctx->local_addrlen);
@@ -2321,6 +2392,8 @@ static CURLcode cf_ngtcp2_connect(struct Curl_cfilter *cf,
{
struct cf_ngtcp2_ctx *ctx = cf->ctx;
CURLcode result = CURLE_OK;
+ struct cf_call_data save;
+ struct curltime now;
if(cf->connected) {
*done = TRUE;
@@ -2335,10 +2408,24 @@ static CURLcode cf_ngtcp2_connect(struct Curl_cfilter *cf,
}
*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);
@@ -2350,8 +2437,12 @@ static CURLcode cf_ngtcp2_connect(struct Curl_cfilter *cf,
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;
@@ -2360,37 +2451,80 @@ static CURLcode cf_ngtcp2_connect(struct Curl_cfilter *cf,
}
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 && result != CURLE_AGAIN) {
+ if(result) {
const char *r_ip;
int r_port;
- Curl_cf_socket_peek(cf->next, NULL, NULL, &r_ip, &r_port);
- infof(data, "connect to %s port %u failed: %s",
+ 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)
+ 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;
}
@@ -2428,21 +2562,22 @@ CURLcode Curl_cf_ngtcp2_create(struct Curl_cfilter **pcf,
CURLcode result;
(void)data;
- (void)conn;
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);
+ 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;
@@ -2455,7 +2590,6 @@ out:
Curl_safefree(cf);
Curl_safefree(ctx);
}
-
return result;
}
lib/vquic/curl_quiche.c+186 -110
@@ -37,6 +37,7 @@
#include "strcase.h"
#include "multiif.h"
#include "connect.h"
+#include "progress.h"
#include "strerror.h"
#include "vquic.h"
#include "curl_quiche.h"
@@ -143,7 +144,10 @@ struct cf_quiche_ctx {
SSL_CTX *sslctx;
SSL *ssl;
struct h3_event_node *pending;
- bool h3_recving; /* TRUE when in h3-body-reading state */
+ struct curltime connect_started; /* time the current attempt started */
+ struct curltime handshake_done; /* time connect handshake finished */
+ int first_reply_ms; /* ms since first data arrived */
+ struct curltime reconnect_at; /* time the next attempt should start */
bool goaway;
};
@@ -169,13 +173,33 @@ static void h3_clear_pending(struct cf_quiche_ctx *ctx)
}
}
+static void cf_quiche_ctx_clear(struct cf_quiche_ctx *ctx)
+{
+ if(ctx) {
+ if(ctx->pending)
+ h3_clear_pending(ctx);
+ 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));
+ ctx->first_reply_ms = -1;
+ }
+}
+
static CURLcode h3_add_event(struct Curl_cfilter *cf,
struct Curl_easy *data,
- int64_t stream3_id, quiche_h3_event *ev)
+ int64_t stream3_id, quiche_h3_event *ev,
+ size_t *pqlen)
{
struct cf_quiche_ctx *ctx = cf->ctx;
struct Curl_easy *mdata;
struct h3_event_node *node, **pnext = &ctx->pending;
+ size_t qlen;
DEBUGASSERT(data->multi);
for(mdata = data->multi->easyp; mdata; mdata = mdata->next) {
@@ -185,9 +209,10 @@ static CURLcode h3_add_event(struct Curl_cfilter *cf,
}
if(!mdata) {
- DEBUGF(LOG_CF(data, cf, "event for unknown stream %"PRId64", discarded",
- stream3_id));
+ DEBUGF(LOG_CF(data, cf, "h3[%"PRId64"] event discarded, easy handle "
+ "not found", stream3_id));
quiche_h3_event_free(ev);
+ *pqlen = 0;
return CURLE_OK;
}
@@ -197,10 +222,13 @@ static CURLcode h3_add_event(struct Curl_cfilter *cf,
node->stream3_id = stream3_id;
node->ev = ev;
/* append to process them in order of arrival */
+ qlen = 0;
while(*pnext) {
pnext = &((*pnext)->next);
+ ++qlen;
}
*pnext = node;
+ *pqlen = qlen + 1;
if(!mdata->state.drain) {
/* tell the multi handle that this data needs processing */
mdata->state.drain = 1;
@@ -260,23 +288,24 @@ static ssize_t h3_process_event(struct Curl_cfilter *cf,
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 in HTTP/3 response header");
+ 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, "h3[%"PRId64"] HEADERS len=%d",
- stream3_id, (int)recvd));
+ DEBUGF(LOG_CF(data, cf, "h3[%"PRId64"] recv, HEADERS len=%zd",
+ stream3_id, recvd));
break;
case QUICHE_H3_EVENT_DATA:
- DEBUGF(LOG_CF(data, cf, "h3[%"PRId64"] DATA", stream3_id));
if(!stream->firstbody) {
/* add a header-body separator CRLF */
buf[0] = '\r';
@@ -291,23 +320,33 @@ static ssize_t h3_process_event(struct Curl_cfilter *cf,
rcode = quiche_h3_recv_body(ctx->h3c, ctx->qconn, stream3_id,
(unsigned char *)buf, len);
if(rcode <= 0) {
+ failf(data, "Error %zd in HTTP/3 response body for stream[%"PRId64"]",
+ rcode, stream3_id);
recvd = -1;
*err = CURLE_AGAIN;
break;
}
- ctx->h3_recving = TRUE;
+ stream->h3_recving_data = TRUE;
recvd += rcode;
+ DEBUGF(LOG_CF(data, cf, "h3[%"PRId64"] recv, DATA len=%zd",
+ stream3_id, rcode));
break;
case QUICHE_H3_EVENT_RESET:
- DEBUGF(LOG_CF(data, cf, "h3[%"PRId64"] RESET", stream3_id));
+ if(quiche_conn_is_draining(ctx->qconn) && !stream->h3_got_header) {
+ DEBUGF(LOG_CF(data, cf, "h3[%"PRId64"] stream RESET without response, "
+ "connection is draining", stream3_id));
+ }
+ else {
+ DEBUGF(LOG_CF(data, cf, "h3[%"PRId64"] recv, RESET", stream3_id));
+ }
streamclose(cf->conn, "Stream reset");
- *err = CURLE_PARTIAL_FILE;
+ *err = stream->h3_got_header? CURLE_PARTIAL_FILE : CURLE_RECV_ERROR;
recvd = -1;
break;
case QUICHE_H3_EVENT_FINISHED:
- DEBUGF(LOG_CF(data, cf, "h3[%"PRId64"] FINISHED", stream3_id));
+ DEBUGF(LOG_CF(data, cf, "h3[%"PRId64"] recv, FINISHED", stream3_id));
stream->closed = TRUE;
streamclose(cf->conn, "End of stream");
*err = CURLE_OK;
@@ -315,13 +354,14 @@ static ssize_t h3_process_event(struct Curl_cfilter *cf,
break;
case QUICHE_H3_EVENT_GOAWAY:
+ DEBUGF(LOG_CF(data, cf, "h3[%"PRId64"] recv, GOAWAY", stream3_id));
recvd = -1;
*err = CURLE_AGAIN;
ctx->goaway = TRUE;
break;
default:
- DEBUGF(LOG_CF(data, cf, "h3[%"PRId64"] unhandled event %d",
+ DEBUGF(LOG_CF(data, cf, "h3[%"PRId64"] recv, unhandled event %d",
stream3_id, quiche_h3_event_type(ev)));
break;
}
@@ -336,16 +376,30 @@ static ssize_t h3_process_pending(struct Curl_cfilter *cf,
struct cf_quiche_ctx *ctx = cf->ctx;
struct HTTP *stream = data->req.p.http;
struct h3_event_node *node = ctx->pending, **pnext = &ctx->pending;
- ssize_t recvd = -1;
+ ssize_t recvd = 0, erecvd;
- for(; node; pnext = &node->next, node = node->next) {
+ DEBUGASSERT(stream);
+ while(node) {
if(node->stream3_id == stream->stream3_id) {
- recvd = h3_process_event(cf, data, buf, len,
- node->stream3_id, node->ev, err);
+ erecvd = h3_process_event(cf, data, buf, len,
+ node->stream3_id, node->ev, err);
quiche_h3_event_free(node->ev);
*pnext = node->next;
free(node);
- break;
+ node = *pnext;
+ if(erecvd < 0) {
+ recvd = erecvd;
+ break;
+ }
+ recvd += erecvd;
+ if(erecvd > INT_MAX || (size_t)erecvd >= len)
+ break;
+ buf += erecvd;
+ len -= erecvd;
+ }
+ else {
+ pnext = &node->next;
+ node = node->next;
}
}
return recvd;
@@ -373,15 +427,24 @@ static CURLcode cf_process_ingress(struct Curl_cfilter *cf,
recvd = recvfrom(ctx->sockfd, buf, bufsize, 0,
(struct sockaddr *)&from, &from_len);
- if((recvd < 0) && ((SOCKERRNO == EAGAIN) || (SOCKERRNO == EWOULDBLOCK)))
- break;
-
if(recvd < 0) {
+ if((SOCKERRNO == EAGAIN) || (SOCKERRNO == EWOULDBLOCK))
+ goto out;
+ 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->sockfd);
return CURLE_RECV_ERROR;
}
+ DEBUGF(LOG_CF(data, cf, "ingress, recvd %zd bytes", recvd));
recv_info.from = (struct sockaddr *) &from;
recv_info.from_len = from_len;
recv_info.to = (struct sockaddr *) &ctx->local_addr;
@@ -389,7 +452,7 @@ static CURLcode cf_process_ingress(struct Curl_cfilter *cf,
recvd = quiche_conn_recv(ctx->qconn, buf, recvd, &recv_info);
if(recvd == QUICHE_ERR_DONE)
- break;
+ goto out;
if(recvd < 0) {
if(QUICHE_ERR_TLS_FAIL == recvd) {
@@ -406,8 +469,13 @@ static CURLcode cf_process_ingress(struct Curl_cfilter *cf,
return CURLE_RECV_ERROR;
}
+ if(ctx->first_reply_ms < 0) {
+ timediff_t ms = Curl_timediff(Curl_now(), ctx->connect_started);
+ ctx->first_reply_ms = (ms < INT_MAX)? (int)ms : INT_MAX;
+ }
} while(1);
+out:
return CURLE_OK;
}
@@ -434,6 +502,7 @@ static CURLcode cf_flush_egress(struct Curl_cfilter *cf,
return CURLE_SEND_ERROR;
}
+ DEBUGF(LOG_CF(data, cf, "egress, send %zu bytes", sent));
sent = send(ctx->sockfd, out, sent, 0);
if(sent < 0) {
failf(data, "send() returned %zd", sent);
@@ -459,9 +528,18 @@ static ssize_t cf_quiche_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
quiche_h3_event *ev;
struct HTTP *stream = data->req.p.http;
- DEBUGF(LOG_CF(data, cf, "recv[%"PRId64"]", stream->stream3_id));
-
*err = CURLE_AGAIN;
+ /* 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, len, err);
+ if(recvd < 0) {
+ goto out;
+ }
+ else if(recvd > 0) {
+ *err = CURLE_OK;
+ goto out;
+ }
recvd = -1;
if(cf_process_ingress(cf, data)) {
@@ -470,12 +548,12 @@ static ssize_t cf_quiche_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
goto out;
}
- if(ctx->h3_recving) {
+ if(stream->h3_recving_data) {
/* body receiving state */
rcode = quiche_h3_recv_body(ctx->h3c, ctx->qconn, stream->stream3_id,
(unsigned char *)buf, len);
if(rcode <= 0) {
- ctx->h3_recving = FALSE;
+ stream->h3_recving_data = FALSE;
/* fall through into the while loop below */
}
else {
@@ -485,27 +563,29 @@ static ssize_t cf_quiche_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
}
}
- if(recvd < 0) {
- recvd = h3_process_pending(cf, data, buf, len, err);
- }
-
while(recvd < 0) {
int64_t stream3_id = quiche_h3_conn_poll(ctx->h3c, ctx->qconn, &ev);
if(stream3_id < 0)
/* nothing more to do */
break;
- if(stream3_id != stream->stream3_id) {
+ if(stream3_id == stream->stream3_id) {
+ recvd = h3_process_event(cf, data, buf, len, stream3_id, ev, err);
+ quiche_h3_event_free(ev);
+ }
+ else {
+ size_t qlen;
/* event for another transfer, preserver for later */
- DEBUGF(LOG_CF(data, cf, "h3[%"PRId64"] queuing event", stream3_id));
- if(h3_add_event(cf, data, stream3_id, ev) != CURLE_OK) {
+ DEBUGF(LOG_CF(data, cf, "h3[%"PRId64"] recv, queue event "
+ "for h3[%"PRId64"]", stream->stream3_id, stream3_id));
+ if(h3_add_event(cf, data, stream3_id, ev, &qlen) != CURLE_OK) {
*err = CURLE_OUT_OF_MEMORY;
goto out;
}
- }
- else {
- recvd = h3_process_event(cf, data, buf, len, stream3_id, ev, err);
- quiche_h3_event_free(ev);
+ if(qlen > 20) {
+ Curl_expire(data, 0, EXPIRE_QUIC);
+ break;
+ }
}
}
@@ -519,6 +599,7 @@ static ssize_t cf_quiche_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
if(recvd >= 0) {
/* Get this called again to drain the event queue */
Curl_expire(data, 0, EXPIRE_QUIC);
+ *err = CURLE_OK;
}
else if(stream->closed) {
*err = CURLE_OK;
@@ -527,7 +608,7 @@ static ssize_t cf_quiche_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
out:
data->state.drain = (recvd >= 0) ? 1 : 0;
- DEBUGF(LOG_CF(data, cf, "recv[%"PRId64"] -> %ld, err=%d",
+ DEBUGF(LOG_CF(data, cf, "h3[%"PRId64"] recv -> %ld, err=%d",
stream->stream3_id, (long)recvd, *err));
return recvd;
}
@@ -549,7 +630,8 @@ static CURLcode cf_http_request(struct Curl_cfilter *cf,
CURLcode result = CURLE_OK;
struct h2h3req *hreq = NULL;
- stream->h3req = TRUE; /* senf off! */
+ DEBUGF(LOG_CF(data, cf, "cf_http_request %s", data->state.url));
+ stream->h3req = TRUE; /* send off! */
result = Curl_pseudo_headers(data, mem, len, NULL, &hreq);
if(result)
@@ -584,22 +666,14 @@ static CURLcode cf_http_request(struct Curl_cfilter *cf,
stream3_id = quiche_h3_send_request(ctx->h3c, ctx->qconn, nva, nheader,
stream->upload_left ? FALSE: TRUE);
- DEBUGF(LOG_CF(data, cf, "send_request(with_body=%d) -> %"PRId64,
- !!stream->upload_left, stream3_id));
- if((stream3_id >= 0) && data->set.postfields) {
- ssize_t sent = quiche_h3_send_body(ctx->h3c, ctx->qconn, 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 */
- }
+ DEBUGF(LOG_CF(data, cf, "h3[%"PRId64"] send request %s, upload=%zu",
+ stream3_id, data->state.url, stream->upload_left));
break;
default:
stream3_id = quiche_h3_send_request(ctx->h3c, ctx->qconn, nva, nheader,
TRUE);
+ DEBUGF(LOG_CF(data, cf, "h3[%"PRId64"] send request %s",
+ stream3_id, data->state.url));
break;
}
@@ -631,6 +705,7 @@ static ssize_t cf_quiche_send(struct Curl_cfilter *cf, struct Curl_easy *data,
struct HTTP *stream = data->req.p.http;
ssize_t sent;
+ DEBUGF(LOG_CF(data, cf, "cf_quiche_send(len=%zu) %s", len, data->state.url));
if(!stream->h3req) {
CURLcode result = cf_http_request(cf, data, buf, len);
if(result) {
@@ -705,8 +780,6 @@ static bool cf_quiche_data_pending(struct Curl_cfilter *cf,
return TRUE;
}
}
- DEBUGF(LOG_CF((struct Curl_easy *)data, cf, "h3[%"PRId64"] no data pending",
- stream->stream3_id));
return FALSE;
}
@@ -730,7 +803,16 @@ static CURLcode cf_quiche_data_event(struct Curl_cfilter *cf,
return CURLE_SEND_ERROR;
break;
}
-
+ case CF_CTRL_DATA_DONE: {
+ struct HTTP *stream = data->req.p.http;
+ DEBUGF(LOG_CF(data, cf, "h3[%"PRId64"] easy handle is %s",
+ stream->stream3_id, arg1? "cancelled" : "done"));
+ break;
+ }
+ case CF_CTRL_CONN_REPORT_STATS:
+ if(cf->sockindex == FIRSTSOCKET)
+ Curl_pgrsTimeWas(data, TIMER_APPCONNECT, ctx->handshake_done);
+ break;
default:
break;
}
@@ -758,7 +840,6 @@ static CURLcode cf_verify_peer(struct Curl_cfilter *cf,
X509_free(server_cert);
if(result)
goto out;
- DEBUGF(LOG_CF(data, cf, "Verified certificate just fine"));
}
else
DEBUGF(LOG_CF(data, cf, "Skipped certificate verification"));
@@ -797,48 +878,16 @@ static CURLcode cf_connect_start(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
struct cf_quiche_ctx *ctx = cf->ctx;
- int rc;
int rv;
CURLcode result;
const struct Curl_sockaddr_ex *sockaddr;
- const char *r_ip;
- int r_port;
- result = Curl_cf_socket_peek(cf->next, &ctx->sockfd,
- &sockaddr, &r_ip, &r_port);
+ result = Curl_cf_socket_peek(cf->next, data, &ctx->sockfd,
+ &sockaddr, NULL, NULL, NULL, NULL);
if(result)
return result;
DEBUGASSERT(ctx->sockfd != CURL_SOCKET_BAD);
- infof(data, "Connect socket %d over QUIC to %s:%d",
- ctx->sockfd, r_ip, r_port);
-
- rc = connect(ctx->sockfd, &sockaddr->sa_addr, sockaddr->addrlen);
- if(-1 == rc) {
- return Curl_socket_connect_result(data, r_ip, SOCKERRNO);
- }
-
- /* QUIC sockets need to be nonblocking */
- (void)curlx_nonblock(ctx->sockfd, TRUE);
- switch(sockaddr->family) {
-#if defined(__linux__) && defined(IP_MTU_DISCOVER)
- case AF_INET: {
- int val = IP_PMTUDISC_DO;
- (void)setsockopt(ctx->sockfd, 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->sockfd, IPPROTO_IPV6, IPV6_MTU_DISCOVER, &val,
- sizeof(val));
- break;
- }
-#endif
- }
-
#ifdef DEBUG_QUICHE
/* initialize debug log callback only once */
static int debug_log_init = 0;
@@ -940,6 +989,7 @@ static CURLcode cf_quiche_connect(struct Curl_cfilter *cf,
{
struct cf_quiche_ctx *ctx = cf->ctx;
CURLcode result = CURLE_OK;
+ struct curltime now;
if(cf->connected) {
*done = TRUE;
@@ -954,10 +1004,19 @@ static CURLcode cf_quiche_connect(struct Curl_cfilter *cf,
}
*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->connect_started = now;
}
result = cf_process_ingress(cf, data);
@@ -969,15 +1028,43 @@ static CURLcode cf_quiche_connect(struct Curl_cfilter *cf,
goto out;
if(quiche_conn_is_established(ctx->qconn)) {
+ DEBUGF(LOG_CF(data, cf, "handshake complete after %dms",
+ (int)Curl_timediff(now, ctx->connect_started)));
+ ctx->handshake_done = now;
result = cf_verify_peer(cf, data);
if(!result) {
- DEBUGF(infof(data, "quiche established connection"));
+ 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
@@ -985,7 +1072,8 @@ out:
const char *r_ip;
int r_port;
- Curl_cf_socket_peek(cf->next, NULL, NULL, &r_ip, &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));
}
@@ -993,23 +1081,6 @@ out:
return result;
}
-static void cf_quiche_ctx_clear(struct cf_quiche_ctx *ctx)
-{
- if(ctx) {
- if(ctx->pending)
- h3_clear_pending(ctx);
- 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 cf_quiche_close(struct Curl_cfilter *cf, struct Curl_easy *data)
{
struct cf_quiche_ctx *ctx = cf->ctx;
@@ -1038,7 +1109,7 @@ static void cf_quiche_destroy(struct Curl_cfilter *cf, struct Curl_easy *data)
static CURLcode cf_quiche_query(struct Curl_cfilter *cf,
struct Curl_easy *data,
- int query, int *pres1, void **pres2)
+ int query, int *pres1, void *pres2)
{
struct cf_quiche_ctx *ctx = cf->ctx;
@@ -1052,6 +1123,11 @@ static CURLcode cf_quiche_query(struct Curl_cfilter *cf,
DEBUGF(LOG_CF(data, cf, "query: MAX_CONCURRENT -> %d", *pres1));
return CURLE_OK;
}
+ case CF_QUERY_CONNECT_REPLY_MS:
+ *pres1 = ctx->first_reply_ms;
+ DEBUGF(LOG_CF(data, cf, "query connect reply: %dms", *pres1));
+ return CURLE_OK;
+
default:
break;
}
@@ -1100,7 +1176,7 @@ CURLcode Curl_cf_quiche_create(struct Curl_cfilter **pcf,
if(result)
goto out;
- result = Curl_cf_udp_create(&udp_cf, data, conn, ai);
+ result = Curl_cf_udp_create(&udp_cf, data, conn, ai, TRNSPRT_QUIC);
if(result)
goto out;
lib/vquic/vquic.c+44 -4
@@ -24,19 +24,25 @@
#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 "curl_log.h"
#include "curl_msh3.h"
#include "curl_ngtcp2.h"
#include "curl_quiche.h"
#include "vquic.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
#else
@@ -102,8 +108,10 @@ CURLcode Curl_qlogdir(struct Curl_easy *data,
CURLcode Curl_cf_quic_create(struct Curl_cfilter **pcf,
struct Curl_easy *data,
struct connectdata *conn,
- const struct Curl_addrinfo *ai)
+ const struct Curl_addrinfo *ai,
+ int transport)
{
+ DEBUGASSERT(transport == TRNSPRT_QUIC);
#ifdef USE_NGTCP2
return Curl_cf_ngtcp2_create(pcf, data, conn, ai);
#elif defined(USE_QUICHE)
@@ -135,4 +143,36 @@ bool Curl_conn_is_http3(const struct Curl_easy *data,
#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 */
lib/vquic/vquic.h+6 -2
@@ -43,7 +43,8 @@ CURLcode Curl_qlogdir(struct Curl_easy *data,
CURLcode Curl_cf_quic_create(struct Curl_cfilter **pcf,
struct Curl_easy *data,
struct connectdata *conn,
- const struct Curl_addrinfo *ai);
+ const struct Curl_addrinfo *ai,
+ int transport);
bool Curl_conn_is_http3(const struct Curl_easy *data,
const struct connectdata *conn,
@@ -53,8 +54,11 @@ extern struct Curl_cftype Curl_cft_http3;
#else /* ENABLE_QUIC */
-#define Curl_conn_is_http3(a,b,c) FALSE
+#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 */
lib/vtls/bearssl.c+15 -48
@@ -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,35 +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;
- if(data->state.httpwant == CURL_HTTP_VERSION_1_0) {
- backend->protocols[cur++] = ALPN_HTTP_1_0;
- infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_0);
+ for(i = 0; i < connssl->alpn->count; ++i) {
+ backend->protocols[i] = connssl->alpn->entries[i];
}
- else {
-#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);
- }
-#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))
@@ -868,26 +850,11 @@ static CURLcode bearssl_connect_step3(struct Curl_cfilter *cf,
DEBUGASSERT(backend);
if(cf->conn->bits.tls_enable_alpn) {
- const char *protocol;
+ const char *proto;
- protocol = br_ssl_engine_get_selected_protocol(&backend->ctx.eng);
- if(protocol) {
- infof(data, VTLS_INFOF_ALPN_ACCEPTED_1STR, protocol);
-
-#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) {
@@ -983,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;
lib/vtls/gskit.c+6 -6
@@ -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;
lib/vtls/gtls.c+16 -49
@@ -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;
@@ -698,37 +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(data->state.httpwant == CURL_HTTP_VERSION_1_0) {
- protocols[cur].data = (unsigned char *)ALPN_HTTP_1_0;
- protocols[cur++].size = ALPN_HTTP_1_0_LENGTH;
- infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_0);
- }
- else {
-#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;
- infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_H2);
- }
-#endif
+ if(connssl->alpn) {
+ struct alpn_proto_buf proto;
+ gnutls_datum_t alpn[ALPN_ENTRIES_MAX];
+ size_t i;
- protocols[cur].data = (unsigned char *)ALPN_HTTP_1_1;
- protocols[cur++].size = ALPN_HTTP_1_1_LENGTH;
- infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_1);
+ 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]);
}
-
- 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
@@ -1272,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) {
@@ -1517,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
lib/vtls/mbedtls.c+13 -36
@@ -646,19 +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];
- if(data->state.httpwant == CURL_HTTP_VERSION_1_0) {
- *p++ = ALPN_HTTP_1_0;
- }
- else {
-#ifdef USE_HTTP2
- if(data->state.httpwant >= CURL_HTTP_VERSION_2)
- *p++ = ALPN_H2;
-#endif
- *p++ = ALPN_HTTP_1_1;
+ 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];
}
- *p = NULL;
/* 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,
@@ -666,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
@@ -847,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
@@ -1084,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;
lib/vtls/nss.c+12 -52
@@ -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,29 +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;
- }
- else if(buflen == ALPN_HTTP_1_0_LENGTH &&
- !memcmp(ALPN_HTTP_1_0, buf, ALPN_HTTP_1_0_LENGTH)) {
- cf->conn->alpn = CURL_HTTP_VERSION_1_0;
- }
-
- /* 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);
}
}
@@ -1901,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);
@@ -2167,34 +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];
-
- if(data->state.httpwant == CURL_HTTP_VERSION_1_0) {
- protocols[cur++] = ALPN_HTTP_1_0_LENGTH;
- memcpy(&protocols[cur], ALPN_HTTP_1_0, ALPN_HTTP_1_0_LENGTH);
- cur += ALPN_HTTP_1_0_LENGTH;
- }
- else {
-#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
lib/vtls/openssl.c+11 -55
@@ -1814,7 +1814,7 @@ 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],
+ nread = recv((RECV_TYPE_ARG1)Curl_conn_cf_get_socket(cf, data),
(RECV_TYPE_ARG2)&buf, (RECV_TYPE_ARG3)1,
(RECV_TYPE_ARG4)MSG_PEEK);
if(nread == 0)
@@ -2008,7 +2008,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();
@@ -3651,43 +3651,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];
-
- if(data->state.httpwant == CURL_HTTP_VERSION_1_0) {
- protocols[cur++] = ALPN_HTTP_1_0_LENGTH;
- memcpy(&protocols[cur], ALPN_HTTP_1_0, ALPN_HTTP_1_0_LENGTH);
- cur += ALPN_HTTP_1_0_LENGTH;
- infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_0);
- }
- else {
-#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;
+ if(connssl->alpn) {
+ struct alpn_proto_buf proto;
- 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);
- }
- /* 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
@@ -4038,26 +4012,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
@@ -4374,7 +4330,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 */
lib/vtls/rustls.c+15 -52
@@ -354,34 +354,19 @@ cr_init_backend(struct Curl_cfilter *cf, struct Curl_easy *data,
rconn = backend->conn;
config_builder = rustls_client_config_builder_new();
- if(data->state.httpwant == CURL_HTTP_VERSION_1_0) {
- rustls_slice_bytes alpn[] = {
- { (const uint8_t *)ALPN_HTTP_1_0, ALPN_HTTP_1_0_LENGTH }
- };
- infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_0);
- rustls_client_config_builder_set_alpn_protocols(config_builder, alpn, 1);
- }
- else {
- 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 },
- };
-#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
- ) {
- infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_1);
- infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_H2);
- rustls_client_config_builder_set_alpn_protocols(config_builder, alpn, 2);
- }
- else
-#endif
- {
- infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_1);
- rustls_client_config_builder_set_alpn_protocols(config_builder, alpn, 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(
@@ -457,29 +442,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
@@ -487,7 +450,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;
@@ -591,7 +554,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;
lib/vtls/schannel.c+21 -48
@@ -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,33 +1216,22 @@ schannel_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data)
list_start_index = cur;
- if(data->state.httpwant == CURL_HTTP_VERSION_1_0) {
- alpn_buffer[cur++] = ALPN_HTTP_1_0_LENGTH;
- memcpy(&alpn_buffer[cur], ALPN_HTTP_1_0, ALPN_HTTP_1_0_LENGTH);
- cur += ALPN_HTTP_1_0_LENGTH;
- infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_0);
- }
- else {
-#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);
- }
-#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);
+ result = Curl_alpn_to_proto_buf(&proto, connssl->alpn);
+ if(result) {
+ failf(data, "Error setting ALPN");
+ return CURLE_SSL_CONNECT_ERROR;
}
+ 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);
@@ -1735,40 +1725,23 @@ schannel_connect_step3(struct Curl_cfilter *cf, struct Curl_easy *data)
if(alpn_result.ProtoNegoStatus ==
SecApplicationProtocolNegotiationStatus_Success) {
- unsigned char alpn = 0;
+ unsigned char prev_alpn = cf->conn->alpn;
- infof(data, VTLS_INFOF_ALPN_ACCEPTED_LEN_1STR,
- alpn_result.ProtocolIdSize, alpn_result.ProtocolId);
-
-#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
@@ -1849,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;
@@ -2064,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);
lib/vtls/sectransp.c+17 -27
@@ -1636,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
@@ -1797,38 +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);
-
- if(data->state.httpwant == CURL_HTTP_VERSION_1_0) {
- CFArrayAppendValue(alpnArr, CFSTR(ALPN_HTTP_1_0));
- infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_0);
- }
- else {
-#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);
- }
-#endif
-
- CFArrayAppendValue(alpnArr, CFSTR(ALPN_HTTP_1_1));
- infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_1);
+ 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);
}
- /* 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
@@ -3018,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 */
@@ -3196,7 +3185,8 @@ 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--) {
@@ -3225,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;
lib/vtls/vtls.c+164 -24
@@ -290,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;
@@ -299,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);
@@ -329,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);
}
@@ -605,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;
}
@@ -1534,8 +1536,7 @@ 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:
@@ -1603,11 +1604,16 @@ static CURLcode ssl_cf_cntrl(struct Curl_cfilter *cf,
struct Curl_easy *data,
int event, int arg1, void *arg2)
{
+ 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);
@@ -1683,14 +1689,16 @@ struct Curl_cftype Curl_cft_ssl_proxy = {
};
static CURLcode cf_ssl_create(struct Curl_cfilter **pcf,
- struct Curl_easy *data)
+ struct Curl_easy *data,
+ struct connectdata *conn)
{
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;
@@ -1712,7 +1720,7 @@ CURLcode Curl_ssl_cfilter_add(struct Curl_easy *data,
struct Curl_cfilter *cf;
CURLcode result;
- result = cf_ssl_create(&cf, data);
+ result = cf_ssl_create(&cf, data, conn);
if(!result)
Curl_conn_cf_add(data, conn, sockindex, cf);
return result;
@@ -1724,7 +1732,7 @@ CURLcode Curl_cf_ssl_insert_after(struct Curl_cfilter *cf_at,
struct Curl_cfilter *cf;
CURLcode result;
- result = cf_ssl_create(&cf, data);
+ result = cf_ssl_create(&cf, data, cf_at->conn);
if(!result)
Curl_conn_cf_insert_after(cf_at, cf);
return result;
@@ -1732,18 +1740,18 @@ CURLcode Curl_cf_ssl_insert_after(struct Curl_cfilter *cf_at,
#ifndef CURL_DISABLE_PROXY
static CURLcode cf_ssl_proxy_create(struct Curl_cfilter **pcf,
- struct Curl_easy *data)
+ 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);
out:
@@ -1760,7 +1768,7 @@ CURLcode Curl_ssl_cfilter_proxy_add(struct Curl_easy *data,
struct Curl_cfilter *cf;
CURLcode result;
- result = cf_ssl_proxy_create(&cf, data);
+ result = cf_ssl_proxy_create(&cf, data, conn);
if(!result)
Curl_conn_cf_add(data, conn, sockindex, cf);
return result;
@@ -1772,7 +1780,7 @@ CURLcode Curl_cf_ssl_proxy_insert_after(struct Curl_cfilter *cf_at,
struct Curl_cfilter *cf;
CURLcode result;
- result = cf_ssl_proxy_create(&cf, data);
+ result = cf_ssl_proxy_create(&cf, data, cf_at->conn);
if(!result)
Curl_conn_cf_insert_after(cf_at, cf);
return result;
@@ -1900,4 +1908,136 @@ struct Curl_cfilter *Curl_ssl_cf_get_ssl(struct Curl_cfilter *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 > 255)
+ 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 > 255)
+ 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'", 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, 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 */
lib/vtls/vtls.h+43 -1
@@ -27,7 +27,6 @@
struct connectdata;
struct ssl_config_data;
-struct ssl_connect_data;
struct ssl_primary_config;
struct Curl_ssl_session;
@@ -73,6 +72,49 @@ CURLsslset Curl_init_sslset_nolock(curl_sslbackend id, const char *name,
#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 accomodate
+ * 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);
lib/vtls/vtls_int.h+2 -0
@@ -36,8 +36,10 @@ struct ssl_connect_data {
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 cf_call_data call_data; /* data handle used in current call */
+ struct curltime handshake_done; /* time when handshake finished */
};
lib/vtls/wolfssl.c+12 -42
@@ -631,34 +631,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" */
-
- if(data->state.httpwant == CURL_HTTP_VERSION_1_0) {
- strcpy(protocols, ALPN_HTTP_1_0);
- infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_0);
- }
- else {
-#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
+ if(connssl->alpn) {
+ struct alpn_proto_buf proto;
+ CURLcode result;
- strcpy(protocols + strlen(protocols), ALPN_HTTP_1_1);
- infof(data, VTLS_INFOF_ALPN_OFFER_1STR, ALPN_HTTP_1_1);
- }
- 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 */
@@ -710,7 +694,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;
}
@@ -886,25 +870,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;
@@ -1169,7 +1139,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 */
tests/Makefile.am+1 -1
@@ -49,7 +49,7 @@ BUILD_UNIT =
DIST_UNIT = unit
endif
-SUBDIRS = certs data server libtest $(BUILD_UNIT)
+SUBDIRS = certs data server libtest tests-httpd $(BUILD_UNIT)
DIST_SUBDIRS = $(SUBDIRS) $(DIST_UNIT)
PERLFLAGS = -I$(srcdir)
tests/libtest/lib2502.c+1 -1
@@ -74,7 +74,7 @@ int test(char *URL)
target_url[sizeof(target_url) - 1] = '\0';
easy_setopt(curl[i], CURLOPT_URL, target_url);
/* go http2 */
- easy_setopt(curl[i], CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_3);
+ easy_setopt(curl[i], CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_3ONLY);
easy_setopt(curl[i], CURLOPT_CONNECTTIMEOUT_MS, (long)5000);
easy_setopt(curl[i], CURLOPT_CAINFO, "./certs/EdelCurlRoot-ca.cacert");
/* wait for first connection establised to see if we can share it */
tests/tests-httpd/Makefile.am+27 -0
@@ -0,0 +1,27 @@
+#***************************************************************************
+# _ _ ____ _
+# Project ___| | | | _ \| |
+# / __| | | | |_) | |
+# | (__| |_| | _ <| |___
+# \___|\___/|_| \_\_____|
+#
+# Copyright (C) Daniel Stenberg, <[email protected]>, 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
+#
+###########################################################################
+
+clean-local:
+ rm -rf *.pyc __pycache__
+ rm -rf gen
tests/tests-httpd/config.ini.in+6 -2
@@ -33,7 +33,11 @@ apachectl = @APACHECTL@
[test]
http_port = 5001
https_port = 5002
-h3_port = 5003
+h3_port = 5002
[nghttpx]
-nghttpx = @HTTPD_NGHTTPX@
+nghttpx = @HTTPD_NGHTTPX@
+
+[caddy]
+caddy = @CADDY@
+port = 5004
tests/tests-httpd/conftest.py+1 -1
@@ -70,7 +70,7 @@ def httpd(env) -> Httpd:
@pytest.fixture(scope='package')
-def nghttpx(env) -> Optional[Nghttpx]:
+def nghttpx(env, httpd) -> Optional[Nghttpx]:
if env.have_h3_server():
nghttpx = Nghttpx(env=env)
nghttpx.clear_logs()
tests/tests-httpd/scorecard.py+400 -0
@@ -0,0 +1,400 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#***************************************************************************
+# _ _ ____ _
+# Project ___| | | | _ \| |
+# / __| | | | |_) | |
+# | (__| |_| | _ <| |___
+# \___|\___/|_| \_\_____|
+#
+# Copyright (C) 2008 - 2022, Daniel Stenberg, <[email protected]>, 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
+#
+###########################################################################
+#
+import argparse
+import json
+import logging
+import os
+import sys
+from datetime import datetime
+from statistics import mean
+from typing import Dict, Any
+
+from testenv import Env, Httpd, Nghttpx, CurlClient, Caddy, ExecResult
+
+
+log = logging.getLogger(__name__)
+
+
+class ScoreCardException(Exception):
+ pass
+
+
+class ScoreCard:
+
+ def __init__(self):
+ self.verbose = 0
+ self.env = None
+ self.httpd = None
+ self.nghttpx = None
+ self.caddy = None
+
+ def info(self, msg):
+ if self.verbose > 0:
+ sys.stderr.write(msg)
+ sys.stderr.flush()
+
+ def handshakes(self, proto: str) -> Dict[str, Any]:
+ props = {}
+ sample_size = 10
+ self.info(f'handshaking ')
+ for authority in [
+ f'{self.env.authority_for(self.env.domain1, proto)}'
+ ]:
+ self.info('localhost')
+ c_samples = []
+ hs_samples = []
+ errors = []
+ for i in range(sample_size):
+ self.info('.')
+ curl = CurlClient(env=self.env)
+ url = f'https://{authority}/'
+ r = curl.http_download(urls=[url], alpn_proto=proto)
+ if r.exit_code == 0 and len(r.stats) == 1:
+ c_samples.append(r.stats[0]['time_connect'])
+ hs_samples.append(r.stats[0]['time_appconnect'])
+ else:
+ errors.append(f'exit={r.exit_code}')
+ props['localhost'] = {
+ 'connect': mean(c_samples),
+ 'handshake': mean(hs_samples),
+ 'errors': errors
+ }
+ for authority in [
+ 'curl.se', 'google.com', 'cloudflare.com', 'nghttp2.org',
+ ]:
+ for ipv in ['ipv4', 'ipv6']:
+ self.info(f'{authority}-{ipv}')
+ c_samples = []
+ hs_samples = []
+ errors = []
+ for i in range(sample_size):
+ self.info('.')
+ curl = CurlClient(env=self.env)
+ args = [
+ '--http3-only' if proto == 'h3' else '--http2',
+ f'--{ipv}', f'https://{authority}/'
+ ]
+ r = curl.run_direct(args=args, with_stats=True)
+ if r.exit_code == 0 and len(r.stats) == 1:
+ c_samples.append(r.stats[0]['time_connect'])
+ hs_samples.append(r.stats[0]['time_appconnect'])
+ else:
+ errors.append(f'exit={r.exit_code}')
+ props[f'{authority}-{ipv}'] = {
+ 'connect': mean(c_samples) if len(c_samples) else -1,
+ 'handshake': mean(hs_samples) if len(hs_samples) else -1,
+ 'errors': errors
+ }
+ self.info('\n')
+ return props
+
+ def _make_docs_file(self, docs_dir: str, fname: str, fsize: int):
+ fpath = os.path.join(docs_dir, fname)
+ data1k = 1024*'x'
+ flen = 0
+ with open(fpath, 'w') as fd:
+ while flen < fsize:
+ fd.write(data1k)
+ flen += len(data1k)
+ return flen
+
+ def _check_downloads(self, r: ExecResult, count: int):
+ error = ''
+ if r.exit_code != 0:
+ error += f'exit={r.exit_code} '
+ if r.exit_code != 0 or len(r.stats) != count:
+ error += f'stats={len(r.stats)}/{count} '
+ fails = [s for s in r.stats if s['response_code'] != 200]
+ if len(fails) > 0:
+ error += f'{len(fails)} failed'
+ return error if len(error) > 0 else None
+
+ def transfer_single(self, url: str, proto: str, count: int):
+ sample_size = count
+ count = 1
+ samples = []
+ errors = []
+ self.info(f'{sample_size}x single')
+ for i in range(sample_size):
+ curl = CurlClient(env=self.env)
+ r = curl.http_download(urls=[url], alpn_proto=proto)
+ err = self._check_downloads(r, count)
+ if err:
+ errors.append(err)
+ else:
+ samples.append(r.stats[0]['speed_download'])
+ self.info(f'.')
+ return {
+ 'count': count,
+ 'samples': sample_size,
+ 'speed': mean(samples) if len(samples) else -1,
+ 'errors': errors
+ }
+
+ def transfer_serial(self, url: str, proto: str, count: int):
+ sample_size = 1
+ samples = []
+ errors = []
+ url = f'{url}?[0-{count - 1}]'
+ self.info(f'{sample_size}x{count} serial')
+ for i in range(sample_size):
+ curl = CurlClient(env=self.env)
+ r = curl.http_download(urls=[url], alpn_proto=proto)
+ self.info(f'.')
+ err = self._check_downloads(r, count)
+ if err:
+ errors.append(err)
+ else:
+ for s in r.stats:
+ samples.append(s['speed_download'])
+ return {
+ 'count': count,
+ 'samples': sample_size,
+ 'speed': mean(samples) if len(samples) else -1,
+ 'errors': errors
+ }
+
+ def transfer_parallel(self, url: str, proto: str, count: int):
+ sample_size = 1
+ samples = []
+ errors = []
+ url = f'{url}?[0-{count - 1}]'
+ self.info(f'{sample_size}x{count} parallel')
+ for i in range(sample_size):
+ curl = CurlClient(env=self.env)
+ start = datetime.now()
+ r = curl.http_download(urls=[url], alpn_proto=proto,
+ extra_args=['--parallel'])
+ err = self._check_downloads(r, count)
+ if err:
+ errors.append(err)
+ else:
+ duration = datetime.now() - start
+ total_size = sum([s['size_download'] for s in r.stats])
+ samples.append(total_size / duration.total_seconds())
+ return {
+ 'count': count,
+ 'samples': sample_size,
+ 'speed': mean(samples) if len(samples) else -1,
+ 'errors': errors
+ }
+
+ def download_url(self, url: str, proto: str, count: int):
+ self.info(f' {url}: ')
+ props = {
+ 'single': self.transfer_single(url=url, proto=proto, count=10),
+ 'serial': self.transfer_serial(url=url, proto=proto, count=count),
+ 'parallel': self.transfer_parallel(url=url, proto=proto, count=count),
+ }
+ self.info(f'\n')
+ return props
+
+ def downloads(self, proto: str) -> Dict[str, Any]:
+ scores = {}
+ if proto == 'h3':
+ port = self.env.h3_port
+ via = 'nghttpx'
+ descr = f'port {port}, proxying httpd'
+ else:
+ port = self.env.https_port
+ via = 'httpd'
+ descr = f'port {port}'
+ self.info('httpd downloads\n')
+ self._make_docs_file(docs_dir=self.httpd.docs_dir, fname='score1.data', fsize=1024*1024)
+ url1 = f'https://{self.env.domain1}:{port}/score1.data'
+ self._make_docs_file(docs_dir=self.httpd.docs_dir, fname='score10.data', fsize=10*1024*1024)
+ url10 = f'https://{self.env.domain1}:{port}/score10.data'
+ self._make_docs_file(docs_dir=self.httpd.docs_dir, fname='score100.data', fsize=100*1024*1024)
+ url100 = f'https://{self.env.domain1}:{port}/score100.data'
+ scores[via] = {
+ 'description': descr,
+ '1MB-local': self.download_url(url=url1, proto=proto, count=50),
+ '10MB-local': self.download_url(url=url10, proto=proto, count=50),
+ '100MB-local': self.download_url(url=url100, proto=proto, count=50),
+ }
+ if self.caddy:
+ port = self.env.caddy_port
+ via = 'caddy'
+ descr = f'port {port}'
+ self.info('caddy downloads\n')
+ self._make_docs_file(docs_dir=self.caddy.docs_dir, fname='score1.data', fsize=1024 * 1024)
+ url1 = f'https://{self.env.domain1}:{port}/score1.data'
+ self._make_docs_file(docs_dir=self.caddy.docs_dir, fname='score10.data', fsize=10 * 1024 * 1024)
+ url10 = f'https://{self.env.domain1}:{port}/score10.data'
+ self._make_docs_file(docs_dir=self.caddy.docs_dir, fname='score100.data', fsize=100 * 1024 * 1024)
+ url100 = f'https://{self.env.domain1}:{port}/score100.data'
+ scores[via] = {
+ 'description': descr,
+ '1MB-local': self.download_url(url=url1, proto=proto, count=50),
+ '10MB-local': self.download_url(url=url10, proto=proto, count=50),
+ '100MB-local': self.download_url(url=url100, proto=proto, count=50),
+ }
+ return scores
+
+ def score_proto(self, proto: str, handshakes: bool = True, downloads: bool = True):
+ self.info(f"scoring {proto}\n")
+ p = {}
+ if proto == 'h3':
+ p['name'] = 'h3'
+ if not self.env.have_h3_curl():
+ raise ScoreCardException('curl does not support HTTP/3')
+ for lib in ['ngtcp2', 'quiche', 'msh3']:
+ if self.env.curl_uses_lib(lib):
+ p['implementation'] = lib
+ break
+ elif proto == 'h2':
+ p['name'] = 'h2'
+ if not self.env.have_h2_curl():
+ raise ScoreCardException('curl does not support HTTP/2')
+ for lib in ['nghttp2', 'hyper']:
+ if self.env.curl_uses_lib(lib):
+ p['implementation'] = lib
+ break
+ else:
+ raise ScoreCardException(f"unknown protocol: {proto}")
+
+ if 'implementation' not in p:
+ raise ScoreCardException(f'did not recognized {p} lib')
+ p['version'] = Env.curl_lib_version(p['implementation'])
+
+ score = {
+ 'curl': self.env.curl_version(),
+ 'os': self.env.curl_os(),
+ 'protocol': p,
+ }
+ if handshakes:
+ score['handshakes'] = self.handshakes(proto=proto)
+ if downloads:
+ score['downloads'] = self.downloads(proto=proto)
+ self.info("\n")
+ return score
+
+ def fmt_ms(self, tval):
+ return f'{int(tval*1000)} ms' if tval >= 0 else '--'
+
+ def fmt_mb(self, val):
+ return f'{val/(1024*1024):0.000f} MB' if val >= 0 else '--'
+
+ def fmt_mbs(self, val):
+ return f'{val/(1024*1024):0.000f} MB/s' if val >= 0 else '--'
+
+ def print_score(self, score):
+ print(f'{score["protocol"]["name"].upper()} in curl {score["curl"]} ({score["os"]}) via '
+ f'{score["protocol"]["implementation"]}/{score["protocol"]["version"]} ')
+ if 'handshakes' in score:
+ print('Handshakes')
+ print(f' {"Host":<25} {"Connect":>12} {"Handshake":>12} {"Errors":<20}')
+ for key, val in score["handshakes"].items():
+ print(f' {key:<25} {self.fmt_ms(val["connect"]):>12} '''
+ f'{self.fmt_ms(val["handshake"]):>12} {"/".join(val["errors"]):<20}')
+ if 'downloads' in score:
+ print('Downloads')
+ for dkey, dval in score["downloads"].items():
+ print(f' {dkey}: {dval["description"]}')
+ for skey, sval in dval.items():
+ if isinstance(sval, str):
+ continue
+ print(f' {skey:<13} {"Samples":>10} {"Count":>10} {"Speed":>17} {"Errors":<20}')
+ for key, val in sval.items():
+ print(f' {key:<11} {val["samples"]:>10} '''
+ f'{val["count"]:>10} {self.fmt_mbs(val["speed"]):>17} '
+ f'{"/".join(val["errors"]):<20}')
+
+ def main(self):
+ parser = argparse.ArgumentParser(prog='scorecard', description="""
+ Run a range of tests to give a scorecard for a HTTP protocol
+ 'h3' or 'h2' implementation in curl.
+ """)
+ parser.add_argument("-v", "--verbose", action='count', default=0,
+ help="log more output on stderr")
+ parser.add_argument("-t", "--text", action='store_true', default=False,
+ help="print text instead of json")
+ parser.add_argument("-d", "--downloads", action='store_true', default=False,
+ help="evaluate downloads only")
+ parser.add_argument("protocols", nargs='*', help="Name(s) of protocol to score")
+ args = parser.parse_args()
+
+ self.verbose = args.verbose
+ if args.verbose > 0:
+ console = logging.StreamHandler()
+ console.setLevel(logging.INFO)
+ console.setFormatter(logging.Formatter(logging.BASIC_FORMAT))
+ logging.getLogger('').addHandler(console)
+
+ protocols = args.protocols if len(args.protocols) else ['h2', 'h3']
+ handshakes = True
+ downloads = True
+ if args.downloads:
+ handshakes = False
+
+ rv = 0
+ self.env = Env()
+ self.env.setup()
+ self.httpd = None
+ self.nghttpx = None
+ self.caddy = None
+ try:
+ self.httpd = Httpd(env=self.env)
+ assert self.httpd.exists(), f'httpd not found: {self.env.httpd}'
+ self.httpd.clear_logs()
+ assert self.httpd.start()
+ if 'h3' in protocols:
+ self.nghttpx = Nghttpx(env=self.env)
+ self.nghttpx.clear_logs()
+ assert self.nghttpx.start()
+ if self.env.caddy:
+ self.caddy = Caddy(env=self.env)
+ self.caddy.clear_logs()
+ assert self.caddy.start()
+
+ for p in protocols:
+ score = self.score_proto(proto=p, handshakes=handshakes, downloads=downloads)
+ if args.text:
+ self.print_score(score)
+ else:
+ print(json.JSONEncoder(indent=2).encode(score))
+
+ except ScoreCardException as ex:
+ sys.stderr.write(f"ERROR: {str(ex)}\n")
+ rv = 1
+ except KeyboardInterrupt:
+ log.warning("aborted")
+ rv = 1
+ finally:
+ if self.caddy:
+ self.caddy.stop()
+ self.caddy = None
+ if self.nghttpx:
+ self.nghttpx.stop(wait_dead=False)
+ if self.httpd:
+ self.httpd.stop()
+ self.httpd = None
+ sys.exit(rv)
+
+
+if __name__ == "__main__":
+ ScoreCard().main()
tests/tests-httpd/test_01_basic.py+5 -0
@@ -38,6 +38,11 @@ log = logging.getLogger(__name__)
reason=f"missing: {Env.incomplete_reason()}")
class TestBasic:
+ @pytest.fixture(autouse=True, scope='class')
+ def _class_scope(self, env, nghttpx):
+ if env.have_h3():
+ nghttpx.start_if_needed()
+
# simple http: GET
def test_01_01_http_get(self, env: Env, httpd):
curl = CurlClient(env=env)
tests/tests-httpd/test_02_download.py+42 -32
@@ -24,12 +24,11 @@
#
###########################################################################
#
-import json
import logging
-from typing import Optional
+import os
import pytest
-from testenv import Env, CurlClient, ExecResult
+from testenv import Env, CurlClient
log = logging.getLogger(__name__)
@@ -39,6 +38,18 @@ log = logging.getLogger(__name__)
reason=f"missing: {Env.incomplete_reason()}")
class TestDownload:
+ @pytest.fixture(autouse=True, scope='class')
+ def _class_scope(self, env, httpd, nghttpx):
+ if env.have_h3():
+ nghttpx.start_if_needed()
+ fpath = os.path.join(httpd.docs_dir, 'data-1mb.data')
+ data1k = 1024*'x'
+ with open(fpath, 'w') as fd:
+ fsize = 0
+ while fsize < 1024*1024:
+ fd.write(data1k)
+ fsize += len(data1k)
+
# download 1 file
@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
def test_02_01_download_1(self, env: Env, httpd, nghttpx, repeat, proto):
@@ -48,7 +59,7 @@ class TestDownload:
url = f'https://{env.authority_for(env.domain1, proto)}/data.json'
r = curl.http_download(urls=[url], alpn_proto=proto)
assert r.exit_code == 0, f'{r}'
- r.check_responses(count=1, exp_status=200)
+ r.check_stats(count=1, exp_status=200)
# download 2 files
@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
@@ -59,7 +70,7 @@ class TestDownload:
url = f'https://{env.authority_for(env.domain1, proto)}/data.json?[0-1]'
r = curl.http_download(urls=[url], alpn_proto=proto)
assert r.exit_code == 0
- r.check_responses(count=2, exp_status=200)
+ r.check_stats(count=2, exp_status=200)
# download 100 files sequentially
@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
@@ -71,8 +82,7 @@ class TestDownload:
urln = f'https://{env.authority_for(env.domain1, proto)}/data.json?[0-99]'
r = curl.http_download(urls=[urln], alpn_proto=proto)
assert r.exit_code == 0
- r.check_responses(count=100, exp_status=200)
- assert len(r.stats) == 100, f'{r.stats}'
+ r.check_stats(count=100, exp_status=200)
# http/1.1 sequential transfers will open 1 connection
assert r.total_connects == 1
@@ -87,7 +97,7 @@ class TestDownload:
r = curl.http_download(urls=[urln], alpn_proto=proto,
extra_args=['--parallel'])
assert r.exit_code == 0
- r.check_responses(count=100, exp_status=200)
+ r.check_stats(count=100, exp_status=200)
if proto == 'http/1.1':
# http/1.1 parallel transfers will open multiple connections
assert r.total_connects > 1
@@ -105,7 +115,7 @@ class TestDownload:
urln = f'https://{env.authority_for(env.domain1, proto)}/data.json?[0-499]'
r = curl.http_download(urls=[urln], alpn_proto=proto)
assert r.exit_code == 0
- r.check_responses(count=500, exp_status=200)
+ r.check_stats(count=500, exp_status=200)
if proto == 'http/1.1':
# http/1.1 parallel transfers will open multiple connections
assert r.total_connects > 1
@@ -124,7 +134,7 @@ class TestDownload:
r = curl.http_download(urls=[urln], alpn_proto=proto,
extra_args=['--parallel'])
assert r.exit_code == 0
- r.check_responses(count=500, exp_status=200)
+ r.check_stats(count=500, exp_status=200)
if proto == 'http/1.1':
# http/1.1 parallel transfers will open multiple connections
assert r.total_connects > 1
@@ -146,28 +156,28 @@ class TestDownload:
'--parallel', '--parallel-max', '200'
])
assert r.exit_code == 0, f'{r}'
- r.check_responses(count=500, exp_status=200)
+ r.check_stats(count=500, exp_status=200)
# http2 should now use 2 connections, at most 5
assert r.total_connects <= 5, "h2 should use fewer connections here"
- def check_response(self, r: ExecResult, count: int,
- exp_status: Optional[int] = None):
- if len(r.responses) != count:
- seen_queries = []
- for idx, resp in enumerate(r.responses):
- assert resp['status'] == 200, f'response #{idx} status: {resp["status"]}'
- if 'rquery' not in resp['header']:
- log.error(f'response #{idx} missing "rquery": {resp["header"]}')
- seen_queries.append(int(resp['header']['rquery']))
- for i in range(0,count-1):
- if i not in seen_queries:
- log.error(f'response for query {i} missing')
- if r.with_stats and len(r.stats) == count:
- log.error(f'got all {count} stats, though')
- assert len(r.responses) == count
- if exp_status is not None:
- for idx, x in enumerate(r.responses):
- assert x['status'] == exp_status, \
- f'response #{idx} unexpectedstatus: {x["status"]}'
- if r.with_stats:
- assert len(r.stats) == count, f'{r}'
+ @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
+ def test_02_08_1MB_serial(self, env: Env,
+ httpd, nghttpx, repeat, proto):
+ count = 2
+ urln = f'https://{env.authority_for(env.domain1, proto)}/data-1mb.data?[0-{count-1}]'
+ curl = CurlClient(env=env)
+ r = curl.http_download(urls=[urln], alpn_proto=proto)
+ assert r.exit_code == 0
+ r.check_stats(count=count, exp_status=200)
+
+ @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
+ def test_02_09_1MB_parallel(self, env: Env,
+ httpd, nghttpx, repeat, proto):
+ count = 2
+ urln = f'https://{env.authority_for(env.domain1, proto)}/data-1mb.data?[0-{count-1}]'
+ curl = CurlClient(env=env)
+ r = curl.http_download(urls=[urln], alpn_proto=proto, extra_args=[
+ '--parallel'
+ ])
+ assert r.exit_code == 0
+ r.check_stats(count=count, exp_status=200)
tests/tests-httpd/test_03_goaway.py+8 -8
@@ -24,12 +24,10 @@
#
###########################################################################
#
-import json
import logging
import time
from datetime import timedelta
from threading import Thread
-from typing import Optional
import pytest
from testenv import Env, CurlClient, ExecResult
@@ -42,6 +40,11 @@ log = logging.getLogger(__name__)
reason=f"missing: {Env.incomplete_reason()}")
class TestGoAway:
+ @pytest.fixture(autouse=True, scope='class')
+ def _class_scope(self, env, nghttpx):
+ if env.have_h3():
+ nghttpx.start_if_needed()
+
# download files sequentially with delay, reload server for GOAWAY
def test_03_01_h2_goaway(self, env: Env, httpd, nghttpx, repeat):
proto = 'h2'
@@ -64,8 +67,7 @@ class TestGoAway:
t.join()
r: ExecResult = self.r
assert r.exit_code == 0, f'{r}'
- r.check_responses(count=count, exp_status=200)
- assert len(r.stats) == count, f'{r.stats}'
+ r.check_stats(count=count, exp_status=200)
# reload will shut down the connection gracefully with GOAWAY
# we expect to see a second connection opened afterwards
assert r.total_connects == 2
@@ -77,7 +79,6 @@ class TestGoAway:
# download files sequentially with delay, reload server for GOAWAY
@pytest.mark.skipif(condition=not Env.have_h3_server(), reason="no h3 server")
- @pytest.mark.skipif(condition=True, reason="2nd and 3rd request sometimes fail")
def test_03_02_h3_goaway(self, env: Env, httpd, nghttpx, repeat):
proto = 'h3'
count = 3
@@ -95,12 +96,10 @@ class TestGoAway:
# each request will take a second, reload the server in the middle
# of the first one.
time.sleep(1.5)
- assert nghttpx.reload(timeout=timedelta(seconds=5))
+ assert nghttpx.reload(timeout=timedelta(seconds=2))
t.join()
r: ExecResult = self.r
assert r.exit_code == 0, f'{r}'
- r.check_responses(count=count, exp_status=200)
- assert len(r.stats) == count, f'{r.stats}'
# reload will shut down the connection gracefully with GOAWAY
# we expect to see a second connection opened afterwards
assert r.total_connects == 2
@@ -109,5 +108,6 @@ class TestGoAway:
log.debug(f'request {idx} connected')
# this should take `count` seconds to retrieve
assert r.duration >= timedelta(seconds=count)
+ r.check_stats(count=count, exp_status=200, exp_exitcode=0)
tests/tests-httpd/test_04_stuttered.py+15 -12
@@ -38,6 +38,11 @@ log = logging.getLogger(__name__)
reason=f"missing: {Env.incomplete_reason()}")
class TestStuttered:
+ @pytest.fixture(autouse=True, scope='class')
+ def _class_scope(self, env, nghttpx):
+ if env.have_h3():
+ nghttpx.start_if_needed()
+
# download 1 file, check that delayed response works in general
@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
def test_04_01_download_1(self, env: Env, httpd, nghttpx, repeat,
@@ -51,7 +56,7 @@ class TestStuttered:
'&chunks=100&chunk_size=100&chunk_delay=10ms'
r = curl.http_download(urls=[urln], alpn_proto=proto)
assert r.exit_code == 0, f'{r}'
- r.check_responses(count=1, exp_status=200)
+ r.check_stats(count=1, exp_status=200)
# download 50 files in 100 chunks a 100 bytes with 10ms delay between
# prepend 100 file requests to warm up connection processing limits
@@ -71,11 +76,11 @@ class TestStuttered:
r = curl.http_download(urls=[url1, urln], alpn_proto=proto,
extra_args=['--parallel'])
assert r.exit_code == 0, f'{r}'
- r.check_responses(count=warmups+count, exp_status=200)
+ r.check_stats(count=warmups+count, exp_status=200)
assert r.total_connects == 1
t_avg, i_min, t_min, i_max, t_max = self.stats_spread(r.stats[warmups:], 'time_total')
- assert t_max < (3 * t_min) and t_min < 2, \
- f'avg time of transfer: {t_avg} [{i_min}={t_min}, {i_max}={t_max}]'
+ if t_max < (5 * t_min) and t_min < 2:
+ log.warning(f'avg time of transfer: {t_avg} [{i_min}={t_min}, {i_max}={t_max}]')
# download 50 files in 1000 chunks a 10 bytes with 1ms delay between
# prepend 100 file requests to warm up connection processing limits
@@ -94,11 +99,11 @@ class TestStuttered:
r = curl.http_download(urls=[url1, urln], alpn_proto=proto,
extra_args=['--parallel'])
assert r.exit_code == 0
- r.check_responses(count=warmups+count, exp_status=200)
+ r.check_stats(count=warmups+count, exp_status=200)
assert r.total_connects == 1
t_avg, i_min, t_min, i_max, t_max = self.stats_spread(r.stats[warmups:], 'time_total')
- assert t_max < (2 * t_min), \
- f'avg time of transfer: {t_avg} [{i_min}={t_min}, {i_max}={t_max}]'
+ if t_max < (5 * t_min):
+ log.warning(f'avg time of transfer: {t_avg} [{i_min}={t_min}, {i_max}={t_max}]')
# download 50 files in 10000 chunks a 1 byte with 10us delay between
# prepend 100 file requests to warm up connection processing limits
@@ -107,8 +112,6 @@ class TestStuttered:
def test_04_04_1000_10_1(self, env: Env, httpd, nghttpx, repeat, proto):
if proto == 'h3' and not env.have_h3():
pytest.skip("h3 not supported")
- if proto == 'h2':
- pytest.skip("h2 shows overly long request times")
count = 50
warmups = 100
curl = CurlClient(env=env)
@@ -119,11 +122,11 @@ class TestStuttered:
r = curl.http_download(urls=[url1, urln], alpn_proto=proto,
extra_args=['--parallel'])
assert r.exit_code == 0
- r.check_responses(count=warmups+count, exp_status=200)
+ r.check_stats(count=warmups+count, exp_status=200)
assert r.total_connects == 1
t_avg, i_min, t_min, i_max, t_max = self.stats_spread(r.stats[warmups:], 'time_total')
- assert t_max < (2 * t_min), \
- f'avg time of transfer: {t_avg} [{i_min}={t_min}, {i_max}={t_max}]'
+ if t_max < (5 * t_min):
+ log.warning(f'avg time of transfer: {t_avg} [{i_min}={t_min}, {i_max}={t_max}]')
def stats_spread(self, stats: List[Dict], key: str) -> Tuple[float, int, float, int, float]:
stotals = 0.0
tests/tests-httpd/test_05_errors.py+9 -10
@@ -37,18 +37,21 @@ log = logging.getLogger(__name__)
@pytest.mark.skipif(condition=Env.setup_incomplete(),
reason=f"missing: {Env.incomplete_reason()}")
+@pytest.mark.skipif(condition=not Env.httpd_is_at_least('2.4.55'),
+ reason=f"httpd version too old for this: {Env.httpd_version()}")
class TestErrors:
+ @pytest.fixture(autouse=True, scope='class')
+ def _class_scope(self, env, nghttpx):
+ if env.have_h3():
+ nghttpx.start_if_needed()
+
# download 1 file, check that we get CURLE_PARTIAL_FILE
@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
def test_05_01_partial_1(self, env: Env, httpd, nghttpx, repeat,
proto):
if proto == 'h3' and not env.have_h3():
pytest.skip("h3 not supported")
- if proto == 'h2': # TODO, fix error code in curl
- pytest.skip("h2 reports exitcode 16(CURLE_HTTP2)")
- if proto == 'h3': # TODO, fix error code in curl
- pytest.skip("h3 reports exitcode 95(CURLE_HTTP3)")
count = 1
curl = CurlClient(env=env)
urln = f'https://{env.authority_for(env.domain1, proto)}' \
@@ -58,7 +61,7 @@ class TestErrors:
assert r.exit_code != 0, f'{r}'
invalid_stats = []
for idx, s in enumerate(r.stats):
- if 'exitcode' not in s or s['exitcode'] != 18:
+ if 'exitcode' not in s or s['exitcode'] not in [18, 56]:
invalid_stats.append(f'request {idx} exit with {s["exitcode"]}')
assert len(invalid_stats) == 0, f'failed: {invalid_stats}'
@@ -68,10 +71,6 @@ class TestErrors:
proto):
if proto == 'h3' and not env.have_h3():
pytest.skip("h3 not supported")
- if proto == 'h2': # TODO, fix error code in curl
- pytest.skip("h2 reports exitcode 16(CURLE_HTTP2)")
- if proto == 'h3': # TODO, fix error code in curl
- pytest.skip("h3 reports exitcode 95(CURLE_HTTP3) and takes a long time")
count = 20
curl = CurlClient(env=env)
urln = f'https://{env.authority_for(env.domain1, proto)}' \
@@ -82,6 +81,6 @@ class TestErrors:
assert len(r.stats) == count, f'did not get all stats: {r}'
invalid_stats = []
for idx, s in enumerate(r.stats):
- if 'exitcode' not in s or s['exitcode'] != 18:
+ if 'exitcode' not in s or s['exitcode'] not in [18, 56]:
invalid_stats.append(f'request {idx} exit with {s["exitcode"]}')
assert len(invalid_stats) == 0, f'failed: {invalid_stats}'
tests/tests-httpd/test_06_eyeballs.py+86 -0
@@ -0,0 +1,86 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#***************************************************************************
+# _ _ ____ _
+# Project ___| | | | _ \| |
+# / __| | | | |_) | |
+# | (__| |_| | _ <| |___
+# \___|\___/|_| \_\_____|
+#
+# Copyright (C) 2008 - 2022, Daniel Stenberg, <[email protected]>, 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
+#
+###########################################################################
+#
+import json
+import logging
+from typing import Optional, Tuple, List, Dict
+import pytest
+
+from testenv import Env, CurlClient, ExecResult
+
+
+log = logging.getLogger(__name__)
+
+
+@pytest.mark.skipif(condition=Env.setup_incomplete(),
+ reason=f"missing: {Env.incomplete_reason()}")
+@pytest.mark.skipif(condition=not Env.have_h3_server(),
+ reason=f"missing HTTP/3 server")
+@pytest.mark.skipif(condition=not Env.have_h3_curl(),
+ reason=f"curl built without HTTP/3")
+class TestEyeballs:
+
+ @pytest.fixture(autouse=True, scope='class')
+ def _class_scope(self, env, nghttpx):
+ if env.have_h3():
+ nghttpx.start_if_needed()
+
+ # download using only HTTP/3 on working server
+ def test_06_01_h3_only(self, env: Env, httpd, nghttpx, repeat):
+ curl = CurlClient(env=env)
+ urln = f'https://{env.authority_for(env.domain1, "h3")}/data.json'
+ r = curl.http_download(urls=[urln], extra_args=['--http3-only'])
+ assert r.exit_code == 0, f'{r}'
+ r.check_stats(count=1, exp_status=200)
+ assert r.stats[0]['http_version'] == '3'
+
+ # download using only HTTP/3 on missing server
+ def test_06_02_h3_only(self, env: Env, httpd, nghttpx, repeat):
+ nghttpx.stop_if_running()
+ curl = CurlClient(env=env)
+ urln = f'https://{env.authority_for(env.domain1, "h3")}/data.json'
+ r = curl.http_download(urls=[urln], extra_args=['--http3-only'])
+ assert r.exit_code == 7, f'{r}' # could not connect
+
+ # download using HTTP/3 on missing server with fallback on h2
+ def test_06_03_h3_fallback_h2(self, env: Env, httpd, nghttpx, repeat):
+ nghttpx.stop_if_running()
+ curl = CurlClient(env=env)
+ urln = f'https://{env.authority_for(env.domain1, "h3")}/data.json'
+ r = curl.http_download(urls=[urln], extra_args=['--http3'])
+ assert r.exit_code == 0, f'{r}'
+ r.check_stats(count=1, exp_status=200)
+ assert r.stats[0]['http_version'] == '2'
+
+ # download using HTTP/3 on missing server with fallback on http/1.1
+ def test_06_04_h3_fallback_h1(self, env: Env, httpd, nghttpx, repeat):
+ nghttpx.stop_if_running()
+ curl = CurlClient(env=env)
+ urln = f'https://{env.authority_for(env.domain2, "h3")}/data.json'
+ r = curl.http_download(urls=[urln], extra_args=['--http3'])
+ assert r.exit_code == 0, f'{r}'
+ r.check_stats(count=1, exp_status=200)
+ assert r.stats[0]['http_version'] == '1.1'
tests/tests-httpd/test_07_upload.py+150 -0
@@ -0,0 +1,150 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#***************************************************************************
+# _ _ ____ _
+# Project ___| | | | _ \| |
+# / __| | | | |_) | |
+# | (__| |_| | _ <| |___
+# \___|\___/|_| \_\_____|
+#
+# Copyright (C) 2008 - 2022, Daniel Stenberg, <[email protected]>, 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
+#
+###########################################################################
+#
+import logging
+import os
+import pytest
+
+from testenv import Env, CurlClient
+
+
+log = logging.getLogger(__name__)
+
+
+@pytest.mark.skipif(condition=Env.setup_incomplete(),
+ reason=f"missing: {Env.incomplete_reason()}")
+class TestUpload:
+
+ @pytest.fixture(autouse=True, scope='class')
+ def _class_scope(self, env, nghttpx):
+ if env.have_h3():
+ nghttpx.start_if_needed()
+ s90 = "01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678\n"
+ with open(os.path.join(env.gen_dir, "data-100k"), 'w') as f:
+ for i in range(1000):
+ f.write(f"{i:09d}-{s90}")
+
+ # upload small data, check that this is what was echoed
+ @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
+ def test_07_01_upload_1_small(self, env: Env, httpd, nghttpx, repeat, proto):
+ if proto == 'h3' and not env.have_h3():
+ pytest.skip("h3 not supported")
+ data = '0123456789'
+ curl = CurlClient(env=env)
+ url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-0]'
+ r = curl.http_upload(urls=[url], data=data, alpn_proto=proto)
+ assert r.exit_code == 0, f'{r}'
+ r.check_stats(count=1, exp_status=200)
+ respdata = open(curl.response_file(0)).readlines()
+ assert respdata == [data]
+
+ # upload large data, check that this is what was echoed
+ @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
+ def test_07_02_upload_1_large(self, env: Env, httpd, nghttpx, repeat, proto):
+ if proto == 'h3' and not env.have_h3():
+ pytest.skip("h3 not supported")
+ fdata = os.path.join(env.gen_dir, 'data-100k')
+ curl = CurlClient(env=env)
+ url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-0]'
+ r = curl.http_upload(urls=[url], data=f'@{fdata}', alpn_proto=proto)
+ assert r.exit_code == 0, f'{r}'
+ r.check_stats(count=1, exp_status=200)
+ indata = open(fdata).readlines()
+ respdata = open(curl.response_file(0)).readlines()
+ assert respdata == indata
+
+ # upload data sequentially, check that they were echoed
+ @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
+ def test_07_10_upload_sequential(self, env: Env, httpd, nghttpx, repeat, proto):
+ if proto == 'h3' and not env.have_h3():
+ pytest.skip("h3 not supported")
+ count = 50
+ data = '0123456789'
+ curl = CurlClient(env=env)
+ url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-{count-1}]'
+ r = curl.http_upload(urls=[url], data=data, alpn_proto=proto)
+ assert r.exit_code == 0, f'{r}'
+ r.check_stats(count=count, exp_status=200)
+ for i in range(count):
+ respdata = open(curl.response_file(i)).readlines()
+ assert respdata == [data]
+
+ # upload large data sequentially, check that this is what was echoed
+ @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
+ def test_07_11_upload_seq_large(self, env: Env, httpd, nghttpx, repeat, proto):
+ if proto == 'h3' and not env.have_h3():
+ pytest.skip("h3 not supported")
+ fdata = os.path.join(env.gen_dir, 'data-100k')
+ count = 50
+ curl = CurlClient(env=env)
+ url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-{count-1}]'
+ r = curl.http_upload(urls=[url], data=f'@{fdata}', alpn_proto=proto)
+ assert r.exit_code == 0, f'{r}'
+ r.check_stats(count=count, exp_status=200)
+ indata = open(fdata).readlines()
+ r.check_stats(count=count, exp_status=200)
+ for i in range(count):
+ respdata = open(curl.response_file(i)).readlines()
+ assert respdata == indata
+
+ # upload data parallel, check that they were echoed
+ @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
+ def test_07_20_upload_parallel(self, env: Env, httpd, nghttpx, repeat, proto):
+ if proto == 'h3' and not env.have_h3():
+ pytest.skip("h3 not supported")
+ count = 50
+ data = '0123456789'
+ curl = CurlClient(env=env)
+ url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-{count-1}]'
+ r = curl.http_upload(urls=[url], data=data, alpn_proto=proto,
+ extra_args=['--parallel'])
+ assert r.exit_code == 0, f'{r}'
+ r.check_stats(count=count, exp_status=200)
+ for i in range(count):
+ respdata = open(curl.response_file(i)).readlines()
+ assert respdata == [data]
+
+ # upload large data parallel, check that this is what was echoed
+ @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
+ def test_07_21_upload_parallel_large(self, env: Env, httpd, nghttpx, repeat, proto):
+ if proto == 'h3' and not env.have_h3():
+ pytest.skip("h3 not supported")
+ if proto == 'h3' and env.curl_uses_lib('quiche'):
+ pytest.skip("quiche stalls on parallel, large uploads")
+ fdata = os.path.join(env.gen_dir, 'data-100k')
+ count = 3
+ curl = CurlClient(env=env)
+ url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-{count-1}]'
+ r = curl.http_upload(urls=[url], data=f'@{fdata}', alpn_proto=proto,
+ extra_args=['--parallel'])
+ assert r.exit_code == 0, f'{r}'
+ r.check_stats(count=count, exp_status=200)
+ indata = open(fdata).readlines()
+ r.check_stats(count=count, exp_status=200)
+ for i in range(count):
+ respdata = open(curl.response_file(i)).readlines()
+ assert respdata == indata
+
tests/tests-httpd/testenv/__init__.py+1 -0
@@ -26,6 +26,7 @@
#
from .env import Env
from .certs import TestCA, Credentials
+from .caddy import Caddy
from .httpd import Httpd
from .curl import CurlClient, ExecResult
from .nghttpx import Nghttpx
tests/tests-httpd/testenv/caddy.py+164 -0
@@ -0,0 +1,164 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#***************************************************************************
+# _ _ ____ _
+# Project ___| | | | _ \| |
+# / __| | | | |_) | |
+# | (__| |_| | _ <| |___
+# \___|\___/|_| \_\_____|
+#
+# Copyright (C) 2008 - 2022, Daniel Stenberg, <[email protected]>, 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
+#
+###########################################################################
+#
+import logging
+import os
+import subprocess
+import time
+from datetime import timedelta, datetime
+from json import JSONEncoder
+
+from .curl import CurlClient
+from .env import Env
+
+
+log = logging.getLogger(__name__)
+
+
+class Caddy:
+
+ def __init__(self, env: Env):
+ self.env = env
+ self._caddy = os.environ['CADDY'] if 'CADDY' in os.environ else env.caddy
+ self._caddy_dir = os.path.join(env.gen_dir, 'caddy')
+ self._docs_dir = os.path.join(self._caddy_dir, 'docs')
+ self._conf_file = os.path.join(self._caddy_dir, 'Caddyfile')
+ self._error_log = os.path.join(self._caddy_dir, 'caddy.log')
+ self._tmp_dir = os.path.join(self._caddy_dir, 'tmp')
+ self._process = None
+ self._rmf(self._error_log)
+
+ @property
+ def docs_dir(self):
+ return self._docs_dir
+
+ def clear_logs(self):
+ self._rmf(self._error_log)
+
+ def is_running(self):
+ if self._process:
+ self._process.poll()
+ return self._process.returncode is None
+ return False
+
+ def start_if_needed(self):
+ if not self.is_running():
+ return self.start()
+ return True
+
+ def start(self, wait_live=True):
+ self._mkpath(self._tmp_dir)
+ if self._process:
+ self.stop()
+ self._write_config()
+ args = [
+ self._caddy, 'run'
+ ]
+ caddyerr = open(self._error_log, 'a')
+ self._process = subprocess.Popen(args=args, cwd=self._caddy_dir, stderr=caddyerr)
+ if self._process.returncode is not None:
+ return False
+ return not wait_live or self.wait_live(timeout=timedelta(seconds=5))
+
+ def stop_if_running(self):
+ if self.is_running():
+ return self.stop()
+ return True
+
+ def stop(self, wait_dead=True):
+ self._mkpath(self._tmp_dir)
+ if self._process:
+ self._process.terminate()
+ self._process.wait(timeout=2)
+ self._process = None
+ return not wait_dead or self.wait_dead(timeout=timedelta(seconds=5))
+ return True
+
+ def restart(self):
+ self.stop()
+ return self.start()
+
+ def wait_dead(self, timeout: timedelta):
+ curl = CurlClient(env=self.env, run_dir=self._tmp_dir)
+ try_until = datetime.now() + timeout
+ while datetime.now() < try_until:
+ check_url = f'https://{self.env.domain1}:{self.env.caddy_port}/'
+ r = curl.http_get(url=check_url)
+ if r.exit_code != 0:
+ return True
+ log.debug(f'waiting for caddy to stop responding: {r}')
+ time.sleep(.1)
+ log.debug(f"Server still responding after {timeout}")
+ return False
+
+ def wait_live(self, timeout: timedelta):
+ curl = CurlClient(env=self.env, run_dir=self._tmp_dir)
+ try_until = datetime.now() + timeout
+ while datetime.now() < try_until:
+ check_url = f'https://{self.env.domain1}:{self.env.caddy_port}/'
+ r = curl.http_get(url=check_url)
+ if r.exit_code == 0:
+ return True
+ log.error(f'curl: {r}')
+ log.debug(f'waiting for caddy to become responsive: {r}')
+ time.sleep(.1)
+ log.error(f"Server still not responding after {timeout}")
+ return False
+
+ def _rmf(self, path):
+ if os.path.exists(path):
+ return os.remove(path)
+
+ def _mkpath(self, path):
+ if not os.path.exists(path):
+ return os.makedirs(path)
+
+ def _write_config(self):
+ domain1 = self.env.domain1
+ creds1 = self.env.get_credentials(domain1)
+ self._mkpath(self._docs_dir)
+ self._mkpath(self._tmp_dir)
+ with open(os.path.join(self._docs_dir, 'data.json'), 'w') as fd:
+ data = {
+ 'server': f'{domain1}',
+ }
+ fd.write(JSONEncoder().encode(data))
+ with open(self._conf_file, 'w') as fd:
+ conf = [ # base server config
+ f'{{',
+ f' https_port {self.env.caddy_port}',
+ f' servers :{self.env.caddy_port} {{',
+ f' protocols h3 h2 h1',
+ f' }}',
+ f'}}',
+ f'{domain1}:{self.env.caddy_port} {{',
+ f' file_server * {{',
+ f' root {self._docs_dir}',
+ f' }}',
+ f' tls {creds1.cert_file} {creds1.pkey_file}',
+ f'}}',
+ ]
+ fd.write("\n".join(conf))
tests/tests-httpd/testenv/curl.py+69 -22
@@ -155,28 +155,36 @@ class ExecResult:
def add_assets(self, assets: List):
self._assets.extend(assets)
- def check_responses(self, count: int, exp_status: Optional[int] = None):
- if len(self.responses) != count:
- seen_queries = []
- for idx, resp in enumerate(self.responses):
- assert resp['status'] == 200, f'response #{idx} status: {resp["status"]}'
- if 'rquery' not in resp['header']:
- log.error(f'response #{idx} missing "rquery": {resp["header"]}')
- seen_queries.append(int(resp['header']['rquery']))
- for i in range(0, count-1):
- if i not in seen_queries:
- log.error(f'response for query {i} missing')
- if self.with_stats and len(self.stats) == count:
- log.error(f'got all {count} stats, though')
+ def check_responses(self, count: int, exp_status: Optional[int] = None,
+ exp_exitcode: Optional[int] = None):
assert len(self.responses) == count, \
f'response count: expected {count}, got {len(self.responses)}'
if exp_status is not None:
for idx, x in enumerate(self.responses):
assert x['status'] == exp_status, \
f'response #{idx} unexpectedstatus: {x["status"]}'
+ if exp_exitcode is not None:
+ for idx, x in enumerate(self.responses):
+ if 'exitcode' in x:
+ assert x['exitcode'] == 0, f'response #{idx} exitcode: {x["exitcode"]}'
if self.with_stats:
assert len(self.stats) == count, f'{self}'
+ def check_stats(self, count: int, exp_status: Optional[int] = None,
+ exp_exitcode: Optional[int] = None):
+ assert len(self.stats) == count, \
+ f'stats count: expected {count}, got {len(self.stats)}'
+ if exp_status is not None:
+ for idx, x in enumerate(self.stats):
+ assert 'http_code' in x, \
+ f'status #{idx} reports no http_code'
+ assert x['http_code'] == exp_status, \
+ f'status #{idx} unexpected http_code: {x["http_code"]}'
+ if exp_exitcode is not None:
+ for idx, x in enumerate(self.stats):
+ if 'exitcode' in x:
+ assert x['exitcode'] == 0, f'status #{idx} exitcode: {x["exitcode"]}'
+
class CurlClient:
@@ -186,7 +194,7 @@ class CurlClient:
'http/1.1': '--http1.1',
'h2': '--http2',
'h2c': '--http2',
- 'h3': '--http3',
+ 'h3': '--http3-only',
}
def __init__(self, env: Env, run_dir: Optional[str] = None):
@@ -219,6 +227,7 @@ class CurlClient:
def http_download(self, urls: List[str],
alpn_proto: Optional[str] = None,
with_stats: bool = True,
+ with_headers: bool = False,
extra_args: List[str] = None):
if extra_args is None:
extra_args = []
@@ -230,7 +239,41 @@ class CurlClient:
'-w', '%{json}\\n'
])
return self._raw(urls, alpn_proto=alpn_proto, options=extra_args,
- with_stats=with_stats)
+ with_stats=with_stats,
+ with_headers=with_headers)
+
+ def http_upload(self, urls: List[str], data: str,
+ alpn_proto: Optional[str] = None,
+ with_stats: bool = True,
+ with_headers: bool = False,
+ extra_args: Optional[List[str]] = None):
+ if extra_args is None:
+ extra_args = []
+ extra_args.extend([
+ '--data-binary', data, '-o', 'download_#1.data',
+ ])
+ if with_stats:
+ extra_args.extend([
+ '-w', '%{json}\\n'
+ ])
+ return self._raw(urls, alpn_proto=alpn_proto, options=extra_args,
+ with_stats=with_stats,
+ with_headers=with_headers)
+
+ def response_file(self, idx: int):
+ return os.path.join(self._run_dir, f'download_{idx}.data')
+
+ def run_direct(self, args, with_stats: bool = False):
+ my_args = [self._curl]
+ if with_stats:
+ my_args.extend([
+ '-w', '%{json}\\n'
+ ])
+ my_args.extend([
+ '-o', 'download.data',
+ ])
+ my_args.extend(args)
+ return self._run(args=my_args, with_stats=with_stats)
def _run(self, args, intext='', with_stats: bool = False):
self._rmf(self._stdoutfile)
@@ -252,12 +295,15 @@ class CurlClient:
def _raw(self, urls, timeout=10, options=None, insecure=False,
alpn_proto: Optional[str] = None,
- force_resolve=True, with_stats=False):
+ force_resolve=True,
+ with_stats=False,
+ with_headers=True):
args = self._complete_args(
urls=urls, timeout=timeout, options=options, insecure=insecure,
- alpn_proto=alpn_proto, force_resolve=force_resolve)
+ alpn_proto=alpn_proto, force_resolve=force_resolve,
+ with_headers=with_headers)
r = self._run(args, with_stats=with_stats)
- if r.exit_code == 0:
+ if r.exit_code == 0 and with_headers:
self._parse_headerfile(self._headerfile, r=r)
if r.json:
r.response["json"] = r.json
@@ -265,13 +311,14 @@ class CurlClient:
def _complete_args(self, urls, timeout=None, options=None,
insecure=False, force_resolve=True,
- alpn_proto: Optional[str] = None):
+ alpn_proto: Optional[str] = None,
+ with_headers: bool = True):
if not isinstance(urls, list):
urls = [urls]
- args = [
- self._curl, "-s", "--path-as-is", "-D", self._headerfile,
- ]
+ args = [self._curl, "-s", "--path-as-is"]
+ if with_headers:
+ args.extend(["-D", self._headerfile])
if self.env.verbose > 2:
args.extend(['--trace', self._tracefile, '--trace-time'])
tests/tests-httpd/testenv/env.py+101 -6
@@ -59,19 +59,41 @@ class EnvConfig:
self.config = DEF_CONFIG
# check cur and its features
self.curl = CURL
- self.curl_features = []
+ self.curl_props = {
+ 'version': None,
+ 'os': None,
+ 'features': [],
+ 'protocols': [],
+ 'libs': [],
+ 'lib_versions': [],
+ }
self.curl_protos = []
p = subprocess.run(args=[self.curl, '-V'],
capture_output=True, text=True)
if p.returncode != 0:
assert False, f'{self.curl} -V failed with exit code: {p.returncode}'
for l in p.stdout.splitlines(keepends=False):
+ if l.startswith('curl '):
+ m = re.match(r'^curl (?P<version>\S+) (?P<os>\S+) (?P<libs>.*)$', l)
+ if m:
+ self.curl_props['version'] = m.group('version')
+ self.curl_props['os'] = m.group('os')
+ self.curl_props['lib_versions'] = [
+ lib.lower() for lib in m.group('libs').split(' ')
+ ]
+ self.curl_props['libs'] = [
+ re.sub(r'/.*', '',lib) for lib in self.curl_props['lib_versions']
+ ]
if l.startswith('Features: '):
- self.curl_features = [feat.lower() for feat in l[10:].split(' ')]
+ self.curl_props['features'] = [
+ feat.lower() for feat in l[10:].split(' ')
+ ]
if l.startswith('Protocols: '):
- self.curl_protos = [prot.lower() for prot in l[11:].split(' ')]
+ self.curl_props['protocols'] = [
+ prot.lower() for prot in l[11:].split(' ')
+ ]
self.nghttpx_with_h3 = re.match(r'.* nghttp3/.*', p.stdout.strip())
- log.error(f'nghttpx -v: {p.stdout}')
+ log.debug(f'nghttpx -v: {p.stdout}')
self.http_port = self.config['test']['http_port']
self.https_port = self.config['test']['https_port']
@@ -81,6 +103,7 @@ class EnvConfig:
self.apxs = self.config['httpd']['apxs']
if len(self.apxs) == 0:
self.apxs = None
+ self._httpd_version = None
self.examples_pem = {
'key': 'xxx',
@@ -110,7 +133,39 @@ class EnvConfig:
self.nghttpx = None
else:
self.nghttpx_with_h3 = re.match(r'.* nghttp3/.*', p.stdout.strip()) is not None
- log.error(f'nghttpx -v: {p.stdout}')
+ log.debug(f'nghttpx -v: {p.stdout}')
+
+ self.caddy = self.config['caddy']['caddy']
+ if len(self.caddy) == 0:
+ self.caddy = 'caddy'
+ if self.caddy is not None:
+ try:
+ p = subprocess.run(args=[self.caddy, 'version'],
+ capture_output=True, text=True)
+ if p.returncode != 0:
+ # not a working caddy
+ self.caddy = None
+ except:
+ self.caddy = None
+ self.caddy_port = self.config['caddy']['port']
+
+ @property
+ def httpd_version(self):
+ if self._httpd_version is None and self.apxs is not None:
+ p = subprocess.run(args=[self.apxs, '-q', 'HTTPD_VERSION'],
+ capture_output=True, text=True)
+ if p.returncode != 0:
+ raise Exception(f'{self.apxs} failed to query HTTPD_VERSION: {p}')
+ self._httpd_version = p.stdout.strip()
+ return self._httpd_version
+
+ def _versiontuple(self, v):
+ v = re.sub(r'(\d+\.\d+(\.\d+)?)(-\S+)?', r'\1', v)
+ return tuple(map(int, v.split('.')))
+
+ def httpd_is_at_least(self, minv):
+ hv = self._versiontuple(self.httpd_version)
+ return hv >= self._versiontuple(minv)
def is_complete(self) -> bool:
return os.path.isfile(self.httpd) and \
@@ -146,14 +201,46 @@ class Env:
def have_h3_server() -> bool:
return Env.CONFIG.nghttpx_with_h3
+ @staticmethod
+ def have_h2_curl() -> bool:
+ return 'http2' in Env.CONFIG.curl_props['features']
+
@staticmethod
def have_h3_curl() -> bool:
- return 'http3' in Env.CONFIG.curl_features
+ return 'http3' in Env.CONFIG.curl_props['features']
+
+ @staticmethod
+ def curl_uses_lib(libname: str) -> bool:
+ return libname.lower() in Env.CONFIG.curl_props['libs']
+
+ @staticmethod
+ def curl_lib_version(libname: str) -> str:
+ prefix = f'{libname.lower()}/'
+ for lversion in Env.CONFIG.curl_props['lib_versions']:
+ if lversion.startswith(prefix):
+ return lversion[len(prefix):]
+ return 'unknown'
+
+ @staticmethod
+ def curl_os() -> bool:
+ return Env.CONFIG.curl_props['os']
+
+ @staticmethod
+ def curl_version() -> bool:
+ return Env.CONFIG.curl_props['version']
@staticmethod
def have_h3() -> bool:
return Env.have_h3_curl() and Env.have_h3_server()
+ @staticmethod
+ def httpd_version() -> str:
+ return Env.CONFIG.httpd_version
+
+ @staticmethod
+ def httpd_is_at_least(minv) -> bool:
+ return Env.CONFIG.httpd_is_at_least(minv)
+
def __init__(self, pytestconfig=None):
self._verbose = pytestconfig.option.verbose \
if pytestconfig is not None else 0
@@ -214,6 +301,14 @@ class Env:
def h3_port(self) -> str:
return self.CONFIG.h3_port
+ @property
+ def caddy(self) -> str:
+ return self.CONFIG.caddy
+
+ @property
+ def caddy_port(self) -> str:
+ return self.CONFIG.caddy_port
+
@property
def curl(self) -> str:
return self.CONFIG.curl
tests/tests-httpd/testenv/httpd.py+12 -13
@@ -69,22 +69,24 @@ class Httpd:
self._error_log = os.path.join(self._logs_dir, 'error_log')
self._tmp_dir = os.path.join(self._apache_dir, 'tmp')
self._mods_dir = None
- if env.apxs is not None:
- p = subprocess.run(args=[env.apxs, '-q', 'libexecdir'],
- capture_output=True, text=True)
- if p.returncode != 0:
- raise Exception(f'{env.apxs} failed to query libexecdir: {p}')
- self._mods_dir = p.stdout.strip()
- else:
- for md in self.COMMON_MODULES_DIRS:
- if os.path.isdir(md):
- self._mods_dir = md
+ assert env.apxs
+ p = subprocess.run(args=[env.apxs, '-q', 'libexecdir'],
+ capture_output=True, text=True)
+ if p.returncode != 0:
+ raise Exception(f'{env.apxs} failed to query libexecdir: {p}')
+ self._mods_dir = p.stdout.strip()
if self._mods_dir is None:
raise Exception(f'apache modules dir cannot be found')
+ if not os.path.exists(self._mods_dir):
+ raise Exception(f'apache modules dir does not exist: {self._mods_dir}')
self._process = None
self._rmf(self._error_log)
self._init_curltest()
+ @property
+ def docs_dir(self):
+ return self._docs_dir
+
def clear_logs(self):
self._rmf(self._error_log)
@@ -213,9 +215,6 @@ class Httpd:
f'Listen {self.env.http_port}',
f'Listen {self.env.https_port}',
f'TypesConfig "{self._conf_dir}/mime.types',
- # we want the quest string in a response header, so we
- # can check responses more easily
- f'Header set rquery "%{{QUERY_STRING}}s"',
]
conf.extend([ # plain http host for domain1
f'<VirtualHost *:{self.env.http_port}>',
tests/tests-httpd/testenv/nghttpx.py+76 -17
@@ -24,15 +24,16 @@
#
###########################################################################
#
-import datetime
import logging
import os
import signal
import subprocess
import time
from typing import Optional
+from datetime import datetime, timedelta
from .env import Env
+from .curl import CurlClient
log = logging.getLogger(__name__)
@@ -43,12 +44,18 @@ class Nghttpx:
def __init__(self, env: Env):
self.env = env
self._cmd = env.nghttpx
- self._pid_file = os.path.join(env.gen_dir, 'nghttpx.pid')
- self._conf_file = os.path.join(env.gen_dir, 'nghttpx.conf')
- self._error_log = os.path.join(env.gen_dir, 'nghttpx.log')
- self._stderr = os.path.join(env.gen_dir, 'nghttpx.stderr')
+ self._run_dir = os.path.join(env.gen_dir, 'nghttpx')
+ self._pid_file = os.path.join(self._run_dir, 'nghttpx.pid')
+ self._conf_file = os.path.join(self._run_dir, 'nghttpx.conf')
+ self._error_log = os.path.join(self._run_dir, 'nghttpx.log')
+ self._stderr = os.path.join(self._run_dir, 'nghttpx.stderr')
+ self._tmp_dir = os.path.join(self._run_dir, 'tmp')
self._process = None
self._process: Optional[subprocess.Popen] = None
+ self._rmf(self._pid_file)
+ self._rmf(self._error_log)
+ self._mkpath(self._run_dir)
+ self._write_config()
def exists(self):
return os.path.exists(self._cmd)
@@ -63,10 +70,15 @@ class Nghttpx:
return self._process.returncode is None
return False
- def start(self):
+ def start_if_needed(self):
+ if not self.is_running():
+ return self.start()
+ return True
+
+ def start(self, wait_live=True):
+ self._mkpath(self._tmp_dir)
if self._process:
self.stop()
- self._write_config()
args = [
self._cmd,
f'--frontend=*,{self.env.h3_port};quic',
@@ -82,31 +94,78 @@ class Nghttpx:
]
ngerr = open(self._stderr, 'a')
self._process = subprocess.Popen(args=args, stderr=ngerr)
- return self._process.returncode is None
+ if self._process.returncode is not None:
+ return False
+ return not wait_live or self.wait_live(timeout=timedelta(seconds=5))
+
+ def stop_if_running(self):
+ if self.is_running():
+ return self.stop()
+ return True
- def stop(self):
+ def stop(self, wait_dead=True):
+ self._mkpath(self._tmp_dir)
if self._process:
self._process.terminate()
self._process.wait(timeout=2)
self._process = None
+ return not wait_dead or self.wait_dead(timeout=timedelta(seconds=5))
return True
def restart(self):
self.stop()
return self.start()
- def reload(self, timeout: datetime.timedelta):
+ def reload(self, timeout: timedelta):
if self._process:
running = self._process
+ self._process = None
os.kill(running.pid, signal.SIGQUIT)
- self.start()
- try:
- log.debug(f'waiting for nghttpx({running.pid}) to exit.')
- running.wait(timeout=timeout.seconds)
- log.debug(f'nghttpx({running.pid}) terminated -> {running.returncode}')
+ end_wait = datetime.now() + timeout
+ if not self.start(wait_live=False):
+ self._process = running
+ return False
+ while datetime.now() < end_wait:
+ try:
+ log.debug(f'waiting for nghttpx({running.pid}) to exit.')
+ running.wait(2)
+ log.debug(f'nghttpx({running.pid}) terminated -> {running.returncode}')
+ break
+ except subprocess.TimeoutExpired:
+ log.warning(f'nghttpx({running.pid}), not shut down yet.')
+ os.kill(running.pid, signal.SIGQUIT)
+ if datetime.now() >= end_wait:
+ log.error(f'nghttpx({running.pid}), terminate forcefully.')
+ os.kill(running.pid, signal.SIGKILL)
+ running.terminate()
+ running.wait(1)
+ return self.wait_live(timeout=timedelta(seconds=5))
+ return False
+
+ def wait_dead(self, timeout: timedelta):
+ curl = CurlClient(env=self.env, run_dir=self._tmp_dir)
+ try_until = datetime.now() + timeout
+ while datetime.now() < try_until:
+ check_url = f'https://{self.env.domain1}:{self.env.h3_port}/'
+ r = curl.http_get(url=check_url, extra_args=['--http3-only'])
+ if r.exit_code != 0:
+ return True
+ log.debug(f'waiting for nghttpx to stop responding: {r}')
+ time.sleep(.1)
+ log.debug(f"Server still responding after {timeout}")
+ return False
+
+ def wait_live(self, timeout: timedelta):
+ curl = CurlClient(env=self.env, run_dir=self._tmp_dir)
+ try_until = datetime.now() + timeout
+ while datetime.now() < try_until:
+ check_url = f'https://{self.env.domain1}:{self.env.h3_port}/'
+ r = curl.http_get(url=check_url, extra_args=['--http3-only'])
+ if r.exit_code == 0:
return True
- except subprocess.TimeoutExpired:
- log.error(f'SIGQUIT nghttpx({running.pid}), but did not shut down.')
+ log.debug(f'waiting for nghttpx to become responsive: {r}')
+ time.sleep(.1)
+ log.error(f"Server still not responding after {timeout}")
return False
def _rmf(self, path):
tests/unit/unit2600.c+4 -1
@@ -89,6 +89,7 @@ static struct test_result *current_tr;
struct cf_test_ctx {
int ai_family;
+ int transport;
char id[16];
struct curltime started;
timediff_t fail_delay_ms;
@@ -147,7 +148,8 @@ static struct Curl_cftype cft_test = {
static CURLcode cf_test_create(struct Curl_cfilter **pcf,
struct Curl_easy *data,
struct connectdata *conn,
- const struct Curl_addrinfo *ai)
+ const struct Curl_addrinfo *ai,
+ int transport)
{
struct cf_test_ctx *ctx = NULL;
struct Curl_cfilter *cf = NULL;
@@ -162,6 +164,7 @@ static CURLcode cf_test_create(struct Curl_cfilter **pcf,
goto out;
}
ctx->ai_family = ai->ai_family;
+ ctx->transport = transport;
ctx->started = Curl_now();
#ifdef ENABLE_IPV6
if(ctx->ai_family == AF_INET6) {