From 5e3a5337a34dcb09418d4f412b11ede4510e3ddc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Mon, 22 Apr 2019 13:40:07 +0200 Subject: Make gun_tls_proxy work for HTTP/2 connections --- src/gun.erl | 6 ++++++ src/gun_http.erl | 6 +++--- src/gun_http2.erl | 1 + src/gun_tls_proxy.erl | 7 ++++++- test/rfc7231_SUITE.erl | 28 +++++++++++++++++++--------- 5 files changed, 35 insertions(+), 13 deletions(-) diff --git a/src/gun.erl b/src/gun.erl index 2f023e1..e86d73e 100644 --- a/src/gun.erl +++ b/src/gun.erl @@ -821,6 +821,12 @@ connected(cast, {connect, ReplyTo, StreamRef, Destination0, Headers}, end, ProtoState2 = Protocol:connect(ProtoState, StreamRef, ReplyTo, Destination, Headers), {keep_state, State#state{protocol_state=ProtoState2}}; +%% When using gun_tls_proxy we need a separate message to know whether +%% we need to switch to a different protocol. +connected(info, {connect_protocol, Protocol}, #state{protocol=Protocol}) -> + keep_state_and_data; +connected(info, {connect_protocol, Protocol}, State=#state{protocol_state=ProtoState}) -> + commands([{switch_protocol, Protocol, ProtoState}], State); connected(cast, {cancel, ReplyTo, StreamRef}, State=#state{protocol=Protocol, protocol_state=ProtoState}) -> ProtoState2 = Protocol:cancel(ProtoState, StreamRef, ReplyTo), diff --git a/src/gun_http.erl b/src/gun_http.erl index efcea35..02344ee 100644 --- a/src/gun_http.erl +++ b/src/gun_http.erl @@ -231,12 +231,12 @@ handle_head(Data, State=#http_state{socket=Socket, transport=Transport, TLSTimeout = maps:get(tls_handshake_timeout, Destination, infinity), {ok, ProxyPid} = gun_tls_proxy:start_link(NewHost, NewPort, TLSOpts, TLSTimeout, Socket, gun_tls), + %% In this case the switch_protocol is delayed and is handled by + %% a message sent from gun_tls_proxy once the connection is established, + %% and handled by the gun module directly. [{state, State2#http_state{socket=ProxyPid, transport=gun_tls_proxy}}, {origin, <<"https">>, NewHost, NewPort, connect}, {switch_transport, gun_tls_proxy, ProxyPid}]; - %% @todo Might also need to switch protocol, but gotta wait - %% @todo for the TLS connection to be established first. - %% @todo Should have a gun_tls_proxy event indicating connection success. #{transport := tls} -> TLSOpts = maps:get(tls_opts, Destination, []), TLSTimeout = maps:get(tls_handshake_timeout, Destination, infinity), diff --git a/src/gun_http2.erl b/src/gun_http2.erl index 8072a00..1dd2a75 100644 --- a/src/gun_http2.erl +++ b/src/gun_http2.erl @@ -285,6 +285,7 @@ prepare_headers(#http2_state{transport=Transport}, Method, Host0, Port, Path, He method => Method, scheme => case Transport of gun_tls -> <<"https">>; + gun_tls_proxy -> <<"https">>; gun_tcp -> <<"http">> end, authority => Authority, diff --git a/src/gun_tls_proxy.erl b/src/gun_tls_proxy.erl index 8adb5b6..123a156 100644 --- a/src/gun_tls_proxy.erl +++ b/src/gun_tls_proxy.erl @@ -222,8 +222,13 @@ not_connected({call, _}, Msg={send, _}, State) -> not_connected(cast, Msg={setopts, _}, State) -> ?DEBUG_LOG("postpone ~0p state ~0p", [Msg, State]), {keep_state_and_data, postpone}; -not_connected(cast, Msg={connect_proc, {ok, Socket}}, State) -> +not_connected(cast, Msg={connect_proc, {ok, Socket}}, State=#state{owner_pid=OwnerPid}) -> ?DEBUG_LOG("msg ~0p state ~0p", [Msg, State]), + Protocol = case ssl:negotiated_protocol(Socket) of + {ok, <<"h2">>} -> gun_http2; + _ -> gun_http + end, + OwnerPid ! {connect_protocol, Protocol}, ok = ssl:setopts(Socket, [{active, true}]), {next_state, connected, State#state{proxy_socket=Socket}}; not_connected(cast, Msg={connect_proc, Error}, State) -> diff --git a/test/rfc7231_SUITE.erl b/test/rfc7231_SUITE.erl index cbace9b..c9bd3b4 100644 --- a/test/rfc7231_SUITE.erl +++ b/test/rfc7231_SUITE.erl @@ -176,23 +176,33 @@ do_connect_http(OriginTransport, ProxyTransport) -> connect_h2c(_) -> doc("CONNECT can be used to establish a TCP connection " "to an HTTP/2 server via an HTTP proxy. (RFC7231 4.3.6)"), - do_connect_h2(tcp). + do_connect_h2(tcp, tcp). connect_h2(_) -> doc("CONNECT can be used to establish a TLS connection " "to an HTTP/2 server via an HTTP proxy. (RFC7231 4.3.6)"), - do_connect_h2(tls). + do_connect_h2(tls, tcp). -do_connect_h2(Transport) -> - {ok, OriginPid, OriginPort} = init_origin(Transport, http2), - {ok, ProxyPid, ProxyPort} = do_proxy_start(tcp), +connect_h2c_over_https_proxy(_) -> + doc("CONNECT can be used to establish a TCP connection " + "to an HTTP/2 server via an HTTPS proxy. (RFC7231 4.3.6)"), + do_connect_h2(tcp, tls). + +connect_h2_over_https_proxy(_) -> + doc("CONNECT can be used to establish a TLS connection " + "to an HTTP/2 server via an HTTPS proxy. (RFC7231 4.3.6)"), + do_connect_h2(tls, tls). + +do_connect_h2(OriginTransport, ProxyTransport) -> + {ok, OriginPid, OriginPort} = init_origin(OriginTransport, http2), + {ok, ProxyPid, ProxyPort} = do_proxy_start(ProxyTransport), Authority = iolist_to_binary(["localhost:", integer_to_binary(OriginPort)]), - {ok, ConnPid} = gun:open("localhost", ProxyPort), + {ok, ConnPid} = gun:open("localhost", ProxyPort, #{transport => ProxyTransport}), {ok, http} = gun:await_up(ConnPid), StreamRef = gun:connect(ConnPid, #{ host => "localhost", port => OriginPort, - transport => Transport, + transport => OriginTransport, protocols => [http2] }), {request, <<"CONNECT">>, Authority, 'HTTP/1.1', _} = receive_from(ProxyPid), @@ -201,7 +211,7 @@ do_connect_h2(Transport) -> _ = gun:get(ConnPid, "/proxied"), <<_:24, 1:8, _/bits>> = receive_from(OriginPid), #{ - transport := Transport, + transport := OriginTransport, protocol := http2, origin_host := "localhost", origin_port := OriginPort, @@ -209,7 +219,7 @@ do_connect_h2(Transport) -> type := connect, host := "localhost", port := ProxyPort, - transport := tcp, + transport := ProxyTransport, protocol := http }]} = gun:info(ConnPid), gun:close(ConnPid). -- cgit v1.2.3