Skip to content

Commit

Permalink
Disterl: dist_listen_min/max options
Browse files Browse the repository at this point in the history
Similar to erlang inet_dist_listen_min.
https://www.erlang.org/doc/apps/kernel/kernel_app.html#inet_dist_listen

Needed for certain gateways/port forwarding scenarios.

Namely running local Wokwi simulator and connecting to that.

Signed-off-by: Peter M <petermm@gmail.com>
  • Loading branch information
petermm committed Jan 26, 2025
1 parent be62d3a commit c064507
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 18 deletions.
40 changes: 39 additions & 1 deletion libs/estdlib/src/net_kernel.erl
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@
%% @param Options options for distribution. Supported options are:
%% - `name_domain' : whether name should be short or long
%% - `proto_dist' : the module used for distribution (e.g. `socket_dist')
%% - `dist_listen_min' : With dist_listen_max defines the port range for the listener socket of a distributed Erlang node.
%% - `dist_listen_max' : With dist_listen_min defines the port range for the listener socket of a distributed Erlang node.
%%-----------------------------------------------------------------------------
-spec start(atom(), map()) -> {ok, pid()}.
start(Name, Options0) when is_atom(Name) andalso is_map(Options0) ->
Expand All @@ -80,11 +82,31 @@ start(Name, Options0) when is_atom(Name) andalso is_map(Options0) ->
case Key of
name_domain when Val =:= shortnames orelse Val =:= longnames -> ok;
proto_dist when is_atom(Val) -> ok;
dist_listen_min when is_integer(Val) -> ok;
dist_listen_max when is_integer(Val) -> ok;
_ -> error({invalid_option, Key, Val}, [Name, Options0])
end
end,
Options0
),
% Check that if one of dist_listen_min and dist_listen_max are configured, both are configured.
% And verify dist_listen_max is larger or equal to dist_listen_min.
ok =
case {maps:is_key(dist_listen_min, Options0), maps:is_key(dist_listen_max, Options0)} of
{true, false} ->
error(missing_dist_listen_max, [Name, Options0]);
{false, true} ->
error(missing_dist_listen_min, [Name, Options0]);
{true, true} ->
Min = maps:get(dist_listen_min, Options0),
Max = maps:get(dist_listen_max, Options0),
if
Min > Max -> error(invalid_port_range, [Name, Options0]);
true -> ok
end;
_ ->
ok
end,
Options1 = Options0#{name => Name},
Options2 = split_name(Options1),
net_kernel_sup:start(Options2);
Expand Down Expand Up @@ -173,13 +195,17 @@ init(Options) ->
process_flag(trap_exit, true),
LongNames = maps:get(name_domain, Options, longnames) =:= longnames,
ProtoDist = maps:get(proto_dist, Options, socket_dist),
DistPortMin = maps:get(dist_listen_min, Options, 0),
DistPortMax = maps:get(dist_listen_max, Options, 0),

Name = maps:get(name, Options),
Node = maps:get(node, Options),
Cookie = crypto:strong_rand_bytes(16),
TickInterval = (?NET_TICK_TIME * 1000) div ?NET_TICK_INTENSITY,
Self = self(),
Ticker = spawn_link(fun() -> ticker(Self, TickInterval) end),
case ProtoDist:listen(Name) of
% Try ports in range until one succeeds
case try_listen_ports(ProtoDist, Name, DistPortMin, DistPortMax) of
{ok, {Listen, _Address, Creation}} ->
true = erlang:setnode(Node, Creation),
AcceptPid = ProtoDist:accept(Listen),
Expand All @@ -198,6 +224,18 @@ init(Options) ->
{stop, Reason}
end.

% try ports in range
try_listen_ports(ProtoDist, Name, DistPortMin, DistPortMax) ->
try_listen_ports(ProtoDist, Name, DistPortMin, DistPortMax, DistPortMin).

try_listen_ports(_ProtoDist, _Name, _DistPortMin, DistPortMax, Port) when Port > DistPortMax ->
{error, no_port_available};
try_listen_ports(ProtoDist, Name, DistPortMin, DistPortMax, Port) ->
case ProtoDist:listen(Name, Port) of
{ok, _} = Success -> Success;
{error, _} -> try_listen_ports(ProtoDist, Name, DistPortMin, DistPortMax, Port + 1)
end.

%% @hidden
handle_call(get_state, _From, #state{longnames = Longnames} = State) ->
NameDomain =
Expand Down
48 changes: 31 additions & 17 deletions libs/estdlib/src/socket_dist.erl
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
% dist interface
-export([
listen/1,
listen/2,
accept/1,
accept_connection/5,
setup/5,
Expand All @@ -37,24 +38,37 @@

-spec listen(string()) -> {ok, {any(), #net_address{}, pos_integer()}} | {error, any()}.
listen(Name) ->
listen(Name, 0).

-spec listen(string(), non_neg_integer()) ->
{ok, {any(), #net_address{}, pos_integer()}} | {error, any()}.
listen(Name, SocketPort) ->
{ok, LSock} = socket:open(inet, stream, tcp),
ok = socket:bind(LSock, #{
family => inet,
port => 0,
addr => {0, 0, 0, 0}
}),
ok = socket:listen(LSock),
{ok, #{addr := Addr, port := Port}} = socket:sockname(LSock),
ErlEpmd = net_kernel:epmd_module(),
Address = #net_address{
host = Addr,
protocol = tcp,
family = inet
},
case ErlEpmd:register_node(Name, Port) of
{ok, Creation} ->
{ok, {LSock, Address, Creation}};
Error ->
case
socket:bind(LSock, #{
family => inet,
port => SocketPort,
addr => {0, 0, 0, 0}
})
of
ok ->
ok = socket:listen(LSock),
{ok, #{addr := Addr, port := Port}} = socket:sockname(LSock),
ErlEpmd = net_kernel:epmd_module(),
Address = #net_address{
host = Addr,
protocol = tcp,
family = inet
},
case ErlEpmd:register_node(Name, Port) of
{ok, Creation} ->
{ok, {LSock, Address, Creation}};
Error ->
socket:close(LSock),
Error
end;
{error, _} = Error ->
socket:close(LSock),
Error
end.

Expand Down

0 comments on commit c064507

Please sign in to comment.