├── .editorconfig ├── .github ├── release-drafter.yml ├── settings.yml ├── version-drafter.yml └── workflows │ ├── default.yml │ ├── release-drafter.yml │ └── release.yml ├── .gitignore ├── .golangci.yml ├── DCO ├── LICENSE ├── Makefile ├── README.md ├── cmd └── openvpn_exporter │ └── main.go ├── example ├── duplicate.status ├── error.status ├── version1.status ├── version2.status └── version3.status ├── go.mod ├── go.sum └── pkg ├── collector ├── exporter.go ├── openvpn.go └── openvpn_test.go ├── command └── command.go ├── config └── config.go ├── openvpn ├── parser.go └── parser_test.go └── version └── version.go /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | 10 | [Makefile] 11 | indent_style = tab 12 | indent_size = 4 13 | 14 | [*.go] 15 | indent_style = tab 16 | indent_size = 4 17 | 18 | [*.md] 19 | trim_trailing_whitespace = true -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name-template: 'v$NEXT_PATCH_VERSION' 2 | tag-template: 'v$NEXT_PATCH_VERSION' 3 | template: | 4 | $CHANGES 5 | categories: 6 | - title: 'Added' 7 | labels: 8 | - 'enhancement' 9 | - title: 'Fixed' 10 | labels: 11 | - 'bug' 12 | - title: 'Changed' 13 | labels: 14 | - 'changed' 15 | - title: 'Deprecated' 16 | labels: 17 | - 'deprecation' 18 | - title: 'Removed' 19 | labels: 20 | - 'removed' 21 | - title: 'Security' 22 | labels: 23 | - 'security' 24 | - title: 'Documentation' 25 | labels: 26 | - 'documentation' 27 | - title: 'Dependency Updates' 28 | labels: 29 | - 'dependencies' 30 | -------------------------------------------------------------------------------- /.github/settings.yml: -------------------------------------------------------------------------------- 1 | --- 2 | repository: 3 | name: openvpn_exporter 4 | description: 📊 Prometheus exporter for OpenVPN 5 | topics: prometheus-exporter, metrics, openvpn, prometheus, exporter 6 | 7 | private: false 8 | has_issues: true 9 | has_projects: false 10 | has_wiki: false 11 | has_downloads: false 12 | 13 | default_branch: master 14 | 15 | allow_squash_merge: true 16 | allow_merge_commit: true 17 | allow_rebase_merge: true 18 | delete_branch_on_merge: true 19 | 20 | labels: 21 | - name: bug 22 | color: d73a4a 23 | description: Something isn't working 24 | - name: documentation 25 | color: 0075ca 26 | description: Improvements or additions to documentation 27 | - name: duplicate 28 | color: cfd3d7 29 | description: This issue or pull request already exists 30 | - name: enhancement 31 | color: a2eeef 32 | description: New feature or request 33 | - name: good first issue 34 | color: 7057ff 35 | description: Good for newcomers 36 | - name: help wanted 37 | color: 008672 38 | description: Extra attention is needed 39 | - name: invalid 40 | color: e4e669 41 | description: This doesn't seem right 42 | - name: question 43 | color: d876e3 44 | description: Further information is requested 45 | - name: wontfix 46 | color: ffffff 47 | description: This will not be worked on 48 | - name: dependencies 49 | color: 0366d6 50 | description: Pull requests that update a dependency file 51 | - name: semver:patch 52 | color: 834dd1 53 | description: Change leading to a patch level version bump 54 | - name: semver:minor 55 | color: 764ab5 56 | description: Change leading to a minor level version bump 57 | - name: semver:major 58 | color: 6b3bad 59 | description: A (breaking) change leading to a major version bump 60 | 61 | ... 62 | -------------------------------------------------------------------------------- /.github/version-drafter.yml: -------------------------------------------------------------------------------- 1 | major-labels: ['semver:major'] 2 | minor-labels: ['semver:minor', 'enhancement'] 3 | patch-labels: ['semver:patch', 'bug'] -------------------------------------------------------------------------------- /.github/workflows/default.yml: -------------------------------------------------------------------------------- 1 | name: Test and Build 2 | on: 3 | - push 4 | - pull_request 5 | 6 | jobs: 7 | default: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - uses: actions/setup-go@v2 12 | with: 13 | go-version: '^1.14.1' 14 | - name: Tests 15 | run: | 16 | make fmt 17 | make vet 18 | make test 19 | make build 20 | - name: golangci-lint 21 | uses: golangci/golangci-lint-action@v2.3.0 22 | with: 23 | version: v1.33 24 | - name: Build release type artifacts 25 | run: make release 26 | -------------------------------------------------------------------------------- /.github/workflows/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name: Release Drafter 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | update_release_draft: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: calculate next version 13 | id: version 14 | uses: patrickjahns/version-drafter-action@v1 15 | env: 16 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 17 | - uses: release-drafter/release-drafter@v5 18 | env: 19 | GITHUB_TOKEN: ${{ secrets.PRIVATE_TOKEN }} 20 | with: 21 | version: ${{ steps.version.outputs.next-version }} 22 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release Actions 2 | on: 3 | push: 4 | tags: 5 | - '*' 6 | 7 | jobs: 8 | default: 9 | name: Upload release artifacts 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: actions/setup-go@v2 14 | with: 15 | go-version: '^1.14.1' 16 | - name: Set VERSION env 17 | run: echo VERSION=$(echo ${GITHUB_REF} | rev | cut -d'/' -f 1 | rev ) >> $GITHUB_ENV 18 | - name: Build artifacts 19 | run: make release VERSION=${{ env.VERSION }} 20 | - name: Upload artifactes to release 21 | uses: svenstaro/upload-release-action@v1-release 22 | with: 23 | repo_token: ${{ secrets.GITHUB_TOKEN }} 24 | file: dist/* 25 | tag: ${{ github.ref }} 26 | overwrite: true 27 | file_glob: true 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage.out 2 | /bin 3 | /dist 4 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | # This file contains all available configuration options 2 | # with their default values. 3 | 4 | # options for analysis running 5 | run: 6 | 7 | # timeout for analysis, e.g. 30s, 5m, default is 1m 8 | timeout: 5m 9 | 10 | # exit code when at least one issue was found, default is 1 11 | issues-exit-code: 1 12 | 13 | # include test files or not, default is true 14 | tests: true 15 | 16 | # list of build tags, all linters use it. Default is empty list. 17 | build-tags: 18 | 19 | # which dirs to skip: they won't be analyzed; 20 | # can use regexp here: generated.*, regexp is applied on full path; 21 | # default value is empty list, but next dirs are always skipped independently 22 | # from this option's value: 23 | # vendor$, third_party$, testdata$, examples$, Godeps$, builtin$ 24 | skip-dirs: 25 | # which files to skip: they will be analyzed, but issues from them 26 | # won't be reported. Default value is empty list, but there is 27 | # no need to include all autogenerated files, we confidently recognize 28 | # autogenerated files. If it's not please let us know. 29 | skip-files: 30 | # output configuration options 31 | output: 32 | # colored-line-number|line-number|json|tab|checkstyle, default is "colored-line-number" 33 | format: colored-line-number 34 | 35 | # print lines of code with issue, default is true 36 | print-issued-lines: true 37 | 38 | # print linter name in the end of issue text, default is true 39 | print-linter-name: true 40 | 41 | linters-settings: 42 | goimports: 43 | local-prefixes: github.com/patrickjahns/openvpn_exporter/pkg 44 | 45 | linters: 46 | enable: 47 | - deadcode 48 | - errcheck 49 | - goconst 50 | - gofmt 51 | - goimports 52 | - golint 53 | - gosimple 54 | - ineffassign 55 | - megacheck 56 | - misspell 57 | - structcheck 58 | - unconvert 59 | - unparam 60 | - varcheck 61 | - govet 62 | - unused 63 | - interfacer 64 | - typecheck 65 | 66 | issues: 67 | exclude: 68 | - Error return value of .*log\.Logger\)\.Log\x60 is not checked 69 | -------------------------------------------------------------------------------- /DCO: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 |
9 |14 | Developer Certificate of Origin 15 | Version 1.1 16 | 17 | Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 18 | 1 Letterman Drive 19 | Suite D4700 20 | San Francisco, CA, 94129 21 | 22 | Everyone is permitted to copy and distribute verbatim copies of this 23 | license document, but changing it is not allowed. 24 | 25 | 26 | Developer's Certificate of Origin 1.1 27 | 28 | By making a contribution to this project, I certify that: 29 | 30 | (a) The contribution was created in whole or in part by me and I 31 | have the right to submit it under the open source license 32 | indicated in the file; or 33 | 34 | (b) The contribution is based upon previous work that, to the best 35 | of my knowledge, is covered under an appropriate open source 36 | license and I have the right under that license to submit that 37 | work with modifications, whether created in whole or in part 38 | by me, under the same open source license (unless I am 39 | permitted to submit under a different license), as indicated 40 | in the file; or 41 | 42 | (c) The contribution was provided directly to me by some other 43 | person who certified (a), (b) or (c) and I have not modified 44 | it. 45 | 46 | (d) I understand and agree that this project and the contribution 47 | are public and that a record of the contribution (including all 48 | personal information I submit with it, including my sign-off) is 49 | maintained indefinitely and may be redistributed consistent with 50 | this project or the open source license(s) involved. 51 |52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Patrick Jahns 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL := bash 2 | NAME := openvpn_exporter 3 | IMPORT := github.com/patrickjahns/$(NAME) 4 | BIN := bin 5 | DIST := dist 6 | GO := go 7 | 8 | ifeq ($(OS), Windows_NT) 9 | EXECUTABLE := $(NAME).exe 10 | else 11 | EXECUTABLE := $(NAME) 12 | endif 13 | 14 | PACKAGES ?= $(shell go list ./...) 15 | SOURCES ?= $(shell find . -name "*.go" -type f) 16 | GENERATE ?= $(PACKAGES) 17 | 18 | ifndef DATE 19 | DATE := $(shell date -u '+%Y%m%d') 20 | endif 21 | 22 | ifndef VERSION 23 | VERSION ?= $(shell git rev-parse --short HEAD) 24 | endif 25 | 26 | ifndef REVISION 27 | REVISION ?= $(shell git rev-parse --short HEAD) 28 | endif 29 | 30 | LDFLAGS += -s -w 31 | LDFLAGS += -X "$(IMPORT)/pkg/version.Version=$(VERSION)" 32 | LDFLAGS += -X "$(IMPORT)/pkg/version.BuildDate=$(DATE)" 33 | LDFLAGS += -X "$(IMPORT)/pkg/version.Revision=$(REVISION)" 34 | 35 | .PHONY: all 36 | all: build 37 | 38 | .PHONY: clean 39 | clean: 40 | $(GO) clean -i ./... 41 | rm -rf $(BIN)/ 42 | rm -rf $(DIST)/ 43 | 44 | .PHONY: sync 45 | sync: 46 | $(GO) mod download 47 | 48 | .PHONY: fmt 49 | fmt: 50 | $(GO) fmt $(PACKAGES) 51 | 52 | .PHONY: vet 53 | vet: 54 | $(GO) vet $(PACKAGES) 55 | 56 | .PHONY: generate 57 | generate: 58 | $(GO) generate $(GENERATE) 59 | 60 | .PHONY: lint 61 | lint: 62 | @which golangci-lint > /dev/null; if [ $$? -ne 0 ]; then \ 63 | (echo "please install golangci-lint"; exit 1) \ 64 | fi 65 | golangci-lint run -v 66 | 67 | .PHONY: test 68 | test: 69 | @which goverage > /dev/null; if [ $$? -ne 0 ]; then \ 70 | GO111MODULE=off $(GO) get -u github.com/haya14busa/goverage; \ 71 | fi 72 | goverage -v -coverprofile coverage.out $(PACKAGES) 73 | 74 | .PHONY: build 75 | build: $(BIN)/$(EXECUTABLE) 76 | 77 | $(BIN)/$(EXECUTABLE): $(SOURCES) 78 | $(GO) build -i -v -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o $@ ./cmd/$(NAME) 79 | 80 | .PHONY: release 81 | release: release-dirs release-build release-checksums 82 | 83 | .PHONY: release-dirs 84 | release-dirs: 85 | mkdir -p $(DIST) 86 | 87 | .PHONY: release-build 88 | release-build: 89 | @which gox > /dev/null; if [ $$? -ne 0 ]; then \ 90 | GO111MODULE=off $(GO) get -u github.com/mitchellh/gox; \ 91 | fi 92 | gox -arch="386 amd64 arm" -osarch '!darwin/386' -verbose -ldflags '-w $(LDFLAGS)' -output="$(DIST)/$(EXECUTABLE)-{{.OS}}-{{.Arch}}" ./cmd/$(NAME) 93 | 94 | .PHONY: release-checksums 95 | release-checksums: 96 | cd $(DIST); $(foreach file, $(wildcard $(DIST)/$(EXECUTABLE)-*), sha256sum $(notdir $(file)) > $(notdir $(file)).sha256;) 97 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # openvpn_exporter 2 | 3 | [](https://github.com/patrickjahns/openvpn_exporter/releases) 4 | [](https://github.com/patrickjahns/openvpn_exporter/blob/master/LICENSE) 5 | [](https://github.com/patrickjahns/openvpn_exporter/actions?query=workflow%3A%22Test+and+Build%22) 6 | [](https://pkg.go.dev/github.com/patrickjahns/openvpn_exporter) 7 | [](https://goreportcard.com/report/github.com/patrickjahns/openvpn_exporter) 8 | 9 | Prometheus exporter for openvpn. Exposes the metrics from [openvpn status file](https://openvpn.net/community-resources/reference-manual-for-openvpn-2-4/) - supports status-version 1-3 10 | 11 | 12 | ## Installation 13 | 14 | For pre built binaries, please take a look at the [github releases](https://github.com/patrickjahns/openvpn_exporter/releases) 15 | 16 | ## Usage 17 | 18 | ```shell script 19 | $ ./bin/openvpn_exporter -h 20 | 21 | GLOBAL OPTIONS: 22 | --web.address value, --web.listen-address value Address to bind the metrics server (default: "0.0.0.0:9176") [$OPENVPN_EXPORTER_WEB_ADDRESS] 23 | --web.path value, --web.telemetry-path value Path to bind the metrics server (default: "/metrics") [$OPENVPN_EXPORTER_WEB_PATH] 24 | --web.root value Root path to exporter endpoints (default: "/") [$OPENVPN_EXPORTER_WEB_ROOT] 25 | --status-file value The OpenVPN status file(s) to export (example test:./example/version1.status ) [$OPENVPN_EXPORTER_STATUS_FILE] 26 | --disable-client-metrics Disables per client (bytes_received, bytes_sent, connected_since) metrics (default: false) [$OPENVPN_EXPORTER_DISABLE_CLIENT_METRICS] 27 | --enable-golang-metrics Enables golang and process metrics for the exporter) (default: false) [$OPENVPN_EXPORTER_ENABLE_GOLANG_METRICS] 28 | --log.level value Only log messages with given severity (default: "info") [$OPENVPN_EXPORTER_LOG_LEVEL] 29 | --help, -h Show help (default: false) 30 | --version, -v Prints the current version (default: false) 31 | ``` 32 | 33 | ### Example metrics 34 | 35 | ``` 36 | # HELP openvpn_build_info A metric with a constant '1' value labeled by version information 37 | # TYPE openvpn_build_info gauge 38 | openvpn_build_info{date="20200503",go="go1.14.2",revision="f84a7a5",version="f84a7a5"} 1 39 | # HELP openvpn_bytes_received Amount of data received via the connection 40 | # TYPE openvpn_bytes_received gauge 41 | openvpn_bytes_received{common_name="test1@localhost",server="v2"} 3871 42 | openvpn_bytes_received{common_name="test1@localhost",server="v3"} 3871 43 | openvpn_bytes_received{common_name="test@localhost",server="v2"} 3860 44 | openvpn_bytes_received{common_name="test@localhost",server="v3"} 3860 45 | openvpn_bytes_received{common_name="user1",server="v1"} 7.883858e+06 46 | openvpn_bytes_received{common_name="user2",server="v1"} 1.6732e+06 47 | openvpn_bytes_received{common_name="user3@test.de",server="v1"} 1.9602844e+07 48 | openvpn_bytes_received{common_name="user4",server="v1"} 582207 49 | # HELP openvpn_bytes_sent Amount of data sent via the connection 50 | # TYPE openvpn_bytes_sent gauge 51 | openvpn_bytes_sent{common_name="test1@localhost",server="v2"} 3924 52 | openvpn_bytes_sent{common_name="test1@localhost",server="v3"} 3924 53 | openvpn_bytes_sent{common_name="test@localhost",server="v2"} 3688 54 | openvpn_bytes_sent{common_name="test@localhost",server="v3"} 3688 55 | openvpn_bytes_sent{common_name="user1",server="v1"} 7.76234e+06 56 | openvpn_bytes_sent{common_name="user2",server="v1"} 2.065632e+06 57 | openvpn_bytes_sent{common_name="user3@test.de",server="v1"} 2.3599532e+07 58 | openvpn_bytes_sent{common_name="user4",server="v1"} 575193 59 | # HELP openvpn_collection_error Error occured during collection 60 | # TYPE openvpn_collection_error counter 61 | openvpn_collection_error{server="wrong"} 5 62 | # HELP openvpn_connected_since Unixtimestamp when the connection was established 63 | # TYPE openvpn_connected_since gauge 64 | openvpn_connected_since{common_name="test1@localhost",server="v2"} 1.58825494e+09 65 | openvpn_connected_since{common_name="test1@localhost",server="v3"} 1.58825494e+09 66 | openvpn_connected_since{common_name="test@localhost",server="v2"} 1.588254938e+09 67 | openvpn_connected_since{common_name="test@localhost",server="v3"} 1.588254938e+09 68 | openvpn_connected_since{common_name="user1",server="v1"} 1.587551802e+09 69 | openvpn_connected_since{common_name="user2",server="v1"} 1.587551812e+09 70 | openvpn_connected_since{common_name="user3@test.de",server="v1"} 1.587552165e+09 71 | openvpn_connected_since{common_name="user4",server="v1"} 1.587551814e+09 72 | # HELP openvpn_connections Amount of currently connected clients 73 | # TYPE openvpn_connections gauge 74 | openvpn_connections{server="v1"} 4 75 | openvpn_connections{server="v2"} 2 76 | openvpn_connections{server="v3"} 2 77 | # HELP openvpn_last_updated Unix timestamp when the last time the status was updated 78 | # TYPE openvpn_last_updated gauge 79 | openvpn_last_updated{server="v1"} 1.587665671e+09 80 | openvpn_last_updated{server="v2"} 1.588254944e+09 81 | openvpn_last_updated{server="v3"} 1.588254944e+09 82 | # HELP openvpn_max_bcast_mcast_queue_len MaxBcastMcastQueueLen of the server 83 | # TYPE openvpn_max_bcast_mcast_queue_len gauge 84 | openvpn_max_bcast_mcast_queue_len{server="v1"} 5 85 | openvpn_max_bcast_mcast_queue_len{server="v2"} 0 86 | openvpn_max_bcast_mcast_queue_len{server="v3"} 0 87 | # HELP openvpn_server_info A metric with a constant '1' value labeled by version information 88 | # TYPE openvpn_server_info gauge 89 | openvpn_server_info{arch="unknown",server="v1",version="unknown"} 1 90 | openvpn_server_info{arch="x86_64-pc-linux-gnu",server="v2",version="2.4.4"} 1 91 | openvpn_server_info{arch="x86_64-pc-linux-gnu",server="v3",version="2.4.4"} 1 92 | # HELP openvpn_start_time Unix timestamp of the start time of the exporter 93 | # TYPE openvpn_start_time gauge 94 | openvpn_start_time 1.588506393e+09 95 | ``` 96 | 97 | ## Development 98 | 99 | This project requires Go >= v1.14. To get started, clone the repository and run `make generate` 100 | 101 | ```shell script 102 | git clone https://github.com/patrickjahns/openvpn_exporter.git 103 | cd openvpn_exporter 104 | 105 | make generate 106 | make build 107 | 108 | ./cmd/openvpn_exporter -h 109 | ``` 110 | 111 | ### Building the project 112 | 113 | ```shell script 114 | make build 115 | ``` 116 | 117 | ### Running tests 118 | 119 | ```shell script 120 | make test 121 | ``` 122 | 123 | 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /cmd/openvpn_exporter/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/patrickjahns/openvpn_exporter/pkg/command" 7 | ) 8 | 9 | func main() { 10 | if err := command.Run(); err != nil { 11 | os.Exit(1) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/duplicate.status: -------------------------------------------------------------------------------- 1 | OpenVPN CLIENT LIST 2 | Updated,Fri Jun 5 09:03:05 2020 3 | Common Name,Real Address,Bytes Received,Bytes Sent,Connected Since 4 | foo_foo,::ffff:1.1.1.1,898582,674355,Thu Jun 4 08:50:24 2020 5 | bar_bar,::ffff:2.2.2.2,1550890,1156181,Wed Jun 3 14:00:04 2020 6 | bar_bar,::ffff:2.2.2.2,1550890,1156181,Wed Jun 3 14:00:04 2020 7 | UNDEF,::ffff:3.3.3.3,527,10441,Fri Jun 5 09:02:17 2020 8 | UNDEF,::ffff:4.4.4.4,527,8830,Fri Jun 5 09:02:39 2020 9 | GLOBAL STATS 10 | Max bcast/mcast queue length,0 11 | END 12 | -------------------------------------------------------------------------------- /example/error.status: -------------------------------------------------------------------------------- 1 | OpenVPN CLIENT LIST 2 | Updated,Fri Jun 5 09:03:05 2020 3 | Common Name,Real Address,Bytes Received,Bytes Sent,Connected Since 4 | foo_foo,::ffff:1.1.1.1,898582,674355,Thu Jun 4 08:50:24 2020 5 | bar_bar,::ffff:2.2.2.2,1550890,1156181,Wed Jun 3 14:00:04 2020 6 | UNDEF,::ffff:3.3.3.3,527,10441,Fri Jun 5 09:02:17 2020 7 | UNDEF,::ffff:4.4.4.4,527,8830,Fri Jun 5 09:02:39 2020 8 | GLOBAL STATS 9 | Max bcast/mcast queue length,0 10 | END 11 | -------------------------------------------------------------------------------- /example/version1.status: -------------------------------------------------------------------------------- 1 | OpenVPN CLIENT LIST 2 | Updated,Thu Apr 23 20:14:31 2020 3 | Common Name,Real Address,Bytes Received,Bytes Sent,Connected Since 4 | user1,1.2.3.4:60102,7883858,7762340,Wed Apr 22 12:36:42 2020 5 | user2,1.2.3.5:50976,1673200,2065632,Wed Apr 22 12:36:52 2020 6 | user3@test.de,1.2.3.6:57688,19602844,23599532,Wed Apr 22 12:42:45 2020 7 | user4,1.2.3.7:40832,582207,575193,Wed Apr 22 12:36:54 2020 8 | ROUTING TABLE 9 | Virtual Address,Common Name,Real Address,Last Ref 10 | 10.240.1.222,user4,1.2.3.7:40832,Wed Apr 22 12:36:56 2020 11 | 10.240.10.126,user1,1.2.3.4:50976,Thu Apr 23 18:44:56 2020 12 | 10.240.79.134,user2,1.2.3.5:60102,Thu Apr 23 20:14:30 2020 13 | 10.240.37.214,user3@test.de,1.2.3.6:57688,Thu Apr 23 20:14:16 2020 14 | GLOBAL STATS 15 | Max bcast/mcast queue length,5 16 | END 17 | -------------------------------------------------------------------------------- /example/version2.status: -------------------------------------------------------------------------------- 1 | TITLE,OpenVPN 2.4.4 x86_64-pc-linux-gnu [SSL (OpenSSL)] [LZO] [LZ4] [EPOLL] [PKCS11] [MH/PKTINFO] [AEAD] built on May 14 2019 2 | TIME,Thu Apr 30 13:55:44 2020,1588254944 3 | HEADER,CLIENT_LIST,Common Name,Real Address,Virtual Address,Virtual IPv6 Address,Bytes Received,Bytes Sent,Connected Since,Connected Since (time_t),Username,Client ID,Peer ID 4 | CLIENT_LIST,test@localhost,1.2.3.4:54190,10.80.0.65,,3860,3688,Thu Apr 30 13:55:38 2020,1588254938,test@localhost,0,0 5 | CLIENT_LIST,test1@localhost,1.2.3.5:51053,10.68.0.25,,3871,3924,Thu Apr 30 13:55:40 2020,1588254940,test1@localhost,1,1 6 | HEADER,ROUTING_TABLE,Virtual Address,Common Name,Real Address,Last Ref,Last Ref (time_t) 7 | ROUTING_TABLE,10.80.0.65,test@localhost,1.2.3.4:54190,Thu Apr 30 13:55:40 2020,1588254940 8 | ROUTING_TABLE,10.68.0.25,test1@localhost,1.2.3.5:51053,Thu Apr 30 13:55:42 2020,1588254942 9 | GLOBAL_STATS,Max bcast/mcast queue length,0 10 | END 11 | -------------------------------------------------------------------------------- /example/version3.status: -------------------------------------------------------------------------------- 1 | TITLE OpenVPN 2.4.4 x86_64-pc-linux-gnu [SSL (OpenSSL)] [LZO] [LZ4] [EPOLL] [PKCS11] [MH/PKTINFO] [AEAD] built on May 14 2019 2 | TIME Thu Apr 30 13:55:44 2020 1588254944 3 | HEADER CLIENT_LIST Common Name Real Address Virtual Address Virtual IPv6 Address Bytes Received Bytes Sent Connected Since Connected Since (time_t) Username Client ID Peer ID 4 | CLIENT_LIST test@localhost 1.2.3.4:54190 10.80.0.65 3860 3688 Thu Apr 30 13:55:38 2020 1588254938 test@localhost 0 0 5 | CLIENT_LIST test1@localhost 1.2.3.5:51053 10.68.0.25 3871 3924 Thu Apr 30 13:55:40 2020 1588254940 test1@localhost 1 1 6 | HEADER ROUTING_TABLE Virtual Address Common Name Real Address Last Ref Last Ref (time_t) 7 | ROUTING_TABLE 10.80.0.65 test@localhost 1.2.3.4:54190 Thu Apr 30 13:55:40 2020 1588254940 8 | ROUTING_TABLE 10.68.0.25 test1@localhost 1.2.3.5:51053 Thu Apr 30 13:55:42 2020 1588254942 9 | GLOBAL_STATS Max bcast/mcast queue length 0 10 | END 11 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/patrickjahns/openvpn_exporter 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/go-kit/kit v0.9.0 7 | github.com/prometheus/client_golang v1.5.1 8 | github.com/urfave/cli/v2 v2.2.0 9 | ) 10 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 2 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 3 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 4 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 5 | github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 6 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 7 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 8 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 9 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 10 | github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= 11 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 12 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= 13 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 14 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 15 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 16 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 17 | github.com/go-kit/kit v0.9.0 h1:wDJmvq38kDhkVxi50ni9ykkdUr1PKgqKOoi01fa0Mdk= 18 | github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 19 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 20 | github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA= 21 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 22 | github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= 23 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 24 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 25 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 26 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 27 | github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= 28 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 29 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 30 | github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= 31 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 32 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 33 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 34 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 35 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 36 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 37 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= 38 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 39 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 40 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 41 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 42 | github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= 43 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 44 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 45 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 46 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 47 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 48 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 49 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 50 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 51 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 52 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 53 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 54 | github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= 55 | github.com/prometheus/client_golang v1.5.1 h1:bdHYieyGlH+6OLEk2YQha8THib30KP0/yD0YH9m6xcA= 56 | github.com/prometheus/client_golang v1.5.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= 57 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 58 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 59 | github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= 60 | github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 61 | github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 62 | github.com/prometheus/common v0.9.1 h1:KOMtN28tlbam3/7ZKEYKHhKoJZYYj3gMH4uc62x7X7U= 63 | github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= 64 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 65 | github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 66 | github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLkt8= 67 | github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= 68 | github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= 69 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 70 | github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= 71 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 72 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 73 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 74 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 75 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 76 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 77 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 78 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 79 | github.com/urfave/cli/v2 v2.2.0 h1:JTTnM6wKzdA0Jqodd966MVj4vWbbquZykeX1sKbe2C4= 80 | github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= 81 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 82 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 83 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 84 | golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 85 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 86 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 87 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 88 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 89 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 90 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 91 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 92 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82 h1:ywK/j/KkyTHcdyYSZNXGjMwgmDSfjglYZ3vStQ/gSCU= 93 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 94 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 95 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 96 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 97 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 98 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 99 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 100 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 101 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 102 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 103 | gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 104 | -------------------------------------------------------------------------------- /pkg/collector/exporter.go: -------------------------------------------------------------------------------- 1 | package collector 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/go-kit/kit/log" 7 | "github.com/go-kit/kit/log/level" 8 | "github.com/prometheus/client_golang/prometheus" 9 | ) 10 | 11 | const ( 12 | // namespace defines the namespace for the exporter. 13 | namespace = "openvpn" 14 | ) 15 | 16 | // GeneralCollector collects metrics, mostly runtime, about this exporter in general. 17 | type GeneralCollector struct { 18 | logger log.Logger 19 | version string 20 | revision string 21 | buildDate string 22 | goVersion string 23 | started time.Time 24 | 25 | StartTime *prometheus.Desc 26 | BuildInfo *prometheus.Desc 27 | } 28 | 29 | // NewGeneralCollector returns a new GeneralCollector. 30 | func NewGeneralCollector(logger log.Logger, version string, revision string, buildDate string, goVersion string, started time.Time) *GeneralCollector { 31 | return &GeneralCollector{ 32 | logger: logger, 33 | version: version, 34 | revision: revision, 35 | buildDate: buildDate, 36 | goVersion: goVersion, 37 | started: started, 38 | 39 | StartTime: prometheus.NewDesc( 40 | prometheus.BuildFQName(namespace, "", "start_time"), 41 | "Unix timestamp of the start time of the exporter", 42 | nil, 43 | nil, 44 | ), 45 | 46 | BuildInfo: prometheus.NewDesc( 47 | prometheus.BuildFQName(namespace, "", "build_info"), 48 | "A metric with a constant '1' value labeled by version information", 49 | []string{"version", "revision", "date", "go"}, 50 | nil, 51 | ), 52 | } 53 | } 54 | 55 | // Describe sends the super-set of all possible descriptors of metrics collected by this Collector. 56 | func (c *GeneralCollector) Describe(ch chan<- *prometheus.Desc) { 57 | ch <- c.StartTime 58 | ch <- c.BuildInfo 59 | } 60 | 61 | // Collect is called by the Prometheus registry when collecting metrics. 62 | func (c *GeneralCollector) Collect(ch chan<- prometheus.Metric) { 63 | level.Debug(c.logger).Log( 64 | "started", c.started.Unix(), 65 | "version", c.version, 66 | "revision", c.revision, 67 | "buildDate", c.buildDate, 68 | "goVersion", c.goVersion, 69 | "started", c.started, 70 | ) 71 | ch <- prometheus.MustNewConstMetric( 72 | c.StartTime, 73 | prometheus.GaugeValue, 74 | float64(c.started.Unix()), 75 | ) 76 | 77 | ch <- prometheus.MustNewConstMetric( 78 | c.BuildInfo, 79 | prometheus.GaugeValue, 80 | 1.0, 81 | c.version, 82 | c.revision, 83 | c.buildDate, 84 | c.goVersion, 85 | ) 86 | } 87 | -------------------------------------------------------------------------------- /pkg/collector/openvpn.go: -------------------------------------------------------------------------------- 1 | package collector 2 | 3 | import ( 4 | "github.com/go-kit/kit/log" 5 | "github.com/go-kit/kit/log/level" 6 | "github.com/prometheus/client_golang/prometheus" 7 | 8 | "github.com/patrickjahns/openvpn_exporter/pkg/openvpn" 9 | ) 10 | 11 | // OpenVPNCollector collects metrics from openvpn status files 12 | type OpenVPNCollector struct { 13 | logger log.Logger 14 | collectClientMetrics bool 15 | OpenVPNServer []OpenVPNServer 16 | LastUpdated *prometheus.Desc 17 | ConnectedClients *prometheus.Desc 18 | BytesReceived *prometheus.Desc 19 | BytesSent *prometheus.Desc 20 | ConnectedSince *prometheus.Desc 21 | MaxBcastMcastQueueLen *prometheus.Desc 22 | ServerInfo *prometheus.Desc 23 | CollectionError *prometheus.CounterVec 24 | } 25 | 26 | // OpenVPNServer contains information of which servers will be scraped 27 | type OpenVPNServer struct { 28 | Name string 29 | StatusFile string 30 | ParseError float64 31 | } 32 | 33 | // NewOpenVPNCollector returns a new OpenVPNCollector 34 | func NewOpenVPNCollector(logger log.Logger, openVPNServer []OpenVPNServer, collectClientMetrics bool) *OpenVPNCollector { 35 | return &OpenVPNCollector{ 36 | logger: logger, 37 | OpenVPNServer: openVPNServer, 38 | collectClientMetrics: collectClientMetrics, 39 | 40 | LastUpdated: prometheus.NewDesc( 41 | prometheus.BuildFQName(namespace, "", "last_updated"), 42 | "Unix timestamp when the last time the status was updated", 43 | []string{"server"}, 44 | nil, 45 | ), 46 | ConnectedClients: prometheus.NewDesc( 47 | prometheus.BuildFQName(namespace, "", "connections"), 48 | "Amount of currently connected clients", 49 | []string{"server"}, 50 | nil, 51 | ), 52 | MaxBcastMcastQueueLen: prometheus.NewDesc( 53 | prometheus.BuildFQName(namespace, "", "max_bcast_mcast_queue_len"), 54 | "MaxBcastMcastQueueLen of the server", 55 | []string{"server"}, 56 | nil, 57 | ), 58 | BytesReceived: prometheus.NewDesc( 59 | prometheus.BuildFQName(namespace, "", "bytes_received"), 60 | "Amount of data received via the connection", 61 | []string{"server", "common_name"}, 62 | nil, 63 | ), 64 | BytesSent: prometheus.NewDesc( 65 | prometheus.BuildFQName(namespace, "", "bytes_sent"), 66 | "Amount of data sent via the connection", 67 | []string{"server", "common_name"}, 68 | nil, 69 | ), 70 | ConnectedSince: prometheus.NewDesc( 71 | prometheus.BuildFQName(namespace, "", "connected_since"), 72 | "Unixtimestamp when the connection was established", 73 | []string{"server", "common_name"}, 74 | nil, 75 | ), 76 | ServerInfo: prometheus.NewDesc( 77 | prometheus.BuildFQName(namespace, "", "server_info"), 78 | "A metric with a constant '1' value labeled by version information", 79 | []string{"server", "version", "arch"}, 80 | nil, 81 | ), 82 | CollectionError: prometheus.NewCounterVec( 83 | prometheus.CounterOpts{ 84 | Name: prometheus.BuildFQName(namespace, "", "collection_error"), 85 | Help: "Error occurred during collection", 86 | }, 87 | []string{"server"}, 88 | ), 89 | } 90 | } 91 | 92 | // Describe sends the super-set of all possible descriptors of metrics collected by this Collector. 93 | func (c *OpenVPNCollector) Describe(ch chan<- *prometheus.Desc) { 94 | ch <- c.LastUpdated 95 | ch <- c.ConnectedClients 96 | ch <- c.MaxBcastMcastQueueLen 97 | ch <- c.ServerInfo 98 | if c.collectClientMetrics { 99 | ch <- c.BytesSent 100 | ch <- c.BytesReceived 101 | ch <- c.ConnectedSince 102 | } 103 | c.CollectionError.Describe(ch) 104 | } 105 | 106 | // Collect is called by the Prometheus registry when collecting metrics. 107 | func (c *OpenVPNCollector) Collect(ch chan<- prometheus.Metric) { 108 | for _, ovpn := range c.OpenVPNServer { 109 | c.collect(ovpn, ch) 110 | } 111 | } 112 | 113 | func (c *OpenVPNCollector) collect(ovpn OpenVPNServer, ch chan<- prometheus.Metric) { 114 | level.Debug(c.logger).Log( 115 | "statusFile", ovpn.StatusFile, 116 | "name", ovpn.Name, 117 | ) 118 | status, err := openvpn.ParseFile(ovpn.StatusFile) 119 | if err != nil { 120 | level.Warn(c.logger).Log( 121 | "msg", "error parsing statusfile", 122 | "err", err, 123 | ) 124 | c.CollectionError.WithLabelValues(ovpn.Name).Add(1) 125 | c.CollectionError.Collect(ch) 126 | return 127 | } 128 | 129 | connectedClients := 0 130 | var clientCommonNames []string 131 | for _, client := range status.ClientList { 132 | connectedClients++ 133 | level.Debug(c.logger).Log( 134 | "commonName", client.CommonName, 135 | "connectedSince", client.ConnectedSince.Unix(), 136 | "bytesReceived", client.BytesReceived, 137 | "bytesSent", client.BytesSent, 138 | ) 139 | if c.collectClientMetrics { 140 | if client.CommonName == "UNDEF" { 141 | continue 142 | } 143 | if contains(clientCommonNames, client.CommonName) { 144 | level.Warn(c.logger).Log( 145 | "msg", "duplicate client common name in statusfile - duplicate metric dropped", 146 | "commonName", client.CommonName, 147 | ) 148 | continue 149 | } 150 | clientCommonNames = append(clientCommonNames, client.CommonName) 151 | ch <- prometheus.MustNewConstMetric( 152 | c.BytesReceived, 153 | prometheus.GaugeValue, 154 | client.BytesReceived, 155 | ovpn.Name, client.CommonName, 156 | ) 157 | ch <- prometheus.MustNewConstMetric( 158 | c.BytesSent, 159 | prometheus.GaugeValue, 160 | client.BytesSent, 161 | ovpn.Name, client.CommonName, 162 | ) 163 | ch <- prometheus.MustNewConstMetric( 164 | c.ConnectedSince, 165 | prometheus.GaugeValue, 166 | float64(client.ConnectedSince.Unix()), 167 | ovpn.Name, client.CommonName, 168 | ) 169 | } 170 | } 171 | level.Debug(c.logger).Log( 172 | "updatedAt", status.UpdatedAt, 173 | "connectedClients", connectedClients, 174 | "maxBcastMcastQueueLen", status.GlobalStats.MaxBcastMcastQueueLen, 175 | ) 176 | ch <- prometheus.MustNewConstMetric( 177 | c.ConnectedClients, 178 | prometheus.GaugeValue, 179 | float64(connectedClients), 180 | ovpn.Name, 181 | ) 182 | ch <- prometheus.MustNewConstMetric( 183 | c.LastUpdated, 184 | prometheus.GaugeValue, 185 | float64(status.UpdatedAt.Unix()), 186 | ovpn.Name, 187 | ) 188 | ch <- prometheus.MustNewConstMetric( 189 | c.MaxBcastMcastQueueLen, 190 | prometheus.GaugeValue, 191 | float64(status.GlobalStats.MaxBcastMcastQueueLen), 192 | ovpn.Name, 193 | ) 194 | ch <- prometheus.MustNewConstMetric( 195 | c.ServerInfo, 196 | prometheus.GaugeValue, 197 | 1.0, 198 | ovpn.Name, 199 | status.ServerInfo.Version, 200 | status.ServerInfo.Arch, 201 | ) 202 | } 203 | 204 | func contains(list []string, item string) bool { 205 | for _, e := range list { 206 | if e == item { 207 | return true 208 | } 209 | } 210 | return false 211 | } 212 | -------------------------------------------------------------------------------- /pkg/collector/openvpn_test.go: -------------------------------------------------------------------------------- 1 | package collector 2 | 3 | import "testing" 4 | 5 | var containsTestCases = []struct { 6 | scenarioName string 7 | list []string 8 | element string 9 | expected bool 10 | }{ 11 | {"contains element", []string{"bar", "foo"}, "bar", true}, 12 | {"does not contain element", []string{"foo", "bar"}, "baz", false}, 13 | } 14 | 15 | func TestContainsFunction(t *testing.T) { 16 | for _, tt := range containsTestCases { 17 | t.Run(tt.scenarioName, func(t *testing.T) { 18 | if contains(tt.list, tt.element) != tt.expected { 19 | t.Errorf("Unexpected result") 20 | } 21 | }) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /pkg/command/command.go: -------------------------------------------------------------------------------- 1 | package command 2 | 3 | import ( 4 | "net/http" 5 | "os" 6 | "strings" 7 | 8 | "github.com/go-kit/kit/log" 9 | "github.com/go-kit/kit/log/level" 10 | "github.com/prometheus/client_golang/prometheus" 11 | "github.com/prometheus/client_golang/prometheus/promhttp" 12 | "github.com/urfave/cli/v2" 13 | 14 | "github.com/patrickjahns/openvpn_exporter/pkg/collector" 15 | "github.com/patrickjahns/openvpn_exporter/pkg/config" 16 | "github.com/patrickjahns/openvpn_exporter/pkg/version" 17 | ) 18 | 19 | // Run parses the command line arguments and executes the program. 20 | func Run() error { 21 | 22 | app := &cli.App{ 23 | Name: "openvpn_exporter", 24 | Version: version.Info(), 25 | Usage: "OpenVPN exporter", 26 | Authors: []*cli.Author{ 27 | { 28 | Name: "Patrick Jahns", 29 | Email: "github@patrickjahns.de", 30 | }, 31 | }, 32 | } 33 | cfg := config.Load() 34 | cli.HelpFlag = &cli.BoolFlag{ 35 | Name: "help", 36 | Aliases: []string{"h"}, 37 | Usage: "Show help", 38 | } 39 | 40 | cli.VersionFlag = &cli.BoolFlag{ 41 | Name: "version", 42 | Aliases: []string{"v"}, 43 | Usage: "Prints the current version", 44 | } 45 | 46 | app.Flags = []cli.Flag{ 47 | &cli.StringFlag{ 48 | Name: "web.address", 49 | Aliases: []string{"web.listen-address"}, 50 | Value: "0.0.0.0:9176", 51 | Usage: "Address to bind the metrics server", 52 | EnvVars: []string{"OPENVPN_EXPORTER_WEB_ADDRESS"}, 53 | Destination: &cfg.Server.Addr, 54 | }, 55 | &cli.StringFlag{ 56 | Name: "web.path", 57 | Aliases: []string{"web.telemetry-path"}, 58 | Value: "/metrics", 59 | Usage: "Path to bind the metrics server", 60 | EnvVars: []string{"OPENVPN_EXPORTER_WEB_PATH"}, 61 | Destination: &cfg.Server.Path, 62 | }, 63 | &cli.StringFlag{ 64 | Name: "web.root", 65 | Value: "/", 66 | Usage: "Root path to exporter endpoints", 67 | EnvVars: []string{"OPENVPN_EXPORTER_WEB_ROOT"}, 68 | Destination: &cfg.Server.Root, 69 | }, 70 | &cli.StringSliceFlag{ 71 | Name: "status-file", 72 | Usage: "The OpenVPN status file(s) to export (example test:./example/version1.status )", 73 | EnvVars: []string{"OPENVPN_EXPORTER_STATUS_FILE"}, 74 | Required: true, 75 | }, 76 | &cli.BoolFlag{ 77 | Name: "disable-client-metrics", 78 | Usage: "Disables per client (bytes_received, bytes_sent, connected_since) metrics", 79 | EnvVars: []string{"OPENVPN_EXPORTER_DISABLE_CLIENT_METRICS"}, 80 | }, 81 | &cli.BoolFlag{ 82 | Name: "enable-golang-metrics", 83 | Value: false, 84 | Usage: "Enables golang and process metrics for the exporter) ", 85 | EnvVars: []string{"OPENVPN_EXPORTER_ENABLE_GOLANG_METRICS"}, 86 | Destination: &cfg.ExportGoMetrics, 87 | }, 88 | &cli.StringFlag{ 89 | Name: "log.level", 90 | Value: "info", 91 | Usage: "Only log messages with given severity", 92 | EnvVars: []string{"OPENVPN_EXPORTER_LOG_LEVEL"}, 93 | Destination: &cfg.Logs.Level, 94 | }, 95 | } 96 | 97 | app.Before = func(c *cli.Context) error { 98 | cfg.StatusCollector.StatusFile = c.StringSlice("status-file") 99 | cfg.StatusCollector.ExportClientMetrics = !c.Bool("disable-client-metrics") 100 | return nil 101 | } 102 | 103 | app.Action = func(c *cli.Context) error { 104 | return run(cfg) 105 | } 106 | 107 | return app.Run(os.Args) 108 | } 109 | 110 | func run(cfg *config.Config) error { 111 | // setup logging 112 | logger := setupLogging(cfg) 113 | level.Info(logger).Log( 114 | "msg", "Starting openvpn_exporter", 115 | "version", version.Version, 116 | "revision", version.Revision, 117 | "buildDate", version.BuildDate, 118 | "goVersion", version.GoVersion, 119 | ) 120 | var openVPServers []collector.OpenVPNServer 121 | r := prometheus.NewRegistry() 122 | if cfg.ExportGoMetrics { 123 | // enable profiler 124 | r.MustRegister(prometheus.NewProcessCollector(prometheus.ProcessCollectorOpts{})) 125 | r.MustRegister(prometheus.NewGoCollector()) 126 | } 127 | r.MustRegister(collector.NewGeneralCollector( 128 | logger, 129 | version.Version, 130 | version.Revision, 131 | version.BuildDate, 132 | version.GoVersion, 133 | version.Started, 134 | )) 135 | for _, statusFile := range cfg.StatusCollector.StatusFile { 136 | serverName, statusFile := parseStatusFileSlice(statusFile) 137 | level.Info(logger).Log( 138 | "msg", "registering collector for", 139 | "serverName", serverName, 140 | "statusFile", statusFile, 141 | ) 142 | openVPServers = append(openVPServers, collector.OpenVPNServer{Name: serverName, StatusFile: statusFile, ParseError: 0}) 143 | } 144 | r.MustRegister(collector.NewOpenVPNCollector( 145 | logger, 146 | openVPServers, 147 | cfg.StatusCollector.ExportClientMetrics, 148 | )) 149 | 150 | http.Handle(cfg.Server.Path, 151 | promhttp.HandlerFor(r, promhttp.HandlerOpts{}), 152 | ) 153 | http.HandleFunc(cfg.Server.Root, func(w http.ResponseWriter, r *http.Request) { 154 | _, _ = w.Write([]byte(` 155 |