diff --git a/router/crypto.go b/router/crypto.go index 0ca7f846..9d9f4d34 100644 --- a/router/crypto.go +++ b/router/crypto.go @@ -6,6 +6,7 @@ import ( "encoding/binary" "encoding/gob" "fmt" + "io" "sync" "github.com/andybalholm/go-bit" @@ -336,6 +337,10 @@ type GobTCPSender struct { encoder *gob.Encoder } +type LengthPrefixTCPSender struct { + writer io.Writer +} + type EncryptedTCPSender struct { sync.RWMutex sender TCPSender @@ -350,6 +355,21 @@ func (sender *GobTCPSender) Send(msg []byte) error { return sender.encoder.Encode(msg) } +func NewLengthPrefixTCPSender(writer io.Writer) *LengthPrefixTCPSender { + return &LengthPrefixTCPSender{writer: writer} +} + +func (sender *LengthPrefixTCPSender) Send(msg []byte) error { + l := len(msg) + // We copy the message so we can send it in a single Write + // operation, thus making this thread-safe without locking. + prefixedMsg := make([]byte, 4+l) + binary.BigEndian.PutUint32(prefixedMsg, uint32(l)) + copy(prefixedMsg[4:], msg) + _, err := sender.writer.Write(prefixedMsg) + return err +} + func NewEncryptedTCPSender(sender TCPSender, sessionKey *[32]byte, outbound bool) *EncryptedTCPSender { return &EncryptedTCPSender{sender: sender, state: NewTCPCryptoState(sessionKey, outbound)} } @@ -370,6 +390,10 @@ type GobTCPReceiver struct { decoder *gob.Decoder } +type LengthPrefixTCPReceiver struct { + reader io.Reader +} + type EncryptedTCPReceiver struct { receiver TCPReceiver state *TCPCryptoState @@ -385,6 +409,21 @@ func (receiver *GobTCPReceiver) Receive() ([]byte, error) { return msg, err } +func NewLengthPrefixTCPReceiver(reader io.Reader) *LengthPrefixTCPReceiver { + return &LengthPrefixTCPReceiver{reader: reader} +} + +func (receiver *LengthPrefixTCPReceiver) Receive() ([]byte, error) { + lenPrefix := make([]byte, 4) + if _, err := io.ReadFull(receiver.reader, lenPrefix); err != nil { + return nil, err + } + l := binary.BigEndian.Uint32(lenPrefix) + msg := make([]byte, l) + _, err := io.ReadFull(receiver.reader, msg) + return msg, err +} + func NewEncryptedTCPReceiver(receiver TCPReceiver, sessionKey *[32]byte, outbound bool) *EncryptedTCPReceiver { return &EncryptedTCPReceiver{receiver: receiver, state: NewTCPCryptoState(sessionKey, !outbound)} } diff --git a/router/protocol.go b/router/protocol.go index 500d0d61..8b5252d1 100644 --- a/router/protocol.go +++ b/router/protocol.go @@ -228,12 +228,11 @@ func filterV1Features(intro map[string]string) map[string]string { // - When the connection is encrypted, 32 bytes follow containing the // public key. // -// - Then a stream of gobified values. +// - Then a stream of length-prefixed messages, which are encrypted +// for an encrypted connection. // -// The gobified values are the messages on the connection (encrypted -// for an encrypted connection). The first message contains the -// encoded features map (so in contrast to V1, it will be encrypted on -// an encrypted connection). +// The first message contains the encoded features map (so in contrast +// to V1, it will be encrypted on an encrypted connection). func (res *ProtocolIntroResults) doIntroV2(params ProtocolIntroParams, pubKey, privKey *[32]byte) error { // Public key exchange var wbuf []byte @@ -266,8 +265,8 @@ func (res *ProtocolIntroResults) doIntroV2(params ProtocolIntroParams, pubKey, p return ErrExpectedCrypto } - res.Sender = NewGobTCPSender(gob.NewEncoder(params.Conn)) - res.Receiver = NewGobTCPReceiver(gob.NewDecoder(params.Conn)) + res.Sender = NewLengthPrefixTCPSender(params.Conn) + res.Receiver = NewLengthPrefixTCPReceiver(params.Conn) case 1: if pubKey == nil { @@ -279,8 +278,8 @@ func (res *ProtocolIntroResults) doIntroV2(params ProtocolIntroParams, pubKey, p return err } - res.Sender = NewGobTCPSender(gob.NewEncoder(params.Conn)) - res.Receiver = NewGobTCPReceiver(gob.NewDecoder(params.Conn)) + res.Sender = NewLengthPrefixTCPSender(params.Conn) + res.Receiver = NewLengthPrefixTCPReceiver(params.Conn) res.setupCrypto(params, rbuf, privKey) default: