Skip to content

Commit c064507

Browse files
committed
Disterl: dist_listen_min/max options
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 <[email protected]>
1 parent be62d3a commit c064507

File tree

2 files changed

+70
-18
lines changed

2 files changed

+70
-18
lines changed

libs/estdlib/src/net_kernel.erl

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@
7272
%% @param Options options for distribution. Supported options are:
7373
%% - `name_domain' : whether name should be short or long
7474
%% - `proto_dist' : the module used for distribution (e.g. `socket_dist')
75+
%% - `dist_listen_min' : With dist_listen_max defines the port range for the listener socket of a distributed Erlang node.
76+
%% - `dist_listen_max' : With dist_listen_min defines the port range for the listener socket of a distributed Erlang node.
7577
%%-----------------------------------------------------------------------------
7678
-spec start(atom(), map()) -> {ok, pid()}.
7779
start(Name, Options0) when is_atom(Name) andalso is_map(Options0) ->
@@ -80,11 +82,31 @@ start(Name, Options0) when is_atom(Name) andalso is_map(Options0) ->
8082
case Key of
8183
name_domain when Val =:= shortnames orelse Val =:= longnames -> ok;
8284
proto_dist when is_atom(Val) -> ok;
85+
dist_listen_min when is_integer(Val) -> ok;
86+
dist_listen_max when is_integer(Val) -> ok;
8387
_ -> error({invalid_option, Key, Val}, [Name, Options0])
8488
end
8589
end,
8690
Options0
8791
),
92+
% Check that if one of dist_listen_min and dist_listen_max are configured, both are configured.
93+
% And verify dist_listen_max is larger or equal to dist_listen_min.
94+
ok =
95+
case {maps:is_key(dist_listen_min, Options0), maps:is_key(dist_listen_max, Options0)} of
96+
{true, false} ->
97+
error(missing_dist_listen_max, [Name, Options0]);
98+
{false, true} ->
99+
error(missing_dist_listen_min, [Name, Options0]);
100+
{true, true} ->
101+
Min = maps:get(dist_listen_min, Options0),
102+
Max = maps:get(dist_listen_max, Options0),
103+
if
104+
Min > Max -> error(invalid_port_range, [Name, Options0]);
105+
true -> ok
106+
end;
107+
_ ->
108+
ok
109+
end,
88110
Options1 = Options0#{name => Name},
89111
Options2 = split_name(Options1),
90112
net_kernel_sup:start(Options2);
@@ -173,13 +195,17 @@ init(Options) ->
173195
process_flag(trap_exit, true),
174196
LongNames = maps:get(name_domain, Options, longnames) =:= longnames,
175197
ProtoDist = maps:get(proto_dist, Options, socket_dist),
198+
DistPortMin = maps:get(dist_listen_min, Options, 0),
199+
DistPortMax = maps:get(dist_listen_max, Options, 0),
200+
176201
Name = maps:get(name, Options),
177202
Node = maps:get(node, Options),
178203
Cookie = crypto:strong_rand_bytes(16),
179204
TickInterval = (?NET_TICK_TIME * 1000) div ?NET_TICK_INTENSITY,
180205
Self = self(),
181206
Ticker = spawn_link(fun() -> ticker(Self, TickInterval) end),
182-
case ProtoDist:listen(Name) of
207+
% Try ports in range until one succeeds
208+
case try_listen_ports(ProtoDist, Name, DistPortMin, DistPortMax) of
183209
{ok, {Listen, _Address, Creation}} ->
184210
true = erlang:setnode(Node, Creation),
185211
AcceptPid = ProtoDist:accept(Listen),
@@ -198,6 +224,18 @@ init(Options) ->
198224
{stop, Reason}
199225
end.
200226

227+
% try ports in range
228+
try_listen_ports(ProtoDist, Name, DistPortMin, DistPortMax) ->
229+
try_listen_ports(ProtoDist, Name, DistPortMin, DistPortMax, DistPortMin).
230+
231+
try_listen_ports(_ProtoDist, _Name, _DistPortMin, DistPortMax, Port) when Port > DistPortMax ->
232+
{error, no_port_available};
233+
try_listen_ports(ProtoDist, Name, DistPortMin, DistPortMax, Port) ->
234+
case ProtoDist:listen(Name, Port) of
235+
{ok, _} = Success -> Success;
236+
{error, _} -> try_listen_ports(ProtoDist, Name, DistPortMin, DistPortMax, Port + 1)
237+
end.
238+
201239
%% @hidden
202240
handle_call(get_state, _From, #state{longnames = Longnames} = State) ->
203241
NameDomain =

libs/estdlib/src/socket_dist.erl

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
% dist interface
2323
-export([
2424
listen/1,
25+
listen/2,
2526
accept/1,
2627
accept_connection/5,
2728
setup/5,
@@ -37,24 +38,37 @@
3738

3839
-spec listen(string()) -> {ok, {any(), #net_address{}, pos_integer()}} | {error, any()}.
3940
listen(Name) ->
41+
listen(Name, 0).
42+
43+
-spec listen(string(), non_neg_integer()) ->
44+
{ok, {any(), #net_address{}, pos_integer()}} | {error, any()}.
45+
listen(Name, SocketPort) ->
4046
{ok, LSock} = socket:open(inet, stream, tcp),
41-
ok = socket:bind(LSock, #{
42-
family => inet,
43-
port => 0,
44-
addr => {0, 0, 0, 0}
45-
}),
46-
ok = socket:listen(LSock),
47-
{ok, #{addr := Addr, port := Port}} = socket:sockname(LSock),
48-
ErlEpmd = net_kernel:epmd_module(),
49-
Address = #net_address{
50-
host = Addr,
51-
protocol = tcp,
52-
family = inet
53-
},
54-
case ErlEpmd:register_node(Name, Port) of
55-
{ok, Creation} ->
56-
{ok, {LSock, Address, Creation}};
57-
Error ->
47+
case
48+
socket:bind(LSock, #{
49+
family => inet,
50+
port => SocketPort,
51+
addr => {0, 0, 0, 0}
52+
})
53+
of
54+
ok ->
55+
ok = socket:listen(LSock),
56+
{ok, #{addr := Addr, port := Port}} = socket:sockname(LSock),
57+
ErlEpmd = net_kernel:epmd_module(),
58+
Address = #net_address{
59+
host = Addr,
60+
protocol = tcp,
61+
family = inet
62+
},
63+
case ErlEpmd:register_node(Name, Port) of
64+
{ok, Creation} ->
65+
{ok, {LSock, Address, Creation}};
66+
Error ->
67+
socket:close(LSock),
68+
Error
69+
end;
70+
{error, _} = Error ->
71+
socket:close(LSock),
5872
Error
5973
end.
6074

0 commit comments

Comments
 (0)