diff options
-rw-r--r-- | src/gun.erl | 3 | ||||
-rw-r--r-- | src/gun_http2.erl | 89 | ||||
-rw-r--r-- | src/gun_tls_proxy.erl | 1 | ||||
-rw-r--r-- | src/gun_tls_proxy_http2_connect.erl | 10 | ||||
-rw-r--r-- | test/raw_SUITE.erl | 1 | ||||
-rw-r--r-- | test/rfc7540_SUITE.erl | 15 | ||||
-rw-r--r-- | test/socks_SUITE.erl | 3 |
7 files changed, 18 insertions, 104 deletions
diff --git a/src/gun.erl b/src/gun.erl index 24ec9c0..e83c709 100644 --- a/src/gun.erl +++ b/src/gun.erl @@ -724,7 +724,7 @@ await(ServerPid, StreamRef, Timeout, MRef) -> {upgrade, Protocols, Headers}; {gun_ws, ServerPid, StreamRef, Frame} -> {ws, Frame}; - {gun_socks_up, ServerPid, StreamRef, Protocol} -> + {gun_tunnel_up, ServerPid, StreamRef, Protocol} -> {up, Protocol}; {gun_error, ServerPid, StreamRef, Reason} -> {error, {stream_error, Reason}}; @@ -808,6 +808,7 @@ await_up(ServerPid, Timeout, MRef) -> receive {gun_up, ServerPid, Protocol} -> {ok, Protocol}; + %% @todo Maybe name it gun_tunnel_up. And send it for HTTP/1.1 CONNECT and HTTP/2 CONNECT and SOCKS. {gun_socks_up, ServerPid, Protocol} -> {ok, Protocol}; {'DOWN', MRef, process, ServerPid, Reason} -> diff --git a/src/gun_http2.erl b/src/gun_http2.erl index bd74957..da6747b 100644 --- a/src/gun_http2.erl +++ b/src/gun_http2.erl @@ -326,29 +326,9 @@ data_frame(State, StreamID, IsFin, Data, EvHandler, EvHandlerState0) -> stream_ref => stream_ref(State, StreamRef) }, ProxyPid ! {tls_proxy_http2_connect, OriginSocket, Data}, -io:format(user, "(~p) ~p:~p/~p: data ~p~n", - [self(), ?MODULE, ?FUNCTION_NAME, ?FUNCTION_ARITY, Data]), %% @todo What about IsFin? {State, EvHandlerState0}; Stream=#stream{tunnel={Protocol, ProtoState0, TunnelInfo}} -> - %% @todo Can't call Protocol:handle directly, may need to unwrap TLS first... - - %% in this case we know Transport is either gun_tcp_proxy or gun_tls_proxy - %% if gun_tcp_proxy we can dispatch to Protocol:handle directly; - %% otherwise we must pass the data to gun_tls_proxy - %% -> send {ssl, Socket, Data} - %% -> eventually Gun process receives {Tag, Socket, Data} - %% -> somehow it needs to call this stream to resume processing and call Protocol:handle - - %% maybe {Tag, Socket, Data, Info} instead and Info is used to dispatch - %% maybe {stream_Tag, StreamRef, Data} - %% -> StreamRef to know which stream is the connect stream (potentially recursive) - %% -> Protocol:resume_handle(Data, StreamRef, State, EvHandler, EvHandlerState) - %% -> if reference() then we do Protocol:handle/4 - %% -> otherwise we pass to the next stream onward - - %% This means that #stream{} must contain both the user-facing StreamRef and the reference. - {Commands, EvHandlerState} = Protocol:handle(Data, ProtoState0, EvHandler, EvHandlerState0), {tunnel_commands(Commands, Stream, Protocol, TunnelInfo, State), EvHandlerState} end. @@ -388,7 +368,7 @@ tunnel_commands([{switch_protocol, Protocol0, ReplyTo}|Tail], Stream=#stream{ref end, %% When we switch_protocol from socks we must send a gun_socks_up message. _ = case CurrentProtocol of - gun_socks -> ReplyTo ! {gun_socks_up, self(), stream_ref(State, StreamRef), Protocol:name()}; + gun_socks -> ReplyTo ! {gun_tunnel_up, self(), stream_ref(State, StreamRef), Protocol:name()}; _ -> ok end, OriginSocket = #{ @@ -524,6 +504,7 @@ headers_frame(State0=#http2_state{content_handlers=Handlers0, commands_queue=Com {_, ProtoState} = Protocol:init(ReplyTo, OriginSocket, gun_tcp_proxy, ProtoOpts#{stream_ref => RealStreamRef}), %% @todo EvHandlerState = EvHandler:protocol_changed(#{protocol => Protocol:name()}, EvHandlerState0), %% @todo What about keepalive? + ReplyTo ! {gun_tunnel_up, self(), RealStreamRef, Protocol:name()}, {store_stream(State, Stream#stream{tunnel={Protocol, ProtoState, TunnelInfo#{origin_host => DestHost, origin_port => DestPort}}}), EvHandlerState} @@ -653,6 +634,7 @@ handle_continue(StreamRef, Msg, State, EvHandler, EvHandlerState0) end, {_, ProtoState} = Protocol:init(ReplyTo, OriginSocket, gun_tcp_proxy, ProtoOpts#{stream_ref => RealStreamRef}), + ReplyTo ! {gun_tunnel_up, self(), RealStreamRef, Protocol:name()}, {{state, store_stream(State, Stream#stream{tunnel={Protocol, ProtoState, TunnelInfo#{origin_host => DestHost, origin_port => DestPort}}})}, EvHandlerState0}; @@ -681,8 +663,6 @@ handle_continue(StreamRef, Msg, State, EvHandler, EvHandlerState0) case Msg of %% Data that was received and decrypted. {tls_proxy, ProxyPid, Data} -> -io:format(user, "(~p) ~p:~p/~p: data ~p~n", - [self(), ?MODULE, ?FUNCTION_NAME, ?FUNCTION_ARITY, Data]), {Commands, EvHandlerState} = Protocol:handle(Data, ProtoState0, EvHandler, EvHandlerState0), {tunnel_commands(Commands, Stream, Protocol, TunnelInfo, State), EvHandlerState}; %% @todo What to do about those? @@ -691,38 +671,14 @@ io:format(user, "(~p) ~p:~p/~p: data ~p~n", {tls_proxy_error, ProxyPid, _Reason} -> todo; %% Data that must be sent as a DATA frame. - {data, ReplyTo, _, IsFin, Data} -> + {data, _, _, IsFin, Data} -> {State1, EvHandlerState} = maybe_send_data(State, StreamID, IsFin, Data, EvHandler, EvHandlerState0), {{state, State1}, EvHandlerState} end - - -% {store_stream(State, Stream#stream{tunnel={Proto, ProtoState, TunnelInfo}}), EvHandlerState}%; - %% The stream may have ended while TLS was being decoded. @todo What should we do? +%% @todo Is this possible? % error -> % {error_stream_not_found(State, StreamRef, ReplyTo), EvHandlerState0} end; - - - -% [Protocol0] = maps:get(protocols, Destination, [http]), -% %% Options are either passed directly or #{} is used. Since the -% %% protocol only applies to a stream we cannot use connection-wide options. -% {Protocol, ProtoOpts} = case Protocol0 of -% {P, PO} -> {gun:protocol_handler(P), PO}; -% P -> {gun:protocol_handler(P), #{}} -% end, -% %% @todo What about the StateName returned? -% {_, ProtoState} = Protocol:init(ReplyTo, OriginSocket, gun_tcp_proxy, -% ProtoOpts#{stream_ref => RealStreamRef}), -% %% @todo EvHandlerState = EvHandler:protocol_changed(#{protocol => Protocol:name()}, EvHandlerState0), -% %% @todo What about keepalive? -% {store_stream(State, Stream#stream{tunnel={Protocol, ProtoState, -% TunnelInfo#{origin_host => DestHost, origin_port => DestPort}}}), -% EvHandlerState} -% -% -% todo; %% Tunneled data. handle_continue([StreamRef|Tail], Msg, State, EvHandler, EvHandlerState0) -> case get_stream_by_ref(State, StreamRef) of @@ -735,31 +691,6 @@ handle_continue([StreamRef|Tail], Msg, State, EvHandler, EvHandlerState0) -> % {error_stream_not_found(State, StreamRef, ReplyTo), EvHandlerState0} end. - - - -%data(State=#http2_state{http2_machine=HTTP2Machine}, StreamRef, ReplyTo, IsFin, Data, -% EvHandler, EvHandlerState) when is_reference(StreamRef) -> -% case get_stream_by_ref(State, StreamRef) of -% #stream{id=StreamID} -> -% case cow_http2_machine:get_stream_local_state(StreamID, HTTP2Machine) of -% {ok, fin, _} -> -% {error_stream_closed(State, StreamRef, ReplyTo), EvHandlerState}; -% {ok, _, fin} -> -% {error_stream_closed(State, StreamRef, ReplyTo), EvHandlerState}; -% {ok, _, _} -> -% maybe_send_data(State, StreamID, IsFin, Data, EvHandler, EvHandlerState) -% end; -% error -> -% {error_stream_not_found(State, StreamRef, ReplyTo), EvHandlerState} -% end; -%%% Tunneled data. -%data(State, [StreamRef|Tail], ReplyTo, IsFin, Data, EvHandler, EvHandlerState0) -> - - - - - update_flow(State, _ReplyTo, StreamRef, Inc) -> case get_stream_by_ref(State, StreamRef) of Stream=#stream{id=StreamID, flow=Flow0} -> @@ -1030,16 +961,6 @@ data(State=#http2_state{http2_machine=HTTP2Machine}, StreamRef, ReplyTo, IsFin, {ok, _, fin} -> {error_stream_closed(State, StreamRef, ReplyTo), EvHandlerState}; {ok, _, _} -> -io:format(user, "(~p) ~p:~p/~p: data ~p~n", - [self(), ?MODULE, ?FUNCTION_NAME, ?FUNCTION_ARITY, Data]), - -%% @todo The data to be sent on the tunnel neeeds to be encrypted as well! So we need -%% to have a different clause when we have a tunnel AND it has a tls_proxy_pid in TunnelInfo. -%% But we would need to differentiate between the incoming data and the encrypted data so -%% that we do not encrypt it in a loop. -%% -%% So I guess we need an handle_continue. - case Tunnel of %% We need to encrypt the data before we can send it. We send it %% directly to the gun_tls_proxy process and then diff --git a/src/gun_tls_proxy.erl b/src/gun_tls_proxy.erl index 35e83b1..ab394bd 100644 --- a/src/gun_tls_proxy.erl +++ b/src/gun_tls_proxy.erl @@ -95,7 +95,6 @@ extra :: any() }). --define(DEBUG_PROXY, 1). -ifdef(DEBUG_PROXY). -define(DEBUG_LOG(Format, Args), io:format(user, "(~p) ~p:~p/~p:" ++ Format ++ "~n", diff --git a/src/gun_tls_proxy_http2_connect.erl b/src/gun_tls_proxy_http2_connect.erl index e70454a..c423571 100644 --- a/src/gun_tls_proxy_http2_connect.erl +++ b/src/gun_tls_proxy_http2_connect.erl @@ -24,14 +24,18 @@ -export([close/1]). -type socket() :: #{ + %% The pid of the Gun connection. + gun_pid := pid(), + + %% The pid of the process that gets replies for this tunnel. reply_to := pid(), + + %% The full stream reference for this tunnel. stream_ref := reference() | [reference()] }. name() -> tls_proxy_http2_connect. -%% We need different message tags because the messages -%% must be propagated to the right stream. messages() -> {tls_proxy_http2_connect, tls_proxy_http2_connect_closed, tls_proxy_http2_connect_error}. -spec connect(_, _, _) -> no_return(). @@ -49,7 +53,7 @@ send(#{gun_pid := GunPid, reply_to := ReplyTo, stream_ref := StreamRef}, Data) - -spec setopts(_, _) -> no_return(). setopts(_, _) -> -% error(not_implemented). + %% We send messages automatically regardless of active mode. ok. -spec sockname(_) -> no_return(). diff --git a/test/raw_SUITE.erl b/test/raw_SUITE.erl index 3d17357..9875467 100644 --- a/test/raw_SUITE.erl +++ b/test/raw_SUITE.erl @@ -295,6 +295,7 @@ do_http2_connect_raw(OriginTransport, ProxyScheme, ProxyTransport) -> }} = receive_from(ProxyPid), {response, nofin, 200, _} = gun:await(ConnPid, StreamRef), handshake_completed = receive_from(OriginPid), + {up, raw} = gun:await(ConnPid, StreamRef), gun:data(ConnPid, StreamRef, nofin, <<"Hello world!">>), {data, nofin, <<"Hello world!">>} = gun:await(ConnPid, StreamRef), #{ diff --git a/test/rfc7540_SUITE.erl b/test/rfc7540_SUITE.erl index a6bb440..38cbe9f 100644 --- a/test/rfc7540_SUITE.erl +++ b/test/rfc7540_SUITE.erl @@ -443,14 +443,6 @@ connect_http_via_h2(_) -> do_connect_http(<<"http">>, tcp, http, <<"https">>, tls). connect_https_via_h2(_) -> - -%dbg:tracer(), -%dbg:tpl(gun, []), -%dbg:tpl(gun_http2, []), -%dbg:tpl(gun_tls_proxy, []), -%dbg:tpl(gun_tls_proxy_http2_connect, []), -%dbg:p(all, c), - doc("CONNECT can be used to establish a TLS connection " "to an HTTP/1.1 server via a TLS HTTP/2 proxy. (RFC7540 8.3)"), do_connect_http(<<"https">>, tls, http, <<"https">>, tls). @@ -514,12 +506,7 @@ do_connect_http(OriginScheme, OriginTransport, OriginProtocol, ProxyScheme, Prox }} = receive_from(ProxyPid), {response, nofin, 200, _} = gun:await(ConnPid, StreamRef), handshake_completed = receive_from(OriginPid), - %% @todo The 200 response must not be sent before the TLS handshake completed successfully? - %% Or the coming request must be kept around until the tunnel is up? We probably need - %% to gun_tunnel_up or something to inform the user the tunnel is up. - %% - %% @todo QUEUE data until the tunnel is up? Send a gun_up of some kind? - timer:sleep(1000), + {up, OriginProtocol} = gun:await(ConnPid, StreamRef), ProxiedStreamRef = gun:get(ConnPid, "/proxied", #{}, #{tunnel => StreamRef}), #{<<":authority">> := Authority} = receive_from(OriginPid), #{ diff --git a/test/socks_SUITE.erl b/test/socks_SUITE.erl index b577cb9..5e15eac 100644 --- a/test/socks_SUITE.erl +++ b/test/socks_SUITE.erl @@ -449,7 +449,8 @@ do_socks5_through_h2_connect_proxy(OriginScheme, OriginTransport, ProxyScheme, P <<":authority">> := Authority1 }} = receive_from(Proxy1Pid), {response, nofin, 200, _} = gun:await(ConnPid, StreamRef), - %% We receive a stream-specific gun_socks_up afterwards. This is the origin HTTP server. + %% First the HTTP/2 tunnel is up, then the SOCKS tunnel to the origin HTTP server. + {up, socks} = gun:await(ConnPid, StreamRef), {up, http} = gun:await(ConnPid, StreamRef), %% The second proxy receives a Socks5 auth/connect request. {auth_methods, 1, [none]} = receive_from(Proxy2Pid), |