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.
This commit is contained in:
Felix Lange
2015-04-18 01:50:31 +02:00
parent 3fef601903
commit fc747ef4a6
11 changed files with 160 additions and 129 deletions

View File

@ -49,16 +49,20 @@ const (
// RPC request structures
type (
ping struct {
Version uint // must match Version
IP string // our IP
Port uint16 // our port
Version uint
From, To rpcEndpoint
Expiration uint64
}
// reply to Ping
// pong is the reply to ping.
pong struct {
ReplyTok []byte
Expiration uint64
// 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.
}
findnode struct {
@ -73,12 +77,25 @@ type (
Nodes []*Node
Expiration uint64
}
rpcEndpoint struct {
IP net.IP // len 4 for IPv4 or 16 for IPv6
UDP uint16 // for discovery protocol
TCP uint16 // for RLPx protocol
}
)
type rpcNode struct {
IP string
Port uint16
ID NodeID
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 validNode(n *Node) bool {
// TODO: don't accept localhost, LAN addresses from internet hosts
return !n.IP.IsMulticast() && !n.IP.IsUnspecified() && n.UDP != 0
}
type packet interface {
@ -94,8 +111,9 @@ type conn interface {
// udp implements the RPC protocol.
type udp struct {
conn conn
priv *ecdsa.PrivateKey
conn conn
priv *ecdsa.PrivateKey
ourEndpoint rpcEndpoint
addpending chan *pending
gotreply chan reply
@ -176,6 +194,8 @@ func newUDP(priv *ecdsa.PrivateKey, c conn, natm nat.Interface, nodeDBPath strin
realaddr = &net.UDPAddr{IP: ext, Port: realaddr.Port}
}
}
// TODO: separate TCP port
udp.ourEndpoint = makeEndpoint(realaddr, uint16(realaddr.Port))
udp.Table = newTable(udp, PubkeyID(&priv.PublicKey), realaddr, nodeDBPath)
go udp.loop()
go udp.readLoop()
@ -194,8 +214,8 @@ func (t *udp) ping(toid NodeID, toaddr *net.UDPAddr) error {
errc := t.pending(toid, pongPacket, func(interface{}) bool { return true })
t.send(toaddr, pingPacket, ping{
Version: Version,
IP: t.self.IP.String(),
Port: uint16(t.self.TCPPort),
From: t.ourEndpoint,
To: makeEndpoint(toaddr, 0), // TODO: maybe use known TCP port from DB
Expiration: uint64(time.Now().Add(expiration).Unix()),
})
return <-errc
@ -214,7 +234,7 @@ func (t *udp) findnode(toid NodeID, toaddr *net.UDPAddr, target NodeID) ([]*Node
reply := r.(*neighbors)
for _, n := range reply.Nodes {
nreceived++
if n.isValid() {
if validNode(n) {
nodes = append(nodes, n)
}
}
@ -374,19 +394,24 @@ func (t *udp) readLoop() {
if err != nil {
return
}
packet, fromID, hash, err := decodePacket(buf[:nbytes])
if err != nil {
glog.V(logger.Debug).Infof("Bad packet from %v: %v\n", from, err)
continue
}
status := "ok"
if err := packet.handle(t, from, fromID, hash); err != nil {
status = err.Error()
}
glog.V(logger.Detail).Infof("<<< %v %T: %s\n", from, packet, status)
t.handlePacket(from, buf[:nbytes])
}
}
func (t *udp) handlePacket(from *net.UDPAddr, buf []byte) error {
packet, fromID, hash, err := decodePacket(buf)
if err != nil {
glog.V(logger.Debug).Infof("Bad packet from %v: %v\n", from, err)
return err
}
status := "ok"
if err = packet.handle(t, from, fromID, hash); err != nil {
status = err.Error()
}
glog.V(logger.Detail).Infof("<<< %v %T: %s\n", from, packet, status)
return err
}
func decodePacket(buf []byte) (packet, NodeID, []byte, error) {
if len(buf) < headSize+1 {
return nil, NodeID{}, nil, errPacketTooSmall
@ -425,12 +450,13 @@ func (req *ping) handle(t *udp, from *net.UDPAddr, fromID NodeID, mac []byte) er
return errBadVersion
}
t.send(from, pongPacket, pong{
To: makeEndpoint(from, req.From.TCP),
ReplyTok: mac,
Expiration: uint64(time.Now().Add(expiration).Unix()),
})
if !t.handleReply(fromID, pingPacket, req) {
// Note: we're ignoring the provided IP address right now
go t.bond(true, fromID, from, req.Port)
go t.bond(true, fromID, from, req.From.TCP)
}
return nil
}