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.
230 lines
4.5 KiB
230 lines
4.5 KiB
package dns |
|
|
|
import ( |
|
"context" |
|
"io/fs" |
|
"net/netip" |
|
"strings" |
|
|
|
"darvaza.org/core" |
|
"darvaza.org/slog" |
|
"github.com/libdns/libdns" |
|
"golang.org/x/net/publicsuffix" |
|
|
|
"git.jpi.io/amery/jpictl/pkg/cluster" |
|
) |
|
|
|
// Manager is a DNS Manager instance |
|
type Manager struct { |
|
domain string |
|
suffix string |
|
zones map[string]*Zone |
|
regions map[string][]string |
|
|
|
p Provider |
|
l slog.Logger |
|
} |
|
|
|
// ManagerOption configures a Manager |
|
type ManagerOption func(*Manager) error |
|
|
|
func newErrorManagerOption(err error, hint string) ManagerOption { |
|
return func(*Manager) error { |
|
return core.Wrap(err, hint) |
|
} |
|
} |
|
|
|
// WithProvider attaches a libdns Provider to the Manager |
|
func WithProvider(p Provider) ManagerOption { |
|
var err error |
|
|
|
if p == nil { |
|
p, err = DefaultDNSProvider() |
|
} |
|
|
|
if err != nil { |
|
return newErrorManagerOption(err, "WithProvider") |
|
} |
|
|
|
return func(mgr *Manager) error { |
|
mgr.p = p |
|
return nil |
|
} |
|
} |
|
|
|
// WithLogger attaches a logger to the Manager |
|
func WithLogger(log slog.Logger) ManagerOption { |
|
if log == nil { |
|
log = cluster.DefaultLogger() |
|
} |
|
|
|
return func(mgr *Manager) error { |
|
mgr.l = log |
|
return nil |
|
} |
|
} |
|
|
|
func (mgr *Manager) setDefaults() error { |
|
var opts []ManagerOption |
|
|
|
if mgr.l == nil { |
|
opts = append(opts, WithLogger(nil)) |
|
} |
|
|
|
if mgr.domain == "" || mgr.suffix == "" { |
|
return ErrNoDomain |
|
} |
|
|
|
for _, opt := range opts { |
|
if err := opt(mgr); err != nil { |
|
return err |
|
} |
|
} |
|
return nil |
|
} |
|
|
|
// WithDomain specifies where the manager operates |
|
func WithDomain(domain string) ManagerOption { |
|
base, err := publicsuffix.EffectiveTLDPlusOne(domain) |
|
if err != nil { |
|
return newErrorManagerOption(err, "publicsuffix") |
|
} |
|
|
|
suffix := strings.TrimSuffix(domain, base) |
|
if suffix != "" { |
|
suffix = "." + suffix[:len(suffix)-1] |
|
} |
|
|
|
return func(mgr *Manager) error { |
|
mgr.domain = base |
|
mgr.suffix = suffix |
|
return nil |
|
} |
|
} |
|
|
|
// NewManager creates a DNS manager with the |
|
func NewManager(opts ...ManagerOption) (*Manager, error) { |
|
mgr := &Manager{ |
|
zones: make(map[string]*Zone), |
|
regions: make(map[string][]string), |
|
} |
|
|
|
for _, opt := range opts { |
|
if err := opt(mgr); err != nil { |
|
return nil, err |
|
} |
|
} |
|
|
|
if err := mgr.setDefaults(); err != nil { |
|
return nil, err |
|
} |
|
return mgr, nil |
|
} |
|
|
|
// GetRecords pulls all the address records on DNS for our domain, |
|
// optionally only those matching the given names. |
|
func (mgr *Manager) GetRecords(ctx context.Context, names ...string) ([]libdns.Record, error) { |
|
if mgr.p == nil { |
|
return nil, ErrNoDNSProvider |
|
} |
|
|
|
recs, err := mgr.p.GetRecords(ctx, mgr.domain) |
|
switch { |
|
case err != nil: |
|
// failed |
|
return nil, err |
|
case len(recs) == 0: |
|
// empty |
|
return []libdns.Record{}, nil |
|
case mgr.suffix == "" && len(names) == 0: |
|
// unfiltered |
|
return recs, nil |
|
default: |
|
// filtered |
|
recs = mgr.filterRecords(recs, names...) |
|
return recs, nil |
|
} |
|
} |
|
|
|
func (mgr *Manager) filterRecords(recs []libdns.Record, names ...string) []libdns.Record { |
|
out := make([]libdns.Record, 0, len(recs)) |
|
for _, rr := range recs { |
|
name, ok := mgr.matchSuffix(rr) |
|
switch { |
|
case !ok: |
|
// skip, wrong subdomain |
|
continue |
|
case len(names) == 0: |
|
// unfiltered, take it |
|
case !core.SliceContains(names, name): |
|
// skip, not one of the requested names |
|
continue |
|
} |
|
|
|
out = append(out, rr) |
|
} |
|
|
|
return out |
|
} |
|
|
|
func (mgr *Manager) matchSuffix(rr libdns.Record) (string, bool) { |
|
if mgr.suffix == "" { |
|
// no suffix |
|
return rr.Name, true |
|
} |
|
|
|
// remove suffix |
|
return strings.CutSuffix(rr.Name, mgr.suffix) |
|
} |
|
|
|
// 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 |
|
}
|
|
|