firehol/netdata

View on GitHub
src/go/plugin/go.d/modules/openvpn/client/client.go

Summary

Maintainability
A
40 mins
Test Coverage
// SPDX-License-Identifier: GPL-3.0-or-later

package client

import (
    "fmt"
    "regexp"
    "strconv"
    "strings"

    "github.com/netdata/netdata/go/plugins/plugin/go.d/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
}