jrouter/aurp/domain.go

159 lines
4.7 KiB
Go

/*
Copyright 2024 Josh Deprez
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package aurp
import (
"encoding/binary"
"fmt"
"io"
"net"
)
// DomainHeader represents the header used to encapsulate both AppleTalk data
// packets and AURP packets within UDP.
type DomainHeader struct {
DestinationDI DomainIdentifier
SourceDI DomainIdentifier
Version uint16 // Should always be 0x0001
Reserved uint16 // Should always be 0x0000
PacketType PacketType // 2 = AppleTalk data packet, 3 = AURP packet
}
// 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()
}
// 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 DomainHeader{}, b, err
}
sdi, b, err := parseDomainIdentifier(b)
if err != nil {
return DomainHeader{}, b, err
}
if len(b) < 6 { // sizeof(version + reserved + packettype)
return DomainHeader{}, b, fmt.Errorf("insufficient remaining input length %d < 6", len(b))
}
return DomainHeader{
DestinationDI: ddi,
SourceDI: sdi,
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 {
fmt.Stringer
io.WriterTo
}
// NullDomainIdentifier represents a null domain identifier.
type NullDomainIdentifier struct{}
func (NullDomainIdentifier) String() string { return "(null DI)" }
// 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
}
// IPDomainIdentifier represents an IP address in a domain identifier.
type IPDomainIdentifier net.IP
func (i IPDomainIdentifier) String() string { return net.IP(i).String() }
// 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 0, fmt.Errorf("need v4 IP address, got %v", i)
}
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
// identifiers.
type Authority byte
// Various authorities.
const (
// AuthorityNull is for null domain identifiers, suitable only when there is
// no need to distinguish the domains connected to a tunnel.
AuthorityNull Authority = iota
// AuthorityIP is for
AuthorityIP
)
// 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))
}
// Now we know there is a length byte and authority byte, see if there is
// that much more data
lf := int(b[0])
if len(b) < 1+lf {
return nil, b, fmt.Errorf("input length %d < 1+specified length %d in domain identifier", len(b), lf)
}
switch Authority(b[1]) {
case AuthorityNull:
// That's it, that's the whole DI.
return NullDomainIdentifier{}, b[2:], nil
case AuthorityIP:
if lf != 7 {
return nil, b, fmt.Errorf("incorrect length %d for IP domain identifier", lf)
}
return IPDomainIdentifier(b[4:8]), b[8:], nil
default:
return nil, b, fmt.Errorf("unknown domain identifier authority %d", b[1])
}
}