Compare commits

..

9 Commits

Author SHA1 Message Date
amery 4f534f7cf1 WIP
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-09-11 23:40:19 +00:00
amery 012b02cc9c dns: introduce AddrRecord{} to abstract A/AAAA entries
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-09-11 23:29:56 +00:00
amery 85b0766418 vscode: add Lookuper and libdns to the dictionary
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-09-11 21:46:20 +00:00
amery 7935a441d0 build-sys: use local asciigoat.org/ini
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-09-11 21:46:06 +00:00
amery 5791220c2e vscode: add asciigoat, cyclomatic and Wrapf to the dictionary
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-09-11 21:46:06 +00:00
amery 12f3d8e26f Merge branch 'pr-amery-wireguard-ini' into next-amery 2023-09-11 21:45:48 +00:00
amery db62adfb9c wireguard: switch from gcfg to asciigoat.org/ini/basic
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-09-05 19:39:09 +00:00
amery 4599eca7d9 wireguard: implement EndpointAddress.UnmarshalText
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-09-05 19:38:03 +00:00
amery 312dbe2269 wireguard: implement UnmarshalText for PrivateKey and PublicKey
Signed-off-by: Alejandro Mery <amery@jpi.io>
2023-09-05 19:38:03 +00:00
18 changed files with 564 additions and 721 deletions
-1
View File
@@ -8,7 +8,6 @@
"jpictl", "jpictl",
"libdns", "libdns",
"Lookuper", "Lookuper",
"publicsuffix",
"Wrapf", "Wrapf",
"zerolog" "zerolog"
] ]
+9 -15
View File
@@ -3,6 +3,7 @@ package main
import ( import (
"context" "context"
"os" "os"
"time"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@@ -10,9 +11,11 @@ import (
"git.jpi.io/amery/jpictl/pkg/dns" "git.jpi.io/amery/jpictl/pkg/dns"
) )
func newDNSManager(m *cluster.Cluster) (*dns.Manager, error) { const (
ctx := context.TODO() DNSTimeout = 10 * time.Second
)
func newDNSManager(ctx context.Context, m *cluster.Cluster) (*dns.Manager, error) {
domain := m.Domain domain := m.Domain
if m.Name != "" { if m.Name != "" {
domain = m.Name + "." + domain domain = m.Name + "." + domain
@@ -31,18 +34,7 @@ func newDNSManager(m *cluster.Cluster) (*dns.Manager, error) {
return err != nil return err != nil
}) })
if err != nil {
return nil, err
}
m.ForEachRegion(func(r *cluster.Region) bool {
r.ForEachZone(func(z *cluster.Zone) bool {
err = mgr.AddRegion(ctx, r.Name, z.Name)
return err != nil
})
return err != nil
})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -57,7 +49,6 @@ var dnsCmd = &cobra.Command{
var dnsWriteCmd = &cobra.Command{ var dnsWriteCmd = &cobra.Command{
Use: "write", Use: "write",
Short: "dns write generates public DNS records",
PreRun: setVerbosity, PreRun: setVerbosity,
RunE: func(_ *cobra.Command, _ []string) error { RunE: func(_ *cobra.Command, _ []string) error {
m, err := cfg.LoadZones(true) m, err := cfg.LoadZones(true)
@@ -65,7 +56,10 @@ var dnsWriteCmd = &cobra.Command{
return err return err
} }
mgr, err := newDNSManager(m) ctx, cancel := context.WithTimeout(context.Background(), DNSTimeout)
defer cancel()
mgr, err := newDNSManager(ctx, m)
if err != nil { if err != nil {
return err return err
} }
+1 -1
View File
@@ -37,5 +37,5 @@ func setVerbosity(_ *cobra.Command, _ []string) {
if desired > 6 { if desired > 6 {
desired = 6 desired = 6
} }
log = zerolog.New(nil, slog.LogLevel(desired)) log = log.WithLevel(slog.LogLevel(desired))
} }
+3 -3
View File
@@ -2,6 +2,8 @@ module git.jpi.io/amery/jpictl
go 1.19 go 1.19
replace asciigoat.org/ini => ../../../asciigoat.org/ini
require ( require (
asciigoat.org/ini v0.2.5 asciigoat.org/ini v0.2.5
darvaza.org/core v0.9.8 darvaza.org/core v0.9.8
@@ -16,8 +18,6 @@ require (
github.com/mgechev/revive v1.3.3 github.com/mgechev/revive v1.3.3
github.com/spf13/cobra v1.7.0 github.com/spf13/cobra v1.7.0
golang.org/x/crypto v0.12.0 golang.org/x/crypto v0.12.0
golang.org/x/net v0.14.0
gopkg.in/gcfg.v1 v1.2.3
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
) )
@@ -42,8 +42,8 @@ require (
github.com/rs/zerolog v1.30.0 // indirect github.com/rs/zerolog v1.30.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/mod v0.12.0 // indirect golang.org/x/mod v0.12.0 // indirect
golang.org/x/net v0.14.0 // indirect
golang.org/x/sys v0.12.0 // indirect golang.org/x/sys v0.12.0 // indirect
golang.org/x/text v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect
golang.org/x/tools v0.12.0 // indirect golang.org/x/tools v0.12.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
) )
-6
View File
@@ -1,7 +1,5 @@
asciigoat.org/core v0.3.9 h1:hgDDz4ecm3ZvehX++m8A/IzAt+B5oDPiRtxatzfUHPQ= asciigoat.org/core v0.3.9 h1:hgDDz4ecm3ZvehX++m8A/IzAt+B5oDPiRtxatzfUHPQ=
asciigoat.org/core v0.3.9/go.mod h1:CAaHwyw8MpAq4a1MYtN2dxJrsK+hmIdW50OndaQZYPI= asciigoat.org/core v0.3.9/go.mod h1:CAaHwyw8MpAq4a1MYtN2dxJrsK+hmIdW50OndaQZYPI=
asciigoat.org/ini v0.2.5 h1:4gRIp9rU+XQt8+HMqZO5R7GavMv9Yl2+N+je6djDIAE=
asciigoat.org/ini v0.2.5/go.mod h1:gmXzJ9XFqf1NLk5nQkj04USQ4tMtdRJHNQX6vp3DzjU=
darvaza.org/core v0.9.8 h1:luLxgfUc2pzuusYPo/Z/dC/qr9XZPKpSQw8/kS7zNUM= darvaza.org/core v0.9.8 h1:luLxgfUc2pzuusYPo/Z/dC/qr9XZPKpSQw8/kS7zNUM=
darvaza.org/core v0.9.8/go.mod h1:Dbme64naxeshQfxcVJX9ZT7AiGyIY8kldfuELVtf8mw= darvaza.org/core v0.9.8/go.mod h1:Dbme64naxeshQfxcVJX9ZT7AiGyIY8kldfuELVtf8mw=
darvaza.org/resolver v0.5.4 h1:dlSBNV14yYsp7Kg7ipwYOMNsLbrpeXa8Z0HBTa0Ryxs= darvaza.org/resolver v0.5.4 h1:dlSBNV14yYsp7Kg7ipwYOMNsLbrpeXa8Z0HBTa0Ryxs=
@@ -103,10 +101,6 @@ golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss=
golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/gcfg.v1 v1.2.3 h1:m8OOJ4ccYHnx2f4gQwpno8nAX5OGOh7RLaaz0pj3Ogs=
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+1 -5
View File
@@ -71,7 +71,7 @@ func newCephScanTODO(cfg *ceph.Config) *cephScanTODO {
return todo return todo
} }
func (m *Cluster) scanCephMonitors(opts *ScanOptions) error { func (m *Cluster) scanCephMonitors(_ *ScanOptions) error {
cfg, err := m.GetCephConfig() cfg, err := m.GetCephConfig()
switch { switch {
case os.IsNotExist(err): case os.IsNotExist(err):
@@ -94,10 +94,6 @@ func (m *Cluster) scanCephMonitors(opts *ScanOptions) error {
todo.LogMissing(m.log) todo.LogMissing(m.log)
} }
return m.initCephMonitors(opts)
}
func (m *Cluster) initCephMonitors(_ *ScanOptions) error {
// make sure every zone has one // make sure every zone has one
m.ForEachZone(func(z *Zone) bool { m.ForEachZone(func(z *Zone) bool {
_ = z.GetCephMonitors() _ = z.GetCephMonitors()
+1 -2
View File
@@ -27,8 +27,7 @@ type Cluster struct {
Domain string `json:"domain,omitempty" yaml:"domain,omitempty"` Domain string `json:"domain,omitempty" yaml:"domain,omitempty"`
CephFSID uuid.UUID `json:"ceph_fsid,omitempty" yaml:"ceph_fsid,omitempty"` CephFSID uuid.UUID `json:"ceph_fsid,omitempty" yaml:"ceph_fsid,omitempty"`
Regions []Region `json:",omitempty" yaml:",omitempty"` Zones []*Zone `json:"zones,omitempty" yaml:"zones,omitempty"`
Zones []*Zone `json:",omitempty" yaml:",omitempty"`
} }
// revive:enable:line-length-limit // revive:enable:line-length-limit
-2
View File
@@ -14,8 +14,6 @@ func (m *Cluster) init(opts *ScanOptions) error {
m.scanZoneIDs, m.scanZoneIDs,
m.scanSort, m.scanSort,
m.scanGateways, m.scanGateways,
m.initCephMonitors,
m.initRegions,
} { } {
if err := fn(opts); err != nil { if err := fn(opts); err != nil {
return err return err
-130
View File
@@ -1,130 +0,0 @@
package cluster
var (
_ MachineIterator = (*Region)(nil)
_ ZoneIterator = (*Region)(nil)
)
// Region represents a group of zones geographically related
type Region struct {
m *Cluster
zones []*Zone
Name string
Regions []string `json:",omitempty" yaml:",omitempty"`
}
// ForEachRegion calls a function for each Region of the cluster
// until instructed to terminate the loop
func (m *Cluster) ForEachRegion(fn func(r *Region) bool) {
for i := range m.Regions {
r := &m.Regions[i]
if fn(r) {
return
}
}
}
// ForEachMachine calls a function for each Machine in the region
// until instructed to terminate the loop
func (r *Region) ForEachMachine(fn func(*Machine) bool) {
r.ForEachZone(func(z *Zone) bool {
var term bool
z.ForEachMachine(func(p *Machine) bool {
term = fn(p)
return term
})
return term
})
}
// ForEachZone calls a function for each Zone in the region
// until instructed to terminate the loop
func (r *Region) ForEachZone(fn func(*Zone) bool) {
for _, p := range r.zones {
if fn(p) {
// terminate
return
}
}
}
func (m *Cluster) initRegions(_ *ScanOptions) error {
regions := make(map[string][]*Zone)
// first regions defined by zones
m.ForEachZone(func(z *Zone) bool {
for _, region := range z.Regions {
regions[region] = append(regions[region], z)
}
return false
})
// bind first level regions and their zones
for name, zones := range regions {
m.syncRegions(name, zones...)
}
// and combine zones to produce larger regions
for i := range m.Regions {
r := &m.Regions[i]
m.finishRegion(r)
}
return nil
}
func (m *Cluster) syncRegions(name string, zones ...*Zone) {
for _, r := range m.Regions {
if r.Name == name {
// found
r.m = m
r.zones = zones
return
}
}
// new
m.Regions = append(m.Regions, Region{
m: m,
zones: zones,
Name: name,
})
}
func (m *Cluster) finishRegion(r *Region) {
if r.m != nil {
// ready
return
}
r.m = m
sub := []string{}
for _, name := range r.Regions {
r2, ok := m.getRegion(name)
if !ok {
m.warn(nil).WithField("region", name).Print("unknown region")
continue
}
sub = append(sub, r2.Name)
r.zones = append(r.zones, r2.zones...)
}
r.Regions = sub
}
func (m *Cluster) getRegion(name string) (*Region, bool) {
for i := range m.Regions {
r := &m.Regions[i]
if name == r.Name {
m.finishRegion(r)
return r, true
}
}
return nil, false
}
+2 -3
View File
@@ -19,9 +19,8 @@ type Zone struct {
zones *Cluster zones *Cluster
logger `json:"-" yaml:"-"` logger `json:"-" yaml:"-"`
ID int ID int
Name string Name string
Regions []string `json:",omitempty" yaml:",omitempty"`
Machines Machines
} }
+27 -35
View File
@@ -1,38 +1,30 @@
// Package dns manages DNS entries for the cluster // Package dns manages DNS entries for the cluster
package dns package dns
import ( //// A Config defines a Region
"fmt" //type Config struct {
"net/netip" // // Name is the identifier of this Region
) // Name string
// // Regions are a list of (sub)regions that belong to this Region
// Zone represents a set of hosts with high affinity // Regions []string
type Zone struct { // // Zones are a list of Zones that directly belong to this Region
Name string // Zones []string
//}
Hosts map[int]*Host //
} //type Region struct {
// Name string
func (z *Zone) String() string { //}
if z == nil { //
return "undetermined" //type Zone struct {
} // Name string
return z.Name //
} // Machines map[int]*Machine
//}
// Host represents a member of the cluster //
type Host struct { //type Machine struct {
zone *Zone // ID int
//
ID int // Active bool
Active bool // Addrs []netip.Addr
Addrs []netip.Addr //}
} //
func (p *Host) String() string {
if p == nil {
return "undetermined"
}
return fmt.Sprintf("%s-%v", p.zone, p.ID)
}
+26
View File
@@ -0,0 +1,26 @@
package dns
//func (mgr *Manager) AddHost(_ context.Context, zone string, id int, active bool, addrs ...netip.Addr) error {
// if zone == "" || id <= 0 {
// return fs.ErrInvalid
// }
//
// z, ok := mgr.zones[zone]
// if !ok {
// z = &Zone{
// Name: zone,
// Machines: make(map[int]*Machine),
// }
//
// mgr.zones[zone] = z
// }
//
// z.Machines[id] = &Machine{
// ID: id,
// Active: active,
// Addrs: addrs,
// }
//
// return nil
//}
//
+114 -171
View File
@@ -1,173 +1,116 @@
package dns package dns
import ( // // Manager is a DNS Manager instance
"context" // type Manager struct {
"errors" // mu sync.Mutex
"io/fs" //
"net/netip" // domain string
"strings" // suffix string
// zones map[string]*Zone
"darvaza.org/core" //
"darvaza.org/slog" // p Provider
"git.jpi.io/amery/jpictl/pkg/cluster" // l slog.Logger
"golang.org/x/net/publicsuffix" // }
) //
// // ManagerOption configures a Manager
// Manager is a DNS Manager instance // type ManagerOption func(*Manager) error
type Manager struct { //
domain string // func newErrorManagerOption(err error, hint string) ManagerOption {
suffix string // return func(*Manager) error {
zones map[string]*Zone // if hint != "" {
regions map[string][]string // err = core.Wrap(err, hint)
// }
p Provider // return err
l slog.Logger // }
} // }
//
// ManagerOption configures a Manager // // WithProvider attaches a libdns Provider to the Manager
type ManagerOption func(*Manager) error // func WithProvider(p Provider) ManagerOption {
// var err error
func newErrorManagerOption(err error, hint string) ManagerOption { //
return func(*Manager) error { // if p == nil {
return core.Wrap(err, hint) // p, err = DefaultDNSProvider()
} // }
} //
// if err != nil {
// WithProvider attaches a libdns Provider to the Manager // return newErrorManagerOption(err, "WithProvider")
func WithProvider(p Provider) ManagerOption { // }
var err error //
// return func(mgr *Manager) error {
if p == nil { // mgr.p = p
p, err = DefaultDNSProvider() // return nil
} // }
// }
if err != nil { //
return newErrorManagerOption(err, "WithProvider") // // WithLogger attaches a logger to the Manager
} // func WithLogger(log slog.Logger) ManagerOption {
// if log == nil {
return func(mgr *Manager) error { // log = cluster.DefaultLogger()
mgr.p = p // }
return nil //
} // return func(mgr *Manager) error {
} // mgr.l = log
// return nil
// WithLogger attaches a logger to the Manager // }
func WithLogger(log slog.Logger) ManagerOption { // }
if log == nil { //
log = cluster.DefaultLogger() // func (mgr *Manager) setDefaults() error {
} // var opts []ManagerOption
//
return func(mgr *Manager) error { // if mgr.p == nil {
mgr.l = log // opts = append(opts, WithProvider(nil))
return nil // }
} //
} // if mgr.l == nil {
// opts = append(opts, WithLogger(nil))
func (mgr *Manager) setDefaults() error { // }
var opts []ManagerOption //
// if mgr.domain == "" || mgr.suffix == "" {
if mgr.l == nil { // return errors.New("domain not specified")
opts = append(opts, WithLogger(nil)) // }
} //
// mgr.zones = make(map[string]*Zone)
if mgr.domain == "" || mgr.suffix == "" { //
return errors.New("domain not specified") // for _, opt := range opts {
} // if err := opt(mgr); err != nil {
// return err
for _, opt := range opts { // }
if err := opt(mgr); err != nil { // }
return err // return nil
} // }
} //
return nil // // WithDomain specifies where the manager operates
} // func WithDomain(domain string) ManagerOption {
// base, err := publicsuffix.EffectiveTLDPlusOne(domain)
// WithDomain specifies where the manager operates // if err != nil {
func WithDomain(domain string) ManagerOption { // return newErrorManagerOption(err, "publicsuffix")
base, err := publicsuffix.EffectiveTLDPlusOne(domain) // }
if err != nil { //
return newErrorManagerOption(err, "publicsuffix") // suffix := strings.TrimSuffix(domain, base)
} // if suffix != "" {
// suffix = "." + suffix[:len(suffix)-1]
suffix := strings.TrimSuffix(domain, base) // }
if suffix != "" { //
suffix = "." + suffix[:len(suffix)-1] // return func(mgr *Manager) error {
} // mgr.domain = base
// mgr.suffix = suffix
return func(mgr *Manager) error { // return nil
mgr.domain = base // }
mgr.suffix = suffix // }
return nil //
} // // NewManager creates a DNS manager with the
} // func NewManager(opts ...ManagerOption) (*Manager, error) {
// mgr := new(Manager)
// NewManager creates a DNS manager with the //
func NewManager(opts ...ManagerOption) (*Manager, error) { // for _, opt := range opts {
mgr := &Manager{ // if err := opt(mgr); err != nil {
zones: make(map[string]*Zone), // return nil, err
regions: make(map[string][]string), // }
} // }
//
for _, opt := range opts { // if err := mgr.setDefaults(); err != nil {
if err := opt(mgr); err != nil { // return nil, err
return nil, err // }
} // return mgr, nil
} // }
//
if err := mgr.setDefaults(); err != nil {
return nil, err
}
return mgr, nil
}
// AddHost registers a host
func (mgr *Manager) AddHost(_ context.Context, zone string, id int,
active bool, addrs ...netip.Addr) error {
//
if zone == "" || id <= 0 {
return fs.ErrInvalid
}
z, ok := mgr.zones[zone]
if !ok {
z = &Zone{
Name: zone,
Hosts: make(map[int]*Host),
}
mgr.zones[zone] = z
}
p := &Host{
zone: z,
ID: id,
Active: active,
Addrs: SortAddrSlice(addrs),
}
z.Hosts[id] = p
if log, ok := mgr.l.Debug().WithEnabled(); ok {
log.WithField("subsystem", "dns").
WithField("zone", zone).
WithField("host", p.String()).
WithField("active", active).
Print()
}
return nil
}
// AddRegion specifies a new region and the zones it contains
func (mgr *Manager) AddRegion(_ context.Context, region string, zones ...string) error {
mgr.regions[region] = append(mgr.regions[region], zones...)
if log, ok := mgr.l.Debug().WithEnabled(); ok {
for _, zoneName := range zones {
log.WithField("subsystem", "dns").
WithField("region", region).
WithField("zone", zoneName).Print()
}
}
return nil
}
+3 -177
View File
@@ -1,9 +1,6 @@
package dns package dns
import ( import (
"bytes"
"fmt"
"io"
"net/netip" "net/netip"
"sort" "sort"
"time" "time"
@@ -12,58 +9,6 @@ import (
"github.com/libdns/libdns" "github.com/libdns/libdns"
) )
func (mgr *Manager) fqdn(name string) string {
return fmt.Sprintf("%s.%s.", name, mgr.domain)
}
// SortAddrSlice sorts a slice of [netip.Addr]
func SortAddrSlice(s []netip.Addr) []netip.Addr {
sort.Slice(s, func(i, j int) bool {
return s[i].Less(s[j])
})
return s
}
// SortAddrRecords sorts a slice of [AddrRecord]
// by Name and Address
func SortAddrRecords(s []AddrRecord) []AddrRecord {
sort.Slice(s, func(i, j int) bool {
return s[i].Name < s[j].Name
})
for _, p := range s {
SortAddrSlice(p.Addr)
}
return s
}
// SortRegions sorts regions. first by length those 3-character
// or shorter, and then by length. It's mostly aimed at
// supporting ISO-3166 order
func SortRegions(regions []string) []string {
sort.Slice(regions, func(i, j int) bool {
r1, r2 := regions[i], regions[j]
switch {
case len(r1) < 4:
switch {
case len(r1) < len(r2):
return true
case len(r1) > len(r2):
return false
default:
return r1 < r2
}
case len(r2) < 4:
return false
default:
return r1 < r2
}
})
return regions
}
// AddrRecord represents an A or AAAA record // AddrRecord represents an A or AAAA record
type AddrRecord struct { type AddrRecord struct {
Name string Name string
@@ -72,7 +17,9 @@ type AddrRecord struct {
// Sort sorts the addresses of the record // Sort sorts the addresses of the record
func (rr *AddrRecord) Sort() { func (rr *AddrRecord) Sort() {
SortAddrSlice(rr.Addr) sort.Slice(rr.Addr, func(i, j int) bool {
return rr.Addr[i].Less(rr.Addr[j])
})
} }
// Export converts the record into libdns.Record // Export converts the record into libdns.Record
@@ -89,124 +36,3 @@ func (rr *AddrRecord) Export() []libdns.Record {
return out return out
} }
// WriteTo writes the record in BIND notation
func (rr *AddrRecord) WriteTo(w io.Writer) (int64, error) {
var total int
for _, addr := range rr.Addr {
n, err := fmt.Fprint(w,
rr.Name, "\t",
1, "\t",
core.IIf(addr.Is6(), "AAAA", "A"), "\t",
addr.String(), "\n")
switch {
case err != nil:
return 0, err
case n > 0:
total += n
}
}
return int64(total), nil
}
// String converts the record into BIND entries
func (rr *AddrRecord) String() string {
var buf bytes.Buffer
_, _ = rr.WriteTo(&buf)
return buf.String()
}
func (mgr *Manager) genRegionsSorted() []string {
regions := make([]string, 0, len(mgr.regions))
for name := range mgr.regions {
regions = append(regions, name)
}
return SortRegions(regions)
}
func (mgr *Manager) genAllAddrRecords() []AddrRecord {
var out []AddrRecord
cache := make(map[string][]netip.Addr)
// zones
for _, z := range mgr.zones {
// hosts
s := mgr.genZoneHostRecords(z)
out = append(out, s...)
// zone alias
addrs := mgr.genZoneAddresses(z)
name := z.Name
out = append(out, AddrRecord{
Name: name + mgr.suffix,
Addr: addrs,
})
// and cache for regions
cache[name] = addrs
}
for _, name := range mgr.genRegionsSorted() {
var addrs []netip.Addr
for _, z := range mgr.regions[name] {
addrs = append(addrs, cache[z]...)
}
rec := AddrRecord{
Name: name + mgr.suffix,
Addr: addrs,
}
rec.Sort()
out = append(out, rec)
}
return out
}
func (*Manager) genZoneAddresses(z *Zone) []netip.Addr {
var out []netip.Addr
for _, p := range z.Hosts {
if p.Active {
out = append(out, p.Addrs...)
}
}
SortAddrSlice(out)
return out
}
func (mgr *Manager) genZoneHostRecords(z *Zone) []AddrRecord {
out := make([]AddrRecord, 0, len(z.Hosts))
for _, p := range z.Hosts {
rec := AddrRecord{
Name: p.String() + mgr.suffix,
Addr: p.Addrs,
}
out = append(out, rec)
}
SortAddrRecords(out)
return out
}
func (mgr *Manager) genRegionAddressesCached(name string,
zones map[string][]netip.Addr) []netip.Addr {
//
var addrs []netip.Addr
for _, zoneName := range mgr.regions[name] {
addrs = append(addrs, zones[zoneName]...)
}
SortAddrSlice(addrs)
return addrs
}
+170 -60
View File
@@ -1,62 +1,172 @@
package dns package dns
import ( // func (mgr *Manager) WriteTo(w io.Writer) (int64, error) {
"bytes" // var buf bytes.Buffer
"fmt" //
"io" // var zoneNames sort.StringSlice
"net/netip" //
) // // sort zones
// for name := range mgr.zones {
// WriteTo writes the DNS data for the cluster // zoneNames = append(zoneNames, name)
func (mgr *Manager) WriteTo(w io.Writer) (int64, error) { // }
var buf bytes.Buffer // sort.Sort(zoneNames)
//
cache := make(map[string][]netip.Addr) // // records
// zones := make(map[string][]libdns.Record)
// zones //
for _, z := range mgr.zones { // for _, name := range zoneNames {
mgr.writeZoneHosts(&buf, z) // z := mgr.zones[name]
//
// zone alias // if buf.Len() > 0 {
addrs := mgr.genZoneAddresses(z) // _, _ = buf.WriteRune('\n')
zoneName := z.Name // }
//
rr := AddrRecord{ // // servers
Name: mgr.fqdn(zoneName + mgr.suffix), // err := mgr.writeZoneMachinesToBuffer(&buf, name, z)
Addr: addrs, // if err != nil {
} // return 0, err
_, _ = rr.WriteTo(&buf) // }
//
// and cache for regions // // zone alias
cache[zoneName] = addrs // _, _ = fmt.Fprintf(&buf, "; %s\n", name)
} // records := mgr.genAliasRecords(name, true, z)
// mgr.Sort(records)
// regions, sorted // mgr.writeRecordsToBuffer(&buf, "", records...)
for _, name := range mgr.genRegionsSorted() { //
addrs := mgr.genRegionAddressesCached(name, cache) // zones[name] = records
// }
mgr.writeRegionAddresses(&buf, name, addrs) //
} // mgr.writeRegionToBuffer(&buf, zones, "uk", "ssd-lon")
// mgr.writeRegionToBuffer(&buf, zones, "nl", "ssd-ams")
return buf.WriteTo(w) // mgr.writeRegionToBuffer(&buf, zones, "de", "htz-fsn")
} // mgr.writeRegionToBuffer(&buf, zones, "eu", "ssd-ams", "htz-fsn")
// mgr.writeRegionToBuffer(&buf, zones, "europe", "ssd-ams", "ssd-lon", "htz-fsn")
func (mgr *Manager) writeZoneHosts(w io.Writer, z *Zone) { // mgr.writeRegionToBuffer(&buf, zones, "earth", "ssd-ams", "ssd-lon", "htz-fsn")
_, _ = fmt.Fprintf(w, ";\n; %s\n;\n", z.Name) //
// return buf.WriteTo(w)
for _, rr := range mgr.genZoneHostRecords(z) { // }
rr.Name = mgr.fqdn(rr.Name) //
_, _ = rr.WriteTo(w) // func (mgr *Manager) writeRecordsToBuffer(buf *bytes.Buffer, rename string, records ...libdns.Record) {
} // for _, rr := range records {
} // name := core.IIf(rename != "", rename, rr.Name)
// fqdn := fmt.Sprintf("%s.%s.", name, mgr.domain)
func (mgr *Manager) writeRegionAddresses(w io.Writer, name string, addrs []netip.Addr) { //
_, _ = fmt.Fprintf(w, "; %s\n", name) // _, _ = fmt.Fprintf(buf, "%s\t%v\tIN\t%s\t%v\n",
// fqdn, int(rr.TTL/time.Second), rr.Type, rr.Value)
rr := AddrRecord{ // }
Name: mgr.fqdn(name + mgr.suffix), // }
Addr: addrs, //
} // func (mgr *Manager) writeRegionToBuffer(buf *bytes.Buffer, cache map[string][]libdns.Record, name string, zones ...string) {
// var alias string
_, _ = rr.WriteTo(w) //
} // _, _ = fmt.Fprintf(buf, "\n; %s\n", name)
//
// switch {
// case mgr.suffix == "":
// // no suffix
// case strings.HasSuffix(name, mgr.suffix):
// // suffixed
// default:
// // unsuffixed
// alias = name
// name = name + mgr.suffix
// }
//
// records := []libdns.Record{}
// for _, zone := range zones {
// records = append(records, cache[zone]...)
// }
// mgr.Sort(records)
// mgr.writeRecordsToBuffer(buf, alias, records...)
//
// if alias != "" {
// rr := libdns.Record{
// Name: alias,
// TTL: 3600 * time.Second,
// Type: "CNAME",
// Value: name + "." + mgr.domain + ".",
// }
//
// mgr.writeRecordsToBuffer(buf, alias, rr)
// }
// }
//
// func (mgr *Manager) writeZoneMachinesToBuffer(buf *bytes.Buffer, zone_name string, z *Zone) error {
// // title
// _, _ = fmt.Fprintf(buf, ";\n; %s\n;\n", zone_name)
//
// // machine records
// records := mgr.genMachineRecords(z)
// mgr.writeRecordsToBuffer(buf, "", records...)
//
// return nil
// }
//
// func (mgr *Manager) genMachineRecords(z *Zone) []libdns.Record {
// var out []libdns.Record
//
// for _, p := range z.Machines {
// pqdn := fmt.Sprintf("%s-%v%s", z.Name, p.ID, mgr.suffix)
//
// for _, addr := range p.Addrs {
// out = append(out, libdns.Record{
// Name: pqdn,
// Type: core.IIf(addr.Is6(), "AAAA", "A"),
// TTL: time.Second,
// Value: addr.String(),
// })
// }
// }
//
// mgr.Sort(out)
// return out
// }
//
// func (mgr *Manager) genAliasRecords(name string, activeOnly bool, zones ...*Zone) []libdns.Record {
// var out []libdns.Record
//
// fqdn := fmt.Sprintf("%s%s", name, mgr.suffix)
// for _, z := range zones {
// for _, p := range z.Machines {
// if p.Active || !activeOnly {
// for _, addr := range p.Addrs {
// out = append(out, libdns.Record{
// Name: fqdn,
// Type: core.IIf(addr.Is6(), "AAAA", "A"),
// TTL: time.Second,
// Value: addr.String(),
// })
// }
// }
// }
// }
//
// return out
// }
//
// func (mgr *Manager) Sort(records []libdns.Record) {
// sort.SliceStable(records, func(i, j int) bool {
// a := &records[i]
// b := &records[j]
// switch {
// case a.Name < b.Name:
// return true
// case a.Name > b.Name:
// return false
// case a.Type < b.Type:
// return true
// case a.Type > b.Type:
// return false
// case a.Type == "A" || a.Type == "AAAA":
// var aAddr, bAddr netip.Addr
// aAddr.UnmarshalText([]byte(a.Value))
// bAddr.UnmarshalText([]byte(b.Value))
// return aAddr.Less(bAddr)
// case a.Value < b.Value:
// return true
// default:
// return false
// }
// })
// }
//
+14 -110
View File
@@ -2,7 +2,6 @@ package wireguard
import ( import (
"bytes" "bytes"
"errors"
"fmt" "fmt"
"io" "io"
"net/netip" "net/netip"
@@ -10,8 +9,8 @@ import (
"strings" "strings"
"text/template" "text/template"
"asciigoat.org/ini/basic"
"darvaza.org/core" "darvaza.org/core"
"gopkg.in/gcfg.v1"
) )
var configTemplate = template.Must(template.New("config").Funcs(template.FuncMap{ var configTemplate = template.Must(template.New("config").Funcs(template.FuncMap{
@@ -107,6 +106,11 @@ func (ep EndpointAddress) String() string {
} }
} }
// 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" // FromString sets the EndpointAddress from a given "[host]:port"
func (ep *EndpointAddress) FromString(s string) error { func (ep *EndpointAddress) FromString(s string) error {
host, port, err := core.SplitHostPort(s) host, port, err := core.SplitHostPort(s)
@@ -127,98 +131,6 @@ func (ep *EndpointAddress) FromString(s string) error {
return nil 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) {
var err error
out := InterfaceConfig{
Address: p.Address,
ListenPort: p.ListenPort,
}
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) { func parseAllowedIPs(data string) ([]netip.Prefix, error) {
var out []netip.Prefix var out []netip.Prefix
@@ -235,25 +147,17 @@ func parseAllowedIPs(data string) ([]netip.Prefix, error) {
return out, nil 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 // NewConfigFromReader parses a wgN.conf file
func NewConfigFromReader(r io.Reader) (*Config, error) { func NewConfigFromReader(r io.Reader) (*Config, error) {
temp := &intermediateConfig{} doc, err := basic.Decode(r)
if err != nil {
if err := gcfg.ReadInto(temp, r); err != nil {
return nil, err return nil, err
} }
return temp.Export() cfg, err := newConfigFromDocument(doc)
if err != nil {
return nil, err
}
return cfg, nil
} }
+169
View File
@@ -0,0 +1,169 @@
package wireguard
import (
"io/fs"
"strconv"
"asciigoat.org/ini/basic"
"darvaza.org/core"
)
type sectionHandler func(*Config, *basic.Section) error
var sectionMap = map[string]func(*Config, *basic.Section) error{
"Interface": loadInterfaceConfSection,
"Peer": loadPeerConfSection,
}
func loadConfSection(out *Config, src *basic.Section) error {
h, ok := sectionMap[src.Key]
if !ok {
return core.Wrapf(fs.ErrInvalid, "unknown section %q", src.Key)
}
return h(out, src)
}
func loadInterfaceConfSection(out *Config, src *basic.Section) error {
var cfg InterfaceConfig
for _, field := range src.Fields {
if err := loadInterfaceConfField(&cfg, field); err != nil {
return core.Wrap(err, "Interface")
}
}
out.Interface = cfg
return nil
}
func loadPeerConfSection(out *Config, src *basic.Section) error {
var cfg PeerConfig
for _, field := range src.Fields {
if err := loadPeerConfField(&cfg, field); err != nil {
return core.Wrapf(err, "Peer[%v]", len(out.Peer))
}
}
out.Peer = append(out.Peer, cfg)
return nil
}
// revive:disable:cyclomatic
// revive:disable:cognitive-complexity
func loadInterfaceConfField(cfg *InterfaceConfig, field basic.Field) error {
// revive:enable:cyclomatic
// revive:enable:cognitive-complexity
// TODO: refactor when asciigoat's ini parser learns to do reflection
switch field.Key {
case "Address":
if !core.IsZero(cfg.Address) {
return core.Wrapf(fs.ErrInvalid, "duplicate field %q", field.Key)
}
err := cfg.Address.UnmarshalText([]byte(field.Value))
switch {
case err != nil:
return core.Wrap(err, field.Key)
default:
return nil
}
case "PrivateKey":
if !core.IsZero(cfg.PrivateKey) {
return core.Wrapf(fs.ErrInvalid, "duplicate field %q", field.Key)
}
err := cfg.PrivateKey.UnmarshalText([]byte(field.Value))
switch {
case err != nil:
return core.Wrap(err, field.Key)
default:
return nil
}
case "ListenPort":
if cfg.ListenPort > 0 {
return core.Wrapf(fs.ErrInvalid, "duplicate field %q", field.Key)
}
u64, err := strconv.ParseUint(field.Value, 10, 16)
switch {
case err != nil:
return core.Wrap(err, field.Key)
case u64 == 0:
return core.Wrapf(fs.ErrInvalid, "invalid %q value", field.Key)
default:
cfg.ListenPort = uint16(u64)
return nil
}
default:
return core.Wrapf(fs.ErrInvalid, "unknown field %q", field.Key)
}
}
// revive:disable:cyclomatic
// revive:disable:cognitive-complexity
func loadPeerConfField(cfg *PeerConfig, field basic.Field) error {
// revive:enable:cyclomatic
// revive:enable:cognitive-complexity
switch field.Key {
case "PublicKey":
if !core.IsZero(cfg.PublicKey) {
return core.Wrapf(fs.ErrInvalid, "duplicate field %q", field.Key)
}
err := cfg.PublicKey.UnmarshalText([]byte(field.Value))
switch {
case err != nil:
return core.Wrap(err, field.Key)
default:
return nil
}
case "Endpoint":
if cfg.Endpoint.String() != "" {
return core.Wrapf(fs.ErrInvalid, "duplicate field %q", field.Key)
}
err := cfg.Endpoint.UnmarshalText([]byte(field.Value))
switch {
case err != nil:
return core.Wrap(err, field.Key)
default:
return nil
}
case "AllowedIPs":
s, err := parseAllowedIPs(field.Value)
switch {
case err != nil:
return core.Wrap(err, field.Key)
case len(s) > 0:
cfg.AllowedIPs = append(cfg.AllowedIPs, s...)
return nil
}
default:
return core.Wrapf(fs.ErrInvalid, "unknown field %q", field.Key)
}
return nil
}
func newConfigFromDocument(doc *basic.Document) (*Config, error) {
var out Config
if len(doc.Global) > 0 {
err := core.Wrap(fs.ErrInvalid, "fields before the first section")
return nil, err
}
for i := range doc.Sections {
src := &doc.Sections[i]
if err := loadConfSection(&out, src); err != nil {
return nil, err
}
}
return &out, nil
}
+24
View File
@@ -51,6 +51,30 @@ func (pub PublicKey) String() string {
} }
} }
// UnmarshalText loads the value from base64
func (key *PrivateKey) UnmarshalText(b []byte) error {
v, err := PrivateKeyFromBase64(string(b))
switch {
case err != nil:
return err
default:
*key = v
return nil
}
}
// UnmarshalText loads the value from base64
func (pub *PublicKey) UnmarshalText(b []byte) error {
v, err := PublicKeyFromBase64(string(b))
switch {
case err != nil:
return err
default:
*pub = v
return nil
}
}
// MarshalJSON encodes the key for JSON, omitting empty. // MarshalJSON encodes the key for JSON, omitting empty.
func (key PrivateKey) MarshalJSON() ([]byte, error) { func (key PrivateKey) MarshalJSON() ([]byte, error) {
return encodeKeyJSON(key.String()) return encodeKeyJSON(key.String())