%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 1996-2012. 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_agent). -include_lib("kernel/include/file.hrl"). -include("snmpa_internal.hrl"). -include("snmp_types.hrl"). -include("snmp_debug.hrl"). -include("snmp_verbosity.hrl"). -include("SNMP-FRAMEWORK-MIB.hrl"). %% External exports -export([start_link/4, start_link/5, stop/1]). -export([subagent_set/2, load_mibs/2, unload_mibs/2, which_mibs/1, whereis_mib/2, info/1, register_subagent/3, unregister_subagent/2, send_notification/3, register_notification_filter/5, unregister_notification_filter/2, which_notification_filter/1, get_net_if/1]). -export([ discovery/6, is_originating_discovery_enabled/0, is_terminating_discovery_enabled/0, terminating_discovery_stage2/0, terminating_trigger_username/0 ]). -export([verbosity/2, dump_mibs/1, dump_mibs/2]). -export([validate_err/3, make_value_a_correct_value/3, do_get/3, do_get/4, get/2, get/3, get_next/2, get_next/3]). -export([mib_of/1, mib_of/2, me_of/1, me_of/2, invalidate_mibs_cache/1, which_mibs_cache_size/1, enable_mibs_cache/1, disable_mibs_cache/1, gc_mibs_cache/1, gc_mibs_cache/2, gc_mibs_cache/3, enable_mibs_cache_autogc/1, disable_mibs_cache_autogc/1, update_mibs_cache_age/2, update_mibs_cache_gclimit/2]). -export([get_agent_mib_storage/0, db/1, backup/2]). -export([get_log_type/1, set_log_type/2]). -export([get_request_limit/1, set_request_limit/2]). -export([invalidate_ca_cache/0]). -export([increment_counter/3]). -export([restart_worker/1, restart_set_worker/1]). %% For backward compatibillity -export([send_trap/6, send_trap/7]). %% Internal exports -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3, tr_var/2, tr_varbind/1, handle_pdu/8, worker/2, worker_loop/1, do_send_trap/7, do_send_trap/8]). %% <BACKWARD-COMPAT> -export([handle_pdu/7]). %% </BACKWARD-COMPAT> -include("snmpa_internal.hrl"). -ifndef(default_verbosity). -define(default_verbosity,silence). -endif. -define(empty_pdu_size, 21). -ifdef(snmp_extended_verbosity). -define(vt(F,A), ?vtrace(F, A)). -else. -define(vt(_F, _A), ok). -endif. -define(DISCO_TERMINATING_TRIGGER_USERNAME, ""). -ifdef(snmp_debug). -define(GS_START_LINK3(Prio, Parent, Ref, Opts), gen_server:start_link(?MODULE, [Prio, Parent, Ref, Opts], [{debug,[trace]}])). -define(GS_START_LINK4(Prio, Name, Parent, Ref, Opts), gen_server:start_link({local, Name}, ?MODULE, [Prio, Parent, Ref, Opts], [{debug,[trace]}])). -else. -define(GS_START_LINK3(Prio, Parent, Ref, Opts), gen_server:start_link(?MODULE, [Prio, Parent, Ref, Opts],[])). -define(GS_START_LINK4(Prio, Name, Parent, Ref, Opts), gen_server:start_link({local, Name}, ?MODULE, [Prio, Parent, Ref, Opts],[])). -endif. %% Increment this whenever a change is made to the worker interface -define(WORKER_INTERFACE_VERSION, 1). %% -- Utility macros for creating worker commands -- -define(mk_pdu_wreq(Vsn, Pdu, PduMS, ACMData, Address, GbMaxVBs, Extra), #wrequest{cmd = handle_pdu, info = [{vsn, Vsn}, {pdu, Pdu}, {pdu_ms, PduMS}, {acm_data, ACMData}, {addr, Address}, {gb_max_vbs, GbMaxVBs}, {extra, Extra}]}). -define(mk_send_trap_wreq(TrapRec, NotifyName, ContextName, Recv, Vbs, LocalEngineID, Extra), #wrequest{cmd = send_trap, info = [{trap_rec, TrapRec}, {notify_name, NotifyName}, {context_name, ContextName}, {receiver, Recv}, {varbinds, Vbs}, {local_engine_id, LocalEngineID}, {extra, Extra}]}). -define(mk_terminate_wreq(), #wrequest{cmd = terminate, info = []}). -define(mk_verbosity_wreq(V), #wrequest{cmd = verbosity, info = [{verbosity, V}]}). -record(notification_filter, {id, mod, data}). -record(disco, {from, rec, sender, target, engine_id, sec_level, ctx, ivbs, stage, handler, extra}). %% This record is used when sending requests to the worker processes -record(wrequest, { version = ?WORKER_INTERFACE_VERSION, cmd, info } ). %%----------------------------------------------------------------- %% The agent is multi-threaded, i.e. each request is handled %% by a separate process. However, in the normal case, there %% is just one request handled at the time. In order to improve %% performance, there is always two worker processes alive. They are %% created at initialization time. There is always one worker %% dedicated to SET-handling. When a get*-request is received, %% it is sent to the worker, and the worker is marked as busy. %% If a request is received when the worker is busy, a new temporary %% worker is spawned. %% Code change %% =========== %% Note that the worker(s) execute the same module as the master %% agent. For code change we have two options - ignore the workers, %% or send them a code change message. %%----------------------------------------------------------------- -record(state, {type, parent, worker, worker_state = ready, set_worker, multi_threaded, ref, vsns, nfilters = [], note_store, mib_server, %% Currently unused net_if, %% Currently unused net_if_mod, backup, disco, mibs_cache_request, gb_max_vbs}). %%%----------------------------------------------------------------- %%% This module implements the agent machinery; both for the master %%% agent and the subagents. %%%----------------------------------------------------------------- %%% Table of contents %%% ================= %%% 1. Interface %%% 2. Main loop %%% 3. GET REQUEST %%% 4. GET-NEXT REQUEST %%% 5. GET-BULK REQUEST %%% 6. SET REQUEST %%% 7. Misc functions %%%----------------------------------------------------------------- %%----------------------------------------------------------------- %% Parent is a Pid (of master_agent) or none %% Options is a list of Option, where Option is %% {mibs, Mibs} %% {net_if, NetIfModule} %% {priority, Prio} %% {verbosity, Verbosity} %% {multi_threaded, Bool} true means that SETs are serialized, %% while GETs are concurrent, even with a SET. %% {set_mechanism, SetModule} % undocumented feature %% %% The following options are now removed - they are not needed %% anymore when VACM is standard for authentication, and works %% with all versions, and trap sending is standardized too. %% {authentication_service, AuthModule} % undocumented feature %% {trap_mechanism, TrapModule} % undocumented feature %% Note: authentication_service is reintroduced for AXD301 (OTP-3324). %%----------------------------------------------------------------- start_link(Prio, Parent, Ref, Options) -> ?d("start_link -> entry with" "~n Prio: ~p" "~n Parent: ~p" "~n Ref: ~p" "~n Options: ~p", [Prio, Parent, Ref, Options]), %% gen_server:start_link(?MODULE, [Prio, Parent, Ref, Options], []). ?GS_START_LINK3(Prio, Parent, Ref, Options). start_link(Prio, Name, Parent, Ref, Options) -> ?d("start_link -> entry with" "~n Prio: ~p" "~n Name: ~p" "~n Parent: ~p" "~n Ref: ~p" "~n Options: ~p", [Prio, Name, Parent, Ref, Options]), % gen_server:start_link({local, Name}, ?MODULE, % [Prio, Parent, Ref, Options], []). ?GS_START_LINK4(Prio, Name, Parent, Ref, Options). stop(Agent) -> call(Agent, stop). restart_worker(Agent) -> call(Agent, restart_worker). restart_set_worker(Agent) -> call(Agent, restart_set_worker). get_log_type(Agent) -> call(Agent, get_log_type). set_log_type(Agent, NewType) -> call(Agent, {set_log_type, NewType}). get_request_limit(Agent) -> call(Agent, get_request_limit). set_request_limit(Agent, NewLimit) -> call(Agent, {set_request_limit, NewLimit}). mib_of(Oid) when is_list(Oid) -> mib_of(snmp_master_agent, Oid). mib_of(Agent, Oid) when is_list(Oid) -> call(Agent, {mib_of, Oid}). me_of(Oid) when is_list(Oid) -> me_of(snmp_master_agent, Oid). me_of(Agent, Oid) when is_list(Oid) -> call(Agent, {me_of, Oid}). invalidate_mibs_cache(Agent) -> call(Agent, {mibs_cache_request, invalidate_cache}). gc_mibs_cache(Agent) -> call(Agent, {mibs_cache_request, gc_cache}). gc_mibs_cache(Agent, Age) -> call(Agent, {mibs_cache_request, {gc_cache, Age}}). gc_mibs_cache(Agent, Age, GcLimit) -> call(Agent, {mibs_cache_request, {gc_cache, Age, GcLimit}}). enable_mibs_cache(Agent) -> call(Agent, {mibs_cache_request, enable_cache}). disable_mibs_cache(Agent) -> call(Agent, {mibs_cache_request, disable_cache}). which_mibs_cache_size(Agent) -> call(Agent, {mibs_cache_request, cache_size}). enable_mibs_cache_autogc(Agent) -> call(Agent, {mibs_cache_request, enable_autogc}). disable_mibs_cache_autogc(Agent) -> call(Agent, {mibs_cache_request, disable_autogc}). update_mibs_cache_gclimit(Agent, GcLimit) -> call(Agent, {mibs_cache_request, {update_gclimit, GcLimit}}). update_mibs_cache_age(Agent, Age) -> call(Agent, {mibs_cache_request, {update_age, Age}}). increment_counter(Counter, Initial, Max) -> %% This is to make sure no one else increments our counter Key = {Counter, self()}, %% Counter data Position = 2, Increment = 1, Threshold = Max, SetValue = Initial, UpdateOp = {Position, Increment, Threshold, SetValue}, %% And now for the actual increment Tab = snmp_agent_table, case (catch ets:update_counter(Tab, Key, UpdateOp)) of {'EXIT', {badarg, _}} -> %% Oups, first time ets:insert(Tab, {Key, Initial}), Initial; Next when is_integer(Next) -> Next end. init([Prio, Parent, Ref, Options]) -> ?d("init -> entry with" "~n Prio: ~p" "~n Parent: ~p" "~n Ref: ~p" "~n Options: ~p", [Prio, Parent, Ref, Options]), case (catch do_init(Prio, Parent, Ref, Options)) of {ok, State} -> ?vdebug("started",[]), {ok, State}; {error, Reason} -> config_err("failed starting agent: ~n~p", [Reason]), {stop, Reason} end. do_init(Prio, Parent, Ref, Options) -> process_flag(priority, Prio), put(sname,short_name(Parent)), put(verbosity,get_verbosity(Options)), ?vlog("starting with: " "~n Prio: ~p" "~n Parent: ~p" "~n Ref: ~p" "~n Options: ~p",[Prio, Parent, Ref, Options]), Mibs = get_mibs(Options), SetModule = get_set_mechanism(Options), put(set_module, SetModule), %% OTP-3324. For AXD301. AuthModule = get_authentication_service(Options), put(auth_module, AuthModule), MultiT = get_multi_threaded(Options), Vsns = get_versions(Options), GbMaxVbs = get_gb_max_vbs(Options), NS = start_note_store(Prio, Ref, Options), {Type, NetIfPid, NetIfMod} = start_net_if(Parent, Prio, Ref, Vsns, NS, Options), MibPid = start_mib_server(Prio, Ref, Mibs, Options), put(net_if, NetIfPid), put(mibserver, MibPid), process_flag(trap_exit, true), {Worker, SetWorker} = workers_start(MultiT), {ok, #state{type = Type, parent = Parent, worker = Worker, set_worker = SetWorker, multi_threaded = MultiT, ref = Ref, vsns = Vsns, note_store = NS, net_if_mod = NetIfMod, gb_max_vbs = GbMaxVbs}}. start_note_store(Prio, Ref, Options) -> ?vdebug("start_note_store -> with Prio: ~p", [Prio]), NsOpts = get_note_store_opt(Options), ?vtrace("start_note_store -> NsOpts: ~p", [NsOpts]), case (catch snmpa_misc_sup:start_note_store(Prio, Ref, NsOpts)) of {ok, Pid} -> ?vdebug("start_note_store -> Pid: ~p", [Pid]), Pid; {error, Reason} -> ?vinfo("error starting note store: ~n~p",[Reason]), throw({error, {note_store_error, Reason}}); {'EXIT', Reason} -> ?vinfo("exit starting note store: ~n~p",[Reason]), throw({error, {note_store_exit, Reason}}); Error -> ?vinfo("failed starting note store: ~n~p",[Error]), throw({error, {note_store_failed, Error}}) end. start_net_if(none, Prio, Ref, Vsns, NoteStore, Options) -> ?vdebug("start_net_if(none) -> with Prio: ~p", [Prio]), NetIfOpts = get_net_if_opt(Options), Verbosity = get_net_if_verbosity(NetIfOpts), Mod = get_net_if_module(NetIfOpts), NiOptions = get_net_if_options(NetIfOpts), NiOpts = [{versions, Vsns}, {verbosity, Verbosity} | NiOptions], ?vtrace("start_net_if -> " "~n Mod: ~p" "~n NiOpts: ~p",[Mod, NiOpts]), case (catch snmpa_misc_sup:start_net_if(Prio, NoteStore, Ref, self(), Mod, NiOpts)) of {ok, Pid} -> ?vdebug("start_net_if -> Pid: ~p", [Pid]), {master_agent, Pid, Mod}; {error, Reason} -> ?vinfo("error starting net if: ~n~p",[Reason]), throw({error, {net_if_error, Reason}}); {'EXIT', Reason} -> ?vinfo("exit starting net if: ~n~p",[Reason]), throw({error, {net_if_exit, Reason}}); Error -> ?vinfo("failed starting net if: ~n~p",[Error]), throw({error, {net_if_failed, Error}}) end; start_net_if(Parent, _Prio, _Ref, _Vsns, _NoteStore, _Options) when is_pid(Parent) -> ?vdebug("start_net_if(~p) -> subagent => ignore", [Parent]), {subagent, undefined, undefined}. start_mib_server(Prio, Ref, Mibs, Options) -> ?vdebug("start_mib_server -> with Prio: ~p", [Prio]), MibStorage = get_mib_storage(Options), MibsOpts = [{mib_storage, MibStorage} | get_option(mib_server, Options, [])], ?vtrace("start_mib_server -> " "~n Mibs: ~p" "~n MibsOpts: ~p", [Mibs, MibsOpts]), case (catch snmpa_misc_sup:start_mib_server(Prio, Ref, Mibs, MibsOpts)) of {ok, Pid} -> ?vdebug("start_mib_server -> Pid: ~p", [Pid]), Pid; {error, Reason} -> ?vinfo("error starting mib server: ~n~p",[Reason]), throw({error, {mib_server_error, Reason}}); {'EXIT', Reason} -> ?vinfo("exit starting mib server: ~n~p",[Reason]), throw({error, {mib_server_exit, Reason}}); Error -> ?vinfo("failed starting mib server: ~n~p",[Error]), throw({error, {mib_server_failed, Error}}) end. %%----------------------------------------------------------------- %% Purpose: We must calculate the length of an empty Pdu. This %% length is used to calculate the max pdu size allowed %% for each get-bulk-request. This size is %% dependent on the varbinds. It is calculated %% as EmptySize + 8. 8 comes from the fact that the %% maximum pdu size needs 31 bits which needs 5 * 7 bits to be %% expressed. One 7bit octet is already present in the %% empty pdu, leaving 4 more 7bit octets. The length is %% repeated twice, once for the varbinds, and once for the %% entire pdu; 2 * 4 = 8. %% Actually, this function is not used, we use a constant instead. %%----------------------------------------------------------------- %% Ret: 21 %% empty_pdu() -> %% Pdu = #pdu{type = 'get-response', %% request_id = 1, %% error_status = noError, %% error_index = 0, %% varbinds = []}, %% length(snmp_pdus:enc_pdu(Pdu)) + 8. %%%-------------------------------------------------- %%% 1. Interface %%%-------------------------------------------------- %% Called by administrator (not subagent; deadlock could occur) register_subagent(Agent, SubTreeOid, SubagentPid) -> call(Agent, {register_subagent, SubTreeOid, SubagentPid}). %% Called by administrator (not subagent; deadlock could occur) unregister_subagent(Agent, SubagentOidOrPid) -> call(Agent, {unregister_subagent, SubagentOidOrPid}). %%----------------------------------------------------------------- %% These subagent_ functions either return a value, or exits %% with {nodedown, Node} | Reason. %%----------------------------------------------------------------- subagent_get(SubAgent, Varbinds, IsNotification) -> PduData = get_pdu_data(), call(SubAgent, {subagent_get, Varbinds, PduData, IsNotification}). subagent_get_next(SubAgent, MibView, Varbinds) -> PduData = get_pdu_data(), call(SubAgent, {subagent_get_next, MibView, Varbinds, PduData}). subagent_set(SubAgent, Arguments) -> PduData = get_pdu_data(), call(SubAgent, {subagent_set, Arguments, PduData}). %% Called by administrator (not agent; deadlock would occur) load_mibs(Agent, Mibs) -> call(Agent, {load_mibs, Mibs}). %% Called by administrator (not agent; deadlock would occur) unload_mibs(Agent, Mibs) -> call(Agent, {unload_mibs, Mibs}). which_mibs(Agent) -> call(Agent, which_mibs). whereis_mib(Agent, Mib) -> call(Agent, {whereis_mib, Mib}). info(Agent) -> call(Agent, info). get_net_if(Agent) -> call(Agent, get_net_if). register_notification_filter(Agent, Id, Mod, Args, Where) when (Where =:= first) orelse (Where =:= last) -> case (catch verify_notification_filter_behaviour(Mod)) of ok -> call(Agent, {register_notification_filter, Id, Mod, Args, Where}); Error -> Error end; register_notification_filter(Agent, Id, Mod, Args, {Loc, _Id} = Where) when (Loc =:= insert_before) orelse (Loc =:= insert_after) -> case (catch verify_notification_filter_behaviour(Mod)) of ok -> call(Agent, {register_notification_filter, Id, Mod, Args, Where}); Error -> Error end. verify_notification_filter_behaviour(Mod) -> snmp_misc:verify_behaviour(snmpa_notification_filter, Mod). unregister_notification_filter(Agent, Id) -> call(Agent, {unregister_notification_filter, Id}). which_notification_filter(Agent) -> call(Agent, which_notification_filter). send_notification(Agent, Notification, SendOpts) -> Msg = {send_notif, Notification, SendOpts}, maybe_call(Agent, Msg). %% <BACKWARD-COMPAT> send_trap(Agent, Trap, NotifyName, CtxName, Recv, Varbinds) -> ?d("send_trap -> entry with" "~n self(): ~p" "~n Agent: ~p [~p]" "~n Trap: ~p" "~n NotifyName: ~p" "~n CtxName: ~p" "~n Recv: ~p" "~n Varbinds: ~p", [self(), Agent, wis(Agent), Trap, NotifyName, CtxName, Recv, Varbinds]), SendOpts = [ {receiver, Recv}, {varbinds, Varbinds}, {name, NotifyName}, {context, CtxName}, {extra, ?DEFAULT_NOTIF_EXTRA_INFO} ], send_notification(Agent, Trap, SendOpts). send_trap(Agent, Trap, NotifyName, CtxName, Recv, Varbinds, LocalEngineID) -> ?d("send_trap -> entry with" "~n self(): ~p" "~n Agent: ~p [~p]" "~n Trap: ~p" "~n NotifyName: ~p" "~n CtxName: ~p" "~n Recv: ~p" "~n Varbinds: ~p" "~n LocalEngineID: ~p", [self(), Agent, wis(Agent), Trap, NotifyName, CtxName, Recv, Varbinds, LocalEngineID]), SendOpts = [ {receiver, Recv}, {varbinds, Varbinds}, {name, NotifyName}, {context, CtxName}, {extra, ?DEFAULT_NOTIF_EXTRA_INFO}, {local_engine_id, LocalEngineID} ], send_notification(Agent, Trap, SendOpts). %% </BACKWARD-COMPAT> %% -- Discovery functions -- disco_opts() -> case ets:lookup(snmp_agent_table, discovery) of [] -> []; [{discovery, DiscoOptions}] -> DiscoOptions end. originating_disco_opts() -> DiscoOpts = disco_opts(), case lists:keysearch(originating, 1, DiscoOpts) of {value, {originating, OrigDisco}} -> OrigDisco; _ -> [] end. is_originating_discovery_enabled() -> OrigDisco = originating_disco_opts(), case lists:keysearch(enable, 1, OrigDisco) of {value, {enable, false}} -> false; _ -> true end. terminating_disco_opts() -> DiscoOpts = disco_opts(), case lists:keysearch(terminating, 1, DiscoOpts) of {value, {terminating, TermDisco}} -> TermDisco; _ -> [] end. is_terminating_discovery_enabled() -> TermDisco = terminating_disco_opts(), case lists:keysearch(enable, 1, TermDisco) of {value, {enable, false}} -> false; _ -> true end. terminating_discovery_stage2() -> Default = discovery, TermDisco = terminating_disco_opts(), case lists:keysearch(stage2, 1, TermDisco) of {value, {stage2, Stage2}} when ((Stage2 =:= discovery) orelse (Stage2 =:= plain)) -> Stage2; _ -> Default end. terminating_trigger_username() -> Default = ?DISCO_TERMINATING_TRIGGER_USERNAME, TermDisco = terminating_disco_opts(), case lists:keysearch(trigger_username, 1, TermDisco) of {value, {trigger_username, Trigger}} when is_list(Trigger) -> Trigger; _ -> Default end. discovery(TargetName, Notification, ContextName, Varbinds, DiscoHandler, ExtraInfo) -> case is_originating_discovery_enabled() of true -> Agent = snmp_master_agent, call(Agent, {discovery, TargetName, Notification, ContextName, Varbinds, DiscoHandler, ExtraInfo}); false -> {error, not_enabled} end. wis(Pid) when is_pid(Pid) -> Pid; wis(Atom) when is_atom(Atom) -> whereis(Atom). forward_trap(Agent, TrapRecord, NotifyName, CtxName, Recv, Varbinds, ExtraInfo) -> Agent ! {forward_trap, TrapRecord, NotifyName, CtxName, Recv, Varbinds, ExtraInfo}. %%----------------------------------------------------------------- %% Args: Vars = [Oid] %% Returns: [Value] %% Called from a program to get variables. Don't call this from %% an instrumentation function; deadlock can occur! %%----------------------------------------------------------------- get(Agent, Vars) -> call(Agent, {get, Vars, ""}). get(Agent, Vars, Context) -> call(Agent, {get, Vars, Context}). get_next(Agent, Vars) -> call(Agent, {get_next, Vars, ""}). get_next(Agent, Vars, Context) -> call(Agent, {get_next, Vars, Context}). %%----------------------------------------------------------------- backup(Agent, BackupDir) when is_list(BackupDir) -> call(Agent, {backup, BackupDir}). %%----------------------------------------------------------------- %% Runtime debug support. %%----------------------------------------------------------------- dump_mibs(Agent) -> call(Agent, dump_mibs). dump_mibs(Agent, File) when is_list(File) -> call(Agent, {dump_mibs, File}). %%----------------------------------------------------------------- %% Runtime debug (verbosity) support. %%----------------------------------------------------------------- verbosity(net_if,Verbosity) -> cast(snmp_master_agent,{net_if_verbosity,Verbosity}); verbosity(mib_server,Verbosity) -> cast(snmp_master_agent,{mib_server_verbosity,Verbosity}); verbosity(note_store,Verbosity) -> cast(snmp_master_agent,{note_store_verbosity,Verbosity}); verbosity(sub_agents,Verbosity) -> cast(snmp_master_agent,{sub_agents_verbosity,Verbosity}); verbosity(master_agent,Verbosity) -> cast(snmp_master_agent,{verbosity,Verbosity}); verbosity(Agent,{sub_agents,Verbosity}) -> cast(Agent,{sub_agents_verbosity,Verbosity}); verbosity(Agent,Verbosity) -> cast(Agent,{verbosity,Verbosity}). %%%-------------------------------------------------- get_agent_mib_storage() -> ets:lookup_element(snmp_agent_table, agent_mib_storage, 2). db(Tab) -> {Tab, get_agent_mib_storage()}. %%%-------------------------------------------------- %%% 2. Main loop %%%-------------------------------------------------- %% gen_server:reply(From, Reply) handle_info({discovery_response, Response}, S) -> ?vdebug("handle_info(discovery_response) -> entry with" "~n Response: ~p", [Response]), NewS = handle_discovery_response(S, Response), {noreply, NewS}; handle_info({snmp_pdu, Vsn, Pdu, PduMS, ACMData, Address, Extra}, S) -> ?vdebug("handle_info(snmp_pdu) -> entry with" "~n Vsn: ~p" "~n Pdu: ~p" "~n Address: ~p" "~n Extra: ~p", [Vsn, Pdu, Address, Extra]), NewS = handle_snmp_pdu(is_valid_pdu_type(Pdu#pdu.type), Vsn, Pdu, PduMS, ACMData, Address, Extra, S), {noreply, NewS}; handle_info(worker_available, S) -> ?vdebug("worker available",[]), {noreply, S#state{worker_state = ready}}; handle_info({send_notif, Notification, SendOpts}, S) -> ?vlog("[handle_info] send notif request:" "~n Notification: ~p" "~n SendOpts: ~p", [Notification, SendOpts]), case (catch handle_send_trap(S, Notification, SendOpts)) of {ok, NewS} -> {noreply, NewS}; {'EXIT', R} -> ?vinfo("Trap not sent:~n ~p", [R]), {noreply, S}; _ -> {noreply, S} end; %% <BACKWARD-COMPAT> handle_info({send_trap, Trap, NotifyName, ContextName, Recv, Varbinds}, S) -> ?vlog("[handle_info] send trap request:" "~n Trap: ~p" "~n NotifyName: ~p" "~n ContextName: ~p" "~n Recv: ~p" "~n Varbinds: ~p", [Trap, NotifyName, ContextName, Recv, Varbinds]), ExtraInfo = ?DEFAULT_NOTIF_EXTRA_INFO, LocalEngineID = local_engine_id(S), case (catch handle_send_trap(S, Trap, NotifyName, ContextName, Recv, Varbinds, LocalEngineID, ExtraInfo)) of {ok, NewS} -> {noreply, NewS}; {'EXIT', R} -> ?vinfo("Trap not sent:~n ~p", [R]), {noreply, S}; _ -> {noreply, S} end; handle_info({send_trap, Trap, NotifyName, ContextName, Recv, Varbinds, LocalEngineID}, S) -> ?vlog("[handle_info] send trap request:" "~n Trap: ~p" "~n NotifyName: ~p" "~n ContextName: ~p" "~n Recv: ~p" "~n Varbinds: ~p" "~n LocalEngineID: ~p", [Trap, NotifyName, ContextName, Recv, Varbinds, LocalEngineID]), ExtraInfo = ?DEFAULT_NOTIF_EXTRA_INFO, case (catch handle_send_trap(S, Trap, NotifyName, ContextName, Recv, Varbinds, LocalEngineID, ExtraInfo)) of {ok, NewS} -> {noreply, NewS}; {'EXIT', R} -> ?vinfo("Trap not sent:~n ~p", [R]), {noreply, S}; _ -> {noreply, S} end; %% </BACKWARD-COMPAT> handle_info({forward_trap, TrapRecord, NotifyName, ContextName, Recv, Varbinds, ExtraInfo}, S) -> ?vlog("[handle_info] forward trap request:" "~n TrapRecord: ~p" "~n NotifyName: ~p" "~n ContextName: ~p" "~n Recv: ~p" "~n Varbinds: ~p", [TrapRecord, NotifyName, ContextName, Recv, Varbinds]), LocalEngineID = ?DEFAULT_LOCAL_ENGINE_ID, case (catch maybe_send_trap(S, TrapRecord, NotifyName, ContextName, Recv, Varbinds, LocalEngineID, ExtraInfo)) of {ok, NewS} -> {noreply, NewS}; {'EXIT', R} -> ?vinfo("Trap not sent:~n ~p", [R]), {noreply, S}; _ -> {noreply, S} end; %% <BACKWARD-COMPAT> handle_info({forward_trap, TrapRecord, NotifyName, ContextName, Recv, Varbinds}, S) -> ?vlog("[handle_info] forward trap request:" "~n TrapRecord: ~p" "~n NotifyName: ~p" "~n ContextName: ~p" "~n Recv: ~p" "~n Varbinds: ~p", [TrapRecord, NotifyName, ContextName, Recv, Varbinds]), ExtraInfo = ?DEFAULT_NOTIF_EXTRA_INFO, LocalEngineID = ?DEFAULT_LOCAL_ENGINE_ID, case (catch maybe_send_trap(S, TrapRecord, NotifyName, ContextName, Recv, Varbinds, LocalEngineID, ExtraInfo)) of {ok, NewS} -> {noreply, NewS}; {'EXIT', R} -> ?vinfo("Trap not sent:~n ~p", [R]), {noreply, S}; _ -> {noreply, S} end; %% </BACKWARD-COMPAT> handle_info({backup_done, Reply}, #state{backup = {_, From}} = S) -> ?vlog("[handle_info] backup done:" "~n Reply: ~p", [Reply]), gen_server:reply(From, Reply), {noreply, S#state{backup = undefined}}; handle_info(invalidate_ca_cache, S) -> invalidate_ca_cache(), {noreply, S}; %%----------------------------------------------------------------- %% If a process crashes, we first check to see if it was the mib, %% net-if or note-store. %% Otherwise, we check to see if it was a subagent. In this case %% we unregister the sa, and unlink us from the sa. %%----------------------------------------------------------------- handle_info({'EXIT', Pid, Reason}, #state{note_store = Pid} = S) -> ?vlog("note store (~p) exited for reason ~n~p", [Pid, Reason]), error_msg("note-store exited: ~n~p", [Reason]), {stop, {note_store_exit, Reason}, S#state{note_store = undefined}}; handle_info({'EXIT', Pid, Reason}, #state{worker = Pid} = S) -> ?vlog("worker (~p) exited -> create new ~n ~p", [Pid, Reason]), NewWorker = worker_start(), {noreply, S#state{worker = NewWorker}}; handle_info({'EXIT', Pid, Reason}, #state{set_worker = Pid} = S) -> ?vlog("set-worker (~p) exited -> create new ~n ~p", [Pid,Reason]), NewWorker = set_worker_start(), {noreply, S#state{set_worker = NewWorker}}; handle_info({'EXIT', Pid, Reason}, #state{parent = Pid} = S) -> ?vlog("parent (~p) exited for reason ~n~p", [Pid,Reason]), {stop, {parent_died, Reason}, S}; handle_info({'EXIT', Pid, Reason}, #state{backup = {Pid, From}} = S) -> ?vlog("backup server (~p) exited for reason ~n~p", [Pid, Reason]), case Reason of normal -> {noreply, S}; _ -> gen_server:reply(From, {error, Reason}), {noreply, S#state{backup = undefined}} end; handle_info({'EXIT', Pid, Reason}, S) -> ?vlog("~p exited for reason ~p", [Pid, Reason]), Mib = get(mibserver), NetIf = get(net_if), case Pid of Mib -> error_msg("mib-server exited: ~n~p", [Reason]), {stop, {mib_server_exit, Reason}, S}; NetIf -> error_msg("net-if exited: ~n~p", [Reason]), {stop, {net_if_exit, Reason}, S}; _ -> %% Could be a sub-agent SAs = snmpa_mib:info(Mib, subagents), case lists:keysearch(Pid, 1, SAs) of {value, _} -> ?vlog("subagent exit", []), snmpa_mib:unregister_subagent(Mib, Pid), unlink(Pid); _ -> %% Otherwise it was probably a worker thread - ignore ok end, {noreply, S} end; handle_info({'DOWN', Ref, process, Pid, {mibs_cache_reply, Reply}}, #state{mibs_cache_request = {Pid, Ref, From}} = S) -> ?vlog("reply from the mibs cache request handler (~p): ~n~p", [Pid, Reply]), gen_server:reply(From, Reply), {noreply, S#state{mibs_cache_request = undefined}}; handle_info(Info, S) -> warning_msg("received unexpected info: ~n~p", [Info]), {noreply, S}. handle_call(restart_worker, _From, #state{worker = Pid} = S) -> if is_pid(Pid) -> ?vlog("[handle_call] restart worker ~p", [Pid]), exit(Pid, kill); true -> ?vlog("[handle_call] not multi-threaded => " "ignoring restart request", []), ok end, {reply, ok, S}; handle_call(restart_set_worker, _From, #state{set_worker = Pid} = S) -> if is_pid(Pid) -> ?vlog("[handle_call] restart set worker: ~p", [Pid]), exit(Pid, kill); true -> ?vlog("[handle_call] not multi-threaded => " "ignoring restart request", []), ok end, {reply, ok, S}; handle_call({send_notif, Notification, SendOpts}, _From, S) -> ?vlog("[handle_call] send notif request:" "~n Notification: ~p" "~n SendOpts: ~p", [Notification, SendOpts]), case (catch handle_send_trap(S, Notification, SendOpts)) of {ok, NewS} -> {reply, ok, NewS}; {'EXIT', Reason} -> ?vinfo("Trap not sent:~n ~p", [Reason]), {reply, {error, {send_failed, Reason}}, S}; _ -> ?vinfo("Trap not sent", []), {reply, {error, send_failed}, S} end; %% <BACKWARD-COMPAT> handle_call({send_trap, Trap, NotifyName, ContextName, Recv, Varbinds}, _From, S) -> ?vlog("[handle_call] send trap request:" "~n Trap: ~p" "~n NotifyName: ~p" "~n ContextName: ~p" "~n Recv: ~p" "~n Varbinds: ~p", [Trap, NotifyName, ContextName, Recv, Varbinds]), ExtraInfo = ?DEFAULT_NOTIF_EXTRA_INFO, LocalEngineID = local_engine_id(S), case (catch handle_send_trap(S, Trap, NotifyName, ContextName, Recv, Varbinds, LocalEngineID, ExtraInfo)) of {ok, NewS} -> {reply, ok, NewS}; {'EXIT', Reason} -> ?vinfo("Trap not sent:~n ~p", [Reason]), {reply, {error, {send_failed, Reason}}, S}; _ -> ?vinfo("Trap not sent", []), {reply, {error, send_failed}, S} end; handle_call({send_trap, Trap, NotifyName, ContextName, Recv, Varbinds, LocalEngineID}, _From, S) -> ?vlog("[handle_call] send trap request:" "~n Trap: ~p" "~n NotifyName: ~p" "~n ContextName: ~p" "~n Recv: ~p" "~n Varbinds: ~p" "~n LocalEngineID: ~p", [Trap, NotifyName, ContextName, Recv, Varbinds, LocalEngineID]), ExtraInfo = ?DEFAULT_NOTIF_EXTRA_INFO, case (catch handle_send_trap(S, Trap, NotifyName, ContextName, Recv, Varbinds, LocalEngineID, ExtraInfo)) of {ok, NewS} -> {reply, ok, NewS}; {'EXIT', Reason} -> ?vinfo("Trap not sent:~n ~p", [Reason]), {reply, {error, {send_failed, Reason}}, S}; _ -> ?vinfo("Trap not sent", []), {reply, {error, send_failed}, S} end; %% </BACKWARD-COMPAT> handle_call({discovery, TargetName, Notification, ContextName, Vbs, DiscoHandler, ExtraInfo}, From, #state{disco = undefined} = S) -> ?vlog("[handle_call] initiate discovery process:" "~n TargetName: ~p" "~n Notification: ~p" "~n ContextName: ~p" "~n Vbs: ~p" "~n DiscoHandler: ~p" "~n ExtraInfo: ~p", [TargetName, Notification, ContextName, Vbs, DiscoHandler, ExtraInfo]), case handle_discovery(S, From, TargetName, Notification, ContextName, Vbs, DiscoHandler, ExtraInfo) of {ok, NewS} -> ?vtrace("[handle_call] first stage of discovery process initiated", []), {noreply, NewS}; {error, _} = Error -> {reply, Error, S} end; handle_call({discovery, _TargetName, _Notification, _ContextName, _Vbs, _DiscoHandler, _ExtraInfo}, _From, #state{disco = DiscoData} = S) -> Reply = {error, {discovery_in_progress, DiscoData}}, {reply, Reply, S}; handle_call({subagent_get, Varbinds, PduData, IsNotification}, _From, S) -> ?vlog("[handle_call] subagent get:" "~n Varbinds: ~p" "~n PduData: ~p", [Varbinds,PduData]), put_pdu_data(PduData), {reply, do_get(Varbinds, IsNotification), S}; handle_call({subagent_get_next, MibView, Varbinds, PduData}, _From, S) -> ?vlog("[handle_call] subagent get-next:" "~n MibView: ~p" "~n Varbinds: ~p" "~n PduData: ~p", [MibView,Varbinds,PduData]), put_pdu_data(PduData), {reply, do_get_next(MibView, Varbinds, infinity), S}; handle_call({subagent_set, Arguments, PduData}, _From, S) -> ?vlog("[handle_call] subagent set:" "~n Arguments: ~p" "~n PduData: ~p", [Arguments,PduData]), put_pdu_data(PduData), {reply, do_subagent_set(Arguments), S}; handle_call({get, Vars, Context}, _From, S) -> ?vlog("[handle_call] get:" "~n Vars: ~p" "~n Context: ~p", [Vars, Context]), put_pdu_data({undefined, undefined, undefined, undefined, Context}), case catch mapfoldl({?MODULE, tr_var}, [], 1, Vars) of {error, Reason} -> {reply, {error, Reason}, S}; {_, Varbinds} -> ?vdebug("Varbinds: ~p",[Varbinds]), Reply = case do_get(Varbinds, false) of {noError, 0, NewVarbinds} -> Vbs = lists:keysort(#varbind.org_index, NewVarbinds), [Value || #varbind{value = Value} <- Vbs]; {ErrorStatus, ErrIndex, _} -> N = lists:nth(ErrIndex, Vars), {error, {ErrorStatus, N}} end, {reply, Reply, S} end; handle_call({get_next, Vars, Context}, _From, S) -> ?vlog("[handle_call] get_next:" "~n Vars: ~p" "~n Context: ~p",[Vars, Context]), put_pdu_data({undefined, undefined, undefined, undefined, Context}), case catch mapfoldl({?MODULE, tr_var}, [], 1, Vars) of {error, Reason} -> {reply, {error, Reason}, S}; {_, Varbinds} -> ?vdebug("Varbinds: ~p",[Varbinds]), MibView = snmpa_acm:get_root_mib_view(), Reply = case do_get_next(MibView, Varbinds, infinity) of {noError, 0, NewVarbinds} -> Vbs = lists:keysort(#varbind.org_index, NewVarbinds), [{Oid,Val} || #varbind{oid = Oid, value = Val} <- Vbs]; {ErrorStatus, ErrIndex, _} -> N = lists:nth(ErrIndex, Vars), {error, {ErrorStatus, N}} end, {reply, Reply, S} end; handle_call({do_get, MibView, UnsortedVarbinds, IsNotification, PduData}, _From, S) -> ?vlog("[handle_call] do_get:" "~n MibView: ~p" "~n UnsortedVarbinds: ~p" "~n IsNotification: ~p" "~n PduData: ~p", [MibView, UnsortedVarbinds, IsNotification, PduData]), put_pdu_data(PduData), Reply = do_get(MibView, UnsortedVarbinds, IsNotification), {reply, Reply, S}; handle_call({register_subagent, SubTreeOid, SubagentPid}, _From, S) -> Reply = case snmpa_mib:register_subagent(get(mibserver), SubTreeOid, SubagentPid) of ok -> link(SubagentPid), ok; Error -> Error end, {reply, Reply, S}; handle_call({unregister_subagent, SubagentPid}, _From, S) when is_pid(SubagentPid) -> ?vlog("[handle_call] unregister subagent ~p", [SubagentPid]), Reply = snmpa_mib:unregister_subagent(get(mibserver), SubagentPid), unlink(SubagentPid), {reply, Reply, S}; handle_call({unregister_subagent, SubTreeOid}, _From, S) -> ?vlog("[handle_call] unregister subagent ~p", [SubTreeOid]), Reply = case snmpa_mib:unregister_subagent(get(mibserver), SubTreeOid) of {ok, DeletedSubagentPid} -> SAs = snmpa_mib:info(get(mibserver), subagents), case lists:keysearch(DeletedSubagentPid, 1, SAs) of {value, _} -> ok; _ -> unlink(DeletedSubagentPid) end, ok; Error -> Error end, {reply, Reply, S}; handle_call({load_mibs, Mibs}, _From, S) -> ?vlog("load mibs ~p", [Mibs]), {reply, snmpa_mib:load_mibs(get(mibserver), Mibs), S}; handle_call({unload_mibs, Mibs}, _From, S) -> ?vlog("unload mibs ~p", [Mibs]), {reply, snmpa_mib:unload_mibs(get(mibserver), Mibs), S}; handle_call(which_mibs, _From, S) -> ?vlog("which mibs", []), {reply, snmpa_mib:which_mibs(get(mibserver)), S}; handle_call({whereis_mib, Mib}, _From, S) -> ?vlog("whereis mib ~p", [Mib]), {reply, snmpa_mib:whereis_mib(get(mibserver), Mib), S}; handle_call({mibs_cache_request, MibsCacheReq}, From, S) -> ?vlog("mibs_cache_request: ~p", [MibsCacheReq]), {MibsCacheWorker, Ref} = handle_mibs_cache_request(get(mibserver), MibsCacheReq), NewS = S#state{mibs_cache_request = {MibsCacheWorker, Ref, From}}, {noreply, NewS}; handle_call(info, _From, S) -> ?vlog("info", []), Vsns = S#state.vsns, Stats = get_stats_counters(), AI = agent_info(S), NI = net_if_info(S), NS = note_store_info(S), SS = symbolic_store_info(), LD = local_db_info(), MS = mib_server_info(), Info = [{vsns, Vsns}, {stats_counters, Stats}, {agent, AI}, {net_if, NI}, {note_store, NS}, {symbolic_store, SS}, {local_db, LD}, {mib_server, MS}], {reply, Info, S}; handle_call(get_net_if, _From, S) -> {reply, get(net_if), S}; %% Only accept a backup request if there is none already in progress handle_call({backup, BackupDir}, From, #state{backup = undefined} = S) -> ?vlog("backup: ~p", [BackupDir]), Pid = self(), V = get(verbosity), MS = get(mibserver), BackupServer = erlang:spawn_link( fun() -> put(sname, abs), put(verbosity, V), Dir = filename:join([BackupDir]), Reply = handle_backup(Dir, MS), Pid ! {backup_done, Reply}, unlink(Pid) end), ?vtrace("backup server: ~p", [BackupServer]), {noreply, S#state{backup = {BackupServer, From}}}; handle_call({backup, _BackupDir}, From, #state{backup = Backup} = S) -> ?vinfo("backup already in progress: ~p", [Backup]), {reply, {error, backup_in_progress}, S}; handle_call(dump_mibs, _From, S) -> Reply = snmpa_mib:dump(get(mibserver)), {reply, Reply, S}; handle_call({dump_mibs,File}, _From, S) -> Reply = snmpa_mib:dump(get(mibserver),File), {reply, Reply, S}; handle_call({register_notification_filter, Id, Mod, Data, Where}, _From, #state{nfilters = NFs} = S) -> ?vlog("register_notification_filter -> " "~n Id: ~p" "~n Mod: ~p" "~n Where: ~p", [Id, Mod, Where]), case lists:keymember(Id, 2, NFs) of true -> {reply, {error, {already_registered, Id}}, S}; false -> NF = #notification_filter{id = Id, mod = Mod, data = Data}, {Reply, NewNFs} = add_notification_filter(Where, NF, NFs), {reply, Reply, S#state{nfilters = NewNFs}} end; handle_call({unregister_notification_filter, Id}, _From, #state{nfilters = NFs} = S) -> ?vlog("unregister_notification_filter -> " "~n Id: ~p", [Id]), case lists:keydelete(Id, 2, NFs) of NFs -> {reply, {error, {not_found, Id}}, S}; NFs2 -> {reply, ok, S#state{nfilters = NFs2}} end; handle_call(which_notification_filter, _From, #state{nfilters = NFs} = S) -> ?vlog("which_notification_filter", []), {reply, [Id || #notification_filter{id = Id} <- NFs], S}; handle_call({mib_of, Oid}, _From, S) -> Reply = handle_mib_of(get(mibserver), Oid), {reply, Reply, S}; handle_call({me_of, Oid}, _From, S) -> Reply = handle_me_of(get(mibserver), Oid), {reply, Reply, S}; handle_call(get_log_type, _From, S) -> ?vlog("handle_call(get_log_type) -> entry with", []), Reply = handle_get_log_type(S), {reply, Reply, S}; handle_call({set_log_type, NewType}, _From, S) -> ?vlog("handle_call(set_log_type) -> entry with" "~n NewType: ~p", [NewType]), Reply = handle_set_log_type(S, NewType), {reply, Reply, S}; handle_call(get_request_limit, _From, S) -> ?vlog("handle_call(get_request_limit) -> entry with", []), Reply = handle_get_request_limit(S), {reply, Reply, S}; handle_call({set_request_limit, NewLimit}, _From, S) -> ?vlog("handle_call(set_request_limit) -> entry with" "~n NewLimit: ~p", [NewLimit]), Reply = handle_set_request_limit(S, NewLimit), {reply, Reply, S}; handle_call(stop, _From, S) -> {stop, normal, ok, S}; handle_call(Req, _From, S) -> warning_msg("received unknown request: ~n~p", [Req]), Reply = {error, {unknown, Req}}, {reply, Reply, S}. handle_cast({verbosity, Verbosity}, S) -> ?vlog("verbosity: ~p -> ~p",[get(verbosity), Verbosity]), put(verbosity,snmp_verbosity:validate(Verbosity)), case S#state.worker of Pid when is_pid(Pid) -> Pid ! ?mk_verbosity_wreq(Verbosity); _ -> ok end, case S#state.set_worker of Pid2 when is_pid(Pid2) -> Pid2 ! ?mk_verbosity_wreq(Verbosity); _ -> ok end, {noreply, S}; handle_cast({sub_agents_verbosity,Verbosity}, S) -> ?vlog("sub_agents verbosity: ~p",[Verbosity]), subagents_verbosity(Verbosity), {noreply, S}; %% This should only happen if we are a master_agent handle_cast({net_if_verbosity, Verbosity}, S) -> net_if_verbosity(get(net_if), Verbosity), {noreply, S}; handle_cast({mib_server_verbosity, Verbosity}, S) -> mib_server_verbosity(get(mibserver),Verbosity), {noreply, S}; handle_cast({note_store_verbosity, Verbosity}, #state{note_store = Pid} = S) -> note_store_verbosity(Pid,Verbosity), {noreply, S}; handle_cast(Msg, S) -> warning_msg("received unknown message: ~n~p", [Msg]), {noreply, S}. terminate(shutdown, #state{worker = Worker, set_worker = SetWorker, backup = Backup, ref = Ref}) -> %% Ordered shutdown - stop misc-workers, net_if, mib-server and note-store. backup_server_stop(Backup), worker_stop(Worker, 100), worker_stop(SetWorker, 100), snmpa_misc_sup:stop_net_if(Ref), snmpa_misc_sup:stop_mib_server(Ref); terminate(_Reason, _S) -> %% We crashed! We will reuse net_if and mib if we get restarted. ok. handle_mibs_cache_request(MibServer, Req) -> {MibsCacheWorker, MibsCacheRef} = spawn_monitor( fun() -> Reply = case Req of invalidate_cache -> snmpa_mib:invalidate_cache(MibServer); gc_cache -> snmpa_mib:gc_cache(MibServer); {gc_cache, Age} -> snmpa_mib:gc_cache(MibServer, Age); {gc_cache, Age, GcLimit} -> snmpa_mib:gc_cache(MibServer, Age, GcLimit); cache_size -> snmpa_mib:which_cache_size(MibServer); enable_cache -> snmpa_mib:enable_cache(MibServer); disable_cache -> snmpa_mib:disable_cache(MibServer); enable_autogc -> snmpa_mib:enable_cache_autogc(MibServer); disable_autogc -> snmpa_mib:disable_cache_autogc(MibServer); {update_gclimit, GcLimit} -> snmpa_mib:update_cache_gclimit(MibServer, GcLimit); {update_age, Age} -> snmpa_mib:update_cache_age(MibServer, Age); _ -> {error, {unknown_mibs_cache_request, Req}} end, exit({mibs_cache_reply, Reply}) end), {MibsCacheWorker, MibsCacheRef}. %%----------------------------------------------------------------- %% Code replacement %% %%----------------------------------------------------------------- %% Downgrade %% code_change({down, _Vsn}, S1, downgrade_to_pre_4_17_3) -> #state{type = Type, parent = Parent, worker = Worker, worker_state = WorkerState, set_worker = SetWorker, multi_threaded = MT, ref = Ref, vsns = Vsns, nfilters = NF, note_store = NoteStore, mib_server = MS, net_if = NetIf, net_if_mod = NetIfMod, backup = Backup, disco = Disco, mibs_cache_request = MCR} = S1, S2 = {state, type = Type, parent = Parent, worker = Worker, worker_state = WorkerState, set_worker = SetWorker, multi_threaded = MT, ref = Ref, vsns = Vsns, nfilters = NF, note_store = NoteStore, mib_server = MS, net_if = NetIf, net_if_mod = NetIfMod, backup = Backup, disco = Disco, mibs_cache_request = MCR}, {ok, S2}; %% Upgrade %% code_change(_Vsn, S1, upgrade_from_pre_4_17_3) -> {state, type = Type, parent = Parent, worker = Worker, worker_state = WorkerState, set_worker = SetWorker, multi_threaded = MT, ref = Ref, vsns = Vsns, nfilters = NF, note_store = NoteStore, mib_server = MS, net_if = NetIf, net_if_mod = NetIfMod, backup = Backup, disco = Disco, mibs_cache_request = MCR} = S1, S2 = #state{type = Type, parent = Parent, worker = Worker, worker_state = WorkerState, set_worker = SetWorker, multi_threaded = MT, ref = Ref, vsns = Vsns, nfilters = NF, note_store = NoteStore, mib_server = MS, net_if = NetIf, net_if_mod = NetIfMod, backup = Backup, disco = Disco, mibs_cache_request = MCR, gb_max_vbs = ?DEFAULT_GB_MAX_VBS}, {ok, S2}; code_change(_Vsn, S, _Extra) -> {ok, S}. %% workers_restart(#state{worker = W, set_worker = SW} = S) -> %% Worker = worker_restart(W), %% SetWorker = set_worker_restart(SW), %% S#state{worker = Worker, %% set_worker = SetWorker}. %%----------------------------------------------------------------- backup_server_stop({Pid, _}) when is_pid(Pid) -> exit(Pid, kill); backup_server_stop(_) -> ok. workers_start(true) -> ?vdebug("start worker and set-worker",[]), {worker_start(), set_worker_start()}; workers_start(_) -> {undefined, undefined}. worker_start() -> worker_start(get()). set_worker_start() -> worker_start([{master, self()} | get()]). worker_start(Dict) -> proc_lib:spawn_link(?MODULE, worker, [self(), Dict]). %% worker_stop(Pid) -> %% worker_stop(Pid, infinity). worker_stop(Pid, Timeout) when is_pid(Pid) -> Pid ! ?mk_terminate_wreq(), receive {'EXIT', Pid, normal} -> ok after Timeout -> (catch exit(Pid, kill)), ok end; worker_stop(_, _) -> ok. %% set_worker_restart(Pid) -> %% worker_restart(Pid, [{master, self()} | get()]). %% worker_restart(Pid) -> %% worker_restart(Pid, get()). %% worker_restart(Pid, Dict) when is_pid(Pid) -> %% worker_stop(Pid), %% worker_start(Dict); %% worker_restart(Any, _Dict) -> %% Any. %%----------------------------------------------------------------- handle_backup(BackupDir, MibServer) -> ?vlog("handle_backup -> entry with" "~n BackupDir: ~p", [BackupDir]), case ets:lookup(snmp_agent_table, db_dir) of [{db_dir, BackupDir}] -> ?vinfo("handle_backup -> backup dir and db dir the same", []), {error, db_dir}; _ -> case file:read_file_info(BackupDir) of {ok, #file_info{type = directory}} -> ?vdebug("handle_backup -> backup dir ok", []), VacmRes = (catch snmpa_vacm:backup(BackupDir)), ?vtrace("handle_backup -> " "~n VacmRes: ~p", [VacmRes]), LdbRes = (catch snmpa_local_db:backup(BackupDir)), ?vtrace("handle_backup -> " "~n LdbRes: ~p", [LdbRes]), MsRes = (catch snmpa_mib:backup(MibServer, BackupDir)), ?vtrace("handle_backup -> " "~n MsRes: ~p", [MsRes]), SsRes = (catch snmpa_symbolic_store:backup(BackupDir)), ?vtrace("handle_backup -> " "~n SsRes: ~p", [SsRes]), handle_backup_res([{vacm, VacmRes}, {local_db, LdbRes}, {mib_server, MsRes}, {symbolic_store, SsRes}]); {ok, _} -> ?vinfo("handle_backup -> backup dir not a dir", []), {error, not_a_directory}; Error -> ?vinfo("handle_backup -> Error: ~p", [Error]), Error end end. handle_backup_res(Results) -> handle_backup_res(Results, []). handle_backup_res([], []) -> ok; handle_backup_res([], Acc) -> {error, lists:reverse(Acc)}; handle_backup_res([{_, ok}|Results], Acc) -> handle_backup_res(Results, Acc); handle_backup_res([{Who, {error, Reason}}|Results], Acc) -> handle_backup_res(Results, [{Who, Reason}|Acc]); handle_backup_res([{Who, Crap}|Results], Acc) -> handle_backup_res(Results, [{Who, Crap}|Acc]). %%----------------------------------------------------------------- %% We must cheat to get the community string out of the ACM data, %% because we (for some reason) support the function %% snmpa:current_community(). %%----------------------------------------------------------------- cheat({community, _SecModel, Community, _TAddress}, Address, ContextName) -> {Community, Address, ContextName}; cheat({community, _SecModel, Community, _TDomain, _TAddress}, Address, ContextName) -> {Community, Address, ContextName}; cheat(_, Address, ContextName) -> {"", Address, ContextName}. %% This function will either be in the context of the: %% 1) master-agent %% 2) set-worker %% 3) user code %% ( calling e.g. snmp_community_mib:snmpCommunityTable(set, ...) ) invalidate_ca_cache() -> case get(master) of undefined -> % 1 or 3 case get(auth_module) of undefined -> % 3 %% Ouch, we are not running in the context of the %% master agent either. Check if we are on the %% master-agent node. If so, sent it there, case whereis(snmp_master_agent) of MasterAgent when is_pid(MasterAgent) -> case node(MasterAgent) =:= node() of true -> %% Ok, we are on the node running the %% master_agent process, so sent it there MasterAgent ! invalidate_ca_cache; false -> %% This is running on a sub-agent node, %% so skip it ok end; _ -> % Not on this node ok end; AuthMod -> % 1 AuthMod:invalidate_ca_cache() end; Pid -> % 2 Pid ! invalidate_ca_cache end. %%----------------------------------------------------------------- %% Threads and workers %% %%----------------------------------------------------------------- %% This functions spawns a temporary worker process, %% that evaluates one request and then silently exits. spawn_thread(Vsn, Pdu, PduMS, ACMData, Address, GbMaxVBs, Extra) -> Dict = get(), Args = [Vsn, Pdu, PduMS, ACMData, Address, GbMaxVBs, Extra, Dict], proc_lib:spawn_link(?MODULE, handle_pdu, Args). spawn_trap_thread(TrapRec, NotifyName, ContextName, Recv, Vbs, LocalEngineID, ExtraInfo) -> Dict = get(), proc_lib:spawn_link(?MODULE, do_send_trap, [TrapRec, NotifyName, ContextName, Recv, Vbs, LocalEngineID, ExtraInfo, Dict]). do_send_trap(TrapRec, NotifyName, ContextName, Recv, Vbs, LocalEngineID, Dict) -> ExtraInfo = ?DEFAULT_NOTIF_EXTRA_INFO, do_send_trap(TrapRec, NotifyName, ContextName, Recv, Vbs, LocalEngineID, ExtraInfo, Dict). do_send_trap(TrapRec, NotifyName, ContextName, Recv, Vbs, LocalEngineID, ExtraInfo, Dict) -> lists:foreach(fun({Key, Val}) -> put(Key, Val) end, Dict), put(sname, trap_sender_short_name(get(sname))), ?vlog("starting",[]), snmpa_trap:send_trap(TrapRec, NotifyName, ContextName, Recv, Vbs, LocalEngineID, ExtraInfo, get(net_if)). worker(Master, Dict) -> lists:foreach(fun({Key, Val}) -> put(Key, Val) end, Dict), put(sname, worker_short_name(get(sname))), ?vlog("starting",[]), worker_loop(Master). worker_loop(Master) -> Res = receive #wrequest{cmd = handle_pdu, info = Info} = Req -> ?vtrace("worker_loop -> received handle_pdu request with" "~n Info: ~p", [Info]), Vsn = proplists:get_value(vsn, Info), Pdu = proplists:get_value(pdu, Info), PduMS = proplists:get_value(pdu_ms, Info), ACMData = proplists:get_value(acm_data, Info), Address = proplists:get_value(addr, Info), GbMaxVBs = proplists:get_value(gb_max_vbs, Info), Extra = proplists:get_value(extra, Info), HandlePduRes = try begin handle_pdu2(Vsn, Pdu, PduMS, ACMData, Address, GbMaxVBs, Extra) end catch T:E -> exit({worker_crash, Req, T, E, erlang:get_stacktrace()}) end, Master ! worker_available, HandlePduRes; % For debugging... #wrequest{cmd = send_trap, info = Info} = Req -> ?vtrace("worker_loop -> received send_trap request with" "~n Info: ~p", [Info]), TrapRec = proplists:get_value(trap_rec, Info), NotifyName = proplists:get_value(notify_name, Info), ContextName = proplists:get_value(context_name, Info), Recv = proplists:get_value(receiver, Info), Vbs = proplists:get_value(varbinds, Info), LocalEngineID = proplists:get_value(local_engine_id, Info), Extra = proplists:get_value(extra, Info), SendTrapRes = try begin snmpa_trap:send_trap(TrapRec, NotifyName, ContextName, Recv, Vbs, LocalEngineID, Extra, get(net_if)) end catch T:E -> exit({worker_crash, Req, T, E, erlang:get_stacktrace()}) end, Master ! worker_available, SendTrapRes; % For debugging... #wrequest{cmd = verbosity, info = Info} -> Verbosity = proplists:get_value(verbosity, Info), put(verbosity, snmp_verbosity:validate(Verbosity)); #wrequest{cmd = terminate} -> ?vtrace("worker_loop -> received terminate request", []), exit(normal); %% ************************************************************* %% %% Kept for backward compatibillity reasons %% %% ************************************************************* {Vsn, Pdu, PduMS, ACMData, Address, Extra} -> ?vtrace("worker_loop -> received request", []), handle_pdu2(Vsn, Pdu, PduMS, ACMData, Address, ?DEFAULT_GB_MAX_VBS, Extra), Master ! worker_available; %% We don't trap exits! {TrapRec, NotifyName, ContextName, Recv, Vbs} -> ?vtrace("worker_loop -> send trap:" "~n ~p", [TrapRec]), snmpa_trap:send_trap(TrapRec, NotifyName, ContextName, Recv, Vbs, get(net_if)), Master ! worker_available; %% We don't trap exits! {send_trap, TrapRec, NotifyName, ContextName, Recv, Vbs, LocalEngineID, ExtraInfo} -> ?vtrace("worker_loop -> send trap:" "~n ~p", [TrapRec]), snmpa_trap:send_trap(TrapRec, NotifyName, ContextName, Recv, Vbs, LocalEngineID, ExtraInfo, get(net_if)), Master ! worker_available; {verbosity, Verbosity} -> put(verbosity, snmp_verbosity:validate(Verbosity)); terminate -> exit(normal); _X -> %% ignore ignore_unknown after 30000 -> %% This is to assure that the worker process leaves a %% possibly old version of this module. ok end, ?vtrace("worker_loop -> wrap with" "~n ~p", [Res]), ?MODULE:worker_loop(Master). %%----------------------------------------------------------------- %%----------------------------------------------------------------- handle_snmp_pdu(true, Vsn, Pdu, PduMS, ACMData, Address, Extra, #state{multi_threaded = false, gb_max_vbs = GbMaxVBs} = S) -> ?vtrace("handle_snmp_pdu -> single-thread agent",[]), handle_pdu2(Vsn, Pdu, PduMS, ACMData, Address, GbMaxVBs, Extra), S; handle_snmp_pdu(true, Vsn, #pdu{type = 'set-request'} = Pdu, PduMS, ACMData, Address, Extra, #state{set_worker = Worker} = S) -> ?vtrace("handle_snmp_pdu -> multi-thread agent: " "send set-request to main worker",[]), WRequest = ?mk_pdu_wreq(Vsn, Pdu, PduMS, ACMData, Address, infinity, Extra), Worker ! WRequest, S#state{worker_state = busy}; handle_snmp_pdu(true, Vsn, Pdu, PduMS, ACMData, Address, Extra, #state{worker_state = busy, gb_max_vbs = GbMaxVBs} = S) -> ?vtrace("handle_snmp_pdu -> multi-thread agent: " "main worker busy - create new worker",[]), spawn_thread(Vsn, Pdu, PduMS, ACMData, Address, GbMaxVBs, Extra), S; handle_snmp_pdu(true, Vsn, Pdu, PduMS, ACMData, Address, Extra, #state{worker = Worker, gb_max_vbs = GbMaxVBs} = S) -> ?vtrace("handle_snmp_pdu -> multi-thread agent: " "send to main worker",[]), WRequest = ?mk_pdu_wreq(Vsn, Pdu, PduMS, ACMData, Address, GbMaxVBs, Extra), Worker ! WRequest, S#state{worker_state = busy}; handle_snmp_pdu(_, _Vsn, _Pdu, _PduMS, _ACMData, _Address, _Extra, S) -> S. %% Called via the spawn_thread function %% <BACKWARD-COMPAT> handle_pdu(Vsn, Pdu, PduMS, ACMData, Address, Extra, Dict) -> handle_pdu(Vsn, Pdu, PduMS, ACMData, Address, ?DEFAULT_GB_MAX_VBS, Extra, Dict). %% </BACKWARD-COMPAT> handle_pdu(Vsn, Pdu, PduMS, ACMData, Address, GbMaxVBs, Extra, Dict) -> lists:foreach(fun({Key, Val}) -> put(Key, Val) end, Dict), put(sname, pdu_handler_short_name(get(sname))), ?vlog("new worker starting",[]), handle_pdu2(Vsn, Pdu, PduMS, ACMData, Address, GbMaxVBs, Extra). handle_pdu2(Vsn, Pdu, PduMS, ACMData, Address, GbMaxVBs, Extra) -> %% OTP-3324 AuthMod = get(auth_module), case AuthMod:init_check_access(Pdu, ACMData) of {ok, MibView, ContextName} -> ?vlog("handle_pdu -> ok:" "~n MibView: ~p" "~n ContextName: ~p", [MibView, ContextName]), AgentData = cheat(ACMData, Address, ContextName), do_handle_pdu(MibView, Vsn, Pdu, PduMS, ACMData, AgentData, GbMaxVBs, Extra); {error, Reason} -> ?vlog("handle_pdu -> error:" "~n Reason: ~p", [Reason]), handle_acm_error(Vsn, Reason, Pdu, ACMData, Address, Extra); {discarded, Variable, Reason} -> ?vlog("handle_pdu -> discarded:" "~n Variable: ~p" "~n Reason: ~p", [Variable, Reason]), get(net_if) ! {discarded_pdu, Vsn, Pdu#pdu.request_id, ACMData, Variable, Extra} end. do_handle_pdu(MibView, Vsn, Pdu, PduMS, ACMData, {Community, Address, ContextName}, GbMaxVBs, Extra) -> put(net_if_data, Extra), RePdu = process_msg(MibView, Vsn, Pdu, PduMS, Community, Address, ContextName, GbMaxVBs), ?vtrace("do_handle_pdu -> processed:" "~n RePdu: ~p", [RePdu]), NetIf = get(net_if), NetIf ! {snmp_response, Vsn, RePdu, RePdu#pdu.type, ACMData, Address, Extra}. handle_acm_error(Vsn, Reason, Pdu, ACMData, Address, Extra) -> #pdu{type = Type, request_id = ReqId, varbinds = Vbs} = Pdu, RawErrStatus = snmpa_acm:error2status(Reason), case is_valid_pdu_type(Type) of true -> %% RawErrStatus can be authorizationError or genErr. If it is %% authorizationError, we'll have to do different things, %% depending on which SNMP version is used. %% v1 - noSuchName error %% v2 - GET: all variables 'noSuchObject' %% NEXT/BULK: all variables 'endOfMibView' %% SET: noAccess error %% v3 - authorizationError error %% %% NOTE: this procedure is not yet defined in the coex document! ?vdebug("~n Raw error status: ~w",[RawErrStatus]), Idx = case Vbs of [] -> 0; _ -> 1 end, RePdu = if Vsn =:= 'version-1' -> ErrStatus = v2err_to_v1err(RawErrStatus), make_response_pdu(ReqId, ErrStatus, Idx, Vbs, Vbs); Vsn =:= 'version-3' -> make_response_pdu(ReqId, RawErrStatus, Idx, Vbs, Vbs); Type =:= 'get-request' -> % this is v2 ReVbs = lists:map( fun(Vb) -> Vb#varbind{value=noSuchObject} end, Vbs), make_response_pdu(ReqId, noError, 0, Vbs, ReVbs); Type =:= 'set-request' -> make_response_pdu(ReqId, noAccess, Idx, Vbs, Vbs); true -> % next or bulk ReVbs = lists:map( fun(Vb) -> Vb#varbind{value=endOfMibView} end, Vbs), make_response_pdu(ReqId, noError, 0, Vbs, ReVbs) end, get(net_if) ! {snmp_response, Vsn, RePdu, 'get-response', ACMData, Address, Extra}; false -> ?vdebug("~n Raw error status: ~w" "~n invalid pdu type: ~w", [RawErrStatus,Type]), ok end. get_send_opt(Key, Default, SendOpts) -> case lists:keysearch(Key, 1, SendOpts) of {value, {Key, Value}} -> Value; false -> Default end. handle_send_trap(S, Notification, SendOpts) -> NotifyName = get_send_opt(name, "", SendOpts), ContextName = get_send_opt(context, "", SendOpts), Recv = get_send_opt(receiver, no_receiver, SendOpts), Varbinds = get_send_opt(varbinds, [], SendOpts), ExtraInfo = get_send_opt(extra, ?DEFAULT_NOTIF_EXTRA_INFO, SendOpts), LocalEngineID = case lists:keysearch(local_engine_id, 1, SendOpts) of {value, {local_engine_id, Value}} -> Value; false -> local_engine_id(S) end, handle_send_trap(S, Notification, NotifyName, ContextName, Recv, Varbinds, LocalEngineID, ExtraInfo). handle_send_trap(#state{type = Type} = S, Notification, NotifyName, ContextName, Recv, Varbinds, LocalEngineID, ExtraInfo) -> ?vtrace("handle_send_trap -> entry with" "~n Agent type: ~p" "~n TrapName: ~p" "~n NotifyName: ~p" "~n ContextName: ~p" "~n LocalEngineID: ~p", [Type, Notification, NotifyName, ContextName, LocalEngineID]), case snmpa_trap:construct_trap(Notification, Varbinds) of {ok, TrapRecord, VarList} -> ?vtrace("handle_send_trap -> construction complete: " "~n TrapRecord: ~p" "~n VarList: ~p", [TrapRecord, VarList]), case Type of subagent -> ?vtrace("handle_send_trap -> [sub] forward trap",[]), maybe_forward_trap(S, TrapRecord, NotifyName, ContextName, Recv, VarList, ExtraInfo), {ok, S}; master_agent -> ?vtrace("handle_send_trap -> " "[master] handle send trap",[]), maybe_send_trap(S, TrapRecord, NotifyName, ContextName, Recv, VarList, LocalEngineID, ExtraInfo) end; error -> error end. maybe_forward_trap(#state{parent = Parent, nfilters = NFs} = S, TrapRec, NotifyName, ContextName, Recv, V, ExtraInfo) -> ?vtrace("maybe_forward_trap -> entry with" "~n NFs: ~p", [NFs]), case filter_notification(NFs, [], TrapRec) of {dont_send, [], Id} -> ?vdebug("trap not forwarded [filter ~p]", [Id]), {ok, S}; {dont_send, Removed, Id} -> ?vdebug("trap not forwarded [filter ~p]", [Id]), NFs2 = del_notification_filter(Removed, NFs), {ok, S#state{nfilters = NFs2}}; {send, [], TrapRec2} -> ?vtrace("maybe_forward_trap -> forward trap:" "~n ~p", [TrapRec2]), forward_trap(Parent, TrapRec2, NotifyName, ContextName, Recv, V, ExtraInfo), {ok, S}; {send, Removed, TrapRec2} -> ?vtrace("maybe_forward_trap -> forward trap:" "~n ~p", [TrapRec2]), forward_trap(Parent, TrapRec2, NotifyName, ContextName, Recv, V, ExtraInfo), NFs2 = del_notification_filter(Removed, NFs), {ok, S#state{nfilters = NFs2}} end. maybe_send_trap(#state{nfilters = NFs} = S, TrapRec, NotifyName, ContextName, Recv, Varbinds, LocalEngineID, ExtraInfo) -> ?vtrace("maybe_send_trap -> entry with" "~n NFs: ~p", [NFs]), case filter_notification(NFs, [], TrapRec) of {dont_send, [], Id} -> ?vdebug("trap not sent [filter ~p]",[Id]), {ok, S}; {dont_send, Removed, Id} -> ?vdebug("trap not sent [filter ~p]",[Id]), NFs2 = del_notification_filter(Removed, NFs), {ok, S#state{nfilters = NFs2}}; {send, [], TrapRec2} -> ?vtrace("maybe_send_trap -> send trap:" "~n ~p", [TrapRec2]), do_handle_send_trap(S, TrapRec2, NotifyName, ContextName, Recv, Varbinds, LocalEngineID, ExtraInfo); {send, Removed, TrapRec2} -> ?vtrace("maybe_send_trap -> send trap:" "~n ~p", [TrapRec2]), NFs2 = del_notification_filter(Removed, NFs), do_handle_send_trap(S#state{nfilters = NFs2}, TrapRec2, NotifyName, ContextName, Recv, Varbinds, LocalEngineID, ExtraInfo) end. do_handle_send_trap(S, TrapRec, NotifyName, ContextName, Recv, Varbinds, LocalEngineID, ExtraInfo) -> Vbs = snmpa_trap:try_initialise_vars(get(mibserver), Varbinds), case S#state.type of subagent -> forward_trap(S#state.parent, TrapRec, NotifyName, ContextName, Recv, Vbs, ExtraInfo), {ok, S}; master_agent when S#state.multi_threaded =:= false -> ?vtrace("do_handle_send_trap -> send trap:" "~n ~p", [TrapRec]), snmpa_trap:send_trap(TrapRec, NotifyName, ContextName, Recv, Vbs, LocalEngineID, ExtraInfo, get(net_if)), {ok, S}; master_agent when S#state.worker_state =:= busy -> %% Main worker busy => create new worker ?vtrace("do_handle_send_trap -> main worker busy: " "spawn a trap sender", []), spawn_trap_thread(TrapRec, NotifyName, ContextName, Recv, Vbs, LocalEngineID, ExtraInfo), {ok, S}; master_agent -> %% Send to main worker ?vtrace("do_handle_send_trap -> send to main worker",[]), S#state.worker ! ?mk_send_trap_wreq(TrapRec, NotifyName, ContextName, Recv, Vbs, LocalEngineID, ExtraInfo), {ok, S#state{worker_state = busy}} end. filter_notification([], RemoveNFs, Notif) -> ?vtrace("filter_notification -> done when" "~n RemoveNFs: ~p" "~n Notif: ~p", [RemoveNFs, Notif]), {send, RemoveNFs, Notif}; filter_notification([NF|NFs], RemoveNFs, Notif0) -> ?vtrace("filter_notification -> entry with" "~n NF: ~p" "~n RemoveNFs: ~p" "~n Notif0: ~p", [NF, RemoveNFs, Notif0]), case do_filter_notification(NF, Notif0) of {dont_send, Id} -> {dont_send, RemoveNFs, Id}; {send, Notif} -> filter_notification(NFs, RemoveNFs, Notif); {error, Id} -> filter_notification(NFs, [Id|RemoveNFs], Notif0) end. do_filter_notification(#notification_filter{id = Id, mod = Mod, data = Data}, Notif) -> case (catch Mod:handle_notification(Notif, Data)) of dont_send -> {dont_send, Id}; send -> {send, Notif}; {send, NewNotif} -> {send, NewNotif}; Else -> user_err("notification filter ~p removed: ~n~p", [Id, Else]), {error, Id} end. add_notification_filter(first, NewNF, NFs) -> {ok, [NewNF | NFs]}; add_notification_filter(last, NewNF, NFs) -> {ok, lists:append(NFs, [NewNF])}; add_notification_filter(Where, NewNF, NFs) -> case add_nf(Where, NewNF, NFs, []) of {ok, NFs2} -> {ok, NFs2}; Error -> {Error, NFs} end. add_nf({_Loc, Id}, _NewNF, [], _Acc) -> {error, {not_found, Id}}; add_nf({insert_before, Id}, NewNF, [NF|NFs], Acc) when Id =:= NF#notification_filter.id -> {ok, lists:reverse(Acc) ++ [NewNF,NF|NFs]}; add_nf({insert_after, Id}, NewNF, [NF|NFs], Acc) when Id =:= NF#notification_filter.id -> {ok, lists:reverse(Acc) ++ [NF,NewNF|NFs]}; add_nf(Where, NewNF, [NF|NFs], Acc) -> add_nf(Where, NewNF, NFs, [NF|Acc]). del_notification_filter(IDs, NFs) -> Fun = fun(Id, NFilters) -> lists:keydelete(Id, 2, NFilters) end, lists:foldl(Fun, NFs, IDs). handle_discovery(#state{type = master_agent} = S, From, TargetName, Notification, ContextName, Varbinds, DiscoHandler, ExtraInfo) -> ?vtrace("handle_discovery -> entry with" "~n TargetName: ~p" "~n Notification: ~p" "~n ContextName: ~p" "~n Varbinds: ~p", [TargetName, Notification, ContextName, Varbinds]), case snmpa_trap:construct_trap(Notification, Varbinds) of {ok, Record, InitVars} -> ?vtrace("handle_discovery -> trap construction complete: " "~n Record: ~p" "~n InitVars: ~p", [Record, InitVars]), send_discovery(S, From, TargetName, Record, ContextName, InitVars, DiscoHandler, ExtraInfo); error -> {error, failed_constructing_notification} end; handle_discovery(_S, _From, _TargetName, _Notification, _ContextName, _Varbinds, _DiscoHandler, _ExtraInfo) -> {error, only_master_discovery}. %% We ignore if the master agent is multi-threaded or not. %% send_discovery(S, From, TargetName, Record, ContextName, InitVars, DiscoHandler, ExtraInfo) -> case snmpa_trap:send_discovery(TargetName, Record, ContextName, InitVars, get(net_if), ExtraInfo) of {ok, Sender, SecLevel} -> Disco = #disco{from = From, rec = Record, sender = Sender, target = TargetName, sec_level = SecLevel, ctx = ContextName, ivbs = InitVars, stage = 1, handler = DiscoHandler, extra = ExtraInfo}, {ok, S#state{disco = Disco}}; Error -> ?vlog("send_discovery -> failed sending discovery: " "~n Error: ~p", [Error]), Error end. handle_discovery_response(#state{disco = #disco{from = From}} = S, {error, _} = Error) -> ?vlog("handle_discovery_response -> entry with" "~n From: ~p" "~n Error: ~p", [From, Error]), gen_server:reply(From, Error), S#state{disco = undefined}; handle_discovery_response(#state{disco = #disco{target = TargetName, stage = 1, extra = ExtraInfo} = Disco} = S, {ok, _Pdu, ManagerEngineId}) when is_record(Disco, disco) -> ?vlog("handle_discovery_response(1) -> entry with" "~n TargetName: ~p" "~n ManagerEngineId: ~p", [TargetName, ManagerEngineId]), %% This is end of stage 1. %% So, first we need to update the database with the EngineId of the %% manager and then deside if we should continue with stage 2. E.g. %% establish authenticated communication. case snmp_target_mib:set_target_engine_id(TargetName, ManagerEngineId) of true when Disco#disco.sec_level =:= ?'SnmpSecurityLevel_noAuthNoPriv' -> %% Ok, we are done From = Disco#disco.from, Handler = Disco#disco.handler, Reply = case handle_discovery_stage1_finish(Handler, TargetName, ManagerEngineId, ExtraInfo) of {ok, _} -> {ok, ManagerEngineId}; Error -> Error end, gen_server:reply(From, Reply), S#state{disco = undefined}; true when Disco#disco.sec_level =/= ?'SnmpSecurityLevel_noAuthNoPriv' -> %% Ok, time for stage 2 %% Send the same inform again, %% this time we have the proper EngineId From = Disco#disco.from, Handler = Disco#disco.handler, case handle_discovery_stage1_finish(Handler, TargetName, ManagerEngineId, ExtraInfo) of {ok, NewExtraInfo} -> ?vdebug("handle_discovery_response(1) -> " "we are done with stage 1 - " "continue with stage 2", []), #disco{rec = Record, ctx = ContextName, ivbs = InitVars} = Disco, case snmpa_trap:send_discovery(TargetName, Record, ContextName, InitVars, get(net_if), ExtraInfo) of {ok, Sender, _SecLevel} -> ?vdebug("handle_discovery_response(1) -> " "stage 2 trap sent", []), Disco2 = Disco#disco{sender = Sender, engine_id = ManagerEngineId, stage = 2, extra = NewExtraInfo}, S#state{disco = Disco2}; Error -> ?vlog("handle_discovery_response(1) -> " "failed sending stage 2 trap: " "~n ~p", [Error]), error_msg("failed sending second " "discovery message: " "~n ~p", [Error]), Reply = {error, {second_send_failed, Error}}, gen_server:reply(From, Reply), S#state{disco = undefined} end; {error, Reason} = Error -> ?vlog("handle_discovery_response(1) -> " "stage 1 finish failed: " "~n ~p", [Reason]), gen_server:reply(From, Error), S#state{disco = undefined} end; false -> ?vinfo("handle_discovery_response(1) -> " "failed setting doscovered engine-id - " "inform the user", []), From = Disco#disco.from, Reason = {failed_setting_engine_id, TargetName, ManagerEngineId}, gen_server:reply(From, {error, Reason}), S#state{disco = undefined} end; handle_discovery_response(#state{disco = #disco{from = From, engine_id = EngineID, stage = 2}} = S, {ok, _Pdu}) -> ?vlog("handle_discovery_response(2) -> entry with" "~n From: ~p", [From]), gen_server:reply(From, {ok, EngineID}), S#state{disco = undefined}; handle_discovery_response(#state{disco = #disco{from = From}} = S, Crap) -> Reason = {invalid_response, Crap}, gen_server:reply(From, {error, Reason}), S#state{disco = undefined}; handle_discovery_response(S, Crap) -> warning_msg("Received unexpected discovery response: ~p", [Crap]), S. handle_discovery_stage1_finish(Handler, TargetName, ManagerEngineID, ExtraInfo) -> case (catch Handler:stage1_finish(TargetName, ManagerEngineID, ExtraInfo)) of ignore -> ?vtrace("handle_discovery_stage1_finish -> " "we are done - [ignore] inform the user", []), {ok, ExtraInfo}; {ok, UsmEntry} when is_tuple(UsmEntry) -> ?vtrace("handle_discovery_stage1_finish -> " "received usm entry - attempt to add it", []), case add_usm_users([UsmEntry]) of ok -> {ok, ExtraInfo}; Error -> Error end; {ok, UsmEntry, NewExtraInfo} when is_tuple(UsmEntry) -> ?vtrace("handle_discovery_stage1_finish -> " "received usm entry - attempt to add it", []), case add_usm_users([UsmEntry]) of ok -> {ok, NewExtraInfo}; Error -> Error end; {ok, UsmEntries} when is_list(UsmEntries) -> ?vtrace("handle_discovery_stage1_finish -> " "received usm entries - attempt to add them", []), case add_usm_users(UsmEntries) of ok -> {ok, ExtraInfo}; Error -> Error end; {ok, UsmEntries, NewExtraInfo} when is_list(UsmEntries) -> ?vtrace("handle_discovery_stage1_finish -> " "received usm entries - attempt to add them", []), case add_usm_users(UsmEntries) of ok -> {ok, NewExtraInfo}; Error -> Error end; {'EXIT', Reason} -> ?vlog("handle_discovery_stage1_finish -> stage 1 function exited: " "~n ~p", [Reason]), {error, {finish_exit, Reason, ManagerEngineID}}; {error, Reason} -> ?vlog("handle_discovery_stage1_finish -> stage 1 function error: " "~n ~p", [Reason]), {error, {finish_error, Reason, ManagerEngineID}}; Unknown -> ?vlog("handle_discovery_stage1_finish -> stage 1 function unknown: " "~n ~p", [Unknown]), {error, {finish_failed, Unknown, ManagerEngineID}} end. add_usm_users([]) -> ok; add_usm_users([UsmEntry|UsmEntries]) when is_tuple(UsmEntry) -> ?vtrace("add_usm_users -> attempt to add entry (~w)", [element(1, UsmEntry)]), case snmp_user_based_sm_mib:add_user(UsmEntry) of {ok, _} -> add_usm_users(UsmEntries); {error, Reason} -> ?vlog("add_usm_users -> failed adding usm entry: " "~n ~p", [Reason]), {error, {failed_adding_entry, Reason, UsmEntry}} end. handle_me_of(MibServer, Oid) -> case snmpa_mib:lookup(MibServer, Oid) of {variable, ME} -> {ok, ME}; {table_column, ME, _TableEntryOid} -> {ok, ME}; {subagent, SubAgentPid, _SANextOid} -> {error, {subagent, SubAgentPid}}; {false, Reason} -> {error, Reason}; Else -> {error, Else} end. handle_mib_of(MibServer, Oid) -> snmpa_mib:which_mib(MibServer, Oid). %%----------------------------------------------------------------- %% Func: process_msg/7 %% Returns: RePdu %%----------------------------------------------------------------- process_msg(MibView, Vsn, Pdu, PduMS, Community, {Ip, Udp}, ContextName, GbMaxVBs) -> #pdu{request_id = ReqId} = Pdu, put(snmp_address, {tuple_to_list(Ip), Udp}), put(snmp_request_id, ReqId), put(snmp_community, Community), put(snmp_context, ContextName), ?vtrace("process ~p",[Pdu#pdu.type]), process_pdu(Pdu, PduMS, Vsn, MibView, GbMaxVBs). process_pdu(#pdu{type='get-request', request_id = ReqId, varbinds=Vbs}, _PduMS, Vsn, MibView, _GbMaxVBs) -> ?vtrace("get ~p",[ReqId]), Res = get_err(do_get(MibView, Vbs, false)), ?vtrace("get result: " "~n ~p",[Res]), {ErrStatus, ErrIndex, ResVarbinds} = if Vsn =:= 'version-1' -> validate_get_v1(Res); true -> Res end, ?vtrace("get final result: " "~n Error status: ~p" "~n Error index: ~p" "~n Varbinds: ~p", [ErrStatus,ErrIndex,ResVarbinds]), ResponseVarbinds = lists:keysort(#varbind.org_index, ResVarbinds), ?vtrace("response varbinds: " "~n ~p",[ResponseVarbinds]), make_response_pdu(ReqId, ErrStatus, ErrIndex, Vbs, ResponseVarbinds); process_pdu(#pdu{type = 'get-next-request', request_id = ReqId, varbinds = Vbs}, _PduMS, Vsn, MibView, _GbMaxVBs) -> ?vtrace("process get-next-request -> entry with" "~n ReqId: ~p" "~n Vbs: ~p" "~n MibView: ~p",[ReqId, Vbs, MibView]), Res = get_err(do_get_next(MibView, Vbs, infinity)), ?vtrace("get-next result: " "~n ~p",[Res]), {ErrStatus, ErrIndex, ResVarbinds} = if Vsn =:= 'version-1' -> validate_next_v1(Res, MibView); true -> Res end, ?vtrace("get-next final result -> validation result:" "~n Error status: ~p" "~n Error index: ~p" "~n Varbinds: ~p",[ErrStatus,ErrIndex,ResVarbinds]), ResponseVarbinds = lists:keysort(#varbind.org_index, ResVarbinds), ?vtrace("get-next final result -> response varbinds: " "~n ~p",[ResponseVarbinds]), make_response_pdu(ReqId, ErrStatus, ErrIndex, Vbs, ResponseVarbinds); process_pdu(#pdu{type = 'get-bulk-request', request_id = ReqId, varbinds = Vbs, error_status = NonRepeaters, error_index = MaxRepetitions}, PduMS, _Vsn, MibView, GbMaxVBs) -> {ErrStatus, ErrIndex, ResponseVarbinds} = get_err(do_get_bulk(MibView, NonRepeaters, MaxRepetitions, PduMS, Vbs, GbMaxVBs)), ?vtrace("get-bulk final result: " "~n Error status: ~p" "~n Error index: ~p" "~n Respons varbinds: ~p", [ErrStatus,ErrIndex,ResponseVarbinds]), make_response_pdu(ReqId, ErrStatus, ErrIndex, Vbs, ResponseVarbinds); process_pdu(#pdu{type = 'set-request', request_id = ReqId, varbinds = Vbs}, _PduMS, Vsn, MibView, _GbMaxVbs)-> Res = do_set(MibView, Vbs), ?vtrace("set result: " "~n ~p",[Res]), {ErrStatus, ErrIndex} = if Vsn =:= 'version-1' -> validate_err(v2_to_v1, Res); true -> Res end, ?vtrace("set final result: " "~n Error status: ~p" "~n Error index: ~p",[ErrStatus,ErrIndex]), make_response_pdu(ReqId, ErrStatus, ErrIndex, Vbs, Vbs). %%----------------------------------------------------------------- %% Transform a value == noSuchInstance | noSuchObject or a %% Counter64 type to a noSuchName error for the whole pdu. %% Args: {Error, Index, Vbs} %% Returns: {NewError, NewIndex, NewVbs} %%----------------------------------------------------------------- validate_get_v1({noError, _, ResponseVarbinds}) -> case validate_get_v1_2(ResponseVarbinds) of true -> {noError, 0, ResponseVarbinds}; {Error, Index} -> {Error, Index, []} % dummy vbs end; validate_get_v1({ErrStatus, ErrIndex, ResponseVarbinds}) -> {v2err_to_v1err(ErrStatus), ErrIndex, ResponseVarbinds}. validate_get_v1_2([Vb | Vbs]) when ((Vb#varbind.value =/= noSuchInstance) andalso (Vb#varbind.value =/= noSuchObject) andalso (Vb#varbind.variabletype =/= 'Counter64')) -> validate_get_v1_2(Vbs); validate_get_v1_2([Vb | _Vbs]) -> {noSuchName, Vb#varbind.org_index}; validate_get_v1_2([]) -> true. %%----------------------------------------------------------------- %% Transform a value == endOfMibView to a noSuchName for the %% whole pdu, and do another get-next for any Counter64 value. %% Args: {Error, Index, Vbs} %% Returns: {NewError, NewIndex, NewVbs} %%----------------------------------------------------------------- validate_next_v1({noError, _, ResponseVarbinds}, MibView) -> case validate_next_v1_2(ResponseVarbinds, MibView, []) of {true, NVbs} -> {noError, 0, NVbs}; {Error, Index} -> {Error, Index, []} % dummy vbs end; validate_next_v1({ErrStatus, ErrIndex, ResponseVarbinds}, _MibView) -> {v2err_to_v1err(ErrStatus), ErrIndex, ResponseVarbinds}. validate_next_v1_2([Vb | _Vbs], _MibView, _Res) when Vb#varbind.value =:= endOfMibView -> {noSuchName, Vb#varbind.org_index}; validate_next_v1_2([Vb | Vbs], MibView, Res) when Vb#varbind.variabletype =:= 'Counter64' -> case validate_next_v1( do_get_next(MibView, [mk_next_oid(Vb)], infinity), MibView) of {noError, 0, [NVb]} -> validate_next_v1_2(Vbs, MibView, [NVb | Res]); {Error, Index, _OrgVb} -> {Error, Index} end; validate_next_v1_2([Vb | Vbs], MibView, Res) -> validate_next_v1_2(Vbs, MibView, [Vb | Res]); validate_next_v1_2([], _MibView, Res) -> {true, Res}. %%----------------------------------------------------------------- %% Optimization. When we get to a Counter64 object that is a table %% column, we'll try to find the next instance. This will be the %% next row in the table, which is a Counter64 value as well. This %% means that we will loop through the entire table, until we find %% a column that isn't a Counter64 column. We can optimze this by %% adding 1 to the column-no in the oid of this instance. %% If the table is implemented by a subagent this does not help, %% we'll call that subagent many times. But it shouldn't be any %% problems. %%----------------------------------------------------------------- mk_next_oid(Vb) -> case snmpa_mib:lookup(get(mibserver), Oid = Vb#varbind.oid) of {table_column, _MibEntry, TableEntryOid} -> [Col | _] = Oid -- TableEntryOid, Vb#varbind{oid = TableEntryOid ++ [Col+1]}; _ -> Vb end. %%%----------------------------------------------------------------- %%% 3. GET REQUEST %%% -------------- %%% According to RFC1157, section 4.1.2 and RFC1905, section 4.2.1. %%% In rfc1157:4.1.2 it isn't specified if noSuchName should be %%% returned even if some other varbind generates a genErr. %%% In rfc1905:4.2.1 this is not a problem since exceptions are %%% used, and thus a genErr will be returned anyway. %%%----------------------------------------------------------------- %%----------------------------------------------------------------- %% Func: do_get/3 %% Purpose: do_get handles "getRequests". %% Pre: incoming varbinds have type == 'NULL', value == unSpecified %% Returns: {noError, 0, ListOfNewVarbinds} | %% {ErrorStatus, ErrorIndex, []} %%----------------------------------------------------------------- %% If this function is called from a worker-process, we *may* %% need to tunnel into the master-agent and let it do the %% work do_get(MibView, UnsortedVarbinds, IsNotification) -> do_get(MibView, UnsortedVarbinds, IsNotification, false). do_get(MibView, UnsortedVarbinds, IsNotification, ForceMaster) -> ?vtrace("do_get -> entry with" "~n MibView: ~p" "~n UnsortedVarbinds: ~p" "~n IsNotification: ~p", [MibView, UnsortedVarbinds, IsNotification]), case (whereis(snmp_master_agent) =:= self()) of false when (ForceMaster =:= true) -> %% I am a lowly worker process, handoff to the master agent PduData = get_pdu_data(), call(snmp_master_agent, {do_get, MibView, UnsortedVarbinds, IsNotification, PduData}); _ -> %% This is me, the master, so go ahead {OutSideView, InSideView} = split_vbs_view(UnsortedVarbinds, MibView), {Error, Index, NewVbs} = do_get(InSideView, IsNotification), {Error, Index, NewVbs ++ OutSideView} end. split_vbs_view(Vbs, MibView) -> ?vtrace("split the varbinds view", []), split_vbs_view(Vbs, MibView, [], []). split_vbs_view([Vb | Vbs], MibView, Out, In) -> case snmpa_acm:validate_mib_view(Vb#varbind.oid, MibView) of true -> split_vbs_view(Vbs, MibView, Out, [Vb | In]); false -> split_vbs_view(Vbs, MibView, [Vb#varbind{value = noSuchObject} | Out], In) end; split_vbs_view([], _MibView, Out, In) -> {Out, In}. do_get(UnsortedVarbinds, IsNotification) -> {MyVarbinds, SubagentVarbinds} = sort_varbindlist(UnsortedVarbinds), case do_get_local(MyVarbinds, [], IsNotification) of {noError, 0, NewMyVarbinds} -> case do_get_subagents(SubagentVarbinds, IsNotification) of {noError, 0, NewSubagentVarbinds} -> {noError, 0, NewMyVarbinds ++ NewSubagentVarbinds}; {ErrorStatus, ErrorIndex, _} -> {ErrorStatus, ErrorIndex, []} end; {ErrorStatus, ErrorIndex, _} -> {ErrorStatus, ErrorIndex, []} end. %%----------------------------------------------------------------- %% Func: do_get_local/3 %% Purpose: Loop the variablebindings list. We know that each varbind %% in that list belongs to us. %% Returns: {noError, 0, ListOfNewVarbinds} | %% {ErrorStatus, ErrorIndex, []} %%----------------------------------------------------------------- do_get_local([Vb | Vbs], Res, IsNotification) -> case try_get(Vb, IsNotification) of NewVb when is_record(NewVb, varbind) -> do_get_local(Vbs, [NewVb | Res], IsNotification); ListOfNewVb when is_list(ListOfNewVb) -> do_get_local(Vbs, lists:append(ListOfNewVb, Res), IsNotification); {error, Error, OrgIndex} -> {Error, OrgIndex, []} end; do_get_local([], Res, _IsNotification) -> {noError, 0, Res}. %%----------------------------------------------------------------- %% Func: do_get_subagents/2 %% Purpose: Loop the list of varbinds for different subagents. %% For each of them, call sub_agent_get to retreive %% the values for them. %% Returns: {noError, 0, ListOfNewVarbinds} | %% {ErrorStatus, ErrorIndex, []} %%----------------------------------------------------------------- do_get_subagents(SubagentVarbinds, IsNotification) -> do_get_subagents(SubagentVarbinds, [], IsNotification). do_get_subagents([{SubAgentPid, SAVbs} | Tail], Res, IsNotification) -> {_SAOids, Vbs} = sa_split(SAVbs), case catch subagent_get(SubAgentPid, Vbs, IsNotification) of {noError, 0, NewVbs} -> do_get_subagents(Tail, lists:append(NewVbs, Res), IsNotification); {ErrorStatus, ErrorIndex, _} -> {ErrorStatus, ErrorIndex, []}; {'EXIT', Reason} -> user_err("Lost contact with subagent (get) ~w. Using genErr", [Reason]), {genErr, 0, []} end; do_get_subagents([], Res, _IsNotification) -> {noError, 0, Res}. %%----------------------------------------------------------------- %% Func: try_get/2 %% Returns: {error, ErrorStatus, OrgIndex} | %% #varbind | %% List of #varbind %%----------------------------------------------------------------- try_get(IVb, IsNotification) when is_record(IVb, ivarbind) -> ?vtrace("try_get(ivarbind) -> entry with" "~n IVb: ~p", [IVb]), get_var_value_from_ivb(IVb, IsNotification); try_get({TableOid, TableVbs}, IsNotification) -> ?vtrace("try_get(table) -> entry with" "~n TableOid: ~p" "~n TableVbs: ~p", [TableOid, TableVbs]), [#ivarbind{mibentry = MibEntry}|_] = TableVbs, {NoAccessVbs, AccessVbs} = check_all_table_vbs(TableVbs, IsNotification, [], []), case get_tab_value_from_mib(MibEntry, TableOid, AccessVbs) of {error, ErrorStatus, OrgIndex} -> {error, ErrorStatus, OrgIndex}; NVbs -> NVbs ++ NoAccessVbs end. %%----------------------------------------------------------------- %% Make sure all requested columns are accessible. %%----------------------------------------------------------------- check_all_table_vbs([IVb| IVbs], IsNotification, NoA, A) -> #ivarbind{mibentry = Me, varbind = Vb} = IVb, case Me#me.access of 'not-accessible' -> NNoA = [Vb#varbind{value = noSuchInstance} | NoA], check_all_table_vbs(IVbs, IsNotification, NNoA, A); 'accessible-for-notify' when IsNotification =:= false -> NNoA = [Vb#varbind{value = noSuchInstance} | NoA], check_all_table_vbs(IVbs, IsNotification, NNoA, A); 'write-only' -> NNoA = [Vb#varbind{value = noSuchInstance} | NoA], check_all_table_vbs(IVbs, IsNotification, NNoA, A); _ -> check_all_table_vbs(IVbs, IsNotification, NoA, [IVb | A]) end; check_all_table_vbs([], _IsNotification, NoA, A) -> {NoA, A}. %%----------------------------------------------------------------- %% Returns: {error, ErrorStatus, OrgIndex} | %% #varbind %%----------------------------------------------------------------- get_var_value_from_ivb(IVb, IsNotification) when IVb#ivarbind.status =:= noError -> ?vtrace("get_var_value_from_ivb(noError) -> entry", []), #ivarbind{mibentry = Me, varbind = Vb} = IVb, #varbind{org_index = OrgIndex, oid = Oid} = Vb, case Me#me.access of 'not-accessible' -> Vb#varbind{value = noSuchInstance}; 'accessible-for-notify' when IsNotification =:= false -> Vb#varbind{value = noSuchInstance}; 'write-only' -> Vb#varbind{value = noSuchInstance}; _ -> case get_var_value_from_mib(Me, Oid) of {value, Type, Value} -> Vb#varbind{variabletype = Type, value = Value}; {error, ErrorStatus} -> {error, ErrorStatus, OrgIndex} end end; get_var_value_from_ivb(#ivarbind{status = Status, varbind = Vb}, _) -> ?vtrace("get_var_value_from_ivb(~p) -> entry", [Status]), Vb#varbind{value = Status}. %%----------------------------------------------------------------- %% Func: get_var_value_from_mib/1 %% Purpose: %% Returns: {error, ErrorStatus} | %% {value, Type, Value} %%----------------------------------------------------------------- %% Pre: Oid is a correct instance Oid (lookup checked that). %% Returns: A correct return value (see make_value_a_correct_value) get_var_value_from_mib(#me{entrytype = variable, asn1_type = ASN1Type, mfa = {Mod, Func, Args}}, _Oid) -> ?vtrace("get_var_value_from_mib(variable) -> entry when" "~n Mod: ~p" "~n Func: ~p" "~n Args: ~p", [Mod, Func, Args]), Result = (catch dbg_apply(Mod, Func, [get | Args])), % mib shall return {value, <a-nice-value-within-range>} | % {noValue, noSuchName} (v1) | % {noValue, noSuchObject | noSuchInstance} (v2, v1) % everything else (including 'genErr') will generate 'genErr'. make_value_a_correct_value(Result, ASN1Type, {Mod, Func, Args}); get_var_value_from_mib(#me{entrytype = table_column, oid = MeOid, asn1_type = ASN1Type, mfa = {Mod, Func, Args}}, Oid) -> ?vtrace("get_var_value_from_mib(table_column) -> entry when" "~n MeOid: ~p" "~n Mod: ~p" "~n Func: ~p" "~n Args: ~p" "~n Oid: ~p", [MeOid, Mod, Func, Args, Oid]), Col = lists:last(MeOid), Indexes = snmp_misc:diff(Oid, MeOid), [Result] = (catch dbg_apply(Mod, Func, [get, Indexes, [Col] | Args])), make_value_a_correct_value(Result, ASN1Type, {Mod, Func, Args, Indexes, Col}). %% For table operations we need to pass RestOid down to the table-function. %% Its up to the table-function to check for noSuchInstance (ex: a %% non-existing row). %% Returns: {error, ErrorStatus, OrgIndex} | %% {value, Type, Value} get_tab_value_from_mib(#me{mfa = {Mod, Func, Args}}, TableOid, TableVbs) -> ?vtrace("get_tab_value_from_mib -> entry when" "~n Mod: ~p" "~n Func: ~p" "~n Args: ~p", [Mod, Func, Args]), TableOpsWithShortOids = deletePrefixes(TableOid, TableVbs), SortedVBsRows = snmpa_svbl:sort_varbinds_rows(TableOpsWithShortOids), case get_value_all_rows(SortedVBsRows, Mod, Func, Args, []) of {Error, Index} -> #ivarbind{varbind = Vb} = lists:nth(Index, TableVbs), {error, Error, Vb#varbind.org_index}; ListOfValues -> merge_varbinds_and_value(TableVbs, ListOfValues) end. %%----------------------------------------------------------------- %% Values is a scrambled list of {CorrectValue, Index}, where Index %% is index into the #ivarbind list. So for each Value, we must %% find the corresponding #ivarbind, and merge them into a new %% #varbind. %% The Values list comes from validate_tab_res. %%----------------------------------------------------------------- merge_varbinds_and_value(IVbs, [{{value, Type, Value}, Index} | Values]) -> #ivarbind{varbind = Vb} = lists:nth(Index, IVbs), [Vb#varbind{variabletype = Type, value = Value} | merge_varbinds_and_value(IVbs, Values)]; merge_varbinds_and_value(_, []) -> []. get_value_all_rows([{[], OrgCols} | Rows], Mod, Func, Args, Res) -> ?vtrace("get_value_all_rows -> entry when" "~n OrgCols: ~p", [OrgCols]), Cols = [{{value, noValue, noSuchInstance}, Index} || {_Col, _ASN1Type, Index} <- OrgCols], NewRes = lists:append(Cols, Res), get_value_all_rows(Rows, Mod, Func, Args, NewRes); get_value_all_rows([{RowIndex, OrgCols} | Rows], Mod, Func, Args, Res) -> ?vtrace("get_value_all_rows -> entry when" "~n RowIndex: ~p" "~n OrgCols: ~p", [RowIndex, OrgCols]), {DOrgCols, Dup} = remove_duplicates(OrgCols), Cols = delete_index(DOrgCols), Result = (catch dbg_apply(Mod, Func, [get, RowIndex, Cols | Args])), case validate_tab_res(Result, DOrgCols, {Mod, Func, Args}) of Values when is_list(Values) -> NVals = restore_duplicates(Dup, Values), NewRes = lists:append(NVals, Res), get_value_all_rows(Rows, Mod, Func, Args, NewRes); {error, ErrorStatus, Index} -> validate_err(row_set, {ErrorStatus, Index}, {Mod, Func, Args}) end; get_value_all_rows([], _Mod, _Func, _Args, Res) -> ?vtrace("get_value_all_rows -> entry when done" "~n Res: ~p", [Res]), Res. %%----------------------------------------------------------------- %% Returns: list of {ShortOid, ASN1TYpe} %%----------------------------------------------------------------- deletePrefixes(Prefix, [#ivarbind{varbind = Varbind, mibentry = ME} | Vbs]) -> #varbind{oid = Oid} = Varbind, [{snmp_misc:diff(Oid, Prefix), ME#me.asn1_type} | deletePrefixes(Prefix, Vbs)]; deletePrefixes(_Prefix, []) -> []. %%----------------------------------------------------------------- %% Args: {RowIndex, list of {ShortOid, ASN1Type}} %% Returns: list of Col %%----------------------------------------------------------------- delete_index([{Col, _Val, _OrgIndex} | T]) -> [Col | delete_index(T)]; delete_index([]) -> []. %%----------------------------------------------------------------- %% This function is called before 'get' on a table, and removes %% any duplicate columns. It returns {Cols, DupInfo}. The Cols %% are the unique columns. The instrumentation function is %% called to get the values. These values, together with the %% DupInfo, is later passed to restore_duplicates, which uses %% the retrieved values to reconstruct the original column list, %% but with the retrieved value for each column. %%----------------------------------------------------------------- remove_duplicates(Cols) -> remove_duplicates(Cols, [], []). remove_duplicates([{Col, V1, OrgIdx1}, {Col, V2, OrgIdx2} | T], NCols, Dup) -> remove_duplicates([{Col, V1, OrgIdx1} | T], NCols, [{Col, V2, OrgIdx2} | Dup]); remove_duplicates([Col | T], NCols, Dup) -> remove_duplicates(T, [Col | NCols], Dup); remove_duplicates([], NCols, Dup) -> {lists:reverse(NCols), lists:reverse(Dup)}. restore_duplicates([], Cols) -> [{Val, OrgIndex} || {_Col, Val, OrgIndex} <- Cols]; restore_duplicates([{Col, _Val2, OrgIndex2} | Dup], [{Col, NVal, OrgIndex1} | Cols]) -> [{NVal, OrgIndex2} | restore_duplicates(Dup, [{Col, NVal, OrgIndex1} | Cols])]; restore_duplicates(Dup, [{_Col, Val, OrgIndex} | T]) -> [{Val, OrgIndex} | restore_duplicates(Dup, T)]. %% Maps the column number to Index. % col_to_index(0, _) -> 0; % col_to_index(Col, [{Col, _, Index}|_]) -> % Index; % col_to_index(Col, [_|Cols]) -> % col_to_index(Col, Cols). %%----------------------------------------------------------------- %% Three cases: %% 1) All values ok %% 2) table_func returned {Error, ...} %% 3) Some value in Values list is erroneous. %% Args: Value is a list of values from table_func(get..) %% OrgCols is a list with {Col, ASN1Type, OrgIndex} %% each element in Values and OrgCols correspond to each %% other. %%----------------------------------------------------------------- validate_tab_res(Values, OrgCols, Mfa) when is_list(Values) -> {_Col, _ASN1Type, OneIdx} = hd(OrgCols), validate_tab_res(Values, OrgCols, Mfa, [], OneIdx); validate_tab_res({noValue, Error}, OrgCols, Mfa) -> Values = lists:duplicate(length(OrgCols), {noValue, Error}), validate_tab_res(Values, OrgCols, Mfa); validate_tab_res({genErr, Col}, OrgCols, Mfa) -> case lists:keysearch(Col, 1, OrgCols) of {value, {_Col, _ASN1Type, Index}} -> {error, genErr, Index}; _ -> user_err("Invalid column in {genErr, ~w} from ~w (get)", [Col, Mfa]), [{_Col, _ASN1Type, Index} | _] = OrgCols, {error, genErr, Index} end; validate_tab_res(genErr, [{_Col, __ASN1Type, Index} | _OrgCols], _Mfa) -> {error, genErr, Index}; validate_tab_res(Error, [{_Col, _ASN1Type, Index} | _OrgCols], Mfa) -> user_err("Invalid return value ~w from ~w (get)",[Error, Mfa]), {error, genErr, Index}. validate_tab_res([Value | Values], [{Col, ASN1Type, Index} | OrgCols], Mfa, Res, I) -> %% This one makes it possible to return a list of genErr, which %% is not allowed according to the manual. But that's ok, as %% everything else will generate a genErr! (the only problem is %% that it won't generate a user_error). case make_value_a_correct_value(Value, ASN1Type, Mfa) of {error, ErrorStatus} -> {error, ErrorStatus, Index}; CorrectValue -> NewRes = [{Col, CorrectValue, Index} | Res], validate_tab_res(Values, OrgCols, Mfa, NewRes, I) end; validate_tab_res([], [], _Mfa, Res, _I) -> lists:reverse(Res); validate_tab_res([], [{_Col, _ASN1Type, Index}|_], Mfa, _Res, _I) -> user_err("Too few values returned from ~w (get)", [Mfa]), {error, genErr, Index}; validate_tab_res(_TooMany, [], Mfa, _Res, I) -> user_err("Too many values returned from ~w (get)", [Mfa]), {error, genErr, I}. %%%----------------------------------------------------------------- %%% 4. GET-NEXT REQUEST %%% -------------- %%% According to RFC1157, section 4.1.3 and RFC1905, section 4.2.2. %%%----------------------------------------------------------------- %%----------------------------------------------------------------- %% Func: do_get_next/2 %% Purpose: do_get_next handles "getNextRequests". %% Note: Even if it is SNMPv1, a varbind's value can be %% endOfMibView. This is converted to noSuchName in process_pdu. %% Returns: {noError, 0, ListOfNewVarbinds} | %% {ErrorStatus, ErrorIndex, []} %% Note2: ListOfNewVarbinds is not sorted in any order!!! %% Alg: First, the variables are sorted in OID order. %% %% Second, next in the MIB is performed for each OID, and %% the result is collected as: if next oid is a variable, %% perform a get to retrieve its value; if next oid is in a %% table, save this value and continue until we get an oid %% outside this table. Then perform get_next on the table, %% and continue with all endOfTables and the oid outside the %% table; if next oid is an subagent, save this value and %% continue as in the table case. %% %% Third, each response is checked for endOfMibView, or (for %% subagents) that the Oid returned has the correct prefix. %% (This is necessary since an SA can be registered under many %% separated subtrees, and if the last variable in the first %% subtree is requested in a next, the SA will return the first %% variable in the second subtree. This might be working, since %% there may be a variable in between these subtrees.) For each %% of these, a new get-next is performed, one at a time. %% This alg. might be optimised in several ways. The most %% striking one is that the same SA might be called several %% times, when one time should be enough. But it isn't clear %% that this really matters, since many nexts across the same %% subagent must be considered to be very rare. %%----------------------------------------------------------------- %% It may be a bit agressive to check this already, %% but since it is a security measure, it makes sense. do_get_next(_MibView, UnsortedVarbinds, GbMaxVBs) when (is_integer(GbMaxVBs) andalso (length(UnsortedVarbinds) > GbMaxVBs)) -> {tooBig, 0, []}; % What is the correct index in this case? do_get_next(MibView, UnsortedVBs, GbMaxVBs) -> ?vt("do_get_next -> entry when" "~n MibView: ~p" "~n UnsortedVBs: ~p", [MibView, UnsortedVBs]), SortedVBs = oid_sort_vbs(UnsortedVBs), ?vt("do_get_next -> " "~n SortedVBs: ~p", [SortedVBs]), next_loop_varbinds([], SortedVBs, MibView, [], [], GbMaxVBs). oid_sort_vbs(Vbs) -> lists:keysort(#varbind.oid, Vbs). next_loop_varbinds(_, Vbs, _MibView, Res, _LAVb, GbMaxVBs) when (is_integer(GbMaxVBs) andalso ((length(Vbs) + length(Res)) > GbMaxVBs)) -> {tooBig, 0, []}; % What is the correct index in this case? %% LAVb is Last Accessible Vb next_loop_varbinds([], [Vb | Vbs], MibView, Res, LAVb, GbMaxVBs) -> ?vt("next_loop_varbinds -> entry when" "~n Vb: ~p" "~n MibView: ~p", [Vb, MibView]), case varbind_next(Vb, MibView) of endOfMibView -> ?vt("next_loop_varbind -> endOfMibView", []), RVb = if LAVb =:= [] -> Vb; true -> LAVb end, NewVb = RVb#varbind{variabletype = 'NULL', value = endOfMibView}, next_loop_varbinds([], Vbs, MibView, [NewVb | Res], [], GbMaxVBs); {variable, ME, VarOid} when ((ME#me.access =/= 'not-accessible') andalso (ME#me.access =/= 'write-only') andalso (ME#me.access =/= 'accessible-for-notify')) -> ?vt("next_loop_varbind -> variable: " "~n ME: ~p" "~n VarOid: ~p", [ME, VarOid]), case try_get_instance(Vb, ME) of {value, noValue, _NoSuchSomething} -> ?vt("next_loop_varbind -> noValue", []), %% Try next one NewVb = Vb#varbind{oid = VarOid, value = 'NULL'}, next_loop_varbinds([], [NewVb | Vbs], MibView, Res, [], GbMaxVBs); {value, Type, Value} -> ?vt("next_loop_varbind -> value" "~n Type: ~p" "~n Value: ~p", [Type, Value]), NewVb = Vb#varbind{oid = VarOid, variabletype = Type, value = Value}, next_loop_varbinds([], Vbs, MibView, [NewVb | Res], [], GbMaxVBs); {error, ErrorStatus} -> ?vdebug("next loop varbinds:" "~n ErrorStatus: ~p",[ErrorStatus]), {ErrorStatus, Vb#varbind.org_index, []} end; {variable, _ME, VarOid} -> ?vt("next_loop_varbind -> variable: " "~n VarOid: ~p", [VarOid]), RVb = if LAVb =:= [] -> Vb; true -> LAVb end, NewVb = Vb#varbind{oid = VarOid, value = 'NULL'}, next_loop_varbinds([], [NewVb | Vbs], MibView, Res, RVb, GbMaxVBs); {table, TableOid, TableRestOid, ME} -> ?vt("next_loop_varbind -> table: " "~n TableOid: ~p" "~n TableRestOid: ~p" "~n ME: ~p", [TableOid, TableRestOid, ME]), next_loop_varbinds({table, TableOid, ME, [{tab_oid(TableRestOid), Vb}]}, Vbs, MibView, Res, [], GbMaxVBs); {subagent, SubAgentPid, SAOid} -> ?vt("next_loop_varbind -> subagent: " "~n SubAgentPid: ~p" "~n SAOid: ~p", [SubAgentPid, SAOid]), NewVb = Vb#varbind{variabletype = 'NULL', value = 'NULL'}, next_loop_varbinds({subagent, SubAgentPid, SAOid, [NewVb]}, Vbs, MibView, Res, [], GbMaxVBs) end; next_loop_varbinds({table, TableOid, ME, TabOids}, [Vb | Vbs], MibView, Res, _LAVb, GbMaxVBs) -> ?vt("next_loop_varbinds(table) -> entry with" "~n TableOid: ~p" "~n Vb: ~p", [TableOid, Vb]), case varbind_next(Vb, MibView) of {table, TableOid, TableRestOid, _ME} -> next_loop_varbinds({table, TableOid, ME, [{tab_oid(TableRestOid), Vb} | TabOids]}, Vbs, MibView, Res, [], GbMaxVBs); _ -> case get_next_table(ME, TableOid, TabOids, MibView) of {ok, TabRes, TabEndOfTabVbs} -> NewVbs = lists:append(TabEndOfTabVbs, [Vb | Vbs]), NewRes = lists:append(TabRes, Res), next_loop_varbinds([], NewVbs, MibView, NewRes, [], GbMaxVBs); {ErrorStatus, OrgIndex} -> ?vdebug("next loop varbinds: next varbind" "~n ErrorStatus: ~p" "~n OrgIndex: ~p", [ErrorStatus,OrgIndex]), {ErrorStatus, OrgIndex, []} end end; next_loop_varbinds({table, TableOid, ME, TabOids}, [], MibView, Res, _LAVb, GbMaxVBs) -> ?vt("next_loop_varbinds(table) -> entry with" "~n TableOid: ~p", [TableOid]), case get_next_table(ME, TableOid, TabOids, MibView) of {ok, TabRes, TabEndOfTabVbs} -> ?vt("next_loop_varbinds(table) -> get_next_table result:" "~n TabRes: ~p" "~n TabEndOfTabVbs: ~p", [TabRes, TabEndOfTabVbs]), NewRes = lists:append(TabRes, Res), next_loop_varbinds([], TabEndOfTabVbs, MibView, NewRes, [], GbMaxVBs); {ErrorStatus, OrgIndex} -> ?vdebug("next loop varbinds: next table" "~n ErrorStatus: ~p" "~n OrgIndex: ~p", [ErrorStatus,OrgIndex]), {ErrorStatus, OrgIndex, []} end; next_loop_varbinds({subagent, SAPid, SAOid, SAVbs}, [Vb | Vbs], MibView, Res, _LAVb, GbMaxVBs) -> ?vt("next_loop_varbinds(subagent) -> entry with" "~n SAPid: ~p" "~n SAOid: ~p" "~n Vb: ~p", [SAPid, SAOid, Vb]), case varbind_next(Vb, MibView) of {subagent, _SubAgentPid, SAOid} -> next_loop_varbinds({subagent, SAPid, SAOid, [Vb | SAVbs]}, Vbs, MibView, Res, [], GbMaxVBs); _ -> case get_next_sa(SAPid, SAOid, SAVbs, MibView) of {ok, SARes, SAEndOfMibViewVbs} -> NewVbs = lists:append(SAEndOfMibViewVbs, [Vb | Vbs]), NewRes = lists:append(SARes, Res), next_loop_varbinds([], NewVbs, MibView, NewRes, [], GbMaxVBs); {noSuchName, OrgIndex} -> %% v1 reply, treat this Vb as endOfMibView, and try again %% for the others. case lists:keysearch(OrgIndex, #varbind.org_index, SAVbs) of {value, EVb} -> NextOid = next_oid(SAOid), EndOfVb = EVb#varbind{oid = NextOid, value = {endOfMibView, NextOid}}, case lists:delete(EVb, SAVbs) of [] -> next_loop_varbinds([], [EndOfVb, Vb | Vbs], MibView, Res, [], GbMaxVBs); TryAgainVbs -> next_loop_varbinds({subagent, SAPid, SAOid, TryAgainVbs}, [EndOfVb, Vb | Vbs], MibView, Res, [], GbMaxVBs) end; false -> %% bad index from subagent {genErr, (hd(SAVbs))#varbind.org_index, []} end; {ErrorStatus, OrgIndex} -> ?vdebug("next loop varbinds: next subagent" "~n Vb: ~p" "~n ErrorStatus: ~p" "~n OrgIndex: ~p", [Vb,ErrorStatus,OrgIndex]), {ErrorStatus, OrgIndex, []} end end; next_loop_varbinds({subagent, SAPid, SAOid, SAVbs}, [], MibView, Res, _LAVb, GbMaxVBs) -> ?vt("next_loop_varbinds(subagent) -> entry with" "~n SAPid: ~p" "~n SAOid: ~p", [SAPid, SAOid]), case get_next_sa(SAPid, SAOid, SAVbs, MibView) of {ok, SARes, SAEndOfMibViewVbs} -> NewRes = lists:append(SARes, Res), next_loop_varbinds([], SAEndOfMibViewVbs, MibView, NewRes, [], GbMaxVBs); {noSuchName, OrgIndex} -> %% v1 reply, treat this Vb as endOfMibView, and try again for %% the others. case lists:keysearch(OrgIndex, #varbind.org_index, SAVbs) of {value, EVb} -> NextOid = next_oid(SAOid), EndOfVb = EVb#varbind{oid = NextOid, value = {endOfMibView, NextOid}}, case lists:delete(EVb, SAVbs) of [] -> next_loop_varbinds([], [EndOfVb], MibView, Res, [], GbMaxVBs); TryAgainVbs -> next_loop_varbinds({subagent, SAPid, SAOid, TryAgainVbs}, [EndOfVb], MibView, Res, [], GbMaxVBs) end; false -> %% bad index from subagent {genErr, (hd(SAVbs))#varbind.org_index, []} end; {ErrorStatus, OrgIndex} -> ?vdebug("next loop varbinds: next subagent" "~n ErrorStatus: ~p" "~n OrgIndex: ~p", [ErrorStatus,OrgIndex]), {ErrorStatus, OrgIndex, []} end; next_loop_varbinds([], [], _MibView, Res, _LAVb, _GbMaxVBs) -> ?vt("next_loop_varbinds -> entry when done", []), {noError, 0, Res}. try_get_instance(_Vb, #me{mfa = {M, F, A}, asn1_type = ASN1Type}) -> ?vtrace("try_get_instance -> entry with" "~n M: ~p" "~n F: ~p" "~n A: ~p", [M,F,A]), Result = (catch dbg_apply(M, F, [get | A])), % mib shall return {value, <a-nice-value-within-range>} | % {noValue, noSuchName} (v1) | % {noValue, noSuchObject | noSuchInstance} (v2, v1) % everything else (including 'genErr') will generate 'genErr'. make_value_a_correct_value(Result, ASN1Type, {M, F, A}). tab_oid([]) -> [0]; tab_oid(X) -> X. %%----------------------------------------------------------------- %% Perform a next, using the varbinds Oid if value is simple %% value. If value is {endOf<something>, NextOid}, use NextOid. %% This case happens when a table has returned endOfTable, or %% a subagent has returned endOfMibView. %%----------------------------------------------------------------- varbind_next(#varbind{value = Value, oid = Oid}, MibView) -> ?vt("varbind_next -> entry with" "~n Value: ~p" "~n Oid: ~p" "~n MibView: ~p", [Value, Oid, MibView]), case Value of {endOfTable, NextOid} -> snmpa_mib:next(get(mibserver), NextOid, MibView); {endOfMibView, NextOid} -> snmpa_mib:next(get(mibserver), NextOid, MibView); _ -> snmpa_mib:next(get(mibserver), Oid, MibView) end. get_next_table(#me{mfa = {M, F, A}}, TableOid, TableOids, MibView) -> % We know that all TableOids have at least a column number as oid ?vt("get_next_table -> entry with" "~n M: ~p" "~n F: ~p" "~n A: ~p" "~n TableOid: ~p" "~n TableOids: ~p" "~n MibView: ~p", [M, F, A, TableOid, TableOids, MibView]), Sorted = snmpa_svbl:sort_varbinds_rows(TableOids), case get_next_values_all_rows(Sorted, M,F,A, [], TableOid) of NewVbs when is_list(NewVbs) -> ?vt("get_next_table -> " "~n NewVbs: ~p", [NewVbs]), % We must now check each Vb for endOfTable and that it is % in the MibView. If not, it becomes a endOfTable. We % collect all of these together. transform_tab_next_result(NewVbs, {[], []}, MibView); {ErrorStatus, OrgIndex} -> {ErrorStatus, OrgIndex} end. get_next_values_all_rows([Row | Rows], M, F, A, Res, TabOid) -> {RowIndex, TableOids} = Row, Cols = delete_index(TableOids), ?vt("get_next_values_all_rows -> " "~n Cols: ~p", [Cols]), Result = (catch dbg_apply(M, F, [get_next, RowIndex, Cols | A])), ?vt("get_next_values_all_rows -> " "~n Result: ~p", [Result]), case validate_tab_next_res(Result, TableOids, {M, F, A}, TabOid) of Values when is_list(Values) -> ?vt("get_next_values_all_rows -> " "~n Values: ~p", [Values]), NewRes = lists:append(Values, Res), get_next_values_all_rows(Rows, M, F, A, NewRes, TabOid); {ErrorStatus, OrgIndex} -> {ErrorStatus, OrgIndex} end; get_next_values_all_rows([], _M, _F, _A, Res, _TabOid) -> Res. transform_tab_next_result([Vb | Vbs], {Res, EndOfs}, MibView) -> case Vb#varbind.value of {endOfTable, _} -> %% ?vtrace("transform_tab_next_result -> endOfTable: " %% "split varbinds",[]), %% R = split_varbinds(Vbs, Res, [Vb | EndOfs]), %% ?vtrace("transform_tab_next_result -> " %% "~n R: ~p", [R]), %% R; split_varbinds(Vbs, Res, [Vb | EndOfs]); _ -> case snmpa_acm:validate_mib_view(Vb#varbind.oid, MibView) of true -> transform_tab_next_result(Vbs, {[Vb|Res], EndOfs},MibView); _ -> Oid = Vb#varbind.oid, NewEndOf = Vb#varbind{value = {endOfTable, Oid}}, transform_tab_next_result(Vbs, {Res, [NewEndOf | EndOfs]}, MibView) end end; transform_tab_next_result([], {Res, EndOfs}, _MibView) -> ?vt("transform_tab_next_result -> entry with: " "~n Res: ~p" "~n EndIfs: ~p",[Res, EndOfs]), {ok, Res, EndOfs}. %%----------------------------------------------------------------- %% Three cases: %% 1) All values ok %% 2) table_func returned {Error, ...} %% 3) Some value in Values list is erroneous. %% Args: Value is a list of values from table_func(get_next, ...) %% TableOids is a list of {TabRestOid, OrgVb} %% each element in Values and TableOids correspond to each %% other. %% Returns: List of NewVarbinds | %% {ErrorStatus, OrgIndex} %% (In the NewVarbinds list, the value may be endOfTable) %%----------------------------------------------------------------- validate_tab_next_res(Values, TableOids, Mfa, TabOid) -> ?vt("validate_tab_next_res -> entry with: " "~n Values: ~p" "~n TableOids: ~p" "~n Mfa: ~p" "~n TabOid: ~p", [Values, TableOids, Mfa, TabOid]), {_Col, _ASN1Type, OneIdx} = hd(TableOids), validate_tab_next_res(Values, TableOids, Mfa, [], TabOid, next_oid(TabOid), OneIdx). validate_tab_next_res([{NextOid, Value} | Values], [{_ColNo, OrgVb, _Index} | TableOids], Mfa, Res, TabOid, TabNextOid, I) -> ?vt("validate_tab_next_res -> entry with: " "~n NextOid: ~p" "~n Value: ~p" "~n Values: ~p" "~n TableOids: ~p" "~n Mfa: ~p" "~n TabOid: ~p", [NextOid, Value, Values, TableOids, Mfa, TabOid]), #varbind{org_index = OrgIndex} = OrgVb, ?vt("validate_tab_next_res -> OrgIndex: ~p", [OrgIndex]), NextCompleteOid = lists:append(TabOid, NextOid), case snmpa_mib:lookup(get(mibserver), NextCompleteOid) of {table_column, #me{asn1_type = ASN1Type}, _TableEntryOid} -> ?vt("validate_tab_next_res -> ASN1Type: ~p", [ASN1Type]), case make_value_a_correct_value({value, Value}, ASN1Type, Mfa) of {error, ErrorStatus} -> ?vt("validate_tab_next_res -> " "~n ErrorStatus: ~p", [ErrorStatus]), {ErrorStatus, OrgIndex}; {value, Type, NValue} -> ?vt("validate_tab_next_res -> " "~n Type: ~p" "~n NValue: ~p", [Type, NValue]), NewVb = OrgVb#varbind{oid = NextCompleteOid, variabletype = Type, value = NValue}, validate_tab_next_res(Values, TableOids, Mfa, [NewVb | Res], TabOid, TabNextOid, I) end; Error -> user_err("Invalid oid ~w from ~w (get_next). Using genErr => ~p", [NextOid, Mfa, Error]), {genErr, OrgIndex} end; validate_tab_next_res([endOfTable | Values], [{_ColNo, OrgVb, _Index} | TableOids], Mfa, Res, TabOid, TabNextOid, I) -> ?vt("validate_tab_next_res(endOfTable) -> entry with: " "~n Values: ~p" "~n OrgVb: ~p" "~n TableOids: ~p" "~n Mfa: ~p" "~n Res: ~p" "~n TabOid: ~p" "~n TabNextOid: ~p" "~n I: ~p", [Values, OrgVb, TableOids, Mfa, Res, TabOid, TabNextOid, I]), NewVb = OrgVb#varbind{value = {endOfTable, TabNextOid}}, validate_tab_next_res(Values, TableOids, Mfa, [NewVb | Res], TabOid, TabNextOid, I); validate_tab_next_res([], [], _Mfa, Res, _TabOid, _TabNextOid, _I) -> Res; validate_tab_next_res([], [{_Col, _OrgVb, Index}|_], Mfa, _Res, _, _, _I) -> user_err("Too few values returned from ~w (get_next)", [Mfa]), {genErr, Index}; validate_tab_next_res({genErr, ColNumber}, OrgCols, Mfa, _Res, _TabOid, _TabNextOid, _I) -> OrgIndex = snmpa_svbl:col_to_orgindex(ColNumber, OrgCols), validate_err(table_next, {genErr, OrgIndex}, Mfa); validate_tab_next_res({error, Reason}, [{_ColNo, OrgVb, _Index} | _TableOids], Mfa, _Res, _TabOid, _TabNextOid, _I) -> #varbind{org_index = OrgIndex} = OrgVb, user_err("Erroneous return value ~w from ~w (get_next)", [Reason, Mfa]), {genErr, OrgIndex}; validate_tab_next_res(Error, [{_ColNo, OrgVb, _Index} | _TableOids], Mfa, _Res, _TabOid, _TabNextOid, _I) -> #varbind{org_index = OrgIndex} = OrgVb, user_err("Invalid return value ~w from ~w (get_next)", [Error, Mfa]), {genErr, OrgIndex}; validate_tab_next_res(TooMany, [], Mfa, _Res, _, _, I) -> user_err("Too many values ~w returned from ~w (get_next)", [TooMany, Mfa]), {genErr, I}. %%----------------------------------------------------------------- %% Func: get_next_sa/4 %% Purpose: Loop the list of varbinds for the subagent. %% Call subagent_get_next to retreive %% the next varbinds. %% Returns: {ok, ListOfNewVbs, ListOfEndOfMibViewsVbs} | %% {ErrorStatus, ErrorIndex} %%----------------------------------------------------------------- get_next_sa(SAPid, SAOid, SAVbs, MibView) -> case catch subagent_get_next(SAPid, MibView, SAVbs) of {noError, 0, NewVbs} -> NewerVbs = transform_sa_next_result(NewVbs,SAOid,next_oid(SAOid)), split_varbinds(NewerVbs, [], []); {ErrorStatus, ErrorIndex, _} -> {ErrorStatus, ErrorIndex}; {'EXIT', Reason} -> user_err("Lost contact with subagent (next) ~w. Using genErr", [Reason]), {genErr, 0} end. %%----------------------------------------------------------------- %% Check for wrong prefix returned or endOfMibView, and convert %% into {endOfMibView, SANextOid}. %%----------------------------------------------------------------- transform_sa_next_result([Vb | Vbs], SAOid, SANextOid) when Vb#varbind.value =:= endOfMibView -> [Vb#varbind{value = {endOfMibView, SANextOid}} | transform_sa_next_result(Vbs, SAOid, SANextOid)]; transform_sa_next_result([Vb | Vbs], SAOid, SANextOid) -> case lists:prefix(SAOid, Vb#varbind.oid) of true -> [Vb | transform_sa_next_result(Vbs, SAOid, SANextOid)]; _ -> [Vb#varbind{oid = SANextOid, value = {endOfMibView, SANextOid}} | transform_sa_next_result(Vbs, SAOid, SANextOid)] end; transform_sa_next_result([], _SAOid, _SANextOid) -> []. split_varbinds([Vb | Vbs], Res, EndOfs) -> case Vb#varbind.value of {endOfMibView, _} -> split_varbinds(Vbs, Res, [Vb | EndOfs]); {endOfTable, _} -> split_varbinds(Vbs, Res, [Vb | EndOfs]); _ -> split_varbinds(Vbs, [Vb | Res], EndOfs) end; split_varbinds([], Res, EndOfs) -> {ok, Res, EndOfs}. next_oid(Oid) -> case lists:reverse(Oid) of [H | T] -> lists:reverse([H+1 | T]); [] -> [] end. %%%----------------------------------------------------------------- %%% 5. GET-BULK REQUEST %%% %%% In order to prevent excesses in reply sizes there are two %%% preventive methods in place. One is to check that the encode %%% size does not exceed Max PDU size (this is mentioned in the %%% standard). The other is a simple VBs limit. That is, the %%% resulting response cannot contain more then this number of VBs. %%%----------------------------------------------------------------- do_get_bulk(MibView, NonRepeaters, MaxRepetitions, PduMS, Varbinds, GbMaxVBs) -> ?vtrace("do_get_bulk -> entry with" "~n MibView: ~p" "~n NonRepeaters: ~p" "~n MaxRepetitions: ~p" "~n PduMS: ~p" "~n Varbinds: ~p" "~n GbMaxVBs: ~p", [MibView, NonRepeaters, MaxRepetitions, PduMS, Varbinds, GbMaxVBs]), {NonRepVbs, RestVbs} = split_vbs(NonRepeaters, Varbinds, []), ?vt("do_get_bulk -> split: " "~n NonRepVbs: ~p" "~n RestVbs: ~p", [NonRepVbs, RestVbs]), case do_get_next(MibView, NonRepVbs, GbMaxVBs) of {noError, 0, UResNonRepVbs} -> ?vt("do_get_bulk -> next noError: " "~n UResNonRepVbs: ~p", [UResNonRepVbs]), ResNonRepVbs = lists:keysort(#varbind.org_index, UResNonRepVbs), %% Decode the first varbinds, produce a reversed list of %% listOfBytes. case (catch enc_vbs(PduMS - ?empty_pdu_size, ResNonRepVbs)) of {error, Idx, Reason} -> user_err("failed encoding varbind ~w:~n~p", [Idx, Reason]), {genErr, Idx, []}; {SizeLeft, Res} when is_integer(SizeLeft) and is_list(Res) -> ?vtrace("do_get_bulk -> encoded: " "~n SizeLeft: ~p" "~n Res: ~w", [SizeLeft, Res]), case (catch do_get_rep(SizeLeft, MibView, MaxRepetitions, RestVbs, Res, length(UResNonRepVbs), GbMaxVBs)) of {error, Idx, Reason} -> user_err("failed encoding varbind ~w:~n~p", [Idx, Reason]), {genErr, Idx, []}; Res when is_list(Res) -> ?vtrace("do get bulk -> Res: " "~n ~w", [Res]), {noError, 0, conv_res(Res)}; {noError, 0, Data} = OK -> ?vtrace("do get bulk -> OK: " "~n length(Data): ~w", [length(Data)]), OK; Else -> ?vtrace("do get bulk -> Else: " "~n ~w", [Else]), Else end; Res when is_list(Res) -> {noError, 0, conv_res(Res)} end; {ErrorStatus, Index, _} -> ?vdebug("do get bulk: " "~n ErrorStatus: ~p" "~n Index: ~p",[ErrorStatus, Index]), {ErrorStatus, Index, []} end. % sz(L) when list(L) -> length(L); % sz(B) when binary(B) -> size(B); % sz(_) -> unknown. split_vbs(N, Varbinds, Res) when N =< 0 -> {Res, Varbinds}; split_vbs(N, [H | T], Res) -> split_vbs(N-1, T, [H | Res]); split_vbs(_N, [], Res) -> {Res, []}. enc_vbs(SizeLeft, Vbs) -> ?vt("enc_vbs -> entry with" "~n SizeLeft: ~w", [SizeLeft]), Fun = fun(Vb, {Sz, Res}) when Sz > 0 -> ?vt("enc_vbs -> (fun) entry with" "~n Vb: ~p" "~n Sz: ~p" "~n Res: ~w", [Vb, Sz, Res]), case (catch snmp_pdus:enc_varbind(Vb)) of {'EXIT', Reason} -> ?vtrace("enc_vbs -> encode failed: " "~n Reason: ~p", [Reason]), throw({error, Vb#varbind.org_index, Reason}); X -> ?vt("enc_vbs -> X: ~w", [X]), Lx = length(X), ?vt("enc_vbs -> Lx: ~w", [Lx]), if Lx < Sz -> {Sz - length(X), [X | Res]}; true -> throw(Res) end end; (_Vb, {_Sz, [_H | T]}) -> ?vt("enc_vbs -> (fun) entry with" "~n T: ~p", [T]), throw(T); (_Vb, {_Sz, []}) -> ?vt("enc_vbs -> (fun) entry", []), throw([]) end, lists:foldl(Fun, {SizeLeft, []}, Vbs). do_get_rep(Sz, MibView, MaxRepetitions, Varbinds, Res, GbNumVBs, GbMaxVBs) when MaxRepetitions >= 0 -> do_get_rep(Sz, MibView, 0, MaxRepetitions, Varbinds, Res, GbNumVBs, GbMaxVBs); do_get_rep(Sz, MibView, _MaxRepetitions, Varbinds, Res, GbNumVBs, GbMaxVBs) -> do_get_rep(Sz, MibView, 0, 0, Varbinds, Res, GbNumVBs, GbMaxVBs). conv_res(ResVarbinds) -> conv_res(ResVarbinds, []). conv_res([VbListOfBytes | T], Bytes) -> conv_res(T, VbListOfBytes ++ Bytes); conv_res([], Bytes) -> Bytes. %% The only other value, then a positive integer, is infinity. do_get_rep(_Sz, _MibView, Count, Max, _, _Res, GbNumVBs, GbMaxVBs) when (is_integer(GbMaxVBs) andalso (GbNumVBs > GbMaxVBs)) -> ?vinfo("Max Get-BULK VBs limit (~w) exceeded (~w) when:" "~n Count: ~p" "~n Max: ~p", [GbMaxVBs, GbNumVBs, Count, Max]), {tooBig, 0, []}; do_get_rep(_Sz, _MibView, Max, Max, _, Res, _GbNumVBs, _GbMaxVBs) -> ?vt("do_get_rep -> done when: " "~n Res: ~p", [Res]), {noError, 0, conv_res(Res)}; do_get_rep(Sz, MibView, Count, Max, Varbinds, Res, GbNumVBs, GbMaxVBs) -> ?vt("do_get_rep -> entry when: " "~n Sz: ~p" "~n Count: ~p" "~n Res: ~w", [Sz, Count, Res]), case try_get_bulk(Sz, MibView, Varbinds, GbMaxVBs) of {noError, NextVarbinds, SizeLeft, Res2} -> ?vt("do_get_rep -> noError: " "~n SizeLeft: ~p" "~n Res2: ~p", [SizeLeft, Res2]), do_get_rep(SizeLeft, MibView, Count+1, Max, NextVarbinds, Res2 ++ Res, GbNumVBs + length(Varbinds), GbMaxVBs); {endOfMibView, _NextVarbinds, _SizeLeft, Res2} -> ?vt("do_get_rep -> endOfMibView: " "~n Res2: ~p", [Res2]), {noError, 0, conv_res(Res2 ++ Res)}; {ErrorStatus, Index} -> ?vtrace("do_get_rep -> done when error: " "~n ErrorStatus: ~p" "~n Index: ~p", [ErrorStatus, Index]), {ErrorStatus, Index, []} end. org_index_sort_vbs(Vbs) -> lists:keysort(#varbind.org_index, Vbs). try_get_bulk(Sz, MibView, Varbinds, GbMaxVBs) -> ?vt("try_get_bulk -> entry with" "~n Sz: ~w" "~n MibView: ~w" "~n Varbinds: ~w", [Sz, MibView, Varbinds]), case do_get_next(MibView, Varbinds, GbMaxVBs) of {noError, 0, UNextVarbinds} -> ?vt("try_get_bulk -> noError: " "~n UNextVarbinds: ~p", [UNextVarbinds]), NextVarbinds = org_index_sort_vbs(UNextVarbinds), case (catch enc_vbs(Sz, NextVarbinds)) of {error, Idx, Reason} -> user_err("failed encoding varbind ~w:~n~p", [Idx, Reason]), ?vtrace("try_get_bulk -> encode error: " "~n Idx: ~p" "~n Reason: ~p", [Idx, Reason]), {genErr, Idx}; {SizeLeft, Res} when is_integer(SizeLeft) andalso is_list(Res) -> ?vt("try get bulk -> encode ok: " "~n SizeLeft: ~w" "~n Res: ~w", [SizeLeft, Res]), {check_end_of_mibview(NextVarbinds), NextVarbinds, SizeLeft, Res}; Res when is_list(Res) -> ?vt("try get bulk -> Res: " "~n ~w", [Res]), {endOfMibView, [], 0, Res} end; {ErrorStatus, Index, _} -> ?vt("try_get_bulk -> error: " "~n ErrorStatus: ~p" "~n Index: ~p", [ErrorStatus, Index]), {ErrorStatus, Index} end. %% If all variables in this pass are endOfMibView, %% there is no reason to continue. check_end_of_mibview([#varbind{value = endOfMibView} | T]) -> check_end_of_mibview(T); check_end_of_mibview([]) -> endOfMibView; check_end_of_mibview(_) -> noError. %%%-------------------------------------------------- %%% 6. SET REQUEST %%%-------------------------------------------------- %% return: {ErrStatus, ErrIndex} %% where ErrIndex is an index in Varbinds list (not org_index (user-functions %% doesn't see org_index)). do_set(MibView, UnsortedVarbinds) -> SetModule = get(set_module), ?vtrace("set module: ~p",[SetModule]), apply(SetModule, do_set, [MibView, UnsortedVarbinds]). do_subagent_set(Arguments) -> SetModule = get(set_module), apply(SetModule, do_subagent_set, [Arguments]). %%%----------------------------------------------------------------- %%% 7. Misc functions %%%----------------------------------------------------------------- sort_varbindlist(Varbinds) -> snmpa_svbl:sort_varbindlist(get(mibserver), Varbinds). sa_split(SubagentVarbinds) -> snmpa_svbl:sa_split(SubagentVarbinds). make_response_pdu(ReqId, ErrStatus, ErrIndex, OrgVarbinds, _ResponseVarbinds) when ErrIndex =/= 0 -> #pdu{type = 'get-response', request_id = ReqId, error_status = ErrStatus, error_index = ErrIndex, varbinds = OrgVarbinds}; make_response_pdu(ReqId, ErrStatus, ErrIndex, _OrgVarbinds, ResponseVarbinds) -> #pdu{type = 'get-response', request_id = ReqId, error_status = ErrStatus, error_index = ErrIndex, varbinds = ResponseVarbinds}. %% Valid errormsgs for different operations. validate_err(consistency_check, {'EXIT', _Reason}, _) -> {genErr, 0}; validate_err(consistency_check, X, _) -> X; validate_err(is_set_ok, noError, _) -> noError; validate_err(is_set_ok, noCreation, _) -> noCreation; validate_err(is_set_ok, inconsistentValue, _) -> inconsistentValue; validate_err(is_set_ok, resourceUnavailable, _) -> resourceUnavailable; validate_err(is_set_ok, inconsistentName, _) -> inconsistentName; validate_err(is_set_ok, badValue, _) -> badValue; validate_err(is_set_ok, wrongValue, _) -> wrongValue; validate_err(is_set_ok, noSuchName, _) -> noSuchName; validate_err(is_set_ok, noAccess, _) -> noAccess; validate_err(is_set_ok, notWritable, _) -> notWritable; validate_err(is_set_ok, genErr, _) -> genErr; validate_err(is_set_ok, X, Mfa) -> user_err("~w with is_set_ok, returned: ~w. Using genErr.", [Mfa, X]), genErr; validate_err(set, commitFailed, _) -> commitFailed; validate_err(set, undoFailed, _) -> undoFailed; validate_err(set, noError, _) -> noError; validate_err(set, genErr, _) -> genErr; validate_err(set, X, Mfa) -> user_err("~w with set, returned: ~w. Using genErr.", [Mfa, X]), genErr; validate_err(undo, undoFailed, _) -> undoFailed; validate_err(undo, noError, _) -> noError; validate_err(undo, genErr, _) -> genErr; validate_err(undo, X, Mfa) -> user_err("~w with undo, returned: ~w. Using genErr.", [Mfa, X]), genErr; validate_err(table_is_set_ok, {Err, Idx}, Mfa) when is_integer(Idx) -> {validate_err(is_set_ok, Err, Mfa), Idx}; validate_err(table_is_set_ok, X, Mfa) -> user_err("~w with is_set_ok (table), returned: ~w. Using genErr.", [Mfa, X]), {genErr, 0}; validate_err(row_is_set_ok, {Err, Idx}, _) when is_integer(Idx) -> {Err, Idx}; validate_err(row_is_set_ok, {_Err, {false, BadCol}}, Mfa) -> user_err("~w with is_set_ok (table), returned bad column: " "~w. Using genErr.", [Mfa, BadCol]), {genErr, 0}; validate_err(table_undo, {Err, Idx}, Mfa) when is_integer(Idx) -> {validate_err(undo, Err, Mfa), Idx}; validate_err(table_undo, X, Mfa) -> user_err("~w with undo (table), returned: ~w. Using genErr.", [Mfa, X]), {genErr, 0}; validate_err(row_undo, {Err, Idx}, _) when is_integer(Idx) -> {Err, Idx}; validate_err(row_undo, {_Err, {false, BadCol}}, Mfa) -> user_err("~w with undo (table), returned bad column: " "~w. Using genErr.", [Mfa, BadCol]), {genErr, 0}; validate_err(table_set, {Err, Idx}, Mfa) when is_integer(Idx) -> {validate_err(set, Err, Mfa), Idx}; validate_err(table_set, X, Mfa) -> user_err("~w with set (table), returned: ~w. Using genErr.", [Mfa, X]), {genErr, 0}; validate_err(row_set, {Err, Idx}, _) when is_integer(Idx) -> {Err, Idx}; validate_err(row_set, {_Err, {false, BadCol}}, Mfa) -> user_err("~w with set (table), returned bad column: " "~w. Using genErr.", [Mfa, BadCol]), {genErr, 0}; validate_err(table_next, {Err, Idx}, _Mfa) when is_integer(Idx) -> {Err, Idx}; validate_err(table_next, {_Err, {false, BadCol}}, Mfa) -> user_err("~w with get_next, returned bad column: " "~w. Using genErr.", [Mfa, BadCol]), {genErr, 0}. validate_err(v2_to_v1, {V2Err, Index}) -> {v2err_to_v1err(V2Err), Index}; validate_err(v2_to_v1, _) -> {genErr, 0}. get_err({ErrC, ErrI, Vbs}) -> {get_err_i(ErrC), ErrI, Vbs}. get_err_i(noError) -> noError; get_err_i(tooBig) -> tooBig; % OTP-9700 get_err_i(ES) -> ?vtrace("convert ErrorStatus '~p' to 'genErr'", [ES]), genErr. v2err_to_v1err(noError) -> noError; v2err_to_v1err(noAccess) -> noSuchName; v2err_to_v1err(noCreation) -> noSuchName; v2err_to_v1err(notWritable) -> noSuchName; v2err_to_v1err(wrongLength) -> badValue; v2err_to_v1err(wrongEncoding) -> badValue; v2err_to_v1err(wrongType) -> badValue; v2err_to_v1err(wrongValue) -> badValue; v2err_to_v1err(inconsistentValue) -> badValue; v2err_to_v1err(inconsistentName) -> noSuchName; v2err_to_v1err(noSuchName) -> noSuchName; v2err_to_v1err(badValue) -> badValue; v2err_to_v1err(authorizationError) -> noSuchName; %% genErr | resourceUnavailable | undoFailed | commitFailed -> genErr v2err_to_v1err(_Error) -> genErr. %%----------------------------------------------------------------- %% transforms a (hopefully correct) return value ((perhaps) from a %% mib-function) to a typed and guaranteed correct return value. %% An incorrect return value is transformed to {error, genErr}. %% A correct return value is on the form: %% {error, <error-msg>} | {value, <variable-type>, <value>} %%----------------------------------------------------------------- make_value_a_correct_value({value, Val}, Asn1, Mfa) when Asn1#asn1_type.bertype =:= 'INTEGER' -> check_integer(Val, Asn1, Mfa); make_value_a_correct_value({value, Val}, Asn1, Mfa) when Asn1#asn1_type.bertype =:= 'Counter32' -> check_integer(Val, Asn1, Mfa); make_value_a_correct_value({value, Val}, Asn1, Mfa) when Asn1#asn1_type.bertype =:= 'Unsigned32' -> check_integer(Val, Asn1, Mfa); make_value_a_correct_value({value, Val}, Asn1, Mfa) when Asn1#asn1_type.bertype =:= 'TimeTicks' -> check_integer(Val, Asn1, Mfa); make_value_a_correct_value({value, Val}, Asn1, Mfa) when Asn1#asn1_type.bertype =:= 'Counter64' -> check_integer(Val, Asn1, Mfa); make_value_a_correct_value({value, Val}, Asn1, Mfa) when (Asn1#asn1_type.bertype =:= 'BITS') andalso is_list(Val) -> {value,Kibbles} = snmp_misc:assq(kibbles,Asn1#asn1_type.assocList), case snmp_misc:bits_to_int(Val,Kibbles) of error -> wrongValue(Val, Mfa); Int -> make_value_a_correct_value({value,Int},Asn1,Mfa) end; make_value_a_correct_value({value, Val}, Asn1, Mfa) when (Asn1#asn1_type.bertype =:= 'BITS') andalso is_integer(Val) -> {value,Kibbles} = snmp_misc:assq(kibbles,Asn1#asn1_type.assocList), {_Kibble,BitNo} = lists:last(Kibbles), case (1 bsl (BitNo+1)) of X when Val < X -> {value,'BITS',Val}; _Big -> wrongValue(Val, Mfa) end; make_value_a_correct_value({value, String}, #asn1_type{bertype = 'OCTET STRING', hi = Hi, lo = Lo}, Mfa) -> check_octet_string(String, Hi, Lo, Mfa, 'OCTET STRING'); make_value_a_correct_value({value, String}, #asn1_type{bertype = 'IpAddress', hi = Hi, lo = Lo}, Mfa) -> check_octet_string(String, Hi, Lo, Mfa, 'IpAddress'); make_value_a_correct_value({value, Oid}, #asn1_type{bertype = 'OBJECT IDENTIFIER'}, _Mfa) -> case snmp_misc:is_oid(Oid) of true -> {value, 'OBJECT IDENTIFIER', Oid}; _Else -> {error, wrongType} end; make_value_a_correct_value({value, Val}, Asn1, _Mfa) when Asn1#asn1_type.bertype =:= 'Opaque' -> if is_list(Val) -> {value, 'Opaque', Val}; true -> {error, wrongType} end; make_value_a_correct_value({noValue, noSuchObject}, _ASN1Type, _Mfa) -> {value, noValue, noSuchObject}; make_value_a_correct_value({noValue, noSuchInstance}, _ASN1Type, _Mfa) -> {value, noValue, noSuchInstance}; make_value_a_correct_value({noValue, noSuchName}, _ASN1Type, _Mfa) -> %% Transform this into a v2 value. It is converted to noSuchName %% later if it was v1. If it was v2, we use noSuchInstance. {value, noValue, noSuchInstance}; %% For backwards compatibility only - we really shouldn't allow this; %% it makes no sense to return unSpecified for a variable! But we did %% allow it previously. -- We transform unSpecified to noSuchInstance %% (OTP-3303). make_value_a_correct_value({noValue, unSpecified}, _ASN1Type, _Mfa) -> {value, noValue, noSuchInstance}; make_value_a_correct_value(genErr, _ASN1Type, _MFA) -> {error, genErr}; make_value_a_correct_value(_WrongVal, _ASN1Type, undef) -> {error, genErr}; make_value_a_correct_value(WrongVal, ASN1Type, Mfa) -> user_err("Got ~w from ~w. (~w) Using genErr", [WrongVal, Mfa, ASN1Type]), {error, genErr}. check_integer(Val, Asn1, Mfa) -> case Asn1#asn1_type.assocList of undefined -> check_size(Val, Asn1, Mfa); Alist -> case snmp_misc:assq(enums, Alist) of {value, Enums} -> check_enums(Val, Asn1, Enums, Mfa); false -> check_size(Val, Asn1, Mfa) end end. check_octet_string(String, Hi, Lo, Mfa, Type) -> Len = (catch length(String)), % it might not be a list case snmp_misc:is_string(String) of true when Lo =:= undefined -> {value, Type, String}; true when Len =< Hi, Len >= Lo -> {value, Type, String}; true -> wrongLength(String, Mfa); _Else -> wrongType(String, Mfa) end. check_size(Val, #asn1_type{lo = Lo, hi = Hi, bertype = Type}, Mfa) when is_integer(Val) -> ?vtrace("check size of integer: " "~n Value: ~p" "~n Upper limit: ~p" "~n Lower limit: ~p" "~n BER-type: ~p", [Val,Hi,Lo,Type]), if (Lo =:= undefined) andalso (Hi =:= undefined) -> {value, Type, Val}; (Lo =:= undefined) andalso is_integer(Hi) andalso (Val =< Hi) -> {value, Type, Val}; is_integer(Lo) andalso (Val >= Lo) andalso (Hi =:= undefined) -> {value, Type, Val}; is_integer(Lo) andalso is_integer(Hi) andalso (Val >= Lo) andalso (Val =< Hi) -> {value, Type, Val}; true -> wrongValue(Val, Mfa) end; check_size(Val, _, Mfa) -> wrongType(Val, Mfa). check_enums(Val, Asn1, Enums, Mfa) -> Association = if is_integer(Val) -> lists:keysearch(Val, 2, Enums); is_atom(Val) -> lists:keysearch(Val, 1, Enums); true -> {error, wrongType} end, case Association of {value, {_AliasIntName, Val2}} -> {value, Asn1#asn1_type.bertype, Val2}; false -> wrongValue(Val, Mfa); {error, wrongType} -> wrongType(Val, Mfa) end. wrongLength(Val, Mfa) -> report_err(Val, Mfa, wrongLength). wrongValue(Val, Mfa) -> report_err(Val, Mfa, wrongValue). wrongType(Val, Mfa) -> report_err(Val, Mfa, wrongType). report_err(_Val, undef, Err) -> {error, Err}; report_err(Val, Mfa, Err) -> user_err("Got ~p from ~w. Using ~w", [Val, Mfa, Err]), {error, Err}. is_valid_pdu_type('get-request') -> true; is_valid_pdu_type('get-next-request') -> true; is_valid_pdu_type('get-bulk-request') -> true; is_valid_pdu_type('set-request') -> true; is_valid_pdu_type(_) -> false. get_pdu_data() -> {get(net_if_data), get(snmp_request_id), get(snmp_address), get(snmp_community), get(snmp_context)}. put_pdu_data({Extra, ReqId, Address, Community, ContextName}) -> {put(net_if_data, Extra), put(snmp_address, Address), put(snmp_request_id, ReqId), put(snmp_community, Community), put(snmp_context, ContextName)}. tr_var(Oid, Idx) -> case snmp_misc:is_oid(Oid) of true -> {#varbind{oid = Oid, value = unSpecified, org_index = Idx}, Idx+1}; false -> throw({error, {bad_oid, Oid}}) end. tr_varbind(#varbind{value = Value}) -> Value. mapfoldl(F, Eas, Accu0, [Hd|Tail]) -> {R,Accu1} = apply(F, [Hd,Accu0|Eas]), {Accu2,Rs} = mapfoldl(F, Eas, Accu1, Tail), {Accu2,[R|Rs]}; mapfoldl(_F, _Eas, Accu, []) -> {Accu,[]}. %%----------------------------------------------------------------- %% Runtime debugging of the agent. %%----------------------------------------------------------------- dbg_apply(M,F,A) -> case get(verbosity) of silence -> apply(M,F,A); _ -> ?vlog("~n apply: ~w,~w,~p~n", [M,F,A]), Res = (catch apply(M,F,A)), case Res of {'EXIT', Reason} -> ?vinfo("Call to: " "~n Module: ~p" "~n Function: ~p" "~n Args: ~p" "~n" "~nresulted in an exit" "~n" "~n ~p", [M, F, A, Reason]); _ -> ?vlog("~n returned: ~p", [Res]) end, Res end. short_name(none) -> ma; short_name(_Pid) -> sa. worker_short_name(ma) -> maw; worker_short_name(_) -> saw. trap_sender_short_name(ma) -> mats; trap_sender_short_name(_) -> sats. pdu_handler_short_name(ma) -> maph; pdu_handler_short_name(_) -> saph. mib_server_verbosity(Pid,Verbosity) when is_pid(Pid) -> snmpa_mib:verbosity(Pid,Verbosity); mib_server_verbosity(_Pid,_Verbosity) -> ok. note_store_verbosity(Pid,Verbosity) when is_pid(Pid) -> snmp_note_store:verbosity(Pid,Verbosity); note_store_verbosity(_Pid,_Verbosity) -> ok. subagents_verbosity(V) -> subagents_verbosity(catch snmpa_mib:info(get(mibserver),subagents),V). subagents_verbosity([],_V) -> ok; subagents_verbosity([{Pid,_Oid}|T],V) -> catch verbosity(Pid,V), %% On the agent catch verbosity(Pid,{subagents,V}), %% and it's subagents subagents_verbosity(T,V); subagents_verbosity(_,_V) -> ok. %% --------------------------------------------------------------------- local_engine_id(#state{type = master_agent}) -> ?DEFAULT_LOCAL_ENGINE_ID; local_engine_id(_) -> %% subagent - %% we don't need this now, eventually the trap send %% request will reach the master-agent and then it %% will look up the proper engine id. ignore. %% --------------------------------------------------------------------- handle_get_log_type(#state{net_if_mod = Mod}) when Mod =/= undefined -> case (catch Mod:get_log_type(get(net_if))) of {'EXIT', _} -> {error, not_supported}; Else -> Else end; handle_get_log_type(_) -> {error, not_supported}. handle_set_log_type(#state{net_if_mod = Mod}, NewType) when Mod =/= undefined -> case (catch Mod:set_log_type(get(net_if), NewType)) of {'EXIT', _} -> {error, not_supported}; Else -> Else end; handle_set_log_type(_, _) -> {error, not_supported}. handle_get_request_limit(#state{net_if_mod = Mod}) when Mod =/= undefined -> case (catch Mod:get_request_limit(get(net_if))) of {'EXIT', _} -> {error, not_supported}; Else -> Else end; handle_get_request_limit(_) -> {error, not_supported}. handle_set_request_limit(#state{net_if_mod = Mod}, NewLimit) when Mod =/= undefined -> case (catch Mod:set_request_limit(get(net_if), NewLimit)) of {'EXIT', _} -> {error, not_supported}; Else -> Else end; handle_set_request_limit(_, _) -> {error, not_supported}. agent_info(#state{worker = W, set_worker = SW}) -> case (catch get_agent_info(W, SW)) of Info when is_list(Info) -> Info; E -> [{error, E}] end. get_agent_info(W, SW) -> MASz = proc_mem(self()), WSz = proc_mem(W), SWSz = proc_mem(SW), ATSz = tab_mem(snmp_agent_table), CCSz = tab_mem(snmp_community_cache), VacmSz = tab_mem(snmpa_vacm), [{process_memory, [{master_agent, MASz}, {worker, WSz}, {set_worker, SWSz}]}, {db_memory, [{agent, ATSz}, {community_cache, CCSz}, {vacm, VacmSz}]}]. 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. tab_mem(T) -> case (catch ets:info(T, memory)) of Sz when is_integer(Sz) -> Sz; _ -> undefined end. net_if_info(#state{net_if_mod = Mod}) when Mod =/= undefined -> case (catch Mod:info(get(net_if))) of Info when is_list(Info) -> Info; E -> [{error, E}] end; net_if_info(_) -> %% This could be a result of a code upgrade %% Make best effert [{process_memory, proc_mem(get(net_if))}]. note_store_info(#state{note_store = NS}) -> case (catch snmp_note_store:info(NS)) of Info when is_list(Info) -> Info; E -> [{error, E}] end. symbolic_store_info() -> case (catch snmpa_symbolic_store:info()) of Info when is_list(Info) -> Info; E -> [{error, E}] end. local_db_info() -> case (catch snmpa_local_db:info()) of Info when is_list(Info) -> Info; E -> [{error, E}] end. mib_server_info() -> case (catch snmpa_mib:info(get(mibserver))) of Info when is_list(Info) -> Info; E -> [{error, E}] end. get_stats_counters() -> Counters = snmpa_mpd:counters(), get_stats_counters(Counters, []). get_stats_counters([], Acc) -> lists:reverse(Acc); get_stats_counters([Counter|Counters], Acc) -> case ets:lookup(snmp_agent_table, Counter) of [CounterVal] -> get_stats_counters(Counters, [CounterVal|Acc]); _ -> get_stats_counters(Counters, Acc) end. %% --------------------------------------------------------------------- %% info_msg(F, A) -> %% ?snmpa_info(F, A). warning_msg(F, A) -> ?snmpa_warning(F, A). error_msg(F, A) -> ?snmpa_error(F, A). %% --- config_err(F, A) -> snmpa_error:config_err(F, A). user_err(F, A) -> snmpa_error:user_err(F, A). %% --------------------------------------------------------------------- maybe_call(Server, Req) -> case (wis(Server) =:= self()) of false -> call(Server, Req); true -> Server ! Req end. call(Server, Req) -> gen_server:call(Server, Req, infinity). cast(Server, Msg) -> gen_server:cast(Server, Msg). %% --------------------------------------------------------------------- get_verbosity(Opts) -> get_option(verbosity, Opts, ?default_verbosity). get_mibs(Opts) -> get_option(mibs, Opts, []). get_mib_storage(Opts) -> get_option(mib_storage, Opts, ets). get_set_mechanism(Opts) -> get_option(set_mechanism, Opts, snmpa_set). get_authentication_service(Opts) -> get_option(authentication_service, Opts, snmpa_acm). get_multi_threaded(Opts) -> get_option(multi_threaded, Opts, false). get_versions(Opts) -> get_option(versions, Opts, [v1,v2,v3]). get_gb_max_vbs(Opts) -> get_option(gb_max_vbs, Opts, infinity). get_note_store_opt(Opts) -> get_option(note_store, Opts, []). get_net_if_opt(Opts) -> get_option(net_if, Opts, []). get_net_if_verbosity(Opts) -> get_option(verbosity, Opts, silence). get_net_if_module(Opts) -> get_option(module, Opts, snmpa_net_if). get_net_if_options(Opts) -> get_option(options, Opts, []). net_if_verbosity(Pid,Verbosity) when is_pid(Pid) -> Pid ! {verbosity,Verbosity}; net_if_verbosity(_Pid,_Verbosity) -> ok. get_option(Key, Opts, Default) -> snmp_misc:get_option(Key, Opts, Default). %% --------------------------------------------------------------------- %% i(F) -> %% i(F, []). %% i(F, A) -> %% io:format("~p: " ++ F ++ "~n", [?MODULE|A]).