1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 2004-2015. All Rights Reserved. 5%% 6%% Licensed under the Apache License, Version 2.0 (the "License"); 7%% you may not use this file except in compliance with the License. 8%% You may obtain a copy of the License at 9%% 10%% http://www.apache.org/licenses/LICENSE-2.0 11%% 12%% Unless required by applicable law or agreed to in writing, software 13%% distributed under the License is distributed on an "AS IS" BASIS, 14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15%% See the License for the specific language governing permissions and 16%% limitations under the License. 17%% 18%% %CopyrightEnd% 19%% 20 21-module(snmpm_mpd). 22 23-export([init/1, 24 25 process_msg/7, process_msg/6, 26 generate_msg/5, generate_response_msg/4, 27 28 next_msg_id/0, 29 next_req_id/0, 30 31 reset/1, 32 inc/1]). 33 34-define(SNMP_USE_V3, true). 35-include("snmp_types.hrl"). 36-include("snmpm_internal.hrl"). 37-include("SNMP-MPD-MIB.hrl"). 38-include("SNMPv2-TM.hrl"). 39 40-define(VMODULE,"MPD"). 41-include("snmp_verbosity.hrl"). 42 43-define(empty_msg_size, 24). 44 45-record(state, {v1 = false, v2c = false, v3 = false}). 46 47 48%%%----------------------------------------------------------------- 49%%% This module implemets the Message Processing and Dispatch part of 50%%% the multi-lingual SNMP agent. 51%%% 52%%% The MPD is responsible for: 53%%% *) call the security module (auth/priv). 54%%% *) decoding the message into a PDU. 55%%% *) decide a suitable Access Control Model, and provide it with 56%%% the data it needs. 57%%% *) maintaining SNMP counters. 58%%% 59%%% In order to take care of the different versions of counters, it 60%%% implements and maintains the union of all SNMP counters (i.e. from 61%%% rfc1213 and from rfc1907). It is up to the administrator of the 62%%% agent to load the correct MIB. Note that this module implements 63%%% the counters only, it does not provide instrumentation functions 64%%% for the counters. 65%%% 66%%% With the terms defined in rfc2271, this module implememts part 67%%% of the Dispatcher and the Message Processing functionality. 68%%%----------------------------------------------------------------- 69init(Vsns) -> 70 ?vdebug("init -> entry with ~p", [Vsns]), 71 random:seed(erlang:phash2([node()]), 72 erlang:monotonic_time(), 73 erlang:unique_integer()), 74 snmpm_config:cre_counter(msg_id, random:uniform(2147483647)), 75 snmpm_config:cre_counter(req_id, random:uniform(2147483647)), 76 init_counters(), 77 State = init_versions(Vsns, #state{}), 78 init_usm(State#state.v3), 79 ?vtrace("init -> done when ~p", [State]), 80 State. 81 82reset(#state{v3 = V3}) -> 83 reset_counters(), 84 reset_usm(V3). 85 86 87%%----------------------------------------------------------------- 88%% Func: process_msg(Packet, TDomain, TAddress, State) -> 89%% {ok, SnmpVsn, Pdu, PduMS, ACMData} | {discarded, Reason} 90%% Types: Packet = binary() 91%% TDomain = snmpUDPDomain | atom() 92%% TAddress = {Ip, Udp} 93%% State = #state 94%% Purpose: This is the main Message Dispatching function. (see 95%% section 4.2.1 in rfc2272) 96%%----------------------------------------------------------------- 97process_msg(Msg, Domain, Ip, Port, State, NoteStore, Logger) -> 98 process_msg(Msg, Domain, {Ip, Port}, State, NoteStore, Logger). 99 100process_msg(Msg, Domain, Addr, State, NoteStore, Logger) -> 101 inc(snmpInPkts), 102 103 case (catch snmp_pdus:dec_message_only(binary_to_list(Msg))) of 104 105 %% Version 1 106 #message{version = 'version-1', vsn_hdr = Community, data = Data} 107 when State#state.v1 =:= true -> 108 HS = ?empty_msg_size + length(Community), 109 process_v1_v2c_msg( 110 'version-1', NoteStore, Msg, Domain, Addr, 111 Community, Data, HS, Logger); 112 113 %% Version 2 114 #message{version = 'version-2', vsn_hdr = Community, data = Data} 115 when State#state.v2c =:= true -> 116 HS = ?empty_msg_size + length(Community), 117 process_v1_v2c_msg( 118 'version-2', NoteStore, Msg, Domain, Addr, 119 Community, Data, HS, Logger); 120 121 %% Version 3 122 #message{version = 'version-3', vsn_hdr = H, data = Data} 123 when State#state.v3 =:= true -> 124 ?vlog("v3:" 125 "~n msgID: ~p" 126 "~n msgFlags: ~p" 127 "~n msgSecModel: ~p", 128 [H#v3_hdr.msgID,H#v3_hdr.msgFlags,H#v3_hdr.msgSecurityModel]), 129 process_v3_msg(NoteStore, Msg, H, Data, Addr, Logger); 130 131 %% Crap 132 {'EXIT', {bad_version, Vsn}} -> 133 ?vinfo("exit: bad version: ~p",[Vsn]), 134 inc(snmpInBadVersions), 135 {discarded, snmpInBadVersions}; 136 137 %% More crap 138 {'EXIT', Reason} -> 139 ?vinfo("exit: ~p",[Reason]), 140 inc(snmpInASNParseErrs), 141 {discarded, Reason}; 142 143 %% Really crap 144 Crap -> 145 ?vinfo("unknown message: " 146 "~n ~p",[Crap]), 147 inc(snmpInBadVersions), 148 {discarded, snmpInBadVersions} 149 end. 150 151 152%%----------------------------------------------------------------- 153%% Handles a Community based message (v1 or v2c). 154%%----------------------------------------------------------------- 155process_v1_v2c_msg( 156 Vsn, _NoteStore, Msg, Domain, Addr, Community, Data, HS, Log) -> 157 158 ?vdebug("process_v1_v2c_msg -> entry with" 159 "~n Vsn: ~p" 160 "~n Domain: ~p" 161 "~n Addr: ~p" 162 "~n Community: ~p" 163 "~n HS: ~p", [Vsn, Domain, Addr, Community, HS]), 164 165 {TDomain, TAddress} = 166 try 167 {snmp_conf:mk_tdomain(Domain), 168 snmp_conf:mk_taddress(Domain, Addr)} 169 catch 170 throw:{error, TReason} -> 171 throw({discarded, {badarg, Domain, TReason}}) 172 end, 173 174 Max = get_max_message_size(), 175 AgentMax = get_agent_max_message_size(Domain, Addr), 176 PduMS = pdu_ms(Max, AgentMax, HS), 177 178 ?vtrace("process_v1_v2c_msg -> PduMS: ~p", [PduMS]), 179 180 case (catch snmp_pdus:dec_pdu(Data)) of 181 Pdu when is_record(Pdu, pdu) -> 182 ?vtrace("process_v1_v2c_msg -> was a pdu", []), 183 Log(Msg), 184 inc_snmp_in(Pdu), 185 MsgData = {Community, sec_model(Vsn), TDomain, TAddress}, 186 {ok, Vsn, Pdu, PduMS, MsgData}; 187 188 Trap when is_record(Trap, trappdu) -> 189 ?vtrace("process_v1_v2c_msg -> was a trap", []), 190 Log(Msg), 191 inc_snmp_in(Trap), 192 MsgData = {Community, sec_model(Vsn), TDomain, TAddress}, 193 {ok, Vsn, Trap, PduMS, MsgData}; 194 195 {'EXIT', Reason} -> 196 ?vlog("process_v1_v2c_msg -> failed decoding PDU: " 197 "~n Reason: ~p", [Reason]), 198 inc(snmpInASNParseErrs), 199 {discarded, Reason} 200 end. 201 202pdu_ms(MgrMMS, AgentMMS, HS) when AgentMMS < MgrMMS -> 203 AgentMMS - HS; 204pdu_ms(MgrMMS, _AgentMMS, HS) -> 205 MgrMMS - HS. 206 207sec_model('version-1') -> ?SEC_V1; 208sec_model('version-2') -> ?SEC_V2C. 209 210 211%%----------------------------------------------------------------- 212%% Handles a SNMPv3 Message, following the procedures in rfc2272, 213%% section 4.2 and 7.2 214%%----------------------------------------------------------------- 215process_v3_msg(NoteStore, Msg, Hdr, Data, Address, Log) -> 216 ?vdebug( 217 "process_v3_msg -> entry with~n" 218 " Hdr: ~p~n" 219 " Address: ~p", [Hdr, Address]), 220 221 %% 7.2.3 222 #v3_hdr{msgID = MsgID, 223 msgMaxSize = MMS, 224 msgFlags = MsgFlags, 225 msgSecurityModel = MsgSecModel, 226 msgSecurityParameters = SecParams, 227 hdr_size = HdrSize} = Hdr, 228 229 %% 7.2.4 230 SecModule = get_security_module(MsgSecModel), 231 ?vtrace("process_v3_msg -> 7.2.4: " 232 "~n SecModule: ~p", [SecModule]), 233 234 %% 7.2.5 235 SecLevel = check_sec_level(MsgFlags), 236 IsReportable = is_reportable(MsgFlags), 237 ?vtrace("process_v3_msg -> 7.2.5: " 238 "~n SecLevel: ~p" 239 "~n IsReportable: ~p", [SecLevel, IsReportable]), 240 241 %% 7.2.6 242 SecRes = (catch SecModule:process_incoming_msg(Msg, Data, 243 SecParams, SecLevel)), 244 ?vtrace("process_v3_msg -> 7.2.6 - message processing result: " 245 "~n ~p",[SecRes]), 246 {SecEngineID, SecName, ScopedPDUBytes, SecData} = 247 check_sec_module_result(SecRes, Hdr, Data, IsReportable, Log), 248 ?vtrace("process_v3_msg -> 7.2.6 - checked module result: " 249 "~n SecEngineID: ~p" 250 "~n SecName: ~p",[SecEngineID, SecName]), 251 252 %% 7.2.7 253 #scopedPdu{contextEngineID = CtxEngineID, 254 contextName = CtxName, 255 data = PDU} = 256 case (catch snmp_pdus:dec_scoped_pdu(ScopedPDUBytes)) of 257 ScopedPDU when is_record(ScopedPDU, scopedPdu) -> 258 ScopedPDU; 259 {'EXIT', Reason} -> 260 ?vlog("failed decoding scoped pdu: " 261 "~n ~p",[Reason]), 262 inc(snmpInASNParseErrs), 263 discard(Reason) 264 end, 265 266 ?vlog("7.2.7" 267 "~n ContextEngineID: ~p " 268 "~n context: \"~s\" ", 269 [CtxEngineID, CtxName]), 270 if 271 SecLevel =:= 3 -> % encrypted message - log decrypted pdu 272 Log({Hdr, ScopedPDUBytes}); 273 true -> % otherwise, log binary 274 Log(Msg) 275 end, 276 277 %% Make sure a get_bulk doesn't get too big. 278 MgrMMS = get_max_message_size(), 279 %% PduMMS is supposed to be the maximum total length of the response 280 %% PDU we can send. From the MMS, we need to subtract everything before 281 %% the PDU, i.e. Message and ScopedPDU. 282 %% Message: [48, TotalLen, Vsn, [Tag, LH, Hdr], [Tag, LM, MsgSec], Data] 283 %% 1 3 <----------- HdrSize -----------> 284 %% HdrSize = everything up to and including msgSecurityParameters. 285 %% ScopedPduData follows. This is 286 %% [Tag, Len, [Tag, L1, CtxName], [Tag, L2, CtxEID]] 287 %% i.e. 6 + length(CtxName) + length(CtxEID) 288 %% 289 %% Total: 1 + TotalLenOctets + 3 + ScopedPduDataLen 290 TotMMS = tot_mms(MgrMMS, MMS), 291 TotalLenOctets = snmp_pdus:get_encoded_length(TotMMS - 1), 292 PduMMS = TotMMS - TotalLenOctets - 10 - HdrSize - 293 length(CtxName) - length(CtxEngineID), 294 ?vtrace("process_v3_msg -> PduMMS = ~p", [PduMMS]), 295 Type = PDU#pdu.type, 296 ?vdebug("process_v3_msg -> PDU type: ~p",[Type]), 297 case Type of 298 report -> 299 %% 7.2.10 & 11 300 %% BMK BMK BMK: discovery? 301 Note = snmp_note_store:get_note(NoteStore, MsgID), 302 case Note of 303 {SecEngineID, MsgSecModel, SecName, SecLevel, 304 CtxEngineID, CtxName, _ReqId} -> 305 ?vtrace("process_v3_msg -> 7.2.11b: ok", []), 306 %% BMK BMK: Should we discard the cached info 307 %% BMK BMK: or do we let the gc deal with it? 308 {ok, 'version-3', PDU, PduMMS, ok}; 309 _ when is_tuple(Note) -> 310 ?vlog("process_v3_msg -> 7.2.11b: error" 311 "~n Note: ~p", [Note]), 312 Recv = {SecEngineID, MsgSecModel, SecName, SecLevel, 313 CtxEngineID, CtxName, PDU#pdu.request_id}, 314 Err = sec_error(Note, Recv), 315 ACM = {invalid_sec_info, Err}, 316 ReqId = element(size(Note), Note), 317 {ok, 'version-3', PDU, PduMMS, {error, ReqId, ACM}}; 318 _NoFound -> 319 ?vtrace("process_v3_msg -> _NoFound: " 320 "~p", [_NoFound]), 321 inc(snmpUnknownPDUHandlers), 322 discard({no_outstanding_req, MsgID}) 323 end; 324 325 'get-response' -> 326 %% 7.2.10 & 12 327 case snmp_note_store:get_note(NoteStore, MsgID) of 328 {SecEngineID, MsgSecModel, SecName, SecLevel, 329 CtxEngineID, CtxName, _} -> 330 %% 7.2.12.d 331 {ok, 'version-3', PDU, PduMMS, undefined}; 332 _ -> 333 %% 7.2.12.b 334 %% BMK BMK: Should we not discard the cached info?? 335 inc(snmpUnknownPDUHandlers), 336 discard({no_outstanding_req, MsgID}) 337 end; 338 339 'snmpv2-trap' -> 340 %% 7.2.14 341 {ok, 'version-3', PDU, PduMMS, undefined}; 342 343 'inform-request' -> 344 %% 7.2.13 345 SnmpEngineID = get_engine_id(), 346 case SecEngineID of 347 SnmpEngineID -> % 7.2.13.b 348 ?vtrace("7.2.13d - valid securityEngineID: ~p", 349 [SecEngineID]), 350 %% 4.2.2.1.1 - we don't handle proxys yet => we only 351 %% handle CtxEngineID to ourselves 352 %% Check that we actually know of an agent with this 353 %% CtxEngineID and Address 354 case is_known_engine_id(CtxEngineID, Address) of 355 true -> 356 ?vtrace("and the agent EngineID (~p) " 357 "is know to us", [CtxEngineID]), 358 %% Uses ACMData that snmpm_acm knows of. 359 %% BUGBUG BUGBUG 360 ACMData = 361 {MsgID, MsgSecModel, SecName, SecLevel, 362 CtxEngineID, CtxName, SecData}, 363 {ok, 'version-3', PDU, PduMMS, ACMData}; 364 UnknownEngineID -> 365 ?vtrace("4.2.2.1.2 - UnknownEngineId: ~p", 366 [UnknownEngineID]), 367 %% 4.2.2.1.2 368 NIsReportable = snmp_misc:is_reportable_pdu(Type), 369 Val = inc(snmpUnknownPDUHandlers), 370 ErrorInfo = 371 {#varbind{oid = ?snmpUnknownPDUHandlers, 372 variabletype = 'Counter32', 373 value = Val}, 374 SecName, 375 [{securityLevel, SecLevel}, 376 {contextEngineID, CtxEngineID}, 377 {contextName, CtxName}]}, 378 case generate_v3_report_msg(MsgID, 379 MsgSecModel, 380 Data, 381 ErrorInfo, 382 Log) of 383 {ok, Report} when NIsReportable =:= true -> 384 discard(snmpUnknownPDUHandlers, Report); 385 _ -> 386 discard(snmpUnknownPDUHandlers) 387 end 388 end; 389 _ -> % 7.2.13.a 390 ?vinfo("7.2.13a - invalid securityEngineID: ~p", 391 [SecEngineID]), 392 discard({badSecurityEngineID, SecEngineID}) 393 end; 394 395 _ -> 396 %% 7.2.13 - This would be the requests which we should not 397 %% receive since we are a manager, barring possible 398 %% proxy... 399 discard(Type) 400 end. 401 402 403sec_error(T1, T2) 404 when is_tuple(T1) andalso is_tuple(T2) andalso (size(T1) =:= size(T2)) -> 405 Tags = {sec_engine_id, msg_sec_model, sec_name, sec_level, 406 ctx_engine_id, ctx_name, request_id}, 407 sec_error(size(T1), T1, T2, Tags, []); 408sec_error(T1, T2) -> 409 [{internal_error, T1, T2}]. 410 411sec_error(0, _T1, _T2, _Tags, Acc) -> 412 Acc; 413sec_error(Idx, T1, T2, Tags, Acc) -> 414 case element(Idx, T1) =:= element(Idx, T2) of 415 true -> 416 sec_error(Idx - 1, T1, T2, Tags, Acc); 417 false -> 418 Elem = {element(Idx, Tags), element(Idx, T1), element(Idx, T2)}, 419 sec_error(Idx - 1, T1, T2, Tags, [Elem|Acc]) 420 end. 421 422tot_mms(MgrMMS, AgentMMS) when MgrMMS > AgentMMS -> AgentMMS; 423tot_mms(MgrMMS, _AgentMMS) -> MgrMMS. 424 425get_security_module(?SEC_USM) -> 426 snmpm_usm; 427get_security_module(_) -> 428 inc(snmpUnknownSecurityModels), 429 discard(snmpUnknownSecurityModels). 430 431check_sec_level([MsgFlag]) -> 432 SecLevel = MsgFlag band 3, 433 if 434 SecLevel == 2 -> 435 inc(snmpInvalidMsgs), 436 discard(snmpInvalidMsgs); 437 true -> 438 SecLevel 439 end; 440check_sec_level(_Unknown) -> 441 inc(snmpInvalidMsgs), 442 discard(snmpInvalidMsgs). 443 444is_reportable([MsgFlag]) -> 445 4 == (MsgFlag band 4). 446 447 448check_sec_module_result({ok, X}, _, _, _, _) -> 449 X; 450check_sec_module_result({error, Reason, Info}, _, _, _, _) 451 when is_list(Info) -> 452 %% case 7.2.6 b 453 discard({securityError, Reason, Info}); 454check_sec_module_result({error, Reason, ErrorInfo}, V3Hdr, Data, true, Log) -> 455 %% case 7.2.6 a 456 ?vtrace("security module result:" 457 "~n Reason: ~p" 458 "~n ErrorInfo: ~p", [Reason, ErrorInfo]), 459 #v3_hdr{msgID = MsgID, msgSecurityModel = MsgSecModel} = V3Hdr, 460 Pdu = get_scoped_pdu(Data), 461 case generate_v3_report_msg(MsgID, MsgSecModel, Pdu, ErrorInfo, Log) of 462 {ok, Report} -> 463 discard({securityError, Reason}, Report); 464 {discarded, _SomeOtherReason} -> 465 discard({securityError, Reason}) 466 end; 467check_sec_module_result({error, Reason, _ErrorInfo}, _, _, _, _) -> 468 ?vtrace("security module result:" 469 "~n Reason: ~p" 470 "~n _ErrorInfo: ~p", [Reason, _ErrorInfo]), 471 discard({securityError, Reason}); 472check_sec_module_result(Res, _, _, _, _) -> 473 ?vtrace("security module result:" 474 "~n Res: ~p", [Res]), 475 discard({securityError, Res}). 476 477get_scoped_pdu(D) when is_list(D) -> 478 (catch snmp_pdus:dec_scoped_pdu(D)); 479get_scoped_pdu(D) -> 480 D. 481 482 483%%----------------------------------------------------------------- 484%% Generate a message 485%%----------------------------------------------------------------- 486generate_msg('version-3', NoteStore, Pdu, 487 {SecModel, SecName, SecLevel, CtxEngineID, CtxName, 488 TargetName}, Log) -> 489 generate_v3_msg(NoteStore, Pdu, 490 SecModel, SecName, SecLevel, CtxEngineID, CtxName, 491 TargetName, Log); 492generate_msg(Vsn, _NoteStore, Pdu, {Comm, _SecModel}, Log) -> 493 generate_v1_v2c_msg(Vsn, Pdu, Comm, Log). 494 495 496generate_v3_msg(NoteStore, Pdu, 497 SecModel, SecName, SecLevel, CtxEngineID, CtxName, 498 TargetName, Log) -> 499 %% rfc2272: 7.1.6 500 ?vdebug("generate_v3_msg -> 7.1.6", []), 501 ScopedPDU = #scopedPdu{contextEngineID = CtxEngineID, 502 contextName = CtxName, 503 data = Pdu}, 504 case (catch snmp_pdus:enc_scoped_pdu(ScopedPDU)) of 505 {'EXIT', Reason} -> 506 user_err("failed encoding scoped pdu " 507 "~n pdu: ~w" 508 "~n contextName: ~w" 509 "~n reason: ~w", [Pdu, CtxName, Reason]), 510 {discarded, Reason}; 511 ScopedPDUBytes -> 512 {ok, generate_v3_msg(NoteStore, Pdu, ScopedPDUBytes, 513 SecModel, SecName, SecLevel, 514 CtxEngineID, CtxName, TargetName, Log)} 515 end. 516 517generate_v3_msg(NoteStore, 518 #pdu{type = Type} = Pdu, ScopedPduBytes, 519 SecModel, SecName, SecLevel, CtxEngineID, CtxName, 520 TargetName, Log) -> 521 %% 7.1.7 522 ?vdebug("generate_v3_msg -> 7.1.7", []), 523 MsgID = next_msg_id(), 524 MsgFlags = snmp_misc:mk_msg_flags(Type, SecLevel), 525 V3Hdr = #v3_hdr{msgID = MsgID, 526 msgMaxSize = get_max_message_size(), 527 msgFlags = MsgFlags, 528 msgSecurityModel = SecModel}, 529 Message = #message{version = 'version-3', 530 vsn_hdr = V3Hdr, 531 data = ScopedPduBytes}, 532 SecModule = sec_module(SecModel), 533 534 %% 7.1.9a 535 ?vdebug("generate_v3_msg -> 7.1.9a", []), 536 SecEngineID = sec_engine_id(TargetName), 537 ?vtrace("SecEngineID: ~p", [SecEngineID]), 538 %% 7.1.9b 539 ?vdebug("generate_v3_msg -> 7.1.9b", []), 540 case generate_v3_outgoing_msg(Message, SecModule, SecEngineID, 541 SecName, [], SecLevel) of 542 {ok, Packet} -> 543 %% 7.1.9c 544 %% Store in cache for 150 sec. 545 ?vdebug("generate_v3_msg -> 7.1.9c", []), 546 %% The request id is just in the case when we receive a 547 %% report with incorrect securityModel and/or securityLevel 548 CacheVal = {SecEngineID, SecModel, SecName, SecLevel, 549 CtxEngineID, CtxName, Pdu#pdu.request_id}, 550 snmp_note_store:set_note(NoteStore, 1500, MsgID, CacheVal), 551 Log(Packet), 552 inc_snmp_out(Pdu), 553 ?vdebug("generate_v3_msg -> done", []), 554 Packet; 555 556 Error -> 557 throw(Error) 558 end. 559 560 561sec_module(?SEC_USM) -> 562 snmpm_usm. 563 564%% 9) If the PDU is a GetRequest-PDU, GetNextRequest-PDU, 565%% GetBulkRequest-PDU, SetRequest-PDU, InformRequest-PDU, or or 566%% SNMPv2-Trap-PDU, then 567%% 568%% a) If the PDU is an SNMPv2-Trap-PDU, then securityEngineID is set 569%% to the value of this entity's snmpEngineID. 570%% 571%% Otherwise, the snmpEngineID of the target entity is determined, 572%% in an implementation-dependent manner, possibly using 573%% transportDomain and transportAddress. The value of 574%% securityEngineID is set to the value of the target entity's 575%% snmpEngineID. 576%% 577%% As we never send traps, the SecEngineID is allways the 578%% snmpEngineID of the target entity! 579sec_engine_id(TargetName) -> 580 case get_agent_engine_id(TargetName) of 581 {ok, EngineId} -> 582 EngineId; 583 _ -> 584 config_err("Can't find engineID for " 585 "snmpTargetAddrName ~p", [TargetName]), 586 %% this will trigger error in secmodule 587 "" 588 end. 589 590 591%% BMK BMK BMK 592%% This one looks very similar to lik generate_v1_v2c_response_msg! 593%% Common/shared? Should there be differences? 594%% 595generate_v1_v2c_msg(Vsn, Pdu, Community, Log) -> 596 ?vdebug("generate_v1_v2c_msg -> encode pdu", []), 597 case (catch snmp_pdus:enc_pdu(Pdu)) of 598 {'EXIT', Reason} -> 599 user_err("failed encoding pdu: " 600 "(pdu: ~w, community: ~w): ~n~w", 601 [Pdu, Community, Reason]), 602 {discarded, Reason}; 603 PduBytes -> 604 MMS = get_max_message_size(), 605 Message = #message{version = Vsn, 606 vsn_hdr = Community, 607 data = PduBytes}, 608 case generate_v1_v2c_outgoing_msg(Message) of 609 {error, Reason} -> 610 user_err("failed encoding message " 611 "(pdu: ~w, community: ~w): ~n~w", 612 [Pdu, Community, Reason]), 613 {discarded, Reason}; 614 {ok, Packet} when size(Packet) =< MMS -> 615 Log(Packet), 616 inc_snmp_out(Pdu), 617 {ok, Packet}; 618 {ok, Packet} -> 619 ?vlog("packet max size exceeded: " 620 "~n MMS: ~p" 621 "~n Len: ~p", 622 [MMS, size(Packet)]), 623 {discarded, tooBig} 624 end 625 end. 626 627 628 629%% ----------------------------------------------------------------------- 630 631generate_response_msg('version-3', Pdu, 632 {MsgID, SecModel, SecName, SecLevel, 633 CtxEngineID, CtxName, SecData}, Log) -> 634 generate_v3_response_msg(Pdu, MsgID, SecModel, SecName, SecLevel, 635 CtxEngineID, CtxName, SecData, Log); 636generate_response_msg(Vsn, Pdu, {Comm, _SecModel}, Log) -> 637 generate_v1_v2c_response_msg(Vsn, Pdu, Comm, Log); 638generate_response_msg(Vsn, Pdu, {Comm, _SecModel, _TDomain, _TAddress}, Log) -> 639 generate_v1_v2c_response_msg(Vsn, Pdu, Comm, Log). 640 641 642generate_v3_response_msg(#pdu{type = Type} = Pdu, MsgID, 643 SecModel, SecName, SecLevel, 644 CtxEngineID, CtxName, SecData, Log) -> 645 %% rfc2272: 7.1 steps 6-8 646 ScopedPdu = #scopedPdu{contextEngineID = CtxEngineID, 647 contextName = CtxName, 648 data = Pdu}, 649 case (catch snmp_pdus:enc_scoped_pdu(ScopedPdu)) of 650 {'EXIT', Reason} -> 651 user_err("failed encoded scoped pdu " 652 "(pdu: ~w, contextName: ~w): ~n~w", 653 [Pdu, CtxName, Reason]), 654 {discarded, Reason}; 655 ScopedPduBytes -> 656 MMS = get_max_message_size(), 657 MsgFlags = snmp_misc:mk_msg_flags(Type, SecLevel), 658 V3Hdr = #v3_hdr{msgID = MsgID, 659 msgMaxSize = MMS, 660 msgFlags = MsgFlags, 661 msgSecurityModel = SecModel}, 662 Message = #message{version = 'version-3', 663 vsn_hdr = V3Hdr, 664 data = ScopedPduBytes}, 665 %% We know that the security model is valid when we 666 %% generate a response. 667 SecModule = sec_module(SecModel), 668 SecEngineID = get_engine_id(), 669 case generate_v3_outgoing_msg(Message, SecModule, SecEngineID, 670 SecName, SecData, SecLevel) of 671 %% Check the packet size. Send the msg even 672 %% if it's larger than the agent can handle - 673 %% it will be dropped. Just check against the 674 %% internal size. 675 {ok, Packet} when size(Packet) =< MMS -> 676 if 677 SecLevel == 3 -> 678 %% encrypted - log decrypted pdu 679 Log({V3Hdr, ScopedPduBytes}); 680 true -> 681 %% otherwise log the entire msg 682 Log(Packet) 683 end, 684 inc_snmp_out(Pdu), 685 {ok, Packet}; 686 687 {ok, _Packet} when Pdu#pdu.error_status =:= tooBig -> 688 ?vlog("packet max size exceeded (tooBog): " 689 "~n MMS: ~p", [MMS]), 690 inc(snmpSilentDrops), 691 {discarded, tooBig}; 692 {ok, _Packet} -> 693 ?vlog("packet max size exceeded: " 694 "~n MMS: ~p", [MMS]), 695 TooBigPdu = Pdu#pdu{error_status = tooBig, 696 error_index = 0, 697 varbinds = []}, 698 generate_v3_response_msg(TooBigPdu, MsgID, 699 SecModel, SecName, SecLevel, 700 CtxEngineID, 701 CtxName, 702 SecData, Log); 703 Error -> 704 Error 705 end 706 end. 707 708 709generate_v3_outgoing_msg(Message, 710 SecModule, SecEngineID, SecName, SecData, SecLevel) -> 711 case (catch SecModule:generate_outgoing_msg(Message, 712 SecEngineID, 713 SecName, SecData, 714 SecLevel)) of 715 {'EXIT', Reason} -> 716 config_err("~p (message: ~p)", [Reason, Message]), 717 {discarded, Reason}; 718 {error, Reason} -> 719 config_err("~p (message: ~p)", [Reason, Message]), 720 {discarded, Reason}; 721 Bin when is_binary(Bin) -> 722 {ok, Bin}; 723 OutMsg when is_list(OutMsg) -> 724 case (catch list_to_binary(OutMsg)) of 725 Bin when is_binary(Bin) -> 726 {ok, Bin}; 727 {'EXIT', Reason} -> 728 {error, Reason} 729 end 730 end. 731 732 733generate_v1_v2c_response_msg(Vsn, Pdu, Comm, Log) -> 734 case (catch snmp_pdus:enc_pdu(Pdu)) of 735 {'EXIT', Reason} -> 736 user_err("failed encoding pdu: " 737 "(pdu: ~w, community: ~w): ~n~w", 738 [Pdu, Comm, Reason]), 739 {discarded, Reason}; 740 PduBytes -> 741 MMS = get_max_message_size(), 742 Message = #message{version = Vsn, 743 vsn_hdr = Comm, 744 data = PduBytes}, 745 case generate_v1_v2c_outgoing_msg(Message) of 746 {error, Reason} -> 747 user_err("failed encoding message only " 748 "(pdu: ~w, community: ~w): ~n~w", 749 [Pdu, Comm, Reason]), 750 {discarded, Reason}; 751 752 {ok, Packet} when size(Packet) =< MMS -> 753 Log(Packet), 754 inc_snmp_out(Pdu), 755 {ok, Packet}; 756 757 {ok, Packet} -> %% Too big 758 too_big(Vsn, Pdu, Comm, MMS, size(Packet), Log) 759 end 760 end. 761 762 763too_big('version-1' = Vsn, #pdu{type = 'get-response'} = Pdu, 764 Comm, _MMS, _Len, Log) -> 765 %% In v1, the varbinds should be identical to the incoming 766 %% request. It isn't identical now! Make acceptable (?) 767 %% approximation. 768 V = set_vb_null(Pdu#pdu.varbinds), 769 TooBigPdu = Pdu#pdu{error_status = tooBig, error_index = 0, varbinds = V}, 770 too_big(Vsn, TooBigPdu, Comm, Log); 771too_big('version-2' = Vsn, #pdu{type = 'get-response'} = Pdu, 772 Comm, _MMS, _Len, Log) -> 773 %% In v2, varbinds should be empty (reasonable!) 774 TooBigPdu = Pdu#pdu{error_status = tooBig, error_index = 0, varbinds = []}, 775 too_big(Vsn, TooBigPdu, Comm, Log); 776too_big(_Vsn, Pdu, _Comm, _Log, MMS, Len) -> 777 user_err("encoded pdu, ~p bytes, exceeded " 778 "max message size of ~p bytes. Pdu: ~n~w", 779 [Len, MMS, Pdu]), 780 {discarded, tooBig}. 781 782 783too_big(Vsn, Pdu, Comm, Log) -> 784 case (catch snmp_pdus:enc_pdu(Pdu)) of 785 {'EXIT', Reason} -> 786 user_err("failed encoding pdu " 787 "(pdu: ~w, community: ~w): ~n~w", 788 [Pdu, Comm, Reason]), 789 {discarded, Reason}; 790 PduBytes -> 791 Message = #message{version = Vsn, 792 vsn_hdr = Comm, 793 data = PduBytes}, 794 case generate_v1_v2c_outgoing_msg(Message) of 795 {error, Reason} -> 796 user_err("failed encoding message only" 797 "(pdu: ~w, community: ~w): ~n~w", 798 [Pdu, Comm, Reason]), 799 {discarded, Reason}; 800 {ok, Bin} -> 801 Log(Bin), 802 inc_snmp_out(Pdu), 803 {ok, Bin} 804 end 805 end. 806 807set_vb_null(Vbs) -> 808 [Vb#varbind{variabletype = 'NULL', value = 'NULL'} || Vb <- Vbs]. 809 810 811generate_v1_v2c_outgoing_msg(Message) -> 812 ?vdebug("generate_v1_v2c_outgoing_msg -> encode message", []), 813 case (catch snmp_pdus:enc_message_only(Message)) of 814 {'EXIT', Reason} -> 815 {error, Reason}; 816 Bin when is_binary(Bin) -> 817 {ok, Bin}; 818 Packet when is_list(Packet) -> 819 case (catch list_to_binary(Packet)) of 820 Bin when is_binary(Bin) -> 821 {ok, Bin}; 822 {'EXIT', Reason} -> 823 {error, Reason} 824 end 825 end. 826 827 828 829generate_v3_report_msg(MsgID, SecModel, ScopedPdu, ErrInfo, Log) 830 when is_record(ScopedPdu, scopedPdu) -> 831 ReqID = (ScopedPdu#scopedPdu.data)#pdu.request_id, 832 generate_v3_report_msg2(MsgID, ReqID, SecModel, ErrInfo, Log); 833generate_v3_report_msg(MsgID, SecModel, _, ErrInfo, Log) -> 834 %% RFC2572, 7.1.3.c.4 835 generate_v3_report_msg2(MsgID, 0, SecModel, ErrInfo, Log). 836 837 838generate_v3_report_msg2(MsgID, ReqID, SecModel, ErrInfo, Log) -> 839 {Varbind, SecName, Opts} = ErrInfo, 840 Pdu = #pdu{type = report, 841 request_id = ReqID, 842 error_status = noError, 843 error_index = 0, 844 varbinds = [Varbind]}, 845 SecLevel = snmp_misc:get_option(securityLevel, Opts, 0), 846 CtxEngineID = snmp_misc:get_option(contextEngineID, Opts, get_engine_id()), 847 CtxName = snmp_misc:get_option(contextName, Opts, ""), 848 SecData = snmp_misc:get_option(sec_data, Opts, []), 849 generate_v3_response_msg(Pdu, 850 MsgID, SecModel, SecName, SecLevel, 851 CtxEngineID, CtxName, SecData, Log). 852 853 854%%----------------------------------------------------------------- 855 856%% Get "our" (manager) MMS 857get_max_message_size() -> 858 case snmpm_config:get_engine_max_message_size() of 859 {ok, MMS} -> 860 MMS; 861 E -> 862 user_err("failed retreiving engine max message size: ~w", [E]), 863 484 864 end. 865 866%% The the MMS of the agent 867get_agent_max_message_size(Domain, Addr) -> 868 case snmpm_config:get_agent_engine_max_message_size(Domain, Addr) of 869 {ok, MMS} -> 870 MMS; 871 _Error -> 872 ?vlog("unknown agent: ~s", 873 [snmp_conf:mk_addr_string({Domain, Addr})]), 874 get_max_message_size() 875 end. 876%% get_agent_max_message_size(Addr, Port) -> 877%% case snmpm_config:get_agent_engine_max_message_size(Addr, Port) of 878%% {ok, MMS} -> 879%% MMS; 880%% _Error -> 881%% ?vlog("unknown agent: ~w:~w", [Addr, Port]), 882%% get_max_message_size() 883%% end. 884 885%% Get "our" (manager) engine id 886get_engine_id() -> 887 case snmpm_config:get_engine_id() of 888 {ok, Id} -> 889 Id; 890 _Error -> 891 "" 892 end. 893 894%% The engine id of the agent 895get_agent_engine_id(Name) -> 896 snmpm_config:get_agent_engine_id(Name). 897 898is_known_engine_id(EngineID, {Addr, Port}) -> 899 snmpm_config:is_known_engine_id(EngineID, Addr, Port). 900 901 902%%----------------------------------------------------------------- 903%% Sequence number (msg-id & req-id) functions 904%%----------------------------------------------------------------- 905next_msg_id() -> 906 next_id(msg_id). 907 908next_req_id() -> 909 next_id(req_id). 910 911next_id(Id) -> 912 snmpm_config:incr_counter(Id, 1). 913 914 915%%----------------------------------------------------------------- 916%% Version(s) functions 917%%----------------------------------------------------------------- 918init_versions([], S) -> 919 S; 920init_versions([v1|Vsns], S) -> 921 init_versions(Vsns, S#state{v1 = true}); 922init_versions([v2|Vsns], S) -> 923 init_versions(Vsns, S#state{v2c = true}); 924init_versions([v3|Vsns], S) -> 925 init_versions(Vsns, S#state{v3 = true}). 926 927init_usm(true) -> 928 snmpm_usm:init(); 929init_usm(_) -> 930 ok. 931 932 933%%----------------------------------------------------------------- 934%% Counter functions 935%%----------------------------------------------------------------- 936init_counters() -> 937 F = fun(Counter) -> maybe_create_counter(Counter) end, 938 lists:map(F, counters()). 939 940reset_counters() -> 941 F = fun(Counter) -> snmpm_config:reset_stats_counter(Counter) end, 942 lists:map(F, counters()). 943 944reset_usm(true) -> 945 snmpm_usm:reset(); 946reset_usm(_) -> 947 ok. 948 949maybe_create_counter(Counter) -> 950 snmpm_config:maybe_cre_stats_counter(Counter, 0). 951 952counters() -> 953 [snmpInPkts, 954 snmpOutPkts, 955 snmpInBadVersions, 956 snmpInBadCommunityNames, 957 snmpInBadCommunityUses, 958 snmpInASNParseErrs, 959 snmpInTooBigs, 960 snmpInNoSuchNames, 961 snmpInBadValues, 962 snmpInReadOnlys, 963 snmpInGenErrs, 964 snmpInTotalReqVars, 965 snmpInTotalSetVars, 966 snmpInGetRequests, 967 snmpInGetNexts, 968 snmpInSetRequests, 969 snmpInGetResponses, 970 snmpInTraps, 971 snmpOutTooBigs, 972 snmpOutNoSuchNames, 973 snmpOutBadValues, 974 snmpOutGenErrs, 975 snmpOutGetRequests, 976 snmpOutGetNexts, 977 snmpOutSetRequests, 978 snmpOutGetResponses, 979 snmpOutTraps, 980 snmpSilentDrops, 981 snmpProxyDrops, 982 %% From SNMP-MPD-MIB 983 snmpUnknownSecurityModels, 984 snmpInvalidMsgs, 985 snmpUnknownPDUHandlers 986 ]. 987 988 989%%----------------------------------------------------------------- 990%% inc(VariableName) increments the variable (Counter) in 991%% the local mib. (e.g. snmpInPkts) 992%%----------------------------------------------------------------- 993inc(Name) -> inc(Name, 1). 994inc(Name, N) -> snmpm_config:incr_stats_counter(Name, N). 995 996inc_snmp_in(#pdu{type = Type}) -> 997 inc_in_type(Type); 998inc_snmp_in(TrapPdu) when is_record(TrapPdu, trappdu) -> 999 inc(snmpInPkts), 1000 inc(snmpInTraps). 1001 1002inc_snmp_out(#pdu{type = Type, 1003 error_status = ErrorStatus}) -> 1004 inc(snmpOutPkts), 1005 inc_out_err(ErrorStatus), 1006 inc_out_type(Type). 1007 1008inc_out_type('get-request') -> inc(snmpOutGetRequests); 1009inc_out_type('get-next-request') -> inc(snmpOutGetNexts); 1010inc_out_type('set-request') -> inc(snmpOutSetRequests); 1011inc_out_type(_) -> ok. 1012 1013inc_out_err(genErr) -> inc(snmpOutGenErrs); 1014inc_out_err(tooBig) -> inc(snmpOutTooBigs); 1015inc_out_err(noSuchName) -> inc(snmpOutNoSuchNames); 1016inc_out_err(badValue) -> inc(snmpOutBadValues); 1017inc_out_err(_) -> ok. 1018 1019inc_in_type('get-response') -> inc(snmpInGetResponses); 1020inc_in_type(_) -> ok. 1021 1022 1023%%----------------------------------------------------------------- 1024 1025discard(Reason) -> 1026 throw({discarded, Reason}). 1027 1028discard(Reason, Report) -> 1029 throw({discarded, Reason, Report}). 1030 1031user_err(F, A) -> 1032 error_msg("USER ERROR: " ++ F ++ "~n", A). 1033 1034config_err(F, A) -> 1035 error_msg("CONFIG ERROR: " ++ F ++ "~n", A). 1036 1037error_msg(F, A) -> 1038 ?snmpm_error("MPD: " ++ F, A). 1039