Skip to content

Commit

Permalink
Add best cold path computation and perform recursive queries NS
Browse files Browse the repository at this point in the history
  • Loading branch information
rs committed Aug 9, 2018
1 parent 37f2851 commit e46102c
Show file tree
Hide file tree
Showing 5 changed files with 357 additions and 169 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ This tool performs a DNS resolution by tracing the delegation path from the root
* Query all name servers in parallel and report stats for each
* Report about non glued name server lookup time
* Enable DNSSEC query option to better emulate name server queries
* Compute the cold best path as if the resolver started with an empty cache to recurse queried name

## Usage

Expand Down
114 changes: 114 additions & 0 deletions client/cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package client

import (
"fmt"
"strings"
"sync"
"time"

"github.com/miekg/dns"
)

var roots = []Server{
{"A.root-servers.net.", true, 446311, []string{"198.41.0.4", "2001:503:ba3e::2:30"}, 0, nil},
{"B.root-servers.net.", true, 446311, []string{"199.9.14.201", "2001:500:200::b"}, 0, nil},
{"C.root-servers.net.", true, 446311, []string{"192.33.4.12", "2001:500:2::c"}, 0, nil},
{"D.root-servers.net.", true, 446311, []string{"199.7.91.13", "2001:500:2d::d"}, 0, nil},
{"E.root-servers.net.", true, 446311, []string{"192.203.230.10", "2001:500:a8::e"}, 0, nil},
{"F.root-servers.net.", true, 446311, []string{"192.5.5.241", "2001:500:2f::f"}, 0, nil},
{"G.root-servers.net.", true, 446311, []string{"192.112.36.4", "2001:500:12::d0d"}, 0, nil},
{"H.root-servers.net.", true, 446311, []string{"198.97.190.53", "2001:500:1::53"}, 0, nil},
{"I.root-servers.net.", true, 446311, []string{"192.36.148.17", "2001:7fe::53"}, 0, nil},
{"J.root-servers.net.", true, 446311, []string{"192.58.128.30", "2001:503:c27::2:30"}, 0, nil},
{"K.root-servers.net.", true, 446311, []string{"193.0.14.129", "2001:7fd::1"}, 0, nil},
{"L.root-servers.net.", true, 446311, []string{"199.7.83.42", "2001:500:9f::42"}, 0, nil},
{"M.root-servers.net.", true, 446311, []string{"202.12.27.33", "2001:dc3::35"}, 0, nil},
}

// Server is a name server hostname with associated IP addresses.
type Server struct {
Name string
HasGlue bool
TTL uint32
Addrs []string
LookupRTT time.Duration
LookupErr error
}

func (s Server) String() string {
return fmt.Sprintf("%s %d NS (%s): %v", s.Name, s.TTL, strings.Join(s.Addrs, ","), s.LookupErr)
}

type Servers []Server

func (s Servers) String() string {
if len(s) > 0 {
if s[0].Name == "A.root-servers.net." {
return "*.root-servers.net."
}
}
names := make([]string, 0, len(s))
for _, s := range s {
names = append(names, s.Name)
}
return strings.Join(names, ", ")
}

// DelegationCache store and retrive delegations.
type DelegationCache struct {
c map[string]Servers
mu sync.Mutex
}

// Get returns the most specific name servers for domain with its matching label.
func (d *DelegationCache) Get(domain string) (label string, servers Servers) {
d.mu.Lock()
defer d.mu.Unlock()
for offset, end := 0, false; !end; offset, end = dns.NextLabel(domain, offset) {
label = domain[offset:]
var found bool
if servers, found = d.c[label]; found {
return
}
}
return ".", roots
}

// Add adds a server as a delegation for domain. If addrs is not specified,
// server will be looked up. An error is returned in case of lookup error.
func (d *DelegationCache) Add(domain string, s Server) error {
d.mu.Lock()
defer d.mu.Unlock()
for _, s2 := range d.c[domain] {
if s2.Name == s.Name {
return nil
}
}
if d.c == nil {
d.c = map[string]Servers{}
}
d.c[domain] = append(d.c[domain], s)
return nil
}

// LookupCache stores mixed lookup results for A and AAAA records of labels with
// not support of TTL.
type LookupCache struct {
c map[string][]string
mu sync.Mutex
}

func (c *LookupCache) Set(label string, addrs []string) {
c.mu.Lock()
defer c.mu.Unlock()
if c.c == nil {
c.c = map[string][]string{}
}
c.c[strings.ToLower(label)] = addrs
}

func (c *LookupCache) Get(label string) []string {
c.mu.Lock()
defer c.mu.Unlock()
return c.c[strings.ToLower(label)]
}
169 changes: 169 additions & 0 deletions client/client.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package client

import (
"errors"
"net"
"time"

Expand All @@ -10,6 +11,8 @@ import (
// Client is a DNS client capable of performing parallel requests.
type Client struct {
dns.Client
DCache DelegationCache
LCache LookupCache
}

// Response stores a DNS response.
Expand All @@ -33,6 +36,19 @@ func (rs Responses) Fastest() *Response {
return nil
}

type Tracer struct {
GotDelegateResponses func(i int, m *dns.Msg, rs Responses)
FollowingCNAME func(domain, target string)
}

// New creates a new Client.
func New() Client {
return Client{
DCache: DelegationCache{},
LCache: LookupCache{},
}
}

// ParallelQuery perform an exchange using m with all servers in parallel and
// return all responses.
func (c *Client) ParallelQuery(m *dns.Msg, servers Servers) Responses {
Expand All @@ -57,3 +73,156 @@ func (c *Client) ParallelQuery(m *dns.Msg, servers Servers) Responses {
}
return rs
}

// RecursiveQuery performs a recursive query by querying all the available name
// servers to gather statistics.
func (c *Client) RecursiveQuery(m *dns.Msg, tracer Tracer) (r *dns.Msg, rtt time.Duration, err error) {
// TODO: check m got a single question
m = m.Copy()
qname := m.Question[0].Name
qtype := m.Question[0].Qtype
for i := 1; i < 100; i++ {
deleg, servers := c.DCache.Get(qname)
m.Question[0].Name = qname
rs := c.ParallelQuery(m, servers)

var r *dns.Msg
fr := rs.Fastest()
if fr != nil {
r = fr.Msg
}
if r == nil {
if len(rs) > 0 {
return rs[0].Msg, rtt + rs[0].RTT, rs[0].Err
}
return nil, rtt, errors.New("no response")
}
rtt += fr.RTT

done := false
for _, rr := range r.Answer {
if rr.Header().Rrtype == qtype && rr.Header().Name == qname {
done = true
break
}
}

if !done {
lrttc := make(chan time.Duration)
lc := 0
for _, ns := range r.Ns {
ns, ok := ns.(*dns.NS)
if !ok {
continue // skip DS records
}
var addrs []string
for _, rr := range r.Extra {
if a, ok := rr.(*dns.A); ok && a.Header().Name == ns.Ns {
addrs = append(addrs, a.A.String())
}
}
s := Server{
Name: ns.Ns,
HasGlue: len(addrs) > 0,
TTL: ns.Header().Ttl,
Addrs: addrs,
}
if !s.HasGlue {
lc++
go func() {
var err error
lm := m.Copy()
lm.SetQuestion(s.Name, 0) // qtypes are set by lookup host
s.Addrs, s.LookupRTT, err = c.lookupHost(lm)
if err != nil {
s.LookupErr = err
}
c.DCache.Add(ns.Header().Name, s)
lrttc <- s.LookupRTT
}()
continue
}
c.DCache.Add(ns.Header().Name, s)
if tracer.GotDelegateResponses == nil {
// If not traced, do not resolve all NS
break
}
}
var lrtt time.Duration
for ; lc > 0; lc-- {
d := <-lrttc
if lrtt == 0 || lrtt > d {
lrtt = d
}
}
rtt += lrtt
}

if tracer.GotDelegateResponses != nil {
tracer.GotDelegateResponses(i, m.Copy(), rs)
}

if len(r.Answer) > 0 {
var cname string
for _, rr := range r.Answer {
if rr.Header().Rrtype == dns.TypeCNAME {
cname = rr.Header().Name
qname = rr.(*dns.CNAME).Target
}
}
if cname != "" {
if tracer.FollowingCNAME != nil {
tracer.FollowingCNAME(cname, qname)
}
continue
}
return r, rtt, nil
}

if label, _ := c.DCache.Get(qname); len(r.Ns) == 0 || deleg == label {
return r, rtt, nil
}
}
return nil, rtt, nil
}

func (c *Client) lookupHost(m *dns.Msg) (addrs []string, rtt time.Duration, err error) {
qname := m.Question[0].Name
addrs = c.LCache.Get(qname)
if len(addrs) > 0 {
return addrs, 0, nil
}
qtypes := []uint16{dns.TypeA, dns.TypeAAAA}
rs := make(chan Response)
for _, qtype := range qtypes {
m := m.Copy()
m.Question[0].Qtype = qtype
go func() {
r, rtt, err := c.RecursiveQuery(m, Tracer{})
rs <- Response{
Msg: r,
Err: err,
RTT: rtt,
}
}()
}
for range qtypes {
r := <-rs
if r.Err != nil {
return nil, 0, err
}
if r.RTT > rtt {
rtt = r.RTT // get the longest of the two // queries
}
for _, rr := range r.Msg.Answer {
switch rr := rr.(type) {
case *dns.A:
addrs = append(addrs, rr.A.String())
case *dns.AAAA:
addrs = append(addrs, rr.AAAA.String())
}
}
}
c.LCache.Set(qname, addrs)
return
}
Loading

0 comments on commit e46102c

Please sign in to comment.