diff options
author | Loïc Hoguin <[email protected]> | 2023-06-05 10:52:07 +0200 |
---|---|---|
committer | Loïc Hoguin <[email protected]> | 2023-06-05 10:54:02 +0200 |
commit | 48eefebff2d0412bf6fe8a53182ef88a443b293f (patch) | |
tree | dfe1b7942e2b3654be30de374d25db6e646d0c63 /test/gun_SUITE.erl | |
parent | db0655def7d113f5aa168a1653df5d62245d3502 (diff) | |
download | gun-48eefebff2d0412bf6fe8a53182ef88a443b293f.tar.gz gun-48eefebff2d0412bf6fe8a53182ef88a443b293f.tar.bz2 gun-48eefebff2d0412bf6fe8a53182ef88a443b293f.zip |
Fix crash when TLS connection closes very early
And ensure that we don't infinite loop when retries are
enabled, by decrementing the retry count instead of using
a new one.
Also check for ssl:negotiated_protocol {error,closed}
which was possible but was not documented in OTP before
this change.
Thanks @voluntas for the help.
Diffstat (limited to 'test/gun_SUITE.erl')
-rw-r--r-- | test/gun_SUITE.erl | 76 |
1 files changed, 71 insertions, 5 deletions
diff --git a/test/gun_SUITE.erl b/test/gun_SUITE.erl index 6d1b00b..656158e 100644 --- a/test/gun_SUITE.erl +++ b/test/gun_SUITE.erl @@ -193,7 +193,7 @@ keepalive_infinity(_) -> killed_streams_http(_) -> doc("Ensure completed responses with a connection: close are not considered killed streams."), {ok, _, OriginPort} = init_origin(tcp, http, - fun (_, ClientSocket, ClientTransport) -> + fun (_, _, ClientSocket, ClientTransport) -> {ok, _} = ClientTransport:recv(ClientSocket, 0, 1000), ClientTransport:send(ClientSocket, "HTTP/1.1 200 OK\r\n" @@ -272,7 +272,7 @@ reply_to_http2(_) -> do_reply_to(Protocol) -> {ok, OriginPid, OriginPort} = init_origin(tcp, Protocol, - fun(_, ClientSocket, ClientTransport) -> + fun(_, _, ClientSocket, ClientTransport) -> {ok, _} = ClientTransport:recv(ClientSocket, 0, infinity), ResponseData = case Protocol of http -> @@ -474,7 +474,7 @@ server_name_indication_default(_) -> do_server_name_indication(Host, Expected, GunOpts) -> Self = self(), {ok, OriginPid, OriginPort} = init_origin(tls, http, - fun(_, ClientSocket, _) -> + fun(_, _, ClientSocket, _) -> {ok, Info} = ssl:connection_information(ClientSocket), Msg = {sni_hostname, _} = lists:keyfind(sni_hostname, 1, Info), Self ! Msg @@ -527,7 +527,7 @@ do_shutdown_reason() -> stream_info_http(_) -> doc("Ensure the function gun:stream_info/2 works as expected for HTTP/1.1."), {ok, OriginPid, OriginPort} = init_origin(tcp, http, - fun(_, ClientSocket, ClientTransport) -> + fun(_, _, ClientSocket, ClientTransport) -> %% Wait for the cancel signal. receive cancel -> ok end, %% Then terminate the stream. @@ -574,7 +574,7 @@ stream_info_http(_) -> stream_info_http2(_) -> doc("Ensure the function gun:stream_info/2 works as expected for HTTP/2."), {ok, OriginPid, OriginPort} = init_origin(tcp, http2, - fun(_, _, _) -> receive disconnect -> ok end end), + fun(_, _, _, _) -> receive disconnect -> ok end end), {ok, Pid} = gun:open("localhost", OriginPort, #{ event_handler => {gun_test_event_h, self()}, protocols => [http2] @@ -604,6 +604,72 @@ supervise_false(_) -> [] = [P || {_, P, _, _} <- supervisor:which_children(gun_sup), P =:= Pid], ok. +tls_handshake_error_gun_http2_init_retry_0(_) -> + doc("Ensure an early TLS connection close is propagated " + "to the user of the connection."), + %% The server will immediately close the connection upon + %% establishment so that the client's socket is down + %% before it attempts to initialise HTTP/2. + %% + %% We use 'http' for the server to skip the HTTP/2 init + %% but the client is connecting using 'http2'. + {ok, _, OriginPort} = init_origin(tls, http, + fun(_, _, ClientSocket, ClientTransport) -> + ClientTransport:close(ClientSocket) + end), + {ok, ConnPid} = gun:open("localhost", OriginPort, #{ + event_handler => {gun_test_fun_event_h, #{ + tls_handshake_end => fun(_, #{socket := _}) -> + %% We sleep right after the gun_tls:connect succeeds to make + %% sure the next call to the socket fails (as the socket + %% should be disconnected at that point by the server because + %% we are not sending a certificate). The call we want to fail + %% is the sending of the HTTP/2 preface in gun_http2:init. + timer:sleep(1000) + end + }}, + protocols => [http2], + retry => 0, + transport => tls + }), + {error, {down, {shutdown, closed}}} = gun:await_up(ConnPid), + gun:close(ConnPid). + +tls_handshake_error_gun_http2_init_retry_1(_) -> + doc("Ensure an early TLS connection close is propagated " + "to the user of the connection and does not result " + "in an infinite connect loop."), + %% The server will immediately close the connection upon + %% establishment so that the client's socket is down + %% before it attempts to initialise HTTP/2. + %% + %% We use 'http' for the server to skip the HTTP/2 init + %% but the client is connecting using 'http2'. + %% + %% We immediately accept another connection to handle + %% the coming retry and close that connection again. + {ok, _, OriginPort} = init_origin(tls, http, + fun(_, ListenSocket, ClientSocket1, ClientTransport) -> + ClientTransport:close(ClientSocket1), + %% We immediately accept a second connection and close it again. + {ok, ClientSocket2t} = ssl:transport_accept(ListenSocket, 5000), + {ok, ClientSocket2} = ssl:handshake(ClientSocket2t, 5000), + ClientTransport:close(ClientSocket2) + end), + {ok, ConnPid} = gun:open("localhost", OriginPort, #{ + event_handler => {gun_test_fun_event_h, #{ + tls_handshake_end => fun(_, #{socket := _}) -> + %% See tls_handshake_error_gun_http2_init_retry_0 for details. + timer:sleep(1000) + end + }}, + protocols => [http2], + retry => 1, + transport => tls + }), + {error, {down, {shutdown, closed}}} = gun:await_up(ConnPid), + gun:close(ConnPid). + tls_handshake_timeout(_) -> doc("Ensure an integer value for tls_handshake_timeout is accepted."), do_timeout(tls_handshake_timeout, 1000). |