Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Split config of auth and modules
Browse files Browse the repository at this point in the history
Allow configuration of auth/version parameters separately from the walk
and metrics in the generator and exporter configuration.
* Simplify startup with `ReloadConfig()`
* Make sure to init metrics on config reload.

Fixes: #619

Signed-off-by: SuperQ <superq@gmail.com>
SuperQ committed Apr 6, 2023

Verified

This commit was signed with the committer’s verified signature.
SuperQ Ben Kochie
1 parent 17510da commit b57a63b
Showing 12 changed files with 57,521 additions and 57,379 deletions.
15 changes: 10 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -4,7 +4,11 @@ This exporter is the recommended way to expose SNMP data in a format which
Prometheus can ingest.

To simply get started, it's recommended to use the `if_mib` module with
switches, access points, or routers.
switches, access points, or routers using the `public_v2` auth module,
which should be a read-only access community on the target device.

Note, community strings in SNMP are not considered secrets, as they are sent
unencrypted in SNMP v1 and v2c. For secure acces, SNMP v3 is required.

# Concepts

@@ -68,9 +72,9 @@ Start `snmp_exporter` as a daemon or from CLI:
./snmp_exporter
```

Visit http://localhost:9116/snmp?module=if_mib&target=1.2.3.4 where `1.2.3.4` is the IP or
FQDN of the SNMP device to get metrics from and `if_mib` is the default module, defined
in `snmp.yml`.
Visit [http://localhost:9116/snmp?auth=public_v2&module=if_mib&target=1.2.3.4] where `1.2.3.4` is the IP or
FQDN of the SNMP device to get metrics from. Also note the use of default auth (`public_v2`) and
default module (`if_mib`). The auth and module must be defined in the `snmp.yml`.

## Configuration

@@ -83,7 +87,7 @@ using SNMP v2 GETBULK.

## Prometheus Configuration

`target` and `module` can be passed as a parameter through relabelling.
The URL params `target`, `auth`, and `module` can be controlled through relabelling.

Example config:
```YAML
@@ -95,6 +99,7 @@ scrape_configs:
- switch.local # SNMP device.
metrics_path: /snmp
params:
auth: [public_v2]
module: [if_mib]
relabel_configs:
- source_labels: [__address__]
29 changes: 15 additions & 14 deletions collector/collector.go
Original file line number Diff line number Diff line change
@@ -116,19 +116,19 @@ type ScrapeResults struct {
retries uint64
}

func ScrapeTarget(ctx context.Context, target string, config *config.Module, logger log.Logger) (ScrapeResults, error) {
func ScrapeTarget(ctx context.Context, target string, auth *config.Auth, module *config.Module, logger log.Logger) (ScrapeResults, error) {
results := ScrapeResults{}
// Set the options.
snmp := gosnmp.GoSNMP{}
snmp.Context = ctx
snmp.MaxRepetitions = config.WalkParams.MaxRepetitions
snmp.Retries = *config.WalkParams.Retries
snmp.Timeout = config.WalkParams.Timeout
snmp.UseUnconnectedUDPSocket = config.WalkParams.UseUnconnectedUDPSocket
snmp.MaxRepetitions = module.WalkParams.MaxRepetitions
snmp.Retries = *module.WalkParams.Retries
snmp.Timeout = module.WalkParams.Timeout
snmp.UseUnconnectedUDPSocket = module.WalkParams.UseUnconnectedUDPSocket
snmp.LocalAddr = *srcAddress

// Allow a set of OIDs that aren't in a strictly increasing order
if config.WalkParams.AllowNonIncreasingOIDs {
if module.WalkParams.AllowNonIncreasingOIDs {
snmp.AppOpts = make(map[string]interface{})
snmp.AppOpts["c"] = true
}
@@ -159,7 +159,7 @@ func ScrapeTarget(ctx context.Context, target string, config *config.Module, log
}

// Configure auth.
config.WalkParams.ConfigureSNMP(&snmp)
auth.ConfigureSNMP(&snmp)

// Do the actual walk.
err := snmp.Connect()
@@ -172,9 +172,9 @@ func ScrapeTarget(ctx context.Context, target string, config *config.Module, log
defer snmp.Conn.Close()

// Evaluate rules.
newGet := config.Get
newWalk := config.Walk
for _, filter := range config.Filters {
newGet := module.Get
newWalk := module.Walk
for _, filter := range module.Filters {
var pdus []gosnmp.SnmpPDU
allowedList := []string{}

@@ -204,7 +204,7 @@ func ScrapeTarget(ctx context.Context, target string, config *config.Module, log
}

getOids := newGet
maxOids := int(config.WalkParams.MaxRepetitions)
maxOids := int(module.WalkParams.MaxRepetitions)
// Max Repetition can be 0, maxOids cannot. SNMPv1 can only report one OID error per call.
if maxOids == 0 || snmp.Version == gosnmp.Version1 {
maxOids = 1
@@ -365,12 +365,13 @@ func buildMetricTree(metrics []*config.Metric) *MetricNode {
type collector struct {
ctx context.Context
target string
auth *config.Auth
module *config.Module
logger log.Logger
}

func New(ctx context.Context, target string, module *config.Module, logger log.Logger) *collector {
return &collector{ctx: ctx, target: target, module: module, logger: logger}
func New(ctx context.Context, target string, auth *config.Auth, module *config.Module, logger log.Logger) *collector {
return &collector{ctx: ctx, target: target, auth: auth, module: module, logger: logger}
}

// Describe implements Prometheus.Collector.
@@ -381,7 +382,7 @@ func (c collector) Describe(ch chan<- *prometheus.Desc) {
// Collect implements Prometheus.Collector.
func (c collector) Collect(ch chan<- prometheus.Metric) {
start := time.Now()
results, err := ScrapeTarget(c.ctx, c.target, c.module, c.logger)
results, err := ScrapeTarget(c.ctx, c.target, c.auth, c.module, c.logger)
if err != nil {
level.Info(c.logger).Log("msg", "Error scraping target", "err", err)
ch <- prometheus.NewInvalidMetric(prometheus.NewDesc("snmp_error", "Error scraping target", nil, nil), err)
100 changes: 54 additions & 46 deletions config/config.go
Original file line number Diff line number Diff line change
@@ -44,13 +44,12 @@ var (
SecurityLevel: "noAuthNoPriv",
AuthProtocol: "MD5",
PrivProtocol: "DES",
Version: 2,
}
DefaultWalkParams = WalkParams{
Version: 2,
MaxRepetitions: 25,
Retries: &defaultRetries,
Timeout: time.Second * 5,
Auth: DefaultAuth,
UseUnconnectedUDPSocket: false,
AllowNonIncreasingOIDs: false,
}
@@ -63,14 +62,15 @@ var (
)

// Config for the snmp_exporter.
type Config map[string]*Module
type Config struct {
Auths map[string]*Auth `yaml:"auths",omitempty"`
Modules map[string]*Module `yaml:"modules",omitempty"`
}

type WalkParams struct {
Version int `yaml:"version,omitempty"`
MaxRepetitions uint32 `yaml:"max_repetitions,omitempty"`
Retries *int `yaml:"retries,omitempty"`
Timeout time.Duration `yaml:"timeout,omitempty"`
Auth Auth `yaml:"auth,omitempty"`
UseUnconnectedUDPSocket bool `yaml:"use_unconnected_udp_socket,omitempty"`
AllowNonIncreasingOIDs bool `yaml:"allow_nonincreasing_oids,omitempty"`
}
@@ -90,43 +90,11 @@ func (c *Module) UnmarshalYAML(unmarshal func(interface{}) error) error {
if err := unmarshal((*plain)(c)); err != nil {
return err
}

wp := c.WalkParams

if wp.Version < 1 || wp.Version > 3 {
return fmt.Errorf("SNMP version must be 1, 2 or 3. Got: %d", wp.Version)
}
if wp.Version == 3 {
switch wp.Auth.SecurityLevel {
case "authPriv":
if wp.Auth.PrivPassword == "" {
return fmt.Errorf("priv password is missing, required for SNMPv3 with priv")
}
if wp.Auth.PrivProtocol != "DES" && wp.Auth.PrivProtocol != "AES" && wp.Auth.PrivProtocol != "AES192" && wp.Auth.PrivProtocol != "AES192C" && wp.Auth.PrivProtocol != "AES256" && wp.Auth.PrivProtocol != "AES256C" {
return fmt.Errorf("priv protocol must be DES or AES")
}
fallthrough
case "authNoPriv":
if wp.Auth.Password == "" {
return fmt.Errorf("auth password is missing, required for SNMPv3 with auth")
}
if wp.Auth.AuthProtocol != "MD5" && wp.Auth.AuthProtocol != "SHA" && wp.Auth.AuthProtocol != "SHA224" && wp.Auth.AuthProtocol != "SHA256" && wp.Auth.AuthProtocol != "SHA384" && wp.Auth.AuthProtocol != "SHA512" {
return fmt.Errorf("auth protocol must be SHA or MD5")
}
fallthrough
case "noAuthNoPriv":
if wp.Auth.Username == "" {
return fmt.Errorf("auth username is missing, required for SNMPv3")
}
default:
return fmt.Errorf("security level must be one of authPriv, authNoPriv or noAuthNoPriv")
}
}
return nil
}

// ConfigureSNMP sets the various version and auth settings.
func (c WalkParams) ConfigureSNMP(g *gosnmp.GoSNMP) {
func (c Auth) ConfigureSNMP(g *gosnmp.GoSNMP) {
switch c.Version {
case 1:
g.Version = gosnmp.Version1
@@ -135,16 +103,16 @@ func (c WalkParams) ConfigureSNMP(g *gosnmp.GoSNMP) {
case 3:
g.Version = gosnmp.Version3
}
g.Community = string(c.Auth.Community)
g.ContextName = c.Auth.ContextName
g.Community = string(c.Community)
g.ContextName = c.ContextName

// v3 security settings.
g.SecurityModel = gosnmp.UserSecurityModel
usm := &gosnmp.UsmSecurityParameters{
UserName: c.Auth.Username,
UserName: c.Username,
}
auth, priv := false, false
switch c.Auth.SecurityLevel {
switch c.SecurityLevel {
case "noAuthNoPriv":
g.MsgFlags = gosnmp.NoAuthNoPriv
case "authNoPriv":
@@ -156,8 +124,8 @@ func (c WalkParams) ConfigureSNMP(g *gosnmp.GoSNMP) {
priv = true
}
if auth {
usm.AuthenticationPassphrase = string(c.Auth.Password)
switch c.Auth.AuthProtocol {
usm.AuthenticationPassphrase = string(c.Password)
switch c.AuthProtocol {
case "SHA":
usm.AuthenticationProtocol = gosnmp.SHA
case "SHA224":
@@ -173,8 +141,8 @@ func (c WalkParams) ConfigureSNMP(g *gosnmp.GoSNMP) {
}
}
if priv {
usm.PrivacyPassphrase = string(c.Auth.PrivPassword)
switch c.Auth.PrivProtocol {
usm.PrivacyPassphrase = string(c.PrivPassword)
switch c.PrivProtocol {
case "DES":
usm.PrivacyProtocol = gosnmp.DES
case "AES":
@@ -261,6 +229,46 @@ type Auth struct {
PrivProtocol string `yaml:"priv_protocol,omitempty"`
PrivPassword Secret `yaml:"priv_password,omitempty"`
ContextName string `yaml:"context_name,omitempty"`
Version int `yaml:"version,omitempty"`
}

func (c *Auth) UnmarshalYAML(unmarshal func(interface{}) error) error {
*c = DefaultAuth
type plain Auth
if err := unmarshal((*plain)(c)); err != nil {
return err
}

if c.Version < 1 || c.Version > 3 {
return fmt.Errorf("SNMP version must be 1, 2 or 3. Got: %d", c.Version)
}
if c.Version == 3 {
switch c.SecurityLevel {
case "authPriv":
if c.PrivPassword == "" {
return fmt.Errorf("priv password is missing, required for SNMPv3 with priv")
}
if c.PrivProtocol != "DES" && c.PrivProtocol != "AES" && c.PrivProtocol != "AES192" && c.PrivProtocol != "AES192C" && c.PrivProtocol != "AES256" && c.PrivProtocol != "AES256C" {
return fmt.Errorf("priv protocol must be DES or AES")
}
fallthrough
case "authNoPriv":
if c.Password == "" {
return fmt.Errorf("auth password is missing, required for SNMPv3 with auth")
}
if c.AuthProtocol != "MD5" && c.AuthProtocol != "SHA" && c.AuthProtocol != "SHA224" && c.AuthProtocol != "SHA256" && c.AuthProtocol != "SHA384" && c.AuthProtocol != "SHA512" {
return fmt.Errorf("auth protocol must be SHA or MD5")
}
fallthrough
case "noAuthNoPriv":
if c.Username == "" {
return fmt.Errorf("auth username is missing, required for SNMPv3")
}
default:
return fmt.Errorf("security level must be one of authPriv, authNoPriv or noAuthNoPriv")
}
}
return nil
}

type RegexpExtract struct {
115 changes: 59 additions & 56 deletions generator/FORMAT.md
Original file line number Diff line number Diff line change
@@ -4,62 +4,65 @@ This is generated by the generator, so only those doing development should
have to care about how this works.

```
module_name:
auth:
auths:
public_v2:
version: 2
# There's various auth/version options here too. See the main README.
community: public
walk:
# List of OID subtrees to walk.
- 1.3.6.1.2.1.2
- 1.3.6.1.2.1.31.1.1
get:
# List of OIDs to get directly.
- 1.3.6.1.2.1.1.3
metrics: # List of metrics to extract.
# A simple metric with no labels.
- name: sysUpTime
oid: 1.3.6.1.2.1.1.3
type: gauge
# See README.md type override for a list of valid types
# Non-numeric types are represented as a gauge with value 1, and the rendered value
# as a label value on that gauge.
# A metric that's part of a table, and thus has labels.
- name: ifMtu
oid: 1.3.6.1.2.1.2.2.1.4
type: gauge
# A list of the table indexes and their types. All indexes become labels.
indexes:
- labelname: ifIndex
type: gauge
- labelname: someString
type: OctetString
fixed_size: 8 # Only possible for OctetString/DisplayString types.
# If only one length is possible this is it. Otherwise
# this will be 0 or missing.
- labelname: someOtherString
type: OctetString
implied: true # Only possible for OctetString/DisplayString types.
# Must be the last index. See RFC2578 section 7.7.
- name: ifSpeed
oid: 1.3.6.1.2.1.2.2.1.5
type: gauge
indexes:
- labelname: ifDescr
type: gauge
# Lookups take original indexes, look them up in another part of the
# oid tree and overwrite the given output label.
lookups:
- labels: [ifDescr] # Input label name(s). Empty means delete the output label.
oid: 1.3.6.1.2.1.2.2.1.2 # OID to look under.
labelname: ifDescr # Output label name.
type: OctetString # Type of output object.
# Creates new metrics based on the regex and the metric value.
regex_extracts:
Temp: # A new metric will be created appending this to the metricName to become metricNameTemp.
- regex: '(.*)' # Regex to extract a value from the returned SNMP walks's value.
value: '$1' # Parsed as float64, defaults to $1.
enum_values: # Enum for this metric. Only used with the enum types.
0: true
1: false
modules:
module_name:
walk:
# List of OID subtrees to walk.
- 1.3.6.1.2.1.2
- 1.3.6.1.2.1.31.1.1
get:
# List of OIDs to get directly.
- 1.3.6.1.2.1.1.3
metrics: # List of metrics to extract.
# A simple metric with no labels.
- name: sysUpTime
oid: 1.3.6.1.2.1.1.3
type: gauge
# See README.md type override for a list of valid types
# Non-numeric types are represented as a gauge with value 1, and the rendered value
# as a label value on that gauge.
# A metric that's part of a table, and thus has labels.
- name: ifMtu
oid: 1.3.6.1.2.1.2.2.1.4
type: gauge
# A list of the table indexes and their types. All indexes become labels.
indexes:
- labelname: ifIndex
type: gauge
- labelname: someString
type: OctetString
fixed_size: 8 # Only possible for OctetString/DisplayString types.
# If only one length is possible this is it. Otherwise
# this will be 0 or missing.
- labelname: someOtherString
type: OctetString
implied: true # Only possible for OctetString/DisplayString types.
# Must be the last index. See RFC2578 section 7.7.
- name: ifSpeed
oid: 1.3.6.1.2.1.2.2.1.5
type: gauge
indexes:
- labelname: ifDescr
type: gauge
# Lookups take original indexes, look them up in another part of the
# oid tree and overwrite the given output label.
lookups:
- labels: [ifDescr] # Input label name(s). Empty means delete the output label.
oid: 1.3.6.1.2.1.2.2.1.2 # OID to look under.
labelname: ifDescr # Output label name.
type: OctetString # Type of output object.
# Creates new metrics based on the regex and the metric value.
regex_extracts:
Temp: # A new metric will be created appending this to the metricName to become metricNameTemp.
- regex: '(.*)' # Regex to extract a value from the returned SNMP walks's value.
value: '$1' # Parsed as float64, defaults to $1.
enum_values: # Enum for this metric. Only used with the enum types.
0: true
1: false
```
53 changes: 28 additions & 25 deletions generator/README.md
Original file line number Diff line number Diff line change
@@ -49,10 +49,36 @@ make docker-generate

## File Format

`generator.yml` provides a list of modules. Each module defines what to collect from a device type. The simplest module is just a name
and a set of OIDs to walk.
`generator.yml` provides a list of auths and modules. Each module defines what to collect from a device type.
The simplest module is just a name and a set of OIDs to walk.

```yaml
auths:
auth_name:
version: 2 # SNMP version to use. Defaults to 2.
# 1 will use GETNEXT, 2 and 3 use GETBULK.

# Community string is used with SNMP v1 and v2. Defaults to "public_v2".
community: public_v2

# v3 has different and more complex settings.
# Which are required depends on the security_level.
# The equivalent options on NetSNMP commands like snmpbulkwalk
# and snmpget are also listed. See snmpcmd(1).
username: user # Required, no default. -u option to NetSNMP.
security_level: noAuthNoPriv # Defaults to noAuthNoPriv. -l option to NetSNMP.
# Can be noAuthNoPriv, authNoPriv or authPriv.
password: pass # Has no default. Also known as authKey, -A option to NetSNMP.
# Required if security_level is authNoPriv or authPriv.
auth_protocol: MD5 # MD5, SHA, SHA224, SHA256, SHA384, or SHA512. Defaults to MD5. -a option to NetSNMP.
# Used if security_level is authNoPriv or authPriv.
priv_protocol: DES # DES, AES, AES192, or AES256. Defaults to DES. -x option to NetSNMP.
# Used if security_level is authPriv.
priv_password: otherPass # Has no default. Also known as privKey, -X option to NetSNMP.
# Required if security_level is authPriv.
context_name: context # Has no default. -n option to NetSNMP.
# Required if context is configured on the device.

modules:
module_name: # The module name. You can have as many modules as you want.
walk: # List of OIDs to walk. Can also be SNMP object names or specific instances.
@@ -62,34 +88,11 @@ modules:
- 1.3.6.1.2.1.2.2.1.4 # Same as ifMtu (used for filter example)
- bsnDot11EssSsid # Same as 1.3.6.1.4.1.14179.2.1.1.1.2 (used for filter example)

version: 2 # SNMP version to use. Defaults to 2.
# 1 will use GETNEXT, 2 and 3 use GETBULK.
max_repetitions: 25 # How many objects to request with GET/GETBULK, defaults to 25.
# May need to be reduced for buggy devices.
retries: 3 # How many times to retry a failed request, defaults to 3.
timeout: 5s # Timeout for each individual SNMP request, defaults to 5s.

auth:
# Community string is used with SNMP v1 and v2. Defaults to "public".
community: public

# v3 has different and more complex settings.
# Which are required depends on the security_level.
# The equivalent options on NetSNMP commands like snmpbulkwalk
# and snmpget are also listed. See snmpcmd(1).
username: user # Required, no default. -u option to NetSNMP.
security_level: noAuthNoPriv # Defaults to noAuthNoPriv. -l option to NetSNMP.
# Can be noAuthNoPriv, authNoPriv or authPriv.
password: pass # Has no default. Also known as authKey, -A option to NetSNMP.
# Required if security_level is authNoPriv or authPriv.
auth_protocol: MD5 # MD5, SHA, SHA224, SHA256, SHA384, or SHA512. Defaults to MD5. -a option to NetSNMP.
# Used if security_level is authNoPriv or authPriv.
priv_protocol: DES # DES, AES, AES192, or AES256. Defaults to DES. -x option to NetSNMP.
# Used if security_level is authPriv.
priv_password: otherPass # Has no default. Also known as privKey, -X option to NetSNMP.
# Required if security_level is authPriv.
context_name: context # Has no default. -n option to NetSNMP.
# Required if context is configured on the device.

lookups: # Optional list of lookups to perform.
# The default for `keep_source_indexes` is false. Indexes must be unique for this option to be used.
1 change: 1 addition & 0 deletions generator/config.go
Original file line number Diff line number Diff line change
@@ -21,6 +21,7 @@ import (

// The generator config.
type Config struct {
Auths map[string]*config.Auth `yaml:"auths"`
Modules map[string]*ModuleConfig `yaml:"modules"`
}

12 changes: 6 additions & 6 deletions generator/generator.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
auths:
public_v1:
version: 1
public_v2:
version: 2

modules:
# Default IF-MIB interfaces table with ifIndex.
if_mib:
@@ -60,7 +66,6 @@ modules:
# Guide: http://www.apc.com/salestools/ASTE-6Z5QEY/ASTE-6Z5QEY_R0_EN.pdf
# Download site: http://www.apc.com/us/en/tools/download/index.cfm
apcups:
version: 1
walk:
- sysUpTime
- interfaces
@@ -315,7 +320,6 @@ modules:
# https://www.ui.com/downloads/firmwares/airfiber5X/v4.0.5/UBNT-MIB.txt
#
ubiquiti_airfiber:
version: 1
walk:
- sysUpTime
- interfaces
@@ -330,7 +334,6 @@ modules:
# https://dl.ubnt.com/firmwares/airos-ubnt-mib/ubnt-mib.zip
#
ubiquiti_airmax:
version: 1
walk:
- sysUpTime
- interfaces
@@ -536,8 +539,6 @@ modules:
overrides:
outletOperationalState:
type: EnumAsStateSet
auth:
community: raritan_public

# Wiener Power Supply Module MPod
#
@@ -662,7 +663,6 @@ modules:
#
# https://www.cyberpowersystems.com/product/software/mib-files/mib-v29/
cyberpower:
version: 1
walk:
- 1.3.6.1.4.1.3808.1.1.1 # ups
- 1.3.6.1.4.1.3808.1.1.4 # environmentSensor
8 changes: 5 additions & 3 deletions generator/main.go
Original file line number Diff line number Diff line change
@@ -47,6 +47,8 @@ func generateConfig(nodes *Node, nameToNode map[string]*Node, logger log.Logger)
}

outputConfig := config.Config{}
outputConfig.Auths = cfg.Auths
outputConfig.Modules = make(map[string]*config.Module, len(cfg.Modules))
for name, m := range cfg.Modules {
level.Info(logger).Log("msg", "Generating config for module", "module", name)
// Give each module a copy of the tree so that it can be modified.
@@ -61,9 +63,9 @@ func generateConfig(nodes *Node, nameToNode map[string]*Node, logger log.Logger)
if err != nil {
return err
}
outputConfig[name] = out
outputConfig[name].WalkParams = m.WalkParams
level.Info(logger).Log("msg", "Generated metrics", "module", name, "metrics", len(outputConfig[name].Metrics))
outputConfig.Modules[name] = out
outputConfig.Modules[name].WalkParams = m.WalkParams
level.Info(logger).Log("msg", "Generated metrics", "module", name, "metrics", len(outputConfig.Modules[name].Metrics))
}

config.DoNotHideSecrets = true
56 changes: 37 additions & 19 deletions main.go
Original file line number Diff line number Diff line change
@@ -51,7 +51,7 @@ var (
Name: "snmp_collection_duration_seconds",
Help: "Duration of collections by the SNMP exporter",
},
[]string{"module"},
[]string{"auth", "module"},
)
snmpRequestErrors = promauto.NewCounter(
prometheus.CounterOpts{
@@ -70,41 +70,58 @@ func handler(w http.ResponseWriter, r *http.Request, logger log.Logger) {

target := query.Get("target")
if len(query["target"]) != 1 || target == "" {
http.Error(w, "'target' parameter must be specified once", 400)
http.Error(w, "'target' parameter must be specified once", http.StatusBadRequest)
snmpRequestErrors.Inc()
return
}

authName := query.Get("auth")
if len(query["auth"]) > 1 {
http.Error(w, "'auth' parameter must only be specified once", http.StatusBadRequest)
snmpRequestErrors.Inc()
return
}
if authName == "" {
authName = "public_v2"
}

moduleName := query.Get("module")
if len(query["module"]) > 1 {
http.Error(w, "'module' parameter must only be specified once", 400)
http.Error(w, "'module' parameter must only be specified once", http.StatusBadRequest)
snmpRequestErrors.Inc()
return
}
if moduleName == "" {
moduleName = "if_mib"
}

sc.RLock()
module, ok := (*(sc.C))[moduleName]
auth, authOk := sc.C.Auths[authName]
module, moduleOk := sc.C.Modules[moduleName]
sc.RUnlock()
if !ok {
http.Error(w, fmt.Sprintf("Unknown module '%s'", moduleName), 400)
if !authOk {
http.Error(w, fmt.Sprintf("Unknown auth '%s'", authName), http.StatusBadRequest)
snmpRequestErrors.Inc()
return
}
if !moduleOk {
http.Error(w, fmt.Sprintf("Unknown module '%s'", moduleName), http.StatusBadRequest)
snmpRequestErrors.Inc()
return
}

logger = log.With(logger, "module", moduleName, "target", target)
logger = log.With(logger, "auth", authName, "module", moduleName, "target", target)
level.Debug(logger).Log("msg", "Starting scrape")

start := time.Now()
registry := prometheus.NewRegistry()
c := collector.New(r.Context(), target, module, logger)
c := collector.New(r.Context(), target, auth, module, logger)
registry.MustRegister(c)
// Delegate http serving to Prometheus client library, which will call collector.Collect.
h := promhttp.HandlerFor(registry, promhttp.HandlerOpts{})
h.ServeHTTP(w, r)
duration := time.Since(start).Seconds()
snmpDuration.WithLabelValues(moduleName).Observe(duration)
snmpDuration.WithLabelValues(authName, moduleName).Observe(duration)
level.Debug(logger).Log("msg", "Finished scrape", "duration_seconds", duration)
}

@@ -117,7 +134,7 @@ func updateConfiguration(w http.ResponseWriter, r *http.Request) {
http.Error(w, fmt.Sprintf("failed to reload config: %s", err), http.StatusInternalServerError)
}
default:
http.Error(w, "POST method expected", 400)
http.Error(w, "POST method expected", http.StatusBadRequest)
}
}

@@ -133,6 +150,12 @@ func (sc *SafeConfig) ReloadConfig(configFile string) (err error) {
}
sc.Lock()
sc.C = conf
// Initialize metrics.
for auth := range sc.C.Auths {
for module := range sc.C.Modules {
snmpDuration.WithLabelValues(auth, module)
}
}
sc.Unlock()
return nil
}
@@ -151,8 +174,7 @@ func main() {
prometheus.MustRegister(version.NewCollector("snmp_exporter"))

// Bail early if the config is bad.
var err error
sc.C, err = config.LoadFile(*configFile)
err := sc.ReloadConfig(*configFile)
if err != nil {
level.Error(logger).Log("msg", "Error parsing config file", "err", err)
os.Exit(1)
@@ -164,11 +186,6 @@ func main() {
return
}

// Initialize metrics.
for module := range *sc.C {
snmpDuration.WithLabelValues(module)
}

hup := make(chan os.Signal, 1)
reloadCh = make(chan chan error)
signal.Notify(hup, syscall.SIGHUP)
@@ -221,10 +238,11 @@ func main() {
<h1>SNMP Exporter</h1>
<form action="/snmp">
<label>Target:</label> <input type="text" name="target" placeholder="X.X.X.X" value="1.2.3.4"><br>
<label>Auth:</label> <input type="text" name="auth" placeholder="auth" value="public"><br>
<label>Module:</label> <input type="text" name="module" placeholder="module" value="if_mib"><br>
<input type="submit" value="Submit">
</form>
<p><a href="/config">Config</a></p>
<p><a href="/config">Config</a></p>
</body>
</html>`))
})
@@ -235,7 +253,7 @@ func main() {
sc.RUnlock()
if err != nil {
level.Error(logger).Log("msg", "Error marshaling configuration", "err", err)
http.Error(w, err.Error(), 500)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Write(c)
114,486 changes: 57,293 additions & 57,193 deletions snmp.yml

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions testdata/snmp-auth.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module-auth-test:
auth:
auths:
with_secret:
community: mysecret
security_level: SomethingReadOnly
username: user
21 changes: 11 additions & 10 deletions testdata/snmp-with-overrides.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
default:
walk:
- 1.1.1.1.1.1
metrics:
- name: testMetric
oid: 1.1.1.1.1
type: gauge
regex_extracts:
Temp:
- regex:
modules:
default:
walk:
- 1.1.1.1.1.1
metrics:
- name: testMetric
oid: 1.1.1.1.1
type: gauge
regex_extracts:
Temp:
- regex:

0 comments on commit b57a63b

Please sign in to comment.