aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorLoïc Hoguin <[email protected]>2020-09-25 13:52:31 +0200
committerLoïc Hoguin <[email protected]>2020-10-03 17:30:36 +0200
commit2f42047d6cec210186d703e31e5fd970b1ea4e33 (patch)
tree30c529955a79df3cb0cb13021c951ce655617f95 /src
parent3ce70c5d96902e0e2718447383992dc7d6232e3a (diff)
downloadgun-2f42047d6cec210186d703e31e5fd970b1ea4e33.tar.gz
gun-2f42047d6cec210186d703e31e5fd970b1ea4e33.tar.bz2
gun-2f42047d6cec210186d703e31e5fd970b1ea4e33.zip
Add tunnel_SUITE testing all 3-layer combinations
The test suite is 216 tests with a tunnel created via two proxies leading to one origin server. The tests are for example socks5_h2_https where socks5 identifies the first SOCKS5 proxy, h2 the second HTTP/2 CONNECT proxy and https the secure HTTP/1.1 origin server. The test not only sets up the tunnel and does a request (or sends/receives data in the case of raw origin servers) but also confirms that the stream_info and info data is correct.
Diffstat (limited to 'src')
-rw-r--r--src/gun.erl156
-rw-r--r--src/gun_http.erl1
-rw-r--r--src/gun_http2.erl30
-rw-r--r--src/gun_socks.erl1
-rw-r--r--src/gun_tunnel.erl90
5 files changed, 237 insertions, 41 deletions
diff --git a/src/gun.erl b/src/gun.erl
index ea599bf..4a0df0a 100644
--- a/src/gun.erl
+++ b/src/gun.erl
@@ -457,7 +457,10 @@ info(ServerPid) ->
<<"http">> -> tcp;
<<"https">> -> tls
end,
- origin_scheme => OriginScheme,
+ origin_scheme => case Protocol of
+ gun_raw -> undefined;
+ _ -> OriginScheme
+ end,
origin_host => OriginHost,
origin_port => OriginPort,
intermediaries => intermediaries_info(Intermediaries, []),
@@ -1096,7 +1099,11 @@ tls_handshake(internal, {tls_handshake, HandshakeEvent, Protocols, ReplyTo},
StreamRef = maps:get(stream_ref, HandshakeEvent, undefined),
case normal_tls_handshake(Socket, State0, HandshakeEvent, Protocols) of
{ok, TLSSocket, NewProtocol0, State} ->
- NewProtocol = gun_protocols:add_stream_ref(NewProtocol0, StreamRef),
+ NewProtocol1 = gun_protocols:add_stream_ref(NewProtocol0, StreamRef),
+ NewProtocol = case NewProtocol1 of
+ {NewProtocolName, NewProtocolOpts} -> {NewProtocolName, NewProtocolOpts#{tunnel_transport => tls}};
+ NewProtocolName -> {NewProtocolName, #{tunnel_transport => tls}}
+ end,
Protocol = gun_protocols:handler(NewProtocol),
ReplyTo ! {gun_tunnel_up, self(), StreamRef, Protocol:name()},
commands([
@@ -1127,7 +1134,11 @@ tls_handshake(info, {gun_tls_proxy, Socket, {ok, Negotiated}, {HandshakeEvent, P
State0=#state{socket=Socket, event_handler=EvHandler, event_handler_state=EvHandlerState0}) ->
NewProtocol0 = gun_protocols:negotiated(Negotiated, Protocols),
StreamRef = maps:get(stream_ref, HandshakeEvent, undefined),
- NewProtocol = gun_protocols:add_stream_ref(NewProtocol0, StreamRef),
+ NewProtocol1 = gun_protocols:add_stream_ref(NewProtocol0, StreamRef),
+ NewProtocol = case NewProtocol1 of
+ {NewProtocolName, NewProtocolOpts} -> {NewProtocolName, NewProtocolOpts#{tunnel_transport => tls}};
+ NewProtocolName -> {NewProtocolName, #{tunnel_transport => tls}}
+ end,
Protocol = gun_protocols:handler(NewProtocol),
ReplyTo ! {gun_tunnel_up, self(), StreamRef, Protocol:name()},
EvHandlerState = EvHandler:tls_handshake_end(HandshakeEvent#{
@@ -1218,7 +1229,8 @@ connected(cast, {headers, ReplyTo, StreamRef, Method, Path, Headers0, InitialFlo
event_handler=EvHandler, event_handler_state=EvHandlerState0}) ->
{Headers, State} = add_cookie_header(Path, Headers0, State0),
{ProtoState2, EvHandlerState} = Protocol:headers(ProtoState,
- StreamRef, ReplyTo, Method, Host, Port, Path, Headers,
+ dereference_stream_ref(StreamRef, State), ReplyTo,
+ Method, Host, Port, Path, Headers,
InitialFlow, EvHandler, EvHandlerState0),
{keep_state, State#state{protocol_state=ProtoState2, event_handler_state=EvHandlerState}};
connected(cast, {request, ReplyTo, StreamRef, Method, Path, Headers0, Body, InitialFlow},
@@ -1227,14 +1239,16 @@ connected(cast, {request, ReplyTo, StreamRef, Method, Path, Headers0, Body, Init
event_handler=EvHandler, event_handler_state=EvHandlerState0}) ->
{Headers, State} = add_cookie_header(Path, Headers0, State0),
{ProtoState2, EvHandlerState} = Protocol:request(ProtoState,
- StreamRef, ReplyTo, Method, Host, Port, Path, Headers, Body,
+ dereference_stream_ref(StreamRef, State), ReplyTo,
+ Method, Host, Port, Path, Headers, Body,
InitialFlow, EvHandler, EvHandlerState0),
{keep_state, State#state{protocol_state=ProtoState2, event_handler_state=EvHandlerState}};
connected(cast, {connect, ReplyTo, StreamRef, Destination, Headers, InitialFlow},
State=#state{origin_host=Host, origin_port=Port,
protocol=Protocol, protocol_state=ProtoState}) ->
%% @todo No events are currently handled for the CONNECT request?
- ProtoState2 = Protocol:connect(ProtoState, StreamRef, ReplyTo,
+ ProtoState2 = Protocol:connect(ProtoState,
+ dereference_stream_ref(StreamRef, State), ReplyTo,
Destination, #{host => Host, port => Port},
Headers, InitialFlow),
{keep_state, State#state{protocol_state=ProtoState2}};
@@ -1302,6 +1316,24 @@ add_cookie_header(PathWithQs, Headers0, State=#state{
end,
{Headers, State#state{cookie_store=Store}}.
+%% When the origin is using raw we do not dereference the stream_ref
+%% because it expects the full stream_ref to function (there's no
+%% other stream involved for this connection).
+dereference_stream_ref(StreamRef, #state{protocol=gun_raw}) ->
+ StreamRef;
+dereference_stream_ref(StreamRef, #state{intermediaries=Intermediaries}) ->
+ %% @todo It would be better to validate with the intermediary's stream_refs.
+ case length([http || #{protocol := http} <- Intermediaries]) of
+ 0 ->
+ StreamRef;
+ N ->
+ {_, Tail} = lists:split(N, StreamRef),
+ case Tail of
+ [SR] -> SR;
+ _ -> Tail
+ end
+ end.
+
%% Switch to the graceful connection close state.
closing(State=#state{protocol=Protocol, protocol_state=ProtoState,
event_handler=EvHandler, event_handler_state=EvHandlerState0}, Reason) ->
@@ -1329,7 +1361,8 @@ handle_common_connected(cast, {data, ReplyTo, StreamRef, IsFin, Data}, _,
State=#state{protocol=Protocol, protocol_state=ProtoState,
event_handler=EvHandler, event_handler_state=EvHandlerState0}) ->
{ProtoState2, EvHandlerState} = Protocol:data(ProtoState,
- StreamRef, ReplyTo, IsFin, Data, EvHandler, EvHandlerState0),
+ dereference_stream_ref(StreamRef, State),
+ ReplyTo, IsFin, Data, EvHandler, EvHandlerState0),
{keep_state, State#state{protocol_state=ProtoState2, event_handler_state=EvHandlerState}};
handle_common_connected(info, {timeout, TRef, Name}, _,
State=#state{protocol=Protocol, protocol_state=ProtoState}) ->
@@ -1364,8 +1397,9 @@ handle_common_connected_no_input(info,
Msg={gun_tls_proxy, _, _, {handle_continue, StreamRef, _, _}}, _,
State0=#state{protocol=Protocol, protocol_state=ProtoState,
event_handler=EvHandler, event_handler_state=EvHandlerState0}) ->
- {Commands, EvHandlerState} = Protocol:handle_continue(StreamRef, Msg,
- ProtoState, EvHandler, EvHandlerState0),
+ {Commands, EvHandlerState} = Protocol:handle_continue(
+ dereference_stream_ref(StreamRef, State0),
+ Msg, ProtoState, EvHandler, EvHandlerState0),
case commands(Commands, State0#state{event_handler_state=EvHandlerState}) of
{keep_state, State} ->
{keep_state, active(State)};
@@ -1390,8 +1424,9 @@ handle_common_connected_no_input(info,
handle_common_connected_no_input(info, {handle_continue, StreamRef, Msg}, _,
State0=#state{protocol=Protocol, protocol_state=ProtoState,
event_handler=EvHandler, event_handler_state=EvHandlerState0}) ->
- {Commands, EvHandlerState} = Protocol:handle_continue(StreamRef, Msg,
- ProtoState, EvHandler, EvHandlerState0),
+ {Commands, EvHandlerState} = Protocol:handle_continue(
+ dereference_stream_ref(StreamRef, State0),
+ Msg, ProtoState, EvHandler, EvHandlerState0),
case commands(Commands, State0#state{event_handler_state=EvHandlerState}) of
{keep_state, State} ->
{keep_state, active(State)};
@@ -1426,11 +1461,93 @@ handle_common_connected_no_input(cast, {cancel, ReplyTo, StreamRef}, _,
StreamRef, ReplyTo, EvHandler, EvHandlerState0),
{keep_state, State#state{protocol_state=ProtoState2, event_handler_state=EvHandlerState}};
handle_common_connected_no_input({call, From}, {stream_info, StreamRef}, _,
- #state{protocol=Protocol, protocol_state=ProtoState}) ->
- {keep_state_and_data, {reply, From, Protocol:stream_info(ProtoState, StreamRef)}};
+ State=#state{intermediaries=Intermediaries0, protocol=Protocol, protocol_state=ProtoState}) ->
+ Intermediaries = [I || I=#{protocol := http} <- Intermediaries0],
+ {keep_state_and_data, {reply, From,
+ if
+ %% The stream_ref refers to an intermediary.
+ %% @todo This is probably wrong.
+ length(StreamRef) =< length(Intermediaries) ->
+ Intermediary = lists:nth(length(StreamRef), lists:reverse(Intermediaries)),
+ {Intermediaries1, Tail} = lists:splitwith(
+ fun(Int) -> Int =/= Intermediary end,
+ lists:reverse(Intermediaries0)),
+ Tunnel = tunnel_info_from_intermediaries(State, Tail),
+ {ok, #{
+ ref => StreamRef,
+ reply_to => undefined, %% @todo
+ state => running,
+ intermediaries => intermediaries_info(lists:reverse(Intermediaries1), []),
+ tunnel => Tunnel
+ }};
+ is_reference(StreamRef), Intermediaries =/= [] ->
+ %% We take all intermediaries up to the first CONNECT intermediary.
+ {Intermediaries1, Tail} = lists:splitwith(
+ fun(#{protocol := P}) -> P =:= socks end,
+ lists:reverse(Intermediaries0)),
+ Tunnel = tunnel_info_from_intermediaries(State, Tail),
+ {ok, #{
+ ref => StreamRef,
+ reply_to => undefined, %% @todo
+ state => running,
+ intermediaries => intermediaries_info(lists:reverse(Intermediaries1), []),
+ tunnel => Tunnel
+ }};
+ true ->
+ {ok, Info0} = Protocol:stream_info(ProtoState, dereference_stream_ref(StreamRef, State)),
+ Info = Info0#{ref => StreamRef},
+ case Intermediaries0 of
+ [] ->
+ {ok, Info};
+ _ ->
+ Tail = maps:get(intermediaries, Info, []),
+ {ok, Info#{
+ intermediaries => intermediaries_info(Intermediaries0, []) ++ Tail
+ }}
+ end
+ end
+ }};
handle_common_connected_no_input(Type, Event, StateName, State) ->
handle_common(Type, Event, StateName, State).
+tunnel_info_from_intermediaries(State, Tail) ->
+ case Tail of
+ %% If the next endpoint is an intermediary take its infos.
+ [_, Intermediary|_] ->
+ #{
+ host := IntermediaryHost,
+ port := IntermediaryPort,
+ transport := IntermediaryTransport,
+ protocol := IntermediaryProtocol
+ } = Intermediary,
+ #{
+ transport => IntermediaryTransport,
+ protocol => IntermediaryProtocol,
+ origin_scheme => case IntermediaryTransport of
+ tcp -> <<"http">>;
+ tls -> <<"https">>
+ end,
+ origin_host => IntermediaryHost,
+ origin_port => IntermediaryPort
+ };
+ %% Otherwise take the infos from the state.
+ _ ->
+ tunnel_info_from_state(State)
+ end.
+
+tunnel_info_from_state(#state{origin_scheme=OriginScheme,
+ origin_host=OriginHost, origin_port=OriginPort, protocol=Proto}) ->
+ #{
+ transport => case OriginScheme of
+ <<"http">> -> tcp;
+ <<"https">> -> tls
+ end,
+ protocol => Proto:name(),
+ origin_scheme => OriginScheme,
+ origin_host => OriginHost,
+ origin_port => OriginPort
+ }.
+
%% Common events.
handle_common(cast, {set_owner, CurrentOwner, NewOwner}, _,
State=#state{owner=CurrentOwner, status={up, CurrentOwnerRef}}) ->
@@ -1544,7 +1661,7 @@ commands([{set_cookie, Authority, PathWithQs, _, Headers}|Tail], State=#state{
%% the transport and/or protocol in order to keep track
%% of the intermediaries properly.
commands([{origin, Scheme, Host, Port, Type}|Tail],
- State=#state{transport=Transport, protocol=Protocol,
+ State=#state{protocol=Protocol, origin_scheme=IntermediateScheme,
origin_host=IntermediateHost, origin_port=IntermediatePort, intermediaries=Intermediaries,
event_handler=EvHandler, event_handler_state=EvHandlerState0}) ->
EvHandlerState = EvHandler:origin_changed(#{
@@ -1557,7 +1674,10 @@ commands([{origin, Scheme, Host, Port, Type}|Tail],
type => Type,
host => IntermediateHost,
port => IntermediatePort,
- transport => Transport:name(),
+ transport => case IntermediateScheme of
+ <<"http">> -> tcp;
+ <<"https">> -> tls
+ end,
protocol => Protocol:name()
},
commands(Tail, State#state{origin_scheme=Scheme,
@@ -1577,7 +1697,11 @@ commands([{switch_transport, Transport, Socket}|Tail], State=#state{
commands([{switch_protocol, NewProtocol, ReplyTo}], State0=#state{
opts=Opts, socket=Socket, transport=Transport,
event_handler=EvHandler, event_handler_state=EvHandlerState0}) ->
- {Protocol, ProtoOpts} = gun_protocols:handler_and_opts(NewProtocol, Opts),
+ {Protocol, ProtoOpts0} = gun_protocols:handler_and_opts(NewProtocol, Opts),
+ ProtoOpts = case ProtoOpts0 of
+ #{tunnel_transport := _} -> ProtoOpts0;
+ _ -> ProtoOpts0#{tunnel_transport => tcp}
+ end,
{StateName, ProtoState} = Protocol:init(ReplyTo, Socket, Transport, ProtoOpts),
EvHandlerState = EvHandler:protocol_changed(#{protocol => Protocol:name()}, EvHandlerState0),
%% We cancel the existing keepalive and, depending on the protocol,
diff --git a/src/gun_http.erl b/src/gun_http.erl
index 064bf04..d2a758b 100644
--- a/src/gun_http.erl
+++ b/src/gun_http.erl
@@ -557,6 +557,7 @@ headers(State=#http_state{opts=Opts, out=head},
{new_stream(State#http_state{connection=Conn, out=Out}, StreamRef, ReplyTo,
Method, Authority, Path, InitialFlow), EvHandlerState}.
+%% @todo I don't think this clause is hit anymore. Same in other related callbacks.
request(State, StreamRef, ReplyTo, Method, Host, Port,
Path, Headers, Body, InitialFlow, EvHandler, EvHandlerState)
when is_list(StreamRef) ->
diff --git a/src/gun_http2.erl b/src/gun_http2.erl
index 61b315b..f8b507d 100644
--- a/src/gun_http2.erl
+++ b/src/gun_http2.erl
@@ -90,6 +90,10 @@
%% inside an HTTP/2 CONNECT stream.
base_stream_ref = undefined :: undefined | gun:stream_ref(),
+ %% Real transport for the HTTP/2 layer, defined when we are
+ %% in a non-HTTP/2 tunnel.
+ tunnel_transport = undefined :: undefined | tcp | tls,
+
%% Current status of the connection. We use this to ensure we are
%% not sending the GOAWAY frame more than once, and to validate
%% the server connection preface.
@@ -172,10 +176,11 @@ init(_ReplyTo, Socket, Transport, Opts0) ->
{ok, Preface, HTTP2Machine} = cow_http2_machine:init(client, Opts),
Handlers = maps:get(content_handlers, Opts, [gun_data_h]),
BaseStreamRef = maps:get(stream_ref, Opts, undefined),
+ TunnelTransport = maps:get(tunnel_transport, Opts, undefined),
%% @todo Better validate the preface being received.
State = #http2_state{socket=Socket, transport=Transport, opts=Opts,
- base_stream_ref=BaseStreamRef, content_handlers=Handlers,
- http2_machine=HTTP2Machine},
+ base_stream_ref=BaseStreamRef, tunnel_transport=TunnelTransport,
+ content_handlers=Handlers, http2_machine=HTTP2Machine},
Transport:send(Socket, Preface),
{connected, State}.
@@ -350,7 +355,10 @@ tunnel_commands([SetCookie={set_cookie, _, _, _, _}|Tail], Stream, State=#http2_
tunnel_commands(Tail, Stream, State#http2_state{commands_queue=[SetCookie|Queue]}).
continue_stream_ref(#http2_state{socket=#{handle_continue_stream_ref := ContinueStreamRef}}, StreamRef) ->
- ContinueStreamRef ++ [StreamRef];
+ case ContinueStreamRef of
+ [_|_] -> ContinueStreamRef ++ [StreamRef];
+ _ -> [ContinueStreamRef|StreamRef]
+ end;
continue_stream_ref(State, StreamRef) ->
stream_ref(State, StreamRef).
@@ -391,8 +399,8 @@ data_frame(State0, StreamID, IsFin, Data, EvHandler, EvHandlerState0,
{maybe_delete_stream(State, StreamID, remote, IsFin), EvHandlerState}.
%% @todo Make separate functions for inform/connect/normal.
-headers_frame(State0=#http2_state{transport=Transport, opts=Opts,
- content_handlers=Handlers0, commands_queue=Commands},
+headers_frame(State0=#http2_state{socket=Socket, transport=Transport, opts=Opts,
+ tunnel_transport=TunnelTransport, content_handlers=Handlers0, commands_queue=Commands},
StreamID, IsFin, Headers, #{status := Status}, _BodyLen,
EvHandler, EvHandlerState0) ->
Stream = get_stream_by_id(State0, StreamID),
@@ -463,7 +471,10 @@ headers_frame(State0=#http2_state{transport=Transport, opts=Opts,
stream_ref => RealStreamRef,
tunnel => #{
type => connect,
- transport_name => Transport:name(),
+ transport_name => case TunnelTransport of
+ undefined -> Transport:name();
+ _ -> TunnelTransport
+ end,
protocol_name => http2,
info => TunnelInfo,
handshake_event => HandshakeEvent,
@@ -476,7 +487,10 @@ headers_frame(State0=#http2_state{transport=Transport, opts=Opts,
stream_ref => RealStreamRef,
tunnel => #{
type => connect,
- transport_name => Transport:name(),
+ transport_name => case TunnelTransport of
+ undefined -> Transport:name();
+ _ -> TunnelTransport
+ end,
protocol_name => http2,
info => TunnelInfo,
new_protocol => NewProtocol
@@ -1045,7 +1059,7 @@ stream_info(State, StreamRef) when is_reference(StreamRef) ->
state => running,
tunnel => #{
transport => Transport,
- protocol => Proto:tunneled_name(ProtoState),
+ protocol => Proto:tunneled_name(ProtoState, true),
origin_scheme => case Transport of
tcp -> <<"http">>;
tls -> <<"https">>
diff --git a/src/gun_socks.erl b/src/gun_socks.erl
index cf5c41c..752daec 100644
--- a/src/gun_socks.erl
+++ b/src/gun_socks.erl
@@ -141,6 +141,7 @@ handle(<<5, 0, 0, Rest0/bits>>, #socks_state{ref=StreamRef, reply_to=ReplyTo, op
case Opts of
#{transport := tls} ->
HandshakeEvent0 = #{
+ reply_to => ReplyTo,
tls_opts => maps:get(tls_opts, Opts, []),
timeout => maps:get(tls_handshake_timeout, Opts, infinity)
},
diff --git a/src/gun_tunnel.erl b/src/gun_tunnel.erl
index 588317b..71ad137 100644
--- a/src/gun_tunnel.erl
+++ b/src/gun_tunnel.erl
@@ -31,7 +31,7 @@
-export([cancel/5]).
-export([timeout/3]).
-export([stream_info/2]).
--export([tunneled_name/1]).
+-export([tunneled_name/2]).
-export([down/1]).
%-export([ws_upgrade/10]).
@@ -51,6 +51,9 @@
%% the stream_ref is the same as the HTTP/1.1 CONNECT one.
stream_ref = undefined :: gun:stream_ref(),
+ %% The pid we send messages to.
+ reply_to = undefined :: pid(),
+
%% When the tunnel is a 'connect' tunnel we must dereference the
%% stream_ref. When it is 'socks' we must not as there was no
%% stream involved in creating the tunnel.
@@ -101,7 +104,7 @@ init(ReplyTo, OriginSocket, OriginTransport, Opts=#{stream_ref := StreamRef, tun
protocol_name := TunnelProtocol,
info := TunnelInfo
} = Tunnel,
- State = #tunnel_state{stream_ref=StreamRef, type=TunnelType,
+ State = #tunnel_state{stream_ref=StreamRef, reply_to=ReplyTo, type=TunnelType,
tunnel_transport=TunnelTransport, tunnel_protocol=TunnelProtocol,
info=TunnelInfo, opts=maps:without([stream_ref, tunnel], Opts)},
case Tunnel of
@@ -109,9 +112,17 @@ init(ReplyTo, OriginSocket, OriginTransport, Opts=#{stream_ref := StreamRef, tun
#{new_protocol := NewProtocol} ->
{Proto, ProtoOpts} = gun_protocols:handler_and_opts(NewProtocol, Opts),
{_, ProtoState} = Proto:init(ReplyTo, OriginSocket, OriginTransport,
- ProtoOpts#{stream_ref => StreamRef}),
+ ProtoOpts#{stream_ref => StreamRef, tunnel_transport => tcp}),
%% @todo EvHandlerState = EvHandler:protocol_changed(#{protocol => Protocol:name()}, EvHandlerState0),
- ReplyTo ! {gun_tunnel_up, self(), StreamRef, Proto:name()},
+ %% When the tunnel protocol is HTTP/1.1 or SOCKS
+ %% the gun_tunnel_up message was already sent.
+ %%
+ %% @todo There's probably a better way.
+ _ = case TunnelProtocol of
+ http -> ok;
+ socks -> ok;
+ _ -> ReplyTo ! {gun_tunnel_up, self(), StreamRef, Proto:name()}
+ end,
{tunnel, State#tunnel_state{socket=OriginSocket, transport=OriginTransport,
protocol=Proto, protocol_state=ProtoState}};
%% We can't initialize the protocol until the TLS handshake has completed.
@@ -172,11 +183,10 @@ handle_continue(ContinueStreamRef, {gun_tls_proxy, ProxyPid, {ok, Negotiated},
OriginSocket = #{
gun_pid => self(),
reply_to => ReplyTo,
- stream_ref => StreamRef%,
-% handle_continue_stream_ref => ContinueStreamRef
+ stream_ref => StreamRef
},
{_, ProtoState} = Proto:init(ReplyTo, OriginSocket, gun_tcp_proxy,
- ProtoOpts#{stream_ref => StreamRef}),
+ ProtoOpts#{stream_ref => StreamRef, tunnel_transport => tls}),
ReplyTo ! {gun_tunnel_up, self(), StreamRef, Proto:name()},
{{state, State#tunnel_state{protocol=Proto, protocol_state=ProtoState}}, EvHandlerState0};
handle_continue(ContinueStreamRef, {gun_tls_proxy, ProxyPid, {error, _Reason},
@@ -269,7 +279,18 @@ request(State=#tunnel_state{protocol=Proto, protocol_state=ProtoState0,
InitialFlow, EvHandler, EvHandlerState0),
{State#tunnel_state{protocol_state=ProtoState}, EvHandlerState}.
-%% We pass the data forward and optionally dereference StreamRef.
+%% When the next tunnel is SOCKS we pass the data forward directly.
+%% This is needed because SOCKS has no StreamRef and the data cannot
+%% therefore be passed forward through the usual method.
+data(State=#tunnel_state{protocol=Proto, protocol_state=ProtoState0,
+ protocol_origin={origin, _, _, _, socks5}},
+ StreamRef, ReplyTo, IsFin, Data, EvHandler, EvHandlerState0) ->
+ {ProtoState, EvHandlerState} = Proto:data(ProtoState0, StreamRef,
+ ReplyTo, IsFin, Data, EvHandler, EvHandlerState0),
+ {State#tunnel_state{protocol_state=ProtoState}, EvHandlerState};
+%% CONNECT tunnels pass the data forward and dereference StreamRef
+%% unless they are the recipient of the callback, in which case the
+%% data is sent to the socket.
data(State=#tunnel_state{socket=Socket, transport=Transport,
stream_ref=TunnelStreamRef0, protocol=Proto, protocol_state=ProtoState0},
StreamRef0, ReplyTo, IsFin, Data, EvHandler, EvHandlerState0) ->
@@ -286,11 +307,12 @@ data(State=#tunnel_state{socket=Socket, transport=Transport,
end.
%% We pass the CONNECT request forward and optionally dereference StreamRef.
-connect(State=#tunnel_state{protocol=Proto, protocol_state=ProtoState0},
- StreamRef0, ReplyTo, Destination, TunnelInfo, Headers, InitialFlow) ->
+connect(State=#tunnel_state{info=#{origin_host := Host, origin_port := Port},
+ protocol=Proto, protocol_state=ProtoState0},
+ StreamRef0, ReplyTo, Destination, _, Headers, InitialFlow) ->
StreamRef = maybe_dereference(State, StreamRef0),
ProtoState = Proto:connect(ProtoState0, StreamRef,
- ReplyTo, Destination, TunnelInfo, Headers, InitialFlow),
+ ReplyTo, Destination, #{host => Host, port => Port}, Headers, InitialFlow),
State#tunnel_state{protocol_state=ProtoState}.
%% @todo ?
@@ -302,6 +324,33 @@ cancel(_State, _StreamRef, _ReplyTo, _EvHandler, _EvHandlerState) ->
timeout(_State, {cow_http2_machine, _Name}, _TRef) ->
todo.
+stream_info(#tunnel_state{transport=Transport0, stream_ref=TunnelStreamRef, reply_to=ReplyTo,
+ tunnel_protocol=TunnelProtocol,
+ info=#{origin_host := OriginHost, origin_port := OriginPort},
+ protocol=Proto, protocol_state=ProtoState}, StreamRef)
+ when is_reference(StreamRef), TunnelProtocol =/= socks ->
+ Transport = case Transport0 of
+ gun_tcp_proxy -> tcp;
+ gun_tls_proxy -> tls
+ end,
+ {ok, #{
+ ref => TunnelStreamRef,
+ reply_to => ReplyTo,
+ state => running,
+ tunnel => #{
+ transport => Transport,
+ protocol => case Proto of
+ gun_tunnel -> Proto:tunneled_name(ProtoState, false);
+ _ -> Proto:name()
+ end,
+ origin_scheme => case Transport of
+ tcp -> <<"http">>;
+ tls -> <<"https">>
+ end,
+ origin_host => OriginHost,
+ origin_port => OriginPort
+ }
+ }};
stream_info(State=#tunnel_state{type=Type,
tunnel_transport=IntermediaryTransport, tunnel_protocol=IntermediaryProtocol,
info=TunnelInfo, protocol=Proto, protocol_state=ProtoState}, StreamRef0) ->
@@ -327,9 +376,11 @@ stream_info(State=#tunnel_state{type=Type,
}}
end.
-tunneled_name(#tunnel_state{protocol=Proto=gun_tunnel, protocol_state=ProtoState}) ->
- Proto:tunneled_name(ProtoState);
-tunneled_name(#tunnel_state{protocol=Proto}) ->
+tunneled_name(#tunnel_state{protocol=Proto=gun_tunnel, protocol_state=ProtoState}, true) ->
+ Proto:tunneled_name(ProtoState, false);
+tunneled_name(#tunnel_state{tunnel_protocol=TunnelProto}, false) ->
+ TunnelProto;
+tunneled_name(#tunnel_state{protocol=Proto}, _) ->
Proto:name().
%% @todo ?
@@ -394,13 +445,18 @@ commands([{switch_protocol, NewProtocol, ReplyTo}|Tail],
{_, ProtoState} = Proto:init(ReplyTo, OriginSocket, gun_tcp_proxy, ProtoOpts),
%% @todo EvHandlerState = EvHandler:protocol_changed(#{protocol => Protocol:name()}, EvHandlerState0),
commands(Tail, State#tunnel_state{protocol=Proto, protocol_state=ProtoState});
-commands([{tls_handshake, HandshakeEvent, Protocols, ReplyTo}|Tail],
+commands([{tls_handshake, HandshakeEvent0, Protocols, ReplyTo}|Tail],
State=#tunnel_state{transport=Transport,
info=#{origin_host := Host, origin_port := Port}, opts=Opts, protocol=CurrentProto,
protocol_origin={origin, _Scheme, OriginHost, OriginPort, Type}}) ->
#{
- stream_ref := StreamRef
- } = HandshakeEvent,
+ stream_ref := StreamRef,
+ tls_opts := TLSOpts0
+ } = HandshakeEvent0,
+ TLSOpts = gun:ensure_alpn_sni(Protocols, TLSOpts0, OriginHost),
+ HandshakeEvent = HandshakeEvent0#{
+ tls_opts => TLSOpts
+ },
ContinueStreamRef0 = continue_stream_ref(State),
ContinueStreamRef = case Type of
socks5 -> ContinueStreamRef0 ++ [make_ref()];