From 30f8d709d2a86d9a3fab3bcedc2e34b30d3b0590 Mon Sep 17 00:00:00 2001 From: Josh Deprez Date: Fri, 15 Mar 2024 15:17:21 +1100 Subject: [PATCH] WIP --- aurp/aurp.go | 215 ++++++++++++++++++++++++++++++++++++-------------- aurp/wtacc.go | 61 ++++++++++++++ main.go | 30 ++++++- 3 files changed, 244 insertions(+), 62 deletions(-) create mode 100644 aurp/wtacc.go diff --git a/aurp/aurp.go b/aurp/aurp.go index e9dcebb..f16ca7e 100644 --- a/aurp/aurp.go +++ b/aurp/aurp.go @@ -3,9 +3,9 @@ package aurp import ( - "bytes" "encoding/binary" "fmt" + "io" "net" ) @@ -15,88 +15,85 @@ type DomainHeader struct { DestinationDI DomainIdentifier SourceDI DomainIdentifier Version uint16 // Should always be 0x0001 - PacketType uint16 // 2 = AppleTalk data packet, 3 = AURP packet + Reserved uint16 + PacketType PacketType // 2 = AppleTalk data packet, 3 = AURP packet } -// Encode returns the encoded form of the header. -func (dh *DomainHeader) Encode() ([]byte, error) { - var b bytes.Buffer - ddi, err := dh.DestinationDI.Encode() - if err != nil { - return nil, err - } - sdi, err := dh.SourceDI.Encode() - if err != nil { - return nil, err - } - b.Write(ddi) - b.Write(sdi) - binary.Write(&b, binary.BigEndian, dh.Version) - binary.Write(&b, binary.BigEndian, uint16(0x0000)) // Reserved - binary.Write(&b, binary.BigEndian, dh.PacketType) - return b.Bytes(), nil +// PacketType is used to distinguish domain-header encapsulated packets. +type PacketType uint16 + +// Various packet types. +const ( + PacketTypeAppleTalk PacketType = 0x0002 + PacketTypeRouting PacketType = 0x0003 +) + +// WriteTo writes the encoded form of the domain header to w. +func (dh *DomainHeader) WriteTo(w io.Writer) (int64, error) { + a := acc(w) + a.writeTo(dh.DestinationDI) + a.writeTo(dh.SourceDI) + a.write16(dh.Version) + a.write16(dh.Reserved) + a.write16(uint16(dh.PacketType)) + return a.ret() } -// ParseDH parses a domain header, returning the DH and the remainder of the -// input slice. -func ParseDH(b []byte) (*DomainHeader, []byte, error) { - ddi, b, err := ParseDI(b) +// 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) { + ddi, b, err := ParseDomainIdentifier(b) if err != nil { return nil, b, err } - sdi, b, err := ParseDI(b) + sdi, b, err := ParseDomainIdentifier(b) if err != nil { return nil, b, err } if len(b) < 6 { // sizeof(version + reserved + packettype) return nil, b, fmt.Errorf("insufficient remaining input length %d < 6", len(b)) } - ver := binary.BigEndian.Uint16(b[:2]) - if ver != 1 { - return nil, b, fmt.Errorf("unknown version %d", ver) - } - // Note: b[2:4] (reserved field) is ignored - pt := binary.BigEndian.Uint16(b[4:6]) - if pt != 2 && pt != 3 { - return nil, b, fmt.Errorf("unknown packet type %d", pt) - } - b = b[6:] return &DomainHeader{ DestinationDI: ddi, SourceDI: sdi, - Version: ver, - PacketType: pt, - }, b, nil + Version: binary.BigEndian.Uint16(b[:2]), + Reserved: binary.BigEndian.Uint16(b[2:4]), + PacketType: PacketType(binary.BigEndian.Uint16(b[4:6])), + }, b[6:], nil } // DomainIdentifier is the byte representation of a domain identifier. type DomainIdentifier interface { - Encode() ([]byte, error) + io.WriterTo } -// NullDI represents a null domain identifier. -type NullDI struct{} +// NullDomainIdentifier represents a null domain identifier. +type NullDomainIdentifier struct{} -// Encode returns the encoded form of the identifier. -func (NullDI) Encode() ([]byte, error) { - return []byte{0x01, 0x00}, nil +// WriteTo writes the encoded form of the domain identifier to w. +func (NullDomainIdentifier) WriteTo(w io.Writer) (int64, error) { + n, err := w.Write([]byte{0x01, 0x00}) + return int64(n), err } -// IPDI represents an IP address in a domain identifier. -type IPDI net.IP +// IPDomainIdentifier represents an IP address in a domain identifier. +type IPDomainIdentifier net.IP -// Encode returns the encoded form of the identifier. -func (i IPDI) Encode() ([]byte, error) { +// WriteTo writes the encoded form of the domain identifier to w. +func (i IPDomainIdentifier) WriteTo(w io.Writer) (int64, error) { v4 := net.IP(i).To4() if v4 == nil { - return nil, fmt.Errorf("need v4 IP address, got %v", i) + return 0, fmt.Errorf("need v4 IP address, got %v", i) } - return append([]byte{ - 0x07, // byte 1: length of the DI in bytes - 0x01, // byte 2: authority: 1 = IP address - 0x00, 0x00, // bytes 3, 4: distinguisher: reserved - }, v4...), // bytes 5-8: the IP address - nil + + a := acc(w) + a.write([]byte{ + 0x07, // byte 1: length of the DI, in bytes + 0x01, // byte 2: authority: 1 = IP address + 0x00, 0x00, // bytes 3, 4: distinguisher: reserved) + }) + a.write(v4) // bytes 5-8: IP address + return a.ret() } // Authority represents the different possible authorities ("types") for domain @@ -113,9 +110,9 @@ const ( AuthorityIP ) -// ParseDI parses a DI from the front of b, and returns the DI and the remainder -// of the input slice. -func ParseDI(b []byte) (DomainIdentifier, []byte, error) { +// ParseDomainIdentifier parses a DI from the front of b, and returns the DI and +// the remainder of the input slice. +func ParseDomainIdentifier(b []byte) (DomainIdentifier, []byte, error) { if len(b) < 2 { return nil, b, fmt.Errorf("insufficient input length %d for domain identifier", len(b)) } @@ -128,31 +125,54 @@ func ParseDI(b []byte) (DomainIdentifier, []byte, error) { switch Authority(b[1]) { case AuthorityNull: // That's it, that's the whole DI. - return NullDI{}, b[2:], nil + return NullDomainIdentifier{}, b[2:], nil case AuthorityIP: if lf != 7 { return nil, b, fmt.Errorf("incorrect length %d for IP domain identifier", lf) } - return IPDI(b[5:8]), b[8:], nil + return IPDomainIdentifier(b[5:8]), b[8:], nil default: return nil, b, fmt.Errorf("unknown domain identifier authority %d", b[1]) } } -// TrHeader represent an AURP-Tr packet header. +// TrHeader represent an AURP-Tr packet header. It includes the domain header. type TrHeader struct { + DomainHeader + ConnectionID uint16 Sequence uint16 // Note: 65535 is succeeded by 1, not 0 } -// Header represents an AURP packet header. +// WriteTo writes the encoded form of the header to w. +func (h *TrHeader) WriteTo(w io.Writer) (int64, error) { + a := acc(w) + a.writeTo(&h.DomainHeader) + a.write16(h.ConnectionID) + a.write16(h.Sequence) + return a.ret() +} + +// Header represents an AURP packet header. It includes the AURP-Tr header, +// which includes the domain header. type Header struct { + TrHeader + CommandCode CmdCode Flags RoutingFlag } +// WriteTo writes the encoded form of the header to w. +func (h *Header) WriteTo(w io.Writer) (int64, error) { + a := acc(w) + a.writeTo(&h.TrHeader) + a.write16(uint16(h.CommandCode)) + a.write16(uint16(h.Flags)) + return a.ret() +} + // CmdCode is the command code used in AURP packets. type CmdCode uint16 @@ -203,3 +223,76 @@ const ( // RI-Ack RoutingFlagSendZoneInfo RoutingFlag = 0x4000 ) + +// OptionTuple is used to pass option information in Open-Req and Open-Rsp +// packets. +type OptionTuple struct { + // Length uint8 = 1(for Type) + len(Data) + Type OptionType + Data []byte +} + +func (ot *OptionTuple) WriteTo(w io.Writer) (int64, error) { + a := acc(w) + a.write8(uint8(len(ot.Data) + 1)) + a.write8(uint8(ot.Type)) + a.write(ot.Data) + return a.ret() +} + +// OptionType is used to distinguish different options. +type OptionType uint8 + +// Various option types +const ( + OptionTypeAuthentication OptionType = 0x01 + // All other types reserved +) + +// Packet represents a full AURP packet, not including UDP or lower layers, but +// including the domain header and higher layers. +type Packet interface { + io.WriterTo +} + +// AppleTalkPacket is for encapsulated AppleTalk traffic. +type AppleTalkPacket struct { + DomainHeader // where PacketTypeAppleTalk + Data []byte +} + +func (p *AppleTalkPacket) WriteTo(w io.Writer) (int64, error) { + a := acc(w) + a.writeTo(&p.DomainHeader) + a.write(p.Data) + return a.ret() +} + +// OpenReq is used to open a one-way connection between AIRs. +type OpenReqPacket struct { + Header + + Version uint16 // currently always 1 + //OptionCount uint8 = len(Options) + Options []OptionTuple +} + +func (p *OpenReqPacket) WriteTo(w io.Writer) (int64, error) { + if len(p.Options) > 255 { + return 0, fmt.Errorf("too many options [%d > 255]", len(p.Options)) + } + + a := acc(w) + a.writeTo(&p.Header) + a.write16(p.Version) + a.write8(uint8(len(p.Options))) + for _, o := range p.Options { + a.writeTo(&o) + } + return a.ret() +} + +func ParsePacket(p []byte) (Packet, error) { + // TODO + return nil, nil +} diff --git a/aurp/wtacc.go b/aurp/wtacc.go new file mode 100644 index 0000000..453a3b6 --- /dev/null +++ b/aurp/wtacc.go @@ -0,0 +1,61 @@ +package aurp + +import ( + "encoding/binary" + "io" +) + +// wtacc is a helper for io.WriterTo implementations. +// It sacrifices early returns for a shorter syntax. However, it refuses to +// continue writing to the destination writer after detecting an error. +type wtacc struct { + w io.Writer + n int64 + err error +} + +func acc(w io.Writer) wtacc { return wtacc{w: w} } + +func (a *wtacc) ret() (int64, error) { + return a.n, a.err +} + +func (a *wtacc) write8(x uint8) { + if a.err != nil { + return + } + a.err = binary.Write(a.w, binary.BigEndian, x) + if a.err != nil { + return + } + a.n++ +} + +func (a *wtacc) write16(x uint16) { + if a.err != nil { + return + } + a.err = binary.Write(a.w, binary.BigEndian, x) + if a.err != nil { + return + } + a.n += 2 +} + +func (a *wtacc) write(b []byte) { + if a.err != nil { + return + } + n, err := a.w.Write(b) + a.n += int64(n) + a.err = err +} + +func (a *wtacc) writeTo(wt io.WriterTo) { + if a.err != nil { + return + } + n, err := wt.WriteTo(a.w) + a.n += n + a.err = err +} diff --git a/main.go b/main.go index 7905807..e7f34f6 100644 --- a/main.go +++ b/main.go @@ -1,5 +1,33 @@ package main -func main() { +import ( + "log" + "net" + "gitea.drjosh.dev/josh/jrouter/aurp" +) + +func main() { + log.Println("jrouter") + + ln, err := net.ListenUDP("udp4", &net.UDPAddr{Port: 387}) + if err != nil { + log.Fatalf("Couldn't listen on udp4:387: %v", err) + } + + // Incoming packet loop + pb := make([]byte, 65536) + for { + plen, _, err := ln.ReadFromUDP(pb) + if err != nil { + log.Printf("Failed to read packet: %v", err) + continue + } + + _, err = aurp.ParsePacket(pb[:plen]) + if err != nil { + log.Printf("Failed to parse packet: %v", err) + } + + } }