mirror of
https://github.com/ipfs/kubo.git
synced 2025-06-29 17:36:38 +08:00
Merge pull request #1227 from ipfs/parallelize-handshake
net/p2p + secio: parallelize crypto handshake
This commit is contained in:
@ -17,28 +17,13 @@ type SessionGenerator struct {
|
|||||||
PrivateKey ci.PrivKey
|
PrivateKey ci.PrivKey
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSession takes an insecure io.ReadWriter, performs a TLS-like
|
// NewSession takes an insecure io.ReadWriter, sets up a TLS-like
|
||||||
// handshake with the other side, and returns a secure session.
|
// handshake with the other side, and returns a secure session.
|
||||||
|
// The handshake isn't run until the connection is read or written to.
|
||||||
// See the source for the protocol details and security implementation.
|
// See the source for the protocol details and security implementation.
|
||||||
// The provided Context is only needed for the duration of this function.
|
// The provided Context is only needed for the duration of this function.
|
||||||
func (sg *SessionGenerator) NewSession(ctx context.Context,
|
func (sg *SessionGenerator) NewSession(ctx context.Context, insecure io.ReadWriteCloser) (Session, error) {
|
||||||
insecure io.ReadWriter) (Session, error) {
|
return newSecureSession(ctx, sg.LocalID, sg.PrivateKey, insecure)
|
||||||
|
|
||||||
ss, err := newSecureSession(sg.LocalID, sg.PrivateKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if ctx == nil {
|
|
||||||
ctx = context.Background()
|
|
||||||
}
|
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
|
||||||
if err := ss.handshake(ctx, insecure); err != nil {
|
|
||||||
cancel()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return ss, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Session interface {
|
type Session interface {
|
||||||
@ -64,6 +49,9 @@ type Session interface {
|
|||||||
|
|
||||||
// SecureReadWriter returns the encrypted communication channel
|
// SecureReadWriter returns the encrypted communication channel
|
||||||
func (s *secureSession) ReadWriter() msgio.ReadWriteCloser {
|
func (s *secureSession) ReadWriter() msgio.ReadWriteCloser {
|
||||||
|
if err := s.Handshake(); err != nil {
|
||||||
|
return &closedRW{err}
|
||||||
|
}
|
||||||
return s.secure
|
return s.secure
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,15 +67,60 @@ func (s *secureSession) LocalPrivateKey() ci.PrivKey {
|
|||||||
|
|
||||||
// RemotePeer retrieves the remote peer.
|
// RemotePeer retrieves the remote peer.
|
||||||
func (s *secureSession) RemotePeer() peer.ID {
|
func (s *secureSession) RemotePeer() peer.ID {
|
||||||
|
if err := s.Handshake(); err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
return s.remotePeer
|
return s.remotePeer
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemotePeer retrieves the remote peer.
|
// RemotePeer retrieves the remote peer.
|
||||||
func (s *secureSession) RemotePublicKey() ci.PubKey {
|
func (s *secureSession) RemotePublicKey() ci.PubKey {
|
||||||
|
if err := s.Handshake(); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return s.remote.permanentPubKey
|
return s.remote.permanentPubKey
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close closes the secure session
|
// Close closes the secure session
|
||||||
func (s *secureSession) Close() error {
|
func (s *secureSession) Close() error {
|
||||||
|
s.cancel()
|
||||||
|
s.handshakeMu.Lock()
|
||||||
|
defer s.handshakeMu.Unlock()
|
||||||
|
if s.secure == nil {
|
||||||
|
return s.insecure.Close() // hadn't secured yet.
|
||||||
|
}
|
||||||
return s.secure.Close()
|
return s.secure.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// closedRW implements a stub msgio interface that's already
|
||||||
|
// closed and errored.
|
||||||
|
type closedRW struct {
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *closedRW) Read(buf []byte) (int, error) {
|
||||||
|
return 0, c.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *closedRW) Write(buf []byte) (int, error) {
|
||||||
|
return 0, c.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *closedRW) NextMsgLen() (int, error) {
|
||||||
|
return 0, c.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *closedRW) ReadMsg() ([]byte, error) {
|
||||||
|
return nil, c.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *closedRW) WriteMsg(buf []byte) error {
|
||||||
|
return c.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *closedRW) Close() error {
|
||||||
|
return c.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *closedRW) ReleaseMsg(m []byte) {
|
||||||
|
}
|
||||||
|
@ -6,6 +6,8 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
msgio "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-msgio"
|
msgio "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-msgio"
|
||||||
context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
|
context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
|
||||||
@ -27,15 +29,23 @@ var ErrClosed = errors.New("connection closed")
|
|||||||
// ErrEcho is returned when we're attempting to handshake with the same keys and nonces.
|
// ErrEcho is returned when we're attempting to handshake with the same keys and nonces.
|
||||||
var ErrEcho = errors.New("same keys and nonces. one side talking to self.")
|
var ErrEcho = errors.New("same keys and nonces. one side talking to self.")
|
||||||
|
|
||||||
|
// HandshakeTimeout governs how long the handshake will be allowed to take place for.
|
||||||
|
// Making this number large means there could be many bogus connections waiting to
|
||||||
|
// timeout in flight. Typical handshakes take ~3RTTs, so it should be completed within
|
||||||
|
// seconds across a typical planet in the solar system.
|
||||||
|
var HandshakeTimeout = time.Second * 30
|
||||||
|
|
||||||
// nonceSize is the size of our nonces (in bytes)
|
// nonceSize is the size of our nonces (in bytes)
|
||||||
const nonceSize = 16
|
const nonceSize = 16
|
||||||
|
|
||||||
// secureSession encapsulates all the parameters needed for encrypting
|
// secureSession encapsulates all the parameters needed for encrypting
|
||||||
// and decrypting traffic from an insecure channel.
|
// and decrypting traffic from an insecure channel.
|
||||||
type secureSession struct {
|
type secureSession struct {
|
||||||
secure msgio.ReadWriteCloser
|
ctx context.Context
|
||||||
|
cancel context.CancelFunc
|
||||||
|
|
||||||
insecure io.ReadWriter
|
secure msgio.ReadWriteCloser
|
||||||
|
insecure io.ReadWriteCloser
|
||||||
insecureM msgio.ReadWriter
|
insecureM msgio.ReadWriter
|
||||||
|
|
||||||
localKey ci.PrivKey
|
localKey ci.PrivKey
|
||||||
@ -46,6 +56,10 @@ type secureSession struct {
|
|||||||
remote encParams
|
remote encParams
|
||||||
|
|
||||||
sharedSecret []byte
|
sharedSecret []byte
|
||||||
|
|
||||||
|
handshakeMu sync.Mutex // guards handshakeDone + handshakeErr
|
||||||
|
handshakeDone bool
|
||||||
|
handshakeErr error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *secureSession) Loggable() map[string]interface{} {
|
func (s *secureSession) Loggable() map[string]interface{} {
|
||||||
@ -56,8 +70,9 @@ func (s *secureSession) Loggable() map[string]interface{} {
|
|||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSecureSession(local peer.ID, key ci.PrivKey) (*secureSession, error) {
|
func newSecureSession(ctx context.Context, local peer.ID, key ci.PrivKey, insecure io.ReadWriteCloser) (*secureSession, error) {
|
||||||
s := &secureSession{localPeer: local, localKey: key}
|
s := &secureSession{localPeer: local, localKey: key}
|
||||||
|
s.ctx, s.cancel = context.WithCancel(ctx)
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case s.localPeer == "":
|
case s.localPeer == "":
|
||||||
@ -66,18 +81,37 @@ func newSecureSession(local peer.ID, key ci.PrivKey) (*secureSession, error) {
|
|||||||
return nil, errors.New("no local private key provided")
|
return nil, errors.New("no local private key provided")
|
||||||
case !s.localPeer.MatchesPrivateKey(s.localKey):
|
case !s.localPeer.MatchesPrivateKey(s.localKey):
|
||||||
return nil, fmt.Errorf("peer.ID does not match PrivateKey")
|
return nil, fmt.Errorf("peer.ID does not match PrivateKey")
|
||||||
|
case insecure == nil:
|
||||||
|
return nil, fmt.Errorf("insecure ReadWriter is nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.ctx = ctx
|
||||||
|
s.insecure = insecure
|
||||||
|
s.insecureM = msgio.NewReadWriter(insecure)
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// handsahke performs initial communication over insecure channel to share
|
func (s *secureSession) Handshake() error {
|
||||||
|
s.handshakeMu.Lock()
|
||||||
|
defer s.handshakeMu.Unlock()
|
||||||
|
|
||||||
|
if s.handshakeErr != nil {
|
||||||
|
return s.handshakeErr
|
||||||
|
}
|
||||||
|
|
||||||
|
if !s.handshakeDone {
|
||||||
|
s.handshakeErr = s.runHandshake()
|
||||||
|
s.handshakeDone = true
|
||||||
|
}
|
||||||
|
return s.handshakeErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// runHandshake performs initial communication over insecure channel to share
|
||||||
// keys, IDs, and initiate communication, assigning all necessary params.
|
// keys, IDs, and initiate communication, assigning all necessary params.
|
||||||
// requires the duplex channel to be a msgio.ReadWriter (for framed messaging)
|
// requires the duplex channel to be a msgio.ReadWriter (for framed messaging)
|
||||||
func (s *secureSession) handshake(ctx context.Context, insecure io.ReadWriter) error {
|
func (s *secureSession) runHandshake() error {
|
||||||
|
ctx, cancel := context.WithTimeout(s.ctx, HandshakeTimeout) // remove
|
||||||
s.insecure = insecure
|
defer cancel()
|
||||||
s.insecureM = msgio.NewReadWriter(insecure)
|
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// step 1. Propose -- propose cipher suite + send pubkeys + nonce
|
// step 1. Propose -- propose cipher suite + send pubkeys + nonce
|
||||||
|
@ -75,18 +75,35 @@ func setupConn(t *testing.T, ctx context.Context, secure bool) (a, b Conn, p1, p
|
|||||||
|
|
||||||
done := make(chan error)
|
done := make(chan error)
|
||||||
go func() {
|
go func() {
|
||||||
|
defer close(done)
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
c2, err = d2.Dial(ctx, p1.Addr, p1.ID)
|
c2, err = d2.Dial(ctx, p1.Addr, p1.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
done <- err
|
done <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// if secure, need to read + write, as that's what triggers the handshake.
|
||||||
|
if secure {
|
||||||
|
if err := sayHello(c2); err != nil {
|
||||||
|
done <- err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
close(done)
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
c1, err := l1.Accept()
|
c1, err := l1.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("failed to accept", err)
|
t.Fatal("failed to accept", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if secure, need to read + write, as that's what triggers the handshake.
|
||||||
|
if secure {
|
||||||
|
if err := sayHello(c1); err != nil {
|
||||||
|
done <- err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err := <-done; err != nil {
|
if err := <-done; err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -94,6 +111,20 @@ func setupConn(t *testing.T, ctx context.Context, secure bool) (a, b Conn, p1, p
|
|||||||
return c1.(Conn), c2, p1, p2
|
return c1.(Conn), c2, p1, p2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sayHello(c net.Conn) error {
|
||||||
|
h := []byte("hello")
|
||||||
|
if _, err := c.Write(h); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := c.Read(h); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if string(h) != "hello" {
|
||||||
|
return fmt.Errorf("did not get hello")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func testDialer(t *testing.T, secure bool) {
|
func testDialer(t *testing.T, secure bool) {
|
||||||
// t.Skip("Skipping in favor of another test")
|
// t.Skip("Skipping in favor of another test")
|
||||||
|
|
||||||
@ -203,7 +234,7 @@ func testDialerCloseEarly(t *testing.T, secure bool) {
|
|||||||
go func() {
|
go func() {
|
||||||
defer func() { done <- struct{}{} }()
|
defer func() { done <- struct{}{} }()
|
||||||
|
|
||||||
_, err := l1.Accept()
|
c, err := l1.Accept()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), "closed") {
|
if strings.Contains(err.Error(), "closed") {
|
||||||
gotclosed <- struct{}{}
|
gotclosed <- struct{}{}
|
||||||
@ -211,7 +242,13 @@ func testDialerCloseEarly(t *testing.T, secure bool) {
|
|||||||
}
|
}
|
||||||
errs <- err
|
errs <- err
|
||||||
}
|
}
|
||||||
errs <- fmt.Errorf("got conn")
|
|
||||||
|
if _, err := c.Write([]byte("hello")); err != nil {
|
||||||
|
gotclosed <- struct{}{}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
errs <- fmt.Errorf("wrote to conn")
|
||||||
}()
|
}()
|
||||||
|
|
||||||
c, err := d2.Dial(ctx, p1.Addr, p1.ID)
|
c, err := d2.Dial(ctx, p1.Addr, p1.ID)
|
||||||
|
@ -5,7 +5,6 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
msgio "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-msgio"
|
|
||||||
ma "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
|
ma "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
|
||||||
context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
|
context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
|
||||||
|
|
||||||
@ -16,15 +15,8 @@ import (
|
|||||||
|
|
||||||
// secureConn wraps another Conn object with an encrypted channel.
|
// secureConn wraps another Conn object with an encrypted channel.
|
||||||
type secureConn struct {
|
type secureConn struct {
|
||||||
|
insecure Conn // the wrapped conn
|
||||||
// the wrapped conn
|
secure secio.Session // secure Session
|
||||||
insecure Conn
|
|
||||||
|
|
||||||
// secure io (wrapping insecure)
|
|
||||||
secure msgio.ReadWriteCloser
|
|
||||||
|
|
||||||
// secure Session
|
|
||||||
session secio.Session
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// newConn constructs a new connection
|
// newConn constructs a new connection
|
||||||
@ -37,23 +29,20 @@ func newSecureConn(ctx context.Context, sk ic.PrivKey, insecure Conn) (Conn, err
|
|||||||
return nil, errors.New("insecure.LocalPeer() is nil")
|
return nil, errors.New("insecure.LocalPeer() is nil")
|
||||||
}
|
}
|
||||||
if sk == nil {
|
if sk == nil {
|
||||||
panic("way")
|
|
||||||
return nil, errors.New("private key is nil")
|
return nil, errors.New("private key is nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSession performs the secure handshake, which takes multiple RTT
|
// NewSession performs the secure handshake, which takes multiple RTT
|
||||||
sessgen := secio.SessionGenerator{LocalID: insecure.LocalPeer(), PrivateKey: sk}
|
sessgen := secio.SessionGenerator{LocalID: insecure.LocalPeer(), PrivateKey: sk}
|
||||||
session, err := sessgen.NewSession(ctx, insecure)
|
secure, err := sessgen.NewSession(ctx, insecure)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
conn := &secureConn{
|
conn := &secureConn{
|
||||||
insecure: insecure,
|
insecure: insecure,
|
||||||
session: session,
|
secure: secure,
|
||||||
secure: session.ReadWriter(),
|
|
||||||
}
|
}
|
||||||
log.Debugf("newSecureConn: %v to %v handshake success!", conn.LocalPeer(), conn.RemotePeer())
|
|
||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,49 +91,49 @@ func (c *secureConn) RemoteMultiaddr() ma.Multiaddr {
|
|||||||
|
|
||||||
// LocalPeer is the Peer on this side
|
// LocalPeer is the Peer on this side
|
||||||
func (c *secureConn) LocalPeer() peer.ID {
|
func (c *secureConn) LocalPeer() peer.ID {
|
||||||
return c.session.LocalPeer()
|
return c.secure.LocalPeer()
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemotePeer is the Peer on the remote side
|
// RemotePeer is the Peer on the remote side
|
||||||
func (c *secureConn) RemotePeer() peer.ID {
|
func (c *secureConn) RemotePeer() peer.ID {
|
||||||
return c.session.RemotePeer()
|
return c.secure.RemotePeer()
|
||||||
}
|
}
|
||||||
|
|
||||||
// LocalPrivateKey is the public key of the peer on this side
|
// LocalPrivateKey is the public key of the peer on this side
|
||||||
func (c *secureConn) LocalPrivateKey() ic.PrivKey {
|
func (c *secureConn) LocalPrivateKey() ic.PrivKey {
|
||||||
return c.session.LocalPrivateKey()
|
return c.secure.LocalPrivateKey()
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemotePubKey is the public key of the peer on the remote side
|
// RemotePubKey is the public key of the peer on the remote side
|
||||||
func (c *secureConn) RemotePublicKey() ic.PubKey {
|
func (c *secureConn) RemotePublicKey() ic.PubKey {
|
||||||
return c.session.RemotePublicKey()
|
return c.secure.RemotePublicKey()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read reads data, net.Conn style
|
// Read reads data, net.Conn style
|
||||||
func (c *secureConn) Read(buf []byte) (int, error) {
|
func (c *secureConn) Read(buf []byte) (int, error) {
|
||||||
return c.secure.Read(buf)
|
return c.secure.ReadWriter().Read(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write writes data, net.Conn style
|
// Write writes data, net.Conn style
|
||||||
func (c *secureConn) Write(buf []byte) (int, error) {
|
func (c *secureConn) Write(buf []byte) (int, error) {
|
||||||
return c.secure.Write(buf)
|
return c.secure.ReadWriter().Write(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *secureConn) NextMsgLen() (int, error) {
|
func (c *secureConn) NextMsgLen() (int, error) {
|
||||||
return c.secure.NextMsgLen()
|
return c.secure.ReadWriter().NextMsgLen()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadMsg reads data, net.Conn style
|
// ReadMsg reads data, net.Conn style
|
||||||
func (c *secureConn) ReadMsg() ([]byte, error) {
|
func (c *secureConn) ReadMsg() ([]byte, error) {
|
||||||
return c.secure.ReadMsg()
|
return c.secure.ReadWriter().ReadMsg()
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteMsg writes data, net.Conn style
|
// WriteMsg writes data, net.Conn style
|
||||||
func (c *secureConn) WriteMsg(buf []byte) error {
|
func (c *secureConn) WriteMsg(buf []byte) error {
|
||||||
return c.secure.WriteMsg(buf)
|
return c.secure.ReadWriter().WriteMsg(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReleaseMsg releases a buffer
|
// ReleaseMsg releases a buffer
|
||||||
func (c *secureConn) ReleaseMsg(m []byte) {
|
func (c *secureConn) ReleaseMsg(m []byte) {
|
||||||
c.secure.ReleaseMsg(m)
|
c.secure.ReadWriter().ReleaseMsg(m)
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,15 @@ func upgradeToSecureConn(t *testing.T, ctx context.Context, sk ic.PrivKey, c Con
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// need to read + write, as that's what triggers the handshake.
|
||||||
|
h := []byte("hello")
|
||||||
|
if _, err := s.Write(h); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if _, err := s.Read(h); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user