WIP: routers on ethertalk

This commit is contained in:
Josh Deprez 2024-04-29 09:32:57 +10:00
parent da4c729912
commit 7f8b0cb451
Signed by: josh
SSH key fingerprint: SHA256:zZji7w1Ilh2RuUpbQcqkLPrqmRwpiCSycbF2EfKm6Kw
7 changed files with 197 additions and 64 deletions

86
main.go
View file

@ -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)

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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 networks 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 {

54
router/peer_eth.go Normal file
View file

@ -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)
}

View file

@ -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

View file

@ -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)