/
usr
/
lib64
/
erlang
/
lib
/
snmp-5.12
/
src
/
agent
/
File Upload :
llllll
Current File: //usr/lib64/erlang/lib/snmp-5.12/src/agent/snmpa_net_if.erl
%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2004-2022. 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(snmpa_net_if). -behaviour(snmpa_network_interface). -export([start_link/4, info/1, verbosity/2]). -export([get_log_type/1, set_log_type/2]). -export([get_request_limit/1, set_request_limit/2]). -export([system_continue/3, system_terminate/4, system_code_change/4]). -export([init/5]). -export([filter_reset/1]). -include("snmp_types.hrl"). -include("snmpa_internal.hrl"). -include("snmpa_atl.hrl"). -include("snmp_debug.hrl"). -include("snmp_verbosity.hrl"). %% Regarding Trap/Notification transport(s), %% it *should* be possible to specify either: %% 1) A fixed set of transport(s) used for sending. %% For instance, one for IPv4 and one for IPv6. %% 2) A single one-shot ephemeral port. %% That is, a port is created, used once, and then closed. %% 3) A pool of ephemeral ports, used for "a time" %% (a configurable number of sends, a set number of bytes %% or time based) thar is cycled through. %% Though, we do *not* currently implement ephemeral sockets, %% so case 2 and 3 are not possible at the moment. %% Also, the request-reponder and trap-sender transport(s), %% has different needs. %% The trap-sender transport will send more data then it will receive. %% Therefor, we should be able to specify; %% bind_to, no_reuse_address, recbuf and sndbuf individually: %% {intAgentTransports, %% [{transportDomainUdpIpv4, {{141,213,11,24}, PortInfo}, %% req_responder, Opts}, %% {transportDomainUdpIpv4, {{141,213,11,24}, PortInfo}, %% trap_sender, Opts}, %% {transportDomainUdpIpv6, {{0,0,0,0,0,0,0,1}, Portinfo}, %% req_responder, Opts}, %% {transportDomainUdpIpv6, {{0,0,0,0,0,0,0,1}, Portinfo}, %% trap_sender, Opts}]}. %% Opts is basically "socket options" for this transport (the same %% options as for net-if). %% Also, always give the option to provide a port range: %% Port :: pos_integer() | system | range() || ranges() %% system => Let the system choose %% (0 is unfortunately already used as 'default', %% so we can't use that for 'system'). %% range() :: {Min :: pos_integer(), Max :: pos_integer()} when Min < Max %% ranges() :: [pos_integer() | range()] %% Examples: %% [{2000, 2004}] %% [2000, 2001, 2002, 2003, 2004] %% [2000, 2001, {2002, 2004}] %% [{5000, 5100}, {6000, 6100}] %% <EPHEMERAL-FOR-FUTUR-USE> %% , *but* %% may also contain the tuple, {ephemeral, EphmOpts}. %% Ephm sockets are created on the fly, used for the specified %% time (number of sends, number of bytes sent, used time, ...). %% </EPHEMERAL-FOR-FUTUR-USE> -record(state, {parent, note_store, master_agent, transports = [], mpd_state, log, reqs = [], debug = false, limit = infinity, filter}). -type transport_kind() :: req_responder | trap_sender. -type port_info() :: non_neg_integer() | system | {pos_integer(), pos_integer()}. %% <EPHEMERAL-FOR-FUTUR-USE> %% How would 'ephemeral' effect this? %% What kind of usage would we have for emphemeral ports? %% once (send and maybe receive reply (inform)) | %% {sends, pos_integer()} (number of sends) | %% {data, pos_integer()} (number of bytes sent) | %% {alive_time, pos_integer()} (once used for the first time, alive time) %% You can't combine a ephemeral info with a fixed port (pos_integer()) %% The port_info() is used when creating a port, even an ephemeral port. %% But it must either be 'system' or a range in that (ephemeral) case. %% Also, ephemeral transports are only allowed if kind = trap_sender %% -type ephemeral() :: none | %% once | %% {sends, pos_integer()} | %% {data, pos_integer()} | %% {alive_time, pos_integer()}. %% Note that since informs require confirmation, %% an ephemeral socket cannot be removed immediately %% when it has been "used up". %% We need to keep it for some time to receive responces %% and in case a resend is needed!. %% </EPHEMERAL-FOR-FUTUR-USE> -record(transport, {socket, mref, kind = all :: all | transport_kind(), domain = snmpUDPDomain, address :: inet:ip_address(), port_no :: pos_integer(), port_info :: port_info(), %% <EPHEMERAL-FOR-FUTUR-USE> ephm = none, %% :: ephemeral(), ephm_info = undefined, % Only used if ephm =/= none and once %% </EPHEMERAL-FOR-FUTUR-USE> inet_backend = [], opts = [], req_refs = [] % Not used for trap/notification transports }). -ifndef(default_verbosity). -define(default_verbosity,silence). -endif. -define(DEFAULT_FILTER_MODULE, snmpa_net_if_filter). -define(DEFAULT_FILTER_OPTS, [{module, ?DEFAULT_FILTER_MODULE}]). -define(ATL_SEQNO_INITIAL, 1). -define(ATL_SEQNO_MAX, 2147483647). %%%----------------------------------------------------------------- %%% This module implements the default Network Interface part %%% of the SNMP agent. It uses UDP, and read the agent.conf to find %%% the UDP port. %%% %%% Opts = [Opt] %%% Opt = {verbosity,silence | log | debug} | %%% {bind_to, bool()} | %%% {recbuf,integer()} | %%% {no_reuse, bool()} | %%% {req_limit, integer() | infinity} %%%----------------------------------------------------------------- start_link(Prio, NoteStore, MasterAgent, Opts) -> ?d("start_link -> entry with" "~n Prio: ~p" "~n NoteStore: ~p" "~n MasterAgent: ~p" "~n Opts: ~p", [Prio, NoteStore, MasterAgent, Opts]), Args = [Prio, NoteStore, MasterAgent, self(), Opts], proc_lib:start_link(?MODULE, init, Args). info(Pid) -> case call(Pid, info) of Info when is_list(Info) -> Info; _ -> [] end. verbosity(Pid, Verbosity) -> Pid ! {verbosity, Verbosity}. get_log_type(Pid) -> call(Pid, get_log_type). set_log_type(Pid, NewType) -> call(Pid, {set_log_type, NewType}). get_request_limit(Pid) -> call(Pid, get_request_limit). set_request_limit(Pid, NewLimit) -> call(Pid, {set_request_limit, NewLimit}). get_transports() -> {value, Transports} = snmp_framework_mib:intAgentTransports(get), Transports. filter_reset(Pid) -> Pid ! filter_reset. %%----------------------------------------------------------------- %%----------------------------------------------------------------- init(Prio, NoteStore, MasterAgent, Parent, Opts) -> ?d("init -> entry with" "~n Prio: ~p" "~n NoteStore: ~p" "~n MasterAgent: ~p" "~n Parent: ~p" "~n Opts: ~p", [Prio, NoteStore, MasterAgent, Parent, Opts]), case (catch do_init(Prio, NoteStore, MasterAgent, Parent, Opts)) of {ok, State} -> proc_lib:init_ack({ok, self()}), try loop(State) catch C:E:S when C =/= exit, E =/= shutdown -> Fmt = "loop/1 EXCEPTION ~w:~w~n" " ~p", case C of exit -> %% Externally killed, root cause is elsewhere info_msg(Fmt, [C, E, S]); _ -> error_msg(Fmt, [C, E, S]) end, erlang:raise(C, E, S) end; {error, Reason} -> config_err("failed starting net-if: ~n~p", [Reason]), proc_lib:init_ack({error, Reason}); Error -> config_err("failed starting net-if: ~n~p", [Error]), proc_lib:init_ack({error, Error}) end. do_init(Prio, NoteStore, MasterAgent, Parent, Opts) -> process_flag(trap_exit, true), process_flag(priority, Prio), %% -- Verbosity -- put(sname, nif), put(verbosity, get_verbosity(Opts)), ?vlog("starting",[]), %% -- Versions -- Vsns = get_vsns(Opts), ?vdebug("vsns: ~w",[Vsns]), %% Flow control -- Limit = get_req_limit(Opts), ?vdebug("Limit: ~w", [Limit]), FilterOpts = get_filter_opts(Opts), FilterMod = create_filter(FilterOpts), ?vdebug("FilterMod: ~w FilterOpts: ~p", [FilterMod,FilterOpts]), %% -- Audit trail log Log = create_log(), ?vdebug("Log: ~w",[Log]), RawTransports = get_transports(), ?vdebug("Raw Transports: " "~n ~p", [RawTransports]), try [begin %% Any socket option not explicitly configured for the transport %% will be taken from the "global" socket options (which serve as %% default values). %% Also, note that Ephm are not actually used at this time. {Ephm, IpAddr, PortInfo, InetBackend, SocketOpts} = socket_opts(Domain, Address, RawSocketOpts, Opts), ?vtrace("socket opts processed:" "~n Ephm: ~p" "~n Port Info: ~p" "~n Inet Backend: ~p" "~n Socket Opts: ~p", [Ephm, PortInfo, InetBackend, SocketOpts]), {Socket, IpPort} = socket_open(Domain, PortInfo, InetBackend, SocketOpts), SockMRef = inet:monitor(Socket), ?vtrace("socket opened:" "~n Socket: ~p" "~n Port No: ~p" "~n Info: ~p", [Socket, IpPort, inet:info(Socket)]), %% Should we really do this here? %% If Kind =:= trap_sender, we only need to receive after %% we have sent an inform! active_once(Socket), ?vtrace("socket activated:" "~n Info: ~p", [inet:info(Socket)]), #transport{ socket = Socket, mref = SockMRef, kind = Kind, domain = Domain, %% We may not have explicitly specified the port ('system' %% or a range), so it could have been "generated". %% Also, shall we push this into the transport (handled by the %% FRAMEWORK MIB)? Would not work for ephemeral sockets. address = IpAddr, port_no = IpPort, port_info = PortInfo, ephm = Ephm, inet_backend = InetBackend, opts = SocketOpts} %% We need to fix this also end || {Domain, Address, Kind, RawSocketOpts} <- RawTransports] of [] -> ?vinfo("No transports configured: ~p", [RawTransports]), {error, {no_transports, RawTransports}}; Transports -> MpdState = snmpa_mpd:init(Vsns), init_counters(), S = #state{parent = Parent, note_store = NoteStore, master_agent = MasterAgent, mpd_state = MpdState, transports = Transports, log = Log, limit = Limit, filter = FilterMod}, ?vdebug("started with MpdState: ~p", [MpdState]), {ok, S} catch Error -> ?vinfo("Failed to initialize socket(s): ~p", [Error]), {error, Error} end. create_log() -> case ets:lookup(snmp_agent_table, audit_trail_log) of [] -> {undefined, []}; [{audit_trail_log, AtlOpts}] -> ?vtrace("AtlOpts: ~p", [AtlOpts]), Type = get_atl_type(AtlOpts), Dir = get_atl_dir(AtlOpts), Size = get_atl_size(AtlOpts), Repair = get_atl_repair(AtlOpts), Name = ?audit_trail_log_name, File = filename:absname(?audit_trail_log_file, Dir), case get_atl_seqno(AtlOpts) of true -> Initial = ?ATL_SEQNO_INITIAL, Max = ?ATL_SEQNO_MAX, Module = snmpa_agent, 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]), {Log, Type}; {error, Reason} -> throw({error, {create_log, Reason}}) end; _ -> case snmp_log:create(Name, File, Size, Repair, true) of {ok, Log} -> ?vdebug("log created: ~w", [Log]), {Log, Type}; {error, Reason} -> throw({error, {create_log, Reason}}) end end end. create_filter(Opts) when is_list(Opts) -> case get_filter_module(Opts) of ?DEFAULT_FILTER_MODULE = Mod -> Mod; Module -> snmpa_network_interface_filter:verify(Module), Module end; create_filter(BadOpts) -> throw({error, {bad_filter_opts, BadOpts}}). log({_, []}, _, _, _) -> ok; log({Log, Types}, 'set-request', Packet, Address) -> case lists:member(write, Types) of true -> snmp_log:log(Log, Packet, format_address(Address)); false -> ok end; log({Log, Types}, _, Packet, Address) -> case lists:member(read, Types) of true -> snmp_log:log(Log, Packet, format_address(Address)); false -> ok end. format_address(Address) -> iolist_to_binary(snmp_conf:mk_addr_string(Address)). socket_open(snmpUDPDomain = Domain, IpPort, InetBackend, Opts) -> ?vdebug("socket_open(~p) -> entry with" "~n Port: ~p" "~n Opts: ~p", [Domain, IpPort, Opts]), case init:get_argument(snmp_fd) of {ok, [[FdStr]]} -> FD = list_to_integer(FdStr), ?vdebug("socket_open(~p) -> open with fd - " "(old) snmp_fd command line argument provided: " "~n FD: ~p" "~n Port: ~p" "~n Opts: ~p", [Domain, FD, IpPort, Opts]), gen_udp_open(0, InetBackend ++ [{fd, FD} | Opts]); error -> case init:get_argument(snmpa_fd) of {ok, [[FdStr]]} -> FD = list_to_integer(FdStr), ?vdebug("socket_open(~p) -> open with fd - " "snmpa_fd command line argument provided: " "~n FD: ~p" "~n Port: ~p" "~n Opts: ~p", [Domain, FD, IpPort, Opts]), gen_udp_open(0, InetBackend ++ [{fd, FD} | Opts]); error -> ?vdebug("socket_open(~p) -> plain open" "~n Port: ~p" "~n Opts: ~p", [Domain, IpPort, Opts]), gen_udp_open(IpPort, InetBackend ++ Opts) end end; socket_open(Domain, PortInfo, InetBackend, Opts) when (Domain =:= transportDomainUdpIpv4) orelse (Domain =:= transportDomainUdpIpv6) -> ?vdebug("socket_open(~p) -> entry with" "~n PortInfo: ~p" "~n InetBackend: ~p" "~n Opts: ~p", [Domain, PortInfo, InetBackend, Opts]), gen_udp_open(PortInfo, InetBackend ++ Opts); socket_open(Domain, PortInfo, InetBackend, Opts) -> ?vinfo("socket_open(~p) -> entry when invalid with" "~n PortInfo: ~p" "~n InetBackend: ~p" "~n Opts: ~p", [Domain, PortInfo, InetBackend, Opts]), throw({socket_open, Domain, InetBackend, Opts}). %% Make the system choose! gen_udp_open(system, Opts) -> ?vtrace("gen_udp_open(system) -> entry"), case gen_udp:open(0, Opts) of {ok, Socket} -> case inet:port(Socket) of {ok, PortNo} -> ?vtrace("gen_udp_open(system) -> created: " "~n ~p (~w)", [Socket, PortNo]), {Socket, PortNo}; {error, PReason} -> (catch gen_udp:close(Socket)), throw({udp_open, {port, PReason}}) end; {error, OReason} -> throw({udp_open, {open, OReason}}) end; %% This is for "future compat" since we cannot actually config '0'... gen_udp_open(IpPort, Opts) when (IpPort =:= 0) -> ?vtrace("gen_udp_open(0) -> entry with" "~n Opts: ~p", [Opts]), case gen_udp:open(IpPort, Opts) of {ok, Socket} -> case inet:port(Socket) of {ok, PortNo} -> ?vtrace("gen_udp_open(0) -> created: " "~n ~p (~w)", [Socket, PortNo]), {Socket, PortNo}; {error, PReason} -> (catch gen_udp:close(Socket)), throw({udp_open, {port, PReason}}) end; {error, Reason} -> throw({udp_open, {open, IpPort, Reason}}) end; gen_udp_open(IpPort, Opts) when is_integer(IpPort) -> ?vtrace("gen_udp_open(~w) -> entry with" "~n Opts: ~p", [IpPort, Opts]), case gen_udp:open(IpPort, Opts) of {ok, Socket} -> ?vtrace("gen_udp_open(~w) -> created: " "~n ~p", [Socket]), {Socket, IpPort}; {error, Reason} -> throw({udp_open, {open, IpPort, Reason}}) end; %% A range is "pointless" if we allow reuseaddr... %% ...but we leave that to the user... gen_udp_open({Min, Max}, Opts) -> ?vtrace("gen_udp_open(~w,~w) -> entry", [Min, Max]), gen_udp_range_open(Min, Max, Opts); %% A range is "pointless" if we allow reuseaddr... %% ...but we leave that to the user... gen_udp_open(Ranges, Opts) when is_list(Ranges) -> gen_udp_ranges_open(Ranges, Opts). gen_udp_range_open(Min, Max, _Opts) when (Min > Max) -> ?vinfo("gen_udp_range_open -> entry when no available ports"), throw({udp_open, no_available_ports}); gen_udp_range_open(Min, Max, Opts) -> ?vtrace("gen_udp_range_open -> entry with" "~n Min: ~w" "~n Max: ~w", [Min, Max]), try gen_udp:open(Min, Opts) of {ok, Socket} -> ?vtrace("gen_udp_range_open(~w,~w) -> created: " "~n ~p", [Min, Max, Socket]), {Socket, Min}; {error, eaddrinuse} -> ?vdebug("gen_udp_range_open(~w,~w) -> eaddrinuse"), gen_udp_range_open(Min+1, Max, Opts); {error, Reason} -> ?vdebug("gen_udp_range_open(~w,~w) -> ~w", [Reason]), throw({udp_open, {open, Reason}}) catch C:E:S -> ?vinfo("gen_udp_range_open(~w,~w) -> failed open socket: " "~n C: ~p" "~n E: ~p" "~n S: ~p", [Min, Max, C, E, S]), erlang:raise(C, E, S) end. gen_udp_ranges_open([], _Opts) -> ?vinfo("gen_udp_ranges_open -> entry when no available ports"), throw({udp_open, no_available_ports}); gen_udp_ranges_open([PortNo|Ranges], Opts) when is_integer(PortNo) andalso (PortNo > 0) -> ?vtrace("gen_udp_ranges_open(~w) -> entry", [PortNo]), try gen_udp_open(PortNo, Opts) of {_Sock, PortNo} = SUCCESS when is_integer(PortNo) -> SUCCESS catch throw:{udp_open, _} -> gen_udp_ranges_open(Ranges, Opts) end; gen_udp_ranges_open([{Min, Max}|Ranges], Opts) -> ?vtrace("gen_udp_ranges_open(~w,~w) -> entry", [Min, Max]), try gen_udp_range_open(Min, Max, Opts) of {_Sock, PortNo} = SUCCESS when is_integer(PortNo) -> SUCCESS catch throw:{udp_open, _} -> gen_udp_ranges_open(Ranges, Opts) end. loop(#state{transports = Transports, limit = Limit, parent = Parent} = S) -> ?vdebug("loop(~p)", [S]), receive {udp, Socket, IpAddr, IpPort, Packet} = Msg -> ?vlog("got paket from ~w:~w on ~w", [IpAddr, IpPort, Socket]), case lists:keyfind(Socket, #transport.socket, Transports) of #transport{socket = Socket, domain = Domain} = Transport -> From = case Domain of snmpUDPDomain -> {IpAddr, IpPort}; _ -> {Domain, {IpAddr, IpPort}} end, loop(maybe_handle_recv(S, Transport, From, Packet)); false -> error_msg("Packet on unknown socket: " "~n ~p", [Msg]), loop(S) end; {udp_error, Socket, Error} -> ?vinfo("got udp-error on ~p: ~w", [Socket, Error]), case lists:keyfind(Socket, #transport.socket, Transports) of #transport{socket = Socket} = Transport -> loop(handle_udp_error(S, Transport, Error)); false -> loop(handle_udp_error_unknown(S, Socket, Error)) end; {info, ReplyRef, Pid} -> Info = get_info(S), Pid ! {ReplyRef, Info, self()}, loop(S); %% response (to get/get_next/get_bulk/set requests) {snmp_response, Vsn, RePdu, Type, ACMData, To, Extra} -> ?vlog("reply pdu: " "~n ~s", [?vapply(snmp_misc, format, [256, "~w", [RePdu]])]), {_, ReqRef} = lists:keyfind(request_ref, 1, Extra), case case (Limit =/= infinity) andalso select_transport(ReqRef, Transports) of false -> select_transport(address_to_domain(To), Type, Transports); T -> T end of false -> error_msg( "Can not find transport for response PDU to: ~s", [format_address(To)]), loop(S); Transport -> NewS = update_req_counter_outgoing(S, Transport, ReqRef), maybe_handle_reply_pdu( NewS, Transport, Vsn, RePdu, Type, ACMData, To), loop(NewS) end; %% Traps/notification {send_pdu, Vsn, Pdu, MsgData, TDomAddrs} -> ?vdebug("send pdu:~n" " Pdu: ~p~n" " TDomAddrs: ~p", [Pdu, TDomAddrs]), NewS = maybe_handle_send_pdu( S, Vsn, Pdu, MsgData, TDomAddrs, undefined), loop(NewS); %% We dont use the extra-info at this time, ... {send_pdu, Vsn, Pdu, MsgData, TDomAddrs, _ExtraInfo} -> ?vdebug("send pdu:~n" " Pdu: ~p~n" " TDomAddrs: ~p", [Pdu, TDomAddrs]), NewS = maybe_handle_send_pdu( S, Vsn, Pdu, MsgData, TDomAddrs, undefined), loop(NewS); %% Informs {send_pdu_req, Vsn, Pdu, MsgData, TDomAddrs, From} -> ?vdebug("send pdu request:~n" " Pdu: ~p~n" " TDomAddrs: ~p~n" " From: ~p", [Pdu, TDomAddrs, toname(From)]), NewS = maybe_handle_send_pdu( S, Vsn, Pdu, MsgData, TDomAddrs, From), loop(NewS); %% We dont use the extra-info at this time, ... {send_pdu_req, Vsn, Pdu, MsgData, TDomAddrs, From, _ExtraInfo} -> ?vdebug("send pdu request:~n" " Pdu: ~p~n" " TDomAddrs: ~p~n" " From: ~p", [Pdu, TDomAddrs, toname(From)]), NewS = maybe_handle_send_pdu( S, Vsn, Pdu, MsgData, TDomAddrs, From), loop(NewS); %% Discovery Inform %% <BACKWARD-COMPAT> {send_discovery, Pdu, MsgData, To, From} -> ?vdebug("received send discovery request: " "~n Pdu: ~p" "~n To: ~p" "~n From: ~p", [Pdu, To, toname(From)]), NewS = handle_send_discovery(S, Pdu, MsgData, To, From), loop(NewS); %% </BACKWARD-COMPAT> %% Discovery Inform {send_discovery, Pdu, MsgData, To, From, ExtraInfo} -> ?vdebug("received send discovery request: " "~n Pdu: ~p" "~n To: ~p" "~n From: ~p" "~n ExtraInfo: ~p", [Pdu, To, toname(From), ExtraInfo]), NewS = handle_send_discovery(S, Pdu, MsgData, To, From), loop(NewS); {discarded_pdu, _Vsn, ReqId, _ACMData, Variable, Extra} -> ?vdebug("discard PDU: ~p - ~p - ~p", [Variable, Extra, Transports]), snmpa_mpd:discarded_pdu(Variable), {_, ReqRef} = lists:keyfind(request_ref, 1, Extra), if Limit =:= infinity -> %% The incoming PDU was not registered loop(update_req_counter_outgoing(S, false, ReqRef)); true -> case select_transport(ReqRef, Transports) of false -> error_msg( "Can not find transport for discarded PDU: ~p", [ReqId]), loop(S); Transport -> loop( update_req_counter_outgoing( S, Transport, ReqRef)) end end; {get_log_type, ReplyRef, Pid} -> ?vdebug("get log type: ~p", []), {_, LogType} = S#state.log, Pid ! {ReplyRef, {ok, LogType}, self()}, loop(S); {{set_log_type, NewType}, ReplyRef, Pid} -> ?vdebug("set log type: ~p", [NewType]), {NewState, Reply} = (catch handle_set_log_type(S, NewType)), Pid ! {ReplyRef, Reply, self()}, loop(NewState); {get_request_limit, ReplyRef, Pid} -> ?vdebug("get request limit: ~p", []), Pid ! {ReplyRef, {ok, Limit}, self()}, loop(S); {{set_request_limit, NewLimit}, ReplyRef, Pid} -> ?vdebug("set request limit: ~p", [NewLimit]), {NewState, Reply} = (catch handle_set_request_limit(S, NewLimit)), Pid ! {ReplyRef, Reply, self()}, loop(NewState); {disk_log, _Node, Log, Info} -> ?vdebug("disk log event: ~p, ~p", [Log, Info]), NewS = handle_disk_log(Log, Info, S), loop(NewS); {verbosity, Verbosity} -> ?vlog("verbosity: ~p -> ~p", [get(verbosity), Verbosity]), put(verbosity, snmp_verbosity:validate(Verbosity)), loop(S); filter_reset -> reset_counters(), loop(S); {'EXIT', Parent, Reason} -> ?vlog("parent (~p) exited: " "~n ~p", [Parent, Reason]), exit(Reason); {'DOWN', _SockMRef, Type, Socket, Reason} when (Type =:= port) orelse (Type =:= socket) -> case lists:keyfind(Socket, #transport.socket, Transports) of #transport{ socket = Socket, domain = Domain, port_info = PortInfo, inet_backend = InetBackend, opts = SocketOpts, req_refs = ReqRefs} = Transport -> try socket_open(Domain, PortInfo, InetBackend, SocketOpts) of {NewSocket, PortNo} -> error_msg( "Socket ~p exited for reason" "~n ~p" "~n Re-opened (~p, ~w)", [Socket, Reason, NewSocket, PortNo]), (length(ReqRefs) < Limit) andalso active_once(NewSocket), NewSockMRef = inet:monitor(NewSocket), S#state{ transports = lists:keyreplace( Socket, #transport.socket, Transports, Transport#transport{socket = NewSocket, mref = NewSockMRef, port_no = PortNo})} catch ReopenReason -> error_msg( "Socket ~p exited for reason" "~n ~p" "~n Re-open failed with reason" "~n ~p", [Socket, Reason, ReopenReason]), exit(ReopenReason) end; false -> error_msg( "Exit message from socket ~p for reason ~p~n", [Socket, Reason]), loop(S) end; {'EXIT', Pid, Reason} when is_pid(Pid) -> ?vlog("~p exited: " "~n ~p", [Pid, Reason]), NewS = clear_reqs(Pid, S), loop(NewS); {system, From, Msg} -> ?vdebug("system event ~p from ~p", [Msg, From]), sys:handle_system_msg(Msg, From, Parent, ?MODULE, [], S); _ -> loop(S) end. handle_udp_error(S, #transport{socket = Socket, kind = Kind}, Error) -> try inet:sockname(Socket) of {ok, {IP, Port}} -> error_msg("UDP Error for transport: " "~n Socket: ~p (~p, ~p)" "~n Kind: ~p" "~n Error: ~p", [Socket, IP, Port, Kind, Error]); {error, _} -> error_msg("UDP Error for transport: " "~n Socket: ~p" "~n Kind: ~p" "~n Error: ~p", [Socket, Kind, Error]) catch _:_:_ -> error_msg("UDP Error for transport: " "~n Socket: ~p" "~n Kind: ~p" "~n Error: ~p", [Socket, Kind, Error]) end, S. handle_udp_error_unknown(S, Socket, Error) -> try inet:sockname(Socket) of {ok, {IP, Port}} -> warning_msg("UDP Error for unknown transport: " "~n Socket: ~p (~p, ~p)" "~n Error: ~p", [Socket, IP, Port, Error]); {error, _} -> warning_msg("UDP Error for unknown transport: " "~n Socket: ~p" "~n Error: ~p", [Socket, Error]) catch _:_:_ -> warning_msg("UDP Error for transport: " "~n Socket: ~p" "~n Error: ~p", [Socket, Error]) end, S. update_req_counter_incoming( #state{limit = infinity} = S, #transport{socket = Socket}, _ReqRef) -> active_once(Socket), %% No limit so activate directly S; update_req_counter_incoming( #state{limit = Limit} = S, #transport{socket = Socket, req_refs = ReqRefs} = T, ReqRef) when length(ReqRefs) + 1 >= Limit -> %% Ok, one more and we are at the limit. %% Just make sure we are not already processing this one... case lists:member(ReqRef, ReqRefs) of false -> %% We are at the limit, do _not_ activate socket update_transport_req_refs(S, T, [ReqRef | ReqRefs]); true -> active_once(Socket), S end; update_req_counter_incoming( #state{} = S, #transport{socket = Socket, req_refs = ReqRefs} = T, ReqRef) -> active_once(Socket), case lists:member(ReqRef, ReqRefs) of false -> update_transport_req_refs(S, T, [ReqRef | ReqRefs]); true -> S end. update_req_counter_outgoing( #state{limit = infinity} = S, _Transport, _ReqRef) -> %% Already activated (in the incoming function) S; update_req_counter_outgoing( #state{limit = Limit} = S, #transport{socket = Socket, req_refs = ReqRefs} = Transport, ReqRef) -> LengthReqRefs = length(ReqRefs), ?vtrace("update_req_counter_outgoing() -> entry with~n" " Limit: ~w~n" " ReqRef: ~w~n" " length(ReqRefs): ~w", [Limit, ReqRef, LengthReqRefs]), NewReqRefs = lists:delete(ReqRef, ReqRefs), (LengthReqRefs >= Limit) andalso (length(NewReqRefs) < Limit) andalso begin ?vtrace("update_req_counter_outgoing -> " "passed below limit: activate", []), active_once(Socket) end, update_transport_req_refs(S, Transport, NewReqRefs). update_transport_req_refs( #state{transports = Transports} = S, #transport{socket = Socket} = T, ReqRefs) -> S#state{ transports = lists:keyreplace( Socket, #transport.socket, Transports, T#transport{req_refs = ReqRefs})}. maybe_handle_recv( #state{filter = FilterMod} = S, #transport{socket = Socket} = Transport, From, Packet) -> {From_1, From_2} = From, case try FilterMod:accept_recv(From_1, From_2) catch Class:Exception:StackTrace -> error_msg( "FilterMod:accept_recv(~p, ~p) crashed: ~w:~w~n ~p", [From_1, From_2, Class, Exception, StackTrace]), true end of false -> %% Drop the received packet %% What if this is an expected (inform) reply %% on an ephemeral socket? inc(netIfMsgInDrops), active_once(Socket), S; Other -> case Other of true -> ok; _ -> error_msg( "FilterMod:accept_recv(~p, ~p) returned: ~p", [From_1,From_2,Other]) end, handle_recv(S, Transport, From, Packet) end. handle_recv( #state{mpd_state = MpdState, note_store = NS, log = Log} = S, #transport{socket = Socket} = Transport, From, Packet) -> put(n1, erlang:monotonic_time(micro_seconds)), LogF = fun(Type, Data) -> log(Log, Type, Data, From) end, case (catch snmpa_mpd:process_packet( Packet, From, MpdState, NS, LogF)) of {ok, _Vsn, Pdu, _PduMS, {discovery, ManagerEngineId}} -> handle_discovery_response( S, Transport, From, Pdu, ManagerEngineId); {ok, _Vsn, Pdu, _PduMS, discovery} -> handle_discovery_response( S, Transport, From, Pdu, undefined); {ok, Vsn, Pdu, PduMS, ACMData} -> ?vlog("got pdu ~s", [?vapply(snmp_misc, format, [256, "~w", [Pdu]])]), %% handle_recv_pdu(S, Transport, From, Vsn, Pdu, PduMS, ACMData); maybe_handle_recv_pdu( S, Transport, From, Vsn, Pdu, PduMS, ACMData); {discarded, Reason} -> ?vlog("packet discarded for reason: ~s", [?vapply(snmp_misc, format, [256, "~w", [Reason]])]), active_once(Socket), S; {discarded, Reason, ReportPacket} -> ?vlog("sending report for reason: " "~n ~s", [?vapply(snmp_misc, format, [256, "~w", [Reason]])]), (catch udp_send(Socket, From, ReportPacket)), active_once(Socket), S; {discovery, ReportPacket} -> ?vlog("sending discovery report", []), (catch udp_send(Socket, From, ReportPacket)), active_once(Socket), S; Error -> error_msg("processing of received message failed: " "~n ~p", [Error]), active_once(Socket), S end. handle_discovery_response( #state{reqs = Reqs} = S, #transport{socket = Socket}, _From, #pdu{request_id = ReqId} = Pdu, ManagerEngineId) -> active_once(Socket), case lists:keyfind(ReqId, 1, S#state.reqs) of {ReqId, Pid} -> Pid ! {snmp_discovery_response_received, Pdu, ManagerEngineId}, %% XXX Strange... Reqs from this Pid should be reaped %% at process exit by clear_reqs/2 so the following %% should be redundant. NReqs = lists:keydelete(ReqId, 1, Reqs -- [{0, Pid}]), % ERIERL-427 S#state{reqs = NReqs}; %% <OTP-16207> %% For some reason 'snmptrapd' response in stage 2 with request-id %% of zero. false when (ReqId =:= 0) -> DiscoReqs = [X|| {0, From1} <- S#state.reqs, {_, From2} = X <- S#state.reqs, From1 =:= From2], case (length(DiscoReqs) =:= 2) of true -> [{_, Pid}, _] = DiscoReqs, Pid ! {snmp_discovery_response_received, Pdu, ManagerEngineId}, NReqs = S#state.reqs -- DiscoReqs, S#state{reqs = NReqs}; false -> S end; %% </OTP-16207> false -> %% Ouch, timeout? resend? S end. maybe_handle_recv_pdu( #state{filter = FilterMod} = S, #transport{socket = Socket} = Transport, From, Vsn, #pdu{type = Type} = Pdu, PduMS, ACMData) -> {From_1, From_2} = From, case try FilterMod:accept_recv_pdu(From_1, From_2, Type) catch Class:Exception:StackTrace -> error_msg( "FilterMod:accept_recv_pdu(~p, ~p, ~p) crashed: ~w:~w~n" " ~p", [From_1, From_2, Type, Class, Exception, StackTrace]), true end of false -> inc(netIfPduInDrops), active_once(Socket), S; Other -> case Other of true -> ok; _ -> error_msg( "FilterMod:accept_recv_pdu(~p, ~p, ~p) returned: ~p", [From_1,From_2,Type,Other]) end, handle_recv_pdu(S, Transport, From, Vsn, Pdu, PduMS, ACMData) end. handle_recv_pdu( #state{reqs = Reqs} = S, #transport{socket = Socket}, From, Vsn, #pdu{type = 'get-response', request_id = ReqId} = Pdu, _PduMS, _ACMData) -> active_once(Socket), case lists:keyfind(ReqId, 1, Reqs) of {ReqId, Pid} -> ?vdebug("handle_recv_pdu -> " "~n send response to receiver ~p", [Pid]), Pid ! {snmp_response_received, Vsn, Pdu, From}; false -> ?vdebug("handle_recv_pdu -> " "~n No receiver available for response pdu", []) end, S; handle_recv_pdu( #state{master_agent = Pid} = S, #transport{} = Transport, From, Vsn, #pdu{type = Type} = Pdu, PduMS, ACMData) when Type =:= 'set-request'; Type =:= 'get-request'; Type =:= 'get-next-request'; Type =:= 'get-bulk-request' -> ?vtrace("handle_recv_pdu -> received request (~w)", [Type]), ReqRef = make_ref(), Extra = [{request_ref, ReqRef}], Pid ! {snmp_pdu, Vsn, Pdu, PduMS, ACMData, From, Extra}, NewS = update_req_counter_incoming(S, Transport, ReqRef), ?vdebug("handle_recv_pdu -> ~p", [NewS]), NewS; handle_recv_pdu( #state{master_agent = Pid} = S, #transport{socket = Socket}, From, Vsn, Pdu, PduMS, ACMData) -> ?vtrace("handle_recv_pdu -> received other request", []), active_once(Socket), Pid ! {snmp_pdu, Vsn, Pdu, PduMS, ACMData, From, []}, S. maybe_handle_reply_pdu( #state{filter = FilterMod, transports = Transports} = S, #transport{} = Transport, Vsn, #pdu{} = Pdu, Type, ACMData, To) -> %% Addresses = [fix_filter_address(Transports, To)], case try FilterMod:accept_send_pdu(Addresses, Type) catch Class:Exception:StackTrace -> error_msg( "FilterMod:accept_send_pdu(~p, ~p) crashed: ~w:~w~n ~p", [Addresses, Type, Class, Exception, StackTrace]), true end of false -> inc(netIfPduOutDrops), ok; Other -> case Other of true -> ok; _ -> error_msg( "FilterMod:accept_send_pdu(~p, ~p) returned: ~p", [Addresses,Type,Other]) end, handle_reply_pdu(S, Transport, Vsn, Pdu, Type, ACMData, To) end. handle_reply_pdu( #state{log = Log} = S, #transport{} = Transport, Vsn, #pdu{} = Pdu, Type, ACMData, To) -> %% LogF = fun(Type2, Data) -> log(Log, Type2, Data, To) end, case (catch snmpa_mpd:generate_response_msg(Vsn, Pdu, Type, ACMData, LogF)) of {ok, Packet} -> ?vinfo("time in agent: ~w mysec", [time_in_agent()]), try maybe_udp_send_wo_log(S, Transport, To, Packet) catch {Reason, Sz} -> error_msg("Cannot send message " "~n size: ~p" "~n reason: ~p" "~n pdu: ~p", [Sz, Reason, Pdu]) end; {discarded, Reason} -> ?vlog("handle_reply_pdu -> " "~n reply discarded for reason: ~s", [?vapply(snmp_misc, format, [256, "~w", [Reason]])]), ok; {'EXIT', Reason} -> user_err("failed generating response message: " "~nPDU: ~p~n~p", [Pdu, Reason]) end. maybe_handle_send_pdu(#state{filter = FilterMod, transports = Transports} = S, Vsn, Pdu, MsgData, TDomAddrSecs, From) -> ?vtrace("maybe_handle_send_pdu -> entry with" "~n FilterMod: ~p" "~n TDomAddrSecs: ~p", [FilterMod, TDomAddrSecs]), DomAddrSecs = snmpa_mpd:process_taddrs(TDomAddrSecs), AddressesToFilter = case is_legacy_transports(Transports) of true -> [fix_filter_legacy_mpd_address(DAS) || DAS <- DomAddrSecs]; false -> [fix_filter_mpd_address(DAS) || DAS <- DomAddrSecs] end, Type = pdu_type_of(Pdu), case try FilterMod:accept_send_pdu(AddressesToFilter, Type) catch Class:Exception:StackTrace -> error_msg( "FilterMod:accept_send_pdu(~p, ~p) crashed: ~w:~w~n ~p", [AddressesToFilter, Type, Class, Exception, StackTrace]), true end of false -> inc(netIfPduOutDrops), ok; true -> handle_send_pdu(S, Vsn, Pdu, MsgData, DomAddrSecs, From); FilteredAddresses when is_list(FilteredAddresses) -> FilteredDomAddrSecs = case is_legacy_transports(Transports) of true -> [DAS || DAS <- DomAddrSecs, lists:member( fix_filter_legacy_mpd_address(DAS), FilteredAddresses)]; false -> [DAS || DAS <- DomAddrSecs, lists:member( fix_filter_mpd_address(DAS), FilteredAddresses)] end, ?vtrace("maybe_handle_send_pdu -> FilteredDomAddrSecs:~n" " ~p", [FilteredDomAddrSecs]), handle_send_pdu(S, Vsn, Pdu, MsgData, FilteredDomAddrSecs, From); Other -> error_msg( "FilterMod:accept_send_pdu(~p, ~p) returned: ~p", [AddressesToFilter,Type,Other]), handle_send_pdu(S, Vsn, Pdu, MsgData, DomAddrSecs, From) end. handle_send_pdu( #state{note_store = NS} = S, Vsn, Pdu, MsgData, DomAddrSecs, From) -> %% ?vtrace("handle_send_pdu -> entry with~n" " Pdu: ~p~n" " DomAddrSecs: ~p", [Pdu, DomAddrSecs]), case (catch snmpa_mpd:generate_msg( Vsn, NS, Pdu, MsgData, DomAddrSecs)) of {ok, Addresses} -> do_handle_send_pdu(S, Pdu, Addresses); {discarded, Reason} -> ?vlog("handle_send_pdu -> " "~n PDU ~p not sent due to ~p", [Pdu, Reason]), ok; {'EXIT', Reason} -> user_err("failed generating message: " "~nPDU: ~p~n~p", [Pdu, Reason]), ok end, case From of undefined -> S; Pid when is_pid(Pid) -> ?vtrace("link to ~p and add to request list", [Pid]), link(Pid), NReqs = snmp_misc:keyreplaceadd( Pid, 2, S#state.reqs, {Pdu#pdu.request_id, From}), S#state{reqs = NReqs} end. handle_send_discovery( #state{ note_store = NS, log = Log, reqs = Reqs, transports = Transports} = S, #pdu{type = Type, request_id = ReqId} = Pdu, MsgData, To, From) when is_pid(From) -> ?vtrace("handle_send_discovery -> entry with" "~n Pdu: ~p" "~n MsgData: ~p" "~n To: ~p" "~n From: ~p", [Pdu, MsgData, To, From]), case (catch snmpa_mpd:generate_discovery_msg(NS, Pdu, MsgData, To)) of {ok, {Domain, Address, Packet}} -> case select_transport(Domain, Type, Transports) of false -> error_msg( "Can not find transport to: ~s", [format_address(To)]), S; #transport{socket = Socket} -> log(Log, Type, Packet, {Domain, Address}), udp_send(Socket, {Domain, Address}, Packet), ?vtrace("handle_send_discovery -> sent (~w)", [ReqId]), link(From), NReqs = snmp_misc:keyreplaceadd(From, 2, Reqs, {ReqId, From}), NReqs2 = (NReqs -- [{0, From}]) ++ [{0, From}], % OTP-16207 S#state{reqs = NReqs2} end; {discarded, Reason} -> ?vlog("handle_send_discovery -> " "~n Discovery PDU ~p not sent due to ~p", [Pdu, Reason]), S; {'EXIT', Reason} -> user_err("failed generating discovery message: " "~n PDU: ~p" "~n Reason: ~p", [Pdu, Reason]), S end. do_handle_send_pdu(S, #pdu{type = Type} = Pdu, Addresses) -> do_handle_send_pdu(S, Type, Pdu, Addresses); do_handle_send_pdu(S, Trap, Addresses) -> do_handle_send_pdu(S, trappdu, Trap, Addresses). do_handle_send_pdu(S, Type, Pdu, Addresses) -> try do_handle_send_pdu1(S, Type, Addresses) catch {Reason, Sz} -> error_msg( "Can not send message~n" " size: ~p~n" " reason: ~p~n" " pdu: ~p", [Sz, Reason, Pdu]) end. %% Because of the ephemeral sockets used by some transports, %% the list of transports may be update for each send... do_handle_send_pdu1(S, _Type, []) -> S; do_handle_send_pdu1(S, Type, [{Domain, Address, Pkg}|Addresses]) when is_binary(Pkg) -> NewS = do_handle_send_pdu2(S, Type, Domain, Address, Pkg, Pkg, ""), do_handle_send_pdu1(NewS, Type, Addresses); do_handle_send_pdu1(S, Type, [{Domain, Address, {Pkg, LogPkg}}|Addresses]) when is_binary(Pkg) -> NewS = do_handle_send_pdu2(S, Type, Domain, Address, Pkg, LogPkg, " encrypted"), do_handle_send_pdu1(NewS, Type, Addresses). do_handle_send_pdu2(#state{transports = Transports} = S, Type, Domain, Address, Pkg, LogPkg, EncrStr) -> ?vdebug("[~w] sending~s packet:" "~n size: ~p" "~n to: ~p", [Domain, EncrStr, sz(Pkg), Address]), To = {Domain, Address}, case select_transport(Domain, Type, Transports) of false -> error_msg("Transport not found: " "~n size: ~p" "~n to: ~s", [sz(Pkg), format_address(To)]), S; #transport{ephm = none} = Transport -> ?vtrace("do_handle_send_pdu2 -> transport(ephm = none) selected: " "~n ~p", [Transport]), maybe_udp_send_w_log(S, Transport, To, Pkg, LogPkg, Type), S%; %% <EPHEMERAL-FOR-FUTUR-USE> %% #transport{} = Transport -> %% ?vtrace("do_handle_send_pdu2 -> transport selected: " %% "~n ~p", [Transport]), %% case maybe_udp_send_w_log(S, Transport, To, Pkg, LogPkg, Type) of %% {ok, Sz} -> % we actually sent something %% maybe_update_ephm_transport(S, Transport, Type, Sz); %% _ -> % Non-fatal error -> Nothing sent %% S %% end %% </EPHEMERAL-FOR-FUTUR-USE> end. %% <EPHEMERAL-FOR-FUTUR-USE> %% For inform: %% This will have a reply, so we cannot close it directly! %% Also, we will resend (a couple of times), if we don't %% get a reply in time. %% So, how do we handle this transport in this case? %% Shall we create a list of used sockets? Tagged with a key %% so we can reuse the correct socket for a resend? %% %% Or shall we used the same socket for all the sends for this %% trap/inform? That is, we send the trap/inform to a number of %% targets (the Addresses list), and *that* is considered *one* %% use? %% That would mean that we would potentially need to wait for %% replies from a large number of targets? %% But it may be better in the long term, because we will not %% use of so many sockets. %% %% maybe_update_ephm_transport(S, #transport{ephm = once} = _Transport, %% 'inform-request' = _Type, _Sz) -> %% S; % Figure out the above first! %% %% Before we close the current socket, create the new. %% %% This is done in case we fail to create a new socket %% %% (if we first close the current and then fail to create %% %% the new, we are stuck). %% %% If we fail to create the new socket, we keep the current. %% %% Better then nothing! %% maybe_update_ephm_transport(S, #transport{socket = OldSocket, %% ephm = once, %% port_info = PortInfo, %% opts = Opts} = Transport, %% _Type, _Sz) -> %% try %% begin %% {Socket, PortNo} = gen_udp_open(PortInfo, Opts), %% (catch gen_udp:close(OldSocket)), %% T2 = Transport#transport{socket = Socket, %% port_no = PortNo}, %% TS = S#state.transports, %% TS2 = lists:keyreplace(OldSocket, #transport.socket, TS, T2), %% S#state{transports = TS2} %% end %% catch %% _:_:_ -> %% %% We need to identify which transport! %% error_msg("Failed creating new ephemeral socket for transport"), %% S %% end; %% %% Note that we do not currently handle inform:s, as that adds a whole %% %% set of issues. See above for more info. %% maybe_update_ephm_transport(S, #transport{socket = Socket, %% ephm = {sends, MaxSends}, %% ephm_info = NumSends, %% port_info = _PortInfo, %% opts = _Opts} = Transport, %% _Type, _Sz) when (MaxSends > NumSends) -> %% T2 = Transport#transport{ephm_info = NumSends + 1}, %% TS = S#state.transports, %% TS2 = lists:keyreplace(Socket, #transport.socket, TS, T2), %% S#state{transports = TS2}; %% maybe_update_ephm_transport(S, #transport{socket = OldSocket, %% ephm = {sends, _MaxSends}, %% ephm_info = _NumSends, %% port_info = PortInfo, %% opts = Opts} = Transport, %% _Type, _Sz) -> %% try %% begin %% {Socket, PortNo} = gen_udp_open(PortInfo, Opts), %% (catch gen_udp:close(OldSocket)), %% T2 = Transport#transport{socket = Socket, %% ephm_info = 0, %% port_no = PortNo}, %% TS = S#state.transports, %% TS2 = lists:keyreplace(OldSocket, #transport.socket, TS, T2), %% S#state{transports = TS2} %% end %% catch %% _:_:_ -> %% %% We need to identify which transport! %% error_msg("Failed creating new ephemeral socket for transport"), %% S %% end; %% %% Note that we do not currently handle inform:s, as that adds a whole %% %% set of issues. See above for more info. %% maybe_update_ephm_transport(S, #transport{socket = Socket, %% ephm = {data, MaxData}, %% ephm_info = AccSent, %% port_info = _PortInfo, %% opts = _Opts} = Transport, %% _Type, Sz) when (MaxData > (AccSent + Sz)) -> %% T2 = Transport#transport{ephm_info = AccSent + Sz}, %% TS = S#state.transports, %% TS2 = lists:keyreplace(Socket, #transport.socket, TS, T2), %% S#state{transports = TS2}; %% maybe_update_ephm_transport(S, #transport{socket = OldSocket, %% ephm = {data, _MaxData}, %% ephm_info = _AccSent, %% port_info = PortInfo, %% opts = Opts} = Transport, %% _Type, _Sz) -> %% try %% begin %% {Socket, PortNo} = gen_udp_open(PortInfo, Opts), %% (catch gen_udp:close(OldSocket)), %% T2 = Transport#transport{socket = Socket, %% ephm_info = 0, %% port_no = PortNo}, %% TS = S#state.transports, %% TS2 = lists:keyreplace(OldSocket, #transport.socket, TS, T2), %% S#state{transports = TS2} %% end %% catch %% _:_:_ -> %% %% We need to identify which transport! %% error_msg("Failed creating new ephemeral socket for transport"), %% S %% end; %% %% Note that we do not currently handle inform:s, as that adds a whole %% %% set of issues. See above for more info. %% maybe_update_ephm_transport(S, #transport{socket = Socket, %% ephm = {alive_time, AliveTime}, %% ephm_info = undefined} = Transport, %% _Type, _Sz) -> %% AliveEnd = erlang:monotonic_time(micro_seconds) + AliveTime, %% T2 = Transport#transport{ephm_info = AliveEnd}, %% TS = S#state.transports, %% TS2 = lists:keyreplace(Socket, #transport.socket, TS, T2), %% S#state{transports = TS2}; %% maybe_update_ephm_transport(S, #transport{socket = OldSocket, %% ephm = {alive_time, _AliveTime}, %% ephm_info = AliveEnd, %% port_info = PortInfo, %% opts = Opts} = Transport, %% _Type, _Sz) -> %% TS = erlang:monotonic_time(micro_seconds), %% if %% (TS > AliveEnd) -> %% try %% begin %% {Socket, PortNo} = gen_udp_open(PortInfo, Opts), %% (catch gen_udp:close(OldSocket)), %% T2 = Transport#transport{socket = Socket, %% %% This will be set when the transport %% %% is first used %% ephm_info = undefined, %% port_no = PortNo}, %% TS = S#state.transports, %% TS2 = lists:keyreplace(OldSocket, #transport.socket, TS, T2), %% S#state{transports = TS2} %% end %% catch %% _:_:_ -> %% %% We need to identify which transport! %% error_msg("Failed creating new ephemeral socket for transport"), %% S %% end; %% true -> %% S %% end; %% maybe_update_ephm_transport(S, _Transport, _Type, _Sz) -> %% S. %% </EPHEMERAL-FOR-FUTUR-USE> %% This function is used when logging has already been done! maybe_udp_send_wo_log( #state{filter = FilterMod, transports = Transports}, #transport{socket = Socket}, To, Packet) -> {To_1, To_2} = fix_filter_address(Transports, To), case try FilterMod:accept_send(To_1, To_2) catch Class:Exception:StackTrace -> error_msg( "FilterMod:accept_send(~p, ~p) crashed: ~w:~w~n ~p", [To_1, To_2, Class, Exception, StackTrace]), true end of false -> inc(netIfMsgOutDrops), ok; Other -> case Other of true -> ok; _ -> error_msg( "FilterMod:accept_send(~p, ~p) returned: ~p", [To_1,To_2,Other]) end, udp_send(Socket, To, Packet) end. maybe_udp_send_w_log( #state{log = Log, filter = FilterMod, transports = Transports}, #transport{socket = Socket}, To, Pkg, LogPkg, Type) -> {To_1, To_2} = fix_filter_address(Transports, To), case try FilterMod:accept_send(To_1, To_2) catch Class:Exception:StackTrace -> error_msg( "FilterMod:accept_send(~p, ~p) crashed for: ~w:~w~n ~p", [To_1, To_2, Class, Exception, StackTrace]), true end of false -> inc(netIfMsgOutDrops), ok; Other -> case Other of true -> ok; _ -> error_msg( "FilterMod:accept_send(~p, ~p) returned: ~p", [To_1, To_2, Other]) end, log(Log, Type, LogPkg, To), udp_send(Socket, To, Pkg) end. udp_send(Socket, To, B) -> {IpAddr, IpPort} = case To of {Domain, Addr} when is_atom(Domain) -> Addr; {_, P} = Addr when is_integer(P) -> Addr end, try gen_udp:send(Socket, IpAddr, IpPort, B) of {error, emsgsize} -> %% From this message we cannot recover, so exit sending loop throw({emsgsize, sz(B)}); {error, ErrorReason} -> error_msg("[error] cannot send message " "(destination: ~p:~p, size: ~p, reason: ~p)", [IpAddr, IpPort, sz(B), ErrorReason]), ok; ok -> %% For future use! Ephemeral ports! {ok, size(B)} catch error:ExitReason:StackTrace -> error_msg("[exit] cannot send message " "(destination: ~p:~p, size: ~p, reason: ~p, at: ~p)", [IpAddr, IpPort, sz(B), ExitReason, StackTrace]) end. sz(L) when is_list(L) -> length(L); sz(B) when is_binary(B) -> size(B); 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("Failure 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. clear_reqs(Pid, S) -> NReqs = lists:keydelete(Pid, 2, S#state.reqs), NReqs2 = NReqs -- [{0, Pid}], % ERIERL-427 S#state{reqs = NReqs2}. toname(P) when is_pid(P) -> case process_info(P, registered_name) of {registered_name, Name} -> Name; _ -> P end; toname(Else) -> Else. active_once(Sock) -> ?vtrace("activate once", []), ok = inet:setopts(Sock, [{active, once}]). select_transport(_, []) -> false; select_transport(ReqRef, [#transport{req_refs = ReqRefs} = Transport | Transports]) -> case lists:member(ReqRef, ReqRefs) of true -> Transport; false -> select_transport(ReqRef, Transports) end. select_transport(snmpUDPDomain = Domain, Type, Transports) -> ?vtrace("select_transport -> entry with" "~n Domain: ~p" "~n Type: ~p" "~n Transports: ~p", [Domain, Type, Transports]), case select_transport2(Domain, Type, Transports) of #transport{} = Transport -> ?vtrace("select_transport -> selected: " "~n ~p", [Transport]), Transport; false -> select_transport2(transportDomainUdpIpv4, Type, Transports) end; select_transport(Domain, Type, Transports) when is_atom(Domain) -> ?vtrace("select_transport -> entry with" "~n Domain: ~p" "~n Type: ~p" "~n Transports: ~p", [Domain, Type, Transports]), case select_transport2(Domain, Type, Transports) of #transport{} = Transport -> ?vtrace("select_transport -> selected: " "~n ~p", [Transport]), Transport; false when Domain =:= transportDomainUdpIpv4 -> ?vdebug("select_transport -> (~p) not found: " "~n try ~p", [Domain, snmpUDPDomain]), select_transport2(snmpUDPDomain, Type, Transports); false -> false end. %% Two kinds of (pdu) type that we (the agent) will ever attempt to send: %% req-responder: 'get-response' %% trap-sender: 'inform-request' | %% 'snmpv2-trap' | %% report | %% trap (which is a 'fake' type (#trappdu{})) select_transport2(_Domain, _Type, []) -> false; select_transport2(snmpUDPDomain = Domain, _Type, [#transport{domain = Domain} = Transport|_Transports]) -> Transport; select_transport2(snmpUDPDomain = Domain, Type, [_|Transports]) -> select_transport2(Domain, Type, Transports); select_transport2(Domain, Type, [#transport{domain = Domain, kind = Kind} = Transport|_Transports]) when ((Type =:= 'inform-request') orelse (Type =:= 'snmpv2-trap') orelse (Type =:= report) orelse (Type =:= trap) orelse (Type =:= trappdu)) andalso ((Kind =:= all) orelse (Kind =:= trap_sender)) -> Transport; select_transport2(Domain, Type, [#transport{domain = Domain, kind = Kind} = Transport|_Transports]) when (Type =:= 'get-response') andalso ((Kind =:= all) orelse (Kind =:= req_responder)) -> Transport; select_transport2(Domain, Type, [_|Transports]) -> select_transport2(Domain, Type, Transports). address_to_domain({Domain, _Addr}) when is_atom(Domain) -> Domain; address_to_domain({_Ip, Port}) when is_integer(Port) -> snmpUDPDomain. %% If the agent uses legacy snmpUDPDomain e.g has not set %% intAgentTransportDomain, then make sure %% snmpa_network_interface_filter gets legacy arguments %% to not break backwards compatibility. %% fix_filter_address(Transports, Address) -> case is_legacy_transports(Transports) of true -> case Address of {Domain, Addr} when is_atom(Domain) -> Addr; {_, IpPort} = Addr when is_integer(IpPort) -> Addr end; false -> Address end. is_legacy_transports([#transport{domain = snmpUDPDomain}]) -> true; is_legacy_transports([#transport{} | _]) -> false. fix_filter_legacy_mpd_address(Domain_Address_SecData) -> case Domain_Address_SecData of {{Domain, Addr}, _SecData} when is_atom(Domain) -> % v3 Addr; {Domain, Addr} when is_atom(Domain) -> % v1 & v2 Addr end. fix_filter_mpd_address(Domain_Address_SecData) -> case Domain_Address_SecData of {{Domain, _Addr} = Address, _SecData} when is_atom(Domain) -> % v3 Address; {Domain, _Addr} = Address when is_atom(Domain) -> % v1 & v2 Address end. %%%----------------------------------------------------------------- handle_set_log_type(#state{log = {Log, OldValue}} = State, NewType) when (Log /= undefined) -> 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}}; handle_set_log_type(State, _NewType) -> {State, {error, not_enabled}}. handle_set_request_limit(#state{limit = OldLimit} = State, NewLimit) when ((is_integer(NewLimit) andalso (NewLimit >= 0)) orelse (NewLimit == infinity)) -> NewState = State#state{limit = NewLimit}, {NewState, {ok, OldLimit}}; handle_set_request_limit(State, BadLimit) -> {State, {error, {bad_request_limit, BadLimit}}}. %%%----------------------------------------------------------------- %%% System messages %%%----------------------------------------------------------------- system_continue(_Parent, _Dbg, S) -> loop(S). system_terminate(Reason, _Parent, _Dbg, #state{log = Log}) -> ?vlog("system-terminate -> entry with" "~n Reason: ~p", [Reason]), do_close_log(Log), exit(Reason). system_code_change(OldState, _Module, _OldVsn, downgrade_to_pre_4_16) -> {OldLog, Type} = OldState#state.log, NewLog = snmp_log:downgrade(OldLog), NewState = OldState#state{log = {NewLog, Type}}, {ok, NewState}; system_code_change(OldState, _Module, _OldVsn, upgrade_from_pre_4_16) -> Initial = ?ATL_SEQNO_INITIAL, Max = ?ATL_SEQNO_MAX, Module = snmpa_agent, Function = increment_counter, Args = [atl_seqno, Initial, Max], SeqNoGen = {Module, Function, Args}, {OldLog, Type} = OldState#state.log, NewLog = snmp_log:upgrade(OldLog, SeqNoGen), NewState = OldState#state{log = {NewLog, Type}}, {ok, NewState}; system_code_change(S, _Module, _OldVsn, _Extra) -> {ok, S}. do_close_log({undefined, []}) -> ok; do_close_log({Log, _}) -> (catch snmp_log:sync(Log)), (catch snmp_log:close(Log)), ok; do_close_log(_) -> ok. %%%----------------------------------------------------------------- %%% DEBUG FUNCTIONS %%%----------------------------------------------------------------- time_in_agent() -> erlang:monotonic_time(micro_seconds) - get(n1). %% ---------------------------------------------------------------- pdu_type_of(#pdu{type = Type}) -> Type; pdu_type_of(TrapPdu) when is_record(TrapPdu, trappdu) -> trap. %%----------------------------------------------------------------- %% Counter functions %%----------------------------------------------------------------- init_counters() -> F = fun(Counter) -> maybe_create_counter(Counter) end, lists:map(F, counters()). reset_counters() -> F = fun(Counter) -> init_counter(Counter) end, lists:map(F, counters()). maybe_create_counter(Counter) -> case ets:lookup(snmp_agent_table, Counter) of [_] -> ok; _ -> init_counter(Counter) end. init_counter(Counter) -> ets:insert(snmp_agent_table, {Counter, 0}). counters() -> [ netIfMsgOutDrops, netIfMsgInDrops, netIfPduOutDrops, netIfPduInDrops ]. inc(Name) -> ets:update_counter(snmp_agent_table, Name, 1). get_counters() -> Counters = counters(), get_counters(Counters, []). get_counters([], Acc) -> lists:reverse(Acc); get_counters([Counter|Counters], Acc) -> case ets:lookup(snmp_agent_table, Counter) of [CounterVal] -> get_counters(Counters, [CounterVal|Acc]); _ -> get_counters(Counters, Acc) end. %% ---------------------------------------------------------------- %% This extracts the socket options from what is specified for %% the transport, or if not, from the "global" socket options. socket_opts(Domain, {IpAddr, PortInfo}, SocketOpts, DefaultOpts) -> ?vtrace("socket_opts -> entry with" "~n Domain: ~p" "~n IpAddr: ~p" "~n PortInfo: ~p" "~n SocketOpts: ~p" "~n DefaultOpts: ~p", [Domain, IpAddr, PortInfo, SocketOpts, DefaultOpts]), Opts = [binary | case snmp_conf:tdomain_to_family(Domain) of inet6 = Family -> [Family, {ipv6_v6only, true}]; Family -> [Family] end ++ case get_bind_to_ip_address(SocketOpts, DefaultOpts) of true -> [{ip, IpAddr}]; _ -> [] end ++ case get_no_reuse_address(SocketOpts, DefaultOpts) of false -> [{reuseaddr, true}]; _ -> [] end ++ case get_recbuf(SocketOpts, DefaultOpts) of use_default -> []; Sz -> [{recbuf, Sz}] end ++ case get_sndbuf(SocketOpts, DefaultOpts) of use_default -> []; Sz -> [{sndbuf, Sz}] end ] ++ case get_extra_sock_opts(SocketOpts, DefaultOpts) of ESO when is_list(ESO) -> ESO; BadESO -> error_msg("Invalid 'extra socket options' (=> ignored):" "~n ~p", [BadESO]), [] end, InetBackend = case get_inet_backend(SocketOpts, DefaultOpts) of use_default -> []; Backend when (Backend =:= inet) orelse (Backend =:= socket) -> [{inet_backend, Backend}] end, %% <EPHEMERAL-FOR-FUTUR-USE> %% Ephm = get_ephemeral(SocketOpts), %% {Ephm, PortInfo, Opts}. %% </EPHEMERAL-FOR-FUTUR-USE> {none, IpAddr, PortInfo, InetBackend, Opts}. %% ---------------------------------------------------------------- get_atl_type(Opts) -> case snmp_misc:get_option(type, Opts, read_write) of read_write -> [read,write]; write -> [write]; read -> [read] end. get_atl_dir(Opts) -> snmp_misc:get_option(dir, Opts). get_atl_size(Opts) -> snmp_misc:get_option(size, Opts). get_atl_repair(Opts) -> snmp_misc:get_option(repair, Opts, true). get_atl_seqno(Opts) -> snmp_misc:get_option(seqno, Opts, false). get_verbosity(Opts) -> snmp_misc:get_option(verbosity, Opts, ?default_verbosity). get_vsns(Opts) -> snmp_misc:get_option(versions, Opts, [v1, v2, v3]). get_req_limit(O) -> snmp_misc:get_option(req_limit, O, infinity). get_filter_opts(O) -> snmp_misc:get_option(filter, O, []). get_filter_module(O) -> snmp_misc:get_option(module, O, ?DEFAULT_FILTER_MODULE). get_recbuf(Opts, DefaultOpts) -> get_socket_opt(recbuf, Opts, DefaultOpts, use_default). get_sndbuf(Opts, DefaultOpts) -> get_socket_opt(sndbuf, Opts, DefaultOpts, use_default). get_bind_to_ip_address(Opts, DefaultOpts) -> get_socket_opt(bind_to, Opts, DefaultOpts, false). get_no_reuse_address(Opts, DefaultOpts) -> get_socket_opt(no_reuse, Opts, DefaultOpts, false). get_extra_sock_opts(Opts, DefaultOpts) -> get_socket_opt(extra_sock_opts, Opts, DefaultOpts, []). get_inet_backend(Opts, DefaultOpts) -> get_socket_opt(inet_backend, Opts, DefaultOpts, use_default). %% <EPHEMERAL-FOR-FUTUR-USE> %% This is not realy a socket option, but rather socket 'meta' %% information. Its still put together with the actual socket %% options. %% get_ephemeral(SocketOpts) -> %% snmp_misc:get_option(ephemeral, SocketOpts, none). %% </EPHEMERAL-FOR-FUTUR-USE> get_socket_opt(Opt, Opts, DefaultOpts, DefaultVal) -> snmp_misc:get_option(Opt, Opts, snmp_misc:get_option(Opt, DefaultOpts, DefaultVal)). %% ---------------------------------------------------------------- %% error_msg(F) -> %% error_msg(F, []). error_msg(F, A) -> ?snmpa_error("NET-IF server: " ++ F, A). warning_msg(F, A) -> ?snmpa_warning("NET-IF server: " ++ F, A). info_msg(F,A) -> ?snmpa_info("NET-IF server: " ++ F, A). %% --- user_err(F, A) -> snmpa_error:user_err(F, A). config_err(F, A) -> snmpa_error:config_err(F, A). %% ---------------------------------------------------------------- call(Pid, Req) -> ReplyRef = make_ref(), Pid ! {Req, ReplyRef, self()}, receive {ReplyRef, Reply, Pid} -> Reply after 5000 -> {error, timeout} end. %% ---------------------------------------------------------------- get_info(#state{transports = Transports, reqs = Reqs}) -> ProcSize = proc_mem(self()), Counters = get_counters(), [{reqs, Reqs}, {counters, Counters}, {process_memory, ProcSize}, {transport_info, [#{tdomain => Domain, taddress => {Address, PortNo}, transport_kind => Kind, port_info => PortInfo, opts => Opts, socket_info => get_socket_info(Socket), num_reqs => length(Refs)} || #transport{socket = Socket, domain = Domain, address = Address, port_no = PortNo, port_info = PortInfo, opts = Opts, kind = Kind, req_refs = Refs} <- 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_socket_info(Id) when is_port(Id) -> Info = inet:info(Id), IfList = case (catch inet:getif(Id)) of {ok, IFs} -> [{interfaces, IFs}]; _ -> [] end, BufSz = case (catch inet:getopts(Id, [recbuf, sndbuf])) of {ok, Sz} -> [{buffer_size, Sz}]; _ -> [] end, [{socket, Id}, {info, Info}] ++ IfList ++ BufSz; get_socket_info(Id) -> Info = inet:info(Id), %% Does not exist for 'socket' IfList = [], %% case (catch inet:getif(Id)) of %% {ok, IFs} -> %% [{interfaces, IFs}]; %% _ -> %% [] %% end, BufSz = case (catch inet:getopts(Id, [recbuf, sndbuf])) of {ok, Sz} -> [{buffer_size, Sz}]; _ -> [] end, [{socket, Id}, {info, Info}] ++ IfList ++ BufSz. %% ---------------------------------------------------------------
Copyright ©2k19 -
Hexid
|
Tex7ure