aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLoïc Hoguin <[email protected]>2019-07-25 14:14:34 +0200
committerLoïc Hoguin <[email protected]>2019-07-25 14:14:34 +0200
commitc2ba2258a0020d82faa3e79162f05fc67d61b53e (patch)
treec812564c3896e1c2489b8c70bff0adc9d68e2867
parent293ca3d58f64cd716e8b1f84330c29f229d5a4f2 (diff)
downloadgun-c2ba2258a0020d82faa3e79162f05fc67d61b53e.tar.gz
gun-c2ba2258a0020d82faa3e79162f05fc67d61b53e.tar.bz2
gun-c2ba2258a0020d82faa3e79162f05fc67d61b53e.zip
Add tls_handshake events for CONNECT through TCP proxies
-rw-r--r--src/gun_event.erl12
-rw-r--r--src/gun_http.erl37
-rw-r--r--test/event_SUITE.erl94
3 files changed, 129 insertions, 14 deletions
diff --git a/src/gun_event.erl b/src/gun_event.erl
index c87af58..ed50fd7 100644
--- a/src/gun_event.erl
+++ b/src/gun_event.erl
@@ -55,8 +55,20 @@
-callback connect_end(connect_event(), State) -> State.
%% tls_handshake_start/tls_handshake_end.
+%%
+%% These events occur when connecting to a TLS server or when
+%% upgrading the connection to use TLS, for example using CONNECT.
+%% The stream_ref/reply_to values are only present when the TLS
+%% handshake occurs as a result of a request.
+%%
+%% @todo The current implementation of TLS over TLS will not result
+%% in an event being triggered when the TLS handshake fails. Instead
+%% the Gun process will exit because of the link to the gun_tls_proxy
+%% process.
-type tls_handshake_event() :: #{
+ stream_ref => reference(),
+ reply_to => pid(),
socket := inet:socket() | ssl:sslsocket(), %% The socket before/after will be different.
tls_opts := [ssl:connect_option()],
timeout := timeout(),
diff --git a/src/gun_http.erl b/src/gun_http.erl
index 08a287c..113de0d 100644
--- a/src/gun_http.erl
+++ b/src/gun_http.erl
@@ -274,8 +274,8 @@ handle_head(Data, State=#http_state{socket=Socket, transport=Transport,
ok
end,
%% @todo Figure out whether the event should trigger if the stream was cancelled.
- EvHandlerState = EvHandler:response_headers(#{
- stream_ref => StreamRef,
+ EvHandlerState1 = EvHandler:response_headers(#{
+ stream_ref => RealStreamRef,
reply_to => ReplyTo,
status => Status,
headers => Headers
@@ -296,33 +296,52 @@ handle_head(Data, State=#http_state{socket=Socket, transport=Transport,
%% 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}], EvHandlerState};
+ {switch_transport, gun_tls_proxy, ProxyPid}], EvHandlerState1};
#{transport := tls} ->
TLSOpts = maps:get(tls_opts, Destination, []),
TLSTimeout = maps:get(tls_handshake_timeout, Destination, infinity),
+ HandshakeEvent = #{
+ stream_ref => RealStreamRef,
+ reply_to => ReplyTo,
+ socket => Socket,
+ tls_opts => TLSOpts,
+ timeout => TLSTimeout
+ },
+ EvHandlerState2 = EvHandler:tls_handshake_start(HandshakeEvent, EvHandlerState1),
case gun_tls:connect(Socket, TLSOpts, TLSTimeout) of
{ok, TLSSocket} ->
- case ssl:negotiated_protocol(TLSSocket) of
- {ok, <<"h2">>} ->
+ Protocol = case ssl:negotiated_protocol(TLSSocket) of
+ {ok, <<"h2">>} -> gun_http2;
+ _ -> gun_http
+ end,
+ EvHandlerState = EvHandler:tls_handshake_end(HandshakeEvent#{
+ socket => TLSSocket,
+ protocol => Protocol:name()
+ }, EvHandlerState2),
+ case Protocol of
+ gun_http2 ->
{[{origin, <<"https">>, NewHost, NewPort, connect},
{switch_transport, gun_tls, TLSSocket},
{switch_protocol, gun_http2, State2}], EvHandlerState};
- _ ->
+ gun_http ->
{[{state, State2#http_state{socket=TLSSocket, transport=gun_tls}},
{origin, <<"https">>, NewHost, NewPort, connect},
{switch_transport, gun_tls, TLSSocket}], EvHandlerState}
end;
- Error ->
+ Error = {error, Reason} ->
+ EvHandlerState = EvHandler:tls_handshake_end(HandshakeEvent#{
+ error => Reason
+ }, EvHandlerState2),
{Error, EvHandlerState}
end;
_ ->
case maps:get(protocols, Destination, [http]) of
[http] ->
{[{state, State2},
- {origin, <<"http">>, NewHost, NewPort, connect}], EvHandlerState};
+ {origin, <<"http">>, NewHost, NewPort, connect}], EvHandlerState1};
[http2] ->
{[{origin, <<"http">>, NewHost, NewPort, connect},
- {switch_protocol, gun_http2, State2}], EvHandlerState}
+ {switch_protocol, gun_http2, State2}], EvHandlerState1}
end
end;
{_, _} when Status >= 100, Status =< 199 ->
diff --git a/test/event_SUITE.erl b/test/event_SUITE.erl
index 79416f3..fb45bc9 100644
--- a/test/event_SUITE.erl
+++ b/test/event_SUITE.erl
@@ -30,15 +30,15 @@ all() ->
groups() ->
Tests = ct_helper:all(?MODULE),
- %% Some tests are written only for HTTP/1.0.
- HTTP10Tests = [T || T <- Tests, lists:sublist(atom_to_list(T), 7) =:= "http10_"],
+ %% Some tests are written only for HTTP/1.0 or HTTP/1.1.
+ HTTP1Tests = [T || T <- Tests, lists:sublist(atom_to_list(T), 6) =:= "http1_"],
%% Push is not possible over HTTP/1.1.
PushTests = [T || T <- Tests, lists:sublist(atom_to_list(T), 5) =:= "push_"],
%% We currently do not support Websocket over HTTP/2.
WsTests = [T || T <- Tests, lists:sublist(atom_to_list(T), 3) =:= "ws_"],
[
{http, [parallel], Tests -- [cancel_remote|PushTests]},
- {http2, [parallel], (Tests -- [protocol_changed|WsTests]) -- HTTP10Tests}
+ {http2, [parallel], (Tests -- [protocol_changed|WsTests]) -- HTTP1Tests}
].
init_per_suite(Config) ->
@@ -233,9 +233,93 @@ tls_handshake_end_ok(Config) ->
timeout := _,
protocol := Protocol
} = do_receive_event(tls_handshake_end),
- false = is_port(Socket),
+ true = is_tuple(Socket),
gun:close(Pid).
+http1_tls_handshake_start_connect(Config) ->
+ doc("Confirm that the tls_handshake_start event callback is called "
+ "when using CONNECT to a TLS server via a TCP proxy."),
+ OriginPort = config(tls_origin_port, Config),
+ {ok, _, ProxyPort} = rfc7231_SUITE:do_proxy_start(tcp),
+ {ok, ConnPid} = gun:open("localhost", ProxyPort, #{
+ event_handler => {?MODULE, self()},
+ protocols => [config(name, config(tc_group_properties, Config))],
+ transport => tcp
+ }),
+ {ok, http} = gun:await_up(ConnPid),
+ StreamRef = gun:connect(ConnPid, #{
+ host => "localhost",
+ port => OriginPort,
+ transport => tls
+ }),
+ ReplyTo = self(),
+ #{
+ stream_ref := StreamRef,
+ reply_to := ReplyTo,
+ socket := Socket,
+ tls_opts := _,
+ timeout := _
+ } = do_receive_event(tls_handshake_start),
+ true = is_port(Socket),
+ gun:close(ConnPid).
+
+http1_tls_handshake_end_error_connect(Config) ->
+ doc("Confirm that the tls_handshake_end event callback is called on TLS handshake error "
+ "when using CONNECT to a TLS server via a TCP proxy."),
+ %% We use the wrong port on purpose to trigger a handshake error.
+ OriginPort = config(tcp_origin_port, Config),
+ {ok, _, ProxyPort} = rfc7231_SUITE:do_proxy_start(tcp),
+ {ok, ConnPid} = gun:open("localhost", ProxyPort, #{
+ event_handler => {?MODULE, self()},
+ protocols => [config(name, config(tc_group_properties, Config))],
+ transport => tcp
+ }),
+ {ok, http} = gun:await_up(ConnPid),
+ StreamRef = gun:connect(ConnPid, #{
+ host => "localhost",
+ port => OriginPort,
+ transport => tls
+ }),
+ ReplyTo = self(),
+ #{
+ stream_ref := StreamRef,
+ reply_to := ReplyTo,
+ socket := Socket,
+ tls_opts := _,
+ timeout := _,
+ error := {tls_alert, _}
+ } = do_receive_event(tls_handshake_end),
+ true = is_port(Socket),
+ gun:close(ConnPid).
+
+http1_tls_handshake_end_ok_connect(Config) ->
+ doc("Confirm that the tls_handshake_end event callback is called on TLS handshake success "
+ "when using CONNECT to a TLS server via a TCP proxy."),
+ OriginPort = config(tls_origin_port, Config),
+ {ok, _, ProxyPort} = rfc7231_SUITE:do_proxy_start(tcp),
+ {ok, ConnPid} = gun:open("localhost", ProxyPort, #{
+ event_handler => {?MODULE, self()},
+ protocols => [config(name, config(tc_group_properties, Config))],
+ transport => tcp
+ }),
+ {ok, http} = gun:await_up(ConnPid),
+ StreamRef = gun:connect(ConnPid, #{
+ host => "localhost",
+ port => OriginPort,
+ transport => tls
+ }),
+ ReplyTo = self(),
+ #{
+ stream_ref := StreamRef,
+ reply_to := ReplyTo,
+ socket := Socket,
+ tls_opts := _,
+ timeout := _,
+ protocol := http
+ } = do_receive_event(tls_handshake_end),
+ true = is_tuple(Socket),
+ gun:close(ConnPid).
+
request_start(Config) ->
doc("Confirm that the request_start event callback is called."),
do_request_event(Config, ?FUNCTION_NAME),
@@ -477,7 +561,7 @@ do_response_end(Config, EventName, Path) ->
} = do_receive_event(EventName),
gun:close(Pid).
-http10_response_end_body_close(Config) ->
+http1_response_end_body_close(Config) ->
doc("Confirm that the request_headers event callback is called "
"when using HTTP/1.0 and the content-length header is not set."),
OriginPort = config(tcp_origin_port, Config),