Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Version handshake #180

Merged
merged 9 commits into from
Oct 16, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, we don't need to use a NetMessage here. Conns deal with raw bytes. (they wont after i'm done with them tonight, but i'll fix this later)


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