gosnmp
/
trap.go
528 строк · 16.0 Кб
1// Copyright 2012 The GoSNMP Authors. All rights reserved. Use of this
2// source code is governed by a BSD-style license that can be found in the
3// LICENSE file.
4
5package gosnmp
6
7import (
8"errors"
9"fmt"
10"net"
11"strings"
12"sync"
13"sync/atomic"
14"time"
15)
16
17//
18// Sending Traps ie GoSNMP acting as an Agent
19//
20
21// SendTrap sends a SNMP Trap
22//
23// pdus[0] can a pdu of Type TimeTicks (with the desired uint32 epoch
24// time). Otherwise a TimeTicks pdu will be prepended, with time set to
25// now. This mirrors the behaviour of the Net-SNMP command-line tools.
26//
27// SendTrap doesn't wait for a return packet from the NMS (Network
28// Management Station).
29//
30// See also Listen() and examples for creating an NMS.
31//
32// NOTE: the trap code is currently unreliable when working with snmpv3 - pull requests welcome
33func (x *GoSNMP) SendTrap(trap SnmpTrap) (result *SnmpPacket, err error) {
34var pdutype PDUType
35
36switch x.Version {
37case Version2c, Version3:
38// Default to a v2 trap.
39pdutype = SNMPv2Trap
40
41if len(trap.Variables) == 0 {
42return nil, fmt.Errorf("function SendTrap requires at least 1 PDU")
43}
44
45if trap.Variables[0].Type == TimeTicks {
46// check is uint32
47if _, ok := trap.Variables[0].Value.(uint32); !ok {
48return nil, fmt.Errorf("function SendTrap TimeTick must be uint32")
49}
50}
51
52switch x.MsgFlags {
53// as per https://www.rfc-editor.org/rfc/rfc3412.html#section-6.4
54// The reportableFlag MUST always be zero when the message contains
55// a PDU from the Unconfirmed Class such as an SNMPv2-trap PDU
56case 0x4, 0x5, 0x7:
57// .. therefor bitclear the Reportable flag from the MsgFlags
58// that we inherited from validateParameters()
59x.MsgFlags = (x.MsgFlags &^ Reportable)
60}
61
62// If it's an inform, do that instead.
63if trap.IsInform {
64pdutype = InformRequest
65}
66
67if trap.Variables[0].Type != TimeTicks {
68now := uint32(time.Now().Unix())
69timetickPDU := SnmpPDU{Name: "1.3.6.1.2.1.1.3.0", Type: TimeTicks, Value: now}
70// prepend timetickPDU
71trap.Variables = append([]SnmpPDU{timetickPDU}, trap.Variables...)
72}
73
74case Version1:
75pdutype = Trap
76if len(trap.Enterprise) == 0 {
77return nil, fmt.Errorf("function SendTrap for SNMPV1 requires an Enterprise OID")
78}
79if len(trap.AgentAddress) == 0 {
80return nil, fmt.Errorf("function SendTrap for SNMPV1 requires an Agent Address")
81}
82
83default:
84err = fmt.Errorf("function SendTrap doesn't support %s", x.Version)
85return nil, err
86}
87
88packetOut := x.mkSnmpPacket(pdutype, trap.Variables, 0, 0)
89if x.Version == Version1 {
90packetOut.Enterprise = trap.Enterprise
91packetOut.AgentAddress = trap.AgentAddress
92packetOut.GenericTrap = trap.GenericTrap
93packetOut.SpecificTrap = trap.SpecificTrap
94packetOut.Timestamp = trap.Timestamp
95}
96
97// all sends wait for the return packet, except for SNMPv2Trap
98// -> wait is only for informs
99return x.send(packetOut, trap.IsInform)
100}
101
102//
103// Receiving Traps ie GoSNMP acting as an NMS (Network Management
104// Station).
105//
106// GoSNMP.unmarshal() currently only handles SNMPv2Trap
107//
108
109// A TrapListener defines parameters for running a SNMP Trap receiver.
110// nil values will be replaced by default values.
111type TrapListener struct {
112done chan bool
113listening chan bool
114sync.Mutex
115
116// Params is a reference to the TrapListener's "parent" GoSNMP instance.
117Params *GoSNMP
118
119// OnNewTrap handles incoming Trap and Inform PDUs.
120OnNewTrap TrapHandlerFunc
121
122// CloseTimeout is the max wait time for the socket to gracefully signal its closure.
123CloseTimeout time.Duration
124
125// These unexported fields are for letting test cases
126// know we are ready.
127conn *net.UDPConn
128proto string
129
130// Total number of packets received referencing an unknown snmpEngineID
131usmStatsUnknownEngineIDsCount uint32
132
133finish int32 // Atomic flag; set to 1 when closing connection
134}
135
136// Default timeout value for CloseTimeout of 3 seconds
137const defaultCloseTimeout = 3 * time.Second
138
139// TrapHandlerFunc is a callback function type which receives SNMP Trap and
140// Inform packets when they are received. If this callback is null, Trap and
141// Inform PDUs will not be received (Inform responses will still be sent,
142// however). This callback should not modify the contents of the SnmpPacket
143// nor the UDPAddr passed to it, and it should copy out any values it wishes to
144// use instead of retaining references in order to avoid memory fragmentation.
145//
146// The general effect of received Trap and Inform packets do not differ for the
147// receiver, and the response is handled by the caller of the handler, so there
148// is no need for the application to handle Informs any different than Traps.
149// Nonetheless, the packet's Type field can be examined to determine what type
150// of event this is for e.g. statistics gathering functions, etc.
151type TrapHandlerFunc func(s *SnmpPacket, u *net.UDPAddr)
152
153// NewTrapListener returns an initialized TrapListener.
154//
155// NOTE: the trap code is currently unreliable when working with snmpv3 - pull requests welcome
156func NewTrapListener() *TrapListener {
157tl := &TrapListener{
158finish: 0,
159done: make(chan bool),
160listening: make(chan bool, 1), // Buffered because one doesn't have to block on it.
161CloseTimeout: defaultCloseTimeout,
162}
163
164return tl
165}
166
167// Listening returns a sentinel channel on which one can block
168// until the listener is ready to receive requests.
169//
170// NOTE: the trap code is currently unreliable when working with snmpv3 - pull requests welcome
171func (t *TrapListener) Listening() <-chan bool {
172t.Lock()
173defer t.Unlock()
174return t.listening
175}
176
177// Close terminates the listening on TrapListener socket
178func (t *TrapListener) Close() {
179if atomic.CompareAndSwapInt32(&t.finish, 0, 1) {
180t.Lock()
181defer t.Unlock()
182
183if t.conn == nil {
184return
185}
186
187if err := t.conn.Close(); err != nil {
188t.Params.Logger.Printf("failed to Close() the TrapListener socket: %s", err)
189}
190
191select {
192case <-t.done:
193case <-time.After(t.CloseTimeout): // A timeout can prevent blocking forever
194t.Params.Logger.Printf("timeout while awaiting done signal on TrapListener Close()")
195}
196}
197}
198
199// SendUDP sends a given SnmpPacket to the provided address using the currently opened connection.
200func (t *TrapListener) SendUDP(packet *SnmpPacket, addr *net.UDPAddr) error {
201ob, err := packet.marshalMsg()
202if err != nil {
203return fmt.Errorf("error marshaling SnmpPacket: %w", err)
204}
205
206// Send the return packet back.
207count, err := t.conn.WriteTo(ob, addr)
208if err != nil {
209return fmt.Errorf("error sending SnmpPacket: %w", err)
210}
211
212// This isn't fatal, but should be logged.
213if count != len(ob) {
214t.Params.Logger.Printf("Failed to send all bytes of SnmpPacket!\n")
215}
216return nil
217}
218
219func (t *TrapListener) listenUDP(addr string) error {
220// udp
221
222udpAddr, err := net.ResolveUDPAddr(t.proto, addr)
223if err != nil {
224return err
225}
226t.conn, err = net.ListenUDP(udp, udpAddr)
227if err != nil {
228return err
229}
230
231defer t.conn.Close()
232
233// Mark that we are listening now.
234t.listening <- true
235
236for {
237switch {
238case atomic.LoadInt32(&t.finish) == 1:
239t.done <- true
240return nil
241
242default:
243var buf [4096]byte
244rlen, remote, err := t.conn.ReadFromUDP(buf[:])
245if err != nil {
246if atomic.LoadInt32(&t.finish) == 1 {
247// err most likely comes from reading from a closed connection
248continue
249}
250t.Params.Logger.Printf("TrapListener: error in read %s\n", err)
251continue
252}
253
254msg := buf[:rlen]
255trap, err := t.Params.UnmarshalTrap(msg, false)
256if err != nil {
257t.Params.Logger.Printf("TrapListener: error in UnmarshalTrap %s\n", err)
258continue
259}
260if trap.Version == Version3 && trap.SecurityModel == UserSecurityModel && t.Params.SecurityModel == UserSecurityModel {
261securityParams, ok := t.Params.SecurityParameters.(*UsmSecurityParameters)
262if !ok {
263t.Params.Logger.Printf("TrapListener: Invalid SecurityParameters types")
264}
265packetSecurityParams, ok := trap.SecurityParameters.(*UsmSecurityParameters)
266if !ok {
267t.Params.Logger.Printf("TrapListener: Invalid SecurityParameters types")
268}
269snmpEngineID := securityParams.AuthoritativeEngineID
270msgAuthoritativeEngineID := packetSecurityParams.AuthoritativeEngineID
271if msgAuthoritativeEngineID != snmpEngineID {
272if len(msgAuthoritativeEngineID) < 5 || len(msgAuthoritativeEngineID) > 32 {
273// RFC3411 section 5. – SnmpEngineID definition.
274// SnmpEngineID is an OCTET STRING which size should be between 5 and 32
275// According to RFC3414 3.2.3b: stop processing and report
276// the listener authoritative engine ID
277atomic.AddUint32(&t.usmStatsUnknownEngineIDsCount, 1)
278err := t.reportAuthoritativeEngineID(trap, snmpEngineID, remote)
279if err != nil {
280t.Params.Logger.Printf("TrapListener: %s\n", err)
281}
282continue
283}
284// RFC3414 3.2.3a: Continue processing
285}
286}
287// Here we assume that t.OnNewTrap will not alter the contents
288// of the PDU (per documentation, because Go does not have
289// compile-time const checking). We don't pass a copy because
290// the SnmpPacket type is somewhat large, but we could without
291// violating any implicit or explicit spec.
292t.OnNewTrap(trap, remote)
293
294// If it was an Inform request, we need to send a response.
295if trap.PDUType == InformRequest { //nolint:whitespace
296
297// Reuse the packet, since we're supposed to send it back
298// with the exact same variables unless there's an error.
299// Change the PDUType to the response, though.
300trap.PDUType = GetResponse
301
302// If the response can be sent, the error-status is
303// supposed to be set to noError and the error-index set to
304// zero.
305trap.Error = NoError
306trap.ErrorIndex = 0
307
308// TODO: Check that the message marshalled is not too large
309// for the originator to accept and if so, send a tooBig
310// error PDU per RFC3416 section 4.2.7. This maximum size,
311// however, does not have a well-defined mechanism in the
312// RFC other than using the path MTU (which is difficult to
313// determine), so it's left to future implementations.
314err := t.SendUDP(trap, remote)
315if err != nil {
316t.Params.Logger.Printf("TrapListener: %s\n", err)
317}
318}
319}
320}
321}
322
323func (t *TrapListener) reportAuthoritativeEngineID(trap *SnmpPacket, snmpEngineID string, addr *net.UDPAddr) error {
324newSecurityParams, ok := trap.SecurityParameters.Copy().(*UsmSecurityParameters)
325if !ok {
326return errors.New("unable to cast SecurityParams to UsmSecurityParameters")
327}
328newSecurityParams.AuthoritativeEngineID = snmpEngineID
329reportPacket := trap
330reportPacket.PDUType = Report
331reportPacket.MsgFlags &= AuthPriv
332reportPacket.SecurityParameters = newSecurityParams
333reportPacket.Variables = []SnmpPDU{
334{
335Name: usmStatsUnknownEngineIDs,
336Value: int(atomic.LoadUint32(&t.usmStatsUnknownEngineIDsCount)),
337Type: Integer,
338},
339}
340return t.SendUDP(reportPacket, addr)
341}
342
343func (t *TrapListener) handleTCPRequest(conn net.Conn) {
344// Make a buffer to hold incoming data.
345buf := make([]byte, 4096)
346// Read the incoming connection into the buffer.
347reqLen, err := conn.Read(buf)
348if err != nil {
349t.Params.Logger.Printf("TrapListener: error in read %s\n", err)
350return
351}
352
353msg := buf[:reqLen]
354traps, err := t.Params.UnmarshalTrap(msg, false)
355if err != nil {
356t.Params.Logger.Printf("TrapListener: error in read %s\n", err)
357return
358}
359// TODO: lying for backward compatibility reason - create UDP Address ... not nice
360r, _ := net.ResolveUDPAddr("", conn.RemoteAddr().String())
361t.OnNewTrap(traps, r)
362// Close the connection when you're done with it.
363conn.Close()
364}
365
366func (t *TrapListener) listenTCP(addr string) error {
367tcpAddr, err := net.ResolveTCPAddr(t.proto, addr)
368if err != nil {
369return err
370}
371
372l, err := net.ListenTCP(tcp, tcpAddr)
373if err != nil {
374return err
375}
376
377defer l.Close()
378
379// Mark that we are listening now.
380t.listening <- true
381
382for {
383switch {
384case atomic.LoadInt32(&t.finish) == 1:
385t.done <- true
386return nil
387default:
388
389// Listen for an incoming connection.
390conn, err := l.Accept()
391fmt.Printf("ACCEPT: %s", conn)
392if err != nil {
393fmt.Println("error accepting: ", err.Error())
394return err
395}
396// Handle connections in a new goroutine.
397go t.handleTCPRequest(conn)
398}
399}
400}
401
402// Listen listens on the UDP address addr and calls the OnNewTrap
403// function specified in *TrapListener for every trap received.
404//
405// NOTE: the trap code is currently unreliable when working with snmpv3 - pull requests welcome
406func (t *TrapListener) Listen(addr string) error {
407if t.Params == nil {
408t.Params = Default
409}
410
411// TODO TODO returning an error cause the following to hang/break
412// TestSendTrapBasic
413// TestSendTrapWithoutWaitingOnListen
414// TestSendV1Trap
415_ = t.Params.validateParameters()
416
417if t.OnNewTrap == nil {
418t.OnNewTrap = t.debugTrapHandler
419}
420
421splitted := strings.SplitN(addr, "://", 2)
422t.proto = udp
423if len(splitted) > 1 {
424t.proto = splitted[0]
425addr = splitted[1]
426}
427
428switch t.proto {
429case tcp:
430return t.listenTCP(addr)
431case udp:
432return t.listenUDP(addr)
433default:
434return fmt.Errorf("not implemented network protocol: %s [use: tcp/udp]", t.proto)
435}
436}
437
438// Default trap handler
439func (t *TrapListener) debugTrapHandler(s *SnmpPacket, u *net.UDPAddr) {
440t.Params.Logger.Printf("got trapdata from %+v: %+v\n", u, s)
441}
442
443// UnmarshalTrap unpacks the SNMP Trap.
444func (x *GoSNMP) UnmarshalTrap(trap []byte, useResponseSecurityParameters bool) (result *SnmpPacket, err error) {
445// Get only the version from the header of the trap
446version, _, err := x.unmarshalVersionFromHeader(trap, new(SnmpPacket))
447if err != nil {
448x.Logger.Printf("UnmarshalTrap version unmarshal: %s\n", err)
449return nil, err
450}
451// If there are multiple users configured and the SNMP trap is v3, see which user has valid credentials
452// by iterating through the list matching the identifier and seeing which credentials are authentic / can be used to decrypt
453if x.TrapSecurityParametersTable != nil && version == Version3 {
454identifier, err := x.getTrapIdentifier(trap)
455if err != nil {
456x.Logger.Printf("UnmarshalTrap V3 get trap identifier: %s\n", err)
457return nil, err
458}
459secParamsList, err := x.TrapSecurityParametersTable.Get(identifier)
460if err != nil {
461x.Logger.Printf("UnmarshalTrap V3 get security parameters from table: %s\n", err)
462return nil, err
463}
464for _, secParams := range secParamsList {
465// Copy the trap and pass the security parameters to try to unmarshal with
466cpTrap := make([]byte, len(trap))
467copy(cpTrap, trap)
468if result, err = x.unmarshalTrapBase(cpTrap, secParams.Copy(), true); err == nil {
469return result, nil
470}
471}
472return nil, fmt.Errorf("no credentials successfully unmarshaled trap: %w", err)
473}
474return x.unmarshalTrapBase(trap, nil, useResponseSecurityParameters)
475}
476
477func (x *GoSNMP) getTrapIdentifier(trap []byte) (string, error) {
478// Initialize a packet with no auth/priv to unmarshal ID/key for security parameters to use
479packet := new(SnmpPacket)
480_, err := x.unmarshalHeader(trap, packet)
481// Return err if no identifier was able to be parsed after unmarshaling
482if err != nil && packet.SecurityParameters.getIdentifier() == "" {
483return "", err
484}
485return packet.SecurityParameters.getIdentifier(), nil
486}
487
488func (x *GoSNMP) unmarshalTrapBase(trap []byte, sp SnmpV3SecurityParameters, useResponseSecurityParameters bool) (*SnmpPacket, error) {
489result := new(SnmpPacket)
490
491if x.SecurityParameters != nil && sp == nil {
492err := x.SecurityParameters.InitSecurityKeys()
493if err != nil {
494return nil, err
495}
496result.SecurityParameters = x.SecurityParameters.Copy()
497} else {
498result.SecurityParameters = sp
499}
500
501cursor, err := x.unmarshalHeader(trap, result)
502if err != nil {
503x.Logger.Printf("UnmarshalTrap: %s\n", err)
504return nil, err
505}
506
507if result.Version == Version3 {
508if result.SecurityModel == UserSecurityModel {
509err = x.testAuthentication(trap, result, useResponseSecurityParameters)
510if err != nil {
511x.Logger.Printf("UnmarshalTrap v3 auth: %s\n", err)
512return nil, err
513}
514}
515
516trap, cursor, err = x.decryptPacket(trap, cursor, result)
517if err != nil {
518x.Logger.Printf("UnmarshalTrap v3 decrypt: %s\n", err)
519return nil, err
520}
521}
522err = x.unmarshalPayload(trap, cursor, result)
523if err != nil {
524x.Logger.Printf("UnmarshalTrap: %s\n", err)
525return nil, err
526}
527return result, nil
528}
529