Table unification #2
10 changed files with 257 additions and 234 deletions
49
main.go
49
main.go
|
@ -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.ToSlice}}{{.}}<br>{{end}}</td>
|
||||
<td>{{$route.Distance}}</td>
|
||||
<td>{{$route.LastSeenAgo}}</td>
|
||||
<td>
|
||||
|
@ -78,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>
|
||||
|
@ -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,7 +395,6 @@ func main() {
|
|||
}
|
||||
// New peer!
|
||||
pr = &router.AURPPeer{
|
||||
Config: cfg,
|
||||
Transport: &aurp.Transport{
|
||||
LocalDI: localDI,
|
||||
RemoteDI: dh.SourceDI, // platinum rule
|
||||
|
@ -437,8 +403,7 @@ func main() {
|
|||
UDPConn: ln,
|
||||
RemoteAddr: raddr,
|
||||
ReceiveCh: make(chan aurp.Packet, 1024),
|
||||
RoutingTable: routes,
|
||||
ZoneTable: zones,
|
||||
RouteTable: routes,
|
||||
}
|
||||
aurp.Inc(&nextConnID)
|
||||
peers[ra] = pr
|
||||
|
|
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"
|
||||
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -26,7 +26,6 @@ import (
|
|||
type Router struct {
|
||||
Config *Config
|
||||
RouteTable *RouteTable
|
||||
ZoneTable *ZoneTable
|
||||
Ports []*EtherTalkPort
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
189
router/zones.go
189
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)
|
||||
}
|
||||
zt.mu.Unlock()
|
||||
r.ZoneNames.Insert(zs...)
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue