Stun done
This commit is contained in:
parent
472306f5ce
commit
d1ba2ee0bc
|
@ -1,8 +1,18 @@
|
||||||
import bitops
|
# Nim-WebRTC
|
||||||
|
# Copyright (c) 2023 Status Research & Development GmbH
|
||||||
|
# Licensed under either of
|
||||||
|
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||||
|
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||||
|
# at your option.
|
||||||
|
# This file may not be copied, modified, or distributed except according to
|
||||||
|
# those terms.
|
||||||
|
|
||||||
|
import bitops, strutils
|
||||||
import chronos,
|
import chronos,
|
||||||
chronicles,
|
chronicles,
|
||||||
binary_serialization,
|
binary_serialization,
|
||||||
stew/objects
|
stew/objects,
|
||||||
|
stew/byteutils
|
||||||
import stunattributes
|
import stunattributes
|
||||||
|
|
||||||
export binary_serialization
|
export binary_serialization
|
||||||
|
@ -18,13 +28,13 @@ const
|
||||||
BindingResponse = 0x0101'u16
|
BindingResponse = 0x0101'u16
|
||||||
|
|
||||||
proc decode(T: typedesc[RawStunAttribute], cnt: seq[byte]): seq[RawStunAttribute] =
|
proc decode(T: typedesc[RawStunAttribute], cnt: seq[byte]): seq[RawStunAttribute] =
|
||||||
const val = @[0, 3, 2, 1]
|
const pad = @[0, 3, 2, 1]
|
||||||
var padding = 0
|
var padding = 0
|
||||||
while padding < cnt.len():
|
while padding < cnt.len():
|
||||||
let attr = Binary.decode(cnt[padding ..^ 1], RawStunAttribute)
|
let attr = Binary.decode(cnt[padding ..^ 1], RawStunAttribute)
|
||||||
result.add(attr)
|
result.add(attr)
|
||||||
padding += 4 + attr.value.len()
|
padding += 4 + attr.value.len()
|
||||||
padding += val[padding mod 4]
|
padding += pad[padding mod 4]
|
||||||
|
|
||||||
type
|
type
|
||||||
# Stun Header
|
# Stun Header
|
||||||
|
@ -50,7 +60,7 @@ type
|
||||||
RawStunMessage = object
|
RawStunMessage = object
|
||||||
msgType: uint16
|
msgType: uint16
|
||||||
# it.conten.len() + 8 Because the Fingerprint is added after the encoding
|
# it.conten.len() + 8 Because the Fingerprint is added after the encoding
|
||||||
length* {.bin_value: it.content.len() + 8.}: uint16
|
length* {.bin_value: it.content.len().}: uint16
|
||||||
magicCookie: uint32
|
magicCookie: uint32
|
||||||
transactionId: array[12, byte]
|
transactionId: array[12, byte]
|
||||||
content* {.bin_len: it.length.}: seq[byte]
|
content* {.bin_len: it.length.}: seq[byte]
|
||||||
|
@ -71,36 +81,56 @@ proc getAttribute(attrs: seq[RawStunAttribute], typ: uint16): Option[seq[byte]]
|
||||||
proc isMessage*(T: typedesc[Stun], msg: seq[byte]): bool =
|
proc isMessage*(T: typedesc[Stun], msg: seq[byte]): bool =
|
||||||
msg.len >= msgHeaderSize and msg[4..<8] == magicCookieSeq 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 addLength(msgEncoded: var seq[byte], length: uint16) =
|
||||||
|
let
|
||||||
|
hi = (length div 256'u16).uint8
|
||||||
|
lo = (length mod 256'u16).uint8
|
||||||
|
msgEncoded[2] = msgEncoded[2] + hi
|
||||||
|
if msgEncoded[3].int + lo.int >= 256:
|
||||||
|
msgEncoded[2] = msgEncoded[2] + 1
|
||||||
|
msgEncoded[3] = ((msgEncoded[3].int + lo.int) mod 256).uint8
|
||||||
|
else:
|
||||||
|
msgEncoded[3] = msgEncoded[3] + lo
|
||||||
|
|
||||||
proc decode*(T: typedesc[StunMessage], msg: seq[byte]): StunMessage =
|
proc decode*(T: typedesc[StunMessage], msg: seq[byte]): StunMessage =
|
||||||
let smi = Binary.decode(msg, RawStunMessage)
|
let smi = Binary.decode(msg, RawStunMessage)
|
||||||
return T(msgType: smi.msgType,
|
return T(msgType: smi.msgType,
|
||||||
transactionId: smi.transactionId,
|
transactionId: smi.transactionId,
|
||||||
attributes: RawStunAttribute.decode(smi.content))
|
attributes: RawStunAttribute.decode(smi.content))
|
||||||
|
|
||||||
proc encode*(msg: StunMessage): seq[byte] =
|
proc encode*(msg: StunMessage, userOpt: Option[seq[byte]]): seq[byte] =
|
||||||
const val = @[0, 3, 2, 1]
|
const pad = @[0, 3, 2, 1]
|
||||||
var smi = RawStunMessage(msgType: msg.msgType,
|
var smi = RawStunMessage(msgType: msg.msgType,
|
||||||
magicCookie: magicCookie,
|
magicCookie: magicCookie,
|
||||||
transactionId: msg.transactionId)
|
transactionId: msg.transactionId)
|
||||||
for attr in msg.attributes:
|
for attr in msg.attributes:
|
||||||
smi.content.add(Binary.encode(attr))
|
smi.content.add(Binary.encode(attr))
|
||||||
smi.content.add(newSeq[byte](val[smi.content.len() mod 4]))
|
smi.content.add(newSeq[byte](pad[smi.content.len() mod 4]))
|
||||||
|
|
||||||
result = Binary.encode(smi)
|
result = Binary.encode(smi)
|
||||||
|
|
||||||
|
if userOpt.isSome():
|
||||||
|
let username = string.fromBytes(userOpt.get())
|
||||||
|
let usersplit = username.split(":")
|
||||||
|
if usersplit.len() == 2 and usersplit[0].startsWith("libp2p+webrtc+v1/"):
|
||||||
|
result.addLength(24)
|
||||||
|
result.add(Binary.encode(MessageIntegrity.encode(result, toBytes(usersplit[0]))))
|
||||||
|
|
||||||
|
result.addLength(8)
|
||||||
result.add(Binary.encode(Fingerprint.encode(result)))
|
result.add(Binary.encode(Fingerprint.encode(result)))
|
||||||
|
|
||||||
proc getResponse*(T: typedesc[Stun], msg: seq[byte],
|
proc getResponse*(T: typedesc[Stun], msg: seq[byte],
|
||||||
ta: TransportAddress): Option[StunMessage] =
|
ta: TransportAddress): Option[seq[byte]] =
|
||||||
if ta.family != AddressFamily.IPv4 and ta.family != AddressFamily.IPv6:
|
if ta.family != AddressFamily.IPv4 and ta.family != AddressFamily.IPv6:
|
||||||
return none(StunMessage)
|
return none(seq[byte])
|
||||||
let sm =
|
let sm =
|
||||||
try:
|
try:
|
||||||
StunMessage.decode(msg)
|
StunMessage.decode(msg)
|
||||||
except CatchableError as exc:
|
except CatchableError as exc:
|
||||||
return none(StunMessage)
|
return none(seq[byte])
|
||||||
|
|
||||||
if sm.msgType != BindingRequest:
|
if sm.msgType != BindingRequest:
|
||||||
return none(StunMessage)
|
return none(seq[byte])
|
||||||
|
|
||||||
var res = StunMessage(msgType: BindingResponse,
|
var res = StunMessage(msgType: BindingResponse,
|
||||||
transactionId: sm.transactionId)
|
transactionId: sm.transactionId)
|
||||||
|
@ -113,10 +143,10 @@ proc getResponse*(T: typedesc[Stun], msg: seq[byte],
|
||||||
if unknownAttr.len() > 0:
|
if unknownAttr.len() > 0:
|
||||||
res.attributes.add(ErrorCode.encode(ECUnknownAttribute))
|
res.attributes.add(ErrorCode.encode(ECUnknownAttribute))
|
||||||
res.attributes.add(UnknownAttribute.encode(unknownAttr))
|
res.attributes.add(UnknownAttribute.encode(unknownAttr))
|
||||||
return some(res)
|
return some(res.encode(sm.attributes.getAttribute(AttrUsername.uint16)))
|
||||||
|
|
||||||
res.attributes.add(XorMappedAddress.encode(ta, sm.transactionId))
|
res.attributes.add(XorMappedAddress.encode(ta, sm.transactionId))
|
||||||
return some(res)
|
return some(res.encode(sm.attributes.getAttribute(AttrUsername.uint16)))
|
||||||
|
|
||||||
proc new*(T: typedesc[Stun]): T =
|
proc new*(T: typedesc[Stun]): T =
|
||||||
result = T()
|
result = T()
|
||||||
|
|
|
@ -1,3 +1,12 @@
|
||||||
|
# Nim-WebRTC
|
||||||
|
# Copyright (c) 2023 Status Research & Development GmbH
|
||||||
|
# Licensed under either of
|
||||||
|
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||||
|
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||||
|
# at your option.
|
||||||
|
# This file may not be copied, modified, or distributed except according to
|
||||||
|
# those terms.
|
||||||
|
|
||||||
import sequtils, typetraits
|
import sequtils, typetraits
|
||||||
import binary_serialization,
|
import binary_serialization,
|
||||||
stew/byteutils,
|
stew/byteutils,
|
||||||
|
@ -150,3 +159,15 @@ proc encode*(T: typedesc[XorMappedAddress], ta: TransportAddress,
|
||||||
result = RawStunAttribute(attributeType: AttrXORMappedAddress.uint16,
|
result = RawStunAttribute(attributeType: AttrXORMappedAddress.uint16,
|
||||||
length: value.len().uint16,
|
length: value.len().uint16,
|
||||||
value: value)
|
value: value)
|
||||||
|
|
||||||
|
# Message Integrity
|
||||||
|
|
||||||
|
type
|
||||||
|
MessageIntegrity* = object
|
||||||
|
msgInt: seq[byte]
|
||||||
|
|
||||||
|
proc encode*(T: typedesc[MessageIntegrity], msg: seq[byte], key: seq[byte]): RawStunAttribute =
|
||||||
|
let value = Binary.encode(T(msgInt: hmacSha1(key, msg)))
|
||||||
|
result = RawStunAttribute(attributeType: AttrMessageIntegrity.uint16,
|
||||||
|
length: value.len().uint16,
|
||||||
|
value: value)
|
||||||
|
|
|
@ -1,3 +1,12 @@
|
||||||
|
# Nim-WebRTC
|
||||||
|
# Copyright (c) 2023 Status Research & Development GmbH
|
||||||
|
# Licensed under either of
|
||||||
|
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
||||||
|
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
||||||
|
# at your option.
|
||||||
|
# This file may not be copied, modified, or distributed except according to
|
||||||
|
# those terms.
|
||||||
|
|
||||||
import chronos, chronicles
|
import chronos, chronicles
|
||||||
import stun
|
import stun
|
||||||
|
|
||||||
|
@ -17,9 +26,8 @@ proc new*(T: typedesc[WebRTC], port: uint16 = 42657): T =
|
||||||
msg = udp.getMessage()
|
msg = udp.getMessage()
|
||||||
if Stun.isMessage(msg):
|
if Stun.isMessage(msg):
|
||||||
let res = Stun.getResponse(msg, address)
|
let res = Stun.getResponse(msg, address)
|
||||||
echo res
|
|
||||||
if res.isSome():
|
if res.isSome():
|
||||||
await udp.sendTo(address, res.get().encode())
|
await udp.sendTo(address, res.get())
|
||||||
|
|
||||||
trace "onReceive", isStun = Stun.isMessage(msg)
|
trace "onReceive", isStun = Stun.isMessage(msg)
|
||||||
if not fut.completed(): fut.complete()
|
if not fut.completed(): fut.complete()
|
||||||
|
|
Loading…
Reference in New Issue