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,31 @@ 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
+ 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 ,
88
110
Options1 = Options0 #{name => Name },
89
111
Options2 = split_name (Options1 ),
90
112
net_kernel_sup :start (Options2 );
@@ -173,13 +195,17 @@ init(Options) ->
173
195
process_flag (trap_exit , true ),
174
196
LongNames = maps :get (name_domain , Options , longnames ) =:= longnames ,
175
197
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
+
176
201
Name = maps :get (name , Options ),
177
202
Node = maps :get (node , Options ),
178
203
Cookie = crypto :strong_rand_bytes (16 ),
179
204
TickInterval = (? NET_TICK_TIME * 1000 ) div ? NET_TICK_INTENSITY ,
180
205
Self = self (),
181
206
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
183
209
{ok , {Listen , _Address , Creation }} ->
184
210
true = erlang :setnode (Node , Creation ),
185
211
AcceptPid = ProtoDist :accept (Listen ),
@@ -198,6 +224,18 @@ init(Options) ->
198
224
{stop , Reason }
199
225
end .
200
226
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
+
201
239
% % @hidden
202
240
handle_call (get_state , _From , # state {longnames = Longnames } = State ) ->
203
241
NameDomain =
0 commit comments