Stun attributes

This commit is contained in:
Ludovic Chenut 2023-04-20 16:58:52 +02:00
parent 2e26deb377
commit 2521ed9f84
No known key found for this signature in database
GPG Key ID: D9A59B1907F1D50C
4 changed files with 160 additions and 31 deletions

View File

@ -7,7 +7,7 @@ suite "Stun suite":
let msg = @[ 0x00'u8, 0x01, 0x00, 0xa4, 0x21, 0x12, 0xa4, 0x42, 0x75, 0x6a, 0x58, 0x46, 0x42, 0x58, 0x4e, 0x72, 0x6a, 0x50, 0x4d, 0x2b, 0x00, 0x06, 0x00, 0x63, 0x6c, 0x69, 0x62, 0x70, 0x32, 0x70, 0x2b, 0x77, 0x65, 0x62, 0x72, 0x74, 0x63, 0x2b, 0x76, 0x31, 0x2f, 0x62, 0x71, 0x36, 0x67, 0x69, 0x43, 0x75, 0x4a, 0x38, 0x6e, 0x78, 0x59, 0x46, 0x4a, 0x36, 0x43, 0x63, 0x67, 0x45, 0x59, 0x58, 0x58, 0x2f, 0x78, 0x51, 0x58, 0x56, 0x4c, 0x74, 0x39, 0x71, 0x7a, 0x3a, 0x6c, 0x69, 0x62, 0x70, 0x32, 0x70, 0x2b, 0x77, 0x65, 0x62, 0x72, 0x74, 0x63, 0x2b, 0x76, 0x31, 0x2f, 0x62, 0x71, 0x36, 0x67, 0x69, 0x43, 0x75, 0x4a, 0x38, 0x6e, 0x78, 0x59, 0x46, 0x4a, 0x36, 0x43, 0x63, 0x67, 0x45, 0x59, 0x58, 0x58, 0x2f, 0x78, 0x51, 0x58, 0x56, 0x4c, 0x74, 0x39, 0x71, 0x7a, 0x00, 0xc0, 0x57, 0x00, 0x04, 0x00, 0x00, 0x03, 0xe7, 0x80, 0x2a, 0x00, 0x08, 0x86, 0x63, 0xfd, 0x45, 0xa9, 0xe5, 0x4c, 0xdb, 0x00, 0x24, 0x00, 0x04, 0x6e, 0x00, 0x1e, 0xff, 0x00, 0x08, 0x00, 0x14, 0x16, 0xff, 0x70, 0x8d, 0x97, 0x0b, 0xd6, 0xa3, 0x5b, 0xac, 0x8f, 0x4c, 0x85, 0xe6, 0xa6, 0xac, 0xaa, 0x7a, 0x68, 0x27, 0x80, 0x28, 0x00, 0x04, 0x79, 0x5e, 0x03, 0xd8 ]
check msg == encode(StunMessage.decode(msg))
test "Error while encoding":
test "Error while decoding":
let msgLengthFailed = @[ 0x00'u8, 0x01, 0x00, 0xa4, 0x21, 0x12, 0xa4, 0x42, 0x75, 0x6a, 0x58, 0x46, 0x42, 0x58, 0x4e, 0x72, 0x6a, 0x50, 0x4d ]
expect AssertionDefect: discard StunMessage.decode(msgLengthFailed)
let msgAttrFailed = @[ 0x00'u8, 0x01, 0x00, 0x08, 0x21, 0x12, 0xa4, 0x42, 0x75, 0x6a, 0x58, 0x46, 0x42, 0x58, 0x4e, 0x72, 0x6a, 0x50, 0x4d, 0x2b, 0x28, 0x00, 0x05, 0x79, 0x5e, 0x03, 0xd8 ]

View File

@ -1,6 +1,11 @@
import bitops
import chronos, chronicles
import binary_serialization
import chronos,
chronicles,
binary_serialization,
stew/objects
import stunattributes
export binary_serialization
logScope:
topics = "webrtc stun"
@ -9,34 +14,18 @@ const
msgHeaderSize = 20
magicCookieSeq = @[ 0x21'u8, 0x12, 0xa4, 0x42 ]
magicCookie = 0x2112a442
BindingRequest = 0x0001'u16
BindingResponse = 0x0101'u16
type
# Stun Attribute
# 0 1 2 3
# 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
# | Type | Length |
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
# | Value (variable) ....
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
StunAttribute* = object
attributeType*: uint16
length* {.bin_value: it.value.len.}: uint16
value* {.bin_len: it.length.}: seq[byte]
proc decode(T: typedesc[StunAttribute], cnt: seq[byte]): seq[StunAttribute] =
proc decode(T: typedesc[RawStunAttribute], cnt: seq[byte]): seq[RawStunAttribute] =
const val = @[0, 3, 2, 1]
var padding = 0
while padding < cnt.len():
let attr = Binary.decode(cnt[padding ..^ 1], StunAttribute)
let attr = Binary.decode(cnt[padding ..^ 1], RawStunAttribute)
result.add(attr)
padding += 4 + attr.value.len()
padding += val[padding mod 4]
proc seqAttrLen(s: seq[StunAttribute]): uint16 =
for it in s:
result = it.length + 4
type
# Stun Header
# 0 1 2 3
@ -50,32 +39,46 @@ type
# | Transaction ID (96 bits) |
# | |
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
StunMessageInner = object
# Message type:
# 0x0001: Binding Request
# 0x0101: Binding Response
# 0x0111: Binding Error Response
# 0x0002: Shared Secret Request
# 0x0102: Shared Secret Response
# 0x0112: Shared Secret Error Response
RawStunMessage = object
msgType: uint16
length* {.bin_value: it.content.len().}: uint16
magicCookie: uint32
transactionId: array[12, byte]
content* {.bin_len: it.length.}: seq[byte]
StunMessage* = object
msgType*: uint16
transactionId*: array[12, byte]
attributes*: seq[StunAttribute]
attributes*: seq[RawStunAttribute]
Stun* = object
proc getAttribute(attrs: seq[RawStunAttribute], typ: uint16): Option[seq[byte]] =
for attr in attrs:
if attr.attributeType == typ:
return some(attr.value)
return none(seq[byte])
proc isMessage*(T: typedesc[Stun], msg: seq[byte]): bool =
msg.len >= msgHeaderSize and msg[4..<8] == magicCookie and bitand(0xC0'u8, msg[0]) == 0'u8
msg.len >= msgHeaderSize and msg[4..<8] == magicCookieSeq and bitand(0xC0'u8, msg[0]) == 0'u8
proc decode*(T: typedesc[StunMessage], msg: seq[byte]): StunMessage =
let smi = Binary.decode(msg, StunMessageInner)
let smi = Binary.decode(msg, RawStunMessage)
return T(msgType: smi.msgType,
transactionId: smi.transactionId,
attributes: StunAttribute.decode(smi.content))
attributes: RawStunAttribute.decode(smi.content))
proc encode*(msg: StunMessage): seq[byte] =
const val = @[0, 3, 2, 1]
var smi = StunMessageInner(msgType: msg.msgType,
var smi = RawStunMessage(msgType: msg.msgType,
magicCookie: magicCookie,
transactionId: msg.transactionId)
for attr in msg.attributes:
@ -84,5 +87,31 @@ proc encode*(msg: StunMessage): seq[byte] =
return Binary.encode(smi)
proc getResponse*(T: typedesc[Stun], msg: seq[byte],
address: TransportAddress): Option[StunMessage] =
let sm =
try:
StunMessage.decode(msg)
except CatchableError as exc:
return none(StunMessage)
if sm.msgType != BindingRequest:
return none(StunMessage)
var res = StunMessage(msgType: BindingResponse,
transactionId: sm.transactionId)
var unknownAttr: seq[uint16]
for attr in sm.attributes:
let typ = attr.attributeType
if typ.isRequired() and typ notin StunAttributeEnum:
unknownAttr.add(typ)
if unknownAttr.len() > 0:
res.attributes.add(ErrorCode.encode(ECUnknownAttribute))
res.attributes.add(UnknownAttribute.encode(unknownAttr))
return some(res)
#if sm.attributes.getAttribute())
proc new*(T: typedesc[Stun]): T =
result = T()

100
webrtc/stunattributes.nim Normal file
View File

@ -0,0 +1,100 @@
import binary_serialization,
stew/byteutils
type
# Stun Attribute
# 0 1 2 3
# 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
# | Type | Length |
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
# | Value (variable) ....
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
RawStunAttribute* = object
attributeType*: uint16
length* {.bin_value: it.value.len.}: uint16
value* {.bin_len: it.length.}: seq[byte]
StunAttributeEnum* = enum
AttrMappedAddress = 0x0001
AttrChangeRequest = 0x0003 # RFC5780 Nat Behavior Discovery
AttrSourceAddress = 0x0004 # Deprecated
AttrChangedAddress = 0x0005 # Deprecated
AttrUsername = 0x0006
AttrMessageIntegrity = 0x0008
AttrErrorCode = 0x0009
AttrUnknownAttributes = 0x000A
AttrChannelNumber = 0x000C # RFC5766 TURN
AttrLifetime = 0x000D # RFC5766 TURN
AttrXORPeerAddress = 0x0012 # RFC5766 TURN
AttrData = 0x0013 # RFC5766 TURN
AttrRealm = 0x0014
AttrNonce = 0x0015
AttrXORRelayedAddress = 0x0016 # RFC5766 TURN
AttrRequestedAddressFamily = 0x0017 # RFC6156
AttrEvenPort = 0x0018 # RFC5766 TURN
AttrRequestedTransport = 0x0019 # RFC5766 TURN
AttrDontFragment = 0x001A # RFC5766 TURN
AttrMessageIntegritySHA256 = 0x001C # RFC8489 STUN (v2)
AttrPasswordAlgorithm = 0x001D # RFC8489 STUN (v2)
AttrUserhash = 0x001E # RFC8489 STUN (v2)
AttrXORMappedAddress = 0x0020
AttrReservationToken = 0x0022 # RFC5766 TURN
AttrPriority = 0x0024 # RFC5245 ICE
AttrUseCandidate = 0x0025 # RFC5245 ICE
AttrPadding = 0x0026 # RFC5780 Nat Behavior Discovery
AttrResponsePort = 0x0027 # RFC5780 Nat Behavior Discovery
AttrConnectionID = 0x002a # RFC6062 TURN Extensions
AttrPasswordAlgorithms = 0x8002 # RFC8489 STUN (v2)
AttrAlternateDomain = 0x8003 # RFC8489 STUN (v2)
AttrSoftware = 0x8022
AttrAlternateServer = 0x8023
AttrCacheTimeout = 0x8027 # RFC5780 Nat Behavior Discovery
AttrFingerprint = 0x8028
AttrICEControlled = 0x8029 # RFC5245 ICE
AttrICEControlling = 0x802A # RFC5245 ICE
AttrResponseOrigin = 0x802b # RFC5780 Nat Behavior Discovery
AttrOtherAddress = 0x802C # RFC5780 Nat Behavior Discovery
AttrOrigin = 0x802F
proc isRequired*(typ: uint16): bool = typ <= 0x7FFF'u16
proc isOptional*(typ: uint16): bool = typ >= 0x8000'u16
# Error Code
type
ErrorCodeEnum* = enum
ECTryAlternate = 300
ECBadRequest = 400
ECUnauthenticated = 401
ECUnknownAttribute = 420
ECStaleNonce = 438
ECServerError = 500
ErrorCode* = object
reserved1: uint16 # should be 0
reserved2 {.bin_bitsize: 5.}: uint8 # should be 0
class {.bin_bitsize: 3.}: uint8
number: uint8
reason: seq[byte]
proc encode*(T: typedesc[ErrorCode], code: ErrorCodeEnum, reason: string = ""): RawStunAttribute =
let
ec = T(class: (code.uint16 div 100'u16).uint8,
number: (code.uint16 mod 100'u16).uint8,
reason: reason.toBytes())
value = Binary.encode(ec)
result = RawStunAttribute(attributeType: AttrErrorCode.uint16,
length: value.len().uint16,
value: value)
# Unknown Attribute
type
UnknownAttribute* = object
unknownAttr: seq[uint16]
proc encode*(T: typedesc[UnknownAttribute], unknownAttr: seq[uint16]): RawStunAttribute =
let
ua = T(unknownAttr: unknownAttr)
value = Binary.encode(ua)
result = RawStunAttribute(attributeType: AttrUnknownAttributes.uint16,
length: value.len().uint16,
value: value)

View File

@ -9,7 +9,7 @@ const usrsctpInclude = root/"usrsctp"/"usrsctplib"
{.passc: fmt"-I{usrsctpInclude}".}
# Generated @ 2022-11-23T14:21:00+01:00
# Generated @ 2023-03-30T13:55:23+02:00
# Command line:
# /home/lchenut/.nimble/pkgs/nimterop-0.6.13/nimterop/toast --compile=./usrsctp/usrsctplib/netinet/sctp_input.c --compile=./usrsctp/usrsctplib/netinet/sctp_asconf.c --compile=./usrsctp/usrsctplib/netinet/sctp_pcb.c --compile=./usrsctp/usrsctplib/netinet/sctp_usrreq.c --compile=./usrsctp/usrsctplib/netinet/sctp_cc_functions.c --compile=./usrsctp/usrsctplib/netinet/sctp_auth.c --compile=./usrsctp/usrsctplib/netinet/sctp_userspace.c --compile=./usrsctp/usrsctplib/netinet/sctp_output.c --compile=./usrsctp/usrsctplib/netinet/sctp_callout.c --compile=./usrsctp/usrsctplib/netinet/sctp_crc32.c --compile=./usrsctp/usrsctplib/netinet/sctp_sysctl.c --compile=./usrsctp/usrsctplib/netinet/sctp_sha1.c --compile=./usrsctp/usrsctplib/netinet/sctp_timer.c --compile=./usrsctp/usrsctplib/netinet/sctputil.c --compile=./usrsctp/usrsctplib/netinet/sctp_bsd_addr.c --compile=./usrsctp/usrsctplib/netinet/sctp_peeloff.c --compile=./usrsctp/usrsctplib/netinet/sctp_indata.c --compile=./usrsctp/usrsctplib/netinet/sctp_ss_functions.c --compile=./usrsctp/usrsctplib/user_socket.c --compile=./usrsctp/usrsctplib/netinet6/sctp6_usrreq.c --compile=./usrsctp/usrsctplib/user_mbuf.c --compile=./usrsctp/usrsctplib/user_environment.c --compile=./usrsctp/usrsctplib/user_recv_thread.c --pnim --preprocess --noHeader --defines=SCTP_PROCESS_LEVEL_LOCKS --defines=SCTP_SIMPLE_ALLOCATOR --defines=__Userspace__ --defines=STDC_HEADERS=1 --defines=HAVE_SYS_TYPES_H=1 --defines=HAVE_SYS_STAT_H=1 --defines=HAVE_STDLIB_H=1 --defines=HAVE_STRING_H=1 --defines=HAVE_MEMORY_H=1 --defines=HAVE_STRINGS_H=1 --defines=HAVE_INTTYPES_H=1 --defines=HAVE_STDINT_H=1 --defines=HAVE_UNISTD_H=1 --defines=HAVE_DLFCN_H=1 --defines=LT_OBJDIR=".libs/" --defines=SCTP_DEBUG=1 --defines=INET=1 --defines=INET6=1 --defines=HAVE_SOCKET=1 --defines=HAVE_INET_ADDR=1 --defines=HAVE_STDATOMIC_H=1 --defines=HAVE_SYS_QUEUE_H=1 --defines=HAVE_LINUX_IF_ADDR_H=1 --defines=HAVE_LINUX_RTNETLINK_H=1 --defines=HAVE_NETINET_IP_ICMP_H=1 --defines=HAVE_NET_ROUTE_H=1 --defines=_GNU_SOURCE --replace=sockaddr=SockAddr --replace=SockAddr_storage=Sockaddr_storage --replace=SockAddr_in=Sockaddr_in --replace=SockAddr_conn=Sockaddr_conn --replace=socklen_t=SockLen --includeDirs=./usrsctp/usrsctplib ./usrsctp/usrsctplib/usrsctp.h