For fast datapath, the router core will only be invoked upon datapath misses. And it needs to yield routing decisions in a form that can be used to configure the in-kernel datapath. So here we make the router core yield FlowOps, which represent operations which actually do something with a packet. The router core obtains these in turn from the Bridge or the Overlay, depending on what it wants to do with a packet. For fast datapath, it's critical that the router makes decisions based only on the required packet header information (as the same header information will be used when configuring the ODP flow rules). So we pass only that information into the router core, in the form of PacketKeys. The overall effect is to curry the router core: Rather than taking a packet and doing something with it, it takes packet header information and returns a function that does something with a packet.
465 lines
13 KiB
Go
465 lines
13 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
|
|
}
|
|
|
|
// 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,
|
|
"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", "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")
|
|
|
|
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), 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.remoteTCPAddr, 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(msg []byte) error {
|
|
return conn.sendProtocolMsg(ProtocolMsg{ProtocolOverlayControlMsg, 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 ProtocolOverlayControlMsg:
|
|
conn.forwarder.ControlMessage(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()
|
|
}
|
|
}
|