557
vendor/github.com/ethereum/go-ethereum/p2p/dial.go
generated
vendored
Normal file
557
vendor/github.com/ethereum/go-ethereum/p2p/dial.go
generated
vendored
Normal file
@@ -0,0 +1,557 @@
|
||||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package p2p
|
||||
|
||||
import (
|
||||
"context"
|
||||
crand "crypto/rand"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
mrand "math/rand"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/mclock"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethereum/go-ethereum/p2p/netutil"
|
||||
)
|
||||
|
||||
const (
|
||||
// This is the amount of time spent waiting in between redialing a certain node. The
|
||||
// limit is a bit higher than inboundThrottleTime to prevent failing dials in small
|
||||
// private networks.
|
||||
dialHistoryExpiration = inboundThrottleTime + 5*time.Second
|
||||
|
||||
// Config for the "Looking for peers" message.
|
||||
dialStatsLogInterval = 10 * time.Second // printed at most this often
|
||||
dialStatsPeerLimit = 3 // but not if more than this many dialed peers
|
||||
|
||||
// Endpoint resolution is throttled with bounded backoff.
|
||||
initialResolveDelay = 60 * time.Second
|
||||
maxResolveDelay = time.Hour
|
||||
)
|
||||
|
||||
// NodeDialer is used to connect to nodes in the network, typically by using
|
||||
// an underlying net.Dialer but also using net.Pipe in tests.
|
||||
type NodeDialer interface {
|
||||
Dial(context.Context, *enode.Node) (net.Conn, error)
|
||||
}
|
||||
|
||||
type nodeResolver interface {
|
||||
Resolve(*enode.Node) *enode.Node
|
||||
}
|
||||
|
||||
// tcpDialer implements NodeDialer using real TCP connections.
|
||||
type tcpDialer struct {
|
||||
d *net.Dialer
|
||||
}
|
||||
|
||||
func (t tcpDialer) Dial(ctx context.Context, dest *enode.Node) (net.Conn, error) {
|
||||
return t.d.DialContext(ctx, "tcp", nodeAddr(dest).String())
|
||||
}
|
||||
|
||||
func nodeAddr(n *enode.Node) net.Addr {
|
||||
return &net.TCPAddr{IP: n.IP(), Port: n.TCP()}
|
||||
}
|
||||
|
||||
// checkDial errors:
|
||||
var (
|
||||
errSelf = errors.New("is self")
|
||||
errAlreadyDialing = errors.New("already dialing")
|
||||
errAlreadyConnected = errors.New("already connected")
|
||||
errRecentlyDialed = errors.New("recently dialed")
|
||||
errNetRestrict = errors.New("not contained in netrestrict list")
|
||||
errNoPort = errors.New("node does not provide TCP port")
|
||||
)
|
||||
|
||||
// dialer creates outbound connections and submits them into Server.
|
||||
// Two types of peer connections can be created:
|
||||
//
|
||||
// - static dials are pre-configured connections. The dialer attempts
|
||||
// keep these nodes connected at all times.
|
||||
//
|
||||
// - dynamic dials are created from node discovery results. The dialer
|
||||
// continuously reads candidate nodes from its input iterator and attempts
|
||||
// to create peer connections to nodes arriving through the iterator.
|
||||
type dialScheduler struct {
|
||||
dialConfig
|
||||
setupFunc dialSetupFunc
|
||||
wg sync.WaitGroup
|
||||
cancel context.CancelFunc
|
||||
ctx context.Context
|
||||
nodesIn chan *enode.Node
|
||||
doneCh chan *dialTask
|
||||
addStaticCh chan *enode.Node
|
||||
remStaticCh chan *enode.Node
|
||||
addPeerCh chan *conn
|
||||
remPeerCh chan *conn
|
||||
|
||||
// Everything below here belongs to loop and
|
||||
// should only be accessed by code on the loop goroutine.
|
||||
dialing map[enode.ID]*dialTask // active tasks
|
||||
peers map[enode.ID]struct{} // all connected peers
|
||||
dialPeers int // current number of dialed peers
|
||||
|
||||
// The static map tracks all static dial tasks. The subset of usable static dial tasks
|
||||
// (i.e. those passing checkDial) is kept in staticPool. The scheduler prefers
|
||||
// launching random static tasks from the pool over launching dynamic dials from the
|
||||
// iterator.
|
||||
static map[enode.ID]*dialTask
|
||||
staticPool []*dialTask
|
||||
|
||||
// The dial history keeps recently dialed nodes. Members of history are not dialed.
|
||||
history expHeap
|
||||
historyTimer mclock.Timer
|
||||
historyTimerTime mclock.AbsTime
|
||||
|
||||
// for logStats
|
||||
lastStatsLog mclock.AbsTime
|
||||
doneSinceLastLog int
|
||||
}
|
||||
|
||||
type dialSetupFunc func(net.Conn, connFlag, *enode.Node) error
|
||||
|
||||
type dialConfig struct {
|
||||
self enode.ID // our own ID
|
||||
maxDialPeers int // maximum number of dialed peers
|
||||
maxActiveDials int // maximum number of active dials
|
||||
netRestrict *netutil.Netlist // IP netrestrict list, disabled if nil
|
||||
resolver nodeResolver
|
||||
dialer NodeDialer
|
||||
log log.Logger
|
||||
clock mclock.Clock
|
||||
rand *mrand.Rand
|
||||
}
|
||||
|
||||
func (cfg dialConfig) withDefaults() dialConfig {
|
||||
if cfg.maxActiveDials == 0 {
|
||||
cfg.maxActiveDials = defaultMaxPendingPeers
|
||||
}
|
||||
if cfg.log == nil {
|
||||
cfg.log = log.Root()
|
||||
}
|
||||
if cfg.clock == nil {
|
||||
cfg.clock = mclock.System{}
|
||||
}
|
||||
if cfg.rand == nil {
|
||||
seedb := make([]byte, 8)
|
||||
crand.Read(seedb)
|
||||
seed := int64(binary.BigEndian.Uint64(seedb))
|
||||
cfg.rand = mrand.New(mrand.NewSource(seed))
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
func newDialScheduler(config dialConfig, it enode.Iterator, setupFunc dialSetupFunc) *dialScheduler {
|
||||
d := &dialScheduler{
|
||||
dialConfig: config.withDefaults(),
|
||||
setupFunc: setupFunc,
|
||||
dialing: make(map[enode.ID]*dialTask),
|
||||
static: make(map[enode.ID]*dialTask),
|
||||
peers: make(map[enode.ID]struct{}),
|
||||
doneCh: make(chan *dialTask),
|
||||
nodesIn: make(chan *enode.Node),
|
||||
addStaticCh: make(chan *enode.Node),
|
||||
remStaticCh: make(chan *enode.Node),
|
||||
addPeerCh: make(chan *conn),
|
||||
remPeerCh: make(chan *conn),
|
||||
}
|
||||
d.lastStatsLog = d.clock.Now()
|
||||
d.ctx, d.cancel = context.WithCancel(context.Background())
|
||||
d.wg.Add(2)
|
||||
go d.readNodes(it)
|
||||
go d.loop(it)
|
||||
return d
|
||||
}
|
||||
|
||||
// stop shuts down the dialer, canceling all current dial tasks.
|
||||
func (d *dialScheduler) stop() {
|
||||
d.cancel()
|
||||
d.wg.Wait()
|
||||
}
|
||||
|
||||
// addStatic adds a static dial candidate.
|
||||
func (d *dialScheduler) addStatic(n *enode.Node) {
|
||||
select {
|
||||
case d.addStaticCh <- n:
|
||||
case <-d.ctx.Done():
|
||||
}
|
||||
}
|
||||
|
||||
// removeStatic removes a static dial candidate.
|
||||
func (d *dialScheduler) removeStatic(n *enode.Node) {
|
||||
select {
|
||||
case d.remStaticCh <- n:
|
||||
case <-d.ctx.Done():
|
||||
}
|
||||
}
|
||||
|
||||
// peerAdded updates the peer set.
|
||||
func (d *dialScheduler) peerAdded(c *conn) {
|
||||
select {
|
||||
case d.addPeerCh <- c:
|
||||
case <-d.ctx.Done():
|
||||
}
|
||||
}
|
||||
|
||||
// peerRemoved updates the peer set.
|
||||
func (d *dialScheduler) peerRemoved(c *conn) {
|
||||
select {
|
||||
case d.remPeerCh <- c:
|
||||
case <-d.ctx.Done():
|
||||
}
|
||||
}
|
||||
|
||||
// loop is the main loop of the dialer.
|
||||
func (d *dialScheduler) loop(it enode.Iterator) {
|
||||
var (
|
||||
nodesCh chan *enode.Node
|
||||
historyExp = make(chan struct{}, 1)
|
||||
)
|
||||
|
||||
loop:
|
||||
for {
|
||||
// Launch new dials if slots are available.
|
||||
slots := d.freeDialSlots()
|
||||
slots -= d.startStaticDials()
|
||||
if slots > 0 {
|
||||
nodesCh = d.nodesIn
|
||||
} else {
|
||||
nodesCh = nil
|
||||
}
|
||||
d.rearmHistoryTimer(historyExp)
|
||||
d.logStats()
|
||||
|
||||
select {
|
||||
case node := <-nodesCh:
|
||||
if err := d.checkDial(node); err != nil {
|
||||
d.log.Trace("Discarding dial candidate", "id", node.ID(), "ip", node.IP(), "reason", err)
|
||||
} else {
|
||||
d.startDial(newDialTask(node, dynDialedConn))
|
||||
}
|
||||
|
||||
case task := <-d.doneCh:
|
||||
id := task.dest.ID()
|
||||
delete(d.dialing, id)
|
||||
d.updateStaticPool(id)
|
||||
d.doneSinceLastLog++
|
||||
|
||||
case c := <-d.addPeerCh:
|
||||
if c.is(dynDialedConn) || c.is(staticDialedConn) {
|
||||
d.dialPeers++
|
||||
}
|
||||
id := c.node.ID()
|
||||
d.peers[id] = struct{}{}
|
||||
// Remove from static pool because the node is now connected.
|
||||
task := d.static[id]
|
||||
if task != nil && task.staticPoolIndex >= 0 {
|
||||
d.removeFromStaticPool(task.staticPoolIndex)
|
||||
}
|
||||
// TODO: cancel dials to connected peers
|
||||
|
||||
case c := <-d.remPeerCh:
|
||||
if c.is(dynDialedConn) || c.is(staticDialedConn) {
|
||||
d.dialPeers--
|
||||
}
|
||||
delete(d.peers, c.node.ID())
|
||||
d.updateStaticPool(c.node.ID())
|
||||
|
||||
case node := <-d.addStaticCh:
|
||||
id := node.ID()
|
||||
_, exists := d.static[id]
|
||||
d.log.Trace("Adding static node", "id", id, "ip", node.IP(), "added", !exists)
|
||||
if exists {
|
||||
continue loop
|
||||
}
|
||||
task := newDialTask(node, staticDialedConn)
|
||||
d.static[id] = task
|
||||
if d.checkDial(node) == nil {
|
||||
d.addToStaticPool(task)
|
||||
}
|
||||
|
||||
case node := <-d.remStaticCh:
|
||||
id := node.ID()
|
||||
task := d.static[id]
|
||||
d.log.Trace("Removing static node", "id", id, "ok", task != nil)
|
||||
if task != nil {
|
||||
delete(d.static, id)
|
||||
if task.staticPoolIndex >= 0 {
|
||||
d.removeFromStaticPool(task.staticPoolIndex)
|
||||
}
|
||||
}
|
||||
|
||||
case <-historyExp:
|
||||
d.expireHistory()
|
||||
|
||||
case <-d.ctx.Done():
|
||||
it.Close()
|
||||
break loop
|
||||
}
|
||||
}
|
||||
|
||||
d.stopHistoryTimer(historyExp)
|
||||
for range d.dialing {
|
||||
<-d.doneCh
|
||||
}
|
||||
d.wg.Done()
|
||||
}
|
||||
|
||||
// readNodes runs in its own goroutine and delivers nodes from
|
||||
// the input iterator to the nodesIn channel.
|
||||
func (d *dialScheduler) readNodes(it enode.Iterator) {
|
||||
defer d.wg.Done()
|
||||
|
||||
for it.Next() {
|
||||
select {
|
||||
case d.nodesIn <- it.Node():
|
||||
case <-d.ctx.Done():
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// logStats prints dialer statistics to the log. The message is suppressed when enough
|
||||
// peers are connected because users should only see it while their client is starting up
|
||||
// or comes back online.
|
||||
func (d *dialScheduler) logStats() {
|
||||
now := d.clock.Now()
|
||||
if d.lastStatsLog.Add(dialStatsLogInterval) > now {
|
||||
return
|
||||
}
|
||||
if d.dialPeers < dialStatsPeerLimit && d.dialPeers < d.maxDialPeers {
|
||||
d.log.Info("Looking for peers", "peercount", len(d.peers), "tried", d.doneSinceLastLog, "static", len(d.static))
|
||||
}
|
||||
d.doneSinceLastLog = 0
|
||||
d.lastStatsLog = now
|
||||
}
|
||||
|
||||
// rearmHistoryTimer configures d.historyTimer to fire when the
|
||||
// next item in d.history expires.
|
||||
func (d *dialScheduler) rearmHistoryTimer(ch chan struct{}) {
|
||||
if len(d.history) == 0 || d.historyTimerTime == d.history.nextExpiry() {
|
||||
return
|
||||
}
|
||||
d.stopHistoryTimer(ch)
|
||||
d.historyTimerTime = d.history.nextExpiry()
|
||||
timeout := time.Duration(d.historyTimerTime - d.clock.Now())
|
||||
d.historyTimer = d.clock.AfterFunc(timeout, func() { ch <- struct{}{} })
|
||||
}
|
||||
|
||||
// stopHistoryTimer stops the timer and drains the channel it sends on.
|
||||
func (d *dialScheduler) stopHistoryTimer(ch chan struct{}) {
|
||||
if d.historyTimer != nil && !d.historyTimer.Stop() {
|
||||
<-ch
|
||||
}
|
||||
}
|
||||
|
||||
// expireHistory removes expired items from d.history.
|
||||
func (d *dialScheduler) expireHistory() {
|
||||
d.historyTimer.Stop()
|
||||
d.historyTimer = nil
|
||||
d.historyTimerTime = 0
|
||||
d.history.expire(d.clock.Now(), func(hkey string) {
|
||||
var id enode.ID
|
||||
copy(id[:], hkey)
|
||||
d.updateStaticPool(id)
|
||||
})
|
||||
}
|
||||
|
||||
// freeDialSlots returns the number of free dial slots. The result can be negative
|
||||
// when peers are connected while their task is still running.
|
||||
func (d *dialScheduler) freeDialSlots() int {
|
||||
slots := (d.maxDialPeers - d.dialPeers) * 2
|
||||
if slots > d.maxActiveDials {
|
||||
slots = d.maxActiveDials
|
||||
}
|
||||
free := slots - len(d.dialing)
|
||||
return free
|
||||
}
|
||||
|
||||
// checkDial returns an error if node n should not be dialed.
|
||||
func (d *dialScheduler) checkDial(n *enode.Node) error {
|
||||
if n.ID() == d.self {
|
||||
return errSelf
|
||||
}
|
||||
if n.IP() != nil && n.TCP() == 0 {
|
||||
// This check can trigger if a non-TCP node is found
|
||||
// by discovery. If there is no IP, the node is a static
|
||||
// node and the actual endpoint will be resolved later in dialTask.
|
||||
return errNoPort
|
||||
}
|
||||
|
||||
if _, ok := d.dialing[n.ID()]; ok {
|
||||
return errAlreadyDialing
|
||||
}
|
||||
if _, ok := d.peers[n.ID()]; ok {
|
||||
return errAlreadyConnected
|
||||
}
|
||||
if d.netRestrict != nil && !d.netRestrict.Contains(n.IP()) {
|
||||
return errNetRestrict
|
||||
}
|
||||
if d.history.contains(string(n.ID().Bytes())) {
|
||||
return errRecentlyDialed
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// startStaticDials starts n static dial tasks.
|
||||
func (d *dialScheduler) startStaticDials() (started int) {
|
||||
for started = 0; len(d.staticPool) > 0; started++ {
|
||||
idx := d.rand.Intn(len(d.staticPool))
|
||||
task := d.staticPool[idx]
|
||||
d.startDial(task)
|
||||
d.removeFromStaticPool(idx)
|
||||
}
|
||||
return started
|
||||
}
|
||||
|
||||
// updateStaticPool attempts to move the given static dial back into staticPool.
|
||||
func (d *dialScheduler) updateStaticPool(id enode.ID) {
|
||||
task, ok := d.static[id]
|
||||
if ok && task.staticPoolIndex < 0 && d.checkDial(task.dest) == nil {
|
||||
d.addToStaticPool(task)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *dialScheduler) addToStaticPool(task *dialTask) {
|
||||
if task.staticPoolIndex >= 0 {
|
||||
panic("attempt to add task to staticPool twice")
|
||||
}
|
||||
d.staticPool = append(d.staticPool, task)
|
||||
task.staticPoolIndex = len(d.staticPool) - 1
|
||||
}
|
||||
|
||||
// removeFromStaticPool removes the task at idx from staticPool. It does that by moving the
|
||||
// current last element of the pool to idx and then shortening the pool by one.
|
||||
func (d *dialScheduler) removeFromStaticPool(idx int) {
|
||||
task := d.staticPool[idx]
|
||||
end := len(d.staticPool) - 1
|
||||
d.staticPool[idx] = d.staticPool[end]
|
||||
d.staticPool[idx].staticPoolIndex = idx
|
||||
d.staticPool[end] = nil
|
||||
d.staticPool = d.staticPool[:end]
|
||||
task.staticPoolIndex = -1
|
||||
}
|
||||
|
||||
// startDial runs the given dial task in a separate goroutine.
|
||||
func (d *dialScheduler) startDial(task *dialTask) {
|
||||
d.log.Trace("Starting p2p dial", "id", task.dest.ID(), "ip", task.dest.IP(), "flag", task.flags)
|
||||
hkey := string(task.dest.ID().Bytes())
|
||||
d.history.add(hkey, d.clock.Now().Add(dialHistoryExpiration))
|
||||
d.dialing[task.dest.ID()] = task
|
||||
go func() {
|
||||
task.run(d)
|
||||
d.doneCh <- task
|
||||
}()
|
||||
}
|
||||
|
||||
// A dialTask generated for each node that is dialed.
|
||||
type dialTask struct {
|
||||
staticPoolIndex int
|
||||
flags connFlag
|
||||
// These fields are private to the task and should not be
|
||||
// accessed by dialScheduler while the task is running.
|
||||
dest *enode.Node
|
||||
lastResolved mclock.AbsTime
|
||||
resolveDelay time.Duration
|
||||
}
|
||||
|
||||
func newDialTask(dest *enode.Node, flags connFlag) *dialTask {
|
||||
return &dialTask{dest: dest, flags: flags, staticPoolIndex: -1}
|
||||
}
|
||||
|
||||
type dialError struct {
|
||||
error
|
||||
}
|
||||
|
||||
func (t *dialTask) run(d *dialScheduler) {
|
||||
if t.needResolve() && !t.resolve(d) {
|
||||
return
|
||||
}
|
||||
|
||||
err := t.dial(d, t.dest)
|
||||
if err != nil {
|
||||
// For static nodes, resolve one more time if dialing fails.
|
||||
if _, ok := err.(*dialError); ok && t.flags&staticDialedConn != 0 {
|
||||
if t.resolve(d) {
|
||||
t.dial(d, t.dest)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *dialTask) needResolve() bool {
|
||||
return t.flags&staticDialedConn != 0 && t.dest.IP() == nil
|
||||
}
|
||||
|
||||
// resolve attempts to find the current endpoint for the destination
|
||||
// using discovery.
|
||||
//
|
||||
// Resolve operations are throttled with backoff to avoid flooding the
|
||||
// discovery network with useless queries for nodes that don't exist.
|
||||
// The backoff delay resets when the node is found.
|
||||
func (t *dialTask) resolve(d *dialScheduler) bool {
|
||||
if d.resolver == nil {
|
||||
return false
|
||||
}
|
||||
if t.resolveDelay == 0 {
|
||||
t.resolveDelay = initialResolveDelay
|
||||
}
|
||||
if t.lastResolved > 0 && time.Duration(d.clock.Now()-t.lastResolved) < t.resolveDelay {
|
||||
return false
|
||||
}
|
||||
resolved := d.resolver.Resolve(t.dest)
|
||||
t.lastResolved = d.clock.Now()
|
||||
if resolved == nil {
|
||||
t.resolveDelay *= 2
|
||||
if t.resolveDelay > maxResolveDelay {
|
||||
t.resolveDelay = maxResolveDelay
|
||||
}
|
||||
d.log.Debug("Resolving node failed", "id", t.dest.ID(), "newdelay", t.resolveDelay)
|
||||
return false
|
||||
}
|
||||
// The node was found.
|
||||
t.resolveDelay = initialResolveDelay
|
||||
t.dest = resolved
|
||||
d.log.Debug("Resolved node", "id", t.dest.ID(), "addr", &net.TCPAddr{IP: t.dest.IP(), Port: t.dest.TCP()})
|
||||
return true
|
||||
}
|
||||
|
||||
// dial performs the actual connection attempt.
|
||||
func (t *dialTask) dial(d *dialScheduler, dest *enode.Node) error {
|
||||
fd, err := d.dialer.Dial(d.ctx, t.dest)
|
||||
if err != nil {
|
||||
d.log.Trace("Dial error", "id", t.dest.ID(), "addr", nodeAddr(t.dest), "conn", t.flags, "err", cleanupDialErr(err))
|
||||
return &dialError{err}
|
||||
}
|
||||
mfd := newMeteredConn(fd, false, &net.TCPAddr{IP: dest.IP(), Port: dest.TCP()})
|
||||
return d.setupFunc(mfd, t.flags, dest)
|
||||
}
|
||||
|
||||
func (t *dialTask) String() string {
|
||||
id := t.dest.ID()
|
||||
return fmt.Sprintf("%v %x %v:%d", t.flags, id[:8], t.dest.IP(), t.dest.TCP())
|
||||
}
|
||||
|
||||
func cleanupDialErr(err error) error {
|
||||
if netErr, ok := err.(*net.OpError); ok && netErr.Op == "dial" {
|
||||
return netErr.Err
|
||||
}
|
||||
return err
|
||||
}
|
||||
82
vendor/github.com/ethereum/go-ethereum/p2p/discover/common.go
generated
vendored
Normal file
82
vendor/github.com/ethereum/go-ethereum/p2p/discover/common.go
generated
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
// Copyright 2019 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package discover
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"net"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/mclock"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethereum/go-ethereum/p2p/enr"
|
||||
"github.com/ethereum/go-ethereum/p2p/netutil"
|
||||
)
|
||||
|
||||
// UDPConn is a network connection on which discovery can operate.
|
||||
type UDPConn interface {
|
||||
ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error)
|
||||
WriteToUDP(b []byte, addr *net.UDPAddr) (n int, err error)
|
||||
Close() error
|
||||
LocalAddr() net.Addr
|
||||
}
|
||||
|
||||
// Config holds settings for the discovery listener.
|
||||
type Config struct {
|
||||
// These settings are required and configure the UDP listener:
|
||||
PrivateKey *ecdsa.PrivateKey
|
||||
|
||||
// These settings are optional:
|
||||
NetRestrict *netutil.Netlist // list of allowed IP networks
|
||||
Bootnodes []*enode.Node // list of bootstrap nodes
|
||||
Unhandled chan<- ReadPacket // unhandled packets are sent on this channel
|
||||
Log log.Logger // if set, log messages go here
|
||||
ValidSchemes enr.IdentityScheme // allowed identity schemes
|
||||
Clock mclock.Clock
|
||||
}
|
||||
|
||||
func (cfg Config) withDefaults() Config {
|
||||
if cfg.Log == nil {
|
||||
cfg.Log = log.Root()
|
||||
}
|
||||
if cfg.ValidSchemes == nil {
|
||||
cfg.ValidSchemes = enode.ValidSchemes
|
||||
}
|
||||
if cfg.Clock == nil {
|
||||
cfg.Clock = mclock.System{}
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
// ListenUDP starts listening for discovery packets on the given UDP socket.
|
||||
func ListenUDP(c UDPConn, ln *enode.LocalNode, cfg Config) (*UDPv4, error) {
|
||||
return ListenV4(c, ln, cfg)
|
||||
}
|
||||
|
||||
// ReadPacket is a packet that couldn't be handled. Those packets are sent to the unhandled
|
||||
// channel if configured.
|
||||
type ReadPacket struct {
|
||||
Data []byte
|
||||
Addr *net.UDPAddr
|
||||
}
|
||||
|
||||
func min(x, y int) int {
|
||||
if x > y {
|
||||
return y
|
||||
}
|
||||
return x
|
||||
}
|
||||
227
vendor/github.com/ethereum/go-ethereum/p2p/discover/lookup.go
generated
vendored
Normal file
227
vendor/github.com/ethereum/go-ethereum/p2p/discover/lookup.go
generated
vendored
Normal file
@@ -0,0 +1,227 @@
|
||||
// Copyright 2019 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package discover
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
)
|
||||
|
||||
// lookup performs a network search for nodes close to the given target. It approaches the
|
||||
// target by querying nodes that are closer to it on each iteration. The given target does
|
||||
// not need to be an actual node identifier.
|
||||
type lookup struct {
|
||||
tab *Table
|
||||
queryfunc func(*node) ([]*node, error)
|
||||
replyCh chan []*node
|
||||
cancelCh <-chan struct{}
|
||||
asked, seen map[enode.ID]bool
|
||||
result nodesByDistance
|
||||
replyBuffer []*node
|
||||
queries int
|
||||
}
|
||||
|
||||
type queryFunc func(*node) ([]*node, error)
|
||||
|
||||
func newLookup(ctx context.Context, tab *Table, target enode.ID, q queryFunc) *lookup {
|
||||
it := &lookup{
|
||||
tab: tab,
|
||||
queryfunc: q,
|
||||
asked: make(map[enode.ID]bool),
|
||||
seen: make(map[enode.ID]bool),
|
||||
result: nodesByDistance{target: target},
|
||||
replyCh: make(chan []*node, alpha),
|
||||
cancelCh: ctx.Done(),
|
||||
queries: -1,
|
||||
}
|
||||
// Don't query further if we hit ourself.
|
||||
// Unlikely to happen often in practice.
|
||||
it.asked[tab.self().ID()] = true
|
||||
return it
|
||||
}
|
||||
|
||||
// run runs the lookup to completion and returns the closest nodes found.
|
||||
func (it *lookup) run() []*enode.Node {
|
||||
for it.advance() {
|
||||
}
|
||||
return unwrapNodes(it.result.entries)
|
||||
}
|
||||
|
||||
// advance advances the lookup until any new nodes have been found.
|
||||
// It returns false when the lookup has ended.
|
||||
func (it *lookup) advance() bool {
|
||||
for it.startQueries() {
|
||||
select {
|
||||
case nodes := <-it.replyCh:
|
||||
it.replyBuffer = it.replyBuffer[:0]
|
||||
for _, n := range nodes {
|
||||
if n != nil && !it.seen[n.ID()] {
|
||||
it.seen[n.ID()] = true
|
||||
it.result.push(n, bucketSize)
|
||||
it.replyBuffer = append(it.replyBuffer, n)
|
||||
}
|
||||
}
|
||||
it.queries--
|
||||
if len(it.replyBuffer) > 0 {
|
||||
return true
|
||||
}
|
||||
case <-it.cancelCh:
|
||||
it.shutdown()
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (it *lookup) shutdown() {
|
||||
for it.queries > 0 {
|
||||
<-it.replyCh
|
||||
it.queries--
|
||||
}
|
||||
it.queryfunc = nil
|
||||
it.replyBuffer = nil
|
||||
}
|
||||
|
||||
func (it *lookup) startQueries() bool {
|
||||
if it.queryfunc == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// The first query returns nodes from the local table.
|
||||
if it.queries == -1 {
|
||||
closest := it.tab.findnodeByID(it.result.target, bucketSize, false)
|
||||
// Avoid finishing the lookup too quickly if table is empty. It'd be better to wait
|
||||
// for the table to fill in this case, but there is no good mechanism for that
|
||||
// yet.
|
||||
if len(closest.entries) == 0 {
|
||||
it.slowdown()
|
||||
}
|
||||
it.queries = 1
|
||||
it.replyCh <- closest.entries
|
||||
return true
|
||||
}
|
||||
|
||||
// Ask the closest nodes that we haven't asked yet.
|
||||
for i := 0; i < len(it.result.entries) && it.queries < alpha; i++ {
|
||||
n := it.result.entries[i]
|
||||
if !it.asked[n.ID()] {
|
||||
it.asked[n.ID()] = true
|
||||
it.queries++
|
||||
go it.query(n, it.replyCh)
|
||||
}
|
||||
}
|
||||
// The lookup ends when no more nodes can be asked.
|
||||
return it.queries > 0
|
||||
}
|
||||
|
||||
func (it *lookup) slowdown() {
|
||||
sleep := time.NewTimer(1 * time.Second)
|
||||
defer sleep.Stop()
|
||||
select {
|
||||
case <-sleep.C:
|
||||
case <-it.tab.closeReq:
|
||||
}
|
||||
}
|
||||
|
||||
func (it *lookup) query(n *node, reply chan<- []*node) {
|
||||
fails := it.tab.db.FindFails(n.ID(), n.IP())
|
||||
r, err := it.queryfunc(n)
|
||||
if errors.Is(err, errClosed) {
|
||||
// Avoid recording failures on shutdown.
|
||||
reply <- nil
|
||||
return
|
||||
} else if len(r) == 0 {
|
||||
fails++
|
||||
it.tab.db.UpdateFindFails(n.ID(), n.IP(), fails)
|
||||
// Remove the node from the local table if it fails to return anything useful too
|
||||
// many times, but only if there are enough other nodes in the bucket.
|
||||
dropped := false
|
||||
if fails >= maxFindnodeFailures && it.tab.bucketLen(n.ID()) >= bucketSize/2 {
|
||||
dropped = true
|
||||
it.tab.delete(n)
|
||||
}
|
||||
it.tab.log.Trace("FINDNODE failed", "id", n.ID(), "failcount", fails, "dropped", dropped, "err", err)
|
||||
} else if fails > 0 {
|
||||
// Reset failure counter because it counts _consecutive_ failures.
|
||||
it.tab.db.UpdateFindFails(n.ID(), n.IP(), 0)
|
||||
}
|
||||
|
||||
// Grab as many nodes as possible. Some of them might not be alive anymore, but we'll
|
||||
// just remove those again during revalidation.
|
||||
for _, n := range r {
|
||||
it.tab.addSeenNode(n)
|
||||
}
|
||||
reply <- r
|
||||
}
|
||||
|
||||
// lookupIterator performs lookup operations and iterates over all seen nodes.
|
||||
// When a lookup finishes, a new one is created through nextLookup.
|
||||
type lookupIterator struct {
|
||||
buffer []*node
|
||||
nextLookup lookupFunc
|
||||
ctx context.Context
|
||||
cancel func()
|
||||
lookup *lookup
|
||||
}
|
||||
|
||||
type lookupFunc func(ctx context.Context) *lookup
|
||||
|
||||
func newLookupIterator(ctx context.Context, next lookupFunc) *lookupIterator {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
return &lookupIterator{ctx: ctx, cancel: cancel, nextLookup: next}
|
||||
}
|
||||
|
||||
// Node returns the current node.
|
||||
func (it *lookupIterator) Node() *enode.Node {
|
||||
if len(it.buffer) == 0 {
|
||||
return nil
|
||||
}
|
||||
return unwrapNode(it.buffer[0])
|
||||
}
|
||||
|
||||
// Next moves to the next node.
|
||||
func (it *lookupIterator) Next() bool {
|
||||
// Consume next node in buffer.
|
||||
if len(it.buffer) > 0 {
|
||||
it.buffer = it.buffer[1:]
|
||||
}
|
||||
// Advance the lookup to refill the buffer.
|
||||
for len(it.buffer) == 0 {
|
||||
if it.ctx.Err() != nil {
|
||||
it.lookup = nil
|
||||
it.buffer = nil
|
||||
return false
|
||||
}
|
||||
if it.lookup == nil {
|
||||
it.lookup = it.nextLookup(it.ctx)
|
||||
continue
|
||||
}
|
||||
if !it.lookup.advance() {
|
||||
it.lookup = nil
|
||||
continue
|
||||
}
|
||||
it.buffer = it.lookup.replyBuffer
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Close ends the iterator.
|
||||
func (it *lookupIterator) Close() {
|
||||
it.cancel()
|
||||
}
|
||||
97
vendor/github.com/ethereum/go-ethereum/p2p/discover/node.go
generated
vendored
Normal file
97
vendor/github.com/ethereum/go-ethereum/p2p/discover/node.go
generated
vendored
Normal file
@@ -0,0 +1,97 @@
|
||||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package discover
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"errors"
|
||||
"math/big"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
)
|
||||
|
||||
// node represents a host on the network.
|
||||
// The fields of Node may not be modified.
|
||||
type node struct {
|
||||
enode.Node
|
||||
addedAt time.Time // time when the node was added to the table
|
||||
livenessChecks uint // how often liveness was checked
|
||||
}
|
||||
|
||||
type encPubkey [64]byte
|
||||
|
||||
func encodePubkey(key *ecdsa.PublicKey) encPubkey {
|
||||
var e encPubkey
|
||||
math.ReadBits(key.X, e[:len(e)/2])
|
||||
math.ReadBits(key.Y, e[len(e)/2:])
|
||||
return e
|
||||
}
|
||||
|
||||
func decodePubkey(curve elliptic.Curve, e []byte) (*ecdsa.PublicKey, error) {
|
||||
if len(e) != len(encPubkey{}) {
|
||||
return nil, errors.New("wrong size public key data")
|
||||
}
|
||||
p := &ecdsa.PublicKey{Curve: curve, X: new(big.Int), Y: new(big.Int)}
|
||||
half := len(e) / 2
|
||||
p.X.SetBytes(e[:half])
|
||||
p.Y.SetBytes(e[half:])
|
||||
if !p.Curve.IsOnCurve(p.X, p.Y) {
|
||||
return nil, errors.New("invalid curve point")
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (e encPubkey) id() enode.ID {
|
||||
return enode.ID(crypto.Keccak256Hash(e[:]))
|
||||
}
|
||||
|
||||
func wrapNode(n *enode.Node) *node {
|
||||
return &node{Node: *n}
|
||||
}
|
||||
|
||||
func wrapNodes(ns []*enode.Node) []*node {
|
||||
result := make([]*node, len(ns))
|
||||
for i, n := range ns {
|
||||
result[i] = wrapNode(n)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func unwrapNode(n *node) *enode.Node {
|
||||
return &n.Node
|
||||
}
|
||||
|
||||
func unwrapNodes(ns []*node) []*enode.Node {
|
||||
result := make([]*enode.Node, len(ns))
|
||||
for i, n := range ns {
|
||||
result[i] = unwrapNode(n)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (n *node) addr() *net.UDPAddr {
|
||||
return &net.UDPAddr{IP: n.IP(), Port: n.UDP()}
|
||||
}
|
||||
|
||||
func (n *node) String() string {
|
||||
return n.Node.String()
|
||||
}
|
||||
119
vendor/github.com/ethereum/go-ethereum/p2p/discover/ntp.go
generated
vendored
Normal file
119
vendor/github.com/ethereum/go-ethereum/p2p/discover/ntp.go
generated
vendored
Normal file
@@ -0,0 +1,119 @@
|
||||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Contains the NTP time drift detection via the SNTP protocol:
|
||||
// https://tools.ietf.org/html/rfc4330
|
||||
|
||||
package discover
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
const (
|
||||
ntpPool = "pool.ntp.org" // ntpPool is the NTP server to query for the current time
|
||||
ntpChecks = 3 // Number of measurements to do against the NTP server
|
||||
)
|
||||
|
||||
// durationSlice attaches the methods of sort.Interface to []time.Duration,
|
||||
// sorting in increasing order.
|
||||
type durationSlice []time.Duration
|
||||
|
||||
func (s durationSlice) Len() int { return len(s) }
|
||||
func (s durationSlice) Less(i, j int) bool { return s[i] < s[j] }
|
||||
func (s durationSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
|
||||
// checkClockDrift queries an NTP server for clock drifts and warns the user if
|
||||
// one large enough is detected.
|
||||
func checkClockDrift() {
|
||||
drift, err := sntpDrift(ntpChecks)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if drift < -driftThreshold || drift > driftThreshold {
|
||||
log.Warn(fmt.Sprintf("System clock seems off by %v, which can prevent network connectivity", drift))
|
||||
log.Warn("Please enable network time synchronisation in system settings.")
|
||||
} else {
|
||||
log.Debug("NTP sanity check done", "drift", drift)
|
||||
}
|
||||
}
|
||||
|
||||
// sntpDrift does a naive time resolution against an NTP server and returns the
|
||||
// measured drift. This method uses the simple version of NTP. It's not precise
|
||||
// but should be fine for these purposes.
|
||||
//
|
||||
// Note, it executes two extra measurements compared to the number of requested
|
||||
// ones to be able to discard the two extremes as outliers.
|
||||
func sntpDrift(measurements int) (time.Duration, error) {
|
||||
// Resolve the address of the NTP server
|
||||
addr, err := net.ResolveUDPAddr("udp", ntpPool+":123")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// Construct the time request (empty package with only 2 fields set):
|
||||
// Bits 3-5: Protocol version, 3
|
||||
// Bits 6-8: Mode of operation, client, 3
|
||||
request := make([]byte, 48)
|
||||
request[0] = 3<<3 | 3
|
||||
|
||||
// Execute each of the measurements
|
||||
drifts := []time.Duration{}
|
||||
for i := 0; i < measurements+2; i++ {
|
||||
// Dial the NTP server and send the time retrieval request
|
||||
conn, err := net.DialUDP("udp", nil, addr)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
sent := time.Now()
|
||||
if _, err = conn.Write(request); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// Retrieve the reply and calculate the elapsed time
|
||||
conn.SetDeadline(time.Now().Add(5 * time.Second))
|
||||
|
||||
reply := make([]byte, 48)
|
||||
if _, err = conn.Read(reply); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
elapsed := time.Since(sent)
|
||||
|
||||
// Reconstruct the time from the reply data
|
||||
sec := uint64(reply[43]) | uint64(reply[42])<<8 | uint64(reply[41])<<16 | uint64(reply[40])<<24
|
||||
frac := uint64(reply[47]) | uint64(reply[46])<<8 | uint64(reply[45])<<16 | uint64(reply[44])<<24
|
||||
|
||||
nanosec := sec*1e9 + (frac*1e9)>>32
|
||||
|
||||
t := time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC).Add(time.Duration(nanosec)).Local()
|
||||
|
||||
// Calculate the drift based on an assumed answer time of RRT/2
|
||||
drifts = append(drifts, sent.Sub(t)+elapsed/2)
|
||||
}
|
||||
// Calculate average drift (drop two extremities to avoid outliers)
|
||||
sort.Sort(durationSlice(drifts))
|
||||
|
||||
drift := time.Duration(0)
|
||||
for i := 1; i < len(drifts)-1; i++ {
|
||||
drift += drifts[i]
|
||||
}
|
||||
return drift / time.Duration(measurements), nil
|
||||
}
|
||||
687
vendor/github.com/ethereum/go-ethereum/p2p/discover/table.go
generated
vendored
Normal file
687
vendor/github.com/ethereum/go-ethereum/p2p/discover/table.go
generated
vendored
Normal file
@@ -0,0 +1,687 @@
|
||||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Package discover implements the Node Discovery Protocol.
|
||||
//
|
||||
// The Node Discovery protocol provides a way to find RLPx nodes that
|
||||
// can be connected to. It uses a Kademlia-like protocol to maintain a
|
||||
// distributed database of the IDs and endpoints of all listening
|
||||
// nodes.
|
||||
package discover
|
||||
|
||||
import (
|
||||
crand "crypto/rand"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
mrand "math/rand"
|
||||
"net"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethereum/go-ethereum/p2p/netutil"
|
||||
)
|
||||
|
||||
const (
|
||||
alpha = 3 // Kademlia concurrency factor
|
||||
bucketSize = 16 // Kademlia bucket size
|
||||
maxReplacements = 10 // Size of per-bucket replacement list
|
||||
|
||||
// We keep buckets for the upper 1/15 of distances because
|
||||
// it's very unlikely we'll ever encounter a node that's closer.
|
||||
hashBits = len(common.Hash{}) * 8
|
||||
nBuckets = hashBits / 15 // Number of buckets
|
||||
bucketMinDistance = hashBits - nBuckets // Log distance of closest bucket
|
||||
|
||||
// IP address limits.
|
||||
bucketIPLimit, bucketSubnet = 2, 24 // at most 2 addresses from the same /24
|
||||
tableIPLimit, tableSubnet = 10, 24
|
||||
|
||||
refreshInterval = 30 * time.Minute
|
||||
revalidateInterval = 10 * time.Second
|
||||
copyNodesInterval = 30 * time.Second
|
||||
seedMinTableTime = 5 * time.Minute
|
||||
seedCount = 30
|
||||
seedMaxAge = 5 * 24 * time.Hour
|
||||
)
|
||||
|
||||
// Table is the 'node table', a Kademlia-like index of neighbor nodes. The table keeps
|
||||
// itself up-to-date by verifying the liveness of neighbors and requesting their node
|
||||
// records when announcements of a new record version are received.
|
||||
type Table struct {
|
||||
mutex sync.Mutex // protects buckets, bucket content, nursery, rand
|
||||
buckets [nBuckets]*bucket // index of known nodes by distance
|
||||
nursery []*node // bootstrap nodes
|
||||
rand *mrand.Rand // source of randomness, periodically reseeded
|
||||
ips netutil.DistinctNetSet
|
||||
|
||||
log log.Logger
|
||||
db *enode.DB // database of known nodes
|
||||
net transport
|
||||
refreshReq chan chan struct{}
|
||||
initDone chan struct{}
|
||||
closeReq chan struct{}
|
||||
closed chan struct{}
|
||||
|
||||
nodeAddedHook func(*node) // for testing
|
||||
}
|
||||
|
||||
// transport is implemented by the UDP transports.
|
||||
type transport interface {
|
||||
Self() *enode.Node
|
||||
RequestENR(*enode.Node) (*enode.Node, error)
|
||||
lookupRandom() []*enode.Node
|
||||
lookupSelf() []*enode.Node
|
||||
ping(*enode.Node) (seq uint64, err error)
|
||||
}
|
||||
|
||||
// bucket contains nodes, ordered by their last activity. the entry
|
||||
// that was most recently active is the first element in entries.
|
||||
type bucket struct {
|
||||
entries []*node // live entries, sorted by time of last contact
|
||||
replacements []*node // recently seen nodes to be used if revalidation fails
|
||||
ips netutil.DistinctNetSet
|
||||
}
|
||||
|
||||
func newTable(t transport, db *enode.DB, bootnodes []*enode.Node, log log.Logger) (*Table, error) {
|
||||
tab := &Table{
|
||||
net: t,
|
||||
db: db,
|
||||
refreshReq: make(chan chan struct{}),
|
||||
initDone: make(chan struct{}),
|
||||
closeReq: make(chan struct{}),
|
||||
closed: make(chan struct{}),
|
||||
rand: mrand.New(mrand.NewSource(0)),
|
||||
ips: netutil.DistinctNetSet{Subnet: tableSubnet, Limit: tableIPLimit},
|
||||
log: log,
|
||||
}
|
||||
if err := tab.setFallbackNodes(bootnodes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := range tab.buckets {
|
||||
tab.buckets[i] = &bucket{
|
||||
ips: netutil.DistinctNetSet{Subnet: bucketSubnet, Limit: bucketIPLimit},
|
||||
}
|
||||
}
|
||||
tab.seedRand()
|
||||
tab.loadSeedNodes()
|
||||
|
||||
return tab, nil
|
||||
}
|
||||
|
||||
func (tab *Table) self() *enode.Node {
|
||||
return tab.net.Self()
|
||||
}
|
||||
|
||||
func (tab *Table) seedRand() {
|
||||
var b [8]byte
|
||||
crand.Read(b[:])
|
||||
|
||||
tab.mutex.Lock()
|
||||
tab.rand.Seed(int64(binary.BigEndian.Uint64(b[:])))
|
||||
tab.mutex.Unlock()
|
||||
}
|
||||
|
||||
// ReadRandomNodes fills the given slice with random nodes from the table. The results
|
||||
// are guaranteed to be unique for a single invocation, no node will appear twice.
|
||||
func (tab *Table) ReadRandomNodes(buf []*enode.Node) (n int) {
|
||||
if !tab.isInitDone() {
|
||||
return 0
|
||||
}
|
||||
tab.mutex.Lock()
|
||||
defer tab.mutex.Unlock()
|
||||
|
||||
var nodes []*enode.Node
|
||||
for _, b := range &tab.buckets {
|
||||
for _, n := range b.entries {
|
||||
nodes = append(nodes, unwrapNode(n))
|
||||
}
|
||||
}
|
||||
// Shuffle.
|
||||
for i := 0; i < len(nodes); i++ {
|
||||
j := tab.rand.Intn(len(nodes))
|
||||
nodes[i], nodes[j] = nodes[j], nodes[i]
|
||||
}
|
||||
return copy(buf, nodes)
|
||||
}
|
||||
|
||||
// getNode returns the node with the given ID or nil if it isn't in the table.
|
||||
func (tab *Table) getNode(id enode.ID) *enode.Node {
|
||||
tab.mutex.Lock()
|
||||
defer tab.mutex.Unlock()
|
||||
|
||||
b := tab.bucket(id)
|
||||
for _, e := range b.entries {
|
||||
if e.ID() == id {
|
||||
return unwrapNode(e)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// close terminates the network listener and flushes the node database.
|
||||
func (tab *Table) close() {
|
||||
close(tab.closeReq)
|
||||
<-tab.closed
|
||||
}
|
||||
|
||||
// setFallbackNodes sets the initial points of contact. These nodes
|
||||
// are used to connect to the network if the table is empty and there
|
||||
// are no known nodes in the database.
|
||||
func (tab *Table) setFallbackNodes(nodes []*enode.Node) error {
|
||||
for _, n := range nodes {
|
||||
if err := n.ValidateComplete(); err != nil {
|
||||
return fmt.Errorf("bad bootstrap node %q: %v", n, err)
|
||||
}
|
||||
}
|
||||
tab.nursery = wrapNodes(nodes)
|
||||
return nil
|
||||
}
|
||||
|
||||
// isInitDone returns whether the table's initial seeding procedure has completed.
|
||||
func (tab *Table) isInitDone() bool {
|
||||
select {
|
||||
case <-tab.initDone:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (tab *Table) refresh() <-chan struct{} {
|
||||
done := make(chan struct{})
|
||||
select {
|
||||
case tab.refreshReq <- done:
|
||||
case <-tab.closeReq:
|
||||
close(done)
|
||||
}
|
||||
return done
|
||||
}
|
||||
|
||||
// loop schedules runs of doRefresh, doRevalidate and copyLiveNodes.
|
||||
func (tab *Table) loop() {
|
||||
var (
|
||||
revalidate = time.NewTimer(tab.nextRevalidateTime())
|
||||
refresh = time.NewTicker(refreshInterval)
|
||||
copyNodes = time.NewTicker(copyNodesInterval)
|
||||
refreshDone = make(chan struct{}) // where doRefresh reports completion
|
||||
revalidateDone chan struct{} // where doRevalidate reports completion
|
||||
waiting = []chan struct{}{tab.initDone} // holds waiting callers while doRefresh runs
|
||||
)
|
||||
defer refresh.Stop()
|
||||
defer revalidate.Stop()
|
||||
defer copyNodes.Stop()
|
||||
|
||||
// Start initial refresh.
|
||||
go tab.doRefresh(refreshDone)
|
||||
|
||||
loop:
|
||||
for {
|
||||
select {
|
||||
case <-refresh.C:
|
||||
tab.seedRand()
|
||||
if refreshDone == nil {
|
||||
refreshDone = make(chan struct{})
|
||||
go tab.doRefresh(refreshDone)
|
||||
}
|
||||
case req := <-tab.refreshReq:
|
||||
waiting = append(waiting, req)
|
||||
if refreshDone == nil {
|
||||
refreshDone = make(chan struct{})
|
||||
go tab.doRefresh(refreshDone)
|
||||
}
|
||||
case <-refreshDone:
|
||||
for _, ch := range waiting {
|
||||
close(ch)
|
||||
}
|
||||
waiting, refreshDone = nil, nil
|
||||
case <-revalidate.C:
|
||||
revalidateDone = make(chan struct{})
|
||||
go tab.doRevalidate(revalidateDone)
|
||||
case <-revalidateDone:
|
||||
revalidate.Reset(tab.nextRevalidateTime())
|
||||
revalidateDone = nil
|
||||
case <-copyNodes.C:
|
||||
go tab.copyLiveNodes()
|
||||
case <-tab.closeReq:
|
||||
break loop
|
||||
}
|
||||
}
|
||||
|
||||
if refreshDone != nil {
|
||||
<-refreshDone
|
||||
}
|
||||
for _, ch := range waiting {
|
||||
close(ch)
|
||||
}
|
||||
if revalidateDone != nil {
|
||||
<-revalidateDone
|
||||
}
|
||||
close(tab.closed)
|
||||
}
|
||||
|
||||
// doRefresh performs a lookup for a random target to keep buckets full. seed nodes are
|
||||
// inserted if the table is empty (initial bootstrap or discarded faulty peers).
|
||||
func (tab *Table) doRefresh(done chan struct{}) {
|
||||
defer close(done)
|
||||
|
||||
// Load nodes from the database and insert
|
||||
// them. This should yield a few previously seen nodes that are
|
||||
// (hopefully) still alive.
|
||||
tab.loadSeedNodes()
|
||||
|
||||
// Run self lookup to discover new neighbor nodes.
|
||||
tab.net.lookupSelf()
|
||||
|
||||
// The Kademlia paper specifies that the bucket refresh should
|
||||
// perform a lookup in the least recently used bucket. We cannot
|
||||
// adhere to this because the findnode target is a 512bit value
|
||||
// (not hash-sized) and it is not easily possible to generate a
|
||||
// sha3 preimage that falls into a chosen bucket.
|
||||
// We perform a few lookups with a random target instead.
|
||||
for i := 0; i < 3; i++ {
|
||||
tab.net.lookupRandom()
|
||||
}
|
||||
}
|
||||
|
||||
func (tab *Table) loadSeedNodes() {
|
||||
seeds := wrapNodes(tab.db.QuerySeeds(seedCount, seedMaxAge))
|
||||
seeds = append(seeds, tab.nursery...)
|
||||
for i := range seeds {
|
||||
seed := seeds[i]
|
||||
age := log.Lazy{Fn: func() interface{} { return time.Since(tab.db.LastPongReceived(seed.ID(), seed.IP())) }}
|
||||
tab.log.Trace("Found seed node in database", "id", seed.ID(), "addr", seed.addr(), "age", age)
|
||||
tab.addSeenNode(seed)
|
||||
}
|
||||
}
|
||||
|
||||
// doRevalidate checks that the last node in a random bucket is still live and replaces or
|
||||
// deletes the node if it isn't.
|
||||
func (tab *Table) doRevalidate(done chan<- struct{}) {
|
||||
defer func() { done <- struct{}{} }()
|
||||
|
||||
last, bi := tab.nodeToRevalidate()
|
||||
if last == nil {
|
||||
// No non-empty bucket found.
|
||||
return
|
||||
}
|
||||
|
||||
// Ping the selected node and wait for a pong.
|
||||
remoteSeq, err := tab.net.ping(unwrapNode(last))
|
||||
|
||||
// Also fetch record if the node replied and returned a higher sequence number.
|
||||
if last.Seq() < remoteSeq {
|
||||
n, err := tab.net.RequestENR(unwrapNode(last))
|
||||
if err != nil {
|
||||
tab.log.Debug("ENR request failed", "id", last.ID(), "addr", last.addr(), "err", err)
|
||||
} else {
|
||||
last = &node{Node: *n, addedAt: last.addedAt, livenessChecks: last.livenessChecks}
|
||||
}
|
||||
}
|
||||
|
||||
tab.mutex.Lock()
|
||||
defer tab.mutex.Unlock()
|
||||
b := tab.buckets[bi]
|
||||
if err == nil {
|
||||
// The node responded, move it to the front.
|
||||
last.livenessChecks++
|
||||
tab.log.Debug("Revalidated node", "b", bi, "id", last.ID(), "checks", last.livenessChecks)
|
||||
tab.bumpInBucket(b, last)
|
||||
return
|
||||
}
|
||||
// No reply received, pick a replacement or delete the node if there aren't
|
||||
// any replacements.
|
||||
if r := tab.replace(b, last); r != nil {
|
||||
tab.log.Debug("Replaced dead node", "b", bi, "id", last.ID(), "ip", last.IP(), "checks", last.livenessChecks, "r", r.ID(), "rip", r.IP())
|
||||
} else {
|
||||
tab.log.Debug("Removed dead node", "b", bi, "id", last.ID(), "ip", last.IP(), "checks", last.livenessChecks)
|
||||
}
|
||||
}
|
||||
|
||||
// nodeToRevalidate returns the last node in a random, non-empty bucket.
|
||||
func (tab *Table) nodeToRevalidate() (n *node, bi int) {
|
||||
tab.mutex.Lock()
|
||||
defer tab.mutex.Unlock()
|
||||
|
||||
for _, bi = range tab.rand.Perm(len(tab.buckets)) {
|
||||
b := tab.buckets[bi]
|
||||
if len(b.entries) > 0 {
|
||||
last := b.entries[len(b.entries)-1]
|
||||
return last, bi
|
||||
}
|
||||
}
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
func (tab *Table) nextRevalidateTime() time.Duration {
|
||||
tab.mutex.Lock()
|
||||
defer tab.mutex.Unlock()
|
||||
|
||||
return time.Duration(tab.rand.Int63n(int64(revalidateInterval)))
|
||||
}
|
||||
|
||||
// copyLiveNodes adds nodes from the table to the database if they have been in the table
|
||||
// longer than seedMinTableTime.
|
||||
func (tab *Table) copyLiveNodes() {
|
||||
tab.mutex.Lock()
|
||||
defer tab.mutex.Unlock()
|
||||
|
||||
now := time.Now()
|
||||
for _, b := range &tab.buckets {
|
||||
for _, n := range b.entries {
|
||||
if n.livenessChecks > 0 && now.Sub(n.addedAt) >= seedMinTableTime {
|
||||
tab.db.UpdateNode(unwrapNode(n))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// findnodeByID returns the n nodes in the table that are closest to the given id.
|
||||
// This is used by the FINDNODE/v4 handler.
|
||||
//
|
||||
// The preferLive parameter says whether the caller wants liveness-checked results. If
|
||||
// preferLive is true and the table contains any verified nodes, the result will not
|
||||
// contain unverified nodes. However, if there are no verified nodes at all, the result
|
||||
// will contain unverified nodes.
|
||||
func (tab *Table) findnodeByID(target enode.ID, nresults int, preferLive bool) *nodesByDistance {
|
||||
tab.mutex.Lock()
|
||||
defer tab.mutex.Unlock()
|
||||
|
||||
// Scan all buckets. There might be a better way to do this, but there aren't that many
|
||||
// buckets, so this solution should be fine. The worst-case complexity of this loop
|
||||
// is O(tab.len() * nresults).
|
||||
nodes := &nodesByDistance{target: target}
|
||||
liveNodes := &nodesByDistance{target: target}
|
||||
for _, b := range &tab.buckets {
|
||||
for _, n := range b.entries {
|
||||
nodes.push(n, nresults)
|
||||
if preferLive && n.livenessChecks > 0 {
|
||||
liveNodes.push(n, nresults)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if preferLive && len(liveNodes.entries) > 0 {
|
||||
return liveNodes
|
||||
}
|
||||
return nodes
|
||||
}
|
||||
|
||||
// len returns the number of nodes in the table.
|
||||
func (tab *Table) len() (n int) {
|
||||
tab.mutex.Lock()
|
||||
defer tab.mutex.Unlock()
|
||||
|
||||
for _, b := range &tab.buckets {
|
||||
n += len(b.entries)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// bucketLen returns the number of nodes in the bucket for the given ID.
|
||||
func (tab *Table) bucketLen(id enode.ID) int {
|
||||
tab.mutex.Lock()
|
||||
defer tab.mutex.Unlock()
|
||||
|
||||
return len(tab.bucket(id).entries)
|
||||
}
|
||||
|
||||
// bucket returns the bucket for the given node ID hash.
|
||||
func (tab *Table) bucket(id enode.ID) *bucket {
|
||||
d := enode.LogDist(tab.self().ID(), id)
|
||||
return tab.bucketAtDistance(d)
|
||||
}
|
||||
|
||||
func (tab *Table) bucketAtDistance(d int) *bucket {
|
||||
if d <= bucketMinDistance {
|
||||
return tab.buckets[0]
|
||||
}
|
||||
return tab.buckets[d-bucketMinDistance-1]
|
||||
}
|
||||
|
||||
// addSeenNode adds a node which may or may not be live to the end of a bucket. If the
|
||||
// bucket has space available, adding the node succeeds immediately. Otherwise, the node is
|
||||
// added to the replacements list.
|
||||
//
|
||||
// The caller must not hold tab.mutex.
|
||||
func (tab *Table) addSeenNode(n *node) {
|
||||
if n.ID() == tab.self().ID() {
|
||||
return
|
||||
}
|
||||
|
||||
tab.mutex.Lock()
|
||||
defer tab.mutex.Unlock()
|
||||
b := tab.bucket(n.ID())
|
||||
if contains(b.entries, n.ID()) {
|
||||
// Already in bucket, don't add.
|
||||
return
|
||||
}
|
||||
if len(b.entries) >= bucketSize {
|
||||
// Bucket full, maybe add as replacement.
|
||||
tab.addReplacement(b, n)
|
||||
return
|
||||
}
|
||||
if !tab.addIP(b, n.IP()) {
|
||||
// Can't add: IP limit reached.
|
||||
return
|
||||
}
|
||||
// Add to end of bucket:
|
||||
b.entries = append(b.entries, n)
|
||||
b.replacements = deleteNode(b.replacements, n)
|
||||
n.addedAt = time.Now()
|
||||
if tab.nodeAddedHook != nil {
|
||||
tab.nodeAddedHook(n)
|
||||
}
|
||||
}
|
||||
|
||||
// addVerifiedNode adds a node whose existence has been verified recently to the front of a
|
||||
// bucket. If the node is already in the bucket, it is moved to the front. If the bucket
|
||||
// has no space, the node is added to the replacements list.
|
||||
//
|
||||
// There is an additional safety measure: if the table is still initializing the node
|
||||
// is not added. This prevents an attack where the table could be filled by just sending
|
||||
// ping repeatedly.
|
||||
//
|
||||
// The caller must not hold tab.mutex.
|
||||
func (tab *Table) addVerifiedNode(n *node) {
|
||||
if !tab.isInitDone() {
|
||||
return
|
||||
}
|
||||
if n.ID() == tab.self().ID() {
|
||||
return
|
||||
}
|
||||
|
||||
tab.mutex.Lock()
|
||||
defer tab.mutex.Unlock()
|
||||
b := tab.bucket(n.ID())
|
||||
if tab.bumpInBucket(b, n) {
|
||||
// Already in bucket, moved to front.
|
||||
return
|
||||
}
|
||||
if len(b.entries) >= bucketSize {
|
||||
// Bucket full, maybe add as replacement.
|
||||
tab.addReplacement(b, n)
|
||||
return
|
||||
}
|
||||
if !tab.addIP(b, n.IP()) {
|
||||
// Can't add: IP limit reached.
|
||||
return
|
||||
}
|
||||
// Add to front of bucket.
|
||||
b.entries, _ = pushNode(b.entries, n, bucketSize)
|
||||
b.replacements = deleteNode(b.replacements, n)
|
||||
n.addedAt = time.Now()
|
||||
if tab.nodeAddedHook != nil {
|
||||
tab.nodeAddedHook(n)
|
||||
}
|
||||
}
|
||||
|
||||
// delete removes an entry from the node table. It is used to evacuate dead nodes.
|
||||
func (tab *Table) delete(node *node) {
|
||||
tab.mutex.Lock()
|
||||
defer tab.mutex.Unlock()
|
||||
|
||||
tab.deleteInBucket(tab.bucket(node.ID()), node)
|
||||
}
|
||||
|
||||
func (tab *Table) addIP(b *bucket, ip net.IP) bool {
|
||||
if len(ip) == 0 {
|
||||
return false // Nodes without IP cannot be added.
|
||||
}
|
||||
if netutil.IsLAN(ip) {
|
||||
return true
|
||||
}
|
||||
if !tab.ips.Add(ip) {
|
||||
tab.log.Debug("IP exceeds table limit", "ip", ip)
|
||||
return false
|
||||
}
|
||||
if !b.ips.Add(ip) {
|
||||
tab.log.Debug("IP exceeds bucket limit", "ip", ip)
|
||||
tab.ips.Remove(ip)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (tab *Table) removeIP(b *bucket, ip net.IP) {
|
||||
if netutil.IsLAN(ip) {
|
||||
return
|
||||
}
|
||||
tab.ips.Remove(ip)
|
||||
b.ips.Remove(ip)
|
||||
}
|
||||
|
||||
func (tab *Table) addReplacement(b *bucket, n *node) {
|
||||
for _, e := range b.replacements {
|
||||
if e.ID() == n.ID() {
|
||||
return // already in list
|
||||
}
|
||||
}
|
||||
if !tab.addIP(b, n.IP()) {
|
||||
return
|
||||
}
|
||||
var removed *node
|
||||
b.replacements, removed = pushNode(b.replacements, n, maxReplacements)
|
||||
if removed != nil {
|
||||
tab.removeIP(b, removed.IP())
|
||||
}
|
||||
}
|
||||
|
||||
// replace removes n from the replacement list and replaces 'last' with it if it is the
|
||||
// last entry in the bucket. If 'last' isn't the last entry, it has either been replaced
|
||||
// with someone else or became active.
|
||||
func (tab *Table) replace(b *bucket, last *node) *node {
|
||||
if len(b.entries) == 0 || b.entries[len(b.entries)-1].ID() != last.ID() {
|
||||
// Entry has moved, don't replace it.
|
||||
return nil
|
||||
}
|
||||
// Still the last entry.
|
||||
if len(b.replacements) == 0 {
|
||||
tab.deleteInBucket(b, last)
|
||||
return nil
|
||||
}
|
||||
r := b.replacements[tab.rand.Intn(len(b.replacements))]
|
||||
b.replacements = deleteNode(b.replacements, r)
|
||||
b.entries[len(b.entries)-1] = r
|
||||
tab.removeIP(b, last.IP())
|
||||
return r
|
||||
}
|
||||
|
||||
// bumpInBucket moves the given node to the front of the bucket entry list
|
||||
// if it is contained in that list.
|
||||
func (tab *Table) bumpInBucket(b *bucket, n *node) bool {
|
||||
for i := range b.entries {
|
||||
if b.entries[i].ID() == n.ID() {
|
||||
if !n.IP().Equal(b.entries[i].IP()) {
|
||||
// Endpoint has changed, ensure that the new IP fits into table limits.
|
||||
tab.removeIP(b, b.entries[i].IP())
|
||||
if !tab.addIP(b, n.IP()) {
|
||||
// It doesn't, put the previous one back.
|
||||
tab.addIP(b, b.entries[i].IP())
|
||||
return false
|
||||
}
|
||||
}
|
||||
// Move it to the front.
|
||||
copy(b.entries[1:], b.entries[:i])
|
||||
b.entries[0] = n
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (tab *Table) deleteInBucket(b *bucket, n *node) {
|
||||
b.entries = deleteNode(b.entries, n)
|
||||
tab.removeIP(b, n.IP())
|
||||
}
|
||||
|
||||
func contains(ns []*node, id enode.ID) bool {
|
||||
for _, n := range ns {
|
||||
if n.ID() == id {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// pushNode adds n to the front of list, keeping at most max items.
|
||||
func pushNode(list []*node, n *node, max int) ([]*node, *node) {
|
||||
if len(list) < max {
|
||||
list = append(list, nil)
|
||||
}
|
||||
removed := list[len(list)-1]
|
||||
copy(list[1:], list)
|
||||
list[0] = n
|
||||
return list, removed
|
||||
}
|
||||
|
||||
// deleteNode removes n from list.
|
||||
func deleteNode(list []*node, n *node) []*node {
|
||||
for i := range list {
|
||||
if list[i].ID() == n.ID() {
|
||||
return append(list[:i], list[i+1:]...)
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// nodesByDistance is a list of nodes, ordered by distance to target.
|
||||
type nodesByDistance struct {
|
||||
entries []*node
|
||||
target enode.ID
|
||||
}
|
||||
|
||||
// push adds the given node to the list, keeping the total size below maxElems.
|
||||
func (h *nodesByDistance) push(n *node, maxElems int) {
|
||||
ix := sort.Search(len(h.entries), func(i int) bool {
|
||||
return enode.DistCmp(h.target, h.entries[i].ID(), n.ID()) > 0
|
||||
})
|
||||
if len(h.entries) < maxElems {
|
||||
h.entries = append(h.entries, n)
|
||||
}
|
||||
if ix == len(h.entries) {
|
||||
// farther away than all nodes we already have.
|
||||
// if there was room for it, the node is now the last element.
|
||||
} else {
|
||||
// slide existing entries down to make room
|
||||
// this will overwrite the entry we just appended.
|
||||
copy(h.entries[ix+1:], h.entries[ix:])
|
||||
h.entries[ix] = n
|
||||
}
|
||||
}
|
||||
787
vendor/github.com/ethereum/go-ethereum/p2p/discover/v4_udp.go
generated
vendored
Normal file
787
vendor/github.com/ethereum/go-ethereum/p2p/discover/v4_udp.go
generated
vendored
Normal file
@@ -0,0 +1,787 @@
|
||||
// Copyright 2019 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package discover
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"container/list"
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
crand "crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/p2p/discover/v4wire"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethereum/go-ethereum/p2p/netutil"
|
||||
)
|
||||
|
||||
// Errors
|
||||
var (
|
||||
errExpired = errors.New("expired")
|
||||
errUnsolicitedReply = errors.New("unsolicited reply")
|
||||
errUnknownNode = errors.New("unknown node")
|
||||
errTimeout = errors.New("RPC timeout")
|
||||
errClockWarp = errors.New("reply deadline too far in the future")
|
||||
errClosed = errors.New("socket closed")
|
||||
errLowPort = errors.New("low port")
|
||||
)
|
||||
|
||||
const (
|
||||
respTimeout = 500 * time.Millisecond
|
||||
expiration = 20 * time.Second
|
||||
bondExpiration = 24 * time.Hour
|
||||
|
||||
maxFindnodeFailures = 5 // nodes exceeding this limit are dropped
|
||||
ntpFailureThreshold = 32 // Continuous timeouts after which to check NTP
|
||||
ntpWarningCooldown = 10 * time.Minute // Minimum amount of time to pass before repeating NTP warning
|
||||
driftThreshold = 10 * time.Second // Allowed clock drift before warning user
|
||||
|
||||
// Discovery packets are defined to be no larger than 1280 bytes.
|
||||
// Packets larger than this size will be cut at the end and treated
|
||||
// as invalid because their hash won't match.
|
||||
maxPacketSize = 1280
|
||||
)
|
||||
|
||||
// UDPv4 implements the v4 wire protocol.
|
||||
type UDPv4 struct {
|
||||
conn UDPConn
|
||||
log log.Logger
|
||||
netrestrict *netutil.Netlist
|
||||
priv *ecdsa.PrivateKey
|
||||
localNode *enode.LocalNode
|
||||
db *enode.DB
|
||||
tab *Table
|
||||
closeOnce sync.Once
|
||||
wg sync.WaitGroup
|
||||
|
||||
addReplyMatcher chan *replyMatcher
|
||||
gotreply chan reply
|
||||
closeCtx context.Context
|
||||
cancelCloseCtx context.CancelFunc
|
||||
}
|
||||
|
||||
// replyMatcher represents a pending reply.
|
||||
//
|
||||
// Some implementations of the protocol wish to send more than one
|
||||
// reply packet to findnode. In general, any neighbors packet cannot
|
||||
// be matched up with a specific findnode packet.
|
||||
//
|
||||
// Our implementation handles this by storing a callback function for
|
||||
// each pending reply. Incoming packets from a node are dispatched
|
||||
// to all callback functions for that node.
|
||||
type replyMatcher struct {
|
||||
// these fields must match in the reply.
|
||||
from enode.ID
|
||||
ip net.IP
|
||||
ptype byte
|
||||
|
||||
// time when the request must complete
|
||||
deadline time.Time
|
||||
|
||||
// callback is called when a matching reply arrives. If it returns matched == true, the
|
||||
// reply was acceptable. The second return value indicates whether the callback should
|
||||
// be removed from the pending reply queue. If it returns false, the reply is considered
|
||||
// incomplete and the callback will be invoked again for the next matching reply.
|
||||
callback replyMatchFunc
|
||||
|
||||
// errc receives nil when the callback indicates completion or an
|
||||
// error if no further reply is received within the timeout.
|
||||
errc chan error
|
||||
|
||||
// reply contains the most recent reply. This field is safe for reading after errc has
|
||||
// received a value.
|
||||
reply v4wire.Packet
|
||||
}
|
||||
|
||||
type replyMatchFunc func(v4wire.Packet) (matched bool, requestDone bool)
|
||||
|
||||
// reply is a reply packet from a certain node.
|
||||
type reply struct {
|
||||
from enode.ID
|
||||
ip net.IP
|
||||
data v4wire.Packet
|
||||
// loop indicates whether there was
|
||||
// a matching request by sending on this channel.
|
||||
matched chan<- bool
|
||||
}
|
||||
|
||||
func ListenV4(c UDPConn, ln *enode.LocalNode, cfg Config) (*UDPv4, error) {
|
||||
cfg = cfg.withDefaults()
|
||||
closeCtx, cancel := context.WithCancel(context.Background())
|
||||
t := &UDPv4{
|
||||
conn: c,
|
||||
priv: cfg.PrivateKey,
|
||||
netrestrict: cfg.NetRestrict,
|
||||
localNode: ln,
|
||||
db: ln.Database(),
|
||||
gotreply: make(chan reply),
|
||||
addReplyMatcher: make(chan *replyMatcher),
|
||||
closeCtx: closeCtx,
|
||||
cancelCloseCtx: cancel,
|
||||
log: cfg.Log,
|
||||
}
|
||||
|
||||
tab, err := newTable(t, ln.Database(), cfg.Bootnodes, t.log)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t.tab = tab
|
||||
go tab.loop()
|
||||
|
||||
t.wg.Add(2)
|
||||
go t.loop()
|
||||
go t.readLoop(cfg.Unhandled)
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// Self returns the local node.
|
||||
func (t *UDPv4) Self() *enode.Node {
|
||||
return t.localNode.Node()
|
||||
}
|
||||
|
||||
// Close shuts down the socket and aborts any running queries.
|
||||
func (t *UDPv4) Close() {
|
||||
t.closeOnce.Do(func() {
|
||||
t.cancelCloseCtx()
|
||||
t.conn.Close()
|
||||
t.wg.Wait()
|
||||
t.tab.close()
|
||||
})
|
||||
}
|
||||
|
||||
// Resolve searches for a specific node with the given ID and tries to get the most recent
|
||||
// version of the node record for it. It returns n if the node could not be resolved.
|
||||
func (t *UDPv4) Resolve(n *enode.Node) *enode.Node {
|
||||
// Try asking directly. This works if the node is still responding on the endpoint we have.
|
||||
if rn, err := t.RequestENR(n); err == nil {
|
||||
return rn
|
||||
}
|
||||
// Check table for the ID, we might have a newer version there.
|
||||
if intable := t.tab.getNode(n.ID()); intable != nil && intable.Seq() > n.Seq() {
|
||||
n = intable
|
||||
if rn, err := t.RequestENR(n); err == nil {
|
||||
return rn
|
||||
}
|
||||
}
|
||||
// Otherwise perform a network lookup.
|
||||
var key enode.Secp256k1
|
||||
if n.Load(&key) != nil {
|
||||
return n // no secp256k1 key
|
||||
}
|
||||
result := t.LookupPubkey((*ecdsa.PublicKey)(&key))
|
||||
for _, rn := range result {
|
||||
if rn.ID() == n.ID() {
|
||||
if rn, err := t.RequestENR(rn); err == nil {
|
||||
return rn
|
||||
}
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (t *UDPv4) ourEndpoint() v4wire.Endpoint {
|
||||
n := t.Self()
|
||||
a := &net.UDPAddr{IP: n.IP(), Port: n.UDP()}
|
||||
return v4wire.NewEndpoint(a, uint16(n.TCP()))
|
||||
}
|
||||
|
||||
// Ping sends a ping message to the given node.
|
||||
func (t *UDPv4) Ping(n *enode.Node) error {
|
||||
_, err := t.ping(n)
|
||||
return err
|
||||
}
|
||||
|
||||
// ping sends a ping message to the given node and waits for a reply.
|
||||
func (t *UDPv4) ping(n *enode.Node) (seq uint64, err error) {
|
||||
rm := t.sendPing(n.ID(), &net.UDPAddr{IP: n.IP(), Port: n.UDP()}, nil)
|
||||
if err = <-rm.errc; err == nil {
|
||||
seq = rm.reply.(*v4wire.Pong).ENRSeq
|
||||
}
|
||||
return seq, err
|
||||
}
|
||||
|
||||
// sendPing sends a ping message to the given node and invokes the callback
|
||||
// when the reply arrives.
|
||||
func (t *UDPv4) sendPing(toid enode.ID, toaddr *net.UDPAddr, callback func()) *replyMatcher {
|
||||
req := t.makePing(toaddr)
|
||||
packet, hash, err := v4wire.Encode(t.priv, req)
|
||||
if err != nil {
|
||||
errc := make(chan error, 1)
|
||||
errc <- err
|
||||
return &replyMatcher{errc: errc}
|
||||
}
|
||||
// Add a matcher for the reply to the pending reply queue. Pongs are matched if they
|
||||
// reference the ping we're about to send.
|
||||
rm := t.pending(toid, toaddr.IP, v4wire.PongPacket, func(p v4wire.Packet) (matched bool, requestDone bool) {
|
||||
matched = bytes.Equal(p.(*v4wire.Pong).ReplyTok, hash)
|
||||
if matched && callback != nil {
|
||||
callback()
|
||||
}
|
||||
return matched, matched
|
||||
})
|
||||
// Send the packet.
|
||||
t.localNode.UDPContact(toaddr)
|
||||
t.write(toaddr, toid, req.Name(), packet)
|
||||
return rm
|
||||
}
|
||||
|
||||
func (t *UDPv4) makePing(toaddr *net.UDPAddr) *v4wire.Ping {
|
||||
return &v4wire.Ping{
|
||||
Version: 4,
|
||||
From: t.ourEndpoint(),
|
||||
To: v4wire.NewEndpoint(toaddr, 0),
|
||||
Expiration: uint64(time.Now().Add(expiration).Unix()),
|
||||
ENRSeq: t.localNode.Node().Seq(),
|
||||
}
|
||||
}
|
||||
|
||||
// LookupPubkey finds the closest nodes to the given public key.
|
||||
func (t *UDPv4) LookupPubkey(key *ecdsa.PublicKey) []*enode.Node {
|
||||
if t.tab.len() == 0 {
|
||||
// All nodes were dropped, refresh. The very first query will hit this
|
||||
// case and run the bootstrapping logic.
|
||||
<-t.tab.refresh()
|
||||
}
|
||||
return t.newLookup(t.closeCtx, encodePubkey(key)).run()
|
||||
}
|
||||
|
||||
// RandomNodes is an iterator yielding nodes from a random walk of the DHT.
|
||||
func (t *UDPv4) RandomNodes() enode.Iterator {
|
||||
return newLookupIterator(t.closeCtx, t.newRandomLookup)
|
||||
}
|
||||
|
||||
// lookupRandom implements transport.
|
||||
func (t *UDPv4) lookupRandom() []*enode.Node {
|
||||
return t.newRandomLookup(t.closeCtx).run()
|
||||
}
|
||||
|
||||
// lookupSelf implements transport.
|
||||
func (t *UDPv4) lookupSelf() []*enode.Node {
|
||||
return t.newLookup(t.closeCtx, encodePubkey(&t.priv.PublicKey)).run()
|
||||
}
|
||||
|
||||
func (t *UDPv4) newRandomLookup(ctx context.Context) *lookup {
|
||||
var target encPubkey
|
||||
crand.Read(target[:])
|
||||
return t.newLookup(ctx, target)
|
||||
}
|
||||
|
||||
func (t *UDPv4) newLookup(ctx context.Context, targetKey encPubkey) *lookup {
|
||||
target := enode.ID(crypto.Keccak256Hash(targetKey[:]))
|
||||
ekey := v4wire.Pubkey(targetKey)
|
||||
it := newLookup(ctx, t.tab, target, func(n *node) ([]*node, error) {
|
||||
return t.findnode(n.ID(), n.addr(), ekey)
|
||||
})
|
||||
return it
|
||||
}
|
||||
|
||||
// findnode sends a findnode request to the given node and waits until
|
||||
// the node has sent up to k neighbors.
|
||||
func (t *UDPv4) findnode(toid enode.ID, toaddr *net.UDPAddr, target v4wire.Pubkey) ([]*node, error) {
|
||||
t.ensureBond(toid, toaddr)
|
||||
|
||||
// Add a matcher for 'neighbours' replies to the pending reply queue. The matcher is
|
||||
// active until enough nodes have been received.
|
||||
nodes := make([]*node, 0, bucketSize)
|
||||
nreceived := 0
|
||||
rm := t.pending(toid, toaddr.IP, v4wire.NeighborsPacket, func(r v4wire.Packet) (matched bool, requestDone bool) {
|
||||
reply := r.(*v4wire.Neighbors)
|
||||
for _, rn := range reply.Nodes {
|
||||
nreceived++
|
||||
n, err := t.nodeFromRPC(toaddr, rn)
|
||||
if err != nil {
|
||||
t.log.Trace("Invalid neighbor node received", "ip", rn.IP, "addr", toaddr, "err", err)
|
||||
continue
|
||||
}
|
||||
nodes = append(nodes, n)
|
||||
}
|
||||
return true, nreceived >= bucketSize
|
||||
})
|
||||
t.send(toaddr, toid, &v4wire.Findnode{
|
||||
Target: target,
|
||||
Expiration: uint64(time.Now().Add(expiration).Unix()),
|
||||
})
|
||||
// Ensure that callers don't see a timeout if the node actually responded. Since
|
||||
// findnode can receive more than one neighbors response, the reply matcher will be
|
||||
// active until the remote node sends enough nodes. If the remote end doesn't have
|
||||
// enough nodes the reply matcher will time out waiting for the second reply, but
|
||||
// there's no need for an error in that case.
|
||||
err := <-rm.errc
|
||||
if errors.Is(err, errTimeout) && rm.reply != nil {
|
||||
err = nil
|
||||
}
|
||||
return nodes, err
|
||||
}
|
||||
|
||||
// RequestENR sends ENRRequest to the given node and waits for a response.
|
||||
func (t *UDPv4) RequestENR(n *enode.Node) (*enode.Node, error) {
|
||||
addr := &net.UDPAddr{IP: n.IP(), Port: n.UDP()}
|
||||
t.ensureBond(n.ID(), addr)
|
||||
|
||||
req := &v4wire.ENRRequest{
|
||||
Expiration: uint64(time.Now().Add(expiration).Unix()),
|
||||
}
|
||||
packet, hash, err := v4wire.Encode(t.priv, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Add a matcher for the reply to the pending reply queue. Responses are matched if
|
||||
// they reference the request we're about to send.
|
||||
rm := t.pending(n.ID(), addr.IP, v4wire.ENRResponsePacket, func(r v4wire.Packet) (matched bool, requestDone bool) {
|
||||
matched = bytes.Equal(r.(*v4wire.ENRResponse).ReplyTok, hash)
|
||||
return matched, matched
|
||||
})
|
||||
// Send the packet and wait for the reply.
|
||||
t.write(addr, n.ID(), req.Name(), packet)
|
||||
if err := <-rm.errc; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Verify the response record.
|
||||
respN, err := enode.New(enode.ValidSchemes, &rm.reply.(*v4wire.ENRResponse).Record)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if respN.ID() != n.ID() {
|
||||
return nil, fmt.Errorf("invalid ID in response record")
|
||||
}
|
||||
if respN.Seq() < n.Seq() {
|
||||
return n, nil // response record is older
|
||||
}
|
||||
if err := netutil.CheckRelayIP(addr.IP, respN.IP()); err != nil {
|
||||
return nil, fmt.Errorf("invalid IP in response record: %v", err)
|
||||
}
|
||||
return respN, nil
|
||||
}
|
||||
|
||||
// pending adds a reply matcher to the pending reply queue.
|
||||
// see the documentation of type replyMatcher for a detailed explanation.
|
||||
func (t *UDPv4) pending(id enode.ID, ip net.IP, ptype byte, callback replyMatchFunc) *replyMatcher {
|
||||
ch := make(chan error, 1)
|
||||
p := &replyMatcher{from: id, ip: ip, ptype: ptype, callback: callback, errc: ch}
|
||||
select {
|
||||
case t.addReplyMatcher <- p:
|
||||
// loop will handle it
|
||||
case <-t.closeCtx.Done():
|
||||
ch <- errClosed
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// handleReply dispatches a reply packet, invoking reply matchers. It returns
|
||||
// whether any matcher considered the packet acceptable.
|
||||
func (t *UDPv4) handleReply(from enode.ID, fromIP net.IP, req v4wire.Packet) bool {
|
||||
matched := make(chan bool, 1)
|
||||
select {
|
||||
case t.gotreply <- reply{from, fromIP, req, matched}:
|
||||
// loop will handle it
|
||||
return <-matched
|
||||
case <-t.closeCtx.Done():
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// loop runs in its own goroutine. it keeps track of
|
||||
// the refresh timer and the pending reply queue.
|
||||
func (t *UDPv4) loop() {
|
||||
defer t.wg.Done()
|
||||
|
||||
var (
|
||||
plist = list.New()
|
||||
timeout = time.NewTimer(0)
|
||||
nextTimeout *replyMatcher // head of plist when timeout was last reset
|
||||
contTimeouts = 0 // number of continuous timeouts to do NTP checks
|
||||
ntpWarnTime = time.Unix(0, 0)
|
||||
)
|
||||
<-timeout.C // ignore first timeout
|
||||
defer timeout.Stop()
|
||||
|
||||
resetTimeout := func() {
|
||||
if plist.Front() == nil || nextTimeout == plist.Front().Value {
|
||||
return
|
||||
}
|
||||
// Start the timer so it fires when the next pending reply has expired.
|
||||
now := time.Now()
|
||||
for el := plist.Front(); el != nil; el = el.Next() {
|
||||
nextTimeout = el.Value.(*replyMatcher)
|
||||
if dist := nextTimeout.deadline.Sub(now); dist < 2*respTimeout {
|
||||
timeout.Reset(dist)
|
||||
return
|
||||
}
|
||||
// Remove pending replies whose deadline is too far in the
|
||||
// future. These can occur if the system clock jumped
|
||||
// backwards after the deadline was assigned.
|
||||
nextTimeout.errc <- errClockWarp
|
||||
plist.Remove(el)
|
||||
}
|
||||
nextTimeout = nil
|
||||
timeout.Stop()
|
||||
}
|
||||
|
||||
for {
|
||||
resetTimeout()
|
||||
|
||||
select {
|
||||
case <-t.closeCtx.Done():
|
||||
for el := plist.Front(); el != nil; el = el.Next() {
|
||||
el.Value.(*replyMatcher).errc <- errClosed
|
||||
}
|
||||
return
|
||||
|
||||
case p := <-t.addReplyMatcher:
|
||||
p.deadline = time.Now().Add(respTimeout)
|
||||
plist.PushBack(p)
|
||||
|
||||
case r := <-t.gotreply:
|
||||
var matched bool // whether any replyMatcher considered the reply acceptable.
|
||||
for el := plist.Front(); el != nil; el = el.Next() {
|
||||
p := el.Value.(*replyMatcher)
|
||||
if p.from == r.from && p.ptype == r.data.Kind() && p.ip.Equal(r.ip) {
|
||||
ok, requestDone := p.callback(r.data)
|
||||
matched = matched || ok
|
||||
p.reply = r.data
|
||||
// Remove the matcher if callback indicates that all replies have been received.
|
||||
if requestDone {
|
||||
p.errc <- nil
|
||||
plist.Remove(el)
|
||||
}
|
||||
// Reset the continuous timeout counter (time drift detection)
|
||||
contTimeouts = 0
|
||||
}
|
||||
}
|
||||
r.matched <- matched
|
||||
|
||||
case now := <-timeout.C:
|
||||
nextTimeout = nil
|
||||
|
||||
// Notify and remove callbacks whose deadline is in the past.
|
||||
for el := plist.Front(); el != nil; el = el.Next() {
|
||||
p := el.Value.(*replyMatcher)
|
||||
if now.After(p.deadline) || now.Equal(p.deadline) {
|
||||
p.errc <- errTimeout
|
||||
plist.Remove(el)
|
||||
contTimeouts++
|
||||
}
|
||||
}
|
||||
// If we've accumulated too many timeouts, do an NTP time sync check
|
||||
if contTimeouts > ntpFailureThreshold {
|
||||
if time.Since(ntpWarnTime) >= ntpWarningCooldown {
|
||||
ntpWarnTime = time.Now()
|
||||
go checkClockDrift()
|
||||
}
|
||||
contTimeouts = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *UDPv4) send(toaddr *net.UDPAddr, toid enode.ID, req v4wire.Packet) ([]byte, error) {
|
||||
packet, hash, err := v4wire.Encode(t.priv, req)
|
||||
if err != nil {
|
||||
return hash, err
|
||||
}
|
||||
return hash, t.write(toaddr, toid, req.Name(), packet)
|
||||
}
|
||||
|
||||
func (t *UDPv4) write(toaddr *net.UDPAddr, toid enode.ID, what string, packet []byte) error {
|
||||
_, err := t.conn.WriteToUDP(packet, toaddr)
|
||||
t.log.Trace(">> "+what, "id", toid, "addr", toaddr, "err", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// readLoop runs in its own goroutine. it handles incoming UDP packets.
|
||||
func (t *UDPv4) readLoop(unhandled chan<- ReadPacket) {
|
||||
defer t.wg.Done()
|
||||
if unhandled != nil {
|
||||
defer close(unhandled)
|
||||
}
|
||||
|
||||
buf := make([]byte, maxPacketSize)
|
||||
for {
|
||||
nbytes, from, err := t.conn.ReadFromUDP(buf)
|
||||
if netutil.IsTemporaryError(err) {
|
||||
// Ignore temporary read errors.
|
||||
t.log.Debug("Temporary UDP read error", "err", err)
|
||||
continue
|
||||
} else if err != nil {
|
||||
// Shut down the loop for permanent errors.
|
||||
if !errors.Is(err, io.EOF) {
|
||||
t.log.Debug("UDP read error", "err", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if t.handlePacket(from, buf[:nbytes]) != nil && unhandled != nil {
|
||||
select {
|
||||
case unhandled <- ReadPacket{buf[:nbytes], from}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *UDPv4) handlePacket(from *net.UDPAddr, buf []byte) error {
|
||||
rawpacket, fromKey, hash, err := v4wire.Decode(buf)
|
||||
if err != nil {
|
||||
t.log.Debug("Bad discv4 packet", "addr", from, "err", err)
|
||||
return err
|
||||
}
|
||||
packet := t.wrapPacket(rawpacket)
|
||||
fromID := fromKey.ID()
|
||||
if err == nil && packet.preverify != nil {
|
||||
err = packet.preverify(packet, from, fromID, fromKey)
|
||||
}
|
||||
t.log.Trace("<< "+packet.Name(), "id", fromID, "addr", from, "err", err)
|
||||
if err == nil && packet.handle != nil {
|
||||
packet.handle(packet, from, fromID, hash)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// checkBond checks if the given node has a recent enough endpoint proof.
|
||||
func (t *UDPv4) checkBond(id enode.ID, ip net.IP) bool {
|
||||
return time.Since(t.db.LastPongReceived(id, ip)) < bondExpiration
|
||||
}
|
||||
|
||||
// ensureBond solicits a ping from a node if we haven't seen a ping from it for a while.
|
||||
// This ensures there is a valid endpoint proof on the remote end.
|
||||
func (t *UDPv4) ensureBond(toid enode.ID, toaddr *net.UDPAddr) {
|
||||
tooOld := time.Since(t.db.LastPingReceived(toid, toaddr.IP)) > bondExpiration
|
||||
if tooOld || t.db.FindFails(toid, toaddr.IP) > maxFindnodeFailures {
|
||||
rm := t.sendPing(toid, toaddr, nil)
|
||||
<-rm.errc
|
||||
// Wait for them to ping back and process our pong.
|
||||
time.Sleep(respTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *UDPv4) nodeFromRPC(sender *net.UDPAddr, rn v4wire.Node) (*node, error) {
|
||||
if rn.UDP <= 1024 {
|
||||
return nil, errLowPort
|
||||
}
|
||||
if err := netutil.CheckRelayIP(sender.IP, rn.IP); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if t.netrestrict != nil && !t.netrestrict.Contains(rn.IP) {
|
||||
return nil, errors.New("not contained in netrestrict list")
|
||||
}
|
||||
key, err := v4wire.DecodePubkey(crypto.S256(), rn.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
n := wrapNode(enode.NewV4(key, rn.IP, int(rn.TCP), int(rn.UDP)))
|
||||
err = n.ValidateComplete()
|
||||
return n, err
|
||||
}
|
||||
|
||||
func nodeToRPC(n *node) v4wire.Node {
|
||||
var key ecdsa.PublicKey
|
||||
var ekey v4wire.Pubkey
|
||||
if err := n.Load((*enode.Secp256k1)(&key)); err == nil {
|
||||
ekey = v4wire.EncodePubkey(&key)
|
||||
}
|
||||
return v4wire.Node{ID: ekey, IP: n.IP(), UDP: uint16(n.UDP()), TCP: uint16(n.TCP())}
|
||||
}
|
||||
|
||||
// wrapPacket returns the handler functions applicable to a packet.
|
||||
func (t *UDPv4) wrapPacket(p v4wire.Packet) *packetHandlerV4 {
|
||||
var h packetHandlerV4
|
||||
h.Packet = p
|
||||
switch p.(type) {
|
||||
case *v4wire.Ping:
|
||||
h.preverify = t.verifyPing
|
||||
h.handle = t.handlePing
|
||||
case *v4wire.Pong:
|
||||
h.preverify = t.verifyPong
|
||||
case *v4wire.Findnode:
|
||||
h.preverify = t.verifyFindnode
|
||||
h.handle = t.handleFindnode
|
||||
case *v4wire.Neighbors:
|
||||
h.preverify = t.verifyNeighbors
|
||||
case *v4wire.ENRRequest:
|
||||
h.preverify = t.verifyENRRequest
|
||||
h.handle = t.handleENRRequest
|
||||
case *v4wire.ENRResponse:
|
||||
h.preverify = t.verifyENRResponse
|
||||
}
|
||||
return &h
|
||||
}
|
||||
|
||||
// packetHandlerV4 wraps a packet with handler functions.
|
||||
type packetHandlerV4 struct {
|
||||
v4wire.Packet
|
||||
senderKey *ecdsa.PublicKey // used for ping
|
||||
|
||||
// preverify checks whether the packet is valid and should be handled at all.
|
||||
preverify func(p *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error
|
||||
// handle handles the packet.
|
||||
handle func(req *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, mac []byte)
|
||||
}
|
||||
|
||||
// PING/v4
|
||||
|
||||
func (t *UDPv4) verifyPing(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error {
|
||||
req := h.Packet.(*v4wire.Ping)
|
||||
|
||||
senderKey, err := v4wire.DecodePubkey(crypto.S256(), fromKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if v4wire.Expired(req.Expiration) {
|
||||
return errExpired
|
||||
}
|
||||
h.senderKey = senderKey
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *UDPv4) handlePing(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, mac []byte) {
|
||||
req := h.Packet.(*v4wire.Ping)
|
||||
|
||||
// Reply.
|
||||
t.send(from, fromID, &v4wire.Pong{
|
||||
To: v4wire.NewEndpoint(from, req.From.TCP),
|
||||
ReplyTok: mac,
|
||||
Expiration: uint64(time.Now().Add(expiration).Unix()),
|
||||
ENRSeq: t.localNode.Node().Seq(),
|
||||
})
|
||||
|
||||
// Ping back if our last pong on file is too far in the past.
|
||||
n := wrapNode(enode.NewV4(h.senderKey, from.IP, int(req.From.TCP), from.Port))
|
||||
if time.Since(t.db.LastPongReceived(n.ID(), from.IP)) > bondExpiration {
|
||||
t.sendPing(fromID, from, func() {
|
||||
t.tab.addVerifiedNode(n)
|
||||
})
|
||||
} else {
|
||||
t.tab.addVerifiedNode(n)
|
||||
}
|
||||
|
||||
// Update node database and endpoint predictor.
|
||||
t.db.UpdateLastPingReceived(n.ID(), from.IP, time.Now())
|
||||
t.localNode.UDPEndpointStatement(from, &net.UDPAddr{IP: req.To.IP, Port: int(req.To.UDP)})
|
||||
}
|
||||
|
||||
// PONG/v4
|
||||
|
||||
func (t *UDPv4) verifyPong(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error {
|
||||
req := h.Packet.(*v4wire.Pong)
|
||||
|
||||
if v4wire.Expired(req.Expiration) {
|
||||
return errExpired
|
||||
}
|
||||
if !t.handleReply(fromID, from.IP, req) {
|
||||
return errUnsolicitedReply
|
||||
}
|
||||
t.localNode.UDPEndpointStatement(from, &net.UDPAddr{IP: req.To.IP, Port: int(req.To.UDP)})
|
||||
t.db.UpdateLastPongReceived(fromID, from.IP, time.Now())
|
||||
return nil
|
||||
}
|
||||
|
||||
// FINDNODE/v4
|
||||
|
||||
func (t *UDPv4) verifyFindnode(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error {
|
||||
req := h.Packet.(*v4wire.Findnode)
|
||||
|
||||
if v4wire.Expired(req.Expiration) {
|
||||
return errExpired
|
||||
}
|
||||
if !t.checkBond(fromID, from.IP) {
|
||||
// No endpoint proof pong exists, we don't process the packet. This prevents an
|
||||
// attack vector where the discovery protocol could be used to amplify traffic in a
|
||||
// DDOS attack. A malicious actor would send a findnode request with the IP address
|
||||
// and UDP port of the target as the source address. The recipient of the findnode
|
||||
// packet would then send a neighbors packet (which is a much bigger packet than
|
||||
// findnode) to the victim.
|
||||
return errUnknownNode
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *UDPv4) handleFindnode(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, mac []byte) {
|
||||
req := h.Packet.(*v4wire.Findnode)
|
||||
|
||||
// Determine closest nodes.
|
||||
target := enode.ID(crypto.Keccak256Hash(req.Target[:]))
|
||||
closest := t.tab.findnodeByID(target, bucketSize, true).entries
|
||||
|
||||
// Send neighbors in chunks with at most maxNeighbors per packet
|
||||
// to stay below the packet size limit.
|
||||
p := v4wire.Neighbors{Expiration: uint64(time.Now().Add(expiration).Unix())}
|
||||
var sent bool
|
||||
for _, n := range closest {
|
||||
if netutil.CheckRelayIP(from.IP, n.IP()) == nil {
|
||||
p.Nodes = append(p.Nodes, nodeToRPC(n))
|
||||
}
|
||||
if len(p.Nodes) == v4wire.MaxNeighbors {
|
||||
t.send(from, fromID, &p)
|
||||
p.Nodes = p.Nodes[:0]
|
||||
sent = true
|
||||
}
|
||||
}
|
||||
if len(p.Nodes) > 0 || !sent {
|
||||
t.send(from, fromID, &p)
|
||||
}
|
||||
}
|
||||
|
||||
// NEIGHBORS/v4
|
||||
|
||||
func (t *UDPv4) verifyNeighbors(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error {
|
||||
req := h.Packet.(*v4wire.Neighbors)
|
||||
|
||||
if v4wire.Expired(req.Expiration) {
|
||||
return errExpired
|
||||
}
|
||||
if !t.handleReply(fromID, from.IP, h.Packet) {
|
||||
return errUnsolicitedReply
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ENRREQUEST/v4
|
||||
|
||||
func (t *UDPv4) verifyENRRequest(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error {
|
||||
req := h.Packet.(*v4wire.ENRRequest)
|
||||
|
||||
if v4wire.Expired(req.Expiration) {
|
||||
return errExpired
|
||||
}
|
||||
if !t.checkBond(fromID, from.IP) {
|
||||
return errUnknownNode
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *UDPv4) handleENRRequest(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, mac []byte) {
|
||||
t.send(from, fromID, &v4wire.ENRResponse{
|
||||
ReplyTok: mac,
|
||||
Record: *t.localNode.Node().Record(),
|
||||
})
|
||||
}
|
||||
|
||||
// ENRRESPONSE/v4
|
||||
|
||||
func (t *UDPv4) verifyENRResponse(h *packetHandlerV4, from *net.UDPAddr, fromID enode.ID, fromKey v4wire.Pubkey) error {
|
||||
if !t.handleReply(fromID, from.IP, h.Packet) {
|
||||
return errUnsolicitedReply
|
||||
}
|
||||
return nil
|
||||
}
|
||||
294
vendor/github.com/ethereum/go-ethereum/p2p/discover/v4wire/v4wire.go
generated
vendored
Normal file
294
vendor/github.com/ethereum/go-ethereum/p2p/discover/v4wire/v4wire.go
generated
vendored
Normal file
@@ -0,0 +1,294 @@
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Package v4wire implements the Discovery v4 Wire Protocol.
|
||||
package v4wire
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethereum/go-ethereum/p2p/enr"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
// RPC packet types
|
||||
const (
|
||||
PingPacket = iota + 1 // zero is 'reserved'
|
||||
PongPacket
|
||||
FindnodePacket
|
||||
NeighborsPacket
|
||||
ENRRequestPacket
|
||||
ENRResponsePacket
|
||||
)
|
||||
|
||||
// RPC request structures
|
||||
type (
|
||||
Ping struct {
|
||||
Version uint
|
||||
From, To Endpoint
|
||||
Expiration uint64
|
||||
ENRSeq uint64 `rlp:"optional"` // Sequence number of local record, added by EIP-868.
|
||||
|
||||
// Ignore additional fields (for forward compatibility).
|
||||
Rest []rlp.RawValue `rlp:"tail"`
|
||||
}
|
||||
|
||||
// Pong is the reply to ping.
|
||||
Pong struct {
|
||||
// This field should mirror the UDP envelope address
|
||||
// of the ping packet, which provides a way to discover the
|
||||
// external address (after NAT).
|
||||
To Endpoint
|
||||
ReplyTok []byte // This contains the hash of the ping packet.
|
||||
Expiration uint64 // Absolute timestamp at which the packet becomes invalid.
|
||||
ENRSeq uint64 `rlp:"optional"` // Sequence number of local record, added by EIP-868.
|
||||
|
||||
// Ignore additional fields (for forward compatibility).
|
||||
Rest []rlp.RawValue `rlp:"tail"`
|
||||
}
|
||||
|
||||
// Findnode is a query for nodes close to the given target.
|
||||
Findnode struct {
|
||||
Target Pubkey
|
||||
Expiration uint64
|
||||
// Ignore additional fields (for forward compatibility).
|
||||
Rest []rlp.RawValue `rlp:"tail"`
|
||||
}
|
||||
|
||||
// Neighbors is the reply to findnode.
|
||||
Neighbors struct {
|
||||
Nodes []Node
|
||||
Expiration uint64
|
||||
// Ignore additional fields (for forward compatibility).
|
||||
Rest []rlp.RawValue `rlp:"tail"`
|
||||
}
|
||||
|
||||
// ENRRequest queries for the remote node's record.
|
||||
ENRRequest struct {
|
||||
Expiration uint64
|
||||
// Ignore additional fields (for forward compatibility).
|
||||
Rest []rlp.RawValue `rlp:"tail"`
|
||||
}
|
||||
|
||||
// ENRResponse is the reply to ENRRequest.
|
||||
ENRResponse struct {
|
||||
ReplyTok []byte // Hash of the ENRRequest packet.
|
||||
Record enr.Record
|
||||
// Ignore additional fields (for forward compatibility).
|
||||
Rest []rlp.RawValue `rlp:"tail"`
|
||||
}
|
||||
)
|
||||
|
||||
// MaxNeighbors is the maximum number of neighbor nodes in a Neighbors packet.
|
||||
const MaxNeighbors = 12
|
||||
|
||||
// This code computes the MaxNeighbors constant value.
|
||||
|
||||
// func init() {
|
||||
// var maxNeighbors int
|
||||
// p := Neighbors{Expiration: ^uint64(0)}
|
||||
// maxSizeNode := Node{IP: make(net.IP, 16), UDP: ^uint16(0), TCP: ^uint16(0)}
|
||||
// for n := 0; ; n++ {
|
||||
// p.Nodes = append(p.Nodes, maxSizeNode)
|
||||
// size, _, err := rlp.EncodeToReader(p)
|
||||
// if err != nil {
|
||||
// // If this ever happens, it will be caught by the unit tests.
|
||||
// panic("cannot encode: " + err.Error())
|
||||
// }
|
||||
// if headSize+size+1 >= 1280 {
|
||||
// maxNeighbors = n
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// fmt.Println("maxNeighbors", maxNeighbors)
|
||||
// }
|
||||
|
||||
// Pubkey represents an encoded 64-byte secp256k1 public key.
|
||||
type Pubkey [64]byte
|
||||
|
||||
// ID returns the node ID corresponding to the public key.
|
||||
func (e Pubkey) ID() enode.ID {
|
||||
return enode.ID(crypto.Keccak256Hash(e[:]))
|
||||
}
|
||||
|
||||
// Node represents information about a node.
|
||||
type Node struct {
|
||||
IP net.IP // len 4 for IPv4 or 16 for IPv6
|
||||
UDP uint16 // for discovery protocol
|
||||
TCP uint16 // for RLPx protocol
|
||||
ID Pubkey
|
||||
}
|
||||
|
||||
// Endpoint represents a network endpoint.
|
||||
type Endpoint struct {
|
||||
IP net.IP // len 4 for IPv4 or 16 for IPv6
|
||||
UDP uint16 // for discovery protocol
|
||||
TCP uint16 // for RLPx protocol
|
||||
}
|
||||
|
||||
// NewEndpoint creates an endpoint.
|
||||
func NewEndpoint(addr *net.UDPAddr, tcpPort uint16) Endpoint {
|
||||
ip := net.IP{}
|
||||
if ip4 := addr.IP.To4(); ip4 != nil {
|
||||
ip = ip4
|
||||
} else if ip6 := addr.IP.To16(); ip6 != nil {
|
||||
ip = ip6
|
||||
}
|
||||
return Endpoint{IP: ip, UDP: uint16(addr.Port), TCP: tcpPort}
|
||||
}
|
||||
|
||||
type Packet interface {
|
||||
// Name is the name of the package, for logging purposes.
|
||||
Name() string
|
||||
// Kind is the packet type, for logging purposes.
|
||||
Kind() byte
|
||||
}
|
||||
|
||||
func (req *Ping) Name() string { return "PING/v4" }
|
||||
func (req *Ping) Kind() byte { return PingPacket }
|
||||
|
||||
func (req *Pong) Name() string { return "PONG/v4" }
|
||||
func (req *Pong) Kind() byte { return PongPacket }
|
||||
|
||||
func (req *Findnode) Name() string { return "FINDNODE/v4" }
|
||||
func (req *Findnode) Kind() byte { return FindnodePacket }
|
||||
|
||||
func (req *Neighbors) Name() string { return "NEIGHBORS/v4" }
|
||||
func (req *Neighbors) Kind() byte { return NeighborsPacket }
|
||||
|
||||
func (req *ENRRequest) Name() string { return "ENRREQUEST/v4" }
|
||||
func (req *ENRRequest) Kind() byte { return ENRRequestPacket }
|
||||
|
||||
func (req *ENRResponse) Name() string { return "ENRRESPONSE/v4" }
|
||||
func (req *ENRResponse) Kind() byte { return ENRResponsePacket }
|
||||
|
||||
// Expired checks whether the given UNIX time stamp is in the past.
|
||||
func Expired(ts uint64) bool {
|
||||
return time.Unix(int64(ts), 0).Before(time.Now())
|
||||
}
|
||||
|
||||
// Encoder/decoder.
|
||||
|
||||
const (
|
||||
macSize = 32
|
||||
sigSize = crypto.SignatureLength
|
||||
headSize = macSize + sigSize // space of packet frame data
|
||||
)
|
||||
|
||||
var (
|
||||
ErrPacketTooSmall = errors.New("too small")
|
||||
ErrBadHash = errors.New("bad hash")
|
||||
ErrBadPoint = errors.New("invalid curve point")
|
||||
)
|
||||
|
||||
var headSpace = make([]byte, headSize)
|
||||
|
||||
// Decode reads a discovery v4 packet.
|
||||
func Decode(input []byte) (Packet, Pubkey, []byte, error) {
|
||||
if len(input) < headSize+1 {
|
||||
return nil, Pubkey{}, nil, ErrPacketTooSmall
|
||||
}
|
||||
hash, sig, sigdata := input[:macSize], input[macSize:headSize], input[headSize:]
|
||||
shouldhash := crypto.Keccak256(input[macSize:])
|
||||
if !bytes.Equal(hash, shouldhash) {
|
||||
return nil, Pubkey{}, nil, ErrBadHash
|
||||
}
|
||||
fromKey, err := recoverNodeKey(crypto.Keccak256(input[headSize:]), sig)
|
||||
if err != nil {
|
||||
return nil, fromKey, hash, err
|
||||
}
|
||||
|
||||
var req Packet
|
||||
switch ptype := sigdata[0]; ptype {
|
||||
case PingPacket:
|
||||
req = new(Ping)
|
||||
case PongPacket:
|
||||
req = new(Pong)
|
||||
case FindnodePacket:
|
||||
req = new(Findnode)
|
||||
case NeighborsPacket:
|
||||
req = new(Neighbors)
|
||||
case ENRRequestPacket:
|
||||
req = new(ENRRequest)
|
||||
case ENRResponsePacket:
|
||||
req = new(ENRResponse)
|
||||
default:
|
||||
return nil, fromKey, hash, fmt.Errorf("unknown type: %d", ptype)
|
||||
}
|
||||
s := rlp.NewStream(bytes.NewReader(sigdata[1:]), 0)
|
||||
err = s.Decode(req)
|
||||
return req, fromKey, hash, err
|
||||
}
|
||||
|
||||
// Encode encodes a discovery packet.
|
||||
func Encode(priv *ecdsa.PrivateKey, req Packet) (packet, hash []byte, err error) {
|
||||
b := new(bytes.Buffer)
|
||||
b.Write(headSpace)
|
||||
b.WriteByte(req.Kind())
|
||||
if err := rlp.Encode(b, req); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
packet = b.Bytes()
|
||||
sig, err := crypto.Sign(crypto.Keccak256(packet[headSize:]), priv)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
copy(packet[macSize:], sig)
|
||||
// Add the hash to the front. Note: this doesn't protect the packet in any way.
|
||||
hash = crypto.Keccak256(packet[macSize:])
|
||||
copy(packet, hash)
|
||||
return packet, hash, nil
|
||||
}
|
||||
|
||||
// recoverNodeKey computes the public key used to sign the given hash from the signature.
|
||||
func recoverNodeKey(hash, sig []byte) (key Pubkey, err error) {
|
||||
pubkey, err := crypto.Ecrecover(hash, sig)
|
||||
if err != nil {
|
||||
return key, err
|
||||
}
|
||||
copy(key[:], pubkey[1:])
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// EncodePubkey encodes a secp256k1 public key.
|
||||
func EncodePubkey(key *ecdsa.PublicKey) Pubkey {
|
||||
var e Pubkey
|
||||
math.ReadBits(key.X, e[:len(e)/2])
|
||||
math.ReadBits(key.Y, e[len(e)/2:])
|
||||
return e
|
||||
}
|
||||
|
||||
// DecodePubkey reads an encoded secp256k1 public key.
|
||||
func DecodePubkey(curve elliptic.Curve, e Pubkey) (*ecdsa.PublicKey, error) {
|
||||
p := &ecdsa.PublicKey{Curve: curve, X: new(big.Int), Y: new(big.Int)}
|
||||
half := len(e) / 2
|
||||
p.X.SetBytes(e[:half])
|
||||
p.Y.SetBytes(e[half:])
|
||||
if !p.Curve.IsOnCurve(p.X, p.Y) {
|
||||
return nil, ErrBadPoint
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
861
vendor/github.com/ethereum/go-ethereum/p2p/discover/v5_udp.go
generated
vendored
Normal file
861
vendor/github.com/ethereum/go-ethereum/p2p/discover/v5_udp.go
generated
vendored
Normal file
@@ -0,0 +1,861 @@
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package discover
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
crand "crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/mclock"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/p2p/discover/v5wire"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethereum/go-ethereum/p2p/enr"
|
||||
"github.com/ethereum/go-ethereum/p2p/netutil"
|
||||
)
|
||||
|
||||
const (
|
||||
lookupRequestLimit = 3 // max requests against a single node during lookup
|
||||
findnodeResultLimit = 16 // applies in FINDNODE handler
|
||||
totalNodesResponseLimit = 5 // applies in waitForNodes
|
||||
nodesResponseItemLimit = 3 // applies in sendNodes
|
||||
|
||||
respTimeoutV5 = 700 * time.Millisecond
|
||||
)
|
||||
|
||||
// codecV5 is implemented by v5wire.Codec (and testCodec).
|
||||
//
|
||||
// The UDPv5 transport is split into two objects: the codec object deals with
|
||||
// encoding/decoding and with the handshake; the UDPv5 object handles higher-level concerns.
|
||||
type codecV5 interface {
|
||||
// Encode encodes a packet.
|
||||
Encode(enode.ID, string, v5wire.Packet, *v5wire.Whoareyou) ([]byte, v5wire.Nonce, error)
|
||||
|
||||
// Decode decodes a packet. It returns a *v5wire.Unknown packet if decryption fails.
|
||||
// The *enode.Node return value is non-nil when the input contains a handshake response.
|
||||
Decode([]byte, string) (enode.ID, *enode.Node, v5wire.Packet, error)
|
||||
}
|
||||
|
||||
// UDPv5 is the implementation of protocol version 5.
|
||||
type UDPv5 struct {
|
||||
// static fields
|
||||
conn UDPConn
|
||||
tab *Table
|
||||
netrestrict *netutil.Netlist
|
||||
priv *ecdsa.PrivateKey
|
||||
localNode *enode.LocalNode
|
||||
db *enode.DB
|
||||
log log.Logger
|
||||
clock mclock.Clock
|
||||
validSchemes enr.IdentityScheme
|
||||
|
||||
// talkreq handler registry
|
||||
trlock sync.Mutex
|
||||
trhandlers map[string]TalkRequestHandler
|
||||
|
||||
// channels into dispatch
|
||||
packetInCh chan ReadPacket
|
||||
readNextCh chan struct{}
|
||||
callCh chan *callV5
|
||||
callDoneCh chan *callV5
|
||||
respTimeoutCh chan *callTimeout
|
||||
|
||||
// state of dispatch
|
||||
codec codecV5
|
||||
activeCallByNode map[enode.ID]*callV5
|
||||
activeCallByAuth map[v5wire.Nonce]*callV5
|
||||
callQueue map[enode.ID][]*callV5
|
||||
|
||||
// shutdown stuff
|
||||
closeOnce sync.Once
|
||||
closeCtx context.Context
|
||||
cancelCloseCtx context.CancelFunc
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
// TalkRequestHandler callback processes a talk request and optionally returns a reply
|
||||
type TalkRequestHandler func(enode.ID, *net.UDPAddr, []byte) []byte
|
||||
|
||||
// callV5 represents a remote procedure call against another node.
|
||||
type callV5 struct {
|
||||
node *enode.Node
|
||||
packet v5wire.Packet
|
||||
responseType byte // expected packet type of response
|
||||
reqid []byte
|
||||
ch chan v5wire.Packet // responses sent here
|
||||
err chan error // errors sent here
|
||||
|
||||
// Valid for active calls only:
|
||||
nonce v5wire.Nonce // nonce of request packet
|
||||
handshakeCount int // # times we attempted handshake for this call
|
||||
challenge *v5wire.Whoareyou // last sent handshake challenge
|
||||
timeout mclock.Timer
|
||||
}
|
||||
|
||||
// callTimeout is the response timeout event of a call.
|
||||
type callTimeout struct {
|
||||
c *callV5
|
||||
timer mclock.Timer
|
||||
}
|
||||
|
||||
// ListenV5 listens on the given connection.
|
||||
func ListenV5(conn UDPConn, ln *enode.LocalNode, cfg Config) (*UDPv5, error) {
|
||||
t, err := newUDPv5(conn, ln, cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
go t.tab.loop()
|
||||
t.wg.Add(2)
|
||||
go t.readLoop()
|
||||
go t.dispatch()
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// newUDPv5 creates a UDPv5 transport, but doesn't start any goroutines.
|
||||
func newUDPv5(conn UDPConn, ln *enode.LocalNode, cfg Config) (*UDPv5, error) {
|
||||
closeCtx, cancelCloseCtx := context.WithCancel(context.Background())
|
||||
cfg = cfg.withDefaults()
|
||||
t := &UDPv5{
|
||||
// static fields
|
||||
conn: conn,
|
||||
localNode: ln,
|
||||
db: ln.Database(),
|
||||
netrestrict: cfg.NetRestrict,
|
||||
priv: cfg.PrivateKey,
|
||||
log: cfg.Log,
|
||||
validSchemes: cfg.ValidSchemes,
|
||||
clock: cfg.Clock,
|
||||
trhandlers: make(map[string]TalkRequestHandler),
|
||||
// channels into dispatch
|
||||
packetInCh: make(chan ReadPacket, 1),
|
||||
readNextCh: make(chan struct{}, 1),
|
||||
callCh: make(chan *callV5),
|
||||
callDoneCh: make(chan *callV5),
|
||||
respTimeoutCh: make(chan *callTimeout),
|
||||
// state of dispatch
|
||||
codec: v5wire.NewCodec(ln, cfg.PrivateKey, cfg.Clock),
|
||||
activeCallByNode: make(map[enode.ID]*callV5),
|
||||
activeCallByAuth: make(map[v5wire.Nonce]*callV5),
|
||||
callQueue: make(map[enode.ID][]*callV5),
|
||||
// shutdown
|
||||
closeCtx: closeCtx,
|
||||
cancelCloseCtx: cancelCloseCtx,
|
||||
}
|
||||
tab, err := newTable(t, t.db, cfg.Bootnodes, cfg.Log)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t.tab = tab
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// Self returns the local node record.
|
||||
func (t *UDPv5) Self() *enode.Node {
|
||||
return t.localNode.Node()
|
||||
}
|
||||
|
||||
// Close shuts down packet processing.
|
||||
func (t *UDPv5) Close() {
|
||||
t.closeOnce.Do(func() {
|
||||
t.cancelCloseCtx()
|
||||
t.conn.Close()
|
||||
t.wg.Wait()
|
||||
t.tab.close()
|
||||
})
|
||||
}
|
||||
|
||||
// Ping sends a ping message to the given node.
|
||||
func (t *UDPv5) Ping(n *enode.Node) error {
|
||||
_, err := t.ping(n)
|
||||
return err
|
||||
}
|
||||
|
||||
// Resolve searches for a specific node with the given ID and tries to get the most recent
|
||||
// version of the node record for it. It returns n if the node could not be resolved.
|
||||
func (t *UDPv5) Resolve(n *enode.Node) *enode.Node {
|
||||
if intable := t.tab.getNode(n.ID()); intable != nil && intable.Seq() > n.Seq() {
|
||||
n = intable
|
||||
}
|
||||
// Try asking directly. This works if the node is still responding on the endpoint we have.
|
||||
if resp, err := t.RequestENR(n); err == nil {
|
||||
return resp
|
||||
}
|
||||
// Otherwise do a network lookup.
|
||||
result := t.Lookup(n.ID())
|
||||
for _, rn := range result {
|
||||
if rn.ID() == n.ID() && rn.Seq() > n.Seq() {
|
||||
return rn
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// AllNodes returns all the nodes stored in the local table.
|
||||
func (t *UDPv5) AllNodes() []*enode.Node {
|
||||
t.tab.mutex.Lock()
|
||||
defer t.tab.mutex.Unlock()
|
||||
nodes := make([]*enode.Node, 0)
|
||||
|
||||
for _, b := range &t.tab.buckets {
|
||||
for _, n := range b.entries {
|
||||
nodes = append(nodes, unwrapNode(n))
|
||||
}
|
||||
}
|
||||
return nodes
|
||||
}
|
||||
|
||||
// LocalNode returns the current local node running the
|
||||
// protocol.
|
||||
func (t *UDPv5) LocalNode() *enode.LocalNode {
|
||||
return t.localNode
|
||||
}
|
||||
|
||||
// RegisterTalkHandler adds a handler for 'talk requests'. The handler function is called
|
||||
// whenever a request for the given protocol is received and should return the response
|
||||
// data or nil.
|
||||
func (t *UDPv5) RegisterTalkHandler(protocol string, handler TalkRequestHandler) {
|
||||
t.trlock.Lock()
|
||||
defer t.trlock.Unlock()
|
||||
t.trhandlers[protocol] = handler
|
||||
}
|
||||
|
||||
// TalkRequest sends a talk request to n and waits for a response.
|
||||
func (t *UDPv5) TalkRequest(n *enode.Node, protocol string, request []byte) ([]byte, error) {
|
||||
req := &v5wire.TalkRequest{Protocol: protocol, Message: request}
|
||||
resp := t.call(n, v5wire.TalkResponseMsg, req)
|
||||
defer t.callDone(resp)
|
||||
select {
|
||||
case respMsg := <-resp.ch:
|
||||
return respMsg.(*v5wire.TalkResponse).Message, nil
|
||||
case err := <-resp.err:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// RandomNodes returns an iterator that finds random nodes in the DHT.
|
||||
func (t *UDPv5) RandomNodes() enode.Iterator {
|
||||
if t.tab.len() == 0 {
|
||||
// All nodes were dropped, refresh. The very first query will hit this
|
||||
// case and run the bootstrapping logic.
|
||||
<-t.tab.refresh()
|
||||
}
|
||||
|
||||
return newLookupIterator(t.closeCtx, t.newRandomLookup)
|
||||
}
|
||||
|
||||
// Lookup performs a recursive lookup for the given target.
|
||||
// It returns the closest nodes to target.
|
||||
func (t *UDPv5) Lookup(target enode.ID) []*enode.Node {
|
||||
return t.newLookup(t.closeCtx, target).run()
|
||||
}
|
||||
|
||||
// lookupRandom looks up a random target.
|
||||
// This is needed to satisfy the transport interface.
|
||||
func (t *UDPv5) lookupRandom() []*enode.Node {
|
||||
return t.newRandomLookup(t.closeCtx).run()
|
||||
}
|
||||
|
||||
// lookupSelf looks up our own node ID.
|
||||
// This is needed to satisfy the transport interface.
|
||||
func (t *UDPv5) lookupSelf() []*enode.Node {
|
||||
return t.newLookup(t.closeCtx, t.Self().ID()).run()
|
||||
}
|
||||
|
||||
func (t *UDPv5) newRandomLookup(ctx context.Context) *lookup {
|
||||
var target enode.ID
|
||||
crand.Read(target[:])
|
||||
return t.newLookup(ctx, target)
|
||||
}
|
||||
|
||||
func (t *UDPv5) newLookup(ctx context.Context, target enode.ID) *lookup {
|
||||
return newLookup(ctx, t.tab, target, func(n *node) ([]*node, error) {
|
||||
return t.lookupWorker(n, target)
|
||||
})
|
||||
}
|
||||
|
||||
// lookupWorker performs FINDNODE calls against a single node during lookup.
|
||||
func (t *UDPv5) lookupWorker(destNode *node, target enode.ID) ([]*node, error) {
|
||||
var (
|
||||
dists = lookupDistances(target, destNode.ID())
|
||||
nodes = nodesByDistance{target: target}
|
||||
err error
|
||||
)
|
||||
var r []*enode.Node
|
||||
r, err = t.findnode(unwrapNode(destNode), dists)
|
||||
if errors.Is(err, errClosed) {
|
||||
return nil, err
|
||||
}
|
||||
for _, n := range r {
|
||||
if n.ID() != t.Self().ID() {
|
||||
nodes.push(wrapNode(n), findnodeResultLimit)
|
||||
}
|
||||
}
|
||||
return nodes.entries, err
|
||||
}
|
||||
|
||||
// lookupDistances computes the distance parameter for FINDNODE calls to dest.
|
||||
// It chooses distances adjacent to logdist(target, dest), e.g. for a target
|
||||
// with logdist(target, dest) = 255 the result is [255, 256, 254].
|
||||
func lookupDistances(target, dest enode.ID) (dists []uint) {
|
||||
td := enode.LogDist(target, dest)
|
||||
dists = append(dists, uint(td))
|
||||
for i := 1; len(dists) < lookupRequestLimit; i++ {
|
||||
if td+i < 256 {
|
||||
dists = append(dists, uint(td+i))
|
||||
}
|
||||
if td-i > 0 {
|
||||
dists = append(dists, uint(td-i))
|
||||
}
|
||||
}
|
||||
return dists
|
||||
}
|
||||
|
||||
// ping calls PING on a node and waits for a PONG response.
|
||||
func (t *UDPv5) ping(n *enode.Node) (uint64, error) {
|
||||
req := &v5wire.Ping{ENRSeq: t.localNode.Node().Seq()}
|
||||
resp := t.call(n, v5wire.PongMsg, req)
|
||||
defer t.callDone(resp)
|
||||
|
||||
select {
|
||||
case pong := <-resp.ch:
|
||||
return pong.(*v5wire.Pong).ENRSeq, nil
|
||||
case err := <-resp.err:
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
// RequestENR requests n's record.
|
||||
func (t *UDPv5) RequestENR(n *enode.Node) (*enode.Node, error) {
|
||||
nodes, err := t.findnode(n, []uint{0})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(nodes) != 1 {
|
||||
return nil, fmt.Errorf("%d nodes in response for distance zero", len(nodes))
|
||||
}
|
||||
return nodes[0], nil
|
||||
}
|
||||
|
||||
// findnode calls FINDNODE on a node and waits for responses.
|
||||
func (t *UDPv5) findnode(n *enode.Node, distances []uint) ([]*enode.Node, error) {
|
||||
resp := t.call(n, v5wire.NodesMsg, &v5wire.Findnode{Distances: distances})
|
||||
return t.waitForNodes(resp, distances)
|
||||
}
|
||||
|
||||
// waitForNodes waits for NODES responses to the given call.
|
||||
func (t *UDPv5) waitForNodes(c *callV5, distances []uint) ([]*enode.Node, error) {
|
||||
defer t.callDone(c)
|
||||
|
||||
var (
|
||||
nodes []*enode.Node
|
||||
seen = make(map[enode.ID]struct{})
|
||||
received, total = 0, -1
|
||||
)
|
||||
for {
|
||||
select {
|
||||
case responseP := <-c.ch:
|
||||
response := responseP.(*v5wire.Nodes)
|
||||
for _, record := range response.Nodes {
|
||||
node, err := t.verifyResponseNode(c, record, distances, seen)
|
||||
if err != nil {
|
||||
t.log.Debug("Invalid record in "+response.Name(), "id", c.node.ID(), "err", err)
|
||||
continue
|
||||
}
|
||||
nodes = append(nodes, node)
|
||||
}
|
||||
if total == -1 {
|
||||
total = min(int(response.Total), totalNodesResponseLimit)
|
||||
}
|
||||
if received++; received == total {
|
||||
return nodes, nil
|
||||
}
|
||||
case err := <-c.err:
|
||||
return nodes, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// verifyResponseNode checks validity of a record in a NODES response.
|
||||
func (t *UDPv5) verifyResponseNode(c *callV5, r *enr.Record, distances []uint, seen map[enode.ID]struct{}) (*enode.Node, error) {
|
||||
node, err := enode.New(t.validSchemes, r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := netutil.CheckRelayIP(c.node.IP(), node.IP()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if t.netrestrict != nil && !t.netrestrict.Contains(node.IP()) {
|
||||
return nil, errors.New("not contained in netrestrict list")
|
||||
}
|
||||
if c.node.UDP() <= 1024 {
|
||||
return nil, errLowPort
|
||||
}
|
||||
if distances != nil {
|
||||
nd := enode.LogDist(c.node.ID(), node.ID())
|
||||
if !containsUint(uint(nd), distances) {
|
||||
return nil, errors.New("does not match any requested distance")
|
||||
}
|
||||
}
|
||||
if _, ok := seen[node.ID()]; ok {
|
||||
return nil, fmt.Errorf("duplicate record")
|
||||
}
|
||||
seen[node.ID()] = struct{}{}
|
||||
return node, nil
|
||||
}
|
||||
|
||||
func containsUint(x uint, xs []uint) bool {
|
||||
for _, v := range xs {
|
||||
if x == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// call sends the given call and sets up a handler for response packets (of message type
|
||||
// responseType). Responses are dispatched to the call's response channel.
|
||||
func (t *UDPv5) call(node *enode.Node, responseType byte, packet v5wire.Packet) *callV5 {
|
||||
c := &callV5{
|
||||
node: node,
|
||||
packet: packet,
|
||||
responseType: responseType,
|
||||
reqid: make([]byte, 8),
|
||||
ch: make(chan v5wire.Packet, 1),
|
||||
err: make(chan error, 1),
|
||||
}
|
||||
// Assign request ID.
|
||||
crand.Read(c.reqid)
|
||||
packet.SetRequestID(c.reqid)
|
||||
// Send call to dispatch.
|
||||
select {
|
||||
case t.callCh <- c:
|
||||
case <-t.closeCtx.Done():
|
||||
c.err <- errClosed
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// callDone tells dispatch that the active call is done.
|
||||
func (t *UDPv5) callDone(c *callV5) {
|
||||
// This needs a loop because further responses may be incoming until the
|
||||
// send to callDoneCh has completed. Such responses need to be discarded
|
||||
// in order to avoid blocking the dispatch loop.
|
||||
for {
|
||||
select {
|
||||
case <-c.ch:
|
||||
// late response, discard.
|
||||
case <-c.err:
|
||||
// late error, discard.
|
||||
case t.callDoneCh <- c:
|
||||
return
|
||||
case <-t.closeCtx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// dispatch runs in its own goroutine, handles incoming packets and deals with calls.
|
||||
//
|
||||
// For any destination node there is at most one 'active call', stored in the t.activeCall*
|
||||
// maps. A call is made active when it is sent. The active call can be answered by a
|
||||
// matching response, in which case c.ch receives the response; or by timing out, in which case
|
||||
// c.err receives the error. When the function that created the call signals the active
|
||||
// call is done through callDone, the next call from the call queue is started.
|
||||
//
|
||||
// Calls may also be answered by a WHOAREYOU packet referencing the call packet's authTag.
|
||||
// When that happens the call is simply re-sent to complete the handshake. We allow one
|
||||
// handshake attempt per call.
|
||||
func (t *UDPv5) dispatch() {
|
||||
defer t.wg.Done()
|
||||
|
||||
// Arm first read.
|
||||
t.readNextCh <- struct{}{}
|
||||
|
||||
for {
|
||||
select {
|
||||
case c := <-t.callCh:
|
||||
id := c.node.ID()
|
||||
t.callQueue[id] = append(t.callQueue[id], c)
|
||||
t.sendNextCall(id)
|
||||
|
||||
case ct := <-t.respTimeoutCh:
|
||||
active := t.activeCallByNode[ct.c.node.ID()]
|
||||
if ct.c == active && ct.timer == active.timeout {
|
||||
ct.c.err <- errTimeout
|
||||
}
|
||||
|
||||
case c := <-t.callDoneCh:
|
||||
id := c.node.ID()
|
||||
active := t.activeCallByNode[id]
|
||||
if active != c {
|
||||
panic("BUG: callDone for inactive call")
|
||||
}
|
||||
c.timeout.Stop()
|
||||
delete(t.activeCallByAuth, c.nonce)
|
||||
delete(t.activeCallByNode, id)
|
||||
t.sendNextCall(id)
|
||||
|
||||
case p := <-t.packetInCh:
|
||||
t.handlePacket(p.Data, p.Addr)
|
||||
// Arm next read.
|
||||
t.readNextCh <- struct{}{}
|
||||
|
||||
case <-t.closeCtx.Done():
|
||||
close(t.readNextCh)
|
||||
for id, queue := range t.callQueue {
|
||||
for _, c := range queue {
|
||||
c.err <- errClosed
|
||||
}
|
||||
delete(t.callQueue, id)
|
||||
}
|
||||
for id, c := range t.activeCallByNode {
|
||||
c.err <- errClosed
|
||||
delete(t.activeCallByNode, id)
|
||||
delete(t.activeCallByAuth, c.nonce)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// startResponseTimeout sets the response timer for a call.
|
||||
func (t *UDPv5) startResponseTimeout(c *callV5) {
|
||||
if c.timeout != nil {
|
||||
c.timeout.Stop()
|
||||
}
|
||||
var (
|
||||
timer mclock.Timer
|
||||
done = make(chan struct{})
|
||||
)
|
||||
timer = t.clock.AfterFunc(respTimeoutV5, func() {
|
||||
<-done
|
||||
select {
|
||||
case t.respTimeoutCh <- &callTimeout{c, timer}:
|
||||
case <-t.closeCtx.Done():
|
||||
}
|
||||
})
|
||||
c.timeout = timer
|
||||
close(done)
|
||||
}
|
||||
|
||||
// sendNextCall sends the next call in the call queue if there is no active call.
|
||||
func (t *UDPv5) sendNextCall(id enode.ID) {
|
||||
queue := t.callQueue[id]
|
||||
if len(queue) == 0 || t.activeCallByNode[id] != nil {
|
||||
return
|
||||
}
|
||||
t.activeCallByNode[id] = queue[0]
|
||||
t.sendCall(t.activeCallByNode[id])
|
||||
if len(queue) == 1 {
|
||||
delete(t.callQueue, id)
|
||||
} else {
|
||||
copy(queue, queue[1:])
|
||||
t.callQueue[id] = queue[:len(queue)-1]
|
||||
}
|
||||
}
|
||||
|
||||
// sendCall encodes and sends a request packet to the call's recipient node.
|
||||
// This performs a handshake if needed.
|
||||
func (t *UDPv5) sendCall(c *callV5) {
|
||||
// The call might have a nonce from a previous handshake attempt. Remove the entry for
|
||||
// the old nonce because we're about to generate a new nonce for this call.
|
||||
if c.nonce != (v5wire.Nonce{}) {
|
||||
delete(t.activeCallByAuth, c.nonce)
|
||||
}
|
||||
|
||||
addr := &net.UDPAddr{IP: c.node.IP(), Port: c.node.UDP()}
|
||||
newNonce, _ := t.send(c.node.ID(), addr, c.packet, c.challenge)
|
||||
c.nonce = newNonce
|
||||
t.activeCallByAuth[newNonce] = c
|
||||
t.startResponseTimeout(c)
|
||||
}
|
||||
|
||||
// sendResponse sends a response packet to the given node.
|
||||
// This doesn't trigger a handshake even if no keys are available.
|
||||
func (t *UDPv5) sendResponse(toID enode.ID, toAddr *net.UDPAddr, packet v5wire.Packet) error {
|
||||
_, err := t.send(toID, toAddr, packet, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// send sends a packet to the given node.
|
||||
func (t *UDPv5) send(toID enode.ID, toAddr *net.UDPAddr, packet v5wire.Packet, c *v5wire.Whoareyou) (v5wire.Nonce, error) {
|
||||
addr := toAddr.String()
|
||||
enc, nonce, err := t.codec.Encode(toID, addr, packet, c)
|
||||
if err != nil {
|
||||
t.log.Warn(">> "+packet.Name(), "id", toID, "addr", addr, "err", err)
|
||||
return nonce, err
|
||||
}
|
||||
_, err = t.conn.WriteToUDP(enc, toAddr)
|
||||
t.log.Trace(">> "+packet.Name(), "id", toID, "addr", addr)
|
||||
return nonce, err
|
||||
}
|
||||
|
||||
// readLoop runs in its own goroutine and reads packets from the network.
|
||||
func (t *UDPv5) readLoop() {
|
||||
defer t.wg.Done()
|
||||
|
||||
buf := make([]byte, maxPacketSize)
|
||||
for range t.readNextCh {
|
||||
nbytes, from, err := t.conn.ReadFromUDP(buf)
|
||||
if netutil.IsTemporaryError(err) {
|
||||
// Ignore temporary read errors.
|
||||
t.log.Debug("Temporary UDP read error", "err", err)
|
||||
continue
|
||||
} else if err != nil {
|
||||
// Shut down the loop for permanent errors.
|
||||
if !errors.Is(err, io.EOF) {
|
||||
t.log.Debug("UDP read error", "err", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
t.dispatchReadPacket(from, buf[:nbytes])
|
||||
}
|
||||
}
|
||||
|
||||
// dispatchReadPacket sends a packet into the dispatch loop.
|
||||
func (t *UDPv5) dispatchReadPacket(from *net.UDPAddr, content []byte) bool {
|
||||
select {
|
||||
case t.packetInCh <- ReadPacket{content, from}:
|
||||
return true
|
||||
case <-t.closeCtx.Done():
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// handlePacket decodes and processes an incoming packet from the network.
|
||||
func (t *UDPv5) handlePacket(rawpacket []byte, fromAddr *net.UDPAddr) error {
|
||||
addr := fromAddr.String()
|
||||
fromID, fromNode, packet, err := t.codec.Decode(rawpacket, addr)
|
||||
if err != nil {
|
||||
t.log.Debug("Bad discv5 packet", "id", fromID, "addr", addr, "err", err)
|
||||
return err
|
||||
}
|
||||
if fromNode != nil {
|
||||
// Handshake succeeded, add to table.
|
||||
t.tab.addSeenNode(wrapNode(fromNode))
|
||||
}
|
||||
if packet.Kind() != v5wire.WhoareyouPacket {
|
||||
// WHOAREYOU logged separately to report errors.
|
||||
t.log.Trace("<< "+packet.Name(), "id", fromID, "addr", addr)
|
||||
}
|
||||
t.handle(packet, fromID, fromAddr)
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleCallResponse dispatches a response packet to the call waiting for it.
|
||||
func (t *UDPv5) handleCallResponse(fromID enode.ID, fromAddr *net.UDPAddr, p v5wire.Packet) bool {
|
||||
ac := t.activeCallByNode[fromID]
|
||||
if ac == nil || !bytes.Equal(p.RequestID(), ac.reqid) {
|
||||
t.log.Debug(fmt.Sprintf("Unsolicited/late %s response", p.Name()), "id", fromID, "addr", fromAddr)
|
||||
return false
|
||||
}
|
||||
if !fromAddr.IP.Equal(ac.node.IP()) || fromAddr.Port != ac.node.UDP() {
|
||||
t.log.Debug(fmt.Sprintf("%s from wrong endpoint", p.Name()), "id", fromID, "addr", fromAddr)
|
||||
return false
|
||||
}
|
||||
if p.Kind() != ac.responseType {
|
||||
t.log.Debug(fmt.Sprintf("Wrong discv5 response type %s", p.Name()), "id", fromID, "addr", fromAddr)
|
||||
return false
|
||||
}
|
||||
t.startResponseTimeout(ac)
|
||||
ac.ch <- p
|
||||
return true
|
||||
}
|
||||
|
||||
// getNode looks for a node record in table and database.
|
||||
func (t *UDPv5) getNode(id enode.ID) *enode.Node {
|
||||
if n := t.tab.getNode(id); n != nil {
|
||||
return n
|
||||
}
|
||||
if n := t.localNode.Database().Node(id); n != nil {
|
||||
return n
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// handle processes incoming packets according to their message type.
|
||||
func (t *UDPv5) handle(p v5wire.Packet, fromID enode.ID, fromAddr *net.UDPAddr) {
|
||||
switch p := p.(type) {
|
||||
case *v5wire.Unknown:
|
||||
t.handleUnknown(p, fromID, fromAddr)
|
||||
case *v5wire.Whoareyou:
|
||||
t.handleWhoareyou(p, fromID, fromAddr)
|
||||
case *v5wire.Ping:
|
||||
t.handlePing(p, fromID, fromAddr)
|
||||
case *v5wire.Pong:
|
||||
if t.handleCallResponse(fromID, fromAddr, p) {
|
||||
t.localNode.UDPEndpointStatement(fromAddr, &net.UDPAddr{IP: p.ToIP, Port: int(p.ToPort)})
|
||||
}
|
||||
case *v5wire.Findnode:
|
||||
t.handleFindnode(p, fromID, fromAddr)
|
||||
case *v5wire.Nodes:
|
||||
t.handleCallResponse(fromID, fromAddr, p)
|
||||
case *v5wire.TalkRequest:
|
||||
t.handleTalkRequest(p, fromID, fromAddr)
|
||||
case *v5wire.TalkResponse:
|
||||
t.handleCallResponse(fromID, fromAddr, p)
|
||||
}
|
||||
}
|
||||
|
||||
// handleUnknown initiates a handshake by responding with WHOAREYOU.
|
||||
func (t *UDPv5) handleUnknown(p *v5wire.Unknown, fromID enode.ID, fromAddr *net.UDPAddr) {
|
||||
challenge := &v5wire.Whoareyou{Nonce: p.Nonce}
|
||||
crand.Read(challenge.IDNonce[:])
|
||||
if n := t.getNode(fromID); n != nil {
|
||||
challenge.Node = n
|
||||
challenge.RecordSeq = n.Seq()
|
||||
}
|
||||
t.sendResponse(fromID, fromAddr, challenge)
|
||||
}
|
||||
|
||||
var (
|
||||
errChallengeNoCall = errors.New("no matching call")
|
||||
errChallengeTwice = errors.New("second handshake")
|
||||
)
|
||||
|
||||
// handleWhoareyou resends the active call as a handshake packet.
|
||||
func (t *UDPv5) handleWhoareyou(p *v5wire.Whoareyou, fromID enode.ID, fromAddr *net.UDPAddr) {
|
||||
c, err := t.matchWithCall(fromID, p.Nonce)
|
||||
if err != nil {
|
||||
t.log.Debug("Invalid "+p.Name(), "addr", fromAddr, "err", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Resend the call that was answered by WHOAREYOU.
|
||||
t.log.Trace("<< "+p.Name(), "id", c.node.ID(), "addr", fromAddr)
|
||||
c.handshakeCount++
|
||||
c.challenge = p
|
||||
p.Node = c.node
|
||||
t.sendCall(c)
|
||||
}
|
||||
|
||||
// matchWithCall checks whether a handshake attempt matches the active call.
|
||||
func (t *UDPv5) matchWithCall(fromID enode.ID, nonce v5wire.Nonce) (*callV5, error) {
|
||||
c := t.activeCallByAuth[nonce]
|
||||
if c == nil {
|
||||
return nil, errChallengeNoCall
|
||||
}
|
||||
if c.handshakeCount > 0 {
|
||||
return nil, errChallengeTwice
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// handlePing sends a PONG response.
|
||||
func (t *UDPv5) handlePing(p *v5wire.Ping, fromID enode.ID, fromAddr *net.UDPAddr) {
|
||||
remoteIP := fromAddr.IP
|
||||
// Handle IPv4 mapped IPv6 addresses in the
|
||||
// event the local node is binded to an
|
||||
// ipv6 interface.
|
||||
if remoteIP.To4() != nil {
|
||||
remoteIP = remoteIP.To4()
|
||||
}
|
||||
t.sendResponse(fromID, fromAddr, &v5wire.Pong{
|
||||
ReqID: p.ReqID,
|
||||
ToIP: remoteIP,
|
||||
ToPort: uint16(fromAddr.Port),
|
||||
ENRSeq: t.localNode.Node().Seq(),
|
||||
})
|
||||
}
|
||||
|
||||
// handleFindnode returns nodes to the requester.
|
||||
func (t *UDPv5) handleFindnode(p *v5wire.Findnode, fromID enode.ID, fromAddr *net.UDPAddr) {
|
||||
nodes := t.collectTableNodes(fromAddr.IP, p.Distances, findnodeResultLimit)
|
||||
for _, resp := range packNodes(p.ReqID, nodes) {
|
||||
t.sendResponse(fromID, fromAddr, resp)
|
||||
}
|
||||
}
|
||||
|
||||
// collectTableNodes creates a FINDNODE result set for the given distances.
|
||||
func (t *UDPv5) collectTableNodes(rip net.IP, distances []uint, limit int) []*enode.Node {
|
||||
var nodes []*enode.Node
|
||||
var processed = make(map[uint]struct{})
|
||||
for _, dist := range distances {
|
||||
// Reject duplicate / invalid distances.
|
||||
_, seen := processed[dist]
|
||||
if seen || dist > 256 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Get the nodes.
|
||||
var bn []*enode.Node
|
||||
if dist == 0 {
|
||||
bn = []*enode.Node{t.Self()}
|
||||
} else if dist <= 256 {
|
||||
t.tab.mutex.Lock()
|
||||
bn = unwrapNodes(t.tab.bucketAtDistance(int(dist)).entries)
|
||||
t.tab.mutex.Unlock()
|
||||
}
|
||||
processed[dist] = struct{}{}
|
||||
|
||||
// Apply some pre-checks to avoid sending invalid nodes.
|
||||
for _, n := range bn {
|
||||
// TODO livenessChecks > 1
|
||||
if netutil.CheckRelayIP(rip, n.IP()) != nil {
|
||||
continue
|
||||
}
|
||||
nodes = append(nodes, n)
|
||||
if len(nodes) >= limit {
|
||||
return nodes
|
||||
}
|
||||
}
|
||||
}
|
||||
return nodes
|
||||
}
|
||||
|
||||
// packNodes creates NODES response packets for the given node list.
|
||||
func packNodes(reqid []byte, nodes []*enode.Node) []*v5wire.Nodes {
|
||||
if len(nodes) == 0 {
|
||||
return []*v5wire.Nodes{{ReqID: reqid, Total: 1}}
|
||||
}
|
||||
|
||||
total := uint8(math.Ceil(float64(len(nodes)) / 3))
|
||||
var resp []*v5wire.Nodes
|
||||
for len(nodes) > 0 {
|
||||
p := &v5wire.Nodes{ReqID: reqid, Total: total}
|
||||
items := min(nodesResponseItemLimit, len(nodes))
|
||||
for i := 0; i < items; i++ {
|
||||
p.Nodes = append(p.Nodes, nodes[i].Record())
|
||||
}
|
||||
nodes = nodes[items:]
|
||||
resp = append(resp, p)
|
||||
}
|
||||
return resp
|
||||
}
|
||||
|
||||
// handleTalkRequest runs the talk request handler of the requested protocol.
|
||||
func (t *UDPv5) handleTalkRequest(p *v5wire.TalkRequest, fromID enode.ID, fromAddr *net.UDPAddr) {
|
||||
t.trlock.Lock()
|
||||
handler := t.trhandlers[p.Protocol]
|
||||
t.trlock.Unlock()
|
||||
|
||||
var response []byte
|
||||
if handler != nil {
|
||||
response = handler(fromID, fromAddr, p.Message)
|
||||
}
|
||||
resp := &v5wire.TalkResponse{ReqID: p.ReqID, Message: response}
|
||||
t.sendResponse(fromID, fromAddr, resp)
|
||||
}
|
||||
180
vendor/github.com/ethereum/go-ethereum/p2p/discover/v5wire/crypto.go
generated
vendored
Normal file
180
vendor/github.com/ethereum/go-ethereum/p2p/discover/v5wire/crypto.go
generated
vendored
Normal file
@@ -0,0 +1,180 @@
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package v5wire
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"golang.org/x/crypto/hkdf"
|
||||
)
|
||||
|
||||
const (
|
||||
// Encryption/authentication parameters.
|
||||
aesKeySize = 16
|
||||
gcmNonceSize = 12
|
||||
)
|
||||
|
||||
// Nonce represents a nonce used for AES/GCM.
|
||||
type Nonce [gcmNonceSize]byte
|
||||
|
||||
// EncodePubkey encodes a public key.
|
||||
func EncodePubkey(key *ecdsa.PublicKey) []byte {
|
||||
switch key.Curve {
|
||||
case crypto.S256():
|
||||
return crypto.CompressPubkey(key)
|
||||
default:
|
||||
panic("unsupported curve " + key.Curve.Params().Name + " in EncodePubkey")
|
||||
}
|
||||
}
|
||||
|
||||
// DecodePubkey decodes a public key in compressed format.
|
||||
func DecodePubkey(curve elliptic.Curve, e []byte) (*ecdsa.PublicKey, error) {
|
||||
switch curve {
|
||||
case crypto.S256():
|
||||
if len(e) != 33 {
|
||||
return nil, errors.New("wrong size public key data")
|
||||
}
|
||||
return crypto.DecompressPubkey(e)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported curve %s in DecodePubkey", curve.Params().Name)
|
||||
}
|
||||
}
|
||||
|
||||
// idNonceHash computes the ID signature hash used in the handshake.
|
||||
func idNonceHash(h hash.Hash, challenge, ephkey []byte, destID enode.ID) []byte {
|
||||
h.Reset()
|
||||
h.Write([]byte("discovery v5 identity proof"))
|
||||
h.Write(challenge)
|
||||
h.Write(ephkey)
|
||||
h.Write(destID[:])
|
||||
return h.Sum(nil)
|
||||
}
|
||||
|
||||
// makeIDSignature creates the ID nonce signature.
|
||||
func makeIDSignature(hash hash.Hash, key *ecdsa.PrivateKey, challenge, ephkey []byte, destID enode.ID) ([]byte, error) {
|
||||
input := idNonceHash(hash, challenge, ephkey, destID)
|
||||
switch key.Curve {
|
||||
case crypto.S256():
|
||||
idsig, err := crypto.Sign(input, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return idsig[:len(idsig)-1], nil // remove recovery ID
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported curve %s", key.Curve.Params().Name)
|
||||
}
|
||||
}
|
||||
|
||||
// s256raw is an unparsed secp256k1 public key ENR entry.
|
||||
type s256raw []byte
|
||||
|
||||
func (s256raw) ENRKey() string { return "secp256k1" }
|
||||
|
||||
// verifyIDSignature checks that signature over idnonce was made by the given node.
|
||||
func verifyIDSignature(hash hash.Hash, sig []byte, n *enode.Node, challenge, ephkey []byte, destID enode.ID) error {
|
||||
switch idscheme := n.Record().IdentityScheme(); idscheme {
|
||||
case "v4":
|
||||
var pubkey s256raw
|
||||
if n.Load(&pubkey) != nil {
|
||||
return errors.New("no secp256k1 public key in record")
|
||||
}
|
||||
input := idNonceHash(hash, challenge, ephkey, destID)
|
||||
if !crypto.VerifySignature(pubkey, input, sig) {
|
||||
return errInvalidNonceSig
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("can't verify ID nonce signature against scheme %q", idscheme)
|
||||
}
|
||||
}
|
||||
|
||||
type hashFn func() hash.Hash
|
||||
|
||||
// deriveKeys creates the session keys.
|
||||
func deriveKeys(hash hashFn, priv *ecdsa.PrivateKey, pub *ecdsa.PublicKey, n1, n2 enode.ID, challenge []byte) *session {
|
||||
const text = "discovery v5 key agreement"
|
||||
var info = make([]byte, 0, len(text)+len(n1)+len(n2))
|
||||
info = append(info, text...)
|
||||
info = append(info, n1[:]...)
|
||||
info = append(info, n2[:]...)
|
||||
|
||||
eph := ecdh(priv, pub)
|
||||
if eph == nil {
|
||||
return nil
|
||||
}
|
||||
kdf := hkdf.New(hash, eph, challenge, info)
|
||||
sec := session{writeKey: make([]byte, aesKeySize), readKey: make([]byte, aesKeySize)}
|
||||
kdf.Read(sec.writeKey)
|
||||
kdf.Read(sec.readKey)
|
||||
for i := range eph {
|
||||
eph[i] = 0
|
||||
}
|
||||
return &sec
|
||||
}
|
||||
|
||||
// ecdh creates a shared secret.
|
||||
func ecdh(privkey *ecdsa.PrivateKey, pubkey *ecdsa.PublicKey) []byte {
|
||||
secX, secY := pubkey.ScalarMult(pubkey.X, pubkey.Y, privkey.D.Bytes())
|
||||
if secX == nil {
|
||||
return nil
|
||||
}
|
||||
sec := make([]byte, 33)
|
||||
sec[0] = 0x02 | byte(secY.Bit(0))
|
||||
math.ReadBits(secX, sec[1:])
|
||||
return sec
|
||||
}
|
||||
|
||||
// encryptGCM encrypts pt using AES-GCM with the given key and nonce. The ciphertext is
|
||||
// appended to dest, which must not overlap with plaintext. The resulting ciphertext is 16
|
||||
// bytes longer than plaintext because it contains an authentication tag.
|
||||
func encryptGCM(dest, key, nonce, plaintext, authData []byte) ([]byte, error) {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("can't create block cipher: %v", err))
|
||||
}
|
||||
aesgcm, err := cipher.NewGCMWithNonceSize(block, gcmNonceSize)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("can't create GCM: %v", err))
|
||||
}
|
||||
return aesgcm.Seal(dest, nonce, plaintext, authData), nil
|
||||
}
|
||||
|
||||
// decryptGCM decrypts ct using AES-GCM with the given key and nonce.
|
||||
func decryptGCM(key, nonce, ct, authData []byte) ([]byte, error) {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't create block cipher: %v", err)
|
||||
}
|
||||
if len(nonce) != gcmNonceSize {
|
||||
return nil, fmt.Errorf("invalid GCM nonce size: %d", len(nonce))
|
||||
}
|
||||
aesgcm, err := cipher.NewGCMWithNonceSize(block, gcmNonceSize)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't create GCM: %v", err)
|
||||
}
|
||||
pt := make([]byte, 0, len(ct))
|
||||
return aesgcm.Open(pt, nonce, ct, authData)
|
||||
}
|
||||
653
vendor/github.com/ethereum/go-ethereum/p2p/discover/v5wire/encoding.go
generated
vendored
Normal file
653
vendor/github.com/ethereum/go-ethereum/p2p/discover/v5wire/encoding.go
generated
vendored
Normal file
@@ -0,0 +1,653 @@
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package v5wire
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/ecdsa"
|
||||
crand "crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/mclock"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethereum/go-ethereum/p2p/enr"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
// TODO concurrent WHOAREYOU tie-breaker
|
||||
// TODO rehandshake after X packets
|
||||
|
||||
// Header represents a packet header.
|
||||
type Header struct {
|
||||
IV [sizeofMaskingIV]byte
|
||||
StaticHeader
|
||||
AuthData []byte
|
||||
|
||||
src enode.ID // used by decoder
|
||||
}
|
||||
|
||||
// StaticHeader contains the static fields of a packet header.
|
||||
type StaticHeader struct {
|
||||
ProtocolID [6]byte
|
||||
Version uint16
|
||||
Flag byte
|
||||
Nonce Nonce
|
||||
AuthSize uint16
|
||||
}
|
||||
|
||||
// Authdata layouts.
|
||||
type (
|
||||
whoareyouAuthData struct {
|
||||
IDNonce [16]byte // ID proof data
|
||||
RecordSeq uint64 // highest known ENR sequence of requester
|
||||
}
|
||||
|
||||
handshakeAuthData struct {
|
||||
h struct {
|
||||
SrcID enode.ID
|
||||
SigSize byte // ignature data
|
||||
PubkeySize byte // offset of
|
||||
}
|
||||
// Trailing variable-size data.
|
||||
signature, pubkey, record []byte
|
||||
}
|
||||
|
||||
messageAuthData struct {
|
||||
SrcID enode.ID
|
||||
}
|
||||
)
|
||||
|
||||
// Packet header flag values.
|
||||
const (
|
||||
flagMessage = iota
|
||||
flagWhoareyou
|
||||
flagHandshake
|
||||
)
|
||||
|
||||
// Protocol constants.
|
||||
const (
|
||||
version = 1
|
||||
minVersion = 1
|
||||
sizeofMaskingIV = 16
|
||||
|
||||
// The minimum size of any Discovery v5 packet is 63 bytes.
|
||||
// Should reject packets smaller than minPacketSize.
|
||||
minPacketSize = 63
|
||||
|
||||
minMessageSize = 48 // this refers to data after static headers
|
||||
randomPacketMsgSize = 20
|
||||
)
|
||||
|
||||
var protocolID = [6]byte{'d', 'i', 's', 'c', 'v', '5'}
|
||||
|
||||
// Errors.
|
||||
var (
|
||||
errTooShort = errors.New("packet too short")
|
||||
errInvalidHeader = errors.New("invalid packet header")
|
||||
errInvalidFlag = errors.New("invalid flag value in header")
|
||||
errMinVersion = errors.New("version of packet header below minimum")
|
||||
errMsgTooShort = errors.New("message/handshake packet below minimum size")
|
||||
errAuthSize = errors.New("declared auth size is beyond packet length")
|
||||
errUnexpectedHandshake = errors.New("unexpected auth response, not in handshake")
|
||||
errInvalidAuthKey = errors.New("invalid ephemeral pubkey")
|
||||
errNoRecord = errors.New("expected ENR in handshake but none sent")
|
||||
errInvalidNonceSig = errors.New("invalid ID nonce signature")
|
||||
errMessageTooShort = errors.New("message contains no data")
|
||||
errMessageDecrypt = errors.New("cannot decrypt message")
|
||||
)
|
||||
|
||||
// Public errors.
|
||||
var (
|
||||
// ErrInvalidReqID represents error when the ID is invalid.
|
||||
ErrInvalidReqID = errors.New("request ID larger than 8 bytes")
|
||||
)
|
||||
|
||||
// Packet sizes.
|
||||
var (
|
||||
sizeofStaticHeader = binary.Size(StaticHeader{})
|
||||
sizeofWhoareyouAuthData = binary.Size(whoareyouAuthData{})
|
||||
sizeofHandshakeAuthData = binary.Size(handshakeAuthData{}.h)
|
||||
sizeofMessageAuthData = binary.Size(messageAuthData{})
|
||||
sizeofStaticPacketData = sizeofMaskingIV + sizeofStaticHeader
|
||||
)
|
||||
|
||||
// Codec encodes and decodes Discovery v5 packets.
|
||||
// This type is not safe for concurrent use.
|
||||
type Codec struct {
|
||||
sha256 hash.Hash
|
||||
localnode *enode.LocalNode
|
||||
privkey *ecdsa.PrivateKey
|
||||
sc *SessionCache
|
||||
|
||||
// encoder buffers
|
||||
buf bytes.Buffer // whole packet
|
||||
headbuf bytes.Buffer // packet header
|
||||
msgbuf bytes.Buffer // message RLP plaintext
|
||||
msgctbuf []byte // message data ciphertext
|
||||
|
||||
// decoder buffer
|
||||
reader bytes.Reader
|
||||
}
|
||||
|
||||
// NewCodec creates a wire codec.
|
||||
func NewCodec(ln *enode.LocalNode, key *ecdsa.PrivateKey, clock mclock.Clock) *Codec {
|
||||
c := &Codec{
|
||||
sha256: sha256.New(),
|
||||
localnode: ln,
|
||||
privkey: key,
|
||||
sc: NewSessionCache(1024, clock),
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// Encode encodes a packet to a node. 'id' and 'addr' specify the destination node. The
|
||||
// 'challenge' parameter should be the most recently received WHOAREYOU packet from that
|
||||
// node.
|
||||
func (c *Codec) Encode(id enode.ID, addr string, packet Packet, challenge *Whoareyou) ([]byte, Nonce, error) {
|
||||
// Create the packet header.
|
||||
var (
|
||||
head Header
|
||||
session *session
|
||||
msgData []byte
|
||||
err error
|
||||
)
|
||||
switch {
|
||||
case packet.Kind() == WhoareyouPacket:
|
||||
head, err = c.encodeWhoareyou(id, packet.(*Whoareyou))
|
||||
case challenge != nil:
|
||||
// We have an unanswered challenge, send handshake.
|
||||
head, session, err = c.encodeHandshakeHeader(id, addr, challenge)
|
||||
default:
|
||||
session = c.sc.session(id, addr)
|
||||
if session != nil {
|
||||
// There is a session, use it.
|
||||
head, err = c.encodeMessageHeader(id, session)
|
||||
} else {
|
||||
// No keys, send random data to kick off the handshake.
|
||||
head, msgData, err = c.encodeRandom(id)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, Nonce{}, err
|
||||
}
|
||||
|
||||
// Generate masking IV.
|
||||
if err := c.sc.maskingIVGen(head.IV[:]); err != nil {
|
||||
return nil, Nonce{}, fmt.Errorf("can't generate masking IV: %v", err)
|
||||
}
|
||||
|
||||
// Encode header data.
|
||||
c.writeHeaders(&head)
|
||||
|
||||
// Store sent WHOAREYOU challenges.
|
||||
if challenge, ok := packet.(*Whoareyou); ok {
|
||||
challenge.ChallengeData = bytesCopy(&c.buf)
|
||||
c.sc.storeSentHandshake(id, addr, challenge)
|
||||
} else if msgData == nil {
|
||||
headerData := c.buf.Bytes()
|
||||
msgData, err = c.encryptMessage(session, packet, &head, headerData)
|
||||
if err != nil {
|
||||
return nil, Nonce{}, err
|
||||
}
|
||||
}
|
||||
|
||||
enc, err := c.EncodeRaw(id, head, msgData)
|
||||
return enc, head.Nonce, err
|
||||
}
|
||||
|
||||
// EncodeRaw encodes a packet with the given header.
|
||||
func (c *Codec) EncodeRaw(id enode.ID, head Header, msgdata []byte) ([]byte, error) {
|
||||
c.writeHeaders(&head)
|
||||
|
||||
// Apply masking.
|
||||
masked := c.buf.Bytes()[sizeofMaskingIV:]
|
||||
mask := head.mask(id)
|
||||
mask.XORKeyStream(masked[:], masked[:])
|
||||
|
||||
// Write message data.
|
||||
c.buf.Write(msgdata)
|
||||
return c.buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (c *Codec) writeHeaders(head *Header) {
|
||||
c.buf.Reset()
|
||||
c.buf.Write(head.IV[:])
|
||||
binary.Write(&c.buf, binary.BigEndian, &head.StaticHeader)
|
||||
c.buf.Write(head.AuthData)
|
||||
}
|
||||
|
||||
// makeHeader creates a packet header.
|
||||
func (c *Codec) makeHeader(toID enode.ID, flag byte, authsizeExtra int) Header {
|
||||
var authsize int
|
||||
switch flag {
|
||||
case flagMessage:
|
||||
authsize = sizeofMessageAuthData
|
||||
case flagWhoareyou:
|
||||
authsize = sizeofWhoareyouAuthData
|
||||
case flagHandshake:
|
||||
authsize = sizeofHandshakeAuthData
|
||||
default:
|
||||
panic(fmt.Errorf("BUG: invalid packet header flag %x", flag))
|
||||
}
|
||||
authsize += authsizeExtra
|
||||
if authsize > int(^uint16(0)) {
|
||||
panic(fmt.Errorf("BUG: auth size %d overflows uint16", authsize))
|
||||
}
|
||||
return Header{
|
||||
StaticHeader: StaticHeader{
|
||||
ProtocolID: protocolID,
|
||||
Version: version,
|
||||
Flag: flag,
|
||||
AuthSize: uint16(authsize),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// encodeRandom encodes a packet with random content.
|
||||
func (c *Codec) encodeRandom(toID enode.ID) (Header, []byte, error) {
|
||||
head := c.makeHeader(toID, flagMessage, 0)
|
||||
|
||||
// Encode auth data.
|
||||
auth := messageAuthData{SrcID: c.localnode.ID()}
|
||||
if _, err := crand.Read(head.Nonce[:]); err != nil {
|
||||
return head, nil, fmt.Errorf("can't get random data: %v", err)
|
||||
}
|
||||
c.headbuf.Reset()
|
||||
binary.Write(&c.headbuf, binary.BigEndian, auth)
|
||||
head.AuthData = c.headbuf.Bytes()
|
||||
|
||||
// Fill message ciphertext buffer with random bytes.
|
||||
c.msgctbuf = append(c.msgctbuf[:0], make([]byte, randomPacketMsgSize)...)
|
||||
crand.Read(c.msgctbuf)
|
||||
return head, c.msgctbuf, nil
|
||||
}
|
||||
|
||||
// encodeWhoareyou encodes a WHOAREYOU packet.
|
||||
func (c *Codec) encodeWhoareyou(toID enode.ID, packet *Whoareyou) (Header, error) {
|
||||
// Sanity check node field to catch misbehaving callers.
|
||||
if packet.RecordSeq > 0 && packet.Node == nil {
|
||||
panic("BUG: missing node in whoareyou with non-zero seq")
|
||||
}
|
||||
|
||||
// Create header.
|
||||
head := c.makeHeader(toID, flagWhoareyou, 0)
|
||||
head.AuthData = bytesCopy(&c.buf)
|
||||
head.Nonce = packet.Nonce
|
||||
|
||||
// Encode auth data.
|
||||
auth := &whoareyouAuthData{
|
||||
IDNonce: packet.IDNonce,
|
||||
RecordSeq: packet.RecordSeq,
|
||||
}
|
||||
c.headbuf.Reset()
|
||||
binary.Write(&c.headbuf, binary.BigEndian, auth)
|
||||
head.AuthData = c.headbuf.Bytes()
|
||||
return head, nil
|
||||
}
|
||||
|
||||
// encodeHandshakeHeader encodes the handshake message packet header.
|
||||
func (c *Codec) encodeHandshakeHeader(toID enode.ID, addr string, challenge *Whoareyou) (Header, *session, error) {
|
||||
// Ensure calling code sets challenge.node.
|
||||
if challenge.Node == nil {
|
||||
panic("BUG: missing challenge.Node in encode")
|
||||
}
|
||||
|
||||
// Generate new secrets.
|
||||
auth, session, err := c.makeHandshakeAuth(toID, addr, challenge)
|
||||
if err != nil {
|
||||
return Header{}, nil, err
|
||||
}
|
||||
|
||||
// Generate nonce for message.
|
||||
nonce, err := c.sc.nextNonce(session)
|
||||
if err != nil {
|
||||
return Header{}, nil, fmt.Errorf("can't generate nonce: %v", err)
|
||||
}
|
||||
|
||||
// TODO: this should happen when the first authenticated message is received
|
||||
c.sc.storeNewSession(toID, addr, session)
|
||||
|
||||
// Encode the auth header.
|
||||
var (
|
||||
authsizeExtra = len(auth.pubkey) + len(auth.signature) + len(auth.record)
|
||||
head = c.makeHeader(toID, flagHandshake, authsizeExtra)
|
||||
)
|
||||
c.headbuf.Reset()
|
||||
binary.Write(&c.headbuf, binary.BigEndian, &auth.h)
|
||||
c.headbuf.Write(auth.signature)
|
||||
c.headbuf.Write(auth.pubkey)
|
||||
c.headbuf.Write(auth.record)
|
||||
head.AuthData = c.headbuf.Bytes()
|
||||
head.Nonce = nonce
|
||||
return head, session, err
|
||||
}
|
||||
|
||||
// makeHandshakeAuth creates the auth header on a request packet following WHOAREYOU.
|
||||
func (c *Codec) makeHandshakeAuth(toID enode.ID, addr string, challenge *Whoareyou) (*handshakeAuthData, *session, error) {
|
||||
auth := new(handshakeAuthData)
|
||||
auth.h.SrcID = c.localnode.ID()
|
||||
|
||||
// Create the ephemeral key. This needs to be first because the
|
||||
// key is part of the ID nonce signature.
|
||||
var remotePubkey = new(ecdsa.PublicKey)
|
||||
if err := challenge.Node.Load((*enode.Secp256k1)(remotePubkey)); err != nil {
|
||||
return nil, nil, fmt.Errorf("can't find secp256k1 key for recipient")
|
||||
}
|
||||
ephkey, err := c.sc.ephemeralKeyGen()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("can't generate ephemeral key")
|
||||
}
|
||||
ephpubkey := EncodePubkey(&ephkey.PublicKey)
|
||||
auth.pubkey = ephpubkey[:]
|
||||
auth.h.PubkeySize = byte(len(auth.pubkey))
|
||||
|
||||
// Add ID nonce signature to response.
|
||||
cdata := challenge.ChallengeData
|
||||
idsig, err := makeIDSignature(c.sha256, c.privkey, cdata, ephpubkey[:], toID)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("can't sign: %v", err)
|
||||
}
|
||||
auth.signature = idsig
|
||||
auth.h.SigSize = byte(len(auth.signature))
|
||||
|
||||
// Add our record to response if it's newer than what remote side has.
|
||||
ln := c.localnode.Node()
|
||||
if challenge.RecordSeq < ln.Seq() {
|
||||
auth.record, _ = rlp.EncodeToBytes(ln.Record())
|
||||
}
|
||||
|
||||
// Create session keys.
|
||||
sec := deriveKeys(sha256.New, ephkey, remotePubkey, c.localnode.ID(), challenge.Node.ID(), cdata)
|
||||
if sec == nil {
|
||||
return nil, nil, fmt.Errorf("key derivation failed")
|
||||
}
|
||||
return auth, sec, err
|
||||
}
|
||||
|
||||
// encodeMessageHeader encodes an encrypted message packet.
|
||||
func (c *Codec) encodeMessageHeader(toID enode.ID, s *session) (Header, error) {
|
||||
head := c.makeHeader(toID, flagMessage, 0)
|
||||
|
||||
// Create the header.
|
||||
nonce, err := c.sc.nextNonce(s)
|
||||
if err != nil {
|
||||
return Header{}, fmt.Errorf("can't generate nonce: %v", err)
|
||||
}
|
||||
auth := messageAuthData{SrcID: c.localnode.ID()}
|
||||
c.buf.Reset()
|
||||
binary.Write(&c.buf, binary.BigEndian, &auth)
|
||||
head.AuthData = bytesCopy(&c.buf)
|
||||
head.Nonce = nonce
|
||||
return head, err
|
||||
}
|
||||
|
||||
func (c *Codec) encryptMessage(s *session, p Packet, head *Header, headerData []byte) ([]byte, error) {
|
||||
// Encode message plaintext.
|
||||
c.msgbuf.Reset()
|
||||
c.msgbuf.WriteByte(p.Kind())
|
||||
if err := rlp.Encode(&c.msgbuf, p); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
messagePT := c.msgbuf.Bytes()
|
||||
|
||||
// Encrypt into message ciphertext buffer.
|
||||
messageCT, err := encryptGCM(c.msgctbuf[:0], s.writeKey, head.Nonce[:], messagePT, headerData)
|
||||
if err == nil {
|
||||
c.msgctbuf = messageCT
|
||||
}
|
||||
return messageCT, err
|
||||
}
|
||||
|
||||
// Decode decodes a discovery packet.
|
||||
func (c *Codec) Decode(input []byte, addr string) (src enode.ID, n *enode.Node, p Packet, err error) {
|
||||
if len(input) < minPacketSize {
|
||||
return enode.ID{}, nil, nil, errTooShort
|
||||
}
|
||||
// Unmask the static header.
|
||||
var head Header
|
||||
copy(head.IV[:], input[:sizeofMaskingIV])
|
||||
mask := head.mask(c.localnode.ID())
|
||||
staticHeader := input[sizeofMaskingIV:sizeofStaticPacketData]
|
||||
mask.XORKeyStream(staticHeader, staticHeader)
|
||||
|
||||
// Decode and verify the static header.
|
||||
c.reader.Reset(staticHeader)
|
||||
binary.Read(&c.reader, binary.BigEndian, &head.StaticHeader)
|
||||
remainingInput := len(input) - sizeofStaticPacketData
|
||||
if err := head.checkValid(remainingInput); err != nil {
|
||||
return enode.ID{}, nil, nil, err
|
||||
}
|
||||
|
||||
// Unmask auth data.
|
||||
authDataEnd := sizeofStaticPacketData + int(head.AuthSize)
|
||||
authData := input[sizeofStaticPacketData:authDataEnd]
|
||||
mask.XORKeyStream(authData, authData)
|
||||
head.AuthData = authData
|
||||
|
||||
// Delete timed-out handshakes. This must happen before decoding to avoid
|
||||
// processing the same handshake twice.
|
||||
c.sc.handshakeGC()
|
||||
|
||||
// Decode auth part and message.
|
||||
headerData := input[:authDataEnd]
|
||||
msgData := input[authDataEnd:]
|
||||
switch head.Flag {
|
||||
case flagWhoareyou:
|
||||
p, err = c.decodeWhoareyou(&head, headerData)
|
||||
case flagHandshake:
|
||||
n, p, err = c.decodeHandshakeMessage(addr, &head, headerData, msgData)
|
||||
case flagMessage:
|
||||
p, err = c.decodeMessage(addr, &head, headerData, msgData)
|
||||
default:
|
||||
err = errInvalidFlag
|
||||
}
|
||||
return head.src, n, p, err
|
||||
}
|
||||
|
||||
// decodeWhoareyou reads packet data after the header as a WHOAREYOU packet.
|
||||
func (c *Codec) decodeWhoareyou(head *Header, headerData []byte) (Packet, error) {
|
||||
if len(head.AuthData) != sizeofWhoareyouAuthData {
|
||||
return nil, fmt.Errorf("invalid auth size %d for WHOAREYOU", len(head.AuthData))
|
||||
}
|
||||
var auth whoareyouAuthData
|
||||
c.reader.Reset(head.AuthData)
|
||||
binary.Read(&c.reader, binary.BigEndian, &auth)
|
||||
p := &Whoareyou{
|
||||
Nonce: head.Nonce,
|
||||
IDNonce: auth.IDNonce,
|
||||
RecordSeq: auth.RecordSeq,
|
||||
ChallengeData: make([]byte, len(headerData)),
|
||||
}
|
||||
copy(p.ChallengeData, headerData)
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (c *Codec) decodeHandshakeMessage(fromAddr string, head *Header, headerData, msgData []byte) (n *enode.Node, p Packet, err error) {
|
||||
node, auth, session, err := c.decodeHandshake(fromAddr, head)
|
||||
if err != nil {
|
||||
c.sc.deleteHandshake(auth.h.SrcID, fromAddr)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Decrypt the message using the new session keys.
|
||||
msg, err := c.decryptMessage(msgData, head.Nonce[:], headerData, session.readKey)
|
||||
if err != nil {
|
||||
c.sc.deleteHandshake(auth.h.SrcID, fromAddr)
|
||||
return node, msg, err
|
||||
}
|
||||
|
||||
// Handshake OK, drop the challenge and store the new session keys.
|
||||
c.sc.storeNewSession(auth.h.SrcID, fromAddr, session)
|
||||
c.sc.deleteHandshake(auth.h.SrcID, fromAddr)
|
||||
return node, msg, nil
|
||||
}
|
||||
|
||||
func (c *Codec) decodeHandshake(fromAddr string, head *Header) (n *enode.Node, auth handshakeAuthData, s *session, err error) {
|
||||
if auth, err = c.decodeHandshakeAuthData(head); err != nil {
|
||||
return nil, auth, nil, err
|
||||
}
|
||||
|
||||
// Verify against our last WHOAREYOU.
|
||||
challenge := c.sc.getHandshake(auth.h.SrcID, fromAddr)
|
||||
if challenge == nil {
|
||||
return nil, auth, nil, errUnexpectedHandshake
|
||||
}
|
||||
// Get node record.
|
||||
n, err = c.decodeHandshakeRecord(challenge.Node, auth.h.SrcID, auth.record)
|
||||
if err != nil {
|
||||
return nil, auth, nil, err
|
||||
}
|
||||
// Verify ID nonce signature.
|
||||
sig := auth.signature
|
||||
cdata := challenge.ChallengeData
|
||||
err = verifyIDSignature(c.sha256, sig, n, cdata, auth.pubkey, c.localnode.ID())
|
||||
if err != nil {
|
||||
return nil, auth, nil, err
|
||||
}
|
||||
// Verify ephemeral key is on curve.
|
||||
ephkey, err := DecodePubkey(c.privkey.Curve, auth.pubkey)
|
||||
if err != nil {
|
||||
return nil, auth, nil, errInvalidAuthKey
|
||||
}
|
||||
// Derive sesssion keys.
|
||||
session := deriveKeys(sha256.New, c.privkey, ephkey, auth.h.SrcID, c.localnode.ID(), cdata)
|
||||
session = session.keysFlipped()
|
||||
return n, auth, session, nil
|
||||
}
|
||||
|
||||
// decodeHandshakeAuthData reads the authdata section of a handshake packet.
|
||||
func (c *Codec) decodeHandshakeAuthData(head *Header) (auth handshakeAuthData, err error) {
|
||||
// Decode fixed size part.
|
||||
if len(head.AuthData) < sizeofHandshakeAuthData {
|
||||
return auth, fmt.Errorf("header authsize %d too low for handshake", head.AuthSize)
|
||||
}
|
||||
c.reader.Reset(head.AuthData)
|
||||
binary.Read(&c.reader, binary.BigEndian, &auth.h)
|
||||
head.src = auth.h.SrcID
|
||||
|
||||
// Decode variable-size part.
|
||||
var (
|
||||
vardata = head.AuthData[sizeofHandshakeAuthData:]
|
||||
sigAndKeySize = int(auth.h.SigSize) + int(auth.h.PubkeySize)
|
||||
keyOffset = int(auth.h.SigSize)
|
||||
recOffset = keyOffset + int(auth.h.PubkeySize)
|
||||
)
|
||||
if len(vardata) < sigAndKeySize {
|
||||
return auth, errTooShort
|
||||
}
|
||||
auth.signature = vardata[:keyOffset]
|
||||
auth.pubkey = vardata[keyOffset:recOffset]
|
||||
auth.record = vardata[recOffset:]
|
||||
return auth, nil
|
||||
}
|
||||
|
||||
// decodeHandshakeRecord verifies the node record contained in a handshake packet. The
|
||||
// remote node should include the record if we don't have one or if ours is older than the
|
||||
// latest sequence number.
|
||||
func (c *Codec) decodeHandshakeRecord(local *enode.Node, wantID enode.ID, remote []byte) (*enode.Node, error) {
|
||||
node := local
|
||||
if len(remote) > 0 {
|
||||
var record enr.Record
|
||||
if err := rlp.DecodeBytes(remote, &record); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if local == nil || local.Seq() < record.Seq() {
|
||||
n, err := enode.New(enode.ValidSchemes, &record)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid node record: %v", err)
|
||||
}
|
||||
if n.ID() != wantID {
|
||||
return nil, fmt.Errorf("record in handshake has wrong ID: %v", n.ID())
|
||||
}
|
||||
node = n
|
||||
}
|
||||
}
|
||||
if node == nil {
|
||||
return nil, errNoRecord
|
||||
}
|
||||
return node, nil
|
||||
}
|
||||
|
||||
// decodeMessage reads packet data following the header as an ordinary message packet.
|
||||
func (c *Codec) decodeMessage(fromAddr string, head *Header, headerData, msgData []byte) (Packet, error) {
|
||||
if len(head.AuthData) != sizeofMessageAuthData {
|
||||
return nil, fmt.Errorf("invalid auth size %d for message packet", len(head.AuthData))
|
||||
}
|
||||
var auth messageAuthData
|
||||
c.reader.Reset(head.AuthData)
|
||||
binary.Read(&c.reader, binary.BigEndian, &auth)
|
||||
head.src = auth.SrcID
|
||||
|
||||
// Try decrypting the message.
|
||||
key := c.sc.readKey(auth.SrcID, fromAddr)
|
||||
msg, err := c.decryptMessage(msgData, head.Nonce[:], headerData, key)
|
||||
if errors.Is(err, errMessageDecrypt) {
|
||||
// It didn't work. Start the handshake since this is an ordinary message packet.
|
||||
return &Unknown{Nonce: head.Nonce}, nil
|
||||
}
|
||||
return msg, err
|
||||
}
|
||||
|
||||
func (c *Codec) decryptMessage(input, nonce, headerData, readKey []byte) (Packet, error) {
|
||||
msgdata, err := decryptGCM(readKey, nonce, input, headerData)
|
||||
if err != nil {
|
||||
return nil, errMessageDecrypt
|
||||
}
|
||||
if len(msgdata) == 0 {
|
||||
return nil, errMessageTooShort
|
||||
}
|
||||
return DecodeMessage(msgdata[0], msgdata[1:])
|
||||
}
|
||||
|
||||
// checkValid performs some basic validity checks on the header.
|
||||
// The packetLen here is the length remaining after the static header.
|
||||
func (h *StaticHeader) checkValid(packetLen int) error {
|
||||
if h.ProtocolID != protocolID {
|
||||
return errInvalidHeader
|
||||
}
|
||||
if h.Version < minVersion {
|
||||
return errMinVersion
|
||||
}
|
||||
if h.Flag != flagWhoareyou && packetLen < minMessageSize {
|
||||
return errMsgTooShort
|
||||
}
|
||||
if int(h.AuthSize) > packetLen {
|
||||
return errAuthSize
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// mask returns a cipher for 'masking' / 'unmasking' packet headers.
|
||||
func (h *Header) mask(destID enode.ID) cipher.Stream {
|
||||
block, err := aes.NewCipher(destID[:16])
|
||||
if err != nil {
|
||||
panic("can't create cipher")
|
||||
}
|
||||
return cipher.NewCTR(block, h.IV[:])
|
||||
}
|
||||
|
||||
func bytesCopy(r *bytes.Buffer) []byte {
|
||||
b := make([]byte, r.Len())
|
||||
copy(b, r.Bytes())
|
||||
return b
|
||||
}
|
||||
249
vendor/github.com/ethereum/go-ethereum/p2p/discover/v5wire/msg.go
generated
vendored
Normal file
249
vendor/github.com/ethereum/go-ethereum/p2p/discover/v5wire/msg.go
generated
vendored
Normal file
@@ -0,0 +1,249 @@
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package v5wire
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/mclock"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethereum/go-ethereum/p2p/enr"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
// Packet is implemented by all message types.
|
||||
type Packet interface {
|
||||
Name() string // Name returns a string corresponding to the message type.
|
||||
Kind() byte // Kind returns the message type.
|
||||
RequestID() []byte // Returns the request ID.
|
||||
SetRequestID([]byte) // Sets the request ID.
|
||||
}
|
||||
|
||||
// Message types.
|
||||
const (
|
||||
PingMsg byte = iota + 1
|
||||
PongMsg
|
||||
FindnodeMsg
|
||||
NodesMsg
|
||||
TalkRequestMsg
|
||||
TalkResponseMsg
|
||||
RequestTicketMsg
|
||||
TicketMsg
|
||||
RegtopicMsg
|
||||
RegconfirmationMsg
|
||||
TopicQueryMsg
|
||||
|
||||
UnknownPacket = byte(255) // any non-decryptable packet
|
||||
WhoareyouPacket = byte(254) // the WHOAREYOU packet
|
||||
)
|
||||
|
||||
// Protocol messages.
|
||||
type (
|
||||
// Unknown represents any packet that can't be decrypted.
|
||||
Unknown struct {
|
||||
Nonce Nonce
|
||||
}
|
||||
|
||||
// Whoareyou contains the handshake challenge.
|
||||
Whoareyou struct {
|
||||
ChallengeData []byte // Encoded challenge
|
||||
Nonce Nonce // Nonce of request packet
|
||||
IDNonce [16]byte // Identity proof data
|
||||
RecordSeq uint64 // ENR sequence number of recipient
|
||||
|
||||
// Node is the locally known node record of recipient.
|
||||
// This must be set by the caller of Encode.
|
||||
Node *enode.Node
|
||||
|
||||
sent mclock.AbsTime // for handshake GC.
|
||||
}
|
||||
|
||||
// Ping is sent during liveness checks.
|
||||
Ping struct {
|
||||
ReqID []byte
|
||||
ENRSeq uint64
|
||||
}
|
||||
|
||||
// Pong is the reply to Ping.
|
||||
Pong struct {
|
||||
ReqID []byte
|
||||
ENRSeq uint64
|
||||
ToIP net.IP // These fields should mirror the UDP envelope address of the ping
|
||||
ToPort uint16 // packet, which provides a way to discover the external address (after NAT).
|
||||
}
|
||||
|
||||
// Findnode is a query for nodes in the given bucket.
|
||||
Findnode struct {
|
||||
ReqID []byte
|
||||
Distances []uint
|
||||
}
|
||||
|
||||
// Nodes is the reply to Findnode and Topicquery.
|
||||
Nodes struct {
|
||||
ReqID []byte
|
||||
Total uint8
|
||||
Nodes []*enr.Record
|
||||
}
|
||||
|
||||
// TalkRequest is an application-level request.
|
||||
TalkRequest struct {
|
||||
ReqID []byte
|
||||
Protocol string
|
||||
Message []byte
|
||||
}
|
||||
|
||||
// TalkResponse is the reply to TalkRequest.
|
||||
TalkResponse struct {
|
||||
ReqID []byte
|
||||
Message []byte
|
||||
}
|
||||
|
||||
// RequestTicket requests a ticket for a topic queue.
|
||||
RequestTicket struct {
|
||||
ReqID []byte
|
||||
Topic []byte
|
||||
}
|
||||
|
||||
// Ticket is the response to RequestTicket.
|
||||
Ticket struct {
|
||||
ReqID []byte
|
||||
Ticket []byte
|
||||
}
|
||||
|
||||
// Regtopic registers the sender in a topic queue using a ticket.
|
||||
Regtopic struct {
|
||||
ReqID []byte
|
||||
Ticket []byte
|
||||
ENR *enr.Record
|
||||
}
|
||||
|
||||
// Regconfirmation is the reply to Regtopic.
|
||||
Regconfirmation struct {
|
||||
ReqID []byte
|
||||
Registered bool
|
||||
}
|
||||
|
||||
// TopicQuery asks for nodes with the given topic.
|
||||
TopicQuery struct {
|
||||
ReqID []byte
|
||||
Topic []byte
|
||||
}
|
||||
)
|
||||
|
||||
// DecodeMessage decodes the message body of a packet.
|
||||
func DecodeMessage(ptype byte, body []byte) (Packet, error) {
|
||||
var dec Packet
|
||||
switch ptype {
|
||||
case PingMsg:
|
||||
dec = new(Ping)
|
||||
case PongMsg:
|
||||
dec = new(Pong)
|
||||
case FindnodeMsg:
|
||||
dec = new(Findnode)
|
||||
case NodesMsg:
|
||||
dec = new(Nodes)
|
||||
case TalkRequestMsg:
|
||||
dec = new(TalkRequest)
|
||||
case TalkResponseMsg:
|
||||
dec = new(TalkResponse)
|
||||
case RequestTicketMsg:
|
||||
dec = new(RequestTicket)
|
||||
case TicketMsg:
|
||||
dec = new(Ticket)
|
||||
case RegtopicMsg:
|
||||
dec = new(Regtopic)
|
||||
case RegconfirmationMsg:
|
||||
dec = new(Regconfirmation)
|
||||
case TopicQueryMsg:
|
||||
dec = new(TopicQuery)
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown packet type %d", ptype)
|
||||
}
|
||||
if err := rlp.DecodeBytes(body, dec); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if dec.RequestID() != nil && len(dec.RequestID()) > 8 {
|
||||
return nil, ErrInvalidReqID
|
||||
}
|
||||
return dec, nil
|
||||
}
|
||||
|
||||
func (*Whoareyou) Name() string { return "WHOAREYOU/v5" }
|
||||
func (*Whoareyou) Kind() byte { return WhoareyouPacket }
|
||||
func (*Whoareyou) RequestID() []byte { return nil }
|
||||
func (*Whoareyou) SetRequestID([]byte) {}
|
||||
|
||||
func (*Unknown) Name() string { return "UNKNOWN/v5" }
|
||||
func (*Unknown) Kind() byte { return UnknownPacket }
|
||||
func (*Unknown) RequestID() []byte { return nil }
|
||||
func (*Unknown) SetRequestID([]byte) {}
|
||||
|
||||
func (*Ping) Name() string { return "PING/v5" }
|
||||
func (*Ping) Kind() byte { return PingMsg }
|
||||
func (p *Ping) RequestID() []byte { return p.ReqID }
|
||||
func (p *Ping) SetRequestID(id []byte) { p.ReqID = id }
|
||||
|
||||
func (*Pong) Name() string { return "PONG/v5" }
|
||||
func (*Pong) Kind() byte { return PongMsg }
|
||||
func (p *Pong) RequestID() []byte { return p.ReqID }
|
||||
func (p *Pong) SetRequestID(id []byte) { p.ReqID = id }
|
||||
|
||||
func (*Findnode) Name() string { return "FINDNODE/v5" }
|
||||
func (*Findnode) Kind() byte { return FindnodeMsg }
|
||||
func (p *Findnode) RequestID() []byte { return p.ReqID }
|
||||
func (p *Findnode) SetRequestID(id []byte) { p.ReqID = id }
|
||||
|
||||
func (*Nodes) Name() string { return "NODES/v5" }
|
||||
func (*Nodes) Kind() byte { return NodesMsg }
|
||||
func (p *Nodes) RequestID() []byte { return p.ReqID }
|
||||
func (p *Nodes) SetRequestID(id []byte) { p.ReqID = id }
|
||||
|
||||
func (*TalkRequest) Name() string { return "TALKREQ/v5" }
|
||||
func (*TalkRequest) Kind() byte { return TalkRequestMsg }
|
||||
func (p *TalkRequest) RequestID() []byte { return p.ReqID }
|
||||
func (p *TalkRequest) SetRequestID(id []byte) { p.ReqID = id }
|
||||
|
||||
func (*TalkResponse) Name() string { return "TALKRESP/v5" }
|
||||
func (*TalkResponse) Kind() byte { return TalkResponseMsg }
|
||||
func (p *TalkResponse) RequestID() []byte { return p.ReqID }
|
||||
func (p *TalkResponse) SetRequestID(id []byte) { p.ReqID = id }
|
||||
|
||||
func (*RequestTicket) Name() string { return "REQTICKET/v5" }
|
||||
func (*RequestTicket) Kind() byte { return RequestTicketMsg }
|
||||
func (p *RequestTicket) RequestID() []byte { return p.ReqID }
|
||||
func (p *RequestTicket) SetRequestID(id []byte) { p.ReqID = id }
|
||||
|
||||
func (*Regtopic) Name() string { return "REGTOPIC/v5" }
|
||||
func (*Regtopic) Kind() byte { return RegtopicMsg }
|
||||
func (p *Regtopic) RequestID() []byte { return p.ReqID }
|
||||
func (p *Regtopic) SetRequestID(id []byte) { p.ReqID = id }
|
||||
|
||||
func (*Ticket) Name() string { return "TICKET/v5" }
|
||||
func (*Ticket) Kind() byte { return TicketMsg }
|
||||
func (p *Ticket) RequestID() []byte { return p.ReqID }
|
||||
func (p *Ticket) SetRequestID(id []byte) { p.ReqID = id }
|
||||
|
||||
func (*Regconfirmation) Name() string { return "REGCONFIRMATION/v5" }
|
||||
func (*Regconfirmation) Kind() byte { return RegconfirmationMsg }
|
||||
func (p *Regconfirmation) RequestID() []byte { return p.ReqID }
|
||||
func (p *Regconfirmation) SetRequestID(id []byte) { p.ReqID = id }
|
||||
|
||||
func (*TopicQuery) Name() string { return "TOPICQUERY/v5" }
|
||||
func (*TopicQuery) Kind() byte { return TopicQueryMsg }
|
||||
func (p *TopicQuery) RequestID() []byte { return p.ReqID }
|
||||
func (p *TopicQuery) SetRequestID(id []byte) { p.ReqID = id }
|
||||
142
vendor/github.com/ethereum/go-ethereum/p2p/discover/v5wire/session.go
generated
vendored
Normal file
142
vendor/github.com/ethereum/go-ethereum/p2p/discover/v5wire/session.go
generated
vendored
Normal file
@@ -0,0 +1,142 @@
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package v5wire
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
crand "crypto/rand"
|
||||
"encoding/binary"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/mclock"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/hashicorp/golang-lru/simplelru"
|
||||
)
|
||||
|
||||
const handshakeTimeout = time.Second
|
||||
|
||||
// The SessionCache keeps negotiated encryption keys and
|
||||
// state for in-progress handshakes in the Discovery v5 wire protocol.
|
||||
type SessionCache struct {
|
||||
sessions *simplelru.LRU
|
||||
handshakes map[sessionID]*Whoareyou
|
||||
clock mclock.Clock
|
||||
|
||||
// hooks for overriding randomness.
|
||||
nonceGen func(uint32) (Nonce, error)
|
||||
maskingIVGen func([]byte) error
|
||||
ephemeralKeyGen func() (*ecdsa.PrivateKey, error)
|
||||
}
|
||||
|
||||
// sessionID identifies a session or handshake.
|
||||
type sessionID struct {
|
||||
id enode.ID
|
||||
addr string
|
||||
}
|
||||
|
||||
// session contains session information
|
||||
type session struct {
|
||||
writeKey []byte
|
||||
readKey []byte
|
||||
nonceCounter uint32
|
||||
}
|
||||
|
||||
// keysFlipped returns a copy of s with the read and write keys flipped.
|
||||
func (s *session) keysFlipped() *session {
|
||||
return &session{s.readKey, s.writeKey, s.nonceCounter}
|
||||
}
|
||||
|
||||
func NewSessionCache(maxItems int, clock mclock.Clock) *SessionCache {
|
||||
cache, err := simplelru.NewLRU(maxItems, nil)
|
||||
if err != nil {
|
||||
panic("can't create session cache")
|
||||
}
|
||||
return &SessionCache{
|
||||
sessions: cache,
|
||||
handshakes: make(map[sessionID]*Whoareyou),
|
||||
clock: clock,
|
||||
nonceGen: generateNonce,
|
||||
maskingIVGen: generateMaskingIV,
|
||||
ephemeralKeyGen: crypto.GenerateKey,
|
||||
}
|
||||
}
|
||||
|
||||
func generateNonce(counter uint32) (n Nonce, err error) {
|
||||
binary.BigEndian.PutUint32(n[:4], counter)
|
||||
_, err = crand.Read(n[4:])
|
||||
return n, err
|
||||
}
|
||||
|
||||
func generateMaskingIV(buf []byte) error {
|
||||
_, err := crand.Read(buf)
|
||||
return err
|
||||
}
|
||||
|
||||
// nextNonce creates a nonce for encrypting a message to the given session.
|
||||
func (sc *SessionCache) nextNonce(s *session) (Nonce, error) {
|
||||
s.nonceCounter++
|
||||
return sc.nonceGen(s.nonceCounter)
|
||||
}
|
||||
|
||||
// session returns the current session for the given node, if any.
|
||||
func (sc *SessionCache) session(id enode.ID, addr string) *session {
|
||||
item, ok := sc.sessions.Get(sessionID{id, addr})
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return item.(*session)
|
||||
}
|
||||
|
||||
// readKey returns the current read key for the given node.
|
||||
func (sc *SessionCache) readKey(id enode.ID, addr string) []byte {
|
||||
if s := sc.session(id, addr); s != nil {
|
||||
return s.readKey
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// storeNewSession stores new encryption keys in the cache.
|
||||
func (sc *SessionCache) storeNewSession(id enode.ID, addr string, s *session) {
|
||||
sc.sessions.Add(sessionID{id, addr}, s)
|
||||
}
|
||||
|
||||
// getHandshake gets the handshake challenge we previously sent to the given remote node.
|
||||
func (sc *SessionCache) getHandshake(id enode.ID, addr string) *Whoareyou {
|
||||
return sc.handshakes[sessionID{id, addr}]
|
||||
}
|
||||
|
||||
// storeSentHandshake stores the handshake challenge sent to the given remote node.
|
||||
func (sc *SessionCache) storeSentHandshake(id enode.ID, addr string, challenge *Whoareyou) {
|
||||
challenge.sent = sc.clock.Now()
|
||||
sc.handshakes[sessionID{id, addr}] = challenge
|
||||
}
|
||||
|
||||
// deleteHandshake deletes handshake data for the given node.
|
||||
func (sc *SessionCache) deleteHandshake(id enode.ID, addr string) {
|
||||
delete(sc.handshakes, sessionID{id, addr})
|
||||
}
|
||||
|
||||
// handshakeGC deletes timed-out handshakes.
|
||||
func (sc *SessionCache) handshakeGC() {
|
||||
deadline := sc.clock.Now().Add(-handshakeTimeout)
|
||||
for key, challenge := range sc.handshakes {
|
||||
if challenge.sent < deadline {
|
||||
delete(sc.handshakes, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
4
vendor/github.com/ethereum/go-ethereum/p2p/discv5/README
generated
vendored
Normal file
4
vendor/github.com/ethereum/go-ethereum/p2p/discv5/README
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
This package is an early prototype of Discovery v5. Do not use this code.
|
||||
|
||||
See https://github.com/ethereum/devp2p/blob/master/discv5/discv5.md for the
|
||||
current Discovery v5 specification.
|
||||
396
vendor/github.com/ethereum/go-ethereum/p2p/discv5/database.go
generated
vendored
Normal file
396
vendor/github.com/ethereum/go-ethereum/p2p/discv5/database.go
generated
vendored
Normal file
@@ -0,0 +1,396 @@
|
||||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Contains the node database, storing previously seen nodes and any collected
|
||||
// metadata about them for QoS purposes.
|
||||
|
||||
package discv5
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
"github.com/syndtr/goleveldb/leveldb/errors"
|
||||
"github.com/syndtr/goleveldb/leveldb/iterator"
|
||||
"github.com/syndtr/goleveldb/leveldb/opt"
|
||||
"github.com/syndtr/goleveldb/leveldb/storage"
|
||||
"github.com/syndtr/goleveldb/leveldb/util"
|
||||
)
|
||||
|
||||
var (
|
||||
nodeDBNilNodeID = NodeID{} // Special node ID to use as a nil element.
|
||||
nodeDBNodeExpiration = 24 * time.Hour // Time after which an unseen node should be dropped.
|
||||
nodeDBCleanupCycle = time.Hour // Time period for running the expiration task.
|
||||
)
|
||||
|
||||
// nodeDB stores all nodes we know about.
|
||||
type nodeDB struct {
|
||||
lvl *leveldb.DB // Interface to the database itself
|
||||
self NodeID // Own node id to prevent adding it into the database
|
||||
runner sync.Once // Ensures we can start at most one expirer
|
||||
quit chan struct{} // Channel to signal the expiring thread to stop
|
||||
}
|
||||
|
||||
// Schema layout for the node database
|
||||
var (
|
||||
nodeDBVersionKey = []byte("version") // Version of the database to flush if changes
|
||||
nodeDBItemPrefix = []byte("n:") // Identifier to prefix node entries with
|
||||
|
||||
nodeDBDiscoverRoot = ":discover"
|
||||
nodeDBDiscoverPing = nodeDBDiscoverRoot + ":lastping"
|
||||
nodeDBDiscoverPong = nodeDBDiscoverRoot + ":lastpong"
|
||||
nodeDBDiscoverFindFails = nodeDBDiscoverRoot + ":findfail"
|
||||
nodeDBTopicRegTickets = ":tickets"
|
||||
)
|
||||
|
||||
// newNodeDB creates a new node database for storing and retrieving infos about
|
||||
// known peers in the network. If no path is given, an in-memory, temporary
|
||||
// database is constructed.
|
||||
func newNodeDB(path string, version int, self NodeID) (*nodeDB, error) {
|
||||
if path == "" {
|
||||
return newMemoryNodeDB(self)
|
||||
}
|
||||
return newPersistentNodeDB(path, version, self)
|
||||
}
|
||||
|
||||
// newMemoryNodeDB creates a new in-memory node database without a persistent
|
||||
// backend.
|
||||
func newMemoryNodeDB(self NodeID) (*nodeDB, error) {
|
||||
db, err := leveldb.Open(storage.NewMemStorage(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &nodeDB{
|
||||
lvl: db,
|
||||
self: self,
|
||||
quit: make(chan struct{}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// newPersistentNodeDB creates/opens a leveldb backed persistent node database,
|
||||
// also flushing its contents in case of a version mismatch.
|
||||
func newPersistentNodeDB(path string, version int, self NodeID) (*nodeDB, error) {
|
||||
opts := &opt.Options{OpenFilesCacheCapacity: 5}
|
||||
db, err := leveldb.OpenFile(path, opts)
|
||||
if _, iscorrupted := err.(*errors.ErrCorrupted); iscorrupted {
|
||||
db, err = leveldb.RecoverFile(path, nil)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// The nodes contained in the cache correspond to a certain protocol version.
|
||||
// Flush all nodes if the version doesn't match.
|
||||
currentVer := make([]byte, binary.MaxVarintLen64)
|
||||
currentVer = currentVer[:binary.PutVarint(currentVer, int64(version))]
|
||||
|
||||
blob, err := db.Get(nodeDBVersionKey, nil)
|
||||
switch err {
|
||||
case leveldb.ErrNotFound:
|
||||
// Version not found (i.e. empty cache), insert it
|
||||
if err := db.Put(nodeDBVersionKey, currentVer, nil); err != nil {
|
||||
db.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
case nil:
|
||||
// Version present, flush if different
|
||||
if !bytes.Equal(blob, currentVer) {
|
||||
db.Close()
|
||||
if err = os.RemoveAll(path); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newPersistentNodeDB(path, version, self)
|
||||
}
|
||||
}
|
||||
return &nodeDB{
|
||||
lvl: db,
|
||||
self: self,
|
||||
quit: make(chan struct{}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// makeKey generates the leveldb key-blob from a node id and its particular
|
||||
// field of interest.
|
||||
func makeKey(id NodeID, field string) []byte {
|
||||
if bytes.Equal(id[:], nodeDBNilNodeID[:]) {
|
||||
return []byte(field)
|
||||
}
|
||||
return append(nodeDBItemPrefix, append(id[:], field...)...)
|
||||
}
|
||||
|
||||
// splitKey tries to split a database key into a node id and a field part.
|
||||
func splitKey(key []byte) (id NodeID, field string) {
|
||||
// If the key is not of a node, return it plainly
|
||||
if !bytes.HasPrefix(key, nodeDBItemPrefix) {
|
||||
return NodeID{}, string(key)
|
||||
}
|
||||
// Otherwise split the id and field
|
||||
item := key[len(nodeDBItemPrefix):]
|
||||
copy(id[:], item[:len(id)])
|
||||
field = string(item[len(id):])
|
||||
|
||||
return id, field
|
||||
}
|
||||
|
||||
// fetchInt64 retrieves an integer instance associated with a particular
|
||||
// database key.
|
||||
func (db *nodeDB) fetchInt64(key []byte) int64 {
|
||||
blob, err := db.lvl.Get(key, nil)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
val, read := binary.Varint(blob)
|
||||
if read <= 0 {
|
||||
return 0
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// storeInt64 update a specific database entry to the current time instance as a
|
||||
// unix timestamp.
|
||||
func (db *nodeDB) storeInt64(key []byte, n int64) error {
|
||||
blob := make([]byte, binary.MaxVarintLen64)
|
||||
blob = blob[:binary.PutVarint(blob, n)]
|
||||
return db.lvl.Put(key, blob, nil)
|
||||
}
|
||||
|
||||
func (db *nodeDB) storeRLP(key []byte, val interface{}) error {
|
||||
blob, err := rlp.EncodeToBytes(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return db.lvl.Put(key, blob, nil)
|
||||
}
|
||||
|
||||
func (db *nodeDB) fetchRLP(key []byte, val interface{}) error {
|
||||
blob, err := db.lvl.Get(key, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = rlp.DecodeBytes(blob, val)
|
||||
if err != nil {
|
||||
log.Warn(fmt.Sprintf("key %x (%T) %v", key, val, err))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// node retrieves a node with a given id from the database.
|
||||
func (db *nodeDB) node(id NodeID) *Node {
|
||||
var node Node
|
||||
if err := db.fetchRLP(makeKey(id, nodeDBDiscoverRoot), &node); err != nil {
|
||||
return nil
|
||||
}
|
||||
node.sha = crypto.Keccak256Hash(node.ID[:])
|
||||
return &node
|
||||
}
|
||||
|
||||
// updateNode inserts - potentially overwriting - a node into the peer database.
|
||||
func (db *nodeDB) updateNode(node *Node) error {
|
||||
return db.storeRLP(makeKey(node.ID, nodeDBDiscoverRoot), node)
|
||||
}
|
||||
|
||||
// deleteNode deletes all information/keys associated with a node.
|
||||
func (db *nodeDB) deleteNode(id NodeID) error {
|
||||
deleter := db.lvl.NewIterator(util.BytesPrefix(makeKey(id, "")), nil)
|
||||
for deleter.Next() {
|
||||
if err := db.lvl.Delete(deleter.Key(), nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ensureExpirer is a small helper method ensuring that the data expiration
|
||||
// mechanism is running. If the expiration goroutine is already running, this
|
||||
// method simply returns.
|
||||
//
|
||||
// The goal is to start the data evacuation only after the network successfully
|
||||
// bootstrapped itself (to prevent dumping potentially useful seed nodes). Since
|
||||
// it would require significant overhead to exactly trace the first successful
|
||||
// convergence, it's simpler to "ensure" the correct state when an appropriate
|
||||
// condition occurs (i.e. a successful bonding), and discard further events.
|
||||
func (db *nodeDB) ensureExpirer() {
|
||||
db.runner.Do(func() { go db.expirer() })
|
||||
}
|
||||
|
||||
// expirer should be started in a go routine, and is responsible for looping ad
|
||||
// infinitum and dropping stale data from the database.
|
||||
func (db *nodeDB) expirer() {
|
||||
tick := time.NewTicker(nodeDBCleanupCycle)
|
||||
defer tick.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-tick.C:
|
||||
if err := db.expireNodes(); err != nil {
|
||||
log.Error(fmt.Sprintf("Failed to expire nodedb items: %v", err))
|
||||
}
|
||||
case <-db.quit:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// expireNodes iterates over the database and deletes all nodes that have not
|
||||
// been seen (i.e. received a pong from) for some allotted time.
|
||||
func (db *nodeDB) expireNodes() error {
|
||||
threshold := time.Now().Add(-nodeDBNodeExpiration)
|
||||
|
||||
// Find discovered nodes that are older than the allowance
|
||||
it := db.lvl.NewIterator(nil, nil)
|
||||
defer it.Release()
|
||||
|
||||
for it.Next() {
|
||||
// Skip the item if not a discovery node
|
||||
id, field := splitKey(it.Key())
|
||||
if field != nodeDBDiscoverRoot {
|
||||
continue
|
||||
}
|
||||
// Skip the node if not expired yet (and not self)
|
||||
if !bytes.Equal(id[:], db.self[:]) {
|
||||
if seen := db.lastPong(id); seen.After(threshold) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
// Otherwise delete all associated information
|
||||
db.deleteNode(id)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// lastPing retrieves the time of the last ping packet send to a remote node,
|
||||
// requesting binding.
|
||||
func (db *nodeDB) lastPing(id NodeID) time.Time {
|
||||
return time.Unix(db.fetchInt64(makeKey(id, nodeDBDiscoverPing)), 0)
|
||||
}
|
||||
|
||||
// updateLastPing updates the last time we tried contacting a remote node.
|
||||
func (db *nodeDB) updateLastPing(id NodeID, instance time.Time) error {
|
||||
return db.storeInt64(makeKey(id, nodeDBDiscoverPing), instance.Unix())
|
||||
}
|
||||
|
||||
// lastPong retrieves the time of the last successful contact from remote node.
|
||||
func (db *nodeDB) lastPong(id NodeID) time.Time {
|
||||
return time.Unix(db.fetchInt64(makeKey(id, nodeDBDiscoverPong)), 0)
|
||||
}
|
||||
|
||||
// updateLastPong updates the last time a remote node successfully contacted.
|
||||
func (db *nodeDB) updateLastPong(id NodeID, instance time.Time) error {
|
||||
return db.storeInt64(makeKey(id, nodeDBDiscoverPong), instance.Unix())
|
||||
}
|
||||
|
||||
// findFails retrieves the number of findnode failures since bonding.
|
||||
func (db *nodeDB) findFails(id NodeID) int {
|
||||
return int(db.fetchInt64(makeKey(id, nodeDBDiscoverFindFails)))
|
||||
}
|
||||
|
||||
// updateFindFails updates the number of findnode failures since bonding.
|
||||
func (db *nodeDB) updateFindFails(id NodeID, fails int) error {
|
||||
return db.storeInt64(makeKey(id, nodeDBDiscoverFindFails), int64(fails))
|
||||
}
|
||||
|
||||
// querySeeds retrieves random nodes to be used as potential seed nodes
|
||||
// for bootstrapping.
|
||||
func (db *nodeDB) querySeeds(n int, maxAge time.Duration) []*Node {
|
||||
var (
|
||||
now = time.Now()
|
||||
nodes = make([]*Node, 0, n)
|
||||
it = db.lvl.NewIterator(nil, nil)
|
||||
id NodeID
|
||||
)
|
||||
defer it.Release()
|
||||
|
||||
seek:
|
||||
for seeks := 0; len(nodes) < n && seeks < n*5; seeks++ {
|
||||
// Seek to a random entry. The first byte is incremented by a
|
||||
// random amount each time in order to increase the likelihood
|
||||
// of hitting all existing nodes in very small databases.
|
||||
ctr := id[0]
|
||||
rand.Read(id[:])
|
||||
id[0] = ctr + id[0]%16
|
||||
it.Seek(makeKey(id, nodeDBDiscoverRoot))
|
||||
|
||||
n := nextNode(it)
|
||||
if n == nil {
|
||||
id[0] = 0
|
||||
continue seek // iterator exhausted
|
||||
}
|
||||
if n.ID == db.self {
|
||||
continue seek
|
||||
}
|
||||
if now.Sub(db.lastPong(n.ID)) > maxAge {
|
||||
continue seek
|
||||
}
|
||||
for i := range nodes {
|
||||
if nodes[i].ID == n.ID {
|
||||
continue seek // duplicate
|
||||
}
|
||||
}
|
||||
nodes = append(nodes, n)
|
||||
}
|
||||
return nodes
|
||||
}
|
||||
|
||||
func (db *nodeDB) fetchTopicRegTickets(id NodeID) (issued, used uint32) {
|
||||
key := makeKey(id, nodeDBTopicRegTickets)
|
||||
blob, _ := db.lvl.Get(key, nil)
|
||||
if len(blob) != 8 {
|
||||
return 0, 0
|
||||
}
|
||||
issued = binary.BigEndian.Uint32(blob[0:4])
|
||||
used = binary.BigEndian.Uint32(blob[4:8])
|
||||
return
|
||||
}
|
||||
|
||||
func (db *nodeDB) updateTopicRegTickets(id NodeID, issued, used uint32) error {
|
||||
key := makeKey(id, nodeDBTopicRegTickets)
|
||||
blob := make([]byte, 8)
|
||||
binary.BigEndian.PutUint32(blob[0:4], issued)
|
||||
binary.BigEndian.PutUint32(blob[4:8], used)
|
||||
return db.lvl.Put(key, blob, nil)
|
||||
}
|
||||
|
||||
// reads the next node record from the iterator, skipping over other
|
||||
// database entries.
|
||||
func nextNode(it iterator.Iterator) *Node {
|
||||
for end := false; !end; end = !it.Next() {
|
||||
id, field := splitKey(it.Key())
|
||||
if field != nodeDBDiscoverRoot {
|
||||
continue
|
||||
}
|
||||
var n Node
|
||||
if err := rlp.DecodeBytes(it.Value(), &n); err != nil {
|
||||
log.Warn(fmt.Sprintf("invalid node %x: %v", id, err))
|
||||
continue
|
||||
}
|
||||
return &n
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// close flushes and closes the database files.
|
||||
func (db *nodeDB) close() {
|
||||
close(db.quit)
|
||||
db.lvl.Close()
|
||||
}
|
||||
24
vendor/github.com/ethereum/go-ethereum/p2p/discv5/metrics.go
generated
vendored
Normal file
24
vendor/github.com/ethereum/go-ethereum/p2p/discv5/metrics.go
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright 2018 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package discv5
|
||||
|
||||
import "github.com/ethereum/go-ethereum/metrics"
|
||||
|
||||
var (
|
||||
ingressTrafficMeter = metrics.NewRegisteredMeter("discv5/InboundTraffic", nil)
|
||||
egressTrafficMeter = metrics.NewRegisteredMeter("discv5/OutboundTraffic", nil)
|
||||
)
|
||||
1269
vendor/github.com/ethereum/go-ethereum/p2p/discv5/net.go
generated
vendored
Normal file
1269
vendor/github.com/ethereum/go-ethereum/p2p/discv5/net.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
413
vendor/github.com/ethereum/go-ethereum/p2p/discv5/node.go
generated
vendored
Normal file
413
vendor/github.com/ethereum/go-ethereum/p2p/discv5/node.go
generated
vendored
Normal file
@@ -0,0 +1,413 @@
|
||||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package discv5
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
)
|
||||
|
||||
// Node represents a host on the network.
|
||||
// The public fields of Node may not be modified.
|
||||
type Node struct {
|
||||
IP net.IP // len 4 for IPv4 or 16 for IPv6
|
||||
UDP, TCP uint16 // port numbers
|
||||
ID NodeID // the node's public key
|
||||
|
||||
// Network-related fields are contained in nodeNetGuts.
|
||||
// These fields are not supposed to be used off the
|
||||
// Network.loop goroutine.
|
||||
nodeNetGuts
|
||||
}
|
||||
|
||||
// NewNode creates a new node. It is mostly meant to be used for
|
||||
// testing purposes.
|
||||
func NewNode(id NodeID, ip net.IP, udpPort, tcpPort uint16) *Node {
|
||||
if ipv4 := ip.To4(); ipv4 != nil {
|
||||
ip = ipv4
|
||||
}
|
||||
return &Node{
|
||||
IP: ip,
|
||||
UDP: udpPort,
|
||||
TCP: tcpPort,
|
||||
ID: id,
|
||||
nodeNetGuts: nodeNetGuts{sha: crypto.Keccak256Hash(id[:])},
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Node) addr() *net.UDPAddr {
|
||||
return &net.UDPAddr{IP: n.IP, Port: int(n.UDP)}
|
||||
}
|
||||
|
||||
// Incomplete returns true for nodes with no IP address.
|
||||
func (n *Node) Incomplete() bool {
|
||||
return n.IP == nil
|
||||
}
|
||||
|
||||
// checks whether n is a valid complete node.
|
||||
func (n *Node) validateComplete() error {
|
||||
if n.Incomplete() {
|
||||
return errors.New("incomplete node")
|
||||
}
|
||||
if n.UDP == 0 {
|
||||
return errors.New("missing UDP port")
|
||||
}
|
||||
if n.TCP == 0 {
|
||||
return errors.New("missing TCP port")
|
||||
}
|
||||
if n.IP.IsMulticast() || n.IP.IsUnspecified() {
|
||||
return errors.New("invalid IP (multicast/unspecified)")
|
||||
}
|
||||
_, err := n.ID.Pubkey() // validate the key (on curve, etc.)
|
||||
return err
|
||||
}
|
||||
|
||||
// The string representation of a Node is a URL.
|
||||
// Please see ParseNode for a description of the format.
|
||||
func (n *Node) String() string {
|
||||
u := url.URL{Scheme: "enode"}
|
||||
if n.Incomplete() {
|
||||
u.Host = fmt.Sprintf("%x", n.ID[:])
|
||||
} else {
|
||||
addr := net.TCPAddr{IP: n.IP, Port: int(n.TCP)}
|
||||
u.User = url.User(fmt.Sprintf("%x", n.ID[:]))
|
||||
u.Host = addr.String()
|
||||
if n.UDP != n.TCP {
|
||||
u.RawQuery = "discport=" + strconv.Itoa(int(n.UDP))
|
||||
}
|
||||
}
|
||||
return u.String()
|
||||
}
|
||||
|
||||
var incompleteNodeURL = regexp.MustCompile("(?i)^(?:enode://)?([0-9a-f]+)$")
|
||||
|
||||
// ParseNode parses a node designator.
|
||||
//
|
||||
// There are two basic forms of node designators
|
||||
// - incomplete nodes, which only have the public key (node ID)
|
||||
// - complete nodes, which contain the public key and IP/Port information
|
||||
//
|
||||
// For incomplete nodes, the designator must look like one of these
|
||||
//
|
||||
// enode://<hex node id>
|
||||
// <hex node id>
|
||||
//
|
||||
// For complete nodes, the node ID is encoded in the username portion
|
||||
// of the URL, separated from the host by an @ sign. The hostname can
|
||||
// only be given as an IP address, DNS domain names are not allowed.
|
||||
// The port in the host name section is the TCP listening port. If the
|
||||
// TCP and UDP (discovery) ports differ, the UDP port is specified as
|
||||
// query parameter "discport".
|
||||
//
|
||||
// In the following example, the node URL describes
|
||||
// a node with IP address 10.3.58.6, TCP listening port 30303
|
||||
// and UDP discovery port 30301.
|
||||
//
|
||||
// enode://<hex node id>@10.3.58.6:30303?discport=30301
|
||||
func ParseNode(rawurl string) (*Node, error) {
|
||||
if m := incompleteNodeURL.FindStringSubmatch(rawurl); m != nil {
|
||||
id, err := HexID(m[1])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid node ID (%v)", err)
|
||||
}
|
||||
return NewNode(id, nil, 0, 0), nil
|
||||
}
|
||||
return parseComplete(rawurl)
|
||||
}
|
||||
|
||||
func parseComplete(rawurl string) (*Node, error) {
|
||||
var (
|
||||
id NodeID
|
||||
ip net.IP
|
||||
tcpPort, udpPort uint64
|
||||
)
|
||||
u, err := url.Parse(rawurl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if u.Scheme != "enode" {
|
||||
return nil, errors.New("invalid URL scheme, want \"enode\"")
|
||||
}
|
||||
// Parse the Node ID from the user portion.
|
||||
if u.User == nil {
|
||||
return nil, errors.New("does not contain node ID")
|
||||
}
|
||||
if id, err = HexID(u.User.String()); err != nil {
|
||||
return nil, fmt.Errorf("invalid node ID (%v)", err)
|
||||
}
|
||||
// Parse the IP address.
|
||||
host, port, err := net.SplitHostPort(u.Host)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid host: %v", err)
|
||||
}
|
||||
if ip = net.ParseIP(host); ip == nil {
|
||||
return nil, errors.New("invalid IP address")
|
||||
}
|
||||
// Ensure the IP is 4 bytes long for IPv4 addresses.
|
||||
if ipv4 := ip.To4(); ipv4 != nil {
|
||||
ip = ipv4
|
||||
}
|
||||
// Parse the port numbers.
|
||||
if tcpPort, err = strconv.ParseUint(port, 10, 16); err != nil {
|
||||
return nil, errors.New("invalid port")
|
||||
}
|
||||
udpPort = tcpPort
|
||||
qv := u.Query()
|
||||
if qv.Get("discport") != "" {
|
||||
udpPort, err = strconv.ParseUint(qv.Get("discport"), 10, 16)
|
||||
if err != nil {
|
||||
return nil, errors.New("invalid discport in query")
|
||||
}
|
||||
}
|
||||
return NewNode(id, ip, uint16(udpPort), uint16(tcpPort)), nil
|
||||
}
|
||||
|
||||
// MustParseNode parses a node URL. It panics if the URL is not valid.
|
||||
func MustParseNode(rawurl string) *Node {
|
||||
n, err := ParseNode(rawurl)
|
||||
if err != nil {
|
||||
panic("invalid node URL: " + err.Error())
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// MarshalText implements encoding.TextMarshaler.
|
||||
func (n *Node) MarshalText() ([]byte, error) {
|
||||
return []byte(n.String()), nil
|
||||
}
|
||||
|
||||
// UnmarshalText implements encoding.TextUnmarshaler.
|
||||
func (n *Node) UnmarshalText(text []byte) error {
|
||||
dec, err := ParseNode(string(text))
|
||||
if err == nil {
|
||||
*n = *dec
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// type nodeQueue []*Node
|
||||
//
|
||||
// // pushNew adds n to the end if it is not present.
|
||||
// func (nl *nodeList) appendNew(n *Node) {
|
||||
// for _, entry := range n {
|
||||
// if entry == n {
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
// *nq = append(*nq, n)
|
||||
// }
|
||||
//
|
||||
// // popRandom removes a random node. Nodes closer to
|
||||
// // to the head of the beginning of the have a slightly higher probability.
|
||||
// func (nl *nodeList) popRandom() *Node {
|
||||
// ix := rand.Intn(len(*nq))
|
||||
// //TODO: probability as mentioned above.
|
||||
// nl.removeIndex(ix)
|
||||
// }
|
||||
//
|
||||
// func (nl *nodeList) removeIndex(i int) *Node {
|
||||
// slice = *nl
|
||||
// if len(*slice) <= i {
|
||||
// return nil
|
||||
// }
|
||||
// *nl = append(slice[:i], slice[i+1:]...)
|
||||
// }
|
||||
|
||||
const nodeIDBits = 512
|
||||
|
||||
// NodeID is a unique identifier for each node.
|
||||
// The node identifier is a marshaled elliptic curve public key.
|
||||
type NodeID [nodeIDBits / 8]byte
|
||||
|
||||
// NodeID prints as a long hexadecimal number.
|
||||
func (n NodeID) String() string {
|
||||
return fmt.Sprintf("%x", n[:])
|
||||
}
|
||||
|
||||
// The Go syntax representation of a NodeID is a call to HexID.
|
||||
func (n NodeID) GoString() string {
|
||||
return fmt.Sprintf("discover.HexID(\"%x\")", n[:])
|
||||
}
|
||||
|
||||
// TerminalString returns a shortened hex string for terminal logging.
|
||||
func (n NodeID) TerminalString() string {
|
||||
return hex.EncodeToString(n[:8])
|
||||
}
|
||||
|
||||
// HexID converts a hex string to a NodeID.
|
||||
// The string may be prefixed with 0x.
|
||||
func HexID(in string) (NodeID, error) {
|
||||
var id NodeID
|
||||
b, err := hex.DecodeString(strings.TrimPrefix(in, "0x"))
|
||||
if err != nil {
|
||||
return id, err
|
||||
} else if len(b) != len(id) {
|
||||
return id, fmt.Errorf("wrong length, want %d hex chars", len(id)*2)
|
||||
}
|
||||
copy(id[:], b)
|
||||
return id, nil
|
||||
}
|
||||
|
||||
// MustHexID converts a hex string to a NodeID.
|
||||
// It panics if the string is not a valid NodeID.
|
||||
func MustHexID(in string) NodeID {
|
||||
id, err := HexID(in)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
// PubkeyID returns a marshaled representation of the given public key.
|
||||
func PubkeyID(pub *ecdsa.PublicKey) NodeID {
|
||||
var id NodeID
|
||||
pbytes := elliptic.Marshal(pub.Curve, pub.X, pub.Y)
|
||||
if len(pbytes)-1 != len(id) {
|
||||
panic(fmt.Errorf("need %d bit pubkey, got %d bits", (len(id)+1)*8, len(pbytes)))
|
||||
}
|
||||
copy(id[:], pbytes[1:])
|
||||
return id
|
||||
}
|
||||
|
||||
// Pubkey returns the public key represented by the node ID.
|
||||
// It returns an error if the ID is not a point on the curve.
|
||||
func (n NodeID) Pubkey() (*ecdsa.PublicKey, error) {
|
||||
p := &ecdsa.PublicKey{Curve: crypto.S256(), X: new(big.Int), Y: new(big.Int)}
|
||||
half := len(n) / 2
|
||||
p.X.SetBytes(n[:half])
|
||||
p.Y.SetBytes(n[half:])
|
||||
if !p.Curve.IsOnCurve(p.X, p.Y) {
|
||||
return nil, errors.New("id is invalid secp256k1 curve point")
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// recoverNodeID computes the public key used to sign the
|
||||
// given hash from the signature.
|
||||
func recoverNodeID(hash, sig []byte) (id NodeID, err error) {
|
||||
pubkey, err := crypto.Ecrecover(hash, sig)
|
||||
if err != nil {
|
||||
return id, err
|
||||
}
|
||||
if len(pubkey)-1 != len(id) {
|
||||
return id, fmt.Errorf("recovered pubkey has %d bits, want %d bits", len(pubkey)*8, (len(id)+1)*8)
|
||||
}
|
||||
for i := range id {
|
||||
id[i] = pubkey[i+1]
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
||||
// distcmp compares the distances a->target and b->target.
|
||||
// Returns -1 if a is closer to target, 1 if b is closer to target
|
||||
// and 0 if they are equal.
|
||||
func distcmp(target, a, b common.Hash) int {
|
||||
for i := range target {
|
||||
da := a[i] ^ target[i]
|
||||
db := b[i] ^ target[i]
|
||||
if da > db {
|
||||
return 1
|
||||
} else if da < db {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// table of leading zero counts for bytes [0..255]
|
||||
var lzcount = [256]int{
|
||||
8, 7, 6, 6, 5, 5, 5, 5,
|
||||
4, 4, 4, 4, 4, 4, 4, 4,
|
||||
3, 3, 3, 3, 3, 3, 3, 3,
|
||||
3, 3, 3, 3, 3, 3, 3, 3,
|
||||
2, 2, 2, 2, 2, 2, 2, 2,
|
||||
2, 2, 2, 2, 2, 2, 2, 2,
|
||||
2, 2, 2, 2, 2, 2, 2, 2,
|
||||
2, 2, 2, 2, 2, 2, 2, 2,
|
||||
1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
}
|
||||
|
||||
// logdist returns the logarithmic distance between a and b, log2(a ^ b).
|
||||
func logdist(a, b common.Hash) int {
|
||||
lz := 0
|
||||
for i := range a {
|
||||
x := a[i] ^ b[i]
|
||||
if x == 0 {
|
||||
lz += 8
|
||||
} else {
|
||||
lz += lzcount[x]
|
||||
break
|
||||
}
|
||||
}
|
||||
return len(a)*8 - lz
|
||||
}
|
||||
|
||||
// hashAtDistance returns a random hash such that logdist(a, b) == n
|
||||
func hashAtDistance(a common.Hash, n int) (b common.Hash) {
|
||||
if n == 0 {
|
||||
return a
|
||||
}
|
||||
// flip bit at position n, fill the rest with random bits
|
||||
b = a
|
||||
pos := len(a) - n/8 - 1
|
||||
bit := byte(0x01) << (byte(n%8) - 1)
|
||||
if bit == 0 {
|
||||
pos++
|
||||
bit = 0x80
|
||||
}
|
||||
b[pos] = a[pos]&^bit | ^a[pos]&bit // TODO: randomize end bits
|
||||
for i := pos + 1; i < len(a); i++ {
|
||||
b[i] = byte(rand.Intn(255))
|
||||
}
|
||||
return b
|
||||
}
|
||||
17
vendor/github.com/ethereum/go-ethereum/p2p/discv5/nodeevent_string.go
generated
vendored
Normal file
17
vendor/github.com/ethereum/go-ethereum/p2p/discv5/nodeevent_string.go
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
// Code generated by "stringer -type=nodeEvent"; DO NOT EDIT.
|
||||
|
||||
package discv5
|
||||
|
||||
import "strconv"
|
||||
|
||||
const _nodeEvent_name = "pongTimeoutpingTimeoutneighboursTimeout"
|
||||
|
||||
var _nodeEvent_index = [...]uint8{0, 11, 22, 39}
|
||||
|
||||
func (i nodeEvent) String() string {
|
||||
i -= 264
|
||||
if i >= nodeEvent(len(_nodeEvent_index)-1) {
|
||||
return "nodeEvent(" + strconv.FormatInt(int64(i+264), 10) + ")"
|
||||
}
|
||||
return _nodeEvent_name[_nodeEvent_index[i]:_nodeEvent_index[i+1]]
|
||||
}
|
||||
318
vendor/github.com/ethereum/go-ethereum/p2p/discv5/table.go
generated
vendored
Normal file
318
vendor/github.com/ethereum/go-ethereum/p2p/discv5/table.go
generated
vendored
Normal file
@@ -0,0 +1,318 @@
|
||||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Package discv5 is a prototype implementation of Discvery v5.
|
||||
// Deprecated: do not use this package.
|
||||
package discv5
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"net"
|
||||
"sort"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
const (
|
||||
alpha = 3 // Kademlia concurrency factor
|
||||
bucketSize = 16 // Kademlia bucket size
|
||||
hashBits = len(common.Hash{}) * 8
|
||||
nBuckets = hashBits + 1 // Number of buckets
|
||||
|
||||
maxFindnodeFailures = 5
|
||||
)
|
||||
|
||||
type Table struct {
|
||||
count int // number of nodes
|
||||
buckets [nBuckets]*bucket // index of known nodes by distance
|
||||
nodeAddedHook func(*Node) // for testing
|
||||
self *Node // metadata of the local node
|
||||
}
|
||||
|
||||
// bucket contains nodes, ordered by their last activity. the entry
|
||||
// that was most recently active is the first element in entries.
|
||||
type bucket struct {
|
||||
entries []*Node
|
||||
replacements []*Node
|
||||
}
|
||||
|
||||
func newTable(ourID NodeID, ourAddr *net.UDPAddr) *Table {
|
||||
self := NewNode(ourID, ourAddr.IP, uint16(ourAddr.Port), uint16(ourAddr.Port))
|
||||
tab := &Table{self: self}
|
||||
for i := range tab.buckets {
|
||||
tab.buckets[i] = new(bucket)
|
||||
}
|
||||
return tab
|
||||
}
|
||||
|
||||
const printTable = false
|
||||
|
||||
// chooseBucketRefreshTarget selects random refresh targets to keep all Kademlia
|
||||
// buckets filled with live connections and keep the network topology healthy.
|
||||
// This requires selecting addresses closer to our own with a higher probability
|
||||
// in order to refresh closer buckets too.
|
||||
//
|
||||
// This algorithm approximates the distance distribution of existing nodes in the
|
||||
// table by selecting a random node from the table and selecting a target address
|
||||
// with a distance less than twice of that of the selected node.
|
||||
// This algorithm will be improved later to specifically target the least recently
|
||||
// used buckets.
|
||||
func (tab *Table) chooseBucketRefreshTarget() common.Hash {
|
||||
entries := 0
|
||||
if printTable {
|
||||
fmt.Println()
|
||||
}
|
||||
for i, b := range &tab.buckets {
|
||||
entries += len(b.entries)
|
||||
if printTable {
|
||||
for _, e := range b.entries {
|
||||
fmt.Println(i, e.state, e.addr().String(), e.ID.String(), e.sha.Hex())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
prefix := binary.BigEndian.Uint64(tab.self.sha[0:8])
|
||||
dist := ^uint64(0)
|
||||
entry := int(randUint(uint32(entries + 1)))
|
||||
for _, b := range &tab.buckets {
|
||||
if entry < len(b.entries) {
|
||||
n := b.entries[entry]
|
||||
dist = binary.BigEndian.Uint64(n.sha[0:8]) ^ prefix
|
||||
break
|
||||
}
|
||||
entry -= len(b.entries)
|
||||
}
|
||||
|
||||
ddist := ^uint64(0)
|
||||
if dist+dist > dist {
|
||||
ddist = dist
|
||||
}
|
||||
targetPrefix := prefix ^ randUint64n(ddist)
|
||||
|
||||
var target common.Hash
|
||||
binary.BigEndian.PutUint64(target[0:8], targetPrefix)
|
||||
rand.Read(target[8:])
|
||||
return target
|
||||
}
|
||||
|
||||
// readRandomNodes fills the given slice with random nodes from the
|
||||
// table. It will not write the same node more than once. The nodes in
|
||||
// the slice are copies and can be modified by the caller.
|
||||
func (tab *Table) readRandomNodes(buf []*Node) (n int) {
|
||||
// TODO: tree-based buckets would help here
|
||||
// Find all non-empty buckets and get a fresh slice of their entries.
|
||||
var buckets [][]*Node
|
||||
for _, b := range &tab.buckets {
|
||||
if len(b.entries) > 0 {
|
||||
buckets = append(buckets, b.entries)
|
||||
}
|
||||
}
|
||||
if len(buckets) == 0 {
|
||||
return 0
|
||||
}
|
||||
// Shuffle the buckets.
|
||||
for i := uint32(len(buckets)) - 1; i > 0; i-- {
|
||||
j := randUint(i)
|
||||
buckets[i], buckets[j] = buckets[j], buckets[i]
|
||||
}
|
||||
// Move head of each bucket into buf, removing buckets that become empty.
|
||||
var i, j int
|
||||
for ; i < len(buf); i, j = i+1, (j+1)%len(buckets) {
|
||||
b := buckets[j]
|
||||
buf[i] = &(*b[0])
|
||||
buckets[j] = b[1:]
|
||||
if len(b) == 1 {
|
||||
buckets = append(buckets[:j], buckets[j+1:]...)
|
||||
}
|
||||
if len(buckets) == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return i + 1
|
||||
}
|
||||
|
||||
func randUint(max uint32) uint32 {
|
||||
if max < 2 {
|
||||
return 0
|
||||
}
|
||||
var b [4]byte
|
||||
rand.Read(b[:])
|
||||
return binary.BigEndian.Uint32(b[:]) % max
|
||||
}
|
||||
|
||||
func randUint64n(max uint64) uint64 {
|
||||
if max < 2 {
|
||||
return 0
|
||||
}
|
||||
var b [8]byte
|
||||
rand.Read(b[:])
|
||||
return binary.BigEndian.Uint64(b[:]) % max
|
||||
}
|
||||
|
||||
// closest returns the n nodes in the table that are closest to the
|
||||
// given id. The caller must hold tab.mutex.
|
||||
func (tab *Table) closest(target common.Hash, nresults int) *nodesByDistance {
|
||||
// This is a very wasteful way to find the closest nodes but
|
||||
// obviously correct. I believe that tree-based buckets would make
|
||||
// this easier to implement efficiently.
|
||||
close := &nodesByDistance{target: target}
|
||||
for _, b := range &tab.buckets {
|
||||
for _, n := range b.entries {
|
||||
close.push(n, nresults)
|
||||
}
|
||||
}
|
||||
return close
|
||||
}
|
||||
|
||||
// add attempts to add the given node its corresponding bucket. If the
|
||||
// bucket has space available, adding the node succeeds immediately.
|
||||
// Otherwise, the node is added to the replacement cache for the bucket.
|
||||
func (tab *Table) add(n *Node) (contested *Node) {
|
||||
//fmt.Println("add", n.addr().String(), n.ID.String(), n.sha.Hex())
|
||||
if n.ID == tab.self.ID {
|
||||
return
|
||||
}
|
||||
b := tab.buckets[logdist(tab.self.sha, n.sha)]
|
||||
switch {
|
||||
case b.bump(n):
|
||||
// n exists in b.
|
||||
return nil
|
||||
case len(b.entries) < bucketSize:
|
||||
// b has space available.
|
||||
b.addFront(n)
|
||||
tab.count++
|
||||
if tab.nodeAddedHook != nil {
|
||||
tab.nodeAddedHook(n)
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
// b has no space left, add to replacement cache
|
||||
// and revalidate the last entry.
|
||||
// TODO: drop previous node
|
||||
b.replacements = append(b.replacements, n)
|
||||
if len(b.replacements) > bucketSize {
|
||||
copy(b.replacements, b.replacements[1:])
|
||||
b.replacements = b.replacements[:len(b.replacements)-1]
|
||||
}
|
||||
return b.entries[len(b.entries)-1]
|
||||
}
|
||||
}
|
||||
|
||||
// stuff adds nodes the table to the end of their corresponding bucket
|
||||
// if the bucket is not full.
|
||||
func (tab *Table) stuff(nodes []*Node) {
|
||||
outer:
|
||||
for _, n := range nodes {
|
||||
if n.ID == tab.self.ID {
|
||||
continue // don't add self
|
||||
}
|
||||
bucket := tab.buckets[logdist(tab.self.sha, n.sha)]
|
||||
for i := range bucket.entries {
|
||||
if bucket.entries[i].ID == n.ID {
|
||||
continue outer // already in bucket
|
||||
}
|
||||
}
|
||||
if len(bucket.entries) < bucketSize {
|
||||
bucket.entries = append(bucket.entries, n)
|
||||
tab.count++
|
||||
if tab.nodeAddedHook != nil {
|
||||
tab.nodeAddedHook(n)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// delete removes an entry from the node table (used to evacuate
|
||||
// failed/non-bonded discovery peers).
|
||||
func (tab *Table) delete(node *Node) {
|
||||
//fmt.Println("delete", node.addr().String(), node.ID.String(), node.sha.Hex())
|
||||
bucket := tab.buckets[logdist(tab.self.sha, node.sha)]
|
||||
for i := range bucket.entries {
|
||||
if bucket.entries[i].ID == node.ID {
|
||||
bucket.entries = append(bucket.entries[:i], bucket.entries[i+1:]...)
|
||||
tab.count--
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (tab *Table) deleteReplace(node *Node) {
|
||||
b := tab.buckets[logdist(tab.self.sha, node.sha)]
|
||||
i := 0
|
||||
for i < len(b.entries) {
|
||||
if b.entries[i].ID == node.ID {
|
||||
b.entries = append(b.entries[:i], b.entries[i+1:]...)
|
||||
tab.count--
|
||||
} else {
|
||||
i++
|
||||
}
|
||||
}
|
||||
// refill from replacement cache
|
||||
// TODO: maybe use random index
|
||||
if len(b.entries) < bucketSize && len(b.replacements) > 0 {
|
||||
ri := len(b.replacements) - 1
|
||||
b.addFront(b.replacements[ri])
|
||||
tab.count++
|
||||
b.replacements[ri] = nil
|
||||
b.replacements = b.replacements[:ri]
|
||||
}
|
||||
}
|
||||
|
||||
func (b *bucket) addFront(n *Node) {
|
||||
b.entries = append(b.entries, nil)
|
||||
copy(b.entries[1:], b.entries)
|
||||
b.entries[0] = n
|
||||
}
|
||||
|
||||
func (b *bucket) bump(n *Node) bool {
|
||||
for i := range b.entries {
|
||||
if b.entries[i].ID == n.ID {
|
||||
// move it to the front
|
||||
copy(b.entries[1:], b.entries[:i])
|
||||
b.entries[0] = n
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// nodesByDistance is a list of nodes, ordered by
|
||||
// distance to target.
|
||||
type nodesByDistance struct {
|
||||
entries []*Node
|
||||
target common.Hash
|
||||
}
|
||||
|
||||
// push adds the given node to the list, keeping the total size below maxElems.
|
||||
func (h *nodesByDistance) push(n *Node, maxElems int) {
|
||||
ix := sort.Search(len(h.entries), func(i int) bool {
|
||||
return distcmp(h.target, h.entries[i].sha, n.sha) > 0
|
||||
})
|
||||
if len(h.entries) < maxElems {
|
||||
h.entries = append(h.entries, n)
|
||||
}
|
||||
if ix == len(h.entries) {
|
||||
// farther away than all nodes we already have.
|
||||
// if there was room for it, the node is now the last element.
|
||||
} else {
|
||||
// slide existing entries down to make room
|
||||
// this will overwrite the entry we just appended.
|
||||
copy(h.entries[ix+1:], h.entries[ix:])
|
||||
h.entries[ix] = n
|
||||
}
|
||||
}
|
||||
884
vendor/github.com/ethereum/go-ethereum/p2p/discv5/ticket.go
generated
vendored
Normal file
884
vendor/github.com/ethereum/go-ethereum/p2p/discv5/ticket.go
generated
vendored
Normal file
@@ -0,0 +1,884 @@
|
||||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package discv5
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/mclock"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
const (
|
||||
ticketTimeBucketLen = time.Minute
|
||||
collectFrequency = time.Second * 30
|
||||
registerFrequency = time.Second * 60
|
||||
maxCollectDebt = 10
|
||||
maxRegisterDebt = 5
|
||||
keepTicketConst = time.Minute * 10
|
||||
keepTicketExp = time.Minute * 5
|
||||
targetWaitTime = time.Minute * 10
|
||||
topicQueryTimeout = time.Second * 5
|
||||
topicQueryResend = time.Minute
|
||||
// topic radius detection
|
||||
maxRadius = 0xffffffffffffffff
|
||||
radiusTC = time.Minute * 20
|
||||
radiusBucketsPerBit = 8
|
||||
minSlope = 1
|
||||
minPeakSize = 40
|
||||
maxNoAdjust = 20
|
||||
lookupWidth = 8
|
||||
minRightSum = 20
|
||||
searchForceQuery = 4
|
||||
)
|
||||
|
||||
// timeBucket represents absolute monotonic time in minutes.
|
||||
// It is used as the index into the per-topic ticket buckets.
|
||||
type timeBucket int
|
||||
|
||||
type ticket struct {
|
||||
topics []Topic
|
||||
regTime []mclock.AbsTime // Per-topic local absolute time when the ticket can be used.
|
||||
|
||||
// The serial number that was issued by the server.
|
||||
serial uint32
|
||||
// Used by registrar, tracks absolute time when the ticket was created.
|
||||
issueTime mclock.AbsTime
|
||||
|
||||
// Fields used only by registrants
|
||||
node *Node // the registrar node that signed this ticket
|
||||
refCnt int // tracks number of topics that will be registered using this ticket
|
||||
pong []byte // encoded pong packet signed by the registrar
|
||||
}
|
||||
|
||||
// ticketRef refers to a single topic in a ticket.
|
||||
type ticketRef struct {
|
||||
t *ticket
|
||||
idx int // index of the topic in t.topics and t.regTime
|
||||
}
|
||||
|
||||
func (ref ticketRef) topic() Topic {
|
||||
return ref.t.topics[ref.idx]
|
||||
}
|
||||
|
||||
func (ref ticketRef) topicRegTime() mclock.AbsTime {
|
||||
return ref.t.regTime[ref.idx]
|
||||
}
|
||||
|
||||
func pongToTicket(localTime mclock.AbsTime, topics []Topic, node *Node, p *ingressPacket) (*ticket, error) {
|
||||
wps := p.data.(*pong).WaitPeriods
|
||||
if len(topics) != len(wps) {
|
||||
return nil, fmt.Errorf("bad wait period list: got %d values, want %d", len(topics), len(wps))
|
||||
}
|
||||
if rlpHash(topics) != p.data.(*pong).TopicHash {
|
||||
return nil, fmt.Errorf("bad topic hash")
|
||||
}
|
||||
t := &ticket{
|
||||
issueTime: localTime,
|
||||
node: node,
|
||||
topics: topics,
|
||||
pong: p.rawData,
|
||||
regTime: make([]mclock.AbsTime, len(wps)),
|
||||
}
|
||||
// Convert wait periods to local absolute time.
|
||||
for i, wp := range wps {
|
||||
t.regTime[i] = localTime + mclock.AbsTime(time.Second*time.Duration(wp))
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func ticketToPong(t *ticket, pong *pong) {
|
||||
pong.Expiration = uint64(t.issueTime / mclock.AbsTime(time.Second))
|
||||
pong.TopicHash = rlpHash(t.topics)
|
||||
pong.TicketSerial = t.serial
|
||||
pong.WaitPeriods = make([]uint32, len(t.regTime))
|
||||
for i, regTime := range t.regTime {
|
||||
pong.WaitPeriods[i] = uint32(time.Duration(regTime-t.issueTime) / time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
type ticketStore struct {
|
||||
// radius detector and target address generator
|
||||
// exists for both searched and registered topics
|
||||
radius map[Topic]*topicRadius
|
||||
|
||||
// Contains buckets (for each absolute minute) of tickets
|
||||
// that can be used in that minute.
|
||||
// This is only set if the topic is being registered.
|
||||
tickets map[Topic]*topicTickets
|
||||
|
||||
regQueue []Topic // Topic registration queue for round robin attempts
|
||||
regSet map[Topic]struct{} // Topic registration queue contents for fast filling
|
||||
|
||||
nodes map[*Node]*ticket
|
||||
nodeLastReq map[*Node]reqInfo
|
||||
|
||||
lastBucketFetched timeBucket
|
||||
nextTicketCached *ticketRef
|
||||
|
||||
searchTopicMap map[Topic]searchTopic
|
||||
nextTopicQueryCleanup mclock.AbsTime
|
||||
queriesSent map[*Node]map[common.Hash]sentQuery
|
||||
}
|
||||
|
||||
type searchTopic struct {
|
||||
foundChn chan<- *Node
|
||||
}
|
||||
|
||||
type sentQuery struct {
|
||||
sent mclock.AbsTime
|
||||
lookup lookupInfo
|
||||
}
|
||||
|
||||
type topicTickets struct {
|
||||
buckets map[timeBucket][]ticketRef
|
||||
nextLookup mclock.AbsTime
|
||||
nextReg mclock.AbsTime
|
||||
}
|
||||
|
||||
func newTicketStore() *ticketStore {
|
||||
return &ticketStore{
|
||||
radius: make(map[Topic]*topicRadius),
|
||||
tickets: make(map[Topic]*topicTickets),
|
||||
regSet: make(map[Topic]struct{}),
|
||||
nodes: make(map[*Node]*ticket),
|
||||
nodeLastReq: make(map[*Node]reqInfo),
|
||||
searchTopicMap: make(map[Topic]searchTopic),
|
||||
queriesSent: make(map[*Node]map[common.Hash]sentQuery),
|
||||
}
|
||||
}
|
||||
|
||||
// addTopic starts tracking a topic. If register is true,
|
||||
// the local node will register the topic and tickets will be collected.
|
||||
func (s *ticketStore) addTopic(topic Topic, register bool) {
|
||||
log.Trace("Adding discovery topic", "topic", topic, "register", register)
|
||||
if s.radius[topic] == nil {
|
||||
s.radius[topic] = newTopicRadius(topic)
|
||||
}
|
||||
if register && s.tickets[topic] == nil {
|
||||
s.tickets[topic] = &topicTickets{buckets: make(map[timeBucket][]ticketRef)}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ticketStore) addSearchTopic(t Topic, foundChn chan<- *Node) {
|
||||
s.addTopic(t, false)
|
||||
if s.searchTopicMap[t].foundChn == nil {
|
||||
s.searchTopicMap[t] = searchTopic{foundChn: foundChn}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ticketStore) removeSearchTopic(t Topic) {
|
||||
if st := s.searchTopicMap[t]; st.foundChn != nil {
|
||||
delete(s.searchTopicMap, t)
|
||||
}
|
||||
}
|
||||
|
||||
// removeRegisterTopic deletes all tickets for the given topic.
|
||||
func (s *ticketStore) removeRegisterTopic(topic Topic) {
|
||||
log.Trace("Removing discovery topic", "topic", topic)
|
||||
if s.tickets[topic] == nil {
|
||||
log.Warn("Removing non-existent discovery topic", "topic", topic)
|
||||
return
|
||||
}
|
||||
for _, list := range s.tickets[topic].buckets {
|
||||
for _, ref := range list {
|
||||
ref.t.refCnt--
|
||||
if ref.t.refCnt == 0 {
|
||||
delete(s.nodes, ref.t.node)
|
||||
delete(s.nodeLastReq, ref.t.node)
|
||||
}
|
||||
}
|
||||
}
|
||||
delete(s.tickets, topic)
|
||||
}
|
||||
|
||||
func (s *ticketStore) regTopicSet() []Topic {
|
||||
topics := make([]Topic, 0, len(s.tickets))
|
||||
for topic := range s.tickets {
|
||||
topics = append(topics, topic)
|
||||
}
|
||||
return topics
|
||||
}
|
||||
|
||||
// nextRegisterLookup returns the target of the next lookup for ticket collection.
|
||||
func (s *ticketStore) nextRegisterLookup() (lookupInfo, time.Duration) {
|
||||
// Queue up any new topics (or discarded ones), preserving iteration order
|
||||
for topic := range s.tickets {
|
||||
if _, ok := s.regSet[topic]; !ok {
|
||||
s.regQueue = append(s.regQueue, topic)
|
||||
s.regSet[topic] = struct{}{}
|
||||
}
|
||||
}
|
||||
// Iterate over the set of all topics and look up the next suitable one
|
||||
for len(s.regQueue) > 0 {
|
||||
// Fetch the next topic from the queue, and ensure it still exists
|
||||
topic := s.regQueue[0]
|
||||
s.regQueue = s.regQueue[1:]
|
||||
delete(s.regSet, topic)
|
||||
|
||||
if s.tickets[topic] == nil {
|
||||
continue
|
||||
}
|
||||
// If the topic needs more tickets, return it
|
||||
if s.tickets[topic].nextLookup < mclock.Now() {
|
||||
next, delay := s.radius[topic].nextTarget(false), 100*time.Millisecond
|
||||
log.Trace("Found discovery topic to register", "topic", topic, "target", next.target, "delay", delay)
|
||||
return next, delay
|
||||
}
|
||||
}
|
||||
// No registration topics found or all exhausted, sleep
|
||||
delay := 40 * time.Second
|
||||
log.Trace("No topic found to register", "delay", delay)
|
||||
return lookupInfo{}, delay
|
||||
}
|
||||
|
||||
func (s *ticketStore) nextSearchLookup(topic Topic) lookupInfo {
|
||||
tr := s.radius[topic]
|
||||
target := tr.nextTarget(tr.radiusLookupCnt >= searchForceQuery)
|
||||
if target.radiusLookup {
|
||||
tr.radiusLookupCnt++
|
||||
} else {
|
||||
tr.radiusLookupCnt = 0
|
||||
}
|
||||
return target
|
||||
}
|
||||
|
||||
func (s *ticketStore) addTicketRef(r ticketRef) {
|
||||
topic := r.t.topics[r.idx]
|
||||
tickets := s.tickets[topic]
|
||||
if tickets == nil {
|
||||
log.Warn("Adding ticket to non-existent topic", "topic", topic)
|
||||
return
|
||||
}
|
||||
bucket := timeBucket(r.t.regTime[r.idx] / mclock.AbsTime(ticketTimeBucketLen))
|
||||
tickets.buckets[bucket] = append(tickets.buckets[bucket], r)
|
||||
r.t.refCnt++
|
||||
|
||||
min := mclock.Now() - mclock.AbsTime(collectFrequency)*maxCollectDebt
|
||||
if tickets.nextLookup < min {
|
||||
tickets.nextLookup = min
|
||||
}
|
||||
tickets.nextLookup += mclock.AbsTime(collectFrequency)
|
||||
|
||||
//s.removeExcessTickets(topic)
|
||||
}
|
||||
|
||||
func (s *ticketStore) nextFilteredTicket() (*ticketRef, time.Duration) {
|
||||
now := mclock.Now()
|
||||
for {
|
||||
ticket, wait := s.nextRegisterableTicket()
|
||||
if ticket == nil {
|
||||
return ticket, wait
|
||||
}
|
||||
log.Trace("Found discovery ticket to register", "node", ticket.t.node, "serial", ticket.t.serial, "wait", wait)
|
||||
|
||||
regTime := now + mclock.AbsTime(wait)
|
||||
topic := ticket.t.topics[ticket.idx]
|
||||
if s.tickets[topic] != nil && regTime >= s.tickets[topic].nextReg {
|
||||
return ticket, wait
|
||||
}
|
||||
s.removeTicketRef(*ticket)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ticketStore) ticketRegistered(ref ticketRef) {
|
||||
now := mclock.Now()
|
||||
|
||||
topic := ref.t.topics[ref.idx]
|
||||
tickets := s.tickets[topic]
|
||||
min := now - mclock.AbsTime(registerFrequency)*maxRegisterDebt
|
||||
if min > tickets.nextReg {
|
||||
tickets.nextReg = min
|
||||
}
|
||||
tickets.nextReg += mclock.AbsTime(registerFrequency)
|
||||
s.tickets[topic] = tickets
|
||||
|
||||
s.removeTicketRef(ref)
|
||||
}
|
||||
|
||||
// nextRegisterableTicket returns the next ticket that can be used
|
||||
// to register.
|
||||
//
|
||||
// If the returned wait time <= zero the ticket can be used. For a positive
|
||||
// wait time, the caller should requery the next ticket later.
|
||||
//
|
||||
// A ticket can be returned more than once with <= zero wait time in case
|
||||
// the ticket contains multiple topics.
|
||||
func (s *ticketStore) nextRegisterableTicket() (*ticketRef, time.Duration) {
|
||||
now := mclock.Now()
|
||||
if s.nextTicketCached != nil {
|
||||
return s.nextTicketCached, time.Duration(s.nextTicketCached.topicRegTime() - now)
|
||||
}
|
||||
|
||||
for bucket := s.lastBucketFetched; ; bucket++ {
|
||||
var (
|
||||
empty = true // true if there are no tickets
|
||||
nextTicket ticketRef // uninitialized if this bucket is empty
|
||||
)
|
||||
for _, tickets := range s.tickets {
|
||||
//s.removeExcessTickets(topic)
|
||||
if len(tickets.buckets) != 0 {
|
||||
empty = false
|
||||
|
||||
list := tickets.buckets[bucket]
|
||||
for _, ref := range list {
|
||||
//debugLog(fmt.Sprintf(" nrt bucket = %d node = %x sn = %v wait = %v", bucket, ref.t.node.ID[:8], ref.t.serial, time.Duration(ref.topicRegTime()-now)))
|
||||
if nextTicket.t == nil || ref.topicRegTime() < nextTicket.topicRegTime() {
|
||||
nextTicket = ref
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if empty {
|
||||
return nil, 0
|
||||
}
|
||||
if nextTicket.t != nil {
|
||||
s.nextTicketCached = &nextTicket
|
||||
return &nextTicket, time.Duration(nextTicket.topicRegTime() - now)
|
||||
}
|
||||
s.lastBucketFetched = bucket
|
||||
}
|
||||
}
|
||||
|
||||
// removeTicket removes a ticket from the ticket store
|
||||
func (s *ticketStore) removeTicketRef(ref ticketRef) {
|
||||
log.Trace("Removing discovery ticket reference", "node", ref.t.node.ID, "serial", ref.t.serial)
|
||||
|
||||
// Make nextRegisterableTicket return the next available ticket.
|
||||
s.nextTicketCached = nil
|
||||
|
||||
topic := ref.topic()
|
||||
tickets := s.tickets[topic]
|
||||
|
||||
if tickets == nil {
|
||||
log.Trace("Removing tickets from unknown topic", "topic", topic)
|
||||
return
|
||||
}
|
||||
bucket := timeBucket(ref.t.regTime[ref.idx] / mclock.AbsTime(ticketTimeBucketLen))
|
||||
list := tickets.buckets[bucket]
|
||||
idx := -1
|
||||
for i, bt := range list {
|
||||
if bt.t == ref.t {
|
||||
idx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if idx == -1 {
|
||||
panic(nil)
|
||||
}
|
||||
list = append(list[:idx], list[idx+1:]...)
|
||||
if len(list) != 0 {
|
||||
tickets.buckets[bucket] = list
|
||||
} else {
|
||||
delete(tickets.buckets, bucket)
|
||||
}
|
||||
ref.t.refCnt--
|
||||
if ref.t.refCnt == 0 {
|
||||
delete(s.nodes, ref.t.node)
|
||||
delete(s.nodeLastReq, ref.t.node)
|
||||
}
|
||||
}
|
||||
|
||||
type lookupInfo struct {
|
||||
target common.Hash
|
||||
topic Topic
|
||||
radiusLookup bool
|
||||
}
|
||||
|
||||
type reqInfo struct {
|
||||
pingHash []byte
|
||||
lookup lookupInfo
|
||||
time mclock.AbsTime
|
||||
}
|
||||
|
||||
// returns -1 if not found
|
||||
func (t *ticket) findIdx(topic Topic) int {
|
||||
for i, tt := range t.topics {
|
||||
if tt == topic {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func (s *ticketStore) registerLookupDone(lookup lookupInfo, nodes []*Node, ping func(n *Node) []byte) {
|
||||
now := mclock.Now()
|
||||
for i, n := range nodes {
|
||||
if i == 0 || (binary.BigEndian.Uint64(n.sha[:8])^binary.BigEndian.Uint64(lookup.target[:8])) < s.radius[lookup.topic].minRadius {
|
||||
if lookup.radiusLookup {
|
||||
if lastReq, ok := s.nodeLastReq[n]; !ok || time.Duration(now-lastReq.time) > radiusTC {
|
||||
s.nodeLastReq[n] = reqInfo{pingHash: ping(n), lookup: lookup, time: now}
|
||||
}
|
||||
} else {
|
||||
if s.nodes[n] == nil {
|
||||
s.nodeLastReq[n] = reqInfo{pingHash: ping(n), lookup: lookup, time: now}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ticketStore) searchLookupDone(lookup lookupInfo, nodes []*Node, query func(n *Node, topic Topic) []byte) {
|
||||
now := mclock.Now()
|
||||
for i, n := range nodes {
|
||||
if i == 0 || (binary.BigEndian.Uint64(n.sha[:8])^binary.BigEndian.Uint64(lookup.target[:8])) < s.radius[lookup.topic].minRadius {
|
||||
if lookup.radiusLookup {
|
||||
if lastReq, ok := s.nodeLastReq[n]; !ok || time.Duration(now-lastReq.time) > radiusTC {
|
||||
s.nodeLastReq[n] = reqInfo{pingHash: nil, lookup: lookup, time: now}
|
||||
}
|
||||
} // else {
|
||||
if s.canQueryTopic(n, lookup.topic) {
|
||||
hash := query(n, lookup.topic)
|
||||
if hash != nil {
|
||||
s.addTopicQuery(common.BytesToHash(hash), n, lookup)
|
||||
}
|
||||
}
|
||||
//}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ticketStore) adjustWithTicket(now mclock.AbsTime, targetHash common.Hash, t *ticket) {
|
||||
for i, topic := range t.topics {
|
||||
if tt, ok := s.radius[topic]; ok {
|
||||
tt.adjustWithTicket(now, targetHash, ticketRef{t, i})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ticketStore) addTicket(localTime mclock.AbsTime, pingHash []byte, ticket *ticket) {
|
||||
log.Trace("Adding discovery ticket", "node", ticket.node.ID, "serial", ticket.serial)
|
||||
|
||||
lastReq, ok := s.nodeLastReq[ticket.node]
|
||||
if !(ok && bytes.Equal(pingHash, lastReq.pingHash)) {
|
||||
return
|
||||
}
|
||||
s.adjustWithTicket(localTime, lastReq.lookup.target, ticket)
|
||||
|
||||
if lastReq.lookup.radiusLookup || s.nodes[ticket.node] != nil {
|
||||
return
|
||||
}
|
||||
|
||||
topic := lastReq.lookup.topic
|
||||
topicIdx := ticket.findIdx(topic)
|
||||
if topicIdx == -1 {
|
||||
return
|
||||
}
|
||||
|
||||
bucket := timeBucket(localTime / mclock.AbsTime(ticketTimeBucketLen))
|
||||
if s.lastBucketFetched == 0 || bucket < s.lastBucketFetched {
|
||||
s.lastBucketFetched = bucket
|
||||
}
|
||||
|
||||
if _, ok := s.tickets[topic]; ok {
|
||||
wait := ticket.regTime[topicIdx] - localTime
|
||||
rnd := rand.ExpFloat64()
|
||||
if rnd > 10 {
|
||||
rnd = 10
|
||||
}
|
||||
if float64(wait) < float64(keepTicketConst)+float64(keepTicketExp)*rnd {
|
||||
// use the ticket to register this topic
|
||||
//fmt.Println("addTicket", ticket.node.ID[:8], ticket.node.addr().String(), ticket.serial, ticket.pong)
|
||||
s.addTicketRef(ticketRef{ticket, topicIdx})
|
||||
}
|
||||
}
|
||||
|
||||
if ticket.refCnt > 0 {
|
||||
s.nextTicketCached = nil
|
||||
s.nodes[ticket.node] = ticket
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ticketStore) canQueryTopic(node *Node, topic Topic) bool {
|
||||
qq := s.queriesSent[node]
|
||||
if qq != nil {
|
||||
now := mclock.Now()
|
||||
for _, sq := range qq {
|
||||
if sq.lookup.topic == topic && sq.sent > now-mclock.AbsTime(topicQueryResend) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *ticketStore) addTopicQuery(hash common.Hash, node *Node, lookup lookupInfo) {
|
||||
now := mclock.Now()
|
||||
qq := s.queriesSent[node]
|
||||
if qq == nil {
|
||||
qq = make(map[common.Hash]sentQuery)
|
||||
s.queriesSent[node] = qq
|
||||
}
|
||||
qq[hash] = sentQuery{sent: now, lookup: lookup}
|
||||
s.cleanupTopicQueries(now)
|
||||
}
|
||||
|
||||
func (s *ticketStore) cleanupTopicQueries(now mclock.AbsTime) {
|
||||
if s.nextTopicQueryCleanup > now {
|
||||
return
|
||||
}
|
||||
exp := now - mclock.AbsTime(topicQueryResend)
|
||||
for n, qq := range s.queriesSent {
|
||||
for h, q := range qq {
|
||||
if q.sent < exp {
|
||||
delete(qq, h)
|
||||
}
|
||||
}
|
||||
if len(qq) == 0 {
|
||||
delete(s.queriesSent, n)
|
||||
}
|
||||
}
|
||||
s.nextTopicQueryCleanup = now + mclock.AbsTime(topicQueryTimeout)
|
||||
}
|
||||
|
||||
func (s *ticketStore) gotTopicNodes(from *Node, hash common.Hash, nodes []rpcNode) (timeout bool) {
|
||||
now := mclock.Now()
|
||||
//fmt.Println("got", from.addr().String(), hash, len(nodes))
|
||||
qq := s.queriesSent[from]
|
||||
if qq == nil {
|
||||
return true
|
||||
}
|
||||
q, ok := qq[hash]
|
||||
if !ok || now > q.sent+mclock.AbsTime(topicQueryTimeout) {
|
||||
return true
|
||||
}
|
||||
inside := float64(0)
|
||||
if len(nodes) > 0 {
|
||||
inside = 1
|
||||
}
|
||||
s.radius[q.lookup.topic].adjust(now, q.lookup.target, from.sha, inside)
|
||||
chn := s.searchTopicMap[q.lookup.topic].foundChn
|
||||
if chn == nil {
|
||||
//fmt.Println("no channel")
|
||||
return false
|
||||
}
|
||||
for _, node := range nodes {
|
||||
ip := node.IP
|
||||
if ip.IsUnspecified() || ip.IsLoopback() {
|
||||
ip = from.IP
|
||||
}
|
||||
n := NewNode(node.ID, ip, node.UDP, node.TCP)
|
||||
select {
|
||||
case chn <- n:
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type topicRadius struct {
|
||||
topic Topic
|
||||
topicHashPrefix uint64
|
||||
radius, minRadius uint64
|
||||
buckets []topicRadiusBucket
|
||||
converged bool
|
||||
radiusLookupCnt int
|
||||
}
|
||||
|
||||
type topicRadiusEvent int
|
||||
|
||||
const (
|
||||
trOutside topicRadiusEvent = iota
|
||||
trInside
|
||||
trNoAdjust
|
||||
trCount
|
||||
)
|
||||
|
||||
type topicRadiusBucket struct {
|
||||
weights [trCount]float64
|
||||
lastTime mclock.AbsTime
|
||||
value float64
|
||||
lookupSent map[common.Hash]mclock.AbsTime
|
||||
}
|
||||
|
||||
func (b *topicRadiusBucket) update(now mclock.AbsTime) {
|
||||
if now == b.lastTime {
|
||||
return
|
||||
}
|
||||
exp := math.Exp(-float64(now-b.lastTime) / float64(radiusTC))
|
||||
for i, w := range b.weights {
|
||||
b.weights[i] = w * exp
|
||||
}
|
||||
b.lastTime = now
|
||||
|
||||
for target, tm := range b.lookupSent {
|
||||
if now-tm > mclock.AbsTime(respTimeout) {
|
||||
b.weights[trNoAdjust] += 1
|
||||
delete(b.lookupSent, target)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *topicRadiusBucket) adjust(now mclock.AbsTime, inside float64) {
|
||||
b.update(now)
|
||||
if inside <= 0 {
|
||||
b.weights[trOutside] += 1
|
||||
} else {
|
||||
if inside >= 1 {
|
||||
b.weights[trInside] += 1
|
||||
} else {
|
||||
b.weights[trInside] += inside
|
||||
b.weights[trOutside] += 1 - inside
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func newTopicRadius(t Topic) *topicRadius {
|
||||
topicHash := crypto.Keccak256Hash([]byte(t))
|
||||
topicHashPrefix := binary.BigEndian.Uint64(topicHash[0:8])
|
||||
|
||||
return &topicRadius{
|
||||
topic: t,
|
||||
topicHashPrefix: topicHashPrefix,
|
||||
radius: maxRadius,
|
||||
minRadius: maxRadius,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *topicRadius) getBucketIdx(addrHash common.Hash) int {
|
||||
prefix := binary.BigEndian.Uint64(addrHash[0:8])
|
||||
var log2 float64
|
||||
if prefix != r.topicHashPrefix {
|
||||
log2 = math.Log2(float64(prefix ^ r.topicHashPrefix))
|
||||
}
|
||||
bucket := int((64 - log2) * radiusBucketsPerBit)
|
||||
max := 64*radiusBucketsPerBit - 1
|
||||
if bucket > max {
|
||||
return max
|
||||
}
|
||||
if bucket < 0 {
|
||||
return 0
|
||||
}
|
||||
return bucket
|
||||
}
|
||||
|
||||
func (r *topicRadius) targetForBucket(bucket int) common.Hash {
|
||||
min := math.Pow(2, 64-float64(bucket+1)/radiusBucketsPerBit)
|
||||
max := math.Pow(2, 64-float64(bucket)/radiusBucketsPerBit)
|
||||
a := uint64(min)
|
||||
b := randUint64n(uint64(max - min))
|
||||
xor := a + b
|
||||
if xor < a {
|
||||
xor = ^uint64(0)
|
||||
}
|
||||
prefix := r.topicHashPrefix ^ xor
|
||||
var target common.Hash
|
||||
binary.BigEndian.PutUint64(target[0:8], prefix)
|
||||
globalRandRead(target[8:])
|
||||
return target
|
||||
}
|
||||
|
||||
// package rand provides a Read function in Go 1.6 and later, but
|
||||
// we can't use it yet because we still support Go 1.5.
|
||||
func globalRandRead(b []byte) {
|
||||
pos := 0
|
||||
val := 0
|
||||
for n := 0; n < len(b); n++ {
|
||||
if pos == 0 {
|
||||
val = rand.Int()
|
||||
pos = 7
|
||||
}
|
||||
b[n] = byte(val)
|
||||
val >>= 8
|
||||
pos--
|
||||
}
|
||||
}
|
||||
|
||||
func (r *topicRadius) chooseLookupBucket(a, b int) int {
|
||||
if a < 0 {
|
||||
a = 0
|
||||
}
|
||||
if a > b {
|
||||
return -1
|
||||
}
|
||||
c := 0
|
||||
for i := a; i <= b; i++ {
|
||||
if i >= len(r.buckets) || r.buckets[i].weights[trNoAdjust] < maxNoAdjust {
|
||||
c++
|
||||
}
|
||||
}
|
||||
if c == 0 {
|
||||
return -1
|
||||
}
|
||||
rnd := randUint(uint32(c))
|
||||
for i := a; i <= b; i++ {
|
||||
if i >= len(r.buckets) || r.buckets[i].weights[trNoAdjust] < maxNoAdjust {
|
||||
if rnd == 0 {
|
||||
return i
|
||||
}
|
||||
rnd--
|
||||
}
|
||||
}
|
||||
panic(nil) // should never happen
|
||||
}
|
||||
|
||||
func (r *topicRadius) needMoreLookups(a, b int, maxValue float64) bool {
|
||||
var max float64
|
||||
if a < 0 {
|
||||
a = 0
|
||||
}
|
||||
if b >= len(r.buckets) {
|
||||
b = len(r.buckets) - 1
|
||||
if r.buckets[b].value > max {
|
||||
max = r.buckets[b].value
|
||||
}
|
||||
}
|
||||
if b >= a {
|
||||
for i := a; i <= b; i++ {
|
||||
if r.buckets[i].value > max {
|
||||
max = r.buckets[i].value
|
||||
}
|
||||
}
|
||||
}
|
||||
return maxValue-max < minPeakSize
|
||||
}
|
||||
|
||||
func (r *topicRadius) recalcRadius() (radius uint64, radiusLookup int) {
|
||||
maxBucket := 0
|
||||
maxValue := float64(0)
|
||||
now := mclock.Now()
|
||||
v := float64(0)
|
||||
for i := range r.buckets {
|
||||
r.buckets[i].update(now)
|
||||
v += r.buckets[i].weights[trOutside] - r.buckets[i].weights[trInside]
|
||||
r.buckets[i].value = v
|
||||
//fmt.Printf("%v %v | ", v, r.buckets[i].weights[trNoAdjust])
|
||||
}
|
||||
//fmt.Println()
|
||||
slopeCross := -1
|
||||
for i, b := range r.buckets {
|
||||
v := b.value
|
||||
if v < float64(i)*minSlope {
|
||||
slopeCross = i
|
||||
break
|
||||
}
|
||||
if v > maxValue {
|
||||
maxValue = v
|
||||
maxBucket = i + 1
|
||||
}
|
||||
}
|
||||
|
||||
minRadBucket := len(r.buckets)
|
||||
sum := float64(0)
|
||||
for minRadBucket > 0 && sum < minRightSum {
|
||||
minRadBucket--
|
||||
b := r.buckets[minRadBucket]
|
||||
sum += b.weights[trInside] + b.weights[trOutside]
|
||||
}
|
||||
r.minRadius = uint64(math.Pow(2, 64-float64(minRadBucket)/radiusBucketsPerBit))
|
||||
|
||||
lookupLeft := -1
|
||||
if r.needMoreLookups(0, maxBucket-lookupWidth-1, maxValue) {
|
||||
lookupLeft = r.chooseLookupBucket(maxBucket-lookupWidth, maxBucket-1)
|
||||
}
|
||||
lookupRight := -1
|
||||
if slopeCross != maxBucket && (minRadBucket <= maxBucket || r.needMoreLookups(maxBucket+lookupWidth, len(r.buckets)-1, maxValue)) {
|
||||
for len(r.buckets) <= maxBucket+lookupWidth {
|
||||
r.buckets = append(r.buckets, topicRadiusBucket{lookupSent: make(map[common.Hash]mclock.AbsTime)})
|
||||
}
|
||||
lookupRight = r.chooseLookupBucket(maxBucket, maxBucket+lookupWidth-1)
|
||||
}
|
||||
if lookupLeft == -1 {
|
||||
radiusLookup = lookupRight
|
||||
} else {
|
||||
if lookupRight == -1 {
|
||||
radiusLookup = lookupLeft
|
||||
} else {
|
||||
if randUint(2) == 0 {
|
||||
radiusLookup = lookupLeft
|
||||
} else {
|
||||
radiusLookup = lookupRight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//fmt.Println("mb", maxBucket, "sc", slopeCross, "mrb", minRadBucket, "ll", lookupLeft, "lr", lookupRight, "mv", maxValue)
|
||||
|
||||
if radiusLookup == -1 {
|
||||
// no more radius lookups needed at the moment, return a radius
|
||||
r.converged = true
|
||||
rad := maxBucket
|
||||
if minRadBucket < rad {
|
||||
rad = minRadBucket
|
||||
}
|
||||
radius = ^uint64(0)
|
||||
if rad > 0 {
|
||||
radius = uint64(math.Pow(2, 64-float64(rad)/radiusBucketsPerBit))
|
||||
}
|
||||
r.radius = radius
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (r *topicRadius) nextTarget(forceRegular bool) lookupInfo {
|
||||
if !forceRegular {
|
||||
_, radiusLookup := r.recalcRadius()
|
||||
if radiusLookup != -1 {
|
||||
target := r.targetForBucket(radiusLookup)
|
||||
r.buckets[radiusLookup].lookupSent[target] = mclock.Now()
|
||||
return lookupInfo{target: target, topic: r.topic, radiusLookup: true}
|
||||
}
|
||||
}
|
||||
|
||||
radExt := r.radius / 2
|
||||
if radExt > maxRadius-r.radius {
|
||||
radExt = maxRadius - r.radius
|
||||
}
|
||||
rnd := randUint64n(r.radius) + randUint64n(2*radExt)
|
||||
if rnd > radExt {
|
||||
rnd -= radExt
|
||||
} else {
|
||||
rnd = radExt - rnd
|
||||
}
|
||||
|
||||
prefix := r.topicHashPrefix ^ rnd
|
||||
var target common.Hash
|
||||
binary.BigEndian.PutUint64(target[0:8], prefix)
|
||||
globalRandRead(target[8:])
|
||||
return lookupInfo{target: target, topic: r.topic, radiusLookup: false}
|
||||
}
|
||||
|
||||
func (r *topicRadius) adjustWithTicket(now mclock.AbsTime, targetHash common.Hash, t ticketRef) {
|
||||
wait := t.t.regTime[t.idx] - t.t.issueTime
|
||||
inside := float64(wait)/float64(targetWaitTime) - 0.5
|
||||
if inside > 1 {
|
||||
inside = 1
|
||||
}
|
||||
if inside < 0 {
|
||||
inside = 0
|
||||
}
|
||||
r.adjust(now, targetHash, t.t.node.sha, inside)
|
||||
}
|
||||
|
||||
func (r *topicRadius) adjust(now mclock.AbsTime, targetHash, addrHash common.Hash, inside float64) {
|
||||
bucket := r.getBucketIdx(addrHash)
|
||||
//fmt.Println("adjust", bucket, len(r.buckets), inside)
|
||||
if bucket >= len(r.buckets) {
|
||||
return
|
||||
}
|
||||
r.buckets[bucket].adjust(now, inside)
|
||||
delete(r.buckets[bucket].lookupSent, targetHash)
|
||||
}
|
||||
407
vendor/github.com/ethereum/go-ethereum/p2p/discv5/topic.go
generated
vendored
Normal file
407
vendor/github.com/ethereum/go-ethereum/p2p/discv5/topic.go
generated
vendored
Normal file
@@ -0,0 +1,407 @@
|
||||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package discv5
|
||||
|
||||
import (
|
||||
"container/heap"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/mclock"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
const (
|
||||
maxEntries = 10000
|
||||
maxEntriesPerTopic = 50
|
||||
|
||||
fallbackRegistrationExpiry = 1 * time.Hour
|
||||
)
|
||||
|
||||
type Topic string
|
||||
|
||||
type topicEntry struct {
|
||||
topic Topic
|
||||
fifoIdx uint64
|
||||
node *Node
|
||||
expire mclock.AbsTime
|
||||
}
|
||||
|
||||
type topicInfo struct {
|
||||
entries map[uint64]*topicEntry
|
||||
fifoHead, fifoTail uint64
|
||||
rqItem *topicRequestQueueItem
|
||||
wcl waitControlLoop
|
||||
}
|
||||
|
||||
// removes tail element from the fifo
|
||||
func (t *topicInfo) getFifoTail() *topicEntry {
|
||||
for t.entries[t.fifoTail] == nil {
|
||||
t.fifoTail++
|
||||
}
|
||||
tail := t.entries[t.fifoTail]
|
||||
t.fifoTail++
|
||||
return tail
|
||||
}
|
||||
|
||||
type nodeInfo struct {
|
||||
entries map[Topic]*topicEntry
|
||||
lastIssuedTicket, lastUsedTicket uint32
|
||||
// you can't register a ticket newer than lastUsedTicket before noRegUntil (absolute time)
|
||||
noRegUntil mclock.AbsTime
|
||||
}
|
||||
|
||||
type topicTable struct {
|
||||
db *nodeDB
|
||||
self *Node
|
||||
nodes map[*Node]*nodeInfo
|
||||
topics map[Topic]*topicInfo
|
||||
globalEntries uint64
|
||||
requested topicRequestQueue
|
||||
requestCnt uint64
|
||||
lastGarbageCollection mclock.AbsTime
|
||||
}
|
||||
|
||||
func newTopicTable(db *nodeDB, self *Node) *topicTable {
|
||||
if printTestImgLogs {
|
||||
fmt.Printf("*N %016x\n", self.sha[:8])
|
||||
}
|
||||
return &topicTable{
|
||||
db: db,
|
||||
nodes: make(map[*Node]*nodeInfo),
|
||||
topics: make(map[Topic]*topicInfo),
|
||||
self: self,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *topicTable) getOrNewTopic(topic Topic) *topicInfo {
|
||||
ti := t.topics[topic]
|
||||
if ti == nil {
|
||||
rqItem := &topicRequestQueueItem{
|
||||
topic: topic,
|
||||
priority: t.requestCnt,
|
||||
}
|
||||
ti = &topicInfo{
|
||||
entries: make(map[uint64]*topicEntry),
|
||||
rqItem: rqItem,
|
||||
}
|
||||
t.topics[topic] = ti
|
||||
heap.Push(&t.requested, rqItem)
|
||||
}
|
||||
return ti
|
||||
}
|
||||
|
||||
func (t *topicTable) checkDeleteTopic(topic Topic) {
|
||||
ti := t.topics[topic]
|
||||
if ti == nil {
|
||||
return
|
||||
}
|
||||
if len(ti.entries) == 0 && ti.wcl.hasMinimumWaitPeriod() {
|
||||
delete(t.topics, topic)
|
||||
heap.Remove(&t.requested, ti.rqItem.index)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *topicTable) getOrNewNode(node *Node) *nodeInfo {
|
||||
n := t.nodes[node]
|
||||
if n == nil {
|
||||
//fmt.Printf("newNode %016x %016x\n", t.self.sha[:8], node.sha[:8])
|
||||
var issued, used uint32
|
||||
if t.db != nil {
|
||||
issued, used = t.db.fetchTopicRegTickets(node.ID)
|
||||
}
|
||||
n = &nodeInfo{
|
||||
entries: make(map[Topic]*topicEntry),
|
||||
lastIssuedTicket: issued,
|
||||
lastUsedTicket: used,
|
||||
}
|
||||
t.nodes[node] = n
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (t *topicTable) checkDeleteNode(node *Node) {
|
||||
if n, ok := t.nodes[node]; ok && len(n.entries) == 0 && n.noRegUntil < mclock.Now() {
|
||||
//fmt.Printf("deleteNode %016x %016x\n", t.self.sha[:8], node.sha[:8])
|
||||
delete(t.nodes, node)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *topicTable) storeTicketCounters(node *Node) {
|
||||
n := t.getOrNewNode(node)
|
||||
if t.db != nil {
|
||||
t.db.updateTopicRegTickets(node.ID, n.lastIssuedTicket, n.lastUsedTicket)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *topicTable) getEntries(topic Topic) []*Node {
|
||||
t.collectGarbage()
|
||||
|
||||
te := t.topics[topic]
|
||||
if te == nil {
|
||||
return nil
|
||||
}
|
||||
nodes := make([]*Node, len(te.entries))
|
||||
i := 0
|
||||
for _, e := range te.entries {
|
||||
nodes[i] = e.node
|
||||
i++
|
||||
}
|
||||
t.requestCnt++
|
||||
t.requested.update(te.rqItem, t.requestCnt)
|
||||
return nodes
|
||||
}
|
||||
|
||||
func (t *topicTable) addEntry(node *Node, topic Topic) {
|
||||
n := t.getOrNewNode(node)
|
||||
// clear previous entries by the same node
|
||||
for _, e := range n.entries {
|
||||
t.deleteEntry(e)
|
||||
}
|
||||
// ***
|
||||
n = t.getOrNewNode(node)
|
||||
|
||||
tm := mclock.Now()
|
||||
te := t.getOrNewTopic(topic)
|
||||
|
||||
if len(te.entries) == maxEntriesPerTopic {
|
||||
t.deleteEntry(te.getFifoTail())
|
||||
}
|
||||
|
||||
if t.globalEntries == maxEntries {
|
||||
t.deleteEntry(t.leastRequested()) // not empty, no need to check for nil
|
||||
}
|
||||
|
||||
fifoIdx := te.fifoHead
|
||||
te.fifoHead++
|
||||
entry := &topicEntry{
|
||||
topic: topic,
|
||||
fifoIdx: fifoIdx,
|
||||
node: node,
|
||||
expire: tm + mclock.AbsTime(fallbackRegistrationExpiry),
|
||||
}
|
||||
if printTestImgLogs {
|
||||
fmt.Printf("*+ %d %v %016x %016x\n", tm/1000000, topic, t.self.sha[:8], node.sha[:8])
|
||||
}
|
||||
te.entries[fifoIdx] = entry
|
||||
n.entries[topic] = entry
|
||||
t.globalEntries++
|
||||
te.wcl.registered(tm)
|
||||
}
|
||||
|
||||
// removes least requested element from the fifo
|
||||
func (t *topicTable) leastRequested() *topicEntry {
|
||||
for t.requested.Len() > 0 && t.topics[t.requested[0].topic] == nil {
|
||||
heap.Pop(&t.requested)
|
||||
}
|
||||
if t.requested.Len() == 0 {
|
||||
return nil
|
||||
}
|
||||
return t.topics[t.requested[0].topic].getFifoTail()
|
||||
}
|
||||
|
||||
// entry should exist
|
||||
func (t *topicTable) deleteEntry(e *topicEntry) {
|
||||
if printTestImgLogs {
|
||||
fmt.Printf("*- %d %v %016x %016x\n", mclock.Now()/1000000, e.topic, t.self.sha[:8], e.node.sha[:8])
|
||||
}
|
||||
ne := t.nodes[e.node].entries
|
||||
delete(ne, e.topic)
|
||||
if len(ne) == 0 {
|
||||
t.checkDeleteNode(e.node)
|
||||
}
|
||||
te := t.topics[e.topic]
|
||||
delete(te.entries, e.fifoIdx)
|
||||
if len(te.entries) == 0 {
|
||||
t.checkDeleteTopic(e.topic)
|
||||
}
|
||||
t.globalEntries--
|
||||
}
|
||||
|
||||
// It is assumed that topics and waitPeriods have the same length.
|
||||
func (t *topicTable) useTicket(node *Node, serialNo uint32, topics []Topic, idx int, issueTime uint64, waitPeriods []uint32) (registered bool) {
|
||||
log.Trace("Using discovery ticket", "serial", serialNo, "topics", topics, "waits", waitPeriods)
|
||||
//fmt.Println("useTicket", serialNo, topics, waitPeriods)
|
||||
t.collectGarbage()
|
||||
|
||||
n := t.getOrNewNode(node)
|
||||
if serialNo < n.lastUsedTicket {
|
||||
return false
|
||||
}
|
||||
|
||||
tm := mclock.Now()
|
||||
if serialNo > n.lastUsedTicket && tm < n.noRegUntil {
|
||||
return false
|
||||
}
|
||||
if serialNo != n.lastUsedTicket {
|
||||
n.lastUsedTicket = serialNo
|
||||
n.noRegUntil = tm + mclock.AbsTime(noRegTimeout())
|
||||
t.storeTicketCounters(node)
|
||||
}
|
||||
|
||||
currTime := uint64(tm / mclock.AbsTime(time.Second))
|
||||
regTime := issueTime + uint64(waitPeriods[idx])
|
||||
relTime := int64(currTime - regTime)
|
||||
if relTime >= -1 && relTime <= regTimeWindow+1 { // give clients a little security margin on both ends
|
||||
if e := n.entries[topics[idx]]; e == nil {
|
||||
t.addEntry(node, topics[idx])
|
||||
} else {
|
||||
// if there is an active entry, don't move to the front of the FIFO but prolong expire time
|
||||
e.expire = tm + mclock.AbsTime(fallbackRegistrationExpiry)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *topicTable) getTicket(node *Node, topics []Topic) *ticket {
|
||||
t.collectGarbage()
|
||||
|
||||
now := mclock.Now()
|
||||
n := t.getOrNewNode(node)
|
||||
n.lastIssuedTicket++
|
||||
t.storeTicketCounters(node)
|
||||
|
||||
tic := &ticket{
|
||||
issueTime: now,
|
||||
topics: topics,
|
||||
serial: n.lastIssuedTicket,
|
||||
regTime: make([]mclock.AbsTime, len(topics)),
|
||||
}
|
||||
for i, topic := range topics {
|
||||
var waitPeriod time.Duration
|
||||
if topic := t.topics[topic]; topic != nil {
|
||||
waitPeriod = topic.wcl.waitPeriod
|
||||
} else {
|
||||
waitPeriod = minWaitPeriod
|
||||
}
|
||||
|
||||
tic.regTime[i] = now + mclock.AbsTime(waitPeriod)
|
||||
}
|
||||
return tic
|
||||
}
|
||||
|
||||
const gcInterval = time.Minute
|
||||
|
||||
func (t *topicTable) collectGarbage() {
|
||||
tm := mclock.Now()
|
||||
if time.Duration(tm-t.lastGarbageCollection) < gcInterval {
|
||||
return
|
||||
}
|
||||
t.lastGarbageCollection = tm
|
||||
|
||||
for node, n := range t.nodes {
|
||||
for _, e := range n.entries {
|
||||
if e.expire <= tm {
|
||||
t.deleteEntry(e)
|
||||
}
|
||||
}
|
||||
|
||||
t.checkDeleteNode(node)
|
||||
}
|
||||
|
||||
for topic := range t.topics {
|
||||
t.checkDeleteTopic(topic)
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
minWaitPeriod = time.Minute
|
||||
regTimeWindow = 10 // seconds
|
||||
avgnoRegTimeout = time.Minute * 10
|
||||
// target average interval between two incoming ad requests
|
||||
wcTargetRegInterval = time.Minute * 10 / maxEntriesPerTopic
|
||||
//
|
||||
wcTimeConst = time.Minute * 10
|
||||
)
|
||||
|
||||
// initialization is not required, will set to minWaitPeriod at first registration
|
||||
type waitControlLoop struct {
|
||||
lastIncoming mclock.AbsTime
|
||||
waitPeriod time.Duration
|
||||
}
|
||||
|
||||
func (w *waitControlLoop) registered(tm mclock.AbsTime) {
|
||||
w.waitPeriod = w.nextWaitPeriod(tm)
|
||||
w.lastIncoming = tm
|
||||
}
|
||||
|
||||
func (w *waitControlLoop) nextWaitPeriod(tm mclock.AbsTime) time.Duration {
|
||||
period := tm - w.lastIncoming
|
||||
wp := time.Duration(float64(w.waitPeriod) * math.Exp((float64(wcTargetRegInterval)-float64(period))/float64(wcTimeConst)))
|
||||
if wp < minWaitPeriod {
|
||||
wp = minWaitPeriod
|
||||
}
|
||||
return wp
|
||||
}
|
||||
|
||||
func (w *waitControlLoop) hasMinimumWaitPeriod() bool {
|
||||
return w.nextWaitPeriod(mclock.Now()) == minWaitPeriod
|
||||
}
|
||||
|
||||
func noRegTimeout() time.Duration {
|
||||
e := rand.ExpFloat64()
|
||||
if e > 100 {
|
||||
e = 100
|
||||
}
|
||||
return time.Duration(float64(avgnoRegTimeout) * e)
|
||||
}
|
||||
|
||||
type topicRequestQueueItem struct {
|
||||
topic Topic
|
||||
priority uint64
|
||||
index int
|
||||
}
|
||||
|
||||
// A topicRequestQueue implements heap.Interface and holds topicRequestQueueItems.
|
||||
type topicRequestQueue []*topicRequestQueueItem
|
||||
|
||||
func (tq topicRequestQueue) Len() int { return len(tq) }
|
||||
|
||||
func (tq topicRequestQueue) Less(i, j int) bool {
|
||||
return tq[i].priority < tq[j].priority
|
||||
}
|
||||
|
||||
func (tq topicRequestQueue) Swap(i, j int) {
|
||||
tq[i], tq[j] = tq[j], tq[i]
|
||||
tq[i].index = i
|
||||
tq[j].index = j
|
||||
}
|
||||
|
||||
func (tq *topicRequestQueue) Push(x interface{}) {
|
||||
n := len(*tq)
|
||||
item := x.(*topicRequestQueueItem)
|
||||
item.index = n
|
||||
*tq = append(*tq, item)
|
||||
}
|
||||
|
||||
func (tq *topicRequestQueue) Pop() interface{} {
|
||||
old := *tq
|
||||
n := len(old)
|
||||
item := old[n-1]
|
||||
item.index = -1
|
||||
*tq = old[0 : n-1]
|
||||
return item
|
||||
}
|
||||
|
||||
func (tq *topicRequestQueue) update(item *topicRequestQueueItem, priority uint64) {
|
||||
item.priority = priority
|
||||
heap.Fix(tq, item.index)
|
||||
}
|
||||
429
vendor/github.com/ethereum/go-ethereum/p2p/discv5/udp.go
generated
vendored
Normal file
429
vendor/github.com/ethereum/go-ethereum/p2p/discv5/udp.go
generated
vendored
Normal file
@@ -0,0 +1,429 @@
|
||||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package discv5
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ecdsa"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/p2p/netutil"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
const Version = 4
|
||||
|
||||
// Errors
|
||||
var (
|
||||
errPacketTooSmall = errors.New("too small")
|
||||
errBadPrefix = errors.New("bad prefix")
|
||||
)
|
||||
|
||||
// Timeouts
|
||||
const (
|
||||
respTimeout = 500 * time.Millisecond
|
||||
expiration = 20 * time.Second
|
||||
)
|
||||
|
||||
// RPC request structures
|
||||
type (
|
||||
ping struct {
|
||||
Version uint
|
||||
From, To rpcEndpoint
|
||||
Expiration uint64
|
||||
|
||||
// v5
|
||||
Topics []Topic
|
||||
|
||||
// Ignore additional fields (for forward compatibility).
|
||||
Rest []rlp.RawValue `rlp:"tail"`
|
||||
}
|
||||
|
||||
// pong is the reply to ping.
|
||||
pong struct {
|
||||
// This field should mirror the UDP envelope address
|
||||
// of the ping packet, which provides a way to discover the
|
||||
// the external address (after NAT).
|
||||
To rpcEndpoint
|
||||
|
||||
ReplyTok []byte // This contains the hash of the ping packet.
|
||||
Expiration uint64 // Absolute timestamp at which the packet becomes invalid.
|
||||
|
||||
// v5
|
||||
TopicHash common.Hash
|
||||
TicketSerial uint32
|
||||
WaitPeriods []uint32
|
||||
|
||||
// Ignore additional fields (for forward compatibility).
|
||||
Rest []rlp.RawValue `rlp:"tail"`
|
||||
}
|
||||
|
||||
// findnode is a query for nodes close to the given target.
|
||||
findnode struct {
|
||||
Target NodeID // doesn't need to be an actual public key
|
||||
Expiration uint64
|
||||
// Ignore additional fields (for forward compatibility).
|
||||
Rest []rlp.RawValue `rlp:"tail"`
|
||||
}
|
||||
|
||||
// findnode is a query for nodes close to the given target.
|
||||
findnodeHash struct {
|
||||
Target common.Hash
|
||||
Expiration uint64
|
||||
// Ignore additional fields (for forward compatibility).
|
||||
Rest []rlp.RawValue `rlp:"tail"`
|
||||
}
|
||||
|
||||
// reply to findnode
|
||||
neighbors struct {
|
||||
Nodes []rpcNode
|
||||
Expiration uint64
|
||||
// Ignore additional fields (for forward compatibility).
|
||||
Rest []rlp.RawValue `rlp:"tail"`
|
||||
}
|
||||
|
||||
topicRegister struct {
|
||||
Topics []Topic
|
||||
Idx uint
|
||||
Pong []byte
|
||||
}
|
||||
|
||||
topicQuery struct {
|
||||
Topic Topic
|
||||
Expiration uint64
|
||||
}
|
||||
|
||||
// reply to topicQuery
|
||||
topicNodes struct {
|
||||
Echo common.Hash
|
||||
Nodes []rpcNode
|
||||
}
|
||||
|
||||
rpcNode struct {
|
||||
IP net.IP // len 4 for IPv4 or 16 for IPv6
|
||||
UDP uint16 // for discovery protocol
|
||||
TCP uint16 // for RLPx protocol
|
||||
ID NodeID
|
||||
}
|
||||
|
||||
rpcEndpoint struct {
|
||||
IP net.IP // len 4 for IPv4 or 16 for IPv6
|
||||
UDP uint16 // for discovery protocol
|
||||
TCP uint16 // for RLPx protocol
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
versionPrefix = []byte("temporary discovery v5")
|
||||
versionPrefixSize = len(versionPrefix)
|
||||
sigSize = 520 / 8
|
||||
headSize = versionPrefixSize + sigSize // space of packet frame data
|
||||
)
|
||||
|
||||
// Neighbors replies are sent across multiple packets to
|
||||
// stay below the 1280 byte limit. We compute the maximum number
|
||||
// of entries by stuffing a packet until it grows too large.
|
||||
var maxNeighbors = func() int {
|
||||
p := neighbors{Expiration: ^uint64(0)}
|
||||
maxSizeNode := rpcNode{IP: make(net.IP, 16), UDP: ^uint16(0), TCP: ^uint16(0)}
|
||||
for n := 0; ; n++ {
|
||||
p.Nodes = append(p.Nodes, maxSizeNode)
|
||||
size, _, err := rlp.EncodeToReader(p)
|
||||
if err != nil {
|
||||
// If this ever happens, it will be caught by the unit tests.
|
||||
panic("cannot encode: " + err.Error())
|
||||
}
|
||||
if headSize+size+1 >= 1280 {
|
||||
return n
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
var maxTopicNodes = func() int {
|
||||
p := topicNodes{}
|
||||
maxSizeNode := rpcNode{IP: make(net.IP, 16), UDP: ^uint16(0), TCP: ^uint16(0)}
|
||||
for n := 0; ; n++ {
|
||||
p.Nodes = append(p.Nodes, maxSizeNode)
|
||||
size, _, err := rlp.EncodeToReader(p)
|
||||
if err != nil {
|
||||
// If this ever happens, it will be caught by the unit tests.
|
||||
panic("cannot encode: " + err.Error())
|
||||
}
|
||||
if headSize+size+1 >= 1280 {
|
||||
return n
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
func makeEndpoint(addr *net.UDPAddr, tcpPort uint16) rpcEndpoint {
|
||||
ip := addr.IP.To4()
|
||||
if ip == nil {
|
||||
ip = addr.IP.To16()
|
||||
}
|
||||
return rpcEndpoint{IP: ip, UDP: uint16(addr.Port), TCP: tcpPort}
|
||||
}
|
||||
|
||||
func nodeFromRPC(sender *net.UDPAddr, rn rpcNode) (*Node, error) {
|
||||
if err := netutil.CheckRelayIP(sender.IP, rn.IP); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
n := NewNode(rn.ID, rn.IP, rn.UDP, rn.TCP)
|
||||
err := n.validateComplete()
|
||||
return n, err
|
||||
}
|
||||
|
||||
func nodeToRPC(n *Node) rpcNode {
|
||||
return rpcNode{ID: n.ID, IP: n.IP, UDP: n.UDP, TCP: n.TCP}
|
||||
}
|
||||
|
||||
type ingressPacket struct {
|
||||
remoteID NodeID
|
||||
remoteAddr *net.UDPAddr
|
||||
ev nodeEvent
|
||||
hash []byte
|
||||
data interface{} // one of the RPC structs
|
||||
rawData []byte
|
||||
}
|
||||
|
||||
type conn interface {
|
||||
ReadFromUDP(b []byte) (n int, addr *net.UDPAddr, err error)
|
||||
WriteToUDP(b []byte, addr *net.UDPAddr) (n int, err error)
|
||||
Close() error
|
||||
LocalAddr() net.Addr
|
||||
}
|
||||
|
||||
// udp implements the RPC protocol.
|
||||
type udp struct {
|
||||
conn conn
|
||||
priv *ecdsa.PrivateKey
|
||||
ourEndpoint rpcEndpoint
|
||||
net *Network
|
||||
}
|
||||
|
||||
// ListenUDP returns a new table that listens for UDP packets on laddr.
|
||||
func ListenUDP(priv *ecdsa.PrivateKey, conn conn, nodeDBPath string, netrestrict *netutil.Netlist) (*Network, error) {
|
||||
realaddr := conn.LocalAddr().(*net.UDPAddr)
|
||||
transport, err := listenUDP(priv, conn, realaddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
net, err := newNetwork(transport, priv.PublicKey, nodeDBPath, netrestrict)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Info("UDP listener up", "net", net.tab.self)
|
||||
transport.net = net
|
||||
go transport.readLoop()
|
||||
return net, nil
|
||||
}
|
||||
|
||||
func listenUDP(priv *ecdsa.PrivateKey, conn conn, realaddr *net.UDPAddr) (*udp, error) {
|
||||
return &udp{conn: conn, priv: priv, ourEndpoint: makeEndpoint(realaddr, uint16(realaddr.Port))}, nil
|
||||
}
|
||||
|
||||
func (t *udp) localAddr() *net.UDPAddr {
|
||||
return t.conn.LocalAddr().(*net.UDPAddr)
|
||||
}
|
||||
|
||||
func (t *udp) Close() {
|
||||
t.conn.Close()
|
||||
}
|
||||
|
||||
func (t *udp) send(remote *Node, ptype nodeEvent, data interface{}) (hash []byte) {
|
||||
hash, _ = t.sendPacket(remote.ID, remote.addr(), byte(ptype), data)
|
||||
return hash
|
||||
}
|
||||
|
||||
func (t *udp) sendPing(remote *Node, toaddr *net.UDPAddr, topics []Topic) (hash []byte) {
|
||||
hash, _ = t.sendPacket(remote.ID, toaddr, byte(pingPacket), ping{
|
||||
Version: Version,
|
||||
From: t.ourEndpoint,
|
||||
To: makeEndpoint(toaddr, uint16(toaddr.Port)), // TODO: maybe use known TCP port from DB
|
||||
Expiration: uint64(time.Now().Add(expiration).Unix()),
|
||||
Topics: topics,
|
||||
})
|
||||
return hash
|
||||
}
|
||||
|
||||
func (t *udp) sendNeighbours(remote *Node, results []*Node) {
|
||||
// Send neighbors in chunks with at most maxNeighbors per packet
|
||||
// to stay below the 1280 byte limit.
|
||||
p := neighbors{Expiration: uint64(time.Now().Add(expiration).Unix())}
|
||||
for i, result := range results {
|
||||
p.Nodes = append(p.Nodes, nodeToRPC(result))
|
||||
if len(p.Nodes) == maxNeighbors || i == len(results)-1 {
|
||||
t.sendPacket(remote.ID, remote.addr(), byte(neighborsPacket), p)
|
||||
p.Nodes = p.Nodes[:0]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *udp) sendFindnodeHash(remote *Node, target common.Hash) {
|
||||
t.sendPacket(remote.ID, remote.addr(), byte(findnodeHashPacket), findnodeHash{
|
||||
Target: target,
|
||||
Expiration: uint64(time.Now().Add(expiration).Unix()),
|
||||
})
|
||||
}
|
||||
|
||||
func (t *udp) sendTopicRegister(remote *Node, topics []Topic, idx int, pong []byte) {
|
||||
t.sendPacket(remote.ID, remote.addr(), byte(topicRegisterPacket), topicRegister{
|
||||
Topics: topics,
|
||||
Idx: uint(idx),
|
||||
Pong: pong,
|
||||
})
|
||||
}
|
||||
|
||||
func (t *udp) sendTopicNodes(remote *Node, queryHash common.Hash, nodes []*Node) {
|
||||
p := topicNodes{Echo: queryHash}
|
||||
var sent bool
|
||||
for _, result := range nodes {
|
||||
if result.IP.Equal(t.net.tab.self.IP) || netutil.CheckRelayIP(remote.IP, result.IP) == nil {
|
||||
p.Nodes = append(p.Nodes, nodeToRPC(result))
|
||||
}
|
||||
if len(p.Nodes) == maxTopicNodes {
|
||||
t.sendPacket(remote.ID, remote.addr(), byte(topicNodesPacket), p)
|
||||
p.Nodes = p.Nodes[:0]
|
||||
sent = true
|
||||
}
|
||||
}
|
||||
if !sent || len(p.Nodes) > 0 {
|
||||
t.sendPacket(remote.ID, remote.addr(), byte(topicNodesPacket), p)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *udp) sendPacket(toid NodeID, toaddr *net.UDPAddr, ptype byte, req interface{}) (hash []byte, err error) {
|
||||
//fmt.Println("sendPacket", nodeEvent(ptype), toaddr.String(), toid.String())
|
||||
packet, hash, err := encodePacket(t.priv, ptype, req)
|
||||
if err != nil {
|
||||
//fmt.Println(err)
|
||||
return hash, err
|
||||
}
|
||||
log.Trace(fmt.Sprintf(">>> %v to %x@%v", nodeEvent(ptype), toid[:8], toaddr))
|
||||
if nbytes, err := t.conn.WriteToUDP(packet, toaddr); err != nil {
|
||||
log.Trace(fmt.Sprint("UDP send failed:", err))
|
||||
} else {
|
||||
egressTrafficMeter.Mark(int64(nbytes))
|
||||
}
|
||||
//fmt.Println(err)
|
||||
return hash, err
|
||||
}
|
||||
|
||||
// zeroed padding space for encodePacket.
|
||||
var headSpace = make([]byte, headSize)
|
||||
|
||||
func encodePacket(priv *ecdsa.PrivateKey, ptype byte, req interface{}) (p, hash []byte, err error) {
|
||||
b := new(bytes.Buffer)
|
||||
b.Write(headSpace)
|
||||
b.WriteByte(ptype)
|
||||
if err := rlp.Encode(b, req); err != nil {
|
||||
log.Error(fmt.Sprint("error encoding packet:", err))
|
||||
return nil, nil, err
|
||||
}
|
||||
packet := b.Bytes()
|
||||
sig, err := crypto.Sign(crypto.Keccak256(packet[headSize:]), priv)
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprint("could not sign packet:", err))
|
||||
return nil, nil, err
|
||||
}
|
||||
copy(packet, versionPrefix)
|
||||
copy(packet[versionPrefixSize:], sig)
|
||||
hash = crypto.Keccak256(packet[versionPrefixSize:])
|
||||
return packet, hash, nil
|
||||
}
|
||||
|
||||
// readLoop runs in its own goroutine. it injects ingress UDP packets
|
||||
// into the network loop.
|
||||
func (t *udp) readLoop() {
|
||||
defer t.conn.Close()
|
||||
// Discovery packets are defined to be no larger than 1280 bytes.
|
||||
// Packets larger than this size will be cut at the end and treated
|
||||
// as invalid because their hash won't match.
|
||||
buf := make([]byte, 1280)
|
||||
for {
|
||||
nbytes, from, err := t.conn.ReadFromUDP(buf)
|
||||
ingressTrafficMeter.Mark(int64(nbytes))
|
||||
if netutil.IsTemporaryError(err) {
|
||||
// Ignore temporary read errors.
|
||||
log.Debug(fmt.Sprintf("Temporary read error: %v", err))
|
||||
continue
|
||||
} else if err != nil {
|
||||
// Shut down the loop for permament errors.
|
||||
log.Debug(fmt.Sprintf("Read error: %v", err))
|
||||
return
|
||||
}
|
||||
t.handlePacket(from, buf[:nbytes])
|
||||
}
|
||||
}
|
||||
|
||||
func (t *udp) handlePacket(from *net.UDPAddr, buf []byte) error {
|
||||
pkt := ingressPacket{remoteAddr: from}
|
||||
if err := decodePacket(buf, &pkt); err != nil {
|
||||
log.Debug(fmt.Sprintf("Bad packet from %v: %v", from, err))
|
||||
//fmt.Println("bad packet", err)
|
||||
return err
|
||||
}
|
||||
t.net.reqReadPacket(pkt)
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodePacket(buffer []byte, pkt *ingressPacket) error {
|
||||
if len(buffer) < headSize+1 {
|
||||
return errPacketTooSmall
|
||||
}
|
||||
buf := make([]byte, len(buffer))
|
||||
copy(buf, buffer)
|
||||
prefix, sig, sigdata := buf[:versionPrefixSize], buf[versionPrefixSize:headSize], buf[headSize:]
|
||||
if !bytes.Equal(prefix, versionPrefix) {
|
||||
return errBadPrefix
|
||||
}
|
||||
fromID, err := recoverNodeID(crypto.Keccak256(buf[headSize:]), sig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pkt.rawData = buf
|
||||
pkt.hash = crypto.Keccak256(buf[versionPrefixSize:])
|
||||
pkt.remoteID = fromID
|
||||
switch pkt.ev = nodeEvent(sigdata[0]); pkt.ev {
|
||||
case pingPacket:
|
||||
pkt.data = new(ping)
|
||||
case pongPacket:
|
||||
pkt.data = new(pong)
|
||||
case findnodePacket:
|
||||
pkt.data = new(findnode)
|
||||
case neighborsPacket:
|
||||
pkt.data = new(neighbors)
|
||||
case findnodeHashPacket:
|
||||
pkt.data = new(findnodeHash)
|
||||
case topicRegisterPacket:
|
||||
pkt.data = new(topicRegister)
|
||||
case topicQueryPacket:
|
||||
pkt.data = new(topicQuery)
|
||||
case topicNodesPacket:
|
||||
pkt.data = new(topicNodes)
|
||||
default:
|
||||
return fmt.Errorf("unknown packet type: %d", sigdata[0])
|
||||
}
|
||||
s := rlp.NewStream(bytes.NewReader(sigdata[1:]), 0)
|
||||
err = s.Decode(pkt.data)
|
||||
return err
|
||||
}
|
||||
393
vendor/github.com/ethereum/go-ethereum/p2p/dnsdisc/client.go
generated
vendored
Normal file
393
vendor/github.com/ethereum/go-ethereum/p2p/dnsdisc/client.go
generated
vendored
Normal file
@@ -0,0 +1,393 @@
|
||||
// Copyright 2019 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package dnsdisc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/mclock"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethereum/go-ethereum/p2p/enr"
|
||||
lru "github.com/hashicorp/golang-lru"
|
||||
"golang.org/x/sync/singleflight"
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
||||
// Client discovers nodes by querying DNS servers.
|
||||
type Client struct {
|
||||
cfg Config
|
||||
clock mclock.Clock
|
||||
entries *lru.Cache
|
||||
ratelimit *rate.Limiter
|
||||
singleflight singleflight.Group
|
||||
}
|
||||
|
||||
// Config holds configuration options for the client.
|
||||
type Config struct {
|
||||
Timeout time.Duration // timeout used for DNS lookups (default 5s)
|
||||
RecheckInterval time.Duration // time between tree root update checks (default 30min)
|
||||
CacheLimit int // maximum number of cached records (default 1000)
|
||||
RateLimit float64 // maximum DNS requests / second (default 3)
|
||||
ValidSchemes enr.IdentityScheme // acceptable ENR identity schemes (default enode.ValidSchemes)
|
||||
Resolver Resolver // the DNS resolver to use (defaults to system DNS)
|
||||
Logger log.Logger // destination of client log messages (defaults to root logger)
|
||||
}
|
||||
|
||||
// Resolver is a DNS resolver that can query TXT records.
|
||||
type Resolver interface {
|
||||
LookupTXT(ctx context.Context, domain string) ([]string, error)
|
||||
}
|
||||
|
||||
func (cfg Config) withDefaults() Config {
|
||||
const (
|
||||
defaultTimeout = 5 * time.Second
|
||||
defaultRecheck = 30 * time.Minute
|
||||
defaultRateLimit = 3
|
||||
defaultCache = 1000
|
||||
)
|
||||
if cfg.Timeout == 0 {
|
||||
cfg.Timeout = defaultTimeout
|
||||
}
|
||||
if cfg.RecheckInterval == 0 {
|
||||
cfg.RecheckInterval = defaultRecheck
|
||||
}
|
||||
if cfg.CacheLimit == 0 {
|
||||
cfg.CacheLimit = defaultCache
|
||||
}
|
||||
if cfg.RateLimit == 0 {
|
||||
cfg.RateLimit = defaultRateLimit
|
||||
}
|
||||
if cfg.ValidSchemes == nil {
|
||||
cfg.ValidSchemes = enode.ValidSchemes
|
||||
}
|
||||
if cfg.Resolver == nil {
|
||||
cfg.Resolver = new(net.Resolver)
|
||||
}
|
||||
if cfg.Logger == nil {
|
||||
cfg.Logger = log.Root()
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
// NewClient creates a client.
|
||||
func NewClient(cfg Config) *Client {
|
||||
cfg = cfg.withDefaults()
|
||||
cache, err := lru.New(cfg.CacheLimit)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
rlimit := rate.NewLimiter(rate.Limit(cfg.RateLimit), 10)
|
||||
return &Client{
|
||||
cfg: cfg,
|
||||
entries: cache,
|
||||
clock: mclock.System{},
|
||||
ratelimit: rlimit,
|
||||
}
|
||||
}
|
||||
|
||||
// SyncTree downloads the entire node tree at the given URL.
|
||||
func (c *Client) SyncTree(url string) (*Tree, error) {
|
||||
le, err := parseLink(url)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid enrtree URL: %v", err)
|
||||
}
|
||||
ct := newClientTree(c, new(linkCache), le)
|
||||
t := &Tree{entries: make(map[string]entry)}
|
||||
if err := ct.syncAll(t.entries); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t.root = ct.root
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// NewIterator creates an iterator that visits all nodes at the
|
||||
// given tree URLs.
|
||||
func (c *Client) NewIterator(urls ...string) (enode.Iterator, error) {
|
||||
it := c.newRandomIterator()
|
||||
for _, url := range urls {
|
||||
if err := it.addTree(url); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return it, nil
|
||||
}
|
||||
|
||||
// resolveRoot retrieves a root entry via DNS.
|
||||
func (c *Client) resolveRoot(ctx context.Context, loc *linkEntry) (rootEntry, error) {
|
||||
e, err, _ := c.singleflight.Do(loc.str, func() (interface{}, error) {
|
||||
txts, err := c.cfg.Resolver.LookupTXT(ctx, loc.domain)
|
||||
c.cfg.Logger.Trace("Updating DNS discovery root", "tree", loc.domain, "err", err)
|
||||
if err != nil {
|
||||
return rootEntry{}, err
|
||||
}
|
||||
for _, txt := range txts {
|
||||
if strings.HasPrefix(txt, rootPrefix) {
|
||||
return parseAndVerifyRoot(txt, loc)
|
||||
}
|
||||
}
|
||||
return rootEntry{}, nameError{loc.domain, errNoRoot}
|
||||
})
|
||||
return e.(rootEntry), err
|
||||
}
|
||||
|
||||
func parseAndVerifyRoot(txt string, loc *linkEntry) (rootEntry, error) {
|
||||
e, err := parseRoot(txt)
|
||||
if err != nil {
|
||||
return e, err
|
||||
}
|
||||
if !e.verifySignature(loc.pubkey) {
|
||||
return e, entryError{typ: "root", err: errInvalidSig}
|
||||
}
|
||||
return e, nil
|
||||
}
|
||||
|
||||
// resolveEntry retrieves an entry from the cache or fetches it from the network
|
||||
// if it isn't cached.
|
||||
func (c *Client) resolveEntry(ctx context.Context, domain, hash string) (entry, error) {
|
||||
// The rate limit always applies, even when the result might be cached. This is
|
||||
// important because it avoids hot-spinning in consumers of node iterators created on
|
||||
// this client.
|
||||
if err := c.ratelimit.Wait(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cacheKey := truncateHash(hash)
|
||||
if e, ok := c.entries.Get(cacheKey); ok {
|
||||
return e.(entry), nil
|
||||
}
|
||||
|
||||
ei, err, _ := c.singleflight.Do(cacheKey, func() (interface{}, error) {
|
||||
e, err := c.doResolveEntry(ctx, domain, hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.entries.Add(cacheKey, e)
|
||||
return e, nil
|
||||
})
|
||||
e, _ := ei.(entry)
|
||||
return e, err
|
||||
}
|
||||
|
||||
// doResolveEntry fetches an entry via DNS.
|
||||
func (c *Client) doResolveEntry(ctx context.Context, domain, hash string) (entry, error) {
|
||||
wantHash, err := b32format.DecodeString(hash)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid base32 hash")
|
||||
}
|
||||
name := hash + "." + domain
|
||||
txts, err := c.cfg.Resolver.LookupTXT(ctx, hash+"."+domain)
|
||||
c.cfg.Logger.Trace("DNS discovery lookup", "name", name, "err", err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, txt := range txts {
|
||||
e, err := parseEntry(txt, c.cfg.ValidSchemes)
|
||||
if errors.Is(err, errUnknownEntry) {
|
||||
continue
|
||||
}
|
||||
if !bytes.HasPrefix(crypto.Keccak256([]byte(txt)), wantHash) {
|
||||
err = nameError{name, errHashMismatch}
|
||||
} else if err != nil {
|
||||
err = nameError{name, err}
|
||||
}
|
||||
return e, err
|
||||
}
|
||||
return nil, nameError{name, errNoEntry}
|
||||
}
|
||||
|
||||
// randomIterator traverses a set of trees and returns nodes found in them.
|
||||
type randomIterator struct {
|
||||
cur *enode.Node
|
||||
ctx context.Context
|
||||
cancelFn context.CancelFunc
|
||||
c *Client
|
||||
|
||||
mu sync.Mutex
|
||||
lc linkCache // tracks tree dependencies
|
||||
trees map[string]*clientTree // all trees
|
||||
// buffers for syncableTrees
|
||||
syncableList []*clientTree
|
||||
disabledList []*clientTree
|
||||
}
|
||||
|
||||
func (c *Client) newRandomIterator() *randomIterator {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
return &randomIterator{
|
||||
c: c,
|
||||
ctx: ctx,
|
||||
cancelFn: cancel,
|
||||
trees: make(map[string]*clientTree),
|
||||
}
|
||||
}
|
||||
|
||||
// Node returns the current node.
|
||||
func (it *randomIterator) Node() *enode.Node {
|
||||
return it.cur
|
||||
}
|
||||
|
||||
// Close closes the iterator.
|
||||
func (it *randomIterator) Close() {
|
||||
it.cancelFn()
|
||||
|
||||
it.mu.Lock()
|
||||
defer it.mu.Unlock()
|
||||
it.trees = nil
|
||||
}
|
||||
|
||||
// Next moves the iterator to the next node.
|
||||
func (it *randomIterator) Next() bool {
|
||||
it.cur = it.nextNode()
|
||||
return it.cur != nil
|
||||
}
|
||||
|
||||
// addTree adds an enrtree:// URL to the iterator.
|
||||
func (it *randomIterator) addTree(url string) error {
|
||||
le, err := parseLink(url)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid enrtree URL: %v", err)
|
||||
}
|
||||
it.lc.addLink("", le.str)
|
||||
return nil
|
||||
}
|
||||
|
||||
// nextNode syncs random tree entries until it finds a node.
|
||||
func (it *randomIterator) nextNode() *enode.Node {
|
||||
for {
|
||||
ct := it.pickTree()
|
||||
if ct == nil {
|
||||
return nil
|
||||
}
|
||||
n, err := ct.syncRandom(it.ctx)
|
||||
if err != nil {
|
||||
if errors.Is(err, it.ctx.Err()) {
|
||||
return nil // context canceled.
|
||||
}
|
||||
it.c.cfg.Logger.Debug("Error in DNS random node sync", "tree", ct.loc.domain, "err", err)
|
||||
continue
|
||||
}
|
||||
if n != nil {
|
||||
return n
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// pickTree returns a random tree to sync from.
|
||||
func (it *randomIterator) pickTree() *clientTree {
|
||||
it.mu.Lock()
|
||||
defer it.mu.Unlock()
|
||||
|
||||
// First check if iterator was closed.
|
||||
// Need to do this here to avoid nil map access in rebuildTrees.
|
||||
if it.trees == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Rebuild the trees map if any links have changed.
|
||||
if it.lc.changed {
|
||||
it.rebuildTrees()
|
||||
it.lc.changed = false
|
||||
}
|
||||
|
||||
for {
|
||||
canSync, trees := it.syncableTrees()
|
||||
switch {
|
||||
case canSync:
|
||||
// Pick a random tree.
|
||||
return trees[rand.Intn(len(trees))]
|
||||
case len(trees) > 0:
|
||||
// No sync action can be performed on any tree right now. The only meaningful
|
||||
// thing to do is waiting for any root record to get updated.
|
||||
if !it.waitForRootUpdates(trees) {
|
||||
// Iterator was closed while waiting.
|
||||
return nil
|
||||
}
|
||||
default:
|
||||
// There are no trees left, the iterator was closed.
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// syncableTrees finds trees on which any meaningful sync action can be performed.
|
||||
func (it *randomIterator) syncableTrees() (canSync bool, trees []*clientTree) {
|
||||
// Resize tree lists.
|
||||
it.syncableList = it.syncableList[:0]
|
||||
it.disabledList = it.disabledList[:0]
|
||||
|
||||
// Partition them into the two lists.
|
||||
for _, ct := range it.trees {
|
||||
if ct.canSyncRandom() {
|
||||
it.syncableList = append(it.syncableList, ct)
|
||||
} else {
|
||||
it.disabledList = append(it.disabledList, ct)
|
||||
}
|
||||
}
|
||||
if len(it.syncableList) > 0 {
|
||||
return true, it.syncableList
|
||||
}
|
||||
return false, it.disabledList
|
||||
}
|
||||
|
||||
// waitForRootUpdates waits for the closest scheduled root check time on the given trees.
|
||||
func (it *randomIterator) waitForRootUpdates(trees []*clientTree) bool {
|
||||
var minTree *clientTree
|
||||
var nextCheck mclock.AbsTime
|
||||
for _, ct := range trees {
|
||||
check := ct.nextScheduledRootCheck()
|
||||
if minTree == nil || check < nextCheck {
|
||||
minTree = ct
|
||||
nextCheck = check
|
||||
}
|
||||
}
|
||||
|
||||
sleep := nextCheck.Sub(it.c.clock.Now())
|
||||
it.c.cfg.Logger.Debug("DNS iterator waiting for root updates", "sleep", sleep, "tree", minTree.loc.domain)
|
||||
timeout := it.c.clock.NewTimer(sleep)
|
||||
defer timeout.Stop()
|
||||
select {
|
||||
case <-timeout.C():
|
||||
return true
|
||||
case <-it.ctx.Done():
|
||||
return false // Iterator was closed.
|
||||
}
|
||||
}
|
||||
|
||||
// rebuildTrees rebuilds the 'trees' map.
|
||||
func (it *randomIterator) rebuildTrees() {
|
||||
// Delete removed trees.
|
||||
for loc := range it.trees {
|
||||
if !it.lc.isReferenced(loc) {
|
||||
delete(it.trees, loc)
|
||||
}
|
||||
}
|
||||
// Add new trees.
|
||||
for loc := range it.lc.backrefs {
|
||||
if it.trees[loc] == nil {
|
||||
link, _ := parseLink(linkPrefix + loc)
|
||||
it.trees[loc] = newClientTree(it.c, &it.lc, link)
|
||||
}
|
||||
}
|
||||
}
|
||||
18
vendor/github.com/ethereum/go-ethereum/p2p/dnsdisc/doc.go
generated
vendored
Normal file
18
vendor/github.com/ethereum/go-ethereum/p2p/dnsdisc/doc.go
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright 2017 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Package dnsdisc implements node discovery via DNS (EIP-1459).
|
||||
package dnsdisc
|
||||
63
vendor/github.com/ethereum/go-ethereum/p2p/dnsdisc/error.go
generated
vendored
Normal file
63
vendor/github.com/ethereum/go-ethereum/p2p/dnsdisc/error.go
generated
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
// Copyright 2019 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package dnsdisc
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Entry parse errors.
|
||||
var (
|
||||
errUnknownEntry = errors.New("unknown entry type")
|
||||
errNoPubkey = errors.New("missing public key")
|
||||
errBadPubkey = errors.New("invalid public key")
|
||||
errInvalidENR = errors.New("invalid node record")
|
||||
errInvalidChild = errors.New("invalid child hash")
|
||||
errInvalidSig = errors.New("invalid base64 signature")
|
||||
errSyntax = errors.New("invalid syntax")
|
||||
)
|
||||
|
||||
// Resolver/sync errors
|
||||
var (
|
||||
errNoRoot = errors.New("no valid root found")
|
||||
errNoEntry = errors.New("no valid tree entry found")
|
||||
errHashMismatch = errors.New("hash mismatch")
|
||||
errENRInLinkTree = errors.New("enr entry in link tree")
|
||||
errLinkInENRTree = errors.New("link entry in ENR tree")
|
||||
)
|
||||
|
||||
type nameError struct {
|
||||
name string
|
||||
err error
|
||||
}
|
||||
|
||||
func (err nameError) Error() string {
|
||||
if ee, ok := err.err.(entryError); ok {
|
||||
return fmt.Sprintf("invalid %s entry at %s: %v", ee.typ, err.name, ee.err)
|
||||
}
|
||||
return err.name + ": " + err.err.Error()
|
||||
}
|
||||
|
||||
type entryError struct {
|
||||
typ string
|
||||
err error
|
||||
}
|
||||
|
||||
func (err entryError) Error() string {
|
||||
return fmt.Sprintf("invalid %s entry: %v", err.typ, err.err)
|
||||
}
|
||||
329
vendor/github.com/ethereum/go-ethereum/p2p/dnsdisc/sync.go
generated
vendored
Normal file
329
vendor/github.com/ethereum/go-ethereum/p2p/dnsdisc/sync.go
generated
vendored
Normal file
@@ -0,0 +1,329 @@
|
||||
// Copyright 2019 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package dnsdisc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/mclock"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
)
|
||||
|
||||
// This is the number of consecutive leaf requests that may fail before
|
||||
// we consider re-resolving the tree root.
|
||||
const rootRecheckFailCount = 5
|
||||
|
||||
// clientTree is a full tree being synced.
|
||||
type clientTree struct {
|
||||
c *Client
|
||||
loc *linkEntry // link to this tree
|
||||
|
||||
lastRootCheck mclock.AbsTime // last revalidation of root
|
||||
leafFailCount int
|
||||
rootFailCount int
|
||||
|
||||
root *rootEntry
|
||||
enrs *subtreeSync
|
||||
links *subtreeSync
|
||||
|
||||
lc *linkCache // tracks all links between all trees
|
||||
curLinks map[string]struct{} // links contained in this tree
|
||||
linkGCRoot string // root on which last link GC has run
|
||||
}
|
||||
|
||||
func newClientTree(c *Client, lc *linkCache, loc *linkEntry) *clientTree {
|
||||
return &clientTree{c: c, lc: lc, loc: loc}
|
||||
}
|
||||
|
||||
// syncAll retrieves all entries of the tree.
|
||||
func (ct *clientTree) syncAll(dest map[string]entry) error {
|
||||
if err := ct.updateRoot(context.Background()); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ct.links.resolveAll(dest); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ct.enrs.resolveAll(dest); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// syncRandom retrieves a single entry of the tree. The Node return value
|
||||
// is non-nil if the entry was a node.
|
||||
func (ct *clientTree) syncRandom(ctx context.Context) (n *enode.Node, err error) {
|
||||
if ct.rootUpdateDue() {
|
||||
if err := ct.updateRoot(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Update fail counter for leaf request errors.
|
||||
defer func() {
|
||||
if err != nil {
|
||||
ct.leafFailCount++
|
||||
}
|
||||
}()
|
||||
|
||||
// Link tree sync has priority, run it to completion before syncing ENRs.
|
||||
if !ct.links.done() {
|
||||
err := ct.syncNextLink(ctx)
|
||||
return nil, err
|
||||
}
|
||||
ct.gcLinks()
|
||||
|
||||
// Sync next random entry in ENR tree. Once every node has been visited, we simply
|
||||
// start over. This is fine because entries are cached internally by the client LRU
|
||||
// also by DNS resolvers.
|
||||
if ct.enrs.done() {
|
||||
ct.enrs = newSubtreeSync(ct.c, ct.loc, ct.root.eroot, false)
|
||||
}
|
||||
return ct.syncNextRandomENR(ctx)
|
||||
}
|
||||
|
||||
// canSyncRandom checks if any meaningful action can be performed by syncRandom.
|
||||
func (ct *clientTree) canSyncRandom() bool {
|
||||
// Note: the check for non-zero leaf count is very important here.
|
||||
// If we're done syncing all nodes, and no leaves were found, the tree
|
||||
// is empty and we can't use it for sync.
|
||||
return ct.rootUpdateDue() || !ct.links.done() || !ct.enrs.done() || ct.enrs.leaves != 0
|
||||
}
|
||||
|
||||
// gcLinks removes outdated links from the global link cache. GC runs once
|
||||
// when the link sync finishes.
|
||||
func (ct *clientTree) gcLinks() {
|
||||
if !ct.links.done() || ct.root.lroot == ct.linkGCRoot {
|
||||
return
|
||||
}
|
||||
ct.lc.resetLinks(ct.loc.str, ct.curLinks)
|
||||
ct.linkGCRoot = ct.root.lroot
|
||||
}
|
||||
|
||||
func (ct *clientTree) syncNextLink(ctx context.Context) error {
|
||||
hash := ct.links.missing[0]
|
||||
e, err := ct.links.resolveNext(ctx, hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ct.links.missing = ct.links.missing[1:]
|
||||
|
||||
if dest, ok := e.(*linkEntry); ok {
|
||||
ct.lc.addLink(ct.loc.str, dest.str)
|
||||
ct.curLinks[dest.str] = struct{}{}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ct *clientTree) syncNextRandomENR(ctx context.Context) (*enode.Node, error) {
|
||||
index := rand.Intn(len(ct.enrs.missing))
|
||||
hash := ct.enrs.missing[index]
|
||||
e, err := ct.enrs.resolveNext(ctx, hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ct.enrs.missing = removeHash(ct.enrs.missing, index)
|
||||
if ee, ok := e.(*enrEntry); ok {
|
||||
return ee.node, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (ct *clientTree) String() string {
|
||||
return ct.loc.String()
|
||||
}
|
||||
|
||||
// removeHash removes the element at index from h.
|
||||
func removeHash(h []string, index int) []string {
|
||||
if len(h) == 1 {
|
||||
return nil
|
||||
}
|
||||
last := len(h) - 1
|
||||
if index < last {
|
||||
h[index] = h[last]
|
||||
h[last] = ""
|
||||
}
|
||||
return h[:last]
|
||||
}
|
||||
|
||||
// updateRoot ensures that the given tree has an up-to-date root.
|
||||
func (ct *clientTree) updateRoot(ctx context.Context) error {
|
||||
if !ct.slowdownRootUpdate(ctx) {
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
ct.lastRootCheck = ct.c.clock.Now()
|
||||
ctx, cancel := context.WithTimeout(ctx, ct.c.cfg.Timeout)
|
||||
defer cancel()
|
||||
root, err := ct.c.resolveRoot(ctx, ct.loc)
|
||||
if err != nil {
|
||||
ct.rootFailCount++
|
||||
return err
|
||||
}
|
||||
ct.root = &root
|
||||
ct.rootFailCount = 0
|
||||
ct.leafFailCount = 0
|
||||
|
||||
// Invalidate subtrees if changed.
|
||||
if ct.links == nil || root.lroot != ct.links.root {
|
||||
ct.links = newSubtreeSync(ct.c, ct.loc, root.lroot, true)
|
||||
ct.curLinks = make(map[string]struct{})
|
||||
}
|
||||
if ct.enrs == nil || root.eroot != ct.enrs.root {
|
||||
ct.enrs = newSubtreeSync(ct.c, ct.loc, root.eroot, false)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// rootUpdateDue returns true when a root update is needed.
|
||||
func (ct *clientTree) rootUpdateDue() bool {
|
||||
tooManyFailures := ct.leafFailCount > rootRecheckFailCount
|
||||
scheduledCheck := ct.c.clock.Now() >= ct.nextScheduledRootCheck()
|
||||
return ct.root == nil || tooManyFailures || scheduledCheck
|
||||
}
|
||||
|
||||
func (ct *clientTree) nextScheduledRootCheck() mclock.AbsTime {
|
||||
return ct.lastRootCheck.Add(ct.c.cfg.RecheckInterval)
|
||||
}
|
||||
|
||||
// slowdownRootUpdate applies a delay to root resolution if is tried
|
||||
// too frequently. This avoids busy polling when the client is offline.
|
||||
// Returns true if the timeout passed, false if sync was canceled.
|
||||
func (ct *clientTree) slowdownRootUpdate(ctx context.Context) bool {
|
||||
var delay time.Duration
|
||||
switch {
|
||||
case ct.rootFailCount > 20:
|
||||
delay = 10 * time.Second
|
||||
case ct.rootFailCount > 5:
|
||||
delay = 5 * time.Second
|
||||
default:
|
||||
return true
|
||||
}
|
||||
timeout := ct.c.clock.NewTimer(delay)
|
||||
defer timeout.Stop()
|
||||
select {
|
||||
case <-timeout.C():
|
||||
return true
|
||||
case <-ctx.Done():
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// subtreeSync is the sync of an ENR or link subtree.
|
||||
type subtreeSync struct {
|
||||
c *Client
|
||||
loc *linkEntry
|
||||
root string
|
||||
missing []string // missing tree node hashes
|
||||
link bool // true if this sync is for the link tree
|
||||
leaves int // counter of synced leaves
|
||||
}
|
||||
|
||||
func newSubtreeSync(c *Client, loc *linkEntry, root string, link bool) *subtreeSync {
|
||||
return &subtreeSync{c, loc, root, []string{root}, link, 0}
|
||||
}
|
||||
|
||||
func (ts *subtreeSync) done() bool {
|
||||
return len(ts.missing) == 0
|
||||
}
|
||||
|
||||
func (ts *subtreeSync) resolveAll(dest map[string]entry) error {
|
||||
for !ts.done() {
|
||||
hash := ts.missing[0]
|
||||
ctx, cancel := context.WithTimeout(context.Background(), ts.c.cfg.Timeout)
|
||||
e, err := ts.resolveNext(ctx, hash)
|
||||
cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dest[hash] = e
|
||||
ts.missing = ts.missing[1:]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ts *subtreeSync) resolveNext(ctx context.Context, hash string) (entry, error) {
|
||||
e, err := ts.c.resolveEntry(ctx, ts.loc.domain, hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch e := e.(type) {
|
||||
case *enrEntry:
|
||||
if ts.link {
|
||||
return nil, errENRInLinkTree
|
||||
}
|
||||
ts.leaves++
|
||||
case *linkEntry:
|
||||
if !ts.link {
|
||||
return nil, errLinkInENRTree
|
||||
}
|
||||
ts.leaves++
|
||||
case *branchEntry:
|
||||
ts.missing = append(ts.missing, e.children...)
|
||||
}
|
||||
return e, nil
|
||||
}
|
||||
|
||||
// linkCache tracks links between trees.
|
||||
type linkCache struct {
|
||||
backrefs map[string]map[string]struct{}
|
||||
changed bool
|
||||
}
|
||||
|
||||
func (lc *linkCache) isReferenced(r string) bool {
|
||||
return len(lc.backrefs[r]) != 0
|
||||
}
|
||||
|
||||
func (lc *linkCache) addLink(from, to string) {
|
||||
if _, ok := lc.backrefs[to][from]; ok {
|
||||
return
|
||||
}
|
||||
|
||||
if lc.backrefs == nil {
|
||||
lc.backrefs = make(map[string]map[string]struct{})
|
||||
}
|
||||
if _, ok := lc.backrefs[to]; !ok {
|
||||
lc.backrefs[to] = make(map[string]struct{})
|
||||
}
|
||||
lc.backrefs[to][from] = struct{}{}
|
||||
lc.changed = true
|
||||
}
|
||||
|
||||
// resetLinks clears all links of the given tree.
|
||||
func (lc *linkCache) resetLinks(from string, keep map[string]struct{}) {
|
||||
stk := []string{from}
|
||||
for len(stk) > 0 {
|
||||
item := stk[len(stk)-1]
|
||||
stk = stk[:len(stk)-1]
|
||||
|
||||
for r, refs := range lc.backrefs {
|
||||
if _, ok := keep[r]; ok {
|
||||
continue
|
||||
}
|
||||
if _, ok := refs[item]; !ok {
|
||||
continue
|
||||
}
|
||||
lc.changed = true
|
||||
delete(refs, item)
|
||||
if len(refs) == 0 {
|
||||
delete(lc.backrefs, r)
|
||||
stk = append(stk, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
423
vendor/github.com/ethereum/go-ethereum/p2p/dnsdisc/tree.go
generated
vendored
Normal file
423
vendor/github.com/ethereum/go-ethereum/p2p/dnsdisc/tree.go
generated
vendored
Normal file
@@ -0,0 +1,423 @@
|
||||
// Copyright 2019 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package dnsdisc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ecdsa"
|
||||
"encoding/base32"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethereum/go-ethereum/p2p/enr"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
// Tree is a merkle tree of node records.
|
||||
type Tree struct {
|
||||
root *rootEntry
|
||||
entries map[string]entry
|
||||
}
|
||||
|
||||
// Sign signs the tree with the given private key and sets the sequence number.
|
||||
func (t *Tree) Sign(key *ecdsa.PrivateKey, domain string) (url string, err error) {
|
||||
root := *t.root
|
||||
sig, err := crypto.Sign(root.sigHash(), key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
root.sig = sig
|
||||
t.root = &root
|
||||
link := newLinkEntry(domain, &key.PublicKey)
|
||||
return link.String(), nil
|
||||
}
|
||||
|
||||
// SetSignature verifies the given signature and assigns it as the tree's current
|
||||
// signature if valid.
|
||||
func (t *Tree) SetSignature(pubkey *ecdsa.PublicKey, signature string) error {
|
||||
sig, err := b64format.DecodeString(signature)
|
||||
if err != nil || len(sig) != crypto.SignatureLength {
|
||||
return errInvalidSig
|
||||
}
|
||||
root := *t.root
|
||||
root.sig = sig
|
||||
if !root.verifySignature(pubkey) {
|
||||
return errInvalidSig
|
||||
}
|
||||
t.root = &root
|
||||
return nil
|
||||
}
|
||||
|
||||
// Seq returns the sequence number of the tree.
|
||||
func (t *Tree) Seq() uint {
|
||||
return t.root.seq
|
||||
}
|
||||
|
||||
// Signature returns the signature of the tree.
|
||||
func (t *Tree) Signature() string {
|
||||
return b64format.EncodeToString(t.root.sig)
|
||||
}
|
||||
|
||||
// ToTXT returns all DNS TXT records required for the tree.
|
||||
func (t *Tree) ToTXT(domain string) map[string]string {
|
||||
records := map[string]string{domain: t.root.String()}
|
||||
for _, e := range t.entries {
|
||||
sd := subdomain(e)
|
||||
if domain != "" {
|
||||
sd = sd + "." + domain
|
||||
}
|
||||
records[sd] = e.String()
|
||||
}
|
||||
return records
|
||||
}
|
||||
|
||||
// Links returns all links contained in the tree.
|
||||
func (t *Tree) Links() []string {
|
||||
var links []string
|
||||
for _, e := range t.entries {
|
||||
if le, ok := e.(*linkEntry); ok {
|
||||
links = append(links, le.String())
|
||||
}
|
||||
}
|
||||
return links
|
||||
}
|
||||
|
||||
// Nodes returns all nodes contained in the tree.
|
||||
func (t *Tree) Nodes() []*enode.Node {
|
||||
var nodes []*enode.Node
|
||||
for _, e := range t.entries {
|
||||
if ee, ok := e.(*enrEntry); ok {
|
||||
nodes = append(nodes, ee.node)
|
||||
}
|
||||
}
|
||||
return nodes
|
||||
}
|
||||
|
||||
/*
|
||||
We want to keep the UDP size below 512 bytes. The UDP size is roughly:
|
||||
UDP length = 8 + UDP payload length ( 229 )
|
||||
UPD Payload length:
|
||||
- dns.id 2
|
||||
- dns.flags 2
|
||||
- dns.count.queries 2
|
||||
- dns.count.answers 2
|
||||
- dns.count.auth_rr 2
|
||||
- dns.count.add_rr 2
|
||||
- queries (query-size + 6)
|
||||
- answers :
|
||||
- dns.resp.name 2
|
||||
- dns.resp.type 2
|
||||
- dns.resp.class 2
|
||||
- dns.resp.ttl 4
|
||||
- dns.resp.len 2
|
||||
- dns.txt.length 1
|
||||
- dns.txt resp_data_size
|
||||
|
||||
So the total size is roughly a fixed overhead of `39`, and the size of the query (domain
|
||||
name) and response. The query size is, for example,
|
||||
FVY6INQ6LZ33WLCHO3BPR3FH6Y.snap.mainnet.ethdisco.net (52)
|
||||
|
||||
We also have some static data in the response, such as `enrtree-branch:`, and potentially
|
||||
splitting the response up with `" "`, leaving us with a size of roughly `400` that we need
|
||||
to stay below.
|
||||
|
||||
The number `370` is used to have some margin for extra overhead (for example, the dns
|
||||
query may be larger - more subdomains).
|
||||
*/
|
||||
const (
|
||||
hashAbbrevSize = 1 + 16*13/8 // Size of an encoded hash (plus comma)
|
||||
maxChildren = 370 / hashAbbrevSize // 13 children
|
||||
minHashLength = 12
|
||||
)
|
||||
|
||||
// MakeTree creates a tree containing the given nodes and links.
|
||||
func MakeTree(seq uint, nodes []*enode.Node, links []string) (*Tree, error) {
|
||||
// Sort records by ID and ensure all nodes have a valid record.
|
||||
records := make([]*enode.Node, len(nodes))
|
||||
|
||||
copy(records, nodes)
|
||||
sortByID(records)
|
||||
for _, n := range records {
|
||||
if len(n.Record().Signature()) == 0 {
|
||||
return nil, fmt.Errorf("can't add node %v: unsigned node record", n.ID())
|
||||
}
|
||||
}
|
||||
|
||||
// Create the leaf list.
|
||||
enrEntries := make([]entry, len(records))
|
||||
for i, r := range records {
|
||||
enrEntries[i] = &enrEntry{r}
|
||||
}
|
||||
linkEntries := make([]entry, len(links))
|
||||
for i, l := range links {
|
||||
le, err := parseLink(l)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
linkEntries[i] = le
|
||||
}
|
||||
|
||||
// Create intermediate nodes.
|
||||
t := &Tree{entries: make(map[string]entry)}
|
||||
eroot := t.build(enrEntries)
|
||||
t.entries[subdomain(eroot)] = eroot
|
||||
lroot := t.build(linkEntries)
|
||||
t.entries[subdomain(lroot)] = lroot
|
||||
t.root = &rootEntry{seq: seq, eroot: subdomain(eroot), lroot: subdomain(lroot)}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (t *Tree) build(entries []entry) entry {
|
||||
if len(entries) == 1 {
|
||||
return entries[0]
|
||||
}
|
||||
if len(entries) <= maxChildren {
|
||||
hashes := make([]string, len(entries))
|
||||
for i, e := range entries {
|
||||
hashes[i] = subdomain(e)
|
||||
t.entries[hashes[i]] = e
|
||||
}
|
||||
return &branchEntry{hashes}
|
||||
}
|
||||
var subtrees []entry
|
||||
for len(entries) > 0 {
|
||||
n := maxChildren
|
||||
if len(entries) < n {
|
||||
n = len(entries)
|
||||
}
|
||||
sub := t.build(entries[:n])
|
||||
entries = entries[n:]
|
||||
subtrees = append(subtrees, sub)
|
||||
t.entries[subdomain(sub)] = sub
|
||||
}
|
||||
return t.build(subtrees)
|
||||
}
|
||||
|
||||
func sortByID(nodes []*enode.Node) []*enode.Node {
|
||||
sort.Slice(nodes, func(i, j int) bool {
|
||||
return bytes.Compare(nodes[i].ID().Bytes(), nodes[j].ID().Bytes()) < 0
|
||||
})
|
||||
return nodes
|
||||
}
|
||||
|
||||
// Entry Types
|
||||
|
||||
type entry interface {
|
||||
fmt.Stringer
|
||||
}
|
||||
|
||||
type (
|
||||
rootEntry struct {
|
||||
eroot string
|
||||
lroot string
|
||||
seq uint
|
||||
sig []byte
|
||||
}
|
||||
branchEntry struct {
|
||||
children []string
|
||||
}
|
||||
enrEntry struct {
|
||||
node *enode.Node
|
||||
}
|
||||
linkEntry struct {
|
||||
str string
|
||||
domain string
|
||||
pubkey *ecdsa.PublicKey
|
||||
}
|
||||
)
|
||||
|
||||
// Entry Encoding
|
||||
|
||||
var (
|
||||
b32format = base32.StdEncoding.WithPadding(base32.NoPadding)
|
||||
b64format = base64.RawURLEncoding
|
||||
)
|
||||
|
||||
const (
|
||||
rootPrefix = "enrtree-root:v1"
|
||||
linkPrefix = "enrtree://"
|
||||
branchPrefix = "enrtree-branch:"
|
||||
enrPrefix = "enr:"
|
||||
)
|
||||
|
||||
func subdomain(e entry) string {
|
||||
h := sha3.NewLegacyKeccak256()
|
||||
io.WriteString(h, e.String())
|
||||
return b32format.EncodeToString(h.Sum(nil)[:16])
|
||||
}
|
||||
|
||||
func (e *rootEntry) String() string {
|
||||
return fmt.Sprintf(rootPrefix+" e=%s l=%s seq=%d sig=%s", e.eroot, e.lroot, e.seq, b64format.EncodeToString(e.sig))
|
||||
}
|
||||
|
||||
func (e *rootEntry) sigHash() []byte {
|
||||
h := sha3.NewLegacyKeccak256()
|
||||
fmt.Fprintf(h, rootPrefix+" e=%s l=%s seq=%d", e.eroot, e.lroot, e.seq)
|
||||
return h.Sum(nil)
|
||||
}
|
||||
|
||||
func (e *rootEntry) verifySignature(pubkey *ecdsa.PublicKey) bool {
|
||||
sig := e.sig[:crypto.RecoveryIDOffset] // remove recovery id
|
||||
enckey := crypto.FromECDSAPub(pubkey)
|
||||
return crypto.VerifySignature(enckey, e.sigHash(), sig)
|
||||
}
|
||||
|
||||
func (e *branchEntry) String() string {
|
||||
return branchPrefix + strings.Join(e.children, ",")
|
||||
}
|
||||
|
||||
func (e *enrEntry) String() string {
|
||||
return e.node.String()
|
||||
}
|
||||
|
||||
func (e *linkEntry) String() string {
|
||||
return linkPrefix + e.str
|
||||
}
|
||||
|
||||
func newLinkEntry(domain string, pubkey *ecdsa.PublicKey) *linkEntry {
|
||||
key := b32format.EncodeToString(crypto.CompressPubkey(pubkey))
|
||||
str := key + "@" + domain
|
||||
return &linkEntry{str, domain, pubkey}
|
||||
}
|
||||
|
||||
// Entry Parsing
|
||||
|
||||
func parseEntry(e string, validSchemes enr.IdentityScheme) (entry, error) {
|
||||
switch {
|
||||
case strings.HasPrefix(e, linkPrefix):
|
||||
return parseLinkEntry(e)
|
||||
case strings.HasPrefix(e, branchPrefix):
|
||||
return parseBranch(e)
|
||||
case strings.HasPrefix(e, enrPrefix):
|
||||
return parseENR(e, validSchemes)
|
||||
default:
|
||||
return nil, errUnknownEntry
|
||||
}
|
||||
}
|
||||
|
||||
func parseRoot(e string) (rootEntry, error) {
|
||||
var eroot, lroot, sig string
|
||||
var seq uint
|
||||
if _, err := fmt.Sscanf(e, rootPrefix+" e=%s l=%s seq=%d sig=%s", &eroot, &lroot, &seq, &sig); err != nil {
|
||||
return rootEntry{}, entryError{"root", errSyntax}
|
||||
}
|
||||
if !isValidHash(eroot) || !isValidHash(lroot) {
|
||||
return rootEntry{}, entryError{"root", errInvalidChild}
|
||||
}
|
||||
sigb, err := b64format.DecodeString(sig)
|
||||
if err != nil || len(sigb) != crypto.SignatureLength {
|
||||
return rootEntry{}, entryError{"root", errInvalidSig}
|
||||
}
|
||||
return rootEntry{eroot, lroot, seq, sigb}, nil
|
||||
}
|
||||
|
||||
func parseLinkEntry(e string) (entry, error) {
|
||||
le, err := parseLink(e)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return le, nil
|
||||
}
|
||||
|
||||
func parseLink(e string) (*linkEntry, error) {
|
||||
if !strings.HasPrefix(e, linkPrefix) {
|
||||
return nil, fmt.Errorf("wrong/missing scheme 'enrtree' in URL")
|
||||
}
|
||||
e = e[len(linkPrefix):]
|
||||
pos := strings.IndexByte(e, '@')
|
||||
if pos == -1 {
|
||||
return nil, entryError{"link", errNoPubkey}
|
||||
}
|
||||
keystring, domain := e[:pos], e[pos+1:]
|
||||
keybytes, err := b32format.DecodeString(keystring)
|
||||
if err != nil {
|
||||
return nil, entryError{"link", errBadPubkey}
|
||||
}
|
||||
key, err := crypto.DecompressPubkey(keybytes)
|
||||
if err != nil {
|
||||
return nil, entryError{"link", errBadPubkey}
|
||||
}
|
||||
return &linkEntry{e, domain, key}, nil
|
||||
}
|
||||
|
||||
func parseBranch(e string) (entry, error) {
|
||||
e = e[len(branchPrefix):]
|
||||
if e == "" {
|
||||
return &branchEntry{}, nil // empty entry is OK
|
||||
}
|
||||
hashes := make([]string, 0, strings.Count(e, ","))
|
||||
for _, c := range strings.Split(e, ",") {
|
||||
if !isValidHash(c) {
|
||||
return nil, entryError{"branch", errInvalidChild}
|
||||
}
|
||||
hashes = append(hashes, c)
|
||||
}
|
||||
return &branchEntry{hashes}, nil
|
||||
}
|
||||
|
||||
func parseENR(e string, validSchemes enr.IdentityScheme) (entry, error) {
|
||||
e = e[len(enrPrefix):]
|
||||
enc, err := b64format.DecodeString(e)
|
||||
if err != nil {
|
||||
return nil, entryError{"enr", errInvalidENR}
|
||||
}
|
||||
var rec enr.Record
|
||||
if err := rlp.DecodeBytes(enc, &rec); err != nil {
|
||||
return nil, entryError{"enr", err}
|
||||
}
|
||||
n, err := enode.New(validSchemes, &rec)
|
||||
if err != nil {
|
||||
return nil, entryError{"enr", err}
|
||||
}
|
||||
return &enrEntry{n}, nil
|
||||
}
|
||||
|
||||
func isValidHash(s string) bool {
|
||||
dlen := b32format.DecodedLen(len(s))
|
||||
if dlen < minHashLength || dlen > 32 || strings.ContainsAny(s, "\n\r") {
|
||||
return false
|
||||
}
|
||||
buf := make([]byte, 32)
|
||||
_, err := b32format.Decode(buf, []byte(s))
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// truncateHash truncates the given base32 hash string to the minimum acceptable length.
|
||||
func truncateHash(hash string) string {
|
||||
maxLen := b32format.EncodedLen(minHashLength)
|
||||
if len(hash) < maxLen {
|
||||
panic(fmt.Errorf("dnsdisc: hash %q is too short", hash))
|
||||
}
|
||||
return hash[:maxLen]
|
||||
}
|
||||
|
||||
// URL encoding
|
||||
|
||||
// ParseURL parses an enrtree:// URL and returns its components.
|
||||
func ParseURL(url string) (domain string, pubkey *ecdsa.PublicKey, err error) {
|
||||
le, err := parseLink(url)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
return le.domain, le.pubkey, nil
|
||||
}
|
||||
161
vendor/github.com/ethereum/go-ethereum/p2p/enode/idscheme.go
generated
vendored
Normal file
161
vendor/github.com/ethereum/go-ethereum/p2p/enode/idscheme.go
generated
vendored
Normal file
@@ -0,0 +1,161 @@
|
||||
// Copyright 2018 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package enode
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/p2p/enr"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
// ValidSchemes is a List of known secure identity schemes.
|
||||
var ValidSchemes = enr.SchemeMap{
|
||||
"v4": V4ID{},
|
||||
}
|
||||
|
||||
// ValidSchemesForTesting is a List of identity schemes for testing.
|
||||
var ValidSchemesForTesting = enr.SchemeMap{
|
||||
"v4": V4ID{},
|
||||
"null": NullID{},
|
||||
}
|
||||
|
||||
// V4ID is the "v4" identity scheme.
|
||||
type V4ID struct{}
|
||||
|
||||
// SignV4 signs a record using the v4 scheme.
|
||||
func SignV4(r *enr.Record, privkey *ecdsa.PrivateKey) error {
|
||||
// Copy r to avoid modifying it if signing fails.
|
||||
cpy := *r
|
||||
cpy.Set(enr.ID("v4"))
|
||||
cpy.Set(Secp256k1(privkey.PublicKey))
|
||||
|
||||
h := sha3.NewLegacyKeccak256()
|
||||
rlp.Encode(h, cpy.AppendElements(nil))
|
||||
sig, err := crypto.Sign(h.Sum(nil), privkey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sig = sig[:len(sig)-1] // remove v
|
||||
if err = cpy.SetSig(V4ID{}, sig); err == nil {
|
||||
*r = cpy
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (V4ID) Verify(r *enr.Record, sig []byte) error {
|
||||
var entry s256raw
|
||||
if err := r.Load(&entry); err != nil {
|
||||
return err
|
||||
} else if len(entry) != 33 {
|
||||
return fmt.Errorf("invalid public key")
|
||||
}
|
||||
|
||||
h := sha3.NewLegacyKeccak256()
|
||||
rlp.Encode(h, r.AppendElements(nil))
|
||||
if !crypto.VerifySignature(entry, h.Sum(nil), sig) {
|
||||
return enr.ErrInvalidSig
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (V4ID) NodeAddr(r *enr.Record) []byte {
|
||||
var pubkey Secp256k1
|
||||
err := r.Load(&pubkey)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
buf := make([]byte, 64)
|
||||
math.ReadBits(pubkey.X, buf[:32])
|
||||
math.ReadBits(pubkey.Y, buf[32:])
|
||||
return crypto.Keccak256(buf)
|
||||
}
|
||||
|
||||
// Secp256k1 is the "secp256k1" key, which holds a public key.
|
||||
type Secp256k1 ecdsa.PublicKey
|
||||
|
||||
func (v Secp256k1) ENRKey() string { return "secp256k1" }
|
||||
|
||||
// EncodeRLP implements rlp.Encoder.
|
||||
func (v Secp256k1) EncodeRLP(w io.Writer) error {
|
||||
return rlp.Encode(w, crypto.CompressPubkey((*ecdsa.PublicKey)(&v)))
|
||||
}
|
||||
|
||||
// DecodeRLP implements rlp.Decoder.
|
||||
func (v *Secp256k1) DecodeRLP(s *rlp.Stream) error {
|
||||
buf, err := s.Bytes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pk, err := crypto.DecompressPubkey(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*v = (Secp256k1)(*pk)
|
||||
return nil
|
||||
}
|
||||
|
||||
// s256raw is an unparsed secp256k1 public key entry.
|
||||
type s256raw []byte
|
||||
|
||||
func (s256raw) ENRKey() string { return "secp256k1" }
|
||||
|
||||
// v4CompatID is a weaker and insecure version of the "v4" scheme which only checks for the
|
||||
// presence of a secp256k1 public key, but doesn't verify the signature.
|
||||
type v4CompatID struct {
|
||||
V4ID
|
||||
}
|
||||
|
||||
func (v4CompatID) Verify(r *enr.Record, sig []byte) error {
|
||||
var pubkey Secp256k1
|
||||
return r.Load(&pubkey)
|
||||
}
|
||||
|
||||
func signV4Compat(r *enr.Record, pubkey *ecdsa.PublicKey) {
|
||||
r.Set((*Secp256k1)(pubkey))
|
||||
if err := r.SetSig(v4CompatID{}, []byte{}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// NullID is the "null" ENR identity scheme. This scheme stores the node
|
||||
// ID in the record without any signature.
|
||||
type NullID struct{}
|
||||
|
||||
func (NullID) Verify(r *enr.Record, sig []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (NullID) NodeAddr(r *enr.Record) []byte {
|
||||
var id ID
|
||||
r.Load(enr.WithEntry("nulladdr", &id))
|
||||
return id[:]
|
||||
}
|
||||
|
||||
func SignNull(r *enr.Record, id ID) *Node {
|
||||
r.Set(enr.ID("null"))
|
||||
r.Set(enr.WithEntry("nulladdr", id))
|
||||
if err := r.SetSig(NullID{}, []byte{}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &Node{r: *r, id: id}
|
||||
}
|
||||
288
vendor/github.com/ethereum/go-ethereum/p2p/enode/iter.go
generated
vendored
Normal file
288
vendor/github.com/ethereum/go-ethereum/p2p/enode/iter.go
generated
vendored
Normal file
@@ -0,0 +1,288 @@
|
||||
// Copyright 2019 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package enode
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Iterator represents a sequence of nodes. The Next method moves to the next node in the
|
||||
// sequence. It returns false when the sequence has ended or the iterator is closed. Close
|
||||
// may be called concurrently with Next and Node, and interrupts Next if it is blocked.
|
||||
type Iterator interface {
|
||||
Next() bool // moves to next node
|
||||
Node() *Node // returns current node
|
||||
Close() // ends the iterator
|
||||
}
|
||||
|
||||
// ReadNodes reads at most n nodes from the given iterator. The return value contains no
|
||||
// duplicates and no nil values. To prevent looping indefinitely for small repeating node
|
||||
// sequences, this function calls Next at most n times.
|
||||
func ReadNodes(it Iterator, n int) []*Node {
|
||||
seen := make(map[ID]*Node, n)
|
||||
for i := 0; i < n && it.Next(); i++ {
|
||||
// Remove duplicates, keeping the node with higher seq.
|
||||
node := it.Node()
|
||||
prevNode, ok := seen[node.ID()]
|
||||
if ok && prevNode.Seq() > node.Seq() {
|
||||
continue
|
||||
}
|
||||
seen[node.ID()] = node
|
||||
}
|
||||
result := make([]*Node, 0, len(seen))
|
||||
for _, node := range seen {
|
||||
result = append(result, node)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// IterNodes makes an iterator which runs through the given nodes once.
|
||||
func IterNodes(nodes []*Node) Iterator {
|
||||
return &sliceIter{nodes: nodes, index: -1}
|
||||
}
|
||||
|
||||
// CycleNodes makes an iterator which cycles through the given nodes indefinitely.
|
||||
func CycleNodes(nodes []*Node) Iterator {
|
||||
return &sliceIter{nodes: nodes, index: -1, cycle: true}
|
||||
}
|
||||
|
||||
type sliceIter struct {
|
||||
mu sync.Mutex
|
||||
nodes []*Node
|
||||
index int
|
||||
cycle bool
|
||||
}
|
||||
|
||||
func (it *sliceIter) Next() bool {
|
||||
it.mu.Lock()
|
||||
defer it.mu.Unlock()
|
||||
|
||||
if len(it.nodes) == 0 {
|
||||
return false
|
||||
}
|
||||
it.index++
|
||||
if it.index == len(it.nodes) {
|
||||
if it.cycle {
|
||||
it.index = 0
|
||||
} else {
|
||||
it.nodes = nil
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (it *sliceIter) Node() *Node {
|
||||
it.mu.Lock()
|
||||
defer it.mu.Unlock()
|
||||
if len(it.nodes) == 0 {
|
||||
return nil
|
||||
}
|
||||
return it.nodes[it.index]
|
||||
}
|
||||
|
||||
func (it *sliceIter) Close() {
|
||||
it.mu.Lock()
|
||||
defer it.mu.Unlock()
|
||||
|
||||
it.nodes = nil
|
||||
}
|
||||
|
||||
// Filter wraps an iterator such that Next only returns nodes for which
|
||||
// the 'check' function returns true.
|
||||
func Filter(it Iterator, check func(*Node) bool) Iterator {
|
||||
return &filterIter{it, check}
|
||||
}
|
||||
|
||||
type filterIter struct {
|
||||
Iterator
|
||||
check func(*Node) bool
|
||||
}
|
||||
|
||||
func (f *filterIter) Next() bool {
|
||||
for f.Iterator.Next() {
|
||||
if f.check(f.Node()) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// FairMix aggregates multiple node iterators. The mixer itself is an iterator which ends
|
||||
// only when Close is called. Source iterators added via AddSource are removed from the
|
||||
// mix when they end.
|
||||
//
|
||||
// The distribution of nodes returned by Next is approximately fair, i.e. FairMix
|
||||
// attempts to draw from all sources equally often. However, if a certain source is slow
|
||||
// and doesn't return a node within the configured timeout, a node from any other source
|
||||
// will be returned.
|
||||
//
|
||||
// It's safe to call AddSource and Close concurrently with Next.
|
||||
type FairMix struct {
|
||||
wg sync.WaitGroup
|
||||
fromAny chan *Node
|
||||
timeout time.Duration
|
||||
cur *Node
|
||||
|
||||
mu sync.Mutex
|
||||
closed chan struct{}
|
||||
sources []*mixSource
|
||||
last int
|
||||
}
|
||||
|
||||
type mixSource struct {
|
||||
it Iterator
|
||||
next chan *Node
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
// NewFairMix creates a mixer.
|
||||
//
|
||||
// The timeout specifies how long the mixer will wait for the next fairly-chosen source
|
||||
// before giving up and taking a node from any other source. A good way to set the timeout
|
||||
// is deciding how long you'd want to wait for a node on average. Passing a negative
|
||||
// timeout makes the mixer completely fair.
|
||||
func NewFairMix(timeout time.Duration) *FairMix {
|
||||
m := &FairMix{
|
||||
fromAny: make(chan *Node),
|
||||
closed: make(chan struct{}),
|
||||
timeout: timeout,
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// AddSource adds a source of nodes.
|
||||
func (m *FairMix) AddSource(it Iterator) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
if m.closed == nil {
|
||||
return
|
||||
}
|
||||
m.wg.Add(1)
|
||||
source := &mixSource{it, make(chan *Node), m.timeout}
|
||||
m.sources = append(m.sources, source)
|
||||
go m.runSource(m.closed, source)
|
||||
}
|
||||
|
||||
// Close shuts down the mixer and all current sources.
|
||||
// Calling this is required to release resources associated with the mixer.
|
||||
func (m *FairMix) Close() {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
if m.closed == nil {
|
||||
return
|
||||
}
|
||||
for _, s := range m.sources {
|
||||
s.it.Close()
|
||||
}
|
||||
close(m.closed)
|
||||
m.wg.Wait()
|
||||
close(m.fromAny)
|
||||
m.sources = nil
|
||||
m.closed = nil
|
||||
}
|
||||
|
||||
// Next returns a node from a random source.
|
||||
func (m *FairMix) Next() bool {
|
||||
m.cur = nil
|
||||
|
||||
var timeout <-chan time.Time
|
||||
if m.timeout >= 0 {
|
||||
timer := time.NewTimer(m.timeout)
|
||||
timeout = timer.C
|
||||
defer timer.Stop()
|
||||
}
|
||||
for {
|
||||
source := m.pickSource()
|
||||
if source == nil {
|
||||
return m.nextFromAny()
|
||||
}
|
||||
select {
|
||||
case n, ok := <-source.next:
|
||||
if ok {
|
||||
m.cur = n
|
||||
source.timeout = m.timeout
|
||||
return true
|
||||
}
|
||||
// This source has ended.
|
||||
m.deleteSource(source)
|
||||
case <-timeout:
|
||||
source.timeout /= 2
|
||||
return m.nextFromAny()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Node returns the current node.
|
||||
func (m *FairMix) Node() *Node {
|
||||
return m.cur
|
||||
}
|
||||
|
||||
// nextFromAny is used when there are no sources or when the 'fair' choice
|
||||
// doesn't turn up a node quickly enough.
|
||||
func (m *FairMix) nextFromAny() bool {
|
||||
n, ok := <-m.fromAny
|
||||
if ok {
|
||||
m.cur = n
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
// pickSource chooses the next source to read from, cycling through them in order.
|
||||
func (m *FairMix) pickSource() *mixSource {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
if len(m.sources) == 0 {
|
||||
return nil
|
||||
}
|
||||
m.last = (m.last + 1) % len(m.sources)
|
||||
return m.sources[m.last]
|
||||
}
|
||||
|
||||
// deleteSource deletes a source.
|
||||
func (m *FairMix) deleteSource(s *mixSource) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
for i := range m.sources {
|
||||
if m.sources[i] == s {
|
||||
copy(m.sources[i:], m.sources[i+1:])
|
||||
m.sources[len(m.sources)-1] = nil
|
||||
m.sources = m.sources[:len(m.sources)-1]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// runSource reads a single source in a loop.
|
||||
func (m *FairMix) runSource(closed chan struct{}, s *mixSource) {
|
||||
defer m.wg.Done()
|
||||
defer close(s.next)
|
||||
for s.it.Next() {
|
||||
n := s.it.Node()
|
||||
select {
|
||||
case s.next <- n:
|
||||
case m.fromAny <- n:
|
||||
case <-closed:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
332
vendor/github.com/ethereum/go-ethereum/p2p/enode/localnode.go
generated
vendored
Normal file
332
vendor/github.com/ethereum/go-ethereum/p2p/enode/localnode.go
generated
vendored
Normal file
@@ -0,0 +1,332 @@
|
||||
// Copyright 2018 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package enode
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
"net"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/p2p/enr"
|
||||
"github.com/ethereum/go-ethereum/p2p/netutil"
|
||||
)
|
||||
|
||||
const (
|
||||
// IP tracker configuration
|
||||
iptrackMinStatements = 10
|
||||
iptrackWindow = 5 * time.Minute
|
||||
iptrackContactWindow = 10 * time.Minute
|
||||
|
||||
// time needed to wait between two updates to the local ENR
|
||||
recordUpdateThrottle = time.Millisecond
|
||||
)
|
||||
|
||||
// LocalNode produces the signed node record of a local node, i.e. a node run in the
|
||||
// current process. Setting ENR entries via the Set method updates the record. A new version
|
||||
// of the record is signed on demand when the Node method is called.
|
||||
type LocalNode struct {
|
||||
cur atomic.Value // holds a non-nil node pointer while the record is up-to-date
|
||||
|
||||
id ID
|
||||
key *ecdsa.PrivateKey
|
||||
db *DB
|
||||
|
||||
// everything below is protected by a lock
|
||||
mu sync.RWMutex
|
||||
seq uint64
|
||||
update time.Time // timestamp when the record was last updated
|
||||
entries map[string]enr.Entry
|
||||
endpoint4 lnEndpoint
|
||||
endpoint6 lnEndpoint
|
||||
}
|
||||
|
||||
type lnEndpoint struct {
|
||||
track *netutil.IPTracker
|
||||
staticIP, fallbackIP net.IP
|
||||
fallbackUDP uint16 // port
|
||||
}
|
||||
|
||||
// NewLocalNode creates a local node.
|
||||
func NewLocalNode(db *DB, key *ecdsa.PrivateKey) *LocalNode {
|
||||
ln := &LocalNode{
|
||||
id: PubkeyToIDV4(&key.PublicKey),
|
||||
db: db,
|
||||
key: key,
|
||||
entries: make(map[string]enr.Entry),
|
||||
endpoint4: lnEndpoint{
|
||||
track: netutil.NewIPTracker(iptrackWindow, iptrackContactWindow, iptrackMinStatements),
|
||||
},
|
||||
endpoint6: lnEndpoint{
|
||||
track: netutil.NewIPTracker(iptrackWindow, iptrackContactWindow, iptrackMinStatements),
|
||||
},
|
||||
}
|
||||
ln.seq = db.localSeq(ln.id)
|
||||
ln.update = time.Now()
|
||||
ln.cur.Store((*Node)(nil))
|
||||
return ln
|
||||
}
|
||||
|
||||
// Database returns the node database associated with the local node.
|
||||
func (ln *LocalNode) Database() *DB {
|
||||
return ln.db
|
||||
}
|
||||
|
||||
// Node returns the current version of the local node record.
|
||||
func (ln *LocalNode) Node() *Node {
|
||||
// If we have a valid record, return that
|
||||
n := ln.cur.Load().(*Node)
|
||||
if n != nil {
|
||||
return n
|
||||
}
|
||||
|
||||
// Record was invalidated, sign a new copy.
|
||||
ln.mu.Lock()
|
||||
defer ln.mu.Unlock()
|
||||
|
||||
// Double check the current record, since multiple goroutines might be waiting
|
||||
// on the write mutex.
|
||||
if n = ln.cur.Load().(*Node); n != nil {
|
||||
return n
|
||||
}
|
||||
|
||||
// The initial sequence number is the current timestamp in milliseconds. To ensure
|
||||
// that the initial sequence number will always be higher than any previous sequence
|
||||
// number (assuming the clock is correct), we want to avoid updating the record faster
|
||||
// than once per ms. So we need to sleep here until the next possible update time has
|
||||
// arrived.
|
||||
lastChange := time.Since(ln.update)
|
||||
if lastChange < recordUpdateThrottle {
|
||||
time.Sleep(recordUpdateThrottle - lastChange)
|
||||
}
|
||||
|
||||
ln.sign()
|
||||
ln.update = time.Now()
|
||||
return ln.cur.Load().(*Node)
|
||||
}
|
||||
|
||||
// Seq returns the current sequence number of the local node record.
|
||||
func (ln *LocalNode) Seq() uint64 {
|
||||
ln.mu.Lock()
|
||||
defer ln.mu.Unlock()
|
||||
|
||||
return ln.seq
|
||||
}
|
||||
|
||||
// ID returns the local node ID.
|
||||
func (ln *LocalNode) ID() ID {
|
||||
return ln.id
|
||||
}
|
||||
|
||||
// Set puts the given entry into the local record, overwriting any existing value.
|
||||
// Use Set*IP and SetFallbackUDP to set IP addresses and UDP port, otherwise they'll
|
||||
// be overwritten by the endpoint predictor.
|
||||
//
|
||||
// Since node record updates are throttled to one per second, Set is asynchronous.
|
||||
// Any update will be queued up and published when at least one second passes from
|
||||
// the last change.
|
||||
func (ln *LocalNode) Set(e enr.Entry) {
|
||||
ln.mu.Lock()
|
||||
defer ln.mu.Unlock()
|
||||
|
||||
ln.set(e)
|
||||
}
|
||||
|
||||
func (ln *LocalNode) set(e enr.Entry) {
|
||||
val, exists := ln.entries[e.ENRKey()]
|
||||
if !exists || !reflect.DeepEqual(val, e) {
|
||||
ln.entries[e.ENRKey()] = e
|
||||
ln.invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
// Delete removes the given entry from the local record.
|
||||
func (ln *LocalNode) Delete(e enr.Entry) {
|
||||
ln.mu.Lock()
|
||||
defer ln.mu.Unlock()
|
||||
|
||||
ln.delete(e)
|
||||
}
|
||||
|
||||
func (ln *LocalNode) delete(e enr.Entry) {
|
||||
_, exists := ln.entries[e.ENRKey()]
|
||||
if exists {
|
||||
delete(ln.entries, e.ENRKey())
|
||||
ln.invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
func (ln *LocalNode) endpointForIP(ip net.IP) *lnEndpoint {
|
||||
if ip.To4() != nil {
|
||||
return &ln.endpoint4
|
||||
}
|
||||
return &ln.endpoint6
|
||||
}
|
||||
|
||||
// SetStaticIP sets the local IP to the given one unconditionally.
|
||||
// This disables endpoint prediction.
|
||||
func (ln *LocalNode) SetStaticIP(ip net.IP) {
|
||||
ln.mu.Lock()
|
||||
defer ln.mu.Unlock()
|
||||
|
||||
ln.endpointForIP(ip).staticIP = ip
|
||||
ln.updateEndpoints()
|
||||
}
|
||||
|
||||
// SetFallbackIP sets the last-resort IP address. This address is used
|
||||
// if no endpoint prediction can be made and no static IP is set.
|
||||
func (ln *LocalNode) SetFallbackIP(ip net.IP) {
|
||||
ln.mu.Lock()
|
||||
defer ln.mu.Unlock()
|
||||
|
||||
ln.endpointForIP(ip).fallbackIP = ip
|
||||
ln.updateEndpoints()
|
||||
}
|
||||
|
||||
// SetFallbackUDP sets the last-resort UDP-on-IPv4 port. This port is used
|
||||
// if no endpoint prediction can be made.
|
||||
func (ln *LocalNode) SetFallbackUDP(port int) {
|
||||
ln.mu.Lock()
|
||||
defer ln.mu.Unlock()
|
||||
|
||||
ln.endpoint4.fallbackUDP = uint16(port)
|
||||
ln.endpoint6.fallbackUDP = uint16(port)
|
||||
ln.updateEndpoints()
|
||||
}
|
||||
|
||||
// UDPEndpointStatement should be called whenever a statement about the local node's
|
||||
// UDP endpoint is received. It feeds the local endpoint predictor.
|
||||
func (ln *LocalNode) UDPEndpointStatement(fromaddr, endpoint *net.UDPAddr) {
|
||||
ln.mu.Lock()
|
||||
defer ln.mu.Unlock()
|
||||
|
||||
ln.endpointForIP(endpoint.IP).track.AddStatement(fromaddr.String(), endpoint.String())
|
||||
ln.updateEndpoints()
|
||||
}
|
||||
|
||||
// UDPContact should be called whenever the local node has announced itself to another node
|
||||
// via UDP. It feeds the local endpoint predictor.
|
||||
func (ln *LocalNode) UDPContact(toaddr *net.UDPAddr) {
|
||||
ln.mu.Lock()
|
||||
defer ln.mu.Unlock()
|
||||
|
||||
ln.endpointForIP(toaddr.IP).track.AddContact(toaddr.String())
|
||||
ln.updateEndpoints()
|
||||
}
|
||||
|
||||
// updateEndpoints updates the record with predicted endpoints.
|
||||
func (ln *LocalNode) updateEndpoints() {
|
||||
ip4, udp4 := ln.endpoint4.get()
|
||||
ip6, udp6 := ln.endpoint6.get()
|
||||
|
||||
if ip4 != nil && !ip4.IsUnspecified() {
|
||||
ln.set(enr.IPv4(ip4))
|
||||
} else {
|
||||
ln.delete(enr.IPv4{})
|
||||
}
|
||||
if ip6 != nil && !ip6.IsUnspecified() {
|
||||
ln.set(enr.IPv6(ip6))
|
||||
} else {
|
||||
ln.delete(enr.IPv6{})
|
||||
}
|
||||
if udp4 != 0 {
|
||||
ln.set(enr.UDP(udp4))
|
||||
} else {
|
||||
ln.delete(enr.UDP(0))
|
||||
}
|
||||
if udp6 != 0 && udp6 != udp4 {
|
||||
ln.set(enr.UDP6(udp6))
|
||||
} else {
|
||||
ln.delete(enr.UDP6(0))
|
||||
}
|
||||
}
|
||||
|
||||
// get returns the endpoint with highest precedence.
|
||||
func (e *lnEndpoint) get() (newIP net.IP, newPort uint16) {
|
||||
newPort = e.fallbackUDP
|
||||
if e.fallbackIP != nil {
|
||||
newIP = e.fallbackIP
|
||||
}
|
||||
if e.staticIP != nil {
|
||||
newIP = e.staticIP
|
||||
} else if ip, port := predictAddr(e.track); ip != nil {
|
||||
newIP = ip
|
||||
newPort = port
|
||||
}
|
||||
return newIP, newPort
|
||||
}
|
||||
|
||||
// predictAddr wraps IPTracker.PredictEndpoint, converting from its string-based
|
||||
// endpoint representation to IP and port types.
|
||||
func predictAddr(t *netutil.IPTracker) (net.IP, uint16) {
|
||||
ep := t.PredictEndpoint()
|
||||
if ep == "" {
|
||||
return nil, 0
|
||||
}
|
||||
ipString, portString, _ := net.SplitHostPort(ep)
|
||||
ip := net.ParseIP(ipString)
|
||||
port, err := strconv.ParseUint(portString, 10, 16)
|
||||
if err != nil {
|
||||
return nil, 0
|
||||
}
|
||||
return ip, uint16(port)
|
||||
}
|
||||
|
||||
func (ln *LocalNode) invalidate() {
|
||||
ln.cur.Store((*Node)(nil))
|
||||
}
|
||||
|
||||
func (ln *LocalNode) sign() {
|
||||
if n := ln.cur.Load().(*Node); n != nil {
|
||||
return // no changes
|
||||
}
|
||||
|
||||
var r enr.Record
|
||||
for _, e := range ln.entries {
|
||||
r.Set(e)
|
||||
}
|
||||
ln.bumpSeq()
|
||||
r.SetSeq(ln.seq)
|
||||
if err := SignV4(&r, ln.key); err != nil {
|
||||
panic(fmt.Errorf("enode: can't sign record: %v", err))
|
||||
}
|
||||
n, err := New(ValidSchemes, &r)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("enode: can't verify local record: %v", err))
|
||||
}
|
||||
ln.cur.Store(n)
|
||||
log.Info("New local node record", "seq", ln.seq, "id", n.ID(), "ip", n.IP(), "udp", n.UDP(), "tcp", n.TCP())
|
||||
}
|
||||
|
||||
func (ln *LocalNode) bumpSeq() {
|
||||
ln.seq++
|
||||
ln.db.storeLocalSeq(ln.id, ln.seq)
|
||||
}
|
||||
|
||||
// nowMilliseconds gives the current timestamp at millisecond precision.
|
||||
func nowMilliseconds() uint64 {
|
||||
ns := time.Now().UnixNano()
|
||||
if ns < 0 {
|
||||
return 0
|
||||
}
|
||||
return uint64(ns / 1000 / 1000)
|
||||
}
|
||||
279
vendor/github.com/ethereum/go-ethereum/p2p/enode/node.go
generated
vendored
Normal file
279
vendor/github.com/ethereum/go-ethereum/p2p/enode/node.go
generated
vendored
Normal file
@@ -0,0 +1,279 @@
|
||||
// Copyright 2018 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package enode
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/bits"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/p2p/enr"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
var errMissingPrefix = errors.New("missing 'enr:' prefix for base64-encoded record")
|
||||
|
||||
// Node represents a host on the network.
|
||||
type Node struct {
|
||||
r enr.Record
|
||||
id ID
|
||||
}
|
||||
|
||||
// New wraps a node record. The record must be valid according to the given
|
||||
// identity scheme.
|
||||
func New(validSchemes enr.IdentityScheme, r *enr.Record) (*Node, error) {
|
||||
if err := r.VerifySignature(validSchemes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
node := &Node{r: *r}
|
||||
if n := copy(node.id[:], validSchemes.NodeAddr(&node.r)); n != len(ID{}) {
|
||||
return nil, fmt.Errorf("invalid node ID length %d, need %d", n, len(ID{}))
|
||||
}
|
||||
return node, nil
|
||||
}
|
||||
|
||||
// MustParse parses a node record or enode:// URL. It panics if the input is invalid.
|
||||
func MustParse(rawurl string) *Node {
|
||||
n, err := Parse(ValidSchemes, rawurl)
|
||||
if err != nil {
|
||||
panic("invalid node: " + err.Error())
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// Parse decodes and verifies a base64-encoded node record.
|
||||
func Parse(validSchemes enr.IdentityScheme, input string) (*Node, error) {
|
||||
if strings.HasPrefix(input, "enode://") {
|
||||
return ParseV4(input)
|
||||
}
|
||||
if !strings.HasPrefix(input, "enr:") {
|
||||
return nil, errMissingPrefix
|
||||
}
|
||||
bin, err := base64.RawURLEncoding.DecodeString(input[4:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var r enr.Record
|
||||
if err := rlp.DecodeBytes(bin, &r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return New(validSchemes, &r)
|
||||
}
|
||||
|
||||
// ID returns the node identifier.
|
||||
func (n *Node) ID() ID {
|
||||
return n.id
|
||||
}
|
||||
|
||||
// Seq returns the sequence number of the underlying record.
|
||||
func (n *Node) Seq() uint64 {
|
||||
return n.r.Seq()
|
||||
}
|
||||
|
||||
// Incomplete returns true for nodes with no IP address.
|
||||
func (n *Node) Incomplete() bool {
|
||||
return n.IP() == nil
|
||||
}
|
||||
|
||||
// Load retrieves an entry from the underlying record.
|
||||
func (n *Node) Load(k enr.Entry) error {
|
||||
return n.r.Load(k)
|
||||
}
|
||||
|
||||
// IP returns the IP address of the node. This prefers IPv4 addresses.
|
||||
func (n *Node) IP() net.IP {
|
||||
var (
|
||||
ip4 enr.IPv4
|
||||
ip6 enr.IPv6
|
||||
)
|
||||
if n.Load(&ip4) == nil {
|
||||
return net.IP(ip4)
|
||||
}
|
||||
if n.Load(&ip6) == nil {
|
||||
return net.IP(ip6)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UDP returns the UDP port of the node.
|
||||
func (n *Node) UDP() int {
|
||||
var port enr.UDP
|
||||
n.Load(&port)
|
||||
return int(port)
|
||||
}
|
||||
|
||||
// TCP returns the TCP port of the node.
|
||||
func (n *Node) TCP() int {
|
||||
var port enr.TCP
|
||||
n.Load(&port)
|
||||
return int(port)
|
||||
}
|
||||
|
||||
// Pubkey returns the secp256k1 public key of the node, if present.
|
||||
func (n *Node) Pubkey() *ecdsa.PublicKey {
|
||||
var key ecdsa.PublicKey
|
||||
if n.Load((*Secp256k1)(&key)) != nil {
|
||||
return nil
|
||||
}
|
||||
return &key
|
||||
}
|
||||
|
||||
// Record returns the node's record. The return value is a copy and may
|
||||
// be modified by the caller.
|
||||
func (n *Node) Record() *enr.Record {
|
||||
cpy := n.r
|
||||
return &cpy
|
||||
}
|
||||
|
||||
// ValidateComplete checks whether n has a valid IP and UDP port.
|
||||
// Deprecated: don't use this method.
|
||||
func (n *Node) ValidateComplete() error {
|
||||
if n.Incomplete() {
|
||||
return errors.New("missing IP address")
|
||||
}
|
||||
if n.UDP() == 0 {
|
||||
return errors.New("missing UDP port")
|
||||
}
|
||||
ip := n.IP()
|
||||
if ip.IsMulticast() || ip.IsUnspecified() {
|
||||
return errors.New("invalid IP (multicast/unspecified)")
|
||||
}
|
||||
// Validate the node key (on curve, etc.).
|
||||
var key Secp256k1
|
||||
return n.Load(&key)
|
||||
}
|
||||
|
||||
// String returns the text representation of the record.
|
||||
func (n *Node) String() string {
|
||||
if isNewV4(n) {
|
||||
return n.URLv4() // backwards-compatibility glue for NewV4 nodes
|
||||
}
|
||||
enc, _ := rlp.EncodeToBytes(&n.r) // always succeeds because record is valid
|
||||
b64 := base64.RawURLEncoding.EncodeToString(enc)
|
||||
return "enr:" + b64
|
||||
}
|
||||
|
||||
// MarshalText implements encoding.TextMarshaler.
|
||||
func (n *Node) MarshalText() ([]byte, error) {
|
||||
return []byte(n.String()), nil
|
||||
}
|
||||
|
||||
// UnmarshalText implements encoding.TextUnmarshaler.
|
||||
func (n *Node) UnmarshalText(text []byte) error {
|
||||
dec, err := Parse(ValidSchemes, string(text))
|
||||
if err == nil {
|
||||
*n = *dec
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// ID is a unique identifier for each node.
|
||||
type ID [32]byte
|
||||
|
||||
// Bytes returns a byte slice representation of the ID
|
||||
func (n ID) Bytes() []byte {
|
||||
return n[:]
|
||||
}
|
||||
|
||||
// ID prints as a long hexadecimal number.
|
||||
func (n ID) String() string {
|
||||
return fmt.Sprintf("%x", n[:])
|
||||
}
|
||||
|
||||
// GoString returns the Go syntax representation of a ID is a call to HexID.
|
||||
func (n ID) GoString() string {
|
||||
return fmt.Sprintf("enode.HexID(\"%x\")", n[:])
|
||||
}
|
||||
|
||||
// TerminalString returns a shortened hex string for terminal logging.
|
||||
func (n ID) TerminalString() string {
|
||||
return hex.EncodeToString(n[:8])
|
||||
}
|
||||
|
||||
// MarshalText implements the encoding.TextMarshaler interface.
|
||||
func (n ID) MarshalText() ([]byte, error) {
|
||||
return []byte(hex.EncodeToString(n[:])), nil
|
||||
}
|
||||
|
||||
// UnmarshalText implements the encoding.TextUnmarshaler interface.
|
||||
func (n *ID) UnmarshalText(text []byte) error {
|
||||
id, err := ParseID(string(text))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*n = id
|
||||
return nil
|
||||
}
|
||||
|
||||
// HexID converts a hex string to an ID.
|
||||
// The string may be prefixed with 0x.
|
||||
// It panics if the string is not a valid ID.
|
||||
func HexID(in string) ID {
|
||||
id, err := ParseID(in)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
func ParseID(in string) (ID, error) {
|
||||
var id ID
|
||||
b, err := hex.DecodeString(strings.TrimPrefix(in, "0x"))
|
||||
if err != nil {
|
||||
return id, err
|
||||
} else if len(b) != len(id) {
|
||||
return id, fmt.Errorf("wrong length, want %d hex chars", len(id)*2)
|
||||
}
|
||||
copy(id[:], b)
|
||||
return id, nil
|
||||
}
|
||||
|
||||
// DistCmp compares the distances a->target and b->target.
|
||||
// Returns -1 if a is closer to target, 1 if b is closer to target
|
||||
// and 0 if they are equal.
|
||||
func DistCmp(target, a, b ID) int {
|
||||
for i := range target {
|
||||
da := a[i] ^ target[i]
|
||||
db := b[i] ^ target[i]
|
||||
if da > db {
|
||||
return 1
|
||||
} else if da < db {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// LogDist returns the logarithmic distance between a and b, log2(a ^ b).
|
||||
func LogDist(a, b ID) int {
|
||||
lz := 0
|
||||
for i := range a {
|
||||
x := a[i] ^ b[i]
|
||||
if x == 0 {
|
||||
lz += 8
|
||||
} else {
|
||||
lz += bits.LeadingZeros8(x)
|
||||
break
|
||||
}
|
||||
}
|
||||
return len(a)*8 - lz
|
||||
}
|
||||
501
vendor/github.com/ethereum/go-ethereum/p2p/enode/nodedb.go
generated
vendored
Normal file
501
vendor/github.com/ethereum/go-ethereum/p2p/enode/nodedb.go
generated
vendored
Normal file
@@ -0,0 +1,501 @@
|
||||
// Copyright 2018 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package enode
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
"github.com/syndtr/goleveldb/leveldb/errors"
|
||||
"github.com/syndtr/goleveldb/leveldb/iterator"
|
||||
"github.com/syndtr/goleveldb/leveldb/opt"
|
||||
"github.com/syndtr/goleveldb/leveldb/storage"
|
||||
"github.com/syndtr/goleveldb/leveldb/util"
|
||||
)
|
||||
|
||||
// Keys in the node database.
|
||||
const (
|
||||
dbVersionKey = "version" // Version of the database to flush if changes
|
||||
dbNodePrefix = "n:" // Identifier to prefix node entries with
|
||||
dbLocalPrefix = "local:"
|
||||
dbDiscoverRoot = "v4"
|
||||
dbDiscv5Root = "v5"
|
||||
|
||||
// These fields are stored per ID and IP, the full key is "n:<ID>:v4:<IP>:findfail".
|
||||
// Use nodeItemKey to create those keys.
|
||||
dbNodeFindFails = "findfail"
|
||||
dbNodePing = "lastping"
|
||||
dbNodePong = "lastpong"
|
||||
dbNodeSeq = "seq"
|
||||
|
||||
// Local information is keyed by ID only, the full key is "local:<ID>:seq".
|
||||
// Use localItemKey to create those keys.
|
||||
dbLocalSeq = "seq"
|
||||
)
|
||||
|
||||
const (
|
||||
dbNodeExpiration = 24 * time.Hour // Time after which an unseen node should be dropped.
|
||||
dbCleanupCycle = time.Hour // Time period for running the expiration task.
|
||||
dbVersion = 9
|
||||
)
|
||||
|
||||
var (
|
||||
errInvalidIP = errors.New("invalid IP")
|
||||
)
|
||||
|
||||
var zeroIP = make(net.IP, 16)
|
||||
|
||||
// DB is the node database, storing previously seen nodes and any collected metadata about
|
||||
// them for QoS purposes.
|
||||
type DB struct {
|
||||
lvl *leveldb.DB // Interface to the database itself
|
||||
runner sync.Once // Ensures we can start at most one expirer
|
||||
quit chan struct{} // Channel to signal the expiring thread to stop
|
||||
}
|
||||
|
||||
// OpenDB opens a node database for storing and retrieving infos about known peers in the
|
||||
// network. If no path is given an in-memory, temporary database is constructed.
|
||||
func OpenDB(path string) (*DB, error) {
|
||||
if path == "" {
|
||||
return newMemoryDB()
|
||||
}
|
||||
return newPersistentDB(path)
|
||||
}
|
||||
|
||||
// newMemoryNodeDB creates a new in-memory node database without a persistent backend.
|
||||
func newMemoryDB() (*DB, error) {
|
||||
db, err := leveldb.Open(storage.NewMemStorage(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &DB{lvl: db, quit: make(chan struct{})}, nil
|
||||
}
|
||||
|
||||
// newPersistentNodeDB creates/opens a leveldb backed persistent node database,
|
||||
// also flushing its contents in case of a version mismatch.
|
||||
func newPersistentDB(path string) (*DB, error) {
|
||||
opts := &opt.Options{OpenFilesCacheCapacity: 5}
|
||||
db, err := leveldb.OpenFile(path, opts)
|
||||
if _, iscorrupted := err.(*errors.ErrCorrupted); iscorrupted {
|
||||
db, err = leveldb.RecoverFile(path, nil)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// The nodes contained in the cache correspond to a certain protocol version.
|
||||
// Flush all nodes if the version doesn't match.
|
||||
currentVer := make([]byte, binary.MaxVarintLen64)
|
||||
currentVer = currentVer[:binary.PutVarint(currentVer, int64(dbVersion))]
|
||||
|
||||
blob, err := db.Get([]byte(dbVersionKey), nil)
|
||||
switch err {
|
||||
case leveldb.ErrNotFound:
|
||||
// Version not found (i.e. empty cache), insert it
|
||||
if err := db.Put([]byte(dbVersionKey), currentVer, nil); err != nil {
|
||||
db.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
case nil:
|
||||
// Version present, flush if different
|
||||
if !bytes.Equal(blob, currentVer) {
|
||||
db.Close()
|
||||
if err = os.RemoveAll(path); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newPersistentDB(path)
|
||||
}
|
||||
}
|
||||
return &DB{lvl: db, quit: make(chan struct{})}, nil
|
||||
}
|
||||
|
||||
// nodeKey returns the database key for a node record.
|
||||
func nodeKey(id ID) []byte {
|
||||
key := append([]byte(dbNodePrefix), id[:]...)
|
||||
key = append(key, ':')
|
||||
key = append(key, dbDiscoverRoot...)
|
||||
return key
|
||||
}
|
||||
|
||||
// splitNodeKey returns the node ID of a key created by nodeKey.
|
||||
func splitNodeKey(key []byte) (id ID, rest []byte) {
|
||||
if !bytes.HasPrefix(key, []byte(dbNodePrefix)) {
|
||||
return ID{}, nil
|
||||
}
|
||||
item := key[len(dbNodePrefix):]
|
||||
copy(id[:], item[:len(id)])
|
||||
return id, item[len(id)+1:]
|
||||
}
|
||||
|
||||
// nodeItemKey returns the database key for a node metadata field.
|
||||
func nodeItemKey(id ID, ip net.IP, field string) []byte {
|
||||
ip16 := ip.To16()
|
||||
if ip16 == nil {
|
||||
panic(fmt.Errorf("invalid IP (length %d)", len(ip)))
|
||||
}
|
||||
return bytes.Join([][]byte{nodeKey(id), ip16, []byte(field)}, []byte{':'})
|
||||
}
|
||||
|
||||
// splitNodeItemKey returns the components of a key created by nodeItemKey.
|
||||
func splitNodeItemKey(key []byte) (id ID, ip net.IP, field string) {
|
||||
id, key = splitNodeKey(key)
|
||||
// Skip discover root.
|
||||
if string(key) == dbDiscoverRoot {
|
||||
return id, nil, ""
|
||||
}
|
||||
key = key[len(dbDiscoverRoot)+1:]
|
||||
// Split out the IP.
|
||||
ip = key[:16]
|
||||
if ip4 := ip.To4(); ip4 != nil {
|
||||
ip = ip4
|
||||
}
|
||||
key = key[16+1:]
|
||||
// Field is the remainder of key.
|
||||
field = string(key)
|
||||
return id, ip, field
|
||||
}
|
||||
|
||||
func v5Key(id ID, ip net.IP, field string) []byte {
|
||||
return bytes.Join([][]byte{
|
||||
[]byte(dbNodePrefix),
|
||||
id[:],
|
||||
[]byte(dbDiscv5Root),
|
||||
ip.To16(),
|
||||
[]byte(field),
|
||||
}, []byte{':'})
|
||||
}
|
||||
|
||||
// localItemKey returns the key of a local node item.
|
||||
func localItemKey(id ID, field string) []byte {
|
||||
key := append([]byte(dbLocalPrefix), id[:]...)
|
||||
key = append(key, ':')
|
||||
key = append(key, field...)
|
||||
return key
|
||||
}
|
||||
|
||||
// fetchInt64 retrieves an integer associated with a particular key.
|
||||
func (db *DB) fetchInt64(key []byte) int64 {
|
||||
blob, err := db.lvl.Get(key, nil)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
val, read := binary.Varint(blob)
|
||||
if read <= 0 {
|
||||
return 0
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// storeInt64 stores an integer in the given key.
|
||||
func (db *DB) storeInt64(key []byte, n int64) error {
|
||||
blob := make([]byte, binary.MaxVarintLen64)
|
||||
blob = blob[:binary.PutVarint(blob, n)]
|
||||
return db.lvl.Put(key, blob, nil)
|
||||
}
|
||||
|
||||
// fetchUint64 retrieves an integer associated with a particular key.
|
||||
func (db *DB) fetchUint64(key []byte) uint64 {
|
||||
blob, err := db.lvl.Get(key, nil)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
val, _ := binary.Uvarint(blob)
|
||||
return val
|
||||
}
|
||||
|
||||
// storeUint64 stores an integer in the given key.
|
||||
func (db *DB) storeUint64(key []byte, n uint64) error {
|
||||
blob := make([]byte, binary.MaxVarintLen64)
|
||||
blob = blob[:binary.PutUvarint(blob, n)]
|
||||
return db.lvl.Put(key, blob, nil)
|
||||
}
|
||||
|
||||
// Node retrieves a node with a given id from the database.
|
||||
func (db *DB) Node(id ID) *Node {
|
||||
blob, err := db.lvl.Get(nodeKey(id), nil)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return mustDecodeNode(id[:], blob)
|
||||
}
|
||||
|
||||
func mustDecodeNode(id, data []byte) *Node {
|
||||
node := new(Node)
|
||||
if err := rlp.DecodeBytes(data, &node.r); err != nil {
|
||||
panic(fmt.Errorf("p2p/enode: can't decode node %x in DB: %v", id, err))
|
||||
}
|
||||
// Restore node id cache.
|
||||
copy(node.id[:], id)
|
||||
return node
|
||||
}
|
||||
|
||||
// UpdateNode inserts - potentially overwriting - a node into the peer database.
|
||||
func (db *DB) UpdateNode(node *Node) error {
|
||||
if node.Seq() < db.NodeSeq(node.ID()) {
|
||||
return nil
|
||||
}
|
||||
blob, err := rlp.EncodeToBytes(&node.r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := db.lvl.Put(nodeKey(node.ID()), blob, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
return db.storeUint64(nodeItemKey(node.ID(), zeroIP, dbNodeSeq), node.Seq())
|
||||
}
|
||||
|
||||
// NodeSeq returns the stored record sequence number of the given node.
|
||||
func (db *DB) NodeSeq(id ID) uint64 {
|
||||
return db.fetchUint64(nodeItemKey(id, zeroIP, dbNodeSeq))
|
||||
}
|
||||
|
||||
// Resolve returns the stored record of the node if it has a larger sequence
|
||||
// number than n.
|
||||
func (db *DB) Resolve(n *Node) *Node {
|
||||
if n.Seq() > db.NodeSeq(n.ID()) {
|
||||
return n
|
||||
}
|
||||
return db.Node(n.ID())
|
||||
}
|
||||
|
||||
// DeleteNode deletes all information associated with a node.
|
||||
func (db *DB) DeleteNode(id ID) {
|
||||
deleteRange(db.lvl, nodeKey(id))
|
||||
}
|
||||
|
||||
func deleteRange(db *leveldb.DB, prefix []byte) {
|
||||
it := db.NewIterator(util.BytesPrefix(prefix), nil)
|
||||
defer it.Release()
|
||||
for it.Next() {
|
||||
db.Delete(it.Key(), nil)
|
||||
}
|
||||
}
|
||||
|
||||
// ensureExpirer is a small helper method ensuring that the data expiration
|
||||
// mechanism is running. If the expiration goroutine is already running, this
|
||||
// method simply returns.
|
||||
//
|
||||
// The goal is to start the data evacuation only after the network successfully
|
||||
// bootstrapped itself (to prevent dumping potentially useful seed nodes). Since
|
||||
// it would require significant overhead to exactly trace the first successful
|
||||
// convergence, it's simpler to "ensure" the correct state when an appropriate
|
||||
// condition occurs (i.e. a successful bonding), and discard further events.
|
||||
func (db *DB) ensureExpirer() {
|
||||
db.runner.Do(func() { go db.expirer() })
|
||||
}
|
||||
|
||||
// expirer should be started in a go routine, and is responsible for looping ad
|
||||
// infinitum and dropping stale data from the database.
|
||||
func (db *DB) expirer() {
|
||||
tick := time.NewTicker(dbCleanupCycle)
|
||||
defer tick.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-tick.C:
|
||||
db.expireNodes()
|
||||
case <-db.quit:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// expireNodes iterates over the database and deletes all nodes that have not
|
||||
// been seen (i.e. received a pong from) for some time.
|
||||
func (db *DB) expireNodes() {
|
||||
it := db.lvl.NewIterator(util.BytesPrefix([]byte(dbNodePrefix)), nil)
|
||||
defer it.Release()
|
||||
if !it.Next() {
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
threshold = time.Now().Add(-dbNodeExpiration).Unix()
|
||||
youngestPong int64
|
||||
atEnd = false
|
||||
)
|
||||
for !atEnd {
|
||||
id, ip, field := splitNodeItemKey(it.Key())
|
||||
if field == dbNodePong {
|
||||
time, _ := binary.Varint(it.Value())
|
||||
if time > youngestPong {
|
||||
youngestPong = time
|
||||
}
|
||||
if time < threshold {
|
||||
// Last pong from this IP older than threshold, remove fields belonging to it.
|
||||
deleteRange(db.lvl, nodeItemKey(id, ip, ""))
|
||||
}
|
||||
}
|
||||
atEnd = !it.Next()
|
||||
nextID, _ := splitNodeKey(it.Key())
|
||||
if atEnd || nextID != id {
|
||||
// We've moved beyond the last entry of the current ID.
|
||||
// Remove everything if there was no recent enough pong.
|
||||
if youngestPong > 0 && youngestPong < threshold {
|
||||
deleteRange(db.lvl, nodeKey(id))
|
||||
}
|
||||
youngestPong = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// LastPingReceived retrieves the time of the last ping packet received from
|
||||
// a remote node.
|
||||
func (db *DB) LastPingReceived(id ID, ip net.IP) time.Time {
|
||||
if ip = ip.To16(); ip == nil {
|
||||
return time.Time{}
|
||||
}
|
||||
return time.Unix(db.fetchInt64(nodeItemKey(id, ip, dbNodePing)), 0)
|
||||
}
|
||||
|
||||
// UpdateLastPingReceived updates the last time we tried contacting a remote node.
|
||||
func (db *DB) UpdateLastPingReceived(id ID, ip net.IP, instance time.Time) error {
|
||||
if ip = ip.To16(); ip == nil {
|
||||
return errInvalidIP
|
||||
}
|
||||
return db.storeInt64(nodeItemKey(id, ip, dbNodePing), instance.Unix())
|
||||
}
|
||||
|
||||
// LastPongReceived retrieves the time of the last successful pong from remote node.
|
||||
func (db *DB) LastPongReceived(id ID, ip net.IP) time.Time {
|
||||
if ip = ip.To16(); ip == nil {
|
||||
return time.Time{}
|
||||
}
|
||||
// Launch expirer
|
||||
db.ensureExpirer()
|
||||
return time.Unix(db.fetchInt64(nodeItemKey(id, ip, dbNodePong)), 0)
|
||||
}
|
||||
|
||||
// UpdateLastPongReceived updates the last pong time of a node.
|
||||
func (db *DB) UpdateLastPongReceived(id ID, ip net.IP, instance time.Time) error {
|
||||
if ip = ip.To16(); ip == nil {
|
||||
return errInvalidIP
|
||||
}
|
||||
return db.storeInt64(nodeItemKey(id, ip, dbNodePong), instance.Unix())
|
||||
}
|
||||
|
||||
// FindFails retrieves the number of findnode failures since bonding.
|
||||
func (db *DB) FindFails(id ID, ip net.IP) int {
|
||||
if ip = ip.To16(); ip == nil {
|
||||
return 0
|
||||
}
|
||||
return int(db.fetchInt64(nodeItemKey(id, ip, dbNodeFindFails)))
|
||||
}
|
||||
|
||||
// UpdateFindFails updates the number of findnode failures since bonding.
|
||||
func (db *DB) UpdateFindFails(id ID, ip net.IP, fails int) error {
|
||||
if ip = ip.To16(); ip == nil {
|
||||
return errInvalidIP
|
||||
}
|
||||
return db.storeInt64(nodeItemKey(id, ip, dbNodeFindFails), int64(fails))
|
||||
}
|
||||
|
||||
// FindFailsV5 retrieves the discv5 findnode failure counter.
|
||||
func (db *DB) FindFailsV5(id ID, ip net.IP) int {
|
||||
if ip = ip.To16(); ip == nil {
|
||||
return 0
|
||||
}
|
||||
return int(db.fetchInt64(v5Key(id, ip, dbNodeFindFails)))
|
||||
}
|
||||
|
||||
// UpdateFindFailsV5 stores the discv5 findnode failure counter.
|
||||
func (db *DB) UpdateFindFailsV5(id ID, ip net.IP, fails int) error {
|
||||
if ip = ip.To16(); ip == nil {
|
||||
return errInvalidIP
|
||||
}
|
||||
return db.storeInt64(v5Key(id, ip, dbNodeFindFails), int64(fails))
|
||||
}
|
||||
|
||||
// localSeq retrieves the local record sequence counter, defaulting to the current
|
||||
// timestamp if no previous exists. This ensures that wiping all data associated
|
||||
// with a node (apart from its key) will not generate already used sequence nums.
|
||||
func (db *DB) localSeq(id ID) uint64 {
|
||||
if seq := db.fetchUint64(localItemKey(id, dbLocalSeq)); seq > 0 {
|
||||
return seq
|
||||
}
|
||||
return nowMilliseconds()
|
||||
}
|
||||
|
||||
// storeLocalSeq stores the local record sequence counter.
|
||||
func (db *DB) storeLocalSeq(id ID, n uint64) {
|
||||
db.storeUint64(localItemKey(id, dbLocalSeq), n)
|
||||
}
|
||||
|
||||
// QuerySeeds retrieves random nodes to be used as potential seed nodes
|
||||
// for bootstrapping.
|
||||
func (db *DB) QuerySeeds(n int, maxAge time.Duration) []*Node {
|
||||
var (
|
||||
now = time.Now()
|
||||
nodes = make([]*Node, 0, n)
|
||||
it = db.lvl.NewIterator(nil, nil)
|
||||
id ID
|
||||
)
|
||||
defer it.Release()
|
||||
|
||||
seek:
|
||||
for seeks := 0; len(nodes) < n && seeks < n*5; seeks++ {
|
||||
// Seek to a random entry. The first byte is incremented by a
|
||||
// random amount each time in order to increase the likelihood
|
||||
// of hitting all existing nodes in very small databases.
|
||||
ctr := id[0]
|
||||
rand.Read(id[:])
|
||||
id[0] = ctr + id[0]%16
|
||||
it.Seek(nodeKey(id))
|
||||
|
||||
n := nextNode(it)
|
||||
if n == nil {
|
||||
id[0] = 0
|
||||
continue seek // iterator exhausted
|
||||
}
|
||||
if now.Sub(db.LastPongReceived(n.ID(), n.IP())) > maxAge {
|
||||
continue seek
|
||||
}
|
||||
for i := range nodes {
|
||||
if nodes[i].ID() == n.ID() {
|
||||
continue seek // duplicate
|
||||
}
|
||||
}
|
||||
nodes = append(nodes, n)
|
||||
}
|
||||
return nodes
|
||||
}
|
||||
|
||||
// reads the next node record from the iterator, skipping over other
|
||||
// database entries.
|
||||
func nextNode(it iterator.Iterator) *Node {
|
||||
for end := false; !end; end = !it.Next() {
|
||||
id, rest := splitNodeKey(it.Key())
|
||||
if string(rest) != dbDiscoverRoot {
|
||||
continue
|
||||
}
|
||||
return mustDecodeNode(id[:], it.Value())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close flushes and closes the database files.
|
||||
func (db *DB) Close() {
|
||||
close(db.quit)
|
||||
db.lvl.Close()
|
||||
}
|
||||
203
vendor/github.com/ethereum/go-ethereum/p2p/enode/urlv4.go
generated
vendored
Normal file
203
vendor/github.com/ethereum/go-ethereum/p2p/enode/urlv4.go
generated
vendored
Normal file
@@ -0,0 +1,203 @@
|
||||
// Copyright 2018 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package enode
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/p2p/enr"
|
||||
)
|
||||
|
||||
var (
|
||||
incompleteNodeURL = regexp.MustCompile("(?i)^(?:enode://)?([0-9a-f]+)$")
|
||||
lookupIPFunc = net.LookupIP
|
||||
)
|
||||
|
||||
// MustParseV4 parses a node URL. It panics if the URL is not valid.
|
||||
func MustParseV4(rawurl string) *Node {
|
||||
n, err := ParseV4(rawurl)
|
||||
if err != nil {
|
||||
panic("invalid node URL: " + err.Error())
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// ParseV4 parses a node URL.
|
||||
//
|
||||
// There are two basic forms of node URLs:
|
||||
//
|
||||
// - incomplete nodes, which only have the public key (node ID)
|
||||
// - complete nodes, which contain the public key and IP/Port information
|
||||
//
|
||||
// For incomplete nodes, the designator must look like one of these
|
||||
//
|
||||
// enode://<hex node id>
|
||||
// <hex node id>
|
||||
//
|
||||
// For complete nodes, the node ID is encoded in the username portion
|
||||
// of the URL, separated from the host by an @ sign. The hostname can
|
||||
// only be given as an IP address or using DNS domain name.
|
||||
// The port in the host name section is the TCP listening port. If the
|
||||
// TCP and UDP (discovery) ports differ, the UDP port is specified as
|
||||
// query parameter "discport".
|
||||
//
|
||||
// In the following example, the node URL describes
|
||||
// a node with IP address 10.3.58.6, TCP listening port 30303
|
||||
// and UDP discovery port 30301.
|
||||
//
|
||||
// enode://<hex node id>@10.3.58.6:30303?discport=30301
|
||||
func ParseV4(rawurl string) (*Node, error) {
|
||||
if m := incompleteNodeURL.FindStringSubmatch(rawurl); m != nil {
|
||||
id, err := parsePubkey(m[1])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid public key (%v)", err)
|
||||
}
|
||||
return NewV4(id, nil, 0, 0), nil
|
||||
}
|
||||
return parseComplete(rawurl)
|
||||
}
|
||||
|
||||
// NewV4 creates a node from discovery v4 node information. The record
|
||||
// contained in the node has a zero-length signature.
|
||||
func NewV4(pubkey *ecdsa.PublicKey, ip net.IP, tcp, udp int) *Node {
|
||||
var r enr.Record
|
||||
if len(ip) > 0 {
|
||||
r.Set(enr.IP(ip))
|
||||
}
|
||||
if udp != 0 {
|
||||
r.Set(enr.UDP(udp))
|
||||
}
|
||||
if tcp != 0 {
|
||||
r.Set(enr.TCP(tcp))
|
||||
}
|
||||
signV4Compat(&r, pubkey)
|
||||
n, err := New(v4CompatID{}, &r)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// isNewV4 returns true for nodes created by NewV4.
|
||||
func isNewV4(n *Node) bool {
|
||||
var k s256raw
|
||||
return n.r.IdentityScheme() == "" && n.r.Load(&k) == nil && len(n.r.Signature()) == 0
|
||||
}
|
||||
|
||||
func parseComplete(rawurl string) (*Node, error) {
|
||||
var (
|
||||
id *ecdsa.PublicKey
|
||||
tcpPort, udpPort uint64
|
||||
)
|
||||
u, err := url.Parse(rawurl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if u.Scheme != "enode" {
|
||||
return nil, errors.New("invalid URL scheme, want \"enode\"")
|
||||
}
|
||||
// Parse the Node ID from the user portion.
|
||||
if u.User == nil {
|
||||
return nil, errors.New("does not contain node ID")
|
||||
}
|
||||
if id, err = parsePubkey(u.User.String()); err != nil {
|
||||
return nil, fmt.Errorf("invalid public key (%v)", err)
|
||||
}
|
||||
// Parse the IP address.
|
||||
ip := net.ParseIP(u.Hostname())
|
||||
if ip == nil {
|
||||
ips, err := lookupIPFunc(u.Hostname())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ip = ips[0]
|
||||
}
|
||||
// Ensure the IP is 4 bytes long for IPv4 addresses.
|
||||
if ipv4 := ip.To4(); ipv4 != nil {
|
||||
ip = ipv4
|
||||
}
|
||||
// Parse the port numbers.
|
||||
if tcpPort, err = strconv.ParseUint(u.Port(), 10, 16); err != nil {
|
||||
return nil, errors.New("invalid port")
|
||||
}
|
||||
udpPort = tcpPort
|
||||
qv := u.Query()
|
||||
if qv.Get("discport") != "" {
|
||||
udpPort, err = strconv.ParseUint(qv.Get("discport"), 10, 16)
|
||||
if err != nil {
|
||||
return nil, errors.New("invalid discport in query")
|
||||
}
|
||||
}
|
||||
return NewV4(id, ip, int(tcpPort), int(udpPort)), nil
|
||||
}
|
||||
|
||||
// parsePubkey parses a hex-encoded secp256k1 public key.
|
||||
func parsePubkey(in string) (*ecdsa.PublicKey, error) {
|
||||
b, err := hex.DecodeString(in)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if len(b) != 64 {
|
||||
return nil, fmt.Errorf("wrong length, want %d hex chars", 128)
|
||||
}
|
||||
b = append([]byte{0x4}, b...)
|
||||
return crypto.UnmarshalPubkey(b)
|
||||
}
|
||||
|
||||
func (n *Node) URLv4() string {
|
||||
var (
|
||||
scheme enr.ID
|
||||
nodeid string
|
||||
key ecdsa.PublicKey
|
||||
)
|
||||
n.Load(&scheme)
|
||||
n.Load((*Secp256k1)(&key))
|
||||
switch {
|
||||
case scheme == "v4" || key != ecdsa.PublicKey{}:
|
||||
nodeid = fmt.Sprintf("%x", crypto.FromECDSAPub(&key)[1:])
|
||||
default:
|
||||
nodeid = fmt.Sprintf("%s.%x", scheme, n.id[:])
|
||||
}
|
||||
u := url.URL{Scheme: "enode"}
|
||||
if n.Incomplete() {
|
||||
u.Host = nodeid
|
||||
} else {
|
||||
addr := net.TCPAddr{IP: n.IP(), Port: n.TCP()}
|
||||
u.User = url.User(nodeid)
|
||||
u.Host = addr.String()
|
||||
if n.UDP() != n.TCP() {
|
||||
u.RawQuery = "discport=" + strconv.Itoa(n.UDP())
|
||||
}
|
||||
}
|
||||
return u.String()
|
||||
}
|
||||
|
||||
// PubkeyToIDV4 derives the v4 node address from the given public key.
|
||||
func PubkeyToIDV4(key *ecdsa.PublicKey) ID {
|
||||
e := make([]byte, 64)
|
||||
math.ReadBits(key.X, e[:len(e)/2])
|
||||
math.ReadBits(key.Y, e[len(e)/2:])
|
||||
return ID(crypto.Keccak256Hash(e))
|
||||
}
|
||||
317
vendor/github.com/ethereum/go-ethereum/p2p/enr/enr.go
generated
vendored
Normal file
317
vendor/github.com/ethereum/go-ethereum/p2p/enr/enr.go
generated
vendored
Normal file
@@ -0,0 +1,317 @@
|
||||
// Copyright 2017 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Package enr implements Ethereum Node Records as defined in EIP-778. A node record holds
|
||||
// arbitrary information about a node on the peer-to-peer network. Node information is
|
||||
// stored in key/value pairs. To store and retrieve key/values in a record, use the Entry
|
||||
// interface.
|
||||
//
|
||||
// # Signature Handling
|
||||
//
|
||||
// Records must be signed before transmitting them to another node.
|
||||
//
|
||||
// Decoding a record doesn't check its signature. Code working with records from an
|
||||
// untrusted source must always verify two things: that the record uses an identity scheme
|
||||
// deemed secure, and that the signature is valid according to the declared scheme.
|
||||
//
|
||||
// When creating a record, set the entries you want and use a signing function provided by
|
||||
// the identity scheme to add the signature. Modifying a record invalidates the signature.
|
||||
//
|
||||
// Package enr supports the "secp256k1-keccak" identity scheme.
|
||||
package enr
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
const SizeLimit = 300 // maximum encoded size of a node record in bytes
|
||||
|
||||
var (
|
||||
ErrInvalidSig = errors.New("invalid signature on node record")
|
||||
errNotSorted = errors.New("record key/value pairs are not sorted by key")
|
||||
errDuplicateKey = errors.New("record contains duplicate key")
|
||||
errIncompletePair = errors.New("record contains incomplete k/v pair")
|
||||
errIncompleteList = errors.New("record contains less than two list elements")
|
||||
errTooBig = fmt.Errorf("record bigger than %d bytes", SizeLimit)
|
||||
errEncodeUnsigned = errors.New("can't encode unsigned record")
|
||||
errNotFound = errors.New("no such key in record")
|
||||
)
|
||||
|
||||
// An IdentityScheme is capable of verifying record signatures and
|
||||
// deriving node addresses.
|
||||
type IdentityScheme interface {
|
||||
Verify(r *Record, sig []byte) error
|
||||
NodeAddr(r *Record) []byte
|
||||
}
|
||||
|
||||
// SchemeMap is a registry of named identity schemes.
|
||||
type SchemeMap map[string]IdentityScheme
|
||||
|
||||
func (m SchemeMap) Verify(r *Record, sig []byte) error {
|
||||
s := m[r.IdentityScheme()]
|
||||
if s == nil {
|
||||
return ErrInvalidSig
|
||||
}
|
||||
return s.Verify(r, sig)
|
||||
}
|
||||
|
||||
func (m SchemeMap) NodeAddr(r *Record) []byte {
|
||||
s := m[r.IdentityScheme()]
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
return s.NodeAddr(r)
|
||||
}
|
||||
|
||||
// Record represents a node record. The zero value is an empty record.
|
||||
type Record struct {
|
||||
seq uint64 // sequence number
|
||||
signature []byte // the signature
|
||||
raw []byte // RLP encoded record
|
||||
pairs []pair // sorted list of all key/value pairs
|
||||
}
|
||||
|
||||
// pair is a key/value pair in a record.
|
||||
type pair struct {
|
||||
k string
|
||||
v rlp.RawValue
|
||||
}
|
||||
|
||||
// Seq returns the sequence number.
|
||||
func (r *Record) Seq() uint64 {
|
||||
return r.seq
|
||||
}
|
||||
|
||||
// SetSeq updates the record sequence number. This invalidates any signature on the record.
|
||||
// Calling SetSeq is usually not required because setting any key in a signed record
|
||||
// increments the sequence number.
|
||||
func (r *Record) SetSeq(s uint64) {
|
||||
r.signature = nil
|
||||
r.raw = nil
|
||||
r.seq = s
|
||||
}
|
||||
|
||||
// Load retrieves the value of a key/value pair. The given Entry must be a pointer and will
|
||||
// be set to the value of the entry in the record.
|
||||
//
|
||||
// Errors returned by Load are wrapped in KeyError. You can distinguish decoding errors
|
||||
// from missing keys using the IsNotFound function.
|
||||
func (r *Record) Load(e Entry) error {
|
||||
i := sort.Search(len(r.pairs), func(i int) bool { return r.pairs[i].k >= e.ENRKey() })
|
||||
if i < len(r.pairs) && r.pairs[i].k == e.ENRKey() {
|
||||
if err := rlp.DecodeBytes(r.pairs[i].v, e); err != nil {
|
||||
return &KeyError{Key: e.ENRKey(), Err: err}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return &KeyError{Key: e.ENRKey(), Err: errNotFound}
|
||||
}
|
||||
|
||||
// Set adds or updates the given entry in the record. It panics if the value can't be
|
||||
// encoded. If the record is signed, Set increments the sequence number and invalidates
|
||||
// the sequence number.
|
||||
func (r *Record) Set(e Entry) {
|
||||
blob, err := rlp.EncodeToBytes(e)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("enr: can't encode %s: %v", e.ENRKey(), err))
|
||||
}
|
||||
r.invalidate()
|
||||
|
||||
pairs := make([]pair, len(r.pairs))
|
||||
copy(pairs, r.pairs)
|
||||
i := sort.Search(len(pairs), func(i int) bool { return pairs[i].k >= e.ENRKey() })
|
||||
switch {
|
||||
case i < len(pairs) && pairs[i].k == e.ENRKey():
|
||||
// element is present at r.pairs[i]
|
||||
pairs[i].v = blob
|
||||
case i < len(r.pairs):
|
||||
// insert pair before i-th elem
|
||||
el := pair{e.ENRKey(), blob}
|
||||
pairs = append(pairs, pair{})
|
||||
copy(pairs[i+1:], pairs[i:])
|
||||
pairs[i] = el
|
||||
default:
|
||||
// element should be placed at the end of r.pairs
|
||||
pairs = append(pairs, pair{e.ENRKey(), blob})
|
||||
}
|
||||
r.pairs = pairs
|
||||
}
|
||||
|
||||
func (r *Record) invalidate() {
|
||||
if r.signature != nil {
|
||||
r.seq++
|
||||
}
|
||||
r.signature = nil
|
||||
r.raw = nil
|
||||
}
|
||||
|
||||
// Signature returns the signature of the record.
|
||||
func (r *Record) Signature() []byte {
|
||||
if r.signature == nil {
|
||||
return nil
|
||||
}
|
||||
cpy := make([]byte, len(r.signature))
|
||||
copy(cpy, r.signature)
|
||||
return cpy
|
||||
}
|
||||
|
||||
// EncodeRLP implements rlp.Encoder. Encoding fails if
|
||||
// the record is unsigned.
|
||||
func (r Record) EncodeRLP(w io.Writer) error {
|
||||
if r.signature == nil {
|
||||
return errEncodeUnsigned
|
||||
}
|
||||
_, err := w.Write(r.raw)
|
||||
return err
|
||||
}
|
||||
|
||||
// DecodeRLP implements rlp.Decoder. Decoding doesn't verify the signature.
|
||||
func (r *Record) DecodeRLP(s *rlp.Stream) error {
|
||||
dec, raw, err := decodeRecord(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*r = dec
|
||||
r.raw = raw
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeRecord(s *rlp.Stream) (dec Record, raw []byte, err error) {
|
||||
raw, err = s.Raw()
|
||||
if err != nil {
|
||||
return dec, raw, err
|
||||
}
|
||||
if len(raw) > SizeLimit {
|
||||
return dec, raw, errTooBig
|
||||
}
|
||||
|
||||
// Decode the RLP container.
|
||||
s = rlp.NewStream(bytes.NewReader(raw), 0)
|
||||
if _, err := s.List(); err != nil {
|
||||
return dec, raw, err
|
||||
}
|
||||
if err = s.Decode(&dec.signature); err != nil {
|
||||
if err == rlp.EOL {
|
||||
err = errIncompleteList
|
||||
}
|
||||
return dec, raw, err
|
||||
}
|
||||
if err = s.Decode(&dec.seq); err != nil {
|
||||
if err == rlp.EOL {
|
||||
err = errIncompleteList
|
||||
}
|
||||
return dec, raw, err
|
||||
}
|
||||
// The rest of the record contains sorted k/v pairs.
|
||||
var prevkey string
|
||||
for i := 0; ; i++ {
|
||||
var kv pair
|
||||
if err := s.Decode(&kv.k); err != nil {
|
||||
if err == rlp.EOL {
|
||||
break
|
||||
}
|
||||
return dec, raw, err
|
||||
}
|
||||
if err := s.Decode(&kv.v); err != nil {
|
||||
if err == rlp.EOL {
|
||||
return dec, raw, errIncompletePair
|
||||
}
|
||||
return dec, raw, err
|
||||
}
|
||||
if i > 0 {
|
||||
if kv.k == prevkey {
|
||||
return dec, raw, errDuplicateKey
|
||||
}
|
||||
if kv.k < prevkey {
|
||||
return dec, raw, errNotSorted
|
||||
}
|
||||
}
|
||||
dec.pairs = append(dec.pairs, kv)
|
||||
prevkey = kv.k
|
||||
}
|
||||
return dec, raw, s.ListEnd()
|
||||
}
|
||||
|
||||
// IdentityScheme returns the name of the identity scheme in the record.
|
||||
func (r *Record) IdentityScheme() string {
|
||||
var id ID
|
||||
r.Load(&id)
|
||||
return string(id)
|
||||
}
|
||||
|
||||
// VerifySignature checks whether the record is signed using the given identity scheme.
|
||||
func (r *Record) VerifySignature(s IdentityScheme) error {
|
||||
return s.Verify(r, r.signature)
|
||||
}
|
||||
|
||||
// SetSig sets the record signature. It returns an error if the encoded record is larger
|
||||
// than the size limit or if the signature is invalid according to the passed scheme.
|
||||
//
|
||||
// You can also use SetSig to remove the signature explicitly by passing a nil scheme
|
||||
// and signature.
|
||||
//
|
||||
// SetSig panics when either the scheme or the signature (but not both) are nil.
|
||||
func (r *Record) SetSig(s IdentityScheme, sig []byte) error {
|
||||
switch {
|
||||
// Prevent storing invalid data.
|
||||
case s == nil && sig != nil:
|
||||
panic("enr: invalid call to SetSig with non-nil signature but nil scheme")
|
||||
case s != nil && sig == nil:
|
||||
panic("enr: invalid call to SetSig with nil signature but non-nil scheme")
|
||||
// Verify if we have a scheme.
|
||||
case s != nil:
|
||||
if err := s.Verify(r, sig); err != nil {
|
||||
return err
|
||||
}
|
||||
raw, err := r.encode(sig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.signature, r.raw = sig, raw
|
||||
// Reset otherwise.
|
||||
default:
|
||||
r.signature, r.raw = nil, nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AppendElements appends the sequence number and entries to the given slice.
|
||||
func (r *Record) AppendElements(list []interface{}) []interface{} {
|
||||
list = append(list, r.seq)
|
||||
for _, p := range r.pairs {
|
||||
list = append(list, p.k, p.v)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
func (r *Record) encode(sig []byte) (raw []byte, err error) {
|
||||
list := make([]interface{}, 1, 2*len(r.pairs)+2)
|
||||
list[0] = sig
|
||||
list = r.AppendElements(list)
|
||||
if raw, err = rlp.EncodeToBytes(list); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(raw) > SizeLimit {
|
||||
return nil, errTooBig
|
||||
}
|
||||
return raw, nil
|
||||
}
|
||||
196
vendor/github.com/ethereum/go-ethereum/p2p/enr/entries.go
generated
vendored
Normal file
196
vendor/github.com/ethereum/go-ethereum/p2p/enr/entries.go
generated
vendored
Normal file
@@ -0,0 +1,196 @@
|
||||
// Copyright 2017 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package enr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
// Entry is implemented by known node record entry types.
|
||||
//
|
||||
// To define a new entry that is to be included in a node record,
|
||||
// create a Go type that satisfies this interface. The type should
|
||||
// also implement rlp.Decoder if additional checks are needed on the value.
|
||||
type Entry interface {
|
||||
ENRKey() string
|
||||
}
|
||||
|
||||
type generic struct {
|
||||
key string
|
||||
value interface{}
|
||||
}
|
||||
|
||||
func (g generic) ENRKey() string { return g.key }
|
||||
|
||||
func (g generic) EncodeRLP(w io.Writer) error {
|
||||
return rlp.Encode(w, g.value)
|
||||
}
|
||||
|
||||
func (g *generic) DecodeRLP(s *rlp.Stream) error {
|
||||
return s.Decode(g.value)
|
||||
}
|
||||
|
||||
// WithEntry wraps any value with a key name. It can be used to set and load arbitrary values
|
||||
// in a record. The value v must be supported by rlp. To use WithEntry with Load, the value
|
||||
// must be a pointer.
|
||||
func WithEntry(k string, v interface{}) Entry {
|
||||
return &generic{key: k, value: v}
|
||||
}
|
||||
|
||||
// TCP is the "tcp" key, which holds the TCP port of the node.
|
||||
type TCP uint16
|
||||
|
||||
func (v TCP) ENRKey() string { return "tcp" }
|
||||
|
||||
// TCP6 is the "tcp6" key, which holds the IPv6-specific tcp6 port of the node.
|
||||
type TCP6 uint16
|
||||
|
||||
func (v TCP6) ENRKey() string { return "tcp6" }
|
||||
|
||||
// UDP is the "udp" key, which holds the UDP port of the node.
|
||||
type UDP uint16
|
||||
|
||||
func (v UDP) ENRKey() string { return "udp" }
|
||||
|
||||
// UDP6 is the "udp6" key, which holds the IPv6-specific UDP port of the node.
|
||||
type UDP6 uint16
|
||||
|
||||
func (v UDP6) ENRKey() string { return "udp6" }
|
||||
|
||||
// ID is the "id" key, which holds the name of the identity scheme.
|
||||
type ID string
|
||||
|
||||
const IDv4 = ID("v4") // the default identity scheme
|
||||
|
||||
func (v ID) ENRKey() string { return "id" }
|
||||
|
||||
// IP is either the "ip" or "ip6" key, depending on the value.
|
||||
// Use this value to encode IP addresses that can be either v4 or v6.
|
||||
// To load an address from a record use the IPv4 or IPv6 types.
|
||||
type IP net.IP
|
||||
|
||||
func (v IP) ENRKey() string {
|
||||
if net.IP(v).To4() == nil {
|
||||
return "ip6"
|
||||
}
|
||||
return "ip"
|
||||
}
|
||||
|
||||
// EncodeRLP implements rlp.Encoder.
|
||||
func (v IP) EncodeRLP(w io.Writer) error {
|
||||
if ip4 := net.IP(v).To4(); ip4 != nil {
|
||||
return rlp.Encode(w, ip4)
|
||||
}
|
||||
if ip6 := net.IP(v).To16(); ip6 != nil {
|
||||
return rlp.Encode(w, ip6)
|
||||
}
|
||||
return fmt.Errorf("invalid IP address: %v", net.IP(v))
|
||||
}
|
||||
|
||||
// DecodeRLP implements rlp.Decoder.
|
||||
func (v *IP) DecodeRLP(s *rlp.Stream) error {
|
||||
if err := s.Decode((*net.IP)(v)); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(*v) != 4 && len(*v) != 16 {
|
||||
return fmt.Errorf("invalid IP address, want 4 or 16 bytes: %v", *v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IPv4 is the "ip" key, which holds the IP address of the node.
|
||||
type IPv4 net.IP
|
||||
|
||||
func (v IPv4) ENRKey() string { return "ip" }
|
||||
|
||||
// EncodeRLP implements rlp.Encoder.
|
||||
func (v IPv4) EncodeRLP(w io.Writer) error {
|
||||
ip4 := net.IP(v).To4()
|
||||
if ip4 == nil {
|
||||
return fmt.Errorf("invalid IPv4 address: %v", net.IP(v))
|
||||
}
|
||||
return rlp.Encode(w, ip4)
|
||||
}
|
||||
|
||||
// DecodeRLP implements rlp.Decoder.
|
||||
func (v *IPv4) DecodeRLP(s *rlp.Stream) error {
|
||||
if err := s.Decode((*net.IP)(v)); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(*v) != 4 {
|
||||
return fmt.Errorf("invalid IPv4 address, want 4 bytes: %v", *v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IPv6 is the "ip6" key, which holds the IP address of the node.
|
||||
type IPv6 net.IP
|
||||
|
||||
func (v IPv6) ENRKey() string { return "ip6" }
|
||||
|
||||
// EncodeRLP implements rlp.Encoder.
|
||||
func (v IPv6) EncodeRLP(w io.Writer) error {
|
||||
ip6 := net.IP(v).To16()
|
||||
if ip6 == nil {
|
||||
return fmt.Errorf("invalid IPv6 address: %v", net.IP(v))
|
||||
}
|
||||
return rlp.Encode(w, ip6)
|
||||
}
|
||||
|
||||
// DecodeRLP implements rlp.Decoder.
|
||||
func (v *IPv6) DecodeRLP(s *rlp.Stream) error {
|
||||
if err := s.Decode((*net.IP)(v)); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(*v) != 16 {
|
||||
return fmt.Errorf("invalid IPv6 address, want 16 bytes: %v", *v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// KeyError is an error related to a key.
|
||||
type KeyError struct {
|
||||
Key string
|
||||
Err error
|
||||
}
|
||||
|
||||
// Error implements error.
|
||||
func (err *KeyError) Error() string {
|
||||
if err.Err == errNotFound {
|
||||
return fmt.Sprintf("missing ENR key %q", err.Key)
|
||||
}
|
||||
return fmt.Sprintf("ENR key %q: %v", err.Key, err.Err)
|
||||
}
|
||||
|
||||
func (err *KeyError) Unwrap() error {
|
||||
return err.Err
|
||||
}
|
||||
|
||||
// IsNotFound reports whether the given error means that a key/value pair is
|
||||
// missing from a record.
|
||||
func IsNotFound(err error) bool {
|
||||
var ke *KeyError
|
||||
if errors.As(err, &ke) {
|
||||
return ke.Err == errNotFound
|
||||
}
|
||||
return false
|
||||
}
|
||||
326
vendor/github.com/ethereum/go-ethereum/p2p/message.go
generated
vendored
Normal file
326
vendor/github.com/ethereum/go-ethereum/p2p/message.go
generated
vendored
Normal file
@@ -0,0 +1,326 @@
|
||||
// Copyright 2014 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package p2p
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
// Msg defines the structure of a p2p message.
|
||||
//
|
||||
// Note that a Msg can only be sent once since the Payload reader is
|
||||
// consumed during sending. It is not possible to create a Msg and
|
||||
// send it any number of times. If you want to reuse an encoded
|
||||
// structure, encode the payload into a byte array and create a
|
||||
// separate Msg with a bytes.Reader as Payload for each send.
|
||||
type Msg struct {
|
||||
Code uint64
|
||||
Size uint32 // Size of the raw payload
|
||||
Payload io.Reader
|
||||
ReceivedAt time.Time
|
||||
|
||||
meterCap Cap // Protocol name and version for egress metering
|
||||
meterCode uint64 // Message within protocol for egress metering
|
||||
meterSize uint32 // Compressed message size for ingress metering
|
||||
}
|
||||
|
||||
// Decode parses the RLP content of a message into
|
||||
// the given value, which must be a pointer.
|
||||
//
|
||||
// For the decoding rules, please see package rlp.
|
||||
func (msg Msg) Decode(val interface{}) error {
|
||||
s := rlp.NewStream(msg.Payload, uint64(msg.Size))
|
||||
if err := s.Decode(val); err != nil {
|
||||
return newPeerError(errInvalidMsg, "(code %x) (size %d) %v", msg.Code, msg.Size, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (msg Msg) String() string {
|
||||
return fmt.Sprintf("msg #%v (%v bytes)", msg.Code, msg.Size)
|
||||
}
|
||||
|
||||
// Discard reads any remaining payload data into a black hole.
|
||||
func (msg Msg) Discard() error {
|
||||
_, err := io.Copy(io.Discard, msg.Payload)
|
||||
return err
|
||||
}
|
||||
|
||||
func (msg Msg) Time() time.Time {
|
||||
return msg.ReceivedAt
|
||||
}
|
||||
|
||||
type MsgReader interface {
|
||||
ReadMsg() (Msg, error)
|
||||
}
|
||||
|
||||
type MsgWriter interface {
|
||||
// WriteMsg sends a message. It will block until the message's
|
||||
// Payload has been consumed by the other end.
|
||||
//
|
||||
// Note that messages can be sent only once because their
|
||||
// payload reader is drained.
|
||||
WriteMsg(Msg) error
|
||||
}
|
||||
|
||||
// MsgReadWriter provides reading and writing of encoded messages.
|
||||
// Implementations should ensure that ReadMsg and WriteMsg can be
|
||||
// called simultaneously from multiple goroutines.
|
||||
type MsgReadWriter interface {
|
||||
MsgReader
|
||||
MsgWriter
|
||||
}
|
||||
|
||||
// Send writes an RLP-encoded message with the given code.
|
||||
// data should encode as an RLP list.
|
||||
func Send(w MsgWriter, msgcode uint64, data interface{}) error {
|
||||
size, r, err := rlp.EncodeToReader(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return w.WriteMsg(Msg{Code: msgcode, Size: uint32(size), Payload: r})
|
||||
}
|
||||
|
||||
// SendItems writes an RLP with the given code and data elements.
|
||||
// For a call such as:
|
||||
//
|
||||
// SendItems(w, code, e1, e2, e3)
|
||||
//
|
||||
// the message payload will be an RLP list containing the items:
|
||||
//
|
||||
// [e1, e2, e3]
|
||||
func SendItems(w MsgWriter, msgcode uint64, elems ...interface{}) error {
|
||||
return Send(w, msgcode, elems)
|
||||
}
|
||||
|
||||
// eofSignal wraps a reader with eof signaling. the eof channel is
|
||||
// closed when the wrapped reader returns an error or when count bytes
|
||||
// have been read.
|
||||
type eofSignal struct {
|
||||
wrapped io.Reader
|
||||
count uint32 // number of bytes left
|
||||
eof chan<- struct{}
|
||||
}
|
||||
|
||||
// note: when using eofSignal to detect whether a message payload
|
||||
// has been read, Read might not be called for zero sized messages.
|
||||
func (r *eofSignal) Read(buf []byte) (int, error) {
|
||||
if r.count == 0 {
|
||||
if r.eof != nil {
|
||||
r.eof <- struct{}{}
|
||||
r.eof = nil
|
||||
}
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
max := len(buf)
|
||||
if int(r.count) < len(buf) {
|
||||
max = int(r.count)
|
||||
}
|
||||
n, err := r.wrapped.Read(buf[:max])
|
||||
r.count -= uint32(n)
|
||||
if (err != nil || r.count == 0) && r.eof != nil {
|
||||
r.eof <- struct{}{} // tell Peer that msg has been consumed
|
||||
r.eof = nil
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
// MsgPipe creates a message pipe. Reads on one end are matched
|
||||
// with writes on the other. The pipe is full-duplex, both ends
|
||||
// implement MsgReadWriter.
|
||||
func MsgPipe() (*MsgPipeRW, *MsgPipeRW) {
|
||||
var (
|
||||
c1, c2 = make(chan Msg), make(chan Msg)
|
||||
closing = make(chan struct{})
|
||||
closed = new(int32)
|
||||
rw1 = &MsgPipeRW{c1, c2, closing, closed}
|
||||
rw2 = &MsgPipeRW{c2, c1, closing, closed}
|
||||
)
|
||||
return rw1, rw2
|
||||
}
|
||||
|
||||
// ErrPipeClosed is returned from pipe operations after the
|
||||
// pipe has been closed.
|
||||
var ErrPipeClosed = errors.New("p2p: read or write on closed message pipe")
|
||||
|
||||
// MsgPipeRW is an endpoint of a MsgReadWriter pipe.
|
||||
type MsgPipeRW struct {
|
||||
w chan<- Msg
|
||||
r <-chan Msg
|
||||
closing chan struct{}
|
||||
closed *int32
|
||||
}
|
||||
|
||||
// WriteMsg sends a message on the pipe.
|
||||
// It blocks until the receiver has consumed the message payload.
|
||||
func (p *MsgPipeRW) WriteMsg(msg Msg) error {
|
||||
if atomic.LoadInt32(p.closed) == 0 {
|
||||
consumed := make(chan struct{}, 1)
|
||||
msg.Payload = &eofSignal{msg.Payload, msg.Size, consumed}
|
||||
select {
|
||||
case p.w <- msg:
|
||||
if msg.Size > 0 {
|
||||
// wait for payload read or discard
|
||||
select {
|
||||
case <-consumed:
|
||||
case <-p.closing:
|
||||
}
|
||||
}
|
||||
return nil
|
||||
case <-p.closing:
|
||||
}
|
||||
}
|
||||
return ErrPipeClosed
|
||||
}
|
||||
|
||||
// ReadMsg returns a message sent on the other end of the pipe.
|
||||
func (p *MsgPipeRW) ReadMsg() (Msg, error) {
|
||||
if atomic.LoadInt32(p.closed) == 0 {
|
||||
select {
|
||||
case msg := <-p.r:
|
||||
return msg, nil
|
||||
case <-p.closing:
|
||||
}
|
||||
}
|
||||
return Msg{}, ErrPipeClosed
|
||||
}
|
||||
|
||||
// Close unblocks any pending ReadMsg and WriteMsg calls on both ends
|
||||
// of the pipe. They will return ErrPipeClosed. Close also
|
||||
// interrupts any reads from a message payload.
|
||||
func (p *MsgPipeRW) Close() error {
|
||||
if atomic.AddInt32(p.closed, 1) != 1 {
|
||||
// someone else is already closing
|
||||
atomic.StoreInt32(p.closed, 1) // avoid overflow
|
||||
return nil
|
||||
}
|
||||
close(p.closing)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExpectMsg reads a message from r and verifies that its
|
||||
// code and encoded RLP content match the provided values.
|
||||
// If content is nil, the payload is discarded and not verified.
|
||||
func ExpectMsg(r MsgReader, code uint64, content interface{}) error {
|
||||
msg, err := r.ReadMsg()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if msg.Code != code {
|
||||
return fmt.Errorf("message code mismatch: got %d, expected %d", msg.Code, code)
|
||||
}
|
||||
if content == nil {
|
||||
return msg.Discard()
|
||||
}
|
||||
contentEnc, err := rlp.EncodeToBytes(content)
|
||||
if err != nil {
|
||||
panic("content encode error: " + err.Error())
|
||||
}
|
||||
if int(msg.Size) != len(contentEnc) {
|
||||
return fmt.Errorf("message size mismatch: got %d, want %d", msg.Size, len(contentEnc))
|
||||
}
|
||||
actualContent, err := io.ReadAll(msg.Payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !bytes.Equal(actualContent, contentEnc) {
|
||||
return fmt.Errorf("message payload mismatch:\ngot: %x\nwant: %x", actualContent, contentEnc)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// msgEventer wraps a MsgReadWriter and sends events whenever a message is sent
|
||||
// or received
|
||||
type msgEventer struct {
|
||||
MsgReadWriter
|
||||
|
||||
feed *event.Feed
|
||||
peerID enode.ID
|
||||
Protocol string
|
||||
localAddress string
|
||||
remoteAddress string
|
||||
}
|
||||
|
||||
// newMsgEventer returns a msgEventer which sends message events to the given
|
||||
// feed
|
||||
func newMsgEventer(rw MsgReadWriter, feed *event.Feed, peerID enode.ID, proto, remote, local string) *msgEventer {
|
||||
return &msgEventer{
|
||||
MsgReadWriter: rw,
|
||||
feed: feed,
|
||||
peerID: peerID,
|
||||
Protocol: proto,
|
||||
remoteAddress: remote,
|
||||
localAddress: local,
|
||||
}
|
||||
}
|
||||
|
||||
// ReadMsg reads a message from the underlying MsgReadWriter and emits a
|
||||
// "message received" event
|
||||
func (ev *msgEventer) ReadMsg() (Msg, error) {
|
||||
msg, err := ev.MsgReadWriter.ReadMsg()
|
||||
if err != nil {
|
||||
return msg, err
|
||||
}
|
||||
ev.feed.Send(&PeerEvent{
|
||||
Type: PeerEventTypeMsgRecv,
|
||||
Peer: ev.peerID,
|
||||
Protocol: ev.Protocol,
|
||||
MsgCode: &msg.Code,
|
||||
MsgSize: &msg.Size,
|
||||
LocalAddress: ev.localAddress,
|
||||
RemoteAddress: ev.remoteAddress,
|
||||
})
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
// WriteMsg writes a message to the underlying MsgReadWriter and emits a
|
||||
// "message sent" event
|
||||
func (ev *msgEventer) WriteMsg(msg Msg) error {
|
||||
err := ev.MsgReadWriter.WriteMsg(msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ev.feed.Send(&PeerEvent{
|
||||
Type: PeerEventTypeMsgSend,
|
||||
Peer: ev.peerID,
|
||||
Protocol: ev.Protocol,
|
||||
MsgCode: &msg.Code,
|
||||
MsgSize: &msg.Size,
|
||||
LocalAddress: ev.localAddress,
|
||||
RemoteAddress: ev.remoteAddress,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close closes the underlying MsgReadWriter if it implements the io.Closer
|
||||
// interface
|
||||
func (ev *msgEventer) Close() error {
|
||||
if v, ok := ev.MsgReadWriter.(io.Closer); ok {
|
||||
return v.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
94
vendor/github.com/ethereum/go-ethereum/p2p/metrics.go
generated
vendored
Normal file
94
vendor/github.com/ethereum/go-ethereum/p2p/metrics.go
generated
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Contains the meters and timers used by the networking layer.
|
||||
|
||||
package p2p
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
)
|
||||
|
||||
const (
|
||||
// ingressMeterName is the prefix of the per-packet inbound metrics.
|
||||
ingressMeterName = "p2p/ingress"
|
||||
|
||||
// egressMeterName is the prefix of the per-packet outbound metrics.
|
||||
egressMeterName = "p2p/egress"
|
||||
|
||||
// HandleHistName is the prefix of the per-packet serving time histograms.
|
||||
HandleHistName = "p2p/handle"
|
||||
)
|
||||
|
||||
var (
|
||||
ingressConnectMeter = metrics.NewRegisteredMeter("p2p/serves", nil)
|
||||
ingressTrafficMeter = metrics.NewRegisteredMeter(ingressMeterName, nil)
|
||||
egressConnectMeter = metrics.NewRegisteredMeter("p2p/dials", nil)
|
||||
egressTrafficMeter = metrics.NewRegisteredMeter(egressMeterName, nil)
|
||||
activePeerGauge = metrics.NewRegisteredGauge("p2p/peers", nil)
|
||||
)
|
||||
|
||||
// meteredConn is a wrapper around a net.Conn that meters both the
|
||||
// inbound and outbound network traffic.
|
||||
type meteredConn struct {
|
||||
net.Conn
|
||||
}
|
||||
|
||||
// newMeteredConn creates a new metered connection, bumps the ingress or egress
|
||||
// connection meter and also increases the metered peer count. If the metrics
|
||||
// system is disabled, function returns the original connection.
|
||||
func newMeteredConn(conn net.Conn, ingress bool, addr *net.TCPAddr) net.Conn {
|
||||
// Short circuit if metrics are disabled
|
||||
if !metrics.Enabled {
|
||||
return conn
|
||||
}
|
||||
// Bump the connection counters and wrap the connection
|
||||
if ingress {
|
||||
ingressConnectMeter.Mark(1)
|
||||
} else {
|
||||
egressConnectMeter.Mark(1)
|
||||
}
|
||||
activePeerGauge.Inc(1)
|
||||
return &meteredConn{Conn: conn}
|
||||
}
|
||||
|
||||
// Read delegates a network read to the underlying connection, bumping the common
|
||||
// and the peer ingress traffic meters along the way.
|
||||
func (c *meteredConn) Read(b []byte) (n int, err error) {
|
||||
n, err = c.Conn.Read(b)
|
||||
ingressTrafficMeter.Mark(int64(n))
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Write delegates a network write to the underlying connection, bumping the common
|
||||
// and the peer egress traffic meters along the way.
|
||||
func (c *meteredConn) Write(b []byte) (n int, err error) {
|
||||
n, err = c.Conn.Write(b)
|
||||
egressTrafficMeter.Mark(int64(n))
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Close delegates a close operation to the underlying connection, unregisters
|
||||
// the peer from the traffic registries and emits close event.
|
||||
func (c *meteredConn) Close() error {
|
||||
err := c.Conn.Close()
|
||||
if err == nil {
|
||||
activePeerGauge.Dec(1)
|
||||
}
|
||||
return err
|
||||
}
|
||||
237
vendor/github.com/ethereum/go-ethereum/p2p/nat/nat.go
generated
vendored
Normal file
237
vendor/github.com/ethereum/go-ethereum/p2p/nat/nat.go
generated
vendored
Normal file
@@ -0,0 +1,237 @@
|
||||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Package nat provides access to common network port mapping protocols.
|
||||
package nat
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
natpmp "github.com/jackpal/go-nat-pmp"
|
||||
)
|
||||
|
||||
// Interface An implementation of nat.Interface can map local ports to ports
|
||||
// accessible from the Internet.
|
||||
type Interface interface {
|
||||
// These methods manage a mapping between a port on the local
|
||||
// machine to a port that can be connected to from the internet.
|
||||
//
|
||||
// protocol is "UDP" or "TCP". Some implementations allow setting
|
||||
// a display name for the mapping. The mapping may be removed by
|
||||
// the gateway when its lifetime ends.
|
||||
AddMapping(protocol string, extport, intport int, name string, lifetime time.Duration) error
|
||||
DeleteMapping(protocol string, extport, intport int) error
|
||||
|
||||
// ExternalIP should return the external (Internet-facing)
|
||||
// address of the gateway device.
|
||||
ExternalIP() (net.IP, error)
|
||||
|
||||
// String should return name of the method. This is used for logging.
|
||||
String() string
|
||||
}
|
||||
|
||||
// Parse parses a NAT interface description.
|
||||
// The following formats are currently accepted.
|
||||
// Note that mechanism names are not case-sensitive.
|
||||
//
|
||||
// "" or "none" return nil
|
||||
// "extip:77.12.33.4" will assume the local machine is reachable on the given IP
|
||||
// "any" uses the first auto-detected mechanism
|
||||
// "upnp" uses the Universal Plug and Play protocol
|
||||
// "pmp" uses NAT-PMP with an auto-detected gateway address
|
||||
// "pmp:192.168.0.1" uses NAT-PMP with the given gateway address
|
||||
func Parse(spec string) (Interface, error) {
|
||||
var (
|
||||
parts = strings.SplitN(spec, ":", 2)
|
||||
mech = strings.ToLower(parts[0])
|
||||
ip net.IP
|
||||
)
|
||||
if len(parts) > 1 {
|
||||
ip = net.ParseIP(parts[1])
|
||||
if ip == nil {
|
||||
return nil, errors.New("invalid IP address")
|
||||
}
|
||||
}
|
||||
switch mech {
|
||||
case "", "none", "off":
|
||||
return nil, nil
|
||||
case "any", "auto", "on":
|
||||
return Any(), nil
|
||||
case "extip", "ip":
|
||||
if ip == nil {
|
||||
return nil, errors.New("missing IP address")
|
||||
}
|
||||
return ExtIP(ip), nil
|
||||
case "upnp":
|
||||
return UPnP(), nil
|
||||
case "pmp", "natpmp", "nat-pmp":
|
||||
return PMP(ip), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown mechanism %q", parts[0])
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
mapTimeout = 10 * time.Minute
|
||||
)
|
||||
|
||||
// Map adds a port mapping on m and keeps it alive until c is closed.
|
||||
// This function is typically invoked in its own goroutine.
|
||||
func Map(m Interface, c <-chan struct{}, protocol string, extport, intport int, name string) {
|
||||
log := log.New("proto", protocol, "extport", extport, "intport", intport, "interface", m)
|
||||
refresh := time.NewTimer(mapTimeout)
|
||||
defer func() {
|
||||
refresh.Stop()
|
||||
log.Debug("Deleting port mapping")
|
||||
m.DeleteMapping(protocol, extport, intport)
|
||||
}()
|
||||
if err := m.AddMapping(protocol, extport, intport, name, mapTimeout); err != nil {
|
||||
log.Debug("Couldn't add port mapping", "err", err)
|
||||
} else {
|
||||
log.Info("Mapped network port")
|
||||
}
|
||||
for {
|
||||
select {
|
||||
case _, ok := <-c:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
case <-refresh.C:
|
||||
log.Trace("Refreshing port mapping")
|
||||
if err := m.AddMapping(protocol, extport, intport, name, mapTimeout); err != nil {
|
||||
log.Debug("Couldn't add port mapping", "err", err)
|
||||
}
|
||||
refresh.Reset(mapTimeout)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ExtIP assumes that the local machine is reachable on the given
|
||||
// external IP address, and that any required ports were mapped manually.
|
||||
// Mapping operations will not return an error but won't actually do anything.
|
||||
type ExtIP net.IP
|
||||
|
||||
func (n ExtIP) ExternalIP() (net.IP, error) { return net.IP(n), nil }
|
||||
func (n ExtIP) String() string { return fmt.Sprintf("ExtIP(%v)", net.IP(n)) }
|
||||
|
||||
// These do nothing.
|
||||
|
||||
func (ExtIP) AddMapping(string, int, int, string, time.Duration) error { return nil }
|
||||
func (ExtIP) DeleteMapping(string, int, int) error { return nil }
|
||||
|
||||
// Any returns a port mapper that tries to discover any supported
|
||||
// mechanism on the local network.
|
||||
func Any() Interface {
|
||||
// TODO: attempt to discover whether the local machine has an
|
||||
// Internet-class address. Return ExtIP in this case.
|
||||
return startautodisc("UPnP or NAT-PMP", func() Interface {
|
||||
found := make(chan Interface, 2)
|
||||
go func() { found <- discoverUPnP() }()
|
||||
go func() { found <- discoverPMP() }()
|
||||
for i := 0; i < cap(found); i++ {
|
||||
if c := <-found; c != nil {
|
||||
return c
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// UPnP returns a port mapper that uses UPnP. It will attempt to
|
||||
// discover the address of your router using UDP broadcasts.
|
||||
func UPnP() Interface {
|
||||
return startautodisc("UPnP", discoverUPnP)
|
||||
}
|
||||
|
||||
// PMP returns a port mapper that uses NAT-PMP. The provided gateway
|
||||
// address should be the IP of your router. If the given gateway
|
||||
// address is nil, PMP will attempt to auto-discover the router.
|
||||
func PMP(gateway net.IP) Interface {
|
||||
if gateway != nil {
|
||||
return &pmp{gw: gateway, c: natpmp.NewClient(gateway)}
|
||||
}
|
||||
return startautodisc("NAT-PMP", discoverPMP)
|
||||
}
|
||||
|
||||
// autodisc represents a port mapping mechanism that is still being
|
||||
// auto-discovered. Calls to the Interface methods on this type will
|
||||
// wait until the discovery is done and then call the method on the
|
||||
// discovered mechanism.
|
||||
//
|
||||
// This type is useful because discovery can take a while but we
|
||||
// want return an Interface value from UPnP, PMP and Auto immediately.
|
||||
type autodisc struct {
|
||||
what string // type of interface being autodiscovered
|
||||
once sync.Once
|
||||
doit func() Interface
|
||||
|
||||
mu sync.Mutex
|
||||
found Interface
|
||||
}
|
||||
|
||||
func startautodisc(what string, doit func() Interface) Interface {
|
||||
// TODO: monitor network configuration and rerun doit when it changes.
|
||||
return &autodisc{what: what, doit: doit}
|
||||
}
|
||||
|
||||
func (n *autodisc) AddMapping(protocol string, extport, intport int, name string, lifetime time.Duration) error {
|
||||
if err := n.wait(); err != nil {
|
||||
return err
|
||||
}
|
||||
return n.found.AddMapping(protocol, extport, intport, name, lifetime)
|
||||
}
|
||||
|
||||
func (n *autodisc) DeleteMapping(protocol string, extport, intport int) error {
|
||||
if err := n.wait(); err != nil {
|
||||
return err
|
||||
}
|
||||
return n.found.DeleteMapping(protocol, extport, intport)
|
||||
}
|
||||
|
||||
func (n *autodisc) ExternalIP() (net.IP, error) {
|
||||
if err := n.wait(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return n.found.ExternalIP()
|
||||
}
|
||||
|
||||
func (n *autodisc) String() string {
|
||||
n.mu.Lock()
|
||||
defer n.mu.Unlock()
|
||||
if n.found == nil {
|
||||
return n.what
|
||||
}
|
||||
return n.found.String()
|
||||
}
|
||||
|
||||
// wait blocks until auto-discovery has been performed.
|
||||
func (n *autodisc) wait() error {
|
||||
n.once.Do(func() {
|
||||
n.mu.Lock()
|
||||
n.found = n.doit()
|
||||
n.mu.Unlock()
|
||||
})
|
||||
if n.found == nil {
|
||||
return fmt.Errorf("no %s router discovered", n.what)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
130
vendor/github.com/ethereum/go-ethereum/p2p/nat/natpmp.go
generated
vendored
Normal file
130
vendor/github.com/ethereum/go-ethereum/p2p/nat/natpmp.go
generated
vendored
Normal file
@@ -0,0 +1,130 @@
|
||||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package nat
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
natpmp "github.com/jackpal/go-nat-pmp"
|
||||
)
|
||||
|
||||
// natPMPClient adapts the NAT-PMP protocol implementation so it conforms to
|
||||
// the common interface.
|
||||
type pmp struct {
|
||||
gw net.IP
|
||||
c *natpmp.Client
|
||||
}
|
||||
|
||||
func (n *pmp) String() string {
|
||||
return fmt.Sprintf("NAT-PMP(%v)", n.gw)
|
||||
}
|
||||
|
||||
func (n *pmp) ExternalIP() (net.IP, error) {
|
||||
response, err := n.c.GetExternalAddress()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return response.ExternalIPAddress[:], nil
|
||||
}
|
||||
|
||||
func (n *pmp) AddMapping(protocol string, extport, intport int, name string, lifetime time.Duration) error {
|
||||
if lifetime <= 0 {
|
||||
return fmt.Errorf("lifetime must not be <= 0")
|
||||
}
|
||||
// Note order of port arguments is switched between our
|
||||
// AddMapping and the client's AddPortMapping.
|
||||
_, err := n.c.AddPortMapping(strings.ToLower(protocol), intport, extport, int(lifetime/time.Second))
|
||||
return err
|
||||
}
|
||||
|
||||
func (n *pmp) DeleteMapping(protocol string, extport, intport int) (err error) {
|
||||
// To destroy a mapping, send an add-port with an internalPort of
|
||||
// the internal port to destroy, an external port of zero and a
|
||||
// time of zero.
|
||||
_, err = n.c.AddPortMapping(strings.ToLower(protocol), intport, 0, 0)
|
||||
return err
|
||||
}
|
||||
|
||||
func discoverPMP() Interface {
|
||||
// run external address lookups on all potential gateways
|
||||
gws := potentialGateways()
|
||||
found := make(chan *pmp, len(gws))
|
||||
for i := range gws {
|
||||
gw := gws[i]
|
||||
go func() {
|
||||
c := natpmp.NewClient(gw)
|
||||
if _, err := c.GetExternalAddress(); err != nil {
|
||||
found <- nil
|
||||
} else {
|
||||
found <- &pmp{gw, c}
|
||||
}
|
||||
}()
|
||||
}
|
||||
// return the one that responds first.
|
||||
// discovery needs to be quick, so we stop caring about
|
||||
// any responses after a very short timeout.
|
||||
timeout := time.NewTimer(1 * time.Second)
|
||||
defer timeout.Stop()
|
||||
for range gws {
|
||||
select {
|
||||
case c := <-found:
|
||||
if c != nil {
|
||||
return c
|
||||
}
|
||||
case <-timeout.C:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
// LAN IP ranges
|
||||
_, lan10, _ = net.ParseCIDR("10.0.0.0/8")
|
||||
_, lan176, _ = net.ParseCIDR("172.16.0.0/12")
|
||||
_, lan192, _ = net.ParseCIDR("192.168.0.0/16")
|
||||
)
|
||||
|
||||
// TODO: improve this. We currently assume that (on most networks)
|
||||
// the router is X.X.X.1 in a local LAN range.
|
||||
func potentialGateways() (gws []net.IP) {
|
||||
ifaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
for _, iface := range ifaces {
|
||||
ifaddrs, err := iface.Addrs()
|
||||
if err != nil {
|
||||
return gws
|
||||
}
|
||||
for _, addr := range ifaddrs {
|
||||
if x, ok := addr.(*net.IPNet); ok {
|
||||
if lan10.Contains(x.IP) || lan176.Contains(x.IP) || lan192.Contains(x.IP) {
|
||||
ip := x.IP.Mask(x.Mask).To4()
|
||||
if ip != nil {
|
||||
ip[3] = ip[3] | 0x01
|
||||
gws = append(gws, ip)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return gws
|
||||
}
|
||||
215
vendor/github.com/ethereum/go-ethereum/p2p/nat/natupnp.go
generated
vendored
Normal file
215
vendor/github.com/ethereum/go-ethereum/p2p/nat/natupnp.go
generated
vendored
Normal file
@@ -0,0 +1,215 @@
|
||||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package nat
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/huin/goupnp"
|
||||
"github.com/huin/goupnp/dcps/internetgateway1"
|
||||
"github.com/huin/goupnp/dcps/internetgateway2"
|
||||
)
|
||||
|
||||
const (
|
||||
soapRequestTimeout = 3 * time.Second
|
||||
rateLimit = 200 * time.Millisecond
|
||||
)
|
||||
|
||||
type upnp struct {
|
||||
dev *goupnp.RootDevice
|
||||
service string
|
||||
client upnpClient
|
||||
mu sync.Mutex
|
||||
lastReqTime time.Time
|
||||
}
|
||||
|
||||
type upnpClient interface {
|
||||
GetExternalIPAddress() (string, error)
|
||||
AddPortMapping(string, uint16, string, uint16, string, bool, string, uint32) error
|
||||
DeletePortMapping(string, uint16, string) error
|
||||
GetNATRSIPStatus() (sip bool, nat bool, err error)
|
||||
}
|
||||
|
||||
func (n *upnp) natEnabled() bool {
|
||||
var ok bool
|
||||
var err error
|
||||
n.withRateLimit(func() error {
|
||||
_, ok, err = n.client.GetNATRSIPStatus()
|
||||
return err
|
||||
})
|
||||
return err == nil && ok
|
||||
}
|
||||
|
||||
func (n *upnp) ExternalIP() (addr net.IP, err error) {
|
||||
var ipString string
|
||||
n.withRateLimit(func() error {
|
||||
ipString, err = n.client.GetExternalIPAddress()
|
||||
return err
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ip := net.ParseIP(ipString)
|
||||
if ip == nil {
|
||||
return nil, errors.New("bad IP in response")
|
||||
}
|
||||
return ip, nil
|
||||
}
|
||||
|
||||
func (n *upnp) AddMapping(protocol string, extport, intport int, desc string, lifetime time.Duration) error {
|
||||
ip, err := n.internalAddress()
|
||||
if err != nil {
|
||||
return nil // TODO: Shouldn't we return the error?
|
||||
}
|
||||
protocol = strings.ToUpper(protocol)
|
||||
lifetimeS := uint32(lifetime / time.Second)
|
||||
n.DeleteMapping(protocol, extport, intport)
|
||||
|
||||
return n.withRateLimit(func() error {
|
||||
return n.client.AddPortMapping("", uint16(extport), protocol, uint16(intport), ip.String(), true, desc, lifetimeS)
|
||||
})
|
||||
}
|
||||
|
||||
func (n *upnp) internalAddress() (net.IP, error) {
|
||||
devaddr, err := net.ResolveUDPAddr("udp4", n.dev.URLBase.Host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ifaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, iface := range ifaces {
|
||||
addrs, err := iface.Addrs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, addr := range addrs {
|
||||
if x, ok := addr.(*net.IPNet); ok && x.Contains(devaddr.IP) {
|
||||
return x.IP, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("could not find local address in same net as %v", devaddr)
|
||||
}
|
||||
|
||||
func (n *upnp) DeleteMapping(protocol string, extport, intport int) error {
|
||||
return n.withRateLimit(func() error {
|
||||
return n.client.DeletePortMapping("", uint16(extport), strings.ToUpper(protocol))
|
||||
})
|
||||
}
|
||||
|
||||
func (n *upnp) String() string {
|
||||
return "UPNP " + n.service
|
||||
}
|
||||
|
||||
func (n *upnp) withRateLimit(fn func() error) error {
|
||||
n.mu.Lock()
|
||||
defer n.mu.Unlock()
|
||||
|
||||
lastreq := time.Since(n.lastReqTime)
|
||||
if lastreq < rateLimit {
|
||||
time.Sleep(rateLimit - lastreq)
|
||||
}
|
||||
err := fn()
|
||||
n.lastReqTime = time.Now()
|
||||
return err
|
||||
}
|
||||
|
||||
// discoverUPnP searches for Internet Gateway Devices
|
||||
// and returns the first one it can find on the local network.
|
||||
func discoverUPnP() Interface {
|
||||
found := make(chan *upnp, 2)
|
||||
// IGDv1
|
||||
go discover(found, internetgateway1.URN_WANConnectionDevice_1, func(sc goupnp.ServiceClient) *upnp {
|
||||
switch sc.Service.ServiceType {
|
||||
case internetgateway1.URN_WANIPConnection_1:
|
||||
return &upnp{service: "IGDv1-IP1", client: &internetgateway1.WANIPConnection1{ServiceClient: sc}}
|
||||
case internetgateway1.URN_WANPPPConnection_1:
|
||||
return &upnp{service: "IGDv1-PPP1", client: &internetgateway1.WANPPPConnection1{ServiceClient: sc}}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
// IGDv2
|
||||
go discover(found, internetgateway2.URN_WANConnectionDevice_2, func(sc goupnp.ServiceClient) *upnp {
|
||||
switch sc.Service.ServiceType {
|
||||
case internetgateway2.URN_WANIPConnection_1:
|
||||
return &upnp{service: "IGDv2-IP1", client: &internetgateway2.WANIPConnection1{ServiceClient: sc}}
|
||||
case internetgateway2.URN_WANIPConnection_2:
|
||||
return &upnp{service: "IGDv2-IP2", client: &internetgateway2.WANIPConnection2{ServiceClient: sc}}
|
||||
case internetgateway2.URN_WANPPPConnection_1:
|
||||
return &upnp{service: "IGDv2-PPP1", client: &internetgateway2.WANPPPConnection1{ServiceClient: sc}}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
for i := 0; i < cap(found); i++ {
|
||||
if c := <-found; c != nil {
|
||||
return c
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// finds devices matching the given target and calls matcher for all
|
||||
// advertised services of each device. The first non-nil service found
|
||||
// is sent into out. If no service matched, nil is sent.
|
||||
func discover(out chan<- *upnp, target string, matcher func(goupnp.ServiceClient) *upnp) {
|
||||
devs, err := goupnp.DiscoverDevices(target)
|
||||
if err != nil {
|
||||
out <- nil
|
||||
return
|
||||
}
|
||||
found := false
|
||||
for i := 0; i < len(devs) && !found; i++ {
|
||||
if devs[i].Root == nil {
|
||||
continue
|
||||
}
|
||||
devs[i].Root.Device.VisitServices(func(service *goupnp.Service) {
|
||||
if found {
|
||||
return
|
||||
}
|
||||
// check for a matching IGD service
|
||||
sc := goupnp.ServiceClient{
|
||||
SOAPClient: service.NewSOAPClient(),
|
||||
RootDevice: devs[i].Root,
|
||||
Location: devs[i].Location,
|
||||
Service: service,
|
||||
}
|
||||
sc.SOAPClient.HTTPClient.Timeout = soapRequestTimeout
|
||||
upnp := matcher(sc)
|
||||
if upnp == nil {
|
||||
return
|
||||
}
|
||||
upnp.dev = devs[i].Root
|
||||
|
||||
// check whether port mapping is enabled
|
||||
if upnp.natEnabled() {
|
||||
out <- upnp
|
||||
found = true
|
||||
}
|
||||
})
|
||||
}
|
||||
if !found {
|
||||
out <- nil
|
||||
}
|
||||
}
|
||||
33
vendor/github.com/ethereum/go-ethereum/p2p/netutil/addrutil.go
generated
vendored
Normal file
33
vendor/github.com/ethereum/go-ethereum/p2p/netutil/addrutil.go
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright 2019 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package netutil
|
||||
|
||||
import "net"
|
||||
|
||||
// AddrIP gets the IP address contained in addr. It returns nil if no address is present.
|
||||
func AddrIP(addr net.Addr) net.IP {
|
||||
switch a := addr.(type) {
|
||||
case *net.IPAddr:
|
||||
return a.IP
|
||||
case *net.TCPAddr:
|
||||
return a.IP
|
||||
case *net.UDPAddr:
|
||||
return a.IP
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
33
vendor/github.com/ethereum/go-ethereum/p2p/netutil/error.go
generated
vendored
Normal file
33
vendor/github.com/ethereum/go-ethereum/p2p/netutil/error.go
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package netutil
|
||||
|
||||
// IsTemporaryError checks whether the given error should be considered temporary.
|
||||
func IsTemporaryError(err error) bool {
|
||||
tempErr, ok := err.(interface {
|
||||
Temporary() bool
|
||||
})
|
||||
return ok && tempErr.Temporary() || isPacketTooBig(err)
|
||||
}
|
||||
|
||||
// IsTimeout checks whether the given error is a timeout.
|
||||
func IsTimeout(err error) bool {
|
||||
timeoutErr, ok := err.(interface {
|
||||
Timeout() bool
|
||||
})
|
||||
return ok && timeoutErr.Timeout()
|
||||
}
|
||||
130
vendor/github.com/ethereum/go-ethereum/p2p/netutil/iptrack.go
generated
vendored
Normal file
130
vendor/github.com/ethereum/go-ethereum/p2p/netutil/iptrack.go
generated
vendored
Normal file
@@ -0,0 +1,130 @@
|
||||
// Copyright 2018 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package netutil
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/mclock"
|
||||
)
|
||||
|
||||
// IPTracker predicts the external endpoint, i.e. IP address and port, of the local host
|
||||
// based on statements made by other hosts.
|
||||
type IPTracker struct {
|
||||
window time.Duration
|
||||
contactWindow time.Duration
|
||||
minStatements int
|
||||
clock mclock.Clock
|
||||
statements map[string]ipStatement
|
||||
contact map[string]mclock.AbsTime
|
||||
lastStatementGC mclock.AbsTime
|
||||
lastContactGC mclock.AbsTime
|
||||
}
|
||||
|
||||
type ipStatement struct {
|
||||
endpoint string
|
||||
time mclock.AbsTime
|
||||
}
|
||||
|
||||
// NewIPTracker creates an IP tracker.
|
||||
//
|
||||
// The window parameters configure the amount of past network events which are kept. The
|
||||
// minStatements parameter enforces a minimum number of statements which must be recorded
|
||||
// before any prediction is made. Higher values for these parameters decrease 'flapping' of
|
||||
// predictions as network conditions change. Window duration values should typically be in
|
||||
// the range of minutes.
|
||||
func NewIPTracker(window, contactWindow time.Duration, minStatements int) *IPTracker {
|
||||
return &IPTracker{
|
||||
window: window,
|
||||
contactWindow: contactWindow,
|
||||
statements: make(map[string]ipStatement),
|
||||
minStatements: minStatements,
|
||||
contact: make(map[string]mclock.AbsTime),
|
||||
clock: mclock.System{},
|
||||
}
|
||||
}
|
||||
|
||||
// PredictFullConeNAT checks whether the local host is behind full cone NAT. It predicts by
|
||||
// checking whether any statement has been received from a node we didn't contact before
|
||||
// the statement was made.
|
||||
func (it *IPTracker) PredictFullConeNAT() bool {
|
||||
now := it.clock.Now()
|
||||
it.gcContact(now)
|
||||
it.gcStatements(now)
|
||||
for host, st := range it.statements {
|
||||
if c, ok := it.contact[host]; !ok || c > st.time {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// PredictEndpoint returns the current prediction of the external endpoint.
|
||||
func (it *IPTracker) PredictEndpoint() string {
|
||||
it.gcStatements(it.clock.Now())
|
||||
|
||||
// The current strategy is simple: find the endpoint with most statements.
|
||||
counts := make(map[string]int)
|
||||
maxcount, max := 0, ""
|
||||
for _, s := range it.statements {
|
||||
c := counts[s.endpoint] + 1
|
||||
counts[s.endpoint] = c
|
||||
if c > maxcount && c >= it.minStatements {
|
||||
maxcount, max = c, s.endpoint
|
||||
}
|
||||
}
|
||||
return max
|
||||
}
|
||||
|
||||
// AddStatement records that a certain host thinks our external endpoint is the one given.
|
||||
func (it *IPTracker) AddStatement(host, endpoint string) {
|
||||
now := it.clock.Now()
|
||||
it.statements[host] = ipStatement{endpoint, now}
|
||||
if time.Duration(now-it.lastStatementGC) >= it.window {
|
||||
it.gcStatements(now)
|
||||
}
|
||||
}
|
||||
|
||||
// AddContact records that a packet containing our endpoint information has been sent to a
|
||||
// certain host.
|
||||
func (it *IPTracker) AddContact(host string) {
|
||||
now := it.clock.Now()
|
||||
it.contact[host] = now
|
||||
if time.Duration(now-it.lastContactGC) >= it.contactWindow {
|
||||
it.gcContact(now)
|
||||
}
|
||||
}
|
||||
|
||||
func (it *IPTracker) gcStatements(now mclock.AbsTime) {
|
||||
it.lastStatementGC = now
|
||||
cutoff := now.Add(-it.window)
|
||||
for host, s := range it.statements {
|
||||
if s.time < cutoff {
|
||||
delete(it.statements, host)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (it *IPTracker) gcContact(now mclock.AbsTime) {
|
||||
it.lastContactGC = now
|
||||
cutoff := now.Add(-it.contactWindow)
|
||||
for host, ct := range it.contact {
|
||||
if ct < cutoff {
|
||||
delete(it.contact, host)
|
||||
}
|
||||
}
|
||||
}
|
||||
322
vendor/github.com/ethereum/go-ethereum/p2p/netutil/net.go
generated
vendored
Normal file
322
vendor/github.com/ethereum/go-ethereum/p2p/netutil/net.go
generated
vendored
Normal file
@@ -0,0 +1,322 @@
|
||||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Package netutil contains extensions to the net package.
|
||||
package netutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var lan4, lan6, special4, special6 Netlist
|
||||
|
||||
func init() {
|
||||
// Lists from RFC 5735, RFC 5156,
|
||||
// https://www.iana.org/assignments/iana-ipv4-special-registry/
|
||||
lan4.Add("0.0.0.0/8") // "This" network
|
||||
lan4.Add("10.0.0.0/8") // Private Use
|
||||
lan4.Add("172.16.0.0/12") // Private Use
|
||||
lan4.Add("192.168.0.0/16") // Private Use
|
||||
lan6.Add("fe80::/10") // Link-Local
|
||||
lan6.Add("fc00::/7") // Unique-Local
|
||||
special4.Add("192.0.0.0/29") // IPv4 Service Continuity
|
||||
special4.Add("192.0.0.9/32") // PCP Anycast
|
||||
special4.Add("192.0.0.170/32") // NAT64/DNS64 Discovery
|
||||
special4.Add("192.0.0.171/32") // NAT64/DNS64 Discovery
|
||||
special4.Add("192.0.2.0/24") // TEST-NET-1
|
||||
special4.Add("192.31.196.0/24") // AS112
|
||||
special4.Add("192.52.193.0/24") // AMT
|
||||
special4.Add("192.88.99.0/24") // 6to4 Relay Anycast
|
||||
special4.Add("192.175.48.0/24") // AS112
|
||||
special4.Add("198.18.0.0/15") // Device Benchmark Testing
|
||||
special4.Add("198.51.100.0/24") // TEST-NET-2
|
||||
special4.Add("203.0.113.0/24") // TEST-NET-3
|
||||
special4.Add("255.255.255.255/32") // Limited Broadcast
|
||||
|
||||
// http://www.iana.org/assignments/iana-ipv6-special-registry/
|
||||
special6.Add("100::/64")
|
||||
special6.Add("2001::/32")
|
||||
special6.Add("2001:1::1/128")
|
||||
special6.Add("2001:2::/48")
|
||||
special6.Add("2001:3::/32")
|
||||
special6.Add("2001:4:112::/48")
|
||||
special6.Add("2001:5::/32")
|
||||
special6.Add("2001:10::/28")
|
||||
special6.Add("2001:20::/28")
|
||||
special6.Add("2001:db8::/32")
|
||||
special6.Add("2002::/16")
|
||||
}
|
||||
|
||||
// Netlist is a list of IP networks.
|
||||
type Netlist []net.IPNet
|
||||
|
||||
// ParseNetlist parses a comma-separated list of CIDR masks.
|
||||
// Whitespace and extra commas are ignored.
|
||||
func ParseNetlist(s string) (*Netlist, error) {
|
||||
ws := strings.NewReplacer(" ", "", "\n", "", "\t", "")
|
||||
masks := strings.Split(ws.Replace(s), ",")
|
||||
l := make(Netlist, 0)
|
||||
for _, mask := range masks {
|
||||
if mask == "" {
|
||||
continue
|
||||
}
|
||||
_, n, err := net.ParseCIDR(mask)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l = append(l, *n)
|
||||
}
|
||||
return &l, nil
|
||||
}
|
||||
|
||||
// MarshalTOML implements toml.MarshalerRec.
|
||||
func (l Netlist) MarshalTOML() interface{} {
|
||||
list := make([]string, 0, len(l))
|
||||
for _, net := range l {
|
||||
list = append(list, net.String())
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// UnmarshalTOML implements toml.UnmarshalerRec.
|
||||
func (l *Netlist) UnmarshalTOML(fn func(interface{}) error) error {
|
||||
var masks []string
|
||||
if err := fn(&masks); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, mask := range masks {
|
||||
_, n, err := net.ParseCIDR(mask)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*l = append(*l, *n)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add parses a CIDR mask and appends it to the list. It panics for invalid masks and is
|
||||
// intended to be used for setting up static lists.
|
||||
func (l *Netlist) Add(cidr string) {
|
||||
_, n, err := net.ParseCIDR(cidr)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
*l = append(*l, *n)
|
||||
}
|
||||
|
||||
// Contains reports whether the given IP is contained in the list.
|
||||
func (l *Netlist) Contains(ip net.IP) bool {
|
||||
if l == nil {
|
||||
return false
|
||||
}
|
||||
for _, net := range *l {
|
||||
if net.Contains(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsLAN reports whether an IP is a local network address.
|
||||
func IsLAN(ip net.IP) bool {
|
||||
if ip.IsLoopback() {
|
||||
return true
|
||||
}
|
||||
if v4 := ip.To4(); v4 != nil {
|
||||
return lan4.Contains(v4)
|
||||
}
|
||||
return lan6.Contains(ip)
|
||||
}
|
||||
|
||||
// IsSpecialNetwork reports whether an IP is located in a special-use network range
|
||||
// This includes broadcast, multicast and documentation addresses.
|
||||
func IsSpecialNetwork(ip net.IP) bool {
|
||||
if ip.IsMulticast() {
|
||||
return true
|
||||
}
|
||||
if v4 := ip.To4(); v4 != nil {
|
||||
return special4.Contains(v4)
|
||||
}
|
||||
return special6.Contains(ip)
|
||||
}
|
||||
|
||||
var (
|
||||
errInvalid = errors.New("invalid IP")
|
||||
errUnspecified = errors.New("zero address")
|
||||
errSpecial = errors.New("special network")
|
||||
errLoopback = errors.New("loopback address from non-loopback host")
|
||||
errLAN = errors.New("LAN address from WAN host")
|
||||
)
|
||||
|
||||
// CheckRelayIP reports whether an IP relayed from the given sender IP
|
||||
// is a valid connection target.
|
||||
//
|
||||
// There are four rules:
|
||||
// - Special network addresses are never valid.
|
||||
// - Loopback addresses are OK if relayed by a loopback host.
|
||||
// - LAN addresses are OK if relayed by a LAN host.
|
||||
// - All other addresses are always acceptable.
|
||||
func CheckRelayIP(sender, addr net.IP) error {
|
||||
if len(addr) != net.IPv4len && len(addr) != net.IPv6len {
|
||||
return errInvalid
|
||||
}
|
||||
if addr.IsUnspecified() {
|
||||
return errUnspecified
|
||||
}
|
||||
if IsSpecialNetwork(addr) {
|
||||
return errSpecial
|
||||
}
|
||||
if addr.IsLoopback() && !sender.IsLoopback() {
|
||||
return errLoopback
|
||||
}
|
||||
if IsLAN(addr) && !IsLAN(sender) {
|
||||
return errLAN
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SameNet reports whether two IP addresses have an equal prefix of the given bit length.
|
||||
func SameNet(bits uint, ip, other net.IP) bool {
|
||||
ip4, other4 := ip.To4(), other.To4()
|
||||
switch {
|
||||
case (ip4 == nil) != (other4 == nil):
|
||||
return false
|
||||
case ip4 != nil:
|
||||
return sameNet(bits, ip4, other4)
|
||||
default:
|
||||
return sameNet(bits, ip.To16(), other.To16())
|
||||
}
|
||||
}
|
||||
|
||||
func sameNet(bits uint, ip, other net.IP) bool {
|
||||
nb := int(bits / 8)
|
||||
mask := ^byte(0xFF >> (bits % 8))
|
||||
if mask != 0 && nb < len(ip) && ip[nb]&mask != other[nb]&mask {
|
||||
return false
|
||||
}
|
||||
return nb <= len(ip) && ip[:nb].Equal(other[:nb])
|
||||
}
|
||||
|
||||
// DistinctNetSet tracks IPs, ensuring that at most N of them
|
||||
// fall into the same network range.
|
||||
type DistinctNetSet struct {
|
||||
Subnet uint // number of common prefix bits
|
||||
Limit uint // maximum number of IPs in each subnet
|
||||
|
||||
members map[string]uint
|
||||
buf net.IP
|
||||
}
|
||||
|
||||
// Add adds an IP address to the set. It returns false (and doesn't add the IP) if the
|
||||
// number of existing IPs in the defined range exceeds the limit.
|
||||
func (s *DistinctNetSet) Add(ip net.IP) bool {
|
||||
key := s.key(ip)
|
||||
n := s.members[string(key)]
|
||||
if n < s.Limit {
|
||||
s.members[string(key)] = n + 1
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Remove removes an IP from the set.
|
||||
func (s *DistinctNetSet) Remove(ip net.IP) {
|
||||
key := s.key(ip)
|
||||
if n, ok := s.members[string(key)]; ok {
|
||||
if n == 1 {
|
||||
delete(s.members, string(key))
|
||||
} else {
|
||||
s.members[string(key)] = n - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Contains whether the given IP is contained in the set.
|
||||
func (s DistinctNetSet) Contains(ip net.IP) bool {
|
||||
key := s.key(ip)
|
||||
_, ok := s.members[string(key)]
|
||||
return ok
|
||||
}
|
||||
|
||||
// Len returns the number of tracked IPs.
|
||||
func (s DistinctNetSet) Len() int {
|
||||
n := uint(0)
|
||||
for _, i := range s.members {
|
||||
n += i
|
||||
}
|
||||
return int(n)
|
||||
}
|
||||
|
||||
// key encodes the map key for an address into a temporary buffer.
|
||||
//
|
||||
// The first byte of key is '4' or '6' to distinguish IPv4/IPv6 address types.
|
||||
// The remainder of the key is the IP, truncated to the number of bits.
|
||||
func (s *DistinctNetSet) key(ip net.IP) net.IP {
|
||||
// Lazily initialize storage.
|
||||
if s.members == nil {
|
||||
s.members = make(map[string]uint)
|
||||
s.buf = make(net.IP, 17)
|
||||
}
|
||||
// Canonicalize ip and bits.
|
||||
typ := byte('6')
|
||||
if ip4 := ip.To4(); ip4 != nil {
|
||||
typ, ip = '4', ip4
|
||||
}
|
||||
bits := s.Subnet
|
||||
if bits > uint(len(ip)*8) {
|
||||
bits = uint(len(ip) * 8)
|
||||
}
|
||||
// Encode the prefix into s.buf.
|
||||
nb := int(bits / 8)
|
||||
mask := ^byte(0xFF >> (bits % 8))
|
||||
s.buf[0] = typ
|
||||
buf := append(s.buf[:1], ip[:nb]...)
|
||||
if nb < len(ip) && mask != 0 {
|
||||
buf = append(buf, ip[nb]&mask)
|
||||
}
|
||||
return buf
|
||||
}
|
||||
|
||||
// String implements fmt.Stringer
|
||||
func (s DistinctNetSet) String() string {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString("{")
|
||||
keys := make([]string, 0, len(s.members))
|
||||
for k := range s.members {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for i, k := range keys {
|
||||
var ip net.IP
|
||||
if k[0] == '4' {
|
||||
ip = make(net.IP, 4)
|
||||
} else {
|
||||
ip = make(net.IP, 16)
|
||||
}
|
||||
copy(ip, k[1:])
|
||||
fmt.Fprintf(&buf, "%v×%d", ip, s.members[k])
|
||||
if i != len(keys)-1 {
|
||||
buf.WriteString(" ")
|
||||
}
|
||||
}
|
||||
buf.WriteString("}")
|
||||
return buf.String()
|
||||
}
|
||||
27
vendor/github.com/ethereum/go-ethereum/p2p/netutil/toobig_notwindows.go
generated
vendored
Normal file
27
vendor/github.com/ethereum/go-ethereum/p2p/netutil/toobig_notwindows.go
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package netutil
|
||||
|
||||
// isPacketTooBig reports whether err indicates that a UDP packet didn't
|
||||
// fit the receive buffer. There is no such error on
|
||||
// non-Windows platforms.
|
||||
func isPacketTooBig(err error) bool {
|
||||
return false
|
||||
}
|
||||
41
vendor/github.com/ethereum/go-ethereum/p2p/netutil/toobig_windows.go
generated
vendored
Normal file
41
vendor/github.com/ethereum/go-ethereum/p2p/netutil/toobig_windows.go
generated
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package netutil
|
||||
|
||||
import (
|
||||
"net"
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
const _WSAEMSGSIZE = syscall.Errno(10040)
|
||||
|
||||
// isPacketTooBig reports whether err indicates that a UDP packet didn't
|
||||
// fit the receive buffer. On Windows, WSARecvFrom returns
|
||||
// code WSAEMSGSIZE and no data if this happens.
|
||||
func isPacketTooBig(err error) bool {
|
||||
if opErr, ok := err.(*net.OpError); ok {
|
||||
if scErr, ok := opErr.Err.(*os.SyscallError); ok {
|
||||
return scErr.Err == _WSAEMSGSIZE
|
||||
}
|
||||
return opErr.Err == _WSAEMSGSIZE
|
||||
}
|
||||
return false
|
||||
}
|
||||
537
vendor/github.com/ethereum/go-ethereum/p2p/peer.go
generated
vendored
Normal file
537
vendor/github.com/ethereum/go-ethereum/p2p/peer.go
generated
vendored
Normal file
@@ -0,0 +1,537 @@
|
||||
// Copyright 2014 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package p2p
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/mclock"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethereum/go-ethereum/p2p/enr"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrShuttingDown = errors.New("shutting down")
|
||||
)
|
||||
|
||||
const (
|
||||
baseProtocolVersion = 5
|
||||
baseProtocolLength = uint64(16)
|
||||
baseProtocolMaxMsgSize = 2 * 1024
|
||||
|
||||
snappyProtocolVersion = 5
|
||||
|
||||
pingInterval = 15 * time.Second
|
||||
)
|
||||
|
||||
const (
|
||||
// devp2p message codes
|
||||
handshakeMsg = 0x00
|
||||
discMsg = 0x01
|
||||
pingMsg = 0x02
|
||||
pongMsg = 0x03
|
||||
)
|
||||
|
||||
// protoHandshake is the RLP structure of the protocol handshake.
|
||||
type protoHandshake struct {
|
||||
Version uint64
|
||||
Name string
|
||||
Caps []Cap
|
||||
ListenPort uint64
|
||||
ID []byte // secp256k1 public key
|
||||
|
||||
// Ignore additional fields (for forward compatibility).
|
||||
Rest []rlp.RawValue `rlp:"tail"`
|
||||
}
|
||||
|
||||
// PeerEventType is the type of peer events emitted by a p2p.Server
|
||||
type PeerEventType string
|
||||
|
||||
const (
|
||||
// PeerEventTypeAdd is the type of event emitted when a peer is added
|
||||
// to a p2p.Server
|
||||
PeerEventTypeAdd PeerEventType = "add"
|
||||
|
||||
// PeerEventTypeDrop is the type of event emitted when a peer is
|
||||
// dropped from a p2p.Server
|
||||
PeerEventTypeDrop PeerEventType = "drop"
|
||||
|
||||
// PeerEventTypeMsgSend is the type of event emitted when a
|
||||
// message is successfully sent to a peer
|
||||
PeerEventTypeMsgSend PeerEventType = "msgsend"
|
||||
|
||||
// PeerEventTypeMsgRecv is the type of event emitted when a
|
||||
// message is received from a peer
|
||||
PeerEventTypeMsgRecv PeerEventType = "msgrecv"
|
||||
)
|
||||
|
||||
// PeerEvent is an event emitted when peers are either added or dropped from
|
||||
// a p2p.Server or when a message is sent or received on a peer connection
|
||||
type PeerEvent struct {
|
||||
Type PeerEventType `json:"type"`
|
||||
Peer enode.ID `json:"peer"`
|
||||
Error string `json:"error,omitempty"`
|
||||
Protocol string `json:"protocol,omitempty"`
|
||||
MsgCode *uint64 `json:"msg_code,omitempty"`
|
||||
MsgSize *uint32 `json:"msg_size,omitempty"`
|
||||
LocalAddress string `json:"local,omitempty"`
|
||||
RemoteAddress string `json:"remote,omitempty"`
|
||||
}
|
||||
|
||||
// Peer represents a connected remote node.
|
||||
type Peer struct {
|
||||
rw *conn
|
||||
running map[string]*protoRW
|
||||
log log.Logger
|
||||
created mclock.AbsTime
|
||||
|
||||
wg sync.WaitGroup
|
||||
protoErr chan error
|
||||
closed chan struct{}
|
||||
disc chan DiscReason
|
||||
|
||||
// events receives message send / receive events if set
|
||||
events *event.Feed
|
||||
testPipe *MsgPipeRW // for testing
|
||||
}
|
||||
|
||||
// NewPeer returns a peer for testing purposes.
|
||||
func NewPeer(id enode.ID, name string, caps []Cap) *Peer {
|
||||
// Generate a fake set of local protocols to match as running caps. Almost
|
||||
// no fields needs to be meaningful here as we're only using it to cross-
|
||||
// check with the "remote" caps array.
|
||||
protos := make([]Protocol, len(caps))
|
||||
for i, cap := range caps {
|
||||
protos[i].Name = cap.Name
|
||||
protos[i].Version = cap.Version
|
||||
}
|
||||
pipe, _ := net.Pipe()
|
||||
node := enode.SignNull(new(enr.Record), id)
|
||||
conn := &conn{fd: pipe, transport: nil, node: node, caps: caps, name: name}
|
||||
peer := newPeer(log.Root(), conn, protos)
|
||||
close(peer.closed) // ensures Disconnect doesn't block
|
||||
return peer
|
||||
}
|
||||
|
||||
// NewPeerPipe creates a peer for testing purposes.
|
||||
// The message pipe given as the last parameter is closed when
|
||||
// Disconnect is called on the peer.
|
||||
func NewPeerPipe(id enode.ID, name string, caps []Cap, pipe *MsgPipeRW) *Peer {
|
||||
p := NewPeer(id, name, caps)
|
||||
p.testPipe = pipe
|
||||
return p
|
||||
}
|
||||
|
||||
// ID returns the node's public key.
|
||||
func (p *Peer) ID() enode.ID {
|
||||
return p.rw.node.ID()
|
||||
}
|
||||
|
||||
// Node returns the peer's node descriptor.
|
||||
func (p *Peer) Node() *enode.Node {
|
||||
return p.rw.node
|
||||
}
|
||||
|
||||
// Name returns an abbreviated form of the name
|
||||
func (p *Peer) Name() string {
|
||||
s := p.rw.name
|
||||
if len(s) > 20 {
|
||||
return s[:20] + "..."
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Fullname returns the node name that the remote node advertised.
|
||||
func (p *Peer) Fullname() string {
|
||||
return p.rw.name
|
||||
}
|
||||
|
||||
// Caps returns the capabilities (supported subprotocols) of the remote peer.
|
||||
func (p *Peer) Caps() []Cap {
|
||||
// TODO: maybe return copy
|
||||
return p.rw.caps
|
||||
}
|
||||
|
||||
// RunningCap returns true if the peer is actively connected using any of the
|
||||
// enumerated versions of a specific protocol, meaning that at least one of the
|
||||
// versions is supported by both this node and the peer p.
|
||||
func (p *Peer) RunningCap(protocol string, versions []uint) bool {
|
||||
if proto, ok := p.running[protocol]; ok {
|
||||
for _, ver := range versions {
|
||||
if proto.Version == ver {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// RemoteAddr returns the remote address of the network connection.
|
||||
func (p *Peer) RemoteAddr() net.Addr {
|
||||
return p.rw.fd.RemoteAddr()
|
||||
}
|
||||
|
||||
// LocalAddr returns the local address of the network connection.
|
||||
func (p *Peer) LocalAddr() net.Addr {
|
||||
return p.rw.fd.LocalAddr()
|
||||
}
|
||||
|
||||
// Disconnect terminates the peer connection with the given reason.
|
||||
// It returns immediately and does not wait until the connection is closed.
|
||||
func (p *Peer) Disconnect(reason DiscReason) {
|
||||
if p.testPipe != nil {
|
||||
p.testPipe.Close()
|
||||
}
|
||||
|
||||
select {
|
||||
case p.disc <- reason:
|
||||
case <-p.closed:
|
||||
}
|
||||
}
|
||||
|
||||
// String implements fmt.Stringer.
|
||||
func (p *Peer) String() string {
|
||||
id := p.ID()
|
||||
return fmt.Sprintf("Peer %x %v", id[:8], p.RemoteAddr())
|
||||
}
|
||||
|
||||
// Inbound returns true if the peer is an inbound connection
|
||||
func (p *Peer) Inbound() bool {
|
||||
return p.rw.is(inboundConn)
|
||||
}
|
||||
|
||||
func newPeer(log log.Logger, conn *conn, protocols []Protocol) *Peer {
|
||||
protomap := matchProtocols(protocols, conn.caps, conn)
|
||||
p := &Peer{
|
||||
rw: conn,
|
||||
running: protomap,
|
||||
created: mclock.Now(),
|
||||
disc: make(chan DiscReason),
|
||||
protoErr: make(chan error, len(protomap)+1), // protocols + pingLoop
|
||||
closed: make(chan struct{}),
|
||||
log: log.New("id", conn.node.ID(), "conn", conn.flags),
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *Peer) Log() log.Logger {
|
||||
return p.log
|
||||
}
|
||||
|
||||
func (p *Peer) run() (remoteRequested bool, err error) {
|
||||
var (
|
||||
writeStart = make(chan struct{}, 1)
|
||||
writeErr = make(chan error, 1)
|
||||
readErr = make(chan error, 1)
|
||||
reason DiscReason // sent to the peer
|
||||
)
|
||||
p.wg.Add(2)
|
||||
go p.readLoop(readErr)
|
||||
go p.pingLoop()
|
||||
|
||||
// Start all protocol handlers.
|
||||
writeStart <- struct{}{}
|
||||
p.startProtocols(writeStart, writeErr)
|
||||
|
||||
// Wait for an error or disconnect.
|
||||
loop:
|
||||
for {
|
||||
select {
|
||||
case err = <-writeErr:
|
||||
// A write finished. Allow the next write to start if
|
||||
// there was no error.
|
||||
if err != nil {
|
||||
reason = DiscNetworkError
|
||||
break loop
|
||||
}
|
||||
writeStart <- struct{}{}
|
||||
case err = <-readErr:
|
||||
if r, ok := err.(DiscReason); ok {
|
||||
remoteRequested = true
|
||||
reason = r
|
||||
} else {
|
||||
reason = DiscNetworkError
|
||||
}
|
||||
break loop
|
||||
case err = <-p.protoErr:
|
||||
reason = discReasonForError(err)
|
||||
break loop
|
||||
case err = <-p.disc:
|
||||
reason = discReasonForError(err)
|
||||
break loop
|
||||
}
|
||||
}
|
||||
|
||||
close(p.closed)
|
||||
p.rw.close(reason)
|
||||
p.wg.Wait()
|
||||
return remoteRequested, err
|
||||
}
|
||||
|
||||
func (p *Peer) pingLoop() {
|
||||
ping := time.NewTimer(pingInterval)
|
||||
defer p.wg.Done()
|
||||
defer ping.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ping.C:
|
||||
if err := SendItems(p.rw, pingMsg); err != nil {
|
||||
p.protoErr <- err
|
||||
return
|
||||
}
|
||||
ping.Reset(pingInterval)
|
||||
case <-p.closed:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Peer) readLoop(errc chan<- error) {
|
||||
defer p.wg.Done()
|
||||
for {
|
||||
msg, err := p.rw.ReadMsg()
|
||||
if err != nil {
|
||||
errc <- err
|
||||
return
|
||||
}
|
||||
msg.ReceivedAt = time.Now()
|
||||
if err = p.handle(msg); err != nil {
|
||||
errc <- err
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Peer) handle(msg Msg) error {
|
||||
switch {
|
||||
case msg.Code == pingMsg:
|
||||
msg.Discard()
|
||||
go SendItems(p.rw, pongMsg)
|
||||
case msg.Code == discMsg:
|
||||
// This is the last message. We don't need to discard or
|
||||
// check errors because, the connection will be closed after it.
|
||||
var m struct{ R DiscReason }
|
||||
rlp.Decode(msg.Payload, &m)
|
||||
return m.R
|
||||
case msg.Code < baseProtocolLength:
|
||||
// ignore other base protocol messages
|
||||
return msg.Discard()
|
||||
default:
|
||||
// it's a subprotocol message
|
||||
proto, err := p.getProto(msg.Code)
|
||||
if err != nil {
|
||||
return fmt.Errorf("msg code out of range: %v", msg.Code)
|
||||
}
|
||||
if metrics.Enabled {
|
||||
m := fmt.Sprintf("%s/%s/%d/%#02x", ingressMeterName, proto.Name, proto.Version, msg.Code-proto.offset)
|
||||
metrics.GetOrRegisterMeter(m, nil).Mark(int64(msg.meterSize))
|
||||
metrics.GetOrRegisterMeter(m+"/packets", nil).Mark(1)
|
||||
}
|
||||
select {
|
||||
case proto.in <- msg:
|
||||
return nil
|
||||
case <-p.closed:
|
||||
return io.EOF
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func countMatchingProtocols(protocols []Protocol, caps []Cap) int {
|
||||
n := 0
|
||||
for _, cap := range caps {
|
||||
for _, proto := range protocols {
|
||||
if proto.Name == cap.Name && proto.Version == cap.Version {
|
||||
n++
|
||||
}
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// matchProtocols creates structures for matching named subprotocols.
|
||||
func matchProtocols(protocols []Protocol, caps []Cap, rw MsgReadWriter) map[string]*protoRW {
|
||||
sort.Sort(capsByNameAndVersion(caps))
|
||||
offset := baseProtocolLength
|
||||
result := make(map[string]*protoRW)
|
||||
|
||||
outer:
|
||||
for _, cap := range caps {
|
||||
for _, proto := range protocols {
|
||||
if proto.Name == cap.Name && proto.Version == cap.Version {
|
||||
// If an old protocol version matched, revert it
|
||||
if old := result[cap.Name]; old != nil {
|
||||
offset -= old.Length
|
||||
}
|
||||
// Assign the new match
|
||||
result[cap.Name] = &protoRW{Protocol: proto, offset: offset, in: make(chan Msg), w: rw}
|
||||
offset += proto.Length
|
||||
|
||||
continue outer
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (p *Peer) startProtocols(writeStart <-chan struct{}, writeErr chan<- error) {
|
||||
p.wg.Add(len(p.running))
|
||||
for _, proto := range p.running {
|
||||
proto := proto
|
||||
proto.closed = p.closed
|
||||
proto.wstart = writeStart
|
||||
proto.werr = writeErr
|
||||
var rw MsgReadWriter = proto
|
||||
if p.events != nil {
|
||||
rw = newMsgEventer(rw, p.events, p.ID(), proto.Name, p.Info().Network.RemoteAddress, p.Info().Network.LocalAddress)
|
||||
}
|
||||
p.log.Trace(fmt.Sprintf("Starting protocol %s/%d", proto.Name, proto.Version))
|
||||
go func() {
|
||||
defer p.wg.Done()
|
||||
err := proto.Run(p, rw)
|
||||
if err == nil {
|
||||
p.log.Trace(fmt.Sprintf("Protocol %s/%d returned", proto.Name, proto.Version))
|
||||
err = errProtocolReturned
|
||||
} else if !errors.Is(err, io.EOF) {
|
||||
p.log.Trace(fmt.Sprintf("Protocol %s/%d failed", proto.Name, proto.Version), "err", err)
|
||||
}
|
||||
p.protoErr <- err
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// getProto finds the protocol responsible for handling
|
||||
// the given message code.
|
||||
func (p *Peer) getProto(code uint64) (*protoRW, error) {
|
||||
for _, proto := range p.running {
|
||||
if code >= proto.offset && code < proto.offset+proto.Length {
|
||||
return proto, nil
|
||||
}
|
||||
}
|
||||
return nil, newPeerError(errInvalidMsgCode, "%d", code)
|
||||
}
|
||||
|
||||
type protoRW struct {
|
||||
Protocol
|
||||
in chan Msg // receives read messages
|
||||
closed <-chan struct{} // receives when peer is shutting down
|
||||
wstart <-chan struct{} // receives when write may start
|
||||
werr chan<- error // for write results
|
||||
offset uint64
|
||||
w MsgWriter
|
||||
}
|
||||
|
||||
func (rw *protoRW) WriteMsg(msg Msg) (err error) {
|
||||
if msg.Code >= rw.Length {
|
||||
return newPeerError(errInvalidMsgCode, "not handled")
|
||||
}
|
||||
msg.meterCap = rw.cap()
|
||||
msg.meterCode = msg.Code
|
||||
|
||||
msg.Code += rw.offset
|
||||
|
||||
select {
|
||||
case <-rw.wstart:
|
||||
err = rw.w.WriteMsg(msg)
|
||||
// Report write status back to Peer.run. It will initiate
|
||||
// shutdown if the error is non-nil and unblock the next write
|
||||
// otherwise. The calling protocol code should exit for errors
|
||||
// as well but we don't want to rely on that.
|
||||
rw.werr <- err
|
||||
case <-rw.closed:
|
||||
err = ErrShuttingDown
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (rw *protoRW) ReadMsg() (Msg, error) {
|
||||
select {
|
||||
case msg := <-rw.in:
|
||||
msg.Code -= rw.offset
|
||||
return msg, nil
|
||||
case <-rw.closed:
|
||||
return Msg{}, io.EOF
|
||||
}
|
||||
}
|
||||
|
||||
// PeerInfo represents a short summary of the information known about a connected
|
||||
// peer. Sub-protocol independent fields are contained and initialized here, with
|
||||
// protocol specifics delegated to all connected sub-protocols.
|
||||
type PeerInfo struct {
|
||||
ENR string `json:"enr,omitempty"` // Ethereum Node Record
|
||||
Enode string `json:"enode"` // Node URL
|
||||
ID string `json:"id"` // Unique node identifier
|
||||
Name string `json:"name"` // Name of the node, including client type, version, OS, custom data
|
||||
Caps []string `json:"caps"` // Protocols advertised by this peer
|
||||
Network struct {
|
||||
LocalAddress string `json:"localAddress"` // Local endpoint of the TCP data connection
|
||||
RemoteAddress string `json:"remoteAddress"` // Remote endpoint of the TCP data connection
|
||||
Inbound bool `json:"inbound"`
|
||||
Trusted bool `json:"trusted"`
|
||||
Static bool `json:"static"`
|
||||
} `json:"network"`
|
||||
Protocols map[string]interface{} `json:"protocols"` // Sub-protocol specific metadata fields
|
||||
}
|
||||
|
||||
// Info gathers and returns a collection of metadata known about a peer.
|
||||
func (p *Peer) Info() *PeerInfo {
|
||||
// Gather the protocol capabilities
|
||||
var caps []string
|
||||
for _, cap := range p.Caps() {
|
||||
caps = append(caps, cap.String())
|
||||
}
|
||||
// Assemble the generic peer metadata
|
||||
info := &PeerInfo{
|
||||
Enode: p.Node().URLv4(),
|
||||
ID: p.ID().String(),
|
||||
Name: p.Fullname(),
|
||||
Caps: caps,
|
||||
Protocols: make(map[string]interface{}),
|
||||
}
|
||||
if p.Node().Seq() > 0 {
|
||||
info.ENR = p.Node().String()
|
||||
}
|
||||
info.Network.LocalAddress = p.LocalAddr().String()
|
||||
info.Network.RemoteAddress = p.RemoteAddr().String()
|
||||
info.Network.Inbound = p.rw.is(inboundConn)
|
||||
info.Network.Trusted = p.rw.is(trustedConn)
|
||||
info.Network.Static = p.rw.is(staticDialedConn)
|
||||
|
||||
// Gather all the running protocol infos
|
||||
for _, proto := range p.running {
|
||||
protoInfo := interface{}("unknown")
|
||||
if query := proto.Protocol.PeerInfo; query != nil {
|
||||
if metadata := query(p.ID()); metadata != nil {
|
||||
protoInfo = metadata
|
||||
} else {
|
||||
protoInfo = "handshake"
|
||||
}
|
||||
}
|
||||
info.Protocols[proto.Name] = protoInfo
|
||||
}
|
||||
return info
|
||||
}
|
||||
119
vendor/github.com/ethereum/go-ethereum/p2p/peer_error.go
generated
vendored
Normal file
119
vendor/github.com/ethereum/go-ethereum/p2p/peer_error.go
generated
vendored
Normal file
@@ -0,0 +1,119 @@
|
||||
// Copyright 2014 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package p2p
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const (
|
||||
errInvalidMsgCode = iota
|
||||
errInvalidMsg
|
||||
)
|
||||
|
||||
var errorToString = map[int]string{
|
||||
errInvalidMsgCode: "invalid message code",
|
||||
errInvalidMsg: "invalid message",
|
||||
}
|
||||
|
||||
type peerError struct {
|
||||
code int
|
||||
message string
|
||||
}
|
||||
|
||||
func newPeerError(code int, format string, v ...interface{}) *peerError {
|
||||
desc, ok := errorToString[code]
|
||||
if !ok {
|
||||
panic("invalid error code")
|
||||
}
|
||||
err := &peerError{code, desc}
|
||||
if format != "" {
|
||||
err.message += ": " + fmt.Sprintf(format, v...)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (pe *peerError) Error() string {
|
||||
return pe.message
|
||||
}
|
||||
|
||||
var errProtocolReturned = errors.New("protocol returned")
|
||||
|
||||
type DiscReason uint8
|
||||
|
||||
const (
|
||||
DiscRequested DiscReason = iota
|
||||
DiscNetworkError
|
||||
DiscProtocolError
|
||||
DiscUselessPeer
|
||||
DiscTooManyPeers
|
||||
DiscAlreadyConnected
|
||||
DiscIncompatibleVersion
|
||||
DiscInvalidIdentity
|
||||
DiscQuitting
|
||||
DiscUnexpectedIdentity
|
||||
DiscSelf
|
||||
DiscReadTimeout
|
||||
DiscSubprotocolError = DiscReason(0x10)
|
||||
)
|
||||
|
||||
var discReasonToString = [...]string{
|
||||
DiscRequested: "disconnect requested",
|
||||
DiscNetworkError: "network error",
|
||||
DiscProtocolError: "breach of protocol",
|
||||
DiscUselessPeer: "useless peer",
|
||||
DiscTooManyPeers: "too many peers",
|
||||
DiscAlreadyConnected: "already connected",
|
||||
DiscIncompatibleVersion: "incompatible p2p protocol version",
|
||||
DiscInvalidIdentity: "invalid node identity",
|
||||
DiscQuitting: "client quitting",
|
||||
DiscUnexpectedIdentity: "unexpected identity",
|
||||
DiscSelf: "connected to self",
|
||||
DiscReadTimeout: "read timeout",
|
||||
DiscSubprotocolError: "subprotocol error",
|
||||
}
|
||||
|
||||
func (d DiscReason) String() string {
|
||||
if len(discReasonToString) <= int(d) {
|
||||
return fmt.Sprintf("unknown disconnect reason %d", d)
|
||||
}
|
||||
return discReasonToString[d]
|
||||
}
|
||||
|
||||
func (d DiscReason) Error() string {
|
||||
return d.String()
|
||||
}
|
||||
|
||||
func discReasonForError(err error) DiscReason {
|
||||
if reason, ok := err.(DiscReason); ok {
|
||||
return reason
|
||||
}
|
||||
if errors.Is(err, errProtocolReturned) {
|
||||
return DiscQuitting
|
||||
}
|
||||
peerError, ok := err.(*peerError)
|
||||
if ok {
|
||||
switch peerError.code {
|
||||
case errInvalidMsgCode, errInvalidMsg:
|
||||
return DiscProtocolError
|
||||
default:
|
||||
return DiscSubprotocolError
|
||||
}
|
||||
}
|
||||
return DiscSubprotocolError
|
||||
}
|
||||
86
vendor/github.com/ethereum/go-ethereum/p2p/protocol.go
generated
vendored
Normal file
86
vendor/github.com/ethereum/go-ethereum/p2p/protocol.go
generated
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
// Copyright 2014 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package p2p
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||
"github.com/ethereum/go-ethereum/p2p/enr"
|
||||
)
|
||||
|
||||
// Protocol represents a P2P subprotocol implementation.
|
||||
type Protocol struct {
|
||||
// Name should contain the official protocol name,
|
||||
// often a three-letter word.
|
||||
Name string
|
||||
|
||||
// Version should contain the version number of the protocol.
|
||||
Version uint
|
||||
|
||||
// Length should contain the number of message codes used
|
||||
// by the protocol.
|
||||
Length uint64
|
||||
|
||||
// Run is called in a new goroutine when the protocol has been
|
||||
// negotiated with a peer. It should read and write messages from
|
||||
// rw. The Payload for each message must be fully consumed.
|
||||
//
|
||||
// The peer connection is closed when Start returns. It should return
|
||||
// any protocol-level error (such as an I/O error) that is
|
||||
// encountered.
|
||||
Run func(peer *Peer, rw MsgReadWriter) error
|
||||
|
||||
// NodeInfo is an optional helper method to retrieve protocol specific metadata
|
||||
// about the host node.
|
||||
NodeInfo func() interface{}
|
||||
|
||||
// PeerInfo is an optional helper method to retrieve protocol specific metadata
|
||||
// about a certain peer in the network. If an info retrieval function is set,
|
||||
// but returns nil, it is assumed that the protocol handshake is still running.
|
||||
PeerInfo func(id enode.ID) interface{}
|
||||
|
||||
// DialCandidates, if non-nil, is a way to tell Server about protocol-specific nodes
|
||||
// that should be dialed. The server continuously reads nodes from the iterator and
|
||||
// attempts to create connections to them.
|
||||
DialCandidates enode.Iterator
|
||||
|
||||
// Attributes contains protocol specific information for the node record.
|
||||
Attributes []enr.Entry
|
||||
}
|
||||
|
||||
func (p Protocol) cap() Cap {
|
||||
return Cap{p.Name, p.Version}
|
||||
}
|
||||
|
||||
// Cap is the structure of a peer capability.
|
||||
type Cap struct {
|
||||
Name string
|
||||
Version uint
|
||||
}
|
||||
|
||||
func (cap Cap) String() string {
|
||||
return fmt.Sprintf("%s/%d", cap.Name, cap.Version)
|
||||
}
|
||||
|
||||
type capsByNameAndVersion []Cap
|
||||
|
||||
func (cs capsByNameAndVersion) Len() int { return len(cs) }
|
||||
func (cs capsByNameAndVersion) Swap(i, j int) { cs[i], cs[j] = cs[j], cs[i] }
|
||||
func (cs capsByNameAndVersion) Less(i, j int) bool {
|
||||
return cs[i].Name < cs[j].Name || (cs[i].Name == cs[j].Name && cs[i].Version < cs[j].Version)
|
||||
}
|
||||
127
vendor/github.com/ethereum/go-ethereum/p2p/rlpx/buffer.go
generated
vendored
Normal file
127
vendor/github.com/ethereum/go-ethereum/p2p/rlpx/buffer.go
generated
vendored
Normal file
@@ -0,0 +1,127 @@
|
||||
// Copyright 2021 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package rlpx
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// readBuffer implements buffering for network reads. This type is similar to bufio.Reader,
|
||||
// with two crucial differences: the buffer slice is exposed, and the buffer keeps all
|
||||
// read data available until reset.
|
||||
//
|
||||
// How to use this type:
|
||||
//
|
||||
// Keep a readBuffer b alongside the underlying network connection. When reading a packet
|
||||
// from the connection, first call b.reset(). This empties b.data. Now perform reads
|
||||
// through b.read() until the end of the packet is reached. The complete packet data is
|
||||
// now available in b.data.
|
||||
type readBuffer struct {
|
||||
data []byte
|
||||
end int
|
||||
}
|
||||
|
||||
// reset removes all processed data which was read since the last call to reset.
|
||||
// After reset, len(b.data) is zero.
|
||||
func (b *readBuffer) reset() {
|
||||
unprocessed := b.end - len(b.data)
|
||||
copy(b.data[:unprocessed], b.data[len(b.data):b.end])
|
||||
b.end = unprocessed
|
||||
b.data = b.data[:0]
|
||||
}
|
||||
|
||||
// read reads at least n bytes from r, returning the bytes.
|
||||
// The returned slice is valid until the next call to reset.
|
||||
func (b *readBuffer) read(r io.Reader, n int) ([]byte, error) {
|
||||
offset := len(b.data)
|
||||
have := b.end - len(b.data)
|
||||
|
||||
// If n bytes are available in the buffer, there is no need to read from r at all.
|
||||
if have >= n {
|
||||
b.data = b.data[:offset+n]
|
||||
return b.data[offset : offset+n], nil
|
||||
}
|
||||
|
||||
// Make buffer space available.
|
||||
need := n - have
|
||||
b.grow(need)
|
||||
|
||||
// Read.
|
||||
rn, err := io.ReadAtLeast(r, b.data[b.end:cap(b.data)], need)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b.end += rn
|
||||
b.data = b.data[:offset+n]
|
||||
return b.data[offset : offset+n], nil
|
||||
}
|
||||
|
||||
// grow ensures the buffer has at least n bytes of unused space.
|
||||
func (b *readBuffer) grow(n int) {
|
||||
if cap(b.data)-b.end >= n {
|
||||
return
|
||||
}
|
||||
need := n - (cap(b.data) - b.end)
|
||||
offset := len(b.data)
|
||||
b.data = append(b.data[:cap(b.data)], make([]byte, need)...)
|
||||
b.data = b.data[:offset]
|
||||
}
|
||||
|
||||
// writeBuffer implements buffering for network writes. This is essentially
|
||||
// a convenience wrapper around a byte slice.
|
||||
type writeBuffer struct {
|
||||
data []byte
|
||||
}
|
||||
|
||||
func (b *writeBuffer) reset() {
|
||||
b.data = b.data[:0]
|
||||
}
|
||||
|
||||
func (b *writeBuffer) appendZero(n int) []byte {
|
||||
offset := len(b.data)
|
||||
b.data = append(b.data, make([]byte, n)...)
|
||||
return b.data[offset : offset+n]
|
||||
}
|
||||
|
||||
func (b *writeBuffer) Write(data []byte) (int, error) {
|
||||
b.data = append(b.data, data...)
|
||||
return len(data), nil
|
||||
}
|
||||
|
||||
const maxUint24 = int(^uint32(0) >> 8)
|
||||
|
||||
func readUint24(b []byte) uint32 {
|
||||
return uint32(b[2]) | uint32(b[1])<<8 | uint32(b[0])<<16
|
||||
}
|
||||
|
||||
func putUint24(v uint32, b []byte) {
|
||||
b[0] = byte(v >> 16)
|
||||
b[1] = byte(v >> 8)
|
||||
b[2] = byte(v)
|
||||
}
|
||||
|
||||
// growslice ensures b has the wanted length by either expanding it to its capacity
|
||||
// or allocating a new slice if b has insufficient capacity.
|
||||
func growslice(b []byte, wantLength int) []byte {
|
||||
if len(b) >= wantLength {
|
||||
return b
|
||||
}
|
||||
if cap(b) >= wantLength {
|
||||
return b[:cap(b)]
|
||||
}
|
||||
return make([]byte, wantLength)
|
||||
}
|
||||
676
vendor/github.com/ethereum/go-ethereum/p2p/rlpx/rlpx.go
generated
vendored
Normal file
676
vendor/github.com/ethereum/go-ethereum/p2p/rlpx/rlpx.go
generated
vendored
Normal file
@@ -0,0 +1,676 @@
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Package rlpx implements the RLPx transport protocol.
|
||||
package rlpx
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
mrand "math/rand"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/crypto/ecies"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/golang/snappy"
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
// Conn is an RLPx network connection. It wraps a low-level network connection. The
|
||||
// underlying connection should not be used for other activity when it is wrapped by Conn.
|
||||
//
|
||||
// Before sending messages, a handshake must be performed by calling the Handshake method.
|
||||
// This type is not generally safe for concurrent use, but reading and writing of messages
|
||||
// may happen concurrently after the handshake.
|
||||
type Conn struct {
|
||||
dialDest *ecdsa.PublicKey
|
||||
conn net.Conn
|
||||
session *sessionState
|
||||
|
||||
// These are the buffers for snappy compression.
|
||||
// Compression is enabled if they are non-nil.
|
||||
snappyReadBuffer []byte
|
||||
snappyWriteBuffer []byte
|
||||
}
|
||||
|
||||
// sessionState contains the session keys.
|
||||
type sessionState struct {
|
||||
enc cipher.Stream
|
||||
dec cipher.Stream
|
||||
|
||||
egressMAC hashMAC
|
||||
ingressMAC hashMAC
|
||||
rbuf readBuffer
|
||||
wbuf writeBuffer
|
||||
}
|
||||
|
||||
// hashMAC holds the state of the RLPx v4 MAC contraption.
|
||||
type hashMAC struct {
|
||||
cipher cipher.Block
|
||||
hash hash.Hash
|
||||
aesBuffer [16]byte
|
||||
hashBuffer [32]byte
|
||||
seedBuffer [32]byte
|
||||
}
|
||||
|
||||
func newHashMAC(cipher cipher.Block, h hash.Hash) hashMAC {
|
||||
m := hashMAC{cipher: cipher, hash: h}
|
||||
if cipher.BlockSize() != len(m.aesBuffer) {
|
||||
panic(fmt.Errorf("invalid MAC cipher block size %d", cipher.BlockSize()))
|
||||
}
|
||||
if h.Size() != len(m.hashBuffer) {
|
||||
panic(fmt.Errorf("invalid MAC digest size %d", h.Size()))
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// NewConn wraps the given network connection. If dialDest is non-nil, the connection
|
||||
// behaves as the initiator during the handshake.
|
||||
func NewConn(conn net.Conn, dialDest *ecdsa.PublicKey) *Conn {
|
||||
return &Conn{
|
||||
dialDest: dialDest,
|
||||
conn: conn,
|
||||
}
|
||||
}
|
||||
|
||||
// SetSnappy enables or disables snappy compression of messages. This is usually called
|
||||
// after the devp2p Hello message exchange when the negotiated version indicates that
|
||||
// compression is available on both ends of the connection.
|
||||
func (c *Conn) SetSnappy(snappy bool) {
|
||||
if snappy {
|
||||
c.snappyReadBuffer = []byte{}
|
||||
c.snappyWriteBuffer = []byte{}
|
||||
} else {
|
||||
c.snappyReadBuffer = nil
|
||||
c.snappyWriteBuffer = nil
|
||||
}
|
||||
}
|
||||
|
||||
// SetReadDeadline sets the deadline for all future read operations.
|
||||
func (c *Conn) SetReadDeadline(time time.Time) error {
|
||||
return c.conn.SetReadDeadline(time)
|
||||
}
|
||||
|
||||
// SetWriteDeadline sets the deadline for all future write operations.
|
||||
func (c *Conn) SetWriteDeadline(time time.Time) error {
|
||||
return c.conn.SetWriteDeadline(time)
|
||||
}
|
||||
|
||||
// SetDeadline sets the deadline for all future read and write operations.
|
||||
func (c *Conn) SetDeadline(time time.Time) error {
|
||||
return c.conn.SetDeadline(time)
|
||||
}
|
||||
|
||||
// Read reads a message from the connection.
|
||||
// The returned data buffer is valid until the next call to Read.
|
||||
func (c *Conn) Read() (code uint64, data []byte, wireSize int, err error) {
|
||||
if c.session == nil {
|
||||
panic("can't ReadMsg before handshake")
|
||||
}
|
||||
|
||||
frame, err := c.session.readFrame(c.conn)
|
||||
if err != nil {
|
||||
return 0, nil, 0, err
|
||||
}
|
||||
code, data, err = rlp.SplitUint64(frame)
|
||||
if err != nil {
|
||||
return 0, nil, 0, fmt.Errorf("invalid message code: %v", err)
|
||||
}
|
||||
wireSize = len(data)
|
||||
|
||||
// If snappy is enabled, verify and decompress message.
|
||||
if c.snappyReadBuffer != nil {
|
||||
var actualSize int
|
||||
actualSize, err = snappy.DecodedLen(data)
|
||||
if err != nil {
|
||||
return code, nil, 0, err
|
||||
}
|
||||
if actualSize > maxUint24 {
|
||||
return code, nil, 0, errPlainMessageTooLarge
|
||||
}
|
||||
c.snappyReadBuffer = growslice(c.snappyReadBuffer, actualSize)
|
||||
data, err = snappy.Decode(c.snappyReadBuffer, data)
|
||||
}
|
||||
return code, data, wireSize, err
|
||||
}
|
||||
|
||||
func (h *sessionState) readFrame(conn io.Reader) ([]byte, error) {
|
||||
h.rbuf.reset()
|
||||
|
||||
// Read the frame header.
|
||||
header, err := h.rbuf.read(conn, 32)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Verify header MAC.
|
||||
wantHeaderMAC := h.ingressMAC.computeHeader(header[:16])
|
||||
if !hmac.Equal(wantHeaderMAC, header[16:]) {
|
||||
return nil, errors.New("bad header MAC")
|
||||
}
|
||||
|
||||
// Decrypt the frame header to get the frame size.
|
||||
h.dec.XORKeyStream(header[:16], header[:16])
|
||||
fsize := readUint24(header[:16])
|
||||
// Frame size rounded up to 16 byte boundary for padding.
|
||||
rsize := fsize
|
||||
if padding := fsize % 16; padding > 0 {
|
||||
rsize += 16 - padding
|
||||
}
|
||||
|
||||
// Read the frame content.
|
||||
frame, err := h.rbuf.read(conn, int(rsize))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Validate frame MAC.
|
||||
frameMAC, err := h.rbuf.read(conn, 16)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wantFrameMAC := h.ingressMAC.computeFrame(frame)
|
||||
if !hmac.Equal(wantFrameMAC, frameMAC) {
|
||||
return nil, errors.New("bad frame MAC")
|
||||
}
|
||||
|
||||
// Decrypt the frame data.
|
||||
h.dec.XORKeyStream(frame, frame)
|
||||
return frame[:fsize], nil
|
||||
}
|
||||
|
||||
// Write writes a message to the connection.
|
||||
//
|
||||
// Write returns the written size of the message data. This may be less than or equal to
|
||||
// len(data) depending on whether snappy compression is enabled.
|
||||
func (c *Conn) Write(code uint64, data []byte) (uint32, error) {
|
||||
if c.session == nil {
|
||||
panic("can't WriteMsg before handshake")
|
||||
}
|
||||
if len(data) > maxUint24 {
|
||||
return 0, errPlainMessageTooLarge
|
||||
}
|
||||
if c.snappyWriteBuffer != nil {
|
||||
// Ensure the buffer has sufficient size.
|
||||
// Package snappy will allocate its own buffer if the provided
|
||||
// one is smaller than MaxEncodedLen.
|
||||
c.snappyWriteBuffer = growslice(c.snappyWriteBuffer, snappy.MaxEncodedLen(len(data)))
|
||||
data = snappy.Encode(c.snappyWriteBuffer, data)
|
||||
}
|
||||
|
||||
wireSize := uint32(len(data))
|
||||
err := c.session.writeFrame(c.conn, code, data)
|
||||
return wireSize, err
|
||||
}
|
||||
|
||||
func (h *sessionState) writeFrame(conn io.Writer, code uint64, data []byte) error {
|
||||
h.wbuf.reset()
|
||||
|
||||
// Write header.
|
||||
fsize := rlp.IntSize(code) + len(data)
|
||||
if fsize > maxUint24 {
|
||||
return errPlainMessageTooLarge
|
||||
}
|
||||
header := h.wbuf.appendZero(16)
|
||||
putUint24(uint32(fsize), header)
|
||||
copy(header[3:], zeroHeader)
|
||||
h.enc.XORKeyStream(header, header)
|
||||
|
||||
// Write header MAC.
|
||||
h.wbuf.Write(h.egressMAC.computeHeader(header))
|
||||
|
||||
// Encode and encrypt the frame data.
|
||||
offset := len(h.wbuf.data)
|
||||
h.wbuf.data = rlp.AppendUint64(h.wbuf.data, code)
|
||||
h.wbuf.Write(data)
|
||||
if padding := fsize % 16; padding > 0 {
|
||||
h.wbuf.appendZero(16 - padding)
|
||||
}
|
||||
framedata := h.wbuf.data[offset:]
|
||||
h.enc.XORKeyStream(framedata, framedata)
|
||||
|
||||
// Write frame MAC.
|
||||
h.wbuf.Write(h.egressMAC.computeFrame(framedata))
|
||||
|
||||
_, err := conn.Write(h.wbuf.data)
|
||||
return err
|
||||
}
|
||||
|
||||
// computeHeader computes the MAC of a frame header.
|
||||
func (m *hashMAC) computeHeader(header []byte) []byte {
|
||||
sum1 := m.hash.Sum(m.hashBuffer[:0])
|
||||
return m.compute(sum1, header)
|
||||
}
|
||||
|
||||
// computeFrame computes the MAC of framedata.
|
||||
func (m *hashMAC) computeFrame(framedata []byte) []byte {
|
||||
m.hash.Write(framedata)
|
||||
seed := m.hash.Sum(m.seedBuffer[:0])
|
||||
return m.compute(seed, seed[:16])
|
||||
}
|
||||
|
||||
// compute computes the MAC of a 16-byte 'seed'.
|
||||
//
|
||||
// To do this, it encrypts the current value of the hash state, then XORs the ciphertext
|
||||
// with seed. The obtained value is written back into the hash state and hash output is
|
||||
// taken again. The first 16 bytes of the resulting sum are the MAC value.
|
||||
//
|
||||
// This MAC construction is a horrible, legacy thing.
|
||||
func (m *hashMAC) compute(sum1, seed []byte) []byte {
|
||||
if len(seed) != len(m.aesBuffer) {
|
||||
panic("invalid MAC seed")
|
||||
}
|
||||
|
||||
m.cipher.Encrypt(m.aesBuffer[:], sum1)
|
||||
for i := range m.aesBuffer {
|
||||
m.aesBuffer[i] ^= seed[i]
|
||||
}
|
||||
m.hash.Write(m.aesBuffer[:])
|
||||
sum2 := m.hash.Sum(m.hashBuffer[:0])
|
||||
return sum2[:16]
|
||||
}
|
||||
|
||||
// Handshake performs the handshake. This must be called before any data is written
|
||||
// or read from the connection.
|
||||
func (c *Conn) Handshake(prv *ecdsa.PrivateKey) (*ecdsa.PublicKey, error) {
|
||||
var (
|
||||
sec Secrets
|
||||
err error
|
||||
h handshakeState
|
||||
)
|
||||
if c.dialDest != nil {
|
||||
sec, err = h.runInitiator(c.conn, prv, c.dialDest)
|
||||
} else {
|
||||
sec, err = h.runRecipient(c.conn, prv)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.InitWithSecrets(sec)
|
||||
c.session.rbuf = h.rbuf
|
||||
c.session.wbuf = h.wbuf
|
||||
return sec.remote, err
|
||||
}
|
||||
|
||||
// InitWithSecrets injects connection secrets as if a handshake had
|
||||
// been performed. This cannot be called after the handshake.
|
||||
func (c *Conn) InitWithSecrets(sec Secrets) {
|
||||
if c.session != nil {
|
||||
panic("can't handshake twice")
|
||||
}
|
||||
macc, err := aes.NewCipher(sec.MAC)
|
||||
if err != nil {
|
||||
panic("invalid MAC secret: " + err.Error())
|
||||
}
|
||||
encc, err := aes.NewCipher(sec.AES)
|
||||
if err != nil {
|
||||
panic("invalid AES secret: " + err.Error())
|
||||
}
|
||||
// we use an all-zeroes IV for AES because the key used
|
||||
// for encryption is ephemeral.
|
||||
iv := make([]byte, encc.BlockSize())
|
||||
c.session = &sessionState{
|
||||
enc: cipher.NewCTR(encc, iv),
|
||||
dec: cipher.NewCTR(encc, iv),
|
||||
egressMAC: newHashMAC(macc, sec.EgressMAC),
|
||||
ingressMAC: newHashMAC(macc, sec.IngressMAC),
|
||||
}
|
||||
}
|
||||
|
||||
// Close closes the underlying network connection.
|
||||
func (c *Conn) Close() error {
|
||||
return c.conn.Close()
|
||||
}
|
||||
|
||||
// Constants for the handshake.
|
||||
const (
|
||||
sskLen = 16 // ecies.MaxSharedKeyLength(pubKey) / 2
|
||||
sigLen = crypto.SignatureLength // elliptic S256
|
||||
pubLen = 64 // 512 bit pubkey in uncompressed representation without format byte
|
||||
shaLen = 32 // hash length (for nonce etc)
|
||||
|
||||
eciesOverhead = 65 /* pubkey */ + 16 /* IV */ + 32 /* MAC */
|
||||
)
|
||||
|
||||
var (
|
||||
// this is used in place of actual frame header data.
|
||||
// TODO: replace this when Msg contains the protocol type code.
|
||||
zeroHeader = []byte{0xC2, 0x80, 0x80}
|
||||
|
||||
// errPlainMessageTooLarge is returned if a decompressed message length exceeds
|
||||
// the allowed 24 bits (i.e. length >= 16MB).
|
||||
errPlainMessageTooLarge = errors.New("message length >= 16MB")
|
||||
)
|
||||
|
||||
// Secrets represents the connection secrets which are negotiated during the handshake.
|
||||
type Secrets struct {
|
||||
AES, MAC []byte
|
||||
EgressMAC, IngressMAC hash.Hash
|
||||
remote *ecdsa.PublicKey
|
||||
}
|
||||
|
||||
// handshakeState contains the state of the encryption handshake.
|
||||
type handshakeState struct {
|
||||
initiator bool
|
||||
remote *ecies.PublicKey // remote-pubk
|
||||
initNonce, respNonce []byte // nonce
|
||||
randomPrivKey *ecies.PrivateKey // ecdhe-random
|
||||
remoteRandomPub *ecies.PublicKey // ecdhe-random-pubk
|
||||
|
||||
rbuf readBuffer
|
||||
wbuf writeBuffer
|
||||
}
|
||||
|
||||
// RLPx v4 handshake auth (defined in EIP-8).
|
||||
type authMsgV4 struct {
|
||||
Signature [sigLen]byte
|
||||
InitiatorPubkey [pubLen]byte
|
||||
Nonce [shaLen]byte
|
||||
Version uint
|
||||
|
||||
// Ignore additional fields (forward-compatibility)
|
||||
Rest []rlp.RawValue `rlp:"tail"`
|
||||
}
|
||||
|
||||
// RLPx v4 handshake response (defined in EIP-8).
|
||||
type authRespV4 struct {
|
||||
RandomPubkey [pubLen]byte
|
||||
Nonce [shaLen]byte
|
||||
Version uint
|
||||
|
||||
// Ignore additional fields (forward-compatibility)
|
||||
Rest []rlp.RawValue `rlp:"tail"`
|
||||
}
|
||||
|
||||
// runRecipient negotiates a session token on conn.
|
||||
// it should be called on the listening side of the connection.
|
||||
//
|
||||
// prv is the local client's private key.
|
||||
func (h *handshakeState) runRecipient(conn io.ReadWriter, prv *ecdsa.PrivateKey) (s Secrets, err error) {
|
||||
authMsg := new(authMsgV4)
|
||||
authPacket, err := h.readMsg(authMsg, prv, conn)
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
if err := h.handleAuthMsg(authMsg, prv); err != nil {
|
||||
return s, err
|
||||
}
|
||||
|
||||
authRespMsg, err := h.makeAuthResp()
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
authRespPacket, err := h.sealEIP8(authRespMsg)
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
if _, err = conn.Write(authRespPacket); err != nil {
|
||||
return s, err
|
||||
}
|
||||
|
||||
return h.secrets(authPacket, authRespPacket)
|
||||
}
|
||||
|
||||
func (h *handshakeState) handleAuthMsg(msg *authMsgV4, prv *ecdsa.PrivateKey) error {
|
||||
// Import the remote identity.
|
||||
rpub, err := importPublicKey(msg.InitiatorPubkey[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h.initNonce = msg.Nonce[:]
|
||||
h.remote = rpub
|
||||
|
||||
// Generate random keypair for ECDH.
|
||||
// If a private key is already set, use it instead of generating one (for testing).
|
||||
if h.randomPrivKey == nil {
|
||||
h.randomPrivKey, err = ecies.GenerateKey(rand.Reader, crypto.S256(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Check the signature.
|
||||
token, err := h.staticSharedSecret(prv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
signedMsg := xor(token, h.initNonce)
|
||||
remoteRandomPub, err := crypto.Ecrecover(signedMsg, msg.Signature[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h.remoteRandomPub, _ = importPublicKey(remoteRandomPub)
|
||||
return nil
|
||||
}
|
||||
|
||||
// secrets is called after the handshake is completed.
|
||||
// It extracts the connection secrets from the handshake values.
|
||||
func (h *handshakeState) secrets(auth, authResp []byte) (Secrets, error) {
|
||||
ecdheSecret, err := h.randomPrivKey.GenerateShared(h.remoteRandomPub, sskLen, sskLen)
|
||||
if err != nil {
|
||||
return Secrets{}, err
|
||||
}
|
||||
|
||||
// derive base secrets from ephemeral key agreement
|
||||
sharedSecret := crypto.Keccak256(ecdheSecret, crypto.Keccak256(h.respNonce, h.initNonce))
|
||||
aesSecret := crypto.Keccak256(ecdheSecret, sharedSecret)
|
||||
s := Secrets{
|
||||
remote: h.remote.ExportECDSA(),
|
||||
AES: aesSecret,
|
||||
MAC: crypto.Keccak256(ecdheSecret, aesSecret),
|
||||
}
|
||||
|
||||
// setup sha3 instances for the MACs
|
||||
mac1 := sha3.NewLegacyKeccak256()
|
||||
mac1.Write(xor(s.MAC, h.respNonce))
|
||||
mac1.Write(auth)
|
||||
mac2 := sha3.NewLegacyKeccak256()
|
||||
mac2.Write(xor(s.MAC, h.initNonce))
|
||||
mac2.Write(authResp)
|
||||
if h.initiator {
|
||||
s.EgressMAC, s.IngressMAC = mac1, mac2
|
||||
} else {
|
||||
s.EgressMAC, s.IngressMAC = mac2, mac1
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// staticSharedSecret returns the static shared secret, the result
|
||||
// of key agreement between the local and remote static node key.
|
||||
func (h *handshakeState) staticSharedSecret(prv *ecdsa.PrivateKey) ([]byte, error) {
|
||||
return ecies.ImportECDSA(prv).GenerateShared(h.remote, sskLen, sskLen)
|
||||
}
|
||||
|
||||
// runInitiator negotiates a session token on conn.
|
||||
// it should be called on the dialing side of the connection.
|
||||
//
|
||||
// prv is the local client's private key.
|
||||
func (h *handshakeState) runInitiator(conn io.ReadWriter, prv *ecdsa.PrivateKey, remote *ecdsa.PublicKey) (s Secrets, err error) {
|
||||
h.initiator = true
|
||||
h.remote = ecies.ImportECDSAPublic(remote)
|
||||
|
||||
authMsg, err := h.makeAuthMsg(prv)
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
authPacket, err := h.sealEIP8(authMsg)
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
|
||||
if _, err = conn.Write(authPacket); err != nil {
|
||||
return s, err
|
||||
}
|
||||
|
||||
authRespMsg := new(authRespV4)
|
||||
authRespPacket, err := h.readMsg(authRespMsg, prv, conn)
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
if err := h.handleAuthResp(authRespMsg); err != nil {
|
||||
return s, err
|
||||
}
|
||||
|
||||
return h.secrets(authPacket, authRespPacket)
|
||||
}
|
||||
|
||||
// makeAuthMsg creates the initiator handshake message.
|
||||
func (h *handshakeState) makeAuthMsg(prv *ecdsa.PrivateKey) (*authMsgV4, error) {
|
||||
// Generate random initiator nonce.
|
||||
h.initNonce = make([]byte, shaLen)
|
||||
_, err := rand.Read(h.initNonce)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Generate random keypair to for ECDH.
|
||||
h.randomPrivKey, err = ecies.GenerateKey(rand.Reader, crypto.S256(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Sign known message: static-shared-secret ^ nonce
|
||||
token, err := h.staticSharedSecret(prv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
signed := xor(token, h.initNonce)
|
||||
signature, err := crypto.Sign(signed, h.randomPrivKey.ExportECDSA())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msg := new(authMsgV4)
|
||||
copy(msg.Signature[:], signature)
|
||||
copy(msg.InitiatorPubkey[:], crypto.FromECDSAPub(&prv.PublicKey)[1:])
|
||||
copy(msg.Nonce[:], h.initNonce)
|
||||
msg.Version = 4
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
func (h *handshakeState) handleAuthResp(msg *authRespV4) (err error) {
|
||||
h.respNonce = msg.Nonce[:]
|
||||
h.remoteRandomPub, err = importPublicKey(msg.RandomPubkey[:])
|
||||
return err
|
||||
}
|
||||
|
||||
func (h *handshakeState) makeAuthResp() (msg *authRespV4, err error) {
|
||||
// Generate random nonce.
|
||||
h.respNonce = make([]byte, shaLen)
|
||||
if _, err = rand.Read(h.respNonce); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msg = new(authRespV4)
|
||||
copy(msg.Nonce[:], h.respNonce)
|
||||
copy(msg.RandomPubkey[:], exportPubkey(&h.randomPrivKey.PublicKey))
|
||||
msg.Version = 4
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
// readMsg reads an encrypted handshake message, decoding it into msg.
|
||||
func (h *handshakeState) readMsg(msg interface{}, prv *ecdsa.PrivateKey, r io.Reader) ([]byte, error) {
|
||||
h.rbuf.reset()
|
||||
h.rbuf.grow(512)
|
||||
|
||||
// Read the size prefix.
|
||||
prefix, err := h.rbuf.read(r, 2)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
size := binary.BigEndian.Uint16(prefix)
|
||||
|
||||
// Read the handshake packet.
|
||||
packet, err := h.rbuf.read(r, int(size))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dec, err := ecies.ImportECDSA(prv).Decrypt(packet, nil, prefix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Can't use rlp.DecodeBytes here because it rejects
|
||||
// trailing data (forward-compatibility).
|
||||
s := rlp.NewStream(bytes.NewReader(dec), 0)
|
||||
err = s.Decode(msg)
|
||||
return h.rbuf.data[:len(prefix)+len(packet)], err
|
||||
}
|
||||
|
||||
// sealEIP8 encrypts a handshake message.
|
||||
func (h *handshakeState) sealEIP8(msg interface{}) ([]byte, error) {
|
||||
h.wbuf.reset()
|
||||
|
||||
// Write the message plaintext.
|
||||
if err := rlp.Encode(&h.wbuf, msg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Pad with random amount of data. the amount needs to be at least 100 bytes to make
|
||||
// the message distinguishable from pre-EIP-8 handshakes.
|
||||
h.wbuf.appendZero(mrand.Intn(100) + 100)
|
||||
|
||||
prefix := make([]byte, 2)
|
||||
binary.BigEndian.PutUint16(prefix, uint16(len(h.wbuf.data)+eciesOverhead))
|
||||
|
||||
enc, err := ecies.Encrypt(rand.Reader, h.remote, h.wbuf.data, nil, prefix)
|
||||
return append(prefix, enc...), err
|
||||
}
|
||||
|
||||
// importPublicKey unmarshals 512 bit public keys.
|
||||
func importPublicKey(pubKey []byte) (*ecies.PublicKey, error) {
|
||||
var pubKey65 []byte
|
||||
switch len(pubKey) {
|
||||
case 64:
|
||||
// add 'uncompressed key' flag
|
||||
pubKey65 = append([]byte{0x04}, pubKey...)
|
||||
case 65:
|
||||
pubKey65 = pubKey
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid public key length %v (expect 64/65)", len(pubKey))
|
||||
}
|
||||
// TODO: fewer pointless conversions
|
||||
pub, err := crypto.UnmarshalPubkey(pubKey65)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ecies.ImportECDSAPublic(pub), nil
|
||||
}
|
||||
|
||||
func exportPubkey(pub *ecies.PublicKey) []byte {
|
||||
if pub == nil {
|
||||
panic("nil pubkey")
|
||||
}
|
||||
return elliptic.Marshal(pub.Curve, pub.X, pub.Y)[1:]
|
||||
}
|
||||
|
||||
func xor(one, other []byte) (xor []byte) {
|
||||
xor = make([]byte, len(one))
|
||||
for i := 0; i < len(one); i++ {
|
||||
xor[i] = one[i] ^ other[i]
|
||||
}
|
||||
return xor
|
||||
}
|
||||
1155
vendor/github.com/ethereum/go-ethereum/p2p/server.go
generated
vendored
Normal file
1155
vendor/github.com/ethereum/go-ethereum/p2p/server.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
182
vendor/github.com/ethereum/go-ethereum/p2p/transport.go
generated
vendored
Normal file
182
vendor/github.com/ethereum/go-ethereum/p2p/transport.go
generated
vendored
Normal file
@@ -0,0 +1,182 @@
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package p2p
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/bitutil"
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
"github.com/ethereum/go-ethereum/p2p/rlpx"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
const (
|
||||
// total timeout for encryption handshake and protocol
|
||||
// handshake in both directions.
|
||||
handshakeTimeout = 5 * time.Second
|
||||
|
||||
// This is the timeout for sending the disconnect reason.
|
||||
// This is shorter than the usual timeout because we don't want
|
||||
// to wait if the connection is known to be bad anyway.
|
||||
discWriteTimeout = 1 * time.Second
|
||||
)
|
||||
|
||||
// rlpxTransport is the transport used by actual (non-test) connections.
|
||||
// It wraps an RLPx connection with locks and read/write deadlines.
|
||||
type rlpxTransport struct {
|
||||
rmu, wmu sync.Mutex
|
||||
wbuf bytes.Buffer
|
||||
conn *rlpx.Conn
|
||||
}
|
||||
|
||||
func newRLPX(conn net.Conn, dialDest *ecdsa.PublicKey) transport {
|
||||
return &rlpxTransport{conn: rlpx.NewConn(conn, dialDest)}
|
||||
}
|
||||
|
||||
func (t *rlpxTransport) ReadMsg() (Msg, error) {
|
||||
t.rmu.Lock()
|
||||
defer t.rmu.Unlock()
|
||||
|
||||
var msg Msg
|
||||
t.conn.SetReadDeadline(time.Now().Add(frameReadTimeout))
|
||||
code, data, wireSize, err := t.conn.Read()
|
||||
if err == nil {
|
||||
// Protocol messages are dispatched to subprotocol handlers asynchronously,
|
||||
// but package rlpx may reuse the returned 'data' buffer on the next call
|
||||
// to Read. Copy the message data to avoid this being an issue.
|
||||
data = common.CopyBytes(data)
|
||||
msg = Msg{
|
||||
ReceivedAt: time.Now(),
|
||||
Code: code,
|
||||
Size: uint32(len(data)),
|
||||
meterSize: uint32(wireSize),
|
||||
Payload: bytes.NewReader(data),
|
||||
}
|
||||
}
|
||||
return msg, err
|
||||
}
|
||||
|
||||
func (t *rlpxTransport) WriteMsg(msg Msg) error {
|
||||
t.wmu.Lock()
|
||||
defer t.wmu.Unlock()
|
||||
|
||||
// Copy message data to write buffer.
|
||||
t.wbuf.Reset()
|
||||
if _, err := io.CopyN(&t.wbuf, msg.Payload, int64(msg.Size)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write the message.
|
||||
t.conn.SetWriteDeadline(time.Now().Add(frameWriteTimeout))
|
||||
size, err := t.conn.Write(msg.Code, t.wbuf.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set metrics.
|
||||
msg.meterSize = size
|
||||
if metrics.Enabled && msg.meterCap.Name != "" { // don't meter non-subprotocol messages
|
||||
m := fmt.Sprintf("%s/%s/%d/%#02x", egressMeterName, msg.meterCap.Name, msg.meterCap.Version, msg.meterCode)
|
||||
metrics.GetOrRegisterMeter(m, nil).Mark(int64(msg.meterSize))
|
||||
metrics.GetOrRegisterMeter(m+"/packets", nil).Mark(1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *rlpxTransport) close(err error) {
|
||||
t.wmu.Lock()
|
||||
defer t.wmu.Unlock()
|
||||
|
||||
// Tell the remote end why we're disconnecting if possible.
|
||||
// We only bother doing this if the underlying connection supports
|
||||
// setting a timeout tough.
|
||||
if t.conn != nil {
|
||||
if r, ok := err.(DiscReason); ok && r != DiscNetworkError {
|
||||
deadline := time.Now().Add(discWriteTimeout)
|
||||
if err := t.conn.SetWriteDeadline(deadline); err == nil {
|
||||
// Connection supports write deadline.
|
||||
t.wbuf.Reset()
|
||||
rlp.Encode(&t.wbuf, []DiscReason{r})
|
||||
t.conn.Write(discMsg, t.wbuf.Bytes())
|
||||
}
|
||||
}
|
||||
}
|
||||
t.conn.Close()
|
||||
}
|
||||
|
||||
func (t *rlpxTransport) doEncHandshake(prv *ecdsa.PrivateKey) (*ecdsa.PublicKey, error) {
|
||||
t.conn.SetDeadline(time.Now().Add(handshakeTimeout))
|
||||
return t.conn.Handshake(prv)
|
||||
}
|
||||
|
||||
func (t *rlpxTransport) doProtoHandshake(our *protoHandshake) (their *protoHandshake, err error) {
|
||||
// Writing our handshake happens concurrently, we prefer
|
||||
// returning the handshake read error. If the remote side
|
||||
// disconnects us early with a valid reason, we should return it
|
||||
// as the error so it can be tracked elsewhere.
|
||||
werr := make(chan error, 1)
|
||||
go func() { werr <- Send(t, handshakeMsg, our) }()
|
||||
if their, err = readProtocolHandshake(t); err != nil {
|
||||
<-werr // make sure the write terminates too
|
||||
return nil, err
|
||||
}
|
||||
if err := <-werr; err != nil {
|
||||
return nil, fmt.Errorf("write error: %v", err)
|
||||
}
|
||||
// If the protocol version supports Snappy encoding, upgrade immediately
|
||||
t.conn.SetSnappy(their.Version >= snappyProtocolVersion)
|
||||
|
||||
return their, nil
|
||||
}
|
||||
|
||||
func readProtocolHandshake(rw MsgReader) (*protoHandshake, error) {
|
||||
msg, err := rw.ReadMsg()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if msg.Size > baseProtocolMaxMsgSize {
|
||||
return nil, fmt.Errorf("message too big")
|
||||
}
|
||||
if msg.Code == discMsg {
|
||||
// Disconnect before protocol handshake is valid according to the
|
||||
// spec and we send it ourself if the post-handshake checks fail.
|
||||
// We can't return the reason directly, though, because it is echoed
|
||||
// back otherwise. Wrap it in a string instead.
|
||||
var reason [1]DiscReason
|
||||
rlp.Decode(msg.Payload, &reason)
|
||||
return nil, reason[0]
|
||||
}
|
||||
if msg.Code != handshakeMsg {
|
||||
return nil, fmt.Errorf("expected handshake, got %x", msg.Code)
|
||||
}
|
||||
var hs protoHandshake
|
||||
if err := msg.Decode(&hs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(hs.ID) != 64 || !bitutil.TestBytes(hs.ID) {
|
||||
return nil, DiscInvalidIdentity
|
||||
}
|
||||
return &hs, nil
|
||||
}
|
||||
75
vendor/github.com/ethereum/go-ethereum/p2p/util.go
generated
vendored
Normal file
75
vendor/github.com/ethereum/go-ethereum/p2p/util.go
generated
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
// Copyright 2019 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package p2p
|
||||
|
||||
import (
|
||||
"container/heap"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/mclock"
|
||||
)
|
||||
|
||||
// expHeap tracks strings and their expiry time.
|
||||
type expHeap []expItem
|
||||
|
||||
// expItem is an entry in addrHistory.
|
||||
type expItem struct {
|
||||
item string
|
||||
exp mclock.AbsTime
|
||||
}
|
||||
|
||||
// nextExpiry returns the next expiry time.
|
||||
func (h *expHeap) nextExpiry() mclock.AbsTime {
|
||||
return (*h)[0].exp
|
||||
}
|
||||
|
||||
// add adds an item and sets its expiry time.
|
||||
func (h *expHeap) add(item string, exp mclock.AbsTime) {
|
||||
heap.Push(h, expItem{item, exp})
|
||||
}
|
||||
|
||||
// contains checks whether an item is present.
|
||||
func (h expHeap) contains(item string) bool {
|
||||
for _, v := range h {
|
||||
if v.item == item {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// expire removes items with expiry time before 'now'.
|
||||
func (h *expHeap) expire(now mclock.AbsTime, onExp func(string)) {
|
||||
for h.Len() > 0 && h.nextExpiry() < now {
|
||||
item := heap.Pop(h)
|
||||
if onExp != nil {
|
||||
onExp(item.(expItem).item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// heap.Interface boilerplate
|
||||
func (h expHeap) Len() int { return len(h) }
|
||||
func (h expHeap) Less(i, j int) bool { return h[i].exp < h[j].exp }
|
||||
func (h expHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
|
||||
func (h *expHeap) Push(x interface{}) { *h = append(*h, x.(expItem)) }
|
||||
func (h *expHeap) Pop() interface{} {
|
||||
old := *h
|
||||
n := len(old)
|
||||
x := old[n-1]
|
||||
*h = old[0 : n-1]
|
||||
return x
|
||||
}
|
||||
Reference in New Issue
Block a user