Current section
Files
Jump to
Current section
Files
src/gen_lldp.erl
%%%-------------------------------------------------------------------
%%% @author vdasari
%%% @copyright (C) 2018, <COMPANY>
%%% @doc
%%%
%%% @end
%%% Created : 18. Jan 2018 9:55 AM
%%%-------------------------------------------------------------------
-module(gen_lldp).
-author("vdasari").
-behaviour(gen_server).
-include("logger.hrl").
-include_lib("lldp/include/lldp_api.hrl").
%% API
-export([start_link/3]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-export([info/2, rx_packet/2, interface/3]).
-include_lib("stdlib/include/ms_transform.hrl").
%%%===================================================================
%%% Callbacks
%%%===================================================================
-callback init(
Config :: list()
) ->
{'ok', State::term()} | {'error', Reason::term()}.
-callback handle_message(
Request :: term(),
State :: term()) ->
{'ok', State::term()} | {'error', Reason::term()}.
-callback notify(
Op :: (add | update | delete),
IfName :: term(),
EntityInfo :: #lldp_entity_t{},
State :: term()) ->
{'ok', State::term()} | {'error', Reason::term()}.
-callback info(
Request :: term(),
State :: term()) ->
{'ok', State::term()} | {'error', Reason::term()}.
-callback terminate(
Reason :: (normal | shutdown | {shutdown, term()} | term()),
State :: term()) ->
term().
%%%===================================================================
%%% LLDP Handler Code
%%%===================================================================
-define(MaxEntities, 1).
-define(HoldTimer, 30).
-define(ReinitTimer, 2).
-define(RTimer, 3).
-define(SERVER, ?MODULE).
-record(state, {
name,
module,
callback_state,
ets_tab,
timer_ref,
if_map = #{}
}).
-record(lldp_handler_intf_t, {
if_name,
if_info :: #lldp_entity_t{},
rx_pkts = 0,
tx_pkts = 0,
ageouts = 0,
inserted = 0,
deleted = 0,
tx_data :: binary(),
nbr_list = #{}
}).
-define(call(A),gen_server:call(?MODULE, A)).
-define(cast(A),gen_server:cast(?MODULE, A)).
%%%===================================================================
%%% API
%%%===================================================================
info(Pid, Args) ->
gen_server:call(Pid, Args).
interface(Op, IfName, IfInfo) ->
gen_server:cast(self(), {interface, Op, IfName, IfInfo}).
rx_packet(IfName, Data) ->
gen_server:cast(self(), {rx_packet,IfName,Data}).
start_link(ProcName, ModuleName, Cfg) ->
gen_server:start_link({local, ProcName}, ?MODULE, [ProcName, ModuleName, Cfg], []).
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
init([ProcName, CallbackModule, CallbackState]) ->
?INFO("~p: Starting ~s with handler module at ~p",
[ProcName, ?MODULE_STRING, CallbackModule]),
self() ! {init},
{ok, #state{
name = ProcName,
module = CallbackModule,
callback_state = CallbackState
}}.
handle_call(Request, From, State) ->
try process_call(Request, State) of
{reply, ok, _} = Return ->
?DEBUG("call: Request From ~p, Returns ~p~n~p", [From, ok, Request]),
Return;
{reply, NotOk, _} = Return when is_atom(NotOk) ->
?INFO("call: Request From ~p, Returns ~p~n~p", [From, NotOk, Request]),
Return;
Return ->
Return
catch
Error:Reason:StackTrace ->
?ERROR("Failed:~n Request ~p~n From ~p~n Error ~p, Reason ~p~n StackTrace ~n~s",
[Request, From, Error, Reason, lldp_utils:pretty_print(StackTrace)]),
{reply, Error, State}
end.
handle_cast(Request, State) ->
?DEBUG("cast: Request ~p", [Request]),
try process_cast(Request, State) of
Return ->
Return
catch
Error:Reason:StackTrace ->
?ERROR("Failed:~n Request ~p~n Error ~p, Reason ~p~n StackTrace ~n~s",
[Request, Error, Reason, lldp_utils:pretty_print(StackTrace)]),
{noreply, State}
end.
handle_info(Info, State) ->
?DEBUG("info: Request ~p", [Info]),
try process_info_msg(Info, State) of
Return ->
Return
catch
Error:Reason:StackTrace ->
?ERROR("Failed:~n Request ~p~n Error ~p, Reason ~p~n StackTrace ~n~s",
[Info, Error, Reason, lldp_utils:pretty_print(StackTrace)]),
{noreply, State}
end.
terminate(Reason, #state{name = ProcName, if_map = IfMap} = State) ->
?INFO("~s:~s going down: ~p", [?MODULE, ProcName, Reason]),
lists:foreach(fun
(IfName) ->
do_interface(destroy, IfName, '_', State)
end, maps:keys(IfMap)),
dispatch(terminate, Reason, State),
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%%===================================================================
%%% Internal functions
%%%===================================================================
process_call(Request, State) when element(1, Request) == info ->
{reply, lists:flatten(do_info(Request, State)), State};
process_call({neighbors, Op, IfInfo} = A, State) ->
R = do_neighbors(Op,IfInfo,State),
?INFO("Get neighbors ~p~n~p", [A, R]),
{reply,R, State};
process_call(Request, State) ->
?INFO("call: Unhandled Request ~p", [Request]),
{reply, ok, State}.
process_cast({interface,Op, IfName, IfInfo}, State) ->
{noreply, do_interface(Op, IfName, IfInfo, State)};
process_cast({rx_packet, IfName, Data}, #state{if_map = IfMap} = State) ->
#{IfName := IfInfo} = IfMap,
{noreply, State#state{if_map = IfMap#{IfName => do_rx_packet(IfInfo, Data, State)}}};
process_cast(Request, State) ->
{noreply, State#state{callback_state = dispatch(Request, State)}}.
process_info_msg({init}, #state{name = Name} = State) ->
{noreply, State#state{
callback_state = dispatch(started, State),
ets_tab = ets:new(Name, [named_table, public, set]),
timer_ref = erlang:send_after(?HoldTimer * 1000, self(), hold_timer_expired)
}};
process_info_msg(hold_timer_expired, State) ->
NewState = do_tx_packet(State),
{noreply, NewState#state{
if_map = do_hold_timer_expired(NewState),
timer_ref = erlang:send_after(?HoldTimer * 1000, self(), hold_timer_expired)
}};
process_info_msg({reinit_timer_expired, IfName}, #state{if_map = IfMap} = State) ->
#{IfName := #lldp_handler_intf_t{
tx_data = TxData, tx_pkts = TxPkts
} = IfInfo} = IfMap,
{noreply, State#state{
callback_state = dispatch({tx_packet, IfName, TxData}, State),
if_map = IfMap#{IfName => IfInfo#lldp_handler_intf_t{
tx_pkts = TxPkts+1
}}
}};
process_info_msg(Request, State) ->
{noreply, State#state{callback_state = dispatch(Request, State)}}.
%%%===================================================================
%%% Internal functions
%%%===================================================================
do_interface(create, IfName, Entity, #state{if_map = IfMap} = State) ->
case Entity#lldp_entity_t.if_state == up of
true ->
erlang:send_after(?ReinitTimer * 1000, self(), {reinit_timer_expired, IfName});
_ ->
ok
end,
State#state{
if_map = IfMap#{
IfName => #lldp_handler_intf_t{
if_name = IfName,
if_info = Entity,
tx_data = lldp_pdu:encode(Entity)
}
}
};
do_interface(update, IfName, down, #state{ets_tab = Table, if_map = IfMap} = State) ->
#{IfName := #lldp_handler_intf_t{
if_info = Entity, nbr_list = NbrList
} = IfInfo} = IfMap,
lists:foreach(fun
({NbrKey, _}) ->
ets:match_delete(Table, {{'_',NbrKey},'_'}),
dispatch(notify, {delete, IfName, NbrKey}, State)
end, maps:to_list(NbrList)),
State#state{
if_map = IfMap#{
IfName => IfInfo#lldp_handler_intf_t{
nbr_list = #{},
if_info = Entity#lldp_entity_t{
if_state = down
}
}
}
};
do_interface(update, IfName, up, #state{if_map = IfMap} = State) ->
#{IfName := #lldp_handler_intf_t{if_info = Entity} = IfInfo} = IfMap,
erlang:send_after(?ReinitTimer * 1000, self(), {reinit_timer_expired, IfName}),
State#state{
if_map = IfMap#{
IfName => IfInfo#lldp_handler_intf_t{
if_info = Entity#lldp_entity_t{
if_state = up
}
}
}
};
do_interface(destroy, IfName, _, #state{if_map = IfMap, ets_tab = Table} = State) ->
case maps:get(IfName, IfMap, []) of
#lldp_handler_intf_t{nbr_list = NbrList} ->
lists:foreach(fun
({NbrKey, NbrInfo}) ->
ets:match_delete(Table, {{'_',NbrKey},'_'}),
dispatch(notify, {delete, IfName, NbrInfo}, State)
end, maps:to_list(NbrList)),
State#state{
if_map = maps:remove(IfName, IfMap)
};
_ ->
State
end.
do_rx_packet(
#lldp_handler_intf_t{
if_name = IfName,
nbr_list = NbrList,
rx_pkts = RxPkts
} = IfInfo, Data, #state{ets_tab = Table} = State) ->
#lldp_entity_t{
chassis_id = ChassisId, port_id = PortId, ttl = Ttl
} = DecodeData = lldp_pdu:decode(Data),
NbrKey = {ChassisId, PortId},
case maps:get(NbrKey, NbrList, []) of
#lldp_entity_t{} = E when E /= DecodeData ->
dispatch(notify, {update, IfName, DecodeData}, State);
[] ->
dispatch(notify, {add, IfName, DecodeData}, State);
_ ->
ok
end,
ets:match_delete(Table, {{'_',NbrKey},'_'}),
ets:insert(Table, {{erlang:system_time(second) + Ttl, NbrKey}, IfName}),
IfInfo#lldp_handler_intf_t{
rx_pkts = RxPkts+1,
nbr_list = NbrList#{
NbrKey => DecodeData
}
}.
do_tx_packet(#state{if_map = IfMap} = State) ->
NewIfMap = maps:map(fun
(IfName, #lldp_handler_intf_t{tx_data = TxData, tx_pkts = TxPkts, if_info = #lldp_entity_t{if_state = up}} = Value) ->
dispatch({tx_packet, IfName, TxData}, State),
Value#lldp_handler_intf_t{tx_data = TxData, tx_pkts = TxPkts+1};
(_, Value) ->
Value
end, IfMap),
State#state{if_map = NewIfMap}.
do_hold_timer_expired(#state{ets_tab = Table, if_map = IfMap} = State) ->
CurrentTime = erlang:system_time(second),
case ets:select(Table,
ets:fun2ms(fun
({{ExpiryTime, L}, IfName}) when ExpiryTime < CurrentTime ->
{IfName, {ExpiryTime,L}}
end)) of
Entities when is_list(Entities) ->
lists:foldl(fun
({IfName, {_, NbrKey} = Key}, Acc) ->
#{IfName := #lldp_handler_intf_t{nbr_list = NbrMap} = IfInfo} = Acc,
ets:delete(Table, Key),
case maps:get(NbrKey, NbrMap, []) of
[] ->
?INFO("Not Deleting from NbrMap ~p", [NbrMap]),
Acc;
NbrInfo ->
dispatch(notify, {delete, IfName, NbrInfo}, State),
?INFO("Deleting from NbrMap ~p", [#{IfName => IfInfo#lldp_handler_intf_t{nbr_list = maps:remove(NbrKey, NbrMap)}}]),
Acc#{IfName => IfInfo#lldp_handler_intf_t{nbr_list = maps:remove(NbrKey, NbrMap)}}
end
end, IfMap, Entities);
_ ->
IfMap
end.
do_info({info,dashboard}, State) ->
do_info({info,detail}, State) ++
do_info({info,1,all,neighbors}, State);
do_info({info,brief}, #state{ets_tab = Table} = State) ->
io_lib:format("~16s ~16s ~4w ~4w~n", [
State#state.name,
State#state.module,
map_size(State#state.if_map),
proplists:get_value(size, ets:info(Table))
]);
do_info({info, _}, #state{if_map = IfMap}) ->
Header =
io_lib:format("~8c ~18c ~12c ~12c ~18c ~4c ~10c ~10c~n",[
$-, $-, $-, $-,$-, $-, $-, $-
]) ++
io_lib:format("~8s ~18s ~12s ~12s ~18s ~4s ~10s ~10s~n",[
"IfName", "Chassis Id", "Port Id", "System Name", "Mgmt Address", "Nbrs", "Rx Pkts", "Tx Pkts"
]) ++
io_lib:format("~8c ~18c ~12c ~12c ~18c ~4c ~10c ~10c~n",[
$-, $-, $-, $-,$-, $-, $-, $-
]),
Header ++ maps:fold(fun
(IfName, #lldp_handler_intf_t{if_info = Entity} = IfInfo, Acc) ->
io_lib:format("~8s ~18s ~12s ~12s ~18s ~4w ~10w ~10w~n", [
IfName,
inet_utils:convert_mac(to_string, Entity#lldp_entity_t.chassis_id),
binary_to_list(Entity#lldp_entity_t.port_id),
binary_to_list(Entity#lldp_entity_t.sys_name),
case size(Entity#lldp_entity_t.mgmt_ip) of
4 ->
inet_utils:convert_ip(to_string, Entity#lldp_entity_t.mgmt_ip);
6 ->
inet_utils:convert_mac(to_string, Entity#lldp_entity_t.mgmt_ip);
_ ->
"unknown"
end,
map_size(IfInfo#lldp_handler_intf_t.nbr_list),
IfInfo#lldp_handler_intf_t.rx_pkts,
IfInfo#lldp_handler_intf_t.tx_pkts
]) ++ Acc
end, [], IfMap);
do_info({info, _, all, neighbors}, #state{if_map = IfMap} = State) ->
Header =
io_lib:format("~10c ~18c ~18c ~3c ~12c ~18c ~6c~n",[
$-, $-, $-, $-, $-,$-, $-
]) ++
io_lib:format("~10s ~18s ~18s ~3s ~12s ~18s ~6s~n",[
"IfName", "Chassis Id", "Port Id", "Ttl", "System Name", "Mgmt Info", "Age"
]) ++
io_lib:format("~10c ~18c ~18c ~3c ~12c ~18c ~6c~n",[
$-, $-, $-, $-, $-,$-, $-
]),
Header ++ maps:fold(fun
(IfName, _, Acc) ->
do_info({info, no_header, IfName, neighbors}, State) ++ Acc
end, [], IfMap);
do_info({info, HeaderReqd, IfName, neighbors}, #state{if_map = IfMap, ets_tab = Table}) ->
Header = case HeaderReqd == no_header of
true -> [];
_ ->
io_lib:format("~10c ~18c ~18c ~3c ~12c ~18c ~6c~n",[
$-, $-, $-, $-, $-,$-, $-
]) ++
io_lib:format("~10s ~18s ~18s ~3s ~12s ~18s ~6s~n",[
"IfName", "Chassis Id", "Port Id", "Ttl", "System Name", "Mgmt Info", "Age"
]) ++
io_lib:format("~10c ~18c ~18c ~3c ~12c ~18c ~6c~n",[
$-, $-, $-, $-, $-,$-, $-
])
end,
Header ++ case maps:get(IfName, IfMap, []) of
#lldp_handler_intf_t{nbr_list = NbrsMap} ->
maps:fold(fun
(NbrKey, #lldp_entity_t{} = Entity, Acc) ->
[{{ExpiresAt,_},_}] = ets:match_object(Table, {{'_', NbrKey},'_'}),
io_lib:format("~10s ~18s ~18s ~3w ~12s ~18s ~6w~n", [
IfName, inet_utils:convert_mac(to_string, Entity#lldp_entity_t.chassis_id),
(Entity#lldp_entity_t.port_id),
Entity#lldp_entity_t.ttl,
(Entity#lldp_entity_t.sys_name),
Entity#lldp_entity_t.mgmt_ip,
ExpiresAt - erlang:system_time(second)
]) ++ Acc
end, [], NbrsMap);
_ ->
[]
end;
do_info(Request, State) ->
io_lib:format("Request ~p", [Request]),
dispatch(info, {Request}, State).
do_neighbors(get, {Type, Key}, #state{if_map = IfMap}) ->
lists:foldl(fun
(#lldp_handler_intf_t{nbr_list = NbrMap}, Acc) when Type == chassis ->
maps:values(NbrMap) ++ Acc;
(#lldp_handler_intf_t{
if_info = #lldp_entity_t{if_index = P},
nbr_list = NbrMap
}, Acc) when Type == if_index, P == Key ->
maps:values(NbrMap) ++ Acc;
(#lldp_handler_intf_t{
if_name = P,
nbr_list = NbrMap
}, Acc) when Type == if_name, P == Key ->
maps:values(NbrMap) ++ Acc;
(_, Acc) ->
Acc
end, [], maps:values(IfMap)).
%%%===================================================================
%%% Utility functions
%%%===================================================================
dispatch(Message, State) ->
dispatch(handle_message, [Message], State).
dispatch(Function, Message, #state{module = CallbackModule, callback_state = CallbackState}) ->
Args = case Message of
_ when is_tuple(Message) ->
tuple_to_list(Message) ++ [CallbackState];
_ when is_list(Message) ->
Message ++ [CallbackState];
_ ->
[Message, CallbackState]
end,
{ok, NewState} = erlang:apply(CallbackModule, Function, Args),
NewState.