72
72
% % @param Options options for distribution. Supported options are:
73
73
% % - `name_domain' : whether name should be short or long
74
74
% % - `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.
75
77
% %-----------------------------------------------------------------------------
76
78
-spec start (atom (), map ()) -> {ok , pid ()}.
77
79
start (Name , Options0 ) when is_atom (Name ) andalso is_map (Options0 ) ->
@@ -80,11 +82,30 @@ start(Name, Options0) when is_atom(Name) andalso is_map(Options0) ->
80
82
case Key of
81
83
name_domain when Val =:= shortnames orelse Val =:= longnames -> ok ;
82
84
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 ;
83
87
_ -> error ({invalid_option , Key , Val }, [Name , Options0 ])
84
88
end
85
89
end ,
86
90
Options0
87
91
),
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
+ case {maps :is_key (dist_listen_min , Options0 ), maps :is_key (dist_listen_max , Options0 )} of
95
+ {true , false } ->
96
+ error (missing_dist_listen_max , [Name , Options0 ]);
97
+ {false , true } ->
98
+ error (missing_dist_listen_min , [Name , Options0 ]);
99
+ {true , true } ->
100
+ Min = maps :get (dist_listen_min , Options0 ),
101
+ Max = maps :get (dist_listen_max , Options0 ),
102
+ if
103
+ Min > Max -> error (invalid_port_range , [Name , Options0 ]);
104
+ true -> ok
105
+ end ;
106
+ _ ->
107
+ ok
108
+ end ,
88
109
Options1 = Options0 #{name => Name },
89
110
Options2 = split_name (Options1 ),
90
111
net_kernel_sup :start (Options2 );
@@ -173,13 +194,17 @@ init(Options) ->
173
194
process_flag (trap_exit , true ),
174
195
LongNames = maps :get (name_domain , Options , longnames ) =:= longnames ,
175
196
ProtoDist = maps :get (proto_dist , Options , socket_dist ),
197
+ DistPortMin = maps :get (dist_listen_min , Options , 0 ),
198
+ DistPortMax = maps :get (dist_listen_max , Options , 0 ),
199
+
176
200
Name = maps :get (name , Options ),
177
201
Node = maps :get (node , Options ),
178
202
Cookie = crypto :strong_rand_bytes (16 ),
179
203
TickInterval = (? NET_TICK_TIME * 1000 ) div ? NET_TICK_INTENSITY ,
180
204
Self = self (),
181
205
Ticker = spawn_link (fun () -> ticker (Self , TickInterval ) end ),
182
- case ProtoDist :listen (Name ) of
206
+ % Try ports in range until one succeeds
207
+ case try_listen_ports (ProtoDist , Name , DistPortMin , DistPortMax ) of
183
208
{ok , {Listen , _Address , Creation }} ->
184
209
true = erlang :setnode (Node , Creation ),
185
210
AcceptPid = ProtoDist :accept (Listen ),
@@ -198,6 +223,18 @@ init(Options) ->
198
223
{stop , Reason }
199
224
end .
200
225
226
+ % try ports in range
227
+ try_listen_ports (ProtoDist , Name , DistPortMin , DistPortMax ) ->
228
+ try_listen_ports (ProtoDist , Name , DistPortMin , DistPortMax , DistPortMin ).
229
+
230
+ try_listen_ports (_ProtoDist , _Name , _DistPortMin , DistPortMax , Port ) when Port > DistPortMax ->
231
+ {error , no_port_available };
232
+ try_listen_ports (ProtoDist , Name , DistPortMin , DistPortMax , Port ) ->
233
+ case ProtoDist :listen (Name , Port ) of
234
+ {ok , _ } = Success -> Success ;
235
+ {error , _ } -> try_listen_ports (ProtoDist , Name , DistPortMin , DistPortMax , Port + 1 )
236
+ end .
237
+
201
238
% % @hidden
202
239
handle_call (get_state , _From , # state {longnames = Longnames } = State ) ->
203
240
NameDomain =
0 commit comments