1
0
Fork 0

Adding upstream version 1.34.4.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-05-24 07:26:29 +02:00
parent e393c3af3f
commit 4978089aab
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
4963 changed files with 677545 additions and 0 deletions

View file

@ -0,0 +1,138 @@
# SNMP Trap Input Plugin
The SNMP Trap plugin is a service input plugin that receives SNMP
notifications (traps and inform requests).
Notifications are received on plain UDP. The port to listen is
configurable.
## Note about Paths
Path is a global variable, separate snmp instances will append the specified
path onto the global path variable
## Global configuration options <!-- @/docs/includes/plugin_config.md -->
In addition to the plugin-specific configuration settings, plugins support
additional global and plugin configuration settings. These settings are used to
modify metrics, tags, and field or create aliases and configure ordering, etc.
See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
[CONFIGURATION.md]: ../../../docs/CONFIGURATION.md#plugins
## Secret-store support
This plugin supports secrets from secret-stores for the `sec_name`,
`auth_password` and `priv_password` option.
See the [secret-store documentation][SECRETSTORE] for more details on how
to use them.
[SECRETSTORE]: ../../../docs/CONFIGURATION.md#secret-store-secrets
## SNMP backend: gosmi and netsnmp
Telegraf has two backends to translate SNMP objects. By default, Telegraf will
use `netsnmp`, however, this option is deprecated and it is encouraged that
users migrate to `gosmi`. If users find issues with `gosmi` that do not occur
with `netsnmp` please open a project issue on GitHub.
The SNMP backend setting is a global-level setting that applies to all use of
SNMP in Telegraf. Users can set this option in the `[agent]` configuration via
the `snmp_translator` option. See the [agent configuration][AGENT] for more
details.
[AGENT]: ../../../docs/CONFIGURATION.md#agent
## Configuration
```toml @sample.conf
# Receive SNMP traps
[[inputs.snmp_trap]]
## Transport, local address, and port to listen on. Transport must
## be "udp://". Omit local address to listen on all interfaces.
## example: "udp://127.0.0.1:1234"
##
## Special permissions may be required to listen on a port less than
## 1024. See README.md for details
##
# service_address = "udp://:162"
##
## Path to mib files
## Used by the gosmi translator.
## To add paths when translating with netsnmp, use the MIBDIRS environment variable
# path = ["/usr/share/snmp/mibs"]
##
## Timeout running snmptranslate command
## Used by the netsnmp translator only
# timeout = "5s"
## Snmp version; one of "1", "2c" or "3".
# version = "2c"
## SNMPv3 authentication and encryption options.
##
## Security Name.
# sec_name = "myuser"
## Authentication protocol; one of "MD5", "SHA", "SHA224", "SHA256", "SHA384", "SHA512" or "".
# auth_protocol = "MD5"
## Authentication password.
# auth_password = "pass"
## Security Level; one of "noAuthNoPriv", "authNoPriv", or "authPriv".
# sec_level = "authNoPriv"
## Privacy protocol used for encrypted messages; one of "DES", "AES", "AES192", "AES192C", "AES256", "AES256C" or "".
# priv_protocol = ""
## Privacy password used for encrypted messages.
# priv_password = ""
```
### Using a Privileged Port
On many operating systems, listening on a privileged port (a port
number less than 1024) requires extra permission. Since the default
SNMP trap port 162 is in this category, using telegraf to receive SNMP
traps may need extra permission.
Instructions for listening on a privileged port vary by operating
system. It is not recommended to run telegraf as superuser in order to
use a privileged port. Instead follow the principle of least privilege
and use a more specific operating system mechanism to allow telegraf to
use the port. You may also be able to have telegraf use an
unprivileged port and then configure a firewall port forward rule from
the privileged port.
To use a privileged port on Linux, you can use setcap to enable the
CAP_NET_BIND_SERVICE capability on the telegraf binary:
```shell
setcap cap_net_bind_service=+ep /usr/bin/telegraf
```
On Mac OS, listening on privileged ports is unrestricted on versions
10.14 and later.
## Metrics
- snmp_trap
- tags:
- source (string, IP address of trap source)
- name (string, value from SNMPv2-MIB::snmpTrapOID.0 PDU)
- mib (string, MIB from SNMPv2-MIB::snmpTrapOID.0 PDU)
- oid (string, OID string from SNMPv2-MIB::snmpTrapOID.0 PDU)
- version (string, "1" or "2c" or "3")
- context_name (string, value from v3 trap)
- engine_id (string, value from v3 trap)
- community (string, value from 1 or 2c trap)
- fields:
- Fields are mapped from variables in the trap. Field names are
the trap variable names after MIB lookup. Field values are trap
variable values.
## Example Output
```text
snmp_trap,mib=SNMPv2-MIB,name=coldStart,oid=.1.3.6.1.6.3.1.1.5.1,source=192.168.122.102,version=2c,community=public snmpTrapEnterprise.0="linux",sysUpTimeInstance=1i 1574109187723429814
snmp_trap,mib=NET-SNMP-AGENT-MIB,name=nsNotifyShutdown,oid=.1.3.6.1.4.1.8072.4.0.2,source=192.168.122.102,version=2c,community=public sysUpTimeInstance=5803i,snmpTrapEnterprise.0="netSnmpNotificationPrefix" 1574109186555115459
```
## References
- [net-snmp project home](http://www.net-snmp.org)
- [`snmpcmd` man-page](http://net-snmp.sourceforge.net/docs/man/snmpcmd.html)

View file

@ -0,0 +1,21 @@
package snmp_trap
import (
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/internal/snmp"
)
type gosmiTranslator struct {
}
func (*gosmiTranslator) lookup(oid string) (snmp.MibEntry, error) {
return snmp.TrapLookup(oid)
}
func newGosmiTranslator(paths []string, log telegraf.Logger) (*gosmiTranslator, error) {
err := snmp.LoadMibsFromPath(paths, log, &snmp.GosmiMibLoader{})
if err == nil {
return &gosmiTranslator{}, nil
}
return nil, err
}

View file

@ -0,0 +1,17 @@
package snmp_trap
import "github.com/influxdata/telegraf"
type logger struct {
telegraf.Logger
}
// Printf formats and writes the given string to the standard output.
func (l logger) Printf(format string, args ...interface{}) {
l.Tracef(format, args...)
}
// Print writes the given string to the standard output.
func (l logger) Print(args ...interface{}) {
l.Trace(args...)
}

View file

@ -0,0 +1,91 @@
package snmp_trap
import (
"bufio"
"bytes"
"errors"
"os/exec"
"strings"
"sync"
"time"
"github.com/influxdata/telegraf/config"
"github.com/influxdata/telegraf/internal"
"github.com/influxdata/telegraf/internal/snmp"
)
type execer func(config.Duration, string, ...string) ([]byte, error)
func realExecCmd(timeout config.Duration, arg0 string, args ...string) ([]byte, error) {
cmd := exec.Command(arg0, args...)
var out bytes.Buffer
cmd.Stdout = &out
err := internal.RunTimeout(cmd, time.Duration(timeout))
if err != nil {
return nil, err
}
return out.Bytes(), nil
}
type netsnmpTranslator struct {
// Each translator has its own cache and each plugin instance has
// its own translator. This is different than the snmp plugin
// which has one global cache.
//
// We may want to change snmp_trap to
// have a global cache although it's not as important for
// snmp_trap to be global because there is usually only one
// instance, while it's common to configure many snmp instances.
cacheLock sync.Mutex
cache map[string]snmp.MibEntry
execCmd execer
timeout config.Duration
}
func (s *netsnmpTranslator) lookup(oid string) (e snmp.MibEntry, err error) {
s.cacheLock.Lock()
defer s.cacheLock.Unlock()
var ok bool
if e, ok = s.cache[oid]; !ok {
// cache miss. exec snmptranslate
e, err = s.snmptranslate(oid)
if err == nil {
s.cache[oid] = e
}
return e, err
}
return e, nil
}
func (s *netsnmpTranslator) snmptranslate(oid string) (e snmp.MibEntry, err error) {
var out []byte
out, err = s.execCmd(s.timeout, "snmptranslate", "-Td", "-Ob", "-m", "all", oid)
if err != nil {
return e, err
}
scanner := bufio.NewScanner(bytes.NewBuffer(out))
ok := scanner.Scan()
if err = scanner.Err(); !ok && err != nil {
return e, err
}
e.OidText = scanner.Text()
i := strings.Index(e.OidText, "::")
if i == -1 {
return e, errors.New("not found")
}
e.MibName = e.OidText[:i]
e.OidText = e.OidText[i+2:]
return e, nil
}
func newNetsnmpTranslator(timeout config.Duration) *netsnmpTranslator {
return &netsnmpTranslator{
execCmd: realExecCmd,
cache: make(map[string]snmp.MibEntry),
timeout: timeout,
}
}

View file

@ -0,0 +1,35 @@
# Receive SNMP traps
[[inputs.snmp_trap]]
## Transport, local address, and port to listen on. Transport must
## be "udp://". Omit local address to listen on all interfaces.
## example: "udp://127.0.0.1:1234"
##
## Special permissions may be required to listen on a port less than
## 1024. See README.md for details
##
# service_address = "udp://:162"
##
## Path to mib files
## Used by the gosmi translator.
## To add paths when translating with netsnmp, use the MIBDIRS environment variable
# path = ["/usr/share/snmp/mibs"]
##
## Timeout running snmptranslate command
## Used by the netsnmp translator only
# timeout = "5s"
## Snmp version; one of "1", "2c" or "3".
# version = "2c"
## SNMPv3 authentication and encryption options.
##
## Security Name.
# sec_name = "myuser"
## Authentication protocol; one of "MD5", "SHA", "SHA224", "SHA256", "SHA384", "SHA512" or "".
# auth_protocol = "MD5"
## Authentication password.
# auth_password = "pass"
## Security Level; one of "noAuthNoPriv", "authNoPriv", or "authPriv".
# sec_level = "authNoPriv"
## Privacy protocol used for encrypted messages; one of "DES", "AES", "AES192", "AES192C", "AES256", "AES256C" or "".
# priv_protocol = ""
## Privacy password used for encrypted messages.
# priv_password = ""

View file

@ -0,0 +1,355 @@
//go:generate ../../../tools/readme_config_includer/generator
package snmp_trap
import (
_ "embed"
"encoding/hex"
"errors"
"fmt"
"net"
"net/url"
"strconv"
"strings"
"time"
"unicode/utf8"
"github.com/gosnmp/gosnmp"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/config"
"github.com/influxdata/telegraf/internal/snmp"
"github.com/influxdata/telegraf/plugins/inputs"
)
var defaultTimeout = config.Duration(time.Second * 5)
//go:embed sample.conf
var sampleConfig string
type SnmpTrap struct {
ServiceAddress string `toml:"service_address"`
Timeout config.Duration `toml:"timeout"`
Version string `toml:"version"`
Path []string `toml:"path"`
// Settings for version 3 security
SecLevel string `toml:"sec_level"`
SecName config.Secret `toml:"sec_name"`
AuthProtocol string `toml:"auth_protocol"`
AuthPassword config.Secret `toml:"auth_password"`
PrivProtocol string `toml:"priv_protocol"`
PrivPassword config.Secret `toml:"priv_password"`
Translator string `toml:"-"`
Log telegraf.Logger `toml:"-"`
acc telegraf.Accumulator
listener *gosnmp.TrapListener
transl translator
}
type translator interface {
lookup(oid string) (snmp.MibEntry, error)
}
func (*SnmpTrap) SampleConfig() string {
return sampleConfig
}
func (s *SnmpTrap) SetTranslator(name string) {
s.Translator = name
}
func (s *SnmpTrap) Init() error {
// Set defaults
if s.ServiceAddress == "" {
s.ServiceAddress = "udp://:162"
}
if len(s.Path) == 0 {
s.Path = []string{"/usr/share/snmp/mibs"}
}
// Check input parameters
switch s.Translator {
case "gosmi":
t, err := newGosmiTranslator(s.Path, s.Log)
if err != nil {
return err
}
s.transl = t
case "netsnmp":
s.transl = newNetsnmpTranslator(s.Timeout)
default:
// Ignore the translator for testing if an instance was set
if s.transl == nil {
return errors.New("invalid translator value")
}
}
// Setup the SNMP parameters
params := *gosnmp.Default
if s.Log.Level().Includes(telegraf.Trace) {
params.Logger = gosnmp.NewLogger(&logger{s.Log})
}
switch s.Version {
case "1":
params.Version = gosnmp.Version1
case "", "2c":
params.Version = gosnmp.Version2c
case "3":
params.Version = gosnmp.Version3
// Setup the security for v3
params.SecurityModel = gosnmp.UserSecurityModel
// Set security mechanisms
switch strings.ToLower(s.SecLevel) {
case "noauthnopriv", "":
params.MsgFlags = gosnmp.NoAuthNoPriv
case "authnopriv":
params.MsgFlags = gosnmp.AuthNoPriv
case "authpriv":
params.MsgFlags = gosnmp.AuthPriv
default:
return fmt.Errorf("unknown security level %q", s.SecLevel)
}
// Set authentication
var security gosnmp.UsmSecurityParameters
switch strings.ToLower(s.AuthProtocol) {
case "":
security.AuthenticationProtocol = gosnmp.NoAuth
case "md5":
security.AuthenticationProtocol = gosnmp.MD5
case "sha":
security.AuthenticationProtocol = gosnmp.SHA
case "sha224":
security.AuthenticationProtocol = gosnmp.SHA224
case "sha256":
security.AuthenticationProtocol = gosnmp.SHA256
case "sha384":
security.AuthenticationProtocol = gosnmp.SHA384
case "sha512":
security.AuthenticationProtocol = gosnmp.SHA512
default:
return fmt.Errorf("unknown authentication protocol %q", s.AuthProtocol)
}
// Set privacy
switch strings.ToLower(s.PrivProtocol) {
case "":
security.PrivacyProtocol = gosnmp.NoPriv
case "aes":
security.PrivacyProtocol = gosnmp.AES
case "des":
security.PrivacyProtocol = gosnmp.DES
case "aes192":
security.PrivacyProtocol = gosnmp.AES192
case "aes192c":
security.PrivacyProtocol = gosnmp.AES192C
case "aes256":
security.PrivacyProtocol = gosnmp.AES256
case "aes256c":
security.PrivacyProtocol = gosnmp.AES256C
default:
return fmt.Errorf("unknown privacy protocol %q", s.PrivProtocol)
}
// Set credentials
secnameSecret, err := s.SecName.Get()
if err != nil {
return fmt.Errorf("getting secname failed: %w", err)
}
security.UserName = secnameSecret.String()
secnameSecret.Destroy()
authPasswdSecret, err := s.AuthPassword.Get()
if err != nil {
return fmt.Errorf("getting auth-password failed: %w", err)
}
security.AuthenticationPassphrase = authPasswdSecret.String()
authPasswdSecret.Destroy()
privPasswdSecret, err := s.PrivPassword.Get()
if err != nil {
return fmt.Errorf("getting priv-password failed: %w", err)
}
security.PrivacyPassphrase = privPasswdSecret.String()
privPasswdSecret.Destroy()
// Enable security settings
params.SecurityParameters = &security
default:
return fmt.Errorf("unknown version %q", s.Version)
}
// Initialize the listener
s.listener = gosnmp.NewTrapListener()
s.listener.OnNewTrap = s.handler
s.listener.Params = &params
return nil
}
func (s *SnmpTrap) Start(acc telegraf.Accumulator) error {
s.acc = acc
u, err := url.Parse(s.ServiceAddress)
if err != nil {
return fmt.Errorf("invalid service address: %s", s.ServiceAddress)
}
// The gosnmp package currently only supports UDP
if u.Scheme != "udp" {
return fmt.Errorf("unknown protocol for service address %q", s.ServiceAddress)
}
// If the listener immediately returns an error we need to return it
errCh := make(chan error, 1)
go func() {
errCh <- s.listener.Listen(u.Host)
}()
select {
case <-s.listener.Listening():
s.Log.Infof("Listening on %s", s.ServiceAddress)
case err := <-errCh:
return fmt.Errorf("listening failed: %w", err)
}
return nil
}
func (*SnmpTrap) Gather(telegraf.Accumulator) error {
return nil
}
func (s *SnmpTrap) Stop() {
s.listener.Close()
}
func setTrapOid(tags map[string]string, oid string, e snmp.MibEntry) {
tags["oid"] = oid
tags["name"] = e.OidText
tags["mib"] = e.MibName
}
func (s *SnmpTrap) handler(packet *gosnmp.SnmpPacket, addr *net.UDPAddr) {
tm := time.Now()
fields := make(map[string]interface{}, len(packet.Variables)+1)
tags := map[string]string{
"version": packet.Version.String(),
"source": addr.IP.String(),
}
if packet.Version == gosnmp.Version1 {
// Follow the procedure described in RFC 2576 3.1 to
// translate a v1 trap to v2.
var trapOid string
if packet.GenericTrap >= 0 && packet.GenericTrap < 6 {
trapOid = ".1.3.6.1.6.3.1.1.5." + strconv.Itoa(packet.GenericTrap+1)
} else if packet.GenericTrap == 6 {
trapOid = packet.Enterprise + ".0." + strconv.Itoa(packet.SpecificTrap)
}
if trapOid != "" {
e, err := s.transl.lookup(trapOid)
if err != nil {
s.Log.Errorf("Error resolving V1 OID, oid=%s, source=%s: %v", trapOid, tags["source"], err)
return
}
setTrapOid(tags, trapOid, e)
}
if packet.AgentAddress != "" {
tags["agent_address"] = packet.AgentAddress
}
fields["sysUpTimeInstance"] = packet.Timestamp
}
for _, v := range packet.Variables {
var value interface{}
// Use system mibs to resolve oids. Don't fall back to numeric oid
// because it's not useful enough to the end user and can be difficult
// to translate or remove from the database later.
//
// TODO: format the pdu value based on its snmp type and the mib's
// textual convention. The snmp input plugin only handles textual
// convention for ip and mac addresses
switch v.Type {
case gosnmp.ObjectIdentifier:
val, ok := v.Value.(string)
if !ok {
s.Log.Errorf("Error getting value OID")
return
}
var e snmp.MibEntry
var err error
e, err = s.transl.lookup(val)
if nil != err {
s.Log.Errorf("Error resolving value OID, oid=%s, source=%s: %v", val, tags["source"], err)
return
}
value = e.OidText
// 1.3.6.1.6.3.1.1.4.1.0 is SNMPv2-MIB::snmpTrapOID.0.
// If v.Name is this oid, set a tag of the trap name.
if v.Name == ".1.3.6.1.6.3.1.1.4.1.0" {
setTrapOid(tags, val, e)
continue
}
case gosnmp.OctetString:
// OctetStrings may contain hex data that needs its own conversion
if !utf8.Valid(v.Value.([]byte)[:]) {
value = hex.EncodeToString(v.Value.([]byte))
} else {
value = v.Value
}
default:
value = v.Value
}
e, err := s.transl.lookup(v.Name)
if nil != err {
s.Log.Errorf("Error resolving OID oid=%s, source=%s: %v", v.Name, tags["source"], err)
return
}
fields[e.OidText] = value
}
if packet.Version == gosnmp.Version3 {
if packet.ContextName != "" {
tags["context_name"] = packet.ContextName
}
if packet.ContextEngineID != "" {
// SNMP RFCs like 3411 and 5343 show engine ID as a hex string
tags["engine_id"] = fmt.Sprintf("%x", packet.ContextEngineID)
}
} else {
if packet.Community != "" {
tags["community"] = packet.Community
}
}
s.acc.AddFields("snmp_trap", fields, tags, tm)
}
func init() {
inputs.Add("snmp_trap", func() telegraf.Input {
return &SnmpTrap{
ServiceAddress: "udp://:162",
Timeout: defaultTimeout,
Path: []string{"/usr/share/snmp/mibs"},
Version: "2c",
}
})
}

File diff suppressed because it is too large Load diff