github.com/core-coin/go-core/v2@v2.1.9/p2p/dnsdisc/client.go (about)

     1  // Copyright 2019 by the Authors
     2  // This file is part of the go-core library.
     3  //
     4  // The go-core library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-core library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-core library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package dnsdisc
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"fmt"
    23  	"math/rand"
    24  	"net"
    25  	"strings"
    26  	"sync"
    27  	"time"
    28  
    29  	lru "github.com/hashicorp/golang-lru"
    30  	"golang.org/x/time/rate"
    31  
    32  	"github.com/core-coin/go-core/v2/common/mclock"
    33  	"github.com/core-coin/go-core/v2/crypto"
    34  	"github.com/core-coin/go-core/v2/log"
    35  	"github.com/core-coin/go-core/v2/p2p/enode"
    36  	"github.com/core-coin/go-core/v2/p2p/enr"
    37  )
    38  
    39  // Client discovers nodes by querying DNS servers.
    40  type Client struct {
    41  	cfg     Config
    42  	clock   mclock.Clock
    43  	entries *lru.Cache
    44  }
    45  
    46  // Config holds configuration options for the client.
    47  type Config struct {
    48  	Timeout         time.Duration      // timeout used for DNS lookups (default 5s)
    49  	RecheckInterval time.Duration      // time between tree root update checks (default 30min)
    50  	CacheLimit      int                // maximum number of cached records (default 1000)
    51  	RateLimit       float64            // maximum DNS requests / second (default 3)
    52  	ValidSchemes    enr.IdentityScheme // acceptable ENR identity schemes (default enode.ValidSchemes)
    53  	Resolver        Resolver           // the DNS resolver to use (defaults to system DNS)
    54  	Logger          log.Logger         // destination of client log messages (defaults to root logger)
    55  }
    56  
    57  // Resolver is a DNS resolver that can query TXT records.
    58  type Resolver interface {
    59  	LookupTXT(ctx context.Context, domain string) ([]string, error)
    60  }
    61  
    62  func (cfg Config) withDefaults() Config {
    63  	const (
    64  		defaultTimeout   = 5 * time.Second
    65  		defaultRecheck   = 30 * time.Minute
    66  		defaultRateLimit = 3
    67  		defaultCache     = 1000
    68  	)
    69  	if cfg.Timeout == 0 {
    70  		cfg.Timeout = defaultTimeout
    71  	}
    72  	if cfg.RecheckInterval == 0 {
    73  		cfg.RecheckInterval = defaultRecheck
    74  	}
    75  	if cfg.CacheLimit == 0 {
    76  		cfg.CacheLimit = defaultCache
    77  	}
    78  	if cfg.RateLimit == 0 {
    79  		cfg.RateLimit = defaultRateLimit
    80  	}
    81  	if cfg.ValidSchemes == nil {
    82  		cfg.ValidSchemes = enode.ValidSchemes
    83  	}
    84  	if cfg.Resolver == nil {
    85  		cfg.Resolver = new(net.Resolver)
    86  	}
    87  	if cfg.Logger == nil {
    88  		cfg.Logger = log.Root()
    89  	}
    90  	return cfg
    91  }
    92  
    93  // NewClient creates a client.
    94  func NewClient(cfg Config) *Client {
    95  	cfg = cfg.withDefaults()
    96  	cache, err := lru.New(cfg.CacheLimit)
    97  	if err != nil {
    98  		panic(err)
    99  	}
   100  	rlimit := rate.NewLimiter(rate.Limit(cfg.RateLimit), 10)
   101  	cfg.Resolver = &rateLimitResolver{cfg.Resolver, rlimit}
   102  	return &Client{cfg: cfg, entries: cache, clock: mclock.System{}}
   103  }
   104  
   105  // SyncTree downloads the entire node tree at the given URL.
   106  func (c *Client) SyncTree(url string) (*Tree, error) {
   107  	le, err := parseLink(url)
   108  	if err != nil {
   109  		return nil, fmt.Errorf("invalid enrtree URL: %v", err)
   110  	}
   111  	ct := newClientTree(c, new(linkCache), le)
   112  	t := &Tree{entries: make(map[string]entry)}
   113  	if err := ct.syncAll(t.entries); err != nil {
   114  		return nil, err
   115  	}
   116  	t.root = ct.root
   117  	return t, nil
   118  }
   119  
   120  // NewIterator creates an iterator that visits all nodes at the
   121  // given tree URLs.
   122  func (c *Client) NewIterator(urls ...string) (enode.Iterator, error) {
   123  	it := c.newRandomIterator()
   124  	for _, url := range urls {
   125  		if err := it.addTree(url); err != nil {
   126  			return nil, err
   127  		}
   128  	}
   129  	return it, nil
   130  }
   131  
   132  // resolveRoot retrieves a root entry via DNS.
   133  func (c *Client) resolveRoot(ctx context.Context, loc *linkEntry) (rootEntry, error) {
   134  	txts, err := c.cfg.Resolver.LookupTXT(ctx, loc.domain)
   135  	c.cfg.Logger.Trace("Updating DNS discovery root", "tree", loc.domain, "err", err)
   136  	if err != nil {
   137  		return rootEntry{}, err
   138  	}
   139  	for _, txt := range txts {
   140  		if strings.HasPrefix(txt, rootPrefix) {
   141  			return parseAndVerifyRoot(txt, loc)
   142  		}
   143  	}
   144  	return rootEntry{}, nameError{loc.domain, errNoRoot}
   145  }
   146  
   147  func parseAndVerifyRoot(txt string, loc *linkEntry) (rootEntry, error) {
   148  	e, err := parseRoot(txt)
   149  	if err != nil {
   150  		return e, err
   151  	}
   152  	if !e.verifySignature(loc.pubkey) {
   153  		return e, entryError{typ: "root", err: errInvalidSig}
   154  	}
   155  	return e, nil
   156  }
   157  
   158  // resolveEntry retrieves an entry from the cache or fetches it from the network
   159  // if it isn't cached.
   160  func (c *Client) resolveEntry(ctx context.Context, domain, hash string) (entry, error) {
   161  	cacheKey := truncateHash(hash)
   162  	if e, ok := c.entries.Get(cacheKey); ok {
   163  		return e.(entry), nil
   164  	}
   165  	e, err := c.doResolveEntry(ctx, domain, hash)
   166  	if err != nil {
   167  		return nil, err
   168  	}
   169  	c.entries.Add(cacheKey, e)
   170  	return e, nil
   171  }
   172  
   173  // doResolveEntry fetches an entry via DNS.
   174  func (c *Client) doResolveEntry(ctx context.Context, domain, hash string) (entry, error) {
   175  	wantHash, err := b32format.DecodeString(hash)
   176  	if err != nil {
   177  		return nil, fmt.Errorf("invalid base32 hash")
   178  	}
   179  	name := hash + "." + domain
   180  	txts, err := c.cfg.Resolver.LookupTXT(ctx, hash+"."+domain)
   181  	c.cfg.Logger.Trace("DNS discovery lookup", "name", name, "err", err)
   182  	if err != nil {
   183  		return nil, err
   184  	}
   185  	for _, txt := range txts {
   186  		e, err := parseEntry(txt, c.cfg.ValidSchemes)
   187  		if err == errUnknownEntry {
   188  			continue
   189  		}
   190  		if !bytes.HasPrefix(crypto.SHA3([]byte(txt)), wantHash) {
   191  			err = nameError{name, errHashMismatch}
   192  		} else if err != nil {
   193  			err = nameError{name, err}
   194  		}
   195  		return e, err
   196  	}
   197  	return nil, nameError{name, errNoEntry}
   198  }
   199  
   200  // rateLimitResolver applies a rate limit to a Resolver.
   201  type rateLimitResolver struct {
   202  	r       Resolver
   203  	limiter *rate.Limiter
   204  }
   205  
   206  func (r *rateLimitResolver) LookupTXT(ctx context.Context, domain string) ([]string, error) {
   207  	if err := r.limiter.Wait(ctx); err != nil {
   208  		return nil, err
   209  	}
   210  	return r.r.LookupTXT(ctx, domain)
   211  }
   212  
   213  // randomIterator traverses a set of trees and returns nodes found in them.
   214  type randomIterator struct {
   215  	cur      *enode.Node
   216  	ctx      context.Context
   217  	cancelFn context.CancelFunc
   218  	c        *Client
   219  
   220  	mu    sync.Mutex
   221  	trees map[string]*clientTree // all trees
   222  	lc    linkCache              // tracks tree dependencies
   223  }
   224  
   225  func (c *Client) newRandomIterator() *randomIterator {
   226  	ctx, cancel := context.WithCancel(context.Background())
   227  	return &randomIterator{
   228  		c:        c,
   229  		ctx:      ctx,
   230  		cancelFn: cancel,
   231  		trees:    make(map[string]*clientTree),
   232  	}
   233  }
   234  
   235  // Node returns the current node.
   236  func (it *randomIterator) Node() *enode.Node {
   237  	return it.cur
   238  }
   239  
   240  // Close closes the iterator.
   241  func (it *randomIterator) Close() {
   242  	it.mu.Lock()
   243  	defer it.mu.Unlock()
   244  
   245  	it.cancelFn()
   246  	it.trees = nil
   247  }
   248  
   249  // Next moves the iterator to the next node.
   250  func (it *randomIterator) Next() bool {
   251  	it.cur = it.nextNode()
   252  	return it.cur != nil
   253  }
   254  
   255  // addTree adds an enrtree:// URL to the iterator.
   256  func (it *randomIterator) addTree(url string) error {
   257  	le, err := parseLink(url)
   258  	if err != nil {
   259  		return fmt.Errorf("invalid enrtree URL: %v", err)
   260  	}
   261  	it.lc.addLink("", le.str)
   262  	return nil
   263  }
   264  
   265  // nextNode syncs random tree entries until it finds a node.
   266  func (it *randomIterator) nextNode() *enode.Node {
   267  	for {
   268  		ct := it.nextTree()
   269  		if ct == nil {
   270  			return nil
   271  		}
   272  		n, err := ct.syncRandom(it.ctx)
   273  		if err != nil {
   274  			if err == it.ctx.Err() {
   275  				return nil // context canceled.
   276  			}
   277  			it.c.cfg.Logger.Debug("Error in DNS random node sync", "tree", ct.loc.domain, "err", err)
   278  			continue
   279  		}
   280  		if n != nil {
   281  			return n
   282  		}
   283  	}
   284  }
   285  
   286  // nextTree returns a random tree.
   287  func (it *randomIterator) nextTree() *clientTree {
   288  	it.mu.Lock()
   289  	defer it.mu.Unlock()
   290  
   291  	if it.lc.changed {
   292  		it.rebuildTrees()
   293  		it.lc.changed = false
   294  	}
   295  	if len(it.trees) == 0 {
   296  		return nil
   297  	}
   298  	limit := rand.Intn(len(it.trees))
   299  	for _, ct := range it.trees {
   300  		if limit == 0 {
   301  			return ct
   302  		}
   303  		limit--
   304  	}
   305  	return nil
   306  }
   307  
   308  // rebuildTrees rebuilds the 'trees' map.
   309  func (it *randomIterator) rebuildTrees() {
   310  	// Delete removed trees.
   311  	for loc := range it.trees {
   312  		if !it.lc.isReferenced(loc) {
   313  			delete(it.trees, loc)
   314  		}
   315  	}
   316  	// Add new trees.
   317  	for loc := range it.lc.backrefs {
   318  		if it.trees[loc] == nil {
   319  			link, _ := parseLink(linkPrefix + loc)
   320  			it.trees[loc] = newClientTree(it.c, &it.lc, link)
   321  		}
   322  	}
   323  }