Skip to content

Commit fc747ef

Browse files
committed
p2p/discover: new endpoint format
This commit changes the discovery protocol to use the new "v4" endpoint format, which allows for separate UDP and TCP ports and makes it possible to discover the UDP address after NAT.
1 parent 3fef601 commit fc747ef

File tree

11 files changed

+160
-129
lines changed

11 files changed

+160
-129
lines changed

eth/backend.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -277,8 +277,8 @@ func (s *Ethereum) NodeInfo() *NodeInfo {
277277
NodeUrl: node.String(),
278278
NodeID: node.ID.String(),
279279
IP: node.IP.String(),
280-
DiscPort: node.DiscPort,
281-
TCPPort: node.TCPPort,
280+
DiscPort: int(node.UDP),
281+
TCPPort: int(node.TCP),
282282
ListenAddr: s.net.ListenAddr,
283283
Td: s.ChainManager().Td().String(),
284284
}

p2p/discover/database_test.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"net"
77
"os"
88
"path/filepath"
9+
"reflect"
910
"testing"
1011
"time"
1112
)
@@ -86,9 +87,10 @@ func TestNodeDBInt64(t *testing.T) {
8687

8788
func TestNodeDBFetchStore(t *testing.T) {
8889
node := &Node{
89-
ID: MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
90-
IP: net.IP([]byte{192, 168, 0, 1}),
91-
TCPPort: 30303,
90+
ID: MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
91+
IP: net.IP([]byte{192, 168, 0, 1}),
92+
UDP: 30303,
93+
TCP: 30303,
9294
}
9395
inst := time.Now()
9496

@@ -124,7 +126,7 @@ func TestNodeDBFetchStore(t *testing.T) {
124126
}
125127
if stored := db.node(node.ID); stored == nil {
126128
t.Errorf("node: not found")
127-
} else if !bytes.Equal(stored.ID[:], node.ID[:]) || !stored.IP.Equal(node.IP) || stored.TCPPort != node.TCPPort {
129+
} else if !reflect.DeepEqual(stored, node) {
128130
t.Errorf("node: data mismatch: have %v, want %v", stored, node)
129131
}
130132
}

p2p/discover/node.go

Lines changed: 22 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"encoding/hex"
77
"errors"
88
"fmt"
9-
"io"
109
"math/big"
1110
"math/rand"
1211
"net"
@@ -16,49 +15,45 @@ import (
1615

1716
"github.com/ethereum/go-ethereum/crypto"
1817
"github.com/ethereum/go-ethereum/crypto/secp256k1"
19-
"github.com/ethereum/go-ethereum/rlp"
2018
)
2119

2220
const nodeIDBits = 512
2321

2422
// Node represents a host on the network.
2523
type Node struct {
26-
ID NodeID
27-
IP net.IP
28-
29-
DiscPort int // UDP listening port for discovery protocol
30-
TCPPort int // TCP listening port for RLPx
24+
IP net.IP // len 4 for IPv4 or 16 for IPv6
25+
UDP, TCP uint16 // port numbers
26+
ID NodeID
3127
}
3228

3329
func newNode(id NodeID, addr *net.UDPAddr) *Node {
30+
ip := addr.IP.To4()
31+
if ip == nil {
32+
ip = addr.IP.To16()
33+
}
3434
return &Node{
35-
ID: id,
36-
IP: addr.IP,
37-
DiscPort: addr.Port,
38-
TCPPort: addr.Port,
35+
IP: ip,
36+
UDP: uint16(addr.Port),
37+
TCP: uint16(addr.Port),
38+
ID: id,
3939
}
4040
}
4141

42-
func (n *Node) isValid() bool {
43-
// TODO: don't accept localhost, LAN addresses from internet hosts
44-
return !n.IP.IsMulticast() && !n.IP.IsUnspecified() && n.TCPPort != 0 && n.DiscPort != 0
45-
}
46-
4742
func (n *Node) addr() *net.UDPAddr {
48-
return &net.UDPAddr{IP: n.IP, Port: n.DiscPort}
43+
return &net.UDPAddr{IP: n.IP, Port: int(n.UDP)}
4944
}
5045

5146
// The string representation of a Node is a URL.
5247
// Please see ParseNode for a description of the format.
5348
func (n *Node) String() string {
54-
addr := net.TCPAddr{IP: n.IP, Port: n.TCPPort}
49+
addr := net.TCPAddr{IP: n.IP, Port: int(n.TCP)}
5550
u := url.URL{
5651
Scheme: "enode",
5752
User: url.User(fmt.Sprintf("%x", n.ID[:])),
5853
Host: addr.String(),
5954
}
60-
if n.DiscPort != n.TCPPort {
61-
u.RawQuery = "discport=" + strconv.Itoa(n.DiscPort)
55+
if n.UDP != n.TCP {
56+
u.RawQuery = "discport=" + strconv.Itoa(int(n.UDP))
6257
}
6358
return u.String()
6459
}
@@ -98,16 +93,20 @@ func ParseNode(rawurl string) (*Node, error) {
9893
if n.IP = net.ParseIP(ip); n.IP == nil {
9994
return nil, errors.New("invalid IP address")
10095
}
101-
if n.TCPPort, err = strconv.Atoi(port); err != nil {
96+
tcp, err := strconv.ParseUint(port, 10, 16)
97+
if err != nil {
10298
return nil, errors.New("invalid port")
10399
}
100+
n.TCP = uint16(tcp)
104101
qv := u.Query()
105102
if qv.Get("discport") == "" {
106-
n.DiscPort = n.TCPPort
103+
n.UDP = n.TCP
107104
} else {
108-
if n.DiscPort, err = strconv.Atoi(qv.Get("discport")); err != nil {
105+
udp, err := strconv.ParseUint(qv.Get("discport"), 10, 16)
106+
if err != nil {
109107
return nil, errors.New("invalid discport in query")
110108
}
109+
n.UDP = uint16(udp)
111110
}
112111
return &n, nil
113112
}
@@ -121,22 +120,6 @@ func MustParseNode(rawurl string) *Node {
121120
return n
122121
}
123122

124-
func (n Node) EncodeRLP(w io.Writer) error {
125-
return rlp.Encode(w, rpcNode{IP: n.IP.String(), Port: uint16(n.TCPPort), ID: n.ID})
126-
}
127-
func (n *Node) DecodeRLP(s *rlp.Stream) (err error) {
128-
var ext rpcNode
129-
if err = s.Decode(&ext); err == nil {
130-
n.TCPPort = int(ext.Port)
131-
n.DiscPort = int(ext.Port)
132-
n.ID = ext.ID
133-
if n.IP = net.ParseIP(ext.IP); n.IP == nil {
134-
return errors.New("invalid IP string")
135-
}
136-
}
137-
return err
138-
}
139-
140123
// NodeID is a unique identifier for each node.
141124
// The node identifier is a marshaled elliptic curve public key.
142125
type NodeID [nodeIDBits / 8]byte

p2p/discover/node_test.go

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -49,28 +49,28 @@ var parseNodeTests = []struct {
4949
{
5050
rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:52150",
5151
wantResult: &Node{
52-
ID: MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
53-
IP: net.ParseIP("127.0.0.1"),
54-
DiscPort: 52150,
55-
TCPPort: 52150,
52+
ID: MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
53+
IP: net.ParseIP("127.0.0.1"),
54+
UDP: 52150,
55+
TCP: 52150,
5656
},
5757
},
5858
{
5959
rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@[::]:52150",
6060
wantResult: &Node{
61-
ID: MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
62-
IP: net.ParseIP("::"),
63-
DiscPort: 52150,
64-
TCPPort: 52150,
61+
ID: MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
62+
IP: net.ParseIP("::"),
63+
UDP: 52150,
64+
TCP: 52150,
6565
},
6666
},
6767
{
68-
rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:52150?discport=223344",
68+
rawurl: "enode://1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439@127.0.0.1:52150?discport=22334",
6969
wantResult: &Node{
70-
ID: MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
71-
IP: net.ParseIP("127.0.0.1"),
72-
DiscPort: 223344,
73-
TCPPort: 52150,
70+
ID: MustHexID("0x1dd9d65c4552b5eb43d5ad55a2ee3f56c6cbc1c64a5c8d659f51fcd51bace24351232b8d7821617d2b29b54b81cdefb9b3e9c37d7fd5f63270bcc9e1a6f6a439"),
71+
IP: net.ParseIP("127.0.0.1"),
72+
UDP: 22334,
73+
TCP: 52150,
7474
},
7575
},
7676
}

p2p/discover/table.go

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ func (tab *Table) bondall(nodes []*Node) (result []*Node) {
220220
rc := make(chan *Node, len(nodes))
221221
for i := range nodes {
222222
go func(n *Node) {
223-
nn, _ := tab.bond(false, n.ID, n.addr(), uint16(n.TCPPort))
223+
nn, _ := tab.bond(false, n.ID, n.addr(), uint16(n.TCP))
224224
rc <- nn
225225
}(nodes[i])
226226
}
@@ -299,12 +299,7 @@ func (tab *Table) pingpong(w *bondproc, pinged bool, id NodeID, addr *net.UDPAdd
299299
tab.net.waitping(id)
300300
}
301301
// Bonding succeeded, update the node database
302-
w.n = &Node{
303-
ID: id,
304-
IP: addr.IP,
305-
DiscPort: addr.Port,
306-
TCPPort: int(tcpPort),
307-
}
302+
w.n = &Node{ID: id, IP: addr.IP, UDP: uint16(addr.Port), TCP: tcpPort}
308303
tab.db.updateNode(w.n)
309304
close(w.done)
310305
}

p2p/discover/table_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -261,9 +261,9 @@ func (t findnodeOracle) findnode(toid NodeID, toaddr *net.UDPAddr, target NodeID
261261
panic("query to node at distance 0")
262262
default:
263263
// TODO: add more randomness to distances
264-
next := toaddr.Port - 1
264+
next := uint16(toaddr.Port) - 1
265265
for i := 0; i < bucketSize; i++ {
266-
result = append(result, &Node{ID: randomID(t.target, next), DiscPort: next})
266+
result = append(result, &Node{ID: randomID(t.target, int(next)), UDP: next})
267267
}
268268
}
269269
return result, nil

p2p/discover/udp.go

Lines changed: 52 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -49,16 +49,20 @@ const (
4949
// RPC request structures
5050
type (
5151
ping struct {
52-
Version uint // must match Version
53-
IP string // our IP
54-
Port uint16 // our port
52+
Version uint
53+
From, To rpcEndpoint
5554
Expiration uint64
5655
}
5756

58-
// reply to Ping
57+
// pong is the reply to ping.
5958
pong struct {
60-
ReplyTok []byte
61-
Expiration uint64
59+
// This field should mirror the UDP envelope address
60+
// of the ping packet, which provides a way to discover the
61+
// the external address (after NAT).
62+
To rpcEndpoint
63+
64+
ReplyTok []byte // This contains the hash of the ping packet.
65+
Expiration uint64 // Absolute timestamp at which the packet becomes invalid.
6266
}
6367

6468
findnode struct {
@@ -73,12 +77,25 @@ type (
7377
Nodes []*Node
7478
Expiration uint64
7579
}
80+
81+
rpcEndpoint struct {
82+
IP net.IP // len 4 for IPv4 or 16 for IPv6
83+
UDP uint16 // for discovery protocol
84+
TCP uint16 // for RLPx protocol
85+
}
7686
)
7787

78-
type rpcNode struct {
79-
IP string
80-
Port uint16
81-
ID NodeID
88+
func makeEndpoint(addr *net.UDPAddr, tcpPort uint16) rpcEndpoint {
89+
ip := addr.IP.To4()
90+
if ip == nil {
91+
ip = addr.IP.To16()
92+
}
93+
return rpcEndpoint{IP: ip, UDP: uint16(addr.Port), TCP: tcpPort}
94+
}
95+
96+
func validNode(n *Node) bool {
97+
// TODO: don't accept localhost, LAN addresses from internet hosts
98+
return !n.IP.IsMulticast() && !n.IP.IsUnspecified() && n.UDP != 0
8299
}
83100

84101
type packet interface {
@@ -94,8 +111,9 @@ type conn interface {
94111

95112
// udp implements the RPC protocol.
96113
type udp struct {
97-
conn conn
98-
priv *ecdsa.PrivateKey
114+
conn conn
115+
priv *ecdsa.PrivateKey
116+
ourEndpoint rpcEndpoint
99117

100118
addpending chan *pending
101119
gotreply chan reply
@@ -176,6 +194,8 @@ func newUDP(priv *ecdsa.PrivateKey, c conn, natm nat.Interface, nodeDBPath strin
176194
realaddr = &net.UDPAddr{IP: ext, Port: realaddr.Port}
177195
}
178196
}
197+
// TODO: separate TCP port
198+
udp.ourEndpoint = makeEndpoint(realaddr, uint16(realaddr.Port))
179199
udp.Table = newTable(udp, PubkeyID(&priv.PublicKey), realaddr, nodeDBPath)
180200
go udp.loop()
181201
go udp.readLoop()
@@ -194,8 +214,8 @@ func (t *udp) ping(toid NodeID, toaddr *net.UDPAddr) error {
194214
errc := t.pending(toid, pongPacket, func(interface{}) bool { return true })
195215
t.send(toaddr, pingPacket, ping{
196216
Version: Version,
197-
IP: t.self.IP.String(),
198-
Port: uint16(t.self.TCPPort),
217+
From: t.ourEndpoint,
218+
To: makeEndpoint(toaddr, 0), // TODO: maybe use known TCP port from DB
199219
Expiration: uint64(time.Now().Add(expiration).Unix()),
200220
})
201221
return <-errc
@@ -214,7 +234,7 @@ func (t *udp) findnode(toid NodeID, toaddr *net.UDPAddr, target NodeID) ([]*Node
214234
reply := r.(*neighbors)
215235
for _, n := range reply.Nodes {
216236
nreceived++
217-
if n.isValid() {
237+
if validNode(n) {
218238
nodes = append(nodes, n)
219239
}
220240
}
@@ -374,17 +394,22 @@ func (t *udp) readLoop() {
374394
if err != nil {
375395
return
376396
}
377-
packet, fromID, hash, err := decodePacket(buf[:nbytes])
378-
if err != nil {
379-
glog.V(logger.Debug).Infof("Bad packet from %v: %v\n", from, err)
380-
continue
381-
}
382-
status := "ok"
383-
if err := packet.handle(t, from, fromID, hash); err != nil {
384-
status = err.Error()
385-
}
386-
glog.V(logger.Detail).Infof("<<< %v %T: %s\n", from, packet, status)
397+
t.handlePacket(from, buf[:nbytes])
398+
}
399+
}
400+
401+
func (t *udp) handlePacket(from *net.UDPAddr, buf []byte) error {
402+
packet, fromID, hash, err := decodePacket(buf)
403+
if err != nil {
404+
glog.V(logger.Debug).Infof("Bad packet from %v: %v\n", from, err)
405+
return err
387406
}
407+
status := "ok"
408+
if err = packet.handle(t, from, fromID, hash); err != nil {
409+
status = err.Error()
410+
}
411+
glog.V(logger.Detail).Infof("<<< %v %T: %s\n", from, packet, status)
412+
return err
388413
}
389414

390415
func decodePacket(buf []byte) (packet, NodeID, []byte, error) {
@@ -425,12 +450,13 @@ func (req *ping) handle(t *udp, from *net.UDPAddr, fromID NodeID, mac []byte) er
425450
return errBadVersion
426451
}
427452
t.send(from, pongPacket, pong{
453+
To: makeEndpoint(from, req.From.TCP),
428454
ReplyTok: mac,
429455
Expiration: uint64(time.Now().Add(expiration).Unix()),
430456
})
431457
if !t.handleReply(fromID, pingPacket, req) {
432458
// Note: we're ignoring the provided IP address right now
433-
go t.bond(true, fromID, from, req.Port)
459+
go t.bond(true, fromID, from, req.From.TCP)
434460
}
435461
return nil
436462
}

0 commit comments

Comments
 (0)