p2p: new dialer, peer management without locks

The most visible change is event-based dialing, which should be an
improvement over the timer-based system that we have at the moment.
The dialer gets a chance to compute new tasks whenever peers change or
dials complete. This is better than checking peers on a timer because
dials happen faster. The dialer can now make more precise decisions
about whom to dial based on the peer set and we can test those
decisions without actually opening any sockets.

Peer management is easier to test because the tests can inject
connections at checkpoints (after enc handshake, after protocol
handshake).

Most of the handshake stuff is now part of the RLPx code. It could be
exported or move to its own package because it is no longer entangled
with Server logic.
This commit is contained in:
Felix Lange
2015-05-16 00:38:28 +02:00
parent 9f38ef5d97
commit 1440f9a37a
11 changed files with 2142 additions and 1353 deletions

View File

@ -33,9 +33,17 @@ const (
peersMsg = 0x05
)
// protoHandshake is the RLP structure of the protocol handshake.
type protoHandshake struct {
Version uint64
Name string
Caps []Cap
ListenPort uint64
ID discover.NodeID
}
// Peer represents a connected remote node.
type Peer struct {
conn net.Conn
rw *conn
running map[string]*protoRW
@ -48,37 +56,36 @@ type Peer struct {
// NewPeer returns a peer for testing purposes.
func NewPeer(id discover.NodeID, name string, caps []Cap) *Peer {
pipe, _ := net.Pipe()
msgpipe, _ := MsgPipe()
conn := &conn{msgpipe, &protoHandshake{ID: id, Name: name, Caps: caps}}
peer := newPeer(pipe, conn, nil)
conn := &conn{fd: pipe, transport: nil, id: id, caps: caps, name: name}
peer := newPeer(conn, nil)
close(peer.closed) // ensures Disconnect doesn't block
return peer
}
// ID returns the node's public key.
func (p *Peer) ID() discover.NodeID {
return p.rw.ID
return p.rw.id
}
// Name returns the node name that the remote node advertised.
func (p *Peer) Name() string {
return p.rw.Name
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
return p.rw.caps
}
// RemoteAddr returns the remote address of the network connection.
func (p *Peer) RemoteAddr() net.Addr {
return p.conn.RemoteAddr()
return p.rw.fd.RemoteAddr()
}
// LocalAddr returns the local address of the network connection.
func (p *Peer) LocalAddr() net.Addr {
return p.conn.LocalAddr()
return p.rw.fd.LocalAddr()
}
// Disconnect terminates the peer connection with the given reason.
@ -92,13 +99,12 @@ func (p *Peer) Disconnect(reason DiscReason) {
// String implements fmt.Stringer.
func (p *Peer) String() string {
return fmt.Sprintf("Peer %.8x %v", p.rw.ID[:], p.RemoteAddr())
return fmt.Sprintf("Peer %x %v", p.rw.id[:8], p.RemoteAddr())
}
func newPeer(fd net.Conn, conn *conn, protocols []Protocol) *Peer {
protomap := matchProtocols(protocols, conn.Caps, conn)
func newPeer(conn *conn, protocols []Protocol) *Peer {
protomap := matchProtocols(protocols, conn.caps, conn)
p := &Peer{
conn: fd,
rw: conn,
running: protomap,
disc: make(chan DiscReason),
@ -117,7 +123,10 @@ func (p *Peer) run() DiscReason {
p.startProtocols()
// Wait for an error or disconnect.
var reason DiscReason
var (
reason DiscReason
requested bool
)
select {
case err := <-readErr:
if r, ok := err.(DiscReason); ok {
@ -131,23 +140,19 @@ func (p *Peer) run() DiscReason {
case err := <-p.protoErr:
reason = discReasonForError(err)
case reason = <-p.disc:
p.politeDisconnect(reason)
requested = true
}
close(p.closed)
p.rw.close(reason)
p.wg.Wait()
if requested {
reason = DiscRequested
}
close(p.closed)
p.wg.Wait()
glog.V(logger.Debug).Infof("%v: Disconnected: %v\n", p, reason)
return reason
}
func (p *Peer) politeDisconnect(reason DiscReason) {
if reason != DiscNetworkError {
SendItems(p.rw, discMsg, uint(reason))
}
p.conn.Close()
}
func (p *Peer) pingLoop() {
ping := time.NewTicker(pingInterval)
defer p.wg.Done()
@ -254,7 +259,7 @@ func (p *Peer) startProtocols() {
glog.V(logger.Detail).Infof("%v: Protocol %s/%d returned\n", p, proto.Name, proto.Version)
err = errors.New("protocol returned")
} else if err != io.EOF {
glog.V(logger.Detail).Infof("%v: Protocol %s/%d error: \n", p, proto.Name, proto.Version, err)
glog.V(logger.Detail).Infof("%v: Protocol %s/%d error: %v\n", p, proto.Name, proto.Version, err)
}
p.protoErr <- err
p.wg.Done()