%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 1997-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_mpd). -export([init/1, reset/0, inc/1, counters/0, discarded_pdu/1, process_packet/6, generate_response_msg/5, generate_msg/5, generate_discovery_msg/4, process_taddrs/1, generate_req_id/0]). -define(SNMP_USE_V3, true). -include("snmp_types.hrl"). -include("SNMP-MPD-MIB.hrl"). -include("SNMPv2-TM.hrl"). -include("SNMP-FRAMEWORK-MIB.hrl"). -define(VMODULE,"MPD"). -include("snmp_verbosity.hrl"). -define(empty_msg_size, 24). -record(state, {v1 = false, v2c = false, v3 = false}). -record(note, {sec_engine_id, sec_model, sec_name, sec_level, ctx_engine_id, ctx_name, disco = false, req_id}). %%%----------------------------------------------------------------- %%% 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) -> ?vlog("init -> entry with" "~n Vsns: ~p", [Vsns]), {A,B,C} = erlang:now(), random:seed(A,B,C), ets:insert(snmp_agent_table, {msg_id, random:uniform(2147483647)}), ets:insert(snmp_agent_table, {req_id, random:uniform(2147483647)}), init_counters(), init_versions(Vsns, #state{}). reset() -> reset_counters(), ok. %%----------------------------------------------------------------- %% Purpose: We must calculate the length of a %% message with an empty Pdu, and zero-length community %% string. This length is used to calculate the max %% pdu size allowed for each request. This size is %% dependent on two dynamic fields, the community string %% and the pdu (varbinds actually). It is calculated %% as EmptySize + length(CommunityString) + 4. %% We assume that the length of the CommunityString is %% less than 128 (thus requiring just one octet for the %% length field (the same as the zero-length community %% string)). 4 comes from the fact that the maximum pdu %% size needs 31 bits which needs 5 * 7 bits to be %% expressed. One 7bit octet is already present in the %% empty msg, leaving 4 more 7bit octets. %% Actually, this function is not used, we use a constant instead. %%----------------------------------------------------------------- %% Ret: 24 %empty_msg() -> % M = #message{version = 'version-1', community = "", data = % #pdu{type = 'get-response', request_id = 1, % error_status = noError, error_index = 0, varbinds = []}}, % length(snmp_pdus:enc_message(M)) + 4. %%----------------------------------------------------------------- %% Func: process_packet(Packet, TDomain, TAddress, State, Log) -> %% {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_packet(Packet, TDomain, TAddress, State, NoteStore, Log) -> inc(snmpInPkts), case catch snmp_pdus:dec_message_only(binary_to_list(Packet)) of #message{version = 'version-1', vsn_hdr = Community, data = Data} when State#state.v1 =:= true -> ?vlog("v1, community: ~s", [Community]), HS = ?empty_msg_size + length(Community), v1_v2c_proc('version-1', NoteStore, Community, TDomain, TAddress, Data, HS, Log, Packet); #message{version = 'version-2', vsn_hdr = Community, data = Data} when State#state.v2c =:= true -> ?vlog("v2c, community: ~s", [Community]), HS = ?empty_msg_size + length(Community), v1_v2c_proc('version-2', NoteStore, Community, TDomain, TAddress, Data, HS, Log, Packet); #message{version = 'version-3', vsn_hdr = V3Hdr, data = Data} when State#state.v3 =:= true -> ?vlog("v3, msgID: ~p, msgFlags: ~p, msgSecModel: ~p", [V3Hdr#v3_hdr.msgID, V3Hdr#v3_hdr.msgFlags, V3Hdr#v3_hdr.msgSecurityModel]), v3_proc(NoteStore, Packet, TDomain, TAddress, V3Hdr, Data, Log); {'EXIT', {bad_version, Vsn}} -> ?vtrace("exit: bad version: ~p",[Vsn]), inc(snmpInBadVersions), {discarded, snmpInBadVersions}; {'EXIT', Reason} -> ?vtrace("exit: ~p", [Reason]), inc(snmpInASNParseErrs), {discarded, Reason}; UnknownMessage -> ?vtrace("Unknown message: ~n ~p" "~nwhen" "~n State: ~p", [UnknownMessage, State]), inc(snmpInBadVersions), {discarded, snmpInBadVersions} end. discarded_pdu(false) -> ok; discarded_pdu(Variable) -> inc(Variable). %%----------------------------------------------------------------- %% Handles a Community based message (v1 or v2c). %%----------------------------------------------------------------- v1_v2c_proc(Vsn, NoteStore, Community, snmpUDPDomain, {Ip, Udp}, Data, HS, Log, Packet) -> TAddress = tuple_to_list(Ip) ++ [Udp div 256, Udp rem 256], AgentMS = snmp_framework_mib:get_engine_max_message_size(), MgrMS = snmp_community_mib:get_target_addr_ext_mms(?snmpUDPDomain, TAddress), PduMS = case MgrMS of {ok, MMS} when MMS < AgentMS -> MMS - HS; _ -> AgentMS - HS end, case (catch snmp_pdus:dec_pdu(Data)) of Pdu when is_record(Pdu, pdu) -> Log(Pdu#pdu.type, Packet), inc_snmp_in_vars(Pdu), #pdu{request_id = ReqId} = Pdu, OkRes = {ok, Vsn, Pdu, PduMS, {community, sec_model(Vsn), Community, TAddress}}, %% Make sure that we don't process duplicate SET request %% twice. We don't know what could happen in that case. %% The mgr does, so he has to generate a new SET request. ?vdebug("PDU type: ~p", [Pdu#pdu.type]), case Pdu#pdu.type of 'set-request' -> %% Check if this message has already been processed Key = {agent, Ip, ReqId}, case snmp_note_store:get_note(NoteStore, Key) of undefined -> %% Set the processed note _after_ pdu processing. %% This makes duplicated requests be ignored even %% if pdu processing took long time. snmp_note_store:set_note(NoteStore, 100, Key, true), %% Uses ACMData that snmpa_acm knows of. %% snmpUDPDomain is implicit, since that's the only %% one we handle. OkRes; true -> {discarded, duplicate_pdu} end; _ -> OkRes end; {'EXIT', Reason} -> ?vtrace("PDU decode exit: ~p",[Reason]), inc(snmpInASNParseErrs), {discarded, Reason}; _TrapPdu -> {discarded, trap_pdu} end; v1_v2c_proc(_Vsn, _NoteStore, _Community, snmpUDPDomain, TAddress, _Data, _HS, _Log, _Packet) -> {discarded, {badarg, TAddress}}; v1_v2c_proc(_Vsn, _NoteStore, _Community, TDomain, _TAddress, _Data, _HS, _Log, _Packet) -> {discarded, {badarg, TDomain}}. 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 %%----------------------------------------------------------------- v3_proc(NoteStore, Packet, _TDomain, _TAddress, V3Hdr, Data, Log) -> case (catch v3_proc(NoteStore, Packet, V3Hdr, Data, Log)) of {'EXIT', Reason} -> exit(Reason); Result -> Result end. v3_proc(NoteStore, Packet, V3Hdr, Data, Log) -> %% 7.2.3 #v3_hdr{msgID = MsgID, msgMaxSize = MMS, msgFlags = MsgFlags, msgSecurityModel = MsgSecurityModel, msgSecurityParameters = SecParams, hdr_size = HdrSize} = V3Hdr, ?vdebug("v3_proc -> version 3 message header:" "~n msgID = ~p" "~n msgMaxSize = ~p" "~n msgFlags = ~p" "~n msgSecurityModel = ~p" "~n msgSecurityParameters = ~w", [MsgID, MMS, MsgFlags, MsgSecurityModel, SecParams]), %% 7.2.4 SecModule = get_security_module(MsgSecurityModel), %% 7.2.5 SecLevel = check_sec_level(MsgFlags), IsReportable = snmp_misc:is_reportable(MsgFlags), %% 7.2.6 ?vtrace("v3_proc -> " "~n SecModule = ~p" "~n SecLevel = ~p" "~n IsReportable = ~p", [SecModule,SecLevel,IsReportable]), SecRes = (catch SecModule:process_incoming_msg(Packet, Data, SecParams, SecLevel)), ?vtrace("v3_proc -> message processing result: " "~n SecRes: ~p", [SecRes]), {SecEngineID, SecName, ScopedPDUBytes, SecData, DiscoOrPlain} = check_sec_module_result(SecRes, V3Hdr, Data, IsReportable, Log), ?vtrace("v3_proc -> " "~n DiscoOrPlain: ~w" "~n SecEngineID: ~w" "~n SecName: ~p", [DiscoOrPlain, SecEngineID, SecName]), %% 7.2.7 #scopedPdu{contextEngineID = ContextEngineID, contextName = ContextName, data = PDU} = case (catch snmp_pdus:dec_scoped_pdu(ScopedPDUBytes)) of ScopedPDU when is_record(ScopedPDU, scopedPdu) -> ?vtrace("v3_proc -> message processing result: " "~n ScopedPDU: ~p", [ScopedPDU]), ScopedPDU; {'EXIT', Reason} -> inc(snmpInASNParseErrs), throw({discarded, Reason}) end, %% We'll have to take care of the unlikely case that we receive an %% v1 trappdu in a v3 message explicitly... if is_record(PDU, trappdu) -> inc(snmpUnknownPDUHandlers), throw({discarded, received_v1_trap}); true -> ok end, ?vlog("7.2.7 result: " "~n contextEngineID: ~w" "~n ContextName: \"~s\"", [ContextEngineID, ContextName]), if SecLevel =:= ?'SnmpSecurityLevel_authPriv' -> %% encrypted message - log decrypted pdu Log(PDU#pdu.type, {V3Hdr, ScopedPDUBytes}); true -> % otherwise, log binary Log(PDU#pdu.type, Packet) end, %% Make sure a get_bulk doesn't get too big. AgentMS = snmp_framework_mib:get_engine_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 = if AgentMS > MMS -> MMS; true -> AgentMS end, TotalLenOctets = snmp_pdus:get_encoded_length(TotMMS - 1), PduMMS = TotMMS - TotalLenOctets - 10 - HdrSize - length(ContextName) - length(ContextEngineID), ?vdebug("v3_proc -> PDU type: ~p", [PDU#pdu.type]), case PDU#pdu.type of report when DiscoOrPlain =:= discovery -> %% Discovery stage 1 response Key = {agent, MsgID}, Note = snmp_note_store:get_note(NoteStore, Key), case Note of #note{sec_engine_id = "", sec_model = _MsgSecModel, sec_name = "", sec_level = _SecLevel, ctx_engine_id = _CtxEngineID, ctx_name = _CtxName, disco = true, req_id = _ReqId} -> %% This is part of the discovery process initiated by us. %% Response to the discovery stage 1 request ?vdebug("v3_proc -> discovery stage 1 response", []), {ok, 'version-3', PDU, PduMMS, {discovery, SecEngineID}}; #note{sec_engine_id = SecEngineID, sec_model = _MsgSecModel, sec_name = SecName, sec_level = SecLevel, ctx_engine_id = _CtxEngineID, ctx_name = _CtxName, disco = true, req_id = _ReqId} -> %% This is part of the discovery process initiated by us. %% Response to the discovery stage 2 request ?vdebug("v3_proc -> discovery stage 2 response", []), {ok, 'version-3', PDU, PduMMS, discovery}; _ -> %% 7.2.11 DiscardReason = {bad_disco_note, Key, Note}, throw({discarded, DiscardReason}) end; report -> %% 7.2.11 throw({discarded, report}); 'get-response' -> %% As a result of a sent inform-request? %% 7.2.12 Key = {agent, MsgID}, Note = snmp_note_store:get_note(NoteStore, Key), case Note of #note{sec_engine_id = "", sec_model = _MsgSecModel, sec_name = "", sec_level = _SecLevel, ctx_engine_id = _CtxEngineID, ctx_name = _CtxName, disco = true, req_id = _ReqId} -> %% This is part of the discovery process initiated by us. %% Response to the discovery stage 1 request ?vdebug("v3_proc -> discovery stage 1 response", []), {ok, 'version-3', PDU, PduMMS, {discovery, SecEngineID}}; #note{sec_engine_id = SecEngineID, sec_model = _MsgSecModel, sec_name = SecName, sec_level = SecLevel, ctx_engine_id = _CtxEngineID, ctx_name = _CtxName, disco = true, req_id = _ReqId} -> %% This is part of the discovery process initiated by us. %% Response to the discovery stage 2 request ?vdebug("v3_proc -> discovery stage 2 response", []), {ok, 'version-3', PDU, PduMMS, discovery}; #note{sec_engine_id = SecEngineID, sec_model = MsgSecurityModel, sec_name = SecName, sec_level = SecLevel, ctx_engine_id = ContextEngineID, ctx_name = ContextName, disco = false, req_id = _ReqId} -> {ok, 'version-3', PDU, PduMMS, undefined}; _ -> inc(snmpUnknownPDUHandlers), throw({discarded, {no_outstanding_req, MsgID}}) end; 'snmpv2-trap' -> inc(snmpUnknownPDUHandlers), throw({discarded, received_v2_trap}); Type -> %% 7.2.13 SnmpEngineID = snmp_framework_mib:get_engine_id(), ?vtrace("v3_proc -> SnmpEngineID = ~w", [SnmpEngineID]), case SecEngineID of SnmpEngineID when (DiscoOrPlain =:= discovery) -> %% This is a discovery step 2 message! ?vtrace("v3_proc -> discovery stage 2", []), generate_discovery2_report_msg(MsgID, MsgSecurityModel, SecName, SecLevel, ContextEngineID, ContextName, SecData, PDU, Log); SnmpEngineID when (DiscoOrPlain =:= plain) -> %% 4.2.2.1.1 - we don't handle proxys yet => we only %% handle ContextEngineID to ourselves case ContextEngineID of SnmpEngineID -> %% Uses ACMData that snmpa_acm knows of. {ok, 'version-3', PDU, PduMMS, {v3, MsgID, MsgSecurityModel, SecName, SecLevel, ContextEngineID, ContextName, SecData}}; _ -> %% 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, ContextEngineID}, {contextName, ContextName}]}, case generate_v3_report_msg(MsgID, MsgSecurityModel, Data, ErrorInfo, Log) of {ok, Report} when NIsReportable =:= true -> {discarded, snmpUnknownPDUHandlers, Report}; _ -> {discarded, snmpUnknownPDUHandlers} end end; "" -> %% This is a discovery step 1 message!! ?vtrace("v3_proc -> discovery step 1", []), generate_discovery1_report_msg(MsgID, MsgSecurityModel, SecName, SecLevel, ContextEngineID, ContextName, SecData, PDU, Log); _ -> {discarded, {badSecurityEngineID, SecEngineID}} end end. get_security_module(?SEC_USM) -> snmpa_usm; get_security_module(_) -> inc(snmpUnknownSecurityModels), throw({discarded, snmpUnknownSecurityModels}). check_sec_level([MsgFlag]) -> SecLevel = MsgFlag band 3, if SecLevel == 2 -> inc(snmpInvalidMsgs), throw({discarded, snmpInvalidMsgs}); true -> SecLevel end; check_sec_level(Unknown) -> ?vlog("invalid msgFlags: ~p",[Unknown]), inc(snmpInvalidMsgs), throw({discarded, snmpInvalidMsgs}). check_sec_module_result(Res, V3Hdr, Data, IsReportable, Log) -> case Res of {ok, X} -> X; {error, Reason, []} -> % case 7.2.6 b ?vdebug("security module result [7.2.6-b]:" "~n Reason: ~p", [Reason]), throw({discarded, {securityError, Reason}}); {error, Reason, ErrorInfo} when IsReportable == true -> % case 7.2.6 a ?vdebug("security module result when reportable [7.2.6-a]:" "~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} -> throw({discarded, {securityError, Reason}, Report}); {discarded, _SomeOtherReason} -> throw({discarded, {securityError, Reason}}) end; {error, Reason, ErrorInfo} -> ?vdebug("security module result when not reportable:" "~n Reason: ~p" "~n ErrorInfo: ~p", [Reason, ErrorInfo]), throw({discarded, {securityError, Reason}}); Else -> ?vdebug("security module result:" "~n Else: ~p", [Else]), throw({discarded, {securityError, Else}}) end. get_scoped_pdu(D) when is_list(D) -> (catch snmp_pdus:dec_scoped_pdu(D)); get_scoped_pdu(D) -> D. %%----------------------------------------------------------------- %% Executed when a response or report message is generated. %%----------------------------------------------------------------- generate_response_msg(Vsn, RePdu, Type, ACMData, Log) -> generate_response_msg(Vsn, RePdu, Type, ACMData, Log, 1). generate_response_msg(Vsn, RePdu, Type, {community, _SecModel, Community, _IpUdp}, Log, _) -> case catch snmp_pdus:enc_pdu(RePdu) of {'EXIT', Reason} -> user_err("failed encoding pdu: " "(pdu: ~w, community: ~w): ~n~w", [RePdu, Community, Reason]), {discarded, Reason}; PduBytes -> Message = #message{version = Vsn, vsn_hdr = Community, data = PduBytes}, case catch list_to_binary( snmp_pdus:enc_message_only(Message)) of {'EXIT', Reason} -> user_err("failed encoding message only " "(pdu: ~w, community: ~w): ~n~w", [RePdu, Community, Reason]), {discarded, Reason}; Packet -> MMS = snmp_framework_mib:get_engine_max_message_size(), case size(Packet) of Len when Len =< MMS -> Log(Type, Packet), inc_snmp_cnt_vars(Type, RePdu), inc_snmp_out_vars(RePdu), {ok, Packet}; Len -> ?vlog("pdu to big:" "~n Max message size: ~p" "~n Encoded message size: ~p", [MMS,Len]), too_big(Vsn, RePdu, Community, Log, MMS, Len) end end end; generate_response_msg(Vsn, RePdu, Type, {v3, MsgID, MsgSecurityModel, SecName, SecLevel, ContextEngineID, ContextName, SecData}, Log, N) -> %% rfc2272: 7.1 steps 6-8 ScopedPDU = #scopedPdu{contextEngineID = ContextEngineID, contextName = ContextName, data = RePdu}, case catch snmp_pdus:enc_scoped_pdu(ScopedPDU) of {'EXIT', Reason} -> user_err("failed encoded scoped pdu " "(pdu: ~w, contextName: ~w): ~n~w", [RePdu, ContextName, Reason]), {discarded, Reason}; ScopedPDUBytes -> AgentMS = snmp_framework_mib:get_engine_max_message_size(), V3Hdr = #v3_hdr{msgID = MsgID, msgMaxSize = AgentMS, msgFlags = snmp_misc:mk_msg_flags(Type, SecLevel), msgSecurityModel = MsgSecurityModel}, Message = #message{version = Vsn, vsn_hdr = V3Hdr, data = ScopedPDUBytes}, %% We know that the security model is valid when we %% generate a response. SecModule = case MsgSecurityModel of ?SEC_USM -> snmpa_usm end, SecEngineID = snmp_framework_mib:get_engine_id(), ?vtrace("generate_response_msg -> SecEngineID: ~w", [SecEngineID]), 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}; OutMsg when is_list(OutMsg) -> %% Check the packet size. Send the msg even %% if it's larger than the mgr can handle - it %% will be dropped. Just check against the %% internal size. For GET-BULk responses: we %% *know* that we're within the right limits, %% because of the calculation we do when we %% receive the bulk-request. Packet = list_to_binary(OutMsg), case size(Packet) of Len when Len =< AgentMS -> if SecLevel =:= 3 -> %% encrypted - log decrypted pdu Log(Type, {V3Hdr, ScopedPDUBytes}); true -> %% otherwise log the entire msg Log(Type, Packet) end, inc_snmp_cnt_vars(Type, RePdu), inc_snmp_out_vars(RePdu), {ok, Packet}; Len when N =:= 2 -> ?vlog("packet max size exceeded: " "~n Max: ~p" "~n Len: ~p", [AgentMS,Len]), inc(snmpSilentDrops), {discarded, tooBig}; Len -> ?vlog("packet max size exceeded: " "~n N: ~p" "~n Max: ~p" "~n Len: ~p", [N, AgentMS, Len]), TooBigPdu = RePdu#pdu{error_status = tooBig, error_index = 0, varbinds = []}, generate_response_msg(Vsn, TooBigPdu, Type, {v3, MsgID, MsgSecurityModel, SecName, SecLevel, ContextEngineID, ContextName, SecData}, Log, N+1) end end end. generate_v3_report_msg(MsgID, MsgSecurityModel, Data, ErrorInfo, Log) -> {Varbind, SecName, Opts} = ErrorInfo, ReqId = if is_record(Data, scopedPdu) -> (Data#scopedPdu.data)#pdu.request_id; true -> 0 %% RFC2572, 7.1.3.c.4 end, ?vtrace("Report ReqId: ~p",[ReqId]), Pdu = #pdu{type = report, request_id = ReqId, error_status = noError, error_index = 0, varbinds = [Varbind]}, SecLevel = snmp_misc:get_option(securityLevel, Opts, 0), SnmpEngineID = snmp_framework_mib:get_engine_id(), ContextEngineID = snmp_misc:get_option(contextEngineID, Opts, SnmpEngineID), ContextName = snmp_misc:get_option(contextName, Opts, ""), SecData = snmp_misc:get_option(sec_data, Opts, []), generate_response_msg('version-3', Pdu, report, {v3, MsgID, MsgSecurityModel, SecName, SecLevel, ContextEngineID, ContextName, SecData}, Log). %% req_id(#scopedPdu{data = #pdu{request_id = ReqId}}) -> %% ?vtrace("Report ReqId: ~p",[ReqId]), %% ReqId; %% req_id(_) -> %% 0. % RFC2572, 7.1.3.c.4 %% maybe_generate_discovery1_report_msg() -> %% case (catch DiscoveryHandler:handle_discovery1(Ip, Udp, EngineId)) of %% {ok, Entry} when is_record(Entry, snmp_discovery_data1) -> %% ok; %% ignore -> %% ok; %% {error, Reason} -> %% Response to stage 1 discovery message (terminating, i.e. from the manager) generate_discovery1_report_msg(MsgID, MsgSecurityModel, SecName, SecLevel, ContextEngineID, ContextName, {SecData, Oid, Value}, #pdu{request_id = ReqId}, Log) -> ?vtrace("generate_discovery1_report_msg -> entry with" "~n ReqId: ~p" "~n Value: ~p", [ReqId, Value]), Varbind = #varbind{oid = Oid, variabletype = 'Counter32', value = Value, org_index = 1}, PduOut = #pdu{type = report, request_id = ReqId, error_status = noError, error_index = 0, varbinds = [Varbind]}, case generate_response_msg('version-3', PduOut, report, {v3, MsgID, MsgSecurityModel, SecName, SecLevel, ContextEngineID, ContextName, SecData}, Log) of {ok, Packet} -> {discovery, Packet}; Error -> Error end. %% Response to stage 2 discovery message (terminating, i.e. from the manager) generate_discovery2_report_msg(MsgID, MsgSecurityModel, SecName, SecLevel, ContextEngineID, ContextName, SecData, #pdu{request_id = ReqId}, Log) -> ?vtrace("generate_discovery2_report_msg -> entry with" "~n ReqId: ~p", [ReqId]), SecModule = get_security_module(MsgSecurityModel), Vb = SecModule:current_statsNotInTimeWindows_vb(), PduOut = #pdu{type = report, request_id = ReqId, error_status = noError, error_index = 0, varbinds = [Vb]}, case generate_response_msg('version-3', PduOut, report, {v3, MsgID, MsgSecurityModel, SecName, SecLevel, ContextEngineID, ContextName, SecData}, Log) of {ok, Packet} -> {discovery, Packet}; Error -> Error end. too_big(Vsn, Pdu, Community, Log, _MMS, _Len) when Pdu#pdu.type =:= 'get-response' -> ErrPdu = if Vsn =:= 'version-1' -> %% 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), Pdu#pdu{error_status = tooBig, error_index = 0, varbinds = V}; true -> %% In v2, varbinds should be empty (reasonable!) Pdu#pdu{error_status = tooBig, error_index = 0, varbinds = []} end, case catch snmp_pdus:enc_pdu(ErrPdu) of {'EXIT', Reason} -> user_err("failed encoding pdu (pdu: ~w, community: ~w): ~n~w", [ErrPdu, Community, Reason]), {discarded, Reason}; PduBytes -> Message = #message{version = Vsn, vsn_hdr = Community, data = PduBytes}, case catch snmp_pdus:enc_message_only(Message) of {'EXIT', Reason} -> user_err("failed encoding message only" "(pdu: ~w, community: ~w): ~n~w", [ErrPdu, Community, Reason]), {discarded, Reason}; Packet -> Bin = list_to_binary(Packet), Log(Pdu#pdu.type, Bin), inc_snmp_out_vars(ErrPdu), {ok, Bin} end end; too_big(_Vsn, Pdu, _Community, _Log, MMS, Len) -> user_err("encoded pdu, ~p bytes, exceeded " "max message size of ~p bytes. Pdu: ~n~w", [Len, MMS, Pdu]), {discarded, tooBig}. set_vb_null([Vb | Vbs]) -> [Vb#varbind{variabletype = 'NULL', value = 'NULL'} | set_vb_null(Vbs)]; set_vb_null([]) -> []. %%----------------------------------------------------------------- %% Executed when a message that isn't a response is generated, i.e. %% a trap or an inform. %%----------------------------------------------------------------- generate_msg(Vsn, _NoteStore, Pdu, {community, Community}, To) -> Message = #message{version = Vsn, vsn_hdr = Community, data = Pdu}, case catch list_to_binary(snmp_pdus:enc_message(Message)) of {'EXIT', Reason} -> user_err("failed encoding message " "(pdu: ~w, community: ~w): ~n~w", [Pdu, Community, Reason]), {discarded, Reason}; Packet -> AgentMax = snmp_framework_mib:get_engine_max_message_size(), case size(Packet) of Len when Len =< AgentMax -> {ok, mk_v1_v2_packet_list(To, Packet, Len, Pdu)}; Len -> ?vlog("packet max size exceeded: " "~n Max: ~p" "~n Len: ~p", [AgentMax, Len]), {discarded, tooBig} end end; generate_msg('version-3', NoteStore, Pdu, {v3, ContextEngineID, ContextName}, To) -> %% rfc2272: 7.1.6 ScopedPDU = #scopedPdu{contextEngineID = ContextEngineID, contextName = ContextName, data = Pdu}, case (catch snmp_pdus:enc_scoped_pdu(ScopedPDU)) of {'EXIT', Reason} -> user_err("failed encoding scoped pdu " "(pdu: ~w, contextName: ~w): ~n~w", [Pdu, ContextName, Reason]), {discarded, Reason}; ScopedPDUBytes -> {ok, mk_v3_packet_list(NoteStore, To, ScopedPDUBytes, Pdu, ContextEngineID, ContextName)} end. generate_discovery_msg(NoteStore, Pdu, MsgData, To) -> Timeout = 1500, generate_discovery_msg(NoteStore, Pdu, MsgData, Timeout, To). generate_discovery_msg(NoteStore, Pdu, MsgData, Timeout, To) -> {SecData, ContextEngineID, ContextName} = MsgData, {SecModel, SecName, SecLevelFlag, TargetName} = SecData, {ManagerEngineId, InitialUserName} = case get_target_engine_id(TargetName) of {ok, discovery} -> {"", ""}; % Discovery stage 1 {ok, {discovery, IUN}} -> {"", IUN}; % Discovery stage 1 {ok, TargetEngineId} -> {TargetEngineId, ""} % Discovery stage 2 end, generate_discovery_msg(NoteStore, Pdu, ContextEngineID, ContextName, SecModel, SecName, SecLevelFlag, ManagerEngineId, InitialUserName, Timeout, To). generate_discovery_msg(NoteStore, Pdu, ContextEngineID, ContextName, SecModel, _SecName, _SecLevelFlag, "" = ManagerEngineID, InitialUserName, Timeout, To) -> %% Discovery step 1 uses SecLevel = noAuthNoPriv SecName = "", SecLevelFlag = 0, % ?'SnmpSecurityLevel_noAuthNoPriv', generate_discovery_msg2(NoteStore, Pdu, ContextEngineID, ManagerEngineID, SecModel, SecName, SecLevelFlag, InitialUserName, ContextName, Timeout, To); generate_discovery_msg(NoteStore, Pdu, ContextEngineID, ContextName, SecModel, SecName, SecLevelFlag, ManagerEngineID, InitialUserName, Timeout, To) -> %% SecLevelFlag = 1, % ?'SnmpSecurityLevel_authNoPriv', generate_discovery_msg2(NoteStore, Pdu, ContextEngineID, ManagerEngineID, SecModel, SecName, SecLevelFlag, InitialUserName, ContextName, Timeout, To). generate_discovery_msg2(NoteStore, Pdu, ContextEngineID, ManagerEngineID, SecModel, SecName, SecLevelFlag, InitialUserName, ContextName, Timeout, To) -> %% rfc2272: 7.1.6 ScopedPDU = #scopedPdu{contextEngineID = ContextEngineID, contextName = ContextName, data = Pdu}, case (catch snmp_pdus:enc_scoped_pdu(ScopedPDU)) of {'EXIT', Reason} -> user_err("failed encoding scoped pdu " "(pdu: ~w, contextName: ~w): ~n~w", [Pdu, ContextName, Reason]), {discarded, Reason}; ScopedPDUBytes -> {ok, generate_discovery_msg(NoteStore, To, Pdu, ScopedPDUBytes, ContextEngineID, ManagerEngineID, SecModel, SecName, SecLevelFlag, InitialUserName, ContextName, Timeout)} end. %% Timeout is in msec but note timeout is in 1/10 seconds discovery_note_timeout(Timeout) -> (Timeout div 100) + 1. generate_discovery_msg(NoteStore, {?snmpUDPDomain, [A,B,C,D,U1,U2]}, Pdu, ScopedPduBytes, ContextEngineID, ManagerEngineID, SecModel, SecName, SecLevelFlag, InitialUserName, ContextName, Timeout) -> %% 7.1.7 ?vdebug("generate_discovery_msg -> 7.1.7 (~w)", [ManagerEngineID]), MsgID = generate_msg_id(), PduType = Pdu#pdu.type, MsgFlags = mk_msg_flags(PduType, SecLevelFlag), 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.9b ?vdebug("generate_discovery_msg -> 7.1.9b", []), case generate_sec_discovery_msg(Message, SecModule, ManagerEngineID, SecName, SecLevelFlag, InitialUserName) of {ok, Packet} -> %% 7.1.9c %% Store in cache for Timeout msec. NoteTimeout = discovery_note_timeout(Timeout), ?vdebug("generate_discovery_msg -> 7.1.9c [~w]", [NoteTimeout]), %% The request id is just in case when we receive a %% report with incorrect securityModel and/or securityLevel Key = {agent, MsgID}, Note = #note{sec_engine_id = ManagerEngineID, sec_model = SecModel, sec_name = SecName, sec_level = SecLevelFlag, ctx_engine_id = ContextEngineID, ctx_name = ContextName, disco = true, req_id = Pdu#pdu.request_id}, snmp_note_store:set_note(NoteStore, Timeout, Key, Note), %% Log(Packet), inc_snmp_out_vars(Pdu), ?vdebug("generate_discovery_msg -> done", []), {Packet, {{A,B,C,D}, U1 bsl 8 + U2}}; Error -> throw(Error) end. generate_sec_discovery_msg(Message, SecModule, SecEngineID, SecName, SecLevelFlag, InitialUserName) -> case (catch SecModule:generate_discovery_msg(Message, SecEngineID, SecName, SecLevelFlag, InitialUserName)) 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. process_taddrs(Dests) -> ?vtrace("process_taddrs -> entry with" "~n Dests: ~p", [Dests]), process_taddrs(Dests, []). process_taddrs([], Acc) -> ?vtrace("process_taddrs -> entry when done with" "~n Acc: ~p", [Acc]), lists:reverse(Acc); %% v3 process_taddrs([{{?snmpUDPDomain, [A,B,C,D,U1,U2]}, SecData} | T], Acc) -> ?vtrace("process_taddrs -> entry when v3 with" "~n A: ~p" "~n B: ~p" "~n C: ~p" "~n D: ~p" "~n U1: ~p" "~n U2: ~p" "~n SecData: ~p", [A, B, C, D, U1, U2, SecData]), Entry = {{snmpUDPDomain, {{A,B,C,D}, U1 bsl 8 + U2}}, SecData}, process_taddrs(T, [Entry | Acc]); %% Bad v3 process_taddrs([{{TDomain, TAddr}, _SecData} | T], Acc) -> ?vtrace("process_taddrs -> entry when bad v3 with" "~n TDomain: ~p" "~n TAddr: ~p", [TDomain, TAddr]), user_err("Bad TDomain/TAddr: ~w/~w", [TDomain, TAddr]), process_taddrs(T, Acc); %% v1 & v2 process_taddrs([{?snmpUDPDomain, [A,B,C,D,U1,U2]} | T], Acc) -> ?vtrace("process_taddrs -> entry when v1/v2 with" "~n A: ~p" "~n B: ~p" "~n C: ~p" "~n D: ~p" "~n U1: ~p" "~n U2: ~p", [A, B, C, D, U1, U2]), Entry = {snmpUDPDomain, {{A,B,C,D}, U1 bsl 8 + U2}}, process_taddrs(T, [Entry | Acc]); %% Bad v1 or v2 process_taddrs([{TDomain, TAddr} | T], Acc) -> ?vtrace("process_taddrs -> entry when bad v1/v2 with" "~n TDomain: ~p" "~n TAddr: ~p", [TDomain, TAddr]), user_err("Bad TDomain/TAddr: ~w/~w", [TDomain, TAddr]), process_taddrs(T, Acc); process_taddrs(Crap, Acc) -> throw({error, {taddrs_crap, Crap, Acc}}). mk_v1_v2_packet_list(To, Packet, Len, Pdu) -> mk_v1_v2_packet_list(To, Packet, Len, Pdu, []). mk_v1_v2_packet_list([], _Packet, _Len, _Pdu, Acc) -> lists:reverse(Acc); %% This (old) clause is for backward compatibillity reasons %% If this is called, then the filter function is not used mk_v1_v2_packet_list([{?snmpUDPDomain, [A,B,C,D,U1,U2]} | T], Packet, Len, Pdu, Acc) -> %% Sending from default UDP port inc_snmp_out_vars(Pdu), Entry = {snmpUDPDomain, {{A,B,C,D}, U1 bsl 8 + U2}, Packet}, mk_v1_v2_packet_list(T, Packet, Len, Pdu, [Entry | Acc]); %% This is the new clause %% This is only called if the actual target was accepted %% (by the filter module) mk_v1_v2_packet_list([{Domain, Addr} | T], Packet, Len, Pdu, Acc) -> %% Sending from default UDP port inc_snmp_out_vars(Pdu), Entry = {Domain, Addr, Packet}, mk_v1_v2_packet_list(T, Packet, Len, Pdu, [Entry | Acc]). get_max_message_size() -> snmp_framework_mib:get_engine_max_message_size(). mk_msg_flags(PduType, SecLevel) -> snmp_misc:mk_msg_flags(PduType, SecLevel). mk_v3_packet_entry(NoteStore, Domain, Addr, {SecModel, SecName, SecLevel, TargetAddrName}, ScopedPDUBytes, Pdu, ContextEngineID, ContextName) -> %% 7.1.7 ?vtrace("mk_v3_packet_entry -> entry - 7.1.7", []), MsgID = generate_msg_id(), PduType = Pdu#pdu.type, MsgFlags = mk_msg_flags(PduType, 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 = case SecModel of ?SEC_USM -> snmpa_usm end, %% 7.1.9a ?vtrace("mk_v3_packet_entry -> sec engine id - 7.1.9a", []), SecEngineID = case PduType of 'snmpv2-trap' -> snmp_framework_mib:get_engine_id(); _ -> %% This is the implementation dependent target engine id %% procedure. case get_target_engine_id(TargetAddrName) of {ok, discovery} -> config_err("Discovery has not yet been performed for " "snmpTargetAddrName ~p~n", [TargetAddrName]), throw({discarded, {discovery, TargetAddrName}}); {ok, TargetEngineId} -> ?vtrace("TargetEngineId: ~p", [TargetEngineId]), TargetEngineId; undefined -> config_err("Can't find engineID for " "snmpTargetAddrName ~p~n", [TargetAddrName]), "" % this will trigger error in secmodule end end, ?vdebug("mk_v3_packet_entry -> secEngineID: ~p", [SecEngineID]), %% 7.1.9b case catch SecModule:generate_outgoing_msg(Message, SecEngineID, SecName, [], SecLevel) of {'EXIT', Reason} -> config_err("~p (message: ~p)", [Reason, Message]), skip; {error, Reason} -> ?vlog("~n ~w error ~p\n", [SecModule, Reason]), skip; OutMsg when is_list(OutMsg) -> %% 7.1.9c %% Store in cache for 150 sec. Packet = list_to_binary(OutMsg), ?vdebug("mk_v3_packet_entry -> generated: ~w bytes", [size(Packet)]), Data = if SecLevel =:= 3 -> %% encrypted - log decrypted pdu {Packet, {V3Hdr, ScopedPDUBytes}}; true -> %% otherwise log the entire msg Packet end, CacheKey = {agent, MsgID}, CacheVal = #note{sec_engine_id = SecEngineID, sec_model = SecModel, sec_name = SecName, sec_level = SecLevel, ctx_engine_id = ContextEngineID, ctx_name = ContextName, disco = false, req_id = Pdu#pdu.request_id}, snmp_note_store:set_note(NoteStore, 1500, CacheKey, CacheVal), inc_snmp_out_vars(Pdu), {ok, {Domain, Addr, Data}} end. mk_v3_packet_list(NoteStore, To, ScopedPDUBytes, Pdu, ContextEngineID, ContextName) -> mk_v3_packet_list(NoteStore, To, ScopedPDUBytes, Pdu, ContextEngineID, ContextName, []). mk_v3_packet_list(_, [], _ScopedPDUBytes, _Pdu, _ContextEngineID, _ContextName, Acc) -> lists:reverse(Acc); %% This clause is for backward compatibillity reasons %% If this is called the filter function is not used mk_v3_packet_list(NoteStore, [{{?snmpUDPDomain, [A,B,C,D,U1,U2]}, SecData} | T], ScopedPDUBytes, Pdu, ContextEngineID, ContextName, Acc) -> case mk_v3_packet_entry(NoteStore, snmpUDPDomain, {{A,B,C,D}, U1 bsl 8 + U2}, SecData, ScopedPDUBytes, Pdu, ContextEngineID, ContextName) of skip -> mk_v3_packet_list(NoteStore, T, ScopedPDUBytes, Pdu, ContextEngineID, ContextName, Acc); {ok, Entry} -> mk_v3_packet_list(NoteStore, T, ScopedPDUBytes, Pdu, ContextEngineID, ContextName, [Entry | Acc]) end; %% This is the new clause %% This is only called if the actual target was accepted %% (by the filter module) mk_v3_packet_list(NoteStore, [{{Domain, Addr}, SecData} | T], ScopedPDUBytes, Pdu, ContextEngineID, ContextName, Acc) -> case mk_v3_packet_entry(NoteStore, Domain, Addr, SecData, ScopedPDUBytes, Pdu, ContextEngineID, ContextName) of skip -> mk_v3_packet_list(NoteStore, T, ScopedPDUBytes, Pdu, ContextEngineID, ContextName, Acc); {ok, Entry} -> mk_v3_packet_list(NoteStore, T, ScopedPDUBytes, Pdu, ContextEngineID, ContextName, [Entry | Acc]) end. generate_msg_id() -> gen(msg_id). generate_req_id() -> gen(req_id). gen(Id) -> case ets:update_counter(snmp_agent_table, Id, 1) of N when N =< 2147483647 -> N; _N -> ets:insert(snmp_agent_table, {Id, 0}), 0 end. get_target_engine_id(TargetAddrName) -> snmp_target_mib:get_target_engine_id(TargetAddrName). sec_module(?SEC_USM) -> snmpa_usm. %%----------------------------------------------------------------- %% 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}). %%----------------------------------------------------------------- %% Counter functions %%----------------------------------------------------------------- init_counters() -> F = fun(Counter) -> maybe_create_counter(Counter) end, lists:map(F, counters()). reset_counters() -> F = fun(Counter) -> init_counter(Counter) end, lists:map(F, counters()). maybe_create_counter(Counter) -> case ets:lookup(snmp_agent_table, Counter) of [_] -> ok; _ -> init_counter(Counter) end. init_counter(Counter) -> ets:insert(snmp_agent_table, {Counter, 0}). counters() -> [ 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) -> ets:update_counter(snmp_agent_table, Name, 1). inc(Name, N) -> ets:update_counter(snmp_agent_table, Name, N). inc_snmp_in_vars(#pdu{type = Type}) -> inc_in_type(Type). inc_snmp_cnt_vars(_, #pdu{error_status = ErrStat}) when ErrStat =/= noError -> ok; inc_snmp_cnt_vars('get-request', #pdu{varbinds = Vbs}) -> inc(snmpInTotalReqVars, length(Vbs)); inc_snmp_cnt_vars('get-next-request', #pdu{varbinds = Vbs}) -> inc(snmpInTotalReqVars, length(Vbs)); inc_snmp_cnt_vars('set-request', #pdu{varbinds = Vbs}) -> inc(snmpInTotalSetVars, length(Vbs)); inc_snmp_cnt_vars(_, _) -> ok. inc_snmp_out_vars(#pdu{type = Type, error_status = ErrorStatus}) -> inc(snmpOutPkts), inc_out_err(ErrorStatus), inc_out_vars_2(Type); inc_snmp_out_vars(TrapPdu) when is_record(TrapPdu, trappdu) -> inc(snmpOutPkts), inc(snmpOutTraps). inc_out_vars_2('get-response') -> inc(snmpOutGetResponses); inc_out_vars_2('get-request') -> inc(snmpOutGetRequests); inc_out_vars_2('get-next-request') -> inc(snmpOutGetNexts); inc_out_vars_2('set-request') -> inc(snmpOutSetRequests); inc_out_vars_2(_) -> 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); % snmpOutReadOnlys is not used any more (rfc1213) %inc_out_err(readOnly) -> inc(snmpOutReadOnlys); inc_out_err(_) -> ok. inc_in_type('get-request') -> inc(snmpInGetRequests); inc_in_type('get-next-request') -> inc(snmpInGetNexts); inc_in_type('set-request') -> inc(snmpInSetRequests); inc_in_type(_) -> ok. user_err(F, A) -> snmpa_error:user_err(F, A). config_err(F, A) -> snmpa_error:config_err(F, A).