dd54fef898
PR #1621 changed Table locking so the mutex is not held while a contested node is being pinged. If multiple nodes ping the local node during this time window, multiple ping packets will be sent to the contested node. The changes in this commit prevent multiple packets by tracking whether the node is being replaced.
325 lines
8.5 KiB
Go
325 lines
8.5 KiB
Go
// 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"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"math/big"
|
|
"math/rand"
|
|
"net"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/crypto"
|
|
"github.com/ethereum/go-ethereum/crypto/secp256k1"
|
|
)
|
|
|
|
const nodeIDBits = 512
|
|
|
|
// Node represents a host on the network.
|
|
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
|
|
|
|
// This is a cached copy of sha3(ID) which is used for node
|
|
// distance calculations. This is part of Node in order to make it
|
|
// possible to write tests that need a node at a certain distance.
|
|
// In those tests, the content of sha will not actually correspond
|
|
// with ID.
|
|
sha common.Hash
|
|
|
|
// whether this node is currently being pinged in order to replace
|
|
// it in a bucket
|
|
contested bool
|
|
}
|
|
|
|
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,
|
|
sha: crypto.Sha3Hash(id[:]),
|
|
}
|
|
}
|
|
|
|
func (n *Node) addr() *net.UDPAddr {
|
|
return &net.UDPAddr{IP: n.IP, Port: int(n.UDP)}
|
|
}
|
|
|
|
// The string representation of a Node is a URL.
|
|
// Please see ParseNode for a description of the format.
|
|
func (n *Node) String() string {
|
|
addr := net.TCPAddr{IP: n.IP, Port: int(n.TCP)}
|
|
u := url.URL{
|
|
Scheme: "enode",
|
|
User: url.User(fmt.Sprintf("%x", n.ID[:])),
|
|
Host: addr.String(),
|
|
}
|
|
if n.UDP != n.TCP {
|
|
u.RawQuery = "discport=" + strconv.Itoa(int(n.UDP))
|
|
}
|
|
return u.String()
|
|
}
|
|
|
|
// ParseNode parses a node URL.
|
|
//
|
|
// A node URL has scheme "enode".
|
|
//
|
|
// The hexadecimal 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) {
|
|
var (
|
|
id NodeID
|
|
ip net.IP
|
|
tcpPort, udpPort uint64
|
|
)
|
|
u, err := url.Parse(rawurl)
|
|
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
|
|
}
|
|
|
|
// 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[:])
|
|
}
|
|
|
|
// HexID converts a hex string to a NodeID.
|
|
// The string may be prefixed with 0x.
|
|
func HexID(in string) (NodeID, error) {
|
|
if strings.HasPrefix(in, "0x") {
|
|
in = in[2:]
|
|
}
|
|
var id NodeID
|
|
b, err := hex.DecodeString(in)
|
|
if err != nil {
|
|
return id, err
|
|
} else if len(b) != len(id) {
|
|
return id, fmt.Errorf("wrong length, need %d hex bytes", len(id))
|
|
}
|
|
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 (id NodeID) Pubkey() (*ecdsa.PublicKey, error) {
|
|
p := &ecdsa.PublicKey{Curve: crypto.S256(), X: new(big.Int), Y: new(big.Int)}
|
|
half := len(id) / 2
|
|
p.X.SetBytes(id[:half])
|
|
p.Y.SetBytes(id[half:])
|
|
if !p.Curve.IsOnCurve(p.X, p.Y) {
|
|
return nil, errors.New("not a point on the S256 curve")
|
|
}
|
|
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 := secp256k1.RecoverPubkey(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
|
|
}
|