Topology gossip can be received on a connection before that connection has been marked as 'established'. If we do not include unestablished connections in topology gossip, then parts of the topology can be removed by gc, since they are deemed unreachable, which can cause subsequent incremental topology gossips to be rejected due to them containing references to peers that have been removed by the gc. Including unestablished connections in topology gossip requires: - removing the check for 'established' connections in the encoding of peers for topology gossip, - adding the 'established' flag to the internal representation of remote connections, - adding the 'established' flag to to encoding of connections in topology gossip, - treating the *addition* of a connection as an update to the local peer. The latter entails - bumping the peer version on connection addition (as well as on marking 'established' as previously), - bumping the peer version *unconditionally* on connection deletion (rather than only when the connection was 'established', as previously), - moving the initial sending of complete topology on the connection, and the broadcasting of a topology update containing the two peers at the ends of the connection, from the connection 'established' logic to the connection addition logic, - broadcasting just an topology update for the local peer when a connection gets marked as 'established', - broadcasting a topology update for the local peer *unconditionally* when a connection is removed (rather than only doing this when the connection was 'established', as previously).
290 lines
8.2 KiB
Go
290 lines
8.2 KiB
Go
package router
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"net"
|
|
"time"
|
|
)
|
|
|
|
type LocalPeer struct {
|
|
*Peer
|
|
Router *Router
|
|
queryChan chan<- *PeerInteraction
|
|
}
|
|
|
|
type PeerInteraction struct {
|
|
Interaction
|
|
payload interface{}
|
|
}
|
|
|
|
func NewLocalPeer(name PeerName, router *Router) *LocalPeer {
|
|
return &LocalPeer{Peer: NewPeer(name, 0, 0), Router: router}
|
|
}
|
|
|
|
func (peer *LocalPeer) Start() {
|
|
queryChan := make(chan *PeerInteraction, ChannelSize)
|
|
peer.queryChan = queryChan
|
|
go peer.queryLoop(queryChan)
|
|
}
|
|
|
|
func (peer *LocalPeer) Forward(dstPeer *Peer, df bool, frame []byte, dec *EthernetDecoder) error {
|
|
return peer.Relay(peer.Peer, dstPeer, df, frame, dec)
|
|
}
|
|
|
|
func (peer *LocalPeer) Broadcast(df bool, frame []byte, dec *EthernetDecoder) error {
|
|
return peer.RelayBroadcast(peer.Peer, df, frame, dec)
|
|
}
|
|
|
|
func (peer *LocalPeer) Relay(srcPeer, dstPeer *Peer, df bool, frame []byte, dec *EthernetDecoder) error {
|
|
relayPeerName, found := peer.Router.Routes.Unicast(dstPeer.Name)
|
|
if !found {
|
|
// Not necessarily an error as there could be a race with the
|
|
// dst disappearing whilst the frame is in flight
|
|
log.Println("Received packet for unknown destination:", dstPeer.Name)
|
|
return nil
|
|
}
|
|
conn, found := peer.ConnectionTo(relayPeerName)
|
|
if !found {
|
|
// Again, could just be a race, not necessarily an error
|
|
log.Println("Unable to find connection to relay peer", relayPeerName)
|
|
return nil
|
|
}
|
|
return conn.(*LocalConnection).Forward(df, &ForwardedFrame{
|
|
srcPeer: srcPeer,
|
|
dstPeer: dstPeer,
|
|
frame: frame},
|
|
dec)
|
|
}
|
|
|
|
func (peer *LocalPeer) RelayBroadcast(srcPeer *Peer, df bool, frame []byte, dec *EthernetDecoder) error {
|
|
for _, conn := range peer.NextBroadcastHops(srcPeer) {
|
|
err := conn.Forward(df, &ForwardedFrame{
|
|
srcPeer: srcPeer,
|
|
dstPeer: conn.Remote(),
|
|
frame: frame},
|
|
dec)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (peer *LocalPeer) NextBroadcastHops(srcPeer *Peer) []*LocalConnection {
|
|
nextHops := peer.Router.Routes.Broadcast(srcPeer.Name)
|
|
if len(nextHops) == 0 {
|
|
return nil
|
|
}
|
|
nextConns := make([]*LocalConnection, 0, len(nextHops))
|
|
peer.RLock()
|
|
defer peer.RUnlock()
|
|
for _, hopName := range nextHops {
|
|
conn, found := peer.connections[hopName]
|
|
// Again, !found could just be due to a race.
|
|
if found {
|
|
nextConns = append(nextConns, conn.(*LocalConnection))
|
|
}
|
|
}
|
|
return nextConns
|
|
}
|
|
|
|
func (peer *LocalPeer) CreateConnection(peerAddr string, acceptNewPeer bool) error {
|
|
if err := peer.checkConnectionLimit(); err != nil {
|
|
return err
|
|
}
|
|
// We're dialing the remote so that means connections will come from random ports
|
|
addrStr := NormalisePeerAddr(peerAddr)
|
|
tcpAddr, tcpErr := net.ResolveTCPAddr("tcp4", addrStr)
|
|
udpAddr, udpErr := net.ResolveUDPAddr("udp4", addrStr)
|
|
if tcpErr != nil || udpErr != nil {
|
|
// they really should have the same value, but just in case...
|
|
if tcpErr == nil {
|
|
return udpErr
|
|
}
|
|
return tcpErr
|
|
}
|
|
tcpConn, err := net.DialTCP("tcp4", nil, tcpAddr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
connRemote := NewRemoteConnection(peer.Peer, nil, tcpConn.RemoteAddr().String(), false)
|
|
connLocal := NewLocalConnection(connRemote, tcpConn, udpAddr, peer.Router)
|
|
connLocal.Start(acceptNewPeer)
|
|
return nil
|
|
}
|
|
|
|
// ACTOR client API
|
|
|
|
const (
|
|
PAddConnection = iota
|
|
PConnectionEstablished
|
|
PDeleteConnection
|
|
PSendProtocolMsg
|
|
)
|
|
|
|
// Sync.
|
|
func (peer *LocalPeer) AddConnection(conn *LocalConnection) {
|
|
resultChan := make(chan interface{})
|
|
peer.queryChan <- &PeerInteraction{
|
|
Interaction: Interaction{code: PAddConnection, resultChan: resultChan},
|
|
payload: conn}
|
|
<-resultChan
|
|
}
|
|
|
|
// Async.
|
|
func (peer *LocalPeer) ConnectionEstablished(conn *LocalConnection) {
|
|
peer.queryChan <- &PeerInteraction{
|
|
Interaction: Interaction{code: PConnectionEstablished},
|
|
payload: conn}
|
|
}
|
|
|
|
// Sync.
|
|
func (peer *LocalPeer) DeleteConnection(conn *LocalConnection) {
|
|
resultChan := make(chan interface{})
|
|
peer.queryChan <- &PeerInteraction{
|
|
Interaction: Interaction{code: PDeleteConnection, resultChan: resultChan},
|
|
payload: conn}
|
|
<-resultChan
|
|
}
|
|
|
|
// Async.
|
|
func (peer *LocalPeer) SendProtocolMsg(m ProtocolMsg) {
|
|
peer.queryChan <- &PeerInteraction{
|
|
Interaction: Interaction{code: PSendProtocolMsg},
|
|
payload: m}
|
|
}
|
|
|
|
// ACTOR server
|
|
|
|
func (peer *LocalPeer) queryLoop(queryChan <-chan *PeerInteraction) {
|
|
gossipTimer := time.Tick(GossipInterval)
|
|
for {
|
|
select {
|
|
case query, ok := <-queryChan:
|
|
if !ok {
|
|
return
|
|
}
|
|
switch query.code {
|
|
case PAddConnection:
|
|
conn := query.payload.(*LocalConnection)
|
|
if peer.handleAddConnection(conn) {
|
|
conn.log("connection added")
|
|
}
|
|
query.resultChan <- nil
|
|
case PConnectionEstablished:
|
|
conn := query.payload.(*LocalConnection)
|
|
if peer.handleConnectionEstablished(conn) {
|
|
conn.log("connection fully established")
|
|
}
|
|
case PDeleteConnection:
|
|
conn := query.payload.(*LocalConnection)
|
|
if peer.handleDeleteConnection(conn) {
|
|
conn.log("connection deleted")
|
|
}
|
|
query.resultChan <- nil
|
|
case PSendProtocolMsg:
|
|
peer.handleSendProtocolMsg(query.payload.(ProtocolMsg))
|
|
}
|
|
case <-gossipTimer:
|
|
peer.Router.SendAllGossip()
|
|
}
|
|
}
|
|
}
|
|
|
|
func (peer *LocalPeer) handleAddConnection(conn Connection) bool {
|
|
if peer.Peer != conn.Local() {
|
|
log.Fatal("Attempt made to add connection to peer where peer is not the source of connection")
|
|
}
|
|
if conn.Remote() == nil {
|
|
log.Fatal("Attempt made to add connection to peer with unknown remote peer")
|
|
}
|
|
toName := conn.Remote().Name
|
|
dupErr := fmt.Errorf("Multiple connections to %s added to %s", toName, peer.Name)
|
|
// deliberately non symmetrical
|
|
if dupConn, found := peer.connections[toName]; found {
|
|
if dupConn == conn {
|
|
return false
|
|
}
|
|
switch conn.BreakTie(dupConn) {
|
|
case TieBreakWon:
|
|
dupConn.Shutdown(dupErr)
|
|
peer.handleDeleteConnection(dupConn)
|
|
case TieBreakLost:
|
|
conn.Shutdown(dupErr)
|
|
return false
|
|
case TieBreakTied:
|
|
// oh good grief. Sod it, just kill both of them.
|
|
conn.Shutdown(dupErr)
|
|
dupConn.Shutdown(dupErr)
|
|
peer.handleDeleteConnection(dupConn)
|
|
return false
|
|
}
|
|
}
|
|
if err := peer.checkConnectionLimit(); err != nil {
|
|
conn.Shutdown(err)
|
|
return false
|
|
}
|
|
peer.addConnection(conn)
|
|
peer.Router.SendAllGossipDown(conn)
|
|
peer.broadcastPeerUpdate(conn.Remote())
|
|
return true
|
|
}
|
|
|
|
func (peer *LocalPeer) handleConnectionEstablished(conn Connection) bool {
|
|
if peer.Peer != conn.Local() {
|
|
log.Fatal("Peer informed of active connection where peer is not the source of connection")
|
|
}
|
|
if dupConn, found := peer.connections[conn.Remote().Name]; !found || conn != dupConn {
|
|
conn.Shutdown(fmt.Errorf("Cannot set unknown connection active"))
|
|
return false
|
|
}
|
|
peer.connectionEstablished(conn)
|
|
peer.broadcastPeerUpdate()
|
|
return true
|
|
}
|
|
|
|
func (peer *LocalPeer) handleDeleteConnection(conn Connection) bool {
|
|
if peer.Peer != conn.Local() {
|
|
log.Fatal("Attempt made to delete connection from peer where peer is not the source of connection")
|
|
}
|
|
if conn.Remote() == nil {
|
|
log.Fatal("Attempt made to delete connection to peer with unknown remote peer")
|
|
}
|
|
toName := conn.Remote().Name
|
|
if connFound, found := peer.connections[toName]; !found || connFound != conn {
|
|
return false
|
|
}
|
|
peer.deleteConnection(conn)
|
|
// Must do garbage collection first to ensure we don't send out an
|
|
// update with unreachable peers (can cause looping)
|
|
peer.Router.Peers.GarbageCollect()
|
|
peer.broadcastPeerUpdate()
|
|
return true
|
|
}
|
|
|
|
func (peer *LocalPeer) handleSendProtocolMsg(m ProtocolMsg) {
|
|
peer.ForEachConnection(func(_ PeerName, conn Connection) {
|
|
conn.(ProtocolSender).SendProtocolMsg(m)
|
|
})
|
|
}
|
|
|
|
func (peer *LocalPeer) broadcastPeerUpdate(peers ...*Peer) {
|
|
peer.Router.Routes.Recalculate()
|
|
// TODO We should just be invoking TopologyGossip.GossipBroadcast
|
|
// here, but route calculation is asynchronous and in this
|
|
// particular case would likely result in the broadcast not
|
|
// reaching all peers. So instead we slightly break the Gossip
|
|
// abstraction (hence the cast) and send a regular update. This is
|
|
// less efficient though since it will almost certainly reach
|
|
// peers more than once.
|
|
peer.Router.TopologyGossip.(*GossipChannel).SendGossipMsg(EncodePeers(append(peers, peer.Peer)...))
|
|
}
|
|
|
|
func (peer *LocalPeer) checkConnectionLimit() error {
|
|
if 0 != peer.Router.ConnLimit && peer.ConnectionCount() >= peer.Router.ConnLimit {
|
|
return fmt.Errorf("Connection limit reached (%v)", peer.Router.ConnLimit)
|
|
}
|
|
return nil
|
|
}
|