This is more efficient since broadcasting about other peers is strictly redundant. Any change to the peers will be broadcast by those peers anyway. And SendAllGossipDown takes care of informing new peers about others on first connect, and periodic gossip take care of missed broadcasts. The only reason we did broadcast about the remote peer is to reduce the liklihood of UnknownPeerError and the associated throwing away of gossip. This was always a bit of kludge, and is no longer necessary since UnknownPeerError is gone. Now peers only ever broadcast information about themselves. Which, unlike information about other peers, is always authorative and most up-to-date. Which is nice.
262 lines
6.4 KiB
Go
262 lines
6.4 KiB
Go
package mesh
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
type LocalPeer struct {
|
|
sync.RWMutex
|
|
*Peer
|
|
router *Router
|
|
actionChan chan<- LocalPeerAction
|
|
}
|
|
|
|
type LocalPeerAction func()
|
|
|
|
func NewLocalPeer(name PeerName, nickName string, router *Router) *LocalPeer {
|
|
actionChan := make(chan LocalPeerAction, ChannelSize)
|
|
peer := &LocalPeer{
|
|
Peer: NewPeer(name, nickName, randomPeerUID(), 0, randomPeerShortID()),
|
|
router: router,
|
|
actionChan: actionChan,
|
|
}
|
|
go peer.actorLoop(actionChan)
|
|
return peer
|
|
}
|
|
|
|
func (peer *LocalPeer) Connections() ConnectionSet {
|
|
connections := make(ConnectionSet)
|
|
peer.RLock()
|
|
defer peer.RUnlock()
|
|
for _, conn := range peer.connections {
|
|
connections[conn] = void
|
|
}
|
|
return connections
|
|
}
|
|
|
|
func (peer *LocalPeer) ConnectionTo(name PeerName) (Connection, bool) {
|
|
peer.RLock()
|
|
defer peer.RUnlock()
|
|
conn, found := peer.connections[name]
|
|
return conn, found // yes, you really can't inline that. FFS.
|
|
}
|
|
|
|
func (peer *LocalPeer) ConnectionsTo(names []PeerName) []Connection {
|
|
conns := make([]Connection, 0, len(names))
|
|
peer.RLock()
|
|
defer peer.RUnlock()
|
|
for _, name := range names {
|
|
conn, found := peer.connections[name]
|
|
// Again, !found could just be due to a race.
|
|
if found {
|
|
conns = append(conns, conn)
|
|
}
|
|
}
|
|
return conns
|
|
}
|
|
|
|
func (peer *LocalPeer) CreateConnection(peerAddr string, acceptNewPeer bool) error {
|
|
if err := peer.checkConnectionLimit(); err != nil {
|
|
return err
|
|
}
|
|
tcpAddr, err := net.ResolveTCPAddr("tcp4", peerAddr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
tcpConn, err := net.DialTCP("tcp4", nil, tcpAddr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
connRemote := NewRemoteConnection(peer.Peer, nil, tcpConn.RemoteAddr().String(), true, false)
|
|
StartLocalConnection(connRemote, tcpConn, peer.router, acceptNewPeer)
|
|
return nil
|
|
}
|
|
|
|
// ACTOR client API
|
|
|
|
// Sync.
|
|
func (peer *LocalPeer) AddConnection(conn *LocalConnection) error {
|
|
resultChan := make(chan error)
|
|
peer.actionChan <- func() {
|
|
resultChan <- peer.handleAddConnection(conn)
|
|
}
|
|
return <-resultChan
|
|
}
|
|
|
|
// Async.
|
|
func (peer *LocalPeer) ConnectionEstablished(conn *LocalConnection) {
|
|
peer.actionChan <- func() {
|
|
peer.handleConnectionEstablished(conn)
|
|
}
|
|
}
|
|
|
|
// Sync.
|
|
func (peer *LocalPeer) DeleteConnection(conn *LocalConnection) {
|
|
resultChan := make(chan interface{})
|
|
peer.actionChan <- func() {
|
|
peer.handleDeleteConnection(conn)
|
|
resultChan <- nil
|
|
}
|
|
<-resultChan
|
|
}
|
|
|
|
// ACTOR server
|
|
|
|
func (peer *LocalPeer) actorLoop(actionChan <-chan LocalPeerAction) {
|
|
gossipTimer := time.Tick(GossipInterval)
|
|
for {
|
|
select {
|
|
case action := <-actionChan:
|
|
action()
|
|
case <-gossipTimer:
|
|
peer.router.SendAllGossip()
|
|
}
|
|
}
|
|
}
|
|
|
|
func (peer *LocalPeer) handleAddConnection(conn Connection) error {
|
|
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", conn.Remote(), peer.String())
|
|
// deliberately non symmetrical
|
|
if dupConn, found := peer.connections[toName]; found {
|
|
if dupConn == conn {
|
|
return nil
|
|
}
|
|
switch conn.BreakTie(dupConn) {
|
|
case TieBreakWon:
|
|
dupConn.Shutdown(dupErr)
|
|
peer.handleDeleteConnection(dupConn)
|
|
case TieBreakLost:
|
|
return dupErr
|
|
case TieBreakTied:
|
|
// oh good grief. Sod it, just kill both of them.
|
|
dupConn.Shutdown(dupErr)
|
|
peer.handleDeleteConnection(dupConn)
|
|
return dupErr
|
|
}
|
|
}
|
|
if err := peer.checkConnectionLimit(); err != nil {
|
|
return err
|
|
}
|
|
_, isConnectedPeer := peer.router.Routes.Unicast(toName)
|
|
peer.addConnection(conn)
|
|
if isConnectedPeer {
|
|
conn.Log("connection added")
|
|
} else {
|
|
conn.Log("connection added (new peer)")
|
|
peer.router.SendAllGossipDown(conn)
|
|
}
|
|
|
|
peer.router.Routes.Recalculate()
|
|
peer.broadcastPeerUpdate()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (peer *LocalPeer) handleConnectionEstablished(conn Connection) {
|
|
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
|
|
}
|
|
peer.connectionEstablished(conn)
|
|
conn.Log("connection fully established")
|
|
|
|
peer.router.Routes.Recalculate()
|
|
peer.broadcastPeerUpdate()
|
|
}
|
|
|
|
func (peer *LocalPeer) handleDeleteConnection(conn Connection) {
|
|
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
|
|
}
|
|
peer.deleteConnection(conn)
|
|
conn.Log("connection deleted")
|
|
// 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.router.Routes.Recalculate()
|
|
peer.broadcastPeerUpdate()
|
|
}
|
|
|
|
// helpers
|
|
|
|
func (peer *LocalPeer) broadcastPeerUpdate() {
|
|
// Some tests run without a router. This should be fixed so
|
|
// that the relevant part of Router can be easily run in the
|
|
// context of a test, but that will involve significant
|
|
// reworking of tests.
|
|
if peer.router != nil {
|
|
peer.router.BroadcastTopologyUpdate([]*Peer{peer.Peer})
|
|
}
|
|
}
|
|
|
|
func (peer *LocalPeer) checkConnectionLimit() error {
|
|
limit := peer.router.ConnLimit
|
|
if 0 != limit && peer.connectionCount() >= limit {
|
|
return fmt.Errorf("Connection limit reached (%v)", limit)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (peer *LocalPeer) addConnection(conn Connection) {
|
|
peer.Lock()
|
|
defer peer.Unlock()
|
|
peer.connections[conn.Remote().Name] = conn
|
|
peer.Version++
|
|
}
|
|
|
|
func (peer *LocalPeer) deleteConnection(conn Connection) {
|
|
peer.Lock()
|
|
defer peer.Unlock()
|
|
delete(peer.connections, conn.Remote().Name)
|
|
peer.Version++
|
|
}
|
|
|
|
func (peer *LocalPeer) connectionEstablished(conn Connection) {
|
|
peer.Lock()
|
|
defer peer.Unlock()
|
|
peer.Version++
|
|
}
|
|
|
|
func (peer *LocalPeer) connectionCount() int {
|
|
peer.RLock()
|
|
defer peer.RUnlock()
|
|
return len(peer.connections)
|
|
}
|
|
|
|
func (peer *LocalPeer) setShortID(shortID PeerShortID) {
|
|
peer.Lock()
|
|
defer peer.Unlock()
|
|
peer.ShortID = shortID
|
|
peer.Version++
|
|
}
|
|
|
|
func (peer *LocalPeer) setVersionBeyond(version uint64) bool {
|
|
peer.Lock()
|
|
defer peer.Unlock()
|
|
if version >= peer.Version {
|
|
peer.Version = version + 1
|
|
return true
|
|
}
|
|
return false
|
|
}
|