This commit is contained in:
Josh Deprez 2024-03-15 15:17:21 +11:00
parent b8abe34c28
commit 30f8d709d2
No known key found for this signature in database
3 changed files with 244 additions and 62 deletions

View file

@ -3,9 +3,9 @@
package aurp package aurp
import ( import (
"bytes"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"io"
"net" "net"
) )
@ -15,88 +15,85 @@ type DomainHeader struct {
DestinationDI DomainIdentifier DestinationDI DomainIdentifier
SourceDI DomainIdentifier SourceDI DomainIdentifier
Version uint16 // Should always be 0x0001 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. // PacketType is used to distinguish domain-header encapsulated packets.
func (dh *DomainHeader) Encode() ([]byte, error) { type PacketType uint16
var b bytes.Buffer
ddi, err := dh.DestinationDI.Encode() // Various packet types.
if err != nil { const (
return nil, err PacketTypeAppleTalk PacketType = 0x0002
} PacketTypeRouting PacketType = 0x0003
sdi, err := dh.SourceDI.Encode() )
if err != nil {
return nil, err // WriteTo writes the encoded form of the domain header to w.
} func (dh *DomainHeader) WriteTo(w io.Writer) (int64, error) {
b.Write(ddi) a := acc(w)
b.Write(sdi) a.writeTo(dh.DestinationDI)
binary.Write(&b, binary.BigEndian, dh.Version) a.writeTo(dh.SourceDI)
binary.Write(&b, binary.BigEndian, uint16(0x0000)) // Reserved a.write16(dh.Version)
binary.Write(&b, binary.BigEndian, dh.PacketType) a.write16(dh.Reserved)
return b.Bytes(), nil a.write16(uint16(dh.PacketType))
return a.ret()
} }
// ParseDH parses a domain header, returning the DH and the remainder of the // ParseDomainHeader parses a domain header, returning the DH and the remainder
// input slice. // of the input slice. It does not validate the version or packet type fields.
func ParseDH(b []byte) (*DomainHeader, []byte, error) { func ParseDomainHeader(b []byte) (*DomainHeader, []byte, error) {
ddi, b, err := ParseDI(b) ddi, b, err := ParseDomainIdentifier(b)
if err != nil { if err != nil {
return nil, b, err return nil, b, err
} }
sdi, b, err := ParseDI(b) sdi, b, err := ParseDomainIdentifier(b)
if err != nil { if err != nil {
return nil, b, err return nil, b, err
} }
if len(b) < 6 { // sizeof(version + reserved + packettype) if len(b) < 6 { // sizeof(version + reserved + packettype)
return nil, b, fmt.Errorf("insufficient remaining input length %d < 6", len(b)) 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{ return &DomainHeader{
DestinationDI: ddi, DestinationDI: ddi,
SourceDI: sdi, SourceDI: sdi,
Version: ver, Version: binary.BigEndian.Uint16(b[:2]),
PacketType: pt, Reserved: binary.BigEndian.Uint16(b[2:4]),
}, b, nil PacketType: PacketType(binary.BigEndian.Uint16(b[4:6])),
}, b[6:], nil
} }
// DomainIdentifier is the byte representation of a domain identifier. // DomainIdentifier is the byte representation of a domain identifier.
type DomainIdentifier interface { type DomainIdentifier interface {
Encode() ([]byte, error) io.WriterTo
} }
// NullDI represents a null domain identifier. // NullDomainIdentifier represents a null domain identifier.
type NullDI struct{} type NullDomainIdentifier struct{}
// Encode returns the encoded form of the identifier. // WriteTo writes the encoded form of the domain identifier to w.
func (NullDI) Encode() ([]byte, error) { func (NullDomainIdentifier) WriteTo(w io.Writer) (int64, error) {
return []byte{0x01, 0x00}, nil n, err := w.Write([]byte{0x01, 0x00})
return int64(n), err
} }
// IPDI represents an IP address in a domain identifier. // IPDomainIdentifier represents an IP address in a domain identifier.
type IPDI net.IP type IPDomainIdentifier net.IP
// Encode returns the encoded form of the identifier. // WriteTo writes the encoded form of the domain identifier to w.
func (i IPDI) Encode() ([]byte, error) { func (i IPDomainIdentifier) WriteTo(w io.Writer) (int64, error) {
v4 := net.IP(i).To4() v4 := net.IP(i).To4()
if v4 == nil { 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 a := acc(w)
a.write([]byte{
0x07, // byte 1: length of the DI, in bytes
0x01, // byte 2: authority: 1 = IP address 0x01, // byte 2: authority: 1 = IP address
0x00, 0x00, // bytes 3, 4: distinguisher: reserved 0x00, 0x00, // bytes 3, 4: distinguisher: reserved)
}, v4...), // bytes 5-8: the IP address })
nil a.write(v4) // bytes 5-8: IP address
return a.ret()
} }
// Authority represents the different possible authorities ("types") for domain // Authority represents the different possible authorities ("types") for domain
@ -113,9 +110,9 @@ const (
AuthorityIP AuthorityIP
) )
// ParseDI parses a DI from the front of b, and returns the DI and the remainder // ParseDomainIdentifier parses a DI from the front of b, and returns the DI and
// of the input slice. // the remainder of the input slice.
func ParseDI(b []byte) (DomainIdentifier, []byte, error) { func ParseDomainIdentifier(b []byte) (DomainIdentifier, []byte, error) {
if len(b) < 2 { if len(b) < 2 {
return nil, b, fmt.Errorf("insufficient input length %d for domain identifier", len(b)) 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]) { switch Authority(b[1]) {
case AuthorityNull: case AuthorityNull:
// That's it, that's the whole DI. // That's it, that's the whole DI.
return NullDI{}, b[2:], nil return NullDomainIdentifier{}, b[2:], nil
case AuthorityIP: case AuthorityIP:
if lf != 7 { if lf != 7 {
return nil, b, fmt.Errorf("incorrect length %d for IP domain identifier", lf) 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: default:
return nil, b, fmt.Errorf("unknown domain identifier authority %d", b[1]) 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 { type TrHeader struct {
DomainHeader
ConnectionID uint16 ConnectionID uint16
Sequence uint16 // Note: 65535 is succeeded by 1, not 0 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 { type Header struct {
TrHeader
CommandCode CmdCode CommandCode CmdCode
Flags RoutingFlag 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. // CmdCode is the command code used in AURP packets.
type CmdCode uint16 type CmdCode uint16
@ -203,3 +223,76 @@ const (
// RI-Ack // RI-Ack
RoutingFlagSendZoneInfo RoutingFlag = 0x4000 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
}

61
aurp/wtacc.go Normal file
View file

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

30
main.go
View file

@ -1,5 +1,33 @@
package main 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)
}
}
} }