aboutsummaryrefslogtreecommitdiffstats
path: root/lib/snmp/src/manager/snmpm_mpd.erl
diff options
context:
space:
mode:
authorErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
committerErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
commit84adefa331c4159d432d22840663c38f155cd4c1 (patch)
treebff9a9c66adda4df2106dfd0e5c053ab182a12bd /lib/snmp/src/manager/snmpm_mpd.erl
downloadotp-84adefa331c4159d432d22840663c38f155cd4c1.tar.gz
otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.bz2
otp-84adefa331c4159d432d22840663c38f155cd4c1.zip
The R13B03 release.OTP_R13B03
Diffstat (limited to 'lib/snmp/src/manager/snmpm_mpd.erl')
-rw-r--r--lib/snmp/src/manager/snmpm_mpd.erl1024
1 files changed, 1024 insertions, 0 deletions
diff --git a/lib/snmp/src/manager/snmpm_mpd.erl b/lib/snmp/src/manager/snmpm_mpd.erl
new file mode 100644
index 0000000000..d76ad20051
--- /dev/null
+++ b/lib/snmp/src/manager/snmpm_mpd.erl
@@ -0,0 +1,1024 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2004-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(snmpm_mpd).
+
+-export([init/1,
+
+ process_msg/7,
+ generate_msg/5, generate_response_msg/4,
+
+ next_msg_id/0,
+ next_req_id/0,
+
+ reset/1,
+ inc/1]).
+
+-define(SNMP_USE_V3, true).
+-include("snmp_types.hrl").
+-include("snmpm_internal.hrl").
+-include("SNMP-MPD-MIB.hrl").
+-include("SNMPv2-TM.hrl").
+
+-define(VMODULE,"MPD").
+-include("snmp_verbosity.hrl").
+
+-define(empty_msg_size, 24).
+
+-record(state, {v1 = false, v2c = false, v3 = false}).
+
+
+%%%-----------------------------------------------------------------
+%%% This module implemets the Message Processing and Dispatch part of
+%%% the multi-lingual SNMP agent.
+%%%
+%%% The MPD is responsible for:
+%%% *) call the security module (auth/priv).
+%%% *) decoding the message into a PDU.
+%%% *) decide a suitable Access Control Model, and provide it with
+%%% the data it needs.
+%%% *) maintaining SNMP counters.
+%%%
+%%% In order to take care of the different versions of counters, it
+%%% implements and maintains the union of all SNMP counters (i.e. from
+%%% rfc1213 and from rfc1907). It is up to the administrator of the
+%%% agent to load the correct MIB. Note that this module implements
+%%% the counters only, it does not provide instrumentation functions
+%%% for the counters.
+%%%
+%%% With the terms defined in rfc2271, this module implememts part
+%%% of the Dispatcher and the Message Processing functionality.
+%%%-----------------------------------------------------------------
+init(Vsns) ->
+ ?vdebug("init -> entry with ~p", [Vsns]),
+ {A,B,C} = erlang:now(),
+ random:seed(A,B,C),
+ snmpm_config:cre_counter(msg_id, random:uniform(2147483647)),
+ snmpm_config:cre_counter(req_id, random:uniform(2147483647)),
+ init_counters(),
+ State = init_versions(Vsns, #state{}),
+ init_usm(State#state.v3),
+ ?vtrace("init -> done when ~p", [State]),
+ State.
+
+reset(#state{v3 = V3}) ->
+ reset_counters(),
+ reset_usm(V3).
+
+
+%%-----------------------------------------------------------------
+%% Func: process_msg(Packet, TDomain, TAddress, State) ->
+%% {ok, SnmpVsn, Pdu, PduMS, ACMData} | {discarded, Reason}
+%% Types: Packet = binary()
+%% TDomain = snmpUDPDomain | atom()
+%% TAddress = {Ip, Udp}
+%% State = #state
+%% Purpose: This is the main Message Dispatching function. (see
+%% section 4.2.1 in rfc2272)
+%%-----------------------------------------------------------------
+process_msg(Msg, TDomain, Addr, Port, State, NoteStore, Logger) ->
+
+ inc(snmpInPkts),
+
+ case (catch snmp_pdus:dec_message_only(binary_to_list(Msg))) of
+
+ %% Version 1
+ #message{version = 'version-1', vsn_hdr = Community, data = Data}
+ when State#state.v1 =:= true ->
+ HS = ?empty_msg_size + length(Community),
+ process_v1_v2c_msg('version-1', NoteStore, Msg, TDomain,
+ Addr, Port,
+ Community, Data, HS, Logger);
+
+ %% Version 2
+ #message{version = 'version-2', vsn_hdr = Community, data = Data}
+ when State#state.v2c =:= true ->
+ HS = ?empty_msg_size + length(Community),
+ process_v1_v2c_msg('version-2', NoteStore, Msg, TDomain,
+ Addr, Port,
+ Community, Data, HS, Logger);
+
+ %% Version 3
+ #message{version = 'version-3', vsn_hdr = H, data = Data}
+ when State#state.v3 =:= true ->
+ ?vlog("v3:"
+ "~n msgID: ~p"
+ "~n msgFlags: ~p"
+ "~n msgSecModel: ~p",
+ [H#v3_hdr.msgID,H#v3_hdr.msgFlags,H#v3_hdr.msgSecurityModel]),
+ process_v3_msg(NoteStore, Msg, H, Data, Addr, Port, Logger);
+
+ %% Crap
+ {'EXIT', {bad_version, Vsn}} ->
+ ?vinfo("exit: bad version: ~p",[Vsn]),
+ inc(snmpInBadVersions),
+ {discarded, snmpInBadVersions};
+
+ %% More crap
+ {'EXIT', Reason} ->
+ ?vinfo("exit: ~p",[Reason]),
+ inc(snmpInASNParseErrs),
+ {discarded, Reason};
+
+ %% Really crap
+ Crap ->
+ ?vinfo("unknown message: "
+ "~n ~p",[Crap]),
+ inc(snmpInBadVersions),
+ {discarded, snmpInBadVersions}
+ end.
+
+
+%%-----------------------------------------------------------------
+%% Handles a Community based message (v1 or v2c).
+%%-----------------------------------------------------------------
+process_v1_v2c_msg(Vsn, _NoteStore, Msg, snmpUDPDomain,
+ Addr, Port,
+ Community, Data, HS, Log) ->
+
+ ?vdebug("process_v1_v2c_msg -> entry with"
+ "~n Vsn: ~p"
+ "~n Addr: ~p"
+ "~n Port: ~p"
+ "~n Community: ~p"
+ "~n HS: ~p", [Vsn, Addr, Port, Community, HS]),
+
+ Max = get_max_message_size(),
+ AgentMax = get_agent_max_message_size(Addr, Port),
+ PduMS = pdu_ms(Max, AgentMax, HS),
+
+ ?vtrace("process_v1_v2c_msg -> PduMS: ~p", [PduMS]),
+
+ case (catch snmp_pdus:dec_pdu(Data)) of
+ Pdu when is_record(Pdu, pdu) ->
+ ?vtrace("process_v1_v2c_msg -> was a pdu", []),
+ Log(Msg),
+ inc_snmp_in(Pdu),
+ MsgData = {Community, sec_model(Vsn)},
+ {ok, Vsn, Pdu, PduMS, MsgData};
+
+ Trap when is_record(Trap, trappdu) ->
+ ?vtrace("process_v1_v2c_msg -> was a trap", []),
+ Log(Msg),
+ inc_snmp_in(Trap),
+ MsgData = {Community, sec_model(Vsn)},
+ {ok, Vsn, Trap, PduMS, MsgData};
+
+ {'EXIT', Reason} ->
+ ?vlog("process_v1_v2c_msg -> failed decoding PDU: "
+ "~n Reason: ~p", [Reason]),
+ inc(snmpInASNParseErrs),
+ {discarded, Reason}
+ end;
+process_v1_v2c_msg(_Vsn, _NoteStore, _Msg, TDomain,
+ _Addr, _Port,
+ _Comm, _HS, _Data, _Log) ->
+ {discarded, {badarg, TDomain}}.
+
+pdu_ms(MgrMMS, AgentMMS, HS) when AgentMMS < MgrMMS ->
+ AgentMMS - HS;
+pdu_ms(MgrMMS, _AgentMMS, HS) ->
+ MgrMMS - HS.
+
+sec_model('version-1') -> ?SEC_V1;
+sec_model('version-2') -> ?SEC_V2C.
+
+
+%%-----------------------------------------------------------------
+%% Handles a SNMPv3 Message, following the procedures in rfc2272,
+%% section 4.2 and 7.2
+%%-----------------------------------------------------------------
+process_v3_msg(NoteStore, Msg, Hdr, Data, Addr, Port, Log) ->
+
+ ?vdebug("process_v3_msg -> entry with"
+ "~n Hdr: ~p"
+ "~n Addr: ~p"
+ "~n Port: ~p", [Hdr, Addr, Port]),
+
+ %% 7.2.3
+ #v3_hdr{msgID = MsgID,
+ msgMaxSize = MMS,
+ msgFlags = MsgFlags,
+ msgSecurityModel = MsgSecModel,
+ msgSecurityParameters = SecParams,
+ hdr_size = HdrSize} = Hdr,
+
+ %% 7.2.4
+ SecModule = get_security_module(MsgSecModel),
+ ?vtrace("process_v3_msg -> 7.2.4: "
+ "~n SecModule: ~p", [SecModule]),
+
+ %% 7.2.5
+ SecLevel = check_sec_level(MsgFlags),
+ IsReportable = is_reportable(MsgFlags),
+ ?vtrace("process_v3_msg -> 7.2.5: "
+ "~n SecLevel: ~p"
+ "~n IsReportable: ~p", [SecLevel, IsReportable]),
+
+ %% 7.2.6
+ SecRes = (catch SecModule:process_incoming_msg(Msg, Data,
+ SecParams, SecLevel)),
+ ?vtrace("process_v3_msg -> 7.2.6 - message processing result: "
+ "~n ~p",[SecRes]),
+ {SecEngineID, SecName, ScopedPDUBytes, SecData} =
+ check_sec_module_result(SecRes, Hdr, Data, IsReportable, Log),
+ ?vtrace("process_v3_msg -> 7.2.6 - checked module result: "
+ "~n SecEngineID: ~p"
+ "~n SecName: ~p",[SecEngineID, SecName]),
+
+ %% 7.2.7
+ #scopedPdu{contextEngineID = CtxEngineID,
+ contextName = CtxName,
+ data = PDU} =
+ case (catch snmp_pdus:dec_scoped_pdu(ScopedPDUBytes)) of
+ ScopedPDU when is_record(ScopedPDU, scopedPdu) ->
+ ScopedPDU;
+ {'EXIT', Reason} ->
+ ?vlog("failed decoding scoped pdu: "
+ "~n ~p",[Reason]),
+ inc(snmpInASNParseErrs),
+ discard(Reason)
+ end,
+
+ ?vlog("7.2.7"
+ "~n ContextEngineID: \"~s\" "
+ "~n context: \"~s\" ",
+ [CtxEngineID, CtxName]),
+ if
+ SecLevel == 3 -> % encrypted message - log decrypted pdu
+ Log({Hdr, ScopedPDUBytes});
+ true -> % otherwise, log binary
+ Log(Msg)
+ end,
+
+ %% Make sure a get_bulk doesn't get too big.
+ MgrMMS = get_max_message_size(),
+ %% PduMMS is supposed to be the maximum total length of the response
+ %% PDU we can send. From the MMS, we need to subtract everything before
+ %% the PDU, i.e. Message and ScopedPDU.
+ %% Message: [48, TotalLen, Vsn, [Tag, LH, Hdr], [Tag, LM, MsgSec], Data]
+ %% 1 3 <----------- HdrSize ----------->
+ %% HdrSize = everything up to and including msgSecurityParameters.
+ %% ScopedPduData follows. This is
+ %% [Tag, Len, [Tag, L1, CtxName], [Tag, L2, CtxEID]]
+ %% i.e. 6 + length(CtxName) + length(CtxEID)
+ %%
+ %% Total: 1 + TotalLenOctets + 3 + ScopedPduDataLen
+ TotMMS = tot_mms(MgrMMS, MMS),
+ TotalLenOctets = snmp_pdus:get_encoded_length(TotMMS - 1),
+ PduMMS = TotMMS - TotalLenOctets - 10 - HdrSize -
+ length(CtxName) - length(CtxEngineID),
+ ?vtrace("process_v3_msg -> PduMMS = ~p", [PduMMS]),
+ Type = PDU#pdu.type,
+ ?vdebug("process_v3_msg -> PDU type: ~p",[Type]),
+ case Type of
+ report ->
+ %% 7.2.10 & 11
+ %% BMK BMK BMK: discovery?
+ Note = snmp_note_store:get_note(NoteStore, MsgID),
+ case Note of
+ {SecEngineID, MsgSecModel, SecName, SecLevel,
+ CtxEngineID, CtxName, _ReqId} ->
+ ?vtrace("process_v3_msg -> 7.2.11b: ok", []),
+ %% BMK BMK: Should we discard the cached info
+ %% BMK BMK: or do we let the gc deal with it?
+ {ok, 'version-3', PDU, PduMMS, ok};
+ _ when is_tuple(Note) ->
+ ?vlog("process_v3_msg -> 7.2.11b: error"
+ "~n Note: ~p", [Note]),
+ Recv = {SecEngineID, MsgSecModel, SecName, SecLevel,
+ CtxEngineID, CtxName, PDU#pdu.request_id},
+ Err = sec_error(Note, Recv),
+ ACM = {invalid_sec_info, Err},
+ ReqId = element(size(Note), Note),
+ {ok, 'version-3', PDU, PduMMS, {error, ReqId, ACM}};
+ _NoFound ->
+ ?vtrace("process_v3_msg -> _NoFound: "
+ "~p", [_NoFound]),
+ inc(snmpUnknownPDUHandlers),
+ discard({no_outstanding_req, MsgID})
+ end;
+
+ 'get-response' ->
+ %% 7.2.10 & 12
+ case snmp_note_store:get_note(NoteStore, MsgID) of
+ {SecEngineID, MsgSecModel, SecName, SecLevel,
+ CtxEngineID, CtxName, _} ->
+ %% 7.2.12.d
+ {ok, 'version-3', PDU, PduMMS, undefined};
+ _ ->
+ %% 7.2.12.b
+ %% BMK BMK: Should we not discard the cached info??
+ inc(snmpUnknownPDUHandlers),
+ discard({no_outstanding_req, MsgID})
+ end;
+
+ 'snmpv2-trap' ->
+ %% 7.2.14
+ {ok, 'version-3', PDU, PduMMS, undefined};
+
+ 'inform-request' ->
+ %% 7.2.13
+ SnmpEngineID = get_engine_id(),
+ case SecEngineID of
+ SnmpEngineID -> % 7.2.13.b
+ ?vtrace("valid securityEngineID: ~p", [SecEngineID]),
+ %% 4.2.2.1.1 - we don't handle proxys yet => we only
+ %% handle CtxEngineID to ourselves
+ %% Check that we actually know of an agent with this
+ %% CtxEngineID and Addr/Port
+ case is_known_engine_id(CtxEngineID, Addr, Port) of
+ true ->
+ ?vtrace("and the agent EngineID (~p) "
+ "is know to us", [CtxEngineID]),
+ %% Uses ACMData that snmpm_acm knows of.
+ %% BUGBUG BUGBUG
+ ACMData =
+ {MsgID, MsgSecModel, SecName, SecLevel,
+ CtxEngineID, CtxName, SecData},
+ {ok, 'version-3', PDU, PduMMS, ACMData};
+ _ ->
+ %% 4.2.2.1.2
+ NIsReportable = snmp_misc:is_reportable_pdu(Type),
+ Val = inc(snmpUnknownPDUHandlers),
+ ErrorInfo =
+ {#varbind{oid = ?snmpUnknownPDUHandlers,
+ variabletype = 'Counter32',
+ value = Val},
+ SecName,
+ [{securityLevel, SecLevel},
+ {contextEngineID, CtxEngineID},
+ {contextName, CtxName}]},
+ case generate_v3_report_msg(MsgID,
+ MsgSecModel,
+ Data,
+ ErrorInfo,
+ Log) of
+ {ok, Report} when NIsReportable =:= true ->
+ discard(snmpUnknownPDUHandlers, Report);
+ _ ->
+ discard(snmpUnknownPDUHandlers)
+ end
+ end;
+ _ -> % 7.2.13.a
+ ?vinfo("invalid securityEngineID: ~p",[SecEngineID]),
+ discard({badSecurityEngineID, SecEngineID})
+ end;
+
+ _ ->
+ %% 7.2.13 - This would be the requests which we should not
+ %% receive since we are a manager, barring possible
+ %% proxy...
+ discard(Type)
+ end.
+
+
+sec_error(T1, T2)
+ when is_tuple(T1) andalso is_tuple(T2) andalso (size(T1) =:= size(T2)) ->
+ Tags = {sec_engine_id, msg_sec_model, sec_name, sec_level,
+ ctx_engine_id, ctx_name, request_id},
+ sec_error(size(T1), T1, T2, Tags, []);
+sec_error(T1, T2) ->
+ [{internal_error, T1, T2}].
+
+sec_error(0, _T1, _T2, _Tags, Acc) ->
+ Acc;
+sec_error(Idx, T1, T2, Tags, Acc) ->
+ case element(Idx, T1) =:= element(Idx, T2) of
+ true ->
+ sec_error(Idx - 1, T1, T2, Tags, Acc);
+ false ->
+ Elem = {element(Idx, Tags), element(Idx, T1), element(Idx, T2)},
+ sec_error(Idx - 1, T1, T2, Tags, [Elem|Acc])
+ end.
+
+tot_mms(MgrMMS, AgentMMS) when MgrMMS > AgentMMS -> AgentMMS;
+tot_mms(MgrMMS, _AgentMMS) -> MgrMMS.
+
+get_security_module(?SEC_USM) ->
+ snmpm_usm;
+get_security_module(_) ->
+ inc(snmpUnknownSecurityModels),
+ discard(snmpUnknownSecurityModels).
+
+check_sec_level([MsgFlag]) ->
+ SecLevel = MsgFlag band 3,
+ if
+ SecLevel == 2 ->
+ inc(snmpInvalidMsgs),
+ discard(snmpInvalidMsgs);
+ true ->
+ SecLevel
+ end;
+check_sec_level(_Unknown) ->
+ inc(snmpInvalidMsgs),
+ discard(snmpInvalidMsgs).
+
+is_reportable([MsgFlag]) ->
+ 4 == (MsgFlag band 4).
+
+
+check_sec_module_result({ok, X}, _, _, _, _) ->
+ X;
+check_sec_module_result({error, Reason, Info}, _, _, _, _)
+ when is_list(Info) ->
+ %% case 7.2.6 b
+ discard({securityError, Reason, Info});
+check_sec_module_result({error, Reason, ErrorInfo}, V3Hdr, Data, true, Log) ->
+ %% case 7.2.6 a
+ ?vtrace("security module result:"
+ "~n Reason: ~p"
+ "~n ErrorInfo: ~p", [Reason, ErrorInfo]),
+ #v3_hdr{msgID = MsgID, msgSecurityModel = MsgSecModel} = V3Hdr,
+ Pdu = get_scoped_pdu(Data),
+ case generate_v3_report_msg(MsgID, MsgSecModel, Pdu, ErrorInfo, Log) of
+ {ok, Report} ->
+ discard({securityError, Reason}, Report);
+ {discarded, _SomeOtherReason} ->
+ discard({securityError, Reason})
+ end;
+check_sec_module_result({error, Reason, _ErrorInfo}, _, _, _, _) ->
+ ?vtrace("security module result:"
+ "~n Reason: ~p"
+ "~n _ErrorInfo: ~p", [Reason, _ErrorInfo]),
+ discard({securityError, Reason});
+check_sec_module_result(Res, _, _, _, _) ->
+ ?vtrace("security module result:"
+ "~n Res: ~p", [Res]),
+ discard({securityError, Res}).
+
+get_scoped_pdu(D) when is_list(D) ->
+ (catch snmp_pdus:dec_scoped_pdu(D));
+get_scoped_pdu(D) ->
+ D.
+
+
+%%-----------------------------------------------------------------
+%% Generate a message
+%%-----------------------------------------------------------------
+generate_msg('version-3', NoteStore, Pdu,
+ {SecModel, SecName, SecLevel, CtxEngineID, CtxName,
+ TargetName}, Log) ->
+ generate_v3_msg(NoteStore, Pdu,
+ SecModel, SecName, SecLevel, CtxEngineID, CtxName,
+ TargetName, Log);
+generate_msg(Vsn, _NoteStore, Pdu, {Community, _SecModel}, Log) ->
+ generate_v1_v2c_msg(Vsn, Pdu, Community, Log).
+
+
+generate_v3_msg(NoteStore, Pdu,
+ SecModel, SecName, SecLevel, CtxEngineID, CtxName,
+ TargetName, Log) ->
+ %% rfc2272: 7.1.6
+ ?vdebug("generate_v3_msg -> 7.1.6", []),
+ ScopedPDU = #scopedPdu{contextEngineID = CtxEngineID,
+ contextName = CtxName,
+ data = Pdu},
+ case (catch snmp_pdus:enc_scoped_pdu(ScopedPDU)) of
+ {'EXIT', Reason} ->
+ user_err("failed encoding scoped pdu "
+ "~n pdu: ~w"
+ "~n contextName: ~w"
+ "~n reason: ~w", [Pdu, CtxName, Reason]),
+ {discarded, Reason};
+ ScopedPDUBytes ->
+ {ok, generate_v3_msg(NoteStore, Pdu, ScopedPDUBytes,
+ SecModel, SecName, SecLevel,
+ CtxEngineID, CtxName, TargetName, Log)}
+ end.
+
+generate_v3_msg(NoteStore,
+ #pdu{type = Type} = Pdu, ScopedPduBytes,
+ SecModel, SecName, SecLevel, CtxEngineID, CtxName,
+ TargetName, Log) ->
+ %% 7.1.7
+ ?vdebug("generate_v3_msg -> 7.1.7", []),
+ MsgID = next_msg_id(),
+ MsgFlags = snmp_misc:mk_msg_flags(Type, SecLevel),
+ V3Hdr = #v3_hdr{msgID = MsgID,
+ msgMaxSize = get_max_message_size(),
+ msgFlags = MsgFlags,
+ msgSecurityModel = SecModel},
+ Message = #message{version = 'version-3',
+ vsn_hdr = V3Hdr,
+ data = ScopedPduBytes},
+ SecModule = sec_module(SecModel),
+
+ %% 7.1.9a
+ ?vdebug("generate_v3_msg -> 7.1.9a", []),
+ SecEngineID = sec_engine_id(TargetName),
+ ?vtrace("SecEngineID: ~p", [SecEngineID]),
+ %% 7.1.9b
+ ?vdebug("generate_v3_msg -> 7.1.9b", []),
+ case generate_v3_outgoing_msg(Message, SecModule, SecEngineID,
+ SecName, [], SecLevel) of
+ {ok, Packet} ->
+ %% 7.1.9c
+ %% Store in cache for 150 sec.
+ ?vdebug("generate_v3_msg -> 7.1.9c", []),
+ %% The request id is just in the case when we receive a
+ %% report with incorrect securityModel and/or securityLevel
+ CacheVal = {SecEngineID, SecModel, SecName, SecLevel,
+ CtxEngineID, CtxName, Pdu#pdu.request_id},
+ snmp_note_store:set_note(NoteStore, 1500, MsgID, CacheVal),
+ Log(Packet),
+ inc_snmp_out(Pdu),
+ ?vdebug("generate_v3_msg -> done", []),
+ Packet;
+
+ Error ->
+ throw(Error)
+ end.
+
+
+sec_module(?SEC_USM) ->
+ snmpm_usm.
+
+%% 9) If the PDU is a GetRequest-PDU, GetNextRequest-PDU,
+%% GetBulkRequest-PDU, SetRequest-PDU, InformRequest-PDU, or or
+%% SNMPv2-Trap-PDU, then
+%%
+%% a) If the PDU is an SNMPv2-Trap-PDU, then securityEngineID is set
+%% to the value of this entity's snmpEngineID.
+%%
+%% Otherwise, the snmpEngineID of the target entity is determined,
+%% in an implementation-dependent manner, possibly using
+%% transportDomain and transportAddress. The value of
+%% securityEngineID is set to the value of the target entity's
+%% snmpEngineID.
+%%
+%% As we never send traps, the SecEngineID is allways the
+%% snmpEngineID of the target entity!
+sec_engine_id(TargetName) ->
+ case get_agent_engine_id(TargetName) of
+ {ok, EngineId} ->
+ EngineId;
+ _ ->
+ config_err("Can't find engineID for "
+ "snmpTargetAddrName ~p", [TargetName]),
+ %% this will trigger error in secmodule
+ ""
+ end.
+
+
+%% BMK BMK BMK
+%% Denna verkar v�ldigt lik generate_v1_v2c_response_msg!
+%% Gemensam? Borde det finnas olikheter?
+%%
+generate_v1_v2c_msg(Vsn, Pdu, Community, Log) ->
+ ?vdebug("generate_v1_v2c_msg -> encode pdu", []),
+ case (catch snmp_pdus:enc_pdu(Pdu)) of
+ {'EXIT', Reason} ->
+ user_err("failed encoding pdu: "
+ "(pdu: ~w, community: ~w): ~n~w",
+ [Pdu, Community, Reason]),
+ {discarded, Reason};
+ PduBytes ->
+ MMS = get_max_message_size(),
+ Message = #message{version = Vsn,
+ vsn_hdr = Community,
+ data = PduBytes},
+ case generate_v1_v2c_outgoing_msg(Message) of
+ {error, Reason} ->
+ user_err("failed encoding message "
+ "(pdu: ~w, community: ~w): ~n~w",
+ [Pdu, Community, Reason]),
+ {discarded, Reason};
+ {ok, Packet} when size(Packet) =< MMS ->
+ Log(Packet),
+ inc_snmp_out(Pdu),
+ {ok, Packet};
+ {ok, Packet} ->
+ ?vlog("packet max size exceeded: "
+ "~n MMS: ~p"
+ "~n Len: ~p",
+ [MMS, size(Packet)]),
+ {discarded, tooBig}
+ end
+ end.
+
+
+
+%% -----------------------------------------------------------------------
+
+generate_response_msg('version-3', Pdu,
+ {MsgID, SecModel, SecName, SecLevel,
+ CtxEngineID, CtxName, SecData}, Log) ->
+ generate_v3_response_msg(Pdu, MsgID, SecModel, SecName, SecLevel,
+ CtxEngineID, CtxName, SecData, Log);
+generate_response_msg(Vsn, Pdu, {Comm, _SecModel}, Log) ->
+ generate_v1_v2c_response_msg(Vsn, Pdu, Comm, Log).
+
+
+generate_v3_response_msg(#pdu{type = Type} = Pdu, MsgID,
+ SecModel, SecName, SecLevel,
+ CtxEngineID, CtxName, SecData, Log) ->
+ %% rfc2272: 7.1 steps 6-8
+ ScopedPdu = #scopedPdu{contextEngineID = CtxEngineID,
+ contextName = CtxName,
+ data = Pdu},
+ case (catch snmp_pdus:enc_scoped_pdu(ScopedPdu)) of
+ {'EXIT', Reason} ->
+ user_err("failed encoded scoped pdu "
+ "(pdu: ~w, contextName: ~w): ~n~w",
+ [Pdu, CtxName, Reason]),
+ {discarded, Reason};
+ ScopedPduBytes ->
+ MMS = get_max_message_size(),
+ MsgFlags = snmp_misc:mk_msg_flags(Type, SecLevel),
+ V3Hdr = #v3_hdr{msgID = MsgID,
+ msgMaxSize = MMS,
+ msgFlags = MsgFlags,
+ msgSecurityModel = SecModel},
+ Message = #message{version = 'version-3',
+ vsn_hdr = V3Hdr,
+ data = ScopedPduBytes},
+ %% We know that the security model is valid when we
+ %% generate a response.
+ SecModule = sec_module(SecModel),
+ SecEngineID = get_engine_id(),
+ case generate_v3_outgoing_msg(Message, SecModule, SecEngineID,
+ SecName, SecData, SecLevel) of
+ %% Check the packet size. Send the msg even
+ %% if it's larger than the agent can handle -
+ %% it will be dropped. Just check against the
+ %% internal size.
+ {ok, Packet} when size(Packet) =< MMS ->
+ if
+ SecLevel == 3 ->
+ %% encrypted - log decrypted pdu
+ Log({V3Hdr, ScopedPduBytes});
+ true ->
+ %% otherwise log the entire msg
+ Log(Packet)
+ end,
+ inc_snmp_out(Pdu),
+ {ok, Packet};
+
+ {ok, _Packet} when Pdu#pdu.error_status =:= tooBig ->
+ ?vlog("packet max size exceeded (tooBog): "
+ "~n MMS: ~p", [MMS]),
+ inc(snmpSilentDrops),
+ {discarded, tooBig};
+ {ok, _Packet} ->
+ ?vlog("packet max size exceeded: "
+ "~n MMS: ~p", [MMS]),
+ TooBigPdu = Pdu#pdu{error_status = tooBig,
+ error_index = 0,
+ varbinds = []},
+ generate_v3_response_msg(TooBigPdu, MsgID,
+ SecModel, SecName, SecLevel,
+ CtxEngineID,
+ CtxName,
+ SecData, Log);
+ Error ->
+ Error
+ end
+ end.
+
+
+generate_v3_outgoing_msg(Message,
+ SecModule, SecEngineID, SecName, SecData, SecLevel) ->
+ case (catch SecModule:generate_outgoing_msg(Message,
+ SecEngineID,
+ SecName, SecData,
+ SecLevel)) of
+ {'EXIT', Reason} ->
+ config_err("~p (message: ~p)", [Reason, Message]),
+ {discarded, Reason};
+ {error, Reason} ->
+ config_err("~p (message: ~p)", [Reason, Message]),
+ {discarded, Reason};
+ Bin when is_binary(Bin) ->
+ {ok, Bin};
+ OutMsg when is_list(OutMsg) ->
+ case (catch list_to_binary(OutMsg)) of
+ Bin when is_binary(Bin) ->
+ {ok, Bin};
+ {'EXIT', Reason} ->
+ {error, Reason}
+ end
+ end.
+
+
+generate_v1_v2c_response_msg(Vsn, Pdu, Comm, Log) ->
+ case (catch snmp_pdus:enc_pdu(Pdu)) of
+ {'EXIT', Reason} ->
+ user_err("failed encoding pdu: "
+ "(pdu: ~w, community: ~w): ~n~w",
+ [Pdu, Comm, Reason]),
+ {discarded, Reason};
+ PduBytes ->
+ MMS = get_max_message_size(),
+ Message = #message{version = Vsn,
+ vsn_hdr = Comm,
+ data = PduBytes},
+ case generate_v1_v2c_outgoing_msg(Message) of
+ {error, Reason} ->
+ user_err("failed encoding message only "
+ "(pdu: ~w, community: ~w): ~n~w",
+ [Pdu, Comm, Reason]),
+ {discarded, Reason};
+
+ {ok, Packet} when size(Packet) =< MMS ->
+ Log(Packet),
+ inc_snmp_out(Pdu),
+ {ok, Packet};
+
+ {ok, Packet} -> %% Too big
+ too_big(Vsn, Pdu, Comm, MMS, size(Packet), Log)
+ end
+ end.
+
+
+too_big('version-1' = Vsn, #pdu{type = 'get-response'} = Pdu,
+ Comm, _MMS, _Len, Log) ->
+ %% In v1, the varbinds should be identical to the incoming
+ %% request. It isn't identical now! Make acceptable (?)
+ %% approximation.
+ V = set_vb_null(Pdu#pdu.varbinds),
+ TooBigPdu = Pdu#pdu{error_status = tooBig, error_index = 0, varbinds = V},
+ too_big(Vsn, TooBigPdu, Comm, Log);
+too_big('version-2' = Vsn, #pdu{type = 'get-response'} = Pdu,
+ Comm, _MMS, _Len, Log) ->
+ %% In v2, varbinds should be empty (reasonable!)
+ TooBigPdu = Pdu#pdu{error_status = tooBig, error_index = 0, varbinds = []},
+ too_big(Vsn, TooBigPdu, Comm, Log);
+too_big(_Vsn, Pdu, _Comm, _Log, MMS, Len) ->
+ user_err("encoded pdu, ~p bytes, exceeded "
+ "max message size of ~p bytes. Pdu: ~n~w",
+ [Len, MMS, Pdu]),
+ {discarded, tooBig}.
+
+
+too_big(Vsn, Pdu, Comm, Log) ->
+ case (catch snmp_pdus:enc_pdu(Pdu)) of
+ {'EXIT', Reason} ->
+ user_err("failed encoding pdu "
+ "(pdu: ~w, community: ~w): ~n~w",
+ [Pdu, Comm, Reason]),
+ {discarded, Reason};
+ PduBytes ->
+ Message = #message{version = Vsn,
+ vsn_hdr = Comm,
+ data = PduBytes},
+ case generate_v1_v2c_outgoing_msg(Message) of
+ {error, Reason} ->
+ user_err("failed encoding message only"
+ "(pdu: ~w, community: ~w): ~n~w",
+ [Pdu, Comm, Reason]),
+ {discarded, Reason};
+ {ok, Bin} ->
+ Log(Bin),
+ inc_snmp_out(Pdu),
+ {ok, Bin}
+ end
+ end.
+
+set_vb_null(Vbs) ->
+ [Vb#varbind{variabletype = 'NULL', value = 'NULL'} || Vb <- Vbs].
+
+
+generate_v1_v2c_outgoing_msg(Message) ->
+ ?vdebug("generate_v1_v2c_outgoing_msg -> encode message", []),
+ case (catch snmp_pdus:enc_message_only(Message)) of
+ {'EXIT', Reason} ->
+ {error, Reason};
+ Bin when is_binary(Bin) ->
+ {ok, Bin};
+ Packet when is_list(Packet) ->
+ case (catch list_to_binary(Packet)) of
+ Bin when is_binary(Bin) ->
+ {ok, Bin};
+ {'EXIT', Reason} ->
+ {error, Reason}
+ end
+ end.
+
+
+
+generate_v3_report_msg(MsgID, SecModel, ScopedPdu, ErrInfo, Log)
+ when is_record(ScopedPdu, scopedPdu) ->
+ ReqID = (ScopedPdu#scopedPdu.data)#pdu.request_id,
+ generate_v3_report_msg2(MsgID, ReqID, SecModel, ErrInfo, Log);
+generate_v3_report_msg(MsgID, SecModel, _, ErrInfo, Log) ->
+ %% RFC2572, 7.1.3.c.4
+ generate_v3_report_msg2(MsgID, 0, SecModel, ErrInfo, Log).
+
+
+generate_v3_report_msg2(MsgID, ReqID, SecModel, ErrInfo, Log) ->
+ {Varbind, SecName, Opts} = ErrInfo,
+ Pdu = #pdu{type = report,
+ request_id = ReqID,
+ error_status = noError,
+ error_index = 0,
+ varbinds = [Varbind]},
+ SecLevel = snmp_misc:get_option(securityLevel, Opts, 0),
+ CtxEngineID = snmp_misc:get_option(contextEngineID, Opts, get_engine_id()),
+ CtxName = snmp_misc:get_option(contextName, Opts, ""),
+ SecData = snmp_misc:get_option(sec_data, Opts, []),
+ generate_v3_response_msg(Pdu,
+ MsgID, SecModel, SecName, SecLevel,
+ CtxEngineID, CtxName, SecData, Log).
+
+
+%%-----------------------------------------------------------------
+
+%% Get "our" (manager) MMS
+get_max_message_size() ->
+ case snmpm_config:get_engine_max_message_size() of
+ {ok, MMS} ->
+ MMS;
+ E ->
+ user_err("failed retreiving engine max message size: ~w", [E]),
+ 484
+ end.
+
+%% The the MMS of the agent
+get_agent_max_message_size(Addr, Port) ->
+ case snmpm_config:get_agent_engine_max_message_size(Addr, Port) of
+ {ok, MMS} ->
+ MMS;
+ _Error ->
+ ?vlog("unknown agent: ~w:~w", [Addr, Port]),
+ get_max_message_size()
+ end.
+
+%% Get "our" (manager) engine id
+get_engine_id() ->
+ case snmpm_config:get_engine_id() of
+ {ok, Id} ->
+ Id;
+ _Error ->
+ ""
+ end.
+
+%% The engine id of the agent
+get_agent_engine_id(Name) ->
+ snmpm_config:get_agent_engine_id(Name).
+
+is_known_engine_id(EngineID, Addr, Port) ->
+ snmpm_config:is_known_engine_id(EngineID, Addr, Port).
+
+% get_agent_engine_id(Addr, Port) ->
+% case snmpm_config:get_agent_engine_id(Addr, Port) of
+% {ok, Id} ->
+% Id;
+% _Error ->
+% ""
+% end.
+
+
+%%-----------------------------------------------------------------
+%% Sequence number (msg-id & req-id) functions
+%%-----------------------------------------------------------------
+next_msg_id() ->
+ next_id(msg_id).
+
+next_req_id() ->
+ next_id(req_id).
+
+next_id(Id) ->
+ snmpm_config:incr_counter(Id, 1).
+
+
+%%-----------------------------------------------------------------
+%% Version(s) functions
+%%-----------------------------------------------------------------
+init_versions([], S) ->
+ S;
+init_versions([v1|Vsns], S) ->
+ init_versions(Vsns, S#state{v1 = true});
+init_versions([v2|Vsns], S) ->
+ init_versions(Vsns, S#state{v2c = true});
+init_versions([v3|Vsns], S) ->
+ init_versions(Vsns, S#state{v3 = true}).
+
+init_usm(true) ->
+ snmpm_usm:init();
+init_usm(_) ->
+ ok.
+
+
+%%-----------------------------------------------------------------
+%% Counter functions
+%%-----------------------------------------------------------------
+init_counters() ->
+ F = fun(Counter) -> maybe_create_counter(Counter) end,
+ lists:map(F, counters()).
+
+reset_counters() ->
+ F = fun(Counter) -> snmpm_config:reset_stats_counter(Counter) end,
+ lists:map(F, counters()).
+
+reset_usm(true) ->
+ snmpm_usm:reset();
+reset_usm(_) ->
+ ok.
+
+maybe_create_counter(Counter) ->
+ snmpm_config:maybe_cre_stats_counter(Counter, 0).
+
+counters() ->
+ [snmpInPkts,
+ snmpOutPkts,
+ snmpInBadVersions,
+ snmpInBadCommunityNames,
+ snmpInBadCommunityUses,
+ snmpInASNParseErrs,
+ snmpInTooBigs,
+ snmpInNoSuchNames,
+ snmpInBadValues,
+ snmpInReadOnlys,
+ snmpInGenErrs,
+ snmpInTotalReqVars,
+ snmpInTotalSetVars,
+ snmpInGetRequests,
+ snmpInGetNexts,
+ snmpInSetRequests,
+ snmpInGetResponses,
+ snmpInTraps,
+ snmpOutTooBigs,
+ snmpOutNoSuchNames,
+ snmpOutBadValues,
+ snmpOutGenErrs,
+ snmpOutGetRequests,
+ snmpOutGetNexts,
+ snmpOutSetRequests,
+ snmpOutGetResponses,
+ snmpOutTraps,
+ snmpSilentDrops,
+ snmpProxyDrops,
+ %% From SNMP-MPD-MIB
+ snmpUnknownSecurityModels,
+ snmpInvalidMsgs,
+ snmpUnknownPDUHandlers
+ ].
+
+
+%%-----------------------------------------------------------------
+%% inc(VariableName) increments the variable (Counter) in
+%% the local mib. (e.g. snmpInPkts)
+%%-----------------------------------------------------------------
+inc(Name) -> inc(Name, 1).
+inc(Name, N) -> snmpm_config:incr_stats_counter(Name, N).
+
+inc_snmp_in(#pdu{type = Type}) ->
+ inc_in_type(Type);
+inc_snmp_in(TrapPdu) when is_record(TrapPdu, trappdu) ->
+ inc(snmpInPkts),
+ inc(snmpInTraps).
+
+inc_snmp_out(#pdu{type = Type,
+ error_status = ErrorStatus}) ->
+ inc(snmpOutPkts),
+ inc_out_err(ErrorStatus),
+ inc_out_type(Type).
+
+inc_out_type('get-request') -> inc(snmpOutGetRequests);
+inc_out_type('get-next-request') -> inc(snmpOutGetNexts);
+inc_out_type('set-request') -> inc(snmpOutSetRequests);
+inc_out_type(_) -> ok.
+
+inc_out_err(genErr) -> inc(snmpOutGenErrs);
+inc_out_err(tooBig) -> inc(snmpOutTooBigs);
+inc_out_err(noSuchName) -> inc(snmpOutNoSuchNames);
+inc_out_err(badValue) -> inc(snmpOutBadValues);
+inc_out_err(_) -> ok.
+
+inc_in_type('get-response') -> inc(snmpInGetResponses);
+inc_in_type(_) -> ok.
+
+
+%%-----------------------------------------------------------------
+
+discard(Reason) ->
+ throw({discarded, Reason}).
+
+discard(Reason, Report) ->
+ throw({discarded, Reason, Report}).
+
+user_err(F, A) ->
+ error_msg("USER ERROR: " ++ F ++ "~n", A).
+
+config_err(F, A) ->
+ error_msg("CONFIG ERROR: " ++ F ++ "~n", A).
+
+error_msg(F, A) ->
+ ?snmpm_error("MPD: " ++ F, A).