From 520046d86adf96f36260cb206a9f17fb253cddb6 Mon Sep 17 00:00:00 2001 From: Josh Deprez Date: Sat, 30 Mar 2024 14:13:34 +1100 Subject: [PATCH] Tickle handling --- aurp/errors.go | 28 +++++--- aurp/transport.go | 20 ++++++ main.go | 7 +- peer.go | 177 ++++++++++++++++++++++++++-------------------- 4 files changed, 146 insertions(+), 86 deletions(-) diff --git a/aurp/errors.go b/aurp/errors.go index 2a60f57..ccb6886 100644 --- a/aurp/errors.go +++ b/aurp/errors.go @@ -1,14 +1,26 @@ package aurp -type ErrorCode = int16 +type ErrorCode int16 // Various error codes. const ( - ErrCodeNormalClose = -1 - ErrCodeRoutingLoop = -2 - ErrCodeOutOfSync = -3 - ErrCodeOptionNegotiation = -4 - ErrCodeInvalidVersion = -5 - ErrCodeInsufficientResources = -6 - ErrCodeAuthentication = -7 + ErrCodeNormalClose ErrorCode = -1 + ErrCodeRoutingLoop ErrorCode = -2 + ErrCodeOutOfSync ErrorCode = -3 + ErrCodeOptionNegotiation ErrorCode = -4 + ErrCodeInvalidVersion ErrorCode = -5 + ErrCodeInsufficientResources ErrorCode = -6 + ErrCodeAuthentication ErrorCode = -7 ) + +func (e ErrorCode) String() string { + return map[ErrorCode]string{ + ErrCodeNormalClose: "normal connection close", + ErrCodeRoutingLoop: "routing loop detected", + ErrCodeOutOfSync: "connection out of sync", + ErrCodeOptionNegotiation: "option-negotiation error", + ErrCodeInvalidVersion: "invalid version number", + ErrCodeInsufficientResources: "insufficient resources for connection", + ErrCodeAuthentication: "authentication error", + }[e] +} diff --git a/aurp/transport.go b/aurp/transport.go index 91b357a..70a57e2 100644 --- a/aurp/transport.go +++ b/aurp/transport.go @@ -110,3 +110,23 @@ func (tr *Transport) NewOpenRspPacket(envFlags RoutingFlag, rateOrErr int16, opt Options: opts, } } + +func (tr *Transport) NewTicklePacket() *TicklePacket { + return &TicklePacket{ + Header: Header{ + TrHeader: tr.transaction(tr.LocalConnID), + CommandCode: CmdCodeTickle, + Flags: 0, + }, + } +} + +func (tr *Transport) NewTickleAckPacket() *TickleAckPacket { + return &TickleAckPacket{ + Header: Header{ + TrHeader: tr.transaction(tr.RemoteConnID), + CommandCode: CmdCodeTickleAck, + Flags: 0, + }, + } +} diff --git a/main.go b/main.go index 45904ac..6e7f3fe 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,7 @@ package main import ( + "context" "flag" "log" "net" @@ -17,6 +18,8 @@ func main() { flag.Parse() log.Println("jrouter") + ctx := context.Background() + cfg, err := loadConfig(*configFilePath) if err != nil { log.Fatalf("Couldn't load configuration file: %v", err) @@ -83,7 +86,7 @@ func main() { raddr: raddr, recv: make(chan aurp.Packet, 1024), } - go peer.handle() + go peer.handle(ctx) peers[udpAddrFromNet(raddr)] = peer } @@ -127,7 +130,7 @@ func main() { recv: make(chan aurp.Packet, 1024), } peers[ra] = pr - go pr.handle() + go pr.handle(ctx) } // Pass the packet to the goroutine in charge of this peer. diff --git a/peer.go b/peer.go index 4b9bd4a..51aafe5 100644 --- a/peer.go +++ b/peer.go @@ -2,8 +2,10 @@ package main import ( "bytes" + "context" "log" "net" + "time" "gitea.drjosh.dev/josh/jrouter/aurp" ) @@ -13,6 +15,8 @@ type peer struct { conn *net.UDPConn raddr *net.UDPAddr recv chan aurp.Packet + + lastHeardFrom time.Time } // send encodes and sends pkt to the remote host. @@ -24,94 +28,115 @@ func (p *peer) send(pkt aurp.Packet) (int, error) { return p.conn.WriteToUDP(b.Bytes(), p.raddr) } -func (p *peer) handle() { +func (p *peer) handle(ctx context.Context) error { + ticker := time.NewTicker(1 * time.Second) + defer ticker.Stop() + // Write an Open-Req packet n, err := p.send(p.tr.NewOpenReqPacket(nil)) if err != nil { log.Printf("Couldn't send Open-Req packet: %v", err) - return + return err } log.Printf("Sent Open-Req (len %d) to peer %v", n, p.raddr) - for pkt := range p.recv { - switch pkt := pkt.(type) { - case *aurp.AppleTalkPacket: - // Probably something like: - // - // * parse the DDP header - // * check that this is headed for our local network - // * write the packet out in an EtherTalk frame - // - // or maybe if we were implementing a "central hub" - // - // * parse the DDP header - // * see if we know the network - // * forward to the peer with that network and lowest metric + for { + select { + case <-ctx.Done(): + return ctx.Err() - case *aurp.OpenReqPacket: - // The peer tells us their connection ID in Open-Req. - p.tr.RemoteConnID = pkt.ConnectionID - - // Formulate a response. - var orsp *aurp.OpenRspPacket - switch { - case pkt.Version != 1: - // Respond with Open-Rsp with unknown version error. - orsp = p.tr.NewOpenRspPacket(0, aurp.ErrCodeInvalidVersion, nil) - - case len(pkt.Options) > 0: - // Options? OPTIONS? We don't accept no stinkin' _options_ - orsp = p.tr.NewOpenRspPacket(0, aurp.ErrCodeOptionNegotiation, nil) - - default: - // Accept it I guess. - orsp = p.tr.NewOpenRspPacket(0, 1, nil) + case <-ticker.C: + // TODO: time-based state changes + // Check LHFT, send tickle? + if time.Since(p.lastHeardFrom) > 10*time.Second { + if _, err := p.send(p.tr.NewTicklePacket()); err != nil { + log.Printf("Couldn't send Tickle: %v", err) + } } - log.Printf("Responding with %T", orsp) + case pkt := <-p.recv: + switch pkt := pkt.(type) { + case *aurp.AppleTalkPacket: + // Probably something like: + // + // * parse the DDP header + // * check that this is headed for our local network + // * write the packet out in an EtherTalk frame + // + // or maybe if we were implementing a "central hub" + // + // * parse the DDP header + // * see if we know the network + // * forward to the peer with that network and lowest metric - if _, err := p.send(orsp); err != nil { - log.Printf("Couldn't send Open-Rsp: %v", err) + case *aurp.OpenReqPacket: + // The peer tells us their connection ID in Open-Req. + p.tr.RemoteConnID = pkt.ConnectionID + + // Formulate a response. + var orsp *aurp.OpenRspPacket + switch { + case pkt.Version != 1: + // Respond with Open-Rsp with unknown version error. + orsp = p.tr.NewOpenRspPacket(0, int16(aurp.ErrCodeInvalidVersion), nil) + + case len(pkt.Options) > 0: + // Options? OPTIONS? We don't accept no stinkin' _options_ + orsp = p.tr.NewOpenRspPacket(0, int16(aurp.ErrCodeOptionNegotiation), nil) + + default: + // Accept it I guess. + orsp = p.tr.NewOpenRspPacket(0, 1, nil) + } + + log.Printf("Responding with %T", orsp) + + if _, err := p.send(orsp); err != nil { + log.Printf("Couldn't send Open-Rsp: %v", err) + } + + case *aurp.OpenRspPacket: + if pkt.RateOrErrCode < 0 { + // It's an error code. + log.Printf("Open-Rsp error code from peer %v: %d", p.raddr.IP, pkt.RateOrErrCode) + // Close the connection + } + + // TODO: Make other requests + + case *aurp.RIReqPacket: + // TODO: Respond with RI-Rsp + + case *aurp.RIRspPacket: + // TODO: Repsond with RI-Ack + // TODO: Integrate info into route table + + case *aurp.RIAckPacket: + // TODO: Continue sending next RI-Rsp (streamed) + // TODO: If SZI flag is set, send ZI-Rsp (transaction) + + case *aurp.RIUpdPacket: + // TODO: Integrate info into route table + + case *aurp.RDPacket: + // TODO: Remove router from tables + // TODO: Close connection + log.Printf("Router Down: error code %d %s", pkt.ErrorCode, pkt.ErrorCode) + + case *aurp.ZIReqPacket: + // TODO: Respond with ZI-Rsp + + case *aurp.ZIRspPacket: + // TODO: Integrate info into zone table + + case *aurp.TicklePacket: + if _, err := p.send(p.tr.NewTickleAckPacket()); err != nil { + log.Printf("Couldn't send Tickle-Ack: %v", err) + } + + case *aurp.TickleAckPacket: + p.lastHeardFrom = time.Now() } - - case *aurp.OpenRspPacket: - if pkt.RateOrErrCode < 0 { - // It's an error code. - log.Printf("Open-Rsp error code from peer %v: %d", p.raddr.IP, pkt.RateOrErrCode) - // Close the connection - } - - // TODO: Make other requests - - case *aurp.RIReqPacket: - // TODO: Respond with RI-Rsp - - case *aurp.RIRspPacket: - // TODO: Repsond with RI-Ack - // TODO: Integrate info into route table - - case *aurp.RIAckPacket: - // TODO: Continue sending next RI-Rsp (streamed) - // TODO: If SZI flag is set, send ZI-Rsp (transaction) - - case *aurp.RIUpdPacket: - // TODO: Integrate info into route table - - case *aurp.RDPacket: - // TODO: Remove router from tables - // TODO: Close connection - - case *aurp.ZIReqPacket: - // TODO: Respond with ZI-Rsp - - case *aurp.ZIRspPacket: - // TODO: Integrate info into zone table - - case *aurp.TicklePacket: - // TODO: Respond with TickleAck - - case *aurp.TickleAckPacket: - // TODO: Reset LHFT } } }