diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/gun.erl | 156 | ||||
-rw-r--r-- | src/gun_http.erl | 1 | ||||
-rw-r--r-- | src/gun_http2.erl | 30 | ||||
-rw-r--r-- | src/gun_socks.erl | 1 | ||||
-rw-r--r-- | src/gun_tunnel.erl | 90 |
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()]; |