pax_global_header00006660000000000000000000000064141604314210014506gustar00rootroot0000000000000052 comment=5ac721e047f91e8906ece32ac2ff62797e2a5d6a prometheus-frr-exporter-1.1.0/000077500000000000000000000000001416043142100163355ustar00rootroot00000000000000prometheus-frr-exporter-1.1.0/.circleci/000077500000000000000000000000001416043142100201705ustar00rootroot00000000000000prometheus-frr-exporter-1.1.0/.circleci/config.yml000066400000000000000000000022651416043142100221650ustar00rootroot00000000000000version: 2.1 executors: golang: docker: # Whenever the Go version is updated here, .promu.yml, Dockerfile and line 6 of this file should also be updated. - image: circleci/golang:1.17 jobs: test: working_directory: /go/src/github.com/tynany/frr_exporter executor: golang steps: - checkout - run: make test build: working_directory: /go/src/github.com/tynany/frr_exporter executor: golang steps: - checkout - setup_remote_docker - run: make setup_promu - run: ./promu crossbuild - run: ./promu crossbuild tarballs - run: ./promu checksum .tarballs release: working_directory: /go/src/github.com/tynany/frr_exporter executor: golang steps: - checkout - setup_remote_docker - run: make setup_promu - run: ./promu crossbuild - run: ./promu crossbuild tarballs - run: ./promu checksum .tarballs - run: ./promu release .tarballs workflows: version: 2 build_and_release: jobs: - test - build - release: filters: branches: ignore: /.*/ tags: only: /v[0-9]+(\.[0-9]+)*(-.*)*/ prometheus-frr-exporter-1.1.0/.github/000077500000000000000000000000001416043142100176755ustar00rootroot00000000000000prometheus-frr-exporter-1.1.0/.github/workflows/000077500000000000000000000000001416043142100217325ustar00rootroot00000000000000prometheus-frr-exporter-1.1.0/.github/workflows/golangci-lint.yml000066400000000000000000000003751416043142100252110ustar00rootroot00000000000000name: golangci-lint on: push: jobs: golangci: name: lint runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: golangci-lint uses: golangci/golangci-lint-action@v2 with: version: v1.43.0 prometheus-frr-exporter-1.1.0/.gitignore000066400000000000000000000007241416043142100203300ustar00rootroot00000000000000 # Created by https://www.gitignore.io/api/go # Edit at https://www.gitignore.io/?templates=go ### Go ### # Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib # Test binary, built with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out # Dependency directories (remove the comment below to include it) # vendor/ ### Go Patch ### /vendor/ /Godeps/ frr_exporter # End of https://www.gitignore.io/api/go prometheus-frr-exporter-1.1.0/.golangci.yml000066400000000000000000000002101416043142100207120ustar00rootroot00000000000000linters-settings: errcheck: exclude-functions: - (github.com/go-kit/log.Logger).Log - (net/http.ResponseWriter).Write prometheus-frr-exporter-1.1.0/.promu.yml000066400000000000000000000015301416043142100202770ustar00rootroot00000000000000go: # Whenever the Go version is updated here, .circle/config.yml and Dockerfile should also be updated. version: 1.17 repository: path: github.com/tynany/frr_exporter build: binaries: - name: frr_exporter flags: -a -tags 'netgo static_build' ldflags: | -X github.com/prometheus/common/version.Version={{.Version}} -X github.com/prometheus/common/version.Revision={{.Revision}} -X github.com/prometheus/common/version.Branch={{.Branch}} -X github.com/prometheus/common/version.BuildUser={{user}}@{{host}} -X github.com/prometheus/common/version.BuildDate={{date "20060102-15:04:05"}} crossbuild: platforms: - linux/amd64 - linux/386 - linux/arm - linux/arm64 - darwin/amd64 - darwin/386 - darwin/arm - darwin/arm64 prometheus-frr-exporter-1.1.0/Dockerfile000066400000000000000000000006341416043142100203320ustar00rootroot00000000000000# Whenever the Go version is updated here, .circle/config.yml and .promu.yml should also be updated. FROM golang:1.17 WORKDIR /go/src/github.com/tynany/frr_exporter COPY . /go/src/github.com/tynany/frr_exporter RUN make setup_promu RUN ./promu build RUN ls -lah FROM frrouting/frr:v7.5.1 WORKDIR /app COPY --from=0 /go/src/github.com/tynany/frr_exporter/frr_exporter . EXPOSE 9342 ENTRYPOINT [ "./frr_exporter"]prometheus-frr-exporter-1.1.0/LICENSE000066400000000000000000000020461416043142100173440ustar00rootroot00000000000000MIT License Copyright (c) 2018 Tynan Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. prometheus-frr-exporter-1.1.0/Makefile000066400000000000000000000004631416043142100200000ustar00rootroot00000000000000PROMU_VERSION := 0.12.0 setup_promu: curl -s -L https://github.com/prometheus/promu/releases/download/v$(PROMU_VERSION)/promu-$(PROMU_VERSION).linux-amd64.tar.gz | tar -xvzf - mv promu-$(PROMU_VERSION).linux-amd64/promu . build: ./promu build --prefix $(PREFIX) $(PROMU_BINARIES) test: go test ./... prometheus-frr-exporter-1.1.0/README.md000066400000000000000000000262041416043142100176200ustar00rootroot00000000000000# Free Range Routing (FRR) Exporter Prometheus exporter for FRR version 3.0+ that collects metrics from the FRR Unix sockets and exposes them via HTTP, ready for collecting by Prometheus. ## Getting Started To run FRR Exporter: ``` ./frr_exporter [flags] ``` To view metrics on the default port (9342) and path (/metrics): ``` http://device:9342/metrics ``` To view available flags: ``` usage: frr_exporter [] Flags: -h, --help Show context-sensitive help (also try --help-long and --help-man). --collector.bgp.peer-types Enable the frr_bgp_peer_types_up metric (default: disabled). --collector.bgp.peer-types.keys=type ... Select the keys from the JSON formatted BGP peer description of which the values will be used with the frr_bgp_peer_types_up metric. Supports multiple values (default: type). --collector.bgp.peer-descriptions Add the value of the desc key from the JSON formatted BGP peer description as a label to peer metrics. (default: disabled). --collector.bgp.peer-descriptions.plain-text Use the full text field of the BGP peer description instead of the value of the JSON formatted desc key (default: disabled). --collector.bgp.advertised-prefixes Enables the frr_exporter_bgp_prefixes_advertised_count_total metric which exports the number of advertised prefixes to a BGP peer. This is an option for older versions of FRR that don't have PfxSent field (default: disabled). --frr.socket.dir-path="/var/run/frr" Path of of the localstatedir containing each daemon's Unix socket. --frr.socket.timeout=20s Timeout when connecting to the FRR daemon Unix sockets --frr.vtysh Use vtysh to query FRR instead of each daemon's Unix socket (default: disabled, recommended: disabled). --frr.vtysh.path="/usr/bin/vtysh" Path of vtysh. --frr.vtysh.timeout=20s The timeout when running vtysh commands (default: 20s). --frr.vtysh.sudo Enable sudo when executing vtysh commands. --frr.vtysh.options="" Additional options passed to vtysh. --collector.bfd Enable the bfd collector (default: enabled, to disable use --no-collector.bfd). --collector.bgp Enable the bgp collector (default: enabled, to disable use --no-collector.bgp). --collector.bgp6 Enable the bgp6 collector (default: disabled). --collector.bgpl2vpn Enable the bgpl2vpn collector (default: disabled). --collector.ospf Enable the ospf collector (default: enabled, to disable use --no-collector.ospf). --collector.pim Enable the pim collector (default: disabled). --collector.vrrp Enable the vrrp collector (default: disabled). --web.listen-address=":9342" Address on which to expose metrics and web interface. --web.telemetry-path="/metrics" Path under which to expose metrics. --web.config="" [EXPERIMENTAL] Path to config yaml file that can enable TLS or authentication. --log.level=info Only log messages with the given severity or above. One of: [debug, info, warn, error] --log.format=logfmt Output format of log messages. One of: [logfmt, json] --version Show application version. ``` Promethues configuraiton: ``` scrape_configs: - job_name: frr static_configs: - targets: - device1:9342 - device2:9342 relabel_configs: - source_labels: [__address__] regex: "(.*):\d+" target: instance ``` ## Docker A Docker container is available at [tynany/frr_exporter](https://hub.docker.com/r/tynany/frr_exporter). ### Example Mount the FRR socket directory (default `/var/run/frr`) inside the container, passing that directory to FRR Exporter via the `--frr.socket.dir-path` flag: ``` docker run --restart unless-stopped -d -p 9342:9342 -v /var/run/frr:/frr_sockets tynany/frr_exporter "--frr.socket.dir-path=/frr_sockets" ``` #### If using the --frr.vtysh flag (not recommended) Mount the FRR config directory (default `/etc/frr`) and FRR socket directory (default `/var/run/frr`) inside the container, passing those directories to vtysh options `--vty_socket` & `--config_dir` via the FRR Exporter flag `--frr.vtysh.options` if using: ``` docker run --restart unless-stopped -d -p 9342:9342 -v /etc/frr:/frr_config -v /var/run/frr:/frr_sockets tynany/frr_exporter "--frr.vtysh --frr.vtysh.options=--vty_socket=/frr_sockets --config_dir=/frr_config" ``` ## Collectors To disable a default collector, use the `--no-collector.$name` flag, or `--collector.$name` to enable it. ### Enabled by Default Name | Description --- | --- BGP | Per VRF and address family (currently support unicast only) BGP metrics:
- RIB entries
- RIB memory usage
- Configured peer count
- Peer memory usage
- Configure peer group count
- Peer group memory usage
- Peer messages in
- Peer messages out
- Peer received prefixes
- Peer advertised prefixes
- Peer state (established/down)
- Peer uptime OSPFv4 | Per VRF OSPF metrics:
- Neighbors
- Neighbor adjacencies BFD | BFD Peer metrics:
- Count of total number of peers
- BFD Peer State (up/down)
- BFD Peer Uptime in seconds ### Disabled by Default Name | Description --- | --- BGP IPv6 | Per VRF and address family (currently support unicast only) BGP IPv6 metrics:
- RIB entries
- RIB memory usage
- Configured peer count
- Peer memory usage
- Configure peer group count
- Peer group memory usage
- Peer messages in
- Peer messages out
- Peer active prfixes
- Peer state (established/down)
- Peer uptime BGP L2VPN | Per VRF and address family (currently support EVPN only) BGP L2VPN EVPN metrics:
- RIB entries
- RIB memory usage
- Configured peer count
- Peer memory usage
- Configure peer group count
- Peer group memory usage
- Peer messages in
- Peer messages out
- Peer active prfixes
- Peer state (established/down)
- Peer uptime VRRP | Per VRRP Interface, VrID and Protocol:
- Rx and TX statistics
- VRRP Status
- VRRP State Transitions
PIM | PIM metrics:
- Neighbor count
- Neighbor uptime ### Sending commands to FRR By default, FRR Exporter sends commands to FRR via the Unix sockets exposed by each FRR daemon (e.g. bgpd, ospfd, etc), usually located in `/var/run/frr`. If the sockets are located in a folder other than `/var/run/frr`, pass that directory to FRR Exporter via the `--frr.socket.dir-path` flag. #### VTYSH If desired, FRR Exporter can interface with FRR via the `vtysh` command by passing the `--frr.vtysh` flag to FRR Exporter. This is not recommended, and is far slower than FRR Exporter's default way of sending commands to FRR via Unix sockets. The default timeout is 20s but can be modified via the `--frr.vtysh.timeout` flag. ### BGP: Peer Description Labels The description of a BGP peer can be added as a label to all peer metrics by passing the `--collector.bgp.peer-descriptions` flag. The peer description must be JSON formatted with a `desc` field. Example configuration: ``` router bgp 64512 neighbor 192.168.0.1 remote-as 64513 neighbor 192.168.0.1 description {"desc":"important peer"} ``` If an unstructured description is preferred, additionally to `--collector.bgp.peer-descriptions` pass the `--collector.bgp.peer-descriptions.plain-text` flag. Example configuration: ``` router bgp 64512 neighbor 192.168.0.1 remote-as 64513 neighbor 192.168.0.1 description important peer ``` Note, it is recommended to leave this feature disabled as peer descriptions can easily change, resulting in a new time series. ### BGP: Advertised Prefixes to a Peer This is an option for older versions of FRR. If your FRR shows the "PfxSnt" field for Peers in the Established state in the output of `show bgp summary json`, you don't need to enable this option. The number of prefixes advertised to a BGP peer can be enabled (i.e. the `frr_exporter_bgp_prefixes_advertised_count_total` metric) by passing the `--collector.bgp.advertised-prefixes` flag. Please note, older FRR versions do not expose a summary of prefixes advertised to BGP peers, so each peer needs to be queried individually. For example, if 20 BGP peers are configured, 20 'sh ip bgp neigh X.X.X.X advertised-routes json' commands are sent to the Unix socket (or `vtysh` if the `--frr.vtysh` is used). This can be slow, especially if using the `--frr.vtysh` flag. The commands are run in parallel by FRR Exporter, but FRR executes them in serial. Due to the potential negative performance implications of running `vtysh` for every BGP peer, this metric is disabled by default. ### BGP: frr_bgp_peer_types_up FRR Exporter exposes a special metric, `frr_bgp_peer_types_up`, that can be used in scenarios where you want to create Prometheus queries that report on the number of types of BGP peers that are currently established, such as for Alertmanager. To implement this metric, a JSON formatted description must be configured on your BGP group. FRR Exporter will then use the value from the keys specific by the `--collector.bgp.peer-types.keys` flag (the default is `type`), and aggregates all BGP peers that are currently established and configured with that type. For example, if you want to know how many BGP peers are currently established that provide internet, you'd set the description of all BGP groups that provide internet to `{"type":"internet"}` and query Prometheus with `frr_bgp_peer_types_up{type="internet"})`. Going further, if you want to create an alert when the number of established BGP peers that provide internet is 1 or less, you'd use `sum(frr_bgp_peer_types_up{type="internet"}) <= 1`. To enable `frr_bgp_peer_types_up`, use the `--collector.bgp.peer-types` flag. ### OSPF: Multiple Instance Support [OSPF Mulit-instace](https://docs.frrouting.org/en/latest/ospfd.html#multi-instance-support) is supported by passing a comma-separated list of instances ID to FRR Exporter via the `--collector.ospf.instances` flag. For example, if `/etc/frr/daemons` contains the below configuration, FRR Exporter should be run as: `./frr_exporter --collector.ospf.instances=1,5,6`. ``` ... ospfd=yes ospfd_instances=1,5,6 ... ``` Note: FRR Exporter does not support multi-instance when using `vtysh` to interface with FRR via the `--frr.vtysh` flag for the following reasons: * Invalid JSON is returned when OSPF commands are executed by `vtysh`. For example,\ `show ip ospf vrf all interface json` returns the concatenated JSON from each OSPF instance. * Vtysh does not support `vrf` and `instance` in the same commend. For example,\ `show ip ospf 1 vrf all interface json` is an invalid command. ## Development ### Building ``` go get github.com/tynany/frr_exporter cd ${GOPATH}/src/github.com/prometheus/frr_exporter go build ``` ## TODO - Collector and main tests - OSPF6 - ISIS - Additional BGP SAFI - Feel free to submit a new feature request prometheus-frr-exporter-1.1.0/collector/000077500000000000000000000000001416043142100203235ustar00rootroot00000000000000prometheus-frr-exporter-1.1.0/collector/bfd.go000066400000000000000000000057411416043142100214140ustar00rootroot00000000000000package collector import ( "encoding/json" "github.com/go-kit/log" "github.com/prometheus/client_golang/prometheus" ) var ( bfdSubsystem = "bfd" ) func init() { registerCollector(bfdSubsystem, enabledByDefault, NewBFDCollector) } type bfdCollector struct { logger log.Logger descriptions map[string]*prometheus.Desc } // NewBFDCollector collects BFD metrics, implemented as per the Collector interface. func NewBFDCollector(logger log.Logger) (Collector, error) { return &bfdCollector{logger: logger, descriptions: getBFDDesc()}, nil } func getBFDDesc() map[string]*prometheus.Desc { countLabels := []string{} peerLabels := []string{"local", "peer"} return map[string]*prometheus.Desc{ "bfdPeerCount": colPromDesc(bfdSubsystem, "peer_count", "Number of peers detected.", countLabels), "bfdPeerUptime": colPromDesc(bfdSubsystem, "peer_uptime", "Uptime of bfd peer in seconds", peerLabels), "bfdPeerState": colPromDesc(bfdSubsystem, "peer_state", "State of the bfd peer (1 = Up, 0 = Down).", peerLabels), } } // Update implemented as per the Collector interface. func (c *bfdCollector) Update(ch chan<- prometheus.Metric) error { cmd := "show bfd peers json" jsonBFDInterface, err := executeBFDCommand(cmd) if err != nil { return err } if err = processBFDPeers(ch, jsonBFDInterface, c.descriptions); err != nil { return cmdOutputProcessError(cmd, string(jsonBFDInterface), err) } return nil } func processBFDPeers(ch chan<- prometheus.Metric, jsonBFDInterface []byte, bfdDesc map[string]*prometheus.Desc) error { var bfdPeers []bfdPeer if err := json.Unmarshal(jsonBFDInterface, &bfdPeers); err != nil { return err } // metric is a count of the number of peers newGauge(ch, bfdDesc["bfdPeerCount"], float64(len(bfdPeers))) for _, p := range bfdPeers { labels := []string{p.Local, p.Peer} // get the uptime of the connection to the peer in seconds newGauge(ch, bfdDesc["bfdPeerUptime"], float64(p.Uptime), labels...) // state of connection to the bfd peer, up or down var bfdState float64 if p.Status == "up" { bfdState = 1 } newGauge(ch, bfdDesc["bfdPeerState"], bfdState, labels...) } return nil } type bfdPeer struct { Multihop bool `json:"multihop"` Peer string `json:"peer"` Local string `json:"local"` Vrf string `json:"vrf"` ID int `json:"id"` RemoteID int `json:"remote-id"` Status string `json:"status"` Uptime int `json:"uptime"` Diagnostic string `json:"diagnostic"` RemoteDiagnostic string `json:"remote-diagnostic"` ReceiveInterval int `json:"receive-interval"` TransmitInterval int `json:"transmit-interval"` EchoInterval int `json:"echo-interval"` RemoteReceiveInterval int `json:"remote-receive-interval"` RemoteTransmitInterval int `json:"remote-transmit-interval"` RemoteEchoInterval int `json:"remote-echo-interval"` } prometheus-frr-exporter-1.1.0/collector/bfd_test.go000066400000000000000000000071711416043142100224520ustar00rootroot00000000000000package collector import ( "fmt" "regexp" "strings" "testing" "github.com/prometheus/client_golang/prometheus" dto "github.com/prometheus/client_model/go" ) var ( bfdPeers = []byte(`[ { "multihop": false, "peer": "10.10.141.61", "local": "10.10.141.81", "vrf": "default", "id": 869087474, "remote-id": 533345668, "status": "up", "uptime": 847716, "diagnostic": "ok", "remote-diagnostic": "ok", "receive-interval": 300, "transmit-interval": 300, "echo-interval": 0, "remote-receive-interval": 300, "remote-transmit-interval": 300, "remote-echo-interval": 300 }, { "multihop": false, "peer": "10.10.141.62", "local": "10.10.141.81", "vrf": "default", "id": 2809641312, "remote-id": 3617154307, "status": "up", "uptime": 847595, "diagnostic": "ok", "remote-diagnostic": "ok", "receive-interval": 300, "transmit-interval": 300, "echo-interval": 0, "remote-receive-interval": 300, "remote-transmit-interval": 300, "remote-echo-interval": 300 }, { "multihop": false, "peer": "10.10.141.63", "local": "10.10.141.81", "vrf": "default", "id": 2809641312, "remote-id": 3617154307, "status": "down", "uptime": 847888, "diagnostic": "ok", "remote-diagnostic": "ok", "receive-interval": 300, "transmit-interval": 300, "echo-interval": 0, "remote-receive-interval": 300, "remote-transmit-interval": 300, "remote-echo-interval": 300 } ] `) expectedBFDMetrics = map[string]float64{ "frr_bfd_peer_count{}": 3, "frr_bfd_peer_uptime{local=10.10.141.81,peer=10.10.141.61}": 847716, "frr_bfd_peer_state{local=10.10.141.81,peer=10.10.141.61}": 1, "frr_bfd_peer_uptime{local=10.10.141.81,peer=10.10.141.62}": 847595, "frr_bfd_peer_state{local=10.10.141.81,peer=10.10.141.62}": 1, "frr_bfd_peer_uptime{local=10.10.141.81,peer=10.10.141.63}": 847888, "frr_bfd_peer_state{local=10.10.141.81,peer=10.10.141.63}": 0, } ) func TestProcessBFDPeers(t *testing.T) { ch := make(chan prometheus.Metric, 1024) if err := processBFDPeers(ch, bfdPeers, getBFDDesc()); err != nil { t.Errorf("error calling processBFDPeers ipv4unicast: %s", err) } close(ch) // Create a map of following format: // key: metric_name{labelname:labelvalue,...} // value: metric value gotMetrics := make(map[string]float64) for { msg, more := <-ch if !more { break } metric := &dto.Metric{} if err := msg.Write(metric); err != nil { t.Errorf("error writing metric: %s", err) } var labels []string for _, label := range metric.GetLabel() { labels = append(labels, fmt.Sprintf("%s=%s", label.GetName(), label.GetValue())) } var value float64 if metric.GetCounter() != nil { value = metric.GetCounter().GetValue() } else if metric.GetGauge() != nil { value = metric.GetGauge().GetValue() } re, err := regexp.Compile(`.*fqName: "(.*)", help:.*`) if err != nil { t.Errorf("could not compile regex: %s", err) } metricName := re.FindStringSubmatch(msg.Desc().String())[1] gotMetrics[fmt.Sprintf("%s{%s}", metricName, strings.Join(labels, ","))] = value } for metricName, metricVal := range gotMetrics { if expectedMetricVal, ok := expectedBFDMetrics[metricName]; ok { if expectedMetricVal != metricVal { t.Errorf("metric %s expected value %v got %v", metricName, expectedMetricVal, metricVal) } } else { t.Errorf("unexpected metric: %s : %v", metricName, metricVal) } } for expectedMetricName, expectedMetricVal := range expectedBFDMetrics { if _, ok := gotMetrics[expectedMetricName]; !ok { t.Errorf("missing metric: %s value %v", expectedMetricName, expectedMetricVal) } } } prometheus-frr-exporter-1.1.0/collector/bgp.go000066400000000000000000000344001416043142100214230ustar00rootroot00000000000000package collector import ( "encoding/json" "fmt" "strconv" "strings" "sync" "github.com/go-kit/log" "github.com/go-kit/log/level" "github.com/prometheus/client_golang/prometheus" kingpin "gopkg.in/alecthomas/kingpin.v2" ) var ( bgpSubsystem = "bgp" bgpPeerTypes = kingpin.Flag("collector.bgp.peer-types", "Enable the frr_bgp_peer_types_up metric (default: disabled).").Default("False").Bool() frrBGPDescKey = kingpin.Flag("collector.bgp.peer-types.keys", "Select the keys from the JSON formatted BGP peer description of which the values will be used with the frr_bgp_peer_types_up metric. Supports multiple values (default: type).").Default("type").Strings() bgpPeerDescs = kingpin.Flag("collector.bgp.peer-descriptions", "Add the value of the desc key from the JSON formatted BGP peer description as a label to peer metrics. (default: disabled).").Default("False").Bool() bgpPeerDescsText = kingpin.Flag("collector.bgp.peer-descriptions.plain-text", "Use the full text field of the BGP peer description instead of the value of the JSON formatted desc key (default: disabled).").Default("False").Bool() bgpAdvertisedPrefixes = kingpin.Flag("collector.bgp.advertised-prefixes", "Enables the frr_exporter_bgp_prefixes_advertised_count_total metric which exports the number of advertised prefixes to a BGP peer. This is an option for older versions of FRR that don't have PfxSent field (default: disabled).").Default("False").Bool() ) func init() { registerCollector(bgpSubsystem, enabledByDefault, NewBGPCollector) registerCollector(bgpSubsystem+"6", disabledByDefault, NewBGP6Collector) registerCollector(bgpSubsystem+"l2vpn", disabledByDefault, NewBGPL2VPNCollector) } type bgpCollector struct { logger log.Logger descriptions map[string]*prometheus.Desc afi string } // NewBGPCollector collects BGP metrics, implemented as per the Collector interface. func NewBGPCollector(logger log.Logger) (Collector, error) { return &bgpCollector{logger: logger, descriptions: getBGPDesc(), afi: "ipv4"}, nil } func getBGPDesc() map[string]*prometheus.Desc { bgpLabels := []string{"vrf", "afi", "safi", "local_as"} bgpPeerTypeLabels := []string{"type", "afi", "safi"} bgpPeerLabels := append(bgpLabels, "peer", "peer_as") if *bgpPeerDescs { bgpPeerLabels = append(bgpLabels, "peer", "peer_as", "peer_desc") } return map[string]*prometheus.Desc{ "ribCount": colPromDesc(bgpSubsystem, "rib_count_total", "Number of routes in the RIB.", bgpLabels), "ribMemory": colPromDesc(bgpSubsystem, "rib_memory_bytes", "Memory consumbed by the RIB.", bgpLabels), "peerCount": colPromDesc(bgpSubsystem, "peers_count_total", "Number peers configured.", bgpLabels), "peerMemory": colPromDesc(bgpSubsystem, "peers_memory_bytes", "Memory consumed by peers.", bgpLabels), "peerGroupCount": colPromDesc(bgpSubsystem, "peer_groups_count_total", "Number of peer groups configured.", bgpLabels), "peerGroupMemory": colPromDesc(bgpSubsystem, "peer_groups_memory_bytes", "Memory consumed by peer groups.", bgpLabels), "msgRcvd": colPromDesc(bgpSubsystem, "peer_message_received_total", "Number of received messages.", bgpPeerLabels), "msgSent": colPromDesc(bgpSubsystem, "peer_message_sent_total", "Number of sent messages.", bgpPeerLabels), "prefixReceivedCount": colPromDesc(bgpSubsystem, "peer_prefixes_received_count_total", "Number of prefixes received.", bgpPeerLabels), "prefixAdvertisedCount": colPromDesc(bgpSubsystem, "peer_prefixes_advertised_count_total", "Number of prefixes advertised.", bgpPeerLabels), "state": colPromDesc(bgpSubsystem, "peer_state", "State of the peer (2 = Administratively Down, 1 = Established, 0 = Down).", bgpPeerLabels), "UptimeSec": colPromDesc(bgpSubsystem, "peer_uptime_seconds", "How long has the peer been up.", bgpPeerLabels), "peerTypesUp": colPromDesc(bgpSubsystem, "peer_types_up", "Total Number of Peer Types that are Up.", bgpPeerTypeLabels), } } // Update implemented as per the Collector interface. func (c *bgpCollector) Update(ch chan<- prometheus.Metric) error { return collectBGP(ch, c.afi, c.logger, c.descriptions) } // NewBGP6Collector collects BGPv6 metrics, implemented as per the Collector interface. func NewBGP6Collector(logger log.Logger) (Collector, error) { return &bgpCollector{logger: logger, descriptions: getBGPDesc(), afi: "ipv6"}, nil } type bgpL2VPNCollector struct { logger log.Logger descriptions map[string]*prometheus.Desc } // NewBGPL2VPNCollector collects BGP L2VPN metrics, implemented as per the Collector interface. func NewBGPL2VPNCollector(logger log.Logger) (Collector, error) { return &bgpL2VPNCollector{logger: logger, descriptions: getBGPL2VPNDesc()}, nil } func getBGPL2VPNDesc() map[string]*prometheus.Desc { bgpDesc := getBGPDesc() labels := []string{"vni", "type", "vxlanIf", "tenantVrf"} metricPrefix := "bgp_l2vpn_evpn" bgpDesc["numMacs"] = colPromDesc(metricPrefix, "mac_count_total", "Number of known MAC addresses", labels) bgpDesc["numArpNd"] = colPromDesc(metricPrefix, "arp_nd_count_total", "Number of ARP / ND entries", labels) bgpDesc["numRemoteVteps"] = colPromDesc(metricPrefix, "remote_vtep_count_total", "Number of known remote VTEPs. A value of -1 indicates a non-integer output from FRR, such as n/a.", labels) return bgpDesc } // Update implemented as per the Collector interface. func (c *bgpL2VPNCollector) Update(ch chan<- prometheus.Metric) error { if err := collectBGP(ch, "l2vpn", c.logger, c.descriptions); err != nil { return err } cmd := "show evpn vni json" jsonBGPL2vpnEvpnSum, err := executeZebraCommand(cmd) if err != nil { return err } if len(jsonBGPL2vpnEvpnSum) == 0 { return nil } if err := processBgpL2vpnEvpnSummary(ch, jsonBGPL2vpnEvpnSum, c.descriptions); err != nil { return cmdOutputProcessError(cmd, string(jsonBGPL2vpnEvpnSum), err) } return nil } type vxLanStats struct { Vni int VxlanType string `json:"type"` VxlanIf string NumMacs float64 NumArpNd float64 NumRemoteVteps interface{} // it's possible for the numRemoteVteps field to contain non-int values such as "n\/a" TenantVrf string } func processBgpL2vpnEvpnSummary(ch chan<- prometheus.Metric, jsonBGPL2vpnEvpnSum []byte, bgpL2vpnDesc map[string]*prometheus.Desc) error { var jsonMap map[string]vxLanStats if err := json.Unmarshal(jsonBGPL2vpnEvpnSum, &jsonMap); err != nil { return err } for _, vxLanStat := range jsonMap { bgpL2vpnLabels := []string{strconv.Itoa(vxLanStat.Vni), vxLanStat.VxlanType, vxLanStat.VxlanIf, vxLanStat.TenantVrf} newGauge(ch, bgpL2vpnDesc["numMacs"], vxLanStat.NumMacs, bgpL2vpnLabels...) newGauge(ch, bgpL2vpnDesc["numArpNd"], vxLanStat.NumArpNd, bgpL2vpnLabels...) remoteVteps, ok := vxLanStat.NumRemoteVteps.(float64) if !ok { remoteVteps = -1 } newGauge(ch, bgpL2vpnDesc["numRemoteVteps"], remoteVteps, bgpL2vpnLabels...) } return nil } func collectBGP(ch chan<- prometheus.Metric, AFI string, logger log.Logger, desc map[string]*prometheus.Desc) error { SAFI := "" if (AFI == "ipv4") || (AFI == "ipv6") { SAFI = "unicast" } else if AFI == "l2vpn" { SAFI = "evpn" } cmd := fmt.Sprintf("show bgp vrf all %s %s summary json", AFI, SAFI) jsonBGPSum, err := executeBGPCommand(cmd) if err != nil { return err } if err := processBGPSummary(ch, jsonBGPSum, AFI, SAFI, logger, desc); err != nil { return cmdOutputProcessError(cmd, string(jsonBGPSum), err) } return nil } func processBGPSummary(ch chan<- prometheus.Metric, jsonBGPSum []byte, AFI string, SAFI string, logger log.Logger, bgpDesc map[string]*prometheus.Desc) error { var jsonMap map[string]bgpProcess if err := json.Unmarshal(jsonBGPSum, &jsonMap); err != nil { return err } var peerDescJSON map[string]map[string]string var peerDescText map[string]string var err error if *bgpPeerTypes || *bgpPeerDescs { peerDescJSON, peerDescText, err = getBGPPeerDesc(logger) if err != nil { return err } } peerTypes := make(map[string]float64) wgAdvertisedPrefixes := &sync.WaitGroup{} for vrfName, vrfData := range jsonMap { // The labels are "vrf", "afi", "safi", "local_as" localAs := strconv.FormatInt(vrfData.AS, 10) procLabels := []string{strings.ToLower(vrfName), strings.ToLower(AFI), strings.ToLower(SAFI), localAs} // No point collecting metrics if no peers configured. if vrfData.PeerCount != 0 { newGauge(ch, bgpDesc["ribCount"], vrfData.RIBCount, procLabels...) newGauge(ch, bgpDesc["ribMemory"], vrfData.RIBMemory, procLabels...) newGauge(ch, bgpDesc["peerCount"], vrfData.PeerCount, procLabels...) newGauge(ch, bgpDesc["peerMemory"], vrfData.PeerMemory, procLabels...) newGauge(ch, bgpDesc["peerGroupCount"], vrfData.PeerGroupCount, procLabels...) newGauge(ch, bgpDesc["peerGroupMemory"], vrfData.PeerGroupMemory, procLabels...) for peerIP, peerData := range vrfData.Peers { // The labels are "vrf", "afi", "safi", "local_as", "peer", "remote_as" peerLabels := []string{strings.ToLower(vrfName), strings.ToLower(AFI), strings.ToLower(SAFI), localAs, peerIP, strconv.FormatInt(peerData.RemoteAs, 10)} if *bgpPeerDescs { d := "" if *bgpPeerDescsText { d = peerDescText[peerIP] } else { d = peerDescJSON[peerIP]["desc"] } // The labels are "vrf", "afi", "safi", "local_as", "peer", "remote_as", "peer_desc" peerLabels = append(peerLabels, d) } // In earlier versions of FRR did not expose a summary of advertised prefixes for all peers, but in later versions it can get with PfxSnt field. if peerData.PfxSnt != nil { newGauge(ch, bgpDesc["prefixAdvertisedCount"], *peerData.PfxSnt, peerLabels...) } else if *bgpAdvertisedPrefixes { wgAdvertisedPrefixes.Add(1) go getPeerAdvertisedPrefixes(ch, wgAdvertisedPrefixes, AFI, SAFI, vrfName, peerIP, logger, bgpDesc, peerLabels...) } newCounter(ch, bgpDesc["msgRcvd"], peerData.MsgRcvd, peerLabels...) newCounter(ch, bgpDesc["msgSent"], peerData.MsgSent, peerLabels...) newGauge(ch, bgpDesc["UptimeSec"], peerData.PeerUptimeMsec*0.001, peerLabels...) // In earlier versions of FRR, the prefixReceivedCount JSON element is used for the number of recieved prefixes, but in later versions it was changed to PfxRcd. prefixReceived := 0.0 if peerData.PrefixReceivedCount != 0 { prefixReceived = peerData.PrefixReceivedCount } else if peerData.PfxRcd != 0 { prefixReceived = peerData.PfxRcd } newGauge(ch, bgpDesc["prefixReceivedCount"], prefixReceived, peerLabels...) if *bgpPeerTypes { for _, descKey := range *frrBGPDescKey { if peerDescJSON[peerIP][descKey] != "" { if _, exist := peerTypes[strings.TrimSpace(peerDescJSON[peerIP][descKey])]; !exist { peerTypes[strings.TrimSpace(peerDescJSON[peerIP][descKey])] = 0 } } } } peerState := 0.0 switch peerDataState := strings.ToLower(peerData.State); peerDataState { case "established": peerState = 1 if *bgpPeerTypes { for _, descKey := range *frrBGPDescKey { if peerDescJSON[peerIP][descKey] != "" { peerTypes[strings.TrimSpace(peerDescJSON[peerIP][descKey])]++ } } } case "idle (admin)": peerState = 2 } newGauge(ch, bgpDesc["state"], peerState, peerLabels...) } } } wgAdvertisedPrefixes.Wait() for peerType, count := range peerTypes { peerTypeLabels := []string{peerType, strings.ToLower(AFI), strings.ToLower(SAFI)} newGauge(ch, bgpDesc["peerTypesUp"], count, peerTypeLabels...) } return nil } func getPeerAdvertisedPrefixes(ch chan<- prometheus.Metric, wg *sync.WaitGroup, AFI string, SAFI string, vrfName string, neighbor string, logger log.Logger, bgpDesc map[string]*prometheus.Desc, peerLabels ...string) { defer wg.Done() var cmd string if strings.ToLower(vrfName) == "default" { cmd = fmt.Sprintf("show bgp %s %s neighbors %s advertised-routes json", AFI, SAFI, neighbor) } else { cmd = fmt.Sprintf("show bgp vrf %s %s %s neighbors %s advertised-routes json", vrfName, AFI, SAFI, neighbor) } output, err := executeBGPCommand(cmd) if err != nil { level.Error(logger).Log("msg", "get neighbor advertised prefixes failed", "afi", AFI, "safi", SAFI, "vrf", vrfName, "neighbor", neighbor, "err", err) return } var advertisedPrefixes bgpAdvertisedRoutes if err := json.Unmarshal(output, &advertisedPrefixes); err != nil { level.Error(logger).Log("msg", "get neighbor advertised prefixes failed", "afi", AFI, "safi", SAFI, "vrf", vrfName, "neighbor", neighbor, "err", err) return } newGauge(ch, bgpDesc["prefixAdvertisedCount"], advertisedPrefixes.TotalPrefixCounter, peerLabels...) } type bgpProcess struct { RouterID string AS int64 RIBCount float64 RIBMemory float64 PeerCount float64 PeerMemory float64 PeerGroupCount float64 PeerGroupMemory float64 Peers map[string]*bgpPeerSession } type bgpPeerSession struct { State string RemoteAs int64 MsgRcvd float64 MsgSent float64 PeerUptimeMsec float64 PrefixReceivedCount float64 PfxRcd float64 PfxSnt *float64 } type bgpAdvertisedRoutes struct { TotalPrefixCounter float64 `json:"totalPrefixCounter"` } // Returns: // - Map from JSON formatted BGP peer descriptions // - Plain text description of peers // - Error func getBGPPeerDesc(logger log.Logger) (map[string]map[string]string, map[string]string, error) { descJSON := make(map[string]map[string]string) descText := make(map[string]string) output, err := executeBGPCommand("show bgp neighbors json") if err != nil { return nil, nil, err } var neighbors map[string]bgpBGPNeighbor if err := json.Unmarshal(output, &neighbors); err != nil { return nil, nil, err } for neighbor, data := range neighbors { if !*bgpPeerDescsText { var peerDesc map[string]string if err := json.Unmarshal([]byte(data.NbrDesc), &peerDesc); err != nil { // Don't return an error as unmarshalling is best effort. level.Error(logger).Log("msg", "cannot unmarshall bgp description", "description", data.NbrDesc, "neighbor", neighbor, "err", err) } descJSON[neighbor] = peerDesc } descText[neighbor] = data.NbrDesc } return descJSON, descText, nil } type bgpBGPNeighbor struct { NbrDesc string `json:"nbrDesc"` } prometheus-frr-exporter-1.1.0/collector/bgp_test.go000066400000000000000000000413531416043142100224670ustar00rootroot00000000000000package collector import ( "fmt" "regexp" "strings" "testing" "github.com/prometheus/client_golang/prometheus" dto "github.com/prometheus/client_model/go" ) var ( bgpSumV4Unicast = []byte(`{ "default":{ "routerId":"192.168.0.1", "as":64512, "vrfId":0, "vrfName":"default", "tableVersion":0, "ribCount":1, "ribMemory":64, "peerCount":2, "peerMemory":39936, "peers":{ "192.168.0.2":{ "remoteAs":64513, "version":4, "msgRcvd":100, "msgSent":100, "tableVersion":0, "outq":0, "inq":0, "peerUptime":"10000", "peerUptimeMsec":10000, "prefixReceivedCount":0, "state":"Established", "idType":"ipv4" }, "192.168.0.3":{ "remoteAs":64514, "version":4, "msgRcvd":0, "msgSent":0, "tableVersion":0, "outq":0, "inq":0, "peerUptime":"never", "peerUptimeMsec":0, "pfxRcd":2, "state":"Active", "idType":"ipv4" }, "192.168.0.4":{ "remoteAs":64515, "version":4, "msgRcvd":0, "msgSent":0, "tableVersion":0, "outq":0, "inq":0, "peerUptime":"never", "peerUptimeMsec":0, "pfxRcd":2, "state":"Idle (Admin)", "idType":"ipv4" } }, "totalPeers":2, "dynamicPeers":0, "bestPath":{ "multiPathRelax":"false" } } , "red":{ "routerId":"192.168.1.1", "as":64612, "vrfId":39, "vrfName":"red", "tableVersion":0, "ribCount":0, "ribMemory":0, "peerCount":2, "peerMemory":39936, "peers":{ "192.168.1.2":{ "remoteAs":64613, "version":4, "msgRcvd":100, "msgSent":100, "tableVersion":0, "outq":0, "inq":0, "peerUptime":"10000", "peerUptimeMsec":20000, "prefixReceivedCount":2, "state":"Established", "idType":"ipv4" }, "192.168.1.3":{ "remoteAs":64614, "version":4, "msgRcvd":200, "msgSent":200, "tableVersion":0, "outq":0, "inq":0, "peerUptime":"never", "peerUptimeMsec":0, "prefixReceivedCount":0, "state":"Active", "idType":"ipv4" } }, "totalPeers":2, "dynamicPeers":0, "bestPath":{ "multiPathRelax":"false" } } } `) bgpSumV6Unicast = []byte(`{ "default":{ "routerId":"192.168.0.1", "as":64512, "vrfId":0, "vrfName":"default", "tableVersion":6, "ribCount":3, "ribMemory":456, "peerCount":2, "peerMemory":59904, "peers":{ "fd00::1":{ "remoteAs":64513, "version":4, "msgRcvd":29285, "msgSent":29285, "tableVersion":0, "outq":0, "inq":0, "peerUptime":"1d00h24m", "peerUptimeMsec":87873000, "prefixReceivedCount":1, "state":"Established", "idType":"ipv6" }, "fd00::5":{ "remoteAs":64514, "version":4, "msgRcvd":0, "msgSent":0, "tableVersion":0, "outq":0, "inq":0, "peerUptime":"never", "peerUptimeMsec":0, "prefixReceivedCount":0, "state":"Active", "idType":"ipv6" } }, "totalPeers":2, "dynamicPeers":0, "bestPath":{ "multiPathRelax":"false" } } , "red":{ "routerId":"192.168.1.1", "as":64612, "vrfId":0, "vrfName":"default", "tableVersion":6, "ribCount":3, "ribMemory":456, "peerCount":2, "peerMemory":59904, "peers":{ "fd00::101":{ "remoteAs":64613, "version":4, "msgRcvd":29285, "msgSent":29285, "tableVersion":0, "outq":0, "inq":0, "peerUptime":"1d00h24m", "peerUptimeMsec":87873000, "prefixReceivedCount":1, "state":"Established", "idType":"ipv6" }, "fd00::105":{ "remoteAs":64614, "version":4, "msgRcvd":0, "msgSent":0, "tableVersion":0, "outq":0, "inq":0, "peerUptime":"never", "peerUptimeMsec":0, "prefixReceivedCount":0, "state":"Active", "idType":"ipv6" } }, "totalPeers":2, "dynamicPeers":0, "bestPath":{ "multiPathRelax":"false" } } } `) evpnVniJson = []byte(` { "174374":{ "vni":174374, "type":"L2", "vxlanIf":"ONTEP1_174374", "numMacs":42, "numArpNd":0, "numRemoteVteps":1, "tenantVrf":"default", "remoteVteps":[ "10.0.0.13" ] }, "172192":{ "vni":172192, "type":"L2", "vxlanIf":"ONTEP1_172192", "numMacs":0, "numArpNd":23, "numRemoteVteps":"n\/a", "tenantVrf":"default", "remoteVteps":[ "10.0.0.13" ] } }`) expectedBGPMetrics = map[string]float64{ "frr_bgp_peer_groups_count_total{afi=ipv4,local_as=64512,safi=unicast,vrf=default}": 0.0, "frr_bgp_peer_groups_count_total{afi=ipv4,local_as=64612,safi=unicast,vrf=red}": 0.0, "frr_bgp_peer_groups_count_total{afi=ipv6,local_as=64512,safi=unicast,vrf=default}": 0.0, "frr_bgp_peer_groups_count_total{afi=ipv6,local_as=64612,safi=unicast,vrf=red}": 0.0, "frr_bgp_peer_groups_memory_bytes{afi=ipv4,local_as=64512,safi=unicast,vrf=default}": 0.0, "frr_bgp_peer_groups_memory_bytes{afi=ipv4,local_as=64612,safi=unicast,vrf=red}": 0.0, "frr_bgp_peer_groups_memory_bytes{afi=ipv6,local_as=64512,safi=unicast,vrf=default}": 0.0, "frr_bgp_peer_groups_memory_bytes{afi=ipv6,local_as=64612,safi=unicast,vrf=red}": 0.0, "frr_bgp_peer_message_received_total{afi=ipv4,local_as=64512,peer=192.168.0.2,peer_as=64513,safi=unicast,vrf=default}": 100.0, "frr_bgp_peer_message_received_total{afi=ipv4,local_as=64512,peer=192.168.0.3,peer_as=64514,safi=unicast,vrf=default}": 0.0, "frr_bgp_peer_message_received_total{afi=ipv4,local_as=64612,peer=192.168.1.2,peer_as=64613,safi=unicast,vrf=red}": 100.0, "frr_bgp_peer_message_received_total{afi=ipv4,local_as=64612,peer=192.168.1.3,peer_as=64614,safi=unicast,vrf=red}": 200.0, "frr_bgp_peer_message_received_total{afi=ipv6,local_as=64512,peer=fd00::1,peer_as=64513,safi=unicast,vrf=default}": 29285.0, "frr_bgp_peer_message_received_total{afi=ipv6,local_as=64512,peer=fd00::5,peer_as=64514,safi=unicast,vrf=default}": 0.0, "frr_bgp_peer_message_received_total{afi=ipv6,local_as=64612,peer=fd00::101,peer_as=64613,safi=unicast,vrf=red}": 29285.0, "frr_bgp_peer_message_received_total{afi=ipv6,local_as=64612,peer=fd00::105,peer_as=64614,safi=unicast,vrf=red}": 0.0, "frr_bgp_peer_message_sent_total{afi=ipv4,local_as=64512,peer=192.168.0.2,peer_as=64513,safi=unicast,vrf=default}": 100.0, "frr_bgp_peer_message_sent_total{afi=ipv4,local_as=64512,peer=192.168.0.3,peer_as=64514,safi=unicast,vrf=default}": 0.0, "frr_bgp_peer_message_sent_total{afi=ipv4,local_as=64612,peer=192.168.1.2,peer_as=64613,safi=unicast,vrf=red}": 100.0, "frr_bgp_peer_message_sent_total{afi=ipv4,local_as=64612,peer=192.168.1.3,peer_as=64614,safi=unicast,vrf=red}": 200.0, "frr_bgp_peer_message_sent_total{afi=ipv6,local_as=64512,peer=fd00::1,peer_as=64513,safi=unicast,vrf=default}": 29285.0, "frr_bgp_peer_message_sent_total{afi=ipv6,local_as=64512,peer=fd00::5,peer_as=64514,safi=unicast,vrf=default}": 0.0, "frr_bgp_peer_message_sent_total{afi=ipv6,local_as=64612,peer=fd00::101,peer_as=64613,safi=unicast,vrf=red}": 29285.0, "frr_bgp_peer_message_sent_total{afi=ipv6,local_as=64612,peer=fd00::105,peer_as=64614,safi=unicast,vrf=red}": 0.0, "frr_bgp_peer_prefixes_received_count_total{afi=ipv4,local_as=64512,peer=192.168.0.2,peer_as=64513,safi=unicast,vrf=default}": 0.0, "frr_bgp_peer_prefixes_received_count_total{afi=ipv4,local_as=64512,peer=192.168.0.3,peer_as=64514,safi=unicast,vrf=default}": 2.0, "frr_bgp_peer_prefixes_received_count_total{afi=ipv4,local_as=64612,peer=192.168.1.2,peer_as=64613,safi=unicast,vrf=red}": 2.0, "frr_bgp_peer_prefixes_received_count_total{afi=ipv4,local_as=64612,peer=192.168.1.3,peer_as=64614,safi=unicast,vrf=red}": 0.0, "frr_bgp_peer_prefixes_received_count_total{afi=ipv6,local_as=64512,peer=fd00::1,peer_as=64513,safi=unicast,vrf=default}": 1.0, "frr_bgp_peer_prefixes_received_count_total{afi=ipv6,local_as=64512,peer=fd00::5,peer_as=64514,safi=unicast,vrf=default}": 0.0, "frr_bgp_peer_prefixes_received_count_total{afi=ipv6,local_as=64612,peer=fd00::101,peer_as=64613,safi=unicast,vrf=red}": 1.0, "frr_bgp_peer_prefixes_received_count_total{afi=ipv6,local_as=64612,peer=fd00::105,peer_as=64614,safi=unicast,vrf=red}": 0.0, "frr_bgp_peers_count_total{afi=ipv4,local_as=64512,safi=unicast,vrf=default}": 2.0, "frr_bgp_peers_count_total{afi=ipv4,local_as=64612,safi=unicast,vrf=red}": 2.0, "frr_bgp_peers_count_total{afi=ipv6,local_as=64512,safi=unicast,vrf=default}": 2.0, "frr_bgp_peers_count_total{afi=ipv6,local_as=64612,safi=unicast,vrf=red}": 2.0, "frr_bgp_peers_memory_bytes{afi=ipv4,local_as=64512,safi=unicast,vrf=default}": 39936.0, "frr_bgp_peers_memory_bytes{afi=ipv4,local_as=64612,safi=unicast,vrf=red}": 39936.0, "frr_bgp_peers_memory_bytes{afi=ipv6,local_as=64512,safi=unicast,vrf=default}": 59904.0, "frr_bgp_peers_memory_bytes{afi=ipv6,local_as=64612,safi=unicast,vrf=red}": 59904.0, "frr_bgp_peer_state{afi=ipv4,local_as=64512,peer=192.168.0.2,peer_as=64513,safi=unicast,vrf=default}": 1.0, "frr_bgp_peer_state{afi=ipv4,local_as=64512,peer=192.168.0.3,peer_as=64514,safi=unicast,vrf=default}": 0.0, "frr_bgp_peer_state{afi=ipv4,local_as=64612,peer=192.168.1.2,peer_as=64613,safi=unicast,vrf=red}": 1.0, "frr_bgp_peer_state{afi=ipv4,local_as=64612,peer=192.168.1.3,peer_as=64614,safi=unicast,vrf=red}": 0.0, "frr_bgp_peer_state{afi=ipv6,local_as=64512,peer=fd00::1,peer_as=64513,safi=unicast,vrf=default}": 1.0, "frr_bgp_peer_state{afi=ipv6,local_as=64512,peer=fd00::5,peer_as=64514,safi=unicast,vrf=default}": 0.0, "frr_bgp_peer_state{afi=ipv6,local_as=64612,peer=fd00::101,peer_as=64613,safi=unicast,vrf=red}": 1.0, "frr_bgp_peer_state{afi=ipv6,local_as=64612,peer=fd00::105,peer_as=64614,safi=unicast,vrf=red}": 0.0, "frr_bgp_peer_uptime_seconds{afi=ipv4,local_as=64512,peer=192.168.0.2,peer_as=64513,safi=unicast,vrf=default}": 10.0, "frr_bgp_peer_uptime_seconds{afi=ipv4,local_as=64512,peer=192.168.0.3,peer_as=64514,safi=unicast,vrf=default}": 0.0, "frr_bgp_peer_uptime_seconds{afi=ipv4,local_as=64612,peer=192.168.1.2,peer_as=64613,safi=unicast,vrf=red}": 20.0, "frr_bgp_peer_uptime_seconds{afi=ipv4,local_as=64612,peer=192.168.1.3,peer_as=64614,safi=unicast,vrf=red}": 0.0, "frr_bgp_peer_uptime_seconds{afi=ipv6,local_as=64512,peer=fd00::1,peer_as=64513,safi=unicast,vrf=default}": 87873.0, "frr_bgp_peer_uptime_seconds{afi=ipv6,local_as=64512,peer=fd00::5,peer_as=64514,safi=unicast,vrf=default}": 0.0, "frr_bgp_peer_uptime_seconds{afi=ipv6,local_as=64612,peer=fd00::101,peer_as=64613,safi=unicast,vrf=red}": 87873.0, "frr_bgp_peer_uptime_seconds{afi=ipv6,local_as=64612,peer=fd00::105,peer_as=64614,safi=unicast,vrf=red}": 0.0, "frr_bgp_rib_count_total{afi=ipv4,local_as=64512,safi=unicast,vrf=default}": 1.0, "frr_bgp_rib_count_total{afi=ipv4,local_as=64612,safi=unicast,vrf=red}": 0.0, "frr_bgp_rib_count_total{afi=ipv6,local_as=64512,safi=unicast,vrf=default}": 3.0, "frr_bgp_rib_count_total{afi=ipv6,local_as=64612,safi=unicast,vrf=red}": 3.0, "frr_bgp_rib_memory_bytes{afi=ipv4,local_as=64512,safi=unicast,vrf=default}": 64.0, "frr_bgp_rib_memory_bytes{afi=ipv4,local_as=64612,safi=unicast,vrf=red}": 0.0, "frr_bgp_rib_memory_bytes{afi=ipv6,local_as=64512,safi=unicast,vrf=default}": 456.0, "frr_bgp_rib_memory_bytes{afi=ipv6,local_as=64612,safi=unicast,vrf=red}": 456.0, "frr_bgp_peer_state{afi=ipv4,local_as=64512,peer=192.168.0.4,peer_as=64515,safi=unicast,vrf=default}": 2.0, "frr_bgp_peer_message_sent_total{afi=ipv4,local_as=64512,peer=192.168.0.4,peer_as=64515,safi=unicast,vrf=default}": 0.0, "frr_bgp_peer_prefixes_received_count_total{afi=ipv4,local_as=64512,peer=192.168.0.4,peer_as=64515,safi=unicast,vrf=default}": 2.0, "frr_bgp_peer_uptime_seconds{afi=ipv4,local_as=64512,peer=192.168.0.4,peer_as=64515,safi=unicast,vrf=default}": 0.0, "frr_bgp_peer_message_received_total{afi=ipv4,local_as=64512,peer=192.168.0.4,peer_as=64515,safi=unicast,vrf=default}": 0.0, } expectedBgpL2vpnMetrics = map[string]float64{ "frr_bgp_l2vpn_evpn_arp_nd_count_total{tenantVrf=default,type=L2,vni=172192,vxlanIf=ONTEP1_172192}": 23.000000, "frr_bgp_l2vpn_evpn_arp_nd_count_total{tenantVrf=default,type=L2,vni=174374,vxlanIf=ONTEP1_174374}": 0.000000, "frr_bgp_l2vpn_evpn_mac_count_total{tenantVrf=default,type=L2,vni=172192,vxlanIf=ONTEP1_172192}": 0.000000, "frr_bgp_l2vpn_evpn_mac_count_total{tenantVrf=default,type=L2,vni=174374,vxlanIf=ONTEP1_174374}": 42.000000, "frr_bgp_l2vpn_evpn_remote_vtep_count_total{tenantVrf=default,type=L2,vni=172192,vxlanIf=ONTEP1_172192}": -1.000000, "frr_bgp_l2vpn_evpn_remote_vtep_count_total{tenantVrf=default,type=L2,vni=174374,vxlanIf=ONTEP1_174374}": 1.000000, } ) func prepareMetrics(ch chan prometheus.Metric, t *testing.T) map[string]float64 { gotMetrics := make(map[string]float64) for { msg, more := <-ch if !more { break } metric := &dto.Metric{} if err := msg.Write(metric); err != nil { t.Errorf("error writing metric: %s", err) } var labels []string for _, label := range metric.GetLabel() { labels = append(labels, fmt.Sprintf("%s=%s", label.GetName(), label.GetValue())) } var value float64 if metric.GetCounter() != nil { value = metric.GetCounter().GetValue() } else if metric.GetGauge() != nil { value = metric.GetGauge().GetValue() } re, err := regexp.Compile(`.*fqName: "(.*)", help:.*`) if err != nil { t.Errorf("could not compile regex: %s", err) } metricName := re.FindStringSubmatch(msg.Desc().String())[1] gotMetrics[fmt.Sprintf("%s{%s}", metricName, strings.Join(labels, ","))] = value } return gotMetrics } func compareMetrics(t *testing.T, gotMetrics map[string]float64, expectedMetrics map[string]float64) { for metricName, metricVal := range gotMetrics { if expectedMetricVal, ok := expectedMetrics[metricName]; ok { if expectedMetricVal != metricVal { t.Errorf("metric %s expected value %v got %v", metricName, expectedMetricVal, metricVal) } } else { t.Errorf("unexpected metric: %s : %v", metricName, metricVal) } } for expectedMetricName, expectedMetricVal := range expectedMetrics { if _, ok := gotMetrics[expectedMetricName]; !ok { t.Errorf("missing metric: %s value %v", expectedMetricName, expectedMetricVal) } } } func TestProcessBGPSummary(t *testing.T) { ch := make(chan prometheus.Metric, 1024) if err := processBGPSummary(ch, bgpSumV4Unicast, "ipv4", "unicast", nil, getBGPDesc()); err != nil { t.Errorf("error calling processBGPSummary ipv4unicast: %s", err) } if err := processBGPSummary(ch, bgpSumV6Unicast, "ipv6", "unicast", nil, getBGPDesc()); err != nil { t.Errorf("error calling processBGPSummary ipv6unicast: %s", err) } close(ch) gotMetrics := prepareMetrics(ch, t) compareMetrics(t, gotMetrics, expectedBGPMetrics) } func TestProcessBgpL2vpnEvpnSummary(t *testing.T) { ch := make(chan prometheus.Metric, 1024) if err := processBgpL2vpnEvpnSummary(ch, evpnVniJson, getBGPL2VPNDesc()); err != nil { t.Errorf("error calling processBgpL2vpnEvpnSummary: %s", err) } close(ch) gotMetrics := prepareMetrics(ch, t) compareMetrics(t, gotMetrics, expectedBgpL2vpnMetrics) } prometheus-frr-exporter-1.1.0/collector/collector.go000066400000000000000000000127311416043142100226440ustar00rootroot00000000000000package collector import ( "fmt" "strconv" "sync" "time" "github.com/go-kit/log" "github.com/go-kit/log/level" "github.com/tynany/frr_exporter/internal/frrsockets" kingpin "gopkg.in/alecthomas/kingpin.v2" "github.com/prometheus/client_golang/prometheus" ) const ( metric_namespace = "frr" enabledByDefault = true disabledByDefault = false ) var ( socketConn *frrsockets.Connection frrTotalScrapeCount = prometheus.NewCounter(prometheus.CounterOpts{ Namespace: metric_namespace, Name: "scrapes_total", Help: "Total number of times FRR has been scraped.", }) frrLabels = []string{"collector"} frrDesc = map[string]*prometheus.Desc{ "frrScrapeDuration": promDesc("scrape_duration_seconds", "Time it took for a collector's scrape to complete.", frrLabels), "frrCollectorUp": promDesc("collector_up", "Whether the collector's last scrape was successful (1 = successful, 0 = unsuccessful).", frrLabels), "frrUp": promDesc("up", "Whether FRR is currently up.", nil), } socketDirPath = kingpin.Flag("frr.socket.dir-path", "Path of of the localstatedir containing each daemon's Unix socket.").Default("/var/run/frr").String() socketTimeout = kingpin.Flag("frr.socket.timeout", "Timeout when connecting to the FRR daemon Unix sockets").Default("20s").Duration() factories = make(map[string]func(logger log.Logger) (Collector, error)) initiatedCollectorsMtx = sync.Mutex{} initiatedCollectors = make(map[string]Collector) collectorState = make(map[string]*bool) ) func registerCollector(name string, enabledByDefaultStatus bool, factory func(logger log.Logger) (Collector, error)) { defaultState := "disabled" if enabledByDefaultStatus { defaultState = "enabled" } help := fmt.Sprintf("Enable the %s collector (default: %s).", name, defaultState) if enabledByDefaultStatus { help = fmt.Sprintf("Enable the %s collector (default: %s, to disable use --no-collector.%s).", name, defaultState, name) } factories[name] = factory collectorState[name] = kingpin.Flag(fmt.Sprintf("collector.%s", name), help).Default(strconv.FormatBool(enabledByDefaultStatus)).Bool() } // Collector is the interface a collector has to implement. type Collector interface { // Update metrics and sends to the Prometheus.Metric channel. Update(ch chan<- prometheus.Metric) error } // Exporter collects all collector metrics, implemented as per the prometheus.Collector interface. type Exporter struct { Collectors map[string]Collector logger log.Logger } // NewExporter returns a new Exporter. func NewExporter(logger log.Logger) (*Exporter, error) { collectors := make(map[string]Collector) initiatedCollectorsMtx.Lock() defer initiatedCollectorsMtx.Unlock() socketConn = frrsockets.NewConnection(*socketDirPath, *socketTimeout) for name, enabled := range collectorState { if !*enabled { continue } if collector, exists := initiatedCollectors[name]; exists { collectors[name] = collector } else { collector, err := factories[name](log.With(logger, "collector", name)) if err != nil { return nil, err } collectors[name] = collector initiatedCollectors[name] = collector } } return &Exporter{ Collectors: collectors, logger: logger, }, nil } // Collect implemented as per the prometheus.Collector interface. func (e *Exporter) Collect(ch chan<- prometheus.Metric) { frrTotalScrapeCount.Inc() ch <- frrTotalScrapeCount wg := &sync.WaitGroup{} wg.Add(len(e.Collectors)) for name, collector := range e.Collectors { go runCollector(ch, name, collector, wg, e.logger) } wg.Wait() } func runCollector(ch chan<- prometheus.Metric, name string, collector Collector, wg *sync.WaitGroup, logger log.Logger) { defer wg.Done() startTime := time.Now() err := collector.Update(ch) scrapeDurationSeconds := time.Since(startTime).Seconds() ch <- prometheus.MustNewConstMetric(frrDesc["frrScrapeDuration"], prometheus.GaugeValue, float64(scrapeDurationSeconds), name) success := 0.0 if err != nil { level.Error(logger).Log("msg", "collector scrape failed", "name", name, "duration_seconds", scrapeDurationSeconds, "err", err) } else { level.Debug(logger).Log("msg", "collector succeeded", "name", name, "duration_seconds", scrapeDurationSeconds) success = 1 } ch <- prometheus.MustNewConstMetric(frrDesc["frrCollectorUp"], prometheus.GaugeValue, success, name) } // Describe implemented as per the prometheus.Collector interface. func (e *Exporter) Describe(ch chan<- *prometheus.Desc) { for _, desc := range frrDesc { ch <- desc } } func promDesc(metricName string, metricDescription string, labels []string) *prometheus.Desc { return prometheus.NewDesc(metric_namespace+"_"+metricName, metricDescription, labels, nil) } func colPromDesc(subsystem string, metricName string, metricDescription string, labels []string) *prometheus.Desc { return prometheus.NewDesc(prometheus.BuildFQName(metric_namespace, subsystem, metricName), metricDescription, labels, nil) } func newGauge(ch chan<- prometheus.Metric, descName *prometheus.Desc, metric float64, labels ...string) { ch <- prometheus.MustNewConstMetric(descName, prometheus.GaugeValue, metric, labels...) } func newCounter(ch chan<- prometheus.Metric, descName *prometheus.Desc, metric float64, labels ...string) { ch <- prometheus.MustNewConstMetric(descName, prometheus.CounterValue, metric, labels...) } func cmdOutputProcessError(cmd, output string, err error) error { return fmt.Errorf("cannot process output of %s: %w: command output: %s", cmd, err, output) } prometheus-frr-exporter-1.1.0/collector/command.go000066400000000000000000000052171416043142100222750ustar00rootroot00000000000000package collector import ( "bytes" "context" "fmt" "os/exec" "strings" kingpin "gopkg.in/alecthomas/kingpin.v2" ) var ( vtyshEnable = kingpin.Flag("frr.vtysh", "Use vtysh to query FRR instead of each daemon's Unix socket (default: disabled, recommended: disabled).").Default("false").Bool() vtyshPath = kingpin.Flag("frr.vtysh.path", "Path of vtysh.").Default("/usr/bin/vtysh").String() vtyshTimeout = kingpin.Flag("frr.vtysh.timeout", "The timeout when running vtysh commands (default: 20s).").Default("20s").Duration() vtyshSudo = kingpin.Flag("frr.vtysh.sudo", "Enable sudo when executing vtysh commands.").Bool() frrVTYSHOptions = kingpin.Flag("frr.vtysh.options", "Additional options passed to vtysh.").Default("").String() ) func executeBGPCommand(cmd string) ([]byte, error) { if *vtyshEnable { return execVtyshCommand(cmd) } return socketConn.ExecBGPCmd(cmd) } func executeOSPFMultiInstanceCommand(cmd string, instanceID int) ([]byte, error) { return socketConn.ExecOSPFMultiInstanceCmd(cmd, instanceID) } func executeOSPFCommand(cmd string) ([]byte, error) { if *vtyshEnable { return execVtyshCommand(cmd) } return socketConn.ExecOSPFCmd(cmd) } func executePIMCommand(cmd string) ([]byte, error) { if *vtyshEnable { return execVtyshCommand(cmd) } return socketConn.ExecPIMCmd(cmd) } func executeZebraCommand(cmd string) ([]byte, error) { if *vtyshEnable { return execVtyshCommand(cmd) } return socketConn.ExecZebraCmd(cmd) } func executeVRRPCommand(cmd string) ([]byte, error) { if *vtyshEnable { return execVtyshCommand(cmd) } return socketConn.ExecVRRPCmd(cmd) } func executeBFDCommand(cmd string) ([]byte, error) { // to do: work out how to interact with the bfdd.vty Unix socket: // % [BFD] Unknown command: show bfd peers json return execVtyshCommand(cmd) } func execVtyshCommand(vtyshCmd string) ([]byte, error) { ctx, cancel := context.WithTimeout(context.Background(), *vtyshTimeout) defer cancel() var a []string var executable string if *vtyshSudo { a = []string{*vtyshPath} executable = "/usr/bin/sudo" } else { a = []string{} executable = *vtyshPath } if *frrVTYSHOptions != "" { frrOptions := strings.Split(*frrVTYSHOptions, " ") a = append(a, frrOptions...) } a = append(a, "-c", vtyshCmd) cmd := exec.CommandContext(ctx, executable, a...) var stdout, stderr bytes.Buffer cmd.Stdout = &stdout cmd.Stderr = &stderr err := cmd.Run() if err != nil { return stdout.Bytes(), fmt.Errorf("command %s failed: %w: stderr: %s: stdout: %s", cmd, err, strings.Replace(stderr.String(), "\n", " ", -1), strings.Replace(stdout.String(), "\n", " ", -1)) } return stdout.Bytes(), nil } prometheus-frr-exporter-1.1.0/collector/ospf.go000066400000000000000000000133131416043142100216220ustar00rootroot00000000000000package collector import ( "encoding/json" "fmt" "strconv" "strings" "github.com/go-kit/log" "github.com/prometheus/client_golang/prometheus" kingpin "gopkg.in/alecthomas/kingpin.v2" ) var ( ospfSubsystem = "ospf" frrOSPFInstances = kingpin.Flag("collector.ospf.instances", "Comma-separated list of instance IDs if using multiple OSPF instances").Default("").String() ) func init() { registerCollector(ospfSubsystem, enabledByDefault, NewOSPFCollector) } type ospfCollector struct { logger log.Logger descriptions map[string]*prometheus.Desc instanceIDs []int } // NewOSPFCollector collects OSPF metrics, implemented as per the Collector interface. func NewOSPFCollector(logger log.Logger) (Collector, error) { var instanceIDs []int if len(*frrOSPFInstances) > 0 { // FRR Exporter does not support multi-instance when using `vtysh` to interface with FRR // via the `--frr.vtysh` flag for the following reasons: // * Invalid JSON is returned when OSPF commands are executed by `vtysh`. For example, // `show ip ospf vrf all interface json` returns the concatenated JSON from each OSPF instance. // * Vtysh does not support `vrf` and `instance` in the same commend. For example, // `show ip ospf 1 vrf all interface json` is an invalid command. if *vtyshEnable { return nil, fmt.Errorf("cannot use --frr.vtysh with --collector.ospf.instances") } instances := strings.Split(*frrOSPFInstances, ",") for _, id := range instances { i, err := strconv.Atoi(id) if err != nil { return nil, fmt.Errorf("unable to parse instance ID %s: %w", id, err) } instanceIDs = append(instanceIDs, i) } } return &ospfCollector{logger: logger, instanceIDs: instanceIDs, descriptions: getOSPFDesc()}, nil } func getOSPFDesc() map[string]*prometheus.Desc { labels := []string{"vrf", "iface", "area"} if len(*frrOSPFInstances) > 0 { labels = append(labels, "instance") } return map[string]*prometheus.Desc{ "ospfIfaceNeigh": colPromDesc(ospfSubsystem, "neighbors", "Number of neighbors detected.", labels), "ospfIfaceNeighAdj": colPromDesc(ospfSubsystem, "neighbor_adjacencies", "Number of neighbor adjacencies formed.", labels), } } // Update implemented as per the Collector interface. func (c *ospfCollector) Update(ch chan<- prometheus.Metric) error { cmd := "show ip ospf vrf all interface json" if len(c.instanceIDs) > 0 { for _, id := range c.instanceIDs { jsonOSPFInterface, err := executeOSPFMultiInstanceCommand(cmd, id) if err != nil { return err } if err = processOSPFInterface(ch, jsonOSPFInterface, c.descriptions, id); err != nil { return cmdOutputProcessError(cmd, string(jsonOSPFInterface), err) } } return nil } jsonOSPFInterface, err := executeOSPFCommand(cmd) if err != nil { return err } if err = processOSPFInterface(ch, jsonOSPFInterface, c.descriptions, 0); err != nil { return cmdOutputProcessError(cmd, string(jsonOSPFInterface), err) } return nil } func processOSPFInterface(ch chan<- prometheus.Metric, jsonOSPFInterface []byte, ospfDesc map[string]*prometheus.Desc, instanceID int) error { // Unfortunately, the 'show ip ospf vrf all interface json' JSON output is poorly structured. Instead // of all interfaces being in a list, each interface is added as a key on the same level of vrfName and // vrfId. As such, we have to loop through each key and apply logic to determine whether the key is an // interface. var jsonMap map[string]json.RawMessage if err := json.Unmarshal(jsonOSPFInterface, &jsonMap); err != nil { return fmt.Errorf("cannot unmarshal ospf interface json: %s", err) } for vrfName, vrfData := range jsonMap { var _tempvrfInstance map[string]json.RawMessage switch vrfName { case "ospfInstance": // Do nothing default: if err := json.Unmarshal(vrfData, &_tempvrfInstance); err != nil { return fmt.Errorf("cannot unmarshal VRF instance json: %s", err) } } for ospfInstanceKey, ospfInstanceVal := range _tempvrfInstance { switch ospfInstanceKey { case "vrfName", "vrfId": // Do nothing as we do not need the value of these keys. case "interfaces": var _tempInterfaceInstance map[string]json.RawMessage if err := json.Unmarshal(ospfInstanceVal, &_tempInterfaceInstance); err != nil { return fmt.Errorf("cannot unmarshal VRF instance json: %s", err) } for interfaceKey, interfaceValue := range _tempInterfaceInstance { var newIface ospfIface if err := json.Unmarshal(interfaceValue, &newIface); err != nil { return fmt.Errorf("cannot unmarshal interface json: %s", err) } if !newIface.TimerPassiveIface { // The labels are "vrf", "newIface", "area" labels := []string{strings.ToLower(vrfName), interfaceKey, newIface.Area} ospfMetrics(ch, newIface, labels, ospfDesc, instanceID) } } default: // All other keys are interfaces. var iface ospfIface if err := json.Unmarshal(ospfInstanceVal, &iface); err != nil { return fmt.Errorf("cannot unmarshal interface json: %s", err) } if !iface.TimerPassiveIface { // The labels are "vrf", "iface", "area" labels := []string{strings.ToLower(vrfName), ospfInstanceKey, iface.Area} ospfMetrics(ch, iface, labels, ospfDesc, instanceID) } } } } return nil } func ospfMetrics(ch chan<- prometheus.Metric, iface ospfIface, labels []string, ospfDesc map[string]*prometheus.Desc, instanceID int) { if instanceID != 0 { labels = append(labels, strconv.Itoa(instanceID)) } newGauge(ch, ospfDesc["ospfIfaceNeigh"], iface.NbrCount, labels...) newGauge(ch, ospfDesc["ospfIfaceNeighAdj"], iface.NbrAdjacentCount, labels...) } type ospfIface struct { NbrCount float64 NbrAdjacentCount float64 Area string TimerPassiveIface bool } prometheus-frr-exporter-1.1.0/collector/ospf_test.go000066400000000000000000000201071416043142100226600ustar00rootroot00000000000000package collector import ( "fmt" "regexp" "strings" "testing" "github.com/prometheus/client_golang/prometheus" dto "github.com/prometheus/client_model/go" ) var ( ospfInterfaceSum = []byte(`{ "default":{ "vrfName":"default", "vrfId":0, "swp1":{ "ifUp":true, "ifIndex":4, "mtuBytes":1500, "bandwidthMbit":4294967295, "ifFlags":"", "ospfEnabled":true, "ipAddress":"192.168.0.1", "ipAddressPrefixlen":24, "area":"0.0.0.0", "routerId":"192.168.255.1", "networkType":"BROADCAST", "cost":1, "transmitDelayMsecs":1000, "state":"DR", "priority":1, "mcastMemberOspfAllRouters":true, "mcastMemberOspfDesignatedRouters":true, "timerMsecs":100, "timerDeadMsecs":25, "timerWaitMsecs":25, "timerRetransmit":200, "timerHelloInMsecs":7769, "nbrCount":0, "nbrAdjacentCount":0 }, "swp2":{ "ifUp":true, "ifIndex":6, "mtuBytes":1500, "bandwidthMbit":4294967295, "ifFlags":"", "ospfEnabled":true, "ipAddress":"192.168.2.1", "ipAddressPrefixlen":24, "area":"0.0.0.0", "routerId":"192.168.255.1", "networkType":"BROADCAST", "cost":1, "transmitDelayMsecs":1000, "state":"DR", "priority":1, "bdrId":"1.1.1.1", "bdrAddress":"192.168.1.2", "networkLsaSequence":2147483717, "mcastMemberOspfAllRouters":true, "mcastMemberOspfDesignatedRouters":true, "timerMsecs":100, "timerDeadMsecs":25, "timerWaitMsecs":25, "timerRetransmit":200, "timerHelloInMsecs":7769, "nbrCount":1, "nbrAdjacentCount":1 } }, "red":{ "vrfName":"red", "vrfId":0, "swp3":{ "ifUp":true, "ifIndex":4, "mtuBytes":1500, "bandwidthMbit":4294967295, "ifFlags":"", "ospfEnabled":true, "ipAddress":"192.168.10.1", "ipAddressPrefixlen":24, "area":"0.0.0.0", "routerId":"192.168.255.1", "networkType":"BROADCAST", "cost":1, "transmitDelayMsecs":1000, "state":"DR", "priority":1, "mcastMemberOspfAllRouters":true, "mcastMemberOspfDesignatedRouters":true, "timerMsecs":100, "timerDeadMsecs":25, "timerWaitMsecs":25, "timerRetransmit":200, "timerHelloInMsecs":7769, "nbrCount":0, "nbrAdjacentCount":0 }, "swp4":{ "ifUp":true, "ifIndex":6, "mtuBytes":1500, "bandwidthMbit":4294967295, "ifFlags":"", "ospfEnabled":true, "ipAddress":"192.168.12.1", "ipAddressPrefixlen":24, "area":"0.0.0.0", "routerId":"192.168.255.1", "networkType":"BROADCAST", "cost":1, "transmitDelayMsecs":1000, "state":"DR", "priority":1, "bdrId":"1.1.1.1", "bdrAddress":"192.168.1.2", "networkLsaSequence":2147483717, "mcastMemberOspfAllRouters":true, "mcastMemberOspfDesignatedRouters":true, "timerMsecs":100, "timerDeadMsecs":25, "timerWaitMsecs":25, "timerRetransmit":200, "timerHelloInMsecs":7769, "nbrCount":1, "nbrAdjacentCount":1 }, "peerlink.4094":{ "ifUp":true, "ifIndex":62, "mtuBytes":9000, "bandwidthMbit":2000, "ifFlags":"", "ospfEnabled":true, "ipAddress":"169.254.1.1", "ipAddressPrefixlen":30, "ospfIfType":"Broadcast", "localIfUsed":"169.254.1.3", "area":"0.0.0.75 [Stub]", "routerId":"10.200.1.222", "networkType":"BROADCAST", "cost":50, "transmitDelaySecs":1, "state":"DR", "priority":1, "timerMsecs":10000, "timerDeadSecs":40, "timerWaitSecs":40, "timerRetransmitSecs":5, "timerPassiveIface":true, "nbrCount":0, "nbrAdjacentCount":0 } } } `) expectedOSPFMetrics = map[string]float64{ "frr_ospf_neighbors{area=0.0.0.0,iface=swp1,vrf=default}": 0, "frr_ospf_neighbors{area=0.0.0.0,iface=swp2,vrf=default}": 1, "frr_ospf_neighbors{area=0.0.0.0,iface=swp3,vrf=red}": 0, "frr_ospf_neighbors{area=0.0.0.0,iface=swp4,vrf=red}": 1, "frr_ospf_neighbor_adjacencies{area=0.0.0.0,iface=swp1,vrf=default}": 0, "frr_ospf_neighbor_adjacencies{area=0.0.0.0,iface=swp2,vrf=default}": 1, "frr_ospf_neighbor_adjacencies{area=0.0.0.0,iface=swp3,vrf=red}": 0, "frr_ospf_neighbor_adjacencies{area=0.0.0.0,iface=swp4,vrf=red}": 1, "frr_ospf_neighbors{area=0.0.0.0,iface=swp1,instance=1,vrf=default}": 0, "frr_ospf_neighbors{area=0.0.0.0,iface=swp2,instance=1,vrf=default}": 1, "frr_ospf_neighbors{area=0.0.0.0,iface=swp3,instance=1,vrf=red}": 0, "frr_ospf_neighbors{area=0.0.0.0,iface=swp4,instance=1,vrf=red}": 1, "frr_ospf_neighbors{area=0.0.0.0,iface=swp1,instance=2,vrf=default}": 0, "frr_ospf_neighbors{area=0.0.0.0,iface=swp2,instance=2,vrf=default}": 1, "frr_ospf_neighbors{area=0.0.0.0,iface=swp3,instance=2,vrf=red}": 0, "frr_ospf_neighbors{area=0.0.0.0,iface=swp4,instance=2,vrf=red}": 1, "frr_ospf_neighbor_adjacencies{area=0.0.0.0,iface=swp1,instance=1,vrf=default}": 0, "frr_ospf_neighbor_adjacencies{area=0.0.0.0,iface=swp2,instance=1,vrf=default}": 1, "frr_ospf_neighbor_adjacencies{area=0.0.0.0,iface=swp3,instance=1,vrf=red}": 0, "frr_ospf_neighbor_adjacencies{area=0.0.0.0,iface=swp4,instance=1,vrf=red}": 1, "frr_ospf_neighbor_adjacencies{area=0.0.0.0,iface=swp1,instance=2,vrf=default}": 0, "frr_ospf_neighbor_adjacencies{area=0.0.0.0,iface=swp2,instance=2,vrf=default}": 1, "frr_ospf_neighbor_adjacencies{area=0.0.0.0,iface=swp3,instance=2,vrf=red}": 0, "frr_ospf_neighbor_adjacencies{area=0.0.0.0,iface=swp4,instance=2,vrf=red}": 1, } ) func TestProcessOSPFInterface(t *testing.T) { ch := make(chan prometheus.Metric, len(expectedOSPFMetrics)) if err := processOSPFInterface(ch, ospfInterfaceSum, getOSPFDesc(), 0); err != nil { t.Errorf("error calling processOSPFInterface ipv4unicast: %s", err) } // test for OSPF multiple instances *frrOSPFInstances = "1,2" for i := 1; i <= 2; i++ { if err := processOSPFInterface(ch, ospfInterfaceSum, getOSPFDesc(), i); err != nil { t.Errorf("error calling processOSPFInterface ipv4unicast: %s", err) } } close(ch) // Create a map of following format: // key: metric_name{labelname:labelvalue,...} // value: metric value gotMetrics := make(map[string]float64) for { msg, more := <-ch if !more { break } metric := &dto.Metric{} if err := msg.Write(metric); err != nil { t.Errorf("error writing metric: %s", err) } var labels []string for _, label := range metric.GetLabel() { labels = append(labels, fmt.Sprintf("%s=%s", label.GetName(), label.GetValue())) } var value float64 if metric.GetCounter() != nil { value = metric.GetCounter().GetValue() } else if metric.GetGauge() != nil { value = metric.GetGauge().GetValue() } re, err := regexp.Compile(`.*fqName: "(.*)", help:.*`) if err != nil { t.Errorf("could not compile regex: %s", err) } metricName := re.FindStringSubmatch(msg.Desc().String())[1] gotMetrics[fmt.Sprintf("%s{%s}", metricName, strings.Join(labels, ","))] = value } for metricName, metricVal := range gotMetrics { if expectedMetricVal, ok := expectedOSPFMetrics[metricName]; ok { if expectedMetricVal != metricVal { t.Errorf("metric %s expected value %v got %v", metricName, expectedMetricVal, metricVal) } } else { t.Errorf("unexpected metric: %s : %v", metricName, metricVal) } } for expectedMetricName, expectedMetricVal := range expectedOSPFMetrics { if _, ok := gotMetrics[expectedMetricName]; !ok { t.Errorf("missing metric: %s value %v", expectedMetricName, expectedMetricVal) } } } prometheus-frr-exporter-1.1.0/collector/pim.go000066400000000000000000000060401416043142100214370ustar00rootroot00000000000000package collector import ( "encoding/json" "fmt" "strings" "github.com/go-kit/log" "github.com/go-kit/log/level" "github.com/prometheus/client_golang/prometheus" ) var ( pimSubsystem = "pim" ) func init() { registerCollector(pimSubsystem, disabledByDefault, NewPIMCollector) } type pimCollector struct { logger log.Logger descriptions map[string]*prometheus.Desc } // NewPIMCollector collects PIM metrics, implemented as per the Collector interface. func NewPIMCollector(logger log.Logger) (Collector, error) { return &pimCollector{logger: logger, descriptions: getPIMDesc()}, nil } func getPIMDesc() map[string]*prometheus.Desc { labels := []string{"vrf"} neighborLabels := append(labels, "iface", "neighbor") return map[string]*prometheus.Desc{ "neighborCount": colPromDesc(pimSubsystem, "neighbor_count_total", "Number of neighbors detected", labels), "upTime": colPromDesc(pimSubsystem, "neighbor_uptime_seconds", "How long has the peer been up.", neighborLabels), } } // Collect implemented as per the Collector interface func (c *pimCollector) Update(ch chan<- prometheus.Metric) error { cmd := "show ip pim vrf all neighbor json" jsonPIMNeighbors, err := executePIMCommand(cmd) if err != nil { return err } if err := processPIMNeighbors(ch, jsonPIMNeighbors, c.logger, c.descriptions); err != nil { return cmdOutputProcessError(cmd, string(jsonPIMNeighbors), err) } return nil } func processPIMNeighbors(ch chan<- prometheus.Metric, jsonPIMNeighbors []byte, logger log.Logger, pimDesc map[string]*prometheus.Desc) error { var jsonMap map[string]json.RawMessage if err := json.Unmarshal(jsonPIMNeighbors, &jsonMap); err != nil { return fmt.Errorf("cannot unmarshal pim neighbors json: %s", err) } for vrfName, vrfData := range jsonMap { neighborCount := 0.0 var _tempvrfInstance map[string]json.RawMessage if err := json.Unmarshal(vrfData, &_tempvrfInstance); err != nil { return fmt.Errorf("cannot unmarshal VRF instance json: %s", err) } for ifaceName, ifaceData := range _tempvrfInstance { var neighbors map[string]pimNeighbor if err := json.Unmarshal(ifaceData, &neighbors); err != nil { return fmt.Errorf("cannot unmarshal neighbor json: %s", err) } for neighborIp, neighborData := range neighbors { neighborCount++ if uptimeSec, err := parseHMS(neighborData.UpTime); err != nil { level.Error(logger).Log("msg", "cannot parse neighbor uptime", "uptime", neighborData.UpTime, "err", err) } else { // The labels are "vrf", "iface", "neighbor" neighborLabels := []string{strings.ToLower(vrfName), strings.ToLower(ifaceName), neighborIp} newGauge(ch, pimDesc["upTime"], float64(uptimeSec), neighborLabels...) } } } newGauge(ch, pimDesc["neighborCount"], neighborCount, vrfName) } return nil } func parseHMS(st string) (int, error) { var h, m, s int n, err := fmt.Sscanf(st, "%d:%d:%d", &h, &m, &s) if err != nil || n != 3 { return 0, err } return h*3600 + m*60 + s, nil } type pimNeighbor struct { Interface string Neighbor string UpTime string } prometheus-frr-exporter-1.1.0/collector/pim_test.go000066400000000000000000000065661416043142100225130ustar00rootroot00000000000000package collector import ( "fmt" "regexp" "strings" "testing" "github.com/prometheus/client_golang/prometheus" dto "github.com/prometheus/client_model/go" ) var ( pimNeighborOutput = []byte(`{ "red": { "red":{ }, "eth2":{ "192.0.2.227":{ "interface":"eth2", "neighbor":"192.0.2.227", "upTime":"03:45:43", "holdTime":"00:01:43", "holdTimeMax":105, "drPriority":1 } } } , "blue": { "blue":{ }, "eth1":{ "192.0.2.45":{ "interface":"eth1", "neighbor":"192.0.2.45", "upTime":"03:45:45", "holdTime":"00:01:34", "holdTimeMax":105, "drPriority":1 } } } , "default": { "eth0":{ "192.0.2.99":{ "interface":"eth1", "neighbor":"192.0.2.99", "upTime":"00:45:45", "holdTime":"00:02:34", "holdTimeMax":105, "drPriority":1 } } } }`) expectedPIMMetrics = map[string]float64{ "frr_pim_neighbor_uptime_seconds{iface=eth2,neighbor=192.0.2.227,vrf=red}": 13543, "frr_pim_neighbor_uptime_seconds{iface=eth1,neighbor=192.0.2.45,vrf=blue}": 13545, "frr_pim_neighbor_uptime_seconds{iface=eth0,neighbor=192.0.2.99,vrf=default}": 2745, "frr_pim_neighbor_count_total{vrf=red}": 1, "frr_pim_neighbor_count_total{vrf=blue}": 1, "frr_pim_neighbor_count_total{vrf=default}": 1, } parseHMStests = []struct { in string out int }{ {"03:45:43", 13543}, {"00:04:01", 241}, {"10:00:43", 36043}, } ) func TestProcessPIMNeighbors(t *testing.T) { ch := make(chan prometheus.Metric, 1024) if err := processPIMNeighbors(ch, pimNeighborOutput, nil, getPIMDesc()); err != nil { t.Errorf("error calling processPIMNeighbors: %s", err) } close(ch) gotMetrics := make(map[string]float64) for { msg, more := <-ch if !more { break } metric := &dto.Metric{} if err := msg.Write(metric); err != nil { t.Errorf("error writing metric: %s", err) } var labels []string for _, label := range metric.GetLabel() { labels = append(labels, fmt.Sprintf("%s=%s", label.GetName(), label.GetValue())) } var value float64 if metric.GetCounter() != nil { value = metric.GetCounter().GetValue() } else if metric.GetGauge() != nil { value = metric.GetGauge().GetValue() } re, err := regexp.Compile(`.*fqName: "(.*)", help:.*`) if err != nil { t.Errorf("could not compile regex: %s", err) } metricName := re.FindStringSubmatch(msg.Desc().String())[1] gotMetrics[fmt.Sprintf("%s{%s}", metricName, strings.Join(labels, ","))] = value } for metricName, metricVal := range gotMetrics { if expectedMetricVal, ok := expectedPIMMetrics[metricName]; ok { if expectedMetricVal != metricVal { t.Errorf("metric %s expected value %v got %v", metricName, expectedMetricVal, metricVal) } } else { t.Errorf("unexpected metric: %s : %v", metricName, metricVal) } } for expectedMetricName, expectedMetricVal := range expectedPIMMetrics { if _, ok := gotMetrics[expectedMetricName]; !ok { t.Errorf("missing metric: %s value %v", expectedMetricName, expectedMetricVal) } } } func TestParseHMS(t *testing.T) { for _, tt := range parseHMStests { t.Run(tt.in, func(t *testing.T) { if uptimeSec, err := parseHMS(tt.in); err != nil || uptimeSec != tt.out { t.Errorf("ParseHMS => %s, got %d, wanted %d (err %s)", tt.in, uptimeSec, tt.out, err) } }) } } prometheus-frr-exporter-1.1.0/collector/vrrp.go000066400000000000000000000101051416043142100216400ustar00rootroot00000000000000package collector import ( "encoding/json" "strconv" "strings" "github.com/go-kit/log" "github.com/prometheus/client_golang/prometheus" ) const ( vrrpStatusInitialize = "Initialize" vrrpStatusBackup = "Backup" vrrpStatusMaster = "Master" ) var ( vrrpSubsystem = "vrrp" vrrpStates = []string{vrrpStatusInitialize, vrrpStatusMaster, vrrpStatusBackup} ) func init() { registerCollector(vrrpSubsystem, disabledByDefault, NewVRRPCollector) } type VrrpVrInfo struct { Vrid int Interface string V6Info VrrpInstanceInfo `json:"v6"` V4Info VrrpInstanceInfo `json:"v4"` } type VrrpInstanceInfo struct { Subinterface string `json:"interface"` Status string Statistics VrrpInstanceStats `json:"stats"` } type VrrpInstanceStats struct { AdverTx *int AdverRx *int GarpTx *int NeighborAdverTx *int Transitions *int } type vrrpCollector struct { logger log.Logger descriptions map[string]*prometheus.Desc } // NewVRRPCollector collects VRRP metrics, implemented as per the Collector interface. func NewVRRPCollector(logger log.Logger) (Collector, error) { return &vrrpCollector{logger: logger, descriptions: getVRRPDesc()}, nil } func getVRRPDesc() map[string]*prometheus.Desc { labels := []string{"proto", "vrid", "interface", "subinterface"} stateLabels := append(labels, "state") return map[string]*prometheus.Desc{ "vrrpState": colPromDesc(vrrpSubsystem, "state", "Status of the VRRP state machine.", stateLabels), "adverTx": colPromDesc(vrrpSubsystem, "advertisements_sent_total", "Advertisements sent total.", labels), "adverRx": colPromDesc(vrrpSubsystem, "advertisements_received_total", "Advertisements received total.", labels), "garpTx": colPromDesc(vrrpSubsystem, "gratuitous_arp_sent_total", "Gratuitous ARP sent total.", labels), "neighborAdverTx": colPromDesc(vrrpSubsystem, "neighbor_advertisements_sent_total", "Neighbor Advertisements sent total.", labels), "transitions": colPromDesc(vrrpSubsystem, "state_transitions_total", "Number of transitions of the VRRP state machine in total.", labels), } } // Update implemented as per the Collector interface. func (c *vrrpCollector) Update(ch chan<- prometheus.Metric) error { cmd := "show vrrp json" jsonVRRPInfo, err := executeVRRPCommand(cmd) if err != nil { return err } if err := processVRRPInfo(ch, jsonVRRPInfo, c.descriptions); err != nil { return cmdOutputProcessError(cmd, string(jsonVRRPInfo), err) } return nil } func processVRRPInfo(ch chan<- prometheus.Metric, jsonVRRPInfo []byte, desc map[string]*prometheus.Desc) error { var jsonList []VrrpVrInfo if err := json.Unmarshal(jsonVRRPInfo, &jsonList); err != nil { return err } for _, vrInfo := range jsonList { processInstance(ch, "v4", vrInfo.Vrid, vrInfo.Interface, vrInfo.V4Info, desc) processInstance(ch, "v6", vrInfo.Vrid, vrInfo.Interface, vrInfo.V6Info, desc) } return nil } func processInstance(ch chan<- prometheus.Metric, proto string, vrid int, iface string, instance VrrpInstanceInfo, vrrpDesc map[string]*prometheus.Desc) { vrrpLabels := []string{proto, strconv.Itoa(vrid), iface, instance.Subinterface} for _, state := range vrrpStates { stateLabels := append(vrrpLabels, state) var value float64 if strings.EqualFold(instance.Status, state) { value = 1 } newGauge(ch, vrrpDesc["vrrpState"], value, stateLabels...) } if instance.Statistics.AdverTx != nil { newCounter(ch, vrrpDesc["adverTx"], float64(*instance.Statistics.AdverTx), vrrpLabels...) } if instance.Statistics.AdverRx != nil { newCounter(ch, vrrpDesc["adverRx"], float64(*instance.Statistics.AdverRx), vrrpLabels...) } if instance.Statistics.GarpTx != nil { newCounter(ch, vrrpDesc["garpTx"], float64(*instance.Statistics.GarpTx), vrrpLabels...) } if instance.Statistics.NeighborAdverTx != nil { newCounter(ch, vrrpDesc["neighborAdverTx"], float64(*instance.Statistics.NeighborAdverTx), vrrpLabels...) } if instance.Statistics.Transitions != nil { newCounter(ch, vrrpDesc["transitions"], float64(*instance.Statistics.Transitions), vrrpLabels...) } } prometheus-frr-exporter-1.1.0/collector/vrrp_test.go000066400000000000000000000164371416043142100227150ustar00rootroot00000000000000package collector import ( "fmt" "regexp" "strings" "testing" "github.com/prometheus/client_golang/prometheus" dto "github.com/prometheus/client_model/go" ) var ( vrrpJson = []byte(`[ { "vrid":1, "version":3, "autoconfigured":false, "shutdown":false, "preemptMode":true, "acceptMode":true, "interface":"gw_extnet", "advertisementInterval":1000, "v4":{ "interface":"extnet_v4_1", "vmac":"00:00:5e:00:01:01", "primaryAddress":"", "status":"Backup", "effectivePriority":100, "masterAdverInterval":1000, "skewTime":600, "masterDownInterval":3600, "stats":{ "adverTx":6, "adverRx":1548196, "garpTx":4, "transitions":9 }, "addresses":[ "192.0.2.1" ] }, "v6":{ "interface":"extnet_v6_1", "vmac":"00:00:5e:00:02:01", "primaryAddress":"::", "status":"Backup", "effectivePriority":100, "masterAdverInterval":1000, "skewTime":600, "masterDownInterval":3600, "stats":{ "adverTx":2, "adverRx":1548195, "neighborAdverTx":5, "transitions":11 }, "addresses":[ "2001:DB8:2c02::1" ] } }, { "vrid":2, "version":3, "autoconfigured":false, "shutdown":false, "preemptMode":true, "acceptMode":true, "interface":"gw_extnet", "advertisementInterval":1000, "v4":{ "interface":"extnet_v4_2", "vmac":"00:00:5e:00:01:02", "primaryAddress":"192.0.2.3", "status":"Master", "effectivePriority":200, "masterAdverInterval":1000, "skewTime":210, "masterDownInterval":3210, "stats":{ "adverTx":1548210, "adverRx":4, "garpTx":1, "transitions":2 }, "addresses":[ "192.0.2.1" ] }, "v6":{ "interface":"", "vmac":"00:00:5e:00:02:02", "primaryAddress":"::", "status":"Initialize", "effectivePriority":200, "masterAdverInterval":0, "skewTime":0, "masterDownInterval":0, "stats":{ "adverTx":0, "adverRx":0, "neighborAdverTx":0, "transitions":0 }, "addresses":[ ] } } ] `) expectedVRRPMetrics = map[string]float64{ "frr_vrrp_advertisements_received_total{interface=gw_extnet,proto=v4,subinterface=extnet_v4_1,vrid=1}": 1548196, "frr_vrrp_advertisements_received_total{interface=gw_extnet,proto=v4,subinterface=extnet_v4_2,vrid=2}": 4.0, "frr_vrrp_advertisements_received_total{interface=gw_extnet,proto=v6,subinterface=,vrid=2}": 0.0, "frr_vrrp_advertisements_received_total{interface=gw_extnet,proto=v6,subinterface=extnet_v6_1,vrid=1}": 1548195, "frr_vrrp_advertisements_sent_total{interface=gw_extnet,proto=v4,subinterface=extnet_v4_1,vrid=1}": 6, "frr_vrrp_advertisements_sent_total{interface=gw_extnet,proto=v4,subinterface=extnet_v4_2,vrid=2}": 1548210, "frr_vrrp_advertisements_sent_total{interface=gw_extnet,proto=v6,subinterface=,vrid=2}": 0, "frr_vrrp_advertisements_sent_total{interface=gw_extnet,proto=v6,subinterface=extnet_v6_1,vrid=1}": 2, "frr_vrrp_gratuitous_arp_sent_total{interface=gw_extnet,proto=v4,subinterface=extnet_v4_1,vrid=1}": 4, "frr_vrrp_gratuitous_arp_sent_total{interface=gw_extnet,proto=v4,subinterface=extnet_v4_2,vrid=2}": 1, "frr_vrrp_neighbor_advertisements_sent_total{interface=gw_extnet,proto=v6,subinterface=,vrid=2}": 0, "frr_vrrp_neighbor_advertisements_sent_total{interface=gw_extnet,proto=v6,subinterface=extnet_v6_1,vrid=1}": 5, "frr_vrrp_state_transitions_total{interface=gw_extnet,proto=v4,subinterface=extnet_v4_1,vrid=1}": 9, "frr_vrrp_state_transitions_total{interface=gw_extnet,proto=v4,subinterface=extnet_v4_2,vrid=2}": 2, "frr_vrrp_state_transitions_total{interface=gw_extnet,proto=v6,subinterface=,vrid=2}": 0, "frr_vrrp_state_transitions_total{interface=gw_extnet,proto=v6,subinterface=extnet_v6_1,vrid=1}": 11, "frr_vrrp_state{interface=gw_extnet,proto=v4,state=Backup,subinterface=extnet_v4_1,vrid=1}": 1, "frr_vrrp_state{interface=gw_extnet,proto=v4,state=Backup,subinterface=extnet_v4_2,vrid=2}": 0, "frr_vrrp_state{interface=gw_extnet,proto=v4,state=Initialize,subinterface=extnet_v4_1,vrid=1}": 0, "frr_vrrp_state{interface=gw_extnet,proto=v4,state=Initialize,subinterface=extnet_v4_2,vrid=2}": 0, "frr_vrrp_state{interface=gw_extnet,proto=v4,state=Master,subinterface=extnet_v4_1,vrid=1}": 0, "frr_vrrp_state{interface=gw_extnet,proto=v4,state=Master,subinterface=extnet_v4_2,vrid=2}": 1, "frr_vrrp_state{interface=gw_extnet,proto=v6,state=Backup,subinterface=,vrid=2}": 0, "frr_vrrp_state{interface=gw_extnet,proto=v6,state=Backup,subinterface=extnet_v6_1,vrid=1}": 1, "frr_vrrp_state{interface=gw_extnet,proto=v6,state=Initialize,subinterface=,vrid=2}": 1, "frr_vrrp_state{interface=gw_extnet,proto=v6,state=Initialize,subinterface=extnet_v6_1,vrid=1}": 0, "frr_vrrp_state{interface=gw_extnet,proto=v6,state=Master,subinterface=,vrid=2}": 0, "frr_vrrp_state{interface=gw_extnet,proto=v6,state=Master,subinterface=extnet_v6_1,vrid=1}": 0, } ) func TestProcessVRRPInfo(t *testing.T) { ch := make(chan prometheus.Metric, 1024) if err := processVRRPInfo(ch, vrrpJson, getVRRPDesc()); err != nil { t.Errorf("error calling processVRRPInfo: %s", err) } close(ch) // Create a map of following format: // key: metric_name{labelname:labelvalue,...} // value: metric value gotMetrics := make(map[string]float64) for { msg, more := <-ch if !more { break } metric := &dto.Metric{} if err := msg.Write(metric); err != nil { t.Errorf("error writing metric: %s", err) } var labels []string for _, label := range metric.GetLabel() { labels = append(labels, fmt.Sprintf("%s=%s", label.GetName(), label.GetValue())) } var value float64 if metric.GetCounter() != nil { value = metric.GetCounter().GetValue() } else if metric.GetGauge() != nil { value = metric.GetGauge().GetValue() } re, err := regexp.Compile(`.*fqName: "(.*)", help:.*`) if err != nil { t.Errorf("could not compile regex: %s", err) } metricName := re.FindStringSubmatch(msg.Desc().String())[1] gotMetrics[fmt.Sprintf("%s{%s}", metricName, strings.Join(labels, ","))] = value } for metricName, metricVal := range gotMetrics { if expectedMetricVal, ok := expectedVRRPMetrics[metricName]; ok { if expectedMetricVal != metricVal { t.Errorf("metric %s expected value %v got %v", metricName, expectedMetricVal, metricVal) } } else { t.Errorf("unexpected metric: %s : %v", metricName, metricVal) } } for expectedMetricName, expectedMetricVal := range expectedVRRPMetrics { if _, ok := gotMetrics[expectedMetricName]; !ok { t.Errorf("missing metric: %s value %v", expectedMetricName, expectedMetricVal) } } } prometheus-frr-exporter-1.1.0/frr_exporter.go000066400000000000000000000050011416043142100214010ustar00rootroot00000000000000package main import ( "fmt" inbuiltLog "log" "net/http" "os" "github.com/go-kit/log/level" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/exporter-toolkit/web" "github.com/go-kit/log" "github.com/prometheus/common/promlog" "github.com/prometheus/common/promlog/flag" "github.com/prometheus/common/version" "github.com/tynany/frr_exporter/collector" kingpin "gopkg.in/alecthomas/kingpin.v2" ) var ( listenAddress = kingpin.Flag("web.listen-address", "Address on which to expose metrics and web interface.").Default(":9342").String() telemetryPath = kingpin.Flag("web.telemetry-path", "Path under which to expose metrics.").Default("/metrics").String() configFile = kingpin.Flag("web.config", "[EXPERIMENTAL] Path to config yaml file that can enable TLS or authentication.").Default("").String() ) func handler(logger log.Logger) http.Handler { registry := prometheus.NewRegistry() nc, err := collector.NewExporter(logger) if err != nil { panic(fmt.Sprintf("Couldn't create collector: %s", err)) } if err := registry.Register(nc); err != nil { panic(fmt.Sprintf("Couldn't register collector: %s", err)) } gatheres := prometheus.Gatherers{ prometheus.DefaultGatherer, registry, } handlerOpts := promhttp.HandlerOpts{ ErrorLog: inbuiltLog.New(log.NewStdlibAdapter(level.Error(logger)), "", 0), ErrorHandling: promhttp.ContinueOnError, } return promhttp.HandlerFor(gatheres, handlerOpts) } func main() { promlogConfig := &promlog.Config{} flag.AddFlags(kingpin.CommandLine, promlogConfig) kingpin.Version(version.Print("frr_exporter")) kingpin.HelpFlag.Short('h') kingpin.Parse() logger := promlog.New(promlogConfig) prometheus.MustRegister(version.NewCollector("frr_exporter")) level.Info(logger).Log("msg", "Starting frr_exporter", "version", version.Info()) level.Info(logger).Log("msg", "Build context", "build_context", version.BuildContext()) level.Info(logger).Log("msg", "Listening on address", "address", *listenAddress) http.Handle(*telemetryPath, handler(logger)) http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte(` FRR Exporter

FRR Exporter

Metrics

`)) }) server := &http.Server{Addr: *listenAddress} if err := web.ListenAndServe(server, *configFile, logger); err != nil { level.Error(logger).Log("err", err) os.Exit(1) } } prometheus-frr-exporter-1.1.0/go.mod000066400000000000000000000017131416043142100174450ustar00rootroot00000000000000module github.com/tynany/frr_exporter go 1.13 require ( github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/go-kit/log v0.1.0 github.com/go-logfmt/logfmt v0.5.1 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/prometheus/client_golang v1.11.0 github.com/prometheus/client_model v0.2.0 github.com/prometheus/common v0.30.0 github.com/prometheus/exporter-toolkit v0.6.1 github.com/prometheus/procfs v0.7.3 // indirect golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect golang.org/x/net v0.0.0-20210825183410-e898025ed96a // indirect golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f // indirect golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf // indirect golang.org/x/text v0.3.7 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.27.1 // indirect gopkg.in/alecthomas/kingpin.v2 v2.2.6 ) prometheus-frr-exporter-1.1.0/go.sum000066400000000000000000001404501416043142100174740ustar00rootroot00000000000000cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15 h1:AUNCr9CiJuwrRYS3XieqF+Z9B9gNxo/eANAJCF2eiN4= github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0 h1:DGJh0Sm43HbOeYDNnVZFl8BvcYVvjD5bqYJvp0REbwQ= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.29.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.30.0 h1:JEkYlQnpzrzQFxi6gnukFPdQ+ac82oRhzMcIduJu/Ug= github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/exporter-toolkit v0.6.1 h1:Aqk75wQD92N9CqmTlZwjKwq6272nOGrWIbc8Z7+xQO0= github.com/prometheus/exporter-toolkit v0.6.1/go.mod h1:ZUBIj498ePooX9t/2xtDjeQYwvRpiPP2lh5u4iblj2g= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210825183410-e898025ed96a h1:bRuuGXV8wwSdGTB+CtJf+FjgO1APK1CoO39T4BN/XBw= golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f h1:Qmd2pbz05z7z6lm0DrgQVVPuBm92jqujBKMHMOlOQEw= golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf h1:2ucpDCmfkl8Bd/FsLtiD653Wf96cW37s+iGx93zsu4k= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= prometheus-frr-exporter-1.1.0/internal/000077500000000000000000000000001416043142100201515ustar00rootroot00000000000000prometheus-frr-exporter-1.1.0/internal/frrsockets/000077500000000000000000000000001416043142100223365ustar00rootroot00000000000000prometheus-frr-exporter-1.1.0/internal/frrsockets/frrsockets.go000066400000000000000000000036601416043142100250570ustar00rootroot00000000000000package frrsockets import ( "bytes" "fmt" "net" "path/filepath" "time" ) type Connection struct { dirPath string timeout time.Duration } func NewConnection(dirPath string, timeout time.Duration) *Connection { return &Connection{dirPath: dirPath, timeout: timeout} } func (c Connection) ExecBGPCmd(cmd string) ([]byte, error) { return execteCmd(filepath.Clean(c.dirPath+"/bgpd.vty"), cmd, c.timeout) } func (c Connection) ExecOSPFCmd(cmd string) ([]byte, error) { return execteCmd(filepath.Clean(c.dirPath+"/ospfd.vty"), cmd, c.timeout) } func (c Connection) ExecOSPFMultiInstanceCmd(cmd string, instanceID int) ([]byte, error) { return execteCmd(filepath.Clean(c.dirPath+fmt.Sprintf("/ospfd-%d.vty", instanceID)), cmd, c.timeout) } func (c Connection) ExecPIMCmd(cmd string) ([]byte, error) { return execteCmd(filepath.Clean(c.dirPath+"/pimd.vty"), cmd, c.timeout) } func (c Connection) ExecZebraCmd(cmd string) ([]byte, error) { return execteCmd(filepath.Clean(c.dirPath+"/zebra.vty"), cmd, c.timeout) } func (c Connection) ExecVRRPCmd(cmd string) ([]byte, error) { return execteCmd(filepath.Clean(c.dirPath+"/vrrpd.vty"), cmd, c.timeout) } func execteCmd(socketPath, cmd string, timeout time.Duration) ([]byte, error) { var buf bytes.Buffer addr := net.UnixAddr{Name: socketPath, Net: "unix"} conn, err := net.DialUnix("unix", nil, &addr) if err != nil { return buf.Bytes(), err } if err = conn.SetDeadline(time.Now().Add(timeout)); err != nil { return buf.Bytes(), err } // frr sockets expect each command to end with \0 _, err = conn.Write([]byte(fmt.Sprintf("%s\000", cmd))) if err != nil { return buf.Bytes(), err } for { b := make([]byte, 1024) _, err := conn.Read(b) if err != nil { return buf.Bytes(), err } // frr signals the end of a response with \x00 if bytes.HasSuffix(b, []byte("\x00")) { buf.Write(bytes.Trim(b, "\x00")) conn.Close() return buf.Bytes(), nil } buf.Write(b) } }