From 7f8b0cb4510a929858f5f7c682d521d4f37060c0 Mon Sep 17 00:00:00 2001 From: Josh Deprez Date: Mon, 29 Apr 2024 09:32:57 +1000 Subject: [PATCH] WIP: routers on ethertalk --- main.go | 86 +++++++++++++++++++++++--------- router/nbp.go | 2 +- router/nbp_aurp.go | 2 +- router/{peer.go => peer_aurp.go} | 34 ++++++------- router/peer_eth.go | 54 ++++++++++++++++++++ router/route.go | 63 +++++++++++++++-------- router/rtmp.go | 20 +++++++- 7 files changed, 197 insertions(+), 64 deletions(-) rename router/{peer.go => peer_aurp.go} (95%) create mode 100644 router/peer_eth.go diff --git a/main.go b/main.go index 446be72..eff6129 100644 --- a/main.go +++ b/main.go @@ -224,18 +224,18 @@ func main() { // -------------------------------- Peers --------------------------------- var peersMu sync.Mutex - peers := make(map[udpAddr]*router.Peer) + peers := make(map[udpAddr]*router.AURPPeer) status.AddItem(ctx, "AURP Peers", peerTableTemplate, func(context.Context) (any, error) { - var peerInfo []*router.Peer + var peerInfo []*router.AURPPeer func() { peersMu.Lock() defer peersMu.Unlock() - peerInfo = make([]*router.Peer, 0, len(peers)) + peerInfo = make([]*router.AURPPeer, 0, len(peers)) for _, p := range peers { peerInfo = append(peerInfo, p) } }() - slices.SortFunc(peerInfo, func(pa, pb *router.Peer) int { + slices.SortFunc(peerInfo, func(pa, pb *router.AURPPeer) int { return cmp.Or( -cmp.Compare( bool2Int(pa.ReceiverState() == router.ReceiverConnected), @@ -257,7 +257,7 @@ func main() { } var wg sync.WaitGroup - goPeerHandler := func(p *router.Peer) { + goPeerHandler := func(p *router.AURPPeer) { wg.Add(1) go func() { defer wg.Done() @@ -308,7 +308,7 @@ func main() { continue } - peer := &router.Peer{ + peer := &router.AURPPeer{ Config: cfg, Transport: &aurp.Transport{ LocalDI: localDI, @@ -434,18 +434,29 @@ func main() { // TODO: more generic routing if ddpkt.DstNet != 0 && !(ddpkt.DstNet >= cfg.EtherTalk.NetStart && ddpkt.DstNet <= cfg.EtherTalk.NetEnd) { // Is it for a network in the routing table? - rt := routes.LookupRoute(ddpkt.DstNet) - if rt == nil { + route := routes.LookupRoute(ddpkt.DstNet) + if route == nil { log.Printf("DDP: no route for network %d", ddpkt.DstNet) continue } - // Encap ethPacket.Payload into an AURP packet - log.Printf("DDP: forwarding to AURP peer %v", rt.Peer.RemoteAddr) - if _, err := rt.Peer.Send(rt.Peer.Transport.NewAppleTalkPacket(ethFrame.Payload)); err != nil { - log.Printf("DDP: Couldn't forward packet to AURP peer: %v", err) - } + switch { + case route.AURPPeer != nil: + // Encap ethPacket.Payload into an AURP packet + log.Printf("DDP: forwarding to AURP peer %v", route.AURPPeer.RemoteAddr) + if _, err := route.AURPPeer.Send(route.AURPPeer.Transport.NewAppleTalkPacket(ethFrame.Payload)); err != nil { + log.Printf("DDP: Couldn't forward packet to AURP peer: %v", err) + } + case route.EtherTalkPeer != nil: + // Route payload to another router over EtherTalk + // TODO: this is unlikely because we currenly only support 1 EtherTalk port + log.Printf("DDP: forwarding to EtherTalk peer %v", route.EtherTalkPeer.PeerAddr) + // Note: resolving AARP can block + if err := route.EtherTalkPeer.Forward(ctx, ddpkt); err != nil { + log.Printf("DDP: Couldn't forward packet to EtherTalk peer: %v", err) + } + } continue } @@ -534,7 +545,7 @@ func main() { continue } // New peer! - pr = &router.Peer{ + pr = &router.AURPPeer{ Config: cfg, Transport: &aurp.Transport{ LocalDI: localDI, @@ -584,14 +595,7 @@ func main() { ddpkt.DstNet, ddpkt.DstNode, ddpkt.DstSocket, ddpkt.Proto, len(ddpkt.Data)) - // "Route" the packet - // Since for now there's only one local network, the routing - // decision is pretty easy - // TODO: Fix this to support other AppleTalk routers - if ddpkt.DstNet < cfg.EtherTalk.NetStart || ddpkt.DstNet > cfg.EtherTalk.NetEnd { - log.Print("DDP/AURP: dropping packet not addressed to our EtherTalk range") - continue - } + // Route the packet // Check and adjust the Hop Count // Note the ddp package doesn't make this simple @@ -604,8 +608,44 @@ func main() { ddpkt.Size &^= 0x3C00 ddpkt.Size |= hopCount << 10 + if ddpkt.DstNet < cfg.EtherTalk.NetStart || ddpkt.DstNet > cfg.EtherTalk.NetEnd { + // Is it a network in the routing table? + route := routes.LookupRoute(ddpkt.DstNet) + if route == nil { + log.Printf("DDP/AURP: no route for packet (dstnet %d); dropping packet", ddpkt.DstNet) + break + } + + switch { + case route.AURPPeer != nil: + // Routing between AURP peers... bit weird but OK + log.Printf("DDP/AURP: forwarding to AURP peer %v", route.AURPPeer.RemoteAddr) + outPkt, err := ddp.ExtMarshal(*ddpkt) + if err != nil { + log.Printf("DDP/AURP: Couldn't re-marshal packet: %v", err) + break + } + if _, err := route.AURPPeer.Send(route.AURPPeer.Transport.NewAppleTalkPacket(outPkt)); err != nil { + log.Printf("DDP/AURP: Couldn't forward packet to AURP peer: %v", err) + } + + case route.EtherTalkPeer != nil: + // AURP peer -> EtherTalk peer + // Note: resolving AARP can block + log.Printf("DDP/AURP: forwarding to EtherTalk peer %v", route.EtherTalkPeer.PeerAddr) + if err := route.EtherTalkPeer.Forward(ctx, ddpkt); err != nil { + log.Printf("DDP/AURP: Couldn't forward packet to EtherTalk peer: %v", err) + } + + default: + log.Print("DDP/AURP: no forwarding mechanism for route; dropping packet") + + } + continue + } + // Is it addressed to me? Is it NBP? - if ddpkt.DstNode == 0 { // Node 0 = the router for the network + if ddpkt.DstNode == 0 { // Node 0 = any router for the network = me if ddpkt.DstSocket != 2 { // Something else?? TODO log.Printf("DDP/AURP: I don't have anything 'listening' on socket %d", ddpkt.DstSocket) diff --git a/router/nbp.go b/router/nbp.go index 822003c..ec7a7e5 100644 --- a/router/nbp.go +++ b/router/nbp.go @@ -110,7 +110,7 @@ func (rtr *Router) HandleNBP(srcHWAddr ethernet.Addr, ddpkt *ddp.ExtPacket) erro if route == nil { return fmt.Errorf("no route for network %d", z.Network) } - peer := route.Peer + peer := route.AURPPeer if peer == nil { return fmt.Errorf("nil peer for route for network %d", z.Network) } diff --git a/router/nbp_aurp.go b/router/nbp_aurp.go index fbf9f2a..306751e 100644 --- a/router/nbp_aurp.go +++ b/router/nbp_aurp.go @@ -24,7 +24,7 @@ import ( "github.com/sfiera/multitalk/pkg/ddp" ) -func (rtr *Router) HandleNBPInAURP(peer *Peer, ddpkt *ddp.ExtPacket) error { +func (rtr *Router) HandleNBPInAURP(peer *AURPPeer, ddpkt *ddp.ExtPacket) error { if ddpkt.Proto != ddp.ProtoNBP { return fmt.Errorf("invalid DDP type %d on socket 2", ddpkt.Proto) } diff --git a/router/peer.go b/router/peer_aurp.go similarity index 95% rename from router/peer.go rename to router/peer_aurp.go index dfba5c8..d36eb56 100644 --- a/router/peer.go +++ b/router/peer_aurp.go @@ -92,8 +92,8 @@ func (ss SenderState) String() string { } } -// Peer handles the peering with a peer AURP router. -type Peer struct { +// AURPPeer handles the peering with a peer AURP router. +type AURPPeer struct { // Whole router config. Config *Config @@ -125,31 +125,31 @@ type Peer struct { sstate SenderState } -func (p *Peer) ReceiverState() ReceiverState { +func (p *AURPPeer) ReceiverState() ReceiverState { p.mu.RLock() defer p.mu.RUnlock() return p.rstate } -func (p *Peer) SenderState() SenderState { +func (p *AURPPeer) SenderState() SenderState { p.mu.RLock() defer p.mu.RUnlock() return p.sstate } -func (p *Peer) setRState(rstate ReceiverState) { +func (p *AURPPeer) setRState(rstate ReceiverState) { p.mu.Lock() defer p.mu.Unlock() p.rstate = rstate } -func (p *Peer) setSState(sstate SenderState) { +func (p *AURPPeer) setSState(sstate SenderState) { p.mu.Lock() defer p.mu.Unlock() p.sstate = sstate } -func (p *Peer) disconnect() { +func (p *AURPPeer) disconnect() { p.mu.Lock() defer p.mu.Unlock() p.rstate = ReceiverUnconnected @@ -157,7 +157,7 @@ func (p *Peer) disconnect() { } // Send encodes and sends pkt to the remote host. -func (p *Peer) Send(pkt aurp.Packet) (int, error) { +func (p *AURPPeer) Send(pkt aurp.Packet) (int, error) { var b bytes.Buffer if _, err := pkt.WriteTo(&b); err != nil { return 0, err @@ -166,7 +166,7 @@ func (p *Peer) Send(pkt aurp.Packet) (int, error) { return p.UDPConn.WriteToUDP(b.Bytes(), p.RemoteAddr) } -func (p *Peer) Handle(ctx context.Context) error { +func (p *AURPPeer) Handle(ctx context.Context) error { rticker := time.NewTicker(1 * time.Second) defer rticker.Stop() sticker := time.NewTicker(1 * time.Second) @@ -244,7 +244,7 @@ func (p *Peer) 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.DeletePeer(p) + p.RoutingTable.DeleteAURPPeer(p) break } @@ -263,7 +263,7 @@ func (p *Peer) 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.DeletePeer(p) + p.RoutingTable.DeleteAURPPeer(p) break } @@ -458,7 +458,7 @@ func (p *Peer) Handle(ctx context.Context) error { log.Printf("AURP Peer: Learned about these networks: %v", pkt.Networks) for _, nt := range pkt.Networks { - p.RoutingTable.InsertRoute( + p.RoutingTable.InsertAURPRoute( p, nt.Extended, ddp.Network(nt.RangeStart), @@ -534,7 +534,7 @@ func (p *Peer) Handle(ctx context.Context) error { // Do nothing except respond with RI-Ack case aurp.EventCodeNA: - if err := p.RoutingTable.InsertRoute( + if err := p.RoutingTable.InsertAURPRoute( p, et.Extended, et.RangeStart, @@ -546,10 +546,10 @@ func (p *Peer) Handle(ctx context.Context) error { ackFlag = aurp.RoutingFlagSendZoneInfo case aurp.EventCodeND: - p.RoutingTable.DeletePeerNetwork(p, et.RangeStart) + p.RoutingTable.DeleteAURPPeerNetwork(p, et.RangeStart) case aurp.EventCodeNDC: - p.RoutingTable.UpdateRouteDistance(p, et.RangeStart, et.Distance+1) + p.RoutingTable.UpdateAURPRouteDistance(p, et.RangeStart, et.Distance+1) case aurp.EventCodeNRC: // "An exterior router sends a Network Route Change @@ -557,7 +557,7 @@ func (p *Peer) 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.DeletePeerNetwork(p, et.RangeStart) + p.RoutingTable.DeleteAURPPeerNetwork(p, et.RangeStart) case aurp.EventCodeZC: // "This event is reserved for future use." @@ -575,7 +575,7 @@ func (p *Peer) Handle(ctx context.Context) error { } log.Printf("AURP Peer: Router Down: error code %d %s", pkt.ErrorCode, pkt.ErrorCode) - p.RoutingTable.DeletePeer(p) + p.RoutingTable.DeleteAURPPeer(p) // Respond with RI-Ack if _, err := p.Send(p.Transport.NewRIAckPacket(pkt.ConnectionID, pkt.Sequence, 0)); err != nil { diff --git a/router/peer_eth.go b/router/peer_eth.go new file mode 100644 index 0000000..046687e --- /dev/null +++ b/router/peer_eth.go @@ -0,0 +1,54 @@ +/* + 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 + +import ( + "context" + + "github.com/google/gopacket/pcap" + "github.com/sfiera/multitalk/pkg/ddp" + "github.com/sfiera/multitalk/pkg/ethernet" + "github.com/sfiera/multitalk/pkg/ethertalk" +) + +// EtherTalkPeer holds data needed to exchange routes and zones with another +// router on the EtherTalk network. +type EtherTalkPeer struct { + PcapHandle *pcap.Handle + MyHWAddr ethernet.Addr + AARP *AARPMachine + PeerAddr ddp.Addr +} + +// Forward forwards a DDP packet to the next router. +func (p *EtherTalkPeer) Forward(ctx context.Context, pkt *ddp.ExtPacket) error { + // TODO: AARP resolution can block + de, err := p.AARP.Resolve(ctx, p.PeerAddr) + if err != nil { + return err + } + outFrame, err := ethertalk.AppleTalk(p.MyHWAddr, *pkt) + if err != nil { + return err + } + outFrame.Dst = de + outFrameRaw, err := ethertalk.Marshal(*outFrame) + if err != nil { + return err + } + return p.PcapHandle.WritePacketData(outFrameRaw) +} diff --git a/router/route.go b/router/route.go index a5b1c87..5263baa 100644 --- a/router/route.go +++ b/router/route.go @@ -24,15 +24,18 @@ import ( "github.com/sfiera/multitalk/pkg/ddp" ) -// const maxRouteAge = 10 * time.Minute // TODO: confirm +const maxRouteAge = 10 * time.Minute // TODO: confirm type Route struct { Extended bool NetStart ddp.Network NetEnd ddp.Network - Peer *Peer Distance uint8 LastSeen time.Time + + // Exactly one of the following should be set + AURPPeer *AURPPeer + EtherTalkPeer *EtherTalkPeer } func (r Route) LastSeenAgo() string { @@ -70,15 +73,13 @@ func (rt *RoutingTable) LookupRoute(network ddp.Network) *Route { var bestRoute *Route for r := range rt.routes { - if r.Peer == nil { - continue - } if network < r.NetStart || network > r.NetEnd { continue } - // if time.Since(r.LastSeen) > maxRouteAge { - // continue - // } + // Exclude EtherTalk routes that are too old + if r.EtherTalkPeer != nil && time.Since(r.LastSeen) > maxRouteAge { + continue + } if bestRoute == nil { bestRoute = r continue @@ -90,41 +91,65 @@ func (rt *RoutingTable) LookupRoute(network ddp.Network) *Route { return bestRoute } -func (rt *RoutingTable) DeletePeer(peer *Peer) { +func (rt *RoutingTable) DeleteAURPPeer(peer *AURPPeer) { rt.mu.Lock() defer rt.mu.Unlock() for route := range rt.routes { - if route.Peer == peer { + if route.AURPPeer == peer { delete(rt.routes, route) } } } -func (rt *RoutingTable) DeletePeerNetwork(peer *Peer, network ddp.Network) { +func (rt *RoutingTable) DeleteAURPPeerNetwork(peer *AURPPeer, network ddp.Network) { rt.mu.Lock() defer rt.mu.Unlock() for route := range rt.routes { - if route.Peer == peer && route.NetStart == network { + if route.AURPPeer == peer && route.NetStart == network { delete(rt.routes, route) } } } -func (rt *RoutingTable) UpdateRouteDistance(peer *Peer, network ddp.Network, distance uint8) { +func (rt *RoutingTable) UpdateAURPRouteDistance(peer *AURPPeer, network ddp.Network, distance uint8) { rt.mu.Lock() defer rt.mu.Unlock() for route := range rt.routes { - if route.Peer == peer && route.NetStart == network { + if route.AURPPeer == peer && route.NetStart == network { route.Distance = distance route.LastSeen = time.Now() } } } -func (rt *RoutingTable) InsertRoute(peer *Peer, extended bool, netStart, netEnd ddp.Network, metric uint8) error { +func (rt *RoutingTable) UpsertEthRoute(peer *EtherTalkPeer, extended bool, netStart, netEnd ddp.Network, metric uint8) error { + if netStart > netEnd { + return 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) + } + + r := &Route{ + Extended: extended, + NetStart: netStart, + NetEnd: netEnd, + Distance: metric, + LastSeen: time.Now(), + EtherTalkPeer: peer, + } + + rt.mu.Lock() + defer rt.mu.Unlock() + // TODO: update if present rather than insert + rt.routes[r] = struct{}{} + return nil +} + +func (rt *RoutingTable) InsertAURPRoute(peer *AURPPeer, extended bool, netStart, netEnd ddp.Network, metric uint8) error { if netStart > netEnd { return fmt.Errorf("invalid network range [%d, %d]", netStart, netEnd) } @@ -136,9 +161,9 @@ func (rt *RoutingTable) InsertRoute(peer *Peer, extended bool, netStart, netEnd Extended: extended, NetStart: netStart, NetEnd: netEnd, - Peer: peer, Distance: metric, LastSeen: time.Now(), + AURPPeer: peer, } rt.mu.Lock() @@ -152,12 +177,10 @@ func (rt *RoutingTable) ValidRoutes() []*Route { defer rt.mu.Unlock() valid := make([]*Route, 0, len(rt.routes)) for r := range rt.routes { - if r.Peer == nil { + // Exclude EtherTalk routes that are too old + if r.EtherTalkPeer != nil && time.Since(r.LastSeen) > maxRouteAge { continue } - // if time.Since(r.LastSeen) > maxRouteAge { - // continue - // } valid = append(valid, r) } return valid diff --git a/router/rtmp.go b/router/rtmp.go index 8a036a1..c7aea62 100644 --- a/router/rtmp.go +++ b/router/rtmp.go @@ -159,14 +159,30 @@ func (m *RTMPMachine) Run(ctx context.Context, incomingCh <-chan *ddp.ExtPacket) } case rtmp.FunctionLoopProbe: - log.Printf("RTMP: TODO: handle Loop Probes") + log.Print("RTMP: TODO: handle Loop Probes") } case ddp.ProtoRTMPResp: // It's a peer router on the AppleTalk network! - // TODO: integrate this information with the routing table? 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 + } + peer := &EtherTalkPeer{ + PcapHandle: m.PcapHandle, + MyHWAddr: m.AARP.myAddr.Hardware, + AARP: m.AARP, + PeerAddr: dataPkt.RouterAddr, + } + + for _, rt := range dataPkt.NetworkTuples { + if err := m.RoutingTable.UpsertEthRoute(peer, rt.Extended, rt.RangeStart, rt.RangeEnd, rt.Distance+1); err != nil { + log.Printf("RTMP: Couldn't upsert EtherTalk route: %v", err) + } + } default: log.Printf("RTMP: invalid DDP type %d on socket 1", pkt.Proto)