mirror of
https://github.com/curl/curl.git
synced 2026-08-25 23:33:37 +03:00
TLS: TLSv1.3 earlydata support for curl
Based on #14135, implement TLSv1.3 earlydata support for the curl command line, libcurl and its implementation in GnuTLS. If a known TLS session announces early data support, and the feature is enabled *and* it is not a "connect-only" transfer, delay the TLS handshake until the first request is being sent. - Add --tls-earldata as new boolean command line option for curl. - Add CURLSSLOPT_EARLYDATA to libcurl to enable use of the feature. - Add CURLINFO_EARLYDATA_SENT_T to libcurl, reporting the amount of bytes sent and accepted/rejected by the server. Implementation details: - store the ALPN protocol selected at the SSL session. - When reusing the session and enabling earlydata, use exactly that ALPN protocol for negoptiation with the server. When the sessions ALPN does not match the connections ALPN, earlydata will not be enabled. - Check that the server selected the correct ALPN protocol for an earlydata connect. If the server does not confirm or reports something different, the connect fails. - HTTP/2: delay sending the initial SETTINGS frames during connect, if not connect-only. Verification: - add test_02_32 to verify earlydata GET with nghttpx. - add test_07_70 to verify earlydata PUT with nghttpx. - add support in 'hx-download', 'hx-upload' clients for the feature Assisted-by: ad-chaos on github Closes #15211
This commit is contained in:
parent
d0377f5a86
commit
962097b8dd
40 changed files with 899 additions and 134 deletions
|
|
@ -228,8 +228,10 @@ static int my_progress_cb(void *userdata,
|
|||
}
|
||||
|
||||
static int setup(CURL *hnd, const char *url, struct transfer *t,
|
||||
int http_version)
|
||||
int http_version, struct curl_slist *host,
|
||||
CURLSH *share, int use_earlydata)
|
||||
{
|
||||
curl_easy_setopt(hnd, CURLOPT_SHARE, share);
|
||||
curl_easy_setopt(hnd, CURLOPT_URL, url);
|
||||
curl_easy_setopt(hnd, CURLOPT_HTTP_VERSION, http_version);
|
||||
curl_easy_setopt(hnd, CURLOPT_SSL_VERIFYPEER, 0L);
|
||||
|
|
@ -240,8 +242,12 @@ static int setup(CURL *hnd, const char *url, struct transfer *t,
|
|||
curl_easy_setopt(hnd, CURLOPT_NOPROGRESS, 0L);
|
||||
curl_easy_setopt(hnd, CURLOPT_XFERINFOFUNCTION, my_progress_cb);
|
||||
curl_easy_setopt(hnd, CURLOPT_XFERINFODATA, t);
|
||||
if(use_earlydata)
|
||||
curl_easy_setopt(hnd, CURLOPT_SSL_OPTIONS, (long)CURLSSLOPT_EARLYDATA);
|
||||
if(forbid_reuse)
|
||||
curl_easy_setopt(hnd, CURLOPT_FORBID_REUSE, 1L);
|
||||
if(host)
|
||||
curl_easy_setopt(hnd, CURLOPT_RESOLVE, host);
|
||||
|
||||
/* please be verbose */
|
||||
if(verbose) {
|
||||
|
|
@ -265,10 +271,14 @@ static void usage(const char *msg)
|
|||
" download a url with following options:\n"
|
||||
" -a abort paused transfer\n"
|
||||
" -m number max parallel downloads\n"
|
||||
" -n number total downloads\n"
|
||||
" -e use TLS early data when possible\n"
|
||||
" -f forbid connection reuse\n"
|
||||
" -n number total downloads\n");
|
||||
fprintf(stderr,
|
||||
" -A number abort transfer after `number` response bytes\n"
|
||||
" -F number fail writing response after `number` response bytes\n"
|
||||
" -P number pause transfer after `number` response bytes\n"
|
||||
" -r <host>:<port>:<addr> resolve information\n"
|
||||
" -V http_version (http/1.1, h2, h3) http version to use\n"
|
||||
);
|
||||
}
|
||||
|
|
@ -282,18 +292,21 @@ int main(int argc, char *argv[])
|
|||
#ifndef _MSC_VER
|
||||
CURLM *multi_handle;
|
||||
struct CURLMsg *m;
|
||||
CURLSH *share;
|
||||
const char *url;
|
||||
size_t i, n, max_parallel = 1;
|
||||
size_t active_transfers;
|
||||
size_t pause_offset = 0;
|
||||
size_t abort_offset = 0;
|
||||
size_t fail_offset = 0;
|
||||
int abort_paused = 0;
|
||||
int abort_paused = 0, use_earlydata = 0;
|
||||
struct transfer *t;
|
||||
int http_version = CURL_HTTP_VERSION_2_0;
|
||||
int ch;
|
||||
struct curl_slist *host = NULL;
|
||||
const char *resolve = NULL;
|
||||
|
||||
while((ch = getopt(argc, argv, "afhm:n:A:F:P:V:")) != -1) {
|
||||
while((ch = getopt(argc, argv, "aefhm:n:A:F:P:r:V:")) != -1) {
|
||||
switch(ch) {
|
||||
case 'h':
|
||||
usage(NULL);
|
||||
|
|
@ -301,6 +314,9 @@ int main(int argc, char *argv[])
|
|||
case 'a':
|
||||
abort_paused = 1;
|
||||
break;
|
||||
case 'e':
|
||||
use_earlydata = 1;
|
||||
break;
|
||||
case 'f':
|
||||
forbid_reuse = 1;
|
||||
break;
|
||||
|
|
@ -319,6 +335,9 @@ int main(int argc, char *argv[])
|
|||
case 'P':
|
||||
pause_offset = (size_t)strtol(optarg, NULL, 10);
|
||||
break;
|
||||
case 'r':
|
||||
resolve = optarg;
|
||||
break;
|
||||
case 'V': {
|
||||
if(!strcmp("http/1.1", optarg))
|
||||
http_version = CURL_HTTP_VERSION_1_1;
|
||||
|
|
@ -349,6 +368,21 @@ int main(int argc, char *argv[])
|
|||
}
|
||||
url = argv[0];
|
||||
|
||||
if(resolve)
|
||||
host = curl_slist_append(NULL, resolve);
|
||||
|
||||
share = curl_share_init();
|
||||
if(!share) {
|
||||
fprintf(stderr, "error allocating share\n");
|
||||
return 1;
|
||||
}
|
||||
curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE);
|
||||
curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
|
||||
curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION);
|
||||
curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT);
|
||||
curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_PSL);
|
||||
curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_HSTS);
|
||||
|
||||
transfers = calloc(transfer_count, sizeof(*transfers));
|
||||
if(!transfers) {
|
||||
fprintf(stderr, "error allocating transfer structs\n");
|
||||
|
|
@ -371,7 +405,8 @@ int main(int argc, char *argv[])
|
|||
for(i = 0; i < n; ++i) {
|
||||
t = &transfers[i];
|
||||
t->easy = curl_easy_init();
|
||||
if(!t->easy || setup(t->easy, url, t, http_version)) {
|
||||
if(!t->easy ||
|
||||
setup(t->easy, url, t, http_version, host, share, use_earlydata)) {
|
||||
fprintf(stderr, "[t-%d] FAILED setup\n", (int)i);
|
||||
return 1;
|
||||
}
|
||||
|
|
@ -404,6 +439,11 @@ int main(int argc, char *argv[])
|
|||
if(t) {
|
||||
t->done = 1;
|
||||
fprintf(stderr, "[t-%d] FINISHED\n", t->idx);
|
||||
if(use_earlydata) {
|
||||
curl_off_t sent;
|
||||
curl_easy_getinfo(e, CURLINFO_EARLYDATA_SENT_T, &sent);
|
||||
fprintf(stderr, "[t-%d] EarlyData: %ld\n", t->idx, (long)sent);
|
||||
}
|
||||
}
|
||||
else {
|
||||
curl_easy_cleanup(e);
|
||||
|
|
@ -444,7 +484,9 @@ int main(int argc, char *argv[])
|
|||
t = &transfers[i];
|
||||
if(!t->started) {
|
||||
t->easy = curl_easy_init();
|
||||
if(!t->easy || setup(t->easy, url, t, http_version)) {
|
||||
if(!t->easy ||
|
||||
setup(t->easy, url, t, http_version, host, share,
|
||||
use_earlydata)) {
|
||||
fprintf(stderr, "[t-%d] FAILED setup\n", (int)i);
|
||||
return 1;
|
||||
}
|
||||
|
|
@ -463,6 +505,8 @@ int main(int argc, char *argv[])
|
|||
|
||||
} while(active_transfers); /* as long as we have transfers going */
|
||||
|
||||
curl_multi_cleanup(multi_handle);
|
||||
|
||||
for(i = 0; i < transfer_count; ++i) {
|
||||
t = &transfers[i];
|
||||
if(t->out) {
|
||||
|
|
@ -476,7 +520,7 @@ int main(int argc, char *argv[])
|
|||
}
|
||||
free(transfers);
|
||||
|
||||
curl_multi_cleanup(multi_handle);
|
||||
curl_share_cleanup(share);
|
||||
|
||||
return 0;
|
||||
#else
|
||||
|
|
|
|||
|
|
@ -252,8 +252,10 @@ static int my_progress_cb(void *userdata,
|
|||
}
|
||||
|
||||
static int setup(CURL *hnd, const char *url, struct transfer *t,
|
||||
int http_version)
|
||||
int http_version, struct curl_slist *host,
|
||||
CURLSH *share, int use_earlydata, int announce_length)
|
||||
{
|
||||
curl_easy_setopt(hnd, CURLOPT_SHARE, share);
|
||||
curl_easy_setopt(hnd, CURLOPT_URL, url);
|
||||
curl_easy_setopt(hnd, CURLOPT_HTTP_VERSION, http_version);
|
||||
curl_easy_setopt(hnd, CURLOPT_SSL_VERIFYPEER, 0L);
|
||||
|
|
@ -261,6 +263,8 @@ static int setup(CURL *hnd, const char *url, struct transfer *t,
|
|||
curl_easy_setopt(hnd, CURLOPT_BUFFERSIZE, (long)(128 * 1024));
|
||||
curl_easy_setopt(hnd, CURLOPT_WRITEFUNCTION, my_write_cb);
|
||||
curl_easy_setopt(hnd, CURLOPT_WRITEDATA, t);
|
||||
if(use_earlydata)
|
||||
curl_easy_setopt(hnd, CURLOPT_SSL_OPTIONS, (long)CURLSSLOPT_EARLYDATA);
|
||||
|
||||
if(!t->method || !strcmp("PUT", t->method))
|
||||
curl_easy_setopt(hnd, CURLOPT_UPLOAD, 1L);
|
||||
|
|
@ -272,11 +276,16 @@ static int setup(CURL *hnd, const char *url, struct transfer *t,
|
|||
}
|
||||
curl_easy_setopt(hnd, CURLOPT_READFUNCTION, my_read_cb);
|
||||
curl_easy_setopt(hnd, CURLOPT_READDATA, t);
|
||||
if(announce_length)
|
||||
curl_easy_setopt(hnd, CURLOPT_INFILESIZE_LARGE, t->send_total);
|
||||
|
||||
curl_easy_setopt(hnd, CURLOPT_NOPROGRESS, 0L);
|
||||
curl_easy_setopt(hnd, CURLOPT_XFERINFOFUNCTION, my_progress_cb);
|
||||
curl_easy_setopt(hnd, CURLOPT_XFERINFODATA, t);
|
||||
if(forbid_reuse)
|
||||
curl_easy_setopt(hnd, CURLOPT_FORBID_REUSE, 1L);
|
||||
if(host)
|
||||
curl_easy_setopt(hnd, CURLOPT_RESOLVE, host);
|
||||
|
||||
/* please be verbose */
|
||||
if(verbose) {
|
||||
|
|
@ -299,11 +308,13 @@ static void usage(const char *msg)
|
|||
"usage: [options] url\n"
|
||||
" upload to a url with following options:\n"
|
||||
" -a abort paused transfer\n"
|
||||
" -e use TLS earlydata\n"
|
||||
" -m number max parallel uploads\n"
|
||||
" -n number total uploads\n"
|
||||
" -A number abort transfer after `number` request body bytes\n"
|
||||
" -F number fail reading request body after `number` of bytes\n"
|
||||
" -P number pause transfer after `number` request body bytes\n"
|
||||
" -r <host>:<port>:<addr> resolve information\n"
|
||||
" -S number size to upload\n"
|
||||
" -V http_version (http/1.1, h2, h3) http version to use\n"
|
||||
);
|
||||
|
|
@ -318,6 +329,7 @@ int main(int argc, char *argv[])
|
|||
#ifndef _MSC_VER
|
||||
CURLM *multi_handle;
|
||||
struct CURLMsg *m;
|
||||
CURLSH *share;
|
||||
const char *url;
|
||||
const char *method = "PUT";
|
||||
size_t i, n, max_parallel = 1;
|
||||
|
|
@ -328,11 +340,15 @@ int main(int argc, char *argv[])
|
|||
size_t send_total = (128 * 1024);
|
||||
int abort_paused = 0;
|
||||
int reuse_easy = 0;
|
||||
int use_earlydata = 0;
|
||||
int announce_length = 0;
|
||||
struct transfer *t;
|
||||
int http_version = CURL_HTTP_VERSION_2_0;
|
||||
struct curl_slist *host = NULL;
|
||||
const char *resolve = NULL;
|
||||
int ch;
|
||||
|
||||
while((ch = getopt(argc, argv, "afhm:n:A:F:M:P:RS:V:")) != -1) {
|
||||
while((ch = getopt(argc, argv, "aefhlm:n:A:F:M:P:r:RS:V:")) != -1) {
|
||||
switch(ch) {
|
||||
case 'h':
|
||||
usage(NULL);
|
||||
|
|
@ -340,9 +356,15 @@ int main(int argc, char *argv[])
|
|||
case 'a':
|
||||
abort_paused = 1;
|
||||
break;
|
||||
case 'e':
|
||||
use_earlydata = 1;
|
||||
break;
|
||||
case 'f':
|
||||
forbid_reuse = 1;
|
||||
break;
|
||||
case 'l':
|
||||
announce_length = 1;
|
||||
break;
|
||||
case 'm':
|
||||
max_parallel = (size_t)strtol(optarg, NULL, 10);
|
||||
break;
|
||||
|
|
@ -361,6 +383,9 @@ int main(int argc, char *argv[])
|
|||
case 'P':
|
||||
pause_offset = (size_t)strtol(optarg, NULL, 10);
|
||||
break;
|
||||
case 'r':
|
||||
resolve = optarg;
|
||||
break;
|
||||
case 'R':
|
||||
reuse_easy = 1;
|
||||
break;
|
||||
|
|
@ -402,6 +427,21 @@ int main(int argc, char *argv[])
|
|||
}
|
||||
url = argv[0];
|
||||
|
||||
if(resolve)
|
||||
host = curl_slist_append(NULL, resolve);
|
||||
|
||||
share = curl_share_init();
|
||||
if(!share) {
|
||||
fprintf(stderr, "error allocating share\n");
|
||||
return 1;
|
||||
}
|
||||
curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE);
|
||||
curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
|
||||
curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION);
|
||||
curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT);
|
||||
curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_PSL);
|
||||
curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_HSTS);
|
||||
|
||||
transfers = calloc(transfer_count, sizeof(*transfers));
|
||||
if(!transfers) {
|
||||
fprintf(stderr, "error allocating transfer structs\n");
|
||||
|
|
@ -429,7 +469,8 @@ int main(int argc, char *argv[])
|
|||
for(i = 0; i < transfer_count; ++i) {
|
||||
t = &transfers[i];
|
||||
t->easy = easy;
|
||||
if(setup(t->easy, url, t, http_version)) {
|
||||
if(setup(t->easy, url, t, http_version, host, share, use_earlydata,
|
||||
announce_length)) {
|
||||
fprintf(stderr, "[t-%d] FAILED setup\n", (int)i);
|
||||
return 1;
|
||||
}
|
||||
|
|
@ -450,7 +491,8 @@ int main(int argc, char *argv[])
|
|||
for(i = 0; i < n; ++i) {
|
||||
t = &transfers[i];
|
||||
t->easy = curl_easy_init();
|
||||
if(!t->easy || setup(t->easy, url, t, http_version)) {
|
||||
if(!t->easy || setup(t->easy, url, t, http_version, host, share,
|
||||
use_earlydata, announce_length)) {
|
||||
fprintf(stderr, "[t-%d] FAILED setup\n", (int)i);
|
||||
return 1;
|
||||
}
|
||||
|
|
@ -483,6 +525,11 @@ int main(int argc, char *argv[])
|
|||
if(t) {
|
||||
t->done = 1;
|
||||
fprintf(stderr, "[t-%d] FINISHED\n", t->idx);
|
||||
if(use_earlydata) {
|
||||
curl_off_t sent;
|
||||
curl_easy_getinfo(e, CURLINFO_EARLYDATA_SENT_T, &sent);
|
||||
fprintf(stderr, "[t-%d] EarlyData: %ld\n", t->idx, (long)sent);
|
||||
}
|
||||
}
|
||||
else {
|
||||
curl_easy_cleanup(e);
|
||||
|
|
@ -523,7 +570,8 @@ int main(int argc, char *argv[])
|
|||
t = &transfers[i];
|
||||
if(!t->started) {
|
||||
t->easy = curl_easy_init();
|
||||
if(!t->easy || setup(t->easy, url, t, http_version)) {
|
||||
if(!t->easy || setup(t->easy, url, t, http_version, host,
|
||||
share, use_earlydata, announce_length)) {
|
||||
fprintf(stderr, "[t-%d] FAILED setup\n", (int)i);
|
||||
return 1;
|
||||
}
|
||||
|
|
@ -557,6 +605,7 @@ int main(int argc, char *argv[])
|
|||
}
|
||||
}
|
||||
free(transfers);
|
||||
curl_share_cleanup(share);
|
||||
|
||||
return 0;
|
||||
#else
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import filecmp
|
|||
import logging
|
||||
import math
|
||||
import os
|
||||
import re
|
||||
from datetime import timedelta
|
||||
import pytest
|
||||
|
||||
|
|
@ -591,3 +592,48 @@ class TestDownload:
|
|||
# we see 3 connections, because Apache only every serves a single
|
||||
# request via Upgrade: and then closed the connection.
|
||||
assert r.total_connects == 3, r.dump_logs()
|
||||
|
||||
# nghttpx is the only server we have that supports TLS early data
|
||||
@pytest.mark.skipif(condition=not Env.have_nghttpx(), reason="no nghttpx")
|
||||
@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
|
||||
def test_02_32_earlydata(self, env: Env, httpd, nghttpx, proto):
|
||||
if not env.curl_uses_lib('gnutls'):
|
||||
pytest.skip('TLS earlydata only implemented in GnuTLS')
|
||||
if proto == 'h3' and not env.have_h3():
|
||||
pytest.skip("h3 not supported")
|
||||
count = 2
|
||||
docname = 'data-10k'
|
||||
# we want this test to always connect to nghttpx, since it is
|
||||
# the only server we have that supports TLS earlydata
|
||||
port = env.port_for(proto)
|
||||
if proto != 'h3':
|
||||
port = env.nghttpx_https_port
|
||||
# url = f'https://{env.domain1}:{env.port_for(proto)}/{docname}'
|
||||
url = f'https://{env.domain1}:{port}/{docname}'
|
||||
client = LocalClient(name='hx-download', env=env)
|
||||
if not client.exists():
|
||||
pytest.skip(f'example client not built: {client.name}')
|
||||
r = client.run(args=[
|
||||
'-n', f'{count}',
|
||||
'-e', # use TLS earlydata
|
||||
'-f', # forbid reuse of connections
|
||||
'-r', f'{env.domain1}:{port}:127.0.0.1',
|
||||
'-V', proto, url
|
||||
])
|
||||
r.check_exit_code(0)
|
||||
srcfile = os.path.join(httpd.docs_dir, docname)
|
||||
self.check_downloads(client, srcfile, count)
|
||||
# check that TLS earlydata worked as expected
|
||||
earlydata = {}
|
||||
for line in r.trace_lines:
|
||||
m = re.match(r'^\[t-(\d+)] EarlyData: (\d+)', line)
|
||||
if m:
|
||||
earlydata[int(m.group(1))] = int(m.group(2))
|
||||
assert earlydata[0] == 0, f'{earlydata}'
|
||||
if proto == 'http/1.1':
|
||||
assert earlydata[1] == 69, f'{earlydata}'
|
||||
elif proto == 'h2':
|
||||
assert earlydata[1] == 107, f'{earlydata}'
|
||||
elif proto == 'h3':
|
||||
# not implemented
|
||||
assert earlydata[1] == 0, f'{earlydata}'
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import difflib
|
|||
import filecmp
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import pytest
|
||||
from typing import List
|
||||
|
||||
|
|
@ -43,6 +44,7 @@ class TestUpload:
|
|||
def _class_scope(self, env, httpd, nghttpx):
|
||||
if env.have_h3():
|
||||
nghttpx.start_if_needed()
|
||||
env.make_data_file(indir=env.gen_dir, fname="data-10k", fsize=10*1024)
|
||||
env.make_data_file(indir=env.gen_dir, fname="data-63k", fsize=63*1024)
|
||||
env.make_data_file(indir=env.gen_dir, fname="data-64k", fsize=64*1024)
|
||||
env.make_data_file(indir=env.gen_dir, fname="data-100k", fsize=100*1024)
|
||||
|
|
@ -651,6 +653,51 @@ class TestUpload:
|
|||
])
|
||||
r.check_stats(count=1, http_status=200, exitcode=0)
|
||||
|
||||
# nghttpx is the only server we have that supports TLS early data and
|
||||
# has a limit of 16k it announces
|
||||
@pytest.mark.skipif(condition=not Env.have_nghttpx(), reason="no nghttpx")
|
||||
@pytest.mark.parametrize("proto,upload_size,exp_early", [
|
||||
['http/1.1', 100, 203], # headers+body
|
||||
['http/1.1', 10*1024, 10345], # headers+body
|
||||
['http/1.1', 32*1024, 16384], # headers+body, limited by server max
|
||||
['h2', 10*1024, 10378], # headers+body
|
||||
['h2', 32*1024, 16384], # headers+body, limited by server max
|
||||
['h3', 1024, 0], # earlydata not supported
|
||||
])
|
||||
def test_07_70_put_earlydata(self, env: Env, httpd, nghttpx, proto, upload_size, exp_early):
|
||||
if not env.curl_uses_lib('gnutls'):
|
||||
pytest.skip('TLS earlydata only implemented in GnuTLS')
|
||||
if proto == 'h3' and not env.have_h3():
|
||||
pytest.skip("h3 not supported")
|
||||
count = 2
|
||||
# we want this test to always connect to nghttpx, since it is
|
||||
# the only server we have that supports TLS earlydata
|
||||
port = env.port_for(proto)
|
||||
if proto != 'h3':
|
||||
port = env.nghttpx_https_port
|
||||
url = f'https://{env.domain1}:{port}/curltest/put?id=[0-{count-1}]'
|
||||
client = LocalClient(name='hx-upload', env=env)
|
||||
if not client.exists():
|
||||
pytest.skip(f'example client not built: {client.name}')
|
||||
r = client.run(args=[
|
||||
'-n', f'{count}',
|
||||
'-e', # use TLS earlydata
|
||||
'-f', # forbid reuse of connections
|
||||
'-l', # announce upload length, no 'Expect: 100'
|
||||
'-S', f'{upload_size}',
|
||||
'-r', f'{env.domain1}:{port}:127.0.0.1',
|
||||
'-V', proto, url
|
||||
])
|
||||
r.check_exit_code(0)
|
||||
self.check_downloads(client, [f"{upload_size}"], count)
|
||||
earlydata = {}
|
||||
for line in r.trace_lines:
|
||||
m = re.match(r'^\[t-(\d+)] EarlyData: (\d+)', line)
|
||||
if m:
|
||||
earlydata[int(m.group(1))] = int(m.group(2))
|
||||
assert earlydata[0] == 0, f'{earlydata}'
|
||||
assert earlydata[1] == exp_early, f'{earlydata}'
|
||||
|
||||
def check_downloads(self, client, source: List[str], count: int,
|
||||
complete: bool = True):
|
||||
for i in range(count):
|
||||
|
|
|
|||
|
|
@ -24,11 +24,14 @@
|
|||
#
|
||||
###########################################################################
|
||||
#
|
||||
import difflib
|
||||
import filecmp
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import pytest
|
||||
|
||||
from testenv import Env, CurlClient, Caddy
|
||||
from testenv import Env, CurlClient, Caddy, LocalClient
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
|
@ -57,6 +60,7 @@ class TestCaddy:
|
|||
|
||||
@pytest.fixture(autouse=True, scope='class')
|
||||
def _class_scope(self, env, caddy):
|
||||
self._make_docs_file(docs_dir=caddy.docs_dir, fname='data10k.data', fsize=10*1024)
|
||||
self._make_docs_file(docs_dir=caddy.docs_dir, fname='data1.data', fsize=1024*1024)
|
||||
self._make_docs_file(docs_dir=caddy.docs_dir, fname='data5.data', fsize=5*1024*1024)
|
||||
self._make_docs_file(docs_dir=caddy.docs_dir, fname='data10.data', fsize=10*1024*1024)
|
||||
|
|
@ -205,3 +209,43 @@ class TestCaddy:
|
|||
for i in range(count):
|
||||
respdata = open(curl.response_file(i)).readlines()
|
||||
assert respdata == exp_data
|
||||
|
||||
@pytest.mark.parametrize("proto", ['http/1.1', 'h2'])
|
||||
def test_08_08_earlydata(self, env: Env, httpd, caddy, proto):
|
||||
count = 2
|
||||
docname = 'data10k.data'
|
||||
url = f'https://{env.domain1}:{caddy.port}/{docname}'
|
||||
client = LocalClient(name='hx-download', env=env)
|
||||
if not client.exists():
|
||||
pytest.skip(f'example client not built: {client.name}')
|
||||
r = client.run(args=[
|
||||
'-n', f'{count}',
|
||||
'-e', # use TLS earlydata
|
||||
'-f', # forbid reuse of connections
|
||||
'-r', f'{env.domain1}:{caddy.port}:127.0.0.1',
|
||||
'-V', proto, url
|
||||
])
|
||||
r.check_exit_code(0)
|
||||
srcfile = os.path.join(caddy.docs_dir, docname)
|
||||
self.check_downloads(client, srcfile, count)
|
||||
earlydata = {}
|
||||
for line in r.trace_lines:
|
||||
m = re.match(r'^\[t-(\d+)] EarlyData: (\d+)', line)
|
||||
if m:
|
||||
earlydata[int(m.group(1))] = int(m.group(2))
|
||||
# Caddy does not support early data
|
||||
assert earlydata[0] == 0, f'{earlydata}'
|
||||
assert earlydata[1] == 0, f'{earlydata}'
|
||||
|
||||
def check_downloads(self, client, srcfile: str, count: int,
|
||||
complete: bool = True):
|
||||
for i in range(count):
|
||||
dfile = client.download_file(i)
|
||||
assert os.path.exists(dfile)
|
||||
if complete and not filecmp.cmp(srcfile, dfile, shallow=False):
|
||||
diff = "".join(difflib.unified_diff(a=open(srcfile).readlines(),
|
||||
b=open(dfile).readlines(),
|
||||
fromfile=srcfile,
|
||||
tofile=dfile,
|
||||
n=1))
|
||||
assert False, f'download {dfile} differs:\n{diff}'
|
||||
|
|
|
|||
|
|
@ -64,9 +64,6 @@ class TestSSLUse:
|
|||
count = 3
|
||||
exp_resumed = 'Resumed'
|
||||
xargs = ['--sessionid', '--tls-max', tls_max, f'--tlsv{tls_max}']
|
||||
if env.curl_uses_lib('gnutls'):
|
||||
if tls_max == '1.3':
|
||||
exp_resumed = 'Initial' # 1.2 works in GnuTLS, but 1.3 does not, TODO
|
||||
if env.curl_uses_lib('libressl'):
|
||||
if tls_max == '1.3':
|
||||
exp_resumed = 'Initial' # 1.2 works in LibreSSL, but 1.3 does not, TODO
|
||||
|
|
@ -279,7 +276,9 @@ class TestSSLUse:
|
|||
])
|
||||
httpd.reload_if_config_changed()
|
||||
proto = 'http/1.1'
|
||||
curl = CurlClient(env=env)
|
||||
run_env = os.environ.copy()
|
||||
run_env['CURL_USE_EARLYDATA'] = '1'
|
||||
curl = CurlClient(env=env, run_env=run_env)
|
||||
url = f'https://{env.authority_for(env.domain1, proto)}/curltest/sslinfo'
|
||||
# SSL backend specifics
|
||||
if env.curl_uses_lib('bearssl'):
|
||||
|
|
|
|||
|
|
@ -110,6 +110,7 @@ class EnvConfig:
|
|||
'ftps': socket.SOCK_STREAM,
|
||||
'http': socket.SOCK_STREAM,
|
||||
'https': socket.SOCK_STREAM,
|
||||
'nghttpx_https': socket.SOCK_STREAM,
|
||||
'proxy': socket.SOCK_STREAM,
|
||||
'proxys': socket.SOCK_STREAM,
|
||||
'h2proxys': socket.SOCK_STREAM,
|
||||
|
|
@ -472,6 +473,10 @@ class Env:
|
|||
def https_port(self) -> int:
|
||||
return self.CONFIG.ports['https']
|
||||
|
||||
@property
|
||||
def nghttpx_https_port(self) -> int:
|
||||
return self.CONFIG.ports['nghttpx_https']
|
||||
|
||||
@property
|
||||
def h3_port(self) -> int:
|
||||
return self.https_port
|
||||
|
|
|
|||
|
|
@ -184,6 +184,7 @@ class NghttpxQuic(Nghttpx):
|
|||
args = [
|
||||
self._cmd,
|
||||
f'--frontend=*,{self.env.h3_port};quic',
|
||||
f'--frontend=*,{self.env.nghttpx_https_port};tls',
|
||||
f'--backend=127.0.0.1,{self.env.https_port};{self.env.domain1};sni={self.env.domain1};proto=h2;tls',
|
||||
f'--backend=127.0.0.1,{self.env.http_port}',
|
||||
'--log-level=INFO',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue