aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorjuhlig <[email protected]>2019-09-17 17:55:25 +0200
committerLoïc Hoguin <[email protected]>2019-10-14 12:38:29 +0200
commitd44e7a16f7d2823cc658e39b5d953ba0850e47ba (patch)
tree72ddf6100e12fa7424067262ee31b1f99ce05fff
parent1db8290685e9cff3dfafde62de6148246075990a (diff)
downloadranch-d44e7a16f7d2823cc658e39b5d953ba0850e47ba.tar.gz
ranch-d44e7a16f7d2823cc658e39b5d953ba0850e47ba.tar.bz2
ranch-d44e7a16f7d2823cc658e39b5d953ba0850e47ba.zip
Enable multiple steps handshake
Also fix some Protocol:start_link/4 into start_link/3 left over in the documentation.
-rw-r--r--doc/src/guide/listeners.asciidoc4
-rw-r--r--doc/src/guide/protocols.asciidoc2
-rw-r--r--doc/src/manual/ranch.asciidoc2
-rw-r--r--doc/src/manual/ranch.handshake.asciidoc22
-rw-r--r--doc/src/manual/ranch.handshake_cancel.asciidoc55
-rw-r--r--doc/src/manual/ranch.handshake_continue.asciidoc67
-rw-r--r--doc/src/manual/ranch.recv_proxy_header.asciidoc2
-rw-r--r--doc/src/manual/ranch_ssl.asciidoc10
-rw-r--r--doc/src/manual/ranch_transport.asciidoc52
-rw-r--r--src/ranch.erl74
-rw-r--r--src/ranch_ssl.erl37
-rw-r--r--src/ranch_tcp.erl20
-rw-r--r--src/ranch_transport.erl6
-rw-r--r--test/acceptor_SUITE.erl30
-rw-r--r--test/handshake_protocol.erl32
15 files changed, 382 insertions, 33 deletions
diff --git a/doc/src/guide/listeners.asciidoc b/doc/src/guide/listeners.asciidoc
index fd988f1..6e2dd19 100644
--- a/doc/src/guide/listeners.asciidoc
+++ b/doc/src/guide/listeners.asciidoc
@@ -239,8 +239,8 @@ with the name of the listener as the only argument.
[source,erlang]
ranch:remove_connection(Ref).
-As seen in the chapter covering protocols, this pid is received as the
-first argument of the protocol's `start_link/4` callback.
+As seen in the chapter covering protocols, this reference is received
+as the first argument of the protocol's `start_link/3` callback.
You can modify the `max_connections` value on a running listener by
using the `ranch:set_max_connections/2` function, with the name of the
diff --git a/doc/src/guide/protocols.asciidoc b/doc/src/guide/protocols.asciidoc
index 89360ef..b455143 100644
--- a/doc/src/guide/protocols.asciidoc
+++ b/doc/src/guide/protocols.asciidoc
@@ -6,7 +6,7 @@ protocol logic executed in this process.
=== Writing a protocol handler
All protocol handlers must implement the `ranch_protocol` behavior
-which defines a single callback, `start_link/4`. This callback is
+which defines a single callback, `start_link/3`. This callback is
responsible for spawning a new process for handling the connection.
It receives four arguments: the name of the listener, the socket, the
transport handler being used and the protocol options defined in
diff --git a/doc/src/manual/ranch.asciidoc b/doc/src/manual/ranch.asciidoc
index 065ded3..ed57236 100644
--- a/doc/src/manual/ranch.asciidoc
+++ b/doc/src/manual/ranch.asciidoc
@@ -26,6 +26,8 @@ Suspend/resume:
Connections:
* link:man:ranch:handshake(3)[ranch:handshake(3)] - Perform the transport handshake
+* link:man:ranch:handshake_continue(3)[ranch:handshake_continue(3)] - Resume the paused transport handshake
+* link:man:ranch:handshake_cancel(3)[ranch:handshake_cancel(3)] - Cancel the paused transport handshake
* link:man:ranch:recv_proxy_header(3)[ranch:recv_proxy_header(3)] - Receive the PROXY protocol header
* link:man:ranch:remove_connection(3)[ranch:remove_connection(3)] - Remove connection from the count
diff --git a/doc/src/manual/ranch.handshake.asciidoc b/doc/src/manual/ranch.handshake.asciidoc
index 5d2694c..508abe5 100644
--- a/doc/src/manual/ranch.handshake.asciidoc
+++ b/doc/src/manual/ranch.handshake.asciidoc
@@ -8,12 +8,13 @@ ranch:handshake - Perform the transport handshake
[source,erlang]
----
-handshake(Ref) -> handshake(Ref, [])
-handshake(Ref, Opts) -> {ok, Socket}
+handshake(Ref) -> {ok, Socket} | {continue, Info}
+handshake(Ref, Opts) -> {ok, Socket} | {continue, Info}
Ref :: ranch:ref()
Opts :: any()
Socket :: any()
+Info :: any()
----
Perform the transport handshake.
@@ -38,12 +39,23 @@ Allowed options depend on the transport module.
== Return value
-An `ok` tuple is returned containing the socket for the connection.
+An `ok` tuple is returned containing the socket for the connection
+by default.
+
+Depending on configuration, a `continue` tuple can otherwise
+be returned when the handshake operation is paused. It contains
+data provided by the transport that can be used to inform further
+decisions before resuming the handshake, for example to provide
+new transport options. The handshake can be resumed using
+link:man:ranch:handshake_continue(3)[ranch:handshake_continue(3)]
+or canceled using
+link:man:ranch:handshake_cancel(3)[ranch:handshake_cancel(3)].
This function will trigger an exception when an error occurs.
== Changelog
+* *2.0*: The `continue` tuple can now be returned.
* *1.6*: Function introduced. Replaces `ranch:accept_ack/1`.
== Examples
@@ -51,7 +63,7 @@ This function will trigger an exception when an error occurs.
.Initialize the connection process
[source,erlang]
----
-start_link(Ref, _, Transport, Opts) ->
+start_link(Ref, Transport, Opts) ->
Pid = proc_lib:spawn_link(?MODULE, init,
[Ref, Transport, Opts]),
{ok, Pid}.
@@ -65,6 +77,8 @@ init(Ref, Transport, Opts) ->
== See also
link:man:ranch:start_listener(3)[ranch:start_listener(3)],
+link:man:ranch:handshake_continue(3)[ranch:handshake_continue(3)],
+link:man:ranch:handshake_cancel(3)[ranch:handshake_cancel(3)],
link:man:ranch:recv_proxy_header(3)[ranch:recv_proxy_header(3)],
link:man:ranch:remove_connection(3)[ranch:remove_connection(3)],
link:man:ranch(3)[ranch(3)]
diff --git a/doc/src/manual/ranch.handshake_cancel.asciidoc b/doc/src/manual/ranch.handshake_cancel.asciidoc
new file mode 100644
index 0000000..6f742f5
--- /dev/null
+++ b/doc/src/manual/ranch.handshake_cancel.asciidoc
@@ -0,0 +1,55 @@
+= ranch:handshake_cancel(3)
+
+== Name
+
+ranch:handshake_cancel - Cancel the paused transport handshake
+
+== Description
+
+[source,erlang]
+----
+handshake_cancel(Ref :: ranch:ref()) -> ok
+----
+
+Cancel the paused transport handshake.
+
+This function may be called by the protocol process
+to cancel a paused handshake.
+
+== Arguments
+
+Ref::
+
+The listener name.
++
+Allowed options depend on the transport module.
+
+== Return value
+
+The return value depends on the transport module.
+
+== Changelog
+
+* *2.0*: Function introduced.
+
+== Examples
+
+.Cancel a paused transport handshake
+[source,erlang]
+----
+start_link(Ref, Transport, Opts) ->
+ Pid = proc_lib:spawn_link(?MODULE, init,
+ [Ref, Transport, Opts]),
+ {ok, Pid}.
+
+init(Ref, Transport, Opts) ->
+ {continue, _Info} = ranch:handshake(Ref),
+ ranch:handshake_cancel(Ref),
+ exit(handshake_cancelled).
+----
+
+== See also
+
+link:man:ranch:handshake(3)[ranch:handshake(3)],
+link:man:ranch:handshake_continue(3)[ranch:handshake_continue(3)],
+link:man:ranch(3)[ranch(3)]
diff --git a/doc/src/manual/ranch.handshake_continue.asciidoc b/doc/src/manual/ranch.handshake_continue.asciidoc
new file mode 100644
index 0000000..4fb08bf
--- /dev/null
+++ b/doc/src/manual/ranch.handshake_continue.asciidoc
@@ -0,0 +1,67 @@
+= ranch:handshake_continue(3)
+
+== Name
+
+ranch:handshake_continue - Resume the paused transport handshake
+
+== Description
+
+[source,erlang]
+----
+handshake_continue(Ref) -> {ok, Socket}
+handshake_continue(Ref, Opts) -> {ok, Socket}
+
+Ref :: ranch:ref()
+Opts :: any()
+Socket :: any()
+----
+
+Resume the paused transport handshake.
+
+This function must be called by the protocol process in order
+to resume a paused handshake.
+
+== Arguments
+
+Ref::
+
+The listener name.
+
+Opts::
+
+Transport handshake options.
++
+Allowed options depend on the transport module.
+
+== Return value
+
+An `ok` tuple is returned containing the socket for the connection.
+
+This function will trigger an exception when an error occurs.
+
+== Changelog
+
+* *2.0*: Function introduced.
+
+== Examples
+
+.Continue a paused transport handshake
+[source,erlang]
+----
+start_link(Ref, Transport, Opts) ->
+ Pid = proc_lib:spawn_link(?MODULE, init,
+ [Ref, Transport, Opts]),
+ {ok, Pid}.
+
+init(Ref, Transport, Opts) ->
+ {continue, _Info} = ranch:handshake(Ref),
+ {ok, Socket} = ranch:handshake_continue(Ref),
+ loop(#state{ref=Ref, socket=Socket,
+ transport=Transport, opts=Opts}).
+----
+
+== See also
+
+link:man:ranch:handshake(3)[ranch:handshake(3)],
+link:man:ranch:handshake_cancel(3)[ranch:handshake_cancel(3)],
+link:man:ranch(3)[ranch(3)]
diff --git a/doc/src/manual/ranch.recv_proxy_header.asciidoc b/doc/src/manual/ranch.recv_proxy_header.asciidoc
index 9f23bde..0536e3d 100644
--- a/doc/src/manual/ranch.recv_proxy_header.asciidoc
+++ b/doc/src/manual/ranch.recv_proxy_header.asciidoc
@@ -51,7 +51,7 @@ the error.
.Receive the PROXY protocol header
[source,erlang]
----
-start_link(Ref, _, Transport, Opts) ->
+start_link(Ref, Transport, Opts) ->
Pid = proc_lib:spawn_link(?MODULE, init,
[Ref, Transport, Opts]),
{ok, Pid}.
diff --git a/doc/src/manual/ranch_ssl.asciidoc b/doc/src/manual/ranch_ssl.asciidoc
index 00f6fad..66f91b1 100644
--- a/doc/src/manual/ranch_ssl.asciidoc
+++ b/doc/src/manual/ranch_ssl.asciidoc
@@ -54,6 +54,7 @@ ssl_opt() = {alpn_preferred_protocols, [binary()]}
| {dhfile, file:filename()}
| {eccs, [atom()]}
| {fail_if_no_peer_cert, boolean()}
+ | {handshake, hello | full}
| {hibernate_after, timeout()}
| {honor_cipher_order, boolean()}
| {honor_ecc_order, boolean()}
@@ -156,6 +157,15 @@ fail_if_no_peer_cert (false)::
Whether to refuse the connection if the client sends an
empty certificate.
+handshake (full)::
+
+If `hello` is specified for this option, the handshake is
+paused after receiving the client hello message. The handshake
+can then be resumed via `handshake_continue/3`, or cancelled
+via `handshake_cancel/1`.
++
+This option cannot be given to `ranch:handshake/1,2`.
+
hibernate_after (undefined)::
Time in ms after which SSL socket processes go into
diff --git a/doc/src/manual/ranch_transport.asciidoc b/doc/src/manual/ranch_transport.asciidoc
index 78468b3..c26d91c 100644
--- a/doc/src/manual/ranch_transport.asciidoc
+++ b/doc/src/manual/ranch_transport.asciidoc
@@ -80,9 +80,17 @@ Get one or more statistic options for the socket.
[source,erlang]
----
handshake(Socket0 :: socket(),
- SockOpts :: any(),
Timeout :: timeout())
- -> {ok, Socket}
+ -> {ok, Socket :: socket()}
+ | {ok, Socket :: socket(), Info :: any()}
+ | {error, any()}
+
+handshake(Socket0 :: socket(),
+ SockOpts :: opts(),
+ Timeout :: timeout())
+ -> {ok, Socket :: socket()}
+ | {ok, Socket :: socket(), Info :: any()}
+ | {error, any()}
----
Perform the transport-level handshake.
@@ -92,11 +100,51 @@ 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.
+If the handshake is completed by this call, the function will
+return `{ok, Socket}`. However, some transports (notably,
+`ranch_ssl` if `{handshake, hello}` is specified in the socket
+options) may pause the handshake at a certain point and return
+`{ok, Socket, Info}` instead, in order to allow for
+additional decisions to be made before resuming the handshake
+with `handshake_continue/3` or cancelling it with
+`handshake_cancel/1`.
+
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.
+=== handshake_continue
+
+[source,erlang]
+----
+handshake_continue(Socket0 :: socket(),
+ Timeout :: timeout())
+ -> {ok, Socket :: socket()}
+ | {error, any()}
+
+handshake_continue(Socket0 :: socket(),
+ SockOpts :: opts(),
+ Timeout :: timeout())
+ -> {ok, Socket :: socket()}
+ | {error, any()}
+----
+
+Resume the paused transport-level handshake and return a socket
+that is ready to use.
+
+This function will be called by connection processes
+to resume a paused handshake.
+
+=== handshake_cancel
+
+[source,erlang]
+----
+handshake_cancel(Socket :: socket()) -> ok
+----
+
+Cancel the paused transport-level handshake.
+
=== listen
[source,erlang]
diff --git a/src/ranch.erl b/src/ranch.erl
index 08eebfc..89ca21e 100644
--- a/src/ranch.erl
+++ b/src/ranch.erl
@@ -22,6 +22,9 @@
-export([child_spec/5]).
-export([handshake/1]).
-export([handshake/2]).
+-export([handshake_continue/1]).
+-export([handshake_continue/2]).
+-export([handshake_cancel/1]).
-export([recv_proxy_header/2]).
-export([remove_connection/1]).
-export([get_status/1]).
@@ -197,28 +200,61 @@ child_spec(Ref, Transport, TransOpts0, Protocol, ProtoOpts) ->
Ref, Transport, TransOpts, Protocol, ProtoOpts
]}, type => supervisor}.
--spec handshake(ref()) -> {ok, ranch_transport:socket()}.
+-spec handshake(ref()) -> {ok, ranch_transport:socket()} | {continue, any()}.
handshake(Ref) ->
- handshake(Ref, []).
+ handshake1(Ref, undefined).
--spec handshake(ref(), any()) -> {ok, ranch_transport:socket()}.
+-spec handshake(ref(), any()) -> {ok, ranch_transport:socket()} | {continue, any()}.
handshake(Ref, Opts) ->
- receive {handshake, Ref, Transport, CSocket, HandshakeTimeout} ->
- case Transport:handshake(CSocket, Opts, HandshakeTimeout) of
- OK = {ok, _} ->
- OK;
- %% Garbage was most likely sent to the socket, don't error out.
- {error, {tls_alert, _}} ->
- ok = Transport:close(CSocket),
- exit(normal);
- %% Socket most likely stopped responding, don't error out.
- {error, Reason} when Reason =:= timeout; Reason =:= closed ->
- ok = Transport:close(CSocket),
- exit(normal);
- {error, Reason} ->
- ok = Transport:close(CSocket),
- error(Reason)
- end
+ handshake1(Ref, {opts, Opts}).
+
+handshake1(Ref, Opts) ->
+ receive {handshake, Ref, Transport, CSocket, Timeout} ->
+ Handshake = handshake_transport(Transport, handshake, CSocket, Opts, Timeout),
+ handshake_result(Handshake, Ref, Transport, CSocket, Timeout)
+ end.
+
+-spec handshake_continue(ref()) -> {ok, ranch_transport:socket()}.
+handshake_continue(Ref) ->
+ handshake_continue1(Ref, undefined).
+
+-spec handshake_continue(ref(), any()) -> {ok, ranch_transport:socket()}.
+handshake_continue(Ref, Opts) ->
+ handshake_continue1(Ref, {opts, Opts}).
+
+handshake_continue1(Ref, Opts) ->
+ receive {handshake_continue, Ref, Transport, CSocket, Timeout} ->
+ Handshake = handshake_transport(Transport, handshake_continue, CSocket, Opts, Timeout),
+ handshake_result(Handshake, Ref, Transport, CSocket, Timeout)
+ end.
+
+handshake_transport(Transport, Fun, CSocket, undefined, Timeout) ->
+ Transport:Fun(CSocket, Timeout);
+handshake_transport(Transport, Fun, CSocket, {opts, Opts}, Timeout) ->
+ Transport:Fun(CSocket, Opts, Timeout).
+
+handshake_result(Result, Ref, Transport, CSocket, Timeout) ->
+ case Result of
+ OK = {ok, _} ->
+ OK;
+ {ok, CSocket2, Info} ->
+ self() ! {handshake_continue, Ref, Transport, CSocket2, Timeout},
+ {continue, Info};
+ {error, {tls_alert, _}} ->
+ ok = Transport:close(CSocket),
+ exit(normal);
+ {error, Reason} when Reason =:= timeout; Reason =:= closed ->
+ ok = Transport:close(CSocket),
+ exit(normal);
+ {error, Reason} ->
+ ok = Transport:close(CSocket),
+ error(Reason)
+ end.
+
+-spec handshake_cancel(ref()) -> ok.
+handshake_cancel(Ref) ->
+ receive {handshake_continue, Ref, Transport, CSocket, _} ->
+ Transport:handshake_cancel(CSocket)
end.
%% Unlike handshake/2 this function always return errors because
diff --git a/src/ranch_ssl.erl b/src/ranch_ssl.erl
index b6ca50b..b86a35f 100644
--- a/src/ranch_ssl.erl
+++ b/src/ranch_ssl.erl
@@ -21,7 +21,11 @@
-export([listen/1]).
-export([disallowed_listen_options/0]).
-export([accept/2]).
+-export([handshake/2]).
-export([handshake/3]).
+-export([handshake_continue/2]).
+-export([handshake_continue/3]).
+-export([handshake_cancel/1]).
-export([connect/3]).
-export([connect/4]).
-export([recv/3]).
@@ -56,6 +60,7 @@
%% @todo Update when ssl exports named_curve().
| {eccs, [atom()]}
| {fail_if_no_peer_cert, boolean()}
+ | {handshake, hello | full}
| {hibernate_after, timeout()}
| {honor_cipher_order, boolean()}
| {honor_ecc_order, boolean()}
@@ -138,16 +143,42 @@ disallowed_listen_options() ->
accept(LSocket, Timeout) ->
ssl:transport_accept(LSocket, Timeout).
+-spec handshake(inet:socket() | ssl:sslsocket(), timeout())
+ -> {ok, ssl:sslsocket()} | {ok, ssl:sslsocket(), ssl:protocol_extensions()} | {error, any()}.
+handshake(CSocket, Timeout) ->
+ handshake(CSocket, [], Timeout).
+
-spec handshake(inet:socket() | ssl:sslsocket(), opts(), timeout())
- -> {ok, ssl:sslsocket()} | {error, any()}.
+ -> {ok, ssl:sslsocket()} | {ok, ssl:sslsocket(), ssl:protocol_extensions()} | {error, any()}.
handshake(CSocket, Opts, Timeout) ->
case ssl:handshake(CSocket, Opts, Timeout) of
- {ok, NewSocket} ->
- {ok, NewSocket};
+ OK = {ok, _} ->
+ OK;
+ OK = {ok, _, _} ->
+ OK;
+ Error = {error, _} ->
+ Error
+ end.
+
+-spec handshake_continue(ssl:sslsocket(), timeout())
+ -> {ok, ssl:sslsocket()} | {error, any()}.
+handshake_continue(CSocket, Timeout) ->
+ handshake_continue(CSocket, [], Timeout).
+
+-spec handshake_continue(ssl:sslsocket(), [ssl:tls_server_option()], timeout())
+ -> {ok, ssl:sslsocket()} | {error, any()}.
+handshake_continue(CSocket, Opts, Timeout) ->
+ case ssl:handshake_continue(CSocket, Opts, Timeout) of
+ OK = {ok, _} ->
+ OK;
Error = {error, _} ->
Error
end.
+-spec handshake_cancel(ssl:sslsocket()) -> ok.
+handshake_cancel(CSocket) ->
+ ok = ssl:handshake_cancel(CSocket).
+
%% @todo Probably filter Opts?
-spec connect(inet:ip_address() | inet:hostname(),
inet:port_number(), any())
diff --git a/src/ranch_tcp.erl b/src/ranch_tcp.erl
index 3b4e4c3..0d8da2e 100644
--- a/src/ranch_tcp.erl
+++ b/src/ranch_tcp.erl
@@ -21,7 +21,11 @@
-export([listen/1]).
-export([disallowed_listen_options/0]).
-export([accept/2]).
+-export([handshake/2]).
-export([handshake/3]).
+-export([handshake_continue/2]).
+-export([handshake_continue/3]).
+-export([handshake_cancel/1]).
-export([connect/3]).
-export([connect/4]).
-export([recv/3]).
@@ -105,10 +109,26 @@ disallowed_listen_options() ->
accept(LSocket, Timeout) ->
gen_tcp:accept(LSocket, Timeout).
+-spec handshake(inet:socket(), timeout()) -> {ok, inet:socket()}.
+handshake(CSocket, Timeout) ->
+ handshake(CSocket, [], Timeout).
+
-spec handshake(inet:socket(), opts(), timeout()) -> {ok, inet:socket()}.
handshake(CSocket, _, _) ->
{ok, CSocket}.
+-spec handshake_continue(inet:socket(), timeout()) -> no_return().
+handshake_continue(CSocket, Timeout) ->
+ handshake_continue(CSocket, [], Timeout).
+
+-spec handshake_continue(inet:socket(), opts(), timeout()) -> no_return().
+handshake_continue(_, _, _) ->
+ error(not_supported).
+
+-spec handshake_cancel(inet:socket()) -> no_return().
+handshake_cancel(_) ->
+ error(not_supported).
+
%% @todo Probably filter Opts?
-spec connect(inet:ip_address() | inet:hostname(),
inet:port_number(), any())
diff --git a/src/ranch_transport.erl b/src/ranch_transport.erl
index 6790977..5fd6242 100644
--- a/src/ranch_transport.erl
+++ b/src/ranch_transport.erl
@@ -30,7 +30,11 @@
-callback listen(ranch:transport_opts(any())) -> {ok, socket()} | {error, atom()}.
-callback accept(socket(), timeout())
-> {ok, socket()} | {error, closed | timeout | atom()}.
--callback handshake(socket(), opts(), timeout()) -> {ok, socket()} | {error, any()}.
+-callback handshake(socket(), timeout()) -> {ok, socket()} | {ok, socket(), any()} | {error, any()}.
+-callback handshake(socket(), opts(), timeout()) -> {ok, socket()} | {ok, socket(), any()} | {error, any()}.
+-callback handshake_continue(socket(), timeout()) -> {ok, socket()} | {error, any()}.
+-callback handshake_continue(socket(), opts(), timeout()) -> {ok, socket()} | {error, any()}.
+-callback handshake_cancel(socket()) -> ok.
-callback connect(string(), inet:port_number(), opts())
-> {ok, socket()} | {error, atom()}.
-callback connect(string(), inet:port_number(), opts(), timeout())
diff --git a/test/acceptor_SUITE.erl b/test/acceptor_SUITE.erl
index 2c8b9e2..7fc26f4 100644
--- a/test/acceptor_SUITE.erl
+++ b/test/acceptor_SUITE.erl
@@ -57,6 +57,7 @@ groups() ->
ssl_echo,
ssl_local_echo,
ssl_graceful,
+ ssl_handshake,
ssl_sni_echo,
ssl_sni_fail,
ssl_upgrade_from_tcp,
@@ -533,6 +534,35 @@ ssl_echo(_) ->
{'EXIT', _} = begin catch ranch:get_port(Name) end,
ok.
+ssl_handshake(_) ->
+ doc("Ensure that multiple steps handshake works with SSL transport."),
+ Name = name(),
+ {CaCert1, Cert1, Key1} = ct_helper:make_certs(),
+ {CaCert2, Cert2, Key2} = ct_helper:make_certs(),
+ Opts1 = [{cert, Cert1}, {key, Key1}, {cacerts, [CaCert1]}, {verify, verify_peer}],
+ Opts2 = [{cert, Cert2}, {key, Key2}, {cacerts, [CaCert2]}, {verify, verify_peer}],
+ DefaultOpts = ct_helper:get_certs_from_ets(),
+ {ok, _} = ranch:start_listener(Name,
+ ranch_ssl, [{handshake, hello}|DefaultOpts],
+ handshake_protocol, #{"ranch1" => Opts1, "ranch2" => Opts2}),
+ Port = ranch:get_port(Name),
+ {ok, Socket1} = ssl:connect("localhost", Port, [binary, {active, false}, {packet, raw},
+ {server_name_indication, "ranch1"}], 5000),
+ {ok, Cert1} = ssl:peercert(Socket1),
+ ok = ssl:send(Socket1, <<"SSL Ranch is working!">>),
+ {ok, <<"SSL Ranch is working!">>} = ssl:recv(Socket1, 21, 1000),
+ {ok, Socket2} = ssl:connect("localhost", Port, [binary, {active, false}, {packet, raw},
+ {server_name_indication, "ranch2"}], 5000),
+ {ok, Cert2} = ssl:peercert(Socket2),
+ ok = ssl:send(Socket2, <<"SSL Ranch is working!">>),
+ {ok, <<"SSL Ranch is working!">>} = ssl:recv(Socket2, 21, 1000),
+ ok = ranch:stop_listener(Name),
+ {error, closed} = ssl:recv(Socket1, 0, 1000),
+ {error, closed} = ssl:recv(Socket2, 0, 1000),
+ %% Make sure the listener stopped.
+ {'EXIT', _} = begin catch ranch:get_port(Name) end,
+ ok.
+
ssl_local_echo(_) ->
case do_os_supports_local_sockets() of
true ->
diff --git a/test/handshake_protocol.erl b/test/handshake_protocol.erl
new file mode 100644
index 0000000..cedbe2d
--- /dev/null
+++ b/test/handshake_protocol.erl
@@ -0,0 +1,32 @@
+-module(handshake_protocol).
+-behaviour(ranch_protocol).
+
+-export([start_link/3]).
+-export([init/3]).
+
+start_link(Ref, Transport, Opts) ->
+ Pid = spawn_link(?MODULE, init, [Ref, Transport, Opts]),
+ {ok, Pid}.
+
+init(Ref, Transport, Opts) ->
+ SniHost = case ranch:handshake(Ref) of
+ %% Due to a bug in ssl (https://bugs.erlang.org/browse/ERL-951,
+ %% fixed in OTP 22.0.3) the value for sni may be {sni, Hostname}
+ %% instead of Hostname.
+ {continue, #{sni := {sni, Hostname}}} ->
+ Hostname;
+ {continue, #{sni := Hostname}} ->
+ Hostname
+ end,
+ SniHostOpts = maps:get(SniHost, Opts),
+ {ok, Socket} = ranch:handshake_continue(Ref, SniHostOpts),
+ loop(Socket, Transport).
+
+loop(Socket, Transport) ->
+ case Transport:recv(Socket, 0, 5000) of
+ {ok, Data} ->
+ Transport:send(Socket, Data),
+ loop(Socket, Transport);
+ _ ->
+ ok = Transport:close(Socket)
+ end.