diff --git a/README.md b/README.md index 088dfac5..d4fe392e 100644 --- a/README.md +++ b/README.md @@ -1,236 +1,35 @@ # Prometheus SNMP Exporter +This project is from https://github.com/prometheus/snmp_exporter/ . But had some improve. -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 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 access, SNMP v3 is required. - -# Concepts - -While SNMP uses a hierarchical data structure and Prometheus uses an -n-dimensional matrix, the two systems map perfectly, and without the need -to walk through data by hand. `snmp_exporter` maps the data for you. - -## Prometheus - -Prometheus is able to map SNMP index instances to labels. For example, the `ifEntry` specifies an INDEX of `ifIndex`. This becomes the `ifIndex` label in Prometheus. - -If an SNMP entry has multiple index values, each value is mapped to a separate Prometheus label. - -## SNMP - -SNMP is structured in OID trees, described by MIBs. OID subtrees have the same -order across different locations in the tree. The order under -`1.3.6.1.2.1.2.2.1.1` (`ifIndex`) is the same as in `1.3.6.1.2.1.2.2.1.2` -(`ifDescr`), `1.3.6.1.2.1.31.1.1.1.10` (`ifHCOutOctets`), etc. The numbers are -OIDs, the names in parentheses are the names from a MIB, in this case -[IF-MIB](http://www.oidview.com/mibs/0/IF-MIB.html). - -## Mapping - -Given a device with an interface at number 2, a partial `snmpwalk` return looks -like: - +## Different +New feature: +Filter: allow you set filters +this will get target data that oid 1.3.6.1.2.1.31.1.1.1.6 notEquals 0 ``` -1.3.6.1.2.1.2.2.1.1.2 = INTEGER: 2 # ifIndex for '2' is literally just '2' -1.3.6.1.2.1.2.2.1.2.2 = STRING: "eth0" # ifDescr -1.3.6.1.2.1.31.1.1.1.1.2 = STRING: "eth0" # IfName -1.3.6.1.2.1.31.1.1.1.10.2 = INTEGER: 1000 # ifHCOutOctets, 1000 bytes -1.3.6.1.2.1.31.1.1.1.18.2 = STRING: "" # ifAlias + filters: + - oid: 1.3.6.1.2.1.31.1.1.1.6 + operation: notEquals + targets: + - 1.3.6.1.2.1.31.1.1.1.6 + - 1.3.6.1.2.1.31.1.1.1.10 + - 1.3.6.1.2.1.2.2.1.8 + - 1.3.6.1.2.1.2.2.1.3 + values: + - "0" ``` +All you set : notEquals/equals/regNotEquals/regEquals -`snmp_exporter` combines all of this data into: - -``` -ifHCOutOctets{ifAlias="",ifDescr="eth0",ifIndex="2",ifName="eth0"} 1000 -``` - -# Scaling - -A single instance of `snmp_exporter` can be run for thousands of devices. - -# Usage - -## Installation - -Binaries can be downloaded from the [Github -releases](https://github.com/prometheus/snmp_exporter/releases) page and need no -special installation. - -We also provide a sample [systemd unit file](examples/systemd/snmp_exporter.service). - -## Running - -Start `snmp_exporter` as a daemon or from CLI: - -```sh -./snmp_exporter -``` - -Visit where `192.0.0.8` is the IP or -FQDN of the SNMP device to get metrics from. Note that this will use the default transport (`udp`), -default port (`161`), default auth (`public_v2`) and default module (`if_mib`). The auth and module -must be defined in the `snmp.yml` file. - -For example, if you have an auth named `my_secure_v3` for walking `ddwrt`, the URL would look like -. - -To configure a different transport and/or port, use the syntax `[transport://]host[:port]`. - -For example, to scrape a device using `tcp` on port `1161`, the URL would look like -. - -Note that [URL encoding](https://en.wikipedia.org/wiki/URL_encoding) should be used for `target` due -to the `:` and `/` characters. Prometheus encodes query parameters automatically and manual encoding -is not necessary within the Prometheus configuration file. - -Metrics concerning the operation of the exporter itself are available at the -endpoint . - -It is possible to supply an optional `snmp_context` parameter in the URL, like this: - -The `snmp_context` parameter in the URL would override the `context_name` parameter in the `snmp.yml` file. - -## Multi-Module Handling -The multi-module functionality allows you to specify multiple modules, enabling the retrieval of information from several modules in a single scrape. -The concurrency can be specified using the snmp-exporter option `--snmp.module-concurrency` (the default is 1). - -Note: This implementation does not perform any de-duplication of walks between different modules. - -There are two ways to specify multiple modules. You can either separate them with a comma or define multiple params_module. -The URLs would look like this: -For comma separation: +Generator: ``` -http://localhost:9116/snmp?module=if_mib,arista_sw&target=192.0.0.8 -``` - -For multiple params_module: -``` -http://localhost:9116/snmp?module=if_mib&module=arista_sw&target=192.0.0.8 -``` - -Prometheus Example: -```YAML - - - job_name: 'my' - params: - module: - - if_mib - - synology - - ucd_la_table -``` - -## Configuration - -The default configuration file name is `snmp.yml` and should not be edited -by hand. If you need to change it, see -[Generating configuration](#generating-configuration). - -The default `snmp.yml` file covers a variety of common hardware walking them -using SNMP v2 GETBULK. - -The `--config.file` parameter can be used multiple times to load more than one file. -It also supports [glob filename matching](https://pkg.go.dev/path/filepath#Glob), e.g. `snmp*.yml`. - -The `--config.expand-environment-variables` parameter allows passing environment variables into some fields of the configuration file. The `username`, `password` & `priv_password` fields in the auths section are supported. Defaults to disabled. - -Duplicate `module` or `auth` entries are treated as invalid and can not be loaded. - -## Prometheus Configuration - -The URL params `target`, `auth`, and `module` can be controlled through relabelling. - -Example config: -```YAML -scrape_configs: - - job_name: 'snmp' - static_configs: + filters: + dynamic: - targets: - - 192.168.1.2 # SNMP device. - - switch.local # SNMP device. - - tcp://192.168.1.3:1161 # SNMP device using TCP transport and custom port. - metrics_path: /snmp - params: - auth: [public_v2] - module: [if_mib] - relabel_configs: - - source_labels: [__address__] - target_label: __param_target - - source_labels: [__param_target] - target_label: instance - - target_label: __address__ - replacement: 127.0.0.1:9116 # The SNMP exporter's real hostname:port. - - # Global exporter-level metrics - - job_name: 'snmp_exporter' - static_configs: - - targets: ['localhost:9116'] + - ifHCInOctets + - ifHCOutOctets + - ifOperStatus + - ifType + oid: 1.3.6.1.2.1.31.1.1.1.6 + operation: notEquals + values: ["0"] ``` - -You could pass `username`, `password` & `priv_password` via environment variables of your choice in below format. -If the variables exist in the environment, they are resolved on the fly otherwise the string in the config file is passed as-is. - -This requires the `--config.expand-environment-variables` flag be set. - -```YAML -auths: - example_with_envs: - community: mysecret - security_level: SomethingReadOnly - username: ${ARISTA_USERNAME} - password: ${ARISTA_PASSWORD} - auth_protocol: SHA256 - priv_protocol: AES - priv_password: ${ARISTA_PRIV_PASSWORD} -``` - -Similarly to [blackbox_exporter](https://github.com/prometheus/blackbox_exporter), -`snmp_exporter` is meant to run on a few central machines and can be thought of -like a "Prometheus proxy". - -### TLS and basic authentication - -The SNMP Exporter supports TLS and basic authentication. This enables better -control of the various HTTP endpoints. - -To use TLS and/or basic authentication, you need to pass a configuration file -using the `--web.config.file` parameter. The format of the file is described -[in the exporter-toolkit repository](https://github.com/prometheus/exporter-toolkit/blob/master/docs/web-configuration.md). - -Note that the TLS and basic authentication settings affect all HTTP endpoints: -/metrics for scraping, /snmp for scraping SNMP devices, and the web UI. - -### Generating configuration - -Most use cases should be covered by our [default configuration](snmp.yml). -If you need to generate your own configuration from MIBs, you can use the -[generator](generator/). - -Use the generator if you need to customize which objects are walked or use -non-public MIBs. - -## Large counter value handling - -In order to provide accurate counters for large Counter64 values, the exporter -will automatically wrap the value every 2^53 to avoid 64-bit float rounding. -Prometheus handles this gracefully for you and you will not notice any negative -effects. - -If you need to disable this feature for non-Prometheus systems, use the -command line flag `--no-snmp.wrap-large-counters`. - -# Once you have it running - -It can be opaque to get started with all this, but in our own experience, -snmp_exporter is honestly the best way to interact with SNMP. To make it -easier for others, please consider contributing back your configurations to -us. -`snmp.yml` config should be accompanied by generator config. -For your dashboard, alerts, and recording rules, please consider -contributing them to . diff --git a/collector/collector.go b/collector/collector.go index 92ea9115..62bb7ff6 100644 --- a/collector/collector.go +++ b/collector/collector.go @@ -30,8 +30,11 @@ import ( "github.com/itchyny/timefmt-go" "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/snmp_exporter/config" - "github.com/prometheus/snmp_exporter/scraper" + "github.com/mengxifl/snmp_exporter/config" + "github.com/mengxifl/snmp_exporter/scraper" + + // "github.com/prometheus/snmp_exporter/config" + // "github.com/prometheus/snmp_exporter/scraper" ) var ( @@ -177,27 +180,44 @@ func configureTarget(g *gosnmp.GoSNMP, target string) error { func filterAllowedIndices(logger *slog.Logger, filter config.DynamicFilter, pdus []gosnmp.SnmpPDU, allowedList []string, metrics Metrics) []string { logger.Debug("Evaluating rule for oid", "oid", filter.Oid) + var operationErr = !regexp.MustCompile(`(?i)equals`).MatchString(filter.Operation) + var isRegMatch = strings.Contains(filter.Operation, `reg`) + var isEquals = !strings.Contains(filter.Operation, `otEquals`) + if operationErr { + return allowedList + } for _, pdu := range pdus { found := false for _, val := range filter.Values { snmpval := pduValueAsString(&pdu, "DisplayString", metrics) - logger.Debug("evaluating filters", "config value", val, "snmp value", snmpval) - - if regexp.MustCompile(val).MatchString(snmpval) { + // "regNotEquals" / "regEquals" / "equals" / notEquals + logger.Debug("evaluating filters", "config value", val, "snmp value", snmpval, "filter operation", filter.Operation) + if regexp.MustCompile(val).MatchString(snmpval) && isRegMatch { + found = true + break + } + if val == snmpval && !isRegMatch { found = true break } } - if found { + if found && isEquals { pduArray := strings.Split(pdu.Name, ".") index := pduArray[len(pduArray)-1] - logger.Debug("Caching index", "index", index) + logger.Debug("Caching index equals", "index", index) + allowedList = append(allowedList, index) + } + if !found && !isEquals { + pduArray := strings.Split(pdu.Name, ".") + index := pduArray[len(pduArray)-1] + logger.Debug("Caching index notEquals", "index", index) allowedList = append(allowedList, index) } } return allowedList } + func updateWalkConfig(walkConfig []string, filter config.DynamicFilter, logger *slog.Logger) []string { newCfg := []string{} for _, elem := range walkConfig { diff --git a/config/config.go b/config/config.go index e2f531d0..ca75a015 100644 --- a/config/config.go +++ b/config/config.go @@ -210,9 +210,10 @@ type StaticFilter struct { Indices []string `yaml:"indices,omitempty"` } type DynamicFilter struct { - Oid string `yaml:"oid"` - Targets []string `yaml:"targets,omitempty"` - Values []string `yaml:"values,omitempty"` + Oid string `yaml:"oid"` + Operation string `yaml:"operation,omitempty"` + Targets []string `yaml:"targets,omitempty"` + Values []string `yaml:"values,omitempty"` } type Metric struct { diff --git a/generator/config.go b/generator/config.go index a0781e27..e2ae44c4 100644 --- a/generator/config.go +++ b/generator/config.go @@ -16,8 +16,9 @@ package main import ( "fmt" "strconv" + "github.com/mengxifl/snmp_exporter/config" + // "github.com/prometheus/snmp_exporter/config" - "github.com/prometheus/snmp_exporter/config" ) // The generator config. diff --git a/generator/main.go b/generator/main.go index 8e61281b..250a0335 100644 --- a/generator/main.go +++ b/generator/main.go @@ -26,7 +26,8 @@ import ( "github.com/prometheus/common/promslog/flag" "gopkg.in/yaml.v2" - "github.com/prometheus/snmp_exporter/config" + "github.com/mengxifl/snmp_exporter/config" + // "github.com/prometheus/snmp_exporter/config" ) var ( @@ -66,6 +67,7 @@ func generateConfig(nodes *Node, nameToNode map[string]*Node, logger *slog.Logge mNameToNode[n.Module+"::"+n.Label] = n } }) + // fmt.Println( m ) out, err := generateConfigModule(m, mNodes, mNameToNode, logger) if err != nil { return err diff --git a/generator/tree.go b/generator/tree.go index 1319ee0b..00b27453 100644 --- a/generator/tree.go +++ b/generator/tree.go @@ -21,7 +21,7 @@ import ( "strconv" "strings" - "github.com/prometheus/snmp_exporter/config" + "github.com/mengxifl/snmp_exporter/config" ) // These types have one following the other. @@ -283,6 +283,7 @@ func generateConfigModule(cfg *ModuleConfig, node *Node, nameToNode map[string]* out := &config.Module{} needToWalk := map[string]struct{}{} tableInstances := map[string][]string{} + // nameToNode[name] // Apply type overrides for the current module. for name, params := range cfg.Overrides { @@ -565,7 +566,8 @@ func generateConfigModule(cfg *ModuleConfig, node *Node, nameToNode map[string]* } out.Filters = cfg.Filters.Dynamic - + // fmt.Printf( "%+T", out.Filters ) + buildDynamicFilter( &cfg.Filters.Dynamic, nameToNode ) oids := []string{} for k := range needToWalk { oids = append(oids, k) @@ -588,3 +590,24 @@ var ( func sanitizeLabelName(name string) string { return invalidLabelCharRE.ReplaceAllString(name, "_") } + + +func buildDynamicFilter( confFilter *[]config.DynamicFilter, nameToNode map[string]*Node ) *[]config.DynamicFilter { + for filterIndex, _ := range ( *confFilter ) { + val := &( *confFilter )[ filterIndex ] + if !isOid( val.Oid ) { + val.Oid = nameToNode[ val.Oid ].Oid + } + for index, target := range val.Targets { + if !isOid( target ) { + val.Targets[index] = nameToNode[ target ].Oid + } + } + } + return confFilter +} + + +func isOid( matchString string ) bool { + return regexp.MustCompile(`^(\d+\.)+\d*\.?$`).MatchString(matchString) +} diff --git a/go.mod b/go.mod index afffcae9..42f9e081 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,5 @@ -module github.com/prometheus/snmp_exporter +// module github.com/prometheus/snmp_exporter +module github.com/mengxifl/snmp_exporter go 1.22 diff --git a/main.go b/main.go index 4b843909..c79a2a11 100644 --- a/main.go +++ b/main.go @@ -36,8 +36,11 @@ import ( webflag "github.com/prometheus/exporter-toolkit/web/kingpinflag" yaml "gopkg.in/yaml.v2" - "github.com/prometheus/snmp_exporter/collector" - "github.com/prometheus/snmp_exporter/config" + "github.com/mengxifl/snmp_exporter/collector" + "github.com/mengxifl/snmp_exporter/config" + + // "github.com/prometheus/snmp_exporter/collector" + // "github.com/prometheus/snmp_exporter/config" ) const ( diff --git a/testdata/snmp-with-filter.yml b/testdata/snmp-with-filter.yml new file mode 100644 index 00000000..15a415c6 --- /dev/null +++ b/testdata/snmp-with-filter.yml @@ -0,0 +1,53 @@ +modules: + default: + walk: + - 1.3.6.1.2.1.31.1.1.1.6 + metrics: + - name: ifHCInOctets + oid: 1.3.6.1.2.1.31.1.1.1.6 + type: counter + help: The total number of octets received on the interface, including framing + characters - 1.3.6.1.2.1.31.1.1.1.6 + indexes: + - labelname: ifIndex + type: gauge + lookups: + - labels: + - ifIndex + labelname: ifName + oid: 1.3.6.1.2.1.31.1.1.1.1 + type: DisplayString + - labels: + - ifIndex + labelname: ifAlias + oid: 1.3.6.1.2.1.31.1.1.1.18 + type: DisplayString + filters: + # filter 1.3.6.1.2.1.31.1.1.1.6 that 1.3.6.1.2.1.31.1.1.1.6 value is notEquals 0 + - oid: 1.3.6.1.2.1.31.1.1.1.6 + operation: notEquals + targets: + - 1.3.6.1.2.1.31.1.1.1.6 + values: + - "0" + # filter 1.3.6.1.2.1.31.1.1.1.6 that 1.3.6.1.2.1.31.1.1.1.6 value is equals 0 + - oid: 1.3.6.1.2.1.31.1.1.1.6 + operation: equals + targets: + - 1.3.6.1.2.1.31.1.1.1.6 + values: + - "0" + # filter 1.3.6.1.2.1.31.1.1.1.6 that 1.3.6.1.2.1.31.1.1.1.6 value mean is contain 0 + - oid: 1.3.6.1.2.1.31.1.1.1.6 + operation: regEquals + targets: + - 1.3.6.1.2.1.31.1.1.1.6 + values: + - "0" + # filter 1.3.6.1.2.1.31.1.1.1.6 that 1.3.6.1.2.1.31.1.1.1.6 value mean is not contain 0 + - oid: 1.3.6.1.2.1.31.1.1.1.6 + operation: regNotEquals + targets: + - 1.3.6.1.2.1.31.1.1.1.6 + values: + - "0"