From 5bb3435dc93b3dc424f3f8ab51b70ba6d68338e5 Mon Sep 17 00:00:00 2001 From: Josh Deprez Date: Fri, 15 Mar 2024 16:15:24 +1100 Subject: [PATCH] WIP --- aurp/aurp.go | 189 ++++++++++++++++++++++++++++++++++++++++++-------- aurp/wtacc.go | 2 +- main.go | 17 +++-- 3 files changed, 170 insertions(+), 38 deletions(-) diff --git a/aurp/aurp.go b/aurp/aurp.go index f16ca7e..d49d83e 100644 --- a/aurp/aurp.go +++ b/aurp/aurp.go @@ -39,14 +39,14 @@ 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) { - ddi, b, err := ParseDomainIdentifier(b) +func parseDomainHeader(b []byte) (*DomainHeader, []byte, error) { + ddi, b, err := parseDomainIdentifier(b) if err != nil { return nil, b, err } - sdi, b, err := ParseDomainIdentifier(b) + sdi, b, err := parseDomainIdentifier(b) if err != nil { return nil, b, err } @@ -110,9 +110,9 @@ const ( AuthorityIP ) -// 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) { +// parseDomainIdentifier parses a DI from the front of b, and returns the DI and +// the remainder of the input slice or an error. +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)) } @@ -140,25 +140,36 @@ func ParseDomainIdentifier(b []byte) (DomainIdentifier, []byte, error) { // TrHeader represent an AURP-Tr packet header. It includes the domain header. type TrHeader struct { - DomainHeader + *DomainHeader ConnectionID uint16 Sequence uint16 // Note: 65535 is succeeded by 1, not 0 } -// WriteTo writes the encoded form of the header to w. +// WriteTo writes the encoded form of the header to w, including the domain +// header. func (h *TrHeader) WriteTo(w io.Writer) (int64, error) { a := acc(w) - a.writeTo(&h.DomainHeader) + a.writeTo(h.DomainHeader) a.write16(h.ConnectionID) a.write16(h.Sequence) return a.ret() } +func parseTrHeader(p []byte) (*TrHeader, []byte, error) { + if len(p) < 4 { + return nil, p, fmt.Errorf("insufficient input length %d for tr header", len(p)) + } + return &TrHeader{ + ConnectionID: binary.BigEndian.Uint16(p[:2]), + Sequence: binary.BigEndian.Uint16(p[2:4]), + }, p[4:], nil +} + // Header represents an AURP packet header. It includes the AURP-Tr header, // which includes the domain header. type Header struct { - TrHeader + *TrHeader CommandCode CmdCode Flags RoutingFlag @@ -167,12 +178,22 @@ type Header struct { // 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.writeTo(h.TrHeader) a.write16(uint16(h.CommandCode)) a.write16(uint16(h.Flags)) return a.ret() } +func parseHeader(p []byte) (*Header, []byte, error) { + if len(p) < 4 { + return nil, p, fmt.Errorf("insufficient input length %d for header", len(p)) + } + return &Header{ + CommandCode: CmdCode(binary.BigEndian.Uint16(p[:2])), + Flags: RoutingFlag(binary.BigEndian.Uint16(p[2:4])), + }, p[4:], nil +} + // CmdCode is the command code used in AURP packets. type CmdCode uint16 @@ -233,6 +254,10 @@ type OptionTuple struct { } func (ot *OptionTuple) WriteTo(w io.Writer) (int64, error) { + if len(ot.Data) > 254 { + return 0, fmt.Errorf("option tuple data too long [%d > 254]", len(ot.Data)) + } + a := acc(w) a.write8(uint8(len(ot.Data) + 1)) a.write8(uint8(ot.Type)) @@ -240,6 +265,20 @@ func (ot *OptionTuple) WriteTo(w io.Writer) (int64, error) { return a.ret() } +func parseOptionTuple(p []byte) (OptionTuple, []byte, error) { + if len(p) < 2 { + return OptionTuple{}, p, fmt.Errorf("insufficient input length %d for option tuple", len(p)) + } + olen := int(p[0]) + 1 + if len(p) < olen { + return OptionTuple{}, p, fmt.Errorf("insufficient input for option tuple data length %d", olen) + } + return OptionTuple{ + Type: OptionType(p[1]), + Data: p[2:olen], + }, p[olen:], nil +} + // OptionType is used to distinguish different options. type OptionType uint8 @@ -249,6 +288,39 @@ const ( // All other types reserved ) +type Options []OptionTuple + +func (o Options) WriteTo(w io.Writer) (int64, error) { + if len(o) > 255 { + return 0, fmt.Errorf("too many options [%d > 255]", len(o)) + } + + a := acc(w) + a.write8(uint8(len(o))) + for _, ot := range o { + a.writeTo(&ot) + } + return a.ret() +} + +func parseOptions(p []byte) (Options, error) { + if len(p) < 1 { + return nil, fmt.Errorf("insufficint input length %d for options", len(p)) + } + optc := p[0] + opts := make([]OptionTuple, optc) + for i := range optc { + ot, np, err := parseOptionTuple(p) + if err != nil { + return nil, fmt.Errorf("parsing option %d: %w", i, err) + } + opts[i] = ot + p = np + } + // TODO: warn about trailing data? + return opts, nil +} + // Packet represents a full AURP packet, not including UDP or lower layers, but // including the domain header and higher layers. type Packet interface { @@ -257,42 +329,99 @@ type Packet interface { // AppleTalkPacket is for encapsulated AppleTalk traffic. type AppleTalkPacket struct { - DomainHeader // where PacketTypeAppleTalk - Data []byte + *DomainHeader // where PacketTypeAppleTalk + + Data []byte } func (p *AppleTalkPacket) WriteTo(w io.Writer) (int64, error) { a := acc(w) - a.writeTo(&p.DomainHeader) + 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 + *Header Version uint16 // currently always 1 - //OptionCount uint8 = len(Options) - Options []OptionTuple + Options Options } 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.writeTo(p.Header) a.write16(p.Version) - a.write8(uint8(len(p.Options))) - for _, o := range p.Options { - a.writeTo(&o) - } + a.writeTo(p.Options) return a.ret() } -func ParsePacket(p []byte) (Packet, error) { - // TODO - return nil, nil +func parseOpenReq(p []byte) (*OpenReqPacket, error) { + if len(p) < 3 { + return nil, fmt.Errorf("insufficient input length %d for Open-Req packet", len(p)) + } + opts, err := parseOptions(p[2:]) + if err != nil { + return nil, err + } + return &OpenReqPacket{ + Version: binary.BigEndian.Uint16(p[:2]), + Options: opts, + }, nil +} + +// ParsePacket parses the body of a UDP packet for a domain header, and then +// based on the packet type, an AURP-Tr header, an AURP routing header, and +// then a particular packet type. +// +// (This function contains the big switch statement.) +func ParsePacket(p []byte) (Packet, error) { + dh, p, err := parseDomainHeader(p) + if err != nil { + return nil, err + } + if dh.Version != 1 { + return nil, fmt.Errorf("unsupported domain header version %d", dh.Version) + } + switch dh.PacketType { + case PacketTypeAppleTalk: + return &AppleTalkPacket{ + DomainHeader: dh, + Data: p, + }, nil + + case PacketTypeRouting: + tr, p, err := parseTrHeader(p) + if err != nil { + return nil, err + } + tr.DomainHeader = dh + h, p, err := parseHeader(p) + if err != nil { + return nil, err + } + h.TrHeader = tr + + switch h.CommandCode { + case CmdCodeOpenReq: + oreq, err := parseOpenReq(p) + if err != nil { + return nil, err + } + oreq.Header = h + return oreq, nil + + case CmdCodeOpenRsp: + orsp, err := parseOpenRsp(p) + if err != nil { + return nil, err + } + orsp.Header = h + return orsp, nil + } + + default: + return nil, fmt.Errorf("unsupported domain header packet type %d", dh.PacketType) + } } diff --git a/aurp/wtacc.go b/aurp/wtacc.go index 453a3b6..a42c4de 100644 --- a/aurp/wtacc.go +++ b/aurp/wtacc.go @@ -24,7 +24,7 @@ func (a *wtacc) write8(x uint8) { if a.err != nil { return } - a.err = binary.Write(a.w, binary.BigEndian, x) + _, a.err = a.w.Write([]byte{x}) if a.err != nil { return } diff --git a/main.go b/main.go index e7f34f6..35f0e6b 100644 --- a/main.go +++ b/main.go @@ -18,15 +18,18 @@ func main() { // 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 + pktlen, _, readErr := ln.ReadFromUDP(pb) + // "Callers should always process + // the n > 0 bytes returned before considering the error err." + + _, parseErr := aurp.ParsePacket(pb[:pktlen]) + if parseErr != nil { + log.Printf("Failed to parse packet: %v", parseErr) } - _, err = aurp.ParsePacket(pb[:plen]) - if err != nil { - log.Printf("Failed to parse packet: %v", err) + if readErr != nil { + log.Printf("Failed to read packet: %v", readErr) + continue } }