package wireguard import ( "encoding/base64" "errors" "io" "net/netip" "strconv" "strings" "darvaza.org/core" "gopkg.in/gcfg.v1" ) // 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) } // InterfaceConfig represents the [Interface] section type InterfaceConfig struct { Address netip.Addr PrivateKey []byte ListenPort uint16 } // PeerConfig represents a [Peer] section type PeerConfig struct { PublicKey []byte Endpoint EndpointAddress AllowedIPs []netip.Prefix } // EndpointAddress is a host:port pair to reach the Peer type EndpointAddress struct { Name string Port uint16 } // 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.Name = 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.Wrapf(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) { out := InterfaceConfig{ Address: p.Address, ListenPort: p.ListenPort, } b, err := base64.StdEncoding.DecodeString(p.PrivateKey) if err != nil { err = core.Wrap(err, "PrivateKey") return InterfaceConfig{}, err } out.PrivateKey = b 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 s = v.Peer.PublicKey[i] out.PublicKey, err = base64.StdEncoding.DecodeString(s) 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() }