From 19f9d1c89165f8babfd1d879dfd288a2a7baf915 Mon Sep 17 00:00:00 2001 From: Josh Deprez Date: Fri, 22 Mar 2024 16:14:55 +1100 Subject: [PATCH] WIP --- aurp/aurp.go | 2 +- aurp/domain.go | 4 +- aurp/errors.go | 16 +++---- aurp/transport.go | 95 +++++++++++++++++++++++------------------ main.go | 106 ++++++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 168 insertions(+), 55 deletions(-) diff --git a/aurp/aurp.go b/aurp/aurp.go index 2f6ae98..995c805 100644 --- a/aurp/aurp.go +++ b/aurp/aurp.go @@ -91,7 +91,7 @@ type Packet interface { // // (This function contains the big switch statement.) func ParsePacket(p []byte) (Packet, error) { - dh, p, err := parseDomainHeader(p) + dh, p, err := ParseDomainHeader(p) if err != nil { return nil, err } diff --git a/aurp/domain.go b/aurp/domain.go index 67d918a..1bf01d6 100644 --- a/aurp/domain.go +++ b/aurp/domain.go @@ -37,9 +37,9 @@ func (dh *DomainHeader) WriteTo(w io.Writer) (int64, error) { return a.ret() } -// parseDomainHeader parses a domain header, returning the DH and the remainder +// ParseDomainHeader parses a domain header, returning the DH and the remainder // of the input slice. It does not validate the version or packet type fields. -func parseDomainHeader(b []byte) (DomainHeader, []byte, error) { +func ParseDomainHeader(b []byte) (DomainHeader, []byte, error) { ddi, b, err := parseDomainIdentifier(b) if err != nil { return DomainHeader{}, b, err diff --git a/aurp/errors.go b/aurp/errors.go index 0abda7e..2a60f57 100644 --- a/aurp/errors.go +++ b/aurp/errors.go @@ -1,14 +1,14 @@ package aurp -type ErrorCode int16 +type ErrorCode = int16 // Various error codes. const ( - ErrCodeNormalClose ErrorCode = -1 - ErrCodeRoutingLoop ErrorCode = -2 - ErrCodeOutOfSync ErrorCode = -3 - ErrCodeOptionNegotiation ErrorCode = -4 - ErrCodeInvalidVersion ErrorCode = -5 - ErrCodeInsufficientResources ErrorCode = -6 - ErrCodeAuthentication ErrorCode = -7 + ErrCodeNormalClose = -1 + ErrCodeRoutingLoop = -2 + ErrCodeOutOfSync = -3 + ErrCodeOptionNegotiation = -4 + ErrCodeInvalidVersion = -5 + ErrCodeInsufficientResources = -6 + ErrCodeAuthentication = -7 ) diff --git a/aurp/transport.go b/aurp/transport.go index da4e43b..91b357a 100644 --- a/aurp/transport.go +++ b/aurp/transport.go @@ -34,62 +34,62 @@ func parseTrHeader(p []byte) (TrHeader, []byte, error) { }, p[4:], nil } -// incSequence increments the sequence number. -// Note that 0 is special and 65535+1 = 1, according to AURP. -func (tr *TrHeader) incSequence() { - tr.Sequence++ - if tr.Sequence == 0 { - tr.Sequence = 1 +type Transport struct { + // LocalDI and RemoteDI are used for producing packets. + // When sending a packet, we use LocalDI as SourceDI and RemoteDI as + // DestinationDI. + // (When receiving a packet, we expect to see LocalDI as DestinationDI + // - but it might not be - and we expect to see RemoteDI as SourceDI.) + LocalDI, RemoteDI DomainIdentifier + + // LocalConnID is used for packets sent in the role of data receiver. + // RemoteConnID is used for packets sent in the role of data sender. + LocalConnID, RemoteConnID uint16 + + // LocalSeq is used for packets sent (as data sender) + // RemoteSeq is used to check packets received (remote is the data sender). + LocalSeq, RemoteSeq uint16 +} + +// domainHeader returns a new domain header suitable for sending a packet. +func (tr *Transport) domainHeader(pt PacketType) DomainHeader { + return DomainHeader{ + DestinationDI: tr.RemoteDI, + SourceDI: tr.LocalDI, + Version: 1, + Reserved: 0, + PacketType: pt, } } -// transaction returns a new TrHeader based on this one, usable for transaction -// requests or responses. -func (tr *TrHeader) transaction() TrHeader { +// transaction returns a new TrHeader, usable for transaction requests or +// responses. Both data senders and data receivers can send transactions. +// It should be given one of tr.LocalConnID (as data receiver) or +// tr.RemoteConnID (as data sender). +func (tr *Transport) transaction(connID uint16) TrHeader { return TrHeader{ - DomainHeader: DomainHeader{ - DestinationDI: tr.DestinationDI, - SourceDI: tr.SourceDI, - Version: 1, - Reserved: 0, - PacketType: PacketTypeRouting, - }, - ConnectionID: tr.ConnectionID, + DomainHeader: tr.domainHeader(PacketTypeRouting), + ConnectionID: connID, Sequence: 0, // Transaction packets all use sequence number 0. } } -// DataSender is used to track sender state in a one-way AURP connection. -// Note that both data senders and data recievers can send packets. -type DataSender struct { - TrHeader -} - -// NewOpenRspPacket returns a new Open-Rsp packet structure. -func (ds *DataSender) NewOpenRspPacket(envFlags RoutingFlag, rateOrErr int16, opts Options) *OpenRspPacket { - return &OpenRspPacket{ - Header: Header{ - TrHeader: ds.transaction(), - CommandCode: CmdCodeOpenRsp, - Flags: envFlags, - }, - RateOrErrCode: rateOrErr, - Options: opts, +// sequenced returns a new TrHeader usable for sending a sequenced data packet. +// Only data senders send sequenced data. +func (tr *Transport) sequenced(connID, seq uint16) TrHeader { + return TrHeader{ + DomainHeader: tr.domainHeader(PacketTypeRouting), + ConnectionID: connID, + Sequence: seq, } } -// DataReceiver is used to track reciever state in a one-way AURP connection. -// Note that both data senders and data recievers can send packets. -type DataReceiver struct { - TrHeader -} - // NewOpenReqPacket returns a new Open-Req packet structure. By default it sets // all SUI flags and uses version 1. -func (dr *DataReceiver) NewOpenReqPacket(opts Options) *OpenReqPacket { +func (tr *Transport) NewOpenReqPacket(opts Options) *OpenReqPacket { return &OpenReqPacket{ Header: Header{ - TrHeader: dr.transaction(), + TrHeader: tr.transaction(tr.LocalConnID), CommandCode: CmdCodeOpenReq, Flags: RoutingFlagAllSUI, }, @@ -97,3 +97,16 @@ func (dr *DataReceiver) NewOpenReqPacket(opts Options) *OpenReqPacket { Options: opts, } } + +// NewOpenRspPacket returns a new Open-Rsp packet structure. +func (tr *Transport) NewOpenRspPacket(envFlags RoutingFlag, rateOrErr int16, opts Options) *OpenRspPacket { + return &OpenRspPacket{ + Header: Header{ + TrHeader: tr.transaction(tr.RemoteConnID), + CommandCode: CmdCodeOpenRsp, + Flags: envFlags, + }, + RateOrErrCode: rateOrErr, + Options: opts, + } +} diff --git a/main.go b/main.go index 92d98b7..cf85461 100644 --- a/main.go +++ b/main.go @@ -1,15 +1,50 @@ package main import ( + "bytes" + "encoding/binary" + "flag" "log" "net" "gitea.drjosh.dev/josh/jrouter/aurp" ) +var localIPAddr = flag.String("local-ip", "", "IPv4 address to use as the Source Domain Identifier") + func main() { + flag.Parse() log.Println("jrouter") + localIP := net.ParseIP(*localIPAddr).To4() + if localIP == nil { + iaddrs, err := net.InterfaceAddrs() + if err != nil { + log.Fatalf("Couldn't read network interface addresses: %v", err) + } + for _, iaddr := range iaddrs { + inet, ok := iaddr.(*net.IPNet) + if !ok { + continue + } + if !inet.IP.IsGlobalUnicast() { + continue + } + localIP = inet.IP.To4() + if localIP != nil { + break + } + } + if localIP == nil { + log.Fatalf("No global unicast IPv4 addresses on any network interfaces, and no valid address passed with --local-ip") + } + } + + log.Printf("Using %v as local domain identifier", localIP) + + peers := make(map[uint32]*aurp.Transport) + var nextConnID uint16 + ln, err := net.ListenUDP("udp4", &net.UDPAddr{Port: 387}) if err != nil { log.Fatalf("Couldn't listen on udp4:387: %v", err) @@ -18,11 +53,16 @@ func main() { // Incoming packet loop pb := make([]byte, 65536) for { - pktlen, _, readErr := ln.ReadFromUDP(pb) - // "Callers should always process + pktlen, raddr, readErr := ln.ReadFromUDP(pb) + // net.PacketConn.ReadFrom: "Callers should always process // the n > 0 bytes returned before considering the error err." - _, parseErr := aurp.ParsePacket(pb[:pktlen]) + dh, _, err := aurp.ParseDomainHeader(pb[:pktlen]) + if err != nil { + log.Printf("Failed to parse domain header: %v", err) + } + + pkt, parseErr := aurp.ParsePacket(pb[:pktlen]) if parseErr != nil { log.Printf("Failed to parse packet: %v", parseErr) } @@ -31,5 +71,65 @@ func main() { log.Printf("Failed to read packet: %v", readErr) continue } + + // Existing peer? + rip := binary.BigEndian.Uint32(raddr.IP) + tr := peers[rip] + if tr == nil { + // New peer! + tr = &aurp.Transport{ + LocalDI: aurp.IPDomainIdentifier(localIP), + RemoteDI: dh.SourceDI, + LocalConnID: nextConnID, + } + nextConnID++ + peers[rip] = tr + } + + switch p := 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 + + case *aurp.OpenReqPacket: + // The peer tells us their connection ID in Open-Req. + tr.RemoteConnID = p.ConnectionID + + // Formulate a response. + var rp *aurp.OpenRspPacket + if p.Version != 1 { + // Respond with Open-Rsp with unknown version error. + rp = tr.NewOpenRspPacket(0, aurp.ErrCodeInvalidVersion, nil) + } else { + // Accept the connection, I guess? + rp = tr.NewOpenRspPacket(0, 1, nil) + } + + // Write an Open-Rsp packet + var b bytes.Buffer + if _, err := rp.WriteTo(&b); err != nil { + log.Printf("Couldn't create response packet: %v", err) + break + } + if _, err := ln.WriteToUDP(b.Bytes(), raddr); err != nil { + log.Printf("Couldn't write response packet to UDP peer %v: %v", raddr, err) + } + + case *aurp.OpenRspPacket: + if p.RateOrErrCode < 0 { + // It's an error code. + log.Printf("Open-Rsp error code from peer %v: %d", raddr.IP, p.RateOrErrCode) + } + + } } }