Compare commits

..

21 Commits
v0.3 ... main

Author SHA1 Message Date
jex 129ee654eb
use TLS when -unbound.key is present (#84)
Previously, we attempted TLS if either of `-unbound.ca` or `-unbound.cert` were present.

In practice TLS configuration requires all three to succeed, though.
2025-06-05 11:16:52 -07:00
dependabot[bot] 39672d0656
Bump google.golang.org/protobuf from 1.31.0 to 1.33.0 (#79)
Bumps google.golang.org/protobuf from 1.31.0 to 1.33.0.

---
updated-dependencies:
- dependency-name: google.golang.org/protobuf
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-11 11:34:23 -05:00
Matthew McPherrin b9062551e8
Dependabot config for unbound_exporter (#80) 2025-02-11 11:24:51 -05:00
Phil Porada ab17c00752
Track memory used by DoH buffers (#72) 2024-01-30 10:17:11 -05:00
Phil Porada e284f508b4
Add metric for num.rpz.action (#70)
Unbound exporter is now able to scrape each num.rpz.action.rpz-TYPE labeled by type. RPZ mode must be enabled in the unbound configuration and functioning for this metric to appear.
2024-01-08 15:07:44 -05:00
Matthew McPherrin 5e9c7ff5c5
Update prometheus dependencies (#67) 2023-11-17 18:20:54 -05:00
Matthew McPherrin e54ee016b6
Add Dockerfile, compose.yaml, and an unbound example config (#66)
This adds a Dockerfile for unbound exporter, which we can publish in the future. An example docker compose.yml is included to demonstrate and test using it with unbound, along with a sample configuration file for unbound showing how to set up the remote-control.

The unbound example config file is based on the one inside the mvance/docker image that's used here.

A small integration test runs against the docker-compose setup to smoke-test unbound_exporter.
2023-11-17 13:12:02 -05:00
Matthew McPherrin cbed00787a
Update github actions (#65)
Test on two latest Go versions.
Release with 1.21.4.
Update the actions to their latest versions
2023-11-13 18:34:00 -08:00
Jacob Hoffman-Andrews 1de6c94faa
Update README with deployment models (#64) 2023-11-13 16:54:00 -08:00
Jonathan Davies ed237708aa
Added cookie and max collision metrics (#61)
* Added metrics for cookies.

* Added metrics for cache max_collisons.
2023-11-13 16:53:46 -08:00
Kasumi Hanazuki 2faff03ab6
Metrics for TLS resumes, DoH, and aggressive NSEC (#53)
Map unbound's `num.query.agressive.*` stats to `query_aggressive_nsec`
metrics. This counter tracks the number of queries that unbound
synthesised answers based on the cached NSEC records.

See https://unbound.docs.nlnetlabs.nl/en/latest/topics/privacy/aggressive-nsec.html

Also add `query_tls_resume_total` and `query_https_total` metrics,
which track the number of queries received using TLS resume
and DNS-over-HTTPS transport, resp.

Fixes https://github.com/letsencrypt/unbound_exporter/issues/48
2023-08-23 13:24:07 -07:00
Phil Porada 1d05a2741b
Add contrib directory (#45)
The new contrib directory contains a script that generates an EC key-pair that satisfies golang >=1.15 CommonName deprecation. 

Co-authored-by: J.C. Jones <jcjones@users.noreply.github.com>
Co-authored-by: Samantha <hello@entropy.cat>
2023-02-22 18:22:22 -05:00
Phil Porada cb8f755e9b
Update dependencies to fix promhttp uncontrolled resource consumption (#51)
* Update dependencies to fix promhttp uncontrolled resource consumption
* Update CI pipeline golang to 1.20.1
2023-02-21 16:42:51 -05:00
Jacob Hoffman-Andrews f536641923
Update installation instructions (#43)
`go get` is no longer supported outside of modules. Use `go install` instead.

Also document minimum required Go version.
2022-07-06 18:33:29 -07:00
Robert Edmonds ef346f5e25
Add new metrics for num.query.tcpout and num.query.udpout (#40)
Unbound has a statistics counter `num.query.tcpout` which measures the
number of outgoing queries that Unbound has made via TCP, which was
added in 1.5.0 in 2014:

330b3219a0

The upcoming release of Unbound (1.16.1) should have support for a new
statistics counter `num.query.udpout` which measures the number of
outgoing queries that Unbound has made via UDP:

b816318106

There are several configuration options that can affect the number of
outgoing queries made by the server (`qname-minimisation`,
`serve-expired`, `prefetch`, `target-fetch-policy`,
`harden-referral-path`, etc.) and recursion itself may require making
more than one outgoing query per cache miss, all of which make it useful
to monitor the number of outgoing queries from the server separately
from the number of incoming queries to the server, or the number of
incoming queries that caused cache misses. Timeouts/retries during
recursion can also affect the number of outgoing queries made.
2022-06-29 13:41:56 -07:00
James Renken 8c54f36172
Restore load-bearing checkout step in release Action 2022-04-22 11:44:00 -07:00
J.C. Jones 43dfb18ca7
Build and Release Action (#38)
Co-authored-by: James Renken <jrenken@letsencrypt.org>
2022-04-22 11:28:46 -07:00
Phil Porada b4edb2be0b
Update repository name in go.mod (#29) 2021-11-17 15:11:42 -05:00
Phil Porada 209c3cbdd4
Fix lints, add testing file, and update go mod and sum files (#28)
* Fix lint errors. Includes PR #25 and PR #23
2021-11-17 14:32:28 -05:00
Phil Porada 6cd7580102
Add Github Actions CI (#27) 2021-11-17 14:15:26 -05:00
Bart Vercoulen 1b0abc3709
Unmaintained readme update. 2021-03-12 10:16:21 +01:00
19 changed files with 744 additions and 124 deletions

10
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,10 @@
version: 2
updates:
- package-ecosystem: "go"
directory: "/"
schedule:
interval: "monthly"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"

27
.github/workflows/integration.yaml vendored Normal file
View File

@ -0,0 +1,27 @@
---
name: integration
on:
push:
branches:
- main
pull_request:
workflow_dispatch:
jobs:
integration:
runs-on: [ubuntu-latest]
steps:
- name: Install Go
uses: actions/setup-go@v4
with:
go-version: "1.21.x"
- name: checkout
uses: actions/checkout@v4
- name: Start containers
run: docker compose up --build --detach
- name: run integration test
run: go test -v --tags=integration
- name: Stop containers
if: always()
run: docker compose down

65
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,65 @@
name: Build and release
on:
# Runs automatically when a tag beginning with 'v' (i.e. a versioned release) is pushed.
push:
tags:
- v*
branches: [main]
pull_request:
branches: [main]
jobs:
build-release:
runs-on: ubuntu-20.04
permissions:
contents: read
steps:
- uses: actions/setup-go@v4
with:
go-version: '1.21.4'
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: build binary
run: go build
- name: install nfpm
run: go install github.com/goreleaser/nfpm/v2/cmd/nfpm@v2.15.1
- name: build deb
run: nfpm package -p deb -t unbound_exporter.deb
- name: upload deb
uses: actions/upload-artifact@v3
with:
name: unbound_exporter deb artifact
path: unbound_exporter.deb
push-release:
if: github.event_name == 'push' && contains(github.ref, 'refs/tags/')
needs: build-release
runs-on: ubuntu-20.04
# Overrides the org default of 'read'. This allows us to upload and post the
# resulting package file as part of a release.
permissions:
contents: write
steps:
- uses: actions/checkout@v2
with:
persist-credentials: false
- name: Download release artifact
uses: actions/download-artifact@v3
with:
name: unbound_exporter deb artifact
- name: rename
run: mv unbound_exporter.deb unbound_exporter-${GITHUB_REF_NAME}.x86_64.deb
- name: push release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# https://cli.github.com/manual/gh_release_create
run: gh release create "${GITHUB_REF_NAME}" unbound_exporter-${GITHUB_REF_NAME}.x86_64.deb

42
.github/workflows/test.yaml vendored Normal file
View File

@ -0,0 +1,42 @@
name: test
on:
push:
branches:
- main
- master
pull_request:
workflow_dispatch:
env:
GO111MODULE: "auto"
jobs:
test:
strategy:
matrix:
go-version:
- 1.20.x
- 1.21.x
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Install Go
uses: actions/setup-go@v4
with:
go-version: ${{ matrix.go-version }}
- name: Checkout code
uses: actions/checkout@v4
- name: golangci-lint
uses: golangci/golangci-lint-action@v2
with:
version: latest
- name: go coverage
run: |
go test -mod=readonly -v -race -covermode=atomic -coverprofile=coverage.out ./...
- uses: codecov/codecov-action@v3
if: success()
with:
file: ./coverage.out
flags: unbound_exporter_tests
name: unbound_exporter tests

20
Dockerfile Normal file
View File

@ -0,0 +1,20 @@
FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.21.4-bookworm AS build
WORKDIR /go/src/app
COPY go.mod .
COPY go.sum .
RUN go mod download
COPY *.go .
ENV CGO_ENABLED=0
RUN GOOS=$TARGETOS GOARCH=$TARGETPLATFORM go build -v -o /go/bin/unbound_exporter ./...
FROM gcr.io/distroless/static-debian12
COPY --from=build /go/bin/unbound_exporter /
ENTRYPOINT ["/unbound_exporter"]

View File

@ -9,22 +9,53 @@ names and labels by using a set of regular expressions.
- - - -
# Prerequisites
Go 1.20 or above is required.
# Installation
To install this code and in your go environment. You can then add the binary to your `PATH`.
go install github.com/letsencrypt/unbound_exporter@latest
go get github.com/kumina/unbound_exporter
go install github.com/kumina/unbound_exporter
This will install the binary in `$GOBIN`, or `$HOME/go/bin` if
`$GOBIN` is unset.
# Updating dependencies
```
go get -u
go mod tidy
```
- - - -
# Usage
# Usage - Unix socket
To show all CLI flags available
The simplest way to run unbound_exporter is on the same machine as your Unbound instance, connecting via a Unix socket. First, make sure you have this in your unbound.conf:
unbound_exporter -h
remote-control:
control-enable: yes
control-interface: /run/unbound.ctl
For extended statistics, you may want to add the following to your unbound.conf
Then, arrange to run this on the same machine:
unbound_exporter -unbound.ca "" -unbound.cert "" -unbound.host "unix:///run/unbound.ctl"
Metrics will be exported under /metrics, on port 9167, on all interfaces.
$ curl 127.0.0.1:9167/metrics | grep '^unbound_up'
unbound_up 1
# Usage - TLS
The more complicated way to run unbound_exporter is to configure unbound's control-interface with a TLS certificate from a private CA, and run unbound_exporter on a separate host. This is more of a hassle because you have to keep the certificate up to date and distribute the private CA to the host that unbound_exporter runs on.
See https://unbound.docs.nlnetlabs.nl/en/latest/getting-started/configuration.html#set-up-remote-control for instructions on setting up the certificates and keys for remote-control via TLS. On the unbound_exporter side you will need to set the `-unbound.ca`, `-unbound.cert`, and `-unbound.key` flags to point to valid files that will trust the Unbound server's certificate and be trusted by Unbound in return.
# Extended statistics
From the Unbound [statistics doc](https://www.nlnetlabs.nl/documentation/unbound/howto-statistics/): Unbound has an option to enable extended statistics collection. If enabled, more statistics are collected, for example what types of queries are sent to the resolver. Otherwise, only the total number of queries is collected. Add the following to your `unbound.conf`.
server:
extended-statistics: yes
# statistics
extended-statistics: yes

View File

@ -1,15 +0,0 @@
#!/bin/sh
docker run -i -v `pwd`:/unbound_exporter alpine:edge /bin/sh << 'EOF'
set -ex
# Install prerequisites for the build process.
apk update
apk add ca-certificates git go libc-dev
update-ca-certificates
# Build the unbound_exporter.
cd /unbound_exporter
go build --ldflags '-extldflags "-static"'
strip unbound_exporter
EOF

45
contrib/README.md Normal file
View File

@ -0,0 +1,45 @@
# Contrib
This collection of scripts and files helps us further configure our unbounds and unbound_exporters.
## unbound-control-setup.sh
From [Golang 1.15 docs:](https://golang.google.cn/doc/go1.15#commonname)
> X.509 CommonName deprecation
> The deprecated, legacy behavior of treating the CommonName field on X.509 certificates as a host name when no Subject Alternative Names are present is now disabled by default. It can be temporarily re-enabled by adding the value x509ignoreCN=0 to the GODEBUG environment variable.
> Note that if the CommonName is an invalid host name, it's always ignored, regardless of GODEBUG settings. Invalid names include those with any characters other than letters, digits, hyphens and underscores, and those with empty labels or trailing dots.
Unbound still ships with an `unbound-control-setup` that generates a problematic keypair. This script will generate a keypair that satisfies newer versions of Golang.
Generate the new keypair
```
$ bash unbound-control-setup.sh
```
You'll then want to configure `/etc/unbound/unbound.conf` with the following stanza
```
$ cat /etc/unbound/unbound.conf
...
remote-control:
control-enable: yes
control-use-cert: yes
server-key-file: "/etc/unbound/unbound_server_ec.key"
server-cert-file: "/etc/unbound/unbound_server_ec.pem"
control-key-file: "/etc/unbound/unbound_control_ec.key"
control-cert-file: "/etc/unbound/unbound_control_ec.pem"
```
Test that you can still communicate with unbound via `unbound_control`. You should be able to see metrics.
```
$ unbound-control stats_noreset
thread0.num.queries=35
thread0.num.queries_ip_ratelimited=0
thread0.num.cachehits=25
thread0.num.cachemiss=10
thread0.num.prefetch=0
thread0.num.expired=0
...
```
To reconfigure `unbound_exporter` as a systemd service, see [this file](unbound_exporter.service).

147
contrib/unbound-cert-setup.sh Executable file
View File

@ -0,0 +1,147 @@
#!/usr/bin/env bash
# Generally based on /usr/sbin/unbound-control-setup but adapted to catch
# up to ~2010. You know, x509v3, secp384r1, AKIs, stuff like that.
# directory for files
DESTDIR="${UNBOUND_CONFIG_DIR:-/etc/unbound}"
# validity period for certificates
DAYS="${UNBOUND_CERT_LIFETIME:-397}"
# hash algorithm
HASH=sha256
# base name for unbound CA keys
CA_BASE=unbound_ca_ec
# base name for unbound server keys
SVR_BASE=unbound_server_ec
# base name for unbound-control keys
CTL_BASE=unbound_control_ec
# we want -rw-r----- access (say you run this as root: grp=yes (server), all=no).
umask 0027
# end of options
# functions:
error ( ) {
echo "$0 fatal error: ${1}"
exit 1
}
# go!:
echo "setup in directory ${DESTDIR}"
cd "${DESTDIR}" || error "could not cd to ${DESTDIR}"
# create certificate keys; do not recreate if they already exist.
if test -f "${CA_BASE}.key"; then
echo "${CA_BASE}.key exists"
else
echo "generating ${CA_BASE}.key"
openssl ecparam -genkey -name secp384r1 > ${CA_BASE}.key || error "could not gen ecdsa"
fi
if test -f "${SVR_BASE}.key"; then
echo "${SVR_BASE}.key exists"
else
echo "generating ${SVR_BASE}.key"
openssl ecparam -genkey -name secp384r1 > ${SVR_BASE}.key || error "could not gen ecdsa"
fi
if test -f "${CTL_BASE}.key"; then
echo "${CTL_BASE}.key exists"
else
echo "generating ${CTL_BASE}.key"
openssl ecparam -genkey -name secp384r1 > ${CTL_BASE}.key || error "could not gen ecdsa"
fi
# create self-signed cert CSR for server
cat > ca_request.cfg <<EOCAConfig
[req]
prompt = no
distinguished_name = req_distinguished_name
x509_extensions = req_v3_extensions
[req_distinguished_name]
commonName = unbound-ca
[req_v3_extensions]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer:always
basicConstraints = CA:true
EOCAConfig
test -f ca_request.cfg || error "could not create ca_request.cfg"
echo "creating ${CA_BASE}.pem (self signed certificate)"
openssl req -key "${CA_BASE}.key" -config ca_request.cfg -new -x509 -days "${DAYS}" -out "${CA_BASE}.pem" || error "could not create ${CA_BASE}.pem"
# --------------
# create server cert CSR and sign it, piped
cat > server_request.cfg <<EOServerConfig
[req]
prompt = no
distinguished_name = req_distinguished_name
[req_distinguished_name]
commonName = unbound
EOServerConfig
test -f server_request.cfg || error "could not create server_request.cfg"
cat > server_exts.cfg <<EOServerConfig
subjectAltName = @alt_names
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer:always
extendedKeyUsage = serverAuth
[alt_names]
DNS.1 = $(hostname)
DNS.2 = unbound
DNS.3 = localhost
EOServerConfig
test -f server_exts.cfg || error "could not create server_exts.cfg"
echo "create ${SVR_BASE}.pem (signed server certificate)"
openssl req -key "${SVR_BASE}.key" -config server_request.cfg -new | openssl x509 -req -days "${DAYS}" -CA "${CA_BASE}.pem" -CAkey "${CA_BASE}.key" -CAcreateserial -"${HASH}" -extfile server_exts.cfg -out "${SVR_BASE}.pem"
test -f ${SVR_BASE}.pem || error "could not create ${SVR_BASE}.pem"
# --------------
# create client cert CSR and sign it, piped
cat > client_request.cfg <<EOCertConfig
[req]
prompt = no
distinguished_name = req_distinguished_name
[req_distinguished_name]
commonName = unbound-control
EOCertConfig
test -f client_request.cfg || error "could not create client_request.cfg"
cat > client_exts.cfg <<EOCertConfig
extendedKeyUsage = clientAuth
EOCertConfig
test -f client_exts.cfg || error "could not create client_exts.cfg"
echo "create ${CTL_BASE}.pem (signed client certificate)"
openssl req -key "${CTL_BASE}.key" -config client_request.cfg -new | openssl x509 -req -days "${DAYS}" -CA "${CA_BASE}.pem" -CAkey "${CA_BASE}.key" -CAcreateserial -"${HASH}" -extfile client_exts.cfg -out "${CTL_BASE}.pem"
test -f "${CTL_BASE}.pem" || error "could not create ${CTL_BASE}.pem"
# --------------
# set desired permissions
chmod 0640 "${CA_BASE}.key" "${SVR_BASE}.key" "${CTL_BASE}.key"
chmod 0644 "${CA_BASE}.pem" "${SVR_BASE}.pem" "${CTL_BASE}.pem"
# cleanup
rm -f ca_request.cfg client_request.cfg client_exts.cfg server_request.cfg server_exts.cfg *.srl
openssl x509 -text -in "${CA_BASE}.pem"
openssl x509 -text -in "${SVR_BASE}.pem"
openssl x509 -text -in "${CTL_BASE}.pem"
echo "Satisfy unbound daemon/remote.c SSL_CTX_use_certificate_chain_file by appending ${CA_BASE}.pem to ${SVR_BASE}.pem"
cat "${CA_BASE}.pem" >> "${SVR_BASE}.pem"
echo "Setup success. Certificates created."

View File

@ -0,0 +1,15 @@
[Unit]
Description=Prometheus exporter for Unbound metrics, written in Go with pluggable metric collectors. The metrics exporter converts Unbound metric names to Prometheus metric names and labels by using a set of regular expressions.
Documentation=https://github.com/letsencrypt/unbound_exporter
After=network.target
[Service]
Type=simple
ExecStart=/bin/unbound_exporter \
-unbound.ca "/etc/unbound/unbound_ca_ec.pem" \
-unbound.cert "/etc/unbound/unbound_control_ec.pem" \
-unbound.key "/etc/unbound/unbound_control_ec.key" \
-unbound.host "tcp://localhost:8953"
[Install]
WantedBy=multi-user.target

22
docker-compose.yml Normal file
View File

@ -0,0 +1,22 @@
services:
unbound_exporter:
build: .
command: [ "-unbound.host=unix:///var/run/socket/unbound.ctl" ]
volumes:
- socket:/var/run/socket:ro
ports:
- "9167:9167"
depends_on:
unbound:
condition: service_started
unbound:
image: "mvance/unbound:1.18.0"
volumes:
- socket:/var/run/socket:rw
- ./unbound-example.conf:/opt/unbound/etc/unbound/unbound.conf
- ./droplist.zone:/opt/unbound/etc/unbound/droplist.zone
ports:
- "1053:1053/udp"
- "1053:1053/tcp"
volumes:
socket:

2
droplist.zone Normal file
View File

@ -0,0 +1,2 @@
*.example.com IN A 127.0.0.1
*.example.net IN A 127.0.0.1

23
go.mod
View File

@ -1,11 +1,20 @@
module github.com/kumina/unbound_exporter
module github.com/letsencrypt/unbound_exporter
go 1.12
go 1.20
require (
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 // indirect
github.com/prometheus/client_golang v1.0.0
github.com/prometheus/common v0.5.0
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 // indirect
github.com/go-kit/log v0.2.1
github.com/prometheus/client_golang v1.17.0
github.com/prometheus/common v0.45.0
)
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
github.com/prometheus/client_model v0.5.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
golang.org/x/sys v0.14.0 // indirect
google.golang.org/protobuf v1.33.0 // indirect
)

95
go.sum
View File

@ -1,72 +1,25 @@
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU=
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 h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 h1:Hs82Z41s6SdL1CELW+XaDYmOH4hkBN4/N9og/AsOv7E=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
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-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/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/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/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-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
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/pkg/errors v0.8.0/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 h1:vrDKnkGzuGvhNAL56c7DBz29ZL+KxnoR0x7enabFceM=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.5.0 h1:2znmQeLeqnfKh7s5Tdg2bjfRzmVBD6JMp6SmWZHwU1E=
github.com/prometheus/common v0.5.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2 h1:6LJUbpNm42llc4HRCuvApCSWB/WfhuNo9K98Q9sNGfs=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
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 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
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/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 h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
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/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU=
github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q=
github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY=
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM=
github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY=
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=

59
integration_test.go Normal file
View File

@ -0,0 +1,59 @@
//go:build integration
package main
import (
"net/http"
"testing"
"github.com/prometheus/common/expfmt"
)
// TestIntegration checks that unbound_exporter is running, successfully
// scraping and exporting metrics.
//
// It assumes unbound_exporter is available on localhost:9167, and Unbound on
// localhost:1053, as is set up in the docker-compose.yml file.
//
// A typical invocation of this test would look like
//
// docker compose up --build -d
// go test --tags=integration
// docker compose down
func TestIntegration(t *testing.T) {
resp, err := http.Get("http://localhost:9167/metrics")
if err != nil {
t.Fatalf("Failed to fetch metrics from unbound_exporter: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Fatalf("Expected a 200 OK from unbound_exporter, got: %v", resp.StatusCode)
}
parser := expfmt.TextParser{}
metrics, err := parser.TextToMetricFamilies(resp.Body)
if err != nil {
t.Fatalf("Failed to parse metrics from unbound_exporter: %v", err)
}
// unbound_up is 1 if we've successfully scraped metrics from it
unbound_up := metrics["unbound_up"].Metric[0].Gauge.GetValue()
if unbound_up != 1 {
t.Errorf("Expected unbound_up to be 1, not: %v", unbound_up)
}
// Check some expected metrics are present
for _, metric := range []string{
"go_info",
"unbound_queries_total",
"unbound_response_time_seconds",
"unbound_cache_hits_total",
"unbound_query_https_total",
"unbound_memory_doh_bytes",
} {
if _, ok := metrics[metric]; !ok {
t.Errorf("Expected metric is missing: %s", metric)
}
}
}

12
nfpm.yaml Normal file
View File

@ -0,0 +1,12 @@
name: "unbound_exporter"
arch: "amd64"
platform: "linux"
version: "${GITHUB_REF_NAME}"
description: "Prometheus exporter for Unbound recursive DNS resolver"
vendor: "ISRG"
maintainer: "ISRG Team <opensource@letsencrypt.org>"
homepage: "https://github.com/letsencrypt/unbound_exporter"
license: "Apache 2.0"
contents:
- src: unbound_exporter
dst: /usr/bin/unbound_exporter

93
unbound-example.conf Normal file
View File

@ -0,0 +1,93 @@
## This is an example Unbound configuration file
## This is needed to use unbound_exporter
remote-control:
control-enable: yes
control-interface: /var/run/socket/unbound.ctl
# The rest of this file is standard Unbound configuration
# There's nothing special here.
server:
module-config: "respip validator iterator"
extended-statistics: yes
cache-max-ttl: 86400
cache-min-ttl: 300
directory: "/opt/unbound/etc/unbound"
do-ip4: yes
do-ip6: no
do-tcp: yes
do-udp: yes
edns-buffer-size: 1232
interface: 0.0.0.0
port: 1053
prefer-ip6: no
rrset-roundrobin: yes
username: "_unbound"
log-local-actions: no
log-queries: no
log-replies: no
log-servfail: yes
logfile: /opt/unbound/etc/unbound/unbound.log
verbosity: 2
infra-cache-slabs: 4
incoming-num-tcp: 10
key-cache-slabs: 4
msg-cache-size: 142768128
msg-cache-slabs: 4
num-queries-per-thread: 4096
num-threads: 3
outgoing-range: 8192
rrset-cache-size: 285536256
rrset-cache-slabs: 4
minimal-responses: yes
prefetch: yes
prefetch-key: yes
serve-expired: yes
so-reuseport: yes
aggressive-nsec: yes
delay-close: 10000
do-daemonize: no
do-not-query-localhost: no
neg-cache-size: 4M
qname-minimisation: yes
access-control: 127.0.0.1/32 allow
access-control: 192.168.0.0/16 allow
access-control: 172.16.0.0/12 allow
access-control: 10.0.0.0/8 allow
access-control: fc00::/7 allow
access-control: ::1/128 allow
auto-trust-anchor-file: "/opt/unbound/etc/unbound/var/root.key"
chroot: ""
deny-any: yes
harden-algo-downgrade: yes
harden-below-nxdomain: yes
harden-dnssec-stripped: yes
harden-glue: yes
harden-large-queries: yes
harden-referral-path: no
harden-short-bufsize: yes
hide-http-user-agent: no
hide-identity: yes
hide-version: no
http-user-agent: "DNS"
identity: "DNS"
private-address: 10.0.0.0/8
private-address: 172.16.0.0/12
private-address: 192.168.0.0/16
private-address: 169.254.0.0/16
private-address: fd00::/8
private-address: fe80::/10
private-address: ::ffff:0:0/96
ratelimit: 1000
tls-cert-bundle: /etc/ssl/certs/ca-certificates.crt
unwanted-reply-threshold: 10000
use-caps-for-id: yes
val-clean-additional: yes
include: /opt/unbound/etc/unbound/a-records.conf
include: /opt/unbound/etc/unbound/srv-records.conf
rpz:
name: unbound_exporter_cloak
zonefile: /opt/unbound/etc/unbound/droplist.zone
rpz-log: yes
rpz-log-name: unbound_exporter_cloak
rpz-action-override: nxdomain

View File

@ -20,7 +20,6 @@ import (
"flag"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"net/url"
@ -31,12 +30,15 @@ import (
"sort"
"github.com/go-kit/log/level"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/prometheus/common/log"
"github.com/prometheus/common/promlog"
)
var (
log = promlog.New(&promlog.Config{})
unboundUpDesc = prometheus.NewDesc(
prometheus.BuildFQName("unbound", "", "up"),
"Whether scraping Unbound's metrics was successful.",
@ -78,6 +80,24 @@ var (
prometheus.CounterValue,
[]string{"thread"},
"^thread(\\d+)\\.num\\.cachemiss$"),
newUnboundMetric(
"queries_cookie_client_total",
"Total number of queries with a client cookie.",
prometheus.CounterValue,
[]string{"thread"},
"^thread(\\d+)\\.num\\.queries_cookie_client$"),
newUnboundMetric(
"queries_cookie_invalid_total",
"Total number of queries with a invalid cookie.",
prometheus.CounterValue,
[]string{"thread"},
"^thread(\\d+)\\.num\\.queries_invalid_client$"),
newUnboundMetric(
"queries_cookie_valid_total",
"Total number of queries with a valid cookie.",
prometheus.CounterValue,
[]string{"thread"},
"^thread(\\d+)\\.num\\.queries_cookie_valid$"),
newUnboundMetric(
"memory_caches_bytes",
"Memory in bytes in use by caches.",
@ -108,6 +128,12 @@ var (
prometheus.CounterValue,
[]string{"thread"},
"^thread(\\d+)\\.num\\.queries$"),
newUnboundMetric(
"expired_total",
"Total number of expired entries served.",
prometheus.CounterValue,
[]string{"thread"},
"^thread(\\d+)\\.num\\.expired$"),
newUnboundMetric(
"query_classes_total",
"Total number of queries with a given query class.",
@ -146,22 +172,52 @@ var (
"^num\\.query\\.edns\\.present$"),
newUnboundMetric(
"query_tcp_total",
"Total number of queries that were made using TCP towards the Unbound server.",
"Total number of queries that were made using TCP towards the Unbound server, including DoT and DoH queries.",
prometheus.CounterValue,
nil,
"^num\\.query\\.tcp$"),
newUnboundMetric(
"query_tcpout_total",
"Total number of queries that the Unbound server made using TCP outgoing towards other servers.",
prometheus.CounterValue,
nil,
"^num\\.query\\.tcpout$"),
newUnboundMetric(
"query_tls_total",
"Total number of queries that were made using TCP TLS towards the Unbound server.",
"Total number of queries that were made using TCP TLS towards the Unbound server, including DoT and DoH queries.",
prometheus.CounterValue,
nil,
"^num\\.query\\.tls$"),
newUnboundMetric(
"query_tls_resume_total",
"Total number of queries that were made using TCP TLS Resume towards the Unbound server.",
prometheus.CounterValue,
nil,
"^num\\.query\\.tls\\.resume$"),
newUnboundMetric(
"query_https_total",
"Total number of DoH queries that were made towards the Unbound server.",
prometheus.CounterValue,
nil,
"^num\\.query\\.https$"),
newUnboundMetric(
"query_types_total",
"Total number of queries with a given query type.",
prometheus.CounterValue,
[]string{"type"},
"^num\\.query\\.type\\.([\\w]+)$"),
newUnboundMetric(
"query_udpout_total",
"Total number of queries that the Unbound server made using UDP outgoing towardsother servers.",
prometheus.CounterValue,
nil,
"^num\\.query\\.udpout$"),
newUnboundMetric(
"query_aggressive_nsec",
"Total number of queries that the Unbound server generated response using Aggressive NSEC.",
prometheus.CounterValue,
[]string{"rcode"},
"^num\\.query\\.aggressive\\.(\\w+)$"),
newUnboundMetric(
"request_list_current_all",
"Current size of the request list, including internally generated queries.",
@ -198,6 +254,12 @@ var (
prometheus.CounterValue,
nil,
"^num\\.rrset\\.bogus$"),
newUnboundMetric(
"rrset_cache_max_collisions_total",
"Total number of rrset cache hashtable collisions.",
prometheus.CounterValue,
nil,
"^rrset\\.cache\\.max_collisions$"),
newUnboundMetric(
"time_elapsed_seconds",
"Time since last statistics printout in seconds.",
@ -246,12 +308,30 @@ var (
prometheus.GaugeValue,
nil,
"^msg\\.cache\\.count$"),
newUnboundMetric(
"msg_cache_max_collisions_total",
"Total number of msg cache hashtable collisions.",
prometheus.CounterValue,
nil,
"^msg\\.cache\\.max_collisions$"),
newUnboundMetric(
"rrset_cache_count",
"The Number of rrset cached",
prometheus.GaugeValue,
nil,
"^rrset\\.cache\\.count$"),
newUnboundMetric(
"rpz_action_count",
"Total number of triggered Response Policy Zone actions, by type.",
prometheus.CounterValue,
[]string{"type"},
"^num\\.rpz\\.action\\.rpz-([\\w-]+)$"),
newUnboundMetric(
"memory_doh_bytes",
"Memory used by DoH buffers, in bytes.",
prometheus.GaugeValue,
[]string{"buffer"},
"^mem\\.http\\.(\\w+)$"),
}
)
@ -276,7 +356,7 @@ func newUnboundMetric(name string, description string, valueType prometheus.Valu
func CollectFromReader(file io.Reader, ch chan<- prometheus.Metric) error {
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
histogramPattern := regexp.MustCompile("^histogram\\.\\d+\\.\\d+\\.to\\.(\\d+\\.\\d+)$")
histogramPattern := regexp.MustCompile(`^histogram\.\d+\.\d+\.to\.(\d+\.\d+)$`)
histogramCount := uint64(0)
histogramAvg := float64(0)
@ -351,14 +431,6 @@ func CollectFromReader(file io.Reader, ch chan<- prometheus.Metric) error {
return scanner.Err()
}
func CollectFromFile(path string, ch chan<- prometheus.Metric) error {
conn, err := os.Open(path)
if err != nil {
return err
}
return CollectFromReader(conn, ch)
}
func CollectFromSocket(socketFamily string, host string, tlsConfig *tls.Config, ch chan<- prometheus.Metric) error {
var (
conn net.Conn
@ -373,6 +445,7 @@ func CollectFromSocket(socketFamily string, host string, tlsConfig *tls.Config,
if err != nil {
return err
}
defer conn.Close()
_, err = conn.Write([]byte("UBCT1 stats_noreset\n"))
if err != nil {
return err
@ -399,7 +472,7 @@ func NewUnboundExporter(host string, ca string, cert string, key string) (*Unbou
}, nil
}
if ca == "" && cert == "" {
if ca == "" && cert == "" && key == "" {
return &UnboundExporter{
socketFamily: u.Scheme,
host: u.Host,
@ -407,7 +480,7 @@ func NewUnboundExporter(host string, ca string, cert string, key string) (*Unbou
}
/* Server authentication. */
caData, err := ioutil.ReadFile(ca)
caData, err := os.ReadFile(ca)
if err != nil {
return &UnboundExporter{}, err
}
@ -417,11 +490,11 @@ func NewUnboundExporter(host string, ca string, cert string, key string) (*Unbou
}
/* Client authentication. */
certData, err := ioutil.ReadFile(cert)
certData, err := os.ReadFile(cert)
if err != nil {
return &UnboundExporter{}, err
}
keyData, err := ioutil.ReadFile(key)
keyData, err := os.ReadFile(key)
if err != nil {
return &UnboundExporter{}, err
}
@ -456,7 +529,7 @@ func (e *UnboundExporter) Collect(ch chan<- prometheus.Metric) {
prometheus.GaugeValue,
1.0)
} else {
log.Error("Failed to scrape socket: %s", err)
_ = level.Error(log).Log("Failed to scrape socket: ", err)
ch <- prometheus.MustNewConstMetric(
unboundUpDesc,
prometheus.GaugeValue,
@ -475,7 +548,7 @@ func main() {
)
flag.Parse()
log.Info("Starting unbound_exporter")
_ = level.Info(log).Log("Starting unbound_exporter")
exporter, err := NewUnboundExporter(*unboundHost, *unboundCa, *unboundCert, *unboundKey)
if err != nil {
panic(err)
@ -484,7 +557,7 @@ func main() {
http.Handle(*metricsPath, promhttp.Handler())
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`
_, _ = w.Write([]byte(`
<html>
<head><title>Unbound Exporter</title></head>
<body>
@ -493,6 +566,7 @@ func main() {
</body>
</html>`))
})
log.Info("Listening on address:port => ", *listenAddress)
log.Fatal(http.ListenAndServe(*listenAddress, nil))
_ = level.Info(log).Log("Listening on address:port => ", *listenAddress)
_ = level.Error(log).Log(http.ListenAndServe(*listenAddress, nil))
os.Exit(1)
}

9
unbound_exporter_test.go Normal file
View File

@ -0,0 +1,9 @@
package main
import "testing"
func TestStub(t *testing.T) {
if 1 != 1 { //nolint
t.Fatal("Math is a lie. We should never have taught computers to think.")
}
}