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}
}
|