%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 1996-2023. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. %% %% %CopyrightEnd% %% -module(net_kernel). -behaviour(gen_server). -define(nodedown(N, State), verbose({?MODULE, ?LINE, nodedown, N}, 1, State)). %%-define(nodeup(N, State), verbose({?MODULE, ?LINE, nodeup, N}, 1, State)). %%-define(dist_debug, true). -ifdef(dist_debug). -define(debug(Term), erlang:display(Term)). -else. -define(debug(Term), ok). -endif. -ifdef(dist_debug). -define(connect_failure(Node,Term), io:format("Net Kernel 2: Failed connection to node ~p, reason ~p~n", [Node,Term])). -else. -define(connect_failure(Node,Term),noop). -endif. %% Default ticktime change transition period in seconds -define(DEFAULT_TRANSITION_PERIOD, 60). %-define(TCKR_DBG, 1). -ifdef(TCKR_DBG). -define(tckr_dbg(X), erlang:display({?LINE, X})). -else. -define(tckr_dbg(X), ok). -endif. %% Documented API functions. -export([allow/1, allowed/0, connect_node/1, monitor_nodes/1, monitor_nodes/2, setopts/2, getopts/2, start/2, start/1, stop/0]). %% Exports for internal use. -export([start_link/1, kernel_apply/3, longnames/0, nodename/0, protocol_childspecs/0, epmd_module/0, get_state/0]). -export([disconnect/1, async_disconnect/1, passive_cnct/1]). -export([hidden_connect_node/1]). -export([set_net_ticktime/1, set_net_ticktime/2, get_net_ticktime/0]). -export([node_info/1, node_info/2, nodes_info/0, connecttime/0, i/0, i/1, verbose/1]). -export([publish_on_node/1]). %% Internal exports for spawning processes. -export([do_spawn/3, spawn_func/6, ticker/2, ticker_loop/2, aux_ticker/4]). -export([init/1,handle_call/3,handle_cast/2,handle_info/2, terminate/2,code_change/3]). -export([passive_connect_monitor/2]). -import(error_logger,[error_msg/2]). -type node_name_type() :: static | dynamic. -record(state, { node, %% The node name including hostname type, %% long or short names tick, %% tick information connecttime, %% the connection setuptime. connections, %% table of connections conn_owners = #{}, %% Map of connection owner pids, dist_ctrlrs = #{}, %% Map of dist controllers (local ports or pids), pend_owners = #{}, %% Map of potential owners listen, %% list of #listen allowed, %% list of allowed nodes in a restricted system verbose = 0, %% level of verboseness dyn_name_pool = #{}, %% Reusable remote node names: #{Host => [{Name,Creation}]} supervisor, %% Our supervisor (net_sup | net_sup_dynamic | {restart,Restarter}) req_map = #{} %% Map for outstanding async requests }). -record(listen, { listen, %% listen socket accept, %% accepting pid address, %% #net_address module %% proto module }). -define(LISTEN_ID, #listen.listen). -define(ACCEPT_ID, #listen.accept). -type connection_state() :: pending | up | up_pending. -type connection_type() :: normal | hidden. -include("net_address.hrl"). %% Relaxed typing to allow ets:select without Dialyzer complains. -record(connection, { node :: node() | '_', %% remote node name conn_id, %% Connection identity state :: connection_state() | '_', owner :: pid() | '_', %% owner pid ctrlr, %% Controller port or pid pending_owner :: pid() | '_' | undefined, %% possible new owner address = #net_address{} :: #net_address{} | '_', waiting = [], %% queued processes type :: connection_type() | '_', remote_name_type :: node_name_type() | '_', creation :: integer() | '_' | undefined, %% only set if remote_name_type == dynamic named_me = false :: boolean() | '_' %% did peer gave me my node name? }). -record(barred_connection, { node %% remote node name }). -record(tick, {ticker :: pid(), %% ticker time :: pos_integer(), %% net tick time (ms) intensity :: 4..1000 %% ticks until timout }). -record(tick_change, {ticker :: pid(), %% ticker time :: pos_integer(), %% net tick time (ms) intensity :: 4..1000, %% ticks until timout how :: 'longer' | 'shorter' %% What type of change }). %% Default connection setup timeout in milliseconds. %% This timeout is set for every distributed action during %% the connection setup. -define(SETUPTIME, 7000). %%% BIF -export([dflag_unicode_io/1]). -spec dflag_unicode_io(pid()) -> boolean(). dflag_unicode_io(_) -> erlang:nif_error(undef). %%% End of BIF %% Interface functions kernel_apply(M,F,A) -> request({apply,M,F,A}). -spec allow(Nodes) -> ok | error when Nodes :: [node()]. allow(Nodes) -> request({allow, Nodes}). allowed() -> request(allowed). longnames() -> request(longnames). nodename() -> request(nodename). -spec get_state() -> #{started => no | static | dynamic, name => atom(), name_type => static | dynamic, name_domain => shortnames | longnames}. get_state() -> case whereis(net_kernel) of undefined -> case retry_request_maybe(get_state) of ignored -> #{started => no}; Reply -> Reply end; _ -> request(get_state) end. -spec stop() -> ok | {error, Reason} when Reason :: not_allowed | not_found. stop() -> erl_distribution:stop(). -type node_info() :: {address, #net_address{}} | {type, connection_type()} | {in, non_neg_integer()} | {out, non_neg_integer()} | {owner, pid()} | {state, connection_state()}. -spec node_info(node()) -> {ok, [node_info()]} | {error, bad_node}. node_info(Node) -> get_node_info(Node). -spec node_info(node(), address) -> {ok, Address} | {error, bad_node} when Address :: #net_address{}; (node(), type) -> {ok, Type} | {error, bad_node} when Type :: connection_type(); (node(), in | out) -> {ok, Bytes} | {error, bad_node} when Bytes :: non_neg_integer(); (node(), owner) -> {ok, Owner} | {error, bad_node} when Owner :: pid(); (node(), state) -> {ok, State} | {error, bad_node} when State :: connection_state(). %(node(), term()) -> {error, invalid_key} | {error, bad_node}. node_info(Node, Key) -> get_node_info(Node, Key). -spec nodes_info() -> {ok, [{node(), [node_info()]}]}. nodes_info() -> get_nodes_info(). i() -> print_info(). i(Node) -> print_info(Node). verbose(Level) when is_integer(Level) -> request({verbose, Level}). -spec set_net_ticktime(NetTicktime, TransitionPeriod) -> Res when NetTicktime :: pos_integer(), TransitionPeriod :: non_neg_integer(), Res :: unchanged | change_initiated | {ongoing_change_to, NewNetTicktime}, NewNetTicktime :: pos_integer(). set_net_ticktime(T, TP) when is_integer(T), T > 0, is_integer(TP), TP >= 0 -> ticktime_res(request({new_ticktime, T*1000, TP*1000})). -spec set_net_ticktime(NetTicktime) -> Res when NetTicktime :: pos_integer(), Res :: unchanged | change_initiated | {ongoing_change_to, NewNetTicktime}, NewNetTicktime :: pos_integer(). set_net_ticktime(T) when is_integer(T) -> set_net_ticktime(T, ?DEFAULT_TRANSITION_PERIOD). -spec get_net_ticktime() -> Res when Res :: NetTicktime | {ongoing_change_to, NetTicktime} | ignored, NetTicktime :: pos_integer(). get_net_ticktime() -> ticktime_res(request(ticktime)). %% The monitor_nodes() feature has been moved into the emulator. %% The feature is reached via (intentionally) undocumented process %% flags (we may want to move it elsewhere later). In order to easily %% be backward compatible, errors are created here when process_flag() %% fails. -spec monitor_nodes(Flag) -> ok | Error when Flag :: boolean(), Error :: error | {error, term()}. monitor_nodes(Flag) -> case catch process_flag(monitor_nodes, Flag) of N when is_integer(N) -> ok; _ -> mk_monitor_nodes_error(Flag, []) end. -spec monitor_nodes(Flag, Options) -> ok | Error when Flag :: boolean(), Options :: OptionsList | OptionsMap, OptionsList :: [ListOption], ListOption :: connection_id | {node_type, NodeType} | nodedown_reason, OptionsMap :: #{connection_id => boolean(), node_type => NodeType, nodedown_reason => boolean()}, NodeType :: visible | hidden | all, Error :: error | {error, term()}. monitor_nodes(Flag, Opts) -> try MapOpts = if is_map(Opts) -> error = maps:find(list, Opts), Opts; is_list(Opts) -> lists:foldl(fun (nodedown_reason, Acc) -> Acc#{nodedown_reason => true}; (connection_id, Acc) -> Acc#{connection_id => true}; ({node_type, Val}, Acc) -> case maps:find(node_type, Acc) of error -> ok; {ok, Val} -> ok end, Acc#{node_type => Val} end, #{list => true}, Opts) end, true = is_integer(process_flag({monitor_nodes, MapOpts}, Flag)), ok catch _:_ -> mk_monitor_nodes_error(Flag, Opts) end. %% ... ticktime_res({A, I}) when is_atom(A), is_integer(I) -> {A, I div 1000}; ticktime_res(I) when is_integer(I) -> I div 1000; ticktime_res(A) when is_atom(A) -> A. %% Called though BIF's %%% Long timeout if blocked (== barred), only affects nodes with %%% {dist_auto_connect, once} set. passive_cnct(Node) -> case request({passive_cnct, Node}) of ignored -> false; Other -> Other end. disconnect(Node) -> request({disconnect, Node}). async_disconnect(Node) -> gen_server:cast(net_kernel, {async_disconnect, Node}). %% Should this node publish itself on Node? publish_on_node(Node) when is_atom(Node) -> global_group:publish(persistent_term:get({?MODULE, publish_type}, hidden), Node). -spec connect_node(Node) -> boolean() | ignored when Node :: node(). %% explicit connects connect_node(Node) when is_atom(Node) -> request({connect, normal, Node}). hidden_connect_node(Node) when is_atom(Node) -> request({connect, hidden, Node}). passive_connect_monitor(From, Node) -> ok = monitor_nodes(true,[{node_type,all}]), Reply = case lists:member(Node,nodes([connected])) of true -> true; _ -> receive {nodeup,Node,_} -> true after connecttime() -> false end end, ok = monitor_nodes(false,[{node_type,all}]), {Pid, Tag} = From, erlang:send(Pid, {Tag, Reply}). %% If the net_kernel isn't running we ignore all requests to the %% kernel, thus basically accepting them :-) request(Req) -> case whereis(net_kernel) of P when is_pid(P) -> try gen_server:call(net_kernel,Req,infinity) catch exit:{Reason,_} when Reason =:= noproc; Reason =:= shutdown; Reason =:= killed -> retry_request_maybe(Req) end; _ -> retry_request_maybe(Req) end. retry_request_maybe(Req) -> case erts_internal:dynamic_node_name() of true -> %% net_kernel must be restarting due to lost connection %% toward the node that named us. %% We want reconnection attempts to succeed so we wait and retry. receive after 100 -> ok end, request(Req); false -> ignored end. %% This function is used to dynamically start the %% distribution. -spec start(Name, Options) -> {ok, pid()} | {error, Reason} when Options :: #{name_domain => NameDomain, net_ticktime => NetTickTime, net_tickintensity => NetTickIntensity, dist_listen => boolean(), hidden => boolean()}, Name :: atom(), NameDomain :: shortnames | longnames, NetTickTime :: pos_integer(), NetTickIntensity :: 4..1000, Reason :: {already_started, pid()} | term(). start(Name, Options) when is_atom(Name), is_map(Options) -> try maps:foreach(fun (name_domain, Val) when Val == shortnames; Val == longnames -> ok; (net_ticktime, Val) when is_integer(Val), Val > 0 -> ok; (net_tickintensity, Val) when is_integer(Val), 4 =< Val, Val =< 1000 -> ok; (dist_listen, Val) when is_boolean(Val) -> ok; (hidden, Val) when is_boolean(Val) -> ok; (Opt, Val) -> error({invalid_option, Opt, Val}) end, Options) catch error:Reason -> error(Reason, [Name, Options]) end, erl_distribution:start(Options#{name => Name}); start(Name, Options) when is_map(Options) -> error(invalid_name, [Name, Options]); start(Name, Options) -> error(invalid_options, [Name, Options]). -spec start(Options) -> {ok, pid()} | {error, Reason} when Options :: nonempty_list(Name | NameDomain | TickTime), Name :: atom(), NameDomain :: shortnames | longnames, TickTime :: pos_integer(), Reason :: {already_started, pid()} | term(). start([Name]) when is_atom(Name) -> start([Name, longnames, 15000]); start([Name, NameDomain]) when is_atom(Name), is_atom(NameDomain) -> start([Name, NameDomain, 15000]); start([Name, NameDomain, TickTime]) when is_atom(Name), is_atom(NameDomain), is_integer(TickTime), TickTime > 0 -> %% NetTickTime is in seconds. TickTime is time in milliseconds %% between ticks when net tick intensity is 4. We round upwards... NetTickTime = ((TickTime*4-1) div 1000)+1, start(Name, #{name_domain => NameDomain, net_ticktime => NetTickTime, net_tickintensity => 4}). %% This is the main startup routine for net_kernel (only for internal %% use) by the Kernel application. start_link(StartOpts) -> case gen_server:start_link({local, net_kernel}, ?MODULE, make_init_opts(StartOpts), []) of {ok, Pid} -> {ok, Pid}; {error, {already_started, Pid}} -> {ok, Pid}; _Error -> exit(nodistribution) end. make_init_opts(Opts) -> %% Net tick time given in seconds, but kept in milliseconds... NTT1 = case maps:find(net_ticktime, Opts) of {ok, NTT0} -> NTT0*1000; error -> case application:get_env(kernel, net_ticktime) of {ok, NTT0} when is_integer(NTT0), NTT0 < 1 -> 1000; {ok, NTT0} when is_integer(NTT0) -> NTT0*1000; _ -> 60000 end end, NTI = case maps:find(net_tickintensity, Opts) of {ok, NTI0} -> NTI0; error -> case application:get_env(kernel, net_tickintensity) of {ok, NTI0} when is_integer(NTI0), NTI0 < 4 -> 4; {ok, NTI0} when is_integer(NTI0), NTI0 > 1000 -> 1000; {ok, NTI0} when is_integer(NTI0) -> NTI0; _ -> 4 end end, %% Net tick time needs to be a multiple of net tick intensity; %% round net tick time upwards if not... NTT = if NTT1 rem NTI =:= 0 -> NTT1; true -> ((NTT1 div NTI) + 1) * NTI end, ND = case maps:find(name_domain, Opts) of {ok, ND0} -> ND0; error -> longnames end, DL = case split_node(maps:get(name, Opts)) of {"undefined", _} -> %% dynamic node name implies dist_listen=false false; _ -> case maps:find(dist_listen, Opts) of error -> dist_listen_argument(); {ok, false} -> false; _ -> true end end, H = case DL of false -> %% dist_listen=false implies hidden=true true; true -> case maps:find(hidden, Opts) of error -> hidden_argument(); {ok, true} -> true; _ -> false end end, Opts#{net_ticktime => NTT, net_tickintensity => NTI, name_domain => ND, dist_listen => DL, hidden => H}. init(#{name := Name, name_domain := NameDomain, net_ticktime := NetTicktime, net_tickintensity := NetTickIntensity, clean_halt := CleanHalt, supervisor := Supervisor, dist_listen := DistListen, hidden := Hidden}) -> %% We enable async_dist so that we won't need to do the %% nosuspend/spawn trick which just cause even larger %% memory consumption... _ = process_flag(async_dist, true), process_flag(trap_exit,true), persistent_term:put({?MODULE, publish_type}, if Hidden -> hidden; true -> normal end), case init_node(Name, NameDomain, CleanHalt, DistListen) of {ok, Node, Listeners} -> process_flag(priority, max), TickInterval = NetTicktime div NetTickIntensity, Ticker = spawn_link(net_kernel, ticker, [self(), TickInterval]), {ok, #state{node = Node, type = NameDomain, tick = #tick{ticker = Ticker, time = NetTicktime, intensity = NetTickIntensity}, connecttime = connecttime(), connections = ets:new(sys_dist,[named_table, protected, {keypos, #connection.node}]), listen = Listeners, allowed = [], verbose = 0, supervisor = Supervisor }}; Error -> _ = persistent_term:erase({?MODULE, publish_type}), erts_internal:dynamic_node_name(false), {stop, Error} end. do_auto_connect_1(Node, ConnId, From, State) -> case ets:lookup(sys_dist, Node) of [#barred_connection{}] -> case ConnId of passive_cnct -> spawn(?MODULE,passive_connect_monitor,[From,Node]), {noreply, State}; _ -> erts_internal:abort_pending_connection(Node, ConnId), {reply, false, State} end; ConnLookup -> do_auto_connect_2(Node, ConnId, From, State, ConnLookup) end. do_auto_connect_2(Node, passive_cnct, From, State, ConnLookup) -> try erts_internal:new_connection(Node) of ConnId -> do_auto_connect_2(Node, ConnId, From, State, ConnLookup) catch _:_ -> error_logger:error_msg("~n** Cannot get connection id for node ~w~n", [Node]), {reply, false, State} end; do_auto_connect_2(Node, ConnId, From, State, ConnLookup) -> case ConnLookup of [#connection{conn_id=ConnId, state = up}] -> {reply, true, State}; [#connection{conn_id=ConnId, waiting=Waiting}=Conn] -> case From of noreply -> ok; _ -> ets:insert(sys_dist, Conn#connection{waiting = [From|Waiting]}) end, {noreply, State}; _ -> case application:get_env(kernel, dist_auto_connect) of {ok, never} -> ?connect_failure(Node,{dist_auto_connect,never}), erts_internal:abort_pending_connection(Node, ConnId), {reply, false, State}; %% This might happen due to connection close %% not being propagated to user space yet. %% Save the day by just not connecting... {ok, once} when ConnLookup =/= [], (hd(ConnLookup))#connection.state =:= up -> ?connect_failure(Node,{barred_connection, ets:lookup(sys_dist, Node)}), {reply, false, State}; _ -> case setup(Node, ConnId, normal, From, State) of {ok, SetupPid} -> Owners = State#state.conn_owners, {noreply,State#state{conn_owners=Owners#{SetupPid => Node}}}; _Error -> ?connect_failure(Node, {setup_call, failed, _Error}), erts_internal:abort_pending_connection(Node, ConnId), {reply, false, State} end end end. do_explicit_connect([#connection{conn_id = ConnId, state = up}], _, _, ConnId, _From, State) -> {reply, true, State}; do_explicit_connect([#connection{conn_id = ConnId}=Conn], _, _, ConnId, From, State) when Conn#connection.state =:= pending; Conn#connection.state =:= up_pending -> Waiting = Conn#connection.waiting, ets:insert(sys_dist, Conn#connection{waiting = [From|Waiting]}), {noreply, State}; do_explicit_connect([#barred_connection{}], Type, Node, ConnId, From , State) -> %% Barred connection only affects auto_connect, ignore it. do_explicit_connect([], Type, Node, ConnId, From , State); do_explicit_connect(_ConnLookup, Type, Node, ConnId, From , State) -> case setup(Node,ConnId,Type,From,State) of {ok, SetupPid} -> Owners = State#state.conn_owners, {noreply,State#state{conn_owners=Owners#{SetupPid => Node}}}; _Error -> ?connect_failure(Node, {setup_call, failed, _Error}), {reply, false, State} end. %% ------------------------------------------------------------ %% handle_call. %% ------------------------------------------------------------ %% %% Passive auto-connect to Node. %% The response is delayed until the connection is up and running. %% handle_call({passive_cnct, Node}, From, State) when Node =:= node() -> async_reply({reply, true, State}, From); handle_call({passive_cnct, Node}, From, State) -> verbose({passive_cnct, Node}, 1, State), R = do_auto_connect_1(Node, passive_cnct, From, State), return_call(R, From); %% %% Explicit connect %% The response is delayed until the connection is up and running. %% handle_call({connect, _, Node}, From, State) when Node =:= node() -> async_reply({reply, true, State}, From); handle_call({connect, _Type, _Node}, _From, #state{supervisor = {restart,_}}=State) -> {noreply, State}; handle_call({connect, Type, Node}, From, State) -> verbose({connect, Type, Node}, 1, State), ConnLookup = ets:lookup(sys_dist, Node), R = try erts_internal:new_connection(Node) of ConnId -> R1 = do_explicit_connect(ConnLookup, Type, Node, ConnId, From, State), case R1 of {reply, true, _S} -> %% already connected ok; {noreply, _S} -> %% connection pending ok; {reply, false, _S} -> %% connection refused erts_internal:abort_pending_connection(Node, ConnId) end, R1 catch _:_ -> error_logger:error_msg("~n** Cannot get connection id for node ~w~n", [Node]), {reply, false, State} end, return_call(R, From); %% %% Close the connection to Node. %% handle_call({disconnect, Node}, From, State) when Node =:= node() -> async_reply({reply, false, State}, From); handle_call({disconnect, Node}, From, State) -> verbose({disconnect, Node}, 1, State), {Reply, State1} = do_disconnect(Node, State, false), async_reply({reply, Reply, State1}, From); %% %% The spawn/4 BIF ends up here. %% handle_call({spawn,M,F,A,Gleader},{From,Tag},State) when is_pid(From) -> do_spawn([no_link,{From,Tag},M,F,A,Gleader],[],State); %% %% The spawn_link/4 BIF ends up here. %% handle_call({spawn_link,M,F,A,Gleader},{From,Tag},State) when is_pid(From) -> do_spawn([link,{From,Tag},M,F,A,Gleader],[],State); %% %% The spawn_opt/5 BIF ends up here. %% handle_call({spawn_opt,M,F,A,O,L,Gleader},{From,Tag},State) when is_pid(From) -> do_spawn([L,{From,Tag},M,F,A,Gleader],O,State); %% %% Only allow certain nodes. %% handle_call({allow, Nodes}, From, State) -> case all_atoms(Nodes) of true -> Allowed = State#state.allowed, async_reply({reply,ok,State#state{allowed = Allowed ++ Nodes}}, From); false -> async_reply({reply,error,State}, From) end; handle_call(allowed, From, #state{allowed = Allowed} = State) -> async_reply({reply,{ok,Allowed},State}, From); %% %% authentication, used by auth. Simply works as this: %% if the message comes through, the other node IS authorized. %% handle_call({is_auth, _Node}, From, State) -> async_reply({reply,yes,State}, From); %% %% Not applicable any longer !? %% handle_call({apply,_Mod,_Fun,_Args}, {Pid, _Tag} = From, State) when is_pid(Pid), node(Pid) =:= node() -> async_reply({reply, not_implemented, State}, From); handle_call(longnames, From, State) -> async_reply({reply, get(longnames), State}, From); handle_call(nodename, From, State) -> async_reply({reply, State#state.node, State}, From); handle_call({verbose, Level}, From, State) -> async_reply({reply, State#state.verbose, State#state{verbose = Level}}, From); %% %% Set new ticktime %% %% The tick field of the state contains either a #tick{} or a %% #tick_change{} record. handle_call(ticktime, From, #state{tick = #tick{time = T}} = State) -> async_reply({reply, T, State}, From); handle_call(ticktime, From, #state{tick = #tick_change{time = T}} = State) -> async_reply({reply, {ongoing_change_to, T}, State}, From); handle_call({new_ticktime,T,_TP}, From, #state{tick = #tick{time = T}} = State) -> ?tckr_dbg(no_tick_change), async_reply({reply, unchanged, State}, From); handle_call({new_ticktime,T,TP}, From, #state{tick = #tick{ticker = Tckr, time = OT, intensity = I}} = State) -> ?tckr_dbg(initiating_tick_change), %% We need to preserve tick intensity and net tick time needs to be a %% multiple of tick intensity... {NT, NIntrvl} = case T < I of true -> %% Max 1 tick per millisecond implies that %% minimum net tick time equals intensity... {I, 1}; _ -> NIntrvl0 = T div I, case T rem I of 0 -> {T, NIntrvl0}; _ -> %% Round net tick time upwards... {(NIntrvl0+1)*I, NIntrvl0+1} end end, case NT == OT of true -> async_reply({reply, unchanged, State}, From); false -> start_aux_ticker(NIntrvl, OT div I, TP), How = case NT > OT of true -> ?tckr_dbg(longer_ticktime), Tckr ! {new_ticktime, NIntrvl}, longer; false -> ?tckr_dbg(shorter_ticktime), shorter end, async_reply({reply, change_initiated, State#state{tick = #tick_change{ticker = Tckr, time = NT, intensity = I, how = How}}}, From) end; handle_call({new_ticktime,_T,_TP}, From, #state{tick = #tick_change{time = T}} = State) -> async_reply({reply, {ongoing_change_to, T}, State}, From); handle_call({setopts, new, Opts}, From, State) -> setopts_new(Opts, From, State); handle_call({setopts, Node, Opts}, From, State) -> opts_node(setopts, Node, Opts, From, State); handle_call({getopts, Node, Opts}, From, State) -> opts_node(getopts, Node, Opts, From, State); handle_call(get_state, From, State) -> Started = case State#state.supervisor of net_sup -> static; _ -> dynamic end, {NameType,Name} = case {erts_internal:dynamic_node_name(), node()} of {false, Node} -> {static, Node}; {true, nonode@nohost} -> {dynamic, undefined}; {true, Node} -> {dynamic, Node} end, NameDomain = case get(longnames) of true -> longnames; false -> shortnames end, Return = #{started => Started, name_type => NameType, name => Name, name_domain => NameDomain}, async_reply({reply, Return, State}, From); handle_call(_Msg, _From, State) -> {noreply, State}. %% ------------------------------------------------------------ %% handle_cast. %% ------------------------------------------------------------ handle_cast({async_disconnect, Node}, State) when Node =:= node() -> {noreply, State}; handle_cast({async_disconnect, Node}, State) -> verbose({async_disconnect, Node}, 1, State), {_Reply, State1} = do_disconnect(Node, State, true), {noreply, State1}; handle_cast(_, State) -> {noreply,State}. %% ------------------------------------------------------------ %% code_change. %% ------------------------------------------------------------ code_change(_OldVsn, State, _Extra) -> {ok,State}. %% ------------------------------------------------------------ %% terminate. %% ------------------------------------------------------------ terminate(Reason, State) -> case State of #state{supervisor = {restart, _}} -> ok; _ -> _ = persistent_term:erase({?MODULE, publish_type}), erts_internal:dynamic_node_name(false) end, case Reason of no_network -> ok; _ -> lists:foreach( fun(#listen {listen = Listen,module = Mod}) -> case Listen of undefined -> ignore; _ -> Mod:close(Listen) end end, State#state.listen) end, lists:foreach(fun(Node) -> ?nodedown(Node, State) end, get_nodes_up_normal() ++ [node()]). %% ------------------------------------------------------------ %% handle_info. %% ------------------------------------------------------------ %% %% Asynchronous auto connect request %% handle_info({auto_connect,Node, DHandle}, State) -> verbose({auto_connect, Node, DHandle}, 1, State), ConnId = DHandle, NewState = case do_auto_connect_1(Node, ConnId, noreply, State) of {noreply, S} -> %% Pending connection S; {reply, true, S} -> %% Already connected S; {reply, false, S} -> %% Connection refused S end, {noreply, NewState}; %% %% accept a new connection. %% handle_info({accept,AcceptPid,Socket,Family,Proto}=Accept, State) -> case get_proto_mod(Family,Proto,State#state.listen) of {ok, Mod} -> Pid = Mod:accept_connection(AcceptPid, Socket, State#state.node, State#state.allowed, State#state.connecttime), verbose({Accept,Pid}, 2, State), AcceptPid ! {self(), controller, Pid}, {noreply,State}; _ -> verbose({Accept,unsupported_protocol}, 2, State), AcceptPid ! {self(), unsupported_protocol}, {noreply, State} end; %% %% New dist controller has been registered %% handle_info({dist_ctrlr, Ctrlr, Node, SetupPid} = Msg, #state{dist_ctrlrs = DistCtrlrs} = State) -> case ets:lookup(sys_dist, Node) of [Conn] when (Conn#connection.state =:= pending) andalso (Conn#connection.owner =:= SetupPid) andalso (Conn#connection.ctrlr =:= undefined) andalso (is_port(Ctrlr) orelse is_pid(Ctrlr)) andalso (node(Ctrlr) == node()) -> link(Ctrlr), verbose(Msg, 2, State), ets:insert(sys_dist, Conn#connection{ctrlr = Ctrlr}), {noreply, State#state{dist_ctrlrs = DistCtrlrs#{Ctrlr => Node}}}; _ -> error_msg("Net kernel got ~tw~n",[Msg]), {noreply, State} end; %% %% A node has successfully been connected. %% handle_info({SetupPid, {nodeup,Node,Address,Type,NamedMe} = Nodeup}, #state{tick = Tick} = State) -> case ets:lookup(sys_dist, Node) of [Conn] when (Conn#connection.state =:= pending) andalso (Conn#connection.owner =:= SetupPid) andalso (Conn#connection.ctrlr /= undefined) -> ets:insert(sys_dist, Conn#connection{state = up, address = Address, waiting = [], type = Type, named_me = NamedMe}), TickIntensity = case Tick of #tick{intensity = TI} -> TI; #tick_change{intensity = TI} -> TI end, SetupPid ! {self(), inserted, TickIntensity}, reply_waiting(Node,Conn#connection.waiting, true), State1 = case NamedMe of true -> State#state{node = node()}; false -> State end, verbose(Nodeup, 1, State1), verbose({nodeup,Node,SetupPid,Conn#connection.ctrlr}, 2, State1), {noreply, State1}; _ -> SetupPid ! {self(), bad_request}, {noreply, State} end; %% %% Mark a node as pending (accept) if not busy. %% handle_info({AcceptPid, {accept_pending,MyNode,NodeOrHost,Type}}, State0) -> {NameType, Node, Creation, ConnLookup, State} = ensure_node_name(NodeOrHost, State0), case ConnLookup of [#connection{state=pending}=Conn] -> if MyNode > Node -> AcceptPid ! {self(),{accept_pending,nok_pending}}, verbose({accept_pending_nok, Node, AcceptPid}, 2, State), {noreply,State}; true -> %% %% A simultaneous connect has been detected and we want to %% change pending process. %% OldOwner = Conn#connection.owner, case maps:is_key(OldOwner, State#state.conn_owners) of true -> verbose({remark,OldOwner,AcceptPid}, 2, State), ?debug({net_kernel, remark, old, OldOwner, new, AcceptPid}), exit(OldOwner, remarked), receive {'EXIT', OldOwner, _} = Exit -> verbose(Exit, 2, State), true end; false -> verbose( {accept_pending, OldOwner, inconsistency}, 2, State), ok % Owner already exited end, ets:insert(sys_dist, Conn#connection{owner = AcceptPid}), AcceptPid ! {self(),{accept_pending,ok_pending}}, Owners = maps:remove(OldOwner, State#state.conn_owners), {noreply, State#state{conn_owners=Owners#{AcceptPid => Node}}} end; [#connection{state=up}=Conn] -> AcceptPid ! {self(), {accept_pending, up_pending}}, ets:insert(sys_dist, Conn#connection { pending_owner = AcceptPid, state = up_pending }), Pend = State#state.pend_owners, {noreply, State#state { pend_owners = Pend#{AcceptPid => Node} }}; [#connection{state=up_pending}] -> AcceptPid ! {self(), {accept_pending, already_pending}}, {noreply, State}; _ -> try erts_internal:new_connection(Node) of ConnId -> ets:insert(sys_dist, #connection{node = Node, conn_id = ConnId, state = pending, owner = AcceptPid, type = Type, remote_name_type = NameType, creation = Creation}), Ret = case NameType of static -> ok; dynamic-> {ok, Node, Creation} end, AcceptPid ! {self(),{accept_pending,Ret}}, Owners = State#state.conn_owners, {noreply, State#state{conn_owners = Owners#{AcceptPid => Node}}} catch _:_ -> error_logger:error_msg("~n** Cannot get connection id for node ~w~n", [Node]), AcceptPid ! {self(),{accept_pending,nok_pending}}, {noreply, State} end end; handle_info({SetupPid, {is_pending, Node}}, State) -> Reply = case maps:get(SetupPid, State#state.conn_owners, undefined) of Node -> true; _ -> false end, SetupPid ! {self(), {is_pending, Reply}}, {noreply, State}; handle_info({AcceptPid, {wait_pending, Node}}, State) -> case get_conn(Node) of {ok, #connection{state = up_pending, ctrlr = OldCtrlr, pending_owner = AcceptPid}} -> %% Kill old controller to make sure new connection setup %% does not get stuck. ?debug({net_kernel, wait_pending, kill, OldCtrlr, new, AcceptPid}), exit(OldCtrlr, wait_pending); _ -> %% Old connnection maybe already gone ignore end, %% Exiting controller will trigger {Kernel,pending} reply %% in up_pending_nodedown() {noreply, State}; %% %% Responses to asynchronous requests we've made... %% handle_info({ReqId, Reply}, #state{req_map = ReqMap} = S) when is_map_key(ReqId, ReqMap) -> handle_async_response(reply, ReqId, Reply, S); handle_info({'DOWN', ReqId, process, _Pid, Reason}, #state{req_map = ReqMap} = S) when is_map_key(ReqId, ReqMap) -> handle_async_response(down, ReqId, Reason, S); %% %% Handle different types of process terminations. %% handle_info({'EXIT', From, Reason}, State) -> handle_exit(From, Reason, State); %% %% Handle badcookie and badname messages ! %% handle_info({From,registered_send,To,Mess},State) -> send(From,To,Mess), {noreply,State}; %% badcookies SHOULD not be sent %% (if someone does erlang:set_cookie(node(),foo) this may be) handle_info({From,badcookie,_To,_Mess}, State) -> error_logger:error_msg("~n** Got OLD cookie from ~w~n", [getnode(From)]), {_Reply, State1} = do_disconnect(getnode(From), State, false), {noreply,State1}; %% %% Tick all connections. %% handle_info(tick, State) -> ?tckr_dbg(tick), maps:foreach(fun (Pid, _Node) -> Pid ! {self(), tick} end, State#state.conn_owners), {noreply,State}; handle_info(aux_tick, State) -> ?tckr_dbg(aux_tick), maps:foreach(fun (Pid, _Node) -> Pid ! {self(), aux_tick} end, State#state.conn_owners), {noreply,State}; handle_info(transition_period_end, #state{tick = #tick_change{ticker = Tckr, time = T, intensity = I, how = How}} = State) -> ?tckr_dbg(transition_period_ended), case How of shorter -> Interval = T div I, Tckr ! {new_ticktime, Interval}, ok; _ -> ok end, {noreply,State#state{tick = #tick{ticker = Tckr, time = T, intensity = I}}}; handle_info(X, State) -> error_msg("Net kernel got ~tw~n",[X]), {noreply,State}. ensure_node_name(Node, State) when is_atom(Node) -> {static, Node, undefined, ets:lookup(sys_dist, Node), State}; ensure_node_name(Host, State0) when is_list(Host) -> case string:split(Host, "@", all) of [Host] -> {Node, Creation, State1} = generate_node_name(Host, State0), case ets:lookup(sys_dist, Node) of [#connection{}] -> %% Either a static named connection setup used a recycled %% dynamic name or we have an unlikely random dynamic %% name clash. Either way try again. ensure_node_name(Host, State1); ConnLookup -> {dynamic, Node, Creation, ConnLookup, State1} end; _ -> {error, Host, undefined, [], State0} end. generate_node_name(Host, State0) -> NamePool = State0#state.dyn_name_pool, case maps:get(Host, NamePool, []) of [] -> Name = integer_to_list(rand:uniform(1 bsl 64), 36), {list_to_atom(Name ++ "@" ++ Host), create_creation(), State0}; [{Node,Creation} | Rest] -> {Node, Creation, State0#state{dyn_name_pool = NamePool#{Host => Rest}}} end. %% ----------------------------------------------------------- %% Handle exit signals. %% We have 6 types of processes to handle. %% %% 1. The Listen process. %% 2. The Accept process. %% 3. Connection owning processes. %% 4. The ticker process. %% (5. Garbage pid.) %% %% The process type function that handled the process throws %% the handle_info return value ! %% ----------------------------------------------------------- handle_exit(Pid, Reason, State) -> catch do_handle_exit(Pid, Reason, State). do_handle_exit(Pid, Reason, State) -> listen_exit(Pid, Reason, State), accept_exit(Pid, Reason, State), conn_own_exit(Pid, Reason, State), dist_ctrlr_exit(Pid, Reason, State), pending_own_exit(Pid, Reason, State), ticker_exit(Pid, Reason, State), restarter_exit(Pid, Reason, State), verbose({'EXIT', Pid, Reason}, 2, State), {noreply,State}. listen_exit(Pid, Reason, State) -> case lists:keymember(Pid, ?LISTEN_ID, State#state.listen) of true -> verbose({listen_exit, Pid, Reason}, 2, State), error_msg("** Netkernel terminating ... **\n", []), throw({stop,no_network,State}); false -> false end. accept_exit(Pid, Reason, State) -> Listen = State#state.listen, case lists:keysearch(Pid, ?ACCEPT_ID, Listen) of {value, ListenR} -> ListenS = ListenR#listen.listen, Mod = ListenR#listen.module, verbose({accept_exit, Pid, Reason, Mod}, 2, State), AcceptPid = Mod:accept(ListenS), L = lists:keyreplace(Pid, ?ACCEPT_ID, Listen, ListenR#listen{accept = AcceptPid}), throw({noreply, State#state{listen = L}}); _ -> false end. conn_own_exit(Pid, Reason, #state{conn_owners = Owners} = State) -> case maps:get(Pid, Owners, undefined) of undefined -> false; Node -> verbose({conn_own_exit, Pid, Reason, Node}, 2, State), throw({noreply, nodedown(Pid, Node, Reason, State)}) end. dist_ctrlr_exit(Pid, Reason, #state{dist_ctrlrs = DCs} = State) -> case maps:get(Pid, DCs, undefined) of undefined -> false; Node -> verbose({dist_ctrlr_exit, Pid, Reason, Node}, 2, State), throw({noreply, nodedown(Pid, Node, Reason, State)}) end. pending_own_exit(Pid, Reason, #state{pend_owners = Pend} = State) -> case maps:get(Pid, Pend, undefined) of undefined -> false; Node -> State1 = State#state { pend_owners = maps:remove(Pid, Pend)}, case get_conn(Node) of {ok, Conn} when Conn#connection.state =:= up_pending -> verbose( {pending_own_exit, Pid, Reason, Node, up_pending}, 2, State), reply_waiting(Node,Conn#connection.waiting, true), Conn1 = Conn#connection { state = up, waiting = [], pending_owner = undefined }, ets:insert(sys_dist, Conn1); _ -> verbose({pending_own_exit, Pid, Reason, Node}, 2, State), ok end, throw({noreply, State1}) end. ticker_exit( Pid, Reason, #state{tick = #tick{ticker = Pid, time = T} = Tck} = State) -> verbose({ticker_exit, Pid, Reason, Tck}, 2, State), Tckr = restart_ticker(T), throw({noreply, State#state{tick = Tck#tick{ticker = Tckr}}}); ticker_exit( Pid, Reason, #state{tick = #tick_change{ticker = Pid, time = T} = TckCng} = State) -> verbose({ticker_exit, Pid, Reason, TckCng}, 2, State), Tckr = restart_ticker(T), throw({noreply, Reason, State#state{tick = TckCng#tick_change{ticker = Tckr}}}); ticker_exit(_, _, _) -> false. restarter_exit(Pid, Reason, State) -> case State#state.supervisor of {restart, Pid} -> verbose({restarter_exit, Pid, Reason}, 2, State), error_msg( "** Distribution restart failed, net_kernel terminating... **\n", []), throw({stop, restarter_exit, State}); _ -> false end. %% ----------------------------------------------------------- %% A node has gone down !! %% nodedown(Owner, Node, Reason, State) -> State' %% ----------------------------------------------------------- nodedown(Exited, Node, Reason, State) -> case get_conn(Node) of {ok, Conn} -> nodedown(Conn, Exited, Node, Reason, Conn#connection.type, State); _ -> State end. get_conn(Node) -> case ets:lookup(sys_dist, Node) of [Conn = #connection{}] -> {ok, Conn}; _ -> error end. delete_owner(Owner, #state{conn_owners = Owners} = State) -> State#state{conn_owners = maps:remove(Owner, Owners)}. delete_ctrlr(Ctrlr, #state{dist_ctrlrs = DCs} = State) -> State#state{dist_ctrlrs = maps:remove(Ctrlr, DCs)}. nodedown(Conn, Exited, Node, Reason, Type, State) -> case Conn#connection.state of pending -> pending_nodedown(Conn, Exited, Node, Type, State); up -> up_nodedown(Conn, Exited, Node, Reason, Type, State); up_pending -> up_pending_nodedown(Conn, Exited, Node, Reason, Type, State); _ -> State end. pending_nodedown(#connection{owner = Owner, waiting = Waiting, conn_id = CID} = Conn, Exited, Node, Type, State0) when Owner =:= Exited -> %% Owner exited! State2 = case erts_internal:abort_pending_connection(Node, CID) of false -> %% Just got connected but that message has not %% reached us yet. Wait for controller to exit and %% handle this then... State0; true -> %% Don't bar connections that have never been alive State1 = delete_connection(Conn, false, State0), reply_waiting(Node, Waiting, false), case Type of normal -> ?nodedown(Node, State1); _ -> ok end, State1 end, delete_owner(Owner, State2); pending_nodedown(#connection{owner = Owner, ctrlr = Ctrlr, waiting = Waiting} = Conn, Exited, Node, Type, State0) when Ctrlr =:= Exited -> %% Controller exited! %% %% Controller has been registered but crashed %% before sending mark up message... %% %% 'nodeup' messages has been sent by the emulator, %% so bar the connection... State1 = delete_connection(Conn, true, State0), reply_waiting(Node,Waiting, true), case Type of normal -> ?nodedown(Node, State1); _ -> ok end, delete_owner(Owner, delete_ctrlr(Ctrlr, State1)); pending_nodedown(_Conn, _Exited, _Node, _Type, State) -> State. up_pending_nodedown(#connection{owner = Owner, ctrlr = Ctrlr, pending_owner = AcceptPid} = Conn, Exited, Node, _Reason, _Type, State) when Ctrlr =:= Exited -> %% Controller exited! Conn1 = Conn#connection { owner = AcceptPid, conn_id = erts_internal:new_connection(Node), ctrlr = undefined, pending_owner = undefined, state = pending }, ets:insert(sys_dist, Conn1), AcceptPid ! {self(), pending}, Pend = maps:remove(AcceptPid, State#state.pend_owners), Owners = State#state.conn_owners, State1 = State#state{conn_owners = Owners#{AcceptPid => Node}, pend_owners = Pend}, delete_owner(Owner, delete_ctrlr(Ctrlr, State1)); up_pending_nodedown(#connection{owner = Owner}, Exited, _Node, _Reason, _Type, State) when Owner =:= Exited -> %% Owner exited! delete_owner(Owner, State); up_pending_nodedown(_Conn, _Exited, _Node, _Reason, _Type, State) -> State. up_nodedown(#connection{owner = Owner, ctrlr = Ctrlr} = Conn, Exited, Node, _Reason, Type, State0) when Ctrlr =:= Exited -> %% Controller exited! State1 = delete_connection(Conn, true, State0), case Type of normal -> ?nodedown(Node, State1); _ -> ok end, delete_owner(Owner, delete_ctrlr(Ctrlr, State1)); up_nodedown(#connection{owner = Owner}, Exited, _Node, _Reason, _Type, State) when Owner =:= Exited -> %% Owner exited! delete_owner(Owner, State); up_nodedown(_Conn, _Exited, _Node, _Reason, _Type, State) -> State. delete_connection(#connection{named_me = true}, _, State) -> restart_distr(State); delete_connection(#connection{node = Node}=Conn, MayBeBarred, State) -> BarrIt = MayBeBarred andalso case application:get_env(kernel, dist_auto_connect) of {ok, once} -> true; _ -> false end, case BarrIt of true -> ets:insert(sys_dist, #barred_connection{node = Node}); _ -> ets:delete(sys_dist, Node) end, case Conn#connection.remote_name_type of dynamic -> %% Return remote node name to pool [_Name,Host] = string:split(atom_to_list(Node), "@", all), NamePool0 = State#state.dyn_name_pool, DynNames = maps:get(Host, NamePool0, []), false = lists:keyfind(Node, 1, DynNames), % ASSERT FreeName = {Node, next_creation(Conn#connection.creation)}, NamePool1 = NamePool0#{Host => [FreeName | DynNames]}, State#state{dyn_name_pool = NamePool1}; static -> State end. restart_distr(State) -> Restarter = spawn_link(fun() -> restart_distr_do(State#state.supervisor) end), State#state{supervisor = {restart, Restarter}}. restart_distr_do(NetSup) -> process_flag(trap_exit,true), ok = supervisor:terminate_child(kernel_sup, NetSup), case supervisor:restart_child(kernel_sup, NetSup) of {ok, Pid} when is_pid(Pid) -> ok end. %% ----------------------------------------------------------- %% monitor_nodes/[1,2] errors %% ----------------------------------------------------------- check_opt(Opt, Opts) -> check_opt(Opt, Opts, false, []). check_opt(_Opt, [], false, _OtherOpts) -> false; check_opt(_Opt, [], {true, ORes}, OtherOpts) -> {true, ORes, OtherOpts}; check_opt(Opt, [Opt|RestOpts], false, OtherOpts) -> check_opt(Opt, RestOpts, {true, Opt}, OtherOpts); check_opt(Opt, [Opt|RestOpts], {true, Opt} = ORes, OtherOpts) -> check_opt(Opt, RestOpts, ORes, OtherOpts); check_opt({Opt, value}=TOpt, [{Opt, _Val}=ORes|RestOpts], false, OtherOpts) -> check_opt(TOpt, RestOpts, {true, ORes}, OtherOpts); check_opt({Opt, value}=TOpt, [{Opt, _Val}=ORes|RestOpts], {true, ORes}=TORes, OtherOpts) -> check_opt(TOpt, RestOpts, TORes, OtherOpts); check_opt({Opt, value}, [{Opt, _Val} = ORes1| _RestOpts], {true, {Opt, _OtherVal} = ORes2}, _OtherOpts) -> throw({error, {option_value_mismatch, [ORes1, ORes2]}}); check_opt(Opt, [OtherOpt | RestOpts], TORes, OtherOpts) -> check_opt(Opt, RestOpts, TORes, [OtherOpt | OtherOpts]). check_options(Opts) when is_list(Opts) -> RestOpts1 = case check_opt({node_type, value}, Opts) of {true, {node_type,Type}, RO1} when Type =:= visible; Type =:= hidden; Type =:= all -> RO1; {true, {node_type, _Type} = Opt, _RO1} -> throw({error, {bad_option_value, Opt}}); false -> Opts end, RestOpts2 = case check_opt(nodedown_reason, RestOpts1) of {true, nodedown_reason, RO2} -> RO2; false -> RestOpts1 end, case RestOpts2 of [] -> %% This should never happen since we only call this function %% when we know there is an error in the option list {error, internal_error}; _ -> {error, {unknown_options, RestOpts2}} end; check_options(Opts) when is_map(Opts) -> BadMap0 = case maps:find(connection_id, Opts) of error -> Opts; {ok, CIdBool} when is_boolean(CIdBool) -> maps:remove(connection_id, Opts); {ok, BadCIdVal} -> throw({error, {bad_option_value, #{connection_id => BadCIdVal}}}) end, BadMap1 = case maps:find(nodedown_reason, BadMap0) of error -> BadMap0; {ok, NRBool} when is_boolean(NRBool) -> maps:remove(nodedown_reason, BadMap0); {ok, BadNRVal} -> throw({error, {bad_option_value, #{nodedown_reason => BadNRVal}}}) end, BadMap2 = case maps:find(node_type, BadMap1) of error -> BadMap1; {ok, NTVal} when NTVal == visible; NTVal == hidden; NTVal == all -> maps:remove(node_type, BadMap1); {ok, BadNTVal} -> throw({error, {bad_option_value, #{node_type => BadNTVal}}}) end, if map_size(BadMap2) == 0 -> {error, internal_error}; true -> throw({error, {unknown_options, BadMap2}}) end; check_options(Opts) -> {error, {invalid_options, Opts}}. mk_monitor_nodes_error(Flag, _Opts) when Flag =/= true, Flag =/= false -> error; mk_monitor_nodes_error(_Flag, Opts) -> case catch check_options(Opts) of {error, _} = Error -> Error; UnexpectedError -> {error, {internal_error, UnexpectedError}} end. % ------------------------------------------------------------- do_disconnect(Node, State, Async) -> case ets:lookup(sys_dist, Node) of [Conn] when Conn#connection.state =:= up -> disconnect_ctrlr(Conn#connection.ctrlr, State, Async); [Conn] when Conn#connection.state =:= up_pending -> disconnect_ctrlr(Conn#connection.ctrlr, State, Async); _ -> {false, State} end. disconnect_ctrlr(Ctrlr, S0, Async) -> exit(Ctrlr, disconnect), S2 = case Async of true -> S0; false -> receive {'EXIT',Ctrlr,Reason} -> {_,S1} = handle_exit(Ctrlr, Reason, S0), S1 end end, {true, S2}. %% %% %% %% Return a list of all nodes that are 'up' and not hidden. get_nodes_up_normal() -> ets:select(sys_dist, [{#connection{node = '$1', state = up, type = normal, _ = '_'}, [], ['$1']}]). ticker(Kernel, Tick) when is_integer(Tick) -> process_flag(priority, max), ?tckr_dbg(ticker_started), ticker_loop(Kernel, Tick). ticker_loop(Kernel, Tick) -> receive {new_ticktime, NewTick} -> ?tckr_dbg({ticker_changed_time, Tick, NewTick}), ?MODULE:ticker_loop(Kernel, NewTick) after Tick -> Kernel ! tick, ?MODULE:ticker_loop(Kernel, Tick) end. start_aux_ticker(NewTick, OldTick, TransitionPeriod) -> spawn_link(?MODULE, aux_ticker, [self(), NewTick, OldTick, TransitionPeriod]). aux_ticker(NetKernel, NewTick, OldTick, TransitionPeriod) -> process_flag(priority, max), ?tckr_dbg(aux_ticker_started), TickInterval = case NewTick > OldTick of true -> OldTick; false -> NewTick end, NoOfTicks = case TransitionPeriod > 0 of true -> %% 1 tick to start %% + ticks to cover the transition period 1 + (((TransitionPeriod - 1) div TickInterval) + 1); false -> 1 end, aux_ticker1(NetKernel, TickInterval, NoOfTicks). aux_ticker1(NetKernel, _, 1) -> NetKernel ! transition_period_end, NetKernel ! aux_tick, bye; aux_ticker1(NetKernel, TickInterval, NoOfTicks) -> NetKernel ! aux_tick, receive after TickInterval -> aux_ticker1(NetKernel, TickInterval, NoOfTicks-1) end. send(_From,To,Mess) -> case whereis(To) of undefined -> Mess; P when is_pid(P) -> P ! Mess end. -ifdef(UNUSED). safesend(Name,Mess) when is_atom(Name) -> case whereis(Name) of undefined -> Mess; P when is_pid(P) -> P ! Mess end; safesend(Pid, Mess) -> Pid ! Mess. -endif. do_spawn(SpawnFuncArgs, SpawnOpts, State) -> [_,From|_] = SpawnFuncArgs, case catch spawn_opt(?MODULE, spawn_func, SpawnFuncArgs, SpawnOpts) of {'EXIT', {Reason,_}} -> async_reply({reply, {'EXIT', {Reason,[]}}, State}, From); {'EXIT', Reason} -> async_reply({reply, {'EXIT', {Reason,[]}}, State}, From); _ -> {noreply,State} end. %% This code is really intricate. The link will go first and then comes %% the pid, This means that the client need not do a network link. %% If the link message would not arrive, the runtime system shall %% generate a nodedown message spawn_func(link,{From,Tag},M,F,A,Gleader) -> link(From), gen_server:reply({From,Tag},self()), %% ahhh group_leader(Gleader,self()), apply(M,F,A); spawn_func(_,{From,Tag},M,F,A,Gleader) -> gen_server:reply({From,Tag},self()), %% ahhh group_leader(Gleader,self()), apply(M,F,A). %% ----------------------------------------------------------- %% Set up connection to a new node. %% ----------------------------------------------------------- setup(Node, ConnId, Type, From, State) -> case setup_check(Node, State) of {ok, L} -> Mod = L#listen.module, LAddr = L#listen.address, MyNode = State#state.node, Pid = Mod:setup(Node, Type, MyNode, State#state.type, State#state.connecttime), verbose( {setup,Node,Type,MyNode,State#state.type,Pid}, 2, State), Addr = LAddr#net_address { address = undefined, host = undefined }, Waiting = case From of noreply -> []; _ -> [From] end, ets:insert(sys_dist, #connection{node = Node, conn_id = ConnId, state = pending, owner = Pid, waiting = Waiting, address = Addr, type = normal, remote_name_type = static}), {ok, Pid}; Error -> Error end. setup_check(Node, State) -> Allowed = State#state.allowed, case lists:member(Node, Allowed) of false when Allowed =/= [] -> error_msg("** Connection attempt with " "disallowed node ~w ** ~n", [Node]), {error, bad_node}; _ -> case select_mod(Node, State#state.listen) of {ok, _L}=OK -> OK; Error -> Error end end. %% %% Find a module that is willing to handle connection setup to Node %% select_mod(Node, [L|Ls]) -> Mod = L#listen.module, case Mod:select(Node) of true -> {ok, L}; false -> select_mod(Node, Ls) end; select_mod(Node, []) -> {error, {unsupported_address_type, Node}}. get_proto_mod(Family,Protocol,[L|Ls]) -> A = L#listen.address, if L#listen.accept =/= undefined, A#net_address.family =:= Family, A#net_address.protocol =:= Protocol -> {ok, L#listen.module}; true -> get_proto_mod(Family,Protocol,Ls) end; get_proto_mod(_Family, _Protocol, []) -> error. %% -------- Initialisation functions ------------------------ init_node(Name, LongOrShortNames, CleanHalt, Listen) -> case create_name(Name, LongOrShortNames, 1) of {ok,Node} -> case start_protos(Node, CleanHalt, Listen) of {ok, Ls} -> {ok, Node, Ls}; Error -> Error end; Error -> Error end. %% Create the node name create_name(Name, LongOrShortNames, Try) -> put(longnames, case LongOrShortNames of shortnames -> false; longnames -> true end), {Head,Host1} = create_hostpart(Name, LongOrShortNames), case Host1 of {ok,HostPart} -> case valid_name_head(Head) of true -> {ok,list_to_atom(Head ++ HostPart)}; false -> error_logger:info_msg("Invalid node name!\n" "Please check your configuration\n"), {error, badarg} end; {error,long} when Try =:= 1 -> %% It could be we haven't read domain name from resolv file yet inet_config:do_load_resolv(os:type(), longnames), create_name(Name, LongOrShortNames, 0); {error, hostname_not_allowed} -> error_logger:info_msg("Invalid node name!\n" "Please check your configuration\n"), {error, badarg}; {error,Type} -> error_logger:info_msg( lists:concat(["Can't set ", Type, " node name!\n" "Please check your configuration\n"])), {error,badarg} end. create_hostpart(Name, LongOrShortNames) -> {Head,Host} = split_node(Name), Host1 = case {Host,LongOrShortNames} of {[$@,_|_] = Host,longnames} -> validate_hostname(Host); {[$@,_|_],shortnames} -> case lists:member($.,Host) of true -> {error,short}; _ -> validate_hostname(Host) end; {_,shortnames} -> case inet_db:gethostname() of H when is_list(H), length(H)>0 -> {ok,"@" ++ H}; _ -> {error,short} end; {_,longnames} -> case {inet_db:gethostname(),inet_db:res_option(domain)} of {H,D} when is_list(D), is_list(H), length(D)> 0, length(H)>0 -> {ok,"@" ++ H ++ "." ++ D}; _ -> {error,long} end end, {Head,Host1}. validate_hostname([$@|HostPart] = Host) -> {ok, MP} = re:compile("^[!-ΓΏ]*$", [unicode]), case re:run(HostPart, MP) of {match, _} -> {ok, Host}; nomatch -> {error, hostname_not_allowed} end. valid_name_head(Head) -> {ok, MP} = re:compile("^[0-9A-Za-z_\\-]+$", [unicode]), case re:run(Head, MP) of {match, _} -> true; nomatch -> false end. split_node(Name) -> lists:splitwith(fun(C) -> C =/= $@ end, atom_to_list(Name)). %% %% %% protocol_childspecs() -> case init:get_argument(proto_dist) of {ok, [Protos]} -> protocol_childspecs(Protos); _ -> protocol_childspecs(["inet_tcp"]) end. protocol_childspecs([]) -> []; protocol_childspecs([H|T]) -> Mod = list_to_atom(H ++ "_dist"), case (catch Mod:childspecs()) of {ok, Childspecs} when is_list(Childspecs) -> Childspecs ++ protocol_childspecs(T); _ -> protocol_childspecs(T) end. %% %% epmd_module argument -> module_name of erl_epmd or similar gen_server_module. %% epmd_module() -> case init:get_argument(epmd_module) of {ok,[[Module | _] | _]} -> list_to_atom(Module); _ -> erl_epmd end. %% %% -dist_listen argument -> whether the erlang distribution should listen for connections %% dist_listen_argument() -> case init:get_argument(dist_listen) of {ok,[["false" | _] | _]} -> false; _ -> true end. %%% %%% -hidden command line argument %%% hidden_argument() -> case init:get_argument(hidden) of {ok,[[] | _]} -> true; {ok,[["true" | _] | _]} -> true; _ -> false end. %% %% Start all protocols %% start_protos(Node, CleanHalt, Listen) -> case init:get_argument(proto_dist) of {ok, [Protos]} -> start_protos(Node, Protos, CleanHalt, Listen); _ -> start_protos(Node, ["inet_tcp"], CleanHalt, Listen) end. start_protos(Node, Ps, CleanHalt, Listen) -> Listeners = case Listen of false -> start_protos_no_listen(Node, Ps, [], CleanHalt); _ -> start_protos_listen(Node, Ps, CleanHalt) end, case Listeners of [] -> case CleanHalt of true -> halt(1); false -> {error, badarg} end; Ls -> {ok, Ls} end. start_protos_no_listen(Node, [Proto | Ps], Ls, CleanHalt) -> {Name, "@"++Host} = split_node(Node), Ok = case Name of "undefined" -> erts_internal:dynamic_node_name(true), true; _ -> (set_node(Node, create_creation()) =:= ok) end, case Ok of true -> auth:sync_cookie(), Mod = list_to_atom(Proto ++ "_dist"), Address = try Mod:address(Host) catch error:undef -> Mod:address() end, L = #listen { listen = undefined, address = Address, accept = undefined, module = Mod }, start_protos_no_listen(Node, Ps, [L|Ls], CleanHalt); false -> S = "invalid node name: " ++ atom_to_list(Node), proto_error(CleanHalt, Proto, S), start_protos_no_listen(Node, Ps, Ls, CleanHalt) end; start_protos_no_listen(_Node, [], Ls, _CleanHalt) -> Ls. create_creation() -> Cr = try binary:decode_unsigned(crypto:strong_rand_bytes(4)) of Creation -> Creation catch _:_ -> rand:uniform((1 bsl 32)-1) end, wrap_creation(Cr). next_creation(Creation) -> wrap_creation(Creation + 1). %% Avoid small creations 0,1,2,3 wrap_creation(Cr) when Cr >= 4 andalso Cr < (1 bsl 32) -> Cr; wrap_creation(Cr) -> wrap_creation((Cr + 4) band ((1 bsl 32) - 1)). start_protos_listen(Node, Ps, CleanHalt) -> case split_node(Node) of {"undefined", _} -> error({internal_error, "Dynamic node name and dist listen both enabled"}); {Name, "@"++Host} -> start_protos_listen(list_to_atom(Name), Host, Node, Ps, [], CleanHalt) end. start_protos_listen(Name, Host, Node, [Proto | Ps], Ls, CleanHalt) -> Mod = list_to_atom(Proto ++ "_dist"), try try Mod:listen(Name,Host) catch error:undef -> Mod:listen(Name) end of {ok, {Socket, Address, Creation}} -> case set_node(Node, Creation) of ok -> AcceptPid = Mod:accept(Socket), auth:sync_cookie(), L = #listen{ listen = Socket, address = Address, accept = AcceptPid, module = Mod }, start_protos_listen(Name, Host, Node, Ps, [L|Ls], CleanHalt); _ -> Mod:close(Socket), S = "invalid node name: " ++ atom_to_list(Node), proto_error(CleanHalt, Proto, S), start_protos_listen(Name, Host, Node, Ps, Ls, CleanHalt) end; {error, duplicate_name} -> S = "the name " ++ atom_to_list(Node) ++ " seems to be in use by another Erlang node", proto_error(CleanHalt, Proto, S), start_protos_listen(Name, Host, Node, Ps, Ls, CleanHalt); {error, Reason} -> register_error(CleanHalt, Proto, Reason), start_protos_listen(Name, Host, Node, Ps, Ls, CleanHalt) catch error:undef -> proto_error(CleanHalt, Proto, "not supported"), start_protos_listen(Name, Host, Node, Ps, Ls, CleanHalt); _:Reason -> register_error(CleanHalt, Proto, Reason), start_protos_listen(Name, Host, Node, Ps, Ls, CleanHalt) end; start_protos_listen(_Name, _Host, _Node, [], Ls, _CleanHalt) -> Ls. register_error(false, Proto, Reason) -> S = io_lib:format("register/listen error: ~p", [Reason]), proto_error(false, Proto, lists:flatten(S)); register_error(true, Proto, Reason) -> S = "Protocol '" ++ Proto ++ "': register/listen error: ", erlang:display_string(stdout, S), erlang:display(Reason). proto_error(CleanHalt, Proto, String) -> S = "Protocol '" ++ Proto ++ "': " ++ String ++ "\n", case CleanHalt of false -> error_logger:info_msg(S); true -> erlang:display_string(S) end. set_node(Node, Creation) when Creation < 0 -> set_node(Node, create_creation()); set_node(Node, Creation) when node() =:= nonode@nohost -> case catch erlang:setnode(Node, Creation) of true -> ok; {'EXIT',Reason} -> {error,Reason} end; set_node(Node, _Creation) when node() =:= Node -> ok. connecttime() -> case application:get_env(kernel, net_setuptime) of {ok,Time} when is_number(Time), Time >= 120 -> 120 * 1000; {ok,Time} when is_number(Time), Time > 0 -> round(Time * 1000); _ -> ?SETUPTIME end. %% -------- End initialisation functions -------------------- %% ------------------------------------------------------------ %% Node information. %% ------------------------------------------------------------ get_node_info(Node) -> case ets:lookup(sys_dist, Node) of [#connection{owner = Owner, state = up, address = Addr, type = Type}] -> MRef = monitor(process, Owner), Owner ! {self(), get_status}, receive {Owner, get_status, {ok, Read, Write}} -> demonitor(MRef, [flush]), {ok, [{owner, Owner}, {state, up}, {address, Addr}, {type, Type}, {in, Read}, {out, Write}]}; {'DOWN', MRef, process, Owner, _Info} -> {error, bad_node} end; [#connection{owner = Owner, state = State, address = Addr, type = Type}] -> {ok, [{owner, Owner}, {state, State}, {address, Addr}, {type, Type}, {in, 0}, {out, 0}]}; _ -> {error, bad_node} end. get_node_info(Node, Key) -> case get_node_info(Node) of {ok, Info} -> case lists:keyfind(Key, 1, Info) of {Key, Value} -> {ok, Value}; false -> {error, invalid_key} end; {error, bad_node} -> {error, bad_node} end. get_nodes_info() -> Conns = ets:select(sys_dist, [{#connection{_ = '_'}, [], ['$_']}]), Info = multi_info(Conns, {self(), get_status}, #{}, []), {ok, Info}. multi_info([], _Msg, PidToRef, NodeInfos) -> multi_receive(PidToRef, NodeInfos); multi_info([#connection{owner = Owner, state = up} = Conn | Conns], Msg, PidToRef, NodeInfos) -> % connection is up, try to figure out in/out bytes MRef = erlang:monitor(process, Owner), Owner ! Msg, multi_info(Conns, Msg, maps:put(Owner, {MRef, Conn}, PidToRef), NodeInfos); multi_info([#connection{node = Node, owner = Owner, type = Type, state = State, address = Addr} | Conns], Msg, PidToRef, NodeInfos) -> % connection is not up: in/out bytes are zero multi_info(Conns, Msg, PidToRef, [ {Node, [{owner, Owner}, {state, State}, {address, Addr}, {type, Type}, {in, 0}, {out, 0}]} | NodeInfos]). multi_receive(PidToRef, NodeInfos) when map_size(PidToRef) =:= 0 -> NodeInfos; multi_receive(PidToRef, NodeInfos) -> receive {DistProc, get_status, {ok, Read, Write}} -> {{MRef, #connection{node = Node, owner = Owner, type = Type, state = State, address = Addr}}, NewRefs} = maps:take(DistProc, PidToRef), erlang:demonitor(MRef, [flush]), multi_receive(NewRefs, [ {Node, [{owner, Owner}, {state, State}, {address, Addr}, {type, Type}, {in, Read}, {out, Write}]} | NodeInfos]); {'DOWN', _MRef, process, Pid, _Info} -> % connection went down: reproducing compatible behaviour with % not showing any information about this connection multi_receive(maps:remove(Pid, PidToRef), NodeInfos) end. %% ------------------------------------------------------------ %% Misc. functions %% ------------------------------------------------------------ reply_waiting(_Node, Waiting, Rep) -> case Rep of false -> ?connect_failure(_Node, {setup_process, failure}); _ -> ok end, reply_waiting1(lists:reverse(Waiting), Rep). reply_waiting1([From|W], Rep) -> gen_server:reply(From, Rep), reply_waiting1(W, Rep); reply_waiting1([], _) -> ok. -ifdef(UNUSED). delete_all(From, [From |Tail]) -> delete_all(From, Tail); delete_all(From, [H|Tail]) -> [H|delete_all(From, Tail)]; delete_all(_, []) -> []. -endif. all_atoms([]) -> true; all_atoms([N|Tail]) when is_atom(N) -> all_atoms(Tail); all_atoms(_) -> false. %% It is assumed that only net_kernel uses restart_ticker() restart_ticker(Time) -> ?tckr_dbg(restarting_ticker), self() ! aux_tick, spawn_link(?MODULE, ticker, [self(), Time]). %% ------------------------------------------------------------ %% Print status information. %% ------------------------------------------------------------ print_info() -> nformat("Node", "State", "Type", "In", "Out", "Address"), {ok, NodesInfo} = nodes_info(), {In,Out} = lists:foldl(fun display_info/2, {0,0}, NodesInfo), nformat("Total", "", "", integer_to_list(In), integer_to_list(Out), ""). display_info({Node, Info}, {I,O}) -> State = atom_to_list(fetch(state, Info)), In = fetch(in, Info), Out = fetch(out, Info), Type = atom_to_list(fetch(type, Info)), Address = fmt_address(fetch(address, Info)), nformat(atom_to_list(Node), State, Type, integer_to_list(In), integer_to_list(Out), Address), {I+In,O+Out}. fmt_address(undefined) -> "-"; fmt_address(A) -> case A#net_address.family of inet -> case A#net_address.address of {IP,Port} -> inet_parse:ntoa(IP) ++ ":" ++ integer_to_list(Port); _ -> "-" end; inet6 -> case A#net_address.address of {IP,Port} -> inet_parse:ntoa(IP) ++ "/" ++ integer_to_list(Port); _ -> "-" end; _ -> lists:flatten(io_lib:format("~p", [A#net_address.address])) end. fetch(Key, Info) -> case lists:keysearch(Key, 1, Info) of {value, {_, Val}} -> Val; false -> 0 end. nformat(A1, A2, A3, A4, A5, A6) -> io:format("~-20s ~-7s ~-6s ~8s ~8s ~s~n", [A1,A2,A3,A4,A5,A6]). print_info(Node) -> case node_info(Node) of {ok, Info} -> State = fetch(state, Info), In = fetch(in, Info), Out = fetch(out, Info), Type = fetch(type, Info), Address = fmt_address(fetch(address, Info)), io:format("Node = ~p~n" "State = ~p~n" "Type = ~p~n" "In = ~p~n" "Out = ~p~n" "Address = ~s~n", [Node, State, Type, In, Out, Address]); Error -> Error end. verbose(Term, Level, #state{verbose = Verbose}) when Verbose >= Level -> error_logger:info_report({net_kernel, Term}); verbose(_, _, _) -> ok. getnode(P) when is_pid(P) -> node(P); getnode(P) -> P. return_call({noreply, _State}=R, _From) -> R; return_call(R, From) -> async_reply(R, From). -compile({inline, [async_reply/2]}). async_reply({reply, _Msg, _State} = Res, _From) -> %% This function call is kept in order to not unnecessarily create a huge diff %% in the code. %% %% Here we used to send the reply explicitly using 'noconnect' and 'nosuspend'. %% %% * 'noconnect' since setting up a connection from net_kernel itself would %% deadlock when connects were synchronous. Since connects nowadays are %% asynchronous this is no longer an issue. %% * 'nosuspend' and spawn a process taking care of the reply in case %% we would have suspended. This in order not to block net_kernel. We now %% use 'async_dist' enabled and by this prevent the blocking, keep the %% signal order, avoid one extra copying of the reply, avoid the overhead %% of creating a process, and avoid the extra memory consumption due to the %% extra process. Res. handle_async_response(ResponseType, ReqId, Result, #state{req_map = ReqMap0} = S0) -> if ResponseType == down -> ok; true -> _ = erlang:demonitor(ReqId, [flush]), ok end, case maps:take(ReqId, ReqMap0) of {{SetGetOpts, From}, ReqMap1} when SetGetOpts == setopts; SetGetOpts == getopts -> Reply = case ResponseType of reply -> Result; down -> {error, noconnection} end, gen_server:reply(From, Reply), {noreply, S0#state{req_map = ReqMap1}}; {{setopts_new, Op}, ReqMap1} -> case maps:get(Op, ReqMap1) of {setopts_new, From, 1} -> %% Last response for this operation... gen_server:reply(From, ok), ReqMap2 = maps:remove(Op, ReqMap1), {noreply, S0#state{req_map = ReqMap2}}; {setopts_new, From, N} -> ReqMap2 = ReqMap1#{Op => {setopts_new, From, N-1}}, {noreply, S0#state{req_map = ReqMap2}} end end. send_owner_request(ReqOpMap, Label, Owner, Msg) -> ReqId = monitor(process, Owner), Owner ! {self(), ReqId, Msg}, ReqOpMap#{ReqId => Label}. -spec setopts(Node, Options) -> ok | {error, Reason} | ignored when Node :: node() | new, Options :: [inet:socket_setopt()], Reason :: inet:posix() | noconnection. setopts(Node, Opts) when is_atom(Node), is_list(Opts) -> request({setopts, Node, Opts}). setopts_new(Opts, From, State) -> %% First try setopts on listening socket(s) %% Bail out on failure. %% If successful, we are pretty sure Opts are ok %% and we continue with config params and pending connections. case setopts_on_listen(Opts, State#state.listen) of ok -> setopts_new_1(Opts, From, State); Fail -> async_reply({reply, Fail, State}, From) end. setopts_on_listen(_, []) -> ok; setopts_on_listen(Opts, [#listen {listen = LSocket, module = Mod} | T]) -> try Mod:setopts(LSocket, Opts) of ok -> setopts_on_listen(Opts, T); Fail -> Fail catch error:undef -> {error, enotsup} end. setopts_new_1(Opts, From, #state{req_map = ReqMap0} = State) -> ConnectOpts = case application:get_env(kernel, inet_dist_connect_options) of {ok, CO} -> CO; _ -> [] end, application:set_env(kernel, inet_dist_connect_options, merge_opts(Opts,ConnectOpts)), ListenOpts = case application:get_env(kernel, inet_dist_listen_options) of {ok, LO} -> LO; _ -> [] end, application:set_env(kernel, inet_dist_listen_options, merge_opts(Opts, ListenOpts)), case lists:keyfind(nodelay, 1, Opts) of {nodelay, ND} when is_boolean(ND) -> application:set_env(kernel, dist_nodelay, ND); _ -> ignore end, %% Update any pending connections PendingConns = ets:select(sys_dist, [{'_', [{'=/=',{element,#connection.state,'$_'},up}], ['$_']}]), Op = make_ref(), SendReq = fun (ReqMap, N, Owner) -> {send_owner_request(ReqMap, {setopts_new, Op}, Owner, {setopts, Opts}), N+1} end, {ReqMap1, NoReqs} = lists:foldl(fun(#connection{state = pending, owner = Owner}, {ReqMap, N}) -> SendReq(ReqMap, N, Owner); (#connection{state = up_pending, pending_owner = Owner}, {ReqMap, N}) -> SendReq(ReqMap, N, Owner); (_, Acc) -> Acc end, {ReqMap0, 0}, PendingConns), if NoReqs == 0 -> async_reply({reply, ok, State}, From); true -> %% Reply made later from handle_async_response() when %% we've got responses from all owners that we've %% made requests to... ReqMap2 = ReqMap1#{Op => {setopts_new, From, NoReqs}}, {noreply, State#state{req_map = ReqMap2}} end. merge_opts([], B) -> B; merge_opts([H|T], B0) -> {Key, _} = H, B1 = lists:filter(fun({K,_}) -> K =/= Key end, B0), merge_opts(T, [H | B1]). -spec getopts(Node, Options) -> {'ok', OptionValues} | {'error', Reason} | ignored when Node :: node(), Options :: [inet:socket_getopt()], OptionValues :: [inet:socket_setopt()], Reason :: inet:posix() | noconnection. getopts(Node, Opts) when is_atom(Node), is_list(Opts) -> request({getopts, Node, Opts}). opts_node(Op, Node, Opts, From, #state{req_map = ReqMap0} = S0) -> case ets:lookup(sys_dist, Node) of [Conn] when Conn#connection.state =:= up -> ReqMap1 = send_owner_request(ReqMap0, {Op, From}, Conn#connection.owner, {Op, Opts}), %% Reply made later from handle_async_response() when %% we get a response from the owner that we made the %% request to... S1 = S0#state{req_map = ReqMap1}, {noreply, S1}; _ -> async_reply({reply, {error, noconnection}, S0}, From) end.