From 7e9fe4ff985475ed42f42638199a1ebe36c88251 Mon Sep 17 00:00:00 2001 From: Josh Deprez Date: Sun, 5 May 2024 17:01:23 +1000 Subject: [PATCH 1/6] Unify route and zone tables --- main.go | 37 ++++----- router/nbp.go | 8 +- router/peer_aurp.go | 27 +++--- router/route.go | 18 ++-- router/router.go | 1 - router/zip.go | 4 +- router/zones.go | 196 +++++++++++++++----------------------------- 7 files changed, 112 insertions(+), 179 deletions(-) diff --git a/main.go b/main.go index 59932e7..ff6809c 100644 --- a/main.go +++ b/main.go @@ -50,6 +50,7 @@ const routingTableTemplate = ` Network range Extended? + Zone names Distance Last seen Port @@ -58,7 +59,8 @@ const routingTableTemplate = ` {{range $route := . }} {{$route.NetStart}}{{if not (eq $route.NetStart $route.NetEnd)}} - {{$route.NetEnd}}{{end}} - {{if $route.Extended}}✅{{else}}❌{{end}} + {{if $route.Extended}}✅{{else}}-{{end}} + {{range $route.ZoneNames}}{{.}}
{{end}} {{$route.Distance}} {{$route.LastSeenAgo}} @@ -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 diff --git a/router/nbp.go b/router/nbp.go index 673b9d3..575b9f1 100644 --- a/router/nbp.go +++ b/router/nbp.go @@ -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, diff --git a/router/peer_aurp.go b/router/peer_aurp.go index 85432cc..0aca253 100644 --- a/router/peer_aurp.go +++ b/router/peer_aurp.go @@ -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 network’s 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: diff --git a/router/route.go b/router/route.go index a399836..1042a34 100644 --- a/router/route.go +++ b/router/route.go @@ -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 } diff --git a/router/router.go b/router/router.go index 7295076..76f2fab 100644 --- a/router/router.go +++ b/router/router.go @@ -26,7 +26,6 @@ import ( type Router struct { Config *Config RouteTable *RouteTable - ZoneTable *ZoneTable Ports []*EtherTalkPort } diff --git a/router/zip.go b/router/zip.go index 669e1f4..ed52f5b 100644 --- a/router/zip.go +++ b/router/zip.go @@ -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 diff --git a/router/zones.go b/router/zones.go index f1d0d74..5f8ca75 100644 --- a/router/zones.go +++ b/router/zones.go @@ -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 +} -- 2.43.4 From 5f3bfe2f76b8fe7c269aad7a03b5b687e4fcda9b Mon Sep 17 00:00:00 2001 From: Josh Deprez Date: Sun, 5 May 2024 17:25:11 +1000 Subject: [PATCH 2/6] AURP: send more routes / zones --- main.go | 2 -- router/peer_aurp.go | 42 +++++++++++++++++++++++++++++------------- router/route.go | 16 ++++++++++++++++ 3 files changed, 45 insertions(+), 15 deletions(-) diff --git a/main.go b/main.go index ff6809c..2f5240d 100644 --- a/main.go +++ b/main.go @@ -318,7 +318,6 @@ func main() { } peer := &router.AURPPeer{ - Config: cfg, Transport: &aurp.Transport{ LocalDI: localDI, RemoteDI: aurp.IPDomainIdentifier(raddr.IP), @@ -426,7 +425,6 @@ func main() { } // New peer! pr = &router.AURPPeer{ - Config: cfg, Transport: &aurp.Transport{ LocalDI: localDI, RemoteDI: dh.SourceDI, // platinum rule diff --git a/router/peer_aurp.go b/router/peer_aurp.go index 0aca253..8e4bde9 100644 --- a/router/peer_aurp.go +++ b/router/peer_aurp.go @@ -94,9 +94,6 @@ func (ss SenderState) String() string { // AURPPeer handles the peering with a peer AURP router. type AURPPeer struct { - // Whole router config. - Config *Config - // AURP-Tr state for producing packets. Transport *aurp.Transport @@ -440,15 +437,17 @@ func (p *AURPPeer) Handle(ctx context.Context) error { log.Printf("AURP Peer: Received RI-Req but was not expecting one (sender state was %v)", p.sstate) } - nets := aurp.NetworkTuples{ - { - Extended: true, - RangeStart: p.Config.EtherTalk.NetStart, - RangeEnd: p.Config.EtherTalk.NetEnd, - Distance: 0, - }, + var nets aurp.NetworkTuples + for _, r := range p.RouteTable.ValidNonAURPRoutes() { + nets = append(nets, aurp.NetworkTuple{ + Extended: r.Extended, + RangeStart: r.NetStart, + RangeEnd: r.NetEnd, + Distance: r.Distance, + }) } p.Transport.LocalSeq = 1 + // TODO: Split tuples across multiple packets as required lastRISent = p.Transport.NewRIRspPacket(aurp.RoutingFlagLast, nets) if _, err := p.Send(lastRISent); err != nil { log.Printf("AURP Peer: Couldn't send RI-Rsp packet: %v", err) @@ -504,11 +503,28 @@ func (p *AURPPeer) Handle(ctx context.Context) error { sendRetries = 0 // If SZI flag is set, send ZI-Rsp (transaction) - // TODO: split ZI-Rsp packets similarly to ZIP Replies if pkt.Flags&aurp.RoutingFlagSendZoneInfo != 0 { - zones := map[ddp.Network][]string{ - p.Config.EtherTalk.NetStart: {p.Config.EtherTalk.ZoneName}, + // Inspect last routing info packet sent to determine + // networks to gather names for + var nets []ddp.Network + switch last := lastRISent.(type) { + case *aurp.RIRspPacket: + for _, nt := range last.Networks { + nets = append(nets, nt.RangeStart) + } + + case *aurp.RIUpdPacket: + for _, et := range last.Events { + // Only networks that were added + if et.EventCode != aurp.EventCodeNA { + continue + } + nets = append(nets, et.RangeStart) + } + } + zones := p.RouteTable.ZonesForNetworks(nets) + // TODO: split ZI-Rsp packets similarly to ZIP Replies if _, err := p.Send(p.Transport.NewZIRspPacket(zones)); err != nil { log.Printf("AURP Peer: Couldn't send ZI-Rsp packet: %v", err) } diff --git a/router/route.go b/router/route.go index 1042a34..f804383 100644 --- a/router/route.go +++ b/router/route.go @@ -227,3 +227,19 @@ func (rt *RouteTable) ValidRoutes() []*Route { } return valid } + +func (rt *RouteTable) ValidNonAURPRoutes() []*Route { + rt.mu.Lock() + defer rt.mu.Unlock() + valid := make([]*Route, 0, len(rt.routes)) + for r := range rt.routes { + if r.AURPPeer != nil { + continue + } + if !r.Valid() { + continue + } + valid = append(valid, r) + } + return valid +} -- 2.43.4 From 10d4610e0d733cdee66c19174b6033ca9775fd96 Mon Sep 17 00:00:00 2001 From: Josh Deprez Date: Sun, 5 May 2024 17:59:49 +1000 Subject: [PATCH 3/6] Zone name slices -> sets --- main.go | 23 +------------------- router/misc.go | 52 +++++++++++++++++++++++++++++++++++++++++++++ router/nbp.go | 3 +-- router/peer_aurp.go | 2 +- router/port.go | 2 +- router/route.go | 19 +++++++++++------ router/rtmp.go | 36 +++++++++++++++++++++++++++---- router/zip.go | 18 +++++++++++++--- router/zones.go | 25 ++++++++-------------- 9 files changed, 124 insertions(+), 56 deletions(-) create mode 100644 router/misc.go diff --git a/main.go b/main.go index 2f5240d..f156af1 100644 --- a/main.go +++ b/main.go @@ -80,27 +80,6 @@ const routingTableTemplate = ` ` -const zoneTableTemplate = ` - - - - - - - - -{{range $zone := . }} - - - - - - -{{end}} - -
NetworkNameLocal PortLast seen
{{$zone.Network}}{{$zone.Name}}{{with $zone.LocalPort}}{{.Device}}{{else}}-{{end}}{{$zone.LastSeenAgo}}
-` - const peerTableTemplate = ` @@ -353,7 +332,7 @@ func main() { NetStart: cfg.EtherTalk.NetStart, NetEnd: cfg.EtherTalk.NetEnd, DefaultZoneName: cfg.EtherTalk.ZoneName, - AvailableZones: []string{cfg.EtherTalk.ZoneName}, + AvailableZones: router.SetFromSlice([]string{cfg.EtherTalk.ZoneName}), PcapHandle: pcapHandle, AARPMachine: aarpMachine, Router: rooter, diff --git a/router/misc.go b/router/misc.go new file mode 100644 index 0000000..e84f00e --- /dev/null +++ b/router/misc.go @@ -0,0 +1,52 @@ +/* + Copyright 2024 Josh Deprez + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package router + +// StringSet is a set of strings. +// Yep, yet another string set implementation. Took me 2 minutes to write *shrug* +type StringSet map[string]struct{} + +func (set StringSet) ToSlice() []string { + ss := make([]string, 0, len(set)) + for s := range set { + ss = append(ss, s) + } + return ss +} + +func (set StringSet) Contains(s string) bool { + _, c := set[s] + return c +} + +func (set StringSet) Insert(ss ...string) { + for _, s := range ss { + set[s] = struct{}{} + } +} + +func (set StringSet) Add(t StringSet) { + for s := range t { + set[s] = struct{}{} + } +} + +func SetFromSlice(ss []string) StringSet { + set := make(StringSet, len(ss)) + set.Insert(ss...) + return set +} diff --git a/router/nbp.go b/router/nbp.go index 575b9f1..03514a6 100644 --- a/router/nbp.go +++ b/router/nbp.go @@ -20,7 +20,6 @@ import ( "context" "fmt" "log" - "slices" "gitea.drjosh.dev/josh/jrouter/atalk" "gitea.drjosh.dev/josh/jrouter/atalk/nbp" @@ -170,7 +169,7 @@ func (rtr *Router) handleNBPFwdReq(ctx context.Context, ddpkt *ddp.ExtPacket, nb tuple := &nbpkt.Tuples[0] for _, outPort := range rtr.Ports { - if !slices.Contains(outPort.AvailableZones, tuple.Zone) { + if !outPort.AvailableZones.Contains(tuple.Zone) { continue } log.Printf("NBP: Converting FwdReq to LkUp (%v)", tuple) diff --git a/router/peer_aurp.go b/router/peer_aurp.go index 8e4bde9..e8b151c 100644 --- a/router/peer_aurp.go +++ b/router/peer_aurp.go @@ -618,7 +618,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.RouteTable.AddZoneToNetwork(zt.Network, zt.Name) + p.RouteTable.AddZonesToNetwork(zt.Network, zt.Name) } case *aurp.GDZLReqPacket: diff --git a/router/port.go b/router/port.go index eb8aad6..760e013 100644 --- a/router/port.go +++ b/router/port.go @@ -37,7 +37,7 @@ type EtherTalkPort struct { NetEnd ddp.Network MyAddr ddp.Addr DefaultZoneName string - AvailableZones []string + AvailableZones StringSet PcapHandle *pcap.Handle AARPMachine *AARPMachine Router *Router diff --git a/router/route.go b/router/route.go index f804383..bf16dfe 100644 --- a/router/route.go +++ b/router/route.go @@ -36,7 +36,7 @@ type Route struct { // ZoneNames may be empty between learning the existence of a route and // receiving zone information. - ZoneNames []string + ZoneNames StringSet // Exactly one of the following should be set AURPPeer *AURPPeer // Next hop is this peer router (over AURP) @@ -51,8 +51,11 @@ func (r Route) LastSeenAgo() string { return fmt.Sprintf("%v ago", time.Since(r.LastSeen).Truncate(time.Millisecond)) } +// Valid reports whether the route is valid. +// A valid route has one or more zone names, and if it is learned from a peer +// router over EtherTalk is not too old. func (r *Route) Valid() bool { - return r.EtherTalkPeer == nil || time.Since(r.LastSeen) <= maxRouteAge + return len(r.ZoneNames) > 0 && (r.EtherTalkPeer == nil || time.Since(r.LastSeen) <= maxRouteAge) } type RouteTable struct { @@ -150,12 +153,12 @@ func (rt *RouteTable) UpdateAURPRouteDistance(peer *AURPPeer, network ddp.Networ } } -func (rt *RouteTable) UpsertEtherTalkRoute(peer *EtherTalkPeer, extended bool, netStart, netEnd ddp.Network, metric uint8) error { +func (rt *RouteTable) UpsertEtherTalkRoute(peer *EtherTalkPeer, extended bool, netStart, netEnd ddp.Network, metric uint8) (*Route, error) { if netStart > netEnd { - return fmt.Errorf("invalid network range [%d, %d]", netStart, netEnd) + return nil, fmt.Errorf("invalid network range [%d, %d]", netStart, netEnd) } if netStart != netEnd && !extended { - return fmt.Errorf("invalid network range [%d, %d] for nonextended network", netStart, netEnd) + return nil, fmt.Errorf("invalid network range [%d, %d] for nonextended network", netStart, netEnd) } rt.mu.Lock() @@ -177,7 +180,7 @@ func (rt *RouteTable) UpsertEtherTalkRoute(peer *EtherTalkPeer, extended bool, n } r.Distance = metric r.LastSeen = time.Now() - return nil + return r, nil } // Insert. @@ -190,7 +193,7 @@ func (rt *RouteTable) UpsertEtherTalkRoute(peer *EtherTalkPeer, extended bool, n EtherTalkPeer: peer, } rt.routes[r] = struct{}{} - return nil + return r, nil } func (rt *RouteTable) InsertAURPRoute(peer *AURPPeer, extended bool, netStart, netEnd ddp.Network, metric uint8) error { @@ -216,6 +219,7 @@ func (rt *RouteTable) InsertAURPRoute(peer *AURPPeer, extended bool, netStart, n return nil } +// ValidRoutes returns all valid routes. func (rt *RouteTable) ValidRoutes() []*Route { rt.mu.Lock() defer rt.mu.Unlock() @@ -228,6 +232,7 @@ func (rt *RouteTable) ValidRoutes() []*Route { return valid } +// ValidNonAURPRoutes returns all valid routes that were not learned via AURP. func (rt *RouteTable) ValidNonAURPRoutes() []*Route { rt.mu.Lock() defer rt.mu.Unlock() diff --git a/router/rtmp.go b/router/rtmp.go index bdfb6b5..d1a9752 100644 --- a/router/rtmp.go +++ b/router/rtmp.go @@ -24,6 +24,7 @@ import ( "gitea.drjosh.dev/josh/jrouter/atalk" "gitea.drjosh.dev/josh/jrouter/atalk/rtmp" + "gitea.drjosh.dev/josh/jrouter/atalk/zip" "gitea.drjosh.dev/josh/jrouter/status" "github.com/sfiera/multitalk/pkg/ddp" @@ -110,18 +111,45 @@ func (port *EtherTalkPort) HandleRTMP(ctx context.Context, pkt *ddp.ExtPacket) e log.Print("RTMP: Got Response or Data") dataPkt, err := rtmp.UnmarshalDataPacket(pkt.Data) if err != nil { - log.Printf("RTMP: Couldn't unmarshal RTMP Data packet: %v", err) - break + return fmt.Errorf("unmarshal RTMP Data packet: %w", err) } peer := &EtherTalkPeer{ Port: port, PeerAddr: dataPkt.RouterAddr, } - for _, rt := range dataPkt.NetworkTuples { - if err := port.Router.RouteTable.UpsertEtherTalkRoute(peer, rt.Extended, rt.RangeStart, rt.RangeEnd, rt.Distance+1); err != nil { + var noZones []ddp.Network + for _, nt := range dataPkt.NetworkTuples { + route, err := port.Router.RouteTable.UpsertEtherTalkRoute(peer, nt.Extended, nt.RangeStart, nt.RangeEnd, nt.Distance+1) + if err != nil { log.Printf("RTMP: Couldn't upsert EtherTalk route: %v", err) } + if len(route.ZoneNames) == 0 { + noZones = append(noZones, route.NetStart) + } + } + if len(noZones) > 0 { + // Send a ZIP Query for all networks we don't have zone names for. + // TODO: split networks to fit in multiple packets as needed + qryPkt, err := (&zip.QueryPacket{Networks: noZones}).Marshal() + if err != nil { + return fmt.Errorf("marshal ZIP Query packet: %w", err) + } + outDDP := &ddp.ExtPacket{ + ExtHeader: ddp.ExtHeader{ + Size: uint16(len(qryPkt)) + atalk.DDPExtHeaderSize, + Cksum: 0, + SrcNet: port.MyAddr.Network, + SrcNode: port.MyAddr.Node, + SrcSocket: 6, + DstNet: pkt.SrcNet, + DstNode: pkt.SrcNode, + DstSocket: 6, // ZIP socket + Proto: ddp.ProtoZIP, + }, + Data: qryPkt, + } + port.Send(ctx, outDDP) } default: diff --git a/router/zip.go b/router/zip.go index ed52f5b..c3b8875 100644 --- a/router/zip.go +++ b/router/zip.go @@ -20,7 +20,6 @@ import ( "context" "fmt" "log" - "slices" "gitea.drjosh.dev/josh/jrouter/atalk" "gitea.drjosh.dev/josh/jrouter/atalk/atp" @@ -52,6 +51,9 @@ func (port *EtherTalkPort) handleZIPZIP(ctx context.Context, ddpkt *ddp.ExtPacke case *zip.QueryPacket: return port.handleZIPQuery(ctx, ddpkt, zipkt) + case *zip.ReplyPacket: + return port.handleZIPReply(ctx, zipkt) + case *zip.GetNetInfoPacket: return port.handleZIPGetNetInfo(ctx, ddpkt, zipkt) @@ -156,11 +158,21 @@ func (port *EtherTalkPort) handleZIPQuery(ctx context.Context, ddpkt *ddp.ExtPac return nil } +func (port *EtherTalkPort) handleZIPReply(ctx context.Context, zipkt *zip.ReplyPacket) error { + log.Printf("ZIP: Got Reply for networks %v", zipkt.Networks) + + // Integrate new zone information into route table. + for n, zs := range zipkt.Networks { + port.Router.RouteTable.AddZonesToNetwork(n, zs...) + } + return nil +} + func (port *EtherTalkPort) handleZIPGetNetInfo(ctx context.Context, ddpkt *ddp.ExtPacket, zipkt *zip.GetNetInfoPacket) error { log.Printf("ZIP: Got GetNetInfo for zone %q", zipkt.ZoneName) // The request is zoneValid if the zone name is available on this network. - zoneValid := slices.Contains(port.AvailableZones, zipkt.ZoneName) + zoneValid := port.AvailableZones.Contains(zipkt.ZoneName) // The multicast address we return depends on the validity of the zone // name. @@ -261,7 +273,7 @@ func (port *EtherTalkPort) handleZIPTReq(ctx context.Context, ddpkt *ddp.ExtPack resp.Zones = port.Router.RouteTable.AllZoneNames() case zip.FunctionGetLocalZones: - resp.Zones = port.AvailableZones + resp.Zones = port.AvailableZones.ToSlice() case zip.FunctionGetMyZone: // Note: This shouldn't happen on extended networks (e.g. EtherTalk) diff --git a/router/zones.go b/router/zones.go index 5f8ca75..1dc3ead 100644 --- a/router/zones.go +++ b/router/zones.go @@ -22,7 +22,7 @@ import ( "github.com/sfiera/multitalk/pkg/ddp" ) -func (rt *RouteTable) AddZoneToNetwork(n ddp.Network, z string) { +func (rt *RouteTable) AddZonesToNetwork(n ddp.Network, zs ...string) { rt.mu.Lock() defer rt.mu.Unlock() for r := range rt.routes { @@ -32,10 +32,7 @@ func (rt *RouteTable) AddZoneToNetwork(n ddp.Network, z string) { if !r.Valid() { continue } - if slices.Contains(r.ZoneNames, z) { - continue - } - r.ZoneNames = append(r.ZoneNames, z) + r.ZoneNames.Insert(zs...) } } @@ -49,7 +46,9 @@ func (rt *RouteTable) ZonesForNetworks(ns []ddp.Network) map[ddp.Network][]strin continue } if _, ok := slices.BinarySearch(ns, r.NetStart); ok { - zs[r.NetStart] = append(zs[r.NetStart], r.ZoneNames...) + for z := range r.ZoneNames { + zs[r.NetStart] = append(zs[r.NetStart], z) + } } } return zs @@ -64,7 +63,7 @@ func (rt *RouteTable) RoutesForZone(zone string) []*Route { if !r.Valid() { continue } - if slices.Contains(r.ZoneNames, zone) { + if r.ZoneNames.Contains(zone) { routes = append(routes, r) } } @@ -77,19 +76,13 @@ func (rt *RouteTable) AllZoneNames() (zones []string) { rt.mu.Lock() defer rt.mu.Unlock() - seen := make(map[string]struct{}) + zs := make(StringSet) 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) - } + zs.Add(r.ZoneNames) } - return zones + return zs.ToSlice() } -- 2.43.4 From 95eec9564f7e82a4ed9d52b204f839a0f837d8cf Mon Sep 17 00:00:00 2001 From: Josh Deprez Date: Sun, 5 May 2024 18:04:54 +1000 Subject: [PATCH 4/6] Fix obvious bugs --- main.go | 2 +- router/zones.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/main.go b/main.go index f156af1..d6b1591 100644 --- a/main.go +++ b/main.go @@ -60,7 +60,7 @@ const routingTableTemplate = ` - +
{{$route.NetStart}}{{if not (eq $route.NetStart $route.NetEnd)}} - {{$route.NetEnd}}{{end}} {{if $route.Extended}}✅{{else}}-{{end}}{{range $route.ZoneNames}}{{.}}
{{end}}
{{range $route.ZoneNames.ToSlice}}{{.}}
{{end}}
{{$route.Distance}} {{$route.LastSeenAgo}} diff --git a/router/zones.go b/router/zones.go index 1dc3ead..42bf81d 100644 --- a/router/zones.go +++ b/router/zones.go @@ -29,8 +29,8 @@ func (rt *RouteTable) AddZonesToNetwork(n ddp.Network, zs ...string) { if n < r.NetStart || n > r.NetEnd { continue } - if !r.Valid() { - continue + if r.ZoneNames == nil { + r.ZoneNames = make(StringSet) } r.ZoneNames.Insert(zs...) } -- 2.43.4 From c1f84c3f29f6c427df5898addb552f5c2613d39a Mon Sep 17 00:00:00 2001 From: Josh Deprez Date: Sun, 5 May 2024 18:09:38 +1000 Subject: [PATCH 5/6] Cleanup --- main.go | 9 --------- 1 file changed, 9 deletions(-) diff --git a/main.go b/main.go index d6b1591..ac05d7b 100644 --- a/main.go +++ b/main.go @@ -201,15 +201,6 @@ 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 - // }) - // -------------------------------- Peers --------------------------------- var peersMu sync.Mutex peers := make(map[udpAddr]*router.AURPPeer) -- 2.43.4 From 376c09d1898d1ee51dafc5e8a21f4e1d4b4aca8e Mon Sep 17 00:00:00 2001 From: Josh Deprez Date: Sun, 5 May 2024 18:13:40 +1000 Subject: [PATCH 6/6] More cleanups --- router/rtmp.go | 11 ++++++----- router/zip.go | 6 +++--- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/router/rtmp.go b/router/rtmp.go index d1a9752..b714e03 100644 --- a/router/rtmp.go +++ b/router/rtmp.go @@ -102,8 +102,7 @@ func (port *EtherTalkPort) HandleRTMP(ctx context.Context, pkt *ddp.ExtPacket) e } case rtmp.FunctionLoopProbe: - log.Print("RTMP: TODO: handle Loop Probes") - return nil + return fmt.Errorf("TODO: handle Loop Probes") } case ddp.ProtoRTMPResp: @@ -122,7 +121,7 @@ func (port *EtherTalkPort) HandleRTMP(ctx context.Context, pkt *ddp.ExtPacket) e for _, nt := range dataPkt.NetworkTuples { route, err := port.Router.RouteTable.UpsertEtherTalkRoute(peer, nt.Extended, nt.RangeStart, nt.RangeEnd, nt.Distance+1) if err != nil { - log.Printf("RTMP: Couldn't upsert EtherTalk route: %v", err) + return fmt.Errorf("upsert EtherTalk route: %v", err) } if len(route.ZoneNames) == 0 { noZones = append(noZones, route.NetStart) @@ -149,11 +148,13 @@ func (port *EtherTalkPort) HandleRTMP(ctx context.Context, pkt *ddp.ExtPacket) e }, Data: qryPkt, } - port.Send(ctx, outDDP) + if err := port.Send(ctx, outDDP); err != nil { + return fmt.Errorf("sending ZIP Query: %w", err) + } } default: - log.Printf("RTMP: invalid DDP type %d on socket 1", pkt.Proto) + return fmt.Errorf("invalid DDP type %d on socket 1", pkt.Proto) } return nil diff --git a/router/zip.go b/router/zip.go index c3b8875..9b668e2 100644 --- a/router/zip.go +++ b/router/zip.go @@ -52,7 +52,7 @@ func (port *EtherTalkPort) handleZIPZIP(ctx context.Context, ddpkt *ddp.ExtPacke return port.handleZIPQuery(ctx, ddpkt, zipkt) case *zip.ReplyPacket: - return port.handleZIPReply(ctx, zipkt) + return port.handleZIPReply(zipkt) case *zip.GetNetInfoPacket: return port.handleZIPGetNetInfo(ctx, ddpkt, zipkt) @@ -158,8 +158,8 @@ func (port *EtherTalkPort) handleZIPQuery(ctx context.Context, ddpkt *ddp.ExtPac return nil } -func (port *EtherTalkPort) handleZIPReply(ctx context.Context, zipkt *zip.ReplyPacket) error { - log.Printf("ZIP: Got Reply for networks %v", zipkt.Networks) +func (port *EtherTalkPort) handleZIPReply(zipkt *zip.ReplyPacket) error { + log.Printf("ZIP: Got Reply containing %v", zipkt.Networks) // Integrate new zone information into route table. for n, zs := range zipkt.Networks { -- 2.43.4