-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
server: add initial Resolver factory
only RootLookuper for now, but supporting reflection and ability to ignore AAAA responses Signed-off-by: Alejandro Mery <amery@jpi.io>
- Loading branch information
Showing
4 changed files
with
298 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters