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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
|
// SPDX-License-Identifier: GPL-3.0-or-later
package client
import (
"fmt"
"regexp"
"strconv"
"strings"
"github.com/netdata/netdata/go/go.d.plugin/pkg/socket"
)
var (
reLoadStats = regexp.MustCompile(`^SUCCESS: nclients=([0-9]+),bytesin=([0-9]+),bytesout=([0-9]+)`)
reVersion = regexp.MustCompile(`^OpenVPN Version: OpenVPN ([0-9]+)\.([0-9]+)\.([0-9]+) .+Management Version: ([0-9])`)
)
const maxLinesToRead = 500
// New creates new OpenVPN client.
func New(config socket.Config) *Client {
return &Client{Client: socket.New(config)}
}
// Client represents OpenVPN client.
type Client struct {
socket.Client
}
// Users Users.
func (c *Client) Users() (Users, error) {
lines, err := c.get(commandStatus3, readUntilEND)
if err != nil {
return nil, err
}
return decodeUsers(lines)
}
// LoadStats LoadStats.
func (c *Client) LoadStats() (*LoadStats, error) {
lines, err := c.get(commandLoadStats, readOneLine)
if err != nil {
return nil, err
}
return decodeLoadStats(lines)
}
// Version Version.
func (c *Client) Version() (*Version, error) {
lines, err := c.get(commandVersion, readUntilEND)
if err != nil {
return nil, err
}
return decodeVersion(lines)
}
func (c *Client) get(command string, stopRead stopReadFunc) (output []string, err error) {
var num int
var maxLinesErr error
err = c.Command(command, func(bytes []byte) bool {
line := string(bytes)
num++
if num > maxLinesToRead {
maxLinesErr = fmt.Errorf("read line limit exceeded (%d)", maxLinesToRead)
return false
}
// skip real-time messages
if strings.HasPrefix(line, ">") {
return true
}
line = strings.Trim(line, "\r\n ")
output = append(output, line)
if stopRead != nil && stopRead(line) {
return false
}
return true
})
if maxLinesErr != nil {
return nil, maxLinesErr
}
return output, err
}
type stopReadFunc func(string) bool
func readOneLine(_ string) bool { return true }
func readUntilEND(s string) bool { return strings.HasSuffix(s, "END") }
func decodeLoadStats(src []string) (*LoadStats, error) {
m := reLoadStats.FindStringSubmatch(strings.Join(src, " "))
if len(m) == 0 {
return nil, fmt.Errorf("parse failed : %v", src)
}
return &LoadStats{
NumOfClients: mustParseInt(m[1]),
BytesIn: mustParseInt(m[2]),
BytesOut: mustParseInt(m[3]),
}, nil
}
func decodeVersion(src []string) (*Version, error) {
m := reVersion.FindStringSubmatch(strings.Join(src, " "))
if len(m) == 0 {
return nil, fmt.Errorf("parse failed : %v", src)
}
return &Version{
Major: mustParseInt(m[1]),
Minor: mustParseInt(m[2]),
Patch: mustParseInt(m[3]),
Management: mustParseInt(m[4]),
}, nil
}
// works only for `status 3\n`
func decodeUsers(src []string) (Users, error) {
var users Users
// [CLIENT_LIST common_name 178.66.34.194:54200 10.9.0.5 9319 8978 Thu May 9 05:01:44 2019 1557345704 username]
for _, v := range src {
if !strings.HasPrefix(v, "CLIENT_LIST") {
continue
}
parts := strings.Fields(v)
// Right after the connection there are no virtual ip, and both common name and username UNDEF
// CLIENT_LIST UNDEF 178.70.95.93:39324 1411 3474 Fri May 10 07:41:54 2019 1557441714 UNDEF
if len(parts) != 13 {
continue
}
u := User{
CommonName: parts[1],
RealAddress: parts[2],
VirtualAddress: parts[3],
BytesReceived: mustParseInt(parts[4]),
BytesSent: mustParseInt(parts[5]),
ConnectedSince: mustParseInt(parts[11]),
Username: parts[12],
}
users = append(users, u)
}
return users, nil
}
func mustParseInt(str string) int64 {
v, err := strconv.ParseInt(str, 10, 64)
if err != nil {
panic(err)
}
return v
}
|