From f1707f56c8247b652a14206be26076ca4c42a33a Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Fri, 8 Mar 2024 13:57:01 +0100 Subject: [PATCH 01/42] feat: dtls connection using mbedtls --- webrtc/dtls/dtls.nim | 377 ++++++++++++++++++++++++++++++++++++++++++ webrtc/dtls/utils.nim | 96 +++++++++++ 2 files changed, 473 insertions(+) create mode 100644 webrtc/dtls/dtls.nim create mode 100644 webrtc/dtls/utils.nim diff --git a/webrtc/dtls/dtls.nim b/webrtc/dtls/dtls.nim new file mode 100644 index 0000000..1bc4cbf --- /dev/null +++ b/webrtc/dtls/dtls.nim @@ -0,0 +1,377 @@ +# Nim-WebRTC +# Copyright (c) 2024 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 times, deques, tables, sequtils +import chronos, chronicles +import ./utils, ../stun/stun_connection + +import mbedtls/ssl +import mbedtls/ssl_cookie +import mbedtls/ssl_cache +import mbedtls/pk +import mbedtls/md +import mbedtls/entropy +import mbedtls/ctr_drbg +import mbedtls/rsa +import mbedtls/x509 +import mbedtls/x509_crt +import mbedtls/bignum +import mbedtls/error +import mbedtls/net_sockets +import mbedtls/timing + +logScope: + topics = "webrtc dtls" + +# Implementation of a DTLS client and a DTLS Server by using the mbedtls library. +# Multiple things here are unintuitive partly because of the callbacks +# used by mbedtls and that those callbacks cannot be async. +# +# TODO: +# - Check the viability of the add/pop first/last of the asyncqueue with the limit. +# There might be some errors (or crashes) with some edge cases with the no wait option +# - Not critical - Check how to make a better use of MBEDTLS_ERR_SSL_WANT_WRITE +# - Not critical - May be interesting to split Dtls and DtlsConn into two files + +# This limit is arbitrary, it could be interesting to make it configurable. +const PendingHandshakeLimit = 1024 + +# -- DtlsConn -- +# A Dtls connection to a specific IP address recovered by the receiving part of +# the Udp "connection" + +type + DtlsError* = object of CatchableError + DtlsConn* = ref object + conn: StunConn + laddr: TransportAddress + raddr*: TransportAddress + dataRecv: AsyncQueue[seq[byte]] + sendFuture: Future[void] + closed: bool + closeEvent: AsyncEvent + + timer: mbedtls_timing_delay_context + + ssl: mbedtls_ssl_context + config: mbedtls_ssl_config + cookie: mbedtls_ssl_cookie_ctx + cache: mbedtls_ssl_cache_context + + ctr_drbg: mbedtls_ctr_drbg_context + entropy: mbedtls_entropy_context + + localCert: seq[byte] + remoteCert: seq[byte] + +proc init(self: DtlsConn, conn: StunConn, laddr: TransportAddress) = + self.conn = conn + self.laddr = laddr + self.dataRecv = newAsyncQueue[seq[byte]]() + self.closed = false + self.closeEvent = newAsyncEvent() + +proc join(self: DtlsConn) {.async.} = + await self.closeEvent.wait() + +proc dtlsHandshake(self: DtlsConn, isServer: bool) {.async.} = + var shouldRead = isServer + while self.ssl.private_state != MBEDTLS_SSL_HANDSHAKE_OVER: + if shouldRead: + if isServer: + case self.raddr.family + of AddressFamily.IPv4: + mb_ssl_set_client_transport_id(self.ssl, self.raddr.address_v4) + of AddressFamily.IPv6: + mb_ssl_set_client_transport_id(self.ssl, self.raddr.address_v6) + else: + raise newException(DtlsError, "Remote address isn't an IP address") + let tmp = await self.dataRecv.popFirst() + self.dataRecv.addFirstNoWait(tmp) + self.sendFuture = nil + let res = mb_ssl_handshake_step(self.ssl) + if not self.sendFuture.isNil(): + await self.sendFuture + shouldRead = false + if res == MBEDTLS_ERR_SSL_WANT_WRITE: + continue + elif res == MBEDTLS_ERR_SSL_WANT_READ: + shouldRead = true + continue + elif res == MBEDTLS_ERR_SSL_HELLO_VERIFY_REQUIRED: + mb_ssl_session_reset(self.ssl) + shouldRead = isServer + continue + elif res != 0: + raise newException(DtlsError, $(res.mbedtls_high_level_strerr())) + +proc close*(self: DtlsConn) {.async.} = + if self.closed: + debug "Try to close DtlsConn twice" + return + + self.closed = true + self.sendFuture = nil + # TODO: proc mbedtls_ssl_close_notify => template mb_ssl_close_notify in nim-mbedtls + let x = mbedtls_ssl_close_notify(addr self.ssl) + if not self.sendFuture.isNil(): + await self.sendFuture + self.closeEvent.fire() + +proc write*(self: DtlsConn, msg: seq[byte]) {.async.} = + if self.closed: + debug "Try to write on an already closed DtlsConn" + return + var buf = msg + try: + let sendFuture = newFuture[void]("DtlsConn write") + self.sendFuture = nil + let write = mb_ssl_write(self.ssl, buf) + if not self.sendFuture.isNil(): + await self.sendFuture + trace "Dtls write", msgLen = msg.len(), actuallyWrote = write + except MbedTLSError as exc: + trace "Dtls write error", errorMsg = exc.msg + raise exc + +proc read*(self: DtlsConn): Future[seq[byte]] {.async.} = + if self.closed: + debug "Try to read on an already closed DtlsConn" + return + var res = newSeq[byte](8192) + while true: + let tmp = await self.dataRecv.popFirst() + self.dataRecv.addFirstNoWait(tmp) + # TODO: Find a clear way to use the template `mb_ssl_read` without + # messing up things with exception + let length = mbedtls_ssl_read(addr self.ssl, cast[ptr byte](addr res[0]), res.len().uint) + if length == MBEDTLS_ERR_SSL_WANT_READ: + continue + if length < 0: + raise newException(DtlsError, $(length.cint.mbedtls_high_level_strerr())) + res.setLen(length) + return res + +# -- Dtls -- +# The Dtls object read every messages from the UdpConn/StunConn and, if the address +# is not yet stored in the Table `Connection`, adds it to the `pendingHandshake` queue +# to be accepted later, if the address is stored, add the message received to the +# corresponding DtlsConn `dataRecv` queue. + +type + Dtls* = ref object of RootObj + connections: Table[TransportAddress, DtlsConn] + pendingHandshakes: AsyncQueue[(TransportAddress, seq[byte])] + conn: StunConn + laddr: TransportAddress + started: bool + readLoop: Future[void] + ctr_drbg: mbedtls_ctr_drbg_context + entropy: mbedtls_entropy_context + + serverPrivKey: mbedtls_pk_context + serverCert: mbedtls_x509_crt + localCert: seq[byte] + +proc updateOrAdd(aq: AsyncQueue[(TransportAddress, seq[byte])], + raddr: TransportAddress, buf: seq[byte]) = + for kv in aq.mitems(): + if kv[0] == raddr: + kv[1] = buf + return + aq.addLastNoWait((raddr, buf)) + +proc init*(self: Dtls, conn: StunConn, laddr: TransportAddress) = + if self.started: + warn "Already started" + return + + proc readLoop() {.async.} = + while true: + let (buf, raddr) = await self.conn.read() + if self.connections.hasKey(raddr): + self.connections[raddr].dataRecv.addLastNoWait(buf) + else: + self.pendingHandshakes.updateOrAdd(raddr, buf) + + self.connections = initTable[TransportAddress, DtlsConn]() + self.pendingHandshakes = newAsyncQueue[(TransportAddress, seq[byte])](PendingHandshakeLimit) + self.conn = conn + self.laddr = laddr + self.started = true + self.readLoop = readLoop() + + mb_ctr_drbg_init(self.ctr_drbg) + mb_entropy_init(self.entropy) + mb_ctr_drbg_seed(self.ctr_drbg, mbedtls_entropy_func, self.entropy, nil, 0) + + self.serverPrivKey = self.ctr_drbg.generateKey() + self.serverCert = self.ctr_drbg.generateCertificate(self.serverPrivKey) + self.localCert = newSeq[byte](self.serverCert.raw.len) + copyMem(addr self.localCert[0], self.serverCert.raw.p, self.serverCert.raw.len) + +proc stop*(self: Dtls) {.async.} = + if not self.started: + warn "Already stopped" + return + + await allFutures(toSeq(self.connections.values()).mapIt(it.close())) + self.readLoop.cancel() + self.started = false + +# -- Remote / Local certificate getter -- + +proc remoteCertificate*(conn: DtlsConn): seq[byte] = + conn.remoteCert + +proc localCertificate*(conn: DtlsConn): seq[byte] = + conn.localCert + +proc localCertificate*(self: Dtls): seq[byte] = + self.localCert + +# -- MbedTLS Callbacks -- + +proc verify(ctx: pointer, pcert: ptr mbedtls_x509_crt, + state: cint, pflags: ptr uint32): cint {.cdecl.} = + # verify is the procedure called by mbedtls when receiving the remote + # certificate. It's usually used to verify the validity of the certificate. + # We use this procedure to store the remote certificate as it's mandatory + # to have it for the Prologue of the Noise protocol, aswell as the localCertificate. + var self = cast[DtlsConn](ctx) + let cert = pcert[] + + self.remoteCert = newSeq[byte](cert.raw.len) + copyMem(addr self.remoteCert[0], cert.raw.p, cert.raw.len) + return 0 + +proc dtlsSend(ctx: pointer, buf: ptr byte, len: uint): cint {.cdecl.} = + # dtlsSend is the procedure called by mbedtls when data needs to be sent. + # As the StunConn's write proc is asynchronous and dtlsSend cannot be async, + # we store the future of this write and await it after the end of the + # function (see write or dtlsHanshake for example). + var self = cast[DtlsConn](ctx) + var toWrite = newSeq[byte](len) + if len > 0: + copyMem(addr toWrite[0], buf, len) + trace "dtls send", len + self.sendFuture = self.conn.write(self.raddr, toWrite) + result = len.cint + +proc dtlsRecv(ctx: pointer, buf: ptr byte, len: uint): cint {.cdecl.} = + # dtlsRecv is the procedure called by mbedtls when data needs to be received. + # As we cannot asynchronously await for data to be received, we use a data received + # queue. If this queue is empty, we return `MBEDTLS_ERR_SSL_WANT_READ` for us to await + # when the mbedtls proc resumed (see read or dtlsHandshake for example) + let self = cast[DtlsConn](ctx) + if self.dataRecv.len() == 0: + return MBEDTLS_ERR_SSL_WANT_READ + + var dataRecv = self.dataRecv.popFirstNoWait() + copyMem(buf, addr dataRecv[0], dataRecv.len()) + result = dataRecv.len().cint + trace "dtls receive", len, result + +# -- Dtls Accept / Connect procedures -- + +proc removeConnection(self: Dtls, conn: DtlsConn, raddr: TransportAddress) {.async.} = + await conn.join() + self.connections.del(raddr) + +proc accept*(self: Dtls): Future[DtlsConn] {.async.} = + var res = DtlsConn() + + res.init(self.conn, self.laddr) + mb_ssl_init(res.ssl) + mb_ssl_config_init(res.config) + mb_ssl_cookie_init(res.cookie) + mb_ssl_cache_init(res.cache) + + res.ctr_drbg = self.ctr_drbg + res.entropy = self.entropy + + var pkey = self.serverPrivKey + var srvcert = self.serverCert + res.localCert = self.localCert + + mb_ssl_config_defaults(res.config, + MBEDTLS_SSL_IS_SERVER, + MBEDTLS_SSL_TRANSPORT_DATAGRAM, + MBEDTLS_SSL_PRESET_DEFAULT) + mb_ssl_conf_rng(res.config, mbedtls_ctr_drbg_random, res.ctr_drbg) + mb_ssl_conf_read_timeout(res.config, 10000) # in milliseconds + mb_ssl_conf_ca_chain(res.config, srvcert.next, nil) + mb_ssl_conf_own_cert(res.config, srvcert, pkey) + mb_ssl_cookie_setup(res.cookie, mbedtls_ctr_drbg_random, res.ctr_drbg) + mb_ssl_conf_dtls_cookies(res.config, res.cookie) + mb_ssl_set_timer_cb(res.ssl, res.timer) + mb_ssl_setup(res.ssl, res.config) + mb_ssl_session_reset(res.ssl) + mb_ssl_set_verify(res.ssl, verify, res) + mb_ssl_conf_authmode(res.config, MBEDTLS_SSL_VERIFY_OPTIONAL) + mb_ssl_set_bio(res.ssl, cast[pointer](res), dtlsSend, dtlsRecv, nil) + while true: + let (raddr, buf) = await self.pendingHandshakes.popFirst() + try: + res.raddr = raddr + res.dataRecv.addLastNoWait(buf) + self.connections[raddr] = res + await res.dtlsHandshake(true) + asyncSpawn self.removeConnection(res, raddr) + break + except CatchableError as exc: + trace "Handshake fail", remoteAddress = raddr, error = exc.msg + self.connections.del(raddr) + continue + return res + +proc connect*(self: Dtls, raddr: TransportAddress): Future[DtlsConn] {.async.} = + var res = DtlsConn() + + res.init(self.conn, self.laddr) + mb_ssl_init(res.ssl) + mb_ssl_config_init(res.config) + + res.ctr_drbg = self.ctr_drbg + res.entropy = self.entropy + + var pkey = res.ctr_drbg.generateKey() + var srvcert = res.ctr_drbg.generateCertificate(pkey) + res.localCert = newSeq[byte](srvcert.raw.len) + copyMem(addr res.localCert[0], srvcert.raw.p, srvcert.raw.len) + + mb_ctr_drbg_init(res.ctr_drbg) + mb_entropy_init(res.entropy) + mb_ctr_drbg_seed(res.ctr_drbg, mbedtls_entropy_func, res.entropy, nil, 0) + + mb_ssl_config_defaults(res.config, + MBEDTLS_SSL_IS_CLIENT, + MBEDTLS_SSL_TRANSPORT_DATAGRAM, + MBEDTLS_SSL_PRESET_DEFAULT) + mb_ssl_conf_rng(res.config, mbedtls_ctr_drbg_random, res.ctr_drbg) + mb_ssl_conf_read_timeout(res.config, 10000) # in milliseconds + mb_ssl_conf_ca_chain(res.config, srvcert.next, nil) + mb_ssl_set_timer_cb(res.ssl, res.timer) + mb_ssl_setup(res.ssl, res.config) + mb_ssl_set_verify(res.ssl, verify, res) + mb_ssl_conf_authmode(res.config, MBEDTLS_SSL_VERIFY_OPTIONAL) + mb_ssl_set_bio(res.ssl, cast[pointer](res), dtlsSend, dtlsRecv, nil) + + res.raddr = raddr + self.connections[raddr] = res + + try: + await res.dtlsHandshake(false) + asyncSpawn self.removeConnection(res, raddr) + except CatchableError as exc: + trace "Handshake fail", remoteAddress = raddr, error = exc.msg + self.connections.del(raddr) + raise exc + + return res diff --git a/webrtc/dtls/utils.nim b/webrtc/dtls/utils.nim new file mode 100644 index 0000000..06fb990 --- /dev/null +++ b/webrtc/dtls/utils.nim @@ -0,0 +1,96 @@ +# Nim-WebRTC +# Copyright (c) 2024 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 std/times + +import stew/byteutils + +import mbedtls/pk +import mbedtls/rsa +import mbedtls/ctr_drbg +import mbedtls/x509_crt +import mbedtls/bignum +import mbedtls/md + +import chronicles + +# This sequence is used for debugging. +const mb_ssl_states* = @[ + "MBEDTLS_SSL_HELLO_REQUEST", + "MBEDTLS_SSL_CLIENT_HELLO", + "MBEDTLS_SSL_SERVER_HELLO", + "MBEDTLS_SSL_SERVER_CERTIFICATE", + "MBEDTLS_SSL_SERVER_KEY_EXCHANGE", + "MBEDTLS_SSL_CERTIFICATE_REQUEST", + "MBEDTLS_SSL_SERVER_HELLO_DONE", + "MBEDTLS_SSL_CLIENT_CERTIFICATE", + "MBEDTLS_SSL_CLIENT_KEY_EXCHANGE", + "MBEDTLS_SSL_CERTIFICATE_VERIFY", + "MBEDTLS_SSL_CLIENT_CHANGE_CIPHER_SPEC", + "MBEDTLS_SSL_CLIENT_FINISHED", + "MBEDTLS_SSL_SERVER_CHANGE_CIPHER_SPEC", + "MBEDTLS_SSL_SERVER_FINISHED", + "MBEDTLS_SSL_FLUSH_BUFFERS", + "MBEDTLS_SSL_HANDSHAKE_WRAPUP", + "MBEDTLS_SSL_NEW_SESSION_TICKET", + "MBEDTLS_SSL_SERVER_HELLO_VERIFY_REQUEST_SENT", + "MBEDTLS_SSL_HELLO_RETRY_REQUEST", + "MBEDTLS_SSL_ENCRYPTED_EXTENSIONS", + "MBEDTLS_SSL_END_OF_EARLY_DATA", + "MBEDTLS_SSL_CLIENT_CERTIFICATE_VERIFY", + "MBEDTLS_SSL_CLIENT_CCS_AFTER_SERVER_FINISHED", + "MBEDTLS_SSL_CLIENT_CCS_BEFORE_2ND_CLIENT_HELLO", + "MBEDTLS_SSL_SERVER_CCS_AFTER_SERVER_HELLO", + "MBEDTLS_SSL_CLIENT_CCS_AFTER_CLIENT_HELLO", + "MBEDTLS_SSL_SERVER_CCS_AFTER_HELLO_RETRY_REQUEST", + "MBEDTLS_SSL_HANDSHAKE_OVER", + "MBEDTLS_SSL_TLS1_3_NEW_SESSION_TICKET", + "MBEDTLS_SSL_TLS1_3_NEW_SESSION_TICKET_FLUSH" +] + +template generateKey*(random: mbedtls_ctr_drbg_context): mbedtls_pk_context = + var res: mbedtls_pk_context + mb_pk_init(res) + discard mbedtls_pk_setup(addr res, mbedtls_pk_info_from_type(MBEDTLS_PK_RSA)) + mb_rsa_gen_key(mb_pk_rsa(res), mbedtls_ctr_drbg_random, random, 2048, 65537) + let x = mb_pk_rsa(res) + res + +template generateCertificate*(random: mbedtls_ctr_drbg_context, + issuer_key: mbedtls_pk_context): mbedtls_x509_crt = + let + # To be honest, I have no clue what to put here as a name + name = "C=FR,O=Status,CN=webrtc" + time_format = initTimeFormat("YYYYMMddHHmmss") + time_from = times.now().format(time_format) + time_to = (times.now() + times.years(1)).format(time_format) + + var write_cert: mbedtls_x509write_cert + var serial_mpi: mbedtls_mpi + mb_x509write_crt_init(write_cert) + mb_x509write_crt_set_md_alg(write_cert, MBEDTLS_MD_SHA256); + mb_x509write_crt_set_subject_key(write_cert, issuer_key) + mb_x509write_crt_set_issuer_key(write_cert, issuer_key) + mb_x509write_crt_set_subject_name(write_cert, name) + mb_x509write_crt_set_issuer_name(write_cert, name) + mb_x509write_crt_set_validity(write_cert, time_from, time_to) + mb_x509write_crt_set_basic_constraints(write_cert, 0, -1) + mb_x509write_crt_set_subject_key_identifier(write_cert) + mb_x509write_crt_set_authority_key_identifier(write_cert) + mb_mpi_init(serial_mpi) + let serial_hex = mb_mpi_read_string(serial_mpi, 16) + mb_x509write_crt_set_serial(write_cert, serial_mpi) + let buf = + try: + mb_x509write_crt_pem(write_cert, 2048, mbedtls_ctr_drbg_random, random) + except MbedTLSError as e: + raise e + var res: mbedtls_x509_crt + mb_x509_crt_parse(res, buf) + res From bef7f8e87bc7971635890f30354e1312bae2f860 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Fri, 12 Apr 2024 17:11:40 +0200 Subject: [PATCH 02/42] refactor: change according to the stun protocol rework --- webrtc/dtls/dtls.nim | 131 ++++++++++++++++++++----------------------- 1 file changed, 61 insertions(+), 70 deletions(-) diff --git a/webrtc/dtls/dtls.nim b/webrtc/dtls/dtls.nim index 1bc4cbf..a54eec4 100644 --- a/webrtc/dtls/dtls.nim +++ b/webrtc/dtls/dtls.nim @@ -9,7 +9,7 @@ import times, deques, tables, sequtils import chronos, chronicles -import ./utils, ../stun/stun_connection +import ./utils, ../stun/stun_connection, ../errors import mbedtls/ssl import mbedtls/ssl_cookie @@ -47,12 +47,11 @@ const PendingHandshakeLimit = 1024 # the Udp "connection" type - DtlsError* = object of CatchableError DtlsConn* = ref object conn: StunConn laddr: TransportAddress raddr*: TransportAddress - dataRecv: AsyncQueue[seq[byte]] + dataRecv: seq[byte] sendFuture: Future[void] closed: bool closeEvent: AsyncEvent @@ -70,17 +69,24 @@ type localCert: seq[byte] remoteCert: seq[byte] -proc init(self: DtlsConn, conn: StunConn, laddr: TransportAddress) = - self.conn = conn - self.laddr = laddr - self.dataRecv = newAsyncQueue[seq[byte]]() +proc init(T: type DtlsConn, conn: StunConn, laddr: TransportAddress): T = + ## Initialize a Dtls Connection + ## + var self = T(conn: conn, laddr: laddr) + self.raddr = conn.raddr self.closed = false self.closeEvent = newAsyncEvent() + return self -proc join(self: DtlsConn) {.async.} = +proc join*(self: DtlsConn) {.async: (raises: [CancelledError]).} = + ## Wait for the Dtls Connection to be closed + ## await self.closeEvent.wait() -proc dtlsHandshake(self: DtlsConn, isServer: bool) {.async.} = +proc dtlsHandshake( + self: DtlsConn, + isServer: bool + ) {.async: (raises: [CancelledError, WebRtcError].} = var shouldRead = isServer while self.ssl.private_state != MBEDTLS_SSL_HANDSHAKE_OVER: if shouldRead: @@ -91,9 +97,8 @@ proc dtlsHandshake(self: DtlsConn, isServer: bool) {.async.} = of AddressFamily.IPv6: mb_ssl_set_client_transport_id(self.ssl, self.raddr.address_v6) else: - raise newException(DtlsError, "Remote address isn't an IP address") - let tmp = await self.dataRecv.popFirst() - self.dataRecv.addFirstNoWait(tmp) + raise newException(WebRtcError, "DTLS - Remote address isn't an IP address") + self.dataRecv = await self.conn.read() self.sendFuture = nil let res = mb_ssl_handshake_step(self.ssl) if not self.sendFuture.isNil(): @@ -109,11 +114,13 @@ proc dtlsHandshake(self: DtlsConn, isServer: bool) {.async.} = shouldRead = isServer continue elif res != 0: - raise newException(DtlsError, $(res.mbedtls_high_level_strerr())) + raise newException(WebRtcError, "DTLS - " & $(res.mbedtls_high_level_strerr())) -proc close*(self: DtlsConn) {.async.} = +proc close*(self: DtlsConn) {.async: (raises: [CancelledError]).} = + ## Close a Dtls Connection + ## if self.closed: - debug "Try to close DtlsConn twice" + debug "Try to close an already closed DtlsConn" return self.closed = true @@ -125,16 +132,20 @@ proc close*(self: DtlsConn) {.async.} = self.closeEvent.fire() proc write*(self: DtlsConn, msg: seq[byte]) {.async.} = + ## Write a message using mbedtls_ssl_write + ## + # Mbed-TLS will wrap the message properly and call `dtlsSend` callback. + # `dtlsSend` will write the message on the higher Stun connection. if self.closed: debug "Try to write on an already closed DtlsConn" return var buf = msg try: - let sendFuture = newFuture[void]("DtlsConn write") self.sendFuture = nil let write = mb_ssl_write(self.ssl, buf) if not self.sendFuture.isNil(): - await self.sendFuture + let sendFuture = self.sendFuture + await sendFuture trace "Dtls write", msgLen = msg.len(), actuallyWrote = write except MbedTLSError as exc: trace "Dtls write error", errorMsg = exc.msg @@ -146,32 +157,25 @@ proc read*(self: DtlsConn): Future[seq[byte]] {.async.} = return var res = newSeq[byte](8192) while true: - let tmp = await self.dataRecv.popFirst() - self.dataRecv.addFirstNoWait(tmp) + self.dataRecv = await self.conn.read() # TODO: Find a clear way to use the template `mb_ssl_read` without # messing up things with exception let length = mbedtls_ssl_read(addr self.ssl, cast[ptr byte](addr res[0]), res.len().uint) if length == MBEDTLS_ERR_SSL_WANT_READ: continue if length < 0: - raise newException(DtlsError, $(length.cint.mbedtls_high_level_strerr())) + raise newException(WebRtcError, "DTLS - " & $(length.cint.mbedtls_high_level_strerr())) res.setLen(length) return res # -- Dtls -- -# The Dtls object read every messages from the UdpConn/StunConn and, if the address -# is not yet stored in the Table `Connection`, adds it to the `pendingHandshake` queue -# to be accepted later, if the address is stored, add the message received to the -# corresponding DtlsConn `dataRecv` queue. type Dtls* = ref object of RootObj connections: Table[TransportAddress, DtlsConn] - pendingHandshakes: AsyncQueue[(TransportAddress, seq[byte])] - conn: StunConn + transport: Stun laddr: TransportAddress started: bool - readLoop: Future[void] ctr_drbg: mbedtls_ctr_drbg_context entropy: mbedtls_entropy_context @@ -187,25 +191,13 @@ proc updateOrAdd(aq: AsyncQueue[(TransportAddress, seq[byte])], return aq.addLastNoWait((raddr, buf)) -proc init*(self: Dtls, conn: StunConn, laddr: TransportAddress) = - if self.started: - warn "Already started" - return - - proc readLoop() {.async.} = - while true: - let (buf, raddr) = await self.conn.read() - if self.connections.hasKey(raddr): - self.connections[raddr].dataRecv.addLastNoWait(buf) - else: - self.pendingHandshakes.updateOrAdd(raddr, buf) +proc init*(T: type Dtls, transport: Stun, laddr: TransportAddress): T = + var self = T() self.connections = initTable[TransportAddress, DtlsConn]() - self.pendingHandshakes = newAsyncQueue[(TransportAddress, seq[byte])](PendingHandshakeLimit) self.conn = conn self.laddr = laddr self.started = true - self.readLoop = readLoop() mb_ctr_drbg_init(self.ctr_drbg) mb_entropy_init(self.entropy) @@ -222,7 +214,6 @@ proc stop*(self: Dtls) {.async.} = return await allFutures(toSeq(self.connections.values()).mapIt(it.close())) - self.readLoop.cancel() self.started = false # -- Remote / Local certificate getter -- @@ -273,21 +264,23 @@ proc dtlsRecv(ctx: pointer, buf: ptr byte, len: uint): cint {.cdecl.} = if self.dataRecv.len() == 0: return MBEDTLS_ERR_SSL_WANT_READ - var dataRecv = self.dataRecv.popFirstNoWait() - copyMem(buf, addr dataRecv[0], dataRecv.len()) - result = dataRecv.len().cint + copyMem(buf, addr self.dataRecv[0], self.dataRecv.len()) + result = self.dataRecv.len().cint + self.dataRecv = @[] trace "dtls receive", len, result # -- Dtls Accept / Connect procedures -- -proc removeConnection(self: Dtls, conn: DtlsConn, raddr: TransportAddress) {.async.} = +proc cleanupDtlsConn(self: Dtls, conn: DtlsConn) {.async.} = + # Waiting for a connection to be closed to remove it from the table await conn.join() - self.connections.del(raddr) + self.connections.del(conn.raddr) proc accept*(self: Dtls): Future[DtlsConn] {.async.} = - var res = DtlsConn() + ## Accept a Dtls Connection + ## + var res = DtlsConn.init(await self.transport.accept(), self.laddr) - res.init(self.conn, self.laddr) mb_ssl_init(res.ssl) mb_ssl_config_init(res.config) mb_ssl_cookie_init(res.cookie) @@ -300,10 +293,12 @@ proc accept*(self: Dtls): Future[DtlsConn] {.async.} = var srvcert = self.serverCert res.localCert = self.localCert - mb_ssl_config_defaults(res.config, - MBEDTLS_SSL_IS_SERVER, - MBEDTLS_SSL_TRANSPORT_DATAGRAM, - MBEDTLS_SSL_PRESET_DEFAULT) + mb_ssl_config_defaults( + res.config, + MBEDTLS_SSL_IS_SERVER, + MBEDTLS_SSL_TRANSPORT_DATAGRAM, + MBEDTLS_SSL_PRESET_DEFAULT + ) mb_ssl_conf_rng(res.config, mbedtls_ctr_drbg_random, res.ctr_drbg) mb_ssl_conf_read_timeout(res.config, 10000) # in milliseconds mb_ssl_conf_ca_chain(res.config, srvcert.next, nil) @@ -317,24 +312,22 @@ proc accept*(self: Dtls): Future[DtlsConn] {.async.} = mb_ssl_conf_authmode(res.config, MBEDTLS_SSL_VERIFY_OPTIONAL) mb_ssl_set_bio(res.ssl, cast[pointer](res), dtlsSend, dtlsRecv, nil) while true: - let (raddr, buf) = await self.pendingHandshakes.popFirst() try: - res.raddr = raddr - res.dataRecv.addLastNoWait(buf) - self.connections[raddr] = res + self.connections[res.raddr] = res await res.dtlsHandshake(true) - asyncSpawn self.removeConnection(res, raddr) + asyncSpawn self.removeConnection(res) break - except CatchableError as exc: - trace "Handshake fail", remoteAddress = raddr, error = exc.msg - self.connections.del(raddr) - continue + except WebRtcError as exc: + trace "Handshake fails, try accept another connection", + remoteAddress = res.raddr, error = exc.msg + self.connections.del(res.raddr) + res.conn = await self.transport.accept() return res proc connect*(self: Dtls, raddr: TransportAddress): Future[DtlsConn] {.async.} = - var res = DtlsConn() + ## Connect to a remote address, creating a Dtls Connection + var res = DtlsConn.init(await self.transport.connect(raddr), self.laddr) - res.init(self.conn, self.laddr) mb_ssl_init(res.ssl) mb_ssl_config_init(res.config) @@ -363,14 +356,12 @@ proc connect*(self: Dtls, raddr: TransportAddress): Future[DtlsConn] {.async.} = mb_ssl_conf_authmode(res.config, MBEDTLS_SSL_VERIFY_OPTIONAL) mb_ssl_set_bio(res.ssl, cast[pointer](res), dtlsSend, dtlsRecv, nil) - res.raddr = raddr - self.connections[raddr] = res - try: + self.connections[raddr] = res await res.dtlsHandshake(false) - asyncSpawn self.removeConnection(res, raddr) - except CatchableError as exc: - trace "Handshake fail", remoteAddress = raddr, error = exc.msg + asyncSpawn self.removeConnection(res) + except WebRtcError as exc: + trace "Handshake fails", remoteAddress = raddr, error = exc.msg self.connections.del(raddr) raise exc From 2da70467b494f774ca0eb6cef8eaf6502e884d30 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Thu, 30 May 2024 14:58:08 +0200 Subject: [PATCH 03/42] chore: rename init proc into new --- webrtc/dtls/dtls.nim | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/webrtc/dtls/dtls.nim b/webrtc/dtls/dtls.nim index a54eec4..424d376 100644 --- a/webrtc/dtls/dtls.nim +++ b/webrtc/dtls/dtls.nim @@ -9,7 +9,8 @@ import times, deques, tables, sequtils import chronos, chronicles -import ./utils, ../stun/stun_connection, ../errors +import ./utils, ../errors, + ../stun/[stun_connection, stun_transport] import mbedtls/ssl import mbedtls/ssl_cookie @@ -69,7 +70,7 @@ type localCert: seq[byte] remoteCert: seq[byte] -proc init(T: type DtlsConn, conn: StunConn, laddr: TransportAddress): T = +proc new(T: type DtlsConn, conn: StunConn, laddr: TransportAddress): T = ## Initialize a Dtls Connection ## var self = T(conn: conn, laddr: laddr) @@ -191,7 +192,7 @@ proc updateOrAdd(aq: AsyncQueue[(TransportAddress, seq[byte])], return aq.addLastNoWait((raddr, buf)) -proc init*(T: type Dtls, transport: Stun, laddr: TransportAddress): T = +proc new*(T: type Dtls, transport: Stun, laddr: TransportAddress): T = var self = T() self.connections = initTable[TransportAddress, DtlsConn]() @@ -279,7 +280,7 @@ proc cleanupDtlsConn(self: Dtls, conn: DtlsConn) {.async.} = proc accept*(self: Dtls): Future[DtlsConn] {.async.} = ## Accept a Dtls Connection ## - var res = DtlsConn.init(await self.transport.accept(), self.laddr) + var res = DtlsConn.new(await self.transport.accept(), self.laddr) mb_ssl_init(res.ssl) mb_ssl_config_init(res.config) @@ -326,7 +327,7 @@ proc accept*(self: Dtls): Future[DtlsConn] {.async.} = proc connect*(self: Dtls, raddr: TransportAddress): Future[DtlsConn] {.async.} = ## Connect to a remote address, creating a Dtls Connection - var res = DtlsConn.init(await self.transport.connect(raddr), self.laddr) + var res = DtlsConn.new(await self.transport.connect(raddr), self.laddr) mb_ssl_init(res.ssl) mb_ssl_config_init(res.config) From d1b9fda00bd7228d538d1d36cd79d2d555e853f1 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Fri, 31 May 2024 13:47:23 +0200 Subject: [PATCH 04/42] docs: adds object field comments --- webrtc/dtls/dtls.nim | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/webrtc/dtls/dtls.nim b/webrtc/dtls/dtls.nim index 424d376..3376892 100644 --- a/webrtc/dtls/dtls.nim +++ b/webrtc/dtls/dtls.nim @@ -30,9 +30,9 @@ import mbedtls/timing logScope: topics = "webrtc dtls" -# Implementation of a DTLS client and a DTLS Server by using the mbedtls library. +# Implementation of a DTLS client and a DTLS Server by using the Mbed-TLS library. # Multiple things here are unintuitive partly because of the callbacks -# used by mbedtls and that those callbacks cannot be async. +# used by Mbed-TLS and that those callbacks cannot be async. # # TODO: # - Check the viability of the add/pop first/last of the asyncqueue with the limit. @@ -49,27 +49,33 @@ const PendingHandshakeLimit = 1024 type DtlsConn* = ref object - conn: StunConn - laddr: TransportAddress - raddr*: TransportAddress - dataRecv: seq[byte] + conn: StunConn # The wrapper protocol Stun Connection + laddr: TransportAddress # Local address + raddr*: TransportAddress # Remote address + dataRecv: seq[byte] # data received which will be read by SCTP sendFuture: Future[void] + # This future is set by synchronous Mbed-TLS callbacks and waited, if set, once + # the synchronous functions ends + + # Close connection management closed: bool closeEvent: AsyncEvent - timer: mbedtls_timing_delay_context + # Local and Remote certificate, needed by wrapped protocol DataChannel + # and by libp2p + localCert: seq[byte] + remoteCert: seq[byte] + # Mbed-TLS contexts ssl: mbedtls_ssl_context config: mbedtls_ssl_config cookie: mbedtls_ssl_cookie_ctx cache: mbedtls_ssl_cache_context + timer: mbedtls_timing_delay_context ctr_drbg: mbedtls_ctr_drbg_context entropy: mbedtls_entropy_context - localCert: seq[byte] - remoteCert: seq[byte] - proc new(T: type DtlsConn, conn: StunConn, laddr: TransportAddress): T = ## Initialize a Dtls Connection ## @@ -305,7 +311,7 @@ proc accept*(self: Dtls): Future[DtlsConn] {.async.} = mb_ssl_conf_ca_chain(res.config, srvcert.next, nil) mb_ssl_conf_own_cert(res.config, srvcert, pkey) mb_ssl_cookie_setup(res.cookie, mbedtls_ctr_drbg_random, res.ctr_drbg) - mb_ssl_conf_dtls_cookies(res.config, res.cookie) + mb_ssl_conf_dtls_cookies(res.config, addr res.cookie) mb_ssl_set_timer_cb(res.ssl, res.timer) mb_ssl_setup(res.ssl, res.config) mb_ssl_session_reset(res.ssl) From 66ca7419954be5071a3e9a6fb51ccc9a09742c77 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Thu, 20 Jun 2024 16:14:05 +0200 Subject: [PATCH 05/42] chore: split dtls.nim into two files & renaming --- webrtc/dtls/dtls.nim | 375 ---------------------- webrtc/dtls/dtls_connection.nim | 222 +++++++++++++ webrtc/dtls/dtls_transport.nim | 194 +++++++++++ webrtc/dtls/{utils.nim => dtls_utils.nim} | 4 - 4 files changed, 416 insertions(+), 379 deletions(-) delete mode 100644 webrtc/dtls/dtls.nim create mode 100644 webrtc/dtls/dtls_connection.nim create mode 100644 webrtc/dtls/dtls_transport.nim rename webrtc/dtls/{utils.nim => dtls_utils.nim} (98%) diff --git a/webrtc/dtls/dtls.nim b/webrtc/dtls/dtls.nim deleted file mode 100644 index 3376892..0000000 --- a/webrtc/dtls/dtls.nim +++ /dev/null @@ -1,375 +0,0 @@ -# Nim-WebRTC -# Copyright (c) 2024 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 times, deques, tables, sequtils -import chronos, chronicles -import ./utils, ../errors, - ../stun/[stun_connection, stun_transport] - -import mbedtls/ssl -import mbedtls/ssl_cookie -import mbedtls/ssl_cache -import mbedtls/pk -import mbedtls/md -import mbedtls/entropy -import mbedtls/ctr_drbg -import mbedtls/rsa -import mbedtls/x509 -import mbedtls/x509_crt -import mbedtls/bignum -import mbedtls/error -import mbedtls/net_sockets -import mbedtls/timing - -logScope: - topics = "webrtc dtls" - -# Implementation of a DTLS client and a DTLS Server by using the Mbed-TLS library. -# Multiple things here are unintuitive partly because of the callbacks -# used by Mbed-TLS and that those callbacks cannot be async. -# -# TODO: -# - Check the viability of the add/pop first/last of the asyncqueue with the limit. -# There might be some errors (or crashes) with some edge cases with the no wait option -# - Not critical - Check how to make a better use of MBEDTLS_ERR_SSL_WANT_WRITE -# - Not critical - May be interesting to split Dtls and DtlsConn into two files - -# This limit is arbitrary, it could be interesting to make it configurable. -const PendingHandshakeLimit = 1024 - -# -- DtlsConn -- -# A Dtls connection to a specific IP address recovered by the receiving part of -# the Udp "connection" - -type - DtlsConn* = ref object - conn: StunConn # The wrapper protocol Stun Connection - laddr: TransportAddress # Local address - raddr*: TransportAddress # Remote address - dataRecv: seq[byte] # data received which will be read by SCTP - sendFuture: Future[void] - # This future is set by synchronous Mbed-TLS callbacks and waited, if set, once - # the synchronous functions ends - - # Close connection management - closed: bool - closeEvent: AsyncEvent - - # Local and Remote certificate, needed by wrapped protocol DataChannel - # and by libp2p - localCert: seq[byte] - remoteCert: seq[byte] - - # Mbed-TLS contexts - ssl: mbedtls_ssl_context - config: mbedtls_ssl_config - cookie: mbedtls_ssl_cookie_ctx - cache: mbedtls_ssl_cache_context - timer: mbedtls_timing_delay_context - - ctr_drbg: mbedtls_ctr_drbg_context - entropy: mbedtls_entropy_context - -proc new(T: type DtlsConn, conn: StunConn, laddr: TransportAddress): T = - ## Initialize a Dtls Connection - ## - var self = T(conn: conn, laddr: laddr) - self.raddr = conn.raddr - self.closed = false - self.closeEvent = newAsyncEvent() - return self - -proc join*(self: DtlsConn) {.async: (raises: [CancelledError]).} = - ## Wait for the Dtls Connection to be closed - ## - await self.closeEvent.wait() - -proc dtlsHandshake( - self: DtlsConn, - isServer: bool - ) {.async: (raises: [CancelledError, WebRtcError].} = - var shouldRead = isServer - while self.ssl.private_state != MBEDTLS_SSL_HANDSHAKE_OVER: - if shouldRead: - if isServer: - case self.raddr.family - of AddressFamily.IPv4: - mb_ssl_set_client_transport_id(self.ssl, self.raddr.address_v4) - of AddressFamily.IPv6: - mb_ssl_set_client_transport_id(self.ssl, self.raddr.address_v6) - else: - raise newException(WebRtcError, "DTLS - Remote address isn't an IP address") - self.dataRecv = await self.conn.read() - self.sendFuture = nil - let res = mb_ssl_handshake_step(self.ssl) - if not self.sendFuture.isNil(): - await self.sendFuture - shouldRead = false - if res == MBEDTLS_ERR_SSL_WANT_WRITE: - continue - elif res == MBEDTLS_ERR_SSL_WANT_READ: - shouldRead = true - continue - elif res == MBEDTLS_ERR_SSL_HELLO_VERIFY_REQUIRED: - mb_ssl_session_reset(self.ssl) - shouldRead = isServer - continue - elif res != 0: - raise newException(WebRtcError, "DTLS - " & $(res.mbedtls_high_level_strerr())) - -proc close*(self: DtlsConn) {.async: (raises: [CancelledError]).} = - ## Close a Dtls Connection - ## - if self.closed: - debug "Try to close an already closed DtlsConn" - return - - self.closed = true - self.sendFuture = nil - # TODO: proc mbedtls_ssl_close_notify => template mb_ssl_close_notify in nim-mbedtls - let x = mbedtls_ssl_close_notify(addr self.ssl) - if not self.sendFuture.isNil(): - await self.sendFuture - self.closeEvent.fire() - -proc write*(self: DtlsConn, msg: seq[byte]) {.async.} = - ## Write a message using mbedtls_ssl_write - ## - # Mbed-TLS will wrap the message properly and call `dtlsSend` callback. - # `dtlsSend` will write the message on the higher Stun connection. - if self.closed: - debug "Try to write on an already closed DtlsConn" - return - var buf = msg - try: - self.sendFuture = nil - let write = mb_ssl_write(self.ssl, buf) - if not self.sendFuture.isNil(): - let sendFuture = self.sendFuture - await sendFuture - trace "Dtls write", msgLen = msg.len(), actuallyWrote = write - except MbedTLSError as exc: - trace "Dtls write error", errorMsg = exc.msg - raise exc - -proc read*(self: DtlsConn): Future[seq[byte]] {.async.} = - if self.closed: - debug "Try to read on an already closed DtlsConn" - return - var res = newSeq[byte](8192) - while true: - self.dataRecv = await self.conn.read() - # TODO: Find a clear way to use the template `mb_ssl_read` without - # messing up things with exception - let length = mbedtls_ssl_read(addr self.ssl, cast[ptr byte](addr res[0]), res.len().uint) - if length == MBEDTLS_ERR_SSL_WANT_READ: - continue - if length < 0: - raise newException(WebRtcError, "DTLS - " & $(length.cint.mbedtls_high_level_strerr())) - res.setLen(length) - return res - -# -- Dtls -- - -type - Dtls* = ref object of RootObj - connections: Table[TransportAddress, DtlsConn] - transport: Stun - laddr: TransportAddress - started: bool - ctr_drbg: mbedtls_ctr_drbg_context - entropy: mbedtls_entropy_context - - serverPrivKey: mbedtls_pk_context - serverCert: mbedtls_x509_crt - localCert: seq[byte] - -proc updateOrAdd(aq: AsyncQueue[(TransportAddress, seq[byte])], - raddr: TransportAddress, buf: seq[byte]) = - for kv in aq.mitems(): - if kv[0] == raddr: - kv[1] = buf - return - aq.addLastNoWait((raddr, buf)) - -proc new*(T: type Dtls, transport: Stun, laddr: TransportAddress): T = - var self = T() - - self.connections = initTable[TransportAddress, DtlsConn]() - self.conn = conn - self.laddr = laddr - self.started = true - - mb_ctr_drbg_init(self.ctr_drbg) - mb_entropy_init(self.entropy) - mb_ctr_drbg_seed(self.ctr_drbg, mbedtls_entropy_func, self.entropy, nil, 0) - - self.serverPrivKey = self.ctr_drbg.generateKey() - self.serverCert = self.ctr_drbg.generateCertificate(self.serverPrivKey) - self.localCert = newSeq[byte](self.serverCert.raw.len) - copyMem(addr self.localCert[0], self.serverCert.raw.p, self.serverCert.raw.len) - -proc stop*(self: Dtls) {.async.} = - if not self.started: - warn "Already stopped" - return - - await allFutures(toSeq(self.connections.values()).mapIt(it.close())) - self.started = false - -# -- Remote / Local certificate getter -- - -proc remoteCertificate*(conn: DtlsConn): seq[byte] = - conn.remoteCert - -proc localCertificate*(conn: DtlsConn): seq[byte] = - conn.localCert - -proc localCertificate*(self: Dtls): seq[byte] = - self.localCert - -# -- MbedTLS Callbacks -- - -proc verify(ctx: pointer, pcert: ptr mbedtls_x509_crt, - state: cint, pflags: ptr uint32): cint {.cdecl.} = - # verify is the procedure called by mbedtls when receiving the remote - # certificate. It's usually used to verify the validity of the certificate. - # We use this procedure to store the remote certificate as it's mandatory - # to have it for the Prologue of the Noise protocol, aswell as the localCertificate. - var self = cast[DtlsConn](ctx) - let cert = pcert[] - - self.remoteCert = newSeq[byte](cert.raw.len) - copyMem(addr self.remoteCert[0], cert.raw.p, cert.raw.len) - return 0 - -proc dtlsSend(ctx: pointer, buf: ptr byte, len: uint): cint {.cdecl.} = - # dtlsSend is the procedure called by mbedtls when data needs to be sent. - # As the StunConn's write proc is asynchronous and dtlsSend cannot be async, - # we store the future of this write and await it after the end of the - # function (see write or dtlsHanshake for example). - var self = cast[DtlsConn](ctx) - var toWrite = newSeq[byte](len) - if len > 0: - copyMem(addr toWrite[0], buf, len) - trace "dtls send", len - self.sendFuture = self.conn.write(self.raddr, toWrite) - result = len.cint - -proc dtlsRecv(ctx: pointer, buf: ptr byte, len: uint): cint {.cdecl.} = - # dtlsRecv is the procedure called by mbedtls when data needs to be received. - # As we cannot asynchronously await for data to be received, we use a data received - # queue. If this queue is empty, we return `MBEDTLS_ERR_SSL_WANT_READ` for us to await - # when the mbedtls proc resumed (see read or dtlsHandshake for example) - let self = cast[DtlsConn](ctx) - if self.dataRecv.len() == 0: - return MBEDTLS_ERR_SSL_WANT_READ - - copyMem(buf, addr self.dataRecv[0], self.dataRecv.len()) - result = self.dataRecv.len().cint - self.dataRecv = @[] - trace "dtls receive", len, result - -# -- Dtls Accept / Connect procedures -- - -proc cleanupDtlsConn(self: Dtls, conn: DtlsConn) {.async.} = - # Waiting for a connection to be closed to remove it from the table - await conn.join() - self.connections.del(conn.raddr) - -proc accept*(self: Dtls): Future[DtlsConn] {.async.} = - ## Accept a Dtls Connection - ## - var res = DtlsConn.new(await self.transport.accept(), self.laddr) - - mb_ssl_init(res.ssl) - mb_ssl_config_init(res.config) - mb_ssl_cookie_init(res.cookie) - mb_ssl_cache_init(res.cache) - - res.ctr_drbg = self.ctr_drbg - res.entropy = self.entropy - - var pkey = self.serverPrivKey - var srvcert = self.serverCert - res.localCert = self.localCert - - mb_ssl_config_defaults( - res.config, - MBEDTLS_SSL_IS_SERVER, - MBEDTLS_SSL_TRANSPORT_DATAGRAM, - MBEDTLS_SSL_PRESET_DEFAULT - ) - mb_ssl_conf_rng(res.config, mbedtls_ctr_drbg_random, res.ctr_drbg) - mb_ssl_conf_read_timeout(res.config, 10000) # in milliseconds - mb_ssl_conf_ca_chain(res.config, srvcert.next, nil) - mb_ssl_conf_own_cert(res.config, srvcert, pkey) - mb_ssl_cookie_setup(res.cookie, mbedtls_ctr_drbg_random, res.ctr_drbg) - mb_ssl_conf_dtls_cookies(res.config, addr res.cookie) - mb_ssl_set_timer_cb(res.ssl, res.timer) - mb_ssl_setup(res.ssl, res.config) - mb_ssl_session_reset(res.ssl) - mb_ssl_set_verify(res.ssl, verify, res) - mb_ssl_conf_authmode(res.config, MBEDTLS_SSL_VERIFY_OPTIONAL) - mb_ssl_set_bio(res.ssl, cast[pointer](res), dtlsSend, dtlsRecv, nil) - while true: - try: - self.connections[res.raddr] = res - await res.dtlsHandshake(true) - asyncSpawn self.removeConnection(res) - break - except WebRtcError as exc: - trace "Handshake fails, try accept another connection", - remoteAddress = res.raddr, error = exc.msg - self.connections.del(res.raddr) - res.conn = await self.transport.accept() - return res - -proc connect*(self: Dtls, raddr: TransportAddress): Future[DtlsConn] {.async.} = - ## Connect to a remote address, creating a Dtls Connection - var res = DtlsConn.new(await self.transport.connect(raddr), self.laddr) - - mb_ssl_init(res.ssl) - mb_ssl_config_init(res.config) - - res.ctr_drbg = self.ctr_drbg - res.entropy = self.entropy - - var pkey = res.ctr_drbg.generateKey() - var srvcert = res.ctr_drbg.generateCertificate(pkey) - res.localCert = newSeq[byte](srvcert.raw.len) - copyMem(addr res.localCert[0], srvcert.raw.p, srvcert.raw.len) - - mb_ctr_drbg_init(res.ctr_drbg) - mb_entropy_init(res.entropy) - mb_ctr_drbg_seed(res.ctr_drbg, mbedtls_entropy_func, res.entropy, nil, 0) - - mb_ssl_config_defaults(res.config, - MBEDTLS_SSL_IS_CLIENT, - MBEDTLS_SSL_TRANSPORT_DATAGRAM, - MBEDTLS_SSL_PRESET_DEFAULT) - mb_ssl_conf_rng(res.config, mbedtls_ctr_drbg_random, res.ctr_drbg) - mb_ssl_conf_read_timeout(res.config, 10000) # in milliseconds - mb_ssl_conf_ca_chain(res.config, srvcert.next, nil) - mb_ssl_set_timer_cb(res.ssl, res.timer) - mb_ssl_setup(res.ssl, res.config) - mb_ssl_set_verify(res.ssl, verify, res) - mb_ssl_conf_authmode(res.config, MBEDTLS_SSL_VERIFY_OPTIONAL) - mb_ssl_set_bio(res.ssl, cast[pointer](res), dtlsSend, dtlsRecv, nil) - - try: - self.connections[raddr] = res - await res.dtlsHandshake(false) - asyncSpawn self.removeConnection(res) - except WebRtcError as exc: - trace "Handshake fails", remoteAddress = raddr, error = exc.msg - self.connections.del(raddr) - raise exc - - return res diff --git a/webrtc/dtls/dtls_connection.nim b/webrtc/dtls/dtls_connection.nim new file mode 100644 index 0000000..f82a4af --- /dev/null +++ b/webrtc/dtls/dtls_connection.nim @@ -0,0 +1,222 @@ +# Nim-WebRTC +# Copyright (c) 2024 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 ../errors, ../stun/[stun_connection] + +import mbedtls/ssl +import mbedtls/ssl_cookie +import mbedtls/ssl_cache +import mbedtls/pk +import mbedtls/md +import mbedtls/entropy +import mbedtls/ctr_drbg +import mbedtls/rsa +import mbedtls/x509 +import mbedtls/x509_crt +import mbedtls/bignum +import mbedtls/error +import mbedtls/net_sockets +import mbedtls/timing + +logScope: + topics = "webrtc dtls_conn" + +# -- DtlsConn -- +# A Dtls connection to a specific IP address recovered by the receiving part of +# the Udp "connection" + +type + MbedTLSCtx* = object + ssl*: mbedtls_ssl_context + config*: mbedtls_ssl_config + cookie*: mbedtls_ssl_cookie_ctx + cache*: mbedtls_ssl_cache_context + timer*: mbedtls_timing_delay_context + + ctr_drbg*: mbedtls_ctr_drbg_context + entropy*: mbedtls_entropy_context + + + DtlsConn* = ref object + conn*: StunConn # The wrapper protocol Stun Connection + laddr: TransportAddress # Local address + raddr*: TransportAddress # Remote address + dataRecv: seq[byte] # data received which will be read by SCTP + sendFuture: Future[void].Raising([CancelledError, WebRtcError]) + # This future is set by synchronous Mbed-TLS callbacks and waited, if set, once + # the synchronous functions ends + + # Close connection management + closed: bool + closeEvent: AsyncEvent + + # Local and Remote certificate, needed by wrapped protocol DataChannel + # and by libp2p + localCert*: seq[byte] + remoteCert*: seq[byte] + + # Mbed-TLS contexts + ctx*: MbedTLSCtx + +proc new*(T: type DtlsConn, conn: StunConn, laddr: TransportAddress): T = + ## Initialize a Dtls Connection + ## + var self = T(conn: conn, laddr: laddr) + self.raddr = conn.raddr + self.closed = false + self.closeEvent = newAsyncEvent() + return self + +proc join*(self: DtlsConn) {.async: (raises: [CancelledError]).} = + ## Wait for the Dtls Connection to be closed + ## + await self.closeEvent.wait() + +proc dtlsHandshake*( + self: DtlsConn, + isServer: bool + ) {.async: (raises: [CancelledError, WebRtcError]).} = + var shouldRead = isServer + try: + while self.ctx.ssl.private_state != MBEDTLS_SSL_HANDSHAKE_OVER: + if shouldRead: + if isServer: + case self.raddr.family + of AddressFamily.IPv4: + mb_ssl_set_client_transport_id(self.ctx.ssl, self.raddr.address_v4) + of AddressFamily.IPv6: + mb_ssl_set_client_transport_id(self.ctx.ssl, self.raddr.address_v6) + else: + raise newException(WebRtcError, "DTLS - Remote address isn't an IP address") + let (data, _) = await self.conn.read() + self.dataRecv = data + self.sendFuture = nil + let res = mb_ssl_handshake_step(self.ctx.ssl) + if not self.sendFuture.isNil(): + await self.sendFuture + shouldRead = false + if res == MBEDTLS_ERR_SSL_WANT_WRITE: + continue + elif res == MBEDTLS_ERR_SSL_WANT_READ: + shouldRead = true + continue + elif res == MBEDTLS_ERR_SSL_HELLO_VERIFY_REQUIRED: + mb_ssl_session_reset(self.ctx.ssl) + shouldRead = isServer + continue + elif res != 0: + raise newException(WebRtcError, "DTLS - " & $(res.mbedtls_high_level_strerr())) + except MbedTLSError as exc: + trace "Dtls handshake error", errorMsg = exc.msg + raise newException(WebRtcError, "DTLS - Handshake error", exc) + +proc close*(self: DtlsConn) {.async: (raises: [CancelledError, WebRtcError]).} = + ## Close a Dtls Connection + ## + if self.closed: + debug "Try to close an already closed DtlsConn" + return + + self.closed = true + self.sendFuture = nil + # TODO: proc mbedtls_ssl_close_notify => template mb_ssl_close_notify in nim-mbedtls + let x = mbedtls_ssl_close_notify(addr self.ctx.ssl) + if not self.sendFuture.isNil(): + await self.sendFuture + self.closeEvent.fire() + +proc write*(self: DtlsConn, msg: seq[byte]) {.async.} = + ## Write a message using mbedtls_ssl_write + ## + # Mbed-TLS will wrap the message properly and call `dtlsSend` callback. + # `dtlsSend` will write the message on the higher Stun connection. + if self.closed: + debug "Try to write on an already closed DtlsConn" + return + var buf = msg + try: + self.sendFuture = nil + let write = mb_ssl_write(self.ctx.ssl, buf) + if not self.sendFuture.isNil(): + let sendFuture = self.sendFuture + await sendFuture + trace "Dtls write", msgLen = msg.len(), actuallyWrote = write + except MbedTLSError as exc: + trace "Dtls write error", errorMsg = exc.msg + raise exc + +proc read*(self: DtlsConn): Future[seq[byte]] {.async.} = + if self.closed: + debug "Try to read on an already closed DtlsConn" + return + var res = newSeq[byte](8192) + while true: + let (data, _) = await self.conn.read() + self.dataRecv = data + # TODO: Find a clear way to use the template `mb_ssl_read` without + # messing up things with exception + let length = mbedtls_ssl_read(addr self.ctx.ssl, cast[ptr byte](addr res[0]), res.len().uint) + if length == MBEDTLS_ERR_SSL_WANT_READ: + continue + if length < 0: + raise newException(WebRtcError, "DTLS - " & $(length.cint.mbedtls_high_level_strerr())) + res.setLen(length) + return res + +# -- Remote / Local certificate getter -- + +proc remoteCertificate*(conn: DtlsConn): seq[byte] = + conn.remoteCert + +proc localCertificate*(conn: DtlsConn): seq[byte] = + conn.localCert + +# -- MbedTLS Callbacks -- + +proc verify*(ctx: pointer, pcert: ptr mbedtls_x509_crt, + state: cint, pflags: ptr uint32): cint {.cdecl.} = + # verify is the procedure called by mbedtls when receiving the remote + # certificate. It's usually used to verify the validity of the certificate. + # We use this procedure to store the remote certificate as it's mandatory + # to have it for the Prologue of the Noise protocol, aswell as the localCertificate. + var self = cast[DtlsConn](ctx) + let cert = pcert[] + + self.remoteCert = newSeq[byte](cert.raw.len) + copyMem(addr self.remoteCert[0], cert.raw.p, cert.raw.len) + return 0 + +proc dtlsSend*(ctx: pointer, buf: ptr byte, len: uint): cint {.cdecl.} = + # dtlsSend is the procedure called by mbedtls when data needs to be sent. + # As the StunConn's write proc is asynchronous and dtlsSend cannot be async, + # we store the future of this write and await it after the end of the + # function (see write or dtlsHanshake for example). + var self = cast[DtlsConn](ctx) + var toWrite = newSeq[byte](len) + if len > 0: + copyMem(addr toWrite[0], buf, len) + trace "dtls send", len + self.sendFuture = self.conn.write(toWrite) + result = len.cint + +proc dtlsRecv*(ctx: pointer, buf: ptr byte, len: uint): cint {.cdecl.} = + # dtlsRecv is the procedure called by mbedtls when data needs to be received. + # As we cannot asynchronously await for data to be received, we use a data received + # queue. If this queue is empty, we return `MBEDTLS_ERR_SSL_WANT_READ` for us to await + # when the mbedtls proc resumed (see read or dtlsHandshake for example) + let self = cast[DtlsConn](ctx) + if self.dataRecv.len() == 0: + return MBEDTLS_ERR_SSL_WANT_READ + + copyMem(buf, addr self.dataRecv[0], self.dataRecv.len()) + result = self.dataRecv.len().cint + self.dataRecv = @[] + trace "dtls receive", len, result + diff --git a/webrtc/dtls/dtls_transport.nim b/webrtc/dtls/dtls_transport.nim new file mode 100644 index 0000000..907eb1d --- /dev/null +++ b/webrtc/dtls/dtls_transport.nim @@ -0,0 +1,194 @@ +# Nim-WebRTC +# Copyright (c) 2024 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 times, deques, tables, sequtils +import chronos, chronicles +import ./[dtls_utils, dtls_connection], ../errors, + ../stun/[stun_connection, stun_transport] + +import mbedtls/ssl +import mbedtls/ssl_cookie +import mbedtls/ssl_cache +import mbedtls/pk +import mbedtls/md +import mbedtls/entropy +import mbedtls/ctr_drbg +import mbedtls/rsa +import mbedtls/x509 +import mbedtls/x509_crt +import mbedtls/bignum +import mbedtls/error +import mbedtls/net_sockets +import mbedtls/timing + +logScope: + topics = "webrtc dtls" + +# Implementation of a DTLS client and a DTLS Server by using the Mbed-TLS library. +# Multiple things here are unintuitive partly because of the callbacks +# used by Mbed-TLS and that those callbacks cannot be async. +# +# TODO: +# - Check the viability of the add/pop first/last of the asyncqueue with the limit. +# There might be some errors (or crashes) with some edge cases with the no wait option +# - Not critical - Check how to make a better use of MBEDTLS_ERR_SSL_WANT_WRITE + +# This limit is arbitrary, it could be interesting to make it configurable. +const PendingHandshakeLimit = 1024 + +# -- Dtls -- + +type + Dtls* = ref object of RootObj + connections: Table[TransportAddress, DtlsConn] + transport: Stun + laddr: TransportAddress + started: bool + ctr_drbg: mbedtls_ctr_drbg_context + entropy: mbedtls_entropy_context + + serverPrivKey: mbedtls_pk_context + serverCert: mbedtls_x509_crt + localCert: seq[byte] + +proc updateOrAdd(aq: AsyncQueue[(TransportAddress, seq[byte])], + raddr: TransportAddress, buf: seq[byte]) = + for kv in aq.mitems(): + if kv[0] == raddr: + kv[1] = buf + return + aq.addLastNoWait((raddr, buf)) + +proc new*(T: type Dtls, transport: Stun, laddr: TransportAddress): T = + var self = T() + + self.connections = initTable[TransportAddress, DtlsConn]() + self.transport = transport + self.laddr = laddr + self.started = true + + mb_ctr_drbg_init(self.ctr_drbg) + mb_entropy_init(self.entropy) + mb_ctr_drbg_seed(self.ctr_drbg, mbedtls_entropy_func, self.entropy, nil, 0) + + self.serverPrivKey = self.ctr_drbg.generateKey() + self.serverCert = self.ctr_drbg.generateCertificate(self.serverPrivKey) + self.localCert = newSeq[byte](self.serverCert.raw.len) + copyMem(addr self.localCert[0], self.serverCert.raw.p, self.serverCert.raw.len) + +proc stop*(self: Dtls) {.async.} = + if not self.started: + warn "Already stopped" + return + + await allFutures(toSeq(self.connections.values()).mapIt(it.close())) + self.started = false + +# -- Remote / Local certificate getter -- + +proc localCertificate*(self: Dtls): seq[byte] = + self.localCert + +# -- Dtls Accept / Connect procedures -- + +proc cleanupDtlsConn(self: Dtls, conn: DtlsConn) {.async.} = + # Waiting for a connection to be closed to remove it from the table + await conn.join() + self.connections.del(conn.raddr) + +proc accept*(self: Dtls): Future[DtlsConn] {.async.} = + ## Accept a Dtls Connection + ## + var res = DtlsConn.new(await self.transport.accept(), self.laddr) + + mb_ssl_init(res.ctx.ssl) + mb_ssl_config_init(res.ctx.config) + mb_ssl_cookie_init(res.ctx.cookie) + mb_ssl_cache_init(res.ctx.cache) + + res.ctx.ctr_drbg = self.ctr_drbg + res.ctx.entropy = self.entropy + + var pkey = self.serverPrivKey + var srvcert = self.serverCert + res.localCert = self.localCert + + mb_ssl_config_defaults( + res.ctx.config, + MBEDTLS_SSL_IS_SERVER, + MBEDTLS_SSL_TRANSPORT_DATAGRAM, + MBEDTLS_SSL_PRESET_DEFAULT + ) + mb_ssl_conf_rng(res.ctx.config, mbedtls_ctr_drbg_random, res.ctx.ctr_drbg) + mb_ssl_conf_read_timeout(res.ctx.config, 10000) # in milliseconds + mb_ssl_conf_ca_chain(res.ctx.config, srvcert.next, nil) + mb_ssl_conf_own_cert(res.ctx.config, srvcert, pkey) + mb_ssl_cookie_setup(res.ctx.cookie, mbedtls_ctr_drbg_random, res.ctx.ctr_drbg) + mb_ssl_conf_dtls_cookies(res.ctx.config, addr res.ctx.cookie) + mb_ssl_set_timer_cb(res.ctx.ssl, res.ctx.timer) + mb_ssl_setup(res.ctx.ssl, res.ctx.config) + mb_ssl_session_reset(res.ctx.ssl) + mb_ssl_set_verify(res.ctx.ssl, verify, res) + mb_ssl_conf_authmode(res.ctx.config, MBEDTLS_SSL_VERIFY_OPTIONAL) + mb_ssl_set_bio(res.ctx.ssl, cast[pointer](res), dtlsSend, dtlsRecv, nil) + while true: + try: + self.connections[res.raddr] = res + await res.dtlsHandshake(true) + asyncSpawn self.cleanupDtlsConn(res) + break + except WebRtcError as exc: + trace "Handshake fails, try accept another connection", + remoteAddress = res.raddr, error = exc.msg + self.connections.del(res.raddr) + res.conn = await self.transport.accept() + return res + +proc connect*(self: Dtls, raddr: TransportAddress): Future[DtlsConn] {.async.} = + ## Connect to a remote address, creating a Dtls Connection + var res = DtlsConn.new(await self.transport.connect(raddr), self.laddr) + + mb_ssl_init(res.ctx.ssl) + mb_ssl_config_init(res.ctx.config) + + res.ctx.ctr_drbg = self.ctr_drbg + res.ctx.entropy = self.entropy + + var pkey = res.ctx.ctr_drbg.generateKey() + var srvcert = res.ctx.ctr_drbg.generateCertificate(pkey) + res.localCert = newSeq[byte](srvcert.raw.len) + copyMem(addr res.localCert[0], srvcert.raw.p, srvcert.raw.len) + + mb_ctr_drbg_init(res.ctx.ctr_drbg) + mb_entropy_init(res.ctx.entropy) + mb_ctr_drbg_seed(res.ctx.ctr_drbg, mbedtls_entropy_func, res.ctx.entropy, nil, 0) + + mb_ssl_config_defaults(res.ctx.config, + MBEDTLS_SSL_IS_CLIENT, + MBEDTLS_SSL_TRANSPORT_DATAGRAM, + MBEDTLS_SSL_PRESET_DEFAULT) + mb_ssl_conf_rng(res.ctx.config, mbedtls_ctr_drbg_random, res.ctx.ctr_drbg) + mb_ssl_conf_read_timeout(res.ctx.config, 10000) # in milliseconds + mb_ssl_conf_ca_chain(res.ctx.config, srvcert.next, nil) + mb_ssl_set_timer_cb(res.ctx.ssl, res.ctx.timer) + mb_ssl_setup(res.ctx.ssl, res.ctx.config) + mb_ssl_set_verify(res.ctx.ssl, verify, res) + mb_ssl_conf_authmode(res.ctx.config, MBEDTLS_SSL_VERIFY_OPTIONAL) + mb_ssl_set_bio(res.ctx.ssl, cast[pointer](res), dtlsSend, dtlsRecv, nil) + + try: + self.connections[raddr] = res + await res.dtlsHandshake(false) + asyncSpawn self.cleanupDtlsConn(res) + except WebRtcError as exc: + trace "Handshake fails", remoteAddress = raddr, error = exc.msg + self.connections.del(raddr) + raise exc + + return res diff --git a/webrtc/dtls/utils.nim b/webrtc/dtls/dtls_utils.nim similarity index 98% rename from webrtc/dtls/utils.nim rename to webrtc/dtls/dtls_utils.nim index 06fb990..b7fbfe7 100644 --- a/webrtc/dtls/utils.nim +++ b/webrtc/dtls/dtls_utils.nim @@ -9,8 +9,6 @@ import std/times -import stew/byteutils - import mbedtls/pk import mbedtls/rsa import mbedtls/ctr_drbg @@ -18,8 +16,6 @@ import mbedtls/x509_crt import mbedtls/bignum import mbedtls/md -import chronicles - # This sequence is used for debugging. const mb_ssl_states* = @[ "MBEDTLS_SSL_HELLO_REQUEST", From bf240b1138b8d7b2bdac5c68191ea1d51f782953 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Fri, 21 Jun 2024 10:52:03 +0200 Subject: [PATCH 06/42] chore: remove useless code --- webrtc/dtls/dtls_transport.nim | 9 --------- 1 file changed, 9 deletions(-) diff --git a/webrtc/dtls/dtls_transport.nim b/webrtc/dtls/dtls_transport.nim index 907eb1d..cfb7bc6 100644 --- a/webrtc/dtls/dtls_transport.nim +++ b/webrtc/dtls/dtls_transport.nim @@ -37,7 +37,6 @@ logScope: # TODO: # - Check the viability of the add/pop first/last of the asyncqueue with the limit. # There might be some errors (or crashes) with some edge cases with the no wait option -# - Not critical - Check how to make a better use of MBEDTLS_ERR_SSL_WANT_WRITE # This limit is arbitrary, it could be interesting to make it configurable. const PendingHandshakeLimit = 1024 @@ -57,14 +56,6 @@ type serverCert: mbedtls_x509_crt localCert: seq[byte] -proc updateOrAdd(aq: AsyncQueue[(TransportAddress, seq[byte])], - raddr: TransportAddress, buf: seq[byte]) = - for kv in aq.mitems(): - if kv[0] == raddr: - kv[1] = buf - return - aq.addLastNoWait((raddr, buf)) - proc new*(T: type Dtls, transport: Stun, laddr: TransportAddress): T = var self = T() From 66a2aa77ae659c5f47eafdf180ae7253b71ee05a Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Fri, 21 Jun 2024 11:00:57 +0200 Subject: [PATCH 07/42] chore: remove TODOs as they were addressed with a Stun refactorization --- webrtc/dtls/dtls_transport.nim | 7 ------- 1 file changed, 7 deletions(-) diff --git a/webrtc/dtls/dtls_transport.nim b/webrtc/dtls/dtls_transport.nim index cfb7bc6..9289618 100644 --- a/webrtc/dtls/dtls_transport.nim +++ b/webrtc/dtls/dtls_transport.nim @@ -33,13 +33,6 @@ logScope: # Implementation of a DTLS client and a DTLS Server by using the Mbed-TLS library. # Multiple things here are unintuitive partly because of the callbacks # used by Mbed-TLS and that those callbacks cannot be async. -# -# TODO: -# - Check the viability of the add/pop first/last of the asyncqueue with the limit. -# There might be some errors (or crashes) with some edge cases with the no wait option - -# This limit is arbitrary, it could be interesting to make it configurable. -const PendingHandshakeLimit = 1024 # -- Dtls -- From 24e42a9f42ffeedb27f5b06f2ef45a30365745cf Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Fri, 21 Jun 2024 13:06:26 +0200 Subject: [PATCH 08/42] fix: oversight on dtls.new --- webrtc/dtls/dtls_transport.nim | 17 +++++++++-------- webrtc/dtls/dtls_utils.nim | 1 + webrtc/stun/stun_transport.nim | 6 ++++-- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/webrtc/dtls/dtls_transport.nim b/webrtc/dtls/dtls_transport.nim index 9289618..3e6e8a8 100644 --- a/webrtc/dtls/dtls_transport.nim +++ b/webrtc/dtls/dtls_transport.nim @@ -40,7 +40,7 @@ type Dtls* = ref object of RootObj connections: Table[TransportAddress, DtlsConn] transport: Stun - laddr: TransportAddress + laddr*: TransportAddress started: bool ctr_drbg: mbedtls_ctr_drbg_context entropy: mbedtls_entropy_context @@ -49,13 +49,13 @@ type serverCert: mbedtls_x509_crt localCert: seq[byte] -proc new*(T: type Dtls, transport: Stun, laddr: TransportAddress): T = - var self = T() - - self.connections = initTable[TransportAddress, DtlsConn]() - self.transport = transport - self.laddr = laddr - self.started = true +proc new*(T: type Dtls, transport: Stun): T = + var self = T( + connections: initTable[TransportAddress, DtlsConn](), + transport: transport, + laddr: transport.laddr, + started: true + ) mb_ctr_drbg_init(self.ctr_drbg) mb_entropy_init(self.entropy) @@ -65,6 +65,7 @@ proc new*(T: type Dtls, transport: Stun, laddr: TransportAddress): T = self.serverCert = self.ctr_drbg.generateCertificate(self.serverPrivKey) self.localCert = newSeq[byte](self.serverCert.raw.len) copyMem(addr self.localCert[0], self.serverCert.raw.p, self.serverCert.raw.len) + return self proc stop*(self: Dtls) {.async.} = if not self.started: diff --git a/webrtc/dtls/dtls_utils.nim b/webrtc/dtls/dtls_utils.nim index b7fbfe7..65f8598 100644 --- a/webrtc/dtls/dtls_utils.nim +++ b/webrtc/dtls/dtls_utils.nim @@ -15,6 +15,7 @@ import mbedtls/ctr_drbg import mbedtls/x509_crt import mbedtls/bignum import mbedtls/md +import mbedtls/error # This sequence is used for debugging. const mb_ssl_states* = @[ diff --git a/webrtc/stun/stun_transport.nim b/webrtc/stun/stun_transport.nim index fc602cb..ce9bd91 100644 --- a/webrtc/stun/stun_transport.nim +++ b/webrtc/stun/stun_transport.nim @@ -21,8 +21,9 @@ type Stun* = ref object connections: Table[TransportAddress, StunConn] pendingConn: AsyncQueue[StunConn] - readingLoop: Future[void] + readingLoop: Future[void].Raising([CancelledError]) udp: UdpTransport + laddr*: TransportAddress usernameProvider: StunUsernameProvider usernameChecker: StunUsernameChecker @@ -106,11 +107,12 @@ proc new*( ## var self = T( udp: udp, + laddr: udp.laddr, usernameProvider: usernameProvider, usernameChecker: usernameChecker, passwordProvider: passwordProvider, rng: rng ) - self.readingLoop = stunReadLoop() + self.readingLoop = self.stunReadLoop() self.pendingConn = newAsyncQueue[StunConn](StunMaxPendingConnections) return self From ba9f04a65fe527f04e21d4b361b423d39e5e002b Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Fri, 21 Jun 2024 14:39:20 +0200 Subject: [PATCH 09/42] feat: add dtls test --- tests/runalltests.nim | 1 + tests/testdtls.nim | 30 ++++++++++++++++++++++++++++++ webrtc/dtls/dtls_connection.nim | 2 -- 3 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 tests/testdtls.nim diff --git a/tests/runalltests.nim b/tests/runalltests.nim index 764ff3c..3a49806 100644 --- a/tests/runalltests.nim +++ b/tests/runalltests.nim @@ -10,3 +10,4 @@ {.used.} import teststun +import testdtls diff --git a/tests/testdtls.nim b/tests/testdtls.nim new file mode 100644 index 0000000..258b897 --- /dev/null +++ b/tests/testdtls.nim @@ -0,0 +1,30 @@ +import chronos +import ./helpers +import ../webrtc/udp_transport +import ../webrtc/stun/stun_transport +import ../webrtc/dtls/dtls_transport +import ../webrtc/dtls/dtls_connection + +suite "DTLS": + teardown: + checkTrackers() + + asyncTest "Simple Test": + let + udp1 = UdpTransport.new(initTAddress("127.0.0.1:4444")) + udp2 = UdpTransport.new(initTAddress("127.0.0.1:5555")) + stun1 = Stun.new(udp1) + stun2 = Stun.new(udp2) + dtls1 = Dtls.new(stun1) + dtls2 = Dtls.new(stun2) + conn1Fut = dtls1.accept() + conn2 = await dtls2.connect(dtls1.laddr) + conn1 = await conn1Fut + + await conn1.write(@[1'u8, 2, 3, 4]) + let seq1 = await conn2.read() + check seq1 == @[1'u8, 2, 3, 4] + + await conn2.write(@[5'u8, 6, 7, 8]) + let seq2 = await conn1.read() + check seq2 == @[5'u8, 6, 7, 8] diff --git a/webrtc/dtls/dtls_connection.nim b/webrtc/dtls/dtls_connection.nim index f82a4af..0ec291b 100644 --- a/webrtc/dtls/dtls_connection.nim +++ b/webrtc/dtls/dtls_connection.nim @@ -43,7 +43,6 @@ type ctr_drbg*: mbedtls_ctr_drbg_context entropy*: mbedtls_entropy_context - DtlsConn* = ref object conn*: StunConn # The wrapper protocol Stun Connection laddr: TransportAddress # Local address @@ -219,4 +218,3 @@ proc dtlsRecv*(ctx: pointer, buf: ptr byte, len: uint): cint {.cdecl.} = result = self.dataRecv.len().cint self.dataRecv = @[] trace "dtls receive", len, result - From 416ff7bffc0d1670db9fbf80e40a1b151f0f25de Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Fri, 21 Jun 2024 15:23:29 +0200 Subject: [PATCH 10/42] chore: added license & used pragma on testdtls --- tests/testdtls.nim | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/testdtls.nim b/tests/testdtls.nim index 258b897..1243e08 100644 --- a/tests/testdtls.nim +++ b/tests/testdtls.nim @@ -1,3 +1,14 @@ +# Nim-WebRTC +# Copyright (c) 2024 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. + +{.used.} + import chronos import ./helpers import ../webrtc/udp_transport From f8c1b2fc085fa374b35ea69284e8ad3ecf7fdcfb Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Fri, 28 Jun 2024 11:52:38 +0200 Subject: [PATCH 11/42] fix: remove usage of deprecated TrackerCounter --- tests/helpers.nim | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/helpers.nim b/tests/helpers.nim index dad73b3..c83245e 100644 --- a/tests/helpers.nim +++ b/tests/helpers.nim @@ -22,23 +22,23 @@ template asyncTest*(name: string, body: untyped): untyped = test name: waitFor((proc () {.async, gcsafe.} = body)()) -iterator testTrackers*(extras: openArray[string] = []): TrackerBase = +iterator testTrackers*(extras: openArray[string] = []): TrackerCounter = for name in trackerNames: - let t = getTracker(name) - if not isNil(t): yield t + let t = getTrackerCounter(name) + yield t for name in extras: - let t = getTracker(name) - if not isNil(t): yield t + let t = getTrackerCounter(name) + yield t template checkTracker*(name: string) = - var tracker = getTracker(name) - if tracker.isLeaked(): + var tracker = getTrackerCounter(name) + if tracker.isCounterLeaked(): checkpoint tracker.dump() fail() template checkTrackers*() = for tracker in testTrackers(): - if tracker.isLeaked(): + if tracker.isCounterLeaked(): checkpoint tracker.dump() fail() # Also test the GC is not fooling with us From 874cff77cde6a9f92cde9d056008299a0ca435f9 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Fri, 28 Jun 2024 14:42:26 +0200 Subject: [PATCH 12/42] fix: trackers counter --- tests/helpers.nim | 20 +++----------------- tests/testdtls.nim | 6 ++++++ webrtc/stun/stun_transport.nim | 2 +- 3 files changed, 10 insertions(+), 18 deletions(-) diff --git a/tests/helpers.nim b/tests/helpers.nim index c83245e..d68182b 100644 --- a/tests/helpers.nim +++ b/tests/helpers.nim @@ -22,24 +22,10 @@ template asyncTest*(name: string, body: untyped): untyped = test name: waitFor((proc () {.async, gcsafe.} = body)()) -iterator testTrackers*(extras: openArray[string] = []): TrackerCounter = - for name in trackerNames: - let t = getTrackerCounter(name) - yield t - for name in extras: - let t = getTrackerCounter(name) - yield t - -template checkTracker*(name: string) = - var tracker = getTrackerCounter(name) - if tracker.isCounterLeaked(): - checkpoint tracker.dump() - fail() - template checkTrackers*() = - for tracker in testTrackers(): - if tracker.isCounterLeaked(): - checkpoint tracker.dump() + for name in trackerNames: + if name.isCounterLeaked(): + echo name, ": ", name.getTrackerCounter() fail() # Also test the GC is not fooling with us try: diff --git a/tests/testdtls.nim b/tests/testdtls.nim index 1243e08..ab64e40 100644 --- a/tests/testdtls.nim +++ b/tests/testdtls.nim @@ -39,3 +39,9 @@ suite "DTLS": await conn2.write(@[5'u8, 6, 7, 8]) let seq2 = await conn1.read() check seq2 == @[5'u8, 6, 7, 8] + await allFutures(conn1.close(), conn2.close()) + await allFutures(dtls1.stop(), dtls2.stop()) + stun1.stop() + stun2.stop() + udp1.close() + udp2.close() diff --git a/webrtc/stun/stun_transport.nim b/webrtc/stun/stun_transport.nim index ce9bd91..ed4ea17 100644 --- a/webrtc/stun/stun_transport.nim +++ b/webrtc/stun/stun_transport.nim @@ -84,7 +84,7 @@ proc stunReadLoop(self: Stun) {.async: (raises: [CancelledError]).} = else: await stunConn.dataRecv.addLast(buf) -proc stop(self: Stun) = +proc stop*(self: Stun) = ## Stop the Stun transport and close all the connections ## for conn in self.connections.values(): From 3eb4b232f0d949485ddabbd355ac01594f3fe378 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Fri, 19 Jul 2024 12:30:17 +0200 Subject: [PATCH 13/42] fix: - add windows linking library - make stun stop asynchronous (causing issue on macos) - store private key and certificate --- tests/testdtls.nim | 10 ++++------ tests/teststun.nim | 8 ++++---- webrtc.nimble | 5 ++++- webrtc/dtls/dtls_connection.nim | 2 ++ webrtc/dtls/dtls_transport.nim | 18 +++++++++--------- webrtc/stun/stun_connection.nim | 4 ++-- webrtc/stun/stun_transport.nim | 12 +++++++----- 7 files changed, 32 insertions(+), 27 deletions(-) diff --git a/tests/testdtls.nim b/tests/testdtls.nim index ab64e40..47ad63a 100644 --- a/tests/testdtls.nim +++ b/tests/testdtls.nim @@ -10,15 +10,15 @@ {.used.} import chronos -import ./helpers import ../webrtc/udp_transport import ../webrtc/stun/stun_transport import ../webrtc/dtls/dtls_transport import ../webrtc/dtls/dtls_connection +import ./asyncunit suite "DTLS": teardown: - checkTrackers() + checkLeaks() asyncTest "Simple Test": let @@ -41,7 +41,5 @@ suite "DTLS": check seq2 == @[5'u8, 6, 7, 8] await allFutures(conn1.close(), conn2.close()) await allFutures(dtls1.stop(), dtls2.stop()) - stun1.stop() - stun2.stop() - udp1.close() - udp2.close() + await allFutures(stun1.stop(), stun2.stop()) + await allFutures(udp1.close(), udp2.close()) diff --git a/tests/teststun.nim b/tests/teststun.nim index e2506be..01f8ba9 100644 --- a/tests/teststun.nim +++ b/tests/teststun.nim @@ -55,7 +55,7 @@ suite "Stun message encoding/decoding": decoded == msg messageIntegrity.attributeType == AttrMessageIntegrity.uint16 fingerprint.attributeType == AttrFingerprint.uint16 - conn.close() + await conn.close() await udp.close() asyncTest "Get BindingResponse from BindingRequest + encode & decode": @@ -82,7 +82,7 @@ suite "Stun message encoding/decoding": bindingResponse == decoded messageIntegrity.attributeType == AttrMessageIntegrity.uint16 fingerprint.attributeType == AttrFingerprint.uint16 - conn.close() + await conn.close() await udp.close() suite "Stun checkForError": @@ -114,7 +114,7 @@ suite "Stun checkForError": check: errorMissUsername.getAttribute(ErrorCode).get().getErrorCode() == ECBadRequest - conn.close() + await conn.close() await udp.close() asyncTest "checkForError: UsernameChecker returns false": @@ -136,5 +136,5 @@ suite "Stun checkForError": check: error.getAttribute(ErrorCode).get().getErrorCode() == ECUnauthorized - conn.close() + await conn.close() await udp.close() diff --git a/webrtc.nimble b/webrtc.nimble index ed35db0..da9e66f 100644 --- a/webrtc.nimble +++ b/webrtc.nimble @@ -17,12 +17,15 @@ let lang = getEnv("NIMLANG", "c") # Which backend (c/cpp/js) let flags = getEnv("NIMFLAGS", "") # Extra flags for the compiler let verbose = getEnv("V", "") notin ["", "0"] -let cfg = +var cfg = " --styleCheck:usages --styleCheck:error" & (if verbose: "" else: " --verbosity:0 --hints:off") & " --skipParentCfg --skipUserCfg -f" & " --threads:on --opt:speed" +when defined(windows): + cfg = cfg & " --clib:ws2_32" + import hashes proc runTest(filename: string) = diff --git a/webrtc/dtls/dtls_connection.nim b/webrtc/dtls/dtls_connection.nim index 0ec291b..6ca9c9a 100644 --- a/webrtc/dtls/dtls_connection.nim +++ b/webrtc/dtls/dtls_connection.nim @@ -39,6 +39,8 @@ type cookie*: mbedtls_ssl_cookie_ctx cache*: mbedtls_ssl_cache_context timer*: mbedtls_timing_delay_context + pkey*: mbedtls_pk_context + srvcert*: mbedtls_x509_crt ctr_drbg*: mbedtls_ctr_drbg_context entropy*: mbedtls_entropy_context diff --git a/webrtc/dtls/dtls_transport.nim b/webrtc/dtls/dtls_transport.nim index 3e6e8a8..acce005 100644 --- a/webrtc/dtls/dtls_transport.nim +++ b/webrtc/dtls/dtls_transport.nim @@ -100,8 +100,8 @@ proc accept*(self: Dtls): Future[DtlsConn] {.async.} = res.ctx.ctr_drbg = self.ctr_drbg res.ctx.entropy = self.entropy - var pkey = self.serverPrivKey - var srvcert = self.serverCert + res.ctx.pkey = self.serverPrivKey + res.ctx.srvcert = self.serverCert res.localCert = self.localCert mb_ssl_config_defaults( @@ -112,8 +112,8 @@ proc accept*(self: Dtls): Future[DtlsConn] {.async.} = ) mb_ssl_conf_rng(res.ctx.config, mbedtls_ctr_drbg_random, res.ctx.ctr_drbg) mb_ssl_conf_read_timeout(res.ctx.config, 10000) # in milliseconds - mb_ssl_conf_ca_chain(res.ctx.config, srvcert.next, nil) - mb_ssl_conf_own_cert(res.ctx.config, srvcert, pkey) + mb_ssl_conf_ca_chain(res.ctx.config, res.ctx.srvcert.next, nil) + mb_ssl_conf_own_cert(res.ctx.config, res.ctx.srvcert, res.ctx.pkey) mb_ssl_cookie_setup(res.ctx.cookie, mbedtls_ctr_drbg_random, res.ctx.ctr_drbg) mb_ssl_conf_dtls_cookies(res.ctx.config, addr res.ctx.cookie) mb_ssl_set_timer_cb(res.ctx.ssl, res.ctx.timer) @@ -145,10 +145,10 @@ proc connect*(self: Dtls, raddr: TransportAddress): Future[DtlsConn] {.async.} = res.ctx.ctr_drbg = self.ctr_drbg res.ctx.entropy = self.entropy - var pkey = res.ctx.ctr_drbg.generateKey() - var srvcert = res.ctx.ctr_drbg.generateCertificate(pkey) - res.localCert = newSeq[byte](srvcert.raw.len) - copyMem(addr res.localCert[0], srvcert.raw.p, srvcert.raw.len) + res.ctx.pkey = res.ctx.ctr_drbg.generateKey() + res.ctx.srvcert = res.ctx.ctr_drbg.generateCertificate(res.ctx.pkey) + res.localCert = newSeq[byte](res.ctx.srvcert.raw.len) + copyMem(addr res.localCert[0], res.ctx.srvcert.raw.p, res.ctx.srvcert.raw.len) mb_ctr_drbg_init(res.ctx.ctr_drbg) mb_entropy_init(res.ctx.entropy) @@ -160,7 +160,7 @@ proc connect*(self: Dtls, raddr: TransportAddress): Future[DtlsConn] {.async.} = MBEDTLS_SSL_PRESET_DEFAULT) mb_ssl_conf_rng(res.ctx.config, mbedtls_ctr_drbg_random, res.ctx.ctr_drbg) mb_ssl_conf_read_timeout(res.ctx.config, 10000) # in milliseconds - mb_ssl_conf_ca_chain(res.ctx.config, srvcert.next, nil) + mb_ssl_conf_ca_chain(res.ctx.config, res.ctx.srvcert.next, nil) mb_ssl_set_timer_cb(res.ctx.ssl, res.ctx.timer) mb_ssl_setup(res.ctx.ssl, res.ctx.config) mb_ssl_set_verify(res.ctx.ssl, verify, res) diff --git a/webrtc/stun/stun_connection.nim b/webrtc/stun/stun_connection.nim index ef355ca..2f119da 100644 --- a/webrtc/stun/stun_connection.nim +++ b/webrtc/stun/stun_connection.nim @@ -220,14 +220,14 @@ proc join*(self: StunConn) {.async: (raises: [CancelledError]).} = ## await self.closeEvent.wait() -proc close*(self: StunConn) = +proc close*(self: StunConn) {.async: (raises: []).} = ## Close a Stun Connection ## if self.closed: debug "Try to close an already closed StunConn" return + await self.handlesFut.cancelAndWait() self.closeEvent.fire() - self.handlesFut.cancelSoon() self.closed = true untrackCounter(StunConnectionTracker) diff --git a/webrtc/stun/stun_transport.nim b/webrtc/stun/stun_transport.nim index 9426026..0fbb1ad 100644 --- a/webrtc/stun/stun_transport.nim +++ b/webrtc/stun/stun_transport.nim @@ -7,7 +7,7 @@ # This file may not be copied, modified, or distributed except according to # those terms. -import tables +import tables, sequtils import chronos, chronicles, bearssl import stun_connection, stun_message, ../udp_transport @@ -85,12 +85,14 @@ proc stunReadLoop(self: Stun) {.async: (raises: [CancelledError]).} = else: await stunConn.dataRecv.addLast(buf) -proc stop*(self: Stun) = +proc stop*(self: Stun) {.async: (raises: []).} = ## Stop the Stun transport and close all the connections ## - for conn in self.connections.values(): - conn.close() - self.readingLoop.cancelSoon() + try: + await allFutures(toSeq(self.connections.values()).mapIt(it.close())) + except CancelledError as exc: + discard + await self.readingLoop.cancelAndWait() untrackCounter(StunTransportTracker) proc defaultUsernameProvider(): string = "" From 0f144ce864d6e8bfc7d65224eb7fb3042782cd0f Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Wed, 31 Jul 2024 11:16:27 +0200 Subject: [PATCH 14/42] chore: renaming test --- tests/testdtls.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testdtls.nim b/tests/testdtls.nim index 47ad63a..f772599 100644 --- a/tests/testdtls.nim +++ b/tests/testdtls.nim @@ -20,7 +20,7 @@ suite "DTLS": teardown: checkLeaks() - asyncTest "Simple Test": + asyncTest "Two DTLS nodes connecting to each other, then sending/receiving data": let udp1 = UdpTransport.new(initTAddress("127.0.0.1:4444")) udp2 = UdpTransport.new(initTAddress("127.0.0.1:5555")) From 33372bc30029d630343585c3722cf33bb9ca5491 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Wed, 31 Jul 2024 11:21:11 +0200 Subject: [PATCH 15/42] docs: update DtlsConn comment --- webrtc/dtls/dtls_connection.nim | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/webrtc/dtls/dtls_connection.nim b/webrtc/dtls/dtls_connection.nim index 6ca9c9a..c3af6da 100644 --- a/webrtc/dtls/dtls_connection.nim +++ b/webrtc/dtls/dtls_connection.nim @@ -28,10 +28,6 @@ import mbedtls/timing logScope: topics = "webrtc dtls_conn" -# -- DtlsConn -- -# A Dtls connection to a specific IP address recovered by the receiving part of -# the Udp "connection" - type MbedTLSCtx* = object ssl*: mbedtls_ssl_context @@ -46,6 +42,8 @@ type entropy*: mbedtls_entropy_context DtlsConn* = ref object + # DtlsConn is a Dtls connection receiving and sending data using + # the underlying Stun Connection conn*: StunConn # The wrapper protocol Stun Connection laddr: TransportAddress # Local address raddr*: TransportAddress # Remote address From 59fd302542dc4b442796276be5ed7c54cb1a3d2c Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Wed, 31 Jul 2024 11:56:59 +0200 Subject: [PATCH 16/42] fix: remove code duplicate --- webrtc/dtls/dtls_connection.nim | 1 - webrtc/dtls/dtls_transport.nim | 11 +---------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/webrtc/dtls/dtls_connection.nim b/webrtc/dtls/dtls_connection.nim index c3af6da..4e27e92 100644 --- a/webrtc/dtls/dtls_connection.nim +++ b/webrtc/dtls/dtls_connection.nim @@ -122,7 +122,6 @@ proc close*(self: DtlsConn) {.async: (raises: [CancelledError, WebRtcError]).} = if self.closed: debug "Try to close an already closed DtlsConn" return - self.closed = true self.sendFuture = nil # TODO: proc mbedtls_ssl_close_notify => template mb_ssl_close_notify in nim-mbedtls diff --git a/webrtc/dtls/dtls_transport.nim b/webrtc/dtls/dtls_transport.nim index acce005..a23d88d 100644 --- a/webrtc/dtls/dtls_transport.nim +++ b/webrtc/dtls/dtls_transport.nim @@ -34,8 +34,6 @@ logScope: # Multiple things here are unintuitive partly because of the callbacks # used by Mbed-TLS and that those callbacks cannot be async. -# -- Dtls -- - type Dtls* = ref object of RootObj connections: Table[TransportAddress, DtlsConn] @@ -75,13 +73,10 @@ proc stop*(self: Dtls) {.async.} = await allFutures(toSeq(self.connections.values()).mapIt(it.close())) self.started = false -# -- Remote / Local certificate getter -- - proc localCertificate*(self: Dtls): seq[byte] = + ## Local certificate getter self.localCert -# -- Dtls Accept / Connect procedures -- - proc cleanupDtlsConn(self: Dtls, conn: DtlsConn) {.async.} = # Waiting for a connection to be closed to remove it from the table await conn.join() @@ -150,10 +145,6 @@ proc connect*(self: Dtls, raddr: TransportAddress): Future[DtlsConn] {.async.} = res.localCert = newSeq[byte](res.ctx.srvcert.raw.len) copyMem(addr res.localCert[0], res.ctx.srvcert.raw.p, res.ctx.srvcert.raw.len) - mb_ctr_drbg_init(res.ctx.ctr_drbg) - mb_entropy_init(res.ctx.entropy) - mb_ctr_drbg_seed(res.ctx.ctr_drbg, mbedtls_entropy_func, res.ctx.entropy, nil, 0) - mb_ssl_config_defaults(res.ctx.config, MBEDTLS_SSL_IS_CLIENT, MBEDTLS_SSL_TRANSPORT_DATAGRAM, From ffa8a514560f6e39318a0e4f8428792f2f98737a Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Wed, 31 Jul 2024 12:03:38 +0200 Subject: [PATCH 17/42] chore: update comment --- webrtc/dtls/dtls_transport.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webrtc/dtls/dtls_transport.nim b/webrtc/dtls/dtls_transport.nim index a23d88d..5844497 100644 --- a/webrtc/dtls/dtls_transport.nim +++ b/webrtc/dtls/dtls_transport.nim @@ -32,7 +32,7 @@ logScope: # Implementation of a DTLS client and a DTLS Server by using the Mbed-TLS library. # Multiple things here are unintuitive partly because of the callbacks -# used by Mbed-TLS and that those callbacks cannot be async. +# used by Mbed-TLS which cannot be async. type Dtls* = ref object of RootObj From d003d200557091c7630224661491b467f4fccb71 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Wed, 31 Jul 2024 13:00:34 +0200 Subject: [PATCH 18/42] chore: remove duplication mbedtls initialization code in accept/connect and un-expose mbedtls context --- webrtc/dtls/dtls_connection.nim | 163 +++++++++++++++++++++----------- webrtc/dtls/dtls_transport.nim | 58 +----------- 2 files changed, 113 insertions(+), 108 deletions(-) diff --git a/webrtc/dtls/dtls_connection.nim b/webrtc/dtls/dtls_connection.nim index 4e27e92..c74af3f 100644 --- a/webrtc/dtls/dtls_connection.nim +++ b/webrtc/dtls/dtls_connection.nim @@ -9,6 +9,7 @@ import chronos, chronicles import ../errors, ../stun/[stun_connection] +import ./dtls_utils import mbedtls/ssl import mbedtls/ssl_cookie @@ -29,17 +30,17 @@ logScope: topics = "webrtc dtls_conn" type - MbedTLSCtx* = object - ssl*: mbedtls_ssl_context - config*: mbedtls_ssl_config - cookie*: mbedtls_ssl_cookie_ctx - cache*: mbedtls_ssl_cache_context - timer*: mbedtls_timing_delay_context - pkey*: mbedtls_pk_context - srvcert*: mbedtls_x509_crt - - ctr_drbg*: mbedtls_ctr_drbg_context - entropy*: mbedtls_entropy_context + MbedTLSCtx = object + ssl: mbedtls_ssl_context + config: mbedtls_ssl_config + cookie: mbedtls_ssl_cookie_ctx + cache: mbedtls_ssl_cache_context + timer: mbedtls_timing_delay_context + pkey: mbedtls_pk_context + srvcert: mbedtls_x509_crt + + ctr_drbg: mbedtls_ctr_drbg_context + entropy: mbedtls_entropy_context DtlsConn* = ref object # DtlsConn is a Dtls connection receiving and sending data using @@ -64,6 +65,46 @@ type # Mbed-TLS contexts ctx*: MbedTLSCtx +proc verify(ctx: pointer, pcert: ptr mbedtls_x509_crt, + state: cint, pflags: ptr uint32): cint {.cdecl.} = + # verify is the procedure called by mbedtls when receiving the remote + # certificate. It's usually used to verify the validity of the certificate. + # We use this procedure to store the remote certificate as it's mandatory + # to have it for the Prologue of the Noise protocol, aswell as the localCertificate. + var self = cast[DtlsConn](ctx) + let cert = pcert[] + + self.remoteCert = newSeq[byte](cert.raw.len) + copyMem(addr self.remoteCert[0], cert.raw.p, cert.raw.len) + return 0 + +proc dtlsSend(ctx: pointer, buf: ptr byte, len: uint): cint {.cdecl.} = + # dtlsSend is the procedure called by mbedtls when data needs to be sent. + # As the StunConn's write proc is asynchronous and dtlsSend cannot be async, + # we store the future of this write and await it after the end of the + # function (see write or dtlsHanshake for example). + var self = cast[DtlsConn](ctx) + var toWrite = newSeq[byte](len) + if len > 0: + copyMem(addr toWrite[0], buf, len) + trace "dtls send", len + self.sendFuture = self.conn.write(toWrite) + result = len.cint + +proc dtlsRecv(ctx: pointer, buf: ptr byte, len: uint): cint {.cdecl.} = + # dtlsRecv is the procedure called by mbedtls when data needs to be received. + # As we cannot asynchronously await for data to be received, we use a data received + # queue. If this queue is empty, we return `MBEDTLS_ERR_SSL_WANT_READ` for us to await + # when the mbedtls proc resumed (see read or dtlsHandshake for example) + let self = cast[DtlsConn](ctx) + if self.dataRecv.len() == 0: + return MBEDTLS_ERR_SSL_WANT_READ + + copyMem(buf, addr self.dataRecv[0], self.dataRecv.len()) + result = self.dataRecv.len().cint + self.dataRecv = @[] + trace "dtls receive", len, result + proc new*(T: type DtlsConn, conn: StunConn, laddr: TransportAddress): T = ## Initialize a Dtls Connection ## @@ -73,6 +114,64 @@ proc new*(T: type DtlsConn, conn: StunConn, laddr: TransportAddress): T = self.closeEvent = newAsyncEvent() return self +proc dtlsConnInit(self: DtlsConn) = + mb_ssl_init(self.ctx.ssl) + mb_ssl_config_init(self.ctx.config) + mb_ssl_conf_rng(self.ctx.config, mbedtls_ctr_drbg_random, self.ctx.ctr_drbg) + mb_ssl_conf_read_timeout(self.ctx.config, 10000) # in milliseconds + mb_ssl_conf_ca_chain(self.ctx.config, self.ctx.srvcert.next, nil) + mb_ssl_set_timer_cb(self.ctx.ssl, self.ctx.timer) + mb_ssl_set_verify(self.ctx.ssl, verify, self) + mb_ssl_set_bio(self.ctx.ssl, cast[pointer](self), dtlsSend, dtlsRecv, nil) + +proc acceptInit*( + self: DtlsConn, + ctr_drbg: mbedtls_ctr_drbg_context, + pkey: mbedtls_pk_context, + srvcert: mbedtls_x509_crt, + localCert: seq[byte] +) = + self.ctx.ctr_drbg = ctr_drbg + self.ctx.pkey = pkey + self.ctx.srvcert = srvcert + self.localCert = localCert + + self.dtlsConnInit() + mb_ssl_cookie_init(self.ctx.cookie) + mb_ssl_cache_init(self.ctx.cache) + mb_ssl_config_defaults( + self.ctx.config, + MBEDTLS_SSL_IS_SERVER, + MBEDTLS_SSL_TRANSPORT_DATAGRAM, + MBEDTLS_SSL_PRESET_DEFAULT + ) + mb_ssl_conf_own_cert(self.ctx.config, self.ctx.srvcert, self.ctx.pkey) + mb_ssl_cookie_setup(self.ctx.cookie, mbedtls_ctr_drbg_random, self.ctx.ctr_drbg) + mb_ssl_conf_dtls_cookies(self.ctx.config, addr self.ctx.cookie) + mb_ssl_setup(self.ctx.ssl, self.ctx.config) + mb_ssl_session_reset(self.ctx.ssl) + mb_ssl_conf_authmode(self.ctx.config, MBEDTLS_SSL_VERIFY_OPTIONAL) + +proc connectInit*( + self: DtlsConn, + ctr_drbg: mbedtls_ctr_drbg_context +) = + self.ctx.ctr_drbg = ctr_drbg + self.ctx.pkey = self.ctx.ctr_drbg.generateKey() + self.ctx.srvcert = self.ctx.ctr_drbg.generateCertificate(self.ctx.pkey) + self.localCert = newSeq[byte](self.ctx.srvcert.raw.len) + copyMem(addr self.localCert[0], self.ctx.srvcert.raw.p, self.ctx.srvcert.raw.len) + + self.dtlsConnInit() + mb_ssl_config_defaults( + self.ctx.config, + MBEDTLS_SSL_IS_CLIENT, + MBEDTLS_SSL_TRANSPORT_DATAGRAM, + MBEDTLS_SSL_PRESET_DEFAULT + ) + mb_ssl_setup(self.ctx.ssl, self.ctx.config) + mb_ssl_conf_authmode(self.ctx.config, MBEDTLS_SSL_VERIFY_OPTIONAL) + proc join*(self: DtlsConn) {.async: (raises: [CancelledError]).} = ## Wait for the Dtls Connection to be closed ## @@ -175,45 +274,3 @@ proc remoteCertificate*(conn: DtlsConn): seq[byte] = proc localCertificate*(conn: DtlsConn): seq[byte] = conn.localCert - -# -- MbedTLS Callbacks -- - -proc verify*(ctx: pointer, pcert: ptr mbedtls_x509_crt, - state: cint, pflags: ptr uint32): cint {.cdecl.} = - # verify is the procedure called by mbedtls when receiving the remote - # certificate. It's usually used to verify the validity of the certificate. - # We use this procedure to store the remote certificate as it's mandatory - # to have it for the Prologue of the Noise protocol, aswell as the localCertificate. - var self = cast[DtlsConn](ctx) - let cert = pcert[] - - self.remoteCert = newSeq[byte](cert.raw.len) - copyMem(addr self.remoteCert[0], cert.raw.p, cert.raw.len) - return 0 - -proc dtlsSend*(ctx: pointer, buf: ptr byte, len: uint): cint {.cdecl.} = - # dtlsSend is the procedure called by mbedtls when data needs to be sent. - # As the StunConn's write proc is asynchronous and dtlsSend cannot be async, - # we store the future of this write and await it after the end of the - # function (see write or dtlsHanshake for example). - var self = cast[DtlsConn](ctx) - var toWrite = newSeq[byte](len) - if len > 0: - copyMem(addr toWrite[0], buf, len) - trace "dtls send", len - self.sendFuture = self.conn.write(toWrite) - result = len.cint - -proc dtlsRecv*(ctx: pointer, buf: ptr byte, len: uint): cint {.cdecl.} = - # dtlsRecv is the procedure called by mbedtls when data needs to be received. - # As we cannot asynchronously await for data to be received, we use a data received - # queue. If this queue is empty, we return `MBEDTLS_ERR_SSL_WANT_READ` for us to await - # when the mbedtls proc resumed (see read or dtlsHandshake for example) - let self = cast[DtlsConn](ctx) - if self.dataRecv.len() == 0: - return MBEDTLS_ERR_SSL_WANT_READ - - copyMem(buf, addr self.dataRecv[0], self.dataRecv.len()) - result = self.dataRecv.len().cint - self.dataRecv = @[] - trace "dtls receive", len, result diff --git a/webrtc/dtls/dtls_transport.nim b/webrtc/dtls/dtls_transport.nim index 5844497..28573d5 100644 --- a/webrtc/dtls/dtls_transport.nim +++ b/webrtc/dtls/dtls_transport.nim @@ -7,7 +7,7 @@ # This file may not be copied, modified, or distributed except according to # those terms. -import times, deques, tables, sequtils +import deques, tables, sequtils import chronos, chronicles import ./[dtls_utils, dtls_connection], ../errors, ../stun/[stun_connection, stun_transport] @@ -86,37 +86,8 @@ proc accept*(self: Dtls): Future[DtlsConn] {.async.} = ## Accept a Dtls Connection ## var res = DtlsConn.new(await self.transport.accept(), self.laddr) + res.acceptInit(self.ctr_drbg, self.serverPrivKey, self.serverCert, self.localCert) - mb_ssl_init(res.ctx.ssl) - mb_ssl_config_init(res.ctx.config) - mb_ssl_cookie_init(res.ctx.cookie) - mb_ssl_cache_init(res.ctx.cache) - - res.ctx.ctr_drbg = self.ctr_drbg - res.ctx.entropy = self.entropy - - res.ctx.pkey = self.serverPrivKey - res.ctx.srvcert = self.serverCert - res.localCert = self.localCert - - mb_ssl_config_defaults( - res.ctx.config, - MBEDTLS_SSL_IS_SERVER, - MBEDTLS_SSL_TRANSPORT_DATAGRAM, - MBEDTLS_SSL_PRESET_DEFAULT - ) - mb_ssl_conf_rng(res.ctx.config, mbedtls_ctr_drbg_random, res.ctx.ctr_drbg) - mb_ssl_conf_read_timeout(res.ctx.config, 10000) # in milliseconds - mb_ssl_conf_ca_chain(res.ctx.config, res.ctx.srvcert.next, nil) - mb_ssl_conf_own_cert(res.ctx.config, res.ctx.srvcert, res.ctx.pkey) - mb_ssl_cookie_setup(res.ctx.cookie, mbedtls_ctr_drbg_random, res.ctx.ctr_drbg) - mb_ssl_conf_dtls_cookies(res.ctx.config, addr res.ctx.cookie) - mb_ssl_set_timer_cb(res.ctx.ssl, res.ctx.timer) - mb_ssl_setup(res.ctx.ssl, res.ctx.config) - mb_ssl_session_reset(res.ctx.ssl) - mb_ssl_set_verify(res.ctx.ssl, verify, res) - mb_ssl_conf_authmode(res.ctx.config, MBEDTLS_SSL_VERIFY_OPTIONAL) - mb_ssl_set_bio(res.ctx.ssl, cast[pointer](res), dtlsSend, dtlsRecv, nil) while true: try: self.connections[res.raddr] = res @@ -133,30 +104,7 @@ proc accept*(self: Dtls): Future[DtlsConn] {.async.} = proc connect*(self: Dtls, raddr: TransportAddress): Future[DtlsConn] {.async.} = ## Connect to a remote address, creating a Dtls Connection var res = DtlsConn.new(await self.transport.connect(raddr), self.laddr) - - mb_ssl_init(res.ctx.ssl) - mb_ssl_config_init(res.ctx.config) - - res.ctx.ctr_drbg = self.ctr_drbg - res.ctx.entropy = self.entropy - - res.ctx.pkey = res.ctx.ctr_drbg.generateKey() - res.ctx.srvcert = res.ctx.ctr_drbg.generateCertificate(res.ctx.pkey) - res.localCert = newSeq[byte](res.ctx.srvcert.raw.len) - copyMem(addr res.localCert[0], res.ctx.srvcert.raw.p, res.ctx.srvcert.raw.len) - - mb_ssl_config_defaults(res.ctx.config, - MBEDTLS_SSL_IS_CLIENT, - MBEDTLS_SSL_TRANSPORT_DATAGRAM, - MBEDTLS_SSL_PRESET_DEFAULT) - mb_ssl_conf_rng(res.ctx.config, mbedtls_ctr_drbg_random, res.ctx.ctr_drbg) - mb_ssl_conf_read_timeout(res.ctx.config, 10000) # in milliseconds - mb_ssl_conf_ca_chain(res.ctx.config, res.ctx.srvcert.next, nil) - mb_ssl_set_timer_cb(res.ctx.ssl, res.ctx.timer) - mb_ssl_setup(res.ctx.ssl, res.ctx.config) - mb_ssl_set_verify(res.ctx.ssl, verify, res) - mb_ssl_conf_authmode(res.ctx.config, MBEDTLS_SSL_VERIFY_OPTIONAL) - mb_ssl_set_bio(res.ctx.ssl, cast[pointer](res), dtlsSend, dtlsRecv, nil) + res.connectInit(self.ctr_drbg) try: self.connections[raddr] = res From a9ec6582b5638c01bdebc6d5548777c5516c733a Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Thu, 1 Aug 2024 15:27:57 +0200 Subject: [PATCH 19/42] feat: add exception management to dtls_transport --- webrtc/dtls/dtls_connection.nim | 72 ++++++++++++++++++--------------- webrtc/dtls/dtls_transport.nim | 11 +++-- webrtc/dtls/dtls_utils.nim | 12 ++++-- 3 files changed, 54 insertions(+), 41 deletions(-) diff --git a/webrtc/dtls/dtls_connection.nim b/webrtc/dtls/dtls_connection.nim index c74af3f..922eed9 100644 --- a/webrtc/dtls/dtls_connection.nim +++ b/webrtc/dtls/dtls_connection.nim @@ -131,46 +131,52 @@ proc acceptInit*( srvcert: mbedtls_x509_crt, localCert: seq[byte] ) = - self.ctx.ctr_drbg = ctr_drbg - self.ctx.pkey = pkey - self.ctx.srvcert = srvcert - self.localCert = localCert + try: + self.ctx.ctr_drbg = ctr_drbg + self.ctx.pkey = pkey + self.ctx.srvcert = srvcert + self.localCert = localCert - self.dtlsConnInit() - mb_ssl_cookie_init(self.ctx.cookie) - mb_ssl_cache_init(self.ctx.cache) - mb_ssl_config_defaults( - self.ctx.config, - MBEDTLS_SSL_IS_SERVER, - MBEDTLS_SSL_TRANSPORT_DATAGRAM, - MBEDTLS_SSL_PRESET_DEFAULT - ) - mb_ssl_conf_own_cert(self.ctx.config, self.ctx.srvcert, self.ctx.pkey) - mb_ssl_cookie_setup(self.ctx.cookie, mbedtls_ctr_drbg_random, self.ctx.ctr_drbg) - mb_ssl_conf_dtls_cookies(self.ctx.config, addr self.ctx.cookie) - mb_ssl_setup(self.ctx.ssl, self.ctx.config) - mb_ssl_session_reset(self.ctx.ssl) - mb_ssl_conf_authmode(self.ctx.config, MBEDTLS_SSL_VERIFY_OPTIONAL) + self.dtlsConnInit() + mb_ssl_cookie_init(self.ctx.cookie) + mb_ssl_cache_init(self.ctx.cache) + mb_ssl_config_defaults( + self.ctx.config, + MBEDTLS_SSL_IS_SERVER, + MBEDTLS_SSL_TRANSPORT_DATAGRAM, + MBEDTLS_SSL_PRESET_DEFAULT + ) + mb_ssl_conf_own_cert(self.ctx.config, self.ctx.srvcert, self.ctx.pkey) + mb_ssl_cookie_setup(self.ctx.cookie, mbedtls_ctr_drbg_random, self.ctx.ctr_drbg) + mb_ssl_conf_dtls_cookies(self.ctx.config, addr self.ctx.cookie) + mb_ssl_setup(self.ctx.ssl, self.ctx.config) + mb_ssl_session_reset(self.ctx.ssl) + mb_ssl_conf_authmode(self.ctx.config, MBEDTLS_SSL_VERIFY_OPTIONAL) + except MbedTLSError as exc: + raise newException(WebRtcError, "DTLS - Accept initialization: " & exc.msg, exc) proc connectInit*( self: DtlsConn, ctr_drbg: mbedtls_ctr_drbg_context ) = - self.ctx.ctr_drbg = ctr_drbg - self.ctx.pkey = self.ctx.ctr_drbg.generateKey() - self.ctx.srvcert = self.ctx.ctr_drbg.generateCertificate(self.ctx.pkey) - self.localCert = newSeq[byte](self.ctx.srvcert.raw.len) - copyMem(addr self.localCert[0], self.ctx.srvcert.raw.p, self.ctx.srvcert.raw.len) + try: + self.ctx.ctr_drbg = ctr_drbg + self.ctx.pkey = self.ctx.ctr_drbg.generateKey() + self.ctx.srvcert = self.ctx.ctr_drbg.generateCertificate(self.ctx.pkey) + self.localCert = newSeq[byte](self.ctx.srvcert.raw.len) + copyMem(addr self.localCert[0], self.ctx.srvcert.raw.p, self.ctx.srvcert.raw.len) - self.dtlsConnInit() - mb_ssl_config_defaults( - self.ctx.config, - MBEDTLS_SSL_IS_CLIENT, - MBEDTLS_SSL_TRANSPORT_DATAGRAM, - MBEDTLS_SSL_PRESET_DEFAULT - ) - mb_ssl_setup(self.ctx.ssl, self.ctx.config) - mb_ssl_conf_authmode(self.ctx.config, MBEDTLS_SSL_VERIFY_OPTIONAL) + self.dtlsConnInit() + mb_ssl_config_defaults( + self.ctx.config, + MBEDTLS_SSL_IS_CLIENT, + MBEDTLS_SSL_TRANSPORT_DATAGRAM, + MBEDTLS_SSL_PRESET_DEFAULT + ) + mb_ssl_setup(self.ctx.ssl, self.ctx.config) + mb_ssl_conf_authmode(self.ctx.config, MBEDTLS_SSL_VERIFY_OPTIONAL) + except MbedTLSError as exc: + raise newException(WebRtcError, "DTLS - Connect initialization: " & exc.msg, exc) proc join*(self: DtlsConn) {.async: (raises: [CancelledError]).} = ## Wait for the Dtls Connection to be closed diff --git a/webrtc/dtls/dtls_transport.nim b/webrtc/dtls/dtls_transport.nim index 28573d5..429354b 100644 --- a/webrtc/dtls/dtls_transport.nim +++ b/webrtc/dtls/dtls_transport.nim @@ -65,7 +65,7 @@ proc new*(T: type Dtls, transport: Stun): T = copyMem(addr self.localCert[0], self.serverCert.raw.p, self.serverCert.raw.len) return self -proc stop*(self: Dtls) {.async.} = +proc stop*(self: Dtls) {.async: (raises: [CancelledError, WebRtcError]).} = if not self.started: warn "Already stopped" return @@ -77,12 +77,12 @@ proc localCertificate*(self: Dtls): seq[byte] = ## Local certificate getter self.localCert -proc cleanupDtlsConn(self: Dtls, conn: DtlsConn) {.async.} = +proc cleanupDtlsConn(self: Dtls, conn: DtlsConn) {.async: (raises: [CancelledError]).} = # Waiting for a connection to be closed to remove it from the table await conn.join() self.connections.del(conn.raddr) -proc accept*(self: Dtls): Future[DtlsConn] {.async.} = +proc accept*(self: Dtls): Future[DtlsConn] {.async: (raises: [CancelledError, WebRtcError]).} = ## Accept a Dtls Connection ## var res = DtlsConn.new(await self.transport.accept(), self.laddr) @@ -101,7 +101,10 @@ proc accept*(self: Dtls): Future[DtlsConn] {.async.} = res.conn = await self.transport.accept() return res -proc connect*(self: Dtls, raddr: TransportAddress): Future[DtlsConn] {.async.} = +proc connect*( + self: Dtls, + raddr: TransportAddress +): Future[DtlsConn] {.async: (raises: [CancelledError, WebRtcError]).} = ## Connect to a remote address, creating a Dtls Connection var res = DtlsConn.new(await self.transport.connect(raddr), self.laddr) res.connectInit(self.ctr_drbg) diff --git a/webrtc/dtls/dtls_utils.nim b/webrtc/dtls/dtls_utils.nim index 65f8598..45005b4 100644 --- a/webrtc/dtls/dtls_utils.nim +++ b/webrtc/dtls/dtls_utils.nim @@ -8,6 +8,7 @@ # those terms. import std/times +import ../errors import mbedtls/pk import mbedtls/rsa @@ -62,9 +63,12 @@ template generateKey*(random: mbedtls_ctr_drbg_context): mbedtls_pk_context = template generateCertificate*(random: mbedtls_ctr_drbg_context, issuer_key: mbedtls_pk_context): mbedtls_x509_crt = let - # To be honest, I have no clue what to put here as a name name = "C=FR,O=Status,CN=webrtc" - time_format = initTimeFormat("YYYYMMddHHmmss") + time_format = + try: + initTimeFormat("YYYYMMddHHmmss") + except TimeFormatParseError as exc: + raise newException(WebRtcError, "DTLS - " & exc.msg, exc) time_from = times.now().format(time_format) time_to = (times.now() + times.years(1)).format(time_format) @@ -86,8 +90,8 @@ template generateCertificate*(random: mbedtls_ctr_drbg_context, let buf = try: mb_x509write_crt_pem(write_cert, 2048, mbedtls_ctr_drbg_random, random) - except MbedTLSError as e: - raise e + except MbedTLSError as exc: + raise newException(WebRtcError, "DTLS - " & exc.msg, exc) var res: mbedtls_x509_crt mb_x509_crt_parse(res, buf) res From f49ecea31098148fc77d121742067d7e95b31d5b Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Fri, 2 Aug 2024 14:14:58 +0200 Subject: [PATCH 20/42] fix: check address family before handshake --- webrtc/dtls/dtls_connection.nim | 2 -- webrtc/dtls/dtls_transport.nim | 24 ++++++++++++++---------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/webrtc/dtls/dtls_connection.nim b/webrtc/dtls/dtls_connection.nim index 922eed9..99cbad6 100644 --- a/webrtc/dtls/dtls_connection.nim +++ b/webrtc/dtls/dtls_connection.nim @@ -197,8 +197,6 @@ proc dtlsHandshake*( mb_ssl_set_client_transport_id(self.ctx.ssl, self.raddr.address_v4) of AddressFamily.IPv6: mb_ssl_set_client_transport_id(self.ctx.ssl, self.raddr.address_v6) - else: - raise newException(WebRtcError, "DTLS - Remote address isn't an IP address") let (data, _) = await self.conn.read() self.dataRecv = data self.sendFuture = nil diff --git a/webrtc/dtls/dtls_transport.nim b/webrtc/dtls/dtls_transport.nim index 429354b..fad0710 100644 --- a/webrtc/dtls/dtls_transport.nim +++ b/webrtc/dtls/dtls_transport.nim @@ -89,16 +89,17 @@ proc accept*(self: Dtls): Future[DtlsConn] {.async: (raises: [CancelledError, We res.acceptInit(self.ctr_drbg, self.serverPrivKey, self.serverCert, self.localCert) while true: - try: - self.connections[res.raddr] = res - await res.dtlsHandshake(true) - asyncSpawn self.cleanupDtlsConn(res) - break - except WebRtcError as exc: - trace "Handshake fails, try accept another connection", - remoteAddress = res.raddr, error = exc.msg - self.connections.del(res.raddr) - res.conn = await self.transport.accept() + if res.raddr.family == AddressFamily.IPv4 or res.raddr.family == AddressFamily.IPv6: + try: + self.connections[res.raddr] = res + await res.dtlsHandshake(true) + asyncSpawn self.cleanupDtlsConn(res) + break + except WebRtcError as exc: + trace "Handshake fails, try accept another connection", + remoteAddress = res.raddr, error = exc.msg + self.connections.del(res.raddr) + res = DtlsConn.new(await self.transport.accept(), self.laddr) return res proc connect*( @@ -106,6 +107,9 @@ proc connect*( raddr: TransportAddress ): Future[DtlsConn] {.async: (raises: [CancelledError, WebRtcError]).} = ## Connect to a remote address, creating a Dtls Connection + ## + if raddr.family != AddressFamily.IPv4 and raddr.family != AddressFamily.IPv6: + raise newException(WebRtcError, "DTLS - Can only connect to IP address") var res = DtlsConn.new(await self.transport.connect(raddr), self.laddr) res.connectInit(self.ctr_drbg) From afd80aaa534582ccd40e41b633918c4fb758c27d Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Fri, 2 Aug 2024 14:16:31 +0200 Subject: [PATCH 21/42] fix: exhaustive case --- webrtc/dtls/dtls_connection.nim | 2 ++ 1 file changed, 2 insertions(+) diff --git a/webrtc/dtls/dtls_connection.nim b/webrtc/dtls/dtls_connection.nim index 99cbad6..d435fa8 100644 --- a/webrtc/dtls/dtls_connection.nim +++ b/webrtc/dtls/dtls_connection.nim @@ -197,6 +197,8 @@ proc dtlsHandshake*( mb_ssl_set_client_transport_id(self.ctx.ssl, self.raddr.address_v4) of AddressFamily.IPv6: mb_ssl_set_client_transport_id(self.ctx.ssl, self.raddr.address_v6) + else: + doAssert(false, "Should never happen") let (data, _) = await self.conn.read() self.dataRecv = data self.sendFuture = nil From 45cc27282731e81aff34b63b7334ca4d7a0bb50f Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Fri, 2 Aug 2024 14:33:13 +0200 Subject: [PATCH 22/42] fix: do not create dtlsConn if the address family is not IP --- webrtc/dtls/dtls_transport.nim | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/webrtc/dtls/dtls_transport.nim b/webrtc/dtls/dtls_transport.nim index fad0710..ddcea2e 100644 --- a/webrtc/dtls/dtls_transport.nim +++ b/webrtc/dtls/dtls_transport.nim @@ -85,12 +85,14 @@ proc cleanupDtlsConn(self: Dtls, conn: DtlsConn) {.async: (raises: [CancelledErr proc accept*(self: Dtls): Future[DtlsConn] {.async: (raises: [CancelledError, WebRtcError]).} = ## Accept a Dtls Connection ## - var res = DtlsConn.new(await self.transport.accept(), self.laddr) - res.acceptInit(self.ctr_drbg, self.serverPrivKey, self.serverCert, self.localCert) + var res: DtlsConn while true: - if res.raddr.family == AddressFamily.IPv4 or res.raddr.family == AddressFamily.IPv6: + let stunConn = await self.transport.accept() + if stunConn.raddr.family == AddressFamily.IPv4 or stunConn.raddr.family == AddressFamily.IPv6: try: + res = DtlsConn.new(stunConn, self.laddr) + res.acceptInit(self.ctr_drbg, self.serverPrivKey, self.serverCert, self.localCert) self.connections[res.raddr] = res await res.dtlsHandshake(true) asyncSpawn self.cleanupDtlsConn(res) @@ -99,7 +101,6 @@ proc accept*(self: Dtls): Future[DtlsConn] {.async: (raises: [CancelledError, We trace "Handshake fails, try accept another connection", remoteAddress = res.raddr, error = exc.msg self.connections.del(res.raddr) - res = DtlsConn.new(await self.transport.accept(), self.laddr) return res proc connect*( From 7cf94239fdd9447370b0c73ff05ecbfb7b9074b0 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Fri, 2 Aug 2024 14:52:03 +0200 Subject: [PATCH 23/42] chore: remove entropy from MbedTLSCtx --- webrtc/dtls/dtls_connection.nim | 3 --- 1 file changed, 3 deletions(-) diff --git a/webrtc/dtls/dtls_connection.nim b/webrtc/dtls/dtls_connection.nim index d435fa8..35f3da3 100644 --- a/webrtc/dtls/dtls_connection.nim +++ b/webrtc/dtls/dtls_connection.nim @@ -16,7 +16,6 @@ import mbedtls/ssl_cookie import mbedtls/ssl_cache import mbedtls/pk import mbedtls/md -import mbedtls/entropy import mbedtls/ctr_drbg import mbedtls/rsa import mbedtls/x509 @@ -38,9 +37,7 @@ type timer: mbedtls_timing_delay_context pkey: mbedtls_pk_context srvcert: mbedtls_x509_crt - ctr_drbg: mbedtls_ctr_drbg_context - entropy: mbedtls_entropy_context DtlsConn* = ref object # DtlsConn is a Dtls connection receiving and sending data using From 59f76a023094eab12d7b5ed73fbf8a1f458cecc5 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Wed, 7 Aug 2024 11:23:52 +0200 Subject: [PATCH 24/42] chore: remove asyncspawn of cleanupdtlsconn --- webrtc/dtls/dtls_transport.nim | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/webrtc/dtls/dtls_transport.nim b/webrtc/dtls/dtls_transport.nim index ddcea2e..1580305 100644 --- a/webrtc/dtls/dtls_transport.nim +++ b/webrtc/dtls/dtls_transport.nim @@ -35,8 +35,11 @@ logScope: # used by Mbed-TLS which cannot be async. type + DtlsConnAndCleanup = object + connection: DtlsConn + cleanup: Future[void].Raising([]) Dtls* = ref object of RootObj - connections: Table[TransportAddress, DtlsConn] + connections: Table[TransportAddress, DtlsConnAndCleanup] transport: Stun laddr*: TransportAddress started: bool @@ -49,7 +52,7 @@ type proc new*(T: type Dtls, transport: Stun): T = var self = T( - connections: initTable[TransportAddress, DtlsConn](), + connections: initTable[TransportAddress, DtlsConnAndCleanup](), transport: transport, laddr: transport.laddr, started: true @@ -70,16 +73,21 @@ proc stop*(self: Dtls) {.async: (raises: [CancelledError, WebRtcError]).} = warn "Already stopped" return - await allFutures(toSeq(self.connections.values()).mapIt(it.close())) + await allFutures(toSeq(self.connections.values()).mapIt(it.connection.close())) + await allFutures(toSeq(self.connections.values()).mapIt(it.cleanup)) self.started = false proc localCertificate*(self: Dtls): seq[byte] = ## Local certificate getter self.localCert -proc cleanupDtlsConn(self: Dtls, conn: DtlsConn) {.async: (raises: [CancelledError]).} = +proc cleanupDtlsConn(self: Dtls, conn: DtlsConn) {.async: (raises: []).} = # Waiting for a connection to be closed to remove it from the table - await conn.join() + try: + await conn.join() + except CancelledError as exc: + discard + self.connections.del(conn.raddr) proc accept*(self: Dtls): Future[DtlsConn] {.async: (raises: [CancelledError, WebRtcError]).} = @@ -93,9 +101,11 @@ proc accept*(self: Dtls): Future[DtlsConn] {.async: (raises: [CancelledError, We try: res = DtlsConn.new(stunConn, self.laddr) res.acceptInit(self.ctr_drbg, self.serverPrivKey, self.serverCert, self.localCert) - self.connections[res.raddr] = res await res.dtlsHandshake(true) - asyncSpawn self.cleanupDtlsConn(res) + self.connections[res.raddr] = DtlsConnAndCleanup( + connection: res, + cleanup: self.cleanupDtlsConn(res) + ) break except WebRtcError as exc: trace "Handshake fails, try accept another connection", @@ -115,9 +125,11 @@ proc connect*( res.connectInit(self.ctr_drbg) try: - self.connections[raddr] = res await res.dtlsHandshake(false) - asyncSpawn self.cleanupDtlsConn(res) + self.connections[res.raddr] = DtlsConnAndCleanup( + connection: res, + cleanup: self.cleanupDtlsConn(res) + ) except WebRtcError as exc: trace "Handshake fails", remoteAddress = raddr, error = exc.msg self.connections.del(raddr) From c0769c0175a8e29c3deebce45ff9e36f81a7949a Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Wed, 7 Aug 2024 11:25:51 +0200 Subject: [PATCH 25/42] chore: ctx is no longer public --- webrtc/dtls/dtls_connection.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webrtc/dtls/dtls_connection.nim b/webrtc/dtls/dtls_connection.nim index 35f3da3..9b4dd85 100644 --- a/webrtc/dtls/dtls_connection.nim +++ b/webrtc/dtls/dtls_connection.nim @@ -60,7 +60,7 @@ type remoteCert*: seq[byte] # Mbed-TLS contexts - ctx*: MbedTLSCtx + ctx: MbedTLSCtx proc verify(ctx: pointer, pcert: ptr mbedtls_x509_crt, state: cint, pflags: ptr uint32): cint {.cdecl.} = From 6a894ac6136d336ec443a3f59cb8a8bea456a064 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Wed, 7 Aug 2024 11:46:34 +0200 Subject: [PATCH 26/42] test: add a test with more than 2 nodes --- tests/testdtls.nim | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/testdtls.nim b/tests/testdtls.nim index f772599..0018ada 100644 --- a/tests/testdtls.nim +++ b/tests/testdtls.nim @@ -43,3 +43,36 @@ suite "DTLS": await allFutures(dtls1.stop(), dtls2.stop()) await allFutures(stun1.stop(), stun2.stop()) await allFutures(udp1.close(), udp2.close()) + + asyncTest "Two DTLS nodes connecting to the same DTLS server, sending/receiving data": + let + udp1 = UdpTransport.new(initTAddress("127.0.0.1:4444")) + udp2 = UdpTransport.new(initTAddress("127.0.0.1:5555")) + udp3 = UdpTransport.new(initTAddress("127.0.0.1:6666")) + stun1 = Stun.new(udp1) + stun2 = Stun.new(udp2) + stun3 = Stun.new(udp3) + dtls1 = Dtls.new(stun1) + dtls2 = Dtls.new(stun2) + dtls3 = Dtls.new(stun3) + servConn1Fut = dtls1.accept() + servConn2Fut = dtls1.accept() + clientConn1 = await dtls2.connect(dtls1.laddr) + clientConn2 = await dtls3.connect(dtls1.laddr) + servConn1 = await servConn1Fut + servConn2 = await servConn2Fut + + await servConn1.write(@[1'u8, 2, 3, 4]) + await servConn2.write(@[5'u8, 6, 7, 8]) + await clientConn1.write(@[9'u8, 10, 11, 12]) + await clientConn2.write(@[13'u8, 14, 15, 16]) + check: + (await clientConn1.read()) == @[1'u8, 2, 3, 4] + (await clientConn2.read()) == @[5'u8, 6, 7, 8] + (await servConn1.read()) == @[9'u8, 10, 11, 12] + (await servConn2.read()) == @[13'u8, 14, 15, 16] + await allFutures(servConn1.close(), servConn2.close()) + await allFutures(clientConn1.close(), clientConn2.close()) + await allFutures(dtls1.stop(), dtls2.stop(), dtls3.stop()) + await allFutures(stun1.stop(), stun2.stop(), stun3.stop()) + await allFutures(udp1.close(), udp2.close(), udp3.close()) From f8bb4b8bc86c193b74bb9f89f55cef8c814e418f Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Wed, 7 Aug 2024 11:47:21 +0200 Subject: [PATCH 27/42] chore: started is now useful --- webrtc/dtls/dtls_transport.nim | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/webrtc/dtls/dtls_transport.nim b/webrtc/dtls/dtls_transport.nim index 1580305..87e5058 100644 --- a/webrtc/dtls/dtls_transport.nim +++ b/webrtc/dtls/dtls_transport.nim @@ -93,6 +93,8 @@ proc cleanupDtlsConn(self: Dtls, conn: DtlsConn) {.async: (raises: []).} = proc accept*(self: Dtls): Future[DtlsConn] {.async: (raises: [CancelledError, WebRtcError]).} = ## Accept a Dtls Connection ## + if not self.started: + raise newException(WebRtcError, "DTLS - Dtls transport not started") var res: DtlsConn while true: @@ -119,6 +121,8 @@ proc connect*( ): Future[DtlsConn] {.async: (raises: [CancelledError, WebRtcError]).} = ## Connect to a remote address, creating a Dtls Connection ## + if not self.started: + raise newException(WebRtcError, "DTLS - Dtls transport not started") if raddr.family != AddressFamily.IPv4 and raddr.family != AddressFamily.IPv6: raise newException(WebRtcError, "DTLS - Can only connect to IP address") var res = DtlsConn.new(await self.transport.connect(raddr), self.laddr) From d7a707c8408547a59e591e3c09ac6cd1340acf22 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Wed, 7 Aug 2024 11:49:47 +0200 Subject: [PATCH 28/42] chore: update Dtls.stop --- webrtc/dtls/dtls_transport.nim | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/webrtc/dtls/dtls_transport.nim b/webrtc/dtls/dtls_transport.nim index 87e5058..193b323 100644 --- a/webrtc/dtls/dtls_transport.nim +++ b/webrtc/dtls/dtls_transport.nim @@ -68,14 +68,16 @@ proc new*(T: type Dtls, transport: Stun): T = copyMem(addr self.localCert[0], self.serverCert.raw.p, self.serverCert.raw.len) return self -proc stop*(self: Dtls) {.async: (raises: [CancelledError, WebRtcError]).} = +proc stop*(self: Dtls) {.async: (raises: [CancelledError]).} = + ## Stop the Dtls transport. Stop every opened connections. + ## if not self.started: warn "Already stopped" return + self.started = false await allFutures(toSeq(self.connections.values()).mapIt(it.connection.close())) await allFutures(toSeq(self.connections.values()).mapIt(it.cleanup)) - self.started = false proc localCertificate*(self: Dtls): seq[byte] = ## Local certificate getter From df2737a8e66020a8ddb1c23dd876afc270bca53a Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Wed, 7 Aug 2024 11:53:49 +0200 Subject: [PATCH 29/42] chore: removed unecessary todos --- webrtc/dtls/dtls_connection.nim | 3 --- 1 file changed, 3 deletions(-) diff --git a/webrtc/dtls/dtls_connection.nim b/webrtc/dtls/dtls_connection.nim index 9b4dd85..8bebf1d 100644 --- a/webrtc/dtls/dtls_connection.nim +++ b/webrtc/dtls/dtls_connection.nim @@ -226,7 +226,6 @@ proc close*(self: DtlsConn) {.async: (raises: [CancelledError, WebRtcError]).} = return self.closed = true self.sendFuture = nil - # TODO: proc mbedtls_ssl_close_notify => template mb_ssl_close_notify in nim-mbedtls let x = mbedtls_ssl_close_notify(addr self.ctx.ssl) if not self.sendFuture.isNil(): await self.sendFuture @@ -260,8 +259,6 @@ proc read*(self: DtlsConn): Future[seq[byte]] {.async.} = while true: let (data, _) = await self.conn.read() self.dataRecv = data - # TODO: Find a clear way to use the template `mb_ssl_read` without - # messing up things with exception let length = mbedtls_ssl_read(addr self.ctx.ssl, cast[ptr byte](addr res[0]), res.len().uint) if length == MBEDTLS_ERR_SSL_WANT_READ: continue From 36700efdc473b071a90833827029cf03b2ffbb35 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Wed, 7 Aug 2024 12:00:18 +0200 Subject: [PATCH 30/42] docs: add comments on DtlsConn.read and getters --- webrtc/dtls/dtls_connection.nim | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/webrtc/dtls/dtls_connection.nim b/webrtc/dtls/dtls_connection.nim index 8bebf1d..6df6c59 100644 --- a/webrtc/dtls/dtls_connection.nim +++ b/webrtc/dtls/dtls_connection.nim @@ -252,6 +252,12 @@ proc write*(self: DtlsConn, msg: seq[byte]) {.async.} = raise exc proc read*(self: DtlsConn): Future[seq[byte]] {.async.} = + ## Read the next received message by StunConn. + ## Uncypher it using mbedtls_ssl_read. + ## + # First we read the StunConn using the asynchronous `StunConn.read` procedure. + # When we received data, we stored it in `DtlsConn.dataRecv` and call `dtlsRecv` + # callback using mbedtls in order to decypher it. if self.closed: debug "Try to read on an already closed DtlsConn" return @@ -267,10 +273,12 @@ proc read*(self: DtlsConn): Future[seq[byte]] {.async.} = res.setLen(length) return res -# -- Remote / Local certificate getter -- - proc remoteCertificate*(conn: DtlsConn): seq[byte] = + ## Get the remote certificate + ## conn.remoteCert proc localCertificate*(conn: DtlsConn): seq[byte] = + ## Get the local certificate + ## conn.localCert From ac80c9c517d30985b475bbffcb90bcdf115fc984 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Wed, 7 Aug 2024 12:06:51 +0200 Subject: [PATCH 31/42] feat: add tracker for dtls connection and transport --- webrtc/dtls/dtls_connection.nim | 4 ++++ webrtc/dtls/dtls_transport.nim | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/webrtc/dtls/dtls_connection.nim b/webrtc/dtls/dtls_connection.nim index 6df6c59..384912e 100644 --- a/webrtc/dtls/dtls_connection.nim +++ b/webrtc/dtls/dtls_connection.nim @@ -28,6 +28,8 @@ import mbedtls/timing logScope: topics = "webrtc dtls_conn" +const DtlsConnTracker* = "webrtc.dtls.conn" + type MbedTLSCtx = object ssl: mbedtls_ssl_context @@ -217,6 +219,7 @@ proc dtlsHandshake*( except MbedTLSError as exc: trace "Dtls handshake error", errorMsg = exc.msg raise newException(WebRtcError, "DTLS - Handshake error", exc) + trackCounter(DtlsConnTracker) proc close*(self: DtlsConn) {.async: (raises: [CancelledError, WebRtcError]).} = ## Close a Dtls Connection @@ -229,6 +232,7 @@ proc close*(self: DtlsConn) {.async: (raises: [CancelledError, WebRtcError]).} = let x = mbedtls_ssl_close_notify(addr self.ctx.ssl) if not self.sendFuture.isNil(): await self.sendFuture + untrackCounter(DtlsConnTracker) self.closeEvent.fire() proc write*(self: DtlsConn, msg: seq[byte]) {.async.} = diff --git a/webrtc/dtls/dtls_transport.nim b/webrtc/dtls/dtls_transport.nim index 193b323..4e0fd3a 100644 --- a/webrtc/dtls/dtls_transport.nim +++ b/webrtc/dtls/dtls_transport.nim @@ -34,6 +34,8 @@ logScope: # Multiple things here are unintuitive partly because of the callbacks # used by Mbed-TLS which cannot be async. +const DtlsTransportTracker* = "webrtc.dtls.transport" + type DtlsConnAndCleanup = object connection: DtlsConn @@ -66,6 +68,7 @@ proc new*(T: type Dtls, transport: Stun): T = self.serverCert = self.ctr_drbg.generateCertificate(self.serverPrivKey) self.localCert = newSeq[byte](self.serverCert.raw.len) copyMem(addr self.localCert[0], self.serverCert.raw.p, self.serverCert.raw.len) + trackCounter(DtlsTransportTracker) return self proc stop*(self: Dtls) {.async: (raises: [CancelledError]).} = @@ -78,6 +81,7 @@ proc stop*(self: Dtls) {.async: (raises: [CancelledError]).} = self.started = false await allFutures(toSeq(self.connections.values()).mapIt(it.connection.close())) await allFutures(toSeq(self.connections.values()).mapIt(it.cleanup)) + untrackCounter(DtlsTransportTracker) proc localCertificate*(self: Dtls): seq[byte] = ## Local certificate getter From 2c327c58dec9c138512b959b4993d1424bee1795 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Wed, 7 Aug 2024 12:07:31 +0200 Subject: [PATCH 32/42] chore: privatize local and remote certificate --- webrtc/dtls/dtls_connection.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webrtc/dtls/dtls_connection.nim b/webrtc/dtls/dtls_connection.nim index 384912e..313f27f 100644 --- a/webrtc/dtls/dtls_connection.nim +++ b/webrtc/dtls/dtls_connection.nim @@ -58,8 +58,8 @@ type # Local and Remote certificate, needed by wrapped protocol DataChannel # and by libp2p - localCert*: seq[byte] - remoteCert*: seq[byte] + localCert: seq[byte] + remoteCert: seq[byte] # Mbed-TLS contexts ctx: MbedTLSCtx From 6975f7659e816dda89a7a6088740892c51fa18e2 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Fri, 9 Aug 2024 13:49:14 +0200 Subject: [PATCH 33/42] style: use nph --- webrtc/dtls/dtls_connection.nim | 36 +++++++++----------- webrtc/dtls/dtls_transport.nim | 35 +++++++++---------- webrtc/dtls/dtls_utils.nim | 60 ++++++++++++++------------------- 3 files changed, 59 insertions(+), 72 deletions(-) diff --git a/webrtc/dtls/dtls_connection.nim b/webrtc/dtls/dtls_connection.nim index 313f27f..8f21c20 100644 --- a/webrtc/dtls/dtls_connection.nim +++ b/webrtc/dtls/dtls_connection.nim @@ -64,8 +64,9 @@ type # Mbed-TLS contexts ctx: MbedTLSCtx -proc verify(ctx: pointer, pcert: ptr mbedtls_x509_crt, - state: cint, pflags: ptr uint32): cint {.cdecl.} = +proc verify( + ctx: pointer, pcert: ptr mbedtls_x509_crt, state: cint, pflags: ptr uint32 +): cint {.cdecl.} = # verify is the procedure called by mbedtls when receiving the remote # certificate. It's usually used to verify the validity of the certificate. # We use this procedure to store the remote certificate as it's mandatory @@ -128,7 +129,7 @@ proc acceptInit*( ctr_drbg: mbedtls_ctr_drbg_context, pkey: mbedtls_pk_context, srvcert: mbedtls_x509_crt, - localCert: seq[byte] + localCert: seq[byte], ) = try: self.ctx.ctr_drbg = ctr_drbg @@ -140,10 +141,8 @@ proc acceptInit*( mb_ssl_cookie_init(self.ctx.cookie) mb_ssl_cache_init(self.ctx.cache) mb_ssl_config_defaults( - self.ctx.config, - MBEDTLS_SSL_IS_SERVER, - MBEDTLS_SSL_TRANSPORT_DATAGRAM, - MBEDTLS_SSL_PRESET_DEFAULT + self.ctx.config, MBEDTLS_SSL_IS_SERVER, MBEDTLS_SSL_TRANSPORT_DATAGRAM, + MBEDTLS_SSL_PRESET_DEFAULT, ) mb_ssl_conf_own_cert(self.ctx.config, self.ctx.srvcert, self.ctx.pkey) mb_ssl_cookie_setup(self.ctx.cookie, mbedtls_ctr_drbg_random, self.ctx.ctr_drbg) @@ -154,10 +153,7 @@ proc acceptInit*( except MbedTLSError as exc: raise newException(WebRtcError, "DTLS - Accept initialization: " & exc.msg, exc) -proc connectInit*( - self: DtlsConn, - ctr_drbg: mbedtls_ctr_drbg_context -) = +proc connectInit*(self: DtlsConn, ctr_drbg: mbedtls_ctr_drbg_context) = try: self.ctx.ctr_drbg = ctr_drbg self.ctx.pkey = self.ctx.ctr_drbg.generateKey() @@ -167,10 +163,8 @@ proc connectInit*( self.dtlsConnInit() mb_ssl_config_defaults( - self.ctx.config, - MBEDTLS_SSL_IS_CLIENT, - MBEDTLS_SSL_TRANSPORT_DATAGRAM, - MBEDTLS_SSL_PRESET_DEFAULT + self.ctx.config, MBEDTLS_SSL_IS_CLIENT, MBEDTLS_SSL_TRANSPORT_DATAGRAM, + MBEDTLS_SSL_PRESET_DEFAULT, ) mb_ssl_setup(self.ctx.ssl, self.ctx.config) mb_ssl_conf_authmode(self.ctx.config, MBEDTLS_SSL_VERIFY_OPTIONAL) @@ -183,9 +177,8 @@ proc join*(self: DtlsConn) {.async: (raises: [CancelledError]).} = await self.closeEvent.wait() proc dtlsHandshake*( - self: DtlsConn, - isServer: bool - ) {.async: (raises: [CancelledError, WebRtcError]).} = + self: DtlsConn, isServer: bool +) {.async: (raises: [CancelledError, WebRtcError]).} = var shouldRead = isServer try: while self.ctx.ssl.private_state != MBEDTLS_SSL_HANDSHAKE_OVER: @@ -269,11 +262,14 @@ proc read*(self: DtlsConn): Future[seq[byte]] {.async.} = while true: let (data, _) = await self.conn.read() self.dataRecv = data - let length = mbedtls_ssl_read(addr self.ctx.ssl, cast[ptr byte](addr res[0]), res.len().uint) + let length = + mbedtls_ssl_read(addr self.ctx.ssl, cast[ptr byte](addr res[0]), res.len().uint) if length == MBEDTLS_ERR_SSL_WANT_READ: continue if length < 0: - raise newException(WebRtcError, "DTLS - " & $(length.cint.mbedtls_high_level_strerr())) + raise newException( + WebRtcError, "DTLS - " & $(length.cint.mbedtls_high_level_strerr()) + ) res.setLen(length) return res diff --git a/webrtc/dtls/dtls_transport.nim b/webrtc/dtls/dtls_transport.nim index 4e0fd3a..b44b86f 100644 --- a/webrtc/dtls/dtls_transport.nim +++ b/webrtc/dtls/dtls_transport.nim @@ -9,8 +9,8 @@ import deques, tables, sequtils import chronos, chronicles -import ./[dtls_utils, dtls_connection], ../errors, - ../stun/[stun_connection, stun_transport] +import + ./[dtls_utils, dtls_connection], ../errors, ../stun/[stun_connection, stun_transport] import mbedtls/ssl import mbedtls/ssl_cookie @@ -40,6 +40,7 @@ type DtlsConnAndCleanup = object connection: DtlsConn cleanup: Future[void].Raising([]) + Dtls* = ref object of RootObj connections: Table[TransportAddress, DtlsConnAndCleanup] transport: Stun @@ -57,7 +58,7 @@ proc new*(T: type Dtls, transport: Stun): T = connections: initTable[TransportAddress, DtlsConnAndCleanup](), transport: transport, laddr: transport.laddr, - started: true + started: true, ) mb_ctr_drbg_init(self.ctr_drbg) @@ -96,7 +97,9 @@ proc cleanupDtlsConn(self: Dtls, conn: DtlsConn) {.async: (raises: []).} = self.connections.del(conn.raddr) -proc accept*(self: Dtls): Future[DtlsConn] {.async: (raises: [CancelledError, WebRtcError]).} = +proc accept*( + self: Dtls +): Future[DtlsConn] {.async: (raises: [CancelledError, WebRtcError]).} = ## Accept a Dtls Connection ## if not self.started: @@ -105,25 +108,25 @@ proc accept*(self: Dtls): Future[DtlsConn] {.async: (raises: [CancelledError, We while true: let stunConn = await self.transport.accept() - if stunConn.raddr.family == AddressFamily.IPv4 or stunConn.raddr.family == AddressFamily.IPv6: + if stunConn.raddr.family == AddressFamily.IPv4 or + stunConn.raddr.family == AddressFamily.IPv6: try: res = DtlsConn.new(stunConn, self.laddr) - res.acceptInit(self.ctr_drbg, self.serverPrivKey, self.serverCert, self.localCert) - await res.dtlsHandshake(true) - self.connections[res.raddr] = DtlsConnAndCleanup( - connection: res, - cleanup: self.cleanupDtlsConn(res) + res.acceptInit( + self.ctr_drbg, self.serverPrivKey, self.serverCert, self.localCert ) + await res.dtlsHandshake(true) + self.connections[res.raddr] = + DtlsConnAndCleanup(connection: res, cleanup: self.cleanupDtlsConn(res)) break except WebRtcError as exc: trace "Handshake fails, try accept another connection", - remoteAddress = res.raddr, error = exc.msg + remoteAddress = res.raddr, error = exc.msg self.connections.del(res.raddr) return res proc connect*( - self: Dtls, - raddr: TransportAddress + self: Dtls, raddr: TransportAddress ): Future[DtlsConn] {.async: (raises: [CancelledError, WebRtcError]).} = ## Connect to a remote address, creating a Dtls Connection ## @@ -136,10 +139,8 @@ proc connect*( try: await res.dtlsHandshake(false) - self.connections[res.raddr] = DtlsConnAndCleanup( - connection: res, - cleanup: self.cleanupDtlsConn(res) - ) + self.connections[res.raddr] = + DtlsConnAndCleanup(connection: res, cleanup: self.cleanupDtlsConn(res)) except WebRtcError as exc: trace "Handshake fails", remoteAddress = raddr, error = exc.msg self.connections.del(raddr) diff --git a/webrtc/dtls/dtls_utils.nim b/webrtc/dtls/dtls_utils.nim index 45005b4..9d71472 100644 --- a/webrtc/dtls/dtls_utils.nim +++ b/webrtc/dtls/dtls_utils.nim @@ -19,38 +19,27 @@ import mbedtls/md import mbedtls/error # This sequence is used for debugging. -const mb_ssl_states* = @[ - "MBEDTLS_SSL_HELLO_REQUEST", - "MBEDTLS_SSL_CLIENT_HELLO", - "MBEDTLS_SSL_SERVER_HELLO", - "MBEDTLS_SSL_SERVER_CERTIFICATE", - "MBEDTLS_SSL_SERVER_KEY_EXCHANGE", - "MBEDTLS_SSL_CERTIFICATE_REQUEST", - "MBEDTLS_SSL_SERVER_HELLO_DONE", - "MBEDTLS_SSL_CLIENT_CERTIFICATE", - "MBEDTLS_SSL_CLIENT_KEY_EXCHANGE", - "MBEDTLS_SSL_CERTIFICATE_VERIFY", - "MBEDTLS_SSL_CLIENT_CHANGE_CIPHER_SPEC", - "MBEDTLS_SSL_CLIENT_FINISHED", - "MBEDTLS_SSL_SERVER_CHANGE_CIPHER_SPEC", - "MBEDTLS_SSL_SERVER_FINISHED", - "MBEDTLS_SSL_FLUSH_BUFFERS", - "MBEDTLS_SSL_HANDSHAKE_WRAPUP", - "MBEDTLS_SSL_NEW_SESSION_TICKET", - "MBEDTLS_SSL_SERVER_HELLO_VERIFY_REQUEST_SENT", - "MBEDTLS_SSL_HELLO_RETRY_REQUEST", - "MBEDTLS_SSL_ENCRYPTED_EXTENSIONS", - "MBEDTLS_SSL_END_OF_EARLY_DATA", - "MBEDTLS_SSL_CLIENT_CERTIFICATE_VERIFY", - "MBEDTLS_SSL_CLIENT_CCS_AFTER_SERVER_FINISHED", - "MBEDTLS_SSL_CLIENT_CCS_BEFORE_2ND_CLIENT_HELLO", - "MBEDTLS_SSL_SERVER_CCS_AFTER_SERVER_HELLO", - "MBEDTLS_SSL_CLIENT_CCS_AFTER_CLIENT_HELLO", - "MBEDTLS_SSL_SERVER_CCS_AFTER_HELLO_RETRY_REQUEST", - "MBEDTLS_SSL_HANDSHAKE_OVER", - "MBEDTLS_SSL_TLS1_3_NEW_SESSION_TICKET", - "MBEDTLS_SSL_TLS1_3_NEW_SESSION_TICKET_FLUSH" -] +const mb_ssl_states* = + @[ + "MBEDTLS_SSL_HELLO_REQUEST", "MBEDTLS_SSL_CLIENT_HELLO", "MBEDTLS_SSL_SERVER_HELLO", + "MBEDTLS_SSL_SERVER_CERTIFICATE", "MBEDTLS_SSL_SERVER_KEY_EXCHANGE", + "MBEDTLS_SSL_CERTIFICATE_REQUEST", "MBEDTLS_SSL_SERVER_HELLO_DONE", + "MBEDTLS_SSL_CLIENT_CERTIFICATE", "MBEDTLS_SSL_CLIENT_KEY_EXCHANGE", + "MBEDTLS_SSL_CERTIFICATE_VERIFY", "MBEDTLS_SSL_CLIENT_CHANGE_CIPHER_SPEC", + "MBEDTLS_SSL_CLIENT_FINISHED", "MBEDTLS_SSL_SERVER_CHANGE_CIPHER_SPEC", + "MBEDTLS_SSL_SERVER_FINISHED", "MBEDTLS_SSL_FLUSH_BUFFERS", + "MBEDTLS_SSL_HANDSHAKE_WRAPUP", "MBEDTLS_SSL_NEW_SESSION_TICKET", + "MBEDTLS_SSL_SERVER_HELLO_VERIFY_REQUEST_SENT", "MBEDTLS_SSL_HELLO_RETRY_REQUEST", + "MBEDTLS_SSL_ENCRYPTED_EXTENSIONS", "MBEDTLS_SSL_END_OF_EARLY_DATA", + "MBEDTLS_SSL_CLIENT_CERTIFICATE_VERIFY", + "MBEDTLS_SSL_CLIENT_CCS_AFTER_SERVER_FINISHED", + "MBEDTLS_SSL_CLIENT_CCS_BEFORE_2ND_CLIENT_HELLO", + "MBEDTLS_SSL_SERVER_CCS_AFTER_SERVER_HELLO", + "MBEDTLS_SSL_CLIENT_CCS_AFTER_CLIENT_HELLO", + "MBEDTLS_SSL_SERVER_CCS_AFTER_HELLO_RETRY_REQUEST", "MBEDTLS_SSL_HANDSHAKE_OVER", + "MBEDTLS_SSL_TLS1_3_NEW_SESSION_TICKET", + "MBEDTLS_SSL_TLS1_3_NEW_SESSION_TICKET_FLUSH", + ] template generateKey*(random: mbedtls_ctr_drbg_context): mbedtls_pk_context = var res: mbedtls_pk_context @@ -60,8 +49,9 @@ template generateKey*(random: mbedtls_ctr_drbg_context): mbedtls_pk_context = let x = mb_pk_rsa(res) res -template generateCertificate*(random: mbedtls_ctr_drbg_context, - issuer_key: mbedtls_pk_context): mbedtls_x509_crt = +template generateCertificate*( + random: mbedtls_ctr_drbg_context, issuer_key: mbedtls_pk_context +): mbedtls_x509_crt = let name = "C=FR,O=Status,CN=webrtc" time_format = @@ -75,7 +65,7 @@ template generateCertificate*(random: mbedtls_ctr_drbg_context, var write_cert: mbedtls_x509write_cert var serial_mpi: mbedtls_mpi mb_x509write_crt_init(write_cert) - mb_x509write_crt_set_md_alg(write_cert, MBEDTLS_MD_SHA256); + mb_x509write_crt_set_md_alg(write_cert, MBEDTLS_MD_SHA256) mb_x509write_crt_set_subject_key(write_cert, issuer_key) mb_x509write_crt_set_issuer_key(write_cert, issuer_key) mb_x509write_crt_set_subject_name(write_cert, name) From c89590f0583f3336dd54f0149ae885b19cbe5f1b Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Fri, 9 Aug 2024 14:04:06 +0200 Subject: [PATCH 34/42] fix: remove laddr from dtls_conn (not used) --- tests/testdtls.nim | 21 +++++++++++++-------- webrtc/dtls/dtls_connection.nim | 5 ++--- webrtc/dtls/dtls_transport.nim | 6 +++--- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/tests/testdtls.nim b/tests/testdtls.nim index 0018ada..871289a 100644 --- a/tests/testdtls.nim +++ b/tests/testdtls.nim @@ -22,14 +22,16 @@ suite "DTLS": asyncTest "Two DTLS nodes connecting to each other, then sending/receiving data": let - udp1 = UdpTransport.new(initTAddress("127.0.0.1:4444")) - udp2 = UdpTransport.new(initTAddress("127.0.0.1:5555")) + localAddr1 = initTAddress("127.0.0.1:4444") + localAddr2 = initTAddress("127.0.0.1:5555") + udp1 = UdpTransport.new(localAddr1) + udp2 = UdpTransport.new(localAddr2) stun1 = Stun.new(udp1) stun2 = Stun.new(udp2) dtls1 = Dtls.new(stun1) dtls2 = Dtls.new(stun2) conn1Fut = dtls1.accept() - conn2 = await dtls2.connect(dtls1.laddr) + conn2 = await dtls2.connect(localAddr1) conn1 = await conn1Fut await conn1.write(@[1'u8, 2, 3, 4]) @@ -46,9 +48,12 @@ suite "DTLS": asyncTest "Two DTLS nodes connecting to the same DTLS server, sending/receiving data": let - udp1 = UdpTransport.new(initTAddress("127.0.0.1:4444")) - udp2 = UdpTransport.new(initTAddress("127.0.0.1:5555")) - udp3 = UdpTransport.new(initTAddress("127.0.0.1:6666")) + localAddr1 = initTAddress("127.0.0.1:4444") + localAddr2 = initTAddress("127.0.0.1:5555") + localAddr3 = initTAddress("127.0.0.1:6666") + udp1 = UdpTransport.new(localAddr1) + udp2 = UdpTransport.new(localAddr2) + udp3 = UdpTransport.new(localAddr3) stun1 = Stun.new(udp1) stun2 = Stun.new(udp2) stun3 = Stun.new(udp3) @@ -57,8 +62,8 @@ suite "DTLS": dtls3 = Dtls.new(stun3) servConn1Fut = dtls1.accept() servConn2Fut = dtls1.accept() - clientConn1 = await dtls2.connect(dtls1.laddr) - clientConn2 = await dtls3.connect(dtls1.laddr) + clientConn1 = await dtls2.connect(localAddr1) + clientConn2 = await dtls3.connect(localAddr1) servConn1 = await servConn1Fut servConn2 = await servConn2Fut diff --git a/webrtc/dtls/dtls_connection.nim b/webrtc/dtls/dtls_connection.nim index 8f21c20..2e3251a 100644 --- a/webrtc/dtls/dtls_connection.nim +++ b/webrtc/dtls/dtls_connection.nim @@ -45,7 +45,6 @@ type # DtlsConn is a Dtls connection receiving and sending data using # the underlying Stun Connection conn*: StunConn # The wrapper protocol Stun Connection - laddr: TransportAddress # Local address raddr*: TransportAddress # Remote address dataRecv: seq[byte] # data received which will be read by SCTP sendFuture: Future[void].Raising([CancelledError, WebRtcError]) @@ -105,10 +104,10 @@ proc dtlsRecv(ctx: pointer, buf: ptr byte, len: uint): cint {.cdecl.} = self.dataRecv = @[] trace "dtls receive", len, result -proc new*(T: type DtlsConn, conn: StunConn, laddr: TransportAddress): T = +proc new*(T: type DtlsConn, conn: StunConn): T = ## Initialize a Dtls Connection ## - var self = T(conn: conn, laddr: laddr) + var self = T(conn: conn) self.raddr = conn.raddr self.closed = false self.closeEvent = newAsyncEvent() diff --git a/webrtc/dtls/dtls_transport.nim b/webrtc/dtls/dtls_transport.nim index b44b86f..e47c9e6 100644 --- a/webrtc/dtls/dtls_transport.nim +++ b/webrtc/dtls/dtls_transport.nim @@ -44,7 +44,7 @@ type Dtls* = ref object of RootObj connections: Table[TransportAddress, DtlsConnAndCleanup] transport: Stun - laddr*: TransportAddress + laddr: TransportAddress started: bool ctr_drbg: mbedtls_ctr_drbg_context entropy: mbedtls_entropy_context @@ -111,7 +111,7 @@ proc accept*( if stunConn.raddr.family == AddressFamily.IPv4 or stunConn.raddr.family == AddressFamily.IPv6: try: - res = DtlsConn.new(stunConn, self.laddr) + res = DtlsConn.new(stunConn) res.acceptInit( self.ctr_drbg, self.serverPrivKey, self.serverCert, self.localCert ) @@ -134,7 +134,7 @@ proc connect*( raise newException(WebRtcError, "DTLS - Dtls transport not started") if raddr.family != AddressFamily.IPv4 and raddr.family != AddressFamily.IPv6: raise newException(WebRtcError, "DTLS - Can only connect to IP address") - var res = DtlsConn.new(await self.transport.connect(raddr), self.laddr) + var res = DtlsConn.new(await self.transport.connect(raddr)) res.connectInit(self.ctr_drbg) try: From 8f51516f9db1fbac949b2cf204a7683bbd6de8d5 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Fri, 9 Aug 2024 14:05:07 +0200 Subject: [PATCH 35/42] style: sort imports --- webrtc/dtls/dtls_connection.nim | 22 ++++++---------------- webrtc/dtls/dtls_transport.nim | 23 +++++++---------------- webrtc/dtls/dtls_utils.nim | 8 +------- 3 files changed, 14 insertions(+), 39 deletions(-) diff --git a/webrtc/dtls/dtls_connection.nim b/webrtc/dtls/dtls_connection.nim index 2e3251a..a90264a 100644 --- a/webrtc/dtls/dtls_connection.nim +++ b/webrtc/dtls/dtls_connection.nim @@ -8,22 +8,12 @@ # those terms. import chronos, chronicles -import ../errors, ../stun/[stun_connection] -import ./dtls_utils - -import mbedtls/ssl -import mbedtls/ssl_cookie -import mbedtls/ssl_cache -import mbedtls/pk -import mbedtls/md -import mbedtls/ctr_drbg -import mbedtls/rsa -import mbedtls/x509 -import mbedtls/x509_crt -import mbedtls/bignum -import mbedtls/error -import mbedtls/net_sockets -import mbedtls/timing +import + mbedtls/[ + ssl, ssl_cookie, ssl_cache, pk, md, ctr_drbg, rsa, x509, x509_crt, bignum, error, + net_sockets, timing, + ] +import ../errors, ../stun/[stun_connection], ./dtls_utils logScope: topics = "webrtc dtls_conn" diff --git a/webrtc/dtls/dtls_transport.nim b/webrtc/dtls/dtls_transport.nim index e47c9e6..90054e6 100644 --- a/webrtc/dtls/dtls_transport.nim +++ b/webrtc/dtls/dtls_transport.nim @@ -8,25 +8,16 @@ # those terms. import deques, tables, sequtils -import chronos, chronicles +import + chronos, + chronicles, + mbedtls/[ + ssl, ssl_cookie, ssl_cache, pk, md, entropy, ctr_drbg, rsa, x509, x509_crt, bignum, + error, net_sockets, timing, + ] import ./[dtls_utils, dtls_connection], ../errors, ../stun/[stun_connection, stun_transport] -import mbedtls/ssl -import mbedtls/ssl_cookie -import mbedtls/ssl_cache -import mbedtls/pk -import mbedtls/md -import mbedtls/entropy -import mbedtls/ctr_drbg -import mbedtls/rsa -import mbedtls/x509 -import mbedtls/x509_crt -import mbedtls/bignum -import mbedtls/error -import mbedtls/net_sockets -import mbedtls/timing - logScope: topics = "webrtc dtls" diff --git a/webrtc/dtls/dtls_utils.nim b/webrtc/dtls/dtls_utils.nim index 9d71472..734eae9 100644 --- a/webrtc/dtls/dtls_utils.nim +++ b/webrtc/dtls/dtls_utils.nim @@ -10,13 +10,7 @@ import std/times import ../errors -import mbedtls/pk -import mbedtls/rsa -import mbedtls/ctr_drbg -import mbedtls/x509_crt -import mbedtls/bignum -import mbedtls/md -import mbedtls/error +import mbedtls/[pk, rsa, ctr_drbg, x509_crt, bignum, md, error] # This sequence is used for debugging. const mb_ssl_states* = From 54f45234db69068905c9dc6b99e5c08243b5e9c4 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Fri, 9 Aug 2024 14:20:58 +0200 Subject: [PATCH 36/42] chore: clean Dtls.stop --- webrtc/dtls/dtls_transport.nim | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/webrtc/dtls/dtls_transport.nim b/webrtc/dtls/dtls_transport.nim index 90054e6..94ff01b 100644 --- a/webrtc/dtls/dtls_transport.nim +++ b/webrtc/dtls/dtls_transport.nim @@ -71,8 +71,11 @@ proc stop*(self: Dtls) {.async: (raises: [CancelledError]).} = return self.started = false - await allFutures(toSeq(self.connections.values()).mapIt(it.connection.close())) - await allFutures(toSeq(self.connections.values()).mapIt(it.cleanup)) + let + allCloses = toSeq(self.connections.values()).mapIt(it.connection.close()) + allCleanup = toSeq(self.connections.values()).mapIt(it.cleanup) + await noCancel allFutures(allCloses) + await noCancel allFutures(allCleanup) untrackCounter(DtlsTransportTracker) proc localCertificate*(self: Dtls): seq[byte] = From ab02a689a245035da23116c899bb0911fb6a1f9e Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Fri, 9 Aug 2024 14:25:13 +0200 Subject: [PATCH 37/42] fix: remote address is no longer exposed --- webrtc/dtls/dtls_connection.nim | 7 ++++++- webrtc/dtls/dtls_transport.nim | 21 ++++++++++++--------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/webrtc/dtls/dtls_connection.nim b/webrtc/dtls/dtls_connection.nim index a90264a..936095f 100644 --- a/webrtc/dtls/dtls_connection.nim +++ b/webrtc/dtls/dtls_connection.nim @@ -35,7 +35,7 @@ type # DtlsConn is a Dtls connection receiving and sending data using # the underlying Stun Connection conn*: StunConn # The wrapper protocol Stun Connection - raddr*: TransportAddress # Remote address + raddr: TransportAddress # Remote address dataRecv: seq[byte] # data received which will be read by SCTP sendFuture: Future[void].Raising([CancelledError, WebRtcError]) # This future is set by synchronous Mbed-TLS callbacks and waited, if set, once @@ -271,3 +271,8 @@ proc localCertificate*(conn: DtlsConn): seq[byte] = ## Get the local certificate ## conn.localCert + +proc remoteAddress*(conn: DtlsConn): TransportAddress = + ## Get the remote address + ## + conn.raddr diff --git a/webrtc/dtls/dtls_transport.nim b/webrtc/dtls/dtls_transport.nim index 94ff01b..2152fe4 100644 --- a/webrtc/dtls/dtls_transport.nim +++ b/webrtc/dtls/dtls_transport.nim @@ -82,6 +82,9 @@ proc localCertificate*(self: Dtls): seq[byte] = ## Local certificate getter self.localCert +proc localAddress*(self: Dtls): TransportAddress = + self.laddr + proc cleanupDtlsConn(self: Dtls, conn: DtlsConn) {.async: (raises: []).} = # Waiting for a connection to be closed to remove it from the table try: @@ -101,22 +104,22 @@ proc accept*( var res: DtlsConn while true: - let stunConn = await self.transport.accept() - if stunConn.raddr.family == AddressFamily.IPv4 or - stunConn.raddr.family == AddressFamily.IPv6: + let + stunConn = await self.transport.accept() + raddr = stunConn.raddr + if raddr.family == AddressFamily.IPv4 or raddr.family == AddressFamily.IPv6: try: res = DtlsConn.new(stunConn) res.acceptInit( self.ctr_drbg, self.serverPrivKey, self.serverCert, self.localCert ) await res.dtlsHandshake(true) - self.connections[res.raddr] = + self.connections[raddr] = DtlsConnAndCleanup(connection: res, cleanup: self.cleanupDtlsConn(res)) break except WebRtcError as exc: - trace "Handshake fails, try accept another connection", - remoteAddress = res.raddr, error = exc.msg - self.connections.del(res.raddr) + trace "Handshake fails, try accept another connection", raddr, error = exc.msg + self.connections.del(raddr) return res proc connect*( @@ -133,10 +136,10 @@ proc connect*( try: await res.dtlsHandshake(false) - self.connections[res.raddr] = + self.connections[raddr] = DtlsConnAndCleanup(connection: res, cleanup: self.cleanupDtlsConn(res)) except WebRtcError as exc: - trace "Handshake fails", remoteAddress = raddr, error = exc.msg + trace "Handshake fails", raddr, error = exc.msg self.connections.del(raddr) raise exc From c5681eda1ee58fedfb1d73a898b1f1b2118c90c3 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Fri, 9 Aug 2024 14:50:32 +0200 Subject: [PATCH 38/42] fix: raddr change oversight --- webrtc/dtls/dtls_transport.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webrtc/dtls/dtls_transport.nim b/webrtc/dtls/dtls_transport.nim index 2152fe4..273234b 100644 --- a/webrtc/dtls/dtls_transport.nim +++ b/webrtc/dtls/dtls_transport.nim @@ -92,7 +92,7 @@ proc cleanupDtlsConn(self: Dtls, conn: DtlsConn) {.async: (raises: []).} = except CancelledError as exc: discard - self.connections.del(conn.raddr) + self.connections.del(conn.remoteAddress()) proc accept*( self: Dtls From a8692af5b56b6c79c5e3fa3ff249f5b0d704c5ae Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Fri, 9 Aug 2024 17:58:04 +0200 Subject: [PATCH 39/42] chore: change `verify` name --- webrtc/dtls/dtls_connection.nim | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/webrtc/dtls/dtls_connection.nim b/webrtc/dtls/dtls_connection.nim index 936095f..f2a925b 100644 --- a/webrtc/dtls/dtls_connection.nim +++ b/webrtc/dtls/dtls_connection.nim @@ -53,13 +53,14 @@ type # Mbed-TLS contexts ctx: MbedTLSCtx -proc verify( +proc getRemoteCertificateCallback( ctx: pointer, pcert: ptr mbedtls_x509_crt, state: cint, pflags: ptr uint32 ): cint {.cdecl.} = - # verify is the procedure called by mbedtls when receiving the remote - # certificate. It's usually used to verify the validity of the certificate. - # We use this procedure to store the remote certificate as it's mandatory - # to have it for the Prologue of the Noise protocol, aswell as the localCertificate. + # getRemoteCertificateCallback is the procedure called by mbedtls when + # receiving the remote certificate. It's usually used to verify the validity + # of the certificate, we don't do it. We use this procedure to store the remot + # certificate as it's mandatory to have it for the Prologue of the Noise + # protocol, aswell as the localCertificate. var self = cast[DtlsConn](ctx) let cert = pcert[] @@ -110,7 +111,7 @@ proc dtlsConnInit(self: DtlsConn) = mb_ssl_conf_read_timeout(self.ctx.config, 10000) # in milliseconds mb_ssl_conf_ca_chain(self.ctx.config, self.ctx.srvcert.next, nil) mb_ssl_set_timer_cb(self.ctx.ssl, self.ctx.timer) - mb_ssl_set_verify(self.ctx.ssl, verify, self) + mb_ssl_set_verify(self.ctx.ssl, getRemoteCertificateCallback, self) mb_ssl_set_bio(self.ctx.ssl, cast[pointer](self), dtlsSend, dtlsRecv, nil) proc acceptInit*( From e056631af6ff62a5c2a0d28b60a10113a8722e19 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Tue, 13 Aug 2024 14:53:02 +0200 Subject: [PATCH 40/42] chore: changed `sendFuture: Future[void]` into `dataToSend: seq[byte]` --- webrtc/dtls/dtls_connection.nim | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/webrtc/dtls/dtls_connection.nim b/webrtc/dtls/dtls_connection.nim index f2a925b..800fe13 100644 --- a/webrtc/dtls/dtls_connection.nim +++ b/webrtc/dtls/dtls_connection.nim @@ -37,9 +37,9 @@ type conn*: StunConn # The wrapper protocol Stun Connection raddr: TransportAddress # Remote address dataRecv: seq[byte] # data received which will be read by SCTP - sendFuture: Future[void].Raising([CancelledError, WebRtcError]) - # This future is set by synchronous Mbed-TLS callbacks and waited, if set, once - # the synchronous functions ends + dataToSend: seq[byte] + # This sequence is set by synchronous Mbed-TLS `dtlsSend` callbacks + # and sent, if set, once the synchronous functions ends # Close connection management closed: bool @@ -71,14 +71,14 @@ proc getRemoteCertificateCallback( proc dtlsSend(ctx: pointer, buf: ptr byte, len: uint): cint {.cdecl.} = # dtlsSend is the procedure called by mbedtls when data needs to be sent. # As the StunConn's write proc is asynchronous and dtlsSend cannot be async, - # we store the future of this write and await it after the end of the - # function (see write or dtlsHanshake for example). + # we store the message to be sent and it after the end of the function + # (see write or dtlsHanshake for example). var self = cast[DtlsConn](ctx) var toWrite = newSeq[byte](len) if len > 0: copyMem(addr toWrite[0], buf, len) trace "dtls send", len - self.sendFuture = self.conn.write(toWrite) + self.dataToSend = toWrite result = len.cint proc dtlsRecv(ctx: pointer, buf: ptr byte, len: uint): cint {.cdecl.} = @@ -183,10 +183,10 @@ proc dtlsHandshake*( doAssert(false, "Should never happen") let (data, _) = await self.conn.read() self.dataRecv = data - self.sendFuture = nil + self.dataToSend = @[] let res = mb_ssl_handshake_step(self.ctx.ssl) - if not self.sendFuture.isNil(): - await self.sendFuture + if self.dataToSend.len() > 0: + await self.conn.write(self.dataToSend) shouldRead = false if res == MBEDTLS_ERR_SSL_WANT_WRITE: continue @@ -211,10 +211,10 @@ proc close*(self: DtlsConn) {.async: (raises: [CancelledError, WebRtcError]).} = debug "Try to close an already closed DtlsConn" return self.closed = true - self.sendFuture = nil + self.dataToSend = @[] let x = mbedtls_ssl_close_notify(addr self.ctx.ssl) - if not self.sendFuture.isNil(): - await self.sendFuture + if self.dataToSend.len() > 0: + await self.conn.write(self.dataToSend) untrackCounter(DtlsConnTracker) self.closeEvent.fire() @@ -222,17 +222,16 @@ proc write*(self: DtlsConn, msg: seq[byte]) {.async.} = ## Write a message using mbedtls_ssl_write ## # Mbed-TLS will wrap the message properly and call `dtlsSend` callback. - # `dtlsSend` will write the message on the higher Stun connection. + # `dtlsSend` will store the message to be sent on the higher Stun connection. if self.closed: debug "Try to write on an already closed DtlsConn" return var buf = msg try: - self.sendFuture = nil + self.dataToSend = @[] let write = mb_ssl_write(self.ctx.ssl, buf) - if not self.sendFuture.isNil(): - let sendFuture = self.sendFuture - await sendFuture + if self.dataToSend.len() > 0: + await self.conn.write(self.dataToSend) trace "Dtls write", msgLen = msg.len(), actuallyWrote = write except MbedTLSError as exc: trace "Dtls write error", errorMsg = exc.msg From 5d7b4283bfd5ab3db68016bba007345b8fee4751 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Tue, 13 Aug 2024 15:07:37 +0200 Subject: [PATCH 41/42] chore: avoid sequence copy --- webrtc/dtls/dtls_connection.nim | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/webrtc/dtls/dtls_connection.nim b/webrtc/dtls/dtls_connection.nim index 800fe13..a5d352a 100644 --- a/webrtc/dtls/dtls_connection.nim +++ b/webrtc/dtls/dtls_connection.nim @@ -74,11 +74,10 @@ proc dtlsSend(ctx: pointer, buf: ptr byte, len: uint): cint {.cdecl.} = # we store the message to be sent and it after the end of the function # (see write or dtlsHanshake for example). var self = cast[DtlsConn](ctx) - var toWrite = newSeq[byte](len) + self.dataToSend = newSeq[byte](len) if len > 0: - copyMem(addr toWrite[0], buf, len) + copyMem(addr self.dataToSend[0], buf, len) trace "dtls send", len - self.dataToSend = toWrite result = len.cint proc dtlsRecv(ctx: pointer, buf: ptr byte, len: uint): cint {.cdecl.} = @@ -187,6 +186,7 @@ proc dtlsHandshake*( let res = mb_ssl_handshake_step(self.ctx.ssl) if self.dataToSend.len() > 0: await self.conn.write(self.dataToSend) + self.dataToSend = @[] shouldRead = false if res == MBEDTLS_ERR_SSL_WANT_WRITE: continue @@ -215,6 +215,7 @@ proc close*(self: DtlsConn) {.async: (raises: [CancelledError, WebRtcError]).} = let x = mbedtls_ssl_close_notify(addr self.ctx.ssl) if self.dataToSend.len() > 0: await self.conn.write(self.dataToSend) + self.dataToSend = @[] untrackCounter(DtlsConnTracker) self.closeEvent.fire() @@ -232,6 +233,7 @@ proc write*(self: DtlsConn, msg: seq[byte]) {.async.} = let write = mb_ssl_write(self.ctx.ssl, buf) if self.dataToSend.len() > 0: await self.conn.write(self.dataToSend) + self.dataToSend = @[] trace "Dtls write", msgLen = msg.len(), actuallyWrote = write except MbedTLSError as exc: trace "Dtls write error", errorMsg = exc.msg From a90d85f2b47a63e9349a7680b336d4df93ec8bb8 Mon Sep 17 00:00:00 2001 From: Ludovic Chenut Date: Tue, 13 Aug 2024 15:37:06 +0200 Subject: [PATCH 42/42] chore: change assert message --- webrtc/dtls/dtls_connection.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webrtc/dtls/dtls_connection.nim b/webrtc/dtls/dtls_connection.nim index a5d352a..ca342db 100644 --- a/webrtc/dtls/dtls_connection.nim +++ b/webrtc/dtls/dtls_connection.nim @@ -179,7 +179,7 @@ proc dtlsHandshake*( of AddressFamily.IPv6: mb_ssl_set_client_transport_id(self.ctx.ssl, self.raddr.address_v6) else: - doAssert(false, "Should never happen") + raiseAssert("Remote address must be IPv4 or IPv6") let (data, _) = await self.conn.read() self.dataRecv = data self.dataToSend = @[]