From 10d4610e0d733cdee66c19174b6033ca9775fd96 Mon Sep 17 00:00:00 2001 From: Josh Deprez Date: Sun, 5 May 2024 17:59:49 +1000 Subject: [PATCH] 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() }