Table unification #2
9 changed files with 124 additions and 56 deletions
23
main.go
23
main.go
|
@ -80,27 +80,6 @@ const routingTableTemplate = `
|
|||
</table>
|
||||
`
|
||||
|
||||
const zoneTableTemplate = `
|
||||
<table>
|
||||
<thead><tr>
|
||||
<th>Network</th>
|
||||
<th>Name</th>
|
||||
<th>Local Port</th>
|
||||
<th>Last seen</th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
{{range $zone := . }}
|
||||
<tr>
|
||||
<td>{{$zone.Network}}</td>
|
||||
<td>{{$zone.Name}}</td>
|
||||
<td>{{with $zone.LocalPort}}{{.Device}}{{else}}-{{end}}</td>
|
||||
<td>{{$zone.LastSeenAgo}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
`
|
||||
|
||||
const peerTableTemplate = `
|
||||
<table>
|
||||
<thead><tr>
|
||||
|
@ -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,
|
||||
|
|
52
router/misc.go
Normal file
52
router/misc.go
Normal file
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue