Browse Source

Add support for UNIX domain sockets

It was working already but the types were wrong and some small
details needed to be corrected.
juhlig 6 years ago
parent
commit
b1e6406e4f
7 changed files with 111 additions and 21 deletions
  1. 14 4
      src/ranch.erl
  2. 17 9
      src/ranch_acceptors_sup.erl
  3. 4 2
      src/ranch_server.erl
  4. 2 2
      src/ranch_ssl.erl
  5. 2 2
      src/ranch_tcp.erl
  6. 2 2
      src/ranch_transport.erl
  7. 70 0
      test/acceptor_SUITE.erl

+ 14 - 4
src/ranch.erl

@@ -201,14 +201,19 @@ get_status(Ref) ->
 			running
 	end.
 
--spec get_addr(ref()) -> {inet:ip_address(), inet:port_number()} | {undefined, undefined}.
+-spec get_addr(ref()) -> {inet:ip_address(), inet:port_number()} |
+	{local, binary()} | {undefined, undefined}.
 get_addr(Ref) ->
 	ranch_server:get_addr(Ref).
 
 -spec get_port(ref()) -> inet:port_number() | undefined.
 get_port(Ref) ->
-	{_, Port} = get_addr(Ref),
-	Port.
+	case get_addr(Ref) of
+		{local, _} ->
+			undefined;
+		{_, Port} ->
+			Port
+	end.
 
 -spec get_connections(ref(), active|all) -> non_neg_integer().
 get_connections(Ref, active) ->
@@ -263,7 +268,12 @@ info(Ref) ->
 listener_info(Ref, Pid) ->
 	[_, Transport, _, Protocol, _] = ranch_server:get_listener_start_args(Ref),
 	Status = get_status(Ref),
-	{IP, Port} = get_addr(Ref),
+	{IP, Port} = case get_addr(Ref) of
+		Addr = {local, _} ->
+			{Addr, undefined};
+		Addr ->
+			Addr
+	end,
 	MaxConns = get_max_connections(Ref),
 	TransOpts = ranch_server:get_transport_options(Ref),
 	ProtoOpts = get_protocol_options(Ref),

+ 17 - 9
src/ranch_acceptors_sup.erl

@@ -50,16 +50,22 @@ init([Ref, NumAcceptors, Transport]) ->
 	-> [{pos_integer(), inet:socket()}].
 start_listen_sockets(Ref, NumListenSockets, Transport, SocketOpts0, Logger) when NumListenSockets > 0 ->
 	BaseSocket = start_listen_socket(Ref, Transport, SocketOpts0, Logger),
-	{ok, Addr={_, Port}} = Transport:sockname(BaseSocket),
-	SocketOpts = case lists:keyfind(port, 1, SocketOpts0) of
-		{port, Port} ->
-			SocketOpts0;
-		_ ->
-			[{port, Port}|lists:keydelete(port, 1, SocketOpts0)]
+	{ok, Addr} = Transport:sockname(BaseSocket),
+	ExtraSockets = case Addr of
+		{local, _} when NumListenSockets > 1 ->
+			listen_error(Ref, Transport, SocketOpts0, reuseport_local, Logger);
+		{local, _} ->
+			[];
+		{_, Port} ->
+			SocketOpts = case lists:keyfind(port, 1, SocketOpts0) of
+				{port, Port} ->
+					SocketOpts0;
+				_ ->
+					[{port, Port}|lists:keydelete(port, 1, SocketOpts0)]
+			end,
+			[{N, start_listen_socket(Ref, Transport, SocketOpts, Logger)}
+				|| N <- lists:seq(2, NumListenSockets)]
 	end,
-	ExtraSockets = [
-		{N, start_listen_socket(Ref, Transport, SocketOpts, Logger)}
-	|| N <- lists:seq(2, NumListenSockets)],
 	ranch_server:set_addr(Ref, Addr),
 	[{1, BaseSocket}|ExtraSockets].
 
@@ -84,5 +90,7 @@ listen_error(Ref, Transport, SocketOpts0, Reason, Logger) ->
 
 format_error(no_cert) ->
 	"no certificate provided; see cert, certfile, sni_fun or sni_hosts options";
+format_error(reuseport_local) ->
+	"num_listen_sockets must be set to 1 for local sockets";
 format_error(Reason) ->
 	inet:format_error(Reason).

+ 4 - 2
src/ranch_server.erl

@@ -120,11 +120,13 @@ get_listener_sup(Ref) ->
 get_listener_sups() ->
 	[{Ref, Pid} || [Ref, Pid] <- ets:match(?TAB, {{listener_sup, '$1'}, '$2'})].
 
--spec set_addr(ranch:ref(), {inet:ip_address(), inet:port_number()} | {undefined, undefined}) -> ok.
+-spec set_addr(ranch:ref(), {inet:ip_address(), inet:port_number()} |
+	{local, binary()} | {undefined, undefined}) -> ok.
 set_addr(Ref, Addr) ->
 	gen_server:call(?MODULE, {set_addr, Ref, Addr}).
 
--spec get_addr(ranch:ref()) -> {inet:ip_address(), inet:port_number()} | {undefined, undefined}.
+-spec get_addr(ranch:ref()) -> {inet:ip_address(), inet:port_number()} |
+	{local, binary()} | {undefined, undefined}.
 get_addr(Ref) ->
 	ets:lookup_element(?TAB, {addr, Ref}, 2).
 

+ 2 - 2
src/ranch_ssl.erl

@@ -218,12 +218,12 @@ controlling_process(Socket, Pid) ->
 	ssl:controlling_process(Socket, Pid).
 
 -spec peername(ssl:sslsocket())
-	-> {ok, {inet:ip_address(), inet:port_number()}} | {error, atom()}.
+	-> {ok, {inet:ip_address(), inet:port_number()} | {local, binary()}} | {error, atom()}.
 peername(Socket) ->
 	ssl:peername(Socket).
 
 -spec sockname(ssl:sslsocket())
-	-> {ok, {inet:ip_address(), inet:port_number()}} | {error, atom()}.
+	-> {ok, {inet:ip_address(), inet:port_number()} | {local, binary()}} | {error, atom()}.
 sockname(Socket) ->
 	ssl:sockname(Socket).
 

+ 2 - 2
src/ranch_tcp.erl

@@ -220,12 +220,12 @@ controlling_process(Socket, Pid) ->
 	gen_tcp:controlling_process(Socket, Pid).
 
 -spec peername(inet:socket())
-	-> {ok, {inet:ip_address(), inet:port_number()}} | {error, atom()}.
+	-> {ok, {inet:ip_address(), inet:port_number()} | {local, binary()}} | {error, atom()}.
 peername(Socket) ->
 	inet:peername(Socket).
 
 -spec sockname(inet:socket())
-	-> {ok, {inet:ip_address(), inet:port_number()}} | {error, atom()}.
+	-> {ok, {inet:ip_address(), inet:port_number()} | {local, binary()}} | {error, atom()}.
 sockname(Socket) ->
 	inet:sockname(Socket).
 

+ 2 - 2
src/ranch_transport.erl

@@ -56,9 +56,9 @@
 -callback controlling_process(socket(), pid())
 	-> ok | {error, closed | not_owner | atom()}.
 -callback peername(socket())
-	-> {ok, {inet:ip_address(), inet:port_number()}} | {error, atom()}.
+	-> {ok, {inet:ip_address(), inet:port_number()} | {local, binary()}} | {error, atom()}.
 -callback sockname(socket())
-	-> {ok, {inet:ip_address(), inet:port_number()}} | {error, atom()}.
+	-> {ok, {inet:ip_address(), inet:port_number()} | {local, binary()}} | {error, atom()}.
 -callback shutdown(socket(), read | write | read_write)
 	-> ok | {error, atom()}.
 -callback close(socket()) -> ok.

+ 70 - 0
test/acceptor_SUITE.erl

@@ -30,6 +30,7 @@ groups() ->
 	[{tcp, [
 		tcp_active_echo,
 		tcp_echo,
+		tcp_local_echo,
 		tcp_graceful,
 		tcp_inherit_options,
 		tcp_max_connections,
@@ -49,6 +50,7 @@ groups() ->
 		ssl_accept_error,
 		ssl_active_echo,
 		ssl_echo,
+		ssl_local_echo,
 		ssl_graceful,
 		ssl_sni_echo,
 		ssl_sni_fail,
@@ -481,6 +483,36 @@ ssl_echo(_) ->
 	{'EXIT', _} = begin catch ranch:get_port(Name) end,
 	ok.
 
+ssl_local_echo(_) ->
+	case do_os_supports_local_sockets() of
+		true ->
+			do_ssl_local_echo();
+		false ->
+			{skip, "No local socket support."}
+	end.
+
+do_ssl_local_echo() ->
+	doc("Ensure that listening on a local socket works with SSL transport."),
+	SockFile = do_tempname(),
+	try
+		Name = name(),
+		Opts = ct_helper:get_certs_from_ets(),
+		{ok, _} = ranch:start_listener(Name,
+			ranch_ssl, #{socket_opts => [{ip, {local, SockFile}}|Opts]},
+			echo_protocol, []),
+		undefined = ranch:get_port(Name),
+		{ok, Socket} = ssl:connect({local, SockFile}, 0, [binary, {active, false}, {packet, raw}]),
+		ok = ssl:send(Socket, <<"SSL Ranch is working!">>),
+		{ok, <<"SSL Ranch is working!">>} = ssl:recv(Socket, 21, 1000),
+		ok = ranch:stop_listener(Name),
+		{error, closed} = ssl:recv(Socket, 0, 1000),
+		%% Make sure the listener stopped.
+		{'EXIT', _} = begin catch ranch:get_port(Name) end,
+		ok
+	after
+		file:delete(SockFile)
+	end.
+
 ssl_sni_echo(_) ->
 	case application:get_key(ssl, vsn) of
 		{ok, Vsn} when Vsn >= "7.0" ->
@@ -744,6 +776,35 @@ tcp_echo(_) ->
 	{'EXIT', _} = begin catch ranch:get_port(Name) end,
 	ok.
 
+tcp_local_echo(_) ->
+	case do_os_supports_local_sockets() of
+		true ->
+			do_tcp_local_echo();
+		false ->
+			{skip, "No local socket support."}
+	end.
+
+do_tcp_local_echo() ->
+	doc("Ensure that listening on a local socket works with TCP transport."),
+	SockFile = do_tempname(),
+	try
+		Name = name(),
+		{ok, _} = ranch:start_listener(Name,
+			ranch_tcp, #{socket_opts => [{ip, {local, SockFile}}]},
+			echo_protocol, []),
+		undefined = ranch:get_port(Name),
+		{ok, Socket} = gen_tcp:connect({local, SockFile}, 0, [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(Name),
+		{error, closed} = gen_tcp:recv(Socket, 0, 1000),
+		%% Make sure the listener stopped.
+		{'EXIT', _} = begin catch ranch:get_port(Name) end,
+		ok
+	after
+		file:delete(SockFile)
+	end.
+
 tcp_graceful(_) ->
 	doc("Ensure suspending and resuming of listeners does not kill active connections."),
 	Name = name(),
@@ -1373,3 +1434,12 @@ do_os_supports_reuseport() ->
 		{{unix, linux}, {3, Minor, _}} when Minor >= 9 -> true;
 		_ -> false
 	end.
+
+do_os_supports_local_sockets() ->
+	case os:type() of
+		{unix, _} -> true;
+		_ -> false
+	end.
+
+do_tempname() ->
+	lists:droplast(os:cmd("mktemp -u")).