aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLoïc Hoguin <[email protected]>2018-07-04 12:33:30 +0200
committerLoïc Hoguin <[email protected]>2018-07-04 12:33:30 +0200
commita767abb47e6d9a0817d37413e1b5c1d338b362d8 (patch)
tree8157fde9eb94bdf8cf0b4b94f252d7104889d917
parent794a816814cbc9a126531829fae5c7e3359179c3 (diff)
downloadranch-a767abb47e6d9a0817d37413e1b5c1d338b362d8.tar.gz
ranch-a767abb47e6d9a0817d37413e1b5c1d338b362d8.tar.bz2
ranch-a767abb47e6d9a0817d37413e1b5c1d338b362d8.zip
Enable TLS upgrades via ranch_ssl:handshake/3
Based on the work done by @juhlig.
-rw-r--r--doc/src/guide/transports.asciidoc11
-rw-r--r--doc/src/manual/ranch_transport.asciidoc7
-rw-r--r--src/ranch_ssl.erl5
-rw-r--r--test/acceptor_SUITE.erl23
-rw-r--r--test/ssl_upgrade_protocol.erl27
5 files changed, 70 insertions, 3 deletions
diff --git a/doc/src/guide/transports.asciidoc b/doc/src/guide/transports.asciidoc
index f5bb17e..70efa1b 100644
--- a/doc/src/guide/transports.asciidoc
+++ b/doc/src/guide/transports.asciidoc
@@ -144,6 +144,17 @@ possible to use a file descriptor opened in raw mode:
{ok, RawFile} = file:open(Filename, [raw, read, binary]),
{ok, SentBytes} = Transport:sendfile(Socket, RawFile, Offset, Bytes, Opts).
+=== Upgrading a TCP socket to SSL
+
+A connected TCP socket can be upgraded to a SSL socket via the function
+`ranch_ssl:handshake/3`. The socket *must* be in `{active, false}` mode
+before telling the client that the server is ready to upgrade in order
+to avoid race conditions.
+
+.Performing a TLS handshake on a TCP socket
+[source,erlang]
+{ok, NewSocket} = ranch_ssl:handshake(Socket, SslOpts, 5000).
+
=== Writing a transport handler
A transport handler is a module implementing the `ranch_transport` behavior.
diff --git a/doc/src/manual/ranch_transport.asciidoc b/doc/src/manual/ranch_transport.asciidoc
index 6545720..ec35589 100644
--- a/doc/src/manual/ranch_transport.asciidoc
+++ b/doc/src/manual/ranch_transport.asciidoc
@@ -55,13 +55,18 @@ Options = any():: Options for initialization.
Timeout = timeout():: Handshake timeout.
CSocket1 = any():: Initialized socket for this connection.
-Perform post-accept initialization of the connection.
+Perform any necessary handshake for this transport.
This function will be called by connection processes
before performing any socket operation. It allows
transports that require extra initialization to perform
their task and return a socket that is ready to use.
+This function may also be used to upgrade a connection
+from a transport to another depending on the capabilities
+of the transports. For example a `ranch_tcp` socket may
+be upgraded to a `ranch_ssl` one using this function.
+
=== listen(TransOpts) -> {ok, LSocket} | {error, atom()}
TransOpts = any():: Transport options.
diff --git a/src/ranch_ssl.erl b/src/ranch_ssl.erl
index e1dd798..f9203e6 100644
--- a/src/ranch_ssl.erl
+++ b/src/ranch_ssl.erl
@@ -135,11 +135,14 @@ accept_ack(CSocket, Timeout) ->
{ok, _} = handshake(CSocket, [], Timeout),
ok.
--spec handshake(ssl:sslsocket(), opts(), timeout()) -> {ok, ssl:sslsocket()}.
+-spec handshake(inet:socket() | ssl:sslsocket(), opts(), timeout())
+ -> {ok, ssl:sslsocket()}.
handshake(CSocket, Opts, Timeout) ->
case ssl:ssl_accept(CSocket, Opts, Timeout) of
ok ->
{ok, CSocket};
+ {ok, NewSocket} ->
+ {ok, NewSocket};
%% Garbage was most likely sent to the socket, don't error out.
{error, {tls_alert, _}} ->
ok = close(CSocket),
diff --git a/test/acceptor_SUITE.erl b/test/acceptor_SUITE.erl
index 50f0ce2..c841a0a 100644
--- a/test/acceptor_SUITE.erl
+++ b/test/acceptor_SUITE.erl
@@ -54,6 +54,7 @@ groups() ->
ssl_accept_ack,
ssl_sni_echo,
ssl_sni_fail,
+ ssl_upgrade_from_tcp,
ssl_getopts_capability,
ssl_getstat_capability,
ssl_error_eaddrinuse,
@@ -466,6 +467,26 @@ do_ssl_sni_fail() ->
{'EXIT', _} = begin catch ranch:get_port(Name) end,
ok.
+ssl_upgrade_from_tcp(_) ->
+ doc("Ensure a TCP socket can be upgraded to SSL"),
+ Name = name(),
+ {ok, _} = ranch:start_listener(Name,
+ ranch_tcp, #{},
+ ssl_upgrade_protocol, []),
+ Port = ranch:get_port(Name),
+ {ok, Socket} = gen_tcp:connect("localhost", Port, [binary, {active, false}, {packet, raw}]),
+ ok = gen_tcp:send(Socket, <<"ECHO Before upgrading to SSL">>),
+ {ok, <<"Before upgrading to SSL">>} = gen_tcp:recv(Socket, 23, 1000),
+ ok = gen_tcp:send(Socket, <<"UPGRADE">>),
+ {ok, <<"READY">>} = gen_tcp:recv(Socket, 5, 1000),
+ {ok, SslSocket} = ssl:connect(Socket, [{verify, verify_none}], 5000),
+ ok = ssl:send(SslSocket, <<"ECHO After upgrading to SSL">>),
+ {ok, <<"After upgrading to SSL">>} = ssl:recv(SslSocket, 22, 1000),
+ ok = ranch:stop_listener(Name),
+ {error, closed} = ssl:recv(SslSocket, 0, 1000),
+ {'EXIT', _} = begin catch ranch:get_port(Name) end,
+ ok.
+
ssl_graceful(_) ->
doc("Ensure suspending and resuming of listeners does not kill active connections."),
Name = name(),
@@ -1041,7 +1062,7 @@ supervisor_clean_conns_sup_restart(_) ->
Server = erlang:whereis(ranch_server),
ServerMonRef = erlang:monitor(process, Server),
%% Exit because Name already registered and is alive.
- {'EXIT', _} = (catch ranch_server:set_connections_sup(Name, self())),
+ {'EXIT', _} = (catch ranch_server:set_connections_sup(Name, self())),
receive
{'DOWN', ServerMonRef, process, Server, _} ->
error(ranch_server_down)
diff --git a/test/ssl_upgrade_protocol.erl b/test/ssl_upgrade_protocol.erl
new file mode 100644
index 0000000..cafbe13
--- /dev/null
+++ b/test/ssl_upgrade_protocol.erl
@@ -0,0 +1,27 @@
+-module(ssl_upgrade_protocol).
+-behaviour(ranch_protocol).
+
+-export([start_link/4]).
+-export([init/3]).
+
+start_link(Ref, _Socket, Transport, Opts) ->
+ Pid = spawn_link(?MODULE, init, [Ref, Transport, Opts]),
+ {ok, Pid}.
+
+init(Ref, Transport, _Opts = []) ->
+ {ok, Socket} = ranch:handshake(Ref),
+ loop(Socket, Transport).
+
+loop(Socket, Transport) ->
+ case Transport:recv(Socket, 0, 5000) of
+ {ok, <<"UPGRADE">>} when Transport =:= ranch_tcp ->
+ ok = Transport:send(Socket, <<"READY">>),
+ Opts = ct_helper:get_certs_from_ets(),
+ {ok, NewSocket} = ranch_ssl:handshake(Socket, [{verify, verify_none}|Opts], 1000),
+ loop(NewSocket, ranch_ssl);
+ {ok, <<"ECHO ", More/binary>>} ->
+ ok = Transport:send(Socket, More),
+ loop(Socket, Transport);
+ _ ->
+ ok = Transport:close(Socket)
+ end.