}
break;
}
+ case CF_QUERY_ALPN_NEGOTIATED: {
+ const char **palpn = pres2;
+ DEBUGASSERT(palpn);
+ *palpn = NULL;
+ return CURLE_OK;
+ }
default:
break;
}
cf->next = winner->cf;
winner->cf = NULL;
- switch(cf->conn->alpn) {
- case CURL_HTTP_VERSION_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;
+ const char *alpn = Curl_conn_cf_get_alpn_negotiated(cf->next, data);
+ if(alpn && !strcmp("h2", alpn)) {
+ result = Curl_http2_switch_at(cf, data);
+ if(result) {
+ ctx->state = CF_HC_FAILURE;
+ ctx->result = result;
+ return result;
+ }
}
-#endif
- break;
- default:
- break;
}
+#endif
+
ctx->state = CF_HC_SUCCESS;
cf->connected = TRUE;
return result;
return FALSE;
}
-bool Curl_conn_cf_is_ssl(struct Curl_cfilter *cf)
+static bool cf_is_ssl(struct Curl_cfilter *cf)
{
for(; cf; cf = cf->next) {
if(cf->cft->flags & CF_TYPE_SSL)
bool Curl_conn_is_ssl(struct connectdata *conn, int sockindex)
{
- return conn ? Curl_conn_cf_is_ssl(conn->cfilter[sockindex]) : FALSE;
+ return conn ? cf_is_ssl(conn->cfilter[sockindex]) : FALSE;
}
bool Curl_conn_get_ssl_info(struct Curl_easy *data,
return Curl_conn_cf_get_transport(cf, data);
}
+const char *Curl_conn_get_alpn_negotiated(struct Curl_easy *data,
+ struct connectdata *conn)
+{
+ struct Curl_cfilter *cf = conn->cfilter[FIRSTSOCKET];
+ return Curl_conn_cf_get_alpn_negotiated(cf, data);
+}
+
unsigned char Curl_conn_http_version(struct Curl_easy *data,
struct connectdata *conn)
{
return (unsigned char)(data->conn ? data->conn->transport_wanted : 0);
}
+const char *Curl_conn_cf_get_alpn_negotiated(struct Curl_cfilter *cf,
+ struct Curl_easy *data)
+{
+ const char *alpn = NULL;
+ CURL_TRC_CF(data, cf, "query ALPN");
+ if(cf && !cf->cft->query(cf, data, CF_QUERY_ALPN_NEGOTIATED, NULL,
+ CURL_UNCONST(&alpn)))
+ return alpn;
+ return NULL;
+}
+
static const struct Curl_sockaddr_ex *
cf_get_remote_addr(struct Curl_cfilter *cf, struct Curl_easy *data)
{
* - CF_QUERY_SSL_CTX_INFO: same as CF_QUERY_SSL_INFO, but give the SSL_CTX
* when available, or the same internal pointer
* when the TLS stack does not differentiate.
+ * - CF_QUERY_ALPN_NEGOTIATED: The ALPN selected by the server as
+ null-terminated string or NULL if none
+ selected/handshake not done. Implemented by filter
+ types CF_TYPE_SSL or CF_TYPE_IP_CONNECT.
*/
/* query res1 res2 */
#define CF_QUERY_MAX_CONCURRENT 1 /* number - */
#define CF_QUERY_SSL_INFO 12 /* - struct curl_tlssessioninfo * */
#define CF_QUERY_SSL_CTX_INFO 13 /* - struct curl_tlssessioninfo * */
#define CF_QUERY_TRANSPORT 14 /* TRNSPRT_* - * */
+#define CF_QUERY_ALPN_NEGOTIATED 15 /* - const char * */
/**
* Query the cfilter for properties. Filters ignorant of a query will
bool ignore_result,
int event, int arg1, void *arg2);
-/**
- * Determine if the connection filter chain is using SSL to the remote host
- * (or will be once connected).
- */
-bool Curl_conn_cf_is_ssl(struct Curl_cfilter *cf);
-
/**
* Get the socket used by the filter chain starting at `cf`.
* Returns CURL_SOCKET_BAD if not available.
unsigned char Curl_conn_cf_get_transport(struct Curl_cfilter *cf,
struct Curl_easy *data);
+const char *Curl_conn_cf_get_alpn_negotiated(struct Curl_cfilter *cf,
+ struct Curl_easy *data);
+
#define CURL_CF_SSL_DEFAULT -1
#define CURL_CF_SSL_DISABLE 0
#define CURL_CF_SSL_ENABLE 1
unsigned char Curl_conn_get_transport(struct Curl_easy *data,
struct connectdata *conn);
+/* Get the negotiated ALPN protocol or NULL if none in play */
+const char *Curl_conn_get_alpn_negotiated(struct Curl_easy *data,
+ struct connectdata *conn);
+
/**
* Close the filter chain at `sockindex` for connection `data->conn`.
* Filters remain in place and may be connected again afterwards.
char *altused = NULL;
const char *p_accept; /* Accept: string */
unsigned char httpversion;
+ const char *alpn;
/* Always consider the DO phase done after this function call, even if there
may be parts of the request that are not yet sent, since we can deal with
the rest of the request in the PERFORM phase. */
*done = TRUE;
-
- switch(conn->alpn) {
- case CURL_HTTP_VERSION_3:
+ alpn = Curl_conn_get_alpn_negotiated(data, conn);
+ if(alpn && !strcmp("h3", alpn)) {
DEBUGASSERT(Curl_conn_http_version(data, conn) == 30);
- break;
- case CURL_HTTP_VERSION_2:
+ }
+ else if(alpn && !strcmp("h2", alpn)) {
#ifndef CURL_DISABLE_PROXY
if((Curl_conn_http_version(data, conn) != 20) &&
conn->bits.proxy && !conn->bits.tunnel_proxy
else
#endif
DEBUGASSERT(Curl_conn_http_version(data, conn) == 20);
- break;
- case CURL_HTTP_VERSION_1_1:
- /* continue with HTTP/1.x when explicitly requested */
- break;
- default:
+ }
+ else {
/* Check if user wants to use HTTP/2 with clear TCP */
if(Curl_http2_may_switch(data)) {
DEBUGF(infof(data, "HTTP/2 over clean TCP"));
if(result)
goto fail;
}
- break;
}
/* Add collecting of headers written to client. For a new connection,
if(!ctx->cf_protocol) {
struct Curl_cfilter *cf_protocol = NULL;
int httpversion = 0;
- int alpn = Curl_conn_cf_is_ssl(cf->next) ?
- cf->conn->proxy_alpn : CURL_HTTP_VERSION_1_1;
-
- /* First time call after the subchain connected */
- switch(alpn) {
- case CURL_HTTP_VERSION_NONE:
- case CURL_HTTP_VERSION_1_0:
- case CURL_HTTP_VERSION_1_1:
+ const char *alpn = Curl_conn_cf_get_alpn_negotiated(cf->next, data);
+
+ if(alpn)
+ infof(data, "CONNECT: '%s' negotiated", alpn);
+ else
+ infof(data, "CONNECT: no ALPN negotiated");
+
+ if(alpn && !strcmp(alpn, "http/1.0")) {
+ CURL_TRC_CF(data, cf, "installing subfilter for HTTP/1.0");
+ result = Curl_cf_h1_proxy_insert_after(cf, data);
+ if(result)
+ goto out;
+ cf_protocol = cf->next;
+ httpversion = 10;
+ }
+ else if(!alpn || !strcmp(alpn, "http/1.1")) {
CURL_TRC_CF(data, cf, "installing subfilter for HTTP/1.1");
- infof(data, "CONNECT tunnel: HTTP/1.%d negotiated",
- (alpn == CURL_HTTP_VERSION_1_0) ? 0 : 1);
result = Curl_cf_h1_proxy_insert_after(cf, data);
if(result)
goto out;
cf_protocol = cf->next;
- httpversion = (alpn == CURL_HTTP_VERSION_1_0) ? 10 : 11;
- break;
+ /* Assume that without an ALPN, we are talking to an ancient one */
+ httpversion = 11;
+ }
#ifdef USE_NGHTTP2
- case CURL_HTTP_VERSION_2:
+ else if(!strcmp(alpn, "h2")) {
CURL_TRC_CF(data, cf, "installing subfilter for HTTP/2");
- infof(data, "CONNECT tunnel: HTTP/2 negotiated");
result = Curl_cf_h2_proxy_insert_after(cf, data);
if(result)
goto out;
cf_protocol = cf->next;
httpversion = 20;
- break;
+ }
#endif
- default:
- infof(data, "CONNECT tunnel: unsupported ALPN(%d) negotiated", alpn);
+ else {
+ failf(data, "CONNECT: negotiated ALPN '%s' not supported", alpn);
result = CURLE_COULDNT_CONNECT;
goto out;
}
*pres1 = (int)cf->conn->http_proxy.port;
*((const char **)pres2) = cf->conn->http_proxy.host.name;
return CURLE_OK;
+ case CF_QUERY_ALPN_NEGOTIATED: {
+ const char **palpn = pres2;
+ DEBUGASSERT(palpn);
+ *palpn = NULL;
+ return CURLE_OK;
+ }
default:
break;
}
{
struct socks_state *sx = cf->ctx;
- if(sx) {
- switch(query) {
- case CF_QUERY_HOST_PORT:
+ switch(query) {
+ case CF_QUERY_HOST_PORT:
+ if(sx) {
*pres1 = sx->remote_port;
*((const char **)pres2) = sx->hostname;
return CURLE_OK;
- default:
- break;
}
+ break;
+ case CF_QUERY_ALPN_NEGOTIATED: {
+ const char **palpn = pres2;
+ DEBUGASSERT(palpn);
+ *palpn = NULL;
+ return CURLE_OK;
+ }
+ default:
+ break;
}
return cf->next ?
cf->next->cft->query(cf->next, data, query, pres1, pres2) :
conn->primary.remote_port);
#ifndef CURL_DISABLE_HTTP
if(conn->handler->protocol & PROTO_FAMILY_HTTP) {
- switch(conn->alpn) {
- case CURL_HTTP_VERSION_3:
- infof(data, "using HTTP/3");
- break;
- case CURL_HTTP_VERSION_2:
- infof(data, "using HTTP/2");
- break;
- default:
+ const char *alpn = Curl_conn_get_alpn_negotiated(data, conn);
+ if(!alpn || !strcmp("http/1.1", alpn) || !strcmp("http/1.0", alpn))
infof(data, "using HTTP/1.x");
- break;
- }
+ else if(!strcmp("h2", alpn))
+ infof(data, "using HTTP/2");
+ else if(!strcmp("h3", alpn))
+ infof(data, "using HTTP/3");
+ else
+ infof(data, "using ALPN protocol '%s'", alpn);
}
#endif
}
unsigned short localport;
unsigned short secondary_port; /* secondary socket remote port to connect to
(ftp) */
- unsigned char alpn; /* APLN TLS negotiated protocol, a CURL_HTTP_VERSION*
- value */
-#ifndef CURL_DISABLE_PROXY
- unsigned char proxy_alpn; /* APLN of proxy tunnel, CURL_HTTP_VERSION* */
-#endif
unsigned char transport_wanted; /* one of the TRNSPRT_* defines. Not
necessarily the transport the connection ends using due to Alt-Svc
and happy eyeballing. Use `Curl_conn_get_transport() for actual value
if(result)
goto out;
if(cf->connected) {
- cf->conn->alpn = CURL_HTTP_VERSION_3;
*done = TRUE;
goto out;
}
if(!result) {
CURL_TRC_CF(data, cf, "peer verified");
cf->connected = TRUE;
- cf->conn->alpn = CURL_HTTP_VERSION_3;
*done = TRUE;
connkeep(cf->conn, "HTTP/3 default");
}
return CURLE_OK;
break;
}
+ case CF_QUERY_ALPN_NEGOTIATED: {
+ const char **palpn = pres2;
+ DEBUGASSERT(palpn);
+ *palpn = cf->connected ? "h3" : NULL;
+ return CURLE_OK;
+ }
default:
break;
}
if(!result) {
CURL_TRC_CF(data, cf, "peer verified");
cf->connected = TRUE;
- cf->conn->alpn = CURL_HTTP_VERSION_3;
*done = TRUE;
connkeep(cf->conn, "HTTP/3 default");
}
return CURLE_OK;
break;
}
+ case CF_QUERY_ALPN_NEGOTIATED: {
+ const char **palpn = pres2;
+ DEBUGASSERT(palpn);
+ *palpn = cf->connected ? "h3" : NULL;
+ return CURLE_OK;
+ }
default:
break;
}
goto out;
}
cf->connected = TRUE;
- cf->conn->alpn = CURL_HTTP_VERSION_3;
*done = TRUE;
connkeep(cf->conn, "HTTP/3 default");
}
return CURLE_OK;
break;
}
+ case CF_QUERY_ALPN_NEGOTIATED: {
+ const char **palpn = pres2;
+ DEBUGASSERT(palpn);
+ *palpn = cf->connected ? "h3" : NULL;
+ return CURLE_OK;
+ }
default:
break;
}
if(alpn_result.ProtoNegoStatus ==
SecApplicationProtocolNegotiationStatus_Success) {
- unsigned char prev_alpn = cf->conn->alpn;
-
+ if(backend->recv_renegotiating &&
+ connssl->negotiated.alpn &&
+ strncmp(connssl->negotiated.alpn,
+ (const char *)alpn_result.ProtocolId,
+ alpn_result.ProtocolIdSize)) {
+ /* 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;
+ }
Curl_alpn_set_negotiated(cf, data, connssl, alpn_result.ProtocolId,
alpn_result.ProtocolIdSize);
- if(backend->recv_renegotiating) {
- 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 {
if(!backend->recv_renegotiating)
return CURLE_OK;
}
case CF_QUERY_SSL_INFO:
- case CF_QUERY_SSL_CTX_INFO: {
- struct curl_tlssessioninfo *info = pres2;
- struct cf_call_data save;
- CF_DATA_SAVE(save, cf, data);
- info->backend = Curl_ssl_backend();
- info->internals = connssl->ssl_impl->get_internals(
- cf->ctx, (query == CF_QUERY_SSL_INFO) ?
- CURLINFO_TLS_SSL_PTR : CURLINFO_TLS_SESSION);
- CF_DATA_RESTORE(cf, save);
+ case CF_QUERY_SSL_CTX_INFO:
+ if(!Curl_ssl_cf_is_proxy(cf)) {
+ struct curl_tlssessioninfo *info = pres2;
+ struct cf_call_data save;
+ CF_DATA_SAVE(save, cf, data);
+ info->backend = Curl_ssl_backend();
+ info->internals = connssl->ssl_impl->get_internals(
+ cf->ctx, (query == CF_QUERY_SSL_INFO) ?
+ CURLINFO_TLS_SSL_PTR : CURLINFO_TLS_SESSION);
+ CF_DATA_RESTORE(cf, save);
+ return CURLE_OK;
+ }
+ break;
+ case CF_QUERY_ALPN_NEGOTIATED: {
+ const char **palpn = pres2;
+ DEBUGASSERT(palpn);
+ *palpn = connssl->negotiated.alpn;
+ CURL_TRC_CF(data, cf, "query ALPN: returning '%s'", *palpn);
return CURLE_OK;
}
default:
Curl_cf_def_cntrl,
cf_ssl_is_alive,
Curl_cf_def_conn_keep_alive,
- Curl_cf_def_query,
+ ssl_cf_query,
};
#endif /* !CURL_DISABLE_PROXY */
struct Curl_cfilter *cf = NULL;
struct ssl_connect_data *ctx;
CURLcode result;
- bool use_alpn = conn->bits.tls_enable_alpn;
+ /* ALPN is default, but if user explicitly disables it, obey */
+ bool use_alpn = data->set.ssl_enable_alpn;
http_majors allowed = CURL_HTTP_V1x;
+ (void)conn;
#ifdef USE_HTTP2
if(conn->http_proxy.proxytype == CURLPROXY_HTTPS2) {
use_alpn = TRUE;
size_t proto_len)
{
CURLcode result = CURLE_OK;
- unsigned char *palpn =
-#ifndef CURL_DISABLE_PROXY
- (cf->conn->bits.tunnel_proxy && Curl_ssl_cf_is_proxy(cf)) ?
- &cf->conn->proxy_alpn : &cf->conn->alpn
-#else
- &cf->conn->alpn
-#endif
- ;
+ (void)cf;
if(connssl->negotiated.alpn) {
/* When we ask for a specific ALPN protocol, we need the confirmation
}
if(proto && proto_len) {
- if(proto_len == ALPN_HTTP_1_1_LENGTH &&
- !memcmp(ALPN_HTTP_1_1, proto, ALPN_HTTP_1_1_LENGTH)) {
- *palpn = CURL_HTTP_VERSION_1_1;
- }
-#ifdef USE_HTTP2
- else if(proto_len == ALPN_H2_LENGTH &&
- !memcmp(ALPN_H2, proto, ALPN_H2_LENGTH)) {
- *palpn = CURL_HTTP_VERSION_2;
- }
-#endif
-#ifdef USE_HTTP3
- else if(proto_len == ALPN_H3_LENGTH &&
- !memcmp(ALPN_H3, proto, ALPN_H3_LENGTH)) {
- *palpn = CURL_HTTP_VERSION_3;
- }
-#endif
- else {
- *palpn = CURL_HTTP_VERSION_NONE;
- failf(data, "unsupported ALPN protocol: '%.*s'", (int)proto_len, proto);
- /* Previous code just ignored it and some vtls backends even ignore the
- * return code of this function. */
- /* return CURLE_NOT_BUILT_IN; */
- goto out;
- }
-
if(connssl->state == ssl_connection_deferred)
infof(data, VTLS_INFOF_ALPN_DEFERRED, (int)proto_len, proto);
else
infof(data, VTLS_INFOF_ALPN_ACCEPTED, (int)proto_len, proto);
}
else {
- *palpn = CURL_HTTP_VERSION_NONE;
if(connssl->state == ssl_connection_deferred)
infof(data, VTLS_INFOF_NO_ALPN_DEFERRED);
else
def get_tunnel_proto_used(self, r: ExecResult):
for line in r.trace_lines:
- m = re.match(r'.* CONNECT tunnel: (\S+) negotiated$', line)
+ m = re.match(r'.* CONNECT: \'(\S+)\' negotiated$', line)
if m:
return m.group(1)
assert False, f'tunnel protocol not found in:\n{"".join(r.trace_lines)}'
extra_args=xargs)
r.check_response(count=1, http_status=200,
protocol='HTTP/2' if proto == 'h2' else 'HTTP/1.1')
- assert self.get_tunnel_proto_used(r) == 'HTTP/2' \
- if tunnel == 'h2' else 'HTTP/1.1'
+ assert self.get_tunnel_proto_used(r) == tunnel
srcfile = os.path.join(httpd.docs_dir, 'data.json')
dfile = curl.download_file(0)
assert filecmp.cmp(srcfile, dfile, shallow=False)
extra_args=xargs)
r.check_response(count=count, http_status=200,
protocol='HTTP/2' if proto == 'h2' else 'HTTP/1.1')
- assert self.get_tunnel_proto_used(r) == 'HTTP/2' \
- if tunnel == 'h2' else 'HTTP/1.1'
+ assert self.get_tunnel_proto_used(r) == tunnel
srcfile = os.path.join(httpd.docs_dir, fname)
for i in range(count):
dfile = curl.download_file(i)
xargs = curl.get_proxy_args(tunnel=True, proto=tunnel)
r = curl.http_upload(urls=[url], data=f'@{srcfile}', alpn_proto=proto,
extra_args=xargs)
- assert self.get_tunnel_proto_used(r) == 'HTTP/2' \
- if tunnel == 'h2' else 'HTTP/1.1'
+ assert self.get_tunnel_proto_used(r) == tunnel
r.check_response(count=count, http_status=200)
indata = open(srcfile).readlines()
for i in range(count):
r = curl.http_download(urls=[url1, url2], alpn_proto='http/1.1', with_stats=True,
extra_args=xargs)
r.check_response(count=2, http_status=200)
- assert self.get_tunnel_proto_used(r) == 'HTTP/2' \
- if tunnel == 'h2' else 'HTTP/1.1'
+ assert self.get_tunnel_proto_used(r) == tunnel
if tunnel == 'h2':
# TODO: we would like to reuse the first connection for the
# second URL, but this is currently not possible
r1 = curl.http_download(urls=[url], alpn_proto='http/1.1', with_stats=True,
extra_args=proxy_args)
r1.check_response(count=1, http_status=200)
- assert self.get_tunnel_proto_used(r1) == 'HTTP/2' \
- if tunnel == 'h2' else 'HTTP/1.1'
+ assert self.get_tunnel_proto_used(r1) == tunnel
# get the args, duplicate separated with '--next'
x2_args = r1.args[1:]
x2_args.append('--next')
r1 = curl.http_download(urls=[url], alpn_proto='http/1.1', with_stats=True,
extra_args=proxy_args)
r1.check_response(count=1, http_status=200)
- assert self.get_tunnel_proto_used(r1) == 'HTTP/2' \
- if tunnel == 'h2' else 'HTTP/1.1'
+ assert self.get_tunnel_proto_used(r1) == tunnel
# get the args, duplicate separated with '--next'
x2_args = r1.args[1:]
x2_args.append('--next')
r1 = curl.http_download(urls=[url], alpn_proto='http/1.1', with_stats=True,
extra_args=proxy_args)
r1.check_response(count=1, http_status=200)
- assert self.get_tunnel_proto_used(r1) == 'HTTP/2' \
- if tunnel == 'h2' else 'HTTP/1.1'
+ assert self.get_tunnel_proto_used(r1) == tunnel
# get the args, duplicate separated with '--next'
x2_args = r1.args[1:]
x2_args.append('--next')
r1 = curl.http_download(urls=[url], alpn_proto='http/1.1', with_stats=True,
extra_args=proxy_args)
r1.check_response(count=1, http_status=200)
- assert self.get_tunnel_proto_used(r1) == 'HTTP/2' \
- if tunnel == 'h2' else 'HTTP/1.1'
+ assert self.get_tunnel_proto_used(r1) == tunnel
# get the args, duplicate separated with '--next'
x2_args = r1.args[1:]
x2_args.append('--next')
def get_tunnel_proto_used(self, r: ExecResult):
for line in r.trace_lines:
- m = re.match(r'.* CONNECT tunnel: (\S+) negotiated$', line)
+ m = re.match(r'.* CONNECT: \'(\S+)\' negotiated$', line)
if m:
return m.group(1)
assert False, f'tunnel protocol not found in:\n{"".join(r.trace_lines)}'
extra_args=xargs)
# expect "COULD_NOT_CONNECT"
r.check_response(exitcode=56, http_status=None)
- assert self.get_tunnel_proto_used(r) == 'HTTP/2' \
- if tunnel == 'h2' else 'HTTP/1.1'
+ assert self.get_tunnel_proto_used(r) == tunnel
@pytest.mark.skipif(condition=not Env.have_nghttpx(), reason="no nghttpx available")
@pytest.mark.skipif(condition=not Env.curl_has_feature('HTTPS-proxy'),
extra_args=xargs)
r.check_response(count=1, http_status=200,
protocol='HTTP/2' if proto == 'h2' else 'HTTP/1.1')
- assert self.get_tunnel_proto_used(r) == 'HTTP/2' \
- if tunnel == 'h2' else 'HTTP/1.1'
+ assert self.get_tunnel_proto_used(r) == tunnel
@pytest.mark.skipif(condition=not Env.curl_has_feature('SPNEGO'),
reason='curl lacks SPNEGO support')