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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
|
package internal
import (
"fmt"
"log"
"os/exec"
"path/filepath"
"regexp"
"slices"
"strings"
"github.com/samber/lo"
)
var interfaceRegex = regexp.MustCompile(`^interface:\s+(.+)$`)
type WireGuardConnection struct {
Name string `json:"name"`
Active bool `json:"active"`
}
func GetStatus() (string, error) {
output, err := showStatus()
if err != nil {
return "", err
}
status := lo.FilterMap(strings.Split(string(output), "\n"), func(line string, _ int) (string, bool) {
line = strings.TrimSpace(line)
if strings.Contains(line, "interface") {
return fmt.Sprintf("Connection: %s", strings.TrimPrefix(line, "interface:")), true
}
if strings.Contains(line, "latest handshake") {
return fmt.Sprintf("Latest Handshake: %s", strings.TrimPrefix(line, "latest handshake:")), true
}
if strings.Contains(line, "transfer") {
return fmt.Sprintf("Transfer: %s", strings.TrimPrefix(line, "transfer:")), true
}
return "", false
})
// This is a simple check on whether a connection started or not.
// Instead of complex logic on looping on connections and figuring which connection might be missing info.
// NOTE: This doesn't handle if 3x connections were started and none of them is still active.
// NOTE: ToggleConnection stops all active connections and activate one.
// to avoid issues with multiple VPNs configuring the same iptable that could happen with default wireguard configs
if len(status)%3 != 0 {
status = append(status, "Connection starting...")
}
return strings.Join(status, "\n"), nil
}
func GetConnections() ([]*WireGuardConnection, error) {
activeConnection, err := getActiveConnections()
if err != nil {
return nil, err
}
allConnections, err := getAllConnections()
if err != nil {
return nil, err
}
connections := make([]*WireGuardConnection, 0, len(allConnections))
for _, i := range allConnections {
connections = append(connections, &WireGuardConnection{
Name: i,
Active: slices.Contains(activeConnection, i),
})
}
return connections, nil
}
func ToggleConnection(name string) ([]byte, error) {
allConnections, err := GetConnections()
if err != nil {
return nil, err
}
activeConnections := lo.Filter(allConnections, func(i *WireGuardConnection, _ int) bool {
return i.Active
})
connection, err := getConnection(name)
if err != nil {
return nil, err
}
output, err := stopActiveConnections(activeConnections)
if err != nil {
return nil, err
}
startOutput, err := startConnection(connection)
if err != nil {
return nil, err
}
output = append(output, startOutput...)
return output, nil
}
func stopActiveConnections(activeConnections []*WireGuardConnection) ([]byte, error) {
var output []byte
for _, activeConnection := range activeConnections {
log.Printf("Stopping connection %s", activeConnection.Name)
cmd := exec.Command("sudo", "wg-quick", "down", activeConnection.Name)
out, err := cmd.CombinedOutput()
if err != nil {
return nil, err
}
output = append(output, out...)
log.Printf("Successfully stopped connection %s", activeConnection.Name)
}
return output, nil
}
func startConnection(connection *WireGuardConnection) ([]byte, error) {
if connection.Active {
return nil, nil
}
log.Printf("Starting connection %s", connection.Name)
cmd := exec.Command("sudo", "wg-quick", "up", connection.Name)
output, err := cmd.CombinedOutput()
if err != nil {
return nil, err
}
log.Printf("Successfully started connection %s", connection.Name)
return output, nil
}
// Get the list of all wireguard connections using config files
func getAllConnections() ([]string, error) {
files, err := filepath.Glob("/etc/wireguard/*.conf")
if err != nil {
return nil, err
}
files = lo.Map(files, func(f string, _ int) string {
return strings.TrimSuffix(filepath.Base(f), filepath.Ext(f))
})
return files, nil
}
// Get the list of active wireguard connections using wg show command
func getActiveConnections() ([]string, error) {
var activeConnections []string
status, err := showStatus()
if err != nil {
return nil, err
}
for line := range strings.SplitSeq(string(status), "\n") {
if matches := interfaceRegex.FindStringSubmatch(strings.TrimSpace(line)); len(matches) > 1 {
activeConnections = append(activeConnections, matches[1])
}
}
return activeConnections, nil
}
func getConnection(name string) (*WireGuardConnection, error) {
allConnections, err := GetConnections()
if err != nil {
return nil, err
}
connection, ok := lo.Find(allConnections, func(dev *WireGuardConnection) bool {
return dev.Name == name
})
if !ok {
return nil, fmt.Errorf("failed to find connection: %s", name)
}
return connection, nil
}
func showStatus() ([]byte, error) {
cmd := exec.Command("sudo", "wg", "show")
output, err := cmd.Output()
if err != nil {
return nil, fmt.Errorf("failed to execute wg show: %w", err)
}
return output, nil
}
|