From 036fbd53189f0a43bd8348e517a17da0f00de980 Mon Sep 17 00:00:00 2001
From: Andrew Thompson <andrew@hijacked.us>
Date: Mon, 24 Sep 2012 13:32:21 -0400
Subject: Add {socket, Socket} transport option, for accepting on existing
 sockets

---
 guide/listeners.md          | 18 +++++++++++++++
 guide/toc.md                |  1 +
 src/ranch.erl               | 17 +++++++++++++--
 src/ranch_acceptors_sup.erl |  8 ++++++-
 test/acceptor_SUITE.erl     | 53 +++++++++++++++++++++++++++++++++++++++++++--
 5 files changed, 92 insertions(+), 5 deletions(-)

diff --git a/guide/listeners.md b/guide/listeners.md
index 1f7c430..48ec622 100644
--- a/guide/listeners.md
+++ b/guide/listeners.md
@@ -113,6 +113,24 @@ We recommend the use of port rewriting for systems with a single server,
 and load balancing for systems with multiple servers. Documenting these
 solutions is however out of the scope of this guide.
 
+Accepting connections on an existing socket
+-------------------------------------------
+
+If you want to accept connections on an existing socket, you can use the
+`socket` transport option, which should just be the relevant data returned
+from the connect() function for the undelying socket library
+(gen_tcp:connect(), ssl:connect()). accept() will be then be called on the
+passed in socket. You should connect() the socket in {active, false} mode, as
+well.
+
+Note, however, that because of a bug in SSL, you cannot change ownership of an
+SSL listen socket prior to R16. Ranch will catch the error thrown, but the
+owner of the SSL socket will remain as whatever process created the socket.
+However, this will not affect accept() behaviour unless the owner process dies,
+in which case the socket is closed. Therefore, to use this feature with SSL
+with an erlang release prior to R16, ensure that the SSL socket is opened in a
+persistant process.
+
 Limiting the number of concurrent connections
 ---------------------------------------------
 
diff --git a/guide/toc.md b/guide/toc.md
index f83b6c6..3cbdbcf 100644
--- a/guide/toc.md
+++ b/guide/toc.md
@@ -9,6 +9,7 @@ Ranch User Guide
    *  Starting and stopping
    *  Listening on a random port
    *  Listening on privileged ports
+   *  Accepting connections on an existing socket
    *  Limiting the number of concurrent connections
    *  Upgrading
  *  [Transports](transports.md)
diff --git a/src/ranch.erl b/src/ranch.erl
index 6d1d5e8..0924ee7 100644
--- a/src/ranch.erl
+++ b/src/ranch.erl
@@ -59,8 +59,21 @@ start_listener(Ref, NbAcceptors, Transport, TransOpts, Protocol, ProtoOpts)
 		false ->
 			{error, badarg};
 		true ->
-			supervisor:start_child(ranch_sup, child_spec(Ref, NbAcceptors,
-				Transport, TransOpts, Protocol, ProtoOpts))
+			Res = supervisor:start_child(ranch_sup, child_spec(Ref, NbAcceptors,
+					Transport, TransOpts, Protocol, ProtoOpts)),
+			case proplists:get_value(socket, TransOpts) of
+				undefined ->
+					ok;
+				Socket ->
+					%% change the controlling process so the caller dying doesn't
+					%% close the port
+					ListenerPid = ranch_server:lookup_listener(Ref),
+					%%% Note: the catch is here because SSL crashes when you change
+					%%% the controlling process of a listen socket because of a bug.
+					%%% The bug will be fixed in R16.
+					catch(Transport:controlling_process(Socket, ListenerPid))
+			end,
+			Res
 	end.
 
 %% @doc Stop a listener identified by <em>Ref</em>.
diff --git a/src/ranch_acceptors_sup.erl b/src/ranch_acceptors_sup.erl
index 2e45acf..1d9503c 100644
--- a/src/ranch_acceptors_sup.erl
+++ b/src/ranch_acceptors_sup.erl
@@ -35,7 +35,13 @@ start_link(Ref, NbAcceptors, Transport, TransOpts,
 
 init([Ref, NbAcceptors, Transport, TransOpts,
 		Protocol, ListenerPid, ConnsPid]) ->
-	{ok, LSocket} = Transport:listen(TransOpts),
+	LSocket = case proplists:get_value(socket, TransOpts) of
+		undefined ->
+			{ok, Socket} = Transport:listen(TransOpts),
+			Socket;
+		Socket ->
+			Socket
+	end,
 	{ok, {_, Port}} = Transport:sockname(LSocket),
 	ranch_listener:set_port(ListenerPid, Port),
 	Procs = [
diff --git a/test/acceptor_SUITE.erl b/test/acceptor_SUITE.erl
index 0b86a4d..5e8d9cf 100644
--- a/test/acceptor_SUITE.erl
+++ b/test/acceptor_SUITE.erl
@@ -29,10 +29,12 @@
 
 %% ssl.
 -export([ssl_accept_error/1]).
+-export([ssl_accept_socket/1]).
 -export([ssl_active_echo/1]).
 -export([ssl_echo/1]).
 
 %% tcp.
+-export([tcp_accept_socket/1]).
 -export([tcp_active_echo/1]).
 -export([tcp_echo/1]).
 -export([tcp_max_connections/1]).
@@ -50,11 +52,13 @@ groups() ->
 		tcp_echo,
 		tcp_max_connections,
 		tcp_max_connections_and_beyond,
-		tcp_upgrade
+		tcp_upgrade,
+		tcp_accept_socket
 	]}, {ssl, [
 		ssl_accept_error,
 		ssl_active_echo,
-		ssl_echo
+		ssl_echo,
+		ssl_accept_socket
 	]}, {misc, [
 		misc_bad_transport
 	]}].
@@ -108,6 +112,26 @@ ssl_accept_error(Config) ->
 	receive after 500 -> ok end,
 	true = is_process_alive(AcceptorPid).
 
+ssl_accept_socket(Config) ->
+	%%% XXX we can't do the spawn to test the controlling process change
+	%%% because of the bug in ssl
+	{ok, S} = ssl:listen(0,
+		[{certfile, ?config(data_dir, Config) ++ "cert.pem"}, binary,
+			{active, false}, {packet, raw}, {reuseaddr, true}]),
+	{ok, _} = ranch:start_listener(ssl_accept_socket, 1,
+		ranch_ssl, [{socket, S}], echo_protocol, []),
+	Port = ranch:get_port(ssl_accept_socket),
+	{ok, Socket} = ssl:connect("localhost", Port,
+		[binary, {active, false}, {packet, raw},
+		{certfile, ?config(data_dir, Config) ++ "cert.pem"}]),
+	ok = ssl:send(Socket, <<"TCP Ranch is working!">>),
+	{ok, <<"TCP Ranch is working!">>} = ssl:recv(Socket, 21, 1000),
+	ok = ranch:stop_listener(ssl_accept_socket),
+	{error, closed} = ssl:recv(Socket, 0, 1000),
+	%% Make sure the listener stopped.
+	{'EXIT', _} = begin catch ranch:get_port(ssl_accept_socket) end,
+	ok.
+
 ssl_active_echo(Config) ->
 	{ok, _} = ranch:start_listener(ssl_active_echo, 1,
 		ranch_ssl, [{port, 0},
@@ -144,6 +168,31 @@ ssl_echo(Config) ->
 
 %% tcp.
 
+tcp_accept_socket(_) ->
+	Ref = make_ref(),
+	Parent = self(),
+	spawn(fun() ->
+				{ok, S} = gen_tcp:listen(0, [binary, {active, false}, {packet, raw},
+						{reuseaddr, true}]),
+				{ok, _} = ranch:start_listener(tcp_accept_socket, 1,
+					ranch_tcp, [{socket, S}], echo_protocol, []),
+				Parent ! Ref
+		end),
+	receive
+		Ref -> ok
+	end,
+
+	Port = ranch:get_port(tcp_accept_socket),
+	{ok, Socket} = gen_tcp:connect("localhost", Port,
+		[binary, {active, false}, {packet, raw}]),
+	ok = gen_tcp:send(Socket, <<"TCP Ranch is working!">>),
+	{ok, <<"TCP Ranch is working!">>} = gen_tcp:recv(Socket, 21, 1000),
+	ok = ranch:stop_listener(tcp_accept_socket),
+	{error, closed} = gen_tcp:recv(Socket, 0, 1000),
+	%% Make sure the listener stopped.
+	{'EXIT', _} = begin catch ranch:get_port(tcp_accept_socket) end,
+	ok.
+
 tcp_active_echo(_) ->
 	{ok, _} = ranch:start_listener(tcp_active_echo, 1,
 		ranch_tcp, [{port, 0}], active_echo_protocol, []),
-- 
cgit v1.2.3