Files
weave/router/handshake.go
T
Matthias Radestock 3124ffede5 more forward/backward compatible protocol versioning
We now first exchange a very minimal header, consisting of just the
name of the protocol ("weave") and the min and max version (encoded as
a byte each) we support. This leaves all other details of the
protocol, including the contents of of the handshake, open.

Peers agree to communicate using the maximum mutually supported
version. We record this in the LocalConnection, though atm nothing is
making use of that information.

We set a shorter than normal deadline (10s instead of 60s) for the
header exchange, so we detect version mismatches, garbage, or
non-responsive remotes quickly.

We reset the protocol version to '1' since this is a fresh start; old
weaves will not be able to parse the header.

Closes #871.
2015-06-16 11:00:41 +01:00

168 lines
4.8 KiB
Go

package router
import (
"encoding/gob"
"encoding/hex"
"fmt"
"strconv"
"time"
)
type FieldValidator struct {
fields map[string]string
err error
}
func NewFieldValidator(fields map[string]string) *FieldValidator {
return &FieldValidator{fields, nil}
}
func (fv *FieldValidator) Value(fieldName string) (string, error) {
if fv.err != nil {
return "", fv.err
}
val, found := fv.fields[fieldName]
if !found {
fv.err = fmt.Errorf("Field %s is missing", fieldName)
return "", fv.err
}
return val, nil
}
func (fv *FieldValidator) CheckEqual(fieldName, expectedValue string) error {
val, err := fv.Value(fieldName)
if err != nil {
return err
}
if val != expectedValue {
fv.err = fmt.Errorf("Field %s has wrong value; expected '%s', received '%s'", fieldName, expectedValue, val)
return fv.err
}
return nil
}
func (fv *FieldValidator) Err() error {
return fv.err
}
func (conn *LocalConnection) handshake(acceptNewPeer bool) (*gob.Encoder, *gob.Decoder, error) {
// We do not need to worry about locking in here as at this point
// the connection is not reachable by any go-routine other than
// ourself. Only when we add this connection to the conn.local
// peer will it be visible from multiple go-routines.
conn.TCPConn.SetDeadline(time.Now().Add(HeaderTimeout))
version, err := exchangeProtocolHeader(conn.TCPConn, conn.TCPConn)
if err != nil {
return nil, nil, err
}
conn.version = version
conn.TCPConn.SetDeadline(time.Time{})
conn.extendReadDeadline()
localConnID := randUint64()
usingPassword := conn.Router.UsingPassword()
enc := gob.NewEncoder(conn.TCPConn)
dec := gob.NewDecoder(conn.TCPConn)
fv, private, err := conn.exchangeHandshake(localConnID, usingPassword, enc, dec)
if err != nil {
return nil, nil, err
}
nameStr, _ := fv.Value("Name")
nickNameStr, _ := fv.Value("NickName")
uidStr, _ := fv.Value("UID")
remoteConnIDStr, _ := fv.Value("ConnID")
if err := fv.Err(); err != nil {
return nil, nil, err
}
name, err := PeerNameFromString(nameStr)
if err != nil {
return nil, nil, err
}
if !acceptNewPeer {
if _, found := conn.Router.Peers.Fetch(name); !found {
return nil, nil, fmt.Errorf("Found unknown remote name: %s at %s", name, conn.remoteTCPAddr)
}
}
if existingConn, found := conn.Router.Ourself.ConnectionTo(name); found && existingConn.Established() {
return nil, nil, fmt.Errorf("Already have connection to %s at %s", existingConn.Remote(), existingConn.RemoteTCPAddr())
}
uid, err := ParsePeerUID(uidStr)
if err != nil {
return nil, nil, err
}
remoteConnID, err := strconv.ParseUint(remoteConnIDStr, 10, 64)
if err != nil {
return nil, nil, err
}
conn.uid = localConnID ^ remoteConnID
remotePublicStr, rpErr := fv.Value("PublicKey")
if usingPassword {
if rpErr != nil {
return nil, nil, rpErr
}
remotePublicSlice, rpErr := hex.DecodeString(remotePublicStr)
if rpErr != nil {
return nil, nil, rpErr
}
remotePublic := [32]byte{}
for idx, elem := range remotePublicSlice {
remotePublic[idx] = elem
}
conn.SessionKey = FormSessionKey(&remotePublic, private, conn.Router.Password)
conn.tcpSender = NewEncryptedTCPSender(enc, conn.SessionKey, conn.outbound)
conn.Decryptor = NewNaClDecryptor(conn.SessionKey, conn.outbound)
} else {
if rpErr == nil {
return nil, nil, fmt.Errorf("Remote network is encrypted. Password required.")
}
conn.tcpSender = NewSimpleTCPSender(enc)
conn.Decryptor = NewNonDecryptor()
}
return enc, dec, conn.setRemote(NewPeer(name, nickNameStr, uid, 0))
}
func (conn *LocalConnection) exchangeHandshake(localConnID uint64, usingPassword bool, enc *gob.Encoder, dec *gob.Decoder) (*FieldValidator, *[32]byte, error) {
handshakeSend := map[string]string{
"PeerNameFlavour": PeerNameFlavour,
"Name": conn.local.Name.String(),
"NickName": conn.local.NickName,
"UID": fmt.Sprint(conn.local.UID),
"ConnID": fmt.Sprint(localConnID)}
handshakeRecv := map[string]string{}
var public, private *[32]byte
var err error
if usingPassword {
public, private, err = GenerateKeyPair()
if err != nil {
return nil, nil, err
}
handshakeSend["PublicKey"] = hex.EncodeToString(public[:])
}
enc.Encode(handshakeSend)
if err = dec.Decode(&handshakeRecv); err != nil {
return nil, nil, err
}
fv := NewFieldValidator(handshakeRecv)
fv.CheckEqual("PeerNameFlavour", PeerNameFlavour)
return fv, private, nil
}
func (conn *LocalConnection) setRemote(toPeer *Peer) error {
toPeer = conn.Router.Peers.FetchWithDefault(toPeer)
switch toPeer {
case nil:
return fmt.Errorf("Connection appears to be with different version of a peer we already know of")
case conn.local:
conn.remote = toPeer // have to do assigment here to ensure Shutdown releases ref count
return fmt.Errorf("Cannot connect to ourself")
default:
conn.remote = toPeer
return nil
}
}