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 }