master
go 131 lines 2.95 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package openvpn_status_log
4
5 import (
6 "bufio"
7 "fmt"
8 "os"
9 "strconv"
10 "strings"
11 "time"
12 )
13
14 type clientInfo struct {
15 commonName string
16 bytesReceived int64
17 bytesSent int64
18 connectedSince int64
19 }
20
21 func parse(path string) ([]clientInfo, error) {
22 f, err := os.Open(path)
23 if err != nil {
24 return nil, err
25 }
26 defer func() { _ = f.Close() }()
27
28 sc := bufio.NewScanner(f)
29 _ = sc.Scan()
30 line := sc.Text()
31
32 if line == "OpenVPN CLIENT LIST" {
33 return parseV1(sc), nil
34 }
35 if strings.HasPrefix(line, "TITLE,OpenVPN") || strings.HasPrefix(line, "TITLE\tOpenVPN") {
36 return parseV2V3(sc), nil
37 }
38 if line == "OpenVPN STATISTICS" {
39 return parseStaticKey(sc), nil
40 }
41 return nil, fmt.Errorf("the status log file is invalid (%s)", path)
42 }
43
44 func parseV1(sc *bufio.Scanner) []clientInfo {
45 // https://github.com/OpenVPN/openvpn/blob/d5315a5d7400a26f1113bbc44766d49dd0c3688f/src/openvpn/multi.c#L836
46 var clients []clientInfo
47
48 for sc.Scan() {
49 if !strings.HasPrefix(sc.Text(), "Common Name") {
50 continue
51 }
52 for sc.Scan() && !strings.HasPrefix(sc.Text(), "ROUTING TABLE") {
53 parts := strings.Split(sc.Text(), ",")
54 if len(parts) != 5 {
55 continue
56 }
57
58 name := parts[0]
59 bytesRx, _ := strconv.ParseInt(parts[2], 10, 64)
60 bytesTx, _ := strconv.ParseInt(parts[3], 10, 64)
61 connSince, _ := time.Parse("Mon Jan 2 15:04:05 2006", parts[4])
62
63 clients = append(clients, clientInfo{
64 commonName: name,
65 bytesReceived: bytesRx,
66 bytesSent: bytesTx,
67 connectedSince: connSince.Unix(),
68 })
69 }
70 break
71 }
72 return clients
73 }
74
75 func parseV2V3(sc *bufio.Scanner) []clientInfo {
76 // https://github.com/OpenVPN/openvpn/blob/d5315a5d7400a26f1113bbc44766d49dd0c3688f/src/openvpn/multi.c#L901
77 var clients []clientInfo
78 var sep string
79 if strings.IndexByte(sc.Text(), '\t') != -1 {
80 sep = "\t"
81 } else {
82 sep = ","
83 }
84
85 for sc.Scan() {
86 line := sc.Text()
87 if !strings.HasPrefix(line, "CLIENT_LIST") {
88 continue
89 }
90 parts := strings.Split(line, sep)
91 if len(parts) != 13 {
92 continue
93 }
94
95 name := parts[1]
96 bytesRx, _ := strconv.ParseInt(parts[5], 10, 64)
97 bytesTx, _ := strconv.ParseInt(parts[6], 10, 64)
98 connSince, _ := strconv.ParseInt(parts[8], 10, 64)
99
100 clients = append(clients, clientInfo{
101 commonName: name,
102 bytesReceived: bytesRx,
103 bytesSent: bytesTx,
104 connectedSince: connSince,
105 })
106 }
107 return clients
108 }
109
110 func parseStaticKey(sc *bufio.Scanner) []clientInfo {
111 // https://github.com/OpenVPN/openvpn/blob/d5315a5d7400a26f1113bbc44766d49dd0c3688f/src/openvpn/sig.c#L283
112 var info clientInfo
113 for sc.Scan() {
114 line := sc.Text()
115 if !strings.HasPrefix(line, "TCP/UDP") {
116 continue
117 }
118 i := strings.IndexByte(line, ',')
119 if i == -1 || len(line) == i {
120 continue
121 }
122 bytes, _ := strconv.ParseInt(line[i+1:], 10, 64)
123 switch line[:i] {
124 case "TCP/UDP read bytes":
125 info.bytesReceived += bytes
126 case "TCP/UDP write bytes":
127 info.bytesSent += bytes
128 }
129 }
130 return []clientInfo{info}
131 }