PeerNameSet -> []PeerName, which is what the other functions return. This is also easier to deal with at the call site.
277 lines
7.2 KiB
Go
277 lines
7.2 KiB
Go
package mesh
|
|
|
|
import (
|
|
"math"
|
|
"sync"
|
|
)
|
|
|
|
type unicastRoutes map[PeerName]PeerName
|
|
type broadcastRoutes map[PeerName][]PeerName
|
|
|
|
type Routes struct {
|
|
sync.RWMutex
|
|
ourself *LocalPeer
|
|
peers *Peers
|
|
onChange []func()
|
|
unicast unicastRoutes
|
|
unicastAll unicastRoutes // [1]
|
|
broadcast broadcastRoutes
|
|
broadcastAll broadcastRoutes // [1]
|
|
recalculate chan<- *struct{}
|
|
wait chan<- chan struct{}
|
|
// [1] based on *all* connections, not just established &
|
|
// symmetric ones
|
|
}
|
|
|
|
func NewRoutes(ourself *LocalPeer, peers *Peers) *Routes {
|
|
recalculate := make(chan *struct{}, 1)
|
|
wait := make(chan chan struct{})
|
|
routes := &Routes{
|
|
ourself: ourself,
|
|
peers: peers,
|
|
unicast: make(unicastRoutes),
|
|
unicastAll: make(unicastRoutes),
|
|
broadcast: make(broadcastRoutes),
|
|
broadcastAll: make(broadcastRoutes),
|
|
recalculate: recalculate,
|
|
wait: wait}
|
|
routes.unicast[ourself.Name] = UnknownPeerName
|
|
routes.unicastAll[ourself.Name] = UnknownPeerName
|
|
routes.broadcast[ourself.Name] = []PeerName{}
|
|
routes.broadcastAll[ourself.Name] = []PeerName{}
|
|
go routes.run(recalculate, wait)
|
|
return routes
|
|
}
|
|
|
|
func (routes *Routes) OnChange(callback func()) {
|
|
routes.Lock()
|
|
defer routes.Unlock()
|
|
routes.onChange = append(routes.onChange, callback)
|
|
}
|
|
|
|
func (routes *Routes) PeerNames() PeerNameSet {
|
|
return routes.peers.Names()
|
|
}
|
|
|
|
func (routes *Routes) Unicast(name PeerName) (PeerName, bool) {
|
|
routes.RLock()
|
|
defer routes.RUnlock()
|
|
hop, found := routes.unicast[name]
|
|
return hop, found
|
|
}
|
|
|
|
func (routes *Routes) UnicastAll(name PeerName) (PeerName, bool) {
|
|
routes.RLock()
|
|
defer routes.RUnlock()
|
|
hop, found := routes.unicastAll[name]
|
|
return hop, found
|
|
}
|
|
|
|
func (routes *Routes) Broadcast(name PeerName) []PeerName {
|
|
routes.RLock()
|
|
defer routes.RUnlock()
|
|
return routes.broadcast[name]
|
|
}
|
|
|
|
func (routes *Routes) BroadcastAll(name PeerName) []PeerName {
|
|
routes.RLock()
|
|
defer routes.RUnlock()
|
|
return routes.broadcastAll[name]
|
|
}
|
|
|
|
// Choose min(log2(n_peers), n_neighbouring_peers) neighbours, with a
|
|
// random distribution that is topology-sensitive, favouring
|
|
// neighbours at the end of "bottleneck links". We determine the
|
|
// latter based on the unicast routing table. If a neighbour appears
|
|
// as the value more frequently than others - meaning that we reach a
|
|
// higher proportion of peers via that neighbour than other neighbours
|
|
// - then it is chosen with a higher probability.
|
|
//
|
|
// Note that we choose log2(n_peers) *neighbours*, not
|
|
// peers. Consequently, on sparsely connected peers this function
|
|
// returns a higher proportion of neighbours than elsewhere. In
|
|
// extremis, on peers with fewer than log2(n_peers) neighbours, all
|
|
// neighbours are returned.
|
|
func (routes *Routes) RandomNeighbours(except PeerName) []PeerName {
|
|
destinations := make(PeerNameSet)
|
|
routes.RLock()
|
|
defer routes.RUnlock()
|
|
count := int(math.Log2(float64(len(routes.unicastAll))))
|
|
// depends on go's random map iteration
|
|
for _, dst := range routes.unicastAll {
|
|
if dst != UnknownPeerName && dst != except {
|
|
destinations[dst] = void
|
|
if len(destinations) >= count {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
res := make([]PeerName, 0, len(destinations))
|
|
for dst := range destinations {
|
|
res = append(res, dst)
|
|
}
|
|
return res
|
|
}
|
|
|
|
// Request recalculation of the routing table. This is async but can
|
|
// effectively be made synchronous with a subsequent call to
|
|
// EnsureRecalculated.
|
|
func (routes *Routes) Recalculate() {
|
|
// The use of a 1-capacity channel in combination with the
|
|
// non-blocking send is an optimisation that results in multiple
|
|
// requests being coalesced.
|
|
select {
|
|
case routes.recalculate <- nil:
|
|
default:
|
|
}
|
|
}
|
|
|
|
// Wait for any preceding Recalculate requests to be processed.
|
|
func (routes *Routes) EnsureRecalculated() {
|
|
done := make(chan struct{})
|
|
routes.wait <- done
|
|
<-done
|
|
}
|
|
|
|
func (routes *Routes) run(recalculate <-chan *struct{}, wait <-chan chan struct{}) {
|
|
for {
|
|
select {
|
|
case <-recalculate:
|
|
routes.calculate()
|
|
case done := <-wait:
|
|
select {
|
|
case <-recalculate:
|
|
routes.calculate()
|
|
default:
|
|
}
|
|
close(done)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (routes *Routes) calculate() {
|
|
routes.peers.RLock()
|
|
routes.ourself.RLock()
|
|
var (
|
|
oldUnicast = routes.unicast
|
|
oldBroadcast = routes.broadcast
|
|
unicast = routes.calculateUnicast(true)
|
|
unicastAll = routes.calculateUnicast(false)
|
|
broadcast = routes.calculateBroadcast(true)
|
|
broadcastAll = routes.calculateBroadcast(false)
|
|
)
|
|
routes.ourself.RUnlock()
|
|
routes.peers.RUnlock()
|
|
|
|
routes.Lock()
|
|
routes.unicast = unicast
|
|
routes.unicastAll = unicastAll
|
|
routes.broadcast = broadcast
|
|
routes.broadcastAll = broadcastAll
|
|
onChange := routes.onChange
|
|
routes.Unlock()
|
|
|
|
if !unicast.equals(oldUnicast) || !broadcast.equals(oldBroadcast) {
|
|
for _, callback := range onChange {
|
|
callback()
|
|
}
|
|
}
|
|
}
|
|
|
|
// Calculate all the routes for the question: if *we* want to send a
|
|
// packet to Peer X, what is the next hop?
|
|
//
|
|
// When we sniff a packet, we determine the destination peer
|
|
// ourself. Consequently, we can relay the packet via any
|
|
// arbitrary peers - the intermediate peers do not have to have
|
|
// any knowledge of the MAC address at all. Thus there's no need
|
|
// to exchange knowledge of MAC addresses, nor any constraints on
|
|
// the routes that we construct.
|
|
func (routes *Routes) calculateUnicast(establishedAndSymmetric bool) unicastRoutes {
|
|
_, unicast := routes.ourself.Routes(nil, establishedAndSymmetric)
|
|
return unicast
|
|
}
|
|
|
|
// Calculate all the routes for the question: if we receive a
|
|
// broadcast originally from Peer X, which peers should we pass the
|
|
// frames on to?
|
|
//
|
|
// When the topology is stable, and thus all peers perform route
|
|
// calculations based on the same data, the algorithm ensures that
|
|
// broadcasts reach every peer exactly once.
|
|
//
|
|
// This is largely due to properties of the Peer.Routes algorithm. In
|
|
// particular:
|
|
//
|
|
// ForAll X,Y,Z in Peers.
|
|
// X.Routes(Y) <= X.Routes(Z) \/
|
|
// X.Routes(Z) <= X.Routes(Y)
|
|
// ForAll X,Y,Z in Peers.
|
|
// Y =/= Z /\ X.Routes(Y) <= X.Routes(Z) =>
|
|
// X.Routes(Y) u [P | Y.HasSymmetricConnectionTo(P)] <= X.Routes(Z)
|
|
// where <= is the subset relationship on keys of the returned map.
|
|
func (routes *Routes) calculateBroadcast(establishedAndSymmetric bool) broadcastRoutes {
|
|
broadcast := make(broadcastRoutes)
|
|
for _, peer := range routes.peers.byName {
|
|
hops := []PeerName{}
|
|
if found, reached := peer.Routes(routes.ourself.Peer, establishedAndSymmetric); found {
|
|
routes.ourself.ForEachConnectedPeer(establishedAndSymmetric, reached,
|
|
func(remotePeer *Peer) { hops = append(hops, remotePeer.Name) })
|
|
}
|
|
broadcast[peer.Name] = hops
|
|
}
|
|
return broadcast
|
|
}
|
|
|
|
func (a unicastRoutes) equals(b unicastRoutes) bool {
|
|
for key, aval := range a {
|
|
if bval, ok := b[key]; !ok || bval != aval {
|
|
return false
|
|
}
|
|
}
|
|
|
|
for key := range b {
|
|
if _, ok := a[key]; !ok {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func (a broadcastRoutes) equals(b broadcastRoutes) bool {
|
|
set := make(map[PeerName]struct{})
|
|
|
|
for key, aval := range a {
|
|
bval, ok := b[key]
|
|
if !ok {
|
|
return false
|
|
}
|
|
|
|
for _, peer := range aval {
|
|
set[peer] = struct{}{}
|
|
}
|
|
|
|
for _, peer := range bval {
|
|
if _, ok := set[peer]; !ok {
|
|
return false
|
|
}
|
|
|
|
delete(set, peer)
|
|
}
|
|
|
|
if len(set) != 0 {
|
|
return false
|
|
}
|
|
|
|
}
|
|
|
|
for key := range b {
|
|
if _, ok := a[key]; !ok {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|