Unify route and zone tables

This commit is contained in:
Josh Deprez 2024-05-05 17:01:23 +10:00
parent 8ce8f52776
commit 7e9fe4ff98
Signed by: josh
SSH key fingerprint: SHA256:zZji7w1Ilh2RuUpbQcqkLPrqmRwpiCSycbF2EfKm6Kw
7 changed files with 112 additions and 179 deletions

37
main.go
View file

@ -50,6 +50,7 @@ const routingTableTemplate = `
<thead><tr>
<th>Network range</th>
<th>Extended?</th>
<th>Zone names</th>
<th>Distance</th>
<th>Last seen</th>
<th>Port</th>
@ -58,7 +59,8 @@ const routingTableTemplate = `
{{range $route := . }}
<tr>
<td>{{$route.NetStart}}{{if not (eq $route.NetStart $route.NetEnd)}} - {{$route.NetEnd}}{{end}}</td>
<td>{{if $route.Extended}}{{else}}{{end}}</td>
<td>{{if $route.Extended}}{{else}}-{{end}}</td>
<td>{{range $route.ZoneNames}}{{.}}<br>{{end}}</td>
<td>{{$route.Distance}}</td>
<td>{{$route.LastSeenAgo}}</td>
<td>
@ -220,14 +222,14 @@ func main() {
return rs, nil
})
zones := router.NewZoneTable()
status.AddItem(ctx, "Zone table", zoneTableTemplate, func(context.Context) (any, error) {
zs := zones.Dump()
slices.SortFunc(zs, func(za, zb router.Zone) int {
return cmp.Compare(za.Name, zb.Name)
})
return zs, nil
})
// zones := router.NewZoneTable()
// status.AddItem(ctx, "Zone table", zoneTableTemplate, func(context.Context) (any, error) {
// zs := zones.Dump()
// slices.SortFunc(zs, func(za, zb router.Zone) int {
// return cmp.Compare(za.Name, zb.Name)
// })
// return zs, nil
// })
// -------------------------------- Peers ---------------------------------
var peersMu sync.Mutex
@ -326,8 +328,7 @@ func main() {
ConfiguredAddr: peerStr,
RemoteAddr: raddr,
ReceiveCh: make(chan aurp.Packet, 1024),
RoutingTable: routes,
ZoneTable: zones,
RouteTable: routes,
}
aurp.Inc(&nextConnID)
peersMu.Lock()
@ -344,7 +345,7 @@ func main() {
rooter := &router.Router{
Config: cfg,
RouteTable: routes,
ZoneTable: zones,
// ZoneTable: zones,
}
etherTalkPort := &router.EtherTalkPort{
@ -360,9 +361,6 @@ func main() {
}
rooter.Ports = append(rooter.Ports, etherTalkPort)
routes.InsertEtherTalkDirect(etherTalkPort)
for _, az := range etherTalkPort.AvailableZones {
zones.Upsert(etherTalkPort.NetStart, az, etherTalkPort)
}
// --------------------------------- RTMP ---------------------------------
go etherTalkPort.RunRTMP(ctx)
@ -434,11 +432,10 @@ func main() {
RemoteDI: dh.SourceDI, // platinum rule
LocalConnID: nextConnID,
},
UDPConn: ln,
RemoteAddr: raddr,
ReceiveCh: make(chan aurp.Packet, 1024),
RoutingTable: routes,
ZoneTable: zones,
UDPConn: ln,
RemoteAddr: raddr,
ReceiveCh: make(chan aurp.Packet, 1024),
RouteTable: routes,
}
aurp.Inc(&nextConnID)
peers[ra] = pr

View file

@ -74,10 +74,10 @@ func (port *EtherTalkPort) handleNBPBrRq(ctx context.Context, ddpkt *ddp.ExtPack
// tuple.Zone = port.DefaultZoneName
// }
zones := port.Router.ZoneTable.LookupName(tuple.Zone)
routes := port.Router.RouteTable.RoutesForZone(tuple.Zone)
for _, z := range zones {
if outPort := z.LocalPort; outPort != nil {
for _, route := range routes {
if outPort := route.EtherTalkDirect; outPort != nil {
// If it's for a local zone, translate it to a LkUp and broadcast
// out the corresponding EtherTalk port.
// "Note: On an internet, nodes on extended networks performing lookups in
@ -147,7 +147,7 @@ func (port *EtherTalkPort) handleNBPBrRq(ctx context.Context, ddpkt *ddp.ExtPack
SrcNet: ddpkt.SrcNet,
SrcNode: ddpkt.SrcNode,
SrcSocket: ddpkt.SrcSocket,
DstNet: z.Network,
DstNet: route.NetStart,
DstNode: 0x00, // Any router for the dest network
DstSocket: 2,
Proto: ddp.ProtoNBP,

View file

@ -114,11 +114,8 @@ type AURPPeer struct {
// Incoming packet channel.
ReceiveCh chan aurp.Packet
// Routing table (the peer will add/remove/update routes)
RoutingTable *RouteTable
// Zone table (the peer will add/remove/update zones)
ZoneTable *ZoneTable
// Route table (the peer will add/remove/update routes and zones)
RouteTable *RouteTable
mu sync.RWMutex
rstate ReceiverState
@ -253,7 +250,7 @@ func (p *AURPPeer) Handle(ctx context.Context) error {
if sendRetries >= tickleRetryLimit {
log.Printf("AURP Peer: Send retry limit reached while waiting for Tickle-Ack, closing connection")
p.setRState(ReceiverUnconnected)
p.RoutingTable.DeleteAURPPeer(p)
p.RouteTable.DeleteAURPPeer(p)
break
}
@ -272,7 +269,7 @@ func (p *AURPPeer) Handle(ctx context.Context) error {
if sendRetries >= sendRetryLimit {
log.Printf("AURP Peer: Send retry limit reached while waiting for RI-Rsp, closing connection")
p.setRState(ReceiverUnconnected)
p.RoutingTable.DeleteAURPPeer(p)
p.RouteTable.DeleteAURPPeer(p)
break
}
@ -467,7 +464,7 @@ func (p *AURPPeer) Handle(ctx context.Context) error {
log.Printf("AURP Peer: Learned about these networks: %v", pkt.Networks)
for _, nt := range pkt.Networks {
p.RoutingTable.InsertAURPRoute(
p.RouteTable.InsertAURPRoute(
p,
nt.Extended,
ddp.Network(nt.RangeStart),
@ -543,7 +540,7 @@ func (p *AURPPeer) Handle(ctx context.Context) error {
// Do nothing except respond with RI-Ack
case aurp.EventCodeNA:
if err := p.RoutingTable.InsertAURPRoute(
if err := p.RouteTable.InsertAURPRoute(
p,
et.Extended,
et.RangeStart,
@ -555,10 +552,10 @@ func (p *AURPPeer) Handle(ctx context.Context) error {
ackFlag = aurp.RoutingFlagSendZoneInfo
case aurp.EventCodeND:
p.RoutingTable.DeleteAURPPeerNetwork(p, et.RangeStart)
p.RouteTable.DeleteAURPPeerNetwork(p, et.RangeStart)
case aurp.EventCodeNDC:
p.RoutingTable.UpdateAURPRouteDistance(p, et.RangeStart, et.Distance+1)
p.RouteTable.UpdateAURPRouteDistance(p, et.RangeStart, et.Distance+1)
case aurp.EventCodeNRC:
// "An exterior router sends a Network Route Change
@ -566,7 +563,7 @@ func (p *AURPPeer) Handle(ctx context.Context) error {
// through its local internet changes to a path through
// a tunneling port, causing split-horizoned processing
// to eliminate that networks routing information."
p.RoutingTable.DeleteAURPPeerNetwork(p, et.RangeStart)
p.RouteTable.DeleteAURPPeerNetwork(p, et.RangeStart)
case aurp.EventCodeZC:
// "This event is reserved for future use."
@ -584,7 +581,7 @@ func (p *AURPPeer) Handle(ctx context.Context) error {
}
log.Printf("AURP Peer: Router Down: error code %d %s", pkt.ErrorCode, pkt.ErrorCode)
p.RoutingTable.DeleteAURPPeer(p)
p.RouteTable.DeleteAURPPeer(p)
// Respond with RI-Ack
if _, err := p.Send(p.Transport.NewRIAckPacket(pkt.ConnectionID, pkt.Sequence, 0)); err != nil {
@ -596,7 +593,7 @@ func (p *AURPPeer) Handle(ctx context.Context) error {
case *aurp.ZIReqPacket:
// TODO: split ZI-Rsp packets similarly to ZIP Replies
zones := p.ZoneTable.Query(pkt.Networks)
zones := p.RouteTable.ZonesForNetworks(pkt.Networks)
if _, err := p.Send(p.Transport.NewZIRspPacket(zones)); err != nil {
log.Printf("AURP Peer: Couldn't send ZI-Rsp packet: %v", err)
return err
@ -605,7 +602,7 @@ func (p *AURPPeer) Handle(ctx context.Context) error {
case *aurp.ZIRspPacket:
log.Printf("AURP Peer: Learned about these zones: %v", pkt.Zones)
for _, zt := range pkt.Zones {
p.ZoneTable.Upsert(ddp.Network(zt.Network), zt.Name, nil)
p.RouteTable.AddZoneToNetwork(zt.Network, zt.Name)
}
case *aurp.GDZLReqPacket:

View file

@ -34,6 +34,10 @@ type Route struct {
LastSeen time.Time
// ZoneNames may be empty between learning the existence of a route and
// receiving zone information.
ZoneNames []string
// Exactly one of the following should be set
AURPPeer *AURPPeer // Next hop is this peer router (over AURP)
EtherTalkPeer *EtherTalkPeer // Next hop is this peer router (over EtherTalk)
@ -47,6 +51,10 @@ func (r Route) LastSeenAgo() string {
return fmt.Sprintf("%v ago", time.Since(r.LastSeen).Truncate(time.Millisecond))
}
func (r *Route) Valid() bool {
return r.EtherTalkPeer == nil || time.Since(r.LastSeen) <= maxRouteAge
}
type RouteTable struct {
mu sync.Mutex
routes map[*Route]struct{}
@ -65,6 +73,7 @@ func (rt *RouteTable) InsertEtherTalkDirect(port *EtherTalkPort) {
NetEnd: port.NetEnd,
Distance: 0, // we're connected directly
LastSeen: time.Now(),
ZoneNames: port.AvailableZones,
EtherTalkDirect: port,
}
@ -93,8 +102,7 @@ func (rt *RouteTable) LookupRoute(network ddp.Network) *Route {
if network < r.NetStart || network > r.NetEnd {
continue
}
// Exclude EtherTalk routes that are too old
if r.EtherTalkPeer != nil && time.Since(r.LastSeen) > maxRouteAge {
if !r.Valid() {
continue
}
if bestRoute == nil {
@ -213,11 +221,9 @@ func (rt *RouteTable) ValidRoutes() []*Route {
defer rt.mu.Unlock()
valid := make([]*Route, 0, len(rt.routes))
for r := range rt.routes {
// Exclude EtherTalk routes that are too old
if r.EtherTalkPeer != nil && time.Since(r.LastSeen) > maxRouteAge {
continue
if r.Valid() {
valid = append(valid, r)
}
valid = append(valid, r)
}
return valid
}

View file

@ -26,7 +26,6 @@ import (
type Router struct {
Config *Config
RouteTable *RouteTable
ZoneTable *ZoneTable
Ports []*EtherTalkPort
}

View file

@ -62,7 +62,7 @@ func (port *EtherTalkPort) handleZIPZIP(ctx context.Context, ddpkt *ddp.ExtPacke
func (port *EtherTalkPort) handleZIPQuery(ctx context.Context, ddpkt *ddp.ExtPacket, zipkt *zip.QueryPacket) error {
log.Printf("ZIP: Got Query for networks %v", zipkt.Networks)
networks := port.Router.ZoneTable.Query(zipkt.Networks)
networks := port.Router.RouteTable.ZonesForNetworks(zipkt.Networks)
sendReply := func(resp *zip.ReplyPacket) error {
respRaw, err := resp.Marshal()
@ -258,7 +258,7 @@ func (port *EtherTalkPort) handleZIPTReq(ctx context.Context, ddpkt *ddp.ExtPack
switch gzl.Function {
case zip.FunctionGetZoneList:
resp.Zones = port.Router.ZoneTable.AllNames()
resp.Zones = port.Router.RouteTable.AllZoneNames()
case zip.FunctionGetLocalZones:
resp.Zones = port.AvailableZones

View file

@ -17,145 +17,79 @@
package router
import (
"fmt"
"slices"
"sort"
"sync"
"time"
"github.com/sfiera/multitalk/pkg/ddp"
)
//const maxZoneAge = 10 * time.Minute // TODO: confirm
type Zone struct {
Network ddp.Network
Name string
LocalPort *EtherTalkPort // nil if remote (local to another router)
LastSeen time.Time
}
func (z Zone) LastSeenAgo() string {
if z.LastSeen.IsZero() {
return "never"
}
return fmt.Sprintf("%v ago", time.Since(z.LastSeen).Truncate(time.Millisecond))
}
type zoneKey struct {
network ddp.Network
name string
}
type ZoneTable struct {
mu sync.Mutex
zones map[zoneKey]*Zone
}
func NewZoneTable() *ZoneTable {
return &ZoneTable{
zones: make(map[zoneKey]*Zone),
}
}
func (zt *ZoneTable) Dump() []Zone {
zt.mu.Lock()
defer zt.mu.Unlock()
zs := make([]Zone, 0, len(zt.zones))
for _, z := range zt.zones {
zs = append(zs, *z)
}
return zs
}
func (zt *ZoneTable) Upsert(network ddp.Network, name string, localPort *EtherTalkPort) {
zt.mu.Lock()
defer zt.mu.Unlock()
key := zoneKey{network, name}
z := zt.zones[key]
if z != nil {
z.LocalPort = localPort
z.LastSeen = time.Now()
return
}
zt.zones[key] = &Zone{
Network: network,
Name: name,
LocalPort: localPort,
LastSeen: time.Now(),
}
}
func (zt *ZoneTable) Query(ns []ddp.Network) map[ddp.Network][]string {
slices.Sort(ns)
zs := make(map[ddp.Network][]string)
zt.mu.Lock()
defer zt.mu.Unlock()
for _, z := range zt.zones {
// if time.Since(z.LastSeen) > maxZoneAge {
// continue
// }
if _, ok := slices.BinarySearch(ns, z.Network); ok {
zs[z.Network] = append(zs[z.Network], z.Name)
}
}
return zs
}
func (zt *ZoneTable) LookupName(name string) []*Zone {
zt.mu.Lock()
defer zt.mu.Unlock()
var zs []*Zone
for _, z := range zt.zones {
if z.Name == name {
zs = append(zs, z)
}
}
return zs
}
// func (zt *ZoneTable) LocalNames() []string {
// zt.mu.Lock()
// seen := make(map[string]struct{})
// zs := make([]string, 0, len(zt.zones))
// for _, z := range zt.zones {
// // if time.Since(z.LastSeen) > maxZoneAge {
// // continue
// // }
// if z.Local != nil {
// continue
// }
// if _, s := seen[z.Name]; s {
// continue
// }
// seen[z.Name] = struct{}{}
// zs = append(zs, z.Name)
// }
// zt.mu.Unlock()
// sort.Strings(zs)
// return zs
// }
func (zt *ZoneTable) AllNames() []string {
zt.mu.Lock()
seen := make(map[string]struct{})
zs := make([]string, 0, len(zt.zones))
for _, z := range zt.zones {
// if time.Since(z.LastSeen) > maxZoneAge {
// continue
// }
if _, s := seen[z.Name]; s {
func (rt *RouteTable) AddZoneToNetwork(n ddp.Network, z string) {
rt.mu.Lock()
defer rt.mu.Unlock()
for r := range rt.routes {
if n < r.NetStart || n > r.NetEnd {
continue
}
seen[z.Name] = struct{}{}
zs = append(zs, z.Name)
if !r.Valid() {
continue
}
if slices.Contains(r.ZoneNames, z) {
continue
}
r.ZoneNames = append(r.ZoneNames, z)
}
zt.mu.Unlock()
}
sort.Strings(zs)
func (rt *RouteTable) ZonesForNetworks(ns []ddp.Network) map[ddp.Network][]string {
zs := make(map[ddp.Network][]string)
rt.mu.Lock()
defer rt.mu.Unlock()
for r := range rt.routes {
if !r.Valid() {
continue
}
if _, ok := slices.BinarySearch(ns, r.NetStart); ok {
zs[r.NetStart] = append(zs[r.NetStart], r.ZoneNames...)
}
}
return zs
}
func (rt *RouteTable) RoutesForZone(zone string) []*Route {
rt.mu.Lock()
defer rt.mu.Unlock()
var routes []*Route
for r := range rt.routes {
if !r.Valid() {
continue
}
if slices.Contains(r.ZoneNames, zone) {
routes = append(routes, r)
}
}
return routes
}
func (rt *RouteTable) AllZoneNames() (zones []string) {
defer slices.Sort(zones)
rt.mu.Lock()
defer rt.mu.Unlock()
seen := make(map[string]struct{})
for r := range rt.routes {
if !r.Valid() {
continue
}
for _, z := range r.ZoneNames {
if _, s := seen[z]; s {
continue
}
seen[z] = struct{}{}
zones = append(zones, z)
}
}
return zones
}