Skip to content

Commit

Permalink
Merge pull request #180 from cryptix/versionHandshake
Browse files Browse the repository at this point in the history
Version handshake
  • Loading branch information
jbenet committed Oct 16, 2014
2 parents f0571d3 + 0f47b93 commit bbfba91
Show file tree
Hide file tree
Showing 6 changed files with 203 additions and 0 deletions.
8 changes: 8 additions & 0 deletions net/handshake/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

all: semver.pb.go

semver.pb.go: semver.proto
protoc --gogo_out=. --proto_path=../../../../../:/usr/local/opt/protobuf/include:. $<

clean:
rm semver.pb.go
51 changes: 51 additions & 0 deletions net/handshake/semver.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions net/handshake/semver.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package handshake;

message Handshake1 {
// protocolVersion determines compatibility between peers
optional string protocolVersion = 1; // semver

// agentVersion is like a UserAgent string in browsers, or client version in bittorrent
// includes the client name and client. e.g. "go-ipfs/0.1.0"
optional string agentVersion = 2; // semver

// we'll have more fields here later.
}
56 changes: 56 additions & 0 deletions net/handshake/version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package handshake

import (
"errors"
"fmt"

updates "github.com/jbenet/go-ipfs/updates"

semver "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/coreos/go-semver/semver"
)

// currentVersion holds the current protocol version for a client running this code
var currentVersion *semver.Version

func init() {
var err error
currentVersion, err = semver.NewVersion("0.0.1")
if err != nil {
panic(fmt.Errorf("invalid protocol version: %v", err))
}
}

// CurrentHandshake returns the current protocol version as a protobuf message
func CurrentHandshake() *Handshake1 {
return NewHandshake1(currentVersion.String(), "go-ipfs/"+updates.Version)
}

// ErrVersionMismatch is returned when two clients don't share a protocol version
var ErrVersionMismatch = errors.New("protocol missmatch")

// Compatible checks wether two versions are compatible
// returns nil if they are fine
func Compatible(handshakeA, handshakeB *Handshake1) error {
a, err := semver.NewVersion(*handshakeA.ProtocolVersion)
if err != nil {
return err
}
b, err := semver.NewVersion(*handshakeB.ProtocolVersion)
if err != nil {
return err
}

if a.Major != b.Major {
return ErrVersionMismatch
}

return nil
}

// NewHandshake1 creates a new Handshake1 from the two strings
func NewHandshake1(protoVer, agentVer string) *Handshake1 {
return &Handshake1{
ProtocolVersion: &protoVer,
AgentVersion: &agentVer,
}
}
23 changes: 23 additions & 0 deletions net/handshake/version_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package handshake

import "testing"

func TestCompatible(t *testing.T) {
tcases := []struct {
a, b string
expected error
}{
{"0.0.0", "0.0.0", nil},
{"1.0.0", "1.1.0", nil},
{"1.0.0", "1.0.1", nil},
{"0.0.0", "1.0.0", ErrVersionMismatch},
{"1.0.0", "0.0.0", ErrVersionMismatch},
}

for i, tcase := range tcases {

if Compatible(NewHandshake1(tcase.a, ""), NewHandshake1(tcase.b, "")) != tcase.expected {
t.Fatalf("case[%d] failed", i)
}
}
}
53 changes: 53 additions & 0 deletions net/swarm/conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import (

spipe "github.com/jbenet/go-ipfs/crypto/spipe"
conn "github.com/jbenet/go-ipfs/net/conn"
handshake "github.com/jbenet/go-ipfs/net/handshake"
msg "github.com/jbenet/go-ipfs/net/message"

proto "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto"
ma "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
manet "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr/net"
)
Expand Down Expand Up @@ -109,6 +111,10 @@ func (s *Swarm) connSetup(c *conn.Conn) error {
// add address of connection to Peer. Maybe it should happen in connSecure.
c.Peer.AddAddress(c.Addr)

if err := s.connVersionExchange(c); err != nil {
return fmt.Errorf("Conn version exchange error: %v", err)
}

// add to conns
s.connsLock.Lock()
if _, ok := s.conns[c.Peer.Key()]; ok {
Expand Down Expand Up @@ -152,6 +158,53 @@ func (s *Swarm) connSecure(c *conn.Conn) error {
return nil
}

// connVersionExchange exchanges local and remote versions and compares them
// closes remote and returns an error in case of major difference
func (s *Swarm) connVersionExchange(remote *conn.Conn) error {
var remoteHandshake, localHandshake *handshake.Handshake1
localHandshake = handshake.CurrentHandshake()

myVerBytes, err := proto.Marshal(localHandshake)
if err != nil {
return err
}

remote.Secure.Out <- myVerBytes

log.Debug("Send my version(%s) [to = %s]", localHandshake, remote.Peer)

select {
case <-s.ctx.Done():
return s.ctx.Err()

case <-remote.Closed:
return errors.New("remote closed connection during version exchange")

case data, ok := <-remote.Secure.In:
if !ok {
return fmt.Errorf("Error retrieving from conn: %v", remote.Peer)
}

remoteHandshake = new(handshake.Handshake1)
err = proto.Unmarshal(data, remoteHandshake)
if err != nil {
s.Close()
return fmt.Errorf("connSetup: could not decode remote version: %q", err)
}

log.Debug("Received remote version(%s) [from = %s]", remoteHandshake, remote.Peer)
}

if err := handshake.Compatible(localHandshake, remoteHandshake); err != nil {
log.Info("%s (%s) incompatible version with %s (%s)", s.local, localHandshake, remote.Peer, remoteHandshake)
remote.Close()
return err
}

log.Debug("[peer: %s] Version compatible", remote.Peer)
return nil
}

// Handles the unwrapping + sending of messages to the right connection.
func (s *Swarm) fanOut() {
for {
Expand Down

0 comments on commit bbfba91

Please sign in to comment.