diff --git a/README.md b/README.md index 45b3ccd..799a2c8 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ GLOBAL OPTIONS: --web.root value Root path to exporter endpoints (default: "/") [$OPENVPN_EXPORTER_WEB_ROOT] --status-file value The OpenVPN status file(s) to export (example test:./example/version1.status ) [$OPENVPN_EXPORTER_STATUS_FILE] --disable-client-metrics Disables per client (bytes_received, bytes_sent, connected_since) metrics (default: false) [$OPENVPN_EXPORTER_DISABLE_CLIENT_METRICS] + --allow-duplicate-cn Allow multiple connections with the same common name distinguished by the Peer ID (only works with version 2 and 3 status types) (default: false) [$OPENVPN_EXPORTER_ALLOW_DUPLICATE_CN] --enable-golang-metrics Enables golang and process metrics for the exporter) (default: false) [$OPENVPN_EXPORTER_ENABLE_GOLANG_METRICS] --log.level value Only log messages with given severity (default: "info") [$OPENVPN_EXPORTER_LOG_LEVEL] --help, -h Show help (default: false) diff --git a/example/version2.status b/example/version2.status index 44d3bc2..7291f34 100644 --- a/example/version2.status +++ b/example/version2.status @@ -2,7 +2,8 @@ TITLE,OpenVPN 2.4.4 x86_64-pc-linux-gnu [SSL (OpenSSL)] [LZO] [LZ4] [EPOLL] [PKC TIME,Thu Apr 30 13:55:44 2020,1588254944 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 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 -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 +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,1,1 +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,2,2 HEADER,ROUTING_TABLE,Virtual Address,Common Name,Real Address,Last Ref,Last Ref (time_t) ROUTING_TABLE,10.80.0.65,test@localhost,1.2.3.4:54190,Thu Apr 30 13:55:40 2020,1588254940 ROUTING_TABLE,10.68.0.25,test1@localhost,1.2.3.5:51053,Thu Apr 30 13:55:42 2020,1588254942 diff --git a/example/version3.status b/example/version3.status index c05956e..dd5cd96 100644 --- a/example/version3.status +++ b/example/version3.status @@ -2,7 +2,8 @@ TITLE OpenVPN 2.4.4 x86_64-pc-linux-gnu [SSL (OpenSSL)] [LZO] [LZ4] [EPOLL] [PKC TIME Thu Apr 30 13:55:44 2020 1588254944 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 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 -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 +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 1 1 +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 2 2 HEADER ROUTING_TABLE Virtual Address Common Name Real Address Last Ref Last Ref (time_t) ROUTING_TABLE 10.80.0.65 test@localhost 1.2.3.4:54190 Thu Apr 30 13:55:40 2020 1588254940 ROUTING_TABLE 10.68.0.25 test1@localhost 1.2.3.5:51053 Thu Apr 30 13:55:42 2020 1588254942 diff --git a/pkg/collector/openvpn.go b/pkg/collector/openvpn.go index fc54972..60b0fe5 100644 --- a/pkg/collector/openvpn.go +++ b/pkg/collector/openvpn.go @@ -4,6 +4,7 @@ import ( "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" "github.com/prometheus/client_golang/prometheus" + "strconv" "github.com/patrickjahns/openvpn_exporter/pkg/openvpn" ) @@ -12,6 +13,7 @@ import ( type OpenVPNCollector struct { logger log.Logger collectClientMetrics bool + allowDuplicateCn bool OpenVPNServer []OpenVPNServer LastUpdated *prometheus.Desc ConnectedClients *prometheus.Desc @@ -31,11 +33,12 @@ type OpenVPNServer struct { } // NewOpenVPNCollector returns a new OpenVPNCollector -func NewOpenVPNCollector(logger log.Logger, openVPNServer []OpenVPNServer, collectClientMetrics bool) *OpenVPNCollector { +func NewOpenVPNCollector(logger log.Logger, openVPNServer []OpenVPNServer, collectClientMetrics bool, allowDuplicateCn bool) *OpenVPNCollector { return &OpenVPNCollector{ logger: logger, OpenVPNServer: openVPNServer, collectClientMetrics: collectClientMetrics, + allowDuplicateCn: allowDuplicateCn, LastUpdated: prometheus.NewDesc( prometheus.BuildFQName(namespace, "", "last_updated"), @@ -58,19 +61,19 @@ func NewOpenVPNCollector(logger log.Logger, openVPNServer []OpenVPNServer, colle BytesReceived: prometheus.NewDesc( prometheus.BuildFQName(namespace, "", "bytes_received"), "Amount of data received via the connection", - []string{"server", "common_name"}, + []string{"server", "common_name", "unique_id"}, nil, ), BytesSent: prometheus.NewDesc( prometheus.BuildFQName(namespace, "", "bytes_sent"), "Amount of data sent via the connection", - []string{"server", "common_name"}, + []string{"server", "common_name", "unique_id"}, nil, ), ConnectedSince: prometheus.NewDesc( prometheus.BuildFQName(namespace, "", "connected_since"), "Unixtimestamp when the connection was established", - []string{"server", "common_name"}, + []string{"server", "common_name", "unique_id"}, nil, ), ServerInfo: prometheus.NewDesc( @@ -127,6 +130,7 @@ func (c *OpenVPNCollector) collect(ovpn OpenVPNServer, ch chan<- prometheus.Metr } connectedClients := 0 + hasFacedZeroPeerId := false var clientCommonNames []string for _, client := range status.ClientList { connectedClients++ @@ -141,30 +145,46 @@ func (c *OpenVPNCollector) collect(ovpn OpenVPNServer, ch chan<- prometheus.Metr continue } if contains(clientCommonNames, client.CommonName) { - level.Warn(c.logger).Log( - "msg", "duplicate client common name in statusfile - duplicate metric dropped", - "commonName", client.CommonName, - ) - continue + if !c.allowDuplicateCn { + level.Warn(c.logger).Log( + "msg", "duplicate client common name in statusfile - duplicate metric dropped (use --allow-duplicate-cn flag)", + "commonName", client.CommonName, + ) + continue + } + if c.allowDuplicateCn && client.PeerID == -1 { + level.Warn(c.logger).Log( + "msg", "allow-duplicate-cn flag with a version 1 statusfile - duplicate metric dropped (use version 2 or 3)", + "commonName", client.CommonName, + ) + continue + } } clientCommonNames = append(clientCommonNames, client.CommonName) + uniqueId := client.PeerID + if uniqueId == 0 { // In TCP mode, PeerID is always 0 + if hasFacedZeroPeerId { // Use ClientID only if a 0 PeerID is matched twice (maybe it's the first item and we are in UDP mode) + uniqueId = -client.ClientID - 1 // ClientID starts at 0; But it may be duplicated with another 0 PeerID + } + hasFacedZeroPeerId = true + } ch <- prometheus.MustNewConstMetric( c.BytesReceived, - prometheus.GaugeValue, + prometheus.CounterValue, client.BytesReceived, - ovpn.Name, client.CommonName, + ovpn.Name, client.CommonName, strconv.FormatInt(uniqueId, 10), ) ch <- prometheus.MustNewConstMetric( c.BytesSent, - prometheus.GaugeValue, + prometheus.CounterValue, client.BytesSent, - ovpn.Name, client.CommonName, + ovpn.Name, client.CommonName, strconv.FormatInt(uniqueId, 10), ) ch <- prometheus.MustNewConstMetric( c.ConnectedSince, prometheus.GaugeValue, float64(client.ConnectedSince.Unix()), - ovpn.Name, client.CommonName, + ovpn.Name, client.CommonName, strconv.FormatInt(uniqueId, 10), ) } } diff --git a/pkg/command/command.go b/pkg/command/command.go index ae3d9ca..87588e8 100644 --- a/pkg/command/command.go +++ b/pkg/command/command.go @@ -78,6 +78,12 @@ func Run() error { Usage: "Disables per client (bytes_received, bytes_sent, connected_since) metrics", EnvVars: []string{"OPENVPN_EXPORTER_DISABLE_CLIENT_METRICS"}, }, + &cli.BoolFlag{ + Name: "allow-duplicate-cn", + Usage: "Allow multiple connections with the same common name distinguished by the Peer ID (only works with version 2 and 3 status types)", + EnvVars: []string{"OPENVPN_EXPORTER_ALLOW_DUPLICATE_CN"}, + Destination: &cfg.AllowDuplicateCN, + }, &cli.BoolFlag{ Name: "enable-golang-metrics", Value: false, @@ -145,6 +151,7 @@ func run(cfg *config.Config) error { logger, openVPServers, cfg.StatusCollector.ExportClientMetrics, + cfg.AllowDuplicateCN, )) http.Handle(cfg.Server.Path, diff --git a/pkg/config/config.go b/pkg/config/config.go index dfdb62d..ccd26ab 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -14,10 +14,11 @@ type Logs struct { // Config defines the general configuration object type Config struct { - Server Server - Logs Logs - StatusCollector StatusCollector - ExportGoMetrics bool + Server Server + Logs Logs + StatusCollector StatusCollector + ExportGoMetrics bool + AllowDuplicateCN bool } // StatusCollector contains configuration for the OpenVPN status collector diff --git a/pkg/openvpn/parser.go b/pkg/openvpn/parser.go index 2ba5b15..c23e841 100644 --- a/pkg/openvpn/parser.go +++ b/pkg/openvpn/parser.go @@ -23,6 +23,8 @@ type Client struct { BytesReceived float64 BytesSent float64 ConnectedSince time.Time + ClientID int64 + PeerID int64 } // ServerInfo reflects information that was collected about the server @@ -114,6 +116,7 @@ func parseStatusV1(reader io.Reader) (*Status, error) { BytesReceived: bytesRec, BytesSent: bytesSent, ConnectedSince: parseTime(fields[4]), + PeerID: -1, } clients = append(clients, client) } @@ -142,12 +145,16 @@ func parseStatusV2AndV3(reader io.Reader, separator string) (*Status, error) { bytesRec, _ := strconv.ParseFloat(fields[5], 64) bytesSent, _ := strconv.ParseFloat(fields[6], 64) connectedSinceInt, _ := strconv.ParseInt(fields[8], 10, 64) + clientIdInt, _ := strconv.ParseInt(fields[10], 10, 64) + peerIdInt, _ := strconv.ParseInt(fields[11], 10, 64) client := Client{ CommonName: fields[1], RealAddress: parseIP(fields[2]), BytesReceived: bytesRec, BytesSent: bytesSent, ConnectedSince: time.Unix(connectedSinceInt, 0), + ClientID: clientIdInt, + PeerID: peerIdInt, } clients = append(clients, client) } else if fields[0] == "GLOBAL_STATS" {