diff options
Diffstat (limited to 'lib/snmp/src/agent/snmpa_net_if.erl')
-rw-r--r-- | lib/snmp/src/agent/snmpa_net_if.erl | 1249 |
1 files changed, 1249 insertions, 0 deletions
diff --git a/lib/snmp/src/agent/snmpa_net_if.erl b/lib/snmp/src/agent/snmpa_net_if.erl new file mode 100644 index 0000000000..d703e5ac55 --- /dev/null +++ b/lib/snmp/src/agent/snmpa_net_if.erl @@ -0,0 +1,1249 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2004-2009. 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% +%% +-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"). + +-record(state, {parent, + note_store, + master_agent, + usock, + usock_opts, + mpd_state, + log, + reqs = [], + debug = false, + limit = infinity, + rcnt = [], + filter}). + +-ifndef(default_verbosity). +-define(default_verbosity,silence). +-endif. + +-define(DEFAULT_FILTER_MODULE, snmpa_net_if_filter). +-define(DEFAULT_FILTER_OPTS, [{module, ?DEFAULT_FILTER_MODULE}]). + + +%%%----------------------------------------------------------------- +%%% 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_port() -> + {value, UDPPort} = snmp_framework_mib:intAgentUDPPort(get), + UDPPort. + +get_address() -> + {value, IPAddress} = snmp_framework_mib:intAgentIpAddress(get), + IPAddress. + +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()}), + loop(State); + {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",[]), + + %% -- Port and address -- + UDPPort = get_port(), + ?vdebug("port: ~w",[UDPPort]), + IPAddress = get_address(), + ?vdebug("addr: ~w",[IPAddress]), + + %% -- 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", [FilterMod]), + + %% -- Audit trail log + Log = create_log(), + ?vdebug("Log: ~w",[Log]), + + + %% -- Socket -- + IPOpts1 = ip_opt_bind_to_ip_address(Opts, IPAddress), + IPOpts2 = ip_opt_no_reuse_address(Opts), + IPOpts3 = ip_opt_recbuf(Opts), + IPOpts4 = ip_opt_sndbuf(Opts), + IPOpts = [binary | IPOpts1 ++ IPOpts2 ++ IPOpts3 ++ IPOpts4], + ?vdebug("open socket with options: ~w",[IPOpts]), + case gen_udp_open(UDPPort, IPOpts) of + {ok, Sock} -> + MpdState = snmpa_mpd:init(Vsns), + init_counters(), + active_once(Sock), + S = #state{parent = Parent, + note_store = NoteStore, + master_agent = MasterAgent, + mpd_state = MpdState, + usock = Sock, + usock_opts = IPOpts, + log = Log, + limit = Limit, + filter = FilterMod}, + ?vdebug("started with MpdState: ~p", [MpdState]), + {ok, S}; + {error, Reason} -> + ?vinfo("Failed to open UDP socket: ~p", [Reason]), + {error, {udp_open, UDPPort, Reason}} + 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 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. + + +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, Addr, Port) -> + case lists:member(write, Types) of + true -> + snmp_log:log(Log, Packet, Addr, Port); + false -> + ok + end; +log({Log, Types}, _, Packet, Addr, Port) -> + case lists:member(read, Types) of + true -> + snmp_log:log(Log, Packet, Addr, Port); + false -> + ok + end; +log(_, _, _, _, _) -> + ok. + + +gen_udp_open(Port, Opts) -> + case init:get_argument(snmp_fd) of + {ok, [[FdStr]]} -> + Fd = list_to_integer(FdStr), + gen_udp:open(0, [{fd, Fd}|Opts]); + error -> + case init:get_argument(snmpa_fd) of + {ok, [[FdStr]]} -> + Fd = list_to_integer(FdStr), + gen_udp:open(0, [{fd, Fd}|Opts]); + error -> + gen_udp:open(Port, Opts) + end + end. + + +loop(S) -> + receive + {udp, _UdpId, Ip, Port, Packet} -> + ?vlog("got paket from ~w:~w",[Ip,Port]), + NewS = maybe_handle_recv(S, Ip, Port, Packet), + loop(NewS); + + {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, Dest, []} -> + ?vlog("reply pdu: " + "~n ~s", + [?vapply(snmp_misc, format, [256, "~w", [RePdu]])]), + NewS = maybe_handle_reply_pdu(S, Vsn, RePdu, Type, ACMData, Dest), + loop(NewS); + + %% Traps/notification + {send_pdu, Vsn, Pdu, MsgData, To} -> + ?vdebug("send pdu: " + "~n Pdu: ~p" + "~n To: ~p", [Pdu, To]), + NewS = maybe_handle_send_pdu(S, Vsn, Pdu, MsgData, To, undefined), + loop(NewS); + + %% Informs + {send_pdu_req, Vsn, Pdu, MsgData, To, From} -> + ?vdebug("send pdu request: " + "~n Pdu: ~p" + "~n To: ~p" + "~n From: ~p", + [Pdu, To, toname(From)]), + NewS = maybe_handle_send_pdu(S, Vsn, Pdu, MsgData, To, From), + loop(NewS); + + %% Discovery Inform + {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); + + {discarded_pdu, _Vsn, ReqId, _ACMData, Variable, _Extra} -> + ?vdebug("discard PDU: ~p", [Variable]), + snmpa_mpd:discarded_pdu(Variable), + NewS = update_req_counter_outgoing(S, ReqId), + loop(NewS); + + {get_log_type, ReplyRef, Pid} -> + ?vdebug("get log type: ~p", []), + #state{log = {_, LogType}} = S, + 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", []), + #state{limit = Limit} = S, + 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} when Parent == S#state.parent -> + ?vlog("parent (~p) exited: " + "~n ~p", [Parent, Reason]), + exit(Reason); + + {'EXIT', Port, Reason} when Port == S#state.usock -> + UDPPort = get_port(), + NewS = + case gen_udp_open(UDPPort, S#state.usock_opts) of + {ok, Id} -> + error_msg("Port ~p exited for reason" + "~n ~p" + "~n Re-opened (~p)", [Port, Reason, Id]), + S#state{usock = Id}; + {error, ReopenReason} -> + error_msg("Port ~p exited for reason" + "~n ~p" + "~n Re-open failed with reason" + "~n ~p", + [Port, Reason, ReopenReason]), + ok + end, + loop(NewS); + + {'EXIT', Port, Reason} when is_port(Port) -> + error_msg("Exit message from port ~p for reason ~p~n", + [Port, Reason]), + loop(S); + + {'EXIT', Pid, Reason} -> + ?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, S#state.parent, ?MODULE, [], S); + + _ -> + loop(S) + end. + + +update_req_counter_incomming(#state{limit = infinity, usock = Sock} = S, _) -> + active_once(Sock), %% No limit so activate directly + S; +update_req_counter_incomming(#state{limit = Limit, + rcnt = RCnt, + usock = Sock} = S, Rid) + when length(RCnt) + 1 == Limit -> + %% Ok, one more and we are at the limit. + %% Just make sure we are not already processing this one... + case lists:member(Rid, RCnt) of + false -> + %% We are at the limit, do _not_ activate socket + S#state{rcnt = [Rid|RCnt]}; + true -> + active_once(Sock), + S + end; +update_req_counter_incomming(#state{rcnt = RCnt, + usock = Sock} = S, Rid) -> + active_once(Sock), + case lists:member(Rid, RCnt) of + false -> + S#state{rcnt = [Rid|RCnt]}; + true -> + S + end. + + +update_req_counter_outgoing(#state{limit = infinity} = S, _Rid) -> + %% Already activated (in the incoming function) + S; +update_req_counter_outgoing(#state{limit = Limit, + rcnt = RCnt, + usock = Sock} = S, Rid) + when length(RCnt) == Limit -> + ?vtrace("handle_req_counter_outgoing(~w) -> entry with" + "~n Rid: ~w" + "~n length(RCnt): ~w", [Limit, Rid, length(RCnt)]), + case lists:delete(Rid, RCnt) of + NewRCnt when length(NewRCnt) < Limit -> + ?vtrace("update_req_counter_outgoing -> " + "passed below limit: activate", []), + active_once(Sock), + S#state{rcnt = NewRCnt}; + _ -> + S + end; +update_req_counter_outgoing(#state{limit = Limit, rcnt = RCnt} = S, + Rid) -> + ?vtrace("handle_req_counter_outgoing(~w) -> entry with" + "~n Rid: ~w" + "~n length(RCnt): ~w", [Limit, Rid, length(RCnt)]), + NewRCnt = lists:delete(Rid, RCnt), + S#state{rcnt = NewRCnt}. + + +maybe_handle_recv(#state{filter = FilterMod} = S, + Ip, Port, Packet) -> + case (catch FilterMod:accept_recv(Ip, Port)) of + false -> + %% Drop the received packet + inc(netIfMsgInDrops), + S; + _ -> + handle_recv(S, Ip, Port, Packet) + end. + +handle_discovery_response(_Ip, _Port, #pdu{request_id = ReqId} = Pdu, + ManagerEngineId, + #state{usock = Sock, reqs = Reqs} = S) -> + case lists:keysearch(ReqId, 1, S#state.reqs) of + {value, {_, Pid}} -> + active_once(Sock), + Pid ! {snmp_discovery_response_received, Pdu, ManagerEngineId}, + NReqs = lists:keydelete(ReqId, 1, Reqs), + S#state{reqs = NReqs}; + _ -> + %% Ouch, timeout? resend? + S + end. + + +handle_recv(#state{usock = Sock, + mpd_state = MpdState, + note_store = NS, + log = Log} = S, Ip, Port, Packet) -> + put(n1, erlang:now()), + LogF = fun(Type, Data) -> + log(Log, Type, Data, Ip, Port) + end, + case (catch snmpa_mpd:process_packet(Packet, snmpUDPDomain, {Ip, Port}, + MpdState, NS, LogF)) of + {ok, _Vsn, Pdu, _PduMS, {discovery, ManagerEngineId}} -> + handle_discovery_response(Ip, Port, Pdu, ManagerEngineId, S); + + {ok, _Vsn, Pdu, _PduMS, discovery} -> + handle_discovery_response(Ip, Port, Pdu, undefined, S); + + {ok, Vsn, Pdu, PduMS, ACMData} -> + ?vlog("got pdu ~s", + [?vapply(snmp_misc, format, [256, "~w", [Pdu]])]), + %% handle_recv_pdu(Ip, Port, Vsn, Pdu, PduMS, ACMData, S); + maybe_handle_recv_pdu(Ip, Port, Vsn, Pdu, PduMS, ACMData, S); + + {discarded, Reason} -> + ?vlog("packet discarded for reason: ~s", + [?vapply(snmp_misc, format, [256, "~w", [Reason]])]), + active_once(Sock), + S; + + {discarded, Reason, ReportPacket} -> + ?vlog("sending report for reason: " + "~n ~s", + [?vapply(snmp_misc, format, [256, "~w", [Reason]])]), + (catch udp_send(S#state.usock, Ip, Port, ReportPacket)), + active_once(Sock), + S; + + {discovery, ReportPacket} -> + ?vlog("sending discovery report", []), + (catch udp_send(S#state.usock, Ip, Port, ReportPacket)), + active_once(Sock), + S; + + Error -> + error_msg("processing of received message failed: " + "~n ~p", [Error]), + active_once(Sock), + S + end. + +maybe_handle_recv_pdu(Ip, Port, Vsn, #pdu{type = Type} = Pdu, PduMS, ACMData, + #state{usock = Sock, filter = FilterMod} = S) -> + case (catch FilterMod:accept_recv_pdu(Ip, Port, Type)) of + false -> + inc(netIfPduInDrops), + active_once(Sock), + ok; + _ -> + handle_recv_pdu(Ip, Port, Vsn, Pdu, PduMS, ACMData, S) + end; +maybe_handle_recv_pdu(Ip, Port, Vsn, Pdu, PduMS, ACMData, S) -> + handle_recv_pdu(Ip, Port, Vsn, Pdu, PduMS, ACMData, S). + +handle_recv_pdu(Ip, Port, Vsn, #pdu{type = 'get-response'} = Pdu, + _PduMS, _ACMData, #state{usock = Sock} = S) -> + active_once(Sock), + handle_response(Vsn, Pdu, {Ip, Port}, S), + S; +handle_recv_pdu(Ip, Port, Vsn, #pdu{request_id = Rid, type = Type} = Pdu, + PduMS, ACMData, #state{master_agent = Pid} = S) + when ((Type =:= 'get-request') orelse + (Type =:= 'get-next-request') orelse + (Type =:= 'get-bulk-request')) -> + ?vtrace("handle_recv_pdu -> received get (~w)", [Type]), + Pid ! {snmp_pdu, Vsn, Pdu, PduMS, ACMData, {Ip, Port}, []}, + update_req_counter_incomming(S, Rid); +handle_recv_pdu(Ip, Port, Vsn, Pdu, PduMS, ACMData, + #state{usock = Sock, master_agent = Pid} = S) -> + ?vtrace("handle_recv_pdu -> received other request", []), + active_once(Sock), + Pid ! {snmp_pdu, Vsn, Pdu, PduMS, ACMData, {Ip, Port}, []}, + S. + + +maybe_handle_reply_pdu(#state{filter = FilterMod} = S, Vsn, + #pdu{request_id = Rid} = Pdu, + Type, ACMData, {Ip, Port} = Dest) -> + S1 = update_req_counter_outgoing(S, Rid), + case (catch FilterMod:accept_send_pdu([{Ip, Port}], Type)) of + false -> + inc(netIfPduOutDrops), + ok; + _ -> + handle_reply_pdu(S1, Vsn, Pdu, Type, ACMData, Dest) + end, + S1. + +handle_reply_pdu(#state{log = Log, + usock = Sock, + filter = FilterMod}, + Vsn, Pdu, Type, ACMData, {Ip, Port}) -> + LogF = fun(Type2, Data) -> + log(Log, Type2, Data, Ip, Port) + 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()]), + maybe_udp_send(FilterMod, Sock, Ip, Port, Packet); + {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: ~w~n~w", [Pdu, Reason]) + end. + + +process_taddrs(To) -> + process_taddrs(To, []). + +process_taddrs([], Acc) -> + lists:reverse(Acc); +%% v3 +process_taddrs([{{_Domain, AddrAndPort}, _SecData}|T], Acc) -> + process_taddrs(T, [AddrAndPort|Acc]); +%% v1 & v2 +process_taddrs([{_Domain, AddrAndPort}|T], Acc) -> + process_taddrs(T, [AddrAndPort|Acc]). + + +merge_taddrs(To1, To2) -> + merge_taddrs(To1, To2, []). + +merge_taddrs([], _To2, Acc) -> + lists:reverse(Acc); +%% v3 +merge_taddrs([{{_, AddrAndPort}, _} = H|To1], To2, Acc) -> + case lists:member(AddrAndPort, To2) of + true -> + merge_taddrs(To1, To2, [H|Acc]); + false -> + merge_taddrs(To1, To2, Acc) + end; +%% v1 & v2 +merge_taddrs([{_, AddrAndPort} = H|To1], To2, Acc) -> + case lists:member(AddrAndPort, To2) of + true -> + merge_taddrs(To1, To2, [H|Acc]); + false -> + merge_taddrs(To1, To2, Acc) + end; +merge_taddrs([_Crap|To1], To2, Acc) -> + merge_taddrs(To1, To2, Acc). + + +maybe_handle_send_pdu(#state{filter = FilterMod} = S, + Vsn, Pdu, MsgData, To0, From) -> + + ?vtrace("maybe_handle_send_pdu -> entry with" + "~n FilterMod: ~p" + "~n To0: ~p", [FilterMod, To0]), + + To1 = snmpa_mpd:process_taddrs(To0), + To2 = process_taddrs(To1), + + case (catch FilterMod:accept_send_pdu(To2, pdu_type_of(Pdu))) of + false -> + inc(netIfPduOutDrops), + ok; + true -> + handle_send_pdu(S, Vsn, Pdu, MsgData, To1, From); + To3 when is_list(To3) -> + To4 = merge_taddrs(To1, To3), + ?vtrace("maybe_handle_send_pdu -> To4: " + "~n ~p", [To4]), + handle_send_pdu(S, Vsn, Pdu, MsgData, To4, From); + _ -> + handle_send_pdu(S, Vsn, Pdu, MsgData, To1, From) + end. + +handle_send_pdu(#state{note_store = NS} = S, Vsn, Pdu, MsgData, To, From) -> + + ?vtrace("handle_send_pdu -> entry with" + "~n Pdu: ~p" + "~n To: ~p", [Pdu, To]), + + case (catch snmpa_mpd:generate_msg(Vsn, NS, Pdu, MsgData, To)) of + {ok, Addresses} -> + 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: ~w~n~w", [Pdu, Reason]), + ok + end, + case From of + undefined -> + S; + 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} = S, + Pdu, MsgData, + To, 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, {Packet, {Ip, Port}}} -> + handle_send_discovery(S, Pdu, Packet, Ip, Port, From); + {discarded, Reason} -> + ?vlog("handle_send_discovery -> " + "~n Discovery PDU ~p not sent due to ~p", [Pdu, Reason]), + ok; + {'EXIT', Reason} -> + user_err("failed generating discovery message: " + "~n PDU: ~w" + "~n Reason: ~w", [Pdu, Reason]), + ok + end. + +handle_send_discovery(#state{log = Log, + usock = Sock, + reqs = Reqs} = S, + #pdu{type = Type, + request_id = ReqId}, + Packet, Ip, Port, From) + when is_binary(Packet) -> + log(Log, Type, Packet, Ip, Port), + udp_send(Sock, Ip, Port, Packet), + ?vtrace("handle_send_discovery -> sent (~w)", [ReqId]), + NReqs = snmp_misc:keyreplaceadd(From, 2, Reqs, {ReqId, From}), + S#state{reqs = NReqs}. + + +handle_send_pdu(S, #pdu{type = Type} = Pdu, Addresses) -> + handle_send_pdu(S, Type, Pdu, Addresses); +handle_send_pdu(S, Trap, Addresses) -> + handle_send_pdu(S, trappdu, Trap, Addresses). + +handle_send_pdu(S, Type, Pdu, Addresses) -> + case (catch handle_send_pdu1(S, Type, Addresses)) of + {Reason, Sz} -> + error_msg("Cannot send message " + "~n size: ~p" + "~n reason: ~p" + "~n pdu: ~p", + [Sz, Reason, Pdu]); + _ -> + ok + end. + +handle_send_pdu1(#state{log = Log, + usock = Sock, + filter = FilterMod}, Type, Addresses) -> + SendFun = + fun({snmpUDPDomain, {Ip, Port}, Packet}) when is_binary(Packet) -> + ?vdebug("sending packet:" + "~n size: ~p" + "~n to: ~p:~p", + [sz(Packet), Ip, Port]), + maybe_udp_send(FilterMod, Log, Type, Sock, Ip, Port, Packet); + + ({snmpUDPDomain, {Ip, Port}, {Packet, _LogData}}) when is_binary(Packet) -> + ?vdebug("sending encrypted packet:" + "~n size: ~p" + "~n to: ~p:~p", + [sz(Packet), Ip, Port]), + maybe_udp_send(FilterMod, Log, Type, Sock, Ip, Port, Packet); + + (_X) -> + ?vlog("** bad res: ~p", [_X]), + ok + end, + lists:foreach(SendFun, Addresses). + + +handle_response(Vsn, Pdu, From, S) -> + case lists:keysearch(Pdu#pdu.request_id, 1, S#state.reqs) of + {value, {_, Pid}} -> + ?vdebug("handle_response -> " + "~n send response to receiver ~p", [Pid]), + Pid ! {snmp_response_received, Vsn, Pdu, From}; + _ -> + ?vdebug("handle_response -> " + "~n No receiver available for response pdu", []) + end. + +maybe_udp_send(FilterMod, Sock, Ip, Port, Packet) -> + case (catch FilterMod:accept_send(Ip, Port)) of + false -> + inc(netIfMsgOutDrops), + ok; + _ -> + (catch udp_send(Sock, Ip, Port, Packet)) + end. + +maybe_udp_send(FilterMod, AtLog, Type, Sock, Ip, Port, Packet) -> + case (catch FilterMod:accept_send(Ip, Port)) of + false -> + inc(netIfMsgOutDrops), + ok; + _ -> + log(AtLog, Type, Packet, Ip, Port), + (catch udp_send(Sock, Ip, Port, Packet)) + end. + + +udp_send(UdpId, AgentIp, UdpPort, B) -> + case (catch gen_udp:send(UdpId, AgentIp, UdpPort, 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)", + [AgentIp, UdpPort, sz(B), ErrorReason]); + {'EXIT', ExitReason} -> + error_msg("[exit] cannot send message " + "(destination: ~p:~p, size: ~p, reason: ~p)", + [AgentIp, UdpPort, sz(B), ExitReason]); + _ -> + ok + 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), + S#state{reqs = NReqs}. + + +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", []), + inet:setopts(Sock, [{active, once}]). + + +%%%----------------------------------------------------------------- + +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}) -> + do_close_log(Log), + exit(Reason). + +system_code_change(OldState, _Module, _OldVsn, upgrade_from_pre_4_10) -> + {state, + parent = Parent, + note_store = NS, + master_agent = MA, + usock = Sock, + usock_opts = SockOpts, + mpd_state = MpdState, + log = Log, + reqs = Reqs, + debug = Dbg, + limit = Limit, + rcnt = RCNT} = OldState, + NewState = #state{parent = Parent, + note_store = NS, + master_agent = MA, + usock = Sock, + usock_opts = SockOpts, + mpd_state = MpdState, + log = Log, + reqs = Reqs, + debug = Dbg, + limit = Limit, + rcnt = RCNT, + filter = create_filter(?DEFAULT_FILTER_OPTS)}, + {ok, NewState}; +system_code_change(OldState, _Module, _OldVsn, downgrade_to_pre_4_10) -> + #state{parent = Parent, + note_store = NS, + master_agent = MA, + usock = Sock, + usock_opts = SockOpts, + mpd_state = MpdState, + log = Log, + reqs = Reqs, + debug = Dbg, + limit = Limit, + rcnt = RCNT} = OldState, + NewState = + {state, + parent = Parent, + note_store = NS, + master_agent = MA, + usock = Sock, + usock_opts = SockOpts, + mpd_state = MpdState, + log = Log, + reqs = Reqs, + debug = Dbg, + limit = Limit, + rcnt = RCNT}, + {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() -> + subtr(erlang:now(), get(n1)). + +subtr({X1,Y1,Z1}, {X1,Y1,Z2}) -> + Z1 - Z2; +subtr({X1,Y1,Z1}, {X1,Y2,Z2}) -> + ((Y1-Y2) * 1000000) + (Z1 - Z2); +subtr({X1,Y1,Z1}, {X2,Y2,Z2}) -> + ((X1 - X2) * 1000000000000) + ((Y1 - Y2) * 1000000) + (Z1 - Z2). + + +%% ---------------------------------------------------------------- + +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. + + +%% ---------------------------------------------------------------- + +ip_opt_bind_to_ip_address(Opts, Ip) -> + case get_bind_to_ip_address(Opts) of + true -> + [{ip, list_to_tuple(Ip)}]; + _ -> + [] + end. + +ip_opt_no_reuse_address(Opts) -> + case get_no_reuse_address(Opts) of + false -> + [{reuseaddr, true}]; + _ -> + [] + end. + +ip_opt_recbuf(Opts) -> + case get_recbuf(Opts) of + use_default -> + []; + Sz -> + [{recbuf, Sz}] + end. + +ip_opt_sndbuf(Opts) -> + case get_sndbuf(Opts) of + use_default -> + []; + Sz -> + [{sndbuf, Sz}] + end. + + +%% ---------------------------------------------------------------- + +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_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) -> + snmp_misc:get_option(recbuf, Opts, use_default). + +get_sndbuf(Opts) -> + snmp_misc:get_option(sndbuf, Opts, use_default). + +get_no_reuse_address(Opts) -> + snmp_misc:get_option(no_reuse, Opts, false). + +get_bind_to_ip_address(Opts) -> + snmp_misc:get_option(bind_to, Opts, false). + + +%% ---------------------------------------------------------------- + +error_msg(F,A) -> + ?snmpa_error("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{usock = Id, reqs = Reqs}) -> + ProcSize = proc_mem(self()), + PortInfo = get_port_info(Id), + Counters = get_counters(), + [{reqs, Reqs}, + {counters, Counters}, + {process_memory, ProcSize}, + {port_info, PortInfo}]. + +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])) of + {ok, Sz} -> + [{buffer_size, Sz}]; + _ -> + [] + end, + [{socket, Id}] ++ + IfList ++ + PortStats ++ + PortInfo ++ + PortStatus ++ + PortAct ++ + BufSz. + + +%% ---------------------------------------------------------------- + +% i(F) -> +% i(F, []). + +% i(F, A) -> +% io:format("~p: " ++ F ++ "~n", [?MODULE|A]). + |