7373% % @param Options options for distribution. Supported options are:
7474% % - `name_domain' : whether name should be short or long
7575% % - `proto_dist' : the module used for distribution (e.g. `socket_dist')
76+ % % - `dist_listen_min' : defines the min port range for the listener socket.
77+ % % - `dist_listen_max' : defines the max port range for the listener socket.
7678% %-----------------------------------------------------------------------------
7779-spec start (atom (), map ()) -> {ok , pid ()}.
7880start (Name , Options0 ) when is_atom (Name ) andalso is_map (Options0 ) ->
@@ -81,11 +83,31 @@ start(Name, Options0) when is_atom(Name) andalso is_map(Options0) ->
8183 case Key of
8284 name_domain when Val =:= shortnames orelse Val =:= longnames -> ok ;
8385 proto_dist when is_atom (Val ) -> ok ;
86+ dist_listen_min when is_integer (Val ) -> ok ;
87+ dist_listen_max when is_integer (Val ) -> ok ;
8488 _ -> error ({invalid_option , Key , Val }, [Name , Options0 ])
8589 end
8690 end ,
8791 Options0
8892 ),
93+ % Check that if one of dist_listen_min and dist_listen_max are configured, both are configured.
94+ % And verify dist_listen_max is larger or equal to dist_listen_min.
95+ ok =
96+ case {maps :is_key (dist_listen_min , Options0 ), maps :is_key (dist_listen_max , Options0 )} of
97+ {true , false } ->
98+ error (missing_dist_listen_max , [Name , Options0 ]);
99+ {false , true } ->
100+ error (missing_dist_listen_min , [Name , Options0 ]);
101+ {true , true } ->
102+ Min = maps :get (dist_listen_min , Options0 ),
103+ Max = maps :get (dist_listen_max , Options0 ),
104+ if
105+ Min > Max -> error (invalid_port_range , [Name , Options0 ]);
106+ true -> ok
107+ end ;
108+ _ ->
109+ ok
110+ end ,
89111 Options1 = Options0 #{name => Name },
90112 Options2 = split_name (Options1 ),
91113 net_kernel_sup :start (Options2 );
@@ -189,13 +211,16 @@ init(Options) ->
189211 process_flag (trap_exit , true ),
190212 LongNames = maps :get (name_domain , Options , longnames ) =:= longnames ,
191213 ProtoDist = maps :get (proto_dist , Options , socket_dist ),
214+ DistPortMin = maps :get (dist_listen_min , Options , 0 ),
215+ DistPortMax = maps :get (dist_listen_max , Options , 0 ),
192216 Name = maps :get (name , Options ),
193217 Node = maps :get (node , Options ),
194218 Cookie = crypto :strong_rand_bytes (16 ),
195219 TickInterval = (? NET_TICK_TIME * 1000 ) div ? NET_TICK_INTENSITY ,
196220 Self = self (),
197221 Ticker = spawn_link (fun () -> ticker (Self , TickInterval ) end ),
198- case ProtoDist :listen (Name ) of
222+ % Try ports in range until one succeeds
223+ case try_listen_ports (ProtoDist , Name , DistPortMin , DistPortMax ) of
199224 {ok , {Listen , _Address , Creation }} ->
200225 true = erlang :setnode (Node , Creation ),
201226 AcceptPid = ProtoDist :accept (Listen ),
@@ -214,6 +239,18 @@ init(Options) ->
214239 {stop , Reason }
215240 end .
216241
242+ % % @hidden
243+ try_listen_ports (ProtoDist , Name , DistPortMin , DistPortMax ) ->
244+ try_listen_ports (ProtoDist , Name , DistPortMin , DistPortMax , DistPortMin ).
245+
246+ try_listen_ports (_ProtoDist , _Name , _DistPortMin , DistPortMax , Port ) when Port > DistPortMax ->
247+ {error , no_port_available };
248+ try_listen_ports (ProtoDist , Name , DistPortMin , DistPortMax , Port ) ->
249+ case ProtoDist :listen (Name , Port ) of
250+ {ok , _ } = Success -> Success ;
251+ {error , _ } -> try_listen_ports (ProtoDist , Name , DistPortMin , DistPortMax , Port + 1 )
252+ end .
253+
217254% % @hidden
218255handle_call (get_state , _From , # state {longnames = Longnames } = State ) ->
219256 NameDomain =
0 commit comments