summaryrefslogtreecommitdiffstats
path: root/src/go/collectors/go.d.plugin/modules/openvpn_status_log/parser.go
blob: c734fd5fb1a4930d7493342add44e15e4c00cdc5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
// SPDX-License-Identifier: GPL-3.0-or-later

package openvpn_status_log

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
	"time"
)

type clientInfo struct {
	commonName     string
	bytesReceived  int64
	bytesSent      int64
	connectedSince int64
}

func parse(path string) ([]clientInfo, error) {
	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer func() { _ = f.Close() }()

	sc := bufio.NewScanner(f)
	_ = sc.Scan()
	line := sc.Text()

	if line == "OpenVPN CLIENT LIST" {
		return parseV1(sc), nil
	}
	if strings.HasPrefix(line, "TITLE,OpenVPN") || strings.HasPrefix(line, "TITLE\tOpenVPN") {
		return parseV2V3(sc), nil
	}
	if line == "OpenVPN STATISTICS" {
		return parseStaticKey(sc), nil
	}
	return nil, fmt.Errorf("the status log file is invalid (%s)", path)
}

func parseV1(sc *bufio.Scanner) []clientInfo {
	// https://github.com/OpenVPN/openvpn/blob/d5315a5d7400a26f1113bbc44766d49dd0c3688f/src/openvpn/multi.c#L836
	var clients []clientInfo

	for sc.Scan() {
		if !strings.HasPrefix(sc.Text(), "Common Name") {
			continue
		}
		for sc.Scan() && !strings.HasPrefix(sc.Text(), "ROUTING TABLE") {
			parts := strings.Split(sc.Text(), ",")
			if len(parts) != 5 {
				continue
			}

			name := parts[0]
			bytesRx, _ := strconv.ParseInt(parts[2], 10, 64)
			bytesTx, _ := strconv.ParseInt(parts[3], 10, 64)
			connSince, _ := time.Parse("Mon Jan 2 15:04:05 2006", parts[4])

			clients = append(clients, clientInfo{
				commonName:     name,
				bytesReceived:  bytesRx,
				bytesSent:      bytesTx,
				connectedSince: connSince.Unix(),
			})
		}
		break
	}
	return clients
}

func parseV2V3(sc *bufio.Scanner) []clientInfo {
	// https://github.com/OpenVPN/openvpn/blob/d5315a5d7400a26f1113bbc44766d49dd0c3688f/src/openvpn/multi.c#L901
	var clients []clientInfo
	var sep string
	if strings.IndexByte(sc.Text(), '\t') != -1 {
		sep = "\t"
	} else {
		sep = ","
	}

	for sc.Scan() {
		line := sc.Text()
		if !strings.HasPrefix(line, "CLIENT_LIST") {
			continue
		}
		parts := strings.Split(line, sep)
		if len(parts) != 13 {
			continue
		}

		name := parts[1]
		bytesRx, _ := strconv.ParseInt(parts[5], 10, 64)
		bytesTx, _ := strconv.ParseInt(parts[6], 10, 64)
		connSince, _ := strconv.ParseInt(parts[8], 10, 64)

		clients = append(clients, clientInfo{
			commonName:     name,
			bytesReceived:  bytesRx,
			bytesSent:      bytesTx,
			connectedSince: connSince,
		})
	}
	return clients
}

func parseStaticKey(sc *bufio.Scanner) []clientInfo {
	// https://github.com/OpenVPN/openvpn/blob/d5315a5d7400a26f1113bbc44766d49dd0c3688f/src/openvpn/sig.c#L283
	var info clientInfo
	for sc.Scan() {
		line := sc.Text()
		if !strings.HasPrefix(line, "TCP/UDP") {
			continue
		}
		i := strings.IndexByte(line, ',')
		if i == -1 || len(line) == i {
			continue
		}
		bytes, _ := strconv.ParseInt(line[i+1:], 10, 64)
		switch line[:i] {
		case "TCP/UDP read bytes":
			info.bytesReceived += bytes
		case "TCP/UDP write bytes":
			info.bytesSent += bytes
		}
	}
	return []clientInfo{info}
}