mirror of
https://github.com/pion/stun.git
synced 2025-10-06 00:06:51 +08:00
331 lines
9.4 KiB
Go
331 lines
9.4 KiB
Go
package stun
|
||
|
||
import (
|
||
"encoding/binary"
|
||
"fmt"
|
||
"net"
|
||
"strconv"
|
||
|
||
"github.com/pkg/errors"
|
||
)
|
||
|
||
// blank is just blank string and exists just because it is ugly to keep it
|
||
// in code.
|
||
const blank = ""
|
||
|
||
// Attributes is list of message attributes.
|
||
type Attributes []Attribute
|
||
|
||
var (
|
||
// BlankAttribute is attribute that is returned by
|
||
// Attributes.Get if nothing found.
|
||
BlankAttribute = Attribute{}
|
||
)
|
||
|
||
// Get returns first attribute from list which match AttrType. If nothing
|
||
// found, it returns blank attribute.
|
||
func (a Attributes) Get(t AttrType) Attribute {
|
||
for _, candidate := range a {
|
||
if candidate.Type == t {
|
||
return candidate
|
||
}
|
||
}
|
||
return BlankAttribute
|
||
}
|
||
|
||
// 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
|
||
)
|
||
|
||
// Value returns uint16 representation of attribute type.
|
||
func (t AttrType) Value() uint16 {
|
||
return uint16(t)
|
||
}
|
||
|
||
func (t AttrType) String() string {
|
||
switch t {
|
||
case AttrMappedAddress:
|
||
return "MAPPED-ADDRESS"
|
||
case AttrUsername:
|
||
return "USERNAME"
|
||
case AttrErrorCode:
|
||
return "ERROR-CODE"
|
||
case AttrMessageIntegrity:
|
||
return "MESSAGE-INTEGRITY"
|
||
case AttrUnknownAttributes:
|
||
return "UNKNOWN-ATTRIBUTES"
|
||
case AttrRealm:
|
||
return "REALM"
|
||
case AttrNonce:
|
||
return "NONCE"
|
||
case AttrXORMappedAddress:
|
||
return "XOR-MAPPED-ADDRESS"
|
||
case AttrSoftware:
|
||
return "SOFTWARE"
|
||
case AttrAlternateServer:
|
||
return "ALTERNATE-SERVER"
|
||
case AttrFingerprint:
|
||
return "FINGERPRINT"
|
||
case AttrPriority:
|
||
return "PRIORITY"
|
||
case AttrUseCandidate:
|
||
return "USE-CANDIDATE"
|
||
case AttrICEControlled:
|
||
return "ICE-CONTROLLED"
|
||
case AttrICEControlling:
|
||
return "ICE-CONTROLLING"
|
||
case AttrChannelNumber:
|
||
return "CHANNEL-NUMBER"
|
||
case AttrLifetime:
|
||
return "LIFETIME"
|
||
case AttrXORPeerAddress:
|
||
return "XOR-PEER-ADDRESS"
|
||
case AttrData:
|
||
return "DATA"
|
||
case AttrXORRelayedAddress:
|
||
return "XOR-RELAYED-ADDRESS"
|
||
case AttrEvenPort:
|
||
return "EVEN-PORT"
|
||
case AttrRequestedTransport:
|
||
return "REQUESTED-TRANSPORT"
|
||
case AttrDontFragment:
|
||
return "DONT-FRAGMENT"
|
||
case AttrReservationToken:
|
||
return "RESERVATION-TOKEN"
|
||
default:
|
||
// just return hex representation of unknown attribute type
|
||
return "0x" + strconv.FormatUint(uint64(t), 16)
|
||
}
|
||
}
|
||
|
||
// Attribute 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 Attribute struct {
|
||
Type AttrType
|
||
Length uint16
|
||
Value []byte
|
||
}
|
||
|
||
// Equal returns true if a == b.
|
||
func (a Attribute) Equal(b Attribute) 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 Attribute) String() string {
|
||
return fmt.Sprintf("%s: %x", a.Type, a.Value)
|
||
}
|
||
|
||
// getAttrValue returns byte slice that represents attribute value,
|
||
// and if there is no value found, error returned.
|
||
func (m *Message) getAttrValue(t AttrType) ([]byte, error) {
|
||
v := m.Attributes.Get(t).Value
|
||
if len(v) == 0 {
|
||
return nil, errors.Wrap(ErrAttributeNotFound, "failed to find")
|
||
}
|
||
return v, nil
|
||
}
|
||
|
||
// AddSoftwareBytes adds SOFTWARE attribute with value from byte slice.
|
||
func (m *Message) AddSoftwareBytes(software []byte) {
|
||
m.Add(AttrSoftware, software)
|
||
}
|
||
|
||
// AddSoftware adds SOFTWARE attribute with value from string.
|
||
func (m *Message) AddSoftware(software string) {
|
||
m.Add(AttrSoftware, []byte(software))
|
||
}
|
||
|
||
// GetSoftwareBytes returns SOFTWARE attribute value in byte slice.
|
||
// If not found, returns nil.
|
||
func (m *Message) GetSoftwareBytes() []byte {
|
||
return m.Attributes.Get(AttrSoftware).Value
|
||
}
|
||
|
||
// GetSoftware returns SOFTWARE attribute value in string.
|
||
// If not found, returns blank string.
|
||
func (m *Message) GetSoftware() string {
|
||
v := m.GetSoftwareBytes()
|
||
if len(v) == 0 {
|
||
return blank
|
||
}
|
||
return string(v)
|
||
}
|
||
|
||
// Address family values.
|
||
const (
|
||
FamilyIPv4 byte = 0x01
|
||
FamilyIPv6 byte = 0x02
|
||
)
|
||
|
||
// AddXORMappedAddress adds XOR MAPPED ADDRESS attribute to message.
|
||
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.
|
||
value := make([]byte, 32+128)
|
||
value[0] = 0 // first 8 bits are zeroes
|
||
family := FamilyIPv4
|
||
if len(ip) == net.IPv6len {
|
||
family = FamilyIPv6
|
||
}
|
||
binary.BigEndian.PutUint16(value[0:2], uint16(family))
|
||
port ^= magicCookie >> 16
|
||
binary.BigEndian.PutUint16(value[2:4], uint16(port))
|
||
xorValue := make([]byte, 128)
|
||
binary.BigEndian.PutUint32(xorValue[0:4], magicCookie)
|
||
copy(xorValue[4:], m.TransactionID[:])
|
||
xorBytes(value[4:4+len(ip)], ip, xorValue)
|
||
|
||
m.Add(AttrXORMappedAddress, value[:4+len(ip)])
|
||
}
|
||
|
||
func (m *Message) allocBuffer(size int) []byte {
|
||
capacity := len(m.buf.B) + size
|
||
m.grow(capacity)
|
||
m.buf.B = m.buf.B[:capacity]
|
||
return m.buf.B[len(m.buf.B)-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.
|
||
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 len(v) == 0 {
|
||
return nil, 0, errors.Wrap(err, "address not found")
|
||
}
|
||
family := byte(binary.BigEndian.Uint16(v[0:2]))
|
||
if family != FamilyIPv6 && family != FamilyIPv4 {
|
||
err := errors.Wrapf(ErrAttributeDecodeError, "bad family %d", family)
|
||
return nil, 0, err
|
||
}
|
||
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).
|
||
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.Add(AttrErrorCode, value)
|
||
}
|
||
|
||
// AddErrorCodeDefault is wrapper for AddErrorCode that uses recommended
|
||
// reason string from RFC. If error code is unknown, reason will be "Unknown
|
||
// Error".
|
||
func (m *Message) AddErrorCodeDefault(code int) {
|
||
m.AddErrorCode(code, ErrorCode(code).Reason())
|
||
}
|
||
|
||
// GetErrorCode returns ERROR-CODE code, reason and decode error if any.
|
||
func (m *Message) GetErrorCode() (int, []byte, error) {
|
||
v, err := m.getAttrValue(AttrErrorCode)
|
||
if err != nil {
|
||
return 0, nil, errors.Wrap(err, "error not found")
|
||
}
|
||
var (
|
||
class = uint16(v[errorCodeClassByte])
|
||
number = uint16(v[errorCodeNumberByte])
|
||
code = int(class*errorCodeModulo + number)
|
||
reason = v[errorCodeReasonStart:]
|
||
)
|
||
return code, reason, nil
|
||
}
|
||
|
||
var (
|
||
// ErrAttributeNotFound means that there is no such attribute.
|
||
ErrAttributeNotFound Error = "Attribute not found"
|
||
|
||
// ErrAttributeDecodeError means that agent is unable to decode value.
|
||
ErrAttributeDecodeError Error = "Attribute decode error"
|
||
)
|