Implement referral resolution for nameserver names without glue records
CI / test (pull_request) Waiting to run
CI / test (pull_request) Waiting to run
- Add Resolve method to Referral struct for sub-traversal from root - Add ResolveNS method to Traverser for nameserver name resolution - Add CircularReferralError and UnresolvableNameserverError types - Add visited map tracking to detect circular referral chains - Add depth tracking to prevent infinite loops - Integrate referral resolution into processReferral - Add unit tests for new error types - Handle edge cases: circular referrals, unresolvable nameservers, max depth Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
@@ -36,6 +36,9 @@ type TraversalResult struct {
|
||||
type Traverser struct {
|
||||
config *TraverserConfig
|
||||
exchange dns.ExchangeFunc
|
||||
visited map[string]bool
|
||||
depth int
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func NewTraverser(cfg *TraverserConfig) *Traverser {
|
||||
@@ -45,6 +48,8 @@ func NewTraverser(cfg *TraverserConfig) *Traverser {
|
||||
return &Traverser{
|
||||
config: cfg,
|
||||
exchange: nil,
|
||||
visited: make(map[string]bool),
|
||||
depth: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,13 +160,32 @@ func (t *Traverser) discoverRoots(ctx context.Context) ([]net.IP, error) {
|
||||
|
||||
func (t *Traverser) processReferral(ctx context.Context, ref *Referral, cache *InfoCache) *Response {
|
||||
if !ref.HasAddresses() {
|
||||
t.mu.Lock()
|
||||
visitedCopy := make(map[string]bool)
|
||||
for k, v := range t.visited {
|
||||
visitedCopy[k] = v
|
||||
}
|
||||
t.mu.Unlock()
|
||||
|
||||
ref.Addresses = t.resolveGlueViaSystem(ctx, ref.Name, cache)
|
||||
if len(ref.Addresses) > 0 {
|
||||
ref.State = StateResolved
|
||||
} else {
|
||||
return &Response{
|
||||
Referral: ref,
|
||||
Type: RespError,
|
||||
addrs, err := t.ResolveNS(ctx, ref.Name, cache, visitedCopy, t.depth)
|
||||
if err != nil {
|
||||
return &Response{
|
||||
Referral: ref,
|
||||
Type: RespError,
|
||||
}
|
||||
}
|
||||
if len(addrs) > 0 {
|
||||
ref.Addresses = addrs
|
||||
ref.State = StateResolved
|
||||
} else {
|
||||
return &Response{
|
||||
Referral: ref,
|
||||
Type: RespError,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -179,6 +203,131 @@ func (t *Traverser) processReferral(ctx context.Context, ref *Referral, cache *I
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Traverser) ResolveNS(ctx context.Context, nsName string, cache *InfoCache, visited map[string]bool, depth int) ([]net.IP, error) {
|
||||
if cache != nil {
|
||||
if addrs := cache.LookupGlue(nsName); len(addrs) > 0 {
|
||||
return addrs, nil
|
||||
}
|
||||
}
|
||||
|
||||
if visited != nil {
|
||||
if visited[nsName] {
|
||||
return nil, &CircularReferralError{
|
||||
Name: nsName,
|
||||
Chain: getVisitedNames(visited),
|
||||
}
|
||||
}
|
||||
visited[nsName] = true
|
||||
}
|
||||
|
||||
if depth > DefaultMaxDepth {
|
||||
return nil, &UnresolvableNameserverError{
|
||||
Name: nsName,
|
||||
Reason: "max depth exceeded",
|
||||
}
|
||||
}
|
||||
|
||||
roots, err := t.discoverRoots(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("root discovery: %w", err)
|
||||
}
|
||||
|
||||
ref := NewReferral(nsName, dns.TypeA, ".", 0, 1.0, nil)
|
||||
ref.Addresses = roots
|
||||
ref.State = StateResolved
|
||||
|
||||
traversalCache := NewInfoCache(nil)
|
||||
if visited != nil {
|
||||
for name := range visited {
|
||||
traversalCache.StoreGlue(name, []net.IP{})
|
||||
}
|
||||
}
|
||||
|
||||
var addrs []net.IP
|
||||
var lastErr error
|
||||
|
||||
stack := NewStack(DefaultMaxDepth)
|
||||
stack.Push(ref)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, fmt.Errorf("resolution cancelled: %w", ctx.Err())
|
||||
default:
|
||||
}
|
||||
|
||||
current := stack.Pop()
|
||||
if current == nil {
|
||||
break
|
||||
}
|
||||
|
||||
cacheForStep := traversalCache
|
||||
if current.Parent != nil {
|
||||
cacheForStep = traversalCache.Child()
|
||||
}
|
||||
|
||||
resp := t.processReferral(ctx, current, cacheForStep)
|
||||
|
||||
if resp.Type == RespAnswer && len(resp.Decoded.Answers) > 0 {
|
||||
for _, rr := range resp.Decoded.Answers {
|
||||
if a, ok := rr.(*miekgdns.A); ok {
|
||||
addrs = append(addrs, a.A)
|
||||
}
|
||||
if aaaa, ok := rr.(*miekgdns.AAAA); ok {
|
||||
addrs = append(addrs, aaaa.AAAA)
|
||||
}
|
||||
}
|
||||
if len(addrs) > 0 {
|
||||
if cache != nil {
|
||||
cache.StoreGlue(nsName, addrs)
|
||||
}
|
||||
return addrs, nil
|
||||
}
|
||||
}
|
||||
|
||||
if resp.Type == RespNXDOMAIN {
|
||||
lastErr = &UnresolvableNameserverError{
|
||||
Name: nsName,
|
||||
Reason: "NXDOMAIN",
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if resp.Type == RespSERVFAIL || resp.Type == RespError {
|
||||
lastErr = fmt.Errorf("server error resolving %s: %s", nsName, resp.Type)
|
||||
continue
|
||||
}
|
||||
|
||||
if resp.Type == RespReferral {
|
||||
children := resp.ChildReferrals()
|
||||
for _, child := range children {
|
||||
if visited != nil && visited[child.Name] {
|
||||
continue
|
||||
}
|
||||
if !stack.Push(child) {
|
||||
lastErr = &UnresolvableNameserverError{
|
||||
Name: nsName,
|
||||
Reason: "max depth exceeded during resolution",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(addrs) > 0 {
|
||||
return addrs, nil
|
||||
}
|
||||
|
||||
if lastErr != nil {
|
||||
return nil, lastErr
|
||||
}
|
||||
|
||||
return nil, &UnresolvableNameserverError{
|
||||
Name: nsName,
|
||||
Reason: "resolution exhausted without answer",
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Traverser) queryServer(ctx context.Context, ref *Referral, server net.IP, cache *InfoCache) *Response {
|
||||
var msg *miekgdns.Msg
|
||||
var err error
|
||||
|
||||
Reference in New Issue
Block a user