handshake.go 3.72 KB
Newer Older
1 2 3 4 5
package conn

import (
	"errors"
	"fmt"
Jeromy's avatar
Jeromy committed
6
	"strings"
7 8

	handshake "github.com/jbenet/go-ipfs/net/handshake"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
9
	hspb "github.com/jbenet/go-ipfs/net/handshake/pb"
Jeromy's avatar
Jeromy committed
10
	u "github.com/jbenet/go-ipfs/util"
11 12 13

	context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
	proto "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/goprotobuf/proto"
Jeromy's avatar
Jeromy committed
14
	ma "github.com/jbenet/go-multiaddr"
15 16
)

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
17
// Handshake1 exchanges local and remote versions and compares them
18
// closes remote and returns an error in case of major difference
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
19
func Handshake1(ctx context.Context, c Conn) error {
20 21 22
	rpeer := c.RemotePeer()
	lpeer := c.LocalPeer()

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
23 24
	var remoteH, localH *hspb.Handshake1
	localH = handshake.Handshake1Msg()
25 26 27 28 29 30 31

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

	c.Out() <- myVerBytes
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
32
	log.Debugf("Sent my version (%s) to %s", localH, rpeer)
33 34 35 36 37

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

38
	case <-c.Closing():
39 40 41 42 43 44 45
		return errors.New("remote closed connection during version exchange")

	case data, ok := <-c.In():
		if !ok {
			return fmt.Errorf("error retrieving from conn: %v", rpeer)
		}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
46
		remoteH = new(hspb.Handshake1)
47 48 49 50 51
		err = proto.Unmarshal(data, remoteH)
		if err != nil {
			return fmt.Errorf("could not decode remote version: %q", err)
		}

Jeromy's avatar
Jeromy committed
52
		log.Debugf("Received remote version (%s) from %s", remoteH, rpeer)
53 54
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
55
	if err := handshake.Handshake1Compatible(localH, remoteH); err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
56
		log.Infof("%s (%s) incompatible version with %s (%s)", lpeer, localH, rpeer, remoteH)
57 58 59
		return err
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
60
	log.Debugf("%s version handshake compatible %s", lpeer, rpeer)
61 62
	return nil
}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
63 64 65 66 67 68 69 70

// Handshake3 exchanges local and remote service information
func Handshake3(ctx context.Context, c Conn) error {
	rpeer := c.RemotePeer()
	lpeer := c.LocalPeer()

	var remoteH, localH *hspb.Handshake3
	localH = handshake.Handshake3Msg(lpeer)
Jeromy's avatar
Jeromy committed
71 72 73 74

	rma := c.RemoteMultiaddr()
	localH.ObservedAddr = proto.String(rma.String())

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
75 76 77 78 79 80
	localB, err := proto.Marshal(localH)
	if err != nil {
		return err
	}

	c.Out() <- localB
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
81
	log.Debugf("Handshake1: sent to %s", rpeer)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100

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

	case <-c.Closing():
		return errors.New("Handshake3: error remote connection closed")

	case remoteB, ok := <-c.In():
		if !ok {
			return fmt.Errorf("Handshake3 error receiving from conn: %v", rpeer)
		}

		remoteH = new(hspb.Handshake3)
		err = proto.Unmarshal(remoteB, remoteH)
		if err != nil {
			return fmt.Errorf("Handshake3 could not decode remote msg: %q", err)
		}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
101
		log.Debugf("Handshake3 received from %s", rpeer)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
102 103 104
	}

	if err := handshake.Handshake3UpdatePeer(rpeer, remoteH); err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
105
		log.Errorf("Handshake3 failed to update %s", rpeer)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
106 107 108
		return err
	}

Jeromy's avatar
Jeromy committed
109 110
	// If we are behind a NAT, inform the user that certain things might not work yet
	nat, err := checkNAT(remoteH.GetObservedAddr())
Jeromy's avatar
Jeromy committed
111 112 113 114
	if err != nil {
		log.Errorf("Error in NAT detection: %s", err)
	}
	if nat {
Jeromy's avatar
Jeromy committed
115 116 117 118 119 120 121 122 123
		msg := `Remote peer observed our address to be: %s
		The local addresses are: %s
		Thus, connection is going through NAT, and other connections may fail.

		IPFS NAT traversal is still under development. Please bug us on github or irc to fix this.
		Baby steps: http://jbenet.static.s3.amazonaws.com/271dfcf/baby-steps.gif
		`
		addrs, _ := u.GetLocalAddresses()
		log.Warning(fmt.Sprintf(msg, remoteH.GetObservedAddr(), addrs))
Jeromy's avatar
Jeromy committed
124 125
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
126 127
	return nil
}
Jeromy's avatar
Jeromy committed
128

Jeromy's avatar
Jeromy committed
129
// checkNAT returns whether or not we might be behind a NAT
Jeromy's avatar
Jeromy committed
130 131
func checkNAT(observedaddr string) (bool, error) {
	observedma, err := ma.NewMultiaddr(observedaddr)
Jeromy's avatar
Jeromy committed
132 133 134 135 136 137 138 139
	if err != nil {
		return false, err
	}
	addrs, err := u.GetLocalAddresses()
	if err != nil {
		return false, err
	}

Jeromy's avatar
Jeromy committed
140
	omastr := observedma.String()
Jeromy's avatar
Jeromy committed
141
	for _, addr := range addrs {
Jeromy's avatar
Jeromy committed
142
		if strings.HasPrefix(omastr, addr.String()) {
Jeromy's avatar
Jeromy committed
143 144 145 146 147
			return false, nil
		}
	}

	return true, nil
Jeromy's avatar
Jeromy committed
148
}