From f328916937c67b3c9679e4f11d4594c39d36f85d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Wed, 26 Sep 2018 12:08:11 +0200 Subject: Keep track of the intermediaries the connection go through Also augment the CONNECT tests to confirm that the intermediaries are accounted for. --- src/gun.erl | 48 +++++++++++++++++++++++++++++++++++---- src/gun_http.erl | 16 ++++++------- src/gun_tcp.erl | 3 +++ src/gun_tls.erl | 3 +++ test/rfc7231_SUITE.erl | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 119 insertions(+), 12 deletions(-) diff --git a/src/gun.erl b/src/gun.erl index 9c92281..daa5c62 100644 --- a/src/gun.erl +++ b/src/gun.erl @@ -128,6 +128,14 @@ }. -export_type([connect_destination/0]). +-type intermediary() :: #{ + type := connect, + host := inet:hostname() | inet:ip_address(), + port := inet:port_number(), + transport := tcp | tls, + protocol := http | http2 +}. + %% @todo When/if HTTP/2 CONNECT gets implemented, we will want an option here %% to indicate that the request must be sent on an existing CONNECT stream. %% This is of course not required for HTTP/1.1 since the CONNECT takes over @@ -163,6 +171,7 @@ port :: inet:port_number(), origin_host :: inet:hostname() | inet:ip_address(), origin_port :: inet:port_number(), + intermediaries = [] :: [intermediary()], opts :: opts(), keepalive_ref :: undefined | reference(), socket :: undefined | inet:socket() | ssl:sslsocket(), @@ -275,9 +284,25 @@ consider_tracing(_, _) -> -spec info(pid()) -> map(). info(ServerPid) -> - {_, #state{socket=Socket, transport=Transport}} = sys:get_state(ServerPid), + {_, #state{ + socket=Socket, + transport=Transport, + protocol=Protocol, + origin_host=OriginHost, + origin_port=OriginPort, + intermediaries=Intermediaries + }} = sys:get_state(ServerPid), {ok, {SockIP, SockPort}} = Transport:sockname(Socket), - #{sock_ip => SockIP, sock_port => SockPort}. + #{ + transport => Transport:name(), + protocol => Protocol:name(), + sock_ip => SockIP, + sock_port => SockPort, + origin_host => OriginHost, + origin_port => OriginPort, + %% Intermediaries are listed in the order data goes through them. + intermediaries => lists:reverse(Intermediaries) + }. -spec close(pid()) -> ok. close(ServerPid) -> @@ -866,8 +891,23 @@ commands([Error={error, _}|_], State=#state{socket=Socket, transport=Transport}) commands([{state, ProtoState}|Tail], State) -> commands(Tail, State#state{protocol_state=ProtoState}); %% @todo The scheme should probably not be ignored. -commands([{origin, _Scheme, Host, Port}|Tail], State) -> - commands(Tail, State#state{origin_host=Host, origin_port=Port}); +%% +%% Order is important: the origin must be changed before +%% 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, + origin_host=IntermediateHost, origin_port=IntermediatePort, + intermediaries=Intermediaries}) -> + Info = #{ + type => Type, + host => IntermediateHost, + port => IntermediatePort, + transport => Transport:name(), + protocol => Protocol:name() + }, + commands(Tail, State#state{origin_host=Host, origin_port=Port, + intermediaries=[Info|Intermediaries]}); commands([{switch_transport, Transport, Socket}|Tail], State) -> commands(Tail, State#state{socket=Socket, transport=Transport}); %% @todo The two loops should be reunified and this clause generalized. diff --git a/src/gun_http.erl b/src/gun_http.erl index c2b0ed6..c4291bc 100644 --- a/src/gun_http.erl +++ b/src/gun_http.erl @@ -229,13 +229,13 @@ handle_head(Data, State=#http_state{socket=Socket, version=ClientVersion, {ok, TLSSocket} -> case ssl:negotiated_protocol(TLSSocket) of {ok, <<"h2">>} -> - [{switch_transport, gun_tls, TLSSocket}, - {switch_protocol, gun_http2, State2}, - {origin, <<"https">>, NewHost, NewPort}]; + [{origin, <<"https">>, NewHost, NewPort, connect}, + {switch_transport, gun_tls, TLSSocket}, + {switch_protocol, gun_http2, State2}]; _ -> [{state, State2#http_state{socket=TLSSocket, transport=gun_tls}}, - {switch_transport, gun_tls, TLSSocket}, - {origin, <<"https">>, NewHost, NewPort}] + {origin, <<"https">>, NewHost, NewPort, connect}, + {switch_transport, gun_tls, TLSSocket}] end; Error -> Error @@ -244,10 +244,10 @@ handle_head(Data, State=#http_state{socket=Socket, version=ClientVersion, case maps:get(protocols, Destination, [http]) of [http] -> [{state, State2}, - {origin, <<"http">>, NewHost, NewPort}]; + {origin, <<"http">>, NewHost, NewPort, connect}]; [http2] -> - [{switch_protocol, gun_http2, State2}, - {origin, <<"http">>, NewHost, NewPort}] + [{origin, <<"http">>, NewHost, NewPort, connect}, + {switch_protocol, gun_http2, State2}] end end; {_, _} when Status >= 100, Status =< 199 -> diff --git a/src/gun_tcp.erl b/src/gun_tcp.erl index 80bc45b..0f5addc 100644 --- a/src/gun_tcp.erl +++ b/src/gun_tcp.erl @@ -14,6 +14,7 @@ -module(gun_tcp). +-export([name/0]). -export([messages/0]). -export([connect/4]). -export([send/2]). @@ -21,6 +22,8 @@ -export([sockname/1]). -export([close/1]). +name() -> tcp. + messages() -> {tcp, tcp_closed, tcp_error}. -spec connect(inet:ip_address() | inet:hostname(), diff --git a/src/gun_tls.erl b/src/gun_tls.erl index f58620f..d377927 100644 --- a/src/gun_tls.erl +++ b/src/gun_tls.erl @@ -14,6 +14,7 @@ -module(gun_tls). +-export([name/0]). -export([messages/0]). -export([connect/3]). -export([connect/4]). @@ -22,6 +23,8 @@ -export([sockname/1]). -export([close/1]). +name() -> tls. + messages() -> {ssl, ssl_closed, ssl_error}. -spec connect(inet:socket(), any(), timeout()) diff --git a/test/rfc7231_SUITE.erl b/test/rfc7231_SUITE.erl index 88bc114..a42e06c 100644 --- a/test/rfc7231_SUITE.erl +++ b/test/rfc7231_SUITE.erl @@ -178,6 +178,18 @@ do_connect_http(Transport) -> Len = byte_size(Authority), <<"GET /proxied HTTP/1.1\r\nhost: ", Authority:Len/binary, "\r\n", _/bits>> = do_receive(OriginPid), + #{ + transport := Transport, + protocol := http, + origin_host := "localhost", + origin_port := OriginPort, + intermediaries := [#{ + type := connect, + host := "localhost", + port := ProxyPort, + transport := tcp, + protocol := http + }]} = gun:info(ConnPid), gun:close(ConnPid). connect_h2c(_) -> @@ -214,6 +226,18 @@ do_connect_h2(Transport) -> <<_:Len/binary, _:24, 1:8, _/bits>> -> ok end, + #{ + transport := Transport, + protocol := http2, + origin_host := "localhost", + origin_port := OriginPort, + intermediaries := [#{ + type := connect, + host := "localhost", + port := ProxyPort, + transport := tcp, + protocol := http + }]} = gun:info(ConnPid), gun:close(ConnPid). connect_through_multiple_proxies(_) -> @@ -243,6 +267,24 @@ connect_through_multiple_proxies(_) -> Len = byte_size(Authority2), <<"GET /proxied HTTP/1.1\r\nhost: ", Authority2:Len/binary, "\r\n", _/bits>> = do_receive(OriginPid), + #{ + transport := tcp, + protocol := http, + origin_host := "localhost", + origin_port := OriginPort, + intermediaries := [#{ + type := connect, + host := "localhost", + port := Proxy1Port, + transport := tcp, + protocol := http + }, #{ + type := connect, + host := "localhost", + port := Proxy2Port, + transport := tcp, + protocol := http + }]} = gun:info(ConnPid), gun:close(ConnPid). connect_response_201(_) -> @@ -263,6 +305,18 @@ connect_response_201(_) -> Len = byte_size(Authority), <<"GET /proxied HTTP/1.1\r\nhost: ", Authority:Len/binary, "\r\n", _/bits>> = do_receive(OriginPid), + #{ + transport := tcp, + protocol := http, + origin_host := "localhost", + origin_port := OriginPort, + intermediaries := [#{ + type := connect, + host := "localhost", + port := ProxyPort, + transport := tcp, + protocol := http + }]} = gun:info(ConnPid), gun:close(ConnPid). connect_response_302(_) -> @@ -295,6 +349,13 @@ do_connect_failure(Status) -> {response, fin, Status, Headers} = gun:await(ConnPid, StreamRef), FailedStreamRef = gun:get(ConnPid, "/proxied"), {response, fin, 501, _} = gun:await(ConnPid, FailedStreamRef), + #{ + transport := tcp, + protocol := http, + origin_host := "localhost", + origin_port := ProxyPort, + intermediaries := [] + } = gun:info(ConnPid), gun:close(ConnPid). connect_authority_form(_) -> -- cgit v1.2.3