Stun done

This commit is contained in:
Ludovic Chenut 2023-04-25 11:56:30 +02:00
parent 472306f5ce
commit d1ba2ee0bc
No known key found for this signature in database
GPG Key ID: D9A59B1907F1D50C
3 changed files with 75 additions and 16 deletions

View File

@ -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()

View File

@ -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)

View File

@ -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()