These are needed to support vxlan encapsulation. In a vxlan packet, the only place we can straightforwardly hold the source and destination peer identifiers used by the weave router is in the 24-bit VNI (vxlan network identifier) field. This gives us 12 bits to indicate a peer. The resulting limit of 4096 peers on a weave network does not seem like a problem in the near future. But it does mean that decentralized allocation makes collisions fairly likely even in weave networks of sizes that are common today. Fortunately, the problem is much more tractable than IPAM because we can detect when collisions occur and take action to resolve them.
473 lines
14 KiB
Go
473 lines
14 KiB
Go
package router
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"strconv"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
type Connection interface {
|
|
Local() *Peer
|
|
Remote() *Peer
|
|
RemoteTCPAddr() string
|
|
Outbound() bool
|
|
Established() bool
|
|
BreakTie(Connection) ConnectionTieBreak
|
|
Shutdown(error)
|
|
Log(args ...interface{})
|
|
}
|
|
|
|
type ConnectionTieBreak int
|
|
|
|
const (
|
|
TieBreakWon ConnectionTieBreak = iota
|
|
TieBreakLost
|
|
TieBreakTied
|
|
)
|
|
|
|
var ErrConnectToSelf = fmt.Errorf("Cannot connect to ourself")
|
|
|
|
type RemoteConnection struct {
|
|
local *Peer
|
|
remote *Peer
|
|
remoteTCPAddr string
|
|
outbound bool
|
|
established bool
|
|
}
|
|
|
|
type LocalConnection struct {
|
|
sync.RWMutex
|
|
RemoteConnection
|
|
TCPConn *net.TCPConn
|
|
version byte
|
|
tcpSender TCPSender
|
|
remoteUDPAddr *net.UDPAddr
|
|
SessionKey *[32]byte
|
|
heartbeatTCP *time.Ticker
|
|
Router *Router
|
|
uid uint64
|
|
actionChan chan<- ConnectionAction
|
|
finished <-chan struct{} // closed to signal that actorLoop has finished
|
|
forwarder OverlayForwarder
|
|
}
|
|
|
|
type ConnectionAction func() error
|
|
|
|
func NewRemoteConnection(from, to *Peer, tcpAddr string, outbound bool, established bool) *RemoteConnection {
|
|
return &RemoteConnection{
|
|
local: from,
|
|
remote: to,
|
|
remoteTCPAddr: tcpAddr,
|
|
outbound: outbound,
|
|
established: established,
|
|
}
|
|
}
|
|
|
|
func (conn *RemoteConnection) Local() *Peer { return conn.local }
|
|
func (conn *RemoteConnection) Remote() *Peer { return conn.remote }
|
|
func (conn *RemoteConnection) RemoteTCPAddr() string { return conn.remoteTCPAddr }
|
|
func (conn *RemoteConnection) Outbound() bool { return conn.outbound }
|
|
func (conn *RemoteConnection) Established() bool { return conn.established }
|
|
func (conn *RemoteConnection) BreakTie(Connection) ConnectionTieBreak { return TieBreakTied }
|
|
func (conn *RemoteConnection) Shutdown(error) {}
|
|
|
|
func (conn *RemoteConnection) Log(args ...interface{}) {
|
|
log.Println(append(append([]interface{}{}, fmt.Sprintf("->[%s|%s]:", conn.remoteTCPAddr, conn.remote)), args...)...)
|
|
}
|
|
|
|
func (conn *RemoteConnection) ErrorLog(args ...interface{}) {
|
|
log.Errorln(append(append([]interface{}{}, fmt.Sprintf("->[%s|%s]:", conn.remoteTCPAddr, conn.remote)), args...)...)
|
|
}
|
|
|
|
// Does not return anything. If the connection is successful, it will
|
|
// end up in the local peer's connections map.
|
|
func StartLocalConnection(connRemote *RemoteConnection, tcpConn *net.TCPConn, udpAddr *net.UDPAddr, router *Router, acceptNewPeer bool) {
|
|
if connRemote.local != router.Ourself.Peer {
|
|
log.Fatal("Attempt to create local connection from a peer which is not ourself")
|
|
}
|
|
// NB, we're taking a copy of connRemote here.
|
|
actionChan := make(chan ConnectionAction, ChannelSize)
|
|
finished := make(chan struct{})
|
|
conn := &LocalConnection{
|
|
RemoteConnection: *connRemote,
|
|
Router: router,
|
|
TCPConn: tcpConn,
|
|
remoteUDPAddr: udpAddr,
|
|
uid: randUint64(),
|
|
actionChan: actionChan,
|
|
finished: finished}
|
|
go conn.run(actionChan, finished, acceptNewPeer)
|
|
}
|
|
|
|
func (conn *LocalConnection) BreakTie(dupConn Connection) ConnectionTieBreak {
|
|
dupConnLocal := dupConn.(*LocalConnection)
|
|
// conn.uid is used as the tie breaker here, in the knowledge that
|
|
// both sides will make the same decision.
|
|
if conn.uid < dupConnLocal.uid {
|
|
return TieBreakWon
|
|
} else if dupConnLocal.uid < conn.uid {
|
|
return TieBreakLost
|
|
} else {
|
|
return TieBreakTied
|
|
}
|
|
}
|
|
|
|
func (conn *LocalConnection) Established() bool {
|
|
conn.RLock()
|
|
defer conn.RUnlock()
|
|
return conn.established
|
|
}
|
|
|
|
// Send directly, not via the Actor. If it goes via the Actor we can
|
|
// get a deadlock where LocalConnection is blocked talking to
|
|
// LocalPeer and LocalPeer is blocked trying send a ProtocolMsg via
|
|
// LocalConnection, and the channels are full in both directions so
|
|
// nothing can proceed.
|
|
func (conn *LocalConnection) SendProtocolMsg(m ProtocolMsg) {
|
|
if err := conn.sendProtocolMsg(m); err != nil {
|
|
conn.Shutdown(err)
|
|
}
|
|
}
|
|
|
|
// ACTOR methods
|
|
|
|
// NB: The conn.* fields are only written by the connection actor
|
|
// process, which is the caller of the ConnectionAction funs. Hence we
|
|
// do not need locks for reading, and only need write locks for fields
|
|
// read by other processes.
|
|
|
|
// Async
|
|
func (conn *LocalConnection) Shutdown(err error) {
|
|
// err should always be a real error, even if only io.EOF
|
|
if err == nil {
|
|
panic("nil error")
|
|
}
|
|
|
|
// Run on its own goroutine in case the channel is backed up
|
|
go func() { conn.sendAction(func() error { return err }) }()
|
|
}
|
|
|
|
// Send an actor request to the actorLoop, but don't block if
|
|
// actorLoop has exited - see http://blog.golang.org/pipelines for
|
|
// pattern
|
|
func (conn *LocalConnection) sendAction(action ConnectionAction) {
|
|
select {
|
|
case conn.actionChan <- action:
|
|
case <-conn.finished:
|
|
}
|
|
}
|
|
|
|
// ACTOR server
|
|
|
|
func (conn *LocalConnection) run(actionChan <-chan ConnectionAction, finished chan<- struct{}, acceptNewPeer bool) {
|
|
var err error // important to use this var and not create another one with 'err :='
|
|
defer func() { conn.shutdown(err) }()
|
|
defer close(finished)
|
|
|
|
conn.TCPConn.SetLinger(0)
|
|
intro, err := ProtocolIntroParams{
|
|
MinVersion: conn.Router.ProtocolMinVersion,
|
|
MaxVersion: ProtocolMaxVersion,
|
|
Features: conn.makeFeatures(),
|
|
Conn: conn.TCPConn,
|
|
Password: conn.Router.Password,
|
|
Outbound: conn.outbound,
|
|
}.DoIntro()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
conn.SessionKey = intro.SessionKey
|
|
conn.tcpSender = intro.Sender
|
|
conn.version = intro.Version
|
|
|
|
remote, err := conn.parseFeatures(intro.Features)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
if err = conn.registerRemote(remote, acceptNewPeer); err != nil {
|
|
return
|
|
}
|
|
|
|
conn.Log("connection ready; using protocol version", conn.version)
|
|
|
|
params := ForwarderParams{
|
|
RemotePeer: conn.remote,
|
|
LocalIP: conn.TCPConn.LocalAddr().(*net.TCPAddr).IP,
|
|
RemoteAddr: conn.remoteUDPAddr,
|
|
ConnUID: conn.uid,
|
|
Crypto: conn.forwarderCrypto(),
|
|
SendControlMessage: conn.sendOverlayControlMessage,
|
|
}
|
|
if conn.forwarder, err = conn.Router.Overlay.MakeForwarder(params); err != nil {
|
|
return
|
|
}
|
|
|
|
// As soon as we do AddConnection, the new connection becomes
|
|
// visible to the packet routing logic. So AddConnection must
|
|
// come after MakeForwarder
|
|
if err = conn.Router.Ourself.AddConnection(conn); err != nil {
|
|
return
|
|
}
|
|
conn.Router.ConnectionMaker.ConnectionCreated(conn)
|
|
|
|
// SetListener has the side-effect of telling the forwarder
|
|
// that the connection is confirmed. This comes after
|
|
// AddConnection, because only after that completes do we know
|
|
// the connection is valid: in particular that it is not a
|
|
// duplicate connection to the same peer. Sending heartbeats
|
|
// on a duplicate connection can trip up crypto at the other
|
|
// end, since the associated UDP packets may get decoded by
|
|
// the other connection. It is also generally wasteful to
|
|
// engage in any interaction with the remote on a connection
|
|
// that turns out to be invalid.
|
|
conn.forwarder.SetListener(ConnectionAsForwarderListener{conn})
|
|
|
|
// receiveTCP must follow also AddConnection. In the absence
|
|
// of any indirect connectivity to the remote peer, the first
|
|
// we hear about it (and any peers reachable from it) is
|
|
// through topology gossip it sends us on the connection. We
|
|
// must ensure that the connection has been added to Ourself
|
|
// prior to processing any such gossip, otherwise we risk
|
|
// immediately gc'ing part of that newly received portion of
|
|
// the topology (though not the remote peer itself, since that
|
|
// will have a positive ref count), leaving behind dangling
|
|
// references to peers. Hence we must invoke AddConnection,
|
|
// which is *synchronous*, first.
|
|
conn.heartbeatTCP = time.NewTicker(TCPHeartbeat)
|
|
go conn.receiveTCP(intro.Receiver)
|
|
|
|
// AddConnection must precede actorLoop. More precisely, it
|
|
// must precede shutdown, since that invokes DeleteConnection
|
|
// and is invoked on termination of this entire
|
|
// function. Essentially this boils down to a prohibition on
|
|
// running AddConnection in a separate goroutine, at least not
|
|
// without some synchronisation. Which in turn requires the
|
|
// launching of the receiveTCP goroutine to precede actorLoop.
|
|
err = conn.actorLoop(actionChan)
|
|
}
|
|
|
|
func (conn *LocalConnection) makeFeatures() map[string]string {
|
|
return map[string]string{
|
|
"PeerNameFlavour": PeerNameFlavour,
|
|
"Name": conn.local.Name.String(),
|
|
"NickName": conn.local.NickName,
|
|
"ShortID": fmt.Sprint(conn.local.ShortID),
|
|
"UID": fmt.Sprint(conn.local.UID),
|
|
"ConnID": fmt.Sprint(conn.uid),
|
|
}
|
|
}
|
|
|
|
type features map[string]string
|
|
|
|
func (features features) MustHave(keys []string) error {
|
|
for _, key := range keys {
|
|
if _, ok := features[key]; !ok {
|
|
return fmt.Errorf("Field %s is missing", key)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (features features) Get(key string) string {
|
|
return features[key]
|
|
}
|
|
|
|
func (conn *LocalConnection) parseFeatures(features features) (*Peer, error) {
|
|
if err := features.MustHave([]string{"PeerNameFlavour", "Name", "NickName", "ShortID", "UID", "ConnID"}); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
remotePeerNameFlavour := features.Get("PeerNameFlavour")
|
|
if remotePeerNameFlavour != PeerNameFlavour {
|
|
return nil, fmt.Errorf("Peer name flavour mismatch (ours: '%s', theirs: '%s')", PeerNameFlavour, remotePeerNameFlavour)
|
|
}
|
|
|
|
name, err := PeerNameFromString(features.Get("Name"))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
nickName := features.Get("NickName")
|
|
|
|
shortID, err := strconv.ParseUint(features.Get("ShortID"), 10,
|
|
PeerShortIDBits)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
uid, err := ParsePeerUID(features.Get("UID"))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
remoteConnID, err := strconv.ParseUint(features.Get("ConnID"), 10, 64)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
conn.uid ^= remoteConnID
|
|
return NewPeer(name, nickName, uid, 0, PeerShortID(shortID)), nil
|
|
}
|
|
|
|
func (conn *LocalConnection) registerRemote(remote *Peer, acceptNewPeer bool) error {
|
|
if acceptNewPeer {
|
|
conn.remote = conn.Router.Peers.FetchWithDefault(remote)
|
|
} else {
|
|
conn.remote = conn.Router.Peers.FetchAndAddRef(remote.Name)
|
|
if conn.remote == nil {
|
|
return fmt.Errorf("Found unknown remote name: %s at %s", remote.Name, conn.remoteTCPAddr)
|
|
}
|
|
}
|
|
|
|
if conn.remote.UID != remote.UID {
|
|
return fmt.Errorf("Connection appears to be with different version of a peer we already know of")
|
|
}
|
|
|
|
if conn.remote == conn.local {
|
|
return ErrConnectToSelf
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (conn *LocalConnection) actorLoop(actionChan <-chan ConnectionAction) (err error) {
|
|
for err == nil {
|
|
select {
|
|
case action := <-actionChan:
|
|
err = action()
|
|
case <-conn.heartbeatTCP.C:
|
|
err = conn.sendSimpleProtocolMsg(ProtocolHeartbeat)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func (conn *LocalConnection) shutdown(err error) {
|
|
if conn.remote == nil {
|
|
log.Errorf("->[%s] connection shutting down due to error during handshake: %v", conn.remoteTCPAddr, err)
|
|
} else {
|
|
conn.ErrorLog("connection shutting down due to error:", err)
|
|
}
|
|
|
|
if conn.TCPConn != nil {
|
|
checkWarn(conn.TCPConn.Close())
|
|
}
|
|
|
|
if conn.remote != nil {
|
|
conn.Router.Peers.Dereference(conn.remote)
|
|
conn.Router.Ourself.DeleteConnection(conn)
|
|
}
|
|
|
|
stopTicker(conn.heartbeatTCP)
|
|
|
|
if conn.forwarder != nil {
|
|
conn.forwarder.Stop()
|
|
}
|
|
|
|
conn.Router.ConnectionMaker.ConnectionTerminated(conn, err)
|
|
}
|
|
|
|
func (conn *LocalConnection) forwarderCrypto() *OverlayCrypto {
|
|
if !conn.Router.UsingPassword() {
|
|
return nil
|
|
}
|
|
|
|
name := conn.local.NameByte
|
|
return &OverlayCrypto{
|
|
Dec: NewNaClDecryptor(conn.SessionKey, conn.outbound),
|
|
Enc: NewNaClEncryptor(name, conn.SessionKey, conn.outbound, false),
|
|
EncDF: NewNaClEncryptor(name, conn.SessionKey, conn.outbound, true),
|
|
}
|
|
}
|
|
|
|
func (conn *LocalConnection) sendOverlayControlMessage(tag ProtocolTag, msg []byte) error {
|
|
return conn.sendProtocolMsg(ProtocolMsg{tag, msg})
|
|
}
|
|
|
|
type ConnectionAsForwarderListener struct{ conn *LocalConnection }
|
|
|
|
func (l ConnectionAsForwarderListener) Established() {
|
|
l.conn.sendAction(func() error {
|
|
old := l.conn.established
|
|
l.conn.Lock()
|
|
l.conn.established = true
|
|
l.conn.Unlock()
|
|
if !old {
|
|
l.conn.Router.Ourself.ConnectionEstablished(l.conn)
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func (l ConnectionAsForwarderListener) Error(err error) {
|
|
l.conn.sendAction(func() error { return err })
|
|
}
|
|
|
|
// Helpers
|
|
|
|
func (conn *LocalConnection) sendSimpleProtocolMsg(tag ProtocolTag) error {
|
|
return conn.sendProtocolMsg(ProtocolMsg{tag: tag})
|
|
}
|
|
|
|
func (conn *LocalConnection) sendProtocolMsg(m ProtocolMsg) error {
|
|
return conn.tcpSender.Send(Concat([]byte{byte(m.tag)}, m.msg))
|
|
}
|
|
|
|
func (conn *LocalConnection) receiveTCP(receiver TCPReceiver) {
|
|
var err error
|
|
for {
|
|
conn.extendReadDeadline()
|
|
|
|
var msg []byte
|
|
if msg, err = receiver.Receive(); err != nil {
|
|
break
|
|
}
|
|
if len(msg) < 1 {
|
|
conn.Log("ignoring blank msg")
|
|
continue
|
|
}
|
|
if err = conn.handleProtocolMsg(ProtocolTag(msg[0]), msg[1:]); err != nil {
|
|
break
|
|
}
|
|
}
|
|
conn.Shutdown(err)
|
|
}
|
|
|
|
func (conn *LocalConnection) handleProtocolMsg(tag ProtocolTag, payload []byte) error {
|
|
switch tag {
|
|
case ProtocolHeartbeat:
|
|
case ProtocolConnectionEstablished, ProtocolFragmentationReceived, ProtocolPMTUVerified, ProtocolOverlayControlMsg:
|
|
conn.forwarder.ControlMessage(tag, payload)
|
|
case ProtocolGossipUnicast, ProtocolGossipBroadcast, ProtocolGossip:
|
|
return conn.Router.handleGossip(tag, payload)
|
|
default:
|
|
conn.Log("ignoring unknown protocol tag:", tag)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (conn *LocalConnection) extendReadDeadline() {
|
|
conn.TCPConn.SetReadDeadline(time.Now().Add(TCPHeartbeat * 2))
|
|
}
|
|
|
|
func (conn *LocalConnection) Forward(key ForwardPacketKey) FlowOp {
|
|
return conn.forwarder.Forward(key)
|
|
}
|
|
|
|
func tickerChan(ticker *time.Ticker) <-chan time.Time {
|
|
if ticker != nil {
|
|
return ticker.C
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func stopTicker(ticker *time.Ticker) {
|
|
if ticker != nil {
|
|
ticker.Stop()
|
|
}
|
|
}
|