1%% This Source Code Form is subject to the terms of the Mozilla Public
2%% License, v. 2.0. If a copy of the MPL was not distributed with this
3%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
4%%
5%% Copyright (c) 2007-2021 VMware, Inc. or its affiliates.  All rights reserved.
6%%
7
8-module(rabbit_epmd_monitor).
9
10-behaviour(gen_server).
11
12-export([start_link/0]).
13
14-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
15         code_change/3]).
16
17-record(state, {timer, mod, me, host, port}).
18
19-define(SERVER, ?MODULE).
20-define(CHECK_FREQUENCY, 60000).
21
22%%----------------------------------------------------------------------------
23%% It's possible for epmd to be killed out from underneath us. If that
24%% happens, then obviously clustering and rabbitmqctl stop
25%% working. This process checks up on epmd and restarts it /
26%% re-registers us with it if it has gone away.
27%%
28%% How could epmd be killed?
29%%
30%% 1) The most popular way for this to happen is when running as a
31%%    Windows service. The user starts rabbitmqctl first, and this starts
32%%    epmd under the user's account. When they log out epmd is killed.
33%%
34%% 2) Some packagings of (non-RabbitMQ?) Erlang apps might do "killall
35%%    epmd" as a shutdown or uninstall step.
36%% ----------------------------------------------------------------------------
37
38-spec start_link() -> rabbit_types:ok_pid_or_error().
39
40start_link() ->
41    gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
42
43init([]) ->
44    {Me, Host} = rabbit_nodes:parts(node()),
45    Mod = net_kernel:epmd_module(),
46    {ok, Port} = handle_port_please(init, Mod:port_please(Me, Host), Me, undefined),
47    State = #state{mod = Mod, me = Me, host = Host, port = Port},
48    {ok, ensure_timer(State)}.
49
50handle_call(_Request, _From, State) ->
51    {noreply, State}.
52
53handle_cast(check, State0) ->
54    {ok, State1} = check_epmd(State0),
55    {noreply, ensure_timer(State1#state{timer = undefined})};
56handle_cast(_Msg, State) ->
57    {noreply, State}.
58
59handle_info(check, State0) ->
60    {ok, State1} = check_epmd(State0),
61    {noreply, ensure_timer(State1#state{timer = undefined})};
62
63handle_info(_Info, State) ->
64    {noreply, State}.
65
66terminate(_Reason, _State) ->
67    ok.
68
69code_change(_OldVsn, State, _Extra) ->
70    {ok, State}.
71
72%%----------------------------------------------------------------------------
73
74ensure_timer(State) ->
75    rabbit_misc:ensure_timer(State, #state.timer, ?CHECK_FREQUENCY, check).
76
77check_epmd(State = #state{mod  = Mod,
78                          me   = Me,
79                          host = Host,
80                          port = Port0}) ->
81    {ok, Port1} = handle_port_please(check, Mod:port_please(Me, Host), Me, Port0),
82    rabbit_nodes:ensure_epmd(),
83    Mod:register_node(Me, Port1),
84    {ok, State#state{port = Port1}}.
85
86handle_port_please(init, noport, Me, Port) ->
87    rabbit_log:info("epmd does not know us, re-registering as ~s", [Me]),
88    {ok, Port};
89handle_port_please(check, noport, Me, Port) ->
90    rabbit_log:warning("epmd does not know us, re-registering ~s at port ~b", [Me, Port]),
91    {ok, Port};
92handle_port_please(_, closed, _Me, Port) ->
93    rabbit_log:error("epmd monitor failed to retrieve our port from epmd: closed"),
94    {ok, Port};
95handle_port_please(init, {port, NewPort, _Version}, _Me, _Port) ->
96    rabbit_log:info("epmd monitor knows us, inter-node communication (distribution) port: ~p", [NewPort]),
97    {ok, NewPort};
98handle_port_please(check, {port, NewPort, _Version}, _Me, _Port) ->
99    {ok, NewPort};
100handle_port_please(_, {error, Error}, _Me, Port) ->
101    rabbit_log:error("epmd monitor failed to retrieve our port from epmd: ~p", [Error]),
102    {ok, Port}.
103