mirror of
https://github.com/pion/stun.git
synced 2025-10-27 17:51:04 +08:00
491 lines
14 KiB
Go
491 lines
14 KiB
Go
package stun
|
||
|
||
import (
|
||
"encoding/binary"
|
||
"fmt"
|
||
"hash/crc32"
|
||
"net"
|
||
"strconv"
|
||
)
|
||
|
||
// AttrWriter wraps AddRaw method.
|
||
type AttrWriter interface {
|
||
AddRaw(t AttrType, v []byte)
|
||
}
|
||
|
||
// AttrEncoder wraps Encode method.
|
||
type AttrEncoder interface {
|
||
Encode(b []byte, m *Message) (AttrType, []byte, error)
|
||
}
|
||
|
||
// Attributes is list of message attributes.
|
||
type Attributes []RawAttribute
|
||
|
||
// Get returns first attribute from list by the type.
|
||
// If attribute is present the RawAttribute is returned and the
|
||
// boolean is true. Otherwise the returned RawAttribute will be
|
||
// empty and boolean will be false.
|
||
func (a Attributes) Get(t AttrType) (RawAttribute, bool) {
|
||
for _, candidate := range a {
|
||
if candidate.Type == t {
|
||
return candidate, true
|
||
}
|
||
}
|
||
return RawAttribute{}, false
|
||
}
|
||
|
||
// AttrType is attribute type.
|
||
type AttrType uint16
|
||
|
||
// Attributes from comprehension-required range (0x0000-0x7FFF).
|
||
const (
|
||
AttrMappedAddress AttrType = 0x0001 // MAPPED-ADDRESS
|
||
AttrUsername AttrType = 0x0006 // USERNAME
|
||
AttrMessageIntegrity AttrType = 0x0008 // MESSAGE-INTEGRITY
|
||
AttrErrorCode AttrType = 0x0009 // ERROR-CODE
|
||
AttrUnknownAttributes AttrType = 0x000A // UNKNOWN-ATTRIBUTES
|
||
AttrRealm AttrType = 0x0014 // REALM
|
||
AttrNonce AttrType = 0x0015 // NONCE
|
||
AttrXORMappedAddress AttrType = 0x0020 // XOR-MAPPED-ADDRESS
|
||
)
|
||
|
||
// Attributes from comprehension-optional range (0x8000-0xFFFF).
|
||
const (
|
||
AttrSoftware AttrType = 0x8022 // SOFTWARE
|
||
AttrAlternateServer AttrType = 0x8023 // ALTERNATE-SERVER
|
||
AttrFingerprint AttrType = 0x8028 // FINGERPRINT
|
||
)
|
||
|
||
// Attributes from RFC 5245 ICE.
|
||
const (
|
||
AttrPriority AttrType = 0x0024 // PRIORITY
|
||
AttrUseCandidate AttrType = 0x0025 // USE-CANDIDATE
|
||
AttrICEControlled AttrType = 0x8029 // ICE-CONTROLLED
|
||
AttrICEControlling AttrType = 0x802A // ICE-CONTROLLING
|
||
)
|
||
|
||
// Attributes from RFC 5766 TURN.
|
||
const (
|
||
AttrChannelNumber AttrType = 0x000C // CHANNEL-NUMBER
|
||
AttrLifetime AttrType = 0x000D // LIFETIME
|
||
AttrXORPeerAddress AttrType = 0x0012 // XOR-PEER-ADDRESS
|
||
AttrData AttrType = 0x0013 // DATA
|
||
AttrXORRelayedAddress AttrType = 0x0016 // XOR-RELAYED-ADDRESS
|
||
AttrEvenPort AttrType = 0x0018 // EVEN-PORT
|
||
AttrRequestedTransport AttrType = 0x0019 // REQUESTED-TRANSPORT
|
||
AttrDontFragment AttrType = 0x001A // DONT-FRAGMENT
|
||
AttrReservationToken AttrType = 0x0022 // RESERVATION-TOKEN
|
||
)
|
||
|
||
// Attributes from An Origin RawAttribute for the STUN Protocol.
|
||
const (
|
||
AttrOrigin AttrType = 0x802F
|
||
)
|
||
|
||
// Value returns uint16 representation of attribute type.
|
||
func (t AttrType) Value() uint16 {
|
||
return uint16(t)
|
||
}
|
||
|
||
var attrNames = map[AttrType]string{
|
||
AttrMappedAddress: "MAPPED-ADDRESS",
|
||
AttrUsername: "USERNAME",
|
||
AttrErrorCode: "ERROR-CODE",
|
||
AttrMessageIntegrity: "MESSAGE-INTEGRITY",
|
||
AttrUnknownAttributes: "UNKNOWN-ATTRIBUTES",
|
||
AttrRealm: "REALM",
|
||
AttrNonce: "NONCE",
|
||
AttrXORMappedAddress: "XOR-MAPPED-ADDRESS",
|
||
AttrSoftware: "SOFTWARE",
|
||
AttrAlternateServer: "ALTERNATE-SERVER",
|
||
AttrFingerprint: "FINGERPRINT",
|
||
AttrPriority: "PRIORITY",
|
||
AttrUseCandidate: "USE-CANDIDATE",
|
||
AttrICEControlled: "ICE-CONTROLLED",
|
||
AttrICEControlling: "ICE-CONTROLLING",
|
||
AttrChannelNumber: "CHANNEL-NUMBER",
|
||
AttrLifetime: "LIFETIME",
|
||
AttrXORPeerAddress: "XOR-PEER-ADDRESS",
|
||
AttrData: "DATA",
|
||
AttrXORRelayedAddress: "XOR-RELAYED-ADDRESS",
|
||
AttrEvenPort: "EVEN-PORT",
|
||
AttrRequestedTransport: "REQUESTED-TRANSPORT",
|
||
AttrDontFragment: "DONT-FRAGMENT",
|
||
AttrReservationToken: "RESERVATION-TOKEN",
|
||
AttrOrigin: "ORIGIN",
|
||
}
|
||
|
||
func (t AttrType) String() string {
|
||
s, ok := attrNames[t]
|
||
if !ok {
|
||
// just return hex representation of unknown attribute type
|
||
return "0x" + strconv.FormatUint(uint64(t), 16)
|
||
}
|
||
return s
|
||
}
|
||
|
||
// RawAttribute is a Type-Length-Value (TLV) object that
|
||
// can be added to a STUN message. Attributes are divided into two
|
||
// types: comprehension-required and comprehension-optional. STUN
|
||
// agents can safely ignore comprehension-optional attributes they
|
||
// don't understand, but cannot successfully process a message if it
|
||
// contains comprehension-required attributes that are not
|
||
// understood.
|
||
type RawAttribute struct {
|
||
Type AttrType
|
||
Length uint16 // ignored while encoding
|
||
Value []byte
|
||
}
|
||
|
||
// Encode implements AttrEncoder.
|
||
func (a *RawAttribute) Encode(m *Message) ([]byte, error) {
|
||
return m.Raw, nil
|
||
}
|
||
|
||
// Decode implements AttrDecoder.
|
||
func (a *RawAttribute) Decode(v []byte, m *Message) error {
|
||
a.Value = v
|
||
a.Length = uint16(len(v))
|
||
return nil
|
||
}
|
||
|
||
// Equal returns true if a == b.
|
||
func (a RawAttribute) Equal(b RawAttribute) bool {
|
||
if a.Type != b.Type {
|
||
return false
|
||
}
|
||
if a.Length != b.Length {
|
||
return false
|
||
}
|
||
if len(b.Value) != len(a.Value) {
|
||
return false
|
||
}
|
||
for i, v := range a.Value {
|
||
if b.Value[i] != v {
|
||
return false
|
||
}
|
||
}
|
||
return true
|
||
}
|
||
|
||
func (a RawAttribute) String() string {
|
||
return fmt.Sprintf("%s: %x", a.Type, a.Value)
|
||
}
|
||
|
||
// getAttrValue returns byte slice that represents attribute value,
|
||
// if there is no value attribute with shuck type,
|
||
// ErrAttributeNotFound is returned.
|
||
func (m *Message) getAttrValue(t AttrType) ([]byte, error) {
|
||
v, ok := m.Attributes.Get(t)
|
||
if !ok {
|
||
return nil, ErrAttributeNotFound
|
||
}
|
||
return v.Value, nil
|
||
}
|
||
|
||
// AddSoftware adds SOFTWARE attribute with value from string.
|
||
// Deprecated: use AddRaw.
|
||
func (m *Message) AddSoftware(software string) {
|
||
m.AddRaw(AttrSoftware, []byte(software))
|
||
}
|
||
|
||
// Set sets the value of attribute if it presents.
|
||
func (m *Message) Set(a AttrEncoder) error {
|
||
var (
|
||
v []byte
|
||
err error
|
||
t AttrType
|
||
)
|
||
t, v, err = a.Encode(v, m)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
buf, err := m.getAttrValue(t)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if len(v) != len(buf) {
|
||
return ErrBadSetLength
|
||
}
|
||
copy(buf, v)
|
||
return nil
|
||
}
|
||
|
||
// GetSoftwareBytes returns SOFTWARE attribute value in byte slice.
|
||
// If not found, returns nil.
|
||
func (m *Message) GetSoftwareBytes() []byte {
|
||
v, ok := m.Attributes.Get(AttrSoftware)
|
||
if !ok {
|
||
return nil
|
||
}
|
||
return v.Value
|
||
}
|
||
|
||
// GetSoftware returns SOFTWARE attribute value in string.
|
||
// If not found, returns blank string.
|
||
// Deprecated.
|
||
func (m *Message) GetSoftware() string { return string(m.GetSoftwareBytes()) }
|
||
|
||
// Address family values.
|
||
const (
|
||
FamilyIPv4 byte = 0x01
|
||
FamilyIPv6 byte = 0x02
|
||
)
|
||
|
||
// XORMappedAddress implements XOR-MAPPED-ADDRESS attribute.
|
||
type XORMappedAddress struct {
|
||
ip net.IP
|
||
port int
|
||
}
|
||
|
||
// Encode implements AttrEncoder.
|
||
func (a *XORMappedAddress) Encode(buf []byte, m *Message) (AttrType, []byte, error) {
|
||
// X-Port is computed by taking the mapped port in host byte order,
|
||
// XOR’ing it with the most significant 16 bits of the magic cookie, and
|
||
// then the converting the result to network byte order.
|
||
family := FamilyIPv6
|
||
ip := a.ip
|
||
port := a.port
|
||
if ipV4 := ip.To4(); ipV4 != nil {
|
||
ip = ipV4
|
||
family = FamilyIPv4
|
||
}
|
||
value := make([]byte, 32+128)
|
||
value[0] = 0 // first 8 bits are zeroes
|
||
xorValue := make([]byte, net.IPv6len)
|
||
copy(xorValue[4:], m.TransactionID[:])
|
||
binary.BigEndian.PutUint32(xorValue[0:4], magicCookie)
|
||
port ^= magicCookie >> 16
|
||
binary.BigEndian.PutUint16(value[0:2], uint16(family))
|
||
binary.BigEndian.PutUint16(value[2:4], uint16(port))
|
||
xorBytes(value[4:4+len(ip)], ip, xorValue)
|
||
buf = append(buf, value...)
|
||
return AttrXORMappedAddress, buf, nil
|
||
}
|
||
|
||
// Decode implements AttrDecoder.
|
||
// TODO(ar): fix signature.
|
||
func (a *XORMappedAddress) Decode(v []byte, m *Message) error {
|
||
// X-Port is computed by taking the mapped port in host byte order,
|
||
// XOR’ing it with the most significant 16 bits of the magic cookie, and
|
||
// then the converting the result to network byte order.
|
||
v, err := m.getAttrValue(AttrXORMappedAddress)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
family := byte(binary.BigEndian.Uint16(v[0:2]))
|
||
if family != FamilyIPv6 && family != FamilyIPv4 {
|
||
return newDecodeErr("xor-mapped address", "family",
|
||
fmt.Sprintf("bad value %d", family),
|
||
)
|
||
}
|
||
ipLen := net.IPv4len
|
||
if family == FamilyIPv6 {
|
||
ipLen = net.IPv6len
|
||
}
|
||
ip := net.IP(m.allocBuffer(ipLen))
|
||
a.port = int(binary.BigEndian.Uint16(v[2:4])) ^ (magicCookie >> 16)
|
||
xorValue := make([]byte, 128)
|
||
binary.BigEndian.PutUint32(xorValue[0:4], magicCookie)
|
||
copy(xorValue[4:], m.TransactionID[:])
|
||
xorBytes(ip, v[4:], xorValue)
|
||
a.ip = ip
|
||
return nil
|
||
}
|
||
|
||
// AddXORMappedAddress adds XOR MAPPED ADDRESS attribute to message.
|
||
// Deprecated: use AddRaw.
|
||
func (m *Message) AddXORMappedAddress(ip net.IP, port int) {
|
||
// X-Port is computed by taking the mapped port in host byte order,
|
||
// XOR’ing it with the most significant 16 bits of the magic cookie, and
|
||
// then the converting the result to network byte order.
|
||
family := FamilyIPv6
|
||
if ipV4 := ip.To4(); ipV4 != nil {
|
||
ip = ipV4
|
||
family = FamilyIPv4
|
||
}
|
||
value := make([]byte, 32+128)
|
||
value[0] = 0 // first 8 bits are zeroes
|
||
xorValue := make([]byte, net.IPv6len)
|
||
copy(xorValue[4:], m.TransactionID[:])
|
||
binary.BigEndian.PutUint32(xorValue[0:4], magicCookie)
|
||
port ^= magicCookie >> 16
|
||
binary.BigEndian.PutUint16(value[0:2], uint16(family))
|
||
binary.BigEndian.PutUint16(value[2:4], uint16(port))
|
||
xorBytes(value[4:4+len(ip)], ip, xorValue)
|
||
m.AddRaw(AttrXORMappedAddress, value[:4+len(ip)])
|
||
}
|
||
|
||
func (m *Message) allocBuffer(size int) []byte {
|
||
capacity := len(m.Raw) + size
|
||
m.grow(capacity)
|
||
m.Raw = m.Raw[:capacity]
|
||
return m.Raw[len(m.Raw)-size:]
|
||
}
|
||
|
||
// GetXORMappedAddress returns ip, port from attribute and error if any.
|
||
// Value for ip is valid until Message is released or underlying buffer is
|
||
// corrupted. Returns *DecodeError or ErrAttributeNotFound.
|
||
// Deprecated: use GetRaw.
|
||
func (m *Message) GetXORMappedAddress() (net.IP, int, error) {
|
||
// X-Port is computed by taking the mapped port in host byte order,
|
||
// XOR’ing it with the most significant 16 bits of the magic cookie, and
|
||
// then the converting the result to network byte order.
|
||
v, err := m.getAttrValue(AttrXORMappedAddress)
|
||
if err != nil {
|
||
return nil, 0, err
|
||
}
|
||
family := byte(binary.BigEndian.Uint16(v[0:2]))
|
||
if family != FamilyIPv6 && family != FamilyIPv4 {
|
||
return nil, 0, newDecodeErr("xor-mapped address", "family",
|
||
fmt.Sprintf("bad value %d", family),
|
||
)
|
||
}
|
||
ipLen := net.IPv4len
|
||
if family == FamilyIPv6 {
|
||
ipLen = net.IPv6len
|
||
}
|
||
ip := net.IP(m.allocBuffer(ipLen))
|
||
port := int(binary.BigEndian.Uint16(v[2:4])) ^ (magicCookie >> 16)
|
||
xorValue := make([]byte, 128)
|
||
binary.BigEndian.PutUint32(xorValue[0:4], magicCookie)
|
||
copy(xorValue[4:], m.TransactionID[:])
|
||
xorBytes(ip, v[4:], xorValue)
|
||
return ip, port, nil
|
||
}
|
||
|
||
// constants for ERROR-CODE encoding.
|
||
const (
|
||
errorCodeReasonStart = 4
|
||
errorCodeClassByte = 2
|
||
errorCodeNumberByte = 3
|
||
errorCodeReasonMaxB = 763
|
||
errorCodeModulo = 100
|
||
)
|
||
|
||
// AddErrorCode adds ERROR-CODE attribute to message.
|
||
//
|
||
// The reason phrase MUST be a UTF-8 [RFC 3629] encoded
|
||
// sequence of less than 128 characters (which can be as long as 763
|
||
// bytes).
|
||
// Deprecated: use AddRaw.
|
||
func (m *Message) AddErrorCode(code int, reason string) {
|
||
value := make([]byte,
|
||
errorCodeReasonStart, errorCodeReasonMaxB+errorCodeReasonStart,
|
||
)
|
||
number := byte(code % errorCodeModulo) // error code modulo 100
|
||
class := byte(code / errorCodeModulo) // hundred digit
|
||
value[errorCodeClassByte] = class
|
||
value[errorCodeNumberByte] = number
|
||
value = append(value, reason...)
|
||
m.AddRaw(AttrErrorCode, value)
|
||
}
|
||
|
||
// AddErrorCodeDefault is wrapper for AddErrorCode that uses recommended
|
||
// reason string from RFC. If error code is unknown, reason will be "Unknown
|
||
// Error".
|
||
// Deprecated: use AddRaw.
|
||
func (m *Message) AddErrorCodeDefault(code int) {
|
||
m.AddErrorCode(code, ErrorCode(code).Reason())
|
||
}
|
||
|
||
// GetErrorCode returns ERROR-CODE code, reason and decode error if any.
|
||
// Deprecated: use GetRaw.
|
||
func (m *Message) GetErrorCode() (int, []byte, error) {
|
||
v, err := m.getAttrValue(AttrErrorCode)
|
||
if err != nil {
|
||
return 0, nil, err
|
||
}
|
||
var (
|
||
class = uint16(v[errorCodeClassByte])
|
||
number = uint16(v[errorCodeNumberByte])
|
||
code = int(class*errorCodeModulo + number)
|
||
reason = v[errorCodeReasonStart:]
|
||
)
|
||
return code, reason, nil
|
||
}
|
||
|
||
const (
|
||
// ErrAttributeNotFound means that there is no such attribute.
|
||
ErrAttributeNotFound Error = "Attribute not found"
|
||
|
||
// ErrBadSetLength means that previous attribute value length differs from
|
||
// new value.
|
||
ErrBadSetLength Error = "Previous attribute length is different"
|
||
)
|
||
|
||
// Software is SOFTWARE attribute.
|
||
type Software struct {
|
||
Raw []byte
|
||
}
|
||
|
||
func (s Software) String() string {
|
||
return string(s.Raw)
|
||
}
|
||
|
||
// NewSoftware returns *Software from string.
|
||
func NewSoftware(software string) *Software {
|
||
return &Software{Raw: []byte(software)}
|
||
}
|
||
|
||
// Encode implements AttrEncoder.
|
||
func (s *Software) Encode(b []byte, m *Message) (AttrType, []byte, error) {
|
||
return AttrSoftware, append(b, s.Raw...), nil
|
||
}
|
||
|
||
// Decode implements AttrDecoder.
|
||
func (s *Software) Decode(v []byte, m *Message) error {
|
||
s.Raw = v
|
||
return nil
|
||
}
|
||
|
||
const (
|
||
fingerprintXORValue uint32 = 0x5354554e
|
||
)
|
||
|
||
// Fingerprint represents FINGERPRINT attribute.
|
||
type Fingerprint struct {
|
||
Value uint32 // CRC-32 of message XOR-ed with 0x5354554e
|
||
}
|
||
|
||
const (
|
||
fingerprintSize = 4 // 32 bit
|
||
)
|
||
|
||
// AddTo adds fingerprint to message.
|
||
func (f *Fingerprint) AddTo(m *Message) error {
|
||
l := m.Length
|
||
// length in header should include size of fingerprint attribute
|
||
m.Length += fingerprintSize + attributeHeaderSize // increasing length
|
||
m.WriteLength() // writing Length to Raw
|
||
b := make([]byte, fingerprintSize)
|
||
f.Value = crc32.ChecksumIEEE(m.Raw) ^ fingerprintXORValue // XOR
|
||
bin.PutUint32(b, f.Value)
|
||
m.Length = l
|
||
m.AddRaw(AttrFingerprint, b)
|
||
return nil
|
||
}
|
||
|
||
// Check reads fingerprint value from m and checks it, returning error if any.
|
||
// Can return *DecodeErr, ErrAttributeNotFound, ErrCRCMissmatch.
|
||
func (f *Fingerprint) Check(m *Message) error {
|
||
v, err := m.getAttrValue(AttrFingerprint)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if len(v) != fingerprintSize {
|
||
return newDecodeErr("message", "fingerprint", "bad length")
|
||
}
|
||
f.Value = bin.Uint32(v)
|
||
attrStart := len(m.Raw) - (fingerprintSize + attributeHeaderSize)
|
||
expected := crc32.ChecksumIEEE(m.Raw[:attrStart]) ^ fingerprintXORValue
|
||
if expected != f.Value {
|
||
return ErrCRCMissmatch
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// ErrCRCMissmatch means that calculated fingerprint attribute differs from
|
||
// expected one.
|
||
const ErrCRCMissmatch Error = "CRC32 missmatch: bad fingerprint value"
|