You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
163 lines
3.2 KiB
163 lines
3.2 KiB
package wireguard |
|
|
|
import ( |
|
"bytes" |
|
"fmt" |
|
"io" |
|
"net/netip" |
|
"strconv" |
|
"strings" |
|
"text/template" |
|
|
|
"asciigoat.org/ini/basic" |
|
"darvaza.org/core" |
|
) |
|
|
|
var configTemplate = template.Must(template.New("config").Funcs(template.FuncMap{ |
|
"PrefixJoin": func(a []netip.Prefix, sep string) string { |
|
s := make([]string, len(a)) |
|
for i, p := range a { |
|
s[i] = p.String() |
|
} |
|
return strings.Join(s, sep) |
|
}, |
|
}).Parse(`[Interface] |
|
{{if .Interface.Name}}# Name: {{.Interface.Name}} |
|
{{end -}} |
|
Address = {{.Interface.Address}} |
|
PrivateKey = {{.Interface.PrivateKey}} |
|
ListenPort = {{.Interface.ListenPort}} |
|
{{- range .Peer }} |
|
|
|
[Peer] |
|
{{if .Name}}# Name: {{.Name}} |
|
{{end -}} |
|
PublicKey = {{.PublicKey}} |
|
Endpoint = {{.Endpoint}} |
|
AllowedIPs = {{ PrefixJoin .AllowedIPs ", "}} |
|
{{- end }} |
|
`)) |
|
|
|
// Config represents a wgN.conf file |
|
type Config struct { |
|
Interface InterfaceConfig |
|
Peer []PeerConfig |
|
} |
|
|
|
// GetAddress is a shortcut to the interface's address |
|
func (f *Config) GetAddress() netip.Addr { |
|
return f.Interface.Address |
|
} |
|
|
|
// Peers tells how many peers are described |
|
func (f *Config) Peers() int { |
|
return len(f.Peer) |
|
} |
|
|
|
// WriteTo writes a Wireguard [Config] onto the provided [io.Writer] |
|
func (f *Config) WriteTo(w io.Writer) (int64, error) { |
|
var buf bytes.Buffer |
|
|
|
if err := configTemplate.Execute(&buf, f); err != nil { |
|
return 0, err |
|
} |
|
|
|
return buf.WriteTo(w) |
|
} |
|
|
|
// InterfaceConfig represents the [Interface] section |
|
type InterfaceConfig struct { |
|
Name string |
|
Address netip.Addr |
|
PrivateKey PrivateKey |
|
ListenPort uint16 |
|
} |
|
|
|
// PeerConfig represents a [Peer] section |
|
type PeerConfig struct { |
|
Name string |
|
PublicKey PublicKey |
|
Endpoint EndpointAddress |
|
AllowedIPs []netip.Prefix |
|
} |
|
|
|
// EndpointAddress is a host:port pair to reach the Peer |
|
type EndpointAddress struct { |
|
Host string |
|
Port uint16 |
|
} |
|
|
|
// Name returns the first part of a hostname |
|
func (ep EndpointAddress) Name() string { |
|
before, _, _ := strings.Cut(ep.Host, ".") |
|
return before |
|
} |
|
|
|
func (ep EndpointAddress) String() string { |
|
switch { |
|
case ep.Host == "": |
|
return "" |
|
case ep.Port == 0: |
|
return ep.Host |
|
case !strings.ContainsRune(ep.Host, ':'): |
|
return fmt.Sprintf("%s:%v", ep.Host, ep.Port) |
|
default: |
|
return fmt.Sprintf("[%s]:%v", ep.Host, ep.Port) |
|
} |
|
} |
|
|
|
// UnmarshalText loads an endpoint address from text data |
|
func (ep *EndpointAddress) UnmarshalText(b []byte) error { |
|
return ep.FromString(string(b)) |
|
} |
|
|
|
// FromString sets the EndpointAddress from a given "[host]:port" |
|
func (ep *EndpointAddress) FromString(s string) error { |
|
host, port, err := core.SplitHostPort(s) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
ep.Host = host |
|
|
|
switch { |
|
case port != "": |
|
n, _ := strconv.ParseUint(port, 10, 16) |
|
ep.Port = uint16(n) |
|
default: |
|
ep.Port = 0 |
|
} |
|
|
|
return nil |
|
} |
|
|
|
func parseAllowedIPs(data string) ([]netip.Prefix, error) { |
|
var out []netip.Prefix |
|
|
|
for _, s := range strings.Split(data, ",") { |
|
s = strings.TrimSpace(s) |
|
p, err := netip.ParsePrefix(s) |
|
if err != nil { |
|
return out, err |
|
} |
|
|
|
out = append(out, p) |
|
} |
|
|
|
return out, nil |
|
} |
|
|
|
// NewConfigFromReader parses a wgN.conf file |
|
func NewConfigFromReader(r io.Reader) (*Config, error) { |
|
doc, err := basic.Decode(r) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
cfg, err := newConfigFromDocument(doc) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
return cfg, nil |
|
}
|
|
|