Current section
Files
Jump to
Current section
Files
src/eradius_server_mon.erl
%% @private
%% @doc Manager for RADIUS server processes.
%% This module manages the RADIUS server registry and
%% validates and applies the server configuration from the application environment.
%% It starts all servers that are configured as part of its initialization,
%% then sends ping requests to all nodes that are part of the configuration in order
%% to keep them connected.
-module(eradius_server_mon).
-export([start_link/0, reconfigure/0, lookup_handler/3, lookup_pid/2, all_nas_keys/0]).
-export_type([handler/0]).
-behaviour(gen_server).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-include("eradius_lib.hrl").
-define(SERVER, ?MODULE).
-define(NAS_TAB, eradius_nas_tab).
-export_type([server/0]).
-import(eradius_lib, [printable_peer/2]).
-record(nas, {
key :: {server(), inet:ip_address()},
server_name :: server_name(),
handler :: handler(),
prop :: #nas_prop{}
}).
%% ------------------------------------------------------------------------------------------
%% -- API
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
%% @doc Apply NAS config from the application environment.
%% Walks the list of configured servers and NASs,
%% starting and stopping servers as necessary.
%% If the configuration is invalid, no servers are modified.
%%
-spec reconfigure() -> ok | {error, invalid_config}.
reconfigure() ->
gen_server:call(?SERVER, reconfigure).
%% @doc Fetch the RADIUS secret, handler and trace flag for a given server/NAS combination.
%% This is a very fast operation that is called for every
%% request by the RADIUS server process.
-spec lookup_handler(inet:ip_address(), eradius_server:port_number(), inet:ip_address()) -> {ok, handler(), #nas_prop{}} | {error, not_found}.
lookup_handler(IP, Port, NasIP) ->
case ets:lookup(?NAS_TAB, {{IP, Port}, NasIP}) of
[] when NasIP == {0, 0, 0, 0} ->
{error, not_found};
[] ->
lookup_handler(IP, Port, {0, 0, 0, 0});
[Rec] ->
Prop = (Rec#nas.prop)#nas_prop{server_ip = IP, server_port = Port},
{ok, Rec#nas.handler, Prop}
end.
%% @doc Fetches the pid of RADIUS server at IP:Port, if there is one.
-spec lookup_pid(inet:ip_address(), eradius_server:port_number()) -> {ok, pid()} | {error, not_found}.
lookup_pid(ServerIP, ServerPort) ->
gen_server:call(?SERVER, {lookup_pid, {ServerIP, ServerPort}}).
%% @doc returns the list of all currently configured NASs
-spec all_nas_keys() -> [term()].
all_nas_keys() ->
ets:select(?NAS_TAB, [{#nas{key = '$1', _ = '_'}, [], ['$1']}]).
%% ------------------------------------------------------------------------------------------
%% -- gen_server callbacks
-record(state, {running}).
init([]) ->
?NAS_TAB = ets:new(?NAS_TAB, [named_table, protected, {keypos, #nas.key}]),
case configure(#state{running = []}) of
{error, invalid_config} -> {stop, invalid_config};
Else -> Else
end.
handle_call({lookup_pid, Server}, _From, State) ->
case proplists:get_value(Server, State#state.running) of
undefined ->
{reply, {error, not_found}, State};
Pid ->
{reply, {ok, Pid}, State}
end;
handle_call(reconfigure, _From, State) ->
case configure(State) of
{error, invalid_config} -> {reply, {error, invalid_config}, State};
{ok, NState} -> {reply, ok, NState}
end;
handle_call(_Request, _From, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
%% unused callbacks
handle_cast(_Msg, State) -> {noreply, State}.
terminate(_Reason, _State) -> ok.
code_change(_OldVsn, State, _Extra) -> {ok, State}.
%% ------------------------------------------------------------------------------------------
%% -- helpers
configure(#state{running = Running}) ->
{ok, ConfServList} = application:get_env(servers),
case eradius_config:validate_config(ConfServList) of
{invalid, Message} ->
lager:error("Invalid server config, ~s", [Message]),
{error, invalid_config};
ServList -> %% list of {ServerName, ServerAddr, NasHandler} tuples
NasList = lists:flatmap(fun(Server) -> server_naslist(Server) end, ServList),
Tab = ets:tab2list(?NAS_TAB),
ToDelete = Tab -- NasList,
ToInsert = NasList -- Tab,
update_nases(ToDelete, ToInsert),
NewServAddrs = [{ServerName, ServerAddr} || {ServerName, ServerAddr, _} <- ServList],
OldServAddrs =[{ServerName, ServerAddr} || {ServerName, ServerAddr, _} <- Running],
ToStop = OldServAddrs -- NewServAddrs,
ToStart = NewServAddrs -- OldServAddrs,
NewRunning = update_server(Running, ToStop, ToStart),
NasHandler = [ NasInfo || {_ServerName, _Addr, NasInfo} <- ServList],
eradius_node_mon:set_nodes(config_nodes(NasHandler)),
{ok, #state{running = NewRunning}}
end.
server_naslist({ServerName, {IP, Port}, HandlerList}) ->
lists:map(fun({NasId, NasIP, Secret, HandlerNodes, HandlerMod, HandlerArgs}) ->
ServerInfo = eradius_metrics:make_addr_info({ServerName, {IP, Port}}),
NasInfo = eradius_metrics:make_addr_info({NasId, {NasIP, undefined}}),
#nas{key = {{IP, Port}, NasIP}, server_name = ServerName, handler = {HandlerMod, HandlerArgs},
prop = #nas_prop{handler_nodes = HandlerNodes, nas_id = NasId, nas_ip = NasIP, secret = Secret,
metrics_info = {ServerInfo, NasInfo}}}
end, HandlerList).
config_nodes(NasHandler) ->
ordsets:from_list(lists:concat([N || {_, _, N, _, _} <- NasHandler, N /= local, N/= node()])).
update_server(Running, ToStop, ToStart) ->
Stopped = lists:map(fun(ServerAddr = {_ServerName, Addr}) ->
StoppedServer = {_, _, Pid} = lists:keyfind(Addr, 2, Running),
eradius_server_sup:stop_instance(ServerAddr, Pid),
StoppedServer
end, ToStop),
NewStarted = lists:map(fun(ServerAddr = {ServerName, Addr = {IP, Port}}) ->
case eradius_server_sup:start_instance(ServerAddr) of
{ok, Pid} ->
{ServerName, Addr, Pid};
{error, Error} ->
lager:error("Could not start listener on host: ~s, occuring error: ~p",
[printable_peer(IP, Port), Error])
end
end, ToStart),
(Running -- Stopped) ++ NewStarted.
update_nases(ToDelete, ToInsert) ->
lists:foreach(fun(Nas) ->
ets:delete_object(?NAS_TAB, Nas),
NasProp = Nas#nas.prop,
MetricsInfo = {ServerAddr, _} = NasProp#nas_prop.metrics_info,
eradius_metrics:delete_nas(MetricsInfo),
eradius_metrics:update_server_time(config_reset, ServerAddr)
end, ToDelete),
lists:foreach(fun(Nas) ->
ets:insert(?NAS_TAB, Nas),
NasProp = Nas#nas.prop,
MetricsInfo = {ServerAddr, _} = NasProp#nas_prop.metrics_info,
eradius_metrics:create_nas(MetricsInfo),
eradius_metrics:update_server_time(config_reset, ServerAddr)
end, ToInsert).