mirror of
https://github.com/curl/curl.git
synced 2026-08-25 14:53:34 +03:00
alt-svc: more flexibility on same destination
When the Alt-Svc points to the same host and port, add the destination ALPN to the `wanted` versions and set it also as the `preferred` version in negotiations. This allows Alt-Svc for h3 to point to h2 and have it tried first. Also, this allows Alt-Svc to say http/1.1 is preferred and changes the ALPN protocol ordering for the TLS handshake. Add tests in various combination to verify this works. Reported-by: yushicheng7788 on github Fixes #19740 Closes #19874
This commit is contained in:
parent
f450f3801b
commit
5ed7b5b01b
9 changed files with 135 additions and 49 deletions
|
|
@ -133,3 +133,19 @@ class TestEyeballs:
|
|||
he_timers_set = [line for line in r.trace_lines
|
||||
if re.match(r'.*\[TIMER] \[HAPPY_EYEBALLS] set for', line)]
|
||||
assert len(he_timers_set) == 2, f'found: {"".join(he_timers_set)}\n{r.dump_logs()}'
|
||||
|
||||
# download using HTTP/3 on missing server with alt-svc pointing there
|
||||
@pytest.mark.skipif(condition=not Env.have_h3(), reason="missing HTTP/3 support")
|
||||
def test_06_20_h2_altsvc_h3_fallback(self, env: Env, httpd, nghttpx):
|
||||
curl = CurlClient(env=env)
|
||||
urln = f'https://{env.domain1}:{env.https_only_tcp_port}/data.json'
|
||||
altsvc_file = curl.mk_altsvc_file('test_06',
|
||||
'h2', env.domain1, env.https_only_tcp_port,
|
||||
'h3', env.domain1, env.https_only_tcp_port)
|
||||
r = curl.http_download(urls=[urln], extra_args=[
|
||||
'--alt-svc', altsvc_file
|
||||
])
|
||||
# Should try a QUIC connection that fails and fallback to h2
|
||||
r.check_exit_code(0)
|
||||
r.check_response(count=1, http_status=200)
|
||||
assert r.stats[0]['http_version'] == '2'
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@
|
|||
#
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
from datetime import datetime, timedelta
|
||||
import pytest
|
||||
|
||||
|
|
@ -77,10 +78,11 @@ class TestReuse:
|
|||
|
||||
@pytest.mark.skipif(condition=not Env.have_h3(), reason="h3 not supported")
|
||||
def test_12_03_as_follow_h2h3(self, env: Env, httpd, configures_httpd, nghttpx):
|
||||
# write an alt-svc file that advises h3 instead of h2
|
||||
asfile = os.path.join(env.gen_dir, 'alt-svc-12_03.txt')
|
||||
self.create_asfile(asfile, f'h2 {env.domain1} {env.https_port} h3 {env.domain1} {env.h3_port}')
|
||||
curl = CurlClient(env=env)
|
||||
# write an alt-svc file that advises h3 instead of h2
|
||||
asfile = curl.mk_altsvc_file('test_12',
|
||||
'h2', env.domain1, env.https_port,
|
||||
'h3', env.domain1, env.h3_port)
|
||||
urln = f'https://{env.authority_for(env.domain1, "h2")}/data.json'
|
||||
r = curl.http_download(urls=[urln], with_stats=True, extra_args=[
|
||||
'--alt-svc', f'{asfile}',
|
||||
|
|
@ -112,54 +114,47 @@ class TestReuse:
|
|||
@pytest.mark.skipif(condition=not Env.have_h3(), reason="h3 not supported")
|
||||
def test_12_05_as_follow_h3h1(self, env: Env, httpd, configures_httpd, nghttpx):
|
||||
# With '--http3` an Alt-Svc redirection from h3 to h1 is allowed
|
||||
count = 2
|
||||
# write an alt-svc file the advises h1 instead of h3
|
||||
asfile = os.path.join(env.gen_dir, 'alt-svc-12_05.txt')
|
||||
ts = datetime.now() + timedelta(hours=24)
|
||||
expires = f'{ts.year:04}{ts.month:02}{ts.day:02} {ts.hour:02}:{ts.minute:02}:{ts.second:02}'
|
||||
with open(asfile, 'w') as fd:
|
||||
fd.write(f'h3 {env.domain1} {env.https_port} http/1.1 {env.domain1} {env.https_port} "{expires}" 0 0')
|
||||
log.info(f'altscv: {open(asfile).readlines()}')
|
||||
curl = CurlClient(env=env)
|
||||
urln = f'https://{env.authority_for(env.domain1, "h3")}/data.json?[0-{count-1}]'
|
||||
asfile = curl.mk_altsvc_file('test_12',
|
||||
'h3', env.domain1, env.https_port,
|
||||
'http/1.1', env.domain1, env.https_port)
|
||||
urln = f'https://{env.authority_for(env.domain1, "h3")}/data.json'
|
||||
r = curl.http_download(urls=[urln], with_stats=True, extra_args=[
|
||||
'--alt-svc', f'{asfile}', '--http3'
|
||||
])
|
||||
r.check_response(count=count, http_status=200)
|
||||
# We expect the connection to be reused and use HTTP/1.1
|
||||
r.check_response(count=1, http_status=200)
|
||||
# We expect the connection to be preferring HTTP/1.1 in the ALPN
|
||||
assert r.total_connects == 1
|
||||
for s in r.stats:
|
||||
assert s['http_version'] == '1.1', f'{s}'
|
||||
re_m = re.compile(r'.* ALPN: curl offers http/1.1,h2')
|
||||
lines = [line for line in r.trace_lines if re_m.match(line)]
|
||||
assert len(lines), f'{r.dump_logs()}'
|
||||
|
||||
@pytest.mark.skipif(condition=not Env.have_h3(), reason="h3 not supported")
|
||||
def test_12_06_as_ignore_h3h1(self, env: Env, httpd, configures_httpd, nghttpx):
|
||||
# With '--http3-only` an Alt-Svc redirection from h3 to h1 is ignored
|
||||
count = 2
|
||||
# write an alt-svc file the advises h1 instead of h3
|
||||
asfile = os.path.join(env.gen_dir, 'alt-svc-12_05.txt')
|
||||
ts = datetime.now() + timedelta(hours=24)
|
||||
expires = f'{ts.year:04}{ts.month:02}{ts.day:02} {ts.hour:02}:{ts.minute:02}:{ts.second:02}'
|
||||
with open(asfile, 'w') as fd:
|
||||
fd.write(f'h3 {env.domain1} {env.https_port} http/1.1 {env.domain1} {env.https_port} "{expires}" 0 0')
|
||||
log.info(f'altscv: {open(asfile).readlines()}')
|
||||
curl = CurlClient(env=env)
|
||||
urln = f'https://{env.authority_for(env.domain1, "h3")}/data.json?[0-{count-1}]'
|
||||
asfile = curl.mk_altsvc_file('test_12',
|
||||
'h3', env.domain1, env.https_port,
|
||||
'http/1.1', env.domain1, env.https_port)
|
||||
urln = f'https://{env.authority_for(env.domain1, "h3")}/data.json'
|
||||
r = curl.http_download(urls=[urln], with_stats=True, extra_args=[
|
||||
'--alt-svc', f'{asfile}', '--http3-only'
|
||||
])
|
||||
r.check_response(count=count, http_status=200)
|
||||
r.check_response(count=1, http_status=200)
|
||||
# We expect the connection to be stay on h3, since we used --http3-only
|
||||
assert r.total_connects == 1
|
||||
for s in r.stats:
|
||||
assert s['http_version'] == '3', f'{s}'
|
||||
assert r.stats[0]['http_version'] == '3', f'{r.stats}'
|
||||
|
||||
@pytest.mark.skipif(condition=not Env.have_h3(), reason="h3 not supported")
|
||||
def test_12_07_as_ignore_h2h3(self, env: Env, httpd, configures_httpd, nghttpx):
|
||||
# With '--http2` an Alt-Svc redirection from h2 to h3 is ignored
|
||||
# write an alt-svc file that advises h3 instead of h2
|
||||
asfile = os.path.join(env.gen_dir, 'alt-svc-12_03.txt')
|
||||
self.create_asfile(asfile, f'h2 {env.domain1} {env.https_port} h3 {env.domain1} {env.h3_port}')
|
||||
curl = CurlClient(env=env)
|
||||
asfile = curl.mk_altsvc_file('test_12',
|
||||
'h2', env.domain1, env.https_port,
|
||||
'h3', env.domain1, env.h3_port)
|
||||
urln = f'https://{env.authority_for(env.domain1, "h2")}/data.json'
|
||||
r = curl.http_download(urls=[urln], with_stats=True, extra_args=[
|
||||
'--alt-svc', f'{asfile}', '--http2'
|
||||
|
|
@ -167,9 +162,17 @@ class TestReuse:
|
|||
r.check_response(count=1, http_status=200)
|
||||
assert r.stats[0]['http_version'] == '2', f'{r.stats}'
|
||||
|
||||
def create_asfile(self, fpath, line):
|
||||
ts = datetime.now() + timedelta(hours=24)
|
||||
expires = f'{ts.year:04}{ts.month:02}{ts.day:02} {ts.hour:02}:{ts.minute:02}:{ts.second:02}'
|
||||
with open(fpath, 'w') as fd:
|
||||
fd.write(f'{line} "{expires}" 0 0')
|
||||
log.info(f'altscv: {open(fpath).readlines()}')
|
||||
# download using HTTP/3 on available server with alt-svc to h2, use h2
|
||||
@pytest.mark.skipif(condition=not Env.have_h3(), reason="missing HTTP/3 support")
|
||||
def test_12_08_h3_altsvc_h2_used(self, env: Env, httpd, nghttpx):
|
||||
curl = CurlClient(env=env)
|
||||
urln = f'https://{env.domain1}:{env.https_port}/data.json'
|
||||
altsvc_file = curl.mk_altsvc_file('test_12',
|
||||
'h3', env.domain1, env.https_port,
|
||||
'h2', env.domain1, env.https_port)
|
||||
r = curl.http_download(urls=[urln], extra_args=[
|
||||
'--http3', '--alt-svc', altsvc_file
|
||||
])
|
||||
r.check_exit_code(0)
|
||||
r.check_response(count=1, http_status=200)
|
||||
assert r.stats[0]['http_version'] == '2'
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ import re
|
|||
import shutil
|
||||
import subprocess
|
||||
from statistics import mean, fmean
|
||||
from datetime import timedelta, datetime
|
||||
from datetime import timedelta, datetime, timezone
|
||||
from typing import List, Optional, Dict, Union, Any
|
||||
from urllib.parse import urlparse
|
||||
|
||||
|
|
@ -1211,3 +1211,12 @@ class CurlClient:
|
|||
rc = p.returncode
|
||||
if rc != 0:
|
||||
raise Exception(f'{fg_gen_flame} returned error {rc}')
|
||||
|
||||
def mk_altsvc_file(self, name, src_alpn, src_host, src_port,
|
||||
dest_alpn, dest_host, dest_port):
|
||||
fpath = os.path.join(self.run_dir, f'{name}.altsvc')
|
||||
ts = datetime.now(timezone.utc) + timedelta(hours=1)
|
||||
ts = ts.strftime('%Y%m%d %H:%M:%S')
|
||||
with open(fpath, 'w') as fd:
|
||||
fd.write(f'{src_alpn} {src_host} {src_port} {dest_alpn} {dest_host} {dest_port} "{ts}" 1 0\n')
|
||||
return fpath
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue