Messenger and SecureMessenger implement MsgChanReadWriter

- implement peer readLoop and writeLoop as internals to Messenger
- cryptoid.Run -> NewSession
- returns a func(net.Conn) MsgChanReadWriter
- MsgChanReadWriter channel read writer interface
- SecureMessenger embeds Messenger
- Peer fields bufconn moved to Messenger
- no more need for short-lived outgoingMsgC
- modify peer.loop to use a MsgChanReadWriter
This commit is contained in:
zelig 2015-01-22 00:21:38 +00:00
parent b29b7fedd0
commit 4bfe53eb6e
6 changed files with 146 additions and 80 deletions

View file

@ -5,6 +5,7 @@ import (
"crypto/rand" "crypto/rand"
"fmt" "fmt"
"io" "io"
"net"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
ethlogger "github.com/ethereum/go-ethereum/logger" ethlogger "github.com/ethereum/go-ethereum/logger"
@ -93,7 +94,7 @@ Run(connection, remotePublicKey, sessionToken) is called when the peer connectio
It returns a secretRW which implements the MsgReadWriter interface. It returns a secretRW which implements the MsgReadWriter interface.
*/ */
func (self *cryptoId) Run(conn io.ReadWriter, remotePubKeyS []byte, sessionToken []byte, initiator bool) (token []byte, rw *secretRW, err error) { func (self *cryptoId) NewSession(conn io.ReadWriter, remotePubKeyS []byte, sessionToken []byte, initiator bool) (token []byte, rwF func(net.Conn) MsgChanReadWriter, err error) {
var auth, initNonce, recNonce []byte var auth, initNonce, recNonce []byte
var read int var read int
var randomPrivKey *ecdsa.PrivateKey var randomPrivKey *ecdsa.PrivateKey
@ -364,7 +365,7 @@ func (self *cryptoId) completeHandshake(auth []byte) (respNonce []byte, remoteRa
/* /*
newSession is called after the handshake is completed. The arguments are values negotiated in the handshake and the return value is a new session : a new session Token to be remembered for the next time we connect with this peer. And a MsgReadWriter that implements an encrypted and authenticated connection with key material obtained from the crypto handshake key exchange newSession is called after the handshake is completed. The arguments are values negotiated in the handshake and the return value is a new session : a new session Token to be remembered for the next time we connect with this peer. And a MsgReadWriter that implements an encrypted and authenticated connection with key material obtained from the crypto handshake key exchange
*/ */
func (self *cryptoId) newSession(initNonce, respNonce, auth []byte, privKey *ecdsa.PrivateKey, remoteRandomPubKey *ecdsa.PublicKey) (sessionToken []byte, rw *secretRW, err error) { func (self *cryptoId) newSession(initNonce, respNonce, auth []byte, privKey *ecdsa.PrivateKey, remoteRandomPubKey *ecdsa.PublicKey) (sessionToken []byte, rwF func(net.Conn) MsgChanReadWriter, err error) {
// 3) Now we can trust ecdhe-random-pubk to derive new keys // 3) Now we can trust ecdhe-random-pubk to derive new keys
//ecdhe-shared-secret = ecdh.agree(ecdhe-random, remote-ecdhe-random-pubk) //ecdhe-shared-secret = ecdh.agree(ecdhe-random, remote-ecdhe-random-pubk)
var dhSharedSecret []byte var dhSharedSecret []byte
@ -388,16 +389,15 @@ func (self *cryptoId) newSession(initNonce, respNonce, auth []byte, privKey *ecd
// ingress-mac = crypto.Sha3(mac-secret^initiator-nonce || auth), // ingress-mac = crypto.Sha3(mac-secret^initiator-nonce || auth),
var ingressMac = crypto.Sha3(append(Xor(macSecret, initNonce), auth...)) var ingressMac = crypto.Sha3(append(Xor(macSecret, initNonce), auth...))
// # destroy remote-nonce // # destroy remote-nonce
rw = &secretRW{
aesSecret: aesSecret,
macSecret: macSecret,
egressMac: egressMac,
ingressMac: ingressMac,
}
clogger.Debugf("aes-secret: %v", hexkey(aesSecret)) clogger.Debugf("aes-secret: %v", hexkey(aesSecret))
clogger.Debugf("mac-secret: %v", hexkey(macSecret)) clogger.Debugf("mac-secret: %v", hexkey(macSecret))
clogger.Debugf("egress-mac: %v", hexkey(egressMac)) clogger.Debugf("egress-mac: %v", hexkey(egressMac))
clogger.Debugf("ingress-mac: %v", hexkey(ingressMac)) clogger.Debugf("ingress-mac: %v", hexkey(ingressMac))
rwF = func(conn net.Conn) MsgChanReadWriter {
return NewSecureMessenger(conn, aesSecret, macSecret, egressMac, ingressMac)
}
return return
} }
@ -410,3 +410,17 @@ func Xor(one, other []byte) (xor []byte) {
} }
return return
} }
type SecureMessenger struct {
Messenger
aesSecret, macSecret, egressMac, ingressMac []byte
}
func NewSecureMessenger(conn net.Conn, aesSecret, macSecret, egressMac, ingressMac []byte) *SecureMessenger {
return &SecureMessenger{
aesSecret: aesSecret,
macSecret: macSecret,
egressMac: egressMac,
ingressMac: ingressMac,
}
}

View file

@ -106,12 +106,12 @@ func TestCryptoHandshake(t *testing.T) {
} }
// now both parties should have the same session parameters // now both parties should have the same session parameters
initSessionToken, initSecretRW, err := initiator.newSession(initNonce, recNonce, auth, randomPrivKey, remoteRandomPubKey) initSessionToken, initSecretRWF, err := initiator.newSession(initNonce, recNonce, auth, randomPrivKey, remoteRandomPubKey)
if err != nil { if err != nil {
t.Errorf("%v", err) t.Errorf("%v", err)
} }
recSessionToken, recSecretRW, err := receiver.newSession(remoteInitNonce, remoteRecNonce, auth, remoteRandomPrivKey, remoteInitRandomPubKey) recSessionToken, recSecretRWF, err := receiver.newSession(remoteInitNonce, remoteRecNonce, auth, remoteRandomPrivKey, remoteInitRandomPubKey)
if err != nil { if err != nil {
t.Errorf("%v", err) t.Errorf("%v", err)
} }
@ -129,6 +129,17 @@ func TestCryptoHandshake(t *testing.T) {
if !bytes.Equal(initSessionToken, recSessionToken) { if !bytes.Equal(initSessionToken, recSessionToken) {
t.Errorf("session tokens do not match") t.Errorf("session tokens do not match")
} }
conn0, conn1 := net.Pipe()
initSecretRW, ok := initSecretRWF(conn0).(*SecureMessenger)
if !ok {
t.Errorf("incorrect return type. expected *SecureMessenger, got %T", initSecretRW)
}
recSecretRW, ok := recSecretRWF(conn1).(*SecureMessenger)
if !ok {
t.Errorf("incorrect return type. expected *SecureMessenger, got %T", initSecretRW)
}
// aesSecret, macSecret, egressMac, ingressMac // aesSecret, macSecret, egressMac, ingressMac
if !bytes.Equal(initSecretRW.aesSecret, recSecretRW.aesSecret) { if !bytes.Equal(initSecretRW.aesSecret, recSecretRW.aesSecret) {
t.Errorf("AES secrets do not match") t.Errorf("AES secrets do not match")
@ -191,7 +202,7 @@ func TestPeersHandshake(t *testing.T) {
<-receiver.cryptoReady <-receiver.cryptoReady
close(ready) close(ready)
}() }()
timeout := time.After(1 * time.Second) timeout := time.After(20 * time.Second)
select { select {
case <-ready: case <-ready:
case <-timeout: case <-timeout:

View file

@ -1,11 +1,13 @@
package p2p package p2p
import ( import (
"bufio"
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"io" "io"
"math/big" "math/big"
"net"
"time" "time"
"github.com/ethereum/go-ethereum/ethutil" "github.com/ethereum/go-ethereum/ethutil"
@ -35,25 +37,82 @@ var magicToken = []byte{34, 64, 8, 145}
Because framing and header structure will change there will be hardly any overlap with the new code so I do not abstract readers any further. Because framing and header structure will change there will be hardly any overlap with the new code so I do not abstract readers any further.
*/ */
func (p *Peer) readLoop(msgc chan<- Msg, errc chan<- error, unblock <-chan bool) {
for _ = range unblock { type MsgChanReadWriter interface {
p.conn.SetReadDeadline(time.Now().Add(msgReadTimeout)) ReadC() chan Msg
if msg, err := readMsg(p.bufconn); err != nil { WriteC() chan Msg
errc <- err ErrorC() chan error
} else { ReadNextC() chan bool
msgc <- msg Close()
}
}
close(errc)
} }
func (p *Peer) writeLoop(msgc <-chan Msg, errc chan<- error) { type Messenger struct {
p.conn.SetWriteDeadline(time.Now().Add(msgWriteTimeout)) msgReadTimeout time.Duration
for msg := range msgc { msgWriteTimeout time.Duration
if err := writeMsg(p.bufconn, msg); err != nil { in chan Msg
errc <- newPeerError(errWrite, "%v", err) out chan Msg
errc chan error
unblock chan bool
conn net.Conn
bufconn *bufio.ReadWriter
}
func NewMessenger(conn net.Conn) *Messenger {
self := &Messenger{
msgReadTimeout: msgReadTimeout,
msgWriteTimeout: msgWriteTimeout,
in: make(chan Msg),
out: make(chan Msg),
errc: make(chan error),
unblock: make(chan bool, 1),
conn: conn,
bufconn: bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn)),
}
go self.readLoop()
go self.writeLoop()
return self
}
func (self *Messenger) Close() {
close(self.unblock)
close(self.out)
}
func (self *Messenger) ReadC() chan Msg {
return self.in
}
func (self *Messenger) WriteC() chan Msg {
return self.out
}
func (self *Messenger) ErrorC() chan error {
return self.errc
}
func (self *Messenger) ReadNextC() chan bool {
return self.unblock
}
func (self *Messenger) readLoop() {
for _ = range self.unblock {
self.conn.SetReadDeadline(time.Now().Add(self.msgReadTimeout))
if msg, err := readMsg(self.bufconn); err != nil {
self.errc <- err
} else {
self.in <- msg
} }
p.bufconn.Flush() }
close(self.errc)
}
func (self *Messenger) writeLoop() {
for msg := range self.out {
self.conn.SetWriteDeadline(time.Now().Add(self.msgWriteTimeout))
if err := writeMsg(self.bufconn, msg); err != nil {
self.errc <- newPeerError(errWrite, "%v", err)
}
self.bufconn.Flush()
} }
} }

View file

@ -1,11 +1,9 @@
package p2p package p2p
import ( import (
"bufio"
"bytes" "bytes"
"crypto/rand" "crypto/rand"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"net" "net"
"sort" "sort"
@ -62,9 +60,8 @@ type Peer struct {
listenAddr *peerAddr // what remote peer is listening on listenAddr *peerAddr // what remote peer is listening on
dialAddr *peerAddr // non-nil if dialing dialAddr *peerAddr // non-nil if dialing
conn net.Conn conn net.Conn
bufconn *bufio.ReadWriter crw MsgChanReadWriter
outgoingMsgC chan Msg
// These fields maintain the running protocols. // These fields maintain the running protocols.
protocols []Protocol protocols []Protocol
@ -120,17 +117,15 @@ func newServerPeer(server *Server, conn net.Conn, dialAddr *peerAddr) *Peer {
func newPeer(conn net.Conn, protocols []Protocol, dialAddr *peerAddr) *Peer { func newPeer(conn net.Conn, protocols []Protocol, dialAddr *peerAddr) *Peer {
p := &Peer{ p := &Peer{
Logger: logger.NewLogger("P2P " + conn.RemoteAddr().String()), Logger: logger.NewLogger("P2P " + conn.RemoteAddr().String()),
conn: conn, conn: conn,
dialAddr: dialAddr, dialAddr: dialAddr,
bufconn: bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn)), protocols: protocols,
protocols: protocols, running: make(map[string]*proto),
running: make(map[string]*proto), disc: make(chan DiscReason),
disc: make(chan DiscReason), protoErr: make(chan error),
protoErr: make(chan error), closed: make(chan struct{}),
closed: make(chan struct{}), cryptoReady: make(chan struct{}),
cryptoReady: make(chan struct{}),
outgoingMsgC: make(chan Msg),
} }
return p return p
} }
@ -214,26 +209,27 @@ func (p *Peer) loop() (reason DiscReason, err error) {
defer close(p.closed) defer close(p.closed)
defer p.conn.Close() defer p.conn.Close()
var readLoop func(chan<- Msg, chan<- error, <-chan bool)
if p.cryptoHandshake { if p.cryptoHandshake {
if readLoop, err = p.handleCryptoHandshake(); err != nil { if crwF, err := p.handleCryptoHandshake(); err != nil {
// from here on everything can be encrypted, authenticated // from here on everything can be encrypted, authenticated
return DiscProtocolError, err // no graceful disconnect return DiscProtocolError, err // no graceful disconnect
} else {
p.crw = crwF(p.conn)
} }
} else { } else {
readLoop = p.readLoop p.crw = NewMessenger(p.conn)
} }
close(p.cryptoReady)
// read loop // read loop
readMsg := make(chan Msg)
rwErr := make(chan error)
readNext := make(chan bool, 1)
protoDone := make(chan struct{}, 1) protoDone := make(chan struct{}, 1)
go readLoop(readMsg, rwErr, readNext)
go p.writeLoop(p.outgoingMsgC, rwErr)
readNext <- true
close(p.cryptoReady) in := p.crw.ReadC()
errc := p.crw.ErrorC()
unblock := p.crw.ReadNextC()
unblock <- true
if p.runBaseProtocol { if p.runBaseProtocol {
p.startBaseProtocol() p.startBaseProtocol()
} }
@ -241,7 +237,7 @@ func (p *Peer) loop() (reason DiscReason, err error) {
loop: loop:
for { for {
select { select {
case msg := <-readMsg: case msg := <-in:
// a new message has arrived. // a new message has arrived.
var wait bool var wait bool
if wait, err = p.dispatch(msg, protoDone); err != nil { if wait, err = p.dispatch(msg, protoDone); err != nil {
@ -251,15 +247,15 @@ loop:
} }
if !wait { if !wait {
// Msg has already been read completely, continue with next message. // Msg has already been read completely, continue with next message.
readNext <- true unblock <- true
} }
p.activity.Post(time.Now()) p.activity.Post(time.Now())
case <-protoDone: case <-protoDone:
// protocol has consumed the message payload, // protocol has consumed the message payload,
// we can continue reading from the socket. // we can continue reading from the socket.
readNext <- true unblock <- true
case err := <-rwErr: case err := <-errc:
// read or write failed. there is no need to run the // read or write failed. there is no need to run the
// polite disconnect sequence because the connection // polite disconnect sequence because the connection
// is probably dead anyway. // is probably dead anyway.
@ -272,23 +268,11 @@ loop:
break loop break loop
} }
} }
// wait for read loop to return.
close(p.outgoingMsgC)
close(readNext)
<-rwErr
// tell the remote end to disconnect // tell the remote end to disconnect
done := make(chan struct{}) p.writeProtoMsg("", NewMsg(discMsg, reason))
go func() { // io.Copy(ioutil.Discard, p.conn)//??
p.conn.SetDeadline(time.Now().Add(disconnectGracePeriod)) p.crw.Close()
p.writeProtoMsg("", NewMsg(discMsg, reason)) <-time.After(disconnectGracePeriod)
io.Copy(ioutil.Discard, p.conn)
close(done)
}()
select {
case <-done:
case <-time.After(disconnectGracePeriod):
}
return reason, err return reason, err
} }
@ -317,7 +301,7 @@ func (p *Peer) dispatch(msg Msg, protoDone chan struct{}) (wait bool, err error)
type readLoop func(chan<- Msg, chan<- error, <-chan bool) type readLoop func(chan<- Msg, chan<- error, <-chan bool)
func (p *Peer) handleCryptoHandshake() (loop readLoop, err error) { func (p *Peer) handleCryptoHandshake() (crw func(net.Conn) MsgChanReadWriter, err error) {
// cryptoId is just created for the lifecycle of the handshake // cryptoId is just created for the lifecycle of the handshake
// it is survived by an encrypted readwriter // it is survived by an encrypted readwriter
var initiator bool var initiator bool
@ -339,13 +323,10 @@ func (p *Peer) handleCryptoHandshake() (loop readLoop, err error) {
// run on peer // run on peer
// this bit handles the handshake and creates a secure communications channel with // this bit handles the handshake and creates a secure communications channel with
// var rw *secretRW // var rw *secretRW
if sessionToken, _, err = crypto.Run(p.conn, p.Pubkey(), sessionToken, initiator); err != nil { if sessionToken, crw, err = crypto.NewSession(p.conn, p.Pubkey(), sessionToken, initiator); err != nil {
p.Debugf("unable to setup secure session: %v", err) p.Debugf("unable to setup secure session: %v", err)
return return
} }
loop = func(msg chan<- Msg, err chan<- error, next <-chan bool) {
// this is the readloop :)
}
return return
} }
@ -382,7 +363,7 @@ outer:
func (p *Peer) startProto(offset uint64, impl Protocol) *proto { func (p *Peer) startProto(offset uint64, impl Protocol) *proto {
rw := &proto{ rw := &proto{
in: make(chan Msg), in: make(chan Msg),
out: p.outgoingMsgC, out: p.crw.WriteC(),
offset: offset, offset: offset,
maxcode: impl.Length, maxcode: impl.Length,
} }

View file

@ -33,7 +33,7 @@ func testPeer(protos []Protocol) (net.Conn, *Peer, <-chan error) {
peer := newPeer(conn1, protos, nil) peer := newPeer(conn1, protos, nil)
peer.ourID = &peerId{} peer.ourID = &peerId{}
peer.pubkeyHook = func(*peerAddr) error { return nil } peer.pubkeyHook = func(*peerAddr) error { return nil }
peer.outgoingMsgC = make(chan Msg) peer.crw = NewMessenger(conn1)
errc := make(chan error, 1) errc := make(chan error, 1)
go func() { go func() {
_, err := peer.loop() _, err := peer.loop()

View file

@ -121,6 +121,7 @@ func TestServerBroadcast(t *testing.T) {
var connected sync.WaitGroup var connected sync.WaitGroup
srv := startTestServer(t, func(srv *Server, c net.Conn, dialAddr *peerAddr) *Peer { srv := startTestServer(t, func(srv *Server, c net.Conn, dialAddr *peerAddr) *Peer {
peer := newPeer(c, []Protocol{discard}, dialAddr) peer := newPeer(c, []Protocol{discard}, dialAddr)
peer.crw = NewMessenger(peer.conn)
peer.startSubprotocols([]Cap{discard.cap()}) peer.startSubprotocols([]Cap{discard.cap()})
connected.Done() connected.Done()
return peer return peer