%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2004-2014. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%%
%% %CopyrightEnd%
%%
-ifndef(snmpm_net_if_mt).
-module(snmpm_net_if).
-endif.
-behaviour(gen_server).
-behaviour(snmpm_network_interface).
%% Network Interface callback functions
-export([
start_link/2,
stop/1,
send_pdu/6, % Backward compatibility
send_pdu/7, % Partly backward compatibility
send_pdu/8, % Backward compatibility
inform_response/4,
note_store/2,
info/1,
verbosity/2,
%% system_info_updated/2,
get_log_type/1, set_log_type/2,
filter_reset/1
]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
code_change/3, terminate/2]).
-define(SNMP_USE_V3, true).
-include("snmp_types.hrl").
-include("snmpm_internal.hrl").
-include("snmpm_atl.hrl").
-include("snmp_debug.hrl").
%% -define(VMODULE,"NET_IF").
-include("snmp_verbosity.hrl").
-record(state,
{
server,
note_store,
transports = [],
mpd_state,
log,
irb = auto, % auto | {user, integer()}
irgc,
filter
}).
-record(transport,
{socket,
domain = snmpUDPDomain}).
-define(DEFAULT_FILTER_MODULE, snmpm_net_if_filter).
-define(DEFAULT_FILTER_OPTS, [{module, ?DEFAULT_FILTER_MODULE}]).
-ifdef(snmp_debug).
-define(GS_START_LINK(Args),
gen_server:start_link(?MODULE, Args, [{debug,[trace]}])).
-else.
-define(GS_START_LINK(Args),
gen_server:start_link(?MODULE, Args, [])).
-endif.
-define(IRGC_TIMEOUT, timer:minutes(5)).
-define(ATL_SEQNO_INITIAL, 1).
-define(ATL_SEQNO_MAX, 2147483647).
%%%-------------------------------------------------------------------
%%% API
%%%-------------------------------------------------------------------
start_link(Server, NoteStore) ->
?d("start_link -> entry with"
"~n Server: ~p"
"~n NoteStore: ~p", [Server, NoteStore]),
Args = [Server, NoteStore],
?GS_START_LINK(Args).
stop(Pid) ->
call(Pid, stop).
send_pdu(Pid, Pdu, Vsn, MsgData, Domain_or_Ip, Addr_or_Port) ->
send_pdu(
Pid, Pdu, Vsn, MsgData, Domain_or_Ip, Addr_or_Port, ?DEFAULT_EXTRA_INFO).
send_pdu(Pid, Pdu, Vsn, MsgData, Domain_or_Ip, Addr_or_Port, ExtraInfo)
when is_record(Pdu, pdu) ->
?d("send_pdu -> entry with~n"
" Pid: ~p~n"
" Pdu: ~p~n"
" Vsn: ~p~n"
" MsgData: ~p~n"
" Domain/IP: ~p~n"
" Addr/Port: ~p",
[Pid, Pdu, Vsn, MsgData, Domain_or_Ip, Addr_or_Port]),
{Domain, Addr} = address(Domain_or_Ip, Addr_or_Port),
cast(Pid, {send_pdu, Pdu, Vsn, MsgData, Domain, Addr, ExtraInfo}).
send_pdu(Pid, Pdu, Vsn, MsgData, Domain, Ip, Port, ExtraInfo) ->
send_pdu(Pid, Pdu, Vsn, MsgData, Domain, {Ip, Port}, ExtraInfo).
note_store(Pid, NoteStore) ->
call(Pid, {note_store, NoteStore}).
inform_response(Pid, Ref, Domain_or_Ip, Addr_or_Port) ->
{Domain, Addr} = address(Domain_or_Ip, Addr_or_Port),
cast(Pid, {inform_response, Ref, Domain, Addr}).
info(Pid) ->
call(Pid, info).
verbosity(Pid, V) ->
call(Pid, {verbosity, V}).
%% system_info_updated(Pid, What) ->
%% call(Pid, {system_info_updated, What}).
get_log_type(Pid) ->
call(Pid, get_log_type).
set_log_type(Pid, NewType) ->
call(Pid, {set_log_type, NewType}).
filter_reset(Pid) ->
cast(Pid, filter_reset).
%%%-------------------------------------------------------------------
%%% Multi-thread manager
%%%-------------------------------------------------------------------
-ifdef(snmpm_net_if_mt).
%% This function is called through the macro below to
%% (in the not multithreaded case) avoid creating the
%% Failer/4 fun, and to avoid calling the Worker through a fun
%% (now it shall not be a fun, just a code snippet).
worker(Worker, Failer, #state{log = Log} = State) ->
Verbosity = get(verbosity),
spawn_opt(
fun () ->
try
put(sname, mnifw),
put(verbosity, Verbosity),
NewState =
case do_reopen_log(Log) of
Log ->
State;
NewLog ->
State#state{log = NewLog}
end,
Worker(NewState)
of
Result ->
%% Winds up in handle_info {'DOWN', ...}
erlang:exit({net_if_worker, Result})
catch
Class:Reason ->
%% Winds up in handle_info {'DOWN', ...}
erlang:exit(
{net_if_worker, Failer,
Class, Reason, erlang:get_stacktrace()})
end
end,
[monitor]).
-define(
worker(S, Worker, Failer, State),
begin
worker(
fun (S) -> begin Worker end end,
begin Failer end,
(State))
end).
-else.
-define(
worker(S, Worker, _Failer, State),
begin (S) = (State), begin Worker end end).
-endif.
%%%-------------------------------------------------------------------
%%% Callback functions from gen_server
%%%-------------------------------------------------------------------
%%--------------------------------------------------------------------
%% Func: init/1
%% Returns: {ok, State} |
%% {ok, State, Timeout} |
%% ignore |
%% {stop, Reason}
%%--------------------------------------------------------------------
init([Server, NoteStore]) ->
?d("init -> entry with"
"~n Server: ~p"
"~n NoteStore: ~p", [Server, NoteStore]),
try do_init(Server, NoteStore)
catch
{error, Reason} ->
{stop, Reason}
end.
-ifdef(snmpm_net_if_mt).
%% This should really be protected, but it also needs to
%% be writable for the worker processes, so...
-define(inform_table_opts, [set, public, named_table, {keypos, 1}]).
-else.
-define(inform_table_opts, [set, protected, named_table, {keypos, 1}]).
-endif.
do_init(Server, NoteStore) ->
process_flag(trap_exit, true),
%% -- Prio --
{ok, Prio} = snmpm_config:system_info(prio),
process_flag(priority, Prio),
%% -- Create inform request table --
ets:new(snmpm_inform_request_table, ?inform_table_opts),
%% -- Verbosity --
{ok, Verbosity} = snmpm_config:system_info(net_if_verbosity),
put(sname, mnif),
put(verbosity, Verbosity),
?vlog("starting", []),
%% -- MPD --
{ok, Vsns} = snmpm_config:system_info(versions),
MpdState = snmpm_mpd:init(Vsns),
?vdebug("MpdState: ~w", [MpdState]),
%% -- Module dependent options --
{ok, Opts} = snmpm_config:system_info(net_if_options),
%% -- Inform response behaviour --
{ok, IRB} = snmpm_config:system_info(net_if_irb),
IrGcRef = irgc_start(IRB),
%% Flow control --
FilterOpts = get_opt(Opts, filter, []),
FilterMod = create_filter(FilterOpts),
?vdebug("FilterMod: ~w", [FilterMod]),
%% -- Audit trail log ---
{ok, ATL} = snmpm_config:system_info(audit_trail_log),
Log = do_init_log(ATL),
?vdebug("Log: ~w", [Log]),
{ok, DomainAddresses} = snmpm_config:system_info(transports),
?vdebug("DomainAddresses: ~w",[DomainAddresses]),
CommonSocketOpts = common_socket_opts(Opts),
BindTo = get_opt(Opts, bind_to, false),
case
[begin
{IpPort, SocketOpts} =
socket_params(Domain, Address, BindTo, CommonSocketOpts),
Socket = socket_open(IpPort, SocketOpts),
#transport{socket = Socket, domain = Domain}
end || {Domain, Address} <- DomainAddresses]
of
[] ->
?vinfo("No transports configured: ~p", [DomainAddresses]),
throw({error, {no_transports,DomainAddresses}});
Transports ->
%% -- Initiate counters ---
init_counters(),
%% -- We are done ---
State = #state{
server = Server,
note_store = NoteStore,
mpd_state = MpdState,
transports = Transports,
log = Log,
irb = IRB,
irgc = IrGcRef,
filter = FilterMod},
?vdebug("started", []),
{ok, State}
end.
socket_open(IpPort, SocketOpts) ->
?vtrace("socket_open -> entry with~n"
" IpPort: ~p~n"
" SocketOpts: ~p", [IpPort, SocketOpts]),
case gen_udp:open(IpPort, SocketOpts) of
{error, _} = Error ->
throw(Error);
{ok, Socket} ->
Socket
end.
socket_params(Domain, {IpAddr, IpPort} = Addr, BindTo, CommonSocketOpts) ->
Family = snmp_conf:tdomain_to_family(Domain),
SocketOpts =
case Family of
inet6 ->
[Family, {ipv6_v6only, true} | CommonSocketOpts];
Family ->
[Family | CommonSocketOpts]
end,
case Family of
inet ->
case init:get_argument(snmp_fd) of
{ok, [[FdStr]]} ->
Fd = list_to_integer(FdStr),
case BindTo of
true ->
{IpPort, [{ip, IpAddr}, {fd, Fd} | SocketOpts]};
_ ->
{0, [{fd, Fd} | SocketOpts]}
end;
error ->
socket_params(SocketOpts, Addr, BindTo)
end;
_ ->
socket_params(SocketOpts, Addr, BindTo)
end.
%%
socket_params(SocketOpts, {IpAddr, IpPort}, BindTo) ->
case BindTo of
true ->
{IpPort, [{ip, IpAddr} | SocketOpts]};
_ ->
{IpPort, SocketOpts}
end.
common_socket_opts(Opts) ->
[binary
| case get_opt(Opts, sndbuf, default) of
default ->
[];
Sz ->
[{sndbuf, Sz}]
end ++
case get_opt(Opts, recbuf, default) of
default ->
[];
Sz ->
[{sndbuf, Sz}]
end ++
case get_opt(Opts, no_reuse, false) of
false ->
[{reuseaddr, true}];
_ ->
[]
end].
create_filter(Opts) when is_list(Opts) ->
case get_opt(Opts, module, ?DEFAULT_FILTER_MODULE) of
?DEFAULT_FILTER_MODULE = Mod ->
Mod;
Module ->
snmpm_network_interface_filter:verify(Module),
Module
end;
create_filter(BadOpts) ->
throw({error, {bad_filter_opts, BadOpts}}).
%% ----------------------------------------------------------------------
%% Audit Trail Logger
%% ----------------------------------------------------------------------
%% Open log
do_init_log(false) ->
?vtrace("do_init_log(false) -> entry", []),
undefined;
do_init_log(true) ->
?vtrace("do_init_log(true) -> entry", []),
{ok, Type} = snmpm_config:system_info(audit_trail_log_type),
{ok, Dir} = snmpm_config:system_info(audit_trail_log_dir),
{ok, Size} = snmpm_config:system_info(audit_trail_log_size),
{ok, Repair} = snmpm_config:system_info(audit_trail_log_repair),
Name = ?audit_trail_log_name,
File = filename:absname(?audit_trail_log_file, Dir),
case snmpm_config:system_info(audit_trail_log_seqno) of
{ok, true} ->
Initial = ?ATL_SEQNO_INITIAL,
Max = ?ATL_SEQNO_MAX,
Module = snmpm_config,
Function = increment_counter,
Args = [atl_seqno, Initial, Max],
SeqNoGen = {Module, Function, Args},
case snmp_log:create(
Name, File, SeqNoGen, Size, Repair, true) of
{ok, Log} ->
?vdebug("log created: ~w", [Log]),
{Name, Log, Type};
{error, Reason} ->
throw({error, {failed_create_audit_log, Reason}})
end;
_ ->
case snmp_log:create(Name, File, Size, Repair, true) of
{ok, Log} ->
?vdebug("log created: ~w", [Log]),
{Name, Log, Type};
{error, Reason} ->
throw({error, {failed_create_audit_log, Reason}})
end
end.
-ifdef(snmpm_net_if_mt).
do_reopen_log(undefined) ->
undefined;
do_reopen_log({Name, Log, Type}) ->
case snmp_log:open(Name, Log) of
{ok, NewLog} ->
{Name, NewLog, Type};
{error, Reason} ->
warning_msg(
"NetIf worker ~p failed to open ATL:~n"
" ~p", [self(), Reason]),
undefined
end.
-endif.
%% Close log
do_close_log(undefined) ->
ok;
do_close_log({_Name, Log, _Type}) ->
(catch snmp_log:sync(Log)),
(catch snmp_log:close(Log)),
ok;
do_close_log(_) ->
ok.
%% Log
logger(undefined, _Type, _Domain, _Addr) ->
fun(_) ->
ok
end;
logger({_Name, Log, Types}, Type, Domain, Addr) ->
case lists:member(Type, Types) of
true ->
AddrString =
iolist_to_binary(snmp_conf:mk_addr_string({Domain, Addr})),
fun(Msg) ->
snmp_log:log(Log, Msg, AddrString)
end;
false ->
fun(_) ->
ok
end
end.
%%--------------------------------------------------------------------
%% Func: handle_call/3
%% Returns: {reply, Reply, State} |
%% {reply, Reply, State, Timeout} |
%% {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, Reply, State} | (terminate/2 is called)
%% {stop, Reason, State} (terminate/2 is called)
%%--------------------------------------------------------------------
handle_call({verbosity, Verbosity}, _From, State) ->
?vlog("received verbosity request", []),
put(verbosity, Verbosity),
{reply, ok, State};
%% handle_call({system_info_updated, What}, _From, State) ->
%% ?vlog("received system_info_updated request with What = ~p", [What]),
%% {NewState, Reply} = handle_system_info_updated(State, What),
%% {reply, Reply, NewState};
handle_call(get_log_type, _From, State) ->
?vlog("received get-log-type request", []),
Reply = (catch handle_get_log_type(State)),
{reply, Reply, State};
handle_call({set_log_type, NewType}, _From, State) ->
?vlog("received set-log-type request with NewType = ~p", [NewType]),
{NewState, Reply} = (catch handle_set_log_type(State, NewType)),
{reply, Reply, NewState};
handle_call({note_store, Pid}, _From, State) ->
?vlog("received new note_store: ~w", [Pid]),
{reply, ok, State#state{note_store = Pid}};
handle_call(stop, _From, State) ->
?vlog("received stop request", []),
Reply = ok,
{stop, normal, Reply, State};
handle_call(info, _From, State) ->
?vlog("received info request", []),
Reply = get_info(State),
{reply, Reply, State};
handle_call(Req, From, State) ->
warning_msg("received unknown request (from ~p): ~n~p", [Req, From]),
{reply, {error, {invalid_request, Req}}, State}.
%%--------------------------------------------------------------------
%% Func: handle_cast/2
%% Returns: {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State} (terminate/2 is called)
%%--------------------------------------------------------------------
handle_cast({send_pdu, Pdu, Vsn, MsgData, Domain, Addr, ExtraInfo},
State) ->
?vlog("received send_pdu message with~n"
" Pdu: ~p~n"
" Vsn: ~p~n"
" MsgData: ~p~n"
" Domain: ~p~n"
" Addr: ~p", [Pdu, Vsn, MsgData, Domain, Addr]),
maybe_process_extra_info(ExtraInfo),
maybe_handle_send_pdu(Pdu, Vsn, MsgData, Domain, Addr, State),
{noreply, State};
handle_cast({inform_response, Ref, Domain, Addr}, State) ->
?vlog("received inform_response message with~n"
" Ref: ~p~n"
" Domain: ~p~n"
" Addr: ~p", [Ref, Domain, Addr]),
handle_inform_response(Ref, Domain, Addr, State),
{noreply, State};
handle_cast(filter_reset, State) ->
?vlog("received filter_reset message", []),
reset_counters(),
{noreply, State};
handle_cast(Msg, State) ->
warning_msg("received unknown message: ~n~p", [Msg]),
{noreply, State}.
%%--------------------------------------------------------------------
%% Func: handle_info/2
%% Returns: {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State} (terminate/2 is called)
%%--------------------------------------------------------------------
handle_info(
{udp, Socket, IpAddr, IpPort, Bytes},
#state{transports = Transports} = State) ->
Size = byte_size(Bytes),
case lists:keyfind(Socket, #transport.socket, Transports) of
#transport{socket = Socket, domain = Domain} ->
?vlog("received ~w bytes from ~p:~p [~w]",
[Size, IpAddr, IpPort, Socket]),
maybe_handle_recv_msg(Domain, {IpAddr, IpPort}, Bytes, State),
{noreply, State};
false ->
warning_msg("Received ~w bytes on unknown port: ~p from ~s",
[Size, Socket, format_address({IpAddr, IpPort})]),
{noreply, State}
end;
handle_info(inform_response_gc, State) ->
?vlog("received inform_response_gc message", []),
State2 = handle_inform_response_gc(State),
{noreply, State2};
handle_info({disk_log, _Node, Log, Info}, State) ->
?vlog("received disk_log message: "
"~n Info: ~p", [Info]),
State2 = handle_disk_log(Log, Info, State),
{noreply, State2};
handle_info({'DOWN', _, _, _, _} = Info, State) ->
handle_info_down(Info, State);
handle_info(Info, State) ->
handle_info_unknown(Info, State).
handle_info_unknown(Info, State) ->
warning_msg("received unknown info: ~n~p", [Info]),
{noreply, State}.
-ifdef(snmpm_net_if_mt).
handle_info_down(
{'DOWN', _MRef, process, _Pid,
{net_if_worker, _Result}},
State) ->
?vdebug("received DOWN message from net_if worker [~w]: "
"~n Result: ~p", [_Pid, _Result]),
{noreply, State};
handle_info_down(
{'DOWN', _MRef, process, Pid,
{net_if_worker, Failer, Class, Reason, Stacktrace} = _ExitStatus},
State) ->
?vdebug("received DOWN message from net_if worker [~w]: "
"~n ExitStatus: ~p", [Pid, _ExitStatus]),
Failer(Pid, Class, Reason, Stacktrace),
{noreply, State};
handle_info_down(Info, State) ->
handle_info_unknown(Info, State).
-else.
handle_info_down(Info, State) ->
handle_info_unknown(Info, State).
-endif.
%%--------------------------------------------------------------------
%% Func: terminate/2
%% Purpose: Shutdown the server
%% Returns: any (ignored by gen_server)
%%--------------------------------------------------------------------
terminate(Reason, #state{log = Log, irgc = IrGcRef}) ->
?vdebug("terminate: ~p", [Reason]),
irgc_stop(IrGcRef),
%% Close logs
do_close_log(Log),
ok.
%%----------------------------------------------------------------------
%% Func: code_change/3
%% Purpose: Convert process state when code is changed
%% Returns: {ok, NewState}
%%----------------------------------------------------------------------
code_change(_Vsn, State, _Extra) ->
?d("code_change -> entry with"
"~n Vsn: ~p"
"~n State: ~p"
"~n Extra: ~p", [_Vsn, State, _Extra]),
{ok, State}.
%%%-------------------------------------------------------------------
%%% Internal functions
%%%-------------------------------------------------------------------
maybe_handle_recv_msg(Domain, Addr, Bytes, State) ->
?worker(
S, maybe_handle_recv_msg_mt(Domain, Addr, Bytes, S),
fun (Pid, Class, Reason, Stacktrace) ->
warning_msg(
"Worker process (~p) terminated "
"while processing (incomming) message from %s:~n"
"~w:~w at ~p",
[Pid, snmp_conf:mk_addr_string({Domain, Addr}),
Class, Reason, Stacktrace])
end,
State).
maybe_handle_recv_msg_mt(
Domain, Addr, Bytes,
#state{filter = FilterMod, transports = Transports} = State) ->
{Arg1, Arg2} = fix_filter_address(Transports, {Domain, Addr}),
case (catch FilterMod:accept_recv(Arg1, Arg2)) of
false ->
%% Drop the received packet
inc(netIfMsgInDrops);
_ ->
handle_recv_msg(Domain, Addr, Bytes, State)
end,
ok.
handle_recv_msg(Domain, Addr, Bytes, #state{server = Pid})
when is_binary(Bytes) andalso (size(Bytes) =:= 0) ->
Pid ! {snmp_error, {empty_message, Domain, Addr}, Domain, Addr};
%%
handle_recv_msg(
Domain, Addr, Bytes,
#state{
server = Pid,
note_store = NoteStore,
mpd_state = MpdState,
log = Log} = State) ->
Logger = logger(Log, read, Domain, Addr),
case (catch snmpm_mpd:process_msg(
Bytes, Domain, Addr, MpdState, NoteStore, Logger)) of
{ok, Vsn, Pdu, MS, ACM} ->
maybe_handle_recv_pdu(
Domain, Addr, Vsn, Pdu, MS, ACM, Logger, State);
{discarded, Reason, Report} ->
?vdebug("discarded: ~p", [Reason]),
ErrorInfo = {failed_processing_message, Reason},
Pid ! {snmp_error, ErrorInfo, Domain, Addr},
maybe_udp_send(Domain, Addr, Report, State);
{discarded, Reason} ->
?vdebug("discarded: ~p", [Reason]),
ErrorInfo = {failed_processing_message, Reason},
Pid ! {snmp_error, ErrorInfo, Domain, Addr};
Error ->
error_msg("processing of received message failed: "
"~n ~p", [Error])
end.
maybe_handle_recv_pdu(
Domain, Addr, Vsn, #pdu{type = Type} = Pdu, PduMS, ACM, Logger,
#state{filter = FilterMod, transports = Transports} = State) ->
{Arg1, Arg2} = fix_filter_address(Transports, {Domain, Addr}),
case (catch FilterMod:accept_recv_pdu(Arg1, Arg2, Type)) of
false ->
inc(netIfPduInDrops);
_ ->
handle_recv_pdu(
Domain, Addr, Vsn, Pdu, PduMS, ACM, Logger, State)
end;
maybe_handle_recv_pdu(
Domain, Addr, Vsn, Trap, PduMS, ACM, Logger,
#state{filter = FilterMod, transports = Transports} = State)
when is_record(Trap, trappdu) ->
{Arg1, Arg2} = fix_filter_address(Transports, {Domain, Addr}),
case (catch FilterMod:accept_recv_pdu(Arg1, Arg2, trappdu)) of
false ->
inc(netIfPduInDrops);
_ ->
handle_recv_pdu(
Domain, Addr, Vsn, Trap, PduMS, ACM, Logger, State)
end;
maybe_handle_recv_pdu(
Domain, Addr, Vsn, Pdu, PduMS, ACM, Logger, State) ->
handle_recv_pdu(Domain, Addr, Vsn, Pdu, PduMS, ACM, Logger, State).
handle_recv_pdu(
Domain, Addr, Vsn,
#pdu{type = 'inform-request'} = Pdu, _PduMS, ACM, Logger,
#state{server = Pid, irb = IRB} = State) ->
handle_inform_request(
IRB, Pid, Vsn, Pdu, ACM, Domain, Addr, Logger, State);
handle_recv_pdu(
Domain, Addr, _Vsn,
#pdu{type = report} = Pdu, _PduMS, ok, _Logger,
#state{server = Pid} = _State) ->
?vtrace("received report - ok", []),
Pid ! {snmp_report, {ok, Pdu}, Domain, Addr};
handle_recv_pdu(
Domain, Addr, _Vsn,
#pdu{type = report} = Pdu, _PduMS, {error, ReqId, Reason}, _Logger,
#state{server = Pid} = _State) ->
?vtrace("received report - error", []),
Pid ! {snmp_report, {error, ReqId, Reason, Pdu}, Domain, Addr};
handle_recv_pdu(
Domain, Addr, _Vsn,
#pdu{type = 'snmpv2-trap'} = Pdu, _PduMS, _ACM, _Logger,
#state{server = Pid} = _State) ->
?vtrace("received snmpv2-trap", []),
Pid ! {snmp_trap, Pdu, Domain, Addr};
handle_recv_pdu(
Domain, Addr, _Vsn, Trap, _PduMS, _ACM, _Logger,
#state{server = Pid} = _State) when is_record(Trap, trappdu) ->
?vtrace("received trappdu", []),
Pid ! {snmp_trap, Trap, Domain, Addr};
handle_recv_pdu(
Domain, Addr, _Vsn, Pdu, _PduMS, _ACM, _Logger,
#state{server = Pid} = _State) when is_record(Pdu, pdu) ->
?vtrace("received pdu", []),
Pid ! {snmp_pdu, Pdu, Domain, Addr};
handle_recv_pdu(
_Domain, _Addr, _Vsn, Pdu, _PduMS, ACM, _Logger, _State) ->
?vlog("received unexpected pdu: "
"~n Pdu: ~p"
"~n ACM: ~p", [Pdu, ACM]).
handle_inform_request(
auto, Pid, Vsn, Pdu, ACM, Domain, Addr, Logger, State) ->
?vtrace("received inform-request (true)", []),
Pid ! {snmp_inform, ignore, Pdu, Domain, Addr},
RePdu = make_response_pdu(Pdu),
maybe_send_inform_response(RePdu, Vsn, ACM, Domain, Addr, Logger, State);
handle_inform_request(
{user, To}, Pid, Vsn, #pdu{request_id = ReqId} = Pdu,
ACM, Domain, Addr, _Logger, _State) ->
?vtrace("received inform-request (false)", []),
Pid ! {snmp_inform, ReqId, Pdu, Domain, Addr},
%% Before we go any further, we need to check that we have not
%% already received this message (possible resend).
Key = {ReqId, Domain, Addr},
case ets:lookup(snmpm_inform_request_table, Key) of
[_] ->
%% OK, we already know about this. We assume this
%% is a resend. Either the agent is really eager or
%% the user has not answered yet. Bad user!
ok;
[] ->
RePdu = make_response_pdu(Pdu),
Expire = t() + To,
Rec = {Key, Expire, {Vsn, ACM, RePdu}},
ets:insert(snmpm_inform_request_table, Rec)
end.
handle_inform_response(Ref, Domain, Addr, State) ->
?worker(
S, handle_inform_response_mt(Ref, Domain, Addr, S),
fun (Pid, Class, Reason, Stacktrace) ->
warning_msg(
"Worker process (~p) terminated "
"while processing (outgoing) inform response for %s:~n"
"~w:~w at ~p",
[Pid, snmp_conf:mk_addr_string({Domain, Addr}),
Class, Reason, Stacktrace])
end,
State).
handle_inform_response_mt(Ref, Domain, Addr, State) ->
Key = {Ref, Domain, Addr},
case ets:lookup(snmpm_inform_request_table, Key) of
[{Key, _, {Vsn, ACM, RePdu}}] ->
Logger = logger(State#state.log, read, Domain, Addr),
ets:delete(snmpm_inform_request_table, Key),
maybe_send_inform_response(
RePdu, Vsn, ACM, Domain, Addr, Logger, State);
[] ->
%% Already acknowledged, or the user was to slow to reply...
ok
end,
ok.
maybe_send_inform_response(
RePdu, Vsn, ACM, Domain, Addr, Logger,
#state{
server = Pid,
filter = FilterMod,
transports = Transports} = State) ->
{Arg1, Arg2} = fix_filter_address(Transports, {Domain, Addr}),
case (catch FilterMod:accept_send_pdu(
Arg1, Arg2, pdu_type_of(RePdu)))
of
false ->
inc(netIfPduOutDrops),
ok;
_ ->
case snmpm_mpd:generate_response_msg(Vsn, RePdu, ACM, Logger) of
{ok, Msg} ->
maybe_udp_send(Domain, Addr, Msg, State);
{discarded, Reason} ->
?vlog("failed generating response message:"
"~n Reason: ~p", [Reason]),
ReqId = RePdu#pdu.request_id,
ErrorInfo = {failed_generating_response, {RePdu, Reason}},
Pid ! {snmp_error, ReqId, ErrorInfo, Domain, Addr}
end
end.
handle_inform_response_gc(#state{irb = IRB} = State) ->
ets:safe_fixtable(snmpm_inform_request_table, true),
do_irgc(ets:first(snmpm_inform_request_table), t()),
ets:safe_fixtable(snmpm_inform_request_table, false),
State#state{irgc = irgc_start(IRB)}.
%% We are deleting at the same time as we are traversing the table!!!
do_irgc('$end_of_table', _) ->
ok;
do_irgc(Key, Now) ->
Next = ets:next(snmpm_inform_request_table, Key),
case ets:lookup(snmpm_inform_request_table, Key) of
[{Key, BestBefore, _}] when BestBefore < Now ->
ets:delete(snmpm_inform_request_table, Key);
_ ->
ok
end,
do_irgc(Next, Now).
irgc_start(auto) ->
undefined;
irgc_start(_) ->
erlang:send_after(?IRGC_TIMEOUT, self(), inform_response_gc).
irgc_stop(undefined) ->
ok;
irgc_stop(Ref) ->
(catch erlang:cancel_timer(Ref)).
maybe_handle_send_pdu(Pdu, Vsn, MsgData, Domain, Addr, State) ->
?worker(
S, maybe_handle_send_pdu_mt(Pdu, Vsn, MsgData, Domain, Addr, S),
fun (Pid, Class, Reason, Stacktrace) ->
warning_msg(
"Worker process (~p) terminated "
"while processing (outgoing) pdu for %s:~n"
"~w:~w at ~p",
[Pid, snmp_conf:mk_addr_string({Domain, Addr}),
Class, Reason, Stacktrace])
end,
State).
maybe_handle_send_pdu_mt(
Pdu, Vsn, MsgData, Domain, Addr,
#state{filter = FilterMod, transports = Transports} = State) ->
{Arg1, Arg2} = fix_filter_address(Transports, {Domain, Addr}),
case (catch FilterMod:accept_send_pdu(Arg1, Arg2, pdu_type_of(Pdu))) of
false ->
inc(netIfPduOutDrops);
_ ->
handle_send_pdu(Pdu, Vsn, MsgData, Domain, Addr, State)
end,
ok.
handle_send_pdu(
Pdu, Vsn, MsgData, Domain, Addr,
#state{
server = Pid,
note_store = NoteStore,
log = Log} = State) ->
Logger = logger(Log, write, Domain, Addr),
case (catch snmpm_mpd:generate_msg(
Vsn, NoteStore, Pdu, MsgData, Logger)) of
{ok, Msg} ->
?vtrace("handle_send_pdu -> message generated", []),
maybe_udp_send(Domain, Addr, Msg, State);
{discarded, Reason} ->
?vlog("PDU not sent: "
"~n PDU: ~p"
"~n Reason: ~p", [Pdu, Reason]),
Pid ! {snmp_error, Pdu, Reason}
end.
maybe_udp_send(
Domain, Addr, Msg,
#state{filter = FilterMod, transports = Transports}) ->
To = {Domain, Addr},
{Arg1, Arg2} = fix_filter_address(Transports, To),
case (catch FilterMod:accept_send(Arg1, Arg2)) of
false ->
inc(netIfMsgOutDrops),
ok;
_ ->
case select_transport_from_domain(Domain, Transports) of
false ->
error_msg(
"Can not find transport~n"
" size: ~p~n"
" to: ~s",
[sz(Msg), format_address(To)]);
#transport{socket = Socket} ->
udp_send(Socket, Addr, Msg)
end
end.
udp_send(Sock, To, Msg) ->
{IpAddr, IpPort} =
case To of
{Domain, Addr} when is_atom(Domain) ->
Addr;
{_, P} = Addr when is_integer(P) ->
Addr
end,
try gen_udp:send(Sock, IpAddr, IpPort, Msg) of
ok ->
?vdebug("sent ~w bytes to ~w:~w [~w]",
[sz(Msg), IpAddr, IpPort, Sock]),
ok;
{error, Reason} ->
error_msg("failed sending message to ~p:~p:~n"
" ~p",[IpAddr, IpPort, Reason])
catch
error:Error ->
error_msg("failed sending message to ~p:~p:~n"
" error:~p~n"
" ~p",
[IpAddr, IpPort, Error, erlang:get_stacktrace()])
end.
sz(B) when is_binary(B) ->
byte_size(B);
sz(L) when is_list(L) ->
length(L);
sz(_) ->
undefined.
handle_disk_log(_Log, {wrap, NoLostItems}, State) ->
?vlog("Audit Trail Log - wrapped: ~w previously logged items where lost",
[NoLostItems]),
State;
handle_disk_log(_Log, {truncated, NoLostItems}, State) ->
?vlog("Audit Trail Log - truncated: ~w items where lost when truncating",
[NoLostItems]),
State;
handle_disk_log(_Log, full, State) ->
error_msg("Failed to write to Audit Trail Log (full)", []),
State;
handle_disk_log(_Log, {error_status, ok}, State) ->
State;
handle_disk_log(_Log, {error_status, {error, Reason}}, State) ->
error_msg("Error status received from Audit Trail Log: "
"~n~p", [Reason]),
State;
handle_disk_log(_Log, _Info, State) ->
State.
%% mk_discovery_msg('version-3', Pdu, _VsnHdr, UserName) ->
%% ScopedPDU = #scopedPdu{contextEngineID = "",
%% contextName = "",
%% data = Pdu},
%% Bytes = snmp_pdus:enc_scoped_pdu(ScopedPDU),
%% MsgID = get(msg_id),
%% put(msg_id,MsgID+1),
%% UsmSecParams =
%% #usmSecurityParameters{msgAuthoritativeEngineID = "",
%% msgAuthoritativeEngineBoots = 0,
%% msgAuthoritativeEngineTime = 0,
%% msgUserName = UserName,
%% msgPrivacyParameters = "",
%% msgAuthenticationParameters = ""},
%% SecBytes = snmp_pdus:enc_usm_security_parameters(UsmSecParams),
%% PduType = Pdu#pdu.type,
%% Hdr = #v3_hdr{msgID = MsgID,
%% msgMaxSize = 1000,
%% msgFlags = snmp_misc:mk_msg_flags(PduType, 0),
%% msgSecurityModel = ?SEC_USM,
%% msgSecurityParameters = SecBytes},
%% Msg = #message{version = 'version-3', vsn_hdr = Hdr, data = Bytes},
%% case (catch snmp_pdus:enc_message_only(Msg)) of
%% {'EXIT', Reason} ->
%% error("Encoding error. Pdu: ~w. Reason: ~w",[Pdu, Reason]),
%% error;
%% L when list(L) ->
%% {Msg, L}
%% end;
%% mk_discovery_msg(Version, Pdu, {Com, _, _, _, _}, UserName) ->
%% Msg = #message{version = Version, vsn_hdr = Com, data = Pdu},
%% case catch snmp_pdus:enc_message(Msg) of
%% {'EXIT', Reason} ->
%% error("Encoding error. Pdu: ~w. Reason: ~w",[Pdu, Reason]),
%% error;
%% L when list(L) ->
%% {Msg, L}
%% end.
%% mk_msg('version-3', Pdu, {Context, User, EngineID, CtxEngineId, SecLevel},
%% MsgData) ->
%% %% Code copied from snmp_mpd.erl
%% {MsgId, SecName, SecData} =
%% if
%% tuple(MsgData), Pdu#pdu.type == 'get-response' ->
%% MsgData;
%% true ->
%% Md = get(msg_id),
%% put(msg_id, Md + 1),
%% {Md, User, []}
%% end,
%% ScopedPDU = #scopedPdu{contextEngineID = CtxEngineId,
%% contextName = Context,
%% data = Pdu},
%% ScopedPDUBytes = snmp_pdus:enc_scoped_pdu(ScopedPDU),
%% PduType = Pdu#pdu.type,
%% V3Hdr = #v3_hdr{msgID = MsgId,
%% msgMaxSize = 1000,
%% msgFlags = snmp_misc:mk_msg_flags(PduType, SecLevel),
%% msgSecurityModel = ?SEC_USM},
%% Message = #message{version = 'version-3', vsn_hdr = V3Hdr,
%% data = ScopedPDUBytes},
%% SecEngineID = case PduType of
%% 'get-response' -> snmp_framework_mib:get_engine_id();
%% _ -> EngineID
%% end,
%% case catch snmp_usm:generate_outgoing_msg(Message, SecEngineID,
%% SecName, SecData, SecLevel) of
%% {'EXIT', Reason} ->
%% error("Encoding error. Pdu: ~w. Reason: ~w",[Pdu, Reason]),
%% error;
%% {error, Reason} ->
%% error("Encoding error. Pdu: ~w. Reason: ~w",[Pdu, Reason]),
%% error;
%% Packet ->
%% Packet
%% end;
%% mk_msg(Version, Pdu, {Com, _User, _EngineID, _Ctx, _SecLevel}, _SecData) ->
%% Msg = #message{version = Version, vsn_hdr = Com, data = Pdu},
%% case catch snmp_pdus:enc_message(Msg) of
%% {'EXIT', Reason} ->
%% error("Encoding error. Pdu: ~w. Reason: ~w",[Pdu, Reason]),
%% error;
%% B when list(B) ->
%% B
%% end.
%% handle_system_info_updated(#state{log = {Log, _OldType}} = State,
%% audit_trail_log_type = _What) ->
%% %% Just to make sure, check that ATL is actually enabled
%% case snmpm_config:system_info(audit_trail_log) of
%% {ok, true} ->
%% {ok, Type} = snmpm_config:system_info(audit_trail_log_type),
%% NewState = State#state{log = {Log, Type}},
%% {NewState, ok};
%% _ ->
%% {State, {error, {adt_not_enabled}}}
%% end;
%% handle_system_info_updated(_State, _What) ->
%% ok.
handle_get_log_type(#state{log = {_Log, Value}} = State) ->
%% Just to make sure, check that ATL is actually enabled
case snmpm_config:system_info(audit_trail_log) of
{ok, true} ->
Type =
case {lists:member(read, Value), lists:member(write, Value)} of
{true, true} ->
read_write;
{true, false} ->
read;
{false, true} ->
write;
{false, false} ->
throw({State, {error, {bad_atl_type, Value}}})
end,
{ok, Type};
_ ->
{error, not_enabled}
end;
handle_get_log_type(_State) ->
{error, not_enabled}.
handle_set_log_type(#state{log = {Log, OldValue}} = State, NewType) ->
%% Just to make sure, check that ATL is actually enabled
case snmpm_config:system_info(audit_trail_log) of
{ok, true} ->
NewValue =
case NewType of
read ->
[read];
write ->
[write];
read_write ->
[read,write];
_ ->
throw({State, {error, {bad_atl_type, NewType}}})
end,
NewState = State#state{log = {Log, NewValue}},
OldType =
case {lists:member(read, OldValue),
lists:member(write, OldValue)} of
{true, true} ->
read_write;
{true, false} ->
read;
{false, true} ->
write;
{false, false} ->
throw({State, {error, {bad_atl_type, OldValue}}})
end,
{NewState, {ok, OldType}};
_ ->
{State, {error, not_enabled}}
end;
handle_set_log_type(State, _NewType) ->
{State, {error, not_enabled}}.
select_transport_from_domain(Domain, Transports) when is_atom(Domain) ->
Pos = #transport.domain,
case lists:keyfind(Domain, Pos, Transports) of
#transport{domain = Domain} = Transport ->
Transport;
false when Domain == snmpUDPDomain ->
lists:keyfind(transportDomainUdpIpv4, Pos, Transports);
false when Domain == transportDomainUdpIpv4 ->
lists:keyfind(snmpUDPDomain, Pos, Transports);
false ->
false
end.
%% If the manager uses legacy snmpUDPDomain e.g has not set
%% {domain, _}, then make sure snmpm_network_interface_filter
%% gets legacy arguments to not break backwards compatibility.
%%
fix_filter_address(Transports, Address) ->
DefaultDomain = snmpm_config:default_transport_domain(),
case Transports of
[#transport{domain = DefaultDomain}, DefaultDomain] ->
case Address of
{Domain, Addr} when is_atom(Domain) ->
Addr;
{_, IpPort} = Addr when is_integer(IpPort) ->
Addr
end;
_ ->
Address
end.
address(Domain, Addr) when is_atom(Domain) ->
{Domain, Addr};
address(Ip, Port) when is_integer(Port) ->
{snmpm_config:default_transport_domain(), {Ip, Port}}.
format_address(Address) ->
iolist_to_binary(snmp_conf:mk_addr_string(Address)).
%% -------------------------------------------------------------------
make_response_pdu(#pdu{request_id = ReqId, varbinds = Vbs}) ->
#pdu{type = 'get-response',
request_id = ReqId,
error_status = noError,
error_index = 0,
varbinds = Vbs}.
%% ----------------------------------------------------------------
pdu_type_of(#pdu{type = Type}) ->
Type;
pdu_type_of(TrapPdu) when is_record(TrapPdu, trappdu) ->
trap.
%% -------------------------------------------------------------------
%% At this point this function is used during testing
maybe_process_extra_info(?DEFAULT_EXTRA_INFO) ->
ok;
maybe_process_extra_info({?SNMPM_EXTRA_INFO_TAG, Fun})
when is_function(Fun, 0) ->
(catch Fun()),
ok;
maybe_process_extra_info(_ExtraInfo) ->
ok.
%% -------------------------------------------------------------------
t() ->
{A,B,C} = erlang:now(),
A*1000000000+B*1000+(C div 1000).
%% -------------------------------------------------------------------
%% info_msg(F, A) ->
%% ?snmpm_info("NET-IF server: " ++ F, A).
warning_msg(F, A) ->
?snmpm_warning("NET-IF server: " ++ F, A).
error_msg(F, A) ->
?snmpm_error("NET-IF server: " ++ F, A).
%%%-------------------------------------------------------------------
% get_opt(Key, Opts) ->
% ?vtrace("get option ~w", [Key]),
% snmp_misc:get_option(Key, Opts).
get_opt(Opts, Key, Def) ->
?vtrace("get option ~w with default ~p", [Key, Def]),
snmp_misc:get_option(Key, Opts, Def).
%% -------------------------------------------------------------------
get_info(#state{transports = Transports}) ->
ProcSize = proc_mem(self()),
[{process_memory, ProcSize}
| [{port_info, get_port_info(Socket)}
|| #transport{socket = Socket} <- Transports]].
proc_mem(P) when is_pid(P) ->
case (catch erlang:process_info(P, memory)) of
{memory, Sz} when is_integer(Sz) ->
Sz;
_ ->
undefined
end.
%% proc_mem(_) ->
%% undefined.
get_port_info(Id) ->
PortInfo =
case (catch erlang:port_info(Id)) of
PI when is_list(PI) ->
[{port_info, PI}];
_ ->
[]
end,
PortStatus =
case (catch prim_inet:getstatus(Id)) of
{ok, PS} ->
[{port_status, PS}];
_ ->
[]
end,
PortAct =
case (catch inet:getopts(Id, [active])) of
{ok, PA} ->
[{port_act, PA}];
_ ->
[]
end,
PortStats =
case (catch inet:getstat(Id)) of
{ok, Stat} ->
[{port_stats, Stat}];
_ ->
[]
end,
IfList =
case (catch inet:getif(Id)) of
{ok, IFs} ->
[{interfaces, IFs}];
_ ->
[]
end,
BufSz =
case (catch inet:getopts(Id, [recbuf, sndbuf, buffer])) of
{ok, Sz} ->
[{buffer_size, Sz}];
_ ->
[]
end,
[{socket, Id}] ++
IfList ++
PortStats ++
PortInfo ++
PortStatus ++
PortAct ++
BufSz.
%%-----------------------------------------------------------------
%% Counter functions
%%-----------------------------------------------------------------
init_counters() ->
F = fun(Counter) -> maybe_create_counter(Counter) end,
lists:map(F, counters()).
reset_counters() ->
F = fun(Counter) -> snmpm_config:reset_stats_counter(Counter) end,
lists:map(F, counters()).
maybe_create_counter(Counter) ->
snmpm_config:maybe_cre_stats_counter(Counter, 0).
counters() ->
[
netIfMsgOutDrops,
netIfMsgInDrops,
netIfPduOutDrops,
netIfPduInDrops
].
inc(Name) -> inc(Name, 1).
inc(Name, N) -> snmpm_config:incr_stats_counter(Name, N).
%% get_counters() ->
%% Counters = counters(),
%% get_counters(Counters, []).
%% get_counters([], Acc) ->
%% lists:reverse(Acc);
%% get_counters([Counter|Counters], Acc) ->
%% case snmpm_config:get_stats_counter(Counter) of
%% {ok, CounterVal} ->
%% get_counters(Counters, [{Counter, CounterVal}|Acc]);
%% _ ->
%% get_counters(Counters, Acc)
%% end.
%% ----------------------------------------------------------------
call(Pid, Req) ->
call(Pid, Req, infinity).
call(Pid, Req, Timeout) ->
gen_server:call(Pid, Req, Timeout).
cast(Pid, Msg) ->
gen_server:cast(Pid, Msg).