github.com/Unheilbar/quorum@v1.0.0/p2p/dnsdisc/client.go (about)

     1  // Copyright 2018 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum 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-ethereum 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-ethereum 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  	"github.com/ethereum/go-ethereum/common/mclock"
    30  	"github.com/ethereum/go-ethereum/crypto"
    31  	"github.com/ethereum/go-ethereum/log"
    32  	"github.com/ethereum/go-ethereum/p2p/enode"
    33  	"github.com/ethereum/go-ethereum/p2p/enr"
    34  	lru "github.com/hashicorp/golang-lru"
    35  	"golang.org/x/sync/singleflight"
    36  	"golang.org/x/time/rate"
    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  	ratelimit    *rate.Limiter
    45  	singleflight singleflight.Group
    46  }
    47  
    48  // Config holds configuration options for the client.
    49  type Config struct {
    50  	Timeout         time.Duration      // timeout used for DNS lookups (default 5s)
    51  	RecheckInterval time.Duration      // time between tree root update checks (default 30min)
    52  	CacheLimit      int                // maximum number of cached records (default 1000)
    53  	RateLimit       float64            // maximum DNS requests / second (default 3)
    54  	ValidSchemes    enr.IdentityScheme // acceptable ENR identity schemes (default enode.ValidSchemes)
    55  	Resolver        Resolver           // the DNS resolver to use (defaults to system DNS)
    56  	Logger          log.Logger         // destination of client log messages (defaults to root logger)
    57  }
    58  
    59  // Resolver is a DNS resolver that can query TXT records.
    60  type Resolver interface {
    61  	LookupTXT(ctx context.Context, domain string) ([]string, error)
    62  }
    63  
    64  func (cfg Config) withDefaults() Config {
    65  	const (
    66  		defaultTimeout   = 5 * time.Second
    67  		defaultRecheck   = 30 * time.Minute
    68  		defaultRateLimit = 3
    69  		defaultCache     = 1000
    70  	)
    71  	if cfg.Timeout == 0 {
    72  		cfg.Timeout = defaultTimeout
    73  	}
    74  	if cfg.RecheckInterval == 0 {
    75  		cfg.RecheckInterval = defaultRecheck
    76  	}
    77  	if cfg.CacheLimit == 0 {
    78  		cfg.CacheLimit = defaultCache
    79  	}
    80  	if cfg.RateLimit == 0 {
    81  		cfg.RateLimit = defaultRateLimit
    82  	}
    83  	if cfg.ValidSchemes == nil {
    84  		cfg.ValidSchemes = enode.ValidSchemes
    85  	}
    86  	if cfg.Resolver == nil {
    87  		cfg.Resolver = new(net.Resolver)
    88  	}
    89  	if cfg.Logger == nil {
    90  		cfg.Logger = log.Root()
    91  	}
    92  	return cfg
    93  }
    94  
    95  // NewClient creates a client.
    96  func NewClient(cfg Config) *Client {
    97  	cfg = cfg.withDefaults()
    98  	cache, err := lru.New(cfg.CacheLimit)
    99  	if err != nil {
   100  		panic(err)
   101  	}
   102  	rlimit := rate.NewLimiter(rate.Limit(cfg.RateLimit), 10)
   103  	return &Client{
   104  		cfg:       cfg,
   105  		entries:   cache,
   106  		clock:     mclock.System{},
   107  		ratelimit: rlimit,
   108  	}
   109  }
   110  
   111  // SyncTree downloads the entire node tree at the given URL.
   112  func (c *Client) SyncTree(url string) (*Tree, error) {
   113  	le, err := parseLink(url)
   114  	if err != nil {
   115  		return nil, fmt.Errorf("invalid enrtree URL: %v", err)
   116  	}
   117  	ct := newClientTree(c, new(linkCache), le)
   118  	t := &Tree{entries: make(map[string]entry)}
   119  	if err := ct.syncAll(t.entries); err != nil {
   120  		return nil, err
   121  	}
   122  	t.root = ct.root
   123  	return t, nil
   124  }
   125  
   126  // NewIterator creates an iterator that visits all nodes at the
   127  // given tree URLs.
   128  func (c *Client) NewIterator(urls ...string) (enode.Iterator, error) {
   129  	it := c.newRandomIterator()
   130  	for _, url := range urls {
   131  		if err := it.addTree(url); err != nil {
   132  			return nil, err
   133  		}
   134  	}
   135  	return it, nil
   136  }
   137  
   138  // resolveRoot retrieves a root entry via DNS.
   139  func (c *Client) resolveRoot(ctx context.Context, loc *linkEntry) (rootEntry, error) {
   140  	e, err, _ := c.singleflight.Do(loc.str, func() (interface{}, error) {
   141  		txts, err := c.cfg.Resolver.LookupTXT(ctx, loc.domain)
   142  		c.cfg.Logger.Trace("Updating DNS discovery root", "tree", loc.domain, "err", err)
   143  		if err != nil {
   144  			return rootEntry{}, err
   145  		}
   146  		for _, txt := range txts {
   147  			if strings.HasPrefix(txt, rootPrefix) {
   148  				return parseAndVerifyRoot(txt, loc)
   149  			}
   150  		}
   151  		return rootEntry{}, nameError{loc.domain, errNoRoot}
   152  	})
   153  	return e.(rootEntry), err
   154  }
   155  
   156  func parseAndVerifyRoot(txt string, loc *linkEntry) (rootEntry, error) {
   157  	e, err := parseRoot(txt)
   158  	if err != nil {
   159  		return e, err
   160  	}
   161  	if !e.verifySignature(loc.pubkey) {
   162  		return e, entryError{typ: "root", err: errInvalidSig}
   163  	}
   164  	return e, nil
   165  }
   166  
   167  // resolveEntry retrieves an entry from the cache or fetches it from the network
   168  // if it isn't cached.
   169  func (c *Client) resolveEntry(ctx context.Context, domain, hash string) (entry, error) {
   170  	// The rate limit always applies, even when the result might be cached. This is
   171  	// important because it avoids hot-spinning in consumers of node iterators created on
   172  	// this client.
   173  	if err := c.ratelimit.Wait(ctx); err != nil {
   174  		return nil, err
   175  	}
   176  	cacheKey := truncateHash(hash)
   177  	if e, ok := c.entries.Get(cacheKey); ok {
   178  		return e.(entry), nil
   179  	}
   180  
   181  	ei, err, _ := c.singleflight.Do(cacheKey, func() (interface{}, error) {
   182  		e, err := c.doResolveEntry(ctx, domain, hash)
   183  		if err != nil {
   184  			return nil, err
   185  		}
   186  		c.entries.Add(cacheKey, e)
   187  		return e, nil
   188  	})
   189  	e, _ := ei.(entry)
   190  	return e, err
   191  }
   192  
   193  // doResolveEntry fetches an entry via DNS.
   194  func (c *Client) doResolveEntry(ctx context.Context, domain, hash string) (entry, error) {
   195  	wantHash, err := b32format.DecodeString(hash)
   196  	if err != nil {
   197  		return nil, fmt.Errorf("invalid base32 hash")
   198  	}
   199  	name := hash + "." + domain
   200  	txts, err := c.cfg.Resolver.LookupTXT(ctx, hash+"."+domain)
   201  	c.cfg.Logger.Trace("DNS discovery lookup", "name", name, "err", err)
   202  	if err != nil {
   203  		return nil, err
   204  	}
   205  	for _, txt := range txts {
   206  		e, err := parseEntry(txt, c.cfg.ValidSchemes)
   207  		if err == errUnknownEntry {
   208  			continue
   209  		}
   210  		if !bytes.HasPrefix(crypto.Keccak256([]byte(txt)), wantHash) {
   211  			err = nameError{name, errHashMismatch}
   212  		} else if err != nil {
   213  			err = nameError{name, err}
   214  		}
   215  		return e, err
   216  	}
   217  	return nil, nameError{name, errNoEntry}
   218  }
   219  
   220  // randomIterator traverses a set of trees and returns nodes found in them.
   221  type randomIterator struct {
   222  	cur      *enode.Node
   223  	ctx      context.Context
   224  	cancelFn context.CancelFunc
   225  	c        *Client
   226  
   227  	mu    sync.Mutex
   228  	lc    linkCache              // tracks tree dependencies
   229  	trees map[string]*clientTree // all trees
   230  	// buffers for syncableTrees
   231  	syncableList []*clientTree
   232  	disabledList []*clientTree
   233  }
   234  
   235  func (c *Client) newRandomIterator() *randomIterator {
   236  	ctx, cancel := context.WithCancel(context.Background())
   237  	return &randomIterator{
   238  		c:        c,
   239  		ctx:      ctx,
   240  		cancelFn: cancel,
   241  		trees:    make(map[string]*clientTree),
   242  	}
   243  }
   244  
   245  // Node returns the current node.
   246  func (it *randomIterator) Node() *enode.Node {
   247  	return it.cur
   248  }
   249  
   250  // Close closes the iterator.
   251  func (it *randomIterator) Close() {
   252  	it.cancelFn()
   253  
   254  	it.mu.Lock()
   255  	defer it.mu.Unlock()
   256  	it.trees = nil
   257  }
   258  
   259  // Next moves the iterator to the next node.
   260  func (it *randomIterator) Next() bool {
   261  	it.cur = it.nextNode()
   262  	return it.cur != nil
   263  }
   264  
   265  // addTree adds an enrtree:// URL to the iterator.
   266  func (it *randomIterator) addTree(url string) error {
   267  	le, err := parseLink(url)
   268  	if err != nil {
   269  		return fmt.Errorf("invalid enrtree URL: %v", err)
   270  	}
   271  	it.lc.addLink("", le.str)
   272  	return nil
   273  }
   274  
   275  // nextNode syncs random tree entries until it finds a node.
   276  func (it *randomIterator) nextNode() *enode.Node {
   277  	for {
   278  		ct := it.pickTree()
   279  		if ct == nil {
   280  			return nil
   281  		}
   282  		n, err := ct.syncRandom(it.ctx)
   283  		if err != nil {
   284  			if err == it.ctx.Err() {
   285  				return nil // context canceled.
   286  			}
   287  			it.c.cfg.Logger.Debug("Error in DNS random node sync", "tree", ct.loc.domain, "err", err)
   288  			continue
   289  		}
   290  		if n != nil {
   291  			return n
   292  		}
   293  	}
   294  }
   295  
   296  // pickTree returns a random tree to sync from.
   297  func (it *randomIterator) pickTree() *clientTree {
   298  	it.mu.Lock()
   299  	defer it.mu.Unlock()
   300  
   301  	// Rebuild the trees map if any links have changed.
   302  	if it.lc.changed {
   303  		it.rebuildTrees()
   304  		it.lc.changed = false
   305  	}
   306  
   307  	for {
   308  		canSync, trees := it.syncableTrees()
   309  		switch {
   310  		case canSync:
   311  			// Pick a random tree.
   312  			return trees[rand.Intn(len(trees))]
   313  		case len(trees) > 0:
   314  			// No sync action can be performed on any tree right now. The only meaningful
   315  			// thing to do is waiting for any root record to get updated.
   316  			if !it.waitForRootUpdates(trees) {
   317  				// Iterator was closed while waiting.
   318  				return nil
   319  			}
   320  		default:
   321  			// There are no trees left, the iterator was closed.
   322  			return nil
   323  		}
   324  	}
   325  }
   326  
   327  // syncableTrees finds trees on which any meaningful sync action can be performed.
   328  func (it *randomIterator) syncableTrees() (canSync bool, trees []*clientTree) {
   329  	// Resize tree lists.
   330  	it.syncableList = it.syncableList[:0]
   331  	it.disabledList = it.disabledList[:0]
   332  
   333  	// Partition them into the two lists.
   334  	for _, ct := range it.trees {
   335  		if ct.canSyncRandom() {
   336  			it.syncableList = append(it.syncableList, ct)
   337  		} else {
   338  			it.disabledList = append(it.disabledList, ct)
   339  		}
   340  	}
   341  	if len(it.syncableList) > 0 {
   342  		return true, it.syncableList
   343  	}
   344  	return false, it.disabledList
   345  }
   346  
   347  // waitForRootUpdates waits for the closest scheduled root check time on the given trees.
   348  func (it *randomIterator) waitForRootUpdates(trees []*clientTree) bool {
   349  	var minTree *clientTree
   350  	var nextCheck mclock.AbsTime
   351  	for _, ct := range trees {
   352  		check := ct.nextScheduledRootCheck()
   353  		if minTree == nil || check < nextCheck {
   354  			minTree = ct
   355  			nextCheck = check
   356  		}
   357  	}
   358  
   359  	sleep := nextCheck.Sub(it.c.clock.Now())
   360  	it.c.cfg.Logger.Debug("DNS iterator waiting for root updates", "sleep", sleep, "tree", minTree.loc.domain)
   361  	timeout := it.c.clock.NewTimer(sleep)
   362  	defer timeout.Stop()
   363  	select {
   364  	case <-timeout.C():
   365  		return true
   366  	case <-it.ctx.Done():
   367  		return false // Iterator was closed.
   368  	}
   369  }
   370  
   371  // rebuildTrees rebuilds the 'trees' map.
   372  func (it *randomIterator) rebuildTrees() {
   373  	// Delete removed trees.
   374  	for loc := range it.trees {
   375  		if !it.lc.isReferenced(loc) {
   376  			delete(it.trees, loc)
   377  		}
   378  	}
   379  	// Add new trees.
   380  	for loc := range it.lc.backrefs {
   381  		if it.trees[loc] == nil {
   382  			link, _ := parseLink(linkPrefix + loc)
   383  			it.trees[loc] = newClientTree(it.c, &it.lc, link)
   384  		}
   385  	}
   386  }