diff options
author | Erlang/OTP <[email protected]> | 2009-11-20 14:54:40 +0000 |
---|---|---|
committer | Erlang/OTP <[email protected]> | 2009-11-20 14:54:40 +0000 |
commit | 84adefa331c4159d432d22840663c38f155cd4c1 (patch) | |
tree | bff9a9c66adda4df2106dfd0e5c053ab182a12bd /lib/snmp/src/agent/snmpa_trap.erl | |
download | otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.gz otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.bz2 otp-84adefa331c4159d432d22840663c38f155cd4c1.zip |
The R13B03 release.OTP_R13B03
Diffstat (limited to 'lib/snmp/src/agent/snmpa_trap.erl')
-rw-r--r-- | lib/snmp/src/agent/snmpa_trap.erl | 1051 |
1 files changed, 1051 insertions, 0 deletions
diff --git a/lib/snmp/src/agent/snmpa_trap.erl b/lib/snmp/src/agent/snmpa_trap.erl new file mode 100644 index 0000000000..b1096b1135 --- /dev/null +++ b/lib/snmp/src/agent/snmpa_trap.erl @@ -0,0 +1,1051 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1996-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +-module(snmpa_trap). + +%%%----------------------------------------------------------------- +%%% This module takes care of all trap(notification handling. +%%%----------------------------------------------------------------- +%% External exports +-export([construct_trap/2, + try_initialise_vars/2, send_trap/6]). +-export([send_discovery/5]). + +%% Internal exports +-export([init_v2_inform/9, init_v3_inform/9, send_inform/6]). +-export([init_discovery_inform/12, send_discovery_inform/5]). + +-include("snmp_types.hrl"). +-include("SNMPv2-MIB.hrl"). +-include("SNMPv2-TM.hrl"). +-include("SNMPv2-TC.hrl"). +-include("SNMP-FRAMEWORK-MIB.hrl"). +-include("SNMP-TARGET-MIB.hrl"). +-define(enterpriseSpecific, 6). + + +-define(VMODULE,"TRAP"). +-include("snmp_verbosity.hrl"). + + +%%----------------------------------------------------------------- +%% Trap mechanism +%% ============== +%% Distributed subagent (dSA) case +%% The MIB with the TRAP-TYPE macro is loaded in dSA. This means +%% that dSA has info on all variables defined in the TRAP-TYPE, +%% even though some variables may be located in other SA:s (or +%% in the MA). Other variables that may be sent in the trap, +%% must be known by either the dSA, or some of its parent agents +%% (e.g. the master agent), if the variable should be referred +%% to by symbolic name. It is however possible to send other +%% variables as well, but then the entire OID must be provided. +%% The dSA locates the asn1 type, oid and value for as many +%% variables as possible. This information, together with the +%% variables for which the type, value or oid isn't known, is +%% sent to the dSA's parent. This agent performs the same +%% operation, and so on, until eventually the MA will receive the +%% info. The MA then fills in the gaps, and at this point all +%% oids and types must be known, otherwise an error is signalled, +%% and the opertaion is aborted. For the unknown values for some +%% oids, a get-operation is performed by the MA. This will +%% retreive the missing values. +%% At this point, all oid, types and values are known, so the MA +%% can distribute the traps according to the information in the +%% internal tables. +%% +%% Local subagent (lSA) case +%% This case is similar to the case above. +%% +%% Master agent (MA) case +%% This case is similar to the case above. +%% +%% NOTE: All trap forwarding between agents is made asynchronously. +%% +%% dSA: Distributed SA (the #trap is loaded here) +%% nSA: [many] SAs between dSA and MA +%% MA: Master Agent. (all trap info (destiniations is here)) +%% 1) application decides to send a trap. +%% 2) dSA calls send_trap which initialises vars +%% 3) dSA sends all to nSA +%% 4) nSA tries to map symbolic names to oids and find the types +%% of all variableoids with a value (and no type). +%% 5) nSA sends all to (n-1)SA +%% 6) MA tries to initialise vars +%% 7) MA makes a trappdu, and sends it to all destination. +%% +%% Problems with this implementation +%% ================================= +%% It's ok to send {Oid, Value} but not just Oid. (it should be for +%% any Oid) +%% It's ok to send {Name, Value} but not just Name. (it should be +%% for Names in the hierarchy) +%% This approach might be too flexible; will people use it? +%% *NOTE* +%% Therefore, in this version we *do not* allow extra variables +%% in traps. +%% *YES* In _this_ version we do. +%%----------------------------------------------------------------- + +%%----------------------------------------------------------------- +%% Func: construct_trap/2 +%% Args: Trap is an atom +%% Varbinds is a list of +%% {Variable, Value} | {SymbolicTableCol, RowIndex, Value} +%% where Variable is an atom or an OID, +%% where RowIndex is the indexes for the row. +%% We don't check the RowIndex. +%% Purpose: This is the initially-called function. It is called +%% by the agent that found out that a trap should be +%% sent. +%% Initialize as many variables as possible. +%% Returns: {ok, TrapRecord, <list of Var>} | error +%% where Var is returned from initiate_vars. +%% NOTE: Executed at the inital SA +%%----------------------------------------------------------------- +construct_trap(Trap, Varbinds) -> + ?vdebug("construct_trap -> entry with" + "~n Trap: ~p", [Trap]), + case snmpa_symbolic_store:get_notification(Trap) of + undefined -> + user_err("construct_trap got undef Trap: ~w" , [Trap]), + error; + + {value, #trap{oidobjects = ListOfVars} = TRec} -> + ?vdebug("construct_trap -> trap" + "~n ~p", [TRec]), + OidVbs = [alias_to_oid(Vb) || Vb <- Varbinds], + LV = initiate_vars(ListOfVars, OidVbs), + InitiatedVars = try_initialise_vars(get(mibserver), LV), + {ok, TRec, InitiatedVars}; + + {value, #notification{oidobjects = ListOfVars} = NRec} -> + ?vdebug("construct_trap -> notification" + "~n ~p", [NRec]), + OidVbs = [alias_to_oid(Vb) || Vb <- Varbinds], + LV = initiate_vars(ListOfVars, OidVbs), + InitiatedVars = try_initialise_vars(get(mibserver), LV), + {ok, NRec, InitiatedVars} + end. + +alias_to_oid({Alias, Val}) when is_atom(Alias) -> + case snmpa_symbolic_store:aliasname_to_oid(Alias) of + {value, Oid} -> {lists:append(Oid, [0]), {value, Val}}; + _ -> {Alias, {value, Val}} + end; +alias_to_oid({Alias, RowIndex, Val}) when is_atom(Alias) -> + case snmpa_symbolic_store:aliasname_to_oid(Alias) of + {value, Oid} -> {lists:append(Oid, RowIndex), {value, Val}}; + _ -> {Alias, RowIndex, {value, Val}} + end; +alias_to_oid({Oid, Val}) -> {Oid, {value, Val}}. + + +%%----------------------------------------------------------------- +%% Func: initiate_vars/2 +%% Args: ListOfVars is a list of {Oid, #asn1_type} +%% Varbinds is a list of +%% {VariableOid, Value} | +%% {VariableAtom, Value} | +%% {TableColAtom, RowIndex, Value} +%% Purpose: For each variable specified in the TRAP-TYPE macro +%% (each in ListOfVars), check if it's got a value given +%% in the Varbinds list. +%% For each Oid: +%% 1) It has corresponding VariableOid. Use Value. +%% 2) No corresponding VariableOid. No value. +%% Returns: A list of +%% {VariableOid, #asn1_type, Value} | +%% {VariableOid, #asn1_type} | +%% {VariableOid, Value} | +%% {VariableAtom, Value} | +%% {TableColAtom, RowIndex, Value} +%% NOTE: Executed at the inital SA +%%----------------------------------------------------------------- +initiate_vars([{Oid, Asn1Type} | T], Varbinds) -> + case delete_oid_from_varbinds(Oid, Varbinds) of + {undefined, _, _} -> + [{Oid, Asn1Type} | initiate_vars(T, Varbinds)]; + {Value, VarOid, RestOfVarbinds} -> + [{VarOid, Asn1Type, Value} | initiate_vars(T, RestOfVarbinds)] + end; +initiate_vars([], Varbinds) -> + Varbinds. + +delete_oid_from_varbinds(Oid, [{VarOid, Value} | T]) -> + case lists:prefix(Oid, VarOid) of + true -> + {Value, VarOid, T}; + _ -> + {Value2, VarOid2, T2} = delete_oid_from_varbinds(Oid, T), + {Value2, VarOid2, [{VarOid, Value} | T2]} + end; +delete_oid_from_varbinds(Oid, [H | T]) -> + {Value, VarOid, T2} = delete_oid_from_varbinds(Oid, T), + {Value, VarOid, [H | T2]}; +delete_oid_from_varbinds(_Oid, []) -> {undefined, undefined, []}. + +%%----------------------------------------------------------------- +%% Func: try_initialise_vars(Mib, Varbinds) +%% Args: Mib is the local mib process +%% Varbinds is a list returned from initiate_vars. +%% Purpose: Try to initialise uninitialised vars. +%% Returns: see initiate_vars +%% NOTE: Executed at the intermediate SAs +%%----------------------------------------------------------------- +try_initialise_vars(Mib, Varbinds) -> + V = try_map_symbolic(Varbinds), + try_find_type(V, Mib). + +%%----------------------------------------------------------------- +%% Func: try_map_symbolic/1 +%% Args: Varbinds is a list returned from initiate_vars. +%% Purpose: Try to map symbolic name to oid for the +%% symbolic names left in the Varbinds list. +%% Returns: see initiate_vars. +%% NOTE: Executed at the intermediate SAs +%%----------------------------------------------------------------- +try_map_symbolic([Varbind | Varbinds]) -> + [localise_oid(Varbind) | try_map_symbolic(Varbinds)]; +try_map_symbolic([]) -> []. + +localise_oid({VariableName, Value}) when is_atom(VariableName) -> + alias_to_oid({VariableName, Value}); +localise_oid({VariableName, RowIndex, Value}) when is_atom(VariableName) -> + alias_to_oid({VariableName, RowIndex, Value}); +localise_oid(X) -> X. + +%%----------------------------------------------------------------- +%% Func: try_find_type/2 +%% Args: Varbinds is a list returned from initiate_vars. +%% Mib is a ref to the Mib process corresponding to +%% this agent. +%% Purpose: Try to find the type for each variableoid with a value +%% but no type. +%% Returns: see initiate_vars. +%% NOTE: Executed at the intermediate SAs +%%----------------------------------------------------------------- +try_find_type([Varbind | Varbinds], Mib) -> + [localise_type(Varbind, Mib) | try_find_type(Varbinds, Mib)]; +try_find_type([], _) -> []. + +localise_type({VariableOid, Type}, _Mib) + when is_list(VariableOid) andalso is_record(Type, asn1_type) -> + {VariableOid, Type}; +localise_type({VariableOid, Value}, Mib) when is_list(VariableOid) -> + case snmpa_mib:lookup(Mib, VariableOid) of + {variable, ME} -> + {VariableOid, ME#me.asn1_type, Value}; + {table_column, ME, _} -> + {VariableOid, ME#me.asn1_type, Value}; + _ -> + {VariableOid, Value} + end; +localise_type(X, _) -> X. + +%%----------------------------------------------------------------- +%% Func: make_v1_trap_pdu/4 +%% Args: Enterprise = oid() +%% Specific = integer() +%% Varbinds is as returned from initiate_vars +%% (but only {Oid, Type[, Value} permitted) +%% SysUpTime = integer() +%% Purpose: Make a #trappdu +%% Checks the Varbinds to see that no symbolic names are +%% present, and that each var has a type. Performs a get +%% to find any missing value. +%% Returns: {#trappdu, [byte()] | error +%% Fails: yes +%% NOTE: Executed at the MA +%%----------------------------------------------------------------- +make_v1_trap_pdu(Enterprise, Specific, VarbindList, SysUpTime) -> + {Enterp,Generic,Spec} = + case Enterprise of + ?snmp -> + {sys_object_id(),Specific,0}; + _ -> + {Enterprise,?enterpriseSpecific,Specific} + end, + {value, AgentIp} = snmp_framework_mib:intAgentIpAddress(get), + #trappdu{enterprise = Enterp, + agent_addr = AgentIp, + generic_trap = Generic, + specific_trap = Spec, + time_stamp = SysUpTime, + varbinds = VarbindList}. + +make_discovery_pdu(Vbs) -> + #pdu{type = 'inform-request', + request_id = snmpa_mpd:generate_req_id(), + error_status = noError, + error_index = 0, + varbinds = Vbs}. + +make_v2_notif_pdu(Vbs, Type) -> + #pdu{type = Type, + request_id = snmpa_mpd:generate_req_id(), + error_status = noError, + error_index = 0, + varbinds = Vbs}. + +make_varbind_list(Varbinds) -> + {VariablesWithValueAndType, VariablesWithType} = + split_variables( order(Varbinds) ), + V = get_values(VariablesWithType), + Vars = lists:append([V, VariablesWithValueAndType]), + [make_varbind(Var) || Var <- unorder(lists:keysort(1, Vars))]. + + +%%----------------------------------------------------------------- +%% Func: send_trap/6 +%% Args: TrapRec = #trap | #notification +%% NotifyName = string() +%% ContextName = string() +%% Recv = no_receiver | {Ref, Receiver} +%% Receiver = pid() | atom() | {M,F,A} +%% Vbs = [varbind()] +%% NetIf = pid() +%% Purpose: Default trap sending function. +%% Sends the trap to the targets pointed out by NotifyName. +%% If NotifyName is ""; the normal procedure defined in +%% SNMP-NOTIFICATION-MIB is used, i.e. the trap is sent to +%% all managers. +%% Otherwise, the NotifyName is used to find an entry in the +%% SnmpNotifyTable which define how to send the notification +%% (as an Inform or a Trap), and to select targets from +%% SnmpTargetAddrTable (using the Tag). +%%----------------------------------------------------------------- +send_trap(TrapRec, NotifyName, ContextName, Recv, Vbs, NetIf) -> + (catch do_send_trap(TrapRec, NotifyName, ContextName, Recv, Vbs, NetIf)). + +do_send_trap(TrapRec, NotifyName, ContextName, Recv, Vbs, NetIf) -> + VarbindList = make_varbind_list(Vbs), + Dests = find_dests(NotifyName), + send_trap_pdus(Dests, ContextName, {TrapRec, VarbindList}, [], [], [], + Recv, NetIf). + +send_discovery(TargetName, Record, ContextName, Vbs, NetIf) -> + case find_dest(TargetName) of + {ok, Dest} -> + send_discovery_pdu(Dest, Record, ContextName, Vbs, NetIf); + Error -> + Error + end. + + +get_values(VariablesWithType) -> + {Order, Varbinds} = extract_order(VariablesWithType, 1), + case snmpa_agent:do_get(snmpa_acm:get_root_mib_view(), Varbinds, true) of + {noError, _, NewVarbinds} -> + %% NewVarbinds is the result of: + %% first a reverse, then a sort on the oid field and finally + %% a reverse during the get-processing so we need to re-sort + %% on the org_index field again before contract-order + NewVarbinds1 = lists:keysort(#varbind.org_index, NewVarbinds), + contract_order(Order, NewVarbinds1); + {ErrorStatus, ErrorIndex, _} -> + user_err("snmpa_trap: get operation failed: ~w" + "~n at ~w" + "~n in ~w", + [ErrorStatus, ErrorIndex, Varbinds]), + throw(error) + end. + +make_varbind(Varbind) when is_record(Varbind, varbind) -> + Varbind; +make_varbind({VarOid, ASN1Type, Value}) -> + case snmpa_agent:make_value_a_correct_value(Value, ASN1Type, undef) of + {value, Type, Val} -> + #varbind{oid = VarOid, variabletype = Type, value = Val}; + {error, Reason} -> + user_err("snmpa_trap: Invalid value: ~w" + "~n Oid: ~w" + "~n Val: ~w" + "~n Type: ~w", + [Reason, VarOid, Value, ASN1Type]), + throw(error) + end. + +order(Varbinds) -> + order(Varbinds, 1). + +order([H | T], No) -> [{No, H} | order(T, No + 1)]; +order([], _) -> []. + +unorder([{_No, H} | T]) -> [H | unorder(T)]; +unorder([]) -> []. + +extract_order([{No, {VarOid, _Type}} | T], Index) -> + {Order, V} = extract_order(T, Index+1), + {[No | Order], [#varbind{oid = VarOid, org_index = Index} | V]}; +extract_order([], _) -> {[], []}. + +contract_order([No | Order], [Varbind | T]) -> + [{No, Varbind} | contract_order(Order, T)]; +contract_order([], []) -> + []. + +split_variables([{No, {VarOid, Type, Val}} | T]) when is_list(VarOid) -> + {A, B} = split_variables(T), + {[{No, {VarOid, Type, Val}} | A], B}; +split_variables([{No, {VarOid, Type}} | T]) + when is_list(VarOid) andalso is_record(Type, asn1_type) -> + {A, B} = split_variables(T), + {A, [{No, {VarOid, Type}} | B]}; +split_variables([{_No, {VarName, Value}} | _T]) -> + user_err("snmpa_trap: Undefined variable ~w (~w)", [VarName, Value]), + throw(error); +split_variables([{_No, {VarName, RowIndex, Value}} | _T]) -> + user_err("snmpa_trap: Undefined variable ~w ~w (~w)", + [VarName, RowIndex, Value]), + throw(error); +split_variables([]) -> {[], []}. + + +%%----------------------------------------------------------------- +%% Func: find_dests(NotifyName) -> +%% [{DestAddr, TargetName, TargetParams, NotifyType}] +%% Types: NotifyType = string() +%% DestAddr = {TDomain, TAddr} +%% TargetName = string() +%% TargetParams = {MpModel, SecModel, SecName, SecLevel} +%% NotifyType = trap | {inform, Timeout, Retry} +%% Returns: A list of all Destination addresses for this community. +%% NOTE: This function is executed in the master agent's context +%%----------------------------------------------------------------- +find_dests("") -> + snmp_notification_mib:get_targets(); +find_dests(NotifyName) -> + case snmp_notification_mib:get_targets(NotifyName) of + [] -> + ?vlog("No dests found for snmpNotifyName: ~p",[NotifyName]), + []; + Dests -> + Dests + end. + +find_dest(TargetName) -> + AddrCols = [?snmpTargetAddrTDomain, + ?snmpTargetAddrTAddress, + ?snmpTargetAddrTimeout, + ?snmpTargetAddrRetryCount, + ?snmpTargetAddrParams, + ?snmpTargetAddrRowStatus], + case snmp_target_mib:snmpTargetAddrTable(get, TargetName, AddrCols) of + [{value, TDomain}, + {value, TAddress}, + {value, Timeout}, + {value, RetryCount}, + {value, Params}, + {value, ?'RowStatus_active'}] -> + ?vtrace("find_dest -> found snmpTargetAddrTable info:" + "~n TDomain: ~p" + "~n TAddress: ~p" + "~n Timeout: ~p" + "~n RetryCount: ~p" + "~n Params: ~p", + [TDomain, TAddress, Timeout, RetryCount, Params]), + ParmCols = [?snmpTargetParamsMPModel, + ?snmpTargetParamsSecurityModel, + ?snmpTargetParamsSecurityName, + ?snmpTargetParamsSecurityLevel, + ?snmpTargetParamsRowStatus], + case snmp_target_mib:snmpTargetParamsTable(get, Params, ParmCols) of + [{value, ?MP_V3}, + {value, SecModel}, + {value, SecName}, + {value, SecLevel}, + {value, ?'RowStatus_active'}] -> + ?vtrace("find_dest -> found snmpTargetParamsTable info:" + "~n SecModel: ~p" + "~n SecName: ~p" + "~n SecLevel: ~p", + [SecModel, SecName, SecLevel]), + DestAddr = {TDomain, TAddress}, + TargetParams = {SecModel, SecName, SecLevel}, + Val = {DestAddr, TargetName, TargetParams, Timeout, RetryCount}, + {ok, Val}; + [{value, ?MP_V3}, + {value, _SecModel}, + {value, _SecName}, + {value, _SecLevel}, + {value, RowStatus}] -> + {error, {invalid_RowStatus, RowStatus, snmpTargetParamsTable}}; + [{value, MpModel}, + {value, _SecModel}, + {value, _SecName}, + {value, _SecLevel}, + {value, ?'RowStatus_active'}] -> + {error, {invalid_MpModel, MpModel, snmpTargetParamsTable}}; + [{value, _MpModel}, + {value, _SecModel}, + {value, _SecName}, + {value, _SecLevel}, + {value, RowStatus}] -> + {error, {invalid_RowStatus, RowStatus, snmpTargetParamsTable}}; + Bad -> + ?vlog("find_dest -> " + "could not find snmpTargetParamsTable info: " + "~n Bad: ~p", [Bad]), + {error, {not_found, snmpTargetParamsTable}} + end; + + [{value, _TDomain}, + {value, _TAddress}, + {value, _Timeout}, + {value, _RetryCount}, + {value, _Params}, + {value, RowStatus}] -> + {error, {invalid_RowStatus, RowStatus, snmpTargetAddrTable}}; + _ -> + {error, {not_found, snmpTargetAddrTable}} + end. + +send_discovery_pdu({Dest, TargetName, {SecModel, SecName, SecLevel}, + Timeout, Retry}, + Record, ContextName, Vbs, NetIf) -> + ?vdebug("send_discovery_pdu -> entry with " + "~n Destination address: ~p" + "~n Target name: ~p" + "~n Sec model: ~p" + "~n Sec name: ~p" + "~n Sec level: ~p" + "~n Timeout: ~p" + "~n Retry: ~p" + "~n Record: ~p" + "~n ContextName: ~p", + [Dest, TargetName, SecModel, SecName, SecLevel, + Timeout, Retry, Record, ContextName]), + case get_mib_view(SecModel, SecName, SecLevel, ContextName) of + {ok, MibView} -> + case check_all_varbinds(Record, Vbs, MibView) of + true -> + SysUpTime = snmp_standard_mib:sys_up_time(), + send_discovery_pdu(Record, Dest, Vbs, + SecModel, SecName, SecLevel, + TargetName, ContextName, + Timeout, Retry, + SysUpTime, NetIf); + false -> + {error, {mibview_validation_failed, Vbs, MibView}} + end; + {discarded, Reason} -> + {error, {failed_get_mibview, Reason}} + end. + +send_discovery_pdu(Record, Dest, Vbs, + SecModel, SecName, SecLevel, TargetName, + ContextName, Timeout, Retry, SysUpTime, NetIf) -> + {_Oid, IVbs} = mk_v2_trap(Record, Vbs, SysUpTime), % v2 refers to SMIv2; + Sender = proc_lib:spawn_link(?MODULE, init_discovery_inform, + [self(), + Dest, + SecModel, SecName, SecLevel, TargetName, + ContextName, + Timeout, Retry, + IVbs, NetIf, + get(verbosity)]), + {ok, Sender, SecLevel}. + +init_discovery_inform(Parent, + Dest, + SecModel, SecName, SecLevel, TargetName, + ContextName, Timeout, Retry, Vbs, NetIf, Verbosity) -> + put(verbosity, Verbosity), + put(sname, madis), + Pdu = make_discovery_pdu(Vbs), + ContextEngineId = snmp_framework_mib:get_engine_id(), + SecLevelFlag = mk_flag(SecLevel), + SecData = {SecModel, SecName, SecLevelFlag, TargetName}, + MsgData = {SecData, ContextEngineId, ContextName}, + Msg = {send_discovery, Pdu, MsgData, Dest, self()}, + ?MODULE:send_discovery_inform(Parent, Timeout*10, Retry, Msg, NetIf). + +%% note_timeout(Timeout, Retry) +%% when ((is_integer(Timeout) andalso (Timeout > 0)) andalso +%% (is_integer(Retry) andalso (Retry > 0))) +%% note_timeout(Timeout*10, Retry, 0); +%% note_timeout(Timeout, Retry) +%% when (is_integer(Timeout) andalso (Timeout > 0)) -> +%% Timeout*10. + +%% note_timeout(_Timeout, -1, NoteTimeout) -> +%% NoteTimeout; +%% note_timeout(Timeout, Retry, NoteTimeout) when -> +%% note_timeout(Timeout*2, Retry-1, NoteTimeout+Timeout). + +send_discovery_inform(Parent, _Timeout, -1, _Msg, _NetIf) -> + Parent ! {discovery_response, {error, timeout}}; +send_discovery_inform(Parent, Timeout, Retry, Msg, NetIf) -> + NetIf ! Msg, + receive + {snmp_discovery_response_received, Pdu, undefined} -> + ?vtrace("received stage 2 discovery response: " + "~n Pdu: ~p", [Pdu]), + Parent ! {discovery_response, {ok, Pdu}}; + {snmp_discovery_response_received, Pdu, ManagerEngineId} -> + ?vtrace("received stage 1 discovery response: " + "~n Pdu: ~p" + "~n ManagerEngineId: ~p", [Pdu, ManagerEngineId]), + Parent ! {discovery_response, {ok, Pdu, ManagerEngineId}} + after + Timeout -> + ?MODULE:send_discovery_inform(Parent, + Timeout*2, Retry-1, Msg, NetIf) + end. + + +%%----------------------------------------------------------------- +%% NOTE: This function is executed in the master agent's context +%% For each target, check if it has access to the objects in the +%% notification, determine which message version (v1, v2c or v3) +%% should be used for the target, and determine the message +%% specific parameters to be used. +%%----------------------------------------------------------------- +send_trap_pdus([{DestAddr, TargetName, {MpModel, SecModel, SecName, SecLevel}, + Type} | T], + ContextName,{TrapRec, Vbs}, V1Res, V2Res, V3Res, Recv, NetIf) -> + ?vdebug("send trap pdus: " + "~n Destination address: ~p" + "~n Target name: ~p" + "~n MP model: ~p" + "~n Type: ~p" + "~n V1Res: ~p" + "~n V2Res: ~p" + "~n V3Res: ~p", + [DestAddr, TargetName, MpModel, Type, V1Res, V2Res, V3Res]), + case get_mib_view(SecModel, SecName, SecLevel, ContextName) of + {ok, MibView} -> + case check_all_varbinds(TrapRec, Vbs, MibView) of + true when MpModel =:= ?MP_V1 -> + ?vtrace("send_trap_pdus -> v1 mp model",[]), + ContextEngineId = snmp_framework_mib:get_engine_id(), + case snmp_community_mib:vacm2community({SecName, + ContextEngineId, + ContextName}, + DestAddr) of + {ok, Community} -> + ?vdebug("community found for v1 dest: ~p", + [element(2, DestAddr)]), + send_trap_pdus(T, ContextName, {TrapRec, Vbs}, + [{DestAddr, Community} | V1Res], + V2Res, V3Res, Recv, NetIf); + undefined -> + ?vdebug("No community found for v1 dest: ~p", + [element(2, DestAddr)]), + send_trap_pdus(T, ContextName, {TrapRec, Vbs}, + V1Res, V2Res, V3Res, Recv, NetIf) + end; + true when MpModel =:= ?MP_V2C -> + ?vtrace("send_trap_pdus -> v2c mp model",[]), + ContextEngineId = snmp_framework_mib:get_engine_id(), + case snmp_community_mib:vacm2community({SecName, + ContextEngineId, + ContextName}, + DestAddr) of + {ok, Community} -> + ?vdebug("community found for v2c dest: ~p", + [element(2, DestAddr)]), + send_trap_pdus(T, ContextName, {TrapRec, Vbs}, + V1Res, + [{DestAddr, Community, Type}|V2Res], + V3Res, Recv, NetIf); + undefined -> + ?vdebug("No community found for v2c dest: ~p", + [element(2, DestAddr)]), + send_trap_pdus(T, ContextName, {TrapRec, Vbs}, + V1Res, V2Res, V3Res, Recv, NetIf) + end; + true when MpModel =:= ?MP_V3 -> + ?vtrace("send_trap_pdus -> v3 mp model",[]), + SecLevelF = mk_flag(SecLevel), + MsgData = {SecModel, SecName, SecLevelF, TargetName}, + send_trap_pdus(T, ContextName, {TrapRec, Vbs}, + V1Res, V2Res, + [{DestAddr, MsgData, Type} | V3Res], + Recv, NetIf); + true -> + ?vlog("bad MpModel ~p for dest ~p", + [MpModel, element(2, DestAddr)]), + send_trap_pdus(T, ContextName, {TrapRec, Vbs}, + V1Res, V2Res, V3Res, Recv, NetIf); + _ -> + ?vlog("no access for dest: " + "~n ~p in target ~p", + [element(2, DestAddr), TargetName]), + send_trap_pdus(T, ContextName, {TrapRec, Vbs}, + V1Res, V2Res, V3Res, Recv, NetIf) + end; + {discarded, Reason} -> + ?vlog("mib view error ~p for" + "~n dest: ~p" + "~n SecName: ~w", + [Reason, element(2, DestAddr), SecName]), + send_trap_pdus(T, ContextName, {TrapRec, Vbs}, + V1Res, V2Res, V3Res, Recv, NetIf) + end; +send_trap_pdus([], ContextName, {TrapRec, Vbs}, V1Res, V2Res, V3Res, + Recv, NetIf) -> + SysUpTime = snmp_standard_mib:sys_up_time(), + ?vdebug("send trap pdus with sysUpTime ~p", [SysUpTime]), + InformRecvs = get_inform_recvs(V2Res ++ V3Res), + InformTargets = [Addr || {Addr, _, _, _} <- InformRecvs], + deliver_recv(Recv, snmp_targets, InformTargets), + send_v1_trap(TrapRec, V1Res, Vbs, NetIf, SysUpTime), + send_v2_trap(TrapRec, V2Res, Vbs, Recv, NetIf, SysUpTime), + send_v3_trap(TrapRec, V3Res, Vbs, Recv, NetIf, SysUpTime, ContextName). + +send_v1_trap(_TrapRec, [], _Vbs, _NetIf, _SysUpTime) -> + ok; +send_v1_trap(#trap{enterpriseoid = Enter, specificcode = Spec}, + V1Res, Vbs, NetIf, SysUpTime) -> + ?vdebug("prepare to send v1 trap " + "~n '~p'" + "~n with" + "~n ~p" + "~n to" + "~n ~p", [Enter, Spec, V1Res]), + TrapPdu = make_v1_trap_pdu(Enter, Spec, Vbs, SysUpTime), + AddrCommunities = mk_addr_communities(V1Res), + lists:foreach(fun({Community, Addrs}) -> + ?vtrace("send v1 trap pdu to ~p",[Addrs]), + NetIf ! {send_pdu, 'version-1', TrapPdu, + {community, Community}, Addrs} + end, AddrCommunities); +send_v1_trap(#notification{oid = Oid}, V1Res, Vbs, NetIf, SysUpTime) -> + %% Use alg. in rfc2089 to map a v2 trap to a v1 trap + % delete Counter64 objects from vbs + ?vdebug("prepare to send v1 trap '~p'",[Oid]), + NVbs = [Vb || Vb <- Vbs, Vb#varbind.variabletype =/= 'Counter64'], + {Enter,Spec} = + case Oid of + [1,3,6,1,6,3,1,1,5,Specific] -> + {?snmp,Specific - 1}; + _ -> + case lists:reverse(Oid) of + [Last, 0 | First] -> + {lists:reverse(First),Last}; + [Last | First] -> + {lists:reverse(First),Last} + end + end, + TrapPdu = make_v1_trap_pdu(Enter, Spec, NVbs, SysUpTime), + AddrCommunities = mk_addr_communities(V1Res), + lists:foreach(fun({Community, Addrs}) -> + ?vtrace("send v1 trap to ~p",[Addrs]), + NetIf ! {send_pdu, 'version-1', TrapPdu, + {community, Community}, Addrs} + end, AddrCommunities). + +send_v2_trap(_TrapRec, [], _Vbs, _Recv, _NetIf, _SysUpTime) -> + ok; +send_v2_trap(TrapRec, V2Res, Vbs, Recv, NetIf, SysUpTime) -> + ?vdebug("prepare to send v2 trap",[]), + {_Oid, IVbs} = mk_v2_trap(TrapRec, Vbs, SysUpTime), + TrapRecvs = get_trap_recvs(V2Res), + InformRecvs = get_inform_recvs(V2Res), + do_send_v2_trap(TrapRecvs, IVbs, NetIf), + do_send_v2_inform(InformRecvs, IVbs, Recv, NetIf). + +send_v3_trap(_TrapRec, [], _Vbs, _Recv, _NetIf, _SysUpTime, _ContextName) -> + ok; +send_v3_trap(TrapRec, V3Res, Vbs, Recv, NetIf, SysUpTime, ContextName) -> + ?vdebug("prepare to send v3 trap",[]), + {_Oid, IVbs} = mk_v2_trap(TrapRec, Vbs, SysUpTime), % v2 refers to SMIv2; + TrapRecvs = get_trap_recvs(V3Res), % same SMI for v3 + InformRecvs = get_inform_recvs(V3Res), + do_send_v3_trap(TrapRecvs, ContextName, IVbs, NetIf), + do_send_v3_inform(InformRecvs, ContextName, IVbs, Recv, NetIf). + + +mk_v2_trap(#notification{oid = Oid}, Vbs, SysUpTime) -> + ?vtrace("make v2 notification '~p'",[Oid]), + mk_v2_notif(Oid, Vbs, SysUpTime); +mk_v2_trap(#trap{enterpriseoid = Enter, specificcode = Spec}, Vbs, SysUpTime) -> + %% Use alg. in rfc1908 to map a v1 trap to a v2 trap + ?vtrace("make v2 trap for '~p' with ~p",[Enter,Spec]), + {Oid,Enterp} = + case Enter of + ?snmp -> + {?snmpTraps ++ [Spec + 1],sys_object_id()}; + _ -> + {Enter ++ [0, Spec],Enter} + end, + ExtraVb = #varbind{oid = ?snmpTrapEnterprise_instance, + variabletype = 'OBJECT IDENTIFIER', + value = Enterp}, + mk_v2_notif(Oid, Vbs ++ [ExtraVb], SysUpTime). + +mk_v2_notif(Oid, Vbs, SysUpTime) -> + IVbs = [#varbind{oid = ?sysUpTime_instance, + variabletype = 'TimeTicks', + value = SysUpTime}, + #varbind{oid = ?snmpTrapOID_instance, + variabletype = 'OBJECT IDENTIFIER', + value = Oid} | Vbs], + {Oid, IVbs}. + +get_trap_recvs(TrapRecvs) -> + [{Addr, MsgData} || {Addr, MsgData, trap} <- TrapRecvs]. + +get_inform_recvs(InformRecvs) -> + [{Addr, MsgData, Timeout, Retry} || + {Addr, MsgData, {inform, Timeout, Retry}} <- InformRecvs]. + +do_send_v2_trap([], _Vbs, _NetIf) -> + ok; +do_send_v2_trap(Recvs, Vbs, NetIf) -> + TrapPdu = make_v2_notif_pdu(Vbs, 'snmpv2-trap'), + AddrCommunities = mk_addr_communities(Recvs), + lists:foreach(fun({Community, Addrs}) -> + ?vtrace("~n send v2 trap to ~p",[Addrs]), + NetIf ! {send_pdu, 'version-2', TrapPdu, + {community, Community}, Addrs} + end, AddrCommunities), + ok. + +do_send_v2_inform([], _Vbs, _Recv, _NetIf) -> + ok; +do_send_v2_inform(Recvs, Vbs, Recv, NetIf) -> + lists:foreach( + fun({Addr, Community, Timeout, Retry}) -> + ?vtrace("~n start inform sender to send v2 inform to ~p", + [Addr]), + proc_lib:spawn_link(?MODULE, init_v2_inform, + [Addr, Timeout, Retry, Vbs, + Recv, NetIf, Community, + get(verbosity), get(sname)]) + end, + Recvs). + +do_send_v3_trap([], _ContextName, _Vbs, _NetIf) -> + ok; +do_send_v3_trap(Recvs, ContextName, Vbs, NetIf) -> + TrapPdu = make_v2_notif_pdu(Vbs, 'snmpv2-trap'), % Yes, v2 + ContextEngineId = snmp_framework_mib:get_engine_id(), + lists:foreach(fun(Recv) -> + ?vtrace("~n send v3 notif to ~p",[Recv]), + NetIf ! {send_pdu, 'version-3', TrapPdu, + {v3, ContextEngineId, ContextName}, [Recv]} + end, Recvs), + ok. + +do_send_v3_inform([], _ContextName, _Vbs, _Recv, _NetIf) -> + ok; +do_send_v3_inform(Recvs, ContextName, Vbs, Recv, NetIf) -> + lists:foreach( + fun({Addr, MsgData, Timeout, Retry}) -> + ?vtrace("~n start inform sender to send v3 inform to ~p", + [Addr]), + proc_lib:spawn_link(?MODULE, init_v3_inform, + [{Addr, MsgData}, Timeout, Retry, Vbs, + Recv, NetIf, ContextName, + get(verbosity), get(sname)]) + end, + Recvs). + +%% New process +init_v2_inform(Addr, Timeout, Retry, Vbs, Recv, NetIf, Community,V,S) -> + %% Make a new Inform for each recipient; they need unique + %% request-ids! + put(verbosity,V), + put(sname,inform_sender_short_name(S)), + ?vdebug("~n starting with timeout = ~p and retry = ~p", + [Timeout,Retry]), + InformPdu = make_v2_notif_pdu(Vbs, 'inform-request'), + Msg = {send_pdu_req, 'version-2', InformPdu, {community, Community}, + [Addr], self()}, + ?MODULE:send_inform(Addr, Timeout*10, Retry, Msg, Recv, NetIf). + + +%% New process +init_v3_inform(Addr, Timeout, Retry, Vbs, Recv, NetIf, ContextName,V,S) -> + %% Make a new Inform for each recipient; they need unique + %% request-ids! + put(verbosity,V), + put(sname,inform_sender_short_name(S)), + ?vdebug("~n starting with timeout = ~p and retry = ~p", + [Timeout,Retry]), + InformPdu = make_v2_notif_pdu(Vbs, 'inform-request'), % Yes, v2 + ContextEngineId = snmp_framework_mib:get_engine_id(), + Msg = {send_pdu_req, 'version-3', InformPdu, + {v3, ContextEngineId, ContextName}, [Addr], self()}, + ?MODULE:send_inform(Addr, Timeout*10, Retry, Msg, Recv, NetIf). + +send_inform(Addr, _Timeout, -1, _Msg, Recv, _NetIf) -> + ?vinfo("~n Delivery of send-pdu-request to net-if failed: reply timeout", + []), + deliver_recv(Recv, snmp_notification, {no_response, Addr}); +send_inform(Addr, Timeout, Retry, Msg, Recv, NetIf) -> + ?vtrace("deliver send-pdu-request to net-if when" + "~n Timeout: ~p" + "~n Retry: ~p",[Timeout, Retry]), + NetIf ! Msg, + receive + {snmp_response_received, _Vsn, _Pdu, _From} -> + ?vtrace("received response for ~p (when Retry = ~p)", + [Recv, Retry]), + deliver_recv(Recv, snmp_notification, {got_response, Addr}) + after + Timeout -> + ?MODULE:send_inform(Addr, Timeout*2, Retry-1, Msg, Recv, NetIf) + end. + +% A nasty bit of verbosity setup... +inform_sender_short_name(ma) -> mais; +inform_sender_short_name(maw) -> mais; +inform_sender_short_name(mats) -> mais; +inform_sender_short_name(_) -> sais. + +deliver_recv(no_receiver, _MsgId, _Result) -> + ?vtrace("deliver_recv -> no receiver", []), + ok; +deliver_recv(#snmpa_notification_delivery_info{tag = Tag, + mod = Mod, + extra = Extra}, + snmp_targets, TAddrs) when is_list(TAddrs) -> + ?vtrace("deliver_recv(snmp_targets) -> entry with" + "~n Tag: ~p" + "~n Mod: ~p" + "~n Extra: ~p" + "~n TAddrs: ~p" + "", [Tag, Mod, Extra, TAddrs]), + Addrs = transform_taddrs(TAddrs), + (catch Mod:delivery_targets(Tag, Addrs, Extra)); +deliver_recv(#snmpa_notification_delivery_info{tag = Tag, + mod = Mod, + extra = Extra}, + snmp_notification, {DeliveryResult, TAddr}) -> + ?vtrace("deliver_recv -> entry with" + "~n Tag: ~p" + "~n Mod: ~p" + "~n Extra: ~p" + "~n DeliveryResult: ~p" + "~n TAddr: ~p" + "", [Tag, Mod, Extra, DeliveryResult, TAddr]), + Addr = transform_taddr(TAddr), + (catch Mod:delivery_info(Tag, Addr, DeliveryResult, Extra)); +deliver_recv({Tag, Receiver}, MsgId, Result) -> + ?vtrace("deliver_recv -> entry with" + "~n Tag: ~p" + "~n Receiver: ~p" + "~n MsgId: ~p" + "~n Result: ~p" + "", [Tag, Receiver, MsgId, Result]), + Msg = {MsgId, Tag, Result}, + case Receiver of + Pid when is_pid(Pid) -> + Pid ! Msg; + Name when is_atom(Name) -> + catch Name ! Msg; + {M, F, A} -> + catch M:F([Msg | A]); + Else -> + ?vinfo("~n Cannot deliver acknowledgment: bad receiver = '~p'", + [Else]), + user_err("snmpa: bad receiver, ~w\n", [Else]) + end; +deliver_recv(Else, _MsgId, _Result) -> + ?vinfo("~n Cannot deliver acknowledgment: bad receiver = '~p'", + [Else]), + user_err("snmpa: bad receiver, ~w\n", [Else]). + +transform_taddrs(Addrs) -> + [transform_taddr(Addr) || Addr <- Addrs]. + +transform_taddr({?snmpUDPDomain, [A1, A2, A3, A4, P1, P2]}) -> % v2 + Addr = {A1, A2, A3, A4}, + Port = P1 bsl 8 + P2, + {Addr, Port}; +transform_taddr({{?snmpUDPDomain, [A1, A2, A3, A4, P1, P2]}, _MsgData}) -> % v3 + Addr = {A1, A2, A3, A4}, + Port = P1 bsl 8 + P2, + {Addr, Port}. + + +check_all_varbinds(#notification{oid = Oid}, Vbs, MibView) -> + case snmpa_acm:validate_mib_view(Oid, MibView) of + true -> check_all_varbinds(Vbs, MibView); + false -> false + end; +check_all_varbinds(#trap{enterpriseoid = Enter, specificcode = Spec}, + Vbs, MibView) -> + %% Use alg. in rfc1908 to map a v1 trap to a v2 trap + Oid = case Enter of + ?snmp -> ?snmpTraps ++ [Spec + 1]; + _ -> Enter ++ [0, Spec] + end, + case snmpa_acm:validate_mib_view(Oid, MibView) of + true -> check_all_varbinds(Vbs, MibView); + false -> false + end. + +check_all_varbinds([#varbind{oid = Oid} | Vbs], MibView) -> + case snmpa_acm:validate_mib_view(Oid, MibView) of + true -> check_all_varbinds(Vbs, MibView); + false -> false + end; +check_all_varbinds([], _MibView) -> + true. + + +%%-------------------------------------------------- +%% Functions to access the local mib. +%%-------------------------------------------------- +sys_object_id() -> + case snmpa_agent:do_get(snmpa_acm:get_root_mib_view(), + [#varbind{oid = ?sysObjectID_instance}], + true) of + {noError, _, [#varbind{value = Value}]} -> + Value; + X -> + user_err("sysObjectID bad return value ~w", [X]) + end. + +%% Collect all ADDRs for each community together. +%% In: [{Addr, Community}] +%% Out: [{Community, [Addr]}] +mk_addr_communities(Recvs) -> + [{Addr, Comm} | T] = lists:keysort(2, Recvs), + mic(T, Comm, [Addr], []). + +mic([{Addr, Comm} | T], CurComm, AddrList, Res) when Comm == CurComm -> + mic(T, CurComm, [Addr | AddrList], Res); +mic([{Addr, Comm} | T], CurComm, AddrList, Res) -> + mic(T, Comm, [Addr], [{CurComm, AddrList} | Res]); +mic([], CurComm, AddrList, Res) -> + [{CurComm, AddrList} | Res]. + +%%----------------------------------------------------------------- +%% Convert the SecurityLevel into a flag value used by snmpa_mpd +%%----------------------------------------------------------------- +mk_flag(?'SnmpSecurityLevel_noAuthNoPriv') -> 0; +mk_flag(?'SnmpSecurityLevel_authNoPriv') -> 1; +mk_flag(?'SnmpSecurityLevel_authPriv') -> 3. + + +%%-------------------------------------------------- +%% Mib view wrapper +%%-------------------------------------------------- +get_mib_view(SecModel, SecName, SecLevel, ContextName) -> + snmpa_vacm:get_mib_view(notify, + SecModel, SecName, SecLevel, ContextName). + + +user_err(F, A) -> + snmpa_error:user_err(F, A). |