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/manager/snmpm_mpd.erl | |
download | otp-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.erl | 1024 |
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). |