gosnmp

Форк
0
/
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

5
package gosnmp
6

7
import (
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
33
func (x *GoSNMP) SendTrap(trap SnmpTrap) (result *SnmpPacket, err error) {
34
	var pdutype PDUType
35

36
	switch x.Version {
37
	case Version2c, Version3:
38
		// Default to a v2 trap.
39
		pdutype = SNMPv2Trap
40

41
		if len(trap.Variables) == 0 {
42
			return nil, fmt.Errorf("function SendTrap requires at least 1 PDU")
43
		}
44

45
		if trap.Variables[0].Type == TimeTicks {
46
			// check is uint32
47
			if _, ok := trap.Variables[0].Value.(uint32); !ok {
48
				return nil, fmt.Errorf("function SendTrap TimeTick must be uint32")
49
			}
50
		}
51

52
		switch 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
56
		case 0x4, 0x5, 0x7:
57
			// .. therefor bitclear the Reportable flag from the MsgFlags
58
			// that we inherited from validateParameters()
59
			x.MsgFlags = (x.MsgFlags &^ Reportable)
60
		}
61

62
		// If it's an inform, do that instead.
63
		if trap.IsInform {
64
			pdutype = InformRequest
65
		}
66

67
		if trap.Variables[0].Type != TimeTicks {
68
			now := uint32(time.Now().Unix())
69
			timetickPDU := SnmpPDU{Name: "1.3.6.1.2.1.1.3.0", Type: TimeTicks, Value: now}
70
			// prepend timetickPDU
71
			trap.Variables = append([]SnmpPDU{timetickPDU}, trap.Variables...)
72
		}
73

74
	case Version1:
75
		pdutype = Trap
76
		if len(trap.Enterprise) == 0 {
77
			return nil, fmt.Errorf("function SendTrap for SNMPV1 requires an Enterprise OID")
78
		}
79
		if len(trap.AgentAddress) == 0 {
80
			return nil, fmt.Errorf("function SendTrap for SNMPV1 requires an Agent Address")
81
		}
82

83
	default:
84
		err = fmt.Errorf("function SendTrap doesn't support %s", x.Version)
85
		return nil, err
86
	}
87

88
	packetOut := x.mkSnmpPacket(pdutype, trap.Variables, 0, 0)
89
	if x.Version == Version1 {
90
		packetOut.Enterprise = trap.Enterprise
91
		packetOut.AgentAddress = trap.AgentAddress
92
		packetOut.GenericTrap = trap.GenericTrap
93
		packetOut.SpecificTrap = trap.SpecificTrap
94
		packetOut.Timestamp = trap.Timestamp
95
	}
96

97
	// all sends wait for the return packet, except for SNMPv2Trap
98
	// -> wait is only for informs
99
	return 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.
111
type TrapListener struct {
112
	done      chan bool
113
	listening chan bool
114
	sync.Mutex
115

116
	// Params is a reference to the TrapListener's "parent" GoSNMP instance.
117
	Params *GoSNMP
118

119
	// OnNewTrap handles incoming Trap and Inform PDUs.
120
	OnNewTrap TrapHandlerFunc
121

122
	// CloseTimeout is the max wait time for the socket to gracefully signal its closure.
123
	CloseTimeout time.Duration
124

125
	// These unexported fields are for letting test cases
126
	// know we are ready.
127
	conn  *net.UDPConn
128
	proto string
129

130
	// Total number of packets received referencing an unknown snmpEngineID
131
	usmStatsUnknownEngineIDsCount uint32
132

133
	finish int32 // Atomic flag; set to 1 when closing connection
134
}
135

136
// Default timeout value for CloseTimeout of 3 seconds
137
const 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.
151
type 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
156
func NewTrapListener() *TrapListener {
157
	tl := &TrapListener{
158
		finish:       0,
159
		done:         make(chan bool),
160
		listening:    make(chan bool, 1), // Buffered because one doesn't have to block on it.
161
		CloseTimeout: defaultCloseTimeout,
162
	}
163

164
	return 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
171
func (t *TrapListener) Listening() <-chan bool {
172
	t.Lock()
173
	defer t.Unlock()
174
	return t.listening
175
}
176

177
// Close terminates the listening on TrapListener socket
178
func (t *TrapListener) Close() {
179
	if atomic.CompareAndSwapInt32(&t.finish, 0, 1) {
180
		t.Lock()
181
		defer t.Unlock()
182

183
		if t.conn == nil {
184
			return
185
		}
186

187
		if err := t.conn.Close(); err != nil {
188
			t.Params.Logger.Printf("failed to Close() the TrapListener socket: %s", err)
189
		}
190

191
		select {
192
		case <-t.done:
193
		case <-time.After(t.CloseTimeout): // A timeout can prevent blocking forever
194
			t.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.
200
func (t *TrapListener) SendUDP(packet *SnmpPacket, addr *net.UDPAddr) error {
201
	ob, err := packet.marshalMsg()
202
	if err != nil {
203
		return fmt.Errorf("error marshaling SnmpPacket: %w", err)
204
	}
205

206
	// Send the return packet back.
207
	count, err := t.conn.WriteTo(ob, addr)
208
	if err != nil {
209
		return fmt.Errorf("error sending SnmpPacket: %w", err)
210
	}
211

212
	// This isn't fatal, but should be logged.
213
	if count != len(ob) {
214
		t.Params.Logger.Printf("Failed to send all bytes of SnmpPacket!\n")
215
	}
216
	return nil
217
}
218

219
func (t *TrapListener) listenUDP(addr string) error {
220
	// udp
221

222
	udpAddr, err := net.ResolveUDPAddr(t.proto, addr)
223
	if err != nil {
224
		return err
225
	}
226
	t.conn, err = net.ListenUDP(udp, udpAddr)
227
	if err != nil {
228
		return err
229
	}
230

231
	defer t.conn.Close()
232

233
	// Mark that we are listening now.
234
	t.listening <- true
235

236
	for {
237
		switch {
238
		case atomic.LoadInt32(&t.finish) == 1:
239
			t.done <- true
240
			return nil
241

242
		default:
243
			var buf [4096]byte
244
			rlen, remote, err := t.conn.ReadFromUDP(buf[:])
245
			if err != nil {
246
				if atomic.LoadInt32(&t.finish) == 1 {
247
					// err most likely comes from reading from a closed connection
248
					continue
249
				}
250
				t.Params.Logger.Printf("TrapListener: error in read %s\n", err)
251
				continue
252
			}
253

254
			msg := buf[:rlen]
255
			trap, err := t.Params.UnmarshalTrap(msg, false)
256
			if err != nil {
257
				t.Params.Logger.Printf("TrapListener: error in UnmarshalTrap %s\n", err)
258
				continue
259
			}
260
			if trap.Version == Version3 && trap.SecurityModel == UserSecurityModel && t.Params.SecurityModel == UserSecurityModel {
261
				securityParams, ok := t.Params.SecurityParameters.(*UsmSecurityParameters)
262
				if !ok {
263
					t.Params.Logger.Printf("TrapListener: Invalid SecurityParameters types")
264
				}
265
				packetSecurityParams, ok := trap.SecurityParameters.(*UsmSecurityParameters)
266
				if !ok {
267
					t.Params.Logger.Printf("TrapListener: Invalid SecurityParameters types")
268
				}
269
				snmpEngineID := securityParams.AuthoritativeEngineID
270
				msgAuthoritativeEngineID := packetSecurityParams.AuthoritativeEngineID
271
				if msgAuthoritativeEngineID != snmpEngineID {
272
					if 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
277
						atomic.AddUint32(&t.usmStatsUnknownEngineIDsCount, 1)
278
						err := t.reportAuthoritativeEngineID(trap, snmpEngineID, remote)
279
						if err != nil {
280
							t.Params.Logger.Printf("TrapListener: %s\n", err)
281
						}
282
						continue
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.
292
			t.OnNewTrap(trap, remote)
293

294
			// If it was an Inform request, we need to send a response.
295
			if 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.
300
				trap.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.
305
				trap.Error = NoError
306
				trap.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.
314
				err := t.SendUDP(trap, remote)
315
				if err != nil {
316
					t.Params.Logger.Printf("TrapListener: %s\n", err)
317
				}
318
			}
319
		}
320
	}
321
}
322

323
func (t *TrapListener) reportAuthoritativeEngineID(trap *SnmpPacket, snmpEngineID string, addr *net.UDPAddr) error {
324
	newSecurityParams, ok := trap.SecurityParameters.Copy().(*UsmSecurityParameters)
325
	if !ok {
326
		return errors.New("unable to cast SecurityParams to UsmSecurityParameters")
327
	}
328
	newSecurityParams.AuthoritativeEngineID = snmpEngineID
329
	reportPacket := trap
330
	reportPacket.PDUType = Report
331
	reportPacket.MsgFlags &= AuthPriv
332
	reportPacket.SecurityParameters = newSecurityParams
333
	reportPacket.Variables = []SnmpPDU{
334
		{
335
			Name:  usmStatsUnknownEngineIDs,
336
			Value: int(atomic.LoadUint32(&t.usmStatsUnknownEngineIDsCount)),
337
			Type:  Integer,
338
		},
339
	}
340
	return t.SendUDP(reportPacket, addr)
341
}
342

343
func (t *TrapListener) handleTCPRequest(conn net.Conn) {
344
	// Make a buffer to hold incoming data.
345
	buf := make([]byte, 4096)
346
	// Read the incoming connection into the buffer.
347
	reqLen, err := conn.Read(buf)
348
	if err != nil {
349
		t.Params.Logger.Printf("TrapListener: error in read %s\n", err)
350
		return
351
	}
352

353
	msg := buf[:reqLen]
354
	traps, err := t.Params.UnmarshalTrap(msg, false)
355
	if err != nil {
356
		t.Params.Logger.Printf("TrapListener: error in read %s\n", err)
357
		return
358
	}
359
	// TODO: lying for backward compatibility reason - create UDP Address ... not nice
360
	r, _ := net.ResolveUDPAddr("", conn.RemoteAddr().String())
361
	t.OnNewTrap(traps, r)
362
	// Close the connection when you're done with it.
363
	conn.Close()
364
}
365

366
func (t *TrapListener) listenTCP(addr string) error {
367
	tcpAddr, err := net.ResolveTCPAddr(t.proto, addr)
368
	if err != nil {
369
		return err
370
	}
371

372
	l, err := net.ListenTCP(tcp, tcpAddr)
373
	if err != nil {
374
		return err
375
	}
376

377
	defer l.Close()
378

379
	// Mark that we are listening now.
380
	t.listening <- true
381

382
	for {
383
		switch {
384
		case atomic.LoadInt32(&t.finish) == 1:
385
			t.done <- true
386
			return nil
387
		default:
388

389
			// Listen for an incoming connection.
390
			conn, err := l.Accept()
391
			fmt.Printf("ACCEPT: %s", conn)
392
			if err != nil {
393
				fmt.Println("error accepting: ", err.Error())
394
				return err
395
			}
396
			// Handle connections in a new goroutine.
397
			go 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
406
func (t *TrapListener) Listen(addr string) error {
407
	if t.Params == nil {
408
		t.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

417
	if t.OnNewTrap == nil {
418
		t.OnNewTrap = t.debugTrapHandler
419
	}
420

421
	splitted := strings.SplitN(addr, "://", 2)
422
	t.proto = udp
423
	if len(splitted) > 1 {
424
		t.proto = splitted[0]
425
		addr = splitted[1]
426
	}
427

428
	switch t.proto {
429
	case tcp:
430
		return t.listenTCP(addr)
431
	case udp:
432
		return t.listenUDP(addr)
433
	default:
434
		return fmt.Errorf("not implemented network protocol: %s [use: tcp/udp]", t.proto)
435
	}
436
}
437

438
// Default trap handler
439
func (t *TrapListener) debugTrapHandler(s *SnmpPacket, u *net.UDPAddr) {
440
	t.Params.Logger.Printf("got trapdata from %+v: %+v\n", u, s)
441
}
442

443
// UnmarshalTrap unpacks the SNMP Trap.
444
func (x *GoSNMP) UnmarshalTrap(trap []byte, useResponseSecurityParameters bool) (result *SnmpPacket, err error) {
445
	// Get only the version from the header of the trap
446
	version, _, err := x.unmarshalVersionFromHeader(trap, new(SnmpPacket))
447
	if err != nil {
448
		x.Logger.Printf("UnmarshalTrap version unmarshal: %s\n", err)
449
		return 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
453
	if x.TrapSecurityParametersTable != nil && version == Version3 {
454
		identifier, err := x.getTrapIdentifier(trap)
455
		if err != nil {
456
			x.Logger.Printf("UnmarshalTrap V3 get trap identifier: %s\n", err)
457
			return nil, err
458
		}
459
		secParamsList, err := x.TrapSecurityParametersTable.Get(identifier)
460
		if err != nil {
461
			x.Logger.Printf("UnmarshalTrap V3 get security parameters from table: %s\n", err)
462
			return nil, err
463
		}
464
		for _, secParams := range secParamsList {
465
			// Copy the trap and pass the security parameters to try to unmarshal with
466
			cpTrap := make([]byte, len(trap))
467
			copy(cpTrap, trap)
468
			if result, err = x.unmarshalTrapBase(cpTrap, secParams.Copy(), true); err == nil {
469
				return result, nil
470
			}
471
		}
472
		return nil, fmt.Errorf("no credentials successfully unmarshaled trap: %w", err)
473
	}
474
	return x.unmarshalTrapBase(trap, nil, useResponseSecurityParameters)
475
}
476

477
func (x *GoSNMP) getTrapIdentifier(trap []byte) (string, error) {
478
	// Initialize a packet with no auth/priv to unmarshal ID/key for security parameters to use
479
	packet := new(SnmpPacket)
480
	_, err := x.unmarshalHeader(trap, packet)
481
	// Return err if no identifier was able to be parsed after unmarshaling
482
	if err != nil && packet.SecurityParameters.getIdentifier() == "" {
483
		return "", err
484
	}
485
	return packet.SecurityParameters.getIdentifier(), nil
486
}
487

488
func (x *GoSNMP) unmarshalTrapBase(trap []byte, sp SnmpV3SecurityParameters, useResponseSecurityParameters bool) (*SnmpPacket, error) {
489
	result := new(SnmpPacket)
490

491
	if x.SecurityParameters != nil && sp == nil {
492
		err := x.SecurityParameters.InitSecurityKeys()
493
		if err != nil {
494
			return nil, err
495
		}
496
		result.SecurityParameters = x.SecurityParameters.Copy()
497
	} else {
498
		result.SecurityParameters = sp
499
	}
500

501
	cursor, err := x.unmarshalHeader(trap, result)
502
	if err != nil {
503
		x.Logger.Printf("UnmarshalTrap: %s\n", err)
504
		return nil, err
505
	}
506

507
	if result.Version == Version3 {
508
		if result.SecurityModel == UserSecurityModel {
509
			err = x.testAuthentication(trap, result, useResponseSecurityParameters)
510
			if err != nil {
511
				x.Logger.Printf("UnmarshalTrap v3 auth: %s\n", err)
512
				return nil, err
513
			}
514
		}
515

516
		trap, cursor, err = x.decryptPacket(trap, cursor, result)
517
		if err != nil {
518
			x.Logger.Printf("UnmarshalTrap v3 decrypt: %s\n", err)
519
			return nil, err
520
		}
521
	}
522
	err = x.unmarshalPayload(trap, cursor, result)
523
	if err != nil {
524
		x.Logger.Printf("UnmarshalTrap: %s\n", err)
525
		return nil, err
526
	}
527
	return result, nil
528
}
529

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.