Skip to content

Commit

Permalink
server: add initial Resolver factory
Browse files Browse the repository at this point in the history
only RootLookuper for now, but supporting reflection
and ability to ignore AAAA responses

Signed-off-by: Alejandro Mery <amery@jpi.io>
  • Loading branch information
amery committed Dec 23, 2023
1 parent d050814 commit 40f46d1
Show file tree
Hide file tree
Showing 4 changed files with 298 additions and 2 deletions.
20 changes: 20 additions & 0 deletions pkg/server/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ type Config struct {
Name string `yaml:"name" default:"localhost"`
Version string `yaml:"version" default:"unspecified"`
Authors string `yaml:"authors" default:"JPI Technologies <oss@jpi.io>"`

Resolvers []ResolverConfig `yaml:"resolvers,omitempty" toml:",omitempty" json:",omitempty"`
}

// SetDefaults fills gaps in the Config
Expand All @@ -28,6 +30,24 @@ func (cfg *Config) SetDefaults() error {
cfg.Logger = discard.New()
}

if err := cfg.setDefaultResolvers(); err != nil {
return err
}

// and the rest
return defaults.Set(cfg)
}

func (cfg *Config) setDefaultResolvers() error {
if len(cfg.Resolvers) == 0 {
// default resolver
cfg.Resolvers = []ResolverConfig{
{
Name: "root",
Iterative: true,
},
}
}

return nil
}
262 changes: 262 additions & 0 deletions pkg/server/resolver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
package server

import (
"context"
"fmt"
"strings"
"time"

"github.com/miekg/dns"

"darvaza.org/core"
"darvaza.org/resolver"
"darvaza.org/resolver/pkg/client"
"darvaza.org/resolver/pkg/reflect"
"darvaza.org/slog"
)

var (
_ resolver.Exchanger = (*Resolver)(nil)

_ core.Unwrappable = (*ResolverError)(nil)
)

func makeResolvers(conf []ResolverConfig, log slog.Logger) ([]string, map[string]*Resolver, error) {
names, m, err := makeResolversMap(conf)
if err != nil {
return nil, nil, err
}

res := make(map[string]*Resolver)
for len(res) < len(m) {
name, next, err := nextMakeResolvers(res, m)
if err != nil {
// broken dependencies tree
return nil, nil, err
}

r, err := m[name].New(next, log)
if err != nil {
// failed to build resolver
return nil, nil, err
}

res[name] = r
}

return names, res, nil
}

func makeResolversMap(conf []ResolverConfig) ([]string, map[string]ResolverConfig, error) {
names := make([]string, 0, len(conf))
m := make(map[string]ResolverConfig)

for _, rc := range conf {
rc.Name = strings.ToLower(rc.Name)
rc.Next = strings.ToLower(rc.Next)

if _, ok := m[rc.Name]; ok {
err := &ResolverError{
Resolver: rc.Name,
Reason: "duplicate name",
Err: core.ErrExists,
}
return nil, nil, err
}

names = append(names, rc.Name)
m[rc.Name] = rc
}

return names, m, nil
}

func nextMakeResolvers(res map[string]*Resolver,
conf map[string]ResolverConfig) (string, *Resolver, error) {
//
var err error

for name, rc := range conf {
if _, ok := res[name]; ok {
// already made, next
continue
}

r, ok := getMakeResolvers(rc.Next, res)
if ok {
// dependency is ready
return name, r, nil
}

if err == nil {
// first unresolvable
err = &ResolverError{
Resolver: name,
Reason: fmt.Sprintf("resolver %q not found", rc.Next),
Err: core.ErrNotExists,
}
}
}

// none ready
return "", nil, err
}

func getMakeResolvers(name string, res map[string]*Resolver) (*Resolver, bool) {
if name == "" {
return nil, true
}

r, ok := res[name]
return r, ok
}

// ResolverConfig describes a [Resolver]
type ResolverConfig struct {
Name string `yaml:"name"`
Next string `yaml:"next,omitempty" toml:",omitempty" json:",omitempty"`

DisableAAAA bool `yaml:"disable_aaaa,omitempty" toml:",omitempty" json:",omitempty"`
Iterative bool `yaml:"iterative,omitempty" toml:",omitempty" json:",omitempty"`
Recursive bool `yaml:"recursive,omitempty" toml:",omitempty" json:",omitempty"`
Servers []string `yaml:"servers,omitempty" toml:",omitempty" json:",omitempty"`
Suffixes []string `yaml:"suffixes,omitempty" toml:",omitempty" json:",omitempty"`
}

// New creates a new [Resolver]
func (rc ResolverConfig) New(next resolver.Exchanger, log slog.Logger) (*Resolver, error) {
r := &Resolver{
Name: rc.Name,
Next: next,
Suffixes: rc.Suffixes,
}

if rc.Iterative {
if err := rc.setupIterative(r, log); err != nil {
return nil, err
}
return r, nil
}

if err := rc.setup(r); err != nil {
return nil, err
}

return r, nil
}

func (rc ResolverConfig) setup(*Resolver) error {
return &ResolverError{
Resolver: rc.Name,
Err: core.ErrNotImplemented,
}
}

func (rc ResolverConfig) setupIterative(r *Resolver, log slog.Logger) error {
var e resolver.Exchanger

switch {
case len(rc.Servers) > 0:
return &ResolverError{
Resolver: rc.Name,
Reason: "iterative resolver with specific servers not yet supported.",
}
case rc.Recursive:
return &ResolverError{
Resolver: rc.Name,
Reason: "iterative resolvers can't be recursive.",
}
}

c, err := rc.newClient(log)
if err != nil {
return &ResolverError{
Resolver: rc.Name,
Reason: "failed to create client",
Err: err,
}
}

e, err = resolver.NewRootLookuperWithClient("", c)
if err != nil {
return &ResolverError{
Resolver: rc.Name,
Reason: "failed to create iterative lookuper",
Err: err,
}
}

// TODO: add cache

r.e, _ = reflect.NewWithExchanger(rc.Name, log, e)
return nil
}

func (rc ResolverConfig) newClient(log slog.Logger) (client.Client, error) {
var c client.Client

// UDP/TCP client mux
udp, err := reflect.NewWithClient(rc.Name+"-udp", log, &dns.Client{Net: "udp"})
if err != nil {
return nil, err
}

tcp, err := reflect.NewWithClient(rc.Name+"-tcp", log, &dns.Client{Net: "tcp"})
if err != nil {
return nil, err
}

c, err = client.NewAutoClient(udp, tcp, 1*time.Second)
if err != nil {
return nil, err
}

if rc.DisableAAAA {
// remove AAAA entries
c = client.NewNoAAAA(c)
}

// and wrap it again for logging
return reflect.NewWithClient(rc.Name+"-mux", log, c)
}

// ResolverError is an error that references the name of the resolver
type ResolverError struct {
Resolver string
Reason string
Err error
}

func (e ResolverError) Error() string {
var s []string

s = append(s, fmt.Sprintf("resolver[%q]", e.Resolver))
if e.Reason != "" {
s = append(s, e.Reason)
}
if e.Err != nil {
s = append(s, e.Err.Error())
}

return strings.Join(s, ": ")
}

func (e ResolverError) Unwrap() error {
return e.Err
}

// Resolver is a custom [resolver.Exchange]
type Resolver struct {
Name string
Next resolver.Exchanger
Suffixes []string

e resolver.Exchanger
}

// Exchange implements the [resolver.Exchange] interface
func (r *Resolver) Exchange(ctx context.Context, req *dns.Msg) (*dns.Msg, error) {
// TODO: filter suffixes
// TODO: pass to Next
return r.e.Exchange(ctx, req)
}
6 changes: 4 additions & 2 deletions pkg/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ package server
// Server is a Penne server
type Server struct {
cfg Config

res map[string]*Resolver
}

func (*Server) init() error {
return nil
func (srv *Server) init() error {
return srv.initResolvers()
}

// New creates a new [Server] based on the given [Config]
Expand Down
12 changes: 12 additions & 0 deletions pkg/server/server_dns.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,18 @@ var (
_ resolver.Exchanger = (*Server)(nil)
)

func (srv *Server) initResolvers() error {
// build resolvers
_, res, err := makeResolvers(srv.cfg.Resolvers, srv.cfg.Logger)
if err != nil {
return err
}

// store
srv.res = res
return nil
}

// Exchange handles only CHAOS requests
func (srv *Server) Exchange(_ context.Context, req *dns.Msg) (*dns.Msg, error) {
var answers []dns.RR
Expand Down

0 comments on commit 40f46d1

Please sign in to comment.