Files
openlan/pkg/cache/openvpn.go
2024-01-02 15:53:19 +08:00

204 lines
4.3 KiB
Go
Executable File

package cache
import (
"bufio"
"io"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"unicode"
"github.com/luscis/openlan/pkg/config"
"github.com/luscis/openlan/pkg/libol"
"github.com/luscis/openlan/pkg/schema"
)
type vpnClient struct {
Directory string
}
func ParseInt64(value string) (int64, error) {
return strconv.ParseInt(value, 10, 64)
}
func (o *vpnClient) GetDevice(name string) string {
sw := config.Manager.Switch
if sw == nil {
return ""
}
for _, n := range sw.Network {
vpn := n.OpenVPN
if vpn == nil {
continue
}
if vpn.Network == name {
return vpn.Device
}
}
return ""
}
func (o *vpnClient) scanStatus(network string, reader io.Reader,
clients map[string]*schema.VPNClient) error {
readAt := "header"
offset := 0
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
line := scanner.Text()
if line == "OpenVPN CLIENT LIST" {
readAt = "common"
offset = 3
}
if line == "ROUTING TABLE" {
readAt = "routing"
offset = 2
}
if line == "GLOBAL STATS" {
readAt = "global"
offset = 1
}
if offset > 0 {
offset -= 1
continue
}
columns := strings.SplitN(line, ",", 5)
switch readAt {
case "common":
if len(columns) == 5 {
name := columns[0]
remote := columns[1]
client := &schema.VPNClient{
Name: name,
Remote: remote,
State: "success",
Device: o.GetDevice(network),
}
if rxc, err := ParseInt64(columns[2]); err == nil {
client.RxBytes = rxc
}
if txc, err := ParseInt64(columns[3]); err == nil {
client.TxBytes = txc
}
if len(columns[4]) > 0 {
var uptime time.Time
var err error
if unicode.IsDigit(rune(columns[4][0])) {
uptime, err = libol.GetLocalTime(libol.SimpleTime, columns[4])
} else {
uptime, err = libol.GetLocalTime(time.ANSIC, columns[4])
}
if err == nil {
client.Uptime = uptime.Unix()
client.AliveTime = time.Now().Unix() - client.Uptime
} else {
libol.Warn("vpnClient.scanStatus %s", err)
}
}
clients[remote] = client
}
case "routing":
if len(columns) == 4 {
remote := columns[2]
address := columns[0]
if client, ok := clients[remote]; ok {
client.Address = address
}
}
}
}
if err := scanner.Err(); err != nil {
return err
}
return nil
}
func (o *vpnClient) Dir(args ...string) string {
values := append([]string{o.Directory}, args...)
return filepath.Join(values...)
}
func (o *vpnClient) statusFile(name string) []string {
files, err := filepath.Glob(o.Dir(name, "*server.status"))
if err != nil {
libol.Warn("vpnClient.statusFile %v", err)
}
return files
}
func (o *vpnClient) readStatus(network string) map[string]*schema.VPNClient {
clients := make(map[string]*schema.VPNClient, 32)
for _, file := range o.statusFile(network) {
reader, err := os.Open(file)
if err != nil {
libol.Debug("vpnClient.readStatus %v", err)
return nil
}
if err := o.scanStatus(network, reader, clients); err != nil {
libol.Warn("vpnClient.readStatus %v", err)
}
reader.Close()
}
return clients
}
func (o *vpnClient) List(name string) <-chan *schema.VPNClient {
c := make(chan *schema.VPNClient, 128)
clients := o.readStatus(name)
go func() {
for _, v := range clients {
c <- v
}
c <- nil //Finish channel by nil.
}()
return c
}
func (o *vpnClient) Get(name, user string) *schema.VPNClient {
username := user + "@" + name
clients := o.readStatus(name)
for _, client := range clients {
if client.Name == username {
return client
}
}
return nil
}
func (o *vpnClient) clientFile(name string) string {
files, _ := filepath.Glob(o.Dir(name, "*client.ovpn"))
if len(files) > 0 {
return files[0]
}
return ""
}
func (o *vpnClient) GetClientProfile(network, remote string) (string, error) {
reader, err := os.Open(o.clientFile(network))
if err != nil {
return "", err
}
profile := ""
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
line := scanner.Text()
elements := strings.SplitN(line, " ", 3)
if len(elements) == 3 && elements[0] == "remote" {
profile += "remote " + remote + " " + elements[2]
} else {
profile += line
}
profile += "\n"
}
if err := scanner.Err(); err != nil {
return profile, err
}
return profile, nil
}
var VPNClient = vpnClient{
Directory: config.VarDir("openvpn"),
}