From 28b24a32b82c6429462569be14e00bec65a850ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Wed, 18 Sep 2019 13:55:37 +0200 Subject: Support connecting to HTTPS server via TCP Socks5 --- src/gun.erl | 9 +++++++-- src/gun_socks.erl | 19 +++++++++++-------- test/socks_SUITE.erl | 11 ++++++++++- 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/src/gun.erl b/src/gun.erl index 85e9405..a6c1edc 100644 --- a/src/gun.erl +++ b/src/gun.erl @@ -1306,10 +1306,15 @@ commands([{switch_protocol, Protocol=gun_ws, ProtoState}], State=#state{ {keep_state, keepalive_cancel(State#state{protocol=Protocol, protocol_state=ProtoState, event_handler_state=EvHandlerState})}; %% @todo And this state should probably not be ignored. -%% @todo Socks is switching to *http* and we don't seem to support it properly yet. +%% @todo Socks can be switching to *http* and we don't seem to support it properly yet. commands([{switch_protocol, Protocol, _ProtoState0}|Tail], State=#state{ - owner=Owner, opts=Opts, socket=Socket, transport=Transport, + owner=Owner, opts=Opts, socket=Socket, transport=Transport, protocol=CurrentProtocol, event_handler=EvHandler, event_handler_state=EvHandlerState0}) -> + %% When we switch_protocol from socks we must send a gun_socks_connected message. + _ = case CurrentProtocol of + gun_socks -> Owner ! {gun_socks_connected, self(), Protocol:name()}; + _ -> ok + end, ProtoOpts = maps:get(http2_opts, Opts, #{}), ProtoState = Protocol:init(Owner, Socket, Transport, ProtoOpts), EvHandlerState = EvHandler:protocol_changed(#{protocol => Protocol:name()}, EvHandlerState0), diff --git a/src/gun_socks.erl b/src/gun_socks.erl index 74f1767..16684f8 100644 --- a/src/gun_socks.erl +++ b/src/gun_socks.erl @@ -17,6 +17,7 @@ -export([check_options/1]). -export([name/0]). -export([init/4]). +-export([switch_transport/3]). -export([handle/4]). -export([closing/4]). -export([close/4]). @@ -89,6 +90,9 @@ init(Owner, Socket, Transport, Opts) -> #socks_state{owner=Owner, socket=Socket, transport=Transport, opts=Opts, version=Version, status=auth_method_select}. +switch_transport(Transport, Socket, State) -> + State#socks_state{socket=Socket, transport=Transport}. + handle(Data, State, _, EvHandlerState) -> {handle(Data, State), EvHandlerState}. @@ -128,22 +132,21 @@ handle(<<5, 0, 0, Rest0/bits>>, State=#socks_state{owner=Owner, socket=Socket, t %% @todo Maybe an event indicating success. #{host := NewHost, port := NewPort} = Opts, case Opts of - %% @todo TLS over TLS here as well. -% #{protocols := Protocols, transport := tls} -> -% TLSOpts = maps:get(tls_opts, Destination, []), -% TLSTimeout = maps:get(tls_handshake_timeout, Destination, infinity), - %% + #{transport := tls} -> + HandshakeEvent = #{ + tls_opts => maps:get(tls_opts, Opts, []), + timeout => maps:get(tls_handshake_timeout, Opts, infinity) + }, + [{origin, <<"https">>, NewHost, NewPort, socks5}, + {tls_handshake, HandshakeEvent, maps:get(protocols, Opts, [http])}]; #{protocols := [{socks, SockOpts}]} -> - Owner ! {gun_socks_connected, self(), name()}, [{origin, <<"http">>, NewHost, NewPort, socks5}, {switch_protocol, ?MODULE, init(Owner, Socket, Transport, SockOpts)}]; #{protocols := [http2]} -> - Owner ! {gun_socks_connected, self(), gun_http2:name()}, [{origin, <<"http">>, NewHost, NewPort, socks5}, {switch_protocol, gun_http2, State}, {mode, http}]; _ -> - Owner ! {gun_socks_connected, self(), gun_http:name()}, [{origin, <<"http">>, NewHost, NewPort, socks5}, {switch_protocol, gun_http, State}, {mode, http}] diff --git a/test/socks_SUITE.erl b/test/socks_SUITE.erl index b0c84b0..07b3497 100644 --- a/test/socks_SUITE.erl +++ b/test/socks_SUITE.erl @@ -152,6 +152,14 @@ socks5_tcp_http_username_password(_) -> doc("Use Socks5 over TCP and without authentication to connect to an HTTP server."), do_socks5_tcp_http(<<"http">>, tcp, tcp, {username_password, <<"user">>, <<"password">>}). +socks5_tcp_https_none(_) -> + doc("Use Socks5 over TCP and without authentication to connect to an HTTPS server."), + do_socks5_tcp_http(<<"https">>, tls, tcp, none). + +socks5_tcp_https_username_password(_) -> + doc("Use Socks5 over TCP and without authentication to connect to an HTTPS server."), + do_socks5_tcp_http(<<"https">>, tls, tcp, {username_password, <<"user">>, <<"password">>}). + do_socks5_tcp_http(OriginScheme, OriginTransport, ProxyTransport, SocksAuth) -> {ok, OriginPid, OriginPort} = init_origin(OriginTransport, http), {ok, ProxyPid, ProxyPort} = do_proxy_start(ProxyTransport, SocksAuth), @@ -161,7 +169,8 @@ do_socks5_tcp_http(OriginScheme, OriginTransport, ProxyTransport, SocksAuth) -> protocols => [{socks, #{ auth => [SocksAuth], host => "localhost", - port => OriginPort + port => OriginPort, + transport => OriginTransport }}] }), %% We receive a gun_up and a gun_socks_connected. -- cgit v1.2.3