diff --git a/main.go b/main.go index 59932e7..ac05d7b 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.ToSlice}}{{.}}
{{end}} {{$route.Distance}} {{$route.LastSeenAgo}} @@ -78,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 = ` @@ -220,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) @@ -316,7 +288,6 @@ func main() { } peer := &router.AURPPeer{ - Config: cfg, Transport: &aurp.Transport{ LocalDI: localDI, RemoteDI: aurp.IPDomainIdentifier(raddr.IP), @@ -326,8 +297,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 +314,7 @@ func main() { rooter := &router.Router{ Config: cfg, RouteTable: routes, - ZoneTable: zones, + // ZoneTable: zones, } etherTalkPort := &router.EtherTalkPort{ @@ -353,16 +323,13 @@ 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, } 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) @@ -428,17 +395,15 @@ func main() { } // New peer! pr = &router.AURPPeer{ - Config: cfg, Transport: &aurp.Transport{ LocalDI: localDI, 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/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 673b9d3..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" @@ -74,10 +73,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 +146,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, @@ -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 85432cc..e8b151c 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 @@ -114,11 +111,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 +247,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 +266,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 } @@ -443,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) @@ -467,7 +463,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), @@ -507,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) } @@ -543,7 +556,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 +568,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 +579,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 +597,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 +609,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 +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.ZoneTable.Upsert(ddp.Network(zt.Network), zt.Name, nil) + 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 a399836..bf16dfe 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 StringSet + // 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,13 @@ 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 len(r.ZoneNames) > 0 && (r.EtherTalkPeer == nil || time.Since(r.LastSeen) <= maxRouteAge) +} + type RouteTable struct { mu sync.Mutex routes map[*Route]struct{} @@ -65,6 +76,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 +105,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 { @@ -142,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() @@ -169,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. @@ -182,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 { @@ -208,13 +219,29 @@ 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() 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 { + if r.Valid() { + valid = append(valid, r) + } + } + 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() + 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) 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/rtmp.go b/router/rtmp.go index bdfb6b5..b714e03 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" @@ -101,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: @@ -110,22 +110,51 @@ 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 { - log.Printf("RTMP: Couldn't upsert EtherTalk route: %v", err) + 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 { + return fmt.Errorf("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, + } + 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 669e1f4..9b668e2 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(zipkt) + case *zip.GetNetInfoPacket: return port.handleZIPGetNetInfo(ctx, ddpkt, zipkt) @@ -62,7 +64,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() @@ -156,11 +158,21 @@ func (port *EtherTalkPort) handleZIPQuery(ctx context.Context, ddpkt *ddp.ExtPac return nil } +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 { + 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. @@ -258,10 +270,10 @@ 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 + 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 f1d0d74..42bf81d 100644 --- a/router/zones.go +++ b/router/zones.go @@ -17,145 +17,72 @@ 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) AddZonesToNetwork(n ddp.Network, zs ...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.ZoneNames == nil { + r.ZoneNames = make(StringSet) + } + r.ZoneNames.Insert(zs...) } - 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 { + for z := range r.ZoneNames { + zs[r.NetStart] = append(zs[r.NetStart], z) + } + } + } 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 r.ZoneNames.Contains(zone) { + routes = append(routes, r) + } + } + return routes +} + +func (rt *RouteTable) AllZoneNames() (zones []string) { + defer slices.Sort(zones) + + rt.mu.Lock() + defer rt.mu.Unlock() + + zs := make(StringSet) + for r := range rt.routes { + if !r.Valid() { + continue + } + zs.Add(r.ZoneNames) + } + + return zs.ToSlice() +}