-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathconfig.go
282 lines (256 loc) · 7.84 KB
/
config.go
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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
package wgquick
import (
"bytes"
"encoding"
"encoding/base64"
"fmt"
"net"
"strconv"
"strings"
"text/template"
"time"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
)
// Config represents full wg-quick like config structure
type Config struct {
wgtypes.Config
// Address list of IP (v4 or v6) addresses (optionally with CIDR masks) to be assigned to the interface. May be specified multiple times.
Address []net.IPNet
// list of IP (v4 or v6) addresses to be set as the interface’s DNS servers. May be specified multiple times. Upon bringing the interface up, this runs ‘resolvconf -a tun.INTERFACE -m 0 -x‘ and upon bringing it down, this runs ‘resolvconf -d tun.INTERFACE‘. If these particular invocations of resolvconf(8) are undesirable, the PostUp and PostDown keys below may be used instead.
DNS []net.IP
// MTU is automatically determined from the endpoint addresses or the system default route, which is usually a sane choice. However, to manually specify an MTU to override this automatic discovery, this value may be specified explicitly.
MTU int
// Table — Controls the routing table to which routes are added.
Table int
// PreUp, PostUp, PreDown, PostDown — script snippets which will be executed by bash(1) before/after setting up/tearing down the interface, most commonly used to configure custom DNS options or firewall rules. The special string ‘%i’ is expanded to INTERFACE. Each one may be specified multiple times, in which case the commands are executed in order.
PreUp string
PostUp string
PreDown string
PostDown string
// RouteProtocol to set on the route. See linux/rtnetlink.h Use value > 4 or default 0
RouteProtocol int
// RouteMetric sets this metric on all managed routes. Lower number means pick this one
RouteMetric int
// Address label to set on the link
AddressLabel string
// SaveConfig — if set to ‘true’, the configuration is saved from the current state of the interface upon shutdown.
// Currently unsupported
SaveConfig bool
}
var _ encoding.TextMarshaler = (*Config)(nil)
var _ encoding.TextUnmarshaler = (*Config)(nil)
func (cfg *Config) String() string {
b, err := cfg.MarshalText()
if err != nil {
panic(err)
}
return string(b)
}
func serializeKey(key *wgtypes.Key) string {
return base64.StdEncoding.EncodeToString(key[:])
}
func toSeconds(duration time.Duration) int {
return int(duration / time.Second)
}
var funcMap = template.FuncMap(map[string]interface{}{
"wgKey": serializeKey,
"toSeconds": toSeconds,
})
var cfgTemplate = template.Must(
template.
New("wg-cfg").
Funcs(funcMap).
Parse(wgtypeTemplateSpec))
func (cfg *Config) MarshalText() (text []byte, err error) {
buff := &bytes.Buffer{}
if err := cfgTemplate.Execute(buff, cfg); err != nil {
return nil, err
}
return buff.Bytes(), nil
}
const wgtypeTemplateSpec = `[Interface]
{{- range .Address }}
Address = {{ . }}
{{- end }}
{{- range .DNS }}
DNS = {{ . }}
{{- end }}
PrivateKey = {{ .PrivateKey | wgKey }}
{{- if .ListenPort }}{{ "\n" }}ListenPort = {{ .ListenPort }}{{ end }}
{{- if .MTU }}{{ "\n" }}MTU = {{ .MTU }}{{ end }}
{{- if .Table }}{{ "\n" }}Table = {{ .Table }}{{ end }}
{{- if .PreUp }}{{ "\n" }}PreUp = {{ .PreUp }}{{ end }}
{{- if .PostUp }}{{ "\n" }}PostUp = {{ .PostUp }}{{ end }}
{{- if .PreDown }}{{ "\n" }}PreDown = {{ .PreDown }}{{ end }}
{{- if .PostDown }}{{ "\n" }}PostDown = {{ .PostDown }}{{ end }}
{{- if .SaveConfig }}{{ "\n" }}SaveConfig = {{ .SaveConfig }}{{ end }}
{{- range .Peers }}
{{- "\n" }}
[Peer]
PublicKey = {{ .PublicKey | wgKey }}
AllowedIPs = {{ range $i, $el := .AllowedIPs }}{{if $i}}, {{ end }}{{ $el }}{{ end }}
{{- if .PresharedKey }}{{ "\n" }}PresharedKey = {{ .PresharedKey }}{{ end }}
{{- if .PersistentKeepaliveInterval }}{{ "\n" }}PersistentKeepalive = {{ .PersistentKeepaliveInterval | toSeconds }}{{ end }}
{{- if .Endpoint }}{{ "\n" }}Endpoint = {{ .Endpoint }}{{ end }}
{{- end }}
`
// ParseKey parses the base64 encoded wireguard private key
func ParseKey(key string) (wgtypes.Key, error) {
var pkey wgtypes.Key
pkeySlice, err := base64.StdEncoding.DecodeString(key)
if err != nil {
return pkey, err
}
copy(pkey[:], pkeySlice[:])
return pkey, nil
}
type parseState int
const (
unknown parseState = iota
inter = iota
peer = iota
)
func (cfg *Config) UnmarshalText(text []byte) error {
*cfg = Config{} // Zero out the config
state := unknown
var peerCfg *wgtypes.PeerConfig
for no, line := range strings.Split(string(text), "\n") {
ln := strings.TrimSpace(line)
if len(ln) == 0 || ln[0] == '#' {
continue
}
switch ln {
case "[Interface]":
state = inter
case "[Peer]":
state = peer
cfg.Peers = append(cfg.Peers, wgtypes.PeerConfig{})
peerCfg = &cfg.Peers[len(cfg.Peers)-1]
default:
parts := strings.Split(ln, "=")
if len(parts) < 2 {
return fmt.Errorf("cannot parse line %d, missing =", no)
}
lhs := strings.TrimSpace(parts[0])
rhs := strings.TrimSpace(strings.Join(parts[1:], "="))
switch state {
case inter:
if err := parseInterfaceLine(cfg, lhs, rhs); err != nil {
return fmt.Errorf("[line %d]: %v", no+1, err)
}
case peer:
if err := parsePeerLine(peerCfg, lhs, rhs); err != nil {
return fmt.Errorf("[line %d]: %v", no+1, err)
}
default:
return fmt.Errorf("[line %d] cannot parse, unknown state", no+1)
}
}
}
return nil
}
func parseInterfaceLine(cfg *Config, lhs string, rhs string) error {
switch lhs {
case "Address":
for _, addr := range strings.Split(rhs, ",") {
ip, cidr, err := net.ParseCIDR(strings.TrimSpace(addr))
if err != nil {
return err
}
cfg.Address = append(cfg.Address, net.IPNet{IP: ip, Mask: cidr.Mask})
}
case "DNS":
for _, addr := range strings.Split(rhs, ",") {
ip := net.ParseIP(strings.TrimSpace(addr))
if ip == nil {
return fmt.Errorf("cannot parse IP")
}
cfg.DNS = append(cfg.DNS, ip)
}
case "MTU":
mtu, err := strconv.ParseInt(rhs, 10, 64)
if err != nil {
return err
}
cfg.MTU = int(mtu)
case "Table":
tbl, err := strconv.ParseInt(rhs, 10, 64)
if err != nil {
return err
}
cfg.Table = int(tbl)
case "ListenPort":
portI64, err := strconv.ParseInt(rhs, 10, 64)
if err != nil {
return err
}
port := int(portI64)
cfg.ListenPort = &port
case "PreUp":
cfg.PreUp = rhs
case "PostUp":
cfg.PostUp = rhs
case "PreDown":
cfg.PreDown = rhs
case "PostDown":
cfg.PostDown = rhs
case "SaveConfig":
save, err := strconv.ParseBool(rhs)
if err != nil {
return err
}
cfg.SaveConfig = save
case "PrivateKey":
key, err := ParseKey(rhs)
if err != nil {
return fmt.Errorf("cannot decode key %v", err)
}
cfg.PrivateKey = &key
default:
return fmt.Errorf("unknown directive %s", lhs)
}
return nil
}
func parsePeerLine(peerCfg *wgtypes.PeerConfig, lhs string, rhs string) error {
switch lhs {
case "PublicKey":
key, err := ParseKey(rhs)
if err != nil {
return fmt.Errorf("cannot decode key %v", err)
}
peerCfg.PublicKey = key
case "PresharedKey":
key, err := ParseKey(rhs)
if err != nil {
return fmt.Errorf("cannot decode key %v", err)
}
if peerCfg.PresharedKey != nil {
return fmt.Errorf("preshared key already defined %v", err)
}
peerCfg.PresharedKey = &key
case "AllowedIPs":
for _, addr := range strings.Split(rhs, ",") {
ip, cidr, err := net.ParseCIDR(strings.TrimSpace(addr))
if err != nil {
return fmt.Errorf("cannot parse %s: %v", addr, err)
}
peerCfg.AllowedIPs = append(peerCfg.AllowedIPs, net.IPNet{IP: ip, Mask: cidr.Mask})
}
case "Endpoint":
addr, err := net.ResolveUDPAddr("", rhs)
if err != nil {
return err
}
peerCfg.Endpoint = addr
case "PersistentKeepalive":
t, err := strconv.ParseInt(rhs, 10, 64)
if err != nil {
return err
}
dur := time.Duration(t * int64(time.Second))
peerCfg.PersistentKeepaliveInterval = &dur
default:
return fmt.Errorf("unknown directive %s", lhs)
}
return nil
}