|
|
|
package wireguard
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"net/netip"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"text/template"
|
|
|
|
|
|
|
|
"darvaza.org/core"
|
|
|
|
"gopkg.in/gcfg.v1"
|
|
|
|
)
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
type intermediateConfig struct {
|
|
|
|
Interface interfaceConfig
|
|
|
|
Peer peersConfig
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v *intermediateConfig) Export() (*Config, error) {
|
|
|
|
var out Config
|
|
|
|
var err error
|
|
|
|
|
|
|
|
// Interface
|
|
|
|
out.Interface, err = v.Interface.Export()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Peers
|
|
|
|
peers, ok := v.PeersCount()
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.New("inconsistent Peer data")
|
|
|
|
}
|
|
|
|
|
|
|
|
for i := 0; i < peers; i++ {
|
|
|
|
p, err := v.ExportPeer(i)
|
|
|
|
if err != nil {
|
|
|
|
err = core.Wrap(err, "Peer[%v]:", i)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
out.Peer = append(out.Peer, p)
|
|
|
|
}
|
|
|
|
|
|
|
|
return &out, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type interfaceConfig struct {
|
|
|
|
Address netip.Addr
|
|
|
|
PrivateKey string
|
|
|
|
ListenPort uint16
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p interfaceConfig) Export() (InterfaceConfig, error) {
|
|
|
|
var err error
|
|
|
|
|
|
|
|
out := InterfaceConfig{
|
|
|
|
Address: p.Address,
|
|
|
|
ListenPort: p.ListenPort,
|
|
|
|
}
|
|
|
|
|
|
|
|
if p.PrivateKey != "" {
|
|
|
|
out.PrivateKey, err = PrivateKeyFromBase64(p.PrivateKey)
|
|
|
|
if err != nil {
|
|
|
|
err = core.Wrap(err, "PrivateKey")
|
|
|
|
return InterfaceConfig{}, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return out, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type peersConfig struct {
|
|
|
|
PublicKey []string
|
|
|
|
Endpoint []string
|
|
|
|
AllowedIPs []string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v *intermediateConfig) ExportPeer(i int) (PeerConfig, error) {
|
|
|
|
var out PeerConfig
|
|
|
|
|
|
|
|
// Endpoint
|
|
|
|
s := v.Peer.Endpoint[i]
|
|
|
|
err := out.Endpoint.FromString(s)
|
|
|
|
if err != nil {
|
|
|
|
err = core.Wrap(err, "Endpoint")
|
|
|
|
return out, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// PublicKey
|
|
|
|
out.PublicKey, err = PublicKeyFromBase64(v.Peer.PublicKey[i])
|
|
|
|
if err != nil {
|
|
|
|
err = core.Wrap(err, "PublicKey")
|
|
|
|
return out, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// AllowedIPs
|
|
|
|
s = v.Peer.AllowedIPs[i]
|
|
|
|
out.AllowedIPs, err = parseAllowedIPs(s)
|
|
|
|
if err != nil {
|
|
|
|
err = core.Wrap(err, "AllowedIPs")
|
|
|
|
return out, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return out, 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
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v *intermediateConfig) PeersCount() (int, bool) {
|
|
|
|
c0 := len(v.Peer.Endpoint)
|
|
|
|
c1 := len(v.Peer.PublicKey)
|
|
|
|
c2 := len(v.Peer.AllowedIPs)
|
|
|
|
|
|
|
|
if c0 != c1 || c1 != c2 {
|
|
|
|
return 0, false
|
|
|
|
}
|
|
|
|
|
|
|
|
return c0, true
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewConfigFromReader parses a wgN.conf file
|
|
|
|
func NewConfigFromReader(r io.Reader) (*Config, error) {
|
|
|
|
temp := &intermediateConfig{}
|
|
|
|
|
|
|
|
if err := gcfg.ReadInto(temp, r); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return temp.Export()
|
|
|
|
}
|